From 49277f5c3f3bb5a9ce2ceeb3f02cd7638a8da1e7 Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Mon, 3 Jul 2023 17:43:57 +0200 Subject: [PATCH] Vendor the current version of WebRender This is a step toward upgrading WebRender, which will be upgraded and patched in the `third_party` directory. This change vendors the current private branch of WebRender that we use and adds a `patches` directory which tracks the changes on top of the upstream WebRender commit described by third_party/webrender/patches/head. --- Cargo.lock | 6 - Cargo.toml | 10 +- components/canvas/Cargo.toml | 4 +- components/canvas_traits/Cargo.toml | 2 +- components/compositing/Cargo.toml | 4 +- components/constellation/Cargo.toml | 2 +- components/embedder_traits/Cargo.toml | 2 +- components/gfx/Cargo.toml | 2 +- components/gfx_traits/Cargo.toml | 2 +- components/layout/Cargo.toml | 2 +- components/layout_2020/Cargo.toml | 2 +- components/layout_thread/Cargo.toml | 2 +- components/layout_thread_2020/Cargo.toml | 2 +- components/layout_traits/Cargo.toml | 2 +- components/malloc_size_of/Cargo.toml | 2 +- components/media/Cargo.toml | 2 +- components/msg/Cargo.toml | 2 +- components/net/Cargo.toml | 2 +- components/script/Cargo.toml | 2 +- components/script_traits/Cargo.toml | 2 +- components/servo/Cargo.toml | 4 +- components/style_traits/Cargo.toml | 2 +- components/webgpu/Cargo.toml | 2 +- components/webrender_traits/Cargo.toml | 2 +- rustfmt.toml | 1 + third_party/webrender/.gitignore | 27 + third_party/webrender/.taskcluster.yml | 176 + third_party/webrender/Cargo.lock | 2267 +++ third_party/webrender/Cargo.toml | 25 + third_party/webrender/LICENSE | 374 + third_party/webrender/README.md | 53 + .../ci-scripts/docker-image/Dockerfile | 12 + .../ci-scripts/docker-image/setup.sh | 37 + .../webrender/ci-scripts/linux-debug-tests.sh | 36 + .../ci-scripts/linux-release-tests.sh | 22 + .../webrender/ci-scripts/macos-debug-tests.sh | 42 + .../ci-scripts/macos-release-tests.sh | 29 + .../ci-scripts/set-screenresolution.ps1 | 124 + .../webrender/ci-scripts/windows-tests.cmd | 36 + third_party/webrender/debugger/.babelrc | 6 + third_party/webrender/debugger/.gitignore | 11 + third_party/webrender/debugger/README.md | 23 + third_party/webrender/debugger/dist/build.js | 13 + .../webrender/debugger/dist/build.js.map | 1 + third_party/webrender/debugger/index.html | 11 + .../webrender/debugger/package-lock.json | 7606 +++++++ third_party/webrender/debugger/package.json | 36 + third_party/webrender/debugger/src/App.vue | 55 + .../src/components/ClipScrollTreeViewPage.vue | 37 + .../src/components/DocumentViewPage.vue | 37 + .../debugger/src/components/NavBar.vue | 41 + .../debugger/src/components/NavMenu.vue | 33 + .../debugger/src/components/OptionsPage.vue | 162 + .../debugger/src/components/PassViewPage.vue | 37 + .../src/components/RenderTaskViewPage.vue | 37 + .../src/components/ScreenshotPage.vue | 32 + .../debugger/src/components/TreeView.vue | 40 + third_party/webrender/debugger/src/main.js | 14 + .../webrender/debugger/src/store/index.js | 105 + .../webrender/debugger/webpack.config.js | 81 + .../webrender/direct-composition/Cargo.toml | 14 + .../webrender/direct-composition/src/com.rs | 112 + .../webrender/direct-composition/src/egl.rs | 174 + .../webrender/direct-composition/src/lib.rs | 179 + .../webrender/direct-composition/src/main.rs | 11 + .../direct-composition/src/main_windows.rs | 212 + .../compositor-windows/Cargo.toml | 9 + .../compositor-windows/build.rs | 25 + .../compositor-windows/src/lib.cpp | 802 + .../compositor-windows/src/lib.rs | 261 + .../example-compositor/compositor/Cargo.toml | 13 + .../example-compositor/compositor/src/main.rs | 484 + third_party/webrender/examples/Cargo.toml | 71 + third_party/webrender/examples/README.md | 8 + third_party/webrender/examples/alpha_perf.rs | 93 + third_party/webrender/examples/animation.rs | 219 + third_party/webrender/examples/basic.rs | 321 + third_party/webrender/examples/blob.rs | 289 + .../webrender/examples/common/boilerplate.rs | 338 + .../webrender/examples/common/image_helper.rs | 19 + third_party/webrender/examples/document.rs | 149 + .../webrender/examples/frame_output.rs | 238 + third_party/webrender/examples/iframe.rs | 95 + .../webrender/examples/image_resize.rs | 121 + third_party/webrender/examples/multiwindow.rs | 326 + third_party/webrender/examples/scrolling.rs | 231 + .../examples/texture_cache_stress.rs | 322 + third_party/webrender/examples/yuv.rs | 226 + third_party/webrender/glsl-to-cxx/Cargo.toml | 11 + third_party/webrender/glsl-to-cxx/README.md | 3 + third_party/webrender/glsl-to-cxx/src/hir.rs | 3856 ++++ third_party/webrender/glsl-to-cxx/src/lib.rs | 3706 ++++ third_party/webrender/glsl-to-cxx/src/main.rs | 8 + ...er-to-catch-segfault-in-build-script.patch | 226 + ...646741-Update-gleam-to-0.12.-r-kvark.patch | 174 + ...889.-Update-to-gleam-0.12.1.-r-kvark.patch | 107 + ...ate-core-foundation-core-graphics.-r.patch | 328 + ...56236-Update-to-euclid-0.22.-r-kvark.patch | 9059 +++++++++ ...-Bump-procedural-masquerade-to-0.1.7.patch | 53 + third_party/webrender/patches/head | 1 + third_party/webrender/patches/series | 6 + third_party/webrender/peek-poke/Cargo.toml | 17 + .../webrender/peek-poke/LICENSE-APACHE | 201 + third_party/webrender/peek-poke/LICENSE-MIT | 44 + third_party/webrender/peek-poke/README.md | 54 + .../peek-poke/peek-poke-derive/Cargo.toml | 19 + .../peek-poke/peek-poke-derive/LICENSE-APACHE | 201 + .../peek-poke/peek-poke-derive/LICENSE-MIT | 44 + .../peek-poke/peek-poke-derive/README.md | 54 + .../peek-poke/peek-poke-derive/src/lib.rs | 266 + third_party/webrender/peek-poke/src/euclid.rs | 170 + third_party/webrender/peek-poke/src/lib.rs | 427 + .../webrender/peek-poke/src/slice_ext.rs | 19 + .../webrender/peek-poke/src/vec_ext.rs | 26 + .../webrender/peek-poke/tests/max_size.rs | 117 + .../webrender/peek-poke/tests/round_trip.rs | 275 + third_party/webrender/rustfmt.toml | 6 + third_party/webrender/servo-tidy.toml | 39 + third_party/webrender/swgl/Cargo.toml | 15 + third_party/webrender/swgl/README.md | 4 + third_party/webrender/swgl/build.rs | 150 + third_party/webrender/swgl/src/gl.cc | 4015 ++++ third_party/webrender/swgl/src/gl_defs.h | 176 + third_party/webrender/swgl/src/glsl.h | 3240 +++ third_party/webrender/swgl/src/lib.rs | 12 + third_party/webrender/swgl/src/program.h | 164 + third_party/webrender/swgl/src/swgl_fns.rs | 2253 +++ third_party/webrender/swgl/src/texture.h | 127 + third_party/webrender/swgl/src/vector_type.h | 478 + third_party/webrender/tileview/Cargo.toml | 15 + third_party/webrender/tileview/src/main.rs | 721 + .../webrender/tileview/src/tilecache.js | 187 + .../webrender/tileview/src/tilecache_base.css | 109 + third_party/webrender/webrender/Cargo.toml | 77 + third_party/webrender/webrender/backtrace.rs | 103 + third_party/webrender/webrender/build.rs | 317 + .../webrender/doc/CLIPPING_AND_POSITIONING.md | 150 + third_party/webrender/webrender/doc/blob.md | 43 + .../webrender/webrender/doc/swizzling.md | 31 + .../webrender/webrender/doc/text-rendering.md | 720 + .../webrender/webrender/res/Proggy.ttf | Bin 0 -> 5284 bytes .../webrender/webrender/res/area-lut.tga | Bin 0 -> 65580 bytes third_party/webrender/webrender/res/base.glsl | 53 + .../webrender/webrender/res/brush.glsl | 317 + .../webrender/webrender/res/brush_blend.glsl | 335 + .../webrender/res/brush_conic_gradient.glsl | 147 + .../webrender/webrender/res/brush_image.glsl | 346 + .../webrender/res/brush_linear_gradient.glsl | 137 + .../webrender/res/brush_mix_blend.glsl | 302 + .../webrender/webrender/res/brush_multi.glsl | 296 + .../webrender/res/brush_opacity.glsl | 91 + .../webrender/res/brush_radial_gradient.glsl | 176 + .../webrender/res/brush_solid.frag.h | 15 + .../webrender/webrender/res/brush_solid.glsl | 65 + .../webrender/res/brush_yuv_image.glsl | 113 + .../webrender/webrender/res/clip_shared.glsl | 97 + .../webrender/webrender/res/composite.glsl | 144 + .../webrender/webrender/res/cs_blur.glsl | 164 + .../webrender/res/cs_border_segment.glsl | 453 + .../webrender/res/cs_border_solid.glsl | 176 + .../webrender/res/cs_clip_box_shadow.glsl | 121 + .../webrender/res/cs_clip_image.glsl | 73 + .../webrender/res/cs_clip_rectangle.glsl | 158 + .../webrender/webrender/res/cs_gradient.glsl | 56 + .../webrender/res/cs_line_decoration.glsl | 163 + .../webrender/webrender/res/cs_scale.glsl | 40 + .../webrender/res/cs_svg_filter.glsl | 591 + .../webrender/webrender/res/debug_color.glsl | 24 + .../webrender/webrender/res/debug_font.glsl | 28 + .../webrender/webrender/res/ellipse.glsl | 93 + .../webrender/webrender/res/gpu_cache.glsl | 138 + .../webrender/res/gpu_cache_update.glsl | 27 + .../webrender/res/pf_vector_cover.glsl | 77 + .../webrender/res/pf_vector_stencil.glsl | 111 + .../webrender/webrender/res/pls_init.glsl | 27 + .../webrender/webrender/res/pls_resolve.glsl | 29 + .../webrender/webrender/res/prim_shared.glsl | 320 + .../webrender/webrender/res/ps_clear.glsl | 25 + .../webrender/res/ps_split_composite.glsl | 118 + .../webrender/webrender/res/ps_text_run.glsl | 335 + third_party/webrender/webrender/res/rect.glsl | 42 + .../webrender/webrender/res/render_task.glsl | 118 + .../webrender/webrender/res/shared.glsl | 241 + .../webrender/webrender/res/shared_other.glsl | 33 + .../webrender/webrender/res/transform.glsl | 124 + third_party/webrender/webrender/res/yuv.glsl | 156 + third_party/webrender/webrender/src/batch.rs | 3439 ++++ third_party/webrender/webrender/src/border.rs | 1491 ++ .../webrender/webrender/src/box_shadow.rs | 277 + .../webrender/webrender/src/capture.rs | 290 + third_party/webrender/webrender/src/clip.rs | 1953 ++ .../webrender/webrender/src/composite.rs | 1007 + .../webrender/webrender/src/debug_colors.rs | 159 + .../webrender/src/debug_font_data.rs | 1914 ++ .../webrender/webrender/src/debug_render.rs | 389 + .../webrender/webrender/src/debug_server.rs | 402 + .../webrender/webrender/src/device/gl.rs | 4046 ++++ .../webrender/webrender/src/device/mod.rs | 9 + .../webrender/src/device/query_gl.rs | 325 + .../webrender/webrender/src/ellipse.rs | 187 + .../webrender/webrender/src/filterdata.rs | 214 + .../webrender/webrender/src/frame_builder.rs | 1097 ++ .../webrender/webrender/src/freelist.rs | 263 + .../webrender/webrender/src/gamma_lut.rs | 414 + .../webrender/webrender/src/glyph_cache.rs | 284 + .../webrender/src/glyph_rasterizer/mod.rs | 1193 ++ .../webrender/webrender/src/gpu_cache.rs | 924 + .../webrender/webrender/src/gpu_types.rs | 832 + .../webrender/webrender/src/hit_test.rs | 620 + third_party/webrender/webrender/src/intern.rs | 377 + .../webrender/webrender/src/internal_types.rs | 602 + third_party/webrender/webrender/src/lib.rs | 226 + .../webrender/webrender/src/lru_cache.rs | 675 + .../webrender/webrender/src/picture.rs | 7185 +++++++ .../webrender/src/platform/macos/font.rs | 854 + .../webrender/src/platform/unix/font.rs | 1038 + .../webrender/src/platform/windows/font.rs | 603 + .../webrender/src/prim_store/backdrop.rs | 97 + .../webrender/src/prim_store/borders.rs | 368 + .../webrender/src/prim_store/gradient.rs | 1007 + .../webrender/src/prim_store/image.rs | 511 + .../webrender/src/prim_store/interned.rs | 14 + .../webrender/src/prim_store/line_dec.rs | 183 + .../webrender/webrender/src/prim_store/mod.rs | 4529 +++++ .../webrender/src/prim_store/picture.rs | 319 + .../webrender/src/prim_store/text_run.rs | 444 + .../webrender/webrender/src/print_tree.rs | 105 + .../webrender/webrender/src/profiler.rs | 1901 ++ .../webrender/webrender/src/render_backend.rs | 2009 ++ .../webrender/webrender/src/render_target.rs | 1091 + .../webrender/webrender/src/render_task.rs | 1564 ++ .../webrender/src/render_task_cache.rs | 267 + .../webrender/src/render_task_graph.rs | 887 + .../webrender/webrender/src/renderer.rs | 7671 ++++++++ .../webrender/webrender/src/resource_cache.rs | 1829 ++ third_party/webrender/webrender/src/scene.rs | 354 + .../webrender/src/scene_builder_thread.rs | 911 + .../webrender/webrender/src/scene_building.rs | 4090 ++++ .../webrender/webrender/src/screen_capture.rs | 493 + .../webrender/webrender/src/segment.rs | 1326 ++ third_party/webrender/webrender/src/shade.rs | 1189 ++ .../webrender/webrender/src/spatial_node.rs | 959 + .../webrender/webrender/src/spatial_tree.rs | 965 + .../webrender/webrender/src/storage.rs | 134 + .../webrender/src/texture_allocator.rs | 270 + .../webrender/webrender/src/texture_cache.rs | 1782 ++ third_party/webrender/webrender/src/util.rs | 1221 ++ .../tests/angle_shader_validation.rs | 62 + .../webrender/webrender/tests/bug_124.html | 5 + .../webrender/webrender/tests/bug_134.html | 29 + .../webrender/webrender/tests/bug_137.html | 25 + .../webrender/webrender/tests/bug_143.html | 11 + .../webrender/webrender/tests/bug_159.html | 2 + .../webrender/webrender/tests/bug_166.html | 10 + .../webrender/webrender/tests/bug_176.html | 1 + .../webrender/webrender/tests/bug_177.html | 23 + .../webrender/webrender/tests/bug_178.html | 24 + .../webrender/webrender/tests/bug_203a.html | 13 + .../webrender/webrender/tests/bug_203b.html | 14 + .../webrender/tests/bug_servo_10136.html | 11 + .../webrender/tests/bug_servo_10164.html | 19 + .../webrender/tests/bug_servo_10307.html | 12 + .../webrender/tests/bug_servo_11358.html | 12 + .../webrender/tests/bug_servo_9983a.html | 17 + .../webrender/tests/color_pattern_1.png | Bin 0 -> 1693 bytes .../webrender/tests/color_pattern_2.png | Bin 0 -> 49144 bytes .../webrender/tests/fixed-position.html | 62 + .../webrender/tests/mix-blend-mode-2.html | 85 + .../webrender/tests/mix-blend-mode.html | 47 + .../webrender/webrender/tests/nav-1.html | 15 + .../webrender/webrender/tests/nav-2.html | 15 + .../webrender/webrender_api/Cargo.toml | 32 + .../webrender/webrender_api/src/api.rs | 2123 ++ .../webrender/webrender_api/src/channel.rs | 131 + .../webrender/webrender_api/src/color.rs | 160 + .../webrender_api/src/display_item.rs | 1589 ++ .../webrender_api/src/display_item_cache.rs | 115 + .../webrender_api/src/display_list.rs | 1969 ++ .../webrender/webrender_api/src/font.rs | 604 + .../webrender_api/src/gradient_builder.rs | 178 + .../webrender/webrender_api/src/image.rs | 595 + .../webrender_api/src/image_tiling.rs | 815 + .../webrender/webrender_api/src/lib.rs | 64 + .../webrender/webrender_api/src/resources.rs | 327 + .../webrender/webrender_api/src/units.rs | 324 + .../webrender/webrender_build/Cargo.toml | 16 + .../webrender/webrender_build/src/lib.rs | 19 + .../webrender/webrender_build/src/shader.rs | 222 + .../webrender_build/src/shader_features.rs | 152 + .../webrender/wr_malloc_size_of/Cargo.toml | 14 + .../wr_malloc_size_of/LICENSE-APACHE | 201 + .../webrender/wr_malloc_size_of/LICENSE-MIT | 23 + .../webrender/wr_malloc_size_of/lib.rs | 435 + third_party/webrender/wrench/.gitignore | 7 + third_party/webrender/wrench/Cargo.toml | 73 + third_party/webrender/wrench/README.md | 35 + third_party/webrender/wrench/android.txt | 93 + .../wrench/benchmarks/aligned-gradient.yaml | 62 + .../wrench/benchmarks/benchmarks.list | 11 + .../wrench/benchmarks/box-shadow-large.yaml | 17 + .../wrench/benchmarks/clip-clear.yaml | 48 + .../wrench/benchmarks/large-blur-radius.yaml | 10 + .../benchmarks/large-boxshadow-ellipse-2.yaml | 15 + .../benchmarks/large-boxshadow-ellipse.yaml | 15 + .../wrench/benchmarks/large-clip-rect.yaml | 33 + .../wrench/benchmarks/many-box-shadows.yaml | 125 + .../wrench/benchmarks/many-images.yaml | 16386 ++++++++++++++++ .../benchmarks/overlapping-text-shadows.yaml | 807 + .../wrench/benchmarks/radial-gradient.yaml | 52 + .../wrench/benchmarks/simple-batching.yaml | 45 + .../wrench/benchmarks/text-rendering.yaml | 260 + .../wrench/benchmarks/transforms-simple.yaml | 44 + .../wrench/benchmarks/unaligned-gradient.yaml | 62 + third_party/webrender/wrench/build.rs | 28 + .../webrender/wrench/examples/animated.anim | 13 + .../webrender/wrench/examples/animated.yaml | 13 + .../wrench/reftests/aa/aa-dist-bug-ref.yaml | 6 + .../wrench/reftests/aa/aa-dist-bug.yaml | 43 + .../reftests/aa/fractional-radii-ref.yaml | 18 + .../wrench/reftests/aa/fractional-radii.yaml | 42 + .../webrender/wrench/reftests/aa/reftest.list | 3 + .../wrench/reftests/aa/rounded-rects-ref.png | Bin 0 -> 10881 bytes .../wrench/reftests/aa/rounded-rects.yaml | 41 + .../reftests/backface/backface-3d-leaf.yaml | 18 + .../backface/backface-both-sides-ref.yaml | 8 + .../backface/backface-both-sides.yaml | 25 + .../backface/backface-double-flip.yaml | 21 + .../reftests/backface/backface-hidden.yaml | 18 + .../reftests/backface/backface-leaf-ref.yaml | 12 + .../reftests/backface/backface-leaf.yaml | 16 + .../backface/backface-picture-ref.yaml | 6 + .../reftests/backface/backface-picture.yaml | 21 + .../reftests/backface/backface-ref.yaml | 6 + .../wrench/reftests/backface/backface-sc.yaml | 17 + .../wrench/reftests/backface/blank.yaml | 2 + .../wrench/reftests/backface/reftest.list | 7 + .../wrench/reftests/blend/blank.yaml | 2 + .../wrench/reftests/blend/darken-ref.yaml | 6 + .../wrench/reftests/blend/darken.yaml | 16 + .../wrench/reftests/blend/difference-ref.yaml | 6 + .../blend/difference-transparent-ref.yaml | 23 + .../blend/difference-transparent.yaml | 22 + .../wrench/reftests/blend/difference.yaml | 16 + .../wrench/reftests/blend/isolated-2-ref.yaml | 21 + .../wrench/reftests/blend/isolated-2.yaml | 25 + .../blend/isolated-premultiplied-2-ref.yaml | 6 + .../blend/isolated-premultiplied-2.yaml | 13 + .../blend/isolated-premultiplied.yaml | 24 + .../wrench/reftests/blend/isolated-ref.yaml | 6 + .../reftests/blend/isolated-with-filter.yaml | 18 + .../wrench/reftests/blend/isolated.yaml | 18 + .../wrench/reftests/blend/large-ref.yaml | 9 + .../wrench/reftests/blend/large.yaml | 13 + .../wrench/reftests/blend/lighten-ref.yaml | 6 + .../wrench/reftests/blend/lighten.yaml | 16 + .../blend/multi-mix-blend-mode-ref.yaml | 14 + .../reftests/blend/multi-mix-blend-mode.yaml | 25 + .../wrench/reftests/blend/multiply-2-ref.yaml | 6 + .../wrench/reftests/blend/multiply-2.yaml | 16 + .../wrench/reftests/blend/multiply-3.yaml | 20 + .../wrench/reftests/blend/multiply-ref.yaml | 6 + .../wrench/reftests/blend/multiply.yaml | 16 + .../wrench/reftests/blend/reftest.list | 24 + .../blend/repeated-difference-ref.yaml | 7 + .../reftests/blend/repeated-difference.yaml | 31 + .../blend/transparent-composite-1-ref.yaml | 8 + .../blend/transparent-composite-1.yaml | 12 + .../blend/transparent-composite-2-ref.yaml | 8 + .../blend/transparent-composite-2.yaml | 16 + .../reftests/blend/transparent-white.png | Bin 0 -> 303 bytes .../wrench/reftests/border/blank.yaml | 3 + .../border/border-clamp-corner-radius.png | Bin 0 -> 12198 bytes .../border/border-clamp-corner-radius.yaml | 24 + .../border/border-dashed-dotted-caching.png | Bin 0 -> 6863 bytes .../border/border-dashed-dotted-caching.yaml | 76 + .../border/border-double-1px-ref.yaml | 17 + .../reftests/border/border-double-1px.yaml | 11 + .../border/border-double-simple-2-ref.yaml | 12 + .../border/border-double-simple-2.yaml | 12 + .../border/border-double-simple-ref.yaml | 21 + .../reftests/border/border-double-simple.yaml | 12 + .../border/border-gradient-nine-patch.png | Bin 0 -> 1051 bytes .../border/border-gradient-nine-patch.yaml | 12 + .../border/border-gradient-simple-ref.yaml | 55 + .../border/border-gradient-simple.yaml | 14 + .../border/border-groove-simple-ref.yaml | 21 + .../reftests/border/border-groove-simple.yaml | 12 + .../border/border-image-crash-ref.yaml | 21 + .../reftests/border/border-image-crash.yaml | 21 + .../border/border-image-empty-slice-ref.png | Bin 0 -> 16010 bytes .../border/border-image-empty-slice.yaml | 50 + .../reftests/border/border-image-fill-ref.png | Bin 0 -> 28383 bytes .../reftests/border/border-image-fill.yaml | 54 + .../reftests/border/border-image-ref.png | Bin 0 -> 4044 bytes .../border/border-image-round-ref.png | Bin 0 -> 20108 bytes .../reftests/border/border-image-round.yaml | 42 + .../reftests/border/border-image-src.png | Bin 0 -> 1247 bytes .../wrench/reftests/border/border-image.yaml | 17 + .../reftests/border/border-invisible-ref.yaml | 9 + .../reftests/border/border-invisible.yaml | 17 + .../border/border-no-bogus-line-ref.png | Bin 0 -> 2253 bytes .../reftests/border/border-no-bogus-line.yaml | 13 + .../reftests/border/border-none-ref.yaml | 9 + .../wrench/reftests/border/border-none.yaml | 12 + .../border/border-overlapping-corner-ref.yaml | 19 + .../border/border-overlapping-corner.yaml | 17 + .../border/border-overlapping-edge-ref.yaml | 9 + .../border/border-overlapping-edge.yaml | 12 + .../border-radial-gradient-nine-patch.png | Bin 0 -> 977 bytes .../border-radial-gradient-nine-patch.yaml | 12 + .../border-radial-gradient-simple-ref.yaml | 54 + .../border/border-radial-gradient-simple.yaml | 14 + .../wrench/reftests/border/border-radii.png | Bin 0 -> 1130 bytes .../wrench/reftests/border/border-radii.yaml | 17 + .../border/border-ridge-simple-ref.yaml | 21 + .../reftests/border/border-ridge-simple.yaml | 12 + .../wrench/reftests/border/border-suite-2.png | Bin 0 -> 48919 bytes .../reftests/border/border-suite-2.yaml | 171 + .../wrench/reftests/border/border-suite-3.png | Bin 0 -> 26021 bytes .../reftests/border/border-suite-3.yaml | 57 + .../wrench/reftests/border/border-suite.png | Bin 0 -> 64147 bytes .../wrench/reftests/border/border-suite.yaml | 351 + .../reftests/border/degenerate-curve.png | Bin 0 -> 20581 bytes .../reftests/border/degenerate-curve.yaml | 258 + .../reftests/border/discontinued-dash.png | Bin 0 -> 1915 bytes .../reftests/border/discontinued-dash.yaml | 12 + .../border/dotted-corner-small-radius.png | Bin 0 -> 8724 bytes .../border/dotted-corner-small-radius.yaml | 11 + .../wrench/reftests/border/green-square.yaml | 9 + .../wrench/reftests/border/max-scale-ref.yaml | 9 + .../wrench/reftests/border/max-scale.yaml | 12 + .../wrench/reftests/border/no-aa.yaml | 20 + .../wrench/reftests/border/overlapping.png | Bin 0 -> 4096 bytes .../wrench/reftests/border/overlapping.yaml | 19 + .../wrench/reftests/border/reftest.list | 34 + .../reftests/border/small-dotted-border.png | Bin 0 -> 1556 bytes .../reftests/border/small-dotted-border.yaml | 19 + .../border/small-inset-outset-notref.yaml | 12 + .../reftests/border/small-inset-outset.yaml | 12 + .../wrench/reftests/border/zero-width.yaml | 12 + .../wrench/reftests/boxshadow/blank.yaml | 2 + ...-shadow-blurred-overlapping-radii-ref.yaml | 20 + .../box-shadow-blurred-overlapping-radii.yaml | 19 + .../boxshadow/box-shadow-border-radii.png | Bin 0 -> 12336 bytes .../boxshadow/box-shadow-border-radii.yaml | 36 + .../reftests/boxshadow/box-shadow-cache.png | Bin 0 -> 35970 bytes .../reftests/boxshadow/box-shadow-cache.yaml | 98 + .../boxshadow/box-shadow-clip-ref.yaml | 40 + .../reftests/boxshadow/box-shadow-clip.yaml | 32 + .../reftests/boxshadow/box-shadow-empty.yaml | 13 + .../boxshadow/box-shadow-huge-radius.png | Bin 0 -> 16290 bytes .../boxshadow/box-shadow-huge-radius.yaml | 128 + .../box-shadow-large-blur-radius-2.png | Bin 0 -> 151753 bytes .../box-shadow-large-blur-radius-2.yaml | 14 + .../box-shadow-large-blur-radius-3.png | Bin 0 -> 60187 bytes .../box-shadow-large-blur-radius-3.yaml | 14 + .../box-shadow-large-blur-radius-ref.yaml | 13 + .../box-shadow-large-blur-radius.yaml | 13 + .../box-shadow-spread-radii-ref.yaml | 21 + .../boxshadow/box-shadow-spread-radii.yaml | 18 + .../reftests/boxshadow/box-shadow-spread.png | Bin 0 -> 7471 bytes .../reftests/boxshadow/box-shadow-spread.yaml | 68 + .../boxshadow/box-shadow-stretch-mode-x.png | Bin 0 -> 16139 bytes .../boxshadow/box-shadow-stretch-mode-x.yaml | 8 + .../boxshadow/box-shadow-stretch-mode-y.png | Bin 0 -> 6245 bytes .../boxshadow/box-shadow-stretch-mode-y.yaml | 9 + .../boxshadow/box-shadow-suite-blur.png | Bin 0 -> 86569 bytes .../boxshadow/box-shadow-suite-blur.yaml | 154 + .../boxshadow/box-shadow-suite-no-blur.png | Bin 0 -> 21181 bytes .../boxshadow/box-shadow-suite-no-blur.yaml | 134 + .../boxshadow/boxshadow-spread-only-ref.png | Bin 0 -> 19279 bytes .../boxshadow/boxshadow-spread-only.yaml | 13 + .../wrench/reftests/boxshadow/inset-alpha.png | Bin 0 -> 1670 bytes .../reftests/boxshadow/inset-alpha.yaml | 14 + .../boxshadow/inset-border-radius-ref.yaml | 16 + .../boxshadow/inset-border-radius.png | Bin 0 -> 3711 bytes .../boxshadow/inset-border-radius.yaml | 11 + .../reftests/boxshadow/inset-downscale.png | Bin 0 -> 13809 bytes .../reftests/boxshadow/inset-downscale.yaml | 10 + .../reftests/boxshadow/inset-empty.yaml | 13 + .../boxshadow/inset-large-offset-ref.png | Bin 0 -> 2472 bytes .../boxshadow/inset-large-offset.yaml | 13 + .../reftests/boxshadow/inset-mask-region.png | Bin 0 -> 23384 bytes .../reftests/boxshadow/inset-mask-region.yaml | 11 + .../reftests/boxshadow/inset-neg-offset.png | Bin 0 -> 3607 bytes .../reftests/boxshadow/inset-neg-offset.yaml | 18 + .../boxshadow/inset-no-blur-radius-ref.png | Bin 0 -> 674 bytes .../boxshadow/inset-no-blur-radius.yaml | 13 + .../reftests/boxshadow/inset-offset.png | Bin 0 -> 3500 bytes .../reftests/boxshadow/inset-offset.yaml | 18 + .../reftests/boxshadow/inset-simple-ref.yaml | 9 + .../reftests/boxshadow/inset-simple.yaml | 13 + .../boxshadow/inset-spread-large-ref.yaml | 9 + .../boxshadow/inset-spread-large.yaml | 12 + .../reftests/boxshadow/inset-spread-ref.yaml | 10 + .../reftests/boxshadow/inset-spread.yaml | 12 + .../wrench/reftests/boxshadow/inset-subpx.png | Bin 0 -> 1810 bytes .../reftests/boxshadow/inset-subpx.yaml | 12 + .../reftests/boxshadow/invalid-ref.yaml | 5 + .../wrench/reftests/boxshadow/invalid.yaml | 12 + .../wrench/reftests/boxshadow/no-stretch.png | Bin 0 -> 3957 bytes .../wrench/reftests/boxshadow/no-stretch.yaml | 21 + .../wrench/reftests/boxshadow/overlap1.png | Bin 0 -> 11085 bytes .../wrench/reftests/boxshadow/overlap1.yaml | 18 + .../wrench/reftests/boxshadow/overlap2.png | Bin 0 -> 8490 bytes .../wrench/reftests/boxshadow/overlap2.yaml | 19 + .../wrench/reftests/boxshadow/reftest.list | 37 + .../reftests/boxshadow/rounding-ref.yaml | 18 + .../wrench/reftests/boxshadow/rounding.yaml | 15 + .../wrench/reftests/boxshadow/scale.png | Bin 0 -> 13527 bytes .../wrench/reftests/boxshadow/scale.yaml | 183 + .../webrender/wrench/reftests/clip/blank.yaml | 2 + .../clip/border-with-rounded-clip.png | Bin 0 -> 1900 bytes .../clip/border-with-rounded-clip.yaml | 21 + .../reftests/clip/clip-3d-transform-ref.yaml | 8 + .../reftests/clip/clip-3d-transform.yaml | 30 + .../clip/clip-45-degree-rotation-ref.png | Bin 0 -> 13358 bytes .../clip/clip-45-degree-rotation.yaml | 32 + .../clip-and-filter-with-rotation-ref.yaml | 18 + .../clip/clip-and-filter-with-rotation.yaml | 35 + .../clip/clip-corner-overlap-ref.yaml | 113 + .../reftests/clip/clip-corner-overlap.yaml | 61 + .../wrench/reftests/clip/clip-ellipse.png | Bin 0 -> 9486 bytes .../wrench/reftests/clip/clip-ellipse.yaml | 63 + .../clip/clip-empty-inner-rect-ref.yaml | 20 + .../reftests/clip/clip-empty-inner-rect.yaml | 28 + .../wrench/reftests/clip/clip-mode.png | Bin 0 -> 2740 bytes .../wrench/reftests/clip/clip-mode.yaml | 17 + .../reftests/clip/clip-out-rotation.yaml | 41 + .../reftests/clip/clip-thin-rotated-ref.yaml | 13 + .../reftests/clip/clip-thin-rotated.yaml | 41 + .../reftests/clip/clipped-occlusion-ref.yaml | 7 + .../reftests/clip/clipped-occlusion.yaml | 23 + .../custom-clip-chain-node-ancestors-ref.yaml | 9 + .../custom-clip-chain-node-ancestors.yaml | 32 + .../reftests/clip/custom-clip-chains-ref.yaml | 17 + .../reftests/clip/custom-clip-chains.yaml | 62 + .../clip/fixed-position-clipping-ref.yaml | 15 + .../clip/fixed-position-clipping.yaml | 49 + ...iframe-nested-in-stacking-context-ref.yaml | 6 + .../iframe-nested-in-stacking-context.yaml | 23 + .../wrench/reftests/clip/reftest.list | 18 + .../segmentation-across-rotation-ref.yaml | 6 + .../clip/segmentation-across-rotation.yaml | 30 + ...with-other-coordinate-system-clip-ref.yaml | 8 + ...tion-with-other-coordinate-system-clip.png | Bin 0 -> 763 bytes ...ion-with-other-coordinate-system-clip.yaml | 47 + .../wrench/reftests/clip/snapping-ref.yaml | 17 + .../wrench/reftests/clip/snapping.yaml | 17 + .../clip/stacking-context-clip-ref.yaml | 32 + .../reftests/clip/stacking-context-clip.yaml | 36 + .../filters/backdrop-filter-basic-ref.yaml | 11 + .../filters/backdrop-filter-basic.yaml | 21 + .../filters/backdrop-filter-perspective.png | Bin 0 -> 60142 bytes .../filters/backdrop-filter-perspective.yaml | 28 + .../wrench/reftests/filters/blank.yaml | 2 + .../wrench/reftests/filters/blend-clipped.png | Bin 0 -> 2102 bytes .../reftests/filters/blend-clipped.yaml | 105 + .../filters/filter-blur-clamping-ref.yaml | 18 + .../filters/filter-blur-clamping.yaml | 30 + .../reftests/filters/filter-blur-huge.yaml | 10 + .../filters/filter-blur-scaled-ref.yaml | 11 + .../filters/filter-blur-scaled-xonly.png | Bin 0 -> 3214 bytes .../filters/filter-blur-scaled-xonly.yaml | 16 + .../reftests/filters/filter-blur-scaled.yaml | 15 + .../wrench/reftests/filters/filter-blur.png | Bin 0 -> 58574 bytes .../wrench/reftests/filters/filter-blur.yaml | 9 + .../filters/filter-brightness-2-ref.yaml | 9 + .../reftests/filters/filter-brightness-2.yaml | 13 + .../filters/filter-brightness-3-ref.yaml | 9 + .../reftests/filters/filter-brightness-3.yaml | 13 + .../filters/filter-brightness-4-ref.yaml | 9 + .../reftests/filters/filter-brightness-4.yaml | 13 + .../filters/filter-brightness-ref.yaml | 13 + .../reftests/filters/filter-brightness.yaml | 17 + .../filters/filter-color-matrix-ref.yaml | 21 + .../reftests/filters/filter-color-matrix.yaml | 53 + .../filter-component-transfer-ref.yaml | 51 + .../filters/filter-component-transfer.yaml | 352 + .../filter-contrast-gray-alpha-1-ref.yaml | 13 + .../filters/filter-contrast-gray-alpha-1.yaml | 13 + .../filter-drop-shadow-blur-clamping-ref.yaml | 18 + .../filter-drop-shadow-blur-clamping.yaml | 30 + .../filters/filter-drop-shadow-clip-2.png | Bin 0 -> 3115 bytes .../filters/filter-drop-shadow-clip-2.yaml | 17 + .../filters/filter-drop-shadow-clip-3.png | Bin 0 -> 37433 bytes .../filters/filter-drop-shadow-clip-3.yaml | 37 + .../filters/filter-drop-shadow-clip.png | Bin 0 -> 12738 bytes .../filters/filter-drop-shadow-clip.yaml | 25 + .../filters/filter-drop-shadow-huge.yaml | 10 + .../filter-drop-shadow-on-viewport-edge.png | Bin 0 -> 12248 bytes .../filter-drop-shadow-on-viewport-edge.yaml | 10 + .../filter-drop-shadow-scaled-ref.yaml | 11 + .../filters/filter-drop-shadow-scaled.yaml | 15 + .../filter-drop-shadow-transform-huge.yaml | 17 + .../reftests/filters/filter-drop-shadow.png | Bin 0 -> 79646 bytes .../reftests/filters/filter-drop-shadow.yaml | 9 + .../filters/filter-grayscale-ref.yaml | 6 + .../reftests/filters/filter-grayscale.yaml | 10 + .../filters/filter-hue-rotate-1-ref.yaml | 21 + .../reftests/filters/filter-hue-rotate-1.yaml | 37 + .../filter-hue-rotate-alpha-1-ref.yaml | 21 + .../filters/filter-hue-rotate-alpha-1.yaml | 37 + .../reftests/filters/filter-invert-2-ref.yaml | 9 + .../reftests/filters/filter-invert-2.yaml | 17 + .../reftests/filters/filter-invert-ref.yaml | 9 + .../reftests/filters/filter-invert.yaml | 13 + .../filters/filter-large-blur-radius.png | Bin 0 -> 115399 bytes .../filters/filter-large-blur-radius.yaml | 10 + .../reftests/filters/filter-long-chain.png | Bin 0 -> 20983 bytes .../reftests/filters/filter-long-chain.yaml | 19 + .../filters/filter-mix-blend-mode-ref.yaml | 6 + .../filters/filter-mix-blend-mode.yaml | 17 + .../filters/filter-mix-blend-scaling-ref.yaml | 11 + .../filters/filter-mix-blend-scaling.yaml | 23 + .../filters/filter-saturate-blue-1-ref.yaml | 13 + .../filters/filter-saturate-blue-1.yaml | 17 + .../filters/filter-saturate-blue-2-ref.yaml | 13 + .../filters/filter-saturate-blue-2.yaml | 17 + .../filters/filter-saturate-blue-3-ref.yaml | 13 + .../filters/filter-saturate-blue-3.yaml | 17 + .../filter-saturate-blue-alpha-1-ref.yaml | 13 + .../filters/filter-saturate-blue-alpha-1.yaml | 17 + .../filters/filter-saturate-green-1-ref.yaml | 13 + .../filters/filter-saturate-green-1.yaml | 17 + .../filters/filter-saturate-green-2-ref.yaml | 13 + .../filters/filter-saturate-green-2.yaml | 17 + .../filters/filter-saturate-green-3-ref.yaml | 13 + .../filters/filter-saturate-green-3.yaml | 17 + .../filter-saturate-green-alpha-1-ref.yaml | 12 + .../filter-saturate-green-alpha-1.yaml | 17 + .../filters/filter-saturate-red-1-ref.yaml | 13 + .../filters/filter-saturate-red-1.yaml | 17 + .../filters/filter-saturate-red-2-ref.yaml | 13 + .../filters/filter-saturate-red-2.yaml | 17 + .../filters/filter-saturate-red-3-ref.yaml | 13 + .../filters/filter-saturate-red-3.yaml | 17 + .../filter-saturate-red-alpha-1-ref.yaml | 12 + .../filters/filter-saturate-red-alpha-1.yaml | 17 + .../reftests/filters/filter-segments-ref.yaml | 18 + .../reftests/filters/filter-segments.yaml | 24 + .../filters/filter-small-blur-radius.png | Bin 0 -> 17466 bytes .../filters/filter-small-blur-radius.yaml | 10 + .../wrench/reftests/filters/firefox.png | Bin 0 -> 45376 bytes .../filters/iframe-dropshadow-ref.yaml | 22 + .../reftests/filters/iframe-dropshadow.yaml | 34 + .../reftests/filters/invisible-ref.yaml | 6 + .../wrench/reftests/filters/invisible.yaml | 27 + .../wrench/reftests/filters/isolated-ref.yaml | 13 + .../wrench/reftests/filters/isolated.yaml | 17 + .../filters/opacity-combined-ref.yaml | 10 + .../reftests/filters/opacity-combined.yaml | 10 + .../reftests/filters/opacity-overlap-ref.yaml | 9 + .../reftests/filters/opacity-overlap.yaml | 16 + .../wrench/reftests/filters/opacity-ref.yaml | 8 + .../wrench/reftests/filters/opacity.yaml | 10 + .../wrench/reftests/filters/reftest.list | 68 + .../reftests/filters/srgb-to-linear-2.yaml | 16 + .../reftests/filters/srgb-to-linear-ref.yaml | 15 + .../reftests/filters/srgb-to-linear.yaml | 16 + .../filters/svg-filter-blend-ref.yaml | 21 + .../reftests/filters/svg-filter-blend.yaml | 83 + .../filters/svg-filter-blur-transforms.png | Bin 0 -> 6159 bytes .../filters/svg-filter-blur-transforms.yaml | 15 + .../reftests/filters/svg-filter-blur.yaml | 13 + .../filters/svg-filter-color-matrix.yaml | 69 + .../svg-filter-component-transfer.yaml | 382 + .../filters/svg-filter-composite-ref.yaml | 73 + .../filters/svg-filter-composite.yaml | 124 + ...vg-filter-drop-shadow-on-viewport-edge.png | Bin 0 -> 12410 bytes ...g-filter-drop-shadow-on-viewport-edge.yaml | 16 + .../svg-filter-drop-shadow-perspective.png | Bin 0 -> 13036 bytes .../svg-filter-drop-shadow-perspective.yaml | 22 + .../svg-filter-drop-shadow-rotate-ref.yaml | 11 + .../svg-filter-drop-shadow-rotate.yaml | 17 + .../filters/svg-filter-drop-shadow.png | Bin 0 -> 79573 bytes .../filters/svg-filter-drop-shadow.yaml | 16 + .../filters/svg-filter-flood-ref.yaml | 10 + .../reftests/filters/svg-filter-flood.yaml | 10 + .../filters/svg-filter-offset-ref.yaml | 11 + .../reftests/filters/svg-filter-offset.yaml | 15 + .../reftests/filters/svg-srgb-to-linear.yaml | 20 + .../conic-angle-wraparound-negative.yaml | 8 + .../gradient/conic-angle-wraparound.yaml | 8 + .../wrench/reftests/gradient/conic-angle.png | Bin 0 -> 24700 bytes .../wrench/reftests/gradient/conic-angle.yaml | 8 + .../reftests/gradient/conic-backdrop-ref.yaml | 11 + .../conic-backdrop-with-spacing-ref.yaml | 13 + .../gradient/conic-backdrop-with-spacing.yaml | 18 + .../reftests/gradient/conic-backdrop.yaml | 16 + .../wrench/reftests/gradient/conic-center.png | Bin 0 -> 3117 bytes .../reftests/gradient/conic-center.yaml | 11 + .../reftests/gradient/conic-color-wheel.png | Bin 0 -> 36018 bytes .../reftests/gradient/conic-color-wheel.yaml | 14 + .../wrench/reftests/gradient/conic-ref.yaml | 18 + .../wrench/reftests/gradient/conic-simple.png | Bin 0 -> 24158 bytes .../reftests/gradient/conic-simple.yaml | 8 + .../wrench/reftests/gradient/conic.yaml | 11 + .../gradient/gradient_cache_5stops.yaml | 13 + .../gradient/gradient_cache_5stops_ref.yaml | 18 + .../gradient_cache_5stops_vertical.yaml | 13 + .../gradient_cache_5stops_vertical_ref.yaml | 18 + .../gradient/gradient_cache_clamp.yaml | 20 + .../gradient/gradient_cache_clamp_ref.yaml | 30 + .../gradient/gradient_cache_hardstop.yaml | 19 + .../gradient_cache_hardstop_clip.yaml | 21 + .../gradient_cache_hardstop_clip_ref.yaml | 28 + .../gradient/gradient_cache_hardstop_ref.yaml | 24 + .../gradient/gradient_cache_repeat.yaml | 119 + .../gradient/gradient_cache_repeat_ref.yaml | 119 + .../gradient/linear-adjust-tile-size-ref.yaml | 9 + .../gradient/linear-adjust-tile-size.yaml | 10 + .../gradient/linear-aligned-border-radius.png | Bin 0 -> 6832 bytes .../linear-aligned-border-radius.yaml | 46 + .../gradient/linear-aligned-clip-ref.yaml | 16 + .../gradient/linear-aligned-clip.yaml | 9 + .../gradient/linear-backdrop-ref.yaml | 11 + .../linear-backdrop-with-spacing-ref.yaml | 14 + .../linear-backdrop-with-spacing.yaml | 18 + .../reftests/gradient/linear-backdrop.yaml | 16 + .../reftests/gradient/linear-clamp-1-ref.yaml | 8 + .../reftests/gradient/linear-clamp-1a.yaml | 8 + .../reftests/gradient/linear-clamp-1b.yaml | 8 + .../reftests/gradient/linear-clamp-2-ref.yaml | 8 + .../reftests/gradient/linear-clamp-2.yaml | 8 + .../reftests/gradient/linear-double.yaml | 13 + .../gradient/linear-hard-stop-ref.png | Bin 0 -> 2026 bytes .../reftests/gradient/linear-hard-stop.yaml | 8 + .../wrench/reftests/gradient/linear-ref.png | Bin 0 -> 3576 bytes .../wrench/reftests/gradient/linear-ref.yaml | 18 + .../reftests/gradient/linear-reverse.yaml | 11 + .../reftests/gradient/linear-stops-ref.png | Bin 0 -> 912 bytes .../reftests/gradient/linear-stops.yaml | 7 + .../wrench/reftests/gradient/linear.yaml | 11 + .../reftests/gradient/norm-conic-1-ref.yaml | 9 + .../reftests/gradient/norm-conic-1.yaml | 9 + .../reftests/gradient/norm-conic-2-ref.yaml | 9 + .../reftests/gradient/norm-conic-2.yaml | 9 + .../reftests/gradient/norm-conic-3-ref.yaml | 8 + .../reftests/gradient/norm-conic-3.yaml | 9 + .../reftests/gradient/norm-conic-4-ref.yaml | 8 + .../reftests/gradient/norm-conic-4.yaml | 9 + .../gradient/norm-conic-degenerate-ref.yaml | 8 + .../gradient/norm-conic-degenerate.yaml | 14 + .../reftests/gradient/norm-linear-1-ref.yaml | 9 + .../reftests/gradient/norm-linear-1.yaml | 9 + .../reftests/gradient/norm-linear-2-ref.yaml | 9 + .../reftests/gradient/norm-linear-2.yaml | 9 + .../reftests/gradient/norm-linear-3-ref.yaml | 8 + .../reftests/gradient/norm-linear-3.yaml | 9 + .../reftests/gradient/norm-linear-4-ref.yaml | 8 + .../reftests/gradient/norm-linear-4.yaml | 9 + .../gradient/norm-linear-degenerate-ref.yaml | 8 + .../gradient/norm-linear-degenerate.yaml | 14 + .../reftests/gradient/norm-radial-1-ref.yaml | 8 + .../reftests/gradient/norm-radial-1.yaml | 8 + .../reftests/gradient/norm-radial-2-ref.yaml | 8 + .../reftests/gradient/norm-radial-2.yaml | 8 + .../reftests/gradient/norm-radial-3-ref.yaml | 8 + .../reftests/gradient/norm-radial-3.yaml | 8 + .../gradient/norm-radial-degenerate-ref.yaml | 8 + .../gradient/norm-radial-degenerate.yaml | 14 + .../gradient/premultiplied-aligned-2.png | Bin 0 -> 10340 bytes .../gradient/premultiplied-aligned-2.yaml | 8 + .../gradient/premultiplied-aligned.png | Bin 0 -> 12560 bytes .../gradient/premultiplied-aligned.yaml | 8 + .../gradient/premultiplied-angle-2.png | Bin 0 -> 19390 bytes .../gradient/premultiplied-angle-2.yaml | 8 + .../reftests/gradient/premultiplied-angle.png | Bin 0 -> 18453 bytes .../gradient/premultiplied-angle.yaml | 8 + .../gradient/premultiplied-conic-2.png | Bin 0 -> 14475 bytes .../gradient/premultiplied-conic-2.yaml | 8 + .../reftests/gradient/premultiplied-conic.png | Bin 0 -> 16782 bytes .../gradient/premultiplied-conic.yaml | 8 + .../gradient/premultiplied-radial-2.png | Bin 0 -> 14593 bytes .../gradient/premultiplied-radial-2.yaml | 8 + .../gradient/premultiplied-radial.png | Bin 0 -> 18857 bytes .../gradient/premultiplied-radial.yaml | 8 + .../gradient/radial-backdrop-ref.yaml | 11 + .../radial-backdrop-with-spacing-ref.yaml | 13 + .../radial-backdrop-with-spacing.yaml | 18 + .../reftests/gradient/radial-backdrop.yaml | 16 + .../reftests/gradient/radial-circle-ref.png | Bin 0 -> 48655 bytes .../reftests/gradient/radial-circle.yaml | 8 + .../reftests/gradient/radial-ellipse-ref.png | Bin 0 -> 39749 bytes .../reftests/gradient/radial-ellipse.yaml | 8 + .../reftests/gradient/radial-zero-size-1.yaml | 8 + .../reftests/gradient/radial-zero-size-2.yaml | 8 + .../reftests/gradient/radial-zero-size-3.yaml | 6 + .../gradient/radial-zero-size-ref.yaml | 8 + .../wrench/reftests/gradient/reftest.list | 106 + .../gradient/repeat-border-radius.png | Bin 0 -> 31964 bytes .../gradient/repeat-border-radius.yaml | 137 + .../gradient/repeat-conic-negative.yaml | 9 + .../reftests/gradient/repeat-conic-ref.yaml | 27 + .../reftests/gradient/repeat-conic.yaml | 9 + .../reftests/gradient/repeat-linear-ref.yaml | 27 + .../gradient/repeat-linear-reverse.yaml | 9 + .../reftests/gradient/repeat-linear.yaml | 9 + .../gradient/repeat-radial-negative.yaml | 9 + .../reftests/gradient/repeat-radial-ref.yaml | 38 + .../reftests/gradient/repeat-radial.yaml | 9 + .../reftests/gradient/tiling-conic-1-ref.yaml | 27 + .../reftests/gradient/tiling-conic-1.yaml | 11 + .../reftests/gradient/tiling-conic-2-ref.yaml | 27 + .../reftests/gradient/tiling-conic-2.yaml | 11 + .../reftests/gradient/tiling-conic-3-ref.yaml | 27 + .../reftests/gradient/tiling-conic-3.yaml | 11 + .../gradient/tiling-linear-1-ref.yaml | 27 + .../reftests/gradient/tiling-linear-1.yaml | 11 + .../gradient/tiling-linear-2-ref.yaml | 27 + .../reftests/gradient/tiling-linear-2.yaml | 11 + .../gradient/tiling-linear-3-ref.yaml | 15 + .../reftests/gradient/tiling-linear-3.yaml | 11 + .../gradient/tiling-radial-1-ref.yaml | 27 + .../reftests/gradient/tiling-radial-1.yaml | 11 + .../gradient/tiling-radial-2-ref.yaml | 27 + .../reftests/gradient/tiling-radial-2.yaml | 11 + .../gradient/tiling-radial-3-ref.yaml | 27 + .../reftests/gradient/tiling-radial-3.yaml | 11 + .../gradient/tiling-radial-4-ref.yaml | 27 + .../reftests/gradient/tiling-radial-4.yaml | 11 + .../wrench/reftests/image/colorrect.png | Bin 0 -> 256 bytes .../wrench/reftests/image/downscale.png | Bin 0 -> 14074 bytes .../wrench/reftests/image/downscale.yaml | 15 + .../wrench/reftests/image/firefox.png | Bin 0 -> 25927 bytes .../wrench/reftests/image/occlusion.png | Bin 0 -> 49427 bytes .../wrench/reftests/image/occlusion.yaml | 15 + .../wrench/reftests/image/reftest.list | 14 + .../wrench/reftests/image/rgb_composite.yaml | 10 + .../reftests/image/rgb_composite_ref.yaml | 9 + .../wrench/reftests/image/segments.png | Bin 0 -> 7182 bytes .../wrench/reftests/image/segments.yaml | 12 + .../wrench/reftests/image/spacex-u.png | Bin 0 -> 81302 bytes .../wrench/reftests/image/spacex-uv.png | Bin 0 -> 202309 bytes .../wrench/reftests/image/spacex-v.png | Bin 0 -> 97851 bytes .../wrench/reftests/image/spacex-y.png | Bin 0 -> 128761 bytes .../wrench/reftests/image/spacex-yuv.png | Bin 0 -> 310131 bytes .../reftests/image/texture-rect-ref.yaml | 15 + .../wrench/reftests/image/texture-rect.yaml | 7 + .../tile-repeat-prim-or-decompose-ref.yaml | 8 + .../image/tile-repeat-prim-or-decompose.yaml | 17 + .../wrench/reftests/image/tile-size-ref.yaml | 14 + .../wrench/reftests/image/tile-size.yaml | 19 + .../reftests/image/tile-with-spacing-ref.yaml | 6 + .../reftests/image/tile-with-spacing.yaml | 12 + .../reftests/image/tiled-clip-chain-ref.yaml | 10 + .../reftests/image/tiled-clip-chain.yaml | 10 + .../image/tiled-complex-clip-ref.yaml | 10 + .../reftests/image/tiled-complex-clip.yaml | 13 + .../wrench/reftests/image/very-big-ref.yaml | 5 + .../image/very-big-tile-size-ref.yaml | 5 + .../reftests/image/very-big-tile-size.yaml | 9 + .../wrench/reftests/image/very-big.yaml | 5 + .../webrender/wrench/reftests/image/yuv.png | Bin 0 -> 621771 bytes .../webrender/wrench/reftests/image/yuv.yaml | 19 + .../invalidation/one-rounded-rect-green.yaml | 17 + .../invalidation/one-rounded-rect.yaml | 17 + .../wrench/reftests/invalidation/reftest.list | 1 + .../reftests/invalidation/rounded-rects.yaml | 42 + .../invalidation/two-rounded-rects.yaml | 27 + .../reftests/mask/aligned-layer-rect-ref.yaml | 8 + .../reftests/mask/aligned-layer-rect.yaml | 13 + .../reftests/mask/checkerboard-tiling.yaml | 16 + .../wrench/reftests/mask/checkerboard.png | Bin 0 -> 11433 bytes .../wrench/reftests/mask/checkerboard.yaml | 13 + .../webrender/wrench/reftests/mask/green.yaml | 6 + .../reftests/mask/mask-atomicity-ref.yaml | 17 + .../reftests/mask/mask-atomicity-tiling.yaml | 25 + .../wrench/reftests/mask/mask-atomicity.yaml | 24 + .../mask/mask-perspective-tiling.yaml | 23 + .../wrench/reftests/mask/mask-perspective.png | Bin 0 -> 311 bytes .../reftests/mask/mask-perspective.yaml | 22 + .../wrench/reftests/mask/mask-ref.yaml | 9 + .../wrench/reftests/mask/mask-tiling.yaml | 14 + .../mask-transformed-to-empty-rect-ref.yaml | 6 + .../mask/mask-transformed-to-empty-rect.yaml | 26 + .../webrender/wrench/reftests/mask/mask.png | Bin 0 -> 187 bytes .../webrender/wrench/reftests/mask/mask.yaml | 13 + .../reftests/mask/missing-mask-ref.yaml | 7 + .../wrench/reftests/mask/missing-mask.yaml | 14 + .../wrench/reftests/mask/nested-mask-ref.yaml | 9 + .../reftests/mask/nested-mask-tiling.yaml | 22 + .../wrench/reftests/mask/nested-mask.yaml | 20 + .../wrench/reftests/mask/out-of-bounds.yaml | 16 + .../wrench/reftests/mask/reftest.list | 16 + .../wrench/reftests/mask/rounded-corners.png | Bin 0 -> 1720 bytes .../wrench/reftests/mask/rounded-corners.yaml | 24 + .../wrench/reftests/mask/tiny-check-mask.png | Bin 0 -> 138 bytes .../compositor-surface-opaque-slice-ref.yaml | 9 + .../compositor-surface-opaque-slice.yaml | 10 + .../reftests/performance/no-clip-mask.png | Bin 0 -> 2776 bytes .../reftests/performance/no-clip-mask.yaml | 24 + .../wrench/reftests/performance/reftest.list | 2 + .../webrender/wrench/reftests/reftest.list | 18 + .../clip-and-scroll-property-ref.yaml | 5 + .../scrolling/clip-and-scroll-property.yaml | 30 + .../reftests/scrolling/empty-mask-ref.yaml | 5 + .../wrench/reftests/scrolling/empty-mask.yaml | 18 + .../scrolling/ext-scroll-offset-1-ref.yaml | 10 + .../scrolling/ext-scroll-offset-1.yaml | 14 + .../scrolling/fixed-position-ref.yaml | 14 + .../fixed-position-scrolling-clip-ref.yaml | 5 + .../fixed-position-scrolling-clip.yaml | 28 + .../reftests/scrolling/fixed-position.yaml | 55 + .../wrench/reftests/scrolling/mask.png | Bin 0 -> 282 bytes .../scrolling/nested-scroll-offset-ref.yaml | 5 + .../scrolling/nested-scroll-offset.yaml | 16 + .../scrolling/nested-stickys-ref.yaml | 11 + .../reftests/scrolling/nested-stickys.yaml | 20 + .../scrolling/out-of-bounds-scroll-ref.yaml | 5 + .../scrolling/out-of-bounds-scroll.yaml | 10 + .../wrench/reftests/scrolling/reftest.list | 19 + .../reftests/scrolling/root-scroll-ref.yaml | 6 + .../reftests/scrolling/root-scroll.yaml | 7 + .../reftests/scrolling/scale-offsets-ref.yaml | 5 + .../reftests/scrolling/scale-offsets.yaml | 16 + .../reftests/scrolling/scroll-layer-ref.yaml | 5 + .../reftests/scrolling/scroll-layer.yaml | 10 + .../scrolling/sibling-hidden-clip-ref.yaml | 8 + .../scrolling/sibling-hidden-clip.yaml | 20 + .../wrench/reftests/scrolling/simple-ref.yaml | 8 + .../wrench/reftests/scrolling/simple.yaml | 16 + .../scrolling/sticky-applied-ref.yaml | 70 + .../reftests/scrolling/sticky-applied.yaml | 307 + .../reftests/scrolling/sticky-nested.yaml | 215 + .../wrench/reftests/scrolling/sticky-ref.yaml | 40 + .../scrolling/sticky-transformed-ref.yaml | 5 + .../scrolling/sticky-transformed.yaml | 21 + .../wrench/reftests/scrolling/sticky.yaml | 167 + .../scrolling/translate-nested-ref.yaml | 14 + .../reftests/scrolling/translate-nested.yaml | 28 + .../scrolling/viewport-offset-ref.yaml | 9 + .../reftests/scrolling/viewport-offset.yaml | 20 + .../wrench/reftests/snap/preserve-3d.png | Bin 0 -> 6807 bytes .../wrench/reftests/snap/preserve-3d.yaml | 55 + .../wrench/reftests/snap/reftest.list | 3 + .../webrender/wrench/reftests/snap/snap.png | Bin 0 -> 2885 bytes .../webrender/wrench/reftests/snap/snap.yaml | 15 + .../wrench/reftests/snap/transform.png | Bin 0 -> 3012 bytes .../wrench/reftests/snap/transform.yaml | 19 + .../wrench/reftests/split/cross-ref.yaml | 36 + .../wrench/reftests/split/cross.yaml | 24 + .../wrench/reftests/split/filter-ref.yaml | 10 + .../wrench/reftests/split/filter.yaml | 21 + .../wrench/reftests/split/gradient-ref.yaml | 39 + .../wrench/reftests/split/gradient.yaml | 42 + .../reftests/split/intermediate-1-ref.yaml | 12 + .../wrench/reftests/split/intermediate-1.yaml | 38 + .../wrench/reftests/split/intermediate-2.yaml | 34 + .../reftests/split/mixed-order-ref.yaml | 22 + .../wrench/reftests/split/mixed-order.yaml | 25 + .../wrench/reftests/split/near-plane.png | Bin 0 -> 3210 bytes .../wrench/reftests/split/near-plane.yaml | 16 + .../split/nested-coord-systems-ref.yaml | 6 + .../reftests/split/nested-coord-systems.yaml | 28 + .../split/nested-preserve3d-crash.yaml | 37 + .../wrench/reftests/split/nested-ref.yaml | 12 + .../wrench/reftests/split/nested.yaml | 25 + .../wrench/reftests/split/order-1-ref.yaml | 8 + .../wrench/reftests/split/order-1.yaml | 20 + .../wrench/reftests/split/order-2-ref.yaml | 8 + .../wrench/reftests/split/order-2.yaml | 27 + .../wrench/reftests/split/order-3-ref.yaml | 15 + .../wrench/reftests/split/order-3.yaml | 31 + .../wrench/reftests/split/ordering-ref.yaml | 15 + .../wrench/reftests/split/ordering.yaml | 25 + .../split/perspective-clipping-ref.yaml | 9 + .../reftests/split/perspective-clipping.yaml | 23 + .../wrench/reftests/split/reftest.list | 22 + .../wrench/reftests/split/same-plane.png | Bin 0 -> 4279 bytes .../wrench/reftests/split/same-plane.yaml | 33 + .../wrench/reftests/split/simple-ref.yaml | 12 + .../wrench/reftests/split/simple.yaml | 24 + .../reftests/split/split-intersect1-ref.yaml | 15 + .../reftests/split/split-intersect1.yaml | 18 + .../wrench/reftests/text/1658-ref.yaml | 26 + .../webrender/wrench/reftests/text/1658.yaml | 19 + .../webrender/wrench/reftests/text/Ahem.ttf | Bin 0 -> 21768 bytes .../wrench/reftests/text/FreeSans.ttf | Bin 0 -> 714456 bytes .../wrench/reftests/text/Proggy-License.txt | 10 + .../webrender/wrench/reftests/text/Proggy.ttf | Bin 0 -> 5284 bytes .../webrender/wrench/reftests/text/VeraBd.ttf | Bin 0 -> 58716 bytes .../wrench/reftests/text/ahem-ref.yaml | 18 + .../webrender/wrench/reftests/text/ahem.yaml | 243 + .../reftests/text/allow-subpixel-ref.yaml | 8 + .../wrench/reftests/text/allow-subpixel.yaml | 19 + .../wrench/reftests/text/alpha-transform.png | Bin 0 -> 23930 bytes .../wrench/reftests/text/alpha-transform.yaml | 10 + .../wrench/reftests/text/bg-color-ref.yaml | 8 + .../wrench/reftests/text/bg-color.yaml | 15 + .../webrender/wrench/reftests/text/blank.yaml | 2 + .../blurred-shadow-local-clip-rect-ref.png | Bin 0 -> 19504 bytes .../text/blurred-shadow-local-clip-rect.yaml | 48 + .../reftests/text/border-radius-alpha.png | Bin 0 -> 14101 bytes .../reftests/text/border-radius-subpx.png | Bin 0 -> 14411 bytes .../wrench/reftests/text/border-radius.yaml | 17 + .../reftests/text/clipped-transform.png | Bin 0 -> 1792 bytes .../reftests/text/clipped-transform.yaml | 12 + .../text/color-bitmap-shadow-ref.yaml | 30 + .../reftests/text/color-bitmap-shadow.yaml | 34 + .../wrench/reftests/text/colors-alpha.png | Bin 0 -> 93670 bytes .../wrench/reftests/text/colors-subpx.png | Bin 0 -> 132119 bytes .../wrench/reftests/text/colors.yaml | 211 + .../wrench/reftests/text/decorations-ref.yaml | 19 + .../reftests/text/decorations-suite.png | Bin 0 -> 24087 bytes .../reftests/text/decorations-suite.yaml | 348 + .../wrench/reftests/text/decorations.yaml | 36 + .../wrench/reftests/text/diacritics-ref.yaml | 6 + .../wrench/reftests/text/diacritics.yaml | 6 + .../wrench/reftests/text/embedded-bitmaps.png | Bin 0 -> 2288 bytes .../reftests/text/embedded-bitmaps.yaml | 9 + .../reftests/text/intermediate-transform.yaml | 54 + .../wrench/reftests/text/isolated-text.png | Bin 0 -> 6737 bytes .../wrench/reftests/text/isolated-text.yaml | 14 + .../wrench/reftests/text/large-glyphs.yaml | 8 + .../reftests/text/large-line-decoration.yaml | 43 + .../wrench/reftests/text/long-text.yaml | 610 + .../wrench/reftests/text/negative-pos.yaml | 10 + .../reftests/text/non-opaque-notref.yaml | 10 + .../wrench/reftests/text/non-opaque.yaml | 10 + .../wrench/reftests/text/perspective-clip.png | Bin 0 -> 1753 bytes .../reftests/text/perspective-clip.yaml | 27 + .../reftests/text/raster-space-snap-ref.yaml | 10 + .../reftests/text/raster-space-snap.yaml | 10 + .../wrench/reftests/text/raster-space.png | Bin 0 -> 64740 bytes .../wrench/reftests/text/raster-space.yaml | 28 + .../reftests/text/raster_root_C_8192.yaml | 385 + .../reftests/text/raster_root_C_ref.yaml | 385 + .../wrench/reftests/text/reftest.list | 84 + .../reftests/text/shadow-atomic-ref.yaml | 81 + .../wrench/reftests/text/shadow-atomic.yaml | 42 + .../wrench/reftests/text/shadow-border.yaml | 19 + .../reftests/text/shadow-clip-rect.yaml | 45 + .../wrench/reftests/text/shadow-clip-ref.yaml | 11 + .../wrench/reftests/text/shadow-clip.yaml | 25 + .../reftests/text/shadow-clipped-text.yaml | 24 + .../wrench/reftests/text/shadow-complex.yaml | 46 + .../wrench/reftests/text/shadow-cover-1.yaml | 18 + .../wrench/reftests/text/shadow-cover-2.yaml | 26 + .../reftests/text/shadow-fast-clip-ref.yaml | 21 + .../reftests/text/shadow-fast-clip.yaml | 22 + .../wrench/reftests/text/shadow-grey-ref.yaml | 6 + .../text/shadow-grey-transparent.yaml | 19 + .../wrench/reftests/text/shadow-grey.yaml | 19 + .../wrench/reftests/text/shadow-huge-ref.yaml | 18 + .../wrench/reftests/text/shadow-huge.yaml | 18 + .../wrench/reftests/text/shadow-image.yaml | 14 + .../wrench/reftests/text/shadow-many.yaml | 30 + .../reftests/text/shadow-ordering-ref.yaml | 131 + .../wrench/reftests/text/shadow-ordering.yaml | 48 + .../text/shadow-partial-glyph-ref.yaml | 50 + .../reftests/text/shadow-partial-glyph.yaml | 43 + .../wrench/reftests/text/shadow-red-ref.yaml | 5 + .../wrench/reftests/text/shadow-red.yaml | 19 + .../wrench/reftests/text/shadow-ref.yaml | 25 + .../wrench/reftests/text/shadow-rotate.yaml | 18 + .../wrench/reftests/text/shadow-single.yaml | 18 + .../reftests/text/shadow-solid-ref.yaml | 16 + .../reftests/text/shadow-transforms.png | Bin 0 -> 96541 bytes .../reftests/text/shadow-transforms.yaml | 58 + .../wrench/reftests/text/shadow.yaml | 33 + .../wrench/reftests/text/snap-clip-ref.yaml | 7 + .../wrench/reftests/text/snap-clip.yaml | 8 + .../reftests/text/snap-text-offset-ref.yaml | 11 + .../reftests/text/snap-text-offset.yaml | 10 + .../wrench/reftests/text/split-batch-ref.yaml | 18 + .../wrench/reftests/text/split-batch.yaml | 18 + .../wrench/reftests/text/subpixel-rotate.png | Bin 0 -> 19021 bytes .../wrench/reftests/text/subpixel-rotate.yaml | 10 + .../wrench/reftests/text/subpixel-scale.png | Bin 0 -> 14063 bytes .../wrench/reftests/text/subpixel-scale.yaml | 10 + .../wrench/reftests/text/subpixel-skew.png | Bin 0 -> 15337 bytes .../wrench/reftests/text/subpixel-skew.yaml | 10 + .../reftests/text/subpixel-translate-ref.yaml | 12 + .../reftests/text/subpixel-translate.yaml | 13 + .../reftests/text/subtle-shadow-ref.yaml | 27 + .../wrench/reftests/text/subtle-shadow.yaml | 21 + .../reftests/text/synthetic-bold-not-ref.yaml | 5 + .../text/synthetic-bold-transparent-ref.yaml | 10 + .../text/synthetic-bold-transparent.yaml | 7 + .../wrench/reftests/text/synthetic-bold.yaml | 6 + .../text/synthetic-italics-custom.yaml | 7 + .../reftests/text/synthetic-italics-ref.yaml | 5 + .../reftests/text/synthetic-italics.yaml | 7 + .../reftests/text/text-fixed-slice-fast.png | Bin 0 -> 5025 bytes .../reftests/text/text-fixed-slice-slow.png | Bin 0 -> 6024 bytes .../reftests/text/text-fixed-slice.yaml | 20 + .../reftests/text/text-masking-alpha.png | Bin 0 -> 18885 bytes .../reftests/text/text-masking-mask.png | Bin 0 -> 129 bytes .../reftests/text/text-masking-subpx.png | Bin 0 -> 28989 bytes .../wrench/reftests/text/text-masking.yaml | 27 + .../webrender/wrench/reftests/text/text.yaml | 10 + .../reftests/text/transparent-no-aa-ref.yaml | 17 + .../reftests/text/transparent-no-aa.yaml | 9 + .../wrench/reftests/text/two-shadows.png | Bin 0 -> 11350 bytes .../wrench/reftests/text/two-shadows.yaml | 24 + .../wrench/reftests/text/white-opacity.png | Bin 0 -> 8266 bytes .../wrench/reftests/text/white-opacity.yaml | 17 + .../reftests/text/writing-modes-ref.yaml | 19 + .../wrench/reftests/text/writing-modes.yaml | 15 + .../wrench/reftests/tiles/prim-suite.yaml | 45 + .../webrender/wrench/reftests/tiles/rect.yaml | 6 + .../wrench/reftests/tiles/reftest.list | 4 + .../reftests/tiles/simple-gradient.yaml | 9 + .../big-axis-aligned-scale-ref.yaml | 6 + .../transforms/big-axis-aligned-scale.yaml | 10 + .../wrench/reftests/transforms/blank.yaml | 2 + .../reftests/transforms/border-scale-2.png | Bin 0 -> 6817 bytes .../reftests/transforms/border-scale-2.yaml | 19 + .../reftests/transforms/border-scale-3.png | Bin 0 -> 6562 bytes .../reftests/transforms/border-scale-3.yaml | 19 + .../reftests/transforms/border-scale-4.png | Bin 0 -> 6545 bytes .../reftests/transforms/border-scale-4.yaml | 19 + .../reftests/transforms/border-scale.png | Bin 0 -> 7232 bytes .../reftests/transforms/border-scale.yaml | 19 + .../reftests/transforms/border-zoom.png | Bin 0 -> 27613 bytes .../reftests/transforms/border-zoom.yaml | 19 + .../transforms/clip-translate-ref.yaml | 6 + .../reftests/transforms/clip-translate.yaml | 21 + .../transforms/complex-preserve-3d.yaml | 24 + .../reftests/transforms/content-offset.png | Bin 0 -> 2121 bytes .../reftests/transforms/content-offset.yaml | 18 + .../reftests/transforms/coord-system.png | Bin 0 -> 4234 bytes .../reftests/transforms/coord-system.yaml | 24 + .../flatten-preserve-3d-root-ref.yaml | 6 + .../transforms/flatten-preserve-3d-root.yaml | 23 + .../transforms/flatten-twice-ref.yaml | 6 + .../reftests/transforms/flatten-twice.yaml | 21 + .../transforms/image-rotated-clip.png | Bin 0 -> 3824 bytes .../transforms/image-rotated-clip.yaml | 19 + .../wrench/reftests/transforms/image.png | Bin 0 -> 528 bytes .../transforms/large-raster-root.yaml | 14 + .../wrench/reftests/transforms/local-clip.png | Bin 0 -> 2138 bytes .../reftests/transforms/local-clip.yaml | 26 + .../reftests/transforms/near-plane-clip.png | Bin 0 -> 69832 bytes .../reftests/transforms/near-plane-clip.yaml | 18 + .../transforms/nested-preserve-3d.png | Bin 0 -> 3830 bytes .../transforms/nested-preserve-3d.yaml | 28 + .../transforms/nested-rotate-x-flat.png | Bin 0 -> 4593 bytes .../transforms/nested-rotate-x-flat.yaml | 27 + .../reftests/transforms/nested-rotate-x.png | Bin 0 -> 3832 bytes .../reftests/transforms/nested-rotate-x.yaml | 27 + .../transforms/perspective-border-radius.png | Bin 0 -> 11839 bytes .../transforms/perspective-border-radius.yaml | 20 + .../perspective-box-shadow-ref.yaml | 24 + .../transforms/perspective-box-shadow.yaml | 23 + .../transforms/perspective-clip-1.png | Bin 0 -> 2019 bytes .../transforms/perspective-clip-1.yaml | 25 + .../reftests/transforms/perspective-clip.png | Bin 0 -> 16932 bytes .../reftests/transforms/perspective-clip.yaml | 27 + .../reftests/transforms/perspective-mask.png | Bin 0 -> 2285 bytes .../reftests/transforms/perspective-mask.yaml | 22 + .../transforms/perspective-origin.png | Bin 0 -> 47142 bytes .../transforms/perspective-origin.yaml | 16 + .../transforms/perspective-shadow.png | Bin 0 -> 10342 bytes .../transforms/perspective-shadow.yaml | 27 + .../reftests/transforms/perspective.png | Bin 0 -> 134799 bytes .../reftests/transforms/perspective.yaml | 54 + .../wrench/reftests/transforms/prim-suite.png | Bin 0 -> 48510 bytes .../reftests/transforms/prim-suite.yaml | 45 + .../transforms/raster-root-huge-scale.yaml | 30 + .../transforms/raster-root-large-mask.yaml | 29 + .../transforms/raster-root-scaling-2-ref.yaml | 11 + .../transforms/raster-root-scaling-2.yaml | 22 + .../transforms/raster-root-scaling-ref.yaml | 10 + .../transforms/raster-root-scaling.yaml | 16 + .../transforms/raster_root_A_8192.yaml | 20 + .../transforms/raster_root_A_ref.yaml | 20 + .../transforms/raster_root_B_8192.yaml | 14 + .../transforms/raster_root_B_ref.yaml | 14 + .../wrench/reftests/transforms/reftest.list | 54 + .../reftests/transforms/rotate-clip-ref.yaml | 7 + .../reftests/transforms/rotate-clip.yaml | 18 + .../transforms/rotated-clip-large.png | Bin 0 -> 7420 bytes .../transforms/rotated-clip-large.yaml | 17 + .../reftests/transforms/rotated-clip.png | Bin 0 -> 3789 bytes .../reftests/transforms/rotated-clip.yaml | 17 + .../reftests/transforms/rotated-image.png | Bin 0 -> 7441 bytes .../reftests/transforms/rotated-image.yaml | 75 + .../transforms/screen-space-blit-trivial.png | Bin 0 -> 75011 bytes .../transforms/screen-space-blit-trivial.yaml | 22 + .../reftests/transforms/screen-space-blit.png | Bin 0 -> 73453 bytes .../transforms/screen-space-blit.yaml | 22 + .../reftests/transforms/screen-space-blur.png | Bin 0 -> 234783 bytes .../transforms/screen-space-blur.yaml | 20 + .../reftests/transforms/segments-bug-ref.yaml | 24 + .../reftests/transforms/segments-bug.yaml | 27 + .../reftests/transforms/singular-ref.yaml | 28 + .../wrench/reftests/transforms/singular.yaml | 39 + .../transforms/snapped-preserve-3d-ref.yaml | 19 + .../transforms/snapped-preserve-3d.yaml | 21 + .../reftests/transforms/strange-w-ref.yaml | 12 + .../wrench/reftests/transforms/strange-w.yaml | 15 + .../webrender/wrench/res/wrench.exe.manifest | 24 + .../wrench/script/benchmark_server.py | 59 + .../wrench/script/gen-many-images.py | 15 + .../webrender/wrench/script/headless.py | 154 + .../wrench/script/reftest-analyzer.xhtml | 857 + .../wrench/script/reftest-debugger.py | 15 + .../wrench/script/wrench_with_renderer.py | 52 + third_party/webrender/wrench/src/angle.rs | 62 + third_party/webrender/wrench/src/args.yaml | 222 + third_party/webrender/wrench/src/blob.rs | 213 + third_party/webrender/wrench/src/egl.rs | 611 + third_party/webrender/wrench/src/main.rs | 1056 + .../webrender/wrench/src/parse_function.rs | 134 + third_party/webrender/wrench/src/perf.rs | 352 + third_party/webrender/wrench/src/png.rs | 118 + .../webrender/wrench/src/premultiply.rs | 56 + third_party/webrender/wrench/src/rawtest.rs | 1463 ++ third_party/webrender/wrench/src/reftest.rs | 995 + third_party/webrender/wrench/src/wrench.rs | 681 + .../webrender/wrench/src/yaml_frame_reader.rs | 2245 +++ .../webrender/wrench/src/yaml_helper.rs | 805 + 1215 files changed, 185677 insertions(+), 34 deletions(-) create mode 100644 third_party/webrender/.gitignore create mode 100644 third_party/webrender/.taskcluster.yml create mode 100644 third_party/webrender/Cargo.lock create mode 100644 third_party/webrender/Cargo.toml create mode 100644 third_party/webrender/LICENSE create mode 100644 third_party/webrender/README.md create mode 100644 third_party/webrender/ci-scripts/docker-image/Dockerfile create mode 100755 third_party/webrender/ci-scripts/docker-image/setup.sh create mode 100755 third_party/webrender/ci-scripts/linux-debug-tests.sh create mode 100755 third_party/webrender/ci-scripts/linux-release-tests.sh create mode 100755 third_party/webrender/ci-scripts/macos-debug-tests.sh create mode 100755 third_party/webrender/ci-scripts/macos-release-tests.sh create mode 100644 third_party/webrender/ci-scripts/set-screenresolution.ps1 create mode 100755 third_party/webrender/ci-scripts/windows-tests.cmd create mode 100644 third_party/webrender/debugger/.babelrc create mode 100644 third_party/webrender/debugger/.gitignore create mode 100644 third_party/webrender/debugger/README.md create mode 100644 third_party/webrender/debugger/dist/build.js create mode 100644 third_party/webrender/debugger/dist/build.js.map create mode 100644 third_party/webrender/debugger/index.html create mode 100644 third_party/webrender/debugger/package-lock.json create mode 100644 third_party/webrender/debugger/package.json create mode 100644 third_party/webrender/debugger/src/App.vue create mode 100644 third_party/webrender/debugger/src/components/ClipScrollTreeViewPage.vue create mode 100644 third_party/webrender/debugger/src/components/DocumentViewPage.vue create mode 100644 third_party/webrender/debugger/src/components/NavBar.vue create mode 100644 third_party/webrender/debugger/src/components/NavMenu.vue create mode 100644 third_party/webrender/debugger/src/components/OptionsPage.vue create mode 100644 third_party/webrender/debugger/src/components/PassViewPage.vue create mode 100644 third_party/webrender/debugger/src/components/RenderTaskViewPage.vue create mode 100644 third_party/webrender/debugger/src/components/ScreenshotPage.vue create mode 100644 third_party/webrender/debugger/src/components/TreeView.vue create mode 100644 third_party/webrender/debugger/src/main.js create mode 100644 third_party/webrender/debugger/src/store/index.js create mode 100644 third_party/webrender/debugger/webpack.config.js create mode 100644 third_party/webrender/direct-composition/Cargo.toml create mode 100644 third_party/webrender/direct-composition/src/com.rs create mode 100644 third_party/webrender/direct-composition/src/egl.rs create mode 100644 third_party/webrender/direct-composition/src/lib.rs create mode 100644 third_party/webrender/direct-composition/src/main.rs create mode 100644 third_party/webrender/direct-composition/src/main_windows.rs create mode 100644 third_party/webrender/example-compositor/compositor-windows/Cargo.toml create mode 100644 third_party/webrender/example-compositor/compositor-windows/build.rs create mode 100644 third_party/webrender/example-compositor/compositor-windows/src/lib.cpp create mode 100644 third_party/webrender/example-compositor/compositor-windows/src/lib.rs create mode 100644 third_party/webrender/example-compositor/compositor/Cargo.toml create mode 100644 third_party/webrender/example-compositor/compositor/src/main.rs create mode 100644 third_party/webrender/examples/Cargo.toml create mode 100644 third_party/webrender/examples/README.md create mode 100644 third_party/webrender/examples/alpha_perf.rs create mode 100644 third_party/webrender/examples/animation.rs create mode 100644 third_party/webrender/examples/basic.rs create mode 100644 third_party/webrender/examples/blob.rs create mode 100644 third_party/webrender/examples/common/boilerplate.rs create mode 100644 third_party/webrender/examples/common/image_helper.rs create mode 100644 third_party/webrender/examples/document.rs create mode 100644 third_party/webrender/examples/frame_output.rs create mode 100644 third_party/webrender/examples/iframe.rs create mode 100644 third_party/webrender/examples/image_resize.rs create mode 100644 third_party/webrender/examples/multiwindow.rs create mode 100644 third_party/webrender/examples/scrolling.rs create mode 100644 third_party/webrender/examples/texture_cache_stress.rs create mode 100644 third_party/webrender/examples/yuv.rs create mode 100644 third_party/webrender/glsl-to-cxx/Cargo.toml create mode 100644 third_party/webrender/glsl-to-cxx/README.md create mode 100644 third_party/webrender/glsl-to-cxx/src/hir.rs create mode 100644 third_party/webrender/glsl-to-cxx/src/lib.rs create mode 100644 third_party/webrender/glsl-to-cxx/src/main.rs create mode 100644 third_party/webrender/patches/0001-Add-signal-handler-to-catch-segfault-in-build-script.patch create mode 100644 third_party/webrender/patches/0002-Bug-1646741-Update-gleam-to-0.12.-r-kvark.patch create mode 100644 third_party/webrender/patches/0003-Bug-1651889.-Update-to-gleam-0.12.1.-r-kvark.patch create mode 100644 third_party/webrender/patches/0004-Bug-1654699.-Update-core-foundation-core-graphics.-r.patch create mode 100644 third_party/webrender/patches/0005-Bug-1656236-Update-to-euclid-0.22.-r-kvark.patch create mode 100644 third_party/webrender/patches/0006-Bump-procedural-masquerade-to-0.1.7.patch create mode 100644 third_party/webrender/patches/head create mode 100644 third_party/webrender/patches/series create mode 100644 third_party/webrender/peek-poke/Cargo.toml create mode 100644 third_party/webrender/peek-poke/LICENSE-APACHE create mode 100644 third_party/webrender/peek-poke/LICENSE-MIT create mode 100644 third_party/webrender/peek-poke/README.md create mode 100644 third_party/webrender/peek-poke/peek-poke-derive/Cargo.toml create mode 100644 third_party/webrender/peek-poke/peek-poke-derive/LICENSE-APACHE create mode 100644 third_party/webrender/peek-poke/peek-poke-derive/LICENSE-MIT create mode 100644 third_party/webrender/peek-poke/peek-poke-derive/README.md create mode 100644 third_party/webrender/peek-poke/peek-poke-derive/src/lib.rs create mode 100644 third_party/webrender/peek-poke/src/euclid.rs create mode 100644 third_party/webrender/peek-poke/src/lib.rs create mode 100644 third_party/webrender/peek-poke/src/slice_ext.rs create mode 100644 third_party/webrender/peek-poke/src/vec_ext.rs create mode 100644 third_party/webrender/peek-poke/tests/max_size.rs create mode 100644 third_party/webrender/peek-poke/tests/round_trip.rs create mode 100644 third_party/webrender/rustfmt.toml create mode 100644 third_party/webrender/servo-tidy.toml create mode 100644 third_party/webrender/swgl/Cargo.toml create mode 100644 third_party/webrender/swgl/README.md create mode 100644 third_party/webrender/swgl/build.rs create mode 100644 third_party/webrender/swgl/src/gl.cc create mode 100644 third_party/webrender/swgl/src/gl_defs.h create mode 100644 third_party/webrender/swgl/src/glsl.h create mode 100644 third_party/webrender/swgl/src/lib.rs create mode 100644 third_party/webrender/swgl/src/program.h create mode 100644 third_party/webrender/swgl/src/swgl_fns.rs create mode 100644 third_party/webrender/swgl/src/texture.h create mode 100644 third_party/webrender/swgl/src/vector_type.h create mode 100644 third_party/webrender/tileview/Cargo.toml create mode 100644 third_party/webrender/tileview/src/main.rs create mode 100644 third_party/webrender/tileview/src/tilecache.js create mode 100644 third_party/webrender/tileview/src/tilecache_base.css create mode 100644 third_party/webrender/webrender/Cargo.toml create mode 100644 third_party/webrender/webrender/backtrace.rs create mode 100644 third_party/webrender/webrender/build.rs create mode 100644 third_party/webrender/webrender/doc/CLIPPING_AND_POSITIONING.md create mode 100644 third_party/webrender/webrender/doc/blob.md create mode 100644 third_party/webrender/webrender/doc/swizzling.md create mode 100644 third_party/webrender/webrender/doc/text-rendering.md create mode 100644 third_party/webrender/webrender/res/Proggy.ttf create mode 100644 third_party/webrender/webrender/res/area-lut.tga create mode 100644 third_party/webrender/webrender/res/base.glsl create mode 100644 third_party/webrender/webrender/res/brush.glsl create mode 100644 third_party/webrender/webrender/res/brush_blend.glsl create mode 100644 third_party/webrender/webrender/res/brush_conic_gradient.glsl create mode 100644 third_party/webrender/webrender/res/brush_image.glsl create mode 100644 third_party/webrender/webrender/res/brush_linear_gradient.glsl create mode 100644 third_party/webrender/webrender/res/brush_mix_blend.glsl create mode 100644 third_party/webrender/webrender/res/brush_multi.glsl create mode 100644 third_party/webrender/webrender/res/brush_opacity.glsl create mode 100644 third_party/webrender/webrender/res/brush_radial_gradient.glsl create mode 100644 third_party/webrender/webrender/res/brush_solid.frag.h create mode 100644 third_party/webrender/webrender/res/brush_solid.glsl create mode 100644 third_party/webrender/webrender/res/brush_yuv_image.glsl create mode 100644 third_party/webrender/webrender/res/clip_shared.glsl create mode 100644 third_party/webrender/webrender/res/composite.glsl create mode 100644 third_party/webrender/webrender/res/cs_blur.glsl create mode 100644 third_party/webrender/webrender/res/cs_border_segment.glsl create mode 100644 third_party/webrender/webrender/res/cs_border_solid.glsl create mode 100644 third_party/webrender/webrender/res/cs_clip_box_shadow.glsl create mode 100644 third_party/webrender/webrender/res/cs_clip_image.glsl create mode 100644 third_party/webrender/webrender/res/cs_clip_rectangle.glsl create mode 100644 third_party/webrender/webrender/res/cs_gradient.glsl create mode 100644 third_party/webrender/webrender/res/cs_line_decoration.glsl create mode 100644 third_party/webrender/webrender/res/cs_scale.glsl create mode 100644 third_party/webrender/webrender/res/cs_svg_filter.glsl create mode 100644 third_party/webrender/webrender/res/debug_color.glsl create mode 100644 third_party/webrender/webrender/res/debug_font.glsl create mode 100644 third_party/webrender/webrender/res/ellipse.glsl create mode 100644 third_party/webrender/webrender/res/gpu_cache.glsl create mode 100644 third_party/webrender/webrender/res/gpu_cache_update.glsl create mode 100644 third_party/webrender/webrender/res/pf_vector_cover.glsl create mode 100644 third_party/webrender/webrender/res/pf_vector_stencil.glsl create mode 100644 third_party/webrender/webrender/res/pls_init.glsl create mode 100644 third_party/webrender/webrender/res/pls_resolve.glsl create mode 100644 third_party/webrender/webrender/res/prim_shared.glsl create mode 100644 third_party/webrender/webrender/res/ps_clear.glsl create mode 100644 third_party/webrender/webrender/res/ps_split_composite.glsl create mode 100644 third_party/webrender/webrender/res/ps_text_run.glsl create mode 100644 third_party/webrender/webrender/res/rect.glsl create mode 100644 third_party/webrender/webrender/res/render_task.glsl create mode 100644 third_party/webrender/webrender/res/shared.glsl create mode 100644 third_party/webrender/webrender/res/shared_other.glsl create mode 100644 third_party/webrender/webrender/res/transform.glsl create mode 100644 third_party/webrender/webrender/res/yuv.glsl create mode 100644 third_party/webrender/webrender/src/batch.rs create mode 100644 third_party/webrender/webrender/src/border.rs create mode 100644 third_party/webrender/webrender/src/box_shadow.rs create mode 100644 third_party/webrender/webrender/src/capture.rs create mode 100644 third_party/webrender/webrender/src/clip.rs create mode 100644 third_party/webrender/webrender/src/composite.rs create mode 100644 third_party/webrender/webrender/src/debug_colors.rs create mode 100644 third_party/webrender/webrender/src/debug_font_data.rs create mode 100644 third_party/webrender/webrender/src/debug_render.rs create mode 100644 third_party/webrender/webrender/src/debug_server.rs create mode 100644 third_party/webrender/webrender/src/device/gl.rs create mode 100644 third_party/webrender/webrender/src/device/mod.rs create mode 100644 third_party/webrender/webrender/src/device/query_gl.rs create mode 100644 third_party/webrender/webrender/src/ellipse.rs create mode 100644 third_party/webrender/webrender/src/filterdata.rs create mode 100644 third_party/webrender/webrender/src/frame_builder.rs create mode 100644 third_party/webrender/webrender/src/freelist.rs create mode 100644 third_party/webrender/webrender/src/gamma_lut.rs create mode 100644 third_party/webrender/webrender/src/glyph_cache.rs create mode 100644 third_party/webrender/webrender/src/glyph_rasterizer/mod.rs create mode 100644 third_party/webrender/webrender/src/gpu_cache.rs create mode 100644 third_party/webrender/webrender/src/gpu_types.rs create mode 100644 third_party/webrender/webrender/src/hit_test.rs create mode 100644 third_party/webrender/webrender/src/intern.rs create mode 100644 third_party/webrender/webrender/src/internal_types.rs create mode 100644 third_party/webrender/webrender/src/lib.rs create mode 100644 third_party/webrender/webrender/src/lru_cache.rs create mode 100644 third_party/webrender/webrender/src/picture.rs create mode 100644 third_party/webrender/webrender/src/platform/macos/font.rs create mode 100644 third_party/webrender/webrender/src/platform/unix/font.rs create mode 100644 third_party/webrender/webrender/src/platform/windows/font.rs create mode 100644 third_party/webrender/webrender/src/prim_store/backdrop.rs create mode 100644 third_party/webrender/webrender/src/prim_store/borders.rs create mode 100644 third_party/webrender/webrender/src/prim_store/gradient.rs create mode 100644 third_party/webrender/webrender/src/prim_store/image.rs create mode 100644 third_party/webrender/webrender/src/prim_store/interned.rs create mode 100644 third_party/webrender/webrender/src/prim_store/line_dec.rs create mode 100644 third_party/webrender/webrender/src/prim_store/mod.rs create mode 100644 third_party/webrender/webrender/src/prim_store/picture.rs create mode 100644 third_party/webrender/webrender/src/prim_store/text_run.rs create mode 100644 third_party/webrender/webrender/src/print_tree.rs create mode 100644 third_party/webrender/webrender/src/profiler.rs create mode 100644 third_party/webrender/webrender/src/render_backend.rs create mode 100644 third_party/webrender/webrender/src/render_target.rs create mode 100644 third_party/webrender/webrender/src/render_task.rs create mode 100644 third_party/webrender/webrender/src/render_task_cache.rs create mode 100644 third_party/webrender/webrender/src/render_task_graph.rs create mode 100644 third_party/webrender/webrender/src/renderer.rs create mode 100644 third_party/webrender/webrender/src/resource_cache.rs create mode 100644 third_party/webrender/webrender/src/scene.rs create mode 100644 third_party/webrender/webrender/src/scene_builder_thread.rs create mode 100644 third_party/webrender/webrender/src/scene_building.rs create mode 100644 third_party/webrender/webrender/src/screen_capture.rs create mode 100644 third_party/webrender/webrender/src/segment.rs create mode 100644 third_party/webrender/webrender/src/shade.rs create mode 100644 third_party/webrender/webrender/src/spatial_node.rs create mode 100644 third_party/webrender/webrender/src/spatial_tree.rs create mode 100644 third_party/webrender/webrender/src/storage.rs create mode 100644 third_party/webrender/webrender/src/texture_allocator.rs create mode 100644 third_party/webrender/webrender/src/texture_cache.rs create mode 100644 third_party/webrender/webrender/src/util.rs create mode 100644 third_party/webrender/webrender/tests/angle_shader_validation.rs create mode 100644 third_party/webrender/webrender/tests/bug_124.html create mode 100644 third_party/webrender/webrender/tests/bug_134.html create mode 100644 third_party/webrender/webrender/tests/bug_137.html create mode 100644 third_party/webrender/webrender/tests/bug_143.html create mode 100644 third_party/webrender/webrender/tests/bug_159.html create mode 100644 third_party/webrender/webrender/tests/bug_166.html create mode 100644 third_party/webrender/webrender/tests/bug_176.html create mode 100644 third_party/webrender/webrender/tests/bug_177.html create mode 100644 third_party/webrender/webrender/tests/bug_178.html create mode 100644 third_party/webrender/webrender/tests/bug_203a.html create mode 100644 third_party/webrender/webrender/tests/bug_203b.html create mode 100644 third_party/webrender/webrender/tests/bug_servo_10136.html create mode 100644 third_party/webrender/webrender/tests/bug_servo_10164.html create mode 100644 third_party/webrender/webrender/tests/bug_servo_10307.html create mode 100644 third_party/webrender/webrender/tests/bug_servo_11358.html create mode 100644 third_party/webrender/webrender/tests/bug_servo_9983a.html create mode 100644 third_party/webrender/webrender/tests/color_pattern_1.png create mode 100644 third_party/webrender/webrender/tests/color_pattern_2.png create mode 100644 third_party/webrender/webrender/tests/fixed-position.html create mode 100644 third_party/webrender/webrender/tests/mix-blend-mode-2.html create mode 100644 third_party/webrender/webrender/tests/mix-blend-mode.html create mode 100644 third_party/webrender/webrender/tests/nav-1.html create mode 100644 third_party/webrender/webrender/tests/nav-2.html create mode 100644 third_party/webrender/webrender_api/Cargo.toml create mode 100644 third_party/webrender/webrender_api/src/api.rs create mode 100644 third_party/webrender/webrender_api/src/channel.rs create mode 100644 third_party/webrender/webrender_api/src/color.rs create mode 100644 third_party/webrender/webrender_api/src/display_item.rs create mode 100644 third_party/webrender/webrender_api/src/display_item_cache.rs create mode 100644 third_party/webrender/webrender_api/src/display_list.rs create mode 100644 third_party/webrender/webrender_api/src/font.rs create mode 100644 third_party/webrender/webrender_api/src/gradient_builder.rs create mode 100644 third_party/webrender/webrender_api/src/image.rs create mode 100644 third_party/webrender/webrender_api/src/image_tiling.rs create mode 100644 third_party/webrender/webrender_api/src/lib.rs create mode 100644 third_party/webrender/webrender_api/src/resources.rs create mode 100644 third_party/webrender/webrender_api/src/units.rs create mode 100644 third_party/webrender/webrender_build/Cargo.toml create mode 100644 third_party/webrender/webrender_build/src/lib.rs create mode 100644 third_party/webrender/webrender_build/src/shader.rs create mode 100644 third_party/webrender/webrender_build/src/shader_features.rs create mode 100644 third_party/webrender/wr_malloc_size_of/Cargo.toml create mode 100644 third_party/webrender/wr_malloc_size_of/LICENSE-APACHE create mode 100644 third_party/webrender/wr_malloc_size_of/LICENSE-MIT create mode 100644 third_party/webrender/wr_malloc_size_of/lib.rs create mode 100644 third_party/webrender/wrench/.gitignore create mode 100644 third_party/webrender/wrench/Cargo.toml create mode 100644 third_party/webrender/wrench/README.md create mode 100644 third_party/webrender/wrench/android.txt create mode 100644 third_party/webrender/wrench/benchmarks/aligned-gradient.yaml create mode 100644 third_party/webrender/wrench/benchmarks/benchmarks.list create mode 100644 third_party/webrender/wrench/benchmarks/box-shadow-large.yaml create mode 100644 third_party/webrender/wrench/benchmarks/clip-clear.yaml create mode 100644 third_party/webrender/wrench/benchmarks/large-blur-radius.yaml create mode 100644 third_party/webrender/wrench/benchmarks/large-boxshadow-ellipse-2.yaml create mode 100644 third_party/webrender/wrench/benchmarks/large-boxshadow-ellipse.yaml create mode 100644 third_party/webrender/wrench/benchmarks/large-clip-rect.yaml create mode 100644 third_party/webrender/wrench/benchmarks/many-box-shadows.yaml create mode 100644 third_party/webrender/wrench/benchmarks/many-images.yaml create mode 100644 third_party/webrender/wrench/benchmarks/overlapping-text-shadows.yaml create mode 100644 third_party/webrender/wrench/benchmarks/radial-gradient.yaml create mode 100644 third_party/webrender/wrench/benchmarks/simple-batching.yaml create mode 100644 third_party/webrender/wrench/benchmarks/text-rendering.yaml create mode 100644 third_party/webrender/wrench/benchmarks/transforms-simple.yaml create mode 100644 third_party/webrender/wrench/benchmarks/unaligned-gradient.yaml create mode 100644 third_party/webrender/wrench/build.rs create mode 100644 third_party/webrender/wrench/examples/animated.anim create mode 100644 third_party/webrender/wrench/examples/animated.yaml create mode 100644 third_party/webrender/wrench/reftests/aa/aa-dist-bug-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/aa/aa-dist-bug.yaml create mode 100644 third_party/webrender/wrench/reftests/aa/fractional-radii-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/aa/fractional-radii.yaml create mode 100644 third_party/webrender/wrench/reftests/aa/reftest.list create mode 100644 third_party/webrender/wrench/reftests/aa/rounded-rects-ref.png create mode 100644 third_party/webrender/wrench/reftests/aa/rounded-rects.yaml create mode 100644 third_party/webrender/wrench/reftests/backface/backface-3d-leaf.yaml create mode 100644 third_party/webrender/wrench/reftests/backface/backface-both-sides-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/backface/backface-both-sides.yaml create mode 100644 third_party/webrender/wrench/reftests/backface/backface-double-flip.yaml create mode 100644 third_party/webrender/wrench/reftests/backface/backface-hidden.yaml create mode 100644 third_party/webrender/wrench/reftests/backface/backface-leaf-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/backface/backface-leaf.yaml create mode 100644 third_party/webrender/wrench/reftests/backface/backface-picture-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/backface/backface-picture.yaml create mode 100644 third_party/webrender/wrench/reftests/backface/backface-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/backface/backface-sc.yaml create mode 100644 third_party/webrender/wrench/reftests/backface/blank.yaml create mode 100644 third_party/webrender/wrench/reftests/backface/reftest.list create mode 100644 third_party/webrender/wrench/reftests/blend/blank.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/darken-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/darken.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/difference-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/difference-transparent-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/difference-transparent.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/difference.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/isolated-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/isolated-2.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/isolated-premultiplied-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/isolated-premultiplied-2.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/isolated-premultiplied.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/isolated-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/isolated-with-filter.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/isolated.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/large-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/large.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/lighten-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/lighten.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/multi-mix-blend-mode-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/multi-mix-blend-mode.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/multiply-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/multiply-2.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/multiply-3.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/multiply-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/multiply.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/reftest.list create mode 100644 third_party/webrender/wrench/reftests/blend/repeated-difference-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/repeated-difference.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/transparent-composite-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/transparent-composite-1.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/transparent-composite-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/transparent-composite-2.yaml create mode 100644 third_party/webrender/wrench/reftests/blend/transparent-white.png create mode 100644 third_party/webrender/wrench/reftests/border/blank.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-clamp-corner-radius.png create mode 100644 third_party/webrender/wrench/reftests/border/border-clamp-corner-radius.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-dashed-dotted-caching.png create mode 100644 third_party/webrender/wrench/reftests/border/border-dashed-dotted-caching.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-double-1px-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-double-1px.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-double-simple-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-double-simple-2.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-double-simple-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-double-simple.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-gradient-nine-patch.png create mode 100644 third_party/webrender/wrench/reftests/border/border-gradient-nine-patch.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-gradient-simple-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-gradient-simple.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-groove-simple-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-groove-simple.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-image-crash-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-image-crash.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-image-empty-slice-ref.png create mode 100644 third_party/webrender/wrench/reftests/border/border-image-empty-slice.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-image-fill-ref.png create mode 100644 third_party/webrender/wrench/reftests/border/border-image-fill.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-image-ref.png create mode 100644 third_party/webrender/wrench/reftests/border/border-image-round-ref.png create mode 100644 third_party/webrender/wrench/reftests/border/border-image-round.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-image-src.png create mode 100644 third_party/webrender/wrench/reftests/border/border-image.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-invisible-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-invisible.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-no-bogus-line-ref.png create mode 100644 third_party/webrender/wrench/reftests/border/border-no-bogus-line.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-none-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-none.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-overlapping-corner-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-overlapping-corner.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-overlapping-edge-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-overlapping-edge.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-radial-gradient-nine-patch.png create mode 100644 third_party/webrender/wrench/reftests/border/border-radial-gradient-nine-patch.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-radial-gradient-simple-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-radial-gradient-simple.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-radii.png create mode 100644 third_party/webrender/wrench/reftests/border/border-radii.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-ridge-simple-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-ridge-simple.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-suite-2.png create mode 100644 third_party/webrender/wrench/reftests/border/border-suite-2.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-suite-3.png create mode 100644 third_party/webrender/wrench/reftests/border/border-suite-3.yaml create mode 100644 third_party/webrender/wrench/reftests/border/border-suite.png create mode 100644 third_party/webrender/wrench/reftests/border/border-suite.yaml create mode 100644 third_party/webrender/wrench/reftests/border/degenerate-curve.png create mode 100644 third_party/webrender/wrench/reftests/border/degenerate-curve.yaml create mode 100644 third_party/webrender/wrench/reftests/border/discontinued-dash.png create mode 100644 third_party/webrender/wrench/reftests/border/discontinued-dash.yaml create mode 100644 third_party/webrender/wrench/reftests/border/dotted-corner-small-radius.png create mode 100644 third_party/webrender/wrench/reftests/border/dotted-corner-small-radius.yaml create mode 100644 third_party/webrender/wrench/reftests/border/green-square.yaml create mode 100644 third_party/webrender/wrench/reftests/border/max-scale-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/border/max-scale.yaml create mode 100644 third_party/webrender/wrench/reftests/border/no-aa.yaml create mode 100644 third_party/webrender/wrench/reftests/border/overlapping.png create mode 100644 third_party/webrender/wrench/reftests/border/overlapping.yaml create mode 100644 third_party/webrender/wrench/reftests/border/reftest.list create mode 100644 third_party/webrender/wrench/reftests/border/small-dotted-border.png create mode 100644 third_party/webrender/wrench/reftests/border/small-dotted-border.yaml create mode 100644 third_party/webrender/wrench/reftests/border/small-inset-outset-notref.yaml create mode 100644 third_party/webrender/wrench/reftests/border/small-inset-outset.yaml create mode 100644 third_party/webrender/wrench/reftests/border/zero-width.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/blank.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-blurred-overlapping-radii-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-blurred-overlapping-radii.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-border-radii.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-border-radii.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-cache.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-cache.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-clip-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-empty.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-huge-radius.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-huge-radius.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-large-blur-radius-2.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-large-blur-radius-2.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-large-blur-radius-3.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-large-blur-radius-3.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-large-blur-radius-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-large-blur-radius.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-spread-radii-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-spread-radii.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-spread.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-spread.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-stretch-mode-x.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-stretch-mode-x.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-stretch-mode-y.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-stretch-mode-y.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-suite-blur.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-suite-blur.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-suite-no-blur.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/box-shadow-suite-no-blur.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/boxshadow-spread-only-ref.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/boxshadow-spread-only.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-alpha.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-alpha.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-border-radius-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-border-radius.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-border-radius.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-downscale.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-downscale.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-empty.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-large-offset-ref.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-large-offset.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-mask-region.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-mask-region.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-neg-offset.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-neg-offset.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-no-blur-radius-ref.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-no-blur-radius.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-offset.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-offset.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-simple-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-simple.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-spread-large-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-spread-large.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-spread-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-spread.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-subpx.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/inset-subpx.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/invalid-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/invalid.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/no-stretch.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/no-stretch.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/overlap1.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/overlap1.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/overlap2.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/overlap2.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/reftest.list create mode 100644 third_party/webrender/wrench/reftests/boxshadow/rounding-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/rounding.yaml create mode 100644 third_party/webrender/wrench/reftests/boxshadow/scale.png create mode 100644 third_party/webrender/wrench/reftests/boxshadow/scale.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/blank.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/border-with-rounded-clip.png create mode 100644 third_party/webrender/wrench/reftests/clip/border-with-rounded-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/clip-3d-transform-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/clip-3d-transform.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/clip-45-degree-rotation-ref.png create mode 100644 third_party/webrender/wrench/reftests/clip/clip-45-degree-rotation.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/clip-and-filter-with-rotation-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/clip-and-filter-with-rotation.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/clip-corner-overlap-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/clip-corner-overlap.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/clip-ellipse.png create mode 100644 third_party/webrender/wrench/reftests/clip/clip-ellipse.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/clip-empty-inner-rect-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/clip-empty-inner-rect.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/clip-mode.png create mode 100644 third_party/webrender/wrench/reftests/clip/clip-mode.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/clip-out-rotation.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/clip-thin-rotated-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/clip-thin-rotated.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/clipped-occlusion-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/clipped-occlusion.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/custom-clip-chain-node-ancestors-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/custom-clip-chain-node-ancestors.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/custom-clip-chains-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/custom-clip-chains.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/fixed-position-clipping-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/fixed-position-clipping.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/iframe-nested-in-stacking-context-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/iframe-nested-in-stacking-context.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/reftest.list create mode 100644 third_party/webrender/wrench/reftests/clip/segmentation-across-rotation-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/segmentation-across-rotation.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/segmentation-with-other-coordinate-system-clip-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/segmentation-with-other-coordinate-system-clip.png create mode 100644 third_party/webrender/wrench/reftests/clip/segmentation-with-other-coordinate-system-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/snapping-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/snapping.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/stacking-context-clip-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/clip/stacking-context-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/backdrop-filter-basic-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/backdrop-filter-basic.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/backdrop-filter-perspective.png create mode 100644 third_party/webrender/wrench/reftests/filters/backdrop-filter-perspective.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/blank.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/blend-clipped.png create mode 100644 third_party/webrender/wrench/reftests/filters/blend-clipped.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-blur-clamping-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-blur-clamping.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-blur-huge.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-blur-scaled-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-blur-scaled-xonly.png create mode 100644 third_party/webrender/wrench/reftests/filters/filter-blur-scaled-xonly.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-blur-scaled.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-blur.png create mode 100644 third_party/webrender/wrench/reftests/filters/filter-blur.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-brightness-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-brightness-2.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-brightness-3-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-brightness-3.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-brightness-4-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-brightness-4.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-brightness-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-brightness.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-color-matrix-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-color-matrix.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-component-transfer-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-component-transfer.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-contrast-gray-alpha-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-contrast-gray-alpha-1.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-drop-shadow-blur-clamping-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-drop-shadow-blur-clamping.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-drop-shadow-clip-2.png create mode 100644 third_party/webrender/wrench/reftests/filters/filter-drop-shadow-clip-2.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-drop-shadow-clip-3.png create mode 100644 third_party/webrender/wrench/reftests/filters/filter-drop-shadow-clip-3.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-drop-shadow-clip.png create mode 100644 third_party/webrender/wrench/reftests/filters/filter-drop-shadow-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-drop-shadow-huge.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-drop-shadow-on-viewport-edge.png create mode 100644 third_party/webrender/wrench/reftests/filters/filter-drop-shadow-on-viewport-edge.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-drop-shadow-scaled-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-drop-shadow-scaled.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-drop-shadow-transform-huge.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-drop-shadow.png create mode 100644 third_party/webrender/wrench/reftests/filters/filter-drop-shadow.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-grayscale-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-grayscale.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-hue-rotate-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-hue-rotate-1.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-hue-rotate-alpha-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-hue-rotate-alpha-1.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-invert-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-invert-2.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-invert-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-invert.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-large-blur-radius.png create mode 100644 third_party/webrender/wrench/reftests/filters/filter-large-blur-radius.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-long-chain.png create mode 100644 third_party/webrender/wrench/reftests/filters/filter-long-chain.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-mix-blend-mode-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-mix-blend-mode.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-mix-blend-scaling-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-mix-blend-scaling.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-blue-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-blue-1.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-blue-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-blue-2.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-blue-3-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-blue-3.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-blue-alpha-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-blue-alpha-1.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-green-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-green-1.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-green-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-green-2.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-green-3-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-green-3.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-green-alpha-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-green-alpha-1.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-red-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-red-1.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-red-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-red-2.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-red-3-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-red-3.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-red-alpha-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-saturate-red-alpha-1.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-segments-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-segments.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/filter-small-blur-radius.png create mode 100644 third_party/webrender/wrench/reftests/filters/filter-small-blur-radius.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/firefox.png create mode 100644 third_party/webrender/wrench/reftests/filters/iframe-dropshadow-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/iframe-dropshadow.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/invisible-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/invisible.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/isolated-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/isolated.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/opacity-combined-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/opacity-combined.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/opacity-overlap-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/opacity-overlap.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/opacity-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/opacity.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/reftest.list create mode 100644 third_party/webrender/wrench/reftests/filters/srgb-to-linear-2.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/srgb-to-linear-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/srgb-to-linear.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-blend-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-blend.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-blur-transforms.png create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-blur-transforms.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-blur.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-color-matrix.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-component-transfer.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-composite-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-composite.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow-on-viewport-edge.png create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow-on-viewport-edge.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow-perspective.png create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow-perspective.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow-rotate-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow-rotate.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow.png create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-flood-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-flood.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-offset-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/svg-filter-offset.yaml create mode 100644 third_party/webrender/wrench/reftests/filters/svg-srgb-to-linear.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/conic-angle-wraparound-negative.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/conic-angle-wraparound.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/conic-angle.png create mode 100644 third_party/webrender/wrench/reftests/gradient/conic-angle.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/conic-backdrop-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/conic-backdrop-with-spacing-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/conic-backdrop-with-spacing.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/conic-backdrop.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/conic-center.png create mode 100644 third_party/webrender/wrench/reftests/gradient/conic-center.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/conic-color-wheel.png create mode 100644 third_party/webrender/wrench/reftests/gradient/conic-color-wheel.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/conic-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/conic-simple.png create mode 100644 third_party/webrender/wrench/reftests/gradient/conic-simple.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/conic.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/gradient_cache_5stops.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/gradient_cache_5stops_ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/gradient_cache_5stops_vertical.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/gradient_cache_5stops_vertical_ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/gradient_cache_clamp.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/gradient_cache_clamp_ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/gradient_cache_hardstop.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/gradient_cache_hardstop_clip.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/gradient_cache_hardstop_clip_ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/gradient_cache_hardstop_ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/gradient_cache_repeat.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/gradient_cache_repeat_ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-adjust-tile-size-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-adjust-tile-size.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-aligned-border-radius.png create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-aligned-border-radius.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-aligned-clip-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-aligned-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-backdrop-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-backdrop-with-spacing-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-backdrop-with-spacing.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-backdrop.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-clamp-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-clamp-1a.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-clamp-1b.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-clamp-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-clamp-2.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-double.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-hard-stop-ref.png create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-hard-stop.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-ref.png create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-reverse.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-stops-ref.png create mode 100644 third_party/webrender/wrench/reftests/gradient/linear-stops.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/linear.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-conic-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-conic-1.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-conic-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-conic-2.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-conic-3-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-conic-3.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-conic-4-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-conic-4.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-conic-degenerate-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-conic-degenerate.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-linear-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-linear-1.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-linear-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-linear-2.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-linear-3-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-linear-3.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-linear-4-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-linear-4.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-linear-degenerate-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-linear-degenerate.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-radial-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-radial-1.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-radial-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-radial-2.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-radial-3-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-radial-3.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-radial-degenerate-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/norm-radial-degenerate.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/premultiplied-aligned-2.png create mode 100644 third_party/webrender/wrench/reftests/gradient/premultiplied-aligned-2.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/premultiplied-aligned.png create mode 100644 third_party/webrender/wrench/reftests/gradient/premultiplied-aligned.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/premultiplied-angle-2.png create mode 100644 third_party/webrender/wrench/reftests/gradient/premultiplied-angle-2.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/premultiplied-angle.png create mode 100644 third_party/webrender/wrench/reftests/gradient/premultiplied-angle.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/premultiplied-conic-2.png create mode 100644 third_party/webrender/wrench/reftests/gradient/premultiplied-conic-2.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/premultiplied-conic.png create mode 100644 third_party/webrender/wrench/reftests/gradient/premultiplied-conic.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/premultiplied-radial-2.png create mode 100644 third_party/webrender/wrench/reftests/gradient/premultiplied-radial-2.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/premultiplied-radial.png create mode 100644 third_party/webrender/wrench/reftests/gradient/premultiplied-radial.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/radial-backdrop-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/radial-backdrop-with-spacing-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/radial-backdrop-with-spacing.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/radial-backdrop.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/radial-circle-ref.png create mode 100644 third_party/webrender/wrench/reftests/gradient/radial-circle.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/radial-ellipse-ref.png create mode 100644 third_party/webrender/wrench/reftests/gradient/radial-ellipse.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/radial-zero-size-1.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/radial-zero-size-2.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/radial-zero-size-3.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/radial-zero-size-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/reftest.list create mode 100644 third_party/webrender/wrench/reftests/gradient/repeat-border-radius.png create mode 100644 third_party/webrender/wrench/reftests/gradient/repeat-border-radius.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/repeat-conic-negative.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/repeat-conic-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/repeat-conic.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/repeat-linear-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/repeat-linear-reverse.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/repeat-linear.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/repeat-radial-negative.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/repeat-radial-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/repeat-radial.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-conic-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-conic-1.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-conic-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-conic-2.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-conic-3-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-conic-3.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-linear-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-linear-1.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-linear-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-linear-2.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-linear-3-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-linear-3.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-radial-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-radial-1.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-radial-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-radial-2.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-radial-3-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-radial-3.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-radial-4-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/gradient/tiling-radial-4.yaml create mode 100644 third_party/webrender/wrench/reftests/image/colorrect.png create mode 100644 third_party/webrender/wrench/reftests/image/downscale.png create mode 100644 third_party/webrender/wrench/reftests/image/downscale.yaml create mode 100644 third_party/webrender/wrench/reftests/image/firefox.png create mode 100644 third_party/webrender/wrench/reftests/image/occlusion.png create mode 100644 third_party/webrender/wrench/reftests/image/occlusion.yaml create mode 100644 third_party/webrender/wrench/reftests/image/reftest.list create mode 100644 third_party/webrender/wrench/reftests/image/rgb_composite.yaml create mode 100644 third_party/webrender/wrench/reftests/image/rgb_composite_ref.yaml create mode 100644 third_party/webrender/wrench/reftests/image/segments.png create mode 100644 third_party/webrender/wrench/reftests/image/segments.yaml create mode 100644 third_party/webrender/wrench/reftests/image/spacex-u.png create mode 100644 third_party/webrender/wrench/reftests/image/spacex-uv.png create mode 100644 third_party/webrender/wrench/reftests/image/spacex-v.png create mode 100644 third_party/webrender/wrench/reftests/image/spacex-y.png create mode 100644 third_party/webrender/wrench/reftests/image/spacex-yuv.png create mode 100644 third_party/webrender/wrench/reftests/image/texture-rect-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/image/texture-rect.yaml create mode 100644 third_party/webrender/wrench/reftests/image/tile-repeat-prim-or-decompose-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/image/tile-repeat-prim-or-decompose.yaml create mode 100644 third_party/webrender/wrench/reftests/image/tile-size-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/image/tile-size.yaml create mode 100644 third_party/webrender/wrench/reftests/image/tile-with-spacing-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/image/tile-with-spacing.yaml create mode 100644 third_party/webrender/wrench/reftests/image/tiled-clip-chain-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/image/tiled-clip-chain.yaml create mode 100644 third_party/webrender/wrench/reftests/image/tiled-complex-clip-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/image/tiled-complex-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/image/very-big-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/image/very-big-tile-size-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/image/very-big-tile-size.yaml create mode 100644 third_party/webrender/wrench/reftests/image/very-big.yaml create mode 100644 third_party/webrender/wrench/reftests/image/yuv.png create mode 100644 third_party/webrender/wrench/reftests/image/yuv.yaml create mode 100644 third_party/webrender/wrench/reftests/invalidation/one-rounded-rect-green.yaml create mode 100644 third_party/webrender/wrench/reftests/invalidation/one-rounded-rect.yaml create mode 100644 third_party/webrender/wrench/reftests/invalidation/reftest.list create mode 100644 third_party/webrender/wrench/reftests/invalidation/rounded-rects.yaml create mode 100644 third_party/webrender/wrench/reftests/invalidation/two-rounded-rects.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/aligned-layer-rect-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/aligned-layer-rect.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/checkerboard-tiling.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/checkerboard.png create mode 100644 third_party/webrender/wrench/reftests/mask/checkerboard.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/green.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/mask-atomicity-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/mask-atomicity-tiling.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/mask-atomicity.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/mask-perspective-tiling.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/mask-perspective.png create mode 100644 third_party/webrender/wrench/reftests/mask/mask-perspective.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/mask-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/mask-tiling.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/mask-transformed-to-empty-rect-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/mask-transformed-to-empty-rect.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/mask.png create mode 100644 third_party/webrender/wrench/reftests/mask/mask.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/missing-mask-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/missing-mask.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/nested-mask-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/nested-mask-tiling.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/nested-mask.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/out-of-bounds.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/reftest.list create mode 100644 third_party/webrender/wrench/reftests/mask/rounded-corners.png create mode 100644 third_party/webrender/wrench/reftests/mask/rounded-corners.yaml create mode 100644 third_party/webrender/wrench/reftests/mask/tiny-check-mask.png create mode 100644 third_party/webrender/wrench/reftests/performance/compositor-surface-opaque-slice-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/performance/compositor-surface-opaque-slice.yaml create mode 100644 third_party/webrender/wrench/reftests/performance/no-clip-mask.png create mode 100644 third_party/webrender/wrench/reftests/performance/no-clip-mask.yaml create mode 100644 third_party/webrender/wrench/reftests/performance/reftest.list create mode 100644 third_party/webrender/wrench/reftests/reftest.list create mode 100644 third_party/webrender/wrench/reftests/scrolling/clip-and-scroll-property-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/clip-and-scroll-property.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/empty-mask-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/empty-mask.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/ext-scroll-offset-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/ext-scroll-offset-1.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/fixed-position-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/fixed-position-scrolling-clip-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/fixed-position-scrolling-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/fixed-position.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/mask.png create mode 100644 third_party/webrender/wrench/reftests/scrolling/nested-scroll-offset-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/nested-scroll-offset.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/nested-stickys-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/nested-stickys.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/out-of-bounds-scroll-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/out-of-bounds-scroll.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/reftest.list create mode 100644 third_party/webrender/wrench/reftests/scrolling/root-scroll-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/root-scroll.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/scale-offsets-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/scale-offsets.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/scroll-layer-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/scroll-layer.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/sibling-hidden-clip-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/sibling-hidden-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/simple-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/simple.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/sticky-applied-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/sticky-applied.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/sticky-nested.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/sticky-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/sticky-transformed-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/sticky-transformed.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/sticky.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/translate-nested-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/translate-nested.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/viewport-offset-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/scrolling/viewport-offset.yaml create mode 100644 third_party/webrender/wrench/reftests/snap/preserve-3d.png create mode 100644 third_party/webrender/wrench/reftests/snap/preserve-3d.yaml create mode 100644 third_party/webrender/wrench/reftests/snap/reftest.list create mode 100644 third_party/webrender/wrench/reftests/snap/snap.png create mode 100644 third_party/webrender/wrench/reftests/snap/snap.yaml create mode 100644 third_party/webrender/wrench/reftests/snap/transform.png create mode 100644 third_party/webrender/wrench/reftests/snap/transform.yaml create mode 100644 third_party/webrender/wrench/reftests/split/cross-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/split/cross.yaml create mode 100644 third_party/webrender/wrench/reftests/split/filter-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/split/filter.yaml create mode 100644 third_party/webrender/wrench/reftests/split/gradient-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/split/gradient.yaml create mode 100644 third_party/webrender/wrench/reftests/split/intermediate-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/split/intermediate-1.yaml create mode 100644 third_party/webrender/wrench/reftests/split/intermediate-2.yaml create mode 100644 third_party/webrender/wrench/reftests/split/mixed-order-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/split/mixed-order.yaml create mode 100644 third_party/webrender/wrench/reftests/split/near-plane.png create mode 100644 third_party/webrender/wrench/reftests/split/near-plane.yaml create mode 100644 third_party/webrender/wrench/reftests/split/nested-coord-systems-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/split/nested-coord-systems.yaml create mode 100644 third_party/webrender/wrench/reftests/split/nested-preserve3d-crash.yaml create mode 100644 third_party/webrender/wrench/reftests/split/nested-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/split/nested.yaml create mode 100644 third_party/webrender/wrench/reftests/split/order-1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/split/order-1.yaml create mode 100644 third_party/webrender/wrench/reftests/split/order-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/split/order-2.yaml create mode 100644 third_party/webrender/wrench/reftests/split/order-3-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/split/order-3.yaml create mode 100644 third_party/webrender/wrench/reftests/split/ordering-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/split/ordering.yaml create mode 100644 third_party/webrender/wrench/reftests/split/perspective-clipping-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/split/perspective-clipping.yaml create mode 100644 third_party/webrender/wrench/reftests/split/reftest.list create mode 100644 third_party/webrender/wrench/reftests/split/same-plane.png create mode 100644 third_party/webrender/wrench/reftests/split/same-plane.yaml create mode 100644 third_party/webrender/wrench/reftests/split/simple-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/split/simple.yaml create mode 100644 third_party/webrender/wrench/reftests/split/split-intersect1-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/split/split-intersect1.yaml create mode 100644 third_party/webrender/wrench/reftests/text/1658-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/1658.yaml create mode 100644 third_party/webrender/wrench/reftests/text/Ahem.ttf create mode 100644 third_party/webrender/wrench/reftests/text/FreeSans.ttf create mode 100644 third_party/webrender/wrench/reftests/text/Proggy-License.txt create mode 100644 third_party/webrender/wrench/reftests/text/Proggy.ttf create mode 100644 third_party/webrender/wrench/reftests/text/VeraBd.ttf create mode 100644 third_party/webrender/wrench/reftests/text/ahem-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/ahem.yaml create mode 100644 third_party/webrender/wrench/reftests/text/allow-subpixel-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/allow-subpixel.yaml create mode 100644 third_party/webrender/wrench/reftests/text/alpha-transform.png create mode 100644 third_party/webrender/wrench/reftests/text/alpha-transform.yaml create mode 100644 third_party/webrender/wrench/reftests/text/bg-color-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/bg-color.yaml create mode 100644 third_party/webrender/wrench/reftests/text/blank.yaml create mode 100644 third_party/webrender/wrench/reftests/text/blurred-shadow-local-clip-rect-ref.png create mode 100644 third_party/webrender/wrench/reftests/text/blurred-shadow-local-clip-rect.yaml create mode 100644 third_party/webrender/wrench/reftests/text/border-radius-alpha.png create mode 100644 third_party/webrender/wrench/reftests/text/border-radius-subpx.png create mode 100644 third_party/webrender/wrench/reftests/text/border-radius.yaml create mode 100644 third_party/webrender/wrench/reftests/text/clipped-transform.png create mode 100644 third_party/webrender/wrench/reftests/text/clipped-transform.yaml create mode 100644 third_party/webrender/wrench/reftests/text/color-bitmap-shadow-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/color-bitmap-shadow.yaml create mode 100644 third_party/webrender/wrench/reftests/text/colors-alpha.png create mode 100644 third_party/webrender/wrench/reftests/text/colors-subpx.png create mode 100644 third_party/webrender/wrench/reftests/text/colors.yaml create mode 100644 third_party/webrender/wrench/reftests/text/decorations-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/decorations-suite.png create mode 100644 third_party/webrender/wrench/reftests/text/decorations-suite.yaml create mode 100644 third_party/webrender/wrench/reftests/text/decorations.yaml create mode 100644 third_party/webrender/wrench/reftests/text/diacritics-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/diacritics.yaml create mode 100644 third_party/webrender/wrench/reftests/text/embedded-bitmaps.png create mode 100644 third_party/webrender/wrench/reftests/text/embedded-bitmaps.yaml create mode 100644 third_party/webrender/wrench/reftests/text/intermediate-transform.yaml create mode 100644 third_party/webrender/wrench/reftests/text/isolated-text.png create mode 100644 third_party/webrender/wrench/reftests/text/isolated-text.yaml create mode 100644 third_party/webrender/wrench/reftests/text/large-glyphs.yaml create mode 100644 third_party/webrender/wrench/reftests/text/large-line-decoration.yaml create mode 100644 third_party/webrender/wrench/reftests/text/long-text.yaml create mode 100644 third_party/webrender/wrench/reftests/text/negative-pos.yaml create mode 100644 third_party/webrender/wrench/reftests/text/non-opaque-notref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/non-opaque.yaml create mode 100644 third_party/webrender/wrench/reftests/text/perspective-clip.png create mode 100644 third_party/webrender/wrench/reftests/text/perspective-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/text/raster-space-snap-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/raster-space-snap.yaml create mode 100644 third_party/webrender/wrench/reftests/text/raster-space.png create mode 100644 third_party/webrender/wrench/reftests/text/raster-space.yaml create mode 100644 third_party/webrender/wrench/reftests/text/raster_root_C_8192.yaml create mode 100644 third_party/webrender/wrench/reftests/text/raster_root_C_ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/reftest.list create mode 100644 third_party/webrender/wrench/reftests/text/shadow-atomic-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-atomic.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-border.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-clip-rect.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-clip-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-clipped-text.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-complex.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-cover-1.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-cover-2.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-fast-clip-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-fast-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-grey-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-grey-transparent.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-grey.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-huge-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-huge.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-image.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-many.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-ordering-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-ordering.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-partial-glyph-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-partial-glyph.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-red-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-red.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-rotate.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-single.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-solid-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow-transforms.png create mode 100644 third_party/webrender/wrench/reftests/text/shadow-transforms.yaml create mode 100644 third_party/webrender/wrench/reftests/text/shadow.yaml create mode 100644 third_party/webrender/wrench/reftests/text/snap-clip-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/snap-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/text/snap-text-offset-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/snap-text-offset.yaml create mode 100644 third_party/webrender/wrench/reftests/text/split-batch-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/split-batch.yaml create mode 100644 third_party/webrender/wrench/reftests/text/subpixel-rotate.png create mode 100644 third_party/webrender/wrench/reftests/text/subpixel-rotate.yaml create mode 100644 third_party/webrender/wrench/reftests/text/subpixel-scale.png create mode 100644 third_party/webrender/wrench/reftests/text/subpixel-scale.yaml create mode 100644 third_party/webrender/wrench/reftests/text/subpixel-skew.png create mode 100644 third_party/webrender/wrench/reftests/text/subpixel-skew.yaml create mode 100644 third_party/webrender/wrench/reftests/text/subpixel-translate-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/subpixel-translate.yaml create mode 100644 third_party/webrender/wrench/reftests/text/subtle-shadow-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/subtle-shadow.yaml create mode 100644 third_party/webrender/wrench/reftests/text/synthetic-bold-not-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/synthetic-bold-transparent-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/synthetic-bold-transparent.yaml create mode 100644 third_party/webrender/wrench/reftests/text/synthetic-bold.yaml create mode 100644 third_party/webrender/wrench/reftests/text/synthetic-italics-custom.yaml create mode 100644 third_party/webrender/wrench/reftests/text/synthetic-italics-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/synthetic-italics.yaml create mode 100644 third_party/webrender/wrench/reftests/text/text-fixed-slice-fast.png create mode 100644 third_party/webrender/wrench/reftests/text/text-fixed-slice-slow.png create mode 100644 third_party/webrender/wrench/reftests/text/text-fixed-slice.yaml create mode 100644 third_party/webrender/wrench/reftests/text/text-masking-alpha.png create mode 100644 third_party/webrender/wrench/reftests/text/text-masking-mask.png create mode 100644 third_party/webrender/wrench/reftests/text/text-masking-subpx.png create mode 100644 third_party/webrender/wrench/reftests/text/text-masking.yaml create mode 100644 third_party/webrender/wrench/reftests/text/text.yaml create mode 100644 third_party/webrender/wrench/reftests/text/transparent-no-aa-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/transparent-no-aa.yaml create mode 100644 third_party/webrender/wrench/reftests/text/two-shadows.png create mode 100644 third_party/webrender/wrench/reftests/text/two-shadows.yaml create mode 100644 third_party/webrender/wrench/reftests/text/white-opacity.png create mode 100644 third_party/webrender/wrench/reftests/text/white-opacity.yaml create mode 100644 third_party/webrender/wrench/reftests/text/writing-modes-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/text/writing-modes.yaml create mode 100644 third_party/webrender/wrench/reftests/tiles/prim-suite.yaml create mode 100644 third_party/webrender/wrench/reftests/tiles/rect.yaml create mode 100644 third_party/webrender/wrench/reftests/tiles/reftest.list create mode 100644 third_party/webrender/wrench/reftests/tiles/simple-gradient.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/big-axis-aligned-scale-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/big-axis-aligned-scale.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/blank.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/border-scale-2.png create mode 100644 third_party/webrender/wrench/reftests/transforms/border-scale-2.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/border-scale-3.png create mode 100644 third_party/webrender/wrench/reftests/transforms/border-scale-3.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/border-scale-4.png create mode 100644 third_party/webrender/wrench/reftests/transforms/border-scale-4.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/border-scale.png create mode 100644 third_party/webrender/wrench/reftests/transforms/border-scale.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/border-zoom.png create mode 100644 third_party/webrender/wrench/reftests/transforms/border-zoom.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/clip-translate-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/clip-translate.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/complex-preserve-3d.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/content-offset.png create mode 100644 third_party/webrender/wrench/reftests/transforms/content-offset.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/coord-system.png create mode 100644 third_party/webrender/wrench/reftests/transforms/coord-system.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/flatten-preserve-3d-root-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/flatten-preserve-3d-root.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/flatten-twice-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/flatten-twice.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/image-rotated-clip.png create mode 100644 third_party/webrender/wrench/reftests/transforms/image-rotated-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/image.png create mode 100644 third_party/webrender/wrench/reftests/transforms/large-raster-root.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/local-clip.png create mode 100644 third_party/webrender/wrench/reftests/transforms/local-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/near-plane-clip.png create mode 100644 third_party/webrender/wrench/reftests/transforms/near-plane-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/nested-preserve-3d.png create mode 100644 third_party/webrender/wrench/reftests/transforms/nested-preserve-3d.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/nested-rotate-x-flat.png create mode 100644 third_party/webrender/wrench/reftests/transforms/nested-rotate-x-flat.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/nested-rotate-x.png create mode 100644 third_party/webrender/wrench/reftests/transforms/nested-rotate-x.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/perspective-border-radius.png create mode 100644 third_party/webrender/wrench/reftests/transforms/perspective-border-radius.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/perspective-box-shadow-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/perspective-box-shadow.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/perspective-clip-1.png create mode 100644 third_party/webrender/wrench/reftests/transforms/perspective-clip-1.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/perspective-clip.png create mode 100644 third_party/webrender/wrench/reftests/transforms/perspective-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/perspective-mask.png create mode 100644 third_party/webrender/wrench/reftests/transforms/perspective-mask.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/perspective-origin.png create mode 100644 third_party/webrender/wrench/reftests/transforms/perspective-origin.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/perspective-shadow.png create mode 100644 third_party/webrender/wrench/reftests/transforms/perspective-shadow.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/perspective.png create mode 100644 third_party/webrender/wrench/reftests/transforms/perspective.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/prim-suite.png create mode 100644 third_party/webrender/wrench/reftests/transforms/prim-suite.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/raster-root-huge-scale.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/raster-root-large-mask.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/raster-root-scaling-2-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/raster-root-scaling-2.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/raster-root-scaling-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/raster-root-scaling.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/raster_root_A_8192.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/raster_root_A_ref.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/raster_root_B_8192.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/raster_root_B_ref.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/reftest.list create mode 100644 third_party/webrender/wrench/reftests/transforms/rotate-clip-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/rotate-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/rotated-clip-large.png create mode 100644 third_party/webrender/wrench/reftests/transforms/rotated-clip-large.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/rotated-clip.png create mode 100644 third_party/webrender/wrench/reftests/transforms/rotated-clip.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/rotated-image.png create mode 100644 third_party/webrender/wrench/reftests/transforms/rotated-image.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/screen-space-blit-trivial.png create mode 100644 third_party/webrender/wrench/reftests/transforms/screen-space-blit-trivial.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/screen-space-blit.png create mode 100644 third_party/webrender/wrench/reftests/transforms/screen-space-blit.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/screen-space-blur.png create mode 100644 third_party/webrender/wrench/reftests/transforms/screen-space-blur.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/segments-bug-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/segments-bug.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/singular-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/singular.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/snapped-preserve-3d-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/snapped-preserve-3d.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/strange-w-ref.yaml create mode 100644 third_party/webrender/wrench/reftests/transforms/strange-w.yaml create mode 100644 third_party/webrender/wrench/res/wrench.exe.manifest create mode 100644 third_party/webrender/wrench/script/benchmark_server.py create mode 100644 third_party/webrender/wrench/script/gen-many-images.py create mode 100755 third_party/webrender/wrench/script/headless.py create mode 100644 third_party/webrender/wrench/script/reftest-analyzer.xhtml create mode 100755 third_party/webrender/wrench/script/reftest-debugger.py create mode 100755 third_party/webrender/wrench/script/wrench_with_renderer.py create mode 100644 third_party/webrender/wrench/src/angle.rs create mode 100644 third_party/webrender/wrench/src/args.yaml create mode 100644 third_party/webrender/wrench/src/blob.rs create mode 100644 third_party/webrender/wrench/src/egl.rs create mode 100644 third_party/webrender/wrench/src/main.rs create mode 100644 third_party/webrender/wrench/src/parse_function.rs create mode 100644 third_party/webrender/wrench/src/perf.rs create mode 100644 third_party/webrender/wrench/src/png.rs create mode 100644 third_party/webrender/wrench/src/premultiply.rs create mode 100644 third_party/webrender/wrench/src/rawtest.rs create mode 100644 third_party/webrender/wrench/src/reftest.rs create mode 100644 third_party/webrender/wrench/src/wrench.rs create mode 100644 third_party/webrender/wrench/src/yaml_frame_reader.rs create mode 100644 third_party/webrender/wrench/src/yaml_helper.rs diff --git a/Cargo.lock b/Cargo.lock index 9ca73c7080b..4cb6c042dc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4396,7 +4396,6 @@ dependencies = [ [[package]] name = "peek-poke" version = "0.2.0" -source = "git+https://github.com/jdm/webrender?branch=crash-backtrace#415b9ba32648667313f6f4ce7965752285bf0b26" dependencies = [ "euclid", "peek-poke-derive", @@ -4405,7 +4404,6 @@ dependencies = [ [[package]] name = "peek-poke-derive" version = "0.2.1" -source = "git+https://github.com/jdm/webrender?branch=crash-backtrace#415b9ba32648667313f6f4ce7965752285bf0b26" dependencies = [ "proc-macro2", "quote", @@ -6990,7 +6988,6 @@ dependencies = [ [[package]] name = "webrender" version = "0.61.0" -source = "git+https://github.com/jdm/webrender?branch=crash-backtrace#415b9ba32648667313f6f4ce7965752285bf0b26" dependencies = [ "backtrace", "base64 0.10.1", @@ -7034,7 +7031,6 @@ dependencies = [ [[package]] name = "webrender_api" version = "0.61.0" -source = "git+https://github.com/jdm/webrender?branch=crash-backtrace#415b9ba32648667313f6f4ce7965752285bf0b26" dependencies = [ "app_units", "bitflags", @@ -7055,7 +7051,6 @@ dependencies = [ [[package]] name = "webrender_build" version = "0.0.1" -source = "git+https://github.com/jdm/webrender?branch=crash-backtrace#415b9ba32648667313f6f4ce7965752285bf0b26" dependencies = [ "bitflags", "lazy_static", @@ -7320,7 +7315,6 @@ dependencies = [ [[package]] name = "wr_malloc_size_of" version = "0.0.1" -source = "git+https://github.com/jdm/webrender?branch=crash-backtrace#415b9ba32648667313f6f4ce7965752285bf0b26" dependencies = [ "app_units", "euclid", diff --git a/Cargo.toml b/Cargo.toml index 62b661f5acd..db89ad5fa9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,8 @@ unicode-script = "0.5" url = "2.0" uuid = { version = "1.3.4", features = ["v4"] } webdriver = "0.48.0" +webrender = { git = "https://github.com/servo/webrender", features = ["capture"] } +webrender_api = { git = "https://github.com/servo/webrender" } winapi = "0.3" xi-unicode = "0.1.0" xml5ever = "0.17" @@ -104,7 +106,9 @@ mio = { git = "https://github.com/servo/mio.git", branch = "servo-mio-0.6.22" } # fork that bumps crates since the original repo is archived. immeta = { git = "https://github.com/fabricedesre/immeta.git" } -# https://github.com/servo/servo/issues/27515#issuecomment-671474054 + +# This is required because we want all dependencies that use WebRender to +# use our vendored version. [patch."https://github.com/servo/webrender"] -webrender = { git = "https://github.com/jdm/webrender", branch = "crash-backtrace" } -webrender_api = { git = "https://github.com/jdm/webrender", branch = "crash-backtrace" } +webrender = { path = "third_party/webrender/webrender" } +webrender_api = { path = "third_party/webrender/webrender_api" } diff --git a/components/canvas/Cargo.toml b/components/canvas/Cargo.toml index c7e7c7c8abe..f7ff8629da4 100644 --- a/components/canvas/Cargo.toml +++ b/components/canvas/Cargo.toml @@ -44,8 +44,8 @@ surfman = { workspace = true, features = ["sm-angle","sm-angle-default"] } surfman-chains = { workspace = true } surfman-chains-api = { workspace = true } time = { workspace = true, optional = true } -webrender = { git = "https://github.com/servo/webrender" } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender = { workspace = true } +webrender_api = { workspace = true } webrender_surfman = { path = "../webrender_surfman" } webrender_traits = { path = "../webrender_traits" } webxr-api = { git = "https://github.com/servo/webxr", features = ["ipc"] } diff --git a/components/canvas_traits/Cargo.toml b/components/canvas_traits/Cargo.toml index 4db44f09eb1..9199be4edf3 100644 --- a/components/canvas_traits/Cargo.toml +++ b/components/canvas_traits/Cargo.toml @@ -29,5 +29,5 @@ servo_config = { path = "../config" } sparkle = { workspace = true } style = { path = "../style" } time = { workspace = true, optional = true } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender_api = { workspace = true } webxr-api = { git = "https://github.com/servo/webxr", features = ["ipc"] } diff --git a/components/compositing/Cargo.toml b/components/compositing/Cargo.toml index 92b86235471..6421de7db7e 100644 --- a/components/compositing/Cargo.toml +++ b/components/compositing/Cargo.toml @@ -39,8 +39,8 @@ servo_geometry = { path = "../geometry" } servo_url = { path = "../url" } style_traits = { path = "../style_traits" } time = { workspace = true } -webrender = { git = "https://github.com/servo/webrender", features = ["capture"] } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender = { workspace = true } +webrender_api = { workspace = true } webrender_surfman = { path = "../webrender_surfman" } webxr = { git = "https://github.com/servo/webxr" } diff --git a/components/constellation/Cargo.toml b/components/constellation/Cargo.toml index dac014e178d..3704d404b62 100644 --- a/components/constellation/Cargo.toml +++ b/components/constellation/Cargo.toml @@ -42,7 +42,7 @@ servo_remutex = { path = "../remutex" } servo_url = { path = "../url" } style_traits = { path = "../style_traits" } webgpu = { path = "../webgpu" } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender_api = { workspace = true } webrender_traits = { path = "../webrender_traits" } webxr-api = { git = "https://github.com/servo/webxr", features = ["ipc"] } diff --git a/components/embedder_traits/Cargo.toml b/components/embedder_traits/Cargo.toml index 430a57090f5..82621113882 100644 --- a/components/embedder_traits/Cargo.toml +++ b/components/embedder_traits/Cargo.toml @@ -21,5 +21,5 @@ num-derive = "0.3" num-traits = { workspace = true } serde = { workspace = true } servo_url = { path = "../url" } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender_api = { workspace = true } webxr-api = { git = "https://github.com/servo/webxr", features = ["ipc"] } diff --git a/components/gfx/Cargo.toml b/components/gfx/Cargo.toml index 294ec44d16a..924d39902ff 100644 --- a/components/gfx/Cargo.toml +++ b/components/gfx/Cargo.toml @@ -38,7 +38,7 @@ time = { workspace = true } ucd = "0.1.1" unicode-bidi = { workspace = true, features = ["with_serde"] } unicode-script = { workspace = true } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender_api = { workspace = true } xi-unicode = { workspace = true } [target.'cfg(target_os = "macos")'.dependencies] diff --git a/components/gfx_traits/Cargo.toml b/components/gfx_traits/Cargo.toml index 142a644e394..8163bb49b5d 100644 --- a/components/gfx_traits/Cargo.toml +++ b/components/gfx_traits/Cargo.toml @@ -15,4 +15,4 @@ malloc_size_of = { path = "../malloc_size_of" } malloc_size_of_derive = { workspace = true } range = { path = "../range" } serde = { workspace = true } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender_api = { workspace = true } diff --git a/components/layout/Cargo.toml b/components/layout/Cargo.toml index 9e5d56a49d9..c73e56e186c 100644 --- a/components/layout/Cargo.toml +++ b/components/layout/Cargo.toml @@ -49,7 +49,7 @@ style = { path = "../style", features = ["servo", "servo-layout-2013"] } style_traits = { path = "../style_traits" } unicode-bidi = { workspace = true, features = ["with_serde"] } unicode-script = { workspace = true } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender_api = { workspace = true } xi-unicode = { workspace = true } [dev-dependencies] diff --git a/components/layout_2020/Cargo.toml b/components/layout_2020/Cargo.toml index 701eebe1d7e..ca7ca77bfe3 100644 --- a/components/layout_2020/Cargo.toml +++ b/components/layout_2020/Cargo.toml @@ -44,7 +44,7 @@ servo_url = { path = "../url" } style = { path = "../style", features = ["servo", "servo-layout-2020"] } style_traits = { path = "../style_traits" } unicode-script = { workspace = true } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender_api = { workspace = true } [dev-dependencies] lazy_static = { workspace = true } diff --git a/components/layout_thread/Cargo.toml b/components/layout_thread/Cargo.toml index 3e47a9ebe7d..9f9dbee4d1f 100644 --- a/components/layout_thread/Cargo.toml +++ b/components/layout_thread/Cargo.toml @@ -49,4 +49,4 @@ servo_url = { path = "../url" } style = { path = "../style" } style_traits = { path = "../style_traits" } time = { workspace = true } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender_api = { workspace = true } diff --git a/components/layout_thread_2020/Cargo.toml b/components/layout_thread_2020/Cargo.toml index 72a7c0f7230..e94ece4f934 100644 --- a/components/layout_thread_2020/Cargo.toml +++ b/components/layout_thread_2020/Cargo.toml @@ -45,4 +45,4 @@ servo_config = { path = "../config" } servo_url = { path = "../url" } style = { path = "../style" } style_traits = { path = "../style_traits" } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender_api = { workspace = true } diff --git a/components/layout_traits/Cargo.toml b/components/layout_traits/Cargo.toml index 2fe80f7cbbf..532d358fd42 100644 --- a/components/layout_traits/Cargo.toml +++ b/components/layout_traits/Cargo.toml @@ -20,4 +20,4 @@ net_traits = { path = "../net_traits" } profile_traits = { path = "../profile_traits" } script_traits = { path = "../script_traits" } servo_url = { path = "../url" } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender_api = { workspace = true } diff --git a/components/malloc_size_of/Cargo.toml b/components/malloc_size_of/Cargo.toml index 23d7a79d371..e90b36b5104 100644 --- a/components/malloc_size_of/Cargo.toml +++ b/components/malloc_size_of/Cargo.toml @@ -49,5 +49,5 @@ tokio = { workspace = true } url = { workspace = true, optional = true } uuid = { workspace = true, optional = true } void = "1.0.2" -webrender_api = { git = "https://github.com/servo/webrender", optional = true } +webrender_api = { workspace = true, optional = true } xml5ever = { workspace = true, optional = true } diff --git a/components/media/Cargo.toml b/components/media/Cargo.toml index 9b600270bf6..e667638e1ba 100644 --- a/components/media/Cargo.toml +++ b/components/media/Cargo.toml @@ -19,5 +19,5 @@ log = { workspace = true } serde = { workspace = true } servo-media = { git = "https://github.com/servo/media" } servo_config = { path = "../config" } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender_api = { workspace = true } webrender_traits = { path = "../webrender_traits" } diff --git a/components/msg/Cargo.toml b/components/msg/Cargo.toml index 9f076c63bf1..8bcf5680a2d 100644 --- a/components/msg/Cargo.toml +++ b/components/msg/Cargo.toml @@ -19,7 +19,7 @@ malloc_size_of = { path = "../malloc_size_of" } malloc_size_of_derive = { workspace = true } parking_lot = { workspace = true } serde = { workspace = true } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender_api = { workspace = true } [dev-dependencies] size_of_test = { path = "../size_of_test" } diff --git a/components/net/Cargo.toml b/components/net/Cargo.toml index 773546efa7f..f54ffe4d7be 100644 --- a/components/net/Cargo.toml +++ b/components/net/Cargo.toml @@ -65,7 +65,7 @@ tokio-stream = "0.1" tungstenite = "0.19" url = { workspace = true } uuid = { workspace = true } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender_api = { workspace = true } [dev-dependencies] futures = {version = "0.3", features = ["compat"]} diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index c1ad657c685..d4095bf6f19 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -117,7 +117,7 @@ utf-8 = "0.7" uuid = { workspace = true, features = ["serde"] } webdriver = { workspace = true } webgpu = { path = "../webgpu" } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender_api = { workspace = true } webxr-api = { git = "https://github.com/servo/webxr", features = ["ipc"] } xml5ever = { workspace = true } diff --git a/components/script_traits/Cargo.toml b/components/script_traits/Cargo.toml index 2c79116a48c..f9f79814a9c 100644 --- a/components/script_traits/Cargo.toml +++ b/components/script_traits/Cargo.toml @@ -43,7 +43,7 @@ time = { workspace = true } uuid = { workspace = true } webdriver = { workspace = true } webgpu = { path = "../webgpu" } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender_api = { workspace = true } webxr-api = { git = "https://github.com/servo/webxr", features = ["ipc"] } [dev-dependencies] diff --git a/components/servo/Cargo.toml b/components/servo/Cargo.toml index 017114ccc92..2d3d46cc8c0 100644 --- a/components/servo/Cargo.toml +++ b/components/servo/Cargo.toml @@ -81,8 +81,8 @@ style_traits = { path = "../style_traits", features = ["servo"] } surfman = { workspace = true } webdriver_server = { path = "../webdriver_server", optional = true } webgpu = { path = "../webgpu" } -webrender = { git = "https://github.com/servo/webrender" } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender = { workspace = true } +webrender_api = { workspace = true } webrender_surfman = { path = "../webrender_surfman" } webrender_traits = { path = "../webrender_traits" } webxr = { git = "https://github.com/servo/webxr" } diff --git a/components/style_traits/Cargo.toml b/components/style_traits/Cargo.toml index f7cb05c1a3a..779bb5487b5 100644 --- a/components/style_traits/Cargo.toml +++ b/components/style_traits/Cargo.toml @@ -29,4 +29,4 @@ servo_atoms = { path = "../atoms", optional = true } servo_url = { path = "../url", optional = true } to_shmem = { path = "../to_shmem" } to_shmem_derive = { path = "../to_shmem_derive" } -webrender_api = { git = "https://github.com/servo/webrender", optional = true } +webrender_api = { workspace = true, optional = true } diff --git a/components/webgpu/Cargo.toml b/components/webgpu/Cargo.toml index 868f9c656c4..c096852c687 100644 --- a/components/webgpu/Cargo.toml +++ b/components/webgpu/Cargo.toml @@ -20,7 +20,7 @@ msg = { path = "../msg" } serde = { workspace = true, features = ["serde_derive"] } servo_config = { path = "../config" } smallvec = { workspace = true, features = ["serde"] } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender_api = { workspace = true } webrender_traits = { path = "../webrender_traits" } wgpu-core = { version = "0.6.0", git = "https://github.com/gfx-rs/wgpu", features = ["replay", "trace", "serial-pass"], rev = "e72724a6e393503c73f37e86aa9317a5c62e32b8" } wgpu-types = { version = "0.6.0", git = "https://github.com/gfx-rs/wgpu", features = ["replay", "trace"], rev = "e72724a6e393503c73f37e86aa9317a5c62e32b8" } diff --git a/components/webrender_traits/Cargo.toml b/components/webrender_traits/Cargo.toml index 69af8e92d3a..98dd3c4e5e4 100644 --- a/components/webrender_traits/Cargo.toml +++ b/components/webrender_traits/Cargo.toml @@ -12,5 +12,5 @@ path = "lib.rs" [dependencies] euclid = { workspace = true } -webrender_api = { git = "https://github.com/servo/webrender" } +webrender_api = { workspace = true } diff --git a/rustfmt.toml b/rustfmt.toml index de839baed13..90fcdc76cd8 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,3 +1,4 @@ match_block_trailing_comma = true binop_separator = "Back" reorder_imports = true +ignore = [ "third_party" ] diff --git a/third_party/webrender/.gitignore b/third_party/webrender/.gitignore new file mode 100644 index 00000000000..849001b76c2 --- /dev/null +++ b/third_party/webrender/.gitignore @@ -0,0 +1,27 @@ +target/ +*~ +*# + +# WR internals +captures +wrench/json_frames +wrench/ron_frames + +# Editors +*.swp +*.swo + +# IntelliJ +.idea +*.iws +*.iml + +# Gradle +.gradle + +# VSCode +.vscode +.vs + +# System +.fuse_hidden* diff --git a/third_party/webrender/.taskcluster.yml b/third_party/webrender/.taskcluster.yml new file mode 100644 index 00000000000..f7289169e50 --- /dev/null +++ b/third_party/webrender/.taskcluster.yml @@ -0,0 +1,176 @@ +# This file specifies the configuration needed to test WebRender using the +# Taskcluster infrastructure. Most of this should be relatively +# self-explanatory; this file was originally generated by using the +# Taskcluster-GitHub integration quickstart tool which no longer exists. +# +# See https://community-tc.services.mozilla.com/docs +version: 1 +policy: + pullRequests: public + +# This file triggers a set of tasks; the ones targeting Linux are run in a docker +# container using docker-worker (which is a worker type provided by TaskCluster). +# The OS X ones are run in a custom worker type, for which we have worker +# instances configured and running. +tasks: + $if: 'tasks_for in ["github-push", "github-pull-request"]' + then: + $let: + should_run: + $if: 'tasks_for == "github-push"' + # for pushes, run on any branch but master + then: {$eval: 'event.ref != "refs/heads/master"'} + # for PRs, run for opened and synchronized events + else: {$eval: 'event.action in ["opened", "synchronize"]'} + repo_url: + $if: 'tasks_for == "github-push"' + then: ${event.repository.clone_url} + else: ${event.pull_request.head.repo.clone_url} + sha: + $if: 'tasks_for == "github-push"' + then: ${event.after} + else: ${event.pull_request.head.sha} + login: ${event.sender.login} + branch: + $if: 'tasks_for == "github-push"' + then: + $if: 'event.ref[:11] == "refs/heads/"' + then: ${event.ref[11:]} + else: ${event.ref} + else: ${event.pull_request.head.ref} + in: + $if: should_run + then: + # For the docker-worker tasks, the Docker image used + # (staktrace/webrender-test:debian-v6) was created using the Dockerfile in + # ci-scripts/docker-image. + # + # The docker image may need to be updated over time if the set of required + # packages increases. Note in particular that rust/cargo are not part of the + # docker image, and are re-installed using rustup on each CI run. This ensures + # the latest stable rust compiler is always used. + # CI runs will be triggered on opening PRs, updating PRs, and pushes to the + # repository. + - metadata: + name: Linux release tests + description: Runs release-mode WebRender CI stuff on a Linux TC worker + owner: noreply@mozilla.com + source: ${repo_url} + provisionerId: proj-webrender + workerType: ci-linux + deadline: {$fromNow: '1 day'} + payload: + maxRunTime: 7200 + image: 'staktrace/webrender-test:debian-v6' + env: + RUST_BACKTRACE: 'full' + RUSTFLAGS: '--deny warnings' + command: + - /bin/bash + - '--login' + - '-c' + - >- + curl https://sh.rustup.rs -sSf | sh -s -- -y && + source $HOME/.cargo/env && + git clone ${repo_url} webrender && cd webrender && + git checkout ${sha} && + servo-tidy && + ci-scripts/linux-release-tests.sh + routes: + - "index.project.webrender.ci.${login}.${branch}.linux-release" + - metadata: + name: Linux debug tests + description: Runs debug-mode WebRender CI stuff on a Linux TC worker + owner: noreply@mozilla.com + source: ${repo_url} + provisionerId: proj-webrender + workerType: ci-linux + deadline: {$fromNow: '1 day'} + payload: + maxRunTime: 7200 + image: 'staktrace/webrender-test:debian-v6' + env: + RUST_BACKTRACE: 'full' + RUSTFLAGS: '--deny warnings' + command: + - /bin/bash + - '--login' + - '-c' + - >- + curl https://sh.rustup.rs -sSf | sh -s -- -y && + source $HOME/.cargo/env && + git clone ${repo_url} webrender && cd webrender && + git checkout ${sha} && + servo-tidy && + ci-scripts/linux-debug-tests.sh + routes: + - "index.project.webrender.ci.${login}.${branch}.linux-debug" + # For the OS X jobs we use a pool of machines that we are managing, because + # Mozilla releng doesn't have any spare OS X machines for us at this time. + # Talk to :kats or :jrmuizel if you need more details about this. The machines + # are hooked up to taskcluster using taskcluster-worker; they use a workerpool + # of proj-webrender/ci-macos. They are set up with all the dependencies needed + # to build and test webrender, including Rust (currently 1.41.1), servo-tidy, + # mako, zlib, etc. Note that unlike the docker-worker used for Linux, these + # machines WILL persist state from one run to the next, so any cleanup needs + # to be handled explicitly. + + # macOS tasks currently disabled, see bug 1639217. + # - metadata: + # name: OS X release tests + # description: Runs release-mode WebRender CI stuff on a OS X TC worker + # owner: noreply@mozilla.com + # source: ${repo_url} + # provisionerId: 'proj-webrender' + # workerType: 'ci-macos' + # deadline: {$fromNow: '1 day'} + # payload: + # maxRunTime: 3600 + # command: + # - - /bin/bash + # - '--login' + # - '-vec' + # - | + # git clone ${repo_url} webrender + # cd webrender + # git checkout ${sha} + # source $HOME/servotidy-venv/bin/activate + # servo-tidy + # export RUST_BACKTRACE=full + # export RUSTFLAGS='--deny warnings' + # export PKG_CONFIG_PATH="/usr/local/opt/zlib/lib/pkgconfig:$PKG_CONFIG_PATH" + # echo 'exec make -j1 "$@"' > $HOME/make # See #2638 + # chmod +x $HOME/make + # export MAKE="$HOME/make" + # ci-scripts/macos-release-tests.sh + # routes: + # - "index.project.webrender.ci.${login}.${branch}.osx-release" + # - metadata: + # name: OS X debug tests + # description: Runs debug-mode WebRender CI stuff on a OS X TC worker + # owner: noreply@mozilla.com + # source: ${repo_url} + # provisionerId: 'proj-webrender' + # workerType: 'ci-macos' + # deadline: {$fromNow: '1 day'} + # payload: + # maxRunTime: 3600 + # command: + # - - /bin/bash + # - '--login' + # - '-vec' + # - | + # git clone ${repo_url} webrender + # cd webrender + # git checkout ${sha} + # source $HOME/servotidy-venv/bin/activate + # servo-tidy + # export RUST_BACKTRACE=full + # export RUSTFLAGS='--deny warnings' + # export PKG_CONFIG_PATH="/usr/local/opt/zlib/lib/pkgconfig:$PKG_CONFIG_PATH" + # echo 'exec make -j1 "$@"' > $HOME/make # See #2638 + # chmod +x $HOME/make + # export MAKE="$HOME/make" + # ci-scripts/macos-debug-tests.sh + # routes: + # - "index.project.webrender.ci.${login}.${branch}.osx-debug" diff --git a/third_party/webrender/Cargo.lock b/third_party/webrender/Cargo.lock new file mode 100644 index 00000000000..b7055733e57 --- /dev/null +++ b/third_party/webrender/Cargo.lock @@ -0,0 +1,2267 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "adler32" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "aho-corasick" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "andrew" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "line_drawing 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rusttype 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "xml-rs 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "android_glue" +version = "0.2.3" +source = "git+https://github.com/rust-windowing/android-rs-glue.git?rev=e3ac6edea5814e1faca0c31ea8fac6877cb929ea#e3ac6edea5814e1faca0c31ea8fac6877cb929ea" + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "app_units" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "approx" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hermit-abi 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "backtrace" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace-sys 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "base64" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "binary-space-partition" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bincode" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "build-parallel" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "jobserver 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bytemuck" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cc" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "jobserver 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cgl" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gleam 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "chrono" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "clap" +version = "2.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cmake" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cocoa" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "block 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "objc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "compositor" +version = "0.1.0" +dependencies = [ + "compositor-windows 0.1.0", + "gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "webrender 0.61.0", +] + +[[package]] +name = "compositor-windows" +version = "0.1.0" +dependencies = [ + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation-sys" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "core-foundation-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "core-graphics" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-graphics" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core-graphics-types 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-text" +version = "19.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core-graphics 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crc32fast" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "crossbeam-deque" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-queue" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cstr" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cstr-macros 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "procedural-masquerade 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cstr-macros" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "procedural-masquerade 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "deflate" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "derive_more" +version = "0.99.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "direct-composition" +version = "0.1.0" +dependencies = [ + "euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mozangle 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "webrender 0.61.0", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winit 0.19.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dlib" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "downcast-rs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "dwrote" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "wio 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "env_logger" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "euclid" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "expat-sys" +version = "2.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cmake 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "font-loader" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core-text 19.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "servo-fontconfig 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "freetype" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "freetype-sys 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "freetype-sys" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cmake 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "getrandom" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gl_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "khronos_api 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "xml-rs 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "khronos_api 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "xml-rs 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gleam" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gl_generator 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gleam" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gl_generator 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "glsl" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "glsl-to-cxx" +version = "0.1.0" +dependencies = [ + "glsl 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "glslopt" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "glutin" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "android_glue 0.2.3 (git+https://github.com/rust-windowing/android-rs-glue.git?rev=e3ac6edea5814e1faca0c31ea8fac6877cb929ea)", + "cgl 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cocoa 0.18.5 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)", + "glutin_egl_sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "glutin_emscripten_sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glutin_gles2_sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "glutin_glx_sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "glutin_wgl_sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "objc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-client 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winit 0.19.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gl_generator 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "glutin_emscripten_sys" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "glutin_gles2_sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gl_generator 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "objc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gl_generator 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "x11-dl 2.18.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gl_generator 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hermit-abi" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "httparse" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "image" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "num-rational 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "png 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "inflate" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itoa" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "jobserver" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazycell" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libloading" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "line_drawing" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lock_api" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "malloc_size_of_derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memoffset" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "minidl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "mio" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mio-extras" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mozangle" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", + "gl_generator 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "net2" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nix" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nom" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-iter" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num_cpus" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hermit-abi 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ordered-float" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "osmesa-src" +version = "0.1.1" +source = "git+https://github.com/servo/osmesa-src#1a9519c3675ebc1117cbb18ed6db420b5941cb8b" + +[[package]] +name = "osmesa-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "shared_library 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "peek-poke" +version = "0.2.0" +dependencies = [ + "euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "peek-poke-derive 0.2.1", +] + +[[package]] +name = "peek-poke-derive" +version = "0.2.1" +dependencies = [ + "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pkg-config" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "plane-split" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "binary-space-partition 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "png" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "deflate 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", + "inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro2" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "procedural-masquerade" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "raw-window-handle" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon-core" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "regex" +version = "1.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.17" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ron" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rusttype" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rusttype 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rusttype" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "approx 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "stb_truetype 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ryu" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-util 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_bytes" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "servo-fontconfig" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "servo-fontconfig-sys 5.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "servo-fontconfig-sys" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "expat-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "freetype-sys 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "shared_library" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sig" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "smallvec" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "smallvec" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "andrew 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dlib 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-client 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-commons 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-protocols 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "stb_truetype" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "svg_fmt" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "swgl" +version = "0.1.0" +dependencies = [ + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glsl-to-cxx 0.1.0", + "webrender_build 0.0.1", +] + +[[package]] +name = "syn" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synstructure" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termcolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-util 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tileview" +version = "0.1.0" +dependencies = [ + "euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", + "webrender 0.61.0", + "webrender_api 0.61.0", +] + +[[package]] +name = "time" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tracy-rs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "minidl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "typenum" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "url" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "version_check" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "walkdir" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "wayland-client" +version = "0.21.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "downcast-rs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-commons 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-scanner 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-sys 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wayland-commons" +version = "0.21.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-sys 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wayland-protocols" +version = "0.21.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-client 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-commons 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-scanner 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-sys 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wayland-scanner" +version = "0.21.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "xml-rs 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wayland-sys" +version = "0.21.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dlib 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "webrender" +version = "0.61.0" +dependencies = [ + "backtrace 0.3.46 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "build-parallel 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core-graphics 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core-text 19.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cstr 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "dwrote 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "freetype 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glslopt 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "image 0.23.3 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "malloc_size_of_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mozangle 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "plane-split 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", + "png 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)", + "sig 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "svg_fmt 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "tracy-rs 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "webrender_api 0.61.0", + "webrender_build 0.0.1", + "wr_malloc_size_of 0.0.1", + "ws 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "webrender-examples" +version = "0.1.0" +dependencies = [ + "app_units 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", + "euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glutin 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "webrender 0.61.0", + "winit 0.19.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "webrender_api" +version = "0.61.0" +dependencies = [ + "app_units 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core-graphics 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)", + "euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "malloc_size_of_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "peek-poke 0.2.0", + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_bytes 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "wr_malloc_size_of 0.0.1", +] + +[[package]] +name = "webrender_build" +version = "0.0.1" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-util" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winit" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "android_glue 0.2.3 (git+https://github.com/rust-windowing/android-rs-glue.git?rev=e3ac6edea5814e1faca0c31ea8fac6877cb929ea)", + "backtrace 0.3.46 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cocoa 0.18.5 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "objc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "raw-window-handle 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "smithay-client-toolkit 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-client 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "x11-dl 2.18.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wr_malloc_size_of" +version = "0.0.1" +dependencies = [ + "app_units 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wrench" +version = "0.3.0" +dependencies = [ + "app_units 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core-graphics 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "dwrote 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", + "euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "font-loader 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glutin 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)", + "image 0.23.3 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mozangle 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "osmesa-src 0.1.1 (git+https://github.com/servo/osmesa-src)", + "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)", + "swgl 0.1.0", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "webrender 0.61.0", + "webrender_api 0.61.0", + "winit 0.19.5 (registry+https://github.com/rust-lang/crates.io-index)", + "yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ws" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-extras 2.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "x11-dl" +version = "2.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "xdg" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "xml-rs" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "yaml-rust" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "yaml-rust" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" +"checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" +"checksum andrew 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9b7f09f89872c2b6b29e319377b1fbe91c6f5947df19a25596e121cf19a7b35e" +"checksum android_glue 0.2.3 (git+https://github.com/rust-windowing/android-rs-glue.git?rev=e3ac6edea5814e1faca0c31ea8fac6877cb929ea)" = "" +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum app_units 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fc3ec9d4c47b25a5a9e5c848e053640331c7cedb1637434d75db68b79fee8a7f" +"checksum approx 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" +"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +"checksum backtrace 0.3.46 (registry+https://github.com/rust-lang/crates.io-index)" = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e" +"checksum backtrace-sys 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "7de8aba10a69c8e8d7622c5710229485ec32e9d55fdad160ea559c086fdcd118" +"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +"checksum binary-space-partition 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "88ceb0d16c4fd0e42876e298d7d3ce3780dd9ebdcbe4199816a32c77e08597ff" +"checksum bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf" +"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +"checksum block 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +"checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +"checksum build-parallel 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4b5f8b148c1884e660514fb1205f36b595e96df4daac5ad526a23db44937de77" +"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" +"checksum bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37fa13df2292ecb479ec23aa06f4507928bef07839be9ef15281411076629431" +"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +"checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" +"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +"checksum cgl 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "55e7ec0b74fe5897894cbc207092c577e87c52f8a59e8ca8d97ef37551f60a49" +"checksum chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00" +"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum cmake 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "81fb25b677f8bf1eb325017cb6bb8452f87969db0fedb4f757b297bee78a7c62" +"checksum cocoa 0.18.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1706996401131526e36b3b49f0c4d912639ce110996f3ca144d78946727bce54" +"checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" +"checksum core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +"checksum core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b5ed8e7e76c45974e15e41bfa8d5b0483cd90191639e01d8f5f1e606299d3fb" +"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" +"checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" +"checksum core-foundation-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9a21fa21941700a3cd8fcb4091f361a6a712fac632f85d9f487cc892045d55c6" +"checksum core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)" = "56790968ab1c8a1202a102e6de05fc6e1ec87da99e4e93e9a7d13efbfc1e95a9" +"checksum core-graphics 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f6082396a349fa49674ba1bda4077332a18bf150e8fa75745ece07085e29a113" +"checksum core-graphics-types 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e92f5d519093a4178296707dbaa3880eae85a5ef5386675f361a1cf25376e93c" +"checksum core-text 19.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "04dfae50af11e72657fe7174cddb1ecddc5398037f7f6f39533ad69207c9a4e2" +"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +"checksum crossbeam 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "bd66663db5a988098a89599d4857919b3acf7f61402e61365acfd3919857b9be" +"checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" +"checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +"checksum crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" +"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +"checksum cstr 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "19f7a08ed4ecd7e077d4cee63937473e6f7cf57b702a9114ef41751b2cbc0f60" +"checksum cstr-macros 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "cd670e5ff58768ef624207fb95709ce63b8d05573fb9a05165f0eef471ea6a3a" +"checksum deflate 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e7e5d2a2273fed52a7f947ee55b092c4057025d7a3e04e5ecdbd25d6c3fb1bd7" +"checksum derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e2323f3f47db9a0e77ce7a300605d8d2098597fc451ed1a97bb1f6411bb550a7" +"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +"checksum dlib 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "77e51249a9d823a4cb79e3eca6dcd756153e8ed0157b6c04775d04bf1b13b76a" +"checksum downcast-rs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "52ba6eb47c2131e784a38b726eb54c1e1484904f013e576a25354d0124161af6" +"checksum dwrote 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" +"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +"checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38" +"checksum euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7ab0e07e345fb061928646949fdf5fb888e5d75a57385e7f5856e45be289e745" +"checksum expat-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa" +"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +"checksum font-loader 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c49d6b4c11dca1a1dd931a34a9f397e2da91abe3de4110505f3530a80e560b52" +"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +"checksum freetype 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bee38378a9e3db1cc693b4f88d166ae375338a0ff75cb8263e1c601d51f35dc6" +"checksum freetype-sys 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" +"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +"checksum gl_generator 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ca98bbde17256e02d17336a6bdb5a50f7d0ccacee502e191d3e3d0ec2f96f84a" +"checksum gl_generator 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +"checksum gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3fdef5b9df6d3a261b80a5ac55e13bf93945725df2463c1b0a2e5a527dce0d37" +"checksum gleam 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "cae10d7c99d0e77b4766e850a60898a17c1abaf01075531f1066f03dc7dc5fc5" +"checksum glsl 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "766443890761b3c4edcce86cafaac97971b200662fbdd0446eb7c6b99b4401ea" +"checksum glslopt 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f22b383fcf6f85c4a268af39a0758ec40970e5f9f8fe9809e4415d48409b8379" +"checksum glutin 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5371b35b309dace06be1b81b5f6adb1c9de578b7dbe1e74bf7e4ef762cf6febd" +"checksum glutin_egl_sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "772edef3b28b8ad41e4ea202748e65eefe8e5ffd1f4535f1219793dbb20b3d4c" +"checksum glutin_emscripten_sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80de4146df76e8a6c32b03007bc764ff3249dcaeb4f675d68a06caf1bac363f1" +"checksum glutin_gles2_sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "07e853d96bebcb8e53e445225c3009758c6f5960d44f2543245f6f07b567dae0" +"checksum glutin_glx_sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "08c243de74d6cf5ea100c788826d2fb9319de315485dd4b310811a663b3809c3" +"checksum glutin_wgl_sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a93dba7ee3a0feeac0f437141ff25e71ce2066bcf1a706acab1559ffff94eb6a" +"checksum hermit-abi 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" +"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" +"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +"checksum image 0.23.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bfc5483f8d5afd3653b38a196c52294dcb239c3e1a5bade1990353ea13bcf387" +"checksum inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" +"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" +"checksum jobserver 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum khronos_api 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" +"checksum libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)" = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" +"checksum libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" +"checksum line_drawing 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5cc7ad3d82c845bdb5dde34ffdcc7a5fb4d2996e1e1ee0f19c33bc80e15196b9" +"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" +"checksum lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +"checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +"checksum malloc_size_of_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e37c5d4cd9473c5f4c9c111f033f15d4df9bd378fdf615944e360a4f55a05f0b" +"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +"checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +"checksum memoffset 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" +"checksum minidl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "647da2489d438eb707e9bcffe0e47fb6f3f355ed288120740fc1e614cfadb9e9" +"checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" +"checksum mio-extras 2.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" +"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +"checksum mozangle 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b74826530a7c9fffbe28d27d0499433bd51f67b4f45d658ba23e62499307cc17" +"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" +"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" +"checksum nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" +"checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" +"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +"checksum num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00" +"checksum num-rational 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +"checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" +"checksum objc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +"checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" +"checksum osmesa-src 0.1.1 (git+https://github.com/servo/osmesa-src)" = "" +"checksum osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b" +"checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" +"checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" +"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" +"checksum plane-split 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2211e7ccc9b6260779dd9bad59f7b10889d6361974623b9e405afd7e7e764654" +"checksum png 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)" = "910f09135b1ed14bb16be445a8c23ddf0777eca485fbfc7cee00d81fecab158a" +"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" +"checksum procedural-masquerade 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1383dff4092fe903ac180e391a8d4121cc48f08ccf850614b0290c6673b69d" +"checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +"checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" +"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +"checksum raw-window-handle 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0a441a7a6c80ad6473bd4b74ec1c9a4c951794285bf941c2126f607c72e48211" +"checksum rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098" +"checksum rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9" +"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +"checksum regex 1.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3" +"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" +"checksum ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2ece421e0c4129b90e4a35b6f625e472e96c552136f5093a2f4fa2bbb75a62d5" +"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum rusttype 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)" = "310942406a39981bed7e12b09182a221a29e0990f3e7e0c971f131922ed135d5" +"checksum rusttype 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f61411055101f7b60ecf1041d87fb74205fb20b0c7a723f07ef39174cf6b4c0" +"checksum ryu 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" +"checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +"checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)" = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" +"checksum serde_bytes 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "325a073952621257820e7a3469f55ba4726d8b28657e7e36653d1c36dc2c84ae" +"checksum serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)" = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" +"checksum serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)" = "da07b57ee2623368351e9a0488bb0b261322a15a6e0ae53e243cbdc0f4208da9" +"checksum servo-fontconfig 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7e3e22fe5fd73d04ebf0daa049d3efe3eae55369ce38ab16d07ddd9ac5c217c" +"checksum servo-fontconfig-sys 5.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e36b879db9892dfa40f95da1c38a835d41634b825fbd8c4c418093d53c24b388" +"checksum sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +"checksum shared_library 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" +"checksum sig 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6567e29578f9bfade6a5d94a32b9a4256348358d2a3f448cab0021f9a02614a2" +"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +"checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" +"checksum smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05720e22615919e4734f6a99ceae50d00226c3c5aca406e102ebc33298214e0a" +"checksum smithay-client-toolkit 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2ccb8c57049b2a34d2cc2b203fa785020ba0129d31920ef0d317430adaf748fa" +"checksum stb_truetype 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f77b6b07e862c66a9f3e62a07588fee67cd90a9135a2b942409f195507b4fb51" +"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum svg_fmt 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2" +"checksum syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" +"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +"checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +"checksum tracy-rs 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dbbb439715d4c258415c7cbf137be32a4f54f7348724076145ede565ee2142e8" +"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" +"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +"checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" +"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +"checksum wayland-client 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)" = "49963e5f9eeaf637bfcd1b9f0701c99fd5cd05225eb51035550d4272806f2713" +"checksum wayland-commons 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)" = "40c08896768b667e1df195d88a62a53a2d1351a1ed96188be79c196b35bb32ec" +"checksum wayland-protocols 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)" = "4afde2ea2a428eee6d7d2c8584fdbe8b82eee8b6c353e129a434cd6e07f42145" +"checksum wayland-scanner 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3828c568714507315ee425a9529edc4a4aa9901409e373e9e0027e7622b79e" +"checksum wayland-sys 0.21.13 (registry+https://github.com/rust-lang/crates.io-index)" = "520ab0fd578017a0ee2206623ba9ef4afe5e8f23ca7b42f6acfba2f4e66b1628" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-util 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fa515c5163a99cc82bab70fd3bfdd36d827be85de63737b40fcef2ce084a436e" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum winit 0.19.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1e96eb4bb472fa43e718e8fa4aef82f86cd9deac9483a1e1529230babdb394a8" +"checksum wio 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +"checksum ws 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a2c47b5798ccc774ffb93ff536aec7c4275d722fd9c740c83cdd1af1f2d94" +"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +"checksum x11-dl 2.18.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2bf981e3a5b3301209754218f962052d4d9ee97e478f4d26d4a6eced34c1fef8" +"checksum xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" +"checksum xml-rs 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2bb76e5c421bbbeb8924c60c030331b345555024d56261dae8f3e786ed817c23" +"checksum yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992" +"checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" diff --git a/third_party/webrender/Cargo.toml b/third_party/webrender/Cargo.toml new file mode 100644 index 00000000000..bb6b204412a --- /dev/null +++ b/third_party/webrender/Cargo.toml @@ -0,0 +1,25 @@ +[workspace] +members = [ + "direct-composition", + "examples", + "webrender", + "webrender_api", + "wrench", + "example-compositor/compositor", + "tileview", +] + +[profile.release] +debug = true +panic = "abort" + +[profile.dev] +panic = "abort" + +# Running wrench on android built with master cargo-apk results in a crash +# due to a mismatched version of android_glue (a dependency of winit). +# Override it to use a suitable version of android_glue. +# See https://github.com/rust-windowing/android-rs-glue/issues/239. +# This can be removed once a new version of android_glue is published to crates.io. +[patch.crates-io] +android_glue = { git = "https://github.com/rust-windowing/android-rs-glue.git", rev = "e3ac6edea5814e1faca0c31ea8fac6877cb929ea" } diff --git a/third_party/webrender/LICENSE b/third_party/webrender/LICENSE new file mode 100644 index 00000000000..398385c9fb9 --- /dev/null +++ b/third_party/webrender/LICENSE @@ -0,0 +1,374 @@ + Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" +means each individual or legal entity that creates, contributes to +the creation of, or owns Covered Software. + +1.2. "Contributor Version" +means the combination of the Contributions of others (if any) used +by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" +means Covered Software of a particular Contributor. + +1.4. "Covered Software" +means Source Code Form to which the initial Contributor has attached +the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case +including portions thereof. + +1.5. "Incompatible With Secondary Licenses" +means + +(a) that the initial Contributor has attached the notice described +in Exhibit B to the Covered Software; or + +(b) that the Covered Software was made available under the terms of +version 1.1 or earlier of the License, but not also under the +terms of a Secondary License. + +1.6. "Executable Form" +means any form of the work other than Source Code Form. + +1.7. "Larger Work" +means a work that combines Covered Software with other material, in +a separate file or files, that is not Covered Software. + +1.8. "License" +means this document. + +1.9. "Licensable" +means having the right to grant, to the maximum extent possible, +whether at the time of the initial grant or subsequently, any and +all of the rights conveyed by this License. + +1.10. "Modifications" +means any of the following: + +(a) any file in Source Code Form that results from an addition to, +deletion from, or modification of the contents of Covered +Software; or + +(b) any new file in Source Code Form that contains any Covered +Software. + +1.11. "Patent Claims" of a Contributor +means any patent claim(s), including without limitation, method, +process, and apparatus claims, in any patent Licensable by such +Contributor that would be infringed, but for the grant of the +License, by the making, using, selling, offering for sale, having +made, import, or transfer of either its Contributions or its +Contributor Version. + +1.12. "Secondary License" +means either the GNU General Public License, Version 2.0, the GNU +Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those +licenses. + +1.13. "Source Code Form" +means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") +means an individual or a legal entity exercising rights under this +License. For legal entities, "You" includes any entity that +controls, is controlled by, or is under common control with You. For +purposes of this definition, "control" means (a) the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or (b) ownership of more than +fifty percent (50%) of the outstanding shares or beneficial +ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) +Licensable by such Contributor to use, reproduce, make available, +modify, display, perform, distribute, and otherwise exploit its +Contributions, either on an unmodified basis, with Modifications, or +as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer +for sale, have made, import, and otherwise transfer either its +Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; +or + +(b) for infringements caused by: (i) Your and any other third party's +modifications of Covered Software, or (ii) the combination of its +Contributions with other software (except as part of its Contributor +Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of +its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code +Form, as described in Section 3.1, and You must inform recipients of +the Executable Form how they can obtain a copy of such Source Code +Form by reasonable means in a timely manner, at a charge no more +than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this +License, or sublicense it under different terms, provided that the +license for the Executable Form does not attempt to limit or alter +the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + +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/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + +This Source Code Form is "Incompatible With Secondary Licenses", as +defined by the Mozilla Public License, v. 2.0. + diff --git a/third_party/webrender/README.md b/third_party/webrender/README.md new file mode 100644 index 00000000000..6162a5f86b1 --- /dev/null +++ b/third_party/webrender/README.md @@ -0,0 +1,53 @@ +# WebRender + +[![Version](https://img.shields.io/crates/v/webrender.svg)](https://crates.io/crates/webrender) + +WebRender is a GPU-based 2D rendering engine written in [Rust](https://www.rust-lang.org/). [Firefox](https://www.mozilla.org/firefox), the research web browser [Servo](https://github.com/servo/servo), and other GUI frameworks draw with it. It currently uses the OpenGL API internally. + +Note that the canonical home for this code is in gfx/wr folder of the +mozilla-central repository at https://hg.mozilla.org/mozilla-central. The +Github repository at https://github.com/servo/webrender should be considered +a downstream mirror, although it contains additional metadata (such as Github +wiki pages) that do not exist in mozilla-central. Pull requests against the +Github repository are still being accepted, although once reviewed, they will +be landed on mozilla-central first and then mirrored back. If you are familiar +with the mozilla-central contribution workflow, filing bugs in +[Bugzilla](https://bugzilla.mozilla.org/enter_bug.cgi?product=Core&component=Graphics%3A%20WebRender) +and submitting patches there would be preferred. + +## Update as a Dependency +After updating shaders in WebRender, go to servo and: + + * Go to the servo directory and do ./mach update-cargo -p webrender + * Create a pull request to servo + + +## Use WebRender with Servo +To use a local copy of WebRender with servo, go to your servo build directory and: + + * Edit Cargo.toml + * Add at the end of the file: + +``` +[patch."https://github.com/servo/webrender"] +"webrender" = { path = "/webrender" } +"webrender_api" = { path = "/webrender_api" } +``` + +where `` is the path to your local copy of WebRender. + + * Build as normal + +## Documentation + +The Wiki has a [few pages](https://github.com/servo/webrender/wiki/) describing the internals and conventions of WebRender. + +## Testing + +Tests run using OSMesa to get consistent rendering across platforms. + +Still there may be differences depending on font libraries on your system, for +example. + +See [this gist](https://gist.github.com/finalfantasia/129cae811e02bf4551ac) for +how to make the text tests useful in Fedora, for example. diff --git a/third_party/webrender/ci-scripts/docker-image/Dockerfile b/third_party/webrender/ci-scripts/docker-image/Dockerfile new file mode 100644 index 00000000000..c187172f5db --- /dev/null +++ b/third_party/webrender/ci-scripts/docker-image/Dockerfile @@ -0,0 +1,12 @@ +FROM debian:buster-20200422 + +# Debian 10 doesn't have openjdk-8, so add the Debian 9 repository, which contains it. +RUN sed s/buster/stretch/ /etc/apt/sources.list | tee /etc/apt/sources.list.d/stretch.list + +COPY setup.sh /root +RUN cd /root && ./setup.sh + +RUN useradd -d /home/worker -s /bin/bash -m worker +USER worker +WORKDIR /home/worker +CMD /bin/bash diff --git a/third_party/webrender/ci-scripts/docker-image/setup.sh b/third_party/webrender/ci-scripts/docker-image/setup.sh new file mode 100755 index 00000000000..4a20147f3b0 --- /dev/null +++ b/third_party/webrender/ci-scripts/docker-image/setup.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +# 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/. */ + +set -o errexit +set -o nounset +set -o pipefail +set -o xtrace + +test "$(whoami)" == 'root' + +# Install stuff we need +apt-get -y update +apt-get install -y \ + bzip2 \ + cmake \ + curl \ + gcc \ + git \ + g++ \ + libfontconfig1-dev \ + libgl1-mesa-dev \ + libx11-dev \ + openjdk-8-jdk \ + pkg-config \ + python \ + python-mako \ + python-pip \ + python-setuptools \ + python-voluptuous \ + python-yaml \ + software-properties-common + +# Other stuff we need +pip install servo-tidy==0.3.0 diff --git a/third_party/webrender/ci-scripts/linux-debug-tests.sh b/third_party/webrender/ci-scripts/linux-debug-tests.sh new file mode 100755 index 00000000000..2c2fc756b63 --- /dev/null +++ b/third_party/webrender/ci-scripts/linux-debug-tests.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +# 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/. */ + +# This must be run from the root webrender directory! +# Users may set the CARGOFLAGS environment variable to pass +# additional flags to cargo if desired. + +set -o errexit +set -o nounset +set -o pipefail +set -o xtrace + +CARGOFLAGS=${CARGOFLAGS:-"--verbose"} # default to --verbose if not set + +pushd webrender +cargo build ${CARGOFLAGS} --no-default-features +cargo build ${CARGOFLAGS} --no-default-features --features capture +cargo build ${CARGOFLAGS} --features capture,profiler +cargo build ${CARGOFLAGS} --features replay +popd + +pushd wrench +cargo build ${CARGOFLAGS} --features env_logger +OPTIMIZED=0 python script/headless.py reftest +popd + +pushd examples +cargo build ${CARGOFLAGS} +popd + +cargo test ${CARGOFLAGS} \ + --all --exclude compositor-windows --exclude compositor \ + --exclude glsl-to-cxx --exclude swgl diff --git a/third_party/webrender/ci-scripts/linux-release-tests.sh b/third_party/webrender/ci-scripts/linux-release-tests.sh new file mode 100755 index 00000000000..f89fc43c44f --- /dev/null +++ b/third_party/webrender/ci-scripts/linux-release-tests.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# 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/. */ + +# This must be run from the root webrender directory! +# Users may set the CARGOFLAGS environment variable to pass +# additional flags to cargo if desired. + +set -o errexit +set -o nounset +set -o pipefail +set -o xtrace + +CARGOFLAGS=${CARGOFLAGS:-""} # default to empty if not set + +pushd wrench +python script/headless.py reftest +python script/headless.py rawtest +cargo build ${CARGOFLAGS} --release +popd diff --git a/third_party/webrender/ci-scripts/macos-debug-tests.sh b/third_party/webrender/ci-scripts/macos-debug-tests.sh new file mode 100755 index 00000000000..2325475d90d --- /dev/null +++ b/third_party/webrender/ci-scripts/macos-debug-tests.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +# 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/. */ + +# This must be run from the root webrender directory! +# Users may set the CARGOFLAGS environment variable to pass +# additional flags to cargo if desired. + +# Note that this script is run in a special cross-compiling configuration, +# where CARGOTESTFLAGS includes `--no-run`, and the binaries produced by +# `cargo test` are run on a different machine. When making changes to this +# file, please ensure any such binaries produced by `cargo test` are not +# deleted, or they may not get run as expected. + +set -o errexit +set -o nounset +set -o pipefail +set -o xtrace + +CARGOFLAGS=${CARGOFLAGS:-"--verbose"} # default to --verbose if not set +CARGOTESTFLAGS=${CARGOTESTFLAGS:-""} + +pushd webrender +cargo check ${CARGOFLAGS} --no-default-features +cargo check ${CARGOFLAGS} --no-default-features --features capture +cargo check ${CARGOFLAGS} --features capture,profiler +cargo check ${CARGOFLAGS} --features replay +popd + +pushd wrench +cargo check ${CARGOFLAGS} --features env_logger +popd + +pushd examples +cargo check ${CARGOFLAGS} +popd + +cargo test ${CARGOFLAGS} ${CARGOTESTFLAGS} \ + --all --exclude compositor-windows --exclude compositor \ + --exclude glsl-to-cxx --exclude swgl diff --git a/third_party/webrender/ci-scripts/macos-release-tests.sh b/third_party/webrender/ci-scripts/macos-release-tests.sh new file mode 100755 index 00000000000..dc8c205ad53 --- /dev/null +++ b/third_party/webrender/ci-scripts/macos-release-tests.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +# 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/. */ + +# This must be run from the root webrender directory! +# Users may set the CARGOFLAGS environment variable to pass +# additional flags to cargo if desired. +# The WRENCH_BINARY environment variable, if set, is used to run +# the precached reftest. + +set -o errexit +set -o nounset +set -o pipefail +set -o xtrace + +CARGOFLAGS=${CARGOFLAGS:-""} # default to empty if not set +WRENCH_BINARY=${WRENCH_BINARY:-""} + +pushd wrench +python script/headless.py reftest +if [[ -z "${WRENCH_BINARY}" ]]; then + cargo build ${CARGOFLAGS} --release + WRENCH_BINARY="../target/release/wrench" +fi +"${WRENCH_BINARY}" --precache \ + reftest reftests/clip/fixed-position-clipping.yaml +popd diff --git a/third_party/webrender/ci-scripts/set-screenresolution.ps1 b/third_party/webrender/ci-scripts/set-screenresolution.ps1 new file mode 100644 index 00000000000..2f584434177 --- /dev/null +++ b/third_party/webrender/ci-scripts/set-screenresolution.ps1 @@ -0,0 +1,124 @@ +# http://blogs.technet.com/b/heyscriptingguy/archive/2010/07/07/hey-scripting-guy-how-can-i-change-my-desktop-monitor-resolution-via-windows-powershell.aspx + +Function Set-ScreenResolution { +param ( +[Parameter(Mandatory=$true, + Position = 0)] +[int] +$Width, +[Parameter(Mandatory=$true, + Position = 1)] +[int] +$Height +) +$pinvokeCode = @" +using System; +using System.Runtime.InteropServices; +namespace Resolution +{ + [StructLayout(LayoutKind.Sequential)] + public struct DEVMODE1 + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string dmDeviceName; + public short dmSpecVersion; + public short dmDriverVersion; + public short dmSize; + public short dmDriverExtra; + public int dmFields; + public short dmOrientation; + public short dmPaperSize; + public short dmPaperLength; + public short dmPaperWidth; + public short dmScale; + public short dmCopies; + public short dmDefaultSource; + public short dmPrintQuality; + public short dmColor; + public short dmDuplex; + public short dmYResolution; + public short dmTTOption; + public short dmCollate; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string dmFormName; + public short dmLogPixels; + public short dmBitsPerPel; + public int dmPelsWidth; + public int dmPelsHeight; + public int dmDisplayFlags; + public int dmDisplayFrequency; + public int dmICMMethod; + public int dmICMIntent; + public int dmMediaType; + public int dmDitherType; + public int dmReserved1; + public int dmReserved2; + public int dmPanningWidth; + public int dmPanningHeight; + }; + class User_32 + { + [DllImport("user32.dll")] + public static extern int EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE1 devMode); + [DllImport("user32.dll")] + public static extern int ChangeDisplaySettings(ref DEVMODE1 devMode, int flags); + public const int ENUM_CURRENT_SETTINGS = -1; + public const int CDS_UPDATEREGISTRY = 0x01; + public const int CDS_TEST = 0x02; + public const int DISP_CHANGE_SUCCESSFUL = 0; + public const int DISP_CHANGE_RESTART = 1; + public const int DISP_CHANGE_FAILED = -1; + } + public class PrmaryScreenResolution + { + static public string ChangeResolution(int width, int height) + { + DEVMODE1 dm = GetDevMode1(); + if (0 != User_32.EnumDisplaySettings(null, User_32.ENUM_CURRENT_SETTINGS, ref dm)) + { + dm.dmPelsWidth = width; + dm.dmPelsHeight = height; + int iRet = User_32.ChangeDisplaySettings(ref dm, User_32.CDS_TEST); + if (iRet == User_32.DISP_CHANGE_FAILED) + { + return "Unable To Process Your Request. Sorry For This Inconvenience."; + } + else + { + iRet = User_32.ChangeDisplaySettings(ref dm, User_32.CDS_UPDATEREGISTRY); + switch (iRet) + { + case User_32.DISP_CHANGE_SUCCESSFUL: + { + return "Success"; + } + case User_32.DISP_CHANGE_RESTART: + { + return "You Need To Reboot For The Change To Happen.\n If You Feel Any Problem After Rebooting Your Machine\nThen Try To Change Resolution In Safe Mode."; + } + default: + { + return "Failed To Change The Resolution"; + } + } + } + } + else + { + return "Failed To Change The Resolution."; + } + } + private static DEVMODE1 GetDevMode1() + { + DEVMODE1 dm = new DEVMODE1(); + dm.dmDeviceName = new String(new char[32]); + dm.dmFormName = new String(new char[32]); + dm.dmSize = (short)Marshal.SizeOf(dm); + return dm; + } + } +} +"@ +Add-Type $pinvokeCode -ErrorAction SilentlyContinue +[Resolution.PrmaryScreenResolution]::ChangeResolution($width,$height) +} diff --git a/third_party/webrender/ci-scripts/windows-tests.cmd b/third_party/webrender/ci-scripts/windows-tests.cmd new file mode 100755 index 00000000000..c3607a90287 --- /dev/null +++ b/third_party/webrender/ci-scripts/windows-tests.cmd @@ -0,0 +1,36 @@ +:: 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/. */ + +:: This must be run from the root webrender directory! +:: Users may set the CARGOFLAGS environment variable to pass +:: additional flags to cargo if desired. + +if NOT DEFINED CARGOFLAGS SET CARGOFLAGS=--verbose + +pushd webrender_api +cargo test %CARGOFLAGS% +if %ERRORLEVEL% NEQ 0 EXIT /b %ERRORLEVEL% +popd + +pushd webrender +cargo test %CARGOFLAGS% +if %ERRORLEVEL% NEQ 0 EXIT /b %ERRORLEVEL% +popd + +pushd wrench +cargo test --verbose +if %ERRORLEVEL% NEQ 0 EXIT /b %ERRORLEVEL% +cargo run --release -- --angle reftest +if %ERRORLEVEL% NEQ 0 EXIT /b %ERRORLEVEL% +popd + +pushd examples +cargo check --verbose +if %ERRORLEVEL% NEQ 0 EXIT /b %ERRORLEVEL% +popd + +pushd direct-composition +cargo check --verbose +if %ERRORLEVEL% NEQ 0 EXIT /b %ERRORLEVEL% +popd diff --git a/third_party/webrender/debugger/.babelrc b/third_party/webrender/debugger/.babelrc new file mode 100644 index 00000000000..e81239406e3 --- /dev/null +++ b/third_party/webrender/debugger/.babelrc @@ -0,0 +1,6 @@ +{ + "presets": [ + ["env", { "modules": false }], + "stage-3" + ] +} diff --git a/third_party/webrender/debugger/.gitignore b/third_party/webrender/debugger/.gitignore new file mode 100644 index 00000000000..15115487ab7 --- /dev/null +++ b/third_party/webrender/debugger/.gitignore @@ -0,0 +1,11 @@ +.DS_Store +node_modules/ +npm-debug.log +yarn-error.log + +# Editor directories and files +.idea +*.suo +*.ntvs* +*.njsproj +*.sln diff --git a/third_party/webrender/debugger/README.md b/third_party/webrender/debugger/README.md new file mode 100644 index 00000000000..d41b5ce16aa --- /dev/null +++ b/third_party/webrender/debugger/README.md @@ -0,0 +1,23 @@ +# WebRender Debugger +A web based debugger for WebRender. + +## Using the debugger +Build your application with the debugger feature enabled, for example in wrench: + +``` +cargo build --features=debugger +``` + +Now, open your browser and open the debugger/index.html file. Click Connect and +the debugger will attempt to connect to WR via websocket. + +## Using the debugger with Gecko + +In the Gecko source tree, open ```gfx/webrender_bindings/Cargo.toml``` in a text editor. + +Add ```features = ['debugger']``` to the end of the file (in the ```dependencies.webrender``` section). + +Vendor the rust dependencies locally for the debugger (we don't want these committed to the repo): +```./mach vendor rust``` + +Now, build and run as usual, and the debugger will be available. diff --git a/third_party/webrender/debugger/dist/build.js b/third_party/webrender/debugger/dist/build.js new file mode 100644 index 00000000000..e8d4439034d --- /dev/null +++ b/third_party/webrender/debugger/dist/build.js @@ -0,0 +1,13 @@ +!function(e){function t(n){if(i[n])return i[n].exports;var o=i[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var i={};t.m=e,t.c=i,t.d=function(e,i,n){t.o(e,i)||Object.defineProperty(e,i,{configurable:!1,enumerable:!0,get:n})},t.n=function(e){var i=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(i,"a",i),i},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="/dist/",t(t.s=16)}([function(e,t){function i(e,t){var i=e[1]||"",o=e[3];if(!o)return i;if(t&&"function"==typeof btoa){var a=n(o);return[i].concat(o.sources.map(function(e){return"/*# sourceURL="+o.sourceRoot+e+" */"})).concat([a]).join("\n")}return[i].join("\n")}function n(e){return"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(e))))+" */"}e.exports=function(e){var t=[];return t.toString=function(){return this.map(function(t){var n=i(t,e);return t[2]?"@media "+t[2]+"{"+n+"}":n}).join("")},t.i=function(e,i){"string"==typeof e&&(e=[[null,e,""]]);for(var n={},o=0;oi.parts.length&&(n.parts.length=i.parts.length)}else{for(var r=[],o=0;o=0&&Math.floor(t)===t&&isFinite(e)}function f(e){return o(e)&&"function"==typeof e.then&&"function"==typeof e.catch}function p(e){return null==e?"":Array.isArray(e)||c(e)&&e.toString===zn?JSON.stringify(e,null,2):String(e)}function h(e){var t=parseFloat(e);return isNaN(t)?e:t}function b(e,t){for(var i=Object.create(null),n=e.split(","),o=0;o-1)return e.splice(i,1)}}function g(e,t){return An.call(e,t)}function v(e){var t=Object.create(null);return function(i){return t[i]||(t[i]=e(i))}}function k(e,t){function i(i){var n=arguments.length;return n?n>1?e.apply(t,arguments):e.call(t,i):e.call(t)}return i._length=e.length,i}function x(e,t){return e.bind(t)}function w(e,t){t=t||0;for(var i=e.length-t,n=new Array(i);i--;)n[i]=e[i+t];return n}function y(e,t){for(var i in t)e[i]=t[i];return e}function _(e){for(var t={},i=0;i-1)if(a&&!g(o,"default"))r=!1;else if(""===r||r===En(e)){var l=oe(String,o.type);(l<0||s0&&(r=we(r,(t||"")+"_"+i),xe(r[0])&&xe(c)&&(d[l]=P(c.text+r[0].text),r.shift()),d.push.apply(d,r)):s(r)?xe(c)?d[l]=P(c.text+r):""!==r&&d.push(P(r)):xe(r)&&xe(c)?d[l]=P(c.text+r.text):(a(e._isVList)&&o(r.tag)&&n(r.key)&&o(t)&&(r.key="__vlist"+t+"_"+i+"__"),d.push(r)));return d}function ye(e){var t=e.$options.provide;t&&(e._provided="function"==typeof t?t.call(e):t)}function _e(e){var t=Ce(e.$options.inject,e);t&&(F(!1),Object.keys(t).forEach(function(i){N(e,i,t[i])}),F(!0))}function Ce(e,t){if(e){for(var i=Object.create(null),n=lo?Reflect.ownKeys(e):Object.keys(e),o=0;o0,a=e?!!e.$stable:!o,r=e&&e.$key;if(e){if(e._normalized)return e._normalized;if(a&&i&&i!==Dn&&r===i.$key&&!o&&!i.$hasNormal)return i;n={};for(var s in e)e[s]&&"$"!==s[0]&&(n[s]=ze(t,s,e[s]))}else n={};for(var l in t)l in n||(n[l]=Oe(t,l));return e&&Object.isExtensible(e)&&(e._normalized=n),O(n,"$stable",a),O(n,"$key",r),O(n,"$hasNormal",o),n}function ze(e,t,i){var n=function(){var e=arguments.length?i.apply(null,arguments):i({});return e=e&&"object"==typeof e&&!Array.isArray(e)?[e]:ke(e),e&&(0===e.length||1===e.length&&e[0].isComment)?void 0:e};return i.proxy&&Object.defineProperty(e,t,{get:n,enumerable:!0,configurable:!0}),n}function Oe(e,t){return function(){return e[t]}}function Ae(e,t){var i,n,a,r,s;if(Array.isArray(e)||"string"==typeof e)for(i=new Array(e.length),n=0,a=e.length;nWo&&Ro[i].id>e.id;)i--;Ro.splice(i+1,0,e)}else Ro.push(e);qo||(qo=!0,de(Ct))}}function Ot(e,t,i){Qo.get=function(){return this[t][i]},Qo.set=function(e){this[t][i]=e},Object.defineProperty(e,i,Qo)}function At(e){e._watchers=[];var t=e.$options;t.props&&Tt(e,t.props),t.methods&&Vt(e,t.methods),t.data?Mt(e):I(e._data={},!0),t.computed&&Pt(e,t.computed),t.watch&&t.watch!==to&&It(e,t.watch)}function Tt(e,t){var i=e.$options.propsData||{},n=e._props={},o=e.$options._propKeys=[],a=!e.$parent;a||F(!1);for(var r in t)!function(a){o.push(a);var r=ee(a,t,i,e);N(n,a,r),a in e||Ot(e,"_props",a)}(r);F(!0)}function Mt(e){var t=e.$options.data;t=e._data="function"==typeof t?jt(t,e):t||{},c(t)||(t={});for(var i=Object.keys(t),n=e.$options.props,o=(e.$options.methods,i.length);o--;){var a=i[o];n&&g(n,a)||z(a)||Ot(e,"_data",a)}I(t,!0)}function jt(e,t){M();try{return e.call(t,t)}catch(e){return ae(e,t,"data()"),{}}finally{j()}}function Pt(e,t){var i=e._computedWatchers=Object.create(null),n=ro();for(var o in t){var a=t[o],r="function"==typeof a?a:a.get;n||(i[o]=new Jo(e,r||C,C,Zo)),o in e||Et(e,o,a)}}function Et(e,t,i){var n=!ro();"function"==typeof i?(Qo.get=n?Ft(t):Bt(i),Qo.set=C):(Qo.get=i.get?n&&!1!==i.cache?Ft(t):Bt(i.get):C,Qo.set=i.set||C),Object.defineProperty(e,t,Qo)}function Ft(e){return function(){var t=this._computedWatchers&&this._computedWatchers[e];if(t)return t.dirty&&t.evaluate(),fo.target&&t.depend(),t.value}}function Bt(e){return function(){return e.call(this,this)}}function Vt(e,t){e.$options.props;for(var i in t)e[i]="function"!=typeof t[i]?C:Fn(t[i],e)}function It(e,t){for(var i in t){var n=t[i];if(Array.isArray(n))for(var o=0;o-1)return this;var i=w(arguments,1);return i.unshift(this),"function"==typeof e.install?e.install.apply(e,i):"function"==typeof e&&e.apply(null,i),t.push(e),this}}function Wt(e){e.mixin=function(e){return this.options=Q(this.options,e),this}}function Yt(e){e.cid=0;var t=1;e.extend=function(e){e=e||{};var i=this,n=i.cid,o=e._Ctor||(e._Ctor={});if(o[n])return o[n];var a=e.name||i.options.name,r=function(e){this._init(e)};return r.prototype=Object.create(i.prototype),r.prototype.constructor=r,r.cid=t++,r.options=Q(i.options,e),r.super=i,r.options.props&&Gt(r),r.options.computed&&Kt(r),r.extend=i.extend,r.mixin=i.mixin,r.use=i.use,Nn.forEach(function(e){r[e]=i[e]}),a&&(r.options.components[a]=r),r.superOptions=i.options,r.extendOptions=e,r.sealedOptions=y({},r.options),o[n]=r,r}}function Gt(e){var t=e.options.props;for(var i in t)Ot(e.prototype,"_props",i)}function Kt(e){var t=e.options.computed;for(var i in t)Et(e.prototype,i,t[i])}function Xt(e){Nn.forEach(function(t){e[t]=function(e,i){return i?("component"===t&&c(i)&&(i.name=i.name||e,i=this.options._base.extend(i)),"directive"===t&&"function"==typeof i&&(i={bind:i,update:i}),this.options[t+"s"][e]=i,i):this.options[t+"s"][e]}})}function Jt(e){return e&&(e.Ctor.options.name||e.tag)}function Qt(e,t){return Array.isArray(e)?e.indexOf(t)>-1:"string"==typeof e?e.split(",").indexOf(t)>-1:!!d(e)&&e.test(t)}function Zt(e,t){var i=e.cache,n=e.keys,o=e._vnode;for(var a in i){var r=i[a];if(r){var s=Jt(r.componentOptions);s&&!t(s)&&ei(i,a,n,o)}}}function ei(e,t,i,n){var o=e[t];!o||n&&o.tag===n.tag||o.componentInstance.$destroy(),e[t]=null,m(i,t)}function ti(e){for(var t=e.data,i=e,n=e;o(n.componentInstance);)(n=n.componentInstance._vnode)&&n.data&&(t=ii(n.data,t));for(;o(i=i.parent);)i&&i.data&&(t=ii(t,i.data));return ni(t.staticClass,t.class)}function ii(e,t){return{staticClass:oi(e.staticClass,t.staticClass),class:o(e.class)?[e.class,t.class]:t.class}}function ni(e,t){return o(e)||o(t)?oi(e,ai(t)):""}function oi(e,t){return e?t?e+" "+t:e:t||""}function ai(e){return Array.isArray(e)?ri(e):l(e)?si(e):"string"==typeof e?e:""}function ri(e){for(var t,i="",n=0,a=e.length;n-1?ya[e]=t.constructor===window.HTMLUnknownElement||t.constructor===window.HTMLElement:ya[e]=/HTMLUnknownElement/.test(t.toString())}function di(e){if("string"==typeof e){var t=document.querySelector(e);return t||document.createElement("div")}return e}function ui(e,t){var i=document.createElement(e);return"select"!==e?i:(t.data&&t.data.attrs&&void 0!==t.data.attrs.multiple&&i.setAttribute("multiple","multiple"),i)}function fi(e,t){return document.createElementNS(va[e],t)}function pi(e){return document.createTextNode(e)}function hi(e){return document.createComment(e)}function bi(e,t,i){e.insertBefore(t,i)}function mi(e,t){e.removeChild(t)}function gi(e,t){e.appendChild(t)}function vi(e){return e.parentNode}function ki(e){return e.nextSibling}function xi(e){return e.tagName}function wi(e,t){e.textContent=t}function yi(e,t){e.setAttribute(t,"")}function _i(e,t){var i=e.data.ref;if(o(i)){var n=e.context,a=e.componentInstance||e.elm,r=n.$refs;t?Array.isArray(r[i])?m(r[i],a):r[i]===a&&(r[i]=void 0):e.data.refInFor?Array.isArray(r[i])?r[i].indexOf(a)<0&&r[i].push(a):r[i]=[a]:r[i]=a}}function Ci(e,t){return e.key===t.key&&(e.tag===t.tag&&e.isComment===t.isComment&&o(e.data)===o(t.data)&&Si(e,t)||a(e.isAsyncPlaceholder)&&e.asyncFactory===t.asyncFactory&&n(t.asyncFactory.error))}function Si(e,t){if("input"!==e.tag)return!0;var i,n=o(i=e.data)&&o(i=i.attrs)&&i.type,a=o(i=t.data)&&o(i=i.attrs)&&i.type;return n===a||_a(n)&&_a(a)}function $i(e,t,i){var n,a,r={};for(n=t;n<=i;++n)a=e[n].key,o(a)&&(r[a]=n);return r}function Di(e,t){(e.data.directives||t.data.directives)&&zi(e,t)}function zi(e,t){var i,n,o,a=e===$a,r=t===$a,s=Oi(e.data.directives,e.context),l=Oi(t.data.directives,t.context),c=[],d=[];for(i in l)n=s[i],o=l[i],n?(o.oldValue=n.value,o.oldArg=n.arg,Ti(o,"update",t,e),o.def&&o.def.componentUpdated&&d.push(o)):(Ti(o,"bind",t,e),o.def&&o.def.inserted&&c.push(o));if(c.length){var u=function(){for(var i=0;i-1?Pi(e,t,i):pa(t)?ga(i)?e.removeAttribute(t):(i="allowfullscreen"===t&&"EMBED"===e.tagName?"true":t,e.setAttribute(t,i)):da(t)?e.setAttribute(t,fa(t,i)):ba(t)?ga(i)?e.removeAttributeNS(ha,ma(t)):e.setAttributeNS(ha,t,i):Pi(e,t,i)}function Pi(e,t,i){if(ga(i))e.removeAttribute(t);else{if(Xn&&!Jn&&"TEXTAREA"===e.tagName&&"placeholder"===t&&""!==i&&!e.__ieph){var n=function(t){t.stopImmediatePropagation(),e.removeEventListener("input",n)};e.addEventListener("input",n),e.__ieph=!0}e.setAttribute(t,i)}}function Ei(e,t){var i=t.elm,a=t.data,r=e.data;if(!(n(a.staticClass)&&n(a.class)&&(n(r)||n(r.staticClass)&&n(r.class)))){var s=ti(t),l=i._transitionClasses;o(l)&&(s=oi(s,ai(l))),s!==i._prevClass&&(i.setAttribute("class",s),i._prevClass=s)}}function Fi(e){if(o(e[ja])){var t=Xn?"change":"input";e[t]=[].concat(e[ja],e[t]||[]),delete e[ja]}o(e[Pa])&&(e.change=[].concat(e[Pa],e.change||[]),delete e[Pa])}function Bi(e,t,i){var n=oa;return function o(){null!==t.apply(null,arguments)&&Ii(e,o,i,n)}}function Vi(e,t,i,n){if(Ea){var o=Yo,a=t;t=a._wrapper=function(e){if(e.target===e.currentTarget||e.timeStamp>=o||e.timeStamp<=0||e.target.ownerDocument!==document)return a.apply(this,arguments)}}oa.addEventListener(e,t,io?{capture:i,passive:n}:i)}function Ii(e,t,i,n){(n||oa).removeEventListener(e,t._wrapper||t,i)}function Ni(e,t){if(!n(e.data.on)||!n(t.data.on)){var i=t.data.on||{},o=e.data.on||{};oa=t.elm,Fi(i),he(i,o,Vi,Ii,Bi,t.context),oa=void 0}}function Ri(e,t){if(!n(e.data.domProps)||!n(t.data.domProps)){var i,a,r=t.elm,s=e.data.domProps||{},l=t.data.domProps||{};o(l.__ob__)&&(l=t.data.domProps=y({},l));for(i in s)i in l||(r[i]="");for(i in l){if(a=l[i],"textContent"===i||"innerHTML"===i){if(t.children&&(t.children.length=0),a===s[i])continue;1===r.childNodes.length&&r.removeChild(r.childNodes[0])}if("value"===i&&"PROGRESS"!==r.tagName){r._value=a;var c=n(a)?"":String(a);Li(r,c)&&(r.value=c)}else if("innerHTML"===i&&xa(r.tagName)&&n(r.innerHTML)){aa=aa||document.createElement("div"),aa.innerHTML=""+a+"";for(var d=aa.firstChild;r.firstChild;)r.removeChild(r.firstChild);for(;d.firstChild;)r.appendChild(d.firstChild)}else if(a!==s[i])try{r[i]=a}catch(e){}}}}function Li(e,t){return!e.composing&&("OPTION"===e.tagName||Hi(e,t)||qi(e,t))}function Hi(e,t){var i=!0;try{i=document.activeElement!==e}catch(e){}return i&&e.value!==t}function qi(e,t){var i=e.value,n=e._vModifiers;if(o(n)){if(n.number)return h(i)!==h(t);if(n.trim)return i.trim()!==t.trim()}return i!==t}function Ui(e){var t=Wi(e.style);return e.staticStyle?y(e.staticStyle,t):t}function Wi(e){return Array.isArray(e)?_(e):"string"==typeof e?Va(e):e}function Yi(e,t){var i,n={};if(t)for(var o=e;o.componentInstance;)(o=o.componentInstance._vnode)&&o.data&&(i=Ui(o.data))&&y(n,i);(i=Ui(e.data))&&y(n,i);for(var a=e;a=a.parent;)a.data&&(i=Ui(a.data))&&y(n,i);return n}function Gi(e,t){var i=t.data,a=e.data;if(!(n(i.staticStyle)&&n(i.style)&&n(a.staticStyle)&&n(a.style))){var r,s,l=t.elm,c=a.staticStyle,d=a.normalizedStyle||a.style||{},u=c||d,f=Wi(t.data.style)||{};t.data.normalizedStyle=o(f.__ob__)?y({},f):f;var p=Yi(t,!0);for(s in u)n(p[s])&&Ra(l,s,"");for(s in p)(r=p[s])!==u[s]&&Ra(l,s,null==r?"":r)}}function Ki(e,t){if(t&&(t=t.trim()))if(e.classList)t.indexOf(" ")>-1?t.split(Ua).forEach(function(t){return e.classList.add(t)}):e.classList.add(t);else{var i=" "+(e.getAttribute("class")||"")+" ";i.indexOf(" "+t+" ")<0&&e.setAttribute("class",(i+t).trim())}}function Xi(e,t){if(t&&(t=t.trim()))if(e.classList)t.indexOf(" ")>-1?t.split(Ua).forEach(function(t){return e.classList.remove(t)}):e.classList.remove(t),e.classList.length||e.removeAttribute("class");else{for(var i=" "+(e.getAttribute("class")||"")+" ",n=" "+t+" ";i.indexOf(n)>=0;)i=i.replace(n," ");i=i.trim(),i?e.setAttribute("class",i):e.removeAttribute("class")}}function Ji(e){if(e){if("object"==typeof e){var t={};return!1!==e.css&&y(t,Wa(e.name||"v")),y(t,e),t}return"string"==typeof e?Wa(e):void 0}}function Qi(e){er(function(){er(e)})}function Zi(e,t){var i=e._transitionClasses||(e._transitionClasses=[]);i.indexOf(t)<0&&(i.push(t),Ki(e,t))}function en(e,t){e._transitionClasses&&m(e._transitionClasses,t),Xi(e,t)}function tn(e,t,i){var n=nn(e,t),o=n.type,a=n.timeout,r=n.propCount;if(!o)return i();var s=o===Ga?Ja:Za,l=0,c=function(){e.removeEventListener(s,d),i()},d=function(t){t.target===e&&++l>=r&&c()};setTimeout(function(){l0&&(i=Ga,d=r,u=a.length):t===Ka?c>0&&(i=Ka,d=c,u=l.length):(d=Math.max(r,c),i=d>0?r>c?Ga:Ka:null,u=i?i===Ga?a.length:l.length:0),{type:i,timeout:d,propCount:u,hasTransform:i===Ga&&tr.test(n[Xa+"Property"])}}function on(e,t){for(;e.length1}function dn(e,t){!0!==t.data.show&&rn(t)}function un(e,t,i){fn(e,t,i),(Xn||Qn)&&setTimeout(function(){fn(e,t,i)},0)}function fn(e,t,i){var n=t.value,o=e.multiple;if(!o||Array.isArray(n)){for(var a,r,s=0,l=e.options.length;s-1,r.selected!==a&&(r.selected=a);else if(S(hn(r),n))return void(e.selectedIndex!==s&&(e.selectedIndex=s));o||(e.selectedIndex=-1)}}function pn(e,t){return t.every(function(t){return!S(t,e)})}function hn(e){return"_value"in e?e._value:e.value}function bn(e){e.target.composing=!0}function mn(e){e.target.composing&&(e.target.composing=!1,gn(e.target,"input"))}function gn(e,t){var i=document.createEvent("HTMLEvents");i.initEvent(t,!0,!0),e.dispatchEvent(i)}function vn(e){return!e.componentInstance||e.data&&e.data.transition?e:vn(e.componentInstance._vnode)}function kn(e){var t=e&&e.componentOptions;return t&&t.Ctor.options.abstract?kn(ct(t.children)):e}function xn(e){var t={},i=e.$options;for(var n in i.propsData)t[n]=e[n];var o=i._parentListeners;for(var a in o)t[Mn(a)]=o[a];return t}function wn(e,t){if(/\d-keep-alive$/.test(t.tag))return e("keep-alive",{props:t.componentOptions.propsData})}function yn(e){for(;e=e.parent;)if(e.data.transition)return!0}function _n(e,t){return t.key===e.key&&t.tag===e.tag}function Cn(e){e.elm._moveCb&&e.elm._moveCb(),e.elm._enterCb&&e.elm._enterCb()}function Sn(e){e.data.newPos=e.elm.getBoundingClientRect()}function $n(e){var t=e.data.pos,i=e.data.newPos,n=t.left-i.left,o=t.top-i.top;if(n||o){e.data.moved=!0;var a=e.elm.style;a.transform=a.WebkitTransform="translate("+n+"px,"+o+"px)",a.transitionDuration="0s"}}/*! + * Vue.js v2.6.10 + * (c) 2014-2019 Evan You + * Released under the MIT License. + */ +var Dn=Object.freeze({}),zn=Object.prototype.toString,On=(b("slot,component",!0),b("key,ref,slot,slot-scope,is")),An=Object.prototype.hasOwnProperty,Tn=/-(\w)/g,Mn=v(function(e){return e.replace(Tn,function(e,t){return t?t.toUpperCase():""})}),jn=v(function(e){return e.charAt(0).toUpperCase()+e.slice(1)}),Pn=/\B([A-Z])/g,En=v(function(e){return e.replace(Pn,"-$1").toLowerCase()}),Fn=Function.prototype.bind?x:k,Bn=function(e,t,i){return!1},Vn=function(e){return e},In="data-server-rendered",Nn=["component","directive","filter"],Rn=["beforeCreate","created","beforeMount","mounted","beforeUpdate","updated","beforeDestroy","destroyed","activated","deactivated","errorCaptured","serverPrefetch"],Ln={optionMergeStrategies:Object.create(null),silent:!1,productionTip:!1,devtools:!1,performance:!1,errorHandler:null,warnHandler:null,ignoredElements:[],keyCodes:Object.create(null),isReservedTag:Bn,isReservedAttr:Bn,isUnknownElement:Bn,getTagNamespace:C,parsePlatformTagName:Vn,mustUseProp:Bn,async:!0,_lifecycleHooks:Rn},Hn=/a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/,qn=new RegExp("[^"+Hn.source+".$_\\d]"),Un="__proto__"in{},Wn="undefined"!=typeof window,Yn="undefined"!=typeof WXEnvironment&&!!WXEnvironment.platform,Gn=Yn&&WXEnvironment.platform.toLowerCase(),Kn=Wn&&window.navigator.userAgent.toLowerCase(),Xn=Kn&&/msie|trident/.test(Kn),Jn=Kn&&Kn.indexOf("msie 9.0")>0,Qn=Kn&&Kn.indexOf("edge/")>0,Zn=(Kn&&Kn.indexOf("android"),Kn&&/iphone|ipad|ipod|ios/.test(Kn)||"ios"===Gn),eo=(Kn&&/chrome\/\d+/.test(Kn),Kn&&/phantomjs/.test(Kn),Kn&&Kn.match(/firefox\/(\d+)/)),to={}.watch,io=!1;if(Wn)try{var no={};Object.defineProperty(no,"passive",{get:function(){io=!0}}),window.addEventListener("test-passive",null,no)}catch(e){}var oo,ao,ro=function(){return void 0===oo&&(oo=!Wn&&!Yn&&void 0!==e&&(e.process&&"server"===e.process.env.VUE_ENV)),oo},so=Wn&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__,lo="undefined"!=typeof Symbol&&T(Symbol)&&"undefined"!=typeof Reflect&&T(Reflect.ownKeys);ao="undefined"!=typeof Set&&T(Set)?Set:function(){function e(){this.set=Object.create(null)}return e.prototype.has=function(e){return!0===this.set[e]},e.prototype.add=function(e){this.set[e]=!0},e.prototype.clear=function(){this.set=Object.create(null)},e}();var co=C,uo=0,fo=function(){this.id=uo++,this.subs=[]};fo.prototype.addSub=function(e){this.subs.push(e)},fo.prototype.removeSub=function(e){m(this.subs,e)},fo.prototype.depend=function(){fo.target&&fo.target.addDep(this)},fo.prototype.notify=function(){for(var e=this.subs.slice(),t=0,i=e.length;tdocument.createEvent("Event").timeStamp&&(Go=function(){return Ko.now()})}var Xo=0,Jo=function(e,t,i,n,o){this.vm=e,o&&(e._watcher=this),e._watchers.push(this),n?(this.deep=!!n.deep,this.user=!!n.user,this.lazy=!!n.lazy,this.sync=!!n.sync,this.before=n.before):this.deep=this.user=this.lazy=this.sync=!1,this.cb=i,this.id=++Xo,this.active=!0,this.dirty=this.lazy,this.deps=[],this.newDeps=[],this.depIds=new ao,this.newDepIds=new ao,this.expression="","function"==typeof t?this.getter=t:(this.getter=A(t),this.getter||(this.getter=C)),this.value=this.lazy?void 0:this.get()};Jo.prototype.get=function(){M(this);var e,t=this.vm;try{e=this.getter.call(t,t)}catch(e){if(!this.user)throw e;ae(e,t,'getter for watcher "'+this.expression+'"')}finally{this.deep&&ue(e),j(),this.cleanupDeps()}return e},Jo.prototype.addDep=function(e){var t=e.id;this.newDepIds.has(t)||(this.newDepIds.add(t),this.newDeps.push(e),this.depIds.has(t)||e.addSub(this))},Jo.prototype.cleanupDeps=function(){for(var e=this.deps.length;e--;){var t=this.deps[e];this.newDepIds.has(t.id)||t.removeSub(this)}var i=this.depIds;this.depIds=this.newDepIds,this.newDepIds=i,this.newDepIds.clear(),i=this.deps,this.deps=this.newDeps,this.newDeps=i,this.newDeps.length=0},Jo.prototype.update=function(){this.lazy?this.dirty=!0:this.sync?this.run():zt(this)},Jo.prototype.run=function(){if(this.active){var e=this.get();if(e!==this.value||l(e)||this.deep){var t=this.value;if(this.value=e,this.user)try{this.cb.call(this.vm,e,t)}catch(e){ae(e,this.vm,'callback for watcher "'+this.expression+'"')}else this.cb.call(this.vm,e,t)}}},Jo.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},Jo.prototype.depend=function(){for(var e=this.deps.length;e--;)this.deps[e].depend()},Jo.prototype.teardown=function(){if(this.active){this.vm._isBeingDestroyed||m(this.vm._watchers,this);for(var e=this.deps.length;e--;)this.deps[e].removeSub(this);this.active=!1}};var Qo={enumerable:!0,configurable:!0,get:C,set:C},Zo={lazy:!0},ea=0;!function(e){e.prototype._init=function(e){var t=this;t._uid=ea++,t._isVue=!0,e&&e._isComponent?Rt(t,e):t.$options=Q(Lt(t.constructor),e||{},t),t._renderProxy=t,t._self=t,mt(t),dt(t),ot(t),yt(t,"beforeCreate"),_e(t),At(t),ye(t),yt(t,"created"),t.$options.el&&t.$mount(t.$options.el)}}(qt),function(e){var t={};t.get=function(){return this._data};var i={};i.get=function(){return this._props},Object.defineProperty(e.prototype,"$data",t),Object.defineProperty(e.prototype,"$props",i),e.prototype.$set=R,e.prototype.$delete=L,e.prototype.$watch=function(e,t,i){var n=this;if(c(t))return Nt(n,e,t,i);i=i||{},i.user=!0;var o=new Jo(n,e,t,i);if(i.immediate)try{t.call(n,o.value)}catch(e){ae(e,n,'callback for immediate watcher "'+o.expression+'"')}return function(){o.teardown()}}}(qt),function(e){var t=/^hook:/;e.prototype.$on=function(e,i){var n=this;if(Array.isArray(e))for(var o=0,a=e.length;o1?w(i):i;for(var n=w(arguments,1),o='event handler for "'+e+'"',a=0,r=i.length;aparseInt(this.max)&&ei(l,c[0],c,this._vnode)),t.data.keepAlive=!0}return t||e&&e[0]}},na={KeepAlive:ia};!function(e){var t={};t.get=function(){return Ln},Object.defineProperty(e,"config",t),e.util={warn:co,extend:y,mergeOptions:Q,defineReactive:N},e.set=R,e.delete=L,e.nextTick=de,e.observable=function(e){return I(e),e},e.options=Object.create(null),Nn.forEach(function(t){e.options[t+"s"]=Object.create(null)}),e.options._base=e,y(e.options.components,na),Ut(e),Wt(e),Yt(e),Xt(e)}(qt),Object.defineProperty(qt.prototype,"$isServer",{get:ro}),Object.defineProperty(qt.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(qt,"FunctionalRenderContext",{value:Ue}),qt.version="2.6.10";var oa,aa,ra,sa=b("style,class"),la=b("input,textarea,option,select,progress"),ca=function(e,t,i){return"value"===i&&la(e)&&"button"!==t||"selected"===i&&"option"===e||"checked"===i&&"input"===e||"muted"===i&&"video"===e},da=b("contenteditable,draggable,spellcheck"),ua=b("events,caret,typing,plaintext-only"),fa=function(e,t){return ga(t)||"false"===t?"false":"contenteditable"===e&&ua(t)?t:"true"},pa=b("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,translate,truespeed,typemustmatch,visible"),ha="http://www.w3.org/1999/xlink",ba=function(e){return":"===e.charAt(5)&&"xlink"===e.slice(0,5)},ma=function(e){return ba(e)?e.slice(6,e.length):""},ga=function(e){return null==e||!1===e},va={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"},ka=b("html,body,base,head,link,meta,style,title,address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,menuitem,summary,content,element,shadow,template,blockquote,iframe,tfoot"),xa=b("svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,foreignObject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view",!0),wa=function(e){return ka(e)||xa(e)},ya=Object.create(null),_a=b("text,number,password,search,email,tel,url"),Ca=Object.freeze({createElement:ui,createElementNS:fi,createTextNode:pi,createComment:hi,insertBefore:bi,removeChild:mi,appendChild:gi,parentNode:vi,nextSibling:ki,tagName:xi,setTextContent:wi,setStyleScope:yi}),Sa={create:function(e,t){_i(t)},update:function(e,t){e.data.ref!==t.data.ref&&(_i(e,!0),_i(t))},destroy:function(e){_i(e,!0)}},$a=new ho("",{},[]),Da=["create","activate","update","remove","destroy"],za={create:Di,update:Di,destroy:function(e){Di(e,$a)}},Oa=Object.create(null),Aa=[Sa,za],Ta={create:Mi,update:Mi},Ma={create:Ei,update:Ei},ja="__r",Pa="__c",Ea=So&&!(eo&&Number(eo[1])<=53),Fa={create:Ni,update:Ni},Ba={create:Ri,update:Ri},Va=v(function(e){var t={},i=/;(?![^(]*\))/g,n=/:(.+)/;return e.split(i).forEach(function(e){if(e){var i=e.split(n);i.length>1&&(t[i[0].trim()]=i[1].trim())}}),t}),Ia=/^--/,Na=/\s*!important$/,Ra=function(e,t,i){if(Ia.test(t))e.style.setProperty(t,i);else if(Na.test(i))e.style.setProperty(En(t),i.replace(Na,""),"important");else{var n=Ha(t);if(Array.isArray(i))for(var o=0,a=i.length;oh?(u=n(i[g+1])?null:i[g+1].elm,v(e,u,i,p,g,a)):p>g&&x(e,t,f,h)}function _(e,t,i,n){for(var a=i;a=0&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},i(18),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(t,i(4))},function(e,t,i){(function(e,t){!function(e,i){"use strict";function n(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),i=0;i1)for(var i=1;i0?n:i)(e)}},function(e,t,i){var n=i(34)("keys"),o=i(25);e.exports=function(e){return n[e]||(n[e]=o(e))}},function(e,t,i){var n=i(7),o=n["__core-js_shared__"]||(n["__core-js_shared__"]={});e.exports=function(e){return o[e]||(o[e]={})}},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t,i){var n=i(31);e.exports=function(e){return Object(n(e))}},function(e,t,i){"use strict";var n=i(79)(!0);i(54)(String,"String",function(e){this._t=String(e),this._i=0},function(){var e,t=this._t,i=this._i;return i>=t.length?{value:void 0,done:!0}:(e=n(t,i),this._i+=e.length,{value:e,done:!1})})},function(e,t){e.exports=!0},function(e,t,i){var n=i(9).f,o=i(11),a=i(4)("toStringTag");e.exports=function(e,t,i){e&&!o(e=i?e:e.prototype,a)&&n(e,a,{configurable:!0,value:t})}},function(e,t,i){t.f=i(4)},function(e,t,i){var n=i(7),o=i(8),a=i(39),r=i(41),s=i(9).f;e.exports=function(e){var t=o.Symbol||(o.Symbol=a?{}:n.Symbol||{});"_"==e.charAt(0)||e in t||s(t,e,{value:r.f(e)})}},function(e,t,i){var n=i(0)(i(118),i(119),null,null,null);e.exports=n.exports},function(e,t,i){var n=i(0)(i(120),i(121),null,null,null);e.exports=n.exports},function(e,t,i){var n=i(0)(i(122),i(125),null,null,null);e.exports=n.exports},function(e,t,i){var n=i(71);e.exports=function(e,t,i){if(n(e),void 0===t)return e;switch(i){case 1:return function(i){return e.call(t,i)};case 2:return function(i,n){return e.call(t,i,n)};case 3:return function(i,n,o){return e.call(t,i,n,o)}}return function(){return e.apply(t,arguments)}}},function(e,t,i){e.exports=!i(10)&&!i(20)(function(){return 7!=Object.defineProperty(i(48)("div"),"a",{get:function(){return 7}}).a})},function(e,t,i){var n=i(19),o=i(7).document,a=n(o)&&n(o.createElement);e.exports=function(e){return a?o.createElement(e):{}}},function(e,t,i){var n=i(11),o=i(17),a=i(73)(!1),r=i(33)("IE_PROTO");e.exports=function(e,t){var i,s=o(e),l=0,c=[];for(i in s)i!=r&&n(s,i)&&c.push(i);for(;t.length>l;)n(s,i=t[l++])&&(~a(c,i)||c.push(i));return c}},function(e,t,i){var n=i(30);e.exports=Object("z").propertyIsEnumerable(0)?Object:function(e){return"String"==n(e)?e.split(""):Object(e)}},function(e,t,i){var n=i(32),o=Math.min;e.exports=function(e){return e>0?o(n(e),9007199254740991):0}},function(e,t,i){var n=i(0)(i(76),i(107),null,null,null);e.exports=n.exports},function(e,t,i){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=i(77),a=n(o),r=i(5),s=n(r),l="function"==typeof s.default&&"symbol"==typeof a.default?function(e){return typeof e}:function(e){return e&&"function"==typeof s.default&&e.constructor===s.default&&e!==s.default.prototype?"symbol":typeof e};t.default="function"==typeof s.default&&"symbol"===l(a.default)?function(e){return void 0===e?"undefined":l(e)}:function(e){return e&&"function"==typeof s.default&&e.constructor===s.default&&e!==s.default.prototype?"symbol":void 0===e?"undefined":l(e)}},function(e,t,i){"use strict";var n=i(39),o=i(18),a=i(55),r=i(15),s=i(22),l=i(80),c=i(40),d=i(83),u=i(4)("iterator"),f=!([].keys&&"next"in[].keys()),p=function(){return this};e.exports=function(e,t,i,h,b,m,g){l(i,t,h);var v,k,x,w=function(e){if(!f&&e in S)return S[e];switch(e){case"keys":case"values":return function(){return new i(this,e)}}return function(){return new i(this,e)}},y=t+" Iterator",_="values"==b,C=!1,S=e.prototype,$=S[u]||S["@@iterator"]||b&&S[b],D=$||w(b),z=b?_?w("entries"):D:void 0,O="Array"==t?S.entries||$:$;if(O&&(x=d(O.call(new e)))!==Object.prototype&&x.next&&(c(x,y,!0),n||"function"==typeof x[u]||r(x,u,p)),_&&$&&"values"!==$.name&&(C=!0,D=function(){return $.call(this)}),n&&!g||!f&&!C&&S[u]||r(S,u,D),s[t]=D,s[y]=p,b)if(v={values:_?D:w("values"),keys:m?D:w("keys"),entries:z},g)for(k in v)k in S||a(S,k,v[k]);else o(o.P+o.F*(f||C),t,v);return v}},function(e,t,i){e.exports=i(15)},function(e,t,i){var n=i(16),o=i(81),a=i(35),r=i(33)("IE_PROTO"),s=function(){},l=function(){var e,t=i(48)("iframe"),n=a.length;for(t.style.display="none",i(82).appendChild(t),t.src="javascript:",e=t.contentWindow.document,e.open(),e.write("\n\n\n\n\n\n// WEBPACK FOOTER //\n// src/App.vue","\n\n\n\n\n\n\n\n// WEBPACK FOOTER //\n// src/components/NavBar.vue","\n\n\n\n\n\n\n\n// WEBPACK FOOTER //\n// src/components/NavMenu.vue","\n\n\n\n\n\n\n\n// WEBPACK FOOTER //\n// src/components/OptionsPage.vue","\n\n\n\n\n\n\n\n// WEBPACK FOOTER //\n// src/components/PassViewPage.vue","\n\n\n\n\n\n\n\n// WEBPACK FOOTER //\n// src/components/RenderTaskViewPage.vue","\n\n\n\n\n\n\n\n// WEBPACK FOOTER //\n// src/components/TreeView.vue","\n\n\n\n\n\n\n\n// WEBPACK FOOTER //\n// src/components/DocumentViewPage.vue","\n\n\n\n\n\n\n\n// WEBPACK FOOTER //\n// src/components/ClipScrollTreeViewPage.vue","\n\n\n\n\n\n\n\n// WEBPACK FOOTER //\n// src/components/ScreenshotPage.vue","import Vue from 'vue';\nimport Buefy from 'buefy';\nimport 'buefy/dist/buefy.css';\nimport \"vue-material-design-icons/styles.css\";\nimport App from './App.vue';\nimport store from './store';\n\nVue.use(Buefy);\n\nnew Vue({\n el: '#app',\n store,\n render: h => h(App)\n})\n\n\n\n// WEBPACK FOOTER //\n// ./src/main.js","var scope = (typeof global !== \"undefined\" && global) ||\n (typeof self !== \"undefined\" && self) ||\n window;\nvar apply = Function.prototype.apply;\n\n// DOM APIs, for completeness\n\nexports.setTimeout = function() {\n return new Timeout(apply.call(setTimeout, scope, arguments), clearTimeout);\n};\nexports.setInterval = function() {\n return new Timeout(apply.call(setInterval, scope, arguments), clearInterval);\n};\nexports.clearTimeout =\nexports.clearInterval = function(timeout) {\n if (timeout) {\n timeout.close();\n }\n};\n\nfunction Timeout(id, clearFn) {\n this._id = id;\n this._clearFn = clearFn;\n}\nTimeout.prototype.unref = Timeout.prototype.ref = function() {};\nTimeout.prototype.close = function() {\n this._clearFn.call(scope, this._id);\n};\n\n// Does not start the time, just sets up the members needed.\nexports.enroll = function(item, msecs) {\n clearTimeout(item._idleTimeoutId);\n item._idleTimeout = msecs;\n};\n\nexports.unenroll = function(item) {\n clearTimeout(item._idleTimeoutId);\n item._idleTimeout = -1;\n};\n\nexports._unrefActive = exports.active = function(item) {\n clearTimeout(item._idleTimeoutId);\n\n var msecs = item._idleTimeout;\n if (msecs >= 0) {\n item._idleTimeoutId = setTimeout(function onTimeout() {\n if (item._onTimeout)\n item._onTimeout();\n }, msecs);\n }\n};\n\n// setimmediate attaches itself to the global object\nrequire(\"setimmediate\");\n// On some exotic environments, it's not clear which object `setimmediate` was\n// able to install onto. Search each possibility in the same order as the\n// `setimmediate` library.\nexports.setImmediate = (typeof self !== \"undefined\" && self.setImmediate) ||\n (typeof global !== \"undefined\" && global.setImmediate) ||\n (this && this.setImmediate);\nexports.clearImmediate = (typeof self !== \"undefined\" && self.clearImmediate) ||\n (typeof global !== \"undefined\" && global.clearImmediate) ||\n (this && this.clearImmediate);\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./node_modules/timers-browserify/main.js\n// module id = 17\n// module chunks = 0","(function (global, undefined) {\n \"use strict\";\n\n if (global.setImmediate) {\n return;\n }\n\n var nextHandle = 1; // Spec says greater than zero\n var tasksByHandle = {};\n var currentlyRunningATask = false;\n var doc = global.document;\n var registerImmediate;\n\n function setImmediate(callback) {\n // Callback can either be a function or a string\n if (typeof callback !== \"function\") {\n callback = new Function(\"\" + callback);\n }\n // Copy function arguments\n var args = new Array(arguments.length - 1);\n for (var i = 0; i < args.length; i++) {\n args[i] = arguments[i + 1];\n }\n // Store and register the task\n var task = { callback: callback, args: args };\n tasksByHandle[nextHandle] = task;\n registerImmediate(nextHandle);\n return nextHandle++;\n }\n\n function clearImmediate(handle) {\n delete tasksByHandle[handle];\n }\n\n function run(task) {\n var callback = task.callback;\n var args = task.args;\n switch (args.length) {\n case 0:\n callback();\n break;\n case 1:\n callback(args[0]);\n break;\n case 2:\n callback(args[0], args[1]);\n break;\n case 3:\n callback(args[0], args[1], args[2]);\n break;\n default:\n callback.apply(undefined, args);\n break;\n }\n }\n\n function runIfPresent(handle) {\n // From the spec: \"Wait until any invocations of this algorithm started before this one have completed.\"\n // So if we're currently running a task, we'll need to delay this invocation.\n if (currentlyRunningATask) {\n // Delay by doing a setTimeout. setImmediate was tried instead, but in Firefox 7 it generated a\n // \"too much recursion\" error.\n setTimeout(runIfPresent, 0, handle);\n } else {\n var task = tasksByHandle[handle];\n if (task) {\n currentlyRunningATask = true;\n try {\n run(task);\n } finally {\n clearImmediate(handle);\n currentlyRunningATask = false;\n }\n }\n }\n }\n\n function installNextTickImplementation() {\n registerImmediate = function(handle) {\n process.nextTick(function () { runIfPresent(handle); });\n };\n }\n\n function canUsePostMessage() {\n // The test against `importScripts` prevents this implementation from being installed inside a web worker,\n // where `global.postMessage` means something completely different and can't be used for this purpose.\n if (global.postMessage && !global.importScripts) {\n var postMessageIsAsynchronous = true;\n var oldOnMessage = global.onmessage;\n global.onmessage = function() {\n postMessageIsAsynchronous = false;\n };\n global.postMessage(\"\", \"*\");\n global.onmessage = oldOnMessage;\n return postMessageIsAsynchronous;\n }\n }\n\n function installPostMessageImplementation() {\n // Installs an event handler on `global` for the `message` event: see\n // * https://developer.mozilla.org/en/DOM/window.postMessage\n // * http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages\n\n var messagePrefix = \"setImmediate$\" + Math.random() + \"$\";\n var onGlobalMessage = function(event) {\n if (event.source === global &&\n typeof event.data === \"string\" &&\n event.data.indexOf(messagePrefix) === 0) {\n runIfPresent(+event.data.slice(messagePrefix.length));\n }\n };\n\n if (global.addEventListener) {\n global.addEventListener(\"message\", onGlobalMessage, false);\n } else {\n global.attachEvent(\"onmessage\", onGlobalMessage);\n }\n\n registerImmediate = function(handle) {\n global.postMessage(messagePrefix + handle, \"*\");\n };\n }\n\n function installMessageChannelImplementation() {\n var channel = new MessageChannel();\n channel.port1.onmessage = function(event) {\n var handle = event.data;\n runIfPresent(handle);\n };\n\n registerImmediate = function(handle) {\n channel.port2.postMessage(handle);\n };\n }\n\n function installReadyStateChangeImplementation() {\n var html = doc.documentElement;\n registerImmediate = function(handle) {\n // Create a + + diff --git a/third_party/webrender/debugger/package-lock.json b/third_party/webrender/debugger/package-lock.json new file mode 100644 index 00000000000..d5fdd52fd93 --- /dev/null +++ b/third_party/webrender/debugger/package-lock.json @@ -0,0 +1,7606 @@ +{ + "name": "debugger", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "dev": true, + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "acorn-dynamic-import": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", + "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", + "dev": true, + "requires": { + "acorn": "^4.0.3" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "dev": true + } + } + }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", + "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==", + "dev": true + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + } + }, + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", + "dev": true + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "array-includes": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", + "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" + } + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true, + "requires": { + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "autoprefixer": { + "version": "6.7.7", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", + "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", + "dev": true, + "requires": { + "browserslist": "^1.7.6", + "caniuse-db": "^1.0.30000634", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^5.2.16", + "postcss-value-parser": "^3.2.3" + }, + "dependencies": { + "browserslist": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", + "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "dev": true, + "requires": { + "caniuse-db": "^1.0.30000639", + "electron-to-chromium": "^1.2.7" + } + } + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + } + }, + "babel-helper-builder-binary-assignment-operator-visitor": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", + "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", + "dev": true, + "requires": { + "babel-helper-explode-assignable-expression": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-call-delegate": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", + "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-define-map": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", + "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-helper-explode-assignable-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", + "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", + "dev": true, + "requires": { + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-get-function-arity": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-hoist-variables": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-optimise-call-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", + "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-regex": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", + "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-helper-remap-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", + "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-replace-supers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", + "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", + "dev": true, + "requires": { + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-loader": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-7.1.5.tgz", + "integrity": "sha512-iCHfbieL5d1LfOQeeVJEUyD9rTwBcP/fcEbRCfempxTDuqrKpu0AZjLAQHEQa3Yqyj9ORKe2iHfoj4rHLf7xpw==", + "dev": true, + "requires": { + "find-cache-dir": "^1.0.0", + "loader-utils": "^1.0.2", + "mkdirp": "^0.5.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-check-es2015-constants": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", + "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-syntax-async-functions": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", + "dev": true + }, + "babel-plugin-syntax-async-generators": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz", + "integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=", + "dev": true + }, + "babel-plugin-syntax-exponentiation-operator": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", + "dev": true + }, + "babel-plugin-syntax-object-rest-spread": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", + "dev": true + }, + "babel-plugin-syntax-trailing-function-commas": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", + "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", + "dev": true + }, + "babel-plugin-transform-async-generator-functions": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz", + "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=", + "dev": true, + "requires": { + "babel-helper-remap-async-to-generator": "^6.24.1", + "babel-plugin-syntax-async-generators": "^6.5.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", + "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", + "dev": true, + "requires": { + "babel-helper-remap-async-to-generator": "^6.24.1", + "babel-plugin-syntax-async-functions": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-arrow-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", + "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-block-scoped-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", + "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-block-scoping": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", + "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-plugin-transform-es2015-classes": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", + "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", + "dev": true, + "requires": { + "babel-helper-define-map": "^6.24.1", + "babel-helper-function-name": "^6.24.1", + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-helper-replace-supers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-computed-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", + "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-destructuring": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-duplicate-keys": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", + "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-for-of": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", + "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", + "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-modules-amd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", + "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", + "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", + "dev": true, + "requires": { + "babel-plugin-transform-strict-mode": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-types": "^6.26.0" + } + }, + "babel-plugin-transform-es2015-modules-systemjs": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", + "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-modules-umd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", + "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-object-super": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", + "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", + "dev": true, + "requires": { + "babel-helper-replace-supers": "^6.24.1", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-parameters": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", + "dev": true, + "requires": { + "babel-helper-call-delegate": "^6.24.1", + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-shorthand-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", + "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-spread": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-sticky-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", + "dev": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-template-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", + "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-typeof-symbol": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", + "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-unicode-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", + "dev": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "regexpu-core": "^2.0.0" + } + }, + "babel-plugin-transform-exponentiation-operator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", + "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", + "dev": true, + "requires": { + "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", + "babel-plugin-syntax-exponentiation-operator": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-object-rest-spread": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", + "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", + "dev": true, + "requires": { + "babel-plugin-syntax-object-rest-spread": "^6.8.0", + "babel-runtime": "^6.26.0" + } + }, + "babel-plugin-transform-regenerator": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", + "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", + "dev": true, + "requires": { + "regenerator-transform": "^0.10.0" + } + }, + "babel-plugin-transform-strict-mode": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", + "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-preset-env": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.7.0.tgz", + "integrity": "sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg==", + "dev": true, + "requires": { + "babel-plugin-check-es2015-constants": "^6.22.0", + "babel-plugin-syntax-trailing-function-commas": "^6.22.0", + "babel-plugin-transform-async-to-generator": "^6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoping": "^6.23.0", + "babel-plugin-transform-es2015-classes": "^6.23.0", + "babel-plugin-transform-es2015-computed-properties": "^6.22.0", + "babel-plugin-transform-es2015-destructuring": "^6.23.0", + "babel-plugin-transform-es2015-duplicate-keys": "^6.22.0", + "babel-plugin-transform-es2015-for-of": "^6.23.0", + "babel-plugin-transform-es2015-function-name": "^6.22.0", + "babel-plugin-transform-es2015-literals": "^6.22.0", + "babel-plugin-transform-es2015-modules-amd": "^6.22.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.23.0", + "babel-plugin-transform-es2015-modules-systemjs": "^6.23.0", + "babel-plugin-transform-es2015-modules-umd": "^6.23.0", + "babel-plugin-transform-es2015-object-super": "^6.22.0", + "babel-plugin-transform-es2015-parameters": "^6.23.0", + "babel-plugin-transform-es2015-shorthand-properties": "^6.22.0", + "babel-plugin-transform-es2015-spread": "^6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "^6.22.0", + "babel-plugin-transform-es2015-template-literals": "^6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "^6.23.0", + "babel-plugin-transform-es2015-unicode-regex": "^6.22.0", + "babel-plugin-transform-exponentiation-operator": "^6.22.0", + "babel-plugin-transform-regenerator": "^6.22.0", + "browserslist": "^3.2.6", + "invariant": "^2.2.2", + "semver": "^5.3.0" + } + }, + "babel-preset-stage-3": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz", + "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=", + "dev": true, + "requires": { + "babel-plugin-syntax-trailing-function-commas": "^6.22.0", + "babel-plugin-transform-async-generator-functions": "^6.24.1", + "babel-plugin-transform-async-to-generator": "^6.24.1", + "babel-plugin-transform-exponentiation-operator": "^6.24.1", + "babel-plugin-transform-object-rest-spread": "^6.22.0" + } + }, + "babel-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "requires": { + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "dev": true + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "bluebird": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", + "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==", + "dev": true + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + } + }, + "bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "dev": true, + "requires": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz", + "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000844", + "electron-to-chromium": "^1.3.47" + } + }, + "buefy": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/buefy/-/buefy-0.6.7.tgz", + "integrity": "sha512-/d8srEpXAXWQjquHNP3ic9AVzwj8ar+jHfc7YjbspHT1AVeY3CDmzY2R1Am8ICnhioKAjRUfIie2/I9DiytXMQ==", + "requires": { + "bulma": "0.7.1" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "bulma": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/bulma/-/bulma-0.7.1.tgz", + "integrity": "sha512-wRSO2LXB+qI9Pyz2id+uZr4quz5aftSN7Ay1ysr1+krzVp3utD+Ci4CeKuZdrYGc800t65b7heXBL6qw2Wo/lQ==" + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + } + } + }, + "caniuse-api": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-1.6.1.tgz", + "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=", + "dev": true, + "requires": { + "browserslist": "^1.3.6", + "caniuse-db": "^1.0.30000529", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + }, + "dependencies": { + "browserslist": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", + "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "dev": true, + "requires": { + "caniuse-db": "^1.0.30000639", + "electron-to-chromium": "^1.2.7" + } + } + } + }, + "caniuse-db": { + "version": "1.0.30000960", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000960.tgz", + "integrity": "sha512-lgvTGHSJcROw38PaUzjAA1M3VA9hBkGsrTfo+Uiq6jSqfQXRSflpcQsM3MbytlFwBhPH6VD0GwLPihJgjqWrew==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30000960", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000960.tgz", + "integrity": "sha512-7nK5qs17icQaX6V3/RYrJkOsZyRNnroA4+ZwxaKJzIKy+crIy0Mz5CBlLySd2SNV+4nbUZeqeNfiaEieUBu3aA==", + "dev": true + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "chokidar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz", + "integrity": "sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "clap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", + "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", + "dev": true, + "requires": { + "chalk": "^1.1.3" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "coa": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", + "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", + "dev": true, + "requires": { + "q": "^1.1.2" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", + "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", + "dev": true, + "requires": { + "clone": "^1.0.2", + "color-convert": "^1.3.0", + "color-string": "^0.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "color-string": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", + "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", + "dev": true, + "requires": { + "color-name": "^1.0.0" + } + }, + "colormin": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz", + "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=", + "dev": true, + "requires": { + "color": "^0.11.0", + "css-color-names": "0.0.4", + "has": "^1.0.1" + } + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "compressible": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.16.tgz", + "integrity": "sha512-JQfEOdnI7dASwCuSPWIeVYwc/zMsu/+tRhoUvEfXz2gxOA2DNjmG5vhtFdBlhWPPGo+RdT9S3tgc/uH5qgDiiA==", + "dev": true, + "requires": { + "mime-db": ">= 1.38.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "^0.1.4" + } + }, + "consolidate": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.14.5.tgz", + "integrity": "sha1-WiUEe8dvcwcmZ8jLUsmJiI9JTGM=", + "dev": true, + "requires": { + "bluebird": "^3.1.1" + } + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "dev": true + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-js": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", + "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cosmiconfig": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-2.2.2.tgz", + "integrity": "sha512-GiNXLwAFPYHy25XmTPpafYvn3CLAkJ8FLsscq78MQd1Kh0OU6Yzhn4eV2MVF4G9WEQZoWEGltatdR+ntGPMl5A==", + "dev": true, + "requires": { + "is-directory": "^0.3.1", + "js-yaml": "^3.4.3", + "minimist": "^1.2.0", + "object-assign": "^4.1.0", + "os-homedir": "^1.0.1", + "parse-json": "^2.2.0", + "require-from-string": "^1.1.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-env": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.0.tgz", + "integrity": "sha512-jtdNFfFW1hB7sMhr/H6rW1Z45LFqyI431m3qU6bFXcQ3Eh7LtBuG3h74o7ohHZ3crrRkkqHlo4jYHFPcjroANg==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.5", + "is-windows": "^1.0.0" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "css-color-names": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", + "dev": true + }, + "css-loader": { + "version": "0.28.11", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.11.tgz", + "integrity": "sha512-wovHgjAx8ZIMGSL8pTys7edA1ClmzxHeY6n/d97gg5odgsxEgKjULPR0viqyC+FWMCL9sfqoC/QCUBo62tLvPg==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "css-selector-tokenizer": "^0.7.0", + "cssnano": "^3.10.0", + "icss-utils": "^2.1.0", + "loader-utils": "^1.0.2", + "lodash.camelcase": "^4.3.0", + "object-assign": "^4.1.1", + "postcss": "^5.0.6", + "postcss-modules-extract-imports": "^1.2.0", + "postcss-modules-local-by-default": "^1.2.0", + "postcss-modules-scope": "^1.1.0", + "postcss-modules-values": "^1.3.0", + "postcss-value-parser": "^3.3.0", + "source-list-map": "^2.0.0" + } + }, + "css-selector-tokenizer": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz", + "integrity": "sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA==", + "dev": true, + "requires": { + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" + }, + "dependencies": { + "regexpu-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + } + } + }, + "cssesc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "dev": true + }, + "cssnano": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", + "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=", + "dev": true, + "requires": { + "autoprefixer": "^6.3.1", + "decamelize": "^1.1.2", + "defined": "^1.0.0", + "has": "^1.0.1", + "object-assign": "^4.0.1", + "postcss": "^5.0.14", + "postcss-calc": "^5.2.0", + "postcss-colormin": "^2.1.8", + "postcss-convert-values": "^2.3.4", + "postcss-discard-comments": "^2.0.4", + "postcss-discard-duplicates": "^2.0.1", + "postcss-discard-empty": "^2.0.1", + "postcss-discard-overridden": "^0.1.1", + "postcss-discard-unused": "^2.2.1", + "postcss-filter-plugins": "^2.0.0", + "postcss-merge-idents": "^2.1.5", + "postcss-merge-longhand": "^2.0.1", + "postcss-merge-rules": "^2.0.3", + "postcss-minify-font-values": "^1.0.2", + "postcss-minify-gradients": "^1.0.1", + "postcss-minify-params": "^1.0.4", + "postcss-minify-selectors": "^2.0.4", + "postcss-normalize-charset": "^1.1.0", + "postcss-normalize-url": "^3.0.7", + "postcss-ordered-values": "^2.1.0", + "postcss-reduce-idents": "^2.2.2", + "postcss-reduce-initial": "^1.0.0", + "postcss-reduce-transforms": "^1.0.3", + "postcss-svgo": "^2.1.1", + "postcss-unique-selectors": "^2.0.2", + "postcss-value-parser": "^3.2.3", + "postcss-zindex": "^2.0.1" + } + }, + "csso": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/csso/-/csso-2.3.2.tgz", + "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=", + "dev": true, + "requires": { + "clap": "^1.0.9", + "source-map": "^0.5.3" + } + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true, + "requires": { + "es5-ext": "^0.10.9" + } + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "del": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "dev": true, + "requires": { + "globby": "^6.1.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", + "rimraf": "^2.2.8" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true + }, + "dns-packet": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", + "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "dev": true, + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "requires": { + "buffer-indexof": "^1.0.0" + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.124", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.124.tgz", + "integrity": "sha512-glecGr/kFdfeXUHOHAWvGcXrxNU+1wSO/t5B23tT1dtlvYB26GY8aHzZSWD7HqhqC800Lr+w/hQul6C5AF542w==", + "dev": true + }, + "elliptic": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", + "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "enhanced-resolve": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", + "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "object-assign": "^4.0.1", + "tapable": "^0.2.7" + } + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es5-ext": { + "version": "0.10.49", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.49.tgz", + "integrity": "sha512-3NMEhi57E31qdzmYp2jwRArIUsj1HI/RxbQ4bgnSB+AIKIxsAmTiK83bYMifIcpWvEc3P1X30DhUKOqEtF/kvg==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "^1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.14", + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "eventemitter3": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", + "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", + "dev": true + }, + "events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", + "dev": true + }, + "eventsource": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-0.1.6.tgz", + "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=", + "dev": true, + "requires": { + "original": ">=0.0.5" + } + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "express": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.3", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "file-loader": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", + "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", + "dev": true, + "requires": { + "loader-utils": "^1.0.2", + "schema-utils": "^0.4.5" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + } + }, + "find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flatten": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", + "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=", + "dev": true + }, + "follow-redirects": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", + "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", + "dev": true, + "requires": { + "debug": "^3.2.6" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.8.tgz", + "integrity": "sha512-tPvHgPGB7m40CZ68xqFGkKuzN+RnpGmSV+hgeKxhRpbxdqKXUFJGC3yonBOLzQBcJyGpdZFDfCsdOC2KFsXzeA==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "handle-thing": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", + "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=", + "dev": true + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" + } + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "html-comment-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", + "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==", + "dev": true + }, + "html-entities": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "dev": true + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "http-parser-js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.0.tgz", + "integrity": "sha512-cZdEF7r4gfRIq7ezX9J0T+kQmJNOub71dWbgAXVHDct80TKP4MCETtZQ31xyv38UwgzkWPYF/Xc0ge55dW9Z9w==", + "dev": true + }, + "http-proxy": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "dev": true, + "requires": { + "eventemitter3": "^3.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", + "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "dev": true, + "requires": { + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", + "dev": true + }, + "icss-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz", + "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", + "dev": true, + "requires": { + "postcss": "^6.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, + "import-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", + "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", + "dev": true, + "requires": { + "pkg-dir": "^2.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "internal-ip": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-1.2.0.tgz", + "integrity": "sha1-rp+/k7mEh4eF1QqN4bNWlWBYz1w=", + "dev": true, + "requires": { + "meow": "^3.3.0" + } + }, + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", + "dev": true + }, + "is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-svg": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-2.1.0.tgz", + "integrity": "sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk=", + "dev": true, + "requires": { + "html-comment-regex": "^1.1.0" + } + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "js-base64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", + "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, + "json-loader": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", + "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, + "loglevel": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", + "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "math-expression-evaluator": { + "version": "1.2.17", + "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", + "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=", + "dev": true + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + } + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true + }, + "mime-db": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.39.0.tgz", + "integrity": "sha512-DTsrw/iWVvwHH+9Otxccdyy0Tgiil6TWK/xhfARJZF/QFhwOgZgOIvA2/VIGpM8U7Q8z5nDmdDWC6tuVMJNibw==", + "dev": true + }, + "mime-types": { + "version": "2.1.23", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.23.tgz", + "integrity": "sha512-ROk/m+gMVSrRxTkMlaQOvFmFmYDc7sZgrjjM76abqmd2Cc5fCV7jAMA5XUccEtJ3cYiYdgixUVI+fApc2LkXlw==", + "dev": true, + "requires": { + "mime-db": "~1.39.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, + "requires": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + } + }, + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true + }, + "nan": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", + "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, + "neo-async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", + "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-forge": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", + "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==", + "dev": true + }, + "node-libs-browser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.0.tgz", + "integrity": "sha512-5MQunG/oyOaBdttrL40dA7bUfPORLRWMUJLQtMg7nluxUvk5XwnLdL9twQHFAjRx/y7mIMkLKT9++qPbbk6BZA==", + "dev": true, + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.0", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "0.0.4" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "opn": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "dev": true, + "requires": { + "url-parse": "^1.4.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", + "dev": true + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "pako": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", + "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", + "dev": true + }, + "parse-asn1": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", + "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "portfinder": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz", + "integrity": "sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw==", + "dev": true, + "requires": { + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "5.2.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "js-base64": "^2.1.9", + "source-map": "^0.5.6", + "supports-color": "^3.2.3" + }, + "dependencies": { + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "postcss-calc": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", + "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", + "dev": true, + "requires": { + "postcss": "^5.0.2", + "postcss-message-helpers": "^2.0.0", + "reduce-css-calc": "^1.2.6" + } + }, + "postcss-colormin": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-2.2.2.tgz", + "integrity": "sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks=", + "dev": true, + "requires": { + "colormin": "^1.0.5", + "postcss": "^5.0.13", + "postcss-value-parser": "^3.2.3" + } + }, + "postcss-convert-values": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz", + "integrity": "sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0=", + "dev": true, + "requires": { + "postcss": "^5.0.11", + "postcss-value-parser": "^3.1.2" + } + }, + "postcss-discard-comments": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", + "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", + "dev": true, + "requires": { + "postcss": "^5.0.14" + } + }, + "postcss-discard-duplicates": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz", + "integrity": "sha1-uavye4isGIFYpesSq8riAmO5GTI=", + "dev": true, + "requires": { + "postcss": "^5.0.4" + } + }, + "postcss-discard-empty": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", + "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", + "dev": true, + "requires": { + "postcss": "^5.0.14" + } + }, + "postcss-discard-overridden": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", + "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", + "dev": true, + "requires": { + "postcss": "^5.0.16" + } + }, + "postcss-discard-unused": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", + "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", + "dev": true, + "requires": { + "postcss": "^5.0.14", + "uniqs": "^2.0.0" + } + }, + "postcss-filter-plugins": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.3.tgz", + "integrity": "sha512-T53GVFsdinJhgwm7rg1BzbeBRomOg9y5MBVhGcsV0CxurUdVj1UlPdKtn7aqYA/c/QVkzKMjq2bSV5dKG5+AwQ==", + "dev": true, + "requires": { + "postcss": "^5.0.4" + } + }, + "postcss-load-config": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-1.2.0.tgz", + "integrity": "sha1-U56a/J3chiASHr+djDZz4M5Q0oo=", + "dev": true, + "requires": { + "cosmiconfig": "^2.1.0", + "object-assign": "^4.1.0", + "postcss-load-options": "^1.2.0", + "postcss-load-plugins": "^2.3.0" + } + }, + "postcss-load-options": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-load-options/-/postcss-load-options-1.2.0.tgz", + "integrity": "sha1-sJixVZ3awt8EvAuzdfmaXP4rbYw=", + "dev": true, + "requires": { + "cosmiconfig": "^2.1.0", + "object-assign": "^4.1.0" + } + }, + "postcss-load-plugins": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/postcss-load-plugins/-/postcss-load-plugins-2.3.0.tgz", + "integrity": "sha1-dFdoEWWZrKLwCfrUJrABdQSdjZI=", + "dev": true, + "requires": { + "cosmiconfig": "^2.1.1", + "object-assign": "^4.1.0" + } + }, + "postcss-merge-idents": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", + "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", + "dev": true, + "requires": { + "has": "^1.0.1", + "postcss": "^5.0.10", + "postcss-value-parser": "^3.1.1" + } + }, + "postcss-merge-longhand": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz", + "integrity": "sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg=", + "dev": true, + "requires": { + "postcss": "^5.0.4" + } + }, + "postcss-merge-rules": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz", + "integrity": "sha1-0d9d+qexrMO+VT8OnhDofGG19yE=", + "dev": true, + "requires": { + "browserslist": "^1.5.2", + "caniuse-api": "^1.5.2", + "postcss": "^5.0.4", + "postcss-selector-parser": "^2.2.2", + "vendors": "^1.0.0" + }, + "dependencies": { + "browserslist": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", + "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "dev": true, + "requires": { + "caniuse-db": "^1.0.30000639", + "electron-to-chromium": "^1.2.7" + } + } + } + }, + "postcss-message-helpers": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz", + "integrity": "sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4=", + "dev": true + }, + "postcss-minify-font-values": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", + "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "postcss": "^5.0.4", + "postcss-value-parser": "^3.0.2" + } + }, + "postcss-minify-gradients": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", + "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", + "dev": true, + "requires": { + "postcss": "^5.0.12", + "postcss-value-parser": "^3.3.0" + } + }, + "postcss-minify-params": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", + "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.1", + "postcss": "^5.0.2", + "postcss-value-parser": "^3.0.2", + "uniqs": "^2.0.0" + } + }, + "postcss-minify-selectors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", + "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.2", + "has": "^1.0.1", + "postcss": "^5.0.14", + "postcss-selector-parser": "^2.0.0" + } + }, + "postcss-modules-extract-imports": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz", + "integrity": "sha512-6jt9XZwUhwmRUhb/CkyJY020PYaPJsCyt3UjbaWo6XEbH/94Hmv6MP7fG2C5NDU/BcHzyGYxNtHvM+LTf9HrYw==", + "dev": true, + "requires": { + "postcss": "^6.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-modules-local-by-default": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", + "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", + "dev": true, + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-modules-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", + "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", + "dev": true, + "requires": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-modules-values": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", + "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", + "dev": true, + "requires": { + "icss-replace-symbols": "^1.1.0", + "postcss": "^6.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-charset": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", + "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", + "dev": true, + "requires": { + "postcss": "^5.0.5" + } + }, + "postcss-normalize-url": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", + "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", + "dev": true, + "requires": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^1.4.0", + "postcss": "^5.0.14", + "postcss-value-parser": "^3.2.3" + } + }, + "postcss-ordered-values": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz", + "integrity": "sha1-7sbCpntsQSqNsgQud/6NpD+VwR0=", + "dev": true, + "requires": { + "postcss": "^5.0.4", + "postcss-value-parser": "^3.0.1" + } + }, + "postcss-reduce-idents": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", + "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", + "dev": true, + "requires": { + "postcss": "^5.0.4", + "postcss-value-parser": "^3.0.2" + } + }, + "postcss-reduce-initial": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", + "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", + "dev": true, + "requires": { + "postcss": "^5.0.4" + } + }, + "postcss-reduce-transforms": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", + "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", + "dev": true, + "requires": { + "has": "^1.0.1", + "postcss": "^5.0.8", + "postcss-value-parser": "^3.0.1" + } + }, + "postcss-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", + "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", + "dev": true, + "requires": { + "flatten": "^1.0.2", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "postcss-svgo": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", + "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", + "dev": true, + "requires": { + "is-svg": "^2.0.0", + "postcss": "^5.0.14", + "postcss-value-parser": "^3.2.3", + "svgo": "^0.7.0" + } + }, + "postcss-unique-selectors": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", + "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.1", + "postcss": "^5.0.4", + "uniqs": "^2.0.0" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "postcss-zindex": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", + "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", + "dev": true, + "requires": { + "has": "^1.0.1", + "postcss": "^5.0.4", + "uniqs": "^2.0.0" + } + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, + "prettier": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.17.0.tgz", + "integrity": "sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw==", + "dev": true + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "proxy-addr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", + "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.0" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "dev": true, + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "dev": true, + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "reduce-css-calc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", + "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", + "dev": true, + "requires": { + "balanced-match": "^0.4.2", + "math-expression-evaluator": "^1.2.14", + "reduce-function-call": "^1.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + } + } + }, + "reduce-function-call": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.2.tgz", + "integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=", + "dev": true, + "requires": { + "balanced-match": "^0.4.2" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + } + } + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "regenerator-transform": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", + "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", + "dev": true, + "requires": { + "babel-runtime": "^6.18.0", + "babel-types": "^6.19.0", + "private": "^0.1.6" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpu-core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", + "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", + "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "requires": { + "align-text": "^0.1.1" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true + }, + "selfsigned": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.4.tgz", + "integrity": "sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==", + "dev": true, + "requires": { + "node-forge": "0.7.5" + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + } + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + } + }, + "sockjs": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", + "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "dev": true, + "requires": { + "faye-websocket": "^0.10.0", + "uuid": "^3.0.1" + } + }, + "sockjs-client": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.5.tgz", + "integrity": "sha1-G7fA9yIsQPQq3xT0RCy9Eml3GoM=", + "dev": true, + "requires": { + "debug": "^2.6.6", + "eventsource": "0.1.6", + "faye-websocket": "~0.11.0", + "inherits": "^2.0.1", + "json3": "^3.3.2", + "url-parse": "^1.1.8" + }, + "dependencies": { + "faye-websocket": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz", + "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + } + } + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "dev": true, + "requires": { + "is-plain-obj": "^1.0.0" + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", + "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", + "dev": true + }, + "spdy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.0.tgz", + "integrity": "sha512-ot0oEGT/PGUpzf/6uk4AWLqkq+irlqHXkrdbk51oWONh3bxQmBuljxPNl66zlRRcIJStWq0QkLUCPOPjgjvU0Q==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "readable-stream": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", + "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "svgo": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", + "integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=", + "dev": true, + "requires": { + "coa": "~1.0.1", + "colors": "~1.1.2", + "csso": "~2.3.1", + "js-yaml": "~3.7.0", + "mkdirp": "~0.5.1", + "sax": "~1.2.1", + "whet.extend": "~0.9.9" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "js-yaml": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", + "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^2.6.0" + } + } + } + }, + "tapable": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.9.tgz", + "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==", + "dev": true + }, + "thunky": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz", + "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==", + "dev": true + }, + "time-stamp": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-2.2.0.tgz", + "integrity": "sha512-zxke8goJQpBeEgD82CXABeMh0LSJcj7CXEd0OHOg45HgcofF7pxNwZm9+RknpxpDhwN4gFpySkApKfFYfRQnUA==", + "dev": true + }, + "timers-browserify": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", + "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "dependencies": { + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "uglifyjs-webpack-plugin": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz", + "integrity": "sha1-uVH0q7a9YX5m9j64kUmOORdj4wk=", + "dev": true, + "requires": { + "source-map": "^0.5.6", + "uglify-js": "^2.8.29", + "webpack-sources": "^1.0.1" + } + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, + "uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "url-parse": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.6.tgz", + "integrity": "sha512-/B8AD9iQ01seoXmXf9z/MjLZQIdOoYl/+gvsQF6+mpnxaTfG9P7srYaiqaDMyKkR36XMXfhqSHss5MyFAO8lew==", + "dev": true, + "requires": { + "querystringify": "^2.0.0", + "requires-port": "^1.0.0" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "vendors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.2.tgz", + "integrity": "sha512-w/hry/368nO21AN9QljsaIhb9ZiZtZARoVH5f3CsFbawdLdayCgKRPup7CggujvySMxx0I91NOyxdVENohprLQ==", + "dev": true + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "requires": { + "indexof": "0.0.1" + } + }, + "vue": { + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.10.tgz", + "integrity": "sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==" + }, + "vue-hot-reload-api": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.3.tgz", + "integrity": "sha512-KmvZVtmM26BQOMK1rwUZsrqxEGeKiYSZGA7SNWE6uExx8UX/cj9hq2MRV/wWC3Cq6AoeDGk57rL9YMFRel/q+g==", + "dev": true + }, + "vue-loader": { + "version": "13.7.3", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-13.7.3.tgz", + "integrity": "sha512-ACCwbfeC6HjY2pnDii+Zer+MZ6sdOtwvLmDXRK/BoD3WNR551V22R6KEagwHoTRJ0ZlIhpCBkptpCU6+Ri/05w==", + "dev": true, + "requires": { + "consolidate": "^0.14.0", + "hash-sum": "^1.0.2", + "loader-utils": "^1.1.0", + "lru-cache": "^4.1.1", + "postcss": "^6.0.8", + "postcss-load-config": "^1.1.0", + "postcss-selector-parser": "^2.0.0", + "prettier": "^1.7.0", + "resolve": "^1.4.0", + "source-map": "^0.6.1", + "vue-hot-reload-api": "^2.2.0", + "vue-style-loader": "^3.0.0", + "vue-template-es2015-compiler": "^1.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "vue-material-design-icons": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/vue-material-design-icons/-/vue-material-design-icons-0.8.2.tgz", + "integrity": "sha512-/NSZtfgcracfNRGf564Du3vH2EQIvQWmlQ3t9Zbg+GAe5kvGMp+tqlY+Yxu9/ow/obxOauR6BRrkLQ07rtqxUA==" + }, + "vue-style-loader": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-3.1.2.tgz", + "integrity": "sha512-ICtVdK/p+qXWpdSs2alWtsXt9YnDoYjQe0w5616j9+/EhjoxZkbun34uWgsMFnC1MhrMMwaWiImz3K2jK1Yp2Q==", + "dev": true, + "requires": { + "hash-sum": "^1.0.2", + "loader-utils": "^1.0.2" + } + }, + "vue-template-compiler": { + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz", + "integrity": "sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg==", + "dev": true, + "requires": { + "de-indent": "^1.0.2", + "he": "^1.1.0" + } + }, + "vue-template-es2015-compiler": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz", + "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", + "dev": true + }, + "vuex": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.0.tgz", + "integrity": "sha512-mdHeHT/7u4BncpUZMlxNaIdcN/HIt1GsGG5LKByArvYG/v6DvHcOxvDCts+7SRdCoIRGllK8IMZvQtQXLppDYg==" + }, + "watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "dev": true, + "requires": { + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "webpack": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.12.0.tgz", + "integrity": "sha512-Sw7MdIIOv/nkzPzee4o0EdvCuPmxT98+vVpIvwtcwcF1Q4SDSNp92vwcKc4REe7NItH9f1S4ra9FuQ7yuYZ8bQ==", + "dev": true, + "requires": { + "acorn": "^5.0.0", + "acorn-dynamic-import": "^2.0.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "async": "^2.1.2", + "enhanced-resolve": "^3.4.0", + "escope": "^3.6.0", + "interpret": "^1.0.0", + "json-loader": "^0.5.4", + "json5": "^0.5.1", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "mkdirp": "~0.5.0", + "node-libs-browser": "^2.0.0", + "source-map": "^0.5.3", + "supports-color": "^4.2.1", + "tapable": "^0.2.7", + "uglifyjs-webpack-plugin": "^0.4.6", + "watchpack": "^1.4.0", + "webpack-sources": "^1.0.1", + "yargs": "^8.0.2" + }, + "dependencies": { + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "^2.0.0" + } + } + } + }, + "webpack-dev-middleware": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz", + "integrity": "sha512-FCrqPy1yy/sN6U/SaEZcHKRXGlqU0DUaEBL45jkUYoB8foVb6wCnbIJ1HKIx+qUFTW+3JpVcCJCxZ8VATL4e+A==", + "dev": true, + "requires": { + "memory-fs": "~0.4.1", + "mime": "^1.5.0", + "path-is-absolute": "^1.0.0", + "range-parser": "^1.0.3", + "time-stamp": "^2.0.0" + }, + "dependencies": { + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + } + } + }, + "webpack-dev-server": { + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.11.5.tgz", + "integrity": "sha512-7TdOKKt7G3sWEhPKV0zP+nD0c4V9YKUJ3wDdBwQsZNo58oZIRoVIu66pg7PYkBW8A74msP9C2kLwmxGHndz/pw==", + "dev": true, + "requires": { + "ansi-html": "0.0.7", + "array-includes": "^3.0.3", + "bonjour": "^3.5.0", + "chokidar": "^2.1.2", + "compression": "^1.7.3", + "connect-history-api-fallback": "^1.3.0", + "debug": "^3.1.0", + "del": "^3.0.0", + "express": "^4.16.2", + "html-entities": "^1.2.0", + "http-proxy-middleware": "^0.19.1", + "import-local": "^1.0.0", + "internal-ip": "1.2.0", + "ip": "^1.1.5", + "killable": "^1.0.0", + "loglevel": "^1.4.1", + "opn": "^5.1.0", + "portfinder": "^1.0.9", + "selfsigned": "^1.9.1", + "serve-index": "^1.9.1", + "sockjs": "0.3.19", + "sockjs-client": "1.1.5", + "spdy": "^4.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^5.1.0", + "webpack-dev-middleware": "1.12.2", + "yargs": "6.6.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "^1.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "yargs": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", + "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^4.2.0" + } + }, + "yargs-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", + "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", + "dev": true, + "requires": { + "camelcase": "^3.0.0" + } + } + } + }, + "webpack-sources": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "websocket-driver": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz", + "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", + "dev": true, + "requires": { + "http-parser-js": ">=0.4.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "dev": true + }, + "whet.extend": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", + "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", + "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "read-pkg-up": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^7.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + } + } + }, + "yargs-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", + "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + } + } + } + } +} diff --git a/third_party/webrender/debugger/package.json b/third_party/webrender/debugger/package.json new file mode 100644 index 00000000000..b7f9aadfc07 --- /dev/null +++ b/third_party/webrender/debugger/package.json @@ -0,0 +1,36 @@ +{ + "name": "debugger", + "description": "WebRender Debugger", + "version": "1.0.0", + "author": "Glenn Watson ", + "license": "MIT", + "private": true, + "scripts": { + "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", + "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" + }, + "dependencies": { + "buefy": "^0.6.7", + "vue": "^2.5.11", + "vue-material-design-icons": "^0.8.2", + "vuex": "^3.0.1" + }, + "browserslist": [ + "> 1%", + "last 2 versions", + "not ie <= 8" + ], + "devDependencies": { + "babel-core": "^6.26.0", + "babel-loader": "^7.1.2", + "babel-preset-env": "^1.6.0", + "babel-preset-stage-3": "^6.24.1", + "cross-env": "^5.0.5", + "css-loader": "^0.28.7", + "file-loader": "^1.1.4", + "vue-loader": "^13.0.5", + "vue-template-compiler": "^2.4.4", + "webpack": "^3.6.0", + "webpack-dev-server": "^2.9.1" + } +} diff --git a/third_party/webrender/debugger/src/App.vue b/third_party/webrender/debugger/src/App.vue new file mode 100644 index 00000000000..01d303e79e5 --- /dev/null +++ b/third_party/webrender/debugger/src/App.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/third_party/webrender/debugger/src/components/ClipScrollTreeViewPage.vue b/third_party/webrender/debugger/src/components/ClipScrollTreeViewPage.vue new file mode 100644 index 00000000000..66a1edaf304 --- /dev/null +++ b/third_party/webrender/debugger/src/components/ClipScrollTreeViewPage.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/third_party/webrender/debugger/src/components/DocumentViewPage.vue b/third_party/webrender/debugger/src/components/DocumentViewPage.vue new file mode 100644 index 00000000000..6d5f02da735 --- /dev/null +++ b/third_party/webrender/debugger/src/components/DocumentViewPage.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/third_party/webrender/debugger/src/components/NavBar.vue b/third_party/webrender/debugger/src/components/NavBar.vue new file mode 100644 index 00000000000..2f8008d67d4 --- /dev/null +++ b/third_party/webrender/debugger/src/components/NavBar.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/third_party/webrender/debugger/src/components/NavMenu.vue b/third_party/webrender/debugger/src/components/NavMenu.vue new file mode 100644 index 00000000000..5a7cc3512a4 --- /dev/null +++ b/third_party/webrender/debugger/src/components/NavMenu.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/third_party/webrender/debugger/src/components/OptionsPage.vue b/third_party/webrender/debugger/src/components/OptionsPage.vue new file mode 100644 index 00000000000..fd8288c42cc --- /dev/null +++ b/third_party/webrender/debugger/src/components/OptionsPage.vue @@ -0,0 +1,162 @@ + + + + + diff --git a/third_party/webrender/debugger/src/components/PassViewPage.vue b/third_party/webrender/debugger/src/components/PassViewPage.vue new file mode 100644 index 00000000000..0ab6d7c4464 --- /dev/null +++ b/third_party/webrender/debugger/src/components/PassViewPage.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/third_party/webrender/debugger/src/components/RenderTaskViewPage.vue b/third_party/webrender/debugger/src/components/RenderTaskViewPage.vue new file mode 100644 index 00000000000..c3937fe0ce2 --- /dev/null +++ b/third_party/webrender/debugger/src/components/RenderTaskViewPage.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/third_party/webrender/debugger/src/components/ScreenshotPage.vue b/third_party/webrender/debugger/src/components/ScreenshotPage.vue new file mode 100644 index 00000000000..8d4017856e4 --- /dev/null +++ b/third_party/webrender/debugger/src/components/ScreenshotPage.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/third_party/webrender/debugger/src/components/TreeView.vue b/third_party/webrender/debugger/src/components/TreeView.vue new file mode 100644 index 00000000000..bde473bda2f --- /dev/null +++ b/third_party/webrender/debugger/src/components/TreeView.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/third_party/webrender/debugger/src/main.js b/third_party/webrender/debugger/src/main.js new file mode 100644 index 00000000000..1259c32a0f3 --- /dev/null +++ b/third_party/webrender/debugger/src/main.js @@ -0,0 +1,14 @@ +import Vue from 'vue'; +import Buefy from 'buefy'; +import 'buefy/dist/buefy.css'; +import "vue-material-design-icons/styles.css"; +import App from './App.vue'; +import store from './store'; + +Vue.use(Buefy); + +new Vue({ + el: '#app', + store, + render: h => h(App) +}) diff --git a/third_party/webrender/debugger/src/store/index.js b/third_party/webrender/debugger/src/store/index.js new file mode 100644 index 00000000000..7749d7a8416 --- /dev/null +++ b/third_party/webrender/debugger/src/store/index.js @@ -0,0 +1,105 @@ +import Vue from 'vue' +import Vuex from 'vuex' + +Vue.use(Vuex) + +class Connection { + constructor() { + this.ws = null; + } + + connect(context) { + var ws = new WebSocket("ws://127.0.0.1:3583"); + + ws.onopen = function() { + context.commit('setConnected', true); + } + + ws.onmessage = function(evt) { + var json = JSON.parse(evt.data); + if (json['kind'] == "passes") { + context.commit('setPasses', json['passes']); + } else if (json['kind'] == "render_tasks") { + context.commit('setRenderTasks', json['root']); + } else if (json['kind'] == "documents") { + context.commit('setDocuments', json['root']); + } else if (json['kind'] == "clip_scroll_tree") { + context.commit('setClipScrollTree', json['root']); + } else if (json['kind'] == "screenshot") { + context.commit('setScreenshot', json['data']); + } else { + console.warn("unknown message kind: " + json['kind']); + } + } + + ws.onclose = function() { + context.commit('setConnected', false); + } + + this.ws = ws; + } + + send(msg) { + if (this.ws !== null) { + this.ws.send(msg); + } + } + + disconnect() { + if (this.ws !== null) { + this.ws.close(); + this.ws = null; + } + } +} + +var connection = new Connection(); + +const store = new Vuex.Store({ + strict: true, + state: { + connected: false, + page: 'options', + passes: [], + render_tasks: [], + documents: [], + clip_scroll_tree: [], + screenshot: [], + }, + mutations: { + setConnected(state, connected) { + state.connected = connected; + }, + setPage(state, name) { + state.page = name; + }, + setPasses(state, passes) { + state.passes = passes; + }, + setRenderTasks(state, render_tasks) { + state.render_tasks = render_tasks; + }, + setDocuments(state, documents) { + state.documents = documents; + }, + setClipScrollTree(state, clip_scroll_tree) { + state.clip_scroll_tree = clip_scroll_tree; + }, + setScreenshot(state, screenshot) { + state.screenshot = screenshot; + }, + }, + actions: { + connect(context) { + connection.connect(context); + }, + disconnect(context) { + connection.disconnect(); + }, + sendMessage(context, msg) { + connection.send(msg); + } + } +}); + +export default store; diff --git a/third_party/webrender/debugger/webpack.config.js b/third_party/webrender/debugger/webpack.config.js new file mode 100644 index 00000000000..6edcbfcc3c3 --- /dev/null +++ b/third_party/webrender/debugger/webpack.config.js @@ -0,0 +1,81 @@ +var path = require('path') +var webpack = require('webpack') + +module.exports = { + entry: './src/main.js', + output: { + path: path.resolve(__dirname, './dist'), + publicPath: '/dist/', + filename: 'build.js' + }, + module: { + rules: [ + { + test: /\.css$/, + use: [ + 'vue-style-loader', + 'css-loader' + ], + }, { + test: /\.vue$/, + loader: 'vue-loader', + options: { + loaders: { + } + // other vue-loader options go here + } + }, + { + test: /\.js$/, + loader: 'babel-loader', + exclude: /node_modules/ + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: '[name].[ext]?[hash]' + } + } + ] + }, + resolve: { + alias: { + 'vue$': 'vue/dist/vue.esm.js' + }, + alias : { + "icons": path.resolve(__dirname, "node_modules/vue-material-design-icons") + }, + extensions: ['*', '.js', '.vue', '.json'] + }, + devServer: { + historyApiFallback: true, + noInfo: true, + overlay: true + }, + performance: { + hints: false + }, + devtool: '#eval-source-map' +} + +if (process.env.NODE_ENV === 'production') { + module.exports.devtool = '#source-map' + // http://vue-loader.vuejs.org/en/workflow/production.html + module.exports.plugins = (module.exports.plugins || []).concat([ + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: '"production"' + } + }), + new webpack.optimize.UglifyJsPlugin({ + sourceMap: true, + compress: { + warnings: false + } + }), + new webpack.LoaderOptionsPlugin({ + minimize: true + }) + ]) +} diff --git a/third_party/webrender/direct-composition/Cargo.toml b/third_party/webrender/direct-composition/Cargo.toml new file mode 100644 index 00000000000..59528f98ce2 --- /dev/null +++ b/third_party/webrender/direct-composition/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "direct-composition" +version = "0.1.0" +authors = ["Simon Sapin "] +license = "MPL-2.0" +edition = "2018" + +[target.'cfg(windows)'.dependencies] +euclid = "0.22" +gleam = "0.12" +mozangle = {version = "0.3.1", features = ["egl"]} +webrender = {path = "../webrender"} +winapi = {version = "0.3", features = ["winerror", "d3d11", "dcomp"]} +winit = "0.19" diff --git a/third_party/webrender/direct-composition/src/com.rs b/third_party/webrender/direct-composition/src/com.rs new file mode 100644 index 00000000000..8fb384695cc --- /dev/null +++ b/third_party/webrender/direct-composition/src/com.rs @@ -0,0 +1,112 @@ +/* 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 std::ops; +use std::ptr; +use winapi::Interface; +use winapi::ctypes::c_void; +use winapi::shared::guiddef::GUID; +use winapi::shared::winerror::HRESULT; +use winapi::shared::winerror::SUCCEEDED; +use winapi::um::unknwnbase::IUnknown; + +pub fn as_ptr(x: &T) -> *mut T { + x as *const T as _ +} + +pub trait CheckHResult { + fn check_hresult(self); +} + +impl CheckHResult for HRESULT { + fn check_hresult(self) { + if !SUCCEEDED(self) { + panic_com(self) + } + } +} + +fn panic_com(hresult: HRESULT) -> ! { + panic!("COM error 0x{:08X}", hresult as u32) +} + +/// Forked from +#[derive(PartialEq, Debug)] +pub struct ComPtr(*mut T) where T: Interface; + +impl ComPtr where T: Interface { + /// Creates a `ComPtr` to wrap a raw pointer. + /// It takes ownership over the pointer which means it does __not__ call `AddRef`. + /// `T` __must__ be a COM interface that inherits from `IUnknown`. + pub unsafe fn from_raw(ptr: *mut T) -> ComPtr { + assert!(!ptr.is_null()); + ComPtr(ptr) + } + + /// For use with APIs that take an interface UUID and + /// "return" a new COM object through a `*mut *mut c_void` out-parameter. + pub unsafe fn new_with_uuid(f: F) -> Self + where F: FnOnce(&GUID, *mut *mut c_void) -> HRESULT + { + Self::new_with(|ptr| f(&T::uuidof(), ptr as _)) + } + + /// For use with APIs that "return" a new COM object through a `*mut *mut T` out-parameter. + pub unsafe fn new_with(f: F) -> Self + where F: FnOnce(*mut *mut T) -> HRESULT + { + let mut ptr = ptr::null_mut(); + let hresult = f(&mut ptr); + if SUCCEEDED(hresult) { + ComPtr::from_raw(ptr) + } else { + if !ptr.is_null() { + let ptr = ptr as *mut IUnknown; + (*ptr).Release(); + } + panic_com(hresult) + } + } + + pub fn as_raw(&self) -> *mut T { + self.0 + } + + fn as_unknown(&self) -> &IUnknown { + unsafe { + &*(self.0 as *mut IUnknown) + } + } + + /// Performs QueryInterface fun. + pub fn cast(&self) -> ComPtr where U: Interface { + unsafe { + ComPtr::::new_with_uuid(|uuid, ptr| self.as_unknown().QueryInterface(uuid, ptr)) + } + } +} + +impl ops::Deref for ComPtr where T: Interface { + type Target = T; + fn deref(&self) -> &T { + unsafe { &*self.0 } + } +} + +impl Clone for ComPtr where T: Interface { + fn clone(&self) -> Self { + unsafe { + self.as_unknown().AddRef(); + ComPtr(self.0) + } + } +} + +impl Drop for ComPtr where T: Interface { + fn drop(&mut self) { + unsafe { + self.as_unknown().Release(); + } + } +} diff --git a/third_party/webrender/direct-composition/src/egl.rs b/third_party/webrender/direct-composition/src/egl.rs new file mode 100644 index 00000000000..8bd5afb72a6 --- /dev/null +++ b/third_party/webrender/direct-composition/src/egl.rs @@ -0,0 +1,174 @@ +/* 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 mozangle::egl::ffi::*; +use std::os::raw::c_void; +use std::ptr; +use std::rc::Rc; +use winapi::um::d3d11::ID3D11Device; +use winapi::um::d3d11::ID3D11Texture2D; + +pub use mozangle::egl::get_proc_address; + +pub struct SharedEglThings { + device: EGLDeviceEXT, + display: types::EGLDisplay, + config: types::EGLConfig, + context: types::EGLContext, +} + +fn cast_attributes(slice: &[types::EGLenum]) -> &EGLint { + unsafe { + &*(slice.as_ptr() as *const EGLint) + } +} + +macro_rules! attributes { + ($( $key: expr => $value: expr, )*) => { + cast_attributes(&[ + $( $key, $value, )* + NONE, + ]) + } +} + +impl SharedEglThings { + pub unsafe fn new(d3d_device: *mut ID3D11Device) -> Rc { + let device = eglCreateDeviceANGLE( + D3D11_DEVICE_ANGLE, + d3d_device as *mut c_void, + ptr::null(), + ).check(); + let display = GetPlatformDisplayEXT( + PLATFORM_DEVICE_EXT, + device, + attributes! [ + EXPERIMENTAL_PRESENT_PATH_ANGLE => EXPERIMENTAL_PRESENT_PATH_FAST_ANGLE, + ], + ).check(); + Initialize(display, ptr::null_mut(), ptr::null_mut()).check(); + + // Adapted from + // https://searchfox.org/mozilla-central/rev/056a4057/gfx/gl/GLContextProviderEGL.cpp#635 + let mut configs = [ptr::null(); 64]; + let mut num_configs = 0; + ChooseConfig( + display, + attributes! [ + SURFACE_TYPE => WINDOW_BIT, + RENDERABLE_TYPE => OPENGL_ES2_BIT, + RED_SIZE => 8, + GREEN_SIZE => 8, + BLUE_SIZE => 8, + ALPHA_SIZE => 8, + ], + configs.as_mut_ptr(), + configs.len() as i32, + &mut num_configs, + ).check(); + let config = pick_config(&configs[..num_configs as usize]); + + let context = CreateContext( + display, config, NO_CONTEXT, + attributes![ + CONTEXT_CLIENT_VERSION => 3, + ] + ).check(); + MakeCurrent(display, NO_SURFACE, NO_SURFACE, context).check(); + + Rc::new(SharedEglThings { device, display, config, context }) + } +} + +fn pick_config(configs: &[types::EGLConfig]) -> types::EGLConfig { + // FIXME: better criteria to make this choice? + // Firefox uses GetConfigAttrib to find a config that has the requested r/g/b/a sizes + // https://searchfox.org/mozilla-central/rev/056a4057/gfx/gl/GLContextProviderEGL.cpp#662-685 + + configs[0] +} + +impl Drop for SharedEglThings { + fn drop(&mut self) { + unsafe { + // FIXME does EGLDisplay or EGLConfig need clean up? How? + DestroyContext(self.display, self.context).check(); + eglReleaseDeviceANGLE(self.device).check(); + } + } +} + +pub struct PerVisualEglThings { + shared: Rc, + surface: types::EGLSurface, +} + +impl PerVisualEglThings { + pub unsafe fn new(shared: Rc, buffer: *const ID3D11Texture2D, + width: u32, height: u32) + -> Self { + let surface = CreatePbufferFromClientBuffer( + shared.display, + D3D_TEXTURE_ANGLE, + buffer as types::EGLClientBuffer, + shared.config, + attributes! [ + WIDTH => width, + HEIGHT => height, + FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE => TRUE, + ], + ).check(); + + PerVisualEglThings { shared, surface } + } + + pub fn make_current(&self) { + unsafe { + MakeCurrent(self.shared.display, self.surface, self.surface, self.shared.context).check(); + } + } +} + +impl Drop for PerVisualEglThings { + fn drop(&mut self) { + unsafe { + DestroySurface(self.shared.display, self.surface).check(); + } + } +} + +fn check_error() { + unsafe { + let error = GetError() as types::EGLenum; + assert_eq!(error, SUCCESS, "0x{:x} != 0x{:x}", error, SUCCESS); + } +} + +trait Check { + fn check(self) -> Self; +} + +impl Check for *const c_void { + fn check(self) -> Self { + check_error(); + assert!(!self.is_null()); + self + } +} + +impl Check for *mut c_void { + fn check(self) -> Self { + check_error(); + assert!(!self.is_null()); + self + } +} + +impl Check for types::EGLBoolean { + fn check(self) -> Self { + check_error(); + assert_eq!(self, TRUE); + self + } +} diff --git a/third_party/webrender/direct-composition/src/lib.rs b/third_party/webrender/direct-composition/src/lib.rs new file mode 100644 index 00000000000..fadb8f2b723 --- /dev/null +++ b/third_party/webrender/direct-composition/src/lib.rs @@ -0,0 +1,179 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![cfg(windows)] + +extern crate gleam; +extern crate mozangle; +extern crate winapi; + +use com::{ComPtr, CheckHResult, as_ptr}; +use std::ptr; +use std::rc::Rc; +use winapi::shared::dxgi1_2::DXGI_SWAP_CHAIN_DESC1; +use winapi::shared::dxgi1_2::IDXGIFactory2; +use winapi::shared::minwindef::{TRUE, FALSE}; +use winapi::shared::windef::HWND; +use winapi::um::d3d11::ID3D11Device; +use winapi::um::dcomp::IDCompositionDevice; +use winapi::um::dcomp::IDCompositionTarget; +use winapi::um::dcomp::IDCompositionVisual; + +mod com; +mod egl; + +pub struct DirectComposition { + d3d_device: ComPtr, + dxgi_factory: ComPtr, + + egl: Rc, + pub gleam: Rc, + + composition_device: ComPtr, + root_visual: ComPtr, + + #[allow(unused)] // Needs to be kept alive + composition_target: ComPtr, +} + +impl DirectComposition { + /// Initialize DirectComposition in the given window + /// + /// # Safety + /// + /// `hwnd` must be a valid handle to a window. + pub unsafe fn new(hwnd: HWND) -> Self { + let d3d_device = ComPtr::new_with(|ptr_ptr| winapi::um::d3d11::D3D11CreateDevice( + ptr::null_mut(), + winapi::um::d3dcommon::D3D_DRIVER_TYPE_HARDWARE, + ptr::null_mut(), + winapi::um::d3d11::D3D11_CREATE_DEVICE_BGRA_SUPPORT | + if cfg!(debug_assertions) { + winapi::um::d3d11::D3D11_CREATE_DEVICE_DEBUG + } else { + 0 + }, + ptr::null_mut(), + 0, + winapi::um::d3d11::D3D11_SDK_VERSION, + ptr_ptr, + &mut 0, + ptr::null_mut(), + )); + + let egl = egl::SharedEglThings::new(d3d_device.as_raw()); + let gleam = gleam::gl::GlesFns::load_with(egl::get_proc_address); + + let dxgi_device = d3d_device.cast::(); + + // https://msdn.microsoft.com/en-us/library/windows/desktop/hh404556(v=vs.85).aspx#code-snippet-1 + // “Because you can create a Direct3D device without creating a swap chain, + // you might need to retrieve the factory that is used to create the device + // in order to create a swap chain.” + let adapter = ComPtr::new_with(|ptr_ptr| dxgi_device.GetAdapter(ptr_ptr)); + let dxgi_factory = ComPtr::::new_with_uuid(|uuid, ptr_ptr| { + adapter.GetParent(uuid, ptr_ptr) + }); + + // Create the DirectComposition device object. + let composition_device = ComPtr::::new_with_uuid(|uuid, ptr_ptr| { + winapi::um::dcomp::DCompositionCreateDevice(&*dxgi_device, uuid, ptr_ptr) + }); + + // Create the composition target object based on the + // specified application window. + let composition_target = ComPtr::new_with(|ptr_ptr| { + composition_device.CreateTargetForHwnd(hwnd, TRUE, ptr_ptr) + }); + + let root_visual = ComPtr::new_with(|ptr_ptr| composition_device.CreateVisual(ptr_ptr)); + composition_target.SetRoot(&*root_visual).check_hresult(); + + DirectComposition { + d3d_device, dxgi_factory, + egl, gleam, + composition_device, composition_target, root_visual, + } + } + + /// Execute changes to the DirectComposition scene. + pub fn commit(&self) { + unsafe { + self.composition_device.Commit().check_hresult() + } + } + + pub fn create_angle_visual(&self, width: u32, height: u32) -> AngleVisual { + unsafe { + let desc = DXGI_SWAP_CHAIN_DESC1 { + Width: width, + Height: height, + Format: winapi::shared::dxgiformat::DXGI_FORMAT_B8G8R8A8_UNORM, + Stereo: FALSE, + SampleDesc: winapi::shared::dxgitype::DXGI_SAMPLE_DESC { + Count: 1, + Quality: 0, + }, + BufferUsage: winapi::shared::dxgitype::DXGI_USAGE_RENDER_TARGET_OUTPUT, + BufferCount: 2, + Scaling: winapi::shared::dxgi1_2::DXGI_SCALING_STRETCH, + SwapEffect: winapi::shared::dxgi::DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, + AlphaMode: winapi::shared::dxgi1_2::DXGI_ALPHA_MODE_PREMULTIPLIED, + Flags: 0, + }; + let swap_chain = ComPtr::::new_with(|ptr_ptr| { + self.dxgi_factory.CreateSwapChainForComposition( + as_ptr(&self.d3d_device), + &desc, + ptr::null_mut(), + ptr_ptr, + ) + }); + let back_buffer = ComPtr::::new_with_uuid(|uuid, ptr_ptr| { + swap_chain.GetBuffer(0, uuid, ptr_ptr) + }); + let egl = egl::PerVisualEglThings::new(self.egl.clone(), &*back_buffer, width, height); + let gleam = self.gleam.clone(); + + let visual = ComPtr::new_with(|ptr_ptr| self.composition_device.CreateVisual(ptr_ptr)); + visual.SetContent(&*****swap_chain).check_hresult(); + self.root_visual.AddVisual(&*visual, FALSE, ptr::null_mut()).check_hresult(); + + AngleVisual { visual, swap_chain, egl, gleam } + } + } +} + +/// A DirectComposition "visual" configured for rendering with Direct3D. +pub struct AngleVisual { + visual: ComPtr, + swap_chain: ComPtr, + egl: egl::PerVisualEglThings, + pub gleam: Rc, +} + +impl AngleVisual { + pub fn set_offset_x(&self, offset_x: f32) { + unsafe { + self.visual.SetOffsetX_1(offset_x).check_hresult() + } + } + + pub fn set_offset_y(&self, offset_y: f32) { + unsafe { + self.visual.SetOffsetY_1(offset_y).check_hresult() + } + } + + pub fn make_current(&self) { + self.egl.make_current() + } + + pub fn present(&self) { + self.gleam.finish(); + unsafe { + self.swap_chain.Present(0, 0).check_hresult() + } + } +} diff --git a/third_party/webrender/direct-composition/src/main.rs b/third_party/webrender/direct-composition/src/main.rs new file mode 100644 index 00000000000..e1999f5f8f1 --- /dev/null +++ b/third_party/webrender/direct-composition/src/main.rs @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#[cfg(not(windows))] +fn main() { + println!("This demo only runs on Windows."); +} + +#[cfg(windows)] +include!("main_windows.rs"); diff --git a/third_party/webrender/direct-composition/src/main_windows.rs b/third_party/webrender/direct-composition/src/main_windows.rs new file mode 100644 index 00000000000..18f1300a51a --- /dev/null +++ b/third_party/webrender/direct-composition/src/main_windows.rs @@ -0,0 +1,212 @@ +/* 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/. */ + +extern crate direct_composition; +extern crate euclid; +extern crate gleam; +extern crate webrender; +extern crate winit; + +use euclid::size2; +use direct_composition::DirectComposition; +use std::sync::mpsc; +use webrender::api; +use winit::os::windows::{WindowExt, WindowBuilderExt}; +use winit::dpi::LogicalSize; + +fn main() { + let mut events_loop = winit::EventsLoop::new(); + + let (tx, rx) = mpsc::channel(); + let notifier = Box::new(Notifier { events_proxy: events_loop.create_proxy(), tx }); + + let window = winit::WindowBuilder::new() + .with_title("WebRender + ANGLE + DirectComposition") + .with_dimensions(LogicalSize::new(1024., 768.)) + .with_decorations(true) + .with_transparency(true) + .with_no_redirection_bitmap(true) + .build(&events_loop) + .unwrap(); + + let composition = direct_composition_from_window(&window); + let factor = window.get_hidpi_factor() as f32; + + let mut clicks: usize = 0; + let mut offset_y = 100.; + let mut rects = [ + Rectangle::new(&composition, ¬ifier, factor, size2(300, 200), 0., 0.2, 0.4, 1.), + Rectangle::new(&composition, ¬ifier, factor, size2(400, 300), 0., 0.5, 0., 0.5), + ]; + rects[0].render(factor, &rx); + rects[1].render(factor, &rx); + + rects[0].visual.set_offset_x(100.); + rects[0].visual.set_offset_y(50.); + + rects[1].visual.set_offset_x(200.); + rects[1].visual.set_offset_y(offset_y); + + composition.commit(); + + events_loop.run_forever(|event| { + if let winit::Event::WindowEvent { event, .. } = event { + match event { + winit::WindowEvent::CloseRequested => { + return winit::ControlFlow::Break + } + winit::WindowEvent::MouseWheel { delta, .. } => { + let dy = match delta { + winit::MouseScrollDelta::LineDelta(_, dy) => dy, + winit::MouseScrollDelta::PixelDelta(pos) => pos.y as f32, + }; + offset_y = (offset_y - 10. * dy).max(0.).min(468.); + + rects[1].visual.set_offset_y(offset_y); + composition.commit(); + } + winit::WindowEvent::MouseInput { + button: winit::MouseButton::Left, + state: winit::ElementState::Pressed, + .. + } => { + clicks += 1; + let rect = &mut rects[clicks % 2]; + rect.color.g += 0.1; + rect.color.g %= 1.; + rect.render(factor, &rx) + } + _ => {} + } + } + winit::ControlFlow::Continue + }); +} + +fn direct_composition_from_window(window: &winit::Window) -> DirectComposition { + unsafe { + DirectComposition::new(window.get_hwnd() as _) + } +} + +struct Rectangle { + visual: direct_composition::AngleVisual, + renderer: Option, + api: api::RenderApi, + document_id: api::DocumentId, + size: api::units::DeviceIntSize, + color: api::ColorF, +} + +impl Rectangle { + fn new(composition: &DirectComposition, notifier: &Box, + device_pixel_ratio: f32, size: api::units::DeviceIntSize, r: f32, g: f32, b: f32, a: f32) + -> Self { + let visual = composition.create_angle_visual(size.width as u32, size.height as u32); + visual.make_current(); + + let (renderer, sender) = webrender::Renderer::new( + composition.gleam.clone(), + notifier.clone(), + webrender::RendererOptions { + clear_color: Some(api::ColorF::new(0., 0., 0., 0.)), + device_pixel_ratio, + ..webrender::RendererOptions::default() + }, + None, + size, + ).unwrap(); + let api = sender.create_api(); + + Rectangle { + visual, + renderer: Some(renderer), + document_id: api.add_document(size, 0), + api, + size, + color: api::ColorF { r, g, b, a }, + } + } + + fn render(&mut self, device_pixel_ratio: f32, rx: &mpsc::Receiver<()>) { + self.visual.make_current(); + + let pipeline_id = api::PipelineId(0, 0); + let layout_size = self.size.to_f32() / euclid::Scale::new(device_pixel_ratio); + let mut builder = api::DisplayListBuilder::new(pipeline_id, layout_size); + + let rect = euclid::Rect::new(euclid::Point2D::zero(), layout_size); + + let region = api::ComplexClipRegion::new( + rect, + api::BorderRadius::uniform(20.), + api::ClipMode::Clip + ); + let clip_id = builder.define_clip_rounded_rect( + &api::SpaceAndClipInfo::root_scroll(pipeline_id), + region, + ); + + builder.push_rect( + &api::CommonItemProperties::new( + rect, + api::SpaceAndClipInfo { + spatial_id: api::SpatialId::root_scroll_node(pipeline_id), + clip_id, + }, + ), + rect, + self.color, + ); + + let mut transaction = api::Transaction::new(); + transaction.set_display_list( + api::Epoch(0), + None, + layout_size, + builder.finalize(), + true, + ); + transaction.set_root_pipeline(pipeline_id); + transaction.generate_frame(); + self.api.send_transaction(self.document_id, transaction); + rx.recv().unwrap(); + let renderer = self.renderer.as_mut().unwrap(); + renderer.update(); + renderer.render(self.size).unwrap(); + let _ = renderer.flush_pipeline_info(); + self.visual.present(); + } +} + +impl Drop for Rectangle { + fn drop(&mut self) { + self.renderer.take().unwrap().deinit() + } +} + +#[derive(Clone)] +struct Notifier { + events_proxy: winit::EventsLoopProxy, + tx: mpsc::Sender<()>, +} + +impl api::RenderNotifier for Notifier { + fn clone(&self) -> Box { + Box::new(Clone::clone(self)) + } + + fn wake_up(&self) { + self.tx.send(()).unwrap(); + let _ = self.events_proxy.wakeup(); + } + + fn new_frame_ready(&self, + _: api::DocumentId, + _: bool, + _: bool, + _: Option) { + self.wake_up(); + } +} diff --git a/third_party/webrender/example-compositor/compositor-windows/Cargo.toml b/third_party/webrender/example-compositor/compositor-windows/Cargo.toml new file mode 100644 index 00000000000..b191626a698 --- /dev/null +++ b/third_party/webrender/example-compositor/compositor-windows/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "compositor-windows" +version = "0.1.0" +authors = ["Glenn Watson "] +edition = "2018" +license = "MPL-2.0" + +[build-dependencies] +cc = "1.0" diff --git a/third_party/webrender/example-compositor/compositor-windows/build.rs b/third_party/webrender/example-compositor/compositor-windows/build.rs new file mode 100644 index 00000000000..dc7358aa6a6 --- /dev/null +++ b/third_party/webrender/example-compositor/compositor-windows/build.rs @@ -0,0 +1,25 @@ +/* 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/. */ + +fn main() { + // HACK - This build script relies on Gecko having been built, so that the ANGLE libraries + // have already been compiled. It also assumes they are being built with an in-tree + // x86_64 object directory. + + cc::Build::new() + .file("src/lib.cpp") + .include("../../../angle/checkout/include") + .compile("windows"); + + // Set up linker paths for ANGLE that is built by Gecko + println!("cargo:rustc-link-search=../../obj-x86_64-pc-mingw32/gfx/angle/targets/libEGL"); + println!("cargo:rustc-link-search=../../obj-x86_64-pc-mingw32/gfx/angle/targets/libGLESv2"); + + // Link to libEGL and libGLESv2 (ANGLE) and D3D11 + DirectComposition + println!("cargo:rustc-link-lib=libEGL"); + println!("cargo:rustc-link-lib=libGLESv2"); + println!("cargo:rustc-link-lib=dcomp"); + println!("cargo:rustc-link-lib=d3d11"); + println!("cargo:rustc-link-lib=dwmapi"); +} diff --git a/third_party/webrender/example-compositor/compositor-windows/src/lib.cpp b/third_party/webrender/example-compositor/compositor-windows/src/lib.cpp new file mode 100644 index 00000000000..e17f602a2ec --- /dev/null +++ b/third_party/webrender/example-compositor/compositor-windows/src/lib.cpp @@ -0,0 +1,802 @@ +/* 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/. */ + +#define UNICODE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define EGL_EGL_PROTOTYPES 1 +#define EGL_EGLEXT_PROTOTYPES 1 +#define GL_GLEXT_PROTOTYPES 1 +#include "EGL/egl.h" +#include "EGL/eglext.h" +#include "EGL/eglext_angle.h" +#include "GL/gl.h" +#include "GLES/gl.h" +#include "GLES/glext.h" +#include "GLES3/gl3.h" + +#define NUM_QUERIES 2 + +#define USE_VIRTUAL_SURFACES +#define VIRTUAL_OFFSET 512 * 1024 + +enum SyncMode { + None = 0, + Swap = 1, + Commit = 2, + Flush = 3, + Query = 4, +}; + +// The OS compositor representation of a picture cache tile. +struct Tile { +#ifndef USE_VIRTUAL_SURFACES + // Represents the underlying DirectComposition surface texture that gets drawn into. + IDCompositionSurface *pSurface; + // Represents the node in the visual tree that defines the properties of this tile (clip, position etc). + IDCompositionVisual2 *pVisual; +#endif +}; + +struct TileKey { + int x; + int y; + + TileKey(int ax, int ay) : x(ax), y(ay) {} +}; + +bool operator==(const TileKey &k0, const TileKey &k1) { + return k0.x == k1.x && k0.y == k1.y; +} + +struct TileKeyHasher { + size_t operator()(const TileKey &key) const { + return key.x ^ key.y; + } +}; + +struct Surface { + int tile_width; + int tile_height; + bool is_opaque; + std::unordered_map tiles; + IDCompositionVisual2 *pVisual; +#ifdef USE_VIRTUAL_SURFACES + IDCompositionVirtualSurface *pVirtualSurface; +#endif +}; + +struct CachedFrameBuffer { + int width; + int height; + GLuint fboId; + GLuint depthRboId; +}; + +struct Window { + // Win32 window details + HWND hWnd; + HINSTANCE hInstance; + bool enable_compositor; + RECT client_rect; + SyncMode sync_mode; + + // Main interfaces to D3D11 and DirectComposition + ID3D11Device *pD3D11Device; + IDCompositionDesktopDevice *pDCompDevice; + IDCompositionTarget *pDCompTarget; + IDXGIDevice *pDXGIDevice; + ID3D11Query *pQueries[NUM_QUERIES]; + int current_query; + + // ANGLE interfaces that wrap the D3D device + EGLDeviceEXT EGLDevice; + EGLDisplay EGLDisplay; + EGLContext EGLContext; + EGLConfig config; + // Framebuffer surface for debug mode when we are not using DC + EGLSurface fb_surface; + + // The currently bound surface, valid during bind() and unbind() + IDCompositionSurface *pCurrentSurface; + EGLImage mEGLImage; + GLuint mColorRBO; + + // The root of the DC visual tree. Nothing is drawn on this, but + // all child tiles are parented to here. + IDCompositionVisual2 *pRoot; + IDCompositionVisualDebug *pVisualDebug; + std::vector mFrameBuffers; + + // Maintain list of layer state between frames to avoid visual tree rebuild. + std::vector mCurrentLayers; + std::vector mPrevLayers; + + // Maps WR surface IDs to each OS surface + std::unordered_map surfaces; +}; + +static const wchar_t *CLASS_NAME = L"WR DirectComposite"; + +static GLuint GetOrCreateFbo(Window *window, int aWidth, int aHeight) { + GLuint fboId = 0; + + // Check if we have a cached FBO with matching dimensions + for (auto it = window->mFrameBuffers.begin(); it != window->mFrameBuffers.end(); ++it) { + if (it->width == aWidth && it->height == aHeight) { + fboId = it->fboId; + break; + } + } + + // If not, create a new FBO with attached depth buffer + if (fboId == 0) { + // Create the depth buffer + GLuint depthRboId; + glGenRenderbuffers(1, &depthRboId); + glBindRenderbuffer(GL_RENDERBUFFER, depthRboId); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, + aWidth, aHeight); + + // Create the framebuffer and attach the depth buffer to it + glGenFramebuffers(1, &fboId); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboId); + glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, + GL_DEPTH_ATTACHMENT, + GL_RENDERBUFFER, depthRboId); + + // Store this in the cache for future calls. + CachedFrameBuffer frame_buffer_info; + frame_buffer_info.width = aWidth; + frame_buffer_info.height = aHeight; + frame_buffer_info.fboId = fboId; + frame_buffer_info.depthRboId = depthRboId; + window->mFrameBuffers.push_back(frame_buffer_info); + } + + return fboId; +} + +static LRESULT CALLBACK WndProc( + HWND hwnd, + UINT message, + WPARAM wParam, + LPARAM lParam +) { + switch (message) { + case WM_DESTROY: + PostQuitMessage(0); + return 1; + } + + return DefWindowProc(hwnd, message, wParam, lParam); +} + +extern "C" { + Window *com_dc_create_window(int width, int height, bool enable_compositor, SyncMode sync_mode) { + // Create a simple Win32 window + Window *window = new Window; + window->hInstance = GetModuleHandle(NULL); + window->enable_compositor = enable_compositor; + window->mEGLImage = EGL_NO_IMAGE; + window->sync_mode = sync_mode; + + WNDCLASSEX wcex = { sizeof(WNDCLASSEX) }; + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = window->hInstance; + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);; + wcex.lpszMenuName = nullptr; + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.lpszClassName = CLASS_NAME; + RegisterClassEx(&wcex); + + int dpiX = 0; + int dpiY = 0; + HDC hdc = GetDC(NULL); + if (hdc) { + dpiX = GetDeviceCaps(hdc, LOGPIXELSX); + dpiY = GetDeviceCaps(hdc, LOGPIXELSY); + ReleaseDC(NULL, hdc); + } + + RECT window_rect = { 0, 0, width, height }; + AdjustWindowRect(&window_rect, WS_OVERLAPPEDWINDOW, FALSE); + UINT window_width = static_cast(ceil(float(window_rect.right - window_rect.left) * dpiX / 96.f)); + UINT window_height = static_cast(ceil(float(window_rect.bottom - window_rect.top) * dpiY / 96.f)); + + LPCWSTR name; + DWORD style; + if (enable_compositor) { + name = L"example-compositor (DirectComposition)"; + style = WS_EX_NOREDIRECTIONBITMAP; + } else { + name = L"example-compositor (Simple)"; + style = 0; + } + + window->hWnd = CreateWindowEx( + style, + CLASS_NAME, + name, + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, + CW_USEDEFAULT, + window_width, + window_height, + NULL, + NULL, + window->hInstance, + NULL + ); + + ShowWindow(window->hWnd, SW_SHOWNORMAL); + UpdateWindow(window->hWnd); + GetClientRect(window->hWnd, &window->client_rect); + + // Create a D3D11 device + D3D_FEATURE_LEVEL featureLevelSupported; + HRESULT hr = D3D11CreateDevice( + nullptr, + D3D_DRIVER_TYPE_HARDWARE, + NULL, + D3D11_CREATE_DEVICE_BGRA_SUPPORT, + NULL, + 0, + D3D11_SDK_VERSION, + &window->pD3D11Device, + &featureLevelSupported, + nullptr + ); + assert(SUCCEEDED(hr)); + + D3D11_QUERY_DESC query_desc; + memset(&query_desc, 0, sizeof(query_desc)); + query_desc.Query = D3D11_QUERY_EVENT; + for (int i=0 ; i < NUM_QUERIES ; ++i) { + hr = window->pD3D11Device->CreateQuery(&query_desc, &window->pQueries[i]); + assert(SUCCEEDED(hr)); + } + window->current_query = 0; + + hr = window->pD3D11Device->QueryInterface(&window->pDXGIDevice); + assert(SUCCEEDED(hr)); + + // Create a DirectComposition device + hr = DCompositionCreateDevice2( + window->pDXGIDevice, + __uuidof(IDCompositionDesktopDevice), + (void **) &window->pDCompDevice + ); + assert(SUCCEEDED(hr)); + + // Create a DirectComposition target for a Win32 window handle + hr = window->pDCompDevice->CreateTargetForHwnd( + window->hWnd, + TRUE, + &window->pDCompTarget + ); + assert(SUCCEEDED(hr)); + + // Create an ANGLE EGL device that wraps D3D11 + window->EGLDevice = eglCreateDeviceANGLE( + EGL_D3D11_DEVICE_ANGLE, + window->pD3D11Device, + nullptr + ); + + EGLint display_attribs[] = { + EGL_NONE + }; + + window->EGLDisplay = eglGetPlatformDisplayEXT( + EGL_PLATFORM_DEVICE_EXT, + window->EGLDevice, + display_attribs + ); + + eglInitialize( + window->EGLDisplay, + nullptr, + nullptr + ); + + EGLint num_configs = 0; + EGLint cfg_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 24, + EGL_NONE + }; + EGLConfig configs[32]; + + eglChooseConfig( + window->EGLDisplay, + cfg_attribs, + configs, + sizeof(configs) / sizeof(EGLConfig), + &num_configs + ); + assert(num_configs > 0); + window->config = configs[0]; + + if (window->enable_compositor) { + window->fb_surface = EGL_NO_SURFACE; + } else { + window->fb_surface = eglCreateWindowSurface( + window->EGLDisplay, + window->config, + window->hWnd, + NULL + ); + assert(window->fb_surface != EGL_NO_SURFACE); + } + + EGLint ctx_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 3, + EGL_NONE + }; + + // Create an EGL context that can be used for drawing + window->EGLContext = eglCreateContext( + window->EGLDisplay, + window->config, + EGL_NO_CONTEXT, + ctx_attribs + ); + + // Create the root of the DirectComposition visual tree + hr = window->pDCompDevice->CreateVisual(&window->pRoot); + assert(SUCCEEDED(hr)); + hr = window->pDCompTarget->SetRoot(window->pRoot); + assert(SUCCEEDED(hr)); + + hr = window->pRoot->QueryInterface( + __uuidof(IDCompositionVisualDebug), + (void **) &window->pVisualDebug + ); + assert(SUCCEEDED(hr)); + + // Uncomment this to see redraw regions during composite + // window->pVisualDebug->EnableRedrawRegions(); + + EGLBoolean ok = eglMakeCurrent( + window->EGLDisplay, + window->fb_surface, + window->fb_surface, + window->EGLContext + ); + assert(ok); + + return window; + } + + void com_dc_destroy_window(Window *window) { + for (auto surface_it=window->surfaces.begin() ; surface_it != window->surfaces.end() ; ++surface_it) { + Surface &surface = surface_it->second; + +#ifndef USE_VIRTUAL_SURFACES + for (auto tile_it=surface.tiles.begin() ; tile_it != surface.tiles.end() ; ++tile_it) { + tile_it->second.pSurface->Release(); + tile_it->second.pVisual->Release(); + } +#endif + + surface.pVisual->Release(); + } + + if (window->fb_surface != EGL_NO_SURFACE) { + eglDestroySurface(window->EGLDisplay, window->fb_surface); + } + eglDestroyContext(window->EGLDisplay, window->EGLContext); + eglTerminate(window->EGLDisplay); + eglReleaseDeviceANGLE(window->EGLDevice); + + for (int i=0 ; i < NUM_QUERIES ; ++i) { + window->pQueries[i]->Release(); + } + window->pRoot->Release(); + window->pVisualDebug->Release(); + window->pD3D11Device->Release(); + window->pDXGIDevice->Release(); + window->pDCompDevice->Release(); + window->pDCompTarget->Release(); + + CloseWindow(window->hWnd); + UnregisterClass(CLASS_NAME, window->hInstance); + + delete window; + } + + bool com_dc_tick(Window *) { + // Check and dispatch the windows event loop + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) { + return false; + } + + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return true; + } + + void com_dc_swap_buffers(Window *window) { + // If not using DC mode, then do a normal EGL swap buffers. + if (window->fb_surface != EGL_NO_SURFACE) { + switch (window->sync_mode) { + case SyncMode::None: + eglSwapInterval(window->EGLDisplay, 0); + break; + case SyncMode::Swap: + eglSwapInterval(window->EGLDisplay, 1); + break; + default: + assert(false); // unexpected vsync mode for simple compositor. + break; + } + + eglSwapBuffers(window->EGLDisplay, window->fb_surface); + } else { + switch (window->sync_mode) { + case SyncMode::None: + break; + case SyncMode::Commit: + window->pDCompDevice->WaitForCommitCompletion(); + break; + case SyncMode::Flush: + DwmFlush(); + break; + case SyncMode::Query: + // todo!!!! + break; + default: + assert(false); // unexpected vsync mode for native compositor + break; + } + } + } + + // Create a new DC surface + void com_dc_create_surface( + Window *window, + uint64_t id, + int tile_width, + int tile_height, + bool is_opaque + ) { + assert(window->surfaces.count(id) == 0); + + Surface surface; + surface.tile_width = tile_width; + surface.tile_height = tile_height; + surface.is_opaque = is_opaque; + + // Create the visual node in the DC tree that stores properties + HRESULT hr = window->pDCompDevice->CreateVisual(&surface.pVisual); + assert(SUCCEEDED(hr)); + +#ifdef USE_VIRTUAL_SURFACES + DXGI_ALPHA_MODE alpha_mode = surface.is_opaque ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED; + + hr = window->pDCompDevice->CreateVirtualSurface( + VIRTUAL_OFFSET * 2, + VIRTUAL_OFFSET * 2, + DXGI_FORMAT_B8G8R8A8_UNORM, + alpha_mode, + &surface.pVirtualSurface + ); + assert(SUCCEEDED(hr)); + + // Bind the surface memory to this visual + hr = surface.pVisual->SetContent(surface.pVirtualSurface); + assert(SUCCEEDED(hr)); +#endif + + window->surfaces[id] = surface; + } + + void com_dc_create_tile( + Window *window, + uint64_t id, + int x, + int y + ) { + assert(window->surfaces.count(id) == 1); + Surface &surface = window->surfaces[id]; + + TileKey key(x, y); + assert(surface.tiles.count(key) == 0); + + Tile tile; + +#ifndef USE_VIRTUAL_SURFACES + // Create the video memory surface. + DXGI_ALPHA_MODE alpha_mode = surface.is_opaque ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED; + HRESULT hr = window->pDCompDevice->CreateSurface( + surface.tile_width, + surface.tile_height, + DXGI_FORMAT_B8G8R8A8_UNORM, + alpha_mode, + &tile.pSurface + ); + assert(SUCCEEDED(hr)); + + // Create the visual node in the DC tree that stores properties + hr = window->pDCompDevice->CreateVisual(&tile.pVisual); + assert(SUCCEEDED(hr)); + + // Bind the surface memory to this visual + hr = tile.pVisual->SetContent(tile.pSurface); + assert(SUCCEEDED(hr)); + + // Place the visual in local-space of this surface + float offset_x = (float) (x * surface.tile_width); + float offset_y = (float) (y * surface.tile_height); + tile.pVisual->SetOffsetX(offset_x); + tile.pVisual->SetOffsetY(offset_y); + + surface.pVisual->AddVisual( + tile.pVisual, + FALSE, + NULL + ); +#endif + + surface.tiles[key] = tile; + } + + void com_dc_destroy_tile( + Window *window, + uint64_t id, + int x, + int y + ) { + assert(window->surfaces.count(id) == 1); + Surface &surface = window->surfaces[id]; + + TileKey key(x, y); + assert(surface.tiles.count(key) == 1); + Tile &tile = surface.tiles[key]; + +#ifndef USE_VIRTUAL_SURFACES + surface.pVisual->RemoveVisual(tile.pVisual); + + tile.pVisual->Release(); + tile.pSurface->Release(); +#endif + + surface.tiles.erase(key); + } + + void com_dc_destroy_surface( + Window *window, + uint64_t id + ) { + assert(window->surfaces.count(id) == 1); + Surface &surface = window->surfaces[id]; + + window->pRoot->RemoveVisual(surface.pVisual); + +#ifdef USE_VIRTUAL_SURFACES + surface.pVirtualSurface->Release(); +#else + // Release the video memory and visual in the tree + for (auto tile_it=surface.tiles.begin() ; tile_it != surface.tiles.end() ; ++tile_it) { + tile_it->second.pSurface->Release(); + tile_it->second.pVisual->Release(); + } +#endif + + surface.pVisual->Release(); + window->surfaces.erase(id); + } + + // Bind a DC surface to allow issuing GL commands to it + GLuint com_dc_bind_surface( + Window *window, + uint64_t surface_id, + int tile_x, + int tile_y, + int *x_offset, + int *y_offset, + int dirty_x0, + int dirty_y0, + int dirty_width, + int dirty_height + ) { + assert(window->surfaces.count(surface_id) == 1); + Surface &surface = window->surfaces[surface_id]; + + TileKey key(tile_x, tile_y); + assert(surface.tiles.count(key) == 1); + Tile &tile = surface.tiles[key]; + + // Inform DC that we want to draw on this surface. DC uses texture + // atlases when the tiles are small. It returns an offset where the + // client code must draw into this surface when this happens. + RECT update_rect; + update_rect.left = dirty_x0; + update_rect.top = dirty_y0; + update_rect.right = dirty_x0 + dirty_width; + update_rect.bottom = dirty_y0 + dirty_height; + POINT offset; + D3D11_TEXTURE2D_DESC desc; + ID3D11Texture2D *pTexture; + HRESULT hr; + + // Store the current surface for unbinding later +#ifdef USE_VIRTUAL_SURFACES + LONG tile_offset_x = VIRTUAL_OFFSET + tile_x * surface.tile_width; + LONG tile_offset_y = VIRTUAL_OFFSET + tile_y * surface.tile_height; + + update_rect.left += tile_offset_x; + update_rect.top += tile_offset_y; + update_rect.right += tile_offset_x; + update_rect.bottom += tile_offset_y; + + hr = surface.pVirtualSurface->BeginDraw( + &update_rect, + __uuidof(ID3D11Texture2D), + (void **) &pTexture, + &offset + ); + window->pCurrentSurface = surface.pVirtualSurface; +#else + hr = tile.pSurface->BeginDraw( + &update_rect, + __uuidof(ID3D11Texture2D), + (void **) &pTexture, + &offset + ); + window->pCurrentSurface = tile.pSurface; +#endif + + // DC includes the origin of the dirty / update rect in the draw offset, + // undo that here since WR expects it to be an absolute offset. + assert(SUCCEEDED(hr)); + offset.x -= dirty_x0; + offset.y -= dirty_y0; + pTexture->GetDesc(&desc); + *x_offset = offset.x; + *y_offset = offset.y; + + // Construct an EGLImage wrapper around the D3D texture for ANGLE. + const EGLAttrib attribs[] = { EGL_NONE }; + window->mEGLImage = eglCreateImage( + window->EGLDisplay, + EGL_NO_CONTEXT, + EGL_D3D11_TEXTURE_ANGLE, + static_cast(pTexture), + attribs + ); + + // Get the current FBO and RBO id, so we can restore them later + GLint currentFboId, currentRboId; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFboId); + glGetIntegerv(GL_RENDERBUFFER_BINDING, ¤tRboId); + + // Create a render buffer object that is backed by the EGL image. + glGenRenderbuffers(1, &window->mColorRBO); + glBindRenderbuffer(GL_RENDERBUFFER, window->mColorRBO); + glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, window->mEGLImage); + + // Get or create an FBO for the specified dimensions + GLuint fboId = GetOrCreateFbo(window, desc.Width, desc.Height); + + // Attach the new renderbuffer to the FBO + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboId); + glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, + window->mColorRBO); + + // Restore previous FBO and RBO bindings + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFboId); + glBindRenderbuffer(GL_RENDERBUFFER, currentRboId); + + return fboId; + } + + // Unbind a currently bound DC surface + void com_dc_unbind_surface(Window *window) { + HRESULT hr = window->pCurrentSurface->EndDraw(); + assert(SUCCEEDED(hr)); + + glDeleteRenderbuffers(1, &window->mColorRBO); + window->mColorRBO = 0; + + eglDestroyImage(window->EGLDisplay, window->mEGLImage); + window->mEGLImage = EGL_NO_IMAGE; + } + + void com_dc_begin_transaction(Window *) { + } + + // Add a DC surface to the visual tree. Called per-frame to build the composition. + void com_dc_add_surface( + Window *window, + uint64_t id, + int x, + int y, + int clip_x, + int clip_y, + int clip_w, + int clip_h + ) { + Surface surface = window->surfaces[id]; + window->mCurrentLayers.push_back(id); + + // Place the visual - this changes frame to frame based on scroll position + // of the slice. + float offset_x = (float) (x + window->client_rect.left); + float offset_y = (float) (y + window->client_rect.top); +#ifdef USE_VIRTUAL_SURFACES + offset_x -= VIRTUAL_OFFSET; + offset_y -= VIRTUAL_OFFSET; +#endif + surface.pVisual->SetOffsetX(offset_x); + surface.pVisual->SetOffsetY(offset_y); + + // Set the clip rect - converting from world space to the pre-offset space + // that DC requires for rectangle clips. + D2D_RECT_F clip_rect; + clip_rect.left = clip_x - offset_x; + clip_rect.top = clip_y - offset_y; + clip_rect.right = clip_rect.left + clip_w; + clip_rect.bottom = clip_rect.top + clip_h; + surface.pVisual->SetClip(clip_rect); + } + + // Finish the composition transaction, telling DC to composite + void com_dc_end_transaction(Window *window) { + bool same = window->mPrevLayers == window->mCurrentLayers; + + if (!same) { + HRESULT hr = window->pRoot->RemoveAllVisuals(); + assert(SUCCEEDED(hr)); + + for (auto it = window->mCurrentLayers.begin(); it != window->mCurrentLayers.end(); ++it) { + Surface &surface = window->surfaces[*it]; + + // Add this visual as the last element in the visual tree (z-order is implicit, + // based on the order tiles are added). + hr = window->pRoot->AddVisual( + surface.pVisual, + FALSE, + NULL + ); + assert(SUCCEEDED(hr)); + } + } + + window->mPrevLayers.swap(window->mCurrentLayers); + window->mCurrentLayers.clear(); + + HRESULT hr = window->pDCompDevice->Commit(); + assert(SUCCEEDED(hr)); + } + + // Get a pointer to an EGL symbol + void *com_dc_get_proc_address(const char *name) { + return eglGetProcAddress(name); + } +} diff --git a/third_party/webrender/example-compositor/compositor-windows/src/lib.rs b/third_party/webrender/example-compositor/compositor-windows/src/lib.rs new file mode 100644 index 00000000000..18b80b5d1e9 --- /dev/null +++ b/third_party/webrender/example-compositor/compositor-windows/src/lib.rs @@ -0,0 +1,261 @@ +/* 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 std::os::raw::{c_void, c_char}; + +/* + + This is a very simple (and unsafe!) rust wrapper for the DirectComposite / D3D11 / ANGLE + implementation in lib.cpp. + + It just proxies the calls from the Compositor impl to the C99 code. This is very + hacky and not suitable for production! + + */ + +// Opaque wrapper for the Window type in lib.cpp +#[repr(C)] +pub struct Window { + _unused: [u8; 0] +} + +// C99 functions that do the compositor work +extern { + fn com_dc_create_window( + width: i32, + height: i32, + enable_compositor: bool, + sync_mode: i32, + ) -> *mut Window; + fn com_dc_destroy_window(window: *mut Window); + fn com_dc_tick(window: *mut Window) -> bool; + fn com_dc_get_proc_address(name: *const c_char) -> *const c_void; + fn com_dc_swap_buffers(window: *mut Window); + + fn com_dc_create_surface( + window: *mut Window, + id: u64, + tile_width: i32, + tile_height: i32, + is_opaque: bool, + ); + + fn com_dc_create_tile( + window: *mut Window, + id: u64, + x: i32, + y: i32, + ); + + fn com_dc_destroy_tile( + window: *mut Window, + id: u64, + x: i32, + y: i32, + ); + + fn com_dc_destroy_surface( + window: *mut Window, + id: u64, + ); + + fn com_dc_bind_surface( + window: *mut Window, + surface_id: u64, + tile_x: i32, + tile_y: i32, + x_offset: &mut i32, + y_offset: &mut i32, + dirty_x0: i32, + dirty_y0: i32, + dirty_width: i32, + dirty_height: i32, + ) -> u32; + fn com_dc_unbind_surface(window: *mut Window); + + fn com_dc_begin_transaction(window: *mut Window); + + fn com_dc_add_surface( + window: *mut Window, + id: u64, + x: i32, + y: i32, + clip_x: i32, + clip_y: i32, + clip_w: i32, + clip_h: i32, + ); + + fn com_dc_end_transaction(window: *mut Window); +} + +pub fn create_window( + width: i32, + height: i32, + enable_compositor: bool, + sync_mode: i32, +) -> *mut Window { + unsafe { + com_dc_create_window(width, height, enable_compositor, sync_mode) + } +} + +pub fn destroy_window(window: *mut Window) { + unsafe { + com_dc_destroy_window(window); + } +} + +pub fn tick(window: *mut Window) -> bool { + unsafe { + com_dc_tick(window) + } +} + +pub fn get_proc_address(name: *const c_char) -> *const c_void { + unsafe { + com_dc_get_proc_address(name) + } +} + +pub fn create_surface( + window: *mut Window, + id: u64, + tile_width: i32, + tile_height: i32, + is_opaque: bool, +) { + unsafe { + com_dc_create_surface( + window, + id, + tile_width, + tile_height, + is_opaque, + ) + } +} + +pub fn create_tile( + window: *mut Window, + id: u64, + x: i32, + y: i32, +) { + unsafe { + com_dc_create_tile( + window, + id, + x, + y, + ) + } +} + +pub fn destroy_tile( + window: *mut Window, + id: u64, + x: i32, + y: i32, +) { + unsafe { + com_dc_destroy_tile( + window, + id, + x, + y, + ) + } +} + +pub fn destroy_surface( + window: *mut Window, + id: u64, +) { + unsafe { + com_dc_destroy_surface( + window, + id, + ) + } +} + +pub fn bind_surface( + window: *mut Window, + surface_id: u64, + tile_x: i32, + tile_y: i32, + dirty_x0: i32, + dirty_y0: i32, + dirty_width: i32, + dirty_height: i32, +) -> (u32, i32, i32) { + unsafe { + let mut x_offset = 0; + let mut y_offset = 0; + + let fbo_id = com_dc_bind_surface( + window, + surface_id, + tile_x, + tile_y, + &mut x_offset, + &mut y_offset, + dirty_x0, + dirty_y0, + dirty_width, + dirty_height, + ); + + (fbo_id, x_offset, y_offset) + } +} + +pub fn add_surface( + window: *mut Window, + id: u64, + x: i32, + y: i32, + clip_x: i32, + clip_y: i32, + clip_w: i32, + clip_h: i32, +) { + unsafe { + com_dc_add_surface( + window, + id, + x, + y, + clip_x, + clip_y, + clip_w, + clip_h, + ) + } +} + +pub fn begin_transaction(window: *mut Window) { + unsafe { + com_dc_begin_transaction(window) + } +} + +pub fn unbind_surface(window: *mut Window) { + unsafe { + com_dc_unbind_surface(window) + } +} + +pub fn end_transaction(window: *mut Window) { + unsafe { + com_dc_end_transaction(window) + } +} + +pub fn swap_buffers(window: *mut Window) { + unsafe { + com_dc_swap_buffers(window); + } +} diff --git a/third_party/webrender/example-compositor/compositor/Cargo.toml b/third_party/webrender/example-compositor/compositor/Cargo.toml new file mode 100644 index 00000000000..d505e9ad295 --- /dev/null +++ b/third_party/webrender/example-compositor/compositor/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "compositor" +version = "0.1.0" +authors = ["Glenn Watson "] +edition = "2018" +license = "MPL-2.0" + +[dependencies] +webrender = { path = "../../webrender" } +gleam = "0.12.0" + +[target.'cfg(windows)'.dependencies] +compositor-windows = { path = "../compositor-windows" } diff --git a/third_party/webrender/example-compositor/compositor/src/main.rs b/third_party/webrender/example-compositor/compositor/src/main.rs new file mode 100644 index 00000000000..b2a7aac3a94 --- /dev/null +++ b/third_party/webrender/example-compositor/compositor/src/main.rs @@ -0,0 +1,484 @@ +/* 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/. */ + +/* + + An example of how to implement the Compositor trait that + allows picture caching surfaces to be composited by the operating + system. + + The current example supports DirectComposite on Windows only. + + */ + +use euclid::Angle; +use gleam::gl; +use std::ffi::CString; +use std::sync::mpsc; +use webrender::api::*; +use webrender::api::units::*; +#[cfg(target_os = "windows")] +use compositor_windows as compositor; +use std::{env, f32, process}; + +// A very hacky integration with DirectComposite. It proxies calls from the compositor +// interface to a simple C99 library which does the DirectComposition / D3D11 / ANGLE +// interfacing. This is a very unsafe impl due to the way the window pointer is passed +// around! +struct DirectCompositeInterface { + window: *mut compositor::Window, +} + +impl DirectCompositeInterface { + fn new(window: *mut compositor::Window) -> Self { + DirectCompositeInterface { + window, + } + } +} + +impl webrender::Compositor for DirectCompositeInterface { + fn create_surface( + &mut self, + id: webrender::NativeSurfaceId, + tile_size: DeviceIntSize, + is_opaque: bool, + ) { + compositor::create_surface( + self.window, + id.0, + tile_size.width, + tile_size.height, + is_opaque, + ); + } + + fn destroy_surface( + &mut self, + id: webrender::NativeSurfaceId, + ) { + compositor::destroy_surface(self.window, id.0); + } + + fn create_tile( + &mut self, + id: webrender::NativeTileId, + ) { + compositor::create_tile( + self.window, + id.surface_id.0, + id.x, + id.y, + ); + } + + fn destroy_tile( + &mut self, + id: webrender::NativeTileId, + ) { + compositor::destroy_tile( + self.window, + id.surface_id.0, + id.x, + id.y, + ); + } + + fn bind( + &mut self, + id: webrender::NativeTileId, + dirty_rect: DeviceIntRect, + ) -> webrender::NativeSurfaceInfo { + let (fbo_id, x, y) = compositor::bind_surface( + self.window, + id.surface_id.0, + id.x, + id.y, + dirty_rect.origin.x, + dirty_rect.origin.y, + dirty_rect.size.width, + dirty_rect.size.height, + ); + + webrender::NativeSurfaceInfo { + origin: DeviceIntPoint::new(x, y), + fbo_id, + } + } + + fn unbind(&mut self) { + compositor::unbind_surface(self.window); + } + + fn begin_frame(&mut self) { + compositor::begin_transaction(self.window); + } + + fn add_surface( + &mut self, + id: webrender::NativeSurfaceId, + position: DeviceIntPoint, + clip_rect: DeviceIntRect, + ) { + compositor::add_surface( + self.window, + id.0, + position.x, + position.y, + clip_rect.origin.x, + clip_rect.origin.y, + clip_rect.size.width, + clip_rect.size.height, + ); + } + + fn end_frame(&mut self) { + compositor::end_transaction(self.window); + } +} + +// Simplisitic implementation of the WR notifier interface to know when a frame +// has been prepared and can be rendered. +struct Notifier { + tx: mpsc::Sender<()>, +} + +impl Notifier { + fn new(tx: mpsc::Sender<()>) -> Self { + Notifier { + tx, + } + } +} + +impl RenderNotifier for Notifier { + fn clone(&self) -> Box { + Box::new(Notifier { + tx: self.tx.clone() + }) + } + + fn wake_up(&self) { + } + + fn new_frame_ready(&self, + _: DocumentId, + _scrolled: bool, + _composite_needed: bool, + _render_time: Option) { + self.tx.send(()).ok(); + } +} + +fn push_rotated_rect( + builder: &mut DisplayListBuilder, + rect: LayoutRect, + color: ColorF, + spatial_id: SpatialId, + root_pipeline_id: PipelineId, + angle: f32, + time: f32, +) { + let color = color.scale_rgb(time); + let rotation = LayoutTransform::create_rotation( + 0.0, + 0.0, + 1.0, + Angle::radians(2.0 * std::f32::consts::PI * angle), + ); + let transform_origin = LayoutVector3D::new( + rect.origin.x + rect.size.width * 0.5, + rect.origin.y + rect.size.height * 0.5, + 0.0, + ); + let transform = rotation + .pre_translate(-transform_origin) + .post_translate(transform_origin); + let spatial_id = builder.push_reference_frame( + LayoutPoint::zero(), + spatial_id, + TransformStyle::Flat, + PropertyBinding::Value(transform), + ReferenceFrameKind::Transform, + ); + builder.push_rect( + &CommonItemProperties::new( + rect, + SpaceAndClipInfo { + spatial_id, + clip_id: ClipId::root(root_pipeline_id), + }, + ), + rect, + color, + ); +} + +fn build_display_list( + builder: &mut DisplayListBuilder, + scroll_id: ExternalScrollId, + root_pipeline_id: PipelineId, + layout_size: LayoutSize, + time: f32, + invalidations: Invalidations, +) { + let size_factor = match invalidations { + Invalidations::Small => 0.1, + Invalidations::Large | Invalidations::Scrolling => 1.0, + }; + + let fixed_space_info = SpaceAndClipInfo { + spatial_id: SpatialId::root_scroll_node(root_pipeline_id), + clip_id: ClipId::root(root_pipeline_id), + }; + + let scroll_space_info = builder.define_scroll_frame( + &fixed_space_info, + Some(scroll_id), + LayoutRect::new(LayoutPoint::zero(), layout_size), + LayoutRect::new(LayoutPoint::zero(), layout_size), + ScrollSensitivity::Script, + LayoutVector2D::zero(), + ); + + builder.push_rect( + &CommonItemProperties::new( + LayoutRect::new(LayoutPoint::zero(), layout_size).inflate(-10.0, -10.0), + fixed_space_info, + ), + LayoutRect::new(LayoutPoint::zero(), layout_size).inflate(-10.0, -10.0), + ColorF::new(0.8, 0.8, 0.8, 1.0), + ); + + push_rotated_rect( + builder, + LayoutRect::new( + LayoutPoint::new(100.0, 100.0), + LayoutSize::new(size_factor * 400.0, size_factor * 400.0), + ), + ColorF::new(1.0, 0.0, 0.0, 1.0), + scroll_space_info.spatial_id, + root_pipeline_id, + time, + time, + ); + + push_rotated_rect( + builder, + LayoutRect::new( + LayoutPoint::new(800.0, 100.0), + LayoutSize::new(size_factor * 100.0, size_factor * 600.0), + ), + ColorF::new(0.0, 1.0, 0.0, 1.0), + fixed_space_info.spatial_id, + root_pipeline_id, + 0.2, + time, + ); + + push_rotated_rect( + builder, + LayoutRect::new( + LayoutPoint::new(700.0, 200.0), + LayoutSize::new(size_factor * 300.0, size_factor * 300.0), + ), + ColorF::new(0.0, 0.0, 1.0, 1.0), + scroll_space_info.spatial_id, + root_pipeline_id, + 0.1, + time, + ); +} + +#[derive(Debug, Copy, Clone)] +enum Invalidations { + Large, + Small, + Scrolling, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +enum Sync { + None = 0, + Swap = 1, + Commit = 2, + Flush = 3, + Query = 4, +} + +fn main() { + let args: Vec = env::args().collect(); + + if args.len() != 6 { + println!("USAGE: compositor [native|none] [small|large|scroll] [none|swap|commit|flush|query] width height"); + process::exit(0); + } + + let enable_compositor = match args[1].parse::().unwrap().as_str() { + "native" => true, + "none" => false, + _ => panic!("invalid compositor [native, none]"), + }; + + let inv_mode = match args[2].parse::().unwrap().as_str() { + "small" => Invalidations::Small, + "large" => Invalidations::Large, + "scroll" => Invalidations::Scrolling, + _ => panic!("invalid invalidations [small, large, scroll]"), + }; + + let sync_mode = match args[3].parse::().unwrap().as_str() { + "none" => Sync::None, + "swap" => Sync::Swap, + "commit" => Sync::Commit, + "flush" => Sync::Flush, + "query" => Sync::Query, + _ => panic!("invalid sync mode [none, swap, commit, flush, query]"), + }; + + let width = args[4].parse().unwrap(); + let height = args[5].parse().unwrap(); + let device_size = DeviceIntSize::new(width, height); + + // Load GL, construct WR and the native compositor interface. + let window = compositor::create_window( + device_size.width, + device_size.height, + enable_compositor, + sync_mode as i32, + ); + let debug_flags = DebugFlags::empty(); + let compositor_config = if enable_compositor { + webrender::CompositorConfig::Native { + max_update_rects: 1, + compositor: Box::new(DirectCompositeInterface::new(window)), + } + } else { + webrender::CompositorConfig::Draw { + max_partial_present_rects: 0, + } + }; + let opts = webrender::RendererOptions { + clear_color: Some(ColorF::new(1.0, 1.0, 1.0, 1.0)), + debug_flags, + enable_picture_caching: true, + compositor_config, + ..webrender::RendererOptions::default() + }; + let (tx, rx) = mpsc::channel(); + let notifier = Box::new(Notifier::new(tx)); + let gl = unsafe { + gl::GlesFns::load_with( + |symbol| { + let symbol = CString::new(symbol).unwrap(); + let ptr = compositor::get_proc_address(symbol.as_ptr()); + ptr + } + ) + }; + let (mut renderer, sender) = webrender::Renderer::new( + gl.clone(), + notifier, + opts, + None, + device_size, + ).unwrap(); + let api = sender.create_api(); + let document_id = api.add_document(device_size, 0); + let device_pixel_ratio = 1.0; + let mut current_epoch = Epoch(0); + let root_pipeline_id = PipelineId(0, 0); + let layout_size = device_size.to_f32() / euclid::Scale::new(device_pixel_ratio); + let mut time = 0.0; + let scroll_id = ExternalScrollId(3, root_pipeline_id); + + // Kick off first transaction which will mean we get a notify below to build the DL and render. + let mut txn = Transaction::new(); + txn.set_root_pipeline(root_pipeline_id); + + if let Invalidations::Scrolling = inv_mode { + let mut root_builder = DisplayListBuilder::new(root_pipeline_id, layout_size); + + build_display_list( + &mut root_builder, + scroll_id, + root_pipeline_id, + layout_size, + 1.0, + inv_mode, + ); + + txn.set_display_list( + current_epoch, + None, + layout_size, + root_builder.finalize(), + true, + ); + } + + txn.generate_frame(); + api.send_transaction(document_id, txn); + + // Tick the compositor (in this sample, we don't block on UI events) + while compositor::tick(window) { + // If there is a new frame ready to draw + if let Ok(..) = rx.try_recv() { + // Update and render. This will invoke the native compositor interface implemented above + // as required. + renderer.update(); + renderer.render(device_size).unwrap(); + let _ = renderer.flush_pipeline_info(); + + // Construct a simple display list that can be drawn and composited by DC. + let mut txn = Transaction::new(); + + match inv_mode { + Invalidations::Small | Invalidations::Large => { + let mut root_builder = DisplayListBuilder::new(root_pipeline_id, layout_size); + + build_display_list( + &mut root_builder, + scroll_id, + root_pipeline_id, + layout_size, + time, + inv_mode, + ); + + txn.set_display_list( + current_epoch, + None, + layout_size, + root_builder.finalize(), + true, + ); + } + Invalidations::Scrolling => { + let d = 0.5 - 0.5 * (2.0 * f32::consts::PI * 5.0 * time).cos(); + txn.scroll_node_with_id( + LayoutPoint::new(0.0, (d * 100.0).round()), + scroll_id, + ScrollClamping::NoClamping, + ); + } + } + + txn.generate_frame(); + api.send_transaction(document_id, txn); + current_epoch.0 += 1; + time += 0.001; + if time > 1.0 { + time = 0.0; + } + + // This does nothing when native compositor is enabled + compositor::swap_buffers(window); + } + } + + renderer.deinit(); + compositor::destroy_window(window); +} diff --git a/third_party/webrender/examples/Cargo.toml b/third_party/webrender/examples/Cargo.toml new file mode 100644 index 00000000000..4cf5d2232d6 --- /dev/null +++ b/third_party/webrender/examples/Cargo.toml @@ -0,0 +1,71 @@ +[package] +name = "webrender-examples" +version = "0.1.0" +authors = ["Glenn Watson "] +license = "MPL-2.0" +repository = "https://github.com/servo/webrender" +edition = "2018" + +[[bin]] +name = "alpha_perf" +path = "alpha_perf.rs" + +[[bin]] +name = "animation" +path = "animation.rs" + +[[bin]] +name = "basic" +path = "basic.rs" + +[[bin]] +name = "blob" +path = "blob.rs" + +[[bin]] +name = "document" +path = "document.rs" + +[[bin]] +name = "frame_output" +path = "frame_output.rs" + +[[bin]] +name = "iframe" +path = "iframe.rs" + +[[bin]] +name = "image_resize" +path = "image_resize.rs" + +[[bin]] +name = "multiwindow" +path = "multiwindow.rs" + +[[bin]] +name = "scrolling" +path = "scrolling.rs" + +[[bin]] +name = "texture_cache_stress" +path = "texture_cache_stress.rs" + +[[bin]] +name = "yuv" +path = "yuv.rs" + +[features] +debug = ["webrender/capture", "webrender/debugger", "webrender/profiler"] + +[dependencies] +app_units = "0.7" +env_logger = "0.5" +euclid = "0.22" +gleam = "0.12" +glutin = "0.21" +rayon = "1" +webrender = { path = "../webrender" } +winit = "0.19" + +[target.'cfg(target_os = "macos")'.dependencies] +core-foundation = "0.7" diff --git a/third_party/webrender/examples/README.md b/third_party/webrender/examples/README.md new file mode 100644 index 00000000000..68efd33a892 --- /dev/null +++ b/third_party/webrender/examples/README.md @@ -0,0 +1,8 @@ +# Examples + +This directory contains a collection of examples which uses the WebRender API. + +To run an example e.g. `basic`, try: +``` +cargo run --bin basic +``` diff --git a/third_party/webrender/examples/alpha_perf.rs b/third_party/webrender/examples/alpha_perf.rs new file mode 100644 index 00000000000..56dc5b1abeb --- /dev/null +++ b/third_party/webrender/examples/alpha_perf.rs @@ -0,0 +1,93 @@ +/* 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/. */ + +extern crate euclid; +extern crate gleam; +extern crate glutin; +extern crate webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; + +use crate::boilerplate::{Example, HandyDandyRectBuilder}; +use std::cmp; +use webrender::api::*; +use webrender::api::units::DeviceIntSize; + + +struct App { + rect_count: usize, +} + +impl Example for App { + fn render( + &mut self, + _api: &mut RenderApi, + builder: &mut DisplayListBuilder, + _txn: &mut Transaction, + _device_size: DeviceIntSize, + pipeline_id: PipelineId, + _document_id: DocumentId, + ) { + let bounds = (0, 0).to(1920, 1080); + let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id); + + builder.push_simple_stacking_context( + bounds.origin, + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + for _ in 0 .. self.rect_count { + builder.push_rect( + &CommonItemProperties::new(bounds, space_and_clip), + bounds, + ColorF::new(1.0, 1.0, 1.0, 0.05) + ); + } + + builder.pop_stacking_context(); + } + + fn on_event( + &mut self, + event: winit::WindowEvent, + _api: &mut RenderApi, + _document_id: DocumentId + ) -> bool { + match event { + winit::WindowEvent::KeyboardInput { + input: winit::KeyboardInput { + state: winit::ElementState::Pressed, + virtual_keycode: Some(key), + .. + }, + .. + } => { + match key { + winit::VirtualKeyCode::Right => { + self.rect_count += 1; + println!("rects = {}", self.rect_count); + } + winit::VirtualKeyCode::Left => { + self.rect_count = cmp::max(self.rect_count, 1) - 1; + println!("rects = {}", self.rect_count); + } + _ => {} + }; + } + _ => (), + } + + true + } +} + +fn main() { + let mut app = App { + rect_count: 1, + }; + boilerplate::main_wrapper(&mut app, None); +} diff --git a/third_party/webrender/examples/animation.rs b/third_party/webrender/examples/animation.rs new file mode 100644 index 00000000000..612d891178d --- /dev/null +++ b/third_party/webrender/examples/animation.rs @@ -0,0 +1,219 @@ +/* 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/. */ + +//! This example creates a 200x200 white rect and allows the user to move it +//! around by using the arrow keys and rotate with '<'/'>'. +//! It does this by using the animation API. + +//! The example also features seamless opaque/transparent split of a +//! rounded cornered rectangle, which is done automatically during the +//! scene building for render optimization. + +extern crate euclid; +extern crate gleam; +extern crate glutin; +extern crate webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; + +use crate::boilerplate::{Example, HandyDandyRectBuilder}; +use euclid::Angle; +use webrender::api::*; +use webrender::api::units::*; + + +struct App { + property_key0: PropertyBindingKey, + property_key1: PropertyBindingKey, + property_key2: PropertyBindingKey, + opacity_key: PropertyBindingKey, + opacity: f32, + angle0: f32, + angle1: f32, + angle2: f32, +} + +impl App { + fn add_rounded_rect( + &mut self, + bounds: LayoutRect, + color: ColorF, + builder: &mut DisplayListBuilder, + pipeline_id: PipelineId, + property_key: PropertyBindingKey, + opacity_key: Option>, + ) { + let filters = match opacity_key { + Some(opacity_key) => { + vec![ + FilterOp::Opacity(PropertyBinding::Binding(opacity_key, self.opacity), self.opacity), + ] + } + None => { + vec![] + } + }; + + let spatial_id = builder.push_reference_frame( + bounds.origin, + SpatialId::root_scroll_node(pipeline_id), + TransformStyle::Flat, + PropertyBinding::Binding(property_key, LayoutTransform::identity()), + ReferenceFrameKind::Transform, + ); + + builder.push_simple_stacking_context_with_filters( + LayoutPoint::zero(), + spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + &filters, + &[], + &[] + ); + + let space_and_clip = SpaceAndClipInfo { + spatial_id, + clip_id: ClipId::root(pipeline_id), + }; + let clip_bounds = LayoutRect::new(LayoutPoint::zero(), bounds.size); + let complex_clip = ComplexClipRegion { + rect: clip_bounds, + radii: BorderRadius::uniform(30.0), + mode: ClipMode::Clip, + }; + let clip_id = builder.define_clip_rounded_rect( + &space_and_clip, + complex_clip, + ); + + // Fill it with a white rect + builder.push_rect( + &CommonItemProperties::new( + LayoutRect::new(LayoutPoint::zero(), bounds.size), + SpaceAndClipInfo { + spatial_id, + clip_id, + } + ), + LayoutRect::new(LayoutPoint::zero(), bounds.size), + color, + ); + + builder.pop_stacking_context(); + builder.pop_reference_frame(); + } +} + +impl Example for App { + const WIDTH: u32 = 2048; + const HEIGHT: u32 = 1536; + + fn render( + &mut self, + _api: &mut RenderApi, + builder: &mut DisplayListBuilder, + _txn: &mut Transaction, + _device_size: DeviceIntSize, + pipeline_id: PipelineId, + _document_id: DocumentId, + ) { + let opacity_key = self.opacity_key; + + let bounds = (150, 150).to(250, 250); + let key0 = self.property_key0; + self.add_rounded_rect(bounds, ColorF::new(1.0, 0.0, 0.0, 0.5), builder, pipeline_id, key0, Some(opacity_key)); + + let bounds = (400, 400).to(600, 600); + let key1 = self.property_key1; + self.add_rounded_rect(bounds, ColorF::new(0.0, 1.0, 0.0, 0.5), builder, pipeline_id, key1, None); + + let bounds = (200, 500).to(350, 580); + let key2 = self.property_key2; + self.add_rounded_rect(bounds, ColorF::new(0.0, 0.0, 1.0, 0.5), builder, pipeline_id, key2, None); + } + + fn on_event(&mut self, win_event: winit::WindowEvent, api: &mut RenderApi, document_id: DocumentId) -> bool { + let mut rebuild_display_list = false; + + match win_event { + winit::WindowEvent::KeyboardInput { + input: winit::KeyboardInput { + state: winit::ElementState::Pressed, + virtual_keycode: Some(key), + .. + }, + .. + } => { + let (delta_angle, delta_opacity) = match key { + winit::VirtualKeyCode::Down => (0.0, -0.1), + winit::VirtualKeyCode::Up => (0.0, 0.1), + winit::VirtualKeyCode::Right => (1.0, 0.0), + winit::VirtualKeyCode::Left => (-1.0, 0.0), + winit::VirtualKeyCode::R => { + rebuild_display_list = true; + (0.0, 0.0) + } + _ => return false, + }; + // Update the transform based on the keyboard input and push it to + // webrender using the generate_frame API. This will recomposite with + // the updated transform. + self.opacity += delta_opacity; + self.angle0 += delta_angle * 0.1; + self.angle1 += delta_angle * 0.2; + self.angle2 -= delta_angle * 0.15; + let xf0 = LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::radians(self.angle0)); + let xf1 = LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::radians(self.angle1)); + let xf2 = LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::radians(self.angle2)); + let mut txn = Transaction::new(); + txn.update_dynamic_properties( + DynamicProperties { + transforms: vec![ + PropertyValue { + key: self.property_key0, + value: xf0, + }, + PropertyValue { + key: self.property_key1, + value: xf1, + }, + PropertyValue { + key: self.property_key2, + value: xf2, + }, + ], + floats: vec![ + PropertyValue { + key: self.opacity_key, + value: self.opacity, + } + ], + colors: vec![], + }, + ); + txn.generate_frame(); + api.send_transaction(document_id, txn); + } + _ => (), + } + + rebuild_display_list + } +} + +fn main() { + let mut app = App { + property_key0: PropertyBindingKey::new(42), // arbitrary magic number + property_key1: PropertyBindingKey::new(44), // arbitrary magic number + property_key2: PropertyBindingKey::new(45), // arbitrary magic number + opacity_key: PropertyBindingKey::new(43), + opacity: 0.5, + angle0: 0.0, + angle1: 0.0, + angle2: 0.0, + }; + boilerplate::main_wrapper(&mut app, None); +} diff --git a/third_party/webrender/examples/basic.rs b/third_party/webrender/examples/basic.rs new file mode 100644 index 00000000000..79e0a87c464 --- /dev/null +++ b/third_party/webrender/examples/basic.rs @@ -0,0 +1,321 @@ +/* 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/. */ + +extern crate euclid; +extern crate gleam; +extern crate glutin; +extern crate webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; + +use crate::boilerplate::{Example, HandyDandyRectBuilder}; +use euclid::vec2; +use winit::TouchPhase; +use std::collections::HashMap; +use webrender::ShaderPrecacheFlags; +use webrender::api::*; +use webrender::api::units::*; + + +#[derive(Debug)] +enum Gesture { + None, + Pan, + Zoom, +} + +#[derive(Debug)] +struct Touch { + id: u64, + start_x: f32, + start_y: f32, + current_x: f32, + current_y: f32, +} + +fn dist(x0: f32, y0: f32, x1: f32, y1: f32) -> f32 { + let dx = x0 - x1; + let dy = y0 - y1; + ((dx * dx) + (dy * dy)).sqrt() +} + +impl Touch { + fn distance_from_start(&self) -> f32 { + dist(self.start_x, self.start_y, self.current_x, self.current_y) + } + + fn initial_distance_from_other(&self, other: &Touch) -> f32 { + dist(self.start_x, self.start_y, other.start_x, other.start_y) + } + + fn current_distance_from_other(&self, other: &Touch) -> f32 { + dist( + self.current_x, + self.current_y, + other.current_x, + other.current_y, + ) + } +} + +struct TouchState { + active_touches: HashMap, + current_gesture: Gesture, + start_zoom: f32, + current_zoom: f32, + start_pan: DeviceIntPoint, + current_pan: DeviceIntPoint, +} + +enum TouchResult { + None, + Pan(DeviceIntPoint), + Zoom(f32), +} + +impl TouchState { + fn new() -> TouchState { + TouchState { + active_touches: HashMap::new(), + current_gesture: Gesture::None, + start_zoom: 1.0, + current_zoom: 1.0, + start_pan: DeviceIntPoint::zero(), + current_pan: DeviceIntPoint::zero(), + } + } + + fn handle_event(&mut self, touch: winit::Touch) -> TouchResult { + match touch.phase { + TouchPhase::Started => { + debug_assert!(!self.active_touches.contains_key(&touch.id)); + self.active_touches.insert( + touch.id, + Touch { + id: touch.id, + start_x: touch.location.x as f32, + start_y: touch.location.y as f32, + current_x: touch.location.x as f32, + current_y: touch.location.y as f32, + }, + ); + self.current_gesture = Gesture::None; + } + TouchPhase::Moved => { + match self.active_touches.get_mut(&touch.id) { + Some(active_touch) => { + active_touch.current_x = touch.location.x as f32; + active_touch.current_y = touch.location.y as f32; + } + None => panic!("move touch event with unknown touch id!"), + } + + match self.current_gesture { + Gesture::None => { + let mut over_threshold_count = 0; + let active_touch_count = self.active_touches.len(); + + for (_, touch) in &self.active_touches { + if touch.distance_from_start() > 8.0 { + over_threshold_count += 1; + } + } + + if active_touch_count == over_threshold_count { + if active_touch_count == 1 { + self.start_pan = self.current_pan; + self.current_gesture = Gesture::Pan; + } else if active_touch_count == 2 { + self.start_zoom = self.current_zoom; + self.current_gesture = Gesture::Zoom; + } + } + } + Gesture::Pan => { + let keys: Vec = self.active_touches.keys().cloned().collect(); + debug_assert!(keys.len() == 1); + let active_touch = &self.active_touches[&keys[0]]; + let x = active_touch.current_x - active_touch.start_x; + let y = active_touch.current_y - active_touch.start_y; + self.current_pan.x = self.start_pan.x + x.round() as i32; + self.current_pan.y = self.start_pan.y + y.round() as i32; + return TouchResult::Pan(self.current_pan); + } + Gesture::Zoom => { + let keys: Vec = self.active_touches.keys().cloned().collect(); + debug_assert!(keys.len() == 2); + let touch0 = &self.active_touches[&keys[0]]; + let touch1 = &self.active_touches[&keys[1]]; + let initial_distance = touch0.initial_distance_from_other(touch1); + let current_distance = touch0.current_distance_from_other(touch1); + self.current_zoom = self.start_zoom * current_distance / initial_distance; + return TouchResult::Zoom(self.current_zoom); + } + } + } + TouchPhase::Ended | TouchPhase::Cancelled => { + self.active_touches.remove(&touch.id).unwrap(); + self.current_gesture = Gesture::None; + } + } + + TouchResult::None + } +} + +fn main() { + let mut app = App { + touch_state: TouchState::new(), + }; + boilerplate::main_wrapper(&mut app, None); +} + +struct App { + touch_state: TouchState, +} + +impl Example for App { + // Make this the only example to test all shaders for compile errors. + const PRECACHE_SHADER_FLAGS: ShaderPrecacheFlags = ShaderPrecacheFlags::FULL_COMPILE; + + fn render( + &mut self, + api: &mut RenderApi, + builder: &mut DisplayListBuilder, + txn: &mut Transaction, + _: DeviceIntSize, + pipeline_id: PipelineId, + _document_id: DocumentId, + ) { + let content_bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size()); + let root_space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id); + let spatial_id = root_space_and_clip.spatial_id; + + builder.push_simple_stacking_context( + content_bounds.origin, + spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + let image_mask_key = api.generate_image_key(); + txn.add_image( + image_mask_key, + ImageDescriptor::new(2, 2, ImageFormat::R8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::new(vec![0, 80, 180, 255]), + None, + ); + let mask = ImageMask { + image: image_mask_key, + rect: (75, 75).by(100, 100), + repeat: false, + }; + let complex = ComplexClipRegion::new( + (50, 50).to(150, 150), + BorderRadius::uniform(20.0), + ClipMode::Clip + ); + let mask_clip_id = builder.define_clip_image_mask( + &root_space_and_clip, + mask, + ); + let clip_id = builder.define_clip_rounded_rect( + &SpaceAndClipInfo { + spatial_id: root_space_and_clip.spatial_id, + clip_id: mask_clip_id, + }, + complex, + ); + + builder.push_rect( + &CommonItemProperties::new( + (100, 100).to(200, 200), + SpaceAndClipInfo { spatial_id, clip_id }, + ), + (100, 100).to(200, 200), + ColorF::new(0.0, 1.0, 0.0, 1.0), + ); + + builder.push_rect( + &CommonItemProperties::new( + (250, 100).to(350, 200), + SpaceAndClipInfo { spatial_id, clip_id }, + ), + (250, 100).to(350, 200), + ColorF::new(0.0, 1.0, 0.0, 1.0), + ); + let border_side = BorderSide { + color: ColorF::new(0.0, 0.0, 1.0, 1.0), + style: BorderStyle::Groove, + }; + let border_widths = LayoutSideOffsets::new_all_same(10.0); + let border_details = BorderDetails::Normal(NormalBorder { + top: border_side, + right: border_side, + bottom: border_side, + left: border_side, + radius: BorderRadius::uniform(20.0), + do_aa: true, + }); + + let bounds = (100, 100).to(200, 200); + builder.push_border( + &CommonItemProperties::new( + bounds, + SpaceAndClipInfo { spatial_id, clip_id }, + ), + bounds, + border_widths, + border_details, + ); + + if false { + // draw box shadow? + let simple_box_bounds = (20, 200).by(50, 50); + let offset = vec2(10.0, 10.0); + let color = ColorF::new(1.0, 1.0, 1.0, 1.0); + let blur_radius = 0.0; + let spread_radius = 0.0; + let simple_border_radius = 8.0; + let box_shadow_type = BoxShadowClipMode::Inset; + + builder.push_box_shadow( + &CommonItemProperties::new(content_bounds, root_space_and_clip), + simple_box_bounds, + offset, + color, + blur_radius, + spread_radius, + BorderRadius::uniform(simple_border_radius), + box_shadow_type, + ); + } + + builder.pop_stacking_context(); + } + + fn on_event(&mut self, event: winit::WindowEvent, api: &mut RenderApi, document_id: DocumentId) -> bool { + let mut txn = Transaction::new(); + match event { + winit::WindowEvent::Touch(touch) => match self.touch_state.handle_event(touch) { + TouchResult::Pan(pan) => { + txn.set_pan(pan); + } + TouchResult::Zoom(zoom) => { + txn.set_pinch_zoom(ZoomFactor::new(zoom)); + } + TouchResult::None => {} + }, + _ => (), + } + + if !txn.is_empty() { + txn.generate_frame(); + api.send_transaction(document_id, txn); + } + + false + } +} diff --git a/third_party/webrender/examples/blob.rs b/third_party/webrender/examples/blob.rs new file mode 100644 index 00000000000..dd58b17f386 --- /dev/null +++ b/third_party/webrender/examples/blob.rs @@ -0,0 +1,289 @@ +/* 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/. */ + +extern crate gleam; +extern crate glutin; +extern crate rayon; +extern crate webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; + +use crate::boilerplate::{Example, HandyDandyRectBuilder}; +use rayon::{ThreadPool, ThreadPoolBuilder}; +use rayon::prelude::*; +use std::collections::HashMap; +use std::sync::Arc; +use webrender::api::{self, DisplayListBuilder, DocumentId, PipelineId, PrimitiveFlags, RenderApi, Transaction}; +use webrender::api::{ColorF, CommonItemProperties, SpaceAndClipInfo, ImageDescriptorFlags}; +use webrender::api::units::*; +use webrender::euclid::size2; + +// This example shows how to implement a very basic BlobImageHandler that can only render +// a checkerboard pattern. + +// The deserialized command list internally used by this example is just a color. +type ImageRenderingCommands = api::ColorU; + +// Serialize/deserialize the blob. +// For real usecases you should probably use serde rather than doing it by hand. + +fn serialize_blob(color: api::ColorU) -> Arc> { + Arc::new(vec![color.r, color.g, color.b, color.a]) +} + +fn deserialize_blob(blob: &[u8]) -> Result { + let mut iter = blob.iter(); + return match (iter.next(), iter.next(), iter.next(), iter.next()) { + (Some(&r), Some(&g), Some(&b), Some(&a)) => Ok(api::ColorU::new(r, g, b, a)), + (Some(&a), None, None, None) => Ok(api::ColorU::new(a, a, a, a)), + _ => Err(()), + }; +} + +// This is the function that applies the deserialized drawing commands and generates +// actual image data. +fn render_blob( + commands: Arc, + descriptor: &api::BlobImageDescriptor, + tile: TileOffset, +) -> api::BlobImageResult { + let color = *commands; + + // Note: This implementation ignores the dirty rect which isn't incorrect + // but is a missed optimization. + + // Allocate storage for the result. Right now the resource cache expects the + // tiles to have have no stride or offset. + let bpp = 4; + let mut texels = Vec::with_capacity((descriptor.rect.size.area() * bpp) as usize); + + // Generate a per-tile pattern to see it in the demo. For a real use case it would not + // make sense for the rendered content to depend on its tile. + let tile_checker = (tile.x % 2 == 0) != (tile.y % 2 == 0); + + let [w, h] = descriptor.rect.size.to_array(); + let offset = descriptor.rect.origin; + + for y in 0..h { + for x in 0..w { + // Apply the tile's offset. This is important: all drawing commands should be + // translated by this offset to give correct results with tiled blob images. + let x2 = x + offset.x; + let y2 = y + offset.y; + + // Render a simple checkerboard pattern + let checker = if (x2 % 20 >= 10) != (y2 % 20 >= 10) { + 1 + } else { + 0 + }; + // ..nested in the per-tile checkerboard pattern + let tc = if tile_checker { 0 } else { (1 - checker) * 40 }; + + match descriptor.format { + api::ImageFormat::BGRA8 => { + texels.push(color.b * checker + tc); + texels.push(color.g * checker + tc); + texels.push(color.r * checker + tc); + texels.push(color.a * checker + tc); + } + api::ImageFormat::R8 => { + texels.push(color.a * checker + tc); + } + _ => { + return Err(api::BlobImageError::Other( + format!("Unsupported image format"), + )); + } + } + } + } + + Ok(api::RasterizedBlobImage { + data: Arc::new(texels), + rasterized_rect: size2(w, h).into(), + }) +} + +struct CheckerboardRenderer { + // We are going to defer the rendering work to worker threads. + // Using a pre-built Arc rather than creating our own threads + // makes it possible to share the same thread pool as the glyph renderer (if we + // want to). + workers: Arc, + + // The deserialized drawing commands. + // In this example we store them in Arcs. This isn't necessary since in this simplified + // case the command list is a simple 32 bits value and would be cheap to clone before sending + // to the workers. But in a more realistic scenario the commands would typically be bigger + // and more expensive to clone, so let's pretend it is also the case here. + image_cmds: HashMap>, +} + +impl CheckerboardRenderer { + fn new(workers: Arc) -> Self { + CheckerboardRenderer { + image_cmds: HashMap::new(), + workers, + } + } +} + +impl api::BlobImageHandler for CheckerboardRenderer { + fn create_similar(&self) -> Box { + Box::new(CheckerboardRenderer::new(Arc::clone(&self.workers))) + } + + fn add(&mut self, key: api::BlobImageKey, cmds: Arc, + _visible_rect: &DeviceIntRect, _: api::TileSize) { + self.image_cmds + .insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap())); + } + + fn update(&mut self, key: api::BlobImageKey, cmds: Arc, + _visible_rect: &DeviceIntRect, _dirty_rect: &BlobDirtyRect) { + // Here, updating is just replacing the current version of the commands with + // the new one (no incremental updates). + self.image_cmds + .insert(key, Arc::new(deserialize_blob(&cmds[..]).unwrap())); + } + + fn delete(&mut self, key: api::BlobImageKey) { + self.image_cmds.remove(&key); + } + + fn prepare_resources( + &mut self, + _services: &dyn api::BlobImageResources, + _requests: &[api::BlobImageParams], + ) {} + + fn enable_multithreading(&mut self, _: bool) {} + fn delete_font(&mut self, _font: api::FontKey) {} + fn delete_font_instance(&mut self, _instance: api::FontInstanceKey) {} + fn clear_namespace(&mut self, _namespace: api::IdNamespace) {} + fn create_blob_rasterizer(&mut self) -> Box { + Box::new(Rasterizer { + workers: Arc::clone(&self.workers), + image_cmds: self.image_cmds.clone(), + }) + } +} + +struct Rasterizer { + workers: Arc, + image_cmds: HashMap>, +} + +impl api::AsyncBlobImageRasterizer for Rasterizer { + fn rasterize( + &mut self, + requests: &[api::BlobImageParams], + _low_priority: bool + ) -> Vec<(api::BlobImageRequest, api::BlobImageResult)> { + let requests: Vec<(&api::BlobImageParams, Arc)> = requests.into_iter().map(|params| { + (params, Arc::clone(&self.image_cmds[¶ms.request.key])) + }).collect(); + + self.workers.install(|| { + requests.into_par_iter().map(|(params, commands)| { + (params.request, render_blob(commands, ¶ms.descriptor, params.request.tile)) + }).collect() + }) + } +} + +struct App {} + +impl Example for App { + fn render( + &mut self, + api: &mut RenderApi, + builder: &mut DisplayListBuilder, + txn: &mut Transaction, + _device_size: DeviceIntSize, + pipeline_id: PipelineId, + _document_id: DocumentId, + ) { + let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id); + + builder.push_simple_stacking_context( + LayoutPoint::zero(), + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + let size1 = DeviceIntSize::new(500, 500); + let blob_img1 = api.generate_blob_image_key(); + txn.add_blob_image( + blob_img1, + api::ImageDescriptor::new( + size1.width, + size1.height, + api::ImageFormat::BGRA8, + ImageDescriptorFlags::IS_OPAQUE, + ), + serialize_blob(api::ColorU::new(50, 50, 150, 255)), + size1.into(), + Some(128), + ); + let bounds = (30, 30).by(size1.width, size1.height); + builder.push_image( + &CommonItemProperties::new(bounds, space_and_clip), + bounds, + api::ImageRendering::Auto, + api::AlphaType::PremultipliedAlpha, + blob_img1.as_image(), + ColorF::WHITE, + ); + + let size2 = DeviceIntSize::new(256, 256); + let blob_img2 = api.generate_blob_image_key(); + txn.add_blob_image( + blob_img2, + api::ImageDescriptor::new( + size2.width, + size2.height, + api::ImageFormat::BGRA8, + ImageDescriptorFlags::IS_OPAQUE, + ), + serialize_blob(api::ColorU::new(50, 150, 50, 255)), + size2.into(), + None, + ); + let bounds = (600, 600).by(size2.width, size2.height); + builder.push_image( + &CommonItemProperties::new(bounds, space_and_clip), + bounds, + api::ImageRendering::Auto, + api::AlphaType::PremultipliedAlpha, + blob_img2.as_image(), + ColorF::WHITE, + ); + + builder.pop_stacking_context(); + } +} + +fn main() { + let workers = + ThreadPoolBuilder::new().thread_name(|idx| format!("WebRender:Worker#{}", idx)) + .build(); + + let workers = Arc::new(workers.unwrap()); + + let opts = webrender::RendererOptions { + workers: Some(Arc::clone(&workers)), + // Register our blob renderer, so that WebRender integrates it in the resource cache.. + // Share the same pool of worker threads between WebRender and our blob renderer. + blob_image_handler: Some(Box::new(CheckerboardRenderer::new(Arc::clone(&workers)))), + ..Default::default() + }; + + let mut app = App {}; + + boilerplate::main_wrapper(&mut app, Some(opts)); +} diff --git a/third_party/webrender/examples/common/boilerplate.rs b/third_party/webrender/examples/common/boilerplate.rs new file mode 100644 index 00000000000..a48a00b9194 --- /dev/null +++ b/third_party/webrender/examples/common/boilerplate.rs @@ -0,0 +1,338 @@ +/* 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 gleam::gl; +use glutin; +use std::env; +use std::path::PathBuf; +use webrender; +use winit; +use webrender::{DebugFlags, ShaderPrecacheFlags}; +use webrender::api::*; +use webrender::api::units::*; + +struct Notifier { + events_proxy: winit::EventsLoopProxy, +} + +impl Notifier { + fn new(events_proxy: winit::EventsLoopProxy) -> Notifier { + Notifier { events_proxy } + } +} + +impl RenderNotifier for Notifier { + fn clone(&self) -> Box { + Box::new(Notifier { + events_proxy: self.events_proxy.clone(), + }) + } + + fn wake_up(&self) { + #[cfg(not(target_os = "android"))] + let _ = self.events_proxy.wakeup(); + } + + fn new_frame_ready(&self, + _: DocumentId, + _scrolled: bool, + _composite_needed: bool, + _render_time: Option) { + self.wake_up(); + } +} + +pub trait HandyDandyRectBuilder { + fn to(&self, x2: i32, y2: i32) -> LayoutRect; + fn by(&self, w: i32, h: i32) -> LayoutRect; +} +// Allows doing `(x, y).to(x2, y2)` or `(x, y).by(width, height)` with i32 +// values to build a f32 LayoutRect +impl HandyDandyRectBuilder for (i32, i32) { + fn to(&self, x2: i32, y2: i32) -> LayoutRect { + LayoutRect::new( + LayoutPoint::new(self.0 as f32, self.1 as f32), + LayoutSize::new((x2 - self.0) as f32, (y2 - self.1) as f32), + ) + } + + fn by(&self, w: i32, h: i32) -> LayoutRect { + LayoutRect::new( + LayoutPoint::new(self.0 as f32, self.1 as f32), + LayoutSize::new(w as f32, h as f32), + ) + } +} + +pub trait Example { + const TITLE: &'static str = "WebRender Sample App"; + const PRECACHE_SHADER_FLAGS: ShaderPrecacheFlags = ShaderPrecacheFlags::EMPTY; + const WIDTH: u32 = 1920; + const HEIGHT: u32 = 1080; + + fn render( + &mut self, + api: &mut RenderApi, + builder: &mut DisplayListBuilder, + txn: &mut Transaction, + device_size: DeviceIntSize, + pipeline_id: PipelineId, + document_id: DocumentId, + ); + fn on_event( + &mut self, + _: winit::WindowEvent, + _: &mut RenderApi, + _: DocumentId, + ) -> bool { + false + } + fn get_image_handlers( + &mut self, + _gl: &dyn gl::Gl, + ) -> (Option>, + Option>) { + (None, None) + } + fn draw_custom(&mut self, _gl: &dyn gl::Gl) { + } +} + +pub fn main_wrapper( + example: &mut E, + options: Option, +) { + env_logger::init(); + + #[cfg(target_os = "macos")] + { + use core_foundation::{self as cf, base::TCFType}; + let i = cf::bundle::CFBundle::main_bundle().info_dictionary(); + let mut i = unsafe { i.to_mutable() }; + i.set( + cf::string::CFString::new("NSSupportsAutomaticGraphicsSwitching"), + cf::boolean::CFBoolean::true_value().into_CFType(), + ); + } + + let args: Vec = env::args().collect(); + let res_path = if args.len() > 1 { + Some(PathBuf::from(&args[1])) + } else { + None + }; + + let mut events_loop = winit::EventsLoop::new(); + let window_builder = winit::WindowBuilder::new() + .with_title(E::TITLE) + .with_multitouch() + .with_dimensions(winit::dpi::LogicalSize::new(E::WIDTH as f64, E::HEIGHT as f64)); + let windowed_context = glutin::ContextBuilder::new() + .with_gl(glutin::GlRequest::GlThenGles { + opengl_version: (3, 2), + opengles_version: (3, 0), + }) + .build_windowed(window_builder, &events_loop) + .unwrap(); + + let windowed_context = unsafe { windowed_context.make_current().unwrap() }; + + let gl = match windowed_context.get_api() { + glutin::Api::OpenGl => unsafe { + gl::GlFns::load_with( + |symbol| windowed_context.get_proc_address(symbol) as *const _ + ) + }, + glutin::Api::OpenGlEs => unsafe { + gl::GlesFns::load_with( + |symbol| windowed_context.get_proc_address(symbol) as *const _ + ) + }, + glutin::Api::WebGl => unimplemented!(), + }; + + println!("OpenGL version {}", gl.get_string(gl::VERSION)); + println!("Shader resource path: {:?}", res_path); + let device_pixel_ratio = windowed_context.window().get_hidpi_factor() as f32; + println!("Device pixel ratio: {}", device_pixel_ratio); + + println!("Loading shaders..."); + let mut debug_flags = DebugFlags::ECHO_DRIVER_MESSAGES | DebugFlags::TEXTURE_CACHE_DBG; + let opts = webrender::RendererOptions { + resource_override_path: res_path, + precache_flags: E::PRECACHE_SHADER_FLAGS, + device_pixel_ratio, + clear_color: Some(ColorF::new(0.3, 0.0, 0.0, 1.0)), + debug_flags, + //allow_texture_swizzling: false, + ..options.unwrap_or(webrender::RendererOptions::default()) + }; + + let device_size = { + let size = windowed_context + .window() + .get_inner_size() + .unwrap() + .to_physical(device_pixel_ratio as f64); + DeviceIntSize::new(size.width as i32, size.height as i32) + }; + let notifier = Box::new(Notifier::new(events_loop.create_proxy())); + let (mut renderer, sender) = webrender::Renderer::new( + gl.clone(), + notifier, + opts, + None, + device_size, + ).unwrap(); + let mut api = sender.create_api(); + let document_id = api.add_document(device_size, 0); + + let (external, output) = example.get_image_handlers(&*gl); + + if let Some(output_image_handler) = output { + renderer.set_output_image_handler(output_image_handler); + } + + if let Some(external_image_handler) = external { + renderer.set_external_image_handler(external_image_handler); + } + + let epoch = Epoch(0); + let pipeline_id = PipelineId(0, 0); + let layout_size = device_size.to_f32() / euclid::Scale::new(device_pixel_ratio); + let mut builder = DisplayListBuilder::new(pipeline_id, layout_size); + let mut txn = Transaction::new(); + + example.render( + &mut api, + &mut builder, + &mut txn, + device_size, + pipeline_id, + document_id, + ); + txn.set_display_list( + epoch, + Some(ColorF::new(0.3, 0.0, 0.0, 1.0)), + layout_size, + builder.finalize(), + true, + ); + txn.set_root_pipeline(pipeline_id); + txn.generate_frame(); + api.send_transaction(document_id, txn); + + println!("Entering event loop"); + events_loop.run_forever(|global_event| { + let mut txn = Transaction::new(); + let mut custom_event = true; + + let old_flags = debug_flags; + let win_event = match global_event { + winit::Event::WindowEvent { event, .. } => event, + _ => return winit::ControlFlow::Continue, + }; + match win_event { + winit::WindowEvent::CloseRequested => return winit::ControlFlow::Break, + winit::WindowEvent::AxisMotion { .. } | + winit::WindowEvent::CursorMoved { .. } => { + custom_event = example.on_event( + win_event, + &mut api, + document_id, + ); + // skip high-frequency events from triggering a frame draw. + if !custom_event { + return winit::ControlFlow::Continue; + } + }, + winit::WindowEvent::KeyboardInput { + input: winit::KeyboardInput { + state: winit::ElementState::Pressed, + virtual_keycode: Some(key), + .. + }, + .. + } => match key { + winit::VirtualKeyCode::Escape => return winit::ControlFlow::Break, + winit::VirtualKeyCode::P => debug_flags.toggle(DebugFlags::PROFILER_DBG), + winit::VirtualKeyCode::O => debug_flags.toggle(DebugFlags::RENDER_TARGET_DBG), + winit::VirtualKeyCode::I => debug_flags.toggle(DebugFlags::TEXTURE_CACHE_DBG), + winit::VirtualKeyCode::S => debug_flags.toggle(DebugFlags::COMPACT_PROFILER), + winit::VirtualKeyCode::T => debug_flags.toggle(DebugFlags::PICTURE_CACHING_DBG), + winit::VirtualKeyCode::Q => debug_flags.toggle( + DebugFlags::GPU_TIME_QUERIES | DebugFlags::GPU_SAMPLE_QUERIES + ), + winit::VirtualKeyCode::F => debug_flags.toggle( + DebugFlags::NEW_FRAME_INDICATOR | DebugFlags::NEW_SCENE_INDICATOR + ), + winit::VirtualKeyCode::G => debug_flags.toggle(DebugFlags::GPU_CACHE_DBG), + winit::VirtualKeyCode::Key1 => txn.set_document_view( + device_size.into(), + 1.0 + ), + winit::VirtualKeyCode::Key2 => txn.set_document_view( + device_size.into(), + 2.0 + ), + winit::VirtualKeyCode::M => api.notify_memory_pressure(), + winit::VirtualKeyCode::C => { + let path: PathBuf = "../captures/example".into(); + //TODO: switch between SCENE/FRAME capture types + // based on "shift" modifier, when `glutin` is updated. + let bits = CaptureBits::all(); + api.save_capture(path, bits); + }, + _ => { + custom_event = example.on_event( + win_event, + &mut api, + document_id, + ) + }, + }, + other => custom_event = example.on_event( + other, + &mut api, + document_id, + ), + }; + + if debug_flags != old_flags { + api.send_debug_cmd(DebugCommand::SetFlags(debug_flags)); + } + + if custom_event { + let mut builder = DisplayListBuilder::new(pipeline_id, layout_size); + + example.render( + &mut api, + &mut builder, + &mut txn, + device_size, + pipeline_id, + document_id, + ); + txn.set_display_list( + epoch, + Some(ColorF::new(0.3, 0.0, 0.0, 1.0)), + layout_size, + builder.finalize(), + true, + ); + txn.generate_frame(); + } + api.send_transaction(document_id, txn); + + renderer.update(); + renderer.render(device_size).unwrap(); + let _ = renderer.flush_pipeline_info(); + example.draw_custom(&*gl); + windowed_context.swap_buffers().ok(); + + winit::ControlFlow::Continue + }); + + renderer.deinit(); +} diff --git a/third_party/webrender/examples/common/image_helper.rs b/third_party/webrender/examples/common/image_helper.rs new file mode 100644 index 00000000000..368674c0e12 --- /dev/null +++ b/third_party/webrender/examples/common/image_helper.rs @@ -0,0 +1,19 @@ +/* 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 webrender::api::{ImageData, ImageDescriptor, ImageFormat, ImageDescriptorFlags}; + +pub fn make_checkerboard(width: u32, height: u32) -> (ImageDescriptor, ImageData) { + let mut image_data = Vec::new(); + for y in 0 .. height { + for x in 0 .. width { + let lum = 255 * (((x & 8) == 0) ^ ((y & 8) == 0)) as u8; + image_data.extend_from_slice(&[lum, lum, lum, 0xff]); + } + } + ( + ImageDescriptor::new(width as i32, height as i32, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::new(image_data) + ) +} diff --git a/third_party/webrender/examples/document.rs b/third_party/webrender/examples/document.rs new file mode 100644 index 00000000000..56f5eedc8f5 --- /dev/null +++ b/third_party/webrender/examples/document.rs @@ -0,0 +1,149 @@ +/* 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/. */ + +extern crate euclid; +extern crate gleam; +extern crate glutin; +extern crate webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; + +use crate::boilerplate::Example; +use euclid::Scale; +use webrender::api::*; +use webrender::api::units::*; + +// This example creates multiple documents overlapping each other with +// specified layer indices. + +struct Document { + id: DocumentId, + pipeline_id: PipelineId, + content_rect: LayoutRect, + color: ColorF, +} + +struct App { + documents: Vec, +} + +impl App { + fn init( + &mut self, + api: &mut RenderApi, + device_pixel_ratio: f32, + ) { + let init_data = vec![ + ( + PipelineId(1, 0), + -2, + ColorF::new(0.0, 1.0, 0.0, 1.0), + DeviceIntPoint::new(0, 0), + ), + ( + PipelineId(2, 0), + -1, + ColorF::new(1.0, 1.0, 0.0, 1.0), + DeviceIntPoint::new(200, 0), + ), + ( + PipelineId(3, 0), + 0, + ColorF::new(1.0, 0.0, 0.0, 1.0), + DeviceIntPoint::new(200, 200), + ), + ( + PipelineId(4, 0), + 1, + ColorF::new(1.0, 0.0, 1.0, 1.0), + DeviceIntPoint::new(0, 200), + ), + ]; + + for (pipeline_id, layer, color, offset) in init_data { + let size = DeviceIntSize::new(250, 250); + let bounds = DeviceIntRect::new(offset, size); + + let document_id = api.add_document(size, layer); + let mut txn = Transaction::new(); + txn.set_document_view(bounds, device_pixel_ratio); + txn.set_root_pipeline(pipeline_id); + api.send_transaction(document_id, txn); + + self.documents.push(Document { + id: document_id, + pipeline_id, + content_rect: LayoutRect::new( + LayoutPoint::origin(), + bounds.size.to_f32() / Scale::new(device_pixel_ratio), + ), + color, + }); + } + } +} + +impl Example for App { + fn render( + &mut self, + api: &mut RenderApi, + base_builder: &mut DisplayListBuilder, + _txn: &mut Transaction, + device_size: DeviceIntSize, + _pipeline_id: PipelineId, + _: DocumentId, + ) { + if self.documents.is_empty() { + let device_pixel_ratio = device_size.width as f32 / + base_builder.content_size().width; + // this is the first run, hack around the boilerplate, + // which assumes an example only needs one document + self.init(api, device_pixel_ratio); + } + + for doc in &self.documents { + let space_and_clip = SpaceAndClipInfo::root_scroll(doc.pipeline_id); + let mut builder = DisplayListBuilder::new( + doc.pipeline_id, + doc.content_rect.size, + ); + let local_rect = LayoutRect::new( + LayoutPoint::zero(), + doc.content_rect.size, + ); + + builder.push_simple_stacking_context( + doc.content_rect.origin, + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + builder.push_rect( + &CommonItemProperties::new(local_rect, space_and_clip), + local_rect, + doc.color, + ); + builder.pop_stacking_context(); + + let mut txn = Transaction::new(); + txn.set_display_list( + Epoch(0), + None, + doc.content_rect.size, + builder.finalize(), + true, + ); + txn.generate_frame(); + api.send_transaction(doc.id, txn); + } + } +} + +fn main() { + let mut app = App { + documents: Vec::new(), + }; + boilerplate::main_wrapper(&mut app, None); +} diff --git a/third_party/webrender/examples/frame_output.rs b/third_party/webrender/examples/frame_output.rs new file mode 100644 index 00000000000..dc1c1d83f06 --- /dev/null +++ b/third_party/webrender/examples/frame_output.rs @@ -0,0 +1,238 @@ +/* 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/. */ + +extern crate euclid; +extern crate gleam; +extern crate glutin; +extern crate webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; + +use crate::boilerplate::{Example, HandyDandyRectBuilder}; +use euclid::Scale; +use gleam::gl; +use webrender::api::*; +use webrender::api::units::*; + + +// This example demonstrates using the frame output feature to copy +// the output of a WR framebuffer to a custom texture. + +#[derive(Debug)] +struct Document { + id: DocumentId, + pipeline_id: PipelineId, + content_rect: LayoutRect, + color: ColorF, +} + + +struct App { + external_image_key: Option, + output_document: Option +} + +struct OutputHandler { + texture_id: gl::GLuint +} + +struct ExternalHandler { + texture_id: gl::GLuint +} + +impl OutputImageHandler for OutputHandler { + fn lock(&mut self, _id: PipelineId) -> Option<(u32, FramebufferIntSize)> { + Some((self.texture_id, FramebufferIntSize::new(500, 500))) + } + + fn unlock(&mut self, _id: PipelineId) {} +} + +impl ExternalImageHandler for ExternalHandler { + fn lock( + &mut self, + _key: ExternalImageId, + _channel_index: u8, + _rendering: ImageRendering + ) -> ExternalImage { + ExternalImage { + uv: TexelRect::new(0.0, 0.0, 1.0, 1.0), + source: ExternalImageSource::NativeTexture(self.texture_id), + } + } + fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) {} +} + +impl App { + fn init_output_document( + &mut self, + api: &mut RenderApi, + device_size: DeviceIntSize, + device_pixel_ratio: f32, + ) { + // Generate the external image key that will be used to render the output document to the root document. + self.external_image_key = Some(api.generate_image_key()); + + let pipeline_id = PipelineId(1, 0); + let layer = 1; + let color = ColorF::new(1., 1., 0., 1.); + let document_id = api.add_document(device_size, layer); + api.enable_frame_output(document_id, pipeline_id, true); + api.set_document_view( + document_id, + device_size.into(), + device_pixel_ratio, + ); + + let document = Document { + id: document_id, + pipeline_id, + content_rect: LayoutRect::new( + LayoutPoint::zero(), + device_size.to_f32() / Scale::new(device_pixel_ratio), + ), + color, + }; + + let mut txn = Transaction::new(); + + txn.add_image( + self.external_image_key.unwrap(), + ImageDescriptor::new(100, 100, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::External(ExternalImageData { + id: ExternalImageId(0), + channel_index: 0, + image_type: ExternalImageType::TextureHandle(TextureTarget::Default), + }), + None, + ); + + let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id); + let mut builder = DisplayListBuilder::new( + document.pipeline_id, + document.content_rect.size, + ); + + builder.push_simple_stacking_context( + document.content_rect.origin, + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + builder.push_rect( + &CommonItemProperties::new(document.content_rect, space_and_clip), + document.content_rect, + ColorF::new(1.0, 1.0, 0.0, 1.0) + ); + builder.pop_stacking_context(); + + txn.set_root_pipeline(pipeline_id); + txn.set_display_list( + Epoch(0), + Some(document.color), + document.content_rect.size, + builder.finalize(), + true, + ); + txn.generate_frame(); + api.send_transaction(document.id, txn); + self.output_document = Some(document); + } +} + +impl Example for App { + fn render( + &mut self, + api: &mut RenderApi, + builder: &mut DisplayListBuilder, + _txn: &mut Transaction, + device_size: DeviceIntSize, + pipeline_id: PipelineId, + _document_id: DocumentId, + ) { + if self.output_document.is_none() { + let device_pixel_ratio = device_size.width as f32 / + builder.content_size().width; + self.init_output_document(api, DeviceIntSize::new(200, 200), device_pixel_ratio); + } + + let bounds = (100, 100).to(200, 200); + let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id); + + builder.push_simple_stacking_context( + bounds.origin, + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + builder.push_image( + &CommonItemProperties::new(bounds, space_and_clip), + bounds, + ImageRendering::Auto, + AlphaType::PremultipliedAlpha, + self.external_image_key.unwrap(), + ColorF::WHITE, + ); + + builder.pop_stacking_context(); + } + + fn get_image_handlers( + &mut self, + gl: &dyn gl::Gl, + ) -> (Option>, + Option>) { + let texture_id = gl.gen_textures(1)[0]; + + gl.bind_texture(gl::TEXTURE_2D, texture_id); + gl.tex_parameter_i( + gl::TEXTURE_2D, + gl::TEXTURE_MAG_FILTER, + gl::LINEAR as gl::GLint, + ); + gl.tex_parameter_i( + gl::TEXTURE_2D, + gl::TEXTURE_MIN_FILTER, + gl::LINEAR as gl::GLint, + ); + gl.tex_parameter_i( + gl::TEXTURE_2D, + gl::TEXTURE_WRAP_S, + gl::CLAMP_TO_EDGE as gl::GLint, + ); + gl.tex_parameter_i( + gl::TEXTURE_2D, + gl::TEXTURE_WRAP_T, + gl::CLAMP_TO_EDGE as gl::GLint, + ); + gl.tex_image_2d( + gl::TEXTURE_2D, + 0, + gl::RGBA as gl::GLint, + 100, + 100, + 0, + gl::BGRA, + gl::UNSIGNED_BYTE, + None, + ); + gl.bind_texture(gl::TEXTURE_2D, 0); + + ( + Some(Box::new(ExternalHandler { texture_id })), + Some(Box::new(OutputHandler { texture_id })) + ) + } +} + +fn main() { + let mut app = App { + external_image_key: None, + output_document: None + }; + + boilerplate::main_wrapper(&mut app, None); +} diff --git a/third_party/webrender/examples/iframe.rs b/third_party/webrender/examples/iframe.rs new file mode 100644 index 00000000000..50e8b46f30c --- /dev/null +++ b/third_party/webrender/examples/iframe.rs @@ -0,0 +1,95 @@ +/* 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/. */ + +extern crate gleam; +extern crate glutin; +extern crate webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; + +use crate::boilerplate::{Example, HandyDandyRectBuilder}; +use webrender::api::*; +use webrender::api::units::*; + +// This example uses the push_iframe API to nest a second pipeline's displaylist +// inside the root pipeline's display list. When it works, a green square is +// shown. If it fails, a red square is shown. + +struct App {} + +impl Example for App { + fn render( + &mut self, + api: &mut RenderApi, + builder: &mut DisplayListBuilder, + _txn: &mut Transaction, + _device_size: DeviceIntSize, + pipeline_id: PipelineId, + document_id: DocumentId, + ) { + // All the sub_* things are for the nested pipeline + let sub_size = DeviceIntSize::new(100, 100); + let sub_bounds = (0, 0).to(sub_size.width as i32, sub_size.height as i32); + + let sub_pipeline_id = PipelineId(pipeline_id.0, 42); + let mut sub_builder = DisplayListBuilder::new(sub_pipeline_id, sub_bounds.size); + let mut space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id); + + sub_builder.push_simple_stacking_context( + sub_bounds.origin, + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + // green rect visible == success + sub_builder.push_rect( + &CommonItemProperties::new(sub_bounds, space_and_clip), + sub_bounds, + ColorF::new(0.0, 1.0, 0.0, 1.0) + ); + sub_builder.pop_stacking_context(); + + let mut txn = Transaction::new(); + txn.set_display_list( + Epoch(0), + None, + sub_bounds.size, + sub_builder.finalize(), + true, + ); + api.send_transaction(document_id, txn); + + space_and_clip.spatial_id = builder.push_reference_frame( + sub_bounds.origin, + space_and_clip.spatial_id, + TransformStyle::Flat, + PropertyBinding::Binding(PropertyBindingKey::new(42), LayoutTransform::identity()), + ReferenceFrameKind::Transform, + ); + + // And this is for the root pipeline + builder.push_simple_stacking_context( + sub_bounds.origin, + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + // red rect under the iframe: if this is visible, things have gone wrong + builder.push_rect( + &CommonItemProperties::new(sub_bounds, space_and_clip), + sub_bounds, + ColorF::new(1.0, 0.0, 0.0, 1.0) + ); + builder.push_iframe(sub_bounds, sub_bounds, &space_and_clip, sub_pipeline_id, false); + builder.pop_stacking_context(); + builder.pop_reference_frame(); + } +} + +fn main() { + let mut app = App {}; + boilerplate::main_wrapper(&mut app, None); +} diff --git a/third_party/webrender/examples/image_resize.rs b/third_party/webrender/examples/image_resize.rs new file mode 100644 index 00000000000..f45add1e7ad --- /dev/null +++ b/third_party/webrender/examples/image_resize.rs @@ -0,0 +1,121 @@ +/* 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/. */ + +extern crate gleam; +extern crate glutin; +extern crate webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; +#[path = "common/image_helper.rs"] +mod image_helper; + +use crate::boilerplate::{Example, HandyDandyRectBuilder}; +use webrender::api::*; +use webrender::api::units::*; + +struct App { + image_key: ImageKey, +} + +impl Example for App { + fn render( + &mut self, + _api: &mut RenderApi, + builder: &mut DisplayListBuilder, + txn: &mut Transaction, + _device_size: DeviceIntSize, + pipeline_id: PipelineId, + _document_id: DocumentId, + ) { + let (image_descriptor, image_data) = image_helper::make_checkerboard(32, 32); + txn.add_image( + self.image_key, + image_descriptor, + image_data, + None, + ); + + let bounds = (0, 0).to(512, 512); + let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id); + + builder.push_simple_stacking_context( + bounds.origin, + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + let image_size = LayoutSize::new(100.0, 100.0); + + builder.push_image( + &CommonItemProperties::new( + LayoutRect::new(LayoutPoint::new(100.0, 100.0), image_size), + space_and_clip, + ), + bounds, + ImageRendering::Auto, + AlphaType::PremultipliedAlpha, + self.image_key, + ColorF::WHITE, + ); + + builder.push_image( + &CommonItemProperties::new( + LayoutRect::new(LayoutPoint::new(250.0, 100.0), image_size), + space_and_clip, + ), + bounds, + ImageRendering::Pixelated, + AlphaType::PremultipliedAlpha, + self.image_key, + ColorF::WHITE, + ); + + builder.pop_stacking_context(); + } + + fn on_event(&mut self, event: winit::WindowEvent, api: &mut RenderApi, document_id: DocumentId) -> bool { + match event { + winit::WindowEvent::KeyboardInput { + input: winit::KeyboardInput { + state: winit::ElementState::Pressed, + virtual_keycode: Some(winit::VirtualKeyCode::Space), + .. + }, + .. + } => { + let mut image_data = Vec::new(); + for y in 0 .. 64 { + for x in 0 .. 64 { + let r = 255 * ((y & 32) == 0) as u8; + let g = 255 * ((x & 32) == 0) as u8; + image_data.extend_from_slice(&[0, g, r, 0xff]); + } + } + + let mut txn = Transaction::new(); + txn.update_image( + self.image_key, + ImageDescriptor::new(64, 64, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::new(image_data), + &DirtyRect::All, + ); + let mut txn = Transaction::new(); + txn.generate_frame(); + api.send_transaction(document_id, txn); + } + _ => {} + } + + false + } +} + +fn main() { + let mut app = App { + image_key: ImageKey(IdNamespace(0), 0), + }; + boilerplate::main_wrapper(&mut app, None); +} diff --git a/third_party/webrender/examples/multiwindow.rs b/third_party/webrender/examples/multiwindow.rs new file mode 100644 index 00000000000..9b20960a94f --- /dev/null +++ b/third_party/webrender/examples/multiwindow.rs @@ -0,0 +1,326 @@ +/* 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/. */ + +extern crate euclid; +extern crate gleam; +extern crate glutin; +extern crate webrender; +extern crate winit; + +use gleam::gl; +use glutin::NotCurrent; +use std::fs::File; +use std::io::Read; +use webrender::api::*; +use webrender::api::units::*; +use webrender::DebugFlags; +use winit::dpi::LogicalSize; + +struct Notifier { + events_proxy: winit::EventsLoopProxy, +} + +impl Notifier { + fn new(events_proxy: winit::EventsLoopProxy) -> Notifier { + Notifier { events_proxy } + } +} + +impl RenderNotifier for Notifier { + fn clone(&self) -> Box { + Box::new(Notifier { + events_proxy: self.events_proxy.clone(), + }) + } + + fn wake_up(&self) { + #[cfg(not(target_os = "android"))] + let _ = self.events_proxy.wakeup(); + } + + fn new_frame_ready(&self, + _: DocumentId, + _scrolled: bool, + _composite_needed: bool, + _render_time: Option) { + self.wake_up(); + } +} + +struct Window { + events_loop: winit::EventsLoop, //TODO: share events loop? + context: Option>, + renderer: webrender::Renderer, + name: &'static str, + pipeline_id: PipelineId, + document_id: DocumentId, + epoch: Epoch, + api: RenderApi, + font_instance_key: FontInstanceKey, +} + +impl Window { + fn new(name: &'static str, clear_color: ColorF) -> Self { + let events_loop = winit::EventsLoop::new(); + let window_builder = winit::WindowBuilder::new() + .with_title(name) + .with_multitouch() + .with_dimensions(LogicalSize::new(800., 600.)); + let context = glutin::ContextBuilder::new() + .with_gl(glutin::GlRequest::GlThenGles { + opengl_version: (3, 2), + opengles_version: (3, 0), + }) + .build_windowed(window_builder, &events_loop) + .unwrap(); + + let context = unsafe { context.make_current().unwrap() }; + + let gl = match context.get_api() { + glutin::Api::OpenGl => unsafe { + gl::GlFns::load_with(|symbol| context.get_proc_address(symbol) as *const _) + }, + glutin::Api::OpenGlEs => unsafe { + gl::GlesFns::load_with(|symbol| context.get_proc_address(symbol) as *const _) + }, + glutin::Api::WebGl => unimplemented!(), + }; + + let device_pixel_ratio = context.window().get_hidpi_factor() as f32; + + let opts = webrender::RendererOptions { + device_pixel_ratio, + clear_color: Some(clear_color), + ..webrender::RendererOptions::default() + }; + + let device_size = { + let size = context + .window() + .get_inner_size() + .unwrap() + .to_physical(device_pixel_ratio as f64); + DeviceIntSize::new(size.width as i32, size.height as i32) + }; + let notifier = Box::new(Notifier::new(events_loop.create_proxy())); + let (renderer, sender) = webrender::Renderer::new(gl.clone(), notifier, opts, None, device_size).unwrap(); + let mut api = sender.create_api(); + let document_id = api.add_document(device_size, 0); + + let epoch = Epoch(0); + let pipeline_id = PipelineId(0, 0); + let mut txn = Transaction::new(); + + let font_key = api.generate_font_key(); + let font_bytes = load_file("../wrench/reftests/text/FreeSans.ttf"); + txn.add_raw_font(font_key, font_bytes, 0); + + let font_instance_key = api.generate_font_instance_key(); + txn.add_font_instance(font_instance_key, font_key, 32.0, None, None, Vec::new()); + + api.send_transaction(document_id, txn); + + Window { + events_loop, + context: Some(unsafe { context.make_not_current().unwrap() }), + renderer, + name, + epoch, + pipeline_id, + document_id, + api, + font_instance_key, + } + } + + fn tick(&mut self) -> bool { + let mut do_exit = false; + let my_name = &self.name; + let renderer = &mut self.renderer; + let api = &mut self.api; + + self.events_loop.poll_events(|global_event| match global_event { + winit::Event::WindowEvent { event, .. } => match event { + winit::WindowEvent::CloseRequested | + winit::WindowEvent::KeyboardInput { + input: winit::KeyboardInput { + virtual_keycode: Some(winit::VirtualKeyCode::Escape), + .. + }, + .. + } => { + do_exit = true + } + winit::WindowEvent::KeyboardInput { + input: winit::KeyboardInput { + state: winit::ElementState::Pressed, + virtual_keycode: Some(winit::VirtualKeyCode::P), + .. + }, + .. + } => { + println!("set flags {}", my_name); + api.send_debug_cmd(DebugCommand::SetFlags(DebugFlags::PROFILER_DBG)) + } + _ => {} + } + _ => {} + }); + if do_exit { + return true + } + + let context = unsafe { self.context.take().unwrap().make_current().unwrap() }; + let device_pixel_ratio = context.window().get_hidpi_factor() as f32; + let device_size = { + let size = context + .window() + .get_inner_size() + .unwrap() + .to_physical(device_pixel_ratio as f64); + DeviceIntSize::new(size.width as i32, size.height as i32) + }; + let layout_size = device_size.to_f32() / euclid::Scale::new(device_pixel_ratio); + let mut txn = Transaction::new(); + let mut builder = DisplayListBuilder::new(self.pipeline_id, layout_size); + let space_and_clip = SpaceAndClipInfo::root_scroll(self.pipeline_id); + + let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size()); + builder.push_simple_stacking_context( + bounds.origin, + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + builder.push_rect( + &CommonItemProperties::new( + LayoutRect::new( + LayoutPoint::new(100.0, 200.0), + LayoutSize::new(100.0, 200.0), + ), + space_and_clip, + ), + LayoutRect::new( + LayoutPoint::new(100.0, 200.0), + LayoutSize::new(100.0, 200.0), + ), + ColorF::new(0.0, 1.0, 0.0, 1.0)); + + let text_bounds = LayoutRect::new( + LayoutPoint::new(100.0, 50.0), + LayoutSize::new(700.0, 200.0) + ); + let glyphs = vec![ + GlyphInstance { + index: 48, + point: LayoutPoint::new(100.0, 100.0), + }, + GlyphInstance { + index: 68, + point: LayoutPoint::new(150.0, 100.0), + }, + GlyphInstance { + index: 80, + point: LayoutPoint::new(200.0, 100.0), + }, + GlyphInstance { + index: 82, + point: LayoutPoint::new(250.0, 100.0), + }, + GlyphInstance { + index: 81, + point: LayoutPoint::new(300.0, 100.0), + }, + GlyphInstance { + index: 3, + point: LayoutPoint::new(350.0, 100.0), + }, + GlyphInstance { + index: 86, + point: LayoutPoint::new(400.0, 100.0), + }, + GlyphInstance { + index: 79, + point: LayoutPoint::new(450.0, 100.0), + }, + GlyphInstance { + index: 72, + point: LayoutPoint::new(500.0, 100.0), + }, + GlyphInstance { + index: 83, + point: LayoutPoint::new(550.0, 100.0), + }, + GlyphInstance { + index: 87, + point: LayoutPoint::new(600.0, 100.0), + }, + GlyphInstance { + index: 17, + point: LayoutPoint::new(650.0, 100.0), + }, + ]; + + builder.push_text( + &CommonItemProperties::new( + text_bounds, + space_and_clip, + ), + text_bounds, + &glyphs, + self.font_instance_key, + ColorF::new(1.0, 1.0, 0.0, 1.0), + None, + ); + + builder.pop_stacking_context(); + + txn.set_display_list( + self.epoch, + None, + layout_size, + builder.finalize(), + true, + ); + txn.set_root_pipeline(self.pipeline_id); + txn.generate_frame(); + api.send_transaction(self.document_id, txn); + + renderer.update(); + renderer.render(device_size).unwrap(); + context.swap_buffers().ok(); + + self.context = Some(unsafe { context.make_not_current().unwrap() }); + + false + } + + fn deinit(self) { + self.renderer.deinit(); + } +} + +fn main() { + let mut win1 = Window::new("window1", ColorF::new(0.3, 0.0, 0.0, 1.0)); + let mut win2 = Window::new("window2", ColorF::new(0.0, 0.3, 0.0, 1.0)); + + loop { + if win1.tick() { + break; + } + if win2.tick() { + break; + } + } + + win1.deinit(); + win2.deinit(); +} + +fn load_file(name: &str) -> Vec { + let mut file = File::open(name).unwrap(); + let mut buffer = vec![]; + file.read_to_end(&mut buffer).unwrap(); + buffer +} diff --git a/third_party/webrender/examples/scrolling.rs b/third_party/webrender/examples/scrolling.rs new file mode 100644 index 00000000000..34cd14304f8 --- /dev/null +++ b/third_party/webrender/examples/scrolling.rs @@ -0,0 +1,231 @@ +/* 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/. */ + +extern crate euclid; +extern crate gleam; +extern crate glutin; +extern crate webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; + +use crate::boilerplate::{Example, HandyDandyRectBuilder}; +use euclid::SideOffsets2D; +use webrender::api::*; +use webrender::api::units::*; +use winit::dpi::LogicalPosition; + + +struct App { + cursor_position: WorldPoint, +} + +impl Example for App { + fn render( + &mut self, + _api: &mut RenderApi, + builder: &mut DisplayListBuilder, + _txn: &mut Transaction, + _device_size: DeviceIntSize, + pipeline_id: PipelineId, + _document_id: DocumentId, + ) { + let root_space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id); + builder.push_simple_stacking_context( + LayoutPoint::zero(), + root_space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + if true { + // scrolling and clips stuff + // let's make a scrollbox + let scrollbox = (0, 0).to(300, 400); + builder.push_simple_stacking_context( + LayoutPoint::new(10., 10.), + root_space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + // set the scrolling clip + let space_and_clip1 = builder.define_scroll_frame( + &root_space_and_clip, + None, + (0, 0).by(1000, 1000), + scrollbox, + ScrollSensitivity::ScriptAndInputEvents, + LayoutVector2D::zero(), + ); + + // now put some content into it. + // start with a white background + let mut info = CommonItemProperties::new((0, 0).to(1000, 1000), space_and_clip1); + info.hit_info = Some((0, 1)); + builder.push_rect(&info, info.clip_rect, ColorF::new(1.0, 1.0, 1.0, 1.0)); + + // let's make a 50x50 blue square as a visual reference + let mut info = CommonItemProperties::new((0, 0).to(50, 50), space_and_clip1); + info.hit_info = Some((0, 2)); + builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 0.0, 1.0, 1.0)); + + // and a 50x50 green square next to it with an offset clip + // to see what that looks like + let mut info = CommonItemProperties::new( + (50, 0).to(100, 50).intersection(&(60, 10).to(110, 60)).unwrap(), + space_and_clip1, + ); + info.hit_info = Some((0, 3)); + builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 0.0, 1.0)); + + // Below the above rectangles, set up a nested scrollbox. It's still in + // the same stacking context, so note that the rects passed in need to + // be relative to the stacking context. + let space_and_clip2 = builder.define_scroll_frame( + &space_and_clip1, + None, + (0, 100).to(300, 1000), + (0, 100).to(200, 300), + ScrollSensitivity::ScriptAndInputEvents, + LayoutVector2D::zero(), + ); + + // give it a giant gray background just to distinguish it and to easily + // visually identify the nested scrollbox + let mut info = CommonItemProperties::new( + (-1000, -1000).to(5000, 5000), + space_and_clip2, + ); + info.hit_info = Some((0, 4)); + builder.push_rect(&info, info.clip_rect, ColorF::new(0.5, 0.5, 0.5, 1.0)); + + // add a teal square to visualize the scrolling/clipping behaviour + // as you scroll the nested scrollbox + let mut info = CommonItemProperties::new((0, 200).to(50, 250), space_and_clip2); + info.hit_info = Some((0, 5)); + builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 1.0, 1.0)); + + // Add a sticky frame. It will "stick" twice while scrolling, once + // at a margin of 10px from the bottom, for 40 pixels of scrolling, + // and once at a margin of 10px from the top, for 60 pixels of + // scrolling. + let sticky_id = builder.define_sticky_frame( + space_and_clip2.spatial_id, + (50, 350).by(50, 50), + SideOffsets2D::new(Some(10.0), None, Some(10.0), None), + StickyOffsetBounds::new(-40.0, 60.0), + StickyOffsetBounds::new(0.0, 0.0), + LayoutVector2D::new(0.0, 0.0) + ); + + let mut info = CommonItemProperties::new( + (50, 350).by(50, 50), + SpaceAndClipInfo { + spatial_id: sticky_id, + clip_id: space_and_clip2.clip_id, + }, + ); + info.hit_info = Some((0, 6)); + builder.push_rect( + &info, + info.clip_rect, + ColorF::new(0.5, 0.5, 1.0, 1.0), + ); + + // just for good measure add another teal square further down and to + // the right, which can be scrolled into view by the user + let mut info = CommonItemProperties::new( + (250, 350).to(300, 400), + space_and_clip2, + ); + info.hit_info = Some((0, 7)); + builder.push_rect(&info, info.clip_rect, ColorF::new(0.0, 1.0, 1.0, 1.0)); + + builder.pop_stacking_context(); + } + + builder.pop_stacking_context(); + } + + fn on_event(&mut self, event: winit::WindowEvent, api: &mut RenderApi, document_id: DocumentId) -> bool { + let mut txn = Transaction::new(); + match event { + winit::WindowEvent::KeyboardInput { + input: winit::KeyboardInput { + state: winit::ElementState::Pressed, + virtual_keycode: Some(key), + .. + }, + .. + } => { + let offset = match key { + winit::VirtualKeyCode::Down => Some((0.0, -10.0)), + winit::VirtualKeyCode::Up => Some((0.0, 10.0)), + winit::VirtualKeyCode::Right => Some((-10.0, 0.0)), + winit::VirtualKeyCode::Left => Some((10.0, 0.0)), + _ => None, + }; + let zoom = match key { + winit::VirtualKeyCode::Key0 => Some(1.0), + winit::VirtualKeyCode::Minus => Some(0.8), + winit::VirtualKeyCode::Equals => Some(1.25), + _ => None, + }; + + if let Some(offset) = offset { + txn.scroll( + ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)), + self.cursor_position, + ); + txn.generate_frame(); + } + if let Some(zoom) = zoom { + txn.set_pinch_zoom(ZoomFactor::new(zoom)); + txn.generate_frame(); + } + } + winit::WindowEvent::CursorMoved { position: LogicalPosition { x, y }, .. } => { + self.cursor_position = WorldPoint::new(x as f32, y as f32); + } + winit::WindowEvent::MouseWheel { delta, .. } => { + const LINE_HEIGHT: f32 = 38.0; + let (dx, dy) = match delta { + winit::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT), + winit::MouseScrollDelta::PixelDelta(pos) => (pos.x as f32, pos.y as f32), + }; + + txn.scroll( + ScrollLocation::Delta(LayoutVector2D::new(dx, dy)), + self.cursor_position, + ); + txn.generate_frame(); + } + winit::WindowEvent::MouseInput { .. } => { + let results = api.hit_test( + document_id, + None, + self.cursor_position, + HitTestFlags::FIND_ALL + ); + + println!("Hit test results:"); + for item in &results.items { + println!(" • {:?}", item); + } + println!(""); + } + _ => (), + } + + api.send_transaction(document_id, txn); + + false + } +} + +fn main() { + let mut app = App { + cursor_position: WorldPoint::zero(), + }; + boilerplate::main_wrapper(&mut app, None); +} diff --git a/third_party/webrender/examples/texture_cache_stress.rs b/third_party/webrender/examples/texture_cache_stress.rs new file mode 100644 index 00000000000..d2e68183021 --- /dev/null +++ b/third_party/webrender/examples/texture_cache_stress.rs @@ -0,0 +1,322 @@ +/* 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/. */ + +extern crate gleam; +extern crate glutin; +extern crate webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; + +use crate::boilerplate::{Example, HandyDandyRectBuilder}; +use gleam::gl; +use std::mem; +use webrender::api::*; +use webrender::api::units::*; + + +struct ImageGenerator { + patterns: [[u8; 3]; 6], + next_pattern: usize, + current_image: Vec, +} + +impl ImageGenerator { + fn new() -> Self { + ImageGenerator { + next_pattern: 0, + patterns: [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1], + [1, 1, 0], + [0, 1, 1], + [1, 0, 1], + ], + current_image: Vec::new(), + } + } + + fn generate_image(&mut self, size: i32) { + let pattern = &self.patterns[self.next_pattern]; + self.current_image.clear(); + for y in 0 .. size { + for x in 0 .. size { + let lum = 255 * (1 - (((x & 8) == 0) ^ ((y & 8) == 0)) as u8); + self.current_image.extend_from_slice(&[ + lum * pattern[0], + lum * pattern[1], + lum * pattern[2], + 0xff, + ]); + } + } + + self.next_pattern = (self.next_pattern + 1) % self.patterns.len(); + } + + fn take(&mut self) -> Vec { + mem::replace(&mut self.current_image, Vec::new()) + } +} + +impl ExternalImageHandler for ImageGenerator { + fn lock( + &mut self, + _key: ExternalImageId, + channel_index: u8, + _rendering: ImageRendering + ) -> ExternalImage { + self.generate_image(channel_index as i32); + ExternalImage { + uv: TexelRect::new(0.0, 0.0, 1.0, 1.0), + source: ExternalImageSource::RawData(&self.current_image), + } + } + fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) {} +} + +struct App { + stress_keys: Vec, + image_key: Option, + image_generator: ImageGenerator, + swap_keys: Vec, + swap_index: usize, +} + +impl Example for App { + fn render( + &mut self, + api: &mut RenderApi, + builder: &mut DisplayListBuilder, + txn: &mut Transaction, + _device_size: DeviceIntSize, + pipeline_id: PipelineId, + _document_id: DocumentId, + ) { + let bounds = (0, 0).to(512, 512); + let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id); + + builder.push_simple_stacking_context( + bounds.origin, + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + let x0 = 50.0; + let y0 = 50.0; + let image_size = LayoutSize::new(4.0, 4.0); + + if self.swap_keys.is_empty() { + let key0 = api.generate_image_key(); + let key1 = api.generate_image_key(); + + self.image_generator.generate_image(128); + txn.add_image( + key0, + ImageDescriptor::new(128, 128, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::new(self.image_generator.take()), + None, + ); + + self.image_generator.generate_image(128); + txn.add_image( + key1, + ImageDescriptor::new(128, 128, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::new(self.image_generator.take()), + None, + ); + + self.swap_keys.push(key0); + self.swap_keys.push(key1); + } + + for (i, key) in self.stress_keys.iter().enumerate() { + let x = (i % 128) as f32; + let y = (i / 128) as f32; + let info = CommonItemProperties::new( + LayoutRect::new( + LayoutPoint::new(x0 + image_size.width * x, y0 + image_size.height * y), + image_size, + ), + space_and_clip, + ); + + builder.push_image( + &info, + bounds, + ImageRendering::Auto, + AlphaType::PremultipliedAlpha, + *key, + ColorF::WHITE, + ); + } + + if let Some(image_key) = self.image_key { + let image_size = LayoutSize::new(100.0, 100.0); + let info = CommonItemProperties::new( + LayoutRect::new(LayoutPoint::new(100.0, 100.0), image_size), + space_and_clip, + ); + builder.push_image( + &info, + bounds, + ImageRendering::Auto, + AlphaType::PremultipliedAlpha, + image_key, + ColorF::WHITE, + ); + } + + let swap_key = self.swap_keys[self.swap_index]; + let image_size = LayoutSize::new(64.0, 64.0); + let info = CommonItemProperties::new( + LayoutRect::new(LayoutPoint::new(100.0, 400.0), image_size), + space_and_clip, + ); + builder.push_image( + &info, + bounds, + ImageRendering::Auto, + AlphaType::PremultipliedAlpha, + swap_key, + ColorF::WHITE, + ); + self.swap_index = 1 - self.swap_index; + + builder.pop_stacking_context(); + } + + fn on_event( + &mut self, + event: winit::WindowEvent, + api: &mut RenderApi, + document_id: DocumentId, + ) -> bool { + match event { + winit::WindowEvent::KeyboardInput { + input: winit::KeyboardInput { + state: winit::ElementState::Pressed, + virtual_keycode: Some(key), + .. + }, + .. + } => { + let mut txn = Transaction::new(); + + match key { + winit::VirtualKeyCode::S => { + self.stress_keys.clear(); + + for _ in 0 .. 16 { + for _ in 0 .. 16 { + let size = 4; + + let image_key = api.generate_image_key(); + + self.image_generator.generate_image(size); + + txn.add_image( + image_key, + ImageDescriptor::new( + size, + size, + ImageFormat::BGRA8, + ImageDescriptorFlags::IS_OPAQUE, + ), + ImageData::new(self.image_generator.take()), + None, + ); + + self.stress_keys.push(image_key); + } + } + } + winit::VirtualKeyCode::D => if let Some(image_key) = self.image_key.take() { + txn.delete_image(image_key); + }, + winit::VirtualKeyCode::U => if let Some(image_key) = self.image_key { + let size = 128; + self.image_generator.generate_image(size); + + txn.update_image( + image_key, + ImageDescriptor::new(size, size, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::new(self.image_generator.take()), + &DirtyRect::All, + ); + }, + winit::VirtualKeyCode::E => { + if let Some(image_key) = self.image_key.take() { + txn.delete_image(image_key); + } + + let size = 32; + let image_key = api.generate_image_key(); + + let image_data = ExternalImageData { + id: ExternalImageId(0), + channel_index: size as u8, + image_type: ExternalImageType::Buffer, + }; + + txn.add_image( + image_key, + ImageDescriptor::new(size, size, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::External(image_data), + None, + ); + + self.image_key = Some(image_key); + } + winit::VirtualKeyCode::R => { + if let Some(image_key) = self.image_key.take() { + txn.delete_image(image_key); + } + + let image_key = api.generate_image_key(); + let size = 32; + self.image_generator.generate_image(size); + + txn.add_image( + image_key, + ImageDescriptor::new(size, size, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::new(self.image_generator.take()), + None, + ); + + self.image_key = Some(image_key); + } + _ => {} + } + + api.send_transaction(document_id, txn); + return true; + } + _ => {} + } + + false + } + + fn get_image_handlers( + &mut self, + _gl: &dyn gl::Gl, + ) -> (Option>, + Option>) { + (Some(Box::new(ImageGenerator::new())), None) + } +} + +fn main() { + let mut app = App { + image_key: None, + stress_keys: Vec::new(), + image_generator: ImageGenerator::new(), + swap_keys: Vec::new(), + swap_index: 0, + }; + boilerplate::main_wrapper(&mut app, None); +} diff --git a/third_party/webrender/examples/yuv.rs b/third_party/webrender/examples/yuv.rs new file mode 100644 index 00000000000..a8f36b33d7e --- /dev/null +++ b/third_party/webrender/examples/yuv.rs @@ -0,0 +1,226 @@ +/* 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/. */ + +extern crate gleam; +extern crate glutin; +extern crate webrender; +extern crate winit; + +#[path = "common/boilerplate.rs"] +mod boilerplate; + +use crate::boilerplate::Example; +use gleam::gl; +use webrender::api::*; +use webrender::api::units::*; + + +fn init_gl_texture( + id: gl::GLuint, + internal: gl::GLenum, + external: gl::GLenum, + bytes: &[u8], + gl: &dyn gl::Gl, +) { + gl.bind_texture(gl::TEXTURE_2D, id); + gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as gl::GLint); + gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as gl::GLint); + gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as gl::GLint); + gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as gl::GLint); + gl.tex_image_2d( + gl::TEXTURE_2D, + 0, + internal as gl::GLint, + 100, + 100, + 0, + external, + gl::UNSIGNED_BYTE, + Some(bytes), + ); + gl.bind_texture(gl::TEXTURE_2D, 0); +} + +struct YuvImageProvider { + texture_ids: Vec, +} + +impl YuvImageProvider { + fn new(gl: &dyn gl::Gl) -> Self { + let texture_ids = gl.gen_textures(4); + + init_gl_texture(texture_ids[0], gl::RED, gl::RED, &[127; 100 * 100], gl); + init_gl_texture(texture_ids[1], gl::RG8, gl::RG, &[0; 100 * 100 * 2], gl); + init_gl_texture(texture_ids[2], gl::RED, gl::RED, &[127; 100 * 100], gl); + init_gl_texture(texture_ids[3], gl::RED, gl::RED, &[127; 100 * 100], gl); + + YuvImageProvider { + texture_ids + } + } +} + +impl ExternalImageHandler for YuvImageProvider { + fn lock( + &mut self, + key: ExternalImageId, + _channel_index: u8, + _rendering: ImageRendering + ) -> ExternalImage { + let id = self.texture_ids[key.0 as usize]; + ExternalImage { + uv: TexelRect::new(0.0, 0.0, 1.0, 1.0), + source: ExternalImageSource::NativeTexture(id), + } + } + fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) { + } +} + +struct App { + texture_id: gl::GLuint, + current_value: u8, +} + +impl Example for App { + fn render( + &mut self, + api: &mut RenderApi, + builder: &mut DisplayListBuilder, + txn: &mut Transaction, + _device_size: DeviceIntSize, + pipeline_id: PipelineId, + _document_id: DocumentId, + ) { + let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size()); + let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id); + + builder.push_simple_stacking_context( + bounds.origin, + space_and_clip.spatial_id, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + + let yuv_chanel1 = api.generate_image_key(); + let yuv_chanel2 = api.generate_image_key(); + let yuv_chanel2_1 = api.generate_image_key(); + let yuv_chanel3 = api.generate_image_key(); + txn.add_image( + yuv_chanel1, + ImageDescriptor::new(100, 100, ImageFormat::R8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::External(ExternalImageData { + id: ExternalImageId(0), + channel_index: 0, + image_type: ExternalImageType::TextureHandle( + TextureTarget::Default, + ), + }), + None, + ); + txn.add_image( + yuv_chanel2, + ImageDescriptor::new(100, 100, ImageFormat::RG8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::External(ExternalImageData { + id: ExternalImageId(1), + channel_index: 0, + image_type: ExternalImageType::TextureHandle( + TextureTarget::Default, + ), + }), + None, + ); + txn.add_image( + yuv_chanel2_1, + ImageDescriptor::new(100, 100, ImageFormat::R8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::External(ExternalImageData { + id: ExternalImageId(2), + channel_index: 0, + image_type: ExternalImageType::TextureHandle( + TextureTarget::Default, + ), + }), + None, + ); + txn.add_image( + yuv_chanel3, + ImageDescriptor::new(100, 100, ImageFormat::R8, ImageDescriptorFlags::IS_OPAQUE), + ImageData::External(ExternalImageData { + id: ExternalImageId(3), + channel_index: 0, + image_type: ExternalImageType::TextureHandle( + TextureTarget::Default, + ), + }), + None, + ); + + let info = CommonItemProperties::new( + LayoutRect::new(LayoutPoint::new(100.0, 0.0), LayoutSize::new(100.0, 100.0)), + space_and_clip, + ); + builder.push_yuv_image( + &info, + bounds, + YuvData::NV12(yuv_chanel1, yuv_chanel2), + ColorDepth::Color8, + YuvColorSpace::Rec601, + ColorRange::Limited, + ImageRendering::Auto, + ); + + let info = CommonItemProperties::new( + LayoutRect::new(LayoutPoint::new(300.0, 0.0), LayoutSize::new(100.0, 100.0)), + space_and_clip, + ); + builder.push_yuv_image( + &info, + bounds, + YuvData::PlanarYCbCr(yuv_chanel1, yuv_chanel2_1, yuv_chanel3), + ColorDepth::Color8, + YuvColorSpace::Rec601, + ColorRange::Limited, + ImageRendering::Auto, + ); + + builder.pop_stacking_context(); + } + + fn on_event( + &mut self, + _event: winit::WindowEvent, + _api: &mut RenderApi, + _document_id: DocumentId, + ) -> bool { + false + } + + fn get_image_handlers( + &mut self, + gl: &dyn gl::Gl, + ) -> (Option>, + Option>) { + let provider = YuvImageProvider::new(gl); + self.texture_id = provider.texture_ids[0]; + (Some(Box::new(provider)), None) + } + + fn draw_custom(&mut self, gl: &dyn gl::Gl) { + init_gl_texture(self.texture_id, gl::RED, gl::RED, &[self.current_value; 100 * 100], gl); + self.current_value = self.current_value.wrapping_add(1); + } +} + +fn main() { + let mut app = App { + texture_id: 0, + current_value: 0, + }; + + let opts = webrender::RendererOptions { + debug_flags: webrender::DebugFlags::NEW_FRAME_INDICATOR | webrender::DebugFlags::NEW_SCENE_INDICATOR, + ..Default::default() + }; + + boilerplate::main_wrapper(&mut app, Some(opts)); +} diff --git a/third_party/webrender/glsl-to-cxx/Cargo.toml b/third_party/webrender/glsl-to-cxx/Cargo.toml new file mode 100644 index 00000000000..1ff4298b541 --- /dev/null +++ b/third_party/webrender/glsl-to-cxx/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "glsl-to-cxx" +version = "0.1.0" +license = "MPL-2.0" +authors = ["The Mozilla Project Developers"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +glsl = "4.0" diff --git a/third_party/webrender/glsl-to-cxx/README.md b/third_party/webrender/glsl-to-cxx/README.md new file mode 100644 index 00000000000..1b5b4845d27 --- /dev/null +++ b/third_party/webrender/glsl-to-cxx/README.md @@ -0,0 +1,3 @@ +A GLSL to C++ translator. + +Translates GLSL to vectorized C++. Intended for use with WebRender software backend. diff --git a/third_party/webrender/glsl-to-cxx/src/hir.rs b/third_party/webrender/glsl-to-cxx/src/hir.rs new file mode 100644 index 00000000000..710b50b7af2 --- /dev/null +++ b/third_party/webrender/glsl-to-cxx/src/hir.rs @@ -0,0 +1,3856 @@ +/* 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 glsl::syntax; +use glsl::syntax::{ArrayedIdentifier, ArraySpecifier, AssignmentOp, BinaryOp, Identifier}; +use glsl::syntax::{NonEmpty, PrecisionQualifier, StructFieldSpecifier, StructSpecifier}; +use glsl::syntax::{TypeSpecifier, TypeSpecifierNonArray, UnaryOp}; +use std::cell::{Cell, Ref, RefCell}; +use std::collections::HashMap; +use std::iter::FromIterator; +use std::mem; +use std::ops::{Deref, DerefMut}; +use std::rc::Rc; + +trait LiftFrom { + fn lift(state: &mut State, s: S) -> Self; +} + +fn lift>(state: &mut State, s: S) -> T { + LiftFrom::lift(state, s) +} + +#[derive(Debug)] +pub struct Symbol { + pub name: String, + pub decl: SymDecl, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct FunctionSignature { + ret: Type, + params: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct FunctionType { + signatures: NonEmpty, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum SamplerFormat { + Unknown, + RGBA8, + RGBA32F, + RGBA32I, + R8, +} + +impl SamplerFormat { + pub fn type_suffix(self) -> Option<&'static str> { + match self { + SamplerFormat::Unknown => None, + SamplerFormat::RGBA8 => Some("RGBA8"), + SamplerFormat::RGBA32F => Some("RGBA32F"), + SamplerFormat::RGBA32I => Some("RGBA32I"), + SamplerFormat::R8 => Some("R8"), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum StorageClass { + None, + Const, + In, + Out, + Uniform, + Sampler(SamplerFormat), + FragColor(i32), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ArraySizes { + pub sizes: Vec, +} + +impl LiftFrom<&ArraySpecifier> for ArraySizes { + fn lift(state: &mut State, a: &ArraySpecifier) -> Self { + ArraySizes { + sizes: vec![match a { + ArraySpecifier::Unsized => panic!(), + ArraySpecifier::ExplicitlySized(expr) => translate_expression(state, expr), + }], + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum TypeKind { + Void, + Bool, + Int, + UInt, + Float, + Double, + Vec2, + Vec3, + Vec4, + DVec2, + DVec3, + DVec4, + BVec2, + BVec3, + BVec4, + IVec2, + IVec3, + IVec4, + UVec2, + UVec3, + UVec4, + Mat2, + Mat3, + Mat4, + Mat23, + Mat24, + Mat32, + Mat34, + Mat42, + Mat43, + DMat2, + DMat3, + DMat4, + DMat23, + DMat24, + DMat32, + DMat34, + DMat42, + DMat43, + // floating point opaque types + Sampler1D, + Image1D, + Sampler2D, + Image2D, + Sampler3D, + Image3D, + SamplerCube, + ImageCube, + Sampler2DRect, + Image2DRect, + Sampler1DArray, + Image1DArray, + Sampler2DArray, + Image2DArray, + SamplerBuffer, + ImageBuffer, + Sampler2DMS, + Image2DMS, + Sampler2DMSArray, + Image2DMSArray, + SamplerCubeArray, + ImageCubeArray, + Sampler1DShadow, + Sampler2DShadow, + Sampler2DRectShadow, + Sampler1DArrayShadow, + Sampler2DArrayShadow, + SamplerCubeShadow, + SamplerCubeArrayShadow, + // signed integer opaque types + ISampler1D, + IImage1D, + ISampler2D, + IImage2D, + ISampler3D, + IImage3D, + ISamplerCube, + IImageCube, + ISampler2DRect, + IImage2DRect, + ISampler1DArray, + IImage1DArray, + ISampler2DArray, + IImage2DArray, + ISamplerBuffer, + IImageBuffer, + ISampler2DMS, + IImage2DMS, + ISampler2DMSArray, + IImage2DMSArray, + ISamplerCubeArray, + IImageCubeArray, + // unsigned integer opaque types + AtomicUInt, + USampler1D, + UImage1D, + USampler2D, + UImage2D, + USampler3D, + UImage3D, + USamplerCube, + UImageCube, + USampler2DRect, + UImage2DRect, + USampler1DArray, + UImage1DArray, + USampler2DArray, + UImage2DArray, + USamplerBuffer, + UImageBuffer, + USampler2DMS, + UImage2DMS, + USampler2DMSArray, + UImage2DMSArray, + USamplerCubeArray, + UImageCubeArray, + Struct(SymRef), +} + +impl TypeKind { + pub fn is_sampler(&self) -> bool { + use TypeKind::*; + match self { + Sampler1D + | Image1D + | Sampler2D + | Image2D + | Sampler3D + | Image3D + | SamplerCube + | ImageCube + | Sampler2DRect + | Image2DRect + | Sampler1DArray + | Image1DArray + | Sampler2DArray + | Image2DArray + | SamplerBuffer + | ImageBuffer + | Sampler2DMS + | Image2DMS + | Sampler2DMSArray + | Image2DMSArray + | SamplerCubeArray + | ImageCubeArray + | Sampler1DShadow + | Sampler2DShadow + | Sampler2DRectShadow + | Sampler1DArrayShadow + | Sampler2DArrayShadow + | SamplerCubeShadow + | SamplerCubeArrayShadow + | ISampler1D + | IImage1D + | ISampler2D + | IImage2D + | ISampler3D + | IImage3D + | ISamplerCube + | IImageCube + | ISampler2DRect + | IImage2DRect + | ISampler1DArray + | IImage1DArray + | ISampler2DArray + | IImage2DArray + | ISamplerBuffer + | IImageBuffer + | ISampler2DMS + | IImage2DMS + | ISampler2DMSArray + | IImage2DMSArray + | ISamplerCubeArray + | IImageCubeArray + | USampler1D + | UImage1D + | USampler2D + | UImage2D + | USampler3D + | UImage3D + | USamplerCube + | UImageCube + | USampler2DRect + | UImage2DRect + | USampler1DArray + | UImage1DArray + | USampler2DArray + | UImage2DArray + | USamplerBuffer + | UImageBuffer + | USampler2DMS + | UImage2DMS + | USampler2DMSArray + | UImage2DMSArray + | USamplerCubeArray + | UImageCubeArray => true, + _ => false, + } + } + + pub fn is_bool(&self) -> bool { + use TypeKind::*; + match self { + Bool | BVec2 | BVec3 | BVec4 => true, + _ => false, + } + } + + pub fn to_bool(&self) -> Self { + use TypeKind::*; + match self { + Int | UInt | Float | Double => Bool, + IVec2 | UVec2 | Vec2 | DVec2 => BVec2, + IVec3 | UVec3 | Vec3 | DVec3 => BVec3, + IVec4 | UVec4 | Vec4 | DVec4 => BVec4, + _ => *self, + } + } + + pub fn to_int(&self) -> Self { + use TypeKind::*; + match self { + Bool | UInt | Float | Double => Int, + BVec2 | UVec2 | Vec2 | DVec2 => IVec2, + BVec3 | UVec3 | Vec3 | DVec3 => IVec3, + BVec4 | UVec4 | Vec4 | DVec4 => IVec4, + _ => *self, + } + } + + pub fn to_scalar(&self) -> Self { + use TypeKind::*; + match self { + IVec2 | IVec3 | IVec4 => Int, + UVec2 | UVec3 | UVec4 => UInt, + Vec2 | Vec3 | Vec4 => Float, + DVec2 | DVec3 | DVec4 => Double, + BVec2 | BVec3 | BVec4 => Bool, + _ => *self, + } + } + + pub fn glsl_primitive_type_name(&self) -> Option<&'static str> { + use TypeKind::*; + Some(match self { + Void => "void", + Bool => "bool", + Int => "int", + UInt => "uint", + Float => "float", + Double => "double", + Vec2 => "vec2", + Vec3 => "vec3", + Vec4 => "vec4", + DVec2 => "dvec2", + DVec3 => "dvec3", + DVec4 => "dvec4", + BVec2 => "bvec2", + BVec3 => "bvec3", + BVec4 => "bvec4", + IVec2 => "ivec2", + IVec3 => "ivec3", + IVec4 => "ivec4", + UVec2 => "uvec2", + UVec3 => "uvec3", + UVec4 => "uvec4", + Mat2 => "mat2", + Mat3 => "mat3", + Mat4 => "mat4", + Mat23 => "mat23", + Mat24 => "mat24", + Mat32 => "mat32", + Mat34 => "mat34", + Mat42 => "mat42", + Mat43 => "mat43", + DMat2 => "dmat2", + DMat3 => "dmat3", + DMat4 => "dmat4", + DMat23 => "dmat23", + DMat24 => "dmat24", + DMat32 => "dmat32", + DMat34 => "dmat34", + DMat42 => "dmat42", + DMat43 => "dmat43", + Sampler1D => "sampler1D", + Image1D => "image1D", + Sampler2D => "sampler2D", + Image2D => "image2D", + Sampler3D => "sampler3D", + Image3D => "image3D", + SamplerCube => "samplerCube", + ImageCube => "imageCube", + Sampler2DRect => "sampler2DRect", + Image2DRect => "image2DRect", + Sampler1DArray => "sampler1DArray", + Image1DArray => "image1DArray", + Sampler2DArray => "sampler2DArray", + Image2DArray => "image2DArray", + SamplerBuffer => "samplerBuffer", + ImageBuffer => "imageBuffer", + Sampler2DMS => "sampler2DMS", + Image2DMS => "image2DMS", + Sampler2DMSArray => "sampler2DMSArray", + Image2DMSArray => "image2DMSArray", + SamplerCubeArray => "samplerCubeArray", + ImageCubeArray => "imageCubeArray", + Sampler1DShadow => "sampler1DShadow", + Sampler2DShadow => "sampler2DShadow", + Sampler2DRectShadow => "sampler2DRectShadow", + Sampler1DArrayShadow => "sampler1DArrayShadow", + Sampler2DArrayShadow => "sampler2DArrayShadow", + SamplerCubeShadow => "samplerCubeShadow", + SamplerCubeArrayShadow => "samplerCubeArrayShadow", + ISampler1D => "isampler1D", + IImage1D => "iimage1D", + ISampler2D => "isampler2D", + IImage2D => "iimage2D", + ISampler3D => "isampler3D", + IImage3D => "iimage3D", + ISamplerCube => "isamplerCube", + IImageCube => "iimageCube", + ISampler2DRect => "isampler2DRect", + IImage2DRect => "iimage2DRect", + ISampler1DArray => "isampler1DArray", + IImage1DArray => "iimage1DArray", + ISampler2DArray => "isampler2DArray", + IImage2DArray => "iimage2DArray", + ISamplerBuffer => "isamplerBuffer", + IImageBuffer => "iimageBuffer", + ISampler2DMS => "isampler2MS", + IImage2DMS => "iimage2DMS", + ISampler2DMSArray => "isampler2DMSArray", + IImage2DMSArray => "iimage2DMSArray", + ISamplerCubeArray => "isamplerCubeArray", + IImageCubeArray => "iimageCubeArray", + AtomicUInt => "atomic_uint", + USampler1D => "usampler1D", + UImage1D => "uimage1D", + USampler2D => "usampler2D", + UImage2D => "uimage2D", + USampler3D => "usampler3D", + UImage3D => "uimage3D", + USamplerCube => "usamplerCube", + UImageCube => "uimageCube", + USampler2DRect => "usampler2DRect", + UImage2DRect => "uimage2DRect", + USampler1DArray => "usampler1DArray", + UImage1DArray => "uimage1DArray", + USampler2DArray => "usampler2DArray", + UImage2DArray => "uimage2DArray", + USamplerBuffer => "usamplerBuffer", + UImageBuffer => "uimageBuffer", + USampler2DMS => "usampler2DMS", + UImage2DMS => "uimage2DMS", + USampler2DMSArray => "usamplerDMSArray", + UImage2DMSArray => "uimage2DMSArray", + USamplerCubeArray => "usamplerCubeArray", + UImageCubeArray => "uimageCubeArray", + Struct(..) => return None, + }) + } + + pub fn cxx_primitive_type_name(&self) -> Option<&'static str> { + use TypeKind::*; + match self { + Bool => Some("Bool"), + Int => Some("I32"), + UInt => Some("U32"), + Float => Some("Float"), + Double => Some("Double"), + _ => self.glsl_primitive_type_name(), + } + } + + pub fn cxx_primitive_scalar_type_name(&self) -> Option<&'static str> { + use TypeKind::*; + match self { + Void => Some("void"), + Bool => Some("bool"), + Int => Some("int32_t"), + UInt => Some("uint32_t"), + Float => Some("float"), + Double => Some("double"), + _ => { + if self.is_sampler() { + self.cxx_primitive_type_name() + } else { + None + } + } + } + } + + pub fn from_glsl_primitive_type_name(name: &str) -> Option { + use TypeKind::*; + Some(match name { + "void" => Void, + "bool" => Bool, + "int" => Int, + "uint" => UInt, + "float" => Float, + "double" => Double, + "vec2" => Vec2, + "vec3" => Vec3, + "vec4" => Vec4, + "dvec2" => DVec2, + "dvec3" => DVec3, + "dvec4" => DVec4, + "bvec2" => BVec2, + "bvec3" => BVec3, + "bvec4" => BVec4, + "ivec2" => IVec2, + "ivec3" => IVec3, + "ivec4" => IVec4, + "uvec2" => UVec2, + "uvec3" => UVec3, + "uvec4" => UVec4, + "mat2" => Mat2, + "mat3" => Mat3, + "mat4" => Mat4, + "mat23" => Mat23, + "mat24" => Mat24, + "mat32" => Mat32, + "mat34" => Mat34, + "mat42" => Mat42, + "mat43" => Mat43, + "dmat2" => DMat2, + "dmat3" => DMat3, + "dmat4" => DMat4, + "dmat23" => DMat23, + "dmat24" => DMat24, + "dmat32" => DMat32, + "dmat34" => DMat34, + "dmat42" => DMat42, + "dmat43" => DMat43, + "sampler1D" => Sampler1D, + "image1D" => Image1D, + "sampler2D" => Sampler2D, + "image2D" => Image2D, + "sampler3D" => Sampler3D, + "image3D" => Image3D, + "samplerCube" => SamplerCube, + "imageCube" => ImageCube, + "sampler2DRect" => Sampler2DRect, + "image2DRect" => Image2DRect, + "sampler1DArray" => Sampler1DArray, + "image1DArray" => Image1DArray, + "sampler2DArray" => Sampler2DArray, + "image2DArray" => Image2DArray, + "samplerBuffer" => SamplerBuffer, + "imageBuffer" => ImageBuffer, + "sampler2DMS" => Sampler2DMS, + "image2DMS" => Image2DMS, + "sampler2DMSArray" => Sampler2DMSArray, + "image2DMSArray" => Image2DMSArray, + "samplerCubeArray" => SamplerCubeArray, + "imageCubeArray" => ImageCubeArray, + "sampler1DShadow" => Sampler1DShadow, + "sampler2DShadow" => Sampler2DShadow, + "sampler2DRectShadow" => Sampler2DRectShadow, + "sampler1DArrayShadow" => Sampler1DArrayShadow, + "sampler2DArrayShadow" => Sampler2DArrayShadow, + "samplerCubeShadow" => SamplerCubeShadow, + "samplerCubeArrayShadow" => SamplerCubeArrayShadow, + "isampler1D" => ISampler1D, + "iimage1D" => IImage1D, + "isampler2D" => ISampler2D, + "iimage2D" => IImage2D, + "isampler3D" => ISampler3D, + "iimage3D" => IImage3D, + "isamplerCube" => ISamplerCube, + "iimageCube" => IImageCube, + "isampler2DRect" => ISampler2DRect, + "iimage2DRect" => IImage2DRect, + "isampler1DArray" => ISampler1DArray, + "iimage1DArray" => IImage1DArray, + "isampler2DArray" => ISampler2DArray, + "iimage2DArray" => IImage2DArray, + "isamplerBuffer" => ISamplerBuffer, + "iimageBuffer" => IImageBuffer, + "isampler2MS" => ISampler2DMS, + "iimage2DMS" => IImage2DMS, + "isampler2DMSArray" => ISampler2DMSArray, + "iimage2DMSArray" => IImage2DMSArray, + "isamplerCubeArray" => ISamplerCubeArray, + "iimageCubeArray" => IImageCubeArray, + "atomic_uint" => AtomicUInt, + "usampler1D" => USampler1D, + "uimage1D" => UImage1D, + "usampler2D" => USampler2D, + "uimage2D" => UImage2D, + "usampler3D" => USampler3D, + "uimage3D" => UImage3D, + "usamplerCube" => USamplerCube, + "uimageCube" => UImageCube, + "usampler2DRect" => USampler2DRect, + "uimage2DRect" => UImage2DRect, + "usampler1DArray" => USampler1DArray, + "uimage1DArray" => UImage1DArray, + "usampler2DArray" => USampler2DArray, + "uimage2DArray" => UImage2DArray, + "usamplerBuffer" => USamplerBuffer, + "uimageBuffer" => UImageBuffer, + "usampler2DMS" => USampler2DMS, + "uimage2DMS" => UImage2DMS, + "usamplerDMSArray" => USampler2DMSArray, + "uimage2DMSArray" => UImage2DMSArray, + "usamplerCubeArray" => USamplerCubeArray, + "uimageCubeArray" => UImageCubeArray, + _ => return None, + }) + } + + pub fn from_primitive_type_specifier(spec: &syntax::TypeSpecifierNonArray) -> Option { + use TypeKind::*; + Some(match spec { + TypeSpecifierNonArray::Void => Void, + TypeSpecifierNonArray::Bool => Bool, + TypeSpecifierNonArray::Int => Int, + TypeSpecifierNonArray::UInt => UInt, + TypeSpecifierNonArray::Float => Float, + TypeSpecifierNonArray::Double => Double, + TypeSpecifierNonArray::Vec2 => Vec2, + TypeSpecifierNonArray::Vec3 => Vec3, + TypeSpecifierNonArray::Vec4 => Vec4, + TypeSpecifierNonArray::DVec2 => DVec2, + TypeSpecifierNonArray::DVec3 => DVec3, + TypeSpecifierNonArray::DVec4 => DVec4, + TypeSpecifierNonArray::BVec2 => BVec2, + TypeSpecifierNonArray::BVec3 => BVec3, + TypeSpecifierNonArray::BVec4 => BVec4, + TypeSpecifierNonArray::IVec2 => IVec2, + TypeSpecifierNonArray::IVec3 => IVec3, + TypeSpecifierNonArray::IVec4 => IVec4, + TypeSpecifierNonArray::UVec2 => UVec2, + TypeSpecifierNonArray::UVec3 => UVec3, + TypeSpecifierNonArray::UVec4 => UVec4, + TypeSpecifierNonArray::Mat2 => Mat2, + TypeSpecifierNonArray::Mat3 => Mat3, + TypeSpecifierNonArray::Mat4 => Mat4, + TypeSpecifierNonArray::Mat23 => Mat23, + TypeSpecifierNonArray::Mat24 => Mat24, + TypeSpecifierNonArray::Mat32 => Mat32, + TypeSpecifierNonArray::Mat34 => Mat34, + TypeSpecifierNonArray::Mat42 => Mat42, + TypeSpecifierNonArray::Mat43 => Mat43, + TypeSpecifierNonArray::DMat2 => DMat2, + TypeSpecifierNonArray::DMat3 => DMat3, + TypeSpecifierNonArray::DMat4 => DMat4, + TypeSpecifierNonArray::DMat23 => DMat23, + TypeSpecifierNonArray::DMat24 => DMat24, + TypeSpecifierNonArray::DMat32 => DMat32, + TypeSpecifierNonArray::DMat34 => DMat34, + TypeSpecifierNonArray::DMat42 => DMat42, + TypeSpecifierNonArray::DMat43 => DMat43, + TypeSpecifierNonArray::Sampler1D => Sampler1D, + TypeSpecifierNonArray::Image1D => Image1D, + TypeSpecifierNonArray::Sampler2D => Sampler2D, + TypeSpecifierNonArray::Image2D => Image2D, + TypeSpecifierNonArray::Sampler3D => Sampler3D, + TypeSpecifierNonArray::Image3D => Image3D, + TypeSpecifierNonArray::SamplerCube => SamplerCube, + TypeSpecifierNonArray::ImageCube => ImageCube, + TypeSpecifierNonArray::Sampler2DRect => Sampler2DRect, + TypeSpecifierNonArray::Image2DRect => Image2DRect, + TypeSpecifierNonArray::Sampler1DArray => Sampler1DArray, + TypeSpecifierNonArray::Image1DArray => Image1DArray, + TypeSpecifierNonArray::Sampler2DArray => Sampler2DArray, + TypeSpecifierNonArray::Image2DArray => Image2DArray, + TypeSpecifierNonArray::SamplerBuffer => SamplerBuffer, + TypeSpecifierNonArray::ImageBuffer => ImageBuffer, + TypeSpecifierNonArray::Sampler2DMS => Sampler2DMS, + TypeSpecifierNonArray::Image2DMS => Image2DMS, + TypeSpecifierNonArray::Sampler2DMSArray => Sampler2DMSArray, + TypeSpecifierNonArray::Image2DMSArray => Image2DMSArray, + TypeSpecifierNonArray::SamplerCubeArray => SamplerCubeArray, + TypeSpecifierNonArray::ImageCubeArray => ImageCubeArray, + TypeSpecifierNonArray::Sampler1DShadow => Sampler1DShadow, + TypeSpecifierNonArray::Sampler2DShadow => Sampler2DShadow, + TypeSpecifierNonArray::Sampler2DRectShadow => Sampler2DRectShadow, + TypeSpecifierNonArray::Sampler1DArrayShadow => Sampler1DArrayShadow, + TypeSpecifierNonArray::Sampler2DArrayShadow => Sampler2DArrayShadow, + TypeSpecifierNonArray::SamplerCubeShadow => SamplerCubeShadow, + TypeSpecifierNonArray::SamplerCubeArrayShadow => SamplerCubeArrayShadow, + TypeSpecifierNonArray::ISampler1D => ISampler1D, + TypeSpecifierNonArray::IImage1D => IImage1D, + TypeSpecifierNonArray::ISampler2D => ISampler2D, + TypeSpecifierNonArray::IImage2D => IImage2D, + TypeSpecifierNonArray::ISampler3D => ISampler3D, + TypeSpecifierNonArray::IImage3D => IImage3D, + TypeSpecifierNonArray::ISamplerCube => ISamplerCube, + TypeSpecifierNonArray::IImageCube => IImageCube, + TypeSpecifierNonArray::ISampler2DRect => ISampler2DRect, + TypeSpecifierNonArray::IImage2DRect => IImage2DRect, + TypeSpecifierNonArray::ISampler1DArray => ISampler1DArray, + TypeSpecifierNonArray::IImage1DArray => IImage1DArray, + TypeSpecifierNonArray::ISampler2DArray => ISampler2DArray, + TypeSpecifierNonArray::IImage2DArray => IImage2DArray, + TypeSpecifierNonArray::ISamplerBuffer => ISamplerBuffer, + TypeSpecifierNonArray::IImageBuffer => IImageBuffer, + TypeSpecifierNonArray::ISampler2DMS => ISampler2DMS, + TypeSpecifierNonArray::IImage2DMS => IImage2DMS, + TypeSpecifierNonArray::ISampler2DMSArray => ISampler2DMSArray, + TypeSpecifierNonArray::IImage2DMSArray => IImage2DMSArray, + TypeSpecifierNonArray::ISamplerCubeArray => ISamplerCubeArray, + TypeSpecifierNonArray::IImageCubeArray => IImageCubeArray, + TypeSpecifierNonArray::AtomicUInt => AtomicUInt, + TypeSpecifierNonArray::USampler1D => USampler1D, + TypeSpecifierNonArray::UImage1D => UImage1D, + TypeSpecifierNonArray::USampler2D => USampler2D, + TypeSpecifierNonArray::UImage2D => UImage2D, + TypeSpecifierNonArray::USampler3D => USampler3D, + TypeSpecifierNonArray::UImage3D => UImage3D, + TypeSpecifierNonArray::USamplerCube => USamplerCube, + TypeSpecifierNonArray::UImageCube => UImageCube, + TypeSpecifierNonArray::USampler2DRect => USampler2DRect, + TypeSpecifierNonArray::UImage2DRect => UImage2DRect, + TypeSpecifierNonArray::USampler1DArray => USampler1DArray, + TypeSpecifierNonArray::UImage1DArray => UImage1DArray, + TypeSpecifierNonArray::USampler2DArray => USampler2DArray, + TypeSpecifierNonArray::UImage2DArray => UImage2DArray, + TypeSpecifierNonArray::USamplerBuffer => USamplerBuffer, + TypeSpecifierNonArray::UImageBuffer => UImageBuffer, + TypeSpecifierNonArray::USampler2DMS => USampler2DMS, + TypeSpecifierNonArray::UImage2DMS => UImage2DMS, + TypeSpecifierNonArray::USampler2DMSArray => USampler2DMSArray, + TypeSpecifierNonArray::UImage2DMSArray => UImage2DMSArray, + TypeSpecifierNonArray::USamplerCubeArray => USamplerCubeArray, + TypeSpecifierNonArray::UImageCubeArray => UImageCubeArray, + TypeSpecifierNonArray::Struct(..) | TypeSpecifierNonArray::TypeName(..) => return None, + }) + } +} + +impl LiftFrom<&syntax::TypeSpecifierNonArray> for TypeKind { + fn lift(state: &mut State, spec: &syntax::TypeSpecifierNonArray) -> Self { + use TypeKind::*; + if let Some(kind) = TypeKind::from_primitive_type_specifier(spec) { + kind + } else { + match spec { + TypeSpecifierNonArray::Struct(s) => { + Struct(state.lookup(s.name.as_ref().unwrap().as_str()).unwrap()) + } + TypeSpecifierNonArray::TypeName(s) => Struct(state.lookup(&s.0).unwrap()), + _ => unreachable!(), + } + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Type { + pub kind: TypeKind, + pub precision: Option, + pub array_sizes: Option>, +} + +impl Type { + pub fn new(kind: TypeKind) -> Self { + Type { + kind, + precision: None, + array_sizes: None, + } + } +} + +impl LiftFrom<&syntax::FullySpecifiedType> for Type { + fn lift(state: &mut State, ty: &syntax::FullySpecifiedType) -> Self { + let kind = lift(state, &ty.ty.ty); + let array_sizes = match ty.ty.array_specifier.as_ref() { + Some(x) => Some(Box::new(lift(state, x))), + None => None, + }; + let precision = get_precision(&ty.qualifier); + Type { + kind, + precision, + array_sizes, + } + } +} + +impl LiftFrom<&syntax::TypeSpecifier> for Type { + fn lift(state: &mut State, ty: &syntax::TypeSpecifier) -> Self { + let kind = lift(state, &ty.ty); + let array_sizes = ty + .array_specifier + .as_ref() + .map(|x| Box::new(lift(state, x))); + Type { + kind, + precision: None, + array_sizes, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct StructField { + pub ty: Type, + pub name: syntax::Identifier, +} + +fn get_precision(qualifiers: &Option) -> Option { + let mut precision = None; + for qual in qualifiers.iter().flat_map(|x| x.qualifiers.0.iter()) { + match qual { + syntax::TypeQualifierSpec::Precision(p) => { + if precision.is_some() { + panic!("Multiple precisions"); + } + precision = Some(p.clone()); + } + _ => {} + } + } + precision +} + +impl LiftFrom<&StructFieldSpecifier> for StructField { + fn lift(state: &mut State, f: &StructFieldSpecifier) -> Self { + let mut ty: Type = lift(state, &f.ty); + match &f.identifiers.0[..] { + [ident] => { + if let Some(a) = &ident.array_spec { + ty.array_sizes = Some(Box::new(lift(state, a))); + } + StructField { + ty, + name: ident.ident.clone(), + } + } + _ => panic!("bad number of identifiers"), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct StructFields { + pub fields: Vec, +} + +impl LiftFrom<&StructSpecifier> for StructFields { + fn lift(state: &mut State, s: &StructSpecifier) -> Self { + let fields = s.fields.0.iter().map(|field| lift(state, field)).collect(); + Self { fields } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum RunClass { + Unknown, + Scalar, + Vector, + Dependent(u32), +} + +impl RunClass { + pub fn merge(self, run_class: RunClass) -> RunClass { + match (self, run_class) { + (RunClass::Vector, _) | (_, RunClass::Vector) => RunClass::Vector, + (RunClass::Dependent(x), RunClass::Dependent(y)) => RunClass::Dependent(x | y), + (RunClass::Unknown, _) | (_, RunClass::Dependent(..)) => run_class, + _ => self, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum SymDecl { + NativeFunction(FunctionType, Option<&'static str>), + UserFunction(Rc, RunClass), + Local(StorageClass, Type, RunClass), + Global( + StorageClass, + Option, + Type, + RunClass, + ), + Struct(StructFields), +} + +#[derive(Clone, Debug, PartialEq, Copy, Eq, Hash)] +pub struct SymRef(u32); + +#[derive(Debug)] +struct Scope { + name: String, + names: HashMap, +} +impl Scope { + fn new(name: String) -> Self { + Scope { + name, + names: HashMap::new(), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct TexelFetchOffsets { + pub min_x: i32, + pub max_x: i32, + pub min_y: i32, + pub max_y: i32, +} + +impl TexelFetchOffsets { + fn new(x: i32, y: i32) -> Self { + TexelFetchOffsets { + min_x: x, + max_x: x, + min_y: y, + max_y: y, + } + } + + fn add_offset(&mut self, x: i32, y: i32) { + self.min_x = self.min_x.min(x); + self.max_x = self.max_x.max(x); + self.min_y = self.min_y.min(y); + self.max_y = self.max_y.max(y); + } +} + +#[derive(Debug)] +pub struct State { + scopes: Vec, + syms: Vec>, + in_function: Option, + run_class_changed: Cell, + last_declaration: SymRef, + branch_run_class: RunClass, + branch_declaration: SymRef, + modified_globals: RefCell>, + pub used_globals: RefCell>, + pub texel_fetches: HashMap<(SymRef, SymRef), TexelFetchOffsets>, +} + +impl State { + pub fn new() -> Self { + State { + scopes: Vec::new(), + syms: Vec::new(), + in_function: None, + run_class_changed: Cell::new(false), + last_declaration: SymRef(0), + branch_run_class: RunClass::Unknown, + branch_declaration: SymRef(0), + modified_globals: RefCell::new(Vec::new()), + used_globals: RefCell::new(Vec::new()), + texel_fetches: HashMap::new(), + } + } + + pub fn lookup(&self, name: &str) -> Option { + for s in self.scopes.iter().rev() { + if let Some(sym) = s.names.get(name) { + return Some(*sym); + } + } + return None; + } + + fn declare(&mut self, name: &str, decl: SymDecl) -> SymRef { + let s = SymRef(self.syms.len() as u32); + self.syms.push(RefCell::new(Symbol { + name: name.into(), + decl, + })); + self.scopes.last_mut().unwrap().names.insert(name.into(), s); + s + } + + pub fn sym(&self, sym: SymRef) -> Ref { + self.syms[sym.0 as usize].borrow() + } + + pub fn sym_mut(&mut self, sym: SymRef) -> &mut Symbol { + self.syms[sym.0 as usize].get_mut() + } + + pub fn lookup_sym_mut(&mut self, name: &str) -> Option<&mut Symbol> { + self.lookup(name) + .map(move |x| self.syms[x.0 as usize].get_mut()) + } + + fn push_scope(&mut self, name: String) { + self.scopes.push(Scope::new(name)); + } + fn pop_scope(&mut self) { + self.scopes.pop(); + } + + fn return_run_class(&self, mut new_run_class: RunClass) { + new_run_class = self.branch_run_class.merge(new_run_class); + if let Some(sym) = self.in_function { + let mut b = self.syms[sym.0 as usize].borrow_mut(); + if let SymDecl::UserFunction(_, ref mut run_class) = b.decl { + *run_class = run_class.merge(new_run_class); + } + } + } + + pub fn function_definition(&self, name: SymRef) -> Option<(Rc, RunClass)> { + if let SymDecl::UserFunction(ref fd, ref run_class) = &self.sym(name).decl { + Some((fd.clone(), *run_class)) + } else { + None + } + } + + fn merge_run_class(&self, sym: SymRef, mut new_run_class: RunClass) -> RunClass { + if sym.0 <= self.branch_declaration.0 { + new_run_class = self.branch_run_class.merge(new_run_class); + } + let mut b = self.syms[sym.0 as usize].borrow_mut(); + let mut old_run_class = new_run_class; + if let SymDecl::Local(_, _, ref mut run_class) = b.decl { + old_run_class = *run_class; + new_run_class = old_run_class.merge(new_run_class); + *run_class = new_run_class; + } + if old_run_class != RunClass::Unknown && old_run_class != new_run_class { + self.run_class_changed.set(true); + } + new_run_class + } +} + +/// A declaration. +#[derive(Clone, Debug, PartialEq)] +pub enum Declaration { + FunctionPrototype(FunctionPrototype), + StructDefinition(SymRef), + InitDeclaratorList(InitDeclaratorList), + Precision(PrecisionQualifier, TypeSpecifier), + Block(Block), + Global(TypeQualifier, Vec), +} + +/// A general purpose block, containing fields and possibly a list of declared identifiers. Semantic +/// is given with the storage qualifier. +#[derive(Clone, Debug, PartialEq)] +pub struct Block { + pub qualifier: TypeQualifier, + pub name: Identifier, + pub fields: Vec, + pub identifier: Option, +} + +/// Function identifier. +#[derive(Clone, Debug, PartialEq)] +pub enum FunIdentifier { + Identifier(SymRef), + Constructor(Type), +} + +/// Function prototype. +#[derive(Clone, Debug, PartialEq)] +pub struct FunctionPrototype { + pub ty: Type, + pub name: Identifier, + pub parameters: Vec, +} + +impl FunctionPrototype { + pub fn has_parameter(&self, sym: SymRef) -> bool { + for param in &self.parameters { + match param { + FunctionParameterDeclaration::Named(_, ref d) => { + if d.sym == sym { + return true; + } + } + _ => {} + } + } + false + } +} + +/// Function parameter declaration. +#[derive(Clone, Debug, PartialEq)] +pub enum FunctionParameterDeclaration { + Named(Option, FunctionParameterDeclarator), + Unnamed(Option, TypeSpecifier), +} + +/// Function parameter declarator. +#[derive(Clone, Debug, PartialEq)] +pub struct FunctionParameterDeclarator { + pub ty: Type, + pub name: Identifier, + pub sym: SymRef, +} + +/// Init declarator list. +#[derive(Clone, Debug, PartialEq)] +pub struct InitDeclaratorList { + // XXX it feels like separating out the type and the names is better than + // head and tail + // Also, it might be nice to separate out type definitions from name definitions + pub head: SingleDeclaration, + pub tail: Vec, +} + +/// Type qualifier. +#[derive(Clone, Debug, PartialEq)] +pub struct TypeQualifier { + pub qualifiers: NonEmpty, +} + +fn lift_type_qualifier_for_declaration( + _state: &mut State, + q: &Option, +) -> Option { + q.as_ref().and_then(|x| { + NonEmpty::from_non_empty_iter(x.qualifiers.0.iter().flat_map(|x| match x { + syntax::TypeQualifierSpec::Precision(_) => None, + syntax::TypeQualifierSpec::Interpolation(_) => None, + syntax::TypeQualifierSpec::Invariant => Some(TypeQualifierSpec::Invariant), + syntax::TypeQualifierSpec::Layout(l) => Some(TypeQualifierSpec::Layout(l.clone())), + syntax::TypeQualifierSpec::Precise => Some(TypeQualifierSpec::Precise), + syntax::TypeQualifierSpec::Storage(_) => None, + })) + .map(|x| TypeQualifier { qualifiers: x }) + }) +} + +fn lift_type_qualifier_for_parameter( + _state: &mut State, + q: &Option, +) -> Option { + let mut qp: Option = None; + if let Some(q) = q { + for x in &q.qualifiers.0 { + match (&qp, x) { + (None, syntax::TypeQualifierSpec::Storage(s)) => match s { + syntax::StorageQualifier::Const => qp = Some(ParameterQualifier::Const), + syntax::StorageQualifier::In => qp = Some(ParameterQualifier::In), + syntax::StorageQualifier::Out => qp = Some(ParameterQualifier::Out), + syntax::StorageQualifier::InOut => qp = Some(ParameterQualifier::InOut), + _ => panic!("Bad storage qualifier for parameter"), + }, + (_, syntax::TypeQualifierSpec::Precision(_)) => {} + _ => panic!("Bad parameter qualifier {:?}", x), + } + } + } + qp +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ParameterQualifier { + Const, + In, + InOut, + Out, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum MemoryQualifier { + Coherent, + Volatile, + Restrict, + ReadOnly, + WriteOnly, +} + +/// Type qualifier spec. +#[derive(Clone, Debug, PartialEq)] +pub enum TypeQualifierSpec { + Layout(syntax::LayoutQualifier), + Invariant, + Parameter(ParameterQualifier), + Memory(MemoryQualifier), + Precise, +} + +/// Single declaration. +#[derive(Clone, Debug, PartialEq)] +pub struct SingleDeclaration { + pub ty: Type, + pub ty_def: Option, + pub qualifier: Option, + pub name: SymRef, + pub initializer: Option, +} + +/// A single declaration with implicit, already-defined type. +#[derive(Clone, Debug, PartialEq)] +pub struct SingleDeclarationNoType { + pub ident: ArrayedIdentifier, + pub initializer: Option, +} + +/// Initializer. +#[derive(Clone, Debug, PartialEq)] +pub enum Initializer { + Simple(Box), + List(NonEmpty), +} + +impl From for Initializer { + fn from(e: Expr) -> Self { + Initializer::Simple(Box::new(e)) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Expr { + pub kind: ExprKind, + pub ty: Type, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum FieldSet { + Rgba, + Xyzw, + Stpq, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SwizzleSelector { + pub field_set: FieldSet, + pub components: Vec, +} + +impl SwizzleSelector { + fn parse(s: &str) -> Self { + let mut components = Vec::new(); + let mut field_set = Vec::new(); + + for c in s.chars() { + match c { + 'r' => { + components.push(0); + field_set.push(FieldSet::Rgba); + } + 'x' => { + components.push(0); + field_set.push(FieldSet::Xyzw); + } + 's' => { + components.push(0); + field_set.push(FieldSet::Stpq); + } + + 'g' => { + components.push(1); + field_set.push(FieldSet::Rgba); + } + 'y' => { + components.push(1); + field_set.push(FieldSet::Xyzw); + } + 't' => { + components.push(1); + field_set.push(FieldSet::Stpq); + } + + 'b' => { + components.push(2); + field_set.push(FieldSet::Rgba); + } + 'z' => { + components.push(2); + field_set.push(FieldSet::Xyzw); + } + 'p' => { + components.push(2); + field_set.push(FieldSet::Stpq); + } + + 'a' => { + components.push(3); + field_set.push(FieldSet::Rgba); + } + 'w' => { + components.push(3); + field_set.push(FieldSet::Xyzw); + } + 'q' => { + components.push(3); + field_set.push(FieldSet::Stpq); + } + _ => panic!("bad selector"), + } + } + + let first = &field_set[0]; + assert!(field_set.iter().all(|item| item == first)); + assert!(components.len() <= 4); + SwizzleSelector { + field_set: first.clone(), + components, + } + } + + pub fn to_string(&self) -> String { + let mut s = String::new(); + let fs = match self.field_set { + FieldSet::Rgba => ['r', 'g', 'b', 'a'], + FieldSet::Xyzw => ['x', 'y', 'z', 'w'], + FieldSet::Stpq => ['s', 't', 'p', 'q'], + }; + for i in &self.components { + s.push(fs[*i as usize]) + } + s + } +} + +/// The most general form of an expression. As you can see if you read the variant list, in GLSL, an +/// assignment is an expression. This is a bit silly but think of an assignment as a statement first +/// then an expression which evaluates to what the statement “returns”. +/// +/// An expression is either an assignment or a list (comma) of assignments. +#[derive(Clone, Debug, PartialEq)] +pub enum ExprKind { + /// A variable expression, using an identifier. + Variable(SymRef), + /// Integral constant expression. + IntConst(i32), + /// Unsigned integral constant expression. + UIntConst(u32), + /// Boolean constant expression. + BoolConst(bool), + /// Single precision floating expression. + FloatConst(f32), + /// Double precision floating expression. + DoubleConst(f64), + /// A unary expression, gathering a single expression and a unary operator. + Unary(UnaryOp, Box), + /// A binary expression, gathering two expressions and a binary operator. + Binary(BinaryOp, Box, Box), + /// A ternary conditional expression, gathering three expressions. + Ternary(Box, Box, Box), + /// An assignment is also an expression. Gathers an expression that defines what to assign to, an + /// assignment operator and the value to associate with. + Assignment(Box, AssignmentOp, Box), + /// Add an array specifier to an expression. + Bracket(Box, Box), + /// A functional call. It has a function identifier and a list of expressions (arguments). + FunCall(FunIdentifier, Vec), + /// An expression associated with a field selection (struct). + Dot(Box, Identifier), + /// An expression associated with a component selection + SwizzleSelector(Box, SwizzleSelector), + /// Post-incrementation of an expression. + PostInc(Box), + /// Post-decrementation of an expression. + PostDec(Box), + /// An expression that contains several, separated with comma. + Comma(Box, Box), + /// A temporary condition variable + Cond(usize, Box), + CondMask, +} + +/* +impl From for Expr { + fn from(x: i32) -> Expr { + ExprKind::IntConst(x) + } +} + +impl From for Expr { + fn from(x: u32) -> Expr { + Expr::UIntConst(x) + } +} + +impl From for Expr { + fn from(x: bool) -> Expr { + Expr::BoolConst(x) + } +} + +impl From for Expr { + fn from(x: f32) -> Expr { + Expr::FloatConst(x) + } +} + +impl From for Expr { + fn from(x: f64) -> Expr { + Expr::DoubleConst(x) + } +} +*/ +/// Starting rule. +#[derive(Clone, Debug, PartialEq)] +pub struct TranslationUnit(pub NonEmpty); + +impl TranslationUnit { + /// Construct a translation unit from an iterator. + /// + /// # Errors + /// + /// `None` if the iterator yields no value. + pub fn from_iter(iter: I) -> Option + where + I: IntoIterator, + { + NonEmpty::from_non_empty_iter(iter).map(TranslationUnit) + } +} + +impl Deref for TranslationUnit { + type Target = NonEmpty; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for TranslationUnit { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl IntoIterator for TranslationUnit { + type IntoIter = as IntoIterator>::IntoIter; + type Item = ExternalDeclaration; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a> IntoIterator for &'a TranslationUnit { + type IntoIter = <&'a NonEmpty as IntoIterator>::IntoIter; + type Item = &'a ExternalDeclaration; + + fn into_iter(self) -> Self::IntoIter { + (&self.0).into_iter() + } +} + +impl<'a> IntoIterator for &'a mut TranslationUnit { + type IntoIter = <&'a mut NonEmpty as IntoIterator>::IntoIter; + type Item = &'a mut ExternalDeclaration; + + fn into_iter(self) -> Self::IntoIter { + (&mut self.0).into_iter() + } +} + +/// External declaration. +#[derive(Clone, Debug, PartialEq)] +pub enum ExternalDeclaration { + Preprocessor(syntax::Preprocessor), + FunctionDefinition(Rc), + Declaration(Declaration), +} + +/// Function definition. +#[derive(Clone, Debug, PartialEq)] +pub struct FunctionDefinition { + pub prototype: FunctionPrototype, + pub body: CompoundStatement, + pub globals: Vec, + pub texel_fetches: HashMap<(SymRef, SymRef), TexelFetchOffsets>, +} + +/// Compound statement (with no new scope). +#[derive(Clone, Debug, PartialEq)] +pub struct CompoundStatement { + pub statement_list: Vec, +} + +impl CompoundStatement { + pub fn new() -> Self { + CompoundStatement { + statement_list: Vec::new(), + } + } +} + +impl FromIterator for CompoundStatement { + fn from_iter(iter: T) -> Self + where + T: IntoIterator, + { + CompoundStatement { + statement_list: iter.into_iter().collect(), + } + } +} + +/// Statement. +#[derive(Clone, Debug, PartialEq)] +pub enum Statement { + Compound(Box), + Simple(Box), +} + +/// Simple statement. +#[derive(Clone, Debug, PartialEq)] +pub enum SimpleStatement { + Declaration(Declaration), + Expression(ExprStatement), + Selection(SelectionStatement), + Switch(SwitchStatement), + Iteration(IterationStatement), + Jump(JumpStatement), +} + +impl SimpleStatement { + /// Create a new expression statement. + pub fn new_expr(expr: E) -> Self + where + E: Into, + { + SimpleStatement::Expression(Some(expr.into())) + } + + /// Create a new selection statement (if / else). + pub fn new_if_else(ife: If, truee: True, falsee: False) -> Self + where + If: Into, + True: Into, + False: Into, + { + SimpleStatement::Selection(SelectionStatement { + cond: Box::new(ife.into()), + body: Box::new(truee.into()), + else_stmt: Some(Box::new(falsee.into())), + }) + } + + /// Create a new while statement. + pub fn new_while(cond: C, body: S) -> Self + where + C: Into, + S: Into, + { + SimpleStatement::Iteration(IterationStatement::While( + cond.into(), + Box::new(body.into()), + )) + } + + /// Create a new do-while statement. + pub fn new_do_while(body: S, cond: C) -> Self + where + S: Into, + C: Into, + { + SimpleStatement::Iteration(IterationStatement::DoWhile( + Box::new(body.into()), + Box::new(cond.into()), + )) + } +} + +/// Expression statement. +pub type ExprStatement = Option; + +/// Selection statement. +#[derive(Clone, Debug, PartialEq)] +pub struct SelectionStatement { + pub cond: Box, + pub body: Box, + // the else branch + pub else_stmt: Option>, +} + +/// Condition. +#[derive(Clone, Debug, PartialEq)] +pub enum Condition { + Expr(Box), +} + +impl From for Condition { + fn from(expr: Expr) -> Self { + Condition::Expr(Box::new(expr)) + } +} + +/// Switch statement. +#[derive(Clone, Debug, PartialEq)] +pub struct SwitchStatement { + pub head: Box, + pub cases: Vec, +} + +/// Case label statement. +#[derive(Clone, Debug, PartialEq)] +pub enum CaseLabel { + Case(Box), + Def, +} + +/// An individual case +#[derive(Clone, Debug, PartialEq)] +pub struct Case { + pub label: CaseLabel, + pub stmts: Vec, +} + +/// Iteration statement. +#[derive(Clone, Debug, PartialEq)] +pub enum IterationStatement { + While(Condition, Box), + DoWhile(Box, Box), + For(ForInitStatement, ForRestStatement, Box), +} + +/// For init statement. +#[derive(Clone, Debug, PartialEq)] +pub enum ForInitStatement { + Expression(Option), + Declaration(Box), +} + +/// For init statement. +#[derive(Clone, Debug, PartialEq)] +pub struct ForRestStatement { + pub condition: Option, + pub post_expr: Option>, +} + +/// Jump statement. +#[derive(Clone, Debug, PartialEq)] +pub enum JumpStatement { + Continue, + Break, + Return(Option>), + Discard, +} + +trait NonEmptyExt { + fn map U>(&self, s: &mut State, f: F) -> NonEmpty; + fn new(x: T) -> NonEmpty; +} + +impl NonEmptyExt for NonEmpty { + fn map U>(&self, s: &mut State, mut f: F) -> NonEmpty { + NonEmpty::from_non_empty_iter(self.into_iter().map(|x| f(s, &x))).unwrap() + } + fn new(x: T) -> NonEmpty { + NonEmpty::from_non_empty_iter(vec![x].into_iter()).unwrap() + } +} + +fn translate_initializater(state: &mut State, i: &syntax::Initializer) -> Initializer { + match i { + syntax::Initializer::Simple(i) => { + Initializer::Simple(Box::new(translate_expression(state, i))) + } + _ => panic!(), + } +} + +fn translate_struct_declaration(state: &mut State, d: &syntax::SingleDeclaration) -> Declaration { + let ty = d.ty.clone(); + let ty_def = match &ty.ty.ty { + TypeSpecifierNonArray::Struct(s) => { + let decl = SymDecl::Struct(lift(state, s)); + Some(state.declare(s.name.as_ref().unwrap().as_str(), decl)) + } + _ => None, + }; + + let ty_def = ty_def.expect("Must be type definition"); + + Declaration::StructDefinition(ty_def) +} + +fn get_expr_index(e: &syntax::Expr) -> i32 { + match e { + syntax::Expr::IntConst(i) => *i, + syntax::Expr::UIntConst(u) => *u as i32, + syntax::Expr::FloatConst(f) => *f as i32, + syntax::Expr::DoubleConst(f) => *f as i32, + _ => panic!(), + } +} + +fn translate_variable_declaration( + state: &mut State, + d: &syntax::InitDeclaratorList, + default_run_class: RunClass, +) -> Declaration { + let mut ty = d.head.ty.clone(); + ty.ty.array_specifier = d.head.array_specifier.clone(); + let ty_def = match &ty.ty.ty { + TypeSpecifierNonArray::Struct(s) => { + let decl = SymDecl::Struct(lift(state, s)); + Some(state.declare(s.name.as_ref().unwrap().as_str(), decl)) + } + _ => None, + }; + + let mut ty: Type = lift(state, &d.head.ty); + if let Some(array) = &d.head.array_specifier { + ty.array_sizes = Some(Box::new(lift(state, array))) + } + + let (sym, decl) = match d.head.name.as_ref() { + Some(name) => { + let mut storage = StorageClass::None; + let mut interpolation = None; + for qual in d + .head + .ty + .qualifier + .iter() + .flat_map(|x| x.qualifiers.0.iter()) + { + match qual { + syntax::TypeQualifierSpec::Storage(s) => match (&storage, s) { + (StorageClass::FragColor(..), syntax::StorageQualifier::Out) => {} + (StorageClass::Sampler(..), syntax::StorageQualifier::Uniform) => {} + (StorageClass::None, syntax::StorageQualifier::Out) => { + storage = StorageClass::Out; + } + (StorageClass::None, syntax::StorageQualifier::In) => { + storage = StorageClass::In; + } + (StorageClass::None, syntax::StorageQualifier::Uniform) => { + if ty.kind.is_sampler() { + storage = StorageClass::Sampler(SamplerFormat::Unknown); + } else { + storage = StorageClass::Uniform; + } + } + (StorageClass::None, syntax::StorageQualifier::Const) => { + storage = StorageClass::Const; + } + _ => panic!("bad storage {:?}", (storage, s)), + }, + syntax::TypeQualifierSpec::Interpolation(i) => match (&interpolation, i) { + (None, i) => interpolation = Some(i.clone()), + _ => panic!("multiple interpolation"), + }, + syntax::TypeQualifierSpec::Layout(l) => { + let mut loc = -1; + let mut index = -1; + for id in &l.ids { + match id { + syntax::LayoutQualifierSpec::Identifier(ref key, None) => { + match key.as_str() { + "rgba8" => { + storage = StorageClass::Sampler(SamplerFormat::RGBA8); + } + "rgba32f" => { + storage = StorageClass::Sampler(SamplerFormat::RGBA32F); + } + "rgba32i" => { + storage = StorageClass::Sampler(SamplerFormat::RGBA32I); + } + "r8" => { + storage = StorageClass::Sampler(SamplerFormat::R8); + } + _ => {} + } + } + syntax::LayoutQualifierSpec::Identifier(ref key, Some(ref e)) => { + match key.as_str() { + "location" => { + loc = get_expr_index(e); + } + "index" => { + index = get_expr_index(e); + } + _ => {} + } + } + _ => {} + } + } + if index >= 0 { + assert!(loc == 0); + assert!(index <= 1); + assert!(storage == StorageClass::None); + storage = StorageClass::FragColor(index); + } + } + _ => {} + } + } + let decl = if state.in_function.is_some() { + let run_class = match storage { + StorageClass::Const => RunClass::Scalar, + StorageClass::None => default_run_class, + _ => panic!("bad local storage {:?}", storage), + }; + SymDecl::Local(storage, ty.clone(), run_class) + } else { + let run_class = match storage { + StorageClass::Const | StorageClass::Uniform | StorageClass::Sampler(..) => { + RunClass::Scalar + } + StorageClass::In | StorageClass::Out | StorageClass::FragColor(..) + if interpolation == Some(syntax::InterpolationQualifier::Flat) => + { + RunClass::Scalar + } + _ => RunClass::Vector, + }; + SymDecl::Global(storage, interpolation, ty.clone(), run_class) + }; + (state.declare(name.as_str(), decl.clone()), decl) + } + None => panic!(), + }; + + let head = SingleDeclaration { + qualifier: lift_type_qualifier_for_declaration(state, &d.head.ty.qualifier), + name: sym, + ty, + ty_def, + initializer: d + .head + .initializer + .as_ref() + .map(|x| translate_initializater(state, x)), + }; + + let tail = d + .tail + .iter() + .map(|d| { + if let Some(_array) = &d.ident.array_spec { + panic!("unhandled array") + } + state.declare(d.ident.ident.as_str(), decl.clone()); + SingleDeclarationNoType { + ident: d.ident.clone(), + initializer: d + .initializer + .as_ref() + .map(|x| translate_initializater(state, x)), + } + }) + .collect(); + Declaration::InitDeclaratorList(InitDeclaratorList { head, tail }) +} + +fn translate_init_declarator_list( + state: &mut State, + l: &syntax::InitDeclaratorList, + default_run_class: RunClass, +) -> Declaration { + match &l.head.name { + Some(_name) => translate_variable_declaration(state, l, default_run_class), + None => translate_struct_declaration(state, &l.head), + } +} + +fn translate_declaration( + state: &mut State, + d: &syntax::Declaration, + default_run_class: RunClass, +) -> Declaration { + match d { + syntax::Declaration::Block(_) => panic!(), //Declaration::Block(..), + syntax::Declaration::FunctionPrototype(p) => { + Declaration::FunctionPrototype(translate_function_prototype(state, p)) + } + syntax::Declaration::Global(_ty, _ids) => { + panic!(); + // glsl non-es supports requalifying variables + // we don't right now + //Declaration::Global(..) + } + syntax::Declaration::InitDeclaratorList(dl) => { + translate_init_declarator_list(state, dl, default_run_class) + } + syntax::Declaration::Precision(p, ts) => Declaration::Precision(p.clone(), ts.clone()), + } +} + +fn is_vector(ty: &Type) -> bool { + match ty.kind { + TypeKind::Vec2 + | TypeKind::Vec3 + | TypeKind::Vec4 + | TypeKind::IVec2 + | TypeKind::IVec3 + | TypeKind::IVec4 => ty.array_sizes == None, + _ => false, + } +} + +fn index_matrix(ty: &Type) -> Option { + use TypeKind::*; + if ty.array_sizes != None { + return None; + } + Some(match ty.kind { + Mat2 => Vec2, + Mat3 => Vec3, + Mat4 => Vec4, + Mat23 => Vec3, + Mat24 => Vec4, + Mat32 => Vec2, + Mat34 => Vec4, + Mat42 => Vec2, + Mat43 => Vec3, + DMat2 => DVec2, + DMat3 => DVec3, + DMat4 => DVec4, + DMat23 => DVec3, + DMat24 => DVec4, + DMat32 => DVec2, + DMat34 => DVec4, + DMat42 => DVec2, + DMat43 => DVec3, + _ => return None, + }) +} + +fn is_ivec(ty: &Type) -> bool { + match ty.kind { + TypeKind::IVec2 | TypeKind::IVec3 | TypeKind::IVec4 => ty.array_sizes == None, + _ => false, + } +} + +fn compatible_type(lhs: &Type, rhs: &Type) -> bool { + // XXX: use an underlying type helper + if lhs == &Type::new(TypeKind::Double) && rhs == &Type::new(TypeKind::Float) { + true + } else if rhs == &Type::new(TypeKind::Double) && lhs == &Type::new(TypeKind::Float) { + true + } else if rhs == &Type::new(TypeKind::Int) && + (lhs == &Type::new(TypeKind::Float) || lhs == &Type::new(TypeKind::Double)) + { + true + } else if (rhs == &Type::new(TypeKind::Float) || rhs == &Type::new(TypeKind::Double)) && + lhs == &Type::new(TypeKind::Int) + { + true + } else if (rhs == &Type::new(TypeKind::Vec2) || rhs == &Type::new(TypeKind::DVec2)) && + lhs == &Type::new(TypeKind::IVec2) + { + true + } else if rhs == &Type::new(TypeKind::IVec2) && + (lhs == &Type::new(TypeKind::Vec2) || lhs == &Type::new(TypeKind::DVec2)) + { + true + } else { + lhs.kind == rhs.kind && lhs.array_sizes == rhs.array_sizes + } +} + +fn promoted_type(lhs: &Type, rhs: &Type) -> Type { + if lhs == &Type::new(TypeKind::Double) && rhs == &Type::new(TypeKind::Float) { + Type::new(TypeKind::Double) + } else if lhs == &Type::new(TypeKind::Float) && rhs == &Type::new(TypeKind::Double) { + Type::new(TypeKind::Double) + } else if lhs == &Type::new(TypeKind::Int) && rhs == &Type::new(TypeKind::Double) { + Type::new(TypeKind::Double) + } else if is_vector(&lhs) && + (rhs == &Type::new(TypeKind::Float) || + rhs == &Type::new(TypeKind::Double) || + rhs == &Type::new(TypeKind::Int)) + { + // scalars promote to vectors + lhs.clone() + } else if is_vector(&rhs) && + (lhs == &Type::new(TypeKind::Float) || + lhs == &Type::new(TypeKind::Double) || + lhs == &Type::new(TypeKind::Int)) + { + // scalars promote to vectors + rhs.clone() + } else if lhs == rhs { + lhs.clone() + } else if lhs.kind == rhs.kind { + if lhs.array_sizes == rhs.array_sizes { + // XXX: we need to be able to query the default precision here + match (&lhs.precision, &rhs.precision) { + (Some(PrecisionQualifier::High), _) => lhs.clone(), + (_, Some(PrecisionQualifier::High)) => rhs.clone(), + (None, _) => lhs.clone(), + (_, None) => rhs.clone(), + _ => panic!("precision mismatch {:?} {:?}", lhs.precision, rhs.precision), + } + } else { + panic!("array size mismatch") + } + } else { + assert_eq!(lhs, rhs); + lhs.clone() + } +} + +pub fn is_output(expr: &Expr, state: &State) -> Option { + match &expr.kind { + ExprKind::Variable(i) => match state.sym(*i).decl { + SymDecl::Global(storage, ..) => match storage { + StorageClass::Out => return Some(*i), + _ => {} + }, + SymDecl::Local(..) => {} + _ => panic!("should be variable"), + }, + ExprKind::SwizzleSelector(e, ..) => { + return is_output(e, state); + } + ExprKind::Bracket(e, ..) => { + return is_output(e, state); + } + ExprKind::Dot(e, ..) => { + return is_output(e, state); + } + _ => {} + }; + None +} + +pub fn get_texel_fetch_offset( + state: &State, + sampler_expr: &Expr, + uv_expr: &Expr, + offset_expr: &Expr, +) -> Option<(SymRef, SymRef, i32, i32)> { + if let ExprKind::Variable(ref sampler) = &sampler_expr.kind { + //if let ExprKind::Binary(BinaryOp::Add, ref lhs, ref rhs) = &uv_expr.kind { + if let ExprKind::Variable(ref base) = &uv_expr.kind { + if let ExprKind::FunCall(ref fun, ref args) = &offset_expr.kind { + if let FunIdentifier::Identifier(ref offset) = fun { + if state.sym(*offset).name == "ivec2" { + if let ExprKind::IntConst(ref x) = &args[0].kind { + if let ExprKind::IntConst(ref y) = &args[1].kind { + return Some((*sampler, *base, *x, *y)); + } + } + } + } + } + } + //} + } + None +} + +fn make_const(t: TypeKind, v: i32) -> Expr { + Expr { + kind: match t { + TypeKind::Int => ExprKind::IntConst(v as _), + TypeKind::UInt => ExprKind::UIntConst(v as _), + TypeKind::Bool => ExprKind::BoolConst(v != 0), + TypeKind::Float => ExprKind::FloatConst(v as _), + TypeKind::Double => ExprKind::DoubleConst(v as _), + _ => panic!("bad constant type"), + }, + ty: Type::new(t), + } +} + +// Any parameters needing to convert to bool should just compare via != 0. +// This ensures they get the proper all-1s pattern for C++ OpenCL vectors. +fn force_params_to_bool(_state: &mut State, params: &mut Vec) { + for e in params { + if !e.ty.kind.is_bool() { + let k = e.ty.kind; + *e = Expr { + kind: ExprKind::Binary( + BinaryOp::NonEqual, + Box::new(e.clone()), + Box::new(make_const(k.to_scalar(), 0)), + ), + ty: Type::new(k.to_bool()), + }; + } + } +} + +// Transform bool params to int, then mask off the low bit so they become 0 or 1. +// C++ OpenCL vectors represent bool as all-1s patterns, which will erroneously +// convert to -1 otherwise. +fn force_params_from_bool(state: &mut State, params: &mut Vec) { + for e in params { + if e.ty.kind.is_bool() { + let k = e.ty.kind.to_int(); + let sym = state.lookup(k.glsl_primitive_type_name().unwrap()).unwrap(); + *e = Expr { + kind: ExprKind::Binary( + BinaryOp::BitAnd, + Box::new(Expr { + kind: ExprKind::FunCall( + FunIdentifier::Identifier(sym), + vec![e.clone()], + ), + ty: Type::new(k), + }), + Box::new(make_const(TypeKind::Int, 1)), + ), + ty: Type::new(k), + }; + } + } +} + +fn translate_expression(state: &mut State, e: &syntax::Expr) -> Expr { + match e { + syntax::Expr::Variable(i) => { + let sym = match state.lookup(i.as_str()) { + Some(sym) => sym, + None => panic!("missing declaration {}", i.as_str()), + }; + let ty = match &state.sym(sym).decl { + SymDecl::Global(_, _, ty, _) => { + let mut globals = state.used_globals.borrow_mut(); + if !globals.contains(&sym) { + globals.push(sym); + } + ty.clone() + } + SymDecl::Local(_, ty, _) => ty.clone(), + _ => panic!("bad variable type"), + }; + Expr { + kind: ExprKind::Variable(sym), + ty, + } + } + syntax::Expr::Assignment(lhs, op, rhs) => { + let lhs = Box::new(translate_expression(state, lhs)); + let rhs = Box::new(translate_expression(state, rhs)); + let ty = if op == &AssignmentOp::Mult { + if lhs.ty.kind == TypeKind::Vec4 && rhs.ty.kind == TypeKind::Float { + lhs.ty.clone() + } else { + promoted_type(&lhs.ty, &rhs.ty) + } + } else { + promoted_type(&lhs.ty, &rhs.ty) + }; + if let Some(global) = is_output(&lhs, state) { + let mut globals = state.modified_globals.borrow_mut(); + if !globals.contains(&global) { + globals.push(global); + } + } + Expr { + kind: ExprKind::Assignment(lhs, op.clone(), rhs), + ty, + } + } + syntax::Expr::Binary(op, lhs, rhs) => { + let lhs = Box::new(translate_expression(state, lhs)); + let rhs = Box::new(translate_expression(state, rhs)); + let ty = if op == &BinaryOp::Mult { + if lhs.ty.kind == TypeKind::Mat3 && rhs.ty.kind == TypeKind::Vec3 { + rhs.ty.clone() + } else if lhs.ty.kind == TypeKind::Mat4 && rhs.ty.kind == TypeKind::Vec4 { + rhs.ty.clone() + } else if lhs.ty.kind == TypeKind::Mat2 && rhs.ty.kind == TypeKind::Vec2 { + rhs.ty.clone() + } else if lhs.ty.kind == TypeKind::Mat2 && rhs.ty.kind == TypeKind::Float { + lhs.ty.clone() + } else { + promoted_type(&lhs.ty, &rhs.ty) + } + } else { + promoted_type(&lhs.ty, &rhs.ty) + }; + + // comparison operators have a bool result + let ty = match op { + BinaryOp::Equal | BinaryOp::GT | BinaryOp::GTE | BinaryOp::LT | BinaryOp::LTE => { + Type::new(TypeKind::Bool) + } + _ => ty, + }; + + Expr { + kind: ExprKind::Binary(op.clone(), lhs, rhs), + ty, + } + } + syntax::Expr::Unary(op, e) => { + let e = Box::new(translate_expression(state, e)); + let ty = e.ty.clone(); + Expr { + kind: ExprKind::Unary(op.clone(), e), + ty, + } + } + syntax::Expr::BoolConst(b) => Expr { + kind: ExprKind::BoolConst(*b), + ty: Type::new(TypeKind::Bool), + }, + syntax::Expr::Comma(lhs, rhs) => { + let lhs = Box::new(translate_expression(state, lhs)); + let rhs = Box::new(translate_expression(state, rhs)); + assert_eq!(lhs.ty, rhs.ty); + let ty = lhs.ty.clone(); + Expr { + kind: ExprKind::Comma(lhs, rhs), + ty, + } + } + syntax::Expr::DoubleConst(d) => Expr { + kind: ExprKind::DoubleConst(*d), + ty: Type::new(TypeKind::Double), + }, + syntax::Expr::FloatConst(f) => Expr { + kind: ExprKind::FloatConst(*f), + ty: Type::new(TypeKind::Float), + }, + syntax::Expr::FunCall(fun, params) => { + let ret_ty: Type; + let mut params: Vec = params + .iter() + .map(|x| translate_expression(state, x)) + .collect(); + Expr { + kind: ExprKind::FunCall( + match fun { + syntax::FunIdentifier::Identifier(i) => { + let name = i.as_str(); + if name == "texelFetchOffset" && params.len() >= 4 { + if let Some((sampler, base, x, y)) = get_texel_fetch_offset( + state, ¶ms[0], ¶ms[1], ¶ms[3], + ) { + if let Some(offsets) = + state.texel_fetches.get_mut(&(sampler, base)) + { + offsets.add_offset(x, y); + } else { + state + .texel_fetches + .insert((sampler, base), TexelFetchOffsets::new(x, y)); + } + } + } + let sym = match state.lookup(name) { + Some(s) => s, + None => panic!("missing symbol {}", name), + }; + // Force any boolean basic type constructors to generate correct + // bit patterns. + if let Some(t) = TypeKind::from_glsl_primitive_type_name(name) { + if t.is_bool() { + force_params_to_bool(state, &mut params); + } else { + force_params_from_bool(state, &mut params); + } + } + match &state.sym(sym).decl { + SymDecl::NativeFunction(fn_ty, _) => { + let mut ret = None; + for sig in &fn_ty.signatures { + let mut matching = true; + for (e, p) in params.iter().zip(sig.params.iter()) { + if !compatible_type(&e.ty, p) { + matching = false; + break; + } + } + if matching { + ret = Some(sig.ret.clone()); + break; + } + } + ret_ty = match ret { + Some(t) => t, + None => { + dbg!(&fn_ty.signatures); + dbg!(params.iter().map(|p| p).collect::>()); + panic!("no matching func {}", i.as_str()) + } + }; + } + SymDecl::UserFunction(fd, _) => { + let mut globals = state.modified_globals.borrow_mut(); + for global in &fd.globals { + if !globals.contains(global) { + globals.push(*global); + } + } + let mut matching = true; + for (e, p) in params.iter().zip(fd.prototype.parameters.iter()) + { + matching &= match p { + FunctionParameterDeclaration::Named(q, d) => { + match q { + Some(ParameterQualifier::InOut) + | Some(ParameterQualifier::Out) => { + if let Some(global) = is_output(e, state) { + if !globals.contains(&global) { + globals.push(global); + } + } + } + _ => {} + } + compatible_type(&e.ty, &d.ty) + } + FunctionParameterDeclaration::Unnamed(..) => panic!(), + }; + } + assert!(matching); + ret_ty = fd.prototype.ty.clone(); + } + SymDecl::Struct(_) => ret_ty = Type::new(TypeKind::Struct(sym)), + _ => panic!("can only call functions"), + }; + FunIdentifier::Identifier(sym) + } + // array constructor + syntax::FunIdentifier::Expr(e) => { + let ty = match &**e { + syntax::Expr::Bracket(i, array) => { + let kind = match &**i { + syntax::Expr::Variable(i) => match i.as_str() { + "vec4" => TypeKind::Vec4, + "vec2" => TypeKind::Vec2, + _ => panic!("unexpected type constructor {:?}", i), + }, + _ => panic!(), + }; + + Type { + kind, + precision: None, + array_sizes: Some(Box::new(lift(state, array))), + } + } + _ => panic!(), + }; + ret_ty = ty.clone(); + + FunIdentifier::Constructor(ty) + } + }, + params, + ), + ty: ret_ty, + } + } + syntax::Expr::IntConst(i) => Expr { + kind: ExprKind::IntConst(*i), + ty: Type::new(TypeKind::Int), + }, + syntax::Expr::UIntConst(u) => Expr { + kind: ExprKind::UIntConst(*u), + ty: Type::new(TypeKind::UInt), + }, + syntax::Expr::PostDec(e) => { + let e = Box::new(translate_expression(state, e)); + let ty = e.ty.clone(); + Expr { + kind: ExprKind::PostDec(e), + ty, + } + } + syntax::Expr::PostInc(e) => { + let e = Box::new(translate_expression(state, e)); + let ty = e.ty.clone(); + Expr { + kind: ExprKind::PostInc(e), + ty, + } + } + syntax::Expr::Ternary(cond, lhs, rhs) => { + let cond = Box::new(translate_expression(state, cond)); + let lhs = Box::new(translate_expression(state, lhs)); + let rhs = Box::new(translate_expression(state, rhs)); + let ty = promoted_type(&lhs.ty, &rhs.ty); + Expr { + kind: ExprKind::Ternary(cond, lhs, rhs), + ty, + } + } + syntax::Expr::Dot(e, i) => { + let e = Box::new(translate_expression(state, e)); + let ty = e.ty.clone(); + let ivec = is_ivec(&ty); + if is_vector(&ty) { + let ty = Type::new(match i.as_str().len() { + 1 => { + if ivec { + TypeKind::Int + } else { + TypeKind::Float + } + } + 2 => { + if ivec { + TypeKind::IVec2 + } else { + TypeKind::Vec2 + } + } + 3 => { + if ivec { + TypeKind::IVec3 + } else { + TypeKind::Vec3 + } + } + 4 => { + if ivec { + TypeKind::IVec4 + } else { + TypeKind::Vec4 + } + } + _ => panic!(), + }); + + let sel = SwizzleSelector::parse(i.as_str()); + + Expr { + kind: ExprKind::SwizzleSelector(e, sel), + ty, + } + } else { + match ty.kind { + TypeKind::Struct(s) => { + let sym = state.sym(s); + let fields = match &sym.decl { + SymDecl::Struct(fields) => fields, + _ => panic!("expected struct"), + }; + let field = fields + .fields + .iter() + .find(|x| &x.name == i) + .expect("missing field"); + Expr { + kind: ExprKind::Dot(e, i.clone()), + ty: field.ty.clone(), + } + } + _ => panic!("expected struct found {:#?} {:#?}", e, ty), + } + } + } + syntax::Expr::Bracket(e, specifier) => { + let e = Box::new(translate_expression(state, e)); + let ty = if is_vector(&e.ty) { + Type::new(TypeKind::Float) + } else if let Some(ty) = index_matrix(&e.ty) { + Type::new(ty) + } else { + let a = match &e.ty.array_sizes { + Some(a) => { + let mut a = *a.clone(); + a.sizes.pop(); + if a.sizes.len() == 0 { + None + } else { + Some(Box::new(a)) + } + } + _ => panic!("{:#?}", e), + }; + Type { + kind: e.ty.kind.clone(), + precision: e.ty.precision.clone(), + array_sizes: a, + } + }; + let indx = match specifier { + ArraySpecifier::Unsized => panic!("need expression"), + ArraySpecifier::ExplicitlySized(e) => translate_expression(state, e), + }; + Expr { + kind: ExprKind::Bracket(e, Box::new(indx)), + ty, + } + } + } +} + +fn translate_switch(state: &mut State, s: &syntax::SwitchStatement) -> SwitchStatement { + let mut cases = Vec::new(); + + let mut case = None; + for stmt in &s.body { + match stmt { + syntax::Statement::Simple(s) => match &**s { + syntax::SimpleStatement::CaseLabel(label) => { + match case.take() { + Some(case) => cases.push(case), + _ => {} + } + case = Some(Case { + label: translate_case(state, &label), + stmts: Vec::new(), + }) + } + _ => match case { + Some(ref mut case) => case.stmts.push(translate_statement(state, stmt)), + _ => panic!("switch must start with case"), + }, + }, + _ => match case { + Some(ref mut case) => case.stmts.push(translate_statement(state, stmt)), + _ => panic!("switch must start with case"), + }, + } + } + match case.take() { + Some(case) => cases.push(case), + _ => {} + } + SwitchStatement { + head: Box::new(translate_expression(state, &s.head)), + cases, + } +} + +fn translate_jump(state: &mut State, s: &syntax::JumpStatement) -> JumpStatement { + match s { + syntax::JumpStatement::Break => JumpStatement::Break, + syntax::JumpStatement::Continue => JumpStatement::Continue, + syntax::JumpStatement::Discard => JumpStatement::Discard, + syntax::JumpStatement::Return(e) => { + JumpStatement::Return(e.as_ref().map(|e| Box::new(translate_expression(state, e)))) + } + } +} + +fn translate_condition(state: &mut State, c: &syntax::Condition) -> Condition { + match c { + syntax::Condition::Expr(e) => Condition::Expr(Box::new(translate_expression(state, e))), + _ => panic!(), + } +} + +fn translate_for_init(state: &mut State, s: &syntax::ForInitStatement) -> ForInitStatement { + match s { + syntax::ForInitStatement::Expression(e) => { + ForInitStatement::Expression(e.as_ref().map(|e| translate_expression(state, e))) + } + syntax::ForInitStatement::Declaration(d) => ForInitStatement::Declaration(Box::new( + translate_declaration(state, d, RunClass::Scalar), + )), + } +} + +fn translate_for_rest(state: &mut State, s: &syntax::ForRestStatement) -> ForRestStatement { + ForRestStatement { + condition: s.condition.as_ref().map(|c| translate_condition(state, c)), + post_expr: s + .post_expr + .as_ref() + .map(|e| Box::new(translate_expression(state, e))), + } +} + +fn translate_iteration(state: &mut State, s: &syntax::IterationStatement) -> IterationStatement { + match s { + syntax::IterationStatement::While(cond, s) => IterationStatement::While( + translate_condition(state, cond), + Box::new(translate_statement(state, s)), + ), + syntax::IterationStatement::For(init, rest, s) => IterationStatement::For( + translate_for_init(state, init), + translate_for_rest(state, rest), + Box::new(translate_statement(state, s)), + ), + syntax::IterationStatement::DoWhile(s, e) => IterationStatement::DoWhile( + Box::new(translate_statement(state, s)), + Box::new(translate_expression(state, e)), + ), + } +} + +fn translate_case(state: &mut State, c: &syntax::CaseLabel) -> CaseLabel { + match c { + syntax::CaseLabel::Def => CaseLabel::Def, + syntax::CaseLabel::Case(e) => CaseLabel::Case(Box::new(translate_expression(state, e))), + } +} + +fn translate_selection_rest( + state: &mut State, + s: &syntax::SelectionRestStatement, +) -> (Box, Option>) { + match s { + syntax::SelectionRestStatement::Statement(s) => { + (Box::new(translate_statement(state, s)), None) + } + syntax::SelectionRestStatement::Else(if_body, rest) => ( + Box::new(translate_statement(state, if_body)), + Some(Box::new(translate_statement(state, rest))), + ), + } +} + +fn translate_selection(state: &mut State, s: &syntax::SelectionStatement) -> SelectionStatement { + let cond = Box::new(translate_expression(state, &s.cond)); + let (body, else_stmt) = translate_selection_rest(state, &s.rest); + SelectionStatement { + cond, + body, + else_stmt, + } +} + +fn translate_simple_statement(state: &mut State, s: &syntax::SimpleStatement) -> SimpleStatement { + match s { + syntax::SimpleStatement::Declaration(d) => { + SimpleStatement::Declaration(translate_declaration(state, d, RunClass::Unknown)) + } + syntax::SimpleStatement::Expression(e) => { + SimpleStatement::Expression(e.as_ref().map(|e| translate_expression(state, e))) + } + syntax::SimpleStatement::Iteration(i) => { + SimpleStatement::Iteration(translate_iteration(state, i)) + } + syntax::SimpleStatement::Selection(s) => { + SimpleStatement::Selection(translate_selection(state, s)) + } + syntax::SimpleStatement::Jump(j) => SimpleStatement::Jump(translate_jump(state, j)), + syntax::SimpleStatement::Switch(s) => SimpleStatement::Switch(translate_switch(state, s)), + syntax::SimpleStatement::CaseLabel(_) => panic!("should be handled by translate_switch"), + } +} + +fn translate_statement(state: &mut State, s: &syntax::Statement) -> Statement { + match s { + syntax::Statement::Compound(s) => { + Statement::Compound(Box::new(translate_compound_statement(state, s))) + } + syntax::Statement::Simple(s) => { + Statement::Simple(Box::new(translate_simple_statement(state, s))) + } + } +} + +fn translate_compound_statement( + state: &mut State, + cs: &syntax::CompoundStatement, +) -> CompoundStatement { + CompoundStatement { + statement_list: cs + .statement_list + .iter() + .map(|x| translate_statement(state, x)) + .collect(), + } +} + +fn translate_function_parameter_declaration( + state: &mut State, + p: &syntax::FunctionParameterDeclaration, + index: usize, +) -> FunctionParameterDeclaration { + match p { + syntax::FunctionParameterDeclaration::Named(qual, p) => { + let mut ty: Type = lift(state, &p.ty); + if let Some(a) = &p.ident.array_spec { + ty.array_sizes = Some(Box::new(lift(state, a))); + } + + ty.precision = get_precision(qual); + + let decl = SymDecl::Local( + StorageClass::None, + ty.clone(), + RunClass::Dependent(1 << index), + ); + let d = FunctionParameterDeclarator { + ty, + name: p.ident.ident.clone(), + sym: state.declare(p.ident.ident.as_str(), decl), + }; + FunctionParameterDeclaration::Named(lift_type_qualifier_for_parameter(state, qual), d) + } + syntax::FunctionParameterDeclaration::Unnamed(qual, p) => { + FunctionParameterDeclaration::Unnamed( + lift_type_qualifier_for_parameter(state, qual), + p.clone(), + ) + } + } +} + +fn translate_prototype( + state: &mut State, + cs: &syntax::FunctionPrototype, +) -> (FunctionPrototype, SymRef) { + let prototype = FunctionPrototype { + ty: lift(state, &cs.ty), + name: cs.name.clone(), + parameters: cs + .parameters + .iter() + .enumerate() + .map(|(i, x)| translate_function_parameter_declaration(state, x, i)) + .collect(), + }; + let sym = if let Some(sym) = state.lookup(prototype.name.as_str()) { + match &state.sym(sym).decl { + SymDecl::UserFunction(..) => {} + _ => panic!( + "prototype conflicts with existing symbol: {}", + prototype.name.as_str() + ), + } + sym + } else { + let pfd = Rc::new(FunctionDefinition { + prototype: prototype.clone(), + body: CompoundStatement::new(), + globals: Vec::new(), + texel_fetches: HashMap::new(), + }); + state.declare( + prototype.name.as_str(), + SymDecl::UserFunction(pfd, RunClass::Unknown), + ) + }; + (prototype, sym) +} + +fn translate_function_prototype( + state: &mut State, + prototype: &syntax::FunctionPrototype, +) -> FunctionPrototype { + let (prototype, _) = translate_prototype(state, prototype); + prototype +} + +fn translate_function_definition( + state: &mut State, + sfd: &syntax::FunctionDefinition, +) -> Rc { + let (prototype, sym) = translate_prototype(state, &sfd.prototype); + + state.push_scope(prototype.name.as_str().into()); + state.in_function = Some(sym); + state.modified_globals.get_mut().clear(); + state.texel_fetches.clear(); + let body = translate_compound_statement(state, &sfd.statement); + let mut globals = Vec::new(); + mem::swap(&mut globals, state.modified_globals.get_mut()); + let mut texel_fetches = HashMap::new(); + mem::swap(&mut texel_fetches, &mut state.texel_fetches); + state.in_function = None; + state.pop_scope(); + + let fd = Rc::new(FunctionDefinition { + prototype, + body, + globals, + texel_fetches, + }); + state.sym_mut(sym).decl = SymDecl::UserFunction(fd.clone(), RunClass::Unknown); + fd +} + +fn translate_external_declaration( + state: &mut State, + ed: &syntax::ExternalDeclaration, +) -> ExternalDeclaration { + match ed { + syntax::ExternalDeclaration::Declaration(d) => { + ExternalDeclaration::Declaration(translate_declaration(state, d, RunClass::Unknown)) + } + syntax::ExternalDeclaration::FunctionDefinition(fd) => { + ExternalDeclaration::FunctionDefinition(translate_function_definition(state, fd)) + } + syntax::ExternalDeclaration::Preprocessor(p) => { + ExternalDeclaration::Preprocessor(p.clone()) + } + } +} + +fn declare_function( + state: &mut State, + name: &str, + cxx_name: Option<&'static str>, + ret: Type, + params: Vec, +) { + let sig = FunctionSignature { ret, params }; + match state.lookup_sym_mut(name) { + Some(Symbol { + decl: SymDecl::NativeFunction(f, ..), + .. + }) => f.signatures.push(sig), + None => { + state.declare( + name, + SymDecl::NativeFunction( + FunctionType { + signatures: NonEmpty::new(sig), + }, + cxx_name, + ), + ); + } + _ => panic!("overloaded function name {}", name), + } + //state.declare(name, Type::Function(FunctionType{ v})) +} + +pub fn ast_to_hir(state: &mut State, tu: &syntax::TranslationUnit) -> TranslationUnit { + // global scope + state.push_scope("global".into()); + use TypeKind::*; + declare_function( + state, + "vec2", + Some("make_vec2"), + Type::new(Vec2), + vec![Type::new(Float)], + ); + declare_function( + state, + "vec2", + Some("make_vec2"), + Type::new(Vec2), + vec![Type::new(IVec2)], + ); + declare_function( + state, + "vec2", + Some("make_vec2"), + Type::new(Vec2), + vec![Type::new(IVec3)], + ); + declare_function( + state, + "vec3", + Some("make_vec3"), + Type::new(Vec3), + vec![Type::new(Float), Type::new(Float), Type::new(Float)], + ); + declare_function( + state, + "vec3", + Some("make_vec3"), + Type::new(Vec3), + vec![Type::new(Float)], + ); + declare_function( + state, + "vec3", + Some("make_vec3"), + Type::new(Vec3), + vec![Type::new(Vec2), Type::new(Float)], + ); + declare_function( + state, + "vec4", + Some("make_vec4"), + Type::new(Vec4), + vec![Type::new(Vec3), Type::new(Float)], + ); + declare_function( + state, + "vec4", + Some("make_vec4"), + Type::new(Vec4), + vec![ + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + ], + ); + declare_function( + state, + "vec4", + Some("make_vec4"), + Type::new(Vec4), + vec![Type::new(Vec2), Type::new(Float), Type::new(Float)], + ); + declare_function( + state, + "vec4", + Some("make_vec4"), + Type::new(Vec4), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "vec4", + Some("make_vec4"), + Type::new(Vec4), + vec![Type::new(Float), Type::new(Float), Type::new(Vec2)], + ); + declare_function( + state, + "vec4", + Some("make_vec4"), + Type::new(Vec4), + vec![Type::new(Vec4)], + ); + + declare_function( + state, + "bvec2", + Some("make_bvec2"), + Type::new(BVec2), + vec![Type::new(Bool)], + ); + declare_function( + state, + "bvec4", + Some("make_bvec4"), + Type::new(BVec4), + vec![Type::new(BVec2), Type::new(BVec2)], + ); + + declare_function( + state, + "int", + Some("make_int"), + Type::new(Int), + vec![Type::new(Float)], + ); + declare_function( + state, + "float", + Some("make_float"), + Type::new(Float), + vec![Type::new(Float)], + ); + declare_function( + state, + "float", + Some("make_float"), + Type::new(Float), + vec![Type::new(Int)], + ); + declare_function( + state, + "int", + Some("make_int"), + Type::new(Int), + vec![Type::new(UInt)], + ); + declare_function( + state, + "uint", + Some("make_uint"), + Type::new(UInt), + vec![Type::new(Float)], + ); + declare_function( + state, + "uint", + Some("make_uint"), + Type::new(UInt), + vec![Type::new(Int)], + ); + declare_function( + state, + "ivec2", + Some("make_ivec2"), + Type::new(IVec2), + vec![Type::new(UInt), Type::new(UInt)], + ); + declare_function( + state, + "ivec2", + Some("make_ivec2"), + Type::new(IVec2), + vec![Type::new(Int), Type::new(Int)], + ); + declare_function( + state, + "ivec2", + Some("make_ivec2"), + Type::new(IVec2), + vec![Type::new(Vec2)], + ); + declare_function( + state, + "ivec3", + Some("make_ivec3"), + Type::new(IVec3), + vec![Type::new(IVec2), Type::new(Int)], + ); + declare_function( + state, + "ivec4", + Some("make_ivec4"), + Type::new(IVec4), + vec![ + Type::new(Int), + Type::new(Int), + Type::new(Int), + Type::new(Int), + ], + ); + declare_function( + state, + "ivec4", + Some("make_ivec4"), + Type::new(IVec4), + vec![Type::new(IVec2), Type::new(Int), Type::new(Int)], + ); + + declare_function( + state, + "mat2", + Some("make_mat2"), + Type::new(Mat2), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "mat2", + Some("make_mat2"), + Type::new(Mat2), + vec![Type::new(Float)], + ); + declare_function( + state, + "mat2", + Some("make_mat2"), + Type::new(Mat2), + vec![Type::new(Mat4)], + ); + declare_function( + state, + "mat3", + Some("make_mat3"), + Type::new(Mat3), + vec![Type::new(Vec3), Type::new(Vec3), Type::new(Vec3)], + ); + declare_function( + state, + "mat3", + Some("make_mat3"), + Type::new(Mat3), + vec![Type::new(Mat4)], + ); + declare_function( + state, + "mat3", + Some("make_mat3"), + Type::new(Mat3), + vec![ + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + ], + ); + declare_function( + state, + "mat4", + Some("make_mat4"), + Type::new(Mat4), + vec![ + Type::new(Vec4), + Type::new(Vec4), + Type::new(Vec4), + Type::new(Vec4), + ], + ); + declare_function( + state, + "mat4", + Some("make_mat4"), + Type::new(Mat4), + vec![ + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + Type::new(Float), + ], + ); + declare_function(state, "abs", None, Type::new(Vec2), vec![Type::new(Vec2)]); + declare_function(state, "abs", None, Type::new(Vec3), vec![Type::new(Vec3)]); + declare_function(state, "abs", None, Type::new(Float), vec![Type::new(Float)]); + declare_function( + state, + "dot", + None, + Type::new(Float), + vec![Type::new(Vec3), Type::new(Vec3)], + ); + declare_function( + state, + "dot", + None, + Type::new(Float), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "min", + None, + Type::new(Float), + vec![Type::new(Float), Type::new(Float)], + ); + declare_function( + state, + "min", + None, + Type::new(Vec2), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "min", + None, + Type::new(Vec3), + vec![Type::new(Vec3), Type::new(Vec3)], + ); + + declare_function( + state, + "max", + None, + Type::new(Float), + vec![Type::new(Float), Type::new(Float)], + ); + declare_function( + state, + "max", + None, + Type::new(Vec2), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "max", + None, + Type::new(Vec2), + vec![Type::new(Vec2), Type::new(Float)], + ); + declare_function( + state, + "max", + None, + Type::new(Vec3), + vec![Type::new(Vec3), Type::new(Vec3)], + ); + + declare_function( + state, + "mix", + None, + Type::new(Vec2), + vec![Type::new(Vec2), Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "mix", + None, + Type::new(Vec2), + vec![Type::new(Vec2), Type::new(Vec2), Type::new(BVec2)], + ); + declare_function( + state, + "mix", + None, + Type::new(Vec2), + vec![Type::new(Vec2), Type::new(Vec2), Type::new(Float)], + ); + declare_function( + state, + "mix", + None, + Type::new(Vec3), + vec![Type::new(Vec3), Type::new(Vec3), Type::new(Vec3)], + ); + declare_function( + state, + "mix", + None, + Type::new(Vec4), + vec![Type::new(Vec4), Type::new(Vec4), Type::new(Vec4)], + ); + declare_function( + state, + "mix", + None, + Type::new(Vec4), + vec![Type::new(Vec4), Type::new(Vec4), Type::new(Float)], + ); + declare_function( + state, + "mix", + None, + Type::new(Vec3), + vec![Type::new(Vec3), Type::new(Vec3), Type::new(Float)], + ); + declare_function( + state, + "mix", + None, + Type::new(Vec3), + vec![Type::new(Vec3), Type::new(Vec3), Type::new(BVec3)], + ); + declare_function( + state, + "mix", + None, + Type::new(Float), + vec![Type::new(Float), Type::new(Float), Type::new(Float)], + ); + declare_function( + state, + "mix", + None, + Type::new(Vec4), + vec![Type::new(Vec4), Type::new(Vec4), Type::new(BVec4)], + ); + declare_function( + state, + "step", + None, + Type::new(Float), + vec![Type::new(Float), Type::new(Float)], + ); + declare_function( + state, + "step", + None, + Type::new(Vec2), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "step", + None, + Type::new(Vec3), + vec![Type::new(Vec3), Type::new(Vec3)], + ); + declare_function( + state, + "notEqual", + None, + Type::new(BVec4), + vec![Type::new(IVec4), Type::new(IVec4)], + ); + + declare_function( + state, + "fwidth", + None, + Type::new(Vec2), + vec![Type::new(Vec2)], + ); + declare_function(state, "cos", None, Type::new(Float), vec![Type::new(Float)]); + declare_function(state, "sin", None, Type::new(Float), vec![Type::new(Float)]); + declare_function(state, "tan", None, Type::new(Float), vec![Type::new(Float)]); + declare_function(state, "atan", None, Type::new(Float), vec![Type::new(Float)]); + declare_function(state, "atan", None, Type::new(Float), vec![Type::new(Float), Type::new(Float)]); + declare_function( + state, + "clamp", + None, + Type::new(Vec3), + vec![Type::new(Vec3), Type::new(Float), Type::new(Float)], + ); + declare_function( + state, + "clamp", + None, + Type::new(Double), + vec![Type::new(Double), Type::new(Double), Type::new(Double)], + ); + declare_function( + state, + "clamp", + None, + Type::new(Vec2), + vec![Type::new(Vec2), Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "clamp", + None, + Type::new(Vec3), + vec![Type::new(Vec3), Type::new(Vec3), Type::new(Vec3)], + ); + declare_function( + state, + "clamp", + None, + Type::new(Vec4), + vec![Type::new(Vec4), Type::new(Vec4), Type::new(Vec4)], + ); + declare_function( + state, + "length", + None, + Type::new(Float), + vec![Type::new(Vec2)], + ); + declare_function(state, "pow", None, Type::new(Vec3), vec![Type::new(Vec3)]); + declare_function(state, "pow", None, Type::new(Float), vec![Type::new(Float)]); + declare_function(state, "exp", None, Type::new(Float), vec![Type::new(Float)]); + declare_function( + state, + "inversesqrt", + None, + Type::new(Float), + vec![Type::new(Float)], + ); + declare_function( + state, + "sqrt", + None, + Type::new(Float), + vec![Type::new(Float)], + ); + declare_function( + state, + "distance", + None, + Type::new(Float), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + + declare_function( + state, + "lessThanEqual", + None, + Type::new(BVec2), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "lessThanEqual", + None, + Type::new(BVec3), + vec![Type::new(Vec3), Type::new(Vec3)], + ); + declare_function( + state, + "lessThanEqual", + None, + Type::new(BVec4), + vec![Type::new(Vec4), Type::new(Vec4)], + ); + declare_function( + state, + "lessThan", + None, + Type::new(BVec2), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "greaterThan", + None, + Type::new(BVec2), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "greaterThanEqual", + None, + Type::new(BVec2), + vec![Type::new(Vec2), Type::new(Vec2)], + ); + declare_function( + state, + "greaterThanEqual", + None, + Type::new(BVec2), + vec![Type::new(Vec4), Type::new(Vec4)], + ); + declare_function(state, "any", None, Type::new(Bool), vec![Type::new(BVec2)]); + declare_function(state, "all", None, Type::new(Bool), vec![Type::new(BVec2)]); + declare_function(state, "all", None, Type::new(Bool), vec![Type::new(BVec4)]); + + declare_function( + state, + "if_then_else", + None, + Type::new(Vec3), + vec![Type::new(BVec3), Type::new(Vec3), Type::new(Vec3)], + ); + declare_function(state, "floor", None, Type::new(Vec4), vec![Type::new(Vec4)]); + declare_function(state, "floor", None, Type::new(Vec2), vec![Type::new(Vec2)]); + declare_function( + state, + "floor", + None, + Type::new(Double), + vec![Type::new(Double)], + ); + declare_function( + state, + "ceil", + None, + Type::new(Double), + vec![Type::new(Double)], + ); + declare_function( + state, + "round", + None, + Type::new(Double), + vec![Type::new(Double)], + ); + declare_function( + state, + "fract", + None, + Type::new(Float), + vec![Type::new(Float)], + ); + declare_function(state, "mod", None, Type::new(Vec2), vec![Type::new(Vec2)]); + declare_function(state, "mod", None, Type::new(Float), vec![Type::new(Float)]); + + declare_function( + state, + "texelFetch", + None, + Type::new(Vec4), + vec![Type::new(Sampler2D), Type::new(IVec2), Type::new(Int)], + ); + declare_function( + state, + "texelFetch", + None, + Type::new(Vec4), + vec![Type::new(Sampler2DArray), Type::new(IVec3), Type::new(Int)], + ); + declare_function( + state, + "texelFetch", + None, + Type::new(IVec4), + vec![Type::new(ISampler2D), Type::new(IVec2), Type::new(Int)], + ); + declare_function( + state, + "texelFetchOffset", + None, + Type::new(Vec4), + vec![ + Type::new(Sampler2D), + Type::new(IVec2), + Type::new(Int), + Type::new(IVec2), + ], + ); + declare_function( + state, + "texelFetchOffset", + None, + Type::new(IVec4), + vec![ + Type::new(ISampler2D), + Type::new(IVec2), + Type::new(Int), + Type::new(IVec2), + ], + ); + declare_function( + state, + "texture", + None, + Type::new(Vec4), + vec![Type::new(Sampler2D), Type::new(Vec2)], + ); + declare_function( + state, + "texture", + None, + Type::new(Vec4), + vec![Type::new(Sampler2DRect), Type::new(Vec2)], + ); + declare_function( + state, + "texture", + None, + Type::new(Vec4), + vec![Type::new(Sampler2DArray), Type::new(Vec3)], + ); + declare_function( + state, + "textureLod", + None, + Type::new(Vec4), + vec![Type::new(Sampler2DArray), Type::new(Vec3), Type::new(Float)], + ); + declare_function( + state, + "textureSize", + None, + Type::new(IVec3), + vec![Type::new(Sampler2DArray), Type::new(Int)], + ); + declare_function( + state, + "textureSize", + None, + Type::new(IVec2), + vec![Type::new(Sampler2D), Type::new(Int)], + ); + declare_function( + state, + "textureSize", + None, + Type::new(IVec2), + vec![Type::new(Sampler2DRect), Type::new(Int)], + ); + + declare_function( + state, + "inverse", + None, + Type::new(Mat2), + vec![Type::new(Mat2)], + ); + declare_function( + state, + "transpose", + None, + Type::new(Mat3), + vec![Type::new(Mat3)], + ); + declare_function( + state, + "normalize", + None, + Type::new(Vec2), + vec![Type::new(Vec2)], + ); + state.declare( + "gl_FragCoord", + SymDecl::Global(StorageClass::In, None, Type::new(Vec4), RunClass::Vector), + ); + state.declare( + "gl_FragColor", + SymDecl::Global(StorageClass::Out, None, Type::new(Vec4), RunClass::Vector), + ); + state.declare( + "gl_Position", + SymDecl::Global(StorageClass::Out, None, Type::new(Vec4), RunClass::Vector), + ); + + TranslationUnit(tu.0.map(state, translate_external_declaration)) +} + +fn infer_expr_inner(state: &mut State, expr: &Expr, assign: &mut SymRef) -> RunClass { + match expr.kind { + ExprKind::Variable(ref i) => { + *assign = *i; + match &state.sym(*i).decl { + SymDecl::Local(_, _, ref run_class) => *run_class, + SymDecl::Global(_, _, _, ref run_class) => *run_class, + _ => panic!(), + } + } + ExprKind::IntConst(_) + | ExprKind::UIntConst(_) + | ExprKind::BoolConst(_) + | ExprKind::FloatConst(_) + | ExprKind::DoubleConst(_) => RunClass::Scalar, + ExprKind::Unary(_, ref e) => infer_expr(state, e), + ExprKind::Binary(_, ref l, ref r) => infer_expr(state, l).merge(infer_expr(state, r)), + ExprKind::Ternary(ref c, ref s, ref e) => infer_expr(state, c) + .merge(infer_expr(state, s)) + .merge(infer_expr(state, e)), + ExprKind::Assignment(ref v, _, ref e) => { + let mut sym = SymRef(!0); + let run_class = infer_expr_inner(state, v, &mut sym).merge(infer_expr(state, e)); + assert!(sym != SymRef(!0)); + state.merge_run_class(sym, run_class) + } + ExprKind::Bracket(ref e, ref indx) => { + infer_expr_inner(state, e, assign).merge(infer_expr(state, indx)) + } + ExprKind::FunCall(ref fun, ref args) => { + let arg_classes: Vec<(RunClass, SymRef)> = args + .iter() + .map(|e| { + let mut assign = SymRef(!0); + let run_class = infer_expr_inner(state, e, &mut assign); + (run_class, assign) + }) + .collect(); + let run_class = if args.is_empty() { + RunClass::Scalar + } else { + arg_classes + .iter() + .fold(RunClass::Unknown, |x, &(y, _)| x.merge(y)) + }; + match fun { + FunIdentifier::Identifier(ref sym) => match &state.sym(*sym).decl { + SymDecl::NativeFunction(..) => run_class, + SymDecl::UserFunction(ref fd, ref run_class) => { + for (&(mut arg_class, assign), param) in + arg_classes.iter().zip(fd.prototype.parameters.iter()) + { + if let FunctionParameterDeclaration::Named(Some(qual), p) = param { + match qual { + ParameterQualifier::InOut | ParameterQualifier::Out => { + if let SymDecl::Local(_, _, param_class) = + &state.sym(p.sym).decl + { + match param_class { + RunClass::Unknown | RunClass::Vector => { + arg_class = RunClass::Vector; + } + RunClass::Dependent(mask) => { + for i in 0 .. 31 { + if (mask & (1 << i)) != 0 { + arg_class = + arg_class.merge(arg_classes[i].0); + } + } + } + RunClass::Scalar => {} + } + } + assert!(assign != SymRef(!0)); + state.merge_run_class(assign, arg_class); + } + _ => {} + } + } + } + if fd.prototype.ty.kind == TypeKind::Void { + RunClass::Scalar + } else { + match *run_class { + RunClass::Unknown | RunClass::Vector => RunClass::Vector, + RunClass::Dependent(mask) => { + let mut ret_class = RunClass::Unknown; + for i in 0 .. 31 { + if (mask & (1 << i)) != 0 { + ret_class = ret_class.merge(arg_classes[i].0); + } + } + ret_class + } + RunClass::Scalar => RunClass::Scalar, + } + } + } + SymDecl::Struct(..) => run_class, + _ => panic!(), + }, + FunIdentifier::Constructor(..) => run_class, + } + } + ExprKind::Dot(ref e, _) => infer_expr_inner(state, e, assign), + ExprKind::SwizzleSelector(ref e, _) => infer_expr_inner(state, e, assign), + ExprKind::PostInc(ref e) => infer_expr_inner(state, e, assign), + ExprKind::PostDec(ref e) => infer_expr_inner(state, e, assign), + ExprKind::Comma(ref a, ref b) => { + infer_expr(state, a); + infer_expr(state, b) + } + ExprKind::Cond(_, ref e) => infer_expr(state, e), + ExprKind::CondMask => RunClass::Vector, + } +} + +fn infer_expr(state: &mut State, expr: &Expr) -> RunClass { + infer_expr_inner(state, expr, &mut SymRef(!0)) +} + +fn infer_condition(state: &mut State, c: &Condition) { + match *c { + Condition::Expr(ref e) => { + infer_expr(state, e); + } + } +} + +fn infer_iteration_statement(state: &mut State, ist: &IterationStatement) { + let changed = state.run_class_changed.replace(true); + match *ist { + IterationStatement::While(ref cond, ref body) => { + while state.run_class_changed.replace(false) { + infer_condition(state, cond); + infer_statement(state, body); + } + } + IterationStatement::DoWhile(ref body, ref cond) => { + while state.run_class_changed.replace(false) { + infer_statement(state, body); + infer_expr(state, cond); + } + } + IterationStatement::For(ref init, ref rest, ref body) => { + match *init { + ForInitStatement::Expression(ref expr) => { + if let Some(ref e) = *expr { + infer_expr(state, e); + } + } + ForInitStatement::Declaration(ref d) => { + infer_declaration(state, d); + } + } + while state.run_class_changed.replace(false) { + if let Some(ref cond) = rest.condition { + infer_condition(state, cond); + } + if let Some(ref e) = rest.post_expr { + infer_expr(state, e); + } + infer_statement(state, body); + } + } + } + state.run_class_changed.set(changed); +} + +fn infer_selection_statement(state: &mut State, sst: &SelectionStatement) { + let mut branch_run_class = state.branch_run_class.merge(infer_expr(state, &sst.cond)); + mem::swap(&mut state.branch_run_class, &mut branch_run_class); + let branch_declaration = state.branch_declaration; + state.branch_declaration = state.last_declaration; + infer_statement(state, &sst.body); + if let Some(ref else_st) = sst.else_stmt { + infer_statement(state, else_st); + } + state.branch_run_class = branch_run_class; + state.branch_declaration = branch_declaration; +} + +fn infer_expression_statement(state: &mut State, est: &ExprStatement) { + if let Some(ref e) = *est { + infer_expr(state, e); + } +} + +fn infer_switch_statement(state: &mut State, sst: &SwitchStatement) { + let mut branch_run_class = state.branch_run_class.merge(infer_expr(state, &sst.head)); + mem::swap(&mut state.branch_run_class, &mut branch_run_class); + let branch_declaration = state.branch_declaration; + state.branch_declaration = state.last_declaration; + for case in &sst.cases { + for st in &case.stmts { + infer_statement(state, st); + } + } + state.branch_run_class = branch_run_class; + state.branch_declaration = branch_declaration; +} + +fn infer_jump_statement(state: &mut State, j: &JumpStatement) { + match *j { + JumpStatement::Continue => {} + JumpStatement::Break => {} + JumpStatement::Discard => {} + JumpStatement::Return(ref e) => { + if let Some(e) = e { + let run_class = infer_expr(state, e); + state.return_run_class(run_class); + } + } + } +} + +fn infer_initializer(state: &mut State, i: &Initializer) -> RunClass { + match *i { + Initializer::Simple(ref e) => infer_expr(state, e), + Initializer::List(ref list) => { + let mut run_class = RunClass::Unknown; + for ini in list.0.iter() { + run_class = run_class.merge(infer_initializer(state, ini)); + } + run_class + } + } +} + +fn infer_declaration(state: &mut State, d: &Declaration) { + match *d { + Declaration::FunctionPrototype(..) => {} + Declaration::InitDeclaratorList(ref list) => { + state.last_declaration = list.head.name; + + let mut run_class = RunClass::Unknown; + for decl in &list.tail { + if let Some(ref initializer) = decl.initializer { + run_class = run_class.merge(infer_initializer(state, initializer)); + } + } + if let Some(ref initializer) = list.head.initializer { + run_class = run_class.merge(infer_initializer(state, initializer)); + state.merge_run_class(list.head.name, run_class); + } + } + Declaration::Precision(..) => {} + Declaration::Block(..) => {} + Declaration::Global(..) => {} + Declaration::StructDefinition(..) => {} + } +} + +fn infer_simple_statement(state: &mut State, sst: &SimpleStatement) { + match *sst { + SimpleStatement::Declaration(ref d) => infer_declaration(state, d), + SimpleStatement::Expression(ref e) => infer_expression_statement(state, e), + SimpleStatement::Selection(ref s) => infer_selection_statement(state, s), + SimpleStatement::Switch(ref s) => infer_switch_statement(state, s), + SimpleStatement::Iteration(ref i) => infer_iteration_statement(state, i), + SimpleStatement::Jump(ref j) => infer_jump_statement(state, j), + } +} + +fn infer_compound_statement(state: &mut State, cst: &CompoundStatement) { + for st in &cst.statement_list { + infer_statement(state, st); + } +} + +fn infer_statement(state: &mut State, st: &Statement) { + match *st { + Statement::Compound(ref cst) => infer_compound_statement(state, cst), + Statement::Simple(ref sst) => infer_simple_statement(state, sst), + } +} + +fn infer_function_definition(state: &mut State, fd: &FunctionDefinition) { + state.in_function = Some(state.lookup(fd.prototype.name.as_str()).unwrap()); + + state.run_class_changed.set(true); + while state.run_class_changed.replace(false) { + for st in &fd.body.statement_list { + infer_statement(state, st); + } + } + + state.in_function = None; +} + +fn infer_external_declaration(state: &mut State, ed: &ExternalDeclaration) { + match *ed { + ExternalDeclaration::Preprocessor(_) => {} + ExternalDeclaration::FunctionDefinition(ref fd) => infer_function_definition(state, fd), + ExternalDeclaration::Declaration(_) => {} + } +} + +pub fn infer_run_class(state: &mut State, tu: &TranslationUnit) { + for ed in &(tu.0).0 { + infer_external_declaration(state, ed); + } +} diff --git a/third_party/webrender/glsl-to-cxx/src/lib.rs b/third_party/webrender/glsl-to-cxx/src/lib.rs new file mode 100644 index 00000000000..e40418aaab2 --- /dev/null +++ b/third_party/webrender/glsl-to-cxx/src/lib.rs @@ -0,0 +1,3706 @@ +/* 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/. */ + +extern crate glsl; + +mod hir; + +use glsl::parser::Parse; +use glsl::syntax; +use glsl::syntax::{TranslationUnit, UnaryOp}; +use hir::{Statement, SwizzleSelector, Type}; +use std::cell::{Cell, RefCell}; +use std::collections::{BTreeMap, HashMap}; +use std::io::Read; +use std::mem; + +#[derive(PartialEq, Eq)] +enum ShaderKind { + Fragment, + Vertex, +} + +type UniformIndices = BTreeMap; + +fn build_uniform_indices(indices: &mut UniformIndices, state: &hir::State) { + for u in state.used_globals.borrow().iter() { + let sym = state.sym(*u); + match &sym.decl { + hir::SymDecl::Global(storage, _, ty, _) => match storage { + hir::StorageClass::Uniform | hir::StorageClass::Sampler(..) => { + let next_index = indices.len() as i32 + 1; + indices.entry(sym.name.clone()).or_insert(( + next_index, + ty.kind.clone(), + *storage, + )); + } + _ => {} + }, + _ => {} + } + } +} + +pub fn translate(args: &mut dyn Iterator) -> String { + let _cmd_name = args.next(); + let vertex_file = args.next().unwrap(); + + let vs_name = std::path::Path::new(&vertex_file) + .file_stem() + .unwrap() + .to_string_lossy() + .to_string(); + + let frag_file = args.next().unwrap(); + + let fs_name = std::path::Path::new(&frag_file) + .file_stem() + .unwrap() + .to_string_lossy() + .to_string(); + + let frag_include = args.next(); + + let (vs_state, vs_hir, vs_is_frag) = parse_shader(vertex_file); + let (fs_state, fs_hir, fs_is_frag) = parse_shader(frag_file); + + // we use a BTree so that iteration is stable + let mut uniform_indices = BTreeMap::new(); + build_uniform_indices(&mut uniform_indices, &vs_state); + build_uniform_indices(&mut uniform_indices, &fs_state); + + assert_eq!(fs_name, vs_name); + + let mut result = translate_shader( + vs_name, + vs_state, + vs_hir, + vs_is_frag, + &uniform_indices, + None, + ); + result += "\n"; + result += &translate_shader( + fs_name, + fs_state, + fs_hir, + fs_is_frag, + &uniform_indices, + frag_include, + ); + result +} + +fn parse_shader(file: String) -> (hir::State, hir::TranslationUnit, bool) { + let mut contents = String::new(); + let is_frag = file.contains("frag"); + std::fs::File::open(&file) + .unwrap() + .read_to_string(&mut contents) + .unwrap(); + let r = TranslationUnit::parse(contents); + + //println!("{:#?}", r); + let mut ast_glsl = String::new(); + let r = r.unwrap(); + glsl::transpiler::glsl::show_translation_unit(&mut ast_glsl, &r); + //let mut fast = std::fs::File::create("ast").unwrap(); + //fast.write(ast_glsl.as_bytes()); + + let mut state = hir::State::new(); + let hir = hir::ast_to_hir(&mut state, &r); + (state, hir, is_frag) +} + +fn translate_shader( + name: String, + mut state: hir::State, + hir: hir::TranslationUnit, + is_frag: bool, + uniform_indices: &UniformIndices, + include_file: Option, +) -> String { + //println!("{:#?}", state); + + hir::infer_run_class(&mut state, &hir); + + let mut uniforms = Vec::new(); + let mut inputs = Vec::new(); + let mut outputs = Vec::new(); + + for i in &hir { + match i { + hir::ExternalDeclaration::Declaration(hir::Declaration::InitDeclaratorList(ref d)) => { + match &state.sym(d.head.name).decl { + hir::SymDecl::Global(storage, ..) + if state.used_globals.borrow().contains(&d.head.name) => + { + match storage { + hir::StorageClass::Uniform | hir::StorageClass::Sampler(..) => { + uniforms.push(d.head.name); + } + hir::StorageClass::In => { + inputs.push(d.head.name); + } + hir::StorageClass::Out | hir::StorageClass::FragColor(_) => { + outputs.push(d.head.name); + } + _ => {} + } + } + _ => {} + } + } + _ => {} + } + } + + //println!("{:#?}", hir); + + let mut state = OutputState { + hir: state, + output: String::new(), + buffer: RefCell::new(String::new()), + indent: 0, + should_indent: false, + output_cxx: false, + mask: None, + cond_index: 0, + return_type: None, + return_declared: false, + return_vector: false, + is_scalar: Cell::new(false), + is_lval: Cell::new(false), + name: name.clone(), + kind: if is_frag { + ShaderKind::Fragment + } else { + ShaderKind::Vertex + }, + functions: HashMap::new(), + deps: RefCell::new(Vec::new()), + vector_mask: 0, + uses_discard: false, + used_fragcoord: Cell::new(0), + use_perspective: false, + has_draw_span_rgba8: false, + has_draw_span_r8: false, + used_globals: RefCell::new(Vec::new()), + texel_fetches: RefCell::new(Vec::new()), + }; + + show_translation_unit(&mut state, &hir); + let _output_glsl = state.finish_output(); + + state.should_indent = true; + state.output_cxx = true; + + if state.output_cxx { + let part_name = name.to_owned() + + match state.kind { + ShaderKind::Vertex => "_vert", + ShaderKind::Fragment => "_frag", + }; + + if state.kind == ShaderKind::Vertex { + write_common_globals(&mut state, &inputs, &outputs, uniform_indices); + write!(state, "struct {0}_vert : VertexShaderImpl, {0}_common {{\nprivate:\n", name); + } else { + write!(state, "struct {0}_frag : FragmentShaderImpl, {0}_vert {{\nprivate:\n", name); + } + + write!(state, "typedef {} Self;\n", part_name); + + show_translation_unit(&mut state, &hir); + + if let Some(include_file) = include_file { + write_include_file(&mut state, include_file); + } + + let pruned_inputs: Vec<_> = inputs + .iter() + .filter(|i| state.used_globals.borrow().contains(i)) + .cloned() + .collect(); + + if state.kind == ShaderKind::Vertex { + write_set_uniform_1i(&mut state, uniform_indices); + write_set_uniform_4fv(&mut state, uniform_indices); + write_set_uniform_matrix4fv(&mut state, uniform_indices); + write_load_attribs(&mut state, &pruned_inputs); + write_store_outputs(&mut state, &outputs); + } else { + write_read_inputs(&mut state, &pruned_inputs); + } + + write_abi(&mut state); + write!(state, "}};\n\n"); + + if state.kind == ShaderKind::Fragment { + write!(state, "struct {0}_program : ProgramImpl, {0}_frag {{\n", name); + write_get_uniform_index(&mut state, uniform_indices); + write!(state, "void bind_attrib(const char* name, int index) override {{\n"); + write!(state, " attrib_locations.bind_loc(name, index);\n}}\n"); + write!(state, "int get_attrib(const char* name) const override {{\n"); + write!(state, " return attrib_locations.get_loc(name);\n}}\n"); + write!(state, "size_t interpolants_size() const override {{ return sizeof(InterpOutputs); }}\n"); + write!(state, "VertexShaderImpl* get_vertex_shader() override {{\n"); + write!(state, " return this;\n}}\n"); + write!(state, "FragmentShaderImpl* get_fragment_shader() override {{\n"); + write!(state, " return this;\n}}\n"); + write!(state, "static ProgramImpl* loader() {{ return new {}_program; }}\n", name); + write!(state, "}};\n\n"); + } + + define_global_consts(&mut state, &hir, &part_name); + } else { + show_translation_unit(&mut state, &hir); + } + let output_cxx = state.finish_output(); + + //let mut hir = std::fs::File::create("hir").unwrap(); + //hir.write(output_glsl.as_bytes()); + + output_cxx +} + +fn write_get_uniform_index(state: &mut OutputState, uniform_indices: &UniformIndices) { + write!( + state, + "int get_uniform(const char *name) const override {{\n" + ); + for (uniform_name, (index, _, _)) in uniform_indices.iter() { + write!( + state, + " if (strcmp(\"{}\", name) == 0) {{ return {}; }}\n", + uniform_name, index + ); + } + write!(state, " return -1;\n"); + write!(state, "}}\n"); +} + +fn float4_compatible(ty: hir::TypeKind) -> bool { + match ty { + hir::TypeKind::Vec4 => true, + _ => false, + } +} + +fn matrix4_compatible(ty: hir::TypeKind) -> bool { + match ty { + hir::TypeKind::Mat4 => true, + _ => false, + } +} + +fn write_program_samplers(state: &mut OutputState, uniform_indices: &UniformIndices) { + write!(state, "struct Samplers {{\n"); + for (name, (_, tk, storage)) in uniform_indices.iter() { + match tk { + hir::TypeKind::Sampler2D + | hir::TypeKind::Sampler2DRect + | hir::TypeKind::ISampler2D + | hir::TypeKind::Sampler2DArray => { + write!(state, " "); + show_type_kind(state, &tk); + let suffix = if let hir::StorageClass::Sampler(format) = storage { + format.type_suffix() + } else { + None + }; + write!(state, "{}_impl {}_impl;\n", suffix.unwrap_or(""), name); + write!(state, " int {}_slot;\n", name); + } + _ => {} + } + } + write!( + state, + " bool set_slot(int index, int value) {{\n" + ); + write!(state, " switch (index) {{\n"); + for (name, (index, tk, _)) in uniform_indices.iter() { + match tk { + hir::TypeKind::Sampler2D + | hir::TypeKind::Sampler2DRect + | hir::TypeKind::ISampler2D + | hir::TypeKind::Sampler2DArray => { + write!(state, " case {}:\n", index); + write!(state, " {}_slot = value;\n", name); + write!(state, " return true;\n"); + } + _ => {} + } + } + write!(state, " }}\n"); + write!(state, " return false;\n"); + write!(state, " }}\n"); + write!(state, "}} samplers;\n"); + +} + +fn write_bind_textures(state: &mut OutputState, uniforms: &UniformIndices) { + write!(state, "void bind_textures() {{\n"); + for (name, (_, tk, storage)) in uniforms { + match storage { + hir::StorageClass::Sampler(_format) => { + match tk { + hir::TypeKind::Sampler2D + | hir::TypeKind::Sampler2DRect => write!(state, + " {0} = lookup_sampler(&samplers.{0}_impl, samplers.{0}_slot);\n", + name), + hir::TypeKind::ISampler2D => write!(state, + " {0} = lookup_isampler(&samplers.{0}_impl, samplers.{0}_slot);\n", + name), + hir::TypeKind::Sampler2DArray => write!(state, + " {0} = lookup_sampler_array(&samplers.{0}_impl, samplers.{0}_slot);\n", + name), + _ => {} + }; + } + _ => {} + } + } + write!(state, "}}\n"); +} + +fn write_set_uniform_1i( + state: &mut OutputState, + uniforms: &UniformIndices, +) { + write!( + state, + "static void set_uniform_1i(Self *self, int index, int value) {{\n" + ); + write!(state, " if (self->samplers.set_slot(index, value)) return;\n"); + write!(state, " switch (index) {{\n"); + for (name, (index, tk, _)) in uniforms { + write!(state, " case {}:\n", index); + match tk { + hir::TypeKind::Int => write!( + state, + " self->{} = {}(value);\n", + name, + tk.cxx_primitive_scalar_type_name().unwrap(), + ), + _ => write!(state, " assert(0); // {}\n", name), + }; + write!(state, " break;\n"); + } + write!(state, " }}\n"); + write!(state, "}}\n"); +} + +fn write_set_uniform_4fv( + state: &mut OutputState, + uniforms: &UniformIndices, +) { + write!( + state, + "static void set_uniform_4fv(Self *self, int index, const float *value) {{\n" + ); + write!(state, " switch (index) {{\n"); + for (name, (index, tk, _)) in uniforms { + write!(state, " case {}:\n", index); + if float4_compatible(tk.clone()) { + write!( + state, + " self->{} = {}_scalar(value);\n", + name, + tk.glsl_primitive_type_name().unwrap(), + ); + } else { + write!(state, " assert(0); // {}\n", name); + } + write!(state, " break;\n"); + } + write!(state, " }}\n"); + write!(state, "}}\n"); +} + +fn write_set_uniform_matrix4fv( + state: &mut OutputState, + uniforms: &UniformIndices, +) { + write!( + state, + "static void set_uniform_matrix4fv(Self *self, int index, const float *value) {{\n" + ); + write!(state, " switch (index) {{\n"); + for (name, (index, tk, _)) in uniforms { + write!(state, " case {}:\n", index); + if matrix4_compatible(tk.clone()) { + write!( + state, + " self->{} = mat4_scalar::load_from_ptr(value);\n", + name + ); + } else { + write!(state, " assert(0); // {}\n", name); + } + write!(state, " break;\n"); + } + write!(state, " }}\n"); + write!(state, "}}\n"); +} + +fn write_bind_attrib_location(state: &mut OutputState, attribs: &[hir::SymRef]) { + write!(state, "struct AttribLocations {{\n"); + for i in attribs { + let sym = state.hir.sym(*i); + write!(state, " int {} = NULL_ATTRIB;\n", sym.name.as_str()); + } + write!(state, " void bind_loc(const char* name, int index) {{\n"); + for i in attribs { + let sym = state.hir.sym(*i); + write!( + state, + " if (strcmp(\"{0}\", name) == 0) {{ {0} = index; return; }}\n", + sym.name.as_str() + ); + } + write!(state, " }}\n"); + write!(state, " int get_loc(const char* name) const {{\n"); + for i in attribs { + let sym = state.hir.sym(*i); + write!(state, + " if (strcmp(\"{0}\", name) == 0) {{ \ + return {0} != NULL_ATTRIB ? {0} : -1; \ + }}\n", + sym.name.as_str()); + } + write!(state, " return -1;\n"); + write!(state, " }}\n"); + write!(state, "}} attrib_locations;\n"); +} + +fn write_common_globals(state: &mut OutputState, attribs: &[hir::SymRef], + outputs: &[hir::SymRef], uniforms: &UniformIndices) { + write!(state, "struct {}_common {{\n", state.name); + + write_program_samplers(state, uniforms); + write_bind_attrib_location(state, attribs); + + let is_scalar = state.is_scalar.replace(true); + for i in outputs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(hir::StorageClass::Out, _, ty, hir::RunClass::Scalar) => { + show_type(state, ty); + write!(state, " {};\n", sym.name.as_str()); + } + _ => {} + } + } + for (name, (_, tk, storage)) in uniforms { + match storage { + hir::StorageClass::Sampler(format) => { + write!(state, + "{}{} {};\n", + tk.cxx_primitive_type_name().unwrap(), + format.type_suffix().unwrap_or(""), + name, + ); + } + _ => { + show_type_kind(state, tk); + write!(state, " {};\n", name); + } + } + } + state.is_scalar.set(is_scalar); + + write_bind_textures(state, uniforms); + + write!(state, "}};\n"); +} + +//fn type_name(state: &OutputState, ty: &Type) -> String { +// let buffer = state.push_buffer(); +// show_type(state, ty); +// state.pop_buffer(buffer) +//} + +fn write_load_attribs(state: &mut OutputState, attribs: &[hir::SymRef]) { + write!(state, "static void load_attribs(\ + Self *self, VertexAttrib *attribs, \ + uint32_t start, int instance, int count) {{\n"); + for i in attribs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _interpolation, _ty, run_class) => { + let name = sym.name.as_str(); + let func = if *run_class == hir::RunClass::Scalar { + "load_flat_attrib" + } else { + "load_attrib" + }; + write!(state, + " {0}(self->{1}, attribs[self->attrib_locations.{1}], start, instance, count);\n", + func, name); + } + _ => panic!(), + } + } + write!(state, "}}\n"); +} + +fn write_store_outputs(state: &mut OutputState, outputs: &[hir::SymRef]) { + let is_scalar = state.is_scalar.replace(true); + write!(state, "public:\nstruct InterpOutputs {{\n"); + for i in outputs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, ty, run_class) => { + if *run_class != hir::RunClass::Scalar { + show_type(state, ty); + write!(state, " {};\n", sym.name.as_str()); + } + } + _ => panic!(), + } + } + + write!(state, "}};\nprivate:\n"); + state.is_scalar.set(is_scalar); + + write!( + state, + "ALWAYS_INLINE void store_interp_outputs(char* dest_ptr, size_t stride) {{\n" + ); + write!(state, " for(int n = 0; n < 4; n++) {{\n"); + write!( + state, + " auto* dest = reinterpret_cast(dest_ptr);\n" + ); + for i in outputs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, _, run_class) => { + if *run_class != hir::RunClass::Scalar { + let name = sym.name.as_str(); + write!(state, " dest->{} = get_nth({}, n);\n", name, name); + } + } + _ => panic!(), + } + } + write!(state, " dest_ptr += stride;\n"); + write!(state, " }}\n"); + write!(state, "}}\n"); +} + +fn write_read_inputs(state: &mut OutputState, inputs: &[hir::SymRef]) { + write!( + state, + "typedef {}_vert::InterpOutputs InterpInputs;\n", + state.name + ); + + write!(state, "InterpInputs interp_step;\n"); + + let mut has_varying = false; + for i in inputs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, ty, run_class) => { + if *run_class != hir::RunClass::Scalar { + if !has_varying { + has_varying = true; + write!(state, "struct InterpPerspective {{\n"); + } + show_type(state, ty); + write!(state, " {};\n", sym.name.as_str()); + } + } + _ => panic!(), + } + } + if has_varying { + write!(state, "}};\n"); + write!(state, "InterpPerspective interp_perspective;\n"); + } + + write!(state, + "static void read_interp_inputs(\ + Self *self, const InterpInputs *init, const InterpInputs *step, float step_width) {{\n"); + for i in inputs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, _, run_class) => { + if *run_class != hir::RunClass::Scalar { + let name = sym.name.as_str(); + write!( + state, + " self->{0} = init_interp(init->{0}, step->{0});\n", + name + ); + write!( + state, + " self->interp_step.{0} = step->{0} * step_width;\n", + name + ); + } + } + _ => panic!(), + } + } + write!(state, "}}\n"); + + let used_fragcoord = state.used_fragcoord.get(); + if has_varying || (used_fragcoord & (4 | 8)) != 0 { + state.use_perspective = true; + } + if state.use_perspective { + write!(state, + "static void read_perspective_inputs(\ + Self *self, const InterpInputs *init, const InterpInputs *step, float step_width) {{\n"); + if has_varying { + write!(state, " Float w = 1.0f / self->gl_FragCoord.w;\n"); + } + for i in inputs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, _, run_class) => { + if *run_class != hir::RunClass::Scalar { + let name = sym.name.as_str(); + write!( + state, + " self->interp_perspective.{0} = init_interp(init->{0}, step->{0});\n", + name + ); + write!(state, " self->{0} = self->interp_perspective.{0} * w;\n", name); + write!( + state, + " self->interp_step.{0} = step->{0} * step_width;\n", + name + ); + } + } + _ => panic!(), + } + } + write!(state, "}}\n"); + } + + write!(state, "ALWAYS_INLINE void step_interp_inputs() {{\n"); + if (used_fragcoord & 1) != 0 { + write!(state, " step_fragcoord();\n"); + } + for i in inputs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, _, run_class) => { + if *run_class != hir::RunClass::Scalar { + let name = sym.name.as_str(); + write!(state, " {0} += interp_step.{0};\n", name); + } + } + _ => panic!(), + } + } + write!(state, "}}\n"); + + if state.use_perspective { + write!(state, "ALWAYS_INLINE void step_perspective_inputs() {{\n"); + if (used_fragcoord & 1) != 0 { + write!(state, " step_fragcoord();\n"); + } + write!(state, " step_perspective();\n"); + if has_varying { + write!(state, " Float w = 1.0f / gl_FragCoord.w;\n"); + } + for i in inputs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, _, run_class) => { + if *run_class != hir::RunClass::Scalar { + let name = sym.name.as_str(); + write!(state, " interp_perspective.{0} += interp_step.{0};\n", name); + write!(state, " {0} = w * interp_perspective.{0};\n", name); + } + } + _ => panic!(), + } + } + write!(state, "}}\n"); + } + + if state.has_draw_span_rgba8 || state.has_draw_span_r8 { + write!( + state, + "ALWAYS_INLINE void step_interp_inputs(int chunks) {{\n" + ); + if (used_fragcoord & 1) != 0 { + write!(state, " step_fragcoord(chunks);\n"); + } + for i in inputs { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, _, run_class) => { + if *run_class != hir::RunClass::Scalar { + let name = sym.name.as_str(); + write!(state, " {0} += interp_step.{0} * chunks;\n", name); + } + } + _ => panic!(), + } + } + write!(state, "}}\n"); + } +} + +fn write_include_file(state: &mut OutputState, include_file: String) { + let include_contents = std::fs::read_to_string(&include_file).unwrap(); + + let mut offset = 0; + while offset < include_contents.len() { + let s = &include_contents[offset ..]; + if let Some(start_proto) = s.find("draw_span") { + let s = &s[start_proto ..]; + if let Some(end_proto) = s.find(')') { + let proto = &s[.. end_proto]; + if proto.contains("uint32_t") { + state.has_draw_span_rgba8 = true; + } else if proto.contains("uint8_t") { + state.has_draw_span_r8 = true; + } + offset += start_proto + end_proto; + continue; + } + } + break; + } + + let include_name = std::path::Path::new(&include_file) + .file_name() + .unwrap() + .to_string_lossy(); + write!(state, "\n#include \"{}\"\n\n", include_name); +} + +pub struct OutputState { + hir: hir::State, + output: String, + buffer: RefCell, + should_indent: bool, + output_cxx: bool, + indent: i32, + mask: Option>, + cond_index: usize, + return_type: Option>, + return_declared: bool, + return_vector: bool, + is_scalar: Cell, + is_lval: Cell, + name: String, + kind: ShaderKind, + functions: HashMap<(hir::SymRef, u32), bool>, + deps: RefCell>, + vector_mask: u32, + uses_discard: bool, + used_fragcoord: Cell, + use_perspective: bool, + has_draw_span_rgba8: bool, + has_draw_span_r8: bool, + used_globals: RefCell>, + texel_fetches: RefCell>, +} + +use std::fmt::{Arguments, Write}; + +impl OutputState { + fn indent(&mut self) { + if self.should_indent { + self.indent += 1 + } + } + fn outdent(&mut self) { + if self.should_indent { + self.indent -= 1 + } + } + + fn write(&self, s: &str) { + self.buffer.borrow_mut().push_str(s); + } + + fn flush_buffer(&mut self) { + self.output.push_str(&self.buffer.borrow()); + self.buffer.borrow_mut().clear(); + } + + fn finish_output(&mut self) -> String { + self.flush_buffer(); + + let mut s = String::new(); + mem::swap(&mut self.output, &mut s); + s + } + + fn push_buffer(&self) -> String { + self.buffer.replace(String::new()) + } + + fn pop_buffer(&self, s: String) -> String { + self.buffer.replace(s) + } + + fn write_fmt(&self, args: Arguments) { + let _ = self.buffer.borrow_mut().write_fmt(args); + } +} + +pub fn show_identifier(state: &OutputState, i: &syntax::Identifier) { + state.write(&i.0); +} + +fn glsl_primitive_type_name_to_cxx(glsl_name: &str) -> &str { + hir::TypeKind::from_glsl_primitive_type_name(glsl_name) + .and_then(|kind| kind.cxx_primitive_type_name()) + .unwrap_or(glsl_name) +} + +fn add_used_global(state: &OutputState, i: &hir::SymRef) { + let mut globals = state.used_globals.borrow_mut(); + if !globals.contains(i) { + globals.push(*i); + } +} + +pub fn show_sym(state: &OutputState, i: &hir::SymRef) { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::NativeFunction(_, ref cxx_name) => { + let mut name = sym.name.as_str(); + if state.output_cxx { + name = cxx_name.unwrap_or(name); + } + state.write(name); + } + hir::SymDecl::Global(..) => { + if state.output_cxx { + add_used_global(state, i); + } + let mut name = sym.name.as_str(); + if state.output_cxx { + name = glsl_primitive_type_name_to_cxx(name); + } + state.write(name); + } + hir::SymDecl::UserFunction(..) | hir::SymDecl::Local(..) | hir::SymDecl::Struct(..) => { + let mut name = sym.name.as_str(); + // we want to replace constructor names + if state.output_cxx { + name = glsl_primitive_type_name_to_cxx(name); + } + state.write(name); + } + } +} + +pub fn show_variable(state: &OutputState, i: &hir::SymRef) { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(_, _, ty, _) => { + show_type(state, ty); + state.write(" "); + let mut name = sym.name.as_str(); + if state.output_cxx { + name = glsl_primitive_type_name_to_cxx(name); + } + state.write(name); + } + _ => panic!(), + } +} + +pub fn write_default_constructor(state: &OutputState, name: &str) { + // write default constructor + let _ = write!(state, "{}() = default;\n", name); +} + +pub fn write_constructor(state: &OutputState, name: &str, s: &hir::StructFields) { + if s.fields.len() == 1 { + state.write("explicit "); + } + let _ = write!(state, "{}(", name); + let mut first_field = true; + for field in &s.fields { + if !first_field { + state.write(", "); + } + show_type(state, &field.ty); + state.write(" "); + show_identifier_and_type(state, &field.name, &field.ty); + first_field = false; + } + state.write(") : "); + + let mut first_field = true; + for field in &s.fields { + if !first_field { + state.write(", "); + } + let _ = write!(state, "{}({})", field.name, field.name); + first_field = false; + } + state.write("{}\n"); +} + +pub fn write_convert_constructor(state: &OutputState, name: &str, s: &hir::StructFields) { + if s.fields.len() == 1 { + state.write("explicit "); + } + let _ = write!(state, "{}(", name); + let mut first_field = true; + for field in &s.fields { + if !first_field { + state.write(", "); + } + + let is_scalar = state.is_scalar.replace(true); + show_type(state, &field.ty); + state.is_scalar.set(is_scalar); + + state.write(" "); + + show_identifier_and_type(state, &field.name, &field.ty); + first_field = false; + } + state.write(")"); + + let mut first_field = true; + for hir::StructField { ty, name } in &s.fields { + if ty.array_sizes.is_none() { + if first_field { + state.write(":"); + } else { + state.write(","); + } + let _ = write!(state, "{}({})", name, name); + first_field = false; + } + } + state.write("{\n"); + for hir::StructField { ty, name } in &s.fields { + if ty.array_sizes.is_some() { + let _ = write!(state, "this->{}.convert({});\n", name, name); + } + } + state.write("}\n"); + + let _ = write!(state, "IMPLICIT {}({}_scalar s)", name, name); + let mut first_field = true; + for hir::StructField { ty, name } in &s.fields { + if ty.array_sizes.is_none() { + if first_field { + state.write(":"); + } else { + state.write(","); + } + let _ = write!(state, "{}(s.{})", name, name); + first_field = false; + } + } + state.write("{\n"); + for hir::StructField { ty, name } in &s.fields { + if ty.array_sizes.is_some() { + let _ = write!(state, "{}.convert(s.{});\n", name, name); + } + } + state.write("}\n"); +} + +pub fn write_if_then_else(state: &OutputState, name: &str, s: &hir::StructFields) { + let _ = write!( + state, + "friend {} if_then_else(I32 c, {} t, {} e) {{ return {}(\n", + name, name, name, name + ); + let mut first_field = true; + for field in &s.fields { + if !first_field { + state.write(", "); + } + let _ = write!(state, "if_then_else(c, t.{}, e.{})", field.name, field.name); + first_field = false; + } + state.write(");\n}"); +} + +pub fn show_storage_class(state: &OutputState, q: &hir::StorageClass) { + match *q { + hir::StorageClass::None => {} + hir::StorageClass::Const => { + state.write("const "); + } + hir::StorageClass::In => { + state.write("in "); + } + hir::StorageClass::Out => { + state.write("out "); + } + hir::StorageClass::FragColor(index) => { + write!(state, "layout(location = 0, index = {}) out ", index); + } + hir::StorageClass::Uniform | hir::StorageClass::Sampler(..) => { + state.write("uniform "); + } + } +} + +pub fn show_sym_decl(state: &OutputState, i: &hir::SymRef) { + let sym = state.hir.sym(*i); + match &sym.decl { + hir::SymDecl::Global(storage, ..) => { + if !state.output_cxx { + show_storage_class(state, storage) + } + if storage == &hir::StorageClass::Const { + state.write("static constexpr "); + } + let mut name = sym.name.as_str(); + if state.output_cxx { + name = glsl_primitive_type_name_to_cxx(name); + } + state.write(name); + } + hir::SymDecl::Local(storage, ..) => { + if !state.output_cxx { + show_storage_class(state, storage) + } + if storage == &hir::StorageClass::Const { + state.write("const "); + } + let mut name = sym.name.as_str(); + if state.output_cxx { + name = glsl_primitive_type_name_to_cxx(name); + } + state.write(name); + } + hir::SymDecl::Struct(s) => { + let name = sym.name.as_str(); + + if state.output_cxx { + let name_scalar = format!("{}_scalar", name); + write!(state, "struct {} {{\n", name_scalar); + let is_scalar = state.is_scalar.replace(true); + for field in &s.fields { + show_struct_field(state, field); + } + write_default_constructor(state, &name_scalar); + write_constructor(state, &name_scalar, s); + state.is_scalar.set(is_scalar); + state.write("};\n"); + } + + write!(state, "struct {} {{\n", name); + for field in &s.fields { + show_struct_field(state, field); + } + + // write if_then_else + if state.output_cxx { + write_default_constructor(state, name); + write_constructor(state, name, s); + write_convert_constructor(state, name, s); + write_if_then_else(state, name, s); + } + state.write("}"); + } + _ => panic!(), + } +} + +pub fn show_type_name(state: &OutputState, t: &syntax::TypeName) { + state.write(&t.0); +} + +pub fn show_type_specifier_non_array(state: &mut OutputState, t: &syntax::TypeSpecifierNonArray) { + if let Some(kind) = hir::TypeKind::from_primitive_type_specifier(t) { + show_type_kind(state, &kind); + } else { + match t { + syntax::TypeSpecifierNonArray::Struct(ref _s) => panic!(), //show_struct_non_declaration(state, s), + syntax::TypeSpecifierNonArray::TypeName(ref tn) => show_type_name(state, tn), + _ => unreachable!(), + } + } +} + +pub fn show_type_kind(state: &OutputState, t: &hir::TypeKind) { + if state.output_cxx { + if state.is_scalar.get() { + if let Some(name) = t.cxx_primitive_scalar_type_name() { + state.write(name); + } else if let Some(name) = t.cxx_primitive_type_name() { + let mut scalar_name = String::from(name); + scalar_name.push_str("_scalar"); + state.write(scalar_name.as_str()); + } else { + match t { + hir::TypeKind::Struct(ref s) => { + let mut scalar_name = String::from(state.hir.sym(*s).name.as_str()); + scalar_name.push_str("_scalar"); + state.write(scalar_name.as_str()); + } + _ => unreachable!(), + } + } + } else if let Some(name) = t.cxx_primitive_type_name() { + state.write(name); + } else { + match t { + hir::TypeKind::Struct(ref s) => { + state.write(state.hir.sym(*s).name.as_str()); + } + _ => unreachable!(), + } + } + } else if let Some(name) = t.glsl_primitive_type_name() { + state.write(name); + } else { + match t { + hir::TypeKind::Struct(ref s) => { + state.write(state.hir.sym(*s).name.as_str()); + } + _ => unreachable!(), + } + } +} + +pub fn show_type_specifier(state: &mut OutputState, t: &syntax::TypeSpecifier) { + show_type_specifier_non_array(state, &t.ty); + + if let Some(ref arr_spec) = t.array_specifier { + show_array_spec(state, arr_spec); + } +} + +pub fn show_type(state: &OutputState, t: &Type) { + if !state.output_cxx { + if let Some(ref precision) = t.precision { + show_precision_qualifier(state, precision); + state.write(" "); + } + } + + if state.output_cxx { + if let Some(ref array) = t.array_sizes { + state.write("Array<"); + show_type_kind(state, &t.kind); + let size = match &array.sizes[..] { + [size] => size, + _ => panic!(), + }; + state.write(","); + show_hir_expr(state, size); + state.write(">"); + } else { + show_type_kind(state, &t.kind); + } + } else { + show_type_kind(state, &t.kind); + } + + /*if let Some(ref arr_spec) = t.array_sizes { + panic!(); + }*/ +} + +/*pub fn show_fully_specified_type(state: &mut OutputState, t: &FullySpecifiedType) { + state.flat = false; + if let Some(ref qual) = t.qualifier { + if !state.output_cxx { + show_type_qualifier(state, &qual); + } else { + state.flat = + qual.qualifiers.0.iter() + .flat_map(|q| match q { syntax::TypeQualifierSpec::Interpolation(Flat) => Some(()), _ => None}) + .next().is_some(); + } + state.write(" "); + } + + show_type_specifier(state, &t.ty); +}*/ + +/*pub fn show_struct_non_declaration(state: &mut OutputState, s: &syntax::StructSpecifier) { + state.write("struct "); + + if let Some(ref name) = s.name { + let _ = write!(state, "{} ", name); + } + + state.write("{\n"); + + for field in &s.fields.0 { + show_struct_field(state, field); + } + + state.write("}"); +}*/ + +pub fn show_struct(_state: &OutputState, _s: &syntax::StructSpecifier) { + panic!(); + //show_struct_non_declaration(state, s); + //state.write(";\n"); +} + +pub fn show_struct_field(state: &OutputState, field: &hir::StructField) { + show_type(state, &field.ty); + state.write(" "); + + show_identifier_and_type(state, &field.name, &field.ty); + + state.write(";\n"); +} + +pub fn show_array_spec(state: &OutputState, a: &syntax::ArraySpecifier) { + match *a { + syntax::ArraySpecifier::Unsized => { + state.write("[]"); + } + syntax::ArraySpecifier::ExplicitlySized(ref e) => { + state.write("["); + show_expr(state, &e); + state.write("]"); + } + } +} + +pub fn show_identifier_and_type(state: &OutputState, ident: &syntax::Identifier, ty: &hir::Type) { + let _ = write!(state, "{}", ident); + + if !state.output_cxx { + if let Some(ref arr_spec) = ty.array_sizes { + show_array_sizes(state, &arr_spec); + } + } +} + +pub fn show_arrayed_identifier(state: &OutputState, ident: &syntax::ArrayedIdentifier) { + let _ = write!(state, "{}", ident.ident); + + if let Some(ref arr_spec) = ident.array_spec { + show_array_spec(state, &arr_spec); + } +} + +pub fn show_array_sizes(state: &OutputState, a: &hir::ArraySizes) { + state.write("["); + match &a.sizes[..] { + [a] => show_hir_expr(state, a), + _ => panic!(), + } + + state.write("]"); + /* + match *a { + syntax::ArraySpecifier::Unsized => { state.write("[]"); } + syntax::ArraySpecifier::ExplicitlySized(ref e) => { + state.write("["); + show_expr(state, &e); + state.write("]"); + } + }*/ +} + +pub fn show_type_qualifier(state: &OutputState, q: &hir::TypeQualifier) { + let mut qualifiers = q.qualifiers.0.iter(); + let first = qualifiers.next().unwrap(); + + show_type_qualifier_spec(state, first); + + for qual_spec in qualifiers { + state.write(" "); + show_type_qualifier_spec(state, qual_spec) + } +} + +pub fn show_type_qualifier_spec(state: &OutputState, q: &hir::TypeQualifierSpec) { + match *q { + hir::TypeQualifierSpec::Layout(ref l) => show_layout_qualifier(state, &l), + hir::TypeQualifierSpec::Parameter(ref _p) => panic!(), + hir::TypeQualifierSpec::Memory(ref _m) => panic!(), + hir::TypeQualifierSpec::Invariant => { + state.write("invariant"); + } + hir::TypeQualifierSpec::Precise => { + state.write("precise"); + } + } +} + +pub fn show_syntax_storage_qualifier(state: &OutputState, q: &syntax::StorageQualifier) { + match *q { + syntax::StorageQualifier::Const => { + state.write("const"); + } + syntax::StorageQualifier::InOut => { + state.write("inout"); + } + syntax::StorageQualifier::In => { + state.write("in"); + } + syntax::StorageQualifier::Out => { + state.write("out"); + } + syntax::StorageQualifier::Centroid => { + state.write("centroid"); + } + syntax::StorageQualifier::Patch => { + state.write("patch"); + } + syntax::StorageQualifier::Sample => { + state.write("sample"); + } + syntax::StorageQualifier::Uniform => { + state.write("uniform"); + } + syntax::StorageQualifier::Attribute => { + state.write("attribute"); + } + syntax::StorageQualifier::Varying => { + state.write("varying"); + } + syntax::StorageQualifier::Buffer => { + state.write("buffer"); + } + syntax::StorageQualifier::Shared => { + state.write("shared"); + } + syntax::StorageQualifier::Coherent => { + state.write("coherent"); + } + syntax::StorageQualifier::Volatile => { + state.write("volatile"); + } + syntax::StorageQualifier::Restrict => { + state.write("restrict"); + } + syntax::StorageQualifier::ReadOnly => { + state.write("readonly"); + } + syntax::StorageQualifier::WriteOnly => { + state.write("writeonly"); + } + syntax::StorageQualifier::Subroutine(ref n) => show_subroutine(state, &n), + } +} + +pub fn show_subroutine(state: &OutputState, types: &[syntax::TypeName]) { + state.write("subroutine"); + + if !types.is_empty() { + state.write("("); + + let mut types_iter = types.iter(); + let first = types_iter.next().unwrap(); + + show_type_name(state, first); + + for type_name in types_iter { + state.write(", "); + show_type_name(state, type_name); + } + + state.write(")"); + } +} + +pub fn show_layout_qualifier(state: &OutputState, l: &syntax::LayoutQualifier) { + let mut qualifiers = l.ids.0.iter(); + let first = qualifiers.next().unwrap(); + + state.write("layout ("); + show_layout_qualifier_spec(state, first); + + for qual_spec in qualifiers { + state.write(", "); + show_layout_qualifier_spec(state, qual_spec); + } + + state.write(")"); +} + +pub fn show_layout_qualifier_spec(state: &OutputState, l: &syntax::LayoutQualifierSpec) { + match *l { + syntax::LayoutQualifierSpec::Identifier(ref i, Some(ref e)) => { + let _ = write!(state, "{} = ", i); + show_expr(state, &e); + } + syntax::LayoutQualifierSpec::Identifier(ref i, None) => show_identifier(state, &i), + syntax::LayoutQualifierSpec::Shared => { + state.write("shared"); + } + } +} + +pub fn show_precision_qualifier(state: &OutputState, p: &syntax::PrecisionQualifier) { + match *p { + syntax::PrecisionQualifier::High => { + state.write("highp"); + } + syntax::PrecisionQualifier::Medium => { + state.write("mediump"); + } + syntax::PrecisionQualifier::Low => { + state.write("low"); + } + } +} + +pub fn show_interpolation_qualifier(state: &OutputState, i: &syntax::InterpolationQualifier) { + match *i { + syntax::InterpolationQualifier::Smooth => { + state.write("smooth"); + } + syntax::InterpolationQualifier::Flat => { + state.write("flat"); + } + syntax::InterpolationQualifier::NoPerspective => { + state.write("noperspective"); + } + } +} + +pub fn show_parameter_qualifier(state: &mut OutputState, i: &Option) { + if let Some(i) = i { + if state.output_cxx { + match *i { + hir::ParameterQualifier::Out => { + state.write("&"); + } + hir::ParameterQualifier::InOut => { + state.write("&"); + } + _ => {} + } + } else { + match *i { + hir::ParameterQualifier::Const => { + state.write("const"); + } + hir::ParameterQualifier::In => { + state.write("in"); + } + hir::ParameterQualifier::Out => { + state.write("out"); + } + hir::ParameterQualifier::InOut => { + state.write("inout"); + } + } + } + } +} + +pub fn show_float(state: &OutputState, x: f32) { + if x.fract() == 0. { + write!(state, "{}.f", x); + } else { + write!(state, "{}f", x); + } +} + +pub fn show_double(state: &OutputState, x: f64) { + // force doubles to print as floats + if x.fract() == 0. { + write!(state, "{}.f", x); + } else { + write!(state, "{}f", x); + } +} + +trait SwizzelSelectorExt { + fn to_args(&self) -> String; +} + +impl SwizzelSelectorExt for SwizzleSelector { + fn to_args(&self) -> String { + let mut s = Vec::new(); + let fs = match self.field_set { + hir::FieldSet::Rgba => ["R", "G", "B", "A"], + hir::FieldSet::Xyzw => ["X", "Y", "Z", "W"], + hir::FieldSet::Stpq => ["S", "T", "P", "Q"], + }; + for i in &self.components { + s.push(fs[*i as usize]) + } + s.join(", ") + } +} + +fn expr_run_class(state: &OutputState, expr: &hir::Expr) -> hir::RunClass { + match &expr.kind { + hir::ExprKind::Variable(i) => symbol_run_class(&state.hir.sym(*i).decl, state.vector_mask), + hir::ExprKind::IntConst(_) + | hir::ExprKind::UIntConst(_) + | hir::ExprKind::BoolConst(_) + | hir::ExprKind::FloatConst(_) + | hir::ExprKind::DoubleConst(_) => hir::RunClass::Scalar, + hir::ExprKind::Unary(_, ref e) => expr_run_class(state, e), + hir::ExprKind::Binary(_, ref l, ref r) => { + expr_run_class(state, l).merge(expr_run_class(state, r)) + } + hir::ExprKind::Ternary(ref c, ref s, ref e) => expr_run_class(state, c) + .merge(expr_run_class(state, s)) + .merge(expr_run_class(state, e)), + hir::ExprKind::Assignment(ref v, _, ref e) => { + expr_run_class(state, v).merge(expr_run_class(state, e)) + } + hir::ExprKind::Bracket(ref e, ref indx) => { + expr_run_class(state, e).merge(expr_run_class(state, indx)) + } + hir::ExprKind::FunCall(ref fun, ref args) => { + let arg_mask: u32 = args.iter().enumerate().fold(0, |mask, (idx, e)| { + if expr_run_class(state, e) == hir::RunClass::Vector { + mask | (1 << idx) + } else { + mask + } + }); + match fun { + hir::FunIdentifier::Identifier(ref sym) => match &state.hir.sym(*sym).decl { + hir::SymDecl::NativeFunction(..) => { + if arg_mask != 0 { + hir::RunClass::Vector + } else { + hir::RunClass::Scalar + } + } + hir::SymDecl::UserFunction(ref fd, ref run_class) => { + let param_mask: u32 = fd.prototype.parameters.iter().enumerate().fold( + arg_mask, + |mask, (idx, param)| { + if let hir::FunctionParameterDeclaration::Named(Some(qual), p) = + param + { + match qual { + hir::ParameterQualifier::InOut + | hir::ParameterQualifier::Out => { + if symbol_run_class( + &state.hir.sym(p.sym).decl, + arg_mask, + ) == hir::RunClass::Vector + { + mask | (1 << idx) + } else { + mask + } + } + _ => mask, + } + } else { + mask + } + }, + ); + match *run_class { + hir::RunClass::Scalar => hir::RunClass::Scalar, + hir::RunClass::Dependent(mask) => { + if (mask & param_mask) != 0 { + hir::RunClass::Vector + } else { + hir::RunClass::Scalar + } + } + _ => hir::RunClass::Vector, + } + } + hir::SymDecl::Struct(..) => { + if arg_mask != 0 { + hir::RunClass::Vector + } else { + hir::RunClass::Scalar + } + } + _ => panic!(), + }, + hir::FunIdentifier::Constructor(..) => { + if arg_mask != 0 { + hir::RunClass::Vector + } else { + hir::RunClass::Scalar + } + } + } + } + hir::ExprKind::Dot(ref e, _) => expr_run_class(state, e), + hir::ExprKind::SwizzleSelector(ref e, _) => expr_run_class(state, e), + hir::ExprKind::PostInc(ref e) => expr_run_class(state, e), + hir::ExprKind::PostDec(ref e) => expr_run_class(state, e), + hir::ExprKind::Comma(_, ref e) => expr_run_class(state, e), + hir::ExprKind::Cond(_, ref e) => expr_run_class(state, e), + hir::ExprKind::CondMask => hir::RunClass::Vector, + } +} + +pub fn show_hir_expr(state: &OutputState, expr: &hir::Expr) { + show_hir_expr_inner(state, expr, false); +} + +pub fn show_hir_expr_inner(state: &OutputState, expr: &hir::Expr, top_level: bool) { + match expr.kind { + hir::ExprKind::Variable(ref i) => show_sym(state, i), + hir::ExprKind::IntConst(ref x) => { + let _ = write!(state, "{}", x); + } + hir::ExprKind::UIntConst(ref x) => { + let _ = write!(state, "{}u", x); + } + hir::ExprKind::BoolConst(ref x) => { + let _ = write!(state, "{}", x); + } + hir::ExprKind::FloatConst(ref x) => show_float(state, *x), + hir::ExprKind::DoubleConst(ref x) => show_double(state, *x), + hir::ExprKind::Unary(ref op, ref e) => { + show_unary_op(state, &op); + state.write("("); + show_hir_expr(state, &e); + state.write(")"); + } + hir::ExprKind::Binary(ref op, ref l, ref r) => { + state.write("("); + show_hir_expr(state, &l); + state.write(")"); + show_binary_op(state, &op); + state.write("("); + show_hir_expr(state, &r); + state.write(")"); + } + hir::ExprKind::Ternary(ref c, ref s, ref e) => { + if state.output_cxx && expr_run_class(state, c) != hir::RunClass::Scalar { + state.write("if_then_else("); + show_hir_expr(state, &c); + state.write(", "); + show_hir_expr(state, &s); + state.write(", "); + show_hir_expr(state, &e); + state.write(")"); + } else { + show_hir_expr(state, &c); + state.write(" ? "); + show_hir_expr(state, &s); + state.write(" : "); + show_hir_expr(state, &e); + } + } + hir::ExprKind::Assignment(ref v, ref op, ref e) => { + let is_output = hir::is_output(v, &state.hir).is_some(); + let is_scalar_var = expr_run_class(state, v) == hir::RunClass::Scalar; + let is_scalar_expr = expr_run_class(state, e) == hir::RunClass::Scalar; + let force_scalar = is_scalar_var && !is_scalar_expr; + + if let Some(mask) = &state.mask { + let is_scalar_mask = expr_run_class(state, mask) == hir::RunClass::Scalar; + let force_scalar_mask = is_scalar_var && is_scalar_expr && !is_scalar_mask; + + if force_scalar || force_scalar_mask { + if top_level { + state.write("if ("); + } else { + state.write("("); + } + } else { + state.is_lval.set(true); + show_hir_expr(state, &v); + state.is_lval.set(false); + state.write(" = if_then_else("); + } + + if is_output && state.return_declared { + state.write("(("); + show_hir_expr(state, mask); + state.write(")&ret_mask)"); + } else { + show_hir_expr(state, mask); + } + if force_scalar || force_scalar_mask { + if top_level { + state.write("[0]) { "); + } else { + state.write("[0] ? "); + } + state.is_lval.set(true); + show_hir_expr(state, &v); + state.is_lval.set(false); + state.write(" = "); + } else { + state.write(","); + } + + if op != &syntax::AssignmentOp::Equal { + show_hir_expr(state, &v); + } + + match *op { + syntax::AssignmentOp::Equal => {} + syntax::AssignmentOp::Mult => { + state.write("*"); + } + syntax::AssignmentOp::Div => { + state.write("/"); + } + syntax::AssignmentOp::Mod => { + state.write("%"); + } + syntax::AssignmentOp::Add => { + state.write("+"); + } + syntax::AssignmentOp::Sub => { + state.write("-"); + } + syntax::AssignmentOp::LShift => { + state.write("<<"); + } + syntax::AssignmentOp::RShift => { + state.write(">>"); + } + syntax::AssignmentOp::And => { + state.write("&"); + } + syntax::AssignmentOp::Xor => { + state.write("^"); + } + syntax::AssignmentOp::Or => { + state.write("|"); + } + } + if force_scalar { + state.write("force_scalar("); + } + show_hir_expr(state, &e); + if force_scalar { + state.write(")"); + } + if force_scalar || force_scalar_mask { + if top_level { + state.write("; }"); + } else { + state.write(" : "); + show_hir_expr(state, &v); + state.write(")"); + } + } else { + state.write(","); + show_hir_expr(state, &v); + state.write(")"); + } + } else { + state.is_lval.set(true); + show_hir_expr(state, &v); + state.is_lval.set(false); + state.write(" "); + + if is_output && state.return_declared { + state.write("= "); + if force_scalar { + state.write("force_scalar("); + } + state.write("if_then_else(ret_mask,"); + + if op != &syntax::AssignmentOp::Equal { + show_hir_expr(state, &v); + } + + match *op { + syntax::AssignmentOp::Equal => {} + syntax::AssignmentOp::Mult => { + state.write("*"); + } + syntax::AssignmentOp::Div => { + state.write("/"); + } + syntax::AssignmentOp::Mod => { + state.write("%"); + } + syntax::AssignmentOp::Add => { + state.write("+"); + } + syntax::AssignmentOp::Sub => { + state.write("-"); + } + syntax::AssignmentOp::LShift => { + state.write("<<"); + } + syntax::AssignmentOp::RShift => { + state.write(">>"); + } + syntax::AssignmentOp::And => { + state.write("&"); + } + syntax::AssignmentOp::Xor => { + state.write("^"); + } + syntax::AssignmentOp::Or => { + state.write("|"); + } + } + show_hir_expr(state, &e); + state.write(","); + show_hir_expr(state, &v); + state.write(")"); + } else { + show_assignment_op(state, &op); + state.write(" "); + if force_scalar { + state.write("force_scalar("); + } + show_hir_expr(state, &e); + } + + if force_scalar { + state.write(")"); + } + } + } + hir::ExprKind::Bracket(ref e, ref indx) => { + show_hir_expr(state, &e); + state.write("["); + show_hir_expr(state, indx); + state.write("]"); + } + hir::ExprKind::FunCall(ref fun, ref args) => { + let mut cond_mask: u32 = 0; + let mut adapt_mask: u32 = 0; + let mut has_ret = false; + let mut array_constructor = false; + + let mut arg_mask: u32 = 0; + for (idx, e) in args.iter().enumerate() { + if expr_run_class(state, e) == hir::RunClass::Vector { + arg_mask |= 1 << idx; + } + } + + match fun { + hir::FunIdentifier::Constructor(t) => { + let is_scalar = state.is_scalar.replace(arg_mask == 0); + show_type(state, t); + state.is_scalar.set(is_scalar); + array_constructor = t.array_sizes.is_some(); + } + hir::FunIdentifier::Identifier(name) => { + if state.output_cxx { + let sym = state.hir.sym(*name); + match &sym.decl { + hir::SymDecl::NativeFunction(..) => { + if sym.name == "texelFetchOffset" && args.len() >= 4 { + if let Some((sampler, base, x, y)) = hir::get_texel_fetch_offset( + &state.hir, &args[0], &args[1], &args[3], + ) { + let base_sym = state.hir.sym(base); + if symbol_run_class(&base_sym.decl, state.vector_mask) + == hir::RunClass::Scalar + { + let sampler_sym = state.hir.sym(sampler); + add_used_global(state, &sampler); + if let hir::SymDecl::Global(..) = &base_sym.decl { + add_used_global(state, &base); + } + if y != 0 { + write!( + state, + "{}_{}_fetch[{}+{}*{}->stride]", + sampler_sym.name, + base_sym.name, + x, + y, + sampler_sym.name + ); + } else { + write!( + state, + "{}_{}_fetch[{}]", + sampler_sym.name, base_sym.name, x + ); + } + return; + } + } + } + show_sym(state, name) + } + hir::SymDecl::UserFunction(ref fd, ref _run_class) => { + if (state.mask.is_some() || state.return_declared) && + !fd.globals.is_empty() + { + cond_mask |= 1 << 31; + } + let mut param_mask: u32 = 0; + for (idx, (param, e)) in + fd.prototype.parameters.iter().zip(args.iter()).enumerate() + { + if let hir::FunctionParameterDeclaration::Named(qual, p) = param + { + if symbol_run_class(&state.hir.sym(p.sym).decl, arg_mask) + == hir::RunClass::Vector + { + param_mask |= 1 << idx; + } + match qual { + Some(hir::ParameterQualifier::InOut) + | Some(hir::ParameterQualifier::Out) => { + if state.mask.is_some() || state.return_declared { + cond_mask |= 1 << idx; + } + if (!arg_mask & param_mask & (1 << idx)) != 0 { + if adapt_mask == 0 { + state.write(if top_level { + "{ " + } else { + "({ " + }); + } + show_type(state, &p.ty); + write!(state, " _arg{}_ = ", idx); + show_hir_expr(state, e); + state.write("; "); + adapt_mask |= 1 << idx; + } + } + _ => {} + } + } + } + if adapt_mask != 0 && + fd.prototype.ty.kind != hir::TypeKind::Void && + !top_level + { + state.write("auto _ret_ = "); + has_ret = true; + } + show_sym(state, name); + let mut deps = state.deps.borrow_mut(); + let dep_key = ( + *name, + if cond_mask != 0 { + param_mask | (1 << 31) + } else { + param_mask + }, + ); + if !deps.contains(&dep_key) { + deps.push(dep_key); + } + } + hir::SymDecl::Struct(..) => { + show_sym(state, name); + if arg_mask == 0 { + state.write("_scalar"); + } + } + _ => panic!("bad identifier to function call"), + } + } + } + } + + if array_constructor { + state.write("{{"); + } else { + state.write("("); + } + + for (idx, e) in args.iter().enumerate() { + if idx != 0 { + state.write(", "); + } + if (adapt_mask & (1 << idx)) != 0 { + write!(state, "_arg{}_", idx); + } else { + show_hir_expr(state, e); + } + } + + if cond_mask != 0 { + if !args.is_empty() { + state.write(", "); + } + if let Some(mask) = &state.mask { + if state.return_declared { + state.write("("); + show_hir_expr(state, mask); + state.write(")&ret_mask"); + } else { + show_hir_expr(state, mask); + } + } else if state.return_declared { + state.write("ret_mask"); + } else { + state.write("~0"); + } + } + + if array_constructor { + state.write("}}"); + } else { + state.write(")"); + } + + if adapt_mask != 0 { + state.write("; "); + for (idx, e) in args.iter().enumerate() { + if (adapt_mask & (1 << idx)) != 0 { + state.is_lval.set(true); + show_hir_expr(state, e); + state.is_lval.set(false); + write!(state, " = force_scalar(_arg{}_); ", idx); + } + } + if has_ret { + state.write("_ret_; })"); + } else { + state.write(if top_level { "}" } else { "})" }); + } + } + } + hir::ExprKind::Dot(ref e, ref i) => { + state.write("("); + show_hir_expr(state, &e); + state.write(")"); + state.write("."); + show_identifier(state, i); + } + hir::ExprKind::SwizzleSelector(ref e, ref s) => { + if state.output_cxx { + if let hir::ExprKind::Variable(ref sym) = &e.kind { + if state.hir.sym(*sym).name == "gl_FragCoord" { + state.used_fragcoord.set( + s.components.iter().fold( + state.used_fragcoord.get(), + |used, c| used | (1 << c))); + } + } + state.write("("); + show_hir_expr(state, &e); + if state.is_lval.get() && s.components.len() > 1 { + state.write(").lsel("); + } else { + state.write(").sel("); + } + state.write(&s.to_args()); + state.write(")"); + } else { + state.write("("); + show_hir_expr(state, &e); + state.write(")"); + state.write("."); + state.write(&s.to_string()); + } + } + hir::ExprKind::PostInc(ref e) => { + show_hir_expr(state, &e); + state.write("++"); + } + hir::ExprKind::PostDec(ref e) => { + show_hir_expr(state, &e); + state.write("--"); + } + hir::ExprKind::Comma(ref a, ref b) => { + show_hir_expr(state, &a); + state.write(", "); + show_hir_expr(state, &b); + } + hir::ExprKind::Cond(index, _) => { + write!(state, "_c{}_", index); + } + hir::ExprKind::CondMask => { + state.write("_cond_mask_"); + } + } +} + +pub fn show_expr(state: &OutputState, expr: &syntax::Expr) { + match *expr { + syntax::Expr::Variable(ref i) => show_identifier(state, &i), + syntax::Expr::IntConst(ref x) => { + let _ = write!(state, "{}", x); + } + syntax::Expr::UIntConst(ref x) => { + let _ = write!(state, "{}u", x); + } + syntax::Expr::BoolConst(ref x) => { + let _ = write!(state, "{}", x); + } + syntax::Expr::FloatConst(ref x) => show_float(state, *x), + syntax::Expr::DoubleConst(ref x) => show_double(state, *x), + syntax::Expr::Unary(ref op, ref e) => { + show_unary_op(state, &op); + state.write("("); + show_expr(state, &e); + state.write(")"); + } + syntax::Expr::Binary(ref op, ref l, ref r) => { + state.write("("); + show_expr(state, &l); + state.write(")"); + show_binary_op(state, &op); + state.write("("); + show_expr(state, &r); + state.write(")"); + } + syntax::Expr::Ternary(ref c, ref s, ref e) => { + show_expr(state, &c); + state.write(" ? "); + show_expr(state, &s); + state.write(" : "); + show_expr(state, &e); + } + syntax::Expr::Assignment(ref v, ref op, ref e) => { + show_expr(state, &v); + state.write(" "); + show_assignment_op(state, &op); + state.write(" "); + show_expr(state, &e); + } + syntax::Expr::Bracket(ref e, ref a) => { + show_expr(state, &e); + show_array_spec(state, &a); + } + syntax::Expr::FunCall(ref fun, ref args) => { + show_function_identifier(state, &fun); + state.write("("); + + if !args.is_empty() { + let mut args_iter = args.iter(); + let first = args_iter.next().unwrap(); + show_expr(state, first); + + for e in args_iter { + state.write(", "); + show_expr(state, e); + } + } + + state.write(")"); + } + syntax::Expr::Dot(ref e, ref i) => { + state.write("("); + show_expr(state, &e); + state.write(")"); + state.write("."); + show_identifier(state, &i); + } + syntax::Expr::PostInc(ref e) => { + show_expr(state, &e); + state.write("++"); + } + syntax::Expr::PostDec(ref e) => { + show_expr(state, &e); + state.write("--"); + } + syntax::Expr::Comma(ref a, ref b) => { + show_expr(state, &a); + state.write(", "); + show_expr(state, &b); + } + } +} + +pub fn show_unary_op(state: &OutputState, op: &syntax::UnaryOp) { + match *op { + syntax::UnaryOp::Inc => { + state.write("++"); + } + syntax::UnaryOp::Dec => { + state.write("--"); + } + syntax::UnaryOp::Add => { + state.write("+"); + } + syntax::UnaryOp::Minus => { + state.write("-"); + } + syntax::UnaryOp::Not => { + state.write("!"); + } + syntax::UnaryOp::Complement => { + state.write("~"); + } + } +} + +pub fn show_binary_op(state: &OutputState, op: &syntax::BinaryOp) { + match *op { + syntax::BinaryOp::Or => { + state.write("||"); + } + syntax::BinaryOp::Xor => { + state.write("^^"); + } + syntax::BinaryOp::And => { + state.write("&&"); + } + syntax::BinaryOp::BitOr => { + state.write("|"); + } + syntax::BinaryOp::BitXor => { + state.write("^"); + } + syntax::BinaryOp::BitAnd => { + state.write("&"); + } + syntax::BinaryOp::Equal => { + state.write("=="); + } + syntax::BinaryOp::NonEqual => { + state.write("!="); + } + syntax::BinaryOp::LT => { + state.write("<"); + } + syntax::BinaryOp::GT => { + state.write(">"); + } + syntax::BinaryOp::LTE => { + state.write("<="); + } + syntax::BinaryOp::GTE => { + state.write(">="); + } + syntax::BinaryOp::LShift => { + state.write("<<"); + } + syntax::BinaryOp::RShift => { + state.write(">>"); + } + syntax::BinaryOp::Add => { + state.write("+"); + } + syntax::BinaryOp::Sub => { + state.write("-"); + } + syntax::BinaryOp::Mult => { + state.write("*"); + } + syntax::BinaryOp::Div => { + state.write("/"); + } + syntax::BinaryOp::Mod => { + state.write("%"); + } + } +} + +pub fn show_assignment_op(state: &OutputState, op: &syntax::AssignmentOp) { + match *op { + syntax::AssignmentOp::Equal => { + state.write("="); + } + syntax::AssignmentOp::Mult => { + state.write("*="); + } + syntax::AssignmentOp::Div => { + state.write("/="); + } + syntax::AssignmentOp::Mod => { + state.write("%="); + } + syntax::AssignmentOp::Add => { + state.write("+="); + } + syntax::AssignmentOp::Sub => { + state.write("-="); + } + syntax::AssignmentOp::LShift => { + state.write("<<="); + } + syntax::AssignmentOp::RShift => { + state.write(">>="); + } + syntax::AssignmentOp::And => { + state.write("&="); + } + syntax::AssignmentOp::Xor => { + state.write("^="); + } + syntax::AssignmentOp::Or => { + state.write("|="); + } + } +} + +pub fn show_function_identifier(state: &OutputState, i: &syntax::FunIdentifier) { + match *i { + syntax::FunIdentifier::Identifier(ref n) => show_identifier(state, &n), + syntax::FunIdentifier::Expr(ref e) => show_expr(state, &*e), + } +} + +pub fn show_hir_function_identifier(state: &OutputState, i: &hir::FunIdentifier) { + match *i { + hir::FunIdentifier::Identifier(ref n) => show_sym(state, n), + hir::FunIdentifier::Constructor(ref t) => show_type(state, &*t), + } +} + +pub fn show_declaration(state: &mut OutputState, d: &hir::Declaration) { + show_indent(state); + match *d { + hir::Declaration::FunctionPrototype(ref proto) => { + if !state.output_cxx { + show_function_prototype(state, &proto); + state.write(";\n"); + } + } + hir::Declaration::InitDeclaratorList(ref list) => { + show_init_declarator_list(state, &list); + state.write(";\n"); + + if state.output_cxx { + let base = list.head.name; + let base_sym = state.hir.sym(base); + if let hir::SymDecl::Local(..) = &base_sym.decl { + if symbol_run_class(&base_sym.decl, state.vector_mask) == hir::RunClass::Scalar + { + let mut texel_fetches = state.texel_fetches.borrow_mut(); + while let Some(idx) = texel_fetches.iter().position(|&(_, b, _)| b == base) + { + let (sampler, _, offsets) = texel_fetches.remove(idx); + let sampler_sym = state.hir.sym(sampler); + define_texel_fetch_ptr(state, &base_sym, &sampler_sym, &offsets); + } + } + } + } + } + hir::Declaration::Precision(ref qual, ref ty) => { + if !state.output_cxx { + show_precision_qualifier(state, &qual); + show_type_specifier(state, &ty); + state.write(";\n"); + } + } + hir::Declaration::Block(ref _block) => { + panic!(); + //show_block(state, &block); + //state.write(";\n"); + } + hir::Declaration::Global(ref qual, ref identifiers) => { + show_type_qualifier(state, &qual); + + if !identifiers.is_empty() { + let mut iter = identifiers.iter(); + let first = iter.next().unwrap(); + show_identifier(state, first); + + for identifier in iter { + let _ = write!(state, ", {}", identifier); + } + } + + state.write(";\n"); + } + hir::Declaration::StructDefinition(ref sym) => { + show_sym_decl(state, sym); + + state.write(";\n"); + } + } +} + +pub fn show_function_prototype(state: &mut OutputState, fp: &hir::FunctionPrototype) { + let is_scalar = state.is_scalar.replace(!state.return_vector); + show_type(state, &fp.ty); + state.is_scalar.set(is_scalar); + + state.write(" "); + show_identifier(state, &fp.name); + + state.write("("); + + if !fp.parameters.is_empty() { + let mut iter = fp.parameters.iter(); + let first = iter.next().unwrap(); + show_function_parameter_declaration(state, first); + + for param in iter { + state.write(", "); + show_function_parameter_declaration(state, param); + } + } + + if state.output_cxx && (state.vector_mask & (1 << 31)) != 0 { + if !fp.parameters.is_empty() { + state.write(", "); + } + state.write("I32 _cond_mask_"); + } + + state.write(")"); +} + +pub fn show_function_parameter_declaration( + state: &mut OutputState, + p: &hir::FunctionParameterDeclaration, +) { + match *p { + hir::FunctionParameterDeclaration::Named(ref qual, ref fpd) => { + if state.output_cxx { + let is_scalar = state.is_scalar.replace( + symbol_run_class(&state.hir.sym(fpd.sym).decl, state.vector_mask) + == hir::RunClass::Scalar, + ); + show_type(state, &fpd.ty); + state.is_scalar.set(is_scalar); + show_parameter_qualifier(state, qual); + } else { + show_parameter_qualifier(state, qual); + state.write(" "); + show_type(state, &fpd.ty); + } + state.write(" "); + show_identifier_and_type(state, &fpd.name, &fpd.ty); + } + hir::FunctionParameterDeclaration::Unnamed(ref qual, ref ty) => { + if state.output_cxx { + show_type_specifier(state, ty); + show_parameter_qualifier(state, qual); + } else { + show_parameter_qualifier(state, qual); + state.write(" "); + show_type_specifier(state, ty); + } + } + } +} + +pub fn show_init_declarator_list(state: &mut OutputState, i: &hir::InitDeclaratorList) { + show_single_declaration(state, &i.head); + + for decl in &i.tail { + state.write(", "); + show_single_declaration_no_type(state, decl); + } +} + +pub fn show_single_declaration(state: &mut OutputState, d: &hir::SingleDeclaration) { + if state.output_cxx { + show_single_declaration_cxx(state, d) + } else { + show_single_declaration_glsl(state, d) + } +} + +pub fn show_single_declaration_glsl(state: &mut OutputState, d: &hir::SingleDeclaration) { + if let Some(ref qual) = d.qualifier { + show_type_qualifier(state, &qual); + state.write(" "); + } + + let sym = state.hir.sym(d.name); + match &sym.decl { + hir::SymDecl::Global(storage, interpolation, ..) => { + show_storage_class(state, storage); + if let Some(i) = interpolation { + show_interpolation_qualifier(state, i); + } + } + hir::SymDecl::Local(storage, ..) => show_storage_class(state, storage), + _ => panic!("should be variable"), + } + + if let Some(ty_def) = d.ty_def { + show_sym_decl(state, &ty_def); + } else { + show_type(state, &d.ty); + } + + state.write(" "); + state.write(sym.name.as_str()); + + if let Some(ref arr_spec) = d.ty.array_sizes { + show_array_sizes(state, &arr_spec); + } + + if let Some(ref initializer) = d.initializer { + state.write(" = "); + show_initializer(state, initializer); + } +} + +fn symbol_run_class(decl: &hir::SymDecl, vector_mask: u32) -> hir::RunClass { + let run_class = match decl { + hir::SymDecl::Global(_, _, _, run_class) => *run_class, + hir::SymDecl::Local(_, _, run_class) => *run_class, + _ => hir::RunClass::Vector, + }; + match run_class { + hir::RunClass::Scalar => hir::RunClass::Scalar, + hir::RunClass::Dependent(mask) => { + if (mask & vector_mask) != 0 { + hir::RunClass::Vector + } else { + hir::RunClass::Scalar + } + } + _ => hir::RunClass::Vector, + } +} + +pub fn show_single_declaration_cxx(state: &mut OutputState, d: &hir::SingleDeclaration) { + let sym = state.hir.sym(d.name); + if state.kind == ShaderKind::Vertex { + match &sym.decl { + hir::SymDecl::Global(hir::StorageClass::Uniform, ..) | + hir::SymDecl::Global(hir::StorageClass::Sampler(_), ..) | + hir::SymDecl::Global(hir::StorageClass::Out, _, _, hir::RunClass::Scalar) => { + state.write("// "); + } + _ => {} + } + } else { + match &sym.decl { + hir::SymDecl::Global(hir::StorageClass::FragColor(index), ..) => { + let fragcolor = match index { + 0 => "gl_FragColor", + 1 => "gl_SecondaryFragColor", + _ => panic!(), + }; + write!(state, "#define {} {}\n", sym.name, fragcolor); + show_indent(state); + state.write("// "); + } + hir::SymDecl::Global(hir::StorageClass::Out, ..) => { + write!(state, "#define {} gl_FragColor\n", sym.name); + show_indent(state); + state.write("// "); + } + hir::SymDecl::Global(hir::StorageClass::Uniform, ..) | + hir::SymDecl::Global(hir::StorageClass::Sampler(_), ..) | + hir::SymDecl::Global(hir::StorageClass::In, _, _, hir::RunClass::Scalar) => { + state.write("// "); + } + _ => {} + } + } + let is_scalar = state + .is_scalar + .replace(symbol_run_class(&sym.decl, state.vector_mask) == hir::RunClass::Scalar); + + if let Some(ref _array) = d.ty.array_sizes { + show_type(state, &d.ty); + } else { + if let Some(ty_def) = d.ty_def { + show_sym_decl(state, &ty_def); + } else { + show_type(state, &d.ty); + } + } + + // XXX: this is pretty grotty + state.write(" "); + show_sym_decl(state, &d.name); + + state.is_scalar.set(is_scalar); + + if let Some(ref initializer) = d.initializer { + state.write(" = "); + show_initializer(state, initializer); + } +} + +pub fn show_single_declaration_no_type(state: &OutputState, d: &hir::SingleDeclarationNoType) { + show_arrayed_identifier(state, &d.ident); + + if let Some(ref initializer) = d.initializer { + state.write(" = "); + show_initializer(state, initializer); + } +} + +pub fn show_initializer(state: &OutputState, i: &hir::Initializer) { + match *i { + hir::Initializer::Simple(ref e) => show_hir_expr(state, e), + hir::Initializer::List(ref list) => { + let mut iter = list.0.iter(); + let first = iter.next().unwrap(); + + state.write("{ "); + show_initializer(state, first); + + for ini in iter { + state.write(", "); + show_initializer(state, ini); + } + + state.write(" }"); + } + } +} + +/* +pub fn show_block(state: &mut OutputState, b: &hir::Block) { + show_type_qualifier(state, &b.qualifier); + state.write(" "); + show_identifier(state, &b.name); + state.write(" {"); + + for field in &b.fields { + show_struct_field(state, field); + state.write("\n"); + } + state.write("}"); + + if let Some(ref ident) = b.identifier { + show_arrayed_identifier(state, ident); + } +} +*/ + +// This is a hack to run through the first time with an empty writter to find if 'return' is declared. +pub fn has_conditional_return(state: &mut OutputState, cst: &hir::CompoundStatement) -> bool { + let buffer = state.push_buffer(); + show_compound_statement(state, cst); + state.pop_buffer(buffer); + let result = state.return_declared; + state.return_declared = false; + result +} + +fn define_texel_fetch_ptr( + state: &OutputState, + base_sym: &hir::Symbol, + sampler_sym: &hir::Symbol, + offsets: &hir::TexelFetchOffsets, +) { + show_indent(state); + if let hir::SymDecl::Global(_, _, ty, _) = &sampler_sym.decl { + match ty.kind { + hir::TypeKind::Sampler2D + | hir::TypeKind::Sampler2DRect => { + write!( + state, + "vec4_scalar* {}_{}_fetch = ", + sampler_sym.name, base_sym.name + ); + } + hir::TypeKind::ISampler2D => { + write!( + state, + "ivec4_scalar* {}_{}_fetch = ", + sampler_sym.name, base_sym.name + ); + } + _ => panic!(), + } + } else { + panic!(); + } + write!( + state, + "texelFetchPtr({}, {}, {}, {}, {}, {});\n", + sampler_sym.name, base_sym.name, offsets.min_x, offsets.max_x, offsets.min_y, offsets.max_y + ); +} + +pub fn show_function_definition( + state: &mut OutputState, + fd: &hir::FunctionDefinition, + vector_mask: u32, +) { + // println!("start {:?} {:?}", fd.prototype.name, vector_mask); + if state.output_cxx && fd.prototype.name.as_str() == "main" { + state.write("ALWAYS_INLINE "); + } + show_function_prototype(state, &fd.prototype); + state.write(" "); + state.return_type = Some(Box::new(fd.prototype.ty.clone())); + + if state.output_cxx && (vector_mask & (1 << 31)) != 0 { + state.mask = Some(Box::new(hir::Expr { + kind: hir::ExprKind::CondMask, + ty: hir::Type::new(hir::TypeKind::Bool), + })); + } + + show_indent(state); + state.write("{\n"); + + state.indent(); + if has_conditional_return(state, &fd.body) { + show_indent(state); + state.write(if state.return_vector { + "I32" + } else { + "int32_t" + }); + state.write(" ret_mask = "); + if let Some(mask) = &state.mask { + show_hir_expr(state, mask); + } else { + state.write("~0"); + } + state.write(";\n"); + // XXX: the cloning here is bad + show_indent(state); + if fd.prototype.ty != Type::new(hir::TypeKind::Void) { + let is_scalar = state.is_scalar.replace(!state.return_vector); + show_type(state, &state.return_type.clone().unwrap()); + state.write(" ret;\n"); + state.is_scalar.set(is_scalar); + } + } + + if state.output_cxx { + let mut texel_fetches = state.texel_fetches.borrow_mut(); + texel_fetches.clear(); + for ((sampler, base), offsets) in fd.texel_fetches.iter() { + let base_sym = state.hir.sym(*base); + if symbol_run_class(&base_sym.decl, vector_mask) == hir::RunClass::Scalar { + add_used_global(state, sampler); + let sampler_sym = state.hir.sym(*sampler); + match &base_sym.decl { + hir::SymDecl::Global(..) => { + add_used_global(state, base); + define_texel_fetch_ptr(state, &base_sym, &sampler_sym, &offsets); + } + hir::SymDecl::Local(..) => { + if fd.prototype.has_parameter(*base) { + define_texel_fetch_ptr(state, &base_sym, &sampler_sym, &offsets); + } else { + texel_fetches.push((*sampler, *base, offsets.clone())); + } + } + _ => panic!(), + } + } + } + } + + for st in &fd.body.statement_list { + show_statement(state, st); + } + + if state.return_declared { + show_indent(state); + if fd.prototype.ty == Type::new(hir::TypeKind::Void) { + state.write("return;\n"); + } else { + state.write("return ret;\n"); + } + } + state.outdent(); + + show_indent(state); + state.write("}\n"); + // println!("end {:?}", fd.prototype.name); + + state.return_type = None; + state.return_declared = false; + state.mask = None; +} + +pub fn show_compound_statement(state: &mut OutputState, cst: &hir::CompoundStatement) { + show_indent(state); + state.write("{\n"); + + state.indent(); + for st in &cst.statement_list { + show_statement(state, st); + } + state.outdent(); + + show_indent(state); + state.write("}\n"); +} + +pub fn show_statement(state: &mut OutputState, st: &hir::Statement) { + match *st { + hir::Statement::Compound(ref cst) => show_compound_statement(state, cst), + hir::Statement::Simple(ref sst) => show_simple_statement(state, sst), + } +} + +pub fn show_simple_statement(state: &mut OutputState, sst: &hir::SimpleStatement) { + match *sst { + hir::SimpleStatement::Declaration(ref d) => show_declaration(state, d), + hir::SimpleStatement::Expression(ref e) => show_expression_statement(state, e), + hir::SimpleStatement::Selection(ref s) => show_selection_statement(state, s), + hir::SimpleStatement::Switch(ref s) => show_switch_statement(state, s), + hir::SimpleStatement::Iteration(ref i) => show_iteration_statement(state, i), + hir::SimpleStatement::Jump(ref j) => show_jump_statement(state, j), + } +} + +pub fn show_indent(state: &OutputState) { + for _ in 0 .. state.indent { + state.write(" "); + } +} + +pub fn show_expression_statement(state: &mut OutputState, est: &hir::ExprStatement) { + show_indent(state); + + if let Some(ref e) = *est { + show_hir_expr_inner(state, e, true); + } + + state.write(";\n"); +} + +pub fn show_selection_statement(state: &mut OutputState, sst: &hir::SelectionStatement) { + show_indent(state); + + if state.output_cxx && + (state.return_declared || expr_run_class(state, &sst.cond) != hir::RunClass::Scalar) + { + let (cond_index, mask) = if state.mask.is_none() || sst.else_stmt.is_some() { + let cond = sst.cond.clone(); + state.cond_index += 1; + let cond_index = state.cond_index; + write!(state, "auto _c{}_ = ", cond_index); + show_hir_expr(state, &cond); + state.write(";\n"); + ( + cond_index, + Box::new(hir::Expr { + kind: hir::ExprKind::Cond(cond_index, cond), + ty: hir::Type::new(hir::TypeKind::Bool), + }), + ) + } else { + (0, sst.cond.clone()) + }; + + let previous = mem::replace(&mut state.mask, None); + state.mask = Some(match previous.clone() { + Some(e) => { + let cond = Box::new(hir::Expr { + kind: hir::ExprKind::Binary(syntax::BinaryOp::BitAnd, e, mask.clone()), + ty: hir::Type::new(hir::TypeKind::Bool), + }); + state.cond_index += 1; + let nested_cond_index = state.cond_index; + show_indent(state); + write!(state, "auto _c{}_ = ", nested_cond_index); + show_hir_expr(state, &cond); + state.write(";\n"); + Box::new(hir::Expr { + kind: hir::ExprKind::Cond(nested_cond_index, cond), + ty: hir::Type::new(hir::TypeKind::Bool), + }) + } + None => mask.clone(), + }); + + show_statement(state, &sst.body); + state.mask = previous; + + if let Some(rest) = &sst.else_stmt { + // invert the condition + let inverted_cond = Box::new(hir::Expr { + kind: hir::ExprKind::Unary(UnaryOp::Complement, mask), + ty: hir::Type::new(hir::TypeKind::Bool), + }); + let previous = mem::replace(&mut state.mask, None); + state.mask = Some(match previous.clone() { + Some(e) => { + let cond = Box::new(hir::Expr { + kind: hir::ExprKind::Binary(syntax::BinaryOp::BitAnd, e, inverted_cond), + ty: hir::Type::new(hir::TypeKind::Bool), + }); + show_indent(state); + write!(state, "_c{}_ = ", cond_index); + show_hir_expr(state, &cond); + state.write(";\n"); + Box::new(hir::Expr { + kind: hir::ExprKind::Cond(cond_index, cond), + ty: hir::Type::new(hir::TypeKind::Bool), + }) + } + None => inverted_cond, + }); + + show_statement(state, rest); + state.mask = previous; + } + } else { + state.write("if ("); + show_hir_expr(state, &sst.cond); + state.write(") {\n"); + + state.indent(); + show_statement(state, &sst.body); + state.outdent(); + + show_indent(state); + if let Some(rest) = &sst.else_stmt { + state.write("} else "); + show_statement(state, rest); + } else { + state.write("}\n"); + } + } +} + +fn case_stmts_to_if_stmts(stmts: &[Statement], last: bool) -> (Option>, bool) { + // Look for jump statements and remove them + // We currently are pretty strict on the form that the statement + // list needs to be in. This can be loosened as needed. + let mut fallthrough = false; + let cstmt = match &stmts[..] { + [hir::Statement::Compound(c)] => match c.statement_list.split_last() { + Some((hir::Statement::Simple(s), rest)) => match **s { + hir::SimpleStatement::Jump(hir::JumpStatement::Break) => hir::CompoundStatement { + statement_list: rest.to_owned(), + }, + _ => panic!("fall through not supported"), + }, + _ => panic!("empty compound"), + }, + [hir::Statement::Simple(s)] => { + match **s { + hir::SimpleStatement::Jump(hir::JumpStatement::Break) => hir::CompoundStatement { + statement_list: Vec::new(), + }, + _ => { + if last { + // we don't need a break at the end + hir::CompoundStatement { + statement_list: vec![hir::Statement::Simple(s.clone())], + } + } else { + panic!("fall through not supported {:?}", s) + } + } + } + } + [] => return (None, true), + stmts => match stmts.split_last() { + Some((hir::Statement::Simple(s), rest)) => match **s { + hir::SimpleStatement::Jump(hir::JumpStatement::Break) => hir::CompoundStatement { + statement_list: rest.to_owned(), + }, + _ => { + if !last { + fallthrough = true; + } + hir::CompoundStatement { + statement_list: stmts.to_owned(), + } + } + }, + _ => panic!("unexpected empty"), + }, + }; + let stmts = Box::new(hir::Statement::Compound(Box::new(cstmt))); + (Some(stmts), fallthrough) +} + +fn build_selection<'a, I: Iterator>( + head: &Box, + case: &hir::Case, + mut cases: I, + default: Option<&hir::Case>, + previous_condition: Option>, + previous_stmts: Option>, +) -> hir::SelectionStatement { + let cond = match &case.label { + hir::CaseLabel::Case(e) => Some(Box::new(hir::Expr { + kind: hir::ExprKind::Binary(syntax::BinaryOp::Equal, head.clone(), e.clone()), + ty: hir::Type::new(hir::TypeKind::Bool), + })), + hir::CaseLabel::Def => None, + }; + + // if we have two conditions join them + let cond = match (&previous_condition, &cond) { + (Some(prev), Some(cond)) => Some(Box::new(hir::Expr { + kind: hir::ExprKind::Binary(syntax::BinaryOp::Or, prev.clone(), cond.clone()), + ty: hir::Type::new(hir::TypeKind::Bool), + })), + (_, cond) => cond.clone(), + }; + + /* + + // find the next case that's not a default + let next_case = loop { + match cases.next() { + Some(hir::Case { label: hir::CaseLabel::Def, ..}) => { }, + case => break case, + } + };*/ + + let (cond, body, else_stmt) = match (cond, cases.next()) { + (None, Some(next_case)) => { + assert!(previous_stmts.is_none()); + // default so just move on to the next + return build_selection(head, next_case, cases, default, None, None); + } + (Some(cond), Some(next_case)) => { + assert!(previous_stmts.is_none()); + let (stmts, fallthrough) = case_stmts_to_if_stmts(&case.stmts, false); + if !fallthrough && stmts.is_some() { + ( + cond, + stmts.unwrap(), + Some(Box::new(hir::Statement::Simple(Box::new( + hir::SimpleStatement::Selection(build_selection( + head, next_case, cases, default, None, None, + )), + )))), + ) + } else { + // empty so fall through to the next + return build_selection(head, next_case, cases, default, Some(cond), stmts); + } + } + (Some(cond), None) => { + // non-default last + assert!(previous_stmts.is_none()); + let (stmts, _) = case_stmts_to_if_stmts(&case.stmts, default.is_none()); + let stmts = stmts.expect("empty case labels unsupported at the end"); + // add the default case at the end if we have one + ( + cond, + stmts, + match default { + Some(default) => { + let (default_stmts, fallthrough) = + case_stmts_to_if_stmts(&default.stmts, true); + assert!(!fallthrough); + Some(default_stmts.expect("empty default unsupported")) + } + None => None, + }, + ) + } + (None, None) => { + // default, last + + assert!(default.is_some()); + + let (stmts, fallthrough) = case_stmts_to_if_stmts(&case.stmts, true); + let stmts = stmts.expect("empty default unsupported"); + assert!(!fallthrough); + + match previous_stmts { + Some(previous_stmts) => { + let cond = previous_condition.expect("must have previous condition"); + (cond, previous_stmts, Some(stmts)) + } + None => { + let cond = Box::new(hir::Expr { + kind: hir::ExprKind::BoolConst(true), + ty: hir::Type::new(hir::TypeKind::Bool), + }); + (cond, stmts, None) + } + } + } + }; + + hir::SelectionStatement { + cond, + body, + else_stmt, + } +} + +pub fn lower_switch_to_ifs(sst: &hir::SwitchStatement) -> hir::SelectionStatement { + let default = sst.cases.iter().find(|x| x.label == hir::CaseLabel::Def); + let mut cases = sst.cases.iter(); + let r = build_selection(&sst.head, cases.next().unwrap(), cases, default, None, None); + r +} + +fn is_declaration(stmt: &hir::Statement) -> bool { + if let hir::Statement::Simple(s) = stmt { + if let hir::SimpleStatement::Declaration(..) = **s { + return true; + } + } + return false; +} + +pub fn show_switch_statement(state: &mut OutputState, sst: &hir::SwitchStatement) { + if state.output_cxx && expr_run_class(state, &sst.head) != hir::RunClass::Scalar { + // XXX: when lowering switches we end up with a mask that has + // a bunch of mutually exclusive conditions. + // It would be nice if we could fold them together. + let ifs = lower_switch_to_ifs(sst); + return show_selection_statement(state, &ifs); + } + + show_indent(state); + state.write("switch ("); + show_hir_expr(state, &sst.head); + state.write(") {\n"); + state.indent(); + + for case in &sst.cases { + show_case_label(state, &case.label); + state.indent(); + + let has_declaration = case.stmts.iter().any(|x| is_declaration(x)); + // glsl allows declarations in switch statements while C requires them to be + // in a compound statement. If we have a declaration wrap the statements in an block. + // This will break some glsl shaders but keeps the saner ones working + if has_declaration { + show_indent(state); + state.write("{\n"); + state.indent(); + } + for st in &case.stmts { + show_statement(state, st); + } + + if has_declaration { + show_indent(state); + state.write("}\n"); + state.outdent(); + } + + state.outdent(); + } + state.outdent(); + show_indent(state); + state.write("}\n"); +} + +pub fn show_case_label(state: &mut OutputState, cl: &hir::CaseLabel) { + show_indent(state); + match *cl { + hir::CaseLabel::Case(ref e) => { + state.write("case "); + show_hir_expr(state, e); + state.write(":\n"); + } + hir::CaseLabel::Def => { + state.write("default:\n"); + } + } +} + +pub fn show_iteration_statement(state: &mut OutputState, ist: &hir::IterationStatement) { + show_indent(state); + match *ist { + hir::IterationStatement::While(ref cond, ref body) => { + state.write("while ("); + show_condition(state, cond); + state.write(") "); + show_statement(state, body); + } + hir::IterationStatement::DoWhile(ref body, ref cond) => { + state.write("do "); + show_statement(state, body); + state.write(" while ("); + show_hir_expr(state, cond); + state.write(")\n"); + } + hir::IterationStatement::For(ref init, ref rest, ref body) => { + state.write("for ("); + show_for_init_statement(state, init); + show_for_rest_statement(state, rest); + state.write(") "); + show_statement(state, body); + } + } +} + +pub fn show_condition(state: &mut OutputState, c: &hir::Condition) { + match *c { + hir::Condition::Expr(ref e) => show_hir_expr(state, e), + /*hir::Condition::Assignment(ref ty, ref name, ref initializer) => { + show_type(state, ty); + state.write(" "); + show_identifier(f, name); + state.write(" = "); + show_initializer(state, initializer); + }*/ + } +} + +pub fn show_for_init_statement(state: &mut OutputState, i: &hir::ForInitStatement) { + match *i { + hir::ForInitStatement::Expression(ref expr) => { + if let Some(ref e) = *expr { + show_hir_expr(state, e); + } + } + hir::ForInitStatement::Declaration(ref d) => { + show_declaration(state, d); + } + } +} + +pub fn show_for_rest_statement(state: &mut OutputState, r: &hir::ForRestStatement) { + if let Some(ref cond) = r.condition { + show_condition(state, cond); + } + + state.write("; "); + + if let Some(ref e) = r.post_expr { + show_hir_expr(state, e); + } +} + +fn use_return_mask(state: &OutputState) -> bool { + if let Some(mask) = &state.mask { + mask.kind != hir::ExprKind::CondMask + } else { + false + } +} + +pub fn show_jump_statement(state: &mut OutputState, j: &hir::JumpStatement) { + show_indent(state); + match *j { + hir::JumpStatement::Continue => { + state.write("continue;\n"); + } + hir::JumpStatement::Break => { + state.write("break;\n"); + } + hir::JumpStatement::Discard => { + if state.output_cxx { + state.uses_discard = true; + if let Some(mask) = &state.mask { + state.write("isPixelDiscarded |= ("); + show_hir_expr(state, mask); + state.write(")"); + if state.return_declared { + state.write("&ret_mask"); + } + state.write(";\n"); + } else { + state.write("isPixelDiscarded = true;\n"); + } + } else { + state.write("discard;\n"); + } + } + hir::JumpStatement::Return(ref e) => { + if let Some(e) = e { + if state.output_cxx { + if use_return_mask(state) { + // We cast any conditions by `ret_mask_type` so that scalars nicely + // convert to -1. i.e. I32 &= bool will give the wrong result. while I32 &= I32(bool) works + let ret_mask_type = if state.return_vector { + "I32" + } else { + "int32_t" + }; + if state.return_declared { + // XXX: the cloning here is bad + write!(state, "ret = if_then_else(ret_mask & {}(", ret_mask_type); + show_hir_expr(state, &state.mask.clone().unwrap()); + state.write("), "); + show_hir_expr(state, e); + state.write(", ret);\n"); + } else { + state.write("ret = "); + show_hir_expr(state, e); + state.write(";\n"); + } + + show_indent(state); + + if state.return_declared { + write!(state, "ret_mask &= ~{}(", ret_mask_type); + } else { + write!(state, "ret_mask = ~{}(", ret_mask_type); + } + show_hir_expr(state, &state.mask.clone().unwrap()); + state.write(");\n"); + state.return_declared = true; + } else { + if state.return_declared { + state.write("ret = if_then_else(ret_mask, "); + show_hir_expr(state, e); + state.write(", ret);\n"); + } else { + state.write("return "); + show_hir_expr(state, e); + state.write(";\n"); + } + } + } else { + state.write("return "); + show_hir_expr(state, e); + state.write(";\n"); + } + } else { + if state.output_cxx { + if use_return_mask(state) { + show_indent(state); + let ret_mask_type = if state.return_vector { + "I32" + } else { + "int32_t" + }; + if state.return_declared { + write!(state, "ret_mask &= ~{}(", ret_mask_type); + } else { + write!(state, "ret_mask = ~{}(", ret_mask_type); + } + show_hir_expr(state, &state.mask.clone().unwrap()); + state.write(");\n"); + state.return_declared = true; + } else { + state.write("return;\n"); + } + } else { + state.write("return;\n"); + } + } + } + } +} + +pub fn show_path(state: &OutputState, path: &syntax::Path) { + match path { + syntax::Path::Absolute(s) => { + let _ = write!(state, "<{}>", s); + } + syntax::Path::Relative(s) => { + let _ = write!(state, "\"{}\"", s); + } + } +} + +pub fn show_preprocessor(state: &OutputState, pp: &syntax::Preprocessor) { + match *pp { + syntax::Preprocessor::Define(ref pd) => show_preprocessor_define(state, pd), + syntax::Preprocessor::Else => show_preprocessor_else(state), + syntax::Preprocessor::ElseIf(ref pei) => show_preprocessor_elseif(state, pei), + syntax::Preprocessor::EndIf => show_preprocessor_endif(state), + syntax::Preprocessor::Error(ref pe) => show_preprocessor_error(state, pe), + syntax::Preprocessor::If(ref pi) => show_preprocessor_if(state, pi), + syntax::Preprocessor::IfDef(ref pid) => show_preprocessor_ifdef(state, pid), + syntax::Preprocessor::IfNDef(ref pind) => show_preprocessor_ifndef(state, pind), + syntax::Preprocessor::Include(ref pi) => show_preprocessor_include(state, pi), + syntax::Preprocessor::Line(ref pl) => show_preprocessor_line(state, pl), + syntax::Preprocessor::Pragma(ref pp) => show_preprocessor_pragma(state, pp), + syntax::Preprocessor::Undef(ref pu) => show_preprocessor_undef(state, pu), + syntax::Preprocessor::Version(ref pv) => show_preprocessor_version(state, pv), + syntax::Preprocessor::Extension(ref pe) => show_preprocessor_extension(state, pe), + } +} + +pub fn show_preprocessor_define(state: &OutputState, pd: &syntax::PreprocessorDefine) { + match *pd { + syntax::PreprocessorDefine::ObjectLike { + ref ident, + ref value, + } => { + let _ = write!(state, "#define {} {}\n", ident, value); + } + + syntax::PreprocessorDefine::FunctionLike { + ref ident, + ref args, + ref value, + } => { + let _ = write!(state, "#define {}(", ident); + + if !args.is_empty() { + let _ = write!(state, "{}", &args[0]); + + for arg in &args[1 .. args.len()] { + let _ = write!(state, ", {}", arg); + } + } + + let _ = write!(state, ") {}\n", value); + } + } +} + +pub fn show_preprocessor_else(state: &OutputState) { + state.write("#else\n"); +} + +pub fn show_preprocessor_elseif(state: &OutputState, pei: &syntax::PreprocessorElseIf) { + let _ = write!(state, "#elseif {}\n", pei.condition); +} + +pub fn show_preprocessor_error(state: &OutputState, pe: &syntax::PreprocessorError) { + let _ = writeln!(state, "#error {}", pe.message); +} + +pub fn show_preprocessor_endif(state: &OutputState) { + state.write("#endif\n"); +} + +pub fn show_preprocessor_if(state: &OutputState, pi: &syntax::PreprocessorIf) { + let _ = write!(state, "#if {}\n", pi.condition); +} + +pub fn show_preprocessor_ifdef(state: &OutputState, pid: &syntax::PreprocessorIfDef) { + state.write("#ifdef "); + show_identifier(state, &pid.ident); + state.write("\n"); +} + +pub fn show_preprocessor_ifndef(state: &OutputState, pind: &syntax::PreprocessorIfNDef) { + state.write("#ifndef "); + show_identifier(state, &pind.ident); + state.write("\n"); +} + +pub fn show_preprocessor_include(state: &OutputState, pi: &syntax::PreprocessorInclude) { + state.write("#include "); + show_path(state, &pi.path); + state.write("\n"); +} + +pub fn show_preprocessor_line(state: &OutputState, pl: &syntax::PreprocessorLine) { + let _ = write!(state, "#line {}", pl.line); + if let Some(source_string_number) = pl.source_string_number { + let _ = write!(state, " {}", source_string_number); + } + state.write("\n"); +} + +pub fn show_preprocessor_pragma(state: &OutputState, pp: &syntax::PreprocessorPragma) { + let _ = writeln!(state, "#pragma {}", pp.command); +} + +pub fn show_preprocessor_undef(state: &OutputState, pud: &syntax::PreprocessorUndef) { + state.write("#undef "); + show_identifier(state, &pud.name); + state.write("\n"); +} + +pub fn show_preprocessor_version(state: &OutputState, pv: &syntax::PreprocessorVersion) { + let _ = write!(state, "#version {}", pv.version); + + if let Some(ref profile) = pv.profile { + match *profile { + syntax::PreprocessorVersionProfile::Core => { + state.write(" core"); + } + syntax::PreprocessorVersionProfile::Compatibility => { + state.write(" compatibility"); + } + syntax::PreprocessorVersionProfile::ES => { + state.write(" es"); + } + } + } + + state.write("\n"); +} + +pub fn show_preprocessor_extension(state: &OutputState, pe: &syntax::PreprocessorExtension) { + state.write("#extension "); + + match pe.name { + syntax::PreprocessorExtensionName::All => { + state.write("all"); + } + syntax::PreprocessorExtensionName::Specific(ref n) => { + state.write(n); + } + } + + if let Some(ref behavior) = pe.behavior { + match *behavior { + syntax::PreprocessorExtensionBehavior::Require => { + state.write(" : require"); + } + syntax::PreprocessorExtensionBehavior::Enable => { + state.write(" : enable"); + } + syntax::PreprocessorExtensionBehavior::Warn => { + state.write(" : warn"); + } + syntax::PreprocessorExtensionBehavior::Disable => { + state.write(" : disable"); + } + } + } + + state.write("\n"); +} + +pub fn show_external_declaration(state: &mut OutputState, ed: &hir::ExternalDeclaration) { + match *ed { + hir::ExternalDeclaration::Preprocessor(ref pp) => { + if !state.output_cxx { + show_preprocessor(state, pp) + } + } + hir::ExternalDeclaration::FunctionDefinition(ref fd) => { + if !state.output_cxx { + show_function_definition(state, fd, !0) + } + } + hir::ExternalDeclaration::Declaration(ref d) => show_declaration(state, d), + } +} + +pub fn show_cxx_function_definition(state: &mut OutputState, name: hir::SymRef, vector_mask: u32) { + if let Some((ref fd, run_class)) = state.hir.function_definition(name) { + state.vector_mask = vector_mask; + state.return_vector = (vector_mask & (1 << 31)) != 0 + || match run_class { + hir::RunClass::Scalar => false, + hir::RunClass::Dependent(mask) => (mask & vector_mask) != 0, + _ => true, + }; + match state.functions.get(&(name, vector_mask)) { + Some(true) => {} + Some(false) => { + show_function_prototype(state, &fd.prototype); + state.functions.insert((name, vector_mask), true); + } + None => { + state.functions.insert((name, vector_mask), false); + let buffer = state.push_buffer(); + show_function_definition(state, fd, vector_mask); + for (name, vector_mask) in state.deps.replace(Vec::new()) { + show_cxx_function_definition(state, name, vector_mask); + } + state.flush_buffer(); + state.pop_buffer(buffer); + state.functions.insert((name, vector_mask), true); + } + } + } +} + +pub fn show_translation_unit(state: &mut OutputState, tu: &hir::TranslationUnit) { + state.flush_buffer(); + + for ed in &(tu.0).0 { + show_external_declaration(state, ed); + state.flush_buffer(); + } + if state.output_cxx { + if let Some(name) = state.hir.lookup("main") { + show_cxx_function_definition(state, name, 0); + state.flush_buffer(); + } + } +} + +fn write_abi(state: &mut OutputState) { + match state.kind { + ShaderKind::Fragment => { + state.write("static void run(Self *self) {\n"); + if state.uses_discard { + state.write(" self->isPixelDiscarded = false;\n"); + } + state.write(" self->main();\n"); + state.write(" self->step_interp_inputs();\n"); + state.write("}\n"); + state.write("static void skip(Self* self, int chunks) {\n"); + state.write(" self->step_interp_inputs();\n"); + state.write(" while (--chunks > 0) self->step_interp_inputs();\n"); + state.write("}\n"); + if state.use_perspective { + state.write("static void run_perspective(Self *self) {\n"); + if state.uses_discard { + state.write(" self->isPixelDiscarded = false;\n"); + } + state.write(" self->main();\n"); + state.write(" self->step_perspective_inputs();\n"); + state.write("}\n"); + state.write("static void skip_perspective(Self* self, int chunks) {\n"); + state.write(" self->step_perspective_inputs();\n"); + state.write(" while (--chunks > 0) self->step_perspective_inputs();\n"); + state.write("}\n"); + } + if state.has_draw_span_rgba8 { + state.write( + "static void draw_span_RGBA8(Self* self, uint32_t* buf, int len) { \ + DISPATCH_DRAW_SPAN(self, buf, len); }\n"); + } + if state.has_draw_span_r8 { + state.write( + "static void draw_span_R8(Self* self, uint8_t* buf, int len) { \ + DISPATCH_DRAW_SPAN(self, buf, len); }\n"); + } + + write!(state, "public:\n{}_frag() {{\n", state.name); + } + ShaderKind::Vertex => { + state.write( + "static void run(Self* self, char* interps, size_t interp_stride) {\n", + ); + state.write(" self->main();\n"); + state.write(" self->store_interp_outputs(interps, interp_stride);\n"); + state.write("}\n"); + state.write("static void init_batch(Self *self) { self->bind_textures(); }\n"); + + write!(state, "public:\n{}_vert() {{\n", state.name); + } + } + match state.kind { + ShaderKind::Fragment => { + state.write(" init_span_func = (InitSpanFunc)&read_interp_inputs;\n"); + state.write(" run_func = (RunFunc)&run;\n"); + state.write(" skip_func = (SkipFunc)&skip;\n"); + if state.has_draw_span_rgba8 { + state.write(" draw_span_RGBA8_func = (DrawSpanRGBA8Func)&draw_span_RGBA8;\n"); + } + if state.has_draw_span_r8 { + state.write(" draw_span_R8_func = (DrawSpanR8Func)&draw_span_R8;\n"); + } + if state.uses_discard { + state.write(" enable_discard();\n"); + } + if state.use_perspective { + state.write(" enable_perspective();\n"); + state.write(" init_span_w_func = (InitSpanWFunc)&read_perspective_inputs;\n"); + state.write(" run_w_func = (RunWFunc)&run_perspective;\n"); + state.write(" skip_w_func = (SkipWFunc)&skip_perspective;\n"); + } else { + state.write(" init_span_w_func = (InitSpanWFunc)&read_interp_inputs;\n"); + state.write(" run_w_func = (RunWFunc)&run;\n"); + state.write(" skip_w_func = (SkipWFunc)&skip;\n"); + } + } + ShaderKind::Vertex => { + state.write(" set_uniform_1i_func = (SetUniform1iFunc)&set_uniform_1i;\n"); + state.write(" set_uniform_4fv_func = (SetUniform4fvFunc)&set_uniform_4fv;\n"); + state.write(" set_uniform_matrix4fv_func = (SetUniformMatrix4fvFunc)&set_uniform_matrix4fv;\n"); + state.write(" init_batch_func = (InitBatchFunc)&init_batch;\n"); + state.write(" load_attribs_func = (LoadAttribsFunc)&load_attribs;\n"); + state.write(" run_primitive_func = (RunPrimitiveFunc)&run;\n"); + } + } + state.write("}\n"); +} + +pub fn define_global_consts(state: &mut OutputState, tu: &hir::TranslationUnit, part_name: &str) { + for i in tu { + match i { + hir::ExternalDeclaration::Declaration(hir::Declaration::InitDeclaratorList(ref d)) => { + let sym = state.hir.sym(d.head.name); + match &sym.decl { + hir::SymDecl::Global(hir::StorageClass::Const, ..) => { + let is_scalar = state.is_scalar.replace( + symbol_run_class(&sym.decl, state.vector_mask) == hir::RunClass::Scalar, + ); + if let Some(ref _array) = d.head.ty.array_sizes { + show_type(state, &d.head.ty); + } else { + if let Some(ty_def) = d.head.ty_def { + show_sym_decl(state, &ty_def); + } else { + show_type(state, &d.head.ty); + } + } + write!(state, " constexpr {}::{};\n", part_name, sym.name); + state.is_scalar.set(is_scalar); + } + _ => {} + } + } + _ => {} + } + } +} diff --git a/third_party/webrender/glsl-to-cxx/src/main.rs b/third_party/webrender/glsl-to-cxx/src/main.rs new file mode 100644 index 00000000000..e40262c84e1 --- /dev/null +++ b/third_party/webrender/glsl-to-cxx/src/main.rs @@ -0,0 +1,8 @@ +/* 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 glsl_to_cxx::translate; +fn main() { + println!("{}", translate(&mut std::env::args())); +} diff --git a/third_party/webrender/patches/0001-Add-signal-handler-to-catch-segfault-in-build-script.patch b/third_party/webrender/patches/0001-Add-signal-handler-to-catch-segfault-in-build-script.patch new file mode 100644 index 00000000000..674e9801466 --- /dev/null +++ b/third_party/webrender/patches/0001-Add-signal-handler-to-catch-segfault-in-build-script.patch @@ -0,0 +1,226 @@ +From 34d968adeda2e06b057a13d14a88df5766b38eda Mon Sep 17 00:00:00 2001 +From: Josh Matthews +Date: Mon, 6 Jul 2020 14:37:42 -0400 +Subject: [PATCH] Add signal handler to catch segfault in build script. + +--- + Cargo.lock | 11 +++++ + webrender/Cargo.toml | 5 ++ + webrender/backtrace.rs | 103 +++++++++++++++++++++++++++++++++++++++++ + webrender/build.rs | 30 ++++++++++++ + 4 files changed, 149 insertions(+) + create mode 100644 webrender/backtrace.rs + +diff --git a/Cargo.lock b/Cargo.lock +index afdd336ae..cf91162d5 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -1477,6 +1477,14 @@ dependencies = [ + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + ] + ++[[package]] ++name = "sig" ++version = "1.0.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++dependencies = [ ++ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", ++] ++ + [[package]] + name = "slab" + version = "0.4.2" +@@ -1750,6 +1758,7 @@ dependencies = [ + name = "webrender" + version = "0.61.0" + dependencies = [ ++ "backtrace 0.3.46 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -1780,6 +1789,7 @@ dependencies = [ + "ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)", ++ "sig 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "svg_fmt 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -2178,6 +2188,7 @@ dependencies = [ + "checksum servo-freetype-sys 4.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2c4ccb6d0d32d277d3ef7dea86203d8210945eb7a45fba89dd445b3595dd0dfc" + "checksum sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" + "checksum shared_library 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" ++"checksum sig 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6567e29578f9bfade6a5d94a32b9a4256348358d2a3f448cab0021f9a02614a2" + "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + "checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" + "checksum smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05720e22615919e4734f6a99ceae50d00226c3c5aca406e102ebc33298214e0a" +diff --git a/webrender/Cargo.toml b/webrender/Cargo.toml +index dcf26d913..f7679da57 100644 +--- a/webrender/Cargo.toml ++++ b/webrender/Cargo.toml +@@ -59,6 +59,11 @@ tracy-rs = { version = "0.1" } + mozangle = "0.3.1" + rand = "0.4" + ++[target.'cfg(any(target_os = "macos", target_os = "linux"))'.build-dependencies] ++backtrace = "0.3" ++sig = "1.0" ++libc = "0.2" ++ + [target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies] + freetype = { version = "0.4", default-features = false } + libc = "0.2" +diff --git a/webrender/backtrace.rs b/webrender/backtrace.rs +new file mode 100644 +index 000000000..aa6bb6b32 +--- /dev/null ++++ b/webrender/backtrace.rs +@@ -0,0 +1,103 @@ ++/* 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 https://mozilla.org/MPL/2.0/. */ ++ ++//! Similar to `println!("{:?}", Backtrace::new())`, but doesn’t allocate. ++//! ++//! Seems to fix some deadlocks: https://github.com/servo/servo/issues/24881 ++//! ++//! FIXME: if/when a future version of the `backtrace` crate has ++//! https://github.com/rust-lang/backtrace-rs/pull/265, use that instead. ++ ++use std::fmt::{self, Write}; ++use backtrace::{BytesOrWideString, PrintFmt}; ++ ++#[inline(never)] ++pub(crate) fn print(w: &mut dyn std::io::Write) -> Result<(), std::io::Error> { ++ write!(w, "{:?}", Print { ++ print_fn_address: print as usize, ++ }) ++} ++ ++struct Print { ++ print_fn_address: usize, ++} ++ ++impl fmt::Debug for Print { ++ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { ++ // Safety: we’re in a signal handler that is about to call `libc::_exit`. ++ // Potential data races from using `*_unsynchronized` functions are perhaps ++ // less bad than potential deadlocks? ++ unsafe { ++ let mut print_fn_frame = 0; ++ let mut frame_count = 0; ++ backtrace::trace_unsynchronized(|frame| { ++ let found = frame.symbol_address() as usize == self.print_fn_address; ++ if found { ++ print_fn_frame = frame_count; ++ } ++ frame_count += 1; ++ !found ++ }); ++ ++ let mode = PrintFmt::Short; ++ let mut p = print_path; ++ let mut f = backtrace::BacktraceFmt::new(fmt, mode, &mut p); ++ f.add_context()?; ++ let mut result = Ok(()); ++ let mut frame_count = 0; ++ backtrace::trace_unsynchronized(|frame| { ++ let skip = frame_count < print_fn_frame; ++ frame_count += 1; ++ if skip { ++ return true ++ } ++ ++ let mut frame_fmt = f.frame(); ++ let mut any_symbol = false; ++ backtrace::resolve_frame_unsynchronized(frame, |symbol| { ++ any_symbol = true; ++ if let Err(e) = frame_fmt.symbol(frame, symbol) { ++ result = Err(e) ++ } ++ }); ++ if !any_symbol { ++ if let Err(e) = frame_fmt.print_raw(frame.ip(), None, None, None) { ++ result = Err(e) ++ } ++ } ++ result.is_ok() ++ }); ++ result?; ++ f.finish() ++ } ++ } ++} ++ ++fn print_path(fmt: &mut fmt::Formatter, path: BytesOrWideString) -> fmt::Result { ++ match path { ++ BytesOrWideString::Bytes(mut bytes) => { ++ loop { ++ match std::str::from_utf8(bytes) { ++ Ok(s) => { ++ fmt.write_str(s)?; ++ break; ++ } ++ Err(err) => { ++ fmt.write_char(std::char::REPLACEMENT_CHARACTER)?; ++ match err.error_len() { ++ Some(len) => bytes = &bytes[err.valid_up_to() + len..], ++ None => break, ++ } ++ } ++ } ++ } ++ } ++ BytesOrWideString::Wide(wide) => { ++ for c in std::char::decode_utf16(wide.iter().cloned()) { ++ fmt.write_char(c.unwrap_or(std::char::REPLACEMENT_CHARACTER))? ++ } ++ } ++ } ++ Ok(()) ++} +diff --git a/webrender/build.rs b/webrender/build.rs +index 3521d1342..36a7f17a8 100644 +--- a/webrender/build.rs ++++ b/webrender/build.rs +@@ -244,7 +244,37 @@ fn write_optimized_shaders(shader_dir: &Path, shader_file: &mut File, out_dir: & + Ok(()) + } + ++#[cfg(any(target_os = "macos", target_os = "linux"))] ++mod backtrace; ++ ++#[cfg(any(target_os = "macos", target_os = "linux"))] ++extern "C" fn handler(sig: i32) { ++ use std::sync::atomic; ++ static BEEN_HERE_BEFORE: atomic::AtomicBool = atomic::AtomicBool::new(false); ++ if !BEEN_HERE_BEFORE.swap(true, atomic::Ordering::SeqCst) { ++ let stdout = std::io::stdout(); ++ let mut stdout = stdout.lock(); ++ let _ = write!(&mut stdout, "Stack trace"); ++ if let Some(name) = std::thread::current().name() { ++ let _ = write!(&mut stdout, " for thread \"{}\"", name); ++ } ++ let _ = write!(&mut stdout, "\n"); ++ let _ = backtrace::print(&mut stdout); ++ } ++ unsafe { ++ libc::_exit(sig); ++ } ++} ++ + fn main() -> Result<(), std::io::Error> { ++ #[cfg(any(target_os = "macos", target_os = "linux"))] ++ { ++ sig::signal!(sig::ffi::Sig::SEGV, handler); // handle segfaults ++ sig::signal!(sig::ffi::Sig::ILL, handler); // handle stack overflow and unsupported CPUs ++ sig::signal!(sig::ffi::Sig::IOT, handler); // handle double panics ++ sig::signal!(sig::ffi::Sig::BUS, handler); // handle invalid memory access ++ } ++ + let out_dir = env::var("OUT_DIR").unwrap_or("out".to_owned()); + + let shaders_file_path = Path::new(&out_dir).join("shaders.rs"); +-- +2.39.2 + diff --git a/third_party/webrender/patches/0002-Bug-1646741-Update-gleam-to-0.12.-r-kvark.patch b/third_party/webrender/patches/0002-Bug-1646741-Update-gleam-to-0.12.-r-kvark.patch new file mode 100644 index 00000000000..7bb98352203 --- /dev/null +++ b/third_party/webrender/patches/0002-Bug-1646741-Update-gleam-to-0.12.-r-kvark.patch @@ -0,0 +1,174 @@ +From 299c4db222eb9f0acd9623b66b8f3d0a8a8f77f2 Mon Sep 17 00:00:00 2001 +From: Jeff Muizelaar +Date: Fri, 19 Jun 2020 04:10:02 +0000 +Subject: [PATCH 2/6] Bug 1646741 - Update gleam to 0.12. r=kvark + +For stride calculation and SSBOs + +Differential Revision: https://phabricator.services.mozilla.com/D80191 + +[ghsync] From https://hg.mozilla.org/mozilla-central/rev/ef8485a16d099e24f4832178664c5a93a28396ec +--- + Cargo.lock | 16 ++++++++-------- + direct-composition/Cargo.toml | 2 +- + example-compositor/compositor/Cargo.toml | 2 +- + examples/Cargo.toml | 2 +- + swgl/Cargo.toml | 2 +- + webrender/Cargo.toml | 2 +- + wrench/Cargo.toml | 2 +- + 7 files changed, 14 insertions(+), 14 deletions(-) + +diff --git a/Cargo.lock b/Cargo.lock +index cf91162d5..3eb484f26 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -257,7 +257,7 @@ name = "compositor" + version = "0.1.0" + dependencies = [ + "compositor-windows 0.1.0", +- "gleam 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "gleam 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "webrender 0.61.0", + ] + +@@ -435,7 +435,7 @@ name = "direct-composition" + version = "0.1.0" + dependencies = [ + "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", +- "gleam 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "gleam 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mozangle 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "webrender 0.61.0", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -617,7 +617,7 @@ dependencies = [ + + [[package]] + name = "gleam" +-version = "0.11.0" ++version = "0.12.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + dependencies = [ + "gl_generator 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -1545,7 +1545,7 @@ name = "swgl" + version = "0.1.0" + dependencies = [ + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", +- "gleam 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "gleam 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glsl-to-cxx 0.1.0", + "webrender_build 0.0.1", + ] +@@ -1773,7 +1773,7 @@ dependencies = [ + "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", + "freetype 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +- "gleam 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "gleam 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glslopt 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "image 0.23.3 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -1808,7 +1808,7 @@ dependencies = [ + "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", + "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", +- "gleam 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "gleam 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glutin 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "webrender 0.61.0", +@@ -1938,7 +1938,7 @@ dependencies = [ + "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", + "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", + "font-loader 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +- "gleam 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "gleam 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glutin 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)", + "image 0.23.3 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -2087,7 +2087,7 @@ dependencies = [ + "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" + "checksum gl_generator 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ca98bbde17256e02d17336a6bdb5a50f7d0ccacee502e191d3e3d0ec2f96f84a" + "checksum gl_generator 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +-"checksum gleam 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9a13b5bb12ab457c15400b43cbba5971df5c1898b6a9c30cc8c52cb01baa112" ++"checksum gleam 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8d023b0b00c16960f0f82816f2f546dabe937e75b25c7d6ce09a63e6a52d71e" + "checksum gleam 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "cae10d7c99d0e77b4766e850a60898a17c1abaf01075531f1066f03dc7dc5fc5" + "checksum glsl 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "766443890761b3c4edcce86cafaac97971b200662fbdd0446eb7c6b99b4401ea" + "checksum glslopt 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f22b383fcf6f85c4a268af39a0758ec40970e5f9f8fe9809e4415d48409b8379" +diff --git a/direct-composition/Cargo.toml b/direct-composition/Cargo.toml +index d099402d8..3506aec02 100644 +--- a/direct-composition/Cargo.toml ++++ b/direct-composition/Cargo.toml +@@ -7,7 +7,7 @@ edition = "2018" + + [target.'cfg(windows)'.dependencies] + euclid = "0.20" +-gleam = "0.11" ++gleam = "0.12" + mozangle = {version = "0.3.1", features = ["egl"]} + webrender = {path = "../webrender"} + winapi = {version = "0.3", features = ["winerror", "d3d11", "dcomp"]} +diff --git a/example-compositor/compositor/Cargo.toml b/example-compositor/compositor/Cargo.toml +index ce4d61928..d505e9ad2 100644 +--- a/example-compositor/compositor/Cargo.toml ++++ b/example-compositor/compositor/Cargo.toml +@@ -7,7 +7,7 @@ license = "MPL-2.0" + + [dependencies] + webrender = { path = "../../webrender" } +-gleam = "0.11.0" ++gleam = "0.12.0" + + [target.'cfg(windows)'.dependencies] + compositor-windows = { path = "../compositor-windows" } +diff --git a/examples/Cargo.toml b/examples/Cargo.toml +index 31c695f98..09e658ca2 100644 +--- a/examples/Cargo.toml ++++ b/examples/Cargo.toml +@@ -61,7 +61,7 @@ debug = ["webrender/capture", "webrender/debugger", "webrender/profiler"] + app_units = "0.7" + env_logger = "0.5" + euclid = "0.20" +-gleam = "0.11" ++gleam = "0.12" + glutin = "0.21" + rayon = "1" + webrender = { path = "../webrender" } +diff --git a/swgl/Cargo.toml b/swgl/Cargo.toml +index 3d57edbab..bc5a04b0a 100644 +--- a/swgl/Cargo.toml ++++ b/swgl/Cargo.toml +@@ -12,4 +12,4 @@ glsl-to-cxx = { path = "../glsl-to-cxx" } + webrender_build = { path = "../webrender_build" } + + [dependencies] +-gleam = "0.11.0" ++gleam = "0.12.0" +diff --git a/webrender/Cargo.toml b/webrender/Cargo.toml +index f7679da57..2b0ab14fb 100644 +--- a/webrender/Cargo.toml ++++ b/webrender/Cargo.toml +@@ -34,7 +34,7 @@ cfg-if = "0.1.2" + cstr = "0.1.2" + euclid = { version = "0.20.0", features = ["serde"] } + fxhash = "0.2.1" +-gleam = "0.11.0" ++gleam = "0.12.0" + image_loader = { optional = true, version = "0.23", package = "image", default-features = false, features = ["png"] } + lazy_static = "1" + log = "0.4" +diff --git a/wrench/Cargo.toml b/wrench/Cargo.toml +index 988e2537a..4ba95e4c4 100644 +--- a/wrench/Cargo.toml ++++ b/wrench/Cargo.toml +@@ -12,7 +12,7 @@ bincode = "1.0" + byteorder = "1.0" + env_logger = { version = "0.5", optional = true } + euclid = "0.20" +-gleam = "0.11" ++gleam = "0.12" + glutin = "0.21" + app_units = "0.7" + clap = { version = "2", features = ["yaml"] } +-- +2.39.2 + diff --git a/third_party/webrender/patches/0003-Bug-1651889.-Update-to-gleam-0.12.1.-r-kvark.patch b/third_party/webrender/patches/0003-Bug-1651889.-Update-to-gleam-0.12.1.-r-kvark.patch new file mode 100644 index 00000000000..6ca3264d183 --- /dev/null +++ b/third_party/webrender/patches/0003-Bug-1651889.-Update-to-gleam-0.12.1.-r-kvark.patch @@ -0,0 +1,107 @@ +From 3767bd8938b5b849bc23bb7ac490cb0c27655560 Mon Sep 17 00:00:00 2001 +From: Jeff Muizelaar +Date: Sat, 11 Jul 2020 09:42:33 +0000 +Subject: [PATCH 3/6] Bug 1651889. Update to gleam 0.12.1. r=kvark + +This should fix a crash caused by an unexpected pixel type. + +Differential Revision: https://phabricator.services.mozilla.com/D83167 + +[ghsync] From https://hg.mozilla.org/mozilla-central/rev/b850773b54e129888b8fb2f1e3bc68f528aeccbf +--- + Cargo.lock | 16 ++++++++-------- + webrender/Cargo.toml | 2 +- + 2 files changed, 9 insertions(+), 9 deletions(-) + +diff --git a/Cargo.lock b/Cargo.lock +index 3eb484f26..24f92084c 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -257,7 +257,7 @@ name = "compositor" + version = "0.1.0" + dependencies = [ + "compositor-windows 0.1.0", +- "gleam 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "webrender 0.61.0", + ] + +@@ -435,7 +435,7 @@ name = "direct-composition" + version = "0.1.0" + dependencies = [ + "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", +- "gleam 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mozangle 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "webrender 0.61.0", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -617,7 +617,7 @@ dependencies = [ + + [[package]] + name = "gleam" +-version = "0.12.0" ++version = "0.12.1" + source = "registry+https://github.com/rust-lang/crates.io-index" + dependencies = [ + "gl_generator 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -1545,7 +1545,7 @@ name = "swgl" + version = "0.1.0" + dependencies = [ + "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", +- "gleam 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glsl-to-cxx 0.1.0", + "webrender_build 0.0.1", + ] +@@ -1773,7 +1773,7 @@ dependencies = [ + "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", + "freetype 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +- "gleam 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glslopt 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "image 0.23.3 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -1808,7 +1808,7 @@ dependencies = [ + "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", + "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", +- "gleam 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glutin 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "webrender 0.61.0", +@@ -1938,7 +1938,7 @@ dependencies = [ + "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", + "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", + "font-loader 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +- "gleam 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glutin 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)", + "image 0.23.3 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -2087,7 +2087,7 @@ dependencies = [ + "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" + "checksum gl_generator 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ca98bbde17256e02d17336a6bdb5a50f7d0ccacee502e191d3e3d0ec2f96f84a" + "checksum gl_generator 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +-"checksum gleam 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8d023b0b00c16960f0f82816f2f546dabe937e75b25c7d6ce09a63e6a52d71e" ++"checksum gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3fdef5b9df6d3a261b80a5ac55e13bf93945725df2463c1b0a2e5a527dce0d37" + "checksum gleam 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "cae10d7c99d0e77b4766e850a60898a17c1abaf01075531f1066f03dc7dc5fc5" + "checksum glsl 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "766443890761b3c4edcce86cafaac97971b200662fbdd0446eb7c6b99b4401ea" + "checksum glslopt 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f22b383fcf6f85c4a268af39a0758ec40970e5f9f8fe9809e4415d48409b8379" +diff --git a/webrender/Cargo.toml b/webrender/Cargo.toml +index 2b0ab14fb..3fa6630bd 100644 +--- a/webrender/Cargo.toml ++++ b/webrender/Cargo.toml +@@ -34,7 +34,7 @@ cfg-if = "0.1.2" + cstr = "0.1.2" + euclid = { version = "0.20.0", features = ["serde"] } + fxhash = "0.2.1" +-gleam = "0.12.0" ++gleam = "0.12.1" + image_loader = { optional = true, version = "0.23", package = "image", default-features = false, features = ["png"] } + lazy_static = "1" + log = "0.4" +-- +2.39.2 + diff --git a/third_party/webrender/patches/0004-Bug-1654699.-Update-core-foundation-core-graphics.-r.patch b/third_party/webrender/patches/0004-Bug-1654699.-Update-core-foundation-core-graphics.-r.patch new file mode 100644 index 00000000000..4791f15a989 --- /dev/null +++ b/third_party/webrender/patches/0004-Bug-1654699.-Update-core-foundation-core-graphics.-r.patch @@ -0,0 +1,328 @@ +From 920168aff79a7cf52980b0c90965a591f2f4204a Mon Sep 17 00:00:00 2001 +From: Jeff Muizelaar +Date: Fri, 24 Jul 2020 09:54:10 +0000 +Subject: [PATCH 4/6] Bug 1654699. Update core-foundation/core-graphics. + r=kvark,keeler,chunmin + +This includes updates to authenticator, cubeb-coreaudio, +metal, gfx-backend-vulkan, gfx-backend-metal, freetype + +libloading is duplicated because of ash + +Differential Revision: https://phabricator.services.mozilla.com/D84688 + +[ghsync] From https://hg.mozilla.org/mozilla-central/rev/45fc4a780b2b4a9e047eceba73b39b988f719c58 +--- + Cargo.lock | 110 +++++++++++++++++++++++++-------------- + webrender/Cargo.toml | 10 ++-- + webrender_api/Cargo.toml | 4 +- + wrench/Cargo.toml | 6 +-- + 4 files changed, 80 insertions(+), 50 deletions(-) + +diff --git a/Cargo.lock b/Cargo.lock +index 24f92084c..617092292 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -286,6 +286,15 @@ dependencies = [ + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + ] + ++[[package]] ++name = "core-foundation" ++version = "0.9.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++dependencies = [ ++ "core-foundation-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", ++] ++ + [[package]] + name = "core-foundation-sys" + version = "0.6.2" +@@ -296,6 +305,11 @@ name = "core-foundation-sys" + version = "0.7.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + ++[[package]] ++name = "core-foundation-sys" ++version = "0.8.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++ + [[package]] + name = "core-graphics" + version = "0.17.3" +@@ -309,22 +323,34 @@ dependencies = [ + + [[package]] + name = "core-graphics" +-version = "0.19.0" ++version = "0.22.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +- "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "core-graphics-types 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ++ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", ++] ++ ++[[package]] ++name = "core-graphics-types" ++version = "0.1.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++dependencies = [ ++ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ++ "core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + ] + + [[package]] + name = "core-text" +-version = "15.0.0" ++version = "19.0.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + dependencies = [ +- "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +- "core-graphics 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "core-graphics 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + ] +@@ -510,13 +536,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" + + [[package]] + name = "font-loader" +-version = "0.9.0" ++version = "0.11.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + dependencies = [ +- "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +- "core-text 15.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "core-text 19.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +- "servo-fontconfig 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "servo-fontconfig 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + ] + +@@ -535,11 +561,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" + + [[package]] + name = "freetype" +-version = "0.4.1" ++version = "0.7.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + dependencies = [ ++ "freetype-sys 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +- "servo-freetype-sys 4.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ++] ++ ++[[package]] ++name = "freetype-sys" ++version = "0.13.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++dependencies = [ ++ "cmake 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ++ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", ++ "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + ] + + [[package]] +@@ -1431,29 +1467,20 @@ dependencies = [ + + [[package]] + name = "servo-fontconfig" +-version = "0.4.0" ++version = "0.5.1" + source = "registry+https://github.com/rust-lang/crates.io-index" + dependencies = [ + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +- "servo-fontconfig-sys 4.0.9 (registry+https://github.com/rust-lang/crates.io-index)", ++ "servo-fontconfig-sys 5.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + ] + + [[package]] + name = "servo-fontconfig-sys" +-version = "4.0.9" ++version = "5.1.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + dependencies = [ + "expat-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +- "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", +- "servo-freetype-sys 4.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +-] +- +-[[package]] +-name = "servo-freetype-sys" +-version = "4.0.5" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-dependencies = [ +- "cmake 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ++ "freetype-sys 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + ] + +@@ -1765,13 +1792,13 @@ dependencies = [ + "build-parallel 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +- "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +- "core-graphics 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", +- "core-text 15.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "core-graphics 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "core-text 19.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cstr 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "dwrote 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", +- "freetype 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", ++ "freetype 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glslopt 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -1822,8 +1849,8 @@ dependencies = [ + "app_units 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +- "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +- "core-graphics 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "core-graphics 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)", + "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", + "malloc_size_of_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -1931,13 +1958,13 @@ dependencies = [ + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", +- "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +- "core-graphics 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "core-graphics 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "dwrote 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", + "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", +- "font-loader 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "font-loader 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glutin 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)", + "image 0.23.3 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -2051,11 +2078,14 @@ dependencies = [ + "checksum cocoa 0.18.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1706996401131526e36b3b49f0c4d912639ce110996f3ca144d78946727bce54" + "checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" + "checksum core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" ++"checksum core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b5ed8e7e76c45974e15e41bfa8d5b0483cd90191639e01d8f5f1e606299d3fb" + "checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" + "checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" ++"checksum core-foundation-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9a21fa21941700a3cd8fcb4091f361a6a712fac632f85d9f487cc892045d55c6" + "checksum core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)" = "56790968ab1c8a1202a102e6de05fc6e1ec87da99e4e93e9a7d13efbfc1e95a9" +-"checksum core-graphics 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "59e78b2e0aaf43f08e7ae0d6bc96895ef72ff0921c7d4ff4762201b2dba376dd" +-"checksum core-text 15.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "131b3fd1f8bd5db9f2b398fa4fdb6008c64afc04d447c306ac2c7e98fba2a61d" ++"checksum core-graphics 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f6082396a349fa49674ba1bda4077332a18bf150e8fa75745ece07085e29a113" ++"checksum core-graphics-types 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e92f5d519093a4178296707dbaa3880eae85a5ef5386675f361a1cf25376e93c" ++"checksum core-text 19.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "04dfae50af11e72657fe7174cddb1ecddc5398037f7f6f39533ad69207c9a4e2" + "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" + "checksum crossbeam 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "bd66663db5a988098a89599d4857919b3acf7f61402e61365acfd3919857b9be" + "checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" +@@ -2075,10 +2105,11 @@ dependencies = [ + "checksum euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0c6a5b0c779cd0b744c73a1d2083faf181080d696903cdad99a3b03d015d7030" + "checksum expat-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa" + "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +-"checksum font-loader 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "933a61458662fbc8e3cd22cdb8331edbd78545fc044e1e2cd3d742f6ce06aa41" ++"checksum font-loader 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c49d6b4c11dca1a1dd931a34a9f397e2da91abe3de4110505f3530a80e560b52" + "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" + "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +-"checksum freetype 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "11926b2b410b469d0e9399eca4cbbe237a9ef02176c485803b29216307e8e028" ++"checksum freetype 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bee38378a9e3db1cc693b4f88d166ae375338a0ff75cb8263e1c601d51f35dc6" ++"checksum freetype-sys 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" + "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" + "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +@@ -2183,9 +2214,8 @@ dependencies = [ + "checksum serde_bytes 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "325a073952621257820e7a3469f55ba4726d8b28657e7e36653d1c36dc2c84ae" + "checksum serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)" = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" + "checksum serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)" = "da07b57ee2623368351e9a0488bb0b261322a15a6e0ae53e243cbdc0f4208da9" +-"checksum servo-fontconfig 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a088f8d775a5c5314aae09bd77340bc9c67d72b9a45258be34c83548b4814cd9" +-"checksum servo-fontconfig-sys 4.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "62b3e166450f523f4db06c14f02a2d39e76d49b5d8cbd224338d93e3595c156c" +-"checksum servo-freetype-sys 4.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2c4ccb6d0d32d277d3ef7dea86203d8210945eb7a45fba89dd445b3595dd0dfc" ++"checksum servo-fontconfig 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7e3e22fe5fd73d04ebf0daa049d3efe3eae55369ce38ab16d07ddd9ac5c217c" ++"checksum servo-fontconfig-sys 5.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e36b879db9892dfa40f95da1c38a835d41634b825fbd8c4c418093d53c24b388" + "checksum sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" + "checksum shared_library 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" + "checksum sig 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6567e29578f9bfade6a5d94a32b9a4256348358d2a3f448cab0021f9a02614a2" +diff --git a/webrender/Cargo.toml b/webrender/Cargo.toml +index 3fa6630bd..d05cf5979 100644 +--- a/webrender/Cargo.toml ++++ b/webrender/Cargo.toml +@@ -10,7 +10,7 @@ edition = "2018" + + [features] + default = ["freetype-lib"] +-freetype-lib = ["freetype/servo-freetype-sys"] ++freetype-lib = ["freetype/freetype-sys"] + profiler = ["tracy-rs/enable_profiler"] + debugger = ["ws", "serde_json", "serde", "image_loader", "base64"] + capture = ["api/serialize", "ron", "serde", "smallvec/serde"] +@@ -65,13 +65,13 @@ sig = "1.0" + libc = "0.2" + + [target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies] +-freetype = { version = "0.4", default-features = false } ++freetype = { version = "0.7", default-features = false } + libc = "0.2" + + [target.'cfg(target_os = "windows")'.dependencies] + dwrote = "0.11" + + [target.'cfg(target_os = "macos")'.dependencies] +-core-foundation = "0.7" +-core-graphics = "0.19" +-core-text = { version = "15", default-features = false } ++core-foundation = "0.9" ++core-graphics = "0.22" ++core-text = { version = "19", default-features = false } +diff --git a/webrender_api/Cargo.toml b/webrender_api/Cargo.toml +index 6b8b1e5f2..4ed7ce8e2 100644 +--- a/webrender_api/Cargo.toml ++++ b/webrender_api/Cargo.toml +@@ -28,5 +28,5 @@ malloc_size_of = { version = "0.0.1", path = "../wr_malloc_size_of", package = " + peek-poke = { version = "0.2", path = "../peek-poke", features = ["extras"] } + + [target.'cfg(target_os = "macos")'.dependencies] +-core-foundation = "0.7" +-core-graphics = "0.19" ++core-foundation = "0.9" ++core-graphics = "0.22" +diff --git a/wrench/Cargo.toml b/wrench/Cargo.toml +index 4ba95e4c4..33679485a 100644 +--- a/wrench/Cargo.toml ++++ b/wrench/Cargo.toml +@@ -38,8 +38,8 @@ default-features = false + features = ["png"] + + [target.'cfg(target_os = "macos")'.dependencies] +-core-graphics = "0.19" +-core-foundation = "0.7" ++core-graphics = "0.22" ++core-foundation = "0.9" + + [features] + default = [ "env_logger" ] +@@ -51,7 +51,7 @@ dwrote = "0.11" + mozangle = {version = "0.3.1", features = ["egl"]} + + [target.'cfg(all(unix, not(target_os = "android")))'.dependencies] +-font-loader = "0.9" ++font-loader = "0.11" + + # Configuration information used when building wrench as an APK. + [package.metadata.android] +-- +2.39.2 + diff --git a/third_party/webrender/patches/0005-Bug-1656236-Update-to-euclid-0.22.-r-kvark.patch b/third_party/webrender/patches/0005-Bug-1656236-Update-to-euclid-0.22.-r-kvark.patch new file mode 100644 index 00000000000..91af85c23ff --- /dev/null +++ b/third_party/webrender/patches/0005-Bug-1656236-Update-to-euclid-0.22.-r-kvark.patch @@ -0,0 +1,9059 @@ +From 97fb9976d408016b4f64dac6516ac2032dcc27b3 Mon Sep 17 00:00:00 2001 +From: Nicolas Silva +Date: Wed, 19 Aug 2020 10:22:02 +0000 +Subject: [PATCH 1/2] Bug 1656236 - Update to euclid 0.22. r=kvark + +Differential Revision: https://phabricator.services.mozilla.com/D85549 + +[ghsync] From https://hg.mozilla.org/mozilla-central/rev/dcfa644ba0788e1ce1e5d8523652cea6f83c8ce9 +--- + Cargo.lock | 28 ++++---- + direct-composition/Cargo.toml | 2 +- + examples/Cargo.toml | 2 +- + examples/animation.rs | 6 +- + peek-poke/Cargo.toml | 2 +- + tileview/Cargo.toml | 2 +- + tileview/src/main.rs | 6 +- + webrender/Cargo.toml | 4 +- + webrender/src/batch.rs | 10 +-- + webrender/src/border.rs | 12 ++-- + webrender/src/device/gl.rs | 2 +- + webrender/src/picture.rs | 6 +- + webrender/src/prim_store/mod.rs | 4 +- + webrender/src/render_backend.rs | 2 +- + webrender/src/render_target.rs | 2 +- + webrender/src/render_task_graph.rs | 2 +- + webrender/src/spatial_node.rs | 14 ++-- + webrender/src/spatial_tree.rs | 34 +++++----- + webrender/src/texture_cache.rs | 2 +- + webrender/src/util.rs | 63 +++++++++--------- + webrender_api/Cargo.toml | 2 +- + webrender_api/src/image.rs | 12 ++-- + webrender_api/src/image_tiling.rs | 13 ++-- + webrender_api/src/resources.rs | 13 ++-- + wr_malloc_size_of/Cargo.toml | 2 +- + wrench/Cargo.toml | 2 +- + wrench/reftests/aa/aa-dist-bug.yaml | 2 +- + .../clip/clip-45-degree-rotation-ref.png | Bin 13247 -> 13358 bytes + .../clip/clip-45-degree-rotation.yaml | 4 +- + wrench/reftests/clip/clip-out-rotation.yaml | 2 +- + wrench/reftests/clip/custom-clip-chains.yaml | 2 +- + wrench/reftests/clip/reftest.list | 2 +- + .../filters/backdrop-filter-perspective.png | Bin 60137 -> 60142 bytes + .../filters/backdrop-filter-perspective.yaml | 2 +- + .../filters/filter-drop-shadow-clip-2.yaml | 2 +- + .../filters/filter-drop-shadow-clip-3.yaml | 4 +- + .../filters/svg-filter-blur-transforms.yaml | 2 +- + .../svg-filter-drop-shadow-perspective.png | Bin 13041 -> 13036 bytes + .../svg-filter-drop-shadow-perspective.yaml | 2 +- + wrench/reftests/split/near-plane.yaml | 2 +- + wrench/reftests/split/same-plane.png | Bin 4280 -> 4279 bytes + wrench/reftests/split/same-plane.yaml | 4 +- + wrench/reftests/split/simple.yaml | 4 +- + wrench/reftests/split/split-intersect1.yaml | 2 +- + wrench/reftests/text/alpha-transform.yaml | 2 +- + wrench/reftests/text/raster-space.yaml | 6 +- + wrench/reftests/text/shadow-transforms.yaml | 4 +- + wrench/reftests/text/subpixel-rotate.yaml | 2 +- + wrench/reftests/text/writing-modes-ref.yaml | 4 +- + wrench/reftests/transforms/border-zoom.png | Bin 27613 -> 27613 bytes + wrench/reftests/transforms/border-zoom.yaml | 2 +- + .../reftests/transforms/content-offset.yaml | 2 +- + .../transforms/large-raster-root.yaml | 2 +- + wrench/reftests/transforms/local-clip.png | Bin 2187 -> 2138 bytes + wrench/reftests/transforms/local-clip.yaml | 2 +- + .../reftests/transforms/near-plane-clip.yaml | 2 +- + .../transforms/perspective-border-radius.yaml | 2 +- + .../reftests/transforms/perspective-clip.png | Bin 16937 -> 16932 bytes + .../reftests/transforms/perspective-clip.yaml | 2 +- + .../reftests/transforms/perspective-mask.png | Bin 2281 -> 2285 bytes + .../reftests/transforms/perspective-mask.yaml | 2 +- + .../transforms/perspective-origin.yaml | 2 +- + wrench/reftests/transforms/prim-suite.yaml | 2 +- + .../transforms/rotated-clip-large.png | Bin 7421 -> 7420 bytes + .../transforms/rotated-clip-large.yaml | 2 +- + wrench/reftests/transforms/rotated-clip.yaml | 2 +- + wrench/reftests/transforms/rotated-image.png | Bin 7449 -> 7441 bytes + wrench/reftests/transforms/rotated-image.yaml | 2 +- + .../transforms/screen-space-blit-trivial.yaml | 4 +- + .../reftests/transforms/screen-space-blit.png | Bin 73335 -> 73453 bytes + .../transforms/screen-space-blit.yaml | 4 +- + .../transforms/screen-space-blur.yaml | 4 +- + wrench/src/yaml_helper.rs | 54 ++++++--------- + 73 files changed, 192 insertions(+), 203 deletions(-) + +diff --git a/Cargo.lock b/Cargo.lock +index 617092292153887184c585e34723e97bcc78acac..b6085604cae8e18de3273bcddac43fa0a7e1abd1 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -460,7 +460,7 @@ dependencies = [ + name = "direct-composition" + version = "0.1.0" + dependencies = [ +- "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", ++ "euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mozangle 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "webrender 0.61.0", +@@ -513,7 +513,7 @@ dependencies = [ + + [[package]] + name = "euclid" +-version = "0.20.10" ++version = "0.22.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + dependencies = [ + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -1142,7 +1142,7 @@ dependencies = [ + name = "peek-poke" + version = "0.2.0" + dependencies = [ +- "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", ++ "euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "peek-poke-derive 0.2.1", + ] + +@@ -1169,11 +1169,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" + + [[package]] + name = "plane-split" +-version = "0.15.0" ++version = "0.17.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + dependencies = [ + "binary-space-partition 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +- "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", ++ "euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + ] +@@ -1626,7 +1626,7 @@ dependencies = [ + name = "tileview" + version = "0.1.0" + dependencies = [ +- "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", ++ "euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", + "webrender 0.61.0", +@@ -1797,7 +1797,7 @@ dependencies = [ + "core-text 19.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cstr 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "dwrote 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", +- "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", ++ "euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "freetype 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -1809,7 +1809,7 @@ dependencies = [ + "malloc_size_of_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mozangle 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +- "plane-split 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", ++ "plane-split 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", + "png 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -1834,7 +1834,7 @@ dependencies = [ + "app_units 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", +- "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", ++ "euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glutin 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -1852,7 +1852,7 @@ dependencies = [ + "core-foundation 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core-graphics 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)", +- "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", ++ "euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "malloc_size_of_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "peek-poke 0.2.0", + "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -1945,7 +1945,7 @@ name = "wr_malloc_size_of" + version = "0.0.1" + dependencies = [ + "app_units 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", +- "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", ++ "euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + ] + + [[package]] +@@ -1963,7 +1963,7 @@ dependencies = [ + "crossbeam 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "dwrote 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", +- "euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)", ++ "euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", + "font-loader 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glutin 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)", +@@ -2102,7 +2102,7 @@ dependencies = [ + "checksum dwrote 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" + "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" + "checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38" +-"checksum euclid 0.20.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0c6a5b0c779cd0b744c73a1d2083faf181080d696903cdad99a3b03d015d7030" ++"checksum euclid 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7ab0e07e345fb061928646949fdf5fb888e5d75a57385e7f5856e45be289e745" + "checksum expat-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa" + "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + "checksum font-loader 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c49d6b4c11dca1a1dd931a34a9f397e2da91abe3de4110505f3530a80e560b52" +@@ -2177,7 +2177,7 @@ dependencies = [ + "checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" + "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + "checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" +-"checksum plane-split 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ffe16a646a08f4b4dd74035b9ff8e378eb1a4012a74f14f5889e7001cdbece33" ++"checksum plane-split 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2211e7ccc9b6260779dd9bad59f7b10889d6361974623b9e405afd7e7e764654" + "checksum png 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)" = "910f09135b1ed14bb16be445a8c23ddf0777eca485fbfc7cee00d81fecab158a" + "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" + "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +diff --git a/direct-composition/Cargo.toml b/direct-composition/Cargo.toml +index 3506aec0284102b887d5d77a0dc94811944a9cee..59528f98ce2c0aa2c13315cb89d711031ffa953d 100644 +--- a/direct-composition/Cargo.toml ++++ b/direct-composition/Cargo.toml +@@ -6,7 +6,7 @@ license = "MPL-2.0" + edition = "2018" + + [target.'cfg(windows)'.dependencies] +-euclid = "0.20" ++euclid = "0.22" + gleam = "0.12" + mozangle = {version = "0.3.1", features = ["egl"]} + webrender = {path = "../webrender"} +diff --git a/examples/Cargo.toml b/examples/Cargo.toml +index 09e658ca2ebaf2d145e19f7fec189d0ae13b405c..4cf5d2232d63b5388b239c4325c0e023df36bab5 100644 +--- a/examples/Cargo.toml ++++ b/examples/Cargo.toml +@@ -60,7 +60,7 @@ debug = ["webrender/capture", "webrender/debugger", "webrender/profiler"] + [dependencies] + app_units = "0.7" + env_logger = "0.5" +-euclid = "0.20" ++euclid = "0.22" + gleam = "0.12" + glutin = "0.21" + rayon = "1" +diff --git a/examples/animation.rs b/examples/animation.rs +index 6aa95d0838088c80530e0550cd60ffd264d3bac5..612d891178d53a1eb6d3e752724a61c48a4966e7 100644 +--- a/examples/animation.rs ++++ b/examples/animation.rs +@@ -165,9 +165,9 @@ impl Example for App { + self.angle0 += delta_angle * 0.1; + self.angle1 += delta_angle * 0.2; + self.angle2 -= delta_angle * 0.15; +- let xf0 = LayoutTransform::create_rotation(0.0, 0.0, 1.0, Angle::radians(self.angle0)); +- let xf1 = LayoutTransform::create_rotation(0.0, 0.0, 1.0, Angle::radians(self.angle1)); +- let xf2 = LayoutTransform::create_rotation(0.0, 0.0, 1.0, Angle::radians(self.angle2)); ++ let xf0 = LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::radians(self.angle0)); ++ let xf1 = LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::radians(self.angle1)); ++ let xf2 = LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::radians(self.angle2)); + let mut txn = Transaction::new(); + txn.update_dynamic_properties( + DynamicProperties { +diff --git a/peek-poke/Cargo.toml b/peek-poke/Cargo.toml +index 709c2f30b68df8f9fe40fc306fc9025fc2313a3b..45179aeda603fce3aa4cd7fcc4eb0cff9e38cf27 100644 +--- a/peek-poke/Cargo.toml ++++ b/peek-poke/Cargo.toml +@@ -8,7 +8,7 @@ license = "MIT/Apache-2.0" + edition = "2018" + + [dependencies] +-euclid = { version = "0.20.0", optional = true } ++euclid = { version = "0.22.0", optional = true } + peek-poke-derive = { version = "0.2", path = "./peek-poke-derive", optional = true } + + [features] +diff --git a/tileview/Cargo.toml b/tileview/Cargo.toml +index cb32309424d9829a8a966643fbf828c5ec39c0ae..66e955d5a34ca233796397def7a3302162242a68 100644 +--- a/tileview/Cargo.toml ++++ b/tileview/Cargo.toml +@@ -12,4 +12,4 @@ ron = "0.5" + serde = {version = "1.0.88", features = ["derive"] } + webrender = {path = "../webrender", features=["capture","replay","debugger","png","profiler","no_static_freetype", "leak_checks"]} + webrender_api = {path = "../webrender_api", features=["serialize","deserialize"]} +-euclid = { version = "0.20.0", features = ["serde"] } ++euclid = { version = "0.22.0", features = ["serde"] } +diff --git a/tileview/src/main.rs b/tileview/src/main.rs +index 1706197b0bc4e7c4cf5358f606ed2e058b5d4208..0e33d15e4264cd7b99da4187e165ac026ac3aab4 100644 +--- a/tileview/src/main.rs ++++ b/tileview/src/main.rs +@@ -73,7 +73,7 @@ fn tile_node_to_svg(node: &TileNode, + { + match &node.kind { + TileNodeKind::Leaf { .. } => { +- let rect_world = transform.transform_rect(&node.rect.to_rect()).unwrap(); ++ let rect_world = transform.outer_transformed_rect(&node.rect.to_rect()).unwrap(); + format!("\n", + rect_world.origin.x * svg_settings.scale + svg_settings.x, + rect_world.origin.y * svg_settings.scale + svg_settings.y, +@@ -296,7 +296,7 @@ fn tile_to_svg(key: TileOffset, + origin: tile.rect.origin, + size: PictureSize::new(1.0, 1.0) + }; +- let rect_visual_id_world = slice.transform.transform_rect(&rect_visual_id).unwrap(); ++ let rect_visual_id_world = slice.transform.outer_transformed_rect(&rect_visual_id).unwrap(); + svg += &format!("\n{},{} ({})", + rect_visual_id_world.origin.x * svg_settings.scale + svg_settings.x, + (rect_visual_id_world.origin.y + 110.0) * svg_settings.scale + svg_settings.y, +@@ -312,7 +312,7 @@ fn tile_to_svg(key: TileOffset, + origin: PicturePoint::new(rect.min.x, rect.min.y), + size: PictureSize::new(rect.max.x - rect.min.x, rect.max.y - rect.min.y), + }; +- let rect_world = slice.transform.transform_rect(&rect_pixel).unwrap(); ++ let rect_world = slice.transform.outer_transformed_rect(&rect_pixel).unwrap(); + + let style = + if let Some(prev_tile) = prev_tile { +diff --git a/webrender/Cargo.toml b/webrender/Cargo.toml +index d05cf5979d31b563d3b51d46c61f9e6ea8537fbe..6e3389a309b62f6578d3d481557ebbc43a6bcb37 100644 +--- a/webrender/Cargo.toml ++++ b/webrender/Cargo.toml +@@ -32,7 +32,7 @@ bitflags = "1.2" + byteorder = "1.0" + cfg-if = "0.1.2" + cstr = "0.1.2" +-euclid = { version = "0.20.0", features = ["serde"] } ++euclid = { version = "0.22.0", features = ["serde"] } + fxhash = "0.2.1" + gleam = "0.12.1" + image_loader = { optional = true, version = "0.23", package = "image", default-features = false, features = ["png"] } +@@ -40,7 +40,7 @@ lazy_static = "1" + log = "0.4" + malloc_size_of_derive = "0.1" + num-traits = "0.2" +-plane-split = "0.15" ++plane-split = "0.17" + png = { optional = true, version = "0.16" } + rayon = "1" + ron = { optional = true, version = "0.5" } +diff --git a/webrender/src/batch.rs b/webrender/src/batch.rs +index 15b1e6e968e7ed28bf490314f0b49c648f59afc0..1ee371c6531533d10e5d9a30115eb8c54fa98477 100644 +--- a/webrender/src/batch.rs ++++ b/webrender/src/batch.rs +@@ -2329,14 +2329,14 @@ impl BatchBuilder { + let specific_resource_address = cache_item.uv_rect_handle.as_int(gpu_cache); + prim_header.specific_prim_address = gpu_cache.get_address(&ctx.globals.default_image_handle); + +- let segment_local_clip_rect = prim_header.local_clip_rect.intersection(&segment.local_rect); +- if segment_local_clip_rect.is_none() { +- continue; +- } ++ let segment_local_clip_rect = match prim_header.local_clip_rect.intersection(&segment.local_rect) { ++ Some(rect) => rect, ++ None => { continue; } ++ }; + + let segment_prim_header = PrimitiveHeader { + local_rect: segment.local_rect, +- local_clip_rect: segment_local_clip_rect.unwrap(), ++ local_clip_rect: segment_local_clip_rect, + specific_prim_address: prim_header.specific_prim_address, + transform_id: prim_header.transform_id, + }; +diff --git a/webrender/src/border.rs b/webrender/src/border.rs +index 094c78866a2285663a97a74a0d70f9be4ae71a1a..3185acd42c55bbc0c2b553e82d5b0e23b7f8a37e 100644 +--- a/webrender/src/border.rs ++++ b/webrender/src/border.rs +@@ -1067,12 +1067,12 @@ fn add_corner_segment( + return; + } + +- let segment_rect = image_rect.intersection(&non_overlapping_rect) +- .unwrap_or_else(LayoutRect::zero); +- +- if segment_rect.size.width <= 0. || segment_rect.size.height <= 0. { +- return; +- } ++ let segment_rect = match image_rect.intersection(&non_overlapping_rect) { ++ Some(rect) => rect, ++ None => { ++ return; ++ } ++ }; + + let texture_rect = segment_rect + .translate(-image_rect.origin.to_vector()) +diff --git a/webrender/src/device/gl.rs b/webrender/src/device/gl.rs +index 4bd4d0491c4f20b301a3af3e4d1631d9a0818f86..6ad0e98eef3179f363cdaf6f2c9e44df0c840eb5 100644 +--- a/webrender/src/device/gl.rs ++++ b/webrender/src/device/gl.rs +@@ -2747,7 +2747,7 @@ impl Device { + debug_assert!(self.shader_is_ready); + + self.gl +- .uniform_matrix_4fv(program.u_transform, false, &transform.to_row_major_array()); ++ .uniform_matrix_4fv(program.u_transform, false, &transform.to_array()); + } + + pub fn switch_mode(&self, mode: i32) { +diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs +index f5e103f0042de94a92d0cebfa7363e2916a71b74..5b71479b0a55fac98b7853a0e23d3c8186814432 100644 +--- a/webrender/src/picture.rs ++++ b/webrender/src/picture.rs +@@ -246,7 +246,7 @@ impl From> for TransformKey { + CoordinateSpaceMapping::Transform(ref m) => { + TransformKey::Transform { + m: MatrixKey { +- m: m.to_row_major_array(), ++ m: m.to_array(), + }, + } + } +@@ -1032,7 +1032,7 @@ impl Tile { + /// Print debug information about this tile to a tree printer. + fn print(&self, pt: &mut dyn PrintTreePrinter) { + pt.new_level(format!("Tile {:?}", self.id)); +- pt.add_item(format!("local_tile_rect: {}", self.local_tile_rect)); ++ pt.add_item(format!("local_tile_rect: {:?}", self.local_tile_rect)); + pt.add_item(format!("fract_offset: {:?}", self.fract_offset)); + pt.add_item(format!("background_color: {:?}", self.background_color)); + pt.add_item(format!("invalidation_reason: {:?}", self.invalidation_reason)); +@@ -3212,7 +3212,7 @@ impl TileCacheInstance { + }; + + // If the rect is invalid, no need to create dependencies. +- if prim_rect.size.is_empty_or_negative() { ++ if prim_rect.size.is_empty() { + return None; + } + +diff --git a/webrender/src/prim_store/mod.rs b/webrender/src/prim_store/mod.rs +index 96938add4527ab29791a51b25de230fc2f6e9f44..0a1514a9b35b994e226a424bc86a75a4ac8133d1 100644 +--- a/webrender/src/prim_store/mod.rs ++++ b/webrender/src/prim_store/mod.rs +@@ -2194,7 +2194,7 @@ impl PrimitiveStore { + prim_instance.local_clip_rect + }; + +- if combined_local_clip_rect.size.is_empty_or_negative() { ++ if combined_local_clip_rect.size.is_empty() { + debug_assert!(combined_local_clip_rect.size.width >= 0.0 && + combined_local_clip_rect.size.height >= 0.0); + if prim_instance.is_chased() { +@@ -4352,7 +4352,7 @@ fn get_clipped_device_rect( + ) -> Option { + let unclipped_raster_rect = { + let world_rect = *unclipped * Scale::new(1.0); +- let raster_rect = world_rect * device_pixel_scale.inv(); ++ let raster_rect = world_rect * device_pixel_scale.inverse(); + + raster_rect.cast_unit() + }; +diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs +index 66ad336bef76216fb9488221f32558b9b30575f4..e22596a1babc1d719491774f21ae800186a908c5 100644 +--- a/webrender/src/render_backend.rs ++++ b/webrender/src/render_backend.rs +@@ -487,7 +487,7 @@ impl Document { + } + + fn has_pixels(&self) -> bool { +- !self.view.scene.device_rect.size.is_empty_or_negative() ++ !self.view.scene.device_rect.size.is_empty() + } + + fn process_frame_msg( +diff --git a/webrender/src/render_target.rs b/webrender/src/render_target.rs +index 2bd6277e6eb46651158891bf2a688f1d75657b2e..9a3c953f42a9b38fe188597e232093203de3d9d6 100644 +--- a/webrender/src/render_target.rs ++++ b/webrender/src/render_target.rs +@@ -256,7 +256,7 @@ impl RenderTargetList { + } + }; + +- if alloc_size.is_empty_or_negative() && self.targets.is_empty() { ++ if alloc_size.is_empty() && self.targets.is_empty() { + // push an unused target here, only if we don't have any + self.targets.push(T::new(self.screen_size, self.gpu_supports_fast_clears)); + } +diff --git a/webrender/src/render_task_graph.rs b/webrender/src/render_task_graph.rs +index 7a2bd8b7f81ab2d31a9b9673d0cb63fa84207a98..3058a98838692c40891b31ff0ac4453be3d7eb61 100644 +--- a/webrender/src/render_task_graph.rs ++++ b/webrender/src/render_task_graph.rs +@@ -573,7 +573,7 @@ pub fn dump_render_tasks_as_svg( + + let saved = if task.saved_index.is_some() { " (Saved)" } else { "" }; + let label = text(tx, ty, format!("{}{}", task.kind.as_str(), saved)); +- let size = text(tx, ty + 12.0, format!("{}", task.location.size())); ++ let size = text(tx, ty + 12.0, format!("{:?}", task.location.size())); + + nodes[task_index] = Some(Node { rect, label, size }); + +diff --git a/webrender/src/spatial_node.rs b/webrender/src/spatial_node.rs +index 68f99e4411dd4a966fd548886a20fafcd5e0eb6d..2283b0fe1a956f910d6fd504aed2a98c2c87ae5c 100644 +--- a/webrender/src/spatial_node.rs ++++ b/webrender/src/spatial_node.rs +@@ -337,7 +337,7 @@ impl SpatialNode { + // perspective matrix using the scroll offset. + source_transform + .pre_translate(scroll_offset) +- .post_translate(-scroll_offset) ++ .then_translate(-scroll_offset) + } + ReferenceFrameKind::Perspective { scrolling_relative_to: None } | + ReferenceFrameKind::Transform | ReferenceFrameKind::Zoom => source_transform, +@@ -352,7 +352,7 @@ impl SpatialNode { + // between our reference frame and this node. Finally, we also include + // whatever local transformation this reference frame provides. + let relative_transform = resolved_transform +- .post_translate(snap_offset(state.parent_accumulated_scroll_offset, state.coordinate_system_relative_scale_offset.scale, global_device_pixel_scale)) ++ .then_translate(snap_offset(state.parent_accumulated_scroll_offset, state.coordinate_system_relative_scale_offset.scale, global_device_pixel_scale)) + .to_transform() + .with_destination::(); + +@@ -389,9 +389,9 @@ impl SpatialNode { + if reset_cs_id { + // If we break 2D axis alignment or have a perspective component, we need to start a + // new incompatible coordinate system with which we cannot share clips without masking. +- let transform = state.coordinate_system_relative_scale_offset +- .to_transform() +- .pre_transform(&relative_transform); ++ let transform = relative_transform.then( ++ &state.coordinate_system_relative_scale_offset.to_transform() ++ ); + + // Push that new coordinate system and record the new id. + let coord_system = { +@@ -400,7 +400,7 @@ impl SpatialNode { + if parent_system.should_flatten { + cur_transform.flatten_z_output(); + } +- let world_transform = cur_transform.post_transform(&parent_system.world_transform); ++ let world_transform = cur_transform.then(&parent_system.world_transform); + let determinant = world_transform.determinant(); + info.invertible = determinant != 0.0 && !determinant.is_nan(); + +@@ -902,7 +902,7 @@ fn test_cst_perspective_relative_scroll() { + let mut cst = SpatialTree::new(); + let pipeline_id = PipelineId::dummy(); + let ext_scroll_id = ExternalScrollId(1, pipeline_id); +- let transform = LayoutTransform::create_perspective(100.0); ++ let transform = LayoutTransform::perspective(100.0); + + let root = cst.add_reference_frame( + None, +diff --git a/webrender/src/spatial_tree.rs b/webrender/src/spatial_tree.rs +index e11c982dfdeafb61465cfd7f296e7f644d764921..d1eaca9d8fc4fa7b1802cdb154f58266661484ba 100644 +--- a/webrender/src/spatial_tree.rs ++++ b/webrender/src/spatial_tree.rs +@@ -284,16 +284,16 @@ impl SpatialTree { + let child_cs = &self.coord_systems[child.coordinate_system_id.0 as usize]; + let child_transform = child.content_transform + .to_transform::() +- .post_transform(&child_cs.world_transform); ++ .then(&child_cs.world_transform); + let parent_cs = &self.coord_systems[parent.coordinate_system_id.0 as usize]; + let parent_transform = parent.content_transform + .to_transform() +- .post_transform(&parent_cs.world_transform); ++ .then(&parent_cs.world_transform); + + let result = parent_transform + .inverse() + .unwrap_or_default() +- .post_transform(&child_transform) ++ .then(&child_transform) + .with_source::() + .with_destination::(); + return CoordinateSpaceMapping::Transform(result); +@@ -314,10 +314,10 @@ impl SpatialTree { + } + + coordinate_system_id = coord_system.parent.expect("invalid parent!"); +- transform = transform.post_transform(&coord_system.transform); ++ transform = transform.then(&coord_system.transform); + } + +- transform = transform.post_transform( ++ transform = transform.then( + &parent.content_transform + .inverse() + .to_transform(), +@@ -347,7 +347,7 @@ impl SpatialTree { + }; + let transform = scale_offset + .to_transform() +- .post_transform(&system.world_transform); ++ .then(&system.world_transform); + + CoordinateSpaceMapping::Transform(transform) + } +@@ -815,21 +815,21 @@ fn test_cst_simple_translation() { + let child1 = add_reference_frame( + &mut cst, + Some(root), +- LayoutTransform::create_translation(100.0, 0.0, 0.0), ++ LayoutTransform::translation(100.0, 0.0, 0.0), + LayoutVector2D::zero(), + ); + + let child2 = add_reference_frame( + &mut cst, + Some(child1), +- LayoutTransform::create_translation(0.0, 50.0, 0.0), ++ LayoutTransform::translation(0.0, 50.0, 0.0), + LayoutVector2D::zero(), + ); + + let child3 = add_reference_frame( + &mut cst, + Some(child2), +- LayoutTransform::create_translation(200.0, 200.0, 0.0), ++ LayoutTransform::translation(200.0, 200.0, 0.0), + LayoutVector2D::zero(), + ); + +@@ -857,21 +857,21 @@ fn test_cst_simple_scale() { + let child1 = add_reference_frame( + &mut cst, + Some(root), +- LayoutTransform::create_scale(4.0, 1.0, 1.0), ++ LayoutTransform::scale(4.0, 1.0, 1.0), + LayoutVector2D::zero(), + ); + + let child2 = add_reference_frame( + &mut cst, + Some(child1), +- LayoutTransform::create_scale(1.0, 2.0, 1.0), ++ LayoutTransform::scale(1.0, 2.0, 1.0), + LayoutVector2D::zero(), + ); + + let child3 = add_reference_frame( + &mut cst, + Some(child2), +- LayoutTransform::create_scale(2.0, 2.0, 1.0), ++ LayoutTransform::scale(2.0, 2.0, 1.0), + LayoutVector2D::zero(), + ); + +@@ -900,28 +900,28 @@ fn test_cst_scale_translation() { + let child1 = add_reference_frame( + &mut cst, + Some(root), +- LayoutTransform::create_translation(100.0, 50.0, 0.0), ++ LayoutTransform::translation(100.0, 50.0, 0.0), + LayoutVector2D::zero(), + ); + + let child2 = add_reference_frame( + &mut cst, + Some(child1), +- LayoutTransform::create_scale(2.0, 4.0, 1.0), ++ LayoutTransform::scale(2.0, 4.0, 1.0), + LayoutVector2D::zero(), + ); + + let child3 = add_reference_frame( + &mut cst, + Some(child2), +- LayoutTransform::create_translation(200.0, -100.0, 0.0), ++ LayoutTransform::translation(200.0, -100.0, 0.0), + LayoutVector2D::zero(), + ); + + let child4 = add_reference_frame( + &mut cst, + Some(child3), +- LayoutTransform::create_scale(3.0, 2.0, 1.0), ++ LayoutTransform::scale(3.0, 2.0, 1.0), + LayoutVector2D::zero(), + ); + +@@ -955,7 +955,7 @@ fn test_cst_translation_rotate() { + let child1 = add_reference_frame( + &mut cst, + Some(root), +- LayoutTransform::create_rotation(0.0, 0.0, 1.0, Angle::degrees(90.0)), ++ LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::degrees(-90.0)), + LayoutVector2D::zero(), + ); + +diff --git a/webrender/src/texture_cache.rs b/webrender/src/texture_cache.rs +index 7c254c7a849d053da9c13e1dcace9898e3c16d1d..9b24465680575dfcce2e16dae1cfc780456c46be 100644 +--- a/webrender/src/texture_cache.rs ++++ b/webrender/src/texture_cache.rs +@@ -1202,7 +1202,7 @@ impl TextureCache { + &mut self, + params: &CacheAllocParams, + ) -> CacheEntry { +- assert!(!params.descriptor.size.is_empty_or_negative()); ++ assert!(!params.descriptor.size.is_empty()); + + // If this image doesn't qualify to go in the shared (batching) cache, + // allocate a standalone entry. +diff --git a/webrender/src/util.rs b/webrender/src/util.rs +index f18b80a45df523de3adb9b3364f8422b43822fa1..c6a1038643265cc7579d48ef35a06f268f497028 100644 +--- a/webrender/src/util.rs ++++ b/webrender/src/util.rs +@@ -275,7 +275,7 @@ impl ScaleOffset { + } + + pub fn to_transform(&self) -> Transform3D { +- Transform3D::row_major( ++ Transform3D::new( + self.scale.x, + 0.0, + 0.0, +@@ -376,13 +376,10 @@ impl MatrixHelpers for Transform3D { + + fn inverse_project(&self, target: &Point2D) -> Option> { + let m: Transform2D; +- m = Transform2D::column_major( +- self.m11 - target.x * self.m14, +- self.m21 - target.x * self.m24, +- self.m41 - target.x * self.m44, +- self.m12 - target.y * self.m14, +- self.m22 - target.y * self.m24, +- self.m42 - target.y * self.m44, ++ m = Transform2D::new( ++ self.m11 - target.x * self.m14, self.m12 - target.y * self.m14, ++ self.m21 - target.x * self.m24, self.m22 - target.y * self.m24, ++ self.m41 - target.x * self.m44, self.m42 - target.y * self.m44, + ); + m.inverse().map(|inv| Point2D::new(inv.m31, inv.m32)) + } +@@ -588,6 +585,9 @@ pub fn extract_inner_rect_safe( + extract_inner_rect_impl(rect, radii, 1.0) + } + ++#[cfg(test)] ++use euclid::vec3; ++ + #[cfg(test)] + pub mod test { + use super::*; +@@ -601,7 +601,7 @@ pub mod test { + let p0 = Point2D::new(1.0, 2.0); + // an identical transform doesn't need any inverse projection + assert_eq!(m0.inverse_project(&p0), Some(p0)); +- let m1 = Transform3D::create_rotation(0.0, 1.0, 0.0, Angle::radians(PI / 3.0)); ++ let m1 = Transform3D::rotation(0.0, 1.0, 0.0, Angle::radians(-PI / 3.0)); + // rotation by 60 degrees would imply scaling of X component by a factor of 2 + assert_eq!(m1.inverse_project(&p0), Some(Point2D::new(2.0, 2.0))); + } +@@ -614,18 +614,18 @@ pub mod test { + + #[test] + fn scale_offset_convert() { +- let xref = LayoutTransform::create_translation(130.0, 200.0, 0.0); ++ let xref = LayoutTransform::translation(130.0, 200.0, 0.0); + validate_convert(&xref); + +- let xref = LayoutTransform::create_scale(13.0, 8.0, 1.0); ++ let xref = LayoutTransform::scale(13.0, 8.0, 1.0); + validate_convert(&xref); + +- let xref = LayoutTransform::create_scale(0.5, 0.5, 1.0) ++ let xref = LayoutTransform::scale(0.5, 0.5, 1.0) + .pre_translate(LayoutVector3D::new(124.0, 38.0, 0.0)); + validate_convert(&xref); + +- let xref = LayoutTransform::create_translation(50.0, 240.0, 0.0) +- .pre_transform(&LayoutTransform::create_scale(30.0, 11.0, 1.0)); ++ let xref = LayoutTransform::scale(30.0, 11.0, 1.0) ++ .then_translate(vec3(50.0, 240.0, 0.0)); + validate_convert(&xref); + } + +@@ -642,23 +642,24 @@ pub mod test { + + #[test] + fn scale_offset_inverse() { +- let xref = LayoutTransform::create_translation(130.0, 200.0, 0.0); ++ let xref = LayoutTransform::translation(130.0, 200.0, 0.0); + validate_inverse(&xref); + +- let xref = LayoutTransform::create_scale(13.0, 8.0, 1.0); ++ let xref = LayoutTransform::scale(13.0, 8.0, 1.0); + validate_inverse(&xref); + +- let xref = LayoutTransform::create_scale(0.5, 0.5, 1.0) +- .pre_translate(LayoutVector3D::new(124.0, 38.0, 0.0)); ++ let xref = LayoutTransform::translation(124.0, 38.0, 0.0). ++ then_scale(0.5, 0.5, 1.0); ++ + validate_inverse(&xref); + +- let xref = LayoutTransform::create_translation(50.0, 240.0, 0.0) +- .pre_transform(&LayoutTransform::create_scale(30.0, 11.0, 1.0)); ++ let xref = LayoutTransform::scale(30.0, 11.0, 1.0) ++ .then_translate(vec3(50.0, 240.0, 0.0)); + validate_inverse(&xref); + } + + fn validate_accumulate(x0: &LayoutTransform, x1: &LayoutTransform) { +- let x = x0.pre_transform(x1); ++ let x = x1.then(&x0); + + let s0 = ScaleOffset::from_transform(x0).unwrap(); + let s1 = ScaleOffset::from_transform(x1).unwrap(); +@@ -670,8 +671,8 @@ pub mod test { + + #[test] + fn scale_offset_accumulate() { +- let x0 = LayoutTransform::create_translation(130.0, 200.0, 0.0); +- let x1 = LayoutTransform::create_scale(7.0, 3.0, 1.0); ++ let x0 = LayoutTransform::translation(130.0, 200.0, 0.0); ++ let x1 = LayoutTransform::scale(7.0, 3.0, 1.0); + + validate_accumulate(&x0, &x1); + } +@@ -780,7 +781,7 @@ impl FastTransform { + pub fn to_transform(&self) -> Cow> { + match *self { + FastTransform::Offset(offset) => Cow::Owned( +- Transform3D::create_translation(offset.x, offset.y, 0.0) ++ Transform3D::translation(offset.x, offset.y, 0.0) + ), + FastTransform::Transform { ref transform, .. } => Cow::Borrowed(transform), + } +@@ -799,7 +800,7 @@ impl FastTransform { + } + } + +- pub fn post_transform(&self, other: &FastTransform) -> FastTransform { ++ pub fn then(&self, other: &FastTransform) -> FastTransform { + match *self { + FastTransform::Offset(offset) => match *other { + FastTransform::Offset(other_offset) => { +@@ -817,15 +818,15 @@ impl FastTransform { + FastTransform::Offset(other_offset) => { + FastTransform::with_transform( + transform +- .post_translate(other_offset.to_3d()) ++ .then_translate(other_offset.to_3d()) + .with_destination::() + ) + } + FastTransform::Transform { transform: ref other_transform, inverse: ref other_inverse, is_2d: other_is_2d } => { + FastTransform::Transform { +- transform: transform.post_transform(other_transform), ++ transform: transform.then(other_transform), + inverse: inverse.as_ref().and_then(|self_inv| +- other_inverse.as_ref().map(|other_inv| self_inv.pre_transform(other_inv)) ++ other_inverse.as_ref().map(|other_inv| other_inv.then(self_inv)) + ), + is_2d: is_2d & other_is_2d, + } +@@ -838,7 +839,7 @@ impl FastTransform { + &self, + other: &FastTransform + ) -> FastTransform { +- other.post_transform(self) ++ other.then(self) + } + + pub fn pre_translate(&self, other_offset: Vector2D) -> Self { +@@ -850,13 +851,13 @@ impl FastTransform { + } + } + +- pub fn post_translate(&self, other_offset: Vector2D) -> Self { ++ pub fn then_translate(&self, other_offset: Vector2D) -> Self { + match *self { + FastTransform::Offset(offset) => { + FastTransform::Offset(offset + other_offset * Scale::<_, _, Src>::new(1.0)) + } + FastTransform::Transform { ref transform, .. } => { +- let transform = transform.post_translate(other_offset.to_3d()); ++ let transform = transform.then_translate(other_offset.to_3d()); + FastTransform::with_transform(transform) + } + } +diff --git a/webrender_api/Cargo.toml b/webrender_api/Cargo.toml +index 4ed7ce8e2344ab3db9e6ab50331a2e813ae00fbb..1a48083a10b49e29a811d3cafad9753b8e363614 100644 +--- a/webrender_api/Cargo.toml ++++ b/webrender_api/Cargo.toml +@@ -18,7 +18,7 @@ app_units = "0.7" + bitflags = "1.2" + byteorder = "1.2.1" + derive_more = "0.99" +-euclid = { version = "0.20.0", features = ["serde"] } ++euclid = { version = "0.22.0", features = ["serde"] } + malloc_size_of_derive = "0.1" + serde = { version = "1.0", features = ["rc"] } + serde_derive = "1.0" +diff --git a/webrender_api/src/image.rs b/webrender_api/src/image.rs +index 869cb05aaaad1c007014429e97590d30e9ff681b..deaeb92aeb1c1a7ef9403bcc171e2ee10877d120 100644 +--- a/webrender_api/src/image.rs ++++ b/webrender_api/src/image.rs +@@ -516,8 +516,9 @@ where + + match (*self, *other) { + (All, rect) | (rect, All) => rect, +- (Partial(rect1), Partial(rect2)) => Partial(rect1.intersection(&rect2) +- .unwrap_or_else(Rect::zero)) ++ (Partial(rect1), Partial(rect2)) => { ++ Partial(rect1.intersection(&rect2).unwrap_or_else(Rect::zero)) ++ } + } + } + +@@ -526,9 +527,10 @@ where + use crate::DirtyRect::*; + + match *self { +- All => *rect, +- Partial(dirty_rect) => dirty_rect.intersection(rect) +- .unwrap_or_else(Rect::zero), ++ All => *rect, ++ Partial(dirty_rect) => { ++ dirty_rect.intersection(rect).unwrap_or_else(Rect::zero) ++ } + } + } + } +diff --git a/webrender_api/src/image_tiling.rs b/webrender_api/src/image_tiling.rs +index 3534ded3640b8d73388d0c9b06f485dac0b6cb3d..8fdc82ef24d49d82169993fab2a880be858615fa 100644 +--- a/webrender_api/src/image_tiling.rs ++++ b/webrender_api/src/image_tiling.rs +@@ -592,13 +592,12 @@ pub fn compute_valid_tiles_if_bounds_change( + new_rect: &DeviceIntRect, + tile_size: u16, + ) -> Option { +- let intersection = prev_rect.intersection(new_rect); +- +- if intersection.is_none() { +- return Some(TileRange::zero()); +- } +- +- let intersection = intersection.unwrap_or_else(DeviceIntRect::zero); ++ let intersection = match prev_rect.intersection(new_rect) { ++ Some(rect) => rect, ++ None => { ++ return Some(TileRange::zero()); ++ } ++ }; + + let left = prev_rect.min_x() != new_rect.min_x(); + let right = prev_rect.max_x() != new_rect.max_x(); +diff --git a/webrender_api/src/resources.rs b/webrender_api/src/resources.rs +index 19b97df1cb40fbcc883d2411cb2f0cc631f86422..c41fdc3009e30806af89e809f3a999f30396b274 100644 +--- a/webrender_api/src/resources.rs ++++ b/webrender_api/src/resources.rs +@@ -291,13 +291,12 @@ fn compute_valid_tiles_if_bounds_change( + new_rect: &DeviceIntRect, + tile_size: u16, + ) -> Option { +- let intersection = prev_rect.intersection(new_rect); +- +- if intersection.is_none() { +- return Some(TileRange::zero()); +- } +- +- let intersection = intersection.unwrap_or_else(DeviceIntRect::zero); ++ let intersection = match prev_rect.intersection(new_rect) { ++ Some(rect) => rect, ++ None => { ++ return Some(TileRange::zero()); ++ } ++ }; + + let left = prev_rect.min_x() != new_rect.min_x(); + let right = prev_rect.max_x() != new_rect.max_x(); +diff --git a/wr_malloc_size_of/Cargo.toml b/wr_malloc_size_of/Cargo.toml +index 589e309ba1396eb84a6a371ec55d4313765ccd67..a1f92126fc4b7bb9e3e1b6852f0550ab563715c2 100644 +--- a/wr_malloc_size_of/Cargo.toml ++++ b/wr_malloc_size_of/Cargo.toml +@@ -11,4 +11,4 @@ path = "lib.rs" + + [dependencies] + app_units = "0.7" +-euclid = "0.20" ++euclid = "0.22" +diff --git a/wrench/Cargo.toml b/wrench/Cargo.toml +index 33679485acf1f6fb885a3c7f0a5f6e813176360d..5595ced68e277f9a18a7a678edfe83bbdfb218ff 100644 +--- a/wrench/Cargo.toml ++++ b/wrench/Cargo.toml +@@ -11,7 +11,7 @@ base64 = "0.10" + bincode = "1.0" + byteorder = "1.0" + env_logger = { version = "0.5", optional = true } +-euclid = "0.20" ++euclid = "0.22" + gleam = "0.12" + glutin = "0.21" + app_units = "0.7" +diff --git a/wrench/reftests/aa/aa-dist-bug.yaml b/wrench/reftests/aa/aa-dist-bug.yaml +index 7d91d87ad6ae30e2017f066c8271b85e29736737..19b747aabce4adf42a94b97995100ecbbab2556f 100644 +--- a/wrench/reftests/aa/aa-dist-bug.yaml ++++ b/wrench/reftests/aa/aa-dist-bug.yaml +@@ -6,7 +6,7 @@ + root: + items: + - type: stacking-context +- transform: rotate-z(45) rotate-x(60) ++ transform: rotate-z(-45) rotate-x(-60) + transform-origin: 300 300 + items: + - type: box-shadow +diff --git a/wrench/reftests/clip/clip-45-degree-rotation-ref.png b/wrench/reftests/clip/clip-45-degree-rotation-ref.png +index cbe0e4d38ecdf6f1c165374050da84d1018bb76b..620eb49e43504e30b19f9c27a680603e2762e1c8 100644 +GIT binary patch +literal 13358 +zcmeHM`(IPXwx1n08Wc4T6;Kffuhs|Ir_?uMgoyG`JZgPk*;YlZD75&Xh-T+lH92Zg +zuSm3t^tQG1p{3VT!F#mXtsWmWT5q+Yf)KGP))y&0qQ-D$cGe{JFSwuk`6NGOvSzLI +zUEf(VYi7kfVf>iR9eZ{}2z5>ytDlHa2LT~=h`c@irTM}SDF`JzPScN?T)6vQ{SWU( +zO<%EY&i1ew{|G-AIse2d_5NuyPODD`^$1Q_Q5h9IJpI&x)ZmTmShVxu&DFP8hX)^- +z7O40Dm^=(E5ZO&KHjzb#ok#{)muye&<*w +zq@gg(kX1grPl?j`tdNmr>;6N=9eFR_U2YPkF`68Qj_pnQyERV3d44Dj6!VvjbB$0M +zJsOqiD5t3Uc%xbwB25tf&hqx +z1mFpal!h!l?{WDg7A0)n;duJnow}6P+n(Qq4D9B7l+Vh&(I|HI&(2jE2(YRHV_CTB +z6;bXdV@k-&!9_OmzG&u@benpJCA&aUHU@X^6*1IE)_Ks@trfh#aDbl@k44tG$ +z`-VGKuFBGuoRqtYk4S+a%TAOzwJC2n1t#OLq5EdhBP~!1camNAsR>IN$}DKgGr(kV +zH_Pu*8J=>6sFlK^WY_)_z+l;cY`2a#7C~IXP`Q|!?%JOL +z;!|7!j?Ww0T(j6t`SHa&Dw+x=zS +zN~NKIDi3A(rLpc??-vbCwQtA+cqd{}f!Z;cn&6Tc%U5RWGiu`7KylV+*PadFfs1D> +zS!QeNpL0?i%koLl?sGJVkP>7Y-K3=)O=J0UD#K8^&rd|6)(@R$W@3(tV?7+Tk3g-R +z9Az2Q>vNWOYutZ?0t*#`7|TeLbr!W>nSoMWH*>(gZk(*;x8vClVqgs}Mxm@s*Zcug +zxd&4?u}M3Vy8m4v#6`RRdmt6JXA0kF((b1uy-*8r8uu@|AmA!3Q>bdv9`%ZqLR^IV +zm%0mQZs4P_)ghAtsEA{R>~@}+-5-+oxms9jw%$9d@|Z$l6zZC^izw4XTWLt6+XOos)jPet+Ru65y* +zodaY~ +zph;U#w`gG&YS`-RbeDp*s)Usq_m6{U$a`6Pn8{j2^TXbOsY!4IUsb@`vGxPUvlr4- +z%ZWo+IXN82=MtEjc8=iZaHt8>1I5i|YgJc(V%du7w>Ue!pyDfXakI%fVJ*;U)1#$2 +z*SZk)(DV*D+y*Ll(v*}O+Hu4>%?+$CnM!5DFyfi&xCpyA7f=|2+ +z(?3-SQ&omJnZS-t!xRQ!aV(I(h_w$lTepmYT-zoWS1JsV8v)U$3L!7rog54J$3~z= +z3^12QJB)FrH)(BDIW-n#k8-U$49tp^tUU`Oiv#VDt;mIu-J-5PQ3_gx!AMhYlO83d +zxB_NFW(t3mi+LuiIFve*G0yIeV5bS7;rl{cnk(QyBm{!53uBtJ`5XV7dlSw)Vy3wQ +z9?StYCcn8H^)MI*LT(A~?CUHxX{!Q&yoznFx2tk?$~_G`v-0i#LH*sb0n@-q_ss6X +zzp^W0Afn54ZxZW8kQ>5?aY`bl0bkB2CeB7sKKlF@F@7%%xGvgAd`~b!K##hG5@i+G +zZjOmZ8?Cz05dQg;$;8%AkfjyhSqQKw>UoDohD~pxMSn(U5>^&XboRQBiRVqUm>wFG +zON_B#yXvz@B911fcJb=Mq}fs;p}RrB7*NF47)}~jt^1hxh&~ALFZGNiK7uHf1J}!O +zI2^s6)l7Xx5GK%EU$wmp5hp-+HE&%ZVoJ@D^!B_X*iMz8O^bUEaim^i2_aXA7uEw@2ZpHK!%@mpPgGW{_e0lGuuNIYB@uzu>h +zVMKfb3W$*Tl_ZX*;)0bV+!G66PfUN%kvQuD;l5Yjn?O&5ZtIPe*}t{R?*y^D^ZY&M8dp1^7hKmSl%OE- +z8n~+*joS0wVJ0s7%(QWy)E0WOJQ@TCi|8f;NDc9OGJt9*t|0?TL=qnCD*V$9(rINJ +zIty#6%f^Fobj3yz2_1x!!ONF>tzqug>b7C7CtAbsB+t!ZhEiY_rn{uOy+Hi!QW~V^ +z5WX{n2(ibrrLEbVg(E3w(n^DSDR?l64#wa?0v%-Fftn78&OnE!#=JwK9}RAXVK)zA +z-4X!Z0E`V^5AqwJduo!Y-gQQ@iF}=IGr!=8Aoly6dG-00F1C=kH +z+QE)1(H2}%h)W@8aDX(X26_+n$VY*^|4K>D4FU%Ls1K7ISir%lfAmFKJuiG~@`SKu +z@p3d9C+=JbA)+o;0&tNM{ry=P38|6-7@8{~)(wF5vdwLkNP35I5RdP*SmAORaL~G_ +zhlC&r5LhN%kQAP)AjH{cCDNL4k(EL`ygX3E8>M0ZgwQPM>w1C1s%8&mhrpiw(^XOg +zbpSx9T_Nf7VW3~QxLR`f09G2^zUQ&9gj(2kO+w&JoZ?UQlF4vb1>xuIl1Y0an5;?t +zLNd7~#*;j#wr{W$WLE&0zj2#H<3Va+W@%>iLjYCK(|}xS$adQg26O?Fq-4(>2N8T}iI*bi2M8vd +z-YzLDrXdf!(4Op``V3I$yK5Lxuuq{1zdn)_Y9Tj;pIG}yvnVd0A>f-OKACIupr0Cn +ztQb%p*cV@8?FrbNSFAzVxZecExt@%(orbef=c&=ikj$=sM(M!U(tak9>k2s*vJ(~K +z3*8^ob~9`5WfHegZIxUctTINH!0O@~@xusNN*7Qbt3i!v?0N?cyeGb0D??7ubTzZ~ +zQavByRaOauBV@;hK$PUFu2hEnPS<6btYy8PucV%HB2Zo?yS@l!^F3twIIN2_aTAn6 +zFQsvgfkxensfkpEJfWK}K_&E38$TWiKrN-H9^W)yQW9*e9hcpOSwJv($3)9ghJ!Hy +z)#Eo_9RNdX6v9f3dj%j7@Dpi|NvtXdPFnggg<~{aJvByNSUcrG)l~f)ey}Nw*Neco +zIA4~p)NsXg$2l!%7QWS&0fz{`?=~up*GB+y`!HGlC=KU|hHRacg`bOTdn%nG%b%m+ +z5~#1G`0*GgONgT4Bv}j6^Eout>$6aw59GEW8p(3jZZwI}RQnQsz(&dbb1blOc|A&u +zP+FoX@D;3mlv#|10}zq7b0(WJkBXnL_8-jRCaS#|h)!#m}>q2Qxak*FY8IfN>28I5~qnwHB{8N2?k@YPz;9yO3?E-o6}yMhzkXq=n6{Vk|1X{Vtp_ +zs2KNCgOpC(ypV2jXs;5tblD_&OxSzMxSJ-?N;xDQ;51DOE$yfpcYqf^k>eMEq@Lf8 +zvHX&=8NW6>)}CT<-XWhD#~o=?8S#5U`dq?Y?9?I_KXjxaX=1BV2)+{5b&-_p&TPkUh+icNj&MMnDh+rRWF +z$_qY6Egd7H!n0?!jJXJi=tuU63!2~(aji4m-7>aaQ$yFGA+%u +z)JOz>EPa3mn6MG+q$Pp1(nl9A_M5G41T6Fofn +zx$`HAe^>ZV6zMeZAD{kO;jgp)!YDD|zd`x0i~g&&=P>XWMt@=S7e;?!^cP0LfWMF8 +z??Lg>RzUv3=r4@^!syux|Lsu3rXPN-?B$cf2hn7;(ngQhA4ul^_HPU( +B|IPpa + +literal 13247 +zcmeHO`(IPnmfz=i)F7xy&;p7)!pr(fdhrn~2v;MIieOtG*lIayMd#WUT6{#I=AVT4nm8c=i-Y0v3|G@k(pHK3O +zuAH^L>$|>t?Xw^GU|NuqF?`sVVF;n&S(&<55E?2V#A#(i@Hcmw{xJuk#Ih{i9~bIt +zI$JJ&5%*V=2ny~Gig`Y_+<;XwqD$n>U#Ia +znZ-el#!0rFXYP3(wydcWx;$0+eH}+`Y<+UCDSxZ?_h6g>e{5-}Y{pxz9<{;ydsDmF +z+WWYe^LE@0@I5^rkIv0FQ(>K<()hkelnXZl3%|QDRuL+FA^a2|I&)KZ4S2)^3pbCK +zIJ!QR1xX&_-#LDNPp0Rz!pKNpgziPQf7Y4?A`U5pr9V$}^`vv8YJ~V$Rx|IH7;kI2 +zDA}lDHY!LeIX{q8ArmLvDtDK}u!AIzqG^m}b$FPeP{;csj>e+n+Y_H5v54azTzaOC +zC2}F_Th%~{n)xWc<41Q%_r+tW`QDK}l=?X+{$I8~ljb?bQnU9N)G&^JCH)!Hj9}r1 +z(4+y>@GKO+qU;%fQH|5UZ8r4^J!hoPK<(ur9`V*}9dLs0b-C +zG5a&Qq3|tQaYG13s>W32(~2KqqJjxYNPW5I~&tC6i?F~))}sFDj|AGx0ng(fHo5Q8#2y{&4i +zw=>(9#k~jF7Bt@IF^N9AREG*e9bFe7=~isZ99_L&x5l92Bv;S7kfKl_oUC>~O@OQ(-ukK(()*i`wOmE-lR^Y>If5Pz_W4x8Ql+QnKlQArJ0`P +zKwFc*SR$2%<~WLN%RueR9cqwl8OOKAdfx*FD`|`J7i*@+>rD!OD}_Ui`RO2eO$HjFG=#z1t!MWu()hO=jxtP4TIrwSn<*82(!LFXJ)Tj8F! +zkCx$_$>>!M^#%xDhl9(l^|PsF`dXCCxXf1p?6N`_n&AD9382}#tW#{vUkuHXoQ@)@ +z-GSC%+Qj!0(8x^Bj$=?*4-SvOh)8u~-7B;E8XTxc-gW0In=ngEVb5xkX +zxaT_5wk%rMF3uiOZq>|yV1-Z@>z%!gGOLz}xpKp&ka+@UA8N8*iKTr#m~{?n%wJ1~ +zNVpy#Zc-Xn45zd;iHv)MLtPKS1#!rb=Gid~XjgFdNV#Ff+u%NyagTGT8-V8lnOG<{ +z7}elDiL)1$TgBHw!y&9Qq%q%~0rq;hcVIZEc=W4m^qj$% +zNSj;Sw_heY)J5?$`H7cikz}K3GW)Q*1bhjAA74EPgH@!_A-Pl?go8Co8myf+g#?#F +zaL{KYI0jTdw3;xnL2xA=G8`NX!9PAEWf#DZWmu%LZBTY$6DfNT;%lgclZ&-1<72tn^I_NmdGITi+}v_OGx|wH9wC=n?Qf +zDLgq9tB5XxY=z68!{mf!XfN}W0IZw173j1-BygPoH~omjP7CZ_BjA-3T>K?R3f@eE +z^O|oGu3F%_Zl;vGnF_>Nx=-9o|YZ|xzuD(JgL*w7h4sJUn}HIywUx@o3RC7x^y +zBuw2j+1i#kQdl<~!h;>R2$+p-Eri2nBBMr4gSXUkE2Z1Cx?^F5!881 +z#4zlfW~P#NfBPbFexEwq?j0qW>Vfv=_d`kW@07OYNFiKetS=y_H+rc-Q|?as;3#mJ^|d?p4?R%dbpZ4OkVLmDqswt{DSdgY955j?qHm +z+c-vBK>_gc&1kE^z%kkyFi6a=*1p?yvwAP5_8!YMJW=;OdK@V2SGBuPkoak1zR#$_ +z#tJaZ*oX$B4jcEWaiRU(VMbF~=yo*JIw*G$n!c=9_|zqhxq_jZCJRH!Pq +zO8%O!44nOPS1#^~RFzjcT!?P(Xn{0Bp~4`Y(D)JggY8&z(M+>jE^K1-c-zqc4pRz; +z5Qm28G*^4~JBXat)+s4MPHK>dkBIza++*Zp>o|u_;raJ-6wUeT{b9u6;C#%&iLnkV_&=2HBl +z=0Q$;zO-zk_-}v9kqR_W0h!f=a5g1ezoJLy94g#cK4Sn4(lQ0jd97YT4FP*NNoK`2>1q#46k +zKk+#L!d|76@epk%0k7($$aEN*HJMVzigaH)p-Uf0N{2!THP45Uu_~HIOVGYPoycgP +z0IKa+PjnWq!&pvuED<;k1jvpBGt~ZtGFQJ%rq~vfK95VMy +zBIrT(ih}Es!x|8we7_`u9&hW(ltUGN@^L5uhwnMNL1{F?t3tfP+0~eH3CNDOd^2!O +zuhW_|fs7k(HjAK_5*5M?yuE{`ct1H*$LE=8Hd`k03bLRM`U(-2;@zs;xB_oEB{F2U +znxoP@1TLL;t6MH!0sZk-cbksC1Hr=>cYsQ>o|c|wLY`PfXnO+f;c2WhP{&&!^QZt( +ztu(HfMKdjkLtSZH*=7jFI^*5ic?jOXIxBU2Cj37Hx~XeW;XGgkA#4$iYO#o^yf1IJrs3Yb&W{QK&A5 +zE9<42AZxIoj$+SkrQSlSObk&NZ*HK4iOrln&?GvOX)-%LQ+=o7^)(dDIi1lXmF8FA +zs;@)IczZqzroP95lI4ofR#2ie8%5w%^ah<1=za`70&-=kv^La(k9ct`yilFv89lGk +zOa$);d|=Gs3h-G+8eg%Pvj>!m^*SmPIgSP>ETaMJ7(Uyo*jItesT_Q^m1Xax)Eh=J +z`q?T?9Dr>l2V-#*1V2>@3Gr-VA}yRZu+IP1@db1(K;d)nsoQwKM8S%}8SQMYUpONSyVG}?J-_qNrAFg +zDTI)CwtE-$hAj+eGS`lx%Ld;=EB!O*oGBb4CnYW +zF>H4nphvKpLZ$IKBrnD{QD)gGSU>q$tR^*QI{TO4-mMf)?v|Yz0%-5B +znoYsR7D)a$Kup$g?|=~GA}gN#8iL2Mnp}l(EI8v^*drzpmVSh<$lg$~mm%|7rLg&c +zEIEW`!uM+hAu4A$1W#uyzmtA=`bO{R_z4As3Ri%aZ$PPbN2k +z#$)hWs>gR^3slZ&06r~14A0?a!ONpz3S)V-T)Zv!ZBo$B@%S22;m)H&fXE%Cu?ohk +zK_Mi4B0Hs~#D%epWnQ`X6oTC!A=_%59Tt03iZ2ddQ8|lgZq%y67pMyN3jp4w5W+r? +zmBL0*h{Sf8%(Duf6ML7;0|O|O$nYI5=Y<7GJdH1bW7)nO+I)5et2y^t3X4x5(kJ;P +zRIv8*bbQ2+OkoWUFZ@!54-t~>oJ1~}mB#OUJ81MqG)nw%3y04P(qsKTWLx-e3VceF +zOnxM*Y5Ht#KiFw<+dUBcC>ACD`}G)nkdnN`R(!QReO*78_5);lSg@O%XQWStLddej +z^)n@x@T(s7Gb@_PSgKzh0K<3q@1+j}uff4%27+sFaL4})1mpJk&(Br;Oh%KR6)O8P +zFTuZLoL$;qmi;AGb5T39pUJZMsNngGfilU@7;C!v!8OAeOWeKD{hFn#(DC{u15DTA +zA5tE@)n7LG9pyp9hyBS9Daj8n1Mc5*y!%Mi0M|R%K3mdHjvvBmO50invIjDutwp=V +zu`r@bWumikGFu1@RuGMRzqTNqO!O&@a8vmHm$n`Ges@4}zZ;RnFNm)({2e0c;GZ&* +z4E~pcB!mCu;D0&z7fN3b_~(LuF8Jqye=Z>ZvBK8`{ymbUgMVG{uM7Tl!M`r}*9HH@ +z8lDCIdnD;q;J-(bWcdHHNAmPN>P#bl*TCo + +diff --git a/wrench/reftests/clip/clip-45-degree-rotation.yaml b/wrench/reftests/clip/clip-45-degree-rotation.yaml +index 21b43586cdf794471483e93465628d818924d93f..316f249a3e052b40e40b8b9ae60e4f625b7c88b3 100644 +--- a/wrench/reftests/clip/clip-45-degree-rotation.yaml ++++ b/wrench/reftests/clip/clip-45-degree-rotation.yaml +@@ -6,7 +6,7 @@ root: + bounds: [0, 0, 0, 0] + "clip-rect": [0, 0, 0, 0] + type: "stacking-context" +- transform: rotate(45) translate(200, 0) ++ transform: rotate(-45) translate(200, 0) + items: + - + bounds: [0, 0, 300, 300] +@@ -23,7 +23,7 @@ root: + "clip-rect": [0, 0, 0, 0] + clip-and-scroll: 5 + type: "stacking-context" +- transform: rotate(-45) translate(-300, 0) ++ transform: rotate(45) translate(-300, 0) + items: + - + bounds: [0, 0, 1598, 1200] +diff --git a/wrench/reftests/clip/clip-out-rotation.yaml b/wrench/reftests/clip/clip-out-rotation.yaml +index 41d511ddc810190ef62b98606279c9b1ef19d1f2..43d4aa36978073fa29ff3ac3cac08801c1d57c26 100644 +--- a/wrench/reftests/clip/clip-out-rotation.yaml ++++ b/wrench/reftests/clip/clip-out-rotation.yaml +@@ -14,7 +14,7 @@ root: + - + bounds: [0, 0, 0, 0] + type: "stacking-context" +- transform: rotate(15) translate(200, 0) ++ transform: rotate(-15) translate(200, 0) + items: + - + bounds: [0, 0, 1000, 1000] +diff --git a/wrench/reftests/clip/custom-clip-chains.yaml b/wrench/reftests/clip/custom-clip-chains.yaml +index 0dfa9f41250ab51c82e89cc9b513de5cc3d2cb64..62e2fde8c513a35d12206bff3f7211aff5f03bb5 100644 +--- a/wrench/reftests/clip/custom-clip-chains.yaml ++++ b/wrench/reftests/clip/custom-clip-chains.yaml +@@ -30,7 +30,7 @@ root: + - + bounds: [0, 0, 200, 200] + type: stacking-context +- transform: rotate(90) ++ transform: rotate(-90) + items: + - type: clip + id: 3 +diff --git a/wrench/reftests/clip/reftest.list b/wrench/reftests/clip/reftest.list +index 625d62a7b43468e4ae8ce2d767d4a8d5a2a19390..8518911a9f4af9a750fc32481f1bdef00cfe4e68 100644 +--- a/wrench/reftests/clip/reftest.list ++++ b/wrench/reftests/clip/reftest.list +@@ -1,4 +1,4 @@ +-platform(linux,mac) == border-with-rounded-clip.yaml border-with-rounded-clip.png ++ platform(linux,mac) == border-with-rounded-clip.yaml border-with-rounded-clip.png + == clip-mode.yaml clip-mode.png + == clip-ellipse.yaml clip-ellipse.png + platform(linux,mac) == clip-45-degree-rotation.yaml clip-45-degree-rotation-ref.png +diff --git a/wrench/reftests/filters/backdrop-filter-perspective.png b/wrench/reftests/filters/backdrop-filter-perspective.png +index 5b0b9aa68cd8599f424118e74d3da1928bc2f14e..83e181e970b2a43a263f3f9e0523400e005483ec 100644 +GIT binary patch +delta 57754 +zcmb??Rb13x^sUmJA}y&XNOyM&DBVcc(48Y8A>Gm-AR-JsGc+PdcMULf*U$_x#MR&Z +z--r8lAMSbHpYxqr-+k6zYwz>jn?XOCK~H7{f>jk{b^Y@W^Re?@DuN%!G}45zf4}lo +z#eeHDAj%SB^r0&z{0lBGqg9X0`@*#foUa8EU38@HNii^3yUDT|)fdc!vaF!>cOW;8E0Ysx6CnOB43aKN&l`> +zW*OFtnEw{#Q|4mj|3cevNN^?pi(FUyq_X=jj+B%eO+hZ{zd(OdMzX;Fy=UB$|9j7W +zf&Z^t{(b)c*FCxUE-o+O8|qmLlNpsRipQTooU>KJQ&xZET4(Ap8vx61v!H>moag1b +z_A}~doCSdmPV;;-c-oN6{bcYAhY*uR17V-<+%RGJLSuqZV`3svBFVr1Qzyktc8gCU +zh~t0Ck~D`PmH-eFYy%t)b%%TtpmHYBGjE+KjvUG|nd)6zS_SLbbI62Tb<9qN25r(| +zoN=iMaxA-a0k3p~U!Y&t>M}ik(Zdep>_p<`0t@z_H(o*zPN95j%ojxe;awoDAP0Q< +zac|PrcZ41vv8)zt`+c}kZikAa?#FY{tbxH=pA5KR*Oe(_uAp<+>G=Z&V=j7DoW4ox +z@9#u!>+Yd>jwUX3qACA=H|ZD!b8$_yVS6C>eybPL*Uj|{STny%KcGO*uUBn`2FScV +zYm6E=5y$Onc_pVN^v34dK8eQ@qSW=YVJ~J=b;BGE%kzlmWMIur+`%2bOlw7pk3$`k +z4TD0v=74Ayez?$zH_?9&B7J_Q3U^NugK<8o36oRb$np`={d>xld5-V7}P6HSN=HETv*91fVY@~&n2*asBplH +zxOINNAZ%JP_7dH9%B&FlTuSy`K +zV8&!2gf;{R4fedn^~=#auz7b>)0=5VTT`xYCNns?Pi=W#<3M#4xPVIpGi*}&cSHtf +z1LG;vTDCC^T4>4ZK@EXx6Grn%!s41d#2wc?`6v~jvadJN=mmsR +z8Rn}c(h{%?7k86^Gx_~*R0K)z24mgnWop=eaId~%R;kPjE7B{ho^^k+xzq +zcf-t_abM$X-%;bcGMgoS-;?@&3MU0L?@H}llj!$RMYg~JUxrw=CyfimGFgsMXhMtR++H7_Le*zmPeGtN{ny6EDyb +z!;?hN{$rH~TOI~yrNnD3nND-$!BBO5YwrkHZpV3 +z7_~JBR5Wq#v8NGVH_pd)VwJ&7%Z*`U1{DlDs_5EXR5#UOUX=9Dwg=*7Efu~nBB +z3LVJzy?YR!i^L^&p#KlBM|3Yye)pb+FU7!OCe&JCb)8_twuvCj3VllqzOTo2g2!4j +zHM{>(vrtdndJPdLh-Ljn5Sf_oz^28X9{0}Dm!0>f +zOFo-UILUI}H(@+Iqg^-GoB#N;gCkd6ZL1xEd$c_XKZ1H+sbey&6S +z8R$P^)>e>v6!tjHfX#85dELWCXNmOZjcYK-qSqQ}V0eq4hu)Ia$k>IAD~97B3xo^Z +zq95;_$7!FA(?u;wT&bY8x(dTc&~c6Fh3oh-hLl;QCYU3Ed%iiv7ZDZk+%T%N4w;3W2by=KO_&Cn1mnp7`#wm5J>z-Rv!~FcVo(9Q4GQT +zq4iA;AX?yPy&y?|A1QNlU +zWdiXJvvtT4`3?ts`=h5Ld(UNUVgM=i%hCG5mD^*pZRBy39H3wQVZ2qj$Y@3dsyJxbjZq2}~A +zEeNR!D=mU8L`s;1N3xg?vj=FQ!IXawE%as~dH5p~A>`fJzMXu}`H%Y_#lG5SJF-03 +z?n>UkxE`YCx5Z6_kNXUW)2s6CUNq}(Qt0ngTRO9O3^Gg1yGts#>H|0psm&thk*zyW +zBoIB`nrg>8h+=FdI&3*_4nUgrNWHSz^Hahu(x!W>GFF2EqD?~a~3iOUY%mk +zL^@%okv+YXI}{=HyprvW&$Sfip=Q|lm>l@11#V$HSD@R8V&d!^)w0&LczxG1AY)^_ +zs?dZppQDeU;f)*_7h%C^BdV}Jj0Eus0fB10uI2>|&IV_zjhX(vg%L~LPLey7J}Irs +zVCb1{n#E;pd(gotWS}bhg6bdH1Iho^8?5S<2c=dqY53r0B9moX^odhDOMk3lJKa%!UYvPt+=KUKd~GZIhpxzx+unwz1>d0R*NA +zuOYOOfqQ;SeGn2{i&eK{SPaD0rtG2=ag*G9#x5`9HSdD#GGO>k`tP_s95a#Kc76&A +zdy3k(IIjJD=z@rS_;N`gy9sz1_bY$AGFcGPZ)y=bb(}Hu{6-apmmm6t6O%($uBi$y +z0MWY3?K`-l6*2pf-|89L>nu={^EMq|2l1#{NwL7>q7wfe)UFt61Z2J>q8AnlCno0b +z?5y*zJ+w?sctn)<=8MMWh)if +zCz_m9MVlv%&~X~vsCQ6f?4dm3zTZ2?Bcf3dI2&Z^u;iR=PKp*vW>_R-?^jJ%AIES6 +z%{7>cN}HDd)FEK-5qMdRog+l|`H1bXNdq4;pErL&^WR&>9!BLDvlQS7FM77#mq~Sh +zxzRV7)oEl_Fb``VY~!XZj^^~mF&63(dbi8Wr@w#kAqsHUF3MVJp%;P8)DfQ_aHH&U +zRVFOw+IZgjWFn=(L2>_~<^#5|*x}*5w6&jWi!>*rR6h|=Q;E^@o8r+kCJ|2&TB*oi +zryn8{PwSdFw}=Sn^H@m~BY#i}_vv*0%;G*xO@8)OcVp6@0=sUA@~7_l({fiuJVh>< +zSm$rp*(4|hq@RdADW}4bt +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 +zBG>%&ouM5&Y*A^Xuj033lbdQcFHux +zFYUmm%{*9ENC2jk)0)Jgj!`M7xE3ec02GQ5ow;KX@4NJ9a1-s$M!M=#5D{cq! +zl%V67W>IUTB%WZ0!v=PvId*nZbp)|o+o<7I5k%t0f+j#=6*H1L;7!dH{N~X=GN0r{ +zs~WStD|Ny{pmvv?_uHbXXcjYX1>sU3@uB!279`2z11T6sDJJR_9@lTBkL=c7@-`oV?JmXQI@2Htn8-ii_z$%+%A +z!B0~dsScwWIMgwM*E(AW>f*-vIeWnCZunwT=GJASb^brDH}x3m3aiD?et%=Z$?ukX +z>(CD$itkGLtQGqAE)s7E^ +zsP9E#pV7Eg?kfY)p+OmyfGJsz(ucm2hLz9%UwjmHyi35`Z+ +zU~@*i1SVB;y5E?dc)c0FQ2DB2w)35v_UAHg-V@Hj`B*lh;wg#E_&-VW68if)ZN9Vz +z(#H?{C3$YXGls%-O;j8o8 +zMuDl{S{runE}qnG>9hy;d>Ox@3TnM~sGMr%6`TTobUvW}^&L|+#{nRadW)f%#Gwu4 +zcDa9rT5p4@+Jg&bd0?qZXb +zTR6I+{hnGVO%pK`m$G6y*Uj>0yza>`3i3ZyL-m(2)DrZSJF{Ag2j7p(3O&RjcT<-D +zy>AnK +z2|#&7=&KQQ)I-3kgHlxFvkVE*!a(U8#-Y>_m7~fJncN3M3IG*(g=!Z+BIE;m=TP8~v0vt?O|E_g) +zYZ~`kNuW6t?u}Yhhkn~it8+I>3OT`s%}FMK>Kr#r5N>RoYXOHJZYMs&kmBeY5gCs2 +z@mG6&R>WxmKMk%Z5j`AYfJGOB`H9DCNYrX!9knvTw!yr8xXQcEEZ~?J&>68}_ct9# +z#{|EMXY<0m-by)gn4oybMZ9Wo-mkoaSzuKHWmDhohp)mYw-q)6g{zw+O2uuBmtMKb +z1ZT3}uBVgEr}H#Fv1ug4#Nd=!J`4EZ;`JK9wjseB#ng+}Hnt_Q5M?32)14S{M-cQ) +zs&H#o7UG>3LjG`F%0*(Y^oZZVt?_JGqsR8Jd}JfiSzcM{2&r6i527mVxT4dNHlS7g$f7&Y!lO}`((3DRqZEGQn$ +z^W~sQPQO%@QIuQ2Sax#U-vv+p91oeba2@hw7Rz*NwMK2Rb@cwUfW-}4OJfyxh8)i; +zNKq>;uK?G>X%8u3k{`?4oxA1WKw2-BIM|V= +z9Ab#op$1Z(%pOI+M2v(Na9HSuVS? +zYPs2AXbv!YgUJ=ZEq_Gv2{NmQ5ojP~ThihplJ+EK?kO%wEP?#XiAZ2h02o~^teMsp +zC-P|AhQwd#UNo0P-i*+s(r4b@kHLDy{PYz=nu_r_4M$S3<+ElCepjfn>++)p1 +zr;GBtw%LW-Ij3XVqSR2)X`GSk^d=8k4wf?GGnt)U-{Da<_z1aBUs$CF-%}R#?xH=O +z*Zzg!9Ek{;&P2|lB{pD!XMX0yvQ%9}<|<5h*;(*WQ0Tr=*OdwuM)hYS3mMHH_-nO3 +z?k>Q6-PrzSf^t*$j}k64d`y8ixWue)y!Dcy)0r6Vm=@b;Av}s2T0(;2+@J?q=2k99 +zo{?$&Z?t{r;SVvW%_R3EIF%Norz;KFh2quDp$V{Z9=G!`2I!L!=;#L7xk>~hJ*3yn +zxoVw{MP`u_cc}k^tf>Cf#BH2aokLQnLy$>3&Nn-~$^c5KaS5$nn^7k-1#WGUVrb*6 +z%l31@1G=FB*?OPR#L^p!HP@ibR5~?_dTlx^=KPpX)l +zeO%(2Q_l%C8wlD~H#NZe>og52xi1*~7wh$sq`_=9froCb!Q;l2ijaj?rq?w5ucMv| +z2E2h|p30FL!hX3!efTN>2$Y@hZo&|Af{X3aDK9u**AZ{xh1RAZsWq0>vJrKXEa +zWdP#m0fO-Sq1V!f#UXcMM7FVgQfdXT===tojG;mu`PsR5ZYgWU(yjB>xltItABpbL +zYuU(AplpDEd_VY*E8;<>{N-<5^>_UhT(ReH|EJw=*>(Ogv>nvK@PL2L#oC{TkLyIi +z#s{M;Q~<`O&6^iO-oU4s7r!^CX*fEiHzQ=#NYL6=j|X|T!bNSSgkOfdN$kGCBSroy +zdO>!D{nT-8tM9(1zCoX^kFi2NZ=AX^#&kmu`^b9Kj~2*6g!=I&3vMrTpW@?vWz;ng +zD>52q48T)q(8?*cOiO9g#8SPC6BDY19-ff4S_1DfJ=fB&96vqKa_%xpCa!Lfj_-Mx +z)ik_SMd)HP4TDSPGb5XN`a(w22Q#Uu!iSsh$Y?hGt)`k3_M57tN)LMPe@pr25+XWR +zAhfeO8?)GjwTPC{ZM|P)H0KYd!qHbEJ}F|tocx#ai*Ar{rV~59tR-RHI={yNdHC(l +zBhXfytNoAAqMJJW)EbpdSfDnh*m_zGo8yYUClmiS&ue~q&cMGK7+=YC`&r}mrW`QC +zy!`=Z+)tlLjkFjtUp8Qr0b}yM%S()Tl0|0!8X<7xV_cR-ckKRl>xHr+nqlNPjSlI1hJ~HCs<=<1T#%c-rsN)U;#C}H>x!ELY-va= +zaO2LECPx(()i3UIXerlxlU^>D-|bL~UIp1gY+rTao3JJdKvrHVwmK`$N?lj#i$A~^ +z8gMu8efzbRdrmvY?%U!*@gVj0A9DF@(TT|ZiHbt(b5{PXz9yT*$;zIMx&wjpHnq8q_t(x$_N+lDM;R0!1Nc?#@PLzJ$(*SsTS$mub{ +z)DQ&`-xsH>i4mkWvQ8(ld*$1x>sBK#>iw%dXq)60D}%Z0KWBzh;E6g9?3~&L6iR)Z +z=?eAr`iUGgvzw>xJN(`CNX>ZKF*KrI*vG_9ht0Qx<)L5>0{mjXm!vwwCL=c1Ze)7# +zV)?Ft5CAI-od+)8?jZo|#98`x&rcU`jh6#^xuB;TZ14ZE*!5U=K&p|aWN)F=`SV7wW}u>z9~DdAWoIA8G`M>F4oJ*@UZf?lV!63fh{ +z|Imp__)&cm=bkN5O5F9>+*T6;xEu%#W^;hW#7NXB0vFhY;oYo-bsk=okW%QD<`#MNpmQnI7NS(zMV<_%fJG?=H +zMfHNZCZMDb$JN!ZArk5QyAw&&+-R=m_KyQJLjN5EM*DVf<&~*i_6BQnhw=x8bG4yH +zm&Ay#q+|gmY_d346)U~?*2RzaL0S~iyCA37c +zF1eYUZ1b`rA=ebz+zR{JFSxbd2V^@djp@ROw|ey|!h1l+L47G~@S6h}!C-jKu5{Vw +zj21dFzL$Ug4jbr9S}sav0_lk1POn- +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 +z5gQAs*b=PVs}7VXcTofj1f!IcFN +z=BV_;&a}T5-HUvmU$RH0mXW6f%dO|JhVD~aD*?46@}eKY^Hof1a7^*2hj#dp)gYL0fgWm{-fymX +zD_}PFpvV#}r;7Yqlm<&BJxi6Sn$14K6}ge7q|Lz4Sd?KIefuh*(^9yxbpEgFobB^9 +zY{Js8;Uc~GxUmxG*TqHjRe!H$Da2a*9Aq+{)o=POE>5JYL>Wi87}C2h2?Sckn_65h +zfjNRZc!eD(_;RKlby~@r!ylB^2QiPK_lei9{tseGBkvCwj&8<-a~Bpi9tX73nsCNr +z@!`sEEx!=Tg>BlQC4Hbqvli?7D5fOJi~kPEN3o5?jK&s_f5-fHf|6;TnQzs%(`lpb +zL>qa^)9IW@$y@FVR9iKFrG%}4cpu&GOIWN`Ie!R~-#w9G^uc945;~p`1(LQx7JWD` +zvL=H)`jZ&2Q?vH6=aO6n%^Acu3n+@?79G8&$tf^v%y# +zRNjW#=)i5puxtNmje-y!X+9LlzUgAI0RU6nk=1`jav!IwvHezQfCQlYQ^%hwOq*VnWsM=Wc2^^js(0e`2TAjl)Vfl3VW_%&_ybytE2|+*^5@NMJF`MsVTvmgB0o0;=I^aR2*zGzUMBQq{l3-|#eVuiK +zcT<{3#ZKcZZ=GB!-?^)>3A@3n@Boz+s{K$tcxZLkc%MnTzf=6frksJl&%6`HDz<5L +zi^MkWd5hTk@Qu6o@}54Zf4^DtuVeZ%A|B~?cs-c3vGeMUW{a;RjWz?OWY4A6A*8yR +zmo)u9$jo@O_zpn!{&%IIG!g{~HvWzOMblM<)s;0%AZTz%aM$4O7Tkgdm*BzOZR745 +zf(Hxk?(PztbCBTf?)S|6bD#HHeZAM}uCA(etZ)0RUh)Q?2%U7hw5FiZT4Kv}#%{?X +zWtFBf??PpfZNR2%E6G`c6;EQvC_|{QuSm!(%GUj=>Ub90z#g|@syL}lf(yDT=|bWI +zP&0tn_P1c-AT8=pNhX4GmyQU#epbP8%QEh#>54N+wZ2F9< +z#_U&*xHgi4;b|yD&7yW9%(d9h0-SWGK9}ww3e=r5(|_V1Ig>W&b*s4iL}-Aove4oo +zXnN|p?^Cw?tQHy1%WzQ}PLzO}P#1sg>`t-g)g6&6c^VEsiN~kLK8ZZAeEzw&loLII +zl22B&AiHMG`pu~7=eS+>ZN~C*hM)0l+KPEv(`236P-oAAdCq3W;%tP8b^3z$@M^$dTI#lyuDcw}O#jlC8Xm3-iom~cqG%wm3((!K>sRa>Y?l`q +zFE>-uIK}YtR0)Up`wLguf$fpz%WMwYE^*<|e}}x|z=uS9d!gP$^r?mg!7{$#N$J6r +z(O;1$D%#`Js0Exp3D8nno=HFIvi_QF(Vzv@ZvMD@fTquP5RL68Taz-Nws|nUt7GyN +zd)hn69yjN`U`tId*uY?k%2Wf(W_Yp0)rt!Q*G{T<-A$ofK=lAY2ofg+1{A?p3uzk? +z$EQm5pbvknik)YJ3bQ4*_EC9W-~zg +zY8BP>1Rz^m+BZ1+UBQ7LZ;@^DhwSp%{~q@0J$)TgX{1N4;mXC9fc8Q=ul??0F-(L< +z7566w+8|8-=tP{&z|RF8Zg~ZEvxYT=ZV|NW*?U4T^XE{P6{T17>N{mWm|0*U>; +z8$_fU{!opB$3OHP4fZj_jlRWM?!BScJ#Z=aRzgHfmP<2OHV;;Uzfso2S%})4(d%le +zkX={z)H)E`5FSu#jt0fi?Jb>eTXFxx)0wtz)hke15j2{_FDn9^9X$V9P0ZS>5Sr!1 +zNO0npx)W`j!6LolKf*)ytC;CP>zL4$<94+E_*=@7tJ3}8Q8lwNWH?K*utt7@54(VE +zI%}}zneqCg)nfJ*k2j +zPMSip=#UNt{l$~+Wi^4J#qT=g#g0x1lH(=e?S&a5D<`${QRvRu817??bt5)8Wt|>r +z+ILgcxR~A_YwbSBGifZD#eP5P-&8alrb~R?Y@+4P}G=8_RU>-5{DjYp{W97fH(A`* +zOR5Z2+FNINv?r|d*I8EOmvrFT|=Wx}H!oAz#6}c6PL9 +zDNQCcEP>+;Hhh4y;jApIUK+Spc=eG$HyvLbiAp`%0nhIeTE}q_p1iOTrxF`C8eZMD +zCMrB)?9xk|MKq;|5VX;7CdlE2%1Ba2pb +zt{oP=JwME(FcV$*bz;t= +z0YH%Yf}1#n*-<9<$R#lo#$o(Neh}GId4mG6eTTcS#aLDIywxKG!SLtU3W%@eozi6N +za>111niJhnnRXuDq4ZYNgXU6|vw#U4>m^hcP)3Q@`V|_FYGB?|X8K#M{6;@oyCQB( +ze^1Q?xf862CitVfxRP-)L(2Px73sm%wYAs@bU&n`Big9PR1!K4uxi +zyZx+}vXkw_*rvOVP}1K{7EUXu$G7Vl%<_DX5QZGKW6u#zEK?T&P9-l`*Nfj +z=961kMvykOFw>RAOseSY2>R55fuShwCEbRBH!b)Cb(`~uc@4c~+m|>&XixOZH8%_) +ze?#0b#>K=sId>5=jnMe!P8DWj*AMR(I{VyGt!*1-8x8EF?V2OiHXoI9Om^CtSAL}1 +z(r&L0l1=ms|MXAV{+kz=?Gf)k{uv%<(5iiJ@+*)apsnXYt}R|~_<1YzH$aS(^clr+ +z<;U|rNxrjSRzt*GiHxM*!CTdI*b(G2D9NcIaVP5C!lVMPVMhDQHYN6cn)O~mjNnLU +zznOgT&glEoZz2rWYAtIS8!XP`3)ONtNsl}vWpjAiq3jp8b@NoYY5RwXm<%6tZ_cDw`fGDpcugvB+R00M=k5}p~k+h$& +z<23q^L|E;!sIbs(-|b$iZyz))r@{zUNgfu=BA(==BB&NKMg`%LAE~B<7)0Lxd!o^v +zWqBq=Ttla0p?1KI(8Gogyd;F-N#;0$*w!DZOvQ{jB9}&=3WPujxUG(G6Xw*|+jmUU +z;F6KSXBf?Anz&9PeiFf#90PZztJOTuy%U_PgBp)~ZQ@mu3~6*D>UR>2^z}8^Bc+ab +zH{l5%{zWA`mPHpf>HswM)tP(+4TRdbp{xH@F(mrBN^YOwBjNE@LmvjZ-?5FuFYNc~ +zZF`v! +zXih4V^ix~RZ-ea$r=S&VAtRErOr8>*25+v~!BXoWw@W*k%l=Aq)E)+k3kfY>ce|Dn +zSDX3-U#r;xx8dyumr7BJ0-u^;u6_)x`Dklfjwgqc7%jg?1+pL8w3XJw3*nu|sq?s- +z;d8)Y`?wZNGeR&Or@!|=%b)k>7=^TK^|XQ4TM=ZDWPe662DYy}tvARe-7wT;+eCHJ +zev=*~7#C82H-(5o5kdsPrz=-zb~Ec!1SWMnxdxcNSZGP*yVI_hWGw+y~> +z-+sK`mWBsx7hXgU1DFT-9d`6d>t4_Vuo-}V8=ZH~?dGj}=IIjlnBNpgBH7q3~`H&*nhcZfQnVyM{!1`H0Nrp}{!k`7kj>3tDqg4A@7i +zdE)j4SRA>YSM~C-%o;)3AD`%notVD-v^a6*uAwY;1BaP2Hr|%=e?b_rMoi#Sk|R2x +zy9hZPJ4pmJ_OY$4aKzVO4UN~NREZ#}?FhLuP-W~0-C=A^*T^98a?46iCTIS#3$~$# +zx!H<94(N?HRFjqxL%!$CPK{MO1(Z@*{eD~Hu+MbSq&F}eBnlsB=6viZKHD7hIeS;( +z);~#vn1LLrXqv@ca6&s=!?0=&@vCy)qCu7(^9E&AOEE_iLbHGIuMj_9YOcGspExh^ +zefW3!5)wQM_Uju$YgDL1=a8-qbQYU3R12^${b`c^ZWTw1j34ptK~)eaPigeikHUgc +z9)E-QYkn+kC+GRO9!)z3zN@%Bly2P4>t*zw>SxhbDq!OR%?U=69IV8&q_vH~ +z7P$XioC&I=LkG{`6E+D2vsKt|4Yx1MPPAx1A{3Hm#BDCnE-Jky-ut`=PkzZ8aLOX) +zcK$Ly`Lk}C+tP%gg^d>=yQEM1(j}MrXmLx$SQc03j +zK~F!f9z*w~3CJm5us3u}y)_;h=7NHSkwbhIKz!Xp1}SyuU2?v_evtz@0e^ydMx!Ye +zk^nZ7vuE6^kxgX)^5S>Yot||p1UvynDz+ZiI}$T*A}?|ce?JAKDyV-DekFz={JJmQ +zSUc$xqE0$Xa`l@kp14s8{tujjb|im_WOsSXeL_qSvEhRQzqb5W8qwYD$R6k^Sc}(w +zri*nIO|gP5F5uMmHYrw9C0Fqx-?V;eXq?wKT87Mm6N}TeA6EA#JO6!kgr@c_eEAH5 +zw--JsZ9k4<%zSkxtR|`Pm_9*C%>a}BZpvqXs0P`>2==c}BO1GAPLUQseVo +zH>pNVo#Le)sE7m?OYLuPKh5B1PWOpP5ioff=EkBc7P+G?{+|BRKuCIt!|>^izGa +z7AT};?MRuz5Q{_TXM}dUzFEr&EIKQ(LD&KhSe;Tu{6X@$mij9=@frU1l&%;Yc}ztw +z8-8s;9`fa#sKlVC+CplXbbWn6F-9gbm3>Mo{5-**V9IDpMKv!YBr?xV=!ceZpk&1v`{ebX-Gb>U5XboR{Tk$@q*{L6x6X-?R&b2jde?y +zYPI=+MMmCYaas@x6q++w4v;^RIinvQmoowk0*p-<*zv-q@0*0`<4!!L&9uzZ{2o`i +z1)QTM_;={7x45MK5utDlT*>XD`+4XRriJ~c9}oD6UYN0`93D%@ckln|L?DSpP>@vM +zR!p}+TRiG2w(#0^P})~D&?SQSS93Wp(8g5VFRh89AUb`ORifPzE9vY_@*HOIFMk8dYye1cMI)Q`B!@F2*)RB6mo=2BBU@qAE*xh +zn4)sVn6%+tae9^$0Hgk?UXG>y_r4kKiwqA^de+Nr%t=;QSMTeS`yyzl(=*~}cxmEu +z^P9d;?CFz1SB#~Su>T{ORlMwq45}XG%!4JyBb{tZbojJq+?sFYymw~^@PT*1Hp;C& +z3$1lBvc0tVEvrrU`&c}a-+Y8ycfuJ3**|xFdO(Y6-OLpbM60;g>-1%t!fGPF^OG@& +zt8sq$0L}B!Rnaq64$nmbjF#gOY4UI}Kcn1nIMx8{Ij +zMdezryp%;o!BY<#jCQxt1niS5dtb`NWykNYGp~1mQWJmrYy?2WzvR|+cx{&*k{I{T +zD9~;IK0Nmds`lMc)E?fSazSpEIQ|)Y)3)ygh1jn|yg%j&^5%K{C8YeL0%KF^J)Nln +zJHO%ElZGX4j +zvHG-{&7ZOzVpsjn@(oxlr_x_Gx9tQBpD4@%lOKSbEu*|zc!hLccC19Jg0%w~5nX?qH^KktbbaQQe-o$eK8m!F>8u$sZn;#vsZl@f<$pRfj(1lS +zkmB{%6>e{u7#G=qtwdUO=QFeBT;f7hxay)2Z@CWPOUSgjU{#~TQDZJEL%FK_tM@qz +z$2Kyd!oCZ+R8@QEQZ8}VFde96A@Huw65e_Fhj$Stu)O~X7kxh4v=-1NRp2{bdEw~y +zPX!Z?`kmTQMhJB}cl%Y}#o07gP<%!(R5vj8);}BVc;_$BfrB=VY1R{!&8K=)uoDKp +z37V~eQ3ar=RU-`(lVC;QW$}!j;P4ml4AiSqD|lJ`cenk0{DO#q{`(1JSR$KlJ};4o +z_*J@so9{uw7 +zozU&5P}rw3REwM`U#&5@Ct;uD6i;G`nEM*=u8#TP6E7;G +z4Tv4XI2#gpH1Fv(7I-=S@k3Ao8JqO|&+2O4k+sXEv6^gxJZe6=kUQ!3xz|(z^HAa< +z?57hEW+TXZrf&5c@&}E4G7{?-y&D4U886*H3Mvh0$9F +zS~CkZNoGYvaT9&eWsaDh^bPat8$ypP=h$OR3XTU^y5PBz?>bMK>pMdW?<|J$PPnVj +z;%vuS!2i%!m>1VQK1JyVEg^n)jc@V->943az~>UfiZ7v5Id`1W?B58VB?FM~2hGwr +zq2SFyw=D_EVTk$wuM`mT#jkhS$#t^m^RG1%%QF7f!4e8>8MlX}j$<`KUliI930_WZ +z3K*~8Dp?uwBnSWEHaSf`2s?7AV6SftEWNeq75_?VLH95CQdB=}gPcExO2u4aUj_OD +z_|<#qGKyw%BA`;j4N;<}e{LC3#1e+DJSvq_%1#iZyihCQEqUTo-5uw1rzgfR+tei8 +z$dB+4`o+HK*YDb=Bwn(>UDi-E!1$$rDEF=1t=n@%NZ1o92=ChpUw +z55C~M;zoN20JX+sa&FbRi4Vo{(#iXSnuX+6SY&87J8Ay#Su~)Ez!A!7?qb~unF)Ou +zdXO*k`D5@Myc;4t$0RD34c%1W|J{fvHnc|Z8&F|5#OM_vq(>lcsV`%!Qb42pKH_4b +z*~NACtiYO3zd(-;SG|vG%K01ceiwiZ&50MJ#9Uky7o?TV@CT%}Kd1bdi>Ge177QOu +zc;yoOmUT4dbWdGsUGc$oU~1j4f;dBq!>-*rf%tyX-ecU-9!#X10p412MZF3CsU +z!Ha!w*3U4t8F_tL_N#hf{I4_L +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 +z^&gCB?$$vXr3g1wlrvx%Q}mTR!3ENbK#R +zZHb(r-xxUq{v={ryv4PPEVJbCi7H6qU%;#1kco>c#mJTpxCh +zej0Myf?H$Ku|_G*LRrmyYgjDm-g%SO-|g97f0hcy?xAs4DB=JbNrkIpa;~1tU;03D +zK_1XIm2ftB2}!}Z!?F|iJ(PsYBX{F@-^sW0|I9tfGz*k4q4ub?Bj7&oLRVp!l2IX; +zxL_;n%HkQMJEN|INfEt@Ld_*4{1#UUm>A9g$+c(sd`cN1bE1kscXfU0|AT$JHFRbV +z{dM>(I2(&~kIMld9pFkmSqbl}=xHKg(hhvVARxV~F*vQC>26$LKN9yDY<{3{zM0j^ +zl8=-@x}#?hdiFi!rQto$d|Dp3vm1<6=rM3Uv$uUZCO{N2@y^z&mDQauw@v%^Ij1R< +zDZad7kL@7cC(`F>u@ViFR%g^dtOOHr6e2OvAvj4K+yEPe=W@16vHPywEVJ5l+R=1! +zUHD(!(+~AKr$gOUk74}WHTL8DYj#JVg}4phmQU|K@npb7-|lD~PlCwHhv7PCe%BTn +z>}=$X%IPs&O11Rf3h2(`E}bpe71jhR)@CLnW*UWv@{@@jYT|-zynI8x(Gq!JN7shL +z2Nf`K>H^XRO)>gS=8U&*2r*JUh@LPXJ({bNr&j~w^~9uNX+okoAXk0cm?T89(FA!# +zz!GUXX7HNdcvz4tTn}!O@TNrBZC!0H>%LS`;N->I@ZFhwuR?%J(1(@)A)5pd)8$*{ +z(#1`2E2UgqgbKCXbd7`g!@<-XN4w4G7f~sURzSk{R@d>*T?w+svU_axlcti<-EYIN +zwYTr!*;Ji?us`BTEZX4v=!Ub-rGM{Eq#v5TgWi)DOR~;P--5?W$GtIXuv?{mJ*YJ9 +z>e$e_V8S#Sv!!QKO_>~*H0#cy91bS=I$EnMcR^R?V!fe&(r +zNqi$@4fYbhS9=m9dwgeg`#p4xuB{r~tiRMpT^|4QbA-#wA+x~s&x%h^B_h;_zU;hG +z@3rv}g*Z5asSAc^X+5Q|zUHU60u3Fr*8`kq(lE{7CA`)WI@2v=*Kq66OMy=gwqin( +zZ@IaNULCadbJW6%*8^cIYge^_S&o}%yl`-i3OCzJWUeG%4__S4-;ZsR8v{Tm(aR?< +ze6ddo&6Yl?mm2n~i%x25apEV`he7B)h^oaoApeh~f}|qmLMC9pjZdwHfNqZOZ4l{m +z#)-Q3c3v{6D8OH*&m^E^wT2qH?#@*QU5jarWNiTAgnvbV9ER&)oXS?P=Y{S=W|0NJ!&1 +z>+-B-%-uVk!m=&?Am&|N@=F|zi;qX2(3Ca*IFv|>?czCtt25;MRb^u1n10xO7vY5~ +z!(X!w|8{386rfZ7 +z3-8?_!A3QY?BlQR;E{P$KRK3{V}d>bp^02L7@@m{9GoRu2j9tMAuu4WUZ32yZX<0w +zjxu11HA~J=ILo^AUZfI3rrYa{7l&Eun7jQMFB!b*yeA-}%PvaO1q88$;+tmg)g{%v +z;Od(_&66}R0k>sfw4txI)k{{`Xw7j;{}_yl(RX|K>%h)YV}6g~;N3@#A5Xc^G;+LS +zobQ)sy!Q0Mpt$y|zGL-P>V9Wa@^Oz4L{>WyI#Nj6_2Ja~IV>fmy$nNu~;6=ROXABgTlQ8tD6&JA((O8WYDz|#ONf=rB%r-f_@|o5EakZJ2 +z!lhyy65h+QRqczY{$4U__$Vdw=sB8$F^N<>v+~_0RjfzO3dhYsurOlYTg0IH(=sZ@ +zNmAPB;P;PWVRLudK3T+Cx;A0iIyDy@;|sSyTn#FETaxhq_vJpj-inSF2ehErF=nHW +zlZ@JCli17b?af{;AeG}A*pahxu0Ng3Owm>*x +zD4S|3ve;mbaAO&5W0+~#E~{dIUzkqRo3YW~Z%97!b{;(>e-?40l^sp`g+cG)gs*mzmIm)p;V@%!KO;f3h5ml9Fqz5^_ndFac3eSzYf(ya5OhucMj +z?-zW5(+NVPZ9N8{+9d-&jb)L1d2jA&K9;uZ+kehXm +z7o4i`mTAJIy5YE%sXbYRaS9dDvx4+Ad{$=@DS}>e5-GNbMIpuQkx+p@_vAN%Wp2^| +z>@n>3upWtw!=F1>J9$nfZp%tou4<;$DNM;jP|{l}mJ9F&d0Fk#$Q@%wnm}|%abVa$ +z+7Fl|M_H|I1oby-BLL1*ikhG0!(}|0JO=Um2pTLxO4HxDVJ~9F&4I8Z#InaSu9%VdrH +z{lXJBXE*I{Qplazd0#%?f(VXiLrLcd(zKbi<>H$k@bb3en{nQ5AhBaR-Ke;(%{{;cL;ICuU=<&DCMz*(J_V +z%SmKiu-rH*HA6jioasp8noeSW7{>8*ue9&?Zl5GHe0nI{OR}g+J1|3vk15A`|ES8Inz`6ji1ohdCYojkCgm; +zY6`#=(+gaV%{a@R3iWvIcfBDom(!0#oQ3h&y?R%jmEE*DDVRLPVVaHcC~9q2|Ms{d +zzQhULRJjDc_1bzf{%@Sgfwa>@%`fk~m*zlnw6?qYG(zF%6mv6#NwU}_qv&+qsHZ`U +zTs$16e$@}M5v^$HN61!QWXiy086fudGtrFrjDJ4BX64g-A%BWIB=B-Q>(xy+i%?qn +zUN0GXZrt{W+l_wKU2Zp8#cev+!G?E2@~T~9j|*`KNneQ|`sfhV&nT(Zrf-HkOk!h5 +zLzVkjK7L8>qtoBfI)5qO_oD?LidXs9CiPfJj?#aXaOA0&mS;A1$)ocOr~q5~5*@jO +zgN3`$G_&)3Cah1|Jg1GP5+8JWDNx1ENo-O$+qhKphLy8UfOtHKMOREKMyV-WWaSP+ +zdDHVN7U55|tQMJxtnIrT&>4lJ;1D5kp>x3NOq9)0%cCHyUem3D+qBBJ?td57kOhiq +zqj#+1;?piTiP=;6U6hbSQh+A`90I?69d=T#CmH?K6Y6oNo&JvmJbXSB(xFyXV-z|q +zzPrkuHHXBdM=9@)LSl}7E1%_TuB1axMgZBqYf8~$0`(qCc_-d(`H#&ZoCffW$a~M$ +z8q!UT(Spt2U%4)nw#shC!te!@>9%4>>)g*psw|$_k4ftybbhV%7Wm8e)!q4+w#gPP +zug(lT?~K0~+zHI37(FA|P2^a5lRkU>YZZpvi$vuft9DiCqAo~Q~OwIMmLA-n<2e>G^JRq5!|rfn~IZK#!NfocQ}n^+*o +z>AM{F3xidWVU^}CV9J>5n_@s_@(Z_SwAdasUuzS4TyK0*03RvgRu00PNie7TutGBb +z>bMki&p3AHA-(ZqNvi!s=nfmYpN^HA2_b=@Nb4U?1 +z?;*EG1Qt2c2N~#y4f+bD_-pF+liHfw3A+yYN +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 +zIC8RqgnZlNwhU#^os$_PL&969#X +zzEZV4pOKp^734!C(61$WKTy77iLrf?jH6a>Z+W?B{^CL-{?QVC2xvlvQvBowvtQ`w +zJy`hCp3m$^y-HFF$n{3p_F>E;oFoxI{XOrd0g!%HlYk)c-yic;9Z%G2U+t3TS@NI5 +zC0@y}?&{F1!10r|8&bxUGX=&g&NIo~-k_q@7rb>1lLv^s$F@5e19i5Mys3U}35h7g +z>_Vta^LLDOBAFbC`WplIy@|Mu(;v=*bCh>t+em)lP16di$yAhIuNaeOYcheuci7wW +zNcB4;wl{9}-yvx6v`DQxH`g7ttLPA+_|NY?nTI_xKy2eb8?HPYYL)x>d#>R-5-|D+ +z|19-D;&~?yF{FaBN1f6uc`H)*)fi`5|b<*AD4#$|*Or`&O^m+(FYnl@m(Zs7&$;$NQ +z!Tt&36y-_SAEa_|If9B<2gB9Y8PbI`F~gh}k^ewc6WKp-+ry5Q<{fGk^GI_b;6xh4 +zA174~D`&`%hD7>z`|Wwxo{+6XLDJR67WWPvhs@vDr~Wx@2(~P#CR?~&K`XUiV`}Y- +zAR%YfrmZu3+4si27#P|GR|%Ny>mdtiJYSSY#H@ +z{d*CL?E%+Nbw74x#I&Ie_^%|gflsfTq|6aNb(WtaV!tCIPanDDut?46;&oLYOAlNP +zl-W#tao0${@6vWxKu%#3i_Y0P%s?RcnQhFdPpLM&bUAjCjSH+_)?-6ykIR)38--ws +z8TMu_$G_VVcT`88;SKP!d;S~zePQJh2$~_@#i|Acr>FU(A2mX;Q7CEL<``z5owBsF +z_jKkRIUkYBK?6~lYtU%1Za0Zd@ha_OTBK#MNw*?j0xa<)k$qvIFp)pmY$&cqWjl8J +zmM0Z)aqFS~nc-TZI!34}jtOd)>zgmr9a(RIXn$;jFx3eGtkAQBAnxvxGljozVSzOy +za}wMPw&uqy9KW~KMk>EMW~uZWZ$*W)OK(ymC0juUPdP&$;^+~let3v)vZ$x_Yi6$0 +z^L{vzzaH9SttV3fvah!O5zsSUxmVXYO=}iA9W)zI?S~pSB7Hun*jrnU>6O@9+UIFq +zGTi}P`6aUf4S5rxu97uGP<9OHX=aU_AW{%aAdHMk+)JZl^l)`L?vE^1wDyYDlXF|| +z+6n2yVfT9Z_iIvKTPyo-I) +z7O&jk`}o#QrLCPg*E(gTk#u?3jSkt?8p$2Fkf{ +znGKL?wPSyd_s{iQobQbm%3cCeqldE#-(FMK{Oz3L$;Ph`hWoasf$RrhU4wX%HbIBXBxWBV3n-JcP +zAj<9M5HP>DDpR#9r=lVJSzgKcsz)(+4haGrQ<_q*L;8-C%k@I2O4s}{lPa&#O-t+I +zT;7U9qeqkeb8Dd=UB +z%VMIV4!o*5eAmD=OGPnbi9zx|4`UrL9y%H1d?ZOn;^HYI$$D-PJO9n)ycl$JD46^| +zK(&j2_@{lMdA_2vKhE~>6!O`z&!wr-#8T}EH)z5dw27DSMN_h`Ex#G9l7)1p@#)u9 +zpVXBh)7Y)Ot?>{BAFq1oe{>#IE?r2Fy!3DXHM7Fq5hiCKago7q=f+G1#<_(LdrfG* +zypDTnJw0WP?6Nb_tJF)jUGe_!LEWkvw@_X_9Jdag=eI3DzNCu+#=$C!e{N|Sgt9YQ +z`9IXnv*xvQK3fWWJ>duCpc4J`pFO^s8?Y!frGKMB2gb~4B6QuE2b-fW$d2+4m`?Ic +zQeK3JEiClJGEY>;d?Gh?S^_w{PCj9AX_4MUowTr)Twfd&W55u%qkLo)8 +zuou}xy2`jyEj~JQ=UTw>z` +z+%?ZSQ%i=RQq96D>+m;*7%x_bx+K8U;znG)@XrYL-V|S+l#I56e22*7o2Q$EWQ%%? +zx!}9~^4`5@h-4NaqWPDubHNJ=h-nrbFnvweM_m5=>vn&d8%OLDhx&ui!yMmgxkD4V +z$%Ff}r^L&P8!ZN39D(H8_lNoPh*HFB7e}U&Y}m{_)(I9bp?LKU@72cfFXgjm*dmOx +zPyJHd3O>bO`tZv>vMn^ITKVe~`h&Q1i)2z7b$o_2pVWO-PxDnp6 +zdVC;j6CJAuRXrpYl^w^m9!CBBYR25&V|rX@aEPr!3t(UR2Ny+rhbukD5?) +zD2Wce)4h#wVvx@|%Lc8hsDL0=dZUM?&_$rB6ZEE8B<*F8EW3-qbX0^`WgK1j-ipNM +zh*J{aR`Fniv-a9n^EP|7#Nm~N}3 +zmJGN0?)ZEp%clQ1b^obhc;X?SLgZH7K!&oB8A9j1RMzPhWk@XV4?IsH_>7 +z-7@_n-R=fWy?dXW7&chq@U~%=e$VBwY_hia61E_2t6ZgO%7rl;=T_A< +ztHP4eLaO~lTb&6xj$sS0mgf#+6FlyD)#iTur$`c*_i9A_Q+C%GtI;3?g?7z#>RTYx +z?7}?RaS{-icHBjYDKS`nIw4 +z1752_)QuI{YZ*;M +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 +zW(L@r`UjJB@ZfmP +zG;MlV1=6 +zIncW9qj(YK74Hjsr?EK33j)8b>+~2PpY#zNvPEE_n6CDkInjguG+Zx)to`Z6f5lYU +zYx{8Yqg9d{Rh__P890#eOgn;~Fc9sQka`fsGUm0lJ?}ZO6$(Qv7alg;H(9#wDBfs{ +zCymrueZ**Kf3BI(p_kt)e(Ijho%1<^>+2Mmsm;MWAQ`(Tc$^R}*!BMQSr|wg8)G&n +zwNYF^e6(R2W$GbY_A-7PHN1CjHsRQY&2$+>eZr1(8oF9atR{Z-LH<$p!=iap^tlbp +z@p`d_yl8eeG+%C$P3??nw_VTPegBM^HF#OS;wB#Fzh{yp8v;!gI8SZdm;2t!6kZdD +z)v0D8Lr&h%TG)1}C85mpRu-@#R8~={Ja<*siET6?r^)M$+p0y}BfRKS_0eno*?00U +z<|uOY>GYsrN;w`Jj2!R#$~W+iUuQ+vE{k*<33Fhk3-RHmi*gYfb(0DVTuhn=N-wDE# +z>4}$z9RMS~MN9qX3Lv=op?Pg?6lRYHf60pW!)Aiwa0<9oijBJQLbFR~T;5IUcqMLV +z#9JCCPge#>&>V-=_>{GK><-TJF3&US0QXIzQzk`~rVn)>B8Pulf1Qr9J$e;SZn5_- +zCul-`5J_aY6)MVw6wrKz6}?{o{UI3Bm_YaB66GW^;upe12hs?X7XfE#(rrNp;TDlz +zO^nR?fm!5ZnIDRkKb(13)_=r3;*2SChE!&MrAM;C +zy6_P#>AVbI2RwUL4SEo9-IW%vz^WqYV-KG4ye@IaR&2jV#Xt6vv5mim +zGhqV0mIIDVib(->b8xSySf?kz!ISmKBy1rDW2y +z9yr*q7Hew%*g!fwM5)roccS;8JQCkhT8Y*Jr>n*7=w +zogXy7-zWQ66}Yyz)v`H%?I`s-eeVvJY~eez^5vNQc8qZH^Bw5z?59O3am{bV&uk>S +z_7}u)hT5_AK50BqN$UoDeTgtvlAIq|vHJ{6k`<6&VKli(kr@dAqvG7^L^giFey@UxA7*2x4ADoGP+GLgGCz#TZEmn2kY#}v9 +z=Zl00Y5S~1VOMBe532D0texU@NH>DdZn}WT3_*mZ=KJEdx +z^-d$9FXVU@_>E!CZ?`G+nOKW-Mf{++2@TVnv!2hHIMbVjoZy270~8eWNNe5XESdZY +z_`*xAVvW9nd`z7yg&Ma^9W7BdI)$h>;H-$9p!_bVx%V{U8&gEB!*HsxFl|&))8=1W +zAD$%DUqo_a2_l2iXIYp#x1H?`Xg1LNzm`eLkxg|4|BSDVz)H+Lw{bn=WFN3*M8*>z +zMjEr2ccip+kP146f35R{mj<`;rl*c3UbL`vEPH*2cw&khMK#`KdJf2z#bEmc1E-hv +zeJ~?KH^7c?3mf5JzhxTlLQ)gI$K~Y~R`Po2F=RWX8IkCIC!D5>1Cj=@jXT?&MUjOp +zWlV$z{Sjl4%(Ps25qgN_Cz=z_I1m%cci81JwUi|c%~Du~!dUUUF0_QbqTxjv_j}aZ +zt%uyv9Da?oV<_!DjUu+1jIc{!9X`Ht8-F46Q2o)@-Zup9PqpKftf#w=w*lu}2#i$h +z>EgqB!O-L@uL-&_6}LHPuqX|-vGxb4BqLmM^&1k>;{;0YPl+JX +zMD}Fy<2_9%Yyr?$CpUm3Dq8G;RtK4EIt}YW%Sj^T5;Z87mdLH+a)`^+NdZJx%ed=v +zpwn3s{;RNOSGi&}B{C)6blek!%va7mgG0F+@3f6n=KeVaN7HOU^F^J0-i~cIM8CZ<%nMQBr;i8 +zVi=K>Oq4Oz19F_1=G8_txJCOl`_GaPD4iB|)Yb9wu%@F_26RQ2P`?mJGYM{+0TE +z-$%_*Aw}tb7#4eL?w +zg1Z?3^$#{?TuG8$Ge44cj5-qn?-d%+`6J!S;mX}YF`Rs5mx*&@AKuQdou=%4sCkfT +zZ;k!JZYU+t7olsIC{g+v$MS{$^`4|0y!7+B{J!!OwbH^PvM`tBb)fr`P)B$lZ=2~TQKV<(GTNfYZG@A%2TPTVgg>w9eM^hGqdj+P01bJuGg352xIBHX&h@Y8?FfE1yJvWeaL!F*Zh6ic^kU+^W +z6-Rdw5nKNGVQ+B?#amQ1^irZ3Y!jOM7oU88SnZK9fF`Sxu}f;u0b +z+VC|_rRX_PHuNpx<&=doY4Q$clOF*~p&{A(J)p8UT_0*YoHH!lo~286%5%1Shmq>d +z8q1O3yQ7Ozl&cq~iW1FmJ_w9~u~T!3 +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 +zo2BM@HY06ktq41p2!AyF*i?R@WH1= +zM`_AvO6W6(Y4YZ$&Vg?n4J2jd47m8G&-az%l8N4sIsL<}i8f!g!j`#?_9)0D^klMa +zNPc<9X7woX3Gyu3q9-TGJZx-3M#cslvo}d(3dP-Kk{@Aw7;(0K;e6*=F~Skyf;ELv +zgRf~>@&+8V9ru=*RVleR*q7^dEe4t1!lBajUQ2mtF241?>>+ZaO6{AUxFo1=yc7NV +zp@6vZ7^(;+taS|b?W;Qq=f-JaV9sH_oT8T{~5XtUK)_ +z!^mAbqIga{p!v)Am$hD+I_Y*ZqlYWAF;B@H(0t5lw{g1cy=1n +zGq}SSax9tqwbF+srgvwGUs_Es%ZIC*#3D<>

BQNImryZm-j$0o_^uV`>+}1flV9 +znL}g4M|xjE2sPXL_WQO|x1H;pfwg8MInEy8p>vWeFmWRlm%xcHC7TWSKHMT+n0kxl +z%5n*R>{bylMKiGl(N}Bua@^{6bM2^Mhg7xGZ%LMpB!?W&(!D}r3xzsM{SFHjr~Qh?i_JA%NmYT<@O}sGG9X +z`JqSz&gT)5hv`+l!BWqy+u7of`X1Mxkcm>Ql +z+x(h0Gn#wYkvI?^dQ}ao4fV*|I)A}ej!$ck(=Ny{KoH7-vNR9G9oBVELy27XN6oC!Fi7VG}$6xugNUP_p4l;OYU>F3L>R+ +z__d`| +zVVAyqJ?eApo*d!UvdL}P#_x8+_HBfnM>*lZf^9vdhTUv2QEpfl=wnB3pZ&P-8lLb3 +zW4NoNJ@C|gOR(DN!%ne`w6m5&o_uCCu4SSO_ldgdG7-WKYaS|4q@FG?m|1v#0sH1N +zGUm(v;+Ic$z6P0Ok^&Fxd?&4ze=2%ecqjWbCW_`j>bIND5<+!Dg)Dm`^eRWkxaV7t +zCgR?N4^0D7Gl5QZfP{)k4~;4NlMkl^UZ}W>RkJa>7=h0Xzp5RXf*17F2^Wzs>}D>O +zQ}2JTKhN=c4Z0O95Zo)9{PTKycVVtk`20wP +z&Mc_v=R%jvYu{ySZ4G>$jhwU!*&QyxG2f2jJYD^eSG#$$;VY^dRj8Yl2K4LUYkXr9 +z!QwLHvw%6!N^Dck2|XDa{%KRjk4YxjUS`eZD|l +z*%67%8;eLge^tuD9|w1ZpVf2J3lj3(f#9{4Re+(z7|ar +z6OZRVIpT9fsjAvWwL=MxOnG5wI(9BUP6Tl0%z_QL#zJG$sh)SmVz09D+;cT7yh%v% +z$~t1*PGC168EcQ2a>$g$@jNY>$`6ij==!X8jZm)|rzYd#Q(2>09*D%ce;VV00|S0} +zIoM&x9lIz#Dbf3b?>Q9^49qyuh{1lT^{N>32dWH703y?g8-pKjn^#J~=&f#ve{sum +zJ1^6R%iD7+v8g;Yd~PGJ!EKq-`U-wWL5hz9yYj8oCwPn2G1EFU@-UuH+_9r$n2Ya< +zcd19%_UVQHlzty0tvj~?$T3FV&THP7X8fSb| +zf6PkSaalMy1l{hb2Bef2)w*vXzPrd$jxNr=S-pblQ69PIIR=4TP7k-N_vjkW#VNy1 +zVW|~vvKj-m+s|FWl>*peaxTki*&!=QY*MN+%3cu$UWYtu>9C2)0Q*Eyy}69S@!qy{ +z9L@0-nQRaju{To8b)hLz=i1J{Rrk3FGuFsvw+nir$8@=$SpXD&GJ->vx!mYHK++jHJwF2D#am7p)v+&>5se5`B2OLV~$Q_5tAE_0|GsrOXsr2S6 +z2o*p&1^h%>0@73J|jad5dCVy0!r^(k2a6|)uOJ9VTwp2Ls> +zT1IcgDUge)W{-xh;5xgOH^=WIeIN4WOcAx##w(Pw +ztNzNX@()t~!+im62;CMeVBarcR~!&~e6p71L^hDm6cjNNICVHUuGQKe0>tK9W$agvK;8)kpOA(LYd +z#DljHp-KF0I=+GBZ5h&V$2Xzb`#ME(#Pb^SC0i;Z8IW3yIR?!OX?`i}{FPe60z$C` +zv$I&-z(U&loi*`oXtl|JCQSjK8$4j>r92I)r;cQKvbRGe$p)w;q4d-1XvXZmJ0&Lyi}O +zj+&M*;Bk8C*^rY$lrT=1qBIwbj%d)N}76`gX +zDV8s6xImq{_%Mqm10BalZWfh3p>NT4Y2>571Q0#CvX +zhZ-B-)f#zG}3rgo{?8tB8m_jtu+mASs`mnrsKgCt4W;G2f!I_{ZJuXmBJ{Pi2)@ +zGC2_g86}ozm8GXvEz=cRzK$Doq-qHGCMP9v7?;;;krNdRWCd|2<5s@JJMDa@w#2}^ +zIF%j_&iNGq7P$QUgjo}eaT?2{f3E1dp;ujssIoJGEmepD@ozpK+>EMZ~EgeZJJ+?X^ +z89;7PXkORci67oe0748b+XDFm +z(7fbQg9b)>zc>gxjXWOsHoO+RLtjxOyqRb|qf_spTb~wgRqyeLM1T!+(4MC2+0@&$Wq@68ZHfRY +zrU*Q>c+lSvN&(0=?R2805dG5Qy$N=0(QdVZkU?EAQ6=X_Fc2rY`{+b0B!GVDye7N! +zKtewa2GtzA3;{x+XhEf5ld||4L3P>9_yI$3OLCjI5wyhaI +z{HCvnh%U?}{q47PJ5B(Tjs(w*xU;it2i`*z7J@>7gu)2ap!g+Tuy<1sp9YDgNZ)$q +ztfNZdN2%Xom;?`+Ebrs>2OlduXa^QpC~0ihe)S^o>yQ4HCxPGDm|w66_}3o%1<;=` +zf&Yh?{u7IU-$kT<0{Rms@UPhWi=aPY0>3$*{;5U4??~c*3i=Z!@XN~aFF=371b(BI +z{fb4vzZKA50sRRR)W6>?>Ho+g;5YRn{|NLaOyJ*iUcUznG8>0Z0{e+Jg#nE(I) + +delta 57683 +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 +z#zyF6DHS+*nNf&7QC +z$6Pm7OOzAVerkG$2sLhsDmJn|rMw`vA?{tQ_O|rNtLUQA=1QSg$VIU!2rO#U=n5P- +z@!SnGZU{!WBxIqh8PgFVnf2}Jd5jJ^@>=dE4KyPOyfuocaOg&4nL^^CD;gXwn~a$+g*Raw+`{B5B^R@>ta~Y>O}Pc83y) +zYp}?qN%jSN7pPqD0Oy=8@D3~qS5uo4>EA_#%*pTG=w@$uk033ILr?qH)IE@7oScUB +z^{;sD_62f0*PSLHSjT69|Id;j(I; +zHLD9h{T$m<#^&1MFZq8xh*a=uE?{%`99}v4bXYWp`tbnE^k-Fa01+9ggp;k7Cz9bHH|k6zd-IBXys~meP~= +z?otmWLw`o6E#cAdQjU2o^;!miUZ>H!MCyle;y{5fIu}RgGYpG0G7;bNnG!R4)Pc~>)FVQd!2OI7<)C}GGxI1=gq?~!t2lj1Q(J!{2os7U9IlpMl)kX!-OjG +zDJ>q*Fk*lkN&bAVdAT+}Jf(ReOD7DU?`v!UDXOnzC6M!RlIT_hb6Cta?A0YEF?2-yEO2Gkun&0V2Oh4Sa3(*^#45 +z2b-nOTpN&5RQR!|Hk6J0lvMmCTpjxG$e +z+K(8d5>R+0^pg}FR3T9tTgD<0jh_)!@_bUp!K1K&GYldArbVyW6i(;)jdW62M{1hT +zjl#y@XJj3mQ~Xm)e;URK%Q>zMfJ(bcrnS5+&2`KZYSuGMWV1a*c+4UpIZarNH=PNw +z`q&a8`(cA)%K>)T<`a}J5z}4ap^poZP>}MfwXT%T5g(n$kM0?x^l*^!}yi?e5*}Kgy1aQ9ANEZY#W&A@h{!7xVyb&T}w*hY;@| +z4X(LSgTv?hMg!fiM>GQ(J;c$n@>cqrl-Uo;kr+i +z?-&<9upp#Sd8Tyaf7xlQ=h3>$;4c2@W~i9nBUZ)YRFX<*F)q2^#Q&n&>2NDNp%!Dv +zg@Y`UxOolb1T8Hlof}c0@e_33tr_~8|Mtz*2;F7ONW&hcJ|)2NAnBB4BU;fFk~;TF +z?Svz%_F_H6_1P~(X@uW#$X(mRD5Ccx>&>2?pvT;``$XWOLXu_zQFd0!~^ +zFWGMA`<=vi(Us%*)j`-jddH^+(>hEyb6BTHlXJ%*boMlGfJ=rcF$L*0KM^EGo`sw9~7Y +z->K1mEl%GzF{keLb0zfWrJ$V1F%69wQICVR*KWsvO2o1hlT$z=sI9iWMO=XI^9dc> +zQ0Ii-CWX1!*8ci(qdv9Sxr&=D)=f`1L)X`#EPk!+9zZSW)jZWgf%@}{(Re((@fKzQ +zMchm}1wy`%Xc05=tWNCG_+)=pJ_r1Xu`QRksFnS?Vx-YAcr#p5KV+Gn!bQ0-E5;n< +z+400saCf&#Kjs{LpKn$QNYIJv{aMlpOx3Nhb0v%D^LX}%4!}r~^LhtaJ;sa9XT7^> +z*&$V*x`7V%@MHa^s^45=jlVxaPZo@!ofQk@C5=ux^|5QZb(A1r3DQz8Rb!wTdh=GE-@W +zj7hvjun3r|#pkp>l|f3}F#88F)6i`{F4XLzo)b05Ne0EtJ$vQMjosv-Q0MXK&4exj +zs9E5j_L#EtigkY1=fK??5AFxt-%n&~D^ja8$ +zQ(T7fkQ*Gtam|n41~zQV1|3%HA7nn-3v_mvbonom!EEl${3+-cVU?v&vP}b(dTRUMJGm&mBA5It@YVLo`OqI2ok$t^r86 +zJc3?&JGLqBLab=F{|rm959blZ9?k5YE$h=h_I&SqRI>L +z)r7N*J01OiAdF0C&TwA;cGeQUS>>CajA7k%;0^%8CSda?z=rgl<@(E~#)h5pJ38dZ +zY~q$kX$WX5cqy5<^>)*nfsNE7KGf(;_Ja*658+@^c#N(gNMFAa^KE3moUA$75f@3e +zttdkL_`$iSvU=GTeo23)aAqUgc8$tzjfpRO#&jEO@?I`(j_&s@K7=o +zT9+k2!@fx!x**ZCo(7g*^i39_HUP+rs&a_u_jGow_ZPSJ%@_JXcdLG*0wpy`Z*5Q} +zJuKWa+ChgA1bpug`I_?9I(i>3l=2#Rh8!LduV7~bd1sHSV3_$k&}(-+%e7N@PnWT+ +zrr`QA1S$=?`X|L5k)&hs=XCHnUzR!5sNSBMp)yNjn7g2>3QS?SWo*rat3_wn4d6qi8K^)f5xr>3Y;fTAT_6!d52$x +zTS7W^j5GI+O8MKoi0ei(06Xs5yDx2|sF*Z4tT0ho4fPyo$AfV}#0Zm!$Ys)_7Sh%Y +zRB8-azQxQ1O~{ZB(~lFlleto>F*P9T2PzEk+s#~itbQuBXVDLYE^m{3Jj#}zyQSTC +z2$fgU^0*lHyPy!IWNDx2=Z*7e$R3`wAOdje?0#SXQHqR}>-R7H8tWSSpyy=c2$%4ZZ|!O1Ug*y_HW +zv{T!VUs#XPgn)MM;#y_{n}?fp=A)a1WNIx^I_YPD>V;KdTdcc_&eG+ix0Kx2zQv;9 +z$@qD3p?t7No*DG-`qDq>g~-3b7{E~bS!`<2>DT6NP}bxYZkmQjL!imC-U#9kh_X4#p>EWbVZo@ +zUlKM%BVR<%HxIQ9Y^MB#?1JM92)_+Od&QrUBF +ziuuzFmkWsAGqT1NgtlcGhScGt3Y@9zG8wMx|(fHHQ1J#OWg5?{7M0*fux~2 +zlut&8L@#M8v+2r>CPt>3MXuW+fj|E33e&Ca7KI&FZuEXvUL4*I-iyiv$xz(RPsye; +z`U9Xkjnh+>@Ffz^ldc5%CwdRjJD`#Xz7cy`5_j}^n$_pWi2HG$=;bPF>4q}aX4mbs +zOo2X?Jyz2W~s{5Q6{fJ23L+p5%Ga +ziR`?S7GEdK?ihTW?nYkA?3)+`+8L`hs;WburA?d0Tmk)T`&hZT2>}ZDaY&Lk`3)NtQLTCkl&4- +zp}_JVr0vKGs+@kb84cP6sEjPK*e_g+AbkU9i>N}zRFqLqza +z-G4Mi!M7~uknb0oFpK1thSN;dN77wn;q +znql9lVuE*^QzOECvA)h>6v&GB;jB4{zR|U^=P`%hue~5gc9C18cOz(e>sUEs%8_01 +z2W?8s-$sjb`1i>MOIDt#UwjKzUAb{F(Y@UGr9CgCTe|DNH3)z +z-$W&!3M&d{^@8g@Urv4&;FF7`^#g!Z*d#ekO}sZF5_y- +zZ+&ji3aOl27x%c^dA^6o79;WftJ(T{o2*rDDLcx1>bGBiXTSyhLUIC!1{Oy8rl?i! +zj`6Ol8%AjXBL{WPBZ#y(-&AQJHRsV{oz=Mjg%fG#kqhW#^gxjoTTUdoe(c(uy!)kp +zHj)KRkp&P~G&zwq=>hy95IwBhawzQ^zdf}qetemDAqjs0(+ixC83lXCHrA^U{SClp +zW+6AJ~vgP?NtuzP(>+IaA5uOe83tZOINk$puVAKq;8vLh|jb +z6Fh?_Y+OJbO;;n!Ci=DJu`=J0;AqXaF;90L@j(%dZCSqnwi9!9PKZ;rzkV+zH~!!i +z8o|(t_6~Ytzl{E^29>$jOhgdlsqT|Hq9x<$sOzvK#cFYcW>k9DU0pIp^Ox%F$r&Zg +z#V=ruhFo;C8bvYe{(IuWCGgXAD +zq%Yp{jMAdV2$UCyEk9m?8w5N2uVU8(Rv$p6`4#v!Y^9kq%TJ+)#z1IL0Ji17{9l6S +zZIWwVqCev;h~O`=?2eXp%^D3aiP2n+kB^_eTl!GNH127?jvrPwi$A`C`GTz6pqq18p +z`EHM*P_m}q=GD=}xla?UCM-B`@SapI$)aCL(z_qfu6=Bf6w_(y*v+$7wBoGizuG8~ +z9In8lH6w!D)IPv-c^$fBC6s`F1dIzn?*}sdv_dl4Dz4tFfme_Q@Y{sJ=7_qQNztCJ37_r6=LrhFcD7 +z4EC~gIW^v;Ph&r>j8E1*u&gNDwZ;O$V_33p*7-T*LQ|M$9AeW>3{J?~5<9%ECYM9b +zC_h%y&axrHC6US;Q4NJdcE!FXC*sEiOK%E&<#rdNrkovo^~z=-+?CS0uOu|JC#g$u +zk)+w5^Sb4wjW6T-qAIu9G#sr{WVIlYo#TF@h$&kd*MZGJus!H-uib_HXl@;FcIfR; +z@jc$^Y8gH8^={c=XV`0nL*DA2o|>$;w|+tA6fwsj%|nZ=RdkhXDy&#Cm8o9*;<>wC +z?%DUs^6q*oulB7VNAF&#MNZaa1Bb+{W#i^N;?tuV%p}zIg7_bN^o9y2>pD^o-X~u* +z+;6dGf9Ka*uY*uTb6I)cq$iWTCPC^;=6cPg5&-7)JMFVx9@!L{Jx#HH)4(^|tG;FR +z{^D89j%J5{|CcE*TEEf<@95coF}|Klf?OyZ&O@TI|76dq#5@a0Fivcq~U;ZyGubOlVL2r^aI8 +z?S4{KITE0`*qg7h^JHmwln^V$e +zBU28q>XeeVFK+GfWt;symH+iH8Fi|=)DOkbU0mC$23*Blbd<%Reejz*@tR +z``rwCQ~u6ut%+@Xz(vF&G(f!qV!NdecVgk(^*MIAo&LOt5Ph)~nqmtLyE-`Sp*Y2JA9;F?nLVt{f}YUaC`BXS8eII +zZ<0WEKQcS-8WH4oW>j@ygMd&*-8qz=YOb-Up-V>t7at|Bk%m@PgZS$=S{FarUe&Rg +zx*`qPP-3F%1-0*-e_P)Du%tHNBZn;;L+qV5>e7JIW(MIvqJ!w_MRFkTif`y2?jt@H%H#Fpmt$A~) +z>g;Ig`Emd-!@6io4u=zo+F%3*Y;}Onjm>Af-tC0DP)B|g3$z7<3em7R+lKPcSgJfy +zX#=Fyw7_4T*{|Qf>=Rt}!0yn08ou*tq87|lx@+8O?^>~6@3>ZLJ1hFRU==^}-hrvr +zMT~AgzFGYd`Kmeg44GlCgkIYC!epZJt|^oHDncF5(Jo6vSoz&w`Ben0+^me=cs>}O +z){5iaAqB|)(_WoT;^y5OPQK~9S>B?BO@KJUzU$80t$oJhn@oP2QLjfj9sN>npQF+v +zt3`KQWYE<$0i09+%Jxya8J1hj7%`Jp@iHP(ynrzK_9+~|1y^Rr7TwF +z1OSG(epsA=RqKc1(8g<_pLm +zczlEs&Y}Ml+jpoiB~X|ZYU5WNkPB@V)WBtw&`3LGUyQBwt8N}$3m6x)b%OkKL0gKE +zwbd4juUdBSSlkkD*<_4+`5&?e-Du2Cw?^NccFxv4-B{`0(A|^n8Y(3SlM>FoR_5j;%Q)!=*JMgbH{;1T6#QxGRI%rvi@(RER{)sv0ivz4_7pNqtB%kxMpd +z9nt2{p0KIT_GIoL^uq@ESlr%R7Sp0mL9_)ag|Y5lVYLA@zyU#gzLSNXGBtw#2` +zNy}xpGU@ip^&A*3QSo=#Y$+VP+{hVjH%xp(XZ(G}sAqLJ*t`xdklh%!;B0gHZbo~3ehb;*dIb7B +zbzHM5yH0l@eWeI~O0mzm(VpkxPBl4r55%7T1W>FOyOLJj9%S0a7b;m7(UZ`T&trx7 +z`KJ{!Um0W^>Q$2ec3;TuUkg(cAaOEHd|0+!Icj*vBE0e9P)f%0c8|S`1C($e(HJz; +zD6`n+j_@I9?`L&Xb&R2|zjxQ40wg2OHe|DFrWx$7S_THMR~0PKj0Yw%?r>m!W7M4@ +zc56FnYQ@GC>WQA?j}sGQ>xVk#g7w?}^zJo-LR5~4(93+__+;yL-JBH&`N((~9!!jfo! +z^myZZec%k}erS&f#Dsi%aLMAa6vrj}=XdC&e0~9o%Gy?iPk)h>`DE}eU=?BD +zliYZFm>sAr?I!k8VXAP{rkFq=HD*w+bjc4QJY7XADd^&hctWC1P>?%JXT1@B_1X1M +zamy{%^`U_WH>qJe!{OkgWmPg*xe}PF=qC|d6jwYwGzNq@)8@U_iGddf^_kjjE^w5) +z*-|{nFu3RG)m${*?}=@`Wn9;4n{vvKUCs +zI0`<`VNZ3UPpF>)nzH3l0~N+D!uX_OWh{`Dh~5*u^q4VXCJZsbiHyb5uXT*>r%wXs +zc23`h@`geM?z#`TV1GLVe=*_`&|b?b)`vICQd85HamYB4Yd0CL)hq@2UPeG4iP>D< +zC)s0%e}3ogM$Qy#zl8d?LuMAQULA~(_{{iw83@8Z_}GICP_Z{A2g(Yc3HsX*S`+i_ +zG)P=TD1;wzE6pT*ko+;`k;L(_WqEdVvt__x1J|YcPfIqj9wvx&yMifiFl{H`Hb@KY +z4mw`8Z10t8X-(C^i}s`#)PGmSt?xk-@!gqfD4VjMz^Ac}ru+iWY=~A#%tm=S+}qpx +zZgR2SFc85H`0edAHI|7P=$*CoyB*p49Fez;4aDb3vHh$4u4C;gTMc`Vr3L(=7C4K` +z0{`rrl2c7)R4X#CfReO5vob4F%8oOUEw*H7Dnv#l@o;GlRpejCohYISiDY{ +zCUsVT+}4ykzYWXCJhi}!o(_l7SQfXr#$eR;bI%Z?2hH*;1DT91#+FXSkF@7{qYVyU +zLemKdmsi`H=Oqx0mWV>}Jy=O~j=8{vmYybFRr|RcC1q0*H?c9KB6>MeI?)`SA>)94SJ`7Y# +zY1NConcjDXxEL8pn{>Z@<2Vd&r|mj${rqdLMm_tw;{yFD?#MdKS?u{X898n1I&e93 +zot~0nS4-@m{PJzTPg$12-U$Y0Q-Q*7gexnm?pJcz0$;hD${}kaSTX+%JZdw@1L2jtf9KM1%IvXm1C5*Wo +zu6T3UzC37`OIde=x^=8gD>-$PhZ~vpx7Y$blm&MHu)|O>Z+85&NI3*o28MBd#Ar~uex(a*%wnPg@;5LP +zpnM4pm)r#HNqcTxwlD~mcUunUK7Cg4*UKUS+VrDz_=2gyeX%)s#9?u1vyE54%4`*~ +z7MfCF<*dN*e36lh1TjnJ^6q{JyTyBs#UkG>ujp;VW(hee1rq?%PilT$@IEGu9&It9 +zSrR+HQRb#81hG%O +z!Wc=0z!70`KbUz1GIrs7_;kP~J<2y4BmDK2(P|FUaAwvRQVYLk2X@jIXIVEU{I2TY +zxGkvc{{b1=u8dWm2N`J&;;sHustWkBH^!k2&mtGF_P`~${m##_n9*p-^6gvxumK(N +zhYx1GSKdj{yPnHa)L}uf-}UsqaSgGBXTyE@%-ThpvY`%_m2lY?#&rdD?8$zKN?#MmefB2)c>{bG=B_@L(AwP^7hnJA=y7t*|a#&j+(T+6%E3}!beig?V%%D_T_lcPc%USE6w8r?;UE-cK9K- +z>n|XtlZA+%7}d31F;E~^TAVuOwX~}=5z}vJdi9{;eM2nU9Q&k%Wm^MQH=4PYlBM4T +z8q}MXDjHpkwRyo~njihdhmve +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 +z)s)p`1HYdxdFR0vV +zaB^@csAlQ8BEq72$Ujykd$q$S4om!sQ0C(MIp>_9Tbpn8>8=f3%7Yn!0f@&e<-k0& +z8l^LwtIY@~y|B!rmR{~3{1DH*xm6ijYw4ba<74h;s2`W@=H34jIxf!#8z|jV>Tl}P +z$z_?Isg@;$0Dbaqsm?29|a +z9Jo1SS>A6Kx<8CUae-_w0gx=ZW5e=o^VUr^c64i74ye76$D<+%%klrBz0x2dS5gsr +z)CX4}p=EGPX}(!2tT)SFiKw;rE6{x%@hzpEe+252Tw}esyPS(@JZVI&hr1_MZmAmE +z(6gYKs-coQ=Fgh%Z9g5ceQzkGQ>toM(&xQO%!n*!b5qVtv%v5ggJA(d$i +zeYq1uESSw?c8wQl6D*d~!)&x|U5e5yX*Z9MKI2RnRVVrEUW08yYnxnh<(r<67T`Z- +z16_WcSStb1;oXM;d{gxnZH%w5e2K^EPU^`y3xbwa!vN8AJTag^Cf_{p$%i023LF{K +zE-TpIJ5Ga~YDdzY(BE-^q}lSR}Ldfmg@$1ypu|0g!x+7B#41@kK)dd +zxfrx?ke|TM&nqe&yRrAFb<=5IKH9ZNJul^9s0jjr)ZBHmb-O>ge_WrT3XKkNRxYyd +zJ0NWVuU(O~*8#~{mjmb5e{^cOrfxU#R(Azsz(!Pmlko`)xHIl$W^o^dg|AHP=5OS= +z48nxDcB_Dc?B1e_7Os +zzac+$M3{by=;<=#$|6waexqS+g&286yhEw|AP#JCdB +zp0Mv7Ph0Xdd^SRzA&D+W;+ID!W89BKKpH}|JkyDyDPyN){Dsv-oLN`$DMEp4*u55kjwg}xt;p_f*jD~6mefH!l1*X*THWJT?u(=9D&^B{1ERn +z6ryw7w&SH{H4tRcNpX9**UQW4xmtS%q@mQLYuyVUHq*VpEU>jrT`xYc{14}Y=8+^j +zPqwKXoIGI@smtiG1hEn06=dn4K!LRMyRkWy2z~icRo6EY_~gi_J(0Z<*fy_mRJus> +zb@hAve{rI|2J+JiChcJSz2g8iiZveNpp)m?DWu4uC1W-X(7Srhq{P@p3&t)`;GJT!jl66)Q|-&neji50`i3p?6cqL2 +zqYJ?odU+Dt2`+KVZ<6l*OO(1w3&uawQT}*NIaQ;rALhF7s+F; +ztYrHja3v1tYir3!-%Li_@ggM0Q +zk2>iEP{hhr028CpG++G0e@MP<(#a&QfWi3ai!J3xDJPbVn2(5Ef%;KNG^Y`tOx+UU +zV{$!lVMFA70vUrgV=c>CubH>nP#ttaz`n467*w>=Xbkz(*roevE78! +zN-sx>!a@6BY*491;?jz@03F91(L?3i7dv8*7$uS13Q{JB1o4V9^0#&iqRDjA3PVLT +zkq4t6C>Ymuw`uFR(J?%%>*Gw5xhTFJ5&M9tVz_H>ipt2hEB+BSXl-P}a!qRv6Qz1fVkeNJ5hDMtfDl-DAm!Dh}(P|jE? +zV$4TQhyqr6L)_SS^N%@VGCh(Vr%42_m~&YIk~0 +z&lou&JNwMV?hhh6v!2R6izyR}O;GY^JF7B@-G*$Zi3Vhsy8I+E^C+(fueq|+t)m+k +zYh;#`qvPr{i&D?|OgQjIVuRtBZdf2er`hset9JH7U9p*6KxTW@tX0d38U@htft7zIra>Q%cJDp5VL$|nmtEliexv(q;0v89&}GbFCXfXSA}Ibcq3La+ +zVvs5K=8=Kl{B0-eWZerhX$TFblrPa)iENVJpad}dhIF=vu1XJ9CSfkJ3kzaJDND9WF7MkrWPn +zN3SK5*WW+GI(B6ojt!gm<6v58Y!k{j>PYAP<={-PzGJD9D3WM|Z8Mc6Z#jFc>N6oH!NsB(o_-@Ph^ +z{M-%-+f?_kg?wiGk47ZP(cz_XmWZS$N`+BLs6SE53W_azC9N8*V%oFVB3ntn8YD*M +zn2?dYCcZ_NjQRO%#ge-OaVC9(t-jitg6q1B%jrW=kId2jn>bY~zi+4*Wu?C)pDXFO +zKx2$5NhH$`FR5wUK>`r)(em43)-*3F3+yv7sda0{^l(inoD~X +z(H`&+v+2@c{UFI91Zq1T#Ax4hDYoz}hH3050?5^BKs4-~+!nw}s0;q`K(lzCPm*Vu +zWM`m3gYNp~Jg@D`1{f6USC_?i{Yx!DZn^4Mb0rn|sq|EQ#C8BujV1Pe#ED8m!xQ*K +zRXdG?VFqa@tu+m0`KzL{t6$`m^&KzahyiiGr5vwd#Y^kw +z5)j$`!93`r$2h?>$g?16kw?1&54`y#sLq-?sx8r`z1JW26}n@ZAazzLZ}^TgsXo;% +zDE^~m6v=PLLoT1!LhUK2ma%ABw2MNM_hnW->^+grB+HGpF`a=rb0=cq +zP`A$P`0Dj)O`KwH34O`du!$-g@beyrJrE%}wDMI@h-q|f7=6^S?_D8AS`TU5v7-=) +zw?N8LQO?Vk@rf1dI+c#)W$&1O5|nep^L*_4UeNl$zz#(zvRm%1)WNO*ZoY*Ax@c}^ +zrffn8`~h{zs-42^aEWVHe8Y=V%i)>*`6gfo79Ak|_zmb0NefYG_*O1b0##0~$Nr>XctUSg)?dlSZfLb^5U;BJ+_6EK^2sn4Q?i1#`N}hv)DSG) +zhaQUeAaOR+0tP0FWB` +z>Aoq->B)t80=&;DKzMFlAE>7Ti3ecv3{g83!dGG)JaQvGZ5JBKO7FLQI2Bzu6j|>V +zLnb+j4Ha69MOZVHG1d~k?N}|Zvl_y0KY0gviMYY}uY0a^8NMoI`+;r^I!9{(T34)w +z-H#@?9C3m*ALq?+p09i~0wX6gZslOho31bvwl%U41q)=AGA*C21QPhk=-e5x?4-%p +z9Jlwjj2+jnKQn7MVUwD)P`vRC$*B<_Dex8-ZZuNL-X*g96 +zHqiCKxL($~7RpHV3kDSW-Bd_~br>YJF~z`C(-a6!-n?&0Y?Lcj*E%=l478#7+{d7x +z>3|Vrr1)^@T>;+ZM4R$ipTSC#I*T#MGpQkZR3%g-#KPE$kny=LQyEpgH2>1AHyZ;( +zJ59ifxF;RGUi;LDJwBP2FfXF9&|O62^^R8u9_0837hv_`2P{EVcHK8Y;ppJiD$#bx +z?gS>`QP?kZEJLbseM-5;3C@haNXWU7Qx)D^WtSE%$R3sijiPNuQ2%`v&n*+Sy5YTm +zcX3Nw;isP;+P5D5hxXCfi1JMCzg9t1FmgttC;LW8(!aNa+%1RtEYlf|hf{j(n$Y^J +zHEdDprvVH02BgNHF~L?4(HN`>aQ@t>+O`cAs5gA^f`pBYPM?>X +z$65Ig0{YnTE0;tEvn;&UUYZsZm))#8g-amU>y>h8;(b6npngdDeK*76V?6NUV|GfB)dyzj}BQvrMbD8Y?lNS)IP +z^U-l;%~grw?H`(NT!+&tDYx*Uk$0u`k#RGVH_h!Jx0y(bZ4EsP?E;r8=Y9Bn0AauHvGd-ROY%<1XAA +zblwe;_8jS<>H2OJngD_`epXA7#Kup11SBikH*=1~c3Q1}gy>8WnDtv`cH5!Eyqe`| +zMs@Q_1A(^*WPVUGsiZmh`pGTPaLx|jC-K{J_$mFIRtFH@J!Q#a&C(y$aI9D8E +zJO*C;P#myyV(xnFLzqCua`L_q$HgSkHd^1xKG%tg(&ixPmsPO`C2$s|+-iy=b(~_6 +zvV0))IAQHsUW*#?!$vFH%p-fPHdojUuHTeeZVan~mYH6q)=T*OQ@M?-t-OlDpQnU3 +z^djRbJY7?&a*t>Cx!hBu0-Mg_U4kZu{BFm6afv^_FgHvyhV{th;4q;Zr7E$GG6rO( +z(j0ny@|?=P-5G3NIsiOlmKp;+le7M=iv3OaLlLRNYXgGVe1Kx);Z%9@J}Wa*BqyyirIQ}!*MtEPFivAwE?2+vBg8<91KLIHbK#ftCDUo7$Ph>n{Yylql +zXet@zvP6USB`6IX;6y!3A-MpGoi?FL%y9-AX4Nso|GLp#E{&~pDV4+jZp7iK`mh3@ +z(<4uTg|3_u@rCSfAsx(rZxf0zM~ON-g*?PUh{O6b#ZDH0%^Mg+&T9M`v#ytY7o4H2hjO9r8i%hB+6`&9~VE; +zj7<$6v)C3@qS)%qK{!l``@AUyl!Cj1gXmdQ9M1P0yH1k1-YAS}C +zl25=3=DO2@r|@`Nj8tGQ*olg6;x>PEw&tzM2zD;7QylgU%1mTamApH;zGJUyx9m)E +zb6>!uE=Ir0^9-X&{oWGT?hl8DV*GcTE*6FjJ%aE8v+}tG6Q;#!6ce+!lmT^p;_te+ +zB}hq<=50KzMyr=X2g|bW8FWTu=A6ebR#JiBJ5bOcy4Qp5ySl=hl%Jv90AUHJ(a`c( +zMnZ`&@?8o6v`L-4`&yE~$U}W58H4AjMqT?s+4<;L2Uq%kUud!Ia4XQl}-Ps-Cc+fY4YKqyXC>t<=mf0QK|5GVr$I +zCjVP|zBfSb9tLUnIDmiJSOt9;IQ?hD#5zAd&&Ry4$XQviwtf#;y{d3<+A&^3;*#g& +z>p7~FvGuj7oD-0t)%2(Ty0tMO1 +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 +zgKcIHvYH|Fqqvtlg4{dIl(#8DaE{Fn9A{%L5P +zkVLKOLBjExa~IxefoIEvg*Eje?Pb>Jbru6{%I@Dhh`fe7q)f@DBw`0~m9xtsNXtEy +znrA3ivE3Yny!{;f5sc{?pA>3+1Vj*nU(+7r`k!BNr|AhORCC{8yidVy5yJegI5ad} +z81G{JdewV158ZD3xqh?9!UEee{loYJYFaUTLYcUKfhgX{iCisNa%FPdoKHLmZ+DM7e&yJX`EC)+#1JzDftkmb8PMPSyZ6Yu@= +zmzFO&PYZvpH%b|f$?%Tj5ndep7O|Pm*nNi%XzYn|YOYuD|I^vH!CsV3db9Co^Se~; +zM8)eR`(RLdN-oH8i;c`1MnOYZCA+klX@A#IZvj?{OfEc0ESs9;A)n}8Q(4PYo`zFL +za_CM!+o%G3r_*#(@6~<+iW1+4l04<;zTXScrH?Nyl^2oT=QB@K6o#yo6B-z$*MPJG +z`(i)0$6R~ZvtZ~aC)%^sWTAgMo=*L-?ZN5;pHVCk=>=~Q?yK|?PtzX(2^BHyL4JKy +z%kfnn)=E`Ef@^c`xq_#PzgiT=CkIV&NlEES`ue;6dkOOB>*)XxA!QS#i$LlxtuZlq +z8R?R|9ctlCKbbqfzFDB1)wsk!W-M9o`zvd->85PrKLq_+#kfdF=Mbl%hPj}r8jGgB +zCsDM9fxyG82+9V8ihFYCeFOZ^xVD6Wd3SKcZ~B;L|L8`R$qN +zxy3vecw8s85CsfeE!l|e|LI)HZlEi-H(W)3(C;t3YV&~Ju_ye0uXwd;u(7AG>Nzx2 +zoqpB}=xra74!>Q2P&aVS_D*dG%MNRzNsLr=OF}CD2r-*8c$4V=Zh2P7)ZU?VeUeB^ +zU)BhxMB)5lUk6eGGM>Iyc+HhKV*XdgFn9I+K*#WjhnQ&FktOTjN_JV3WDTdqv8!)p +zVDM?f%Q$a?);!0eMJV!XH2R#kSgux!U?tbSc?TZlXEQvLn%GSk4?MI~Uuw%(tVXof +zl6Ul>+qbii2cFlx8rA84+G*vea$;rC1~D(I46Z89>oa>k#NVsW&8tj^5FGE>@@uIo +z0GwTDG)OVN3+gS2Z{VD&7Zh=&)Z4od7U9ANn^lh5NbY?mDF`3pRGorpHf{)%DT +z9H;-P2yOdS1&>|$d%A+xd&(Twm$$%6K!*q9dA}45-@viw83XgbG3Rl&p{|XmpP9?u +zWT2(NsyDnFh`UP~$tF#f!a)Bt^9VC{*U&M;UJ{?hCu`a`Vy#ESR>hlY?sJN(h5vSaU%9g+V!Z1iW*zJ +zUPZ%dZJXQsye)}$_3MQ{c^QHGre^#~xW~fcemPBX8!S|H$~Rkyr1ugJHHX*PQf1em +zoFCC>xLc^>XzrV#Q!##&k_#hkmC1MN=#X%=BDMO6Hsj&uiAnxm&O46213_|?&4`;} +z5se&`{jZq!R@{}#lb_S=a@yT>M9VM*3p;M|iI2Z5QwnV-eT&I41OEho=n7ze_3~e> +zpC5O(?QyN6e2Y>Q>-N<3MJjW)9%gjQ9!mLWo1Gl7Arn8=i|LC7Coh@wt@~(`aI8`g +zx6EZI2^?};YPjw_DY;M2FH%o4Dap`yc6}kqKY<@KLxo715AM+Dq5Bno? +zWSg~lAn0}FjsrtbE8wgmh|q}D4mmZo@>@z;H`o)$$O;YOWL%IBGjF4}o6{z=MW^hN +zUY|0??ngYSg4{(7K>VCC!=4N!J``HpE|5bo&XMG^Kp8c2K90FM@9wu)ahF05 +zEVN9vlNey)6ergExdtjtgD_x-nUNx>{-EX)sHgmZai6JL21;U86{g6dSaR1NzU2pO +zS^U}OmTan28Nh4-dws(64&bT9){{sp)x7neFBRyaxL_>I&a!6V5-mYCKj@7Vo~Cp& +zIaie4m*TzcYPi&B*qX?(0Rp`7bEptWm$o7^j+0rUj^}KWpSW5IZA3CIN{Q`2|9{lQ +zX{Vw603KcY^e+0uO|#|)oAZ%Oly(k*sK*}<{@JsA%-DFBD(OCHuv~oBg%l3EX1Iyo +zt6S394ox^QAoa<*PhPn6zv;iKexVxhjJsUk@uH~gTS-O+3c~S4EklWs4l(_?5ba +zIzE9%2@xRauEkXoS5;PC2L5FM&lMQU)t)jyao4# +zr=Fj%t0a9pvL+sXP1~+nHxho8CjpFeM@kvD-utO(Cgao}I;TC8@vf>#$m%h<8*Ah< +z)P{VRCZv3uCkXRox|eBr6u?BZfisKo3iC|-=MN1-pnXPRV`;ri@gB-Vog3y+|{C~qp5ehmXrg9pp9ai`d^e{;pP+5;X@P}gbH +za$IluNyN{GONgj^@`uTItCn>K@%rgP3D1SmA5TPz5 +zVBEJ8uk;Xc8L4e#dKx>S1wd?foOemFBUFS5@D{swraV;L42e}i4A@wESX7yWRN9dB +z>=AORB0hD^@j(Fp11yL1FObwzE5D8_pyvOFcdBVnW7CzpgjL5+j-iRX7p#e7eF#{e +z4q}E2BQ{&DY+h1vrS;u3|pQlM+Oh-y;dwV14I9aVAzc9% +zAeGGNTfso9LWHjbU$o-*Tcw|N25XZPq@VL$-kq~fVSnGmU61(9v7#B~Y}6}&0Fb+W +z8>zmE|1n>}HcYoxKc(Vx!^590o=nwQv2^=;EQRb(;*%?WVAB21TKmdEO<02c +zf?OuU_m&dbebj)uRhbz%t~9RH(P9KlJIBtbwj#WOnpuLx`{{Y;2f@?oE!wlvAW;!2 +zhOIMtHW+UCkX_qbIEU}-hH?BvVHRI%pikjf$3ad;KN1KC1yDhQ%k1W;47vh0m4DWp +zzc1d)`n4C4a`k=kU&@|KqR<^Fd8I?U-G7NVn>6A3(+?-U_z(h2r-5Cue!a@K*lIiR +zPD|Ymhj7}!UuXVXq?XVw8q8)K!M@6Mk@=LZ$~lyzoqQY?;7_e>)X+NxwQX{D(ev3Nb6QnqTxVm +zYb}e1<*1woQb4`Okd9EQ(r=}F%kp`dc#p%s@zwRIY1Me8osqGhl)}=4Gh=R3k0Pc| +zv;7QJzEc>(eSeoD#Y+i&L$<%N*toXmiK^Fw(l!mgFN*811DrLl~KJxyhW +zXF~Ht-kTbG;X{3k8T9T;!<;9d`%`B%`2QMV|6LwCAWWl|KO-o44 +zuwzmOESH*0iCD4*b=g-^xPg}|w5unYtF|Q)+~yFpH$zw{RU0N6L95}HJr4nxzQxeT +zoz?O<)pqSYBT~ZFzfwTW1pYFg*}-n=@<7Jee@Pwx%FJTkFX(*31)@ublOs1xr$uWS +zBPktW{#QDRfI`BtnTsal8IMP!dV$G}ofChmLxC-xV>(KA=XYb+N9J=RYgX&MtmqIq +zKN?A7J<3!i(TIv0ucgDE6*q(i3yISHrM#JM?@>~Mtw!M9L}rPM5G8!s&zw{lF7g9@cSmTBjQ!6If6dY=xzQ^~paa=cv +zZCJymLkY8g_6e~-RIVVl?nN{mleGP+jLq3E@h*7l@ktn`se%`~8;{SZ>)LA*9oZ?C +z)^!8mNGg()O=yv(kEi}IYvO-c0p7A!tbQAz{uuMBH{`ij&fn&QmnH;Tiv-Khyw12kCUT~Yh#0s;N}7qm*Y +zZlwDsSISJefWp_ggr?HOr|3K0USTFNH+s07%7fHeMAD&a{`dN>N*Hfc_LXaM-83F1 +zqAiXqCs-aKP~wS-2O)o~Mlgg2%sF_zE@oqIR9LCi8mOhbP4j)qYOc8uEP<*EoMe0_ +ztv4BKUYVcdI6x^yDAiG@g*ha^?)rdiOAvXWhi&s3F06lyve1Ag^sME5lPy5B$_X~3 +z2A5fmt*khJFjzCWLEPM;AIvwr)JSMuV8bh^mLEQn_P;LA0wNgYK7lGt7&RrfgV!!h +zUMeyicavx-cRCRD3HRvQ2at(@j<_>XV&8q{vhwEe-ZR4O>;qVh)$PI9h6w>K+mA9$ +zOV8OP34ug;7qHXk2_uK-LdUlO(1Z@8>kEsM`tb$DlRJ9hMXm);{{@==S0s(qr9T<# +z3P0wtRN%>4W?OCcbz@5Jr}J}A43*;)(n`PBJBtgYmr}u*?yvF#-&njVwiz`3;&HmM +z3lu^}_D(V#A`Ag{*;f);og$PuK}=a-aU$zlg-MmrCedczJC~q&l@%InBF!p<8vpUu +z$C<=;j6eZkwZyj$;ace;@^?Q5P^venYt$petpDf +zJ9Z4J?+Z{D=FTl_FINJK18evzSM9`(hJ)nu?9#iXtPbAw4duYQ^CxZnzjVZgmvlD| +zXNETA_P>WyKm}AT7e?f+0rXc)pRlo7)-7{+BT<=2?Gb=LOOVjZR}=?@)McKa-r^>^ +zeL0~sM;r71JYRfm^dZkf2#vLG%5_V3nRBeLEq9LG*n3D`nXE>9sr#Oc2m@e^t1^4!^_P(dTqIohYRiYS?G%&{&gKM?x;7ce~O%M-!`sQt{no%~jyZrPpGz0hzVM +zfX>xya_ORm?+$47uBNC*%SlFU +zVtd58o_6lmO86PFnaZ7M=&9(lJ+f3m7WwESwHFEjkF2NBQ(j~L)++?G7ui?|jNQ$Y#%Y5VE +zWG%LMA8@_>)6FN>)NpJ^f=jd>M|k@L!NMVsL0%98S=1QUih6onG|WtIxzqj$0ZR4z +zv}PWaRPYz3nrL7*agp4yG>>@~QYv5#eQXsFUE9Nue~H9Sk0Vr?eDl2~4C_q6G8gpx +zTbuh#Fr<(<=Fd+$-YFSuTqc)X5!pAmUq`v(B;=NTy_(oe9sK&8UY09#dqqE}AFuo^ +zNXcQ(c=l^9S)C8)_43}|g#Vo^Ip8#H(X0|c?g3DX;|14C$CJ@~#ZU$R9bpBKm50cc +z$;Xb*AdV|d=LG-Fh|t*3^8iytrlc~@n^_UZGQ0(e9#n}!bdFD=DVkAcF*JX9wE6CP +z(jtB?)yY26{S +zudJxby`rC$ryAK2sRhbHbd6A|>Q9rk(KwhvTBJW}xN@+OK_jTZ6$Mh>)<&ruMXiSO +z$CdCx&mnq7l{fCNtID!|oT_M)#q3s+Wh^E%Z+-?}rWt*B+^KxNi7B8;6u*2P8!AqFDD3# +z#fc@ZaD~yR7^n}68yAi>QV(;M%HaadVM*`4k#a+kgxm+V!0VTE6=fB;m9PtHf{8YM +zhVMtJu(k%a50^z0>6ZI3lp0Y^m-N}R|IiX!IOhfXxl5R*v|Q8fRW +zX?TrKG7!z|LrJ2z`obNnbQM80w;@FL*?RBUw`jG_6t1Wx%X5~}?|0kIqCM>R05kx3 +z&!8VaugXj-pdC%*9&vBt(EouZMHw*3T!dF|)oXPQlPi5VUF`ZK!)l3KDr*tw@)`2* +zBggc=ZtBGj;vElR!zA;4NtqX0bSg7>u>&&PhHWZ%b1B5TReW_V@hYa$TCeQwCyj2QZfGZCGk2v{fXvu(c1CSEsHxDtW%1 +z*~g!+v%~mQ68|4fU%^#Z(=;0_Xdt+2kl^lef&>X3g2N$jaM$4M;O@cQ-QC^YH9)X~ +zyX)nBzIA`WUNgI=r@E%Q3ZJ3JJ={{Z<--Tn}{*x`9b!1-p6u8gScS+|u;4?MTC8H*dxTPHCS6vOu0)!tuu_H#!KvkR)6??l4?hoJ5t*S; +z^rKF&!>y(65Y>+I3uUK!O?U1Rk8z;-hHs;0+M!;dntQy~dK$*!;&HtFT8O!r*0x7l +zD?;sxbWJ(NZrSTNJv?=$IoFkE^bxU5)%g~k(tox0iHkDQ^BSa~!GGqaTHWhkOZ{15 +zlNzyM^_!L>G)55fpd)VI257S=PzB~yCyW1jD}fIQ(-g1 +zT@0K${c-r1$osKS!Jpw!k2FGXa>)q)X1yB3*V{=4Lsr*EzRSZ|u{C?xE&=VG< +zgsDs;kn1h)Gl*q76STz4Jx~e$+{Ch&skhbo*GY?902VNWu5}_nfDNMC2yC@w3u6)U +z3PTG|9Y!w?0jasXdW-kXYI^1-7N!rg*K_3eW@F;JI;M;5YFqt=%SX+Kqas%Wx|VYk +zry)+|$b1gN1Q9N;)0Wg;G=|5X$n0r>utQ1X_VGLcP{`qPqi +zLyUGC#u4=Rw8^xy-)bFH_OAvcBF}b&cvh|IfktPsM3cbYTkGviMftxftt38}&HiI2 +zDvz>ywl-GGKMBZv)Ug=M+FvZD=Q^It(TtvCoiWNAnmmES+od)L4=N0aP6cveyJu>u +zj}97{sD;tm(dA`qS_nn>TcaapF3p$Yy6#n{Zj=?*Rg{(5oQ3>`>y<^1CG~#Ao9*M& +z^@#?V>o}l(Bp0`SCU=J?`gRj-bV61Onte#AjWry^<$G04eT`YNz7*Kt>3?#%a6hHt +zIKC9GcF7uZ{^y5(Cb^bYLylKn2J@lKr^HMgE+uDz^t~<)&r-iS*ad_xd=BLMMS6WE +z@u>TE@QjlKZ)^jKow)5z(paKa!wQR6UgY7j#VJtJ@p5kdcy=N?{r;0^Y`IlK!@L=G +zBWM|6UoySdD^>T?VCtpi@5S2KFB=4Zh3$Qk{h^@`sQ%Il6RsLh{ +zUbx@jve#DNO^s%O&d)=e#;m{B>7=DOy~W|0nBa5JBLmMu;dilwvN?U+Y&A`X91ki2 +zz!O2vN_E_9gmkkgVJ9p7%iMv!X#L=~H-`+vJPwC;#C_eWRX_CDX864k*CQ;!EGT6? +zXpDdK&8canOmKb2^HI9qm&K?;;pX!7TIO +z?yn2c8w0|tHe?4fQX!R?wF&1Vzq(F=_5f;Ecl^ptFE^!fCk5_q!-Q6R&Z| +zT!7ngI~~JKGAhPd2W#h`5W>%>KSjiW?R!cr$?Y%|DdmFudj!}o7Q}uTQ>PVztyLwH +zWN%xn`3`^UxngWdU8F@2VNK8Z_Kiz#>a0(WY7HUykQ}jMVKH*4V{omiGtmc_$`Fc8 +zq`U;QJjjUN{&+GBN*2n|WOURFxo!9m8k*F}oyA#}BN0JeU!O!$7X`7OZCJeS<11T}3e(^bv-cUV +z3%UzdA$Cpr!jI-x7G{>f4+vN`&>0@_h_%_YVb3!ygITY?55W1qfIVy;H6FY2Pb))t +zgk}e_7GS83-28pG7-GYey-3Bh#AUuZp_>K&|4`pBJI1;>&ETr)5sV{vApuTN4V +zw^d`+czfxJLY8>YW}kdT%Drvy+zehUaw}3o-|Q@y +zq(ss!afohiZgKC%yU|N&n>c|S>aN`Su^HOO+C|a;ii~tx+5ybJ#(M_B?)Wox&n#BE +zBy%xumFcuMcQB`Ct^LB@v`i7kGl+ekT@(vQ&$i$SrA1$~^looRfyBQDDR^o1mX9R&*(5y+B*G +zk6lL%nl^_7tQH=Q1C5l9W_YO&Tb$DhSUyi!FvoWPE2Si*W!~q~_;86`93>7<94!v} +zqlIulW;Gg1tVf_bz47Z4W5*Od*R|2%jW&LM%>|D%SNlXTZF#B3E^GQYW}n_L2wJ8L +zC64bO!_N<*`Ysox?zrFZ*$_m&2VsAXf<2g5D_qL%xH;$7_diu8B-W_xet=eVdI<>d +z99E5&IijHy?SZo_^6(fjYFmWS#)n6)n8%<>-?TQJ%A-$OaZhcoCr!GmeE}ygi4tW4$x4&BbXt$J1Q8= +z7}Q9!pS^Na6PBmr5VcC@WAhA{DUNP}rB=F51>*p*{}gw8oqQ&3+-^^JifX5W6&Yn; +zBh6rQKUJUGv{?KJl0>!o&VA%SP1KOmL~_ab%#VB+*j`<&PjPEUBIjW^yRX|g%mU6p +zGD^M-i3{UlC}hQ1KH{B;tRx>7SbkV(E-Xl92%oBZKX|8WNY&Gkpa4-jJNSmdNYr4U +z1HhXF|EBeqqsr_MI{P4)YtNnL_KkxgEvSEQD?Z*<{tC3k`Oy(!oJ1q0>LstW+ZaFl +zJNkEHXK?^q&#>Hrk7&-0Uwd`VDAMVdme;rW{<;J!JH-;+hYdq*6|gvmj^g_XC>h|TD+BcV){{*F+fy5t?a%8tbJt1nlUjb%2(8*Gl5){T{4tbC`i0IVPLw6v;P4^Fg7;z3T%GS^ +z_in)O?Hw~+y%gK;WnjWD#Q!7R=*&q)7FJ~ULcow2NCFFfHY{CHdZ+Vo$6&}uq;QGcQEkRPX7Q!f!yI5`R@O~R +z3m<;AT-xv=I%BSuw}h&&}TKF>a=~K}fN}2@D9jMnO-6v6cx8 +zx><-vP8C3q%LcwJ!3e1c)mpId!LPOFK*&^~`*=E;#p(DNwH$}F +zkOF2c@JOOq)8X`i5^h(uz6k98WSs{l&DpE +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 +zhGr`FfbsL0NVx%b@`OjaR4lF~oy +z4K*&g?7W}iAKrHyKXXk@JkVy{uLx?A+u-jx9|W%5MA%KGb+rlN@a?RllU0%L@_S0n>A0ICs=ay`hRV%q*0sdAF#(3{h_OG9w +zdle1)fot=Zc!XPmj-Jv2q(tZa1;>%6%{LUvarhT?3@P8Gtb85jx5jl0#z|RCQed4Z +zBbljyUc^%f+d2Io*SCRRW2<}o&BaJ={nfpHd5v3(KUxoR7L*Dbvz)1|QwZ;Di4LA0qyZ +zpXU5XeNo7kJ(i`4y5MrH@@a<8Eb_ogd7%%`NOMIjk4nW+>{;eduRM_2!zAQ5P)#?O +zNK7TIXqm!k-rUDeR_}Q|3rOhGuJFD(!2xN_#_Xm*)$8Ur{63kO&68^Hzo| +zTx~i87Hr*~wz7$M{B%f9=)idB;(eaN|79gV-z>f^Tto0^>{<(3@!AY`u4wJ?XYU6P +ze;>t@n7wm}xRE}I@vOK1?U*`}6foq95KgY>rms}{ioER#qV5&A-Cuaf_x7q9O}k$` +zkG`vqT>a?a@i(j5@9{PL&i?3NtnY47TIfC*EO$oa@L+H%tB22e-vORLdJ6;RR9+tu +zK|x|?U}DC=EP|2RJuKkO6MHALP!_0k;$rGO*z%G0_D}T98^5%9xR+v~_QA54m*BR~ +z>ZJPAMLAh2nW=?oiY$Pk}26w4YG_cV2E9>-$x7l0@=I&`Jw4;9~uXEP{~s{%^Ewr#o;TYkb7( +z484y%=`DaPl0M7_=eb;mW4vLbjOXo>yY>|Fkkht({a;bzJS6U3A4?vSRj%`!I6EAJ +zpQl}|9J$2$5O2eF+zmcoi{|DG(NUH5RKwPlR=||>4@P^no~Wqz7od+vsn(fLr(+S +z1Sv1RJhB@me4jw35W_{+mh&V+e6&lyZ9yu(KUZ%YnGk!lIYZWK5D)CQ%N){BQj|h) +zONgs;#02|;`hm+F|H#!G*+A6Qa{S$5o?;~Lx`*~^+s?31K*sZ;BG5ZScK;_}hrn1i +zL&>m+_%rxVt={m{nn3~UDNm#J(TqnGjFc9JNC+#r2V$^kK +z_ul1vCvdZ+LbzAg9Axdju6o`o8`*DR?G*+72`uk1{>i +zMc}*sc-bTS;V8@&2arubq469Tbw>icQ4&3!746L|3wLk8)-_>EXrVIRa9MVX%?W3c914~w{2O2A+U!Hb)RL;+( +zj5li#3$`v7ed+ETf8;cXzoL0jSpR|hE5wh-Yn2j8r^`pY?l?5%a`CGj!&?g9?OZma +zi7(xL%F<)ujru8iw_?)+MdAqyZq^$B)j;U;F&)#$mxa*Nwz_`$bMU!k;?tp5P4B;T +zgSbRxD(>Ysf%Q&N9bNfKKRJH85#hpnJ%TL6z%%mI>VHo`xNFyhyf-LsFrJY%+uz7L-we-Xw4Y4WCYm2D +zwwERJYg@aj>OAg|o>#v~9Se9m22Z62M49RyV8vjX2M;$Ed)MRB^w<1AgC!fA2p>p5 +zm+u4M1_owsG6+Hg@5Z$iD*#)7u)<~ZxxWe9#RjnvJyZv07%$}DE9J>^(pPt3yC +zbT;PGS=`_KG5Hnn$?(yGQM68YkSx8j-r8nCb>(bImD1%ewdW6q#u8!bvQ6ocK^g!z +zR;!=hp{Y9x=+_eG=0(qKFHH>wTRah?nYHA`+)vnwA7E2m&FP!$-o76Ctv(9cpZvj+ +zA{q^o<9(IMrnA?cb>o1TF6bv;nm;|U5AuQ1U +z$9L*W1BRJTTa*x0`DyXOMzUvhDGOvQ;l3Vw9}}ggbU50JcpQZY-u!cF1XvW +zyV*Sh_Zd}2hiZ~UkkKY+E6&xYK#T!(@OdeLrq-r)%HM^rICe@JEQQS$u`^Tj2@8C| +zLJ4~4B#PyXH4nMiOhKPNDATk`3?qM6q7>#vZm5p}Ly8JE*fKo^Sf-Rf#K5OAVJFu- +zcqYbdkuWFK$xs6?u1IC05SGQ$+%a +zonsn7ZMAn}oi%Maer0;O0XiL@&I-zNV~zMX>(oyoZPw2}L+Y%r!P6x7;GXr%Y=Dr} +z^Et9QpS@p5BsIZ@<>e0a($csK*MTp4>B*9<^SdbsZzPlYUpPg7^o7B5u6UOHz^qrb4a-V*PjwDsH28ZCVAN`k)^8y~S;5{) +zMG$ImOzdEy5h;_C&*Hj6lo7tGlKXy|)A+Z&a;s@Ce>)pjx$p7^)14&ane6gwC*IMQ +z(5j@J#MNnx=PnB6W4N@Y^;E`Xdj&%$|KCKTq-5dPi~EzeyO%Zm4(<5)je-+aShtv0 +zbs8EZ!3kbx4}9b50Mf+bFQ%7k)!V*!I}y>!@-F{!?rrLtci0!44yM>+_bn`9I&YPL$`^}B{iu&tbWY6GLiRr<%G#?l?Gv+Z($pA)D +zX+{~9^bsp^Jd*S?NUl-<>yp1WRn?_UGXvjKOR*dG+o9F-aPjP~r*qPW0D8S?Jn2s%FbZRjB!|{zf-cT_?4XPp*g2oCE~f2KxMjf_ +zPCtaACqgS`wE~Mm`7xZ%i&Z^|7!h(4YPx);s1h~1VvT+eEB}Fte>wBLK$RQ}sg}+;-ok3uP=}#CBho7a0&}J?P1m;>{`oGe7_>7e1jUMa +zSI$c0uq1l<8#5^kALjl-NV9qVmdL}-GZVRn^ElQt=*s^-+$2j`mOLT>zSyBJh^wsP +zJZzBtZP+vP8snHIyfPExql1(th|UlX@42sOpLw)Jy4$O)FKa939;kP}L6BOaUDH;+PJNHCJI1-RkDg{8-uZYKRbz{xLk^0o~!BS8B-=8^hX2@aj|CcE}|<6KlpOPUP-x9v=6`>7PSe@g1K*&94KDV3HQHwUMvt&IiR)UCR4|!+rH3PL%O3gBEC; +z3Z)9yb{s#3QC*0{XFp3SmWX`wZPY)AGLfnHvb9a4fmOMwNgVw{L0ZR8*Uak+MY%xH +zKQ6ULzK?1l$+Cueq!zGaar~k1Y0)kKO^9fCn52VgqnqOSlPrOXk{&$PFoO +z5>@$Wq5Bq&J&1ysB%L$O`&S0iB7O`*eyB851G9utrBKx%`m`3Rcqs^d09}lw{S;n% +z({J(*&|I1tTS2DTWr9mo-&P?NL1K&fjWF{lBjQie5mV6{Ierub+y#W`quhgkwJl2F@0 +z4<=hWk#dNT6Nynj)Gfq_4hrPT2#k$@#(I$Q7~aVzVK{n-Ddc;cb-@)l`EROZ_IGbD +z+uUGNo}Sm$?@BLzHu}k0C!by1H77z@eiOt0Z8Y1#2V$w_Qr0L4;z*91R@Ca~f_0w1 +z4C41Svu!PR8{_t{G^eJ$kBS!4%enQIv-!v~uoyp8aBMC&M3@Y|j|Qrk?GCj;d!W;| +zQRZTwh`ks`udEh({1y0OoIB^*ga&C+%5%!6L?8_EF!Q!so2fgyt?Yx +z?;AB`yyg74%~tFys_i~}m6wiZ(=Vxm|-!(VktX` +zfp2n6=6njCzdt7X_K?it6b}i!!s{b+5oE7IGad-RWwMzzft*cKa21V}N$0$%=qT(rbAC~n!P+T9Ap+OQ^2D)vr8~ec9zqqK8Ox{EcY1euy +z55Ruf?N)g&=NT^ILjO)!DSU#kUmuzi3v3+{b%JD6XyY%tBi~Vc+HrSsx^c=g5i)r{ +zgLYB3IQj8=ez_M#?huW?A~^iO>>)6UItgMz$@l6b> +z6a5laG9J2VnV5S}G&EQEk29u#m^lxH%PlP)-zCjuETC$T^nR78hC@L*=-REOL}{*W +zcURs8b~<-|#swHWT(p1PF`6v_h-f3nsCIKeHzRg43m_VQo%yYnfX(o?Y_{8q=@@*? +z#wfN{`v?ZKEt3U_wu0PH1f&WDZ?1HXob=;O_s*Mz5?oJ?khUF!)IFIs+)`t3XaCfy=MJZ=7wmA8mG2zmfx(`O>gN=UgW=Z@`ROD#+72;V|FB+>fA^IgatQ{?Q +z&bufRCCjUhNiP=fVEQzHb+krpe*wJ;+-zCwVqQ&-jh&mRGl&s(E8(^q&FcKmjHdt4 +zilc>#F!uFkVe_J-LgpNZ4{DHif`qvW@w39~QnisWZhBn}{?oWys0gll`5yKuac473 +z4f?f-a75OMnqe5Zaiq7Xh+8Z~zLxr@J(?x|a$v#}>`+Z?C+NCPXJy+^b1t +zT()V~OJIn$&mJGwSP!3}f?2GbM&pShScPR6Bmp*SlgF}32w)+a^ClDF`Zy^SCssdO +z?1f7x`c5k_iiWt%dxY;1o?TW;4e6?j3PK^2Bw4PKrvm}{`;*e1r(aCcwlMz?B%*qn +z<8bnLLI90m8n;vea6s$^r`B2=>p^7qcK5sY*!Bc-?d7d`NwJ=btx3vrmH+Z@{XAw| +z2&W+rUmAOzZ!seDKH|T&yt3w-7=N!^fc&>qgycNg`TpFX8cThB%4hR=678dxl@6SQ +zryJI7K=%R3xzqKZF*w+#wS1ob3=5WJ`9gL!3g&=_68C49%twvl06*?GrJ=l`ybxuU +zrZ8Meh7iqav;J?6bc{$deiHoR!C5!R1+*$JL$a3Kg-5bHT3qC^*M9G8H7&ZE|D4Yv +zYZPzwmZ+vWY-r*b3uO}GPnK6znd_M?NgWr>1BQHG(S5%Nqjg?brzQ*07ldCb6#m`C +zF)qtous*rth+K%PWO=PMRT7{z(HF6wpZVLgn<6^u-d0>;MfAC10lYHSNKkhbHdWDi +zyyPUW^Bj<|eaDa`f?ZQc3?akdDb2*d-WxD*2JYbOo?F(&Aqyh-YAi>xI=xMkvXxF% +zz&u8;%xv&S2bK+2N_91N1SG#NvDH~2bnp59hNCo_+7AWL5YN%DV6pTu?c?SmB9m1F +zd~@lCTrhFX?z2`!X)ux_aX&NID$2rcGvS6QmqG|tJE>|}Xkbgd>+=Mx{RPV1GRG0L +zQJ!Cyr1}#x!fls-2hi|_EY#m=^t~uk0c2DBCU9Z-*l-M4dciqzzTP4p`xw1UqeY#2 +zB*lrP-_BGOdb?Bkd=i&xM`vKEF7o5p&F9c(tG#?7LFmt0_{nTpAQl|Yuy9!1ahqu;T5A-UMQ0OHn&c7I*mN>2FL?!twg20Q8elcJ2cc +z8Jq-afAp^GzM9-AwotCOW{(+ter7P_`{$+l5W{U0v)ai7wg~HoM7WA;j&~9HAx7rK +z{`o|*thP+lOi7I6)SMLMIVkqwLt6tQMWEY2bbdVlDEa19K#Y4Sa7Z!ivMG2$bhEAT +z_dlBUdjYmY%HCS0;(aFrfYC*O!XyvWAj`?$mI|6Ge;GcgN_;v{3r`Uiz`)fY)C;~9@!3ulMMe4yKVV6?^B$gk^bGPAhGWrUEuOd@XXcHG;_DO@#m +z+TRZ75}^FnI24Ou+TLEJQOVtfpvQbr9{15D;4&tcx^$;ECj**m1H-Xn%_`~TvPFiB`WyW`#ECo$YNX7V0V5C6wK?z?~a-^F`l~-4w +zfk<2$|CM7$(BWhhxCZ%B7MXv5XgvJuk0lb3BN3hsod9|`AyAM9U(;gJ4fODT +z??%7h){M1Qk1J}uV(zqL?uq!iu-+T{^3b!|bq(<;$b9%$go8cq!*YOyA1ta0-5kmG +z`a49oP5&tfTipbOPws;(&)iq-XDlnC-Iy3m)sKa#=>U+-=wKl1)~|nMDr_K@tW%v8 +z7~mv5S2-DFK~YjjF%^&RDm8I4S0Ipt&Mu6Q?42X-YwIWW7<*Y=7*8%nWSU<5DG+TC +z3*lbE!4{onS0JN_v{35-i3=YU21-_dp(IwNHGH)oHU`RC2-7u$c#_pPyfh-Kbwsh| +z|GOw4ssOkjIo(~T$;ei45W(lgi?vqnAvP<^n>t5w2ko-F{i%$TXu9(}@0W3dPuSSW +z)MAC>AO20e=&oM8+@2RUOxzwUv-ICw!)XxC>`e*6!1f}5EYRNVNTZmPW2HE16jiYi +z^q4RuGJI7fwI`6xX6J|ozuz}4Cc3OHe?#4dqkyLS4wgB6_9jUVSu0A8AK?P`H>L2r +z?vyVdE~A-iDvC>_v`5*9m7!r-oCE)giIn?QW1zC^5f1bt!bG#hR@D(6Vy_hArQ@0~ +zSILHh>O)XpdsHh++5~@@MsUjgXDN;lb=}jPX=>mc@DH7a*NDDn6 +zh*A{<3?)eDMLHU)bPx*(NR<;1AvEvcx%Yd{-{bMi&+N>8-!*HkcV^a1_Lk`uI5>R} +zW82MfkQIx8x&<^$|^$v#Tg_}0)4pAnWa=3ik-QQ +zb^b?trMsTz;$q@DKQ_$$h%9uUG<0|(tsORM6AT|bd7CXXm +z3{#IOHPw +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 +zDR|n`m;DtTW)AS1T}JL} +zY7cV%cCDd`R2fM;Z^3x#^uF@l3)z{;5o~JpJQ}}d`_od|<@z(1FI3n+P5{Jgfh6zu +zKY#(j)v*fZ2vHG%K;(Q+({9Nnq%qryT942J-=NaweF-eU);(=)>?~&GGcV+Jn0D)Q +z?jT=IO0oLu2s58JFl_+AJ{eXIexWA872bx|2L`x+6bifE=#p3Y2=sP@nNfu!-^Gr! +zQe%-h=Ma3D)eBQ_&7@u%cMHGi`#*-UZ591-jw;stq*3uGEDD#j@3l=|U|@br=)IHu +zLMf~ei^YX~CxOm#RB3dvlY)bIxTa4FeDyd1^bl&s@vlfsa}@_A_EucBQXHc#kVd3+ +zrFs1$n!v}%9<)$A7h6H=op +zS$Z~oB@g2{3BbOt(#~iINnm6LbG{&Ce;>m)aVecH7KWFB12G_ys`P7O!!en%4jQ_#O*LcgpW{{r-Hn9y(3 +zvR`2Y{aXS370|z70{#2llKzhvLBDAn`A49C!vy;Gt4)7POZVS9oc^CJdl#h+Ai;ai +R@qg@pG}LreOTZgY{sV^9x)A^X + +diff --git a/wrench/reftests/filters/backdrop-filter-perspective.yaml b/wrench/reftests/filters/backdrop-filter-perspective.yaml +index 07d64ebb9fbc08e557e8c725290e16735d221071..43d29ddf1d6223d02232805bc586b76506f76b41 100644 +--- a/wrench/reftests/filters/backdrop-filter-perspective.yaml ++++ b/wrench/reftests/filters/backdrop-filter-perspective.yaml +@@ -13,7 +13,7 @@ root: + bounds: 0 0 256 256 + - type: stacking-context + bounds: 50 50 0 0 +- transform: ["rotate-y(50)", "rotate-z(45)"] ++ transform: ["rotate-y(-50)", "rotate-z(-45)"] + items: + - type: clip + id: 2 +diff --git a/wrench/reftests/filters/filter-drop-shadow-clip-2.yaml b/wrench/reftests/filters/filter-drop-shadow-clip-2.yaml +index 232700c555ba5a4814fbbbc8b025cd294ff5a92c..6aafa7313725f5a61385af269203251cb5f986ee 100644 +--- a/wrench/reftests/filters/filter-drop-shadow-clip-2.yaml ++++ b/wrench/reftests/filters/filter-drop-shadow-clip-2.yaml +@@ -8,7 +8,7 @@ root: + clip-rect: 10 0 300 300 + - type: stacking-context + bounds: 30 30 0 0 +- transform: rotate-z(45) ++ transform: rotate-z(-45) + filters: drop-shadow([15, 0], 0, red) + clip-node: 2 + items: +diff --git a/wrench/reftests/filters/filter-drop-shadow-clip-3.yaml b/wrench/reftests/filters/filter-drop-shadow-clip-3.yaml +index 8f6552977f29d3ce80ad3358c730dbb82e781bd6..4b9ad3d36ebd1bc84986af42770ef90b0f6f3eed 100644 +--- a/wrench/reftests/filters/filter-drop-shadow-clip-3.yaml ++++ b/wrench/reftests/filters/filter-drop-shadow-clip-3.yaml +@@ -13,7 +13,7 @@ root: + items: + - type: stacking-context + bounds: [50, -10, 200, 100] +- transform: rotate-z(90) ++ transform: rotate-z(-90) + items: + - + bounds: [0, 0, 500, 150] +@@ -26,7 +26,7 @@ root: + - type: stacking-context + bounds: [150, 35, 200, 100] + filters: drop-shadow([200, 10], 5, red) +- transform: rotate-z(90) ++ transform: rotate-z(-90) + items: + - + bounds: [0, 0, 500, 150] +diff --git a/wrench/reftests/filters/svg-filter-blur-transforms.yaml b/wrench/reftests/filters/svg-filter-blur-transforms.yaml +index 23dbbcb9d456f2c6c46dafbfefd24bf9730acedd..4962ecbe130b1c787b26a4b1a7dd6667f7105adf 100644 +--- a/wrench/reftests/filters/svg-filter-blur-transforms.yaml ++++ b/wrench/reftests/filters/svg-filter-blur-transforms.yaml +@@ -3,7 +3,7 @@ root: + items: + - type: stacking-context + bounds: [0, 100, 300, 300] +- transform: scale-x(0.1) rotate-z(45) ++ transform: scale-x(0.1) rotate-z(-45) + filter-primitives: + - type: blur + radius: 10 +diff --git a/wrench/reftests/filters/svg-filter-drop-shadow-perspective.png b/wrench/reftests/filters/svg-filter-drop-shadow-perspective.png +index 1566a152ead9be48f0312db7d0e2699985b77914..7ae7393f87a6f97635febedef322dbdfe7ee589f 100644 +GIT binary patch +literal 13036 +zcmb`u`9IX_|3CifP&j27jvV_igLA4wkr>M`8OubR_LRMeG-VlEW(=V+QW#q7&XJO` +zWZy}Yk!2ccWTz&BF{3OqF%6$<^m@H--#_5{L*=ID<9c3??S6ke9@q6u{M*@H`d5`- +z|MQ>!NMjt($HBkrqCb*5z~AJBbC&=4k6{%CjXHTPhchl!zlw7o+9amX5W9YniBUu! +z6yHa;!XERAbuIWaRZQ1e{L+-7>Gj*XPcjOByP-R)bIjUw`a0zg!;25P|9%j<{f4W| +z^?`pR#4V|zdC}#599m>6jl>}Es(AH04?b&&!xCh3iJY3q#TwcJc71U05mjYn#E^19 +z%AFF_tyvfhm#Vc+rK(`bNW;n<|G)owdG9(xSjjk~syeu-g1FRQG|T$xsFG=jwF>Ib +zpJgrdD9?eP|L$*{JwseO({uCH=omMUN__FPw(R6Eb-so@SQe^20|0dE|tos +zIw1t+aiDEcUeY3ge}8fz`qr!l{b_eFq0b1diCA^}5wBYI!#A6fQPF^4Po1s4fN`8z +zQSrNI8}nhWK`kw6>idpT8;>;wlj^qcP5#lr-;N&+CHL-slpnwHSC~gt$&=YZ$Msu* +zW0z>x-W*KH5m-z=pZKuxEj=J(W6(5FQmfqb5WBRrhvSxlvmD^7_+GT#EF=b0o3!|6 +z+4qw+dfg{SC%#v0hGacSg+NTy8;s|7bp-{QIZWan+*c%B2(fI_VrM0l3uFglREicO +zp}3p9-bBG$nN=ii#evXQNhnxs$??Hqp-Vc7MN$>%2cco;gKbXbz8g +zAye}Bb>9KTg`{`yUWu}hvy7um~4fA{BfX)Zf*ykn;8d=KxalsA>j{5bvUrfb63 +zBX8-m_Pz4$gSgcDzX>5JW0y8RZT~3}C|onCWmzqJF;Yf3%ROj@8c9Zvhn=Zi)}o#5 +zDycXi^xNY=kgilIVCgKZsxMX#Pi(Y3@y1?VqSr-62wJ+VIF+g5(uKB#jQi~Jwy3<> +zpqS{tFX7IqemXnAvUsky-uZ^Na#z{4$t`MZs244cLY@7jZK%-1I$f9L9+Eccm3$s9e&zzIZzx^$FSJAB9O#(O>hgbvbZ2EH_-^K +zb5_e=&S{+;_Hg;}CMJ->jqzQ70>m~1oAyAD%j9lvkH* +zj0#FsO+$7eEa~jp%p#X%KjDcw@9ii5L9 +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 +zrjBNS@o0VojiG7`dV#?=i#2ub +z^=v2WRD4958d_%`UCIet5I=U`EhOJ>3SgoFB%>4!1aF2 +zAN`-v!h#mE1WUW8Bj#YRav>5cOq0hW7@Tsz`-n85%{ki^Fik+(br@Ub*-t7 +zLG&G?U48CaEOp+UY^t(r>Q9J6g!@2q90>m+T+ZRms +zpyzmzNg$SaeQl<+sIG_`EpW1smzPKD?eRz90UuK=I2L2}c-pnRS%!_`IPfoiDAE@A +zR|(14Hp=<%*>lkXPaX?gHs+iOx4NLKAt@Cn4?+W=bbwrQI9H31w<^{_fHqiK2OSm_ +z10Hlh%7aDg-)+XI$C$e}Pn|tU4}&ub?ZjPJW)|swr3vJ+?kg_j&xQSav(h1?G&fng +zeX_O)1O80p4rY{m{qv}oAt)5=XWWLw`8lcU6@U*l0g~0OvwoG{5qOxw#Pn#gI!(RF}-_53AQi^TlB7%Ysz5Bv$}*9_?yv +zv!EE(6jt2jeM!lfsHRYCX6$PNt}F-Cqx#o$z>QBoS$r5 +z{QK1E_mQvhh6OHdts;(5yI2N{1J>^K2>nGtxf3vc1J{FgeU?#GW?Vz^a>1NwU^n|9 +z%;;>x%vE@UMnQ_&#ej_-^J%h!({cISr#EyAC@5ciJm@Xl6Sm-DC`*O+%fB@lU{hzE +z(JMo3tu@3+KSGC{UGg74>zq=PUaX#$7V28%j$h*6F=gOG{T-Ik`%}k4meF-enHZVi +z9MLA50c>RFuf}I{aIMjVbdfx5EINuf)?ekf6q^bq00m)&6*lMu95*;HPp5G;4g^Z7 +zO4UMC-bqPJ{fmd)>U%-ScGy*#m>j&?|PU!g^QJuSHTaIr-h2D8s-Yh7F7 +z4UE&Wr6lF$)9)eg!hRa<91OH9+iC4puCk0e+5d%p+Gj=5`l>Kovto2&wQTMO@E6Nw +zq-jLcgHJIUrrLLsF`)(WyT$F`Llm`S_XNQ-;3d%l3sT2z29C27snfRQDSp6$gdeX- +zOw$lRG!VM6DBmP}_~=kh^fe6ztf4j`lbISSLiyASio%%9MFw)de=ys~5bw0+*`zs& +z#IyMZJ00xMp{b1WMYwKACqr?I$MqU(D#LkNCX%I~osZlO%mqSo^GlM+h|-U~xuakT +z_~b`#zfv7-GP)ZODTE3C)3w`KWU%-#MV4DcIcv%lM;UD#K|)P|#c=DNPxR80Jib2( +zoVen$IWZ?KS|RE)gI`<3LHg^eu2Fw?P@y{d$YlW@-=5Y{g|5>e&{3BF`bND)oTgiG +zHUNH>yXP8&Gk<)E&Mv>&Rw+26pJe~$Z!5ZSEGjg$=~E6nC?>Bec~y>HBZSTY)OSH7 +zD73`956*}tiE1N6B0!46ClOp&Y# +z`C6@X_9n|R=8@$*eNffV5Z@cg=iR +zoCJ}gww1S0w-NQT!VAeAD{O(~VvEtnYClX5xOtU^hG|N{%&5PUP64HBr#ViWQ@Ru;J2ZTF8qBVcsd$L2@F1 +z%0Hg$0a`LkFfi>N+h;btl&InG>pEIINz)$~+Rlo>W^JhQ+^qWPK7Htzq|8=HTm-f0+ +z%-m@~cfP*oS1R=YD=?t-sj!p#(DOkuIW`9rjFKAEU2!lYy8FBEb`<{ +zHK&zz2j9DML;Musja(k1E|05psM4ch`OW#!^ZH{X-SbQsEIh(5Ev}lWbAVjht&W%B +zuHNzU{tNd?gBM5o8SuX%&WWZeBGUKAPHQr$b3l)%_V6lBc`Le7$-*3Bie|%s^4n$q`D>J +z(iCs#9Oy>P$aRNt>3QGw?M@8v{23BK50qNk#)GG;G^;;&Y}}qpQWq`2nAtnp6RwGO +zHIq*h;flkS=2gke5k!5LQQl2W6-G63L1HUmjfLN&z62HcF+hs9b*#`>i$6N%#zb21 +zII^SWWrC-f*iJF6W402Jumi<5@S%n>R$?-_jI+~_-5CrN=%s`1yR6G|%--2ZR5eLr +zLgVC5+fN-7O(`3=h%_ASyaFS^5``t=L|7DuP@(hTo1Do2&gVrW=^G4IbZ>H`#a>Wf +zTGwAFCMBgz@Ba0bolmM68eczyWFkZp7DhsgHU6i{UNQ!RX}xItBg=)JA_D@Jd2i`e +zZuuq8W?@0*l}^|NsGck8?2;W8pKaVeScWLK{Oo$RCkvj?;^irKE~(*(6*6|89wFxW +z=U1QGf~!cAd>79JD)rAV?7F<+)(BSO!^fgEFk&Kg_d>?Q>Wgl3PmF^Fgw>WP-&|AY +z`KZGn=`)R=cup?zujlfzwrZ%?V-d?WoDUnTGLn6evfj=nfZc5n^26ut>_xr+62&QggoL*7xUKf`YzxyKVB}BygDh=Fd+D+Sn +zQKcKV3Lp3D-Rnr|zp~ufNdK+#ZYQ*+EUg2&k2#LM +z8L!<2H8n$$o&Hx$trytaIV1kBnCcW;lT0L+J)g~be_wL(9QT`3UTFdDc(yUMWDAd> +zL;8f6?9Z>N@~h~r>lK4a2t)kawSPHXHC+o95xTAhW{5-$s%j$UDubfd-)=T|@_)~l+g4PFEEcXS>3B^&(YTmJVMDLI +zxK-m#BED*cRynJIiQ$Zz^NMBnWXR{&;m=21!0?57oB3vE#HFFY)(Bm7ZEY%Mz$C4ugd@?)UiuU6v9 +z=3A$O1GeZ?1vIEV@r4XP?n4mEt0H501#amJ{DfX9vLsbmemh@ea0QFCT}r8Kt04{c +z^ZU>+bqLC?tx>@Oun+{{LldBfxO_D-uvaN3^Hw3TLGby)+a-{|#3*`4?Fe}Lgp#3Q +zt;4fx>xBWUitZ=eC~l +z@R9QqcteA0SmwE<3e~`?Z6VWKqju|;xt7Wi5e7+RiK@VYeP;Rd$_y2Xr%{Y6`0;s-}#U#fC8|R8IA`@M|B%CCsVmpCe#=Hfq9pz!sKez<3>VW+%oo-{U{4h_^B~j%zpfs!71NzIDkyh4#hCyYe)BbAV?4xZ0`e!fYxU%QT%qJZb=El9dYRg<%r7k +z@oW$(7>xzLMe9orW^*Yira~tiK(d4jHQcMJ)#@gCe9R +zSDjti2u%ibH6@{6%Q=74^F4?WmY7n&cH~bk%o)lPHw#7mG{G8Ztx+rp58GVaRzxz +z#kW9$?<>u|c=c+VSIkcf6u4#2X0B>jeFY6|u%}hacaeNmZCk5lxM29<#iScq8I+Uh +zhnD2c&_U9CaX#S??yM@{+A5QuFrKnsVz-du+3+E6v*XL_aco(!c^uhl4Vvk@Z7Wt{nu14q`z<5Z{Xe^$DyO7;pnY`oYe%??1wA`1Xe9 +zY4D>V6ARU(O8VFxKkBB9^GSE@SgT$R%4aP#ua@9sQ!V6%aqm(?2Q)QNGf5NqZ@gl6<=lrCVNhio_=nEBqJ!5mPTtgn#iE470%Rex<-Zh-Mx@S` +zU;APwD52$gfZkBKyC*{9y+*w-$*4h6A}+z<8Adfy$~}=eVhK9iK9!bPUc3-B0|j6X +zxfnw?Vq3PdinBTA+eidD4CV))Y!Pxeivi#a{0dX78`bad3|6jd+z@}EemCfCX$d8P +zE>)yH^dX!m$7M6W*aD*oceY(?680t)DVZ7K?UuHmHwZ8o5e{T}Va3+SlHfd1+`D_{ +zFZSH(<&g?y?y_56U0#^Crl5TT7#uf4^p2?GE-xDgBdj){pPgV4J_#-RUch(Gz~t!iR|r +z8NF_+%1LU)ZW(glhUY=cP{C`XcB&mR6+$_Ye&`q6;q>tm^^S^gO>%Nc`l|7QJEdB5 +z3`gDf*ZL%Bz?V2b_{78?%e?dDd~NbwiEt`q@!E@fJ$Lr +z0s(lSaaVO@(KCjX +z=>=6^)AnJCyTT%~-Y*CJRX?=cKJo&&ASFmQ-Lk^As*a +z;O384lm00qLQ3W(=79Jea;NyTl%t#c8S;2cW%l?^nybxu2}Xj@YtecJI#+0gw0cM<7vO4wCYU>F5h>a-vuDk6fl?ON{8#f&6olYYY<*pa +z0S@gQ4Xfg*4VP|XpVP!9wAgr@_*DPIN&_)je0(K@jNP9uiSYk&vOj{Z +zv%HUf6ysG#*baOO(+xv<@sx7WGgH6c5Zl%*>9gaNyop>^ubJsBc6&&k#B?c6_M-t2 +zckQUd?mifs;&EGzZT$z@bZPtHG$XmX!?WbvTf?i?q4`Y4I}kJREz`#BIilX+CE(mh +zw4wAbaj9^fq8-6Dom|y237Reb?9lpf&xB(6pCW{??~i!)n@~;ilr8KU6m~!gj0wQo +zKT=Dk314$q@ilvSrPtg=dDx7dNy>9QLsrz*=q-d_g6<1O&5(2LW(ehe-j-+c7kJh@ +zM&wbm;;NM{7|Xmx4#9{$8~YXNnW0C>dUq +zdTL#5jZRJbXMg&kR0NuPL~*=|aeY{$Wp+Pt>&+IG8o-i94vN&>RENU_@&7>esgAR9 +z?R&ZLM9b{3P~N*2R=pl)fO#aSPAhXUWe%2y6!so{aj@MOf{}_Vcz +zKo(RN*v2=pbc;%)Xi@4ZEfw$>1YztCGsxU~DqO@}yBV>Thj|~`aW<8gkk-ICCzHK( +z&t^qw;h!Y#TuO_CG}semTss=?FFc?A|E#5bO7U(~>}=|YzK4}EH1NPn0UTMrF{if(vur9s&BY4{5|*s+LU&@iQx*b}to +z4^Y%PcD=Gis?){VQ65L{vdEBF)u$nGm{&D}Bi)rGgcWthKW +z#kN5Xh1LQ0$7}NT&@Xg{ +zd5g-{xyM0cHv3*Zmik@0NY^x2timR_T(>doFptiY_HeNk<@jCKeT`hL-?i&?u{WOr +z-$BEkcCBhu`ISNzwh8`e-4P2%?TA`o$2?+>ls>-W +z)eiYH+Vyr^`q!w}Bg0)2FJizIoCh1h7VcI=H>&2FqGV +z;2xLe*BUh6N(Hk!(Z1{nkok&+1h3dAO6tA#q67Y76rw}fv<5B1(zAR0g&#?JCiT7R +z`ez*Vet{$No!sH}3SAHJ_i$Ob&I{ca$}^II&K)i71b;9X@QrcDh-^zGVDltba}cRJ +zGhzSgUesB-oLaBPsv-8}m7X;xzHA-#Op+nZ_or=G|nw +z<1C%3oY|~U#lv<^N5Roy>N!3W$#KQ1<#PlwcL*WPGbApmz#J-IE +z*zez^$eLa&dBnLu%9u7Tw6z(K=)FkSdBoMauj_s@L+jPNWQh`m=v`~4!l)~+t#dsC +zw=}Hu)V?D_ag2|Oy+{71P#WU;=r%X%brnMrMAwZWIXq}X|I@H<-NXHMLjqP&bXao< +z8UPKY1~*RNq)q+CZwr@HLTv}YJ%F40QK5y0I|rdU+I#$`O%LN%LV^Nc?3mZ^{TQQ> +zhV%mPcCw)t>Ip5RO*KT$Kxq| +zmXN>g-?d_PbuEurhH<;sL3(Ya_FuLS)rNL_JJ@^Q;n`k*mu37+VrA>COCmG@H>PM7 +zsCk4D+P?LmYoAt^_@RUi(|Qy4itrTCtz~T^ditH^#su=ezh=7{Xb%mBwe&V>g9e03 +zMV)wkz~-Q~56Al4`$&z3vSWTY{llOe(=qZ4?Jm($fgFo3Y5b&S$d1Z_u~KU;^BpE( +zu!N)c^{Tju_Zsy+%>#xw)6u9ncOJxY*>CRnv#JF)H{LHl)%IiTAM!I|+y0+`2f7pf +zB3vyeUO6)?#sKdu5$ufL7lyPh`>o}U39UveD?_Vmj_Ov-!fUQKXpvO8T}jfIIoAK)phP5_L$~=L6ZIqwDGpTU2eHu3$0lbkPw#nPw_7o%Sgr!x6Kyq +zxgV(e%tXNfK&ODHeTDN*6UJKkkXpSj3oM(pht}-sd6)cbudqRFhzq%krGwqOhtki$ +zmipOKrYl`?TNO2xdaUr_A6+U1ds|*0cwJLVf+ha5>;}Ec1`Sq?0LqsU!FI;B)Dk +z60Yx$`^}}4A4pJeU(qHMv$T@Vfl3f&pX*UT$Y}_-UD5uRmHV(7>+G2O*sApn$dmbR +z%?-R72WdKLAPpgkJOp>e@QdkRGq-0irnc&YfV%G?AOpSClQY#TA#yF61(e?GLDKGA +zaB}OgTk0f@mTipYlReOt3!ngQn#I>B7sQM1$rpElI)d`q+2E9Ao(V9FIvO?{=&Y%| +zZ2K_BS#Q-e_gE>%@0xjUJ#~MRx*E0UhdfgJ})>^HMvTmq`!@R)z{=fbMLV*5V(74+wUa|fg_>^RdPr8J=CV&mINxe*2(;%s-(VWBIc!rTV@KE1V;2{#zyZd3f!bgt +z5HQq(uN3T}gUP%$xRXZ-$vg4DP4rPnSFY%zkSylCi<;m=ksP$M>&m7?K~L4w!06<+ +R;47B@U~HYyWrw}u{vYF~K@I=_ + +literal 13041 +zcmb`u`CpUA_deVT3aCJUDrFCZrLG8w81^MVkge6KpzKCaBa2}t1O!w-)`Y68p-oXr +zML_l~Ac7?*ghn(#*i<3`LO@JNgc$fvudVV?D6ekYr_Q|@L=HPof+O$ +zH&0h7OOh#3o?8NMA$EAhGWJQsFk_a1wVHXL)!n_tvqaYph1B9&q9$5TKFrC^E@khf +zIXlsz4m>>Glmfw_P_o7dEfw#y|KESTzE4z|3 +zUSvW}Lp`bc^FHN?N}GtpG`e=}msK^!OzRf5^6Ksb+UAJhLfWw1#(ncz?{jB(3wk(_ +z+4aUs8(gt76{op%Z{_+HFVoCz>uM?DR{nYQk~KR0V@yqdljZ_e7EGM{!OD!GHgj?a +z#{eI48?eslnZ=yP`t%Q5dYnkPsfXE>bFKKB8=HNq?ZX9258 +zxu6|6Hr-Y4Kps}LBKS2_6X&spAsJnMZC~!5_n6@=VCA0gdVTG5dRduw?~-PN95H3> +zAi?j?>fHH{mpeswvN&lrQ5*U>)$U<&n^_YYVZxLJ8+^enh2QF+H!jD<>Yp^WsGZWP +zmOx)MGKjWjd0WvclX~`?U??XqY$Pf>t<)>6j$|3Y)S|bAK+JY-y +z5_E+I#3!CAO3yL*3+x1=J7M$N5R2qhSx&q>Zrsf&U+I>fA!To>f9%vfj^(6s^=v#q +zh|L+h*XaDW5G%vL+LgGiX!C0|gu6l4=_X_lgPE(Cfvgjihl`iC_ +z*7*WOEfsqTp2z{VSM~>sXIYmM|$^jMP+=^m{m*r+ofXc)}heg!f@=w +zxM{25@H5rAFsNC@N@U?%1zes~@^mnEb@pQkL~Yw;^TRuD_Ooyq$(9pULqQ&kiMjO`NS@v8;Y%Jf`S_-x7ad8HZiv&QyBNxqp>>G2EX^o$Q)8xWoX9I1F$+M2ax2(dH-!9) +zHlh3~ISk=V?Yj!SV|csk`Id+%h{o9{bSl&C?|0GG25?{6NC9)Md!uTCW7Dfh#P%ZHtr +zA3_bhU4EqDbkdj=dPYdu)bl0 +zZPYrc0XJh(X@&#iO)t*OY>wYH7}V9(v$OO1@|(U<5iExC6uqq7F3j}6u)iGXVL&aa +znhN!B9+(7Dr2w(jXVWq@{RPcp4{5-?tOI998EQ2`W>Lo3sCq>Wg`zQNtOyl5WTl|N +zMMza)X%DF(mX(JR3Q=0g8ERE+aZ|b`GMOH3spn|0di|I@w3n`VwdW`3*=6(c)za$u +z=tGe|lJcV3;sOc9U`i4}T~gmT@ow6?PXo)AU!fFH($Ay!6}iX}r21>x`pKWFO{>%WqK*W+l&4kS +znjgk)I$`_HdxURLD1O8aQS3x-Y!XUe=YY)a@|P7mGwrTHEKlm2PwpWJpFB$#BW{{V +zr50Ssf>&`>s0$fHqOO_E()){?^rWa2cTJU|&mS+^Fo@m4dnU92h8;}tId21kM +zean}JO_^e5%5sT{4p^)NOx$Xgd1e$MbH!CAdgRRXD5A!(ZW{(zU_)G948vAn*2;W2UV`1H27aN3gIqJ#Q +zm$`2_Hp@wj%V7UMlM41D42&`~bj*Du2JE8HUh>}M@9h4b6c3N5{Rg@x=4&lxhV*Gg +z4PApLRD)*y6X2DvM8)&W=KD=U#mtI3Jlrl5gqHXEps4ow^Gf5uOjlHX(O98DyJxZh +zn>+8F$ArR>-`oI_O1bWqXr^P#ddXu%&CcSIs3igxd +zgEhuAdU3i^v?mT26z*F#+xs!jvDP(29Kn3_i3!2hJKjIJ*-Hie*F^!?x#igq9UY8Q +z)az7c#+W&&*R*vsvhGfkErtye0odL@n0k`(a(pd^f5(hCWERRqaWmDzuh({e8Zi0( +z7e_&rn@|3ax>9Yd%;hs>%uk(7wo&<$)0w9yc3{6S-(;sWNDgy&qAcEQ5(~SPD@dbAcm9~F6uL=qnR?S={DeoZ>rRDu~}jY{@kZ#6c{P_d2K{Py@pa +zUX}GG#0-<~v+>Ge0f|Inp4U$68XQR(*c()EL7V~%happ{)&oyMa1CVJYsi@pU4ug# +zx^0@IJaygkUq6;*ROnj%BD3c+p@^bzQK}!2jwmOD$l_)vycQ>?Eb5b!Mm^G%oSC8WLU0Se$T +zNjP+qN!idf%kl=o%I|$?>-?1gx}F| +z7dh-spftg79%=NWF(5tv&s;T5!xo*VpPQ|I%%+9g#%XBww^ll4pIU*C#}~eX#;Vq& +zy;~lo+tPKJ|Bm +z-eI74^VKA4%di>55Ye+bO=Orn~KMelR_9A)T{e<+;rCRkn@7GWA}B~IwK7s; +zS&AllMrmhQ>y85G#+IEw)^7-qzV4h9^(St;x6&*W#mrxS&v9-voLXNkii7Hk!z=X} +zYFC@xr$%l>`OJ5ptD~zkGvrd0wk6N*xj09|#J88484X0TU{_AIx=!A7LX5CysivQpfqaY<;gO>+!rb?MY@v&A@ffjM)n6p+Y4k+*fv)bMC>GFQYaE`Ap~m +zZ#zG+7zrfKo0HH0KjUfdpr*PHJydJ#%xj<6bpVs7#s{75GNl{&oJUg->bw; +zo70;f-67jBlZq1kID<1PRx#Ojo1(Bo|q15MnB +zJgvRDf=Npn$1#}Tpl$#bsa^{t!Cc7ZvtZ#O5Cs+D@g~h$qnaJu>Y_xbZic3}-E@ZA +z5`^|1)TSjNnPKDrd5C(∨IlL%#vG{Pe=V`K{k4rI8X7V+N3)!r +zex(hZ^3zFsbE}3>;u|?lCZ>fvx$HjHU%SHJ@kdi(fqCuE9thrQdMkdGGL)PzAtU4B +zfjIYBQ3a(R3vG*;j*gQfu1WM3Q#6YQb<bRRE}N9n?kx)BKbtlzr0&1Gl|q@g2fLjq*cXK6#a=@pMbOyU7) +z-Zn@c4Himy7Ek{!9v>TKQ-xzwZHfk*?2Q%Au#|iY9!w(Weaj#*B*+m#gXqas`3BK{ +zjwW1E7By%#zD?S^j1B*XLQ$9t59_KXqXdy5J5q1;=|1$FrF=6Qonp{|?uq<#ou0PF +zk@ye#cbRdH~isdP?rY*(bYjWMQg{dYcDrSnYgA^7>L$Qzcl(j +zNendc1lZI!`HgSlcVgmb>{(#JHqGQ4T5zh*>VsHBl)E_IQpT(ld^@y=>1)UuPM|(q +z*B7Mn-ApgXt?sRsro2`zZ!SAq{mka@vU7|4%!cUI4uXP$M#UQ2B6C3fY4i)AO-w#z +zhCQh+!%<|v<(L#;E^&epk#hL!$=HN5PENT8Y{*)DD6%sbJqN +zy<I5))MAZ+|M|2tM*OXAfhuERWVik`y7e5qdn+l%k=2i8!AW;AO}T +zF_g7;cnu8tl6Dsu6zw;{z^&Mj6<{!f-_Ea0c8fl8{EG(UbatgWc>$A{=Ue_~5-SW^ +zc$4{`3t!SQ{k{;Bo5wY`az@mS8Ds3@H~317*%%^RDJK!J_nZH1mooe&D%$`68gl(- +zu(Uw`|C~vAnz8BVU;j4gL*+|lOJmI7`b3H0b(=2Xj$BY-Rr+E?9ozzzyJD`nl=#<){W6uUAJ|D +z949HN>xwftW0^(-0e2?cwdI`sZXo;f%H2F>=eR*&*-K@0dQ37xruitb1~zJ-l?j$P|S5$}tgl +z-C{Gs+{_sBybUMp<@4FrLHa7Iw#Z*;cGF0=MaV#O4LhC$BcX+HwWDuw(IeW#j-*3HDIO!e+>5Qqt<}w*v`mMD?0-%w)XZ;QDDFLA1mbW>NRch@l2s9j1 +zzC{nJS;+H*zRH}QS#15(8a{S?Mu;0xwlKz=T5o?6(zr4n|NQcTc#hY$;_aS7#A+;8 +zk%YgtfC+3uJ@dT{atqAMe+EGxoU<@05LyLPk`u%l%EL%7mFSMW2O@t!M@w76MIs({ +zqXM-2ELiQ!j)Jt_jh-ZEy-2L3tp8BY0l6T7y)orFOQBr7+%WaVIwDZ9AOZ!0_DI;l +zs`Vof;=J!9Zm$I5=lMJT5Q}J;kDo{;6P8AsAFS(lw}CRX_+84p?#>%9Bjl6E%m^y( +zlMl4r6e#wqLV`-L(I~IKzZ9nQKQYw>IwWVP|0kwaZLRHfNbG~{`q6sNJMLpG3ql^x +z7rzOXHQwf9B}!A)^vqoAr|OXK#@d8Io?~uyDJCgG_>@Dl7%p<5XL)##q&_DiUne +zChtN95Rs_c% +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 +ziq3RziIi5*92~Y&Kz+-Vk*8qvGkZ+b6mHK +z*Dpo6OcN~5F}(_0d)1F-wt@tww#oFofE9EYa{H4Li`2q9>wV48!}uxHx|%h+m@x~c +zsa`I6V;0XXJLqVWj@)Ik4$gN>AZGdSRCfL)!A<5T%W4_1cWaOrI;j;@sliQB0YzOs +zEdmM!L}u&tVV8Y@w^VFcfM_Ijg#b45pzAf$CWsAh@`fyHK +zN@R50H_!!pKjy=(8jt;p0zp2$tCi+RztulA6bN*)X*0yOM? +zYTuVu^qZvYExn@@^`|2gpOv-4)l%$&DNV|>d98~<^!?2^W@4qXPQN+i5JZ!?hl}u3 +zYJVAeXMF8l(y)|`?V|PgMV9H+Z&SW?R5p>`m$6@bUid@s>s`j_6yGWmmacki;!kCb +z;BRksro+zOP1;qpV|<5-3o;7wL*LGL84aDd7dX%eY{;c*s`Asv*}Dfw>)#b+T8oDF +zRrLo4J(Rhx-wKZE?SB3>ejw2lQ_H_je_CUeic7D}BPHLc3N +zwEYJ`bMXv5>a~_3{^H%~;d=*FS9fUJd6f4-Lv{6|B93ZI{%I8=>m^Tj=6hhR?xARw +z_0|iwDNk!4dv8gs?=X@Gl-avO=|XSzpt&HP%1xQ4-w_3Dy)(UyqIIHbe4{XDDaz8B +z8GnoO!qRc$4g-Ki`fIwJf+fdv=YqvTo-;KzL1po2oBdJ9(R@gsBwWUI+V%r7XYi_^ +zGEl>7G-2F%!Y0mL+xxg-QBIcG`bxq5UyTM+d{VAMp*q3P!#C{ah)6jBGpVepvB`n| +z_ET{x55yacWkKExuhF2Ktx_x=s2bQ!Kr6z6r23buykK6hK(}ix8bx<0duY@3&yrz6 +z#oz8HKR<-&4_55FG0-V3c!py%)X7zAEL~i^09AD(jW{P +zLs@-`Xz`#}GrD@%Rbm*{8f7_-T1h%cj^3(<+-t-uSIDikPG90zo2FU?XuTN?*ydpT +zn(IBChCBI>?JalgI-U9#LMmROZKUT$gpFmmRVaDsPmWq=&8hk;emc$u?UzV8AD+8i +zEBu8uIScnXX!b@0ngf +zeXZs1Q1b(MchDWko?O!foxodf5If@~G9QmwEvK*oo;0qF@NbVoFr{z20nGrU*jt?t +zKn~{{DiVyp9M=zpV&n%*!2xOm>yWn=zq+ooG9CNIx16L)&8fG^uDZ{#mtln +zNxI|-0kiWCHf`-SU7kO**-rR2CB;%<5grLuHg>jme|d`e7J9>ziWU@GU3?nDzlm$; +z-mkGSXwl*syu~sdf9z@mUJfyY5Z)F4p7Jc?U#_{}Dypn;Ee*#tkJ78+QjqTVVD@KA +zB~~E{1K0Hbg6}{>REp3n>oU_QN|Jfp5lBu2)ioGSGhH}%E4#3`4jKV%S{?wdq?prd +zP+NB+GiUL5$0|Z0OJMg?!;FlB?5{ +z!$J4<-9`?e_2UJC_4)Oj#I +z0iZ^vR+|ahq2FV?dF1vVBIO4TG9|R_{$FEwk&mV&p@nKVT$a!Y(3n6Hn}>adW-&-38wr` +zbTyvp$JnGCR)xV{-8!BHRbM~Yw^zYz%C&5^5u3_Ni*9(}#2+Q$-^33b-tQaLm#TvI +zR64Enw%YEUD55*2TZO)R++jj1*vR$5qNVZaGkOEn*o^(_$8{d*U*9*DXgZl@SrQ$^ +z|F_P@7(CI5+Mm@WO;R{ra13!y1DPEUE`#o&r>+H3yUc;Awlp?2Jp0?AIpUbc_og}q +z-dJgH_4LN4#5%df4ZbhpG)phim4+Tpi(tCKQk^bn=tR#@$PDgPG^_!~(#GXwA3nA3 +zg*rmXPy?B9@TVJCotV(!!azCt&9%Vh70Zeh36%Z@&&JFkk^}U~TR7(V-p|i{UQm}3 +z&EvX)L;>BJ;+Sxndw7@7c4Fc6J{=vxHOP;_K_!7P)V-0P5+BYC8&!l?aTUD}w-+9& +zU{?`}522O^;x#NUJ`H;@E#@`x1_((tkfSH{QMc=~b=aD$L-FTeY&41v_}i5kKnl^n +ztoM4+_8JP@PhC7kcMdj!4U}rCjEgs}N`jTh^;JXm&mo`Sy?d<|?nXQCM@Zr(m!h{v +zRKADO>uM}z!o(9($TkligZYX%SG;S(Q@Lp8R))eE*oQ?T`kqgSi1BR{ymej`e@5!_ +zBP_=6eD(K{^wE6_7lEBIO*|6QYd9@&ALfx=&>J|@hhzEJflssB016LHY05lm{&_(& +zbyvT2&rrsgYOvr+A%0x*SQcD2YYvZp{P01AW(ij3_uSuc1wZDpny`zL1#8iIRSmrU +z(fo^dkHTwYKU^(5g#Y!tPE3T)iirS)lcgaS4}%nsERRop6B(S5n|QUp*dbTux}4}n +zM0k4;M-wcp6A?@@P+>*OD>&>s(cp)usbnVgXvlM{~JKg$Z!eZoIELIV$9z3W0uN&C}nyr +zCf@Wpkf0JyL^~Wea>yOQx1ApTXw8ds1edP!vei$VB@H%7d@pngwr8KRdLXpVZMs4s +zSqJcyeFyEL_Kjql`{yK-$J-DbxfF`snVm0vgxB(ah@Q@ye2n*w5`zPfj-voE#`wd= +ziMsGu`>OYGFzxZP#Q26TRfV(XHzMIRQ!rH^yaqr6fmG|nEF0~)F`uWsEvWJMKaqyN +z?=jYgNvb}4IbKdkN+RVDlSxjJUj%-A&)IpdNH-2cOZ-pd=INDd?u-K)Ma +z3k9u7M=rtJkJp!Is)I4-_1F80^%&)sN6J;2CE87g&N9vaw2`a->!COyT#_?rpLgV) +z9(LH@1k{UDt~nX8b~2K4ciz!{*uwDYK_}nu8u;O3Dax-+svI02+HNO5` +zTMX^vH4}ss&5HEPnLOno7_7g{@9zZ5a9|ko45#{;QftIq)=_ww$Ks7SMm__S4rQ0S +z0bduKG8`k#GVG2c9Dui6!PWimP5gPAza}4E^O=j+072cb6bsgNdSU0qsKll_hp&l$ +zk$7`Z*>__^bmN01VNmtk-d}Y_g0~j=)ogk#!c99R%$ZQ=j-)C-b<=;Njr=ID{4k@_n&-cd;?W@7`gHKD5&0<`hk8uDSiTVwjSBFnP^qh@MhLr(8g%ei#$nt=+}K8e34KS+|353 +zBlkqNN@B^xj{EU`{EjXJIOeIa8Px)-H5K}TvzMwRgUL>|sBP10uI%yn%7)^&f7JNO +z{l~s5wQKz_80)}Us(4~k@Lz!YXsn0H)i%owGi$|{mo-%2nTKK1Ndo?;aP|_V$ML}? +zZL@VaX%y5@4t%8+zgn)*+!e>}3-{Y%QOv_3D)A561Q2jP>hib{JQ8#tA6tZ#$AeQ( +z$}hp*Hy)6;Zh~`_`lZ=d)4<2~>#JK|O(#E7_v1OjA6*6mP#(y6NAMv;b}0f}2!*jM +zD-XAcKLyFP02fg3rUM6FS%c3*;F+cM^IKY^u@6Z7f8qIY +AWdHyG + +diff --git a/wrench/reftests/filters/svg-filter-drop-shadow-perspective.yaml b/wrench/reftests/filters/svg-filter-drop-shadow-perspective.yaml +index 10c4502c28de1b42af7352f761179ba573580932..744e2f655e55f4aa344e82e11d20f9dc6defd5a4 100644 +--- a/wrench/reftests/filters/svg-filter-drop-shadow-perspective.yaml ++++ b/wrench/reftests/filters/svg-filter-drop-shadow-perspective.yaml +@@ -8,7 +8,7 @@ root: + items: + - type: "stacking-context" + transform-origin: 0 250 +- transform: rotate-x(15) ++ transform: rotate-x(-15) + filter-primitives: + - type: drop-shadow + color: red +diff --git a/wrench/reftests/split/near-plane.yaml b/wrench/reftests/split/near-plane.yaml +index 15a02fdca6736b86cb254b4b1b1ef182f318116e..f1911674cafdc2db4bd93837af76a87f164acf20 100644 +--- a/wrench/reftests/split/near-plane.yaml ++++ b/wrench/reftests/split/near-plane.yaml +@@ -9,7 +9,7 @@ root: + items: + - type: stacking-context + bounds: [0, 0, 600, 600] +- transform: rotate-x(60.0) ++ transform: rotate-x(-60.0) + items: + - type: rect + bounds: [000, 0, 600, 600] +diff --git a/wrench/reftests/split/same-plane.png b/wrench/reftests/split/same-plane.png +index 76fb240d36953ad0941a28d4583f8813a9c2ce3e..414dc5cd84fc940b24d3b844c1a3b796212c6f59 100644 +GIT binary patch +delta 1925 +zcmXX`eN@WOj$3DdKRH3wdb@0ECcgP& +zVa~a~!XF^UmX2rq34y`i9q#_kR44VI|08_`g}0pEGVUC~sx^fKoNBQ~{ek8sP3H|y2h +z{a;TI_6cTV4UAQtP5gXhyLX_$;byW@PgCix!}!$YT4%Oz%n++6aAb@{;!^|F&YXYJ +zN+DMwd)BMsN)Nm<)1<3Zel;WNn0a4v{esyiyJkqJ@1$xk>AEhzLHO2QGbGlZrFP!a +za8LhC+&%wG?`P9#WN}aHMdH6dTWnLkJ<}%gPLy>xZ~VF;iIp2K#BnI?dZYHMD)Np| +zKX=G>V6wv3PNgT~Iu&=okT!VfXk;vbzd~Db881`G6*+bvwH-Ki@XV!~EuxM;AX5{# +zv7ggBcAx5wMf@qHEhwZdl=~xS%Pr!9rh}4WOe7RQm8~z3#ZkByd6d%L`Yc%-51>Cq +zc`}MDPAUObaeHRH6h#xNT_GJ)l%~T%92bs<+y`3aN;bXJmxVLX>&vMom +zA@sL%l|-Ahg_kM)co7NjknvZ`hfYzWC%}||)9%YE*0+$)IFMW=Gb(b0hR*au+9mOv +z*gDtp{daGYaH8Pa7_-@NR}Zpt<;40lugtiyjUSA%?Inq}OfOiqy##5?ig@g7tAeyq +zQr`A_gO$ol<0s?%)C*~ga>pY<_)FVcsZqTZEN7~B=-+J)e3P(^>1Rn;F6VE4D>c3h +zb_dAKBWtjx&3vas-{jvK)}{!ydTwrR=E@D7g=wNjyMpU~{6P9JpZQzG+BOCaTaQnj +zTa1q_XA$7ZJ>xHSDDkm)76E}dg}<7#Xgm!f^a)9zv4ft^3m&Rjq=?C^`(XAkR&6z# +zNmz;aTOQhUF+g@Afwf6W2;Ir2{n3YAP~e~lU9aK9KhX1)j%vL*nS|SA{)m-$ED|Jx +z3>mn0%duHIwSmsrfQjdKFqymHfFvkfOv3*ajz?Zp_~k*RG7&lmly)r&EWmC^A2mSQ +z8nC{Li2nrXf=t<%*b>j7{RMNs0V(3u0!L$$#B}Q-zGl%u4L2b-POt2;R1Dan#luqn +z45anRa9wI4lPH;O?%tE?evPUIS-t~V +zSli;+IuW`;?Gfi6DR*4eu(>O7oo_$0AjmGlm`v9P0=ow?-C8XurlKV}Ur~rs$V6tz +z>aeL6D&j}*(53_x`&#Spy%ipE1jWRs!F$;=nnJKnsj*^fmxT&ZehBU<=O`5+wVKO$ +zP+~P_^Wa0P8RbE*-n^6t_pRprJa8iZ49Tp5gbf9ZJC5B8X>G~)xx-Yj>!0<*ok0*5 +zYaMRk0eE~fJlmlrxC!aQG!Z(jq%ApEY@Q?BqVl+9drx&ZZRynVrr{~j!(sIv~P>Q3$A7zmKwRH}h5y0^{ +z6sYFAh;n9gJSeJgcP|dMllmb#n!?kqb&Ia^;pI-XKuS_&gh1Rn4}UFbd2*<-Dj@{+ +zKLWc1Lda`Bg~01y3)0A&=Rt)Co*9k^XST++kXwG`62Yy#Em_bu>^mJI4S57$fBYiY +z85l-b&X88Ht8w2J#O&%e_YINzyJUM + +delta 1926 +zcmXX_eOME993L>)UEtV;3BJq$L#RwleRyfp!GS`EfK$^*gk6L_2BgUXWx}xyJgg_s +z%7*3SBrB}vu~T|{2~ZpDGZ-$aA4)gt^vy +zCv8%Lj2#rAqd%5zPkFLp1S*Vk6^vz52@lK`N6FG_kA>D!uk`(89<$|kiTc`op>=@* +z{T(C4%l~Ye6WA=(udjLAdq1hF-rJNDI2xw^I+B*RIZKL>R$|rjs9VCse-W$!Vdc1} +zzWZj7c~ofK4SW31tHmE)D^EB$GZXxaj0v#S_9YhYGTmBP-<>|(S(Z>4<9a!hQiF=h +z;yBZ-kJozZ$f`U0=u}7k=cGcyVCqEq-$E;7t4*cc)c(^leH&!J+pF#zJT28n!v-9% +z>rPAbZylcXL*3`$d@B)_M})mUx8?EPgzxe9bkb(U@18RgoWIvffaEII2F +z5L^Lq^L?=?%W)6kWxm}a(eMA5D}69`I~h1Aa5V9n5r~{%v4)on__KLV0X7Eduwm+qap-Vf;0MzLDx%TDZ!b +zCZgA$pLD7QR=u1CCdCXE`xOO(UuQ>sM!SnJulG@}Mkt@R1f2hAYcr8#yCMR4))3qB +zA8%8O^Oj*XLFv*9#(`@ +zE9r?aJq6Bx8u#|IvhfI>gEh_?hsmoEQyJ9nN*6gFO*t};Nix?e$4jMp>Qhqj8tkcK +z$w3HihYgu&!=j*WDRUWCO=3WJAC(6I;R>1lZV_ndI@!u9jYYe9A5&?PA>41)g?qt_T +zWKpKWXn{P&e48fAv1hG!fl1u8jtbp^*R+AIYU2^nAqmqo(4NQo4n?_oHrkw8DRbED +zggkGU#36CxqO&0$e`HU6O$Z{&RnnrfP$#hmsQvabi%M52tWA04CE9dFj)>L+dQ~sS);(u)KFa0U8`e?kLYPo75TwTWnsatTHy%wu>q!X%yy(56_pP^gTlfVfZ$s>tT};jkyd +zq&Y+cbY)SaN>?$Y>JhX(Rk4PCK{95HQ`qZ|VkbBS@Q=0fp_STaC%I6#l+keX_*l9X +z##s4{Ybo$}!N>&l5DjO6&R?CmgEKr#M^tk3*dI1if$TUfl-Nh|>-Pca8L(5fY{hP0 +zMEjx-y!RcrB|E9)yF)yPT$;gy)TIqOFfONCTF-~(3@+l`5?2=UZq#6zPkVERk7R$H +zmlQnx9y|_q8I%3L@^*FVZM?WZyhF+>&D=ij%i&gEX#aq#J(wN5Cp$l*fpZo(|9O`; +z)HQAc&kQV9kiKRiFA?$ZxR1YkF?eh2wN5_z-}KJs=`Y-F0|H;33ia#Q +dE57VEZ${y_J#;`>RU;FCFKb=y+Ougo)BkFPc*Fnz + +diff --git a/wrench/reftests/split/same-plane.yaml b/wrench/reftests/split/same-plane.yaml +index d560f22351ddea1207cc0f969c1f9086c762f875..277709bad1f2346f8011e55bfee03aa55acde31e 100644 +--- a/wrench/reftests/split/same-plane.yaml ++++ b/wrench/reftests/split/same-plane.yaml +@@ -7,7 +7,7 @@ root: + items: + - type: "stacking-context" + transform-style: preserve-3d +- transform: rotate-y(30) rotate-x(75) translate(-100, 100, 0) ++ transform: rotate-y(-30) rotate-x(-75) translate(-100, 100, 0) + items: + - type: "stacking-context" + perspective: 400 +@@ -25,7 +25,7 @@ root: + color: 255 0 0 1.0000 + - type: stacking-context + bounds: [0, 0, 600, 600] +- transform: rotate-z(90) ++ transform: rotate-z(-90) + items: + - + bounds: [0, 200, 150, 200] +diff --git a/wrench/reftests/split/simple.yaml b/wrench/reftests/split/simple.yaml +index 7714690e0f0ae65e57b283b8c04f6fa503a4f340..b6445780f5f9cb61b866669548a8b9f9e42943e3 100644 +--- a/wrench/reftests/split/simple.yaml ++++ b/wrench/reftests/split/simple.yaml +@@ -10,14 +10,14 @@ root: + items: + - type: stacking-context + bounds: [0, 0, 600, 600] +- transform: rotate-y(60.0) ++ transform: rotate-y(-60.0) + items: + - type: rect + bounds: [0, 0, 600, 600] + color: [255, 0, 0, 0.5] + - type: stacking-context + bounds: [0, 0, 600, 600] +- transform: rotate-y(-60.0) ++ transform: rotate-y(60.0) + items: + - type: rect + bounds: [0, 0, 600, 600] +diff --git a/wrench/reftests/split/split-intersect1.yaml b/wrench/reftests/split/split-intersect1.yaml +index 65f9da4d5a9449008d72d6a9467849887dd39d5c..dff763870728c98c6bded01d9e25a2bac4c6ff8a 100644 +--- a/wrench/reftests/split/split-intersect1.yaml ++++ b/wrench/reftests/split/split-intersect1.yaml +@@ -10,7 +10,7 @@ root: + bounds: 0 0 100 100 + color: red + - type: stacking-context +- transform: rotate-y(0.1) ++ transform: rotate-y(-0.1) + bounds: 0 0 100 100 + items: + - type: rect +diff --git a/wrench/reftests/text/alpha-transform.yaml b/wrench/reftests/text/alpha-transform.yaml +index b2aa366c18986c425fc4c591f6613be0c8cf2836..8a623b80671321dda5b186bf5e356a36cd20a2cd 100644 +--- a/wrench/reftests/text/alpha-transform.yaml ++++ b/wrench/reftests/text/alpha-transform.yaml +@@ -2,7 +2,7 @@ root: + items: + - type: stacking-context + bounds: [0, 0, 660, 210] +- transform: scale(1.5, 2.5) rotate(10) ++ transform: scale(1.5, 2.5) rotate(-10) + items: + - text: "a Bcd Efgh Ijklm Nopqrs Tuvwxyz" + origin: 20 50 +diff --git a/wrench/reftests/text/raster-space.yaml b/wrench/reftests/text/raster-space.yaml +index 6bae473a7fb9a6b3f21a20e1b68f05b2a78e139a..e6534784123f7ce80f2b1063c03c5a5aa9204282 100644 +--- a/wrench/reftests/text/raster-space.yaml ++++ b/wrench/reftests/text/raster-space.yaml +@@ -1,7 +1,7 @@ + root: + items: + - type: stacking-context +- transform: scale(5.0) rotate(45) ++ transform: scale(5.0) rotate(-45) + transform-origin: 300 300 + raster-space: local(1.0) + items: +@@ -10,7 +10,7 @@ root: + size: 20 + font: "FreeSans.ttf" + - type: stacking-context +- transform: scale(5.0) rotate(45) ++ transform: scale(5.0) rotate(-45) + transform-origin: 0 400 + items: + - text: "Screen" +@@ -18,7 +18,7 @@ root: + size: 20 + font: "FreeSans.ttf" + - type: stacking-context +- transform: scale(5.0) rotate(45) ++ transform: scale(5.0) rotate(-45) + transform-origin: -80 240 + raster-space: local(5.0) + items: +diff --git a/wrench/reftests/text/shadow-transforms.yaml b/wrench/reftests/text/shadow-transforms.yaml +index 8e5d942458c78ce8affe16fb98dba367b5afdeb7..7a75133dab4c82c22f41399f9e7ea53240c5a469 100644 +--- a/wrench/reftests/text/shadow-transforms.yaml ++++ b/wrench/reftests/text/shadow-transforms.yaml +@@ -3,7 +3,7 @@ + root: + items: + - type: stacking-context +- transform: rotate(30) ++ transform: rotate(-30) + transform-origin: 80 80 + items: + - +@@ -38,7 +38,7 @@ root: + items: + - type: "stacking-context" + transform-origin: 235 235 +- transform: rotate-x(15) ++ transform: rotate-x(-15) + items: + - + type: "shadow" +diff --git a/wrench/reftests/text/subpixel-rotate.yaml b/wrench/reftests/text/subpixel-rotate.yaml +index 1edcdbd8d263ab0cb45b85bd75641230443945ff..296afb0d28dcf0c106613f11b560ddfbca97e83d 100644 +--- a/wrench/reftests/text/subpixel-rotate.yaml ++++ b/wrench/reftests/text/subpixel-rotate.yaml +@@ -2,7 +2,7 @@ root: + items: + - type: stacking-context + bounds: [0, 0, 430, 330] +- transform: rotate(30) ++ transform: rotate(-30) + items: + - text: "a Bcd Efgh Ijklm Nopqrs Tuvwxyz" + origin: 50 200 +diff --git a/wrench/reftests/text/writing-modes-ref.yaml b/wrench/reftests/text/writing-modes-ref.yaml +index a1d374b66d717b3b5a384dd0b2bb095699a53427..c392822e607d9cdb71488df6848eb60928eebabf 100644 +--- a/wrench/reftests/text/writing-modes-ref.yaml ++++ b/wrench/reftests/text/writing-modes-ref.yaml +@@ -2,7 +2,7 @@ root: + items: + - type: stacking-context + bounds: [0, 0, 300, 60] +- transform: rotate(90) translate(-120, 160) ++ transform: rotate(-90) translate(-120, 160) + items: + - text: "This is sideways-left" + origin: 0 40 +@@ -10,7 +10,7 @@ root: + font: "FreeSans.ttf" + - type: stacking-context + bounds: [0, 0, 300, 60] +- transform: rotate(-90) translate(-90, 120) ++ transform: rotate(90) translate(-90, 120) + items: + - text: "This is sideways-right" + origin: 0 40 +diff --git a/wrench/reftests/transforms/border-zoom.png b/wrench/reftests/transforms/border-zoom.png +index 627f69184418de1babbfd7f44a67a7a38db4ccf8..cb634ef97c4d92a3c388b716b3ee32e099e9070b 100644 +GIT binary patch +literal 27613 +zcmeFZ^;?u}*ET%LEeHq(AS$USLkOrST?QdBAR#Ixh$7t`gBKu*ASfj=11jB$bPNhe +zDInb`A>Cc?I=JrV`Mw{%zu?>6AFjSc3E +z215q_9jrP`4*#N>_+bo#F^|7}>$;{3VWy9wgrTi(Wg$V1gXNaY_2cx%#ZOKjdPJS~ +z+r3cp$H_-?EK1W16OsLQzC4^O75>k4`eV2L6Mr*(u6Q*{;^pm&M|$m6Ma$+ke^qqp +zJaP#v*K>Zd>mIjfauD8xNf!*DM}NlOdV~J%9mWOAz+gg7$fBiT)ITX4fInmu>ABHg +z6<(e@g#P5d6OaC2zwm!Q{C^MZ{~F2vKOCdx?oj^UAfZKn{ARUue7$gFduI6Vy8MJc +zTYSAxXnXpbx`>@Mw!H;;(@>RdHVlTI5qW>Y%+}lv{^Is2s;2FY=1;P>FFz;|;g+=@ +zQyBfJ6pQEAO6TI!<fYgS0TYA*?N;%%Dcqabw&v+iK +zw+C1KrPs@jVLp=(429*W8Ak}H&N6$h-e274a}Kqm5BNRZb^GP!{LK{jvh#l8Dnr$E +z;h{H{tECkOytd}pc(oLF#jcs1#}n03N$WSdod-r}+pUUkx20k6`hTiw_0o4%HRPhM +z)EZD=J~!;Y#QPelLUT1KHh!&NDO2skjXMtt)!&P1bh|v7D4vU-AODZ+MvuLV#3fj% +zUb2et%JZ3$uXuIy_(!jA&p!1Nzl-1fnq#JwO^(^-MCbA2VFGpY8OHEJ`|(LXM*ZJ= +z(PCP9i7FboHu+on&^C#z?T<7D|*YUm8;PF)(fym5$K0EG#KiHf7r +zzvz=qVq7`x`R->|vOId^SH0!+^CM|?HP=7uuix=03TF11ufYz3&uR~0Tzw(Nd=ood +z!SSU2eS)^Wt1KD$k(ypls=1px`mURWd>%Bbj-RrEJV$-Ia}!vcY?K^SEc$i3?NzN0 +z%FM<=yqagao28$@=Cyv^hvH}hee#c2jOI@uPMQ5^=mhDRqnAp_wlLtf&FAlH&N$4+4 +z;8ZE0#1#6W?MeHU(^tI#t1=++Lk~deTB1LZr_lEP(GHr=zBAax%Ih9{$dw0%2a$ +zoCpDfS>juUIt>AXwatHKO6=7WZ=|H^_c38YcED)8OO7Wlmd=+`N6Bx*cx{@_g~h~q +z^!-UA32e6o8m1{L~2xGjzM}c(x(g&ef)%I|0{S;CAG5U>rOF4N9ZH*naqYJ@>@hP+UG}FDkL!=!{Ao~v@ott!rxJgu +zxdgwrTQo=7%CQ?&oHLFMAW@B44Zf=0YQHYK$3pGHRnC$s7gTZpli-goNmTI3uiu{y +zD;z3lgpFqvAAjZb(BqC*PkODX7NXl@iN$cyK(;1>+n6n$+g%Ycc-IhF571>JZ1y>; +zjug`DwROQcsa!9WiUGdz!v0sDUK*=!Nxl>3@Pk47FWcU_zjL<1O#|l*qu*=fhv!sQ +zX8Bm$(-|>pSJ2%MJ{#Ab2x61gk2LTqJjwT9gy?c)rR{qMC+TxcUgx=}>5i`^S0;{P +z0!{;vh0s|Qj-Cu+m3&}bwv(s5a5#94I68IGfa{?6>^p*FTZ)F=Nd^o(9l97Z>Fs%+ +z7}v3=U+#Ad)eQn)FFD3$S$cka!EC2+g}?N^V2Z_kYm7`2a=K3|Gx-BgSSQ36D(P0f +zQ?MB;Y6W^6m0jBut78nf7cJCWi&c_m9y)}fXF*^6pe0eI#rpu6Lyu*?D?`4!$Hq@j +z%VBESbUB)oN7W;5%81UZGc{|5dBxX(fE^mVXpYe4?b`o6Uc<_P@3G*v#Y)E{;H|^ +zli#=a3%_@`l7v?MPGr{$sgF0P=UX+?ii2CV)4&NI3*VY8R_)NW4A&Z7<6+ZICGJEM_=(`t;7Txr*_p4gfzTfp(w3aqh0%B{1~a6g(vK-Vz`I*B-wsfm +zcTNHTtBYJC$E-&_-FqHZT*d*uT|W5Tr>)f88DFP|w^>LTywO^4mrpOdg0?%!$#Np( +z=3DWzhMLZ8sY9xYbsAiEKC1b#RS;9u!h{BNA<5aW?Uyi}P@jmlF3%|WeJjDa|IL$n +zI~{wyt!nA*ffudsuPOD%ijAETz>J8avrMZeigySfd@%a!d-2{(FzJzInV09x;G#pX +zZS{uv@*~Z9zm(|+olWP7pttfH?bv5h8F8Dyeh8nfYzuulBwf^1B +zO&g-;YF9T;G#lC0B(LeZjp=XpiBQ#&XPV`EdAlY;OEm^A%SUB!C_Bh*XiiXS`*@Q= +zCF;tLG#AUl(THx(wLZsc7U$%<84ps`69NK8calD>4*4%;$e6Y!8=qirUf3I(Y~nbr +zID{w1jG#P^eQJ58M>{FTEmitQn%!t!(ce$6uFTz>DIAaK771htRyjrV8t|-*myRFb +zn-QE-y=Fao$Xge|fN{90^v+emhr_V)B_64v*DW9YSyRilM+J7zY|@9*P)t=_3{;4{ +zbbYcz)otdQHGBBtXhH;E=vq?wcM`^q9D%A9KWW`a55KdrOCPKD-mE=6Q9;nWB0ble}%jxEU)&c@9j2seraAD?U&)%n&w!Uys+oPua!DNgZYB+H(PGJ +zfp|?qIp~%0sYk4JEwJyjsvss_0f}v!#a;Sw)r?p=oC4VYu8RPs@&&p(sz24i+Sji? +zYfr!bM@wRNy;?feZDrDJxk+9tOJC9wlBoWAI>SR|IXSPZiV0&R17{yr)MHi5XMfMq0+}w+Yja|_#S&hjI-?6&EZI{oDE1n#y%y+;NZ@3JU6;I}-iP`kj>C84RQC(@-G@_fmX*x%xr_)7&NXNK|Dse68=vQt0paFK$UkNN6C1eX_rj{=>dbD*!k92* +z?;k4bW2FZM?pen{)bC0w@IBahwxsxcXi2v}-HK5ddVmZN2=V~yOq +zdH=)xv*INnVm_lNkuj@gjV_UH%SmCx +zC{iK&_=FRmx!UJGSGr3dY2P3!56Adb`s2NHU2ebc978)P!u|8)>M3g1cWKd$d2P9p +z$=D)XQ2Yf!|H>RApI%XGn#*+2ROap(e2G)Pt4Wqx`dC9a6?MYdoAF&={ld&TBB-e1 +zGxZY09RKrS9f9NpBk;Q@Zcjj`RUZDKZ-ZYO#D2!A8TET;Ob!^F5$4~n0Bdeee$$#3 +z*;}uIMbsoKUM;*4U98~{oRiZ^yIap2R-jf=v86$Zez{`Pd>c$g +z<-N;+4_wHwepszeckNcWm=74w>pg!F#;c9<^Yb(7%1+Scx;Au2EwbyEZy6v9>``6T +ztb;#lzT@*pc-tZLwoX0Q(eR?l)U<7GexkT@`|po8cPQ_lRd!>R5E1n;+x37ugoNR1 +z(spC6S=%DHHx0sU`ZTEeIP8VZ8VI8LKORL}<~4PGdF1%}I)$rW`UzS)+Lj$oTv&Vh +z0=YVPqj8bJtCJ^8J{_1sB?Z^rzGCM!72of08#WJZVx>H{Des$auR~^J60>kH#m{yE +z?nHX0mKtNazrC?%6s&445>}5zIJnNa&*Vmy$+2azICXyKHO|c{i;#PLq(wPCSb7!q +zr>{5p`}$u|46VNAnnSPmT3gL_?}>>)8x|n +zsC$;E_UT3xRRT{KFQyD-EnFv^2R!o|ME)GX2mG|HW*N`=A)gA6u76f+=yKV7LF^ot +zk)&_+cDeBJ@<(`#7ag*)ktYp=sMDu+A@y`&2v!W|F@?%T43g2(8#(2efJ-mV9f?2e +z!ySSu2a`@rLAZ>`{y>LsCiu&TtIDTsf?M* +z`}1?EM2EMG802*4=x9IBbmykEXX;I9Nvsaw@j+0d0@|)piCX@k+Q3_IOO!*k3qgT_ +zS206JsnqRu&>2^<8iV~;-7B#9*qvvY2DK9I3nc})|G^g$SKRZi8o#*ekbKqTb%;7q +zY_aUBdL)0dQx9d0{QiduMk90_AjjO)(0zZ+JkhY?c?zT{`e)}#7UZtFOjn6;&zxn>sjT6FAGR!9e{3%e_xr<^SQ +z>y4H~aG+||sY4-bXp1yX?e|S4UW?DpCl&aT`OHBjh(90wzOA%7O9drvgC?&?^MGR%u_H!6RVV +zwcm^ug3SdnQP*66v;5{3C2Sq@Am1uO+rYC8K>7e3T;>w?Aq!A&L8s2rFmuTYXG!{u1jxIDS^s)>S!{d0qV%B9Hx1Pb!CSKH +zUJvhzOeT-mmYw7)!3-l0h;J`;w#L&;2%O`fd#>brmQLo)z0wEoFP%NDaD0d(Ga*w~ +zH0AM6WA-1_+`VHpomYeq$*oqB$la(A)e6z<=r@Th3v3oaT +zkzAc}kJi6Bpdw6q;4yjj0XWn7;v%P+)bjEhKRB6BG5^uKI`a8;=vzo~n-dcr1c}SPc8k67ydt291inw^E*lA|FercWL-k)V4y+S3BO4cngE;5G9#Nef_ +z;!T&I3WRhI`D5o)H=?CvZtpL)3A=0Mddhj%&^VY?3#>`w)9a%qO_(qDitoA&K7)&9 +z)Q8jL){rynz6xo?=Zca8^vDYa8HxDajUuRWAU;AA-zsN}lL;*6@EW>PHeVB47WFLb +zfBu5%2`)M0b9*j86tv}8>flwz1t!m(AXf?mZUpj=d~KP^a^m0tvbP`Ul_FfoQhd+S +zOS*I3f&SX +zz5mA-A*Tk$u$E)Pm!B2Sle{36x9!Z4NoxJRVEU4K=u?+A-Xz +z$jNqw&mYn1@RSu7%0pgu<~(h>feg9OZnWf}U2l&y);X!_b$Eop8aR$g?8t0bnd$l0 +z3g5_)zg;PRw{&S%B2Y58HbTcL%9VK`Iqvl$RSm~c$*>aXmA`TN?74KR%1{~1iU@e` +zpF$wGq=s{!W8{87^w^ro-=Yighmxl%@b;`)=~gC`Fr#(qJ$bb(simunp(lj^SX6%+oO>*Vv%DpQ%pnLZx`* +zItyHNjXh*qlV=j|$tK$VsX8P0B+#rid5FP#?~>sSK=Bq;_h+pS^3c{66$HEuU^E9inJ+xgU6&_}oS0Gt*oH2iM3$;09!3 +z+~5QS|1?MkPd{wT$nSsfsQPsq)R>bD-j1u&Ujg-Y*C?ijUT}c37tp=N?;nX+m%*{+ +zqa2|e_TLXN{(a75HIK!lWa#m#yhpdztORd*T!+vWP{Hnna7ZIuL5M%A*SewObmyI) +zkpgQxtnSlaJ$r`{%}{kUHv_%nj< +zXUU`Gzj48C43Hbn18+L3iV3LPnp1-MFHT-*zyp%z?C}`&#u)Wtr{cb56o*J5-n}LQB{nhtc&#|X)C^(bqNyYwM0mQr1!e0M@|4_jXa~I<4peWK_*Q*Yn6u; +zs6gv6D@`J*Oz8Ra-Yc(C)9v=VH$u^<7;c|ZEDP``iFSm?Lr`R})6J-4yAWAjFO!kV3zM{GVb&|~MVjz-M^2>JWV~6dUZ(~v7 +zn_5$RNM_Y_;zEjnv}yBs)A}=T_mgkGhL{K5WJ(p2 +z-QQuL#383ZM)7>EAHY>J3ps&b^*iVOE~XcU(A$98l1$38>@gcE_k#pcztjz`pT_kp +z(~F +z3Ga@?yKFp$1G(h=N%oa7yB}WfmJ@Qi*BgsJ1q6eaOV@{A>F?J9K)vA)hR}^Q9^Eri +z@}a=SwYrZBQ^%{bp5Ai+LU(r@1HWCq%kH#8w*(*z)Nc=Ks0`R;Nff%Ov5|!u? +z+BVXb8(9MCaET5tRI_Pst@>^XvZ(E_giRAn@GB_2WQJmv3BPksQs@O{b7*ayV&>uuKr*NRgeu(M1=a!$?VRbdaAmi<2?OF5W+fca}YKNoI493@$EJD*>3u7|8@w +z5Cow5@0@d+mi7d=xTZzRN-9|X+jNtxyz3H1t#zzE^f@g}Qm9g_=NJSK=$SpX*0uGMJ|IKsGV%u23020y6c)S(yg +zMhqw*vWBlb{mt%3-PlJaV|sW_ndbD{lA9|(7$Gtb5mV;PU9LaVOpBPe284L=4Z?mZ +zkYClY!_7IS8gTo{3_P0iR2aoO?(9Tpq$?kYDNel522n^=#9V2YU*;vhZUQw(ofkD*?y +zapBiSDIM5ZwDgbBAd0blpm31Jlda1P@kI`>^n*+I>HrIK-$ssERrEY&=$#6;(l>XPx@*7wx*C +zjNsfJ6xZxZG;C73K!dF&6)?N~{fOW`uiwoaUU$O0Lvd|D0FuGd`G17xi-o#wrRoSv +z_{5Y5Gsq>5A4hxrxUn>zhEi&9qsg^g>Dg>!3E@nT+d`m=FXaFweCTu>@`HlSIY +z=TOZu)JrX}h$JD|zo4c_x-`4pqAI<@!+OoE_4m8Wk6H_CA03csa2b9H#y8)|=B2kz +z-hH`RL0k7tpxnSy4(l=0^fQhE7uNFJT3z`U!$})Y@PO=J?H=2OSVPoZ=_q|>z|cQa +zx*&&IKQ)#p%$(CmRIV*C$Z?oZ&l>(Rm07%L9!P2S`@=JBOi0{5AsRZh)aYlRR$2t~ +zje3^8sI&+2uKS1#oor1B1q7Pb(zPnvaoSnQ!mnU4HDuPM7gpv3Al2eCn5EhF7jrDP +zJ|Zf8lDPk|!Eetd0p-AUpk#o=C{8ou!8N#zV2Ncl@F&O#uBz)4<s-KDhM5~j9GvRui +zRkmA%2$sU33u7-VzSaw!5w;@G9-+8&5OX4{@D +zd|AC6H5%z!27Uzox)7=R$vmsHJr;x6d$bR4kb#{Go+))+T>E&3L%&?*Q2q2~WRu(#_B +znM{^`>9%_reu)BO@lK#D>^Y5+Tw)_(B8Zuhc8tb(xIDh&(iuq#@Y@f*C}A>dNenu9 +z@!p`81auAYv@8DU@>A4VcoWB?)x1|c`oyxe)Sq$xr57$9d3Oqk +z!heg+`yvUf7`0_q_ltmR-ziFKXa;sKhtZf`SDElB71sLkI>~g8GtzviK5ad?Dj3#$ +z;VDpDsQLLXMM^6&i1D)>hqh7%9Fx%(BR`KRc>QF(aOv?#w%rijy-r;>6Sj#^qDO`0 +z(Y;QY_uB4s;aSDSi6YksL4;1=2S4~;)Gbi6?8$3G`D*8p(^qaCI&=t{RoGyg2ETk2l=G@gwx6EChDevEOxk +z1K3ny>&%Zg{Zl<&v&0|9+!yE#r`i{O*yQt2`wr(!93}l6XhM1>zP-^pU99;86OOiW7aH>|C83bH0__tD6Xuy2G +zCa8}}g!1{g3)bc8+Rxi<}dg2c;K_D+& +zDq~#QRMK)R$8l5aqvns-a4SEiQ<6}TIFsYDU<7Cd;v4Qj@d4=T!upK;Y{oQ2+<6Gs +z85%zE92~MOl?JWgf#YoJkvnQJV;zcA&=`PD& +zFD#FP^@l=We+w6}@W?-Po%T+VSYoFD?D9&9zPp#Gk|# +zLOpTc>kpS8=C=5bfyaU$Fse>Hme;I$MObXU9YBX?pR}ZnB@ZX`5D#J*5$GLunCaF6 +z{wYAE;1v>jY|+7zV?{CuM~SB_=1TthevL2xPZ|2=hh!>XqQD|FrD}EUS!_1WKvo76 +zF__B=$c3Vph|z?5IkuS&XG98-sDkO?!tf8X0a9s<$Ca&El|5_2@`nhFDdE|1VqSwb +z?ns^J(75>TS_ZJtmnYQwQSl$6-pUNY;+%OGv?z>@YF +zX~m~QPG9=cq3;g$!wZ#D{KuawR~nm)8)50csSx*<6b8_phDaPha_A8PsZ#_2We4r| +z(wIcaBl@bCY9qRFk?{v6N3oG8Q%txMCzpcKJNif;S}_Ggs0sC0{6SV@BIYc5xvQMg +z`8U6f9nxc|-~0UV`qqyCGjuH}b+e}6=JrhQ}dZRx*1TzRfHsJrp>@bt*Y8NizO +z`L(h=Jx1MQ1I}&b4&Q-~59ifx;Kj%&-xVbiw}|h2*FIX=HrUu90;RplpUvwX_jynN +z&hxOgrE0bOZHjXoc_Z(J_F53vQvXurkx3w(T+H}6`0n($f0@uYA`$l6-{n!BXnaF$^MaP+usgGG=1I71xU)Hua+#6TJMuI&KIzvc1 +zZ*Qe(fpR6$DbO;W*~f>Urw1!J1I5a|*E+%i)@kg=8?>g{GgdaI-bhC4Cn_gm@=RMG +zFP5=jwG$Kl2}g0$87m?#R)`S4Z0KKhRDRvU&7bzn<#YM5Diq$jxxXmqPDxHyLQlh^ +zDD)@1bL^;nN5@a^;e#%J_c-f0<5>P@^aR@n>~Y0ICf{GPaV@ +zBxrg*fu3N&qkli~_r$muRtW6(I!+35%WwGmMGh%qPSeTqY(aBf%f)jfog|{u`+dU* +zfVL}fywX_Qgzz;sR28k-U~_dQZNKmywwwM^ji9;P=+(B15DUgmg%OJgLS6zd(E}Bj +zu~FB|BT(bXc_3)I3p%7%Ejlkif8odp +zJ!d`N#zg0-#OpqbQ(X?28irQ{r-l|BqBJB49wMhQzKFIJR@a(7_?7`pe|7*inT%73IJyRq!r{(CV*pKDCZa*9gOTh)72U9 +z{r734Bmw<>+P_(2qvYB;f+PbmOF|@})0f-x^2u+*P}_y%qf$NBvgfV=ekT`bX6>w) +zs$jOP=QK-OXd!Ju4SsOG4&QGirGvG3OXNPOKjS#leNAwIU#P!{i8SDS0MopM +z<2o-$e5VP3inl|&ehsjlfoedVLT`$K&BG>fyDj~G3-S(}vk@~BEA0_d+xIfGHKIV- +zwz_3^PLq;=?7GF~{Lp0Q8~g`PECg%9yXj$H+wmd!Z6;YNnf^lHPk`QB2(q;7 +zE6RO-Vh3r#6l4gk!P2{bwIs?Lx1c2ijTRY;F_8?SFyWo= +zgomM`!Kv5J_JA`F5sq=21$*4HcwVs5+-S&U^9VuOrnMOeI$N +z@6>d2t~*}}J0@lZ +z^U>wx01W90_c=gu{lh^Ra*qdycc7Jew|_$)0|{f5i7y44Q-=Th_)9wMWEGQjYqT{9 +zNL%pC4!vpsu48dgxKo!FNOYkA4L0NFDaxQWVK?!+JU-M^pd?TV3MPaBg0m(1k}-gb +zr~Izd2#b87^tntfa!(j78pe9Oexqn|nrU}?snH$Fy4LTW<+e7qe}+mprg0@Krz7Fg +zLn8xx?a&b9Hw`B-DkYO~{yy4JFP-NJ582`@KHb!)`G^F0|Md*HRL+VWg +zxKG~~B8X_F-@5}OX~%8sf`u5S@+qOE?f#!iV`*$}Q0mOXK6Ebeh>IQxRc#0Qw>02p +zfxFN{48oW|l2f->qZm??INOKNg2^^)>~77qfyM^}oU*9xfm=-jP4km)m87jj^!0!5 +zjx%G0REUPSLlj6)JwgDDo+466>$?mc${Myo+$~V=z}d=JNI5+aYazU|7z(s|?HB?K +zvTJAmR&}$g#Lz+&`J;;zVL6^JJi7p3~=D +zh#7WOz=Sx?jVm9y`~fOC3q{wYz43`EK19kIlOB +zcs=L{q}eeobsMcOZ_}6rwsd}fjx69b)UZ*PhoOzK!s9*z1d1EbdpT`)bxeV8U!}#c +zb0&#-5<4Mvuy#^IT&`U?vPRM0LWHy7Ggi=qhia2AuM>fba-s+eApxiCWDm$mP8$Fk +zSea#TXKXz|R0D=$N?=lGlsMg%e21eC=vA@5Af<;~#MrN;$+hNX{Y--a3q6bhcoTku +z@&IvBZD`y^YgKHm*WMAj8|K9`c}tKQhaSKQV1?vGA~_x9g}heJ6_fb +zHHeJMP{<+&cY^|;XymQZGIWLA!ilJZ20=*)Fmn{6k9nai3}KE5H4bxW1Ax(yX)PDO +z15x95lu1lfn-8bk^4~Yy?*9f_)-~~=g@SOL@~RV5I5RBtASdxo_A@9)LbyI)BJ>|8 +z+30J&<8m4DV>APzYYFa*T#2ySK!>xKwZ}QBdBCN85`z9FeBo^YXh)4nq*&y@Sb<4^ +z{(ozvVXN^YSfOfh4niGmz)`fIMAg_##6>nrZKZ2>rGVHA@uymO7WYvB@ooqr6Pw88 +zlo8((j{o=!LsU4Hi$>1;neASX_IBN~vNc?Uc_g~E_nAS=<$`Y;#So$%+DKFUKnG+7 +z1zHQljYJr~a|`!GJuZ-9^4d2*s!l5a(fSaS4-a2nn)P7&_X7`x_*>T({vbU#d^Zr6 +zE5C0Zo%mcqi|oONwt)*P)FBcn#)YJGcf^ss0J52)yybE1&7+c$P@ +zVY3XnP%;8AbvhImfQVy%0Lqsp72&>kChL->wCUx@vTa!)$RYG%&VBw(8N4hxhjLZj +z!4D&VRJW!eE!03cFqE=WVA;z!r!I#Ul{Qe`tbpDezh1#sLWZe%A}l^G`o(nih+~~N +zmnbZ)33>|=4N_YcDlrmWY18zq;z!1HFK;qqvU}ulr<|qwUe-q7TwNbUdnB8^1fdyyL;9#`vI(`tW;wf}y+)-~0XeE}c +zY&47bPIMepS*0;{-J-9E@1{orot>Q37Lh@$04$`*zB=i)*5_PWCk6X)fAYKXjXdiF +zTme&t9Tnl_eu8Vf%a +zBZd1kLp^yc18GqO8LW}$?{O?h&eIIayiyIyNI<_?Ecww@5z8hk;{ye2D1$jZE$^Qx +zgyp+nkZ8IpE4wkXcs3Oq<8tGgdHXW#!7|g4-~Qh+)V~?oTLuy@LX@isyxu4WSKu*r +zB*iknuN|$&wHm^{w7t;@)={P@=CKbB5V>wmfd)n^TF4S)GUu|ge?qWMhXWJ0zh?s- +zC3IVyNx-Ca`VO5itoe^3^eSX>V(`1JDA7TKnA%D0f)0S|V@;_RoE$ +zAqzc*^nY3`;!eN1#nr2s?E`0v=cHgzqXX^XaPX2}^`?=1pkQ6fB)jAzrU};WYrSS7PN^J +zApXIu?aUYh=0lF(opHkvSBoMs9~NVn69yZCC}swp(*9Z+$b}+^#D2eu)tM}F-T*2l +zTwpF@q8*^|pRbC~6Kv=jxb6fc(~G+|>3k~*oOHzXaaLRo0JKt-zdo{I1h +z3`s91jm4M^y`cWx<(rc3tG{|zYVQIiT^m*^y*+dk>{+qm7M4Cm3y{thkgkX}8v#*H +zxq!h?dZIjQZU6B+IIUbn-9P~w_WTD&hstj~zr#cOv> +zK(f>Ugc*#_@wk9^*c8zrVqZj?PS0xEg%FWN!`R9LqT6K387Yqfuk8i-IpfHAk%8M- +zVmyd{lA$}&e*hB}KLLKZ`-U#Gu0dg3gaRoD*&jf@@mq4OHx4|k6TDb1R`vZ=Zu>+~ +zZW|NFd#4dn^mbf#LV=?jwfLYZg9CtB;MuYOP=VQ1%|BH^uqe?+aZ6$rWcq_}aqglu +zRtUY^AsdI1L6FaY)SEV~LGp0z1AY|gQsI){er}%_3$!(AsV?lizKUM(;FM>7ocvJ; +zu`Td405p^-6^|!r0jDn*hP8ZuS%1CZo(U60*FNq-0@ij%o+ZJLn(;~1M-%@bPs=gh +zZI3e(FF0Onm3fus{&}rcN{*pVI)rnHpF6Z)7&p+OlId738kG*yz%_yXzJy05dTXT8-@V5g=(+w29*hkHAHh2LJPC?);o9%rXTyV8M`jS&(-_>$!- +z|6#AGLzR@b!l2mFKw4eal2?~B(AlIaE3t~dw6Wc7o*>vTe*&XsA!P%Wa2lta3Zb5Z +zr4j+P8xmDyk~YmU?tiUH8(yR^h+gNJGq*Mn-{`K;UbijA>z6^lC!SL!-uBPiMvV8c +z?Q{Ely6CTj9_C6=Ah5fk(WIU{OF@d|lf)p8ou-a8((01dKt~M8X-(^AM+2!{k0_>Y +z_|c^VW+Q9qaPB5BK0+iyEYha1MdscQ1J(pr)ycSEsYSD!7PC|C1|QGQ4D)U$o~+Yw +z@vz1kjC%MPToGOj$8}{l(+De+x^MO0EhvNwzrsu8Z`j!f)^U*+$iSD&IXodtucc3Q +zn7a*k(OCxzIkQd`JY_j&PGNA(gCR&Q1}Ad8Xo}yIDY5&Y%$qaAzV>lO_P%ueL8(LR +zQYflyt@c}G3wt_#DnwK@>$&j`1Cm~T~=j&l< +z8q7EH=>Kv{?Wd=6Y>%Nj@b3=vf+st&Vxbh2$j~qC7MIR1%)+m4maP%WS(4OZ%r?7! +zbX1WPtK^ut1q=k_d}t5{xHP?6rV5jxzs@@kW=J5mnlHHH$r6dnE7k2 +zZSe7;-(n6i>$7%k$KKNDT@9+$g`m&7I|oM6_y{G@byF@JNRy028DQCkm&q8|rec^uz@3*|4_zT>9)NI$64p5+LX=+<2Gl-t&P9HsF;1D +z%B=E2Ca{0S_i?=AdRR^d!cc<&^9VV5zBxb3VJxcosI<$QQM2~2*%A%2_CQK6%;!vg +z+e~|-mcG+}*_~0%6lN0h26+1SqAjm3=1(Ld$$pTqqK?H7+_AOW57^&0?6cwY`1JUH +zKaijldhVL-r))1b1GU0zhwq1K6&zR>$h-&IyC{RJk;>R3!KWA0s3G>VMM^~@Crn2b +z1U%cjk@ingpww`NK)n7_>9clR;Poq2>GopW1Ku)|Nj +z+Vf@iEb^jS3%)qiUxUGGO3Y&SSDencIhIpg%o^ +z5SrK54%71{aw0@RC-ZQiqnX%#8()-HdD?wR1({O=!T7MOGU)lppX6Bbwj~c4d=_5h +z1PP%4l!r?D;K9N&B86T7!(a|or$J|`e=FI;b)s1*VX1LVpc9VFb(nno99AfUcouh` +zq7Y$!($0F^Rj2gU;`|&VKjCwlF#BKl)uE2NR*wf$^LS1?gAfHsEB=VU!8`&gC~rc( +zjp1TuYt5Tx=K)ua8{)HEE7RP$rW}@EgK`$C`v2q2?C{<`3y_tl!;A(!yh|=w5gaB{ +zn%`c~zjaX}(dAE2?IkKq&9(ofvy^R|oI<4Gb{5OwH%JaKo_9_#%G(%IkCUrq$+Lk0 +z#E}37oXPn0{TnJ1tr0-`cmy*ruN%_u>)%ShamW+|Wu{&qmD|!MR4^}k*^~U~qNPQL +z`jX9P7oiz1)4#s>(|EJ*Z&S0Xv@3_*P=n_6XAZ4*V4S7-Ni-$q)75H9P#`!MsW(t+ +zY?e2&Y@kX%t7J|8%FZl^D{78oV+r(?lWm~t0`8KRmT1H91KHG>`i+oNS-XN(BLmaP|>E%G7bz!D3i4hZ$Q0_~bcj;Bc +za?%hNaFP@oCYrGA+fudB +z{PH#n_Q<==j(6zkG(qw7g+X$cTUo*!+D +zmEZQtyvvsY1GwheDf*J|0|v(*{LQhCaWoR_q&#$NT@Jl9mi +zijhkLFpUDzxdRy4!F@if9`fBo6#qW1yw0%jog#ECVX;v8kji^V1eErN_AR_qaJ(k( +z=|WQ*xW-RWtRpelx&Zab^W#c><_kvFBVPg-5`-+@ifeAoPSIfQ9mU&QIRkHZg!J=( +z7n-j1y6z%B`44q`wDs!2R1K`FbLt0~R6QN|t(+JqV0K>--nBXdI7>1QqQ2N5qwhIr +zGdO@l#(DAHb)kbfa~S{o8KLe+1wW85@VT4YhEwEAVUy^<0WZ`W^QzFXzTlh~<61?% +zt@QN?sNJfW|G;kz1Q_BNu|oc$j|dqogB|Fbok45purb=q5sa>%##l3hC)XScKr!<@ +zQ~_ +z_eKnU707Lva$>b7L3kjHw9_8F&Nc)S^2z(d>8>gH_OW!)_QYuBhkCGl_JpRmo#>gu +zW}s=F8ED4IEhZbdjAUJhftVVmU@;cL<9#x#V2PaeGe5Y+XV!jK`J!cY(>i*A`81~< +zzJG^dMJPNbo0g_6`vyFjJ^|GIdsBY;C!v8Pb9*5)b?yfXa4i+-s2VnKzYF +zV?@opTYkQ*ztiy4M2TX5DzX!!Dj6Oqcl0q;S%$|(yn-K@=TV8@Oc=Ymaa4mu(1kAI +zKDLw}Xa=wQM!q@5G?D!?-fJfw=m&DImb@NC_`w^!V#OZoIU0=Uklpy1eH>Mb_s&5} +ze^xH40ayfj$v;)M1x?)O%FO3V6ZA{tg)HOIG!^@UEOGdm0bML-kC#qFhbza_u~o%SYfy&IWdsA^k-F!gRA#(~ +z9eS-AF8!X>1KW$ASI>?MTEFaL+|`GA%0}1X1KPAQkLd-F4$E +z5ohTCNHwQ^pvLOI3>FaSSn+S0NO*V|#)95U>bR8~=+K{3oGZn~A4}ZyoA5HMGkCX3 +z!7ko6TIsUdywGqsDVr=^0KAnxZoa>jTHw477cf%~idv)ZnS506TS_Vy)x>1~Jd%{K +z^pP%9E#}5cs>f8F$Iup@q-kRxn>ELe&}yB!b)0ptqNvMG2`MK{R88~y;HMUNbqBZm +zAdw*mNVHQ}aKTH$LPYK3De2xC(i`f!aoxM;Omjw|uULsiToGmfX)p<6hmtbBetUWQ +z)WzfE%D&g>{*~@hy3bsL+{d()Yl3a>p|aZpf3_Hj*^aynn2we`b?GuBOQ#%JblxnhOwvnFoAw-|XACe(^ +zXIFyY#&D9e0}VX2W%4o5pV#gyI{5iOi~~q@en5{@1WeS5R;?8*J7g57)&22p+yhcC +zh|UK*ky*EAz$gV=S-2MN)cI?|%1nz5l@b!~0vV*33NX +zc|PlNyzhJQcT`2bGpZ>fno7e5Rz~hn)%sr?C+tz){?tEL-+95=krARIs)TI%k4iq5 +zpSTk!rhXlW{9`6huGBa@{mE6$4VyNygW*^sw3R)UQ0*dV}%G5^LK@#tKvSsUm{@*6ls`%0gGRg0YF5Ylzrb+CLl!;+hyzv%mQ*pQLV^T +z)U))gNY-5tG1wS72>J|=>)|2>Nad;Wv;=r +z35=@yrT?&yvTflnQ~=3El=MS{7>5Yq`-=RXin9)BvVns+V0fY%Cl;m8&b$XmSLjTX +zB<+ThdwI7WExI4fI)3xl^Co?0evT*FUnxINtc?o`L>Wv$Y|M9-V16Aj-WtPrX)de4owe-` +z7Ui~iBM3)jBz>(@m=~MCS_j+xIW#QtB2cmjOSZ(#F^v=ZpkrHy%RE(D_L4QIIl?ck +z0~i-E6{e4NzD;+Zw9ve)jOatYtvks-p?8aNtGtX)IDvDNVUO0>5>0QTMRWkW4Cgwk +zWm*H@%d_%V_zO`V-UJeq&bV^i|M+5sKf_Tn_wvEEv4L$Xl9z`6!xU1oKsryRSM^}H +z-6So5gDH?+xG;Siu%4y20_)l64iEv%c+WtTtnNWX{&-D0X3(@K_4;`7>O1zvilUWE +z;GqQeG-VHB0s$=woYX)T{T5L2GA!Sb099FA^xkt;lv`P|7=7KWQC$aJ-fChhREEG?ohliuwb`GGPDUMs#O54-}6I!a@}@Z*@jX{ +zqU|KVDEfp$6ZVSY1Rq+=Egd`1Zvj(vMdX^y<4}DgQ}rELVj3pv2U90`$RB(aG>bK9 +zdm%f)uC^8r8z9ocEByu1LcvW$S}4p!qy>g}K3_Pv?{mf9>w3?LR_=vcrR4-n)FQTm +z>=>{W{7wS8I#MxER9=);f#xGlP1xgyc$;m{%o?+*s1^mTK?JKq$Ee3NbA){> +z{21q;F2Pu7jk89hI}pd;xn3EFHnUSE4}nxAAFnnn{#m1>2jyX$#5!-t!!&7y5UeqS +zm$OsALDU)zlhlV!9o)m|8>5S+*J^Pt;RS3Xm9`gZ63pOTtUN%-`3w<$#72-?LHVQG +zuv$WpLGkdA_-7{tNUGL?tMYzDrBZ%=r0p82c=;op{NESDy?z}qgn-MT&=G$}z~dTg +zs=GkS8N@c1k^6)U(YiZa67-E^{b>uNo&Q!lLWTR7rJdrdU1Ls8DS6=poK_|`5Oz8E +z!2Q6v05#@u%}R|m880NJ76tU0Ls8=YK9a8MlsGOa`Uj$BGvlF)0ctOx`SMM$8ulf# +zFqY!W0>VDbMM#w-N$O6b%Thq4YOW`snhmRT*%-oF@nGqJt?Sf48Ke396UX(s>kV=s +zfutD+H9L2|^>fJO0D@_^C&LQzQ|yTUSQW5}>2V;Sa%dwMa$`x6WTiESvtA;H$B!KS +za7us(9da8wu?|Q=2qAVs?@0Rcz$?x@<9TmbMAV-^mT#@y>fWHav-!PLZ&CR|EYp)HO?u2tcBn$E(9CyUL9eo{isV{gC +ztfljHVpv{s`K=?JgGzFUo=r2hD%@6tGDhC`4Y0?a>MBmUdxXm{k9X;881)F`ljn@R +zhI|=T=tRl#CF*}K_;MH7ci}k|=geqM+IcXyoEc_2Cqu!0?tGmRNDrk!o +zdEZ=iwW%GRIRUVL{hsH9I6QSM@@%IyEc-a~jAGwqgAi4c6xv&~L+rgCNb|wmBmmgL +z`A8zEUrCL5^k03OpVvJBTD@lqqSeQI2SBmPXX7 +zBPs2)KR3;T15KPfAuV?d9Z$gdzZN++J$7{bc1;Q`+ikng5c}HUY)B53EX~;?7i-NF +z6^bd{9FEMw2OdjkGncGj>P08OBm<21v;?HZ={8j07rEW5%ychjl~&=;wiB7AioD&3 +zdbAE?wNt6eU6zJ5vP!aZcEC}<~OLz?9Moed?~Q=zBzlMu=_6=ACMs_a0wvHcau_i<4;%LDXrN` +zWWGVSd5&?@4umLg16u{K?D{>QFN2cR@qA`=!q+8pJ+i&tFt#Ue=2ZW@e9fhW)b_19 +zZ27`JQ*{DF-g%c-j!3W*Ej+JK6)ChVKjqj+Q +zZ1IPpS`_S_mJx8gC8oO#4KMeni}i>J{}Jiwxg!ZZ$wh!bLh}(j(MnNxma(gJXfHvQ +zQj@el5J~gQS!jkTCwY4VEGeg`rRc557~=gh9M%Bs7U!*xy@6A)$0b0&o7?Zu%_0qW +z`!vALb-+l#g-R=hiWis&C^JOiR#q={GY6RJsy4APLVNE(RcO@s+=Z4gaRqVl%1TAF +z1_M*g3cu%=^F$v`vL%jwX*LI$C336A&DJjVK*hsb>TkCWV>nTrwl#5=d_b$)m?OCS +zI0eXS6gdK2Vj{{OKLWPTN00nklU9ms+SI`Ek^Yh+6T@n}8I+#Byjmp(NYoV2{alrk +z$ELEI*qyCHYzD=nl9uZs~Aivu)Z{yyuH_XF1jy@Q)_r%}VnKU&HeEs+>8ChE0lVBk)FoQ0BqscC@_F_# +z=x~ax?({e)hp?GgrGvVY!Q~}0H7|EK<1#rieynGHHz|Gzh3zdQ>y&7(bm-Wgpi +ze-NhC@9mxdaPD`vJ`V~oWE?9q)6Kaegp?x>QWnVKZAbgzz&mVFUMPR62oOe1cav_@1$aNS{~2*HiNRHT%s*EtVi(@!8KsbL#@;(TGuGw+yZ +zzVV=rMbhoV8#9Y*R*F7HU`>p9xjp3Atrx+suf-(1-1AXRWw79wXQJDuLbIOg_9j|3 +zIg_IXxVM38UPN~c;Wgn025S#-OGCH|BvwB*wDi3}-sT)5%lJOtYrE!G%ZP=UEhhJR +zyWL&34hpL>jRR!s`(|9h4-{9ELP}!zTT-L)sw1nk!T%NT2%kf716Z9Dn)SBWJUpB) +zX}5H1edYkw&<1>~^QZF##tE8V*ovH{39mphb`kOE8T-atT<10w+3ivdIeJoqo?8wg +zp1AzeSPyTMH00^9FG%={QPdoZ*!Ck%}8p=)u +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 +z-YIAkHCe;zJnt~-uZ^?bl$rG2% +zKBRaR-5ju#+?hYJD!oTQa)w#qZc9p|WtDuWfTA}C|7Fwn-IrAb#``h@U}jmZoRyKz;ie(8o)=PZ5ue}3)kJm#R49FH5e +z{65Fh|Fw&Ib71BeW>_2@5bKF8kwV|uDd&|A!{egQbw*~IMx))*j|o3r3m`YLmy(up +z!!wemay2i0IVrifJ=;(~Maly%Nz(kv(wZG@T>3ogoz0rKBwg)J1M2a)^7sU$c&XJTZp`o%wBoWy^&c*8 +z40v;1diLw%?H5{EkA05{>P<;!8KlqKhG!@_{|T`Wvk)ud4@nj=d;9MmjQDG?wr}S9 +z@G&$rG)v8D%1?SdXqr|2w$9)4_ywm7Fpc!9_a6%%hylDsV&!m#%XQOFmo}t~oC4>O69|lsM=YHQ64$T8}%PqBRpzKHiU*P)b^~&CV!3(i=U|sWH)hCq?5YE9Q_WYz5bB +z33+4kxbV~09Ik5y^-)sVH~9{Pi+$JB*Vo>x#d@=yBsiute?B&cGV(?p +zA!$xe-O}lsCSE^cy;i#=MSi+#)7*0hHngmI>bT*ef4S6QAN}YmSjwq1^n|``Xn;MZqT=f1JQ@OVq61tk_0pIF`$Ub?2TT6LbAFDz|Nu1#)vAd1MbJL=v45k5|yh_QrA()j`RR8mV_3 +zcs4^;HMsCp`jWkQ9JQOX_gHQmo>94MI!1@#6-W0cg$q4;`I4SJCE|G@pS31`gzoqx +z|A|lu+c**-*raIsAIzB-V9?ckeJ-m#J6A}|35!p%EtvVc{f`f)eiDq!*&)~TBwK`~ +z=!nCX%p-fWsw{R`yks>jr|a-1bfc||{D&~b+#66>rD?BE!^&GM+L9V((`~5-KmUk+ +zPHc=K*PS;=74N_qag8a89Uj3dg +z<(SJ&6J&gQ{h(51yr>y}==o{b2OYEzIi=exU3I5icnCIcuWZeg-%j=mnvEHA)tBA8 +zcEY4ZL1I~by!uoL#+`T_-P)ux$7rIjtZXKKh(2S`E?Us7Afw(nCa@*xM6P+XamDzpCT#-OXbkk~}h@OJV@J-h=Q!T6-<@Gq^|VxLoj)+AK#(!JWc8inN{Ck8 +zJN~;%dof!GAi0jKCE_#!IHZ=q&nGu$%T{sE#ecO)m98v$9kJS)uh~@NAq?-qY>7~Q +z4AG(#yyZ%ob{;9UTdh;`MVy+m7gnA31QY+-%7fHl`j*m7M=A^%l@f=%5~E$UbPH`D +z8(1u#lQ3BwHIAb(!tK +zNXx=OJub1psaL&WU!E&JC=lk>1yFDQ(ez^mf4qyEaj??c^2a+%Jjpo1Fit+-xHZK5 +zr+g^C{Hvq#e`@6i2$|+(p+w9ZaqG7`bxY%o(K~0;?w>OCq&j$e1_uxpdTx>qGbi|) +zOvi_JliiIO_Ppw`H(5LHIXR?=JUpmI@=Cgsrd4R;&7NaE-Psu9&5gfCOq$`>AV!CT +zB&vM67DFTb5~~)&YgQ1S^H_disMw(~>q(<%pQpA%jIY9MsX+aQKVL@*S2Enz%MK@B +zHv3R4Z9A2zhpEORi@h6x#iyJ9lT#h;f!}G0cBx9zD{<`n{%3q`C}6d1tSaRLt=f2s +z#&*#XcTKD_E5GDKUdPkM*T-t~M>`DTeHr5a{ER;+9db8gCE8Q{T8vrO-HG4VJ6-VR +zm}*VR{fkCEnArRY)!a~+u-Hy_Y7_yyPqe0`s>Um<2OP}~6(}FCY^)1!ELl|ukQz~nvs)Xe8FE$n +zP`z{AkxHf%fk)O|F=69x?z_k#tT@T}iaOioJF-t`nx7727QH<>qvAF0rYw0v*R2Gg +zx%Y;#S*KiL6hFJACo^VY7ebRzZlw=#^z0IAmA?|M9WeDk~_&E5B4-i{6DPK;?0@ +z#A=^gnd9orwDNq)z06PNOVF(AFhl0 +zUPfNd2tUCfK2E^Lxo*b^J&I!zD1M>7(xW-ip+=_GO;G{Zd3X~o!~YFC*7)B0$I@4*3x`}HtBhtXRzRr<3gk_cFn>CRleA43h0 +zx2I@kq*rme>0oE_W50G=Db9}gf|dV{jVEu%J3tgu;R##M +zYpL(HoeOv#I~wIgO3i1psy-pH+^oh#Dz4Rzc8e;;GWEuM5N%+Ud$Ktlj87zD)w@iG +z=rBUa{uH{|@!dh#F8!RF(`h;8xFFkO-hUZ19vN-Ep?RcZ!W@XC)#Tt +zi%izdPm$NnW9x_8cw>AC@z&;C`D2zJ)j1p +zf`82_TZ*rCTiZ^iUJ&DVe(wEzO>O)``p0*IJxqdstr-9Ki@$O(Th=O +zM<@4C=)pJ4@;<>z*^#8!H?_sA8fjXWGgPLo&C|~+uRrG&`BuZjGpl_0`P3eFA|l}! +z3p%n5H(7_yxdea5A3LdTJ_jJax;{h`W&|8f->A<9x0OkL~RF +zX;f%@76fGbC2Qe)Xorz#)CN;1kwK +z=3?E|yUinQri`=2k{vU0md`XDLp;AY%-S};&+s1jv;(NlXcX#akB +zQkfRnjDND7Y;|A?prRL(fl5^aFuUX$PP;&r%&AQiU{SjK4vcwy0a-HGbNS0&%mOod +z6z|b2;A=%eO%kgN +z8JVR>XLGU|07Rm4^o)jNihA;=pi{CF?T-SMX&k0wyaAg}O3FwGTrzqKkqDJA>j?YW +zpF!5}b*6CgJOGl|g*8ZUS9rbwG4;H)#2#!O2FS`!Iy5%6glnLs(XEG;77smffO&=k{9X|EsCTl8bg(q&*OAl +zq9_=!ngS~^0c+;I{k~sAhrHh4HnFwXu*%afTD)Zij!@yN`Z$<3n`q#DW>%SC<)@w! +zBI@3asv^@b7vfGZCbzS>t__6WnbtPT4b}X{IVUX;zM80Kf3+QxrUjQ=Ha^W2hR74v +z$Gyc4#vNJldT!gc8G}dm-p(;Dm)j<@ex6@U%=g@V*`i-2bnwn8Q=eqDU<1Zex8xMxA&@RK0g%?kCL3#*W9E@9 +zt2(Xr;DzzAkc0fukR--B$I1q@9+qVrVES@6%7NRVTadU)l}-FD%M$>oFtXO)3z57tG}L<4I@y5tO=DTSSHq{rG*`A~s* +zed)dX`Cbs;oq#JFNty_PqHSC6lmB51!uKilMjw~bBI7*Z!YiAnXO(lGpVRm}M#oiz +zPHi$xzsyzQvgAvK@JDaNxrLK6_yzM~#n#f(h_M-mUCU1hL|R;f!}H~F-1Qh?&w~=@ +z`5(;2zdw2PI*hVI#QJJ_pYGVj8;PFRuf7-@EXoUCOx&uc-ccN)i&KrcW<^$Sh-yUj +zQ}q=Hci$Wf2K@wVrgPgi)N~~9GoQahbp3dGjrGv+OS}%=6F&u!ZMB9L+9~zMblPc~ +zV>&#*n%AzI7th~%+PxkYcEt!(Z%*6uUCZ2Nn-TM?qn}noAOPyDz>3LDp}|N +z`Y!+0U$dQPuIam>Kdm9T(u41-%U>)U_|3%?arXX6e>Ofk7B0w|0T)<~H+@ZOE^!iT +z`^G9$gB;bh>4@9+wpRTwlI#QchRv1f)Tn`*wp`jS8_Aa}K3+0?zto}$nb*MW{O3K8 +z;K!wXUD&HqOnIyg*w^H&+fETca|MHP^V~qW#);eH-B~@a1YeE_`XB$ZM-@Bh1FtP& +zR*dmG2q!+5dSw~^vpjz>;H1-1(!(Eq;YVz*iM`tE^P|wV@%lmV7a(WhJ(E>cgSi3w +zyg6s+K8~RyFPr$6_WZANdEJAQU?qL1lx5FDMvi&ffDJEM@>czUxc}!b#8@|(!^i{3 +zKVJ!!CIRMcnn<|aVGaT|?~3q)htV!8^E5v9@)^1Twn83`t3qtN;Q2{5;4$ybQ2t&F +z4JUe2TEz2;S{E6qd(N8ZKp9fF)Tp?F$nJ3(IF>PY;a;m?9)2X;OxdQd9@S09%%{Z)08G~j9JyZA?CAKLmIrk9GI +zCm1q~T0e)s1l|!!4f{RA`S@P>8Jz#IQ)%}GAXk_zt?A)&Ym#FNa`#6z_1IHr)S%me +z#fbxGlGLXhvXUEPu_e0~s!s)ge@T(^ndPSAf&rP|NcYlmNg*&8`kYN_lv?c`qU?De +zP;KOYhGQs&KrmK)wqz8%CG%5rp{#7&v3aI~ZhVJ-jI(Gj02)Hz>oF0`vRd0?^nS66~EEzyP#5!*_!EQ4UpOKx?n^pC9YQt=#8aUy1MwV@?1oFO8*OD=w+I+&!Hp$Gdi^ahJNCHrQn +zjo&$QH)A1BbjZe15Ql3!ZQO`{^cHREQeK(E3+Joos*A7}b?&~ay-p3O_8|hAxa;2Z +z8o}I3#k*Bb-%Wd4`6iTD6s(p22fxvH<*x*Pkz3waWx?J +zZO%GDs+71s%QLJgs%CsimuSe +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%0Af|suGCi#I1*wEo`ge=| +z>4o+KWKs>%JKd@Fp0ewJ0rMm?%GNv2ii4s>u7!#vuXXRRKdaYCb+XAuaoTg^c +z;(yrwBYrUzqt-mPe>hdu-1Wxd#ht_{J9WZoDzmD&)WzwXekr>5f1DCM#;4&A$rksO +z1|MfE7Be>gh7r@x{QstR3xJTf9SJwwV8C#Rn2P~ihT%l8KMHy}NR*AExnmzGDA~#! +z0gUb~h@q;G-WTu2R3yAo>wlarEk5wObU*GUj~yh@yr&Jm++e|zN!VFP6tkZEM12Y4 +z&YWbhr}o=665^=@Q+%m)Np`_s|9A_kIh{7$u#$#^op-1tpX>h)34u@jk;JscoZ#tN +ztfp8A;T(Ztt%r)|D_Nl!(05&r*PL&+>oU8Hf(T#h`(4r2eY^ER%<>bYwQ((}**hIe +z(OvjH7if_H&j{GaCBiJ0Czko_kItWFJjDq`h;Pp*Hh(qKmfE&ik&Ylo6Ft92NjXvW +zPf#L_kP*oeVo7Yky9Wz^TYNc8>`-*uabkltcFXe+;ta}PCSUWSK`)&}8XtLW_3&`Q +zlD`Ep)~5hPJl3%$WoeDYk@{ued7PL6{FnrcD|`-1kk^JFqEv7*CS6DtGJQ^Whg>AzYVG9s>%ID7}t_ltG8m1V!2r=KTy{gxkuTk_$>YTJmr2k*qnLpjPTMe<;gyA`v76zS?Cf9@ +z*v~+s^v=4nuDyYCp9XEy9$Fe2b?D~8orUgC+t_f}JUr6tU05&PRC5#VeNUOwW$8{r +zAmpj#srKrrca^|^8}9(yhz@q(4Nj9s#6R*kj>RDgB`RN{ND`B0d#sgp8WN}}9WM~S +zfqPdxO>b&+X!4ckgH3_$*97!@ASDSB3e+`ycV2fwLlUGUfyeNr&;o~w(WgEaC+5K? +zG95X9JC96^^InOg?NhmOkAqjnG|(Y|5+fZFnTLyS7%B;F0~K)z!jQj;>2nQZuO%j4o!ar~O30K=wTTx~X1#39Sfg2sZ{8d3Bj;rFwjCf|H@ +z)N!_?I!a0Ke|XLHMNraUnQtyih>bEJ7*5Offh8Dkebi03Lq*m^HG)Y&i3MDhYhBh2`{l@ +z@z6R;xQ=gj_!PRYb|Q{&F`(+lNUcCdGpG#CU}T&V#0|RF)p@u>BM;*q@#ur8efwXG +zWh?*)z##OM4r92kjEd)pXNva}G}IWv(j3g8)rul|l#2+_uxWEub9JT%%I!lDqlCHV +z%_n)du@Xa(C6Ufh;RLSE6ffeohd9yEEz}$rF@0Yf*3f`wXg&%UtGC0A0pCg}J5V4HHn1K;3NIoo@*`_AOViqldQH)5HV4$hF&BNV7xI@xsI}L}i&@nM`lNctV8D{EvBmGc)KJqf!)V1}AX&8vaC&#V-sI6#k7U +z2P-&NB3LQKL5qNLmCwRD>BYckG>ko}{@qjIJP&~D;nz*eD~iCoTe0)|sQ2y+aY}*}3pxJWkGCP}CckgC +zsuc4U`}_M7j7iX4xc-+}a9jVWe{uE}l@6SWaEcT*he%dSa +z;gXT+2v_et&vEBHd-jA6uwdNRJome>6GCyfSFB(Q+1Zgs);&`6arGfxXxwc8kp)bm +z*aiDBl_(`@cNVYgPsH#$15-)u-Lp$pbDOY8t4_e7813f;XVelN7P7)SoI^SqZ#E+3 +zgj8-hVl$1VUR&^suercN#myl3ELo^e*ah@+-UG(@HNHwLuYdP_-vGH%wY>D!oW~XD +z%+4EN4DRbSX7)u7Bm9$8~QeE~qWZ>4NJ7lL^ +z(7%FOtM$WWlj7iQB&`5yzt5Fo1BE@}ZN5qjhDmCqs}g7%N5c)He&@F7rH1J_)})H* +z00CGCt#-=ol_N(;mjIs#K_pB1MzoK71HXDu*)6>M{7-FYZ;dN8n}vJj8}`)BTodFP +z_2MLUoGHm-#`22+I@4OvT-weQjDk??s@@NS{%OB9)W^=qfu8Uy&s=|dd)BQDDAGxd6$zM0uz~Kz +zv%h@op$$r~dxzK8(*#Q~nxRMW5%ET} +zN6*gjV}VKbBV#(4(YDNl}AUfs?<@-VFY4cF^`~EW%H>hud +z%8tueE#da>7;lag==$}hPBHfS(*&&ihusIx+mOeRw{*8%{SBV$)A=jrx-Bq6CNtdy +zdQYM!T2dzglu41+;I-w7I1|Gd2s-ypLZ0^D1$Xml(IiW*NgFA3N*!Pdb+jd5oCMj= +zM}lypZ+$N53B4cCX)?`F2TDr8tc6zgQ-95lQD~_*1)jLBp7&fA7TfVWhk@j;HCiz& +z1DRH5>s2%>_03=O8D6zh*}|y5A?r_B@p&gfLy4wDHK(cpO`qt +zO(4G;0ilM*N;2q8!Bap<4k%lF%RufruWF_^aer)Qo|Vc)^z+M4YD4POi@}PVLS2A; +zii1hG6i||Z@nqG9!BWYE48f`-86L?uK5vo1r9s(qS1CdSsKV6Z+ABacrA9$J&j+b=Iw~~`C93I>H8=vL(*is5x#Sd#pHxk}pyp8NDYONXe(W(Prf>gQ +zP^3!Y6oH>U3;0)=9E8i4&3kozzB{+bzd4oDgmWD9<$-=#7aRkx5#m5D9Nw!`5%*cR +z*=T~x3HkjboUmGv-Sj21&Py3t%FqG5{OlJO?9_AERs}5@&Y@jD19)VtWkzFZX9&_*k&( +z1emkvOPyli7zdEl14~>1Hsv>1HMAz&-+rku8j>8U +zNQiDKzlz2MeOd>9yt0LUi^*_x5CA&Ngvo^I+J!@NV-$$ye6Q><@=V3ZX~Xww-~YP+ +z0-g=@T1?u%7;FINQ4*|69CAhtO_W@t=?qwLWC9j!sS0JI44a2cZ(A8G=h(=BXHoyrE?Q!=Rd +zKpcMr^Kq?sEXS_Y>S2}#N;;+~QpOw8>!Z<{5cG2&{-A*a>&SXy3mggPL~w9me&rI0 +zsluJU^~QUdDHlO;3g+1y +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 +zLd#>s^Q1=Wf4`S|Ke76as?p=17%%h{ +zhWk%jQXUuT!^}a1Iovu#fakJ#6ij-4+Xb`LyyThgY06QDQ87a51Pajt>-c~F9fofr +zoQUttjfW5Bq(0Z()xJ*|YoOY9gok^X0QHzCHLof7WVMxf*}3IO9!ReL&N=nD{x`Vo +zMFWmdYs_=N=>1s5$k9JWm{i5lIZ~b8$ +zOG{}e72_Uwj~kCWE`mrOrm~%8LG2BL8EnI>+*{Knd7_xDbU}bNR@`j`agWFgDeS!b +z=+?hJ-$#mO&|7Y;gYp_X#r1#MJI|;lvu=++m{%gG;D`*PfS{;E7{!97iAE8mO{j_} +z3ZgRzqLk2U7(q~}g5p4eAfW^cBSk?eDxhGABd8QHh}1+$qy(_eyU&Bpy?3qme!m~? +zH&`nPC(k)&pS}0_|9=Dw>jj9HJuimEP$#Ah`ayCNarFl!8w6*Zcdc!|<>JXKitU~< +zFh=yhKAv{}3p$kT@2O*7+vunwf)-OIGngm!A(9Zput^TBGY3#0g88Di#W~`J7Rbb47=GlZn_hIE)85s~>A@P&nxu+948maV +zey}%_Ht9_N#Mmr)%Nb}G*bTSzjdG{Z67*~DtZ3FgRU47-xPL_6Zsk7O!tK6dJ|J7b +zO6dz7tJ#D5bXW9qmN4y!q}AyA5oZ5rW*oOdr`25mn(2Y?<5SnZ=sBIuc +zAj-$F^4M>{4h~||J4Cy>$mZPI?r^YbmJVja+{15Y^}`5FOG;4lW|QuU3hp(5oRSR( +z9(hV!cJR`WC~;3*4a4)lAWtbeG@KpkgAe3w;ekG{ +z!b3+klxp>nQ2P9X;0n_#lsZXgm_rD{BJC+GJ&uv8AV<;Nb_tgoXn{MSW6R*9M}Kea +zbn0t3M0Dqr-5{hdgBA5!xn|U2w6YtV4@cTy2Wx~L9<6j~EkKST%%Yv93pb1R@X~5N +zk>r6!9SYu{ZMmUeL%`Aj4tvGtsXIdD(sNRoblr_an3#}N(=TM`0Tl?U41&B_YXR)C +zX8?M_NdYK<%sIcxADZvy-jN=XI(U&)+m6Z-XQ=oo`c&I8QcK#{yUT +zxwUq!^m#yGY=qPltl(u&ASF)J0;>`oC{MnS^VDFc+T&2Oz5b;62KRJUI^huVAi3rN +zg6Nm?+OlE&nu$!<0Slv!*Ep>gJ+se0t~?=cvt8gX!4K5hdbv^sSlTv+2cBe{m}72Q +z@FWu^(eBbYaJa8q2a=U)xdj(>xoceA_$q1gf?#(wB5ns_myz=$!3R{A-)nT_|jnMw*-8r5X +zR5Tz!?3TTb;+Ewham9g0y|K3fU6=d1b +z_Llj&{j3ueU`jcwsr?c?87Ex`@rut+8^D?NPu|!>zku2RpO{t;=*HYSDq{|ME|C1cqY34uKUPt`Z1OKXpRCX7F3TW(G3$!9Bu%;tZkG5jaEf +zFEj;I$nj%p-;>;^#CWtM=hf`c0?&TvyR~r0od_a||1b^Zi_dRKA-FhPKHv2RIBX_5?yJX}R;s8<%o+=Buu2FG2sjGgiOb)5 +z{0uDK$78syCSW+T+PLEJMj>O@Z0UBmj<`#=TiemBP``GGBA}VCy{|h&$lzCpz +z)~KK&MG?BTNzM&4N?2d{f8Yg0UtZTGdfhN!ns*YKm0=5rx_@d$l0Abx2Z +z0Eg0O%GIuDe`E*50Ed-)k5-gzi(>PSs +z(GCD;qXk+`qktCho0-9NhyPGX+sE}~5{ONz5Y4f7^~wnKWSa+YSO64Ev>OA{%(Kh#-Y97#W?spz;D9BsiAm@jWB~YPGyhLW#_G_r` +z#nAgStc!4(rTNI%kn&&wTmonIHt^1^e?e{k_7)i^ia_Sw*9>tt&6)|sqOSJYmv0;s +zXydJ^x0>2Wi3ti)JUxUI0RUkzmk#DYzyis6NlkThw=6|vaq`~&hC|*kSKW8y{8Z6k +z=DzQ62VzgpoMKSwMLPs`t)+(Pakc`^jQrlE*0+;pO}i%QzUJZhvY#`V@#{Q|C-$L~1+veTNj|0k8m;mg@_>W&l)eLg* +zmJM8}D?)>L8L+!-3#GwVByx>{I+l(t`;^ORHKsdL~D}84u|3nm22qo +zlC(3|c?lN)dw>DeOTQF8R@(Z&X{dpB74^{vu@BJV!5BX%B!7qn2pXh@2Ac-dz-usQ +z-JdqW3`STRN(OzJFJ}{T7dr)|7O+twfU&iFP7b9%9lqiDQ1pDBk +z$zKx44o5L9C+2bXD)57}aj4rYpaIFH>$U~HTuFOEJIZNfT8XK>+*g{~T4*6P%?El; +zZ_E9}G|*H*(i`>nA?MK3KZ18h7=L?FVVR1eIl4AhqcCbw>Xz~=XrACqZPtYx_Loe6 +zHE0HL)SOn&@fhlB81R9-pjB=ZUv58-KPERHssopBoED0!S2^G;ms$^BizAo*wW;vz +zSSH+fyDr_7G^=kt+E-3q16ELucUS$Sx5{}44u)1H>do9wLcN)$rET;KxDs^V7t6}C +zpfWb2_%ts%!v!`-CX_HY1W>~KS&uxGP{N#@>GF5oAT*UhFZ)0td0evx`vB4>jIkZu +z$Z#+qD=hSCs<%8ZO0{sn+cHRDq=BK;d9my}0|)x}aL6YvLI^bgseSnk35ALjIf +zo`H3bPN!pV6azI>?7)wTf_mCx#0@el;D3gWr*vLF3wfdG+TA_nL5uO+ZSzC96cqmE +zRk>dPQir-$x9xVPo}xSyoye0H<7F&nfEz18Wrb{K0EWbzU{~I7ga>Cq^n#L_0oW!5 +z2_TzTm+}ULOSb>2^#R#k5a)b)zzIqr1w%qjjIFWGJKOM3JX~!U9s%?c!rXjoD!zN2 +zxXnM7bwwphAaEt#Mp8|zX{)rR@X>+Vyd9Pgdow*!R40UV0M$a@-_69^i$~_16J#*0 +zt_R$s5os7wYhnzgu+P4HYc0{!9Wed(wG3q9<=o(lYU;UQj6hYiM(;ku912>lO}_fO +zmJM111ytG~3uW#qvROin((B6Y407@289o*DT@G|BErG7jQ7Ja%jUN1HgPIFzOyU3y +zmi`P_wr@WQ?9`^YpB4K-RKzLdwvPvd!cfp@27rfq>R*>ud9Pa?5#UTVFTER0TmclD +z%5g{yN9Yn?w@qQ9?e3NKcjR!$2G)m~D`SP(Nz&iUccK0MnSU^8n+Dp6N^G282f5TvdguJb&H+**PuVSc=OkJ$x!c0!qthlk(Lu&uXdh9bvDsLGb1{0XbmpOZTaPxsbvW<+VH7cz}`(XXoGjn})! +z+uxJJ0kP1%jae(|0SoL#&8XM$8N4t-N?_3J$9_Ax?>DzSRbnFnCcutXX}nekGbr)n +ziQ^F?>cy)^H2IR2lgVaTU;7f@$$|!%L^{J`G%~a>m5#d6}vk0puL_=!0|7D<4WGF +zCll%5Y#76E%bSBgi`ehN1`vyLM3)0J`atOST$HZaL!Ss=L_>cS!V=(esuZ_ +zivwy72Vcw!VWWZG2z-8UV-r@Qw5+Ol!uiI%{dT?gYe)^2R$Q+vcMs(zwwi;=ay}HLwzPA +zmxe)n=z}5kB&<*%nJOFgnOvoetBSJM=hp})F9z>J?F}5ujHLu1lu9Dt7!hRg +z+NDb8MO`~-A9~y(s=c>B%2Lw%{e=|+k5UyC&m5WXY8)0{3J)9dR4x3<41$s4>;E!b +zTSBdO@Vrs>K)(iEH +zcQN<#YmM^vjMb19ktu3)>=Z&NKn1cUerbYb6x?`}J{MfDnB*?elqIOeO6R#J*gurB +zLA*mu+dceCg`r!Fp(}g5Sy$L(8o|s69oIiI$vZ*Q)WA&e_3!r&vT)~%S^fODD7Oga +z*hQZTA4N_J3?5%PRDC=#T0|C{_VP`SvYV8h3(S_7^b|=F{Jy@yNQ?39+X@*HUWVLt +zJvIM^YzDuZ-=dHlYD@Xr(2Q5Qn#7t?~cr=sQJ3uPf)yZX0UL=oFt~yIP8T5^Bi5~c6;V(w +zrSJEu1lVaXS+81WW==4^DTRBgM*kLE2>o=_dqGC&rHCEHWzYI@=ho`07L3ge+(W~V +zA`u>H4fN~p9Dj9Gkypjqx#?=IS*n3R1-07RM{2rnU;k +zE0~en7dL)jS%qd65F97Wz-fRy{L>!jbJd?PClD>*^6=onm}r%n +zk3jVJk|be95gCCf3}9Y4$y?6J&q*;&NICcOi0Pi(O#zxe%ii`hdS62F-?^Gt2kO3J +zf2QKZK0tXIw9rOpcAupgcLo$kKHModtHgeXKUvbhj=V7Iy>AJetdcrOqHJgH5q?yV +zs9{IF<8Q*Le5C33q!Sqv_}^V{F=ULl#Q03A2pR&3GT+|iEwSX-l1J53`O!>A7#-Xq +zLd1>^>OT8XW?Argm5U^?yR;+O?zFGnR(H{pZt-9y#hB{{e;lRM7-?Iw(K(o`p}hT{ +z&k=;_5~`&qrSbIkU*W!!L(u+&vJ>dT0ak+xjVj<^RgYY-_@ZB>^V}=kFYZAoeUgUZ +z=pDEfjtMG|S7r)xKaN^@e@+HFQ6t%+%|#qrp0-}tQ7@Om#izdUT_X7mP6ie~7tS&f +z^LFs$Ugdk94oG9=612n6vIU=g!%5VZ+A`dDOv`dIn_r(aWqXj538TiBKWQtf=BG1` +z3$OOo(>3z~qdfb!%+Sul-(PyWkoCgjc7mh-L=B%ISuGNY)o?qMKo_y +zZQEeagIm3o3HfE~3zG8YH0RAwlLF#vQJDGSQK6jDadv$7G2i{j>F(>E@u45HYzZ_R +zK9LxRZ}J6)20!G~{mm*(%`JJf$`69SvdNFnrEep5-u@oz}dm_L+BvL$6RK%)a; +z&}D$x88rT?8nx7;#l@`YzJ8h}8^yEKwa- +z?KsDVJL;h~cb|_|rpr~!g#PWl+N%<+ZxJz$?nhTH^7<3MS{=thxbgRk5MWH|X +z+$;>*>D&k~3!O!(qdg$9!rEr&bz<4%QiOiDe8%isK@4w9Fk5!{)r#mw^IzjOarnSl +zGYk+vEj~P$Q#UqR5o_kX2Z=FOH=bLXy5{t`8cy;#h5iu(oJ^-}CwM*NSBD_xo1k|| +zIrkWe-36Lw0WntAqZw;e$5KfV0Q#CPH`+vq3oU3gO>RWug?g067PR7M8`LiwV7Z;f +zc}eL4$eFKsY=nD>u=^6!T?W`ceavF1r8T*L>lo5k+2#hEuG<4*=30;6Xa;7k-if{!#-3*!sMMu*q2GtO?uks7uc@&eFau+4yNr8CR?0>T| +V*A49b#(9Pl*el9kM{g64@*gzxO#lD@ + +delta 916 +zcmXw2e@qld6!z>!XHC=W+_m<&8pH11VF*}QPsm}cAbLk6?^V +zYnH6rIOi>>MdhLdG%BQjaB{`OODwgUZis}M)>a}Kl?%q$TCgWZ!~|=Hg7e>d-}k=v +z-ZwLMxd=SwT(Pkjdp|wUIyN{^_rYg#d^h*Y@2R=qkhH;{+4p|tGN&bQd-Iqzo$XzJ +z?cmkn^7Hk=kn;L@(9%HS&(PfF2FnS>So@>0_0*-UEsuUaeE6NcUXm;j(LcjE16OJW +z#-E+H8?px4#yu!YQr7`E_2fIyWrA2KNyb}*%MRY*of|_W_~JC^l8Jn*A;Q9UJ8@MW +zTCOq`m?T?g^F1tlIri<_0Kk`jJ79Su*Qlghme_=+f8>cXVxUCdCNMI$O*_Z +zYCjy|>I$pjxj}FfSW`vFJeb(7m5Eqf^HW4453^=#+FW>1AZ?cA?Q$jJ? +z#MLPjqN^>T5M6$)9$Y{nn|Pgt1YXdjk;ijhJ7d +zm|%Ieg}}Yk`t>Hrcj1&H#GHY`fu;Z7jM9qqB(%w+;+;f%XoEA|cJb4kG6E=eu43PY#u +zMnnVEUOo9aVy*~s%E_w%>$Gu3R~S+&&vrkyE4^R#WVaXQ3=AuUixRDzcl%Mv%IBx! +z4R<%`m^1=-Yl0JDXfN=n2kv(3?RAF*VSXyl4+|%{IYvq%-71qqN=el}*P%+=iH6G&ULPHK1cS0eVGsTJ?7r`HJ_)C8p>}QeMc>- +zg>j)lk52_XADsAZDS8a!EJCj#nW)D$F`4dHgjt#7NMkZ{RXQ(oo{6jmb>zu1ONX)7 +zw>Jsq4Q_T@kgsi&kl)+zhSLxJHNM9EiQ;2^)0rH}joA{5#i&T%%&Mlv>5Rx{russ6 +zKGYU6r}6xK~YF7?$CD-d>>8j~ogp@mgc +zIF%Z+!PThACu?)B31D +zJc#K*d6Hg7MMS{OylT=+QRR8VJEDxA5ZjwY>1Uasl6+5I%!W{~^a0o5(axgyhn8&xkCclY8p^Tn(Opsgq=Ud_#@>`eDH2tb__AVj>4PUNlUDCOd+%Sl} +zo^G2Bx3ck3tE0l79pUVU>Y^{HmANI=kZ|@!l2gf0aUZJT)Ig5!RH}#hiaYmf6K7oN +zz1#H>rv}W!S?%-+-npzIOWRv_aqKbtze +z>F&?*EwpQO-j-(n4G5gC@Y4QUnP$NL>sFZxRNiq +zZ*p{+Q>saE;R6>yuTFx@Y*A+iMM7x2f!ja?37WEGm>V6nEPogmntg_Jn7N6?u{mp# +zwJ7pl$FLbbc8z|@Y>AJou{~)C5aRi%$3@Hb3T^%z@uNhmBaQrzx3=#dP|c2f87uLK +zgWMnJiJ$|vzu6&@d56qi(xk|`+ph3Yrj2Q($eg|NSVaHYPLW^vEe}wzy9fz`&Vt;} +zMnuRjd|0X2vKGg9fBR~&?d?6G5ZFIS_=Z?HhI9@HXGbUs@0(iJc$Ms2F5qvoT +zWu+^9w#iQYIr1^bb}BFi8&Gfy6gAQ;SQPaM32?LIB2rJMYP{SjtO78@M(WN(cW+Ls +zA~b5W7scz$pi~{c*_%z*vnUOwQ5_T*=U3ClfkJAQcRA*$n(MCqv>N(HH2Uo~q?yKp +z^ff(odUfG=%xis_Z(!HODSTl|q!_+JCeJ}NWD)jO1DUosb!jH-Nf{?jrj25LjNOB> +zgcm#f0|4Hx)1M55mR{jSMLe+}j+g;_d(#50BI9j%m)jijsy@qad@Hn{F#e+d{yi;X +z&D8B>8VB1OemgeysZCjWLNs^ +zux)(rk`h4#2=_WVbrC~hEYc!A-7HXZ{an@RaN8oQD|OgFvR1jQQ4zgg6%}7?Ved_h +zwv>I|g4pkNq&8vG?X`4x?7gU) +z<1*h%$2-=cvWWsS@nV>Tcg;dDu>pVoZP(4Kc4{9bYn%BpEhe!` +zd2O(M;>RqjFGDeD;~7kNlBt0thC+@I`V=^E{vEmITR~h^cd*Rh1F(XUsX7h +z@Q*hjg8IMX%ek93GmAZc0G)^4c>@@)Z8Nxakv9c?qcfm)j8@I-ZzHpMrN+W +ztoUcTr^?pCAp$=5X{V%-R&8#fp@rX@swU;+4AH5~KNHU|cZd{^jwmV{fs3CrIMwHS +zS)btNQa`*V*E;@atrrx~V*&XkIl80vlK~B`Jgr(=Z-vryih~A4Y%WIfN;GAdHMJt; +zCCil`p +z6-*s3*dDYKi8k8fKM~N`>^(#Xa$Qh;qNi$G&5{H;mq!8yWFf0gORGXaWY(t7T$P9S +z8+D4DMVT`K3*$8d&J|fyaW^n-Eva(Sr~a<<4>9A;|5ujdW&ZR}W@v0O*+nO?Z*ufu +zBlF@TOWrFkHX3$dm3iZ!0G57K$tewq820qQrMv0IJ+hW~O+>3!6yLH>HY_FKol +zB1(U}qwZ{~{t$`4wYoP87kJ$Mlp>p5HHdCT(ue*xZ1$qFpOC)667vyHAfg?~RMQs5 +zan`_8io1^Yy|{ov=r2Cqw857JO=o7WRcGttq(rONtT(P#E7Qnk_E`mvoFN7Zp|%p3c6^ +zlOpg;;Z+6y%q!6!`QsP3Zi&uDaD0OBa{izyh%Um3jaoCOqkAE84|J0~7B6@sLXkAR +zQjySkz-Qo*cA_{>(bQpi1flO5h5AeGL_k}rZF}XwNY;WJ8h83)5~@DzgfW&`@yC7I +zOa=6EB#!7j&OHcJ7Ur%459gb&#jypfvMg=O1VOR38m!54!rUXl) +zBA)J}m$o2k2KqY~zpqxgu^2f!+x$Uhv`{&^&J8cvWG+nT9v^*6u`aCITLx9M^m(Fspw>}9}UdCCYDTm +z4SM;Iu7~T9OxI(0`^+3UCj0choS@NfIY2A__>)=~$F4QsnW#{V1rrn0s=s!%9xah= +zYILwMJ@$iBy9wM-xSmdUZmmxxn>v^A+_@!UWUGiG;M*=W*DIEEUqd_UYX+jgikJ2ZEIcf(1H3695`TKtAS9k1Em@}=Bu>w3<=#qtT*aD +z>-N$rD3hU?2}EpKk%}INc6+{Tx^dT>Vpe$19@A{rP!WOòw@L%Z|S=vwo!p?w* +z;h3}j$^^JXJmqz(%Zwg&YYEqB48hipCgxJGw#S5aPRYLZ$g5g`Zo?7WNidv}l@8~A +zY^V%_3xN#@63lhZ>fM8wO2pDr02p$u4ezxFog-fDT&*tYo^kYkSswyu6?+Ho4LI3D +zW+KOVj?`cU{j*=}AXz`s3^d@*twRLu!Hj7aQtB3aaEtVa{He>V_71rK*hm>9;Z+6n +z{cz1UI*YA^c;!%zw-sgR1q}8*ho0*m+7M^L3ytU#6AOXfU((^wsUF?)Y-wBV +zWy!F23l(3=(cJP7Pc;7ftMuv2oyvapcVXo6egnRkUOE36YK%mp0I9;t3k0%Pu5rQP*>6gvK` +zb#AC=D#62CyWag5caa>!_|!Gkwvi<{GJ-xv*(+uM?ubYuPV{)V^fM+!71s?So +z2$H;KG;uF4zkH0wUl#VQPOI^7C!-V&r1 +zNS|1oD^v~3qXV_v4q<`A{7rmYdONw^gk-NKcB+yd!i3LT3KPB)pu^ae+l>hyWNZBy +zdNZn5tLCz%M55Szny`}gR|<*Gl8X)3p~+V42;iNJL^%&m~M=fmJhpp?xB}h4$bQ13*WsO-WPEjv()bX2-Y!AA(W;LE;npkAD1z2;= +zTWCpULU^(5Gz6H)IB!YM^3jw9NuzdsW*4Yo2l@qK^9J&#V_z{m3+BZ+f~|p8-q~Q0 +zxq4%})b7eH(pNAY@NWYd?EKeXe=ttW3wm1od8D@SUMMD<<~1Uze3LNNzqd +zeT2AuYklr699DDm`ox_V_?O0o{W&~0-p?vgp6UWK!mFwfS@Ke6XIl25CJ6E4?SbUw +z9*Y`~z-AtGUf=ND4k0W6B8(;+Rocs$YBQ9#BW7H_YQ5(2dh_8Ci*tg`-JoGx(sCX& +z%Hi?0U0NL5GPN#x$qCH!yoqGOUm8gS#@6txF +zu5UV7-AW!5F>4-x?C*-9r0|H%ZilCDiHzubEwpL<@tU!S~{f) +zdju+o<2xRU4ocq^z|KD@fg^c|9;{y=&A4*@`>Xo-RQG`RoxOGhSH0-#%+04Jba=wu +zANi%r>vii4s9Cmoxw7Lb^n$<2bvHK%@=@frzT&u&%Z|LrTCX&CuzF3n@l4Lk=;7#( +zC@nq5eWOl|S}*7vdnah`23vM6Tr<2Ld%Wt>V?5045FpQ>CqqS81RbExPVk+G@qfyv +zXW`gIeM}f-^Zik~a(lkC?wpKFf(|;e{CoM=q>Z^6g!T(MpKtl9Nm?0!J#NKgv4-X+ +z%v)6d4+m+B$N$;;YJ6^J0_1kj~Sf@}&;VWL0)Q$y61(T49ml&z0{E +z3j&gx{FsPIJAr!b_EVkxWD*KcLrcnxiaMtk%(Sk4kLmW&wwR>$SmuW-qhoU%OhxbgW_n9@E2nB{1T?|WW43X$$_1zEF-vBh1bjHo2^ot%Kz)9{Q?3Ll;ZLeMYX#H=o! +z&$U_ZHgw4&1vF}%obaSxKIJhO#rv9n_~G59c2!)Xq<qQDhVX<13{L!(c +zPF-D#4Cw!Kl*Oq?k3_-jWET#vt{YTLot@?tUp*(Ujv!9e;cZT~6Yq4Kh}M)l>V<;G +zdRwqB*If?c42}iE8jh`t&mVB24ol=A0Hw=2A%(wQUQmvhiRo}^+1V`1($qsY51yi& +zP|5#sf54%ghbS*d=*{KvJ849(<2AK(L^1YK6kY?YtMzGT7?d`G56lPsf|8iX@xXwf +znja5EGZ*0Idvd}^$)oRPeAcQJ(r-o|tI_->1H+=(5eNc>^FKsGoap9(XJDUW%JhO@ +zyQi!0T$cixWxqZCTU428!$Rwf!9q;`MEkwZAHb`CHEXS;&&s!@WY1t-h}3ECe?vUn +zbNXpu2XRZ&f*j8 +zgJpBrcqE(W=zXYY`UyT8mu6C=w~9lM?M-Mt4J2{FG +zQr9=6zcz_3UikLX0^VMKd(&gC>B3u0UKtq3k@+MUCZgIfxbuGl6m&s8efpP^j0TS7ty&$=Yx(x^ikQzZ*SRwsKj122$#~atzumkN1y;pV +zSt6J_gxlZ`h%*96wu$P6QO>*tADUa-|3Y?cG$6F;c{oT*ossKXhn0*x;o2mJSACo7 +z*ln+NaiD|{8GdPdrMU7tvK86Ylhk^4~>tr*PT(+X_<0h{0&6updjxT +zTck77LOXG6tH$Um%Fa5#gOfeuSD*y$_FV6&TqA@6$|*^a5l&N=oB^UkjqlB3@#LZ7 +zoUb|VKcxCIlDmNnKnGjPi;0|{T{B`9QE+lm7xh7-h5%r+*82&1!-8!eEB!fAQto*Jvgus}q6BgWg+?XP6NjAhHG`|k#&rR-B|-|WMZjY%hwzo;Wf +zlY)HV)EWa)fthK1U_Sr3_YV&!rTIh8?ly1$`OVb2we4L(nCI%}r-5w!=5;8|uwab3 +zq`$XTlB7V@Q`wM*hn1D*F%lQ(Z>Y-whKHW#0v6_^!3UIHYNK7EBwz6dP!IY%UR4jT +zoPY3196u%$cC(u`q#5@QeY^$Q%iBq9Nj25fKr1EX4Ba_s3)(CqIqP9BsQ!W!KVZ2X +zR{^AWn3=+8>w|$J(48+Hg%|Y-(x%KlPamx!W7ocW)Fhj;(3AD-PRRMhNe$iUTsiY> +z=^t~TF7_w`$r?n73Vy;qO-st6826vAm5Bl}IvWg+Ic4zk)0bi~F@wR`{025+%rZR~ +z%ACCuZGeB|%W&5&oUdmoF+3}Dx@IEIG>I?GGaZyTrk0NbdinCkmGQp5cW|$_1=@*- +zxe#3V>AYa78C_j%sgM*iGxM!Qz86L84g$ +z=0Ybddcj*soK8qkHGy4g7=B?bPd!mLqcD8Hz)dcvb9k65_+zqk-Y;HE)xqt7<$t`4 +zR{-n2#ZzE+{JNyqI@EUIOXKXd!5fhZamN$Hyj1ezgSno3HgU)WaXBbr@v>j2Zv?6J +zXPS3B#ZiE`UZKaV9^P3`U8;RXd}y&!3PhbG1FzOC8t~A#a>n`dOJgwXm|=KZ`G>>n +z#wvMoCXJ9Ec*l^!cTNcKe#00DazF-e@;R)=dVVbO+UR()J7Qa6M)lcC&dT29ybV9t +z-2qo)UJ9{!P;xlhgVXVVfx)L;`KmPad1Pv~AR*mj1XH?R_RIvSjzJ}uj7 +z|E`N)0sHA*LYGl`%ZYI6E&c-NbbOUoijQ1lVP+3wsMoB4r;Md~7MDy3((x^u`bUFc2Ph_dK(uzjMe0!eW`aJg`R&Fl +zKlsfTA)v5u(&tovb$YmLp*`rq3H25;C#Aykud`2n5^9XB@-kt3A|xxPOC}1d%f<7Y +zvSwX1GRit+Oz2%8J6dMQAe=B{=6jN`%lXPxoIwZXIYf8>koT+|m%R6_cd3{djkjQd +zaU%2|t1=nB`$E&*h89j~z6*_aSfBz5pQj1F83}A_6!|}#+E^>qqLB=~-lH4*O?KUU +zyYiO3I`WAxLCI`QF$7Mvm?AS@So}e!qIMPg?Gvr5JF4WSOx|3SDx3YQ@|hPaXCmz6 +zEY{vJg`nm5`U))lS6z^}!%cS9+isf&@ +zLDM*T5LxPRTju6v+FIsIX)!q03fpZX&9_egu+7d{CSKy&+|_ +z>ceyYcqBq`=u-&c-pK|;{({JD0DSG%yWmH<%Dr{9u*2(6VZGL>rgPT@SAO@o@ +z$_5#Lhwn*u?1tHyj2*r7sDwj8@80>;XB2$OM`&oCA!+q0SHFT$!|D9NyH<%Zfnx>i +zFTnK2(irA_nSJJ1xBa-1S&%LxSsYBGr0h$$T(%pg1O-pfA!?u&wT803%bQGtQ<)w`#U*ob-<+tAc6aHxQ)tAk~!IwopwxL+-}q5 +zfia{b=lp11>YIT6v&<@6@;f*utml2i@e@{KK_N?V?Mxq(;7(lSr|m0fY#$#K=9s6kXi +zR^dkJEaN#KbSGB^T%du)1T)9MA+VE72CumlRUup$4N+Fem$tt_vGCkyqm12YgVBmX +zgvD)LyMoatL8c;7T_OZCWGv7yudA0*T>GV|8b|MVSaI>AL}w2@7+@~v@q=1{LkNFo +z7JwZ~J+0Bzi{dv=@2+i5Ax6w&tt!QwnGvIj9M}uQ?VVYJ +zPHIeCX~7htTDj~8Za%auG+|-f0#>h>nPYWLF5(+ChQUn@jS{z +z(D&aWsvypqGfP8V)T}*s*Tw>woDe(G7TNot8IO1A<4JhFO%UDUJe7FWA4%ibbp6Mj +zV6&W8<+n}Vlr)P}6d9tp^PRaRW|aJ%8!`ea!s95&%7}Igp1l~fd?0d)w8VEYEKasK +zfWa=rpwX&o`LlzL{5+nZUHC1_I^CDN+yf2dFsBVXzOW|wKZ%Zc0!{Ogawx`cK0gJH +zn_M>*M7I}2J3|_Oetf~cxI7+MBTXjUh)~Deq>6AP95prPJXyEkdYsEO+K1|YKl>?m +zI*&dqehj6&e;mU`tqEzkSbPS@1mT>l82n$oV*^ON6xZ7ODxsLn^?!kIw6rKwt*31w+RwCNa~4GH=$>OL{JYFlrhhO}M&xWit-F +zOB16j|h +z!8uP?B)s)Py&8^0FJ%;oM<`p#CR)3?z81I(==NNsBI#C=PzJO{;r@0XW#-5a%^yjO +zaj*5U^g_1N^=U#oz@qwT)NeZPO2BNX6*#aV!Fu=U$mXQZLnm{df?= +zDV(+I=<$X~(DKx}1~tgwiGBF@X2cUjJ%3t9ZwGSiE9^4j4h5X95DCBwIO +z&c%+}fSS@R$*m7(>Dj`_@T$hWrmdmjzMT;+rNQ^nrF85Okf2~NOw?%_b$}g*04+?b +z`0W_y0p%8o7OVwsU(Q3rN_!gYA$PjGWWKmS0cW_K9nV_KD*N_4yM46K{1`1%+6VM( +z2e)xL2)q7}mz?{5OAZKQft@IyQ39;6WJ(3!4Gv(*VE?dulu!CK^ZkPygS-T&>rpgW +zm*XsYwPHlLf15&Tc;U#S0W42xZiAj?7PW|)^^ltF4$;yB^d800FENmjHL;e1VrXX7 +z6rm0m7-RDTGIMo=x)7mdMM4l)e~qxRb>-$lb0@d4b+S{jI^WHgV3{#{ja5~s1w2jM +zvS8Y=y4VkzAZEW5J=6~FZbr?B*m_RS|um(JvT!9E8O7ve<#53SZiUm{}C=>exJ1t7eWrWK}x54P| +zou`6*`6>4lp{-3xfw-+WTE)AAfZIVH@;AO$;CixRQe5wYLnEO#QHy+#H+!kA +z13VI2Is6QMYzvgxx7DF3s;wRl#iFu8sou_f2xRl-*N*1tRem#DJRrrsIZ6rG`59C6 +zUnT8pj3)k57jS9bw@D`cuy{a{(|-pIYvmPqv)K~hgb +zJ!XiL?ow|E6vUSLIvsyB_ZkH!qK=)2_q0TmT#B8YfMHk`b6OSIJR)f +zZ>A$8gYz%4msB^N1kh-NQfcT|@fl+zJ%eu&pIQEw>;G%4O?QcjY!EVExV^Q3>r^L! +z&CSedMV{9}$qE;>LWVqD5qD?0?xd?hjge9tF}?pZJLN=wseNP_{a2oh7uenQ*92#< +z?ZHoUh6$+vID^w%Zx?~qo2+ghPPgzTJ9bpg`OP`(9$d0n(~l3C$jixX{&?R>VeCJi +zzcpd8#gkfZv5=vykdWC|h!<|-SM=Hf2Y{N5<6wX0xXlYiod4PNpIb2QfGJaF=U3Vx +zFbRwYg?HYl53~RI3yEvr`U;c5O-_~Ux7Y9cYXsh#=G3gWxE%K*{HHZwE%SrT^@Z$! +z#>7me1rDot%k=Mi$kMCfhv}4I{)`d;>XL;(eLGMkiHft#t2>*R%E`3A?U%2|6O9ET +zZkNNiE0`V`A!yW}I>dtca6MgKz?iHf0?f+_cqb9IEq4)i29xa7nr$+1#Zchc!I^rE +ziYvbU#LvQc(O(KVfP~SYV<%jy)Y$ti@RViJ-Wx9U8Av~Pna`TH1fJoZ4VY&cB~^i2 +zuj9HQ^354Kz8!+Ke@)%LXFbu2yE4d@0)f{-l1T{ZNYQdSnr9~iJfJkfw*EUzjk$O^AUsg@jA}BS># +zdj1U~7#q6sC;+SCb0`3*v_n~InXSCO(Z)L;Szpa3HSg97xe_C}SZrnYr7ual@!Ee1 +z`*!ENHq~*J*G!>P8d-oyG;BfD>ebMLE6KWG&)6q5)6%3o#?av2g?1O06e$p{dI_3{ +zBI}DneR%4)V0T4!%nhm)OVp29eO-la^Sa#1g(my67l{HVID+pAChEbZrEzp8a%~n% +zED+f+sg+820a|AV_-aI|uCq +zT4%%jF1hHBpUvD`?j+xaj}r5m;H!|90}wd+iRe31%Mxjv)i4mL3Ff*#Jjw#Ok3?Tp +zgzwjXybNRDF*@|FP(m?xqSy8P-<|vS^VVGfW1(h0z6G5{4?h|ef_Hi;x!EFpkTCAg +zxi%_(&zY75m3FEWM&we)0=OU2d+?A$sC`P>&n7KnV$;((zwUTTjDUXm!pDcX`%@-c +zadq^*O&q8n0?+^F+d|M8+taEOpnqf>LPS&^e`Ff26>S#DNPW`tEEmR_N(v<*Tj&wO +zWE^MYko%^CPP;1?^H|{+w8VZ_AO4J{9}&VjaU4%k1E|ar77y^$11|^duL#xhUF4nW +zFwC?B6Pz4{W>!(h=NG=h*}eh_Pv#Z*wJh?WEim +zS@S4-aL*a0IN~_rDu8|~gCJ8XQuz7y$R_Y4t0G{~6!o!m=)Gv`ZEscFLGW|2t^~ukTkGw;vYx?lI!!KVw +zXfc5}OD?gT#e_^q6BmdYug@B=9Wv^=K>yW1JVOz5saOt0e6S~73H=faSru^x3oq?U +z!y%}0-&%kz9)S_bq?f+!z)v7 +z-JWO3&i&@Gr++a99UlQXsL|%K#7=(h3)=gB?!2)KjGCj+(XqKlC`S4c9Jt3%=B=ZD +z`6QFII?nq3!3LGs;ZFy04vK=k@y{?ZqGnVo0f?bU@Ru2Zs0|djx%JPiBOJq4~rB!l1>`urE<$4Qc +zeo9VQ-{Yu-a)L|o){rH(Mn{IQ1vM$p0Wxr}7#O1toF5o|~4vHoz1Epq^^;RB`1 +zGLn*Ao1~r^9jK(jNU~s*uY{Eqhd*7Zm7evy2Z(HwO?j$+XHkIk4J_L=OIX!yhHg2) +zL;u`9p}*EAM4?39iz^EAj=?v{bgXZil`3Dp6s+S@aZYX#G%KdzUHKCX(noX~xh2C> +z8nwJ>M;oAoFLitpTd(`0zSI|ID47ygi#w8bF!qTKH?_HaXSkjIf4yN} +z7E($HVnXT90SUIsjL!60d54yaFd_(2MLgv4^&iK&%TfE*h#hBj4RzT0vlqgeOW{~@ +zfT!1AS3{;$;6q%);}>CNSHK>jhujI2cGV4W42c3Uqc!**DxGY7XLS4%)%T&&7UY$Q +z&e8OyMx>wGEni{e4r?L(e*U~-Q6{?&@07F272~pURftt(bE6_0raJEJOBUy + +delta 13540 +zcmaKTc|6o>^#ASVCRdRqDqC5TJ!A=!Y}u16Aw=1-G{|l~H(Kms?Ayo^g)GTZCMiqE +zuIz@fj>tB)Vfa0tsqXFG@Av!jS1+&T%z57Dea?BG^E`6y?|XKC-(%i=OQn1Rv=Fjn +z`43i$$NV_Mt+WyoU2fvb8A$B% +zydCy{PR@tr!^@m|_wMoc=|iL*&l6CfXsoGJvH|DR^oe7~E-mC>PpJuq8^Dd9tc(|wv9|o*` +zeep(g1l5{;K=xc{GZahKk@HEYzt1LT6wJ$_YZs?c9ft{C@6sDg4fbI)!Ka3!au9?* +ziwNMd=BEDcr2eoyKVhUn{aQjaeeD5$>Tu>L#O~($n}9?McTKbI^z(=BI4mktbHtH< +z*BaEkGA>2>-#li-9fa|1fHrTiF9%&Vk<{|*Lzc{cHnB=iFqsMa_Tc^242H`L+2kUe +z(LAZ7jc@xc&HJNB)qibS)W>hCcY=e9HquXo=nh%!;8}-i30(Y=j?C7KHg&(X7I=u4 +zE|z?tBN&_357=YUjq9dt8aTF}xLe7+=l%m49caKFUFdp&8^s9)w_t(rL;-deM+($a;bFj8b&s)pTYUgq$iTuX$ljD9P`ps +zN^P4eOn92E#T72GDVfUk%_wbDuS2VVXID^kgBs@@-9+(P0X(yiJ}G{;tq!1o^pC+h +zy|9ZTzOZ)3?(J@;;&!HG2wR6bsf4}2$80`gKtk5Mp*6W@I +zeo8B)(SeY#?q71UuR|isq7_yy`NuzE;@3OBP%gD^8lM|KT#>yi*I$2LRyN7?$&}6W +zu(!WITsbi0E{LF}5StYwO1&H_S+lmgR_+)gf+u5jjSZdLt1|B|zxqisbw9mANwWBj +zi`z23n+xevIQAQH;kYrx_CUu|O&QjL{r2r?P%^psp1mxSfZidFoYt#H|3yF$2P=G9 +z_->9B0cR1B%@eIqMt)HXGM#gq)K{Bf>D%$7qFUBGCQvPA$a!;$p{#$?ZM +z27bwBk5Fp`R?-MkS9G^qPmPX*NnVepf3mv?;oecdG^x6rR3X1Fr)U{XIX?Qocb72w +zX*CO^`9ElQ4equz$l`k$Gnm$yU_2CX)`TR(IuQ~%3c{$1kVw{4Bo5v# +zmhMp6b~f{6+3H+0340CPMC%R~DE!NA?uZ=V9bY>GQHZRKn6YMGC~e{tjQI|O&egQ> +zCKqx3TK0Ke+uZ9n2V|q5^WUmGcdcBjg-vRAR$5gm-}QfO5Gh%{r^fLAvzuvQx#at9 +zA9cA2`NK{AwIc{r+Af3KjUe+l6%`f7p1V}7jn&xXa5W(X6+e8a0Ps?wLvC{{xQF|J +zMg18#*#U`JL6$(}=UFN4e!VN`UmF2p3vqypmNnKxuceRvehJEWoAo$^vBp=W?+cXx5`?KQjPTZ4-$calV= +zRtm-XUVnX&sw6S_)`#&wI8=wgakCOkM71~k*)rmdQ^c|rS;0xcoxIFbHSFH{%EUM6 +zW;MBCW!FNd%!<^V*xd*Ik@l(m%qNQ)saVC!F++j%g5N=aC@8=nL^dX)av{Qu#jxEZ +z`ZzV;1})6ow4@FtN&XiW!&#rMvx`Tl{oaKe6ijS5QeCk9ZnR)3(Ml;WGDM;B_bIj3 +z-1PE9FO4Jicn;+~^_E+o`Pmw>r~e;ds~<%=BDiCo}VO|%5hWv)5-QJZqWA7 +zGib*muGR5`PExh3$5P7e7c`0x_o+LrQN`7NhX{6#?8WxOE(c4fQ4xqtbUKPeAY2$? +z*SS)&=+vE~xnQKk!fN41$R!wRh@vJivPhd*slzOWcfP`1#ie+qb9%N_PVi^ozsJ2E +zIv=!~i{WwkCIeIdlFdwpzw3IKv@6$g_a!9>)jOkOYkgK2{vnIB3kXmK10BRryQ`Qy +zv0TlEM;VA>WF6>sGlh@B +zj|^8mI>u}T_eT8}(=A;X>*f@qw8C~a7b}Kb9fPL&b1Xb&+I2NlN;A1xHHCkn`v2yi +z9^_rMHJ#dXqe9MSoTuq1a@v}TdS>O^#K&j^TBwOnt%B)}6tm_7qqOQvHougQ+!2_D +z|8@=8d1n%9ZJtwhBoO{f7aRkGrHwbCJ +z@nEpR>(ubfT9J}>zr-d3BHIww}tn0hxlrGA;jjx4GVDZQ){X`%#ID +zk5%X#z4~uFx_sq^5w3yNY8aImy^_^@2rh4@qu0B?9sG|?eAD7>t8Fv0%*Vk)`t!sEVfRs$M*%PmRS +z(Mqq$qSM58qZKy{pX$&oRR7))?c;S+nNdj74tz1gPyVYRdT?fE;q)oAOOvow|03{^ +zK45IWV+T{-JB`N|I^@3=dWK7~HYs!5?KK#0zPmm@;20V}rCG?E3|f>%r{$pTwg-bf +zUUyiNXV#vSit59|u;6vn6Q&WwPL+kvAMZ4tEZz($eDQ%+tfq5kyLQh%<)%kzS+u=u +z7f*s+Tgqn(AdVNjw=UW6HAjG27lbZmWrYL`?_5-E7kC+OVkw7Z!5sBsNDD%Csj=p2 +zo?~ST@%3W%efZ((>bw2)Ig*S#KR$}_o6N$Nr>@U%x7VjuX>@iFWymiYBtZ+RUu9N` +zFU9H1u^20tIqzxxpKVPZW~tX^lS>gi+hwq>D>D-JcKU>*olZ)pTo-rHvoj=jz00CghS3(~GJ~#w +z2H$PX#I-@=AM-N&oHi4jBS9|R@>zdRv!vh_J{H+p7!|JL8P_Hd=es%$%BQJ9=|t4| +zr|2=Bq~}_($EU6+t$ma(b`e{6O0Eu(T;~7AK$Ik>8?L{5cfqWrTfbufHYaQK80*-@ +zHKLDNK|9{%L_3Q{1(U|e@B^x4z;vU8Dw-O7J#aB +z1t>Wq67R=1{iFxYI?oRY%&(^U_2wbS1kW#Az#N^bozOlFsVO|smu`OKb}^O$ASJ&4 +z2~ro1xP3`CN&FPwB7NYj%7PIjo~(Iv93pyK56N6HaFK1>^@0sr#$G;CJeWb~<-H3rRu(8*UB^kS;O9PwVv +zzdo^cXR4F?;F9YL%+931lOn45uED!~*LyGrpFWShsb0PFgtOWKMvkE#0{j84ohKt0 +zzZO+mdc3C*$Z_9>&w4QLt7Pf2xgQmJ2)Da{dzg@CJN+q{Kp)QNUV7}FzJ@~Oe|*~s +zJvmyRzNyjq-JESxlnbm|%VW#nRv5N4|4>mwPT-+%$k6q?GAu>pTT?P;cy}f9vsmq*G>!1T(Wk?9Y`AkF-9z-(?qvV(CnLeP| +zg-GA3XELTMYsoG^G1GW;C~i1c;85G^XSfWFG|vJVkoWhQQ>&Qv4*q};+SyztY!+R@ +zzlev=j6T8Uph03Iy*K4<7g9^I;7Ph%Jr(coL}PkQ2J{-e@sRKFINzV}Gdce4WO3mQ +z=?SNTqA!+f)WFpkfefFltvY(tV^@E*65m84*DiZOo&2s3e#xfP)YPfbP{1S)Xp=3% +zAjsd18t_vB^NW*NyvV*5kZJe(c2`Mzr!$W0g6!3O!BD-O&QeaAE0Br9P9>OEm~8!> +z86lYzrszAj^6wf@f?EScZe&(VS#`geZj-?|?=C$;l;cE>?}>r2jOBM#iR7{;>*R&W +zl<*a~Zo+aCms{})^28sh2{MBY*)WEmYso{kxy20VPWGb^k03^OaQI8ai;Th;xTT_Y +zzEw>`d9pe3n&PqZWvvyshMKi6<^-~tLE7z)tC)j>Ef>QawsyAX876L1fpLq7%L5LE +zt%O3%l+f;3(HTt>W8k^lFNEeBT;EeoEPZpaW@q``^-9L5*7Fww7ySv<+LKKgE0BP0 +zO0V;4!qWw#w_B?-GLm+m9H!gThb*UTZA5+L1mM~LBY=^euA2G +zx~X40<#BmNum`>F8z$e?JtGv +zPR;#y{5zGYrNIbr8!EQZLy(E(_Gf}t=oOaU*^$@!K)jaSHDhV4e)I_K7Jx8DR`rNC +zo=dH^XRm7o_mv`6hUA&%FIkF|jXz|2YRHjQnT#TTwUWVYHp+D8oRAO~@0_p1p~jnu +zgglI^Yt{I}B^RYw(;o@M0D2Dnp+k@q1IY6pdIrF(4%yE06n$`)ZTpdYy538vROd2H +zk6r@}>8_)9YphplR@=41^|(!ctE1)Wv-Dp8WUAbHQM*eb&YuqAHIDrR#M(0rD!}>r +zQ$bZs5b0D9sBOoo{RH44nI +zf|sLG@zL=>Bki)5GX*)7ydLa^%;vxOn4d*EQL%Y#sa)soFNXp?8=2&)i81h&!c`lT +zMQ#U~g&BP+P!x&aNFB17qJHOLn0O`mtEjmGO0Z@MuE@i5bWhQ^>=$poy`J9PUerfB +z)pPpK$ME;ej=xHC@tKM@d1$iwe>oVuAZz{O&`9X(*DvO7*XNWZjk`;Q(+p2%iDHS} +z(_I=|x_Jhwcf!LfrHf16{|T~C&SN^qUY)&={ip8g5kEV +zca>fIQ{IDI-4f>{#mUDiDR;kI%fp6MFTJPjDI?BC@9d*y?+hSjbLl?nSHF4(f4=zg +zcnR*;u%`aV6vCs^Bn0?)$^5sfqT}Qkdp?et{ZYip^#{w( +zCRbX;^j)IR3|z;c_S&^A9I(7k2FsFuRWO(m8MsHHhLVHeCSqNv6U?Vm?NGD$cN*mY*kof9qg2#%WXP +z6)3tg8l1j20Xwg*#TN(g1x98C%qa(QJ0OrJIT`9Ps7yrGF={B{5#Q%~JjFg0jlNN^ +zh>LW0E_}N&>fb{k8L-FtgYo=nvs94$9Y1x?WvZ!!(Jb5NXTTarUol^wYC(Xmo+rqi +zKuzKa1NfU>;o#RvdaNgOEpYAgP{A%Jvg&f97XzC(ymM%T!@O^BfJLw4*~qc^OLTIz^*)ld4d3!dMTKK)6;dd=*bgu +zJk4Kz4}384nsSqJwSjQJ*t(ggrhG@3tk-Q#%z=dahNE(d!TNj#yrtbY)wgf0z^z-Y|X=FBULP`>ckt+J@weJFj~U+#&>;8emAhdEM>{vy+XnM1U0W*~uUk +zKi|8}V3ZxPYds;=Z +z|7)|0o~S_s+zeVSJzbcPQ>1YID=Tczf5GupUw3LK7;^_wJG%4tdSuh8&BdG+Pt`whRP(H>LdNP2fI +z;7DB_!)T`tcPM&{ml+~zGf&);hYwazRN+LYv^kb_*2xy+i3AJ#QVMzYwsy&M%I$N| +z?hIsEu9GT1?2QW~xU?|@TpTlG=qLb8$peo6k|%0_*#8){w$7AW@3-yAdq)Yb#JQQ# +zfYmnbVZ(B%{R{P8JJ51do~Bfo?(2XPro)mcx!3M~HtJEokQ +zdT>zqlJ$*?{{{{^5)C7lX_a-1J|bN2yb?=enC5zN>Bswv@SrhwieDqaRP5WB8S3Tq +zacvscRuNa4jY|g9bBqmADvVj8d|Ga)l)N^mzvmN|iOAQcnY(bE;D=X!YA +zs2LHc9X|M5tEx0=XVa5t(CpT)Ffp$0xst<~2Bp>6;T#lA^Dl5@3E2OoF9ogJLkVxy +z|EU%9ct&N!W$Im31Nz2>O|Jmq=bSqnGiO&`!MVqY_hQ&i=$9-P=jR#4-kp8&sKNv} +z%xH_h$rzc*iRRbUa&w5XdGfyV^3LR;$MvS&?VzA@Iek?Q%nI4Rd{UGFLEKMPu8o7K +zowepu3~!31cA6v9{-CwJCjAJ%BA8hZ>OzJk8zb3ZCgCWPK%SOr8wP6#x +zb~t?!su9_Qcjz5$ERB}=wB@F&53W+}q$g67uLL3MHm%b>f;cb>eNF21@v10+ +z+j=sPNhIFO_`;X%S^_4jNO95d!v_u=NXfTieoK7_cJqs4D_s1YG9zK24E|#afwI~o +z_4O>MzqRUwNMDOFO8X+%WL{vyZV>k1mYoo!;Q|CIVe8WYHs?;u1_e;39kV4;1G7!T +zc5}r71%EWZ?_q$gU))ob5Ix2f!2}do26yQ7faQkpngy6L)8IBrJ5LTc`K|YvVbA38 +zhAYInx;iOe^%&IzPn!nl(m$0@jr{;MwV_4UEIsMLZm10i>oEZ*pc2J^IMOCmuxdB5 +z!Z#ebXzr}yO=|Q8e3$BKuU_gGz42H4aQj?&<;ErM)#tqc!|FiEot8# +z05yS?@4QGwe^#=Bue<-zjegtc+&}nrG7RQ#;Sc7&`eIf9aJffYa87&On*sf^6HplN +z-BCd}Msf9%SkUg*jtCf(IQoqzD!u^vt?fPyQ%W$Md_FcTRrR~i*e$+!a6OSaG2E=*-X~E}@NJQDg)0L5n@Fmkt>75#yWTsK- +zA$v)_&E6 li9BLHG&|G#x(0?9i0+-mV^CXvv%8P-7JOAuO1ILaFBq19f5+!x+2( +zycM@3QRW~STmba48XqP!ihL7#xY{{PBw1WS;l^2C^<}~PeyX_kL+6A%TKt=XsAEkX +z&t}7d=T|i2GC3od8ntUFYVUc)Y6oJEqe-arRAS$#8+Pr>f)RUVna?d6v^{e;2fV+` +z!tKs{N=C#B^_PqFEWULRjC96;M?K-8Q(pLqFR4zk>=6_XGPO%R)N~pIQc?X`gBv!Wn73 +z$(f!@G;?)syIr<9Nj9?KOXH8Y^eT!M;{maD%fmjI9rU6_A6oIY%5y^4> +z9M}PCunqQWg$ilcE-jz1c;D +z;oMXTTe-H@{9VyLTI4y6@j1f!HaZIqX#{;E&BQHbHkWdPL8x&ROKDce+rRox#%QDz +zymAWNO~CBgJVrnq+6lX3v{y}C1|a=KdBCCU +zLzNLeMJU2*9GK{*Hz%U?D_>?+r8h3?7uJNE0aexGk*>WLA@yXm;(CA9{p;Y;SE{8%XRisdi}a(r5)_F|8)?cpzBd1|vK#iv2L2+?N-WwKMB59Bm!V_V5*WAmb|;TfgGmRSf^ +zPjTZSA9do=_3{MknvVnr2RnADFfWo{gTW(JPx-njfBTK9`A27z8NaHZ&F1WVucc}H>Z+&Qi>Je?=22JC+<^?4 +z^IXKM%NCzer3Q6?Nrd={sw2Pust!0PHS&~_0g;xhf{3>P)H#N*HlS&acyN+3yKy_e +z4;;Hkonr1|kMmovvrn7ao;r1&@|m`m7)kEHszy3Kv?8l=%y=)`MAB^jkd!w!;jl{ +zg$9Pyx=;Gv;k2QN?T265yiLfYWF-a75JP6bs4gqMV`33(jSzUOW4JmHG^Qk}x%el) +znsFu;-2M}a>V$>Ij136UeA|ghh1YgD8hG1$q2AEfYNc6GukU;?lV~Mex)}O>|hWo7`xK;fVIKIf=Z1!|+#} +zhNAqlM1@!Fx+852eqS8sW{4lV%b%C-Wkxa!^1IA6cT`Owgm(Fqp22s3vqZu3Cj$f+ +zJQeG!{V1B9wWmB1y +z*Bm}}>)6FWF~vpG+eNQZM!bfKd~UI|Ly8;2o+#_)XqgV=I(S3idCZk2L+CKLm~No? +zL0sGXBKoy6ph(jr!?GyEVE)D5sy0tBz~<7pfDz5*z>v9Gu7A{~8)||LcWH(gzvJT( +zMm4h?2|thv<1z3MTAM-a`Tmk(kY7A#e2-++y5JmBtBtoF1|^%t?HznGGWVo%)UQBp +zZ)dgV;$v|Aju@#pA>(SXt&dM)zxG~fNB(hs7KP<7zFc?zS7oBdBNb~AC70U +zt(}5x$7f<@#Yja|z-tvO3BEgc03%G<+5|*f~Dd1OW+qt0}^w}?z-F6hX>HU;sP+m+6 +zn|Yc|RAXEyoHq=Uuk^crBynrC|2X2h?e7&NjSM|;|S}|epBeaG7rIIp&CUPW?@jGX1 +zz)MFhOE*JsAqgI;{00$n6QAZZwp4XDP+8xdRH0Xl<8Nl1e=-Pmd#UuOrC=~&_Rwku +z4><(`rKgU3@z8(zcs*~(VFvdW#|$s9|#e+9UT6c*zvG +zjA1lQXyZJ^-{7iHg^|DOByW&2sgW9}<@|1{!bo$)T18wl*dz^1*K7;8dV}a65|MQ+ +zJc|=#2t)Dd@AQ*E(aB9dwnOWla~$1xyYUL@p*(()2wirCL^9GjP@Ch9LBe&*rf|oK +z)teC8>Uzql(>;F;&*H$0QGnK8Vsa;!r&HZ3ASR9UpFOHqEUfAX7yjxM0m`?`)-v`6 +zBkLOp61I)yKfa+QXWa|Y$9vAgXxu$T>ay19M{2Rh?#=m +zig2`iJnD*d@al#0rS9{Fg0A#>Q0h66KoPaT1A~4BwTzSqFoM)j-;1B~lqd8Ea%d@g +zbuan!r&P2&{oxHBxmQ3H;gbYfH{U80v>m)^jaH`>k&SE;)}ZRVHCrj82R~q&I8Lo$ +zCUGjk8UIIXy&NXGxs=MkM0RuiSzd-yG2YnHcdGo@6Ji`#PQ8x*L`f|^&_TE@`9Lj> +zgm#4*j^T|93y$+)3pN=hnY~9H8}rH;(E|E7=6#ar24XAS=CNv;Lv-gl|`zI>h; +z>WXdwyAAbyO6lMM8cWbpym@rBX|i{VU|vpJCHU;6=NmW(THX~LZTv(lJfLC}#`~q{ +zruu(j;Ueb;zyp-^GIeEv=^Tfq-R&3EcCU3`eLCA(ZgR^ND2VBM)SQ=^oa4tLn+m%p +z7MgOwV4(%sfidbQHLrl+Yaq^Gz#PHnW84_ZC`>gza2)f+-5G4c9QW-*+dKnf0G|uq +zuJ%f(^cU0Y0a&1?dksFro>BQP!hn(plED`I9{v5rBMGyssY)GP?kEL0;(C5|)Le@s +zYsH^;QXm9|MSDpwxV|ng^M9Z#NOA$gRvU}CbC;!HB0Sw}A}3#o4?EqAo%G8w?R*!0 +zJegU$Bs8@-8EaVcv8>+gc2RtkLzXM$Xuq6H3~w9+;*_7d0$P#5LvLqf85K?BP!taq +z8mR)Il?Km|bijLad{UN)wF?zPPc=)art~mMin`q~M)9=x5_5isD5o(0 +zW*vw%tprl?i6t7R$O5?(&R`%VK1s{pn^(&l?1GwX9F$mx(ry$~JT)tCg$|s$AyWO-5;r(s6ywcGC(+%G-mG`Cr55!U? +zd&VZB5u=vz1J=QZyZv9E8}aN}e|`uC2KtUnm@LDOIL&HIfmO)SlfH+!(p7D2?;V6> +zBC;P>DN1|I_e%*TSpUH{we*UaR#^7jbD|fgsfbTAUt-P=P;x^IW$35Ifk4+U0{AGwozk91lV$=vengxX2=suT;QnL6x(W=)mQ`^E9t+T`$Znijb +zk0dGhs{L6Yq0OrCmu9+*#Of<%0C8_IGZKH?LMG$8Q_Wu;&yD&_z`a*fjN$rHPYN{J +ze3Ktby)-U3ZRm22H{81vhNEJ_9A)~gS%iqG16 +zxKX%_n_{JPec;7&ueJ|Rd*e4(MY_X>rE7QBihCABSt5lun?FZ-#6*3f6ABL(ZGiCoMDf#pA2H!1LOH_y*kFH{cRrJYl8%oz(_hKfeAM +zy$GQxqq#aY;i~Zq*Do&RU$iCA%llrkY}b0pCkyp0Uw!@?_|a6;Q+;vKI_Uoa8e3&| + +diff --git a/wrench/reftests/transforms/perspective-clip.yaml b/wrench/reftests/transforms/perspective-clip.yaml +index 7c314dd22b6ae5b8a0bb27226463139992ab8dd4..ae905b8a441a4ee03ba927a606aa05468be44b45 100644 +--- a/wrench/reftests/transforms/perspective-clip.yaml ++++ b/wrench/reftests/transforms/perspective-clip.yaml +@@ -17,7 +17,7 @@ root: + items: + - + type: "stacking-context" +- transform: rotate-x(10) ++ transform: rotate-x(-10) + transform-origin: 300 300 + filters: identity + items: +diff --git a/wrench/reftests/transforms/perspective-mask.png b/wrench/reftests/transforms/perspective-mask.png +index 80c2dae951a8e884e5942fc5f22fa9209cdc35c2..41ef1283dbb146a533e378f536eec02ef97d9b52 100644 +GIT binary patch +literal 2285 +zcmeHJ`!`$Z8V(Mpov3RCRhN;fF`^;HZCXMbGM%K40h)5=UGjuQ<<^n5%2!~AyE+AC{+-+I62d!Ofh-gk!+ +z6iD9l$-YleDAbl$w#4fwH+t7f-$-CQ@nrf +z^COWAz@w#e!A9ro*}Yn^x@OMjdvbTDiGS26|7_Q$Z-L|c@ofy;0QFkI4{hhGL_^hC +zIkSU5wOY+t$BjhL*ll<>LT~eQ@{4b61G#AZ2zpr^>jAX-gH_ +zW`T8;!5(~x=W;Wf;=?)DT950wXb3k#mT*Z}a`EJ2r|9KL0qU3?M157rh#7BWi?Xm4 +zxZP3z0Eo9nW$B7R_ENQ@*2Vi^UB+g_ObOWDS+@0oPUwrx$QC%XAZ8Ey6rJl3;Ch}I +zz>NU}$nxE|f8y(KojG3)Bjgmb`$K)UXY`#q$+>2mg#kSF?JyqQOazJjN)qzDLXa2*|d5DIAbI~wA<_xF4> +zh8r`zq2jwIB=%GF6#ZJjg3e|v{^uE%wajjxFA$&rLWPvoNV919KEXVy8KiSt>aI`T +z-cIW%?Qyu*w(^I!tWD4pWdt`80)+ew%E~i+o`w!r8e8rK6~AjLn(T0me)^-JXAUqE +zkcBQu--?%N$l*VvP@|mLj1ZLpTw)r*PFKAr_#)|SIzYvWp@iYJp(^u{VIa`uB1X(i +zyAs~h5K^wa2@LRxJ1T2mf35Tu4VrR(9!4k;A`lnRYZ9w5PCq-D+XBKx&>77HDx`Sct1 +zJnc-NnjN$?{7g#eoc`-v(*Szg?0NxzpN+tt^f&1>pi&e$(`q{1r9y0%pL*G +zvv7Oh9l~0N!3HQGNB*Q!rBvn>w!{o*VvivR$I`Y}Wmxf2m9euC$-{Bf@>a10);;kv +ztub}{2rV;yYxl<6M`b}AB!g#*Z0xk@oyfFP)Y>g|d<55L-|8rto+}5!kS=YwSwBe} +zTB~NBrq&)t0%X;ehi&DZzv?<~tCvl|4YrSyiZ+&r1DemsNDA^wFW2bdYPJGcR~Kma +zHS=AD>=D~E5~rnm8_`34Y1V&xGoiybl*asKD{`eRo}PCM;m61}KG;SS&~mb=b0eUZ +zgK_b<^v@JBvh}_9uKsb4o|~^$;rW;E$CM|N6{dPu031o!AsX2(JqTg%Wd#48uV0~| +zcO9W7;&=2(X5J;qKRmq*u<9gX^^|0aM|k>qYSgSc^;3A!-qk-F=(*iU)fg+^RYyrW +zJ)(QB=MoX_LSeD>?7utb-&PK2eniw`=&jFMw`6CTBhABMSSgl-ahVjN2&elc!csBS +z1n1`n`e6KhY%a0H%*_I2A8eR +zoWW#XRri(RqIjj7hi=vv&KR{Mg^Al!=;!Z%xm7pt#8cj`Jh{@bq=NeV*T`1!6we@at#o1n=pLrJ=Q*4N6O8La=7lHgnVz@^a9vmN%X7k`z+TDmY%+ +z%}gUn?bNiR<%lJUDLSGykrz&xDZZFFqgdi4FIWoh_htTu{cV4F&-;DP^Lf6X=kt7? +z=e)w>A@nVq?KY!Os4Yi>s3%Y;Gy}YMZL$E*6lPd93bpCyqg2YtjN3YRu)!uA#~&NI +zsi1AgV$oQvf{!K$&^9)me1e%L4TIksS6I5&#mb!G{4DvN=GJHxH+=BT-RAHw%UgQI +z+^o@_vU;bt&PlJB;Y&qXHWa^5f5&;96zjjXeSW;?_g^TD3%vrbXjfW+r{(eM+QifY +zD)#M+c!lk0DlsM!SAkRYyidNXo}X#P_8khdsIXA=oPAKGj%*;t>t_VNURQ450M!0s +z726^8q~Z|+8^H+2RYX(C)s=-%_w`WPr8x?L4{e8Qd{hSGs^AXK(>AAnC`sxs=_w1t +z#qJV9-@^G&f +zG9B8+K+sv5bo`;zRh-p|X{)q?YjWIV1^2#jTb>q{?z=~9YezoqmKA(q?drEmjPJ~o +zpf(=i)Z6uH(`p#@b!Wl|e6z1)QTtVf3DNSHU@Q+xFs=#q$n#6dwZt}~0Q$DY3ND<8 +z9LnBSf?n~h5{1_WN>dhkuxcqi-#!BOwiXG&!(EG0x~Uw?J(#zBploIV)LmUmd;0Mh +zYCV)w|9(kJDj}X(|4>ePNDKhiKi>{_Vd>QAH`tW%0u~w|h$>xVy?gwJ*ZbOHtr4Br +zPK7IT@m=|Ro;&yu +z&9P7DZkUB4nxvOvw8Z83`hDK@ +zZoXTIGEjh~#ahy%ruznWwL-|cG6IrN@}Tk31lP3L!p60ak^p9vXrLlcI^Hzts^K+b +zt|6XzpkfnGy%JBA@89iW4{UYqt{Q*XCtg`12(Ny=;UW+>+X*ggo`ptFkqyoO1+o +z$jM9em;Vs^xm5CQ6)bgcY)%U#o@U~-UR9zS+L=*VD%PG{g3ip|t_azh93p%t8+-tDL}vq|VGHqS9ZawopG9;OTW4wGXK0NPdD +z4AMUYk@8l|@ta7-+*yfoIJAviTk!ocgn^?S-D#-R#V~wMxd8?}5zh2yPS**F{JqUM +zCcu%}Pn!}i+}HmG#tQ~=3n4w?mvsY+Ux~y+=?`NFb?j(`|9TV-?}HSPHkwbsa+j$w +z$&{$rzau1~`##jtpDvytyqyEWm1}u=hN4zpV#yikw3V(eg_NJd9pE6)*JR$MRX6n( +zaiMxU_c>}K +z@Q4+xQH_kVXO>9%OL6`POy||@I4Fi1wN;X~R=}W2En@NxzI!tG9F-Mp0t_5#Z%99{ +zSe!y<0ecIf^Y-LNm(x4Eq{Ze&lh6)M87}dw%liUMr$ZAx=lT}KDYPiOiT6bPnJ~rh +z6ISdsB)jNko((%$eTX%V`}~ +z%_&)6#ZaYk_6WWXVi2@sM9yXT?@G5cU0x}0+@d9tVPl7iPM_8|rgYk{DxHzu_(tA3 +z|E@*PBs>d2lxVL{jsqQ-9-@FbxuM;9|8IAP`{bjk5y+ejQ1#Wd!`=MA^tqZ-13Y2I +zqE>QEi12uEYd`}2U|d=L>+HozbJL74wswln)1JG +z;?4F)XHD0!DoxXR(81cBIz2*2fm2zP?ubf+%|(90;^Ycbvk0CGlb|i32k#L%B7{EX +z324ge8V@rVv5GgG{4yBpn?^smteusjmXL3>&;_<2;@3Rji+ +igP{K(|F>!wx2T6nU-+$f$ADiL)X~5Y>Z7k?ul^TJz)A-I + +diff --git a/wrench/reftests/transforms/perspective-mask.yaml b/wrench/reftests/transforms/perspective-mask.yaml +index 7df62461d27e6316e5d2bc513febfa079f32d9d4..eadc48e0bab09eda5a1fbbf8128ee0e00808344f 100644 +--- a/wrench/reftests/transforms/perspective-mask.yaml ++++ b/wrench/reftests/transforms/perspective-mask.yaml +@@ -14,7 +14,7 @@ root: + - + type: "stacking-context" + bounds: [0, 0, 250, 100] +- transform: rotate-y(-54) ++ transform: rotate-y(54) + items: + - + bounds: [0, 0, 128, 128] +diff --git a/wrench/reftests/transforms/perspective-origin.yaml b/wrench/reftests/transforms/perspective-origin.yaml +index d6ae149d44b7c7ec80346b063e1bc7c045710ecc..ca5eb5f95cf6db9625f220c8b24baea0007d7dc3 100644 +--- a/wrench/reftests/transforms/perspective-origin.yaml ++++ b/wrench/reftests/transforms/perspective-origin.yaml +@@ -9,7 +9,7 @@ root: + - + bounds: [0, 0, 1000, 1000] + type: "stacking-context" +- transform: rotate-x(45) ++ transform: rotate-x(-45) + items: + - + bounds: [350, 400, 260, 260] +diff --git a/wrench/reftests/transforms/prim-suite.yaml b/wrench/reftests/transforms/prim-suite.yaml +index 2a170f8fc90dcdaf604e706078502818cc3fee13..241a2e1c0a5b13c4316abf9f1549643715710ca5 100644 +--- a/wrench/reftests/transforms/prim-suite.yaml ++++ b/wrench/reftests/transforms/prim-suite.yaml +@@ -3,7 +3,7 @@ root: + items: + - type: stacking-context + bounds: [50, 50, 100, 100] +- transform: rotate(30) ++ transform: rotate(-30) + items: + - type: rect + bounds: [ 10, 10, 80, 80 ] +diff --git a/wrench/reftests/transforms/rotated-clip-large.png b/wrench/reftests/transforms/rotated-clip-large.png +index 88d2eda08523205e54163e5b89ea5e3f941c3b4d..c4e484037098077d8a101925d0724fa40f87e7f2 100644 +GIT binary patch +literal 7420 +zcmeHs`9G9z)c-xBv5l>ap%O!?DWpYIiYYr4sjQV2kr+!t_Ni=HGWIA-%n-?%QL<$z +zDd{s=vPWdgzK`vDrsrSy{`9<_U$1jt*LAk*yx-^Cj}6ah^Yd=wMG%BvS4YDLLC^vS +zg1XJk2`g&rF24~(yhc~!)W!SH$Gbg##^3DuG~M*|u*EB#j6*UqfstC_M2&5osps3% +zCTOQ`DKGdJDwu^XNEiCQtdA;;q36hl$S?z$$7>EI4|%bUpNNYatC;M)D?i!h{IzfO +z`ANUV`xB~*u1RgnYb_hwPza(ya)yf|&x<1n$qL7XAVOQ~*vne~tCuy49g^ne+n9bE +zc4}1LU2A`ojbisLa3MDy_amu<*4$WCmQLbf?L-xmL^VtEGE?)4u8qIX9BGyt=B)0? +zvZKu}rO4&QZ`Kx!)>ua6p2;YjXdQ~wlVd7XzFw;6QK)z;MA0s8uH9yVi+(XH>SE@T +zH=iV!6)SpN62$R0(Q7Em!}Oy_{k!%e`krL!$0xBFXQPet7>(i`ZWdV{E+fqs=pVz^ +z<~b?Vsqk6C*2A>Oo%A5d1(PiSPSre2jHr&^a_AaqC6q#y<|0{(B|;aTk$1Ze6I{nWii(l;L%zf` +ztS&R(4e6z8%WkeV5w&?Pm2^K1gA79E2(_naGWTZc{QhXBLv<3=SYoAV3vRH`q;}Y+ +zQFteIqgB;9EWn?aE)^5xHckr>A#;$jKxS|BbmxDs;+x->7)jw$o$eQ&--$)^0{W3_ +z(be))Z3){@91qv-nh&3yD2K#pn}qo5P*qw!s|C$(Y*$s^eT`Kjua2k}S>qA=Eg<0x +z*c)eJDbK$n`yw`8Gs!KnIV<|jU>pN=?GaG`Q5ybRwDvGF=jf`_NKO&$kw77GtsK@{ +zWxIX|`Em%R7ZlNq<+JofHw?x*7^q)+K<0b>1A1fTSDmQ5)x}V}??PYjPB~-=1$QZ2 +z_Aa>Bc!W{^cC=+JOJ6MUQb{fuf#@w|Imue-K7M1LFjYQvN%ubMJL5Hm@SA;D{{4bp +zJ0Y!GzcaIqV>?RXOWor~+e-Ke>1?4>%O#_xWJVR|_`6KOM&4cMdT9vsul6MQmik2+y-uVwL-4mU?!-2~ +zD^k1@gBhs(PkZG@(Aq)~LKCV()#s~B&biOkzb2Q=>!%-gjbd~(C{b`PV9ybs(&pSG +zMK1w8Y+MmL(e1;<5r)cSgMaauiy*Tc9dqf&rZy=Z-IPbaeFNk3xs{nv+e +zu^j;}RVZ-j%XB$}2a@QJ^`A3-n$xWoctb-Adnwny!v&(%%COk2* +zd~qRD@RNicN=FRrky`AXoa`{kV^Gqvjq3821T2gH7KV}$o>OpOe~sXpNZqSEbCSKH +z{jhqGp57)IG?)|Y(iH1Z(T)#J;uWF&elm+L_$0wV)rx_L@=0P=%J@sX1<8JGxoLe= +z`Z7x5>Mg`s!x=G}h&EWg!lLQosoZ@d>PJts(;j08)17LSf62oF`%{43pszLPy)CB2)1*iJoYdDU_d_Aq0V5y!S?B_$BPI +zzqH+Y+AXj6o9(*vs)Lit0gCkn3e20}##(ty^KL$wu8CgxdlN<61BzWMvR;-AQSN<% +z*lTo`UOk}V7L#pK!k-!j=RFMg^xeJ37E4Im88MeB$R%+bMdW}zhUr?mNhtnC!xmVVDQxcDmIf!g$lV<-cE3aJ0Fhv?(-qM#$bp3f9PYH(O&`Ew3_kM>nkcKQR +z{*8Tm_!_e>d{UyEg-6VD*g8zoX=O$qOM3G6eO`*sZ77lXn?dnY`2BN^GkeaxK?Y2V +z@Ax`jEO7upV4!liz5&%w +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 +z+IPvD(&GHdjFbU1u`T +zpG952^&*Uw0DxA2lhfTLrxAdN_98Mzed^Sv+?(1K|CsJpe~#$BFQJCg6uG^7r%iBx +zPg^yek%rsj<|FGX^iIe49^XzSN=)Z1Q9sT9@?T)Az^}y2t%u7__)ETgWzxw>HR==T +z9?9%PCqVys*J?mV?Zk429i9?%Y{3+Y!v>#lJ6k|J^OJAVFGwIZPC=B)+vzwddUCwH +z1Y;$^rYG8x^P%ax4H$?_SGcm?P_iAi3T!%ImQN~Oaj^YGvK=vku6Oul`s(bL17CT$ +zfh8x0OHSHICi`}vsf6oi+?EEGC>(M~FEC0Bj0n^`xD5!ZYt7L3z8&2+!qg|DR)Gi+ +zQy&Yjjnw>(ut0A7o%D+j_Pmns_?#iC5{7*t +zRt>mH2I~Qr9CGmPf11n|lTNfL8}v0^8SC!U(sA#S0>Ic#iX1OV*-&{m&cIXVA(98D +z{yg&~yWS5#;q;5|FjG%4|I@ZW;DsZ#wWY4>9n!d9LmA3xl`*-II177}{U&Hj5}>X> +zHN1aW96IKJsgla&RK^Z>Xyn+kEa(@#OOLr`V+axZOcqqM&K>=L&gTK%@KJTu5L){t +zRsW`=54?vvE{*o~;z^r8f~pQUfBkkVR)M=di<5sJX#%BQD{QJw)UoV3 +zs$k1EMgJoHzAz4|0gS!rhislmKIh{iRmO@|u3q$8YO+ab=bYJprkwAG!h_Qg?E0*ssVx7&K8``pRm4eK4x@Wle~0y8Fslej}TH3q}#Z@sNSWFA6Z>36dzJJRfPl +z03D_Uq6x}TO=L}2j-SAzi@hcMG5Vo+Hu{fuz=!}I3^CaD$7<{6sd$No3{O|JHpn{= +zfO??q(2W6|k%dm5b&TH7>MVUMuLQ#$L;(c%c&RGNcAXO1?R4YELi?wUm28VP^go%V +za0WTBO*F6E8nyjmakk<$%Dt}#;Y285#O!x2^!F1{fda9{IYO=JUhaP79JMeqCCPJ4(ghGgOqm^btB +zbw&^H?>C|`bFdhv`d788E2-81k<(5bg~`^#z}Z+ +zWMBM@7E*^zk70J*^qw0=%#DTNI35GBjC1p|{G`he2pQ(wi0@wJ_ZNEMPL}HL-5AJ? +zFgUBA^>y?_WiPhY!m(ZJVF1b)2Ga1m0SpD-=#)W)yxj`$Kia~GJRlyZ5e4~9)4I8l!3rDCI!Q6zY@e*dIqkg%Ue!6z2)&uzMQG92Rem%u!lMH|zDZ_iM +zIK}JjAbw;po*pYpJ}fxe5-o5{Kp`bx6URv2}kWZ;3(0V)EPvE@pLrYOG6RV#?KhA{7sp +zY9wjU +zTl4$RGkpvDzKuktmZ?Q(4+bh)*F~BH5>`MV7HAgqh0LqDGXoSVtOU-?D!# +zrA111Q6fcIl4UHzJ=4ASb^n3;(|z3^=Q*F}Ip=vlXL+A9*Gy073G#2`M-W8NKwrlU +zL9jvyf;rF24J%r!{=*0&-DIG1#60NH#6WOQqVwR(-)(RAlXLW+?30s=iaQod(%Gnf +zvNwH_ap$#{ymrx9`y$493!Qs-LmRVuMG2+4aXNjbwyvr#{)C#?NArC28(3hI^S-gw +zZXJ4*@^NMV{?SQ)`|jY!pY7TCA_#*1BhG^$c*$lAg6JIc1ta24O2hKm|I^E+=0Cd( +zK8J2)f7hDl8nbHm4&Q3QbfR*h@^E?aroPhlmoi5Fqx$~e4gY*Mx*=ioCUN=m@;rI0 +zyY622g1VBBvb#RnTY*wnV_sKlo}cWBH0$~z^n<%C^}VaN7$?gcC&{)x*cP_>oE>YH +zZElc#O0UmZ*zI#zCYNwRVBf1%ZrYU$Rix#4Z*k_1l!WAP+XpJfwl#e!{^$ECnWL?l +zA59WB9-Qx%Lin$KMTo90Z|TQwC2S6EvNsTKFxX)rVMk(rQ<~?Ruqwk4^GQO;YsxE8 +zHHcYx3rMBG=G<%SQ5K|kB8KzX5>E5howDAS4BvrZJO~*vvJ((3h<{MQnp|CLA)^a +zPGSS92skCYURi7p{%(+=w_BvaO2RdH=i|^K9P$mk2;ZBo+wPWS;QZ27pY9{9!&*vr +zHCbE2qP?(Br^x;8T9>BF&BzG;%qnU6)6J(xd_qTqiDi4_JG>klT;&-=1 +z>_!{JbQd_|LvgRm8sj7~Ltk}|q|k{du7n}tHrjc(yUKXH3FC?J@WQKec_nVcDPAfb +zE|~i5oYPG2I|a8Yw--1XmipM3mwTCIooK&D?&GEj!sU8Wi`(vbRkac%eOW`RDT}FB +zP8>s+)s{)Gb(pX(dX|B`1~7>&0#uUS-;Q4p3S$kvJ+E>lAm+l>hOvULTtmVHLz +z2*^szSL93GX}@a$6QyM>72}<3rqPB67Ug*qBvln*J@?st*5F!+XVUc5X$ym(rT1me +zaKvG-JiH{R@UsX0*ty~F*_LEqqyg2W7tFrd@IEZ?rIo_peX)@L-mp_fIs +z`(SNQUnKRR*B!-*dIBkaPm#KG@=E__d1R-aFS5_;wfI_W@SuhOsW@bgvH#t)l3BKe +z7~NK!huCPDH!CHo7h6Poq&ja+T`Kq-PN0bca*|#7DRmioUX-e5_8U8y@g<#>Wf%r; +zGp6O~*vCwG&)#AMqLR?zj%XJ)dx}6y02CxkydKxxmQgw0Qv5(U^W9_i-S;W0XQj}F +zlhU+x8}~KIWMudVsn)k?N{5x;i2ss=&=+qXjpdUD!~3!P?l%+|w>Gie+UEQFx#=j_ +z=3IFp9=sBF-DRqVJ=Xa9B^eQo`ih)w;Sy$5&3EP~q(03y|Gm4m(MqA}my;BF)Kr>w +zll5aORrhzF2LUf7D7ExH_l_v$8ep^UYxN%n*{cxelyww079m!R{wj>7AbY`)_kH>O +zGAyQZ<{JS|rmhy(Rq|TVqOBxRX|OtBdG-`FQPb@XPVQ*X5?;te#mxy515QHsOSIcH +zH|8uL_gYAu<@#{jwpGa;cnPq&IXU>n+83K59sQ@4&sN5?|BPatE`LQB<|KhlYWl-3 +z$!L0HPIlw_+>a;XS6O=f$aU~6hI!Co5xY~w!$O)p(Jq_(VsNSY01tl+E4`z(M&_nY +zWyIcrp;y{L*uqwoGK~0!W{iH)^s95M*?pl`uFdvQso_Y!DWc)FcpioyJehqtg* +zK7SIfi^^9D{B#mM0^o=zsdXv|^LHCa)#EgWeU8_q`lVxI0b +za;^yW>pnx_Y7v8rin5oNw|bl<-@ovoI7=&xcmE#RX~=6kr|0`M7yU+$3()v321TUV +zP}w;+Vl>C-fN?oNNhg6XI|2uL?axHmY_rJe +z8AI9uN*sz^7I$ASA1p#Vbp|T!_NWIYWLuRBrrw0}u0+0k&tB@q#{77iu`t6F|7ms%o3Oa~^riPo6!#tmAmtJG(TbCz)y2{v=mm!}2 +z0M_(2%|E`qVmoXRzg%18I@Rn;7nyd4`;jh4-E#W*cI_5cz1WtF7qRRyZB~Z_Ya1xS +zi*09lU%eqgMrLx&wkZiYPBwd0^>R;hfebN9G~BkWy}}p#eqo?S<59pJ^TKor@ef#m +zJ$>m}z^iR#$aT55RlM588fbmAs4cJd?f!UgL=! +zbs+z4vU)cbe@ah!D8*}Y03CN0+l+}xQ}zhu{lH!zBTjjN6q);-nK$y|ykJYzWNp7I +z4`R@s9+?uxYqEuI2a7I)VcklD!E0dxXhXywp`lOOs%!XnM~Go?nYR&owO6@&vHAtm +z&AO_~oV3W`2MuvUjqHP^r5*|hpTuMc??ugsDw&2&czvGhTzD~=&@cI9X=E&gGrXIa^a&?VDUBO2plr62|&9AvAekB&u2d1B5UuL%kFDbnU) +z+A8;>vK&#!_)ey?-q;N#c|`KUuflNOj@o5g^cNT*ySU=NW+6SpD0`1>35%- +zoDoGKmVq5{vy%#Ed?S&SCdcT-kJ0xO6o~g=Swv&ukru_eqy?VXQFV)M7^FWp_F7@Crlw&sw +zkkn7(-HF_ScOEDg|D`m+Dn6+lnKu-Wk+*5lhF5uvO5JVHBLIy?$kbt~uE;7sv0Sn0 +zc;$>CIW-k-zxl$%t^xns@crou$C=t?20=kS)d2*_5i;k8JR2V<6Oa+zihjqQJxsH0 +zX((q)ME;Hwnl>TBf&$3QlcP8#p_iL>h;ypX)jb1KW&NHQ;?yk-vyF$$vQPJ7XKsB( +zMtU-CFA2u&wzNj*7l)LapPpZ;p`0?-Wq;(Xl{>d7G8y$&=5q{*^kW#?&Z +z@fnEf>kSs$7L8Y#DW_=N!^+V)H=9qG(5GSO0)C{4tTtZ&A! +zBDWbagf=mL4=W)05nFH3kKMi(zD)k>1k(am_6lbLlN1oS7mdP3l)fHpFc7Kmugwx{ +zbi_4<4?TLolVqIy)*9t1iCA>6$NqD +zrq?&WPb)*{8-|Lqrq}uv%1bzrODt{>PTo_#X1@mCinw>*ixN(>Eh(U-r>YX;SJ~cf +zz_ToHw2>bJPRfo|q&G9X7YE +z>;PX;pE%(51tk*$IeY%S%H|QFvHB?o$5xj!2QHS=dN|*|(>PT*EJTKbs;2H$1^{Ke +z23D%F9Nry74Dc^LMSnGA-oZ7AHe;9)nsw{qXa7Q9Hr@%;hErNRPfF86rfR-7`l@c= +zYa)NHZMSjl#TFj(MaPYkf7zSop7Fq-@}*uA2X!oSTrhtHhuH_j=4h%pr(U|A{8g2Y +zYuXH_9}Fp3Q{f^e=hawpY5-A$B&1)XCvy%J!dpde+Ab)yz4Pf7IiM%ZgDy7%WPUXf*EaEQ}z}}Orl*4V|QKwPBii>t>d9eD>cLBZ&ayHqtw2Fxh?{o +zB+kPaTv-gqbW{gp@mvrp+EMy?o6cdbfnSb4v;D?XU>DvCsZV_^bnJx`<+4sXK&2Kq +z8xI85BPEQnJQ+XURZ9E2>{Z)KeS{Lzo8Q&#z9NGhrF +zV&Y`=M}qzVcv8pSeOlkAM;~(Xt^L|~ywb(NUaV;BSK8bEP7A52yUgj(!Kwum(3-%F +z_rHjZ?<>38m0O6UI-EFcu~DOkppS#2hJWUVxitH?K|hJFnjFt3Z9GswGY9WJ#l4PR +zd7&6byTY4KI-xZ{(C0YQw?A!RP4gX0C_kOob{H;v+G>&KiD4drNSyA#wA5|7IC~=* +zm@?K3aC^HrW5f)*fbl~Y +z4EkK!FD78BrrnZkgSvxD1-EHzo%^*jjFpB%&dt2k3ULFM6wp_`&gJmpX52e9qX$~{ +z$m=idMu+w%tQY6`!V|xJ(c{J;e~5@&2qgSsmKL8RbVA`ylc$+_!fp-!2p}qMHxyPr +zzOj5JPXzv3uatXw=RkN>KrhrjPxB2{)IY6ds0$?MIo725*j=lYQT5siq$}S*tj;nd +zdXqeHKWsWyJ$5|0yL +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 +zhg~WxchyTpQc7|9LBH~x>cT&@v>adh +z)pai449!+(V9ln+h+CRwpQ|or89&84Gonr~>aj6b?=d{J9`)v{yE&X6&1Cy<+!RDb +zAhv1pzqOw(S0(&j9ZnM0aQ|DbiN3NxyE!Z`@7JF$?ze9^$}{GT!O3i`64AUPhP#!B +zs(%yBlYNTs&QAG1M(GM~CWe0J)7)2&_Dv6{u$hNE+eoZDvSFB( +zF?*`KWRclOi+hApKNFVQ(b7LG*s@`=nbEUx&YqTp+xWd?R~H@>nplEo{Kal@@Y{D| +z@9E5g`*JrVkNPCUVf0mM57QOl96?2RUtfuS)ks$~)1{%Mn;~NGomBAm&S3R)=l4Bq +za^yVvM2dun65upj{@(LV8-lE8NE>6%)M#AqrX;G`o?rpvl}4ffM^J`k$a-l_PO~=I +zI9wZviWug%5gzH4fC%q?_Fm5NXwMUf7Po| +zpdtpAqmRaHMn-O~F&}BVz(1x*dv`Tb89xBw8@Z*DhrNh(%imkdT-_h2I~=Y3$gJG- +ztC<#a+FcUf>$9}C;ZEYMQDm~X21YHZxM5RY>I_Z}m-y)$iykL@B3VqmDTzpKRFEqx +zFL91B&})2sMMkolk)a9eEEYD<3=^tUG{lEh$ryznHeP4FEBP8N^D5@|f8=&3WSub= +zWfmLwHIN=(|G9n0?IW(Y#6?9#Wk*3$-O7-pg-n>j{K7)Odl8FUWJNAA$C{&evrq|B#})t(jc!R1w^NSY?12yPDOoj`Yk8^9UVhE}5{S)1M!G-L!^~w@7=hO*3@s +zu}f~c8aNQp%(!>vPc#Y{cCE4TM22)~SE*2l{d0ku8+6-pt +zh14MdVeq>xo7Ecm?UeGy`DM8|5(>!FjZlaGMQ6~u>h +zkO)JxmwVFi7jsuOHLB_G8XSVRDOni{W-(qn}(Zi|n6 +zl=Qy{zncRuaFfg|E7j};D5fa#zv>jelMgmC@C;ei^pR+9h>2PJ)ul!OLXFzA +z1A2tmBj8IV6?pPTN!RoG%g{LaKdoWqf(P6sU>9mCf`arHOKS*N$xZ(P!cL6A46u!e; +zoblbe*nFQcQN1j#RNt7vmv>gO*EevBE}vE*Ym-#`c}xM&*5h1_cUiq9;|a`*$znGd +z?%?wK&nD}nKaStIMQ-7HXGVYu&S;Wa(TKR{@;u=p=yut#MyS9OFAsJr~?Cwrfq@PP#v| +zdQdEWu&fG`-kUKsFuchxxt@%%Yy-w_$qqeczB0rze)K&OM$8F*{r9b*HYfKqF)oGV +z^B!#q0S}R?*8LR5%!$X?rs1F17zeILh^Bs827mSXS@um|>y=iUWF<(uv|S+x;d`z) +zuH!F+YR(_JlAEK40hO2L=jWRg!PUE%xCMA<`eu(zo}=*H#x)K$YEh(mOyW$;W*Eum +zMp9}8$xr;U)cWX;JD=K!U8Sj~`lS!iV^I_+5cv#3-U)%z)czq_C~jW%haC{NGP$-W +zbLNH4e7HreUmwIoD_-vKHx(+nuPtZn+7pYk#k}7S~}QDKbGl0bns8DqGU$x)`YR%-s#clW0`s&L9~J6oA}Sf3!!1S +z@=m5>^rng7L}C|W&eY@TNM8>6rW!zS6)E1(>r3cafBQq(KrMG|%|q4}E~T-MDE*`@ +z;Oc#OQXdN(KV5H_ywH8+*s;Txh=_2*0_{h;LTm2!Y*_9vd=yqfP#|}~U#@8BrW3OS +zNVct$QMRz!AD48`B}`e`Ne@Sb92Nr&Jd8`_X?jEbV@7Q;cRYh-#-w`Pj)303Lv7OI +z?g%=ql&jL8o0htMXgYPg_R6QYx?G`|(gEgU;wmUCinvzc92`tZV=t%9@Z;N3AmTLf +zxWg?!on8K=;nuL^r8ht5vhr5IddwqDJ9Nnya}}7KCKQ>(b-~!?o8l$<(GyR+i~-ND +zzoCsT4rM{eF;__Jnj1~G(1sh_D8Z!jSpvu7Pgolyp4?q9s!UGFMbqz>8>1fqt0{#3 +zsw!nUbff?)+cWh4cQQ)^1sndxk;RU12+~jV!?#7L+8h0WsrQ1+6L&3KB +z-5ql^L{4Pc*6!-~^MP7!5?6bVQ*t-I&)|2i7GkMetjAX$YqQFol4q>cL2 +z!c~L|YJGk%it+%VL|j{lHCn0Iy0`vS2c+18P7zSQ)x=kV|HG#8HKhJ8Cm9Tby?nAx}Rn+gi6aP_e`nX;VbnVtuj)p?JC%QeUF)RL*Z# +zx4+j-7@A>ITTN*7`H;k2xI};aRN$a~-qlmw2W8I)>y2g#2DZ(TI&jIOfJp=GqvWxl +z-`YxRA%DoM?p0+8*+%--$2<(QsiF|1=({o$GB<1aa6f0=o=aVPMVImqNPxWThJgpN +z(&LErXw^%>Y4@efpl~|`?L*60R7;fT#e(sHo5=3nTvACppn^@>G4?pm<1AA^KvvEk +zvgjht3I<(<`6|5)bRw!Mxm+B=0(Tm~u +zVZOr4dOEJRwiM5GSRgEc9hoc_HY9ljJ;&xR45`4!{R)T;M8xnNG+taDWuQ4MO-eNKf=>UuP>#^w8c| +z<#1{5xq=!95^t{wfn-^%cWpsaUR=e$uhZ+da;0b4yH +zFv+si^twx6=6Y|jvOe(?lurk;99U?#JA}|p@Cb)Gp!6eAhN(;t9_oaPmafGx0OH>H9zjB>HNwY|WpGf1oT*X~>wtjLz#DceM +z!;%98D9K>ahG%EU5bR?b@}DE+&;(Is{Sa0oFZ^cfP$^h!oqW4a&igIP=166kjDXFwD;p(|Eg%TG; +zNwg8&?Ky={_TEU%Z2sh?Y{Exa(%y0_nhGr889kf47oe?EfXHW%z|EeGh$)d&1jXJ> +zTIM`_*Qv$~PT-Gx1DM*S%kl9K0JHO~p_mUz9galz7RtNhrvbSGy6oc95511kM?Q2j +z{!j!m_K-Nvb>WMKAXGDXHzMIBwuKXk;7xewDYU5qvIlr|LHV*MZrNv&b$2)Mq(Bk; +zVncL=YJ1AQ?HP_J{BYFMAlS>Ze +zB1TUk+@Sq^;{QajbeDMZSrkdLQQwe@Ze%bdc0CfRhOq&obVaVMZq0D0mL2xTr0m-U +zJoi!3G+7LE<_M(7`0_Y76sUk-pEE761rtQJRckZ|l5XtAk0gRD#{X{B0;J&m_+S5m +z3p58FSAx<4q%fuSB5BR52|=MJA?zRMjbIoP#?0L*xSvmhi%dcjsGg9^i;BpLiQ&s%l0kyEUKt>_e3Wesvg@|awCX;ULm&S#2QC!dm?;=zoDPZ)_qcTY>WaO+GZwj%c)e?z`qvj> +znFnEF8vIa^;Kl4KI}xaVva1q&WwRZk`Hp%}YD2Xl&j)(x8$N( +zD)zg3OJH^lGAieqc#i(CG7OaogrUAI1xOO7T5*%+YNNJ^8j!nY4_iGAp-&E)gmVA< +z^ZWq7nftUfJm~Pe^9#!x%HSo)UG}WEjf@de}we0eZ&1wcvds*~gyWckmYI-!ZYxO^&NZ3}>~Ny-qa +zx8mAjG|!~Dp$JgK0*J?AQn3Egxm>G9azcpXU!g7?>eUP1UEt!XT@u4jl|tJN;9q}@ +zC*|6ylJ~b-p8FZx*vU!O1Fkj2wQy3XT>Fc&oy%L+{l8x@4nVcyU~ZI{R}V&aR1h~| +zvpvh&2uX_g6WU2ZdSf?ZONt-E_nnIRvv!w+Ca7=t97@6uPt)Pj2JkFLxZylh2DYF; +zd%1oPyr>#p&57RDwKBBx#*Cn2)Db+*Xj|7zttgbu)ie5eA^zJ^)It6mvzQx`{EJf% +z7=hDS!G2j+h`PapN8Wr*$v6Lx+Mrk#ASy6^>Sl)!m10Nf0$3ufo4aD-$Fw7#q%#+0Iz%8~3YK+6~m| +zuf9@qwD;r-RmGBiK==}TrW#Sb89vFnSmKdNaHs+Te1@Pe_ode^RJgzQ<$iy6mb~3? +zltA7(`B-JQ3}{vP4OVT;7_bi**;0C;cK0z5h&fI6*q7Ma|C0_8B<}{S?j)r?wWS +z_77-52VC6cK8kQG*Y%>P?3r`Baat-;0pI9H9o{GMpy`G#zpRgnRV%Kz^Rm_octdB; +zQWfF+{n?3fegp^Ik*oK8*$z*)>~T0T(6&AVtr5UtCQ{e!IsGz`yQ-(Oyt!W7RQ4H= +zP=qPPHHjz-`IVcKe7GhRYNQ#h@af+u@dvr#r)LSJUObdz71kL*=B4r80K?krtkOl`4>L(! +z*%bhQfIlGs9}oEDa`)XW0FWkGnVDY0WG;^J<1ft&^H980UY&j1b#w6H6{>klPmSnB +z`9r7goV?R-lJ^?-^nBFpUzdB%!G4%no}zZpto3oQgSj1VYO6skg_|Q_KiHw_2Y>jd+VGWp?@8@|fRdTFef7_VGQK +zzSy;#$eIyvoZr#UCrMIzyxj39%^wZ<)sJj@x<5?|CDO37;T+D~ +zp`{syX&*S@DLa +zPL+Reh&qn$nx+HVLy%9KG-W(ddDH=ueYr+GfSKwRRQ9H+^)vEDY*k#dm2YQ+^yQT& +z&qYA52T2cYRhfl8h^@M8wm4U=sjwP$BSY+%c2)YXn#xG|;!|JRz8oI^ET$yr+be&` +zAbq4csb8-B%)p?|RFVedS|OjBt9O_qyzVbVnFs${n`~LxwTbXm;RyOo^)CC|3I%zEbQKP?`%`IlsV7C4B8goM0vt=BvM +zLfJk0m&9&8b@>6mvX%gC|2S&jk}At0TBo|TCc~OX+_}Qj+v@MpyV;A82_Et{JgS(1 +zW|6mkj_%@<&<@)V-2{4*Khf}5(Yi@~xRb>1?vrMcf~ohp$7EA;E(^~_Cj +z_RYZ(cXudh-RMGi%De%^v3b?Hfi@nNd+XcFcQ-t_M3Jr|S`Z461R=sAfB$?>pNds) +z?&PgRojddQPm^4W`0x0T0O?2ub<$}2puE0-efx%L;s-nTMq>Y@nEsE<@(XwM +z2|mj@0p#vB2e4x4Dty_oPf#D{7!4*XGIdhB +zBN~lOoMFa@DQF-GSade;)C6-kI5T86eKI|JfVr@(bztP309jN>&PbYQ%*0>6d4ezb +zi+{UJ^o5hPN2T}u%H!shGOyCHZ`U66{O9<>k3JB;B3AJt5*ERjeEZmwxxL(&7(8)b +z#{lqhUTo1?wNiSu7B<905xX*V2z4+?wQYP>k#(vx$ZrkCpI-BA5 +z*x6NpauP5ub}YX&!dNxiRo36P@I!*0&BX0wIk7B9&;3h?mXnvJhi(0r-=YuT_mOfY +zz9?l&XQk6h*V@aORjvBaOcdc%boF?ss=;)CNr_#7J$$jE*EZ?uKJzLq`}QC{By-1o +z+Ma`rF1t$=CC#L|S!5gCZ%&WQob7}q4g6AT*QG{RZ&n>kIHl1eB%^oQ+5oRWAnLCF +zyxKyX8HKV!;@|tWL|nemsZu2IAwS`RPu(n897lW&mceA4arZC%={T+LwE<+bG#14) +z4qJ4ST!y37kQ7pr`_yD6q}eii&!i&vL|nS3NM +z1-j85`b-8|a8oL!tW;x=P#K6hiiF-CJA|1QCcV$k5)cQJ7BnCgeiKI4fWaQ73_muz +z=_r91FAfyM#i}>J87{6OF{~&i>hi$1Zj)XG7q%z_ndzf~=^7q<->d!H))t1e3IU8^ +zx0ZkE$?LY2fA>+wnT@It18S>y&2)l(b0Fm@F8TXz4@hsr8RCm3GaaB^6(;5{=~?TthtJV+fD1uys4qi>ufwADn}0DLax0I{Jc^ +zs{s9(h#WALTNi#lu^gyHxBnY0d#HP8B86@Z7{>+x^ubfh+pFooIM1(Fz<9`>p>c%F +zi14Y*2t7)(AedWN{+zSCq%TTh-GHdw4#aJj8SjZBa$ZqYT)4uHZXJ8-qe`jQ>nJNS*rmXjY{E4Mxnj8apfnY*1HEsxGh +z>Gs#ICUR_uO#`dUw@OocKQ{x=rmypRNzNz|_Caj{XE!Q&xw$QeEfff2IFtO@gkI_y>aXq8zD@JFe_ +zn-kcNL7D!r5?4wo=SS)kZFeBJS=v%+PW>K4wptVBJ5mAMl6w&k> +z$7|4QPRfbhf+Bx@uu~l+!y!>$uuaDw^l=rhhB%fYn;xKpj`*qH^}qa4U&Hh33yPPd +zwVKdJp{`c>G6na*@D^dh`T#6=!Ut?dX~0qqtY-5T*y@SHct!Gq93i6u;yMIVHS6&T +zkH=GjWXQa&1ze{vBMD8_uv0aAltm+qT)T?NS0DGjy;yD&gSN&+T4ZSZeE$?Uk9NTw +zVJ$IQ>2)uTf}mQZ0cm|{W16QR6w(aX66WzoNf;n|l5EPxq3hKFUFmhps8 +z>v`<7r%|cGPu&FXyAQRbWCvZoq@w)CKdCCm%nG1Q5>bIX;!r<{l~39HI9@H$poCxL +zPNX$g9-y=tNzi~Kzqcb6)j~XRv +z5e$Q)J}bFH=O&C_&a6eczb7I$<+9gnXGn(?E&+_lGz|oQ$uBY;SY8bHXdKlC**4#m +zD$$Er>$zF_08MHY;VmW +zN=H)9Q-b$4D15Ws!p~5QrDM5XqF#>lE*Io?cPO1cJvWAuf8MGCjXx+}e?-|>&J~1c +zw5=Ap{XXVssow5f?wkxRTJ~?&K8o~wlhE5!jETG>cM?*`%v7-voX}ZVRqD= +zxr2Kmj^h`9U1ihEr4e|%gq+1Pv@k)*hNpqC_x8^YHpKsWai0QxK!^w#n{+66R!A)^ +zXR$D{U+u6u5mbchJC$0tR(eGw4Vdlb4w@Ka-=oqdU|P3;um$#}j=HEM^9};3> +z3egDDwkqo$5YSzF+kg`$?M8mor}2=f^-*reB_SQ+dUc77#MUZlZeB!_nd9R#&KlZHIbC3`BV)Ggk|G8`X?}Gq*+Yx!I +z!USEujwWLf=$7I;mm%FtclWTOi5Kn|iIi5OU9>HF8L&F*c!A~A0Qo&ifWQ1RzXvbR +zyi!%h#&|USPP>ZJ-OIKUT)eV#qjV}~DVGD8owxu;G%Zn|dWtB2QgKXESm_0>~EeWhpU7DS0{Jx^qXO&3q=f+GXcQJCgpW +zFvLVetc8M#i=1?IywfUK`9r;I39*#r$J4R`F^X`i8TqkDC8z^ztxND6b3sy3bzzHE +z)BAiq+K&sMnuj_QT*DO*r)r*YObcjDX2{zBqOsozF)L(X_{tZ8l@jFaV+4pXfEegY +z0N5C9jAh`9r%7!RMJ-yU+Z)~Mi(VZ0jUj>~3PLnQ$z<`Ltp8ctE?W*RZLlhYZsI0v +zl3_N+KHw9*pnD%}+?L +zM8`DsWcT{Q9dHE<#gz2Y3tRB<>2L%8r@(-ZVZkgd^)%>!o9MkI>B{@!_m-Djs(o2@ +z)h$7N8xGb`Kfhbd$U)hVKiw+@v5ZFOqepxQC#qS#tPj&4c%KY}X!96PRTm>knW^5K_Z7OSs|wE3b)hP?+!= +zJoSTg0sV;o0lHC0k~CUNW{D$YjHz4{`ZVEKD$9+gQJG!?-Ruc9k&!uW#-wIJ(2Y%< +z>iWT7RXA}a8VL$X^JNAkfIgAb`&lb)Q^Ag4#3VP8f+WRhu_9RF-d*{(C)@9g-vtEK +zV!&s_30$ChB4{b?W+CWArA#dP1Yy867W9@y{N0Zs0id@)iWO79PPX7uPOz5cm4gS< +zCppV5HwfZaMbK+K{gdg)Q8-z|Xpm#r4h_fVR@Sm~YPSp~hm&d1a!`<6m-*H%YWTkn +zh?jC^n;W^pPocJ(^uA@OS@9qr(&nJg(tywxw7`=&OU($ZEx!GcFZG$M^`D?hNL8%Ar(JXz9(2@>i&2Tnkb+m(UJ3+#0rK2>m(mwAHkF~GMO +zEK?Vq$md^_umj~Wf$rc2fJ54P~ZT|I}ch4MR}{rJHAlk2YC@B +zR*2bOy4^5X`sN~F*^DM +zV0H!ltI?%)?h;{TrhoFtF(7Flp5|71SB||Du0j7Bh&jh^ +za+I6QbSRcJ6xHwpeZszx4HVj`hRT=Z0IpY{d#swy0Kb1e>E}w*?bS1Q!*%%ig94zh +zSguImw9m^wGX@}|VavC#6I8Aiwwz_5tl5jswE!kEPKAIinyqw#`}-TKHDBx#@gj~0 +zT{{P4wsDcu5%~Wn!i(uR(z6X|k1rPyWm!ij!44bC!Ke6;U6L3p5Nq394Wtjl(eOb(t(8u~ +zoqPy*BE+vf=KyFUH4)NL(8Xa$6we>+thx)9MK9G7un@{~(0xxMcEHmZbhw&#Yon5b +z62tH(m33k-sB_DPfv-VW9-8_$8l)wtUJ%#TS}8}WX4xnw0F`r9DJR&E6N#s;^;od~ +zeYyt;-KgLQ&Go=)W)9>y>*Rz5{|T2Y!y0NpPv;6}dw#}P9fsIl)g)N5ybK3Aa3&(a +z-NWD6LyX|FYG?@Sa-S`njD;VJVO=^n(!y!II9=9-{dfw$`kMCvk5G&(-%1iIF^mMd +zEFbU+Kb7U*V2@AoG@F~&Nb*T=Q#oVa&z?;+(vqp%tDG_4*GHzchm|FG);VMT?!2bj +zqwrK78hhNZ@1Z*HXl +zYT#$FsCBl8bq3GR0u)E+a=wU$*8z~VXpl#|>kYLMgCN;nZyb`Swe8_^)G?A*_Z{f+7| +zY+nJ+t^oTZ@6WXJDO~+Uu>OzSAR(})c~E<8K$-v&6FfVyQ*A}PtfFzGbP)xxs0e$Y +z0kr~_wA6RDxEf;5H)gTZnNV5I^sZM@h7d%q#hO_TTRzQD-wAeDUEN9i&+{~~BIvbh +z--_eUIM4OYIPs#3>t6>#?&pF2X&Z2}RD~?p`Bi9^a0E`PgtBPbe3^#>NL +zjYG&wg{Hbd4R&k(NJY!>fW`a~zZWxir)saWb?)!|U`eYoW +z(%Mfb_)4ghdCt#YkeQj7?{~&6jp0W8cztWz^bGc!EgP?ki$*Bo;0Mv2G2-U4Rp?fJ +zr2r|*$(^G3uHwXzQBBQ}^aDF_KsxqM4V6DuUd6Xtku~f +zQ%$q_k%HDx$+r$~KOc|nVer3{j005K7awUVvGqh`Kl8=>M_4G0XQ^IdZgfb`U}jws +z`2S#&&1bc>G?Oo~pfzthOQpNXRo8lYRB1I<*L-7Z$nFiUTRLN+`W&iFdz2+e*k_m{y#EB +zHDxUwo?JVWRU0O2%9KQVOWea2+!5);PcnQ!8cRruVcj(kGb8ziiRv9=DXiDVD^?j< +zmc6A{S1Swd2)8R1FwH6Z8!i?|K^Pe7a;hCM5$b2h4<5LjDD +z%GkWVzBTJ5v?G6BYHLa>e`b5NNtAJf8bhO=y*CF3MFd7-BhCrtp8tEHdgI${cZb*Z +z;th=d>SpoIa&@=0)M64YLRu>K{K`Q>(ma*DcikU%Xe7IrDzloBnUjboQE6{fbPD%_ +zBS(&8Mxbxm!?L$DeoBF>JQoRn@uII9et2(MF3hj54#$QhP?{Z&Rcia_1J}Ug( +z|Ba03*huygHA^fy-J&vkD}u}OUFG>k)Xa(AJ?`J6a~8m)za=m@jNT?>?L& +zc6IRe-+`&`M=oA4-C6(KbE$gWV2yeDJ?>RsG;?0nr;&u}P0KgGo;*ctD50-vz7cHV +z$v?Yv?1oVqVRA<5dd19Ij0FgFQM)Smx7r^RDdu%5F};FPs^o+>j-sP5+MA5TW7mTt +zuN}#}HInU0^f(0_O7FiZ$;p#($LSLO;+6_g^j?36SlU5}z3Gjm@>}Jpz~D|usXina +zN6qk{$iI`ML90AlM342|amNVxjvx=E)8AWnF5)Bg%2Ql27w2{ElSCPyMozgmua7kp +z=XC~%o8h+HgbVoFGVI;_G5xiRLr>s-Z$_sM74MFWupH`cHyh<_osXgFg@my +zBE^&qI=1FcKYObw^$B?an!Oje(a9z?^?J0s_@e3gt&yYEQ9D^oVIPI%oI1|0h$`FM +zs15G^T=n0CFLlx^hsu~wYt@>5d-YL+9+ +zN;cjFwl9gW^X96@g&*nbc_)u7<_f8(p50!BMGK+Ywb&n7p{qQni5{D{;}|f#d{U|c +zgzIGJvhiNnH4UFQsl6p(BK*Z66>`a20-X$X*1b{iUk{1Ab~w{>B-@baal(Ig2JznN +z^5kl}(|kMx$_dHkh&b0aGTzNwE~ +zOg%LH{*Cpw)f?y5n19t^Thi`*PwF%E@>tVP=!?!6xbk4;rR{j!rCSk(pT?@Ur>nd5 +z&duO1RecYJt_(j@xslE)w95)Vz%vlnXdziVzP6 +zmXRAz@Dg3WUanpLo*BmkD@^@ia9hkN_? +z89`hyvbkM%P20?Ql#)3&gWxi(CASRiyv{m_b^)@Vo7oWO_+!ucbg2=~>%1X}f`{J1 +zjKM=w2rh%MaLF(sL%^KaHK8y~zL3anL7CIA`bY7rA9cwc=R){|6}g?+zuPr&P%D=f`hYCkvSMe5u0bEovj +zj;YaD&yg{5y}R2n_f-D0r5@nfQMrHHUnN=EZ5j|{P0k}l_wGAQxn)gEQ$ssjLr?c_ +zUZ7wwlP2DV|Q}+Vuijb;2=E;gPy68Q&W|g{aM|HZf;( +zr#}EZ;X$%SFRI{q%j8aja}(dw<{(KMBHM#b6?3&@J;h#Ib}Hk~V=IrO-23jv8P{}J +z`55c=--w(A8&QS=|3-qX9qHu)tz3`4UzBy-ItkP7G}6bkSac~K)kv`~Ps&+v{o!+< +z`|f8G?gGh+rXI^>sIhQ-4lNiGtBjD +zKgcT1@3?e>G%6%gci{Uy`Gy0BiSh^>rEg$6Ga3*tB7()gy0LkH^Tv~>EkTlfm#x#v +zd^g1^Hm9q%?baiARw-1*W#=>~(_81}92n^7RVseJ9*t<5BCI&ZeY`K_b`@`dJqB^rMh6+|RrXgArv2*O#;4+uqr!-dWkD6gv-3n4XpOtUB63HOz<)^Qdi!4TNMdEnG~?hfz2I6hm%N%7fKr$>~i8R +zUA?U9Ht4?CHudc7pBQ~6yW)2)MmkE_juqSM(>+@&LqY|T;BB_be!pl{<%{fYTum9; +zv|4YuRK->xDOkO^u+(o6slpejapnt$K-KC?oj84_(Z+B!d3kx>1gCa!?(;jFi?C8}XUf$UJEW^~L@rw;;o`4|ZW7eHAp7Ewq8g%E*jQg&# +z$W#o%6ip3n&kS+I9mrM<6C7Xo;YX1Nqr0f!v0_vipNFC4m9|T$>AHOs8*;Y|I_?ss +z&*W^FpXwNrvyf=_tfKqY+ZnC`btNTThErThUcM}k<$YFpaxuMH?lhqUpDxByxZzq<3MBa}=W3@+x5rJ9VPL`z +zjfr$fF6NlHd{NYO+Z?9wx}ePLQ3j6&gYQ|X-tkZw@39fpB?&Ls+}YVTMYsL)_O_C) +z+p)X7I&GSg2`2Zxoo+k7G8oe`W@oBq>qD&j=C{Zin7mm%iweUotmqoNYIxG +zS7MLrOswD8$=Dp@o&sBjNea6uTpJC>UvkhPEa%FZ0h{claUn0Ty)l;o)dCsmwXxW# +z7&XCYeI~Nt=9^#-uqTy@yM4(TC&AaaAYj%`O|3!s!{N;CDiin{03hC9i(~Zp5eM6; +z)ZCHjv&sMV=9|0ApT^~xa(mwNTkMqeXvXR@`JHvtl~|Zdn|0^*h&Xj!Wa{(#S9%i5 +za}T-GFC<*OUy!q4_UgRLx7P7G3TgEqru&a`7HkpMqBOj-^R|2VM9gozQpfC*#%3lnw@MR->+*C7TP@7TcK2?R;#!U71T7tKo%{Ko6EBtX2N +z_vUAg^8g*Xt+VHLOeGv0zgQ;ZHZgl$(RIU}Nxezj9{;0)vX^==p%Y!n+MOL0?ljz0 +zk7WaJf%A=w`J7*DSg3soeSNP~%>@YM2$c8B=_XZfisTcab7iSd!yHf^aYkz%!jX!1 +z|Fg_3y5uU~U||pycn|w|8Z;yDo6z_7@#Ban`xNK9vuozqtE1?3A{+~ +zes)GqWM*rlm6?*hdv4exZ|*3UFFO%UcKlCHJoUoF%2@64;=Qrjtzjw4#Q{6s=Q?tt +zda{&D*^2H`5?)>|??fScwLxcn +zx?`b0j19{N;cKcZ-%l_DH6sDwUhLUBm;2+U+qo5LwjTuJx6C+v50hB&3bW}@Uu8S_ +zq8>}i&U@PS_*wx8yTKrZk`>+aCnW4Qf2+4{@wl;mra1I#duV6FE8lI*dWP}#K|+Sy +z-4TRtZf;2Ll)5+jd3#3IW^HwQ-z{xBW4C{$g3`g%Z9FcfC8#TxD&p)&@T22j`iyVC +zd$j-l_3Jq{-zad~(j*$!wfE&ysW5@6D`XU|jO>bDa>O-l%vNW3uJ>f9@?~^)vE`i{8I|k(2t_jLHZx^fl8B> +zHT?pqDkMDkOmq(uLl6es3s?(*%9NQ9r{%nrxFH_zU#Pp%>-(*rpOFstZ00q1(Vpt9 +zuhlAi6cP-F+WCheyxO~nRBYBsZ_2Mv_#PHvsAld_p)GlZYZ&V0v2y%uaxWL~19(!F +z%wQXm-@Q!{9u<=Lt<>f4ts9T;$)9o+tn{Egbsa^5N5lVi_g2ka}SFqI71l8oL +z(8!U)nVNej9r7?VwBBDPV*rPvgeW;BaWvuwrG)r*AD62Z=SAyNZsQ$k;aX9vd;>a +z^?p`thC5%AKD*ROw?*RHrJ1$L!?{NaptKAmAULe{Gvu&>w2W7j{4QG{dLMnSjBodL +zT!q##h+ECdG4}-+n_p2QLsGbAKBFZV%l@NkBK<+PhS)a_g9J&0AXY*iu9>O>8BPRp4HZUI0zYx(MN-2=Yf)O+!oeSHbH2h; +zBD($5hGH}Smed`J)tB0XZAZ_ro9X#dxg4cW0|3VvcYs|jOgK*a4Q~y5q@-?T%GK9OP%S2eyY|!9G{dof&kf`+2bs`OPM=on_OWOZ{`Q9H)Q_m*bq* +zksv|xG)<4de+eGs_mJRmAPFA+BxR;;3b>SgYhT=~|EkARtcjBDyP$NPE{WGCt=q=X +z@v?K5V#>t6IES%l<-Auzo&Y7o=udIkzgzB$X_;6ZjnumBU}MGQESk%BBGqNUb_&VB +zU~?YRfAFVbA`jMH6CfpkjBd|_(?6WqI%w2SS}cX=6jJdYzZbSmRKxS=#VSwHUVwyz +z@-4yTfsR{cq}l@WI!45gfif(@D7edIj3X?R%>XC5U(cQz$LWp9OWnNN7XP`z!EB1w +z_$F(CyC$xS=TO_nfP!o4^e228|4Q#HN<$=dcC7YX%4Svp2X`toQ->!Z*1|q|eDG1X +zK)b2u+W3gPZc{j8LSSH^u3&s=XqD&kr>UpKRmr+F!%t)>)dSSo=5;7p4!zG4D1r@( +z@h|M9Vh)fbS)H;53d@(^B$-du>v-TkDBb#~fcO|uoQ8z#5}myTmVN;Xhem56{8L+kg+9H)dX`@ns+}(q +zN6eK6<#Y|@KQzl~&`rtc(@i}lXPbv`yvvd~whCM_>GzHn9t(;tNfvfo3BT>0&1(?$ +zE^X{)8fk7lsXtjFlr;Rs+#9Cq{f^+o@~0X!*X1K>)7B2!gS6=+I%rUl=&H+ +z5+-0SF)h~rvYv&^7H0JySKjwOx9Blk=JdR`;%GDZKpq9 +z;A8$pdzb&{FTuO7{g=lDhF(;!kuRPa)1xHnp(z@v>!Nt@yX)p#w>t)E-jvKr+H~S( +z_1=|sB$Ok5nFO+K8idzwFtCdT6A4A!Affc2T-j%Cq41FR?n@c>D0P`aI0p2pqS72) +z#i%FHIrTx6N#-#T(%Yk_=%zl)dFS1i^W0C)Btr7Dhu*q5$X!!nDrbqW$6F+;=Et#g +zDw|5%dbR%B)mz<#rR{tLlG}4Z(mC0YQbxLKn~TyrPU}Hls|QrZ{Wfv>9uiGymqQH* +zZc}}UD?;N59i+r{b#(>)ZDK=`0xz1`Vp{TbTutKTwZK{bUaBP%y1S}?Q7L|-kGLyE;H}UGs8kxEOuyx +z&J9&=zCe8RlYC%}{RJ+l+IrR-MaFH%?Tw#h>uImKdb09DiVJx8DZw(; +zZodAUR9k3Xrw~0Hx_kIMk`X2ULYSU0pN(A6%u7dFD8i3Y*rPz%jKq8P?QE5E9l01R +z_MK6#cg-i?=)QR=dYtz7C6dD7EGJ%Y=C9oUmS$+2d!eF4ckOR(Y1#m$)mIs1fdPGUYWhKnfs? +zj{&Vngr^nR0@-HM5js)h6M}~qI=6JckC?`N3FaHx{`;zjN{t^}=ppb+OWz;Ng0n>6 +zP)XQz!@jvx_k|A)Jue{lA?w-h|MQlp727k`Z!r76obq|$sChQg8Fij4=Td*Jg~1#M +z^HV89IG{Sz4e08XS^Z^oA*)3xOMQsZXiENHCKCcUdiV%Z6%hSI1*Ac<-%4?>jIi1HmPW?g`~~j(=ejf^q7R;VVAc~vl@2fK{DCt8 +zruyaQt8ZPy+y0l*_3&(N2w1Onl63JNeZI53Da(80*(g{pDG;Us!uwT#`$Xwd$}PdI +z;s8$+R7exP2#)O5+704pY!8+SstiR9mJeO>;f=^*ch0S8u_{*Ph0FK8-R*(oO{l=V +zU1GX$38)o-*395FY$37RWf9DhV)}|@|I`Bp|0Mab$RA1DI!_nIei$I_I?%HqbyLLgz^~sZg$_T;VlUX^z`lXXc~<_iPX+I^_7wIl;Ve% +z`&#RiDOvt(=a0@goS8Vg)ylVfS^$mm@e~41Y1pf-{YLIYeP`4y$SlD@qu=Zo`+J--<@|v=7s!o>gQX6LXI1|NLfW8SIcpa7-3`=hu)6Dy +zO@zp;UABxf=>GMzSg^qTS=IWlC7^B~oEDW2@Of-6X2AT%Pu-2R@F!si*dhOF!K|!o +z^1icEn?dZ>6+WLupR?SL{hwPj|6O1;p{~}A@o?Y-|Ayg_e(d+qTQQ=ri|4``D7WtQrXP8jT&Im8cuacvKP3Q +zf4%iq6nHx|K4vxV^Wu3qvKAtnVqxpT>w1_2qmDASNXP`rv^b5hEknLRY)^m` +z_Pojwhq)Tw<8B=1tAM32`vLWme3!wK0{4FvELfUM9!U4vUdzkr{h9vYkFDlOYlTN= +z0Oma~y8Fq1)8J^Di=@>@x;1MPu1}{=)w=9#t%zBq46qnvYz$5$_cYio_G+HXRZN`3X>q^*e{3Y9vJOh^jeJ +z7I~gE>G#z}K`YKHkhrvod2@y_OIKBdJCWq8YR@?i52rn7X4F6AAu#+nJA&%cYIe>7 +z{dL-+ChFM*tIxTfG6 +zja_)gE3;b_V&{En*21{5k8aKUm$87WTZ@{$(i0#Nh)3$w^wux8l6}4^%o$Vp<#+H# +zO>eW&=GW>i<2OhGi|<-y_b} +zk}dQqaY5<5sXVX?G-?OCA6^8moCHYyz<25%0C*^&eg)9E1aVb4&&!)T9)$`f10+AC +zGG2FCB$ZdM_MiP9AMd~XjHtCHEuQyESd6fduCL|BA7HLIXF6Oy61&gU9GK#kUV8|O +zS-R5|>B&(bg5-UliL^Q%W^-f^U!t>@@2%{;7q&FQ@;<|*1-h~rx +z-{$`+X54pj268!DfYp%C9aCuaDdRLe)b}#A={hMULV}O8{ZhzJ0c;*WgmfsyL79Ay +z3qVS7H#UgYUH&M;d*P27c;810bpNTT`>7xR483A_WcW@zSxpyIYbg+7H;RR +z5}Ye}PHoIf?#C~Ba3|byD@2i&|EvG0cM-NOoP&3oj!(fZ1g{-m@GzJHqmOaQ6Xz-S +z2lOR$+@;?gRwRQ=SSnAT8cAfXyw +z$vIg)>4cBTHB||6S4cW~nwu~1nFrEaT_6*xEXipp!F7$Wear29E_}Y2$R#v;RA4wa +z;i~HG%6V9e%d2muyDzn$69SgYOw|k|AbWKB6%=tm;&}zBTnD%$dQ0aEwSoy=@>Z=^ +z{&0k4oqM63+(`{(;wdw0*bvm(c-bfnCs=(&YOGFNA~j!6wQf#n3kn$ +zGPL@7l@p$iWU26m64dFLrm`n;41pVMv%@QK?}zG!3^2yI3AawDu4!v>8umHq3_ZS= +zE4q4{u3&5k43g;>q8UM`^?${3K+`Ch5W_B$i=1RZBl~|84>k@+SjjQhyy+xBew`#< +zakW#JXcYKt_jzMi1{sE0D*m@=cg|2+6UB844+b$`82$7#cDlY|B0U9sskq8`>Ca@l +zyts=-D!OZu%e{J200iRorGRiuDQg%HzfyeP3Yv4P)cD8Iyerbz3 +z$qKtY-lO4xJ}BIy4dl +z*g+D4=k5W9x}h%{MIHKytYc{HA4MSBij)H(t0 +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 +zu}UYqk3uy9;Y|Q_>Up +zslhv=y1c(}4H1`f4a=JY16fOs?-qGzriWY>K5&cWOXDCi+Ea6Ds!ycdOV4S^w8--l +zw7jwU^=xl^oxPg)R8DL+lo-L6Fa%a>Jle-+2({4c6e(0QzNaWAojEa680Uhj|Is1} +z!k4hiq=94*RT)E50341az|$yjn1pcQFoNSPZp3Bma+ocMD)TTeZ>~93RJ=E~?!4Ob +zGw%G4JywTfqx7B7%n#O@E}AcZ$e +z-D+TAPDNkBvJ)GQYE8XcF}%yq>}!UL=0>o-zS!f#+8)qS}TTQMUwAZOtLG!z=?{ +zm@TOM;DykAXEQb^yL&GXB>OTe|mk-*_}Lq*5u4YX&(h@pj8Cq4XEyc)85^d`HR;7 +z^whf7Vhdn`$4J8;>nKNikt5CF1$9Vg@dqV9W4hpH&%$yc8lY`S1 +zs%sy#JY%F*f9ykM9E<6xO=P;*0vT)4neVO^DsgupD&#;4si$k%S@GQODsR&ohbjO&}h+^|pUDL@?4Do*}vvdZ= +z+><8cgB}58JP(rz0t!ds;B7z89RFj4k2=Paoe+8kIV|Vhg6>H|4_W__-?Q$@QaE#y +zkDk*-ug<*c=MNAN7cjOfO-xgq$t^j-@HQLx52AI^LC9l@b6Q-t*Cu)ZoUj*&Jo+us +zlNe^nU1#*07~~!`lkcJR2iS{$XX`)i1m$`$G>P{&!CCg1pPAaGl|32RSgLnL#qm~qR_O)EYLsLZU^;3Oa +zOOMR4{fGAvWzn1=HgodUZ49H2EkE;{$HqPtqSbgA2!;U^l`mQu&*}m(T3R;wC{*=H +zlx4#BQUmU)yN(o5ji@SKP_ZYa2&f8`Dm)H)kG-180*WkX55df7rJ`0k0@cGl(p_yfK= +zM*?0$vCC&65AOjE^aM)0zUWL0+eusQM5cQe3o1xZ6!e1*K>e*gE!t!QkKa}9v)Gw- +zX}%@+1pK0j_YFAC_g_W7Hu;|1Vg_?Pyhh?M28R-g;dut0pp58#jW|6~1<{YWhDY0- +zZY-h-V-ZB}RyG{SsQM&qBj+TD2pKsH_JeISxbRW~q@fz1yvn)!9RkBKK^`2341xHUIjiFmMiocK->Af*?RiddyWNGW|> +z0XOI)9Yc>W3O_ow9-~JmS^!-Hcm|J>V3#U}UjC4xoO5Dserq*ZfYH}Oh>;CY79foX +zHlX1{+q||=tFGB>+g!{)_3GP2Fzat3NjjuTCsZE}eWw4ukcQ~%0Z?E2db|2vPD?Nx +zoSs#qIKMQ%B<4$6r~i(H?gHS0>c8K_(oRknLIK4!w(THMtNtg?LY7B=$6~cw#&=#p +z2LjZezF(2P%(zbZ=b@z-oPE*4_wbt-JxW(pu9g;Z&KwY`raO|O^{g$2PhRPmTbG?% +zO+Uq%f{{6gKPvX+UK=w@_mqz}FjHcBeE?3^u}W8}l-xY91r^Vn(MTwRTe|L)92&q%3aL0lH+BvykHUC7A)^d +zb{;{r(3+Z=a(*i_6{Z(So>>X@uYpvVKZOY=S7$r(I5EVk#&M3PlSCvf +zlZHXD_3uDsL9*M=B#2kHo%kK++;|^^9+QOP>J-$2WA*7PlMuoU&dLVoE+YJ7B@te@p}?sAec;}y<#6wz?x3=4xX0(uln9&vNOjFO +zljk&Xu=|Id4u68X9N^P3@0hiOn-37woVh(F09xPcS>~It(J6iaAH`K5lDK`{R=DV` +zmxh7;BwJ9&*Dz{2xysPHvGlyfXhlZFqKcmfvz3 +zv>Fx)Z|W;SRihrz$qeL7!@y4-zzr~_SKWL4h4_jL<&8USOh~&S{D=n}|F61DFo|!s +zCOILS>B7zlIq&TdB+(#GvO!paZ(@T`oH%PN$ +zvrA8m^U^m|fR+V7jAU_TjEnd<4cluR9WkkI!NY@)W6&t%;sA|A39Th`YoH*BdRM-| +zIyjOnfaxu!DBaU>py#`ec5*w{@yWacA!Z{hGB)x^!~*Ye5={yrg#a6lL{%&7q05Uk +z0~BNjjyr27L#YJ39V3Y$t%3O)ELkeuMN3Wfi5h=KkC&-nZYu251Fr(vOA +zgoEn&lZwE690)Zzzk%o=hR-5ekyZsXLlrqKGNe)a$jB``N;OT((w7x}C=y(3xl;Tt +z-i4YzE-M>%@avlcHP?fzD}dY?oX7$C1Kfz`7+`TWnF_R#4aBAXCRdyykT#8fnh;cR +zigc$wBRIU{&;}}(F;ZmPjKQysscnDpCpe9Ga~uA6qmbCaF?n&2q+jlbdRr*d{c&)T +z>WIJztZDi}E4^%bG~IN&)jx5)!G3Gb!k2wUF?^#)MGKT`Z*p2}=~{PidYZOpUgsKG +z)=Q*CrN#dJdRtm0Zm}n0TlC$|_6A&T$`NIl*rhR*>1URCCpgkKlZEeF3Fx(|#)iWG +zRhLlR2x->Q&x2~-2xIb}9z48Gbx%@)tP#z+#UEuMWSxY#h~#NX>Bgw?p{)f>k-}#H@^8?alQ6v +z6$@;x9+HVh8s_d;xC%qHBgBg +zrhmAG)RYzZERBNiN(?9D{<&G!r3C3-X&1&fXfp0v+IQvmHEZW-fiQhhl7wq|q;+1g +zj4cDyBacBn;_LgE2jmvu%q~@;nwDauHALZD{ZQfn$bD$7ZAlcty@kpt0_Ag&7)`H_SlUuT|!c1i%7;_iQ(3l!dZN~e}DPY +zC9S6pPdr#}SFPnOy=p?NL+zyf6B-6%hHtu((~6qlg{|_9NT +z08Jwu0fLz?ggNUqOk@|Jv_2DyCN$ +z?JWfK9&Ow~G7f8lUW7!Fz7T)^(ya{~5Kh2myBM8qE`>sB)2U>*YT;9OtA8quv!&HZ7=x)pM3xA5p+R-&! +ztQ*MC{h8|2UIwX*+LL3`OI1J?ohbtLCx#E*jW@5>aM9+fr=C5T)?~!6uaBG`BqT(S +zaateL0z4wb1A`7aCENx%88ohnseavhO?0KK;PgI;@1i|@R6Glf6h{N(DZIn^uR7?muTrLEte?~+pN*wQdw#)pW +z>4hLH)1eJPQ$Yo{-{Zs}4;As7!E2jvhg*jg!%`$id +z#Z2cjj)+|T?7M4ZO;!YbGTBPrXcoRYc0}R2y~-7q^wCI`2@>SID4-?Aff>7c9_g7O^&s&G2}01{|Jqog +zz@=a+lhKL)vI8(w;D1!lohAZYSRKcJsrSg#gbXuV5kH)z?nj|i0WCp}iE{P&k927b +z&3wwBSg#6(>J#@-+RDy~tZ&NWw@p*K3vC3>du8U;5bVm2UEdI~xIud)XLAB4vCtH +za70Qw|7(LXJvq8jFZdX%5fhmdq0{QW}~`(Dl@+CRs#?cQZAnJ)}!B +z!#%=%vCcR@^P!z<|5;pYow!=oodKvp!@yvV#xsej`*5tn6&~P>i&-8WMP(MzAG>2dQ=gtMUF*B|U1j4Tmb>pw*GZz9F&0P`2MB!GF*{e*fux3z&Iu}7 +zfL+!8sb@krWZs*y$!sD0If(8T%4+zE8Q@7a1TAL0%h%j1rYY>mN_SI)BC%tomQ7~x +zz%OTL?ajN3y5XGXi_MXz4b!3a3JvbFLIsZhn&S|c8NTCBLfuWY_8eK1Je89sExvbE +z6PJ&1%2-7Y=%zylxHB*-YEM$v(&E2SsNP0|$EHQ`$qs6MLD +zus~F#(! +z%%P{)=Fe4qQbv`MEdPLTvF~REKG6OQa4oO2+R$mudS;e(%liEFBZY6jwn^`7+CmRO +ztiE{P;rgT@53%D8^@sI;;KG9H_RG@lee!~ah*)Pz5!?X8%6B{flwqlcP{ajYvu_(I-W6JvX4?8Ez{y!Rr22NtO09^j7x&5a`2Z_oxDA<9)8BcN|23K}kGliBIaAarH{y)p&6YAW* +z%ToZkC?zz0#*n=~oP;O8zTdY~iL*${JAj~Ov+H4pMaXWF*8HT*rR*&|ksg3&k1cBM +zPZn8=2ABn9Hp$f)=A}o!|DTMOl}MSb`{DAH#wjP+&Lab%qIcX#I&AB04m6bZBTCQA9{nj9G+W46-58RXAEVe +z$OvZ;yQ9L}UAdToXM`BE4v>Y15o0c5KIa%@YA&W(N(NaIa=LOdOu-cS2Bfa7^jBqtBH65qjeeME`m8q%_q +z2_lxHuS-`Q+N!sgi=QHeIab~a254>WDhs}3mz}V|L5KD`)ssCA6@2p;2()^xV@G9; +zO{j^mDp_0HKZ;2(_0NxCRqZJ=qobp5AXU6$Y#paRQ^9x+#X~k%_+ZGqnUl54Rr2jlYY5RQR!eGNbaq^euR!FSNeyWhF@!MAz$=pgKYRK +zA|?`Ay?EtL!%yfiRy!>#63q-!qT1%;%YI3RD(9Y($VF~yOW*M8j11nKqk7GSJ%5Kq +z`GRn*F}448#utuYPpv;2y?lsgL^}M$6g?wOwRC`JKMnfy#N40%OW +zsP_EFm&+edky@nPS^*Va4tqcANAC%TK2Y~_fks6>hR^yQy$^sy(xm0NXJ5eX{Q%!1Lwl=l|J2uW-1}M +z4``*2sQvu~Ljl4^LO+v9R_QCUHIauLkR|MBZPf&bu#Y|WCE`uD%={CgP5vmum76?G +zT$OVvA$X^x)Wk0BOzBKRd$umH^d>+0v5=tl{~30wTK$>=?d$46YXCE5&aTp#J|~6m +zjMO`Scbfn@$>8E;&LU9tgXdNHJizNU`(Q}zchn_PyC;UmmgXnjTC1k=QdC6 +z+icBzB)-bJ(_pMIHV77jZCA@RZ~ov$?Sn +z@uZe!|31{xQUOS<8xWabfuoH#?N4{MePNrOBncv0OFj8WZ%7Q^gjn^Oh6L2XIStyO +z^Ct!Q6cTI?`hYLclALn9JOI8o@%oEOyd3)*XdQzz3jkBzmpevgJ%`TBmN?8e9L>)U +z3nD809?a6^FZp(1E*F~dp#CW`#V$qabBeHx_?M`x4ve_$9T9{cT2M0~Smz?5YN{H$ +z?c;*m=z>9tL;kg&NZxDXIxI|m_FnC74H9wI&b22HXPq{O1y{B% +zoAE8;k~fcZ%X4mTvX+uiQ +z1~F;aw1>nUDfe6Jo;g%?BH|T;h`Xc=2$@?`O50dEzTBfFEwGQO{4hxe7X!b{%Udto +zXhicKT`u`^1$!X4-jJeZd@art;p_k6w&ZH&(T(|tNh`nVyn$nZfO +z!!feWw9R+;Et@W{l>jZZWq;mG!zK*h&I+OmXbxhT^wmLk;p9~u`u(c-zf3@Gu0yjn +z=~|4J&?I98GYry!d6F96&5dIoNBg_6Q?GvlnT9roAuE&+g;zWl-k+ColDy5=+JZ!l{fTQ|?XH-FlPp~)@jM4lDr6;)b-4Wyk+8{U>1{-x}@ +z(*|Wk7^XBK;%&-1@h#)jEbIE~7s4YJwN7c8X)c%%_+ +zRqwe1*Zq>Wr_SrWu!$->a0mH5ku55ku)cnsgPekbo6Pw_-%EpZUYk&%D%Sx-&TXJs +zt@P$-`b*)PbMKWXXeG0S-(f;M3o92|Y{O6YMd5t>C^QSYSTHKGb4T@IGp%c!zukcL +zkwI}2aUnCVDD8$EMAE}*6Qs2a861N3ursfJy6l!g5vx28DlsUZd_xlpN#|+f4+)#d +zF|f)3m;o^)U??Be^TDqn3w*-^K2a2V-|DGNCFg{FMF853QoXYc4ii+X8bDCJ&S{xf +z|FBS4o%HWxL2-5a32qXZ`Dk{%89yB<@wVzR$hAJGTHv-`Nj6>6=KXdj$Q_Hx0=X4Y +zJ5i_hUd!2kIK&_ZJa9$f91aCzfKN~cpt!D|3qs@; +z%RVKz!~Fkn_1%G7x8M7`ZQ^Z&vPDUD_R5TG(IZ)rtjv%R$;c`dQW;rUDI~K(Rv~2+ +z*+h0xRz#`axnFNR&*%I5tFm&xUiW?O>s;qL*Llzbs~;kZS;^seBtA|!WI1bhDBpI) +z4budj4Sgt{7j7RSR{s%qS?qE(S?84rw>>l&f~N|=74PEa^j)|HK#89QX+E*eZ6C<} +z3Lv{RGnw{UI!F<3k!70YQ`(SS$1&#G=rbqCrxHh$^5eCU2}l~a4sFuMUCd2S*$rDu +zt~?|m3w6;SQe9X#M|yFxyA|H&B8)+g}cy#QNB}AA5JGju9xv +zVt%g@Dje3l_BbW9p5>6LFoUY`IdyLB)7mcp%wnC5vWLfrI|9GEPq&Hv9k%xDbjjA3U_;zzl$Kt50l`0i)*A#Pbb59E3m^O +za9*fwp>t!=($abl@|7}G@oG%;!SRx%v9f{P{L4X0rocNrWpBLimjqL4iS*X1#CAnk +zx+UC6V5OHPF&^&_31iJ+s0@U5r8F_ibL3gnuA==`J_b4;B4vNZT>YM>BQ}r~-Ear^ +zHu3Poc1@%{!(-Qwj-viF$jgp>t6ja3+iur^cfBmU +zG^55oXELx9dL1w9^*|`P#v;q!;Y3pt_`rSf<)sZLzPT$Pe-A=KW<}A1IK@2{iif(i +z@I|s`MiZz4@4P3b=q724wmpIPMexXZ?jOmVn%2CuUWN2l6`8e-(;e;|vP=4v6W1j@ +zQ=d8FS}c~LIP5g|159mbUPk|HWDkKG&4Ql)@V>@Nwwm|Rx7Ehj^GCuabycg^9cz&8 +z-_3I=w)Ge@D&D?~c0Dlebfq}Im+wT}uEYC|rB|&BrSqI#(dOUtu80i6uEC$zl)|RQ +zu4-^XUNVo|1Y;Wuua1`rLSz1?Ez%_U18vAPG4^Vnz99G4J+CIJ`S1Ow@1BrfSl4Rx +zXe=jTv$QUst|U_+=Q5`8>5x7-1PUbnf9P&h-MBu{;!&v43POz* +zo$de^Xi0)-dhIH9fo@P610aHEIv4r-dhNVt=vE6z26%9eIH!NLV1W3J!uP|q3qGHS +zzk&-E1AI9k6no%lCHX05S1KV|$<+y^zdic)wp3GCP9J%UAf40^7M9yQT|+TIo6=TB +zf=;7HX*3RRRx(yqjpz}iZtwZ0wli-z>Sktws +zGJ>|N_gVXt{uW3be7EBT3$ibSX@wU5w$33;s|!pVTrtJn+7*PwRbwhKKnL%r-IW5X +zqXT+nO9uH=N#BxoE(U<`Cmvp|XR8njTswta^e;qO6})&Oq>3(KKy$D{A9zL)`~<>B +z-{tEw(~Hy0o3;_0!voLYnp}&Tx7X(@Z&1I;(rdX?^o6LaF*}2nG9aO5cjyZUCT3LVmbY~{OAv;BBtE1Pk5dXp&8=%V`SExQ#do+ +zg=^?O$l<#C7@g_k?{q`pLYJz^bBtaDIud9}T|q!=z?z8VK}R2g@k1XNF9Mi_t(fNv +z1RlIgOVXIol!j;Qm>vz_8$$2wTv$;w>Yvc3_}+O#BuYauy2g%~=QDf%2za5Y?Od#R +zQvFtfbz2XEOV2|&K}J!VMk!aRh-J@fF#L7h{|}wwV{^PyB)49vn$sD(N5xgTnSXxG +z!pF;^*G}z`bkrZZ_@x-4I5^oQ(aBHI>kQg<(9_)}87W(D_Bs +zJ;q{LKlVPKeedgbtbIy8UvsZ5^&Znk;kVGPtUNeG3A-1opFPtr-3S +zk4g{9)##1G^AQvzoO#XI2+8Y;C5=F@k6QJ5*u7#tsqd;c>mEjNCu^V`93yOP%sm#1 +zLL+qLOMBa23uNAtGwy04c)mPo_%`KlIpI0uS=~@KlVO<^C+I5FEAcs%?6b^;5nR{q +zt~ITSL%r44Fs +zLd6w{07S2{eyK5^b!cR|NB>~aUP*Ni2&>wf%1pHToD4YnU$n5T7>Ix1>pBf&+Ysh% +zgaGJO*smEvB`FQ3k+tEN0BH2!k~+OyAySOU(|<82`Z^S&4q-vuhVTOxv6`<9(4|m# +zzw3uxQDEv&L4(dPxF?{fw!pyc08Ww^IT7D0T9urLmofKm-eRYL`mI-aLRk}WDo3tM +zb{&Jz1Z~2rNzUPjo7^f#L?C+rrllob>m6#b^G$Sz*!{8~|NH+VDwBo&dY8wA+{VoB!rFRpjikh#*#LFRMa0lr)xf4}eQvuLGd~ +zI|)XKNbGVBD^Q$IdD?Hlwe>i%g>LM{Q_l7q +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 +zsg0X2)NyJ!jUgS&xxxszh%Qv?Hywc0zX=$au +zX~XA0-t`I7@w!3BeFp@?k^_|VJ&#}4I6lb3y?o}D5mLrPNxv-sG~j8Pt$ih%pgZ)S +z)-^a0iU@p6sH-H|I}Zw%2SnH7;-n_iQ+ZU`-7Ag*8|!!u#K2>{H{p}NPK5@{%l~}_ +zJmak}_ULz}Z>X6c*x^aRxUOUEEk{|clQKfKTyzGP$TK*++wBU>oOj7D^ioIP^ByRq +z`1V}r+jDk>h)7l&a}yc4g{008)Y0kQbA`-o?sUqPO{oHXM*`)3j7M0jUGv{67*VaN +zpJdJmrX-JIA>Nt>`^-oDwnZJFpdAg~G*;xHu-V1Cp7DH?YV7Pym_h-_(|V;)R6oyw +zG|y3$E@mdxB&x!{(k?fGX9MI}2}T!q4}wzYXIi)w6V7tz>rf(c7yw6be$vUVKOgQp^PSl6QVYPEj1rK%wM&+wguhq)}$-oY044MuCXhy8bI`|?(rT{$2-W|y=P?rFLY!ExdjF?d&G_eR$ +z>>O6er%{;j!J64m1{k)b&9;yLvU)e<2U!QihZFF?VC^1@KCVx*Mia)G_(G`e_YI==?axgI7%CRXw3#3(-y0g-N098Mfi2|*_S{2F8^y% +zh +zW>f_C1C|&62)PHL3VAVNffW%qf^lSv7;L;w1`jI7w0;6ZOK#D>GZfqjDv3kbL|YJJ +zFlPz!63zq}`Qu-1V8QGHo#6?9ZOod5Z6=FY@xnil?>kUVPg=#?;%}&G&g)mweQRR>?0I}i^4Wv+D)SU;!j7r{! +z3LhOu#PH9zHU!pC5UF5j0WY~>G_c1GYzC-7vF$GlKL)PPzf3r$1P0)K1{S}cu=xE^ +znYyKGzx5?`+R1CcCO3(h&56xL2wc$hhB6naIqyL=U>;uZt)%NA=}syGb+ID#%HoT0 +z^G;^;#+tpxu;MrRhXjDf#`L)Q$sM~gsM|{}-QN&A$2WPHT{P%Cuq;CKnY(`>5d+ZSbzot%NCGxL +zpqlAI_~{up!cs7d0T2W%@EO_!D(EQ0Vm)jTSTOKLPYRX`(`X?S4BRxqbP?!hG4p(a +z=hJ1NqD$tidzEk?5*me6#*X9tF50)s;>Uc)FsX%ba +z>e+wSYM-E@%8y2T8QWuzZu9rf7HYxtJoso1?_*n#SfW(&y8;HgBI&M<{`pb8_bYEd +zy}|nU8G7XYNT3e4K-vMyUzmW4Ui#=wgks5JWa2^V0fQ)`LmvDUg+!qIfE8_M@O>t% +zcUu*j6RpsCZ=m(gHem&kI<#JJdwNaC5x}<^`wAGaF}*eq6#(R9onJvn5pM6LQydRn +z%dk(4+lV*n{4r6)jUPLH6RN=?qrjWN24`t@lU`{2+48YwatN%QZr%9NqjuG-)|96% +zOeNsxK+U?dv-830;8zzY2PLNWXbg)Wd)zN^+*z%5wOu#j<}OTMv*i(_N9NUp6+b@? +z^d)8?fUOw$4s21OQ*H$~7*4rwGeK7^ixJ@{-UGA*2?+$U1&jtqU@-{E1uhF>Rl*)r +zDarxCM+mmf^S}%n4g&l%!v6xWoLr5N4^q=cf2+TI(I3oJ;N|q?v?PgryWyjxs&tIY +z+_UL9lh8fsmnj-%l)I~7hJel>A`_p>gvtv;)!YR}M%_xHRfyyM^P@GfOe0h~%PB7^ +zuAKaOrL)<ptWt`Ct|W|osZXa07O!(cz-TuMl5iRZ#^ +z$C<){0j>Cmio~_5m80UNYxHQYB_S~a&BxBD<)E|~r~z1qQjVj2P>ULKO+da2i@{PM +z8d!?jWjescsN9E2TD6Dw+N@@lev9ej~X^>nyGuUFY3Ny +zW62bq>?=}B^$A2bqTZ7zmUK*+x2`Xgcggr?ma7*u#yp5F&^!W5^}tz+z=Zb63cn_wV&qv);%ys`l(t{qyXd*8DX_=U#H+ +ziy*_elylX3JZ1j?O30>6tq&Ya6ZcyfIKkdGBk@mP`JldRz}_mD@$`J_iicO0q~&VA +z!I1(zjiFyRUp6ZIuiGq)yPMmo|*`;MRYO8&!FNWz` +z?~KZHWoQs{tl-RXP!>&+# +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 +z*wpBywtR^D +zA9!z9t&M&QIcaj_T%0`<@94}<7hg-8-=}pF +zrO`QjeKvb1iIXKPsGs(oS71Nw6J`m|#?5Mql87(M4cp@{0LoKo+w=1X6! +zav~?aTWG;0S05wW&ove>d0nOcLwVDkL1_hV1&=P4u?EW*ShnFeBlo39N|ycLv9v@w +zIaQFT?}f!j0H-V2{=mUKU^4EPF{^91IkK7WXijvcrtT)^QK=*&^MK0kOiYlwnjLAR +zHP_&?GMO0WUg!QHJF`}3D;zVKV`^J&!JuR9C4sC-T6xb6Gzmpw##BO1S(gckh&mjfxzG^Ne)9?1@aw2 +zt*Qxd?*KG~u>3aW0Cp82)e#a(P`dp?JaqXo-F8m-Ug@3NDBdfTG^~EV?fC|?Hhs(0 +zh6?$p^U6yHE~(5X)zK0U^Y)YmWWCE;UTU!5>{|#ZJn!|$?+DkqqQSx^J{Ydco>?%2Gxif*G+3qP{lja*Md +z?R6wwWFS3DZ(5Dgr;opgM8!ybOM{Z-|8&P* +z10V%qnj=EgeLt>XP(x`66W8uOv**lSS!hegWTepyhQoHzYnCqhO1%N9$)8?X>=Kj4 +zUFxF^tbXe_#6IjF1QH;8Zk=4&rYGKtqJJ{G<>2mgX2sUPzd`_#@3$|03F}pz|~-`frV-of`odp +z+vxr2)V;5E-f4{2*7|V4-I+8a6F=}Z|4=b>?vYMAovURng-X-<%0@uHPpQz}np=T9 +z$@?FCH?hCw%qGevj@KtStB}PibIbOcBYsrjF5@|a1kC{kfBb@Xn@2QPa$;MrrdV6o +zo&+A&1>dKwT-=X;+HD(&FCJG5eg3b2ct;xyVufCa$(UV+bC2`zb30}<^bvs!Oyqz# +zE)!U8O(QH7oE|oaW09{fumX1w+dYaXP(sg}V(iXR_Y+>~bG2Wp9LL`tWF-)QyjR#( +znnm5mUWE&v*~pN^Dg9aqoem?LfjAOT8pD1IJGyzHjqvw1VE~_XaeO^Vun9-gV}J{yX?a +zsaOp(^FUouH_g!YE(t*j4cdc)JGx0LjNS!3G>D1Oaz%maNksIfC;-T9tf~(l522dK +zMFZ|9AB@;Vyf(q<0f07v?c*T4+3kZYS@f9)kSG$Z&)w}+8D`{m%#6{2y%(CXR;IX-&LJ8^%paPfmKeQ4W%3w7s6Elj5N|l89)s|tt +z5T77-QREqer0D6pr(Zm?yb%qpRbre_6q*OFB+1Bcpd^*VPq|mFnG$&8;h7r&qK5MO +z_sa|oxQh8j#fyczFh-zJ3F +z?C@EXB?7qoO2U``0ugxa>k-()fY-j$2M6YIJ3BZwz3x;(Xidc2h!C4OnP;L38<8k(!JW=WRI3WHPpnD8(jLe;I#IKn^a5( +za;a7(!+6eQURqB`y7{Q&`JwHVyY)#)oSi;wDj~JG!Cp39FJqy#$mw5OlzYbn?qyuM +z!v%BEdxTA9!LtL(G6bsMkiLNdlEvHqqZmf=j%T}vM8QR&o(bI6P_D#|YW`u653|Dt +z$V2J>=oWB&jL3Kn7Vd`@u7|2VJAH6}7Y>aCXyHlkPll=o{|-kj0$fURreiC=qjW!q +zw!~?5yrB}fH-03z4C2&3x2rd87}yp4+}MOvxa1Bp3r?j1hEBLymK|4XeDZ-Lmz>~M +zoMdNw5yf&evqDRy=~-aWN8yXC{_gxsAY-AO)V4&GieeqJ}EUd2k_uX8Z^j +z6?MM#VXT*LM<|R$-%8P7W${-k?1#2|F+r_R(N)p22*K^z!Y%NXV-}gBVZ$(F +zk6%zRZcBV?GLrL^bAz(i3%c2b06SA^nSv$NR&9#8>D^QhF1Mx}raR|WPg|sRMkJA< +zGxAgLK{4q*M~Mqbg1H{_Z=5o+W$(g)Hoz0ZK=2);Nl>)!xLP3co*Lap;Anx|)`AqT +zL&A$nlqX?jb!cUWz^jPAiLi$c!NTUy!U#fM0O}alB*8km*~KpJhaJp`=7mg!MU=PP +zG!j#o-B_KuCp%};31-u4!aZj>b|yRT7lU!N=fTIJ07%>KM~Z$fo8;IhgM6z8?79yL +za&w5ctL1RbmAdN7K6+9o`6U|-PnK%lCD|sRDD6j?%QR)-n0~=*^K3y&c0p+?S5U6} +zOtOXvUFVUsqnf*1^G?_)n~0D5(xai{h{6X5E=>aG*Wv*YLqMW`M=83BQ!(bYqQFXW +zksMR;LgPdBpKR`jRG@Q)fF^PxH2GPWCQ>}Z78ZmYk)>kWO7i+{Ka-TEJ|#Q3LkT6RCSU^f+4Xg!!7ZDEQVluj6Bf`kLtBQqG+NYcZfB0BH5 +z{L1v=ijIv7Sa78l91XQe%KB2IQunUpHSGKWj*j)OR|aFDlcws~lWYE~>4sjyFF}+n +zTKF7hcD>+?j_i*bX9-K+t^cq#koQg4fb>!0loJkZ9+`1Z%OjlK&F^*?W{pXbC_X8U +zix=fFVgA&U0*7+aXvJsU|HUmEb1x9s+VLvWe6H&qFmStb%F%_If;Ki9-kJb5CuQXc +z4CKy(!8IpM`~qlvCn8yO+bMd8Q&{H0=%&pLm;Kc#J}I+P%MC|KdMF8tHf|nBbK57# +zUHeZ^l%NgWxz~ziaZ*7Or?#d9R0IVnnwtkiX^Nie2<{=PpzOBZw<`OF{gZ`$djfYp +zSAuLH^4euR{g|~%{7gd|F&}vxM{l^Gjg@4VV84~*xYOAv1~x6nJ>WIG^DgPtw3>eZ +zXjPHKYCNHe?%O$yIz-1IT!GQ7 +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 +zC*J^N-Kask6Liwx` +z1*xIfCwE+1F#?62-lkSzwA8+%zB%mq@>}}%e^$iG92VDIV93ZwHoN%?s!Lh{`EY>k +zx$L$AIvQ3UX3eyFhxy4(iBzE=rZ?A@8S8moVh6~qGyQt$)XPpxAEcf7u^;`0}oZZV@INyNIhZNzD4`NN{ +zAjpbdfbhJWEa*ku26i+_8;p#o*7%z^Cj_p685DE=?QsX0V#wjc4P1cycL><(#7&W= +zR81_aurq(o;WuO^r}IOZeQB}j^jFgWXiW!uc|g{~a4YM0fom}_c5ZG?R9HBn-o<1F +zOkL&TVXmS3162%6e0#;Y_#-y(*v3Fy<`}DZuwVm*o2h}}5n1_uLlcsDFIZe7`6B9k +zQVck3_P#$!#xPUpEFugA)9(5`Hbb4tym9qpjCT%}TV{v&YoQJ%0Ca(7{ROaSXrHve +zYv1hCLIX{4%YF4n9i{9U#9RVkJtIg>;CfSe{~@x1fXv!?kb4KaJWGP!lM(Jc38kXo +zqW{pP(^Kdy%nAtdpSRC1P^l@SRkOI3d*Bi_2xwsmJIJDkP2c{ug);tdwx55z>aeY3 +z-!n*A7-13gDyA17Zt5F|J82oO(QunswkOA12$F%hO%rxrlAUScm}wtAqL!=`w!fp~ +zaq1k5wfeU%5hA2<>JxxS*Tk~F=bYdJ>=kl&{5qMzKHd;Rp$N{j+}|;Tn0vq=2FQ0k +zhES`NHJotjidtzYh7q0M5J%g8Tji{F@D9Ao6cAxCthVO!SN;@@sL*PX-Sf$&zrpfb +z@7Z(jK`jZb=*QVbiH?U?o;nDC$?QCU_!W7{nSX|`CClML_Sux@q+Ohe$Xh@cfCmZ) +z!7QsKu9zum>}Y6n$~Y+u>Xrvd*i5Kf9`tIVm4+N{n#rSIszdp1LI-fy&f0%)^RYs` +z42m2L?r+-TTT2WBA=Ad8ZAupyzdHLV0=?aEVjCJ3|AKMINAVjY#|WEuiCB~nptToQ +zH8XktJM4J`@K&Ja^S4W>_mN#+oH;6`pdmnu_JAx}3(U!S2gy_<#1@Y63b0W>V9}Km +zcXE$=V4xlGt6)S*D=V?TVVv+|7iGzT<3uyNTKf7zcna3$|^ncwM)R`%tg!+mW6DbKmaR&E& +zCZDb)1dQQqcWWK6eQ8LaO4?;EBo%;po7XfI@IiTCy7!yrOZ!l}J9ak%okewSBGHVM +zYQ9Yk8hu3rHTjvpcj?)&5}TC~%5CqU4%T;Tj{-UYN(g|tSEJpqE0AH*K*wEKC1H<+ +z^kp<6i$?=@f36q4t&BOatM6Nm3VBj)>${;sMARgs+cjQE>~1uj1#CZHnpX3kTVwsk +zvehajo^Jo6z|JxdB)bbpu6W7m%Pi-EB49H0gAxzoeyCg{eRae3PK^EvtLlpi%9-eaT0UnzF|CfYetlt!DN)dU@`%2kRg0(`7o58Q7%}K +zql4_%eG~g1VJo&^bbC*Ir5kyKp-+iasl5GGw@9yA7sFmz)FQxc>}Ro)`OX>tI!=g;urEo$$Qknh8fz_F$8<)P4^-I<3L2Npqw +z_69V%@!#AqST4Ao1qj2Hg7SxQO}%1!V2dDX?f7s06ad3oaA_lwkE9=C{f{OSu-6~Y +zE3JflOT;j%h`KVS2MpanP`}&-{5XP_Uv{>nh>htHDBPeGtWiPWvDys61q;XnB%Gc* +zvApUiqyMHjpDHOg`V~~u4^4%SeEpAd>Y#*kaQmslZg%fzHt|C~6zWg@e4T}U!tbBc8>ZFD<7*4VR=s>IT +z{e;M4SG>dk+3?5fk)!QuH0=6fmSm#^FU*koGNtB&hQMX +zS%3YJmd5NgHx&;0HjMQHoH7POfngOZuIX%Mi{>AY=C2@juN5nVRZ%?sqb6qQo~WlS +zxQ#s6$e(OerKjG3P^|x@A^(}|1l9e$Qv@VTptl;T8UZVX5G)EH`aa+S{uvf^*#94e +ze+JkCCoacaPdeBNct5CRB$LCoO`UW#L;=ZeL!rZv$pyXp8N@74J93i&@eC^Jn}uhy +z=ja39^+n*)Zw%~CIt%j1f(A~Z7LmpD#yEo&{{ +zCmHu8VD;QEwn>IY>Y(NI0~m|r+QqZ-r0FOFSb7@r+Q)V`?FGd~9V=giiCq&L19vhw +z<<<+paUJPRr4JY}N80~Y;4)W$&Wal=`H38h_BF#s8X&P=`mY|dDs!#nuaLi%a~dPv +zbn`2>W#Y0*@Z}qSbr{GXdTpv&Y5;~HS{9X8RG3ug!V;Zv^g?(~i&=5+1p*bdI+FFA +zUf1EM_3LMI0r5D_vsUyY&Vu6Y#fZ9(ebAc!D+a?%l8@7XT2doy&Y!h_aOBTVl{8t#)Cdh`2_4VD{BME@uzfkZm$J06+O}i!*gc +zCm*HyelbJwmUz@V@MLcw27~J>73mF%#%^_g6$I=x?ts@=y^C^EvO1$WId#j-KolX| +z-c6Q4xoB*29}8?g%U!M(FLJL*kTMO>0|l1P>#1FaB`ZiFp0ux1!t=Fd+GWpPb95B^ +z|2i}W5%1~>c3I7brD4zAg{b8(=@~#yKZCp#M*&@o``hQoLta6-%WA4UV|Xx)Iy-Ya +zU^YjCJ!21Z!2k8E91yDqN|Q^r+s{I|?YGyHW;r@95elYDDPTbc$+?q@S*+;gu$crw +z&4@!x$K)Fj(V#+7!LDA#-aV=xy4*jz&NV4Dil(Rwcc{Eh=#k+lXnv8^Ccm5*O8pL0 +z1pMIxq4h(WK_03AK`}~MbK}g#MP=!ahOICViG6rS +zs3O-PiYI9EIVS$6_kG|;)xEIQsM8U}lhBz`o +zu;o}34z2pHL$dz-|91`Zh34S~Ac2Ps@##Q)RCfqy=0%37wnQ8p*pWq*!$t|%HU$^D +zP~Q~L$T|Qz;8}QN#VD~0a3iWTA2*ylY_c0Cu +zrK}uxy%&=bwyLLneieYEq_%#5WW`grwECeb%@FU62mW_MJRa3wf8{dc&*Q)Mk%!3x +z1AO6>zELTnVqEJl$P)ZJX&nqang_@k#0G>*&H>G<56|HSSpi%W35`*L49H7D?sof9 +z$Z+%#yL-K#P(<;mWw?ElN()LZh<7epdYhJN(CxGy`iYV5_wZ%6Zq_Wnq06@N)bXDf +zr?~M2$@K3xN6uelFx_colu+3K9xcNz$y^%LS_XPjH@oj4Q7WHOhc+0 +z*^7ca^qgluu`V?v5RoDhnC4snM+lJNp8$V|Kg&-VjEh(SIx`9Q9>N#zURZ=eT!j^UYcLZ +z@G~ePAW4h3&O+li7AOHMrcmV@xht_EaRz2}ggMZLZhk +zB@L7I1)l%EEN9!cSH*acWkwhIl=c*!Q?l2-kk`og0Mhz~fs6BY|WLZv6$fdn}l}EDyi?`~r&60FAtae$)*b_j`Em +zniqQ#_8d==jkb9VqmSB8O7W{qY%u4?YGSh(D_;fztQ!N}jMIZQaty1j={XfMr=e@X +z7geP13l)1rlgj{ee@p$q)I=pE0?`YGjg$9y5nG#qnv7@a}-C5C~WN4ESN`|@V6n%>Wwo;wBg`_`$? +zw_kbD<=rPO|I707Pu8o?oc+S96Bq!xaU*KDs~4hci)Cf4^&nU +zQeNN>5FSYbpg6}U$ai2wzV`_sA81Qb33zK&jPKlX|MD0t?Ln2gncwCo&c&a((E>;v +z*^{^V$9?-3q(^7{NKZ5UcTIsc;E9e=8ux%ulborUZgF{y&HkOeC7viuozIlbL9Iw<(uifcspG4 +zsE2=zr_J%%dWLVh(XjUhGc0?aoVMkr2sfN9>W-4Kt(+dBoy@$iIK^Qgzf(PRr&<15$iNY9Xe%%Bz!**<-- +z7WDJzAg27}bkokZ$+zOq@rYX)IYDHlX#ut~=O;Z0OVnPIIE+R5Fp*1Vm+#sbtC9nE +zm<~n5{8g<~jteqUuf36XxOHKXzkOiHv$1mQSFpTeZ`T5=xOPlWxedR +zyj;zzT=!OimfX@?aS`q%Ep5J=f!ib-t>tmJ +zvm(RG6hmj2H^5_1I5t#b;E%fD6@bN4-`{=L13WQp$!;ZD{1AW8i0JU4sWATL-=Lf>NyyO?nr5!I4Xz_Pl6*>tXe9|E@H&qnh}>8_3i2So +zp5vusDvO}N4>I(Q_XYpsg?(xCqs`sd&wX@;J +z*6pXTQ_FT;aG@T^^4n5pPNJBmt>=fEteNc4=w6;b{8h21w=y&|WMXKT#A7*Vz|iw+ +zTl~`x1op=X#-4T5iQH3~LMx>q@930t*a +z?41SuT=&fzn`jjF*S?4D^Ds8*Hrs>{k6+fS;Vp=;-CY|O2g!Y~XyuFCpR+ALMbS1& +zoPGp{sg)*f%6Wt6{r>eOo?oxzlD-ho&}Vm`32aYa2#3DO&28m9*1>nPeguDSJS9SO +z9De))?p5pcnWOJ!vD?pA=GkL*1VrVV1o)vs^4V*V`a}qDZX1#Mgni#ab+ma198_Dm +zifHiPip(X3$jKLrhGvjvU2aU?aE7t^?FJ|u(~w*6%kcz6{@}{Az>g-4jtbt|a1H(lP)6H7ea)H;H29^%sA9K{H;WP!UtFuD*bt-PcasLFet)V^{UF4uBr +z^Q@wuF=){LWY_z$)VMe6>>%s*WbH$6vU_!ps~(ICGbcS4R_$v%^RM3Ui`YWh#mfF> +zCX|1Q;YK5_!Z3IHb@AKxwoX3gvJyBv0qX`IB64xw)qQnWzP{ao~3y_8iN)IZz +zKeRGZ(1+^p2mA)33GI4qc~%yS?VJPBf0wG&euk}Grym=3?(;{Jlxx(o-#>VWAia4_ +zYMcy5OwHv73$?1JQb%6h21l;j5W{N+=T_EKw$ +zIY-#X3vzyfGc$;>ONa|Mh1(dxnR;@fO^Qq_F15ZU)K&a@*j4P%e*jN1P!$~Z9`3L= +z(VOQ;CLZjKanBMaC*C1i7@56hsi^Dn_1e2JsO~&(V0_rx!s5o0-wy!I)w5V62_>1; +zo!hk!Hp>R|!WZa^dBG1N6$r3<+8#R)XMCvuc}|1D{A!P{%kTPa?U}T| +zjl2P?AJ*p#u%L^0;$&|I{Lbn}Re>tGME~EwVl>3MX~M`Jnm2YD&5Z{ +zrkC0A)5KHsifukcVNVHtJlUYvNJBJ928o8T?rpphph_O%w>P$&=4996=>rWy2v;CN +zwQ@xOjm{d{+o%zll{*&=)4V9uZ1)(+E9cFoV}*ZS_OP8>cWHz>v2i%uZ=L8J3X>^a +z^RmEQinE(m*91*)4qiO!IVvQU=R)coI1DJbY(MpsX%{HVH9ocXUD)1CJ&R-UrZLQmd5t55|h?=Dbz&j{xu5-%7!kaN3TIsEg>p8k$YP%QE%aN4Rt +zCDK<_#tv_mJ^7Zmj)M=XvV?hbjXKY1Z=h@wlES5vj)WEr=YqXtH-mXiWo_z^iihYg>DM^H}j({odP4mKKJs +znaB%|M|h-pkWRh<7)u$~*^VpEzaUAk0EUT*_w(>3YDJhp&U>(6Q)s~tqotwuXO%>At_d`|+^Y9ZNlXO*d#?o{*>b_3K)vGlOR0VqS0Z +z&;_d0BJ@nc3<|&ZVlQKFI>9vlG!c=Qn3$2Fp)D^a0&jOPEA?d2BGqF}O_2-8YC8vw +zoY#4*+U(P2_8e+C7&Ao +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 +zA35jZ?>~CUDDyhkg72VJ78tQ2=$iJK7o-s +z84N9=L8$&J+piBYyQn8PhD3BcGT~_xfPiX^oheT>wl<86{!oo1sdxEFKF&GEKt%DT +z1&W*t`6XWyim=*+4#=+&W<#0P~M2aVHi1 +z&q;ZMlZyOAslKrzqqha>9PK}UaM~pD$+Cn&rZ=Do7jNV)5Z8A8;_i8T8UurRyZKb|N4Zb0K5Ms56h6>#zkge~T{ +z!k^FZe1vT13gUHM=w);6I#!? +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 +zuD+FWT2?rNn

H%9nTDf@#_Qqibd9fGtv)L7Cmv_l_TqxPbGxOTF><36Zh1AIg$(nN>r9anvZ +zOdBQJ9%{|~8N9cn+Vi9hi*)HZ{ZuvReSF2ii42-Cj)0qhzm(p??68-4w+Kb86XVQo +zya2I>BH%3o^EGby0YHPrZr@5&xt1228%xqm%;O~znKW6$z=Bkap$ziMHd)RW(O`hG +zK_0h1?&xZ&z%J5!WKqi|6bSI~Du5-$*-nd}J=-~9z?lBT3}s#r9w1Sc^em5eK}s-` +zAo!irZ7dXl=Aln5EKM>_+8k1Kzu^WQv*h29WH2tFk@P6LnY|iEAxoT0FO04QH`fC4 +zydQY0dhA7{9P40O8b4Okh=-osKWr%SHGlO}AVRX_G|`EzlU9Hkxer}Fx2>TKx|hz4 +za>LgjOVKb!@HHyhh4h6p?E>`$fe=*H13`zuKnPCo!8+OjpR0dI6|8<%81!31vXY-p +zSI}rBaYQb{>G3IX>A~kSY)tJv8 +zNl<#;UY7Pr(U$cZs|_;ag3lW_uCe;2z_(9k+)tbL_L?mNFCqS563Ao=B$F|JWpZZJ +zNt-5TZ!FRH4VbfAr)#BGD_U{U(9+V9cSgG4X)aD+L>Bt?j20cwhzb4+TOQxWM-1Fl{hTXSLNrWI0Ow# +z1ok{oV!{ANd}8M3RvU;25p1sZR=kt`uBcl|Ln9W_&`t;gQg9jFLyO`1-`>qU!7>vW +zNnrj|3D}d5Y^McJWNti3FC7B|^WKa!L^*`^bCI2sqcF<KG(ZbNCa?4pCn6zNq{I+0gx)b7}OKr93^w=Pb^4uFgUaeW8Q5jz<1|1jU=_Yfs7m1 +zBbbp6I^7n^^<1)JPEp4w(6ybdp1P5&Cr06C%$_v3k11H8KPR1$*IG-t8Mi2T2B1f) +zTK`4>)2DCuWQ*`@#iww_Ct85Sq>WDlmxCxQUnxMFXN3fwoAUrXi5-0ghVuhAkNQ%s +zZViWd7pi%EtWB+6e+J4}8XO9j4-hSkBT?I0_N3yi7FJr#3=00+oHsibo&7j%Rs1{52S7@q7*cjiRCluD~9d>F7jboxWHv~LMawqEz> +zZ@J%I&`XLRSa5k~JSd@UsI%}S%Q#P+%%MNaJjjjonrL6N&aUADoUI3+a-M}-!~|TL +zha;go>@$LQH-NCH>c@z_9`kjNk5^vOtLa$+)y^MYV>hCwLEg|6p??;l*XZm~r@Y +z*uj&^dR#^jt*I&!jhHT?Z`kh5+`Y#;Lw0OWkyQlA3DACOZ+LGIiN$D(3OY9^@9|R7 +zj-aswt&h#)YH|nh$&W{K7cpUiI>(C&3JO|DEcY*>B!)cV0erNh&q9JJtGgQ6ylKC) +z{&PNX4BzWRHCNqNkCO)$S-Se3n~#U=SoVO=P|zFCas8Se%{vSz0oRtq+C%WWnoRV^ +zN25;BBF*4AOu78b`gRIE4{g~xo8*)hC}h{P4G8M%YyzMRCItY4OXDvk;^uBa;=e+p*?4sifYpiJZfmZxo3gZVf{#I;At(`U| +zAMG$^8WD@a5-+i_p&^JI=3@C@u~Ji5Rp$=q*#(c(L4A6c2GTEkugi%`mUBNpy!|sj +zG^A@aYPoyY*)A4;_fqE)`fSZDrS~t}1R17)?*XF0p&$w!MdK*LIUYlg`plWr$rveC +z9pI6!dbWyq0|Xo-A~8IYuTa6@`eR(K!+(Spp-b`ZDoDiRt1$|gVhuJGVv771c`7v2 +zbQR?NTY8_t9MS90AdJA&r1S;>DNHjZ(PW-1qPM3wMm!yv9}Gcxo)b +zN1zc3Fc;J!(PFk26b1jDT1oB2+XBUuvx}JEA*Tu4Ld#!P(8j6)8=}kM4UR((~2~Th59L&%ju}RsegO& +zjA6fjye*Bdz|dr%es!CL;dQ%;EJ3QJILSTY@B3(xe_v=Pw(A&R>%=5Ufgs9z=_?_l +z3R}NmfOQAC4`@XcUNxMjOXeoQ)){Rsyxw@sxZTKu`h_K1wj)*fA>DDk8#g-k63x7x +zbI^M|mx)aKbZ%1j=WwQ4@W=QHg*Mby_NTmSSAt?*ejVEWH2c+hXa8Mgp>G}=jlz01 +z8kalbvLmXFE?uCU3f^8!mEo2>P9<}573k|G +ziajVxsTa?LcvZP418FKd%}lUb?EL=S+h|B+T182zyD;HmM)vn-S$+D +zbI6tcXOl^3gzR*{FUByNMr;7GlKnfiFHA*S$-%N)J;?{hKXp)RoE{1;T6gKLKU+bT +zIP$ay{@{vMMQ#WxkAusM0=GakNh0`&D3_ciJdSYOH~Jzx9w`m#8kT+#-L~D=jZ1*%w_ak +z)I9n+a8>0f7-%=g<&H=2*}Mc-%XR7cgQ0J7zXz*704cm9;87*5@z5zo0JAMf7Yze$ +z%UjaIaWIqD`c2ACuUaa&gk#qQmsrvO$Z=Jru^$V8E7bIN+wqM#cQ|NWv1KH=d9+ +zhSVJWqBOK3c?JR?uLb=3TD3=uu`l+7JP0bD!IfbOZsv(gg3k`7H?Vm*bhKal!cQJ#kEVqwe#Raa&rXjbR#U(MIM7=> +zqTMh@upqmp2$6ZN94Z5D&{6)|@37`yLpzer&7Kvl&P>n7Z?6)t3ScHp_`7eC7A0Ua +ze1j^#`97ckt#0mg6j)9WXp|MU`~rZNR{_^R2bpW!@_nXxV}oFy+4 +zT#xoDm2IbT@VCmpa_JSV>G9kID5|-|wQ;ygcotVo8qI!3TcTV$$6e7*#z8|Qf^Mny +z;jtIVFe!(1e;(kgxDyYy!{Z3$==m0)lB|1EznAAA@Ma}eK&78o`sx(?3k_rd!bxWowzsZZ)AK}*PsG95Ug9jX!HKo=5$Za}Z{pzuDpWLlsuXmOG91x#@@eS* +z2ECA@WX$SKsDiOfeFh^Ms$ZMd*mi$1Z0<$w%W^Hp-F8Ie`U9K8Y> +zq{KgbE3I0J0fDC=ef>uOgyM1|BqG}blCcdeeD`<|?(j4@C!}I`~C}%&P +zVqP@suQq$m_m@sNXdjx@Ra-ic1t?bs9x1$9i0omzlC*%>5BEhg|X8wldspy=!~7&(=w@}tP26ioT?HBM0ifvK-a +zic*s(1>;ZhQ)>OjD}<srOC&r-IHi2h|u+8FWCh0@M{Cs!;zSV9rct#`8FREhO4HTbA9p==c(|Y5oOdScDT+nVXb6 +zkaqcrw_j+2{{S3e5cj9VuaH^g8#$T9FPr!!Dxao%0XIy=0tXS=swm~ +z!wmsPgwWkYG3)uN8jH|B62NCTpM%`x?M1DNZ*+KXXf-J`vhz?VT|SY1z010WZOYs+ +z|H#~pf#sv-Du2E5gQaAhKA)SDx~8+l*LW&mwydv;T_4j0AFqlBfZwe_2Cen?E~1_>IqVwphM1OPa;PfB5U;IJk*m744i)HEr`41 +zUn3Uv!rJMwr3*j`p#*2v@2{7bs|IiG5f0vbT0N@D>Bdkhg8Mk3pWkv8nZ-ev~?K`8crA%cpwo&hWr(;o-2Iv@fxV~Se-5J +z+|)C!$s@gDS*m*W-4!wke|weGu5~mThsG@(zkFQ6ZQI_|(3M@k?PV5W2lHRI1&JVh +z1E4Q`<)I5^iE``_7_A9IfyZgk@0(=Bg)%~QcdGXT#+9qSBN5)J8{teTD6i*+>F2t2 +z{Y@2BUVz++b`o67GH+oOcTe_0du9VX@XPCycVs@ys{$?(fD99%=;?J+Cp>z`-A@h)COXcJ#)BL)`*l4}(lXi40*~R#WDV7%-Q> +zchd!iD*Fk+KOP6CknYt1G77=NnFA+Stww)h@p9TC4t`c?5j_eDbifz3vuedK=GS?J +zuGb|#p=rB^ExAA!ZK&-8hGguUUrq#_n9N=M4|yRRXiX8UdiRkS7+Ekks{HjM%__AQ +zoDm-t`?c6NAeT@_g1P|m&)}xYdO*jXb9N*0qzb(s^M8TYirtm#cDaYxX(NRMn2-f* +zR{WoP>=fw`bWUxj12qRLC&OzCG*%Xh1w&2oJq@X*+Ew5dkUo_?&_xNX1<&bslLgwi +zHhjP8+{U!^wc97k#-SHAIe1ZgKQ0AabQ+EMUtGA^~T +zZt67A-H1qvSKB=&&y4+}fwl~iazJ0xX4erC7N-lh-sn-dL@V5X&01WxJC{=?O +zR)c%L +z^CI1qVlC^3z^-$Mah}S-yGtR>>PG$0udy>=zU3IR8?MDK9potg%)j6s_`Wgp4R#wvIF6+Sq(al=qd +zILJBLtw9P25du#&vitEe@pIKTNEed==1{O{)UR0iSx_onNf0n1$}%!F>1(vj-1Id{ +zxqz~8HI0e~z2l$3{Tbkt3%oD_0kg7C<(=Yyqgu(roWM`!f-x$l>dq2Eg$Sr&@724z +zp!)}CY_WLi68;%=^H1v9WWLhiFoR4H7-hv6I-CgcE!^RX4b@_)>!`VNHCC^!muetuF)b_7JshR4AO99zjj|8Eecamk|UA +zV7!Rx)2)3fo4@!a%?D4o$t%3V=NEU2uawo7uB?z$e{N2)#oydw3l&^%JOCJe(bVsb +zz{I1*`TQZ9_sG__PS-!qD<{T13lMfTh$O_Fb9S+L!{np?ygEhT4jdN96caUkH6%7u +z8m{4cXMPY!TJ}kHv`Dbl%Q7>3}ry_K7XFt(c +zK<@`MqYT|%eGZNcuvtET*UKdp)Q6hQM^}5@>r2zbxYR!HXs;U9>u|X{7hU#lYZY^9 +zVmA$Owk~&k9KV8*PTMkKyvp1XrYC}9^vm(Ll4!|96UDx6w!mL#2_If8JKNbFwDbNx +zE;sLSsl3>myYp}r7;Yt@ddzDG>SURQs0+naRRg;I98U*)w1b)BEUWwbH~7;v=qj~G +zq9D1>>}h-rL|ZuFWC`w-%bjTJP`UT&gRQ(n!&vocTdu;V6MMFejRQWHmfmeur3N$e +z{}~AC=o{GC(_-dd9^mAhZywkS7lj4i+PXVR}hT;ruN2egiZUqOZMgTDv%_*nQl1ad<^cA3Bo?tc&X?G|85 +m{QX?;yV(AE|NkCQx5sc>d?vA5*F^CU_=mn|q+OzE7y5rz$h_bH + +literal 73335 +zcmeFZhdY-48$OO6GLo5)%~?>K(P@jgC%+|RmS>$=YCJkRUCWvH(~P0310L_|cbrFjZVM6^eV +zh=_QOoD8n`F4>+YB4$_AI;CRblQmtlx4QJSSD6y6vU2PPPU11k3zI@U)WnZyRW(i* +zj2XyYE$&f?j(Vkn +zJqhE#?Psjy->gAFO!-D`_ZN+&GQQ=95s}(u?0#Qyit2)qYEZ}%?Z11i;X{;>AC5NH +zMuh(Fu9T=8W#r(+2r#?s?q6L!6MFQD&c7?7CQRrR;eS`Cb-4-Oti1cpxxL(qZtv}>38*Dc8e{+1Cu9O$QgbC!jKQTO)l&+ZdC+^kW|s~pIss7-Dt5%+HpQA@NO +zuZ5KlzeZ-HP-S=0$l~@b7?qHGFhB7UtNS_yITiH3t}WXBwZ5 +z`Idj5NaYy%J;k_x-=h+H0r>EgrBCQC4C!Uq=?Z)#ILqg|6&S#?6>?e^1tf@Tl^8xfm=vm(pBG` +zRuA{+gdUy0ixsmfu!U(MoT!Z5nAwt-yeg25X@Q^NCtgIC){^iu|8`OUgdF^tpi8$B +zdW4jZHETE@%oQTMC4G0ZSs^iqHaBNybK=Zv4V9qW)Kvi?<((yi9TNleNYd|4d{-RP +z!b`6v>fQ@QcQdz_-FD7yNBT;HcuZtpFZ|PQZLRnb?=@C*yx;1ktGRv3nM|Iu$JQS2 +ztUq2F>yR({__$UMBXIv89^f}`o_JH)ufOu>#f9}Tex(7nJP1iqG&};a1ri~Rj8Dxu +z72zNrBaJHdjWs=reZ6%bd3oh~Lg1#I`Bd#j&!Ef~W)en|-Nnt#-v2HC1AXpUSYir8 +z*xktCM8aGtI^ar*znR7fAGs=EMf~%KV)VHnxmyX}4&-_-7*!*S4Iw?3Z} +z;ccd|Dt@>tBxCTScZhC4ROM5KQ9}&XK!#Aio2Kk;qet41dckOr^gaw{207UH^Ff!; +zuQUDkbw|0uR7oxSpNS=$MiiORM2j*7i(Gd@3y +zi^h!4Z!d7{9Nik(*$Cu4lcSZgaxX?~kd1U>#w}1d=jWn0cUnxAFPzZC)9`agf0YQ< +z{MKL33He&Fwj;8-lg6~>NqVEnB)e|xeDY1H3F6kkf1#Q3PQ~Qb(EanVQXPpV>@lLxOb0KQw;xG) +z3!yUcQPr%R*7%e4pAU=9Q2jJ_j(jnmYEf9XQB`-_gs*2x+aQ(RZ5|-frCwNWB*8S9 +zh<63$ERRt6pY*TA8V8y9;d+*94+$woaY~RTZWyTrZHpiwKT(Bn>z|{A3qPN;&{G_H +z(joV&;J|p@yK(ECKi0igqj{lTNMnMcLk$^l4hgPfV44@%yc( +zHSS(dytg33@va&62Akh}gXHSGQgUm4w^IQ8Iyoj3mSxj$fZ2jj_tv~$+ZKBV!3Kj)iW9zp6 +zJT5|#b#m+X#Wrz+1jY6H1&g09-q!1(*Ad|=RuHRQ{kAlA+dx~0MlK#1dYTFQLCdQu +z>zzBB23q4s6%?`}A|kM26Is{veYe*pcvIdke|F3HkrNo8W&d>gsi0`3iS|S`KL@OZ +zP$Cx+5MDvtphE(J59a$X1EX$jG&r!b#y9dFpnp$oCHv|w?v|NiseZPwe8=iOf)fZ@I4P17CO{2{}eviCwr +z?pR2ASN+7|d#TQX-G^T%RSGu#4BOUXc4$QVvt*_q#pKd&pfwYg{GL +zt5vW_Qz+r(k4)j2XP>Vmw_fy=#=C#Nump9J93z^L2dHgO}t3 +zKmZ7YR9s4-5iOlFZQ?msHd@b(sj=fdutGF3B2P8f(*oBHYK=GCC(7UR67EaEIqkeR +z+)dzW6Gu|fVxs{$XwHP>mZ@~!^s*huZ_~&XjuE=f9H}R$zpofwV_N<1|y3zXA +zP?{qtdwCAUULBK^l;qySmM@qVcYIL5f0W7^PU*|+XIP;w1BHcCuf~#fU}j0UB|p19 +z&n}Qm>V8=9HRLG7n&88bM79QdumHs{_bc4a!T-W(B*g1@q60BVF0Y`2-OZ`gCYpBT +zyua64qQ+E(*zzZ(nTlTcQqK4+T)e{Ns+ng~|HJBDOICQBZp>^1EO8-WSn;?0CPJb3o|4^uUev-BmekdHA?is953n7oYDHjEAT%s!Avu +zCbmr|uDq;x&19h|bUV%Z2dfTHd{6q^p6x1O +zfN448Yb??i$0l~fsxGhJ2;RhrMeR)HC-iVdvT+ytA-_H9<9rc*k6LToNUrpur*U{s +z5(C_GdtBqr&Qwv(T`S)yUf7lu(uGCijqzU7mT6SkYgRXBc;VyWfxlJu9>zJO+mdP7 +zt48wwIX3M3&=0cJWYZB^j?<)3&ytl#07;?vmH?UVk(fYHa+df3pJH?~A0&Mws`4o9 +zTpv&A?WAH~?iqfSi$p{1(7qV4R{|&hd@zQ?8-C!d=C4Opj|vuTPK74Lp_4Y+!jquu +zR=4#G0@h*Kyx!d3E#dpGW7{a!e&6&MU0;*;7~Q^_^Yq3{CGL{|S7tFM`Fc0EBFEut +zx|vpk<%2^WZK8U?_jwg5Nj%>kzAkO;@sW&}&wkZqBvXC05a;NoT^g+SHu!I3S*ACmV_azY}2&oxFY=iw+{I^oC$l@o~9cT +zywa3A5&p{q{Qf_UbKrc!xjgUtHFz&1w1o;WSaX@O<>+IkFC`Y6$PLBt9+I+sOj^>3 +z7nzUZZ_cIA0F`qXa_^yjEBTs5@tvL_e8t6sZ2=PW4N;hKM&_jZ6yizDW*0EwNx004 +z_{|StNkX%Gd8A@ZKZemA_bwa{z*Q&y+Pl|lCuiq(V2{FL`Es001W}yP-w~V+2-xo2 +z5#dQpCfGYnZYv%y~sGjz`C2EJg?*0VT_C;r4eCyi%+`Yayb4`x4nSk!!F +z4?b|u#CY}g`Y%|R%8zGVt~^R?bCLA$05GC0l+#leEEV(Vi$a=xs#&2EF2TlOUG_&5 +zlD*`5`4m^Soqgv=wuVQ}FmN8=P(KnBRF_J`r5Jl}503z5vl2tTV2dGTUIT;)1&ArJ +z@gME=ek)ES7dO7yy3Ykyr+uh|hp$_ccQNyhId;*mC2>V39Qv&*VxhzEHC5NH3ht|S +zYh^^dSV8f3MrT2`_n;ObT#{t7I?|3~eSHL_DIZTk>e7E)y%=4vxVo5Dm(^$PBC0jM +zu)Q%?#IdvbHo3LPvm(lQ--=dIV%wOhLFG%_=X+A^&0J>|1<6Cc8g!lWhB7_kZzeY1 +zg~T6d5!ee*3qk`FLr|sC=04Fv^OnTHN*kHuZDeG47x~%RjoBr)on<$GG4TM))m{Cr`bu|erXdH`(`fEGnsT+)PA8~Iyz&>tN;Aek2`LuChX(_+~y?mf1cP` +ziwU=Be#>-uBv`BuKTpYiq(& +z&9&Dj^N@68mkkURWJCfd@UIr3BP7bGfS_&8$iHNP(=>v)IF+jAwim3HNYUi1)r6U= +z{;hv4G??D>chg{wC7(vUf9J`Sf;(4UAv=;X@a=(fmPw!RdKdRfA9UQb1nTc +z1zdSD8gc0O^QTBem9O1?ypvhm5<$bA0VFt`5Y*^eXh!@gr2u)6ffLTe|5o?ti(y21 +zpTjoyv9@$RuYG;@9XdZm@1pkcNb!~#vr_AbAvMNtzg+rwg~gU7fU!`fxPO0xFMa;S +z9J|IAxVbPzy}$i^SbU+UU3jm)yy({Q^STrh_SDczu;4>WL)SVX?GHHI(Hoq5N|vQR +zHWec15NmZb`b-G^dpv|jX0`u|QsC6t7>I*3NnnQ{*qjN9GMFrf7#)~xr6&fhA+g39!crE#eescg0$4Z!sw&+B^cJvJF5vh0Kt0SZLK%Z1aKDn +zX?Kc*sOMSg$5)PJrU +zLRG$`RRMC^y*9n6ee@ip#Hwe3|4_Y)*<6yUYzBrSJzUw^QU`ddC9^2}Bp1#g?<*9O40+pfcqJdnx?}D8ZhLMnaO5;-)-jPte`J6l +zHlL^^k<)QPO>yj6j}|}vTm~7Af|_h%9;f_WkH@MpU}GlDA=~RL +z6FEC;IlW4oO-hkkR0!o;$b#gE8(rHsp;ny>SmIerq={pUv!M_!tw`RK;^^DJd7sb`c@kE^ +z&|}DWb*^TmRq1yIEM|1*jlQ<(wI6pN`7Z?=&0JXho}eSd!D@M2Ng!`2;K@P*R!0<2 +zFFEBWzUr`^jn`(_o$a|Dv0}eerJc4x?Fvvh>r?LuijBbAcHfAdMaw{&m!>))xJ&C +z<7$dcP4+SG(VYBWLTe&9^@BI5$o9Ni|W7edI-{=_@(x +zX(GM6mCGMR) +z*b`)RMnFm0N}+-m$%rAmM72;p_E5_VK@5tBPfj(qIGdVDMSYqx7?zy{Y6#a-C=f$oq)i2+yQwE}h +z5O$z9Sw~E;*l)l5blK9En{!%1IS9Hhi@j*tH=pIV`0jP@Q>c2VdbdB#R$g)%yC_TJkRnlzQv&e;YXTNzpT@cnLNYXtictgf(0P~7|SJV +zOy?HnK4fT1RBt#Jq}_}Z;e_;!UKTjQTh5BAhC +z>5t?!3;Fb +z%Nu_G2tUAlb5hxcQ%qBJTQhFG&fybhyQg~B4!U=#a9mfF9Aw5c1Q{FoF*asE-Uk`6 +zis&Qc{qsRi8%}#_DyRsQbJS|+i2|}xz{jCkUDh`V+C$e0fP!P_cBb9Nj!i_Xu9lNIfHLY-KmhOJ?{ZiN?*aQ> +zvO||4W#%fB?t9$_gh>lSm%dBF11ka>< +z<9h%Rs?L&%8{D|ns(FCtY^AcFkF&Ot%Z<@Ns=`yrGItKUE2XoV#RP6V57fCn;(whW +z7U?F?5VM*h`Icgc{rsh3&l|RC7o7cQNyGBO@M%`BZrpdE7_`OJv$FKH;_6ddFPB<2 +z!VyX9vkvr!gPd2&_QjmUjdB2Y57Wk?HNE<#!;RPU$S3+T&ibgU6c}R6p3`MjSuJMK +zg`(tSksw8x4&K(kzba8lk=_$l0M+(#KuIAH=&}_zD*UZX{FcUA+LJl?F_$CB(7+p8Zib +ziqudq(TSrOAwl`^+P8kE+`B;)BB(t5{gZJd@NZ$1o;f6K$Y>o8H*BSaa +zQ0EI{%6K^?b<>2yv#Yd6SLQdgzs`-$3E2jdG9@mLKZa19&1d{W+a@hNS%1$Lz_!wI +za+%ucwh=5n+%Y2D#gZcS6=9m!1wBP{JB5RXzfJn+_)Ou`dJef&a~1nBy_|iu^!j&4 +z3{-IG^pM{Fk?8+y1m_TLJ?}8z*UqUn0?Uphc+#ltTh-T^hIZM5GK6?3a8G68!MBO0 +zL46dze_{&PBT;Rn1lz6r5W{Q9R~KYM-#q=AI6< +zW)>B^^uCqRn>{{%evZkx&%T@}oVa^aAG=lpYKilB$~nN628wGJU#)(Nfglb%BcH4L +z4Z9(UA^EE?Av_cqXr3m=*ltOy%l`i|RuEVN8RDnA5`NcQ@6Pk@ +zs9tLZL*YgATGb%3P$X}&jxYUr%rNyX(vNS#JUS^U30>K;EShF6FQ7EykxjQ<2<#?z +z4KP5E4qM9tog#9#^2W! +z*C+EmTeu=MAMAT(?QrM^{fR?SQX$&P^oim0raIVHAEnr!Y;^{>57lLwb-)eZrEU$_ +z#6^*A%>w6%OKhJ``#G}MBMM6ogMk8U(aBSjy4tzD_W8H4tYotJ#y1IB)Pv?Eu217c +zelb;u)?1XRzYhk5(p_pEFr99LHzu-q&!{)e%eX#y%`|l_X9HBlC!T)fO!v;TCTXw_3~lCyK}--A +zt=n9L3jUZ9*d6Y|i~}NMe5PB(BNkljr#mGa>{>UlU4{EGNt5pw;pMJEyOej@k58LZ +z&V34x{gaFd=0eJ +zqK&yaYq-XD1%e!832L=$o3Qw#SMq +z*VAJ*y72vwI!@c#=}!oAP?Chz=27~y3}a!{w{(1x%oqxy(~5^^zJ-~W35)h`X6T5m +ztx8;36H&pEcU|O$@05ZF`FWTAZ97qx9-RU#*W +zrcQ2kbaE1)rlBdqvPS%}8$9GzuAQFclEK_AG_#LyqL41NJRxA?^GG4^V0u`4zqu~8 +zUTfEKJ#ELkg1}>31r5MC;pA9bLXHv1HjQ038PQznc&scAce4d=$biMitr=splj5^y(j$@d~&Tqi{xA2dy +zepd_PlY(SVMlw@1+v22H++_wUTEmAROc5uGj&Rm>A>*9`pZ5s!mXAv-V4{sYY+o+N8|C +z2F_^7>)CibpE&uZCml!l#O`IzycwPJo1GDX!y~8HT=AUg2zi;092@n?Ayk({ON;<+D=lg +zv)Gv374boCfRCV5??oQiXJ1vU91k-^v_wkM(l-}iek^;uuopzo(*+j$d!v=pM;z!x +zU6fr}y5SBZdyd_9zVuyBX81$aA*)8H1Ji3ahUc^&#O7!h8L~=WT@{V|ntDOj?dR{F +zfq+ogvHSrll0)D!{f8Go*|VkVw;W9UL;rtR%O)e8KkNJlDhS#GC=9jV@=+RW3c>F_ +z=^(rD?spKuXyqmejmB7SURal#pghonRD8FME1cdtf6#Ge4mcQNpJwL3qBU-JGVR?{ +z^+Smc1ikt7;C12eKV#AnD)~5z$I%kX6reG-)(8IGG~$YLZ(k#l49BXV9{`x4$I8@%(pjDQPK1mezuHeuCWa(f?_N<5nI(ISmga +zVY547g11JfY^iLXk%$GK*Zsu$iqTuW?Z%`ssFZ5D1dz+a +z8MXD|!rNXM@v7KKbwyH@#PA9(rd{~~Kn866`6!oJe#DUVKejx|89CxWaa5Pk2#Vrr +zkq5Mdz8g?SG~$xYEI{}Aheg`LJ6Dov?6W*t1=}N^U|7$!F||&gee=kk_r2^ymh;N+ +z^)wTf>fs5|Knk{@R^mr^)BRV&k(2S#2$Ob=RU26!mkiTh6YZw?DyZ5OSzc7DVC_x& +zoZSQ79MH8BzGzJ`rufcWrpb$}Azkh6K@bNBC#2w-<_G3P5iz{GklM@9r +zax#+H&&YWjmrw +za0vhbhWb#$UxcjQJ3s&?u)p|;JrHk&v=)Lx$3_DjM#Fp8EUpN0v@#PEf#R^0kwB%b +z)wfEiCM?o-rSIX9JQ3~npr6GtYvEwrwf#BkABuVuH>PCAduCvQ~B7x6n>w)OZag$77R;` +zyhp-+_klVRLUgn(_)k76k01t6a@yV=S*C`bgg+$27tE#H^}r2Cn+w~c2>p1_-J}H_NyMA(pQqI= +z2XA5AV&lcyCK{QzvE>k%vFd5lz^y;LcW(zh;;tUh2=FPlKBRsm5fuANjpVJM+H_C9 +zb$;m5Be!VGp#A_cw^RTQI<8RyFCPLwgM}+IOM% +z6Fbiwa@$AzK$##4Ih&9?O)7n!N@GuZ%7Xoe+J4z1AS!48Dtyi=KT~nLptUSZcF)Gl +zbL&j6J~O0b6Rzq>QB?y{>mh`x!pNg~C1F5-3myh+oGcXDO-B-m1%FmmMLh|XCSle_ +z+wbGQU%8%Pr(sMRP +z%52o%*~&zO7VZX3Our=}pMb$a5HP{jYl9Y|#HxPg8Ud(5x=mNTgt?f2T-3gV74-!a +z9E$23mXsv#OD|@KqUnDh#mAE|lz_-KlO`)EWA+!x6LV1L|IsLavjiA;XRx>cN=$W4 +zEUhXwqgff!R&ShSgb+q@q2pPGgl!1SLv*%%Ip&vnDMGEtwiht6{&{!o!b*>hI35Vq +z^H}?Id2f>R3`d8%;jlxG#x9Y9=l~W$2nf3tUf@NrI*6c>_n60LnG*3S{P2Dn4ya_; +z-b>I_KA^YvxBL~$L5+4b>d#!T`KDHjH~53xqndwY6H$Hm4LU`ql& +zzvIst%Gx%BVEgNbbS2#f~b15sLrK)b}qEx`|wk19u5L)#7#+{{)%Rvk62;t +zmwpcU+74fAc#5y^3Y&*g*8nA(TWua4yW`GS-b^o8v%>x`eq4<<_Ne6R|J>^TmoB0| +zVnfF{7)Y`Ow@z?4_npWEpEDR9cJ0sfBo|SwBw}f;GvIi3dHeCjynQa;^%J$q%vwQx +z5bexT@C=vU7oxmpvDD392Wvm(8J%5r*X|PG{f3M-Q6sd{Lg1`=FLBsXUeVz +zQxL=nr*$BJ0gUXEdY`yoZjw{$fXBv+;KDb~%qE)>BLeV1EsVUbe$E^NU{e2I|1}*d +zppXin0t#Zw2G1bS=>ekw;J-#q*~ih@KUKQebja4Yd+{%XUq-oK6gH`y#D` +z%Ff?ZFjn&Dpa~@+6bT-3tuVSKQ+R=V?1-jLgi#k~y3pv2<6vy$L2<@kKRD7R@Q5e{ +z^*a*a$N$*Gy92utaEUz1OD`?2!QDe!4GK2{`%W|H$)GXcV72}KnI!i4}eEZ+Zx8vk<<}OmOgpI3c+%wdZ4e0aX+P{XZLB|Gc6u%?&}w{#cKsPLTp&^ +zghmL!2Qo+_gzCs?j=p$MTv3mFxRK}LmAN#;Q-NZQVNA>J8^LgaTxQ^O85x9GRYYes +zOI)l>Sr#OrnMK2ydE!>ykOGyY=?TFcm!Df{%8 +z_A719F9Ccbq(BjAzVCsKCzm{7!gaxVR8>Hk)S)Ki9lMR`6-xoeck9SvLzdoEW|-KwMDd9G +zii$IDH1M5aWA8Xw+`iwfE_-|Ve*Wqn)gU>$$7ap7ylx{6)deY@2ogDtgWO3xMM+?_feu84M3CqY@$Mqh5#RUo|ydJqt +zLtP6UQ5g<7{@!pQ-hX=N9OYst<^WG{_$l+t2azo=q;dD$0i^lw{gYC;k7VSFH*V`G +zovIzPrYdA#AiFCwR~`IVP&t-a!lOQlm}hTg?(k^9d!gia$1KzzS2gS-R(ArOI^)NE +ztSIrl1p8CBlJ=|P(y`~L3W<_eKGWl_wO4<5K+1DH_UloPx8zo$v>cBzk~GxWGCa(v +z3Zo>kt>l6$`GrJD;%hoK8bxg!^dukDk=(oZd%cmW=+6F=CW<%}+P1>I_^zww@S$@Ll{V?K}OM@pKN^b>VNV{x0WKL7`Gob+ANQd6~F~9>%|q- +zJ5WNx)SGTiHw +zK`Plb$csAQzyhJ*#XobWWJ%Q>IuTlFoaB`(*OlX8Q_TzpI1?7w%699s*Xx9fJy_q! +zHO+lrNo^c?J7U(_H<6R`=9cHJ8}EuZ(<$?mCb<=(E8C5rRY|pW%niTWPY8<4AdL3I +zx6G4Bd*S~r&{+NLaiV3w;IqMdQaiR&M^3zMjnsV7v?8i9Y;x<6x$#Z=<#n;+P$gX) +z1-rFAV&Xq?<_!$$Hi(m4kSAPyUU{)@**sbXFeg+j$*ebka;9YY#%v@mP>vj-ddq&Z +zq`2YqGq~*(kM?8z1(BxbjLEavvd;?|vjcEcxTccuGZ^U=eKtzFEMKR#y-F +z8X>uyLiiX`e(|-lTO>K~3-XVAgM4V&$Rdi`_ +zSKuOi94zF4h(lY2r@=&0?P^OVO<#cNiOJm19sm-Y#gd;i>!120o&PLi{KM3%n61|-4*F&AMnnad8=c4B#y2Uo8IVPW=BT-{iOXVTv9jo +z83@u#*L%3M!gzDw5UTvV3FHPj<-KGbNX}zA+lZUvpXd_+#E6G`J9Jb)Bd50`&m8zC +zAi0_ZtwTH6ZrcTX4o?!6&MB}}Mozny9B4gxT`edRT_Z+lQvIHp4z-lXG$hhRh0RLX +zS5RQl%g^H)uSll47HDW_^tQBU3WZN$b?-c_cVf-Igue_%=k{X?c*He6g?hYM*nYkn +zy^H50Z3zyKcv5RtByQg6%RSFcBy9hv-%6+IR%~mRk#>aZaH!4GrgBSwShLr#Ko!94 +zD1j6ti26v;mQ%(?u}88Rf4eYHedxbF)N?^mfiHf5eZ_;6{HThdhaes2BJa*+)xV=* +z(h+ahy0Vy;g^s79SN}9kjmP&mnL;q*p2OOgc@>Pua&~IQdO4Q=bJGijmeZql=ahwi +zrNg*%?06vbw@4ov!j?#0$kuP^(1MaHr=WFhAt5m9OW=;bR?31ny{1UxWT9u5ibNMQ +z)u|ZLrd%-v9B92brw$@4p*fb|O6`hqndL>arCq3lbaJ4=(?1*(MG=Cf+&a3FpN53I +zgA62$i&9s+Yz#9vZk$rYfwdx;^w)Y^z+p9+$rGZVJ=B_3Wv&2ES@`B|=-3rRNKa|# +zaDd9`VL_`i^UfR5XP>zJXRqZz$sgRakqkDS?WES>Sdj53?i@fNP}&4J4{ +zMy+ga)k(|Xp}}>?CyXkr@sX1yQbI8=5Tz{-?hpu1pP%e%OrF0cptF}Yz`Z>iHLaph +z=zrUNcY%+pE-j-l@I-#^M&2_Y%!S%6RQ)@Zb?!{VI~W-P`zbHV*vC%!80RzC!ByHlNtoc-RpXfIz5 +z;=L0qrl66$YjnV}$DCX{(j5&}IpX|#=yAFCffhOP)(dd&sI9evigt!}r3peX7jyrR +z5W)csg3ByTc?6&u5JRAup~TQG8bS_?Rg~8Y4se&$j?FpjqcTK}ev^vYR-1Keq{j&b +zoZ2ZJq$xSGh&pX?^LJ;_wGLSgLYNDF6Qf?js7~cHDIy))uRWnzWJnpv5m*!+v0yH! +z(OS;_*YTM1rrA!TGw#hfMPwqAVvhWP-x3UBO+ +z@jj~KYO;N$Mdz&j|5OMS4IW$sGre};&8&h}+gFUeh1z*f@ZF-X%dN)X2)TGaz?ZQ{ +zcpb(2t&T=cfj2Y7L>Bmj(kaev4fY#H&O^ve7AitQ^g$~h+x0(>14WA^PxyJ?);peL +zB1`?f@`}fRz4NWN9Q@cM0~HA(AXft0?(bMLRGUP&PC=7g!vn~y@%{Uj4h&vWpQd{_ +z*tL`B24oQV>rY;{YhN56d}CgCYUO*Mg_~=~1GGaWOp`OTl4}H**~br71#Umk=TQdF +zq^fypzM;_Xk)DXnKPz=gg%|-K*>j0)*9Ym?#2&<^oHZ9Ltyvy~y(j3cTUlOz +zN9qf6O5^9FUqVc69voKN!I%ArtW?7P$E(WrP5DDvW@(PlKxg!k)cmPBL{CKf1{kq= +zZo+Byp$*X|Lg`O+HPe+xfIeT!+I{$SL~mT7JbaG_{_-V=k5ZT~EqYG??2h +z%SDx_^^V74={ldq*Y<5-%X<9EI}dG}Lr2TjS6>!rXu+QT+>*v +zp|$DNvRzfr3ZVa^h0`lD>6y6%cgwL(aQC+-HDq3`iFJ}pzQx%e=11oX#E%i8HY;dUt|YU2hyxJ> +zWqXQ5Va7lnnzPC3$tn9S)u8hyI(a +z?=j|f0q~VTKvKbUrn4H<#NQMRi@ct7X|D{+IH21gF~Yzqul!({pTI;(c?Z;VtM>|8 +zbMwZ7pW0Acl^QoflhqL_&$FK9c%Qx^Q;pM3>FHM-8(w+I-VrGq2npY_{{&~d!teJm +zQ~L289jZ;HG@t&&4UcHA6<`!MwEYb<*fV-+H(F&>Br@{0y}+l2Hp3v$gB?LofEPj2 +zqFf37D}8mPbrt!5h529447>@bsU^}Sic*lpyb;xl7Wy(PjMWf)XaTPc?fqF1ogjIZ +zFdTJp(Pzv?_x%JS1yAYn3fY5&LlbQvY<@RQQy{9ibj%rf)j09}B +zLWW=)rat~j89Tmd7{Vs^1gntkf$Ze;hgZyG0D#P$x|=kKJyDU`KUM^q!DVLUX +zcEea9AYD+d0564v+&7(sf7g=h!hy)S%0a#=N^)xC&6d+aj@P|?-auFZkHE6qU_&s> +zKo|U9cMWGVMrA5O0~wP1C>`hQ9g2Rm*F{<5i~O^li$ +zP!tV7FkYhM_B{&wRO^tl%MqwpqnC8T?$OFM`^WJ})X1e;yN;`g9DN?qLVLv(y@m-u2MXRp0Yd;oSTBHCK +z-}9RJbPk?|;I9=Tdy8bum{#`!{d=aw +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 +zw+DT(-%pV$Xq=nsb8eQR +zy2%ooS1Z61C4S%4%vdVfuu!ezM3x-3HvGZpz~x`!JqTTN(12E@fJEYdnME-D(E8yE +zTq)So5SC-T>cwkuO0|GrSD#%Fi*ko2GNb*3vowyV9Z;U{#`{Z>&mZ~KG9FSArd2w9 +zO@&kal6nm$>O$SL)+vS9UqX_1o}TlH-)>^~jz=-y!2ESU90ZDd$=eF`Mb@juG`fuNo|C54#4S(X)^z}5NcCMtWzfBRK*9n*bGa+Q-75|7lG +zsP`wflq5eijikRhsVZunv9qn|!!dL@yfnYs?+LRXfujRi_5O$_ZGqIs`+WNK#qiD< +zxj+ksoz~MMpyE24FQV&Jzejq7f2jjGehPvdn{PNYN6zHJ{gZa>9OsL +z*EwKwS>Lj@Dv|`Y(I5@9pC@Pav-I-zX4+T#nNPgrpJ`Mzy&m_lYUzzXJtA6FWg8V+ +zlb&X>F=V0rrp|WbgcqPXAmHtMjkYZPrKks#;@^aj*!!6Kt6r2D`b@wslu5^kT;Fmj +zY-#p-Sk;L&(jahuS>Xd?Uwy~G8Ba+s^VhWBB6V6yVyUG4IF#W1#(-VouEl8I5L-w^ +z1@er^*q)#~;^<&k<8Skn(lOby>w^TnXb;HPNLdfmC#`_%;Slx6(gQ$y0bJ3vWZwyS +z=+KXSIXQHDXZp6}`}@xM#M_5F341RX_^tAB@SMXn)gy#QTb`(Y|MbygEn!?Y2L_(I +zB8(Spexd{_?{nhY0z5@J$_2y_%;?TTpCcK~wR#EqZ~3mHw`{MsM@@J#)8OKiYI4kq +z%NsI*tlmqvFGB1cWM!`+{z!EdCxIZKpm5zQpUwy`s<0H8X3eK;9CA6E#>;Hqy`a-a +zVwB6c?`md5qQP-ton+~wy0M%TzfbvA5P!_by=Qgov8{Cov%ZP_(Q@%v>iUGosBiuO +z5sMirx?;#L9ODXsgT#Oz;2$8fnBZ%`gDf&2MLGgH6x*zxU88tEBTzXPp`3pO-T$h# +zaV8kZf$9MixPKp12N-x}^riJ%b!KtgGH-aDmwgnTAY>avs9r!xW59ORM_8C&Ikk+| +zBI!EIH3}#fyP=G%JWC6>GPuORqBH>x3yLuyH}@fH1fRPf3;lnUw~+5Pu+???}sj8_UCAYQ}B%TX!>Ci +zr-aTSm)zA>p_TeQa^=M?NyHy#D7%^y)U5r3SB0Ncj2ZXhrv|Dju#$>a49cc$K%a<4 +z`~~Mb0M|1|245y|6{&J`UbXP(^EIP!$tzfy=s)lq6k9sY#Tvsc$_Et1eAY@d53p_Q +z)A`ZC_!DAo`9q~ZztSKxT)2+kb;P5`;+fVgq~HKP1&7hiO0(2e_^<;~TY#U!*}z10 +zOBMDXz~1F>(Qf-bD)9_2VKq^yeC%bq1FSuy1n9kw@)@>PK*M|Q;xh4r+0Y9$`}zE2 +zUaJ7)qu9&iD@2wgSaFXeDy +z!0TH9gmNf+#Zt*k&}jCXx6x202O}ddnV8EKdD#!Opp&i`o(;YEa8%r__2G*Ccg8QF +zKfP=GzT%NQ=bfQGbpyEt1F(0hP(rPXspn*e_^ +z01{n*!mtXc5wwXgoXeSwN$T@33QTc^^+^}T?u}AkK6Lrn1(eQgR{H{Mgd@Ec_{(n# +zU?x1rO2E*!ztek~?z2^UqJGFzKV}(Q0u>-g}5d~_zYDMTri9Yd*}mzRX8y& +znxpdBxNEKHV4I8$op&^~y(EXHb_wZfyb0N~f@Lt-UCLL8o-Z3lMW8ReZ~G$&D`SbZVJ`p6Ap1ib`Ju +z+>0&n-u}5tw<4G@<%GEq06aus#Xa+H5gFa)uas{$Yy+l71Sl(1nh^n7MMkqt>|c}3 +zj@InZAsORttDz3d_9mmj7xQ-!+};J32ROKn9E5%YgrT7a5@LC2w&Q{TItNAwd)9!$ +z00>ddT`;Z1mTWt8gR&h2?o*H5C{k0pJj}`N79di0DtNM|om~1!ETMJ*GKjcC_*3{E +zr{eL5cwCTX*sb7aFrH3f@s*~A>w);$ +z0SE&ETqO4w!*@mGs25ojE?>88hEL(7%az)dIvT#DCi)Gwv#@jkSg2Nw-mDz9#FDtb +z7PcsC$C)9D*AYw(5OdvAL-`7$BTT^C77Gr?TVv_4yPG9Ld~06t^>T<$(HG+&JsYK5 +za||`!s}Uu)^bgds)hH01jLdUqlopR|P(3U<*D!OR0dD685(>7ri0oG!=5gKFp +zkz$2k0Ma@kc{rHoe08twz5=A;nFK2Zhg8Gs{%R==ss+ieotm@f#s|bdZYF`wm1}!O +zT`?v$0-BXdD{qw%2QcN-8uyEKx8jdZ1O2`A_=}yqkbh&j0z{>@A3ZyBzj>}HtKc0VSO|WBfb$)Cid@y7F +zN!Mx|`k~XgFI>Wp{WCeEwMIn +z1KFiILcS{v^cM~n)e+bNweui00;{gtC%-DcN0iKZw}`*zk46ebjy`x&E0gUhWH8Qa +z122+(pGxxryVvH|b8p7CngMxgTGyUdX2HX;DG@L|p!0M~x#L5Ub0q&0%4S|J`!ZE& +zBGTyeThA#)Que<)aoJ8RuQh5X+UAoNskeyHmKLWOP|=z<6{mn5n@Y4uZ5+rKIaZde +zaZYSc>f2o|s!Q0zmMi}PRPAS>^%Va7XRm!!@ff*lI_gwBEIxS3bUBR4amR!3YNOcK +zlLAeT6d65y;H8>t_AkwUR~Kk1pECs7wPdRPC1bT*JF3jsLPin~;o7sUr`K=|>U67l +zv$^gO-}e|qF)nL1v&ZkGmBv@UD=+s>;WD41d=~k8DY-X5>PHwFr5NF%&Tl+!k{VrRfRsVZM`nsy9|rZBJfTW@ctvyr;ycXk|4w<}U@r +zmp@1tJRG9F>}N95GIj|B_%OUcGx$*!?g354^xAN`=c~z27kz+*>i5pawegq<*Vxe3 +zFnkPSYN$-UWf>e&H*kg~i2j}_rmFAsGud@Yc-IPE#*ch|B#ml3Ws`Mc(HF8;xSm4w +zHmTYXK8$%$uffUV%lPmi-Z#6IZ6RusQ=d>-Fc>l^y_N>p<2Yz6`oUL{q +z=tUaoUetM!CtsIjUsxFe&JJ9(eo*HEIu=ER*H3=Ybl|W1Z-OcFRjGGLO8XE-+k=jW +z5=6j)Z4)7)_ZP(Q6^r;EZPvStpM)B-AYy*}N^i7DV3i^3tbGZMr?nBdI|C+@5CxfO +zi8jr!61q%=@6wQX^v+FvO!&ghptCn;XuATAd^fg3Yg&8 +zVx;*k6wSortvUAFR@S%Kg+s?0hG?j8sTa-A?s`a80gh)c<>oNKD&@N%H`EP#5(OQR +z^ewaoLi;SoTU9fe2SFGFw-pf1A0kQ=D_ +zd7OH}DKB-qo|h534GujjT@_?@UkU*AyTtH~X7-pD%iD{$6?H$BvJrj=f1UbsuNo>d +z<$crdr^MX_s9)Wh0^IH{$gU5$@LjP4!RD_^V4aswVg3VO=n{CUbpm+_^|pk$=2%6c +zRQ72^4GQTj6gj-Y!<>u7N|%UdXCz;Jy&HR^GUz(hWRiLMEV>NY>L34YGX*sx0s^Kp +ze@pDB3d;BjQ*mA*$7l0r!@np1AdWg5c~qh6@h}arf$jmDO%kRcb4<45A6pDk#~?o| +z2O=yMiLM_9NhHQE!DGCS2UmwBVB-_8p{g1MIPkw>+rrg~jvR?exET(&$qMNuz&*(x +zyd)f8V=>J~{V5HXyYe{|qYw4!>tdi@3eVv3o&U@) +zxSja@jv!taNLvA{3K!b}a!BBzE|890RVSPAs)vmcTiOf7YJ`O7X543Pdnv<`aK-#p +z_HOFbqzfqV;bg>~Lhwl}@qy(CmoeUPvR}nho8j+kFhHR&-?8V~A@LQbaxW +zuc8kVMihO)XXP{++q%O4Rm$MGH2nP#b~dj5wi`n?9<=pWOsT%L@#yf1jYGTVoKFIzCY^cGs-ssEZ@ +zF@S_j79!T8&VR%&895`OHn{(0$s1SI9Cnj* +zu@yG2Irb~VKPMh^4IOZzGj7Q>PZtFg$JH#>uZ?Uu-q9k%EkuBOpTSD`|EJ#1O5{Z3 +zOytVv#QB8tHS+X8$A6v}L0mE{TR{Q=sQV*190&nmmGRK-f*6Zix@1uH2bhozd@HOn +z8Tw=*@s^WMD|$HGjp{4R!H|;vZ085D+|_nD$Z{zqD9afR@f&0acQ|cF`PH{D)hq>< +zB5h*p9-EOO#~R`unc|UmTBj^OQ+(|CDBqNN=6Uu*lS9|5mzyB|pH0e&1@p0Uizy78y&68i%d_`B3MjD`<0+Aj5kK>et?HF0)cg329GjWx9JR4jdKNg>g&to@RUkHyoojf +z`hX~GItcCw%f~pafc`SH3)E{lZouOI>Li?dPl5 +z_oEe)!<#N4L7Nm)b^;AUM?k5e{-9qR +z&IQ$Q20NQv$Y9PPVG +z+n7aK3vC@FhBc_ymxgBtJXN@zrby8f01({rx_%GqS;Xo9Og;Y;Vu4)qYp(QXWCU+X +zk9FNAJ7MQsK}rMg2wD=+IEH8WFSNwQo|E19fM)wl@oSWU1M8PFNY@ZnG7Z9(*VoYR +z6?wg?e-6nPHtVpzhhE^6eT{S;xIY=-*sRqZ8}K?}UvVYRSHNt{BzhkqM=asOf2_@g3ldgKTEDH!|M!8hMy$8Bk7tMW?s_a7u2zi!}t@AKn! +z;u5*u>0X=E>FC>F8WtNVtx}Uu;;3Z&przlijL>Fe=zwB%BPKnZ1Yq;tzDItneuM2s8Cyw_CH-jPzSG_>5%-9HN=v7|20`;M?J7q;$f<+@Hp +z&!259eVTnM&{SLQ$pg>;#7t+LkcA4&4AEqlX_$*Pm=w@ +z@Q#GGA@Y060bRKvYy-KU1%DfNV<|tj+ZNS|Ym^k3X5cT*P~uNMj69sV2nr8yzm|ef +z={S5brFrGO0sc(HgsSMtgcHyTOk=*pAFC9J7r$vj2>r$tbxEcHjYzVv)Wl4hjM<=U +zkLk)dwp31GpIUR$U?WRZl}O;KlTh+$NpiXeho)%FVu9;8fMAI00F}ANa^cKCtzTk~ +zG5q_Jeg;nG?13MsYcW5J<%-Ootau;T74jjvgY=$k4;{J*@|Pc0QQP@r5wSr-YCSU% +zPc!)oEhpwpMZ4TeZL{iRc%NZ0k$bWGXpV|JB!Dq5l=0@tgyjdXbddpxr5F`Ux!5fs +z%gMqlG;&kbY-8oF2uY5!(^ISpvgEz7ZT)ibTmFw5mhA^mi(Yvmj1&dvX7?HpMAFcQ +z$P@*U0t*?~7%5b$hf=8rmtUqHZ@L}uJfld&0M}UPd7LN#u#Gx3{F2kaLT_?WL)h;G +zY8p_TBj`*K1ZGiDf1=X@YyaHZFbAoQ +zjh^>0rp}gsJ)Qka4oO3^79#cDPJC4li@hH)w3C1Rz$FB<=ISM6g}{a{pE7W?nTK((_ma-&ED%6y@m^*`xK5moq?6jSsU>L6G*`?wlmxTmL~?tgRV? +zw4`IC6Qnb;%D~=a0Nah$e9mu1byizW8D28u3=g6L+^~?2fmwPp48{Sp#qrnWIR2U* +z{NG_OK=R`70Qch{50ecnm;>&O)#j=u8zA*TZ +z+k%Zzd@Q9KrRBP)klDuNc2Nq20#I}?wv9EDv#QZ#87WuOItd|JP8YZxiR3xMB#01b +z4%NyK~z2^?BWYp +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 +zGZ2KR_<45GUK7hJeAE$JGbnbe +zmUR_h=N;;Go;0|~f9kR9Hn80im()BYU(D^1Yki@X?2#+^hg-yICzVHaj|O&^Lx;jf +zD3@aaNH?|L?5SS^3?d>Zhs;(tS928)3$pH4oLa$)bRxc@ +zV~eLjJFD|ZY%c+=Rkz>GXVAR~PErS2So1EQc+7tc6;VXM4YbG +zqoEy$Fs?+Ku_oFl1vam@Kb +z9EcNUTX;xLVNa@~{UnQnr!1dEW{Agp^^bO25HJEcC`-;^M4eCnr67}m +zYUAcv@_Kr?*+upE2=S5qKK#+h+WR8@U#zRTMO@lIWQN%%peteR@mC+C8;6x#~LTElzz!Xum0IC-VATbF#k4NfI@mfZh7)h4_7Tw## +z6XU|Z11pB%=X@}vPseA(VE%-%yl +zC}kj!CXgX`Wq1vfb%5gat3L*KA!G;vwTp-%4vhM916cow5a6y5O8T*iFKXda{U0r} +zHRi#9y9-V74k!^G!S2Eq)sYh+QrU4aL6nTvsQLv_0zZRo-?d++`6hVic@FLq!1o%oxPfIPIktgVemRwW@ +z)+ZhFNChhV3+5`0?g?TPCmj&-MQUh-`muU`Cne&)lxikM$*`;M|42H*E`?cYp=9WP +z$#AlGJY8bkjVM2+n??R8Im_@Bn#_k%tmNw6c)! +zn&OxrA@7yY#ujYW`262JG6_;{dyei|f3@VMAwoNcgev>s$ib=&0{s2{2_)szHmo;< +zK%8Z3WyR}hwD{^<=ZbzsR&K0b{nj?5??n7t +z0!<(?qUpSmZU1 +zlZ{x{nJp&{CJ`-OC(4`y_mCbNgMxpx8QCA@kpNZ^2MwGLVlocELwuNw;{q%XRN2T| +zWHTC*IK)r@BAS91pGF8^uN|l5ftfKN?1lr6Krnp#T>$y0|8QblHZ +z(csR#>Vto_F``=TSwXqUX6yNy1A=n%-y4ryGVC+^IXJpgo;5Eze9zW~WB;wo`&t`d +z97O8Kt@VqV{h)MDE)_7TQMDu7ikmk*RBHQs5qw!5K;o$ +zZzF`@FyLeahGof6Z>JH`H=TlDV2ZL`93^}NVq82*crqEjl778|mB}R3Kna3lIVco} +zHNKYQ>EiNyl2`A3d@RW=Fi|uOMsaw~;C$PErl3lX8SHzckw)+O +z8CC*dLew~_ilS|wa~p9#w$G5a1X3{}C=4H$V`gADeIJJ`c?YCjD8p}Q7z(QR8d>0a +z8^-bFLUV_qzMHg8j4Gc3%{=|8cOMk3yMU!SA}FU*a`{ypgKR5iF5@<1WIHDB)I)8v +zVeWPiAe4%22PUYidd9^eJm1!GmU~Ay^^c-DD3TaplipSpVu#KKlP#4xmilq@zKyI1LPh=&R~9EAr_UH +z_JoW;TwMGtX!-}(0%Uxz1>n(!y*N_pCLNScbB|8C*BV;GueLu0D*_OHBx*U<4wG%} +zox1=T0}Yp3ZuSUB=ZWzjoBgQn4)Kzbl{JCuZ;tIF#VtgmCla{+*)}M{1<-Z=9(mdN_u`Rdv=ZhgfDQ)thK!xMxgmbmP?K +zTY=-y%ORmg46J-7Sjw-Qw&b~slgx}~G?{9c(Jj5GBfO&0Z}ezm*u4usEZNm(wx9xq +zu!-s)K&`tMR6D;iCdf)$QcEbGfBLNO=@B}d-npjl39|{8JxpxyvI{5T*%Li@s-`vT`cE+^*}^*wR2Pua>_r*4rR$g +zF}G{UGk;iT>pmi{fqrG_nS6zAX`9?8mD?;fY7O$D4AFvjII6?vEnqmTgWDTJ8rQie +zxnzmZz19Ub#BeA+f)|l2h&SQIH)Z5vQeS@wIjS8H$agAsMw@xr#!$pK2IR$P@+yT< +z<1^vQfH3ul${D2c=m%|cz{!s#1(=xG-vyD@ElEnTw1hY-38Hqt^lmz?feX-m$O&iO +z&hHo3K_qyC4*~F@8=eJj{H}Xrh`cnx?zp9U)loShUiLuvMuR~V&HBZi`7r+8`Gs$B +zwkL7HF7)voR}TGfK8w&?Z)ONG_i}hVXk)!jE$(3dd_JS;h)urSKIj`zD^k;Ngnt-9?cC39`RZy8OD6Kvn#ai?+YHs7u& +zEh+JQ{{jFgUYVRZ+es-`YkDrDsl_}(RgYHxZFj?5#qdMJ*W*-chI;x+XvO-PNYa5= +zysKE}AxJK8A&nl=UQ?|E@*Wc|xTD^W +z!{{x;GeUq3m};Q+z+?gwu{5eL|GmDUK~dLk7rn9Rrurr_n$SUR_A9_$S%wWp@67@t +zjg4i`89hq*u>mwHmJW3cZXieOd${xr%vtNqw`!+UeX#IOEaB3i}_OVOk)UCN@K{O2* +z<7;uI5=3D(cc7SE$b^=T3@4z;5Zz+0mB2KV4qz;@cqBjsZ2&Ns2Nx8fkmHS{ElAVa +z^?LI%KzPt{Ad-(F9HZ%R(qtTFg;qt~@ZD8&>85%e%09^$9h72lN`?fnD=~K-xq)yJlZraioP^-aIlAk4<5z_l+PtY{&NU74#72#PR)Fasv7y3v__z +z^xdxM6mK?)_u +z0W;lEx|@18bOzD?O6f|xM&vxt@F!d3mZrX&!vnl|x12F)BS^E7Y>s-Qe;M~*KRrIr +z@g%A&4eIFDOIUz2e(OYE>P29stc560?`W2 +zu>KI85Fed;7#E$s`WeE)KY>{p+PNUr2563>VPSLt3&rkB71iY>)rRTXWO5QYNnk)_ +z(nk1EUi9-lK^qQ_zW&ZW!X(8avY>dD@ZU09VPaA8-TN)fH`%+E~XgeD~ +zYTBbaYZZKxnN0_~cPHL(T+0vs`hl~3Cz`t^Ho0pV2H}RI*~!7MfzMzagwQsKDn_iA +z2!T?#3Ir+m-=deG|9&f`OqGg;bCD$;v4YGkp5{rmi-WG_^rSs! +zJ97l6#-UtgemU +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 +z3?x*Ox!O(0?iMj?Em7`ntU +z$r`Qo{Wc|GVHO3khw|0Q_asxHso+?m3w1wClS{$CMWDA*$Xt$2Ej>cf0c){#Im2U# +zWWR8V2E|oX2}q9w07eIs(}+n$g@MEwF}83U^#qis+2!oqXzQ*8@d8Y#lKi%-$>lzN +z5nH!KAj?Wa!#PW-V(=ptSAZ%xDCmHz%erWEbs=ahnyXW_pe_+FO=*#jQtKKHUjl&)Hd$t4;6E{&Ct)i>r$Nh4*6;$xG$~{7FYW%oBOL_=bgxPiX1M +z*9+;wIsH*e+7V9Ik@7QFYCz%xngkal#p^;cBtthiwu4VDo4I^Oa#8| +z72`!q12jViiXH@L#z~1-$DhN{1Ci27_RFO6MG+MTA4O9 +ztLa69X*x`sJPp|ZRZFQ7wd=uV=exD^vNVhRr|bG9lW&Y$kdd`Zp?UNhV+8|UI?K+d +zwMU|_Wc5>g>=XbohNjGZ=6W|{^G02*`u*uA{l3#Zsx=;=>AkEU`CIg7SoOOfxr*C) +zzr0=-1=A9$QSCo!K9SC-v-~yD^Az;F;46YDaEIuz2AD7oK?zB1{Yj5?E;lRg6dMDR +zj{}Nu>zatJG)Rc0Jg~EFy>hta!Djt?l{xjc=roBdC)}@WKB7r0MD0c^Maq;Lc@nR~ +zn}XCDwIhb#?)SgU+e)DHD{twRH$!mI8M=A*csVS~Z5>rCz{a>bzrKtTv-k +zrM>m6d+yuJG|~i{-(^BG7LXzq&8=a9dj*4pFR3+%4Db;vlTQ5;2?P*g@EGw&!L%^JLIFx)ADnjt$;L?u)CHJfXGB#AH6eoqLr +zNkww}8@1f34s4@MqHWIVg5&L~4M(PX7aS`2&QLS)Um~&$xVxLb_oDLYV|6sT3d|l* +zd|jZC9zBd-8V5q!-MI=#E{i%W2H{**fV<-Xz|aT^^I#ks6$TSw(SR7SVp7Q)Oc!Ab +zeRRRUCeQ}Ph1i?C`2x$%&@z?{H4SC`<5zWNKFC-9>@jUq>d`J>ukNc!xPDK8rmlz7 +zsuEH>iuK^F@nGl2)$$!1sBtW2MR_}$!@D0kC_6#c!Y3$KMx{`|w`BY1ivCymtQqnq +zMuz`bQ>czH;y-@QW5b%-+MB3U>P))$q +z34;g__p}FA_7L44v);0(D1LaiBs2@Haw-dKo@8LacK{p7JsWF@k1q3eSA8& +zAiV!gABCN%(fHSlp4CE=%*ze>Bk&U65wd>+Qu(kTr?63B@wQups#->qMmL<(sCMg6 +zF@a}=udbQEBs|1WP~C;uWu`9f$7Su9m@dX?-6kU-S +zCe$8Y8T2a_dyfqyJPi%D-GYkwapouLtKQ+seZFwQZz8KH(zwplWhB+xoc7RqNK<>m +zKTYdKI(M$}x{4pTfcm%JO&`!M_-PFOWQLn7@wLK&oJiy11&v<7$l>KSQ3RW0y3TJl +zeRp&)G~(tf*Si1ReBOcv0|ErE*bv%xA;Jx~I&|HJ49K-D?~Rw32G;4 +z-uJZsBlT7Aw7I?oH+~boy#8iGHo0UxqsOpyeyyp{(d^m+e|cGVEey)kY^54>KSuWx +ziao6Ya`vy=7>CS^s2g7$6uEczsO(gB6Mla3dPAk615L56q~@KJo#g2Q#0K2NyT|c7 +z0eYiJ9uoP^r +zG+Ifj+>68GLruPwNaH;WG=JTlIC2w=R&zm?d{+PXHGAFmO&+TcgA<=+6`wu4cm3s; +zkUK61hcwKizgLEGTs%%>8MznsYvwSRFDOBQ1E*hIrNbd@ULIkiBt~y!TZvyXJd*TYVyyg +zxa~Md=gF2{chK0sYu7&o&XE4DV6&{*N|Jf{o|>nH7MB6xYp2M))9*aY)y2w3zjqrw +zECR%u16@k6c6MI&Y^HXOuX_)Ol&MkntE3i8D0H(htCB~nO3kmWML3F{(9Yt5h#JX( +zdxdh}{mbDF`8%5dwcv@MMc#!P^C?VeVF*QF7v1D^zBOI%2dAYTjfK9^Q|_)kRr8T&x`INEP5R$- +zwfCv3Rd8azaG&Wr#d>qwx%0(`dA#d$SC<-~Q-t4^HofZ4GG|vc4^Nw4AwQ9`y>cz+ +zvMaZO+-TB~8FE`)rDS!{*hv_<1O(^60*lDG0p-)_;I_tgwe8^+NOP6_W=#7J=1G#$ +zOml%taIn!GW5I9vC8yAE3xZWs+LYU4lV`*3CqbL|xL!&f@d3p@g7ua03ot7>mEQAo +za<}w{2UL-Mp9VFK>5?*A4n&cZ>shm25h=6E?^zoxQ)`+TV?MLA0*zofcF$iM>L2eD +zEK*Z9(xxKQ-;clY*ir8_XiTSI4iHmZ?1Se%eA##CXWto!hQQKFM|}m^Zv^=mQ;$T6 +zk-ukM8Mxe-bPFb;Zmr6dnezse$I)&MTt3NK=Ve$}S#I>H2@Lfmluz%E{w2W7o`2Yo +zlsr_~KyknAGh%L$-h02h+0GEC9n0f{wjE=a6D5bX7SlST)kA)rJpftqF9X$E^^B%j +zfATxHi?kW5u6X7Z>x#}pR(FnNjpf)Li5$0O(xYYJQtlkqAcN0gYX@_qm3LImEkCQl +zkQ)BPbMpcT2>!dkF-ky?Q)kqQB{$yWMXNTk*%1#dtO>=71RQZ{=Gr(b67KV$AYHC&NBb +zmRGN-;C-Xa;V#UTx}os;&bL|Jf^Wvqe%Ezr^tR%s03Y%u@i<~M6CDcesLe(TW|;yp +z2lbKxLSWrW!CnABl_~1Oq6SV;1uF}!qrbZV)k|>g_c$8P3)#}bzoiPYR+|`1g+tgi +z5yC26f12)if??eON0HIrQ&L@Ek|!w031rksmxF_F)gJut@yfciNSRxt<$!9$_3CCE +z5yg}PlkY!JtcQ3{O!|_wJhJ>A{7RL5Ma5j%?@O>vxXlNOkFp=D<3DQp6$KNK-_bpv +z>Zw*k2FoErG$Pmqz{&p(Am|m;Vo`Hd#`&!;SY7fIrm;lyApj}JV2KfoBElgm#67^L +z2rYsI7lIj*6sN;J^76nQiVDpp!yl3TrtwC-z{OCq&pfac%muYtNhnzBKgbuI +z!5>6YjSnmDNP2(3s0cIt<264E93`eRyk!7HcLDSQJ_eeydDoIU=bpWns!wRb>e0pI +zr%%;hhAHfUyvOPB8OckkH&P7bB6px%8%G4-cE!HL~r3ijl}&%-;Y-|&gTf`HGOUUfB$For#SwrI3UZqt&=CZ`m$Cr4Ct$CS!CQvD0m=f9 +zORB}5!Hckd6!W!u!bF!!cG5cX>)XaL$t@O2rJlyId$R+@#E-J)i;5pr(dDQdn%O;~ +zg}Pl2!zITu6kaMp!d}96x3&dF-wUt@I=-@h*Cry5ZRrP?9)ACGt$9;W<^@7j(DC|8 +z0=4S=JGB6+pql*~^MKp)-?~50vVtlOU}hDxR%l)Vwzr1{+}%%#Sl^-!WJ?Y`K63Hg +z{`J5iC56Vw^k((*CR#~F22OB6&W^yW-E^tAktxrzy4r65uuJmWiZllifUUqwXyEyF +z+Z&ByU9UqEdId02gb4szU)NI|fRx?FCmxo%XDkoP*$u8VRavjgoqd-B=TnJXhEqHI +z4VzB4%<-e?qmvy2Kmy|I@ZZ*^fTiInd+Wy95etJJBdAdwD)O2`ajXdxzW>2ihCyc* +zFjZF|^cqx!kJc}0oTDCPzM)pH+AEb)h#K(L +z)e%%1zO$T9CPssXUzF_OB(Tn(0X+{`=sK~%Qm$~u#a_$TW-EwKlc2AFYdNgLKtg%E +z&xY1K^GZUC?#s+9)ujwpiX#{R?7-}Ito$knWFR06Jg3wDX~;kF02bLFGLYap(LkXP +zL|l4|!)LRzHOB)djo-s>1R>JA(nR +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 +zP^=eI%b}~9*er +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 +zY2wN1t8%mM{FypOBd`=RyhZ{fgEp8mV> +z_cVi6sn!plqKL$)be{dPHOTaNf^UohGKTw=iwOvT{AVsM#>r71l)(Ri?h +z4mLzKLYfy7TZT{3Uh?*b8drcIQ<^X{zqDb0+Fx_rcA^-nY%%Fa`s8M?oL1s31Ch4^ +z9ti>;`fdJ+V(TS02)!FJ%F5*Pzdo!k2rT4cx0{s2a+7d5S;c$4+mj6j7d1<*L_?j*Cux~xiPv`Srl?A +zR>u$aQzLUqR2IX!)IAyGo~=(_yJCcUHB5+e<4en#`&$oWEQ9Y0A=Ayqox$3^6M8-Q +zX7cu^6BdF*SPA+a9_E^>!F2--~w=wM8IJPos;pNDNs@n%N_nR +zmD2q1j+qlTvX&X&KL~~M8$=44H1g|2;o)GRC1~6?!V3uo5jYR7+YHu)=<1Z=g*SPt +zfv;+C;0x4a?47zJlg-02E0Lc+JIy0=hlqK;?T+gSTDIduu{$`tKn1?hcds_*Zye!b +z^{0=hT-j)&I{;x6<*GE~ZSqV^>}EyfA>J&VP#9Dy(&OFRUTMCx`#oPEC5TzZYjVCV +z*q5_a=8a!XfTAny{zF%q4HiNLJbXMmBe1$WJiF)r^TCD(bB&YOxP7n$ymfW=2CxPq +z9?_%=)a&xVKvnz3dC$@or?GD-@pyLX*4hl5l)I_V@&uZ-_RdM&Dyh{S+;@@x0Am{%*sk0tD2q^o4`aXb} +zP8WNeKJYu$>cv*YC>v+YurvWsYjw33^zUupKz!@#wK09eLG=&4UP_E_v%6IK@upng-d7N>>gC*dK +z$JM}^206I*I0IHWd|X!90|cqlT<-(-`;5r-TLR95jj{^xB(>0?SP#OU-^F5tTsX6f +z-(|UzCoRY<3D=cL&Tt_5vsQV5mFjurBGs$wu%D1 +znW`CCMh)EhvT4PgS2Z00GV~JeAxiMe{0I@}Z_*}eXft7>H#V3?W?vB+g_B@bb#)nh +zlrQjBzypH_IA6A%$5t1wlns15$AeMFNqhn8U)R79RR$k=I3b(F&y=ud!ZKrV(YE`> +zm4YM1QgYS45r8>NA|@Hp!vTK*XAk5JFO6%vA&B>@8iU=pXA2>;N2P9e=PBt#^3egd +z1Yl=J3e;xexwODZPh40@n;BQVZ(d$e@oM@UA;`1R%}DcP=-qSVf%f5Jn$4G6{afpN +zxjAZb)*)gHj6*rPij3zUPod>AfpuT?HdUHR!a=HlHEQAvWKKONhvGw0U^)k6C^`{HN9iS2W7JN%V|o#{~ecc3?-$1@U_7mtQu8Kx&c2&TA20g +z66{AGgEN~jmo#V87DX@os5g;Mb~JaLJopRIMoh$+-@9RxUB5D|m(SNHM?%S2{&0{1 +z9K+T3=A2rQ5;J3aoIlra;=$(}F{w>?VZEOg6zj=fVc5vv1)e$^SQbJa4$OfGjwOT8 +z4=7T&`1(v4+U3=Yo2QVmx@g+}76 +zBFtjX`ZNdMkEWNZb9K(}m*}N!zn*hF)SWvvNl8h?iu7iLIfwnWJ{z(lYDESH23CfK +zhQ*4f9~cHQfAr@`P&!$hv%;u4By)Di74|ZWF_9?F85q7~8<_4lV!1Cn48Jy@aDtTHda0M_pXOO?ZK_o{MF^r +zBCS(R5+Vx5!ZPh!(wdB_NOD&fN%z&n<)jBnrSG4PuuF{-Jc8xq-oU1Eo2??Wa=M3j +zg?Jk(o}TYP`XG(h$iMOV)qNao9(&m`#i=k@qFF6aSRC*>>@z=7?*Q~s$4i8yn{lBx +zt-2={yCQwjk}%ajOx-njI&5lDbPF70Xv2brbhwUYih%i_f>8xG*Nw|yly3`?$-PLS +zy!ol*wT`ZA7Au3P_LnuILGxC5$vEu&nNM74Fdk12l-k%Xv%vo9$FaPq;;#8q+J6f} +z?%G|8QTr!Wk_LmbNL)}R=E{vgshpP?yb>p}0_Ry03MCE}FF9-#T#Jl4X5K33ty?z6R+>D85Jqr +zrC+c{j#*FGBQ;(1XgrEHQGB^brf)2U&1rfzGS1UiP4qXP#>?X@w}+twEPm?~=b9cY +zmExlwFC-LD0UynVpr~R?tyP51FM}dBw57Pc_GKvBrT#6Ps#K%!j=Tp#_2-7lvi-Q~ +zPmr3ggK5ZHafD(FVvID`j*#TI*|rPMV +zvFToiC5~nANkxF?O*xub@jp3mKO;YL`j^i?mn6JV6UqP~B@4A8?wbM-G{9K**!BvT +z_zZD=1Mdd%=w}Eh#YeRB*zsbs9>vXv=VxLC`R!UBN_gF=oCe^J0A+6or~^a@_uJ2iH`Mfl9Z@P}VxbanXt +zKgS&IYi|o28vFfWYHtCDV|F(N4XSa=|kS3ygFk;Yy0)@sB(=@-m0)5+J27dgo4XT2-y%-@f$ +zvz(8t@6ABFm|7P0-+UY-;!_DY4&mVAvlrSNcmZ1IJ)VH +z>pTj#nRhraTDVEWsph`o=l2yt#E^Amy!@ZNn8z3Q+-UW`ENAymW(PH?FyW&>MmqTC +z;^Q?wiVwJcqdnz+zl7z;UnHpp-AvC@eb-Ei6`8!0ad8;($|lDjOryqp)yJ}h>pak5 +zqUBAO9G+I}N$ax)o83Z!@f(s-Ai4u#3+U)i5z}l0qo^S|Ke#=lSCnHTGSs4~5^ln7 +zx-_4;pL4?Y@IFw=7lFM0-|YEcA30mAMI$RAMGagxo+$Rl>d?b-0G3AsW5JJwyW5dx +z9hW5*Db{%|l^)NP?l`RUr&$3a=WZOKCY=ErilCh|uy_%4$-jk!XVe%dY+xcZFk$?3 +zIK$9rz@097+e}MHK{rN(Vo&uTBSNPydFt7~JrR($ZK6BpmMZlm9$U0G>cz8(Ovc2}O;{&*X^OK}r5Kfr20h6ZBVo +zy*tGNj`R5N?+K$l@Sl${PiOO0NWdz#4O4TFm4vA*?CM>h;N-}E=jCP`p}QUZ;#JDm +z;5-}xlA?`FKDx9iW>?(nXI_|3;ev=cwNno{_NZ{B5_+`Ov+Xepw{XZuPA_!>~uz8&(9rZd`y`sRNl^gM$Soo7Hu;8?u_ +zsBk$zDmh?#sh>wZ*O{k@eG~HzC3=o;ts;r~5rTc6aMI_b9yAUc1q}~9jMMG}e3$u7 +zk2d_P(q24%+V +zP%y@F3EUCYX4dL=fF>h5EYHP{yjPa{561wbA;@;6j3vx_FIUKCvW=oG#w=&cet!K> +z|94|BQ +z{Da-MDBg|UJ^{tYboGy>goM-6MIBd+7zERQ+UG9*~r!X~S5IhK3V3Mf7;_-jj` +zy;sKHqAjYd+#a-6>GI0^Wj-#N(We$#Cr)u(?j +z1CujeDFV#o=AVUSgS;D;6(yYhuoYPt&IfihkXoe}OG_$G*oVC_jl4kT*Y0pW1K!afuA`iQ-0q^5b{ZNTr@Ac-_c*&z+V2pR-k# +z!RGe4l-y)D4Pz6Oo#G=PE7($tG=fNu{ +z=!aL>RGJImm(f03mUIcA`1?Ryy=TcJ1@5nqmc%%N7d@cIV* +zu`bEJOhH2kMlAs#v3V*Xo8JtUoUFQbI}X9M_?&1>VeeZ4MSM>rUFOoO +z(%e)W6$13wJ4*vYLu<`R)DU5Wx1sUuF^kqgHJRs +zsY-Q!fa@qgur$lBRw>Im6`>*IC|WX<-k43b|fU_+2eZIS=2l6-y{9+m8aZiJ&GO>^|P>o;!)2 +zBKMoWM8?T%#rt^n-3Zt>-I*`1?`I594h2NC1rwRXFv|cRCN!Fua7S_S>Vh*e`MQZs +zfok-2++}w^+Pq*4Mfp4@BCvROn@`CwU{gz)A7fR{s`kQEf<{Ef`bF>AeZ+h&MOZ`v +zFo8}3b8VB&LHQR>OuIuV*p@IZpGqFaqD-^K$6OT0p?1Vy?D-M63Ka-}+zh$?eQ##q +zY>UM=H}IV|hT!P|l0p{7N(0_%?@D-zCV|EZXGVp*w2)e7A^flX@zLM4S_x#6&*|}g +z{kP`8idK_0#x1i_IzbEhXAjEoDhXVD&QJGGHG`*38od6lK6jP}H^}jhJ*tvXc47`}kc16y^Ku~`3b!lsON2);}?nlO( +zKhbV$69WLOS>#|s8x)kM!?%lja`Y9;Y&69xT7^TxG>J3Hg)DOxuOb#uK}2M_>F}sz +z(A)A!P13GW>yjDG^767%#z|U?8wFCcr!?a2+1Xc+XUe)fAR&o}s_cSaV7BEK{>u=` +z7$Vf67fE8;ZGK)iMehIe5*Z&x1zzCW8BRI%pdw!>?DAA--oy3D>bK#Umnaiyn}5HZ +zIVqVtn;}URv5z0fmYGpw0I}T9M=!z{UBb=nQM56vJpT-fyCkBA1(Un0HJzIN-jVuM +zm;jEz;uXYuR8Y9`?Q;2Wj-Dh^dhx-A>vt_eZ%SB40LMxYV%vg`+xe|k_ANk`KcjHp +zNP&C5es?qu(i)& +zpUwHnknC{VCw`tPqhljHHiB-E+ia@RfTR +zk9ZVN7Cz*!p<*q-RxAGV0WAtD5p>Xgh{9hCQ_)JZipzgmAc=sFS*r3$Uok!pZp6(D +zK(w=ml<3|kUI(K3eNlU$w{o;nZ1T-H$|?XpS+1HqjPwJS7#l!m#Y?Z=n+Yd8Rh}ld9(kP{@!&BOU5!S){>rTyDzV^d{+%} +zw&PwP@=&Xr(wb%ok;VOGQ19#SUjEA6xXX1_^Q{5T!!F^|c(~tGPb^hpQeg7)K1ml% +z+b`jZMq5ddFp!)vuiwVu`!CQ5@K12xyCL5QIsv+ATkQ4(&KEB+t-93mk-kH)S+C-| +z%IC6;p$mJo*&D;7bv0B}LJSXbs8)JR4Z+yDlvx9slZlfR&9SBg`Rmv8mw1Xc_sB9u +zK6x$tmDstxqcSK*9Hbi1QdvhZj5eo6xAXAO%0m^-_c#p?dlLa(Q2?1hbzVOptmnL= +ze)&opF-<6CQMw`21dwK=GIdu-MdJ@&^W>04HEsUuV%5|u`|(xRX*_&4&v^4(m}zzs +zQM&<)L`b{t-X_y}rz~@O^@ZOV-|;LKJO7a#Rk+1%nTDKn+1%svwyyL09^HTZ`O(^) +zTKmsWj1Nl`hGC^#0Tcc!!q3J8{1#Xo6bb`e{686=|L}KG`|554j*1>+3g6}S9GIW? +zt@Frj1zwI~kK*7GpYw5D(KOlufRe51N_X_tQ%$9aa<^HvKP6Q0eHubW+iT|MM=eUs +z2#Qs8T;gqi^n6*XRbHX(P_|h3pL4~rB$&c=56Xr{IhZ-kGI4sg{!-8bT1`Ufz0@oz +z$1Cu!w^L{6@IP6e&fExCuP_(=u%mO1y0qs1cqqoDy +zq@M1U$PPoS``y{Cf`KPCZf +z_YVhC%zK3cFqY(4UCruXfcT4IAguckE(=OOuTpGtL*PJ=DipcIMdl3oe_J;{A19IolocCjd~S?eDZgXj +zw21<>9;qe39qOFyEE_2=7X^_K>!!yOkX)Gr7aTYg&ejB2B?j(~J~B}BV7Bx^${x%I +zuZx>EdDWTQv&#KrYhbJpquSkXK+J~0P$3n-$xJj|C6fMeynoL)?Y-7WP_7;uLVi6D +z6#HIaF?cxOK4YmpKbrlq&Qbj%Y}I_ph&N9zx~4!jmbB^NCn}a-7S#g${K7Pu +zrW~V*zHTrsr4wE<%SJv#?Bvr+*9gS4rExWRKWXcvQ#B$1d$&5q4Q*u9*Hq$hJVpdV +zKkGCo!VK$K`?1iRd4&DcE6iVRaK8h<>gl#!DF*Qu7_M^>7y~69?i+p(?8Re4<4aJk +zqn&=>KWpb6)7w`+2i`=c=}t+Z{paGT9IE!*XLWYsiI>Qw1x+@SSOa*yUlkhH+B|E; +zB|1h~w?hwZBqZ;)@qQB821CAz&^yWuGA +z^=68g=aDdLaBlU#wYvr1*Y((Ca6rqLA?pnRn7YC1_uXoerC!B1UTJM47<1B-uo-8` +zabYGY@QN{1)3*BejCvAuWz#?xPSCy*{Tabh%rXrI9eb>AW(HR8k&tNE6@VI|7JR8< +zQ8@n_?L|pxfW9TYl4=E<*oQybd8W-Zsd#2Oe|XNz?P`49Gp#fK;{HQWbJME7bIp#z +z=h}a;LoLYZ@3BG2!|z2~rx@nF(s%c0f)wxL@~eyswSo{LBXu=*_d3JlocM?cc*Uk1 +zm#*}dGWT%O?pld+p@aE(14yF68uBrP0KATl^@8Od0VGV47^+{&s$LxIKVN^3fSPV0 +zhsqD{^kNr=mxX|{CFb@M1wz=2d?$U4zx?ih`hXIF1r#>NxViltW^>Fe!9*wfkZ~>l +z`KyRE?oqZ!Ai9uuccjL;@8#SGTNvY_ +z5&Nbld+Ef>oBnzIq2&AI!MT+BfQ;caO932-2^4os5@AUJkvX0>{JJB%$l30F30IO& +zzH#4Sf6l)p#%*{XbH<~oPuB*j**-R=$Q{;ipsnapHlS3>JWbm4+0H~R$SnHmEB?H?@LKcdkWvmamj`OAQ1h@`yubPQey_}S +z>k$#K1yj*DNPzLKqp>ef4|;i~CvIyt-@B3`B%l9z1o&b*aW6oS^KiT~(c3+z-}yKv)0IqjScf@Y2d +zfi>uUwk15*HFT@`fbCb-Hy%x(#!%OP*XVk){022?9%y`+trS5WMtD-06=I$Uwf82* +zo-{vMP-OYV`Xg+Xg$ogf+PBwO$m*0wh5e+|?-riMq7hZ#;;z#hCi@k$v+PKNVDdy$ +z4K6TlvF}#Vw=x6aw#PH?X!yMC6oyLyU?_HSm@}h0{U+{DQvl9U{>GZx*&;14p}xsb +zY!pqS&gKXE%x0=aEP(^+gsOCja8gZDG1A*ynXzTwIfa6gcFI~x_qZ^!lz2s)Aw=^) +z@?g-<+xoA-wjBjci)*j~F*A)czb0?5!qdxKe2r+(7j8PCrLHma_Hpf;S(=u0Wn<02 +zF{na@-rP?cx=SJAaM!h-B$wVVS9CdE>qLp^v_z-wo3ITA+(i3}mznANHu|birRn$g5Zv+r&SUrSnc4(#0D-gW^mX??5FbH}GJ*yT$~xOfhti(Vf?6Sq6)TzPGT +zluwy}X1BDkgj3~jC&H{85fuM8c7cm%6%wLgUMPJZt;C&pGL!J@N{EG_yHRB6cluP%yxWkV8;#^9_1hvf7a%*Iqjvtk$rlw6p7+g1riYoOY=VetZ +z>)qg^fhQH-I;iqL2zwmL-kD6h&L0kis22Vl|EcIMmN*)DcSaj>y}DP|D=kN4aAepM +zPRVim>u))g?;U^>IEBTxgAM%u@a3s!c1O`$MbZlF#a-4UHqgII%vA_#G_%=b*Eu}O +z-j(pwP?F@)3ut>LQRJ(XCdVT@-Q=5DJpDEGJq!#GHlL18=TF&wY=p^)9Iv&!Wu4^V +z#qtgI)c!Tbc~=d|BfE6V1+)Ic94O=n$TyaY&p-)qM?7)b)N#mzpKBtPiP=qkr&~OUm!>5`bW7(8m +zn$$Z1yL!%e^pjV^x-{mh_~6P=&ISk5e=!jvne6hDeWT>~Q2y8N+8ds40LR6LnFeT& +zL(*)Q1sIW6zN7q!yH-Pm(Fv|&}s^iIsDe?Nu(^Yvy#u&e{wJZ6`LsS +zRKYeiNea89vzP3XiG>snOC_--`4~+v!?R<96Gh?>PF~(T%pxhm9+l8u;2?L<_LeY? +z!o<9)vd#N05!UL@{cH7Y@1IBm%yj7p-Jmel;O^Kms-Vl@^_e#h38>`O$>({TD&b~E +z`=j@{&R)Sd%F9Q2enO)iv2+l1qOKc)e&dS2K|@F_Xv_Wz0)X29-sQS}@6D5$g&~G6 +z@1%TrB9Y|%Uf#js${hMbx978#d#{|0Db@`R?lnq5D}l|9J^Q_5e&&oqP(`qmH(C~! +zQGY7Y&2cjDRWOW4%JGGyl)t=fo<*wfHljY6 +z470RCa+U8QC4l5k&HF_HG#2*J1SC$|q_ak`sWY8Y)|7}$Bo|-1$bxvwVRVyvh!9)i +zO0jZ1)!R6T+E>MwH!EYkQS=UHuu<+0!JC%j509;@f-TIgh;Z?NCAYYW^)*V)aaVb* +zx)zD_J4XzXB3983$gi8%L9oe%u*sDVrc&k#!n3+h{GM;Alvl$&UG_gfq#MO{k16rE +zzM_k)#*yY_>CM(huQ85D!0Nu1RJe^(atq)7TjP7fBgrQxTOW^;VnOD(-eTwn+@gOd$mt)aSaATRWe0{YE9-e$I0+VQeV#i?~$=iLk(BXs;W()S#Wu +z3!K~;n`t8vaIt#Bsqe)D(?Jk-AGb@$$+x>qpA3#$$2BmpLyft8(|db +z*+O3+#$nP6g!M*rmD+ieX`S=hq0$C;BeYx3g{b8>{m-4j`+4$jMDXi@RaYKgZ2t6@ +zK7p1;EoXl#eG9ol;&lFABNc(edD~(;bJ2?SHY??Sa&@s6nN1+%J-8yAe4gv96O_L- +zH%k|d4TDqQ&Pi158e3-+X62kJ{>XB)&=)YkZlV%%vRh(fev(g~IY}7oOK+Ybd?Eu6 +zYSn)HMfQC8oKVH`!-4VoqU~+Gtzfo)K($=?IjOn(&M<=KSD8);rG2=lD4&b0KK(Dg +z2#vzt5>OPmC>`8Ob%fX{Zr1?KKsB0SndAC@$upx0?uy?kfilL&O1CFft+>Gma}u98 +zHnX9167xy!&>&V=kZ<+{YI+0J9bVz05V83ZZK({+555luXvmJb015T!2pWE$!tbDp +z_ZBF%OsU;t_Fli~ee|mY+y%H-ILluRuLvKcx?a|!zpLX@!pA_Z2-uV?oW^$+yYadOm|0Gj#4%~0^DO4Na+~Fsy5VnrLs`SQ +zX5Ux3IkK{&ZTju50b3QAUbk5jmi%zvi13Wt&9g0Fer|5mhODE7Ecv0g0$n@)0u-!fEQ#ALx|YMa3KO-qx-7(p^TB|holS85 +zd;RSAv;&-c3v-B?Ht7@~y%GBP5>!VaF=J_zb12UYBZlUsk>_>lUtMD +zP>T8L8Z-5({;2hz$2Y-0n!^SCqc{AL-yP6r$)8Y2D#PL!yo!gVR0e?3f^(LnI{@~} +z<17>7lwV_>@I(o@^G^N0-md%~>h|kL_J|nC5+=nAl_f*UzAxDZq0N$Y(yGNsqwG{> +ztl1jWSVqb%6xpIwgpsI7M6#q9OZMeCAHLuFdH#aO%TLU5eXi>|@3XznIsbZXrL-w~ +z%T$#HujZ6~nSC2aqAsjI4`UzyD(tjhGbr3in(rs&$Rs&IY9pOb6l~⪼crgFMi37 +zb(cLNhXu`SD}Ll<-}$h~BSc_*7uM^6KuObiPc<>|v5tHkq7kWSs1}39N!?}2y(~6s +zVU7uy@AAj-5U>eL76e)Ib~{)($oEst3v)*3?3!Dmr1x-LeJxhuYK{-r%l-A}XF|^E +zbC7B=M5@g;z_tAOY1QIk_mN41A}Jibi6c*bm(sbOgXucKHRMtn)86(jZ|HMirYBPD +z!dd8Q0d~HO-ztw?=7xAS4O^wH0XdQv;(j9SHLOg67JyC(Kxz8$V= +zzVTp}>k0g}SPvRBF!1VG6V}osyr>5~K3x$eZ&ijFdMML19kBA=dh*4|O*fD9JIiqL +zPTGNGj4WFca%-vTKM{Vh!pNOxD*X&jVkRd>pd3hiY62Ym(S?7FA>8^LuA0L0d +zn6x#1GF7p0!5qr`_lYaJ8_pI94mAd)J{JUOyth;sHhgno>O{Wi&%9xvKu^ad!)d@2 +z=;x3l07-YFgc%f=IT#;upq?zS`3S9bzCNL;mWgRTUJ#S`>H!d`jS!jp-(ae~&=a&q +zI&WMNwYIww#VRVew~6|*BP8%~_6lHvX+;sP(TkqNd*<8AAZ^jsR2hqZ(`ZR?I*^uGrWbbqXX7H7d;E +z%B*kOdBE!qEb5cNMb@%@)BIfwHjW}O`9@n?D?ZQKZ4CqgL9F?sk7E(n3%938s4xMO6ovn-mNrZQ1x?6#sOR+7fQ}P$FE?L(q2pikGPRl+ +z0%;pP0o9`$0BhrwWO!UzT>V@#FPr}T4$9>teSEmEz9Z>sEhfhV2wZBoi{v#fK8&v+ +zJ%VXDUVMlsX*!#yLl&!l>a$uPJ3c98c9ndR*`q{ABujkq{4@-gF3T2S4-XD_w@H_n +zNd3LFU4Fk?TbsHu-ziiTmN3+Mf9$z_up%?-0_$B9Bkm&9TuQGAVSmlMdnHU(yFm*N +zN<>)_8YjO?N?re5Ov?EQzU)))#s-0p2%Xb3GFUl)4J?rd;o22|u$Z@)eqF7h9nz7eHdr8ObFPYTzM-`*5P9_@C)}{-~S9T5|44o$YdO`ET9Fki8EA$XFDjzjbf9; +zrV`&d;-}w;pZ8#>Xy_6mVt1Z7xm{81Ojo>0^s3qaS%|?G%Mg##Y-}7dA^Mi`GWX;? +zno|or2tKBPP&2e>`u=~dG{H&bOAwOy<9JM|Nx8KXi3X>aq$M&gP4rej2BS~AU63&) +zm3>QDzeZW>eO}c}{I|h+`A~_hv7)*4M^8Z>UGTQlcYDC+7@X`KYLW;tKlv@qq326j<_;yp2iKK6G7QqXi +zo`M7>f%vhR!%s+Op-e#VtJwv|gr@mjdJ+ah>!#5@y45@~NZgy_bj<59XzA7gEf#~M +ze9e+5{F)+~QiOn5BDGU>a&GJSPAqrY_gwk!dIy}@zCtHbz>REig{| +zHN)`QKVO +znSF`6-mL+uozjEbW(Y@%PTg<(oE!NrgY)od$p>Sf7Bpr1Ru?de00Tpf1kw{FQ +z)!(@71r%lPf_QrB0q@wuW^$lqg#V`aLyVNJXDBU;*ZpxyY`!3SKs>1rugj1MT+1iI +zbVrp5>zK3WqKY1V6F6FUjr-DVL{X|DYeKSD60iA*Eaz_0>ug@T+9%*mY4STQscdiD +zc^(G!RuR>x=a}5zwe5^7`LujSj1pKEg+An&^6e=yd2uU!SH=BajhQxEgLmTovTRm- +z)z3yMa%4R*a`l`ni6QoGHJ&(}^El&HSm(-EZQ701=~Z92hBlWska%XI)^b_(%n-8G +zw=75FbPLhl_?H=WT-@@%;vOXq2qR2(@RZ*J{BI|idM4UUr7bmMYsEA%Q9n>L^LIK> +z@>T-kS`ZhVs^OoD%ooybZ|~6R>G9V8-m&iH+a@1kahw&Xa2(k< +zk8GD%ag${5Ej}3rB%~!~TSCzFnet@^Yh8IVo7w!1pBYn+%=YZw#Nd~d#sTLg^_IoK +z)HHi^1Vq-}4fW(&ir2PJEl)GPLXPjdj21F=?#e{(H(u|7mK)Y_6Aq>7I@@HCz*uAf98q?8~9aG +zX$EO=^eXi6TQKEEdq6U8CT@F7EHkLB(1DM0ThEz#zD|34d#CU<#L7#pc%;$DuYT_G +z`BA;&&r-e-^ST^se`O-U1tY}md)HuHOZxhnQOCIyzWvU=J5NxQ(j!w(#V*-^f}rKQ +z)ifVwr&nRH{=0o-Z)c}GxT)LJCd(#XZ)s^xX6p|R6V1(9?+l^Hj(kMxFd;xK +zz!-mF;dS&?D{8IqaRS`LY{a)F+?J9LFY^J3=W<40-os9}nw^|?rPy^@SUNj9dtJa; +zP+XFp+HAgS{a!f +zEi|w3o(huMbUt}TI(Azl;n}%wY2U`6?85Hjo6kAoE0xFRD^CZn&3X0BN35eFDh@yz +ziT9BWyos+F=*ao-C}n<+I8DJ@G%22T2Wgmfr1ie^i)=|EBD3|7f$iEa@A2Z+YZa|LEt3a1s-uX@=P +z`|9%}L@rYP1A*dMS4Xl**?T!0(T1#mRkG3e=D{Rg!>!uVM+?AY)XPqLQRFap1F&pyFM=x>;FoDjXoq1l1Qt}?6hWwj839k|b3{@pyly13MxQMI+JMSfPmq%uzUSA#`R~J|D +zh$7E?*Q-m2XI<3$AR^CBFAx=+dQ)P_m64W+zAjgI>a6;_+grDEwbUCw6}pKrVE@%lJ +z3uzJrAy&-aLqOub*4Hzi3MUoz1z4x3_o8qCwkhfjvK=;oRHAz0=j~{0^*fYS*1R6i +zyW(A+<=O99rMeF@GDcx9KEqzrBMGxXjwU0KvqCy15+EicSLzz&sueqE%?3p1)cXgl +z)u}jYVzv`-sBC!lPYu(6b+6qXD#uDTY?(Gn0jh>5STHz9#z4-=RaBsnJA6n7q2`|4fYT*Tg$~LwHDd7SW +zM&wkKu=mx$I8wGJ^omKX(;s!jfmdbj<#S^|D3hsxsL=@&*$+&qv-8Y;?e+wfW&s0< +zP)&AhgIANl)5K0*yFeLy%wS?g0Ja*lVL4%4Tbs4m*R!U?;3Bp$2gG7r4|;bGM8^2t +z-TOKs#y3g|k--Vx>D*xuCL#uP*tHj>WAhR4*RMBnHzwL@)MuzVdYN5WjvC;uHen!y +znlaUw5VAUmPM>q*zBIXRlpHqldf*WcRt{jXcE$!_M&+Hpy-A^Jvw}#xMS8C?Z}z^}-Hlyn!8<$r5-I2Ci^|*kZP8zRf>qcC68BDE2-JjfJ$^4U!HObmmBVO7fiI +zs@jIt^_eOJLGqECke$x$C{IhAkmWn(Y$x`kQsci}188}DLe^i5h^K7*%0MZFORp;> +za^`;T0XaPrBCi5(QW;5wE?dUA$J5ECiG=x>XZ@URm$ztYZN9BIXkqHNvtB@fS!yH< +zsGXK28@I4GH~W`$kmk(`_()rGKYxBJ7Ot*_yOLb9%(3Z2!6Qyx;IW?eI-xw*1J +z8IVk%y7+2*YWRI2cq@4l_xW7a2fsnnb(7&s`UXlfUhyxD6AQs+=;3>%WVDsXwT%ix +zqu{EyhhC~<7x}X1-iZMjE$mWu8+eVRHa*UTq~yP2pS>C8F%cAdHbX;jX6lM&K#a|t +z-Gln;!&iSTRc3C|e^)*%fzPi?xD#diVaGj+`q2GdCcJo_))=U|379gI(kT80n)>&Z +z4RFr5)~hy|AkC85Bg%Vh;F}#q#c+=&@mN}TyQ(TXgr}Q$4Pv-ApPF*;jour2 +ziWdJw9@JsuK`GZmhUIR}XIEf3njq1$Y|ik!cXFuvk0zX(sGuRg$Q=buEa9N-5Y@jv +zBV=`DN&N0!*bR@OGM&C2fAhPK+d))x&6Ea#RwSSKet@xImyWjP_?zP8%wqoN-S{I!HJL%%v +zh45su_9Es0kKJEAGv^z7J+)N>(4^LapLs?i)GQ_79lfKtZ|AZCLY=i8?zV7bh}M$0=0`g;uR5 +zfp8ce8tjA3u1>aa>rk3mOg)}_StAEJs_n$~NrN5EJos6CGv_x4%Q(Zm@>I1fS^5K; +zwHgF1J$1tn;z`b?IPw>37UI!nt?;%ymu+Jf1B=hnhY<|n=SgBAo&tw;FMC+7;DfXfN0%G8^rfMe^c8dKrkHr_tJ&C|f6&vb +z5_5}V%bUD9JGX;F9aM$v#JSgA<#W4*bk26qhfAtmQ(HjMRJrK8!flb~w#mO0!iUXT +zV>sB9HB=(AAX82qDrH*Ikn(_0}GJ(tf*HOeBc3`&WO_oe@8-8!7RZ(Mr5MR3pTC +zk9A5K9}Q)z`P5{dDR2DG=rYY&r!k7Du1!907hOvE#y&44QIFQNX7toRNoNqVKXUd# +z-`rhs^~EIWkJ!VlNY@D(XK~|!&=Ko8?lYg94!eXN$lr7iv#bQJ*}Pp^`7C%SyDz=! +zv{q~3yJ%r!Eb{Jf*DvuP9?Xh`{$G}bH4$ayx%&x0^IeLwpckoh8 +z#(eKt+G20U{OXJ1qLqn3)MPMOf>Cs%g%jSRXRg1DychZX$3mdQdjwb!*_0nGy*VnoJuR0P`5N~`Ah$E!O|6jj?xyIto_>;hv +S`+>9xe$0%l44>$`hWrnV>B(~d + +diff --git a/wrench/reftests/transforms/screen-space-blit.yaml b/wrench/reftests/transforms/screen-space-blit.yaml +index 5b5a914f376251512b821abf5912b2d4c829b7f7..0bae3a37361bc9adb52ac2f379874f13a1eb1fa7 100644 +--- a/wrench/reftests/transforms/screen-space-blit.yaml ++++ b/wrench/reftests/transforms/screen-space-blit.yaml +@@ -9,14 +9,14 @@ root: + items: + - type: "stacking-context" + transform-origin: 235 235 +- transform: rotate-x(15) ++ transform: rotate-x(-15) + filters: identity + items: + - image: checkerboard(2, 16, 16) + bounds: [100, 100, 260, 260] + - type: "stacking-context" + transform-origin: 635 235 +- transform: rotate-z(45) ++ transform: rotate-z(-45) + items: + - image: checkerboard(2, 16, 16) + bounds: [500, 100, 260, 260] +diff --git a/wrench/reftests/transforms/screen-space-blur.yaml b/wrench/reftests/transforms/screen-space-blur.yaml +index 14c791eb8dd47c497ebbe2dc5db7a9f55d8c50f2..e9c5f5d32dc4e8d1f8196173c1c19a011d4ec264 100644 +--- a/wrench/reftests/transforms/screen-space-blur.yaml ++++ b/wrench/reftests/transforms/screen-space-blur.yaml +@@ -6,14 +6,14 @@ root: + items: + - type: "stacking-context" + transform-origin: 235 235 +- transform: rotate-x(15) ++ transform: rotate-x(-15) + filters: blur(3) + items: + - image: checkerboard(2, 16, 16) + bounds: [100, 100, 260, 260] + - type: "stacking-context" + transform-origin: 635 235 +- transform: rotate-z(45) ++ transform: rotate-z(-45) + filters: blur(3) + items: + - image: checkerboard(2, 16, 16) +diff --git a/wrench/src/yaml_helper.rs b/wrench/src/yaml_helper.rs +index 1915b9f531184246a200e6ab1cc6870f6547af37..d82f863e9cce87be6273e353e92685743cd18ab6 100644 +--- a/wrench/src/yaml_helper.rs ++++ b/wrench/src/yaml_helper.rs +@@ -172,24 +172,24 @@ fn make_rotation( + axis_y: f32, + axis_z: f32, + ) -> LayoutTransform { +- let pre_transform = LayoutTransform::create_translation(origin.x, origin.y, 0.0); +- let post_transform = LayoutTransform::create_translation(-origin.x, -origin.y, -0.0); ++ let pre_transform = LayoutTransform::translation(-origin.x, -origin.y, -0.0); ++ let post_transform = LayoutTransform::translation(origin.x, origin.y, 0.0); + + let theta = 2.0f32 * f32::consts::PI - degrees.to_radians(); + let transform = + LayoutTransform::identity().pre_rotate(axis_x, axis_y, axis_z, Angle::radians(theta)); + +- pre_transform.pre_transform(&transform).pre_transform(&post_transform) ++ pre_transform.then(&transform).then(&post_transform) + } + + pub fn make_perspective( + origin: LayoutPoint, + perspective: f32, + ) -> LayoutTransform { +- let pre_transform = LayoutTransform::create_translation(origin.x, origin.y, 0.0); +- let post_transform = LayoutTransform::create_translation(-origin.x, -origin.y, -0.0); +- let transform = LayoutTransform::create_perspective(perspective); +- pre_transform.pre_transform(&transform).pre_transform(&post_transform) ++ let pre_transform = LayoutTransform::translation(-origin.x, -origin.y, -0.0); ++ let post_transform = LayoutTransform::translation(origin.x, origin.y, 0.0); ++ let transform = LayoutTransform::perspective(perspective); ++ pre_transform.then(&transform).then(&post_transform) + } + + // Create a skew matrix, specified in degrees. +@@ -199,7 +199,7 @@ fn make_skew( + ) -> LayoutTransform { + let alpha = Angle::radians(skew_x.to_radians()); + let beta = Angle::radians(skew_y.to_radians()); +- LayoutTransform::create_skew(alpha, beta) ++ LayoutTransform::skew(alpha, beta) + } + + impl YamlHelper for Yaml { +@@ -327,23 +327,11 @@ impl YamlHelper for Yaml { + fn as_matrix4d(&self) -> Option { + if let Some(nums) = self.as_vec_f32() { + assert_eq!(nums.len(), 16, "expected 16 floats, got '{:?}'", self); +- Some(LayoutTransform::row_major( +- nums[0], +- nums[1], +- nums[2], +- nums[3], +- nums[4], +- nums[5], +- nums[6], +- nums[7], +- nums[8], +- nums[9], +- nums[10], +- nums[11], +- nums[12], +- nums[13], +- nums[14], +- nums[15], ++ Some(LayoutTransform::new( ++ nums[0], nums[1], nums[2], nums[3], ++ nums[4], nums[5], nums[6], nums[7], ++ nums[8], nums[9], nums[10], nums[11], ++ nums[12], nums[13], nums[14], nums[15], + )) + } else { + None +@@ -365,7 +353,7 @@ impl YamlHelper for Yaml { + let mx = match function { + "translate" if args.len() >= 2 => { + let z = args.get(2).and_then(|a| a.parse().ok()).unwrap_or(0.); +- LayoutTransform::create_translation( ++ LayoutTransform::translation( + args[0].parse().unwrap(), + args[1].parse().unwrap(), + z, +@@ -386,16 +374,16 @@ impl YamlHelper for Yaml { + let y = args.get(1).and_then(|a| a.parse().ok()).unwrap_or(x); + // Default to no Z scale if unspecified. + let z = args.get(2).and_then(|a| a.parse().ok()).unwrap_or(1.0); +- LayoutTransform::create_scale(x, y, z) ++ LayoutTransform::scale(x, y, z) + } + "scale-x" if args.len() == 1 => { +- LayoutTransform::create_scale(args[0].parse().unwrap(), 1.0, 1.0) ++ LayoutTransform::scale(args[0].parse().unwrap(), 1.0, 1.0) + } + "scale-y" if args.len() == 1 => { +- LayoutTransform::create_scale(1.0, args[0].parse().unwrap(), 1.0) ++ LayoutTransform::scale(1.0, args[0].parse().unwrap(), 1.0) + } + "scale-z" if args.len() == 1 => { +- LayoutTransform::create_scale(1.0, 1.0, args[0].parse().unwrap()) ++ LayoutTransform::scale(1.0, 1.0, args[0].parse().unwrap()) + } + "skew" if args.len() >= 1 => { + // Default to no Y skew if unspecified. +@@ -409,14 +397,14 @@ impl YamlHelper for Yaml { + make_skew(0.0, args[0].parse().unwrap()) + } + "perspective" if args.len() == 1 => { +- LayoutTransform::create_perspective(args[0].parse().unwrap()) ++ LayoutTransform::perspective(args[0].parse().unwrap()) + } + _ => { + println!("unknown function {}", function); + break; + } + }; +- transform = transform.post_transform(&mx); ++ transform = transform.then(&mx); + } + Some(transform) + } +@@ -424,7 +412,7 @@ impl YamlHelper for Yaml { + let transform = array.iter().fold( + LayoutTransform::identity(), + |u, yaml| match yaml.as_transform(transform_origin) { +- Some(ref transform) => u.pre_transform(transform), ++ Some(ref transform) => transform.then(&u), + None => u, + }, + ); +-- +2.39.2 + diff --git a/third_party/webrender/patches/0006-Bump-procedural-masquerade-to-0.1.7.patch b/third_party/webrender/patches/0006-Bump-procedural-masquerade-to-0.1.7.patch new file mode 100644 index 00000000000..f8e985bbc03 --- /dev/null +++ b/third_party/webrender/patches/0006-Bump-procedural-masquerade-to-0.1.7.patch @@ -0,0 +1,53 @@ +From 415b9ba32648667313f6f4ce7965752285bf0b26 Mon Sep 17 00:00:00 2001 +From: Martin Robinson +Date: Thu, 12 Jan 2023 12:35:31 +0100 +Subject: [PATCH 2/2] Bump procedural-masquerade to 0.1.7 + +This fixes a build issue in this branch. +--- + Cargo.lock | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/Cargo.lock b/Cargo.lock +index b6085604cae8e18de3273bcddac43fa0a7e1abd1..b7055733e57fcd0acff07881ef72369b560c2abe 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -417,7 +417,7 @@ version = "0.1.7" + source = "registry+https://github.com/rust-lang/crates.io-index" + dependencies = [ + "cstr-macros 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +- "procedural-masquerade 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ++ "procedural-masquerade 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + ] + + [[package]] +@@ -425,7 +425,7 @@ name = "cstr-macros" + version = "0.1.6" + source = "registry+https://github.com/rust-lang/crates.io-index" + dependencies = [ +- "procedural-masquerade 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ++ "procedural-masquerade 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + ] + +@@ -1212,7 +1212,7 @@ dependencies = [ + + [[package]] + name = "procedural-masquerade" +-version = "0.1.6" ++version = "0.1.7" + source = "registry+https://github.com/rust-lang/crates.io-index" + + [[package]] +@@ -2182,7 +2182,7 @@ dependencies = [ + "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" + "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" + "checksum proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" +-"checksum procedural-masquerade 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9a1574a51c3fd37b26d2c0032b649d08a7d51d4cca9c41bbc5bf7118fa4509d0" ++"checksum procedural-masquerade 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1383dff4092fe903ac180e391a8d4121cc48f08ccf850614b0290c6673b69d" + "checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" + "checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" +-- +2.39.2 + diff --git a/third_party/webrender/patches/head b/third_party/webrender/patches/head new file mode 100644 index 00000000000..1e8d57b32a9 --- /dev/null +++ b/third_party/webrender/patches/head @@ -0,0 +1 @@ +1175acad2d4f49fa712e105c84149ac7f394261d diff --git a/third_party/webrender/patches/series b/third_party/webrender/patches/series new file mode 100644 index 00000000000..5670e534e71 --- /dev/null +++ b/third_party/webrender/patches/series @@ -0,0 +1,6 @@ +0001-Add-signal-handler-to-catch-segfault-in-build-script.patch +0002-Bug-1646741-Update-gleam-to-0.12.-r-kvark.patch +0003-Bug-1651889.-Update-to-gleam-0.12.1.-r-kvark.patch +0004-Bug-1654699.-Update-core-foundation-core-graphics.-r.patch +0005-Bug-1656236-Update-to-euclid-0.22.-r-kvark.patch +0006-Bump-procedural-masquerade-to-0.1.7.patch diff --git a/third_party/webrender/peek-poke/Cargo.toml b/third_party/webrender/peek-poke/Cargo.toml new file mode 100644 index 00000000000..45179aeda60 --- /dev/null +++ b/third_party/webrender/peek-poke/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "peek-poke" +version = "0.2.0" +authors = ["Dan Glastonbury "] +repository = "https://github.com/servo/webrender" +description = "A mechanism for serializing and deserializing data into/from byte buffers, for use in WebRender." +license = "MIT/Apache-2.0" +edition = "2018" + +[dependencies] +euclid = { version = "0.22.0", optional = true } +peek-poke-derive = { version = "0.2", path = "./peek-poke-derive", optional = true } + +[features] +default = ["derive"] +derive = ["peek-poke-derive"] +extras = ["derive", "euclid"] diff --git a/third_party/webrender/peek-poke/LICENSE-APACHE b/third_party/webrender/peek-poke/LICENSE-APACHE new file mode 100644 index 00000000000..16fe87b06e8 --- /dev/null +++ b/third_party/webrender/peek-poke/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/webrender/peek-poke/LICENSE-MIT b/third_party/webrender/peek-poke/LICENSE-MIT new file mode 100644 index 00000000000..110bb347123 --- /dev/null +++ b/third_party/webrender/peek-poke/LICENSE-MIT @@ -0,0 +1,44 @@ +MIT License + +Copyright (c) 2019 Daniel Glastonbury + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +This work incorporates work covered by the following copyright and permission +notice: + +Copyright (c) 2019 Devashish Dixit + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/webrender/peek-poke/README.md b/third_party/webrender/peek-poke/README.md new file mode 100644 index 00000000000..1c379aa1211 --- /dev/null +++ b/third_party/webrender/peek-poke/README.md @@ -0,0 +1,54 @@ +# Peeks, Pokes, and Pointers + +Peek from and poke structures into byte slices. + +## Benchmark + +Below are the benchmark results of comparison between `peek-poke` and `bincode` serializing and deserializing same `struct`: +``` +struct MyPeekPokeStruct { + a: u8, + b: u16, + c: MyPeekPokeEnum, + d: Option, +} + +enum MyPeekPokeEnum { + Variant1, + Variant2(u16), +} +``` + +``` +Benchmarking struct::serialize/peek_poke::poke_into: Collecting 100 samples in struct::serialize/peek_poke::poke_into + time: [2.7267 ns 2.7321 ns 2.7380 ns] + +Benchmarking struct::serialize/bincode::serialize: Collecting 100 samples in est struct::serialize/bincode::serialize + time: [31.264 ns 31.326 ns 31.389 ns] + +Benchmarking struct::deserialize/peek_poke::peek_from: Collecting 100 samples struct::deserialize/peek_poke::peek_from + time: [5.3544 ns 5.3672 ns 5.3817 ns] + +Benchmarking struct::deserialize/bincode::deserialize: Collecting 100 samples in struct::deserialize/bincode::deserialize + time: [25.155 ns 26.439 ns 27.661 ns] +``` + +You can run benchmarks by running following command: +``` +cargo bench +``` + +## License +[license]: #license + +Licensed under either of +- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +- MIT license (http://opensource.org/licenses/MIT) + +at your option. + +see [LICENSE-APACHE](LICENSE-APACHE), [LICENSE-MIT](LICENSE-MIT) for details. + +## Contribution +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as +defined in the Apache-2.0 license, shall be dual licensed as about, without any additional terms or conditions. diff --git a/third_party/webrender/peek-poke/peek-poke-derive/Cargo.toml b/third_party/webrender/peek-poke/peek-poke-derive/Cargo.toml new file mode 100644 index 00000000000..e537c7304ab --- /dev/null +++ b/third_party/webrender/peek-poke/peek-poke-derive/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "peek-poke-derive" +version = "0.2.1" +authors = ["Dan Glastonbury "] +repository = "https://github.com/servo/webrender" +description = "Derive macro for peek-poke." +license = "MIT/Apache-2.0" +edition = "2018" + +[lib] +doctest = false +proc-macro = true + +[dependencies] +proc-macro2 = "1" +quote = "1" +syn = "1" +synstructure = "0.12" +unicode-xid = "0.2" diff --git a/third_party/webrender/peek-poke/peek-poke-derive/LICENSE-APACHE b/third_party/webrender/peek-poke/peek-poke-derive/LICENSE-APACHE new file mode 100644 index 00000000000..16fe87b06e8 --- /dev/null +++ b/third_party/webrender/peek-poke/peek-poke-derive/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/webrender/peek-poke/peek-poke-derive/LICENSE-MIT b/third_party/webrender/peek-poke/peek-poke-derive/LICENSE-MIT new file mode 100644 index 00000000000..110bb347123 --- /dev/null +++ b/third_party/webrender/peek-poke/peek-poke-derive/LICENSE-MIT @@ -0,0 +1,44 @@ +MIT License + +Copyright (c) 2019 Daniel Glastonbury + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +This work incorporates work covered by the following copyright and permission +notice: + +Copyright (c) 2019 Devashish Dixit + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/webrender/peek-poke/peek-poke-derive/README.md b/third_party/webrender/peek-poke/peek-poke-derive/README.md new file mode 100644 index 00000000000..1c379aa1211 --- /dev/null +++ b/third_party/webrender/peek-poke/peek-poke-derive/README.md @@ -0,0 +1,54 @@ +# Peeks, Pokes, and Pointers + +Peek from and poke structures into byte slices. + +## Benchmark + +Below are the benchmark results of comparison between `peek-poke` and `bincode` serializing and deserializing same `struct`: +``` +struct MyPeekPokeStruct { + a: u8, + b: u16, + c: MyPeekPokeEnum, + d: Option, +} + +enum MyPeekPokeEnum { + Variant1, + Variant2(u16), +} +``` + +``` +Benchmarking struct::serialize/peek_poke::poke_into: Collecting 100 samples in struct::serialize/peek_poke::poke_into + time: [2.7267 ns 2.7321 ns 2.7380 ns] + +Benchmarking struct::serialize/bincode::serialize: Collecting 100 samples in est struct::serialize/bincode::serialize + time: [31.264 ns 31.326 ns 31.389 ns] + +Benchmarking struct::deserialize/peek_poke::peek_from: Collecting 100 samples struct::deserialize/peek_poke::peek_from + time: [5.3544 ns 5.3672 ns 5.3817 ns] + +Benchmarking struct::deserialize/bincode::deserialize: Collecting 100 samples in struct::deserialize/bincode::deserialize + time: [25.155 ns 26.439 ns 27.661 ns] +``` + +You can run benchmarks by running following command: +``` +cargo bench +``` + +## License +[license]: #license + +Licensed under either of +- Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +- MIT license (http://opensource.org/licenses/MIT) + +at your option. + +see [LICENSE-APACHE](LICENSE-APACHE), [LICENSE-MIT](LICENSE-MIT) for details. + +## Contribution +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as +defined in the Apache-2.0 license, shall be dual licensed as about, without any additional terms or conditions. diff --git a/third_party/webrender/peek-poke/peek-poke-derive/src/lib.rs b/third_party/webrender/peek-poke/peek-poke-derive/src/lib.rs new file mode 100644 index 00000000000..7000f28bf15 --- /dev/null +++ b/third_party/webrender/peek-poke/peek-poke-derive/src/lib.rs @@ -0,0 +1,266 @@ +// Copyright 2019 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{Ident, Index, TraitBound}; +use synstructure::{decl_derive, Structure, BindStyle, AddBounds}; +use unicode_xid::UnicodeXID; + +// Internal method for sanitizing an identifier for hygiene purposes. +fn sanitize_ident(s: &str) -> Ident { + let mut res = String::with_capacity(s.len()); + for mut c in s.chars() { + if !UnicodeXID::is_xid_continue(c) { + c = '_' + } + // Deduplicate consecutive _ characters. + if res.ends_with('_') && c == '_' { + continue; + } + res.push(c); + } + Ident::new(&res, Span::call_site()) +} + +/// Calculates size type for number of variants (used for enums) +fn get_discriminant_size_type(len: usize) -> TokenStream { + if len <= ::max_value() as usize { + quote! { u8 } + } else if len <= ::max_value() as usize { + quote! { u16 } + } else { + quote! { u32 } + } +} + +fn is_struct(s: &Structure) -> bool { + // a single variant with no prefix is 'struct' + match &s.variants()[..] { + [v] if v.prefix.is_none() => true, + _ => false, + } +} + +fn derive_max_size(s: &Structure) -> TokenStream { + let max_size = s.variants().iter().fold(quote!(0), |acc, vi| { + let variant_size = vi.bindings().iter().fold(quote!(0), |acc, bi| { + // compute size of each variant by summing the sizes of its bindings + let ty = &bi.ast().ty; + quote!(#acc + <#ty>::max_size()) + }); + + // find the maximum of each variant + quote! { + max(#acc, #variant_size) + } + }); + + let body = if is_struct(s) { + max_size + } else { + let discriminant_size_type = get_discriminant_size_type(s.variants().len()); + quote! { + #discriminant_size_type ::max_size() + #max_size + } + }; + + quote! { + #[inline(always)] + fn max_size() -> usize { + use std::cmp::max; + #body + } + } +} + +fn derive_peek_from_for_enum(s: &mut Structure) -> TokenStream { + assert!(!is_struct(s)); + s.bind_with(|_| BindStyle::Move); + + let num_variants = s.variants().len(); + let discriminant_size_type = get_discriminant_size_type(num_variants); + let body = s + .variants() + .iter() + .enumerate() + .fold(quote!(), |acc, (i, vi)| { + let bindings = vi + .bindings() + .iter() + .map(|bi| quote!(#bi)) + .collect::>(); + + let variant_pat = Index::from(i); + let poke_exprs = bindings.iter().fold(quote!(), |acc, bi| { + quote! { + #acc + let (#bi, bytes) = peek_poke::peek_from_default(bytes); + } + }); + let construct = vi.construct(|_, i| { + let bi = &bindings[i]; + quote!(#bi) + }); + + quote! { + #acc + #variant_pat => { + #poke_exprs + *output = #construct; + bytes + } + } + }); + + let type_name = s.ast().ident.to_string(); + let max_tag_value = num_variants - 1; + + quote! { + #[inline(always)] + unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 { + let (variant, bytes) = peek_poke::peek_from_default::<#discriminant_size_type>(bytes); + match variant { + #body + out_of_range_tag => { + panic!("WRDL: memory corruption detected while parsing {} - enum tag should be <= {}, but was {}", + #type_name, #max_tag_value, out_of_range_tag); + } + } + } + } +} + +fn derive_peek_from_for_struct(s: &mut Structure) -> TokenStream { + assert!(is_struct(&s)); + + s.variants_mut()[0].bind_with(|_| BindStyle::RefMut); + let pat = s.variants()[0].pat(); + let peek_exprs = s.variants()[0].bindings().iter().fold(quote!(), |acc, bi| { + let ty = &bi.ast().ty; + quote! { + #acc + let bytes = <#ty>::peek_from(bytes, #bi); + } + }); + + let body = quote! { + #pat => { + #peek_exprs + bytes + } + }; + + quote! { + #[inline(always)] + unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 { + match &mut (*output) { + #body + } + } + } +} + +fn derive_poke_into(s: &Structure) -> TokenStream { + let is_struct = is_struct(&s); + let discriminant_size_type = get_discriminant_size_type(s.variants().len()); + let body = s + .variants() + .iter() + .enumerate() + .fold(quote!(), |acc, (i, vi)| { + let init = if !is_struct { + let index = Index::from(i); + quote! { + let bytes = #discriminant_size_type::poke_into(&#index, bytes); + } + } else { + quote!() + }; + let variant_pat = vi.pat(); + let poke_exprs = vi.bindings().iter().fold(init, |acc, bi| { + quote! { + #acc + let bytes = #bi.poke_into(bytes); + } + }); + + quote! { + #acc + #variant_pat => { + #poke_exprs + bytes + } + } + }); + + quote! { + #[inline(always)] + unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 { + match &*self { + #body + } + } + } +} + +fn peek_poke_derive(mut s: Structure) -> TokenStream { + s.binding_name(|_, i| Ident::new(&format!("__self_{}", i), Span::call_site())); + + let max_size_fn = derive_max_size(&s); + let poke_into_fn = derive_poke_into(&s); + let peek_from_fn = if is_struct(&s) { + derive_peek_from_for_struct(&mut s) + } else { + derive_peek_from_for_enum(&mut s) + }; + + let poke_impl = s.gen_impl(quote! { + extern crate peek_poke; + + gen unsafe impl peek_poke::Poke for @Self { + #max_size_fn + #poke_into_fn + } + }); + + // To implement `fn peek_from` we require that types implement `Default` + // trait to create temporary values. This code does the addition all + // manually until https://github.com/mystor/synstructure/issues/24 is fixed. + let default_trait = syn::parse_str::("::std::default::Default").unwrap(); + let peek_trait = syn::parse_str::("peek_poke::Peek").unwrap(); + + let ast = s.ast(); + let name = &ast.ident; + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + let mut where_clause = where_clause.cloned(); + s.add_trait_bounds(&default_trait, &mut where_clause, AddBounds::Generics); + s.add_trait_bounds(&peek_trait, &mut where_clause, AddBounds::Generics); + + let dummy_const: Ident = sanitize_ident(&format!("_DERIVE_peek_poke_Peek_FOR_{}", name)); + + let peek_impl = quote! { + #[allow(non_upper_case_globals)] + const #dummy_const: () = { + extern crate peek_poke; + + impl #impl_generics peek_poke::Peek for #name #ty_generics #where_clause { + #peek_from_fn + } + }; + }; + + quote! { + #poke_impl + #peek_impl + } +} + +decl_derive!([PeekPoke] => peek_poke_derive); diff --git a/third_party/webrender/peek-poke/src/euclid.rs b/third_party/webrender/peek-poke/src/euclid.rs new file mode 100644 index 00000000000..44b0ed27e73 --- /dev/null +++ b/third_party/webrender/peek-poke/src/euclid.rs @@ -0,0 +1,170 @@ +// Copyright 2019 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::{Peek, Poke}; +use euclid::{Point2D, Rect, SideOffsets2D, Size2D, Transform3D, Vector2D}; + +unsafe impl Poke for Point2D { + #[inline(always)] + fn max_size() -> usize { + 2 * T::max_size() + } + #[inline(always)] + unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 { + let bytes = self.x.poke_into(bytes); + let bytes = self.y.poke_into(bytes); + bytes + } +} +impl Peek for Point2D { + #[inline(always)] + unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 { + let bytes = T::peek_from(bytes, &mut (*output).x); + let bytes = T::peek_from(bytes, &mut (*output).y); + bytes + } +} + +unsafe impl Poke for Rect { + #[inline(always)] + fn max_size() -> usize { + Point2D::::max_size() + Size2D::::max_size() + } + #[inline(always)] + unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 { + let bytes = self.origin.poke_into(bytes); + let bytes = self.size.poke_into(bytes); + bytes + } +} +impl Peek for Rect { + #[inline(always)] + unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 { + let bytes = Point2D::::peek_from(bytes, &mut (*output).origin); + let bytes = Size2D::::peek_from(bytes, &mut (*output).size); + bytes + } +} + +unsafe impl Poke for SideOffsets2D { + #[inline(always)] + fn max_size() -> usize { + 4 * T::max_size() + } + #[inline(always)] + unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 { + let bytes = self.top.poke_into(bytes); + let bytes = self.right.poke_into(bytes); + let bytes = self.bottom.poke_into(bytes); + let bytes = self.left.poke_into(bytes); + bytes + } +} +impl Peek for SideOffsets2D { + #[inline(always)] + unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 { + let bytes = T::peek_from(bytes, &mut (*output).top); + let bytes = T::peek_from(bytes, &mut (*output).right); + let bytes = T::peek_from(bytes, &mut (*output).bottom); + let bytes = T::peek_from(bytes, &mut (*output).left); + bytes + } +} + +unsafe impl Poke for Size2D { + #[inline(always)] + fn max_size() -> usize { + 2 * T::max_size() + } + #[inline(always)] + unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 { + let bytes = self.width.poke_into(bytes); + let bytes = self.height.poke_into(bytes); + bytes + } +} +impl Peek for Size2D { + #[inline(always)] + unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 { + let bytes = T::peek_from(bytes, &mut (*output).width); + let bytes = T::peek_from(bytes, &mut (*output).height); + bytes + } +} + +unsafe impl Poke for Transform3D { + #[inline(always)] + fn max_size() -> usize { + 16 * T::max_size() + } + #[inline(always)] + unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 { + let bytes = self.m11.poke_into(bytes); + let bytes = self.m12.poke_into(bytes); + let bytes = self.m13.poke_into(bytes); + let bytes = self.m14.poke_into(bytes); + let bytes = self.m21.poke_into(bytes); + let bytes = self.m22.poke_into(bytes); + let bytes = self.m23.poke_into(bytes); + let bytes = self.m24.poke_into(bytes); + let bytes = self.m31.poke_into(bytes); + let bytes = self.m32.poke_into(bytes); + let bytes = self.m33.poke_into(bytes); + let bytes = self.m34.poke_into(bytes); + let bytes = self.m41.poke_into(bytes); + let bytes = self.m42.poke_into(bytes); + let bytes = self.m43.poke_into(bytes); + let bytes = self.m44.poke_into(bytes); + bytes + } +} +impl Peek for Transform3D { + #[inline(always)] + unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 { + let bytes = T::peek_from(bytes, &mut (*output).m11); + let bytes = T::peek_from(bytes, &mut (*output).m12); + let bytes = T::peek_from(bytes, &mut (*output).m13); + let bytes = T::peek_from(bytes, &mut (*output).m14); + let bytes = T::peek_from(bytes, &mut (*output).m21); + let bytes = T::peek_from(bytes, &mut (*output).m22); + let bytes = T::peek_from(bytes, &mut (*output).m23); + let bytes = T::peek_from(bytes, &mut (*output).m24); + let bytes = T::peek_from(bytes, &mut (*output).m31); + let bytes = T::peek_from(bytes, &mut (*output).m32); + let bytes = T::peek_from(bytes, &mut (*output).m33); + let bytes = T::peek_from(bytes, &mut (*output).m34); + let bytes = T::peek_from(bytes, &mut (*output).m41); + let bytes = T::peek_from(bytes, &mut (*output).m42); + let bytes = T::peek_from(bytes, &mut (*output).m43); + let bytes = T::peek_from(bytes, &mut (*output).m44); + bytes + } +} + +unsafe impl Poke for Vector2D { + #[inline(always)] + fn max_size() -> usize { + 2 * T::max_size() + } + #[inline(always)] + unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 { + let bytes = self.x.poke_into(bytes); + let bytes = self.y.poke_into(bytes); + bytes + } +} +impl Peek for Vector2D { + #[inline(always)] + unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 { + let bytes = T::peek_from(bytes, &mut (*output).x); + let bytes = T::peek_from(bytes, &mut (*output).y); + bytes + } +} diff --git a/third_party/webrender/peek-poke/src/lib.rs b/third_party/webrender/peek-poke/src/lib.rs new file mode 100644 index 00000000000..55864599bbd --- /dev/null +++ b/third_party/webrender/peek-poke/src/lib.rs @@ -0,0 +1,427 @@ +// Copyright 2019 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Fast binary serialization and deserialization for types with a known maximum size. +//! +//! ## Binary Encoding Scheme +//! +//! ## Usage +//! +//! ## Comparison to bincode + +#[cfg(feature = "derive")] +pub use peek_poke_derive::*; + +use core::{marker::PhantomData, mem::size_of, slice}; +use crate::{slice_ext::*, vec_ext::*}; + +mod slice_ext; +mod vec_ext; + +union MaybeUninitShim { + uninit: (), + init: T, +} + +/// Peek helper for constructing a `T` by `Copy`ing into an uninitialized stack +/// allocation. +pub unsafe fn peek_from_uninit(bytes: *const u8) -> (T, *const u8) { + let mut val = MaybeUninitShim { uninit: () }; + let bytes = ::peek_from(bytes, &mut val.init); + (val.init, bytes) +} + +/// Peek helper for constructing a `T` by `Default` initialized stack +/// allocation. +pub unsafe fn peek_from_default(bytes: *const u8) -> (T, *const u8) { + let mut val = T::default(); + let bytes = ::peek_from(bytes, &mut val); + (val, bytes) +} + +/// Peek inplace a `T` from a slice of bytes, returning a slice of the remaining +/// bytes. `src` must contain at least `T::max_size()` bytes. +/// +/// [`ensure_red_zone`] can be used to add required padding. +pub fn peek_from_slice<'a, T: Peek>(src: &'a [u8], dst: &mut T) -> &'a [u8] { + unsafe { + // If src.len() == T::max_size() then src is at the start of the red-zone. + assert!(T::max_size() < src.len(), "WRDL: unexpected end of display list"); + let end_ptr = T::peek_from(src.as_ptr(), dst); + let len = end_ptr as usize - src.as_ptr() as usize; + // Did someone break the T::peek_from() can't read more than T::max_size() + // bytes contract? + assert!(len <= src.len(), "WRDL: Peek::max_size was wrong"); + slice::from_raw_parts(end_ptr, src.len() - len) + } +} + +/// Poke helper to insert a serialized version of `src` at the beginning for `dst`. +pub fn poke_inplace_slice(src: &T, dst: &mut [u8]) { + assert!(T::max_size() <= dst.len(), "WRDL: buffer too small to write into"); + unsafe { + src.poke_into(dst.as_mut_ptr()); + } +} + +/// Poke helper to append a serialized version of `src` to the end of `dst`. +pub fn poke_into_vec(src: &T, dst: &mut Vec) { + dst.reserve(T::max_size()); + unsafe { + let ptr = dst.as_end_mut_ptr(); + let end_ptr = src.poke_into(ptr); + dst.set_end_ptr(end_ptr); + } +} + +// TODO: Is returning the len of the iterator of any practical use? +pub fn poke_extend_vec(src: I, dst: &mut Vec) -> usize +where + I: ExactSizeIterator, + I::Item: Poke, +{ + let len = src.len(); + let max_size = len * I::Item::max_size(); + dst.reserve(max_size); + unsafe { + let ptr = dst.as_end_mut_ptr(); + // Guard against the possibility of a misbehaved implementation of + // ExactSizeIterator by writing at most `len` items. + let end_ptr = src.take(len).fold(ptr, |ptr, item| item.poke_into(ptr)); + dst.set_end_ptr(end_ptr); + } + + len +} + +/// Add `T::max_size()` "red zone" (padding of zeroes) to the end of the vec of +/// `bytes`. This allows deserialization to assert that at least `T::max_size()` +/// bytes exist at all times. +pub fn ensure_red_zone(bytes: &mut Vec) { + bytes.reserve(T::max_size()); + unsafe { + let end_ptr = bytes.as_end_mut_ptr(); + end_ptr.write_bytes(0, T::max_size()); + bytes.set_end_ptr(end_ptr.add(T::max_size())); + } +} + +#[inline] +unsafe fn read_verbatim(src: *const u8, dst: *mut T) -> *const u8 { + *dst = (src as *const T).read_unaligned(); + src.add(size_of::()) +} + +#[inline] +unsafe fn write_verbatim(src: T, dst: *mut u8) -> *mut u8 { + (dst as *mut T).write_unaligned(src); + dst.add(size_of::()) +} + +#[cfg(feature = "extras")] +mod euclid; + +/// A trait for values that provide serialization into buffers of bytes. +/// +/// # Example +/// +/// ```no_run +/// use peek_poke::Poke; +/// +/// struct Bar { +/// a: u32, +/// b: u8, +/// c: i16, +/// } +/// +/// unsafe impl Poke for Bar { +/// fn max_size() -> usize { +/// ::max_size() + ::max_size() + ::max_size() +/// } +/// unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 { +/// let bytes = self.a.poke_into(bytes); +/// let bytes = self.b.poke_into(bytes); +/// self.c.poke_into(bytes) +/// } +/// } +/// ``` +/// +/// # Safety +/// +/// The `Poke` trait is an `unsafe` trait for the reasons, and implementors must +/// ensure that they adhere to these contracts: +/// +/// * `max_size()` query and calculations in general must be correct. Callers +/// of this trait are expected to rely on the contract defined on each +/// method, and implementors must ensure such contracts remain true. +pub unsafe trait Poke { + /// Return the maximum number of bytes that the serialized version of `Self` + /// will occupy. + /// + /// # Safety + /// + /// Implementors of `Poke` guarantee to not write more than the result of + /// calling `max_size()` into the buffer pointed to by `bytes` when + /// `poke_into()` is called. + fn max_size() -> usize; + /// Serialize into the buffer pointed to by `bytes`. + /// + /// Returns a pointer to the next byte after the serialized representation of `Self`. + /// + /// # Safety + /// + /// This function is unsafe because undefined behavior can result if the + /// caller does not ensure all of the following: + /// + /// * `bytes` must denote a valid pointer to a block of memory. + /// + /// * `bytes` must pointer to at least the number of bytes returned by + /// `max_size()`. + unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8; +} + +/// A trait for values that provide deserialization from buffers of bytes. +/// +/// # Example +/// +/// ```ignore +/// use peek_poke::Peek; +/// +/// struct Bar { +/// a: u32, +/// b: u8, +/// c: i16, +/// } +/// +/// ... +/// +/// impl Peek for Bar { +/// unsafe fn peek_from(&mut self, bytes: *const u8) -> *const u8 { +/// let bytes = self.a.peek_from(bytes); +/// let bytes = self.b.peek_from(bytes); +/// self.c.peek_from(bytes) +/// } +/// } +/// ``` +/// +/// # Safety +/// +/// The `Peek` trait contains unsafe methods for the following reasons, and +/// implementors must ensure that they adhere to these contracts: +/// +/// * Callers of this trait are expected to rely on the contract defined on each +/// method, and implementors must ensure that `peek_from()` doesn't read more +/// bytes from `bytes` than is returned by `Peek::max_size()`. +pub trait Peek: Poke { + /// Deserialize from the buffer pointed to by `bytes`. + /// + /// Returns a pointer to the next byte after the unconsumed bytes not used + /// to deserialize the representation of `Self`. + /// + /// # Safety + /// + /// This function is unsafe because undefined behavior can result if the + /// caller does not ensure all of the following: + /// + /// * `bytes` must denote a valid pointer to a block of memory. + /// + /// * `bytes` must pointer to at least the number of bytes returned by + /// `Poke::max_size()`. + unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8; +} + +macro_rules! impl_poke_for_deref { + (<$($desc:tt)+) => { + unsafe impl <$($desc)+ { + #[inline(always)] + fn max_size() -> usize { + ::max_size() + } + unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 { + (**self).poke_into(bytes) + } + } + } +} + +impl_poke_for_deref!(<'a, T: Poke> Poke for &'a T); +impl_poke_for_deref!(<'a, T: Poke> Poke for &'a mut T); + +macro_rules! impl_for_primitive { + ($($ty:ty)+) => { + $(unsafe impl Poke for $ty { + #[inline(always)] + fn max_size() -> usize { + size_of::() + } + #[inline(always)] + unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 { + write_verbatim(*self, bytes) + } + } + impl Peek for $ty { + #[inline(always)] + unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 { + read_verbatim(bytes, output) + } + })+ + }; +} + +impl_for_primitive! { + i8 i16 i32 i64 isize + u8 u16 u32 u64 usize + f32 f64 +} + +unsafe impl Poke for bool { + #[inline(always)] + fn max_size() -> usize { + u8::max_size() + } + #[inline] + unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 { + (*self as u8).poke_into(bytes) + } +} + +impl Peek for bool { + #[inline] + unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 { + let mut int_bool = 0u8; + let ptr = ::peek_from(bytes, &mut int_bool); + *output = int_bool != 0; + ptr + } +} + +unsafe impl Poke for PhantomData { + #[inline(always)] + fn max_size() -> usize { + 0 + } + #[inline(always)] + unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 { + bytes + } +} + +impl Peek for PhantomData { + #[inline(always)] + unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 { + *output = PhantomData; + bytes + } +} + +unsafe impl Poke for Option { + #[inline(always)] + fn max_size() -> usize { + u8::max_size() + T::max_size() + } + + #[inline] + unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 { + match self { + None => 0u8.poke_into(bytes), + Some(ref v) => { + let bytes = 1u8.poke_into(bytes); + let bytes = v.poke_into(bytes); + bytes + } + } + } +} + +impl Peek for Option { + #[inline] + unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 { + let (variant, bytes) = peek_from_default::(bytes); + match variant { + 0 => { + *output = None; + bytes + } + 1 => { + let (val, bytes) = peek_from_default(bytes); + *output = Some(val); + bytes + } + _ => unreachable!(), + } + } +} + +macro_rules! impl_for_arrays { + ($($len:tt)+) => { + $(unsafe impl Poke for [T; $len] { + fn max_size() -> usize { + $len * T::max_size() + } + unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 { + self.iter().fold(bytes, |bytes, e| e.poke_into(bytes)) + } + } + impl Peek for [T; $len] { + unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 { + (&mut *output).iter_mut().fold(bytes, |bytes, e| ::peek_from(bytes, e)) + } + })+ + } +} + +impl_for_arrays! { + 01 02 03 04 05 06 07 08 09 10 + 11 12 13 14 15 16 17 18 19 20 + 21 22 23 24 25 26 27 28 29 30 + 31 32 +} + +unsafe impl Poke for () { + fn max_size() -> usize { + 0 + } + unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 { + bytes + } +} +impl Peek for () { + unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 { + *output = (); + bytes + } +} + +macro_rules! impl_for_tuple { + ($($n:tt: $ty:ident),+) => { + unsafe impl<$($ty: Poke),+> Poke for ($($ty,)+) { + #[inline(always)] + fn max_size() -> usize { + 0 $(+ <$ty>::max_size())+ + } + unsafe fn poke_into(&self, bytes: *mut u8) -> *mut u8 { + $(let bytes = self.$n.poke_into(bytes);)+ + bytes + } + } + impl<$($ty: Peek),+> Peek for ($($ty,)+) { + unsafe fn peek_from(bytes: *const u8, output: *mut Self) -> *const u8 { + $(let bytes = $ty::peek_from(bytes, &mut (*output).$n);)+ + bytes + } + } + } +} + +impl_for_tuple!(0: A); +impl_for_tuple!(0: A, 1: B); +impl_for_tuple!(0: A, 1: B, 2: C); +impl_for_tuple!(0: A, 1: B, 2: C, 3: D); +impl_for_tuple!(0: A, 1: B, 2: C, 3: D, 4: E); diff --git a/third_party/webrender/peek-poke/src/slice_ext.rs b/third_party/webrender/peek-poke/src/slice_ext.rs new file mode 100644 index 00000000000..f309d2f7410 --- /dev/null +++ b/third_party/webrender/peek-poke/src/slice_ext.rs @@ -0,0 +1,19 @@ +// Copyright 2019 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +pub trait AsEndMutPtr { + fn as_end_mut_ptr(self) -> *mut T; +} + +impl<'a> AsEndMutPtr for &'a mut [u8] { + fn as_end_mut_ptr(self) -> *mut u8 { + unsafe { self.as_mut_ptr().add(self.len()) } + } +} diff --git a/third_party/webrender/peek-poke/src/vec_ext.rs b/third_party/webrender/peek-poke/src/vec_ext.rs new file mode 100644 index 00000000000..42e26032e52 --- /dev/null +++ b/third_party/webrender/peek-poke/src/vec_ext.rs @@ -0,0 +1,26 @@ +// Copyright 2019 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::vec::Vec; + +pub trait VecExt { + type Item; + unsafe fn set_end_ptr(&mut self, end: *const Self::Item); +} + +impl VecExt for Vec { + type Item = T; + unsafe fn set_end_ptr(&mut self, end: *const T) { + assert!(end as usize >= self.as_ptr() as usize); + let new_len = end as usize - self.as_ptr() as usize; + assert!(new_len <= self.capacity()); + self.set_len(new_len); + } +} diff --git a/third_party/webrender/peek-poke/tests/max_size.rs b/third_party/webrender/peek-poke/tests/max_size.rs new file mode 100644 index 00000000000..67cd1ca4a68 --- /dev/null +++ b/third_party/webrender/peek-poke/tests/max_size.rs @@ -0,0 +1,117 @@ +// Copyright 2019 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(dead_code)] + +use peek_poke::{PeekPoke, Poke}; +use std::{marker::PhantomData, mem::size_of}; + +#[test] +fn test_numbers() { + assert_eq!(u8::max_size(), size_of::()); + assert_eq!(u16::max_size(), size_of::()); + assert_eq!(u32::max_size(), size_of::()); + assert_eq!(u64::max_size(), size_of::()); + assert_eq!(usize::max_size(), size_of::()); + assert_eq!(i8::max_size(), size_of::()); + assert_eq!(i16::max_size(), size_of::()); + assert_eq!(i32::max_size(), size_of::()); + assert_eq!(i64::max_size(), size_of::()); + assert_eq!(isize::max_size(), size_of::()); + // floating + assert_eq!(f32::max_size(), size_of::()); + assert_eq!(f64::max_size(), size_of::()); +} + +#[test] +fn test_bool() { + assert_eq!(bool::max_size(), size_of::()); +} + +#[test] +fn test_option() { + assert_eq!( + Option::::max_size(), + ::max_size() + ::max_size() + ); +} + +#[test] +fn test_fixed_size_array() { + assert_eq!(<[u32; 32]>::max_size(), 32 * size_of::()); + assert_eq!(<[u64; 8]>::max_size(), 8 * size_of::()); + assert_eq!(<[u8; 19]>::max_size(), 19 * size_of::()); +} + +#[test] +fn test_tuple() { + assert_eq!(<(isize, )>::max_size(), size_of::()); + assert_eq!(<(isize, isize, isize)>::max_size(), 3 * size_of::()); + assert_eq!(<(isize, ())>::max_size(), size_of::()); +} + +#[test] +fn test_basic_struct() { + #[derive(Debug, PeekPoke)] + struct Bar { + a: u32, + b: u32, + c: u32, + } + + assert_eq!(::max_size(), 3 * ::max_size()); +} + +#[test] +fn test_enum() { + #[derive(Clone, Copy, PeekPoke)] + enum TestEnum { + NoArg, + OneArg(usize), + Args(usize, usize), + AnotherNoArg, + StructLike { x: usize, y: f32 }, + } + assert_eq!( + TestEnum::max_size(), + ::max_size() + 2 * ::max_size() + ); +} + +#[test] +fn test_enum_cstyle() { + #[repr(u32)] + #[derive(Clone, Copy, PeekPoke)] + enum BorderStyle { + None = 0, + Solid = 1, + Double = 2, + Dotted = 3, + Dashed = 4, + Hidden = 5, + Groove = 6, + Ridge = 7, + Inset = 8, + Outset = 9, + } + assert_eq!(BorderStyle::max_size(), ::max_size()); +} + +#[test] +fn test_phantom_data() { + struct Bar; + #[derive(PeekPoke)] + struct Foo { + x: u32, + y: u32, + _marker: PhantomData, + } + assert_eq!(Foo::max_size(), 2 * size_of::()) +} diff --git a/third_party/webrender/peek-poke/tests/round_trip.rs b/third_party/webrender/peek-poke/tests/round_trip.rs new file mode 100644 index 00000000000..3134c207ec4 --- /dev/null +++ b/third_party/webrender/peek-poke/tests/round_trip.rs @@ -0,0 +1,275 @@ +// Copyright 2019 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use peek_poke::{Peek, PeekPoke, Poke}; +use std::{fmt::Debug, marker::PhantomData}; + +fn poke_into(a: &V) -> Vec { + let mut v = >::with_capacity(::max_size()); + let end_ptr = unsafe { a.poke_into(v.as_mut_ptr()) }; + let new_size = end_ptr as usize - v.as_ptr() as usize; + assert!(new_size <= v.capacity()); + unsafe { + v.set_len(new_size); + } + v +} + +#[cfg(not(feature = "option_copy"))] +fn the_same(a: V) +where + V: Debug + Default + PartialEq + Peek + Poke, +{ + let v = poke_into(&a); + let (b, end_ptr) = unsafe { peek_poke::peek_from_default(v.as_ptr()) }; + let size = end_ptr as usize - v.as_ptr() as usize; + assert_eq!(size, v.len()); + assert_eq!(a, b); +} + +#[cfg(feature = "option_copy")] +fn the_same(a: V) +where + V: Copy + Debug + PartialEq + Peek + Poke, +{ + let v = poke_into(&a); + let mut b = a; + let end_ptr = unsafe { b.peek_from(v.as_ptr()) }; + let size = end_ptr as usize - v.as_ptr() as usize; + assert_eq!(size, v.len()); + assert_eq!(a, b); +} + +#[test] +fn test_numbers() { + // unsigned positive + the_same(5u8); + the_same(5u16); + the_same(5u32); + the_same(5u64); + the_same(5usize); + // signed positive + the_same(5i8); + the_same(5i16); + the_same(5i32); + the_same(5i64); + the_same(5isize); + // signed negative + the_same(-5i8); + the_same(-5i16); + the_same(-5i32); + the_same(-5i64); + the_same(-5isize); + // floating + the_same(-100f32); + the_same(0f32); + the_same(5f32); + the_same(-100f64); + the_same(5f64); +} + +#[test] +fn test_bool() { + the_same(true); + the_same(false); +} + +#[cfg(any(feature = "option_copy", feature = "option_default"))] +#[test] +fn test_option() { + the_same(Some(5usize)); + //the_same(Some("foo bar".to_string())); + the_same(None::); +} + +#[test] +fn test_fixed_size_array() { + the_same([24u32; 32]); + the_same([1u64, 2, 3, 4, 5, 6, 7, 8]); + the_same([0u8; 19]); +} + +#[test] +fn test_tuple() { + the_same((1isize, )); + the_same((1isize, 2isize, 3isize)); + the_same((1isize, ())); +} + +#[test] +fn test_basic_struct() { + #[derive(Copy, Clone, Debug, Default, PartialEq, PeekPoke)] + struct Bar { + a: u32, + b: u32, + c: u32, + #[cfg(any(feature = "option_copy", feature = "option_default"))] + d: Option, + } + + the_same(Bar { + a: 2, + b: 4, + c: 42, + #[cfg(any(feature = "option_copy", feature = "option_default"))] + d: None, + }); +} + +#[test] +fn test_enum() { + #[derive(Clone, Copy, Debug, PartialEq, PeekPoke)] + enum TestEnum { + NoArg, + OneArg(usize), + Args(usize, usize), + AnotherNoArg, + StructLike { x: usize, y: f32 }, + } + + impl Default for TestEnum { + fn default() -> Self { + TestEnum::NoArg + } + } + + the_same(TestEnum::NoArg); + the_same(TestEnum::OneArg(4)); + the_same(TestEnum::Args(4, 5)); + the_same(TestEnum::AnotherNoArg); + the_same(TestEnum::StructLike { x: 4, y: 3.14159 }); +} + +#[test] +fn test_enum_cstyle() { + #[repr(u32)] + #[derive(Clone, Copy, Debug, PartialEq, Eq, PeekPoke)] + enum BorderStyle { + None = 0, + Solid = 1, + Double = 2, + Dotted = 3, + Dashed = 4, + Hidden = 5, + Groove = 6, + Ridge = 7, + Inset = 8, + Outset = 9, + } + + impl Default for BorderStyle { + fn default() -> Self { + BorderStyle::None + } + } + + the_same(BorderStyle::None); + the_same(BorderStyle::Solid); + the_same(BorderStyle::Double); + the_same(BorderStyle::Dotted); + the_same(BorderStyle::Dashed); + the_same(BorderStyle::Hidden); + the_same(BorderStyle::Groove); + the_same(BorderStyle::Ridge); + the_same(BorderStyle::Inset); + the_same(BorderStyle::Outset); +} + +#[test] +fn test_phantom_data() { + struct Bar; + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PeekPoke)] + struct Foo { + x: u32, + y: u32, + _marker: PhantomData, + } + the_same(Foo { + x: 19, + y: 42, + _marker: PhantomData, + }); +} + +#[test] +fn test_generic() { + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PeekPoke)] + struct Foo { + x: T, + y: T, + } + the_same(Foo { x: 19.0, y: 42.0 }); +} + +#[test] +fn test_generic_enum() { + #[derive(Clone, Copy, Debug, Default, PartialEq, PeekPoke)] + pub struct PropertyBindingKey { + pub id: usize, + _phantom: PhantomData, + } + + #[derive(Clone, Copy, Debug, PartialEq, PeekPoke)] + pub enum PropertyBinding { + Value(T), + Binding(PropertyBindingKey, T), + } + + impl Default for PropertyBinding { + fn default() -> Self { + PropertyBinding::Value(Default::default()) + } + } +} + +#[cfg(all(feature = "extras", feature = "option_copy"))] +mod extra_tests { + use super::*; + use euclid::{Point2D, Rect, SideOffsets2D, Size2D, Transform3D, Vector2D}; + use std::mem::size_of; + + #[test] + fn euclid_types() { + the_same(Point2D::::new(1.0, 2.0)); + assert_eq!(Point2D::::max_size(), 2 * size_of::()); + + the_same(Rect::::new( + Point2D::::new(0.0, 0.0), + Size2D::::new(100.0, 80.0), + )); + assert_eq!(Rect::::max_size(), 4 * size_of::()); + + the_same(SideOffsets2D::::new(0.0, 10.0, -1.0, -10.0)); + assert_eq!(SideOffsets2D::::max_size(), 4 * size_of::()); + + the_same(Transform3D::::identity()); + assert_eq!(Transform3D::::max_size(), 16 * size_of::()); + + the_same(Vector2D::::new(1.0, 2.0)); + assert_eq!(Vector2D::::max_size(), 2 * size_of::()); + } + + #[test] + fn webrender_api_types() { + type PipelineSourceId = i32; + #[derive(Clone, Copy, Debug, PartialEq, PeekPoke)] + struct PipelineId(pub PipelineSourceId, pub u32); + + #[derive(Clone, Copy, Debug, PartialEq, PeekPoke)] + struct ClipChainId(pub u64, pub PipelineId); + + #[derive(Clone, Copy, Debug, PartialEq, PeekPoke)] + struct SpatialId(pub usize, pub PipelineId); + + the_same(PipelineId(42, 2)); + the_same(ClipChainId(19u64, PipelineId(42, 2))); + the_same(SpatialId(19usize, PipelineId(42, 2))); + } +} diff --git a/third_party/webrender/rustfmt.toml b/third_party/webrender/rustfmt.toml new file mode 100644 index 00000000000..d43a44fcbd2 --- /dev/null +++ b/third_party/webrender/rustfmt.toml @@ -0,0 +1,6 @@ +reorder_imports = false +reorder_imports_in_group = true +reorder_imported_names = true +error_on_line_overflow_comments = false +max_width = 100 +spaces_around_ranges = true diff --git a/third_party/webrender/servo-tidy.toml b/third_party/webrender/servo-tidy.toml new file mode 100644 index 00000000000..15c2b7bae39 --- /dev/null +++ b/third_party/webrender/servo-tidy.toml @@ -0,0 +1,39 @@ +[configs] +skip-check-length = false +skip-check-licenses = false +check-alphabetical-order = false + +[ignore] +# Ignored packages with duplicated versions +packages = [ + "core-foundation", + "core-foundation-sys", + "core-graphics", + "gl_generator", + "gleam", + "rand", + "rand_core", + # https://github.com/trimental/andrew/issues/5 + "rusttype", + # https://bugzilla.mozilla.org/show_bug.cgi?id=1615148 + "smallvec", + "winapi", + "yaml-rust", + # These are tracked in bug 1587468, see there for pending work. + "proc-macro2", + "quote", + "unicode-xid", +] + +# Files that are ignored for all tidy and lint checks. +files = [ + "./wrench/src/egl.rs", # Copied from glutin +] + +# Many directories are currently ignored while we tidy things up +# gradually. +directories = [ + # Generated and upstream code combined with our own. Could use cleanup + "./target", + "./webrender/src", +] diff --git a/third_party/webrender/swgl/Cargo.toml b/third_party/webrender/swgl/Cargo.toml new file mode 100644 index 00000000000..bc5a04b0a34 --- /dev/null +++ b/third_party/webrender/swgl/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "swgl" +version = "0.1.0" +license = "MPL-2.0" +authors = ["The Mozilla Project Developers"] +build = "build.rs" +description = "Software OpenGL implementation for WebRender." + +[build-dependencies] +cc = "1.0.46" +glsl-to-cxx = { path = "../glsl-to-cxx" } +webrender_build = { path = "../webrender_build" } + +[dependencies] +gleam = "0.12.0" diff --git a/third_party/webrender/swgl/README.md b/third_party/webrender/swgl/README.md new file mode 100644 index 00000000000..158a4157930 --- /dev/null +++ b/third_party/webrender/swgl/README.md @@ -0,0 +1,4 @@ +swgl +======== + +Software OpenGL implementation for WebRender diff --git a/third_party/webrender/swgl/build.rs b/third_party/webrender/swgl/build.rs new file mode 100644 index 00000000000..1de3568aa07 --- /dev/null +++ b/third_party/webrender/swgl/build.rs @@ -0,0 +1,150 @@ +/* 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/. */ + +extern crate cc; +extern crate glsl_to_cxx; +extern crate webrender_build; + +use std::collections::HashSet; +use std::fmt::Write; +use webrender_build::shader::{ShaderFeatureFlags, get_shader_features}; + +// Shader key is in "name feature,feature" format. +// File name needs to be formatted as "name_feature_feature". +fn shader_file(shader_key: &str) -> String { + shader_key.replace(' ', "_").replace(',', "_") +} + +fn write_load_shader(shader_keys: &[String]) { + let mut load_shader = String::new(); + for s in shader_keys { + let _ = write!(load_shader, "#include \"{}.h\"\n", shader_file(s)); + } + load_shader.push_str("ProgramLoader load_shader(const char* name) {\n"); + for s in shader_keys { + let _ = write!(load_shader, " if (!strcmp(name, \"{}\")) {{ return {}_program::loader; }}\n", + s, shader_file(s)); + } + load_shader.push_str(" return nullptr;\n}\n"); + std::fs::write(std::env::var("OUT_DIR").unwrap() + "/load_shader.h", load_shader).unwrap(); +} + +fn process_imports(shader_dir: &str, shader: &str, included: &mut HashSet, output: &mut String) { + if !included.insert(shader.into()) { + return; + } + println!("cargo:rerun-if-changed={}/{}.glsl", shader_dir, shader); + let source = std::fs::read_to_string(format!("{}/{}.glsl", shader_dir, shader)).unwrap(); + for line in source.lines() { + if line.starts_with("#include ") { + let imports = line["#include ".len() ..].split(','); + for import in imports { + process_imports(shader_dir, import, included, output); + } + } else if line.starts_with("#version ") || line.starts_with("#extension ") { + // ignore + } else { + output.push_str(line); + output.push('\n'); + } + } +} + +fn translate_shader(shader_key: &str, shader_dir: &str) { + let mut imported = String::from("#define SWGL 1\n"); + let _ = write!(imported, "#define WR_MAX_VERTEX_TEXTURE_WIDTH {}U\n", + webrender_build::MAX_VERTEX_TEXTURE_WIDTH); + + let (basename, features) = + shader_key.split_at(shader_key.find(' ').unwrap_or(shader_key.len())); + if !features.is_empty() { + for feature in features.trim().split(',') { + let _ = write!(imported, "#define WR_FEATURE_{}\n", feature); + } + } + + process_imports(shader_dir, basename, &mut HashSet::new(), &mut imported); + + let shader = shader_file(shader_key); + + let out_dir = std::env::var("OUT_DIR").unwrap(); + let imp_name = format!("{}/{}.c", out_dir, shader); + std::fs::write(&imp_name, imported).unwrap(); + + let mut build = cc::Build::new(); + if build.get_compiler().is_like_msvc() { + build.flag("/EP"); + } else { + build.flag("-xc").flag("-P"); + } + build.file(&imp_name); + let vs = build.clone() + .define("WR_VERTEX_SHADER", Some("1")) + .expand(); + let fs = build.clone() + .define("WR_FRAGMENT_SHADER", Some("1")) + .expand(); + let vs_name = format!("{}/{}.vert", out_dir, shader); + let fs_name = format!("{}/{}.frag", out_dir, shader); + std::fs::write(&vs_name, vs).unwrap(); + std::fs::write(&fs_name, fs).unwrap(); + + let mut args = vec![ + "glsl_to_cxx".to_string(), + vs_name, + fs_name, + ]; + let frag_include = format!("{}/{}.frag.h", shader_dir, shader); + if std::path::Path::new(&frag_include).exists() { + println!("cargo:rerun-if-changed={}/{}.frag.h", shader_dir, shader); + args.push(frag_include); + } + let result = glsl_to_cxx::translate(&mut args.into_iter()); + std::fs::write(format!("{}/{}.h", out_dir, shader), result).unwrap(); +} + +fn main() { + let shader_dir = match std::env::var("MOZ_SRC") { + Ok(dir) => dir + "/gfx/wr/webrender/res", + Err(_) => std::env::var("CARGO_MANIFEST_DIR").unwrap() + "/../webrender/res", + }; + + let shader_flags = + ShaderFeatureFlags::GL | + ShaderFeatureFlags::DUAL_SOURCE_BLENDING; + let mut shaders: Vec = Vec::new(); + for (name, features) in get_shader_features(shader_flags) { + shaders.extend(features.iter().map(|f| { + if f.is_empty() { name.to_owned() } else { format!("{} {}", name, f) } + })); + } + + shaders.sort(); + + for shader in &shaders { + translate_shader(shader, &shader_dir); + } + + write_load_shader(&shaders); + + println!("cargo:rerun-if-changed=src/gl_defs.h"); + println!("cargo:rerun-if-changed=src/glsl.h"); + println!("cargo:rerun-if-changed=src/program.h"); + println!("cargo:rerun-if-changed=src/texture.h"); + println!("cargo:rerun-if-changed=src/vector_type.h"); + println!("cargo:rerun-if-changed=src/gl.cc"); + cc::Build::new() + .cpp(true) + .file("src/gl.cc") + .flag("-std=c++14") + .flag("-UMOZILLA_CONFIG_H") + .flag("-fno-exceptions") + .flag("-fno-rtti") + .flag("-fno-math-errno") + .define("_GLIBCXX_USE_CXX11_ABI", Some("0")) + .include(shader_dir) + .include("src") + .include(std::env::var("OUT_DIR").unwrap()) + .compile("gl_cc"); +} diff --git a/third_party/webrender/swgl/src/gl.cc b/third_party/webrender/swgl/src/gl.cc new file mode 100644 index 00000000000..f4a69752dde --- /dev/null +++ b/third_party/webrender/swgl/src/gl.cc @@ -0,0 +1,4015 @@ +/* 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/. */ + +#include +#include +#include +#include +#include +#include + +#ifdef __MACH__ +# include +# include +#else +# include +#endif + +#ifdef NDEBUG +# define debugf(...) +#else +# define debugf(...) printf(__VA_ARGS__) +#endif + +#ifdef _WIN32 +# define ALWAYS_INLINE __forceinline +#else +# define ALWAYS_INLINE __attribute__((always_inline)) inline +#endif + +#define UNREACHABLE __builtin_unreachable() + +#define UNUSED __attribute__((unused)) + +#ifdef MOZILLA_CLIENT +# define IMPLICIT __attribute__((annotate("moz_implicit"))) +#else +# define IMPLICIT +#endif + +#include "gl_defs.h" +#include "glsl.h" +#include "program.h" + +using namespace glsl; + +struct IntRect { + int x0; + int y0; + int x1; + int y1; + + int width() const { return x1 - x0; } + int height() const { return y1 - y0; } + bool is_empty() const { return width() <= 0 || height() <= 0; } + + bool same_size(const IntRect& o) const { + return width() == o.width() && height() == o.height(); + } + + bool contains(const IntRect& o) const { + return o.x0 >= x0 && o.y0 >= y0 && o.x1 <= x1 && o.y1 <= y1; + } + + IntRect& intersect(const IntRect& o) { + x0 = max(x0, o.x0); + y0 = max(y0, o.y0); + x1 = min(x1, o.x1); + y1 = min(y1, o.y1); + return *this; + } + + // Scale from source-space to dest-space, optionally rounding inward + IntRect& scale(int srcWidth, int srcHeight, int dstWidth, int dstHeight, + bool roundIn = false) { + x0 = (x0 * dstWidth + (roundIn ? srcWidth - 1 : 0)) / srcWidth; + y0 = (y0 * dstHeight + (roundIn ? srcHeight - 1 : 0)) / srcHeight; + x1 = (x1 * dstWidth) / srcWidth; + y1 = (y1 * dstHeight) / srcHeight; + return *this; + } + + // Flip the rect's Y coords around inflection point at Y=offset + void invert_y(int offset) { + y0 = offset - y0; + y1 = offset - y1; + swap(y0, y1); + } + + IntRect& offset(int dx, int dy) { + x0 += dx; + y0 += dy; + x1 += dx; + y1 += dy; + return *this; + } +}; + +struct VertexAttrib { + size_t size = 0; // in bytes + GLenum type = 0; + bool normalized = false; + GLsizei stride = 0; + GLuint offset = 0; + bool enabled = false; + GLuint divisor = 0; + int vertex_array = 0; + int vertex_buffer = 0; + char* buf = nullptr; // XXX: this can easily dangle + size_t buf_size = 0; // this will let us bounds check +}; + +static int bytes_for_internal_format(GLenum internal_format) { + switch (internal_format) { + case GL_RGBA32F: + return 4 * 4; + case GL_RGBA32I: + return 4 * 4; + case GL_RGBA8: + case GL_BGRA8: + case GL_RGBA: + return 4; + case GL_R8: + case GL_RED: + return 1; + case GL_DEPTH_COMPONENT: + case GL_DEPTH_COMPONENT16: + return 2; + case GL_DEPTH_COMPONENT24: + case GL_DEPTH_COMPONENT32: + return 4; + default: + debugf("internal format: %x\n", internal_format); + assert(0); + return 0; + } +} + +static inline int aligned_stride(int row_bytes) { return (row_bytes + 3) & ~3; } + +static TextureFormat gl_format_to_texture_format(int type) { + switch (type) { + case GL_RGBA32F: + return TextureFormat::RGBA32F; + case GL_RGBA32I: + return TextureFormat::RGBA32I; + case GL_RGBA8: + return TextureFormat::RGBA8; + case GL_R8: + return TextureFormat::R8; + default: + assert(0); + return TextureFormat::RGBA8; + } +} + +struct Query { + uint64_t value = 0; +}; + +struct Buffer { + char* buf = nullptr; + size_t size = 0; + + bool allocate(size_t new_size) { + if (new_size != size) { + char* new_buf = (char*)realloc(buf, new_size); + assert(new_buf); + if (new_buf) { + buf = new_buf; + size = new_size; + return true; + } + cleanup(); + } + return false; + } + + void cleanup() { + if (buf) { + free(buf); + buf = nullptr; + size = 0; + } + } + + ~Buffer() { cleanup(); } +}; + +struct Framebuffer { + GLuint color_attachment = 0; + GLint layer = 0; + GLuint depth_attachment = 0; +}; + +struct Renderbuffer { + GLuint texture = 0; + + void on_erase(); +}; + +TextureFilter gl_filter_to_texture_filter(int type) { + switch (type) { + case GL_NEAREST: + return TextureFilter::NEAREST; + case GL_NEAREST_MIPMAP_LINEAR: + return TextureFilter::NEAREST; + case GL_NEAREST_MIPMAP_NEAREST: + return TextureFilter::NEAREST; + case GL_LINEAR: + return TextureFilter::LINEAR; + case GL_LINEAR_MIPMAP_LINEAR: + return TextureFilter::LINEAR; + case GL_LINEAR_MIPMAP_NEAREST: + return TextureFilter::LINEAR; + default: + assert(0); + return TextureFilter::NEAREST; + } +} + +struct Texture { + GLenum internal_format = 0; + int width = 0; + int height = 0; + int depth = 0; + char* buf = nullptr; + size_t buf_size = 0; + GLenum min_filter = GL_NEAREST; + GLenum mag_filter = GL_LINEAR; + + enum FLAGS { + SHOULD_FREE = 1 << 1, + }; + int flags = SHOULD_FREE; + bool should_free() const { return bool(flags & SHOULD_FREE); } + + void set_flag(int flag, bool val) { + if (val) { + flags |= flag; + } else { + flags &= ~flag; + } + } + void set_should_free(bool val) { set_flag(SHOULD_FREE, val); } + + // Delayed-clearing state. When a clear of an FB is requested, we don't + // immediately clear each row, as the rows may be subsequently overwritten + // by draw calls, allowing us to skip the work of clearing the affected rows + // either fully or partially. Instead, we keep a bit vector of rows that need + // to be cleared later and save the value they need to be cleared with so + // that we can clear these rows individually when they are touched by draws. + // This currently only works for 2D textures, but not on texture arrays. + int delay_clear = 0; + uint32_t clear_val = 0; + uint32_t* cleared_rows = nullptr; + + void enable_delayed_clear(uint32_t val) { + delay_clear = height; + clear_val = val; + if (!cleared_rows) { + cleared_rows = new uint32_t[(height + 31) / 32]; + } + memset(cleared_rows, 0, ((height + 31) / 32) * sizeof(uint32_t)); + if (height & 31) { + cleared_rows[height / 32] = ~0U << (height & 31); + } + } + + void disable_delayed_clear() { + if (cleared_rows) { + delete[] cleared_rows; + cleared_rows = nullptr; + delay_clear = 0; + } + } + + int bpp() const { return bytes_for_internal_format(internal_format); } + + size_t stride(int b = 0, int min_width = 0) const { + return aligned_stride((b ? b : bpp()) * max(width, min_width)); + } + + size_t layer_stride(int b = 0, int min_width = 0, int min_height = 0) const { + return stride(b ? b : bpp(), min_width) * max(height, min_height); + } + + bool allocate(bool force = false, int min_width = 0, int min_height = 0) { + if ((!buf || force) && should_free()) { + size_t size = layer_stride(bpp(), min_width, min_height) * max(depth, 1); + if (!buf || size > buf_size) { + // Allocate with a SIMD register-sized tail of padding at the end so we + // can safely read or write past the end of the texture with SIMD ops. + char* new_buf = (char*)realloc(buf, size + sizeof(Float)); + assert(new_buf); + if (new_buf) { + buf = new_buf; + buf_size = size; + return true; + } + cleanup(); + } + } + return false; + } + + void cleanup() { + if (buf && should_free()) { + free(buf); + buf = nullptr; + buf_size = 0; + } + disable_delayed_clear(); + } + + ~Texture() { cleanup(); } + + IntRect bounds() const { return IntRect{0, 0, width, height}; } + + // Find the valid sampling bounds relative to the requested region + IntRect sample_bounds(const IntRect& req, bool invertY = false) const { + IntRect bb = bounds().intersect(req).offset(-req.x0, -req.y0); + if (invertY) bb.invert_y(req.height()); + return bb; + } + + // Get a pointer for sampling at the given offset + char* sample_ptr(int x, int y, int z, int bpp, size_t stride) const { + return buf + (height * z + y) * stride + x * bpp; + } + + char* sample_ptr(int x, int y, int z, int bpp) const { + return sample_ptr(x, y, z, bpp, stride(bpp)); + } + + char* sample_ptr(int x, int y, int z) const { + return sample_ptr(x, y, z, bpp()); + } + + // Get a pointer for sampling the requested region and limit to the provided + // sampling bounds + char* sample_ptr(const IntRect& req, const IntRect& bounds, int z, + bool invertY = false) const { + // Offset the sample pointer by the clamped bounds + int x = req.x0 + bounds.x0; + // Invert the Y offset if necessary + int y = invertY ? req.y1 - 1 - bounds.y0 : req.y0 + bounds.y0; + return sample_ptr(x, y, z); + } +}; + +#define MAX_ATTRIBS 16 +#define NULL_ATTRIB 15 +struct VertexArray { + VertexAttrib attribs[MAX_ATTRIBS]; + int max_attrib = -1; + + void validate(); +}; + +struct Shader { + GLenum type = 0; + ProgramLoader loader = nullptr; +}; + +struct Program { + ProgramImpl* impl = nullptr; + VertexShaderImpl* vert_impl = nullptr; + FragmentShaderImpl* frag_impl = nullptr; + bool deleted = false; + + ~Program() { + delete impl; + } +}; + +// for GL defines to fully expand +#define CONCAT_KEY(prefix, x, y, z, w, ...) prefix##x##y##z##w +#define BLEND_KEY(...) CONCAT_KEY(BLEND_, __VA_ARGS__, 0, 0) +#define FOR_EACH_BLEND_KEY(macro) \ + macro(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE) \ + macro(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, 0, 0) \ + macro(GL_ZERO, GL_ONE_MINUS_SRC_COLOR, 0, 0) \ + macro(GL_ZERO, GL_ONE_MINUS_SRC_COLOR, GL_ZERO, GL_ONE) \ + macro(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA, 0, 0) macro( \ + GL_ZERO, GL_SRC_COLOR, 0, 0) macro(GL_ONE, GL_ONE, 0, 0) \ + macro(GL_ONE, GL_ONE, GL_ONE, GL_ONE_MINUS_SRC_ALPHA) \ + macro(GL_ONE, GL_ZERO, 0, 0) macro( \ + GL_ONE_MINUS_DST_ALPHA, GL_ONE, GL_ZERO, GL_ONE) \ + macro(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR, \ + 0, 0) \ + macro(GL_ONE, GL_ONE_MINUS_SRC1_COLOR, 0, 0) + +#define DEFINE_BLEND_KEY(...) BLEND_KEY(__VA_ARGS__), +enum BlendKey : uint8_t { + BLEND_KEY_NONE = 0, + FOR_EACH_BLEND_KEY(DEFINE_BLEND_KEY) +}; + +const size_t MAX_TEXTURE_UNITS = 16; + +template +static inline bool unlink(T& binding, T n) { + if (binding == n) { + binding = 0; + return true; + } + return false; +} + +template +struct ObjectStore { + O** objects = nullptr; + size_t size = 0; + // reserve object 0 as null + size_t first_free = 1; + O invalid; + + ~ObjectStore() { + if (objects) { + for (size_t i = 0; i < size; i++) delete objects[i]; + free(objects); + } + } + + bool grow(size_t i) { + size_t new_size = size ? size : 8; + while (new_size <= i) new_size += new_size / 2; + O** new_objects = (O**)realloc(objects, new_size * sizeof(O*)); + assert(new_objects); + if (!new_objects) return false; + while (size < new_size) new_objects[size++] = nullptr; + objects = new_objects; + return true; + } + + void insert(size_t i, const O& o) { + if (i >= size && !grow(i)) return; + if (!objects[i]) objects[i] = new O(o); + } + + size_t next_free() { + size_t i = first_free; + while (i < size && objects[i]) i++; + first_free = i; + return i; + } + + size_t insert(const O& o = O()) { + size_t i = next_free(); + insert(i, o); + return i; + } + + O& operator[](size_t i) { + insert(i, O()); + return i < size ? *objects[i] : invalid; + } + + O* find(size_t i) const { return i < size ? objects[i] : nullptr; } + + template void on_erase(T*, ...) {} + template void on_erase(T* o, decltype(&T::on_erase)) { + o->on_erase(); + } + + bool erase(size_t i) { + if (i < size && objects[i]) { + on_erase(objects[i], nullptr); + delete objects[i]; + objects[i] = nullptr; + if (i < first_free) first_free = i; + return true; + } + return false; + } + + O** begin() const { return objects; } + O** end() const { return &objects[size]; } +}; + +struct Context { + ObjectStore queries; + ObjectStore buffers; + ObjectStore textures; + ObjectStore vertex_arrays; + ObjectStore framebuffers; + ObjectStore renderbuffers; + ObjectStore shaders; + ObjectStore programs; + + IntRect viewport = {0, 0, 0, 0}; + + bool blend = false; + GLenum blendfunc_srgb = GL_ONE; + GLenum blendfunc_drgb = GL_ZERO; + GLenum blendfunc_sa = GL_ONE; + GLenum blendfunc_da = GL_ZERO; + GLenum blend_equation = GL_FUNC_ADD; + V8 blendcolor = 0; + BlendKey blend_key = BLEND_KEY_NONE; + + bool depthtest = false; + bool depthmask = true; + GLenum depthfunc = GL_LESS; + + bool scissortest = false; + IntRect scissor = {0, 0, 0, 0}; + + uint32_t clearcolor = 0; + GLdouble cleardepth = 1; + + int unpack_row_length = 0; + + int shaded_rows = 0; + int shaded_pixels = 0; + + struct TextureUnit { + GLuint texture_2d_binding = 0; + GLuint texture_3d_binding = 0; + GLuint texture_2d_array_binding = 0; + GLuint texture_rectangle_binding = 0; + + void unlink(GLuint n) { + ::unlink(texture_2d_binding, n); + ::unlink(texture_3d_binding, n); + ::unlink(texture_2d_array_binding, n); + ::unlink(texture_rectangle_binding, n); + } + }; + TextureUnit texture_units[MAX_TEXTURE_UNITS]; + int active_texture_unit = 0; + + GLuint current_program = 0; + + GLuint current_vertex_array = 0; + bool validate_vertex_array = true; + + GLuint pixel_pack_buffer_binding = 0; + GLuint pixel_unpack_buffer_binding = 0; + GLuint array_buffer_binding = 0; + GLuint element_array_buffer_binding = 0; + GLuint time_elapsed_query = 0; + GLuint samples_passed_query = 0; + GLuint renderbuffer_binding = 0; + GLuint draw_framebuffer_binding = 0; + GLuint read_framebuffer_binding = 0; + GLuint unknown_binding = 0; + + GLuint& get_binding(GLenum name) { + switch (name) { + case GL_PIXEL_PACK_BUFFER: + return pixel_pack_buffer_binding; + case GL_PIXEL_UNPACK_BUFFER: + return pixel_unpack_buffer_binding; + case GL_ARRAY_BUFFER: + return array_buffer_binding; + case GL_ELEMENT_ARRAY_BUFFER: + return element_array_buffer_binding; + case GL_TEXTURE_2D: + return texture_units[active_texture_unit].texture_2d_binding; + case GL_TEXTURE_2D_ARRAY: + return texture_units[active_texture_unit].texture_2d_array_binding; + case GL_TEXTURE_3D: + return texture_units[active_texture_unit].texture_3d_binding; + case GL_TEXTURE_RECTANGLE: + return texture_units[active_texture_unit].texture_rectangle_binding; + case GL_TIME_ELAPSED: + return time_elapsed_query; + case GL_SAMPLES_PASSED: + return samples_passed_query; + case GL_RENDERBUFFER: + return renderbuffer_binding; + case GL_DRAW_FRAMEBUFFER: + return draw_framebuffer_binding; + case GL_READ_FRAMEBUFFER: + return read_framebuffer_binding; + default: + debugf("unknown binding %x\n", name); + assert(false); + return unknown_binding; + } + } + + Texture& get_texture(sampler2D, int unit) { + return textures[texture_units[unit].texture_2d_binding]; + } + + Texture& get_texture(isampler2D, int unit) { + return textures[texture_units[unit].texture_2d_binding]; + } + + Texture& get_texture(sampler2DArray, int unit) { + return textures[texture_units[unit].texture_2d_array_binding]; + } + + Texture& get_texture(sampler2DRect, int unit) { + return textures[texture_units[unit].texture_rectangle_binding]; + } + + IntRect apply_scissor(IntRect bb) const { + return scissortest ? bb.intersect(scissor) : bb; + } +}; +static Context* ctx = nullptr; +static VertexShaderImpl* vertex_shader = nullptr; +static FragmentShaderImpl* fragment_shader = nullptr; +static BlendKey blend_key = BLEND_KEY_NONE; + +static void prepare_texture(Texture& t, const IntRect* skip = nullptr); + +template +static inline void init_depth(S* s, Texture& t) { + s->depth = max(t.depth, 1); + s->height_stride = s->stride * t.height; +} + +template +static inline void init_filter(S* s, Texture& t) { + s->filter = gl_filter_to_texture_filter(t.mag_filter); +} + +template +static inline void init_sampler(S* s, Texture& t) { + prepare_texture(t); + s->width = t.width; + s->height = t.height; + int bpp = t.bpp(); + s->stride = t.stride(bpp); + if (bpp >= 4) s->stride /= 4; + // Use uint32_t* for easier sampling, but need to cast to uint8_t* for formats + // with bpp < 4. + s->buf = (uint32_t*)t.buf; + s->format = gl_format_to_texture_format(t.internal_format); +} + +template +S* lookup_sampler(S* s, int texture) { + Texture& t = ctx->get_texture(s, texture); + if (!t.buf) { + *s = S(); + } else { + init_sampler(s, t); + init_filter(s, t); + } + return s; +} + +template +S* lookup_isampler(S* s, int texture) { + Texture& t = ctx->get_texture(s, texture); + if (!t.buf) { + *s = S(); + } else { + init_sampler(s, t); + } + return s; +} + +template +S* lookup_sampler_array(S* s, int texture) { + Texture& t = ctx->get_texture(s, texture); + if (!t.buf) { + *s = S(); + } else { + init_sampler(s, t); + init_depth(s, t); + init_filter(s, t); + } + return s; +} + +int bytes_per_type(GLenum type) { + switch (type) { + case GL_INT: + return 4; + case GL_FLOAT: + return 4; + case GL_UNSIGNED_SHORT: + return 2; + case GL_UNSIGNED_BYTE: + return 1; + default: + assert(0); + return 0; + } +} + +template +static inline S expand_attrib(const char* buf, size_t size, bool normalized) { + typedef typename ElementType::ty elem_type; + S scalar = {0}; + const C* src = reinterpret_cast(buf); + if (normalized) { + const float scale = 1.0f / ((1 << (8 * sizeof(C))) - 1); + for (size_t i = 0; i < size / sizeof(C); i++) { + put_nth_component(scalar, i, elem_type(src[i]) * scale); + } + } else { + for (size_t i = 0; i < size / sizeof(C); i++) { + put_nth_component(scalar, i, elem_type(src[i])); + } + } + return scalar; +} + +template +static inline S load_attrib_scalar(VertexAttrib& va, const char* src) { + if (sizeof(S) <= va.size) { + return *reinterpret_cast(src); + } + if (va.type == GL_UNSIGNED_SHORT) { + return expand_attrib(src, va.size, va.normalized); + } + if (va.type == GL_UNSIGNED_BYTE) { + return expand_attrib(src, va.size, va.normalized); + } + assert(sizeof(typename ElementType::ty) == bytes_per_type(va.type)); + S scalar = {0}; + memcpy(&scalar, src, va.size); + return scalar; +} + +template +void load_attrib(T& attrib, VertexAttrib& va, uint32_t start, int instance, + int count) { + typedef decltype(force_scalar(attrib)) scalar_type; + if (!va.enabled) { + attrib = T(scalar_type{0}); + } else if (va.divisor != 0) { + char* src = (char*)va.buf + va.stride * instance + va.offset; + assert(src + va.size <= va.buf + va.buf_size); + attrib = T(load_attrib_scalar(va, src)); + } else { + // Specialized for WR's primitive vertex order/winding. + // Triangles must be indexed at offsets 0, 1, 2. + // Quads must be successive triangles indexed at offsets 0, 1, 2, 2, 1, 3. + // Triangle vertexes fill vertex shader SIMD lanes as 0, 1, 2, 2. + // Quad vertexes fill vertex shader SIMD lanes as 0, 1, 3, 2, so that the + // points form a convex path that can be traversed by the rasterizer. + if (!count) return; + assert(count == 3 || count == 4); + char* src = (char*)va.buf + va.stride * start + va.offset; + attrib = (T){ + load_attrib_scalar(va, src), + load_attrib_scalar(va, src + va.stride), + load_attrib_scalar(va, src + va.stride * 2 + + (count > 3 ? va.stride : 0)), + load_attrib_scalar(va, src + va.stride * 2) + }; + } +} + +template +void load_flat_attrib(T& attrib, VertexAttrib& va, uint32_t start, int instance, + int count) { + typedef decltype(force_scalar(attrib)) scalar_type; + if (!va.enabled) { + attrib = T{0}; + return; + } + char* src = nullptr; + if (va.divisor != 0) { + src = (char*)va.buf + va.stride * instance + va.offset; + } else { + if (!count) return; + src = (char*)va.buf + va.stride * start + va.offset; + } + assert(src + va.size <= va.buf + va.buf_size); + attrib = T(load_attrib_scalar(va, src)); +} + +void setup_program(GLuint program) { + if (!program) { + vertex_shader = nullptr; + fragment_shader = nullptr; + return; + } + Program& p = ctx->programs[program]; + assert(p.impl); + assert(p.vert_impl); + assert(p.frag_impl); + vertex_shader = p.vert_impl; + fragment_shader = p.frag_impl; +} + +extern ProgramLoader load_shader(const char* name); + +extern "C" { + +void UseProgram(GLuint program) { + if (ctx->current_program && program != ctx->current_program) { + auto* p = ctx->programs.find(ctx->current_program); + if (p && p->deleted) { + ctx->programs.erase(ctx->current_program); + } + } + ctx->current_program = program; + setup_program(program); +} + +void SetViewport(GLint x, GLint y, GLsizei width, GLsizei height) { + ctx->viewport = IntRect{x, y, x + width, y + height}; +} + +void Enable(GLenum cap) { + switch (cap) { + case GL_BLEND: + ctx->blend = true; + blend_key = ctx->blend_key; + break; + case GL_DEPTH_TEST: + ctx->depthtest = true; + break; + case GL_SCISSOR_TEST: + ctx->scissortest = true; + break; + } +} + +void Disable(GLenum cap) { + switch (cap) { + case GL_BLEND: + ctx->blend = false; + blend_key = BLEND_KEY_NONE; + break; + case GL_DEPTH_TEST: + ctx->depthtest = false; + break; + case GL_SCISSOR_TEST: + ctx->scissortest = false; + break; + } +} + +GLenum GetError() { return GL_NO_ERROR; } + +static const char* const extensions[] = { + "GL_ARB_blend_func_extended", "GL_ARB_copy_image", + "GL_ARB_draw_instanced", "GL_ARB_explicit_attrib_location", + "GL_ARB_instanced_arrays", "GL_ARB_invalidate_subdata", + "GL_ARB_texture_storage", "GL_EXT_timer_query", +}; + +void GetIntegerv(GLenum pname, GLint* params) { + assert(params); + switch (pname) { + case GL_MAX_TEXTURE_UNITS: + case GL_MAX_TEXTURE_IMAGE_UNITS: + params[0] = MAX_TEXTURE_UNITS; + break; + case GL_MAX_TEXTURE_SIZE: + params[0] = 1 << 15; + break; + case GL_MAX_ARRAY_TEXTURE_LAYERS: + params[0] = 1 << 15; + break; + case GL_READ_FRAMEBUFFER_BINDING: + params[0] = ctx->read_framebuffer_binding; + break; + case GL_DRAW_FRAMEBUFFER_BINDING: + params[0] = ctx->draw_framebuffer_binding; + break; + case GL_PIXEL_PACK_BUFFER_BINDING: + params[0] = ctx->pixel_pack_buffer_binding; + break; + case GL_PIXEL_UNPACK_BUFFER_BINDING: + params[0] = ctx->pixel_unpack_buffer_binding; + break; + case GL_NUM_EXTENSIONS: + params[0] = sizeof(extensions) / sizeof(extensions[0]); + break; + default: + debugf("unhandled glGetIntegerv parameter %x\n", pname); + assert(false); + } +} + +void GetBooleanv(GLenum pname, GLboolean* params) { + assert(params); + switch (pname) { + case GL_DEPTH_WRITEMASK: + params[0] = ctx->depthmask; + break; + default: + debugf("unhandled glGetBooleanv parameter %x\n", pname); + assert(false); + } +} + +const char* GetString(GLenum name) { + switch (name) { + case GL_VENDOR: + return "Mozilla Gfx"; + case GL_RENDERER: + return "Software WebRender"; + case GL_VERSION: + return "3.2"; + default: + debugf("unhandled glGetString parameter %x\n", name); + assert(false); + return nullptr; + } +} + +const char* GetStringi(GLenum name, GLuint index) { + switch (name) { + case GL_EXTENSIONS: + if (index >= sizeof(extensions) / sizeof(extensions[0])) { + return nullptr; + } + return extensions[index]; + default: + debugf("unhandled glGetStringi parameter %x\n", name); + assert(false); + return nullptr; + } +} + +GLenum remap_blendfunc(GLenum rgb, GLenum a) { + switch (a) { + case GL_SRC_ALPHA: + if (rgb == GL_SRC_COLOR) a = GL_SRC_COLOR; + break; + case GL_ONE_MINUS_SRC_ALPHA: + if (rgb == GL_ONE_MINUS_SRC_COLOR) a = GL_ONE_MINUS_SRC_COLOR; + break; + case GL_DST_ALPHA: + if (rgb == GL_DST_COLOR) a = GL_DST_COLOR; + break; + case GL_ONE_MINUS_DST_ALPHA: + if (rgb == GL_ONE_MINUS_DST_COLOR) a = GL_ONE_MINUS_DST_COLOR; + break; + case GL_CONSTANT_ALPHA: + if (rgb == GL_CONSTANT_COLOR) a = GL_CONSTANT_COLOR; + break; + case GL_ONE_MINUS_CONSTANT_ALPHA: + if (rgb == GL_ONE_MINUS_CONSTANT_COLOR) a = GL_ONE_MINUS_CONSTANT_COLOR; + break; + case GL_SRC_COLOR: + if (rgb == GL_SRC_ALPHA) a = GL_SRC_ALPHA; + break; + case GL_ONE_MINUS_SRC_COLOR: + if (rgb == GL_ONE_MINUS_SRC_ALPHA) a = GL_ONE_MINUS_SRC_ALPHA; + break; + case GL_DST_COLOR: + if (rgb == GL_DST_ALPHA) a = GL_DST_ALPHA; + break; + case GL_ONE_MINUS_DST_COLOR: + if (rgb == GL_ONE_MINUS_DST_ALPHA) a = GL_ONE_MINUS_DST_ALPHA; + break; + case GL_CONSTANT_COLOR: + if (rgb == GL_CONSTANT_ALPHA) a = GL_CONSTANT_ALPHA; + break; + case GL_ONE_MINUS_CONSTANT_COLOR: + if (rgb == GL_ONE_MINUS_CONSTANT_ALPHA) a = GL_ONE_MINUS_CONSTANT_ALPHA; + break; + case GL_SRC1_ALPHA: + if (rgb == GL_SRC1_COLOR) a = GL_SRC1_COLOR; + break; + case GL_ONE_MINUS_SRC1_ALPHA: + if (rgb == GL_ONE_MINUS_SRC1_COLOR) a = GL_ONE_MINUS_SRC1_COLOR; + break; + case GL_SRC1_COLOR: + if (rgb == GL_SRC1_ALPHA) a = GL_SRC1_ALPHA; + break; + case GL_ONE_MINUS_SRC1_COLOR: + if (rgb == GL_ONE_MINUS_SRC1_ALPHA) a = GL_ONE_MINUS_SRC1_ALPHA; + break; + } + return a; +} + +void BlendFunc(GLenum srgb, GLenum drgb, GLenum sa, GLenum da) { + ctx->blendfunc_srgb = srgb; + ctx->blendfunc_drgb = drgb; + sa = remap_blendfunc(srgb, sa); + da = remap_blendfunc(drgb, da); + ctx->blendfunc_sa = sa; + ctx->blendfunc_da = da; + +#define HASH_BLEND_KEY(x, y, z, w) ((x << 4) | (y) | (z << 24) | (w << 20)) + int hash = HASH_BLEND_KEY(srgb, drgb, 0, 0); + if (srgb != sa || drgb != da) hash |= HASH_BLEND_KEY(0, 0, sa, da); + switch (hash) { +#define MAP_BLEND_KEY(...) \ + case HASH_BLEND_KEY(__VA_ARGS__): \ + ctx->blend_key = BLEND_KEY(__VA_ARGS__); \ + break; + FOR_EACH_BLEND_KEY(MAP_BLEND_KEY) + default: + debugf("blendfunc: %x, %x, separate: %x, %x\n", srgb, drgb, sa, da); + assert(false); + break; + } + + if (ctx->blend) { + blend_key = ctx->blend_key; + } +} + +void BlendColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) { + I32 c = round_pixel((Float){b, g, r, a}); + ctx->blendcolor = CONVERT(c, U16).xyzwxyzw; +} + +void BlendEquation(GLenum mode) { + assert(mode == GL_FUNC_ADD); + ctx->blend_equation = mode; +} + +void DepthMask(GLboolean flag) { ctx->depthmask = flag; } + +void DepthFunc(GLenum func) { + switch (func) { + case GL_LESS: + case GL_LEQUAL: + break; + default: + assert(false); + } + ctx->depthfunc = func; +} + +void SetScissor(GLint x, GLint y, GLsizei width, GLsizei height) { + ctx->scissor = IntRect{x, y, x + width, y + height}; +} + +void ClearColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) { + I32 c = round_pixel((Float){b, g, r, a}); + ctx->clearcolor = bit_cast(CONVERT(c, U8)); +} + +void ClearDepth(GLdouble depth) { ctx->cleardepth = depth; } + +void ActiveTexture(GLenum texture) { + assert(texture >= GL_TEXTURE0); + assert(texture < GL_TEXTURE0 + MAX_TEXTURE_UNITS); + ctx->active_texture_unit = + clamp(int(texture - GL_TEXTURE0), 0, int(MAX_TEXTURE_UNITS - 1)); +} + +void GenQueries(GLsizei n, GLuint* result) { + for (int i = 0; i < n; i++) { + Query q; + result[i] = ctx->queries.insert(q); + } +} + +void DeleteQuery(GLuint n) { + if (n && ctx->queries.erase(n)) { + unlink(ctx->time_elapsed_query, n); + unlink(ctx->samples_passed_query, n); + } +} + +void GenBuffers(int n, GLuint* result) { + for (int i = 0; i < n; i++) { + Buffer b; + result[i] = ctx->buffers.insert(b); + } +} + +void DeleteBuffer(GLuint n) { + if (n && ctx->buffers.erase(n)) { + unlink(ctx->pixel_pack_buffer_binding, n); + unlink(ctx->pixel_unpack_buffer_binding, n); + unlink(ctx->array_buffer_binding, n); + unlink(ctx->element_array_buffer_binding, n); + } +} + +void GenVertexArrays(int n, GLuint* result) { + for (int i = 0; i < n; i++) { + VertexArray v; + result[i] = ctx->vertex_arrays.insert(v); + } +} + +void DeleteVertexArray(GLuint n) { + if (n && ctx->vertex_arrays.erase(n)) { + unlink(ctx->current_vertex_array, n); + } +} + +GLuint CreateShader(GLenum type) { + Shader s; + s.type = type; + return ctx->shaders.insert(s); +} + +void ShaderSourceByName(GLuint shader, char* name) { + Shader& s = ctx->shaders[shader]; + s.loader = load_shader(name); + if (!s.loader) { + debugf("unknown shader %s\n", name); + } +} + +void AttachShader(GLuint program, GLuint shader) { + Program& p = ctx->programs[program]; + Shader& s = ctx->shaders[shader]; + if (s.type == GL_VERTEX_SHADER) { + if (!p.impl && s.loader) p.impl = s.loader(); + } else if (s.type == GL_FRAGMENT_SHADER) { + if (!p.impl && s.loader) p.impl = s.loader(); + } else { + assert(0); + } +} + +void DeleteShader(GLuint n) { + if (n) ctx->shaders.erase(n); +} + +GLuint CreateProgram() { + Program p; + return ctx->programs.insert(p); +} + +void DeleteProgram(GLuint n) { + if (!n) return; + if (ctx->current_program == n) { + if (auto* p = ctx->programs.find(n)) { + p->deleted = true; + } + } else { + ctx->programs.erase(n); + } +} + +void LinkProgram(GLuint program) { + Program& p = ctx->programs[program]; + assert(p.impl); + assert(p.impl->interpolants_size() <= sizeof(Interpolants)); + if (!p.vert_impl) p.vert_impl = p.impl->get_vertex_shader(); + if (!p.frag_impl) p.frag_impl = p.impl->get_fragment_shader(); +} + +void BindAttribLocation(GLuint program, GLuint index, char* name) { + Program& p = ctx->programs[program]; + assert(p.impl); + p.impl->bind_attrib(name, index); +} + +GLint GetAttribLocation(GLuint program, char* name) { + Program& p = ctx->programs[program]; + assert(p.impl); + return p.impl->get_attrib(name); +} + +GLint GetUniformLocation(GLuint program, char* name) { + Program& p = ctx->programs[program]; + assert(p.impl); + GLint loc = p.impl->get_uniform(name); + // debugf("location: %d\n", loc); + return loc; +} + +static uint64_t get_time_value() { +#ifdef __MACH__ + return mach_absolute_time(); +#elif defined(_WIN32) + return uint64_t(clock()) * (1000000000ULL / CLOCKS_PER_SEC); +#else + return ({ + struct timespec tp; + clock_gettime(CLOCK_MONOTONIC, &tp); + tp.tv_sec * 1000000000ULL + tp.tv_nsec; + }); +#endif +} + +void BeginQuery(GLenum target, GLuint id) { + ctx->get_binding(target) = id; + Query& q = ctx->queries[id]; + switch (target) { + case GL_SAMPLES_PASSED: + q.value = 0; + break; + case GL_TIME_ELAPSED: + q.value = get_time_value(); + break; + default: + debugf("unknown query target %x for query %d\n", target, id); + assert(false); + } +} + +void EndQuery(GLenum target) { + Query& q = ctx->queries[ctx->get_binding(target)]; + switch (target) { + case GL_SAMPLES_PASSED: + break; + case GL_TIME_ELAPSED: + q.value = get_time_value() - q.value; + break; + default: + debugf("unknown query target %x\n", target); + assert(false); + } + ctx->get_binding(target) = 0; +} + +void GetQueryObjectui64v(GLuint id, GLenum pname, GLuint64* params) { + Query& q = ctx->queries[id]; + switch (pname) { + case GL_QUERY_RESULT: + assert(params); + params[0] = q.value; + break; + default: + assert(false); + } +} + +void BindVertexArray(GLuint vertex_array) { + if (vertex_array != ctx->current_vertex_array) { + ctx->validate_vertex_array = true; + } + ctx->current_vertex_array = vertex_array; +} + +void BindTexture(GLenum target, GLuint texture) { + ctx->get_binding(target) = texture; +} + +void BindBuffer(GLenum target, GLuint buffer) { + ctx->get_binding(target) = buffer; +} + +void BindFramebuffer(GLenum target, GLuint fb) { + if (target == GL_FRAMEBUFFER) { + ctx->read_framebuffer_binding = fb; + ctx->draw_framebuffer_binding = fb; + } else { + assert(target == GL_READ_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER); + ctx->get_binding(target) = fb; + } +} + +void BindRenderbuffer(GLenum target, GLuint rb) { + ctx->get_binding(target) = rb; +} + +void PixelStorei(GLenum name, GLint param) { + if (name == GL_UNPACK_ALIGNMENT) { + assert(param == 1); + } else if (name == GL_UNPACK_ROW_LENGTH) { + ctx->unpack_row_length = param; + } +} + +static GLenum remap_internal_format(GLenum format) { + switch (format) { + case GL_DEPTH_COMPONENT: + return GL_DEPTH_COMPONENT16; + case GL_RGBA: + return GL_RGBA8; + case GL_RED: + return GL_R8; + default: + return format; + } +} + +void TexStorage3D(GLenum target, GLint levels, GLenum internal_format, + GLsizei width, GLsizei height, GLsizei depth) { + assert(levels == 1); + Texture& t = ctx->textures[ctx->get_binding(target)]; + internal_format = remap_internal_format(internal_format); + bool changed = false; + if (t.width != width || t.height != height || t.depth != depth || + t.internal_format != internal_format) { + changed = true; + t.internal_format = internal_format; + t.width = width; + t.height = height; + t.depth = depth; + } + t.disable_delayed_clear(); + t.allocate(changed); +} + +static void set_tex_storage(Texture& t, GLenum internal_format, + GLsizei width, GLsizei height, + bool should_free = true, void* buf = nullptr, + GLsizei min_width = 0, GLsizei min_height = 0) { + internal_format = remap_internal_format(internal_format); + bool changed = false; + if (t.width != width || t.height != height || t.depth != 0 || + t.internal_format != internal_format) { + changed = true; + t.internal_format = internal_format; + t.width = width; + t.height = height; + t.depth = 0; + } + if (t.should_free() != should_free || buf != nullptr) { + if (t.should_free()) { + t.cleanup(); + } + t.set_should_free(should_free); + t.buf = (char*)buf; + t.buf_size = 0; + } + t.disable_delayed_clear(); + t.allocate(changed, min_width, min_height); +} + +void TexStorage2D(GLenum target, GLint levels, GLenum internal_format, + GLsizei width, GLsizei height) { + assert(levels == 1); + Texture& t = ctx->textures[ctx->get_binding(target)]; + set_tex_storage(t, internal_format, width, height); +} + +GLenum internal_format_for_data(GLenum format, GLenum ty) { + if (format == GL_RED && ty == GL_UNSIGNED_BYTE) { + return GL_R8; + } else if ((format == GL_RGBA || format == GL_BGRA) && + ty == GL_UNSIGNED_BYTE) { + return GL_RGBA8; + } else if (format == GL_RGBA && ty == GL_FLOAT) { + return GL_RGBA32F; + } else if (format == GL_RGBA_INTEGER && ty == GL_INT) { + return GL_RGBA32I; + } else { + debugf("unknown internal format for format %x, type %x\n", format, ty); + assert(false); + return 0; + } +} + +static inline void copy_bgra8_to_rgba8(uint32_t* dest, uint32_t* src, + int width) { + for (; width >= 4; width -= 4, dest += 4, src += 4) { + U32 p = unaligned_load(src); + U32 rb = p & 0x00FF00FF; + unaligned_store(dest, (p & 0xFF00FF00) | (rb << 16) | (rb >> 16)); + } + for (; width > 0; width--, dest++, src++) { + uint32_t p = *src; + uint32_t rb = p & 0x00FF00FF; + *dest = (p & 0xFF00FF00) | (rb << 16) | (rb >> 16); + } +} + +static Buffer* get_pixel_pack_buffer() { + return ctx->pixel_pack_buffer_binding + ? &ctx->buffers[ctx->pixel_pack_buffer_binding] + : nullptr; +} + +static void* get_pixel_pack_buffer_data(void* data) { + if (Buffer* b = get_pixel_pack_buffer()) { + return b->buf ? b->buf + (size_t)data : nullptr; + } + return data; +} + +static Buffer* get_pixel_unpack_buffer() { + return ctx->pixel_unpack_buffer_binding + ? &ctx->buffers[ctx->pixel_unpack_buffer_binding] + : nullptr; +} + +static void* get_pixel_unpack_buffer_data(void* data) { + if (Buffer* b = get_pixel_unpack_buffer()) { + return b->buf ? b->buf + (size_t)data : nullptr; + } + return data; +} + +void TexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLsizei width, GLsizei height, GLenum format, GLenum ty, + void* data) { + if (level != 0) { assert(false); return; } + data = get_pixel_unpack_buffer_data(data); + if (!data) return; + Texture& t = ctx->textures[ctx->get_binding(target)]; + IntRect skip = {xoffset, yoffset, xoffset + width, yoffset + height}; + prepare_texture(t, &skip); + assert(xoffset + width <= t.width); + assert(yoffset + height <= t.height); + assert(ctx->unpack_row_length == 0 || ctx->unpack_row_length >= width); + GLsizei row_length = + ctx->unpack_row_length != 0 ? ctx->unpack_row_length : width; + assert(t.internal_format == internal_format_for_data(format, ty)); + int bpp = t.bpp(); + if (!bpp || !t.buf) return; + size_t dest_stride = t.stride(bpp); + char* dest = t.sample_ptr(xoffset, yoffset, 0, bpp, dest_stride); + char* src = (char*)data; + for (int y = 0; y < height; y++) { + if (t.internal_format == GL_RGBA8 && format != GL_BGRA) { + copy_bgra8_to_rgba8((uint32_t*)dest, (uint32_t*)src, width); + } else { + memcpy(dest, src, width * bpp); + } + dest += dest_stride; + src += row_length * bpp; + } +} + +void TexImage2D(GLenum target, GLint level, GLint internal_format, + GLsizei width, GLsizei height, GLint border, GLenum format, + GLenum ty, void* data) { + if (level != 0) { assert(false); return; } + assert(border == 0); + TexStorage2D(target, 1, internal_format, width, height); + TexSubImage2D(target, 0, 0, 0, width, height, format, ty, data); +} + +void TexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, + GLenum format, GLenum ty, void* data) { + if (level != 0) { assert(false); return; } + data = get_pixel_unpack_buffer_data(data); + if (!data) return; + Texture& t = ctx->textures[ctx->get_binding(target)]; + prepare_texture(t); + assert(ctx->unpack_row_length == 0 || ctx->unpack_row_length >= width); + GLsizei row_length = + ctx->unpack_row_length != 0 ? ctx->unpack_row_length : width; + if (format == GL_BGRA) { + assert(ty == GL_UNSIGNED_BYTE); + assert(t.internal_format == GL_RGBA8); + } else { + assert(t.internal_format == internal_format_for_data(format, ty)); + } + int bpp = t.bpp(); + if (!bpp || !t.buf) return; + char* src = (char*)data; + assert(xoffset + width <= t.width); + assert(yoffset + height <= t.height); + assert(zoffset + depth <= t.depth); + size_t dest_stride = t.stride(bpp); + for (int z = 0; z < depth; z++) { + char* dest = t.sample_ptr(xoffset, yoffset, zoffset + z, bpp, dest_stride); + for (int y = 0; y < height; y++) { + if (t.internal_format == GL_RGBA8 && format != GL_BGRA) { + copy_bgra8_to_rgba8((uint32_t*)dest, (uint32_t*)src, width); + } else { + memcpy(dest, src, width * bpp); + } + dest += dest_stride; + src += row_length * bpp; + } + } +} + +void TexImage3D(GLenum target, GLint level, GLint internal_format, + GLsizei width, GLsizei height, GLsizei depth, GLint border, + GLenum format, GLenum ty, void* data) { + if (level != 0) { assert(false); return; } + assert(border == 0); + TexStorage3D(target, 1, internal_format, width, height, depth); + TexSubImage3D(target, 0, 0, 0, 0, width, height, depth, format, ty, data); +} + +void GenerateMipmap(UNUSED GLenum target) { + // TODO: support mipmaps +} + +void TexParameteri(GLenum target, GLenum pname, GLint param) { + Texture& t = ctx->textures[ctx->get_binding(target)]; + switch (pname) { + case GL_TEXTURE_WRAP_S: + assert(param == GL_CLAMP_TO_EDGE); + break; + case GL_TEXTURE_WRAP_T: + assert(param == GL_CLAMP_TO_EDGE); + break; + case GL_TEXTURE_MIN_FILTER: + t.min_filter = param; + break; + case GL_TEXTURE_MAG_FILTER: + t.mag_filter = param; + break; + default: + break; + } +} + +void GenTextures(int n, GLuint* result) { + for (int i = 0; i < n; i++) { + Texture t; + result[i] = ctx->textures.insert(t); + } +} + +void DeleteTexture(GLuint n) { + if (n && ctx->textures.erase(n)) { + for (size_t i = 0; i < MAX_TEXTURE_UNITS; i++) { + ctx->texture_units[i].unlink(n); + } + } +} + +void GenRenderbuffers(int n, GLuint* result) { + for (int i = 0; i < n; i++) { + Renderbuffer r; + result[i] = ctx->renderbuffers.insert(r); + } +} + +void Renderbuffer::on_erase() { + for (auto* fb : ctx->framebuffers) { + if (fb) { + if (unlink(fb->color_attachment, texture)) { + fb->layer = 0; + } + unlink(fb->depth_attachment, texture); + } + } + DeleteTexture(texture); +} + +void DeleteRenderbuffer(GLuint n) { + if (n && ctx->renderbuffers.erase(n)) { + unlink(ctx->renderbuffer_binding, n); + } +} + +void GenFramebuffers(int n, GLuint* result) { + for (int i = 0; i < n; i++) { + Framebuffer f; + result[i] = ctx->framebuffers.insert(f); + } +} + +void DeleteFramebuffer(GLuint n) { + if (n && ctx->framebuffers.erase(n)) { + unlink(ctx->read_framebuffer_binding, n); + unlink(ctx->draw_framebuffer_binding, n); + } +} + +void RenderbufferStorage(GLenum target, GLenum internal_format, GLsizei width, + GLsizei height) { + // Just refer a renderbuffer to a texture to simplify things for now... + Renderbuffer& r = ctx->renderbuffers[ctx->get_binding(target)]; + if (!r.texture) { + GenTextures(1, &r.texture); + } + switch (internal_format) { + case GL_DEPTH_COMPONENT: + case GL_DEPTH_COMPONENT24: + case GL_DEPTH_COMPONENT32: + // Force depth format to 16 bits... + internal_format = GL_DEPTH_COMPONENT16; + break; + } + set_tex_storage(ctx->textures[r.texture], internal_format, width, height); +} + +void VertexAttribPointer(GLuint index, GLint size, GLenum type, bool normalized, + GLsizei stride, GLuint offset) { + // debugf("cva: %d\n", ctx->current_vertex_array); + VertexArray& v = ctx->vertex_arrays[ctx->current_vertex_array]; + if (index >= NULL_ATTRIB) { + assert(0); + return; + } + VertexAttrib& va = v.attribs[index]; + va.size = size * bytes_per_type(type); + va.type = type; + va.normalized = normalized; + va.stride = stride; + va.offset = offset; + // Buffer &vertex_buf = ctx->buffers[ctx->array_buffer_binding]; + va.vertex_buffer = ctx->array_buffer_binding; + va.vertex_array = ctx->current_vertex_array; + ctx->validate_vertex_array = true; +} + +void VertexAttribIPointer(GLuint index, GLint size, GLenum type, GLsizei stride, + GLuint offset) { + // debugf("cva: %d\n", ctx->current_vertex_array); + VertexArray& v = ctx->vertex_arrays[ctx->current_vertex_array]; + if (index >= NULL_ATTRIB) { + assert(0); + return; + } + VertexAttrib& va = v.attribs[index]; + va.size = size * bytes_per_type(type); + va.type = type; + va.normalized = false; + va.stride = stride; + va.offset = offset; + // Buffer &vertex_buf = ctx->buffers[ctx->array_buffer_binding]; + va.vertex_buffer = ctx->array_buffer_binding; + va.vertex_array = ctx->current_vertex_array; + ctx->validate_vertex_array = true; +} + +void EnableVertexAttribArray(GLuint index) { + VertexArray& v = ctx->vertex_arrays[ctx->current_vertex_array]; + if (index >= NULL_ATTRIB) { + assert(0); + return; + } + VertexAttrib& va = v.attribs[index]; + if (!va.enabled) { + ctx->validate_vertex_array = true; + } + va.enabled = true; + v.max_attrib = max(v.max_attrib, (int)index); +} + +void DisableVertexAttribArray(GLuint index) { + VertexArray& v = ctx->vertex_arrays[ctx->current_vertex_array]; + if (index >= NULL_ATTRIB) { + assert(0); + return; + } + VertexAttrib& va = v.attribs[index]; + if (va.enabled) { + ctx->validate_vertex_array = true; + } + va.enabled = false; +} + +void VertexAttribDivisor(GLuint index, GLuint divisor) { + VertexArray& v = ctx->vertex_arrays[ctx->current_vertex_array]; + // Only support divisor being 0 (per-vertex) or 1 (per-instance). + if (index >= NULL_ATTRIB || divisor > 1) { + assert(0); + return; + } + VertexAttrib& va = v.attribs[index]; + va.divisor = divisor; +} + +void BufferData(GLenum target, GLsizeiptr size, void* data, UNUSED GLenum usage) { + Buffer& b = ctx->buffers[ctx->get_binding(target)]; + if (b.allocate(size)) { + ctx->validate_vertex_array = true; + } + if (data && b.buf && size <= b.size) { + memcpy(b.buf, data, size); + } +} + +void BufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, + void* data) { + Buffer& b = ctx->buffers[ctx->get_binding(target)]; + assert(offset + size <= b.size); + if (data && b.buf && offset + size <= b.size) { + memcpy(&b.buf[offset], data, size); + } +} + +void* MapBuffer(GLenum target, UNUSED GLbitfield access) { + Buffer& b = ctx->buffers[ctx->get_binding(target)]; + return b.buf; +} + +void* MapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, + UNUSED GLbitfield access) { + Buffer& b = ctx->buffers[ctx->get_binding(target)]; + if (b.buf && offset >= 0 && length > 0 && offset + length <= b.size) { + return b.buf + offset; + } + return nullptr; +} + +GLboolean UnmapBuffer(GLenum target) { + Buffer& b = ctx->buffers[ctx->get_binding(target)]; + return b.buf != nullptr; +} + +void Uniform1i(GLint location, GLint V0) { + // debugf("tex: %d\n", (int)ctx->textures.size); + vertex_shader->set_uniform_1i(location, V0); +} +void Uniform4fv(GLint location, GLsizei count, const GLfloat* v) { + assert(count == 1); + vertex_shader->set_uniform_4fv(location, v); +} +void UniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, + const GLfloat* value) { + assert(count == 1); + assert(!transpose); + vertex_shader->set_uniform_matrix4fv(location, value); +} + +void FramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, + GLuint texture, GLint level) { + assert(target == GL_READ_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER); + assert(textarget == GL_TEXTURE_2D || textarget == GL_TEXTURE_RECTANGLE); + assert(level == 0); + Framebuffer& fb = ctx->framebuffers[ctx->get_binding(target)]; + if (attachment == GL_COLOR_ATTACHMENT0) { + fb.color_attachment = texture; + fb.layer = 0; + } else if (attachment == GL_DEPTH_ATTACHMENT) { + fb.depth_attachment = texture; + } else { + assert(0); + } +} + +void FramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, + GLint level, GLint layer) { + assert(target == GL_READ_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER); + assert(level == 0); + Framebuffer& fb = ctx->framebuffers[ctx->get_binding(target)]; + if (attachment == GL_COLOR_ATTACHMENT0) { + fb.color_attachment = texture; + fb.layer = layer; + } else if (attachment == GL_DEPTH_ATTACHMENT) { + assert(layer == 0); + fb.depth_attachment = texture; + } else { + assert(0); + } +} + +void FramebufferRenderbuffer(GLenum target, GLenum attachment, + GLenum renderbuffertarget, GLuint renderbuffer) { + assert(target == GL_READ_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER); + assert(renderbuffertarget == GL_RENDERBUFFER); + Framebuffer& fb = ctx->framebuffers[ctx->get_binding(target)]; + Renderbuffer& rb = ctx->renderbuffers[renderbuffer]; + if (attachment == GL_COLOR_ATTACHMENT0) { + fb.color_attachment = rb.texture; + fb.layer = 0; + } else if (attachment == GL_DEPTH_ATTACHMENT) { + fb.depth_attachment = rb.texture; + } else { + assert(0); + } +} + +} // extern "C" + +static inline Framebuffer* get_framebuffer(GLenum target) { + if (target == GL_FRAMEBUFFER) { + target = GL_DRAW_FRAMEBUFFER; + } + return ctx->framebuffers.find(ctx->get_binding(target)); +} + +template +static inline void fill_n(T* dst, size_t n, T val) { + for (T* end = &dst[n]; dst < end; dst++) *dst = val; +} + +#if USE_SSE2 +template <> +inline void fill_n(uint32_t* dst, size_t n, uint32_t val) { + __asm__ __volatile__("rep stosl\n" + : "+D"(dst), "+c"(n) + : "a"(val) + : "memory", "cc"); +} +#endif + +static inline uint32_t clear_chunk(uint8_t value) { + return uint32_t(value) * 0x01010101U; +} + +static inline uint32_t clear_chunk(uint16_t value) { + return uint32_t(value) | (uint32_t(value) << 16); +} + +static inline uint32_t clear_chunk(uint32_t value) { + return value; +} + +template +static inline void clear_row(T* buf, size_t len, T value, uint32_t chunk) { + const size_t N = sizeof(uint32_t) / sizeof(T); + // fill any leading unaligned values + if (N > 1) { + size_t align = (-(intptr_t)buf & (sizeof(uint32_t) - 1)) / sizeof(T); + if (align <= len) { + fill_n(buf, align, value); + len -= align; + buf += align; + } + } + // fill as many aligned chunks as possible + fill_n((uint32_t*)buf, len / N, chunk); + // fill any remaining values + if (N > 1) { + fill_n(buf + (len & ~(N - 1)), len & (N - 1), value); + } +} + +template +static void clear_buffer(Texture& t, T value, int layer, IntRect bb, + int skip_start = 0, int skip_end = 0) { + if (!t.buf) return; + skip_start = max(skip_start, bb.x0); + skip_end = max(skip_end, skip_start); + assert(sizeof(T) == t.bpp()); + size_t stride = t.stride(sizeof(T)); + // When clearing multiple full-width rows, collapse them into a single + // large "row" to avoid redundant setup from clearing each row individually. + if (bb.width() == t.width && bb.height() > 1 && skip_start >= skip_end) { + bb.x1 += (stride / sizeof(T)) * (bb.height() - 1); + bb.y1 = bb.y0 + 1; + } + T* buf = (T*)t.sample_ptr(bb.x0, bb.y0, layer, sizeof(T), stride); + uint32_t chunk = clear_chunk(value); + for (int rows = bb.height(); rows > 0; rows--) { + if (bb.x0 < skip_start) { + clear_row(buf, skip_start - bb.x0, value, chunk); + } + if (skip_end < bb.x1) { + clear_row(buf + (skip_end - bb.x0), bb.x1 - skip_end, value, chunk); + } + buf += stride / sizeof(T); + } +} + +template +static inline void clear_buffer(Texture& t, T value, int layer = 0) { + IntRect bb = ctx->apply_scissor(t.bounds()); + if (bb.width() > 0) { + clear_buffer(t, value, layer, bb); + } +} + +template +static inline void force_clear_row(Texture& t, int y, int skip_start = 0, + int skip_end = 0) { + assert(t.buf != nullptr); + assert(sizeof(T) == t.bpp()); + assert(skip_start <= skip_end); + T* buf = (T*)t.sample_ptr(0, y, 0, sizeof(T)); + uint32_t chunk = clear_chunk((T)t.clear_val); + if (skip_start > 0) { + clear_row(buf, skip_start, t.clear_val, chunk); + } + if (skip_end < t.width) { + clear_row(buf + skip_end, t.width - skip_end, t.clear_val, chunk); + } +} + +template +static void force_clear(Texture& t, const IntRect* skip = nullptr) { + if (!t.delay_clear || !t.cleared_rows) { + return; + } + int y0 = 0; + int y1 = t.height; + int skip_start = 0; + int skip_end = 0; + if (skip) { + y0 = clamp(skip->y0, 0, t.height); + y1 = clamp(skip->y1, y0, t.height); + skip_start = clamp(skip->x0, 0, t.width); + skip_end = clamp(skip->x1, skip_start, t.width); + if (skip_start <= 0 && skip_end >= t.width && y0 <= 0 && y1 >= t.height) { + t.disable_delayed_clear(); + return; + } + } + int num_masks = (y1 + 31) / 32; + uint32_t* rows = t.cleared_rows; + for (int i = y0 / 32; i < num_masks; i++) { + uint32_t mask = rows[i]; + if (mask != ~0U) { + rows[i] = ~0U; + int start = i * 32; + while (mask) { + int count = __builtin_ctz(mask); + if (count > 0) { + clear_buffer(t, t.clear_val, 0, + IntRect{0, start, t.width, start + count}, + skip_start, skip_end); + t.delay_clear -= count; + start += count; + mask >>= count; + } + count = __builtin_ctz(mask + 1); + start += count; + mask >>= count; + } + int count = (i + 1) * 32 - start; + if (count > 0) { + clear_buffer(t, t.clear_val, 0, + IntRect{0, start, t.width, start + count}, + skip_start, skip_end); + t.delay_clear -= count; + } + } + } + if (t.delay_clear <= 0) t.disable_delayed_clear(); +} + +static void prepare_texture(Texture& t, const IntRect* skip) { + if (t.delay_clear) { + switch (t.internal_format) { + case GL_RGBA8: + force_clear(t, skip); + break; + case GL_R8: + force_clear(t, skip); + break; + case GL_DEPTH_COMPONENT16: + force_clear(t, skip); + break; + default: + assert(false); + break; + } + } +} + +extern "C" { + +void InitDefaultFramebuffer(int width, int height) { + Framebuffer& fb = ctx->framebuffers[0]; + if (!fb.color_attachment) { + GenTextures(1, &fb.color_attachment); + fb.layer = 0; + } + Texture& colortex = ctx->textures[fb.color_attachment]; + if (colortex.width != width || colortex.height != height) { + colortex.cleanup(); + set_tex_storage(colortex, GL_RGBA8, width, height); + } + if (!fb.depth_attachment) { + GenTextures(1, &fb.depth_attachment); + } + Texture& depthtex = ctx->textures[fb.depth_attachment]; + if (depthtex.width != width || depthtex.height != height) { + depthtex.cleanup(); + set_tex_storage(depthtex, GL_DEPTH_COMPONENT16, width, height); + } +} + +void* GetColorBuffer(GLuint fbo, GLboolean flush, int32_t* width, + int32_t* height) { + Framebuffer* fb = ctx->framebuffers.find(fbo); + if (!fb || !fb->color_attachment) { + return nullptr; + } + Texture& colortex = ctx->textures[fb->color_attachment]; + if (flush) { + prepare_texture(colortex); + } + *width = colortex.width; + *height = colortex.height; + return colortex.buf ? colortex.sample_ptr(0, 0, fb->layer) : nullptr; +} + +void SetTextureBuffer(GLuint texid, GLenum internal_format, GLsizei width, + GLsizei height, void* buf, GLsizei min_width, + GLsizei min_height) { + Texture& t = ctx->textures[texid]; + set_tex_storage(t, internal_format, width, height, !buf, buf, min_width, + min_height); +} + +GLenum CheckFramebufferStatus(GLenum target) { + Framebuffer* fb = get_framebuffer(target); + if (!fb || !fb->color_attachment) { + return GL_FRAMEBUFFER_UNSUPPORTED; + } + return GL_FRAMEBUFFER_COMPLETE; +} + +static inline bool clear_requires_scissor(Texture& t) { + return ctx->scissortest && !ctx->scissor.contains(t.bounds()); +} + +void Clear(GLbitfield mask) { + Framebuffer& fb = *get_framebuffer(GL_DRAW_FRAMEBUFFER); + if ((mask & GL_COLOR_BUFFER_BIT) && fb.color_attachment) { + Texture& t = ctx->textures[fb.color_attachment]; + if (t.internal_format == GL_RGBA8) { + uint32_t color = ctx->clearcolor; + // If the clear would require a scissor, force clear anything outside + // the scissor, and then immediately clear anything inside the scissor. + if (clear_requires_scissor(t)) { + force_clear(t, &ctx->scissor); + clear_buffer(t, color, fb.layer); + } else if (t.depth > 1) { + // Delayed clear is not supported on texture arrays. + t.disable_delayed_clear(); + clear_buffer(t, color, fb.layer); + } else { + // Do delayed clear for 2D texture without scissor. + t.enable_delayed_clear(color); + } + } else if (t.internal_format == GL_R8) { + uint8_t color = uint8_t((ctx->clearcolor >> 16) & 0xFF); + if (clear_requires_scissor(t)) { + force_clear(t, &ctx->scissor); + clear_buffer(t, color, fb.layer); + } else if (t.depth > 1) { + t.disable_delayed_clear(); + clear_buffer(t, color, fb.layer); + } else { + t.enable_delayed_clear(color); + } + } else { + assert(false); + } + } + if ((mask & GL_DEPTH_BUFFER_BIT) && fb.depth_attachment) { + Texture& t = ctx->textures[fb.depth_attachment]; + assert(t.internal_format == GL_DEPTH_COMPONENT16); + uint16_t depth = uint16_t(0xFFFF * ctx->cleardepth) - 0x8000; + if (clear_requires_scissor(t)) { + force_clear(t, &ctx->scissor); + clear_buffer(t, depth); + } else { + t.enable_delayed_clear(depth); + } + } +} + +void InvalidateFramebuffer(GLenum target, GLsizei num_attachments, + const GLenum* attachments) { + Framebuffer* fb = get_framebuffer(target); + if (!fb || num_attachments <= 0 || !attachments) { + return; + } + for (GLsizei i = 0; i < num_attachments; i++) { + switch (attachments[i]) { + case GL_DEPTH_ATTACHMENT: { + Texture& t = ctx->textures[fb->depth_attachment]; + t.disable_delayed_clear(); + break; + } + case GL_COLOR_ATTACHMENT0: { + Texture& t = ctx->textures[fb->color_attachment]; + t.disable_delayed_clear(); + break; + } + } + } +} + +void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, + GLenum type, void* data) { + data = get_pixel_pack_buffer_data(data); + if (!data) return; + Framebuffer* fb = get_framebuffer(GL_READ_FRAMEBUFFER); + if (!fb) return; + assert(format == GL_RED || format == GL_RGBA || format == GL_RGBA_INTEGER || + format == GL_BGRA); + Texture& t = ctx->textures[fb->color_attachment]; + if (!t.buf) return; + prepare_texture(t); + // debugf("read pixels %d, %d, %d, %d from fb %d with format %x\n", x, y, + // width, height, ctx->read_framebuffer_binding, t.internal_format); + assert(x + width <= t.width); + assert(y + height <= t.height); + if (internal_format_for_data(format, type) != t.internal_format) { + debugf("mismatched format for read pixels: %x vs %x\n", t.internal_format, + internal_format_for_data(format, type)); + assert(false); + } + int bpp = t.bpp(); + char* dest = (char*)data; + size_t src_stride = t.stride(bpp); + char* src = t.sample_ptr(x, y, fb->layer, bpp, src_stride); + for (; height > 0; height--) { + if (t.internal_format == GL_RGBA8 && format != GL_BGRA) { + copy_bgra8_to_rgba8((uint32_t*)dest, (uint32_t*)src, width); + } else { + memcpy(dest, src, width * bpp); + } + dest += width * bpp; + src += src_stride; + } +} + +void CopyImageSubData(GLuint srcName, GLenum srcTarget, UNUSED GLint srcLevel, + GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, + GLenum dstTarget, UNUSED GLint dstLevel, GLint dstX, GLint dstY, + GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, + GLsizei srcDepth) { + assert(srcLevel == 0 && dstLevel == 0); + if (srcTarget == GL_RENDERBUFFER) { + Renderbuffer& rb = ctx->renderbuffers[srcName]; + srcName = rb.texture; + } + if (dstTarget == GL_RENDERBUFFER) { + Renderbuffer& rb = ctx->renderbuffers[dstName]; + dstName = rb.texture; + } + Texture& srctex = ctx->textures[srcName]; + if (!srctex.buf) return; + prepare_texture(srctex); + Texture& dsttex = ctx->textures[dstName]; + if (!dsttex.buf) return; + IntRect skip = {dstX, dstY, dstX + srcWidth, dstY + srcHeight}; + prepare_texture(dsttex, &skip); + assert(srctex.internal_format == dsttex.internal_format); + assert(srcWidth >= 0); + assert(srcHeight >= 0); + assert(srcDepth >= 0); + assert(srcX + srcWidth <= srctex.width); + assert(srcY + srcHeight <= srctex.height); + assert(srcZ + srcDepth <= max(srctex.depth, 1)); + assert(dstX + srcWidth <= dsttex.width); + assert(dstY + srcHeight <= dsttex.height); + assert(dstZ + srcDepth <= max(dsttex.depth, 1)); + int bpp = srctex.bpp(); + int src_stride = srctex.stride(bpp); + int dest_stride = dsttex.stride(bpp); + for (int z = 0; z < srcDepth; z++) { + char* dest = dsttex.sample_ptr(dstX, dstY, dstZ + z, bpp, dest_stride); + char* src = srctex.sample_ptr(srcX, srcY, srcZ + z, bpp, src_stride); + for (int y = 0; y < srcHeight; y++) { + memcpy(dest, src, srcWidth * bpp); + dest += dest_stride; + src += src_stride; + } + } +} + +void CopyTexSubImage3D(GLenum target, UNUSED GLint level, GLint xoffset, GLint yoffset, + GLint zoffset, GLint x, GLint y, GLsizei width, + GLsizei height) { + assert(level == 0); + Framebuffer* fb = get_framebuffer(GL_READ_FRAMEBUFFER); + if (!fb) return; + CopyImageSubData(fb->color_attachment, GL_TEXTURE_3D, 0, x, y, fb->layer, + ctx->get_binding(target), GL_TEXTURE_3D, 0, xoffset, yoffset, + zoffset, width, height, 1); +} + +void CopyTexSubImage2D(GLenum target, UNUSED GLint level, GLint xoffset, GLint yoffset, + GLint x, GLint y, GLsizei width, GLsizei height) { + assert(level == 0); + Framebuffer* fb = get_framebuffer(GL_READ_FRAMEBUFFER); + if (!fb) return; + CopyImageSubData(fb->color_attachment, GL_TEXTURE_2D_ARRAY, 0, x, y, + fb->layer, ctx->get_binding(target), GL_TEXTURE_2D_ARRAY, 0, + xoffset, yoffset, 0, width, height, 1); +} + +} // extern "C" + +using PackedRGBA8 = V16; +using WideRGBA8 = V16; +using HalfRGBA8 = V8; + +static inline WideRGBA8 unpack(PackedRGBA8 p) { return CONVERT(p, WideRGBA8); } + +static inline PackedRGBA8 pack(WideRGBA8 p) { +#if USE_SSE2 + return _mm_packus_epi16(lowHalf(p), highHalf(p)); +#elif USE_NEON + return vcombine_u8(vqmovn_u16(lowHalf(p)), vqmovn_u16(highHalf(p))); +#else + return CONVERT(p, PackedRGBA8); +#endif +} + +static inline HalfRGBA8 packRGBA8(I32 a, I32 b) { +#if USE_SSE2 + return _mm_packs_epi32(a, b); +#elif USE_NEON + return vcombine_u16(vqmovun_s32(a), vqmovun_s32(b)); +#else + return CONVERT(combine(a, b), HalfRGBA8); +#endif +} + +using PackedR8 = V4; +using WideR8 = V4; + +static inline WideR8 unpack(PackedR8 p) { return CONVERT(p, WideR8); } + +static inline WideR8 packR8(I32 a) { +#if USE_SSE2 + return lowHalf(bit_cast>(_mm_packs_epi32(a, a))); +#elif USE_NEON + return vqmovun_s32(a); +#else + return CONVERT(a, WideR8); +#endif +} + +static inline PackedR8 pack(WideR8 p) { +#if USE_SSE2 + auto m = expand(p); + auto r = bit_cast>(_mm_packus_epi16(m, m)); + return SHUFFLE(r, r, 0, 1, 2, 3); +#elif USE_NEON + return lowHalf(bit_cast>(vqmovn_u16(expand(p)))); +#else + return CONVERT(p, PackedR8); +#endif +} + +using ZMask4 = V4; +using ZMask8 = V8; + +static inline PackedRGBA8 unpack(ZMask4 mask, uint32_t*) { + return bit_cast(mask.xxyyzzww); +} + +static inline WideR8 unpack(ZMask4 mask, uint8_t*) { + return bit_cast(mask); +} + +#if USE_SSE2 +# define ZMASK_NONE_PASSED 0xFFFF +# define ZMASK_ALL_PASSED 0 +static inline uint32_t zmask_code(ZMask8 mask) { + return _mm_movemask_epi8(mask); +} +static inline uint32_t zmask_code(ZMask4 mask) { + return zmask_code(mask.xyzwxyzw); +} +#else +using ZMask4Code = V4; +using ZMask8Code = V8; +# define ZMASK_NONE_PASSED 0xFFFFFFFFU +# define ZMASK_ALL_PASSED 0 +static inline uint32_t zmask_code(ZMask4 mask) { + return bit_cast(CONVERT(mask, ZMask4Code)); +} +static inline uint32_t zmask_code(ZMask8 mask) { + return zmask_code( + ZMask4((U16(lowHalf(mask)) >> 12) | (U16(highHalf(mask)) << 4))); +} +#endif + +template +static ALWAYS_INLINE int check_depth8(uint16_t z, uint16_t* zbuf, + ZMask8& outmask) { + ZMask8 dest = unaligned_load(zbuf); + ZMask8 src = int16_t(z); + // Invert the depth test to check which pixels failed and should be discarded. + ZMask8 mask = FUNC == GL_LEQUAL ? + // GL_LEQUAL: Not(LessEqual) = Greater + ZMask8(src > dest) + : + // GL_LESS: Not(Less) = GreaterEqual + ZMask8(src >= dest); + switch (zmask_code(mask)) { + case ZMASK_NONE_PASSED: + return 0; + case ZMASK_ALL_PASSED: + if (MASK) { + unaligned_store(zbuf, src); + } + return -1; + default: + if (MASK) { + unaligned_store(zbuf, (mask & dest) | (~mask & src)); + } + outmask = mask; + return 1; + } +} + +template +static ALWAYS_INLINE bool check_depth4(ZMask4 src, uint16_t* zbuf, + ZMask4& outmask, int span = 0) { + ZMask4 dest = unaligned_load(zbuf); + // Invert the depth test to check which pixels failed and should be discarded. + ZMask4 mask = ctx->depthfunc == GL_LEQUAL + ? + // GL_LEQUAL: Not(LessEqual) = Greater + ZMask4(src > dest) + : + // GL_LESS: Not(Less) = GreaterEqual + ZMask4(src >= dest); + if (!FULL_SPANS) { + mask |= ZMask4(span) < ZMask4{1, 2, 3, 4}; + } + if (zmask_code(mask) == ZMASK_NONE_PASSED) { + return false; + } + if (!DISCARD && ctx->depthmask) { + unaligned_store(zbuf, (mask & dest) | (~mask & src)); + } + outmask = mask; + return true; +} + +template +static ALWAYS_INLINE bool check_depth4(uint16_t z, uint16_t* zbuf, + ZMask4& outmask, int span = 0) { + return check_depth4(ZMask4(int16_t(z)), zbuf, outmask, + span); +} + +template +static inline ZMask4 packZMask4(T a) { +#if USE_SSE2 + return lowHalf(bit_cast(_mm_packs_epi32(a, a))); +#elif USE_NEON + return vqmovn_s32(a); +#else + return CONVERT(a, ZMask4); +#endif +} + +static ALWAYS_INLINE ZMask4 packDepth() { + return packZMask4(cast(fragment_shader->gl_FragCoord.z * 0xFFFF) - 0x8000); +} + +static ALWAYS_INLINE void discard_depth(ZMask4 src, uint16_t* zbuf, + ZMask4 mask) { + if (ctx->depthmask) { + ZMask4 dest = unaligned_load(zbuf); + mask |= packZMask4(fragment_shader->isPixelDiscarded); + unaligned_store(zbuf, (mask & dest) | (~mask & src)); + } +} + +static ALWAYS_INLINE void discard_depth(uint16_t z, uint16_t* zbuf, + ZMask4 mask) { + discard_depth(ZMask4(int16_t(z)), zbuf, mask); +} + +static inline WideRGBA8 pack_pixels_RGBA8(const vec4& v) { + ivec4 i = round_pixel(v); + HalfRGBA8 xz = packRGBA8(i.z, i.x); + HalfRGBA8 yw = packRGBA8(i.y, i.w); + HalfRGBA8 xy = zipLow(xz, yw); + HalfRGBA8 zw = zipHigh(xz, yw); + HalfRGBA8 lo = zip2Low(xy, zw); + HalfRGBA8 hi = zip2High(xy, zw); + return combine(lo, hi); +} + +static inline WideRGBA8 pack_pixels_RGBA8(const vec4_scalar& v) { + I32 i = round_pixel((Float){v.z, v.y, v.x, v.w}); + HalfRGBA8 c = packRGBA8(i, i); + return combine(c, c); +} + +static inline WideRGBA8 pack_pixels_RGBA8() { + return pack_pixels_RGBA8(fragment_shader->gl_FragColor); +} + +template +static inline PackedRGBA8 pack_span(uint32_t*, const V& v) { + return pack(pack_pixels_RGBA8(v)); +} + +static inline PackedRGBA8 pack_span(uint32_t*) { + return pack(pack_pixels_RGBA8()); +} + +// (x*y + x) >> 8, cheap approximation of (x*y) / 255 +template +static inline T muldiv255(T x, T y) { + return (x * y + x) >> 8; +} + +// Byte-wise addition for when x or y is a signed 8-bit value stored in the +// low byte of a larger type T only with zeroed-out high bits, where T is +// greater than 8 bits, i.e. uint16_t. This can result when muldiv255 is used +// upon signed operands, using up all the precision in a 16 bit integer, and +// potentially losing the sign bit in the last >> 8 shift. Due to the +// properties of two's complement arithmetic, even though we've discarded the +// sign bit, we can still represent a negative number under addition (without +// requiring any extra sign bits), just that any negative number will behave +// like a large unsigned number under addition, generating a single carry bit +// on overflow that we need to discard. Thus, just doing a byte-wise add will +// overflow without the troublesome carry, giving us only the remaining 8 low +// bits we actually need while keeping the high bits at zero. +template +static inline T addlow(T x, T y) { + typedef VectorType bytes; + return bit_cast(bit_cast(x) + bit_cast(y)); +} + +static inline WideRGBA8 alphas(WideRGBA8 c) { + return SHUFFLE(c, c, 3, 3, 3, 3, 7, 7, 7, 7, 11, 11, 11, 11, 15, 15, 15, 15); +} + +static inline WideRGBA8 blend_pixels_RGBA8(PackedRGBA8 pdst, WideRGBA8 src) { + WideRGBA8 dst = unpack(pdst); + const WideRGBA8 RGB_MASK = {0xFFFF, 0xFFFF, 0xFFFF, 0, 0xFFFF, 0xFFFF, + 0xFFFF, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0, + 0xFFFF, 0xFFFF, 0xFFFF, 0}; + const WideRGBA8 ALPHA_MASK = {0, 0, 0, 0xFFFF, 0, 0, 0, 0xFFFF, + 0, 0, 0, 0xFFFF, 0, 0, 0, 0xFFFF}; + const WideRGBA8 ALPHA_OPAQUE = {0, 0, 0, 255, 0, 0, 0, 255, + 0, 0, 0, 255, 0, 0, 0, 255}; + switch (blend_key) { + case BLEND_KEY_NONE: + return src; + case BLEND_KEY(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE): + // dst + src.a*(src.rgb1 - dst.rgb0) + // use addlow for signed overflow + return addlow(dst, + muldiv255(alphas(src), (src | ALPHA_OPAQUE) - (dst & RGB_MASK))); + case BLEND_KEY(GL_ONE, GL_ONE_MINUS_SRC_ALPHA): + return src + dst - muldiv255(dst, alphas(src)); + case BLEND_KEY(GL_ZERO, GL_ONE_MINUS_SRC_COLOR): + return dst - muldiv255(dst, src); + case BLEND_KEY(GL_ZERO, GL_ONE_MINUS_SRC_COLOR, GL_ZERO, GL_ONE): + return dst - (muldiv255(dst, src) & RGB_MASK); + case BLEND_KEY(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA): + return dst - muldiv255(dst, alphas(src)); + case BLEND_KEY(GL_ZERO, GL_SRC_COLOR): + return muldiv255(src, dst); + case BLEND_KEY(GL_ONE, GL_ONE): + return src + dst; + case BLEND_KEY(GL_ONE, GL_ONE, GL_ONE, GL_ONE_MINUS_SRC_ALPHA): + return src + dst - (muldiv255(dst, src) & ALPHA_MASK); + case BLEND_KEY(GL_ONE, GL_ZERO): + return src; + case BLEND_KEY(GL_ONE_MINUS_DST_ALPHA, GL_ONE, GL_ZERO, GL_ONE): + // src*(1-dst.a) + dst*1 = src - src*dst.a + dst + return dst + ((src - muldiv255(src, alphas(dst))) & RGB_MASK); + case BLEND_KEY(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR): + // src*k + (1-src)*dst = src*k + dst - src*dst = dst + src*(k - dst) + // use addlow for signed overflow + return addlow(dst, + muldiv255(src, combine(ctx->blendcolor, ctx->blendcolor) - dst)); + case BLEND_KEY(GL_ONE, GL_ONE_MINUS_SRC1_COLOR): { + WideRGBA8 secondary = + pack_pixels_RGBA8(fragment_shader->gl_SecondaryFragColor); + return src + dst - muldiv255(dst, secondary); + } + default: + UNREACHABLE; + // return src; + } +} + +template +static inline void discard_output(uint32_t* buf, PackedRGBA8 mask) { + PackedRGBA8 dst = unaligned_load(buf); + WideRGBA8 r = pack_pixels_RGBA8(); + if (blend_key) r = blend_pixels_RGBA8(dst, r); + if (DISCARD) mask |= bit_cast(fragment_shader->isPixelDiscarded); + unaligned_store(buf, (mask & dst) | (~mask & pack(r))); +} + +template +static inline void discard_output(uint32_t* buf) { + discard_output(buf, 0); +} + +template <> +inline void discard_output(uint32_t* buf) { + WideRGBA8 r = pack_pixels_RGBA8(); + if (blend_key) r = blend_pixels_RGBA8(unaligned_load(buf), r); + unaligned_store(buf, pack(r)); +} + +static inline PackedRGBA8 span_mask_RGBA8(int span) { + return bit_cast(I32(span) < I32{1, 2, 3, 4}); +} + +static inline PackedRGBA8 span_mask(uint32_t*, int span) { + return span_mask_RGBA8(span); +} + +static inline WideR8 pack_pixels_R8(Float c) { + return packR8(round_pixel(c)); +} + +static inline WideR8 pack_pixels_R8() { + return pack_pixels_R8(fragment_shader->gl_FragColor.x); +} + +template +static inline PackedR8 pack_span(uint8_t*, C c) { + return pack(pack_pixels_R8(c)); +} + +static inline PackedR8 pack_span(uint8_t*) { return pack(pack_pixels_R8()); } + +static inline WideR8 blend_pixels_R8(WideR8 dst, WideR8 src) { + switch (blend_key) { + case BLEND_KEY_NONE: + return src; + case BLEND_KEY(GL_ZERO, GL_SRC_COLOR): + return muldiv255(src, dst); + case BLEND_KEY(GL_ONE, GL_ONE): + return src + dst; + case BLEND_KEY(GL_ONE, GL_ZERO): + return src; + default: + UNREACHABLE; + // return src; + } +} + +template +static inline void discard_output(uint8_t* buf, WideR8 mask) { + WideR8 dst = unpack(unaligned_load(buf)); + WideR8 r = pack_pixels_R8(); + if (blend_key) r = blend_pixels_R8(dst, r); + if (DISCARD) mask |= packR8(fragment_shader->isPixelDiscarded); + unaligned_store(buf, pack((mask & dst) | (~mask & r))); +} + +template +static inline void discard_output(uint8_t* buf) { + discard_output(buf, 0); +} + +template <> +inline void discard_output(uint8_t* buf) { + WideR8 r = pack_pixels_R8(); + if (blend_key) r = blend_pixels_R8(unpack(unaligned_load(buf)), r); + unaligned_store(buf, pack(r)); +} + +static inline WideR8 span_mask_R8(int span) { + return bit_cast(WideR8(span) < WideR8{1, 2, 3, 4}); +} + +static inline WideR8 span_mask(uint8_t*, int span) { + return span_mask_R8(span); +} + +template +static inline void commit_output(P* buf, M mask) { + fragment_shader->run(); + discard_output(buf, mask); +} + +template +static inline void commit_output(P* buf) { + fragment_shader->run(); + discard_output(buf); +} + +template +static inline void commit_output(P* buf, int span) { + commit_output(buf, span_mask(buf, span)); +} + +template +static inline void commit_output(P* buf, Z z, uint16_t* zbuf) { + ZMask4 zmask; + if (check_depth4(z, zbuf, zmask)) { + commit_output(buf, unpack(zmask, buf)); + if (DISCARD) { + discard_depth(z, zbuf, zmask); + } + } else { + fragment_shader->skip(); + } +} + +template +static inline void commit_output(P* buf, Z z, uint16_t* zbuf, int span) { + ZMask4 zmask; + if (check_depth4(z, zbuf, zmask, span)) { + commit_output(buf, unpack(zmask, buf)); + if (DISCARD) { + discard_depth(z, zbuf, zmask); + } + } +} + +static inline void commit_span(uint32_t* buf, PackedRGBA8 r) { + if (blend_key) + r = pack(blend_pixels_RGBA8(unaligned_load(buf), unpack(r))); + unaligned_store(buf, r); +} + +UNUSED static inline void commit_solid_span(uint32_t* buf, PackedRGBA8 r, + int len) { + if (blend_key) { + auto src = unpack(r); + for (uint32_t* end = &buf[len]; buf < end; buf += 4) { + unaligned_store( + buf, pack(blend_pixels_RGBA8(unaligned_load(buf), src))); + } + } else { + fill_n(buf, len, bit_cast(r).x); + } +} + +UNUSED static inline void commit_texture_span(uint32_t* buf, uint32_t* src, + int len) { + if (blend_key) { + for (uint32_t* end = &buf[len]; buf < end; buf += 4, src += 4) { + PackedRGBA8 r = unaligned_load(src); + unaligned_store(buf, pack(blend_pixels_RGBA8( + unaligned_load(buf), unpack(r)))); + } + } else { + memcpy(buf, src, len * sizeof(uint32_t)); + } +} + +static inline void commit_span(uint8_t* buf, PackedR8 r) { + if (blend_key) + r = pack(blend_pixels_R8(unpack(unaligned_load(buf)), unpack(r))); + unaligned_store(buf, r); +} + +UNUSED static inline void commit_solid_span(uint8_t* buf, PackedR8 r, int len) { + if (blend_key) { + auto src = unpack(r); + for (uint8_t* end = &buf[len]; buf < end; buf += 4) { + unaligned_store(buf, pack(blend_pixels_R8( + unpack(unaligned_load(buf)), src))); + } + } else { + fill_n((uint32_t*)buf, len / 4, bit_cast(r)); + } +} + +#define DISPATCH_DRAW_SPAN(self, buf, len) do { \ + int drawn = self->draw_span(buf, len); \ + if (drawn) self->step_interp_inputs(drawn >> 2); \ + for (buf += drawn; drawn < len; drawn += 4, buf += 4) { \ + run(self); \ + commit_span(buf, pack_span(buf)); \ + } \ +} while (0) + +#include "texture.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuninitialized" +#pragma GCC diagnostic ignored "-Wunused-function" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#ifdef __clang__ +#pragma GCC diagnostic ignored "-Wunused-private-field" +#else +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif +#include "load_shader.h" +#pragma GCC diagnostic pop + +typedef vec2_scalar Point2D; +typedef vec4_scalar Point3D; + +struct ClipRect { + float x0; + float y0; + float x1; + float y1; + + ClipRect(const IntRect& i) : x0(i.x0), y0(i.y0), x1(i.x1), y1(i.y1) {} + ClipRect(Texture& t) : ClipRect(ctx->apply_scissor(t.bounds())) {} + + template + bool overlaps(int nump, const P* p) const { + // Generate a mask of which side of the clip rect all of a polygon's points + // fall inside of. This is a cheap conservative estimate of whether the + // bounding box of the polygon might overlap the clip rect, rather than an + // exact test that would require multiple slower line intersections. + int sides = 0; + for (int i = 0; i < nump; i++) { + sides |= p[i].x < x1 ? (p[i].x > x0 ? 1 | 2 : 1) : 2; + sides |= p[i].y < y1 ? (p[i].y > y0 ? 4 | 8 : 4) : 8; + } + return sides == 0xF; + } +}; + +// Helper function for drawing 8-pixel wide chunks of a span with depth buffer. +// Using 8-pixel chunks maximizes use of 16-bit depth values in 128-bit wide +// SIMD register. However, since fragment shaders process only 4 pixels per +// invocation, we need to run fragment shader twice for every 8 pixel batch +// of results we get from the depth test. Perspective is not supported. +template +static inline void draw_depth_span(uint16_t z, P* buf, uint16_t* depth, + int span) { + int skip = 0; + // Check if the fragment shader has an optimized draw specialization. + if (fragment_shader->has_draw_span(buf)) { + // The loop tries to accumulate runs of pixels that passed (len) and + // runs of pixels that failed (skip). This allows it to pass the largest + // possible span in between changes in depth pass or fail status to the + // fragment shader's draw specialer. + int len = 0; + do { + ZMask8 zmask; + // Process depth in 8-pixel chunks. + switch (check_depth8(z, depth, zmask)) { + case 0: // All pixels failed the depth test. + if (len) { + // Flush out passed pixels. + fragment_shader->draw_span(buf - len, len); + len = 0; + } + // Accumulate 2 skipped chunks. + skip += 2; + break; + case -1: // All pixels passed the depth test. + if (skip) { + // Flushed out any skipped chunks. + fragment_shader->skip(skip); + skip = 0; + } + // Accumulate 8 passed pixels. + len += 8; + break; + default: // Mixture of pass and fail results. + if (len) { + // Flush out any passed pixels. + fragment_shader->draw_span(buf - len, len); + len = 0; + } else if (skip) { + // Flush out any skipped chunks. + fragment_shader->skip(skip); + skip = 0; + } + // Run fragment shader on first 4 depth results. + commit_output(buf, unpack(lowHalf(zmask), buf)); + // Run fragment shader on next 4 depth results. + commit_output(buf + 4, unpack(highHalf(zmask), buf)); + break; + } + // Advance to next 8 pixels... + buf += 8; + depth += 8; + span -= 8; + } while (span >= 8); + // Flush out any remaining passed pixels. + if (len) { + fragment_shader->draw_span(buf - len, len); + } + } else { + // No draw specialization, so we can use a simpler loop here that just + // accumulates depth failures, but otherwise invokes fragment shader + // immediately on depth pass. + do { + ZMask8 zmask; + // Process depth in 8-pixel chunks. + switch (check_depth8(z, depth, zmask)) { + case 0: // All pixels failed the depth test. + // Accumulate 2 skipped chunks. + skip += 2; + break; + case -1: // All pixels passed the depth test. + if (skip) { + // Flush out any skipped chunks. + fragment_shader->skip(skip); + skip = 0; + } + // Run the fragment shader for two 4-pixel chunks. + commit_output(buf); + commit_output(buf + 4); + break; + default: // Mixture of pass and fail results. + if (skip) { + // Flush out any skipped chunks. + fragment_shader->skip(skip); + skip = 0; + } + // Run fragment shader on first 4 depth results. + commit_output(buf, unpack(lowHalf(zmask), buf)); + // Run fragment shader on next 4 depth results. + commit_output(buf + 4, unpack(highHalf(zmask), buf)); + break; + } + // Advance to next 8 pixels... + buf += 8; + depth += 8; + span -= 8; + } while (span >= 8); + } + // Flush out any remaining skipped chunks. + if (skip) { + fragment_shader->skip(skip); + } +} + +// Draw a simple span in 4-pixel wide chunks, optionally using depth. +template +static ALWAYS_INLINE void draw_span(P* buf, uint16_t* depth, int span, Z z) { + if (depth) { + // Depth testing is enabled. If perspective is used, Z values will vary + // across the span, we use packDepth to generate 16-bit Z values suitable + // for depth testing based on current values from gl_FragCoord.z. + // Otherwise, for the no-perspective case, we just use the provided Z. + // Process 4-pixel chunks first. + for (; span >= 4; span -= 4, buf += 4, depth += 4) { + commit_output(buf, z(), depth); + } + // If there are any remaining pixels, do a partial chunk. + if (span > 0) { + commit_output(buf, z(), depth, span); + } + } else { + // Process 4-pixel chunks first. + for (; span >= 4; span -= 4, buf += 4) { + commit_output(buf); + } + // If there are any remaining pixels, do a partial chunk. + if (span > 0) { + commit_output(buf, span); + } + } +} + +// Draw spans for each row of a given quad (or triangle) with a constant Z +// value. The quad is assumed convex. It is clipped to fall within the given +// clip rect. In short, this function rasterizes a quad by first finding a +// top most starting point and then from there tracing down the left and right +// sides of this quad until it hits the bottom, outputting a span between the +// current left and right positions at each row along the way. Points are +// assumed to be ordered in either CW or CCW to support this, but currently +// both orders (CW and CCW) are supported and equivalent. +template +static inline void draw_quad_spans(int nump, Point2D p[4], uint16_t z, + Interpolants interp_outs[4], + Texture& colortex, int layer, + Texture& depthtex, + const ClipRect& clipRect) { + // Only triangles and convex quads supported. + assert(nump == 3 || nump == 4); + Point2D l0, r0, l1, r1; + int l0i, r0i, l1i, r1i; + { + // Find the index of the top-most (smallest Y) point from which + // rasterization can start. + int top = nump > 3 && p[3].y < p[2].y + ? (p[0].y < p[1].y ? (p[0].y < p[3].y ? 0 : 3) + : (p[1].y < p[3].y ? 1 : 3)) + : (p[0].y < p[1].y ? (p[0].y < p[2].y ? 0 : 2) + : (p[1].y < p[2].y ? 1 : 2)); + // Helper to find next index in the points array, walking forward. +#define NEXT_POINT(idx) \ + ({ \ + int cur = (idx) + 1; \ + cur < nump ? cur : 0; \ + }) + // Helper to find the previous index in the points array, walking backward. +#define PREV_POINT(idx) \ + ({ \ + int cur = (idx)-1; \ + cur >= 0 ? cur : nump - 1; \ + }) + // Start looking for "left"-side and "right"-side descending edges starting + // from the determined top point. + int next = NEXT_POINT(top); + int prev = PREV_POINT(top); + if (p[top].y == p[next].y) { + // If the next point is on the same row as the top, then advance one more + // time to the next point and use that as the "left" descending edge. + l0i = next; + l1i = NEXT_POINT(next); + // Assume top and prev form a descending "right" edge, as otherwise this + // will be a collapsed polygon and harmlessly bail out down below. + r0i = top; + r1i = prev; + } else if (p[top].y == p[prev].y) { + // If the prev point is on the same row as the top, then advance to the + // prev again and use that as the "right" descending edge. + // Assume top and next form a non-empty descending "left" edge. + l0i = top; + l1i = next; + r0i = prev; + r1i = PREV_POINT(prev); + } else { + // Both next and prev are on distinct rows from top, so both "left" and + // "right" edges are non-empty/descending. + l0i = r0i = top; + l1i = next; + r1i = prev; + } + // Load the points from the indices. + l0 = p[l0i]; // Start of left edge + r0 = p[r0i]; // End of left edge + l1 = p[l1i]; // Start of right edge + r1 = p[r1i]; // End of right edge + // debugf("l0: %d(%f,%f), r0: %d(%f,%f) -> l1: %d(%f,%f), r1: + // %d(%f,%f)\n", l0i, l0.x, l0.y, r0i, r0.x, r0.y, l1i, l1.x, l1.y, r1i, + // r1.x, r1.y); + } + + struct Edge + { + float yScale; + float xSlope; + float x; + Interpolants interpSlope; + Interpolants interp; + + Edge(float y, const Point2D& p0, const Point2D& p1, + const Interpolants& i0, const Interpolants& i1) : + // Inverse Y scale for slope calculations. Avoid divide on 0-length edge. + // Later checks below ensure that Y <= p1.y, or otherwise we don't use + // this edge. We just need to guard against Y == p1.y == p0.y. In that + // case, Y - p0.y == 0 and will cancel out the slopes below, except if + // yScale is Inf for some reason (or worse, NaN), which 1/(p1.y-p0.y) + // might produce if we don't bound it. + yScale(1.0f / max(p1.y - p0.y, 1.0f / 256)), + // Calculate dX/dY slope + xSlope((p1.x - p0.x) * yScale), + // Initialize current X based on Y and slope + x(p0.x + (y - p0.y) * xSlope), + // Calculate change in interpolants per change in Y + interpSlope((i1 - i0) * yScale), + // Initialize current interpolants based on Y and slope + interp(i0 + (y - p0.y) * interpSlope) + {} + + void nextRow() { + // step current X and interpolants to next row from slope + x += xSlope; + interp += interpSlope; + } + }; + + // Vertex selection above should result in equal left and right start rows + assert(l0.y == r0.y); + // Find the start y, clip to within the clip rect, and round to row center. + float y = floor(max(l0.y, clipRect.y0) + 0.5f) + 0.5f; + // Initialize left and right edges from end points and start Y + Edge left(y, l0, l1, interp_outs[l0i], interp_outs[l1i]); + Edge right(y, r0, r1, interp_outs[r0i], interp_outs[r1i]); + // Get pointer to color buffer and depth buffer at current Y + P* fbuf = (P*)colortex.sample_ptr(0, int(y), layer, sizeof(P)); + uint16_t* fdepth = + (uint16_t*)depthtex.sample_ptr(0, int(y), 0, sizeof(uint16_t)); + // Loop along advancing Ys, rasterizing spans at each row + float checkY = min(min(l1.y, r1.y), clipRect.y1); + for (;;) { + // Check if we maybe passed edge ends or outside clip rect... + if (y > checkY) { + // If we're outside the clip rect, we're done. + if (y > clipRect.y1) break; + // Helper to find the next non-duplicate vertex that doesn't loop back. +#define STEP_EDGE(e0i, e0, e1i, e1, STEP_POINT, end) \ + for (;;) { \ + /* Set new start of edge to be end of old edge */ \ + e0i = e1i; \ + e0 = e1; \ + /* Set new end of edge to next point */ \ + e1i = STEP_POINT(e1i); \ + e1 = p[e1i]; \ + /* If the edge is descending, use it. */ \ + if (e1.y > e0.y) break; \ + /* If the edge is ascending or crossed the end, we're done. */ \ + if (e1.y < e0.y || e0i == end) return; \ + /* Otherwise, it's a duplicate, so keep searching. */ \ + } + // Check if Y advanced past the end of the left edge + if (y > l1.y) { + // Step to next left edge past Y and reset edge interpolants. + do { STEP_EDGE(l0i, l0, l1i, l1, NEXT_POINT, r1i); } while (y > l1.y); + left = Edge(y, l0, l1, interp_outs[l0i], interp_outs[l1i]); + } + // Check if Y advanced past the end of the right edge + if (y > r1.y) { + // Step to next right edge past Y and reset edge interpolants. + do { STEP_EDGE(r0i, r0, r1i, r1, PREV_POINT, l1i); } while (y > r1.y); + right = Edge(y, r0, r1, interp_outs[r0i], interp_outs[r1i]); + } + // Reset check condition for next time around. + checkY = min(min(l1.y, r1.y), clipRect.y1); + } + // lx..rx form the bounds of the span. WR does not use backface culling, + // so we need to use min/max to support the span in either orientation. + // Clip the span to fall within the clip rect and then round to nearest + // column. + int startx = int(max(min(left.x, right.x), clipRect.x0) + 0.5f); + int endx = int(min(max(left.x, right.x), clipRect.x1) + 0.5f); + // Check if span is non-empty. + int span = endx - startx; + if (span > 0) { + ctx->shaded_rows++; + ctx->shaded_pixels += span; + // Advance color/depth buffer pointers to the start of the span. + P* buf = fbuf + startx; + // Check if the we will need to use depth-buffer or discard on this span. + uint16_t* depth = depthtex.buf != nullptr ? fdepth + startx : nullptr; + bool use_discard = fragment_shader->use_discard(); + if (depthtex.delay_clear) { + // Delayed clear is enabled for the depth buffer. Check if this row + // needs to be cleared. + int yi = int(y); + uint32_t& mask = depthtex.cleared_rows[yi / 32]; + if ((mask & (1 << (yi & 31))) == 0) { + // The depth buffer is unitialized on this row, but we know it will + // thus be cleared entirely to the clear value. This lets us quickly + // check the constant Z value of the quad against the clear Z to know + // if the entire span passes or fails the depth test all at once. + switch (ctx->depthfunc) { + case GL_LESS: + if (int16_t(z) < int16_t(depthtex.clear_val)) + break; + else + goto next_span; + case GL_LEQUAL: + if (int16_t(z) <= int16_t(depthtex.clear_val)) + break; + else + goto next_span; + } + // If we got here, we passed the depth test. + if (ctx->depthmask) { + // Depth writes are enabled, so we need to initialize depth. + mask |= 1 << (yi & 31); + depthtex.delay_clear--; + if (use_discard) { + // if discard is enabled, we don't know what pixels may be + // written to, so we have to clear the entire row. + force_clear_row(depthtex, yi); + } else { + // Otherwise, we only need to clear the pixels that fall outside + // the current span on this row. + if (startx > 0 || endx < depthtex.width) { + force_clear_row(depthtex, yi, startx, endx); + } + // Fill in the span's Z values with constant Z. + clear_buffer(depthtex, z, 0, + IntRect{startx, yi, endx, yi + 1}); + // We already passed the depth test, so no need to test depth + // any more. + depth = nullptr; + } + } else { + // No depth writes, so don't clear anything, and no need to test. + depth = nullptr; + } + } + } + if (colortex.delay_clear) { + // Delayed clear is enabled for the color buffer. Check if needs clear. + int yi = int(y); + uint32_t& mask = colortex.cleared_rows[yi / 32]; + if ((mask & (1 << (yi & 31))) == 0) { + mask |= 1 << (yi & 31); + colortex.delay_clear--; + if (depth || blend_key || use_discard) { + // If depth test, blending, or discard is used, old color values + // might be sampled, so we need to clear the entire row to fill it. + force_clear_row

(colortex, yi); + } else if (startx > 0 || endx < colortex.width) { + // Otherwise, we only need to clear the row outside of the span. + // The fragment shader will fill the row within the span itself. + force_clear_row

(colortex, yi, startx, endx); + } + } + } + // Initialize fragment shader interpolants to current span position. + fragment_shader->gl_FragCoord.x = init_interp(startx + 0.5f, 1); + fragment_shader->gl_FragCoord.y = y; + { + // Change in interpolants is difference between current right and left + // edges per the change in right and left X. + Interpolants step = + (right.interp - left.interp) * (1.0f / (right.x - left.x)); + // Advance current interpolants to X at start of span. + Interpolants o = left.interp + step * (startx + 0.5f - left.x); + fragment_shader->init_span(&o, &step, 4.0f); + } + if (!use_discard) { + // Fast paths for the case where fragment discard is not used. + if (depth) { + // If depth is used, we want to process spans in 8-pixel chunks to + // maximize sampling and testing 16-bit depth values within the 128- + // bit width of a SIMD register. + if (span >= 8) { + // Specializations for supported depth functions depending on + // whether depth writes are enabled. + if (ctx->depthfunc == GL_LEQUAL) { + if (ctx->depthmask) + draw_depth_span(z, buf, depth, span); + else + draw_depth_span(z, buf, depth, span); + } else { + if (ctx->depthmask) + draw_depth_span(z, buf, depth, span); + else + draw_depth_span(z, buf, depth, span); + } + // Advance buffers past processed chunks. + buf += span & ~7; + depth += span & ~7; + span &= 7; + } + } else { + // Check if the fragment shader has an optimized draw specialization. + if (span >= 4 && fragment_shader->has_draw_span(buf)) { + // Draw specialization expects 4-pixel chunks. + int len = span & ~3; + fragment_shader->draw_span(buf, len); + buf += len; + span &= 3; + } + } + draw_span(buf, depth, span, [=]{ return z; }); + } else { + // If discard is used, then use slower fallbacks. This should be rare. + // Just needs to work, doesn't need to be too fast yet... + draw_span(buf, depth, span, [=]{ return z; }); + } + } + next_span: + // Advance Y and edge interpolants to next row. + y++; + left.nextRow(); + right.nextRow(); + // Advance buffers to next row. + fbuf += colortex.stride(sizeof(P)) / sizeof(P); + fdepth += depthtex.stride(sizeof(uint16_t)) / sizeof(uint16_t); + } +} + +// Draw perspective-correct spans for a convex quad that has been clipped to +// the near and far Z planes, possibly producing a clipped convex polygon with +// more than 4 sides. This assumes the Z value will vary across the spans and +// requires interpolants to factor in W values. This tends to be slower than +// the simpler 2D draw_quad_spans above, especially since we can't optimize the +// depth test easily when Z values, and should be used only rarely if possible. +template +static inline void draw_perspective_spans(int nump, Point3D* p, + Interpolants* interp_outs, + Texture& colortex, int layer, + Texture& depthtex, + const ClipRect& clipRect) { + Point3D l0, r0, l1, r1; + int l0i, r0i, l1i, r1i; + { + // Find the index of the top-most point (smallest Y) from which + // rasterization can start. + int top = 0; + for (int i = 1; i < nump; i++) { + if (p[i].y < p[top].y) { + top = i; + } + } + // Find left-most top point, the start of the left descending edge. + // Advance forward in the points array, searching at most nump points + // in case the polygon is flat. + l0i = top; + for (int i = top + 1; i < nump && p[i].y == p[top].y; i++) { + l0i = i; + } + if (l0i == nump - 1) { + for (int i = 0; i <= top && p[i].y == p[top].y; i++) { + l0i = i; + } + } + // Find right-most top point, the start of the right descending edge. + // Advance backward in the points array, searching at most nump points. + r0i = top; + for (int i = top - 1; i >= 0 && p[i].y == p[top].y; i--) { + r0i = i; + } + if (r0i == 0) { + for (int i = nump - 1; i >= top && p[i].y == p[top].y; i--) { + r0i = i; + } + } + // End of left edge is next point after left edge start. + l1i = NEXT_POINT(l0i); + // End of right edge is prev point after right edge start. + r1i = PREV_POINT(r0i); + l0 = p[l0i]; // Start of left edge + r0 = p[r0i]; // End of left edge + l1 = p[l1i]; // Start of right edge + r1 = p[r1i]; // End of right edge + } + + struct Edge + { + float yScale; + // Current coordinates for edge. Where in the 2D case of draw_quad_spans, + // it is enough to just track the X coordinate as we advance along the rows, + // for the perspective case we also need to keep track of Z and W. For + // simplicity, we just use the full 3D point to track all these coordinates. + Point3D pSlope; + Point3D p; + Interpolants interpSlope; + Interpolants interp; + + Edge(float y, const Point3D& p0, const Point3D& p1, + const Interpolants& i0, const Interpolants& i1) : + // Inverse Y scale for slope calculations. Avoid divide on 0-length edge. + yScale(1.0f / max(p1.y - p0.y, 1.0f / 256)), + // Calculate dX/dY slope + pSlope((p1 - p0) * yScale), + // Initialize current coords based on Y and slope + p(p0 + (y - p0.y) * pSlope), + // Crucially, these interpolants must be scaled by the point's 1/w value, + // which allows linear interpolation in a perspective-correct manner. + // This will be canceled out inside the fragment shader later. + // Calculate change in interpolants per change in Y + interpSlope((i1 * p1.w - i0 * p0.w) * yScale), + // Initialize current interpolants based on Y and slope + interp(i0 * p0.w + (y - p0.y) * interpSlope) + {} + + float x() const { return p.x; } + vec2_scalar zw() const { return {p.z, p.w}; } + + void nextRow() { + // step current coords and interpolants to next row from slope + p += pSlope; + interp += interpSlope; + } + }; + + // Vertex selection above should result in equal left and right start rows + assert(l0.y == r0.y); + // Find the start y, clip to within the clip rect, and round to row center. + float y = floor(max(l0.y, clipRect.y0) + 0.5f) + 0.5f; + // Initialize left and right edges from end points and start Y + Edge left(y, l0, l1, interp_outs[l0i], interp_outs[l1i]); + Edge right(y, r0, r1, interp_outs[r0i], interp_outs[r1i]); + // Get pointer to color buffer and depth buffer at current Y + P* fbuf = (P*)colortex.sample_ptr(0, int(y), layer, sizeof(P)); + uint16_t* fdepth = + (uint16_t*)depthtex.sample_ptr(0, int(y), 0, sizeof(uint16_t)); + // Loop along advancing Ys, rasterizing spans at each row + float checkY = min(min(l1.y, r1.y), clipRect.y1); + for (;;) { + // Check if we maybe passed edge ends or outside clip rect... + if (y > checkY) { + // If we're outside the clip rect, we're done. + if (y > clipRect.y1) break; + // Check if Y advanced past the end of the left edge + if (y > l1.y) { + // Step to next left edge past Y and reset edge interpolants. + do { STEP_EDGE(l0i, l0, l1i, l1, NEXT_POINT, r1i); } while (y > l1.y); + left = Edge(y, l0, l1, interp_outs[l0i], interp_outs[l1i]); + } + // Check if Y advanced past the end of the right edge + if (y > r1.y) { + // Step to next right edge past Y and reset edge interpolants. + do { STEP_EDGE(r0i, r0, r1i, r1, PREV_POINT, l1i); } while (y > r1.y); + right = Edge(y, r0, r1, interp_outs[r0i], interp_outs[r1i]); + } + // Reset check condition for next time around. + checkY = min(min(l1.y, r1.y), clipRect.y1); + } + // lx..rx form the bounds of the span. WR does not use backface culling, + // so we need to use min/max to support the span in either orientation. + // Clip the span to fall within the clip rect and then round to nearest + // column. + int startx = int(max(min(left.x(), right.x()), clipRect.x0) + 0.5f); + int endx = int(min(max(left.x(), right.x()), clipRect.x1) + 0.5f); + // Check if span is non-empty. + int span = endx - startx; + if (span > 0) { + ctx->shaded_rows++; + ctx->shaded_pixels += span; + // Advance color/depth buffer pointers to the start of the span. + P* buf = fbuf + startx; + // Check if the we will need to use depth-buffer or discard on this span. + uint16_t* depth = depthtex.buf != nullptr ? fdepth + startx : nullptr; + bool use_discard = fragment_shader->use_discard(); + if (depthtex.delay_clear) { + // Delayed clear is enabled for the depth buffer. Check if this row + // needs to be cleared. + int yi = int(y); + uint32_t& mask = depthtex.cleared_rows[yi / 32]; + if ((mask & (1 << (yi & 31))) == 0) { + mask |= 1 << (yi & 31); + depthtex.delay_clear--; + // Since Z varies across the span, it's easier to just clear the + // row and rely on later depth testing. If necessary, this could be + // optimized to test against the start and end Z values of the span + // here. + force_clear_row(depthtex, yi); + } + } + if (colortex.delay_clear) { + // Delayed clear is enabled for the color buffer. Check if needs clear. + int yi = int(y); + uint32_t& mask = colortex.cleared_rows[yi / 32]; + if ((mask & (1 << (yi & 31))) == 0) { + mask |= 1 << (yi & 31); + colortex.delay_clear--; + if (depth || blend_key || use_discard) { + // If depth test, blending, or discard is used, old color values + // might be sampled, so we need to clear the entire row to fill it. + force_clear_row

(colortex, yi); + } else if (startx > 0 || endx < colortex.width) { + // Otherwise, we only need to clear the row outside of the span. + // The fragment shader will fill the row within the span itself. + force_clear_row

(colortex, yi, startx, endx); + } + } + } + // Initialize fragment shader interpolants to current span position. + fragment_shader->gl_FragCoord.x = init_interp(startx + 0.5f, 1); + fragment_shader->gl_FragCoord.y = y; + { + // Calculate the fragment Z and W change per change in fragment X step. + vec2_scalar stepZW = + (right.zw() - left.zw()) * (1.0f / (right.x() - left.x())); + // Calculate initial Z and W values for span start. + vec2_scalar zw = left.zw() + stepZW * (startx + 0.5f - left.x()); + // Set fragment shader's Z and W values so that it can use them to + // cancel out the 1/w baked into the interpolants. + fragment_shader->gl_FragCoord.z = init_interp(zw.x, stepZW.x); + fragment_shader->gl_FragCoord.w = init_interp(zw.y, stepZW.y); + fragment_shader->stepZW = stepZW * 4.0f; + // Change in interpolants is difference between current right and left + // edges per the change in right and left X. The left and right + // interpolant values were previously multipled by 1/w, so the step and + // initial span values take this into account. + Interpolants step = + (right.interp - left.interp) * (1.0f / (right.x() - left.x())); + // Advance current interpolants to X at start of span. + Interpolants o = left.interp + step * (startx + 0.5f - left.x()); + fragment_shader->init_span(&o, &step, 4.0f); + } + if (!use_discard) { + // No discard is used. Common case. + draw_span(buf, depth, span, packDepth); + } else { + // Discard is used. Rare. + draw_span(buf, depth, span, packDepth); + } + } + // Advance Y and edge interpolants to next row. + y++; + left.nextRow(); + right.nextRow(); + // Advance buffers to next row. + fbuf += colortex.stride(sizeof(P)) / sizeof(P); + fdepth += depthtex.stride(sizeof(uint16_t)) / sizeof(uint16_t); + } +} + +// Clip a primitive against both sides of a view-frustum axis, producing +// intermediate vertexes with interpolated attributes that will no longer +// intersect the selected axis planes. This assumes the primitive is convex +// and should produce at most N+2 vertexes for each invocation (only in the +// worst case where one point falls outside on each of the opposite sides +// with the rest of the points inside). +template +static int clip_side(int nump, Point3D* p, Interpolants* interp, Point3D* outP, + Interpolants* outInterp) { + int numClip = 0; + Point3D prev = p[nump - 1]; + Interpolants prevInterp = interp[nump - 1]; + float prevCoord = prev.select(AXIS); + // Coordinate must satisfy -W <= C <= W. Determine if it is outside, and + // if so, remember which side it is outside of. + int prevSide = prevCoord < -prev.w ? -1 : (prevCoord > prev.w ? 1 : 0); + // Loop through points, finding edges that cross the planes by evaluating + // the side at each point. + for (int i = 0; i < nump; i++) { + Point3D cur = p[i]; + Interpolants curInterp = interp[i]; + float curCoord = cur.select(AXIS); + int curSide = curCoord < -cur.w ? -1 : (curCoord > cur.w ? 1 : 0); + // Check if the previous and current end points are on different sides. + if (curSide != prevSide) { + // One of the edge's end points is outside the plane with the other + // inside the plane. Find the offset where it crosses the plane and + // adjust the point and interpolants to there. + if (prevSide) { + // Edge that was previously outside crosses inside. + // Evaluate plane equation for previous and current end-point + // based on previous side and calculate relative offset. + assert(numClip < nump + 2); + float prevDist = prevCoord - prevSide * prev.w; + float curDist = curCoord - prevSide * cur.w; + float k = prevDist / (prevDist - curDist); + outP[numClip] = prev + (cur - prev) * k; + outInterp[numClip] = prevInterp + (curInterp - prevInterp) * k; + numClip++; + } + if (curSide) { + // Edge that was previously inside crosses outside. + // Evaluate plane equation for previous and current end-point + // based on current side and calculate relative offset. + assert(numClip < nump + 2); + float prevDist = prevCoord - curSide * prev.w; + float curDist = curCoord - curSide * cur.w; + float k = prevDist / (prevDist - curDist); + outP[numClip] = prev + (cur - prev) * k; + outInterp[numClip] = prevInterp + (curInterp - prevInterp) * k; + numClip++; + } + } + if (!curSide) { + // The current end point is inside the plane, so output point unmodified. + assert(numClip < nump + 2); + outP[numClip] = cur; + outInterp[numClip] = curInterp; + numClip++; + } + prev = cur; + prevInterp = curInterp; + prevCoord = curCoord; + prevSide = curSide; + } + return numClip; +} + +// Helper function to dispatch to perspective span drawing with points that +// have already been transformed and clipped. +static inline void draw_perspective_clipped(int nump, Point3D* p_clip, + Interpolants* interp_clip, + Texture& colortex, int layer, + Texture& depthtex) { + // If polygon is ouside clip rect, nothing to draw. + ClipRect clipRect(colortex); + if (!clipRect.overlaps(nump, p_clip)) { + return; + } + + // Finally draw perspective-correct spans for the polygon. + if (colortex.internal_format == GL_RGBA8) { + draw_perspective_spans(nump, p_clip, interp_clip, colortex, + layer, depthtex, clipRect); + } else if (colortex.internal_format == GL_R8) { + draw_perspective_spans(nump, p_clip, interp_clip, colortex, + layer, depthtex, clipRect); + } else { + assert(false); + } +} + +// Draws a perspective-correct 3D primitive with varying Z value, as opposed +// to a simple 2D planar primitive with a constant Z value that could be +// trivially Z rejected. This requires clipping the primitive against the near +// and far planes to ensure it stays within the valid Z-buffer range. The Z +// and W of each fragment of the primitives are interpolated across the +// generated spans and then depth-tested as appropriate. +// Additionally, vertex attributes must be interpolated with perspective- +// correction by dividing by W before interpolation, and then later multiplied +// by W again to produce the final correct attribute value for each fragment. +// This process is expensive and should be avoided if possible for primitive +// batches that are known ahead of time to not need perspective-correction. +static void draw_perspective(int nump, + Interpolants interp_outs[4], + Texture& colortex, int layer, + Texture& depthtex) { + // Convert output of vertex shader to screen space. + vec4 pos = vertex_shader->gl_Position; + vec3_scalar scale = + vec3_scalar(ctx->viewport.width(), ctx->viewport.height(), 1) * 0.5f; + vec3_scalar offset = + vec3_scalar(ctx->viewport.x0, ctx->viewport.y0, 0.0f) + scale; + if (test_none(pos.z <= -pos.w || pos.z >= pos.w)) { + // No points cross the near or far planes, so no clipping required. + // Just divide coords by W and convert to viewport. + Float w = 1.0f / pos.w; + vec3 screen = pos.sel(X, Y, Z) * w * scale + offset; + Point3D p[4] = { + {screen.x.x, screen.y.x, screen.z.x, w.x}, + {screen.x.y, screen.y.y, screen.z.y, w.y}, + {screen.x.z, screen.y.z, screen.z.z, w.z}, + {screen.x.w, screen.y.w, screen.z.w, w.w} + }; + draw_perspective_clipped(nump, p, interp_outs, colortex, layer, depthtex); + } else { + // Points cross the near or far planes, so we need to clip. + // Start with the original 3 or 4 points... + Point3D p[4] = { + {pos.x.x, pos.y.x, pos.z.x, pos.w.x}, + {pos.x.y, pos.y.y, pos.z.y, pos.w.y}, + {pos.x.z, pos.y.z, pos.z.z, pos.w.z}, + {pos.x.w, pos.y.w, pos.z.w, pos.w.w} + }; + // Clipping can expand the points by 1 for each of 6 view frustum planes. + Point3D p_clip[4 + 6]; + Interpolants interp_clip[4 + 6]; + // Clip against near and far Z planes. + nump = clip_side(nump, p, interp_outs, p_clip, interp_clip); + // If no points are left inside the view frustum, there's nothing to draw. + if (nump < 3) { + return; + } + // After clipping against only the near and far planes, we might still + // produce points where W = 0, exactly at the camera plane. OpenGL specifies + // that for clip coordinates, points must satisfy: + // -W <= X <= W + // -W <= Y <= W + // -W <= Z <= W + // When Z = W = 0, this is trivially satisfied, but when we transform and + // divide by W below it will produce a divide by 0. Usually we want to only + // clip Z to avoid the extra work of clipping X and Y. We can still project + // points that fall outside the view frustum X and Y so long as Z is valid. + // The span drawing code will then ensure X and Y are clamped to viewport + // boundaries. However, in the Z = W = 0 case, sometimes clipping X and Y, + // will push W further inside the view frustum so that it is no longer 0, + // allowing us to finally proceed to projecting the points to the screen. + for (int i = 0; i < nump; i++) { + // Found an invalid W, so need to clip against X and Y... + if (p_clip[i].w <= 0.0f) { + // Ping-pong p_clip -> p_tmp -> p_clip. + Point3D p_tmp[4 + 6]; + Interpolants interp_tmp[4 + 6]; + nump = clip_side(nump, p_clip, interp_clip, p_tmp, interp_tmp); + if (nump < 3) return; + nump = clip_side(nump, p_tmp, interp_tmp, p_clip, interp_clip); + if (nump < 3) return; + // After clipping against X and Y planes, there's still points left + // to draw, so proceed to trying projection now... + break; + } + } + // Divide coords by W and convert to viewport. + for (int i = 0; i < nump; i++) { + float w = 1.0f / p_clip[i].w; + p_clip[i] = Point3D(p_clip[i].sel(X, Y, Z) * w * scale + offset, w); + } + draw_perspective_clipped(nump, p_clip, interp_clip, colortex, layer, + depthtex); + } +} + +static void draw_quad(int nump, Texture& colortex, int layer, + Texture& depthtex) { + // Run vertex shader once for the primitive's vertices. + // Reserve space for 6 sets of interpolants, in case we need to clip against + // near and far planes in the perspective case. + Interpolants interp_outs[4]; + vertex_shader->run_primitive((char*)interp_outs, sizeof(Interpolants)); + vec4 pos = vertex_shader->gl_Position; + // Check if any vertex W is different from another. If so, use perspective. + if (test_any(pos.w != pos.w.x)) { + draw_perspective(nump, interp_outs, colortex, layer, depthtex); + return; + } + + // Convert output of vertex shader to screen space. + // Divide coords by W and convert to viewport. + float w = 1.0f / pos.w.x; + vec2 screen = + (pos.sel(X, Y) * w + 1) * 0.5f * + vec2_scalar(ctx->viewport.width(), ctx->viewport.height()) + + vec2_scalar(ctx->viewport.x0, ctx->viewport.y0); + Point2D p[4] = {{screen.x.x, screen.y.x}, + {screen.x.y, screen.y.y}, + {screen.x.z, screen.y.z}, + {screen.x.w, screen.y.w}}; + + // If quad is ouside clip rect, nothing to draw. + ClipRect clipRect(colortex); + if (!clipRect.overlaps(nump, p)) { + return; + } + + // Since the quad is assumed 2D, Z is constant across the quad. + float screenZ = (pos.z.x * w + 1) * 0.5f; + if (screenZ < 0 || screenZ > 1) { + // Z values would cross the near or far plane, so just bail. + return; + } + // Since Z doesn't need to be interpolated, just set the fragment shader's + // Z and W values here, once and for all fragment shader invocations. + // SSE2 does not support unsigned comparison, so bias Z to be negative. + uint16_t z = uint16_t(0xFFFF * screenZ) - 0x8000; + fragment_shader->gl_FragCoord.z = screenZ; + fragment_shader->gl_FragCoord.w = w; + + // Finally draw 2D spans for the quad. Currently only supports drawing to + // RGBA8 and R8 color buffers. + if (colortex.internal_format == GL_RGBA8) { + draw_quad_spans(nump, p, z, interp_outs, colortex, layer, + depthtex, clipRect); + } else if (colortex.internal_format == GL_R8) { + draw_quad_spans(nump, p, z, interp_outs, colortex, layer, depthtex, + clipRect); + } else { + assert(false); + } +} + +void VertexArray::validate() { + int last_enabled = -1; + for (int i = 0; i <= max_attrib; i++) { + VertexAttrib& attr = attribs[i]; + if (attr.enabled) { + // VertexArray &v = ctx->vertex_arrays[attr.vertex_array]; + Buffer& vertex_buf = ctx->buffers[attr.vertex_buffer]; + attr.buf = vertex_buf.buf; + attr.buf_size = vertex_buf.size; + // debugf("%d %x %d %d %d %d\n", i, attr.type, attr.size, attr.stride, + // attr.offset, attr.divisor); + last_enabled = i; + } + } + max_attrib = last_enabled; +} + +template +static inline void draw_elements(GLsizei count, GLsizei instancecount, + Buffer& indices_buf, size_t offset, + VertexArray& v, Texture& colortex, int layer, + Texture& depthtex) { + assert((offset & (sizeof(INDEX) - 1)) == 0); + INDEX* indices = (INDEX*)(indices_buf.buf + offset); + count = min(count, + (GLsizei)((indices_buf.size - offset) / sizeof(INDEX))); + // Triangles must be indexed at offsets 0, 1, 2. + // Quads must be successive triangles indexed at offsets 0, 1, 2, 2, 1, 3. + if (count == 6 && indices[1] == indices[0] + 1 && + indices[2] == indices[0] + 2 && indices[5] == indices[0] + 3) { + assert(indices[3] == indices[0] + 2 && indices[4] == indices[0] + 1); + // Fast path - since there is only a single quad, we only load per-vertex + // attribs once for all instances, as they won't change across instances + // or within an instance. + vertex_shader->load_attribs(v.attribs, indices[0], 0, 4); + draw_quad(4, colortex, layer, depthtex); + for (GLsizei instance = 1; instance < instancecount; instance++) { + vertex_shader->load_attribs(v.attribs, indices[0], instance, 0); + draw_quad(4, colortex, layer, depthtex); + } + } else { + for (GLsizei instance = 0; instance < instancecount; instance++) { + for (GLsizei i = 0; i + 3 <= count; i += 3) { + if (indices[i + 1] != indices[i] + 1 || + indices[i + 2] != indices[i] + 2) { + continue; + } + int nump = 3; + if (i + 6 <= count && indices[i + 5] == indices[i] + 3) { + assert(indices[i + 3] == indices[i] + 2 && + indices[i + 4] == indices[i] + 1); + nump = 4; + i += 3; + } + vertex_shader->load_attribs(v.attribs, indices[i], instance, nump); + draw_quad(nump, colortex, layer, depthtex); + } + } + } +} + +extern "C" { + +void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, + void* indicesptr, GLsizei instancecount) { + assert(mode == GL_TRIANGLES); + assert(type == GL_UNSIGNED_SHORT || type == GL_UNSIGNED_INT); + if (count <= 0 || instancecount <= 0) { + return; + } + + Framebuffer& fb = *get_framebuffer(GL_DRAW_FRAMEBUFFER); + Texture& colortex = ctx->textures[fb.color_attachment]; + if (!colortex.buf) { + return; + } + assert(colortex.internal_format == GL_RGBA8 || + colortex.internal_format == GL_R8); + Texture& depthtex = ctx->textures[ctx->depthtest ? fb.depth_attachment : 0]; + if (depthtex.buf) { + assert(depthtex.internal_format == GL_DEPTH_COMPONENT16); + assert(colortex.width == depthtex.width && + colortex.height == depthtex.height); + } + + Buffer& indices_buf = ctx->buffers[ctx->element_array_buffer_binding]; + size_t offset = (size_t)indicesptr; + if (!indices_buf.buf || offset >= indices_buf.size) { + return; + } + + // debugf("current_vertex_array %d\n", ctx->current_vertex_array); + // debugf("indices size: %d\n", indices_buf.size); + VertexArray& v = ctx->vertex_arrays[ctx->current_vertex_array]; + if (ctx->validate_vertex_array) { + ctx->validate_vertex_array = false; + v.validate(); + } + +#ifndef NDEBUG + // uint64_t start = get_time_value(); +#endif + + ctx->shaded_rows = 0; + ctx->shaded_pixels = 0; + + vertex_shader->init_batch(); + + if (type == GL_UNSIGNED_SHORT) { + draw_elements(count, instancecount, indices_buf, offset, v, + colortex, fb.layer, depthtex); + } else if (type == GL_UNSIGNED_INT) { + draw_elements(count, instancecount, indices_buf, offset, v, + colortex, fb.layer, depthtex); + } else { + assert(false); + } + + if (ctx->samples_passed_query) { + Query& q = ctx->queries[ctx->samples_passed_query]; + q.value += ctx->shaded_pixels; + } + +#ifndef NDEBUG + // uint64_t end = get_time_value(); + // debugf("draw(%d): %fms for %d pixels in %d rows (avg %f pixels/row, %f + // ns/pixel)\n", instancecount, double(end - start)/(1000.*1000.), + // ctx->shaded_pixels, ctx->shaded_rows, + // double(ctx->shaded_pixels)/ctx->shaded_rows, double(end - + // start)/max(ctx->shaded_pixels, 1)); +#endif +} + +} // extern "C" + +template +static inline void scale_row(P* dst, int dstWidth, const P* src, int srcWidth, + int span) { + int frac = 0; + for (P* end = dst + span; dst < end; dst++) { + *dst = *src; + // Step source according to width ratio. + for (frac += srcWidth; frac >= dstWidth; frac -= dstWidth) { + src++; + } + } +} + +static void scale_blit(Texture& srctex, const IntRect& srcReq, int srcZ, + Texture& dsttex, const IntRect& dstReq, int dstZ, + bool invertY) { + // Cache scaling ratios + int srcWidth = srcReq.width(); + int srcHeight = srcReq.height(); + int dstWidth = dstReq.width(); + int dstHeight = dstReq.height(); + // Compute valid dest bounds + IntRect dstBounds = dsttex.sample_bounds(dstReq, invertY); + // Compute valid source bounds + // Scale source to dest, rounding inward to avoid sampling outside source + IntRect srcBounds = srctex.sample_bounds(srcReq) + .scale(srcWidth, srcHeight, dstWidth, dstHeight, true); + // Limit dest sampling bounds to overlap source bounds + dstBounds.intersect(srcBounds); + // Check if sampling bounds are empty + if (dstBounds.is_empty()) { + return; + } + // Compute final source bounds from clamped dest sampling bounds + srcBounds = IntRect(dstBounds) + .scale(dstWidth, dstHeight, srcWidth, srcHeight); + // Calculate source and dest pointers from clamped offsets + int bpp = srctex.bpp(); + int srcStride = srctex.stride(bpp); + int destStride = dsttex.stride(bpp); + char* dest = dsttex.sample_ptr(dstReq, dstBounds, dstZ, invertY); + char* src = srctex.sample_ptr(srcReq, srcBounds, srcZ); + // Inverted Y must step downward along dest rows + if (invertY) { + destStride = -destStride; + } + int span = dstBounds.width(); + int frac = 0; + for (int rows = dstBounds.height(); rows > 0; rows--) { + if (srcWidth == dstWidth) { + // No scaling, so just do a fast copy. + memcpy(dest, src, span * bpp); + } else { + // Do scaling with different source and dest widths. + switch (bpp) { + case 1: + scale_row((uint8_t*)dest, dstWidth, (uint8_t*)src, srcWidth, span); + break; + case 2: + scale_row((uint16_t*)dest, dstWidth, (uint16_t*)src, srcWidth, span); + break; + case 4: + scale_row((uint32_t*)dest, dstWidth, (uint32_t*)src, srcWidth, span); + break; + default: + assert(false); + break; + } + } + dest += destStride; + // Step source according to height ratio. + for (frac += srcHeight; frac >= dstHeight; frac -= dstHeight) { + src += srcStride; + } + } +} + +static void linear_row(uint32_t* dest, int span, const vec2_scalar& srcUV, + float srcDU, int srcZOffset, sampler2DArray sampler) { + vec2 uv = init_interp(srcUV, vec2_scalar(srcDU, 0.0f)); + for (; span >= 4; span -= 4) { + auto srcpx = textureLinearPackedRGBA8(sampler, ivec2(uv), srcZOffset); + unaligned_store(dest, srcpx); + dest += 4; + uv.x += 4 * srcDU; + } + if (span > 0) { + auto srcpx = textureLinearPackedRGBA8(sampler, ivec2(uv), srcZOffset); + auto mask = span_mask_RGBA8(span); + auto dstpx = unaligned_load(dest); + unaligned_store(dest, (mask & dstpx) | (~mask & srcpx)); + } +} + +static void linear_row(uint8_t* dest, int span, const vec2_scalar& srcUV, + float srcDU, int srcZOffset, sampler2DArray sampler) { + vec2 uv = init_interp(srcUV, vec2_scalar(srcDU, 0.0f)); + for (; span >= 4; span -= 4) { + auto srcpx = textureLinearPackedR8(sampler, ivec2(uv), srcZOffset); + unaligned_store(dest, pack(srcpx)); + dest += 4; + uv.x += 4 * srcDU; + } + if (span > 0) { + auto srcpx = textureLinearPackedR8(sampler, ivec2(uv), srcZOffset); + auto mask = span_mask_R8(span); + auto dstpx = unpack(unaligned_load(dest)); + unaligned_store(dest, pack((mask & dstpx) | (~mask & srcpx))); + } +} + +static void linear_blit(Texture& srctex, const IntRect& srcReq, int srcZ, + Texture& dsttex, const IntRect& dstReq, int dstZ, + bool invertY) { + assert(srctex.internal_format == GL_RGBA8 || + srctex.internal_format == GL_R8); + // Compute valid dest bounds + IntRect dstBounds = dsttex.sample_bounds(dstReq, invertY); + // Check if sampling bounds are empty + if (dstBounds.is_empty()) { + return; + } + // Initialize sampler for source texture + sampler2DArray_impl sampler; + init_sampler(&sampler, srctex); + init_depth(&sampler, srctex); + sampler.filter = TextureFilter::LINEAR; + // Compute source UVs + int srcZOffset = srcZ * sampler.height_stride; + vec2_scalar srcUV(srcReq.x0, srcReq.y0); + vec2_scalar srcDUV(float(srcReq.width()) / dstReq.width(), + float(srcReq.height()) / dstReq.height()); + // Skip to clamped source start + srcUV += srcDUV * vec2_scalar(dstBounds.x0, dstBounds.y0); + // Offset source UVs to texel centers and scale by lerp precision + srcUV = linearQuantize(srcUV + 0.5f, 128); + srcDUV *= 128.0f; + // Calculate dest pointer from clamped offsets + int bpp = dsttex.bpp(); + int destStride = dsttex.stride(bpp); + char* dest = dsttex.sample_ptr(dstReq, dstBounds, dstZ, invertY); + // Inverted Y must step downward along dest rows + if (invertY) { + destStride = -destStride; + } + int span = dstBounds.width(); + for (int rows = dstBounds.height(); rows > 0; rows--) { + switch (bpp) { + case 1: + linear_row((uint8_t*)dest, span, srcUV, srcDUV.x, srcZOffset, + &sampler); + break; + case 4: + linear_row((uint32_t*)dest, span, srcUV, srcDUV.x, srcZOffset, + &sampler); + break; + default: + assert(false); + break; + } + dest += destStride; + srcUV.y += srcDUV.y; + } +} + +extern "C" { + +void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, + GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, + GLbitfield mask, GLenum filter) { + assert(mask == GL_COLOR_BUFFER_BIT); + Framebuffer* srcfb = get_framebuffer(GL_READ_FRAMEBUFFER); + if (!srcfb || srcfb->layer < 0) return; + Framebuffer* dstfb = get_framebuffer(GL_DRAW_FRAMEBUFFER); + if (!dstfb || dstfb->layer < 0) return; + Texture& srctex = ctx->textures[srcfb->color_attachment]; + if (!srctex.buf || srcfb->layer >= max(srctex.depth, 1)) return; + Texture& dsttex = ctx->textures[dstfb->color_attachment]; + if (!dsttex.buf || dstfb->layer >= max(dsttex.depth, 1)) return; + if (srctex.internal_format != dsttex.internal_format) { + assert(false); + return; + } + // Force flipped Y onto dest coordinates + if (srcY1 < srcY0) { + swap(srcY0, srcY1); + swap(dstY0, dstY1); + } + bool invertY = dstY1 < dstY0; + if (invertY) { + swap(dstY0, dstY1); + } + IntRect srcReq = {srcX0, srcY0, srcX1, srcY1}; + IntRect dstReq = {dstX0, dstY0, dstX1, dstY1}; + if (srcReq.is_empty() || dstReq.is_empty()) { + return; + } + prepare_texture(srctex); + prepare_texture(dsttex, &dstReq); + if (!srcReq.same_size(dstReq) && filter == GL_LINEAR && + (srctex.internal_format == GL_RGBA8 || + srctex.internal_format == GL_R8)) { + linear_blit(srctex, srcReq, srcfb->layer, dsttex, dstReq, dstfb->layer, + invertY); + } else { + scale_blit(srctex, srcReq, srcfb->layer, dsttex, dstReq, dstfb->layer, + invertY); + } +} + +void Finish() {} + +void MakeCurrent(void* ctx_ptr) { + ctx = (Context*)ctx_ptr; + if (ctx) { + setup_program(ctx->current_program); + blend_key = ctx->blend ? ctx->blend_key : BLEND_KEY_NONE; + } else { + setup_program(0); + blend_key = BLEND_KEY_NONE; + } +} + +void* CreateContext() { return new Context; } + +void DestroyContext(void* ctx_ptr) { + if (!ctx_ptr) { + return; + } + if (ctx == ctx_ptr) { + MakeCurrent(nullptr); + } + delete (Context*)ctx_ptr; +} + +void Composite(GLuint srcId, GLint srcX, GLint srcY, GLsizei srcWidth, + GLsizei srcHeight, GLint dstX, GLint dstY, GLboolean opaque, + GLboolean flip) { + Framebuffer& fb = ctx->framebuffers[0]; + if (!fb.color_attachment) { + return; + } + Texture& srctex = ctx->textures[srcId]; + if (!srctex.buf) return; + prepare_texture(srctex); + Texture& dsttex = ctx->textures[fb.color_attachment]; + if (!dsttex.buf) return; + assert(srctex.bpp() == 4); + const int bpp = 4; + size_t src_stride = srctex.stride(bpp); + size_t dest_stride = dsttex.stride(bpp); + if (srcY < 0) { + dstY -= srcY; + srcHeight += srcY; + srcY = 0; + } + if (dstY < 0) { + srcY -= dstY; + srcHeight += dstY; + dstY = 0; + } + if (srcY + srcHeight > srctex.height) { + srcHeight = srctex.height - srcY; + } + if (dstY + srcHeight > dsttex.height) { + srcHeight = dsttex.height - dstY; + } + IntRect skip = {dstX, dstY, dstX + srcWidth, dstY + srcHeight}; + prepare_texture(dsttex, &skip); + char* dest = dsttex.sample_ptr(dstX, flip ? dsttex.height - 1 - dstY : dstY, + fb.layer, bpp, dest_stride); + char* src = srctex.sample_ptr(srcX, srcY, 0, bpp, src_stride); + if (flip) { + dest_stride = -dest_stride; + } + if (opaque) { + for (int y = 0; y < srcHeight; y++) { + memcpy(dest, src, srcWidth * bpp); + dest += dest_stride; + src += src_stride; + } + } else { + for (int y = 0; y < srcHeight; y++) { + char* end = src + srcWidth * bpp; + while (src + 4 * bpp <= end) { + WideRGBA8 srcpx = unpack(unaligned_load(src)); + WideRGBA8 dstpx = unpack(unaligned_load(dest)); + PackedRGBA8 r = pack(srcpx + dstpx - muldiv255(dstpx, alphas(srcpx))); + unaligned_store(dest, r); + src += 4 * bpp; + dest += 4 * bpp; + } + if (src < end) { + WideRGBA8 srcpx = unpack(unaligned_load(src)); + WideRGBA8 dstpx = unpack(unaligned_load(dest)); + U32 r = bit_cast( + pack(srcpx + dstpx - muldiv255(dstpx, alphas(srcpx)))); + unaligned_store(dest, r.x); + if (src + bpp < end) { + unaligned_store(dest + bpp, r.y); + if (src + 2 * bpp < end) { + unaligned_store(dest + 2 * bpp, r.z); + } + } + dest += end - src; + src = end; + } + dest += dest_stride - srcWidth * bpp; + src += src_stride - srcWidth * bpp; + } + } +} + +} // extern "C" diff --git a/third_party/webrender/swgl/src/gl_defs.h b/third_party/webrender/swgl/src/gl_defs.h new file mode 100644 index 00000000000..c7e87230a3d --- /dev/null +++ b/third_party/webrender/swgl/src/gl_defs.h @@ -0,0 +1,176 @@ +/* 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/. */ + +typedef int8_t GLbyte; +typedef uint8_t GLubyte; +typedef int16_t GLshort; +typedef uint16_t GLushort; +typedef int32_t GLint; +typedef uint32_t GLuint; +typedef int64_t GLint64; +typedef uint64_t GLuint64; + +typedef float GLfloat; +typedef double GLdouble; + +typedef uint32_t GLenum; +typedef int32_t GLboolean; +typedef uint32_t GLbitfield; + +typedef int32_t GLsizei; +typedef size_t GLsizeiptr; +typedef intptr_t GLintptr; + +#define GL_NO_ERROR 0 + +#define GL_RGBA32F 0x8814 +#define GL_RGBA8 0x8058 +#define GL_R8 0x8229 +#define GL_RGBA32I 0x8D82 +#define GL_BGRA8 0x93A1 + +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 + +#define GL_RED 0x1903 +#define GL_GREEN 0x1904 +#define GL_BLUE 0x1905 +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_RGBA_INTEGER 0x8D99 +#define GL_BGRA 0x80E1 + +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 + +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 + +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#define GL_FRAMEBUFFER 0x8D40 +#define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA +#define GL_RENDERBUFFER 0x8D41 +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_STENCIL_ATTACHMENT 0x8D20 +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_STENCIL_BUFFER_BIT 0x00000400 + +#define GL_PIXEL_PACK_BUFFER 0x88EB +#define GL_PIXEL_UNPACK_BUFFER 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GL_UNPACK_ALIGNMENT 0x0CF5 + +#define GL_QUERY_RESULT 0x8866 +#define GL_QUERY_RESULT_AVAILABLE 0x8867 +#define GL_TIME_ELAPSED 0x88BF +#define GL_SAMPLES_PASSED 0x8914 + +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_2D 0x0DE1 +#define GL_TEXTURE_3D 0x806F +#define GL_TEXTURE_2D_ARRAY 0x8C1A +#define GL_TEXTURE_RECTANGLE 0x84F5 +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF + +#define GL_VERTEX_SHADER 0x8B31 +#define GL_FRAGMENT_SHADER 0x8B30 + +#define GL_BLEND 0x0BE2 +#define GL_ZERO 0 +#define GL_ONE 1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_SRC1_ALPHA 0x8589 +#define GL_SRC1_COLOR 0x88F9 +#define GL_ONE_MINUS_SRC1_COLOR 0x88FA +#define GL_ONE_MINUS_SRC1_ALPHA 0x88FB + +#define GL_FUNC_ADD 0x8006 + +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 +#define GL_DEPTH_TEST 0x0B71 +#define GL_DEPTH_WRITEMASK 0x0B72 + +#define GL_SCISSOR_TEST 0x0C11 + +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 +#define GL_NUM_EXTENSIONS 0x821D + +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_QUADS 0x0007 diff --git a/third_party/webrender/swgl/src/glsl.h b/third_party/webrender/swgl/src/glsl.h new file mode 100644 index 00000000000..cdedb43d567 --- /dev/null +++ b/third_party/webrender/swgl/src/glsl.h @@ -0,0 +1,3240 @@ +/* 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/. */ + +// Some of this is copied from Skia and is governed by a BSD-style license +// Every function in this file should be marked static and inline using SI. +#define SI ALWAYS_INLINE static + +#include "vector_type.h" + +namespace glsl { + +#if USE_SSE2 +SI bool test_all(Bool cond) { return _mm_movemask_ps(cond) == 0xF; } +SI bool test_any(Bool cond) { return _mm_movemask_ps(cond) != 0; } +SI bool test_none(Bool cond) { return _mm_movemask_ps(cond) == 0; } +#else +SI bool test_all(Bool cond) { + return bit_cast(CONVERT(cond, U8)) == 0xFFFFFFFFU; +} +SI bool test_any(Bool cond) { return bit_cast(CONVERT(cond, U8)) != 0; } +SI bool test_none(Bool cond) { return bit_cast(CONVERT(cond, U8)) == 0; } +#endif + +float make_float(float n) { return n; } + +float make_float(int32_t n) { return float(n); } + +float make_float(uint32_t n) { return float(n); } + +float make_float(bool n) { return float(n); } + +template +Float make_float(T v) { + return CONVERT(v, Float); +} + +int32_t make_int(uint32_t n) { return n; } + +int32_t make_int(int32_t n) { return n; } + +int32_t make_int(float n) { return int32_t(n); } + +int32_t make_int(bool n) { return int32_t(n); } + +template +I32 make_int(T v) { + return CONVERT(v, I32); +} + +uint32_t make_uint(uint32_t n) { return n; } + +uint32_t make_uint(int32_t n) { return n; } + +uint32_t make_uint(float n) { return uint32_t(n); } + +uint32_t make_uint(bool n) { return uint32_t(n); } + +template +U32 make_uint(T v) { + return CONVERT(v, U32); +} + +template +T force_scalar(T n) { + return n; +} + +float force_scalar(Float f) { return f[0]; } + +int32_t force_scalar(I32 i) { return i[0]; } + +struct vec4; +struct ivec2; + +SI int32_t if_then_else(int32_t c, int32_t t, int32_t e) { return c ? t : e; } + +SI float if_then_else(int32_t c, float t, float e) { return c ? t : e; } + +SI Float if_then_else(I32 c, float t, float e) { + return bit_cast((c & bit_cast(Float(t))) | (~c & bit_cast(Float(e)))); +} + +SI I32 if_then_else(I32 c, int32_t t, int32_t e) { + return (c & I32(t)) | (~c & I32(e)); +} + +SI Float if_then_else(I32 c, Float t, Float e) { + return bit_cast((c & bit_cast(t)) | (~c & bit_cast(e))); +} + +SI Float if_then_else(int32_t c, Float t, Float e) { return c ? t : e; } + +SI Bool if_then_else(I32 c, Bool t, Bool e) { return (c & t) | (~c & e); } + +SI Bool if_then_else(int32_t c, Bool t, Bool e) { return c ? t : e; } + +template SI void swap(T& a, T& b) { + T t(a); + a = b; + b = t; +} + +SI int32_t min(int32_t a, int32_t b) { return a < b ? a : b; } +SI int32_t max(int32_t a, int32_t b) { return a > b ? a : b; } + +SI int32_t clamp(int32_t a, int32_t minVal, int32_t maxVal) { + return min(max(a, minVal), maxVal); +} + +SI float min(float a, float b) { return a < b ? a : b; } +SI float max(float a, float b) { return a > b ? a : b; } + +SI float clamp(float a, float minVal, float maxVal) { + return min(max(a, minVal), maxVal); +} + +SI Float min(Float a, Float b) { +#if USE_SSE2 + return _mm_min_ps(a, b); +#elif USE_NEON + return vminq_f32(a, b); +#else + return if_then_else(a < b, a, b); +#endif +} + +SI Float max(Float a, Float b) { +#if USE_SSE2 + return _mm_max_ps(a, b); +#elif USE_NEON + return vmaxq_f32(a, b); +#else + return if_then_else(a > b, a, b); +#endif +} + +SI Float clamp(Float a, Float minVal, Float maxVal) { + return min(max(a, minVal), maxVal); +} + +#define sqrt __glsl_sqrt + +SI float sqrt(float x) { return sqrtf(x); } + +SI Float sqrt(Float v) { +#if USE_SSE2 + return _mm_sqrt_ps(v); +#elif USE_NEON + Float e = vrsqrteq_f32(v); + e *= vrsqrtsq_f32(v, e * e); + e *= vrsqrtsq_f32(v, e * e); + return v * e; +#else + return (Float){sqrtf(v.x), sqrtf(v.y), sqrtf(v.z), sqrtf(v.w)}; +#endif +} + +SI float inversesqrt(float x) { return 1.0f / sqrtf(x); } + +SI Float inversesqrt(Float v) { +#if USE_SSE2 + return _mm_rsqrt_ps(v); +#elif USE_NEON + Float e = vrsqrteq_f32(v); + return vrsqrtsq_f32(v, e * e) * e; +#else + return 1.0f / sqrt(v); +#endif +} + +SI float step(float edge, float x) { return float(x >= edge); } + +SI Float step(Float edge, Float x) { + return if_then_else(x < edge, Float(0), Float(1)); +} + +/* +enum RGBA { + R, + G, + B, + A +};*/ + +enum XYZW { + X = 0, + Y = 1, + Z = 2, + W = 3, + R = 0, + G = 1, + B = 2, + A = 3, +}; + +struct bvec2_scalar { + bool x; + bool y; + + bvec2_scalar() : bvec2_scalar(false) {} + constexpr bvec2_scalar(bool a) : x(a), y(a) {} + constexpr bvec2_scalar(bool x, bool y) : x(x), y(y) {} +}; + +struct bvec2 { + bvec2() : bvec2(0) {} + bvec2(Bool a) : x(a), y(a) {} + bvec2(Bool x, Bool y) : x(x), y(y) {} + Bool& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + default: + UNREACHABLE; + } + } + Bool sel(XYZW c1) { return select(c1); } + + bvec2 operator~() { return bvec2(~x, ~y); } + + Bool x; + Bool y; +}; + +bvec2_scalar make_bvec2(bool n) { return bvec2_scalar{n, n}; } + +bvec2_scalar make_bvec2(bool x, bool y) { return bvec2_scalar{x, y}; } + +template +bvec2 make_bvec2(const N& n) { + return bvec2(n); +} + +template +bvec2 make_bvec2(const X& x, const Y& y) { + return bvec2(x, y); +} + +struct vec4_scalar; + +struct vec2_scalar { + typedef struct vec2 vector_type; + typedef float element_type; + + float x; + float y; + + constexpr vec2_scalar() : vec2_scalar(0.0f) {} + constexpr vec2_scalar(float a) : x(a), y(a) {} + constexpr vec2_scalar(int a) : x(a), y(a) {} + constexpr vec2_scalar(float x, float y) : x(x), y(y) {} + + float& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + default: + UNREACHABLE; + } + } + float& sel(XYZW c1) { return select(c1); } + vec2_scalar sel(XYZW c1, XYZW c2) { + return vec2_scalar(select(c1), select(c2)); + } + vec4_scalar sel(XYZW c1, XYZW c2, XYZW c3, XYZW c4); + + friend bool operator==(const vec2_scalar& l, const vec2_scalar& r) { + return l.x == r.x && l.y == r.y; + } + + friend bool operator!=(const vec2_scalar& l, const vec2_scalar& r) { + return l.x != r.x || l.y != r.y; + } + + friend vec2_scalar operator*(float a, vec2_scalar b) { + return vec2_scalar(a * b.x, a * b.y); + } + friend vec2_scalar operator*(vec2_scalar a, float b) { + return vec2_scalar(a.x * b, a.y * b); + } + friend vec2_scalar operator*(vec2_scalar a, vec2_scalar b) { + return vec2_scalar(a.x * b.x, a.y * b.y); + } + friend vec2_scalar operator/(vec2_scalar a, vec2_scalar b) { + return vec2_scalar(a.x / b.x, a.y / b.y); + } + + friend vec2_scalar operator-(vec2_scalar a, vec2_scalar b) { + return vec2_scalar(a.x - b.x, a.y - b.y); + } + friend vec2_scalar operator+(vec2_scalar a, vec2_scalar b) { + return vec2_scalar(a.x + b.x, a.y + b.y); + } + friend vec2_scalar operator+(vec2_scalar a, float b) { + return vec2_scalar(a.x + b, a.y + b); + } + + vec2_scalar operator-() { return vec2_scalar(-x, -y); } + + vec2_scalar operator*=(vec2_scalar a) { + x *= a.x; + y *= a.y; + return *this; + } + + vec2_scalar operator+=(vec2_scalar a) { + x += a.x; + y += a.y; + return *this; + } + + vec2_scalar operator-=(vec2_scalar a) { + x -= a.x; + y -= a.y; + return *this; + } +}; + +struct vec2_scalar_ref { + vec2_scalar_ref(float& x, float& y) : x(x), y(y) {} + float& x; + float& y; + + float& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + default: + UNREACHABLE; + } + } + float& sel(XYZW c1) { return select(c1); } + + vec2_scalar_ref& operator=(const vec2_scalar& a) { + x = a.x; + y = a.y; + return *this; + } + vec2_scalar_ref& operator*=(vec2_scalar a) { + x *= a.x; + y *= a.y; + return *this; + } + operator vec2_scalar() const { return vec2_scalar{x, y}; } +}; + +struct vec2 { + typedef struct vec2 vector_type; + typedef float element_type; + + constexpr vec2() : vec2(Float(0.0f)) {} + constexpr vec2(Float a) : x(a), y(a) {} + vec2(Float x, Float y) : x(x), y(y) {} + constexpr vec2(vec2_scalar s) : x(s.x), y(s.y) {} + constexpr vec2(vec2_scalar s0, vec2_scalar s1, vec2_scalar s2, vec2_scalar s3) + : x(Float{s0.x, s1.x, s2.x, s3.x}), y(Float{s0.y, s1.y, s2.y, s3.y}) {} + vec2(ivec2 a); + Float x; + Float y; + + Float& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + default: + UNREACHABLE; + } + } + Float& sel(XYZW c1) { return select(c1); } + vec2 sel(XYZW c1, XYZW c2) { return vec2(select(c1), select(c2)); } + + vec4 sel(XYZW c1, XYZW c2, XYZW c3, XYZW c4); + + vec2 operator*=(Float a) { + x *= a; + y *= a; + return *this; + } + vec2 operator*=(vec2 a) { + x *= a.x; + y *= a.y; + return *this; + } + + vec2 operator/=(Float a) { + x /= a; + y /= a; + return *this; + } + vec2 operator/=(vec2 a) { + x /= a.x; + y /= a.y; + return *this; + } + + vec2 operator+=(vec2 a) { + x += a.x; + y += a.y; + return *this; + } + vec2 operator-=(vec2 a) { + x -= a.x; + y -= a.y; + return *this; + } + vec2 operator-=(Float a) { + x -= a; + y -= a; + return *this; + } + + vec2 operator-() { return vec2(-x, -y); } + + friend I32 operator==(const vec2& l, const vec2& r) { + return l.x == r.x && l.y == r.y; + } + + friend I32 operator!=(const vec2& l, const vec2& r) { + return l.x != r.x || l.y != r.y; + } + + friend vec2 operator*(vec2 a, Float b) { return vec2(a.x * b, a.y * b); } + friend vec2 operator*(vec2 a, vec2 b) { return vec2(a.x * b.x, a.y * b.y); } + friend vec2 operator*(Float a, vec2 b) { return vec2(a * b.x, a * b.y); } + + friend vec2 operator/(vec2 a, vec2 b) { return vec2(a.x / b.x, a.y / b.y); } + friend vec2 operator/(vec2 a, Float b) { return vec2(a.x / b, a.y / b); } + + friend vec2 operator-(vec2 a, vec2 b) { return vec2(a.x - b.x, a.y - b.y); } + friend vec2 operator-(vec2 a, Float b) { return vec2(a.x - b, a.y - b); } + friend vec2 operator-(Float a, vec2 b) { return vec2(a - b.x, a - b.y); } + friend vec2 operator+(vec2 a, vec2 b) { return vec2(a.x + b.x, a.y + b.y); } + friend vec2 operator+(vec2 a, Float b) { return vec2(a.x + b, a.y + b); } + friend vec2 operator+(Float a, vec2 b) { return vec2(a + b.x, a + b.y); } +}; + +vec2_scalar force_scalar(const vec2& v) { + return vec2_scalar{force_scalar(v.x), force_scalar(v.y)}; +} + +vec2_scalar make_vec2(float n) { return vec2_scalar{n, n}; } + +vec2_scalar make_vec2(float x, float y) { return vec2_scalar{x, y}; } + +vec2_scalar make_vec2(int32_t x, int32_t y) { + return vec2_scalar{float(x), float(y)}; +} + +template +vec2 make_vec2(const N& n) { + return vec2(n); +} + +template +vec2 make_vec2(const X& x, const Y& y) { + return vec2(x, y); +} + +vec2 operator*(vec2_scalar a, Float b) { return vec2(a.x * b, a.y * b); } + +vec2 operator*(Float a, vec2_scalar b) { return vec2(a * b.x, a * b.y); } + +SI vec2 min(vec2 a, vec2 b) { return vec2(min(a.x, b.x), min(a.y, b.y)); } + +SI vec2_scalar min(vec2_scalar a, vec2_scalar b) { + return vec2_scalar{min(a.x, b.x), min(a.y, b.y)}; +} + +SI vec2 if_then_else(I32 c, vec2 t, vec2 e) { + return vec2(if_then_else(c, t.x, e.x), if_then_else(c, t.y, e.y)); +} + +SI vec2 if_then_else(int32_t c, vec2 t, vec2 e) { return c ? t : e; } + +vec2 step(vec2 edge, vec2 x) { + return vec2(step(edge.x, x.x), step(edge.y, x.y)); +} + +vec2 max(vec2 a, vec2 b) { return vec2(max(a.x, b.x), max(a.y, b.y)); } +vec2 max(vec2 a, Float b) { return vec2(max(a.x, b), max(a.y, b)); } + +SI vec2_scalar max(vec2_scalar a, vec2_scalar b) { + return vec2_scalar{max(a.x, b.x), max(a.y, b.y)}; +} +SI vec2_scalar max(vec2_scalar a, float b) { + return vec2_scalar{max(a.x, b), max(a.y, b)}; +} + +Float length(vec2 a) { return sqrt(a.x * a.x + a.y * a.y); } + +float length(vec2_scalar a) { return hypotf(a.x, a.y); } + +SI Float distance(vec2 a, vec2 b) { return length(a - b); } + +SI vec2 normalize(vec2 a) { return a / length(a); } + +#define abs __glsl_abs + +int32_t abs(int32_t a) { return a < 0 ? -a : a; } + +float abs(float a) { return fabsf(a); } + +Float abs(Float v) { +#if USE_NEON + return vabsq_f32(v); +#else + return bit_cast(bit_cast(v) & bit_cast(0.0f - v)); +#endif +} + +Float cast(U32 v) { return CONVERT((I32)v, Float); } +Float cast(I32 v) { return CONVERT((I32)v, Float); } +I32 cast(Float v) { return CONVERT(v, I32); } + +#define floor __glsl_floor + +float floor(float a) { return floorf(a); } + +Float floor(Float v) { + Float roundtrip = cast(cast(v)); + return roundtrip - if_then_else(roundtrip > v, Float(1), Float(0)); +} + +vec2 floor(vec2 v) { return vec2(floor(v.x), floor(v.y)); } + +vec2_scalar floor(vec2_scalar v) { + return vec2_scalar{floorf(v.x), floorf(v.y)}; +} + +#define ceil __glsl_ceil + +float ceil(float a) { return ceilf(a); } + +Float ceil(Float v) { + Float roundtrip = cast(cast(v)); + return roundtrip + if_then_else(roundtrip < v, Float(1), Float(0)); +} + +// Round to nearest even +SI int32_t roundeven(float v, float scale) { +#if USE_SSE2 + return _mm_cvtss_si32(_mm_set_ss(v * scale)); +#else + return bit_cast(v * scale + float(0xC00000)) - 0x4B400000; +#endif +} + +SI I32 roundeven(Float v, Float scale) { +#if USE_SSE2 + return _mm_cvtps_epi32(v * scale); +#else + // Magic number implementation of round-to-nearest-even + // see http://stereopsis.com/sree/fpu2006.html + return bit_cast(v * scale + Float(0xC00000)) - 0x4B400000; +#endif +} + +// Round towards zero +SI int32_t roundzero(float v, float scale) { return int32_t(v * scale); } + +SI I32 roundzero(Float v, Float scale) { return cast(v * scale); } + +// Round whichever direction is fastest for positive numbers +SI I32 roundfast(Float v, Float scale) { +#if USE_SSE2 + return _mm_cvtps_epi32(v * scale); +#else + return cast(v * scale + 0.5f); +#endif +} + +template SI auto round_pixel(T v) { return roundfast(v, 255.0f); } + +#define round __glsl_round + +float round(float a) { return roundf(a); } + +float fract(float a) { return a - floor(a); } + +Float round(Float v) { return floor(v + 0.5f); } + +Float fract(Float v) { return v - floor(v); } + +// X derivatives can be approximated by dFdx(x) = x[1] - x[0]. +// Y derivatives are not easily available since we operate in terms of X spans +// only. To work around, assume dFdy(p.x) = dFdx(p.y), which only holds for +// uniform scaling, and thus abs(dFdx(p.x)) + abs(dFdy(p.x)) = abs(dFdx(p.x)) + +// abs(dFdx(p.y)) which mirrors abs(dFdx(p.y)) + abs(dFdy(p.y)) = abs(dFdx(p.y)) +// + abs(dFdx(p.x)). +vec2 fwidth(vec2 p) { + Float d = abs(SHUFFLE(p.x, p.y, 1, 1, 5, 5) - SHUFFLE(p.x, p.y, 0, 0, 4, 4)); + return vec2(d.xyxy + d.zwzw); +} + +// See +// http://www.machinedlearnings.com/2011/06/fast-approximate-logarithm-exponential.html. +Float approx_log2(Float x) { + // e - 127 is a fair approximation of log2(x) in its own right... + Float e = cast(bit_cast(x)) * (1.0f / (1 << 23)); + + // ... but using the mantissa to refine its error is _much_ better. + Float m = bit_cast((bit_cast(x) & 0x007fffff) | 0x3f000000); + return e - 124.225514990f - 1.498030302f * m - + 1.725879990f / (0.3520887068f + m); +} +Float approx_pow2(Float x) { + Float f = fract(x); + return bit_cast( + roundfast(1.0f * (1 << 23), x + 121.274057500f - 1.490129070f * f + + 27.728023300f / (4.84252568f - f))); +} + +// From skia +Float pow(Float x, Float y) { + return if_then_else((x == 0) | (x == 1), x, approx_pow2(approx_log2(x) * y)); +} + +Float exp(Float y) { + float x = 2.718281828459045235360287471352; + return approx_pow2(log2f(x) * y); +} + +struct ivec4; + +struct ivec2_scalar { + typedef int32_t element_type; + + int32_t x; + int32_t y; + + ivec2_scalar() : ivec2_scalar(0) {} + constexpr ivec2_scalar(int32_t a) : x(a), y(a) {} + constexpr ivec2_scalar(int32_t x, int32_t y) : x(x), y(y) {} + + int32_t& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + default: + UNREACHABLE; + } + } + int32_t& sel(XYZW c1) { return select(c1); } + ivec2_scalar sel(XYZW c1, XYZW c2) { + return ivec2_scalar{select(c1), select(c2)}; + } + + ivec2_scalar& operator+=(ivec2_scalar a) { + x += a.x; + y += a.y; + return *this; + } + ivec2_scalar& operator+=(int n) { + x += n; + y += n; + return *this; + } + + ivec2_scalar& operator>>=(int shift) { + x >>= shift; + y >>= shift; + return *this; + } + + friend ivec2_scalar operator&(ivec2_scalar a, int b) { + return ivec2_scalar{a.x & b, a.y & b}; + } + + friend ivec2_scalar operator+(ivec2_scalar a, ivec2_scalar b) { + return ivec2_scalar{a.x + b.x, a.y + b.y}; + } +}; + +struct ivec2 { + typedef int32_t element_type; + + ivec2() : ivec2(I32(0)) {} + ivec2(I32 a) : x(a), y(a) {} + ivec2(I32 x, I32 y) : x(x), y(y) {} + ivec2(vec2 a) : x(cast(a.x)), y(cast(a.y)) {} + ivec2(U32 x, U32 y) : x(CONVERT(x, I32)), y(CONVERT(y, I32)) {} + constexpr ivec2(ivec2_scalar s) : x(s.x), y(s.y) {} + constexpr ivec2(ivec2_scalar s0, ivec2_scalar s1, ivec2_scalar s2, + ivec2_scalar s3) + : x(I32{s0.x, s1.x, s2.x, s3.x}), y(I32{s0.y, s1.y, s2.y, s3.y}) {} + I32 x; + I32 y; + + I32& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + default: + UNREACHABLE; + } + } + I32& sel(XYZW c1) { return select(c1); } + + ivec2 sel(XYZW c1, XYZW c2) { return ivec2(select(c1), select(c2)); } + + ivec4 sel(XYZW c1, XYZW c2, XYZW c3, XYZW c4); + + ivec2& operator*=(I32 a) { + x *= a; + y *= a; + return *this; + } + ivec2& operator+=(ivec2 a) { + x += a.x; + y += a.y; + return *this; + } + ivec2& operator>>=(int shift) { + x >>= shift; + y >>= shift; + return *this; + } + + friend ivec2 operator*(ivec2 a, I32 b) { return ivec2(a.x * b, a.y * b); } + friend ivec2 operator&(ivec2 a, ivec2 b) { + return ivec2(a.x & b.x, a.y & b.y); + } + friend ivec2 operator&(ivec2 a, I32 b) { return ivec2(a.x & b, a.y & b); } + friend ivec2 operator+(ivec2 a, ivec2 b) { + return ivec2(a.x + b.x, a.y + b.y); + } +}; + +vec2::vec2(ivec2 a) : x(cast(a.x)), y(cast(a.y)) {} + +ivec2_scalar make_ivec2(int32_t n) { return ivec2_scalar{n, n}; } + +ivec2_scalar make_ivec2(uint32_t n) { + return ivec2_scalar{int32_t(n), int32_t(n)}; +} + +ivec2_scalar make_ivec2(int32_t x, int32_t y) { return ivec2_scalar{x, y}; } + +ivec2_scalar make_ivec2(uint32_t x, uint32_t y) { + return ivec2_scalar{int32_t(x), int32_t(y)}; +} + +vec2_scalar make_vec2(const ivec2_scalar& v) { + return vec2_scalar{float(v.x), float(v.y)}; +} + +ivec2_scalar make_ivec2(const vec2_scalar& v) { + return ivec2_scalar{int32_t(v.x), int32_t(v.y)}; +} + +template +ivec2 make_ivec2(const N& n) { + return ivec2(n); +} + +template +ivec2 make_ivec2(const X& x, const Y& y) { + return ivec2(x, y); +} + +ivec2_scalar force_scalar(const ivec2& v) { + return ivec2_scalar{force_scalar(v.x), force_scalar(v.y)}; +} + +struct ivec3_scalar { + int32_t x; + int32_t y; + int32_t z; + + ivec3_scalar() : ivec3_scalar(0) {} + constexpr ivec3_scalar(int32_t a) : x(a), y(a), z(a) {} + constexpr ivec3_scalar(int32_t x, int32_t y, int32_t z) : x(x), y(y), z(z) {} + + int32_t& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + default: + UNREACHABLE; + } + } + int32_t& sel(XYZW c1) { return select(c1); } + ivec2_scalar sel(XYZW c1, XYZW c2) { + return ivec2_scalar{select(c1), select(c2)}; + } +}; + +struct ivec3 { + ivec3() : ivec3(0) {} + ivec3(I32 a) : x(a), y(a), z(a) {} + ivec3(I32 x, I32 y, I32 z) : x(x), y(y), z(z) {} + ivec3(ivec2 a, I32 b) : x(a.x), y(a.y), z(b) {} + ivec3(vec2 a, Float b) : x(cast(a.x)), y(cast(a.y)), z(cast(b)) {} + I32 x; + I32 y; + I32 z; + + friend ivec3 operator+(ivec3 a, ivec3 b) { + return ivec3(a.x + b.x, a.y + b.y, a.z + b.z); + } +}; + +vec2_scalar make_vec2(ivec3_scalar s) { + return vec2_scalar{float(s.x), float(s.y)}; +} + +ivec3_scalar make_ivec3(int32_t n) { return ivec3_scalar{n, n, n}; } + +ivec3_scalar make_ivec3(const ivec2_scalar& v, int32_t z) { + return ivec3_scalar{v.x, v.y, z}; +} + +ivec3_scalar make_ivec3(int32_t x, int32_t y, int32_t z) { + return ivec3_scalar{x, y, z}; +} + +template +ivec3 make_ivec3(const N& n) { + return ivec3(n); +} + +template +ivec3 make_ivec3(const X& x, const Y& y) { + return ivec3(x, y); +} + +template +ivec3 make_ivec3(const X& x, const Y& y, const Z& z) { + return ivec3(x, y, z); +} + +struct ivec4_scalar { + typedef int32_t element_type; + + int32_t x; + int32_t y; + int32_t z; + int32_t w; + + ivec4_scalar() : ivec4_scalar(0) {} + constexpr ivec4_scalar(int32_t a) : x(a), y(a), z(a), w(a) {} + constexpr ivec4_scalar(int32_t x, int32_t y, int32_t z, int32_t w) + : x(x), y(y), z(z), w(w) {} + + int32_t& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + case W: + return w; + default: + UNREACHABLE; + } + } + int32_t& sel(XYZW c1) { return select(c1); } + ivec2_scalar sel(XYZW c1, XYZW c2) { + return ivec2_scalar{select(c1), select(c2)}; + } + + friend ivec4_scalar operator&(int32_t a, ivec4_scalar b) { + return ivec4_scalar{a & b.x, a & b.y, a & b.z, a & b.w}; + } +}; + +struct ivec4 { + typedef int32_t element_type; + + ivec4() : ivec4(I32(0)) {} + ivec4(I32 a) : x(a), y(a), z(a), w(a) {} + ivec4(I32 x, I32 y, I32 z, I32 w) : x(x), y(y), z(z), w(w) {} + ivec4(ivec2 a, I32 b, I32 c) : x(a.x), y(a.y), z(b), w(c) {} + constexpr ivec4(ivec4_scalar s) : x(s.x), y(s.y), z(s.z), w(s.w) {} + constexpr ivec4(ivec4_scalar s0, ivec4_scalar s1, ivec4_scalar s2, + ivec4_scalar s3) + : x(I32{s0.x, s1.x, s2.x, s3.x}), + y(I32{s0.y, s1.y, s2.y, s3.y}), + z(I32{s0.z, s1.z, s2.z, s3.z}), + w(I32{s0.w, s1.w, s2.w, s3.w}) {} + + I32& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + case W: + return w; + default: + UNREACHABLE; + } + } + I32 sel(XYZW c1) { return select(c1); } + + ivec2 sel(XYZW c1, XYZW c2) { return ivec2(select(c1), select(c2)); } + + ivec3 sel(XYZW c1, XYZW c2, XYZW c3) { + return ivec3(select(c1), select(c2), select(c3)); + } + + friend ivec4 operator&(I32 a, ivec4 b) { + return ivec4(a & b.x, a & b.y, a & b.z, a & b.w); + } + + I32 x; + I32 y; + I32 z; + I32 w; +}; + +ivec4_scalar force_scalar(const ivec4& v) { + return ivec4_scalar{force_scalar(v.x), force_scalar(v.y), force_scalar(v.z), + force_scalar(v.w)}; +} + +ivec4_scalar make_ivec4(int32_t n) { return ivec4_scalar{n, n, n, n}; } + +ivec4_scalar make_ivec4(const ivec2_scalar& xy, int32_t z, int32_t w) { + return ivec4_scalar{xy.x, xy.y, z, w}; +} + +ivec4_scalar make_ivec4(int32_t x, int32_t y, int32_t z, int32_t w) { + return ivec4_scalar{x, y, z, w}; +} + +template +ivec4 make_ivec4(const N& n) { + return ivec4(n); +} + +template +ivec4 make_ivec4(const X& x, const Y& y, const Z& z) { + return ivec4(x, y, z); +} + +template +ivec4 make_ivec4(const X& x, const Y& y, const Z& z, const W& w) { + return ivec4(x, y, z, w); +} + +SI ivec2 if_then_else(I32 c, ivec2 t, ivec2 e) { + return ivec2(if_then_else(c, t.x, e.x), if_then_else(c, t.y, e.y)); +} + +SI ivec2 if_then_else(int32_t c, ivec2 t, ivec2 e) { return c ? t : e; } + +SI ivec4 if_then_else(I32 c, ivec4 t, ivec4 e) { + return ivec4(if_then_else(c, t.x, e.x), if_then_else(c, t.y, e.y), + if_then_else(c, t.z, e.z), if_then_else(c, t.w, e.w)); +} + +SI ivec4 if_then_else(int32_t c, ivec4 t, ivec4 e) { return c ? t : e; } + +ivec4 operator&(I32 a, ivec4_scalar b) { + return ivec4(a & b.x, a & b.y, a & b.z, a & b.w); +} + +struct bvec3_scalar { + bool x; + bool y; + bool z; + + bvec3_scalar() : bvec3_scalar(false) {} + constexpr bvec3_scalar(bool a) : x(a), y(a), z(a) {} + constexpr bvec3_scalar(bool x, bool y, bool z) : x(x), y(y), z(z) {} +}; + +struct bvec3 { + bvec3() : bvec3(0) {} + bvec3(Bool a) : x(a), y(a), z(a) {} + bvec3(Bool x, Bool y, Bool z) : x(x), y(y), z(z) {} + Bool& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + default: + UNREACHABLE; + } + } + Bool sel(XYZW c1) { return select(c1); } + + Bool x; + Bool y; + Bool z; +}; + +struct bvec4_scalar { + bool x; + bool y; + bool z; + bool w; + + bvec4_scalar() : bvec4_scalar(false) {} + constexpr bvec4_scalar(bool a) : x(a), y(a), z(a), w(a) {} + constexpr bvec4_scalar(bool x, bool y, bool z, bool w) + : x(x), y(y), z(z), w(w) {} +}; + +struct bvec4 { + bvec4() : bvec4(0) {} + bvec4(Bool a) : x(a), y(a), z(a), w(a) {} + bvec4(Bool x, Bool y, Bool z, Bool w) : x(x), y(y), z(z), w(w) {} + bvec4(bvec2 x, bvec2 y) : x(x.x), y(x.y), z(y.x), w(y.y) {} + Bool& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + case W: + return w; + } + } + Bool sel(XYZW c1) { return select(c1); } + + Bool x; + Bool y; + Bool z; + Bool w; +}; + +bvec4_scalar make_bvec4(bool n) { return bvec4_scalar{n, n, n, n}; } + +bvec4_scalar make_bvec4(bool x, bool y, bool z, bool w) { + return bvec4_scalar{x, y, z, w}; +} + +template +bvec4 make_bvec4(const N& n) { + return bvec4(n); +} + +template +bvec4 make_bvec4(const X& x, const Y& y) { + return bvec4(x, y); +} + +template +bvec4 make_bvec4(const X& x, const Y& y, const Z& z, const W& w) { + return bvec4(x, y, z, w); +} + +struct vec2_ref { + vec2_ref(Float& x, Float& y) : x(x), y(y) {} + Float& x; + Float& y; + + Float& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + default: + UNREACHABLE; + } + } + Float& sel(XYZW c1) { return select(c1); } + + vec2_ref& operator=(const vec2& a) { + x = a.x; + y = a.y; + return *this; + } + + vec2_ref& operator/=(Float a) { + x /= a; + y /= a; + return *this; + } + + vec2_ref& operator/=(vec2 a) { + x /= a.x; + y /= a.y; + return *this; + } + + vec2_ref& operator+=(vec2 a) { + x += a.x; + y += a.y; + return *this; + } + vec2_ref& operator-=(vec2 a) { + x -= a.x; + y -= a.y; + return *this; + } + vec2_ref& operator*=(vec2 a) { + x *= a.x; + y *= a.y; + return *this; + } +}; + +struct vec3_scalar { + typedef struct vec3 vector_type; + typedef float element_type; + + float x; + float y; + float z; + + constexpr vec3_scalar() : vec3_scalar(0.0f) {} + constexpr vec3_scalar(float a) : x(a), y(a), z(a) {} + constexpr vec3_scalar(float x, float y, float z) : x(x), y(y), z(z) {} + + float& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + default: + UNREACHABLE; + } + } + float& sel(XYZW c1) { return select(c1); } + vec2_scalar sel(XYZW c1, XYZW c2) { + return vec2_scalar(select(c1), select(c2)); + } + vec3_scalar sel(XYZW c1, XYZW c2, XYZW c3) { + return vec3_scalar(select(c1), select(c2), select(c3)); + } + vec2_scalar_ref lsel(XYZW c1, XYZW c2) { + return vec2_scalar_ref(select(c1), select(c2)); + } + + friend vec3_scalar operator*(vec3_scalar a, vec3_scalar b) { + return vec3_scalar{a.x * b.x, a.y * b.y, a.z * b.z}; + } + friend vec3_scalar operator*(vec3_scalar a, float b) { + return vec3_scalar{a.x * b, a.y * b, a.z * b}; + } + + friend vec3_scalar operator-(vec3_scalar a, vec3_scalar b) { + return vec3_scalar{a.x - b.x, a.y - b.y, a.z - b.z}; + } + friend vec3_scalar operator+(vec3_scalar a, vec3_scalar b) { + return vec3_scalar{a.x + b.x, a.y + b.y, a.z + b.z}; + } + + friend vec3_scalar operator/(vec3_scalar a, float b) { + return vec3_scalar{a.x / b, a.y / b, a.z / b}; + } + + vec3_scalar operator+=(vec3_scalar a) { + x += a.x; + y += a.y; + z += a.z; + return *this; + } + + friend bool operator==(const vec3_scalar& l, const vec3_scalar& r) { + return l.x == r.x && l.y == r.y && l.z == r.z; + } +}; + +struct vec3_scalar_ref { + vec3_scalar_ref(float& x, float& y, float& z) : x(x), y(y), z(z) {} + float& x; + float& y; + float& z; + + float& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + default: + UNREACHABLE; + } + } + float& sel(XYZW c1) { return select(c1); } + + vec3_scalar_ref& operator=(const vec3_scalar& a) { + x = a.x; + y = a.y; + z = a.z; + return *this; + } + + operator vec3_scalar() const { return vec3_scalar{x, y, z}; } +}; + +struct vec3 { + typedef struct vec3 vector_type; + typedef float element_type; + + constexpr vec3() : vec3(Float(0.0f)) {} + constexpr vec3(Float a) : x(a), y(a), z(a) {} + constexpr vec3(Float x, Float y, Float z) : x(x), y(y), z(z) {} + vec3(vec2 a, Float z) : x(a.x), y(a.y), z(z) {} + constexpr vec3(vec3_scalar s) : x(s.x), y(s.y), z(s.z) {} + constexpr vec3(vec3_scalar s0, vec3_scalar s1, vec3_scalar s2, vec3_scalar s3) + : x(Float{s0.x, s1.x, s2.x, s3.x}), + y(Float{s0.y, s1.y, s2.y, s3.y}), + z(Float{s0.z, s1.z, s2.z, s3.z}) {} + Float x; + Float y; + Float z; + + Float& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + default: + UNREACHABLE; + } + } + Float& sel(XYZW c1) { return select(c1); } + + vec2 sel(XYZW c1, XYZW c2) { return vec2(select(c1), select(c2)); } + + vec3 sel(XYZW c1, XYZW c2, XYZW c3) { + return vec3(select(c1), select(c2), select(c3)); + } + + vec2_ref lsel(XYZW c1, XYZW c2) { return vec2_ref(select(c1), select(c2)); } + + friend vec3 operator*(vec3 a, Float b) { + return vec3(a.x * b, a.y * b, a.z * b); + } + friend vec3 operator*(vec3 a, vec3 b) { + return vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + friend vec3 operator*(Float a, vec3 b) { + return vec3(a * b.x, a * b.y, a * b.z); + } + + friend vec3 operator/(vec3 a, Float b) { + return vec3(a.x / b, a.y / b, a.z / b); + } + + friend I32 operator==(const vec3& l, const vec3& r) { + return l.x == r.x && l.y == r.y && l.z == r.z; + } + + friend vec3 operator-(vec3 a, Float b) { + return vec3(a.x - b, a.y - b, a.z - b); + } + friend vec3 operator-(vec3 a, vec3 b) { + return vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + friend vec3 operator+(vec3 a, Float b) { + return vec3(a.x + b, a.y + b, a.z + b); + } + friend vec3 operator+(vec3 a, vec3 b) { + return vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + + vec3 operator+=(vec3_scalar a) { + x += a.x; + y += a.y; + z += a.z; + return *this; + } + vec3& operator+=(vec3 a) { + x += a.x; + y += a.y; + z += a.z; + return *this; + } +}; + +vec3_scalar force_scalar(const vec3& v) { + return vec3_scalar{force_scalar(v.x), force_scalar(v.y), force_scalar(v.z)}; +} + +vec3_scalar make_vec3(float n) { return vec3_scalar{n, n, n}; } + +vec3_scalar make_vec3(const vec2_scalar& v, float z) { + return vec3_scalar{v.x, v.y, z}; +} + +vec3_scalar make_vec3(float x, float y, float z) { + return vec3_scalar{x, y, z}; +} + +vec3_scalar make_vec3(int32_t x, int32_t y, float z) { + return vec3_scalar{float(x), float(y), z}; +} + +template +vec3 make_vec3(const N& n) { + return vec3(n); +} + +template +vec3 make_vec3(const X& x, const Y& y) { + return vec3(x, y); +} + +template +vec3 make_vec3(const X& x, const Y& y, const Z& z) { + return vec3(x, y, z); +} + +SI vec3 if_then_else(I32 c, vec3 t, vec3 e) { + return vec3(if_then_else(c, t.x, e.x), if_then_else(c, t.y, e.y), + if_then_else(c, t.z, e.z)); +} + +SI vec3 if_then_else(int32_t c, vec3 t, vec3 e) { return c ? t : e; } + +SI vec3 if_then_else(ivec3 c, vec3 t, vec3 e) { + return vec3(if_then_else(c.x, t.x, e.x), if_then_else(c.y, t.y, e.y), + if_then_else(c.z, t.z, e.z)); +} + +vec3 step(vec3 edge, vec3 x) { + return vec3(step(edge.x, x.x), step(edge.y, x.y), step(edge.z, x.z)); +} + +SI vec3 min(vec3 a, vec3 b) { + return vec3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); +} +SI vec3 max(vec3 a, vec3 b) { + return vec3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); +} + +SI vec3_scalar max(vec3_scalar a, vec3_scalar b) { + return vec3_scalar{max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)}; +} + +vec3 pow(vec3 x, vec3 y) { + return vec3(pow(x.x, y.x), pow(x.y, y.y), pow(x.z, y.z)); +} + +struct vec3_ref { + vec3_ref(Float& x, Float& y, Float& z) : x(x), y(y), z(z) {} + Float& x; + Float& y; + Float& z; + vec3_ref& operator=(const vec3& a) { + x = a.x; + y = a.y; + z = a.z; + return *this; + } + + vec3_ref& operator/=(Float a) { + x /= a; + y /= a; + z /= a; + return *this; + } + + vec3_ref& operator*=(Float a) { + x *= a; + y *= a; + z *= a; + return *this; + } +}; + +struct vec4_scalar { + typedef struct vec4 vector_type; + typedef float element_type; + + float x; + float y; + float z; + float w; + + constexpr vec4_scalar() : vec4_scalar(0.0f) {} + constexpr vec4_scalar(float a) : x(a), y(a), z(a), w(a) {} + constexpr vec4_scalar(float x, float y, float z, float w) + : x(x), y(y), z(z), w(w) {} + vec4_scalar(vec3_scalar xyz, float w) : x(xyz.x), y(xyz.y), z(xyz.z), w(w) {} + + ALWAYS_INLINE float& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + case W: + return w; + default: + UNREACHABLE; + } + } + float& sel(XYZW c1) { return select(c1); } + vec2_scalar sel(XYZW c1, XYZW c2) { + return vec2_scalar{select(c1), select(c2)}; + } + vec3_scalar sel(XYZW c1, XYZW c2, XYZW c3) { + return vec3_scalar{select(c1), select(c2), select(c3)}; + } + vec2_scalar_ref lsel(XYZW c1, XYZW c2) { + return vec2_scalar_ref(select(c1), select(c2)); + } + vec3_scalar_ref lsel(XYZW c1, XYZW c2, XYZW c3) { + return vec3_scalar_ref(select(c1), select(c2), select(c3)); + } + + friend vec4_scalar operator*(vec4_scalar a, vec4_scalar b) { + return vec4_scalar{a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w}; + } + friend vec4_scalar operator*(vec4_scalar a, float b) { + return vec4_scalar{a.x * b, a.y * b, a.z * b, a.w * b}; + } + vec4_scalar& operator*=(float a) { + x *= a; + y *= a; + z *= a; + w *= a; + return *this; + } + + friend vec4_scalar operator-(vec4_scalar a, vec4_scalar b) { + return vec4_scalar{a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w}; + } + friend vec4_scalar operator+(vec4_scalar a, vec4_scalar b) { + return vec4_scalar{a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w}; + } + + friend vec4_scalar operator/(vec4_scalar a, vec4_scalar b) { + return vec4_scalar{a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w}; + } + + vec4_scalar& operator+=(vec4_scalar a) { + x += a.x; + y += a.y; + z += a.z; + w += a.w; + return *this; + } + + vec4_scalar& operator/=(vec4_scalar a) { + x /= a.x; + y /= a.y; + z /= a.z; + w /= a.w; + return *this; + } +}; + +vec4_scalar vec2_scalar::sel(XYZW c1, XYZW c2, XYZW c3, XYZW c4) { + return vec4_scalar{select(c1), select(c2), select(c3), select(c4)}; +} + +struct vec4 { + typedef struct vec4 vector_type; + typedef float element_type; + + constexpr vec4() : vec4(Float(0.0f)) {} + constexpr vec4(Float a) : x(a), y(a), z(a), w(a) {} + vec4(Float x, Float y, Float z, Float w) : x(x), y(y), z(z), w(w) {} + vec4(vec3 xyz, Float w) : x(xyz.x), y(xyz.y), z(xyz.z), w(w) {} + vec4(vec2 xy, vec2 zw) : x(xy.x), y(xy.y), z(zw.x), w(zw.y) {} + vec4(vec2 xy, Float z, Float w) : x(xy.x), y(xy.y), z(z), w(w) {} + vec4(Float x, Float y, vec2 zw) : x(x), y(y), z(zw.x), w(zw.y) {} + constexpr vec4(vec4_scalar s) : x(s.x), y(s.y), z(s.z), w(s.w) {} + constexpr vec4(vec4_scalar s0, vec4_scalar s1, vec4_scalar s2, vec4_scalar s3) + : x(Float{s0.x, s1.x, s2.x, s3.x}), + y(Float{s0.y, s1.y, s2.y, s3.y}), + z(Float{s0.z, s1.z, s2.z, s3.z}), + w(Float{s0.w, s1.w, s2.w, s3.w}) {} + Float& select(XYZW c) { + switch (c) { + case X: + return x; + case Y: + return y; + case Z: + return z; + case W: + return w; + default: + UNREACHABLE; + } + } + Float& sel(XYZW c1) { return select(c1); } + + vec2 sel(XYZW c1, XYZW c2) { return vec2(select(c1), select(c2)); } + + vec3 sel(XYZW c1, XYZW c2, XYZW c3) { + return vec3(select(c1), select(c2), select(c3)); + } + vec3_ref lsel(XYZW c1, XYZW c2, XYZW c3) { + return vec3_ref(select(c1), select(c2), select(c3)); + } + + vec2_ref lsel(XYZW c1, XYZW c2) { return vec2_ref(select(c1), select(c2)); } + + Float& operator[](int index) { + switch (index) { + case 0: + return x; + case 1: + return y; + case 2: + return z; + case 3: + return w; + default: + UNREACHABLE; + } + } + + // glsl supports non-const indexing of vecs. + // hlsl doesn't. The code it generates is probably not wonderful. + Float operator[](I32 index) { + float sel_x = 0; + switch (index.x) { + case 0: + sel_x = x.x; + break; + case 1: + sel_x = y.x; + break; + case 2: + sel_x = z.x; + break; + case 3: + sel_x = w.x; + break; + } + float sel_y = 0; + switch (index.y) { + case 0: + sel_y = x.y; + break; + case 1: + sel_y = y.y; + break; + case 2: + sel_y = z.y; + break; + case 3: + sel_y = w.y; + break; + } + float sel_z = 0; + switch (index.z) { + case 0: + sel_z = x.z; + break; + case 1: + sel_z = y.z; + break; + case 2: + sel_z = z.z; + break; + case 3: + sel_z = w.z; + break; + } + float sel_w = 0; + switch (index.w) { + case 0: + sel_w = x.w; + break; + case 1: + sel_w = y.w; + break; + case 2: + sel_w = z.w; + break; + case 3: + sel_w = w.w; + break; + } + Float ret = {sel_x, sel_y, sel_z, sel_w}; + return ret; + } + + friend vec4 operator/(vec4 a, Float b) { + return vec4(a.x / b, a.y / b, a.z / b, a.w / b); + } + friend vec4 operator/(vec4 a, vec4 b) { + return vec4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); + } + + friend vec4 operator*(vec4 a, Float b) { + return vec4(a.x * b, a.y * b, a.z * b, a.w * b); + } + + friend vec4 operator*(Float b, vec4 a) { + return vec4(a.x * b, a.y * b, a.z * b, a.w * b); + } + friend vec4 operator*(vec4 a, vec4 b) { + return vec4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); + } + + friend vec4 operator-(vec4 a, vec4 b) { + return vec4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); + } + friend vec4 operator+(vec4 a, vec4 b) { + return vec4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); + } + vec4& operator+=(vec4 a) { + x += a.x; + y += a.y; + z += a.z; + w += a.w; + return *this; + } + vec4& operator/=(vec4 a) { + x /= a.x; + y /= a.y; + z /= a.z; + w /= a.w; + return *this; + } + vec4& operator*=(Float a) { + x *= a; + y *= a; + z *= a; + w *= a; + return *this; + } + + Float x; + Float y; + Float z; + Float w; +}; + +vec4_scalar force_scalar(const vec4& v) { + return vec4_scalar{force_scalar(v.x), force_scalar(v.y), force_scalar(v.z), + force_scalar(v.w)}; +} + +vec4_scalar make_vec4(float n) { return vec4_scalar{n, n, n, n}; } + +vec4_scalar make_vec4(const vec2_scalar& v, float z, float w) { + return vec4_scalar{v.x, v.y, z, w}; +} + +vec4_scalar make_vec4(const vec2_scalar& a, const vec2_scalar& b) { + return vec4_scalar{a.x, a.y, b.x, b.y}; +} + +vec4_scalar make_vec4(const vec3_scalar& v, float w) { + return vec4_scalar{v.x, v.y, v.z, w}; +} + +vec4_scalar make_vec4(float x, float y, float z, float w) { + return vec4_scalar{x, y, z, w}; +} + +vec4_scalar make_vec4(float x, float y, const vec2_scalar& v) { + return vec4_scalar{x, y, v.x, v.y}; +} + +template +vec4 make_vec4(const N& n) { + return vec4(n); +} + +template +vec4 make_vec4(const X& x, const Y& y) { + return vec4(x, y); +} + +template +vec4 make_vec4(const X& x, const Y& y, const Z& z) { + return vec4(x, y, z); +} + +template +vec4 make_vec4(const X& x, const Y& y, const Z& z, const W& w) { + return vec4(x, y, z, w); +} + +SI ivec4 roundfast(vec4 v, Float scale) { + return ivec4(roundfast(v.x, scale), roundfast(v.y, scale), + roundfast(v.z, scale), roundfast(v.w, scale)); +} + +vec4 operator*(vec4_scalar a, Float b) { + return vec4(a.x * b, a.y * b, a.z * b, a.w * b); +} + +SI vec4 if_then_else(I32 c, vec4 t, vec4 e) { + return vec4(if_then_else(c, t.x, e.x), if_then_else(c, t.y, e.y), + if_then_else(c, t.z, e.z), if_then_else(c, t.w, e.w)); +} + +SI vec4 if_then_else(int32_t c, vec4 t, vec4 e) { return c ? t : e; } + +SI vec2 clamp(vec2 a, vec2 minVal, vec2 maxVal) { + return vec2(clamp(a.x, minVal.x, maxVal.x), clamp(a.y, minVal.y, maxVal.y)); +} + +SI vec2_scalar clamp(vec2_scalar a, vec2_scalar minVal, vec2_scalar maxVal) { + return vec2_scalar{clamp(a.x, minVal.x, maxVal.x), + clamp(a.y, minVal.y, maxVal.y)}; +} + +SI I32 clamp(I32 a, I32 minVal, I32 maxVal) { + a = if_then_else(a < minVal, minVal, a); + return if_then_else(a > maxVal, maxVal, a); +} + +SI vec3 clamp(vec3 a, vec3 minVal, vec3 maxVal) { + return vec3(clamp(a.x, minVal.x, maxVal.x), clamp(a.y, minVal.y, maxVal.y), + clamp(a.z, minVal.z, maxVal.z)); +} + +SI vec4 clamp(vec4 a, vec4 minVal, vec4 maxVal) { + return vec4(clamp(a.x, minVal.x, maxVal.x), clamp(a.y, minVal.y, maxVal.y), + clamp(a.z, minVal.z, maxVal.z), clamp(a.w, minVal.w, maxVal.w)); +} +template +auto lessThanEqual(T x, T y) -> decltype(x <= y) { + return x <= y; +} + +template +auto lessThan(T x, T y) -> decltype(x < y) { + return x < y; +} + +SI bvec3 lessThanEqual(vec3 x, vec3 y) { + return bvec3(lessThanEqual(x.x, y.x), lessThanEqual(x.y, y.y), + lessThanEqual(x.z, y.z)); +} + +SI bvec2 lessThanEqual(vec2 x, vec2 y) { + return bvec2(lessThanEqual(x.x, y.x), lessThanEqual(x.y, y.y)); +} + +SI bvec2_scalar lessThanEqual(vec2_scalar x, vec2_scalar y) { + return bvec2_scalar{lessThanEqual(x.x, y.x), lessThanEqual(x.y, y.y)}; +} + +SI bvec4 lessThanEqual(vec4 x, vec4 y) { + return bvec4(lessThanEqual(x.x, y.x), lessThanEqual(x.y, y.y), + lessThanEqual(x.z, y.z), lessThanEqual(x.w, y.w)); +} + +SI bvec4_scalar lessThanEqual(vec4_scalar x, vec4_scalar y) { + return bvec4_scalar{lessThanEqual(x.x, y.x), lessThanEqual(x.y, y.y), + lessThanEqual(x.z, y.z), lessThanEqual(x.w, y.w)}; +} + +SI bvec2 lessThan(vec2 x, vec2 y) { + return bvec2(lessThan(x.x, y.x), lessThan(x.y, y.y)); +} + +template +auto greaterThan(T x, T y) -> decltype(x > y) { + return x > y; +} + +bvec2 greaterThan(vec2 x, vec2 y) { + return bvec2(greaterThan(x.x, y.x), greaterThan(x.y, y.y)); +} + +template +auto greaterThanEqual(T x, T y) -> decltype(x >= y) { + return x >= y; +} + +bvec4 greaterThanEqual(vec4 x, vec4 y) { + return bvec4(greaterThanEqual(x.x, y.x), greaterThanEqual(x.y, y.y), + greaterThanEqual(x.z, y.z), greaterThanEqual(x.w, y.w)); +} + +enum TextureFormat { RGBA32F, RGBA32I, RGBA8, R8 }; + +enum TextureFilter { NEAREST, LINEAR }; + +struct samplerCommon { + uint32_t* buf = nullptr; + uint32_t stride = 0; // in dwords + uint32_t height = 0; + uint32_t width = 0; + TextureFormat format = TextureFormat::RGBA8; +}; + +struct samplerDepth { + int depth = 0; + uint32_t height_stride = 0; // in dwords +}; + +struct samplerFilter { + TextureFilter filter = TextureFilter::NEAREST; +}; + +struct sampler2DArray_impl : samplerCommon, samplerDepth, samplerFilter {}; +typedef sampler2DArray_impl* sampler2DArray; + +typedef struct sampler2DArrayR8_impl : sampler2DArray_impl{} * sampler2DArrayR8; +typedef struct sampler2DArrayRGBA8_impl : sampler2DArray_impl{} * + sampler2DArrayRGBA8; +typedef struct sampler2DArrayRGBA32F_impl : sampler2DArray_impl{} * + sampler2DArrayRGBA32F; + +struct sampler2D_impl : samplerCommon, samplerFilter {}; +typedef sampler2D_impl* sampler2D; + +typedef struct sampler2DR8_impl : sampler2D_impl{} * sampler2DR8; +typedef struct sampler2DRGBA8_impl : sampler2D_impl{} * sampler2DRGBA8; +typedef struct sampler2DRGBA32F_impl : sampler2D_impl{} * sampler2DRGBA32F; + +struct isampler2D_impl : samplerCommon {}; +typedef isampler2D_impl* isampler2D; + +struct isampler2DRGBA32I_impl : isampler2D_impl {}; +typedef isampler2DRGBA32I_impl* isampler2DRGBA32I; + +struct sampler2DRect_impl : samplerCommon, samplerFilter {}; +typedef sampler2DRect_impl* sampler2DRect; + +struct mat4_scalar; + +struct mat2_scalar { + vec2_scalar data[2]; + + mat2_scalar() = default; + constexpr mat2_scalar(float a) { + data[0] = vec2_scalar(a); + data[1] = vec2_scalar(a); + } + constexpr mat2_scalar(vec2_scalar a, vec2_scalar b) { + data[0] = a; + data[1] = b; + } + mat2_scalar(const mat4_scalar& mat); + + vec2_scalar& operator[](int index) { return data[index]; } + const vec2_scalar& operator[](int index) const { return data[index]; } + + friend vec2_scalar operator*(mat2_scalar m, vec2_scalar v) { + vec2_scalar u; + u.x = m[0].x * v.x + m[1].x * v.y; + u.y = m[0].y * v.x + m[1].y * v.y; + return u; + } + + friend vec2 operator*(mat2_scalar m, vec2 v) { + vec2 u; + u.x = m[0].x * v.x + m[1].x * v.y; + u.y = m[0].y * v.x + m[1].y * v.y; + return u; + } + + friend mat2_scalar operator*(mat2_scalar m, float f) { + mat2_scalar u = m; + u[0].x *= f; + u[0].y *= f; + u[1].x *= f; + u[1].y *= f; + return u; + } +}; + +struct mat4; + +struct mat2 { + vec2 data[2]; + + vec2& operator[](int index) { return data[index]; } + const vec2& operator[](int index) const { return data[index]; } + mat2() = default; + + mat2(Float a) { + data[0] = vec2(a); + data[1] = vec2(a); + } + + mat2(vec2 a, vec2 b) { + data[0] = a; + data[1] = b; + } + mat2(const mat4& mat); + constexpr mat2(mat2_scalar s) { + data[0] = vec2(s.data[0]); + data[1] = vec2(s.data[1]); + } + + friend vec2 operator*(mat2 m, vec2 v) { + vec2 u; + u.x = m[0].x * v.x + m[1].x * v.y; + u.y = m[0].y * v.x + m[1].y * v.y; + return u; + } + friend mat2 operator*(mat2 m, Float f) { + mat2 u = m; + u[0].x *= f; + u[0].y *= f; + u[1].x *= f; + u[1].y *= f; + return u; + } +}; + +mat2_scalar make_mat2(float n) { return mat2_scalar{{n, n}, {n, n}}; } + +mat2_scalar make_mat2(const mat2_scalar& m) { return m; } + +mat2_scalar make_mat2(const vec2_scalar& x, const vec2_scalar& y) { + return mat2_scalar{x, y}; +} + +template +mat2 make_mat2(const N& n) { + return mat2(n); +} + +template +mat2 make_mat2(const X& x, const Y& y) { + return mat2(x, y); +} + +SI mat2 if_then_else(I32 c, mat2 t, mat2 e) { + return mat2(if_then_else(c, t[0], e[0]), if_then_else(c, t[0], e[1])); +} + +SI mat2 if_then_else(int32_t c, mat2 t, mat2 e) { return c ? t : e; } + +struct mat3_scalar { + vec3_scalar data[3]; + + mat3_scalar() = default; + constexpr mat3_scalar(vec3_scalar a, vec3_scalar b, vec3_scalar c) { + data[0] = a; + data[1] = b; + data[2] = c; + } + mat3_scalar(const mat4_scalar& mat); + + vec3_scalar& operator[](int index) { return data[index]; } + const vec3_scalar& operator[](int index) const { return data[index]; } + + friend vec3_scalar operator*(mat3_scalar m, vec3_scalar v) { + vec3_scalar u; + u.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z; + u.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z; + u.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z; + return u; + } + + friend vec3 operator*(mat3_scalar m, vec3 v) { + vec3 u; + u.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z; + u.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z; + u.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z; + return u; + } +}; + +struct mat3 { + vec3 data[3]; + + vec3& operator[](int index) { return data[index]; } + const vec3& operator[](int index) const { return data[index]; } + mat3() = default; + mat3(vec3 a, vec3 b, vec3 c) { + data[0] = a; + data[1] = b; + data[2] = c; + } + + constexpr mat3(mat3_scalar s) { + data[0] = vec3(s.data[0]); + data[1] = vec3(s.data[1]); + data[2] = vec3(s.data[2]); + } + constexpr mat3(mat3_scalar s0, mat3_scalar s1, mat3_scalar s2, + mat3_scalar s3) { + data[0] = vec3(s0.data[0], s1.data[0], s2.data[0], s3.data[0]); + data[1] = vec3(s0.data[1], s1.data[1], s2.data[1], s3.data[1]); + data[2] = vec3(s0.data[2], s1.data[2], s2.data[2], s3.data[2]); + } + + constexpr mat3(Float d1, Float d2, Float d3, Float d4, Float d5, Float d6, + Float d7, Float d8, Float d9) { + data[0] = vec3(d1, d2, d3); + data[1] = vec3(d4, d5, d6); + data[2] = vec3(d7, d8, d9); + } + + mat3(const mat4& mat); + + friend vec3 operator*(mat3 m, vec3 v) { + vec3 u; + u.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z; + u.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z; + u.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z; + return u; + } +}; + +mat3_scalar force_scalar(const mat3& v) { + return mat3_scalar{force_scalar(v[0]), force_scalar(v[1]), + force_scalar(v[2])}; +} + +mat3_scalar make_mat3(const mat3_scalar& m) { return m; } + +mat3_scalar make_mat3(const vec3_scalar& x, const vec3_scalar& y, + const vec3_scalar& z) { + return mat3_scalar{x, y, z}; +} + +constexpr mat3_scalar make_mat3(float m0, float m1, float m2, float m3, + float m4, float m5, float m6, float m7, + float m8) { + return mat3_scalar{{m0, m1, m2}, {m3, m4, m5}, {m6, m7, m8}}; +} + +template +mat3 make_mat3(const N& n) { + return mat3(n); +} + +template +mat3 make_mat3(const X& x, const Y& y, const Z& z) { + return mat3(x, y, z); +} + +struct mat4_scalar { + vec4_scalar data[4]; + + mat4_scalar() = default; + constexpr mat4_scalar(vec4_scalar a, vec4_scalar b, vec4_scalar c, + vec4_scalar d) { + data[0] = a; + data[1] = b; + data[2] = c; + data[3] = d; + } + + vec4_scalar& operator[](int index) { return data[index]; } + const vec4_scalar& operator[](int index) const { return data[index]; } + + static mat4_scalar load_from_ptr(const float* f) { + mat4_scalar m; + // XXX: hopefully this is in the right order + m.data[0] = vec4_scalar{f[0], f[1], f[2], f[3]}; + m.data[1] = vec4_scalar{f[4], f[5], f[6], f[7]}; + m.data[2] = vec4_scalar{f[8], f[9], f[10], f[11]}; + m.data[3] = vec4_scalar{f[12], f[13], f[14], f[15]}; + return m; + } + + friend vec4_scalar operator*(mat4_scalar m, vec4_scalar v) { + vec4_scalar u; + u.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z + m[3].x * v.w; + u.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z + m[3].y * v.w; + u.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z + m[3].z * v.w; + u.w = m[0].w * v.x + m[1].w * v.y + m[2].w * v.z + m[3].w * v.w; + return u; + } + + friend vec4 operator*(mat4_scalar m, vec4 v) { + vec4 u; + u.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z + m[3].x * v.w; + u.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z + m[3].y * v.w; + u.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z + m[3].z * v.w; + u.w = m[0].w * v.x + m[1].w * v.y + m[2].w * v.z + m[3].w * v.w; + return u; + } +}; + +struct mat4 { + vec4 data[4]; + + mat4() = default; + constexpr mat4(mat4_scalar s) { + data[0] = vec4(s.data[0]); + data[1] = vec4(s.data[1]); + data[2] = vec4(s.data[2]); + data[3] = vec4(s.data[3]); + } + + mat4(vec4 a, vec4 b, vec4 c, vec4 d) { + data[0] = a; + data[1] = b; + data[2] = c; + data[3] = d; + } + + vec4& operator[](int index) { return data[index]; } + const vec4& operator[](int index) const { return data[index]; } + + friend vec4 operator*(mat4 m, vec4 v) { + vec4 u; + u.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z + m[3].x * v.w; + u.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z + m[3].y * v.w; + u.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z + m[3].z * v.w; + u.w = m[0].w * v.x + m[1].w * v.y + m[2].w * v.z + m[3].w * v.w; + return u; + } +}; + +mat3::mat3(const mat4& mat) + : mat3(vec3(mat[0].x, mat[0].y, mat[0].z), + vec3(mat[1].x, mat[1].y, mat[1].z), + vec3(mat[2].x, mat[2].y, mat[2].z)) {} + +mat3_scalar::mat3_scalar(const mat4_scalar& mat) + : mat3_scalar(vec3_scalar(mat[0].x, mat[0].y, mat[0].z), + vec3_scalar(mat[1].x, mat[1].y, mat[1].z), + vec3_scalar(mat[2].x, mat[2].y, mat[2].z)) {} + +mat2::mat2(const mat4& mat) + : mat2(vec2(mat[0].x, mat[0].y), vec2(mat[1].x, mat[1].y)) {} + +mat2_scalar::mat2_scalar(const mat4_scalar& mat) + : mat2_scalar(vec2_scalar(mat[0].x, mat[0].y), + vec2_scalar(mat[1].x, mat[1].y)) {} + +mat2_scalar make_mat2(const mat4_scalar& m) { return mat2_scalar(m); } + +mat3_scalar make_mat3(const mat4_scalar& m) { return mat3_scalar(m); } + +mat4_scalar force_scalar(const mat4& v) { + return mat4_scalar(force_scalar(v[0]), force_scalar(v[1]), force_scalar(v[2]), + force_scalar(v[3])); +} + +mat4_scalar make_mat4(const mat4_scalar& m) { return m; } + +mat4_scalar make_mat4(const vec4_scalar& x, const vec4_scalar& y, + const vec4_scalar& z, const vec4_scalar& w) { + return mat4_scalar{x, y, z, w}; +} + +constexpr mat4_scalar make_mat4(float m0, float m1, float m2, float m3, + float m4, float m5, float m6, float m7, + float m8, float m9, float m10, float m11, + float m12, float m13, float m14, float m15) { + return mat4_scalar{{m0, m1, m2, m3}, + {m4, m5, m6, m7}, + {m8, m9, m10, m11}, + {m12, m13, m14, m15}}; +} + +template +mat4 make_mat4(const N& n) { + return mat4(n); +} + +template +mat4 make_mat4(const X& x, const Y& y, const Z& z, const W& w) { + return mat4(x, y, z, w); +} + +SI mat3 if_then_else(I32 c, mat3 t, mat3 e) { + return mat3{if_then_else(c, t[0], e[0]), if_then_else(c, t[1], e[1]), + if_then_else(c, t[2], e[2])}; +} + +SI mat3 if_then_else(int32_t c, mat3 t, mat3 e) { return c ? t : e; } + +SI mat4 if_then_else(I32 c, mat4 t, mat4 e) { + return mat4{if_then_else(c, t[0], e[0]), if_then_else(c, t[1], e[1]), + if_then_else(c, t[2], e[2]), if_then_else(c, t[3], e[3])}; +} + +SI mat4 if_then_else(int32_t c, mat4 t, mat4 e) { return c ? t : e; } + +SI I32 clampCoord(I32 coord, int limit) { +#if USE_SSE2 + return _mm_min_epi16(_mm_max_epi16(coord, _mm_setzero_si128()), + _mm_set1_epi32(limit - 1)); +#else + return clamp(coord, 0, limit - 1); +#endif +} +SI int clampCoord(int coord, int limit) { + return min(max(coord, 0), limit - 1); +} +template +SI T clamp2D(T P, S sampler) { + return T{clampCoord(P.x, sampler->width), clampCoord(P.y, sampler->height)}; +} +template +SI T clamp2DArray(T P, sampler2DArray sampler) { + return T{clampCoord(P.x, sampler->width), clampCoord(P.y, sampler->height), + clampCoord(P.z, sampler->depth)}; +} + +float to_float(uint32_t x) { return x * (1.f / 255.f); } + +vec4 pixel_to_vec4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { + U32 pixels = {a, b, c, d}; + return vec4(cast((pixels >> 16) & 0xFF), cast((pixels >> 8) & 0xFF), + cast(pixels & 0xFF), cast(pixels >> 24)) * + (1.0f / 255.0f); +} + +vec4 pixel_float_to_vec4(Float a, Float b, Float c, Float d) { + return vec4(Float{a.x, b.x, c.x, d.x}, Float{a.y, b.y, c.y, d.y}, + Float{a.z, b.z, c.z, d.z}, Float{a.w, b.w, c.w, d.w}); +} + +ivec4 pixel_int_to_ivec4(I32 a, I32 b, I32 c, I32 d) { + return ivec4(I32{a.x, b.x, c.x, d.x}, I32{a.y, b.y, c.y, d.y}, + I32{a.z, b.z, c.z, d.z}, I32{a.w, b.w, c.w, d.w}); +} + +vec4_scalar pixel_to_vec4(uint32_t p) { + U32 i = {(p >> 16) & 0xFF, (p >> 8) & 0xFF, p & 0xFF, p >> 24}; + Float f = cast(i) * (1.0f / 255.0f); + return vec4_scalar(f.x, f.y, f.z, f.w); +} + +template +SI vec4 fetchOffsetsRGBA8(S sampler, I32 offset) { + return pixel_to_vec4(sampler->buf[offset.x], sampler->buf[offset.y], + sampler->buf[offset.z], sampler->buf[offset.w]); +} + +vec4 texelFetchRGBA8(sampler2D sampler, ivec2 P) { + I32 offset = P.x + P.y * sampler->stride; + return fetchOffsetsRGBA8(sampler, offset); +} + +vec4 texelFetchRGBA8(sampler2DArray sampler, ivec3 P) { + assert(test_all(P.z == P.z.x)); + I32 offset = P.x + P.y * sampler->stride + P.z.x * sampler->height_stride; + return fetchOffsetsRGBA8(sampler, offset); +} + +template +SI Float fetchOffsetsR8(S sampler, I32 offset) { + U32 i = { + ((uint8_t*)sampler->buf)[offset.x], ((uint8_t*)sampler->buf)[offset.y], + ((uint8_t*)sampler->buf)[offset.z], ((uint8_t*)sampler->buf)[offset.w]}; + return cast(i) * (1.0f / 255.0f); +} + +vec4 texelFetchR8(sampler2D sampler, ivec2 P) { + I32 offset = P.x + P.y * sampler->stride; + return vec4(fetchOffsetsR8(sampler, offset), 0.0f, 0.0f, 1.0f); +} + +vec4 texelFetchR8(sampler2DArray sampler, ivec3 P) { + assert(test_all(P.z == P.z.x)); + I32 offset = P.x + P.y * sampler->stride + P.z.x * sampler->height_stride; + return vec4(fetchOffsetsR8(sampler, offset), 0.0f, 0.0f, 1.0f); +} + +template +SI vec4 fetchOffsetsFloat(S sampler, I32 offset) { + return pixel_float_to_vec4( + *(Float*)&sampler->buf[offset.x], *(Float*)&sampler->buf[offset.y], + *(Float*)&sampler->buf[offset.z], *(Float*)&sampler->buf[offset.w]); +} + +vec4 texelFetchFloat(sampler2D sampler, ivec2 P) { + I32 offset = P.x * 4 + P.y * sampler->stride; + return fetchOffsetsFloat(sampler, offset); +} + +SI vec4 texelFetchFloat(sampler2DArray sampler, ivec3 P) { + assert(test_all(P.z == P.z.x)); + I32 offset = P.x * 4 + P.y * sampler->stride + P.z.x * sampler->height_stride; + return fetchOffsetsFloat(sampler, offset); +} + +vec4 texelFetch(sampler2D sampler, ivec2 P, int lod) { + assert(lod == 0); + P = clamp2D(P, sampler); + if (sampler->format == TextureFormat::RGBA32F) { + return texelFetchFloat(sampler, P); + } else if (sampler->format == TextureFormat::RGBA8) { + return texelFetchRGBA8(sampler, P); + } else { + assert(sampler->format == TextureFormat::R8); + return texelFetchR8(sampler, P); + } +} + +vec4 texelFetch(sampler2DRGBA32F sampler, ivec2 P, int lod) { + assert(lod == 0); + P = clamp2D(P, sampler); + assert(sampler->format == TextureFormat::RGBA32F); + return texelFetchFloat(sampler, P); +} + +vec4 texelFetch(sampler2DRGBA8 sampler, ivec2 P, int lod) { + assert(lod == 0); + P = clamp2D(P, sampler); + assert(sampler->format == TextureFormat::RGBA8); + return texelFetchRGBA8(sampler, P); +} + +vec4 texelFetch(sampler2DR8 sampler, ivec2 P, int lod) { + assert(lod == 0); + P = clamp2D(P, sampler); + assert(sampler->format == TextureFormat::R8); + return texelFetchR8(sampler, P); +} + +vec4_scalar texelFetch(sampler2D sampler, ivec2_scalar P, int lod) { + assert(lod == 0); + P = clamp2D(P, sampler); + if (sampler->format == TextureFormat::RGBA32F) { + return *(vec4_scalar*)&sampler->buf[P.x * 4 + P.y * sampler->stride]; + } else { + assert(sampler->format == TextureFormat::RGBA8); + return pixel_to_vec4(sampler->buf[P.x + P.y * sampler->stride]); + } +} + +vec4_scalar texelFetch(sampler2DRGBA32F sampler, ivec2_scalar P, int lod) { + assert(lod == 0); + P = clamp2D(P, sampler); + assert(sampler->format == TextureFormat::RGBA32F); + return *(vec4_scalar*)&sampler->buf[P.x * 4 + P.y * sampler->stride]; +} + +vec4_scalar texelFetch(sampler2DRGBA8 sampler, ivec2_scalar P, int lod) { + assert(lod == 0); + P = clamp2D(P, sampler); + assert(sampler->format == TextureFormat::RGBA8); + return pixel_to_vec4(sampler->buf[P.x + P.y * sampler->stride]); +} + +vec4_scalar texelFetch(sampler2DR8 sampler, ivec2_scalar P, int lod) { + assert(lod == 0); + P = clamp2D(P, sampler); + assert(sampler->format == TextureFormat::R8); + return vec4_scalar{ + to_float(((uint8_t*)sampler->buf)[P.x + P.y * sampler->stride]), 0.0f, + 0.0f, 0.0f}; +} + +vec4 texelFetch(sampler2DRect sampler, ivec2 P) { + P = clamp2D(P, sampler); + assert(sampler->format == TextureFormat::RGBA8); + I32 offset = P.x + P.y * sampler->stride; + return fetchOffsetsRGBA8(sampler, offset); +} + +SI vec4 texelFetch(sampler2DArray sampler, ivec3 P, int lod) { + assert(lod == 0); + P = clamp2DArray(P, sampler); + if (sampler->format == TextureFormat::RGBA32F) { + return texelFetchFloat(sampler, P); + } else if (sampler->format == TextureFormat::R8) { + return texelFetchR8(sampler, P); + } else { + assert(sampler->format == TextureFormat::RGBA8); + return texelFetchRGBA8(sampler, P); + } +} + +vec4 texelFetch(sampler2DArrayRGBA32F sampler, ivec3 P, int lod) { + assert(lod == 0); + P = clamp2DArray(P, sampler); + assert(sampler->format == TextureFormat::RGBA32F); + return texelFetchFloat(sampler, P); +} + +vec4 texelFetch(sampler2DArrayRGBA8 sampler, ivec3 P, int lod) { + assert(lod == 0); + P = clamp2DArray(P, sampler); + assert(sampler->format == TextureFormat::RGBA8); + return texelFetchRGBA8(sampler, P); +} + +vec4 texelFetch(sampler2DArrayR8 sampler, ivec3 P, int lod) { + assert(lod == 0); + P = clamp2DArray(P, sampler); + assert(sampler->format == TextureFormat::R8); + return texelFetchR8(sampler, P); +} + +template +SI ivec4 fetchOffsetsInt(S sampler, I32 offset) { + return pixel_int_to_ivec4( + *(I32*)&sampler->buf[offset.x], *(I32*)&sampler->buf[offset.y], + *(I32*)&sampler->buf[offset.z], *(I32*)&sampler->buf[offset.w]); +} + +ivec4 texelFetch(isampler2D sampler, ivec2 P, int lod) { + assert(lod == 0); + P = clamp2D(P, sampler); + assert(sampler->format == TextureFormat::RGBA32I); + I32 offset = P.x * 4 + P.y * sampler->stride; + return fetchOffsetsInt(sampler, offset); +} + +ivec4_scalar texelFetch(isampler2D sampler, ivec2_scalar P, int lod) { + assert(lod == 0); + P = clamp2D(P, sampler); + assert(sampler->format == TextureFormat::RGBA32I); + return *(ivec4_scalar*)&sampler->buf[P.x * 4 + P.y * sampler->stride]; +} + +SI vec4_scalar* texelFetchPtr(sampler2D sampler, ivec2_scalar P, int min_x, + int max_x, int min_y, int max_y) { + P.x = min(max(P.x, -min_x), int(sampler->width) - 1 - max_x); + P.y = min(max(P.y, -min_y), int(sampler->height) - 1 - max_y); + assert(sampler->format == TextureFormat::RGBA32F); + return (vec4_scalar*)&sampler->buf[P.x * 4 + P.y * sampler->stride]; +} + +SI ivec4_scalar* texelFetchPtr(isampler2D sampler, ivec2_scalar P, int min_x, + int max_x, int min_y, int max_y) { + P.x = min(max(P.x, -min_x), int(sampler->width) - 1 - max_x); + P.y = min(max(P.y, -min_y), int(sampler->height) - 1 - max_y); + assert(sampler->format == TextureFormat::RGBA32I); + return (ivec4_scalar*)&sampler->buf[P.x * 4 + P.y * sampler->stride]; +} + +#define texelFetchOffset(sampler, P, lod, offset) \ + texelFetch(sampler, (P) + (offset), lod) + +template +SI R mix(T x, U y, A a) { + return (y - x) * a + x; +} + +SI Float mix(Float x, Float y, Float a) { return (y - x) * a + x; } + +template +SI T mix(T x, T y, float a) { + return (y - x) * a + x; +} + +template +SI T mix(T x, T y, vec4_scalar a) { + return T{mix(x.x, y.x, a.x), mix(x.y, y.y, a.y), mix(x.z, y.z, a.z), + mix(x.w, y.w, a.w)}; +} + +// Scale texture coords for quantization, subtract offset for filtering +// (assuming coords already offset to texel centers), and round to nearest +// 1/scale increment +template +SI T linearQuantize(T P, float scale) { + return P * scale + (0.5f - 0.5f * scale); +} + +// Helper version that also scales normalized texture coords for sampler +template +SI T linearQuantize(T P, float scale, S sampler) { + P.x *= sampler->width; + P.y *= sampler->height; + return linearQuantize(P, scale); +} + +template +vec4 textureLinearRGBA8(S sampler, vec2 P, int32_t zoffset = 0) { + assert(sampler->format == TextureFormat::RGBA8); + +#if USE_SSE2 + ivec2 i(linearQuantize(P, 256, sampler)); + ivec2 frac = i & (I32)0xFF; + i >>= 8; + + // Pack coords so they get clamped into range, and also for later bounding + // of fractional coords. Store Y as low-bits for easier access, X as high. + __m128i yx = _mm_packs_epi32(i.y, i.x); + __m128i hw = _mm_packs_epi32(_mm_set1_epi32(sampler->height - 1), + _mm_set1_epi32(sampler->width - 1)); + // Clamp coords to valid range to prevent sampling outside texture. + __m128i clampyx = _mm_min_epi16(_mm_max_epi16(yx, _mm_setzero_si128()), hw); + // Multiply clamped Y by stride and add X offset. + __m128i row0 = _mm_madd_epi16( + _mm_unpacklo_epi16(clampyx, _mm_setzero_si128()), + _mm_set1_epi16(sampler->stride)); + row0 = _mm_add_epi32(row0, _mm_unpackhi_epi16(clampyx, _mm_setzero_si128())); + // Add in layer offset if available + row0 = _mm_add_epi32(row0, _mm_set1_epi32(zoffset)); + + // Check if fractional coords are all zero, in which case skip filtering. + __m128i fracyx = _mm_packs_epi32(frac.y, frac.x); + if (!_mm_movemask_epi8(_mm_cmpgt_epi16(fracyx, _mm_setzero_si128()))) { + return fetchOffsetsRGBA8(sampler, row0); + } + + // Check if coords were clamped at all above. If so, need to adjust fractions + // to avoid sampling outside the texture on the edges. + __m128i yxinside = _mm_andnot_si128( + _mm_cmplt_epi16(yx, _mm_setzero_si128()), + _mm_cmplt_epi16(yx, hw)); + // Set fraction to zero when outside. + fracyx = _mm_and_si128(fracyx, yxinside); + // Store two side-by-side copies of X fraction, as below each pixel value + // will be interleaved to be next to the pixel value for the next row. + __m128i fracx = _mm_unpackhi_epi16(fracyx, fracyx); + // For Y fraction, we need to store 1-fraction before each fraction, as a + // madd will be used to weight and collapse all results as last step. + __m128i fracy = _mm_unpacklo_epi16( + _mm_sub_epi16(_mm_set1_epi16(256), fracyx), fracyx); + + // Ensure we don't sample row off end of texture from added stride. + __m128i row1 = _mm_and_si128(yxinside, _mm_set1_epi16(sampler->stride)); + + // Load two adjacent pixels on each row and interleave them. + // r0,g0,b0,a0,r1,g1,b1,a1 \/ R0,G0,B0,A0,R1,G1,B1,A1 + // r0,R0,g0,G0,b0,B0,a0,A0,r1,R1,g1,G1,b1,B1,a1,A1 +# define LOAD_LANE(out, idx) \ + { \ + uint32_t* buf = &sampler->buf[_mm_cvtsi128_si32( \ + _mm_shuffle_epi32(row0, _MM_SHUFFLE(idx, idx, idx, idx)))]; \ + out = _mm_unpacklo_epi8( \ + _mm_loadl_epi64((__m128i*)buf), \ + _mm_loadl_epi64((__m128i*)(buf + _mm_extract_epi16(row1, idx)))); \ + } + __m128i x, y, z, w; + LOAD_LANE(x, 0) + LOAD_LANE(y, 1) + LOAD_LANE(z, 2) + LOAD_LANE(w, 3) +# undef LOAD_LANE + + // Need to transpose the data from AoS to SoA format. Best to do this here + // while the data is still packed into 8-bit components, requiring fewer + // insns. + // r0,R0,g0,G0,b0,B0,a0,A0,r1,R1,g1,G1,b1,B1,a1,A1 \/ + // r2,R2,g2,G2,b2,B2,a2,A2,r3,R3,g3,G3,b3,B3,a3,A3 + // ... r0,R0,r2,R2,g0,G0,g2,G2,b0,B0,b2,B2,a0,A0,a2,A2 + // ... r1,R1,r3,R3,g1,G1,g3,G3,b1,B1,b3,B3,a1,A1,a3,A3 + __m128i xy0 = _mm_unpacklo_epi16(x, y); + __m128i xy1 = _mm_unpackhi_epi16(x, y); + __m128i zw0 = _mm_unpacklo_epi16(z, w); + __m128i zw1 = _mm_unpackhi_epi16(z, w); + // r0,R0,r2,R2,g0,G0,g2,G2,b0,B0,b2,B2,a0,A0,a2,A2 \/ + // r4,R4,r6,R6,g4,G4,g6,G6,b4,B4,b6,B6,a4,A4,a6,A6 + // ... r0,R0,r2,R2,r4,R4,r6,R6,g0,G0,g2,G2,g4,G4,g6,G6 + // ... b0,B0,b2,B2,b4,B4,b6,B6,a0,A0,a2,A2,a4,A4,a6,A6 + __m128i rg0 = _mm_unpacklo_epi32(xy0, zw0); + __m128i ba0 = _mm_unpackhi_epi32(xy0, zw0); + __m128i rg1 = _mm_unpacklo_epi32(xy1, zw1); + __m128i ba1 = _mm_unpackhi_epi32(xy1, zw1); + + // Expand packed SoA pixels for each column. Multiply then add columns with + // 8-bit precision so we don't carry to high byte of word accidentally. Use + // final madd insn to blend interleaved rows and expand result to 32 bits. +# define FILTER_COMPONENT(out, unpack, src0, src1) \ + { \ + __m128i cc0 = unpack(src0, _mm_setzero_si128()); \ + __m128i cc1 = unpack(src1, _mm_setzero_si128()); \ + cc0 = _mm_add_epi8( \ + cc0, _mm_srli_epi16(_mm_mullo_epi16(_mm_sub_epi16(cc1, cc0), fracx), \ + 8)); \ + out = _mm_cvtepi32_ps(_mm_madd_epi16(cc0, fracy)); \ + } + __m128 fr, fg, fb, fa; + FILTER_COMPONENT(fr, _mm_unpacklo_epi8, rg0, rg1); + FILTER_COMPONENT(fg, _mm_unpackhi_epi8, rg0, rg1); + FILTER_COMPONENT(fb, _mm_unpacklo_epi8, ba0, ba1); + FILTER_COMPONENT(fa, _mm_unpackhi_epi8, ba0, ba1); +# undef FILTER_COMPONENT + + return vec4(fb, fg, fr, fa) * (1.0f / 0xFF00); +#else + ivec2 i(linearQuantize(P, 128, sampler)); + ivec2 frac = i & (I32)0x7F; + i >>= 7; + + I32 row0 = clampCoord(i.x, sampler->width) + + clampCoord(i.y, sampler->height) * sampler->stride + zoffset; + I32 row1 = row0 + ((i.y >= 0 && i.y < int32_t(sampler->height) - 1) & + I32(sampler->stride)); + I16 fracx = + CONVERT(frac.x & (i.x >= 0 && i.x < int32_t(sampler->width) - 1), I16); + I16 fracy = CONVERT(frac.y, I16); + + auto a0 = + CONVERT(unaligned_load >(&sampler->buf[row0.x]), V8); + auto a1 = + CONVERT(unaligned_load >(&sampler->buf[row1.x]), V8); + a0 += ((a1 - a0) * fracy.x) >> 7; + + auto b0 = + CONVERT(unaligned_load >(&sampler->buf[row0.y]), V8); + auto b1 = + CONVERT(unaligned_load >(&sampler->buf[row1.y]), V8); + b0 += ((b1 - b0) * fracy.y) >> 7; + + auto abl = zipLow(a0, b0); + auto abh = zipHigh(a0, b0); + abl += ((abh - abl) * fracx.xyxyxyxy) >> 7; + + auto c0 = + CONVERT(unaligned_load >(&sampler->buf[row0.z]), V8); + auto c1 = + CONVERT(unaligned_load >(&sampler->buf[row1.z]), V8); + c0 += ((c1 - c0) * fracy.z) >> 7; + + auto d0 = + CONVERT(unaligned_load >(&sampler->buf[row0.w]), V8); + auto d1 = + CONVERT(unaligned_load >(&sampler->buf[row1.w]), V8); + d0 += ((d1 - d0) * fracy.w) >> 7; + + auto cdl = zipLow(c0, d0); + auto cdh = zipHigh(c0, d0); + cdl += ((cdh - cdl) * fracx.zwzwzwzw) >> 7; + + auto rg = CONVERT(V8(zip2Low(abl, cdl)), V8); + auto ba = CONVERT(V8(zip2High(abl, cdl)), V8); + + auto r = lowHalf(rg); + auto g = highHalf(rg); + auto b = lowHalf(ba); + auto a = highHalf(ba); + return vec4(b, g, r, a) * (1.0f / 255.0f); +#endif +} + +template +static U16 textureLinearPackedR8(S sampler, ivec2 i, int32_t zoffset) { + assert(sampler->format == TextureFormat::R8); + ivec2 frac = i & (I32)0x7F; + i >>= 7; + + I32 row0 = clampCoord(i.x, sampler->width) + + clampCoord(i.y, sampler->height) * sampler->stride + zoffset; + I32 row1 = row0 + ((i.y >= 0 && i.y < int32_t(sampler->height) - 1) & + I32(sampler->stride)); + I16 fracx = + CONVERT(frac.x & (i.x >= 0 && i.x < int32_t(sampler->width) - 1), I16); + I16 fracy = CONVERT(frac.y, I16); + + uint8_t* buf = (uint8_t*)sampler->buf; + auto a0 = unaligned_load >(&buf[row0.x]); + auto b0 = unaligned_load >(&buf[row0.y]); + auto c0 = unaligned_load >(&buf[row0.z]); + auto d0 = unaligned_load >(&buf[row0.w]); + auto abcd0 = CONVERT(combine(combine(a0, b0), combine(c0, d0)), V8); + + auto a1 = unaligned_load >(&buf[row1.x]); + auto b1 = unaligned_load >(&buf[row1.y]); + auto c1 = unaligned_load >(&buf[row1.z]); + auto d1 = unaligned_load >(&buf[row1.w]); + auto abcd1 = CONVERT(combine(combine(a1, b1), combine(c1, d1)), V8); + + abcd0 += ((abcd1 - abcd0) * fracy.xxyyzzww) >> 7; + + abcd0 = SHUFFLE(abcd0, abcd0, 0, 2, 4, 6, 1, 3, 5, 7); + auto abcdl = lowHalf(abcd0); + auto abcdh = highHalf(abcd0); + abcdl += ((abcdh - abcdl) * fracx) >> 7; + + return U16(abcdl); +} + +template +vec4 textureLinearR8(S sampler, vec2 P, int32_t zoffset = 0) { + assert(sampler->format == TextureFormat::R8); + +#if USE_SSE2 + ivec2 i(linearQuantize(P, 256, sampler)); + ivec2 frac = i & (I32)0xFF; + i >>= 8; + + // Pack coords so they get clamped into range, and also for later bounding + // of fractional coords. Store Y as low-bits for easier access, X as high. + __m128i yx = _mm_packs_epi32(i.y, i.x); + __m128i hw = _mm_packs_epi32(_mm_set1_epi32(sampler->height - 1), + _mm_set1_epi32(sampler->width - 1)); + // Clamp coords to valid range to prevent sampling outside texture. + __m128i clampyx = _mm_min_epi16(_mm_max_epi16(yx, _mm_setzero_si128()), hw); + // Multiply clamped Y by stride and add X offset. + __m128i row0 = _mm_madd_epi16( + _mm_unpacklo_epi16(clampyx, _mm_setzero_si128()), + _mm_set1_epi16(sampler->stride)); + row0 = _mm_add_epi32(row0, _mm_unpackhi_epi16(clampyx, _mm_setzero_si128())); + // Add in layer offset if available + row0 = _mm_add_epi32(row0, _mm_set1_epi32(zoffset)); + + __m128i fracyx = _mm_packs_epi32(frac.y, frac.x); + + // Check if coords were clamped at all above. If so, need to adjust fractions + // to avoid sampling outside the texture on the edges. + __m128i yxinside = _mm_andnot_si128( + _mm_cmplt_epi16(yx, _mm_setzero_si128()), + _mm_cmplt_epi16(yx, hw)); + // Set fraction to zero when outside. + fracyx = _mm_and_si128(fracyx, yxinside); + // For X fraction, we need to store 1-fraction before each fraction, as a + // madd will be used to weight and collapse all results as last step. + __m128i fracx = _mm_unpackhi_epi16( + _mm_sub_epi16(_mm_set1_epi16(256), fracyx), fracyx); + // Store two side-by-side copies of Y fraction, as below each pixel value + // will be interleaved to be next to the pixel value for the next column. + __m128i fracy = _mm_unpacklo_epi16(fracyx, fracyx); + + // Ensure we don't sample row off end of texture from added stride. + __m128i row1 = _mm_and_si128(yxinside, _mm_set1_epi16(sampler->stride)); + + // Calculate pointers for first row in each lane + uint8_t* buf = (uint8_t*)sampler->buf; + uint8_t* buf0 = + buf + _mm_cvtsi128_si32(_mm_shuffle_epi32(row0, _MM_SHUFFLE(0, 0, 0, 0))); + uint8_t* buf1 = + buf + _mm_cvtsi128_si32(_mm_shuffle_epi32(row0, _MM_SHUFFLE(1, 1, 1, 1))); + uint8_t* buf2 = + buf + _mm_cvtsi128_si32(_mm_shuffle_epi32(row0, _MM_SHUFFLE(2, 2, 2, 2))); + uint8_t* buf3 = + buf + _mm_cvtsi128_si32(_mm_shuffle_epi32(row0, _MM_SHUFFLE(3, 3, 3, 3))); + // Load adjacent columns from first row, pack into register, then expand. + __m128i cc0 = _mm_unpacklo_epi8( + _mm_setr_epi16(*(uint16_t*)buf0, *(uint16_t*)buf1, *(uint16_t*)buf2, + *(uint16_t*)buf3, 0, 0, 0, 0), + _mm_setzero_si128()); + // Load adjacent columns from next row, pack into register, then expand. + __m128i cc1 = _mm_unpacklo_epi8( + _mm_setr_epi16(*(uint16_t*)(buf0 + _mm_extract_epi16(row1, 0)), + *(uint16_t*)(buf1 + _mm_extract_epi16(row1, 1)), + *(uint16_t*)(buf2 + _mm_extract_epi16(row1, 2)), + *(uint16_t*)(buf3 + _mm_extract_epi16(row1, 3)), + 0, 0, 0, 0), + _mm_setzero_si128()); + // Multiply then add rows with 8-bit precision so we don't carry to high byte + // of word accidentally. Use final madd insn to blend interleaved columns and + // expand result to 32 bits. + __m128i cc = _mm_add_epi8( + cc0, _mm_srli_epi16(_mm_mullo_epi16(_mm_sub_epi16(cc1, cc0), fracy), 8)); + __m128 r = _mm_cvtepi32_ps(_mm_madd_epi16(cc, fracx)); + return vec4((Float)r * (1.0f / 0xFF00), 0.0f, 0.0f, 1.0f); +#else + ivec2 i(linearQuantize(P, 128, sampler)); + Float r = CONVERT(textureLinearPackedR8(sampler, i, zoffset), Float); + return vec4(r * (1.0f / 255.0f), 0.0f, 0.0f, 1.0f); +#endif +} + +template +vec4 textureLinearRGBA32F(S sampler, vec2 P, int32_t zoffset = 0) { + assert(sampler->format == TextureFormat::RGBA32F); + P.x *= sampler->width; + P.y *= sampler->height; + P -= 0.5f; + vec2 f = floor(P); + vec2 r = P - f; + ivec2 i(f); + ivec2 c = clamp2D(i, sampler); + r.x = if_then_else(i.x >= 0 && i.x < sampler->width - 1, r.x, 0.0f); + I32 offset0 = c.x * 4 + c.y * sampler->stride + zoffset; + I32 offset1 = offset0 + ((i.y >= 0 && i.y < int32_t(sampler->height) - 1) & + I32(sampler->stride)); + + Float c0 = mix(mix(*(Float*)&sampler->buf[offset0.x], + *(Float*)&sampler->buf[offset0.x + 4], r.x), + mix(*(Float*)&sampler->buf[offset1.x], + *(Float*)&sampler->buf[offset1.x + 4], r.x), + r.y); + Float c1 = mix(mix(*(Float*)&sampler->buf[offset0.y], + *(Float*)&sampler->buf[offset0.y + 4], r.x), + mix(*(Float*)&sampler->buf[offset1.y], + *(Float*)&sampler->buf[offset1.y + 4], r.x), + r.y); + Float c2 = mix(mix(*(Float*)&sampler->buf[offset0.z], + *(Float*)&sampler->buf[offset0.z + 4], r.x), + mix(*(Float*)&sampler->buf[offset1.z], + *(Float*)&sampler->buf[offset1.z + 4], r.x), + r.y); + Float c3 = mix(mix(*(Float*)&sampler->buf[offset0.w], + *(Float*)&sampler->buf[offset0.w + 4], r.x), + mix(*(Float*)&sampler->buf[offset1.w], + *(Float*)&sampler->buf[offset1.w + 4], r.x), + r.y); + return pixel_float_to_vec4(c0, c1, c2, c3); +} + +SI vec4 texture(sampler2D sampler, vec2 P) { + if (sampler->filter == TextureFilter::LINEAR) { + if (sampler->format == TextureFormat::RGBA8) { + return textureLinearRGBA8(sampler, P); + } else if (sampler->format == TextureFormat::R8) { + return textureLinearR8(sampler, P); + } else { + assert(sampler->format == TextureFormat::RGBA32F); + return textureLinearRGBA32F(sampler, P); + } + } else { + ivec2 coord(roundzero(P.x, sampler->width), roundzero(P.y, sampler->height)); + return texelFetch(sampler, coord, 0); + } +} + +vec4 texture(sampler2DRect sampler, vec2 P) { + assert(sampler->format == TextureFormat::RGBA8); + if (sampler->filter == TextureFilter::LINEAR) { + return textureLinearRGBA8(sampler, + P * vec2_scalar{1.0f / sampler->width, 1.0f / sampler->height}); + } else { + ivec2 coord(roundzero(P.x, 1.0f), roundzero(P.y, 1.0f)); + return texelFetch(sampler, coord); + } +} + +SI vec4 texture(sampler2DArray sampler, vec3 P) { + if (sampler->filter == TextureFilter::LINEAR) { + // SSE2 can generate slow code for 32-bit multiply, and we never actually sample + // from different layers in one chunk, so do cheaper scalar multiplication instead. + assert(test_all(P.z == P.z.x)); + int32_t zoffset = + clampCoord(roundeven(P.z.x, 1.0f), sampler->depth) * sampler->height_stride; + if (sampler->format == TextureFormat::RGBA8) { + return textureLinearRGBA8(sampler, vec2(P.x, P.y), zoffset); + } else if (sampler->format == TextureFormat::R8) { + return textureLinearR8(sampler, vec2(P.x, P.y), zoffset); + } else { + assert(sampler->format == TextureFormat::RGBA32F); + return textureLinearRGBA32F(sampler, vec2(P.x, P.y), zoffset); + } + } else { + // just do nearest for now + ivec3 coord(roundzero(P.x, sampler->width), roundzero(P.y, sampler->height), + roundeven(P.z, 1.0f)); + return texelFetch(sampler, coord, 0); + } +} + +vec4 texture(sampler2DArray sampler, vec3 P, float bias) { + assert(bias == 0.0f); + return texture(sampler, P); +} + +vec4 textureLod(sampler2DArray sampler, vec3 P, float lod) { + assert(lod == 0.0f); + return texture(sampler, P); +} + +ivec3_scalar textureSize(sampler2DArray sampler, int) { + return ivec3_scalar{int32_t(sampler->width), int32_t(sampler->height), + int32_t(sampler->depth)}; +} + +ivec2_scalar textureSize(sampler2D sampler, int) { + return ivec2_scalar{int32_t(sampler->width), int32_t(sampler->height)}; +} + +ivec2_scalar textureSize(sampler2DRect sampler) { + return ivec2_scalar{int32_t(sampler->width), int32_t(sampler->height)}; +} + +ivec4 ivec2::sel(XYZW c1, XYZW c2, XYZW c3, XYZW c4) { + return ivec4(select(c1), select(c2), select(c3), select(c4)); +} + +vec4 vec2::sel(XYZW c1, XYZW c2, XYZW c3, XYZW c4) { + return vec4(select(c1), select(c2), select(c3), select(c4)); +} + +bool any(bool x) { return x; } + +Bool any(bvec4 x) { return x.x | x.y | x.z | x.w; } + +bool any(bvec4_scalar x) { return x.x | x.y | x.z | x.w; } + +Bool any(bvec2 x) { return x.x | x.y; } + +bool any(bvec2_scalar x) { return x.x | x.y; } + +bool all(bool x) { return x; } + +Bool all(bvec2 x) { return x.x & x.y; } + +bool all(bvec2_scalar x) { return x.x & x.y; } + +Bool all(bvec4 x) { return x.x & x.y & x.z & x.w; } + +bool all(bvec4_scalar x) { return x.x & x.y & x.z & x.w; } + +SI vec4 if_then_else(bvec4 c, vec4 t, vec4 e) { + return vec4(if_then_else(c.x, t.x, e.x), if_then_else(c.y, t.y, e.y), + if_then_else(c.z, t.z, e.z), if_then_else(c.w, t.w, e.w)); +} +SI vec3 if_then_else(bvec3 c, vec3 t, vec3 e) { + return vec3(if_then_else(c.x, t.x, e.x), if_then_else(c.y, t.y, e.y), + if_then_else(c.z, t.z, e.z)); +} + +SI vec2 if_then_else(bvec2 c, vec2 t, vec2 e) { + return vec2(if_then_else(c.x, t.x, e.x), if_then_else(c.y, t.y, e.y)); +} + +template +SI R mix(T x, T y, bvec4 a) { + return if_then_else(a, y, x); +} + +template +SI R mix(T x, T y, bvec3 a) { + return if_then_else(a, y, x); +} + +template +SI R mix(T x, T y, bvec2 a) { + return if_then_else(a, y, x); +} + +template +SI T mix(T x, T y, bvec4_scalar a) { + return T{a.x ? y.x : x.x, a.y ? y.y : x.y, a.z ? y.z : x.z, a.w ? y.w : x.w}; +} + +template +SI T mix(T x, T y, bvec3_scalar a) { + return T{a.x ? y.x : x.x, a.y ? y.y : x.y, a.z ? y.z : x.z}; +} + +template +SI T mix(T x, T y, bvec2_scalar a) { + return T{a.x ? y.x : x.x, a.y ? y.y : x.y}; +} + +float dot(vec3_scalar a, vec3_scalar b) { + return a.x * b.x + a.y * b.y + a.z * b.z; +} + +Float dot(vec3 a, vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; } + +float dot(vec2_scalar a, vec2_scalar b) { return a.x * b.x + a.y * b.y; } + +Float dot(vec2 a, vec2 b) { return a.x * b.x + a.y * b.y; } + +#define sin __glsl_sin + +float sin(float x) { return sinf(x); } + +Float sin(Float v) { return {sinf(v.x), sinf(v.y), sinf(v.z), sinf(v.w)}; } + +#define cos __glsl_cos + +float cos(float x) { return cosf(x); } + +Float cos(Float v) { return {cosf(v.x), cosf(v.y), cosf(v.z), cosf(v.w)}; } + +#define tan __glsl_tan + +float tan(float x) { return tanf(x); } + +Float tan(Float v) { return {tanf(v.x), tanf(v.y), tanf(v.z), tanf(v.w)}; } + +#define atan __glsl_atan + +float atan(float x) { return atanf(x); } + +Float atan(Float v) { return {atanf(v.x), atanf(v.y), atanf(v.z), atanf(v.w)}; } + +float atan(float a, float b) { return atan2f(a, b); } + +Float atan(Float a, Float b) { + return {atan2f(a.x, b.x), atan2f(a.y, b.y), atan2f(a.z, b.z), atan2f(a.w, b.w)}; +} + +bvec4 notEqual(ivec4 a, ivec4 b) { + return bvec4(a.x != b.x, a.y != b.y, a.z != b.z, a.w != b.w); +} + +bvec4_scalar notEqual(ivec4_scalar a, ivec4_scalar b) { + return bvec4_scalar{a.x != b.x, a.y != b.y, a.z != b.z, a.w != b.w}; +} + +mat3 transpose(mat3 m) { + return mat3(vec3(m[0].x, m[1].x, m[2].x), vec3(m[0].y, m[1].y, m[2].y), + vec3(m[0].z, m[1].z, m[2].z)); +} + +mat3_scalar transpose(mat3_scalar m) { + return mat3_scalar{vec3_scalar(m[0].x, m[1].x, m[2].x), + vec3_scalar(m[0].y, m[1].y, m[2].y), + vec3_scalar(m[0].z, m[1].z, m[2].z)}; +} + +vec2 abs(vec2 v) { return vec2(abs(v.x), abs(v.y)); } + +vec2_scalar abs(vec2_scalar v) { return vec2_scalar{fabsf(v.x), fabsf(v.y)}; } + +Float mod(Float a, Float b) { return a - b * floor(a / b); } + +vec2 mod(vec2 a, vec2 b) { return vec2(mod(a.x, b.x), mod(a.y, b.y)); } + +vec3 abs(vec3 v) { return vec3(abs(v.x), abs(v.y), abs(v.z)); } + +mat2 inverse(mat2 v) { + Float det = v[0].x * v[1].y - v[0].y * v[1].x; + return mat2(vec2(v[1].y, -v[0].y), vec2(-v[1].x, v[0].x)) * (1. / det); +} + +mat2_scalar inverse(mat2_scalar v) { + float det = v[0].x * v[1].y - v[0].y * v[1].x; + return mat2_scalar{{v[1].y, -v[0].y}, {-v[1].x, v[0].x}} * (1. / det); +} + +int32_t get_nth(I32 a, int n) { return a[n]; } + +float get_nth(Float a, int n) { return a[n]; } + +float get_nth(float a, int) { return a; } + +ivec2_scalar get_nth(ivec2 a, int n) { return ivec2_scalar{a.x[n], a.y[n]}; } + +vec2_scalar get_nth(vec2 a, int n) { return vec2_scalar{a.x[n], a.y[n]}; } + +vec3_scalar get_nth(vec3 a, int n) { + return vec3_scalar{a.x[n], a.y[n], a.z[n]}; +} + +vec4_scalar get_nth(vec4 a, int n) { + return vec4_scalar{a.x[n], a.y[n], a.z[n], a.w[n]}; +} + +ivec4_scalar get_nth(ivec4 a, int n) { + return ivec4_scalar{a.x[n], a.y[n], a.z[n], a.w[n]}; +} + +mat3_scalar get_nth(mat3 a, int n) { + return make_mat3(get_nth(a[0], n), get_nth(a[1], n), get_nth(a[2], n)); +} + +void put_nth(Float& dst, int n, float src) { dst[n] = src; } + +void put_nth(I32& dst, int n, int32_t src) { dst[n] = src; } + +void put_nth(ivec2& dst, int n, ivec2_scalar src) { + dst.x[n] = src.x; + dst.y[n] = src.y; +} + +void put_nth(vec2& dst, int n, vec2_scalar src) { + dst.x[n] = src.x; + dst.y[n] = src.y; +} + +void put_nth(vec3& dst, int n, vec3_scalar src) { + dst.x[n] = src.x; + dst.y[n] = src.y; + dst.z[n] = src.z; +} + +void put_nth(ivec4& dst, int n, ivec4_scalar src) { + dst.x[n] = src.x; + dst.y[n] = src.y; + dst.z[n] = src.z; + dst.w[n] = src.w; +} + +void put_nth(vec4& dst, int n, vec4_scalar src) { + dst.x[n] = src.x; + dst.y[n] = src.y; + dst.z[n] = src.z; + dst.w[n] = src.w; +} + +// Use an ElementType type constructor +// so that we can implement element_type for +// Int and Float +template +struct ElementType { + typedef typename V::element_type ty; +}; + +template <> +struct ElementType { + typedef float ty; +}; + +template <> +struct ElementType { + typedef float ty; +}; + +template <> +struct ElementType { + typedef float ty; +}; + +template <> +struct ElementType { + typedef int32_t ty; +}; + +void put_nth_component(ivec2_scalar& dst, int n, int32_t src) { + switch (n) { + case 0: + dst.x = src; + break; + case 1: + dst.y = src; + break; + } +} + +void put_nth_component(ivec4_scalar& dst, int n, int32_t src) { + switch (n) { + case 0: + dst.x = src; + break; + case 1: + dst.y = src; + break; + case 2: + dst.z = src; + break; + case 3: + dst.w = src; + break; + } +} + +void put_nth_component(int& dst, int n, int src) { + switch (n) { + case 0: + dst = src; + break; + } +} + +void put_nth_component(float& dst, int n, float src) { + switch (n) { + case 0: + dst = src; + break; + } +} + +void put_nth_component(vec2_scalar& dst, int n, float src) { + switch (n) { + case 0: + dst.x = src; + break; + case 1: + dst.y = src; + break; + } +} + +void put_nth_component(vec3_scalar& dst, int n, float src) { + switch (n) { + case 0: + dst.x = src; + break; + case 1: + dst.y = src; + break; + case 2: + dst.z = src; + break; + } +} + +void put_nth_component(vec4_scalar& dst, int n, float src) { + switch (n) { + case 0: + dst.x = src; + break; + case 1: + dst.y = src; + break; + case 2: + dst.z = src; + break; + case 3: + dst.w = src; + break; + } +} + +Float init_interp(float init0, float step) { + float init1 = init0 + step; + float init2 = init1 + step; + float init3 = init2 + step; + return {init0, init1, init2, init3}; +} + +vec2 init_interp(vec2_scalar init, vec2_scalar step) { + return vec2(init_interp(init.x, step.x), init_interp(init.y, step.y)); +} + +vec3 init_interp(vec3_scalar init, vec3_scalar step) { + return vec3(init_interp(init.x, step.x), init_interp(init.y, step.y), + init_interp(init.z, step.z)); +} + +vec4 init_interp(vec4_scalar init, vec4_scalar step) { + return vec4(init_interp(init.x, step.x), init_interp(init.y, step.y), + init_interp(init.z, step.z), init_interp(init.w, step.w)); +} + +template +struct Array { + T elements[N]; + T& operator[](size_t i) { return elements[i]; } + const T& operator[](size_t i) const { return elements[i]; } + template + void convert(const Array& s) { + for (size_t i = 0; i < N; ++i) elements[i] = T(s[i]); + } +}; + +template +Array if_then_else(I32 c, Array t, + Array e) { + Array r; + for (size_t i = 0; i < SIZE; i++) { + r[i] = if_then_else(c, t[i], e[i]); + } + return r; +} + +} // namespace glsl diff --git a/third_party/webrender/swgl/src/lib.rs b/third_party/webrender/swgl/src/lib.rs new file mode 100644 index 00000000000..e8fc030e0c9 --- /dev/null +++ b/third_party/webrender/swgl/src/lib.rs @@ -0,0 +1,12 @@ +/* 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/. */ + +#![crate_name = "swgl"] +#![crate_type = "lib"] + +extern crate gleam; + +mod swgl_fns; + +pub use crate::swgl_fns::*; diff --git a/third_party/webrender/swgl/src/program.h b/third_party/webrender/swgl/src/program.h new file mode 100644 index 00000000000..80e5a5b68f7 --- /dev/null +++ b/third_party/webrender/swgl/src/program.h @@ -0,0 +1,164 @@ +/* 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/. */ + +struct VertexAttrib; + +namespace glsl { + +// Type holding group of scalars interpolated across rasterized rows and spans, +// shuttling values between vertex shaders and fragment shaders. +// GCC requires power-of-two vector sizes, so must use glsl type as workaround +// to operate in Float-sized chunks. +typedef vec3 Interpolants; + +struct VertexShaderImpl; +struct FragmentShaderImpl; + +struct ProgramImpl { + virtual ~ProgramImpl() {} + virtual int get_uniform(const char* name) const = 0; + virtual void bind_attrib(const char* name, int index) = 0; + virtual int get_attrib(const char* name) const = 0; + virtual size_t interpolants_size() const = 0; + virtual VertexShaderImpl* get_vertex_shader() = 0; + virtual FragmentShaderImpl* get_fragment_shader() = 0; +}; + +typedef ProgramImpl* (*ProgramLoader)(); + +struct VertexShaderImpl { + typedef void (*SetUniform1iFunc)(VertexShaderImpl*, int index, int value); + typedef void (*SetUniform4fvFunc)(VertexShaderImpl*, int index, + const float* value); + typedef void (*SetUniformMatrix4fvFunc)(VertexShaderImpl*, int index, + const float* value); + typedef void (*InitBatchFunc)(VertexShaderImpl*); + typedef void (*LoadAttribsFunc)(VertexShaderImpl*, VertexAttrib* attribs, + uint32_t start, int instance, int count); + typedef void (*RunPrimitiveFunc)(VertexShaderImpl*, char* interps, + size_t interp_stride); + + SetUniform1iFunc set_uniform_1i_func = nullptr; + SetUniform4fvFunc set_uniform_4fv_func = nullptr; + SetUniformMatrix4fvFunc set_uniform_matrix4fv_func = nullptr; + InitBatchFunc init_batch_func = nullptr; + LoadAttribsFunc load_attribs_func = nullptr; + RunPrimitiveFunc run_primitive_func = nullptr; + + vec4 gl_Position; + + void set_uniform_1i(int index, int value) { + (*set_uniform_1i_func)(this, index, value); + } + + void set_uniform_4fv(int index, const float* value) { + (*set_uniform_4fv_func)(this, index, value); + } + + void set_uniform_matrix4fv(int index, const float* value) { + (*set_uniform_matrix4fv_func)(this, index, value); + } + + void init_batch() { (*init_batch_func)(this); } + + ALWAYS_INLINE void load_attribs(VertexAttrib* attribs, uint32_t start, + int instance, int count) { + (*load_attribs_func)(this, attribs, start, instance, count); + } + + ALWAYS_INLINE void run_primitive(char* interps, size_t interp_stride) { + (*run_primitive_func)(this, interps, interp_stride); + } +}; + +struct FragmentShaderImpl { + typedef void (*InitSpanFunc)(FragmentShaderImpl*, const void* interps, + const void* step, float step_width); + typedef void (*RunFunc)(FragmentShaderImpl*); + typedef void (*SkipFunc)(FragmentShaderImpl*, int chunks); + typedef void (*InitSpanWFunc)(FragmentShaderImpl*, const void* interps, + const void* step, float step_width); + typedef void (*RunWFunc)(FragmentShaderImpl*); + typedef void (*SkipWFunc)(FragmentShaderImpl*, int chunks); + typedef void (*DrawSpanRGBA8Func)(FragmentShaderImpl*, uint32_t* buf, + int len); + typedef void (*DrawSpanR8Func)(FragmentShaderImpl*, uint8_t* buf, int len); + + InitSpanFunc init_span_func = nullptr; + RunFunc run_func = nullptr; + SkipFunc skip_func = nullptr; + InitSpanWFunc init_span_w_func = nullptr; + RunWFunc run_w_func = nullptr; + SkipWFunc skip_w_func = nullptr; + DrawSpanRGBA8Func draw_span_RGBA8_func = nullptr; + DrawSpanR8Func draw_span_R8_func = nullptr; + + enum FLAGS { + DISCARD = 1 << 0, + PERSPECTIVE = 1 << 1, + }; + int flags = 0; + void enable_discard() { flags |= DISCARD; } + void enable_perspective() { flags |= PERSPECTIVE; } + ALWAYS_INLINE bool use_discard() const { return (flags & DISCARD) != 0; } + ALWAYS_INLINE bool use_perspective() const { + return (flags & PERSPECTIVE) != 0; + } + + vec4 gl_FragCoord; + vec2_scalar stepZW; + Bool isPixelDiscarded = false; + vec4 gl_FragColor; + vec4 gl_SecondaryFragColor; + + ALWAYS_INLINE void step_fragcoord() { gl_FragCoord.x += 4; } + + ALWAYS_INLINE void step_fragcoord(int chunks) { + gl_FragCoord.x += 4 * chunks; + } + + ALWAYS_INLINE void step_perspective() { + gl_FragCoord.z += stepZW.x; + gl_FragCoord.w += stepZW.y; + } + + ALWAYS_INLINE void step_perspective(int chunks) { + gl_FragCoord.z += stepZW.x * chunks; + gl_FragCoord.w += stepZW.y * chunks; + } + + template + ALWAYS_INLINE void init_span(const void* interps, const void* step, + float step_width) { + (*(W ? init_span_w_func : init_span_func))(this, interps, step, step_width); + } + + template + ALWAYS_INLINE void run() { + (*(W ? run_w_func : run_func))(this); + } + + template + ALWAYS_INLINE void skip(int chunks = 1) { + (*(W ? skip_w_func : skip_func))(this, chunks); + } + + ALWAYS_INLINE void draw_span(uint32_t* buf, int len) { + (*draw_span_RGBA8_func)(this, buf, len); + } + + ALWAYS_INLINE bool has_draw_span(uint32_t*) { + return draw_span_RGBA8_func != nullptr; + } + + ALWAYS_INLINE void draw_span(uint8_t* buf, int len) { + (*draw_span_R8_func)(this, buf, len); + } + + ALWAYS_INLINE bool has_draw_span(uint8_t*) { + return draw_span_R8_func != nullptr; + } +}; + +} // namespace glsl diff --git a/third_party/webrender/swgl/src/swgl_fns.rs b/third_party/webrender/swgl/src/swgl_fns.rs new file mode 100644 index 00000000000..0cb60c6d4c8 --- /dev/null +++ b/third_party/webrender/swgl/src/swgl_fns.rs @@ -0,0 +1,2253 @@ +/* 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/. */ +#![allow(unused_variables)] + +use gleam::gl::*; +use std::ffi::{CStr, CString}; +use std::os::raw::{c_char, c_int, c_void}; +use std::ptr; +use std::str; + +#[allow(unused)] +macro_rules! debug { + ($($x:tt)*) => {}; +} + +extern "C" {} + +extern "C" { + fn ActiveTexture(texture: GLenum); + fn BindTexture(target: GLenum, texture: GLuint); + fn BindBuffer(target: GLenum, buffer: GLuint); + fn BindVertexArray(vao: GLuint); + fn BindFramebuffer(target: GLenum, fb: GLuint); + fn BindRenderbuffer(target: GLenum, rb: GLuint); + fn BlendFunc(srgb: GLenum, drgb: GLenum, sa: GLenum, da: GLenum); + fn BlendColor(r: GLfloat, g: GLfloat, b: GLfloat, a: GLfloat); + fn BlendEquation(mode: GLenum); + fn Enable(cap: GLenum); + fn Disable(cap: GLenum); + fn GenQueries(n: GLsizei, result: *mut GLuint); + fn BeginQuery(target: GLenum, id: GLuint); + fn EndQuery(target: GLenum); + fn GetQueryObjectui64v(id: GLuint, pname: GLenum, params: *mut GLuint64); + fn GenBuffers(n: i32, result: *mut GLuint); + fn GenTextures(n: i32, result: *mut GLuint); + fn GenFramebuffers(n: i32, result: *mut GLuint); + fn GenRenderbuffers(n: i32, result: *mut GLuint); + fn BufferData(target: GLenum, size: GLsizeiptr, data: *const GLvoid, usage: GLenum); + fn BufferSubData(target: GLenum, offset: GLintptr, size: GLsizeiptr, data: *const GLvoid); + fn MapBuffer(target: GLenum, access: GLbitfield) -> *mut c_void; + fn MapBufferRange( + target: GLenum, + offset: GLintptr, + length: GLsizeiptr, + access: GLbitfield, + ) -> *mut c_void; + fn UnmapBuffer(target: GLenum) -> GLboolean; + fn TexStorage2D( + target: GLenum, + levels: GLint, + internal_format: GLenum, + width: GLsizei, + height: GLsizei, + ); + fn FramebufferTexture2D( + target: GLenum, + attachment: GLenum, + textarget: GLenum, + texture: GLuint, + level: GLint, + ); + fn CheckFramebufferStatus(target: GLenum) -> GLenum; + fn InvalidateFramebuffer( + target: GLenum, + num_attachments: GLsizei, + attachments: *const GLenum, + ); + fn TexStorage3D( + target: GLenum, + levels: GLint, + internal_format: GLenum, + width: GLsizei, + height: GLsizei, + depth: GLsizei, + ); + fn TexImage2D( + target: GLenum, + level: GLint, + internal_format: GLint, + width: GLsizei, + height: GLsizei, + border: GLint, + format: GLenum, + ty: GLenum, + data: *const c_void, + ); + fn TexImage3D( + target: GLenum, + level: GLint, + internal_format: GLint, + width: GLsizei, + height: GLsizei, + depth: GLsizei, + border: GLint, + format: GLenum, + ty: GLenum, + data: *const c_void, + ); + fn TexSubImage2D( + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + width: GLsizei, + height: GLsizei, + format: GLenum, + ty: GLenum, + data: *const c_void, + ); + fn TexSubImage3D( + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + zoffset: GLint, + width: GLsizei, + height: GLsizei, + depth: GLsizei, + format: GLenum, + ty: GLenum, + data: *const c_void, + ); + fn GenerateMipmap(target: GLenum); + fn GetUniformLocation(program: GLuint, name: *const GLchar) -> GLint; + fn BindAttribLocation(program: GLuint, index: GLuint, name: *const GLchar); + fn GetAttribLocation(program: GLuint, name: *const GLchar) -> GLint; + fn GenVertexArrays(n: i32, result: *mut GLuint); + fn VertexAttribPointer( + index: GLuint, + size: GLint, + type_: GLenum, + normalized: GLboolean, + stride: GLsizei, + offset: *const GLvoid, + ); + fn VertexAttribIPointer( + index: GLuint, + size: GLint, + type_: GLenum, + stride: GLsizei, + offset: *const GLvoid, + ); + fn CreateShader(shader_type: GLenum) -> GLuint; + fn AttachShader(program: GLuint, shader: GLuint); + fn CreateProgram() -> GLuint; + fn Uniform1i(location: GLint, v0: GLint); + fn Uniform4fv(location: GLint, count: GLsizei, value: *const GLfloat); + fn UniformMatrix4fv( + location: GLint, + count: GLsizei, + transpose: GLboolean, + value: *const GLfloat, + ); + + fn DrawElementsInstanced( + mode: GLenum, + count: GLsizei, + type_: GLenum, + indices: *const c_void, + instancecount: GLsizei, + ); + fn EnableVertexAttribArray(index: GLuint); + fn VertexAttribDivisor(index: GLuint, divisor: GLuint); + fn LinkProgram(program: GLuint); + fn UseProgram(program: GLuint); + fn SetViewport(x: GLint, y: GLint, width: GLsizei, height: GLsizei); + fn FramebufferTextureLayer( + target: GLenum, + attachment: GLenum, + texture: GLuint, + level: GLint, + layer: GLint, + ); + fn FramebufferRenderbuffer( + target: GLenum, + attachment: GLenum, + renderbuffertarget: GLenum, + renderbuffer: GLuint, + ); + fn RenderbufferStorage(target: GLenum, internalformat: GLenum, width: GLsizei, height: GLsizei); + fn DepthMask(flag: GLboolean); + fn DepthFunc(func: GLenum); + fn SetScissor(x: GLint, y: GLint, width: GLsizei, height: GLsizei); + fn ClearColor(r: GLfloat, g: GLfloat, b: GLfloat, a: GLfloat); + fn ClearDepth(depth: GLdouble); + fn Clear(mask: GLbitfield); + fn PixelStorei(name: GLenum, param: GLint); + fn ReadPixels( + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + format: GLenum, + ty: GLenum, + data: *mut c_void, + ); + fn Finish(); + fn ShaderSourceByName(shader: GLuint, name: *const GLchar); + fn TexParameteri(target: GLenum, pname: GLenum, param: GLint); + fn CopyImageSubData( + src_name: GLuint, + src_target: GLenum, + src_level: GLint, + src_x: GLint, + src_y: GLint, + src_z: GLint, + dst_name: GLuint, + dst_target: GLenum, + dst_level: GLint, + dst_x: GLint, + dst_y: GLint, + dst_z: GLint, + src_width: GLsizei, + src_height: GLsizei, + src_depth: GLsizei, + ); + fn CopyTexSubImage2D( + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + ); + fn CopyTexSubImage3D( + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + zoffset: GLint, + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + ); + fn BlitFramebuffer( + src_x0: GLint, + src_y0: GLint, + src_x1: GLint, + src_y1: GLint, + dst_x0: GLint, + dst_y0: GLint, + dst_x1: GLint, + dst_y1: GLint, + mask: GLbitfield, + filter: GLenum, + ); + fn GetIntegerv(pname: GLenum, params: *mut GLint); + fn GetBooleanv(pname: GLenum, params: *mut GLboolean); + fn GetString(name: GLenum) -> *const c_char; + fn GetStringi(name: GLenum, index: GLuint) -> *const c_char; + fn GetError() -> GLenum; + fn InitDefaultFramebuffer(width: i32, height: i32); + fn GetColorBuffer( + fbo: GLuint, + flush: GLboolean, + width: *mut i32, + height: *mut i32, + ) -> *mut c_void; + fn SetTextureBuffer( + tex: GLuint, + internal_format: GLenum, + width: GLsizei, + height: GLsizei, + buf: *mut c_void, + min_width: GLsizei, + min_height: GLsizei, + ); + fn DeleteTexture(n: GLuint); + fn DeleteRenderbuffer(n: GLuint); + fn DeleteFramebuffer(n: GLuint); + fn DeleteBuffer(n: GLuint); + fn DeleteVertexArray(n: GLuint); + fn DeleteQuery(n: GLuint); + fn DeleteShader(shader: GLuint); + fn DeleteProgram(program: GLuint); + fn Composite( + src_id: GLuint, + src_x: GLint, + src_y: GLint, + src_width: GLsizei, + src_height: GLsizei, + dst_x: GLint, + dst_y: GLint, + opaque: GLboolean, + flip: GLboolean, + ); + fn CreateContext() -> *mut c_void; + fn DestroyContext(ctx: *mut c_void); + fn MakeCurrent(ctx: *mut c_void); +} + +#[derive(Clone)] +pub struct Context(*mut c_void); + +impl Context { + pub fn create() -> Self { + Context(unsafe { CreateContext() }) + } + + pub fn destroy(&self) { + unsafe { + DestroyContext(self.0); + } + } + + pub fn make_current(&self) { + unsafe { + MakeCurrent(self.0); + } + } + + pub fn init_default_framebuffer(&self, width: i32, height: i32) { + unsafe { + InitDefaultFramebuffer(width, height); + } + } + + pub fn get_color_buffer(&self, fbo: GLuint, flush: bool) -> (*mut c_void, i32, i32) { + unsafe { + let mut width: i32 = 0; + let mut height: i32 = 0; + let data_ptr = GetColorBuffer(fbo, flush as GLboolean, &mut width, &mut height); + (data_ptr, width, height) + } + } + + pub fn set_texture_buffer( + &self, + tex: GLuint, + internal_format: GLenum, + width: GLsizei, + height: GLsizei, + buf: *mut c_void, + min_width: GLsizei, + min_height: GLsizei, + ) { + unsafe { + SetTextureBuffer( + tex, + internal_format, + width, + height, + buf, + min_width, + min_height, + ); + } + } + + pub fn composite( + &self, + src_id: GLuint, + src_x: GLint, + src_y: GLint, + src_width: GLsizei, + src_height: GLint, + dst_x: GLint, + dst_y: GLint, + opaque: bool, + flip: bool, + ) { + unsafe { + Composite( + src_id, + src_x, + src_y, + src_width, + src_height, + dst_x, + dst_y, + opaque as GLboolean, + flip as GLboolean, + ); + } + } +} + +impl From<*mut c_void> for Context { + fn from(ptr: *mut c_void) -> Self { + Context(ptr) + } +} + +impl From for *mut c_void { + fn from(ctx: Context) -> Self { + ctx.0 + } +} + +fn calculate_length(width: GLsizei, height: GLsizei, format: GLenum, pixel_type: GLenum) -> usize { + let colors = match format { + RED => 1, + RGB => 3, + BGR => 3, + + RGBA => 4, + BGRA => 4, + + ALPHA => 1, + R16 => 1, + LUMINANCE => 1, + DEPTH_COMPONENT => 1, + _ => panic!("unsupported format for read_pixels: {:?}", format), + }; + let depth = match pixel_type { + UNSIGNED_BYTE => 1, + UNSIGNED_SHORT => 2, + SHORT => 2, + FLOAT => 4, + _ => panic!("unsupported pixel_type for read_pixels: {:?}", pixel_type), + }; + + return (width * height * colors * depth) as usize; +} + +impl Gl for Context { + fn get_type(&self) -> GlType { + GlType::Gl + } + + fn buffer_data_untyped( + &self, + target: GLenum, + size: GLsizeiptr, + data: *const GLvoid, + usage: GLenum, + ) { + debug!( + "buffer_data_untyped {} {} {:?} {}", + target, size, data, usage + ); + //panic!(); + unsafe { + BufferData(target, size, data, usage); + } + } + + fn buffer_sub_data_untyped( + &self, + target: GLenum, + offset: isize, + size: GLsizeiptr, + data: *const GLvoid, + ) { + debug!( + "buffer_sub_data_untyped {} {} {} {:?}", + target, offset, size, data + ); + //panic!(); + unsafe { + BufferSubData(target, offset, size, data); + } + } + + fn map_buffer(&self, target: GLenum, access: GLbitfield) -> *mut c_void { + unsafe { MapBuffer(target, access) } + } + + fn map_buffer_range( + &self, + target: GLenum, + offset: GLintptr, + length: GLsizeiptr, + access: GLbitfield, + ) -> *mut c_void { + unsafe { MapBufferRange(target, offset, length, access) } + } + + fn unmap_buffer(&self, target: GLenum) -> GLboolean { + unsafe { UnmapBuffer(target) } + } + + fn shader_source(&self, shader: GLuint, strings: &[&[u8]]) { + //panic!(); + debug!("shader_source {}", shader); + //for s in strings { + // debug!("{}", str::from_utf8(s).unwrap()); + //} + //panic!(); + for s in strings { + let u = str::from_utf8(s).unwrap(); + const PREFIX: &'static str = "// shader: "; + if let Some(start) = u.find(PREFIX) { + if let Some(end) = u[start ..].find('\n') { + let name = u[start + PREFIX.len() .. start + end].trim(); + debug!("shader name: {}", name); + unsafe { + let c_string = CString::new(name).unwrap(); + ShaderSourceByName(shader, c_string.as_ptr()); + return; + } + } + } + } + panic!("unknown shader"); + } + + fn tex_buffer(&self, target: GLenum, internal_format: GLenum, buffer: GLuint) { + panic!(); + } + + fn read_buffer(&self, mode: GLenum) { + panic!(); + } + + fn read_pixels_into_buffer( + &self, + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + format: GLenum, + pixel_type: GLenum, + dst_buffer: &mut [u8], + ) { + // Assumes that the user properly allocated the size for dst_buffer. + assert!(calculate_length(width, height, format, pixel_type) == dst_buffer.len()); + + unsafe { + ReadPixels( + x, + y, + width, + height, + format, + pixel_type, + dst_buffer.as_mut_ptr() as *mut c_void, + ); + } + } + + fn read_pixels( + &self, + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + format: GLenum, + pixel_type: GLenum, + ) -> Vec { + let len = calculate_length(width, height, format, pixel_type); + let mut pixels: Vec = Vec::new(); + pixels.reserve(len); + unsafe { + pixels.set_len(len); + } + + self.read_pixels_into_buffer( + x, + y, + width, + height, + format, + pixel_type, + pixels.as_mut_slice(), + ); + + pixels + } + + unsafe fn read_pixels_into_pbo( + &self, + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + format: GLenum, + pixel_type: GLenum, + ) { + ReadPixels(x, y, width, height, format, pixel_type, ptr::null_mut()); + } + + fn sample_coverage(&self, value: GLclampf, invert: bool) { + panic!(); + } + + fn polygon_offset(&self, factor: GLfloat, units: GLfloat) { + panic!(); + } + + fn pixel_store_i(&self, name: GLenum, param: GLint) { + //panic!(); + debug!("pixel_store_i {:x} {}", name, param); + unsafe { + PixelStorei(name, param); + } + } + + fn gen_buffers(&self, n: GLsizei) -> Vec { + //panic!(); + let mut result = vec![0 as GLuint; n as usize]; + unsafe { + GenBuffers(n, result.as_mut_ptr()); + } + result + } + + fn gen_renderbuffers(&self, n: GLsizei) -> Vec { + debug!("gen_renderbuffers {}", n); + //panic!(); + let mut result = vec![0 as GLuint; n as usize]; + unsafe { + GenRenderbuffers(n, result.as_mut_ptr()); + } + result + } + + fn gen_framebuffers(&self, n: GLsizei) -> Vec { + //panic!(); + debug!("gen_framebuffers {}", n); + let mut result = vec![0 as GLuint; n as usize]; + unsafe { + GenFramebuffers(n, result.as_mut_ptr()); + } + result + } + + fn gen_textures(&self, n: GLsizei) -> Vec { + //panic!(); + let mut result = vec![0 as GLuint; n as usize]; + unsafe { + GenTextures(n, result.as_mut_ptr()); + } + result + } + + fn gen_vertex_arrays(&self, n: GLsizei) -> Vec { + //panic!(); + let mut result = vec![0 as GLuint; n as usize]; + unsafe { + GenVertexArrays(n, result.as_mut_ptr()); + } + result + } + + fn gen_vertex_arrays_apple(&self, n: GLsizei) -> Vec { + self.gen_vertex_arrays(n) + } + + fn gen_queries(&self, n: GLsizei) -> Vec { + let mut result = vec![0 as GLuint; n as usize]; + unsafe { + GenQueries(n, result.as_mut_ptr()); + } + result + } + + fn begin_query(&self, target: GLenum, id: GLuint) { + unsafe { + BeginQuery(target, id); + } + } + + fn end_query(&self, target: GLenum) { + unsafe { + EndQuery(target); + } + } + + fn query_counter(&self, id: GLuint, target: GLenum) { + panic!(); + } + + fn get_query_object_iv(&self, id: GLuint, pname: GLenum) -> i32 { + panic!(); + //0 + } + + fn get_query_object_uiv(&self, id: GLuint, pname: GLenum) -> u32 { + panic!(); + //0 + } + + fn get_query_object_i64v(&self, id: GLuint, pname: GLenum) -> i64 { + panic!(); + //0 + } + + fn get_query_object_ui64v(&self, id: GLuint, pname: GLenum) -> u64 { + let mut result = 0; + unsafe { + GetQueryObjectui64v(id, pname, &mut result); + } + result + } + + fn delete_queries(&self, queries: &[GLuint]) { + unsafe { + for q in queries { + DeleteQuery(*q); + } + } + } + + fn delete_vertex_arrays(&self, vertex_arrays: &[GLuint]) { + unsafe { + for v in vertex_arrays { + DeleteVertexArray(*v); + } + } + } + + fn delete_vertex_arrays_apple(&self, vertex_arrays: &[GLuint]) { + self.delete_vertex_arrays(vertex_arrays) + } + + fn delete_buffers(&self, buffers: &[GLuint]) { + unsafe { + for b in buffers { + DeleteBuffer(*b); + } + } + } + + fn delete_renderbuffers(&self, renderbuffers: &[GLuint]) { + unsafe { + for r in renderbuffers { + DeleteRenderbuffer(*r); + } + } + } + + fn delete_framebuffers(&self, framebuffers: &[GLuint]) { + unsafe { + for f in framebuffers { + DeleteFramebuffer(*f); + } + } + } + + fn delete_textures(&self, textures: &[GLuint]) { + unsafe { + for t in textures { + DeleteTexture(*t); + } + } + } + + fn framebuffer_renderbuffer( + &self, + target: GLenum, + attachment: GLenum, + renderbuffertarget: GLenum, + renderbuffer: GLuint, + ) { + debug!( + "framebufer_renderbuffer {} {} {} {}", + target, attachment, renderbuffertarget, renderbuffer + ); + //panic!(); + unsafe { + FramebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer); + } + } + + fn renderbuffer_storage( + &self, + target: GLenum, + internalformat: GLenum, + width: GLsizei, + height: GLsizei, + ) { + debug!( + "renderbuffer_storage {} {} {} {}", + target, internalformat, width, height + ); + //panic!(); + unsafe { + RenderbufferStorage(target, internalformat, width, height); + } + } + + fn depth_func(&self, func: GLenum) { + debug!("depth_func {}", func); + //panic!(); + unsafe { + DepthFunc(func); + } + } + + fn active_texture(&self, texture: GLenum) { + //panic!(); + unsafe { + ActiveTexture(texture); + } + } + + fn attach_shader(&self, program: GLuint, shader: GLuint) { + debug!("attach shader {} {}", program, shader); + //panic!(); + unsafe { + AttachShader(program, shader); + } + } + + fn bind_attrib_location(&self, program: GLuint, index: GLuint, name: &str) { + debug!("bind_attrib_location {} {} {}", program, index, name); + //panic!(); + let c_string = CString::new(name).unwrap(); + unsafe { BindAttribLocation(program, index, c_string.as_ptr()) } + } + + // https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glGetUniform.xml + unsafe fn get_uniform_iv(&self, program: GLuint, location: GLint, result: &mut [GLint]) { + panic!(); + //assert!(!result.is_empty()); + } + + // https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glGetUniform.xml + unsafe fn get_uniform_fv(&self, program: GLuint, location: GLint, result: &mut [GLfloat]) { + panic!(); + //assert!(!result.is_empty()); + } + + fn get_uniform_block_index(&self, program: GLuint, name: &str) -> GLuint { + panic!(); + //0 + } + + fn get_uniform_indices(&self, program: GLuint, names: &[&str]) -> Vec { + panic!(); + //Vec::new() + } + + fn bind_buffer_base(&self, target: GLenum, index: GLuint, buffer: GLuint) { + panic!(); + } + + fn bind_buffer_range( + &self, + target: GLenum, + index: GLuint, + buffer: GLuint, + offset: GLintptr, + size: GLsizeiptr, + ) { + panic!(); + } + + fn uniform_block_binding( + &self, + program: GLuint, + uniform_block_index: GLuint, + uniform_block_binding: GLuint, + ) { + panic!(); + } + + fn bind_buffer(&self, target: GLenum, buffer: GLuint) { + //panic!(); + unsafe { + BindBuffer(target, buffer); + } + } + + fn bind_vertex_array(&self, vao: GLuint) { + //panic!(); + unsafe { + BindVertexArray(vao); + } + } + + fn bind_vertex_array_apple(&self, vao: GLuint) { + self.bind_vertex_array(vao) + } + + fn bind_renderbuffer(&self, target: GLenum, renderbuffer: GLuint) { + debug!("bind_renderbuffer {} {}", target, renderbuffer); + //panic!(); + unsafe { + BindRenderbuffer(target, renderbuffer); + } + } + + fn bind_framebuffer(&self, target: GLenum, framebuffer: GLuint) { + debug!("bind_framebuffer {} {}", target, framebuffer); + //panic!(); + unsafe { + BindFramebuffer(target, framebuffer); + } + } + + fn bind_texture(&self, target: GLenum, texture: GLuint) { + //panic!(); + unsafe { + BindTexture(target, texture); + } + } + + fn draw_buffers(&self, bufs: &[GLenum]) { + panic!(); + //unsafe {} + } + + // FIXME: Does not verify buffer size -- unsafe! + fn tex_image_2d( + &self, + target: GLenum, + level: GLint, + internal_format: GLint, + width: GLsizei, + height: GLsizei, + border: GLint, + format: GLenum, + ty: GLenum, + opt_data: Option<&[u8]>, + ) { + unsafe { + let pdata = match opt_data { + Some(data) => data.as_ptr() as *const GLvoid, + None => ptr::null(), + }; + TexImage2D( + target, + level, + internal_format, + width, + height, + border, + format, + ty, + pdata, + ); + } + } + + fn compressed_tex_image_2d( + &self, + target: GLenum, + level: GLint, + internal_format: GLenum, + width: GLsizei, + height: GLsizei, + border: GLint, + data: &[u8], + ) { + panic!(); + } + + fn compressed_tex_sub_image_2d( + &self, + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + width: GLsizei, + height: GLsizei, + format: GLenum, + data: &[u8], + ) { + panic!(); + } + + // FIXME: Does not verify buffer size -- unsafe! + fn tex_image_3d( + &self, + target: GLenum, + level: GLint, + internal_format: GLint, + width: GLsizei, + height: GLsizei, + depth: GLsizei, + border: GLint, + format: GLenum, + ty: GLenum, + opt_data: Option<&[u8]>, + ) { + unsafe { + let pdata = match opt_data { + Some(data) => data.as_ptr() as *const GLvoid, + None => ptr::null(), + }; + TexImage3D( + target, + level, + internal_format, + width, + height, + depth, + border, + format, + ty, + pdata, + ); + } + } + + fn copy_tex_image_2d( + &self, + target: GLenum, + level: GLint, + internal_format: GLenum, + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + border: GLint, + ) { + panic!(); + } + + fn copy_tex_sub_image_2d( + &self, + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + ) { + unsafe { + CopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height); + } + } + + fn copy_tex_sub_image_3d( + &self, + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + zoffset: GLint, + x: GLint, + y: GLint, + width: GLsizei, + height: GLsizei, + ) { + unsafe { + CopyTexSubImage3D( + target, level, xoffset, yoffset, zoffset, x, y, width, height, + ); + } + } + + fn tex_sub_image_2d( + &self, + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + width: GLsizei, + height: GLsizei, + format: GLenum, + ty: GLenum, + data: &[u8], + ) { + debug!( + "tex_sub_image_2d {} {} {} {} {} {} {} {}", + target, level, xoffset, yoffset, width, height, format, ty + ); + //panic!(); + unsafe { + TexSubImage2D( + target, + level, + xoffset, + yoffset, + width, + height, + format, + ty, + data.as_ptr() as *const c_void, + ); + } + } + + fn tex_sub_image_2d_pbo( + &self, + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + width: GLsizei, + height: GLsizei, + format: GLenum, + ty: GLenum, + offset: usize, + ) { + debug!( + "tex_sub_image_2d_pbo {} {} {} {} {} {} {} {} {}", + target, level, xoffset, yoffset, width, height, format, ty, offset + ); + //panic!(); + unsafe { + TexSubImage2D( + target, + level, + xoffset, + yoffset, + width, + height, + format, + ty, + offset as *const c_void, + ); + } + } + + fn tex_sub_image_3d( + &self, + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + zoffset: GLint, + width: GLsizei, + height: GLsizei, + depth: GLsizei, + format: GLenum, + ty: GLenum, + data: &[u8], + ) { + debug!("tex_sub_image_3d"); + //panic!(); + unsafe { + TexSubImage3D( + target, + level, + xoffset, + yoffset, + zoffset, + width, + height, + depth, + format, + ty, + data.as_ptr() as *const c_void, + ); + } + } + + fn tex_sub_image_3d_pbo( + &self, + target: GLenum, + level: GLint, + xoffset: GLint, + yoffset: GLint, + zoffset: GLint, + width: GLsizei, + height: GLsizei, + depth: GLsizei, + format: GLenum, + ty: GLenum, + offset: usize, + ) { + unsafe { + TexSubImage3D( + target, + level, + xoffset, + yoffset, + zoffset, + width, + height, + depth, + format, + ty, + offset as *const c_void, + ); + } + } + + fn tex_storage_2d( + &self, + target: GLenum, + levels: GLint, + internal_format: GLenum, + width: GLsizei, + height: GLsizei, + ) { + //panic!(); + unsafe { + TexStorage2D(target, levels, internal_format, width, height); + } + } + + fn tex_storage_3d( + &self, + target: GLenum, + levels: GLint, + internal_format: GLenum, + width: GLsizei, + height: GLsizei, + depth: GLsizei, + ) { + //panic!(); + unsafe { + TexStorage3D(target, levels, internal_format, width, height, depth); + } + } + + fn get_tex_image_into_buffer( + &self, + target: GLenum, + level: GLint, + format: GLenum, + ty: GLenum, + output: &mut [u8], + ) { + panic!(); + } + + unsafe fn copy_image_sub_data( + &self, + src_name: GLuint, + src_target: GLenum, + src_level: GLint, + src_x: GLint, + src_y: GLint, + src_z: GLint, + dst_name: GLuint, + dst_target: GLenum, + dst_level: GLint, + dst_x: GLint, + dst_y: GLint, + dst_z: GLint, + src_width: GLsizei, + src_height: GLsizei, + src_depth: GLsizei, + ) { + CopyImageSubData( + src_name, src_target, src_level, src_x, src_y, src_z, dst_name, dst_target, dst_level, + dst_x, dst_y, dst_z, src_width, src_height, src_depth, + ); + } + + fn invalidate_framebuffer(&self, target: GLenum, attachments: &[GLenum]) { + unsafe { + InvalidateFramebuffer(target, attachments.len() as GLsizei, attachments.as_ptr()); + } + } + + fn invalidate_sub_framebuffer( + &self, + target: GLenum, + attachments: &[GLenum], + xoffset: GLint, + yoffset: GLint, + width: GLsizei, + height: GLsizei, + ) { + } + + #[inline] + unsafe fn get_integer_v(&self, name: GLenum, result: &mut [GLint]) { + //panic!(); + assert!(!result.is_empty()); + GetIntegerv(name, result.as_mut_ptr()); + } + + #[inline] + unsafe fn get_integer_64v(&self, name: GLenum, result: &mut [GLint64]) { + panic!(); + //assert!(!result.is_empty()); + } + + #[inline] + unsafe fn get_integer_iv(&self, name: GLenum, index: GLuint, result: &mut [GLint]) { + panic!(); + //assert!(!result.is_empty()); + } + + #[inline] + unsafe fn get_integer_64iv(&self, name: GLenum, index: GLuint, result: &mut [GLint64]) { + panic!(); + //assert!(!result.is_empty()); + } + + #[inline] + unsafe fn get_boolean_v(&self, name: GLenum, result: &mut [GLboolean]) { + debug!("get_boolean_v {}", name); + //panic!(); + assert!(!result.is_empty()); + GetBooleanv(name, result.as_mut_ptr()); + } + + #[inline] + unsafe fn get_float_v(&self, name: GLenum, result: &mut [GLfloat]) { + panic!(); + //assert!(!result.is_empty()); + } + + fn get_framebuffer_attachment_parameter_iv( + &self, + target: GLenum, + attachment: GLenum, + pname: GLenum, + ) -> GLint { + panic!(); + //0 + } + + fn get_renderbuffer_parameter_iv(&self, target: GLenum, pname: GLenum) -> GLint { + panic!(); + //0 + } + + fn get_tex_parameter_iv(&self, target: GLenum, pname: GLenum) -> GLint { + panic!(); + //0 + } + + fn get_tex_parameter_fv(&self, target: GLenum, pname: GLenum) -> GLfloat { + panic!(); + //0.0 + } + + fn tex_parameter_i(&self, target: GLenum, pname: GLenum, param: GLint) { + //panic!(); + unsafe { + TexParameteri(target, pname, param); + } + } + + fn tex_parameter_f(&self, target: GLenum, pname: GLenum, param: GLfloat) { + panic!(); + } + + fn framebuffer_texture_2d( + &self, + target: GLenum, + attachment: GLenum, + textarget: GLenum, + texture: GLuint, + level: GLint, + ) { + debug!( + "framebuffer_texture_2d {} {} {} {} {}", + target, attachment, textarget, texture, level + ); + //panic!(); + unsafe { + FramebufferTexture2D(target, attachment, textarget, texture, level); + } + } + + fn framebuffer_texture_layer( + &self, + target: GLenum, + attachment: GLenum, + texture: GLuint, + level: GLint, + layer: GLint, + ) { + debug!( + "framebuffer_texture_layer {} {} {} {} {}", + target, attachment, texture, level, layer + ); + //panic!(); + unsafe { + FramebufferTextureLayer(target, attachment, texture, level, layer); + } + } + + fn blit_framebuffer( + &self, + src_x0: GLint, + src_y0: GLint, + src_x1: GLint, + src_y1: GLint, + dst_x0: GLint, + dst_y0: GLint, + dst_x1: GLint, + dst_y1: GLint, + mask: GLbitfield, + filter: GLenum, + ) { + unsafe { + BlitFramebuffer( + src_x0, src_y0, src_x1, src_y1, dst_x0, dst_y0, dst_x1, dst_y1, mask, filter, + ); + } + } + + fn vertex_attrib_4f(&self, index: GLuint, x: GLfloat, y: GLfloat, z: GLfloat, w: GLfloat) { + panic!(); + } + + fn vertex_attrib_pointer_f32( + &self, + index: GLuint, + size: GLint, + normalized: bool, + stride: GLsizei, + offset: GLuint, + ) { + panic!(); + } + + fn vertex_attrib_pointer( + &self, + index: GLuint, + size: GLint, + type_: GLenum, + normalized: bool, + stride: GLsizei, + offset: GLuint, + ) { + debug!( + "vertex_attrib_pointer {} {} {} {} {} {}", + index, size, type_, normalized, stride, offset + ); + //panic!(); + unsafe { + VertexAttribPointer( + index, + size, + type_, + normalized as GLboolean, + stride, + offset as *const GLvoid, + ); + } + } + + fn vertex_attrib_i_pointer( + &self, + index: GLuint, + size: GLint, + type_: GLenum, + stride: GLsizei, + offset: GLuint, + ) { + debug!( + "vertex_attrib_i_pointer {} {} {} {} {}", + index, size, type_, stride, offset + ); + //panic!(); + unsafe { + VertexAttribIPointer(index, size, type_, stride, offset as *const GLvoid); + } + } + + fn vertex_attrib_divisor(&self, index: GLuint, divisor: GLuint) { + debug!("vertex_attrib_divisor {} {}", index, divisor); + //assert!(index == 0 && divisor == 0); + //panic!(); + unsafe { + VertexAttribDivisor(index, divisor); + } + } + + fn viewport(&self, x: GLint, y: GLint, width: GLsizei, height: GLsizei) { + debug!("viewport {} {} {} {}", x, y, width, height); + //panic!(); + unsafe { + SetViewport(x, y, width, height); + } + } + + fn scissor(&self, x: GLint, y: GLint, width: GLsizei, height: GLsizei) { + //panic!(); + unsafe { + SetScissor(x, y, width, height); + } + } + + fn line_width(&self, width: GLfloat) { + panic!(); + } + + fn use_program(&self, program: GLuint) { + //panic!(); + unsafe { + UseProgram(program); + } + } + + fn validate_program(&self, program: GLuint) { + panic!(); + } + + fn draw_arrays(&self, mode: GLenum, first: GLint, count: GLsizei) { + panic!(); + } + + fn draw_arrays_instanced( + &self, + mode: GLenum, + first: GLint, + count: GLsizei, + primcount: GLsizei, + ) { + panic!(); + } + + fn draw_elements( + &self, + mode: GLenum, + count: GLsizei, + element_type: GLenum, + indices_offset: GLuint, + ) { + debug!( + "draw_elements {} {} {} {} {}", + mode, count, element_type, indices_offset + ); + //panic!(); + unsafe { + DrawElementsInstanced( + mode, + count, + element_type, + indices_offset as *const c_void, + 1, + ); + } + } + + fn draw_elements_instanced( + &self, + mode: GLenum, + count: GLsizei, + element_type: GLenum, + indices_offset: GLuint, + primcount: GLsizei, + ) { + debug!( + "draw_elements_instanced {} {} {} {} {}", + mode, count, element_type, indices_offset, primcount + ); + //panic!(); + unsafe { + DrawElementsInstanced( + mode, + count, + element_type, + indices_offset as *const c_void, + primcount, + ); + } + } + + fn blend_color(&self, r: f32, g: f32, b: f32, a: f32) { + unsafe { + BlendColor(r, g, b, a); + } + } + + fn blend_func(&self, sfactor: GLenum, dfactor: GLenum) { + unsafe { + BlendFunc(sfactor, dfactor, sfactor, dfactor); + } + } + + fn blend_func_separate( + &self, + src_rgb: GLenum, + dest_rgb: GLenum, + src_alpha: GLenum, + dest_alpha: GLenum, + ) { + unsafe { + BlendFunc(src_rgb, dest_rgb, src_alpha, dest_alpha); + } + } + + fn blend_equation(&self, mode: GLenum) { + unsafe { + BlendEquation(mode); + } + } + + fn blend_equation_separate(&self, mode_rgb: GLenum, mode_alpha: GLenum) { + panic!(); + } + + fn color_mask(&self, r: bool, g: bool, b: bool, a: bool) { + panic!(); + } + + fn cull_face(&self, mode: GLenum) { + panic!(); + } + + fn front_face(&self, mode: GLenum) { + panic!(); + } + + fn enable(&self, cap: GLenum) { + debug!("enable {}", cap); + //panic!(); + unsafe { + Enable(cap); + } + } + + fn disable(&self, cap: GLenum) { + debug!("disable {}", cap); + //panic!(); + unsafe { + Disable(cap); + } + } + + fn hint(&self, param_name: GLenum, param_val: GLenum) { + panic!(); + } + + fn is_enabled(&self, cap: GLenum) -> GLboolean { + panic!(); + //0 + } + + fn is_shader(&self, shader: GLuint) -> GLboolean { + panic!(); + //0 + } + + fn is_texture(&self, texture: GLenum) -> GLboolean { + panic!(); + //0 + } + + fn is_framebuffer(&self, framebuffer: GLenum) -> GLboolean { + panic!(); + //0 + } + + fn is_renderbuffer(&self, renderbuffer: GLenum) -> GLboolean { + panic!(); + //0 + } + + fn check_frame_buffer_status(&self, target: GLenum) -> GLenum { + debug!("check_frame_buffer_status {}", target); + //panic!(); + unsafe { CheckFramebufferStatus(target) } + } + + fn enable_vertex_attrib_array(&self, index: GLuint) { + //panic!(); + debug!("enable_vertex_attrib_array {}", index); + unsafe { + EnableVertexAttribArray(index); + //assert_eq!(index, 0); + } + } + + fn disable_vertex_attrib_array(&self, index: GLuint) { + panic!(); + } + + fn uniform_1f(&self, location: GLint, v0: GLfloat) { + panic!(); + } + + fn uniform_1fv(&self, location: GLint, values: &[f32]) { + panic!(); + } + + fn uniform_1i(&self, location: GLint, v0: GLint) { + debug!("uniform_1i {} {}", location, v0); + //panic!(); + unsafe { + Uniform1i(location, v0); + } + } + + fn uniform_1iv(&self, location: GLint, values: &[i32]) { + panic!(); + } + + fn uniform_1ui(&self, location: GLint, v0: GLuint) { + panic!(); + } + + fn uniform_2f(&self, location: GLint, v0: GLfloat, v1: GLfloat) { + panic!(); + } + + fn uniform_2fv(&self, location: GLint, values: &[f32]) { + panic!(); + } + + fn uniform_2i(&self, location: GLint, v0: GLint, v1: GLint) { + panic!(); + } + + fn uniform_2iv(&self, location: GLint, values: &[i32]) { + panic!(); + } + + fn uniform_2ui(&self, location: GLint, v0: GLuint, v1: GLuint) { + panic!(); + } + + fn uniform_3f(&self, location: GLint, v0: GLfloat, v1: GLfloat, v2: GLfloat) { + panic!(); + } + + fn uniform_3fv(&self, location: GLint, values: &[f32]) { + panic!(); + } + + fn uniform_3i(&self, location: GLint, v0: GLint, v1: GLint, v2: GLint) { + panic!(); + } + + fn uniform_3iv(&self, location: GLint, values: &[i32]) { + panic!(); + } + + fn uniform_3ui(&self, location: GLint, v0: GLuint, v1: GLuint, v2: GLuint) { + panic!(); + } + + fn uniform_4f(&self, location: GLint, x: GLfloat, y: GLfloat, z: GLfloat, w: GLfloat) { + panic!(); + } + + fn uniform_4i(&self, location: GLint, x: GLint, y: GLint, z: GLint, w: GLint) { + panic!(); + } + + fn uniform_4iv(&self, location: GLint, values: &[i32]) { + panic!(); + } + + fn uniform_4ui(&self, location: GLint, x: GLuint, y: GLuint, z: GLuint, w: GLuint) { + panic!(); + } + + fn uniform_4fv(&self, location: GLint, values: &[f32]) { + unsafe { + Uniform4fv(location, (values.len() / 4) as GLsizei, values.as_ptr()); + } + } + + fn uniform_matrix_2fv(&self, location: GLint, transpose: bool, value: &[f32]) { + panic!(); + } + + fn uniform_matrix_3fv(&self, location: GLint, transpose: bool, value: &[f32]) { + panic!(); + } + + fn uniform_matrix_4fv(&self, location: GLint, transpose: bool, value: &[f32]) { + debug!("uniform_matrix_4fv {} {} {:?}", location, transpose, value); + //panic!(); + unsafe { + UniformMatrix4fv( + location, + (value.len() / 16) as GLsizei, + transpose as GLboolean, + value.as_ptr(), + ); + } + } + + fn depth_mask(&self, flag: bool) { + debug!("depth_mask {}", flag); + //panic!(); + unsafe { + DepthMask(flag as GLboolean); + } + } + + fn depth_range(&self, near: f64, far: f64) { + panic!(); + } + + fn get_active_attrib(&self, program: GLuint, index: GLuint) -> (i32, u32, String) { + panic!(); + //(0, 0, String::new()) + } + + fn get_active_uniform(&self, program: GLuint, index: GLuint) -> (i32, u32, String) { + panic!(); + //(0, 0, String::new()) + } + + fn get_active_uniforms_iv( + &self, + program: GLuint, + indices: Vec, + pname: GLenum, + ) -> Vec { + panic!(); + //Vec::new() + } + + fn get_active_uniform_block_i(&self, program: GLuint, index: GLuint, pname: GLenum) -> GLint { + panic!(); + //0 + } + + fn get_active_uniform_block_iv( + &self, + program: GLuint, + index: GLuint, + pname: GLenum, + ) -> Vec { + panic!(); + //Vec::new() + } + + fn get_active_uniform_block_name(&self, program: GLuint, index: GLuint) -> String { + panic!(); + //String::new() + } + + fn get_attrib_location(&self, program: GLuint, name: &str) -> c_int { + let name = CString::new(name).unwrap(); + unsafe { GetAttribLocation(program, name.as_ptr()) } + } + + fn get_frag_data_location(&self, program: GLuint, name: &str) -> c_int { + panic!(); + //0 + } + + fn get_uniform_location(&self, program: GLuint, name: &str) -> c_int { + debug!("get_uniform_location {} {}", program, name); + //panic!(); + let name = CString::new(name).unwrap(); + unsafe { GetUniformLocation(program, name.as_ptr()) } + } + + fn get_program_info_log(&self, program: GLuint) -> String { + panic!(); + //String::new() + } + + #[inline] + unsafe fn get_program_iv(&self, program: GLuint, pname: GLenum, result: &mut [GLint]) { + debug!("get_program_iv {}", pname); + //panic!(); + assert!(!result.is_empty()); + //#define GL_LINK_STATUS 0x8B82 + if pname == 0x8b82 { + result[0] = 1; + } + } + + fn get_program_binary(&self, program: GLuint) -> (Vec, GLenum) { + panic!(); + //(Vec::new(), NONE) + } + + fn program_binary(&self, program: GLuint, format: GLenum, binary: &[u8]) { + panic!(); + } + + fn program_parameter_i(&self, program: GLuint, pname: GLenum, value: GLint) { + panic!(); + } + + #[inline] + unsafe fn get_vertex_attrib_iv(&self, index: GLuint, pname: GLenum, result: &mut [GLint]) { + panic!(); + //assert!(!result.is_empty()); + } + + #[inline] + unsafe fn get_vertex_attrib_fv(&self, index: GLuint, pname: GLenum, result: &mut [GLfloat]) { + panic!(); + //assert!(!result.is_empty()); + } + + fn get_vertex_attrib_pointer_v(&self, index: GLuint, pname: GLenum) -> GLsizeiptr { + panic!(); + //0 + } + + fn get_buffer_parameter_iv(&self, target: GLuint, pname: GLenum) -> GLint { + panic!(); + //0 + } + + fn get_shader_info_log(&self, shader: GLuint) -> String { + debug!("get_shader_info_log {}", shader); + //panic!(); + String::new() + } + + fn get_string(&self, which: GLenum) -> String { + // panic!(); + unsafe { + let llstr = GetString(which); + if !llstr.is_null() { + return str::from_utf8_unchecked(CStr::from_ptr(llstr).to_bytes()).to_string(); + } else { + return "".to_string(); + } + } + } + + fn get_string_i(&self, which: GLenum, index: GLuint) -> String { + //panic!(); + unsafe { + let llstr = GetStringi(which, index); + if !llstr.is_null() { + str::from_utf8_unchecked(CStr::from_ptr(llstr).to_bytes()).to_string() + } else { + "".to_string() + } + } + } + + unsafe fn get_shader_iv(&self, shader: GLuint, pname: GLenum, result: &mut [GLint]) { + debug!("get_shader_iv"); + //panic!(); + assert!(!result.is_empty()); + if pname == 0x8B81 + /*gl::COMPILE_STATUS*/ + { + result[0] = 1; + } + } + + fn get_shader_precision_format( + &self, + _shader_type: GLuint, + precision_type: GLuint, + ) -> (GLint, GLint, GLint) { + // gl.GetShaderPrecisionFormat is not available until OpenGL 4.1. + // Fallback to OpenGL standard precissions that most desktop hardware support. + match precision_type { + LOW_FLOAT | MEDIUM_FLOAT | HIGH_FLOAT => { + // Fallback to IEEE 754 single precision + // Range: from -2^127 to 2^127 + // Significand precision: 23 bits + (127, 127, 23) + } + LOW_INT | MEDIUM_INT | HIGH_INT => { + // Fallback to single precision integer + // Range: from -2^24 to 2^24 + // Precision: For integer formats this value is always 0 + (24, 24, 0) + } + _ => (0, 0, 0), + } + } + + fn compile_shader(&self, shader: GLuint) { + debug!("compile_shader {}", shader); + //panic!(); + } + + fn create_program(&self) -> GLuint { + debug!("create_program"); + //panic!(); + unsafe { CreateProgram() } + } + + fn delete_program(&self, program: GLuint) { + unsafe { + DeleteProgram(program); + } + } + + fn create_shader(&self, shader_type: GLenum) -> GLuint { + debug!("create_shader {}", shader_type); + //panic!(); + unsafe { CreateShader(shader_type) } + } + + fn delete_shader(&self, shader: GLuint) { + debug!("delete_shader {}", shader); + //panic!(); + unsafe { + DeleteShader(shader); + } + } + + fn detach_shader(&self, program: GLuint, shader: GLuint) { + debug!("detach_shader {} {}", program, shader); + //panic!(); + } + + fn link_program(&self, program: GLuint) { + debug!("link_program {}", program); + //panic!(); + unsafe { + LinkProgram(program); + } + } + + fn clear_color(&self, r: f32, g: f32, b: f32, a: f32) { + //panic!(); + unsafe { + ClearColor(r, g, b, a); + } + } + + fn clear(&self, buffer_mask: GLbitfield) { + debug!("clear {}", buffer_mask); + //panic!(); + unsafe { + Clear(buffer_mask); + } + } + + fn clear_depth(&self, depth: f64) { + debug!("clear_depth {}", depth); + //panic!(); + unsafe { + ClearDepth(depth as GLclampd); + } + } + + fn clear_stencil(&self, s: GLint) { + panic!(); + } + + fn flush(&self) {} + + fn finish(&self) { + unsafe { + Finish(); + } + } + + fn get_error(&self) -> GLenum { + //panic!(); + unsafe { GetError() } + } + + fn stencil_mask(&self, mask: GLuint) { + panic!(); + } + + fn stencil_mask_separate(&self, face: GLenum, mask: GLuint) { + panic!(); + } + + fn stencil_func(&self, func: GLenum, ref_: GLint, mask: GLuint) { + panic!(); + } + + fn stencil_func_separate(&self, face: GLenum, func: GLenum, ref_: GLint, mask: GLuint) { + panic!(); + } + + fn stencil_op(&self, sfail: GLenum, dpfail: GLenum, dppass: GLenum) { + panic!(); + } + + fn stencil_op_separate(&self, face: GLenum, sfail: GLenum, dpfail: GLenum, dppass: GLenum) { + panic!(); + } + + fn egl_image_target_texture2d_oes(&self, target: GLenum, image: GLeglImageOES) { + panic!("not supported") + } + + fn egl_image_target_renderbuffer_storage_oes(&self, target: GLenum, image: GLeglImageOES) { + panic!("not supported") + } + + fn generate_mipmap(&self, target: GLenum) { + unsafe { + GenerateMipmap(target); + } + } + + fn insert_event_marker_ext(&self, message: &str) { + panic!(); + } + + fn push_group_marker_ext(&self, message: &str) { + debug!("push group {}", message); + panic!(); + } + + fn pop_group_marker_ext(&self) { + debug!("pop group"); + panic!(); + } + + fn debug_message_insert_khr( + &self, + source: GLenum, + type_: GLenum, + id: GLuint, + severity: GLenum, + message: &str, + ) { + panic!(); + } + + fn push_debug_group_khr(&self, source: GLenum, id: GLuint, message: &str) { + panic!(); + } + + fn pop_debug_group_khr(&self) { + panic!(); + } + + fn fence_sync(&self, condition: GLenum, flags: GLbitfield) -> GLsync { + panic!(); + //ptr::null() + } + + fn client_wait_sync(&self, sync: GLsync, flags: GLbitfield, timeout: GLuint64) { + panic!(); + } + + fn wait_sync(&self, sync: GLsync, flags: GLbitfield, timeout: GLuint64) { + panic!(); + } + + fn texture_range_apple(&self, target: GLenum, data: &[u8]) { + panic!(); + } + + fn delete_sync(&self, sync: GLsync) { + panic!(); + } + + fn gen_fences_apple(&self, n: GLsizei) -> Vec { + panic!(); + //Vec::new() + } + + fn delete_fences_apple(&self, fences: &[GLuint]) { + panic!(); + } + + fn set_fence_apple(&self, fence: GLuint) { + panic!(); + } + + fn finish_fence_apple(&self, fence: GLuint) { + panic!(); + } + + fn test_fence_apple(&self, fence: GLuint) { + panic!(); + } + + fn test_object_apple(&self, object: GLenum, name: GLuint) -> GLboolean { + panic!(); + //0 + } + + fn finish_object_apple(&self, object: GLenum, name: GLuint) { + panic!(); + } + + // GL_ARB_blend_func_extended + fn bind_frag_data_location_indexed( + &self, + program: GLuint, + color_number: GLuint, + index: GLuint, + name: &str, + ) { + panic!(); + } + + fn get_frag_data_index(&self, program: GLuint, name: &str) -> GLint { + panic!(); + //-1 + } + + // GL_KHR_debug + fn get_debug_messages(&self) -> Vec { + Vec::new() + } + + fn provoking_vertex_angle(&self, _mode: GLenum) { + unimplemented!("This extension is GLES only"); + } + + // GL_KHR_blend_equation_advanced + fn blend_barrier_khr(&self) { + panic!(); + } + + // GL_CHROMIUM_copy_texture + fn copy_texture_chromium( + &self, + _source_id: GLuint, + _source_level: GLint, + _dest_target: GLenum, + _dest_id: GLuint, + _dest_level: GLint, + _internal_format: GLint, + _dest_type: GLenum, + _unpack_flip_y: GLboolean, + _unpack_premultiply_alpha: GLboolean, + _unpack_unmultiply_alpha: GLboolean, + ) { + unimplemented!("This extension is GLES only"); + } + fn copy_sub_texture_chromium( + &self, + _source_id: GLuint, + _source_level: GLint, + _dest_target: GLenum, + _dest_id: GLuint, + _dest_level: GLint, + _x_offset: GLint, + _y_offset: GLint, + _x: GLint, + _y: GLint, + _width: GLsizei, + _height: GLsizei, + _unpack_flip_y: GLboolean, + _unpack_premultiply_alpha: GLboolean, + _unpack_unmultiply_alpha: GLboolean, + ) { + unimplemented!("This extension is GLES only"); + } + + // GL_ANGLE_copy_texture_3d + fn copy_texture_3d_angle( + &self, + _source_id: GLuint, + _source_level: GLint, + _dest_target: GLenum, + _dest_id: GLuint, + _dest_level: GLint, + _internal_format: GLint, + _dest_type: GLenum, + _unpack_flip_y: GLboolean, + _unpack_premultiply_alpha: GLboolean, + _unpack_unmultiply_alpha: GLboolean, + ) { + unimplemented!("Not supported by SWGL"); + } + + fn copy_sub_texture_3d_angle( + &self, + _source_id: GLuint, + _source_level: GLint, + _dest_target: GLenum, + _dest_id: GLuint, + _dest_level: GLint, + _x_offset: GLint, + _y_offset: GLint, + _z_offset: GLint, + _x: GLint, + _y: GLint, + _z: GLint, + _width: GLsizei, + _height: GLsizei, + _depth: GLsizei, + _unpack_flip_y: GLboolean, + _unpack_premultiply_alpha: GLboolean, + _unpack_unmultiply_alpha: GLboolean, + ) { + unimplemented!("Not supported by SWGL"); + } +} diff --git a/third_party/webrender/swgl/src/texture.h b/third_party/webrender/swgl/src/texture.h new file mode 100644 index 00000000000..0219d078bcf --- /dev/null +++ b/third_party/webrender/swgl/src/texture.h @@ -0,0 +1,127 @@ +/* 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/. */ + +template +static PackedRGBA8 textureLinearPackedRGBA8(S sampler, ivec2 i, int zoffset) { + assert(sampler->format == TextureFormat::RGBA8); + ivec2 frac = i & 0x7F; + i >>= 7; + + I32 row0 = clampCoord(i.x, sampler->width) + + clampCoord(i.y, sampler->height) * sampler->stride + zoffset; + I32 row1 = row0 + ((i.y >= 0 && i.y < int32_t(sampler->height) - 1) & + I32(sampler->stride)); + I16 fracx = + CONVERT(frac.x & (i.x >= 0 && i.x < int32_t(sampler->width) - 1), I16); + I16 fracy = CONVERT(frac.y, I16); + + auto a0 = + CONVERT(unaligned_load>(&sampler->buf[row0.x]), V8); + auto a1 = + CONVERT(unaligned_load>(&sampler->buf[row1.x]), V8); + a0 += ((a1 - a0) * fracy.x) >> 7; + + auto b0 = + CONVERT(unaligned_load>(&sampler->buf[row0.y]), V8); + auto b1 = + CONVERT(unaligned_load>(&sampler->buf[row1.y]), V8); + b0 += ((b1 - b0) * fracy.y) >> 7; + + auto abl = combine(lowHalf(a0), lowHalf(b0)); + auto abh = combine(highHalf(a0), highHalf(b0)); + abl += ((abh - abl) * fracx.xxxxyyyy) >> 7; + + auto c0 = + CONVERT(unaligned_load>(&sampler->buf[row0.z]), V8); + auto c1 = + CONVERT(unaligned_load>(&sampler->buf[row1.z]), V8); + c0 += ((c1 - c0) * fracy.z) >> 7; + + auto d0 = + CONVERT(unaligned_load>(&sampler->buf[row0.w]), V8); + auto d1 = + CONVERT(unaligned_load>(&sampler->buf[row1.w]), V8); + d0 += ((d1 - d0) * fracy.w) >> 7; + + auto cdl = combine(lowHalf(c0), lowHalf(d0)); + auto cdh = combine(highHalf(c0), highHalf(d0)); + cdl += ((cdh - cdl) * fracx.zzzzwwww) >> 7; + + return pack(combine(HalfRGBA8(abl), HalfRGBA8(cdl))); +} + +template +static inline void textureLinearCommit4(S sampler, ivec2 i, int zoffset, + uint32_t* buf) { + commit_span(buf, textureLinearPackedRGBA8(sampler, i, zoffset)); +} + +template +static void textureLinearCommit8(S sampler, ivec2_scalar i, int zoffset, + uint32_t* buf) { + assert(sampler->format == TextureFormat::RGBA8); + ivec2_scalar frac = i & 0x7F; + i >>= 7; + + uint32_t* row0 = + &sampler + ->buf[clampCoord(i.x, sampler->width) + + clampCoord(i.y, sampler->height) * sampler->stride + zoffset]; + uint32_t* row1 = + row0 + + ((i.y >= 0 && i.y < int32_t(sampler->height) - 1) ? sampler->stride : 0); + int16_t fracx = i.x >= 0 && i.x < int32_t(sampler->width) - 1 ? frac.x : 0; + int16_t fracy = frac.y; + + U32 pix0 = unaligned_load(row0); + U32 pix0n = unaligned_load(row0 + 4); + uint32_t pix0x = row0[8]; + U32 pix1 = unaligned_load(row1); + U32 pix1n = unaligned_load(row1 + 4); + uint32_t pix1x = row1[8]; + + { + auto ab0 = CONVERT(bit_cast>(SHUFFLE(pix0, pix0, 0, 1, 1, 2)), + V16); + auto ab1 = CONVERT(bit_cast>(SHUFFLE(pix1, pix1, 0, 1, 1, 2)), + V16); + ab0 += ((ab1 - ab0) * fracy) >> 7; + + auto cd0 = CONVERT(bit_cast>(SHUFFLE(pix0, pix0n, 2, 3, 3, 4)), + V16); + auto cd1 = CONVERT(bit_cast>(SHUFFLE(pix1, pix1n, 2, 3, 3, 4)), + V16); + cd0 += ((cd1 - cd0) * fracy) >> 7; + + auto abcdl = combine(lowHalf(ab0), lowHalf(cd0)); + auto abcdh = combine(highHalf(ab0), highHalf(cd0)); + abcdl += ((abcdh - abcdl) * fracx) >> 7; + + commit_span(buf, pack(WideRGBA8(abcdl))); + } + + { + auto ab0 = + CONVERT(bit_cast>(SHUFFLE(pix0n, pix0n, 0, 1, 1, 2)), + V16); + auto ab1 = + CONVERT(bit_cast>(SHUFFLE(pix1n, pix1n, 0, 1, 1, 2)), + V16); + ab0 += ((ab1 - ab0) * fracy) >> 7; + + auto cd0 = + CONVERT(bit_cast>(SHUFFLE(pix0n, U32(pix0x), 2, 3, 3, 4)), + V16); + auto cd1 = + CONVERT(bit_cast>(SHUFFLE(pix1n, U32(pix1x), 2, 3, 3, 4)), + V16); + cd0 += ((cd1 - cd0) * fracy) >> 7; + + auto abcdl = combine(lowHalf(ab0), lowHalf(cd0)); + auto abcdh = combine(highHalf(ab0), highHalf(cd0)); + abcdl += ((abcdh - abcdl) * fracx) >> 7; + + commit_span(buf + 4, pack(WideRGBA8(abcdl))); + } +} diff --git a/third_party/webrender/swgl/src/vector_type.h b/third_party/webrender/swgl/src/vector_type.h new file mode 100644 index 00000000000..8ec5876c340 --- /dev/null +++ b/third_party/webrender/swgl/src/vector_type.h @@ -0,0 +1,478 @@ +/* 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/. */ + +#ifdef __clang__ +# ifdef __SSE2__ +# include +# define USE_SSE2 1 +# endif +# ifdef __ARM_NEON +# include +# define USE_NEON 1 +# endif +#endif + +namespace glsl { + +#ifdef __clang__ +template +using VectorType = T __attribute__((ext_vector_type(N))); + +# define CONVERT(vector, type) __builtin_convertvector(vector, type) +# define SHUFFLE(a, b, ...) __builtin_shufflevector(a, b, __VA_ARGS__) + +template +SI VectorType combine(VectorType a, VectorType b) { + return __builtin_shufflevector(a, b, 0, 1, 2, 3); +} + +template +SI VectorType combine(VectorType a, VectorType b) { + return __builtin_shufflevector(a, b, 0, 1, 2, 3, 4, 5, 6, 7); +} + +template +SI VectorType combine(VectorType a, VectorType b) { + return __builtin_shufflevector(a, b, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15); +} + +template +SI VectorType lowHalf(VectorType a) { + return __builtin_shufflevector(a, a, 0, 1, 2, 3); +} + +template +SI VectorType highHalf(VectorType a) { + return __builtin_shufflevector(a, a, 4, 5, 6, 7); +} + +template +SI VectorType lowHalf(VectorType a) { + return __builtin_shufflevector(a, a, 0, 1, 2, 3, 4, 5, 6, 7); +} + +template +SI VectorType highHalf(VectorType a) { + return __builtin_shufflevector(a, a, 8, 9, 10, 11, 12, 13, 14, 15); +} + +template +SI VectorType expand(VectorType a) { + return __builtin_shufflevector(a, a, 0, 1, 2, 3, -1, -1, -1, -1); +} +#else +template +struct VectorMask { + typedef T type; +}; +template <> +struct VectorMask { + typedef int32_t type; +}; +template <> +struct VectorMask { + typedef int16_t type; +}; +template <> +struct VectorMask { + typedef int8_t type; +}; +template <> +struct VectorMask { + typedef int type; +}; + +template +struct VectorType { + enum { SIZE = N }; + + typedef T data_type __attribute__((vector_size(sizeof(T) * N))); + typedef typename VectorMask::type mask_index; + typedef mask_index mask_type + __attribute__((vector_size(sizeof(mask_index) * N))); + typedef T half_type __attribute__((vector_size(sizeof(T) * (N / 2)))); + union { + data_type data; + struct { + T x, y, z, w; + }; + T elements[N]; + struct { + half_type low_half, high_half; + }; + }; + + VectorType() : data{0} { } + + constexpr VectorType(const VectorType& rhs) : data(rhs.data) {} + // GCC vector extensions only support broadcasting scalars on arithmetic ops, + // but not on initializers, hence the following... + constexpr VectorType(T n) : data((data_type){0} + n) {} + constexpr VectorType(T a, T b, T c, T d) : data{a, b, c, d} {} + constexpr VectorType(T a, T b, T c, T d, T e, T f, T g, T h) + : data{a, b, c, d, e, f, g, h} {} + constexpr VectorType(T a, T b, T c, T d, T e, T f, T g, T h, T i, T j, T k, + T l, T m, T n, T o, T p) + : data{a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p} {} + + SI VectorType wrap(const data_type& data) { + VectorType v; + v.data = data; + return v; + } + + T& operator[](size_t i) { return elements[i]; } + T operator[](size_t i) const { return elements[i]; } + + template + operator VectorType() const { + return VectorType::wrap( + (typename VectorType::data_type){U(x), U(y)}); + } + template + operator VectorType() const { + return VectorType::wrap( + (typename VectorType::data_type){U(x), U(y), U(z), U(w)}); + } + template + operator VectorType() const { + return VectorType::wrap((typename VectorType::data_type){ + U(elements[0]), U(elements[1]), U(elements[2]), U(elements[3]), + U(elements[4]), U(elements[5]), U(elements[6]), U(elements[7])}); + } + template + operator VectorType() const { + return VectorType::wrap((typename VectorType::data_type){ + U(elements[0]), + U(elements[1]), + U(elements[2]), + U(elements[3]), + U(elements[4]), + U(elements[5]), + U(elements[6]), + U(elements[7]), + U(elements[8]), + U(elements[9]), + U(elements[10]), + U(elements[11]), + U(elements[12]), + U(elements[13]), + U(elements[14]), + U(elements[15]), + }); + } + + VectorType operator-() const { return wrap(-data); } + VectorType operator~() const { return wrap(~data); } + + VectorType operator&(VectorType x) const { return wrap(data & x.data); } + VectorType operator&(T x) const { return wrap(data & x); } + VectorType operator|(VectorType x) const { return wrap(data | x.data); } + VectorType operator|(T x) const { return wrap(data | x); } + VectorType operator^(VectorType x) const { return wrap(data ^ x.data); } + VectorType operator^(T x) const { return wrap(data ^ x); } + VectorType operator<<(int x) const { return wrap(data << x); } + VectorType operator>>(int x) const { return wrap(data >> x); } + VectorType operator+(VectorType x) const { return wrap(data + x.data); } + VectorType operator+(T x) const { return wrap(data + x); } + friend VectorType operator+(T x, VectorType y) { return wrap(x + y.data); } + VectorType operator-(VectorType x) const { return wrap(data - x.data); } + VectorType operator-(T x) const { return wrap(data - x); } + friend VectorType operator-(T x, VectorType y) { return wrap(x - y.data); } + VectorType operator*(VectorType x) const { return wrap(data * x.data); } + VectorType operator*(T x) const { return wrap(data * x); } + friend VectorType operator*(T x, VectorType y) { return wrap(x * y.data); } + VectorType operator/(VectorType x) const { return wrap(data / x.data); } + VectorType operator/(T x) const { return wrap(data / x); } + friend VectorType operator/(T x, VectorType y) { return wrap(x / y.data); } + VectorType operator%(int x) const { return wrap(data % x); } + + VectorType& operator&=(VectorType x) { + data &= x.data; + return *this; + } + VectorType& operator|=(VectorType x) { + data |= x.data; + return *this; + } + VectorType& operator^=(VectorType x) { + data ^= x.data; + return *this; + } + VectorType& operator<<=(int x) { + data <<= x; + return *this; + } + VectorType& operator>>=(int x) { + data >>= x; + return *this; + } + VectorType& operator+=(VectorType x) { + data += x.data; + return *this; + } + VectorType& operator-=(VectorType x) { + data -= x.data; + return *this; + } + VectorType& operator*=(VectorType x) { + data *= x.data; + return *this; + } + VectorType& operator/=(VectorType x) { + data /= x.data; + return *this; + } + VectorType& operator%=(int x) { + data %= x; + return *this; + } + + VectorType operator==(VectorType x) const { + return VectorType::wrap(data == x.data); + } + VectorType operator!=(VectorType x) const { + return VectorType::wrap(data != x.data); + } + VectorType operator<(VectorType x) const { + return VectorType::wrap(data < x.data); + } + VectorType operator>(VectorType x) const { + return VectorType::wrap(data > x.data); + } + VectorType operator<=(VectorType x) const { + return VectorType::wrap(data <= x.data); + } + VectorType operator>=(VectorType x) const { + return VectorType::wrap(data >= x.data); + } + + VectorType operator!() const { return wrap(!data); } + VectorType operator&&(VectorType x) const { return wrap(data & x.data); } + VectorType operator||(VectorType x) const { return wrap(data | x.data); } + + VectorType& operator=(VectorType x) { + data = x.data; + return *this; + } + + VectorType shuffle(VectorType b, mask_index x, mask_index y, + mask_index z, mask_index w) const { + return VectorType::wrap(__builtin_shuffle( + data, b.data, (typename VectorType::mask_type){x, y, z, w})); + } + VectorType shuffle(VectorType b, mask_index x, mask_index y, + mask_index z, mask_index w, mask_index s, + mask_index t, mask_index u, mask_index v) const { + return VectorType::wrap(__builtin_shuffle( + data, b.data, + (typename VectorType::mask_type){x, y, z, w, s, t, u, v})); + } + VectorType shuffle(VectorType b, mask_index x, mask_index y, + mask_index z, mask_index w, mask_index s, + mask_index t, mask_index u, mask_index v, + mask_index i, mask_index j, mask_index k, + mask_index l, mask_index m, mask_index n, + mask_index o, mask_index p) const { + return VectorType::wrap( + __builtin_shuffle(data, b.data, + (typename VectorType::mask_type){ + x, y, z, w, s, t, u, v, i, j, k, l, m, n, o, p})); + } + + VectorType swizzle(mask_index x, mask_index y, mask_index z, + mask_index w) const { + return VectorType::wrap(__builtin_shuffle( + data, (typename VectorType::mask_type){x, y, z, w})); + } + VectorType swizzle(mask_index x, mask_index y, mask_index z, + mask_index w, mask_index s, mask_index t, + mask_index u, mask_index v) const { + return VectorType::wrap(__builtin_shuffle( + data, (typename VectorType::mask_type){x, y, z, w, s, t, u, v})); + } + + SI VectorType wrap(half_type low, half_type high) { + VectorType v; + v.low_half = low; + v.high_half = high; + return v; + } + + VectorType combine(VectorType high) const { + return VectorType::wrap(data, high.data); + } + +# define xyxy swizzle(0, 1, 0, 1) +# define zwzw swizzle(2, 3, 2, 3) +# define zyxw swizzle(2, 1, 0, 3) +# define xyzz swizzle(0, 1, 2, 2) +# define xxxxyyyy XXXXYYYY() + VectorType XXXXYYYY() const { + return swizzle(0, 0, 0, 0).combine(swizzle(1, 1, 1, 1)); + } +# define zzzzwwww ZZZZWWWW() + VectorType ZZZZWWWW() const { + return swizzle(2, 2, 2, 2).combine(swizzle(3, 3, 3, 3)); + } +# define xyzwxyzw XYZWXYZW() + VectorType XYZWXYZW() const { return combine(*this); } +# define xyxyxyxy XYXYXYXY() + VectorType XYXYXYXY() const { + return swizzle(0, 1, 0, 1).combine(swizzle(0, 1, 0, 1)); + } +# define zwzwzwzw ZWZWZWZW() + VectorType ZWZWZWZW() const { + return swizzle(2, 3, 2, 3).combine(swizzle(2, 3, 2, 3)); + } +# define xxyyzzww XXYYZZWW() + VectorType XXYYZZWW() const { + return swizzle(0, 0, 1, 1).combine(swizzle(2, 2, 3, 3)); + } +}; + +template +struct VectorType { + typedef T data_type __attribute__((vector_size(sizeof(T) * 2))); + union { + data_type data; + struct { + T x, y; + }; + T elements[2]; + }; +}; + +# define CONVERT(vector, type) ((type)(vector)) +# define SHUFFLE(a, b, ...) a.shuffle(b, __VA_ARGS__) + +template +SI VectorType combine(VectorType a, VectorType b) { + return VectorType::wrap(a.data, b.data); +} + +template +SI VectorType lowHalf(VectorType a) { + return VectorType::wrap(a.low_half); +} + +template +SI VectorType highHalf(VectorType a) { + return VectorType::wrap(a.high_half); +} + +template +SI VectorType expand(VectorType a) { + return combine(a, a); +} +#endif + +template +SI VectorType zipLow(VectorType a, VectorType b) { + return SHUFFLE(a, b, 0, 4, 1, 5); +} + +template +SI VectorType zipHigh(VectorType a, VectorType b) { + return SHUFFLE(a, b, 2, 6, 3, 7); +} + +template +SI VectorType zipLow(VectorType a, VectorType b) { + return SHUFFLE(a, b, 0, 8, 1, 9, 2, 10, 3, 11); +} + +template +SI VectorType zipHigh(VectorType a, VectorType b) { + return SHUFFLE(a, b, 4, 12, 5, 13, 6, 14, 7, 15); +} + +template +SI VectorType zipLow(VectorType a, VectorType b) { + return SHUFFLE(a, b, 0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23); +} + +template +SI VectorType zipHigh(VectorType a, VectorType b) { + return SHUFFLE(a, b, 8, 9, 10, 11, 12, 13, 14, 15, 24, 25, 26, 27, 28, 29, 30, + 31); +} + +template +SI VectorType zip2Low(VectorType a, VectorType b) { + return SHUFFLE(a, b, 0, 1, 8, 9, 2, 3, 10, 11); +} + +template +SI VectorType zip2High(VectorType a, VectorType b) { + return SHUFFLE(a, b, 4, 5, 12, 13, 6, 7, 14, 15); +} + +template +struct Unaligned { + template + SI T load(const P* p) { + T v; + memcpy(&v, p, sizeof(v)); + return v; + } + + template + SI void store(P* p, T v) { + memcpy(p, &v, sizeof(v)); + } +}; + +#ifndef __clang__ +template +struct Unaligned> { + template + SI VectorType load(const P* p) { + VectorType v; + memcpy(v.elements, p, sizeof(v)); + return v; + } + + template + SI void store(P* p, VectorType v) { + memcpy(p, v.elements, sizeof(v)); + } +}; +#endif + +template +SI T unaligned_load(const P* p) { + return Unaligned::load(p); +} + +template +SI void unaligned_store(P* p, T v) { + Unaligned::store(p, v); +} + +template +SI D bit_cast(const S& src) { + static_assert(sizeof(D) == sizeof(S), ""); + return unaligned_load(&src); +} + +template +using V2 = VectorType; +template +using V4 = VectorType; +using Float = V4; +using I32 = V4; +using I16 = V4; +using U64 = V4; +using U32 = V4; +using U16 = V4; +using U8 = V4; +using Bool = V4; +template +using V8 = VectorType; +template +using V16 = VectorType; + +} // namespace glsl diff --git a/third_party/webrender/tileview/Cargo.toml b/third_party/webrender/tileview/Cargo.toml new file mode 100644 index 00000000000..66e955d5a34 --- /dev/null +++ b/third_party/webrender/tileview/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "tileview" +version = "0.1.0" +authors = ["Bert Peers "] +license = "MPL-2.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ron = "0.5" +serde = {version = "1.0.88", features = ["derive"] } +webrender = {path = "../webrender", features=["capture","replay","debugger","png","profiler","no_static_freetype", "leak_checks"]} +webrender_api = {path = "../webrender_api", features=["serialize","deserialize"]} +euclid = { version = "0.22.0", features = ["serde"] } diff --git a/third_party/webrender/tileview/src/main.rs b/third_party/webrender/tileview/src/main.rs new file mode 100644 index 00000000000..0e33d15e426 --- /dev/null +++ b/third_party/webrender/tileview/src/main.rs @@ -0,0 +1,721 @@ +/* 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/. */ + +/// Command line tool to convert logged tile cache files into a visualization. +/// +/// Steps to use this: +/// 1. enable webrender; enable gfx.webrender.debug.tile-cache-logging +/// 2. take a capture using ctrl-shift-3 +/// if all is well, there will be a .../wr-capture/tilecache folder with *.ron files +/// 3. run tileview with that folder as the first parameter and some empty output folder as the +/// 2nd: +/// cargo run --release -- /foo/bar/wr-capture/tilecache /tmp/tilecache +/// 4. open /tmp/tilecache/index.html +/// +/// Note: accurate interning info requires that the circular buffer doesn't wrap around. +/// So for best results, use this workflow: +/// a. start up blank browser; in about:config enable logging; close browser +/// b. start new browser, quickly load the repro +/// c. capture. +/// +/// If that's tricky, you can also just throw more memory at it: in render_backend.rs, +/// increase the buffer size here: 'TileCacheLogger::new(500usize)' +/// +/// Note: some features don't work when opening index.html directly due to cross-scripting +/// protections. Instead use a HTTP server: +/// python -m SimpleHTTPServer 8000 + + +use webrender::{TileNode, TileNodeKind, InvalidationReason, TileOffset}; +use webrender::{TileSerializer, TileCacheInstanceSerializer, TileCacheLoggerUpdateLists}; +use webrender::{PrimitiveCompareResultDetail, CompareHelperResult, ItemUid}; +use serde::Deserialize; +use std::fs::File; +use std::io::prelude::*; +use std::path::Path; +use std::ffi::OsString; +use std::collections::HashMap; +use webrender::api::{enumerate_interners, ColorF}; +use euclid::{Rect, Transform3D}; +use webrender_api::units::{PicturePoint, PictureSize, PicturePixel, WorldPixel}; + +static RES_JAVASCRIPT: &'static str = include_str!("tilecache.js"); +static RES_BASE_CSS: &'static str = include_str!("tilecache_base.css"); + +#[derive(Deserialize)] +pub struct Slice { + pub transform: Transform3D, + pub tile_cache: TileCacheInstanceSerializer +} + +// invalidation reason CSS colors +static CSS_FRACTIONAL_OFFSET: &str = "fill:#4040c0;fill-opacity:0.1;"; +static CSS_BACKGROUND_COLOR: &str = "fill:#10c070;fill-opacity:0.1;"; +static CSS_SURFACE_OPACITY_CHANNEL: &str = "fill:#c040c0;fill-opacity:0.1;"; +static CSS_NO_TEXTURE: &str = "fill:#c04040;fill-opacity:0.1;"; +static CSS_NO_SURFACE: &str = "fill:#40c040;fill-opacity:0.1;"; +static CSS_PRIM_COUNT: &str = "fill:#40f0f0;fill-opacity:0.1;"; +static CSS_CONTENT: &str = "fill:#f04040;fill-opacity:0.1;"; +static CSS_COMPOSITOR_KIND_CHANGED: &str = "fill:#f0c070;fill-opacity:0.1;"; +static CSS_VALID_RECT_CHANGED: &str = "fill:#ff00ff;fill-opacity:0.1;"; + +// parameters to tweak the SVG generation +struct SvgSettings { + pub scale: f32, + pub x: f32, + pub y: f32, +} + +fn tile_node_to_svg(node: &TileNode, + transform: &Transform3D, + svg_settings: &SvgSettings) -> String +{ + match &node.kind { + TileNodeKind::Leaf { .. } => { + let rect_world = transform.outer_transformed_rect(&node.rect.to_rect()).unwrap(); + format!("\n", + rect_world.origin.x * svg_settings.scale + svg_settings.x, + rect_world.origin.y * svg_settings.scale + svg_settings.y, + rect_world.size.width * svg_settings.scale, + rect_world.size.height * svg_settings.scale) + }, + TileNodeKind::Node { children } => { + children.iter().fold(String::new(), |acc, child| acc + &tile_node_to_svg(child, transform, svg_settings) ) + } + } +} + +fn tile_to_svg(key: TileOffset, + tile: &TileSerializer, + slice: &Slice, + prev_tile: Option<&TileSerializer>, + itemuid_to_string: &HashMap, + tile_stroke: &str, + prim_class: &str, + invalidation_report: &mut String, + svg_width: &mut i32, svg_height: &mut i32, + svg_settings: &SvgSettings) -> String +{ + let mut svg = format!("\n\n", key.x, key.y); + + + let tile_fill = + match tile.invalidation_reason { + Some(InvalidationReason::FractionalOffset { .. }) => CSS_FRACTIONAL_OFFSET.to_string(), + Some(InvalidationReason::BackgroundColor { .. }) => CSS_BACKGROUND_COLOR.to_string(), + Some(InvalidationReason::SurfaceOpacityChanged { .. }) => CSS_SURFACE_OPACITY_CHANNEL.to_string(), + Some(InvalidationReason::NoTexture) => CSS_NO_TEXTURE.to_string(), + Some(InvalidationReason::NoSurface) => CSS_NO_SURFACE.to_string(), + Some(InvalidationReason::PrimCount { .. }) => CSS_PRIM_COUNT.to_string(), + Some(InvalidationReason::CompositorKindChanged) => CSS_COMPOSITOR_KIND_CHANGED.to_string(), + Some(InvalidationReason::Content { .. } ) => CSS_CONTENT.to_string(), + Some(InvalidationReason::ValidRectChanged) => CSS_VALID_RECT_CHANGED.to_string(), + None => { + let mut background = tile.background_color; + if background.is_none() { + background = slice.tile_cache.background_color + } + match background { + Some(color) => { + let rgb = ( (color.r * 255.0) as u8, + (color.g * 255.0) as u8, + (color.b * 255.0) as u8 ); + format!("fill:rgb({},{},{});fill-opacity:0.3;", rgb.0, rgb.1, rgb.2) + } + None => "fill:none;".to_string() + } + } + }; + + //let tile_style = format!("{}{}", tile_fill, tile_stroke); + let tile_style = format!("{}stroke:none;", tile_fill); + + let title = match tile.invalidation_reason { + Some(_) => format!("slice {} tile ({},{}) - {:?}", + slice.tile_cache.slice, key.x, key.y, + tile.invalidation_reason), + None => String::new() + }; + + if let Some(reason) = &tile.invalidation_reason { + invalidation_report.push_str( + &format!("

", + slice.tile_cache.slice, + key.x, key.y)); + + // go through most reasons individually so we can print something nicer than + // the default debug formatting of old and new: + match reason { + InvalidationReason::FractionalOffset { old, new } => { + invalidation_report.push_str( + &format!("FractionalOffset changed from ({},{}) to ({},{})", + old.x, old.y, new.x, new.y)); + }, + InvalidationReason::BackgroundColor { old, new } => { + fn to_str(c: &Option) -> String { + if let Some(c) = c { + format!("({},{},{},{})", c.r, c.g, c.b, c.a) + } else { + "none".to_string() + } + } + + invalidation_report.push_str( + &format!("BackGroundColor changed from {} to {}", + to_str(old), to_str(new))); + }, + InvalidationReason::SurfaceOpacityChanged { became_opaque } => { + invalidation_report.push_str( + &format!("SurfaceOpacityChanged changed from {} to {}", + !became_opaque, became_opaque)); + }, + InvalidationReason::PrimCount { old, new } => { + // diff the lists to find removed and added ItemUids, + // and convert them to strings to pretty-print what changed: + let old = old.as_ref().unwrap(); + let new = new.as_ref().unwrap(); + let removed = old.iter() + .filter(|i| !new.contains(i)) + .fold(String::new(), + |acc, i| acc + "
  • " + &(i.get_uid()).to_string() + "..." + + &itemuid_to_string.get(i).unwrap_or(&String::new()) + + "
  • \n"); + let added = new.iter() + .filter(|i| !old.contains(i)) + .fold(String::new(), + |acc, i| acc + "
  • " + &(i.get_uid()).to_string() + "..." + + &itemuid_to_string.get(i).unwrap_or(&String::new()) + + "
  • \n"); + invalidation_report.push_str( + &format!("PrimCount changed from {} to {}:
    \ + removed:
      {}
    + added:
      {}
    ", + old.len(), new.len(), + removed, added)); + }, + InvalidationReason::Content { prim_compare_result, prim_compare_result_detail } => { + let _ = prim_compare_result; + match prim_compare_result_detail { + Some(PrimitiveCompareResultDetail::Descriptor { old, new }) => { + if old.prim_uid == new.prim_uid { + // if the prim uid hasn't changed then try to print something useful + invalidation_report.push_str( + &format!("Content: Descriptor changed for uid {}
    ", + old.prim_uid.get_uid())); + let mut changes = String::new(); + if old.prim_clip_box != new.prim_clip_box { + changes += &format!("
  • prim_clip_rect changed from {},{} -> {},{}", + old.prim_clip_box.min.x, + old.prim_clip_box.min.y, + old.prim_clip_box.max.x, + old.prim_clip_box.max.y); + changes += &format!(" to {},{} -> {},{}
  • ", + new.prim_clip_box.min.x, + new.prim_clip_box.min.y, + new.prim_clip_box.max.x, + new.prim_clip_box.max.y); + } + invalidation_report.push_str( + &format!("
      {}
    • Item: {}
    ", + changes, + &itemuid_to_string.get(&old.prim_uid).unwrap_or(&String::new()))); + } else { + // .. if prim UIDs have changed, just dump both items and descriptors. + invalidation_report.push_str( + &format!("Content: Descriptor changed; old uid {}, new uid {}:
    ", + old.prim_uid.get_uid(), + new.prim_uid.get_uid())); + invalidation_report.push_str( + &format!("old:
    • Desc: {:?}
    • Item: {}
    ", + old, + &itemuid_to_string.get(&old.prim_uid).unwrap_or(&String::new()))); + invalidation_report.push_str( + &format!("new:
    • Desc: {:?}
    • Item: {}
    ", + new, + &itemuid_to_string.get(&new.prim_uid).unwrap_or(&String::new()))); + } + }, + Some(PrimitiveCompareResultDetail::Clip { detail }) => { + match detail { + CompareHelperResult::Count { prev_count, curr_count } => { + invalidation_report.push_str( + &format!("Content: Clip count changed from {} to {}
    ", + prev_count, curr_count )); + }, + CompareHelperResult::NotEqual { prev, curr } => { + invalidation_report.push_str( + &format!("Content: Clip ItemUids changed from {} to {}:
    ", + prev.get_uid(), curr.get_uid() )); + invalidation_report.push_str( + &format!("old:
    • {}
    ", + &itemuid_to_string.get(&prev).unwrap_or(&String::new()))); + invalidation_report.push_str( + &format!("new:
    • {}
    ", + &itemuid_to_string.get(&curr).unwrap_or(&String::new()))); + }, + reason => { + invalidation_report.push_str(&format!("{:?}", reason)); + }, + } + }, + reason => { + invalidation_report.push_str(&format!("{:?}", reason)); + }, + } + }, + reason => { + invalidation_report.push_str(&format!("{:?}", reason)); + }, + } + invalidation_report.push_str("
    \n"); + } + + svg += &format!(r#""#, + tile.rect.origin.x * svg_settings.scale + svg_settings.x, + tile.rect.origin.y * svg_settings.scale + svg_settings.y, + tile.rect.size.width * svg_settings.scale, + tile.rect.size.height * svg_settings.scale, + tile_style); + + svg += &format!("\n\n\n{}\n", + tile_node_to_svg(&tile.root, &slice.transform, svg_settings)); + + let right = (tile.rect.origin.x + tile.rect.size.width) as i32; + let bottom = (tile.rect.origin.y + tile.rect.size.height) as i32; + + *svg_width = if right > *svg_width { right } else { *svg_width }; + *svg_height = if bottom > *svg_height { bottom } else { *svg_height }; + + svg += "\n\n"; + + svg += &format!("\n\t", prim_class); + + + let rect_visual_id = Rect { + origin: tile.rect.origin, + size: PictureSize::new(1.0, 1.0) + }; + let rect_visual_id_world = slice.transform.outer_transformed_rect(&rect_visual_id).unwrap(); + svg += &format!("\n{},{} ({})", + rect_visual_id_world.origin.x * svg_settings.scale + svg_settings.x, + (rect_visual_id_world.origin.y + 110.0) * svg_settings.scale + svg_settings.y, + key.x, key.y, slice.tile_cache.slice); + + + for prim in &tile.current_descriptor.prims { + let rect = prim.prim_clip_box; + + // the transform could also be part of the CSS, let the browser do it; + // might be a bit faster and also enable actual 3D transforms. + let rect_pixel = Rect { + origin: PicturePoint::new(rect.min.x, rect.min.y), + size: PictureSize::new(rect.max.x - rect.min.x, rect.max.y - rect.min.y), + }; + let rect_world = slice.transform.outer_transformed_rect(&rect_pixel).unwrap(); + + let style = + if let Some(prev_tile) = prev_tile { + // when this O(n^2) gets too slow, stop brute-forcing and use a set or something + if prev_tile.current_descriptor.prims.iter().find(|&prim| prim.prim_clip_box == rect).is_some() { + "" + } else { + "class=\"svg_changed_prim\" " + } + } else { + "class=\"svg_changed_prim\" " + }; + + svg += &format!("", + rect_world.origin.x * svg_settings.scale + svg_settings.x, + rect_world.origin.y * svg_settings.scale + svg_settings.y, + rect_world.size.width * svg_settings.scale, + rect_world.size.height * svg_settings.scale, + style); + + svg += "\n\t"; + } + + svg += "\n\n"; + + // nearly invisible, all we want is the toolip really + let style = "style=\"fill-opacity:0.001;"; + svg += &format!("{}<\u{2f}rect>", + tile.rect.origin.x * svg_settings.scale + svg_settings.x, + tile.rect.origin.y * svg_settings.scale + svg_settings.y, + tile.rect.size.width * svg_settings.scale, + tile.rect.size.height * svg_settings.scale, + style, + tile_stroke, + title); + + svg +} + +fn slices_to_svg(slices: &[Slice], prev_slices: Option>, + itemuid_to_string: &HashMap, + svg_width: &mut i32, svg_height: &mut i32, + max_slice_index: &mut usize, + svg_settings: &SvgSettings) -> (String, String) +{ + let svg_begin = "\n\ + \n"; + + let mut svg = String::new(); + let mut invalidation_report = "
    Invalidation
    \n".to_string(); + + for slice in slices { + let tile_cache = &slice.tile_cache; + *max_slice_index = if tile_cache.slice > *max_slice_index { tile_cache.slice } else { *max_slice_index }; + + invalidation_report.push_str(&format!("
    \n", tile_cache.slice)); + + let prim_class = format!("tile_slice{}", tile_cache.slice); + + svg += &format!("\n", tile_cache.slice); + + //println!("slice {}", tile_cache.slice); + svg += &format!("\n\n", + tile_cache.slice); + + //let tile_stroke = "stroke:grey;stroke-width:1;".to_string(); + let tile_stroke = "stroke:none;".to_string(); + + let mut prev_slice = None; + if let Some(prev) = &prev_slices { + for prev_search in prev { + if prev_search.tile_cache.slice == tile_cache.slice { + prev_slice = Some(prev_search); + break; + } + } + } + + for (key, tile) in &tile_cache.tiles { + let mut prev_tile = None; + if let Some(prev) = prev_slice { + prev_tile = prev.tile_cache.tiles.get(key); + } + + svg += &tile_to_svg(*key, &tile, &slice, prev_tile, + itemuid_to_string, + &tile_stroke, &prim_class, + &mut invalidation_report, + svg_width, svg_height, svg_settings); + } + + svg += "\n"; + + invalidation_report.push_str("
    \n"); + } + + ( + format!("{}", + svg_begin, + svg_width, + svg_height) + + "\n" + + "\n" + + &svg + + "\n\n", + invalidation_report + ) +} + +fn write_html(output_dir: &Path, max_slice_index: usize, svg_files: &[String], intern_files: &[String]) { + let html_head = "\n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n" + .to_string(); + + let html_body = "\n" + .to_string(); + + + let mut script = "\n\n\n", script); + + script = format!("{}\n\n", script); + + + let html_end = "\n\ + \n" + .to_string(); + + let mut html_slices_form = + "\n
    \n\ + Slice\n".to_string(); + + for ix in 0..max_slice_index + 1 { + html_slices_form += + &format!( + "\n\ + \n", + ix, + max_slice_index + 1, + ix, + ix ); + } + + html_slices_form += "\n"; + + let html_body = format!( + "{}\n\ +
    \n\ +
    \n\ + \n\ + \n\ +
    \n\ +
    \n\ + \n\ +
    \n\ + \n\ +
    \n\ + \n\ +
    \n\ +
    {}
    \n\ +
    Spacebar to Play
    \n\ +
    Use Left/Right to Step
    \n\ + + {} +
    ", + html_body, + svg_files[0], + svg_files[0], + intern_files[0], + svg_files[0], + svg_files.len(), + html_slices_form ); + + let html = format!("{}{}{}{}", html_head, html_body, script, html_end); + + let output_file = output_dir.join("index.html"); + let mut html_output = File::create(output_file).unwrap(); + html_output.write_all(html.as_bytes()).unwrap(); +} + +fn write_css(output_dir: &Path, max_slice_index: usize, svg_settings: &SvgSettings) { + let mut css = String::new(); + + for ix in 0..max_slice_index + 1 { + let color = ( ix % 7 ) + 1; + let rgb = format!("rgb({},{},{})", + if color & 2 != 0 { 205 } else { 90 }, + if color & 4 != 0 { 205 } else { 90 }, + if color & 1 != 0 { 225 } else { 90 }); + + let prim_class = format!("tile_slice{}", ix); + + css += &format!("#{} {{\n\ + fill: {};\n\ + fill-opacity: 0.03;\n\ + stroke-width: {};\n\ + stroke: {};\n\ + }}\n\n", + prim_class, + //rgb, + "none", + 0.8 * svg_settings.scale, + rgb); + } + + css += &format!(".svg_tile_visual_id {{\n\ + font: {}px sans-serif;\n\ + fill: rgb(50,50,50);\n\ + }}\n\n", + 150.0 * svg_settings.scale); + + let output_file = output_dir.join("tilecache.css"); + let mut css_output = File::create(output_file).unwrap(); + css_output.write_all(css.as_bytes()).unwrap(); +} + +macro_rules! updatelist_to_html_macro { + ( $( $name:ident: $ty:ty, )+ ) => { + fn updatelist_to_html(update_lists: &TileCacheLoggerUpdateLists, + invalidation_report: String) -> String + { + let mut html = "\ + \n\ + \n\ + \n\ + \n\ + \n\ +
    \n".to_string(); + + html += &invalidation_report; + + html += "
    Interning
    \n"; + $( + html += &format!("
    {}
    \n
    \n", + stringify!($name)); + for list in &update_lists.$name.1 { + for insertion in &list.insertions { + html += &format!("
    {} {}
    \n", + insertion.uid.get_uid(), + format!("({:?})", insertion.value)); + } + + for removal in &list.removals { + html += &format!("
    {}
    \n", + removal.uid.get_uid()); + } + } + html += "

    \n"; + )+ + html += "
    \n"; + html + } + } +} +enumerate_interners!(updatelist_to_html_macro); + +fn write_tile_cache_visualizer_svg(entry: &std::fs::DirEntry, output_dir: &Path, + slices: &[Slice], prev_slices: Option>, + itemuid_to_string: &HashMap, + svg_width: &mut i32, svg_height: &mut i32, + max_slice_index: &mut usize, + svg_files: &mut Vec::, + svg_settings: &SvgSettings) -> String +{ + let (svg, invalidation_report) = slices_to_svg(&slices, prev_slices, + itemuid_to_string, + svg_width, svg_height, + max_slice_index, + svg_settings); + + let mut output_filename = OsString::from(entry.path().file_name().unwrap()); + output_filename.push(".svg"); + svg_files.push(output_filename.to_string_lossy().to_string()); + + output_filename = output_dir.join(output_filename).into_os_string(); + let mut svg_output = File::create(output_filename).unwrap(); + svg_output.write_all(svg.as_bytes()).unwrap(); + + invalidation_report +} + +fn write_update_list_html(entry: &std::fs::DirEntry, output_dir: &Path, + update_lists: &TileCacheLoggerUpdateLists, + html_files: &mut Vec::, + invalidation_report: String) +{ + let html = updatelist_to_html(update_lists, invalidation_report); + + let mut output_filename = OsString::from(entry.path().file_name().unwrap()); + output_filename.push(".html"); + html_files.push(output_filename.to_string_lossy().to_string()); + + output_filename = output_dir.join(output_filename).into_os_string(); + let mut html_output = File::create(output_filename).unwrap(); + html_output.write_all(html.as_bytes()).unwrap(); +} + +fn main() { + let args: Vec = std::env::args().collect(); + + if args.len() < 3 { + println!("Usage: tileview input_dir output_dir [scale [x y]]"); + println!(" where input_dir is a tile_cache folder inside a wr-capture."); + println!(" Scale is an optional scaling factor to compensate for high-DPI."); + println!(" X, Y is an optional offset to shift the entire SVG by."); + println!("\nexample: cargo run c:/Users/me/AppData/Local/wr-capture.6/tile_cache/ c:/temp/tilecache/"); + std::process::exit(1); + } + + let input_dir = Path::new(&args[1]); + let output_dir = Path::new(&args[2]); + std::fs::create_dir_all(output_dir).unwrap(); + + let scale = if args.len() >= 4 { args[3].parse::().unwrap() } else { 1.0 }; + let x = if args.len() >= 6 { args[4].parse::().unwrap() } else { 0.0 }; // >= 6, requires X and Y + let y = if args.len() >= 6 { args[5].parse::().unwrap() } else { 0.0 }; + let svg_settings = SvgSettings { scale, x, y }; + + let mut svg_width = 100i32; + let mut svg_height = 100i32; + let mut max_slice_index = 0; + + let mut entries: Vec<_> = std::fs::read_dir(input_dir).unwrap() + .filter_map(|r| r.ok()) + .collect(); + // auto-fix a missing 'tile_cache' postfix on the input path -- easy to do when copy-pasting a + // path to a wr-capture; there should at least be a frame00000.ron... + let frame00000 = entries.iter().find(|&entry| entry.path().ends_with("frame00000.ron")); + // ... and if not, try again with 'tile_cache' appended to the input folder + if frame00000.is_none() { + let new_path = input_dir.join("tile_cache"); + entries = std::fs::read_dir(new_path).unwrap() + .filter_map(|r| r.ok()) + .collect(); + } + entries.sort_by_key(|dir| dir.path()); + + let mut svg_files: Vec:: = Vec::new(); + let mut intern_files: Vec:: = Vec::new(); + let mut prev_slices = None; + + let mut itemuid_to_string = HashMap::default(); + + for entry in &entries { + if entry.path().is_dir() { + continue; + } + print!("processing {:?}\t", entry.path()); + let file_data = std::fs::read_to_string(entry.path()).unwrap(); + let chunks: Vec<_> = file_data.split("// @@@ chunk @@@").collect(); + let slices: Vec = match ron::de::from_str(&chunks[0]) { + Ok(data) => { data } + Err(e) => { + println!("ERROR: failed to deserialize slicesg {:?}\n{:?}", entry.path(), e); + prev_slices = None; + continue; + } + }; + let mut update_lists = TileCacheLoggerUpdateLists::new(); + update_lists.from_ron(&chunks[1]); + update_lists.insert_in_lookup(&mut itemuid_to_string); + + let invalidation_report = write_tile_cache_visualizer_svg( + &entry, &output_dir, + &slices, prev_slices, + &itemuid_to_string, + &mut svg_width, &mut svg_height, + &mut max_slice_index, + &mut svg_files, + &svg_settings); + + write_update_list_html(&entry, &output_dir, &update_lists, + &mut intern_files, invalidation_report); + + print!("\r"); + prev_slices = Some(slices); + } + + write_html(output_dir, max_slice_index, &svg_files, &intern_files); + write_css(output_dir, max_slice_index, &svg_settings); + + std::fs::write(output_dir.join("tilecache.js"), RES_JAVASCRIPT).unwrap(); + std::fs::write(output_dir.join("tilecache_base.css"), RES_BASE_CSS).unwrap(); + + println!("\n"); +} diff --git a/third_party/webrender/tileview/src/tilecache.js b/third_party/webrender/tileview/src/tilecache.js new file mode 100644 index 00000000000..cf716e84ef6 --- /dev/null +++ b/third_party/webrender/tileview/src/tilecache.js @@ -0,0 +1,187 @@ +// current SVG file for scrubbing and playback +var svg_index = 0; + +// double buffered s each holding an SVG file +var backbuffer; +var frontbuffer; + + +// timer for animation +var svg_timer; +var is_playing = false; + +function toggle_play() { + if( is_playing ) { + is_playing = false; + clearInterval(svg_timer); + document.getElementById("text_spacebar").innerHTML = + 'Spacebar to play'; + } else { + is_playing = true; + svg_timer = setInterval(on_tick, 100); + document.getElementById("text_spacebar").innerHTML = + 'Playing (Spacebar to stop)'; + function on_tick() { + if( svg_index + 1 == svg_files.length ) { + toggle_play(); + } else { + go_to_svg(svg_index+1); + } + } + } +} + +function toggle_quadtree() { + var quad_groups = document.getElementsByClassName("svg_quadtree") + var i; + for (i = 0; i < quad_groups.length; i++) { + if( quad_groups[i].style.display == "none" ) + quad_groups[i].style.display = "block"; + else + quad_groups[i].style.display = "none"; + } +} + +function update_slice_visibility(max_slice) { + let content = frontbuffer.contentDocument; + update_slice_visibility_for_content(content, max_slice); +} + +function update_slice_visibility_for_content(content, max_slice) { + + intern = document.getElementById('intern').contentDocument; + + for (let slice = 0; slice != max_slice; ++slice) { + var cbox_name = "slice_toggle" + slice; + let cbox = document.getElementById(cbox_name); + if( !cbox ) + continue; + let checked = cbox.checked; + if (content) { // might fail due to cross scripting -- use SimpleHTTPServer + var id = "tile_slice" + slice + "_everything"; + var group = content.getElementById(id); + if (group) { + if (checked) + group.style.display = "block"; + else + group.style.display = "none"; + } + } + if (intern) { + var id = "invalidation_slice" + slice; + var div = intern.getElementById(id); + if (div) { + if (checked) + div.style.display = "block"; + else + div.style.display = "none"; + } + } + } +} + +// try to block repeated keypressed from causing flickering +// when they land between go_to_svg returning and onload +// firing. +var is_loading = false; + +function go_to_svg(index) { + if( index >= svg_files.length || + index < 0 || + is_loading ) { + return; + } + + is_loading = true; + svg_index = index; + + var slider = document.getElementById('frame_slider'); + // won't recurse thanks to is_loading + slider.value = svg_index; + + backbuffer.onload = function(){ + + document.getElementById("text_frame_counter").innerHTML = + svg_files[svg_index]; + + let content = backbuffer.contentDocument; + update_slice_visibility_for_content(content, 20); + + backbuffer.style.display = ''; + frontbuffer.style.display = 'none'; + + var t = frontbuffer; + frontbuffer = backbuffer; + backbuffer = t; + is_loading = false; + } + document.getElementById('intern').src = intern_files[svg_index]; + backbuffer.setAttribute('data', svg_files[svg_index]); + + // also see https://stackoverflow.com/a/29915275 +} + +function load() { + window.addEventListener('keypress', handle_keyboard_shortcut); + window.addEventListener('keydown', handle_keydown); + + frontbuffer = document.getElementById("svg_container0"); + backbuffer = document.getElementById("svg_container1"); + backbuffer.style.display='none'; + + var slider = document.getElementById('frame_slider'); + slider.oninput = function() { + if( is_playing ) { + toggle_play(); + } + go_to_svg(this.value); + } +} + +function handle_keyboard_shortcut(event) { + switch (event.charCode) { + case 32: // ' ' + toggle_play(); + break; + case 113: // 'q' + toggle_quadtree(); + break; + /* + case 49: // "1" key + document.getElementById("radio1").checked = true; + show_image(1); + break; + case 50: // "2" key + document.getElementById("radio2").checked = true; + show_image(2); + break; + case 100: // "d" key + document.getElementById("differences").click(); + break; + case 112: // "p" key + shift_images(-1); + break; + case 110: // "n" key + shift_images(1); + break; + */ + } +} + +function handle_keydown(event) { + switch (event.keyCode) { + case 37: // left arrow + go_to_svg(svg_index-1); + event.preventDefault(); + break; + case 38: // up arrow + break; + case 39: // right arrow + go_to_svg(svg_index+1); + event.preventDefault(); + break; + case 40: // down arrow + break; + } +} + diff --git a/third_party/webrender/tileview/src/tilecache_base.css b/third_party/webrender/tileview/src/tilecache_base.css new file mode 100644 index 00000000000..cfd51af6d16 --- /dev/null +++ b/third_party/webrender/tileview/src/tilecache_base.css @@ -0,0 +1,109 @@ +.tile_svg { + float: left; +} + +#intern { + position:relative; + top:60px; + width: 100%; + height: 100%; + color: orange; + border: 0px; + overflow: auto; + background: white; +} + +.datasheet .header { + color: white; + font-family: Arial; + font-weight: bold; + font-size: 150%; + line-height: 200%; + background-color: grey; + margin-top: 15px; + margin-bottom: 5px; + padding-left: 10px; +} + +.datasheet .subheader { + color: blue; + font-family: Arial; + font-weight: bold; + line-height: 200%; + background-color: lightgrey; + margin-top: 5px; + margin-bottom: 5px; +} + +.datasheet .data { + font-family: monospace; + margin-bottom: 5px; +} + +.datasheet .data *:nth-child(even) { + background: #FFFFFF; +} +.datasheet .data *:nth-child(odd) { + background: #EFEFEF; +} + +.datasheet .data .insert { + color: #006000; +} + +.datasheet .data .remove { + color: #600000; +} + +.datasheet .data .change { + color: #000060; +} + + +.split { + position: fixed; + z-index: 1; + top: 0; + padding-top: 14px; +} + +.left { + left: 0; +} + +.right { + right: 0; + width: 30%; + height: 90%; +} + +#svg_ui_overlay { + position:absolute; + right:0; + top:0; + z-index:70; + color: rgb(255,255,100); + font-family:monospace; + background-color: #404040a0; +} + +#svg_ui_slider { + width:90%; +} + +.svg_invalidated { + fill: white; + font-family:monospace; +} + +.svg_quadtree { + fill: none; + stroke-width: 1; + stroke: orange; +} + +.svg_changed_prim { + stroke: red; + stroke-width: 2.0; +} + diff --git a/third_party/webrender/webrender/Cargo.toml b/third_party/webrender/webrender/Cargo.toml new file mode 100644 index 00000000000..6e3389a309b --- /dev/null +++ b/third_party/webrender/webrender/Cargo.toml @@ -0,0 +1,77 @@ +[package] +name = "webrender" +version = "0.61.0" +authors = ["Glenn Watson "] +license = "MPL-2.0" +repository = "https://github.com/servo/webrender" +description = "A GPU accelerated 2D renderer for web content" +build = "build.rs" +edition = "2018" + +[features] +default = ["freetype-lib"] +freetype-lib = ["freetype/freetype-sys"] +profiler = ["tracy-rs/enable_profiler"] +debugger = ["ws", "serde_json", "serde", "image_loader", "base64"] +capture = ["api/serialize", "ron", "serde", "smallvec/serde"] +replay = ["api/deserialize", "ron", "serde", "smallvec/serde"] +display_list_stats = ["api/display_list_stats"] +serialize_program = ["serde", "webrender_build/serialize_program"] +no_static_freetype = [] +leak_checks = [] + +[build-dependencies] +build-parallel = "0.1.1" +glslopt = "0.1.2" +webrender_build = { version = "0.0.1", path = "../webrender_build" } + +[dependencies] +base64 = { optional = true, version = "0.10" } +bincode = "1.0" +bitflags = "1.2" +byteorder = "1.0" +cfg-if = "0.1.2" +cstr = "0.1.2" +euclid = { version = "0.22.0", features = ["serde"] } +fxhash = "0.2.1" +gleam = "0.12.1" +image_loader = { optional = true, version = "0.23", package = "image", default-features = false, features = ["png"] } +lazy_static = "1" +log = "0.4" +malloc_size_of_derive = "0.1" +num-traits = "0.2" +plane-split = "0.17" +png = { optional = true, version = "0.16" } +rayon = "1" +ron = { optional = true, version = "0.5" } +serde = { optional = true, version = "1.0", features = ["serde_derive"] } +serde_json = { optional = true, version = "1.0" } +smallvec = "1" +time = "0.1" +api = { version = "0.61.0", path = "../webrender_api", package = "webrender_api" } +webrender_build = { version = "0.0.1", path = "../webrender_build" } +malloc_size_of = { version = "0.0.1", path = "../wr_malloc_size_of", package = "wr_malloc_size_of" } +ws = { optional = true, version = "0.9" } +svg_fmt = "0.4" +tracy-rs = { version = "0.1" } + +[dev-dependencies] +mozangle = "0.3.1" +rand = "0.4" + +[target.'cfg(any(target_os = "macos", target_os = "linux"))'.build-dependencies] +backtrace = "0.3" +sig = "1.0" +libc = "0.2" + +[target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies] +freetype = { version = "0.7", default-features = false } +libc = "0.2" + +[target.'cfg(target_os = "windows")'.dependencies] +dwrote = "0.11" + +[target.'cfg(target_os = "macos")'.dependencies] +core-foundation = "0.9" +core-graphics = "0.22" +core-text = { version = "19", default-features = false } diff --git a/third_party/webrender/webrender/backtrace.rs b/third_party/webrender/webrender/backtrace.rs new file mode 100644 index 00000000000..aa6bb6b3297 --- /dev/null +++ b/third_party/webrender/webrender/backtrace.rs @@ -0,0 +1,103 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! Similar to `println!("{:?}", Backtrace::new())`, but doesn’t allocate. +//! +//! Seems to fix some deadlocks: https://github.com/servo/servo/issues/24881 +//! +//! FIXME: if/when a future version of the `backtrace` crate has +//! https://github.com/rust-lang/backtrace-rs/pull/265, use that instead. + +use std::fmt::{self, Write}; +use backtrace::{BytesOrWideString, PrintFmt}; + +#[inline(never)] +pub(crate) fn print(w: &mut dyn std::io::Write) -> Result<(), std::io::Error> { + write!(w, "{:?}", Print { + print_fn_address: print as usize, + }) +} + +struct Print { + print_fn_address: usize, +} + +impl fmt::Debug for Print { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + // Safety: we’re in a signal handler that is about to call `libc::_exit`. + // Potential data races from using `*_unsynchronized` functions are perhaps + // less bad than potential deadlocks? + unsafe { + let mut print_fn_frame = 0; + let mut frame_count = 0; + backtrace::trace_unsynchronized(|frame| { + let found = frame.symbol_address() as usize == self.print_fn_address; + if found { + print_fn_frame = frame_count; + } + frame_count += 1; + !found + }); + + let mode = PrintFmt::Short; + let mut p = print_path; + let mut f = backtrace::BacktraceFmt::new(fmt, mode, &mut p); + f.add_context()?; + let mut result = Ok(()); + let mut frame_count = 0; + backtrace::trace_unsynchronized(|frame| { + let skip = frame_count < print_fn_frame; + frame_count += 1; + if skip { + return true + } + + let mut frame_fmt = f.frame(); + let mut any_symbol = false; + backtrace::resolve_frame_unsynchronized(frame, |symbol| { + any_symbol = true; + if let Err(e) = frame_fmt.symbol(frame, symbol) { + result = Err(e) + } + }); + if !any_symbol { + if let Err(e) = frame_fmt.print_raw(frame.ip(), None, None, None) { + result = Err(e) + } + } + result.is_ok() + }); + result?; + f.finish() + } + } +} + +fn print_path(fmt: &mut fmt::Formatter, path: BytesOrWideString) -> fmt::Result { + match path { + BytesOrWideString::Bytes(mut bytes) => { + loop { + match std::str::from_utf8(bytes) { + Ok(s) => { + fmt.write_str(s)?; + break; + } + Err(err) => { + fmt.write_char(std::char::REPLACEMENT_CHARACTER)?; + match err.error_len() { + Some(len) => bytes = &bytes[err.valid_up_to() + len..], + None => break, + } + } + } + } + } + BytesOrWideString::Wide(wide) => { + for c in std::char::decode_utf16(wide.iter().cloned()) { + fmt.write_char(c.unwrap_or(std::char::REPLACEMENT_CHARACTER))? + } + } + } + Ok(()) +} diff --git a/third_party/webrender/webrender/build.rs b/third_party/webrender/webrender/build.rs new file mode 100644 index 00000000000..36a7f17a8e0 --- /dev/null +++ b/third_party/webrender/webrender/build.rs @@ -0,0 +1,317 @@ +/* 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/. */ + +extern crate webrender_build; + +use std::borrow::Cow; +use std::env; +use std::fs::{canonicalize, read_dir, File}; +use std::io::prelude::*; +use std::path::{Path, PathBuf}; +use std::collections::hash_map::DefaultHasher; +use std::hash::Hasher; +use webrender_build::shader::*; +use webrender_build::shader_features::{ShaderFeatureFlags, get_shader_features}; + +/// Compute the shader path for insertion into the include_str!() macro. +/// This makes for more compact generated code than inserting the literal +/// shader source into the generated file. +/// +/// If someone is building on a network share, I'm sorry. +fn escape_include_path(path: &Path) -> String { + let full_path = canonicalize(path).unwrap(); + let full_name = full_path.as_os_str().to_str().unwrap(); + let full_name = full_name.replace("\\\\?\\", ""); + let full_name = full_name.replace("\\", "/"); + + full_name +} + +fn write_unoptimized_shaders(mut glsl_files: Vec, shader_file: &mut File) -> Result<(), std::io::Error> { + writeln!( + shader_file, + " pub static ref UNOPTIMIZED_SHADERS: HashMap<&'static str, SourceWithDigest> = {{" + )?; + writeln!(shader_file, " let mut shaders = HashMap::new();")?; + + // Sort the file list so that the shaders.rs file is filled + // deterministically. + glsl_files.sort_by(|a, b| a.file_name().cmp(&b.file_name())); + + for glsl in glsl_files { + // Compute the shader name. + assert!(glsl.is_file()); + let shader_name = glsl.file_name().unwrap().to_str().unwrap(); + let shader_name = shader_name.replace(".glsl", ""); + + // Compute a digest of the #include-expanded shader source. We store + // this as a literal alongside the source string so that we don't need + // to hash large strings at runtime. + let mut hasher = DefaultHasher::new(); + let base = glsl.parent().unwrap(); + assert!(base.is_dir()); + ShaderSourceParser::new().parse( + Cow::Owned(shader_source_from_file(&glsl)), + &|f| Cow::Owned(shader_source_from_file(&base.join(&format!("{}.glsl", f)))), + &mut |s| hasher.write(s.as_bytes()), + ); + let digest: ProgramSourceDigest = hasher.into(); + + writeln!( + shader_file, + " shaders.insert(\"{}\", SourceWithDigest {{ source: include_str!(\"{}\"), digest: \"{}\"}});", + shader_name, + escape_include_path(&glsl), + digest, + )?; + } + writeln!(shader_file, " shaders")?; + writeln!(shader_file, " }};")?; + + Ok(()) +} + +#[derive(Clone, Debug)] +struct ShaderOptimizationInput { + shader_name: &'static str, + config: String, + gl_version: ShaderVersion, +} + +#[derive(Debug)] +struct ShaderOptimizationOutput { + full_shader_name: String, + gl_version: ShaderVersion, + vert_file_path: PathBuf, + frag_file_path: PathBuf, + digest: ProgramSourceDigest, +} + +#[derive(Debug)] +struct ShaderOptimizationError { + shader: ShaderOptimizationInput, + message: String, +} + +fn write_optimized_shaders(shader_dir: &Path, shader_file: &mut File, out_dir: &str) -> Result<(), std::io::Error> { + writeln!( + shader_file, + " pub static ref OPTIMIZED_SHADERS: HashMap<(ShaderVersion, &'static str), OptimizedSourceWithDigest> = {{" + )?; + writeln!(shader_file, " let mut shaders = HashMap::new();")?; + + // The full set of optimized shaders can be quite large, so only optimize + // for the GL version we expect to be used on the target platform. If a different GL + // version is used we will simply fall back to the unoptimized shaders. + let shader_versions = match env::var("CARGO_CFG_TARGET_OS").as_ref().map(|s| &**s) { + Ok("android") | Ok("windows") => [ShaderVersion::Gles], + _ => [ShaderVersion::Gl], + }; + + let mut shaders = Vec::default(); + for &gl_version in &shader_versions { + let mut flags = ShaderFeatureFlags::all(); + if gl_version != ShaderVersion::Gl { + flags.remove(ShaderFeatureFlags::GL); + } + if gl_version != ShaderVersion::Gles { + flags.remove(ShaderFeatureFlags::GLES); + flags.remove(ShaderFeatureFlags::TEXTURE_EXTERNAL); + } + flags.remove(ShaderFeatureFlags::DITHERING); + flags.remove(ShaderFeatureFlags::PIXEL_LOCAL_STORAGE); + + for (shader_name, configs) in get_shader_features(flags) { + for config in configs { + shaders.push(ShaderOptimizationInput { + shader_name, + config, + gl_version, + }); + } + } + } + + let outputs = build_parallel::compile_objects(&|shader: &ShaderOptimizationInput| { + println!("Optimizing shader {:?}", shader); + let target = match shader.gl_version { + ShaderVersion::Gl => glslopt::Target::OpenGl, + ShaderVersion::Gles => glslopt::Target::OpenGles30, + }; + let glslopt_ctx = glslopt::Context::new(target); + + let features = shader.config.split(",").filter(|f| !f.is_empty()).collect::>(); + + let (vert_src, frag_src) = build_shader_strings( + shader.gl_version, + &features, + shader.shader_name, + &|f| Cow::Owned(shader_source_from_file(&shader_dir.join(&format!("{}.glsl", f)))), + ); + + let full_shader_name = if shader.config.is_empty() { + shader.shader_name.to_string() + } else { + format!("{}_{}", shader.shader_name, shader.config.replace(",", "_")) + }; + + let vert = glslopt_ctx.optimize(glslopt::ShaderType::Vertex, vert_src); + if !vert.get_status() { + return Err(ShaderOptimizationError { + shader: shader.clone(), + message: vert.get_log().to_string(), + }); + } + let frag = glslopt_ctx.optimize(glslopt::ShaderType::Fragment, frag_src); + if !frag.get_status() { + return Err(ShaderOptimizationError { + shader: shader.clone(), + message: frag.get_log().to_string(), + }); + } + + let vert_source = vert.get_output().unwrap(); + let frag_source = frag.get_output().unwrap(); + + // Compute a digest of the optimized shader sources. We store this + // as a literal alongside the source string so that we don't need + // to hash large strings at runtime. + let mut hasher = DefaultHasher::new(); + hasher.write(vert_source.as_bytes()); + hasher.write(frag_source.as_bytes()); + let digest: ProgramSourceDigest = hasher.into(); + + let vert_file_path = Path::new(out_dir) + .join(format!("{}_{:?}.vert", full_shader_name, shader.gl_version)); + let mut vert_file = File::create(&vert_file_path).unwrap(); + vert_file.write_all(vert_source.as_bytes()).unwrap(); + let frag_file_path = vert_file_path.with_extension("frag"); + let mut frag_file = File::create(&frag_file_path).unwrap(); + frag_file.write_all(frag_source.as_bytes()).unwrap(); + + println!("Finished optimizing shader {:?}", shader); + + Ok(ShaderOptimizationOutput { + full_shader_name, + gl_version: shader.gl_version, + vert_file_path, + frag_file_path, + digest, + }) + }, &shaders); + + match outputs { + Ok(mut outputs) => { + // Sort the shader list so that the shaders.rs file is filled + // deterministically. + outputs.sort_by(|a, b| { + (a.gl_version, a.full_shader_name.clone()).cmp(&(b.gl_version, b.full_shader_name.clone())) + }); + + for shader in outputs { + writeln!( + shader_file, + " shaders.insert(({}, \"{}\"), OptimizedSourceWithDigest {{", + shader.gl_version.variant_name(), + shader.full_shader_name, + )?; + writeln!( + shader_file, + " vert_source: include_str!(\"{}\"),", + escape_include_path(&shader.vert_file_path), + )?; + writeln!( + shader_file, + " frag_source: include_str!(\"{}\"),", + escape_include_path(&shader.frag_file_path), + )?; + writeln!(shader_file, " digest: \"{}\",", shader.digest)?; + writeln!(shader_file, " }});")?; + } + } + Err(err) => match err { + build_parallel::Error::BuildError(err) => { + panic!("Error optimizing shader {:?}: {}", err.shader, err.message) + } + _ => panic!("Error optimizing shaders."), + } + } + + writeln!(shader_file, " shaders")?; + writeln!(shader_file, " }};")?; + + Ok(()) +} + +#[cfg(any(target_os = "macos", target_os = "linux"))] +mod backtrace; + +#[cfg(any(target_os = "macos", target_os = "linux"))] +extern "C" fn handler(sig: i32) { + use std::sync::atomic; + static BEEN_HERE_BEFORE: atomic::AtomicBool = atomic::AtomicBool::new(false); + if !BEEN_HERE_BEFORE.swap(true, atomic::Ordering::SeqCst) { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + let _ = write!(&mut stdout, "Stack trace"); + if let Some(name) = std::thread::current().name() { + let _ = write!(&mut stdout, " for thread \"{}\"", name); + } + let _ = write!(&mut stdout, "\n"); + let _ = backtrace::print(&mut stdout); + } + unsafe { + libc::_exit(sig); + } +} + +fn main() -> Result<(), std::io::Error> { + #[cfg(any(target_os = "macos", target_os = "linux"))] + { + sig::signal!(sig::ffi::Sig::SEGV, handler); // handle segfaults + sig::signal!(sig::ffi::Sig::ILL, handler); // handle stack overflow and unsupported CPUs + sig::signal!(sig::ffi::Sig::IOT, handler); // handle double panics + sig::signal!(sig::ffi::Sig::BUS, handler); // handle invalid memory access + } + + let out_dir = env::var("OUT_DIR").unwrap_or("out".to_owned()); + + let shaders_file_path = Path::new(&out_dir).join("shaders.rs"); + let mut glsl_files = vec![]; + + println!("cargo:rerun-if-changed=res"); + let res_dir = Path::new("res"); + for entry in read_dir(res_dir)? { + let entry = entry?; + let path = entry.path(); + + if entry.file_name().to_str().unwrap().ends_with(".glsl") { + println!("cargo:rerun-if-changed={}", path.display()); + glsl_files.push(path.to_owned()); + } + } + + let mut shader_file = File::create(shaders_file_path)?; + + writeln!(shader_file, "/// AUTO GENERATED BY build.rs\n")?; + writeln!(shader_file, "use std::collections::HashMap;\n")?; + writeln!(shader_file, "use webrender_build::shader::ShaderVersion;\n")?; + writeln!(shader_file, "pub struct SourceWithDigest {{")?; + writeln!(shader_file, " pub source: &'static str,")?; + writeln!(shader_file, " pub digest: &'static str,")?; + writeln!(shader_file, "}}\n")?; + writeln!(shader_file, "pub struct OptimizedSourceWithDigest {{")?; + writeln!(shader_file, " pub vert_source: &'static str,")?; + writeln!(shader_file, " pub frag_source: &'static str,")?; + writeln!(shader_file, " pub digest: &'static str,")?; + writeln!(shader_file, "}}\n")?; + writeln!(shader_file, "lazy_static! {{")?; + + write_unoptimized_shaders(glsl_files, &mut shader_file)?; + writeln!(shader_file, "")?; + write_optimized_shaders(&res_dir, &mut shader_file, &out_dir)?; + writeln!(shader_file, "}}")?; + + Ok(()) +} diff --git a/third_party/webrender/webrender/doc/CLIPPING_AND_POSITIONING.md b/third_party/webrender/webrender/doc/CLIPPING_AND_POSITIONING.md new file mode 100644 index 00000000000..4aa8d0c6848 --- /dev/null +++ b/third_party/webrender/webrender/doc/CLIPPING_AND_POSITIONING.md @@ -0,0 +1,150 @@ +# Original Design + +To understand the current design for clipping and positioning (transformations +and scrolling) in WebRender it can be useful to have a little background about +the original design for these features. The most important thing to remember is +that originally clipping, scrolling regions, and transformations were +properties of stacking contexts and they were completely _hierarchical_. This +goes a long way toward representing the majority of CSS content on the web, but +fails when dealing with important edges cases and features including: + 1. Support for sticky positioned content + 2. Scrolling areas that include content that is ordered both above and below + intersecting content from outside the scroll area. + 3. Items in the same scrolling root, clipped by different clips one or more of + which are defined outside the scrolling root itself. + 4. Completely non-hierarchical clipping situations, such as when items are + clipped by some clips in the hierarchy, but not others. + +Design changes have been a step by step path from the original design to one +that can handle all CSS content. + +# Current Design + +All positioning and clipping is handled by the `SpatialTree`. The name is a +holdover from when this tree was a tree of `Layers` which handled both +positioning and clipping. Currently the `SpatialTree` holds: + 1. A hierarchical collection of `SpatialNodes`, with the final screen + transformation of each node depending on the relative transformation of the + node combined with the transformations of all of its ancestors. These nodes + are responsible for positioning display list items and clips. + 2. A collection of `ClipNodes` which specify a rectangular clip and, optionally, + a set of rounded rectangle clips and a masking image. + 3. A collection of `ClipChains`. Each `ClipChain` is a list of `ClipNode` + elements. Every display list item has an assigned `ClipChain` which + specifies what `ClipNodes` are applied to that item. + +The `SpatialNode` of each clip applied to an item is completely independent of +the `SpatialNode` applied to the item itself. + +One holdover from the previous design is that both `ClipNode` and `SpatialNodes` +have a parent node, which is either a `SpatialNode` or a `ClipNode`. From this +node WebRender can determine both a parent `ClipNode` and a parent `SpatialNode` +by finding the first ancestor of that type. This is handled by the +`DisplayListFlattener`. + +## `SpatialNode` +There are three types of `SpatialNodes`: + 1. Reference frames which are created when content needs to apply + transformation or perspective properties to display list items. Reference + frames establish a new coordinate system, so internally all coordinates on + display list items are relative to the reference frame origin. Later + any non-reference frame positioning nodes that display list items belong + to can adjust this position relative to the reference frame origin. + 2. Scrolling nodes are used to define scrolling areas. These nodes have scroll + offsets which are a 2D translation relative to ancestor nodes and, ultimately, + the reference frame origin. + 3. Sticky frames are responsible for implementing position:sticky behavior. + This is also an 2D translation. + +`SpatialNodes` are defined as items in the display list. After scene building +each node is traversed hierarchically during the `SpatialTree::update()` step. +Once reference frame transforms and relative offsets are calculated, a to screen +space transformation can be calculated for each `SpatialNode`. This transformation +is added the `TransformPalette` and becomes directly available to WebRender shaders. + +In addition to screen space transformation calculation, the `SpatialNode` tree +is divided up into _compatible coordinate systems_. These are coordinate systems +which differ only by 2D translations from their parent system. These compatible +coordinate systems may even cross reference frame boundaries. The goal here is +to allow the application clipping rectangles from different compatible +coordinate systems without generating mask images. + +## `ClipNode` + +Each clip node holds a clip rectangle along with an optional collection of +rounded clip rectangles and a mask image. The fact that `ClipNodes` all have a +clip rectangle is important because it means that all content clipped by a +clip node has a bounding rectangle, which can be converted into a bounding +screen space rectangle. This rectangle is called the _outer rectangle_ of the +clip. `ClipNodes` may also have an _inner rectangle_, which is an area within +the boundaries of the _outer rectangle_ that is completely unclipped. + +These rectangles are calculated during the `SpatialTree::update()` phase. In +addition, each `ClipNode` produces a template `ClipChainNode` used to build +the `ClipChains` which use that node. + +## `ClipChains` + +There are two ways that `ClipChains` are defined in WebRender. The first is +through using the API for manually specifying `ClipChains` via a parent +`ClipChain` and a list of `ClipNodes`. The second is through the hierarchy of a +`ClipNode` established by its parent node. Every `ClipNode` has a chain of +ancestor `SpatialNodes` and `ClipNodes`. The creation of a `ClipNode` +automatically defines a `ClipChain` for this hierarchy. This behavior is a +compatibility feature with the old completely hierarchical clipping architecture +and is still how Gecko and Servo create most of their `ClipChains`. These +hierarchical `ClipChains` are constructed during the `ClipNode::update()` step. + +During `ClipChain` construction, WebRender tries to eliminate clips that will +not affect rendering, by looking at the combined _outer rectangle_ and _inner +rectangle_ of a `ClipChain` and the _outer rectangle_ and _inner rectangle_ of +any `ClipNode` appended to the chain. An example of the goal of this process is +to avoid having to render a mask for a large rounded rectangle when the rest of +the clip chain constrains the content to an area completely inside that +rectangle. Avoiding mask rasterization in this case and others has large +performance impacts on WebRender. + +# Clipping and Positioning in the Display List + +Each non-structural WebRender display list item has + * A `SpatialId` of a `SpatialNode` for positioning + * A `ClipId` of a `ClipNode` or a `ClipChain` for clipping + * An item-specific rectangular clip rectangle + +The positioning node determines how that item is positioned. It's assumed that +the positioning node and the item are children of the same reference frame. The +clipping node determines how that item is clipped. This should be fully +independent of how the node is positioned and items can be clipped by any +`ClipChain` regardless of the reference frame of their member clips. Finally, +the item-specific clipping rectangle is applied directly to the item and should +never result in the creation of a clip mask itself. + +## Converting user-exposed `ClipId`/`SpatialId` to internal indices + +WebRender must access `ClipNodes` and `SpatialNodes` quite a bit when building +scenes and frames, so it tries to convert `ClipId`/`SpatialId`, which are already +per-pipeline indices, to global scene-wide indices. Internally this is a +conversion from `ClipId` into `ClipNodeIndex` or `ClipChainIndex`, and from +`SpatialId` into `SpatialNodeIndex`. In order to make this conversion cheaper, the +`DisplayListFlattner` assigns offsets for each pipeline and node type in the +scene-wide `SpatialTree`. + +Nodes are added to their respective arrays sequentially as the display list is +processed during scene building. When encountering an iframe, the +`DisplayListFlattener` must start processing the nodes for that iframe's +pipeline, meaning that nodes are now being added out of order to the node arrays +of the `SpatialTree`. In this case, the `SpatialTree` fills in the gaps in +the node arrays with placeholder nodes. + +# Hit Testing + +Hit testing is the responsibility of the `HitTester` data structure. This +structure copies information necessary for hit testing from the +`SpatialTree`. This is done so that hit testing can still take place while a +new `SpatialTree` is under construction. + +# Ideas for the Future +1. Expose the difference between `ClipId` and `ClipChainId` in the API. +2. Prevent having to duplicate the `SpatialTree` for hit testing. +3. Avoid having to create placeholder nodes in the `SpatialTree` while + processing iframes. diff --git a/third_party/webrender/webrender/doc/blob.md b/third_party/webrender/webrender/doc/blob.md new file mode 100644 index 00000000000..b910f6f76a3 --- /dev/null +++ b/third_party/webrender/webrender/doc/blob.md @@ -0,0 +1,43 @@ +# Blob images + +Blob image is fallback mechanism for webrender that Gecko uses to render primitives that aren't currently supported by webrender. The main idea is to provide webrender with a custom handler that can take arbitray drawing commands serialized as buffers of bytes (the blobs) and turn them into images that webrender internally will treat as regular images. + +At the API level, blob images are treated as other images. They are resources created and associated with image keys, and are used in the display list with regular image display items. + + +## Active area + +In order to support scrolling very large content, blob images don't necessarily have a finite size. They can grow in any direction. At any time they do have an "active area", also called "visible area" which defines the portion that has to be rasterized. Typically this active area moves along large blob images depending on the scroll position. +The coordinate system of active area the *should* be the one of the blob's drawing commands (this is really up to the blob handler implementation to enforce that, Gecko does), and its scale should correspond to device pixels. The active area's coordinates can be negative. + +As far as positioning goes, the active area maps to the image display item's bounds. In other words the content at the top-left corner of the active area will be rendered on screen at the position of the top-left corner of the display item's local rect. + +In Gecko, the active area corresponds to the intersection of the fallback content's rect and the displayport. + +The terms "visible area" and "visible rect" are used a lot in the blobs code, unfortunately they collide with frame building's visibility/culling terminology. They don't correspond to what is visible to the user, but rather what is in the displayport. + + +## Tiling + +Blob images can be either tiled or non-tiled. Non-tiled blob images support invalid rects while tiled blob images track only validty at the tile level. In gecko all blobs are tiled with a tile size of 256x256. + +Just like regular tiled images, blob image tiles along the border of the image are shrinked to fit the remaining size. The only difference is that the tiling pattern always starts at the top-left corner for regular images (smaller boundary tiles only along the right and bottom edges), while it can be aribtrarily positioned for blob images (smaller boundary tiles potentially on all sides). + +The tiling logic is in webrender/src/image.rs. + + +## Async rasterization + +Blobs are typically too slow to rasterize on the critical path. We try to avoid blocking frame building on blob image rasterization. In order to do that we rasterize blobs as part of scene building. Rather than rasterize tiles on demand from visibility informating, we rasterize the entire active area during scene building. This means we potentially process a lot more content than will be displayed if the user doesn't scroll through all of the visible area. + +When the render backend receives a transaction, it looks for all new and update blob images, and generate blob rasterization requests for all tiles of the blob images that overlap their active area. The requests are bundled with an `AsyncBlobImageRasterizer` object in the transaction that is sent to the scene builder thread. The async rasterizer is created by the `BlobImageHandler` at each transaction. It is a snapshot of the state of the blobs as well as external information such as fonts, and does the actual rasterization. + +While tiles are rasterized eagerly during scene building, their content is uploaded lazily to the texture cache depending on the result of the visibility pass during frame building. + + +## Late rasterization + +In some case we run into a missing blob image during frame building and have to rasterize it synchronously. This happens when a rasterized tile is uploaded to the texture cache (at which point the CPU side is discarded), the texture cache entry expires and after scrolling back into view the tile is needed again. +We should really keep the rasterized blobs around just like we keep regular images in the cache. Hopefully this section will become obsolete eventually and we'll be able to remove late blob rasterization. + +The information needed for async rasterization corresponds to the state of blobs before scene building while late rasterization needs the state of blobs after the last complete scene build. This means we have to be careful about which version we manipulate in the resource cache. diff --git a/third_party/webrender/webrender/doc/swizzling.md b/third_party/webrender/webrender/doc/swizzling.md new file mode 100644 index 00000000000..4b38791940e --- /dev/null +++ b/third_party/webrender/webrender/doc/swizzling.md @@ -0,0 +1,31 @@ +> It'd be great to have some (in-tree) docs describing the process you've worked through here, the overall motivation, how it works on different GPUs / platforms etc. Perhaps as a follow up? + +# Swizzling in WR + +## Problem statement + +The goal is to avoid the CPU conversion of data done by the driver on texture data uploads. It's slow and always done synchronously, hurting our "Renderer" thread CPU utilization. + +Gecko produces all of the image data in BGRA. Switching "imagelib" to RGBA is possible, but modifying Skia to follow is less trivial. +OpenGL support for BGRA8 as an internal texture format is a complex story: it's officially supported in Angle and a fair share of Android devices, but it's not available on the desktop GL (and until recently wasn't available in the Android emulator). Unofficially, when textures are initialized with `glTexImage` (as opposed to `glTexStorage`) with RGBA internal format, the desktop GL drivers often prefer to store the data in BGRA8 format, actually. + +The only way to avoid the CPU conversion is to provide the data in exactly the same format that the driver is using internally for a texture. In this case, the driver does a straght `memcpy` into its CPU-visible memory, which is the best we can hope for with OpenGL API. + +## Solution: swizzling + +https://phabricator.services.mozilla.com/D21965 is providing the solution to this problem. The main principles are: + + 1. Use `glTexStorage` whenever it's available. Doing so gives us full control of the internal format, also allows to avoid allocating memory for mipmaps that we don't use. + 2. Make the shared texture cache format to be determined at the init time, based on the GL device capabilities. For Angle and OpenGL ES this is BGRA8, for desktop this is RGBA8 (since desktop GL doesn't support BGRA internal formats). WebRender is now able to tell Gecko, which color format it prefers the texture data to use. + 3. If the data comes in a format that is different from our best case, we pretend that the data is actually in our best case format, and associate the allocated cache entry with the `Swizzle`. That swizzle configuration changes the way shaders sample from a texture, adjusting for the fact the data was provided in a different format. + 4. The lifetime of a "swizzled" texture cache data is starting at the point the data is uploaded and ending at a point where any shader samples from this data. Any other operation on that data (copying or blitting) is not configurable by `Swizzle` and thus would produce incorrect results. To address this, the change enhances `cs_copy` shader to be used in place of blitting from the texture cache, where needed. + 5. Swizzling becomes a part of the batch key per texture. Mixing up draw calls with texture data that is differently swizzled then introduces the batch breaks. This is a downside for the swizzling approach in general, but it's not clear to what extent this would affect Gecko. + +## Code paths + +Windows/Angle and Android: + - we use `glTexStorage` with BGRA8 internal format, no swizzling is needed in general case. + +Windows (non-Angle), Mac, Linux: + - if `glTexStorage` is available, we use it with RGBA8 internal format, swizzling everything on texture sampling. + - otherwise, we use RGBA unsized format with `gTexImage` and expect the data to come in BGRA format, no swizzling is involved. diff --git a/third_party/webrender/webrender/doc/text-rendering.md b/third_party/webrender/webrender/doc/text-rendering.md new file mode 100644 index 00000000000..b965562b99a --- /dev/null +++ b/third_party/webrender/webrender/doc/text-rendering.md @@ -0,0 +1,720 @@ +# Text Rendering + +This document describes the details of how WebRender renders text, particularly the blending stage of text rendering. +We will go into grayscale text blending, subpixel text blending, and "subpixel text with background color" blending. + +### Prerequisites + +The description below assumes you're familiar with regular rgba compositing, operator over, +and the concept of premultiplied alpha. + +### Not covered in this document + +We are going to treat the origin of the text mask as a black box. +We're also going to assume we can blend text in the device color space and will not go into the gamma correction and linear pre-blending that happens in some of the backends that produce the text masks. + +## Grayscale Text Blending + +Grayscale text blending is the simplest form of text blending. Our blending function has three inputs: + + - The text color, as a premultiplied rgba color. + - The text mask, as a single-channel alpha texture. + - The existing contents of the framebuffer that we're rendering to, the "destination". This is also a premultiplied rgba buffer. + +Note: The word "grayscale" here does *not* mean that we can only draw gray text. +It means that the mask only has a single alpha value per pixel, so we can visualize +the mask in our minds as a grayscale image. + +### Deriving the math + +We want to mask our text color using the single-channel mask, and composite that to the destination. +This compositing step uses operator "over", just like regular compositing of rgba images. + +I'll be using GLSL syntax to describe the blend equations, but please consider most of the code below pseudocode. + +We can express the blending described above as the following blend equation: + +```glsl +vec4 textblend(vec4 text_color, vec4 mask, vec4 dest) { + return over(in(text_color, mask), dest); +} +``` + +with `over` being the blend function for (premultiplied) operator "over": + +```glsl +vec4 over(vec4 src, vec4 dest) { + return src + (1.0 - src.a) * dest; +} +``` + +and `in` being the blend function for (premultiplied) operator "in", i.e. the masking operator: + +```glsl +vec4 in(vec4 src, vec4 mask) { + return src * mask.a; +} +``` + +So the complete blending function is: + +```glsl +result.r = text_color.r * mask.a + (1.0 - text_color.a * mask.a) * dest.r; +result.g = text_color.g * mask.a + (1.0 - text_color.a * mask.a) * dest.g; +result.b = text_color.b * mask.a + (1.0 - text_color.a * mask.a) * dest.b; +result.a = text_color.a * mask.a + (1.0 - text_color.a * mask.a) * dest.a; +``` + +### Rendering this with OpenGL + +In general, a fragment shader does not have access to the destination. +So the full blend equation needs to be expressed in a way that the shader only computes values that are independent of the destination, +and the parts of the equation that use the destination values need to be applied by the OpenGL blend pipeline itself. +The OpenGL blend pipeline can be tweaked using the functions `glBlendEquation` and `glBlendFunc`. + +In our example, the fragment shader can output just `text_color * mask.a`: + +```glsl + oFragColor = text_color * mask.a; +``` + +and the OpenGL blend pipeline can be configured like so: + +```rust + pub fn set_blend_mode_premultiplied_alpha(&self) { + self.gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC_ALPHA); + self.gl.blend_equation(gl::FUNC_ADD); + } +``` + +This results in an overall blend equation of + +``` +result.r = 1 * oFragColor.r + (1 - oFragColor.a) * dest.r; + ^ ^ ^^^^^^^^^^^^^^^^^ + | | | + +--gl::ONE | +-- gl::ONE_MINUS_SRC_ALPHA + | + +-- gl::FUNC_ADD + + = 1 * (text_color.r * mask.a) + (1 - (text_color.a * mask.a)) * dest.r + = text_color.r * mask.a + (1 - text_color.a * mask.a) * dest.r +``` + +which is exactly what we wanted. + +### Differences to the actual WebRender code + +There are two minor differences between the shader code above and the actual code in the text run shader in WebRender: + +```glsl +oFragColor = text_color * mask.a; // (shown above) +// vs. +oFragColor = vColor * mask * alpha; // (actual webrender code) +``` + +`vColor` is set to the text color. The differences are: + + - WebRender multiplies with all components of `mask` instead of just with `mask.a`. + However, our font rasterization code fills the rgb values of `mask` with the value of `mask.a`, + so this is completely equivalent. + - WebRender applies another alpha to the text. This is coming from the clip. + You can think of this alpha to be a pre-adjustment of the text color for that pixel, or as an + additional mask that gets applied to the mask. + +## Subpixel Text Blending + +Now that we have the blend equation for single-channel text blending, we can look at subpixel text blending. + +The main difference between subpixel text blending and grayscale text blending is the fact that, +for subpixel text, the text mask contains a separate alpha value for each color component. + +### Component alpha + +Regular painting uses four values per pixel: three color values, and one alpha value. The alpha value applies to all components of the pixel equally. + +Imagine for a second a world in which you have *three alpha values per pixel*, one for each color component. + + - Old world: Each pixel has four values: `color.r`, `color.g`, `color.b`, and `color.a`. + - New world: Each pixel has *six* values: `color.r`, `color.a_r`, `color.g`, `color.a_g`, `color.b`, and `color.a_b`. + +In such a world we can define a component-alpha-aware operator "over": + +```glsl +vec6 over_comp(vec6 src, vec6 dest) { + vec6 result; + result.r = src.r + (1.0 - src.a_r) * dest.r; + result.g = src.g + (1.0 - src.a_g) * dest.g; + result.b = src.b + (1.0 - src.a_b) * dest.b; + result.a_r = src.a_r + (1.0 - src.a_r) * dest.a_r; + result.a_g = src.a_g + (1.0 - src.a_g) * dest.a_g; + result.a_b = src.a_b + (1.0 - src.a_b) * dest.a_b; + return result; +} +``` + +and a component-alpha-aware operator "in": + +```glsl +vec6 in_comp(vec6 src, vec6 mask) { + vec6 result; + result.r = src.r * mask.a_r; + result.g = src.g * mask.a_g; + result.b = src.b * mask.a_b; + result.a_r = src.a_r * mask.a_r; + result.a_g = src.a_g * mask.a_g; + result.a_b = src.a_b * mask.a_b; + return result; +} +``` + +and even a component-alpha-aware version of `textblend`: + +```glsl +vec6 textblend_comp(vec6 text_color, vec6 mask, vec6 dest) { + return over_comp(in_comp(text_color, mask), dest); +} +``` + +This results in the following set of equations: + +```glsl +result.r = text_color.r * mask.a_r + (1.0 - text_color.a_r * mask.a_r) * dest.r; +result.g = text_color.g * mask.a_g + (1.0 - text_color.a_g * mask.a_g) * dest.g; +result.b = text_color.b * mask.a_b + (1.0 - text_color.a_b * mask.a_b) * dest.b; +result.a_r = text_color.a_r * mask.a_r + (1.0 - text_color.a_r * mask.a_r) * dest.a_r; +result.a_g = text_color.a_g * mask.a_g + (1.0 - text_color.a_g * mask.a_g) * dest.a_g; +result.a_b = text_color.a_b * mask.a_b + (1.0 - text_color.a_b * mask.a_b) * dest.a_b; +``` + +### Back to the real world + +If we want to transfer the component alpha blend equation into the real world, we need to make a few small changes: + + - Our text color only needs one alpha value. + So we'll replace all instances of `text_color.a_r/g/b` with `text_color.a`. + - We're currently not making use of the mask's `r`, `g` and `b` values, only of the `a_r`, `a_g` and `a_b` values. + So in the real world, we can use the rgb channels of `mask` to store those component alphas and + replace `mask.a_r/g/b` with `mask.r/g/b`. + +These two changes give us: + +```glsl +result.r = text_color.r * mask.r + (1.0 - text_color.a * mask.r) * dest.r; +result.g = text_color.g * mask.g + (1.0 - text_color.a * mask.g) * dest.g; +result.b = text_color.b * mask.b + (1.0 - text_color.a * mask.b) * dest.b; +result.a_r = text_color.a * mask.r + (1.0 - text_color.a * mask.r) * dest.a_r; +result.a_g = text_color.a * mask.g + (1.0 - text_color.a * mask.g) * dest.a_g; +result.a_b = text_color.a * mask.b + (1.0 - text_color.a * mask.b) * dest.a_b; +``` + +There's a third change we need to make: + + - We're rendering to a destination surface that only has one alpha channel instead of three. + So `dest.a_r/g/b` and `result.a_r/g/b` will need to become `dest.a` and `result.a`. + +This creates a problem: We're currently assigning different values to `result.a_r`, `result.a_g` and `result.a_b`. +Which of them should we use to compute `result.a`? + +This question does not have an answer. One alpha value per pixel is simply not sufficient +to express the same information as three alpha values. + +However, see what happens if the destination is already opaque: + +We have `dest.a_r == 1`, `dest.a_g == 1`, and `dest.a_b == 1`. + +``` +result.a_r = text_color.a * mask.r + (1 - text_color.a * mask.r) * dest.a_r + = text_color.a * mask.r + (1 - text_color.a * mask.r) * 1 + = text_color.a * mask.r + 1 - text_color.a * mask.r + = 1 +same for result.a_g and result.a_b +``` + +In other words, for opaque destinations, it doesn't matter what which channel of the mask we use when computing `result.a`, the result will always be completely opaque anyways. In WebRender we just pick `mask.g` (or rather, +have font rasterization set `mask.a` to the value of `mask.g`) because it's as good as any. + +The takeaway here is: **Subpixel text blending is only supported for opaque destinations.** Attempting to render subpixel +text into partially transparent destinations will result in bad alpha values. Or rather, it will result in alpha values which +are not anticipated by the r, g, and b values in the same pixel, so that subsequent blend operations, which will mix r and a values +from the same pixel, will produce incorrect colors. + +Here's the final subpixel blend function: + +```glsl +vec4 subpixeltextblend(vec4 text_color, vec4 mask, vec4 dest) { + vec4 result; + result.r = text_color.r * mask.r + (1.0 - text_color.a * mask.r) * dest.r; + result.g = text_color.g * mask.g + (1.0 - text_color.a * mask.g) * dest.g; + result.b = text_color.b * mask.b + (1.0 - text_color.a * mask.b) * dest.b; + result.a = text_color.a * mask.a + (1.0 - text_color.a * mask.a) * dest.a; + return result; +} +``` + +or for short: + +```glsl +vec4 subpixeltextblend(vec4 text_color, vec4 mask, vec4 dest) { + return text_color * mask + (1.0 - text_color.a * mask) * dest; +} +``` + +To recap, here's what we gained and lost by making the transition from the full-component-alpha world to the +regular rgba world: All colors and textures now only need four values to be represented, we still use a +component alpha mask, and the results are equivalent to the full-component-alpha result assuming that the +destination is opaque. We lost the ability to draw to partially transparent destinations. + +### Making this work in OpenGL + +We have the complete subpixel blend function. +Now we need to cut it into pieces and mix it with the OpenGL blend pipeline in such a way that +the fragment shader does not need to know about the destination. + +Compare the equation for the red channel and the alpha channel between the two ways of text blending: + +``` + single-channel alpha: + result.r = text_color.r * mask.a + (1.0 - text_color.a * mask.a) * dest.r + result.a = text_color.a * mask.a + (1.0 - text_color.a * mask.a) * dest.r + + component alpha: + result.r = text_color.r * mask.r + (1.0 - text_color.a * mask.r) * dest.r + result.a = text_color.a * mask.a + (1.0 - text_color.a * mask.a) * dest.r +``` + +Notably, in the single-channel alpha case, all three destination color channels are multiplied with the same thing: +`(1.0 - text_color.a * mask.a)`. This factor also happens to be "one minus `oFragColor.a`". +So we were able to take advantage of OpenGL's `ONE_MINUS_SRC_ALPHA` blend func. + +In the component alpha case, we're not so lucky: Each destination color channel +is multiplied with a different factor. We can use `ONE_MINUS_SRC_COLOR` instead, +and output `text_color.a * mask` from our fragment shader. +But then there's still the problem that the first summand of the computation for `result.r` uses +`text_color.r * mask.r` and the second summand uses `text_color.a * mask.r`. + +There are multiple ways to deal with this. They are: + + 1. Making use of `glBlendColor` and the `GL_CONSTANT_COLOR` blend func. + 2. Using a two-pass method. + 3. Using "dual source blending". + +Let's look at them in order. + +#### 1. Subpixel text blending in OpenGL using `glBlendColor` + +In this approach we return `text_color.a * mask` from the shader. +Then we set the blend color to `text_color / text_color.a` and use `GL_CONSTANT_COLOR` as the source blendfunc. +This results in the following blend equation: + +``` +result.r = (text_color.r / text_color.a) * oFragColor.r + (1 - oFragColor.r) * dest.r; + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ ^^^^^^^^^^^^^^^^^ + | | | + +--gl::CONSTANT_COLOR | +-- gl::ONE_MINUS_SRC_COLOR + | + +-- gl::FUNC_ADD + + = (text_color.r / text_color.a) * (text_color.a * mask.r) + (1 - (text_color.a * mask.r)) * dest.r + = text_color.r * mask.r + (1 - text_color.a * mask.r) * dest.r +``` + +At the very beginning of this document, we defined `text_color` as the *premultiplied* text color. +So instead of actually doing the calculation `text_color.r / text_color.a` when specifying the blend color, +we really just want to use the *unpremultiplied* text color in that place. +That's usually the representation we start with anyway. + +#### 2. Two-pass subpixel blending in OpenGL + +The `glBlendColor` method has the disadvantage that the text color is part of the OpenGL state. +So if we want to draw text with different colors, we have two use separate batches / draw calls +to draw the differently-colored parts of text. + +Alternatively, we can use a two-pass method which avoids the need to use the `GL_CONSTANT_COLOR` blend func: + + - The first pass outputs `text_color.a * mask` from the fragment shader and + uses `gl::ZERO, gl::ONE_MINUS_SRC_COLOR` as the glBlendFuncs. This achieves: + +``` +oFragColor = text_color.a * mask; + +result_after_pass0.r = 0 * oFragColor.r + (1 - oFragColor.r) * dest.r + = (1 - text_color.a * mask.r) * dest.r + +result_after_pass0.g = 0 * oFragColor.g + (1 - oFragColor.g) * dest.r + = (1 - text_color.a * mask.r) * dest.r + +... +``` + + - The second pass outputs `text_color * mask` from the fragment shader and uses + `gl::ONE, gl::ONE` as the glBlendFuncs. This results in the correct overall blend equation. + +``` +oFragColor = text_color * mask; + +result_after_pass1.r + = 1 * oFragColor.r + 1 * result_after_pass0.r + = text_color.r * mask.r + result_after_pass0.r + = text_color.r * mask.r + (1 - text_color.a * mask.r) * dest.r +``` + +#### 3. Dual source subpixel blending in OpenGL + +The third approach is similar to the second approach, but makes use of the [`ARB_blend_func_extended`](https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_blend_func_extended.txt) extension +in order to fold the two passes into one: +Instead of outputting the two different colors in two separate passes, we output them from the same pass, +as two separate fragment shader outputs. +Those outputs can then be treated as two different sources in the blend equation. + +## Subpixel Text Rendering to Transparent Destinations with a Background Color Hint + +### Motivation + +As we've seen in the previous section, subpixel text drawing has the limitation that it only works on opaque destinations. + +In other words, if you use the `subpixeltextblend` function to draw something to a transparent surface, +and then composite that surface onto on opaque background, +the result will generally be different from drawing the text directly onto the opaque background. + +Let's express that inequality in code. + +``` + - vec4 text_color + - vec4 mask + - vec4 transparency = vec4(0.0, 0.0, 0.0, 0.0) + - vec4 background with background.a == 1.0 + +over(subpixeltextblend(text_color, mask, transparency), background).rgb + is, in general, not equal to +subpixeltextblend(text_color, mask, background).rgb +``` + +However, one interesting observation is that if the background is black, the two *are* equal: + +``` +vec4 black = vec4(0.0, 0.0, 0.0, 1.0); + +over(subpixeltextblend(text_color, mask, transparency), black).r + = subpixeltextblend(text_color, mask, transparency).r + + (1 - subpixeltextblend(text_color, mask, transparency).a) * black.r + = subpixeltextblend(text_color, mask, transparency).r + + (1 - subpixeltextblend(text_color, mask, transparency).a) * 0 + = subpixeltextblend(text_color, mask, transparency).r + = text_color.r * mask.r + (1 - text_color.a * mask.r) * transparency.r + = text_color.r * mask.r + (1 - text_color.a * mask.r) * 0 + = text_color.r * mask.r + (1 - text_color.a * mask.r) * black.r + = subpixeltextblend(text_color, mask, black).r +``` + +So it works out for black backgrounds. The further your *actual* background color gets away from black, +the more incorrect your result will be. + +If it works for black, is there a way to make it work for other colors? +This is the motivating question for this third way of text blending: + +We want to be able to specify an *estimated background color*, and have a blending function +`vec4 subpixeltextblend_withbgcolor(vec4 text_color, vec4 mask, vec4 bg_color, vec4 dest)`, +in such a way that the error we get by using an intermediate surface is somehow in relation +to the error we made when estimating the background color. In particular, if we estimated +the background color perfectly, we want the intermediate surface to go unnoticed. + +Expressed as code: + +``` +over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, transparency), bg_color) + should always be equal to +subpixeltextblend(text_color, mask, bg_color) +``` + +This is one of three constraints we'd like `subpixeltextblend_withbgcolor` to satisfy. + +The next constraint is the following: If `dest` is already opaque, `subpixeltextblend_withbgcolor` +should have the same results as `subpixeltextblend`, and the background color hint should be ignored. + +``` + If dest.a == 1.0, +subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest) + should always be equal to +subpixeltextblend(text_color, mask, dest) +``` + +And there's a third condition we'd like it to fulfill: +In places where the mask is zero, the destination should be unaffected. + +``` +subpixeltextblend_withbgcolor(text_color, transparency, bg_color, dest) + should always be equal to +dest +``` + +### Use cases + +The primary use case for such a blend method is text on top of vibrant areas of a window on macOS. + +Vibrant backgrounds with behind-window blending are computed by the window server, and they are tinted +in a color that's based on the chosen vibrancy type. + +The window's rgba buffer is transparent in the vibrant areas. Window contents, even text, are drawn onto +that transparent rgba buffer. Then the window server composites the window onto an opaque backdrop. +So the results on the screen are computed as follows: + +```glsl +window_buffer_pixel = subpixeltextblend_withbgcolor(text_color, mask, bg_color, transparency); +screen_pixel = over(window_buffer_pixel, window_backdrop); +``` + +### Prior art + +Apple has implemented such a method of text blending in CoreGraphics, specifically for rendering text onto vibrant backgrounds. +It's hidden behind the private API `CGContextSetFontSmoothingBackgroundColor` and is called by AppKit internally before +calling the `-[NSView drawRect:]` method of your `NSVisualEffectView`, with the appropriate font smoothing background color +for the vibrancy type of that view. + +I'm not aware of any public documentation of this way of text blending. +It seems to be considered an implementation detail by Apple, and is probably hidden by default because it can be a footgun: +If the font smoothing background color you specify is very different from the actual background that our surface is placed +on top of, the text will look glitchy. + +### Deriving the blending function from first principles + +Before we dive into the math, let's repeat our goal once more. + +We want to create a blending function of the form +`vec4 subpixeltextblend_withbgcolor(vec4 text_color, vec4 mask, vec4 bg_color, vec4 dest)` +(with `bg_color` being an opaque color) +which satisfies the following three constraints: + +``` +Constraint I: + over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, transparency), bg_color) + should always be equal to + subpixeltextblend(text_color, mask, bg_color) + +Constraint II: + If dest.a == 1.0, + subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest) + should always be equal to + subpixeltextblend(text_color, mask, dest) + +Constraint II: + subpixeltextblend_withbgcolor(text_color, transparency, bg_color, dest) + should always be equal to + dest +``` + +Constraint I and constraint II are about what happens depending on the destination's alpha. +In particular: If the destination is completely transparent, we should blend into the +estimated background color, and if it's completely opaque, we should blend into the destination color. +In fact, we really want to blend into `over(dest, bg_color)`: we want `bg_color` to be used +as a backdrop *behind* the current destination. So let's combine constraints I and II into a new +constraint IV: + +``` +Constraint IV: + over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest), bg_color) + should always be equal to + subpixeltextblend(text_color, mask, over(dest, bg_color)) +``` + +Let's look at just the left side of that equation and rejiggle it a bit: + +``` +over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest), bg_color).r + = subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).r + + (1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r + +<=> + +over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest), bg_color).r - +(1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r + = subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).r +``` + +Now insert the right side of constraint IV: + +``` +subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).r + = over(subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest), bg_color).r - + (1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r + = subpixeltextblend(text_color, mask, over(dest, bg_color)).r - + (1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r +``` + +Our blend function is almost finished. We just need select an alpha for our result. +Constraints I, II and IV don't really care about the alpha value. But constraint III requires that: + +``` + subpixeltextblend_withbgcolor(text_color, transparency, bg_color, dest).a + should always be equal to + dest.a +``` + +so the computation of the alpha value somehow needs to take into account the mask. + +Let's say we have an unknown function `make_alpha(text_color.a, mask)` which returns +a number between 0 and 1 and which is 0 if the mask is entirely zero, and let's defer +the actual implementation of that function until later. + +Now we can define the alpha of our overall function using the `over` function: + +``` +subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a + := make_alpha(text_color.a, mask) + (1 - make_alpha(text_color.a, mask)) * dest.a +``` + +We can plug this in to our previous result: + +``` +subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).r + = subpixeltextblend(text_color, mask, over(dest, bg_color)).r + - (1 - subpixeltextblend_withbgcolor(text_color, mask, bg_color, dest).a) * bg_color.r + = subpixeltextblend(text_color, mask, over(dest, bg_color)).r + - (1 - (make_alpha(text_color.a, mask) + + (1 - make_alpha(text_color.a, mask)) * dest.a)) * bg_color.r + = text_color.r * mask.r + (1 - text_color.a * mask.r) * over(dest, bg_color).r + - (1 - (make_alpha(text_color.a, mask) + + (1 - make_alpha(text_color.a, mask)) * dest.a)) * bg_color.r + = text_color.r * mask.r + + (1 - text_color.a * mask.r) * (dest.r + (1 - dest.a) * bg_color.r) + - (1 - (make_alpha(text_color.a, mask) + + (1 - make_alpha(text_color.a, mask)) * dest.a)) * bg_color.r + = text_color.r * mask.r + + (1 - text_color.a * mask.r) * (dest.r + (1 - dest.a) * bg_color.r) + - (1 - (make_alpha(text_color.a, mask) + + (1 - make_alpha(text_color.a, mask)) * dest.a)) * bg_color.r + = text_color.r * mask.r + + (dest.r + (1 - dest.a) * bg_color.r) + - (text_color.a * mask.r) * (dest.r + (1 - dest.a) * bg_color.r) + - (1 - make_alpha(text_color.a, mask) + - (1 - make_alpha(text_color.a, mask)) * dest.a) * bg_color.r + = text_color.r * mask.r + + dest.r + (1 - dest.a) * bg_color.r + - text_color.a * mask.r * dest.r + - text_color.a * mask.r * (1 - dest.a) * bg_color.r + - (1 - make_alpha(text_color.a, mask) + - (1 - make_alpha(text_color.a, mask)) * dest.a) * bg_color.r + = text_color.r * mask.r + + dest.r + (1 - dest.a) * bg_color.r + - text_color.a * mask.r * dest.r + - text_color.a * mask.r * (1 - dest.a) * bg_color.r + - ((1 - make_alpha(text_color.a, mask)) * 1 + - (1 - make_alpha(text_color.a, mask)) * dest.a) * bg_color.r + = text_color.r * mask.r + + dest.r + (1 - dest.a) * bg_color.r + - text_color.a * mask.r * dest.r + - text_color.a * mask.r * (1 - dest.a) * bg_color.r + - ((1 - make_alpha(text_color.a, mask)) * (1 - dest.a)) * bg_color.r + = text_color.r * mask.r + + dest.r - text_color.a * mask.r * dest.r + + (1 - dest.a) * bg_color.r + - text_color.a * mask.r * (1 - dest.a) * bg_color.r + - (1 - make_alpha(text_color.a, mask)) * (1 - dest.a) * bg_color.r + = text_color.r * mask.r + + (1 - text_color.a * mask.r) * dest.r + + (1 - dest.a) * bg_color.r + - text_color.a * mask.r * (1 - dest.a) * bg_color.r + - (1 - make_alpha(text_color.a, mask)) * (1 - dest.a) * bg_color.r + = text_color.r * mask.r + + (1 - text_color.a * mask.r) * dest.r + + (1 - text_color.a * mask.r) * (1 - dest.a) * bg_color.r + - (1 - make_alpha(text_color.a, mask)) * (1 - dest.a) * bg_color.r + = text_color.r * mask.r + + (1 - text_color.a * mask.r) * dest.r + + ((1 - text_color.a * mask.r) + - (1 - make_alpha(text_color.a, mask))) * (1 - dest.a) * bg_color.r + = text_color.r * mask.r + + (1 - text_color.a * mask.r) * dest.r + + (1 - text_color.a * mask.r + - 1 + make_alpha(text_color.a, mask)) * (1 - dest.a) * bg_color.r + = text_color.r * mask.r + + (1 - text_color.a * mask.r) * dest.r + + (make_alpha(text_color.a, mask) - text_color.a * mask.r) * (1 - dest.a) * bg_color.r +``` + +We now have a term of the form `A + B + C`, with `A` and `B` being guaranteed to +be between zero and one. + +We also want `C` to be between zero and one. +We can use this restriction to help us decide on an implementation of `make_alpha`. + +If we define `make_alpha` as + +```glsl +float make_alpha(text_color_a, mask) { + float max_rgb = max(max(mask.r, mask.g), mask.b); + return text_color_a * max_rgb; +} +``` + +, then `(make_alpha(text_color.a, mask) - text_color.a * mask.r)` becomes +`(text_color.a * max(max(mask.r, mask.g), mask.b) - text_color.a * mask.r)`, which is +`text_color.a * (max(max(mask.r, mask.g), mask.b) - mask.r)`, and the subtraction will +always yield something that's greater or equal to zero for r, g, and b, +because we will subtract each channel from the maximum of the channels. + +Putting this all together, we have: + +```glsl +vec4 subpixeltextblend_withbgcolor(vec4 text_color, vec4 mask, vec4 bg_color, vec4 dest) { + float max_rgb = max(max(mask.r, mask.g), mask.b); + vec4 result; + result.r = text_color.r * mask.r + (1 - text_color.a * mask.r) * dest.r + + text_color.a * bg_color.r * (max_rgb - mask.r) * (1 - dest.a); + result.g = text_color.g * mask.g + (1 - text_color.a * mask.g) * dest.g + + text_color.a * bg_color.g * (max_rgb - mask.g) * (1 - dest.a); + result.b = text_color.b * mask.b + (1 - text_color.a * mask.b) * dest.b + + text_color.a * bg_color.b * (max_rgb - mask.b) * (1 - dest.a); + result.a = text_color.a * max_rgb + (1 - text_color.a * max_rgb) * dest.a; + return result; +} +``` + +This is the final form of this blend function. It satisfies all of the four constraints. + +### Implementing it with OpenGL + +Our color channel equations consist of three pieces: + + - `text_color.r * mask.r`, which simply gets added to the rest. + - `(1 - text_color.a * mask.r) * dest.r`, a factor which gets multiplied with the destination color. + - `text_color.a * bg_color.r * (max_rgb - mask.r) * (1 - dest.a)`, a factor which gets multiplied + with "one minus destination alpha". + +We will need three passes. Each pass modifies the color channels in the destination. +This means that the part that uses `dest.r` needs to be applied first. +Then we can apply the part that uses `1 - dest.a`. +(This means that the first pass needs to leave `dest.a` untouched.) +And the final pass can apply the `result.a` equation and modify `dest.a`. + +``` +pub fn set_blend_mode_subpixel_with_bg_color_pass0(&self) { + self.gl.blend_func_separate(gl::ZERO, gl::ONE_MINUS_SRC_COLOR, gl::ZERO, gl::ONE); +} +pub fn set_blend_mode_subpixel_with_bg_color_pass1(&self) { + self.gl.blend_func_separate(gl::ONE_MINUS_DST_ALPHA, gl::ONE, gl::ZERO, gl::ONE); +} +pub fn set_blend_mode_subpixel_with_bg_color_pass2(&self) { + self.gl.blend_func_separate(gl::ONE, gl::ONE, gl::ONE, gl::ONE_MINUS_SRC_ALPHA); +} + +Pass0: + oFragColor = vec4(text.color.a) * mask; +Pass1: + oFragColor = vec4(text.color.a) * text.bg_color * (vec4(mask.a) - mask); +Pass2: + oFragColor = text.color * mask; + +result_after_pass0.r = 0 * (text_color.a * mask.r) + (1 - text_color.a * mask.r) * dest.r +result_after_pass0.a = 0 * (text_color.a * mask.a) + 1 * dest.a + +result_after_pass1.r = (1 - result_after_pass0.a) * (text_color.a * (mask.max_rgb - mask.r) * bg_color.r) + 1 * result_after_pass0.r +result_after_pass1.a = 0 * (text_color.a * (mask.max_rgb - mask.a) * bg_color.a) + 1 * result_after_pass0.a + +result_after_pass2.r = 1 * (text_color.r * mask.r) + 1 * result_after_pass1.r +result_after_pass2.a = 1 * (text_color.a * mask.max_rgb) + (1 - text_color.a * mask.max_rgb) * result_after_pass1.a +``` + +Instead of computing `max_rgb` in the shader, we can just require the font rasterization code to fill +`mask.a` with the `max_rgb` value. + diff --git a/third_party/webrender/webrender/res/Proggy.ttf b/third_party/webrender/webrender/res/Proggy.ttf new file mode 100644 index 0000000000000000000000000000000000000000..308d3e1ac9b89f4866fdef05289aaf59411c1c17 GIT binary patch literal 5284 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/webrender/res/area-lut.tga b/third_party/webrender/webrender/res/area-lut.tga new file mode 100644 index 0000000000000000000000000000000000000000..5edcddc3d16058b22265792febf739529044b5a8 GIT binary patch literal 65580 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 k2`eyf$b)P2xI)Ao5-u@YW)t@E!`30x~ zuzdnx_%i{tuK)r81pM|PfZl;p1qeN;Er9TsXMh}j{>V`;-qHKy*e^ssWgmX^VCx^V zzwoR3qWjhFelq-e{Pz2A8oxDw{0IyL;0cHX$OK>n7)n4p0JVU!K>8R;9@sty3lf+W zfjJLk_{)ypzIagW&GlFB>*r7L5BO#NO6f15|E&E>>#x%JTLH)y5Pt%7{Q3w$0!RXg z__GXb2VfSEHK4u+HVeQ7upX?u1f=-0@q6#%;&0dQ_&xeM{$PG{?k6dKeEvLte*7T( zl>c(~9}6HCK;6GpfT;ka4k)$2dANX8?J%|BWHB>Ox4@w0!OzvR!$|7`xUe{ufN|ET~B1&AA<&;SGiumlhffFA-y1>`S6 zod9wzfJ=Kx$wzOVS@Pwx2iZTr{#o`l_v0_WDSm$XMfwl>FZ`+8IBP<=02%(=_(Sa(*)Q?FdiP1QpUi(6{v!U2{vE&D zzYKq2{t!UtKRy8xe@+1N5s(C^1@IO$Spc^{dJ9$x3ivGrWeboSe|+46KpZ0!%{e1oPd#7mr;`u}VQumj`AL!5G zUla2u0cQ7~;V(MC+yNBuM+OKXs5}E?OuGz=J*>Jzpa0E{e|^6_ZPFjuYcq6-<19-0EY&o zXaFMqMhL(xQ2G#%;V)qT;)66ieJ$`XZ}m@m&jj5{2%Avpa2#K zAfEvf{P_jQ9e^)@<8O5Q3H~JdmOcMQ>EG>t<@+D-H!J`-0%%kLya+8W0PvR?zq5ZN z|1|pXqnDWdqWY`*D&ziGeg^q#C4LG(IR}_p;4&7Vkb+|fumv#um5x7M|2Y39&cFKj zV+2ZG0n7mz0XBF5!2@{{N)NEW|291S#QT;uKM{VaesTTt^q<=QqW(3;|M~ro^H1@& z;sCS&0l(Ki<-Jek>))XK$M{nZ{FN>MVleQ>H;?e&wqS z|Ln6*LiEWsKW6@(%F*A04^Gp|4?cUuhadP8f8c)c-Mh*^ynSANDfw@L+TRP#J8#^$ zvrFcWKLD6NfMEdQ10X+v=Qr?d!1xUaK7zx~V219dub}W5T>pOkcH}E4e+=b+(ZkWhn!CfL6RK?1hF2JSkj_uxJQ`t00q!`-&+vt#d#7WjX8 CI}EP? literal 0 HcmV?d00001 diff --git a/third_party/webrender/webrender/res/base.glsl b/third_party/webrender/webrender/res/base.glsl new file mode 100644 index 00000000000..eb343c019b5 --- /dev/null +++ b/third_party/webrender/webrender/res/base.glsl @@ -0,0 +1,53 @@ +/* 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/. */ + +#if defined(GL_ES) + #if GL_ES == 1 + #ifdef GL_FRAGMENT_PRECISION_HIGH + precision highp sampler2DArray; + #else + precision mediump sampler2DArray; + #endif + + // Sampler default precision is lowp on mobile GPUs. + // This causes RGBA32F texture data to be clamped to 16 bit floats on some GPUs (e.g. Mali-T880). + // Define highp precision macro to allow lossless FLOAT texture sampling. + #define HIGHP_SAMPLER_FLOAT highp + + // Default int precision in GLES 3 is highp (32 bits) in vertex shaders + // and mediump (16 bits) in fragment shaders. If an int is being used as + // a texel address in a fragment shader it, and therefore requires > 16 + // bits, it must be qualified with this. + #define HIGHP_FS_ADDRESS highp + + // texelFetchOffset is buggy on some Android GPUs (see issue #1694). + // Fallback to texelFetch on mobile GPUs. + #define TEXEL_FETCH(sampler, position, lod, offset) texelFetch(sampler, position + offset, lod) + #else + #define HIGHP_SAMPLER_FLOAT + #define HIGHP_FS_ADDRESS + #define TEXEL_FETCH(sampler, position, lod, offset) texelFetchOffset(sampler, position, lod, offset) + #endif +#else + #define HIGHP_SAMPLER_FLOAT + #define HIGHP_FS_ADDRESS + #define TEXEL_FETCH(sampler, position, lod, offset) texelFetchOffset(sampler, position, lod, offset) +#endif + +#ifdef WR_VERTEX_SHADER + #ifdef SWGL + // Annotate a vertex attribute as being flat per each drawn primitive instance. + // SWGL can use this information to avoid redundantly loading the attribute in all SIMD lanes. + #define PER_INSTANCE flat + #else + #define PER_INSTANCE + #endif + + #define varying out +#endif + +#ifdef WR_FRAGMENT_SHADER + precision highp float; + #define varying in +#endif diff --git a/third_party/webrender/webrender/res/brush.glsl b/third_party/webrender/webrender/res/brush.glsl new file mode 100644 index 00000000000..4589e34b29e --- /dev/null +++ b/third_party/webrender/webrender/res/brush.glsl @@ -0,0 +1,317 @@ +/* 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/. */ + +/// # Brush vertex shaders memory layout +/// +/// The overall memory layout is the same for all brush shaders. +/// +/// The vertex shader receives a minimal amount of data from vertex attributes (packed into a single +/// ivec4 per instance) and the rest is fetched from various uniform samplers using offsets decoded +/// from the vertex attributes. +/// +/// The diagram below shows the the various pieces of data fectched in the vertex shader: +/// +///```ascii +/// (sPrimitiveHeadersI) +/// (VBO) +-----------------------+ +/// +----------------------------+ +----------------------------> | Int header | +/// | Instance vertex attributes | | (sPrimitiveHeadersF) | | +/// | | | +---------------------+ | z | +/// | x: prim_header_address +-------+---> | Float header | | specific_address +-----+ +/// | y: picture_task_address +---------+ | | | transform_address +---+ | +/// | clip_address +-----+ | | local_rect | | user_data | | | +/// | z: flags | | | | local_clip_rect | +-----------------------+ | | +/// | segment_index | | | +---------------------+ | | +/// | w: resource_address +--+ | | | | +/// +----------------------------+ | | | (sGpuCache) | | +/// | | | (sGpuCache) +------------+ | | +/// | | | +---------------+ | Transform | <--------+ | +/// (sGpuCache) | | +-> | Picture task | +------------+ | +/// +-------------+ | | | | | +/// | Resource | <---+ | | ... | | +/// | | | +---------------+ +--------------------------------+ +/// | | | | +/// +-------------+ | (sGpuCache) v (sGpuCache) +/// | +---------------+ +--------------+---------------+-+-+ +/// +-----> | Clip area | | Brush data | Segment data | | | +/// | | | | | | | +/// | ... | | ... | ... | | | ... +/// +---------------+ +--------------+---------------+-+-+ +///``` +/// +/// - Segment data address is obtained by combining the address stored in the int header and the +/// segment index decoded from the vertex attributes. +/// - Resource data is optional, some brush types (such as images) store some extra data there while +/// other brush types don't use it. +/// + +#ifdef WR_FEATURE_MULTI_BRUSH +flat varying int v_brush_kind; +#endif + +// A few varying slots for the brushes to use. +// Using these instead of adding dedicated varyings avoids using a high +// number of varyings in the multi-brush shader. +flat varying vec4 flat_varying_vec4_0; +flat varying vec4 flat_varying_vec4_1; +flat varying vec4 flat_varying_vec4_2; +flat varying vec4 flat_varying_vec4_3; +flat varying vec4 flat_varying_vec4_4; + +flat varying ivec4 flat_varying_ivec4_0; + +varying vec4 varying_vec4_0; +varying vec4 varying_vec4_1; + +flat varying HIGHP_FS_ADDRESS int flat_varying_highp_int_address_0; + +#ifdef WR_VERTEX_SHADER + +#define FWD_DECLARE_VS_FUNCTION(name) \ +void name( \ + VertexInfo vi, \ + int prim_address, \ + RectWithSize local_rect, \ + RectWithSize segment_rect, \ + ivec4 prim_user_data, \ + int specific_resource_address, \ + mat4 transform, \ + PictureTask pic_task, \ + int brush_flags, \ + vec4 segment_data \ +); + +// Forward-declare all brush vertex entry points. +FWD_DECLARE_VS_FUNCTION(image_brush_vs) +FWD_DECLARE_VS_FUNCTION(solid_brush_vs) +FWD_DECLARE_VS_FUNCTION(blend_brush_vs) +FWD_DECLARE_VS_FUNCTION(mix_blend_brush_vs) +FWD_DECLARE_VS_FUNCTION(linear_gradient_brush_vs) +FWD_DECLARE_VS_FUNCTION(radial_gradient_brush_vs) +FWD_DECLARE_VS_FUNCTION(conic_gradient_brush_vs) +FWD_DECLARE_VS_FUNCTION(yuv_brush_vs) +FWD_DECLARE_VS_FUNCTION(opacity_brush_vs) +FWD_DECLARE_VS_FUNCTION(text_brush_vs) + +// Forward-declare the text vertex shader entry point which is currently +// different from other brushes. +void text_shader_main( + Instance instance, + PrimitiveHeader ph, + Transform transform, + PictureTask task, + ClipArea clip_area +); + +void multi_brush_vs( + VertexInfo vi, + int prim_address, + RectWithSize local_rect, + RectWithSize segment_rect, + ivec4 prim_user_data, + int specific_resource_address, + mat4 transform, + PictureTask pic_task, + int brush_flags, + vec4 texel_rect, + int brush_kind +); + +#define VECS_PER_SEGMENT 2 + +#define BRUSH_FLAG_PERSPECTIVE_INTERPOLATION 1 +#define BRUSH_FLAG_SEGMENT_RELATIVE 2 +#define BRUSH_FLAG_SEGMENT_REPEAT_X 4 +#define BRUSH_FLAG_SEGMENT_REPEAT_Y 8 +#define BRUSH_FLAG_SEGMENT_REPEAT_X_ROUND 16 +#define BRUSH_FLAG_SEGMENT_REPEAT_Y_ROUND 32 +#define BRUSH_FLAG_SEGMENT_NINEPATCH_MIDDLE 64 +#define BRUSH_FLAG_TEXEL_RECT 128 + +#define INVALID_SEGMENT_INDEX 0xffff + +void brush_shader_main_vs( + Instance instance, + PrimitiveHeader ph, + Transform transform, + PictureTask pic_task, + ClipArea clip_area +) { + int edge_flags = instance.flags & 0xff; + int brush_flags = (instance.flags >> 8) & 0xff; + + // Fetch the segment of this brush primitive we are drawing. + vec4 segment_data; + RectWithSize segment_rect; + if (instance.segment_index == INVALID_SEGMENT_INDEX) { + segment_rect = ph.local_rect; + segment_data = vec4(0.0); + } else { + // This contraption is needed by the Mac GLSL compiler which falls + // over (generating garbage values) if: + // - we use a variable insead of the ACTUAL_VECS_PER_BRUSH define, + // - this compile time condition is done inside of vecs_per_brush. + #ifdef WR_FEATURE_MULTI_BRUSH + #define ACTUAL_VECS_PER_BRUSH vecs_per_brush(instance.brush_kind) + #else + #define ACTUAL_VECS_PER_BRUSH VECS_PER_SPECIFIC_BRUSH + #endif + + int segment_address = ph.specific_prim_address + + ACTUAL_VECS_PER_BRUSH + + instance.segment_index * VECS_PER_SEGMENT; + + vec4[2] segment_info = fetch_from_gpu_cache_2(segment_address); + segment_rect = RectWithSize(segment_info[0].xy, segment_info[0].zw); + segment_rect.p0 += ph.local_rect.p0; + segment_data = segment_info[1]; + } + + VertexInfo vi; + + // Write the normal vertex information out. + if (transform.is_axis_aligned) { + + // Select the corner of the local rect that we are processing. + vec2 local_pos = segment_rect.p0 + segment_rect.size * aPosition.xy; + + vi = write_vertex( + local_pos, + ph.local_clip_rect, + ph.z, + transform, + pic_task + ); + + // TODO(gw): transform bounds may be referenced by + // the fragment shader when running in + // the alpha pass, even on non-transformed + // items. For now, just ensure it has no + // effect. We can tidy this up as we move + // more items to be brush shaders. +#ifdef WR_FEATURE_ALPHA_PASS + init_transform_vs(vec4(vec2(-1.0e16), vec2(1.0e16))); +#endif + } else { + bvec4 edge_mask = notEqual(edge_flags & ivec4(1, 2, 4, 8), ivec4(0)); + + vi = write_transform_vertex( + segment_rect, + ph.local_rect, + ph.local_clip_rect, + mix(vec4(0.0), vec4(1.0), edge_mask), + ph.z, + transform, + pic_task + ); + } + + // For brush instances in the alpha pass, always write + // out clip information. + // TODO(gw): It's possible that we might want alpha + // shaders that don't clip in the future, + // but it's reasonable to assume that one + // implies the other, for now. +#ifdef WR_FEATURE_ALPHA_PASS + write_clip( + vi.world_pos, + clip_area + ); +#endif + + // Run the specific brush VS code to write interpolators. +#ifdef WR_FEATURE_MULTI_BRUSH + v_brush_kind = instance.brush_kind; + multi_brush_vs( + vi, + ph.specific_prim_address, + ph.local_rect, + segment_rect, + ph.user_data, + instance.resource_address, + transform.m, + pic_task, + brush_flags, + segment_data, + instance.brush_kind + ); +#else + WR_BRUSH_VS_FUNCTION( + vi, + ph.specific_prim_address, + ph.local_rect, + segment_rect, + ph.user_data, + instance.resource_address, + transform.m, + pic_task, + brush_flags, + segment_data + ); +#endif + +} + +#ifndef WR_VERTEX_SHADER_MAIN_FUNCTION +// If the entry-point was not overridden before including the brush shader, +// use the default one. +#define WR_VERTEX_SHADER_MAIN_FUNCTION brush_shader_main_vs +#endif + +void main(void) { + + Instance instance = decode_instance_attributes(); + PrimitiveHeader ph = fetch_prim_header(instance.prim_header_address); + Transform transform = fetch_transform(ph.transform_id); + PictureTask task = fetch_picture_task(instance.picture_task_address); + ClipArea clip_area = fetch_clip_area(instance.clip_address); + + WR_VERTEX_SHADER_MAIN_FUNCTION(instance, ph, transform, task, clip_area); +} + +#endif // WR_VERTEX_SHADER + +#ifdef WR_FRAGMENT_SHADER + +// Foward-declare all brush entry-points. +Fragment image_brush_fs(); +Fragment solid_brush_fs(); +Fragment blend_brush_fs(); +Fragment mix_blend_brush_fs(); +Fragment linear_gradient_brush_fs(); +Fragment radial_gradient_brush_fs(); +Fragment conic_gradient_brush_fs(); +Fragment yuv_brush_fs(); +Fragment opacity_brush_fs(); +Fragment text_brush_fs(); +Fragment multi_brush_fs(int brush_kind); + +void main(void) { +#ifdef WR_FEATURE_DEBUG_OVERDRAW + oFragColor = WR_DEBUG_OVERDRAW_COLOR; +#else + + // Run the specific brush FS code to output the color. +#ifdef WR_FEATURE_MULTI_BRUSH + Fragment frag = multi_brush_fs(v_brush_kind); +#else + Fragment frag = WR_BRUSH_FS_FUNCTION(); +#endif + + +#ifdef WR_FEATURE_ALPHA_PASS + // Apply the clip mask + float clip_alpha = do_clip(); + + frag.color *= clip_alpha; + + #ifdef WR_FEATURE_DUAL_SOURCE_BLENDING + oFragBlend = frag.blend * clip_alpha; + #endif +#endif + + write_output(frag.color); +#endif +} +#endif diff --git a/third_party/webrender/webrender/res/brush_blend.glsl b/third_party/webrender/webrender/res/brush_blend.glsl new file mode 100644 index 00000000000..c871c390d28 --- /dev/null +++ b/third_party/webrender/webrender/res/brush_blend.glsl @@ -0,0 +1,335 @@ +/* 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/. */ + +#define VECS_PER_BLEND_BRUSH 3 +#define VECS_PER_SPECIFIC_BRUSH VECS_PER_BLEND_BRUSH + +#define WR_BRUSH_VS_FUNCTION blend_brush_vs +#define WR_BRUSH_FS_FUNCTION blend_brush_fs + +#define COMPONENT_TRANSFER_IDENTITY 0 +#define COMPONENT_TRANSFER_TABLE 1 +#define COMPONENT_TRANSFER_DISCRETE 2 +#define COMPONENT_TRANSFER_LINEAR 3 +#define COMPONENT_TRANSFER_GAMMA 4 + +// Must be kept in sync with `Filter::as_int` in internal_types.rs +// Not all filters are defined here because some filter use different shaders. +#define FILTER_CONTRAST 0 +#define FILTER_GRAYSCALE 1 +#define FILTER_HUE_ROTATE 2 +#define FILTER_INVERT 3 +#define FILTER_SATURATE 4 +#define FILTER_SEPIA 5 +#define FILTER_BRIGHTNESS 6 +#define FILTER_COLOR_MATRIX 7 +#define FILTER_SRGB_TO_LINEAR 8 +#define FILTER_LINEAR_TO_SRGB 9 +#define FILTER_FLOOD 10 +#define FILTER_COMPONENT_TRANSFER 11 + +#include shared,prim_shared,brush + +// Interpolated UV coordinates to sample. +#define V_UV varying_vec4_0.zw +#define V_LOCAL_POS varying_vec4_0.xy + +#define V_FLOOD_COLOR flat_varying_vec4_1 + +// Normalized bounds of the source image in the texture. +#define V_UV_BOUNDS flat_varying_vec4_2 + +#define V_COLOR_OFFSET flat_varying_vec4_3 + +// Layer index to sample. +#define V_LAYER flat_varying_vec4_4.x +// Flag to allow perspective interpolation of UV. +#define V_PERSPECTIVE flat_varying_vec4_4.y + +#define V_AMOUNT flat_varying_vec4_4.z + +#define V_OP flat_varying_ivec4_0.x +#define V_TABLE_ADDRESS flat_varying_ivec4_0.y + +flat varying mat4 vColorMat; + +flat varying int vFuncs[4]; + +#ifdef WR_VERTEX_SHADER + +void blend_brush_vs( + VertexInfo vi, + int prim_address, + RectWithSize local_rect, + RectWithSize segment_rect, + ivec4 prim_user_data, + int specific_resource_address, + mat4 transform, + PictureTask pic_task, + int brush_flags, + vec4 unused +) { + ImageResource res = fetch_image_resource(prim_user_data.x); + vec2 uv0 = res.uv_rect.p0; + vec2 uv1 = res.uv_rect.p1; + + vec2 texture_size = vec2(textureSize(sColor0, 0).xy); + vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size; + f = get_image_quad_uv(prim_user_data.x, f); + vec2 uv = mix(uv0, uv1, f); + float perspective_interpolate = (brush_flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0 ? 1.0 : 0.0; + + V_UV = uv / texture_size * mix(vi.world_pos.w, 1.0, perspective_interpolate); + V_LAYER = res.layer; + V_PERSPECTIVE = perspective_interpolate; + + // TODO: The image shader treats this differently: deflate the rect by half a pixel on each side and + // clamp the uv in the frame shader. Does it make sense to do the same here? + V_UV_BOUNDS = vec4(uv0, uv1) / texture_size.xyxy; + V_LOCAL_POS = vi.local_pos; + + float lumR = 0.2126; + float lumG = 0.7152; + float lumB = 0.0722; + float oneMinusLumR = 1.0 - lumR; + float oneMinusLumG = 1.0 - lumG; + float oneMinusLumB = 1.0 - lumB; + + float amount = float(prim_user_data.z) / 65536.0; + float invAmount = 1.0 - amount; + + V_OP = prim_user_data.y & 0xffff; + V_AMOUNT = amount; + + // This assignment is only used for component transfer filters but this + // assignment has to be done here and not in the component transfer case + // below because it doesn't get executed on Windows because of a suspected + // miscompile of this shader on Windows. See + // https://github.com/servo/webrender/wiki/Driver-issues#bug-1505871---assignment-to-varying-flat-arrays-inside-switch-statement-of-vertex-shader-suspected-miscompile-on-windows + // default: just to satisfy angle_shader_validation.rs which needs one + // default: for every switch, even in comments. + vFuncs[0] = (prim_user_data.y >> 28) & 0xf; // R + vFuncs[1] = (prim_user_data.y >> 24) & 0xf; // G + vFuncs[2] = (prim_user_data.y >> 20) & 0xf; // B + vFuncs[3] = (prim_user_data.y >> 16) & 0xf; // A + + switch (V_OP) { + case FILTER_GRAYSCALE: { + vColorMat = mat4( + vec4(lumR + oneMinusLumR * invAmount, lumR - lumR * invAmount, lumR - lumR * invAmount, 0.0), + vec4(lumG - lumG * invAmount, lumG + oneMinusLumG * invAmount, lumG - lumG * invAmount, 0.0), + vec4(lumB - lumB * invAmount, lumB - lumB * invAmount, lumB + oneMinusLumB * invAmount, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); + V_COLOR_OFFSET = vec4(0.0); + break; + } + case FILTER_HUE_ROTATE: { + float c = cos(amount); + float s = sin(amount); + vColorMat = mat4( + vec4(lumR + oneMinusLumR * c - lumR * s, lumR - lumR * c + 0.143 * s, lumR - lumR * c - oneMinusLumR * s, 0.0), + vec4(lumG - lumG * c - lumG * s, lumG + oneMinusLumG * c + 0.140 * s, lumG - lumG * c + lumG * s, 0.0), + vec4(lumB - lumB * c + oneMinusLumB * s, lumB - lumB * c - 0.283 * s, lumB + oneMinusLumB * c + lumB * s, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); + V_COLOR_OFFSET = vec4(0.0); + break; + } + case FILTER_SATURATE: { + vColorMat = mat4( + vec4(invAmount * lumR + amount, invAmount * lumR, invAmount * lumR, 0.0), + vec4(invAmount * lumG, invAmount * lumG + amount, invAmount * lumG, 0.0), + vec4(invAmount * lumB, invAmount * lumB, invAmount * lumB + amount, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); + V_COLOR_OFFSET = vec4(0.0); + break; + } + case FILTER_SEPIA: { + vColorMat = mat4( + vec4(0.393 + 0.607 * invAmount, 0.349 - 0.349 * invAmount, 0.272 - 0.272 * invAmount, 0.0), + vec4(0.769 - 0.769 * invAmount, 0.686 + 0.314 * invAmount, 0.534 - 0.534 * invAmount, 0.0), + vec4(0.189 - 0.189 * invAmount, 0.168 - 0.168 * invAmount, 0.131 + 0.869 * invAmount, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) + ); + V_COLOR_OFFSET = vec4(0.0); + break; + } + case FILTER_COLOR_MATRIX: { + vec4 mat_data[4] = fetch_from_gpu_cache_4(prim_user_data.z); + vec4 offset_data = fetch_from_gpu_cache_1(prim_user_data.z + 4); + vColorMat = mat4(mat_data[0], mat_data[1], mat_data[2], mat_data[3]); + V_COLOR_OFFSET = offset_data; + break; + } + case FILTER_COMPONENT_TRANSFER: { + V_TABLE_ADDRESS = prim_user_data.z; + break; + } + case FILTER_FLOOD: { + V_FLOOD_COLOR = fetch_from_gpu_cache_1(prim_user_data.z); + break; + } + default: break; + } +} +#endif + +#ifdef WR_FRAGMENT_SHADER +vec3 Contrast(vec3 Cs, float amount) { + return Cs.rgb * amount - 0.5 * amount + 0.5; +} + +vec3 Invert(vec3 Cs, float amount) { + return mix(Cs.rgb, vec3(1.0) - Cs.rgb, amount); +} + +vec3 Brightness(vec3 Cs, float amount) { + // Apply the brightness factor. + // Resulting color needs to be clamped to output range + // since we are pre-multiplying alpha in the shader. + return clamp(Cs.rgb * amount, vec3(0.0), vec3(1.0)); +} + +// Based on the Gecko's implementation in +// https://hg.mozilla.org/mozilla-central/file/91b4c3687d75/gfx/src/FilterSupport.cpp#l24 +// These could be made faster by sampling a lookup table stored in a float texture +// with linear interpolation. + +vec3 SrgbToLinear(vec3 color) { + vec3 c1 = color / 12.92; + vec3 c2 = pow(color / 1.055 + vec3(0.055 / 1.055), vec3(2.4)); + return if_then_else(lessThanEqual(color, vec3(0.04045)), c1, c2); +} + +vec3 LinearToSrgb(vec3 color) { + vec3 c1 = color * 12.92; + vec3 c2 = vec3(1.055) * pow(color, vec3(1.0 / 2.4)) - vec3(0.055); + return if_then_else(lessThanEqual(color, vec3(0.0031308)), c1, c2); +} + +// This function has to be factored out due to the following issue: +// https://github.com/servo/webrender/wiki/Driver-issues#bug-1532245---switch-statement-inside-control-flow-inside-switch-statement-fails-to-compile-on-some-android-phones +// (and now the words "default: default:" so angle_shader_validation.rs passes) +vec4 ComponentTransfer(vec4 colora) { + // We push a different amount of data to the gpu cache depending on the + // function type. + // Identity => 0 blocks + // Table/Discrete => 64 blocks (256 values) + // Linear => 1 block (2 values) + // Gamma => 1 block (3 values) + // We loop through the color components and increment the offset (for the + // next color component) into the gpu cache based on how many blocks that + // function type put into the gpu cache. + // Table/Discrete use a 256 entry look up table. + // Linear/Gamma are a simple calculation. + int offset = 0; + vec4 texel; + int k; + + for (int i = 0; i < 4; i++) { + switch (vFuncs[i]) { + case COMPONENT_TRANSFER_IDENTITY: + break; + case COMPONENT_TRANSFER_TABLE: + case COMPONENT_TRANSFER_DISCRETE: { + // fetch value from lookup table + k = int(floor(colora[i]*255.0)); + texel = fetch_from_gpu_cache_1(V_TABLE_ADDRESS + offset + k/4); + colora[i] = clamp(texel[k % 4], 0.0, 1.0); + // offset plus 256/4 blocks + offset = offset + 64; + break; + } + case COMPONENT_TRANSFER_LINEAR: { + // fetch the two values for use in the linear equation + texel = fetch_from_gpu_cache_1(V_TABLE_ADDRESS + offset); + colora[i] = clamp(texel[0] * colora[i] + texel[1], 0.0, 1.0); + // offset plus 1 block + offset = offset + 1; + break; + } + case COMPONENT_TRANSFER_GAMMA: { + // fetch the three values for use in the gamma equation + texel = fetch_from_gpu_cache_1(V_TABLE_ADDRESS + offset); + colora[i] = clamp(texel[0] * pow(colora[i], texel[1]) + texel[2], 0.0, 1.0); + // offset plus 1 block + offset = offset + 1; + break; + } + default: + // shouldn't happen + break; + } + } + return colora; +} + +Fragment blend_brush_fs() { + float perspective_divisor = mix(gl_FragCoord.w, 1.0, V_PERSPECTIVE); + vec2 uv = V_UV * perspective_divisor; + vec4 Cs = texture(sColor0, vec3(uv, V_LAYER)); + + // Un-premultiply the input. + float alpha = Cs.a; + vec3 color = alpha != 0.0 ? Cs.rgb / alpha : Cs.rgb; + + switch (V_OP) { + case FILTER_CONTRAST: + color = Contrast(color, V_AMOUNT); + break; + case FILTER_INVERT: + color = Invert(color, V_AMOUNT); + break; + case FILTER_BRIGHTNESS: + color = Brightness(color, V_AMOUNT); + break; + case FILTER_SRGB_TO_LINEAR: + color = SrgbToLinear(color); + break; + case FILTER_LINEAR_TO_SRGB: + color = LinearToSrgb(color); + break; + case FILTER_COMPONENT_TRANSFER: { + // Get the unpremultiplied color with alpha. + vec4 colora = vec4(color, alpha); + colora = ComponentTransfer(colora); + color = colora.rgb; + alpha = colora.a; + break; + } + case FILTER_FLOOD: + color = V_FLOOD_COLOR.rgb; + alpha = V_FLOOD_COLOR.a; + break; + default: + // Color matrix type filters (sepia, hue-rotate, etc...) + vec4 result = vColorMat * vec4(color, alpha) + V_COLOR_OFFSET; + result = clamp(result, vec4(0.0), vec4(1.0)); + color = result.rgb; + alpha = result.a; + } + + // Fail-safe to ensure that we don't sample outside the rendered + // portion of a blend source. + alpha *= min(point_inside_rect(uv, V_UV_BOUNDS.xy, V_UV_BOUNDS.zw), + init_transform_fs(V_LOCAL_POS)); + + // Pre-multiply the alpha into the output value. + return Fragment(alpha * vec4(color, 1.0)); +} +#endif + +// Undef macro names that could be re-defined by other shaders. +#undef V_UV +#undef V_LOCAL_POS +#undef V_FLOOD_COLOR +#undef V_UV_BOUNDS +#undef V_COLOR_OFFSET +#undef V_AMOUNT +#undef V_LAYER +#undef V_PERSPECTIVE +#undef V_OP +#undef V_TABLE_ADDRESS diff --git a/third_party/webrender/webrender/res/brush_conic_gradient.glsl b/third_party/webrender/webrender/res/brush_conic_gradient.glsl new file mode 100644 index 00000000000..56bd3c25a97 --- /dev/null +++ b/third_party/webrender/webrender/res/brush_conic_gradient.glsl @@ -0,0 +1,147 @@ +/* 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/. */ + +#define VECS_PER_CONIC_GRADIENT_BRUSH 2 +#define VECS_PER_SPECIFIC_BRUSH VECS_PER_CONIC_GRADIENT_BRUSH + +#define WR_BRUSH_VS_FUNCTION conic_gradient_brush_vs +#define WR_BRUSH_FS_FUNCTION conic_gradient_brush_fs + +#include shared,prim_shared,brush + +#define V_GRADIENT_ADDRESS flat_varying_highp_int_address_0 + +#define V_CENTER flat_varying_vec4_0.xy +#define V_START_OFFSET flat_varying_vec4_0.z +#define V_END_OFFSET flat_varying_vec4_0.w +#define V_ANGLE flat_varying_vec4_1.w + +// Size of the gradient pattern's rectangle, used to compute horizontal and vertical +// repetitions. Not to be confused with another kind of repetition of the pattern +// which happens along the gradient stops. +#define V_REPEATED_SIZE flat_varying_vec4_1.xy +// Repetition along the gradient stops. +#define V_GRADIENT_REPEAT flat_varying_vec4_1.z + +#define V_POS varying_vec4_0.zw + +#ifdef WR_FEATURE_ALPHA_PASS +#define V_LOCAL_POS varying_vec4_0.xy +#define V_TILE_REPEAT flat_varying_vec4_2.xy +#endif + +#define PI 3.141592653589793 + +#ifdef WR_VERTEX_SHADER + +struct ConicGradient { + vec2 center_point; + vec2 start_end_offset; + float angle; + int extend_mode; + vec2 stretch_size; +}; + +ConicGradient fetch_gradient(int address) { + vec4 data[2] = fetch_from_gpu_cache_2(address); + return ConicGradient( + data[0].xy, + data[0].zw, + float(data[1].x), + int(data[1].y), + data[1].zw + ); +} + +void conic_gradient_brush_vs( + VertexInfo vi, + int prim_address, + RectWithSize local_rect, + RectWithSize segment_rect, + ivec4 prim_user_data, + int specific_resource_address, + mat4 transform, + PictureTask pic_task, + int brush_flags, + vec4 texel_rect +) { + ConicGradient gradient = fetch_gradient(prim_address); + + if ((brush_flags & BRUSH_FLAG_SEGMENT_RELATIVE) != 0) { + V_POS = (vi.local_pos - segment_rect.p0) / segment_rect.size; + V_POS = V_POS * (texel_rect.zw - texel_rect.xy) + texel_rect.xy; + V_POS = V_POS * local_rect.size; + } else { + V_POS = vi.local_pos - local_rect.p0; + } + + V_CENTER = gradient.center_point; + V_ANGLE = gradient.angle; + V_START_OFFSET = gradient.start_end_offset.x; + V_END_OFFSET = gradient.start_end_offset.y; + + vec2 tile_repeat = local_rect.size / gradient.stretch_size; + V_REPEATED_SIZE = gradient.stretch_size; + + V_GRADIENT_ADDRESS = prim_user_data.x; + + // Whether to repeat the gradient along the line instead of clamping. + V_GRADIENT_REPEAT = float(gradient.extend_mode != EXTEND_MODE_CLAMP); + +#ifdef WR_FEATURE_ALPHA_PASS + V_TILE_REPEAT = tile_repeat; + V_LOCAL_POS = vi.local_pos; +#endif +} +#endif + +#ifdef WR_FRAGMENT_SHADER +Fragment conic_gradient_brush_fs() { + +#ifdef WR_FEATURE_ALPHA_PASS + // Handle top and left inflated edges (see brush_image). + vec2 local_pos = max(V_POS, vec2(0.0)); + + // Apply potential horizontal and vertical repetitions. + vec2 pos = mod(local_pos, V_REPEATED_SIZE); + + vec2 prim_size = V_REPEATED_SIZE * V_TILE_REPEAT; + // Handle bottom and right inflated edges (see brush_image). + if (local_pos.x >= prim_size.x) { + pos.x = V_REPEATED_SIZE.x; + } + if (local_pos.y >= prim_size.y) { + pos.y = V_REPEATED_SIZE.y; + } +#else + // Apply potential horizontal and vertical repetitions. + vec2 pos = mod(V_POS, V_REPEATED_SIZE); +#endif + + vec2 current_dir = pos - V_CENTER; + float current_angle = atan(current_dir.y, current_dir.x) + (PI / 2.0 - V_ANGLE); + float offset = mod(current_angle / (2.0 * PI), 1.0) - V_START_OFFSET; + offset = offset / (V_END_OFFSET - V_START_OFFSET); + + vec4 color = sample_gradient(V_GRADIENT_ADDRESS, + offset, + V_GRADIENT_REPEAT); + +#ifdef WR_FEATURE_ALPHA_PASS + color *= init_transform_fs(V_LOCAL_POS); +#endif + + return Fragment(color); +} +#endif + +// Undef macro names that could be re-defined by other shaders. +#undef V_GRADIENT_ADDRESS +#undef V_START_POINT +#undef V_SCALE_DIR +#undef V_REPEATED_SIZE +#undef V_GRADIENT_REPEAT +#undef V_POS +#undef V_LOCAL_POS +#undef V_TILE_REPEAT diff --git a/third_party/webrender/webrender/res/brush_image.glsl b/third_party/webrender/webrender/res/brush_image.glsl new file mode 100644 index 00000000000..05ee9043e00 --- /dev/null +++ b/third_party/webrender/webrender/res/brush_image.glsl @@ -0,0 +1,346 @@ +/* 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/. */ + +#define VECS_PER_IMAGE_BRUSH 3 +#define VECS_PER_SPECIFIC_BRUSH VECS_PER_IMAGE_BRUSH + +#define WR_BRUSH_VS_FUNCTION image_brush_vs +#define WR_BRUSH_FS_FUNCTION image_brush_fs + +#include shared,prim_shared,brush + +#ifdef WR_FEATURE_ALPHA_PASS +#define V_LOCAL_POS varying_vec4_0.xy +#endif + +// Interpolated UV coordinates to sample. +#define V_UV varying_vec4_0.zw + +#ifdef WR_FEATURE_ALPHA_PASS +#define V_COLOR flat_varying_vec4_0 +#define V_MASK_SWIZZLE flat_varying_vec4_1.xy +#define V_TILE_REPEAT flat_varying_vec4_1.zw +#endif + +// Normalized bounds of the source image in the texture. +#define V_UV_BOUNDS flat_varying_vec4_2 +// Normalized bounds of the source image in the texture, adjusted to avoid +// sampling artifacts. +#define V_UV_SAMPLE_BOUNDS flat_varying_vec4_3 +// Layer index to sample. +#define V_LAYER flat_varying_vec4_4.x +// Flag to allow perspective interpolation of UV. +#define V_PERSPECTIVE flat_varying_vec4_4.y + +#ifdef WR_VERTEX_SHADER + +// Must match the AlphaType enum. +#define BLEND_MODE_ALPHA 0 +#define BLEND_MODE_PREMUL_ALPHA 1 + +struct ImageBrushData { + vec4 color; + vec4 background_color; + vec2 stretch_size; +}; + +ImageBrushData fetch_image_data(int address) { + vec4[3] raw_data = fetch_from_gpu_cache_3(address); + ImageBrushData data = ImageBrushData( + raw_data[0], + raw_data[1], + raw_data[2].xy + ); + return data; +} + +void image_brush_vs( + VertexInfo vi, + int prim_address, + RectWithSize prim_rect, + RectWithSize segment_rect, + ivec4 prim_user_data, + int specific_resource_address, + mat4 transform, + PictureTask pic_task, + int brush_flags, + vec4 segment_data +) { + ImageBrushData image_data = fetch_image_data(prim_address); + + // If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use + // non-normalized texture coordinates. +#ifdef WR_FEATURE_TEXTURE_RECT + vec2 texture_size = vec2(1, 1); +#else + vec2 texture_size = vec2(textureSize(sColor0, 0)); +#endif + + ImageResource res = fetch_image_resource(specific_resource_address); + vec2 uv0 = res.uv_rect.p0; + vec2 uv1 = res.uv_rect.p1; + + RectWithSize local_rect = prim_rect; + vec2 stretch_size = image_data.stretch_size; + if (stretch_size.x < 0.0) { + stretch_size = local_rect.size; + } + + // If this segment should interpolate relative to the + // segment, modify the parameters for that. + if ((brush_flags & BRUSH_FLAG_SEGMENT_RELATIVE) != 0) { + local_rect = segment_rect; + stretch_size = local_rect.size; + + // If the extra data is a texel rect, modify the UVs. + if ((brush_flags & BRUSH_FLAG_TEXEL_RECT) != 0) { + vec2 uv_size = res.uv_rect.p1 - res.uv_rect.p0; + uv0 = res.uv_rect.p0 + segment_data.xy * uv_size; + uv1 = res.uv_rect.p0 + segment_data.zw * uv_size; + + // Size of the uv rect of the segment we are considering when computing + // the repetitions. In most case it is the current segment, but for the + // middle area we look at the border size instead. + vec2 segment_uv_size = uv1 - uv0; + #ifdef WR_FEATURE_REPETITION + // Value of the stretch size with repetition. We have to compute it for + // both axis even if we only repeat on one axis because the value for + // each axis depends on what the repeated value would have been for the + // other axis. + vec2 repeated_stretch_size = stretch_size; + // The repetition parameters for the middle area of a nine-patch are based + // on the size of the border segments rather than the middle segment itself, + // taking top and left by default, falling back to bottom and right when a + // size is empty. + // TODO(bug 1609893): Move this logic to the CPU as well as other sources of + // branchiness in this shader. + if ((brush_flags & BRUSH_FLAG_SEGMENT_NINEPATCH_MIDDLE) != 0) { + segment_uv_size = uv0 - res.uv_rect.p0; + repeated_stretch_size = segment_rect.p0 - prim_rect.p0; + float epsilon = 0.001; + + if (segment_uv_size.x < epsilon || repeated_stretch_size.x < epsilon) { + segment_uv_size.x = res.uv_rect.p1.x - uv1.x; + repeated_stretch_size.x = prim_rect.p0.x + prim_rect.size.x + - segment_rect.p0.x - segment_rect.size.x; + } + + if (segment_uv_size.y < epsilon || repeated_stretch_size.y < epsilon) { + segment_uv_size.y = res.uv_rect.p1.y - uv1.y; + repeated_stretch_size.y = prim_rect.p0.y + prim_rect.size.y + - segment_rect.p0.y - segment_rect.size.y; + } + } + + if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_X) != 0) { + stretch_size.x = repeated_stretch_size.y / segment_uv_size.y * segment_uv_size.x; + } + if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y) != 0) { + stretch_size.y = repeated_stretch_size.x / segment_uv_size.x * segment_uv_size.y; + } + #endif + + } else { + #ifdef WR_FEATURE_REPETITION + if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_X) != 0) { + stretch_size.x = segment_data.z - segment_data.x; + } + if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y) != 0) { + stretch_size.y = segment_data.w - segment_data.y; + } + #endif + } + + #ifdef WR_FEATURE_REPETITION + if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_X_ROUND) != 0) { + float nx = max(1.0, round(segment_rect.size.x / stretch_size.x)); + stretch_size.x = segment_rect.size.x / nx; + } + if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y_ROUND) != 0) { + float ny = max(1.0, round(segment_rect.size.y / stretch_size.y)); + stretch_size.y = segment_rect.size.y / ny; + } + #endif + } + + float perspective_interpolate = (brush_flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0 ? 1.0 : 0.0; + V_LAYER = res.layer; + V_PERSPECTIVE = perspective_interpolate; + + // Handle case where the UV coords are inverted (e.g. from an + // external image). + vec2 min_uv = min(uv0, uv1); + vec2 max_uv = max(uv0, uv1); + + V_UV_SAMPLE_BOUNDS = vec4( + min_uv + vec2(0.5), + max_uv - vec2(0.5) + ) / texture_size.xyxy; + + vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size; + +#ifdef WR_FEATURE_ALPHA_PASS + int color_mode = prim_user_data.x & 0xffff; + int blend_mode = prim_user_data.x >> 16; + int raster_space = prim_user_data.y; + + if (color_mode == COLOR_MODE_FROM_PASS) { + color_mode = uMode; + } + + // Derive the texture coordinates for this image, based on + // whether the source image is a local-space or screen-space + // image. + switch (raster_space) { + case RASTER_SCREEN: { + // Since the screen space UVs specify an arbitrary quad, do + // a bilinear interpolation to get the correct UV for this + // local position. + f = get_image_quad_uv(specific_resource_address, f); + break; + } + default: + break; + } +#endif + + // Offset and scale V_UV here to avoid doing it in the fragment shader. + vec2 repeat = local_rect.size / stretch_size; + V_UV = mix(uv0, uv1, f) - min_uv; + V_UV /= texture_size; + V_UV *= repeat.xy; + if (perspective_interpolate == 0.0) { + V_UV *= vi.world_pos.w; + } + +#ifdef WR_FEATURE_TEXTURE_RECT + V_UV_BOUNDS = vec4(0.0, 0.0, vec2(textureSize(sColor0))); +#else + V_UV_BOUNDS = vec4(min_uv, max_uv) / texture_size.xyxy; +#endif + +#ifdef WR_FEATURE_ALPHA_PASS + V_TILE_REPEAT = repeat.xy; + + float opacity = float(prim_user_data.z) / 65535.0; + switch (blend_mode) { + case BLEND_MODE_ALPHA: + image_data.color.a *= opacity; + break; + case BLEND_MODE_PREMUL_ALPHA: + default: + image_data.color *= opacity; + break; + } + + switch (color_mode) { + case COLOR_MODE_ALPHA: + case COLOR_MODE_BITMAP: + V_MASK_SWIZZLE = vec2(0.0, 1.0); + V_COLOR = image_data.color; + break; + case COLOR_MODE_SUBPX_BG_PASS2: + case COLOR_MODE_SUBPX_DUAL_SOURCE: + case COLOR_MODE_IMAGE: + V_MASK_SWIZZLE = vec2(1.0, 0.0); + V_COLOR = image_data.color; + break; + case COLOR_MODE_SUBPX_CONST_COLOR: + case COLOR_MODE_SUBPX_BG_PASS0: + case COLOR_MODE_COLOR_BITMAP: + V_MASK_SWIZZLE = vec2(1.0, 0.0); + V_COLOR = vec4(image_data.color.a); + break; + case COLOR_MODE_SUBPX_BG_PASS1: + V_MASK_SWIZZLE = vec2(-1.0, 1.0); + V_COLOR = vec4(image_data.color.a) * image_data.background_color; + break; + default: + V_MASK_SWIZZLE = vec2(0.0); + V_COLOR = vec4(1.0); + } + + V_LOCAL_POS = vi.local_pos; +#endif +} +#endif + +#ifdef WR_FRAGMENT_SHADER + +vec2 compute_repeated_uvs(float perspective_divisor) { + vec2 uv_size = V_UV_BOUNDS.zw - V_UV_BOUNDS.xy; + +#ifdef WR_FEATURE_ALPHA_PASS + // This prevents the uv on the top and left parts of the primitive that was inflated + // for anti-aliasing purposes from going beyound the range covered by the regular + // (non-inflated) primitive. + vec2 local_uv = max(V_UV * perspective_divisor, vec2(0.0)); + + // Handle horizontal and vertical repetitions. + vec2 repeated_uv = mod(local_uv, uv_size) + V_UV_BOUNDS.xy; + + // This takes care of the bottom and right inflated parts. + // We do it after the modulo because the latter wraps around the values exactly on + // the right and bottom edges, which we do not want. + if (local_uv.x >= V_TILE_REPEAT.x * uv_size.x) { + repeated_uv.x = V_UV_BOUNDS.z; + } + if (local_uv.y >= V_TILE_REPEAT.y * uv_size.y) { + repeated_uv.y = V_UV_BOUNDS.w; + } +#else + vec2 repeated_uv = mod(V_UV * perspective_divisor, uv_size) + V_UV_BOUNDS.xy; +#endif + + return repeated_uv; +} + +Fragment image_brush_fs() { + float perspective_divisor = mix(gl_FragCoord.w, 1.0, V_PERSPECTIVE); + +#ifdef WR_FEATURE_REPETITION + vec2 repeated_uv = compute_repeated_uvs(perspective_divisor); +#else + vec2 repeated_uv = V_UV * perspective_divisor + V_UV_BOUNDS.xy; +#endif + + // Clamp the uvs to avoid sampling artifacts. + vec2 uv = clamp(repeated_uv, V_UV_SAMPLE_BOUNDS.xy, V_UV_SAMPLE_BOUNDS.zw); + + vec4 texel = TEX_SAMPLE(sColor0, vec3(uv, V_LAYER)); + + Fragment frag; + +#ifdef WR_FEATURE_ALPHA_PASS + #ifdef WR_FEATURE_ANTIALIASING + float alpha = init_transform_fs(V_LOCAL_POS); + #else + float alpha = 1.0; + #endif + texel.rgb = texel.rgb * V_MASK_SWIZZLE.x + texel.aaa * V_MASK_SWIZZLE.y; + + vec4 alpha_mask = texel * alpha; + frag.color = V_COLOR * alpha_mask; + + #ifdef WR_FEATURE_DUAL_SOURCE_BLENDING + frag.blend = alpha_mask * V_COLOR.a; + #endif +#else + frag.color = texel; +#endif + + return frag; +} +#endif + +// Undef macro names that could be re-defined by other shaders. +#undef V_LOCAL_POS +#undef V_UV +#undef V_COLOR +#undef V_MASK_SWIZZLE +#undef V_TILE_REPEAT +#undef V_UV_BOUNDS +#undef V_UV_SAMPLE_BOUNDS +#undef V_LAYER +#undef V_PERSPECTIVE diff --git a/third_party/webrender/webrender/res/brush_linear_gradient.glsl b/third_party/webrender/webrender/res/brush_linear_gradient.glsl new file mode 100644 index 00000000000..6e6933252cd --- /dev/null +++ b/third_party/webrender/webrender/res/brush_linear_gradient.glsl @@ -0,0 +1,137 @@ +/* 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/. */ + +#define VECS_PER_LINEAR_GRADIENT_BRUSH 2 +#define VECS_PER_SPECIFIC_BRUSH VECS_PER_LINEAR_GRADIENT_BRUSH + +#define WR_BRUSH_VS_FUNCTION linear_gradient_brush_vs +#define WR_BRUSH_FS_FUNCTION linear_gradient_brush_fs + +#include shared,prim_shared,brush + +#define V_GRADIENT_ADDRESS flat_varying_highp_int_address_0 + +#define V_START_POINT flat_varying_vec4_0.xy +#define V_SCALE_DIR flat_varying_vec4_0.zw +// Size of the gradient pattern's rectangle, used to compute horizontal and vertical +// repetitions. Not to be confused with another kind of repetition of the pattern +// which happens along the gradient stops. +#define V_REPEATED_SIZE flat_varying_vec4_1.xy +// Repetition along the gradient stops. +#define V_GRADIENT_REPEAT flat_varying_vec4_1.z + +#define V_POS varying_vec4_0.zw + +#ifdef WR_FEATURE_ALPHA_PASS +#define V_LOCAL_POS varying_vec4_0.xy +#define V_TILE_REPEAT flat_varying_vec4_2.xy +#endif + +#ifdef WR_VERTEX_SHADER + +struct Gradient { + vec4 start_end_point; + int extend_mode; + vec2 stretch_size; +}; + +Gradient fetch_gradient(int address) { + vec4 data[2] = fetch_from_gpu_cache_2(address); + return Gradient( + data[0], + int(data[1].x), + data[1].yz + ); +} + +void linear_gradient_brush_vs( + VertexInfo vi, + int prim_address, + RectWithSize local_rect, + RectWithSize segment_rect, + ivec4 prim_user_data, + int specific_resource_address, + mat4 transform, + PictureTask pic_task, + int brush_flags, + vec4 texel_rect +) { + Gradient gradient = fetch_gradient(prim_address); + + if ((brush_flags & BRUSH_FLAG_SEGMENT_RELATIVE) != 0) { + V_POS = (vi.local_pos - segment_rect.p0) / segment_rect.size; + V_POS = V_POS * (texel_rect.zw - texel_rect.xy) + texel_rect.xy; + V_POS = V_POS * local_rect.size; + } else { + V_POS = vi.local_pos - local_rect.p0; + } + + vec2 start_point = gradient.start_end_point.xy; + vec2 end_point = gradient.start_end_point.zw; + vec2 dir = end_point - start_point; + + V_START_POINT = start_point; + V_SCALE_DIR = dir / dot(dir, dir); + + vec2 tile_repeat = local_rect.size / gradient.stretch_size; + V_REPEATED_SIZE = gradient.stretch_size; + + V_GRADIENT_ADDRESS = prim_user_data.x; + + // Whether to repeat the gradient along the line instead of clamping. + V_GRADIENT_REPEAT = float(gradient.extend_mode != EXTEND_MODE_CLAMP); + +#ifdef WR_FEATURE_ALPHA_PASS + V_TILE_REPEAT = tile_repeat; + V_LOCAL_POS = vi.local_pos; +#endif +} +#endif + +#ifdef WR_FRAGMENT_SHADER +Fragment linear_gradient_brush_fs() { + +#ifdef WR_FEATURE_ALPHA_PASS + // Handle top and left inflated edges (see brush_image). + vec2 local_pos = max(V_POS, vec2(0.0)); + + // Apply potential horizontal and vertical repetitions. + vec2 pos = mod(local_pos, V_REPEATED_SIZE); + + vec2 prim_size = V_REPEATED_SIZE * V_TILE_REPEAT; + // Handle bottom and right inflated edges (see brush_image). + if (local_pos.x >= prim_size.x) { + pos.x = V_REPEATED_SIZE.x; + } + if (local_pos.y >= prim_size.y) { + pos.y = V_REPEATED_SIZE.y; + } +#else + // Apply potential horizontal and vertical repetitions. + vec2 pos = mod(V_POS, V_REPEATED_SIZE); +#endif + + float offset = dot(pos - V_START_POINT, V_SCALE_DIR); + + vec4 color = sample_gradient(V_GRADIENT_ADDRESS, + offset, + V_GRADIENT_REPEAT); + +#ifdef WR_FEATURE_ALPHA_PASS + color *= init_transform_fs(V_LOCAL_POS); +#endif + + return Fragment(color); +} +#endif + +// Undef macro names that could be re-defined by other shaders. +#undef V_GRADIENT_ADDRESS +#undef V_START_POINT +#undef V_SCALE_DIR +#undef V_REPEATED_SIZE +#undef V_GRADIENT_REPEAT +#undef V_POS +#undef V_LOCAL_POS +#undef V_TILE_REPEAT diff --git a/third_party/webrender/webrender/res/brush_mix_blend.glsl b/third_party/webrender/webrender/res/brush_mix_blend.glsl new file mode 100644 index 00000000000..774f2fcb677 --- /dev/null +++ b/third_party/webrender/webrender/res/brush_mix_blend.glsl @@ -0,0 +1,302 @@ +/* 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/. */ + +#define VECS_PER_MIX_BLEND_BRUSH 3 +#define VECS_PER_SPECIFIC_BRUSH VECS_PER_MIX_BLEND_BRUSH + +#define WR_BRUSH_VS_FUNCTION mix_blend_brush_vs +#define WR_BRUSH_FS_FUNCTION mix_blend_brush_fs + +#include shared,prim_shared,brush + +#define V_SRC_UV varying_vec4_0.xy +#define V_SRC_LAYER varying_vec4_0.w + +#define V_BACKDROP_UV varying_vec4_1.xy +#define V_BACKDROP_LAYER varying_vec4_1.w + +#define V_OP flat_varying_ivec4_0.x + +#ifdef WR_VERTEX_SHADER + +void mix_blend_brush_vs( + VertexInfo vi, + int prim_address, + RectWithSize local_rect, + RectWithSize segment_rect, + ivec4 prim_user_data, + int specific_resource_address, + mat4 transform, + PictureTask pic_task, + int brush_flags, + vec4 unused +) { + //Note: this is unsafe for `vi.world_pos.w <= 0.0` + vec2 device_pos = vi.world_pos.xy * pic_task.device_pixel_scale / max(0.0, vi.world_pos.w); + vec2 texture_size = vec2(textureSize(sPrevPassColor, 0)); + V_OP = prim_user_data.x; + + PictureTask src_task = fetch_picture_task(prim_user_data.z); + vec2 src_device_pos = vi.world_pos.xy * (src_task.device_pixel_scale / max(0.0, vi.world_pos.w)); + vec2 src_uv = src_device_pos + + src_task.common_data.task_rect.p0 - + src_task.content_origin; + V_SRC_UV = src_uv / texture_size; + V_SRC_LAYER = src_task.common_data.texture_layer_index; + + RenderTaskCommonData backdrop_task = fetch_render_task_common_data(prim_user_data.y); + float src_to_backdrop_scale = pic_task.device_pixel_scale / src_task.device_pixel_scale; + vec2 backdrop_uv = device_pos + + backdrop_task.task_rect.p0 - + src_task.content_origin * src_to_backdrop_scale; + V_BACKDROP_UV = backdrop_uv / texture_size; + V_BACKDROP_LAYER = backdrop_task.texture_layer_index; +} +#endif + +#ifdef WR_FRAGMENT_SHADER +vec3 Multiply(vec3 Cb, vec3 Cs) { + return Cb * Cs; +} + +vec3 Screen(vec3 Cb, vec3 Cs) { + return Cb + Cs - (Cb * Cs); +} + +vec3 HardLight(vec3 Cb, vec3 Cs) { + vec3 m = Multiply(Cb, 2.0 * Cs); + vec3 s = Screen(Cb, 2.0 * Cs - 1.0); + vec3 edge = vec3(0.5, 0.5, 0.5); + return mix(m, s, step(edge, Cs)); +} + +// TODO: Worth doing with mix/step? Check GLSL output. +float ColorDodge(float Cb, float Cs) { + if (Cb == 0.0) + return 0.0; + else if (Cs == 1.0) + return 1.0; + else + return min(1.0, Cb / (1.0 - Cs)); +} + +// TODO: Worth doing with mix/step? Check GLSL output. +float ColorBurn(float Cb, float Cs) { + if (Cb == 1.0) + return 1.0; + else if (Cs == 0.0) + return 0.0; + else + return 1.0 - min(1.0, (1.0 - Cb) / Cs); +} + +float SoftLight(float Cb, float Cs) { + if (Cs <= 0.5) { + return Cb - (1.0 - 2.0 * Cs) * Cb * (1.0 - Cb); + } else { + float D; + + if (Cb <= 0.25) + D = ((16.0 * Cb - 12.0) * Cb + 4.0) * Cb; + else + D = sqrt(Cb); + + return Cb + (2.0 * Cs - 1.0) * (D - Cb); + } +} + +vec3 Difference(vec3 Cb, vec3 Cs) { + return abs(Cb - Cs); +} + +vec3 Exclusion(vec3 Cb, vec3 Cs) { + return Cb + Cs - 2.0 * Cb * Cs; +} + +// These functions below are taken from the spec. +// There's probably a much quicker way to implement +// them in GLSL... +float Sat(vec3 c) { + return max(c.r, max(c.g, c.b)) - min(c.r, min(c.g, c.b)); +} + +float Lum(vec3 c) { + vec3 f = vec3(0.3, 0.59, 0.11); + return dot(c, f); +} + +vec3 ClipColor(vec3 C) { + float L = Lum(C); + float n = min(C.r, min(C.g, C.b)); + float x = max(C.r, max(C.g, C.b)); + + if (n < 0.0) + C = L + (((C - L) * L) / (L - n)); + + if (x > 1.0) + C = L + (((C - L) * (1.0 - L)) / (x - L)); + + return C; +} + +vec3 SetLum(vec3 C, float l) { + float d = l - Lum(C); + return ClipColor(C + d); +} + +void SetSatInner(inout float Cmin, inout float Cmid, inout float Cmax, float s) { + if (Cmax > Cmin) { + Cmid = (((Cmid - Cmin) * s) / (Cmax - Cmin)); + Cmax = s; + } else { + Cmid = 0.0; + Cmax = 0.0; + } + Cmin = 0.0; +} + +vec3 SetSat(vec3 C, float s) { + if (C.r <= C.g) { + if (C.g <= C.b) { + SetSatInner(C.r, C.g, C.b, s); + } else { + if (C.r <= C.b) { + SetSatInner(C.r, C.b, C.g, s); + } else { + SetSatInner(C.b, C.r, C.g, s); + } + } + } else { + if (C.r <= C.b) { + SetSatInner(C.g, C.r, C.b, s); + } else { + if (C.g <= C.b) { + SetSatInner(C.g, C.b, C.r, s); + } else { + SetSatInner(C.b, C.g, C.r, s); + } + } + } + return C; +} + +vec3 Hue(vec3 Cb, vec3 Cs) { + return SetLum(SetSat(Cs, Sat(Cb)), Lum(Cb)); +} + +vec3 Saturation(vec3 Cb, vec3 Cs) { + return SetLum(SetSat(Cb, Sat(Cs)), Lum(Cb)); +} + +vec3 Color(vec3 Cb, vec3 Cs) { + return SetLum(Cs, Lum(Cb)); +} + +vec3 Luminosity(vec3 Cb, vec3 Cs) { + return SetLum(Cb, Lum(Cs)); +} + +const int MixBlendMode_Multiply = 1; +const int MixBlendMode_Screen = 2; +const int MixBlendMode_Overlay = 3; +const int MixBlendMode_Darken = 4; +const int MixBlendMode_Lighten = 5; +const int MixBlendMode_ColorDodge = 6; +const int MixBlendMode_ColorBurn = 7; +const int MixBlendMode_HardLight = 8; +const int MixBlendMode_SoftLight = 9; +const int MixBlendMode_Difference = 10; +const int MixBlendMode_Exclusion = 11; +const int MixBlendMode_Hue = 12; +const int MixBlendMode_Saturation = 13; +const int MixBlendMode_Color = 14; +const int MixBlendMode_Luminosity = 15; + +Fragment mix_blend_brush_fs() { + vec4 Cb = textureLod(sPrevPassColor, vec3(V_BACKDROP_UV, V_BACKDROP_LAYER), 0.0); + vec4 Cs = textureLod(sPrevPassColor, vec3(V_SRC_UV, V_SRC_LAYER), 0.0); + + // The mix-blend-mode functions assume no premultiplied alpha + if (Cb.a != 0.0) { + Cb.rgb /= Cb.a; + } + + if (Cs.a != 0.0) { + Cs.rgb /= Cs.a; + } + + // Return yellow if none of the branches match (shouldn't happen). + vec4 result = vec4(1.0, 1.0, 0.0, 1.0); + + switch (V_OP) { + case MixBlendMode_Multiply: + result.rgb = Multiply(Cb.rgb, Cs.rgb); + break; + case MixBlendMode_Screen: + result.rgb = Screen(Cb.rgb, Cs.rgb); + break; + case MixBlendMode_Overlay: + // Overlay is inverse of Hardlight + result.rgb = HardLight(Cs.rgb, Cb.rgb); + break; + case MixBlendMode_Darken: + result.rgb = min(Cs.rgb, Cb.rgb); + break; + case MixBlendMode_Lighten: + result.rgb = max(Cs.rgb, Cb.rgb); + break; + case MixBlendMode_ColorDodge: + result.r = ColorDodge(Cb.r, Cs.r); + result.g = ColorDodge(Cb.g, Cs.g); + result.b = ColorDodge(Cb.b, Cs.b); + break; + case MixBlendMode_ColorBurn: + result.r = ColorBurn(Cb.r, Cs.r); + result.g = ColorBurn(Cb.g, Cs.g); + result.b = ColorBurn(Cb.b, Cs.b); + break; + case MixBlendMode_HardLight: + result.rgb = HardLight(Cb.rgb, Cs.rgb); + break; + case MixBlendMode_SoftLight: + result.r = SoftLight(Cb.r, Cs.r); + result.g = SoftLight(Cb.g, Cs.g); + result.b = SoftLight(Cb.b, Cs.b); + break; + case MixBlendMode_Difference: + result.rgb = Difference(Cb.rgb, Cs.rgb); + break; + case MixBlendMode_Exclusion: + result.rgb = Exclusion(Cb.rgb, Cs.rgb); + break; + case MixBlendMode_Hue: + result.rgb = Hue(Cb.rgb, Cs.rgb); + break; + case MixBlendMode_Saturation: + result.rgb = Saturation(Cb.rgb, Cs.rgb); + break; + case MixBlendMode_Color: + result.rgb = Color(Cb.rgb, Cs.rgb); + break; + case MixBlendMode_Luminosity: + result.rgb = Luminosity(Cb.rgb, Cs.rgb); + break; + default: break; + } + + result.rgb = (1.0 - Cb.a) * Cs.rgb + Cb.a * result.rgb; + result.a = Cs.a; + + result.rgb *= result.a; + + return Fragment(result); +} +#endif + +// Undef macro names that could be re-defined by other shaders. +#undef V_SRC_UV +#undef V_SRC_LAYER +#undef V_BACKDROP_UV +#undef V_BACKDROP_LAYER +#undef V_OP diff --git a/third_party/webrender/webrender/res/brush_multi.glsl b/third_party/webrender/webrender/res/brush_multi.glsl new file mode 100644 index 00000000000..447186b1fcb --- /dev/null +++ b/third_party/webrender/webrender/res/brush_multi.glsl @@ -0,0 +1,296 @@ +/* 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/. */ + +// The multi-brush shader is capable of rendering most types of brushes +// if they are enabled via WR_FEATURE_*_BRUSH defines. +// This type of uber-shader comes at a cost so the goal for this is to +// provide opportunities for aggressive batching when the number of draw +// calls so high that reducing the number of draw calls is worth the +// cost of this "uber-shader". + + +#define WR_FEATURE_MULTI_BRUSH + +// These constants must match the BrushShaderKind enum in gpu_types.rs. +#define BRUSH_KIND_SOLID 1 +#define BRUSH_KIND_IMAGE 2 +#define BRUSH_KIND_TEXT 3 +#define BRUSH_KIND_LINEAR_GRADIENT 4 +#define BRUSH_KIND_RADIAL_GRADIENT 5 +#define BRUSH_KIND_CONIC_GRADIENT 6 +#define BRUSH_KIND_BLEND 7 +#define BRUSH_KIND_MIX_BLEND 8 +#define BRUSH_KIND_YV 9 +#define BRUSH_KIND_OPACITY 10 + +int vecs_per_brush(int brush_kind); + + +#ifdef WR_FEATURE_TEXT_BRUSH +// Before including the brush source, if we need support for text we override +// the vertex shader's main entry point with one that can call into the text +// shader or the regular brush shaders. + +// Foward-declare the new entry point. +void multi_brush_main_vs( + Instance instance, + PrimitiveHeader ph, + Transform transform, + PictureTask pic_task, + ClipArea clip_area +); + +// Override the default entry point. +#define WR_VERTEX_SHADER_MAIN_FUNCTION multi_brush_main_vs + +#endif + + +#include shared,prim_shared,brush + + +#ifdef WR_FEATURE_IMAGE_BRUSH +#include brush_image +#endif + +#undef VECS_PER_SPECIFIC_BRUSH +#undef WR_BRUSH_VS_FUNCTION +#undef WR_BRUSH_FS_FUNCTION + +#ifdef WR_FEATURE_SOLID_BRUSH +#include brush_solid +#endif + +#undef VECS_PER_SPECIFIC_BRUSH +#undef WR_BRUSH_VS_FUNCTION +#undef WR_BRUSH_FS_FUNCTION + +#ifdef WR_FEATURE_BLEND_BRUSH +#include brush_blend +#endif + +#undef VECS_PER_SPECIFIC_BRUSH +#undef WR_BRUSH_VS_FUNCTION +#undef WR_BRUSH_FS_FUNCTION + +#ifdef WR_FEATURE_MIX_BLEND_BRUSH +#include brush_mix_blend +#endif + +#undef VECS_PER_SPECIFIC_BRUSH +#undef WR_BRUSH_VS_FUNCTION +#undef WR_BRUSH_FS_FUNCTION + +#ifdef WR_FEATURE_LINEAR_GRADIENT_BRUSH +#include brush_linear_gradient +#endif + +#undef VECS_PER_SPECIFIC_BRUSH +#undef WR_BRUSH_VS_FUNCTION +#undef WR_BRUSH_FS_FUNCTION + +#ifdef WR_FEATURE_RADIAL_GRADIENT_BRUSH +#include brush_radial_gradient +#endif + +#undef VECS_PER_SPECIFIC_BRUSH +#undef WR_BRUSH_VS_FUNCTION +#undef WR_BRUSH_FS_FUNCTION + +#ifdef WR_FEATURE_CONIC_GRADIENT_BRUSH +#include brush_conic_gradient +#endif + +#undef VECS_PER_SPECIFIC_BRUSH +#undef WR_BRUSH_VS_FUNCTION +#undef WR_BRUSH_FS_FUNCTION + +#ifdef WR_FEATURE_OPACITY_BRUSH +#include brush_opacity +#endif + +#undef VECS_PER_SPECIFIC_BRUSH +#undef WR_BRUSH_VS_FUNCTION +#undef WR_BRUSH_FS_FUNCTION + +#ifdef WR_FEATURE_TEXT_BRUSH +#include ps_text_run + +// Special entry point when text support is needed. +void multi_brush_main_vs( + Instance instance, + PrimitiveHeader ph, + Transform transform, + PictureTask pic_task, + ClipArea clip_area +) { + if (instance.brush_kind == BRUSH_SHADER_KIND_TEXT) { + text_shader_main(instance, ph, transform, task, clip_area); + } else { + brush_shader_main(instance, ph, transform, task, clip_area); + } +} + +#endif + +int vecs_per_brush(int brush_kind) { + switch (brush_kind) { + // The default arm should never be taken, we let it point to whichever shader + // is enabled first to satisfy ANGLE validation. + default: + + #ifdef WR_FEATURE_IMAGE_BRUSH + case BRUSH_KIND_IMAGE: return VECS_PER_IMAGE_BRUSH; + #endif + + #ifdef WR_FEATURE_IMAGE_BRUSH + case BRUSH_KIND_SOLID: return VECS_PER_SOLID_BRUSH; + #endif + + #ifdef WR_FEATURE_BLEND_BRUSH + case BRUSH_KIND_BLEND: return VECS_PER_BLEND_BRUSH; + #endif + + #ifdef WR_FEATURE_MIX_BLEND_BRUSH + case BRUSH_KIND_MIX_BLEND: return VECS_PER_MIX_BLEND_BRUSH; + #endif + + #ifdef WR_FEATURE_LINEAR_GRADIENT_BRUSH + case BRUSH_KIND_LINEAR_GRADIENT: return VECS_PER_LINEAR_GRADIENT_BRUSH; + #endif + + #ifdef WR_FEATURE_RADIAL_GRADIENT_BRUSH + case BRUSH_KIND_RADIAL_GRADIENT: return VECS_PER_RADIAL_GRADIENT_BRUSH; + #endif + + + #ifdef WR_FEATURE_CONIC_GRADIENT_BRUSH + case BRUSH_KIND_CONIC_GRADIENT: return VECS_PER_CONIC_GRADIENT_BRUSH; + #endif + + #ifdef WR_FEATURE_OPACITY_BRUSH + case BRUSH_KIND_OPACITY: return VECS_PER_OPACITY_BRUSH; + #endif + } +} + +#define BRUSH_VS_PARAMS vi, prim_address, local_rect, segment_rect, \ + prim_user_data, specific_resource_address, transform, pic_task, \ + brush_flags, texel_rect + + +#ifdef WR_VERTEX_SHADER +void multi_brush_vs( + VertexInfo vi, + int prim_address, + RectWithSize local_rect, + RectWithSize segment_rect, + ivec4 prim_user_data, + int specific_resource_address, + mat4 transform, + PictureTask pic_task, + int brush_flags, + vec4 texel_rect, + int brush_kind +) { + switch (brush_kind) { + default: + + #ifdef WR_FEATURE_IMAGE_BRUSH + case BRUSH_KIND_IMAGE: + image_brush_vs(BRUSH_VS_PARAMS); + break; + #endif + + #ifdef WR_FEATURE_SOLID_BRUSH + case BRUSH_KIND_SOLID: + solid_brush_vs(BRUSH_VS_PARAMS); + break; + #endif + + #ifdef WR_FEATURE_BLEND_BRUSH + case BRUSH_KIND_BLEND: + blend_brush_vs(BRUSH_VS_PARAMS); + break; + #endif + + #ifdef WR_FEATURE_MIX_BLEND_BRUSH + case BRUSH_KIND_MIX_BLEND: + mix_blend_brush_vs(BRUSH_VS_PARAMS); + break; + #endif + + #ifdef WR_FEATURE_LINEAR_GRADIENT_BRUSH + case BRUSH_KIND_LINEAR_GRADIENT: + linear_gradient_brush_vs(BRUSH_VS_PARAMS); + break; + #endif + + #ifdef WR_FEATURE_RADIAL_GRADIENT_BRUSH + case BRUSH_KIND_RADIAL_GRADIENT: + radial_gradient_brush_vs(BRUSH_VS_PARAMS); + break; + #endif + + #ifdef WR_FEATURE_CONIC_GRADIENT_BRUSH + case BRUSH_KIND_CONIC_GRADIENT: + conic_gradient_brush_vs(BRUSH_VS_PARAMS); + break; + #endif + + #ifdef WR_FEATURE_OPACITY_BRUSH + case BRUSH_KIND_OPACITY: + opacity_brush_vs(BRUSH_VS_PARAMS); + break; + #endif + } +} + +#endif // WR_VERTEX_SHADER + +#ifdef WR_FRAGMENT_SHADER + +Fragment multi_brush_fs(int brush_kind) { + switch (brush_kind) { + default: + + #ifdef WR_FEATURE_IMAGE_BRUSH + case BRUSH_KIND_IMAGE: return image_brush_fs(); + #endif + + #ifdef WR_FEATURE_SOLID_BRUSH + case BRUSH_KIND_SOLID: return solid_brush_fs(); + #endif + + #ifdef WR_FEATURE_BLEND_BRUSH + case BRUSH_KIND_BLEND: return blend_brush_fs(); + #endif + + #ifdef WR_FEATURE_MIX_BLEND_BRUSH + case BRUSH_KIND_MIX_BLEND: return mix_blend_brush_fs(); + #endif + + #ifdef WR_FEATURE_LINEAR_GRADIENT_BRUSH + case BRUSH_KIND_LINEAR_GRADIENT: return linear_gradient_brush_fs(); + #endif + + #ifdef WR_FEATURE_RADIAL_GRADIENT_BRUSH + case BRUSH_KIND_RADIAL_GRADIENT: return radial_gradient_brush_fs(); + #endif + + #ifdef WR_FEATURE_CONIC_GRADIENT_BRUSH + case BRUSH_KIND_CONIC_GRADIENT: return conic_gradient_brush_fs(); + #endif + + #ifdef WR_FEATURE_OPACITY_BRUSH + case BRUSH_KIND_OPACITY: return opacity_brush_fs(); + #endif + + #ifdef WR_FEATURE_TEXT_BRUSH + case BRUSH_KIND_TEXT: return text_brush_fs(); + #endif + } +} + +#endif diff --git a/third_party/webrender/webrender/res/brush_opacity.glsl b/third_party/webrender/webrender/res/brush_opacity.glsl new file mode 100644 index 00000000000..e2a0b8e1c3a --- /dev/null +++ b/third_party/webrender/webrender/res/brush_opacity.glsl @@ -0,0 +1,91 @@ +/* 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/. */ + +#define VECS_PER_OPACITY_BRUSH 3 +#define VECS_PER_SPECIFIC_BRUSH VECS_PER_OPACITY_BRUSH + +#define WR_BRUSH_VS_FUNCTION opacity_brush_vs +#define WR_BRUSH_FS_FUNCTION opacity_brush_fs + +#include shared,prim_shared,brush + +// Interpolated UV coordinates to sample. +#define V_UV varying_vec4_0.zw +#define V_LOCAL_POS varying_vec4_0.xy + +// Normalized bounds of the source image in the texture. +#define V_UV_BOUNDS flat_varying_vec4_1 + +// Layer index to sample. +#define V_LAYER flat_varying_vec4_2.x +// Flag to allow perspective interpolation of UV. +#define V_PERSPECTIVE flat_varying_vec4_2.y + +#define V_OPACITY flat_varying_vec4_2.z + +#ifdef WR_VERTEX_SHADER +void opacity_brush_vs( + VertexInfo vi, + int prim_address, + RectWithSize local_rect, + RectWithSize segment_rect, + ivec4 prim_user_data, + int specific_resource_address, + mat4 transform, + PictureTask pic_task, + int brush_flags, + vec4 unused +) { + ImageResource res = fetch_image_resource(prim_user_data.x); + vec2 uv0 = res.uv_rect.p0; + vec2 uv1 = res.uv_rect.p1; + + vec2 texture_size = vec2(textureSize(sColor0, 0).xy); + vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size; + f = get_image_quad_uv(prim_user_data.x, f); + vec2 uv = mix(uv0, uv1, f); + float perspective_interpolate = (brush_flags & BRUSH_FLAG_PERSPECTIVE_INTERPOLATION) != 0 ? 1.0 : 0.0; + + V_UV = uv / texture_size * mix(vi.world_pos.w, 1.0, perspective_interpolate); + V_LAYER = res.layer; + V_PERSPECTIVE = perspective_interpolate; + + // TODO: The image shader treats this differently: deflate the rect by half a pixel on each side and + // clamp the uv in the frame shader. Does it make sense to do the same here? + V_UV_BOUNDS = vec4(uv0, uv1) / texture_size.xyxy; + V_LOCAL_POS = vi.local_pos; + + V_OPACITY = float(prim_user_data.y) / 65536.0; +} +#endif + +#ifdef WR_FRAGMENT_SHADER +Fragment opacity_brush_fs() { + float perspective_divisor = mix(gl_FragCoord.w, 1.0, V_PERSPECTIVE); + vec2 uv = V_UV * perspective_divisor; + vec4 Cs = texture(sColor0, vec3(uv, V_LAYER)); + + // Un-premultiply the input. + float alpha = Cs.a; + vec3 color = alpha != 0.0 ? Cs.rgb / alpha : Cs.rgb; + + alpha *= V_OPACITY; + + // Fail-safe to ensure that we don't sample outside the rendered + // portion of a blend source. + alpha *= min(point_inside_rect(uv, V_UV_BOUNDS.xy, V_UV_BOUNDS.zw), + init_transform_fs(V_LOCAL_POS)); + + // Pre-multiply the alpha into the output value. + return Fragment(alpha * vec4(color, 1.0)); +} +#endif + +// Undef macro names that could be re-defined by other shaders. +#undef V_UV +#undef V_LOCAL_POS +#undef V_UV_BOUNDS +#undef V_LAYER +#undef V_PERSPECTIVE +#undef V_OPACITY diff --git a/third_party/webrender/webrender/res/brush_radial_gradient.glsl b/third_party/webrender/webrender/res/brush_radial_gradient.glsl new file mode 100644 index 00000000000..b178d606e1f --- /dev/null +++ b/third_party/webrender/webrender/res/brush_radial_gradient.glsl @@ -0,0 +1,176 @@ +/* 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/. */ + +#define VECS_PER_RADIAL_GRADIENT_BRUSH 2 +#define VECS_PER_SPECIFIC_BRUSH VECS_PER_RADIAL_GRADIENT_BRUSH + +#define WR_BRUSH_VS_FUNCTION radial_gradient_brush_vs +#define WR_BRUSH_FS_FUNCTION radial_gradient_brush_fs + +#include shared,prim_shared,brush + +#define V_GRADIENT_ADDRESS flat_varying_highp_int_address_0 + +#define V_CENTER flat_varying_vec4_0.xy +#define V_START_RADIUS flat_varying_vec4_0.z +#define V_END_RADIUS flat_varying_vec4_0.w + +#define V_REPEATED_SIZE flat_varying_vec4_1.xy +#define V_GRADIENT_REPEAT flat_varying_vec4_1.z + +#define V_POS varying_vec4_0.zw + +#ifdef WR_FEATURE_ALPHA_PASS +#define V_LOCAL_POS varying_vec4_0.xy +#define V_TILE_REPEAT flat_varying_vec4_2.xy +#endif + +#ifdef WR_VERTEX_SHADER + +struct RadialGradient { + vec4 center_start_end_radius; + float ratio_xy; + int extend_mode; + vec2 stretch_size; +}; + +RadialGradient fetch_radial_gradient(int address) { + vec4 data[2] = fetch_from_gpu_cache_2(address); + return RadialGradient( + data[0], + data[1].x, + int(data[1].y), + data[1].zw + ); +} + +void radial_gradient_brush_vs( + VertexInfo vi, + int prim_address, + RectWithSize local_rect, + RectWithSize segment_rect, + ivec4 prim_user_data, + int specific_resource_address, + mat4 transform, + PictureTask pic_task, + int brush_flags, + vec4 texel_rect +) { + RadialGradient gradient = fetch_radial_gradient(prim_address); + + if ((brush_flags & BRUSH_FLAG_SEGMENT_RELATIVE) != 0) { + V_POS = (vi.local_pos - segment_rect.p0) / segment_rect.size; + V_POS = V_POS * (texel_rect.zw - texel_rect.xy) + texel_rect.xy; + V_POS = V_POS * local_rect.size; + } else { + V_POS = vi.local_pos - local_rect.p0; + } + + V_CENTER = gradient.center_start_end_radius.xy; + V_START_RADIUS = gradient.center_start_end_radius.z; + V_END_RADIUS = gradient.center_start_end_radius.w; + + // Transform all coordinates by the y scale so the + // fragment shader can work with circles + vec2 tile_repeat = local_rect.size / gradient.stretch_size; + V_POS.y *= gradient.ratio_xy; + V_CENTER.y *= gradient.ratio_xy; + V_REPEATED_SIZE = gradient.stretch_size; + V_REPEATED_SIZE.y *= gradient.ratio_xy; + + V_GRADIENT_ADDRESS = prim_user_data.x; + + // Whether to repeat the gradient instead of clamping. + V_GRADIENT_REPEAT = float(gradient.extend_mode != EXTEND_MODE_CLAMP); + +#ifdef WR_FEATURE_ALPHA_PASS + V_TILE_REPEAT = tile_repeat.xy; + V_LOCAL_POS = vi.local_pos; +#endif +} +#endif + +#ifdef WR_FRAGMENT_SHADER +Fragment radial_gradient_brush_fs() { + +#ifdef WR_FEATURE_ALPHA_PASS + // Handle top and left inflated edges (see brush_image). + vec2 local_pos = max(V_POS, vec2(0.0)); + + // Apply potential horizontal and vertical repetitions. + vec2 pos = mod(local_pos, V_REPEATED_SIZE); + + vec2 prim_size = V_REPEATED_SIZE * V_TILE_REPEAT; + // Handle bottom and right inflated edges (see brush_image). + if (local_pos.x >= prim_size.x) { + pos.x = V_REPEATED_SIZE.x; + } + if (local_pos.y >= prim_size.y) { + pos.y = V_REPEATED_SIZE.y; + } +#else + // Apply potential horizontal and vertical repetitions. + vec2 pos = mod(V_POS, V_REPEATED_SIZE); +#endif + + vec2 pd = pos - V_CENTER; + float rd = V_END_RADIUS - V_START_RADIUS; + + // Solve for t in length(t - pd) = V_START_RADIUS + t * rd + // using a quadratic equation in form of At^2 - 2Bt + C = 0 + float A = -(rd * rd); + float B = V_START_RADIUS * rd; + float C = dot(pd, pd) - V_START_RADIUS * V_START_RADIUS; + + float offset; + if (A == 0.0) { + // Since A is 0, just solve for -2Bt + C = 0 + if (B == 0.0) { + discard; + } + float t = 0.5 * C / B; + if (V_START_RADIUS + rd * t >= 0.0) { + offset = t; + } else { + discard; + } + } else { + float discr = B * B - A * C; + if (discr < 0.0) { + discard; + } + discr = sqrt(discr); + float t0 = (B + discr) / A; + float t1 = (B - discr) / A; + if (V_START_RADIUS + rd * t0 >= 0.0) { + offset = t0; + } else if (V_START_RADIUS + rd * t1 >= 0.0) { + offset = t1; + } else { + discard; + } + } + + vec4 color = sample_gradient(V_GRADIENT_ADDRESS, + offset, + V_GRADIENT_REPEAT); + +#ifdef WR_FEATURE_ALPHA_PASS + color *= init_transform_fs(V_LOCAL_POS); +#endif + + return Fragment(color); +} +#endif + +// Undef macro names that could be re-defined by other shaders. +#undef V_GRADIENT_ADDRESS +#undef V_CENTER +#undef V_START_RADIUS +#undef V_END_RADIUS +#undef V_REPEATED_SIZE +#undef V_GRADIENT_REPEAT +#undef V_POS +#undef V_LOCAL_POS +#undef V_TILE_REPEAT diff --git a/third_party/webrender/webrender/res/brush_solid.frag.h b/third_party/webrender/webrender/res/brush_solid.frag.h new file mode 100644 index 00000000000..9f650638166 --- /dev/null +++ b/third_party/webrender/webrender/res/brush_solid.frag.h @@ -0,0 +1,15 @@ +/* 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/. */ + +ALWAYS_INLINE int draw_span(uint32_t* buf, int len) { + auto color = pack_span(buf, flat_varying_vec4_0); + commit_solid_span(buf, color, len); + return len; +} + +ALWAYS_INLINE int draw_span(uint8_t* buf, int len) { + auto color = pack_span(buf, flat_varying_vec4_0.x); + commit_solid_span(buf, color, len); + return len; +} diff --git a/third_party/webrender/webrender/res/brush_solid.glsl b/third_party/webrender/webrender/res/brush_solid.glsl new file mode 100644 index 00000000000..86713b04d12 --- /dev/null +++ b/third_party/webrender/webrender/res/brush_solid.glsl @@ -0,0 +1,65 @@ +/* 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/. */ + +#define VECS_PER_SOLID_BRUSH 1 +#define VECS_PER_SPECIFIC_BRUSH VECS_PER_SOLID_BRUSH + +#define WR_BRUSH_VS_FUNCTION solid_brush_vs +#define WR_BRUSH_FS_FUNCTION solid_brush_fs + +#include shared,prim_shared,brush + +#define V_COLOR flat_varying_vec4_0 + +#ifdef WR_FEATURE_ALPHA_PASS +#define V_LOCAL_POS varying_vec4_0.xy +#endif + +#ifdef WR_VERTEX_SHADER + +struct SolidBrush { + vec4 color; +}; + +SolidBrush fetch_solid_primitive(int address) { + vec4 data = fetch_from_gpu_cache_1(address); + return SolidBrush(data); +} + +void solid_brush_vs( + VertexInfo vi, + int prim_address, + RectWithSize local_rect, + RectWithSize segment_rect, + ivec4 prim_user_data, + int specific_resource_address, + mat4 transform, + PictureTask pic_task, + int brush_flags, + vec4 unused +) { + SolidBrush prim = fetch_solid_primitive(prim_address); + + float opacity = float(prim_user_data.x) / 65535.0; + V_COLOR = prim.color * opacity; + +#ifdef WR_FEATURE_ALPHA_PASS + V_LOCAL_POS = vi.local_pos; +#endif +} +#endif + +#ifdef WR_FRAGMENT_SHADER +Fragment solid_brush_fs() { + vec4 color = V_COLOR; +#ifdef WR_FEATURE_ALPHA_PASS + color *= init_transform_fs(V_LOCAL_POS); +#endif + return Fragment(color); +} +#endif + +// Undef macro names that could be re-defined by other shaders. +#undef V_COLOR +#undef V_LOCAL_POS diff --git a/third_party/webrender/webrender/res/brush_yuv_image.glsl b/third_party/webrender/webrender/res/brush_yuv_image.glsl new file mode 100644 index 00000000000..a8ba62731a7 --- /dev/null +++ b/third_party/webrender/webrender/res/brush_yuv_image.glsl @@ -0,0 +1,113 @@ +/* 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/. */ + +#define VECS_PER_YUV_BRUSH 1 +#define VECS_PER_SPECIFIC_BRUSH VECS_PER_YUV_BRUSH + +#define WR_BRUSH_VS_FUNCTION yuv_brush_vs +#define WR_BRUSH_FS_FUNCTION yuv_brush_fs + +#include shared,prim_shared,brush,yuv + +#ifdef WR_FEATURE_ALPHA_PASS +varying vec2 vLocalPos; +#endif + +flat varying vec3 vYuvLayers; + +varying vec2 vUv_Y; +flat varying vec4 vUvBounds_Y; + +varying vec2 vUv_U; +flat varying vec4 vUvBounds_U; + +varying vec2 vUv_V; +flat varying vec4 vUvBounds_V; + +flat varying float vCoefficient; +flat varying mat3 vYuvColorMatrix; +flat varying int vFormat; + +#ifdef WR_VERTEX_SHADER + +struct YuvPrimitive { + float coefficient; + int color_space; + int yuv_format; +}; + +YuvPrimitive fetch_yuv_primitive(int address) { + vec4 data = fetch_from_gpu_cache_1(address); + return YuvPrimitive(data.x, int(data.y), int(data.z)); +} + +void yuv_brush_vs( + VertexInfo vi, + int prim_address, + RectWithSize local_rect, + RectWithSize segment_rect, + ivec4 prim_user_data, + int specific_resource_address, + mat4 transform, + PictureTask pic_task, + int brush_flags, + vec4 unused +) { + vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size; + + YuvPrimitive prim = fetch_yuv_primitive(prim_address); + vCoefficient = prim.coefficient; + + vYuvColorMatrix = get_yuv_color_matrix(prim.color_space); + vFormat = prim.yuv_format; + +#ifdef WR_FEATURE_ALPHA_PASS + vLocalPos = vi.local_pos; +#endif + + if (vFormat == YUV_FORMAT_PLANAR) { + ImageResource res_y = fetch_image_resource(prim_user_data.x); + ImageResource res_u = fetch_image_resource(prim_user_data.y); + ImageResource res_v = fetch_image_resource(prim_user_data.z); + write_uv_rect(res_y.uv_rect.p0, res_y.uv_rect.p1, f, TEX_SIZE(sColor0), vUv_Y, vUvBounds_Y); + write_uv_rect(res_u.uv_rect.p0, res_u.uv_rect.p1, f, TEX_SIZE(sColor1), vUv_U, vUvBounds_U); + write_uv_rect(res_v.uv_rect.p0, res_v.uv_rect.p1, f, TEX_SIZE(sColor2), vUv_V, vUvBounds_V); + vYuvLayers = vec3(res_y.layer, res_u.layer, res_v.layer); + } else if (vFormat == YUV_FORMAT_NV12) { + ImageResource res_y = fetch_image_resource(prim_user_data.x); + ImageResource res_u = fetch_image_resource(prim_user_data.y); + write_uv_rect(res_y.uv_rect.p0, res_y.uv_rect.p1, f, TEX_SIZE(sColor0), vUv_Y, vUvBounds_Y); + write_uv_rect(res_u.uv_rect.p0, res_u.uv_rect.p1, f, TEX_SIZE(sColor1), vUv_U, vUvBounds_U); + vYuvLayers = vec3(res_y.layer, res_u.layer, 0.0); + } else if (vFormat == YUV_FORMAT_INTERLEAVED) { + ImageResource res_y = fetch_image_resource(prim_user_data.x); + write_uv_rect(res_y.uv_rect.p0, res_y.uv_rect.p1, f, TEX_SIZE(sColor0), vUv_Y, vUvBounds_Y); + vYuvLayers = vec3(res_y.layer, 0.0, 0.0); + } +} +#endif + +#ifdef WR_FRAGMENT_SHADER + +Fragment yuv_brush_fs() { + vec4 color = sample_yuv( + vFormat, + vYuvColorMatrix, + vCoefficient, + vYuvLayers, + vUv_Y, + vUv_U, + vUv_V, + vUvBounds_Y, + vUvBounds_U, + vUvBounds_V + ); + +#ifdef WR_FEATURE_ALPHA_PASS + color *= init_transform_fs(vLocalPos); +#endif + + return Fragment(color); +} +#endif diff --git a/third_party/webrender/webrender/res/clip_shared.glsl b/third_party/webrender/webrender/res/clip_shared.glsl new file mode 100644 index 00000000000..e5b80389687 --- /dev/null +++ b/third_party/webrender/webrender/res/clip_shared.glsl @@ -0,0 +1,97 @@ +/* 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/. */ + +#include rect,render_task,gpu_cache,transform + +#ifdef WR_VERTEX_SHADER + +PER_INSTANCE in ivec2 aTransformIds; +PER_INSTANCE in ivec4 aClipDataResourceAddress; +PER_INSTANCE in vec2 aClipLocalPos; +PER_INSTANCE in vec4 aClipTileRect; +PER_INSTANCE in vec4 aClipDeviceArea; +PER_INSTANCE in vec4 aClipOrigins; +PER_INSTANCE in float aDevicePixelScale; + +struct ClipMaskInstance { + int clip_transform_id; + int prim_transform_id; + ivec2 clip_data_address; + ivec2 resource_address; + vec2 local_pos; + RectWithSize tile_rect; + RectWithSize sub_rect; + vec2 task_origin; + vec2 screen_origin; + float device_pixel_scale; +}; + +ClipMaskInstance fetch_clip_item() { + ClipMaskInstance cmi; + + cmi.clip_transform_id = aTransformIds.x; + cmi.prim_transform_id = aTransformIds.y; + cmi.clip_data_address = aClipDataResourceAddress.xy; + cmi.resource_address = aClipDataResourceAddress.zw; + cmi.local_pos = aClipLocalPos; + cmi.tile_rect = RectWithSize(aClipTileRect.xy, aClipTileRect.zw); + cmi.sub_rect = RectWithSize(aClipDeviceArea.xy, aClipDeviceArea.zw); + cmi.task_origin = aClipOrigins.xy; + cmi.screen_origin = aClipOrigins.zw; + cmi.device_pixel_scale = aDevicePixelScale; + + return cmi; +} + +struct ClipVertexInfo { + vec4 local_pos; + RectWithSize clipped_local_rect; +}; + +RectWithSize intersect_rect(RectWithSize a, RectWithSize b) { + vec4 p = clamp(vec4(a.p0, a.p0 + a.size), b.p0.xyxy, b.p0.xyxy + b.size.xyxy); + return RectWithSize(p.xy, max(vec2(0.0), p.zw - p.xy)); +} + + +// The transformed vertex function that always covers the whole clip area, +// which is the intersection of all clip instances of a given primitive +ClipVertexInfo write_clip_tile_vertex(RectWithSize local_clip_rect, + Transform prim_transform, + Transform clip_transform, + RectWithSize sub_rect, + vec2 task_origin, + vec2 screen_origin, + float device_pixel_scale) { + vec2 device_pos = screen_origin + sub_rect.p0 + aPosition.xy * sub_rect.size; + vec2 world_pos = device_pos / device_pixel_scale; + + vec4 pos = prim_transform.m * vec4(world_pos, 0.0, 1.0); + pos.xyz /= pos.w; + + vec4 p = get_node_pos(pos.xy, clip_transform); + vec4 local_pos = p * pos.w; + + //TODO: Interpolate in clip space, where "local_pos.w" contains + // the W of the homogeneous transform *from* clip space into the world. + // float interpolate_w = 1.0 / local_pos.w; + // This is problematic today, because the W<=0 hemisphere is going to be + // clipped, while we currently want this shader to fill out the whole rect. + // We can therefore simplify this when the clip construction is rewritten + // to only affect the areas touched by a clip. + vec4 vertex_pos = vec4( + task_origin + sub_rect.p0 + aPosition.xy * sub_rect.size, + 0.0, + 1.0 + ); + + gl_Position = uTransform * vertex_pos; + + init_transform_vs(vec4(local_clip_rect.p0, local_clip_rect.p0 + local_clip_rect.size)); + + ClipVertexInfo vi = ClipVertexInfo(local_pos, local_clip_rect); + return vi; +} + +#endif //WR_VERTEX_SHADER diff --git a/third_party/webrender/webrender/res/composite.glsl b/third_party/webrender/webrender/res/composite.glsl new file mode 100644 index 00000000000..2a2f36969c7 --- /dev/null +++ b/third_party/webrender/webrender/res/composite.glsl @@ -0,0 +1,144 @@ +/* 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/. */ + +// Composite a picture cache tile into the framebuffer. + +#include shared,yuv + +#ifdef WR_FEATURE_YUV +flat varying mat3 vYuvColorMatrix; +flat varying float vYuvCoefficient; +flat varying int vYuvFormat; +flat varying vec3 vYuvLayers; +varying vec2 vUV_y; +varying vec2 vUV_u; +varying vec2 vUV_v; +flat varying vec4 vUVBounds_y; +flat varying vec4 vUVBounds_u; +flat varying vec4 vUVBounds_v; +#else +flat varying vec4 vColor; +flat varying float vLayer; +varying vec2 vUv; +flat varying vec4 vUVBounds; +#endif + +#ifdef WR_VERTEX_SHADER +// CPU side data is in CompositeInstance (gpu_types.rs) and is +// converted to GPU data using desc::COMPOSITE (renderer.rs) by +// filling vaos.composite_vao with VertexArrayKind::Composite. +PER_INSTANCE in vec4 aDeviceRect; +PER_INSTANCE in vec4 aDeviceClipRect; +PER_INSTANCE in vec4 aColor; +PER_INSTANCE in vec4 aParams; +PER_INSTANCE in vec3 aTextureLayers; + +#ifdef WR_FEATURE_YUV +// YUV treats these as a UV clip rect (clamp) +PER_INSTANCE in vec4 aUvRect0; +PER_INSTANCE in vec4 aUvRect1; +PER_INSTANCE in vec4 aUvRect2; +#else +PER_INSTANCE in vec4 aUvRect0; +#endif + +void main(void) { + // Get world position + vec2 world_pos = aDeviceRect.xy + aPosition.xy * aDeviceRect.zw; + + // Clip the position to the world space clip rect + vec2 clipped_world_pos = clamp(world_pos, aDeviceClipRect.xy, aDeviceClipRect.xy + aDeviceClipRect.zw); + + // Derive the normalized UV from the clipped vertex position + vec2 uv = (clipped_world_pos - aDeviceRect.xy) / aDeviceRect.zw; + +#ifdef WR_FEATURE_YUV + int yuv_color_space = int(aParams.y); + int yuv_format = int(aParams.z); + float yuv_coefficient = aParams.w; + + vYuvColorMatrix = get_yuv_color_matrix(yuv_color_space); + vYuvCoefficient = yuv_coefficient; + vYuvFormat = yuv_format; + + vYuvLayers = aTextureLayers.xyz; + write_uv_rect( + aUvRect0.xy, + aUvRect0.zw, + uv, + TEX_SIZE(sColor0), + vUV_y, + vUVBounds_y + ); + write_uv_rect( + aUvRect1.xy, + aUvRect1.zw, + uv, + TEX_SIZE(sColor1), + vUV_u, + vUVBounds_u + ); + write_uv_rect( + aUvRect2.xy, + aUvRect2.zw, + uv, + TEX_SIZE(sColor2), + vUV_v, + vUVBounds_v + ); +#else + vUv = mix(aUvRect0.xy, aUvRect0.zw, uv); + // flip_y might have the UV rect "upside down", make sure + // clamp works correctly: + vUVBounds = vec4(aUvRect0.x, min(aUvRect0.y, aUvRect0.w), + aUvRect0.z, max(aUvRect0.y, aUvRect0.w)); + int rescale_uv = int(aParams.y); + if (rescale_uv == 1) + { + // using an atlas, so UVs are in pixels, and need to be + // normalized and clamped. + vec2 texture_size = TEX_SIZE(sColor0); + vUVBounds += vec4(0.5, 0.5, -0.5, -0.5); + #ifndef WR_FEATURE_TEXTURE_RECT + vUv /= texture_size; + vUVBounds /= texture_size.xyxy; + #endif + } + // Pass through color and texture array layer + vColor = aColor; + vLayer = aTextureLayers.x; +#endif + + gl_Position = uTransform * vec4(clipped_world_pos, aParams.x /* z_id */, 1.0); +} +#endif + +#ifdef WR_FRAGMENT_SHADER +void main(void) { +#ifdef WR_FEATURE_YUV + vec4 color = sample_yuv( + vYuvFormat, + vYuvColorMatrix, + vYuvCoefficient, + vYuvLayers, + vUV_y, + vUV_u, + vUV_v, + vUVBounds_y, + vUVBounds_u, + vUVBounds_v + ); +#else + // The color is just the texture sample modulated by a supplied color + vec2 uv = clamp(vUv.xy, vUVBounds.xy, vUVBounds.zw); +# if defined(WR_FEATURE_TEXTURE_EXTERNAL) || defined(WR_FEATURE_TEXTURE_2D) || defined(WR_FEATURE_TEXTURE_RECT) + vec4 texel = TEX_SAMPLE(sColor0, vec3(uv, vLayer)); +# else + vec4 texel = textureLod(sColor0, vec3(uv, vLayer), 0.0); +# endif + vec4 color = vColor * texel; +#endif + write_output(color); +} +#endif diff --git a/third_party/webrender/webrender/res/cs_blur.glsl b/third_party/webrender/webrender/res/cs_blur.glsl new file mode 100644 index 00000000000..a10fe00d790 --- /dev/null +++ b/third_party/webrender/webrender/res/cs_blur.glsl @@ -0,0 +1,164 @@ +/* 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/. */ + +#include shared,prim_shared + +varying vec3 vUv; +flat varying vec4 vUvRect; +flat varying vec2 vOffsetScale; +flat varying float vSigma; +// The number of pixels on each end that we apply the blur filter over. +flat varying int vSupport; + +#ifdef WR_VERTEX_SHADER +// Applies a separable gaussian blur in one direction, as specified +// by the dir field in the blur command. + +#define DIR_HORIZONTAL 0 +#define DIR_VERTICAL 1 + +PER_INSTANCE in int aBlurRenderTaskAddress; +PER_INSTANCE in int aBlurSourceTaskAddress; +PER_INSTANCE in int aBlurDirection; + +struct BlurTask { + RenderTaskCommonData common_data; + float blur_radius; + vec2 blur_region; +}; + +BlurTask fetch_blur_task(int address) { + RenderTaskData task_data = fetch_render_task_data(address); + + BlurTask task = BlurTask( + task_data.common_data, + task_data.user_data.x, + task_data.user_data.yz + ); + + return task; +} + +void main(void) { + BlurTask blur_task = fetch_blur_task(aBlurRenderTaskAddress); + RenderTaskCommonData src_task = fetch_render_task_common_data(aBlurSourceTaskAddress); + + RectWithSize src_rect = src_task.task_rect; + RectWithSize target_rect = blur_task.common_data.task_rect; + +#if defined WR_FEATURE_COLOR_TARGET + vec2 texture_size = vec2(textureSize(sPrevPassColor, 0).xy); +#else + vec2 texture_size = vec2(textureSize(sPrevPassAlpha, 0).xy); +#endif + vUv.z = src_task.texture_layer_index; + vSigma = blur_task.blur_radius; + + // Ensure that the support is an even number of pixels to simplify the + // fragment shader logic. + // + // TODO(pcwalton): Actually make use of this fact and use the texture + // hardware for linear filtering. + vSupport = int(ceil(1.5 * blur_task.blur_radius)) * 2; + + switch (aBlurDirection) { + case DIR_HORIZONTAL: + vOffsetScale = vec2(1.0 / texture_size.x, 0.0); + break; + case DIR_VERTICAL: + vOffsetScale = vec2(0.0, 1.0 / texture_size.y); + break; + default: + vOffsetScale = vec2(0.0); + } + + vUvRect = vec4(src_rect.p0 + vec2(0.5), + src_rect.p0 + blur_task.blur_region - vec2(0.5)); + vUvRect /= texture_size.xyxy; + + vec2 pos = target_rect.p0 + target_rect.size * aPosition.xy; + + vec2 uv0 = src_rect.p0 / texture_size; + vec2 uv1 = (src_rect.p0 + src_rect.size) / texture_size; + vUv.xy = mix(uv0, uv1, aPosition.xy); + + gl_Position = uTransform * vec4(pos, 0.0, 1.0); +} +#endif + +#ifdef WR_FRAGMENT_SHADER + +#if defined WR_FEATURE_COLOR_TARGET +#define SAMPLE_TYPE vec4 +#define SAMPLE_TEXTURE(uv) texture(sPrevPassColor, uv) +#else +#define SAMPLE_TYPE float +#define SAMPLE_TEXTURE(uv) texture(sPrevPassAlpha, uv).r +#endif + +// TODO(gw): Write a fast path blur that handles smaller blur radii +// with a offset / weight uniform table and a constant +// loop iteration count! + +void main(void) { + SAMPLE_TYPE original_color = SAMPLE_TEXTURE(vUv); + + // TODO(gw): The gauss function gets NaNs when blur radius + // is zero. In the future, detect this earlier + // and skip the blur passes completely. + if (vSupport == 0) { + oFragColor = vec4(original_color); + return; + } + + // Incremental Gaussian Coefficent Calculation (See GPU Gems 3 pp. 877 - 889) + vec3 gauss_coefficient; + gauss_coefficient.x = 1.0 / (sqrt(2.0 * 3.14159265) * vSigma); + gauss_coefficient.y = exp(-0.5 / (vSigma * vSigma)); + gauss_coefficient.z = gauss_coefficient.y * gauss_coefficient.y; + + float gauss_coefficient_total = gauss_coefficient.x; + SAMPLE_TYPE avg_color = original_color * gauss_coefficient.x; + gauss_coefficient.xy *= gauss_coefficient.yz; + + // Evaluate two adjacent texels at a time. We can do this because, if c0 + // and c1 are colors of adjacent texels and k0 and k1 are arbitrary + // factors, this formula: + // + // k0 * c0 + k1 * c1 (Equation 1) + // + // is equivalent to: + // + // k1 + // (k0 + k1) * lerp(c0, c1, -------) + // k0 + k1 + // + // A texture lookup of adjacent texels evaluates this formula: + // + // lerp(c0, c1, t) + // + // for some t. So we can let `t = k1/(k0 + k1)` and effectively evaluate + // Equation 1 with a single texture lookup. + + for (int i = 1; i <= vSupport; i += 2) { + float gauss_coefficient_subtotal = gauss_coefficient.x; + gauss_coefficient.xy *= gauss_coefficient.yz; + gauss_coefficient_subtotal += gauss_coefficient.x; + float gauss_ratio = gauss_coefficient.x / gauss_coefficient_subtotal; + + vec2 offset = vOffsetScale * (float(i) + gauss_ratio); + + vec2 st0 = clamp(vUv.xy - offset, vUvRect.xy, vUvRect.zw); + avg_color += SAMPLE_TEXTURE(vec3(st0, vUv.z)) * gauss_coefficient_subtotal; + + vec2 st1 = clamp(vUv.xy + offset, vUvRect.xy, vUvRect.zw); + avg_color += SAMPLE_TEXTURE(vec3(st1, vUv.z)) * gauss_coefficient_subtotal; + + gauss_coefficient_total += 2.0 * gauss_coefficient_subtotal; + gauss_coefficient.xy *= gauss_coefficient.yz; + } + + oFragColor = vec4(avg_color) / gauss_coefficient_total; +} +#endif diff --git a/third_party/webrender/webrender/res/cs_border_segment.glsl b/third_party/webrender/webrender/res/cs_border_segment.glsl new file mode 100644 index 00000000000..9eb5155f1f6 --- /dev/null +++ b/third_party/webrender/webrender/res/cs_border_segment.glsl @@ -0,0 +1,453 @@ +/* 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/. */ + +#include shared,ellipse + +// For edges, the colors are the same. For corners, these +// are the colors of each edge making up the corner. +flat varying vec4 vColor00; +flat varying vec4 vColor01; +flat varying vec4 vColor10; +flat varying vec4 vColor11; + +// A point + tangent defining the line where the edge +// transition occurs. Used for corners only. +flat varying vec4 vColorLine; + +// x = segment, y = styles, z = edge axes, w = clip mode +// Since by default in GLES the vertex shader uses highp +// and the fragment shader uses mediump, we explicitely +// use mediump precision so we align with the default +// mediump precision in the fragment shader. +flat varying mediump ivec4 vConfig; + +// xy = Local space position of the clip center. +// zw = Scale the rect origin by this to get the outer +// corner from the segment rectangle. +flat varying vec4 vClipCenter_Sign; + +// An outer and inner elliptical radii for border +// corner clipping. +flat varying vec4 vClipRadii; + +// Reference point for determine edge clip lines. +flat varying vec4 vEdgeReference; + +// Stores widths/2 and widths/3 to save doing this in FS. +flat varying vec4 vPartialWidths; + +// Clipping parameters for dot or dash. +flat varying vec4 vClipParams1; +flat varying vec4 vClipParams2; + +// Local space position +varying vec2 vPos; + +#define SEGMENT_TOP_LEFT 0 +#define SEGMENT_TOP_RIGHT 1 +#define SEGMENT_BOTTOM_RIGHT 2 +#define SEGMENT_BOTTOM_LEFT 3 +#define SEGMENT_LEFT 4 +#define SEGMENT_TOP 5 +#define SEGMENT_RIGHT 6 +#define SEGMENT_BOTTOM 7 + +// Border styles as defined in webrender_api/types.rs +#define BORDER_STYLE_NONE 0 +#define BORDER_STYLE_SOLID 1 +#define BORDER_STYLE_DOUBLE 2 +#define BORDER_STYLE_DOTTED 3 +#define BORDER_STYLE_DASHED 4 +#define BORDER_STYLE_HIDDEN 5 +#define BORDER_STYLE_GROOVE 6 +#define BORDER_STYLE_RIDGE 7 +#define BORDER_STYLE_INSET 8 +#define BORDER_STYLE_OUTSET 9 + +#define CLIP_NONE 0 +#define CLIP_DASH_CORNER 1 +#define CLIP_DASH_EDGE 2 +#define CLIP_DOT 3 + +#ifdef WR_VERTEX_SHADER + +PER_INSTANCE in vec2 aTaskOrigin; +PER_INSTANCE in vec4 aRect; +PER_INSTANCE in vec4 aColor0; +PER_INSTANCE in vec4 aColor1; +PER_INSTANCE in int aFlags; +PER_INSTANCE in vec2 aWidths; +PER_INSTANCE in vec2 aRadii; +PER_INSTANCE in vec4 aClipParams1; +PER_INSTANCE in vec4 aClipParams2; + +vec2 get_outer_corner_scale(int segment) { + vec2 p; + + switch (segment) { + case SEGMENT_TOP_LEFT: + p = vec2(0.0, 0.0); + break; + case SEGMENT_TOP_RIGHT: + p = vec2(1.0, 0.0); + break; + case SEGMENT_BOTTOM_RIGHT: + p = vec2(1.0, 1.0); + break; + case SEGMENT_BOTTOM_LEFT: + p = vec2(0.0, 1.0); + break; + default: + // The result is only used for non-default segment cases + p = vec2(0.0); + break; + } + + return p; +} + +// NOTE(emilio): If you change this algorithm, do the same change +// in border.rs +vec4 mod_color(vec4 color, bool is_black, bool lighter) { + const float light_black = 0.7; + const float dark_black = 0.3; + + const float dark_scale = 0.66666666; + const float light_scale = 1.0; + + if (is_black) { + if (lighter) { + return vec4(vec3(light_black), color.a); + } + return vec4(vec3(dark_black), color.a); + } + + if (lighter) { + return vec4(color.rgb * light_scale, color.a); + } + return vec4(color.rgb * dark_scale, color.a); +} + +vec4[2] get_colors_for_side(vec4 color, int style) { + vec4 result[2]; + + bool is_black = color.rgb == vec3(0.0, 0.0, 0.0); + + switch (style) { + case BORDER_STYLE_GROOVE: + result[0] = mod_color(color, is_black, true); + result[1] = mod_color(color, is_black, false); + break; + case BORDER_STYLE_RIDGE: + result[0] = mod_color(color, is_black, false); + result[1] = mod_color(color, is_black, true); + break; + default: + result[0] = color; + result[1] = color; + break; + } + + return result; +} + +void main(void) { + int segment = aFlags & 0xff; + int style0 = (aFlags >> 8) & 0xff; + int style1 = (aFlags >> 16) & 0xff; + int clip_mode = (aFlags >> 24) & 0x0f; + + vec2 outer_scale = get_outer_corner_scale(segment); + vec2 outer = outer_scale * aRect.zw; + vec2 clip_sign = 1.0 - 2.0 * outer_scale; + + // Set some flags used by the FS to determine the + // orientation of the two edges in this corner. + ivec2 edge_axis = ivec2(0, 0); + // Derive the positions for the edge clips, which must be handled + // differently between corners and edges. + vec2 edge_reference = vec2(0.0); + switch (segment) { + case SEGMENT_TOP_LEFT: + edge_axis = ivec2(0, 1); + edge_reference = outer; + break; + case SEGMENT_TOP_RIGHT: + edge_axis = ivec2(1, 0); + edge_reference = vec2(outer.x - aWidths.x, outer.y); + break; + case SEGMENT_BOTTOM_RIGHT: + edge_axis = ivec2(0, 1); + edge_reference = outer - aWidths; + break; + case SEGMENT_BOTTOM_LEFT: + edge_axis = ivec2(1, 0); + edge_reference = vec2(outer.x, outer.y - aWidths.y); + break; + case SEGMENT_TOP: + case SEGMENT_BOTTOM: + edge_axis = ivec2(1, 1); + break; + case SEGMENT_LEFT: + case SEGMENT_RIGHT: + default: + break; + } + + vConfig = ivec4( + segment, + style0 | (style1 << 8), + edge_axis.x | (edge_axis.y << 8), + clip_mode + ); + vPartialWidths = vec4(aWidths / 3.0, aWidths / 2.0); + vPos = aRect.zw * aPosition.xy; + + vec4[2] color0 = get_colors_for_side(aColor0, style0); + vColor00 = color0[0]; + vColor01 = color0[1]; + vec4[2] color1 = get_colors_for_side(aColor1, style1); + vColor10 = color1[0]; + vColor11 = color1[1]; + vClipCenter_Sign = vec4(outer + clip_sign * aRadii, clip_sign); + vClipRadii = vec4(aRadii, max(aRadii - aWidths, 0.0)); + vColorLine = vec4(outer, aWidths.y * -clip_sign.y, aWidths.x * clip_sign.x); + vEdgeReference = vec4(edge_reference, edge_reference + aWidths); + vClipParams1 = aClipParams1; + vClipParams2 = aClipParams2; + + // For the case of dot and dash clips, optimize the number of pixels that + // are hit to just include the dot itself. + if (clip_mode == CLIP_DOT) { + float radius = aClipParams1.z; + + // Expand by a small amount to allow room for AA around + // the dot if it's big enough. + if (radius > 0.5) + radius += 2.0; + + vPos = vClipParams1.xy + radius * (2.0 * aPosition.xy - 1.0); + vPos = clamp(vPos, vec2(0.0), aRect.zw); + } else if (clip_mode == CLIP_DASH_CORNER) { + vec2 center = (aClipParams1.xy + aClipParams2.xy) * 0.5; + // This is a gross approximation which works out because dashes don't have + // a strong curvature and we will overshoot by inflating the geometry by + // this amount on each side (sqrt(2) * length(dash) would be enough and we + // compute 2 * approx_length(dash)). + float dash_length = length(aClipParams1.xy - aClipParams2.xy); + float width = max(aWidths.x, aWidths.y); + // expand by a small amout for AA just like we do for dots. + vec2 r = vec2(max(dash_length, width)) + 2.0; + vPos = clamp(vPos, center - r, center + r); + } + + gl_Position = uTransform * vec4(aTaskOrigin + aRect.xy + vPos, 0.0, 1.0); +} +#endif + +#ifdef WR_FRAGMENT_SHADER +vec4 evaluate_color_for_style_in_corner( + vec2 clip_relative_pos, + int style, + vec4 color0, + vec4 color1, + vec4 clip_radii, + float mix_factor, + int segment, + float aa_range +) { + switch (style) { + case BORDER_STYLE_DOUBLE: { + // Get the distances from 0.33 of the radii, and + // also 0.67 of the radii. Use these to form a + // SDF subtraction which will clip out the inside + // third of the rounded edge. + float d_radii_a = distance_to_ellipse( + clip_relative_pos, + clip_radii.xy - vPartialWidths.xy, + aa_range + ); + float d_radii_b = distance_to_ellipse( + clip_relative_pos, + clip_radii.xy - 2.0 * vPartialWidths.xy, + aa_range + ); + float d = min(-d_radii_a, d_radii_b); + color0 *= distance_aa(aa_range, d); + break; + } + case BORDER_STYLE_GROOVE: + case BORDER_STYLE_RIDGE: { + float d = distance_to_ellipse( + clip_relative_pos, + clip_radii.xy - vPartialWidths.zw, + aa_range + ); + float alpha = distance_aa(aa_range, d); + float swizzled_factor; + switch (segment) { + case SEGMENT_TOP_LEFT: swizzled_factor = 0.0; break; + case SEGMENT_TOP_RIGHT: swizzled_factor = mix_factor; break; + case SEGMENT_BOTTOM_RIGHT: swizzled_factor = 1.0; break; + case SEGMENT_BOTTOM_LEFT: swizzled_factor = 1.0 - mix_factor; break; + default: swizzled_factor = 0.0; break; + }; + vec4 c0 = mix(color1, color0, swizzled_factor); + vec4 c1 = mix(color0, color1, swizzled_factor); + color0 = mix(c0, c1, alpha); + break; + } + default: + break; + } + + return color0; +} + +vec4 evaluate_color_for_style_in_edge( + vec2 pos_vec, + int style, + vec4 color0, + vec4 color1, + float aa_range, + int edge_axis_id +) { + vec2 edge_axis = edge_axis_id != 0 ? vec2(0.0, 1.0) : vec2(1.0, 0.0); + float pos = dot(pos_vec, edge_axis); + switch (style) { + case BORDER_STYLE_DOUBLE: { + float d = -1.0; + float partial_width = dot(vPartialWidths.xy, edge_axis); + if (partial_width >= 1.0) { + vec2 ref = vec2( + dot(vEdgeReference.xy, edge_axis) + partial_width, + dot(vEdgeReference.zw, edge_axis) - partial_width + ); + d = min(pos - ref.x, ref.y - pos); + } + color0 *= distance_aa(aa_range, d); + break; + } + case BORDER_STYLE_GROOVE: + case BORDER_STYLE_RIDGE: { + float ref = dot(vEdgeReference.xy + vPartialWidths.zw, edge_axis); + float d = pos - ref; + float alpha = distance_aa(aa_range, d); + color0 = mix(color0, color1, alpha); + break; + } + default: + break; + } + + return color0; +} + +void main(void) { + float aa_range = compute_aa_range(vPos); + vec4 color0, color1; + + int segment = vConfig.x; + ivec2 style = ivec2(vConfig.y & 0xff, vConfig.y >> 8); + ivec2 edge_axis = ivec2(vConfig.z & 0xff, vConfig.z >> 8); + int clip_mode = vConfig.w; + + float mix_factor = 0.0; + if (edge_axis.x != edge_axis.y) { + float d_line = distance_to_line(vColorLine.xy, vColorLine.zw, vPos); + mix_factor = distance_aa(aa_range, -d_line); + } + + // Check if inside corner clip-region + vec2 clip_relative_pos = vPos - vClipCenter_Sign.xy; + bool in_clip_region = all(lessThan(vClipCenter_Sign.zw * clip_relative_pos, vec2(0.0))); + float d = -1.0; + + switch (clip_mode) { + case CLIP_DOT: { + // Set clip distance based or dot position and radius. + d = distance(vClipParams1.xy, vPos) - vClipParams1.z; + break; + } + case CLIP_DASH_EDGE: { + bool is_vertical = vClipParams1.x == 0.; + float half_dash = is_vertical ? vClipParams1.y : vClipParams1.x; + // We want to draw something like: + // +---+---+---+---+ + // |xxx| | |xxx| + // +---+---+---+---+ + float pos = is_vertical ? vPos.y : vPos.x; + bool in_dash = pos < half_dash || pos > 3.0 * half_dash; + if (!in_dash) { + d = 1.; + } + break; + } + case CLIP_DASH_CORNER: { + // Get SDF for the two line/tangent clip lines, + // do SDF subtract to get clip distance. + float d0 = distance_to_line(vClipParams1.xy, + vClipParams1.zw, + vPos); + float d1 = distance_to_line(vClipParams2.xy, + vClipParams2.zw, + vPos); + d = max(d0, -d1); + break; + } + case CLIP_NONE: + default: + break; + } + + if (in_clip_region) { + float d_radii_a = distance_to_ellipse(clip_relative_pos, vClipRadii.xy, aa_range); + float d_radii_b = distance_to_ellipse(clip_relative_pos, vClipRadii.zw, aa_range); + float d_radii = max(d_radii_a, -d_radii_b); + d = max(d, d_radii); + + color0 = evaluate_color_for_style_in_corner( + clip_relative_pos, + style.x, + vColor00, + vColor01, + vClipRadii, + mix_factor, + segment, + aa_range + ); + color1 = evaluate_color_for_style_in_corner( + clip_relative_pos, + style.y, + vColor10, + vColor11, + vClipRadii, + mix_factor, + segment, + aa_range + ); + } else { + color0 = evaluate_color_for_style_in_edge( + vPos, + style.x, + vColor00, + vColor01, + aa_range, + edge_axis.x + ); + color1 = evaluate_color_for_style_in_edge( + vPos, + style.y, + vColor10, + vColor11, + aa_range, + edge_axis.y + ); + } + + float alpha = distance_aa(aa_range, d); + vec4 color = mix(color0, color1, mix_factor); + oFragColor = color * alpha; +} +#endif diff --git a/third_party/webrender/webrender/res/cs_border_solid.glsl b/third_party/webrender/webrender/res/cs_border_solid.glsl new file mode 100644 index 00000000000..d5748143533 --- /dev/null +++ b/third_party/webrender/webrender/res/cs_border_solid.glsl @@ -0,0 +1,176 @@ +/* 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/. */ + +#include shared,ellipse + +#define DONT_MIX 0 +#define MIX_AA 1 +#define MIX_NO_AA 2 + +// For edges, the colors are the same. For corners, these +// are the colors of each edge making up the corner. +flat varying vec4 vColor0; +flat varying vec4 vColor1; + +// A point + tangent defining the line where the edge +// transition occurs. Used for corners only. +flat varying vec4 vColorLine; + +// A boolean indicating that we should be mixing between edge colors. +flat varying int vMixColors; + +// xy = Local space position of the clip center. +// zw = Scale the rect origin by this to get the outer +// corner from the segment rectangle. +flat varying vec4 vClipCenter_Sign; + +// An outer and inner elliptical radii for border +// corner clipping. +flat varying vec4 vClipRadii; + +// Position, scale, and radii of horizontally and vertically adjacent corner clips. +flat varying vec4 vHorizontalClipCenter_Sign; +flat varying vec2 vHorizontalClipRadii; +flat varying vec4 vVerticalClipCenter_Sign; +flat varying vec2 vVerticalClipRadii; + +// Local space position +varying vec2 vPos; + +#define SEGMENT_TOP_LEFT 0 +#define SEGMENT_TOP_RIGHT 1 +#define SEGMENT_BOTTOM_RIGHT 2 +#define SEGMENT_BOTTOM_LEFT 3 + +#ifdef WR_VERTEX_SHADER + +PER_INSTANCE in vec2 aTaskOrigin; +PER_INSTANCE in vec4 aRect; +PER_INSTANCE in vec4 aColor0; +PER_INSTANCE in vec4 aColor1; +PER_INSTANCE in int aFlags; +PER_INSTANCE in vec2 aWidths; +PER_INSTANCE in vec2 aRadii; +PER_INSTANCE in vec4 aClipParams1; +PER_INSTANCE in vec4 aClipParams2; + +vec2 get_outer_corner_scale(int segment) { + vec2 p; + + switch (segment) { + case SEGMENT_TOP_LEFT: + p = vec2(0.0, 0.0); + break; + case SEGMENT_TOP_RIGHT: + p = vec2(1.0, 0.0); + break; + case SEGMENT_BOTTOM_RIGHT: + p = vec2(1.0, 1.0); + break; + case SEGMENT_BOTTOM_LEFT: + p = vec2(0.0, 1.0); + break; + default: + // The result is only used for non-default segment cases + p = vec2(0.0); + break; + } + + return p; +} + +void main(void) { + int segment = aFlags & 0xff; + bool do_aa = ((aFlags >> 24) & 0xf0) != 0; + + vec2 outer_scale = get_outer_corner_scale(segment); + vec2 outer = outer_scale * aRect.zw; + vec2 clip_sign = 1.0 - 2.0 * outer_scale; + + int mix_colors; + switch (segment) { + case SEGMENT_TOP_LEFT: + case SEGMENT_TOP_RIGHT: + case SEGMENT_BOTTOM_RIGHT: + case SEGMENT_BOTTOM_LEFT: { + mix_colors = do_aa ? MIX_AA : MIX_NO_AA; + break; + } + default: + mix_colors = DONT_MIX; + break; + } + + vMixColors = mix_colors; + vPos = aRect.zw * aPosition.xy; + + vColor0 = aColor0; + vColor1 = aColor1; + vClipCenter_Sign = vec4(outer + clip_sign * aRadii, clip_sign); + vClipRadii = vec4(aRadii, max(aRadii - aWidths, 0.0)); + vColorLine = vec4(outer, aWidths.y * -clip_sign.y, aWidths.x * clip_sign.x); + + vec2 horizontal_clip_sign = vec2(-clip_sign.x, clip_sign.y); + vHorizontalClipCenter_Sign = vec4(aClipParams1.xy + + horizontal_clip_sign * aClipParams1.zw, + horizontal_clip_sign); + vHorizontalClipRadii = aClipParams1.zw; + + vec2 vertical_clip_sign = vec2(clip_sign.x, -clip_sign.y); + vVerticalClipCenter_Sign = vec4(aClipParams2.xy + + vertical_clip_sign * aClipParams2.zw, + vertical_clip_sign); + vVerticalClipRadii = aClipParams2.zw; + + gl_Position = uTransform * vec4(aTaskOrigin + aRect.xy + vPos, 0.0, 1.0); +} +#endif + +#ifdef WR_FRAGMENT_SHADER +void main(void) { + float aa_range = compute_aa_range(vPos); + bool do_aa = vMixColors != MIX_NO_AA; + + float mix_factor = 0.0; + if (vMixColors != DONT_MIX) { + float d_line = distance_to_line(vColorLine.xy, vColorLine.zw, vPos); + if (do_aa) { + mix_factor = distance_aa(aa_range, -d_line); + } else { + mix_factor = d_line + EPSILON >= 0. ? 1.0 : 0.0; + } + } + + // Check if inside main corner clip-region + vec2 clip_relative_pos = vPos - vClipCenter_Sign.xy; + bool in_clip_region = all(lessThan(vClipCenter_Sign.zw * clip_relative_pos, vec2(0.0))); + + float d = -1.0; + if (in_clip_region) { + float d_radii_a = distance_to_ellipse(clip_relative_pos, vClipRadii.xy, aa_range); + float d_radii_b = distance_to_ellipse(clip_relative_pos, vClipRadii.zw, aa_range); + d = max(d_radii_a, -d_radii_b); + } + + // And again for horizontally-adjacent corner + clip_relative_pos = vPos - vHorizontalClipCenter_Sign.xy; + in_clip_region = all(lessThan(vHorizontalClipCenter_Sign.zw * clip_relative_pos, vec2(0.0))); + if (in_clip_region) { + float d_radii = distance_to_ellipse(clip_relative_pos, vHorizontalClipRadii.xy, aa_range); + d = max(d_radii, d); + } + + // And finally for vertically-adjacent corner + clip_relative_pos = vPos - vVerticalClipCenter_Sign.xy; + in_clip_region = all(lessThan(vVerticalClipCenter_Sign.zw * clip_relative_pos, vec2(0.0))); + if (in_clip_region) { + float d_radii = distance_to_ellipse(clip_relative_pos, vVerticalClipRadii.xy, aa_range); + d = max(d_radii, d); + } + + float alpha = do_aa ? distance_aa(aa_range, d) : 1.0; + vec4 color = mix(vColor0, vColor1, mix_factor); + oFragColor = color * alpha; +} +#endif diff --git a/third_party/webrender/webrender/res/cs_clip_box_shadow.glsl b/third_party/webrender/webrender/res/cs_clip_box_shadow.glsl new file mode 100644 index 00000000000..5b4937139ef --- /dev/null +++ b/third_party/webrender/webrender/res/cs_clip_box_shadow.glsl @@ -0,0 +1,121 @@ +/* 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/. */ + +#include shared,clip_shared + +varying vec4 vLocalPos; +varying vec2 vUv; +flat varying vec4 vUvBounds; +flat varying float vLayer; +flat varying vec4 vEdge; +flat varying vec4 vUvBounds_NoClamp; +flat varying float vClipMode; + +#define MODE_STRETCH 0 +#define MODE_SIMPLE 1 + +#ifdef WR_VERTEX_SHADER + +struct BoxShadowData { + vec2 src_rect_size; + float clip_mode; + int stretch_mode_x; + int stretch_mode_y; + RectWithSize dest_rect; +}; + +BoxShadowData fetch_data(ivec2 address) { + vec4 data[3] = fetch_from_gpu_cache_3_direct(address); + RectWithSize dest_rect = RectWithSize(data[2].xy, data[2].zw); + BoxShadowData bs_data = BoxShadowData( + data[0].xy, + data[0].z, + int(data[1].x), + int(data[1].y), + dest_rect + ); + return bs_data; +} + +void main(void) { + ClipMaskInstance cmi = fetch_clip_item(); + Transform clip_transform = fetch_transform(cmi.clip_transform_id); + Transform prim_transform = fetch_transform(cmi.prim_transform_id); + BoxShadowData bs_data = fetch_data(cmi.clip_data_address); + ImageResource res = fetch_image_resource_direct(cmi.resource_address); + + RectWithSize dest_rect = bs_data.dest_rect; + + ClipVertexInfo vi = write_clip_tile_vertex( + dest_rect, + prim_transform, + clip_transform, + cmi.sub_rect, + cmi.task_origin, + cmi.screen_origin, + cmi.device_pixel_scale + ); + vLayer = res.layer; + vClipMode = bs_data.clip_mode; + + vec2 texture_size = vec2(textureSize(sColor0, 0)); + vec2 local_pos = vi.local_pos.xy / vi.local_pos.w; + vLocalPos = vi.local_pos; + + switch (bs_data.stretch_mode_x) { + case MODE_STRETCH: { + vEdge.x = 0.5; + vEdge.z = (dest_rect.size.x / bs_data.src_rect_size.x) - 0.5; + vUv.x = (local_pos.x - dest_rect.p0.x) / bs_data.src_rect_size.x; + break; + } + case MODE_SIMPLE: + default: { + vEdge.xz = vec2(1.0); + vUv.x = (local_pos.x - dest_rect.p0.x) / dest_rect.size.x; + break; + } + } + + switch (bs_data.stretch_mode_y) { + case MODE_STRETCH: { + vEdge.y = 0.5; + vEdge.w = (dest_rect.size.y / bs_data.src_rect_size.y) - 0.5; + vUv.y = (local_pos.y - dest_rect.p0.y) / bs_data.src_rect_size.y; + break; + } + case MODE_SIMPLE: + default: { + vEdge.yw = vec2(1.0); + vUv.y = (local_pos.y - dest_rect.p0.y) / dest_rect.size.y; + break; + } + } + + vUv *= vi.local_pos.w; + vec2 uv0 = res.uv_rect.p0; + vec2 uv1 = res.uv_rect.p1; + vUvBounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) / texture_size.xyxy; + vUvBounds_NoClamp = vec4(uv0, uv1) / texture_size.xyxy; +} +#endif + +#ifdef WR_FRAGMENT_SHADER +void main(void) { + vec2 uv_linear = vUv / vLocalPos.w; + vec2 uv = clamp(uv_linear, vec2(0.0), vEdge.xy); + uv += max(vec2(0.0), uv_linear - vEdge.zw); + uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv); + uv = clamp(uv, vUvBounds.xy, vUvBounds.zw); + + float in_shadow_rect = init_transform_rough_fs(vLocalPos.xy / vLocalPos.w); + + float texel = TEX_SAMPLE(sColor0, vec3(uv, vLayer)).r; + + float alpha = mix(texel, 1.0 - texel, vClipMode); + float result = vLocalPos.w > 0.0 ? mix(vClipMode, alpha, in_shadow_rect) : 0.0; + + oFragColor = vec4(result); +} +#endif diff --git a/third_party/webrender/webrender/res/cs_clip_image.glsl b/third_party/webrender/webrender/res/cs_clip_image.glsl new file mode 100644 index 00000000000..918223343d2 --- /dev/null +++ b/third_party/webrender/webrender/res/cs_clip_image.glsl @@ -0,0 +1,73 @@ +/* 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/. */ + +#include shared,clip_shared + +varying vec4 vLocalPos; +varying vec2 vClipMaskImageUv; + +flat varying vec4 vClipMaskUvRect; +flat varying vec4 vClipMaskUvInnerRect; +flat varying float vLayer; + +#ifdef WR_VERTEX_SHADER +struct ImageMaskData { + vec2 local_mask_size; +}; + +ImageMaskData fetch_mask_data(ivec2 address) { + vec4 data = fetch_from_gpu_cache_1_direct(address); + ImageMaskData mask_data = ImageMaskData(data.xy); + return mask_data; +} + +void main(void) { + ClipMaskInstance cmi = fetch_clip_item(); + Transform clip_transform = fetch_transform(cmi.clip_transform_id); + Transform prim_transform = fetch_transform(cmi.prim_transform_id); + ImageMaskData mask = fetch_mask_data(cmi.clip_data_address); + RectWithSize local_rect = RectWithSize(cmi.local_pos, mask.local_mask_size); + ImageResource res = fetch_image_resource_direct(cmi.resource_address); + + ClipVertexInfo vi = write_clip_tile_vertex( + local_rect, + prim_transform, + clip_transform, + cmi.sub_rect, + cmi.task_origin, + cmi.screen_origin, + cmi.device_pixel_scale + ); + vLocalPos = vi.local_pos; + vLayer = res.layer; + vClipMaskImageUv = (vi.local_pos.xy - cmi.tile_rect.p0 * vi.local_pos.w) / cmi.tile_rect.size; + + vec2 texture_size = vec2(textureSize(sColor0, 0)); + vClipMaskUvRect = vec4(res.uv_rect.p0, res.uv_rect.p1 - res.uv_rect.p0) / texture_size.xyxy; + // applying a half-texel offset to the UV boundaries to prevent linear samples from the outside + vec4 inner_rect = vec4(res.uv_rect.p0, res.uv_rect.p1); + vClipMaskUvInnerRect = (inner_rect + vec4(0.5, 0.5, -0.5, -0.5)) / texture_size.xyxy; +} +#endif + +#ifdef WR_FRAGMENT_SHADER +void main(void) { + vec2 local_pos = vLocalPos.xy / vLocalPos.w; + float alpha = vLocalPos.w > 0.0 ? init_transform_fs(local_pos) : 0.0; + + // TODO: Handle repeating masks? + vec2 clamped_mask_uv = clamp(vClipMaskImageUv, vec2(0.0, 0.0), vLocalPos.ww); + + // Ensure we don't draw outside of our tile. + // FIXME(emilio): Can we do this earlier? + if (clamped_mask_uv != vClipMaskImageUv) + discard; + + vec2 source_uv = clamp( + clamped_mask_uv / vLocalPos.w * vClipMaskUvRect.zw + vClipMaskUvRect.xy, + vClipMaskUvInnerRect.xy, vClipMaskUvInnerRect.zw); + float clip_alpha = texture(sColor0, vec3(source_uv, vLayer)).r; //careful: texture has type A8 + oFragColor = vec4(alpha * clip_alpha, 1.0, 1.0, 1.0); +} +#endif diff --git a/third_party/webrender/webrender/res/cs_clip_rectangle.glsl b/third_party/webrender/webrender/res/cs_clip_rectangle.glsl new file mode 100644 index 00000000000..dfbf60ed41f --- /dev/null +++ b/third_party/webrender/webrender/res/cs_clip_rectangle.glsl @@ -0,0 +1,158 @@ +/* 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/. */ + +#include shared,clip_shared,ellipse + +varying vec4 vLocalPos; +#ifdef WR_FEATURE_FAST_PATH +flat varying vec3 vClipParams; // xy = box size, z = radius +#else +flat varying vec4 vClipCenter_Radius_TL; +flat varying vec4 vClipCenter_Radius_TR; +flat varying vec4 vClipCenter_Radius_BL; +flat varying vec4 vClipCenter_Radius_BR; +#endif + +flat varying float vClipMode; + +#ifdef WR_VERTEX_SHADER +struct ClipRect { + RectWithSize rect; + vec4 mode; +}; + +ClipRect fetch_clip_rect(ivec2 address) { + vec4 data[2] = fetch_from_gpu_cache_2_direct(address); + ClipRect rect = ClipRect(RectWithSize(data[0].xy, data[0].zw), data[1]); + return rect; +} + +struct ClipCorner { + RectWithSize rect; + vec4 outer_inner_radius; +}; + +// index is of type float instead of int because using an int led to shader +// miscompilations with a macOS 10.12 Intel driver. +ClipCorner fetch_clip_corner(ivec2 address, float index) { + address += ivec2(2 + 2 * int(index), 0); + vec4 data[2] = fetch_from_gpu_cache_2_direct(address); + ClipCorner corner = ClipCorner(RectWithSize(data[0].xy, data[0].zw), data[1]); + return corner; +} + +struct ClipData { + ClipRect rect; + ClipCorner top_left; + ClipCorner top_right; + ClipCorner bottom_left; + ClipCorner bottom_right; +}; + +ClipData fetch_clip(ivec2 address) { + ClipData clip; + + clip.rect = fetch_clip_rect(address); + clip.top_left = fetch_clip_corner(address, 0.0); + clip.top_right = fetch_clip_corner(address, 1.0); + clip.bottom_left = fetch_clip_corner(address, 2.0); + clip.bottom_right = fetch_clip_corner(address, 3.0); + + return clip; +} + +void main(void) { + ClipMaskInstance cmi = fetch_clip_item(); + Transform clip_transform = fetch_transform(cmi.clip_transform_id); + Transform prim_transform = fetch_transform(cmi.prim_transform_id); + ClipData clip = fetch_clip(cmi.clip_data_address); + + RectWithSize local_rect = clip.rect.rect; + local_rect.p0 = cmi.local_pos; + + ClipVertexInfo vi = write_clip_tile_vertex( + local_rect, + prim_transform, + clip_transform, + cmi.sub_rect, + cmi.task_origin, + cmi.screen_origin, + cmi.device_pixel_scale + ); + + vClipMode = clip.rect.mode.x; + vLocalPos = vi.local_pos; + +#ifdef WR_FEATURE_FAST_PATH + // If the radii are all uniform, we can use a much simpler 2d + // signed distance function to get a rounded rect clip. + vec2 half_size = 0.5 * local_rect.size; + float radius = clip.top_left.outer_inner_radius.x; + vLocalPos.xy -= (half_size + cmi.local_pos) * vi.local_pos.w; + vClipParams = vec3(half_size - vec2(radius), radius); +#else + RectWithEndpoint clip_rect = to_rect_with_endpoint(local_rect); + + vec2 r_tl = clip.top_left.outer_inner_radius.xy; + vec2 r_tr = clip.top_right.outer_inner_radius.xy; + vec2 r_br = clip.bottom_right.outer_inner_radius.xy; + vec2 r_bl = clip.bottom_left.outer_inner_radius.xy; + + vClipCenter_Radius_TL = vec4(clip_rect.p0 + r_tl, r_tl); + + vClipCenter_Radius_TR = vec4(clip_rect.p1.x - r_tr.x, + clip_rect.p0.y + r_tr.y, + r_tr); + + vClipCenter_Radius_BR = vec4(clip_rect.p1 - r_br, r_br); + + vClipCenter_Radius_BL = vec4(clip_rect.p0.x + r_bl.x, + clip_rect.p1.y - r_bl.y, + r_bl); +#endif +} +#endif + +#ifdef WR_FRAGMENT_SHADER + +#ifdef WR_FEATURE_FAST_PATH +// See http://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm +float sd_box(in vec2 pos, in vec2 box_size) { + vec2 d = abs(pos) - box_size; + return length(max(d, vec2(0.0))) + min(max(d.x,d.y), 0.0); +} + +float sd_rounded_box(in vec2 pos, in vec2 box_size, in float radius) { + return sd_box(pos, box_size) - radius; +} +#endif + +void main(void) { + vec2 local_pos = vLocalPos.xy / vLocalPos.w; + float aa_range = compute_aa_range(local_pos); + +#ifdef WR_FEATURE_FAST_PATH + float d = sd_rounded_box(local_pos, vClipParams.xy, vClipParams.z); + float f = distance_aa(aa_range, d); + float final_alpha = mix(f, 1.0 - f, vClipMode); +#else + float alpha = init_transform_fs(local_pos); + + float clip_alpha = rounded_rect(local_pos, + vClipCenter_Radius_TL, + vClipCenter_Radius_TR, + vClipCenter_Radius_BR, + vClipCenter_Radius_BL, + aa_range); + + float combined_alpha = alpha * clip_alpha; + + // Select alpha or inverse alpha depending on clip in/out. + float final_alpha = mix(combined_alpha, 1.0 - combined_alpha, vClipMode); +#endif + + float final_final_alpha = vLocalPos.w > 0.0 ? final_alpha : 0.0; + oFragColor = vec4(final_final_alpha, 0.0, 0.0, 1.0); +} +#endif diff --git a/third_party/webrender/webrender/res/cs_gradient.glsl b/third_party/webrender/webrender/res/cs_gradient.glsl new file mode 100644 index 00000000000..6e6b5c4ad06 --- /dev/null +++ b/third_party/webrender/webrender/res/cs_gradient.glsl @@ -0,0 +1,56 @@ +/* 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/. */ + +#include shared + +varying float vPos; +flat varying vec4 vStops; +flat varying vec4 vColor0; +flat varying vec4 vColor1; +flat varying vec4 vColor2; +flat varying vec4 vColor3; + +#ifdef WR_VERTEX_SHADER + +PER_INSTANCE in vec4 aTaskRect; +PER_INSTANCE in float aAxisSelect; +PER_INSTANCE in vec4 aStops; +PER_INSTANCE in vec4 aColor0; +PER_INSTANCE in vec4 aColor1; +PER_INSTANCE in vec4 aColor2; +PER_INSTANCE in vec4 aColor3; +PER_INSTANCE in vec2 aStartStop; + +void main(void) { + vPos = mix(aStartStop.x, aStartStop.y, mix(aPosition.x, aPosition.y, aAxisSelect)); + + vStops = aStops; + vColor0 = aColor0; + vColor1 = aColor1; + vColor2 = aColor2; + vColor3 = aColor3; + + gl_Position = uTransform * vec4(aTaskRect.xy + aTaskRect.zw * aPosition.xy, 0.0, 1.0); +} +#endif + +#ifdef WR_FRAGMENT_SHADER +float linear_step(float edge0, float edge1, float x) { + if (edge0 >= edge1) { + return 0.0; + } + + return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); +} + +void main(void) { + vec4 color = vColor0; + + color = mix(color, vColor1, linear_step(vStops.x, vStops.y, vPos)); + color = mix(color, vColor2, linear_step(vStops.y, vStops.z, vPos)); + color = mix(color, vColor3, linear_step(vStops.z, vStops.w, vPos)); + + oFragColor = color; +} +#endif diff --git a/third_party/webrender/webrender/res/cs_line_decoration.glsl b/third_party/webrender/webrender/res/cs_line_decoration.glsl new file mode 100644 index 00000000000..7063bcffb12 --- /dev/null +++ b/third_party/webrender/webrender/res/cs_line_decoration.glsl @@ -0,0 +1,163 @@ +/* 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/. */ + +#include shared + +#define LINE_STYLE_SOLID 0 +#define LINE_STYLE_DOTTED 1 +#define LINE_STYLE_DASHED 2 +#define LINE_STYLE_WAVY 3 + +// Fragment position in the coordinate system used for positioning decorations. +// To keep the code independent of whether the line is horizontal or vertical, +// vLocalPos.x is always parallel, and .y always perpendicular, to the line +// being decorated. +varying vec2 vLocalPos; + +flat varying int vStyle; +flat varying vec4 vParams; + +#ifdef WR_VERTEX_SHADER + +// The size of the mask tile we're rendering, in pixels. +PER_INSTANCE in vec4 aTaskRect; + +// The size of the mask tile. aLocalSize.x is always horizontal and .y vertical, +// regardless of the line's orientation. The size is chosen by +// prim_store::get_line_decoration_sizes. +PER_INSTANCE in vec2 aLocalSize; + +// A LINE_STYLE_* value, indicating what sort of line to draw. +PER_INSTANCE in int aStyle; + +// 0.0 for a horizontal line, 1.0 for a vertical line. +PER_INSTANCE in float aAxisSelect; + +// The thickness of the wavy line itself, not the amplitude of the waves (i.e., +// the thickness of the final decorated line). +PER_INSTANCE in float aWavyLineThickness; + +void main(void) { + vec2 size = mix(aLocalSize, aLocalSize.yx, aAxisSelect); + vStyle = aStyle; + + switch (vStyle) { + case LINE_STYLE_SOLID: { + break; + } + case LINE_STYLE_DASHED: { + vParams = vec4(size.x, // period + 0.5 * size.x, // dash length + 0.0, + 0.0); + break; + } + case LINE_STYLE_DOTTED: { + float diameter = size.y; + float period = diameter * 2.0; + float center_line = 0.5 * size.y; + vParams = vec4(period, + diameter / 2.0, // radius + center_line, + 0.0); + break; + } + case LINE_STYLE_WAVY: { + // This logic copied from gecko to get the same results + float line_thickness = max(aWavyLineThickness, 1.0); + // Difference in height between peaks and troughs + // (and since slopes are 45 degrees, the length of each slope) + float slope_length = size.y - line_thickness; + // Length of flat runs + float flat_length = max((line_thickness - 1.0) * 2.0, 1.0); + + vParams = vec4(line_thickness / 2.0, + slope_length, + flat_length, + size.y); + break; + } + default: + vParams = vec4(0.0); + } + + vLocalPos = mix(aPosition.xy, aPosition.yx, aAxisSelect) * size; + + gl_Position = uTransform * vec4(aTaskRect.xy + aTaskRect.zw * aPosition.xy, 0.0, 1.0); +} +#endif + +#ifdef WR_FRAGMENT_SHADER + +#define MAGIC_WAVY_LINE_AA_SNAP 0.5 + +void main(void) { + // Find the appropriate distance to apply the step over. + vec2 pos = vLocalPos; + float aa_range = compute_aa_range(pos); + float alpha = 1.0; + + switch (vStyle) { + case LINE_STYLE_SOLID: { + break; + } + case LINE_STYLE_DASHED: { + // Calculate dash alpha (on/off) based on dash length + alpha = step(floor(pos.x + 0.5), vParams.y); + break; + } + case LINE_STYLE_DOTTED: { + // Get the dot alpha + vec2 dot_relative_pos = pos - vParams.yz; + float dot_distance = length(dot_relative_pos) - vParams.y; + alpha = distance_aa(aa_range, dot_distance); + break; + } + case LINE_STYLE_WAVY: { + float half_line_thickness = vParams.x; + float slope_length = vParams.y; + float flat_length = vParams.z; + float vertical_bounds = vParams.w; + // Our pattern is just two slopes and two flats + float half_period = slope_length + flat_length; + + float mid_height = vertical_bounds / 2.0; + float peak_offset = mid_height - half_line_thickness; + // Flip the wave every half period + float flip = -2.0 * (step(mod(pos.x, 2.0 * half_period), half_period) - 0.5); + // float flip = -1.0; + peak_offset *= flip; + float peak_height = mid_height + peak_offset; + + // Convert pos to a local position within one half period + pos.x = mod(pos.x, half_period); + + // Compute signed distance to the 3 lines that make up an arc + float dist1 = distance_to_line(vec2(0.0, peak_height), + vec2(1.0, -flip), + pos); + float dist2 = distance_to_line(vec2(0.0, peak_height), + vec2(0, -flip), + pos); + float dist3 = distance_to_line(vec2(flat_length, peak_height), + vec2(-1.0, -flip), + pos); + float dist = abs(max(max(dist1, dist2), dist3)); + + // Apply AA based on the thickness of the wave + alpha = distance_aa(aa_range, dist - half_line_thickness); + + // Disable AA for thin lines + if (half_line_thickness <= 1.0) { + alpha = 1.0 - step(alpha, MAGIC_WAVY_LINE_AA_SNAP); + } + + break; + } + default: break; + } + + oFragColor = vec4(alpha); +} +#endif diff --git a/third_party/webrender/webrender/res/cs_scale.glsl b/third_party/webrender/webrender/res/cs_scale.glsl new file mode 100644 index 00000000000..6d0c0e59982 --- /dev/null +++ b/third_party/webrender/webrender/res/cs_scale.glsl @@ -0,0 +1,40 @@ +/* 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/. */ + +#include shared,prim_shared + +varying vec3 vUv; +flat varying vec4 vUvRect; + +#ifdef WR_VERTEX_SHADER + +PER_INSTANCE in vec4 aScaleTargetRect; +PER_INSTANCE in ivec4 aScaleSourceRect; +PER_INSTANCE in int aScaleSourceLayer; + +void main(void) { + RectWithSize src_rect = RectWithSize(vec2(aScaleSourceRect.xy), vec2(aScaleSourceRect.zw)); + + vec2 texture_size = vec2(textureSize(sColor0, 0).xy); + vUv.z = float(aScaleSourceLayer); + + vUvRect = vec4(src_rect.p0 + vec2(0.5), + src_rect.p0 + src_rect.size - vec2(0.5)) / texture_size.xyxy; + + vec2 pos = aScaleTargetRect.xy + aScaleTargetRect.zw * aPosition.xy; + vUv.xy = (src_rect.p0 + src_rect.size * aPosition.xy) / texture_size; + + gl_Position = uTransform * vec4(pos, 0.0, 1.0); +} + +#endif + +#ifdef WR_FRAGMENT_SHADER + +void main(void) { + vec2 st = clamp(vUv.xy, vUvRect.xy, vUvRect.zw); + oFragColor = texture(sColor0, vec3(st, vUv.z)); +} + +#endif diff --git a/third_party/webrender/webrender/res/cs_svg_filter.glsl b/third_party/webrender/webrender/res/cs_svg_filter.glsl new file mode 100644 index 00000000000..cfb6dd13cfa --- /dev/null +++ b/third_party/webrender/webrender/res/cs_svg_filter.glsl @@ -0,0 +1,591 @@ +/* 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/. */ + +#include shared,prim_shared + +varying vec3 vInput1Uv; +varying vec3 vInput2Uv; +flat varying vec4 vInput1UvRect; +flat varying vec4 vInput2UvRect; +flat varying int vFilterInputCount; +flat varying int vFilterKind; +flat varying ivec4 vData; +flat varying vec4 vFilterData0; +flat varying vec4 vFilterData1; +flat varying float vFloat0; +flat varying mat4 vColorMat; +flat varying int vFuncs[4]; + +#define FILTER_BLEND 0 +#define FILTER_FLOOD 1 +#define FILTER_LINEAR_TO_SRGB 2 +#define FILTER_SRGB_TO_LINEAR 3 +#define FILTER_OPACITY 4 +#define FILTER_COLOR_MATRIX 5 +#define FILTER_DROP_SHADOW 6 +#define FILTER_OFFSET 7 +#define FILTER_COMPONENT_TRANSFER 8 +#define FILTER_IDENTITY 9 +#define FILTER_COMPOSITE 10 + +#define COMPOSITE_OVER 0 +#define COMPOSITE_IN 1 +#define COMPOSITE_OUT 2 +#define COMPOSITE_ATOP 3 +#define COMPOSITE_XOR 4 +#define COMPOSITE_LIGHTER 5 +#define COMPOSITE_ARITHMETIC 6 + +#ifdef WR_VERTEX_SHADER + +PER_INSTANCE in int aFilterRenderTaskAddress; +PER_INSTANCE in int aFilterInput1TaskAddress; +PER_INSTANCE in int aFilterInput2TaskAddress; +PER_INSTANCE in int aFilterKind; +PER_INSTANCE in int aFilterInputCount; +PER_INSTANCE in int aFilterGenericInt; +PER_INSTANCE in ivec2 aFilterExtraDataAddress; + +struct FilterTask { + RenderTaskCommonData common_data; + vec3 user_data; +}; + +FilterTask fetch_filter_task(int address) { + RenderTaskData task_data = fetch_render_task_data(address); + + FilterTask task = FilterTask( + task_data.common_data, + task_data.user_data.xyz + ); + + return task; +} + +vec4 compute_uv_rect(RenderTaskCommonData task, vec2 texture_size) { + RectWithSize task_rect = task.task_rect; + + vec4 uvRect = vec4(task_rect.p0 + vec2(0.5), + task_rect.p0 + task_rect.size - vec2(0.5)); + uvRect /= texture_size.xyxy; + return uvRect; +} + +vec3 compute_uv(RenderTaskCommonData task, vec2 texture_size) { + RectWithSize task_rect = task.task_rect; + vec3 uv = vec3(0.0, 0.0, task.texture_layer_index); + + vec2 uv0 = task_rect.p0 / texture_size; + vec2 uv1 = floor(task_rect.p0 + task_rect.size) / texture_size; + uv.xy = mix(uv0, uv1, aPosition.xy); + + return uv; +} + +void main(void) { + FilterTask filter_task = fetch_filter_task(aFilterRenderTaskAddress); + RectWithSize target_rect = filter_task.common_data.task_rect; + + vec2 pos = target_rect.p0 + target_rect.size * aPosition.xy; + + RenderTaskCommonData input_1_task; + if (aFilterInputCount > 0) { + vec2 texture_size = vec2(textureSize(sColor0, 0).xy); + input_1_task = fetch_render_task_common_data(aFilterInput1TaskAddress); + vInput1UvRect = compute_uv_rect(input_1_task, texture_size); + vInput1Uv = compute_uv(input_1_task, texture_size); + } + + RenderTaskCommonData input_2_task; + if (aFilterInputCount > 1) { + vec2 texture_size = vec2(textureSize(sColor1, 0).xy); + input_2_task = fetch_render_task_common_data(aFilterInput2TaskAddress); + vInput2UvRect = compute_uv_rect(input_2_task, texture_size); + vInput2Uv = compute_uv(input_2_task, texture_size); + } + + vFilterInputCount = aFilterInputCount; + vFilterKind = aFilterKind; + + // This assignment is only used for component transfer filters but this + // assignment has to be done here and not in the component transfer case + // below because it doesn't get executed on Windows because of a suspected + // miscompile of this shader on Windows. See + // https://github.com/servo/webrender/wiki/Driver-issues#bug-1505871---assignment-to-varying-flat-arrays-inside-switch-statement-of-vertex-shader-suspected-miscompile-on-windows + // default: just to satisfy angle_shader_validation.rs which needs one + // default: for every switch, even in comments. + vFuncs[0] = (aFilterGenericInt >> 12) & 0xf; // R + vFuncs[1] = (aFilterGenericInt >> 8) & 0xf; // G + vFuncs[2] = (aFilterGenericInt >> 4) & 0xf; // B + vFuncs[3] = (aFilterGenericInt) & 0xf; // A + + switch (aFilterKind) { + case FILTER_BLEND: + vData = ivec4(aFilterGenericInt, 0, 0, 0); + break; + case FILTER_FLOOD: + vFilterData0 = fetch_from_gpu_cache_1_direct(aFilterExtraDataAddress); + break; + case FILTER_OPACITY: + vFloat0 = filter_task.user_data.x; + break; + case FILTER_COLOR_MATRIX: + vec4 mat_data[4] = fetch_from_gpu_cache_4_direct(aFilterExtraDataAddress); + vColorMat = mat4(mat_data[0], mat_data[1], mat_data[2], mat_data[3]); + vFilterData0 = fetch_from_gpu_cache_1_direct(aFilterExtraDataAddress + ivec2(4, 0)); + break; + case FILTER_DROP_SHADOW: + vFilterData0 = fetch_from_gpu_cache_1_direct(aFilterExtraDataAddress); + break; + case FILTER_OFFSET: + vec2 texture_size = vec2(textureSize(sColor0, 0).xy); + vFilterData0 = vec4(-filter_task.user_data.xy / texture_size, vec2(0.0)); + + RectWithSize task_rect = input_1_task.task_rect; + vec4 clipRect = vec4(task_rect.p0, task_rect.p0 + task_rect.size); + clipRect /= texture_size.xyxy; + vFilterData1 = clipRect; + break; + case FILTER_COMPONENT_TRANSFER: + vData = ivec4(aFilterExtraDataAddress, 0, 0); + break; + case FILTER_COMPOSITE: + vData = ivec4(aFilterGenericInt, 0, 0, 0); + if (aFilterGenericInt == COMPOSITE_ARITHMETIC) { + vFilterData0 = fetch_from_gpu_cache_1_direct(aFilterExtraDataAddress); + } + break; + default: + break; + } + + gl_Position = uTransform * vec4(pos, 0.0, 1.0); +} +#endif + +#ifdef WR_FRAGMENT_SHADER + +#define COMPONENT_TRANSFER_IDENTITY 0 +#define COMPONENT_TRANSFER_TABLE 1 +#define COMPONENT_TRANSFER_DISCRETE 2 +#define COMPONENT_TRANSFER_LINEAR 3 +#define COMPONENT_TRANSFER_GAMMA 4 + +vec3 Multiply(vec3 Cb, vec3 Cs) { + return Cb * Cs; +} + +vec3 Screen(vec3 Cb, vec3 Cs) { + return Cb + Cs - (Cb * Cs); +} + +vec3 HardLight(vec3 Cb, vec3 Cs) { + vec3 m = Multiply(Cb, 2.0 * Cs); + vec3 s = Screen(Cb, 2.0 * Cs - 1.0); + vec3 edge = vec3(0.5, 0.5, 0.5); + return mix(m, s, step(edge, Cs)); +} + +// TODO: Worth doing with mix/step? Check GLSL output. +float ColorDodge(float Cb, float Cs) { + if (Cb == 0.0) + return 0.0; + else if (Cs == 1.0) + return 1.0; + else + return min(1.0, Cb / (1.0 - Cs)); +} + +// TODO: Worth doing with mix/step? Check GLSL output. +float ColorBurn(float Cb, float Cs) { + if (Cb == 1.0) + return 1.0; + else if (Cs == 0.0) + return 0.0; + else + return 1.0 - min(1.0, (1.0 - Cb) / Cs); +} + +float SoftLight(float Cb, float Cs) { + if (Cs <= 0.5) { + return Cb - (1.0 - 2.0 * Cs) * Cb * (1.0 - Cb); + } else { + float D; + + if (Cb <= 0.25) + D = ((16.0 * Cb - 12.0) * Cb + 4.0) * Cb; + else + D = sqrt(Cb); + + return Cb + (2.0 * Cs - 1.0) * (D - Cb); + } +} + +vec3 Difference(vec3 Cb, vec3 Cs) { + return abs(Cb - Cs); +} + +vec3 Exclusion(vec3 Cb, vec3 Cs) { + return Cb + Cs - 2.0 * Cb * Cs; +} + +// These functions below are taken from the spec. +// There's probably a much quicker way to implement +// them in GLSL... +float Sat(vec3 c) { + return max(c.r, max(c.g, c.b)) - min(c.r, min(c.g, c.b)); +} + +float Lum(vec3 c) { + vec3 f = vec3(0.3, 0.59, 0.11); + return dot(c, f); +} + +vec3 ClipColor(vec3 C) { + float L = Lum(C); + float n = min(C.r, min(C.g, C.b)); + float x = max(C.r, max(C.g, C.b)); + + if (n < 0.0) + C = L + (((C - L) * L) / (L - n)); + + if (x > 1.0) + C = L + (((C - L) * (1.0 - L)) / (x - L)); + + return C; +} + +vec3 SetLum(vec3 C, float l) { + float d = l - Lum(C); + return ClipColor(C + d); +} + +void SetSatInner(inout float Cmin, inout float Cmid, inout float Cmax, float s) { + if (Cmax > Cmin) { + Cmid = (((Cmid - Cmin) * s) / (Cmax - Cmin)); + Cmax = s; + } else { + Cmid = 0.0; + Cmax = 0.0; + } + Cmin = 0.0; +} + +vec3 SetSat(vec3 C, float s) { + if (C.r <= C.g) { + if (C.g <= C.b) { + SetSatInner(C.r, C.g, C.b, s); + } else { + if (C.r <= C.b) { + SetSatInner(C.r, C.b, C.g, s); + } else { + SetSatInner(C.b, C.r, C.g, s); + } + } + } else { + if (C.r <= C.b) { + SetSatInner(C.g, C.r, C.b, s); + } else { + if (C.g <= C.b) { + SetSatInner(C.g, C.b, C.r, s); + } else { + SetSatInner(C.b, C.g, C.r, s); + } + } + } + return C; +} + +vec3 Hue(vec3 Cb, vec3 Cs) { + return SetLum(SetSat(Cs, Sat(Cb)), Lum(Cb)); +} + +vec3 Saturation(vec3 Cb, vec3 Cs) { + return SetLum(SetSat(Cb, Sat(Cs)), Lum(Cb)); +} + +vec3 Color(vec3 Cb, vec3 Cs) { + return SetLum(Cs, Lum(Cb)); +} + +vec3 Luminosity(vec3 Cb, vec3 Cs) { + return SetLum(Cb, Lum(Cs)); +} + +const int BlendMode_Normal = 0; +const int BlendMode_Multiply = 1; +const int BlendMode_Screen = 2; +const int BlendMode_Overlay = 3; +const int BlendMode_Darken = 4; +const int BlendMode_Lighten = 5; +const int BlendMode_ColorDodge = 6; +const int BlendMode_ColorBurn = 7; +const int BlendMode_HardLight = 8; +const int BlendMode_SoftLight = 9; +const int BlendMode_Difference = 10; +const int BlendMode_Exclusion = 11; +const int BlendMode_Hue = 12; +const int BlendMode_Saturation = 13; +const int BlendMode_Color = 14; +const int BlendMode_Luminosity = 15; + +vec4 blend(vec4 Cs, vec4 Cb, int mode) { + vec4 result = vec4(1.0, 0.0, 0.0, 1.0); + + switch (mode) { + case BlendMode_Normal: + result.rgb = Cs.rgb; + break; + case BlendMode_Multiply: + result.rgb = Multiply(Cb.rgb, Cs.rgb); + break; + case BlendMode_Screen: + result.rgb = Screen(Cb.rgb, Cs.rgb); + break; + case BlendMode_Overlay: + // Overlay is inverse of Hardlight + result.rgb = HardLight(Cs.rgb, Cb.rgb); + break; + case BlendMode_Darken: + result.rgb = min(Cs.rgb, Cb.rgb); + break; + case BlendMode_Lighten: + result.rgb = max(Cs.rgb, Cb.rgb); + break; + case BlendMode_ColorDodge: + result.r = ColorDodge(Cb.r, Cs.r); + result.g = ColorDodge(Cb.g, Cs.g); + result.b = ColorDodge(Cb.b, Cs.b); + break; + case BlendMode_ColorBurn: + result.r = ColorBurn(Cb.r, Cs.r); + result.g = ColorBurn(Cb.g, Cs.g); + result.b = ColorBurn(Cb.b, Cs.b); + break; + case BlendMode_HardLight: + result.rgb = HardLight(Cb.rgb, Cs.rgb); + break; + case BlendMode_SoftLight: + result.r = SoftLight(Cb.r, Cs.r); + result.g = SoftLight(Cb.g, Cs.g); + result.b = SoftLight(Cb.b, Cs.b); + break; + case BlendMode_Difference: + result.rgb = Difference(Cb.rgb, Cs.rgb); + break; + case BlendMode_Exclusion: + result.rgb = Exclusion(Cb.rgb, Cs.rgb); + break; + case BlendMode_Hue: + result.rgb = Hue(Cb.rgb, Cs.rgb); + break; + case BlendMode_Saturation: + result.rgb = Saturation(Cb.rgb, Cs.rgb); + break; + case BlendMode_Color: + result.rgb = Color(Cb.rgb, Cs.rgb); + break; + case BlendMode_Luminosity: + result.rgb = Luminosity(Cb.rgb, Cs.rgb); + break; + default: break; + } + vec3 rgb = (1.0 - Cb.a) * Cs.rgb + Cb.a * result.rgb; + result = mix(vec4(Cb.rgb * Cb.a, Cb.a), vec4(rgb, 1.0), Cs.a); + return result; +} + +// Based on the Gecko's implementation in +// https://hg.mozilla.org/mozilla-central/file/91b4c3687d75/gfx/src/FilterSupport.cpp#l24 +// These could be made faster by sampling a lookup table stored in a float texture +// with linear interpolation. + +vec3 SrgbToLinear(vec3 color) { + vec3 c1 = color / 12.92; + vec3 c2 = pow(color / 1.055 + vec3(0.055 / 1.055), vec3(2.4)); + return if_then_else(lessThanEqual(color, vec3(0.04045)), c1, c2); +} + +vec3 LinearToSrgb(vec3 color) { + vec3 c1 = color * 12.92; + vec3 c2 = vec3(1.055) * pow(color, vec3(1.0 / 2.4)) - vec3(0.055); + return if_then_else(lessThanEqual(color, vec3(0.0031308)), c1, c2); +} + +// This function has to be factored out due to the following issue: +// https://github.com/servo/webrender/wiki/Driver-issues#bug-1532245---switch-statement-inside-control-flow-inside-switch-statement-fails-to-compile-on-some-android-phones +// (and now the words "default: default:" so angle_shader_validation.rs passes) +vec4 ComponentTransfer(vec4 colora) { + // We push a different amount of data to the gpu cache depending on the + // function type. + // Identity => 0 blocks + // Table/Discrete => 64 blocks (256 values) + // Linear => 1 block (2 values) + // Gamma => 1 block (3 values) + // We loop through the color components and increment the offset (for the + // next color component) into the gpu cache based on how many blocks that + // function type put into the gpu cache. + // Table/Discrete use a 256 entry look up table. + // Linear/Gamma are a simple calculation. + int offset = 0; + vec4 texel; + int k; + + for (int i = 0; i < 4; i++) { + switch (vFuncs[i]) { + case COMPONENT_TRANSFER_IDENTITY: + break; + case COMPONENT_TRANSFER_TABLE: + case COMPONENT_TRANSFER_DISCRETE: + // fetch value from lookup table + k = int(floor(colora[i]*255.0)); + texel = fetch_from_gpu_cache_1_direct(vData.xy + ivec2(offset + k/4, 0)); + colora[i] = clamp(texel[k % 4], 0.0, 1.0); + // offset plus 256/4 blocks + offset = offset + 64; + break; + case COMPONENT_TRANSFER_LINEAR: + // fetch the two values for use in the linear equation + texel = fetch_from_gpu_cache_1_direct(vData.xy + ivec2(offset, 0)); + colora[i] = clamp(texel[0] * colora[i] + texel[1], 0.0, 1.0); + // offset plus 1 block + offset = offset + 1; + break; + case COMPONENT_TRANSFER_GAMMA: + // fetch the three values for use in the gamma equation + texel = fetch_from_gpu_cache_1_direct(vData.xy + ivec2(offset, 0)); + colora[i] = clamp(texel[0] * pow(colora[i], texel[1]) + texel[2], 0.0, 1.0); + // offset plus 1 block + offset = offset + 1; + break; + default: + // shouldn't happen + break; + } + } + return colora; +} + +// Composite Filter + +vec4 composite(vec4 Cs, vec4 Cb, int mode) { + vec4 Cr = vec4(0.0, 1.0, 0.0, 1.0); + switch (mode) { + case COMPOSITE_OVER: + Cr.rgb = Cs.a * Cs.rgb + Cb.a * Cb.rgb * (1.0 - Cs.a); + Cr.a = Cs.a + Cb.a * (1.0 - Cs.a); + break; + case COMPOSITE_IN: + Cr.rgb = Cs.a * Cs.rgb * Cb.a; + Cr.a = Cs.a * Cb.a; + break; + case COMPOSITE_OUT: + Cr.rgb = Cs.a * Cs.rgb * (1.0 - Cb.a); + Cr.a = Cs.a * (1.0 - Cb.a); + break; + case COMPOSITE_ATOP: + Cr.rgb = Cs.a * Cs.rgb * Cb.a + Cb.a * Cb.rgb * (1.0 - Cs.a); + Cr.a = Cs.a * Cb.a + Cb.a * (1.0 - Cs.a); + break; + case COMPOSITE_XOR: + Cr.rgb = Cs.a * Cs.rgb * (1.0 - Cb.a) + Cb.a * Cb.rgb * (1.0 - Cs.a); + Cr.a = Cs.a * (1.0 - Cb.a) + Cb.a * (1.0 - Cs.a); + break; + case COMPOSITE_LIGHTER: + Cr.rgb = Cs.a * Cs.rgb + Cb.a * Cb.rgb; + Cr.a = Cs.a + Cb.a; + Cr = clamp(Cr, vec4(0.0), vec4(1.0)); + break; + case COMPOSITE_ARITHMETIC: + Cr = vec4(vFilterData0.x) * Cs * Cb + vec4(vFilterData0.y) * Cs + vec4(vFilterData0.z) * Cb + vec4(vFilterData0.w); + Cr = clamp(Cr, vec4(0.0), vec4(1.0)); + break; + default: + break; + } + return Cr; +} + +vec4 sampleInUvRect(sampler2DArray sampler, vec3 uv, vec4 uvRect) { + vec2 clamped = clamp(uv.xy, uvRect.xy, uvRect.zw); + return texture(sampler, vec3(clamped, uv.z)); +} + +void main(void) { + vec4 Ca = vec4(0.0, 0.0, 0.0, 0.0); + vec4 Cb = vec4(0.0, 0.0, 0.0, 0.0); + if (vFilterInputCount > 0) { + Ca = sampleInUvRect(sColor0, vInput1Uv, vInput1UvRect); + if (Ca.a != 0.0) { + Ca.rgb /= Ca.a; + } + } + if (vFilterInputCount > 1) { + Cb = sampleInUvRect(sColor1, vInput2Uv, vInput2UvRect); + if (Cb.a != 0.0) { + Cb.rgb /= Cb.a; + } + } + + vec4 result = vec4(1.0, 0.0, 0.0, 1.0); + + bool needsPremul = true; + + switch (vFilterKind) { + case FILTER_BLEND: + result = blend(Ca, Cb, vData.x); + needsPremul = false; + break; + case FILTER_FLOOD: + result = vFilterData0; + needsPremul = false; + break; + case FILTER_LINEAR_TO_SRGB: + result.rgb = LinearToSrgb(Ca.rgb); + result.a = Ca.a; + break; + case FILTER_SRGB_TO_LINEAR: + result.rgb = SrgbToLinear(Ca.rgb); + result.a = Ca.a; + break; + case FILTER_OPACITY: + result.rgb = Ca.rgb; + result.a = Ca.a * vFloat0; + break; + case FILTER_COLOR_MATRIX: + result = vColorMat * Ca + vFilterData0; + result = clamp(result, vec4(0.0), vec4(1.0)); + break; + case FILTER_DROP_SHADOW: + vec4 shadow = vec4(vFilterData0.rgb, Cb.a * vFilterData0.a); + // Normal blend + source-over coposite + result = blend(Ca, shadow, BlendMode_Normal); + needsPremul = false; + break; + case FILTER_OFFSET: + vec2 offsetUv = vInput1Uv.xy + vFilterData0.xy; + result = sampleInUvRect(sColor0, vec3(offsetUv, vInput1Uv.z), vInput1UvRect); + result *= point_inside_rect(offsetUv, vFilterData1.xy, vFilterData1.zw); + needsPremul = false; + break; + case FILTER_COMPONENT_TRANSFER: + result = ComponentTransfer(Ca); + break; + case FILTER_IDENTITY: + result = Ca; + break; + case FILTER_COMPOSITE: + result = composite(Ca, Cb, vData.x); + needsPremul = false; + default: + break; + } + + if (needsPremul) { + result.rgb *= result.a; + } + + oFragColor = result; +} +#endif diff --git a/third_party/webrender/webrender/res/debug_color.glsl b/third_party/webrender/webrender/res/debug_color.glsl new file mode 100644 index 00000000000..b5a636e535e --- /dev/null +++ b/third_party/webrender/webrender/res/debug_color.glsl @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include shared,shared_other + +varying vec4 vColor; + +#ifdef WR_VERTEX_SHADER +in vec4 aColor; + +void main(void) { + vColor = vec4(aColor.rgb * aColor.a, aColor.a); + vec4 pos = vec4(aPosition, 0.0, 1.0); + pos.xy = floor(pos.xy + 0.5); + gl_Position = uTransform * pos; +} +#endif + +#ifdef WR_FRAGMENT_SHADER +void main(void) { + oFragColor = vColor; +} +#endif diff --git a/third_party/webrender/webrender/res/debug_font.glsl b/third_party/webrender/webrender/res/debug_font.glsl new file mode 100644 index 00000000000..5b9fb2109fb --- /dev/null +++ b/third_party/webrender/webrender/res/debug_font.glsl @@ -0,0 +1,28 @@ +/* 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/. */ + +#include shared,shared_other + +varying vec2 vColorTexCoord; +varying vec4 vColor; + +#ifdef WR_VERTEX_SHADER +in vec4 aColor; +in vec2 aColorTexCoord; + +void main(void) { + vColor = aColor; + vColorTexCoord = aColorTexCoord; + vec4 pos = vec4(aPosition, 0.0, 1.0); + pos.xy = floor(pos.xy + 0.5); + gl_Position = uTransform * pos; +} +#endif + +#ifdef WR_FRAGMENT_SHADER +void main(void) { + float alpha = texture(sColor0, vec3(vColorTexCoord.xy, 0.0)).r; + oFragColor = vColor * alpha; +} +#endif diff --git a/third_party/webrender/webrender/res/ellipse.glsl b/third_party/webrender/webrender/res/ellipse.glsl new file mode 100644 index 00000000000..1bcacaafda5 --- /dev/null +++ b/third_party/webrender/webrender/res/ellipse.glsl @@ -0,0 +1,93 @@ +/* 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/. */ + +#ifdef WR_FRAGMENT_SHADER + +// One iteration of Newton's method on the 2D equation of an ellipse: +// +// E(x, y) = x^2/a^2 + y^2/b^2 - 1 +// +// The Jacobian of this equation is: +// +// J(E(x, y)) = [ 2*x/a^2 2*y/b^2 ] +// +// We approximate the distance with: +// +// E(x, y) / ||J(E(x, y))|| +// +// See G. Taubin, "Distance Approximations for Rasterizing Implicit +// Curves", section 3. +float distance_to_ellipse(vec2 p, vec2 radii, float aa_range) { + float dist; + if (any(lessThanEqual(radii, vec2(0.0)))) { + dist = length(p); + } else { + vec2 invRadiiSq = 1.0 / (radii * radii); + float g = dot(p * p * invRadiiSq, vec2(1.0)) - 1.0; + vec2 dG = 2.0 * p * invRadiiSq; + dist = g * inversesqrt(dot(dG, dG)); + } + return clamp(dist, -aa_range, aa_range); +} + +float clip_against_ellipse_if_needed( + vec2 pos, + float current_distance, + vec4 ellipse_center_radius, + vec2 sign_modifier, + float aa_range +) { + if (!all(lessThan(sign_modifier * pos, sign_modifier * ellipse_center_radius.xy))) { + return current_distance; + } + + float distance = distance_to_ellipse(pos - ellipse_center_radius.xy, + ellipse_center_radius.zw, + aa_range); + + return max(distance, current_distance); +} + +float rounded_rect(vec2 pos, + vec4 clip_center_radius_tl, + vec4 clip_center_radius_tr, + vec4 clip_center_radius_br, + vec4 clip_center_radius_bl, + float aa_range) { + // Start with a negative value (means "inside") for all fragments that are not + // in a corner. If the fragment is in a corner, one of the clip_against_ellipse_if_needed + // calls below will update it. + float current_distance = -aa_range; + + // Clip against each ellipse. + current_distance = clip_against_ellipse_if_needed(pos, + current_distance, + clip_center_radius_tl, + vec2(1.0), + aa_range); + + current_distance = clip_against_ellipse_if_needed(pos, + current_distance, + clip_center_radius_tr, + vec2(-1.0, 1.0), + aa_range); + + current_distance = clip_against_ellipse_if_needed(pos, + current_distance, + clip_center_radius_br, + vec2(-1.0), + aa_range); + + current_distance = clip_against_ellipse_if_needed(pos, + current_distance, + clip_center_radius_bl, + vec2(1.0, -1.0), + aa_range); + + // Apply AA + // See comment in ps_border_corner about the choice of constants. + + return distance_aa(aa_range, current_distance); +} +#endif diff --git a/third_party/webrender/webrender/res/gpu_cache.glsl b/third_party/webrender/webrender/res/gpu_cache.glsl new file mode 100644 index 00000000000..e3ff0f43976 --- /dev/null +++ b/third_party/webrender/webrender/res/gpu_cache.glsl @@ -0,0 +1,138 @@ +/* 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/. */ + +uniform HIGHP_SAMPLER_FLOAT sampler2D sGpuCache; + +#define VECS_PER_IMAGE_RESOURCE 2 + +// TODO(gw): This is here temporarily while we have +// both GPU store and cache. When the GPU +// store code is removed, we can change the +// PrimitiveInstance instance structure to +// use 2x unsigned shorts as vertex attributes +// instead of an int, and encode the UV directly +// in the vertices. +ivec2 get_gpu_cache_uv(HIGHP_FS_ADDRESS int address) { + return ivec2(uint(address) % WR_MAX_VERTEX_TEXTURE_WIDTH, + uint(address) / WR_MAX_VERTEX_TEXTURE_WIDTH); +} + +vec4[2] fetch_from_gpu_cache_2_direct(ivec2 address) { + return vec4[2]( + TEXEL_FETCH(sGpuCache, address, 0, ivec2(0, 0)), + TEXEL_FETCH(sGpuCache, address, 0, ivec2(1, 0)) + ); +} + +vec4[2] fetch_from_gpu_cache_2(HIGHP_FS_ADDRESS int address) { + ivec2 uv = get_gpu_cache_uv(address); + return vec4[2]( + TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0)), + TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0)) + ); +} + +vec4 fetch_from_gpu_cache_1_direct(ivec2 address) { + return texelFetch(sGpuCache, address, 0); +} + +vec4 fetch_from_gpu_cache_1(HIGHP_FS_ADDRESS int address) { + ivec2 uv = get_gpu_cache_uv(address); + return texelFetch(sGpuCache, uv, 0); +} + +#ifdef WR_VERTEX_SHADER + +vec4[8] fetch_from_gpu_cache_8(int address) { + ivec2 uv = get_gpu_cache_uv(address); + return vec4[8]( + TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0)), + TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0)), + TEXEL_FETCH(sGpuCache, uv, 0, ivec2(2, 0)), + TEXEL_FETCH(sGpuCache, uv, 0, ivec2(3, 0)), + TEXEL_FETCH(sGpuCache, uv, 0, ivec2(4, 0)), + TEXEL_FETCH(sGpuCache, uv, 0, ivec2(5, 0)), + TEXEL_FETCH(sGpuCache, uv, 0, ivec2(6, 0)), + TEXEL_FETCH(sGpuCache, uv, 0, ivec2(7, 0)) + ); +} + +vec4[3] fetch_from_gpu_cache_3(int address) { + ivec2 uv = get_gpu_cache_uv(address); + return vec4[3]( + TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0)), + TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0)), + TEXEL_FETCH(sGpuCache, uv, 0, ivec2(2, 0)) + ); +} + +vec4[3] fetch_from_gpu_cache_3_direct(ivec2 address) { + return vec4[3]( + TEXEL_FETCH(sGpuCache, address, 0, ivec2(0, 0)), + TEXEL_FETCH(sGpuCache, address, 0, ivec2(1, 0)), + TEXEL_FETCH(sGpuCache, address, 0, ivec2(2, 0)) + ); +} + +vec4[4] fetch_from_gpu_cache_4_direct(ivec2 address) { + return vec4[4]( + TEXEL_FETCH(sGpuCache, address, 0, ivec2(0, 0)), + TEXEL_FETCH(sGpuCache, address, 0, ivec2(1, 0)), + TEXEL_FETCH(sGpuCache, address, 0, ivec2(2, 0)), + TEXEL_FETCH(sGpuCache, address, 0, ivec2(3, 0)) + ); +} + +vec4[4] fetch_from_gpu_cache_4(int address) { + ivec2 uv = get_gpu_cache_uv(address); + return vec4[4]( + TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0)), + TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0)), + TEXEL_FETCH(sGpuCache, uv, 0, ivec2(2, 0)), + TEXEL_FETCH(sGpuCache, uv, 0, ivec2(3, 0)) + ); +} + +//TODO: image resource is too specific for this module + +struct ImageResource { + RectWithEndpoint uv_rect; + float layer; + vec3 user_data; +}; + +ImageResource fetch_image_resource(int address) { + //Note: number of blocks has to match `renderer::BLOCKS_PER_UV_RECT` + vec4 data[2] = fetch_from_gpu_cache_2(address); + RectWithEndpoint uv_rect = RectWithEndpoint(data[0].xy, data[0].zw); + return ImageResource(uv_rect, data[1].x, data[1].yzw); +} + +ImageResource fetch_image_resource_direct(ivec2 address) { + vec4 data[2] = fetch_from_gpu_cache_2_direct(address); + RectWithEndpoint uv_rect = RectWithEndpoint(data[0].xy, data[0].zw); + return ImageResource(uv_rect, data[1].x, data[1].yzw); +} + +// Fetch optional extra data for a texture cache resource. This can contain +// a polygon defining a UV rect within the texture cache resource. +// Note: the polygon coordinates are in homogeneous space. +struct ImageResourceExtra { + vec4 st_tl; + vec4 st_tr; + vec4 st_bl; + vec4 st_br; +}; + +ImageResourceExtra fetch_image_resource_extra(int address) { + vec4 data[4] = fetch_from_gpu_cache_4(address + VECS_PER_IMAGE_RESOURCE); + return ImageResourceExtra( + data[0], + data[1], + data[2], + data[3] + ); +} + +#endif //WR_VERTEX_SHADER diff --git a/third_party/webrender/webrender/res/gpu_cache_update.glsl b/third_party/webrender/webrender/res/gpu_cache_update.glsl new file mode 100644 index 00000000000..90a85342461 --- /dev/null +++ b/third_party/webrender/webrender/res/gpu_cache_update.glsl @@ -0,0 +1,27 @@ +/* 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/. */ + +#include base + +varying vec4 vData; + +#ifdef WR_VERTEX_SHADER +in vec4 aValue; +in vec2 aPosition; + +void main() { + vData = aValue; + gl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0); + gl_PointSize = 1.0; +} + +#endif //WR_VERTEX_SHADER + +#ifdef WR_FRAGMENT_SHADER +out vec4 oValue; + +void main() { + oValue = vData; +} +#endif //WR_FRAGMENT_SHADER diff --git a/third_party/webrender/webrender/res/pf_vector_cover.glsl b/third_party/webrender/webrender/res/pf_vector_cover.glsl new file mode 100644 index 00000000000..1b2eeabb7dc --- /dev/null +++ b/third_party/webrender/webrender/res/pf_vector_cover.glsl @@ -0,0 +1,77 @@ +/* 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/. */ + +#include shared + +#ifdef WR_VERTEX_SHADER + +PER_INSTANCE in ivec4 aTargetRect; +PER_INSTANCE in ivec2 aStencilOrigin; +PER_INSTANCE in int aSubpixel; +PER_INSTANCE in int aPad; + +out vec2 vStencilUV; +flat out int vSubpixel; + +void main(void) { + vec4 targetRect = vec4(aTargetRect); + vec2 stencilOrigin = vec2(aStencilOrigin); + + vec2 targetOffset = mix(vec2(0.0), targetRect.zw, aPosition.xy); + vec2 targetPosition = targetRect.xy + targetOffset; + vec2 stencilOffset = targetOffset * vec2(aSubpixel == 0 ? 1.0 : 3.0, 1.0); + vec2 stencilPosition = stencilOrigin + stencilOffset; + + gl_Position = uTransform * vec4(targetPosition, aPosition.z, 1.0); + vStencilUV = stencilPosition; + vSubpixel = aSubpixel; +} + +#endif + +#ifdef WR_FRAGMENT_SHADER + +#define LCD_FILTER_FACTOR_0 (86.0 / 255.0) +#define LCD_FILTER_FACTOR_1 (77.0 / 255.0) +#define LCD_FILTER_FACTOR_2 (8.0 / 255.0) + +in vec2 vStencilUV; +flat in int vSubpixel; + +/// Applies a slight horizontal blur to reduce color fringing on LCD screens +/// when performing subpixel AA. +/// +/// The algorithm should be identical to that of FreeType: +/// https://www.freetype.org/freetype2/docs/reference/ft2-lcd_filtering.html +float lcdFilter(float shadeL2, float shadeL1, float shade0, float shadeR1, float shadeR2) { + return LCD_FILTER_FACTOR_2 * shadeL2 + + LCD_FILTER_FACTOR_1 * shadeL1 + + LCD_FILTER_FACTOR_0 * shade0 + + LCD_FILTER_FACTOR_1 * shadeR1 + + LCD_FILTER_FACTOR_2 * shadeR2; +} + +void main(void) { + ivec2 stencilUV = ivec2(vStencilUV); + float shade0 = abs(TEXEL_FETCH(sColor0, stencilUV, 0, ivec2(0, 0)).r); + + if (vSubpixel == 0) { + oFragColor = vec4(shade0); + return; + } + + vec3 shadeL = abs(vec3(TEXEL_FETCH(sColor0, stencilUV, 0, ivec2(-1, 0)).r, + TEXEL_FETCH(sColor0, stencilUV, 0, ivec2(-2, 0)).r, + TEXEL_FETCH(sColor0, stencilUV, 0, ivec2(-3, 0)).r)); + vec3 shadeR = abs(vec3(TEXEL_FETCH(sColor0, stencilUV, 0, ivec2(1, 0)).r, + TEXEL_FETCH(sColor0, stencilUV, 0, ivec2(2, 0)).r, + TEXEL_FETCH(sColor0, stencilUV, 0, ivec2(3, 0)).r)); + + oFragColor = vec4(lcdFilter(shadeL.z, shadeL.y, shadeL.x, shade0, shadeR.x), + lcdFilter(shadeL.y, shadeL.x, shade0, shadeR.x, shadeR.y), + lcdFilter(shadeL.x, shade0, shadeR.x, shadeR.y, shadeR.z), + 1.0); +} + +#endif diff --git a/third_party/webrender/webrender/res/pf_vector_stencil.glsl b/third_party/webrender/webrender/res/pf_vector_stencil.glsl new file mode 100644 index 00000000000..2029768fcb9 --- /dev/null +++ b/third_party/webrender/webrender/res/pf_vector_stencil.glsl @@ -0,0 +1,111 @@ +/* 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/. */ + +#include shared + +#ifdef WR_VERTEX_SHADER + +PER_INSTANCE in vec2 aFromPosition; +PER_INSTANCE in vec2 aCtrlPosition; +PER_INSTANCE in vec2 aToPosition; +PER_INSTANCE in vec2 aFromNormal; +PER_INSTANCE in vec2 aCtrlNormal; +PER_INSTANCE in vec2 aToNormal; +PER_INSTANCE in int aPathID; +PER_INSTANCE in int aPad; + +out vec2 vFrom; +out vec2 vCtrl; +out vec2 vTo; + +void main(void) { + // Unpack. + int pathID = int(aPathID); + + ivec2 pathAddress = ivec2(0.0, aPathID); + mat2 transformLinear = mat2(TEXEL_FETCH(sColor1, pathAddress, 0, ivec2(0, 0))); + vec2 transformTranslation = TEXEL_FETCH(sColor1, pathAddress, 0, ivec2(1, 0)).xy; + + vec4 miscInfo = TEXEL_FETCH(sColor1, pathAddress, 0, ivec2(2, 0)); + float rectHeight = miscInfo.y; + vec2 emboldenAmount = miscInfo.zw * 0.5; + + // TODO(pcwalton): Hint positions. + vec2 from = aFromPosition; + vec2 ctrl = aCtrlPosition; + vec2 to = aToPosition; + + // Embolden as necessary. + from -= aFromNormal * emboldenAmount; + ctrl -= aCtrlNormal * emboldenAmount; + to -= aToNormal * emboldenAmount; + + // Perform the transform. + from = transformLinear * from + transformTranslation; + ctrl = transformLinear * ctrl + transformTranslation; + to = transformLinear * to + transformTranslation; + + // Choose correct quadrant for rotation. + vec2 corner = vec2(0.0, rectHeight) + transformTranslation; + + // Compute edge vectors. De Casteljau subdivide if necessary. + // TODO(pcwalton): Actually do the two-pass rendering. + + // Compute position and dilate. If too thin, discard to avoid artefacts. + vec2 position; + if (abs(from.x - to.x) < 0.0001) + position.x = 0.0; + else if (aPosition.x < 0.5) + position.x = floor(min(min(from.x, to.x), ctrl.x)); + else + position.x = ceil(max(max(from.x, to.x), ctrl.x)); + if (aPosition.y < 0.5) + position.y = floor(min(min(from.y, to.y), ctrl.y)); + else + position.y = corner.y; + + // Compute final position and depth. + vec4 clipPosition = uTransform * vec4(position, aPosition.z, 1.0); + + // Finish up. + gl_Position = clipPosition; + vFrom = from - position; + vCtrl = ctrl - position; + vTo = to - position; +} + +#endif + +#ifdef WR_FRAGMENT_SHADER + +uniform sampler2D uAreaLUT; + +in vec2 vFrom; +in vec2 vCtrl; +in vec2 vTo; + +void main(void) { + // Unpack. + vec2 from = vFrom, ctrl = vCtrl, to = vTo; + + // Determine winding, and sort into a consistent order so we only need to find one root below. + bool winding = from.x < to.x; + vec2 left = winding ? from : to, right = winding ? to : from; + vec2 v0 = ctrl - left, v1 = right - ctrl; + + // Shoot a vertical ray toward the curve. + vec2 window = clamp(vec2(from.x, to.x), -0.5, 0.5); + float offset = mix(window.x, window.y, 0.5) - left.x; + float t = offset / (v0.x + sqrt(v1.x * offset - v0.x * (offset - v0.x))); + + // Compute position and derivative to form a line approximation. + float y = mix(mix(left.y, ctrl.y, t), mix(ctrl.y, right.y, t), t); + float d = mix(v0.y, v1.y, t) / mix(v0.x, v1.x, t); + + // Look up area under that line, and scale horizontally to the window size. + float dX = window.x - window.y; + oFragColor = vec4(texture(sColor0, vec2(y + 8.0, abs(d * dX)) / 16.0).r * dX); +} + +#endif diff --git a/third_party/webrender/webrender/res/pls_init.glsl b/third_party/webrender/webrender/res/pls_init.glsl new file mode 100644 index 00000000000..a9fe0a6c4a6 --- /dev/null +++ b/third_party/webrender/webrender/res/pls_init.glsl @@ -0,0 +1,27 @@ +/* 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/. */ + +// Initialize the pixel local storage area by reading the current +// framebuffer color. We might be able to skip this in future by +// making the opaque pass also write to pixel local storage. + +#define PLS_WRITEONLY + +#include shared + +#ifdef WR_VERTEX_SHADER +PER_INSTANCE in vec4 aRect; + +void main(void) { + vec2 pos = aRect.xy + aPosition.xy * aRect.zw; + gl_Position = uTransform * vec4(pos, 0.0, 1.0); +} +#endif + +#ifdef WR_FRAGMENT_SHADER +void main(void) { + // Store current framebuffer color in our custom PLS struct. + PLS.color = gl_LastFragColorARM; +} +#endif diff --git a/third_party/webrender/webrender/res/pls_resolve.glsl b/third_party/webrender/webrender/res/pls_resolve.glsl new file mode 100644 index 00000000000..363ce2ccfef --- /dev/null +++ b/third_party/webrender/webrender/res/pls_resolve.glsl @@ -0,0 +1,29 @@ +/* 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/. */ + +// Write the final value stored in pixel local store out to normal +// fragment outputs. This will be the color that gets resolved out +// to main memory. + +#define PLS_READONLY + +#include shared + +#ifdef WR_VERTEX_SHADER +PER_INSTANCE in vec4 aRect; + +void main(void) { + vec2 pos = aRect.xy + aPosition.xy * aRect.zw; + gl_Position = uTransform * vec4(pos, 0.0, 1.0); +} +#endif + +#ifdef WR_FRAGMENT_SHADER +out vec4 oFragColor; + +void main(void) { + // Write the final color value in pixel local storage out as a fragment color. + oFragColor = PLS.color; +} +#endif diff --git a/third_party/webrender/webrender/res/prim_shared.glsl b/third_party/webrender/webrender/res/prim_shared.glsl new file mode 100644 index 00000000000..f89340ff49c --- /dev/null +++ b/third_party/webrender/webrender/res/prim_shared.glsl @@ -0,0 +1,320 @@ +/* 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/. */ + +#include rect,render_task,gpu_cache,transform + +#define EXTEND_MODE_CLAMP 0 +#define EXTEND_MODE_REPEAT 1 + +#define SUBPX_DIR_NONE 0 +#define SUBPX_DIR_HORIZONTAL 1 +#define SUBPX_DIR_VERTICAL 2 +#define SUBPX_DIR_MIXED 3 + +#define RASTER_LOCAL 0 +#define RASTER_SCREEN 1 + +uniform sampler2DArray sPrevPassAlpha; +uniform sampler2DArray sPrevPassColor; + +vec2 clamp_rect(vec2 pt, RectWithSize rect) { + return clamp(pt, rect.p0, rect.p0 + rect.size); +} + +// TODO: convert back to RectWithEndPoint if driver issues are resolved, if ever. +flat varying vec4 vClipMaskUvBounds; +// XY and W are homogeneous coordinates, Z is the layer index +varying vec4 vClipMaskUv; + + +#ifdef WR_VERTEX_SHADER + +#define COLOR_MODE_FROM_PASS 0 +#define COLOR_MODE_ALPHA 1 +#define COLOR_MODE_SUBPX_CONST_COLOR 2 +#define COLOR_MODE_SUBPX_BG_PASS0 3 +#define COLOR_MODE_SUBPX_BG_PASS1 4 +#define COLOR_MODE_SUBPX_BG_PASS2 5 +#define COLOR_MODE_SUBPX_DUAL_SOURCE 6 +#define COLOR_MODE_BITMAP 7 +#define COLOR_MODE_COLOR_BITMAP 8 +#define COLOR_MODE_IMAGE 9 + +uniform HIGHP_SAMPLER_FLOAT sampler2D sPrimitiveHeadersF; +uniform HIGHP_SAMPLER_FLOAT isampler2D sPrimitiveHeadersI; + +// Instanced attributes +PER_INSTANCE in ivec4 aData; + +#define VECS_PER_PRIM_HEADER_F 2U +#define VECS_PER_PRIM_HEADER_I 2U + +struct Instance +{ + int prim_header_address; + int picture_task_address; + int clip_address; + int segment_index; + int flags; + int resource_address; + int brush_kind; +}; + +Instance decode_instance_attributes() { + Instance instance; + + instance.prim_header_address = aData.x; + instance.picture_task_address = aData.y >> 16; + instance.clip_address = aData.y & 0xffff; + instance.segment_index = aData.z & 0xffff; + instance.flags = aData.z >> 16; + instance.resource_address = aData.w & 0xffffff; + instance.brush_kind = aData.w >> 24; + + return instance; +} + +struct PrimitiveHeader { + RectWithSize local_rect; + RectWithSize local_clip_rect; + float z; + int specific_prim_address; + int transform_id; + ivec4 user_data; +}; + +PrimitiveHeader fetch_prim_header(int index) { + PrimitiveHeader ph; + + ivec2 uv_f = get_fetch_uv(index, VECS_PER_PRIM_HEADER_F); + vec4 local_rect = TEXEL_FETCH(sPrimitiveHeadersF, uv_f, 0, ivec2(0, 0)); + vec4 local_clip_rect = TEXEL_FETCH(sPrimitiveHeadersF, uv_f, 0, ivec2(1, 0)); + ph.local_rect = RectWithSize(local_rect.xy, local_rect.zw); + ph.local_clip_rect = RectWithSize(local_clip_rect.xy, local_clip_rect.zw); + + ivec2 uv_i = get_fetch_uv(index, VECS_PER_PRIM_HEADER_I); + ivec4 data0 = TEXEL_FETCH(sPrimitiveHeadersI, uv_i, 0, ivec2(0, 0)); + ivec4 data1 = TEXEL_FETCH(sPrimitiveHeadersI, uv_i, 0, ivec2(1, 0)); + ph.z = float(data0.x); + ph.specific_prim_address = data0.y; + ph.transform_id = data0.z; + ph.user_data = data1; + + return ph; +} + +struct VertexInfo { + vec2 local_pos; + vec4 world_pos; +}; + +VertexInfo write_vertex(vec2 local_pos, + RectWithSize local_clip_rect, + float z, + Transform transform, + PictureTask task) { + // Clamp to the two local clip rects. + vec2 clamped_local_pos = clamp_rect(local_pos, local_clip_rect); + + // Transform the current vertex to world space. + vec4 world_pos = transform.m * vec4(clamped_local_pos, 0.0, 1.0); + + // Convert the world positions to device pixel space. + vec2 device_pos = world_pos.xy * task.device_pixel_scale; + + // Apply offsets for the render task to get correct screen location. + vec2 final_offset = -task.content_origin + task.common_data.task_rect.p0; + + gl_Position = uTransform * vec4(device_pos + final_offset * world_pos.w, z * world_pos.w, world_pos.w); + + VertexInfo vi = VertexInfo( + clamped_local_pos, + world_pos + ); + + return vi; +} + +float cross2(vec2 v0, vec2 v1) { + return v0.x * v1.y - v0.y * v1.x; +} + +// Return intersection of line (p0,p1) and line (p2,p3) +vec2 intersect_lines(vec2 p0, vec2 p1, vec2 p2, vec2 p3) { + vec2 d0 = p0 - p1; + vec2 d1 = p2 - p3; + + float s0 = cross2(p0, p1); + float s1 = cross2(p2, p3); + + float d = cross2(d0, d1); + float nx = s0 * d1.x - d0.x * s1; + float ny = s0 * d1.y - d0.y * s1; + + return vec2(nx / d, ny / d); +} + +VertexInfo write_transform_vertex(RectWithSize local_segment_rect, + RectWithSize local_prim_rect, + RectWithSize local_clip_rect, + vec4 clip_edge_mask, + float z, + Transform transform, + PictureTask task) { + // Calculate a clip rect from local_rect + local clip + RectWithEndpoint clip_rect = to_rect_with_endpoint(local_clip_rect); + RectWithEndpoint segment_rect = to_rect_with_endpoint(local_segment_rect); + segment_rect.p0 = clamp(segment_rect.p0, clip_rect.p0, clip_rect.p1); + segment_rect.p1 = clamp(segment_rect.p1, clip_rect.p0, clip_rect.p1); + + // Calculate a clip rect from local_rect + local clip + RectWithEndpoint prim_rect = to_rect_with_endpoint(local_prim_rect); + prim_rect.p0 = clamp(prim_rect.p0, clip_rect.p0, clip_rect.p1); + prim_rect.p1 = clamp(prim_rect.p1, clip_rect.p0, clip_rect.p1); + + // As this is a transform shader, extrude by 2 (local space) pixels + // in each direction. This gives enough space around the edge to + // apply distance anti-aliasing. Technically, it: + // (a) slightly over-estimates the number of required pixels in the simple case. + // (b) might not provide enough edge in edge case perspective projections. + // However, it's fast and simple. If / when we ever run into issues, we + // can do some math on the projection matrix to work out a variable + // amount to extrude. + + // Only extrude along edges where we are going to apply AA. + float extrude_amount = 2.0; + vec4 extrude_distance = vec4(extrude_amount) * clip_edge_mask; + local_segment_rect.p0 -= extrude_distance.xy; + local_segment_rect.size += extrude_distance.xy + extrude_distance.zw; + + // Select the corner of the local rect that we are processing. + vec2 local_pos = local_segment_rect.p0 + local_segment_rect.size * aPosition.xy; + + // Convert the world positions to device pixel space. + vec2 task_offset = task.common_data.task_rect.p0 - task.content_origin; + + // Transform the current vertex to the world cpace. + vec4 world_pos = transform.m * vec4(local_pos, 0.0, 1.0); + vec4 final_pos = vec4( + world_pos.xy * task.device_pixel_scale + task_offset * world_pos.w, + z * world_pos.w, + world_pos.w + ); + + gl_Position = uTransform * final_pos; + + init_transform_vs(mix( + vec4(prim_rect.p0, prim_rect.p1), + vec4(segment_rect.p0, segment_rect.p1), + clip_edge_mask + )); + + VertexInfo vi = VertexInfo( + local_pos, + world_pos + ); + + return vi; +} + +void write_clip(vec4 world_pos, ClipArea area) { + vec2 uv = world_pos.xy * area.device_pixel_scale + + world_pos.w * (area.common_data.task_rect.p0 - area.screen_origin); + vClipMaskUvBounds = vec4( + area.common_data.task_rect.p0, + area.common_data.task_rect.p0 + area.common_data.task_rect.size + ); + vClipMaskUv = vec4(uv, area.common_data.texture_layer_index, world_pos.w); +} + +// Read the exta image data containing the homogeneous screen space coordinates +// of the corners, interpolate between them, and return real screen space UV. +vec2 get_image_quad_uv(int address, vec2 f) { + ImageResourceExtra extra_data = fetch_image_resource_extra(address); + vec4 x = mix(extra_data.st_tl, extra_data.st_tr, f.x); + vec4 y = mix(extra_data.st_bl, extra_data.st_br, f.x); + vec4 z = mix(x, y, f.y); + return z.xy / z.w; +} +#endif //WR_VERTEX_SHADER + +#ifdef WR_FRAGMENT_SHADER + +struct Fragment { + vec4 color; +#ifdef WR_FEATURE_DUAL_SOURCE_BLENDING + vec4 blend; +#endif +}; + + +float do_clip() { + // check for the dummy bounds, which are given to the opaque objects + if (vClipMaskUvBounds.xy == vClipMaskUvBounds.zw) { + return 1.0; + } + // anything outside of the mask is considered transparent + //Note: we assume gl_FragCoord.w == interpolated(1 / vClipMaskUv.w) + vec2 mask_uv = vClipMaskUv.xy * gl_FragCoord.w; + bvec2 left = lessThanEqual(vClipMaskUvBounds.xy, mask_uv); // inclusive + bvec2 right = greaterThan(vClipMaskUvBounds.zw, mask_uv); // non-inclusive + // bail out if the pixel is outside the valid bounds + if (!all(bvec4(left, right))) { + return 0.0; + } + // finally, the slow path - fetch the mask value from an image + // Note the Z getting rounded to the nearest integer because the variable + // is still interpolated and becomes a subject of precision-caused + // fluctuations, see https://bugzilla.mozilla.org/show_bug.cgi?id=1491911 + ivec3 tc = ivec3(mask_uv, vClipMaskUv.z + 0.5); + return texelFetch(sPrevPassAlpha, tc, 0).r; +} + +#ifdef WR_FEATURE_DITHERING +vec4 dither(vec4 color) { + const int matrix_mask = 7; + + ivec2 pos = ivec2(gl_FragCoord.xy) & ivec2(matrix_mask); + float noise_normalized = (texelFetch(sDither, pos, 0).r * 255.0 + 0.5) / 64.0; + float noise = (noise_normalized - 0.5) / 256.0; // scale down to the unit length + + return color + vec4(noise, noise, noise, 0); +} +#else +vec4 dither(vec4 color) { + return color; +} +#endif //WR_FEATURE_DITHERING + +vec4 sample_gradient(HIGHP_FS_ADDRESS int address, float offset, float gradient_repeat) { + // Modulo the offset if the gradient repeats. + float x = mix(offset, fract(offset), gradient_repeat); + + // Calculate the color entry index to use for this offset: + // offsets < 0 use the first color entry, 0 + // offsets from [0, 1) use the color entries in the range of [1, N-1) + // offsets >= 1 use the last color entry, N-1 + // so transform the range [0, 1) -> [1, N-1) + + // TODO(gw): In the future we might consider making the size of the + // LUT vary based on number / distribution of stops in the gradient. + const int GRADIENT_ENTRIES = 128; + x = 1.0 + x * float(GRADIENT_ENTRIES); + + // Calculate the texel to index into the gradient color entries: + // floor(x) is the gradient color entry index + // fract(x) is the linear filtering factor between start and end + int lut_offset = 2 * int(floor(x)); // There is a [start, end] color per entry. + + // Ensure we don't fetch outside the valid range of the LUT. + lut_offset = clamp(lut_offset, 0, 2 * (GRADIENT_ENTRIES + 1)); + + // Fetch the start and end color. + vec4 texels[2] = fetch_from_gpu_cache_2(address + lut_offset); + + // Finally interpolate and apply dithering + return dither(mix(texels[0], texels[1], fract(x))); +} + +#endif //WR_FRAGMENT_SHADER diff --git a/third_party/webrender/webrender/res/ps_clear.glsl b/third_party/webrender/webrender/res/ps_clear.glsl new file mode 100644 index 00000000000..5ff1c206f76 --- /dev/null +++ b/third_party/webrender/webrender/res/ps_clear.glsl @@ -0,0 +1,25 @@ +/* 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/. */ + +#include shared + +varying vec4 vColor; + +#ifdef WR_VERTEX_SHADER +PER_INSTANCE in vec4 aRect; +PER_INSTANCE in vec4 aColor; + +void main(void) { + vec2 pos = aRect.xy + aPosition.xy * aRect.zw; + gl_Position = uTransform * vec4(pos, 0.0, 1.0); + gl_Position.z = gl_Position.w; // force depth clear to 1.0 + vColor = aColor; +} +#endif + +#ifdef WR_FRAGMENT_SHADER +void main(void) { + oFragColor = vColor; +} +#endif diff --git a/third_party/webrender/webrender/res/ps_split_composite.glsl b/third_party/webrender/webrender/res/ps_split_composite.glsl new file mode 100644 index 00000000000..2de94126848 --- /dev/null +++ b/third_party/webrender/webrender/res/ps_split_composite.glsl @@ -0,0 +1,118 @@ +/* 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/. */ + +#include shared,prim_shared + +// interpolated UV coordinates to sample. +varying vec2 vUv; +// X = layer index to sample, Y = flag to allow perspective interpolation of UV. +flat varying vec2 vLayerAndPerspective; +flat varying vec4 vUvSampleBounds; + +#ifdef WR_VERTEX_SHADER +struct SplitGeometry { + vec2 local[4]; +}; + +SplitGeometry fetch_split_geometry(int address) { + ivec2 uv = get_gpu_cache_uv(address); + + vec4 data0 = TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0)); + vec4 data1 = TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0)); + + SplitGeometry geo; + geo.local = vec2[4]( + data0.xy, + data0.zw, + data1.xy, + data1.zw + ); + + return geo; +} + +vec2 bilerp(vec2 a, vec2 b, vec2 c, vec2 d, float s, float t) { + vec2 x = mix(a, b, t); + vec2 y = mix(c, d, t); + return mix(x, y, s); +} + +struct SplitCompositeInstance { + int prim_header_index; + int polygons_address; + float z; + int render_task_index; +}; + +SplitCompositeInstance fetch_composite_instance() { + SplitCompositeInstance ci; + + ci.prim_header_index = aData.x; + ci.polygons_address = aData.y; + ci.z = float(aData.z); + ci.render_task_index = aData.w; + + return ci; +} + +void main(void) { + SplitCompositeInstance ci = fetch_composite_instance(); + SplitGeometry geometry = fetch_split_geometry(ci.polygons_address); + PrimitiveHeader ph = fetch_prim_header(ci.prim_header_index); + PictureTask dest_task = fetch_picture_task(ci.render_task_index); + Transform transform = fetch_transform(ph.transform_id); + ImageResource res = fetch_image_resource(ph.user_data.x); + ClipArea clip_area = fetch_clip_area(ph.user_data.w); + + vec2 dest_origin = dest_task.common_data.task_rect.p0 - + dest_task.content_origin; + + vec2 local_pos = bilerp(geometry.local[0], geometry.local[1], + geometry.local[3], geometry.local[2], + aPosition.y, aPosition.x); + vec4 world_pos = transform.m * vec4(local_pos, 0.0, 1.0); + + vec4 final_pos = vec4( + dest_origin * world_pos.w + world_pos.xy * dest_task.device_pixel_scale, + world_pos.w * ci.z, + world_pos.w + ); + + write_clip( + world_pos, + clip_area + ); + + gl_Position = uTransform * final_pos; + + vec2 texture_size = vec2(textureSize(sPrevPassColor, 0)); + vec2 uv0 = res.uv_rect.p0; + vec2 uv1 = res.uv_rect.p1; + + vec2 min_uv = min(uv0, uv1); + vec2 max_uv = max(uv0, uv1); + + vUvSampleBounds = vec4( + min_uv + vec2(0.5), + max_uv - vec2(0.5) + ) / texture_size.xyxy; + + vec2 f = (local_pos - ph.local_rect.p0) / ph.local_rect.size; + f = get_image_quad_uv(ph.user_data.x, f); + vec2 uv = mix(uv0, uv1, f); + float perspective_interpolate = float(ph.user_data.y); + + vUv = uv / texture_size * mix(gl_Position.w, 1.0, perspective_interpolate); + vLayerAndPerspective = vec2(res.layer, perspective_interpolate); +} +#endif + +#ifdef WR_FRAGMENT_SHADER +void main(void) { + float alpha = do_clip(); + float perspective_divisor = mix(gl_FragCoord.w, 1.0, vLayerAndPerspective.y); + vec2 uv = clamp(vUv * perspective_divisor, vUvSampleBounds.xy, vUvSampleBounds.zw); + write_output(alpha * textureLod(sPrevPassColor, vec3(uv, vLayerAndPerspective.x), 0.0)); +} +#endif diff --git a/third_party/webrender/webrender/res/ps_text_run.glsl b/third_party/webrender/webrender/res/ps_text_run.glsl new file mode 100644 index 00000000000..c2f626dded6 --- /dev/null +++ b/third_party/webrender/webrender/res/ps_text_run.glsl @@ -0,0 +1,335 @@ +/* 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/. */ + +#define WR_VERTEX_SHADER_MAIN_FUNCTION text_shader_main_vs +#define WR_BRUSH_FS_FUNCTION text_brush_fs +#define WR_BRUSH_VS_FUNCTION text_brush_vs +// The text brush shader doesn't use this but the macro must be defined +// to compile the brush infrastructure. +#define VECS_PER_SPECIFIC_BRUSH 0 + +#include shared,prim_shared + +#ifdef WR_VERTEX_SHADER +// Forward-declare the text vertex shader's main entry-point before including +// the brush shader. +void text_shader_main_vs( + Instance instance, + PrimitiveHeader ph, + Transform transform, + PictureTask task, + ClipArea clip_area +); +#endif + +#include brush + +#define V_COLOR flat_varying_vec4_0 +#define V_MASK_SWIZZLE flat_varying_vec4_1.xy +// Normalized bounds of the source image in the texture. +#define V_UV_BOUNDS flat_varying_vec4_2 + +// Interpolated UV coordinates to sample. +#define V_UV varying_vec4_0.xy +#define V_LAYER varying_vec4_0.z + + +#ifdef WR_FEATURE_GLYPH_TRANSFORM +#define V_UV_CLIP varying_vec4_1 +#endif + +#ifdef WR_VERTEX_SHADER + +#define VECS_PER_TEXT_RUN 2 +#define GLYPHS_PER_GPU_BLOCK 2U + +#ifdef WR_FEATURE_GLYPH_TRANSFORM +RectWithSize transform_rect(RectWithSize rect, mat2 transform) { + vec2 center = transform * (rect.p0 + rect.size * 0.5); + vec2 radius = mat2(abs(transform[0]), abs(transform[1])) * (rect.size * 0.5); + return RectWithSize(center - radius, radius * 2.0); +} + +bool rect_inside_rect(RectWithSize little, RectWithSize big) { + return all(lessThanEqual(vec4(big.p0, little.p0 + little.size), + vec4(little.p0, big.p0 + big.size))); +} +#endif //WR_FEATURE_GLYPH_TRANSFORM + +struct Glyph { + vec2 offset; +}; + +Glyph fetch_glyph(int specific_prim_address, + int glyph_index) { + // Two glyphs are packed in each texel in the GPU cache. + int glyph_address = specific_prim_address + + VECS_PER_TEXT_RUN + + int(uint(glyph_index) / GLYPHS_PER_GPU_BLOCK); + vec4 data = fetch_from_gpu_cache_1(glyph_address); + // Select XY or ZW based on glyph index. + // We use "!= 0" instead of "== 1" here in order to work around a driver + // bug with equality comparisons on integers. + vec2 glyph = mix(data.xy, data.zw, + bvec2(uint(glyph_index) % GLYPHS_PER_GPU_BLOCK != 0U)); + + return Glyph(glyph); +} + +struct GlyphResource { + vec4 uv_rect; + float layer; + vec2 offset; + float scale; +}; + +GlyphResource fetch_glyph_resource(int address) { + vec4 data[2] = fetch_from_gpu_cache_2(address); + return GlyphResource(data[0], data[1].x, data[1].yz, data[1].w); +} + +struct TextRun { + vec4 color; + vec4 bg_color; +}; + +TextRun fetch_text_run(int address) { + vec4 data[2] = fetch_from_gpu_cache_2(address); + return TextRun(data[0], data[1]); +} + +vec2 get_snap_bias(int subpx_dir) { + // In subpixel mode, the subpixel offset has already been + // accounted for while rasterizing the glyph. However, we + // must still round with a subpixel bias rather than rounding + // to the nearest whole pixel, depending on subpixel direciton. + switch (subpx_dir) { + case SUBPX_DIR_NONE: + default: + return vec2(0.5); + case SUBPX_DIR_HORIZONTAL: + // Glyphs positioned [-0.125, 0.125] get a + // subpx position of zero. So include that + // offset in the glyph position to ensure + // we round to the correct whole position. + return vec2(0.125, 0.5); + case SUBPX_DIR_VERTICAL: + return vec2(0.5, 0.125); + case SUBPX_DIR_MIXED: + return vec2(0.125); + } +} + +void text_shader_main_vs( + Instance instance, + PrimitiveHeader ph, + Transform transform, + PictureTask task, + ClipArea clip_area +) { + int glyph_index = instance.segment_index; + int subpx_dir = (instance.flags >> 8) & 0xff; + int color_mode = instance.flags & 0xff; + + // Note that the reference frame relative offset is stored in the prim local + // rect size during batching, instead of the actual size of the primitive. + TextRun text = fetch_text_run(ph.specific_prim_address); + vec2 text_offset = ph.local_rect.size; + + if (color_mode == COLOR_MODE_FROM_PASS) { + color_mode = uMode; + } + + // Note that the unsnapped reference frame relative offset has already + // been subtracted from the prim local rect origin during batching. + // It was done this way to avoid pushing both the snapped and the + // unsnapped offsets to the shader. + Glyph glyph = fetch_glyph(ph.specific_prim_address, glyph_index); + glyph.offset += ph.local_rect.p0; + + GlyphResource res = fetch_glyph_resource(instance.resource_address); + + vec2 snap_bias = get_snap_bias(subpx_dir); + + // Glyph space refers to the pixel space used by glyph rasterization during frame + // building. If a non-identity transform was used, WR_FEATURE_GLYPH_TRANSFORM will + // be set. Otherwise, regardless of whether the raster space is LOCAL or SCREEN, + // we ignored the transform during glyph rasterization, and need to snap just using + // the device pixel scale and the raster scale. +#ifdef WR_FEATURE_GLYPH_TRANSFORM + // Transform from local space to glyph space. + mat2 glyph_transform = mat2(transform.m) * task.device_pixel_scale; + vec2 glyph_translation = transform.m[3].xy * task.device_pixel_scale; + + // Transform from glyph space back to local space. + mat2 glyph_transform_inv = inverse(glyph_transform); + + // Glyph raster pixels include the impact of the transform. This path can only be + // entered for 3d transforms that can be coerced into a 2d transform; they have no + // perspective, and have a 2d inverse. This is a looser condition than axis aligned + // transforms because it also allows 2d rotations. + vec2 raster_glyph_offset = floor(glyph_transform * glyph.offset + snap_bias); + + // We want to eliminate any subpixel translation in device space to ensure glyph + // snapping is stable for equivalent glyph subpixel positions. Note that we must take + // into account the translation from the transform for snapping purposes. + vec2 raster_text_offset = floor(glyph_transform * text_offset + glyph_translation + 0.5) - glyph_translation; + + // Compute the glyph rect in glyph space. + RectWithSize glyph_rect = RectWithSize(res.offset + raster_glyph_offset + raster_text_offset, + res.uv_rect.zw - res.uv_rect.xy); + + // The glyph rect is in glyph space, so transform it back to local space. + RectWithSize local_rect = transform_rect(glyph_rect, glyph_transform_inv); + + // Select the corner of the glyph's local space rect that we are processing. + vec2 local_pos = local_rect.p0 + local_rect.size * aPosition.xy; + + // If the glyph's local rect would fit inside the local clip rect, then select a corner from + // the device space glyph rect to reduce overdraw of clipped pixels in the fragment shader. + // Otherwise, fall back to clamping the glyph's local rect to the local clip rect. + if (rect_inside_rect(local_rect, ph.local_clip_rect)) { + local_pos = glyph_transform_inv * (glyph_rect.p0 + glyph_rect.size * aPosition.xy); + } +#else + float raster_scale = float(ph.user_data.x) / 65535.0; + + // Scale in which the glyph is snapped when rasterized. + float glyph_raster_scale = raster_scale * task.device_pixel_scale; + + // Scale from glyph space to local space. + float glyph_scale_inv = res.scale / glyph_raster_scale; + + // Glyph raster pixels do not include the impact of the transform. Instead it was + // replaced with an identity transform during glyph rasterization. As such only the + // impact of the raster scale (if in local space) and the device pixel scale (for both + // local and screen space) are included. + // + // This implies one or more of the following conditions: + // - The transform is an identity. In that case, setting WR_FEATURE_GLYPH_TRANSFORM + // should have the same output result as not. We just distingush which path to use + // based on the transform used during glyph rasterization. (Screen space). + // - The transform contains an animation. We will imply local raster space in such + // cases to avoid constantly rerasterizing the glyphs. + // - The transform has perspective or does not have a 2d inverse (Screen or local space). + // - The transform's scale will result in result in very large rasterized glyphs and + // we clamped the size. This will imply local raster space. + vec2 raster_glyph_offset = floor(glyph.offset * glyph_raster_scale + snap_bias) / res.scale; + + // Compute the glyph rect in local space. + // + // The transform may be animated, so we don't want to do any snapping here for the + // text offset to avoid glyphs wiggling. The text offset should have been snapped + // already for axis aligned transforms excluding any animations during frame building. + RectWithSize glyph_rect = RectWithSize(glyph_scale_inv * (res.offset + raster_glyph_offset) + text_offset, + glyph_scale_inv * (res.uv_rect.zw - res.uv_rect.xy)); + + // Select the corner of the glyph rect that we are processing. + vec2 local_pos = glyph_rect.p0 + glyph_rect.size * aPosition.xy; +#endif + + VertexInfo vi = write_vertex( + local_pos, + ph.local_clip_rect, + ph.z, + transform, + task + ); + +#ifdef WR_FEATURE_GLYPH_TRANSFORM + vec2 f = (glyph_transform * vi.local_pos - glyph_rect.p0) / glyph_rect.size; + V_UV_CLIP = vec4(f, 1.0 - f); +#else + vec2 f = (vi.local_pos - glyph_rect.p0) / glyph_rect.size; +#endif + + write_clip(vi.world_pos, clip_area); + + switch (color_mode) { + case COLOR_MODE_ALPHA: + case COLOR_MODE_BITMAP: + V_MASK_SWIZZLE = vec2(0.0, 1.0); + V_COLOR = text.color; + break; + case COLOR_MODE_SUBPX_BG_PASS2: + case COLOR_MODE_SUBPX_DUAL_SOURCE: + V_MASK_SWIZZLE = vec2(1.0, 0.0); + V_COLOR = text.color; + break; + case COLOR_MODE_SUBPX_CONST_COLOR: + case COLOR_MODE_SUBPX_BG_PASS0: + case COLOR_MODE_COLOR_BITMAP: + V_MASK_SWIZZLE = vec2(1.0, 0.0); + V_COLOR = vec4(text.color.a); + break; + case COLOR_MODE_SUBPX_BG_PASS1: + V_MASK_SWIZZLE = vec2(-1.0, 1.0); + V_COLOR = vec4(text.color.a) * text.bg_color; + break; + default: + V_MASK_SWIZZLE = vec2(0.0); + V_COLOR = vec4(1.0); + } + + vec2 texture_size = vec2(textureSize(sColor0, 0)); + vec2 st0 = res.uv_rect.xy / texture_size; + vec2 st1 = res.uv_rect.zw / texture_size; + + V_UV = mix(st0, st1, f); + V_LAYER = res.layer; + V_UV_BOUNDS = (res.uv_rect + vec4(0.5, 0.5, -0.5, -0.5)) / texture_size.xyxy; +} + +void text_brush_vs( + VertexInfo vi, + int prim_address, + RectWithSize prim_rect, + RectWithSize segment_rect, + ivec4 prim_user_data, + int specific_resource_address, + mat4 transform, + PictureTask pic_task, + int brush_flags, + vec4 segment_data +) { + // This function is empty and unused for now. It has to be defined to build the shader + // as a brush, but the brush shader currently branches into text_shader_main_vs earlier + // instead of using the regular brush vertex interface for text. + // In the future we should strive to further unify text and brushes, and actually make + // use of this function. +} + +#endif // WR_VERTEX_SHADER + +#ifdef WR_FRAGMENT_SHADER + +Fragment text_brush_fs(void) { + Fragment frag; + + vec3 tc = vec3(clamp(V_UV, V_UV_BOUNDS.xy, V_UV_BOUNDS.zw), V_LAYER); + vec4 mask = texture(sColor0, tc); + mask.rgb = mask.rgb * V_MASK_SWIZZLE.x + mask.aaa * V_MASK_SWIZZLE.y; + + #ifdef WR_FEATURE_GLYPH_TRANSFORM + mask *= float(all(greaterThanEqual(V_UV_CLIP, vec4(0.0)))); + #endif + + frag.color = V_COLOR * mask; + + #ifdef WR_FEATURE_DUAL_SOURCE_BLENDING + frag.blend = V_COLOR.a * mask; + #endif + + return frag; +} + +#endif // WR_FRAGMENT_SHADER + +// Undef macro names that could be re-defined by other shaders. +#undef V_COLOR +#undef V_MASK_SWIZZLE +#undef V_UV_BOUNDS +#undef V_UV +#undef V_LAYER +#undef V_UV_CLIP diff --git a/third_party/webrender/webrender/res/rect.glsl b/third_party/webrender/webrender/res/rect.glsl new file mode 100644 index 00000000000..093aea02804 --- /dev/null +++ b/third_party/webrender/webrender/res/rect.glsl @@ -0,0 +1,42 @@ +/* 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/. */ + +struct RectWithSize { + vec2 p0; + vec2 size; +}; + +struct RectWithEndpoint { + vec2 p0; + vec2 p1; +}; + +RectWithEndpoint to_rect_with_endpoint(RectWithSize rect) { + RectWithEndpoint result; + result.p0 = rect.p0; + result.p1 = rect.p0 + rect.size; + + return result; +} + +RectWithSize to_rect_with_size(RectWithEndpoint rect) { + RectWithSize result; + result.p0 = rect.p0; + result.size = rect.p1 - rect.p0; + + return result; +} + +RectWithSize intersect_rects(RectWithSize a, RectWithSize b) { + RectWithSize result; + result.p0 = max(a.p0, b.p0); + result.size = min(a.p0 + a.size, b.p0 + b.size) - result.p0; + + return result; +} + +float point_inside_rect(vec2 p, vec2 p0, vec2 p1) { + vec2 s = step(p0, p) - step(p1, p); + return s.x * s.y; +} diff --git a/third_party/webrender/webrender/res/render_task.glsl b/third_party/webrender/webrender/res/render_task.glsl new file mode 100644 index 00000000000..54582cd9adf --- /dev/null +++ b/third_party/webrender/webrender/res/render_task.glsl @@ -0,0 +1,118 @@ +/* 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/. */ + + +#ifdef WR_VERTEX_SHADER +#define VECS_PER_RENDER_TASK 2U + +uniform HIGHP_SAMPLER_FLOAT sampler2D sRenderTasks; + +struct RenderTaskCommonData { + RectWithSize task_rect; + float texture_layer_index; +}; + +struct RenderTaskData { + RenderTaskCommonData common_data; + vec3 user_data; +}; + +RenderTaskData fetch_render_task_data(int index) { + ivec2 uv = get_fetch_uv(index, VECS_PER_RENDER_TASK); + + vec4 texel0 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(0, 0)); + vec4 texel1 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(1, 0)); + + RectWithSize task_rect = RectWithSize( + texel0.xy, + texel0.zw + ); + + RenderTaskCommonData common_data = RenderTaskCommonData( + task_rect, + texel1.x + ); + + RenderTaskData data = RenderTaskData( + common_data, + texel1.yzw + ); + + return data; +} + +RenderTaskCommonData fetch_render_task_common_data(int index) { + ivec2 uv = get_fetch_uv(index, VECS_PER_RENDER_TASK); + + vec4 texel0 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(0, 0)); + vec4 texel1 = TEXEL_FETCH(sRenderTasks, uv, 0, ivec2(1, 0)); + + RectWithSize task_rect = RectWithSize( + texel0.xy, + texel0.zw + ); + + RenderTaskCommonData data = RenderTaskCommonData( + task_rect, + texel1.x + ); + + return data; +} + +#define PIC_TYPE_IMAGE 1 +#define PIC_TYPE_TEXT_SHADOW 2 + +/* + The dynamic picture that this brush exists on. Right now, it + contains minimal information. In the future, it will describe + the transform mode of primitives on this picture, among other things. + */ +struct PictureTask { + RenderTaskCommonData common_data; + float device_pixel_scale; + vec2 content_origin; +}; + +PictureTask fetch_picture_task(int address) { + RenderTaskData task_data = fetch_render_task_data(address); + + PictureTask task = PictureTask( + task_data.common_data, + task_data.user_data.x, + task_data.user_data.yz + ); + + return task; +} + +#define CLIP_TASK_EMPTY 0x7FFF + +struct ClipArea { + RenderTaskCommonData common_data; + float device_pixel_scale; + vec2 screen_origin; +}; + +ClipArea fetch_clip_area(int index) { + ClipArea area; + + if (index >= CLIP_TASK_EMPTY) { + RectWithSize rect = RectWithSize(vec2(0.0), vec2(0.0)); + + area.common_data = RenderTaskCommonData(rect, 0.0); + area.device_pixel_scale = 0.0; + area.screen_origin = vec2(0.0); + } else { + RenderTaskData task_data = fetch_render_task_data(index); + + area.common_data = task_data.common_data; + area.device_pixel_scale = task_data.user_data.x; + area.screen_origin = task_data.user_data.yz; + } + + return area; +} + +#endif //WR_VERTEX_SHADER diff --git a/third_party/webrender/webrender/res/shared.glsl b/third_party/webrender/webrender/res/shared.glsl new file mode 100644 index 00000000000..7723b70f19c --- /dev/null +++ b/third_party/webrender/webrender/res/shared.glsl @@ -0,0 +1,241 @@ +/* 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/. */ + +#ifdef WR_FEATURE_PIXEL_LOCAL_STORAGE +// For now, we need both extensions here, in order to initialize +// the PLS to the current framebuffer color. In future, we can +// possibly remove that requirement, or at least support the +// other framebuffer fetch extensions that provide the same +// functionality. +#extension GL_EXT_shader_pixel_local_storage : require +#extension GL_ARM_shader_framebuffer_fetch : require +#endif + +#ifdef WR_FEATURE_TEXTURE_EXTERNAL +// Please check https://www.khronos.org/registry/OpenGL/extensions/OES/OES_EGL_image_external_essl3.txt +// for this extension. +#extension GL_OES_EGL_image_external_essl3 : require +#endif + +#ifdef WR_FEATURE_ADVANCED_BLEND +#extension GL_KHR_blend_equation_advanced : require +#endif + +#ifdef WR_FEATURE_DUAL_SOURCE_BLENDING +#ifdef GL_ES +#extension GL_EXT_blend_func_extended : require +#else +#extension GL_ARB_explicit_attrib_location : require +#endif +#endif + +#include base + +#if defined(WR_FEATURE_TEXTURE_EXTERNAL) || defined(WR_FEATURE_TEXTURE_RECT) || defined(WR_FEATURE_TEXTURE_2D) +#define TEX_SAMPLE(sampler, tex_coord) texture(sampler, tex_coord.xy) +#else +#define TEX_SAMPLE(sampler, tex_coord) texture(sampler, tex_coord) +#endif + +//====================================================================================== +// Vertex shader attributes and uniforms +//====================================================================================== +#ifdef WR_VERTEX_SHADER + // A generic uniform that shaders can optionally use to configure + // an operation mode for this batch. + uniform int uMode; + + // Uniform inputs + uniform mat4 uTransform; // Orthographic projection + + // Attribute inputs + in vec2 aPosition; + + // get_fetch_uv is a macro to work around a macOS Intel driver parsing bug. + // TODO: convert back to a function once the driver issues are resolved, if ever. + // https://github.com/servo/webrender/pull/623 + // https://github.com/servo/servo/issues/13953 + // Do the division with unsigned ints because that's more efficient with D3D + #define get_fetch_uv(i, vpi) ivec2(int(vpi * (uint(i) % (WR_MAX_VERTEX_TEXTURE_WIDTH/vpi))), int(uint(i) / (WR_MAX_VERTEX_TEXTURE_WIDTH/vpi))) +#endif + +//====================================================================================== +// Fragment shader attributes and uniforms +//====================================================================================== +#ifdef WR_FRAGMENT_SHADER + // Uniform inputs + + #ifdef WR_FEATURE_PIXEL_LOCAL_STORAGE + // Define the storage class of the pixel local storage. + // If defined as writable, it's a compile time error to + // have a normal fragment output variable declared. + #if defined(PLS_READONLY) + #define PLS_BLOCK __pixel_local_inEXT + #elif defined(PLS_WRITEONLY) + #define PLS_BLOCK __pixel_local_outEXT + #else + #define PLS_BLOCK __pixel_localEXT + #endif + + // The structure of pixel local storage. Right now, it's + // just the current framebuffer color. In future, we have + // (at least) 12 bytes of space we can store extra info + // here (such as clip mask values). + PLS_BLOCK FrameBuffer { + layout(rgba8) highp vec4 color; + } PLS; + + #ifndef PLS_READONLY + // Write the output of a fragment shader to PLS. Applies + // premultipled alpha blending by default, since the blender + // is disabled when PLS is active. + // TODO(gw): Properly support alpha blend mode for webgl / canvas. + void write_output(vec4 color) { + PLS.color = color + PLS.color * (1.0 - color.a); + } + + // Write a raw value straight to PLS, if the fragment shader has + // already applied blending. + void write_output_raw(vec4 color) { + PLS.color = color; + } + #endif + + #ifndef PLS_WRITEONLY + // Retrieve the current framebuffer color. Useful in conjunction with + // the write_output_raw function. + vec4 get_current_framebuffer_color() { + return PLS.color; + } + #endif + #else + // Fragment shader outputs + #ifdef WR_FEATURE_ADVANCED_BLEND + layout(blend_support_all_equations) out; + #endif + + #ifdef WR_FEATURE_DUAL_SOURCE_BLENDING + layout(location = 0, index = 0) out vec4 oFragColor; + layout(location = 0, index = 1) out vec4 oFragBlend; + #else + out vec4 oFragColor; + #endif + + // Write an output color in normal (non-PLS) shaders. + void write_output(vec4 color) { + oFragColor = color; + } + #endif + + #define EPSILON 0.0001 + + // "Show Overdraw" color. Premultiplied. + #define WR_DEBUG_OVERDRAW_COLOR vec4(0.110, 0.077, 0.027, 0.125) + + float distance_to_line(vec2 p0, vec2 perp_dir, vec2 p) { + vec2 dir_to_p0 = p0 - p; + return dot(normalize(perp_dir), dir_to_p0); + } + + /// Find the appropriate half range to apply the AA approximation over. + /// This range represents a coefficient to go from one CSS pixel to half a device pixel. + float compute_aa_range(vec2 position) { + // The constant factor is chosen to compensate for the fact that length(fw) is equal + // to sqrt(2) times the device pixel ratio in the typical case. 0.5/sqrt(2) = 0.35355. + // + // This coefficient is chosen to ensure that any sample 0.5 pixels or more inside of + // the shape has no anti-aliasing applied to it (since pixels are sampled at their center, + // such a pixel (axis aligned) is fully inside the border). We need this so that antialiased + // curves properly connect with non-antialiased vertical or horizontal lines, among other things. + // + // Lines over a half-pixel away from the pixel center *can* intersect with the pixel square; + // indeed, unless they are horizontal or vertical, they are guaranteed to. However, choosing + // a nonzero area for such pixels causes noticeable artifacts at the junction between an anti- + // aliased corner and a straight edge. + // + // We may want to adjust this constant in specific scenarios (for example keep the principled + // value for straight edges where we want pixel-perfect equivalence with non antialiased lines + // when axis aligned, while selecting a larger and smoother aa range on curves). + return 0.35355 * length(fwidth(position)); + } + + /// Return the blending coefficient for distance antialiasing. + /// + /// 0.0 means inside the shape, 1.0 means outside. + /// + /// This cubic polynomial approximates the area of a 1x1 pixel square under a + /// line, given the signed Euclidean distance from the center of the square to + /// that line. Calculating the *exact* area would require taking into account + /// not only this distance but also the angle of the line. However, in + /// practice, this complexity is not required, as the area is roughly the same + /// regardless of the angle. + /// + /// The coefficients of this polynomial were determined through least-squares + /// regression and are accurate to within 2.16% of the total area of the pixel + /// square 95% of the time, with a maximum error of 3.53%. + /// + /// See the comments in `compute_aa_range()` for more information on the + /// cutoff values of -0.5 and 0.5. + float distance_aa(float aa_range, float signed_distance) { + float dist = 0.5 * signed_distance / aa_range; + if (dist <= -0.5 + EPSILON) + return 1.0; + if (dist >= 0.5 - EPSILON) + return 0.0; + return 0.5 + dist * (0.8431027 * dist * dist - 1.14453603); + } + + /// Component-wise selection. + /// + /// The idea of using this is to ensure both potential branches are executed before + /// selecting the result, to avoid observable timing differences based on the condition. + /// + /// Example usage: color = if_then_else(LessThanEqual(color, vec3(0.5)), vec3(0.0), vec3(1.0)); + /// + /// The above example sets each component to 0.0 or 1.0 independently depending on whether + /// their values are below or above 0.5. + /// + /// This is written as a macro in order to work with vectors of any dimension. + /// + /// Note: Some older android devices don't support mix with bvec. If we ever run into them + /// the only option we have is to polyfill it with a branch per component. + #define if_then_else(cond, then_branch, else_branch) mix(else_branch, then_branch, cond) +#endif + +//====================================================================================== +// Shared shader uniforms +//====================================================================================== +#ifdef WR_FEATURE_TEXTURE_2D +uniform sampler2D sColor0; +uniform sampler2D sColor1; +uniform sampler2D sColor2; +#elif defined WR_FEATURE_TEXTURE_RECT +uniform sampler2DRect sColor0; +uniform sampler2DRect sColor1; +uniform sampler2DRect sColor2; +#elif defined WR_FEATURE_TEXTURE_EXTERNAL +uniform samplerExternalOES sColor0; +uniform samplerExternalOES sColor1; +uniform samplerExternalOES sColor2; +#else +uniform sampler2DArray sColor0; +uniform sampler2DArray sColor1; +uniform sampler2DArray sColor2; +#endif + +#ifdef WR_FEATURE_DITHERING +uniform sampler2D sDither; +#endif + +//====================================================================================== +// Interpolator definitions +//====================================================================================== + +//====================================================================================== +// VS only types and UBOs +//====================================================================================== + +//====================================================================================== +// VS only functions +//====================================================================================== diff --git a/third_party/webrender/webrender/res/shared_other.glsl b/third_party/webrender/webrender/res/shared_other.glsl new file mode 100644 index 00000000000..03cad173cdb --- /dev/null +++ b/third_party/webrender/webrender/res/shared_other.glsl @@ -0,0 +1,33 @@ +/* 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/. */ + +//====================================================================================== +// Vertex shader attributes and uniforms +//====================================================================================== +#ifdef WR_VERTEX_SHADER +#endif + +//====================================================================================== +// Fragment shader attributes and uniforms +//====================================================================================== +#ifdef WR_FRAGMENT_SHADER +#endif + +//====================================================================================== +// Interpolator definitions +//====================================================================================== + +//====================================================================================== +// VS only types and UBOs +//====================================================================================== + +//====================================================================================== +// VS only functions +//====================================================================================== + +//====================================================================================== +// FS only functions +//====================================================================================== +#ifdef WR_FRAGMENT_SHADER +#endif diff --git a/third_party/webrender/webrender/res/transform.glsl b/third_party/webrender/webrender/res/transform.glsl new file mode 100644 index 00000000000..99eabec2a46 --- /dev/null +++ b/third_party/webrender/webrender/res/transform.glsl @@ -0,0 +1,124 @@ +/* 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/. */ + +flat varying vec4 vTransformBounds; + +#ifdef WR_VERTEX_SHADER + +#define VECS_PER_TRANSFORM 8U +uniform HIGHP_SAMPLER_FLOAT sampler2D sTransformPalette; + +void init_transform_vs(vec4 local_bounds) { + vTransformBounds = local_bounds; +} + +struct Transform { + mat4 m; + mat4 inv_m; + bool is_axis_aligned; +}; + +Transform fetch_transform(int id) { + Transform transform; + + transform.is_axis_aligned = (id >> 24) == 0; + int index = id & 0x00ffffff; + + // Create a UV base coord for each 8 texels. + // This is required because trying to use an offset + // of more than 8 texels doesn't work on some versions + // of macOS. + ivec2 uv = get_fetch_uv(index, VECS_PER_TRANSFORM); + ivec2 uv0 = ivec2(uv.x + 0, uv.y); + + transform.m[0] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(0, 0)); + transform.m[1] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(1, 0)); + transform.m[2] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(2, 0)); + transform.m[3] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(3, 0)); + + transform.inv_m[0] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(4, 0)); + transform.inv_m[1] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(5, 0)); + transform.inv_m[2] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(6, 0)); + transform.inv_m[3] = TEXEL_FETCH(sTransformPalette, uv0, 0, ivec2(7, 0)); + + return transform; +} + +// Return the intersection of the plane (set up by "normal" and "point") +// with the ray (set up by "ray_origin" and "ray_dir"), +// writing the resulting scaler into "t". +bool ray_plane(vec3 normal, vec3 pt, vec3 ray_origin, vec3 ray_dir, out float t) +{ + float denom = dot(normal, ray_dir); + if (abs(denom) > 1e-6) { + vec3 d = pt - ray_origin; + t = dot(d, normal) / denom; + return t >= 0.0; + } + + return false; +} + +// Apply the inverse transform "inv_transform" +// to the reference point "ref" in CSS space, +// producing a local point on a Transform plane, +// set by a base point "a" and a normal "n". +vec4 untransform(vec2 ref, vec3 n, vec3 a, mat4 inv_transform) { + vec3 p = vec3(ref, -10000.0); + vec3 d = vec3(0, 0, 1.0); + + float t = 0.0; + // get an intersection of the Transform plane with Z axis vector, + // originated from the "ref" point + ray_plane(n, a, p, d, t); + float z = p.z + d.z * t; // Z of the visible point on the Transform + + vec4 r = inv_transform * vec4(ref, z, 1.0); + return r; +} + +// Given a CSS space position, transform it back into the Transform space. +vec4 get_node_pos(vec2 pos, Transform transform) { + // get a point on the scroll node plane + vec4 ah = transform.m * vec4(0.0, 0.0, 0.0, 1.0); + vec3 a = ah.xyz / ah.w; + + // get the normal to the scroll node plane + vec3 n = transpose(mat3(transform.inv_m)) * vec3(0.0, 0.0, 1.0); + return untransform(pos, n, a, transform.inv_m); +} + +#endif //WR_VERTEX_SHADER + +#ifdef WR_FRAGMENT_SHADER + +float signed_distance_rect(vec2 pos, vec2 p0, vec2 p1) { + vec2 d = max(p0 - pos, pos - p1); + return length(max(vec2(0.0), d)) + min(0.0, max(d.x, d.y)); +} + +float init_transform_fs(vec2 local_pos) { + // Get signed distance from local rect bounds. + float d = signed_distance_rect( + local_pos, + vTransformBounds.xy, + vTransformBounds.zw + ); + + // Find the appropriate distance to apply the AA smoothstep over. + float aa_range = compute_aa_range(local_pos); + + // Only apply AA to fragments outside the signed distance field. + return distance_aa(aa_range, d); +} + +float init_transform_rough_fs(vec2 local_pos) { + return point_inside_rect( + local_pos, + vTransformBounds.xy, + vTransformBounds.zw + ); +} + +#endif //WR_FRAGMENT_SHADER diff --git a/third_party/webrender/webrender/res/yuv.glsl b/third_party/webrender/webrender/res/yuv.glsl new file mode 100644 index 00000000000..ce582467be5 --- /dev/null +++ b/third_party/webrender/webrender/res/yuv.glsl @@ -0,0 +1,156 @@ +/* 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/. */ + +#include shared + +#define YUV_FORMAT_NV12 0 +#define YUV_FORMAT_PLANAR 1 +#define YUV_FORMAT_INTERLEAVED 2 + +#ifdef WR_VERTEX_SHADER + +#ifdef WR_FEATURE_TEXTURE_RECT + #define TEX_SIZE(sampler) vec2(1.0) +#else + #define TEX_SIZE(sampler) vec2(textureSize(sampler, 0).xy) +#endif + +#define YUV_COLOR_SPACE_REC601 0 +#define YUV_COLOR_SPACE_REC709 1 +#define YUV_COLOR_SPACE_REC2020 2 + +// The constants added to the Y, U and V components are applied in the fragment shader. + +// From Rec601: +// [R] [1.1643835616438356, 0.0, 1.5960267857142858 ] [Y - 16] +// [G] = [1.1643835616438358, -0.3917622900949137, -0.8129676472377708 ] x [U - 128] +// [B] [1.1643835616438356, 2.017232142857143, 8.862867620416422e-17] [V - 128] +// +// For the range [0,1] instead of [0,255]. +// +// The matrix is stored in column-major. +const mat3 YuvColorMatrixRec601 = mat3( + 1.16438, 1.16438, 1.16438, + 0.0, -0.39176, 2.01723, + 1.59603, -0.81297, 0.0 +); + +// From Rec709: +// [R] [1.1643835616438356, 0.0, 1.7927410714285714] [Y - 16] +// [G] = [1.1643835616438358, -0.21324861427372963, -0.532909328559444 ] x [U - 128] +// [B] [1.1643835616438356, 2.1124017857142854, 0.0 ] [V - 128] +// +// For the range [0,1] instead of [0,255]: +// +// The matrix is stored in column-major. +const mat3 YuvColorMatrixRec709 = mat3( + 1.16438, 1.16438, 1.16438, + 0.0 , -0.21325, 2.11240, + 1.79274, -0.53291, 0.0 +); + +// From Re2020: +// [R] [1.16438356164384, 0.0, 1.678674107142860 ] [Y - 16] +// [G] = [1.16438356164384, -0.187326104219343, -0.650424318505057 ] x [U - 128] +// [B] [1.16438356164384, 2.14177232142857, 0.0 ] [V - 128] +// +// For the range [0,1] instead of [0,255]: +// +// The matrix is stored in column-major. +const mat3 YuvColorMatrixRec2020 = mat3( + 1.16438356164384 , 1.164383561643840, 1.16438356164384, + 0.0 , -0.187326104219343, 2.14177232142857, + 1.67867410714286 , -0.650424318505057, 0.0 +); + +mat3 get_yuv_color_matrix(int color_space) { + switch (color_space) { + case YUV_COLOR_SPACE_REC601: + return YuvColorMatrixRec601; + case YUV_COLOR_SPACE_REC709: + return YuvColorMatrixRec709; + default: + return YuvColorMatrixRec2020; + } +} + +void write_uv_rect( + vec2 uv0, + vec2 uv1, + vec2 f, + vec2 texture_size, + out vec2 uv, + out vec4 uv_bounds +) { + uv = mix(uv0, uv1, f); + + uv_bounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)); + + #ifndef WR_FEATURE_TEXTURE_RECT + uv /= texture_size; + uv_bounds /= texture_size.xyxy; + #endif +} +#endif + +#ifdef WR_FRAGMENT_SHADER + +vec4 sample_yuv( + int format, + mat3 yuv_color_matrix, + float coefficient, + vec3 yuv_layers, + vec2 in_uv_y, + vec2 in_uv_u, + vec2 in_uv_v, + vec4 uv_bounds_y, + vec4 uv_bounds_u, + vec4 uv_bounds_v +) { + vec3 yuv_value; + + switch (format) { + case YUV_FORMAT_PLANAR: + { + // The yuv_planar format should have this third texture coordinate. + vec2 uv_y = clamp(in_uv_y, uv_bounds_y.xy, uv_bounds_y.zw); + vec2 uv_u = clamp(in_uv_u, uv_bounds_u.xy, uv_bounds_u.zw); + vec2 uv_v = clamp(in_uv_v, uv_bounds_v.xy, uv_bounds_v.zw); + yuv_value.x = TEX_SAMPLE(sColor0, vec3(uv_y, yuv_layers.x)).r; + yuv_value.y = TEX_SAMPLE(sColor1, vec3(uv_u, yuv_layers.y)).r; + yuv_value.z = TEX_SAMPLE(sColor2, vec3(uv_v, yuv_layers.z)).r; + } + break; + + case YUV_FORMAT_NV12: + { + vec2 uv_y = clamp(in_uv_y, uv_bounds_y.xy, uv_bounds_y.zw); + vec2 uv_uv = clamp(in_uv_u, uv_bounds_u.xy, uv_bounds_u.zw); + yuv_value.x = TEX_SAMPLE(sColor0, vec3(uv_y, yuv_layers.x)).r; + yuv_value.yz = TEX_SAMPLE(sColor1, vec3(uv_uv, yuv_layers.y)).rg; + } + break; + + case YUV_FORMAT_INTERLEAVED: + { + // "The Y, Cb and Cr color channels within the 422 data are mapped into + // the existing green, blue and red color channels." + // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_rgb_422.txt + vec2 uv_y = clamp(in_uv_y, uv_bounds_y.xy, uv_bounds_y.zw); + yuv_value = TEX_SAMPLE(sColor0, vec3(uv_y, yuv_layers.x)).gbr; + } + break; + + default: + yuv_value = vec3(0.0); + break; + } + + // See the YuvColorMatrix definition for an explanation of where the constants come from. + vec3 rgb = yuv_color_matrix * (yuv_value * coefficient - vec3(0.06275, 0.50196, 0.50196)); + vec4 color = vec4(rgb, 1.0); + + return color; +} +#endif diff --git a/third_party/webrender/webrender/src/batch.rs b/third_party/webrender/webrender/src/batch.rs new file mode 100644 index 00000000000..1ee371c6531 --- /dev/null +++ b/third_party/webrender/webrender/src/batch.rs @@ -0,0 +1,3439 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{AlphaType, ClipMode, ExternalImageType, ImageRendering, EdgeAaSegmentMask}; +use api::{YuvColorSpace, YuvFormat, ColorDepth, ColorRange, PremultipliedColorF}; +use api::units::*; +use crate::clip::{ClipDataStore, ClipNodeFlags, ClipNodeRange, ClipItemKind, ClipStore}; +use crate::spatial_tree::{SpatialTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex, CoordinateSystemId}; +use crate::composite::{CompositeState}; +use crate::glyph_rasterizer::GlyphFormat; +use crate::gpu_cache::{GpuBlockData, GpuCache, GpuCacheHandle, GpuCacheAddress}; +use crate::gpu_types::{BrushFlags, BrushInstance, PrimitiveHeaders, ZBufferId, ZBufferIdGenerator}; +use crate::gpu_types::{ClipMaskInstance, SplitCompositeInstance, BrushShaderKind}; +use crate::gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance}; +use crate::gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette}; +use crate::gpu_types::{ImageBrushData, get_shader_opacity}; +use crate::internal_types::{FastHashMap, SavedTargetIndex, Swizzle, TextureSource, Filter}; +use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive}; +use crate::prim_store::{DeferredResolve, PrimitiveInstanceKind, PrimitiveVisibilityIndex, PrimitiveVisibilityMask}; +use crate::prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex}; +use crate::prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, PrimitiveVisibility, PrimitiveVisibilityFlags}; +use crate::prim_store::{VECS_PER_SEGMENT, SpaceMapper}; +use crate::prim_store::image::ImageSource; +use crate::render_target::RenderTargetContext; +use crate::render_task_graph::{RenderTaskId, RenderTaskGraph}; +use crate::render_task::RenderTaskAddress; +use crate::renderer::{BlendMode, ImageBufferKind, ShaderColorMode}; +use crate::renderer::{BLOCKS_PER_UV_RECT, MAX_VERTEX_TEXTURE_WIDTH}; +use crate::resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache}; +use smallvec::SmallVec; +use std::{f32, i32, usize}; +use crate::util::{project_rect, TransformedRectKind}; + +// Special sentinel value recognized by the shader. It is considered to be +// a dummy task that doesn't mask out anything. +const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fff); + +/// Used to signal there are no segments provided with this primitive. +const INVALID_SEGMENT_INDEX: i32 = 0xffff; + +/// Size in device pixels for tiles that clip masks are drawn in. +const CLIP_RECTANGLE_TILE_SIZE: i32 = 128; + +/// The minimum size of a clip mask before trying to draw in tiles. +const CLIP_RECTANGLE_AREA_THRESHOLD: i32 = CLIP_RECTANGLE_TILE_SIZE * CLIP_RECTANGLE_TILE_SIZE * 4; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum BrushBatchKind { + Solid, + Image(ImageBufferKind), + Blend, + MixBlend { + task_id: RenderTaskId, + source_id: RenderTaskId, + backdrop_id: RenderTaskId, + }, + YuvImage(ImageBufferKind, YuvFormat, ColorDepth, YuvColorSpace, ColorRange), + ConicGradient, + RadialGradient, + LinearGradient, + Opacity, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum BatchKind { + SplitComposite, + TextRun(GlyphFormat), + Brush(BrushBatchKind), +} + +impl BatchKind { + fn shader_kind(&self) -> BrushShaderKind { + match self { + BatchKind::Brush(BrushBatchKind::Solid) => BrushShaderKind::Solid, + BatchKind::Brush(BrushBatchKind::Image(..)) => BrushShaderKind::Image, + BatchKind::Brush(BrushBatchKind::LinearGradient) => BrushShaderKind::LinearGradient, + BatchKind::Brush(BrushBatchKind::RadialGradient) => BrushShaderKind::RadialGradient, + BatchKind::Brush(BrushBatchKind::ConicGradient) => BrushShaderKind::ConicGradient, + BatchKind::Brush(BrushBatchKind::Blend) => BrushShaderKind::Blend, + BatchKind::Brush(BrushBatchKind::MixBlend { .. }) => BrushShaderKind::MixBlend, + BatchKind::Brush(BrushBatchKind::YuvImage(..)) => BrushShaderKind::Yuv, + BatchKind::Brush(BrushBatchKind::Opacity) => BrushShaderKind::Opacity, + BatchKind::TextRun(..) => BrushShaderKind::Text, + _ => BrushShaderKind::None, + } + } +} + +/// Optional textures that can be used as a source in the shaders. +/// Textures that are not used by the batch are equal to TextureId::invalid(). +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct BatchTextures { + pub colors: [TextureSource; 3], +} + +impl BatchTextures { + pub fn no_texture() -> Self { + BatchTextures { + colors: [TextureSource::Invalid; 3], + } + } + + pub fn render_target_cache() -> Self { + BatchTextures { + colors: [ + TextureSource::PrevPassColor, + TextureSource::PrevPassAlpha, + TextureSource::Invalid, + ], + } + } + + pub fn color(texture: TextureSource) -> Self { + BatchTextures { + colors: [texture, texture, TextureSource::Invalid], + } + } + + pub fn is_compatible_with(&self, other: &BatchTextures) -> bool { + self.colors.iter().zip(other.colors.iter()).all(|(t1, t2)| textures_compatible(*t1, *t2)) + } + + pub fn combine_textures(&self, other: BatchTextures) -> Option { + if !self.is_compatible_with(&other) { + return None; + } + + let mut new_textures = BatchTextures::no_texture(); + for (i, (color, other_color)) in self.colors.iter().zip(other.colors.iter()).enumerate() { + // If these textures are compatible, for each source either both sources are invalid or only one is not invalid. + new_textures.colors[i] = if *color == TextureSource::Invalid { + *other_color + } else { + *color + }; + } + Some(new_textures) + } +} + +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct BatchKey { + pub kind: BatchKind, + pub blend_mode: BlendMode, + pub textures: BatchTextures, +} + +impl BatchKey { + pub fn new(kind: BatchKind, blend_mode: BlendMode, textures: BatchTextures) -> Self { + BatchKey { + kind, + blend_mode, + textures, + } + } + + pub fn is_compatible_with(&self, other: &BatchKey) -> bool { + self.kind == other.kind && self.blend_mode == other.blend_mode && self.textures.is_compatible_with(&other.textures) + } +} + +#[inline] +fn textures_compatible(t1: TextureSource, t2: TextureSource) -> bool { + t1 == TextureSource::Invalid || t2 == TextureSource::Invalid || t1 == t2 +} + +pub struct AlphaBatchList { + pub batches: Vec, + pub item_rects: Vec>, + current_batch_index: usize, + current_z_id: ZBufferId, + break_advanced_blend_batches: bool, + lookback_count: usize, +} + +impl AlphaBatchList { + fn new(break_advanced_blend_batches: bool, lookback_count: usize) -> Self { + AlphaBatchList { + batches: Vec::new(), + item_rects: Vec::new(), + current_z_id: ZBufferId::invalid(), + current_batch_index: usize::MAX, + break_advanced_blend_batches, + lookback_count, + } + } + + /// Clear all current batches in this list. This is typically used + /// when a primitive is encountered that occludes all previous + /// content in this batch list. + fn clear(&mut self) { + self.current_batch_index = usize::MAX; + self.current_z_id = ZBufferId::invalid(); + self.batches.clear(); + self.item_rects.clear(); + } + + pub fn set_params_and_get_batch( + &mut self, + key: BatchKey, + features: BatchFeatures, + // The bounding box of everything at this Z plane. We expect potentially + // multiple primitive segments coming with the same `z_id`. + z_bounding_rect: &PictureRect, + z_id: ZBufferId, + ) -> &mut Vec { + if z_id != self.current_z_id || + self.current_batch_index == usize::MAX || + !self.batches[self.current_batch_index].key.is_compatible_with(&key) + { + let mut selected_batch_index = None; + + match key.blend_mode { + BlendMode::SubpixelWithBgColor => { + 'outer_multipass: for (batch_index, batch) in self.batches.iter().enumerate().rev().take(self.lookback_count) { + // Some subpixel batches are drawn in two passes. Because of this, we need + // to check for overlaps with every batch (which is a bit different + // than the normal batching below). + for item_rect in &self.item_rects[batch_index] { + if item_rect.intersects(z_bounding_rect) { + break 'outer_multipass; + } + } + + if batch.key.is_compatible_with(&key) { + selected_batch_index = Some(batch_index); + break; + } + } + } + BlendMode::Advanced(_) if self.break_advanced_blend_batches => { + // don't try to find a batch + } + _ => { + 'outer_default: for (batch_index, batch) in self.batches.iter().enumerate().rev().take(self.lookback_count) { + // For normal batches, we only need to check for overlaps for batches + // other than the first batch we consider. If the first batch + // is compatible, then we know there isn't any potential overlap + // issues to worry about. + if batch.key.is_compatible_with(&key) { + selected_batch_index = Some(batch_index); + break; + } + + // check for intersections + for item_rect in &self.item_rects[batch_index] { + if item_rect.intersects(z_bounding_rect) { + break 'outer_default; + } + } + } + } + } + + if selected_batch_index.is_none() { + let new_batch = PrimitiveBatch::new(key); + selected_batch_index = Some(self.batches.len()); + self.batches.push(new_batch); + self.item_rects.push(Vec::new()); + } + + self.current_batch_index = selected_batch_index.unwrap(); + self.item_rects[self.current_batch_index].push(*z_bounding_rect); + self.current_z_id = z_id; + } else if cfg!(debug_assertions) { + // If it's a different segment of the same (larger) primitive, we expect the bounding box + // to be the same - coming from the primitive itself, not the segment. + assert_eq!(self.item_rects[self.current_batch_index].last(), Some(z_bounding_rect)); + } + + let batch = &mut self.batches[self.current_batch_index]; + batch.features |= features; + + &mut batch.instances + } +} + +pub struct OpaqueBatchList { + pub pixel_area_threshold_for_new_batch: f32, + pub batches: Vec, + pub current_batch_index: usize, + lookback_count: usize, +} + +impl OpaqueBatchList { + fn new(pixel_area_threshold_for_new_batch: f32, lookback_count: usize) -> Self { + OpaqueBatchList { + batches: Vec::new(), + pixel_area_threshold_for_new_batch, + current_batch_index: usize::MAX, + lookback_count, + } + } + + /// Clear all current batches in this list. This is typically used + /// when a primitive is encountered that occludes all previous + /// content in this batch list. + fn clear(&mut self) { + self.current_batch_index = usize::MAX; + self.batches.clear(); + } + + pub fn set_params_and_get_batch( + &mut self, + key: BatchKey, + features: BatchFeatures, + // The bounding box of everything at the current Z, whatever it is. We expect potentially + // multiple primitive segments produced by a primitive, which we allow to check + // `current_batch_index` instead of iterating the batches. + z_bounding_rect: &PictureRect, + ) -> &mut Vec { + if self.current_batch_index == usize::MAX || + !self.batches[self.current_batch_index].key.is_compatible_with(&key) { + let mut selected_batch_index = None; + let item_area = z_bounding_rect.size.area(); + + // If the area of this primitive is larger than the given threshold, + // then it is large enough to warrant breaking a batch for. In this + // case we just see if it can be added to the existing batch or + // create a new one. + if item_area > self.pixel_area_threshold_for_new_batch { + if let Some(batch) = self.batches.last() { + if batch.key.is_compatible_with(&key) { + selected_batch_index = Some(self.batches.len() - 1); + } + } + } else { + // Otherwise, look back through a reasonable number of batches. + for (batch_index, batch) in self.batches.iter().enumerate().rev().take(self.lookback_count) { + if batch.key.is_compatible_with(&key) { + selected_batch_index = Some(batch_index); + break; + } + } + } + + if selected_batch_index.is_none() { + let new_batch = PrimitiveBatch::new(key); + selected_batch_index = Some(self.batches.len()); + self.batches.push(new_batch); + } + + self.current_batch_index = selected_batch_index.unwrap(); + } + + let batch = &mut self.batches[self.current_batch_index]; + batch.features |= features; + + &mut batch.instances + } + + fn finalize(&mut self) { + // Reverse the instance arrays in the opaque batches + // to get maximum z-buffer efficiency by drawing + // front-to-back. + // TODO(gw): Maybe we can change the batch code to + // build these in reverse and avoid having + // to reverse the instance array here. + for batch in &mut self.batches { + batch.instances.reverse(); + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct PrimitiveBatch { + pub key: BatchKey, + pub instances: Vec, + pub features: BatchFeatures, +} + +bitflags! { + /// Features of the batch that, if not requested, may allow a fast-path. + /// + /// Rather than breaking batches when primitives request different features, + /// we always request the minimum amount of features to satisfy all items in + /// the batch. + /// The goal is to let the renderer be optionally select more specialized + /// versions of a shader if the batch doesn't require code certain code paths. + /// Not all shaders necessarily implement all of these features. + #[cfg_attr(feature = "capture", derive(Serialize))] + #[cfg_attr(feature = "replay", derive(Deserialize))] + pub struct BatchFeatures: u8 { + const ALPHA_PASS = 1 << 0; + const ANTIALIASING = 1 << 1; + const REPETITION = 1 << 2; + } +} + +impl PrimitiveBatch { + fn new(key: BatchKey) -> PrimitiveBatch { + PrimitiveBatch { + key, + instances: Vec::new(), + features: BatchFeatures::empty(), + } + } + + fn merge(&mut self, other: PrimitiveBatch) { + self.instances.extend(other.instances); + self.features |= other.features; + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct AlphaBatchContainer { + pub opaque_batches: Vec, + pub alpha_batches: Vec, + /// The overall scissor rect for this render task, if one + /// is required. + pub task_scissor_rect: Option, + /// The rectangle of the owning render target that this + /// set of batches affects. + pub task_rect: DeviceIntRect, +} + +impl AlphaBatchContainer { + pub fn new( + task_scissor_rect: Option, + ) -> AlphaBatchContainer { + AlphaBatchContainer { + opaque_batches: Vec::new(), + alpha_batches: Vec::new(), + task_scissor_rect, + task_rect: DeviceIntRect::zero(), + } + } + + pub fn is_empty(&self) -> bool { + self.opaque_batches.is_empty() && + self.alpha_batches.is_empty() + } + + fn merge(&mut self, builder: AlphaBatchBuilder, task_rect: &DeviceIntRect) { + self.task_rect = self.task_rect.union(task_rect); + + for other_batch in builder.opaque_batch_list.batches { + let batch_index = self.opaque_batches.iter().position(|batch| { + batch.key.is_compatible_with(&other_batch.key) + }); + + match batch_index { + Some(batch_index) => { + self.opaque_batches[batch_index].merge(other_batch); + } + None => { + self.opaque_batches.push(other_batch); + } + } + } + + let mut min_batch_index = 0; + + for other_batch in builder.alpha_batch_list.batches { + let batch_index = self.alpha_batches.iter().skip(min_batch_index).position(|batch| { + batch.key.is_compatible_with(&other_batch.key) + }); + + match batch_index { + Some(batch_index) => { + let index = batch_index + min_batch_index; + self.alpha_batches[index].merge(other_batch); + min_batch_index = index; + } + None => { + self.alpha_batches.push(other_batch); + min_batch_index = self.alpha_batches.len(); + } + } + } + } +} + +/// Each segment can optionally specify a per-segment +/// texture set and one user data field. +#[derive(Debug, Copy, Clone)] +struct SegmentInstanceData { + textures: BatchTextures, + specific_resource_address: i32, +} + +/// Encapsulates the logic of building batches for items that are blended. +pub struct AlphaBatchBuilder { + pub alpha_batch_list: AlphaBatchList, + pub opaque_batch_list: OpaqueBatchList, + pub render_task_id: RenderTaskId, + render_task_address: RenderTaskAddress, + pub vis_mask: PrimitiveVisibilityMask, +} + +impl AlphaBatchBuilder { + pub fn new( + screen_size: DeviceIntSize, + break_advanced_blend_batches: bool, + lookback_count: usize, + render_task_id: RenderTaskId, + render_task_address: RenderTaskAddress, + vis_mask: PrimitiveVisibilityMask, + ) -> Self { + // The threshold for creating a new batch is + // one quarter the screen size. + let batch_area_threshold = (screen_size.width * screen_size.height) as f32 / 4.0; + + AlphaBatchBuilder { + alpha_batch_list: AlphaBatchList::new(break_advanced_blend_batches, lookback_count), + opaque_batch_list: OpaqueBatchList::new(batch_area_threshold, lookback_count), + render_task_id, + render_task_address, + vis_mask, + } + } + + /// Clear all current batches in this builder. This is typically used + /// when a primitive is encountered that occludes all previous + /// content in this batch list. + fn clear(&mut self) { + self.alpha_batch_list.clear(); + self.opaque_batch_list.clear(); + } + + pub fn build( + mut self, + batch_containers: &mut Vec, + merged_batches: &mut AlphaBatchContainer, + task_rect: DeviceIntRect, + task_scissor_rect: Option, + ) { + self.opaque_batch_list.finalize(); + + if task_scissor_rect.is_none() { + merged_batches.merge(self, &task_rect); + } else { + batch_containers.push(AlphaBatchContainer { + alpha_batches: self.alpha_batch_list.batches, + opaque_batches: self.opaque_batch_list.batches, + task_scissor_rect, + task_rect, + }); + } + } + + pub fn push_single_instance( + &mut self, + key: BatchKey, + features: BatchFeatures, + bounding_rect: &PictureRect, + z_id: ZBufferId, + instance: PrimitiveInstanceData, + ) { + self.set_params_and_get_batch(key, features, bounding_rect, z_id) + .push(instance); + } + + pub fn set_params_and_get_batch( + &mut self, + key: BatchKey, + features: BatchFeatures, + bounding_rect: &PictureRect, + z_id: ZBufferId, + ) -> &mut Vec { + match key.blend_mode { + BlendMode::None => { + self.opaque_batch_list + .set_params_and_get_batch(key, features, bounding_rect) + } + BlendMode::Alpha | + BlendMode::PremultipliedAlpha | + BlendMode::PremultipliedDestOut | + BlendMode::SubpixelConstantTextColor(..) | + BlendMode::SubpixelWithBgColor | + BlendMode::SubpixelDualSource | + BlendMode::Advanced(_) => { + self.alpha_batch_list + .set_params_and_get_batch(key, features, bounding_rect, z_id) + } + } + } +} + +/// Supports (recursively) adding a list of primitives and pictures to an alpha batch +/// builder. In future, it will support multiple dirty regions / slices, allowing the +/// contents of a picture to be spliced into multiple batch builders. +pub struct BatchBuilder { + /// A temporary buffer that is used during glyph fetching, stored here + /// to reduce memory allocations. + glyph_fetch_buffer: Vec, + + pub batchers: Vec, +} + +impl BatchBuilder { + pub fn new(batchers: Vec) -> Self { + BatchBuilder { + glyph_fetch_buffer: Vec::new(), + batchers, + } + } + + pub fn finalize(self) -> Vec { + self.batchers + } + + fn add_brush_instance_to_batches( + &mut self, + batch_key: BatchKey, + features: BatchFeatures, + bounding_rect: &PictureRect, + z_id: ZBufferId, + segment_index: i32, + edge_flags: EdgeAaSegmentMask, + clip_task_address: RenderTaskAddress, + brush_flags: BrushFlags, + prim_header_index: PrimitiveHeaderIndex, + resource_address: i32, + prim_vis_mask: PrimitiveVisibilityMask, + ) { + for batcher in &mut self.batchers { + if batcher.vis_mask.intersects(prim_vis_mask) { + let render_task_address = batcher.render_task_address; + + let instance = BrushInstance { + segment_index, + edge_flags, + clip_task_address, + render_task_address, + brush_flags, + prim_header_index, + resource_address, + brush_kind: batch_key.kind.shader_kind(), + }; + + batcher.push_single_instance( + batch_key, + features, + bounding_rect, + z_id, + PrimitiveInstanceData::from(instance), + ); + } + } + } + + fn add_split_composite_instance_to_batches( + &mut self, + batch_key: BatchKey, + bounding_rect: &PictureRect, + z_id: ZBufferId, + prim_header_index: PrimitiveHeaderIndex, + polygons_address: GpuCacheAddress, + prim_vis_mask: PrimitiveVisibilityMask, + ) { + for batcher in &mut self.batchers { + if batcher.vis_mask.intersects(prim_vis_mask) { + let render_task_address = batcher.render_task_address; + + batcher.push_single_instance( + batch_key, + BatchFeatures::empty(), + bounding_rect, + z_id, + PrimitiveInstanceData::from(SplitCompositeInstance { + prim_header_index, + render_task_address, + polygons_address, + z: z_id, + }), + ); + } + } + } + + /// Clear all current batchers. This is typically used when a primitive + /// is encountered that occludes all previous content in this batch list. + fn clear_batches(&mut self) { + for batcher in &mut self.batchers { + batcher.clear(); + } + } + + /// Add a picture to a given batch builder. + pub fn add_pic_to_batch( + &mut self, + pic: &PicturePrimitive, + ctx: &RenderTargetContext, + gpu_cache: &mut GpuCache, + render_tasks: &RenderTaskGraph, + deferred_resolves: &mut Vec, + prim_headers: &mut PrimitiveHeaders, + transforms: &mut TransformPalette, + root_spatial_node_index: SpatialNodeIndex, + surface_spatial_node_index: SpatialNodeIndex, + z_generator: &mut ZBufferIdGenerator, + composite_state: &mut CompositeState, + ) { + for cluster in &pic.prim_list.clusters { + profile_scope!("cluster"); + // Add each run in this picture to the batch. + for prim_instance in &cluster.prim_instances { + self.add_prim_to_batch( + prim_instance, + cluster.spatial_node_index, + ctx, + gpu_cache, + render_tasks, + deferred_resolves, + prim_headers, + transforms, + root_spatial_node_index, + surface_spatial_node_index, + z_generator, + composite_state, + ); + } + } + } + + // If an image is being drawn as a compositor surface, we don't want + // to draw the surface itself into the tile. Instead, we draw a transparent + // rectangle that writes to the z-buffer where this compositor surface is. + // That ensures we 'cut out' the part of the tile that has the compositor + // surface on it, allowing us to draw this tile as an overlay on top of + // the compositor surface. + // TODO(gw): There's a slight performance cost to doing this cutout rectangle + // if we end up not needing to use overlay mode. Consider skipping + // the cutout completely in this path. + fn emit_placeholder( + &mut self, + prim_rect: LayoutRect, + prim_info: &PrimitiveVisibility, + z_id: ZBufferId, + transform_id: TransformPaletteId, + batch_features: BatchFeatures, + ctx: &RenderTargetContext, + gpu_cache: &mut GpuCache, + render_tasks: &RenderTaskGraph, + prim_headers: &mut PrimitiveHeaders, + ) { + let batch_params = BrushBatchParameters::shared( + BrushBatchKind::Solid, + BatchTextures::no_texture(), + [get_shader_opacity(0.0), 0, 0, 0], + 0, + ); + + let prim_cache_address = gpu_cache.get_address( + &ctx.globals.default_transparent_rect_handle, + ); + + let prim_header = PrimitiveHeader { + local_rect: prim_rect, + local_clip_rect: prim_info.combined_local_clip_rect, + specific_prim_address: prim_cache_address, + transform_id, + }; + + let prim_header_index = prim_headers.push( + &prim_header, + z_id, + batch_params.prim_user_data, + ); + + let bounding_rect = &prim_info.clip_chain.pic_clip_rect; + let transform_kind = transform_id.transform_kind(); + let prim_vis_mask = prim_info.visibility_mask; + + self.add_segmented_prim_to_batch( + None, + PrimitiveOpacity::translucent(), + &batch_params, + BlendMode::None, + BlendMode::None, + batch_features, + prim_header_index, + bounding_rect, + transform_kind, + render_tasks, + z_id, + prim_info.clip_task_index, + prim_vis_mask, + ctx, + ); + } + + // Adds a primitive to a batch. + // It can recursively call itself in some situations, for + // example if it encounters a picture where the items + // in that picture are being drawn into the same target. + fn add_prim_to_batch( + &mut self, + prim_instance: &PrimitiveInstance, + prim_spatial_node_index: SpatialNodeIndex, + ctx: &RenderTargetContext, + gpu_cache: &mut GpuCache, + render_tasks: &RenderTaskGraph, + deferred_resolves: &mut Vec, + prim_headers: &mut PrimitiveHeaders, + transforms: &mut TransformPalette, + root_spatial_node_index: SpatialNodeIndex, + surface_spatial_node_index: SpatialNodeIndex, + z_generator: &mut ZBufferIdGenerator, + composite_state: &mut CompositeState, + ) { + if prim_instance.visibility_info == PrimitiveVisibilityIndex::INVALID { + return; + } + + #[cfg(debug_assertions)] //TODO: why is this needed? + debug_assert_eq!(prim_instance.prepared_frame_id, render_tasks.frame_id()); + + let is_chased = prim_instance.is_chased(); + + let transform_id = transforms + .get_id( + prim_spatial_node_index, + root_spatial_node_index, + ctx.spatial_tree, + ); + + // TODO(gw): Calculating this for every primitive is a bit + // wasteful. We should probably cache this in + // the scroll node... + let transform_kind = transform_id.transform_kind(); + let prim_info = &ctx.scratch.prim_info[prim_instance.visibility_info.0 as usize]; + let bounding_rect = &prim_info.clip_chain.pic_clip_rect; + + // If this primitive is a backdrop, that means that it is known to cover + // the entire picture cache background. In that case, the renderer will + // use the backdrop color as a clear color, and so we can drop this + // primitive and any prior primitives from the batch lists for this + // picture cache slice. + if prim_info.flags.contains(PrimitiveVisibilityFlags::IS_BACKDROP) { + self.clear_batches(); + return; + } + + let z_id = z_generator.next(); + + let prim_rect = ctx.data_stores.get_local_prim_rect( + prim_instance, + ctx.prim_store, + ); + + let mut batch_features = BatchFeatures::empty(); + if ctx.data_stores.prim_may_need_repetition(prim_instance) { + batch_features |= BatchFeatures::REPETITION; + } + + if transform_kind != TransformedRectKind::AxisAligned { + batch_features |= BatchFeatures::ANTIALIASING; + } + + let prim_vis_mask = prim_info.visibility_mask; + let clip_task_address = ctx.get_prim_clip_task_address( + prim_info.clip_task_index, + render_tasks, + ); + + if is_chased { + println!("\tbatch {:?} with bound {:?} and clip task {:?}", prim_rect, bounding_rect, clip_task_address); + } + + if !bounding_rect.is_empty() { + debug_assert_eq!(prim_info.clip_chain.pic_spatial_node_index, surface_spatial_node_index, + "The primitive's bounding box is specified in a different coordinate system from the current batch!"); + } + + match prim_instance.kind { + PrimitiveInstanceKind::Clear { data_handle } => { + let prim_data = &ctx.data_stores.prim[data_handle]; + let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle); + + // TODO(gw): We can abstract some of the common code below into + // helper methods, as we port more primitives to make + // use of interning. + + let prim_header = PrimitiveHeader { + local_rect: prim_rect, + local_clip_rect: prim_info.combined_local_clip_rect, + specific_prim_address: prim_cache_address, + transform_id, + }; + + let prim_header_index = prim_headers.push( + &prim_header, + z_id, + [get_shader_opacity(1.0), 0, 0, 0], + ); + + let batch_key = BatchKey { + blend_mode: BlendMode::PremultipliedDestOut, + kind: BatchKind::Brush(BrushBatchKind::Solid), + textures: BatchTextures::no_texture(), + }; + + self.add_brush_instance_to_batches( + batch_key, + batch_features, + bounding_rect, + z_id, + INVALID_SEGMENT_INDEX, + EdgeAaSegmentMask::all(), + clip_task_address.unwrap(), + BrushFlags::PERSPECTIVE_INTERPOLATION, + prim_header_index, + 0, + prim_vis_mask, + ); + } + PrimitiveInstanceKind::NormalBorder { data_handle, ref cache_handles, .. } => { + let prim_data = &ctx.data_stores.normal_border[data_handle]; + let common_data = &prim_data.common; + let prim_cache_address = gpu_cache.get_address(&common_data.gpu_cache_handle); + let cache_handles = &ctx.scratch.border_cache_handles[*cache_handles]; + let specified_blend_mode = BlendMode::PremultipliedAlpha; + let mut segment_data: SmallVec<[SegmentInstanceData; 8]> = SmallVec::new(); + + // Collect the segment instance data from each render + // task for each valid edge / corner of the border. + + for handle in cache_handles { + let rt_cache_entry = ctx.resource_cache + .get_cached_render_task(handle); + let cache_item = ctx.resource_cache + .get_texture_cache_item(&rt_cache_entry.handle); + segment_data.push( + SegmentInstanceData { + textures: BatchTextures::color(cache_item.texture_id), + specific_resource_address: cache_item.uv_rect_handle.as_int(gpu_cache), + } + ); + } + + let non_segmented_blend_mode = if !common_data.opacity.is_opaque || + prim_info.clip_task_index != ClipTaskIndex::INVALID || + transform_kind == TransformedRectKind::Complex + { + specified_blend_mode + } else { + BlendMode::None + }; + + let prim_header = PrimitiveHeader { + local_rect: prim_rect, + local_clip_rect: prim_info.combined_local_clip_rect, + specific_prim_address: prim_cache_address, + transform_id, + }; + + let batch_params = BrushBatchParameters::instanced( + BrushBatchKind::Image(ImageBufferKind::Texture2DArray), + ImageBrushData { + color_mode: ShaderColorMode::Image, + alpha_type: AlphaType::PremultipliedAlpha, + raster_space: RasterizationSpace::Local, + opacity: 1.0, + }.encode(), + segment_data, + ); + + let prim_header_index = prim_headers.push( + &prim_header, + z_id, + batch_params.prim_user_data, + ); + + let border_data = &prim_data.kind; + self.add_segmented_prim_to_batch( + Some(border_data.brush_segments.as_slice()), + common_data.opacity, + &batch_params, + specified_blend_mode, + non_segmented_blend_mode, + batch_features, + prim_header_index, + bounding_rect, + transform_kind, + render_tasks, + z_id, + prim_info.clip_task_index, + prim_vis_mask, + ctx, + ); + } + PrimitiveInstanceKind::TextRun { data_handle, run_index, .. } => { + let run = &ctx.prim_store.text_runs[run_index]; + let subpx_dir = run.used_font.get_subpx_dir(); + + // The GPU cache data is stored in the template and reused across + // frames and display lists. + let prim_data = &ctx.data_stores.text_run[data_handle]; + let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle); + + // The local prim rect is only informative for text primitives, as + // thus is not directly necessary for any drawing of the text run. + // However the glyph offsets are relative to the prim rect origin + // less the unsnapped reference frame offset. We also want the + // the snapped reference frame offset, because cannot recalculate + // it as it ignores the animated components for the transform. As + // such, we adjust the prim rect origin here, and replace the size + // with the unsnapped and snapped offsets respectively. This has + // the added bonus of avoiding quantization effects when storing + // floats in the extra header integers. + let prim_header = PrimitiveHeader { + local_rect: LayoutRect::new( + prim_rect.origin - run.reference_frame_relative_offset, + run.snapped_reference_frame_relative_offset.to_size(), + ), + local_clip_rect: prim_info.combined_local_clip_rect, + specific_prim_address: prim_cache_address, + transform_id, + }; + + let glyph_keys = &ctx.scratch.glyph_keys[run.glyph_keys_range]; + let raster_scale = run.raster_space.local_scale().unwrap_or(1.0).max(0.001); + let prim_header_index = prim_headers.push( + &prim_header, + z_id, + [ + (raster_scale * 65535.0).round() as i32, + 0, + 0, + 0, + ], + ); + let base_instance = GlyphInstance::new( + prim_header_index, + ); + let batchers = &mut self.batchers; + + ctx.resource_cache.fetch_glyphs( + run.used_font.clone(), + &glyph_keys, + &mut self.glyph_fetch_buffer, + gpu_cache, + |texture_id, mut glyph_format, glyphs| { + debug_assert_ne!(texture_id, TextureSource::Invalid); + + // Ignore color and only sample alpha when shadowing. + if run.shadow { + glyph_format = glyph_format.ignore_color(); + } + + let subpx_dir = subpx_dir.limit_by(glyph_format); + + let textures = BatchTextures { + colors: [ + texture_id, + TextureSource::Invalid, + TextureSource::Invalid, + ], + }; + + let kind = BatchKind::TextRun(glyph_format); + + let (blend_mode, color_mode) = match glyph_format { + GlyphFormat::Subpixel | + GlyphFormat::TransformedSubpixel => { + if run.used_font.bg_color.a != 0 { + ( + BlendMode::SubpixelWithBgColor, + ShaderColorMode::FromRenderPassMode, + ) + } else if ctx.use_dual_source_blending { + ( + BlendMode::SubpixelDualSource, + ShaderColorMode::SubpixelDualSource, + ) + } else { + ( + BlendMode::SubpixelConstantTextColor(run.used_font.color.into()), + ShaderColorMode::SubpixelConstantTextColor, + ) + } + } + GlyphFormat::Alpha | + GlyphFormat::TransformedAlpha => { + ( + BlendMode::PremultipliedAlpha, + ShaderColorMode::Alpha, + ) + } + GlyphFormat::Bitmap => { + ( + BlendMode::PremultipliedAlpha, + ShaderColorMode::Bitmap, + ) + } + GlyphFormat::ColorBitmap => { + ( + BlendMode::PremultipliedAlpha, + ShaderColorMode::ColorBitmap, + ) + } + }; + + let key = BatchKey::new(kind, blend_mode, textures); + + for batcher in batchers.iter_mut() { + if batcher.vis_mask.intersects(prim_vis_mask) { + let render_task_address = batcher.render_task_address; + let batch = batcher.alpha_batch_list.set_params_and_get_batch( + key, + BatchFeatures::empty(), + bounding_rect, + z_id, + ); + + for glyph in glyphs { + batch.push(base_instance.build( + render_task_address, + clip_task_address.unwrap(), + subpx_dir, + glyph.index_in_text_run, + glyph.uv_rect_address, + color_mode, + )); + } + } + } + }, + ); + } + PrimitiveInstanceKind::LineDecoration { data_handle, ref cache_handle, .. } => { + // The GPU cache data is stored in the template and reused across + // frames and display lists. + let common_data = &ctx.data_stores.line_decoration[data_handle].common; + let prim_cache_address = gpu_cache.get_address(&common_data.gpu_cache_handle); + + let (batch_kind, textures, prim_user_data, specific_resource_address) = match cache_handle { + Some(cache_handle) => { + let rt_cache_entry = ctx + .resource_cache + .get_cached_render_task(cache_handle); + let cache_item = ctx + .resource_cache + .get_texture_cache_item(&rt_cache_entry.handle); + let textures = BatchTextures::color(cache_item.texture_id); + ( + BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)), + textures, + ImageBrushData { + color_mode: ShaderColorMode::Image, + alpha_type: AlphaType::PremultipliedAlpha, + raster_space: RasterizationSpace::Local, + opacity: 1.0, + }.encode(), + cache_item.uv_rect_handle.as_int(gpu_cache), + ) + } + None => { + ( + BrushBatchKind::Solid, + BatchTextures::no_texture(), + [get_shader_opacity(1.0), 0, 0, 0], + 0, + ) + } + }; + + // TODO(gw): We can abstract some of the common code below into + // helper methods, as we port more primitives to make + // use of interning. + let blend_mode = if !common_data.opacity.is_opaque || + prim_info.clip_task_index != ClipTaskIndex::INVALID || + transform_kind == TransformedRectKind::Complex + { + BlendMode::PremultipliedAlpha + } else { + BlendMode::None + }; + + let prim_header = PrimitiveHeader { + local_rect: prim_rect, + local_clip_rect: prim_info.combined_local_clip_rect, + specific_prim_address: prim_cache_address, + transform_id, + }; + + let prim_header_index = prim_headers.push( + &prim_header, + z_id, + prim_user_data, + ); + + let batch_key = BatchKey { + blend_mode, + kind: BatchKind::Brush(batch_kind), + textures, + }; + + self.add_brush_instance_to_batches( + batch_key, + batch_features, + bounding_rect, + z_id, + INVALID_SEGMENT_INDEX, + EdgeAaSegmentMask::all(), + clip_task_address.unwrap(), + BrushFlags::PERSPECTIVE_INTERPOLATION, + prim_header_index, + specific_resource_address, + prim_vis_mask, + ); + } + PrimitiveInstanceKind::Picture { pic_index, segment_instance_index, .. } => { + let picture = &ctx.prim_store.pictures[pic_index.0]; + let non_segmented_blend_mode = BlendMode::PremultipliedAlpha; + let prim_cache_address = gpu_cache.get_address(&ctx.globals.default_image_handle); + + let prim_header = PrimitiveHeader { + local_rect: picture.precise_local_rect, + local_clip_rect: prim_info.combined_local_clip_rect, + specific_prim_address: prim_cache_address, + transform_id, + }; + + match picture.context_3d { + // Convert all children of the 3D hierarchy root into batches. + Picture3DContext::In { root_data: Some(ref list), .. } => { + for child in list { + let cluster = &picture.prim_list.clusters[child.anchor.cluster_index]; + let child_prim_instance = &cluster.prim_instances[child.anchor.instance_index]; + let child_prim_info = &ctx.scratch.prim_info[child_prim_instance.visibility_info.0 as usize]; + + let child_pic_index = match child_prim_instance.kind { + PrimitiveInstanceKind::Picture { pic_index, .. } => pic_index, + _ => unreachable!(), + }; + let pic = &ctx.prim_store.pictures[child_pic_index.0]; + + // Get clip task, if set, for the picture primitive. + let child_clip_task_address = ctx.get_prim_clip_task_address( + child_prim_info.clip_task_index, + render_tasks, + ); + + let prim_header = PrimitiveHeader { + local_rect: pic.precise_local_rect, + local_clip_rect: child_prim_info.combined_local_clip_rect, + specific_prim_address: GpuCacheAddress::INVALID, + transform_id: transforms + .get_id( + child.spatial_node_index, + root_spatial_node_index, + ctx.spatial_tree, + ), + }; + + let raster_config = pic + .raster_config + .as_ref() + .expect("BUG: 3d primitive was not assigned a surface"); + let (uv_rect_address, _) = render_tasks.resolve_surface( + ctx.surfaces[raster_config.surface_index.0] + .render_tasks + .expect("BUG: no surface") + .root, + gpu_cache, + ); + + // Need a new z-id for each child preserve-3d context added + // by this inner loop. + let z_id = z_generator.next(); + + let prim_header_index = prim_headers.push(&prim_header, z_id, [ + uv_rect_address.as_int(), + if raster_config.establishes_raster_root { 1 } else { 0 }, + 0, + child_clip_task_address.unwrap().0 as i32, + ]); + + let key = BatchKey::new( + BatchKind::SplitComposite, + BlendMode::PremultipliedAlpha, + BatchTextures::no_texture(), + ); + + self.add_split_composite_instance_to_batches( + key, + &child_prim_info.clip_chain.pic_clip_rect, + z_id, + prim_header_index, + child.gpu_address, + child_prim_info.visibility_mask, + ); + } + } + // Ignore the 3D pictures that are not in the root of preserve-3D + // hierarchy, since we process them with the root. + Picture3DContext::In { root_data: None, .. } => return, + // Proceed for non-3D pictures. + Picture3DContext::Out => () + } + + match picture.raster_config { + Some(ref raster_config) => { + // If the child picture was rendered in local space, we can safely + // interpolate the UV coordinates with perspective correction. + let brush_flags = if raster_config.establishes_raster_root { + BrushFlags::PERSPECTIVE_INTERPOLATION + } else { + BrushFlags::empty() + }; + + let surface = &ctx.surfaces[raster_config.surface_index.0]; + let surface_task = surface.render_tasks.map(|s| s.root); + + match raster_config.composite_mode { + PictureCompositeMode::TileCache { .. } => { + // Tile cache instances are added to the composite config, rather than + // directly added to batches. This allows them to be drawn with various + // present modes during render, such as partial present etc. + let tile_cache = picture.tile_cache.as_ref().unwrap(); + let map_local_to_world = SpaceMapper::new_with_target( + ROOT_SPATIAL_NODE_INDEX, + tile_cache.spatial_node_index, + ctx.screen_world_rect, + ctx.spatial_tree, + ); + // TODO(gw): As a follow up to the valid_rect work, see why we use + // prim_info.combined_local_clip_rect here instead of the + // local_clip_rect built in the TileCacheInstance. Perhaps + // these can be unified or are different for a good reason? + let world_clip_rect = map_local_to_world + .map(&prim_info.combined_local_clip_rect) + .expect("bug: unable to map clip rect"); + let device_clip_rect = (world_clip_rect * ctx.global_device_pixel_scale).round(); + + composite_state.push_surface( + tile_cache, + device_clip_rect, + ctx.global_device_pixel_scale, + ctx.resource_cache, + gpu_cache, + deferred_resolves, + ); + } + PictureCompositeMode::Filter(ref filter) => { + assert!(filter.is_visible()); + match filter { + Filter::Blur(..) => { + let kind = BatchKind::Brush( + BrushBatchKind::Image(ImageBufferKind::Texture2DArray) + ); + let (uv_rect_address, textures) = render_tasks.resolve_surface( + surface_task.expect("bug: surface must be allocated by now"), + gpu_cache, + ); + let key = BatchKey::new( + kind, + non_segmented_blend_mode, + textures, + ); + let prim_header_index = prim_headers.push( + &prim_header, + z_id, + ImageBrushData { + color_mode: ShaderColorMode::Image, + alpha_type: AlphaType::PremultipliedAlpha, + raster_space: RasterizationSpace::Screen, + opacity: 1.0, + }.encode(), + ); + + self.add_brush_instance_to_batches( + key, + batch_features, + bounding_rect, + z_id, + INVALID_SEGMENT_INDEX, + EdgeAaSegmentMask::empty(), + clip_task_address.unwrap(), + brush_flags, + prim_header_index, + uv_rect_address.as_int(), + prim_vis_mask, + ); + } + Filter::DropShadows(shadows) => { + // Draw an instance per shadow first, following by the content. + + // The shadows and the content get drawn as a brush image. + let kind = BatchKind::Brush( + BrushBatchKind::Image(ImageBufferKind::Texture2DArray), + ); + + // Gets the saved render task ID of the content, which is + // deeper in the render task graph than the direct child. + let secondary_id = picture.secondary_render_task_id.expect("no secondary!?"); + let content_source = { + let secondary_task = &render_tasks[secondary_id]; + let saved_index = secondary_task.saved_index.expect("no saved index!?"); + debug_assert_ne!(saved_index, SavedTargetIndex::PENDING); + TextureSource::RenderTaskCache(saved_index, Swizzle::default()) + }; + + // Build BatchTextures for shadow/content + let shadow_textures = BatchTextures::render_target_cache(); + let content_textures = BatchTextures { + colors: [ + content_source, + TextureSource::Invalid, + TextureSource::Invalid, + ], + }; + + // Build batch keys for shadow/content + let shadow_key = BatchKey::new(kind, non_segmented_blend_mode, shadow_textures); + let content_key = BatchKey::new(kind, non_segmented_blend_mode, content_textures); + + // Retrieve the UV rect addresses for shadow/content. + let cache_task_id = surface_task + .expect("bug: surface must be allocated by now"); + let shadow_uv_rect_address = render_tasks[cache_task_id] + .get_texture_address(gpu_cache) + .as_int(); + let content_uv_rect_address = render_tasks[secondary_id] + .get_texture_address(gpu_cache) + .as_int(); + + for (shadow, shadow_gpu_data) in shadows.iter().zip(picture.extra_gpu_data_handles.iter()) { + // Get the GPU cache address of the extra data handle. + let shadow_prim_address = gpu_cache.get_address(shadow_gpu_data); + + let shadow_rect = prim_header.local_rect.translate(shadow.offset); + + let shadow_prim_header = PrimitiveHeader { + local_rect: shadow_rect, + specific_prim_address: shadow_prim_address, + ..prim_header + }; + + let shadow_prim_header_index = prim_headers.push( + &shadow_prim_header, + z_id, + ImageBrushData { + color_mode: ShaderColorMode::Alpha, + alpha_type: AlphaType::PremultipliedAlpha, + raster_space: RasterizationSpace::Screen, + opacity: 1.0, + }.encode(), + ); + + self.add_brush_instance_to_batches( + shadow_key, + batch_features, + bounding_rect, + z_id, + INVALID_SEGMENT_INDEX, + EdgeAaSegmentMask::empty(), + clip_task_address.unwrap(), + brush_flags, + shadow_prim_header_index, + shadow_uv_rect_address, + prim_vis_mask, + ); + } + let z_id_content = z_generator.next(); + + let content_prim_header_index = prim_headers.push( + &prim_header, + z_id_content, + ImageBrushData { + color_mode: ShaderColorMode::Image, + alpha_type: AlphaType::PremultipliedAlpha, + raster_space: RasterizationSpace::Screen, + opacity: 1.0, + }.encode(), + ); + + self.add_brush_instance_to_batches( + content_key, + batch_features, + bounding_rect, + z_id_content, + INVALID_SEGMENT_INDEX, + EdgeAaSegmentMask::empty(), + clip_task_address.unwrap(), + brush_flags, + content_prim_header_index, + content_uv_rect_address, + prim_vis_mask, + ); + } + Filter::Opacity(_, amount) => { + let amount = (amount * 65536.0) as i32; + + let (uv_rect_address, textures) = render_tasks.resolve_surface( + surface_task.expect("bug: surface must be allocated by now"), + gpu_cache, + ); + + let key = BatchKey::new( + BatchKind::Brush(BrushBatchKind::Opacity), + BlendMode::PremultipliedAlpha, + textures, + ); + + let prim_header_index = prim_headers.push(&prim_header, z_id, [ + uv_rect_address.as_int(), + amount, + 0, + 0, + ]); + + self.add_brush_instance_to_batches( + key, + batch_features, + bounding_rect, + z_id, + INVALID_SEGMENT_INDEX, + EdgeAaSegmentMask::empty(), + clip_task_address.unwrap(), + brush_flags, + prim_header_index, + 0, + prim_vis_mask, + ); + } + _ => { + // Must be kept in sync with brush_blend.glsl + let filter_mode = filter.as_int(); + + let user_data = match filter { + Filter::Identity => 0x10000i32, // matches `Contrast(1)` + Filter::Contrast(amount) | + Filter::Grayscale(amount) | + Filter::Invert(amount) | + Filter::Saturate(amount) | + Filter::Sepia(amount) | + Filter::Brightness(amount) => { + (amount * 65536.0) as i32 + } + Filter::SrgbToLinear | Filter::LinearToSrgb => 0, + Filter::HueRotate(angle) => { + (0.01745329251 * angle * 65536.0) as i32 + } + Filter::ColorMatrix(_) => { + picture.extra_gpu_data_handles[0].as_int(gpu_cache) + } + Filter::Flood(_) => { + picture.extra_gpu_data_handles[0].as_int(gpu_cache) + } + + // These filters are handled via different paths. + Filter::ComponentTransfer | + Filter::Blur(..) | + Filter::DropShadows(..) | + Filter::Opacity(..) => unreachable!(), + }; + + let (uv_rect_address, textures) = render_tasks.resolve_surface( + surface_task.expect("bug: surface must be allocated by now"), + gpu_cache, + ); + + let key = BatchKey::new( + BatchKind::Brush(BrushBatchKind::Blend), + BlendMode::PremultipliedAlpha, + textures, + ); + + let prim_header_index = prim_headers.push(&prim_header, z_id, [ + uv_rect_address.as_int(), + filter_mode, + user_data, + 0, + ]); + + self.add_brush_instance_to_batches( + key, + batch_features, + bounding_rect, + z_id, + INVALID_SEGMENT_INDEX, + EdgeAaSegmentMask::empty(), + clip_task_address.unwrap(), + brush_flags, + prim_header_index, + 0, + prim_vis_mask, + ); + } + } + } + PictureCompositeMode::ComponentTransferFilter(handle) => { + // This is basically the same as the general filter case above + // except we store a little more data in the filter mode and + // a gpu cache handle in the user data. + let filter_data = &ctx.data_stores.filter_data[handle]; + let filter_mode : i32 = Filter::ComponentTransfer.as_int() | + ((filter_data.data.r_func.to_int() << 28 | + filter_data.data.g_func.to_int() << 24 | + filter_data.data.b_func.to_int() << 20 | + filter_data.data.a_func.to_int() << 16) as i32); + + let user_data = filter_data.gpu_cache_handle.as_int(gpu_cache); + + let (uv_rect_address, textures) = render_tasks.resolve_surface( + surface_task.expect("bug: surface must be allocated by now"), + gpu_cache, + ); + + let key = BatchKey::new( + BatchKind::Brush(BrushBatchKind::Blend), + BlendMode::PremultipliedAlpha, + textures, + ); + + let prim_header_index = prim_headers.push(&prim_header, z_id, [ + uv_rect_address.as_int(), + filter_mode, + user_data, + 0, + ]); + + self.add_brush_instance_to_batches( + key, + batch_features, + bounding_rect, + z_id, + INVALID_SEGMENT_INDEX, + EdgeAaSegmentMask::empty(), + clip_task_address.unwrap(), + brush_flags, + prim_header_index, + 0, + prim_vis_mask, + ); + } + PictureCompositeMode::MixBlend(mode) if ctx.use_advanced_blending => { + let (uv_rect_address, textures) = render_tasks.resolve_surface( + surface_task.expect("bug: surface must be allocated by now"), + gpu_cache, + ); + let key = BatchKey::new( + BatchKind::Brush( + BrushBatchKind::Image(ImageBufferKind::Texture2DArray), + ), + BlendMode::Advanced(mode), + textures, + ); + let prim_header_index = prim_headers.push( + &prim_header, + z_id, + ImageBrushData { + color_mode: ShaderColorMode::Image, + alpha_type: AlphaType::PremultipliedAlpha, + raster_space: RasterizationSpace::Local, + opacity: 1.0, + }.encode(), + ); + + self.add_brush_instance_to_batches( + key, + batch_features, + bounding_rect, + z_id, + INVALID_SEGMENT_INDEX, + EdgeAaSegmentMask::empty(), + clip_task_address.unwrap(), + brush_flags, + prim_header_index, + uv_rect_address.as_int(), + prim_vis_mask, + ); + } + PictureCompositeMode::MixBlend(mode) => { + let cache_task_id = surface_task.expect("bug: surface must be allocated by now"); + let backdrop_id = picture.secondary_render_task_id.expect("no backdrop!?"); + + // TODO(gw): For now, mix-blend is not supported as a picture + // caching root, so we can safely assume there is + // only a single batcher present. + assert_eq!(self.batchers.len(), 1); + + let key = BatchKey::new( + BatchKind::Brush( + BrushBatchKind::MixBlend { + task_id: self.batchers[0].render_task_id, + source_id: cache_task_id, + backdrop_id, + }, + ), + BlendMode::PremultipliedAlpha, + BatchTextures::no_texture(), + ); + let backdrop_task_address = render_tasks.get_task_address(backdrop_id); + let source_task_address = render_tasks.get_task_address(cache_task_id); + let prim_header_index = prim_headers.push(&prim_header, z_id, [ + mode as u32 as i32, + backdrop_task_address.0 as i32, + source_task_address.0 as i32, + 0, + ]); + + self.add_brush_instance_to_batches( + key, + batch_features, + bounding_rect, + z_id, + INVALID_SEGMENT_INDEX, + EdgeAaSegmentMask::empty(), + clip_task_address.unwrap(), + brush_flags, + prim_header_index, + 0, + prim_vis_mask, + ); + } + PictureCompositeMode::Blit(_) => { + let cache_task_id = surface_task.expect("bug: surface must be allocated by now"); + let uv_rect_address = render_tasks[cache_task_id] + .get_texture_address(gpu_cache) + .as_int(); + let textures = match render_tasks[cache_task_id].saved_index { + Some(saved_index) => BatchTextures { + colors: [ + TextureSource::RenderTaskCache(saved_index, Swizzle::default()), + TextureSource::PrevPassAlpha, + TextureSource::Invalid, + ] + }, + None => BatchTextures::render_target_cache(), + }; + let batch_params = BrushBatchParameters::shared( + BrushBatchKind::Image(ImageBufferKind::Texture2DArray), + textures, + ImageBrushData { + color_mode: ShaderColorMode::Image, + alpha_type: AlphaType::PremultipliedAlpha, + raster_space: RasterizationSpace::Screen, + opacity: 1.0, + }.encode(), + uv_rect_address, + ); + + let is_segmented = + segment_instance_index != SegmentInstanceIndex::INVALID && + segment_instance_index != SegmentInstanceIndex::UNUSED; + + let (prim_cache_address, segments) = if is_segmented { + let segment_instance = &ctx.scratch.segment_instances[segment_instance_index]; + let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]); + (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments) + } else { + (prim_cache_address, None) + }; + + let prim_header = PrimitiveHeader { + local_rect: picture.precise_local_rect, + local_clip_rect: prim_info.combined_local_clip_rect, + specific_prim_address: prim_cache_address, + transform_id, + }; + + let prim_header_index = prim_headers.push( + &prim_header, + z_id, + batch_params.prim_user_data, + ); + + // TODO(gw): As before, all pictures that get blitted are assumed + // to have alpha. However, we could determine (at least for + // simple, common cases) if the picture content is opaque. + // That would allow inner segments of pictures to be drawn + // with blend disabled, which is a big performance win on + // integrated GPUs. + let opacity = PrimitiveOpacity::translucent(); + let specified_blend_mode = BlendMode::PremultipliedAlpha; + + self.add_segmented_prim_to_batch( + segments, + opacity, + &batch_params, + specified_blend_mode, + non_segmented_blend_mode, + batch_features, + prim_header_index, + bounding_rect, + transform_kind, + render_tasks, + z_id, + prim_info.clip_task_index, + prim_vis_mask, + ctx, + ); + } + PictureCompositeMode::SvgFilter(..) => { + let kind = BatchKind::Brush( + BrushBatchKind::Image(ImageBufferKind::Texture2DArray) + ); + let (uv_rect_address, textures) = render_tasks.resolve_surface( + surface_task.expect("bug: surface must be allocated by now"), + gpu_cache, + ); + let key = BatchKey::new( + kind, + non_segmented_blend_mode, + textures, + ); + let prim_header_index = prim_headers.push( + &prim_header, + z_id, + ImageBrushData { + color_mode: ShaderColorMode::Image, + alpha_type: AlphaType::PremultipliedAlpha, + raster_space: RasterizationSpace::Screen, + opacity: 1.0, + }.encode(), + ); + + self.add_brush_instance_to_batches( + key, + batch_features, + bounding_rect, + z_id, + INVALID_SEGMENT_INDEX, + EdgeAaSegmentMask::empty(), + clip_task_address.unwrap(), + brush_flags, + prim_header_index, + uv_rect_address.as_int(), + prim_vis_mask, + ); + } + } + } + None => { + // If this picture is being drawn into an existing target (i.e. with + // no composition operation), recurse and add to the current batch list. + self.add_pic_to_batch( + picture, + ctx, + gpu_cache, + render_tasks, + deferred_resolves, + prim_headers, + transforms, + root_spatial_node_index, + surface_spatial_node_index, + z_generator, + composite_state, + ); + } + } + } + PrimitiveInstanceKind::ImageBorder { data_handle, .. } => { + let prim_data = &ctx.data_stores.image_border[data_handle]; + let common_data = &prim_data.common; + let border_data = &prim_data.kind; + + let cache_item = resolve_image( + border_data.request, + ctx.resource_cache, + gpu_cache, + deferred_resolves, + ); + if cache_item.texture_id == TextureSource::Invalid { + return; + } + + let textures = BatchTextures::color(cache_item.texture_id); + let prim_cache_address = gpu_cache.get_address(&common_data.gpu_cache_handle); + let specified_blend_mode = BlendMode::PremultipliedAlpha; + let non_segmented_blend_mode = if !common_data.opacity.is_opaque || + prim_info.clip_task_index != ClipTaskIndex::INVALID || + transform_kind == TransformedRectKind::Complex + { + specified_blend_mode + } else { + BlendMode::None + }; + + let prim_header = PrimitiveHeader { + local_rect: prim_rect, + local_clip_rect: prim_info.combined_local_clip_rect, + specific_prim_address: prim_cache_address, + transform_id, + }; + + let batch_params = BrushBatchParameters::shared( + BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)), + textures, + ImageBrushData { + color_mode: ShaderColorMode::Image, + alpha_type: AlphaType::PremultipliedAlpha, + raster_space: RasterizationSpace::Local, + opacity: 1.0, + }.encode(), + cache_item.uv_rect_handle.as_int(gpu_cache), + ); + + let prim_header_index = prim_headers.push( + &prim_header, + z_id, + batch_params.prim_user_data, + ); + + self.add_segmented_prim_to_batch( + Some(border_data.brush_segments.as_slice()), + common_data.opacity, + &batch_params, + specified_blend_mode, + non_segmented_blend_mode, + batch_features, + prim_header_index, + bounding_rect, + transform_kind, + render_tasks, + z_id, + prim_info.clip_task_index, + prim_vis_mask, + ctx, + ); + } + PrimitiveInstanceKind::Rectangle { data_handle, segment_instance_index, opacity_binding_index, .. } => { + let prim_data = &ctx.data_stores.prim[data_handle]; + let specified_blend_mode = BlendMode::PremultipliedAlpha; + let opacity_binding = ctx.prim_store.get_opacity_binding(opacity_binding_index); + + let opacity = PrimitiveOpacity::from_alpha(opacity_binding); + let opacity = opacity.combine(prim_data.opacity); + + let non_segmented_blend_mode = if !opacity.is_opaque || + prim_info.clip_task_index != ClipTaskIndex::INVALID || + transform_kind == TransformedRectKind::Complex + { + specified_blend_mode + } else { + BlendMode::None + }; + + let batch_params = BrushBatchParameters::shared( + BrushBatchKind::Solid, + BatchTextures::no_texture(), + [get_shader_opacity(opacity_binding), 0, 0, 0], + 0, + ); + + let (prim_cache_address, segments) = if segment_instance_index == SegmentInstanceIndex::UNUSED { + (gpu_cache.get_address(&prim_data.gpu_cache_handle), None) + } else { + let segment_instance = &ctx.scratch.segment_instances[segment_instance_index]; + let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]); + (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments) + }; + + let prim_header = PrimitiveHeader { + local_rect: prim_rect, + local_clip_rect: prim_info.combined_local_clip_rect, + specific_prim_address: prim_cache_address, + transform_id, + }; + + let prim_header_index = prim_headers.push( + &prim_header, + z_id, + batch_params.prim_user_data, + ); + + self.add_segmented_prim_to_batch( + segments, + opacity, + &batch_params, + specified_blend_mode, + non_segmented_blend_mode, + batch_features, + prim_header_index, + bounding_rect, + transform_kind, + render_tasks, + z_id, + prim_info.clip_task_index, + prim_vis_mask, + ctx, + ); + } + PrimitiveInstanceKind::YuvImage { data_handle, segment_instance_index, is_compositor_surface, .. } => { + if is_compositor_surface { + self.emit_placeholder(prim_rect, + prim_info, + z_id, + transform_id, + batch_features, + ctx, + gpu_cache, + render_tasks, + prim_headers); + return; + } + + let yuv_image_data = &ctx.data_stores.yuv_image[data_handle].kind; + let mut textures = BatchTextures::no_texture(); + let mut uv_rect_addresses = [0; 3]; + + //yuv channel + let channel_count = yuv_image_data.format.get_plane_num(); + debug_assert!(channel_count <= 3); + for channel in 0 .. channel_count { + let image_key = yuv_image_data.yuv_key[channel]; + + let cache_item = resolve_image( + ImageRequest { + key: image_key, + rendering: yuv_image_data.image_rendering, + tile: None, + }, + ctx.resource_cache, + gpu_cache, + deferred_resolves, + ); + + if cache_item.texture_id == TextureSource::Invalid { + warn!("Warnings: skip a PrimitiveKind::YuvImage"); + return; + } + + textures.colors[channel] = cache_item.texture_id; + uv_rect_addresses[channel] = cache_item.uv_rect_handle.as_int(gpu_cache); + } + + // All yuv textures should be the same type. + let buffer_kind = get_buffer_kind(textures.colors[0]); + assert!( + textures.colors[1 .. yuv_image_data.format.get_plane_num()] + .iter() + .all(|&tid| buffer_kind == get_buffer_kind(tid)) + ); + + let kind = BrushBatchKind::YuvImage( + buffer_kind, + yuv_image_data.format, + yuv_image_data.color_depth, + yuv_image_data.color_space, + yuv_image_data.color_range, + ); + + let batch_params = BrushBatchParameters::shared( + kind, + textures, + [ + uv_rect_addresses[0], + uv_rect_addresses[1], + uv_rect_addresses[2], + 0, + ], + 0, + ); + + let specified_blend_mode = BlendMode::PremultipliedAlpha; + let prim_common_data = &ctx.data_stores.as_common_data(&prim_instance); + + let non_segmented_blend_mode = if !prim_common_data.opacity.is_opaque || + prim_info.clip_task_index != ClipTaskIndex::INVALID || + transform_kind == TransformedRectKind::Complex + { + specified_blend_mode + } else { + BlendMode::None + }; + + debug_assert_ne!(segment_instance_index, SegmentInstanceIndex::INVALID); + let (prim_cache_address, segments) = if segment_instance_index == SegmentInstanceIndex::UNUSED { + (gpu_cache.get_address(&prim_common_data.gpu_cache_handle), None) + } else { + let segment_instance = &ctx.scratch.segment_instances[segment_instance_index]; + let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]); + (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments) + }; + + let prim_header = PrimitiveHeader { + local_rect: prim_rect, + local_clip_rect: prim_info.combined_local_clip_rect, + specific_prim_address: prim_cache_address, + transform_id, + }; + + let prim_header_index = prim_headers.push( + &prim_header, + z_id, + batch_params.prim_user_data, + ); + + self.add_segmented_prim_to_batch( + segments, + prim_common_data.opacity, + &batch_params, + specified_blend_mode, + non_segmented_blend_mode, + batch_features, + prim_header_index, + bounding_rect, + transform_kind, + render_tasks, + z_id, + prim_info.clip_task_index, + prim_vis_mask, + ctx, + ); + } + PrimitiveInstanceKind::Image { data_handle, image_instance_index, is_compositor_surface, .. } => { + if is_compositor_surface { + self.emit_placeholder(prim_rect, + prim_info, + z_id, + transform_id, + batch_features, + ctx, + gpu_cache, + render_tasks, + prim_headers); + return; + } + let image_data = &ctx.data_stores.image[data_handle].kind; + let common_data = &ctx.data_stores.image[data_handle].common; + let image_instance = &ctx.prim_store.images[image_instance_index]; + let opacity_binding = ctx.prim_store.get_opacity_binding(image_instance.opacity_binding_index); + let specified_blend_mode = match image_data.alpha_type { + AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha, + AlphaType::Alpha => BlendMode::Alpha, + }; + let request = ImageRequest { + key: image_data.key, + rendering: image_data.image_rendering, + tile: None, + }; + let prim_user_data = ImageBrushData { + color_mode: ShaderColorMode::Image, + alpha_type: image_data.alpha_type, + raster_space: RasterizationSpace::Local, + opacity: opacity_binding, + }.encode(); + + if image_instance.visible_tiles.is_empty() { + let cache_item = match image_data.source { + ImageSource::Default => { + resolve_image( + request, + ctx.resource_cache, + gpu_cache, + deferred_resolves, + ) + } + ImageSource::Cache { ref handle, .. } => { + let rt_handle = handle + .as_ref() + .expect("bug: render task handle not allocated"); + let rt_cache_entry = ctx.resource_cache + .get_cached_render_task(rt_handle); + ctx.resource_cache.get_texture_cache_item(&rt_cache_entry.handle) + } + }; + + if cache_item.texture_id == TextureSource::Invalid { + return; + } + + let textures = BatchTextures::color(cache_item.texture_id); + + let opacity = PrimitiveOpacity::from_alpha(opacity_binding); + let opacity = opacity.combine(common_data.opacity); + + let non_segmented_blend_mode = if !opacity.is_opaque || + prim_info.clip_task_index != ClipTaskIndex::INVALID || + transform_kind == TransformedRectKind::Complex + { + specified_blend_mode + } else { + BlendMode::None + }; + + let batch_params = BrushBatchParameters::shared( + BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)), + textures, + prim_user_data, + cache_item.uv_rect_handle.as_int(gpu_cache), + ); + + debug_assert_ne!(image_instance.segment_instance_index, SegmentInstanceIndex::INVALID); + let (prim_cache_address, segments) = if image_instance.segment_instance_index == SegmentInstanceIndex::UNUSED { + (gpu_cache.get_address(&common_data.gpu_cache_handle), None) + } else { + let segment_instance = &ctx.scratch.segment_instances[image_instance.segment_instance_index]; + let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]); + (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments) + }; + + let prim_header = PrimitiveHeader { + local_rect: prim_rect, + local_clip_rect: prim_info.combined_local_clip_rect, + specific_prim_address: prim_cache_address, + transform_id, + }; + + let prim_header_index = prim_headers.push( + &prim_header, + z_id, + batch_params.prim_user_data, + ); + + self.add_segmented_prim_to_batch( + segments, + opacity, + &batch_params, + specified_blend_mode, + non_segmented_blend_mode, + batch_features, + prim_header_index, + bounding_rect, + transform_kind, + render_tasks, + z_id, + prim_info.clip_task_index, + prim_vis_mask, + ctx, + ); + } else { + const VECS_PER_SPECIFIC_BRUSH: usize = 3; + let max_tiles_per_header = (MAX_VERTEX_TEXTURE_WIDTH - VECS_PER_SPECIFIC_BRUSH) / VECS_PER_SEGMENT; + + // use temporary block storage since we don't know the number of visible tiles beforehand + let mut gpu_blocks = Vec::::new(); + for chunk in image_instance.visible_tiles.chunks(max_tiles_per_header) { + gpu_blocks.clear(); + gpu_blocks.push(PremultipliedColorF::WHITE.into()); //color + gpu_blocks.push(PremultipliedColorF::WHITE.into()); //bg color + gpu_blocks.push([-1.0, 0.0, 0.0, 0.0].into()); //stretch size + // negative first value makes the shader code ignore it and use the local size instead + for tile in chunk { + let tile_rect = tile.local_rect.translate(-prim_rect.origin.to_vector()); + gpu_blocks.push(tile_rect.into()); + gpu_blocks.push(GpuBlockData::EMPTY); + } + + let gpu_handle = gpu_cache.push_per_frame_blocks(&gpu_blocks); + let prim_header = PrimitiveHeader { + local_rect: prim_rect, + local_clip_rect: image_instance.tight_local_clip_rect, + specific_prim_address: gpu_cache.get_address(&gpu_handle), + transform_id, + }; + let prim_header_index = prim_headers.push(&prim_header, z_id, prim_user_data); + + for (i, tile) in chunk.iter().enumerate() { + if let Some((batch_kind, textures, uv_rect_address)) = get_image_tile_params( + ctx.resource_cache, + gpu_cache, + deferred_resolves, + request.with_tile(tile.tile_offset), + ) { + let batch_key = BatchKey { + blend_mode: specified_blend_mode, + kind: BatchKind::Brush(batch_kind), + textures, + }; + self.add_brush_instance_to_batches( + batch_key, + batch_features, + bounding_rect, + z_id, + i as i32, + tile.edge_flags, + clip_task_address.unwrap(), + BrushFlags::SEGMENT_RELATIVE | BrushFlags::PERSPECTIVE_INTERPOLATION, + prim_header_index, + uv_rect_address.as_int(), + prim_vis_mask, + ); + } + } + } + } + } + PrimitiveInstanceKind::LinearGradient { data_handle, gradient_index, .. } => { + let gradient = &ctx.prim_store.linear_gradients[gradient_index]; + let prim_data = &ctx.data_stores.linear_grad[data_handle]; + let specified_blend_mode = BlendMode::PremultipliedAlpha; + + let mut prim_header = PrimitiveHeader { + local_rect: prim_rect, + local_clip_rect: prim_info.combined_local_clip_rect, + specific_prim_address: GpuCacheAddress::INVALID, + transform_id, + }; + + let non_segmented_blend_mode = if !prim_data.opacity.is_opaque || + prim_info.clip_task_index != ClipTaskIndex::INVALID || + transform_kind == TransformedRectKind::Complex + { + specified_blend_mode + } else { + BlendMode::None + }; + + if !gradient.cache_segments.is_empty() { + + for segment in &gradient.cache_segments { + let ref cache_handle = segment.handle; + let rt_cache_entry = ctx.resource_cache + .get_cached_render_task(cache_handle); + let cache_item = ctx.resource_cache + .get_texture_cache_item(&rt_cache_entry.handle); + + if cache_item.texture_id == TextureSource::Invalid { + return; + } + + let textures = BatchTextures::color(cache_item.texture_id); + let batch_kind = BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)); + let prim_user_data = ImageBrushData { + color_mode: ShaderColorMode::Image, + alpha_type: AlphaType::PremultipliedAlpha, + raster_space: RasterizationSpace::Local, + opacity: 1.0, + }.encode(); + + let specific_resource_address = cache_item.uv_rect_handle.as_int(gpu_cache); + prim_header.specific_prim_address = gpu_cache.get_address(&ctx.globals.default_image_handle); + + let segment_local_clip_rect = match prim_header.local_clip_rect.intersection(&segment.local_rect) { + Some(rect) => rect, + None => { continue; } + }; + + let segment_prim_header = PrimitiveHeader { + local_rect: segment.local_rect, + local_clip_rect: segment_local_clip_rect, + specific_prim_address: prim_header.specific_prim_address, + transform_id: prim_header.transform_id, + }; + + let prim_header_index = prim_headers.push( + &segment_prim_header, + z_id, + prim_user_data, + ); + + let batch_key = BatchKey { + blend_mode: non_segmented_blend_mode, + kind: BatchKind::Brush(batch_kind), + textures, + }; + + self.add_brush_instance_to_batches( + batch_key, + batch_features, + bounding_rect, + z_id, + INVALID_SEGMENT_INDEX, + EdgeAaSegmentMask::all(), + clip_task_address.unwrap(), + BrushFlags::PERSPECTIVE_INTERPOLATION, + prim_header_index, + specific_resource_address, + prim_vis_mask, + ); + } + } else if gradient.visible_tiles_range.is_empty() { + let batch_params = BrushBatchParameters::shared( + BrushBatchKind::LinearGradient, + BatchTextures::no_texture(), + [ + prim_data.stops_handle.as_int(gpu_cache), + 0, + 0, + 0, + ], + 0, + ); + + prim_header.specific_prim_address = gpu_cache.get_address(&prim_data.gpu_cache_handle); + + let prim_header_index = prim_headers.push( + &prim_header, + z_id, + batch_params.prim_user_data, + ); + + let segments = if prim_data.brush_segments.is_empty() { + None + } else { + Some(prim_data.brush_segments.as_slice()) + }; + + self.add_segmented_prim_to_batch( + segments, + prim_data.opacity, + &batch_params, + specified_blend_mode, + non_segmented_blend_mode, + batch_features, + prim_header_index, + bounding_rect, + transform_kind, + render_tasks, + z_id, + prim_info.clip_task_index, + prim_vis_mask, + ctx, + ); + } else { + let visible_tiles = &ctx.scratch.gradient_tiles[gradient.visible_tiles_range]; + + self.add_gradient_tiles( + visible_tiles, + &prim_data.stops_handle, + BrushBatchKind::LinearGradient, + specified_blend_mode, + bounding_rect, + clip_task_address.unwrap(), + gpu_cache, + &prim_header, + prim_headers, + z_id, + prim_vis_mask, + ); + } + } + PrimitiveInstanceKind::RadialGradient { data_handle, ref visible_tiles_range, .. } => { + let prim_data = &ctx.data_stores.radial_grad[data_handle]; + let specified_blend_mode = BlendMode::PremultipliedAlpha; + + let mut prim_header = PrimitiveHeader { + local_rect: prim_rect, + local_clip_rect: prim_info.combined_local_clip_rect, + specific_prim_address: GpuCacheAddress::INVALID, + transform_id, + }; + + if visible_tiles_range.is_empty() { + let non_segmented_blend_mode = if !prim_data.opacity.is_opaque || + prim_info.clip_task_index != ClipTaskIndex::INVALID || + transform_kind == TransformedRectKind::Complex + { + specified_blend_mode + } else { + BlendMode::None + }; + + let batch_params = BrushBatchParameters::shared( + BrushBatchKind::RadialGradient, + BatchTextures::no_texture(), + [ + prim_data.stops_handle.as_int(gpu_cache), + 0, + 0, + 0, + ], + 0, + ); + + prim_header.specific_prim_address = gpu_cache.get_address(&prim_data.gpu_cache_handle); + + let prim_header_index = prim_headers.push( + &prim_header, + z_id, + batch_params.prim_user_data, + ); + + let segments = if prim_data.brush_segments.is_empty() { + None + } else { + Some(prim_data.brush_segments.as_slice()) + }; + + self.add_segmented_prim_to_batch( + segments, + prim_data.opacity, + &batch_params, + specified_blend_mode, + non_segmented_blend_mode, + batch_features, + prim_header_index, + bounding_rect, + transform_kind, + render_tasks, + z_id, + prim_info.clip_task_index, + prim_vis_mask, + ctx, + ); + } else { + let visible_tiles = &ctx.scratch.gradient_tiles[*visible_tiles_range]; + + self.add_gradient_tiles( + visible_tiles, + &prim_data.stops_handle, + BrushBatchKind::RadialGradient, + specified_blend_mode, + bounding_rect, + clip_task_address.unwrap(), + gpu_cache, + &prim_header, + prim_headers, + z_id, + prim_vis_mask, + ); + } + } + PrimitiveInstanceKind::ConicGradient { data_handle, ref visible_tiles_range, .. } => { + let prim_data = &ctx.data_stores.conic_grad[data_handle]; + let specified_blend_mode = BlendMode::PremultipliedAlpha; + + let mut prim_header = PrimitiveHeader { + local_rect: prim_rect, + local_clip_rect: prim_info.combined_local_clip_rect, + specific_prim_address: GpuCacheAddress::INVALID, + transform_id, + }; + + if visible_tiles_range.is_empty() { + let non_segmented_blend_mode = if !prim_data.opacity.is_opaque || + prim_info.clip_task_index != ClipTaskIndex::INVALID || + transform_kind == TransformedRectKind::Complex + { + specified_blend_mode + } else { + BlendMode::None + }; + + let batch_params = BrushBatchParameters::shared( + BrushBatchKind::ConicGradient, + BatchTextures::no_texture(), + [ + prim_data.stops_handle.as_int(gpu_cache), + 0, + 0, + 0, + ], + 0, + ); + + prim_header.specific_prim_address = gpu_cache.get_address(&prim_data.gpu_cache_handle); + + let prim_header_index = prim_headers.push( + &prim_header, + z_id, + batch_params.prim_user_data, + ); + + let segments = if prim_data.brush_segments.is_empty() { + None + } else { + Some(prim_data.brush_segments.as_slice()) + }; + + self.add_segmented_prim_to_batch( + segments, + prim_data.opacity, + &batch_params, + specified_blend_mode, + non_segmented_blend_mode, + batch_features, + prim_header_index, + bounding_rect, + transform_kind, + render_tasks, + z_id, + prim_info.clip_task_index, + prim_vis_mask, + ctx, + ); + } else { + let visible_tiles = &ctx.scratch.gradient_tiles[*visible_tiles_range]; + + self.add_gradient_tiles( + visible_tiles, + &prim_data.stops_handle, + BrushBatchKind::ConicGradient, + specified_blend_mode, + bounding_rect, + clip_task_address.unwrap(), + gpu_cache, + &prim_header, + prim_headers, + z_id, + prim_vis_mask, + ); + } + } + PrimitiveInstanceKind::Backdrop { data_handle } => { + let prim_data = &ctx.data_stores.backdrop[data_handle]; + let backdrop_pic_index = prim_data.kind.pic_index; + let backdrop_surface_index = ctx.prim_store.pictures[backdrop_pic_index.0] + .raster_config + .as_ref() + .expect("backdrop surface should be alloc by now") + .surface_index; + + let backdrop_task_id = ctx.surfaces[backdrop_surface_index.0] + .render_tasks + .as_ref() + .expect("backdrop task not available") + .root; + + let backdrop_uv_rect_address = render_tasks[backdrop_task_id] + .get_texture_address(gpu_cache) + .as_int(); + + let textures = BatchTextures::render_target_cache(); + let batch_key = BatchKey::new( + BatchKind::Brush(BrushBatchKind::Image(ImageBufferKind::Texture2DArray)), + BlendMode::PremultipliedAlpha, + textures, + ); + + let prim_cache_address = gpu_cache.get_address(&ctx.globals.default_image_handle); + let backdrop_picture = &ctx.prim_store.pictures[backdrop_pic_index.0]; + let prim_header = PrimitiveHeader { + local_rect: backdrop_picture.precise_local_rect, + local_clip_rect: prim_info.combined_local_clip_rect, + transform_id, + specific_prim_address: prim_cache_address, + }; + + let prim_header_index = prim_headers.push( + &prim_header, + z_id, + ImageBrushData { + color_mode: ShaderColorMode::Image, + alpha_type: AlphaType::PremultipliedAlpha, + raster_space: RasterizationSpace::Screen, + opacity: 1.0, + }.encode(), + ); + + self.add_brush_instance_to_batches( + batch_key, + batch_features, + bounding_rect, + z_id, + INVALID_SEGMENT_INDEX, + EdgeAaSegmentMask::empty(), + OPAQUE_TASK_ADDRESS, + BrushFlags::empty(), + prim_header_index, + backdrop_uv_rect_address, + prim_vis_mask, + ); + } + } + } + + /// Add a single segment instance to a batch. + fn add_segment_to_batch( + &mut self, + segment: &BrushSegment, + segment_data: &SegmentInstanceData, + segment_index: i32, + batch_kind: BrushBatchKind, + prim_header_index: PrimitiveHeaderIndex, + alpha_blend_mode: BlendMode, + features: BatchFeatures, + bounding_rect: &PictureRect, + transform_kind: TransformedRectKind, + render_tasks: &RenderTaskGraph, + z_id: ZBufferId, + prim_opacity: PrimitiveOpacity, + clip_task_index: ClipTaskIndex, + prim_vis_mask: PrimitiveVisibilityMask, + ctx: &RenderTargetContext, + ) { + debug_assert!(clip_task_index != ClipTaskIndex::INVALID); + + // Get GPU address of clip task for this segment, or None if + // the entire segment is clipped out. + let clip_task_address = match ctx.get_clip_task_address( + clip_task_index, + segment_index, + render_tasks, + ) { + Some(clip_task_address) => clip_task_address, + None => return, + }; + + // If a got a valid (or OPAQUE) clip task address, add the segment. + let is_inner = segment.edge_flags.is_empty(); + let needs_blending = !prim_opacity.is_opaque || + clip_task_address != OPAQUE_TASK_ADDRESS || + (!is_inner && transform_kind == TransformedRectKind::Complex); + + let batch_key = BatchKey { + blend_mode: if needs_blending { alpha_blend_mode } else { BlendMode::None }, + kind: BatchKind::Brush(batch_kind), + textures: segment_data.textures, + }; + + self.add_brush_instance_to_batches( + batch_key, + features, + bounding_rect, + z_id, + segment_index, + segment.edge_flags, + clip_task_address, + BrushFlags::PERSPECTIVE_INTERPOLATION | segment.brush_flags, + prim_header_index, + segment_data.specific_resource_address, + prim_vis_mask, + ); + } + + /// Add any segment(s) from a brush to batches. + fn add_segmented_prim_to_batch( + &mut self, + brush_segments: Option<&[BrushSegment]>, + prim_opacity: PrimitiveOpacity, + params: &BrushBatchParameters, + alpha_blend_mode: BlendMode, + non_segmented_blend_mode: BlendMode, + features: BatchFeatures, + prim_header_index: PrimitiveHeaderIndex, + bounding_rect: &PictureRect, + transform_kind: TransformedRectKind, + render_tasks: &RenderTaskGraph, + z_id: ZBufferId, + clip_task_index: ClipTaskIndex, + prim_vis_mask: PrimitiveVisibilityMask, + ctx: &RenderTargetContext, + ) { + match (brush_segments, ¶ms.segment_data) { + (Some(ref brush_segments), SegmentDataKind::Instanced(ref segment_data)) => { + // In this case, we have both a list of segments, and a list of + // per-segment instance data. Zip them together to build batches. + debug_assert_eq!(brush_segments.len(), segment_data.len()); + for (segment_index, (segment, segment_data)) in brush_segments + .iter() + .zip(segment_data.iter()) + .enumerate() + { + self.add_segment_to_batch( + segment, + segment_data, + segment_index as i32, + params.batch_kind, + prim_header_index, + alpha_blend_mode, + features, + bounding_rect, + transform_kind, + render_tasks, + z_id, + prim_opacity, + clip_task_index, + prim_vis_mask, + ctx, + ); + } + } + (Some(ref brush_segments), SegmentDataKind::Shared(ref segment_data)) => { + // A list of segments, but the per-segment data is common + // between all segments. + for (segment_index, segment) in brush_segments + .iter() + .enumerate() + { + self.add_segment_to_batch( + segment, + segment_data, + segment_index as i32, + params.batch_kind, + prim_header_index, + alpha_blend_mode, + features, + bounding_rect, + transform_kind, + render_tasks, + z_id, + prim_opacity, + clip_task_index, + prim_vis_mask, + ctx, + ); + } + } + (None, SegmentDataKind::Shared(ref segment_data)) => { + // No segments, and thus no per-segment instance data. + // Note: the blend mode already takes opacity into account + let batch_key = BatchKey { + blend_mode: non_segmented_blend_mode, + kind: BatchKind::Brush(params.batch_kind), + textures: segment_data.textures, + }; + let clip_task_address = ctx.get_prim_clip_task_address( + clip_task_index, + render_tasks, + ).unwrap(); + self.add_brush_instance_to_batches( + batch_key, + features, + bounding_rect, + z_id, + INVALID_SEGMENT_INDEX, + EdgeAaSegmentMask::all(), + clip_task_address, + BrushFlags::PERSPECTIVE_INTERPOLATION, + prim_header_index, + segment_data.specific_resource_address, + prim_vis_mask, + ); + } + (None, SegmentDataKind::Instanced(..)) => { + // We should never hit the case where there are no segments, + // but a list of segment instance data. + unreachable!(); + } + } + } + + fn add_gradient_tiles( + &mut self, + visible_tiles: &[VisibleGradientTile], + stops_handle: &GpuCacheHandle, + kind: BrushBatchKind, + blend_mode: BlendMode, + bounding_rect: &PictureRect, + clip_task_address: RenderTaskAddress, + gpu_cache: &GpuCache, + base_prim_header: &PrimitiveHeader, + prim_headers: &mut PrimitiveHeaders, + z_id: ZBufferId, + prim_vis_mask: PrimitiveVisibilityMask, + ) { + let key = BatchKey { + blend_mode, + kind: BatchKind::Brush(kind), + textures: BatchTextures::no_texture(), + }; + + let user_data = [stops_handle.as_int(gpu_cache), 0, 0, 0]; + + for tile in visible_tiles { + let prim_header = PrimitiveHeader { + specific_prim_address: gpu_cache.get_address(&tile.handle), + local_rect: tile.local_rect, + local_clip_rect: tile.local_clip_rect, + ..*base_prim_header + }; + let prim_header_index = prim_headers.push(&prim_header, z_id, user_data); + + self.add_brush_instance_to_batches( + key, + BatchFeatures::empty(), + bounding_rect, + z_id, + INVALID_SEGMENT_INDEX, + EdgeAaSegmentMask::all(), + clip_task_address, + BrushFlags::PERSPECTIVE_INTERPOLATION, + prim_header_index, + 0, + prim_vis_mask, + ); + } + } +} + +fn get_image_tile_params( + resource_cache: &ResourceCache, + gpu_cache: &mut GpuCache, + deferred_resolves: &mut Vec, + request: ImageRequest, +) -> Option<(BrushBatchKind, BatchTextures, GpuCacheAddress)> { + + let cache_item = resolve_image( + request, + resource_cache, + gpu_cache, + deferred_resolves, + ); + + if cache_item.texture_id == TextureSource::Invalid { + None + } else { + let textures = BatchTextures::color(cache_item.texture_id); + Some(( + BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)), + textures, + gpu_cache.get_address(&cache_item.uv_rect_handle), + )) + } +} + +/// Either a single texture / user data for all segments, +/// or a list of one per segment. +enum SegmentDataKind { + Shared(SegmentInstanceData), + Instanced(SmallVec<[SegmentInstanceData; 8]>), +} + +/// The parameters that are specific to a kind of brush, +/// used by the common method to add a brush to batches. +struct BrushBatchParameters { + batch_kind: BrushBatchKind, + prim_user_data: [i32; 4], + segment_data: SegmentDataKind, +} + +impl BrushBatchParameters { + /// This brush instance has a list of per-segment + /// instance data. + fn instanced( + batch_kind: BrushBatchKind, + prim_user_data: [i32; 4], + segment_data: SmallVec<[SegmentInstanceData; 8]>, + ) -> Self { + BrushBatchParameters { + batch_kind, + prim_user_data, + segment_data: SegmentDataKind::Instanced(segment_data), + } + } + + /// This brush instance shares the per-segment data + /// across all segments. + fn shared( + batch_kind: BrushBatchKind, + textures: BatchTextures, + prim_user_data: [i32; 4], + specific_resource_address: i32, + ) -> Self { + BrushBatchParameters { + batch_kind, + prim_user_data, + segment_data: SegmentDataKind::Shared( + SegmentInstanceData { + textures, + specific_resource_address, + } + ), + } + } +} + +impl RenderTaskGraph { + fn resolve_surface( + &self, + task_id: RenderTaskId, + gpu_cache: &GpuCache, + ) -> (GpuCacheAddress, BatchTextures) { + ( + self[task_id].get_texture_address(gpu_cache), + BatchTextures::render_target_cache(), + ) + } +} + +pub fn resolve_image( + request: ImageRequest, + resource_cache: &ResourceCache, + gpu_cache: &mut GpuCache, + deferred_resolves: &mut Vec, +) -> CacheItem { + match resource_cache.get_image_properties(request.key) { + Some(image_properties) => { + // Check if an external image that needs to be resolved + // by the render thread. + match image_properties.external_image { + Some(external_image) => { + // This is an external texture - we will add it to + // the deferred resolves list to be patched by + // the render thread... + let cache_handle = gpu_cache.push_deferred_per_frame_blocks(BLOCKS_PER_UV_RECT); + let cache_item = CacheItem { + texture_id: TextureSource::External(external_image), + uv_rect_handle: cache_handle, + uv_rect: DeviceIntRect::new( + DeviceIntPoint::zero(), + image_properties.descriptor.size, + ), + texture_layer: 0, + }; + + deferred_resolves.push(DeferredResolve { + image_properties, + address: gpu_cache.get_address(&cache_handle), + rendering: request.rendering, + }); + + cache_item + } + None => { + if let Ok(cache_item) = resource_cache.get_cached_image(request) { + cache_item + } else { + // There is no usable texture entry for the image key. Just return an invalid texture here. + CacheItem::invalid() + } + } + } + } + None => { + CacheItem::invalid() + } + } +} + +/// A list of clip instances to be drawn into a target. +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ClipBatchList { + /// Rectangle draws fill up the rectangles with rounded corners. + pub slow_rectangles: Vec, + pub fast_rectangles: Vec, + /// Image draws apply the image masking. + pub images: FastHashMap>, + pub box_shadows: FastHashMap>, +} + +impl ClipBatchList { + fn new() -> Self { + ClipBatchList { + slow_rectangles: Vec::new(), + fast_rectangles: Vec::new(), + images: FastHashMap::default(), + box_shadows: FastHashMap::default(), + } + } +} + +/// Batcher managing draw calls into the clip mask (in the RT cache). +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ClipBatcher { + /// The first clip in each clip task. This will overwrite all pixels + /// in the clip region, so we can skip doing a clear and write with + /// blending disabled, which is a big performance win on Intel GPUs. + pub primary_clips: ClipBatchList, + /// Any subsequent clip masks (rare) for a clip task get drawn in + /// a second pass with multiplicative blending enabled. + pub secondary_clips: ClipBatchList, + + gpu_supports_fast_clears: bool, +} + +impl ClipBatcher { + pub fn new( + gpu_supports_fast_clears: bool, + ) -> Self { + ClipBatcher { + primary_clips: ClipBatchList::new(), + secondary_clips: ClipBatchList::new(), + gpu_supports_fast_clears, + } + } + + pub fn add_clip_region( + &mut self, + clip_data_address: GpuCacheAddress, + local_pos: LayoutPoint, + sub_rect: DeviceRect, + task_origin: DevicePoint, + screen_origin: DevicePoint, + device_pixel_scale: f32, + ) { + let instance = ClipMaskInstance { + clip_transform_id: TransformPaletteId::IDENTITY, + prim_transform_id: TransformPaletteId::IDENTITY, + clip_data_address, + resource_address: GpuCacheAddress::INVALID, + local_pos, + tile_rect: LayoutRect::zero(), + sub_rect, + task_origin, + screen_origin, + device_pixel_scale, + }; + + self.primary_clips.slow_rectangles.push(instance); + } + + /// Where appropriate, draw a clip rectangle as a small series of tiles, + /// instead of one large rectangle. + fn add_tiled_clip_mask( + &mut self, + mask_screen_rect: DeviceIntRect, + local_clip_rect: LayoutRect, + clip_spatial_node_index: SpatialNodeIndex, + spatial_tree: &SpatialTree, + world_rect: &WorldRect, + device_pixel_scale: DevicePixelScale, + gpu_address: GpuCacheAddress, + instance: &ClipMaskInstance, + is_first_clip: bool, + ) -> bool { + // Only try to draw in tiles if the clip mark is big enough. + if mask_screen_rect.area() < CLIP_RECTANGLE_AREA_THRESHOLD { + return false; + } + + let clip_spatial_node = &spatial_tree + .spatial_nodes[clip_spatial_node_index.0 as usize]; + + // Only support clips that are axis-aligned to the root coordinate space, + // for now, to simplify the logic below. This handles the vast majority + // of real world cases, but could be expanded in future if needed. + if clip_spatial_node.coordinate_system_id != CoordinateSystemId::root() { + return false; + } + + // Get the world rect of the clip rectangle. If we can't transform it due + // to the matrix, just fall back to drawing the entire clip mask. + let transform = spatial_tree.get_world_transform( + clip_spatial_node_index, + ); + let world_clip_rect = match project_rect( + &transform.into_transform(), + &local_clip_rect, + world_rect, + ) { + Some(rect) => rect, + None => return false, + }; + + // Work out how many tiles to draw this clip mask in, stretched across the + // device rect of the primitive clip mask. + let world_device_rect = world_clip_rect * device_pixel_scale; + let x_tiles = (mask_screen_rect.size.width + CLIP_RECTANGLE_TILE_SIZE-1) / CLIP_RECTANGLE_TILE_SIZE; + let y_tiles = (mask_screen_rect.size.height + CLIP_RECTANGLE_TILE_SIZE-1) / CLIP_RECTANGLE_TILE_SIZE; + + // Because we only run this code path for axis-aligned rects (the root coord system check above), + // and only for rectangles (not rounded etc), the world_device_rect is not conservative - we know + // that there is no inner_rect, and the world_device_rect should be the real, axis-aligned clip rect. + let mask_origin = mask_screen_rect.origin.to_f32().to_vector(); + let clip_list = self.get_batch_list(is_first_clip); + + for y in 0 .. y_tiles { + for x in 0 .. x_tiles { + let p0 = DeviceIntPoint::new( + x * CLIP_RECTANGLE_TILE_SIZE, + y * CLIP_RECTANGLE_TILE_SIZE, + ); + let p1 = DeviceIntPoint::new( + (p0.x + CLIP_RECTANGLE_TILE_SIZE).min(mask_screen_rect.size.width), + (p0.y + CLIP_RECTANGLE_TILE_SIZE).min(mask_screen_rect.size.height), + ); + let normalized_sub_rect = DeviceIntRect::new( + p0, + DeviceIntSize::new( + p1.x - p0.x, + p1.y - p0.y, + ), + ).to_f32(); + let world_sub_rect = normalized_sub_rect.translate(mask_origin); + + // If the clip rect completely contains this tile rect, then drawing + // these pixels would be redundant - since this clip can't possibly + // affect the pixels in this tile, skip them! + if !world_device_rect.contains_rect(&world_sub_rect) { + clip_list.slow_rectangles.push(ClipMaskInstance { + clip_data_address: gpu_address, + sub_rect: normalized_sub_rect, + local_pos: local_clip_rect.origin, + ..*instance + }); + } + } + } + + true + } + + /// Retrieve the correct clip batch list to append to, depending + /// on whether this is the first clip mask for a clip task. + fn get_batch_list( + &mut self, + is_first_clip: bool, + ) -> &mut ClipBatchList { + if is_first_clip && !self.gpu_supports_fast_clears { + &mut self.primary_clips + } else { + &mut self.secondary_clips + } + } + + pub fn add( + &mut self, + clip_node_range: ClipNodeRange, + root_spatial_node_index: SpatialNodeIndex, + resource_cache: &ResourceCache, + gpu_cache: &GpuCache, + clip_store: &ClipStore, + spatial_tree: &SpatialTree, + transforms: &mut TransformPalette, + clip_data_store: &ClipDataStore, + actual_rect: DeviceIntRect, + world_rect: &WorldRect, + device_pixel_scale: DevicePixelScale, + task_origin: DevicePoint, + screen_origin: DevicePoint, + ) { + let mut is_first_clip = true; + + for i in 0 .. clip_node_range.count { + let clip_instance = clip_store.get_instance_from_range(&clip_node_range, i); + let clip_node = &clip_data_store[clip_instance.handle]; + + let clip_transform_id = transforms.get_id( + clip_instance.spatial_node_index, + ROOT_SPATIAL_NODE_INDEX, + spatial_tree, + ); + + let prim_transform_id = transforms.get_id( + root_spatial_node_index, + ROOT_SPATIAL_NODE_INDEX, + spatial_tree, + ); + + let instance = ClipMaskInstance { + clip_transform_id, + prim_transform_id, + clip_data_address: GpuCacheAddress::INVALID, + resource_address: GpuCacheAddress::INVALID, + local_pos: LayoutPoint::zero(), + tile_rect: LayoutRect::zero(), + sub_rect: DeviceRect::new( + DevicePoint::zero(), + actual_rect.size.to_f32(), + ), + task_origin, + screen_origin, + device_pixel_scale: device_pixel_scale.0, + }; + + let added_clip = match clip_node.item.kind { + ClipItemKind::Image { image, rect, .. } => { + let request = ImageRequest { + key: image, + rendering: ImageRendering::Auto, + tile: None, + }; + + let clip_data_address = + gpu_cache.get_address(&clip_node.gpu_cache_handle); + + let mut add_image = |request: ImageRequest, local_tile_rect: LayoutRect| { + let cache_item = match resource_cache.get_cached_image(request) { + Ok(item) => item, + Err(..) => { + warn!("Warnings: skip a image mask"); + debug!("request: {:?}", request); + return; + } + }; + + self.get_batch_list(is_first_clip) + .images + .entry(cache_item.texture_id) + .or_insert_with(Vec::new) + .push(ClipMaskInstance { + clip_data_address, + resource_address: gpu_cache.get_address(&cache_item.uv_rect_handle), + tile_rect: local_tile_rect, + local_pos: rect.origin, + ..instance + }); + }; + + match clip_instance.visible_tiles { + Some(ref tiles) => { + for tile in tiles { + add_image( + request.with_tile(tile.tile_offset), + tile.tile_rect, + ) + } + } + None => { + add_image(request, rect) + } + } + + true + } + ClipItemKind::BoxShadow { ref source } => { + let gpu_address = + gpu_cache.get_address(&clip_node.gpu_cache_handle); + let rt_handle = source + .cache_handle + .as_ref() + .expect("bug: render task handle not allocated"); + let rt_cache_entry = resource_cache + .get_cached_render_task(rt_handle); + let cache_item = resource_cache + .get_texture_cache_item(&rt_cache_entry.handle); + debug_assert_ne!(cache_item.texture_id, TextureSource::Invalid); + + self.get_batch_list(is_first_clip) + .box_shadows + .entry(cache_item.texture_id) + .or_insert_with(Vec::new) + .push(ClipMaskInstance { + clip_data_address: gpu_address, + resource_address: gpu_cache.get_address(&cache_item.uv_rect_handle), + ..instance + }); + + true + } + ClipItemKind::Rectangle { rect, mode: ClipMode::ClipOut } => { + let gpu_address = + gpu_cache.get_address(&clip_node.gpu_cache_handle); + self.get_batch_list(is_first_clip) + .slow_rectangles + .push(ClipMaskInstance { + local_pos: rect.origin, + clip_data_address: gpu_address, + ..instance + }); + + true + } + ClipItemKind::Rectangle { rect, mode: ClipMode::Clip } => { + if clip_instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) { + false + } else { + let gpu_address = gpu_cache.get_address(&clip_node.gpu_cache_handle); + + if !self.add_tiled_clip_mask( + actual_rect, + rect, + clip_instance.spatial_node_index, + spatial_tree, + world_rect, + device_pixel_scale, + gpu_address, + &instance, + is_first_clip, + ) { + self.get_batch_list(is_first_clip) + .slow_rectangles + .push(ClipMaskInstance { + clip_data_address: gpu_address, + local_pos: rect.origin, + ..instance + }); + } + + true + } + } + ClipItemKind::RoundedRectangle { rect, .. } => { + let gpu_address = + gpu_cache.get_address(&clip_node.gpu_cache_handle); + let batch_list = self.get_batch_list(is_first_clip); + let instance = ClipMaskInstance { + clip_data_address: gpu_address, + local_pos: rect.origin, + ..instance + }; + if clip_instance.flags.contains(ClipNodeFlags::USE_FAST_PATH) { + batch_list.fast_rectangles.push(instance); + } else { + batch_list.slow_rectangles.push(instance); + } + + true + } + }; + + is_first_clip &= !added_clip; + } + } +} + +// TODO(gw): This should probably be a method on TextureSource +pub fn get_buffer_kind(texture: TextureSource) -> ImageBufferKind { + match texture { + TextureSource::External(ext_image) => { + match ext_image.image_type { + ExternalImageType::TextureHandle(target) => { + target.into() + } + ExternalImageType::Buffer => { + // The ExternalImageType::Buffer should be handled by resource_cache. + // It should go through the non-external case. + panic!("Unexpected non-texture handle type"); + } + } + } + _ => ImageBufferKind::Texture2DArray, + } +} + +impl<'a, 'rc> RenderTargetContext<'a, 'rc> { + /// Retrieve the GPU task address for a given clip task instance. + /// Returns None if the segment was completely clipped out. + /// Returns Some(OPAQUE_TASK_ADDRESS) if no clip mask is needed. + /// Returns Some(task_address) if there was a valid clip mask. + fn get_clip_task_address( + &self, + clip_task_index: ClipTaskIndex, + offset: i32, + render_tasks: &RenderTaskGraph, + ) -> Option { + let address = match self.scratch.clip_mask_instances[clip_task_index.0 as usize + offset as usize] { + ClipMaskKind::Mask(task_id) => { + render_tasks.get_task_address(task_id) + } + ClipMaskKind::None => { + OPAQUE_TASK_ADDRESS + } + ClipMaskKind::Clipped => { + return None; + } + }; + + Some(address) + } + + /// Helper function to get the clip task address for a + /// non-segmented primitive. + fn get_prim_clip_task_address( + &self, + clip_task_index: ClipTaskIndex, + render_tasks: &RenderTaskGraph, + ) -> Option { + self.get_clip_task_address( + clip_task_index, + 0, + render_tasks, + ) + } +} diff --git a/third_party/webrender/webrender/src/border.rs b/third_party/webrender/webrender/src/border.rs new file mode 100644 index 00000000000..3185acd42c5 --- /dev/null +++ b/third_party/webrender/webrender/src/border.rs @@ -0,0 +1,1491 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{BorderRadius, BorderSide, BorderStyle, ColorF, ColorU}; +use api::{NormalBorder as ApiNormalBorder, RepeatMode, EdgeAaSegmentMask}; +use api::units::*; +use crate::clip::ClipChainId; +use crate::ellipse::Ellipse; +use euclid::vec2; +use crate::scene_building::SceneBuilder; +use crate::spatial_tree::SpatialNodeIndex; +use crate::gpu_types::{BorderInstance, BorderSegment, BrushFlags}; +use crate::prim_store::{BorderSegmentInfo, BrushSegment, NinePatchDescriptor}; +use crate::prim_store::borders::{NormalBorderPrim, NormalBorderData}; +use crate::util::{lerp, RectHelpers}; +use crate::internal_types::LayoutPrimitiveInfo; + +// Using 2048 as the maximum radius in device space before which we +// start stretching is up for debate. +// the value must be chosen so that the corners will not use an +// unreasonable amount of memory but should allow crisp corners in the +// common cases. + +/// Maximum resolution in device pixels at which borders are rasterized. +pub const MAX_BORDER_RESOLUTION: u32 = 2048; +/// Maximum number of dots or dashes per segment to avoid freezing and filling up +/// memory with unreasonable inputs. It would be better to address this by not building +/// a list of per-dot information in the first place. +pub const MAX_DASH_COUNT: u32 = 2048; + +// TODO(gw): Perhaps there is a better way to store +// the border cache key than duplicating +// all the border structs with hashable +// variants... + +#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct BorderRadiusAu { + pub top_left: LayoutSizeAu, + pub top_right: LayoutSizeAu, + pub bottom_left: LayoutSizeAu, + pub bottom_right: LayoutSizeAu, +} + +impl From for BorderRadiusAu { + fn from(radius: BorderRadius) -> BorderRadiusAu { + BorderRadiusAu { + top_left: radius.top_left.to_au(), + top_right: radius.top_right.to_au(), + bottom_right: radius.bottom_right.to_au(), + bottom_left: radius.bottom_left.to_au(), + } + } +} + +impl From for BorderRadius { + fn from(radius: BorderRadiusAu) -> Self { + BorderRadius { + top_left: LayoutSize::from_au(radius.top_left), + top_right: LayoutSize::from_au(radius.top_right), + bottom_right: LayoutSize::from_au(radius.bottom_right), + bottom_left: LayoutSize::from_au(radius.bottom_left), + } + } +} + +#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct BorderSideAu { + pub color: ColorU, + pub style: BorderStyle, +} + +impl From for BorderSideAu { + fn from(side: BorderSide) -> Self { + BorderSideAu { + color: side.color.into(), + style: side.style, + } + } +} + +impl From for BorderSide { + fn from(side: BorderSideAu) -> Self { + BorderSide { + color: side.color.into(), + style: side.style, + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, Hash, Eq, MallocSizeOf, PartialEq)] +pub struct NormalBorderAu { + pub left: BorderSideAu, + pub right: BorderSideAu, + pub top: BorderSideAu, + pub bottom: BorderSideAu, + pub radius: BorderRadiusAu, + /// Whether to apply anti-aliasing on the border corners. + /// + /// Note that for this to be `false` and work, this requires the borders to + /// be solid, and no border-radius. + pub do_aa: bool, +} + +impl NormalBorderAu { + // Construct a border based upon self with color + pub fn with_color(&self, color: ColorU) -> Self { + let mut b = self.clone(); + b.left.color = color; + b.right.color = color; + b.top.color = color; + b.bottom.color = color; + b + } +} + +impl From for NormalBorderAu { + fn from(border: ApiNormalBorder) -> Self { + NormalBorderAu { + left: border.left.into(), + right: border.right.into(), + top: border.top.into(), + bottom: border.bottom.into(), + radius: border.radius.into(), + do_aa: border.do_aa, + } + } +} + +impl From for ApiNormalBorder { + fn from(border: NormalBorderAu) -> Self { + ApiNormalBorder { + left: border.left.into(), + right: border.right.into(), + top: border.top.into(), + bottom: border.bottom.into(), + radius: border.radius.into(), + do_aa: border.do_aa, + } + } +} + +/// Cache key that uniquely identifies a border +/// segment in the render task cache. +#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct BorderSegmentCacheKey { + pub size: LayoutSizeAu, + pub radius: LayoutSizeAu, + pub side0: BorderSideAu, + pub side1: BorderSideAu, + pub segment: BorderSegment, + pub do_aa: bool, + pub h_adjacent_corner_outer: LayoutPointAu, + pub h_adjacent_corner_radius: LayoutSizeAu, + pub v_adjacent_corner_outer: LayoutPointAu, + pub v_adjacent_corner_radius: LayoutSizeAu, +} + +pub fn ensure_no_corner_overlap( + radius: &mut BorderRadius, + size: LayoutSize, +) { + let mut ratio = 1.0; + let top_left_radius = &mut radius.top_left; + let top_right_radius = &mut radius.top_right; + let bottom_right_radius = &mut radius.bottom_right; + let bottom_left_radius = &mut radius.bottom_left; + + let sum = top_left_radius.width + top_right_radius.width; + if size.width < sum { + ratio = f32::min(ratio, size.width / sum); + } + + let sum = bottom_left_radius.width + bottom_right_radius.width; + if size.width < sum { + ratio = f32::min(ratio, size.width / sum); + } + + let sum = top_left_radius.height + bottom_left_radius.height; + if size.height < sum { + ratio = f32::min(ratio, size.height / sum); + } + + let sum = top_right_radius.height + bottom_right_radius.height; + if size.height < sum { + ratio = f32::min(ratio, size.height / sum); + } + + if ratio < 1. { + top_left_radius.width *= ratio; + top_left_radius.height *= ratio; + + top_right_radius.width *= ratio; + top_right_radius.height *= ratio; + + bottom_left_radius.width *= ratio; + bottom_left_radius.height *= ratio; + + bottom_right_radius.width *= ratio; + bottom_right_radius.height *= ratio; + } +} + +impl<'a> SceneBuilder<'a> { + pub fn add_normal_border( + &mut self, + info: &LayoutPrimitiveInfo, + border: &ApiNormalBorder, + widths: LayoutSideOffsets, + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, + ) { + let mut border = *border; + ensure_no_corner_overlap(&mut border.radius, info.rect.size); + + self.add_primitive( + spatial_node_index, + clip_chain_id, + info, + Vec::new(), + NormalBorderPrim { + border: border.into(), + widths: widths.to_au(), + }, + ); + } +} + +pub trait BorderSideHelpers { + fn border_color(&self, is_inner_border: bool) -> ColorF; +} + +impl BorderSideHelpers for BorderSide { + fn border_color(&self, is_inner_border: bool) -> ColorF { + let lighter = match self.style { + BorderStyle::Inset => is_inner_border, + BorderStyle::Outset => !is_inner_border, + _ => return self.color, + }; + + // The modulate colors below are not part of the specification. They are + // derived from the Gecko source code and experimentation, and used to + // modulate the colors in order to generate colors for the inset/outset + // and groove/ridge border styles. + // + // NOTE(emilio): Gecko at least takes the background color into + // account, should we do the same? Looks a bit annoying for this. + // + // NOTE(emilio): If you change this algorithm, do the same change on + // get_colors_for_side in cs_border_segment.glsl. + if self.color.r != 0.0 || self.color.g != 0.0 || self.color.b != 0.0 { + let scale = if lighter { 1.0 } else { 2.0 / 3.0 }; + return self.color.scale_rgb(scale) + } + + let black = if lighter { 0.7 } else { 0.3 }; + ColorF::new(black, black, black, self.color.a) + } +} + +/// The kind of border corner clip. +#[repr(C)] +#[derive(Copy, Debug, Clone, PartialEq)] +pub enum BorderClipKind { + DashCorner = 1, + DashEdge = 2, + Dot = 3, +} + +fn compute_outer_and_clip_sign( + corner_segment: BorderSegment, + radius: DeviceSize, +) -> (DevicePoint, DeviceVector2D) { + let outer_scale = match corner_segment { + BorderSegment::TopLeft => DeviceVector2D::new(0.0, 0.0), + BorderSegment::TopRight => DeviceVector2D::new(1.0, 0.0), + BorderSegment::BottomRight => DeviceVector2D::new(1.0, 1.0), + BorderSegment::BottomLeft => DeviceVector2D::new(0.0, 1.0), + _ => panic!("bug: expected a corner segment"), + }; + let outer = DevicePoint::new( + outer_scale.x * radius.width, + outer_scale.y * radius.height, + ); + + let clip_sign = DeviceVector2D::new( + 1.0 - 2.0 * outer_scale.x, + 1.0 - 2.0 * outer_scale.y, + ); + + (outer, clip_sign) +} + +fn write_dashed_corner_instances( + corner_radius: DeviceSize, + widths: DeviceSize, + segment: BorderSegment, + base_instance: &BorderInstance, + instances: &mut Vec, +) -> Result<(), ()> { + let ellipse = Ellipse::new(corner_radius); + + let average_border_width = 0.5 * (widths.width + widths.height); + + let (_half_dash, num_half_dashes) = + compute_half_dash(average_border_width, ellipse.total_arc_length); + + if num_half_dashes == 0 { + return Err(()); + } + + let num_half_dashes = num_half_dashes.min(MAX_DASH_COUNT); + + let (outer, clip_sign) = compute_outer_and_clip_sign(segment, corner_radius); + + let instance_count = num_half_dashes / 4 + 1; + instances.reserve(instance_count as usize); + + let half_dash_arc_length = + ellipse.total_arc_length / num_half_dashes as f32; + let dash_length = 2. * half_dash_arc_length; + + let mut current_length = 0.; + for i in 0..instance_count { + let arc_length0 = current_length; + current_length += if i == 0 { + half_dash_arc_length + } else { + dash_length + }; + + let arc_length1 = current_length; + current_length += dash_length; + + let alpha = ellipse.find_angle_for_arc_length(arc_length0); + let beta = ellipse.find_angle_for_arc_length(arc_length1); + + let (point0, tangent0) = ellipse.get_point_and_tangent(alpha); + let (point1, tangent1) = ellipse.get_point_and_tangent(beta); + + let point0 = DevicePoint::new( + outer.x + clip_sign.x * (corner_radius.width - point0.x), + outer.y + clip_sign.y * (corner_radius.height - point0.y), + ); + + let tangent0 = DeviceVector2D::new( + -tangent0.x * clip_sign.x, + -tangent0.y * clip_sign.y, + ); + + let point1 = DevicePoint::new( + outer.x + clip_sign.x * (corner_radius.width - point1.x), + outer.y + clip_sign.y * (corner_radius.height - point1.y), + ); + + let tangent1 = DeviceVector2D::new( + -tangent1.x * clip_sign.x, + -tangent1.y * clip_sign.y, + ); + + instances.push(BorderInstance { + flags: base_instance.flags | ((BorderClipKind::DashCorner as i32) << 24), + clip_params: [ + point0.x, + point0.y, + tangent0.x, + tangent0.y, + point1.x, + point1.y, + tangent1.x, + tangent1.y, + ], + .. *base_instance + }); + } + + Ok(()) +} + +fn write_dotted_corner_instances( + corner_radius: DeviceSize, + widths: DeviceSize, + segment: BorderSegment, + base_instance: &BorderInstance, + instances: &mut Vec, +) -> Result<(), ()> { + let mut corner_radius = corner_radius; + if corner_radius.width < (widths.width / 2.0) { + corner_radius.width = 0.0; + } + if corner_radius.height < (widths.height / 2.0) { + corner_radius.height = 0.0; + } + + let (ellipse, max_dot_count) = + if corner_radius.width == 0. && corner_radius.height == 0. { + (Ellipse::new(corner_radius), 1) + } else { + // The centers of dots follow an ellipse along the middle of the + // border radius. + let inner_radius = (corner_radius - widths * 0.5).abs(); + let ellipse = Ellipse::new(inner_radius); + + // Allocate a "worst case" number of dot clips. This can be + // calculated by taking the minimum edge radius, since that + // will result in the maximum number of dots along the path. + let min_diameter = widths.width.min(widths.height); + + // Get the number of circles (assuming spacing of one diameter + // between dots). + let max_dot_count = 0.5 * ellipse.total_arc_length / min_diameter; + + // Add space for one extra dot since they are centered at the + // start of the arc. + (ellipse, max_dot_count.ceil() as usize) + }; + + if max_dot_count == 0 { + return Err(()); + } + + if max_dot_count == 1 { + let dot_diameter = lerp(widths.width, widths.height, 0.5); + instances.push(BorderInstance { + flags: base_instance.flags | ((BorderClipKind::Dot as i32) << 24), + clip_params: [ + widths.width / 2.0, widths.height / 2.0, 0.5 * dot_diameter, 0., + 0., 0., 0., 0., + ], + .. *base_instance + }); + return Ok(()); + } + + let max_dot_count = max_dot_count.min(MAX_DASH_COUNT as usize); + + // FIXME(emilio): Should probably use SmallVec. + let mut forward_dots = Vec::with_capacity(max_dot_count / 2 + 1); + let mut back_dots = Vec::with_capacity(max_dot_count / 2 + 1); + let mut leftover_arc_length = 0.0; + + // Alternate between adding dots at the start and end of the + // ellipse arc. This ensures that we always end up with an exact + // half dot at each end of the arc, to match up with the edges. + forward_dots.push(DotInfo::new(widths.width, widths.width)); + back_dots.push(DotInfo::new( + ellipse.total_arc_length - widths.height, + widths.height, + )); + + let (outer, clip_sign) = compute_outer_and_clip_sign(segment, corner_radius); + for dot_index in 0 .. max_dot_count { + let prev_forward_pos = *forward_dots.last().unwrap(); + let prev_back_pos = *back_dots.last().unwrap(); + + // Select which end of the arc to place a dot from. + // This just alternates between the start and end of + // the arc, which ensures that there is always an + // exact half-dot at each end of the ellipse. + let going_forward = dot_index & 1 == 0; + + let (next_dot_pos, leftover) = if going_forward { + let next_dot_pos = + prev_forward_pos.arc_pos + 2.0 * prev_forward_pos.diameter; + (next_dot_pos, prev_back_pos.arc_pos - next_dot_pos) + } else { + let next_dot_pos = prev_back_pos.arc_pos - 2.0 * prev_back_pos.diameter; + (next_dot_pos, next_dot_pos - prev_forward_pos.arc_pos) + }; + + // Use a lerp between each edge's dot + // diameter, based on the linear distance + // along the arc to get the diameter of the + // dot at this arc position. + let t = next_dot_pos / ellipse.total_arc_length; + let dot_diameter = lerp(widths.width, widths.height, t); + + // If we can't fit a dot, bail out. + if leftover < dot_diameter { + leftover_arc_length = leftover; + break; + } + + // We can place a dot! + let dot = DotInfo::new(next_dot_pos, dot_diameter); + if going_forward { + forward_dots.push(dot); + } else { + back_dots.push(dot); + } + } + + // Now step through the dots, and distribute any extra + // leftover space on the arc between them evenly. Once + // the final arc position is determined, generate the correct + // arc positions and angles that get passed to the clip shader. + let number_of_dots = forward_dots.len() + back_dots.len(); + let extra_space_per_dot = leftover_arc_length / (number_of_dots - 1) as f32; + + let create_dot_data = |arc_length: f32, dot_radius: f32| -> [f32; 8] { + // Represents the GPU data for drawing a single dot to a clip mask. The order + // these are specified must stay in sync with the way this data is read in the + // dot clip shader. + let theta = ellipse.find_angle_for_arc_length(arc_length); + let (center, _) = ellipse.get_point_and_tangent(theta); + + let center = DevicePoint::new( + outer.x + clip_sign.x * (corner_radius.width - center.x), + outer.y + clip_sign.y * (corner_radius.height - center.y), + ); + + [center.x, center.y, dot_radius, 0.0, 0.0, 0.0, 0.0, 0.0] + }; + + instances.reserve(number_of_dots); + for (i, dot) in forward_dots.iter().enumerate() { + let extra_dist = i as f32 * extra_space_per_dot; + instances.push(BorderInstance { + flags: base_instance.flags | ((BorderClipKind::Dot as i32) << 24), + clip_params: create_dot_data(dot.arc_pos + extra_dist, 0.5 * dot.diameter), + .. *base_instance + }); + } + + for (i, dot) in back_dots.iter().enumerate() { + let extra_dist = i as f32 * extra_space_per_dot; + instances.push(BorderInstance { + flags: base_instance.flags | ((BorderClipKind::Dot as i32) << 24), + clip_params: create_dot_data(dot.arc_pos - extra_dist, 0.5 * dot.diameter), + .. *base_instance + }); + } + + Ok(()) +} + +#[derive(Copy, Clone, Debug)] +struct DotInfo { + arc_pos: f32, + diameter: f32, +} + +impl DotInfo { + fn new(arc_pos: f32, diameter: f32) -> DotInfo { + DotInfo { arc_pos, diameter } + } +} + +/// Information needed to place and draw a border edge. +#[derive(Debug)] +struct EdgeInfo { + /// Offset in local space to place the edge from origin. + local_offset: f32, + /// Size of the edge in local space. + local_size: f32, + /// Local stretch size for this edge (repeat past this). + stretch_size: f32, +} + +impl EdgeInfo { + fn new( + local_offset: f32, + local_size: f32, + stretch_size: f32, + ) -> Self { + Self { + local_offset, + local_size, + stretch_size, + } + } +} + +// Given a side width and the available space, compute the half-dash (half of +// the 'on' segment) and the count of them for a given segment. +fn compute_half_dash(side_width: f32, total_size: f32) -> (f32, u32) { + let half_dash = side_width * 1.5; + let num_half_dashes = (total_size / half_dash).ceil() as u32; + + if num_half_dashes == 0 { + return (0., 0); + } + + // TODO(emilio): Gecko has some other heuristics here to start with a full + // dash when the border side is zero, for example. We might consider those + // in the future. + let num_half_dashes = if num_half_dashes % 4 != 0 { + num_half_dashes + 4 - num_half_dashes % 4 + } else { + num_half_dashes + }; + + let half_dash = total_size / num_half_dashes as f32; + (half_dash, num_half_dashes) +} + + +// Get the needed size in device pixels for an edge, +// based on the border style of that edge. This is used +// to determine how big the render task should be. +fn get_edge_info( + style: BorderStyle, + side_width: f32, + avail_size: f32, +) -> EdgeInfo { + // To avoid division by zero below. + if side_width <= 0.0 || avail_size <= 0.0 { + return EdgeInfo::new(0.0, 0.0, 0.0); + } + + match style { + BorderStyle::Dashed => { + // Basically, two times the dash size. + let (half_dash, _num_half_dashes) = + compute_half_dash(side_width, avail_size); + let stretch_size = 2.0 * 2.0 * half_dash; + EdgeInfo::new(0., avail_size, stretch_size) + } + BorderStyle::Dotted => { + let dot_and_space_size = 2.0 * side_width; + if avail_size < dot_and_space_size * 0.75 { + return EdgeInfo::new(0.0, 0.0, 0.0); + } + let approx_dot_count = avail_size / dot_and_space_size; + let dot_count = approx_dot_count.floor().max(1.0); + let used_size = dot_count * dot_and_space_size; + let extra_space = avail_size - used_size; + let stretch_size = dot_and_space_size; + let offset = (extra_space * 0.5).round(); + EdgeInfo::new(offset, used_size, stretch_size) + } + _ => { + EdgeInfo::new(0.0, avail_size, 8.0) + } + } +} + +/// Create the set of border segments and render task +/// cache keys for a given CSS border. +pub fn create_border_segments( + size: LayoutSize, + border: &ApiNormalBorder, + widths: &LayoutSideOffsets, + border_segments: &mut Vec, + brush_segments: &mut Vec, +) { + let rect = LayoutRect::new( + LayoutPoint::zero(), + size, + ); + + let overlap = LayoutSize::new( + (widths.left + widths.right - size.width).max(0.0), + (widths.top + widths.bottom - size.height).max(0.0), + ); + let non_overlapping_widths = LayoutSideOffsets::new( + widths.top - overlap.height / 2.0, + widths.right - overlap.width / 2.0, + widths.bottom - overlap.height / 2.0, + widths.left - overlap.width / 2.0, + ); + + let local_size_tl = LayoutSize::new( + border.radius.top_left.width.max(widths.left), + border.radius.top_left.height.max(widths.top), + ); + let local_size_tr = LayoutSize::new( + border.radius.top_right.width.max(widths.right), + border.radius.top_right.height.max(widths.top), + ); + let local_size_br = LayoutSize::new( + border.radius.bottom_right.width.max(widths.right), + border.radius.bottom_right.height.max(widths.bottom), + ); + let local_size_bl = LayoutSize::new( + border.radius.bottom_left.width.max(widths.left), + border.radius.bottom_left.height.max(widths.bottom), + ); + + let top_edge_info = get_edge_info( + border.top.style, + widths.top, + rect.size.width - local_size_tl.width - local_size_tr.width, + ); + let bottom_edge_info = get_edge_info( + border.bottom.style, + widths.bottom, + rect.size.width - local_size_bl.width - local_size_br.width, + ); + + let left_edge_info = get_edge_info( + border.left.style, + widths.left, + rect.size.height - local_size_tl.height - local_size_bl.height, + ); + let right_edge_info = get_edge_info( + border.right.style, + widths.right, + rect.size.height - local_size_tr.height - local_size_br.height, + ); + + add_edge_segment( + LayoutRect::from_floats( + rect.origin.x, + rect.origin.y + local_size_tl.height + left_edge_info.local_offset, + rect.origin.x + non_overlapping_widths.left, + rect.origin.y + local_size_tl.height + left_edge_info.local_offset + left_edge_info.local_size, + ), + &left_edge_info, + border.left, + non_overlapping_widths.left, + BorderSegment::Left, + EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT, + brush_segments, + border_segments, + border.do_aa, + ); + add_edge_segment( + LayoutRect::from_floats( + rect.origin.x + local_size_tl.width + top_edge_info.local_offset, + rect.origin.y, + rect.origin.x + local_size_tl.width + top_edge_info.local_offset + top_edge_info.local_size, + rect.origin.y + non_overlapping_widths.top, + ), + &top_edge_info, + border.top, + non_overlapping_widths.top, + BorderSegment::Top, + EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM, + brush_segments, + border_segments, + border.do_aa, + ); + add_edge_segment( + LayoutRect::from_floats( + rect.origin.x + rect.size.width - non_overlapping_widths.right, + rect.origin.y + local_size_tr.height + right_edge_info.local_offset, + rect.origin.x + rect.size.width, + rect.origin.y + local_size_tr.height + right_edge_info.local_offset + right_edge_info.local_size, + ), + &right_edge_info, + border.right, + non_overlapping_widths.right, + BorderSegment::Right, + EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT, + brush_segments, + border_segments, + border.do_aa, + ); + add_edge_segment( + LayoutRect::from_floats( + rect.origin.x + local_size_bl.width + bottom_edge_info.local_offset, + rect.origin.y + rect.size.height - non_overlapping_widths.bottom, + rect.origin.x + local_size_bl.width + bottom_edge_info.local_offset + bottom_edge_info.local_size, + rect.origin.y + rect.size.height, + ), + &bottom_edge_info, + border.bottom, + non_overlapping_widths.bottom, + BorderSegment::Bottom, + EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP, + brush_segments, + border_segments, + border.do_aa, + ); + + add_corner_segment( + LayoutRect::from_floats( + rect.origin.x, + rect.origin.y, + rect.origin.x + local_size_tl.width, + rect.origin.y + local_size_tl.height, + ), + LayoutRect::from_floats( + rect.origin.x, + rect.origin.y, + rect.max_x() - non_overlapping_widths.right, + rect.max_y() - non_overlapping_widths.bottom + ), + border.left, + border.top, + LayoutSize::new(widths.left, widths.top), + border.radius.top_left, + BorderSegment::TopLeft, + EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT, + rect.top_right(), + border.radius.top_right, + rect.bottom_left(), + border.radius.bottom_left, + brush_segments, + border_segments, + border.do_aa, + ); + add_corner_segment( + LayoutRect::from_floats( + rect.origin.x + rect.size.width - local_size_tr.width, + rect.origin.y, + rect.origin.x + rect.size.width, + rect.origin.y + local_size_tr.height, + ), + LayoutRect::from_floats( + rect.origin.x + non_overlapping_widths.left, + rect.origin.y, + rect.max_x(), + rect.max_y() - non_overlapping_widths.bottom, + ), + border.top, + border.right, + LayoutSize::new(widths.right, widths.top), + border.radius.top_right, + BorderSegment::TopRight, + EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT, + rect.origin, + border.radius.top_left, + rect.bottom_right(), + border.radius.bottom_right, + brush_segments, + border_segments, + border.do_aa, + ); + add_corner_segment( + LayoutRect::from_floats( + rect.origin.x + rect.size.width - local_size_br.width, + rect.origin.y + rect.size.height - local_size_br.height, + rect.origin.x + rect.size.width, + rect.origin.y + rect.size.height, + ), + LayoutRect::from_floats( + rect.origin.x + non_overlapping_widths.left, + rect.origin.y + non_overlapping_widths.top, + rect.max_x(), + rect.max_y(), + ), + border.right, + border.bottom, + LayoutSize::new(widths.right, widths.bottom), + border.radius.bottom_right, + BorderSegment::BottomRight, + EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT, + rect.bottom_left(), + border.radius.bottom_left, + rect.top_right(), + border.radius.top_right, + brush_segments, + border_segments, + border.do_aa, + ); + add_corner_segment( + LayoutRect::from_floats( + rect.origin.x, + rect.origin.y + rect.size.height - local_size_bl.height, + rect.origin.x + local_size_bl.width, + rect.origin.y + rect.size.height, + ), + LayoutRect::from_floats( + rect.origin.x, + rect.origin.y + non_overlapping_widths.top, + rect.max_x() - non_overlapping_widths.right, + rect.max_y(), + ), + border.bottom, + border.left, + LayoutSize::new(widths.left, widths.bottom), + border.radius.bottom_left, + BorderSegment::BottomLeft, + EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::LEFT, + rect.bottom_right(), + border.radius.bottom_right, + rect.origin, + border.radius.top_left, + brush_segments, + border_segments, + border.do_aa, + ); +} + +/// Computes the maximum scale that we allow for this set of border parameters. +/// capping the scale will result in rendering very large corners at a lower +/// resolution and stretching them, so they will have the right shape, but +/// blurrier. +pub fn get_max_scale_for_border( + border_data: &NormalBorderData, +) -> LayoutToDeviceScale { + let mut r = 1.0; + for segment in &border_data.border_segments { + let size = segment.local_task_size; + r = size.width.max(size.height.max(r)); + } + + LayoutToDeviceScale::new(MAX_BORDER_RESOLUTION as f32 / r) +} + +fn add_segment( + task_rect: DeviceRect, + style0: BorderStyle, + style1: BorderStyle, + color0: ColorF, + color1: ColorF, + segment: BorderSegment, + instances: &mut Vec, + widths: DeviceSize, + radius: DeviceSize, + do_aa: bool, + h_adjacent_corner_outer: DevicePoint, + h_adjacent_corner_radius: DeviceSize, + v_adjacent_corner_outer: DevicePoint, + v_adjacent_corner_radius: DeviceSize, +) { + let base_flags = (segment as i32) | + ((style0 as i32) << 8) | + ((style1 as i32) << 16) | + ((do_aa as i32) << 28); + + let base_instance = BorderInstance { + task_origin: DevicePoint::zero(), + local_rect: task_rect, + flags: base_flags, + color0: color0.premultiplied(), + color1: color1.premultiplied(), + widths, + radius, + clip_params: [0.0; 8], + }; + + match segment { + BorderSegment::TopLeft | + BorderSegment::TopRight | + BorderSegment::BottomLeft | + BorderSegment::BottomRight => { + // TODO(gw): Similarly to the old border code, we don't correctly handle a a corner + // that is dashed on one edge, and dotted on another. We can handle this + // in the future by submitting two instances, each one with one side + // color set to have an alpha of 0. + if (style0 == BorderStyle::Dotted && style1 == BorderStyle::Dashed) || + (style0 == BorderStyle::Dashed && style0 == BorderStyle::Dotted) { + warn!("TODO: Handle a corner with dotted / dashed transition."); + } + + let dashed_or_dotted_corner = match style0 { + BorderStyle::Dashed => { + write_dashed_corner_instances( + radius, + widths, + segment, + &base_instance, + instances, + ) + } + BorderStyle::Dotted => { + write_dotted_corner_instances( + radius, + widths, + segment, + &base_instance, + instances, + ) + } + _ => Err(()), + }; + + if dashed_or_dotted_corner.is_err() { + let clip_params = [ + h_adjacent_corner_outer.x, + h_adjacent_corner_outer.y, + h_adjacent_corner_radius.width, + h_adjacent_corner_radius.height, + v_adjacent_corner_outer.x, + v_adjacent_corner_outer.y, + v_adjacent_corner_radius.width, + v_adjacent_corner_radius.height, + ]; + + instances.push(BorderInstance { + clip_params, + ..base_instance + }); + } + } + BorderSegment::Top | + BorderSegment::Bottom | + BorderSegment::Right | + BorderSegment::Left => { + let is_vertical = segment == BorderSegment::Left || + segment == BorderSegment::Right; + + match style0 { + BorderStyle::Dashed => { + let (x, y) = if is_vertical { + let half_dash_size = task_rect.size.height * 0.25; + (0., half_dash_size) + } else { + let half_dash_size = task_rect.size.width * 0.25; + (half_dash_size, 0.) + }; + + instances.push(BorderInstance { + flags: base_flags | ((BorderClipKind::DashEdge as i32) << 24), + clip_params: [ + x, y, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + ], + ..base_instance + }); + } + BorderStyle::Dotted => { + let (x, y, r) = if is_vertical { + (widths.width * 0.5, + widths.width, + widths.width * 0.5) + } else { + (widths.height, + widths.height * 0.5, + widths.height * 0.5) + }; + + instances.push(BorderInstance { + flags: base_flags | ((BorderClipKind::Dot as i32) << 24), + clip_params: [ + x, y, r, 0.0, 0.0, 0.0, 0.0, 0.0, + ], + ..base_instance + }); + } + _ => { + instances.push(base_instance); + } + } + } + } +} + +/// Add a corner segment (if valid) to the list of +/// border segments for this primitive. +fn add_corner_segment( + image_rect: LayoutRect, + non_overlapping_rect: LayoutRect, + side0: BorderSide, + side1: BorderSide, + widths: LayoutSize, + radius: LayoutSize, + segment: BorderSegment, + edge_flags: EdgeAaSegmentMask, + h_adjacent_corner_outer: LayoutPoint, + h_adjacent_corner_radius: LayoutSize, + v_adjacent_corner_outer: LayoutPoint, + v_adjacent_corner_radius: LayoutSize, + brush_segments: &mut Vec, + border_segments: &mut Vec, + do_aa: bool, +) { + if side0.color.a <= 0.0 && side1.color.a <= 0.0 { + return; + } + + if widths.width <= 0.0 && widths.height <= 0.0 { + return; + } + + if side0.style.is_hidden() && side1.style.is_hidden() { + return; + } + + let segment_rect = match image_rect.intersection(&non_overlapping_rect) { + Some(rect) => rect, + None => { + return; + } + }; + + let texture_rect = segment_rect + .translate(-image_rect.origin.to_vector()) + .scale(1.0 / image_rect.size.width, 1.0 / image_rect.size.height); + + brush_segments.push( + BrushSegment::new( + segment_rect, + /* may_need_clip_mask = */ true, + edge_flags, + [texture_rect.min_x(), texture_rect.min_y(), texture_rect.max_x(), texture_rect.max_y()], + BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_TEXEL_RECT, + ) + ); + + // If the radii of the adjacent corners do not overlap with this segment, + // then set the outer position to this segment's corner and the radii to zero. + // That way the cache key is unaffected by non-overlapping corners, resulting + // in fewer misses. + let (h_corner_outer, h_corner_radius) = match segment { + BorderSegment::TopLeft => { + if h_adjacent_corner_outer.x - h_adjacent_corner_radius.width < image_rect.max_x() { + (h_adjacent_corner_outer, h_adjacent_corner_radius) + } else { + (LayoutPoint::new(image_rect.max_x(), image_rect.min_y()), LayoutSize::zero()) + } + } + BorderSegment::TopRight => { + if h_adjacent_corner_outer.x + h_adjacent_corner_radius.width > image_rect.min_x() { + (h_adjacent_corner_outer, h_adjacent_corner_radius) + } else { + (LayoutPoint::new(image_rect.min_x(), image_rect.min_y()), LayoutSize::zero()) + } + } + BorderSegment::BottomRight => { + if h_adjacent_corner_outer.x + h_adjacent_corner_radius.width > image_rect.min_x() { + (h_adjacent_corner_outer, h_adjacent_corner_radius) + } else { + (LayoutPoint::new(image_rect.min_x(), image_rect.max_y()), LayoutSize::zero()) + } + } + BorderSegment::BottomLeft => { + if h_adjacent_corner_outer.x - h_adjacent_corner_radius.width < image_rect.max_x() { + (h_adjacent_corner_outer, h_adjacent_corner_radius) + } else { + (image_rect.bottom_right(), LayoutSize::zero()) + } + } + _ => unreachable!() + }; + + let (v_corner_outer, v_corner_radius) = match segment { + BorderSegment::TopLeft => { + if v_adjacent_corner_outer.y - v_adjacent_corner_radius.height < image_rect.max_y() { + (v_adjacent_corner_outer, v_adjacent_corner_radius) + } else { + (LayoutPoint::new(image_rect.min_x(), image_rect.max_y()), LayoutSize::zero()) + } + } + BorderSegment::TopRight => { + if v_adjacent_corner_outer.y - v_adjacent_corner_radius.height < image_rect.max_y() { + (v_adjacent_corner_outer, v_adjacent_corner_radius) + } else { + (image_rect.bottom_right(), LayoutSize::zero()) + } + } + BorderSegment::BottomRight => { + if v_adjacent_corner_outer.y + v_adjacent_corner_radius.height > image_rect.min_y() { + (v_adjacent_corner_outer, v_adjacent_corner_radius) + } else { + (LayoutPoint::new(image_rect.max_x(), image_rect.min_y()), LayoutSize::zero()) + } + } + BorderSegment::BottomLeft => { + if v_adjacent_corner_outer.y + v_adjacent_corner_radius.height > image_rect.min_y() { + (v_adjacent_corner_outer, v_adjacent_corner_radius) + } else { + (LayoutPoint::new(image_rect.min_x(), image_rect.min_y()), LayoutSize::zero()) + } + } + _ => unreachable!() + }; + + border_segments.push(BorderSegmentInfo { + local_task_size: image_rect.size, + cache_key: BorderSegmentCacheKey { + do_aa, + side0: side0.into(), + side1: side1.into(), + segment, + radius: radius.to_au(), + size: widths.to_au(), + h_adjacent_corner_outer: (h_corner_outer - image_rect.origin).to_point().to_au(), + h_adjacent_corner_radius: h_corner_radius.to_au(), + v_adjacent_corner_outer: (v_corner_outer - image_rect.origin).to_point().to_au(), + v_adjacent_corner_radius: v_corner_radius.to_au(), + }, + }); +} + +/// Add an edge segment (if valid) to the list of +/// border segments for this primitive. +fn add_edge_segment( + image_rect: LayoutRect, + edge_info: &EdgeInfo, + side: BorderSide, + width: f32, + segment: BorderSegment, + edge_flags: EdgeAaSegmentMask, + brush_segments: &mut Vec, + border_segments: &mut Vec, + do_aa: bool, +) { + if side.color.a <= 0.0 { + return; + } + + if side.style.is_hidden() { + return; + } + + let (size, brush_flags) = match segment { + BorderSegment::Left | BorderSegment::Right => { + (LayoutSize::new(width, edge_info.stretch_size), BrushFlags::SEGMENT_REPEAT_Y) + } + BorderSegment::Top | BorderSegment::Bottom => { + (LayoutSize::new(edge_info.stretch_size, width), BrushFlags::SEGMENT_REPEAT_X) + } + _ => { + unreachable!(); + } + }; + + if image_rect.size.width <= 0. || image_rect.size.height <= 0. { + return; + } + + brush_segments.push( + BrushSegment::new( + image_rect, + /* may_need_clip_mask = */ true, + edge_flags, + [0.0, 0.0, size.width, size.height], + BrushFlags::SEGMENT_RELATIVE | brush_flags, + ) + ); + + border_segments.push(BorderSegmentInfo { + local_task_size: size, + cache_key: BorderSegmentCacheKey { + do_aa, + side0: side.into(), + side1: side.into(), + radius: LayoutSizeAu::zero(), + size: size.to_au(), + segment, + h_adjacent_corner_outer: LayoutPointAu::zero(), + h_adjacent_corner_radius: LayoutSizeAu::zero(), + v_adjacent_corner_outer: LayoutPointAu::zero(), + v_adjacent_corner_radius: LayoutSizeAu::zero(), + }, + }); +} + +/// Build the set of border instances needed to draw a border +/// segment into the render task cache. +pub fn build_border_instances( + cache_key: &BorderSegmentCacheKey, + cache_size: DeviceIntSize, + border: &ApiNormalBorder, + scale: LayoutToDeviceScale, +) -> Vec { + let mut instances = Vec::new(); + + let (side0, side1, flip0, flip1) = match cache_key.segment { + BorderSegment::Left => (&border.left, &border.left, false, false), + BorderSegment::Top => (&border.top, &border.top, false, false), + BorderSegment::Right => (&border.right, &border.right, true, true), + BorderSegment::Bottom => (&border.bottom, &border.bottom, true, true), + BorderSegment::TopLeft => (&border.left, &border.top, false, false), + BorderSegment::TopRight => (&border.top, &border.right, false, true), + BorderSegment::BottomRight => (&border.right, &border.bottom, true, true), + BorderSegment::BottomLeft => (&border.bottom, &border.left, true, false), + }; + + let style0 = if side0.style.is_hidden() { + side1.style + } else { + side0.style + }; + let style1 = if side1.style.is_hidden() { + side0.style + } else { + side1.style + }; + + let color0 = side0.border_color(flip0); + let color1 = side1.border_color(flip1); + + let widths = (LayoutSize::from_au(cache_key.size) * scale).ceil(); + let radius = (LayoutSize::from_au(cache_key.radius) * scale).ceil(); + + let h_corner_outer = (LayoutPoint::from_au(cache_key.h_adjacent_corner_outer) * scale).round(); + let h_corner_radius = (LayoutSize::from_au(cache_key.h_adjacent_corner_radius) * scale).ceil(); + let v_corner_outer = (LayoutPoint::from_au(cache_key.v_adjacent_corner_outer) * scale).round(); + let v_corner_radius = (LayoutSize::from_au(cache_key.v_adjacent_corner_radius) * scale).ceil(); + + add_segment( + DeviceRect::new(DevicePoint::zero(), cache_size.to_f32()), + style0, + style1, + color0, + color1, + cache_key.segment, + &mut instances, + widths, + radius, + border.do_aa, + h_corner_outer, + h_corner_radius, + v_corner_outer, + v_corner_radius, + ); + + instances +} + +impl NinePatchDescriptor { + pub fn create_segments( + &self, + size: LayoutSize, + ) -> Vec { + let rect = LayoutRect::new( + LayoutPoint::zero(), + size, + ); + + // Calculate the modified rect as specific by border-image-outset + let origin = LayoutPoint::new( + rect.origin.x - self.outset.left, + rect.origin.y - self.outset.top, + ); + let size = LayoutSize::new( + rect.size.width + self.outset.left + self.outset.right, + rect.size.height + self.outset.top + self.outset.bottom, + ); + let rect = LayoutRect::new(origin, size); + + // Calculate the local texel coords of the slices. + let px0 = 0.0; + let px1 = self.slice.left as f32 / self.width as f32; + let px2 = (self.width as f32 - self.slice.right as f32) / self.width as f32; + let px3 = 1.0; + + let py0 = 0.0; + let py1 = self.slice.top as f32 / self.height as f32; + let py2 = (self.height as f32 - self.slice.bottom as f32) / self.height as f32; + let py3 = 1.0; + + let tl_outer = LayoutPoint::new(rect.origin.x, rect.origin.y); + let tl_inner = tl_outer + vec2(self.widths.left, self.widths.top); + + let tr_outer = LayoutPoint::new(rect.origin.x + rect.size.width, rect.origin.y); + let tr_inner = tr_outer + vec2(-self.widths.right, self.widths.top); + + let bl_outer = LayoutPoint::new(rect.origin.x, rect.origin.y + rect.size.height); + let bl_inner = bl_outer + vec2(self.widths.left, -self.widths.bottom); + + let br_outer = LayoutPoint::new( + rect.origin.x + rect.size.width, + rect.origin.y + rect.size.height, + ); + let br_inner = br_outer - vec2(self.widths.right, self.widths.bottom); + + fn add_segment( + segments: &mut Vec, + rect: LayoutRect, + uv_rect: TexelRect, + repeat_horizontal: RepeatMode, + repeat_vertical: RepeatMode, + extra_flags: BrushFlags, + ) { + if uv_rect.uv1.x <= uv_rect.uv0.x || uv_rect.uv1.y <= uv_rect.uv0.y { + return; + } + + // Use segment relative interpolation for all + // instances in this primitive. + let mut brush_flags = + BrushFlags::SEGMENT_RELATIVE | + BrushFlags::SEGMENT_TEXEL_RECT | + extra_flags; + + // Enable repeat modes on the segment. + if repeat_horizontal == RepeatMode::Repeat { + brush_flags |= BrushFlags::SEGMENT_REPEAT_X; + } else if repeat_horizontal == RepeatMode::Round { + brush_flags |= BrushFlags::SEGMENT_REPEAT_X | BrushFlags::SEGMENT_REPEAT_X_ROUND; + } + + if repeat_vertical == RepeatMode::Repeat { + brush_flags |= BrushFlags::SEGMENT_REPEAT_Y; + } else if repeat_vertical == RepeatMode::Round { + brush_flags |= BrushFlags::SEGMENT_REPEAT_Y | BrushFlags::SEGMENT_REPEAT_Y_ROUND; + } + + let segment = BrushSegment::new( + rect, + true, + EdgeAaSegmentMask::empty(), + [ + uv_rect.uv0.x, + uv_rect.uv0.y, + uv_rect.uv1.x, + uv_rect.uv1.y, + ], + brush_flags, + ); + + segments.push(segment); + } + + // Build the list of image segments + let mut segments = Vec::new(); + + // Top left + add_segment( + &mut segments, + LayoutRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y), + TexelRect::new(px0, py0, px1, py1), + RepeatMode::Stretch, + RepeatMode::Stretch, + BrushFlags::empty(), + ); + // Top right + add_segment( + &mut segments, + LayoutRect::from_floats(tr_inner.x, tr_outer.y, tr_outer.x, tr_inner.y), + TexelRect::new(px2, py0, px3, py1), + RepeatMode::Stretch, + RepeatMode::Stretch, + BrushFlags::empty(), + ); + // Bottom right + add_segment( + &mut segments, + LayoutRect::from_floats(br_inner.x, br_inner.y, br_outer.x, br_outer.y), + TexelRect::new(px2, py2, px3, py3), + RepeatMode::Stretch, + RepeatMode::Stretch, + BrushFlags::empty(), + ); + // Bottom left + add_segment( + &mut segments, + LayoutRect::from_floats(bl_outer.x, bl_inner.y, bl_inner.x, bl_outer.y), + TexelRect::new(px0, py2, px1, py3), + RepeatMode::Stretch, + RepeatMode::Stretch, + BrushFlags::empty(), + ); + + // Center + if self.fill { + add_segment( + &mut segments, + LayoutRect::from_floats(tl_inner.x, tl_inner.y, tr_inner.x, bl_inner.y), + TexelRect::new(px1, py1, px2, py2), + self.repeat_horizontal, + self.repeat_vertical, + BrushFlags::SEGMENT_NINEPATCH_MIDDLE, + ); + } + + // Add edge segments. + + // Top + add_segment( + &mut segments, + LayoutRect::from_floats(tl_inner.x, tl_outer.y, tr_inner.x, tl_inner.y), + TexelRect::new(px1, py0, px2, py1), + self.repeat_horizontal, + RepeatMode::Stretch, + BrushFlags::empty(), + ); + // Bottom + add_segment( + &mut segments, + LayoutRect::from_floats(bl_inner.x, bl_inner.y, br_inner.x, bl_outer.y), + TexelRect::new(px1, py2, px2, py3), + self.repeat_horizontal, + RepeatMode::Stretch, + BrushFlags::empty(), + ); + // Left + add_segment( + &mut segments, + LayoutRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y), + TexelRect::new(px0, py1, px1, py2), + RepeatMode::Stretch, + self.repeat_vertical, + BrushFlags::empty(), + ); + // Right + add_segment( + &mut segments, + LayoutRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y), + TexelRect::new(px2, py1, px3, py2), + RepeatMode::Stretch, + self.repeat_vertical, + BrushFlags::empty(), + ); + + segments + } +} diff --git a/third_party/webrender/webrender/src/box_shadow.rs b/third_party/webrender/webrender/src/box_shadow.rs new file mode 100644 index 00000000000..49d6f884ffb --- /dev/null +++ b/third_party/webrender/webrender/src/box_shadow.rs @@ -0,0 +1,277 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, PrimitiveKeyKind}; +use api::PropertyBinding; +use api::units::*; +use crate::clip::{ClipItemKey, ClipItemKeyKind, ClipChainId}; +use crate::scene_building::SceneBuilder; +use crate::spatial_tree::SpatialNodeIndex; +use crate::gpu_cache::GpuCacheHandle; +use crate::gpu_types::BoxShadowStretchMode; +use crate::render_task_cache::RenderTaskCacheEntryHandle; +use crate::util::RectHelpers; +use crate::internal_types::LayoutPrimitiveInfo; + +#[derive(Debug, Clone, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct BoxShadowClipSource { + // Parameters that define the shadow and are constant. + pub shadow_radius: BorderRadius, + pub blur_radius: f32, + pub clip_mode: BoxShadowClipMode, + pub stretch_mode_x: BoxShadowStretchMode, + pub stretch_mode_y: BoxShadowStretchMode, + + // The current cache key (in device-pixels), and handles + // to the cached clip region and blurred texture. + pub cache_key: Option<(DeviceIntSize, BoxShadowCacheKey)>, + pub cache_handle: Option, + pub clip_data_handle: GpuCacheHandle, + + // Local-space size of the required render task size. + pub shadow_rect_alloc_size: LayoutSize, + + // Local-space size of the required render task size without any downscaling + // applied. This is needed to stretch the shadow properly. + pub original_alloc_size: LayoutSize, + + // The minimal shadow rect for the parameters above, + // used when drawing the shadow rect to be blurred. + pub minimal_shadow_rect: LayoutRect, + + // Local space rect for the shadow to be drawn or + // stretched in the shadow primitive. + pub prim_shadow_rect: LayoutRect, +} + +// The blur shader samples BLUR_SAMPLE_SCALE * blur_radius surrounding texels. +pub const BLUR_SAMPLE_SCALE: f32 = 3.0; + +// Maximum blur radius for box-shadows (different than blur filters). +// Taken from nsCSSRendering.cpp in Gecko. +pub const MAX_BLUR_RADIUS: f32 = 300.; + +// A cache key that uniquely identifies a minimally sized +// and blurred box-shadow rect that can be stored in the +// texture cache and applied to clip-masks. +#[derive(Debug, Clone, Eq, Hash, MallocSizeOf, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct BoxShadowCacheKey { + pub blur_radius_dp: i32, + pub clip_mode: BoxShadowClipMode, + // NOTE(emilio): Only the original allocation size needs to be in the cache + // key, since the actual size is derived from that. + pub original_alloc_size: DeviceIntSize, + pub br_top_left: DeviceIntSize, + pub br_top_right: DeviceIntSize, + pub br_bottom_right: DeviceIntSize, + pub br_bottom_left: DeviceIntSize, +} + +impl<'a> SceneBuilder<'a> { + pub fn add_box_shadow( + &mut self, + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, + prim_info: &LayoutPrimitiveInfo, + box_offset: &LayoutVector2D, + color: ColorF, + mut blur_radius: f32, + spread_radius: f32, + border_radius: BorderRadius, + clip_mode: BoxShadowClipMode, + ) { + if color.a == 0.0 { + return; + } + + // Inset shadows get smaller as spread radius increases. + let (spread_amount, prim_clip_mode) = match clip_mode { + BoxShadowClipMode::Outset => (spread_radius, ClipMode::ClipOut), + BoxShadowClipMode::Inset => (-spread_radius, ClipMode::Clip), + }; + + // Ensure the blur radius is somewhat sensible. + blur_radius = f32::min(blur_radius, MAX_BLUR_RADIUS); + + // Adjust the border radius of the box shadow per CSS-spec. + let shadow_radius = adjust_border_radius_for_box_shadow(border_radius, spread_amount); + + // Apply parameters that affect where the shadow rect + // exists in the local space of the primitive. + let shadow_rect = self.snap_rect( + &prim_info + .rect + .translate(*box_offset) + .inflate(spread_amount, spread_amount), + spatial_node_index, + ); + + // If blur radius is zero, we can use a fast path with + // no blur applied. + if blur_radius == 0.0 { + // Trivial reject of box-shadows that are not visible. + if box_offset.x == 0.0 && box_offset.y == 0.0 && spread_amount == 0.0 { + return; + } + + let mut clips = Vec::with_capacity(2); + let (final_prim_rect, clip_radius) = match clip_mode { + BoxShadowClipMode::Outset => { + if !shadow_rect.is_well_formed_and_nonempty() { + return; + } + + // TODO(gw): Add a fast path for ClipOut + zero border radius! + clips.push(ClipItemKey { + kind: ClipItemKeyKind::rounded_rect( + prim_info.rect, + border_radius, + ClipMode::ClipOut, + ), + }); + + (shadow_rect, shadow_radius) + } + BoxShadowClipMode::Inset => { + if shadow_rect.is_well_formed_and_nonempty() { + clips.push(ClipItemKey { + kind: ClipItemKeyKind::rounded_rect( + shadow_rect, + shadow_radius, + ClipMode::ClipOut, + ), + }); + } + + (prim_info.rect, border_radius) + } + }; + + clips.push(ClipItemKey { + kind: ClipItemKeyKind::rounded_rect( + final_prim_rect, + clip_radius, + ClipMode::Clip, + ), + }); + + self.add_primitive( + spatial_node_index, + clip_chain_id, + &LayoutPrimitiveInfo::with_clip_rect(final_prim_rect, prim_info.clip_rect), + clips, + PrimitiveKeyKind::Rectangle { + color: PropertyBinding::Value(color.into()), + }, + ); + } else { + // Normal path for box-shadows with a valid blur radius. + let blur_offset = (BLUR_SAMPLE_SCALE * blur_radius).ceil(); + let mut extra_clips = vec![]; + + // Add a normal clip mask to clip out the contents + // of the surrounding primitive. + extra_clips.push(ClipItemKey { + kind: ClipItemKeyKind::rounded_rect( + prim_info.rect, + border_radius, + prim_clip_mode, + ), + }); + + // Get the local rect of where the shadow will be drawn, + // expanded to include room for the blurred region. + let dest_rect = shadow_rect.inflate(blur_offset, blur_offset); + + // Draw the box-shadow as a solid rect, using a box-shadow + // clip mask item. + let prim = PrimitiveKeyKind::Rectangle { + color: PropertyBinding::Value(color.into()), + }; + + // Create the box-shadow clip item. + let shadow_clip_source = ClipItemKey { + kind: ClipItemKeyKind::box_shadow( + shadow_rect, + shadow_radius, + dest_rect, + blur_radius, + clip_mode, + ), + }; + + let prim_info = match clip_mode { + BoxShadowClipMode::Outset => { + // Certain spread-radii make the shadow invalid. + if !shadow_rect.is_well_formed_and_nonempty() { + return; + } + + // Add the box-shadow clip source. + extra_clips.push(shadow_clip_source); + + // Outset shadows are expanded by the shadow + // region from the original primitive. + LayoutPrimitiveInfo::with_clip_rect(dest_rect, prim_info.clip_rect) + } + BoxShadowClipMode::Inset => { + // If the inner shadow rect contains the prim + // rect, no pixels will be shadowed. + if border_radius.is_zero() && shadow_rect + .inflate(-blur_radius, -blur_radius) + .contains_rect(&prim_info.rect) + { + return; + } + + // Inset shadows are still visible, even if the + // inset shadow rect becomes invalid (they will + // just look like a solid rectangle). + if shadow_rect.is_well_formed_and_nonempty() { + extra_clips.push(shadow_clip_source); + } + + // Inset shadows draw inside the original primitive. + prim_info.clone() + } + }; + + self.add_primitive( + spatial_node_index, + clip_chain_id, + &prim_info, + extra_clips, + prim, + ); + } + } +} + +fn adjust_border_radius_for_box_shadow(radius: BorderRadius, spread_amount: f32) -> BorderRadius { + BorderRadius { + top_left: adjust_corner_for_box_shadow(radius.top_left, spread_amount), + top_right: adjust_corner_for_box_shadow(radius.top_right, spread_amount), + bottom_right: adjust_corner_for_box_shadow(radius.bottom_right, spread_amount), + bottom_left: adjust_corner_for_box_shadow(radius.bottom_left, spread_amount), + } +} + +fn adjust_corner_for_box_shadow(corner: LayoutSize, spread_amount: f32) -> LayoutSize { + LayoutSize::new( + adjust_radius_for_box_shadow(corner.width, spread_amount), + adjust_radius_for_box_shadow(corner.height, spread_amount), + ) +} + +fn adjust_radius_for_box_shadow(border_radius: f32, spread_amount: f32) -> f32 { + if border_radius > 0.0 { + (border_radius + spread_amount).max(0.0) + } else { + 0.0 + } +} diff --git a/third_party/webrender/webrender/src/capture.rs b/third_party/webrender/webrender/src/capture.rs new file mode 100644 index 00000000000..d6952b7f82d --- /dev/null +++ b/third_party/webrender/webrender/src/capture.rs @@ -0,0 +1,290 @@ +/* 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 std::fs::File; +use std::path::{Path, PathBuf}; + +use api::{CaptureBits, ExternalImageData, ImageDescriptor}; +#[cfg(feature = "png")] +use api::ImageFormat; +use api::units::TexelRect; +#[cfg(feature = "png")] +use api::units::DeviceIntSize; +#[cfg(feature = "capture")] +use crate::print_tree::{PrintableTree, PrintTree}; +use ron; +use serde; + + +#[derive(Clone)] +pub struct CaptureConfig { + pub root: PathBuf, + pub bits: CaptureBits, + /// Scene sequence ID when capturing multiple frames. Zero for a single frame capture. + pub scene_id: u32, + /// Frame sequence ID when capturing multiple frames. Zero for a single frame capture. + pub frame_id: u32, + /// Resource sequence ID when capturing multiple frames. Zero for a single frame capture. + pub resource_id: u32, + #[cfg(feature = "capture")] + pretty: ron::ser::PrettyConfig, +} + +impl CaptureConfig { + #[cfg(any(feature = "capture", feature = "replay"))] + pub fn new(root: PathBuf, bits: CaptureBits) -> Self { + CaptureConfig { + root, + bits, + scene_id: 0, + frame_id: 0, + resource_id: 0, + #[cfg(feature = "capture")] + pretty: ron::ser::PrettyConfig { + enumerate_arrays: true, + .. ron::ser::PrettyConfig::default() + }, + } + } + + #[cfg(feature = "capture")] + pub fn prepare_scene(&mut self) { + use std::fs::create_dir_all; + self.scene_id += 1; + let _ = create_dir_all(&self.scene_root()); + } + + #[cfg(feature = "capture")] + pub fn prepare_frame(&mut self) { + use std::fs::create_dir_all; + self.frame_id += 1; + let _ = create_dir_all(&self.frame_root()); + } + + #[cfg(feature = "capture")] + pub fn prepare_resource(&mut self) { + use std::fs::create_dir_all; + self.resource_id += 1; + let _ = create_dir_all(&self.resource_root()); + } + + #[cfg(any(feature = "capture", feature = "replay"))] + pub fn scene_root(&self) -> PathBuf { + if self.scene_id > 0 { + let path = format!("scenes/{:05}", self.scene_id); + self.root.join(path) + } else { + self.root.clone() + } + } + + #[cfg(any(feature = "capture", feature = "replay"))] + pub fn frame_root(&self) -> PathBuf { + if self.frame_id > 0 { + let path = format!("frames/{:05}", self.frame_id); + self.scene_root().join(path) + } else { + self.root.clone() + } + } + + #[cfg(any(feature = "capture", feature = "replay"))] + pub fn resource_root(&self) -> PathBuf { + if self.resource_id > 0 { + let path = format!("resources/{:05}", self.resource_id); + self.root.join(path) + } else { + self.root.clone() + } + } + + #[cfg(feature = "capture")] + pub fn serialize_for_scene(&self, data: &T, name: P) + where + T: serde::Serialize, + P: AsRef, + { + self.serialize(data, self.scene_root(), name) + } + + #[cfg(feature = "capture")] + pub fn serialize_for_frame(&self, data: &T, name: P) + where + T: serde::Serialize, + P: AsRef, + { + self.serialize(data, self.frame_root(), name) + } + + #[cfg(feature = "capture")] + pub fn serialize_for_resource(&self, data: &T, name: P) + where + T: serde::Serialize, + P: AsRef, + { + self.serialize(data, self.resource_root(), name) + } + + #[cfg(feature = "capture")] + pub fn file_path_for_frame

    (&self, name: P, ext: &str) -> PathBuf + where P: AsRef { + self.frame_root().join(name).with_extension(ext) + } + + #[cfg(feature = "capture")] + fn serialize(&self, data: &T, path: PathBuf, name: P) + where + T: serde::Serialize, + P: AsRef, + { + use std::io::Write; + let ron = ron::ser::to_string_pretty(data, self.pretty.clone()) + .unwrap(); + let mut file = File::create(path.join(name).with_extension("ron")) + .unwrap(); + write!(file, "{}\n", ron) + .unwrap(); + } + + #[cfg(feature = "capture")] + fn serialize_tree(data: &T, root: PathBuf, name: P) + where + T: PrintableTree, + P: AsRef + { + let path = root + .join(name) + .with_extension("tree"); + let file = File::create(path) + .unwrap(); + let mut pt = PrintTree::new_with_sink("", file); + data.print_with(&mut pt); + } + + #[cfg(feature = "capture")] + pub fn serialize_tree_for_frame(&self, data: &T, name: P) + where + T: PrintableTree, + P: AsRef + { + Self::serialize_tree(data, self.frame_root(), name) + } + + #[cfg(feature = "replay")] + fn deserialize(root: &PathBuf, name: P) -> Option + where + T: for<'a> serde::Deserialize<'a>, + P: AsRef, + { + use std::io::Read; + + let mut string = String::new(); + let path = root + .join(name.as_ref()) + .with_extension("ron"); + File::open(path) + .ok()? + .read_to_string(&mut string) + .unwrap(); + match ron::de::from_str(&string) { + Ok(out) => Some(out), + Err(e) => panic!("File {:?} deserialization failed: {:?}", name.as_ref(), e), + } + } + + #[cfg(feature = "replay")] + pub fn deserialize_for_scene(&self, name: P) -> Option + where + T: for<'a> serde::Deserialize<'a>, + P: AsRef, + { + Self::deserialize(&self.scene_root(), name) + } + + #[cfg(feature = "replay")] + pub fn deserialize_for_frame(&self, name: P) -> Option + where + T: for<'a> serde::Deserialize<'a>, + P: AsRef, + { + Self::deserialize(&self.frame_root(), name) + } + + #[cfg(feature = "replay")] + pub fn deserialize_for_resource(&self, name: P) -> Option + where + T: for<'a> serde::Deserialize<'a>, + P: AsRef, + { + Self::deserialize(&self.resource_root(), name) + } + + #[cfg(feature = "png")] + pub fn save_png( + path: PathBuf, size: DeviceIntSize, format: ImageFormat, stride: Option, data: &[u8], + ) { + use png::{BitDepth, ColorType, Encoder}; + use std::io::BufWriter; + use std::borrow::Cow; + + // `png` expects + let data = match stride { + Some(stride) if stride != format.bytes_per_pixel() * size.width => { + let mut unstrided = Vec::new(); + for y in 0..size.height { + let start = (y * stride) as usize; + unstrided.extend_from_slice(&data[start..start+(size.width * format.bytes_per_pixel()) as usize]); + } + Cow::from(unstrided) + } + _ => Cow::from(data), + }; + + let color_type = match format { + ImageFormat::RGBA8 => ColorType::RGBA, + ImageFormat::BGRA8 => { + warn!("Unable to swizzle PNG of BGRA8 type"); + ColorType::RGBA + }, + ImageFormat::R8 => ColorType::Grayscale, + ImageFormat::RG8 => ColorType::GrayscaleAlpha, + _ => { + error!("Unable to save PNG of {:?}", format); + return; + } + }; + let w = BufWriter::new(File::create(path).unwrap()); + let mut enc = Encoder::new(w, size.width as u32, size.height as u32); + enc.set_color(color_type); + enc.set_depth(BitDepth::Eight); + enc + .write_header() + .unwrap() + .write_image_data(&*data) + .unwrap(); + } +} + +/// An image that `ResourceCache` is unable to resolve during a capture. +/// The image has to be transferred to `Renderer` and locked with the +/// external image handler to get the actual contents and serialize them. +#[derive(Deserialize, Serialize)] +pub struct ExternalCaptureImage { + pub short_path: String, + pub descriptor: ImageDescriptor, + pub external: ExternalImageData, +} + +/// A short description of an external image to be saved separately as +/// "externals/XX.ron", redirecting into a specific texture/blob with +/// the corresponding UV rectangle. +#[derive(Deserialize, Serialize)] +pub struct PlainExternalImage { + /// Path to the RON file describing the texel data. + pub data: String, + /// External image data source. + pub external: ExternalImageData, + /// UV sub-rectangle of the image. + pub uv: TexelRect, +} diff --git a/third_party/webrender/webrender/src/clip.rs b/third_party/webrender/webrender/src/clip.rs new file mode 100644 index 00000000000..391170f0b08 --- /dev/null +++ b/third_party/webrender/webrender/src/clip.rs @@ -0,0 +1,1953 @@ +/* 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/. */ + +//! Internal representation of clips in WebRender. +//! +//! # Data structures +//! +//! There are a number of data structures involved in the clip module: +//! +//! - ClipStore - Main interface used by other modules. +//! +//! - ClipItem - A single clip item (e.g. a rounded rect, or a box shadow). +//! These are an exposed API type, stored inline in a ClipNode. +//! +//! - ClipNode - A ClipItem with an attached GPU handle. The GPU handle is populated +//! when a ClipNodeInstance is built from this node (which happens while +//! preparing primitives for render). +//! +//! ClipNodeInstance - A ClipNode with attached positioning information (a spatial +//! node index). This is stored as a contiguous array of nodes +//! within the ClipStore. +//! +//! ```ascii +//! +-----------------------+-----------------------+-----------------------+ +//! | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | +//! +-----------------------+-----------------------+-----------------------+ +//! | ClipItem | ClipItem | ClipItem | +//! | Spatial Node Index | Spatial Node Index | Spatial Node Index | +//! | GPU cache handle | GPU cache handle | GPU cache handle | +//! | ... | ... | ... | +//! +-----------------------+-----------------------+-----------------------+ +//! 0 1 2 +//! +----------------+ | | +//! | ClipNodeRange |____| | +//! | index: 1 | | +//! | count: 2 |___________________________________________________| +//! +----------------+ +//! ``` +//! +//! - ClipNodeRange - A clip item range identifies a range of clip nodes instances. +//! It is stored as an (index, count). +//! +//! - ClipChainNode - A clip chain node contains a handle to an interned clip item, +//! positioning information (from where the clip was defined), and +//! an optional parent link to another ClipChainNode. ClipChainId +//! is an index into an array, or ClipChainId::NONE for no parent. +//! +//! ```ascii +//! +----------------+ ____+----------------+ ____+----------------+ /---> ClipChainId::NONE +//! | ClipChainNode | | | ClipChainNode | | | ClipChainNode | | +//! +----------------+ | +----------------+ | +----------------+ | +//! | ClipDataHandle | | | ClipDataHandle | | | ClipDataHandle | | +//! | Spatial index | | | Spatial index | | | Spatial index | | +//! | Parent Id |___| | Parent Id |___| | Parent Id |___| +//! | ... | | ... | | ... | +//! +----------------+ +----------------+ +----------------+ +//! ``` +//! +//! - ClipChainInstance - A ClipChain that has been built for a specific primitive + positioning node. +//! +//! When given a clip chain ID, and a local primitive rect and its spatial node, the clip module +//! creates a clip chain instance. This is a struct with various pieces of useful information +//! (such as a local clip rect). It also contains a (index, count) +//! range specifier into an index buffer of the ClipNodeInstance structures that are actually relevant +//! for this clip chain instance. The index buffer structure allows a single array to be used for +//! all of the clip-chain instances built in a single frame. Each entry in the index buffer +//! also stores some flags relevant to the clip node in this positioning context. +//! +//! ```ascii +//! +----------------------+ +//! | ClipChainInstance | +//! +----------------------+ +//! | ... | +//! | local_clip_rect |________________________________________________________________________ +//! | clips_range |_______________ | +//! +----------------------+ | | +//! | | +//! +------------------+------------------+------------------+------------------+------------------+ +//! | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | +//! +------------------+------------------+------------------+------------------+------------------+ +//! | flags | flags | flags | flags | flags | +//! | ... | ... | ... | ... | ... | +//! +------------------+------------------+------------------+------------------+------------------+ +//! ``` +//! +//! # Rendering clipped primitives +//! +//! See the [`segment` module documentation][segment.rs]. +//! +//! +//! [segment.rs]: ../segment/index.html +//! + +use api::{BorderRadius, ClipIntern, ClipMode, ComplexClipRegion, ImageMask}; +use api::{BoxShadowClipMode, ClipId, ImageKey, ImageRendering, PipelineId}; +use api::units::*; +use api::image_tiling::{self, Repetition}; +use crate::border::{ensure_no_corner_overlap, BorderRadiusAu}; +use crate::box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey}; +use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX, SpatialTree, SpatialNodeIndex, CoordinateSystemId}; +use crate::ellipse::Ellipse; +use crate::gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks}; +use crate::gpu_types::{BoxShadowStretchMode}; +use crate::intern::{self, ItemUid}; +use crate::internal_types::{FastHashMap, FastHashSet}; +use crate::prim_store::{ClipData, ImageMaskData, SpaceMapper, VisibleMaskImageTile}; +use crate::prim_store::{PointKey, SizeKey, RectangleKey}; +use crate::render_task_cache::to_cache_size; +use crate::resource_cache::{ImageRequest, ResourceCache}; +use crate::util::{extract_inner_rect_safe, project_rect, ScaleOffset}; +use euclid::approxeq::ApproxEq; +use std::{iter, ops, u32}; +use smallvec::SmallVec; + +// Type definitions for interning clip nodes. + +pub type ClipDataStore = intern::DataStore; +pub type ClipDataHandle = intern::Handle; + +/// Defines a clip that is positioned by a specific spatial node +#[cfg_attr(feature = "capture", derive(Serialize))] +#[derive(Copy, Clone)] +pub struct ClipInstance { + /// Handle to the interned clip + pub handle: ClipDataHandle, + /// Positioning node for this clip + pub spatial_node_index: SpatialNodeIndex, +} + +impl ClipInstance { + /// Construct a new positioned clip + pub fn new( + handle: ClipDataHandle, + spatial_node_index: SpatialNodeIndex, + ) -> Self { + ClipInstance { + handle, + spatial_node_index, + } + } +} + +/// A clip template defines clips in terms of the public API. Specifically, +/// this is a parent `ClipId` and some number of clip instances. See the +/// CLIPPING_AND_POSITIONING.md document in doc/ for more information. +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct ClipTemplate { + /// Parent of this clip, in terms of the public clip API + pub parent: ClipId, + /// List of instances that define this clip template + pub instances: SmallVec<[ClipInstance; 2]>, +} + +/// A helper used during scene building to construct (internal) clip chains from +/// the public API definitions (a hierarchy of ClipIds) +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct ClipChainBuilder { + /// The built clip chain id for this level of the stack + clip_chain_id: ClipChainId, + /// A list of parent clips in the current clip chain, to de-duplicate clips as + /// we build child chains from this level. + parent_clips: FastHashSet<(ItemUid, SpatialNodeIndex)>, + /// A cache used during building child clip chains. Retained here to avoid + /// extra memory allocations each time we build a clip. + existing_clips_cache: FastHashSet<(ItemUid, SpatialNodeIndex)>, + /// Cache the previous ClipId we built, since it's quite common to share clip + /// id between primitives. + prev_clip_id: ClipId, + prev_clip_chain_id: ClipChainId, +} + +impl ClipChainBuilder { + /// Construct a new clip chain builder with specified parent clip chain. If + /// the clip_id is Some(..), the clips in that template will be added to the + /// clip chain at this level (this functionality isn't currently used, but will + /// be in the follow up patches). + fn new( + parent_clip_chain_id: ClipChainId, + clip_id: Option, + clip_chain_nodes: &mut Vec, + templates: &FastHashMap, + ) -> Self { + let mut parent_clips = FastHashSet::default(); + + // Walk the current clip chain ID, building a set of existing clips + let mut current_clip_chain_id = parent_clip_chain_id; + while current_clip_chain_id != ClipChainId::NONE { + let clip_chain_node = &clip_chain_nodes[current_clip_chain_id.0 as usize]; + parent_clips.insert((clip_chain_node.handle.uid(), clip_chain_node.spatial_node_index)); + current_clip_chain_id = clip_chain_node.parent_clip_chain_id; + } + + // If specified, add the clips from the supplied template to this builder + let clip_chain_id = match clip_id { + Some(clip_id) => { + ClipChainBuilder::add_new_clips_to_chain( + clip_id, + parent_clip_chain_id, + &mut parent_clips, + clip_chain_nodes, + templates, + ) + } + None => { + ClipChainId::NONE + } + }; + + ClipChainBuilder { + clip_chain_id, + existing_clips_cache: parent_clips.clone(), + parent_clips, + prev_clip_id: ClipId::root(PipelineId::dummy()), + prev_clip_chain_id: ClipChainId::NONE, + } + } + + /// Internal helper function that appends all clip instances from a template + /// to a clip-chain (if they don't already exist in this chain). + fn add_new_clips_to_chain( + clip_id: ClipId, + parent_clip_chain_id: ClipChainId, + existing_clips: &mut FastHashSet<(ItemUid, SpatialNodeIndex)>, + clip_chain_nodes: &mut Vec, + templates: &FastHashMap, + ) -> ClipChainId { + let template = &templates[&clip_id]; + let mut clip_chain_id = parent_clip_chain_id; + + for clip in &template.instances { + let key = (clip.handle.uid(), clip.spatial_node_index); + + // If this clip chain already has this clip instance, skip it + if existing_clips.contains(&key) { + continue; + } + + // Create a new clip-chain entry for this instance + let new_clip_chain_id = ClipChainId(clip_chain_nodes.len() as u32); + existing_clips.insert(key); + clip_chain_nodes.push(ClipChainNode { + handle: clip.handle, + spatial_node_index: clip.spatial_node_index, + parent_clip_chain_id: clip_chain_id, + }); + clip_chain_id = new_clip_chain_id; + } + + // The ClipId parenting is terminated when we reach the root ClipId + if clip_id == template.parent { + return clip_chain_id; + } + + ClipChainBuilder::add_new_clips_to_chain( + template.parent, + clip_chain_id, + existing_clips, + clip_chain_nodes, + templates, + ) + } + + /// This is the main method used to get a clip chain for a primitive. Given a + /// clip id, it builds a clip-chain for that primitive, parented to the current + /// root clip chain hosted in this builder. + fn get_or_build_clip_chain_id( + &mut self, + clip_id: ClipId, + clip_chain_nodes: &mut Vec, + templates: &FastHashMap, + ) -> ClipChainId { + if self.prev_clip_id == clip_id { + return self.prev_clip_chain_id; + } + + // Instead of cloning here, do a clear and manual insertions, to + // avoid any extra heap allocations each time we build a clip-chain here. + // Maybe there is a better way to do this? + self.existing_clips_cache.clear(); + self.existing_clips_cache.reserve(self.parent_clips.len()); + for clip in &self.parent_clips { + self.existing_clips_cache.insert(*clip); + } + + let clip_chain_id = ClipChainBuilder::add_new_clips_to_chain( + clip_id, + self.clip_chain_id, + &mut self.existing_clips_cache, + clip_chain_nodes, + templates, + ); + + self.prev_clip_id = clip_id; + self.prev_clip_chain_id = clip_chain_id; + + clip_chain_id + } +} + +/// Helper to identify simple clips (normal rects) from other kinds of clips, +/// which can often be handled via fast code paths. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Copy, Clone, MallocSizeOf)] +pub enum ClipNodeKind { + /// A normal clip rectangle, with Clip mode. + Rectangle, + /// A rectangle with ClipOut, or any other kind of clip. + Complex, +} + +// Result of comparing a clip node instance against a local rect. +#[derive(Debug)] +enum ClipResult { + // The clip does not affect the region at all. + Accept, + // The clip prevents the region from being drawn. + Reject, + // The clip affects part of the region. This may + // require a clip mask, depending on other factors. + Partial, +} + +// A clip node is a single clip source, along with some +// positioning information and implementation details +// that control where the GPU data for this clip source +// can be found. +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct ClipNode { + pub item: ClipItem, + pub gpu_cache_handle: GpuCacheHandle, +} + +// Convert from an interning key for a clip item +// to a clip node, which is cached in the document. +impl From for ClipNode { + fn from(item: ClipItemKey) -> Self { + let kind = match item.kind { + ClipItemKeyKind::Rectangle(rect, mode) => { + ClipItemKind::Rectangle { rect: rect.into(), mode } + } + ClipItemKeyKind::RoundedRectangle(rect, radius, mode) => { + ClipItemKind::RoundedRectangle { + rect: rect.into(), + radius: radius.into(), + mode, + } + } + ClipItemKeyKind::ImageMask(rect, image, repeat) => { + ClipItemKind::Image { + image, + rect: rect.into(), + repeat, + } + } + ClipItemKeyKind::BoxShadow(shadow_rect_fract_offset, shadow_rect_size, shadow_radius, prim_shadow_rect, blur_radius, clip_mode) => { + ClipItemKind::new_box_shadow( + shadow_rect_fract_offset.into(), + shadow_rect_size.into(), + shadow_radius.into(), + prim_shadow_rect.into(), + blur_radius.to_f32_px(), + clip_mode, + ) + } + }; + + ClipNode { + item: ClipItem { + kind, + }, + gpu_cache_handle: GpuCacheHandle::new(), + } + } +} + +// Flags that are attached to instances of clip nodes. +bitflags! { + #[cfg_attr(feature = "capture", derive(Serialize))] + #[cfg_attr(feature = "replay", derive(Deserialize))] + #[derive(MallocSizeOf)] + pub struct ClipNodeFlags: u8 { + const SAME_SPATIAL_NODE = 0x1; + const SAME_COORD_SYSTEM = 0x2; + const USE_FAST_PATH = 0x4; + } +} + +// Identifier for a clip chain. Clip chains are stored +// in a contiguous array in the clip store. They are +// identified by a simple index into that array. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct ClipChainId(pub u32); + +// The root of each clip chain is the NONE id. The +// value is specifically set to u32::MAX so that if +// any code accidentally tries to access the root +// node, a bounds error will occur. +impl ClipChainId { + pub const NONE: Self = ClipChainId(u32::MAX); + pub const INVALID: Self = ClipChainId(0xDEADBEEF); +} + +// A clip chain node is an id for a range of clip sources, +// and a link to a parent clip chain node, or ClipChainId::NONE. +#[derive(Clone, Debug, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct ClipChainNode { + pub handle: ClipDataHandle, + pub spatial_node_index: SpatialNodeIndex, + pub parent_clip_chain_id: ClipChainId, +} + +// When a clip node is found to be valid for a +// clip chain instance, it's stored in an index +// buffer style structure. This struct contains +// an index to the node data itself, as well as +// some flags describing how this clip node instance +// is positioned. +#[derive(Debug, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ClipNodeInstance { + pub handle: ClipDataHandle, + pub spatial_node_index: SpatialNodeIndex, + pub flags: ClipNodeFlags, + pub visible_tiles: Option>, +} + +// A range of clip node instances that were found by +// building a clip chain instance. +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ClipNodeRange { + pub first: u32, + pub count: u32, +} + +impl ClipNodeRange { + pub fn to_range(&self) -> ops::Range { + let start = self.first as usize; + let end = start + self.count as usize; + + ops::Range { + start, + end, + } + } +} + +/// A helper struct for converting between coordinate systems +/// of clip sources and primitives. +// todo(gw): optimize: +// separate arrays for matrices +// cache and only build as needed. +//TODO: merge with `CoordinateSpaceMapping`? +#[derive(Debug, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +enum ClipSpaceConversion { + Local, + ScaleOffset(ScaleOffset), + Transform(LayoutToWorldTransform), +} + +impl ClipSpaceConversion { + /// Construct a new clip space converter between two spatial nodes. + fn new( + prim_spatial_node_index: SpatialNodeIndex, + clip_spatial_node_index: SpatialNodeIndex, + spatial_tree: &SpatialTree, + ) -> Self { + //Note: this code is different from `get_relative_transform` in a way that we only try + // getting the relative transform if it's Local or ScaleOffset, + // falling back to the world transform otherwise. + let clip_spatial_node = &spatial_tree + .spatial_nodes[clip_spatial_node_index.0 as usize]; + let prim_spatial_node = &spatial_tree + .spatial_nodes[prim_spatial_node_index.0 as usize]; + + if prim_spatial_node_index == clip_spatial_node_index { + ClipSpaceConversion::Local + } else if prim_spatial_node.coordinate_system_id == clip_spatial_node.coordinate_system_id { + let scale_offset = prim_spatial_node.content_transform + .inverse() + .accumulate(&clip_spatial_node.content_transform); + ClipSpaceConversion::ScaleOffset(scale_offset) + } else { + ClipSpaceConversion::Transform( + spatial_tree + .get_world_transform(clip_spatial_node_index) + .into_transform() + ) + } + } + + fn to_flags(&self) -> ClipNodeFlags { + match *self { + ClipSpaceConversion::Local => { + ClipNodeFlags::SAME_SPATIAL_NODE | ClipNodeFlags::SAME_COORD_SYSTEM + } + ClipSpaceConversion::ScaleOffset(..) => { + ClipNodeFlags::SAME_COORD_SYSTEM + } + ClipSpaceConversion::Transform(..) => { + ClipNodeFlags::empty() + } + } + } +} + +// Temporary information that is cached and reused +// during building of a clip chain instance. +#[derive(MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +struct ClipNodeInfo { + conversion: ClipSpaceConversion, + handle: ClipDataHandle, + spatial_node_index: SpatialNodeIndex, +} + +impl ClipNodeInfo { + fn create_instance( + &self, + node: &ClipNode, + clipped_rect: &LayoutRect, + gpu_cache: &mut GpuCache, + resource_cache: &mut ResourceCache, + spatial_tree: &SpatialTree, + request_resources: bool, + ) -> Option { + // Calculate some flags that are required for the segment + // building logic. + let mut flags = self.conversion.to_flags(); + + // Some clip shaders support a fast path mode for simple clips. + // TODO(gw): We could also apply fast path when segments are created, since we only write + // the mask for a single corner at a time then, so can always consider radii uniform. + let is_raster_2d = + flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) || + spatial_tree + .get_world_viewport_transform(self.spatial_node_index) + .is_2d_axis_aligned(); + if is_raster_2d && node.item.kind.supports_fast_path_rendering() { + flags |= ClipNodeFlags::USE_FAST_PATH; + } + + let mut visible_tiles = None; + + if let ClipItemKind::Image { rect, image, repeat } = node.item.kind { + let request = ImageRequest { + key: image, + rendering: ImageRendering::Auto, + tile: None, + }; + + if let Some(props) = resource_cache.get_image_properties(image) { + if let Some(tile_size) = props.tiling { + let mut mask_tiles = Vec::new(); + + let visible_rect = if repeat { + *clipped_rect + } else { + clipped_rect.intersection(&rect).unwrap() + }; + + let repetitions = image_tiling::repetitions( + &rect, + &visible_rect, + rect.size, + ); + + for Repetition { origin, .. } in repetitions { + let layout_image_rect = LayoutRect { + origin, + size: rect.size, + }; + let tiles = image_tiling::tiles( + &layout_image_rect, + &visible_rect, + &props.visible_rect, + tile_size as i32, + ); + for tile in tiles { + if request_resources { + resource_cache.request_image( + request.with_tile(tile.offset), + gpu_cache, + ); + } + mask_tiles.push(VisibleMaskImageTile { + tile_offset: tile.offset, + tile_rect: tile.rect, + }); + } + } + visible_tiles = Some(mask_tiles); + } else if request_resources { + resource_cache.request_image(request, gpu_cache); + } + } else { + // If the supplied image key doesn't exist in the resource cache, + // skip the clip node since there is nothing to mask with. + warn!("Clip mask with missing image key {:?}", request.key); + return None; + } + } + + Some(ClipNodeInstance { + handle: self.handle, + flags, + visible_tiles, + spatial_node_index: self.spatial_node_index, + }) + } +} + +impl ClipNode { + pub fn update( + &mut self, + gpu_cache: &mut GpuCache, + device_pixel_scale: DevicePixelScale, + ) { + match self.item.kind { + ClipItemKind::Image { rect, .. } => { + if let Some(request) = gpu_cache.request(&mut self.gpu_cache_handle) { + let data = ImageMaskData { + local_mask_size: rect.size, + }; + data.write_gpu_blocks(request); + } + } + ClipItemKind::BoxShadow { ref mut source } => { + if let Some(mut request) = gpu_cache.request(&mut self.gpu_cache_handle) { + request.push([ + source.original_alloc_size.width, + source.original_alloc_size.height, + source.clip_mode as i32 as f32, + 0.0, + ]); + request.push([ + source.stretch_mode_x as i32 as f32, + source.stretch_mode_y as i32 as f32, + 0.0, + 0.0, + ]); + request.push(source.prim_shadow_rect); + } + + // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur + // "the image that would be generated by applying to the shadow a + // Gaussian blur with a standard deviation equal to half the blur radius." + let blur_radius_dp = source.blur_radius * 0.5; + + // Create scaling from requested size to cache size. + let content_scale = LayoutToWorldScale::new(1.0) * device_pixel_scale; + + // Create the cache key for this box-shadow render task. + let cache_size = to_cache_size(source.shadow_rect_alloc_size * content_scale); + let bs_cache_key = BoxShadowCacheKey { + blur_radius_dp: (blur_radius_dp * content_scale.0).round() as i32, + clip_mode: source.clip_mode, + original_alloc_size: (source.original_alloc_size * content_scale).round().to_i32(), + br_top_left: (source.shadow_radius.top_left * content_scale).round().to_i32(), + br_top_right: (source.shadow_radius.top_right * content_scale).round().to_i32(), + br_bottom_right: (source.shadow_radius.bottom_right * content_scale).round().to_i32(), + br_bottom_left: (source.shadow_radius.bottom_left * content_scale).round().to_i32(), + }; + + source.cache_key = Some((cache_size, bs_cache_key)); + + if let Some(mut request) = gpu_cache.request(&mut source.clip_data_handle) { + let data = ClipData::rounded_rect( + source.minimal_shadow_rect.size, + &source.shadow_radius, + ClipMode::Clip, + ); + + data.write(&mut request); + } + } + ClipItemKind::Rectangle { rect, mode } => { + if let Some(mut request) = gpu_cache.request(&mut self.gpu_cache_handle) { + let data = ClipData::uniform(rect.size, 0.0, mode); + data.write(&mut request); + } + } + ClipItemKind::RoundedRectangle { rect, ref radius, mode } => { + if let Some(mut request) = gpu_cache.request(&mut self.gpu_cache_handle) { + let data = ClipData::rounded_rect(rect.size, radius, mode); + data.write(&mut request); + } + } + } + } +} + +/// The main clipping public interface that other modules access. +#[derive(MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct ClipStore { + pub clip_chain_nodes: Vec, + pub clip_node_instances: Vec, + + active_clip_node_info: Vec, + active_local_clip_rect: Option, + + // No malloc sizeof since it's not implemented for ops::Range, but these + // allocations are tiny anyway. + + /// Map of all clip templates defined by the public API to templates + #[ignore_malloc_size_of = "range missing"] + templates: FastHashMap, + + /// A stack of current clip-chain builders. A new clip-chain builder is + /// typically created each time a clip root (such as an iframe or stacking + /// context) is defined. + #[ignore_malloc_size_of = "range missing"] + chain_builder_stack: Vec, +} + +// A clip chain instance is what gets built for a given clip +// chain id + local primitive region + positioning node. +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct ClipChainInstance { + pub clips_range: ClipNodeRange, + // Combined clip rect for clips that are in the + // same coordinate system as the primitive. + pub local_clip_rect: LayoutRect, + pub has_non_local_clips: bool, + // If true, this clip chain requires allocation + // of a clip mask. + pub needs_mask: bool, + // Combined clip rect in picture space (may + // be more conservative that local_clip_rect). + pub pic_clip_rect: PictureRect, + // Space, in which the `pic_clip_rect` is defined. + pub pic_spatial_node_index: SpatialNodeIndex, +} + +impl ClipChainInstance { + pub fn empty() -> Self { + ClipChainInstance { + clips_range: ClipNodeRange { + first: 0, + count: 0, + }, + local_clip_rect: LayoutRect::zero(), + has_non_local_clips: false, + needs_mask: false, + pic_clip_rect: PictureRect::zero(), + pic_spatial_node_index: ROOT_SPATIAL_NODE_INDEX, + } + } +} + +/// Maintains a (flattened) list of clips for a given level in the surface level stack. +pub struct ClipChainLevel { + /// These clips will be handled when compositing this surface into the parent, + /// and can thus be ignored on the primitives that are drawn as part of this surface. + shared_clips: Vec, + + /// Index of the first element in ClipChainStack::clip that belongs to this level. + first_clip_index: usize, + /// Used to sanity check push/pop balance. + initial_clip_counts_len: usize, +} + +/// Maintains a stack of clip chain ids that are currently active, +/// when a clip exists on a picture that has no surface, and is passed +/// on down to the child primitive(s). +/// +/// +/// In order to avoid many small vector allocations, all clip chain ids are +/// stored in a single vector instead of per-level. +/// Since we only work with the top-most level of the stack, we only need to +/// know the first index in the clips vector that belongs to each level. The +/// last index for the top-most level is always the end of the clips array. +/// +/// Likewise, we push several clip chain ids to the clips array at each +/// push_clip, and the number of clip chain ids removed during pop_clip +/// must match. This is done by having a separate stack of clip counts +/// in the clip-stack rather than per-level to avoid vector allocations. +/// +/// ```ascii +/// +----+----+--- +/// levels: | | | ... +/// +----+----+--- +/// |first \ +/// | \ +/// | \ +/// +--+--+--+--+--+--+-- +/// clips: | | | | | | | ... +/// +--+--+--+--+--+--+-- +/// | / / +/// | / / +/// | / / +/// +--+--+--+-- +/// clip_counts: | 1| 2| 2| ... +/// +--+--+--+-- +/// ``` +pub struct ClipChainStack { + /// A stack of clip chain lists. Each time a new surface is pushed, + /// a new level is added. Each time a new picture without surface is + /// pushed, it adds the picture clip chain to the clips vector in the + /// range belonging to the level (always the top-most level, so always + /// at the end of the clips array). + levels: Vec, + /// The actual stack of clip ids. + clips: Vec, + /// How many clip ids to pop from the vector each time we call pop_clip. + clip_counts: Vec, +} + +impl ClipChainStack { + pub fn new() -> Self { + ClipChainStack { + levels: vec![ + ClipChainLevel { + shared_clips: Vec::new(), + first_clip_index: 0, + initial_clip_counts_len: 0, + } + ], + clips: Vec::new(), + clip_counts: Vec::new(), + } + } + + /// Push a clip chain root onto the currently active list. + pub fn push_clip( + &mut self, + clip_chain_id: ClipChainId, + clip_store: &ClipStore, + ) { + let mut clip_count = 0; + + let mut current_clip_chain_id = clip_chain_id; + while current_clip_chain_id != ClipChainId::NONE { + let clip_chain_node = &clip_store.clip_chain_nodes[current_clip_chain_id.0 as usize]; + let clip_uid = clip_chain_node.handle.uid(); + + // The clip is required, so long as it doesn't exist in any of the shared_clips + // array from this or any parent surfaces. + // TODO(gw): We could consider making this a HashSet if it ever shows up in + // profiles, but the typical array length is 2-3 elements. + let mut valid_clip = true; + for level in &self.levels { + if level.shared_clips.iter().any(|instance| { + instance.handle.uid() == clip_uid && + instance.spatial_node_index == clip_chain_node.spatial_node_index + }) { + valid_clip = false; + break; + } + } + + if valid_clip { + self.clips.push(current_clip_chain_id); + clip_count += 1; + } + + current_clip_chain_id = clip_chain_node.parent_clip_chain_id; + } + + self.clip_counts.push(clip_count); + } + + /// Pop a clip chain root from the currently active list. + pub fn pop_clip(&mut self) { + let count = self.clip_counts.pop().unwrap(); + for _ in 0 .. count { + self.clips.pop().unwrap(); + } + } + + /// When a surface is created, it takes all clips and establishes a new + /// stack of clips to be propagated. + pub fn push_surface( + &mut self, + maybe_shared_clips: &[ClipInstance], + spatial_tree: &SpatialTree, + ) { + let mut shared_clips = Vec::new(); + + // If there are clips in the shared list for a picture cache, only include + // them if they are simple, axis-aligned clips (i.e. in the root coordinate + // system). This is necessary since when compositing picture cache tiles + // into the parent, we don't support applying a clip mask. This only ever + // occurs in wrench tests, not in display lists supplied by Gecko. + // TODO(gw): We can remove this when we update the WR API to have better + // knowledge of what coordinate system a clip must be in (by + // knowing if a reference frame exists in the chain between the + // clip's spatial node and the picture cache reference spatial node). + for clip in maybe_shared_clips { + let spatial_node = &spatial_tree.spatial_nodes[clip.spatial_node_index.0 as usize]; + if spatial_node.coordinate_system_id == CoordinateSystemId::root() { + shared_clips.push(*clip); + } + } + + let level = ClipChainLevel { + shared_clips: shared_clips.to_vec(), + first_clip_index: self.clips.len(), + initial_clip_counts_len: self.clip_counts.len(), + }; + + self.levels.push(level); + } + + /// Pop a surface from the clip chain stack + pub fn pop_surface(&mut self) { + let level = self.levels.pop().unwrap(); + assert!(self.clip_counts.len() == level.initial_clip_counts_len); + assert!(self.clips.len() == level.first_clip_index); + } + + /// Get the list of currently active clip chains + pub fn current_clips_array(&self) -> &[ClipChainId] { + let first = self.levels.last().unwrap().first_clip_index; + &self.clips[first..] + } +} + +impl ClipStore { + pub fn new() -> Self { + ClipStore { + clip_chain_nodes: Vec::new(), + clip_node_instances: Vec::new(), + active_clip_node_info: Vec::new(), + active_local_clip_rect: None, + templates: FastHashMap::default(), + chain_builder_stack: Vec::new(), + } + } + + /// Register a new clip template for the clip_id defined in the display list. + pub fn register_clip_template( + &mut self, + clip_id: ClipId, + parent: ClipId, + instances: &[ClipInstance], + ) { + self.templates.insert(clip_id, ClipTemplate { + parent, + instances: instances.into(), + }); + } + + pub fn get_template( + &self, + clip_id: ClipId, + ) -> &ClipTemplate { + &self.templates[&clip_id] + } + + /// The main method used to build a clip-chain for a given ClipId on a primitive + pub fn get_or_build_clip_chain_id( + &mut self, + clip_id: ClipId, + ) -> ClipChainId { + // TODO(gw): If many primitives reference the same ClipId, it might be worth + // maintaining a hash map cache of ClipId -> ClipChainId in each + // ClipChainBuilder + + self.chain_builder_stack + .last_mut() + .unwrap() + .get_or_build_clip_chain_id( + clip_id, + &mut self.clip_chain_nodes, + &self.templates, + ) + } + + /// Push a new clip root. This is used at boundaries of clips (such as iframes + /// and stacking contexts). This means that any clips on the existing clip + /// chain builder will not be added to clip-chains defined within this level, + /// since the clips will be applied by the parent. + pub fn push_clip_root( + &mut self, + clip_id: Option, + link_to_parent: bool, + ) { + let parent_clip_chain_id = if link_to_parent { + self.chain_builder_stack.last().unwrap().clip_chain_id + } else { + ClipChainId::NONE + }; + + let builder = ClipChainBuilder::new( + parent_clip_chain_id, + clip_id, + &mut self.clip_chain_nodes, + &self.templates, + ); + + self.chain_builder_stack.push(builder); + } + + /// On completion of a stacking context or iframe, pop the current clip root. + pub fn pop_clip_root( + &mut self, + ) { + self.chain_builder_stack.pop().unwrap(); + } + + pub fn get_clip_chain(&self, clip_chain_id: ClipChainId) -> &ClipChainNode { + &self.clip_chain_nodes[clip_chain_id.0 as usize] + } + + pub fn add_clip_chain_node( + &mut self, + handle: ClipDataHandle, + spatial_node_index: SpatialNodeIndex, + parent_clip_chain_id: ClipChainId, + ) -> ClipChainId { + let id = ClipChainId(self.clip_chain_nodes.len() as u32); + self.clip_chain_nodes.push(ClipChainNode { + handle, + spatial_node_index, + parent_clip_chain_id, + }); + id + } + + pub fn get_instance_from_range( + &self, + node_range: &ClipNodeRange, + index: u32, + ) -> &ClipNodeInstance { + &self.clip_node_instances[(node_range.first + index) as usize] + } + + /// Setup the active clip chains for building a clip chain instance. + pub fn set_active_clips( + &mut self, + local_prim_clip_rect: LayoutRect, + spatial_node_index: SpatialNodeIndex, + clip_chains: &[ClipChainId], + spatial_tree: &SpatialTree, + clip_data_store: &ClipDataStore, + ) { + self.active_clip_node_info.clear(); + self.active_local_clip_rect = None; + + let mut local_clip_rect = local_prim_clip_rect; + + for clip_chain_id in clip_chains { + let clip_chain_node = &self.clip_chain_nodes[clip_chain_id.0 as usize]; + + if !add_clip_node_to_current_chain( + clip_chain_node, + spatial_node_index, + &mut local_clip_rect, + &mut self.active_clip_node_info, + clip_data_store, + spatial_tree, + ) { + return; + } + } + + self.active_local_clip_rect = Some(local_clip_rect); + } + + /// Setup the active clip chains, based on an existing primitive clip chain instance. + pub fn set_active_clips_from_clip_chain( + &mut self, + prim_clip_chain: &ClipChainInstance, + prim_spatial_node_index: SpatialNodeIndex, + spatial_tree: &SpatialTree, + ) { + // TODO(gw): Although this does less work than set_active_clips(), it does + // still do some unnecessary work (such as the clip space conversion). + // We could consider optimizing this if it ever shows up in a profile. + + self.active_clip_node_info.clear(); + self.active_local_clip_rect = Some(prim_clip_chain.local_clip_rect); + + let clip_instances = &self + .clip_node_instances[prim_clip_chain.clips_range.to_range()]; + for clip_instance in clip_instances { + let conversion = ClipSpaceConversion::new( + prim_spatial_node_index, + clip_instance.spatial_node_index, + spatial_tree, + ); + self.active_clip_node_info.push(ClipNodeInfo { + handle: clip_instance.handle, + spatial_node_index: clip_instance.spatial_node_index, + conversion, + }); + } + } + + /// The main interface external code uses. Given a local primitive, positioning + /// information, and a clip chain id, build an optimized clip chain instance. + pub fn build_clip_chain_instance( + &mut self, + local_prim_rect: LayoutRect, + prim_to_pic_mapper: &SpaceMapper, + pic_to_world_mapper: &SpaceMapper, + spatial_tree: &SpatialTree, + gpu_cache: &mut GpuCache, + resource_cache: &mut ResourceCache, + device_pixel_scale: DevicePixelScale, + world_rect: &WorldRect, + clip_data_store: &mut ClipDataStore, + request_resources: bool, + is_chased: bool, + ) -> Option { + let local_clip_rect = match self.active_local_clip_rect { + Some(rect) => rect, + None => return None, + }; + profile_scope!("build_clip_chain_instance"); + if is_chased { + println!("\tbuilding clip chain instance with local rect {:?}", local_prim_rect); + } + + let local_bounding_rect = local_prim_rect.intersection(&local_clip_rect)?; + let pic_clip_rect = prim_to_pic_mapper.map(&local_bounding_rect)?; + let world_clip_rect = pic_to_world_mapper.map(&pic_clip_rect)?; + + // Now, we've collected all the clip nodes that *potentially* affect this + // primitive region, and reduced the size of the prim region as much as possible. + + // Run through the clip nodes, and see which ones affect this prim region. + + let first_clip_node_index = self.clip_node_instances.len() as u32; + let mut has_non_local_clips = false; + let mut needs_mask = false; + + // For each potential clip node + for node_info in self.active_clip_node_info.drain(..) { + let node = &mut clip_data_store[node_info.handle]; + + // See how this clip affects the prim region. + let clip_result = match node_info.conversion { + ClipSpaceConversion::Local => { + node.item.kind.get_clip_result(&local_bounding_rect) + } + ClipSpaceConversion::ScaleOffset(ref scale_offset) => { + has_non_local_clips = true; + node.item.kind.get_clip_result(&scale_offset.unmap_rect(&local_bounding_rect)) + } + ClipSpaceConversion::Transform(ref transform) => { + has_non_local_clips = true; + node.item.kind.get_clip_result_complex( + transform, + &world_clip_rect, + world_rect, + ) + } + }; + + if is_chased { + println!("\t\tclip {:?}", node.item); + println!("\t\tflags {:?}, resulted in {:?}", node_info.conversion.to_flags(), clip_result); + } + + match clip_result { + ClipResult::Accept => { + // Doesn't affect the primitive at all, so skip adding to list + } + ClipResult::Reject => { + // Completely clips the supplied prim rect + return None; + } + ClipResult::Partial => { + // Needs a mask -> add to clip node indices + + // TODO(gw): Ensure this only runs once on each node per frame? + node.update( + gpu_cache, + device_pixel_scale, + ); + + // Create the clip node instance for this clip node + if let Some(instance) = node_info.create_instance( + node, + &local_bounding_rect, + gpu_cache, + resource_cache, + spatial_tree, + request_resources, + ) { + // As a special case, a partial accept of a clip rect that is + // in the same coordinate system as the primitive doesn't need + // a clip mask. Instead, it can be handled by the primitive + // vertex shader as part of the local clip rect. This is an + // important optimization for reducing the number of clip + // masks that are allocated on common pages. + needs_mask |= match node.item.kind { + ClipItemKind::Rectangle { mode: ClipMode::ClipOut, .. } | + ClipItemKind::RoundedRectangle { .. } | + ClipItemKind::Image { .. } | + ClipItemKind::BoxShadow { .. } => { + true + } + + ClipItemKind::Rectangle { mode: ClipMode::Clip, .. } => { + !instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) + } + }; + + // Store this in the index buffer for this clip chain instance. + self.clip_node_instances.push(instance); + } + } + } + } + + // Get the range identifying the clip nodes in the index buffer. + let clips_range = ClipNodeRange { + first: first_clip_node_index, + count: self.clip_node_instances.len() as u32 - first_clip_node_index, + }; + + // Return a valid clip chain instance + Some(ClipChainInstance { + clips_range, + has_non_local_clips, + local_clip_rect, + pic_clip_rect, + pic_spatial_node_index: prim_to_pic_mapper.ref_spatial_node_index, + needs_mask, + }) + } + + pub fn clear_old_instances(&mut self) { + self.clip_node_instances.clear(); + } +} + +pub struct ComplexTranslateIter { + source: I, + offset: LayoutVector2D, +} + +impl> Iterator for ComplexTranslateIter { + type Item = ComplexClipRegion; + fn next(&mut self) -> Option { + self.source + .next() + .map(|mut complex| { + complex.rect = complex.rect.translate(self.offset); + complex + }) + } +} + +#[derive(Clone, Debug)] +pub struct ClipRegion { + pub main: LayoutRect, + pub complex_clips: I, +} + +impl ClipRegion> { + pub fn create_for_clip_node( + rect: LayoutRect, + complex_clips: J, + reference_frame_relative_offset: &LayoutVector2D, + ) -> Self + where + J: Iterator + { + ClipRegion { + main: rect.translate(*reference_frame_relative_offset), + complex_clips: ComplexTranslateIter { + source: complex_clips, + offset: *reference_frame_relative_offset, + }, + } + } +} + +impl ClipRegion> { + pub fn create_for_clip_node_with_local_clip( + local_clip: &LayoutRect, + reference_frame_relative_offset: &LayoutVector2D + ) -> Self { + ClipRegion { + main: local_clip.translate(*reference_frame_relative_offset), + complex_clips: None, + } + } +} + +// The ClipItemKey is a hashable representation of the contents +// of a clip item. It is used during interning to de-duplicate +// clip nodes between frames and display lists. This allows quick +// comparison of clip node equality by handle, and also allows +// the uploaded GPU cache handle to be retained between display lists. +// TODO(gw): Maybe we should consider constructing these directly +// in the DL builder? +#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum ClipItemKeyKind { + Rectangle(RectangleKey, ClipMode), + RoundedRectangle(RectangleKey, BorderRadiusAu, ClipMode), + ImageMask(RectangleKey, ImageKey, bool), + BoxShadow(PointKey, SizeKey, BorderRadiusAu, RectangleKey, Au, BoxShadowClipMode), +} + +impl ClipItemKeyKind { + pub fn rectangle(rect: LayoutRect, mode: ClipMode) -> Self { + ClipItemKeyKind::Rectangle(rect.into(), mode) + } + + pub fn rounded_rect(rect: LayoutRect, mut radii: BorderRadius, mode: ClipMode) -> Self { + if radii.is_zero() { + ClipItemKeyKind::rectangle(rect, mode) + } else { + ensure_no_corner_overlap(&mut radii, rect.size); + ClipItemKeyKind::RoundedRectangle( + rect.into(), + radii.into(), + mode, + ) + } + } + + pub fn image_mask(image_mask: &ImageMask, mask_rect: LayoutRect) -> Self { + ClipItemKeyKind::ImageMask( + mask_rect.into(), + image_mask.image, + image_mask.repeat, + ) + } + + pub fn box_shadow( + shadow_rect: LayoutRect, + shadow_radius: BorderRadius, + prim_shadow_rect: LayoutRect, + blur_radius: f32, + clip_mode: BoxShadowClipMode, + ) -> Self { + // Get the fractional offsets required to match the + // source rect with a minimal rect. + let fract_offset = LayoutPoint::new( + shadow_rect.origin.x.fract().abs(), + shadow_rect.origin.y.fract().abs(), + ); + + ClipItemKeyKind::BoxShadow( + fract_offset.into(), + shadow_rect.size.into(), + shadow_radius.into(), + prim_shadow_rect.into(), + Au::from_f32_px(blur_radius), + clip_mode, + ) + } + + pub fn node_kind(&self) -> ClipNodeKind { + match *self { + ClipItemKeyKind::Rectangle(_, ClipMode::Clip) => ClipNodeKind::Rectangle, + + ClipItemKeyKind::Rectangle(_, ClipMode::ClipOut) | + ClipItemKeyKind::RoundedRectangle(..) | + ClipItemKeyKind::ImageMask(..) | + ClipItemKeyKind::BoxShadow(..) => ClipNodeKind::Complex, + } + } +} + +#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ClipItemKey { + pub kind: ClipItemKeyKind, +} + +/// The data available about an interned clip node during scene building +#[derive(Debug, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ClipInternData { + /// Whether this is a simple rectangle clip + pub clip_node_kind: ClipNodeKind, +} + +impl intern::InternDebug for ClipItemKey {} + +impl intern::Internable for ClipIntern { + type Key = ClipItemKey; + type StoreData = ClipNode; + type InternData = ClipInternData; +} + +#[derive(Debug, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum ClipItemKind { + Rectangle { + rect: LayoutRect, + mode: ClipMode, + }, + RoundedRectangle { + rect: LayoutRect, + radius: BorderRadius, + mode: ClipMode, + }, + Image { + image: ImageKey, + rect: LayoutRect, + repeat: bool, + }, + BoxShadow { + source: BoxShadowClipSource, + }, +} + +#[derive(Debug, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ClipItem { + pub kind: ClipItemKind, +} + +fn compute_box_shadow_parameters( + shadow_rect_fract_offset: LayoutPoint, + shadow_rect_size: LayoutSize, + mut shadow_radius: BorderRadius, + prim_shadow_rect: LayoutRect, + blur_radius: f32, + clip_mode: BoxShadowClipMode, +) -> BoxShadowClipSource { + // Make sure corners don't overlap. + ensure_no_corner_overlap(&mut shadow_radius, shadow_rect_size); + + let fract_size = LayoutSize::new( + shadow_rect_size.width.fract().abs(), + shadow_rect_size.height.fract().abs(), + ); + + // Create a minimal size primitive mask to blur. In this + // case, we ensure the size of each corner is the same, + // to simplify the shader logic that stretches the blurred + // result across the primitive. + let max_corner_width = shadow_radius.top_left.width + .max(shadow_radius.bottom_left.width) + .max(shadow_radius.top_right.width) + .max(shadow_radius.bottom_right.width); + let max_corner_height = shadow_radius.top_left.height + .max(shadow_radius.bottom_left.height) + .max(shadow_radius.top_right.height) + .max(shadow_radius.bottom_right.height); + + // Get maximum distance that can be affected by given blur radius. + let blur_region = (BLUR_SAMPLE_SCALE * blur_radius).ceil(); + + // If the largest corner is smaller than the blur radius, we need to ensure + // that it's big enough that the corners don't affect the middle segments. + let used_corner_width = max_corner_width.max(blur_region); + let used_corner_height = max_corner_height.max(blur_region); + + // Minimal nine-patch size, corner + internal + corner. + let min_shadow_rect_size = LayoutSize::new( + 2.0 * used_corner_width + blur_region, + 2.0 * used_corner_height + blur_region, + ); + + // The minimal rect to blur. + let mut minimal_shadow_rect = LayoutRect::new( + LayoutPoint::new( + blur_region + shadow_rect_fract_offset.x, + blur_region + shadow_rect_fract_offset.y, + ), + LayoutSize::new( + min_shadow_rect_size.width + fract_size.width, + min_shadow_rect_size.height + fract_size.height, + ), + ); + + // If the width or height ends up being bigger than the original + // primitive shadow rect, just blur the entire rect along that + // axis and draw that as a simple blit. This is necessary for + // correctness, since the blur of one corner may affect the blur + // in another corner. + let mut stretch_mode_x = BoxShadowStretchMode::Stretch; + if shadow_rect_size.width < minimal_shadow_rect.size.width { + minimal_shadow_rect.size.width = shadow_rect_size.width; + stretch_mode_x = BoxShadowStretchMode::Simple; + } + + let mut stretch_mode_y = BoxShadowStretchMode::Stretch; + if shadow_rect_size.height < minimal_shadow_rect.size.height { + minimal_shadow_rect.size.height = shadow_rect_size.height; + stretch_mode_y = BoxShadowStretchMode::Simple; + } + + // Expand the shadow rect by enough room for the blur to take effect. + let shadow_rect_alloc_size = LayoutSize::new( + 2.0 * blur_region + minimal_shadow_rect.size.width.ceil(), + 2.0 * blur_region + minimal_shadow_rect.size.height.ceil(), + ); + + BoxShadowClipSource { + original_alloc_size: shadow_rect_alloc_size, + shadow_rect_alloc_size, + shadow_radius, + prim_shadow_rect, + blur_radius, + clip_mode, + stretch_mode_x, + stretch_mode_y, + cache_handle: None, + cache_key: None, + clip_data_handle: GpuCacheHandle::new(), + minimal_shadow_rect, + } +} + +impl ClipItemKind { + pub fn new_box_shadow( + shadow_rect_fract_offset: LayoutPoint, + shadow_rect_size: LayoutSize, + mut shadow_radius: BorderRadius, + prim_shadow_rect: LayoutRect, + blur_radius: f32, + clip_mode: BoxShadowClipMode, + ) -> Self { + let mut source = compute_box_shadow_parameters( + shadow_rect_fract_offset, + shadow_rect_size, + shadow_radius, + prim_shadow_rect, + blur_radius, + clip_mode, + ); + + fn needed_downscaling(source: &BoxShadowClipSource) -> Option { + // This size is fairly arbitrary, but it's the same as the size that + // we use to avoid caching big blurred stacking contexts. + // + // If you change it, ensure that the reftests + // box-shadow-large-blur-radius-* still hit the downscaling path, + // and that they render correctly. + const MAX_SIZE: f32 = 2048.; + + let max_dimension = + source.shadow_rect_alloc_size.width.max(source.shadow_rect_alloc_size.height); + + if max_dimension > MAX_SIZE { + Some(MAX_SIZE / max_dimension) + } else { + None + } + } + + if let Some(downscale) = needed_downscaling(&source) { + shadow_radius.bottom_left.height *= downscale; + shadow_radius.bottom_left.width *= downscale; + shadow_radius.bottom_right.height *= downscale; + shadow_radius.bottom_right.width *= downscale; + shadow_radius.top_left.height *= downscale; + shadow_radius.top_left.width *= downscale; + shadow_radius.top_right.height *= downscale; + shadow_radius.top_right.width *= downscale; + + let original_alloc_size = source.shadow_rect_alloc_size; + + source = compute_box_shadow_parameters( + shadow_rect_fract_offset * downscale, + shadow_rect_size * downscale, + shadow_radius, + prim_shadow_rect, + blur_radius * downscale, + clip_mode, + ); + source.original_alloc_size = original_alloc_size; + } + ClipItemKind::BoxShadow { source } + } + + /// Returns true if this clip mask can run through the fast path + /// for the given clip item type. + /// + /// Note: this logic has to match `ClipBatcher::add` behavior. + fn supports_fast_path_rendering(&self) -> bool { + match *self { + ClipItemKind::Rectangle { .. } | + ClipItemKind::Image { .. } | + ClipItemKind::BoxShadow { .. } => { + false + } + ClipItemKind::RoundedRectangle { ref radius, .. } => { + // The rounded clip rect fast path shader can only work + // if the radii are uniform. + radius.is_uniform().is_some() + } + } + } + + // Get an optional clip rect that a clip source can provide to + // reduce the size of a primitive region. This is typically + // used to eliminate redundant clips, and reduce the size of + // any clip mask that eventually gets drawn. + pub fn get_local_clip_rect(&self) -> Option { + match *self { + ClipItemKind::Rectangle { rect, mode: ClipMode::Clip } => Some(rect), + ClipItemKind::Rectangle { mode: ClipMode::ClipOut, .. } => None, + ClipItemKind::RoundedRectangle { rect, mode: ClipMode::Clip, .. } => Some(rect), + ClipItemKind::RoundedRectangle { mode: ClipMode::ClipOut, .. } => None, + ClipItemKind::Image { repeat, rect, .. } => { + if repeat { + None + } else { + Some(rect) + } + } + ClipItemKind::BoxShadow { .. } => None, + } + } + + fn get_clip_result_complex( + &self, + transform: &LayoutToWorldTransform, + prim_world_rect: &WorldRect, + world_rect: &WorldRect, + ) -> ClipResult { + let visible_rect = match prim_world_rect.intersection(world_rect) { + Some(rect) => rect, + None => return ClipResult::Reject, + }; + + let (clip_rect, inner_rect, mode) = match *self { + ClipItemKind::Rectangle { rect, mode } => { + (rect, Some(rect), mode) + } + ClipItemKind::RoundedRectangle { rect, ref radius, mode } => { + let inner_clip_rect = extract_inner_rect_safe(&rect, radius); + (rect, inner_clip_rect, mode) + } + ClipItemKind::Image { rect, repeat: false, .. } => { + (rect, None, ClipMode::Clip) + } + ClipItemKind::Image { repeat: true, .. } | + ClipItemKind::BoxShadow { .. } => { + return ClipResult::Partial; + } + }; + + if let Some(ref inner_clip_rect) = inner_rect { + if let Some(()) = projected_rect_contains(inner_clip_rect, transform, &visible_rect) { + return match mode { + ClipMode::Clip => ClipResult::Accept, + ClipMode::ClipOut => ClipResult::Reject, + }; + } + } + + match mode { + ClipMode::Clip => { + let outer_clip_rect = match project_rect( + transform, + &clip_rect, + world_rect, + ) { + Some(outer_clip_rect) => outer_clip_rect, + None => return ClipResult::Partial, + }; + + match outer_clip_rect.intersection(prim_world_rect) { + Some(..) => { + ClipResult::Partial + } + None => { + ClipResult::Reject + } + } + } + ClipMode::ClipOut => ClipResult::Partial, + } + } + + // Check how a given clip source affects a local primitive region. + fn get_clip_result( + &self, + prim_rect: &LayoutRect, + ) -> ClipResult { + match *self { + ClipItemKind::Rectangle { rect, mode: ClipMode::Clip } => { + if rect.contains_rect(prim_rect) { + return ClipResult::Accept; + } + + match rect.intersection(prim_rect) { + Some(..) => { + ClipResult::Partial + } + None => { + ClipResult::Reject + } + } + } + ClipItemKind::Rectangle { rect, mode: ClipMode::ClipOut } => { + if rect.contains_rect(prim_rect) { + return ClipResult::Reject; + } + + match rect.intersection(prim_rect) { + Some(_) => { + ClipResult::Partial + } + None => { + ClipResult::Accept + } + } + } + ClipItemKind::RoundedRectangle { rect, ref radius, mode: ClipMode::Clip } => { + // TODO(gw): Consider caching this in the ClipNode + // if it ever shows in profiles. + // TODO(gw): extract_inner_rect_safe is overly + // conservative for this code! + let inner_clip_rect = extract_inner_rect_safe(&rect, radius); + if let Some(inner_clip_rect) = inner_clip_rect { + if inner_clip_rect.contains_rect(prim_rect) { + return ClipResult::Accept; + } + } + + match rect.intersection(prim_rect) { + Some(..) => { + ClipResult::Partial + } + None => { + ClipResult::Reject + } + } + } + ClipItemKind::RoundedRectangle { rect, ref radius, mode: ClipMode::ClipOut } => { + // TODO(gw): Consider caching this in the ClipNode + // if it ever shows in profiles. + // TODO(gw): extract_inner_rect_safe is overly + // conservative for this code! + let inner_clip_rect = extract_inner_rect_safe(&rect, radius); + if let Some(inner_clip_rect) = inner_clip_rect { + if inner_clip_rect.contains_rect(prim_rect) { + return ClipResult::Reject; + } + } + + match rect.intersection(prim_rect) { + Some(_) => { + ClipResult::Partial + } + None => { + ClipResult::Accept + } + } + } + ClipItemKind::Image { rect, repeat, .. } => { + if repeat { + ClipResult::Partial + } else { + match rect.intersection(prim_rect) { + Some(..) => { + ClipResult::Partial + } + None => { + ClipResult::Reject + } + } + } + } + ClipItemKind::BoxShadow { .. } => { + ClipResult::Partial + } + } + } +} + +/// Represents a local rect and a device space +/// rectangles that are either outside or inside bounds. +#[derive(Clone, Debug, PartialEq)] +pub struct Geometry { + pub local_rect: LayoutRect, + pub device_rect: DeviceIntRect, +} + +impl From for Geometry { + fn from(local_rect: LayoutRect) -> Self { + Geometry { + local_rect, + device_rect: DeviceIntRect::zero(), + } + } +} + +pub fn rounded_rectangle_contains_point( + point: &LayoutPoint, + rect: &LayoutRect, + radii: &BorderRadius +) -> bool { + if !rect.contains(*point) { + return false; + } + + let top_left_center = rect.origin + radii.top_left.to_vector(); + if top_left_center.x > point.x && top_left_center.y > point.y && + !Ellipse::new(radii.top_left).contains(*point - top_left_center.to_vector()) { + return false; + } + + let bottom_right_center = rect.bottom_right() - radii.bottom_right.to_vector(); + if bottom_right_center.x < point.x && bottom_right_center.y < point.y && + !Ellipse::new(radii.bottom_right).contains(*point - bottom_right_center.to_vector()) { + return false; + } + + let top_right_center = rect.top_right() + + LayoutVector2D::new(-radii.top_right.width, radii.top_right.height); + if top_right_center.x < point.x && top_right_center.y > point.y && + !Ellipse::new(radii.top_right).contains(*point - top_right_center.to_vector()) { + return false; + } + + let bottom_left_center = rect.bottom_left() + + LayoutVector2D::new(radii.bottom_left.width, -radii.bottom_left.height); + if bottom_left_center.x > point.x && bottom_left_center.y < point.y && + !Ellipse::new(radii.bottom_left).contains(*point - bottom_left_center.to_vector()) { + return false; + } + + true +} + +pub fn projected_rect_contains( + source_rect: &LayoutRect, + transform: &LayoutToWorldTransform, + target_rect: &WorldRect, +) -> Option<()> { + let points = [ + transform.transform_point2d(source_rect.origin)?, + transform.transform_point2d(source_rect.top_right())?, + transform.transform_point2d(source_rect.bottom_right())?, + transform.transform_point2d(source_rect.bottom_left())?, + ]; + let target_points = [ + target_rect.origin, + target_rect.top_right(), + target_rect.bottom_right(), + target_rect.bottom_left(), + ]; + // iterate the edges of the transformed polygon + for (a, b) in points + .iter() + .cloned() + .zip(points[1..].iter().cloned().chain(iter::once(points[0]))) + { + // If this edge is redundant, it's a weird, case, and we shouldn't go + // length in trying to take the fast path (e.g. when the whole rectangle is a point). + // If any of edges of the target rectangle crosses the edge, it's not completely + // inside our transformed polygon either. + if a.approx_eq(&b) || target_points.iter().any(|&c| (b - a).cross(c - a) < 0.0) { + return None + } + } + + Some(()) +} + + +// Add a clip node into the list of clips to be processed +// for the current clip chain. Returns false if the clip +// results in the entire primitive being culled out. +fn add_clip_node_to_current_chain( + node: &ClipChainNode, + spatial_node_index: SpatialNodeIndex, + local_clip_rect: &mut LayoutRect, + clip_node_info: &mut Vec, + clip_data_store: &ClipDataStore, + spatial_tree: &SpatialTree, +) -> bool { + let clip_node = &clip_data_store[node.handle]; + + // Determine the most efficient way to convert between coordinate + // systems of the primitive and clip node. + let conversion = ClipSpaceConversion::new( + spatial_node_index, + node.spatial_node_index, + spatial_tree, + ); + + // If we can convert spaces, try to reduce the size of the region + // requested, and cache the conversion information for the next step. + if let Some(clip_rect) = clip_node.item.kind.get_local_clip_rect() { + match conversion { + ClipSpaceConversion::Local => { + *local_clip_rect = match local_clip_rect.intersection(&clip_rect) { + Some(rect) => rect, + None => return false, + }; + } + ClipSpaceConversion::ScaleOffset(ref scale_offset) => { + let clip_rect = scale_offset.map_rect(&clip_rect); + *local_clip_rect = match local_clip_rect.intersection(&clip_rect) { + Some(rect) => rect, + None => return false, + }; + } + ClipSpaceConversion::Transform(..) => { + // TODO(gw): In the future, we can reduce the size + // of the pic_clip_rect here. To do this, + // we can use project_rect or the + // inverse_rect_footprint method, depending + // on the relationship of the clip, pic + // and primitive spatial nodes. + // I have left this for now until we + // find some good test cases where this + // would be a worthwhile perf win. + } + } + } + + clip_node_info.push(ClipNodeInfo { + conversion, + spatial_node_index: node.spatial_node_index, + handle: node.handle, + }); + + true +} + +#[cfg(test)] +mod tests { + use super::projected_rect_contains; + use euclid::{Transform3D, rect}; + + #[test] + fn test_empty_projected_rect() { + assert_eq!( + None, + projected_rect_contains( + &rect(10.0, 10.0, 0.0, 0.0), + &Transform3D::identity(), + &rect(20.0, 20.0, 10.0, 10.0), + ), + "Empty rectangle is considered to include a non-empty!" + ); + } +} diff --git a/third_party/webrender/webrender/src/composite.rs b/third_party/webrender/webrender/src/composite.rs new file mode 100644 index 00000000000..bdc11097689 --- /dev/null +++ b/third_party/webrender/webrender/src/composite.rs @@ -0,0 +1,1007 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{ColorF, YuvColorSpace, YuvFormat, ImageRendering}; +use api::units::{DeviceRect, DeviceIntSize, DeviceIntRect, DeviceIntPoint, WorldRect}; +use api::units::{DevicePixelScale, DevicePoint, PictureRect, TexelRect}; +use crate::batch::{resolve_image, get_buffer_kind}; +use crate::gpu_cache::GpuCache; +use crate::gpu_types::{ZBufferId, ZBufferIdGenerator}; +use crate::internal_types::TextureSource; +use crate::picture::{ImageDependency, ResolvedSurfaceTexture, TileCacheInstance, TileId, TileSurface}; +use crate::prim_store::DeferredResolve; +use crate::renderer::ImageBufferKind; +use crate::resource_cache::{ImageRequest, ResourceCache}; +use std::{ops, u64}; + +/* + Types and definitions related to compositing picture cache tiles + and/or OS compositor integration. + */ + +/// Describes details of an operation to apply to a native surface +#[derive(Debug, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum NativeSurfaceOperationDetails { + CreateSurface { + id: NativeSurfaceId, + virtual_offset: DeviceIntPoint, + tile_size: DeviceIntSize, + is_opaque: bool, + }, + DestroySurface { + id: NativeSurfaceId, + }, + CreateTile { + id: NativeTileId, + }, + DestroyTile { + id: NativeTileId, + } +} + +/// Describes an operation to apply to a native surface +#[derive(Debug, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct NativeSurfaceOperation { + pub details: NativeSurfaceOperationDetails, +} + +/// Describes the source surface information for a tile to be composited. This +/// is the analog of the TileSurface type, with target surface information +/// resolved such that it can be used by the renderer. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum CompositeTileSurface { + Texture { + surface: ResolvedSurfaceTexture, + }, + Color { + color: ColorF, + }, + Clear, + ExternalSurface { + external_surface_index: ResolvedExternalSurfaceIndex, + }, +} + +/// The surface format for a tile being composited. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum CompositeSurfaceFormat { + Rgba, + Yuv, +} + +/// Describes the geometry and surface of a tile to be composited +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct CompositeTile { + pub surface: CompositeTileSurface, + pub rect: DeviceRect, + pub clip_rect: DeviceRect, + pub dirty_rect: DeviceRect, + pub valid_rect: DeviceRect, + pub z_id: ZBufferId, +} + +pub enum ExternalSurfaceDependency { + Yuv { + image_dependencies: [ImageDependency; 3], + color_space: YuvColorSpace, + format: YuvFormat, + rescale: f32, + }, + Rgb { + image_dependency: ImageDependency, + flip_y: bool, + }, +} + +/// Describes information about drawing a primitive as a compositor surface. +/// For now, we support only YUV images as compositor surfaces, but in future +/// this will also support RGBA images. +pub struct ExternalSurfaceDescriptor { + pub local_rect: PictureRect, + pub world_rect: WorldRect, + pub device_rect: DeviceRect, + pub local_clip_rect: PictureRect, + pub clip_rect: DeviceRect, + pub image_rendering: ImageRendering, + pub z_id: ZBufferId, + pub dependency: ExternalSurfaceDependency, + /// If native compositing is enabled, the native compositor surface handle. + /// Otherwise, this will be None + pub native_surface_id: Option, + /// If the native surface needs to be updated, this will contain the size + /// of the native surface as Some(size). If not dirty, this is None. + pub update_params: Option, +} + +/// Information about a plane in a YUV or RGB surface. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Copy, Clone)] +pub struct ExternalPlaneDescriptor { + pub texture: TextureSource, + pub texture_layer: i32, + pub uv_rect: TexelRect, +} + +impl ExternalPlaneDescriptor { + fn invalid() -> Self { + ExternalPlaneDescriptor { + texture: TextureSource::Invalid, + texture_layer: 0, + uv_rect: TexelRect::invalid(), + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Copy, Clone)] +pub struct ResolvedExternalSurfaceIndex(pub usize); + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum ResolvedExternalSurfaceColorData { + Yuv { + // YUV specific information + image_dependencies: [ImageDependency; 3], + planes: [ExternalPlaneDescriptor; 3], + color_space: YuvColorSpace, + format: YuvFormat, + rescale: f32, + }, + Rgb { + image_dependency: ImageDependency, + plane: ExternalPlaneDescriptor, + flip_y: bool, + }, +} + +/// An ExternalSurfaceDescriptor that has had image keys +/// resolved to texture handles. This contains all the +/// information that the compositor step in renderer +/// needs to know. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ResolvedExternalSurface { + pub color_data: ResolvedExternalSurfaceColorData, + pub image_buffer_kind: ImageBufferKind, + // Update information for a native surface if it's dirty + pub update_params: Option<(NativeSurfaceId, DeviceIntSize)>, +} + +/// Public interface specified in `RendererOptions` that configures +/// how WR compositing will operate. +pub enum CompositorConfig { + /// Let WR draw tiles via normal batching. This requires no special OS support. + Draw { + /// If this is zero, a full screen present occurs at the end of the + /// frame. This is the simplest and default mode. If this is non-zero, + /// then the operating system supports a form of 'partial present' where + /// only dirty regions of the framebuffer need to be updated. + max_partial_present_rects: usize, + /// If this is true, WR would draw the previous frame's dirty region when + /// doing a partial present. This is used for EGL which requires the front + /// buffer to always be fully consistent. + draw_previous_partial_present_regions: bool, + }, + /// Use a native OS compositor to draw tiles. This requires clients to implement + /// the Compositor trait, but can be significantly more power efficient on operating + /// systems that support it. + Native { + /// The maximum number of dirty rects that can be provided per compositor + /// surface update. If this is zero, the entire compositor surface for + /// a given tile will be drawn if it's dirty. + max_update_rects: usize, + /// A client provided interface to a native / OS compositor. + compositor: Box, + } +} + +impl CompositorConfig { + pub fn compositor(&mut self) -> Option<&mut Box> { + match self { + CompositorConfig::Native { ref mut compositor, .. } => { + Some(compositor) + } + CompositorConfig::Draw { .. } => { + None + } + } + } +} + +impl Default for CompositorConfig { + /// Default compositor config is full present without partial present. + fn default() -> Self { + CompositorConfig::Draw { + max_partial_present_rects: 0, + draw_previous_partial_present_regions: false, + } + } +} + +/// This is a representation of `CompositorConfig` without the `Compositor` trait +/// present. This allows it to be freely copied to other threads, such as the render +/// backend where the frame builder can access it. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum CompositorKind { + /// WR handles compositing via drawing. + Draw { + /// Partial present support. + max_partial_present_rects: usize, + /// Draw previous regions when doing partial present. + draw_previous_partial_present_regions: bool, + }, + /// Native OS compositor. + Native { + /// Maximum dirty rects per compositor surface. + max_update_rects: usize, + /// The virtual surface size used by underlying platform. + virtual_surface_size: i32, + }, +} + +impl Default for CompositorKind { + /// Default compositor config is full present without partial present. + fn default() -> Self { + CompositorKind::Draw { + max_partial_present_rects: 0, + draw_previous_partial_present_regions: false, + } + } +} + +impl CompositorKind { + pub fn get_virtual_surface_size(&self) -> i32 { + match self { + CompositorKind::Draw { .. } => 0, + CompositorKind::Native { virtual_surface_size, .. } => *virtual_surface_size, + } + } +} + +/// Information about an opaque surface used to occlude tiles. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct Occluder { + z_id: ZBufferId, + device_rect: DeviceIntRect, +} + +/// The backing surface kind for a tile. Same as `TileSurface`, minus +/// the texture cache handles, visibility masks etc. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(PartialEq, Clone)] +pub enum TileSurfaceKind { + Texture, + Color { + color: ColorF, + }, + Clear, +} + +impl From<&TileSurface> for TileSurfaceKind { + fn from(surface: &TileSurface) -> Self { + match surface { + TileSurface::Texture { .. } => TileSurfaceKind::Texture, + TileSurface::Color { color } => TileSurfaceKind::Color { color: *color }, + TileSurface::Clear => TileSurfaceKind::Clear, + } + } +} + +/// Describes properties that identify a tile composition uniquely. +/// The backing surface for this tile. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(PartialEq, Clone)] +pub struct CompositeTileDescriptor { + pub tile_id: TileId, + pub surface_kind: TileSurfaceKind, +} + +/// Describes the properties that identify a surface composition uniquely. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(PartialEq, Clone)] +pub struct CompositeSurfaceDescriptor { + pub surface_id: Option, + pub offset: DevicePoint, + pub clip_rect: DeviceRect, + // A list of image keys and generations that this compositor surface + // depends on. This avoids composites being skipped when the only + // thing that has changed is the generation of an compositor surface + // image dependency. + pub image_dependencies: [ImageDependency; 3], + // List of the surface information for each tile added to this virtual surface + pub tile_descriptors: Vec, +} + +/// Describes surface properties used to composite a frame. This +/// is used to compare compositions between frames. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(PartialEq, Clone)] +pub struct CompositeDescriptor { + pub surfaces: Vec, +} + +impl CompositeDescriptor { + /// Construct an empty descriptor. + pub fn empty() -> Self { + CompositeDescriptor { + surfaces: Vec::new(), + } + } +} + +/// The list of tiles to be drawn this frame +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct CompositeState { + // TODO(gw): Consider splitting up CompositeState into separate struct types depending + // on the selected compositing mode. Many of the fields in this state struct + // are only applicable to either Native or Draw compositing mode. + /// List of opaque tiles to be drawn by the Draw compositor. + pub opaque_tiles: Vec, + /// List of alpha tiles to be drawn by the Draw compositor. + pub alpha_tiles: Vec, + /// List of clear tiles to be drawn by the Draw compositor. + pub clear_tiles: Vec, + /// List of primitives that were promoted to be compositor surfaces. + pub external_surfaces: Vec, + /// Used to generate z-id values for tiles in the Draw compositor mode. + pub z_generator: ZBufferIdGenerator, + // If false, we can't rely on the dirty rects in the CompositeTile + // instances. This currently occurs during a scroll event, as a + // signal to refresh the whole screen. This is only a temporary + // measure until we integrate with OS compositors. In the meantime + // it gives us the ability to partial present for any non-scroll + // case as a simple win (e.g. video, animation etc). + pub dirty_rects_are_valid: bool, + /// The kind of compositor for picture cache tiles (e.g. drawn by WR, or OS compositor) + pub compositor_kind: CompositorKind, + /// Picture caching may be disabled dynamically, based on debug flags, pinch zoom etc. + pub picture_caching_is_enabled: bool, + /// The overall device pixel scale, used for tile occlusion conversions. + global_device_pixel_scale: DevicePixelScale, + /// List of registered occluders + occluders: Vec, + /// Description of the surfaces and properties that are being composited. + pub descriptor: CompositeDescriptor, +} + +impl CompositeState { + /// Construct a new state for compositing picture tiles. This is created + /// during each frame construction and passed to the renderer. + pub fn new( + compositor_kind: CompositorKind, + mut picture_caching_is_enabled: bool, + global_device_pixel_scale: DevicePixelScale, + max_depth_ids: i32, + ) -> Self { + // The native compositor interface requires picture caching to work, so + // force it here and warn if it was disabled. + if let CompositorKind::Native { .. } = compositor_kind { + if !picture_caching_is_enabled { + warn!("Picture caching cannot be disabled in native compositor config"); + } + picture_caching_is_enabled = true; + } + + CompositeState { + opaque_tiles: Vec::new(), + alpha_tiles: Vec::new(), + clear_tiles: Vec::new(), + z_generator: ZBufferIdGenerator::new(0, max_depth_ids), + dirty_rects_are_valid: true, + compositor_kind, + picture_caching_is_enabled, + global_device_pixel_scale, + occluders: Vec::new(), + descriptor: CompositeDescriptor::empty(), + external_surfaces: Vec::new(), + } + } + + /// Register an occluder during picture cache updates that can be + /// used during frame building to occlude tiles. + pub fn register_occluder( + &mut self, + z_id: ZBufferId, + rect: WorldRect, + ) { + let device_rect = (rect * self.global_device_pixel_scale).round().to_i32(); + + self.occluders.push(Occluder { + device_rect, + z_id, + }); + } + + /// Returns true if a tile with the specified rectangle and z_id + /// is occluded by an opaque surface in front of it. + pub fn is_tile_occluded( + &self, + z_id: ZBufferId, + device_rect: DeviceRect, + ) -> bool { + // It's often the case that a tile is only occluded by considering multiple + // picture caches in front of it (for example, the background tiles are + // often occluded by a combination of the content slice + the scrollbar slices). + + // The basic algorithm is: + // For every occluder: + // If this occluder is in front of the tile we are querying: + // Clip the occluder rectangle to the query rectangle. + // Calculate the total non-overlapping area of those clipped occluders. + // If the cumulative area of those occluders is the same as the area of the query tile, + // Then the entire tile must be occluded and can be skipped during rasterization and compositing. + + // Get the reference area we will compare against. + let device_rect = device_rect.round().to_i32(); + let ref_area = device_rect.size.width * device_rect.size.height; + + // Calculate the non-overlapping area of the valid occluders. + let cover_area = area_of_occluders(&self.occluders, z_id, &device_rect); + debug_assert!(cover_area <= ref_area); + + // Check if the tile area is completely covered + ref_area == cover_area + } + + /// Add a picture cache to be composited + pub fn push_surface( + &mut self, + tile_cache: &TileCacheInstance, + device_clip_rect: DeviceRect, + global_device_pixel_scale: DevicePixelScale, + resource_cache: &ResourceCache, + gpu_cache: &mut GpuCache, + deferred_resolves: &mut Vec, + ) { + let mut visible_opaque_tile_count = 0; + let mut visible_alpha_tile_count = 0; + let mut opaque_tile_descriptors = Vec::new(); + let mut alpha_tile_descriptors = Vec::new(); + + for tile in tile_cache.tiles.values() { + if !tile.is_visible { + // This can occur when a tile is found to be occluded during frame building. + continue; + } + + let device_rect = (tile.world_tile_rect * global_device_pixel_scale).round(); + let surface = tile.surface.as_ref().expect("no tile surface set!"); + + let descriptor = CompositeTileDescriptor { + surface_kind: surface.into(), + tile_id: tile.id, + }; + + let (surface, is_opaque) = match surface { + TileSurface::Color { color } => { + (CompositeTileSurface::Color { color: *color }, true) + } + TileSurface::Clear => { + (CompositeTileSurface::Clear, false) + } + TileSurface::Texture { descriptor, .. } => { + let surface = descriptor.resolve(resource_cache, tile_cache.current_tile_size); + ( + CompositeTileSurface::Texture { surface }, + // If a tile has compositor surface intersecting with it, we need to + // respect the tile.is_opaque property even if the overall tile cache + // is opaque. In this case, the tile.is_opaque property is required + // in order to ensure correct draw order with compositor surfaces. + tile.is_opaque || (!tile.has_compositor_surface && tile_cache.is_opaque()), + ) + } + }; + + if is_opaque { + opaque_tile_descriptors.push(descriptor); + visible_opaque_tile_count += 1; + } else { + alpha_tile_descriptors.push(descriptor); + visible_alpha_tile_count += 1; + } + + let tile = CompositeTile { + surface, + rect: device_rect, + valid_rect: tile.device_valid_rect.translate(-device_rect.origin.to_vector()), + dirty_rect: tile.device_dirty_rect.translate(-device_rect.origin.to_vector()), + clip_rect: device_clip_rect, + z_id: tile.z_id, + }; + + self.push_tile(tile, is_opaque); + } + + // Sort the tile descriptor lists, since iterating values in the tile_cache.tiles + // hashmap doesn't provide any ordering guarantees, but we want to detect the + // composite descriptor as equal if the tiles list is the same, regardless of + // ordering. + opaque_tile_descriptors.sort_by_key(|desc| desc.tile_id); + alpha_tile_descriptors.sort_by_key(|desc| desc.tile_id); + + // Add opaque surface before any compositor surfaces + if visible_opaque_tile_count > 0 { + self.descriptor.surfaces.push( + CompositeSurfaceDescriptor { + surface_id: tile_cache.native_surface.as_ref().map(|s| s.opaque), + offset: tile_cache.device_position, + clip_rect: device_clip_rect, + image_dependencies: [ImageDependency::INVALID; 3], + tile_descriptors: opaque_tile_descriptors, + } + ); + } + + // For each compositor surface that was promoted, build the + // information required for the compositor to draw it + for external_surface in &tile_cache.external_surfaces { + + let mut planes = [ + ExternalPlaneDescriptor::invalid(), + ExternalPlaneDescriptor::invalid(), + ExternalPlaneDescriptor::invalid(), + ]; + + // Step through the image keys, and build a plane descriptor for each + let required_plane_count = + match external_surface.dependency { + ExternalSurfaceDependency::Yuv { format, .. } => { + format.get_plane_num() + }, + ExternalSurfaceDependency::Rgb { .. } => { + 1 + } + }; + let mut valid_plane_count = 0; + + let mut image_dependencies = [ImageDependency::INVALID; 3]; + + for i in 0 .. required_plane_count { + let dependency = match external_surface.dependency { + ExternalSurfaceDependency::Yuv { image_dependencies, .. } => { + image_dependencies[i] + }, + ExternalSurfaceDependency::Rgb { image_dependency, .. } => { + image_dependency + } + }; + image_dependencies[i] = dependency; + + let request = ImageRequest { + key: dependency.key, + rendering: external_surface.image_rendering, + tile: None, + }; + + let cache_item = resolve_image( + request, + resource_cache, + gpu_cache, + deferred_resolves, + ); + + if cache_item.texture_id != TextureSource::Invalid { + valid_plane_count += 1; + let plane = &mut planes[i]; + *plane = ExternalPlaneDescriptor { + texture: cache_item.texture_id, + texture_layer: cache_item.texture_layer, + uv_rect: cache_item.uv_rect.into(), + }; + } + } + + // Check if there are valid images added for each YUV plane + if valid_plane_count < required_plane_count { + warn!("Warnings: skip a YUV/RGB compositor surface, found {}/{} valid images", + valid_plane_count, + required_plane_count, + ); + continue; + } + + let clip_rect = external_surface + .clip_rect + .intersection(&device_clip_rect) + .unwrap_or_else(DeviceRect::zero); + + // Get a new z_id for each compositor surface, to ensure correct ordering + // when drawing with the simple (Draw) compositor. + + let surface = CompositeTileSurface::ExternalSurface { + external_surface_index: ResolvedExternalSurfaceIndex(self.external_surfaces.len()), + }; + + // If the external surface descriptor reports that the native surface + // needs to be updated, create an update params tuple for the renderer + // to use. + let update_params = external_surface.update_params.map(|surface_size| { + ( + external_surface.native_surface_id.expect("bug: no native surface!"), + surface_size + ) + }); + + match external_surface.dependency { + ExternalSurfaceDependency::Yuv{ color_space, format, rescale, .. } => { + + let image_buffer_kind = get_buffer_kind(planes[0].texture); + + self.external_surfaces.push(ResolvedExternalSurface { + color_data: ResolvedExternalSurfaceColorData::Yuv { + image_dependencies, + planes, + color_space, + format, + rescale, + }, + image_buffer_kind, + update_params, + }); + }, + ExternalSurfaceDependency::Rgb{ flip_y, .. } => { + + let image_buffer_kind = get_buffer_kind(planes[0].texture); + + self.external_surfaces.push(ResolvedExternalSurface { + color_data: ResolvedExternalSurfaceColorData::Rgb { + image_dependency: image_dependencies[0], + plane: planes[0], + flip_y, + }, + image_buffer_kind, + update_params, + }); + }, + } + + let tile = CompositeTile { + surface, + rect: external_surface.device_rect, + valid_rect: external_surface.device_rect.translate(-external_surface.device_rect.origin.to_vector()), + dirty_rect: external_surface.device_rect.translate(-external_surface.device_rect.origin.to_vector()), + clip_rect, + z_id: external_surface.z_id, + }; + + // Add a surface descriptor for each compositor surface. For the Draw + // compositor, this is used to avoid composites being skipped by adding + // a dependency on the compositor surface external image keys / generations. + self.descriptor.surfaces.push( + CompositeSurfaceDescriptor { + surface_id: external_surface.native_surface_id, + offset: tile.rect.origin, + clip_rect: tile.clip_rect, + image_dependencies: image_dependencies, + tile_descriptors: Vec::new(), + } + ); + + self.push_tile(tile, true); + } + + // Add alpha / overlay tiles after compositor surfaces + if visible_alpha_tile_count > 0 { + self.descriptor.surfaces.push( + CompositeSurfaceDescriptor { + surface_id: tile_cache.native_surface.as_ref().map(|s| s.alpha), + offset: tile_cache.device_position, + clip_rect: device_clip_rect, + image_dependencies: [ImageDependency::INVALID; 3], + tile_descriptors: alpha_tile_descriptors, + } + ); + } + } + + /// Add a tile to the appropriate array, depending on tile properties and compositor mode. + fn push_tile( + &mut self, + tile: CompositeTile, + is_opaque: bool, + ) { + match tile.surface { + CompositeTileSurface::Color { .. } => { + // Color tiles are, by definition, opaque. We might support non-opaque color + // tiles if we ever find pages that have a lot of these. + self.opaque_tiles.push(tile); + } + CompositeTileSurface::Clear => { + // Clear tiles have a special bucket + self.clear_tiles.push(tile); + } + CompositeTileSurface::Texture { .. } => { + // Texture surfaces get bucketed by opaque/alpha, for z-rejection + // on the Draw compositor mode. + if is_opaque { + self.opaque_tiles.push(tile); + } else { + self.alpha_tiles.push(tile); + } + } + CompositeTileSurface::ExternalSurface { .. } => { + self.opaque_tiles.push(tile); + } + } + } +} + +/// An arbitrary identifier for a native (OS compositor) surface +#[repr(C)] +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct NativeSurfaceId(pub u64); + +impl NativeSurfaceId { + /// A special id for the native surface that is used for debug / profiler overlays. + pub const DEBUG_OVERLAY: NativeSurfaceId = NativeSurfaceId(u64::MAX); +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct NativeTileId { + pub surface_id: NativeSurfaceId, + pub x: i32, + pub y: i32, +} + +impl NativeTileId { + /// A special id for the native surface that is used for debug / profiler overlays. + pub const DEBUG_OVERLAY: NativeTileId = NativeTileId { + surface_id: NativeSurfaceId::DEBUG_OVERLAY, + x: 0, + y: 0, + }; +} + +/// Information about a bound surface that the native compositor +/// returns to WR. +#[repr(C)] +#[derive(Copy, Clone)] +pub struct NativeSurfaceInfo { + /// An offset into the surface that WR should draw. Some compositing + /// implementations (notably, DirectComposition) use texture atlases + /// when the surface sizes are small. In this case, an offset can + /// be returned into the larger texture where WR should draw. This + /// can be (0, 0) if texture atlases are not used. + pub origin: DeviceIntPoint, + /// The ID of the FBO that WR should bind to, in order to draw to + /// the bound surface. On Windows (ANGLE) this will always be 0, + /// since creating a p-buffer sets the default framebuffer to + /// be the DirectComposition surface. On Mac, this will be non-zero, + /// since it identifies the IOSurface that has been bound to draw to. + // TODO(gw): This may need to be a larger / different type for WR + // backends that are not GL. + pub fbo_id: u32, +} + +#[repr(C)] +pub struct CompositorCapabilities { + pub virtual_surface_size: i32, +} + +/// Defines an interface to a native (OS level) compositor. If supplied +/// by the client application, then picture cache slices will be +/// composited by the OS compositor, rather than drawn via WR batches. +pub trait Compositor { + /// Create a new OS compositor surface with the given properties. + fn create_surface( + &mut self, + id: NativeSurfaceId, + virtual_offset: DeviceIntPoint, + tile_size: DeviceIntSize, + is_opaque: bool, + ); + + /// Destroy the surface with the specified id. WR may call this + /// at any time the surface is no longer required (including during + /// renderer deinit). It's the responsibility of the embedder + /// to ensure that the surface is only freed once the GPU is + /// no longer using the surface (if this isn't already handled + /// by the operating system). + fn destroy_surface( + &mut self, + id: NativeSurfaceId, + ); + + /// Create a new OS compositor tile with the given properties. + fn create_tile( + &mut self, + id: NativeTileId, + ); + + /// Destroy an existing compositor tile. + fn destroy_tile( + &mut self, + id: NativeTileId, + ); + + /// Bind this surface such that WR can issue OpenGL commands + /// that will target the surface. Returns an (x, y) offset + /// where WR should draw into the surface. This can be set + /// to (0, 0) if the OS doesn't use texture atlases. The dirty + /// rect is a local surface rect that specifies which part + /// of the surface needs to be updated. If max_update_rects + /// in CompositeConfig is 0, this will always be the size + /// of the entire surface. The returned offset is only + /// relevant to compositors that store surfaces in a texture + /// atlas (that is, WR expects that the dirty rect doesn't + /// affect the coordinates of the returned origin). + fn bind( + &mut self, + id: NativeTileId, + dirty_rect: DeviceIntRect, + valid_rect: DeviceIntRect, + ) -> NativeSurfaceInfo; + + /// Unbind the surface. This is called by WR when it has + /// finished issuing OpenGL commands on the current surface. + fn unbind( + &mut self, + ); + + /// Begin the frame + fn begin_frame(&mut self); + + /// Add a surface to the visual tree to be composited. Visuals must + /// be added every frame, between the begin/end transaction call. The + /// z-order of the surfaces is determined by the order they are added + /// to the visual tree. + // TODO(gw): Adding visuals every frame makes the interface simple, + // but may have performance implications on some compositors? + // We might need to change the interface to maintain a visual + // tree that can be mutated? + // TODO(gw): We might need to add a concept of a hierachy in future. + // TODO(gw): In future, expand to support a more complete transform matrix. + fn add_surface( + &mut self, + id: NativeSurfaceId, + position: DeviceIntPoint, + clip_rect: DeviceIntRect, + ); + + /// Commit any changes in the compositor tree for this frame. WR calls + /// this once when all surface and visual updates are complete, to signal + /// that the OS composite transaction should be applied. + fn end_frame(&mut self); + + /// Enable/disable native compositor usage + fn enable_native_compositor(&mut self, enable: bool); + + /// Safely deinitialize any remaining resources owned by the compositor. + fn deinit(&mut self); + + /// Get the capabilities struct for this compositor. This is used to + /// specify what features a compositor supports, depending on the + /// underlying platform + fn get_capabilities(&self) -> CompositorCapabilities; +} + +/// Return the total area covered by a set of occluders, accounting for +/// overlapping areas between those rectangles. +fn area_of_occluders( + occluders: &[Occluder], + z_id: ZBufferId, + clip_rect: &DeviceIntRect, +) -> i32 { + // This implementation is based on the article https://leetcode.com/articles/rectangle-area-ii/. + // This is not a particularly efficient implementation (it skips building segment trees), however + // we typically use this where the length of the rectangles array is < 10, so simplicity is more important. + + let mut area = 0; + + // Whether this event is the start or end of a rectangle + #[derive(Debug)] + enum EventKind { + Begin, + End, + } + + // A list of events on the y-axis, with the rectangle range that it affects on the x-axis + #[derive(Debug)] + struct Event { + y: i32, + x_range: ops::Range, + kind: EventKind, + } + + impl Event { + fn new(y: i32, kind: EventKind, x0: i32, x1: i32) -> Self { + Event { + y, + x_range: ops::Range { + start: x0, + end: x1, + }, + kind, + } + } + } + + // Step through each rectangle and build the y-axis event list + let mut events = Vec::with_capacity(occluders.len() * 2); + for occluder in occluders { + // Only consider occluders in front of this rect + if occluder.z_id.0 > z_id.0 { + // Clip the source rect to the rectangle we care about, since we only + // want to record area for the tile we are comparing to. + if let Some(rect) = occluder.device_rect.intersection(clip_rect) { + let x0 = rect.origin.x; + let x1 = x0 + rect.size.width; + events.push(Event::new(rect.origin.y, EventKind::Begin, x0, x1)); + events.push(Event::new(rect.origin.y + rect.size.height, EventKind::End, x0, x1)); + } + } + } + + // If we didn't end up with any valid events, the area must be 0 + if events.is_empty() { + return 0; + } + + // Sort the events by y-value + events.sort_by_key(|e| e.y); + let mut active: Vec> = Vec::new(); + let mut cur_y = events[0].y; + + // Step through each y interval + for event in &events { + // This is the dimension of the y-axis we are accumulating areas for + let dy = event.y - cur_y; + + // If we have active events covering x-ranges in this y-interval, process them + if dy != 0 && !active.is_empty() { + assert!(dy > 0); + + // Step through the x-ranges, ordered by x0 of each event + active.sort_by_key(|i| i.start); + let mut query = 0; + let mut cur = active[0].start; + + // Accumulate the non-overlapping x-interval that contributes to area for this y-interval. + for interval in &active { + cur = interval.start.max(cur); + query += (interval.end - cur).max(0); + cur = cur.max(interval.end); + } + + // Accumulate total area for this y-interval + area += query * dy; + } + + // Update the active events list + match event.kind { + EventKind::Begin => { + active.push(event.x_range.clone()); + } + EventKind::End => { + let index = active.iter().position(|i| *i == event.x_range).unwrap(); + active.remove(index); + } + } + + cur_y = event.y; + } + + area +} diff --git a/third_party/webrender/webrender/src/debug_colors.rs b/third_party/webrender/webrender/src/debug_colors.rs new file mode 100644 index 00000000000..4ce8887126a --- /dev/null +++ b/third_party/webrender/webrender/src/debug_colors.rs @@ -0,0 +1,159 @@ +/* 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/. */ + +#![allow(dead_code)] +#![cfg_attr(feature = "cargo-clippy", allow(clippy::excessive_precision))] + +use api::ColorF; + +// A subset of the standard CSS colors, useful for defining GPU tag colors etc. + +pub const INDIGO: ColorF = ColorF { r: 0.294117647059, g: 0.0, b: 0.509803921569, a: 1.0 }; +pub const GOLD: ColorF = ColorF { r: 1.0, g: 0.843137254902, b: 0.0, a: 1.0 }; +pub const FIREBRICK: ColorF = ColorF { r: 0.698039215686, g: 0.133333333333, b: 0.133333333333, a: 1.0 }; +pub const INDIANRED: ColorF = ColorF { r: 0.803921568627, g: 0.360784313725, b: 0.360784313725, a: 1.0 }; +pub const YELLOW: ColorF = ColorF { r: 1.0, g: 1.0, b: 0.0, a: 1.0 }; +pub const DARKOLIVEGREEN: ColorF = ColorF { r: 0.333333333333, g: 0.419607843137, b: 0.18431372549, a: 1.0 }; +pub const DARKSEAGREEN: ColorF = ColorF { r: 0.560784313725, g: 0.737254901961, b: 0.560784313725, a: 1.0 }; +pub const SLATEGREY: ColorF = ColorF { r: 0.439215686275, g: 0.501960784314, b: 0.564705882353, a: 1.0 }; +pub const DARKSLATEGREY: ColorF = ColorF { r: 0.18431372549, g: 0.309803921569, b: 0.309803921569, a: 1.0 }; +pub const MEDIUMVIOLETRED: ColorF = ColorF { r: 0.780392156863, g: 0.0823529411765, b: 0.521568627451, a: 1.0 }; +pub const MEDIUMORCHID: ColorF = ColorF { r: 0.729411764706, g: 0.333333333333, b: 0.827450980392, a: 1.0 }; +pub const CHARTREUSE: ColorF = ColorF { r: 0.498039215686, g: 1.0, b: 0.0, a: 1.0 }; +pub const MEDIUMSLATEBLUE: ColorF = ColorF { r: 0.482352941176, g: 0.407843137255, b: 0.933333333333, a: 1.0 }; +pub const BLACK: ColorF = ColorF { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }; +pub const SPRINGGREEN: ColorF = ColorF { r: 0.0, g: 1.0, b: 0.498039215686, a: 1.0 }; +pub const CRIMSON: ColorF = ColorF { r: 0.862745098039, g: 0.078431372549, b: 0.235294117647, a: 1.0 }; +pub const LIGHTSALMON: ColorF = ColorF { r: 1.0, g: 0.627450980392, b: 0.478431372549, a: 1.0 }; +pub const BROWN: ColorF = ColorF { r: 0.647058823529, g: 0.164705882353, b: 0.164705882353, a: 1.0 }; +pub const TURQUOISE: ColorF = ColorF { r: 0.250980392157, g: 0.878431372549, b: 0.81568627451, a: 1.0 }; +pub const OLIVEDRAB: ColorF = ColorF { r: 0.419607843137, g: 0.556862745098, b: 0.137254901961, a: 1.0 }; +pub const CYAN: ColorF = ColorF { r: 0.0, g: 1.0, b: 1.0, a: 1.0 }; +pub const SILVER: ColorF = ColorF { r: 0.752941176471, g: 0.752941176471, b: 0.752941176471, a: 1.0 }; +pub const SKYBLUE: ColorF = ColorF { r: 0.529411764706, g: 0.807843137255, b: 0.921568627451, a: 1.0 }; +pub const GRAY: ColorF = ColorF { r: 0.501960784314, g: 0.501960784314, b: 0.501960784314, a: 1.0 }; +pub const DARKTURQUOISE: ColorF = ColorF { r: 0.0, g: 0.807843137255, b: 0.819607843137, a: 1.0 }; +pub const GOLDENROD: ColorF = ColorF { r: 0.854901960784, g: 0.647058823529, b: 0.125490196078, a: 1.0 }; +pub const DARKGREEN: ColorF = ColorF { r: 0.0, g: 0.392156862745, b: 0.0, a: 1.0 }; +pub const DARKVIOLET: ColorF = ColorF { r: 0.580392156863, g: 0.0, b: 0.827450980392, a: 1.0 }; +pub const DARKGRAY: ColorF = ColorF { r: 0.662745098039, g: 0.662745098039, b: 0.662745098039, a: 1.0 }; +pub const LIGHTPINK: ColorF = ColorF { r: 1.0, g: 0.713725490196, b: 0.756862745098, a: 1.0 }; +pub const TEAL: ColorF = ColorF { r: 0.0, g: 0.501960784314, b: 0.501960784314, a: 1.0 }; +pub const DARKMAGENTA: ColorF = ColorF { r: 0.545098039216, g: 0.0, b: 0.545098039216, a: 1.0 }; +pub const LIGHTGOLDENRODYELLOW: ColorF = ColorF { r: 0.980392156863, g: 0.980392156863, b: 0.823529411765, a: 1.0 }; +pub const LAVENDER: ColorF = ColorF { r: 0.901960784314, g: 0.901960784314, b: 0.980392156863, a: 1.0 }; +pub const YELLOWGREEN: ColorF = ColorF { r: 0.603921568627, g: 0.803921568627, b: 0.196078431373, a: 1.0 }; +pub const THISTLE: ColorF = ColorF { r: 0.847058823529, g: 0.749019607843, b: 0.847058823529, a: 1.0 }; +pub const VIOLET: ColorF = ColorF { r: 0.933333333333, g: 0.509803921569, b: 0.933333333333, a: 1.0 }; +pub const NAVY: ColorF = ColorF { r: 0.0, g: 0.0, b: 0.501960784314, a: 1.0 }; +pub const DIMGREY: ColorF = ColorF { r: 0.411764705882, g: 0.411764705882, b: 0.411764705882, a: 1.0 }; +pub const ORCHID: ColorF = ColorF { r: 0.854901960784, g: 0.439215686275, b: 0.839215686275, a: 1.0 }; +pub const BLUE: ColorF = ColorF { r: 0.0, g: 0.0, b: 1.0, a: 1.0 }; +pub const GHOSTWHITE: ColorF = ColorF { r: 0.972549019608, g: 0.972549019608, b: 1.0, a: 1.0 }; +pub const HONEYDEW: ColorF = ColorF { r: 0.941176470588, g: 1.0, b: 0.941176470588, a: 1.0 }; +pub const CORNFLOWERBLUE: ColorF = ColorF { r: 0.392156862745, g: 0.58431372549, b: 0.929411764706, a: 1.0 }; +pub const DARKBLUE: ColorF = ColorF { r: 0.0, g: 0.0, b: 0.545098039216, a: 1.0 }; +pub const DARKKHAKI: ColorF = ColorF { r: 0.741176470588, g: 0.717647058824, b: 0.419607843137, a: 1.0 }; +pub const MEDIUMPURPLE: ColorF = ColorF { r: 0.576470588235, g: 0.439215686275, b: 0.858823529412, a: 1.0 }; +pub const CORNSILK: ColorF = ColorF { r: 1.0, g: 0.972549019608, b: 0.862745098039, a: 1.0 }; +pub const RED: ColorF = ColorF { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }; +pub const BISQUE: ColorF = ColorF { r: 1.0, g: 0.894117647059, b: 0.76862745098, a: 1.0 }; +pub const SLATEGRAY: ColorF = ColorF { r: 0.439215686275, g: 0.501960784314, b: 0.564705882353, a: 1.0 }; +pub const DARKCYAN: ColorF = ColorF { r: 0.0, g: 0.545098039216, b: 0.545098039216, a: 1.0 }; +pub const KHAKI: ColorF = ColorF { r: 0.941176470588, g: 0.901960784314, b: 0.549019607843, a: 1.0 }; +pub const WHEAT: ColorF = ColorF { r: 0.960784313725, g: 0.870588235294, b: 0.701960784314, a: 1.0 }; +pub const DEEPSKYBLUE: ColorF = ColorF { r: 0.0, g: 0.749019607843, b: 1.0, a: 1.0 }; +pub const REBECCAPURPLE: ColorF = ColorF { r: 0.4, g: 0.2, b: 0.6, a: 1.0 }; +pub const DARKRED: ColorF = ColorF { r: 0.545098039216, g: 0.0, b: 0.0, a: 1.0 }; +pub const STEELBLUE: ColorF = ColorF { r: 0.274509803922, g: 0.509803921569, b: 0.705882352941, a: 1.0 }; +pub const ALICEBLUE: ColorF = ColorF { r: 0.941176470588, g: 0.972549019608, b: 1.0, a: 1.0 }; +pub const LIGHTSLATEGREY: ColorF = ColorF { r: 0.466666666667, g: 0.533333333333, b: 0.6, a: 1.0 }; +pub const GAINSBORO: ColorF = ColorF { r: 0.862745098039, g: 0.862745098039, b: 0.862745098039, a: 1.0 }; +pub const MEDIUMTURQUOISE: ColorF = ColorF { r: 0.282352941176, g: 0.819607843137, b: 0.8, a: 1.0 }; +pub const FLORALWHITE: ColorF = ColorF { r: 1.0, g: 0.980392156863, b: 0.941176470588, a: 1.0 }; +pub const CORAL: ColorF = ColorF { r: 1.0, g: 0.498039215686, b: 0.313725490196, a: 1.0 }; +pub const PURPLE: ColorF = ColorF { r: 0.501960784314, g: 0.0, b: 0.501960784314, a: 1.0 }; +pub const LIGHTGREY: ColorF = ColorF { r: 0.827450980392, g: 0.827450980392, b: 0.827450980392, a: 1.0 }; +pub const LIGHTCYAN: ColorF = ColorF { r: 0.878431372549, g: 1.0, b: 1.0, a: 1.0 }; +pub const DARKSALMON: ColorF = ColorF { r: 0.913725490196, g: 0.588235294118, b: 0.478431372549, a: 1.0 }; +pub const BEIGE: ColorF = ColorF { r: 0.960784313725, g: 0.960784313725, b: 0.862745098039, a: 1.0 }; +pub const AZURE: ColorF = ColorF { r: 0.941176470588, g: 1.0, b: 1.0, a: 1.0 }; +pub const LIGHTSTEELBLUE: ColorF = ColorF { r: 0.690196078431, g: 0.76862745098, b: 0.870588235294, a: 1.0 }; +pub const OLDLACE: ColorF = ColorF { r: 0.992156862745, g: 0.960784313725, b: 0.901960784314, a: 1.0 }; +pub const GREENYELLOW: ColorF = ColorF { r: 0.678431372549, g: 1.0, b: 0.18431372549, a: 1.0 }; +pub const ROYALBLUE: ColorF = ColorF { r: 0.254901960784, g: 0.411764705882, b: 0.882352941176, a: 1.0 }; +pub const LIGHTSEAGREEN: ColorF = ColorF { r: 0.125490196078, g: 0.698039215686, b: 0.666666666667, a: 1.0 }; +pub const MISTYROSE: ColorF = ColorF { r: 1.0, g: 0.894117647059, b: 0.882352941176, a: 1.0 }; +pub const SIENNA: ColorF = ColorF { r: 0.627450980392, g: 0.321568627451, b: 0.176470588235, a: 1.0 }; +pub const LIGHTCORAL: ColorF = ColorF { r: 0.941176470588, g: 0.501960784314, b: 0.501960784314, a: 1.0 }; +pub const ORANGERED: ColorF = ColorF { r: 1.0, g: 0.270588235294, b: 0.0, a: 1.0 }; +pub const NAVAJOWHITE: ColorF = ColorF { r: 1.0, g: 0.870588235294, b: 0.678431372549, a: 1.0 }; +pub const LIME: ColorF = ColorF { r: 0.0, g: 1.0, b: 0.0, a: 1.0 }; +pub const PALEGREEN: ColorF = ColorF { r: 0.596078431373, g: 0.98431372549, b: 0.596078431373, a: 1.0 }; +pub const BURLYWOOD: ColorF = ColorF { r: 0.870588235294, g: 0.721568627451, b: 0.529411764706, a: 1.0 }; +pub const SEASHELL: ColorF = ColorF { r: 1.0, g: 0.960784313725, b: 0.933333333333, a: 1.0 }; +pub const MEDIUMSPRINGGREEN: ColorF = ColorF { r: 0.0, g: 0.980392156863, b: 0.603921568627, a: 1.0 }; +pub const FUCHSIA: ColorF = ColorF { r: 1.0, g: 0.0, b: 1.0, a: 1.0 }; +pub const PAPAYAWHIP: ColorF = ColorF { r: 1.0, g: 0.937254901961, b: 0.835294117647, a: 1.0 }; +pub const BLANCHEDALMOND: ColorF = ColorF { r: 1.0, g: 0.921568627451, b: 0.803921568627, a: 1.0 }; +pub const PERU: ColorF = ColorF { r: 0.803921568627, g: 0.521568627451, b: 0.247058823529, a: 1.0 }; +pub const AQUAMARINE: ColorF = ColorF { r: 0.498039215686, g: 1.0, b: 0.83137254902, a: 1.0 }; +pub const WHITE: ColorF = ColorF { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }; +pub const DARKSLATEGRAY: ColorF = ColorF { r: 0.18431372549, g: 0.309803921569, b: 0.309803921569, a: 1.0 }; +pub const TOMATO: ColorF = ColorF { r: 1.0, g: 0.388235294118, b: 0.278431372549, a: 1.0 }; +pub const IVORY: ColorF = ColorF { r: 1.0, g: 1.0, b: 0.941176470588, a: 1.0 }; +pub const DODGERBLUE: ColorF = ColorF { r: 0.117647058824, g: 0.564705882353, b: 1.0, a: 1.0 }; +pub const LEMONCHIFFON: ColorF = ColorF { r: 1.0, g: 0.980392156863, b: 0.803921568627, a: 1.0 }; +pub const CHOCOLATE: ColorF = ColorF { r: 0.823529411765, g: 0.411764705882, b: 0.117647058824, a: 1.0 }; +pub const ORANGE: ColorF = ColorF { r: 1.0, g: 0.647058823529, b: 0.0, a: 1.0 }; +pub const FORESTGREEN: ColorF = ColorF { r: 0.133333333333, g: 0.545098039216, b: 0.133333333333, a: 1.0 }; +pub const DARKGREY: ColorF = ColorF { r: 0.662745098039, g: 0.662745098039, b: 0.662745098039, a: 1.0 }; +pub const OLIVE: ColorF = ColorF { r: 0.501960784314, g: 0.501960784314, b: 0.0, a: 1.0 }; +pub const MINTCREAM: ColorF = ColorF { r: 0.960784313725, g: 1.0, b: 0.980392156863, a: 1.0 }; +pub const ANTIQUEWHITE: ColorF = ColorF { r: 0.980392156863, g: 0.921568627451, b: 0.843137254902, a: 1.0 }; +pub const DARKORANGE: ColorF = ColorF { r: 1.0, g: 0.549019607843, b: 0.0, a: 1.0 }; +pub const CADETBLUE: ColorF = ColorF { r: 0.372549019608, g: 0.619607843137, b: 0.627450980392, a: 1.0 }; +pub const MOCCASIN: ColorF = ColorF { r: 1.0, g: 0.894117647059, b: 0.709803921569, a: 1.0 }; +pub const LIMEGREEN: ColorF = ColorF { r: 0.196078431373, g: 0.803921568627, b: 0.196078431373, a: 1.0 }; +pub const SADDLEBROWN: ColorF = ColorF { r: 0.545098039216, g: 0.270588235294, b: 0.0745098039216, a: 1.0 }; +pub const GREY: ColorF = ColorF { r: 0.501960784314, g: 0.501960784314, b: 0.501960784314, a: 1.0 }; +pub const DARKSLATEBLUE: ColorF = ColorF { r: 0.282352941176, g: 0.239215686275, b: 0.545098039216, a: 1.0 }; +pub const LIGHTSKYBLUE: ColorF = ColorF { r: 0.529411764706, g: 0.807843137255, b: 0.980392156863, a: 1.0 }; +pub const DEEPPINK: ColorF = ColorF { r: 1.0, g: 0.078431372549, b: 0.576470588235, a: 1.0 }; +pub const PLUM: ColorF = ColorF { r: 0.866666666667, g: 0.627450980392, b: 0.866666666667, a: 1.0 }; +pub const AQUA: ColorF = ColorF { r: 0.0, g: 1.0, b: 1.0, a: 1.0 }; +pub const DARKGOLDENROD: ColorF = ColorF { r: 0.721568627451, g: 0.525490196078, b: 0.043137254902, a: 1.0 }; +pub const MAROON: ColorF = ColorF { r: 0.501960784314, g: 0.0, b: 0.0, a: 1.0 }; +pub const SANDYBROWN: ColorF = ColorF { r: 0.956862745098, g: 0.643137254902, b: 0.376470588235, a: 1.0 }; +pub const MAGENTA: ColorF = ColorF { r: 1.0, g: 0.0, b: 1.0, a: 1.0 }; +pub const TAN: ColorF = ColorF { r: 0.823529411765, g: 0.705882352941, b: 0.549019607843, a: 1.0 }; +pub const ROSYBROWN: ColorF = ColorF { r: 0.737254901961, g: 0.560784313725, b: 0.560784313725, a: 1.0 }; +pub const PINK: ColorF = ColorF { r: 1.0, g: 0.752941176471, b: 0.796078431373, a: 1.0 }; +pub const LIGHTBLUE: ColorF = ColorF { r: 0.678431372549, g: 0.847058823529, b: 0.901960784314, a: 1.0 }; +pub const PALEVIOLETRED: ColorF = ColorF { r: 0.858823529412, g: 0.439215686275, b: 0.576470588235, a: 1.0 }; +pub const MEDIUMSEAGREEN: ColorF = ColorF { r: 0.235294117647, g: 0.701960784314, b: 0.443137254902, a: 1.0 }; +pub const SLATEBLUE: ColorF = ColorF { r: 0.41568627451, g: 0.352941176471, b: 0.803921568627, a: 1.0 }; +pub const DIMGRAY: ColorF = ColorF { r: 0.411764705882, g: 0.411764705882, b: 0.411764705882, a: 1.0 }; +pub const POWDERBLUE: ColorF = ColorF { r: 0.690196078431, g: 0.878431372549, b: 0.901960784314, a: 1.0 }; +pub const SEAGREEN: ColorF = ColorF { r: 0.180392156863, g: 0.545098039216, b: 0.341176470588, a: 1.0 }; +pub const SNOW: ColorF = ColorF { r: 1.0, g: 0.980392156863, b: 0.980392156863, a: 1.0 }; +pub const MEDIUMBLUE: ColorF = ColorF { r: 0.0, g: 0.0, b: 0.803921568627, a: 1.0 }; +pub const MIDNIGHTBLUE: ColorF = ColorF { r: 0.0980392156863, g: 0.0980392156863, b: 0.439215686275, a: 1.0 }; +pub const PALETURQUOISE: ColorF = ColorF { r: 0.686274509804, g: 0.933333333333, b: 0.933333333333, a: 1.0 }; +pub const PALEGOLDENROD: ColorF = ColorF { r: 0.933333333333, g: 0.909803921569, b: 0.666666666667, a: 1.0 }; +pub const WHITESMOKE: ColorF = ColorF { r: 0.960784313725, g: 0.960784313725, b: 0.960784313725, a: 1.0 }; +pub const DARKORCHID: ColorF = ColorF { r: 0.6, g: 0.196078431373, b: 0.8, a: 1.0 }; +pub const SALMON: ColorF = ColorF { r: 0.980392156863, g: 0.501960784314, b: 0.447058823529, a: 1.0 }; +pub const LIGHTSLATEGRAY: ColorF = ColorF { r: 0.466666666667, g: 0.533333333333, b: 0.6, a: 1.0 }; +pub const LAWNGREEN: ColorF = ColorF { r: 0.486274509804, g: 0.988235294118, b: 0.0, a: 1.0 }; +pub const LIGHTGREEN: ColorF = ColorF { r: 0.564705882353, g: 0.933333333333, b: 0.564705882353, a: 1.0 }; +pub const LIGHTGRAY: ColorF = ColorF { r: 0.827450980392, g: 0.827450980392, b: 0.827450980392, a: 1.0 }; +pub const HOTPINK: ColorF = ColorF { r: 1.0, g: 0.411764705882, b: 0.705882352941, a: 1.0 }; +pub const LIGHTYELLOW: ColorF = ColorF { r: 1.0, g: 1.0, b: 0.878431372549, a: 1.0 }; +pub const LAVENDERBLUSH: ColorF = ColorF { r: 1.0, g: 0.941176470588, b: 0.960784313725, a: 1.0 }; +pub const LINEN: ColorF = ColorF { r: 0.980392156863, g: 0.941176470588, b: 0.901960784314, a: 1.0 }; +pub const MEDIUMAQUAMARINE: ColorF = ColorF { r: 0.4, g: 0.803921568627, b: 0.666666666667, a: 1.0 }; +pub const GREEN: ColorF = ColorF { r: 0.0, g: 0.501960784314, b: 0.0, a: 1.0 }; +pub const BLUEVIOLET: ColorF = ColorF { r: 0.541176470588, g: 0.16862745098, b: 0.886274509804, a: 1.0 }; +pub const PEACHPUFF: ColorF = ColorF { r: 1.0, g: 0.854901960784, b: 0.725490196078, a: 1.0 }; diff --git a/third_party/webrender/webrender/src/debug_font_data.rs b/third_party/webrender/webrender/src/debug_font_data.rs new file mode 100644 index 00000000000..a891bf0d387 --- /dev/null +++ b/third_party/webrender/webrender/src/debug_font_data.rs @@ -0,0 +1,1914 @@ +/* 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/. */ + +#[derive(Debug)] +pub struct BakedGlyph { + pub x0: u32, + pub y0: u32, + pub x1: u32, + pub y1: u32, + pub xo: f32, + pub yo: f32, + pub xa: f32, +} + +pub const FIRST_GLYPH_INDEX: i32 = 32; +pub const BMP_WIDTH: i32 = 128; +pub const BMP_HEIGHT: i32 = 128; +pub const FONT_SIZE: i32 = 19; + +pub const GLYPHS: [BakedGlyph; 96] = [ + BakedGlyph { + x0: 1, + y0: 1, + x1: 1, + y1: 1, + xo: 0.000000, + yo: 0.000000, + xa: 3.864407, + }, + BakedGlyph { + x0: 2, + y0: 1, + x1: 5, + y1: 14, + xo: 1.000000, + yo: -12.000000, + xa: 4.644068, + }, + BakedGlyph { + x0: 6, + y0: 1, + x1: 11, + y1: 6, + xo: 1.000000, + yo: -13.000000, + xa: 6.644068, + }, + BakedGlyph { + x0: 12, + y0: 1, + x1: 23, + y1: 13, + xo: 0.000000, + yo: -12.000000, + xa: 11.067797, + }, + BakedGlyph { + x0: 24, + y0: 1, + x1: 32, + y1: 17, + xo: 1.000000, + yo: -14.000000, + xa: 9.559322, + }, + BakedGlyph { + x0: 33, + y0: 1, + x1: 46, + y1: 14, + xo: 1.000000, + yo: -12.000000, + xa: 14.084745, + }, + BakedGlyph { + x0: 47, + y0: 1, + x1: 58, + y1: 14, + xo: 0.000000, + yo: -12.000000, + xa: 10.983051, + }, + BakedGlyph { + x0: 59, + y0: 1, + x1: 61, + y1: 6, + xo: 1.000000, + yo: -13.000000, + xa: 4.067797, + }, + BakedGlyph { + x0: 62, + y0: 1, + x1: 67, + y1: 19, + xo: 1.000000, + yo: -14.000000, + xa: 5.254237, + }, + BakedGlyph { + x0: 68, + y0: 1, + x1: 72, + y1: 19, + xo: 0.000000, + yo: -14.000000, + xa: 5.254237, + }, + BakedGlyph { + x0: 73, + y0: 1, + x1: 81, + y1: 8, + xo: 0.000000, + yo: -12.000000, + xa: 8.000000, + }, + BakedGlyph { + x0: 82, + y0: 1, + x1: 91, + y1: 11, + xo: 0.000000, + yo: -10.000000, + xa: 9.559322, + }, + BakedGlyph { + x0: 92, + y0: 1, + x1: 95, + y1: 6, + xo: 0.000000, + yo: -2.000000, + xa: 4.169492, + }, + BakedGlyph { + x0: 96, + y0: 1, + x1: 101, + y1: 3, + xo: 0.000000, + yo: -6.000000, + xa: 4.779661, + }, + BakedGlyph { + x0: 102, + y0: 1, + x1: 105, + y1: 4, + xo: 1.000000, + yo: -2.000000, + xa: 4.169492, + }, + BakedGlyph { + x0: 106, + y0: 1, + x1: 114, + y1: 19, + xo: -1.000000, + yo: -14.000000, + xa: 6.084746, + }, + BakedGlyph { + x0: 115, + y0: 1, + x1: 123, + y1: 14, + xo: 1.000000, + yo: -12.000000, + xa: 9.559322, + }, + BakedGlyph { + x0: 1, + y0: 20, + x1: 6, + y1: 32, + xo: 1.000000, + yo: -12.000000, + xa: 9.559322, + }, + BakedGlyph { + x0: 7, + y0: 20, + x1: 15, + y1: 32, + xo: 1.000000, + yo: -12.000000, + xa: 9.559322, + }, + BakedGlyph { + x0: 16, + y0: 20, + x1: 24, + y1: 33, + xo: 1.000000, + yo: -12.000000, + xa: 9.559322, + }, + BakedGlyph { + x0: 25, + y0: 20, + x1: 34, + y1: 32, + xo: 0.000000, + yo: -12.000000, + xa: 9.559322, + }, + BakedGlyph { + x0: 35, + y0: 20, + x1: 43, + y1: 33, + xo: 1.000000, + yo: -12.000000, + xa: 9.559322, + }, + BakedGlyph { + x0: 44, + y0: 20, + x1: 52, + y1: 33, + xo: 1.000000, + yo: -12.000000, + xa: 9.559322, + }, + BakedGlyph { + x0: 53, + y0: 20, + x1: 61, + y1: 32, + xo: 1.000000, + yo: -12.000000, + xa: 9.559322, + }, + BakedGlyph { + x0: 62, + y0: 20, + x1: 70, + y1: 33, + xo: 1.000000, + yo: -12.000000, + xa: 9.559322, + }, + BakedGlyph { + x0: 71, + y0: 20, + x1: 79, + y1: 33, + xo: 1.000000, + yo: -12.000000, + xa: 9.559322, + }, + BakedGlyph { + x0: 80, + y0: 20, + x1: 83, + y1: 30, + xo: 1.000000, + yo: -9.000000, + xa: 4.169492, + }, + BakedGlyph { + x0: 84, + y0: 20, + x1: 88, + y1: 32, + xo: 0.000000, + yo: -9.000000, + xa: 4.169492, + }, + BakedGlyph { + x0: 89, + y0: 20, + x1: 98, + y1: 28, + xo: 0.000000, + yo: -9.000000, + xa: 9.559322, + }, + BakedGlyph { + x0: 99, + y0: 20, + x1: 108, + y1: 26, + xo: 0.000000, + yo: -8.000000, + xa: 9.559322, + }, + BakedGlyph { + x0: 109, + y0: 20, + x1: 118, + y1: 28, + xo: 0.000000, + yo: -9.000000, + xa: 9.559322, + }, + BakedGlyph { + x0: 119, + y0: 20, + x1: 125, + y1: 33, + xo: 0.000000, + yo: -12.000000, + xa: 6.440678, + }, + BakedGlyph { + x0: 1, + y0: 34, + x1: 15, + y1: 49, + xo: 1.000000, + yo: -12.000000, + xa: 15.932203, + }, + BakedGlyph { + x0: 16, + y0: 34, + x1: 27, + y1: 46, + xo: 0.000000, + yo: -12.000000, + xa: 10.864407, + }, + BakedGlyph { + x0: 28, + y0: 34, + x1: 37, + y1: 47, + xo: 1.000000, + yo: -12.000000, + xa: 10.677966, + }, + BakedGlyph { + x0: 38, + y0: 34, + x1: 47, + y1: 47, + xo: 1.000000, + yo: -12.000000, + xa: 10.322034, + }, + BakedGlyph { + x0: 48, + y0: 34, + x1: 58, + y1: 47, + xo: 1.000000, + yo: -12.000000, + xa: 11.898305, + }, + BakedGlyph { + x0: 59, + y0: 34, + x1: 67, + y1: 46, + xo: 1.000000, + yo: -12.000000, + xa: 9.406779, + }, + BakedGlyph { + x0: 68, + y0: 34, + x1: 76, + y1: 46, + xo: 1.000000, + yo: -12.000000, + xa: 8.813560, + }, + BakedGlyph { + x0: 77, + y0: 34, + x1: 86, + y1: 47, + xo: 1.000000, + yo: -12.000000, + xa: 11.152542, + }, + BakedGlyph { + x0: 87, + y0: 34, + x1: 97, + y1: 46, + xo: 1.000000, + yo: -12.000000, + xa: 11.728813, + }, + BakedGlyph { + x0: 98, + y0: 34, + x1: 100, + y1: 46, + xo: 1.000000, + yo: -12.000000, + xa: 4.203390, + }, + BakedGlyph { + x0: 101, + y0: 34, + x1: 108, + y1: 47, + xo: 0.000000, + yo: -12.000000, + xa: 8.254237, + }, + BakedGlyph { + x0: 109, + y0: 34, + x1: 118, + y1: 46, + xo: 1.000000, + yo: -12.000000, + xa: 10.152542, + }, + BakedGlyph { + x0: 1, + y0: 50, + x1: 9, + y1: 62, + xo: 1.000000, + yo: -12.000000, + xa: 8.508474, + }, + BakedGlyph { + x0: 10, + y0: 50, + x1: 23, + y1: 62, + xo: 1.000000, + yo: -12.000000, + xa: 14.661017, + }, + BakedGlyph { + x0: 24, + y0: 50, + x1: 34, + y1: 62, + xo: 1.000000, + yo: -12.000000, + xa: 12.016949, + }, + BakedGlyph { + x0: 35, + y0: 50, + x1: 47, + y1: 63, + xo: 1.000000, + yo: -12.000000, + xa: 13.118644, + }, + BakedGlyph { + x0: 48, + y0: 50, + x1: 57, + y1: 62, + xo: 1.000000, + yo: -12.000000, + xa: 10.033898, + }, + BakedGlyph { + x0: 58, + y0: 50, + x1: 70, + y1: 66, + xo: 1.000000, + yo: -12.000000, + xa: 13.118644, + }, + BakedGlyph { + x0: 71, + y0: 50, + x1: 81, + y1: 62, + xo: 1.000000, + yo: -12.000000, + xa: 10.474576, + }, + BakedGlyph { + x0: 82, + y0: 50, + x1: 91, + y1: 63, + xo: 0.000000, + yo: -12.000000, + xa: 8.762712, + }, + BakedGlyph { + x0: 92, + y0: 50, + x1: 101, + y1: 62, + xo: 0.000000, + yo: -12.000000, + xa: 9.288136, + }, + BakedGlyph { + x0: 102, + y0: 50, + x1: 112, + y1: 63, + xo: 1.000000, + yo: -12.000000, + xa: 11.525424, + }, + BakedGlyph { + x0: 113, + y0: 50, + x1: 124, + y1: 62, + xo: 0.000000, + yo: -12.000000, + xa: 10.576271, + }, + BakedGlyph { + x0: 1, + y0: 67, + x1: 16, + y1: 79, + xo: 0.000000, + yo: -12.000000, + xa: 15.610169, + }, + BakedGlyph { + x0: 17, + y0: 67, + x1: 27, + y1: 79, + xo: 0.000000, + yo: -12.000000, + xa: 10.305085, + }, + BakedGlyph { + x0: 28, + y0: 67, + x1: 38, + y1: 79, + xo: 0.000000, + yo: -12.000000, + xa: 9.644068, + }, + BakedGlyph { + x0: 39, + y0: 67, + x1: 48, + y1: 79, + xo: 0.000000, + yo: -12.000000, + xa: 9.491526, + }, + BakedGlyph { + x0: 49, + y0: 67, + x1: 54, + y1: 85, + xo: 1.000000, + yo: -14.000000, + xa: 5.254237, + }, + BakedGlyph { + x0: 55, + y0: 67, + x1: 63, + y1: 85, + xo: -1.000000, + yo: -14.000000, + xa: 6.084746, + }, + BakedGlyph { + x0: 64, + y0: 67, + x1: 68, + y1: 85, + xo: 0.000000, + yo: -14.000000, + xa: 5.254237, + }, + BakedGlyph { + x0: 69, + y0: 67, + x1: 77, + y1: 74, + xo: 1.000000, + yo: -12.000000, + xa: 9.559322, + }, + BakedGlyph { + x0: 78, + y0: 67, + x1: 88, + y1: 69, + xo: -1.000000, + yo: 2.000000, + xa: 8.305085, + }, + BakedGlyph { + x0: 89, + y0: 67, + x1: 93, + y1: 72, + xo: 1.000000, + yo: -14.000000, + xa: 6.372881, + }, + BakedGlyph { + x0: 94, + y0: 67, + x1: 102, + y1: 77, + xo: 0.000000, + yo: -9.000000, + xa: 8.627119, + }, + BakedGlyph { + x0: 103, + y0: 67, + x1: 111, + y1: 82, + xo: 1.000000, + yo: -14.000000, + xa: 9.881356, + }, + BakedGlyph { + x0: 112, + y0: 67, + x1: 120, + y1: 77, + xo: 0.000000, + yo: -9.000000, + xa: 7.796610, + }, + BakedGlyph { + x0: 1, + y0: 86, + x1: 10, + y1: 101, + xo: 0.000000, + yo: -14.000000, + xa: 9.881356, + }, + BakedGlyph { + x0: 11, + y0: 86, + x1: 20, + y1: 96, + xo: 0.000000, + yo: -9.000000, + xa: 9.288136, + }, + BakedGlyph { + x0: 21, + y0: 86, + x1: 27, + y1: 100, + xo: 1.000000, + yo: -14.000000, + xa: 6.372881, + }, + BakedGlyph { + x0: 28, + y0: 86, + x1: 37, + y1: 99, + xo: 0.000000, + yo: -9.000000, + xa: 9.711864, + }, + BakedGlyph { + x0: 38, + y0: 86, + x1: 46, + y1: 100, + xo: 1.000000, + yo: -14.000000, + xa: 9.644068, + }, + BakedGlyph { + x0: 47, + y0: 86, + x1: 49, + y1: 99, + xo: 1.000000, + yo: -13.000000, + xa: 4.016949, + }, + BakedGlyph { + x0: 50, + y0: 86, + x1: 55, + y1: 103, + xo: -2.000000, + yo: -13.000000, + xa: 4.016949, + }, + BakedGlyph { + x0: 56, + y0: 86, + x1: 64, + y1: 100, + xo: 1.000000, + yo: -14.000000, + xa: 8.389831, + }, + BakedGlyph { + x0: 65, + y0: 86, + x1: 68, + y1: 101, + xo: 1.000000, + yo: -14.000000, + xa: 4.322034, + }, + BakedGlyph { + x0: 69, + y0: 86, + x1: 82, + y1: 95, + xo: 1.000000, + yo: -9.000000, + xa: 14.627119, + }, + BakedGlyph { + x0: 83, + y0: 86, + x1: 91, + y1: 95, + xo: 1.000000, + yo: -9.000000, + xa: 9.644068, + }, + BakedGlyph { + x0: 92, + y0: 86, + x1: 101, + y1: 96, + xo: 0.000000, + yo: -9.000000, + xa: 9.864407, + }, + BakedGlyph { + x0: 102, + y0: 86, + x1: 110, + y1: 99, + xo: 1.000000, + yo: -9.000000, + xa: 9.881356, + }, + BakedGlyph { + x0: 111, + y0: 86, + x1: 120, + y1: 99, + xo: 0.000000, + yo: -9.000000, + xa: 9.881356, + }, + BakedGlyph { + x0: 1, + y0: 104, + x1: 7, + y1: 113, + xo: 1.000000, + yo: -9.000000, + xa: 6.338983, + }, + BakedGlyph { + x0: 8, + y0: 104, + x1: 15, + y1: 114, + xo: 0.000000, + yo: -9.000000, + xa: 7.254237, + }, + BakedGlyph { + x0: 16, + y0: 104, + x1: 22, + y1: 117, + xo: 1.000000, + yo: -12.000000, + xa: 6.559322, + }, + BakedGlyph { + x0: 23, + y0: 104, + x1: 31, + y1: 114, + xo: 1.000000, + yo: -9.000000, + xa: 9.644068, + }, + BakedGlyph { + x0: 32, + y0: 104, + x1: 40, + y1: 113, + xo: 0.000000, + yo: -9.000000, + xa: 8.135593, + }, + BakedGlyph { + x0: 41, + y0: 104, + x1: 54, + y1: 113, + xo: 0.000000, + yo: -9.000000, + xa: 13.135593, + }, + BakedGlyph { + x0: 55, + y0: 104, + x1: 63, + y1: 113, + xo: 0.000000, + yo: -9.000000, + xa: 8.457627, + }, + BakedGlyph { + x0: 64, + y0: 104, + x1: 72, + y1: 117, + xo: 0.000000, + yo: -9.000000, + xa: 8.033898, + }, + BakedGlyph { + x0: 73, + y0: 104, + x1: 81, + y1: 113, + xo: 0.000000, + yo: -9.000000, + xa: 7.711864, + }, + BakedGlyph { + x0: 82, + y0: 104, + x1: 88, + y1: 122, + xo: 0.000000, + yo: -14.000000, + xa: 5.406780, + }, + BakedGlyph { + x0: 89, + y0: 104, + x1: 91, + y1: 122, + xo: 1.000000, + yo: -14.000000, + xa: 4.440678, + }, + BakedGlyph { + x0: 92, + y0: 104, + x1: 97, + y1: 122, + xo: 0.000000, + yo: -14.000000, + xa: 5.406780, + }, + BakedGlyph { + x0: 98, + y0: 104, + x1: 107, + y1: 108, + xo: 0.000000, + yo: -7.000000, + xa: 9.559322, + }, + BakedGlyph { + x0: 108, + y0: 104, + x1: 116, + y1: 117, + xo: 0.000000, + yo: -13.000000, + xa: 8.474576, + }, +]; + +pub const FONT_BITMAP: [u8; 16384] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x34, 0xae, 0x00, 0x00, 0x81, 0x90, 0x00, 0xe0, 0x31, 0x00, 0x00, 0x00, 0x00, 0x06, + 0xba, 0x05, 0x00, 0x00, 0xa2, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x0d, 0x00, 0x00, 0x00, + 0x00, 0x16, 0xb2, 0xec, 0xbc, 0x1f, 0x00, 0x00, 0x00, 0x49, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0d, 0x9d, 0xeb, 0xe5, 0x89, 0x03, 0x00, 0x00, 0x00, 0x00, 0x81, 0x90, 0x00, 0x00, 0x00, + 0x00, 0x13, 0x00, 0x00, 0x11, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x6b, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x76, 0x00, + 0x3b, 0x70, 0x70, 0x70, 0x22, 0x00, 0x56, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2d, 0x1e, 0x00, 0x00, 0x18, 0xac, 0xea, 0xd2, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x45, 0xe9, 0x00, 0x00, 0x8a, 0x99, 0x00, 0xf6, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x2f, + 0xd8, 0x00, 0x00, 0x08, 0xf7, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0x52, 0x00, 0x00, 0x00, + 0x00, 0xa5, 0x8f, 0x0c, 0x7d, 0xba, 0x00, 0x00, 0x06, 0xda, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x92, 0xb9, 0x20, 0x28, 0xd7, 0x64, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x99, 0x00, 0x00, 0x00, + 0x28, 0xdd, 0x03, 0x00, 0xa5, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x7f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb6, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xaa, 0x00, + 0x54, 0xa0, 0xa0, 0xa0, 0x31, 0x00, 0xbb, 0xe4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xe0, 0x34, 0x00, 0x0b, 0xd7, 0x95, 0x1d, 0x40, 0xe1, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x45, 0xe9, 0x00, 0x00, 0x7a, 0x88, 0x00, 0xe6, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x60, + 0xa8, 0x00, 0x00, 0x34, 0xd3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x6b, 0x0b, 0x00, 0x00, + 0x00, 0xea, 0x22, 0x00, 0x10, 0xf7, 0x05, 0x00, 0x73, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xd6, 0x50, 0x00, 0x00, 0x81, 0x98, 0x00, 0x00, 0x00, 0x00, 0x7a, 0x88, 0x00, 0x00, 0x00, + 0xb4, 0x6a, 0x00, 0x00, 0x2e, 0xe4, 0x0d, 0x00, 0x00, 0x24, 0xcd, 0x61, 0x65, 0x62, 0x61, 0xcd, + 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb6, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x83, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, + 0xda, 0x00, 0x00, 0x5e, 0xd0, 0x03, 0x00, 0x00, 0x49, 0xe6, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x44, 0xe9, 0x00, 0x00, 0x5a, 0x67, 0x00, 0xbf, 0x02, 0x00, 0x1b, 0x86, 0x86, 0xc4, + 0xc5, 0x86, 0x86, 0xb0, 0xd9, 0x86, 0x24, 0x00, 0x00, 0x57, 0xc9, 0xea, 0xd3, 0xef, 0x85, 0x00, + 0x00, 0xed, 0x1e, 0x00, 0x0d, 0xf7, 0x07, 0x15, 0xe8, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xbf, 0x65, 0x00, 0x00, 0xc6, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x67, 0x00, 0x00, 0x47, + 0xd7, 0x04, 0x00, 0x00, 0x00, 0x9d, 0x85, 0x00, 0x00, 0x0d, 0x55, 0x8d, 0xcc, 0xcc, 0x8c, 0x54, + 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb6, 0x45, 0x00, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x2e, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, + 0x81, 0x00, 0x00, 0xb7, 0x80, 0x00, 0x00, 0x00, 0x04, 0xf1, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3d, 0xe3, 0x00, 0x00, 0x0f, 0x12, 0x00, 0x21, 0x00, 0x00, 0x13, 0x5f, 0x5f, 0xe0, + 0x86, 0x5f, 0x5f, 0xc2, 0x9f, 0x5f, 0x1a, 0x00, 0x16, 0xf9, 0x3d, 0x01, 0x00, 0x03, 0x13, 0x00, + 0x00, 0xae, 0x80, 0x04, 0x6c, 0xc4, 0x00, 0x96, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x56, 0xe2, 0x1f, 0x8e, 0xc2, 0x06, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x12, 0x00, 0x00, 0xc7, + 0x4f, 0x00, 0x00, 0x00, 0x00, 0x1a, 0xee, 0x0d, 0x00, 0x00, 0x00, 0x3b, 0xb4, 0xb5, 0x37, 0x00, + 0x00, 0x00, 0x05, 0x5f, 0x5f, 0x5f, 0xd1, 0x8a, 0x5f, 0x5f, 0x3a, 0x00, 0x0b, 0x76, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xea, + 0x27, 0x00, 0x00, 0xd8, 0x4f, 0x00, 0x00, 0x00, 0x00, 0xc2, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x35, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xf2, + 0x16, 0x00, 0x00, 0xc6, 0x3f, 0x00, 0x00, 0x00, 0x65, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1f, 0xc7, 0xef, 0xd1, 0x29, 0x2c, 0xe4, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0d, 0xd1, 0xfa, 0x8f, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xf6, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0xca, 0x3b, 0x00, 0x00, 0x23, 0xec, 0x3c, 0x3d, 0xea, 0x21, + 0x00, 0x00, 0x07, 0x93, 0x93, 0x93, 0xe0, 0xb0, 0x93, 0x93, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0xcd, + 0x00, 0x00, 0x00, 0xf0, 0x3e, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x25, 0xc5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0xe4, + 0x00, 0x00, 0x03, 0xf4, 0x0f, 0x00, 0x00, 0x00, 0x4d, 0xec, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0xba, 0x63, 0x07, 0x82, 0xc2, 0x8d, 0x0c, 0x00, 0x00, 0x00, + 0x24, 0xda, 0x7b, 0x97, 0xc2, 0x0b, 0x00, 0x01, 0xa2, 0x03, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xe1, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa3, 0x69, 0x00, 0x00, 0x0a, 0x5b, 0x00, 0x00, 0x5d, 0x0a, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb6, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x74, + 0x00, 0x00, 0x00, 0xf6, 0x3a, 0x00, 0x00, 0x00, 0x00, 0xab, 0x85, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x09, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x1e, 0x67, 0xbe, + 0x1e, 0x1e, 0x40, 0xe4, 0x1e, 0x1e, 0x08, 0x00, 0x01, 0x96, 0xf2, 0x91, 0x2b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0xce, 0x02, 0x8c, 0xb1, 0x37, 0xa3, 0xa2, 0x00, 0x00, 0x00, + 0xc6, 0x75, 0x00, 0x01, 0x9c, 0xbe, 0x0a, 0x28, 0xd1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0xbc, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb6, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0xf1, 0x1c, + 0x00, 0x00, 0x00, 0xde, 0x49, 0x00, 0x00, 0x00, 0x00, 0xbc, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0xcb, 0xea, 0xe2, + 0xcb, 0xcb, 0xe1, 0xeb, 0xcb, 0xcb, 0x37, 0x00, 0x00, 0x00, 0x2d, 0x93, 0xee, 0xb4, 0x1f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x05, 0xd8, 0x40, 0x00, 0xe2, 0x2e, 0x00, 0x19, 0xf5, 0x02, 0x00, 0x13, + 0xff, 0x1d, 0x00, 0x00, 0x02, 0xa1, 0xbb, 0x94, 0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x96, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb6, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0xc0, 0x00, + 0x00, 0x00, 0x00, 0xc4, 0x6e, 0x00, 0x00, 0x00, 0x00, 0xe2, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb6, 0x52, + 0x00, 0x00, 0x8b, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xa7, 0xd7, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x70, 0xad, 0x00, 0x00, 0xf2, 0x15, 0x00, 0x06, 0xf7, 0x0a, 0x00, 0x10, + 0xff, 0x23, 0x00, 0x00, 0x00, 0x03, 0xb2, 0xfb, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x9b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb3, 0x67, 0x00, + 0x00, 0x00, 0x00, 0x77, 0xb5, 0x00, 0x00, 0x00, 0x2b, 0xf5, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x33, 0x8c, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x20, + 0x00, 0x00, 0xbd, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xfc, 0x2c, + 0x00, 0x00, 0x00, 0x13, 0xe7, 0x23, 0x00, 0x00, 0xc1, 0x5c, 0x00, 0x47, 0xd7, 0x00, 0x00, 0x00, + 0xb5, 0xaf, 0x0d, 0x00, 0x0a, 0x60, 0xe1, 0xbd, 0xa5, 0x01, 0x00, 0x00, 0x00, 0x00, 0x51, 0xc1, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xf4, 0x13, 0x00, + 0x00, 0x00, 0x00, 0x19, 0xf3, 0x57, 0x00, 0x0b, 0xc0, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7f, 0xfe, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0xec, 0x00, + 0x00, 0x01, 0xee, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0xef, 0x0c, + 0x00, 0x00, 0x00, 0x94, 0x8a, 0x00, 0x00, 0x00, 0x35, 0xe8, 0xc4, 0xed, 0x44, 0x00, 0x00, 0x00, + 0x15, 0xa9, 0xf1, 0xdc, 0xea, 0xaf, 0x1e, 0x07, 0xc7, 0x73, 0x00, 0x00, 0x00, 0x00, 0x24, 0xe6, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0xb3, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3c, 0xea, 0xdb, 0xf2, 0xae, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0x9b, 0x61, 0x52, 0x6c, 0xd1, 0x89, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x34, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x1b, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xf4, + 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcf, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x5a, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x2c, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x69, 0x8f, 0xe2, 0xa5, 0x28, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xba, + 0x5e, 0x00, 0x00, 0x00, 0x00, 0x26, 0xe9, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0xf2, 0x0b, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0x52, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, + 0xe1, 0x0a, 0x00, 0x00, 0x00, 0xaf, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xa6, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x93, 0x46, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xa1, 0x7d, 0x00, 0x00, 0x3e, 0xd9, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcf, 0x4d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1a, 0xd2, 0x03, 0x00, 0xa0, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xed, 0x06, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x05, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x1a, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x28, 0xb6, 0x00, 0x14, 0x93, 0xe1, 0xe8, 0x99, 0x42, 0x00, 0x00, 0x00, + 0x2b, 0xaa, 0xea, 0xf3, 0xca, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0xbd, + 0x30, 0x00, 0x00, 0x00, 0x63, 0xbe, 0xbe, 0xbe, 0xbe, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, + 0x5a, 0x8c, 0x92, 0x00, 0x00, 0x9e, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0x3a, 0x00, 0x00, 0x39, + 0xbe, 0xed, 0xde, 0x94, 0x0f, 0x00, 0x00, 0x00, 0x47, 0xc5, 0xf0, 0xce, 0x62, 0x00, 0x00, 0x00, + 0x6a, 0x88, 0x00, 0x00, 0x00, 0x6a, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2a, 0x29, 0x00, 0x02, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x18, 0x00, 0x00, 0x4a, 0x0a, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xd0, 0xf3, 0xe5, 0xa3, 0x17, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x36, 0xe8, 0xfb, 0x00, 0x98, 0x8a, 0x21, 0x16, 0x68, 0xfb, 0x1d, 0x00, 0x00, + 0x3a, 0x6a, 0x22, 0x11, 0x62, 0xf8, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0xd7, 0xed, + 0x41, 0x00, 0x00, 0x00, 0x8e, 0x8e, 0x38, 0x38, 0x38, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x42, 0xdd, + 0xa9, 0x5c, 0x2b, 0x00, 0x00, 0x39, 0x45, 0x45, 0x45, 0x45, 0x5a, 0xf1, 0x24, 0x00, 0x25, 0xef, + 0x54, 0x05, 0x17, 0xad, 0xb3, 0x00, 0x00, 0x3e, 0xe9, 0x46, 0x08, 0x39, 0xe1, 0x65, 0x00, 0x00, + 0xaf, 0xd8, 0x00, 0x00, 0x00, 0xaf, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x50, 0xbc, + 0xe2, 0x4f, 0x00, 0x0a, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0x7c, 0x00, 0x02, 0xaa, 0xe9, + 0x8b, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x49, 0x0e, 0x16, 0xae, 0xb1, 0x00, 0x00, 0x00, + 0x00, 0x0d, 0xad, 0xdf, 0x61, 0xfb, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xba, 0x7a, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xac, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xc6, 0x50, 0xd4, + 0x41, 0x00, 0x00, 0x00, 0x9b, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0xf1, 0x36, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xae, 0x71, 0x00, 0x00, 0x7d, 0xa3, + 0x00, 0x00, 0x00, 0x18, 0xff, 0x13, 0x00, 0xb6, 0x78, 0x00, 0x00, 0x00, 0x4b, 0xe0, 0x04, 0x00, + 0x02, 0x05, 0x00, 0x00, 0x00, 0x02, 0x05, 0x00, 0x00, 0x00, 0x00, 0x13, 0x76, 0xdf, 0xbe, 0x53, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, + 0x84, 0xe6, 0xb1, 0x45, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0xeb, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x58, 0x08, 0x1e, 0xfb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0xa8, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x8a, 0x00, 0xd4, + 0x41, 0x00, 0x00, 0x00, 0xad, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xd0, 0x54, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0xd4, 0x02, 0x00, 0x00, 0x8b, 0x93, + 0x00, 0x00, 0x00, 0x1e, 0xff, 0x18, 0x00, 0xe2, 0x4c, 0x00, 0x00, 0x00, 0x06, 0xfb, 0x24, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x9d, 0xe9, 0x94, 0x2a, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x5b, 0xc5, 0xd6, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0xbd, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1e, 0xfb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x69, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3e, 0xe4, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x39, 0xd0, 0x07, 0x00, 0xd4, + 0x41, 0x00, 0x00, 0x00, 0xc5, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0xeb, 0x04, 0x01, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xcc, 0x58, 0x00, 0x00, 0x00, 0x45, 0xe5, + 0x1a, 0x00, 0x00, 0x74, 0xc5, 0x00, 0x00, 0xd2, 0x80, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x41, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0xd8, 0xcb, 0x58, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x5c, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x21, 0x89, 0xef, 0x73, 0x00, 0x00, 0x00, 0x00, 0x2b, 0xe6, 0x32, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1e, 0xfb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xd1, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x82, 0xd2, 0xed, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcd, 0x3d, 0x00, 0x00, 0xd4, + 0x41, 0x00, 0x00, 0x00, 0xc5, 0xf6, 0xe8, 0xb5, 0x3e, 0x00, 0x00, 0x00, 0x71, 0xdc, 0xc6, 0xef, + 0xf3, 0xc1, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0xd8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x5c, + 0xe9, 0x8e, 0x7d, 0xd8, 0x20, 0x00, 0x00, 0x82, 0xd4, 0x1c, 0x00, 0x00, 0x16, 0xed, 0x39, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x46, 0xb3, 0xe4, 0x81, 0x1b, + 0x00, 0x00, 0x00, 0x05, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x37, 0x00, 0x00, 0x00, 0x01, + 0x47, 0xb2, 0xe5, 0x81, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x1d, 0xdf, 0x49, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1e, 0xfb, 0x00, 0x00, 0x00, 0x00, 0x67, 0xe2, 0x1f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x25, 0x4b, 0x95, 0xea, 0x3f, 0x00, 0x00, 0x00, 0x63, 0xac, 0x00, 0x00, 0x00, 0xd4, + 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x6c, 0xee, 0x5c, 0x00, 0x00, 0x94, 0xb6, 0x29, 0x02, + 0x1d, 0x8a, 0xef, 0x11, 0x00, 0x00, 0x00, 0x00, 0xaf, 0x78, 0x00, 0x00, 0x00, 0x00, 0x03, 0x9e, + 0xc5, 0x6f, 0xc8, 0xc2, 0x19, 0x00, 0x00, 0x0c, 0xab, 0xfd, 0xc6, 0xcd, 0xdb, 0xff, 0x17, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x8d, 0xea, + 0xab, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x71, 0xda, + 0xc7, 0x5b, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1e, 0xfb, 0x00, 0x00, 0x00, 0x6f, 0xe1, 0x24, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0xe0, 0x01, 0x00, 0x02, 0xd9, 0x45, 0x16, 0x16, 0x16, 0xd7, + 0x51, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0xed, 0x01, 0x00, 0xa1, 0x81, 0x00, 0x00, + 0x00, 0x06, 0xf1, 0x65, 0x00, 0x00, 0x00, 0x0d, 0xf6, 0x20, 0x00, 0x00, 0x00, 0x00, 0x76, 0xc8, + 0x07, 0x00, 0x00, 0x63, 0xe6, 0x0a, 0x00, 0x00, 0x00, 0x16, 0x3b, 0x26, 0x3e, 0xf2, 0x00, 0x00, + 0x56, 0x70, 0x00, 0x00, 0x00, 0x4a, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, + 0x66, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x35, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1e, 0xfb, 0x00, 0x00, 0x59, 0xe6, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xff, 0x1c, 0x00, 0x1f, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xf7, + 0xdc, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xff, 0x20, 0x00, 0x64, 0x92, 0x00, 0x00, + 0x00, 0x00, 0xb4, 0x81, 0x00, 0x00, 0x00, 0x5a, 0xc7, 0x00, 0x00, 0x00, 0x00, 0x00, 0xca, 0x72, + 0x00, 0x00, 0x00, 0x00, 0xdc, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8d, 0x9b, 0x00, 0x00, + 0xbb, 0xe4, 0x00, 0x00, 0x00, 0x80, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1e, 0xfb, 0x00, 0x13, 0xec, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0xfc, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, + 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xfe, 0x0d, 0x00, 0x21, 0xcc, 0x00, 0x00, + 0x00, 0x00, 0xca, 0x63, 0x00, 0x00, 0x00, 0x9b, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0xca, 0x5e, + 0x00, 0x00, 0x00, 0x00, 0xce, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0xed, 0x18, 0x00, 0x00, + 0x09, 0x11, 0x00, 0x00, 0x00, 0x9b, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1e, 0xfb, 0x00, 0x6d, 0xb6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x25, 0x04, 0x00, 0x01, 0x32, 0xc1, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, + 0x41, 0x00, 0x00, 0x22, 0x06, 0x00, 0x00, 0x10, 0xb0, 0xac, 0x00, 0x00, 0x00, 0xdd, 0x5b, 0x00, + 0x00, 0x4b, 0xec, 0x10, 0x00, 0x00, 0x00, 0xc6, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7a, 0xcb, + 0x11, 0x00, 0x00, 0x52, 0xeb, 0x14, 0x00, 0x00, 0x03, 0x22, 0x88, 0xda, 0x4b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x8e, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1e, 0xfb, 0x00, 0x9b, 0xfb, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0x21, 0x00, + 0xa4, 0xf0, 0xcc, 0xe7, 0xfd, 0x9d, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, + 0x41, 0x00, 0x00, 0x8f, 0xf2, 0xce, 0xd2, 0xf4, 0xa2, 0x13, 0x00, 0x00, 0x00, 0x33, 0xe0, 0xd8, + 0xcd, 0xe0, 0x3a, 0x00, 0x00, 0x00, 0x01, 0xf1, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x91, + 0xea, 0xbe, 0xcf, 0xdc, 0x3f, 0x00, 0x00, 0x52, 0xe1, 0xbf, 0x89, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0b, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xfe, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x17, 0x39, 0x2e, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x12, 0x37, 0x30, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x24, + 0x28, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0e, 0x34, 0x24, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0e, 0x7d, 0xb2, 0xdc, 0xe5, 0xb8, 0x84, 0x14, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x30, 0xbd, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xb2, 0xce, 0xd4, + 0xc2, 0x9a, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x90, 0xce, 0xf7, 0xea, 0xb6, 0x4c, 0x00, + 0x45, 0xb1, 0xcb, 0xd0, 0xb0, 0x8b, 0x34, 0x00, 0x00, 0x00, 0x00, 0x5a, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0x3a, 0x00, 0x5a, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0x2a, 0x00, 0x00, 0x00, 0x11, + 0x8e, 0xcb, 0xf4, 0xdf, 0xbb, 0x5f, 0x00, 0x5a, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb4, + 0x27, 0x00, 0x5a, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x94, 0x00, 0x5a, 0x81, 0x00, + 0x00, 0x00, 0x00, 0x21, 0xb7, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x54, 0xe4, 0x9c, 0x4a, 0x1d, 0x13, 0x45, 0x8c, 0xe7, 0x5c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xa6, 0xec, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0xc0, 0x2c, 0x27, + 0x39, 0x74, 0xeb, 0x79, 0x00, 0x00, 0x00, 0x37, 0xe5, 0x9f, 0x43, 0x16, 0x29, 0x63, 0x62, 0x00, + 0x79, 0xc1, 0x31, 0x2d, 0x4f, 0x7b, 0xe7, 0xa6, 0x0f, 0x00, 0x00, 0x79, 0xc2, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x14, 0x00, 0x79, 0xc2, 0x41, 0x41, 0x41, 0x41, 0x41, 0x0e, 0x00, 0x00, 0x37, 0xe4, + 0x9e, 0x3b, 0x13, 0x28, 0x5b, 0x83, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, + 0x34, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0xc7, 0x00, 0x79, 0xad, 0x00, + 0x00, 0x00, 0x1b, 0xda, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x39, 0xeb, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0xe5, 0x44, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1f, 0xed, 0x45, 0xea, 0x09, 0x00, 0x00, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, + 0x00, 0x00, 0x42, 0xf4, 0x06, 0x00, 0x08, 0xd9, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x10, 0xb6, 0xa5, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xda, 0x73, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, + 0x34, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0xc7, 0x00, 0x79, 0xad, 0x00, + 0x00, 0x1b, 0xd9, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x07, 0xd6, 0x67, 0x00, 0x00, 0x19, 0x63, 0x86, 0x70, 0x36, 0x00, 0x53, 0xe2, 0x0e, 0x00, + 0x00, 0x00, 0x00, 0x89, 0x93, 0x00, 0xcb, 0x61, 0x00, 0x00, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, + 0x00, 0x00, 0x1b, 0xfd, 0x13, 0x00, 0x72, 0xd4, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0xf0, 0x4d, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0xd1, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, + 0x34, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0xc7, 0x00, 0x79, 0xad, 0x00, + 0x1d, 0xd9, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x4f, 0xd4, 0x02, 0x00, 0x3f, 0xe7, 0x94, 0x64, 0x8d, 0xe1, 0x00, 0x00, 0xc8, 0x50, 0x00, + 0x00, 0x00, 0x08, 0xea, 0x29, 0x00, 0x60, 0xc9, 0x00, 0x00, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, + 0x00, 0x00, 0x80, 0xc2, 0x00, 0x00, 0xac, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x8d, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xac, 0x81, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, + 0x34, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0xc7, 0x00, 0x79, 0xad, 0x22, + 0xda, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x7b, 0x99, 0x00, 0x05, 0xdc, 0x5a, 0x00, 0x00, 0x34, 0xe1, 0x00, 0x00, 0x91, 0x82, 0x00, + 0x00, 0x00, 0x5d, 0xc5, 0x00, 0x00, 0x0b, 0xf1, 0x32, 0x00, 0x00, 0x00, 0x79, 0xdf, 0x9c, 0x9c, + 0xa6, 0xd5, 0xcc, 0x12, 0x00, 0x00, 0xd7, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xbb, 0x00, 0x79, 0xe3, 0xa9, 0xa9, 0xa9, + 0xa9, 0x75, 0x00, 0x00, 0x79, 0xdf, 0x9c, 0x9c, 0x9c, 0x9c, 0x5c, 0x00, 0x00, 0xd7, 0x5c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0xe2, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xfa, + 0x34, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0xc7, 0x00, 0x79, 0xcf, 0xe6, + 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xa7, 0x70, 0x00, 0x2b, 0xf3, 0x01, 0x00, 0x00, 0x34, 0xe1, 0x00, 0x00, 0x6c, 0xab, 0x00, + 0x00, 0x00, 0xc1, 0x64, 0x00, 0x00, 0x00, 0x99, 0x97, 0x00, 0x00, 0x00, 0x79, 0xcc, 0x5f, 0x5f, + 0x66, 0x80, 0xd4, 0xc1, 0x0f, 0x00, 0xe2, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xc6, 0x00, 0x79, 0xc7, 0x52, 0x52, 0x52, + 0x52, 0x39, 0x00, 0x00, 0x79, 0xcd, 0x63, 0x63, 0x63, 0x63, 0x3b, 0x00, 0x00, 0xe2, 0x52, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x16, 0x8f, 0x00, 0x79, 0xca, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0xf7, + 0x34, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0xc7, 0x00, 0x79, 0xc5, 0xba, + 0xd2, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xb9, 0x60, 0x00, 0x3d, 0xe4, 0x00, 0x00, 0x00, 0x34, 0xe1, 0x00, 0x00, 0x79, 0x98, 0x00, + 0x00, 0x24, 0xfb, 0x41, 0x34, 0x34, 0x34, 0x68, 0xf0, 0x0a, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, + 0x00, 0x00, 0x08, 0xce, 0x80, 0x00, 0xbb, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x98, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x72, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x27, 0xff, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, + 0x34, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0xc7, 0x00, 0x79, 0xad, 0x01, + 0x74, 0xea, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x8f, 0x87, 0x00, 0x16, 0xfd, 0x15, 0x00, 0x00, 0x34, 0xe1, 0x00, 0x00, 0xb4, 0x6d, 0x00, + 0x00, 0x85, 0xe3, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xee, 0x5e, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x89, 0xb2, 0x00, 0x90, 0xae, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xdb, 0x66, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xaf, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x27, 0xff, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, + 0x34, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xc0, 0x00, 0x79, 0xad, 0x00, + 0x00, 0x50, 0xec, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x64, 0xb2, 0x00, 0x00, 0xa8, 0xae, 0x0e, 0x00, 0x3d, 0xfb, 0x16, 0x4b, 0xec, 0x0f, 0x00, + 0x02, 0xe2, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xbf, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xb4, 0x90, 0x00, 0x21, 0xf5, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0xce, 0x06, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xf6, 0x32, + 0x00, 0x00, 0x00, 0x00, 0x27, 0xff, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, + 0x34, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0xa5, 0x00, 0x79, 0xad, 0x00, + 0x00, 0x00, 0x52, 0xee, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x2a, 0xf3, 0x24, 0x00, 0x0f, 0x96, 0xe4, 0xe3, 0xc4, 0xa4, 0xf0, 0xcd, 0x33, 0x00, 0x00, + 0x45, 0xe5, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xfd, 0x22, 0x00, 0x79, 0xad, 0x00, 0x00, + 0x00, 0x21, 0x8d, 0xed, 0x2a, 0x00, 0x00, 0x74, 0xec, 0x50, 0x04, 0x00, 0x00, 0x11, 0x33, 0x00, + 0x79, 0xad, 0x00, 0x00, 0x0d, 0x37, 0xb5, 0xdf, 0x27, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xec, + 0x53, 0x05, 0x00, 0x00, 0x28, 0xff, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, + 0x34, 0x00, 0x79, 0xad, 0x00, 0x4f, 0x28, 0x00, 0x04, 0x48, 0xf5, 0x51, 0x00, 0x79, 0xad, 0x00, + 0x00, 0x00, 0x00, 0x6e, 0xe1, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x8a, 0xbf, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xa4, 0x8d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc2, 0x81, 0x00, 0x65, 0xf3, 0xeb, 0xe4, + 0xef, 0xe5, 0x97, 0x29, 0x00, 0x00, 0x00, 0x00, 0x4c, 0xdd, 0xf5, 0xd1, 0xe2, 0xf7, 0x95, 0x00, + 0x65, 0xf3, 0xef, 0xeb, 0xf0, 0xcc, 0x74, 0x07, 0x00, 0x00, 0x00, 0x79, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xbe, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4b, + 0xd9, 0xf7, 0xd1, 0xd4, 0xf2, 0xd1, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, + 0x34, 0x00, 0x79, 0xad, 0x00, 0x75, 0xf0, 0xd8, 0xe9, 0xfd, 0x86, 0x02, 0x00, 0x79, 0xad, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xad, 0xa9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x08, 0xcf, 0xbd, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x11, 0x15, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x3a, 0x30, 0x0a, 0x00, 0x00, + 0x00, 0x02, 0x10, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x34, 0x24, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x30, 0x33, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x78, 0xeb, 0xba, 0x91, 0x72, 0x7c, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x41, 0x68, 0x84, 0x76, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x5a, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x37, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x6e, 0x77, 0x00, 0x00, 0x5a, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7a, 0x5d, 0x00, 0x00, 0x00, 0x13, 0x93, 0xcd, 0xf3, 0xd3, 0x9e, 0x1e, 0x00, 0x00, 0x00, 0x00, + 0x44, 0xaf, 0xca, 0xd3, 0xbc, 0x9b, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x93, 0xcd, 0xf3, + 0xd3, 0x9e, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x44, 0xaf, 0xca, 0xd3, 0xbc, 0x99, 0x36, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x09, 0x63, 0xab, 0xee, 0xed, 0xb8, 0x4b, 0x00, 0x00, 0x84, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbb, 0x00, 0x64, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xbe, 0x0a, + 0x00, 0x85, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xb1, 0x34, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0xff, 0xc4, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1a, 0xf4, 0xb2, 0x00, 0x00, 0x79, 0xff, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xa4, 0x7d, 0x00, 0x00, 0x3c, 0xe8, 0x9c, 0x3f, 0x16, 0x39, 0x8c, 0xee, 0x4f, 0x00, 0x00, 0x00, + 0x79, 0xc2, 0x34, 0x2b, 0x3d, 0x7d, 0xed, 0x83, 0x00, 0x00, 0x00, 0x3c, 0xe8, 0x9c, 0x3f, 0x16, + 0x39, 0x8c, 0xee, 0x4f, 0x00, 0x00, 0x00, 0x79, 0xc2, 0x35, 0x2b, 0x3c, 0x7b, 0xed, 0x78, 0x00, + 0x00, 0x00, 0x00, 0x6a, 0xc2, 0x30, 0x08, 0x1e, 0x5e, 0x58, 0x00, 0x00, 0x2d, 0x41, 0x41, 0x41, + 0xf2, 0x6b, 0x41, 0x41, 0x40, 0x00, 0x86, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0xff, 0x0d, + 0x00, 0x63, 0xe1, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0xed, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xef, 0xdd, 0x4e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x98, 0xcd, 0xc8, 0x00, 0x00, 0x79, 0xc4, 0xe6, 0x4b, 0x00, 0x00, 0x00, 0x00, + 0xa4, 0x7d, 0x00, 0x09, 0xdd, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0xed, 0x16, 0x00, 0x00, + 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x32, 0xfe, 0x27, 0x00, 0x09, 0xdd, 0x71, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x53, 0xed, 0x16, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x3b, 0xfd, 0x1a, + 0x00, 0x00, 0x02, 0xd8, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xee, 0x38, 0x00, 0x00, 0x00, 0x00, 0x86, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0xff, 0x0d, + 0x00, 0x0f, 0xf7, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0xd8, 0x61, 0xcb, 0x01, 0x00, + 0x00, 0x00, 0x1f, 0xea, 0x4a, 0xdd, 0x00, 0x00, 0x79, 0xa9, 0x3d, 0xe9, 0x1f, 0x00, 0x00, 0x00, + 0xa4, 0x7d, 0x00, 0x76, 0xd4, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x94, 0x00, 0x00, + 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0x5b, 0x00, 0x76, 0xd4, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xb9, 0x94, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0xeb, 0x49, + 0x00, 0x00, 0x09, 0xfc, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xee, 0x38, 0x00, 0x00, 0x00, 0x00, 0x86, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0xff, 0x0d, + 0x00, 0x00, 0xac, 0x93, 0x00, 0x00, 0x00, 0x00, 0x04, 0xe9, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0xc4, 0x05, 0xdf, 0x4a, 0x00, + 0x00, 0x00, 0x97, 0x84, 0x27, 0xf3, 0x00, 0x00, 0x79, 0xa9, 0x00, 0x79, 0xc8, 0x06, 0x00, 0x00, + 0xa4, 0x7d, 0x00, 0xad, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0xcb, 0x00, 0x00, + 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x01, 0xed, 0x47, 0x00, 0xad, 0x81, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x62, 0xcb, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x09, 0xf9, 0x1f, + 0x00, 0x00, 0x00, 0xaf, 0xc5, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xee, 0x38, 0x00, 0x00, 0x00, 0x00, 0x86, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0xff, 0x0d, + 0x00, 0x00, 0x4f, 0xea, 0x05, 0x00, 0x00, 0x00, 0x49, 0xdc, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0xb1, 0x00, 0x6a, 0xc2, 0x00, + 0x00, 0x18, 0xed, 0x15, 0x14, 0xff, 0x09, 0x00, 0x79, 0xa9, 0x00, 0x02, 0xbc, 0x88, 0x00, 0x00, + 0xa4, 0x7d, 0x00, 0xd8, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0xf4, 0x02, 0x00, + 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x75, 0xeb, 0x10, 0x00, 0xd8, 0x5c, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x3e, 0xf4, 0x02, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x08, 0x95, 0xc4, 0x00, + 0x00, 0x00, 0x00, 0x0e, 0x9f, 0xf5, 0x9e, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xee, 0x38, 0x00, 0x00, 0x00, 0x00, 0x86, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0xff, 0x0d, + 0x00, 0x00, 0x05, 0xe8, 0x4a, 0x00, 0x00, 0x00, 0xa4, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x9d, 0x00, 0x09, 0xe8, 0x3a, + 0x00, 0x88, 0x92, 0x00, 0x03, 0xfd, 0x1b, 0x00, 0x79, 0xa9, 0x00, 0x00, 0x1b, 0xea, 0x3c, 0x00, + 0xa4, 0x7d, 0x00, 0xe1, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0xfa, 0x05, 0x00, + 0x79, 0xd9, 0x8a, 0x90, 0xa1, 0xdb, 0xda, 0x38, 0x00, 0x00, 0xe2, 0x54, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x35, 0xfc, 0x06, 0x00, 0x79, 0xe9, 0xba, 0xbd, 0xc9, 0xf2, 0x90, 0x0f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x92, 0xf2, 0xa9, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xee, 0x38, 0x00, 0x00, 0x00, 0x00, 0x86, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0xff, 0x0d, + 0x00, 0x00, 0x00, 0x8c, 0xa9, 0x00, 0x00, 0x0f, 0xf3, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x90, 0x00, 0x00, 0x79, 0xb0, + 0x0d, 0xea, 0x1f, 0x00, 0x00, 0xf2, 0x29, 0x00, 0x79, 0xa9, 0x00, 0x00, 0x00, 0x57, 0xdc, 0x0b, + 0xa4, 0x7d, 0x00, 0xb8, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0xd6, 0x00, 0x00, + 0x79, 0xd2, 0x75, 0x6f, 0x5a, 0x37, 0x01, 0x00, 0x00, 0x00, 0xb9, 0x77, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x59, 0xdc, 0x00, 0x00, 0x79, 0xc2, 0x41, 0x40, 0x4a, 0xf0, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0xc5, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xee, 0x38, 0x00, 0x00, 0x00, 0x00, 0x84, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0x0b, + 0x00, 0x00, 0x00, 0x29, 0xf7, 0x12, 0x00, 0x66, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x91, 0x85, 0x00, 0x00, 0x10, 0xf0, + 0x96, 0xa0, 0x00, 0x00, 0x00, 0xe7, 0x38, 0x00, 0x79, 0xa9, 0x00, 0x00, 0x00, 0x00, 0xab, 0x8a, + 0xa4, 0x7d, 0x00, 0x8b, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0xa9, 0x00, 0x00, + 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8b, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x9a, 0xab, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x62, 0xe3, 0x12, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xfd, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xee, 0x38, 0x00, 0x00, 0x00, 0x00, 0x6b, 0xc2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0xef, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbe, 0x6e, 0x00, 0xca, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9f, 0x7a, 0x00, 0x00, 0x00, 0x8e, + 0xff, 0x2d, 0x00, 0x00, 0x00, 0xdb, 0x47, 0x00, 0x79, 0xa9, 0x00, 0x00, 0x00, 0x00, 0x16, 0xe8, + 0xcf, 0x7d, 0x00, 0x1d, 0xf3, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0xf7, 0x31, 0x00, 0x00, + 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0xf4, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x29, 0xf9, 0x32, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0xb1, 0xa4, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0xe9, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xee, 0x38, 0x00, 0x00, 0x00, 0x00, 0x3e, 0xf3, 0x16, 0x00, 0x00, 0x00, 0x00, 0x86, 0xc0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x55, 0xd7, 0x35, 0xe4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0x6f, 0x00, 0x00, 0x00, 0x15, + 0x46, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x56, 0x00, 0x79, 0xa9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, + 0xff, 0x7d, 0x00, 0x00, 0x6b, 0xf1, 0x5c, 0x05, 0x00, 0x03, 0x4c, 0xe9, 0x84, 0x00, 0x00, 0x00, + 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, 0xf1, 0x5c, 0x05, 0x00, + 0x03, 0x4c, 0xe9, 0x84, 0x00, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x1b, 0xf0, 0x44, + 0x00, 0x00, 0x15, 0x47, 0x02, 0x00, 0x00, 0x15, 0xb7, 0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xee, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc4, 0xb7, 0x14, 0x00, 0x00, 0x4f, 0xf5, 0x4a, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0xe1, 0xd3, 0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x79, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x34, 0x00, 0xbb, 0x64, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xc4, 0x65, 0x00, 0x79, 0xa9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xc4, 0x7d, 0x00, 0x00, 0x00, 0x41, 0xd4, 0xf7, 0xd4, 0xf4, 0xde, 0x52, 0x00, 0x00, 0x00, 0x00, + 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0xd4, 0xf7, 0xd4, + 0xf4, 0xd5, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x79, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0xcc, + 0x01, 0x00, 0x33, 0xda, 0xee, 0xc9, 0xd9, 0xdc, 0x99, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xee, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xaf, 0xf6, 0xd5, 0xe7, 0xeb, 0x58, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0xf7, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x35, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x94, + 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1b, 0x38, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x32, 0x1f, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, + 0xd6, 0xa9, 0x37, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0a, 0x75, 0xce, 0xf0, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3e, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0xb1, + 0x00, 0x18, 0xba, 0x29, 0x00, 0x00, 0x00, 0x00, 0x07, 0xac, 0x40, 0x00, 0x77, 0x85, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0xb2, 0x32, 0x00, 0x06, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0x7a, + 0x00, 0x15, 0x45, 0x45, 0x45, 0x08, 0x00, 0x18, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3c, 0x45, 0x45, 0x27, 0x00, 0x00, 0x00, 0x07, 0xb1, 0x65, 0x00, 0x00, 0x00, 0x00, 0x12, 0xcf, + 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0x51, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, + 0xc9, 0xea, 0xe5, 0xad, 0x20, 0x00, 0x00, 0x02, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1e, 0xa5, 0xde, 0xea, 0xc5, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x2c, 0xfc, 0x09, 0x00, 0x00, 0x00, 0x00, 0x39, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x62, 0xc5, + 0x00, 0x00, 0x81, 0xc9, 0x04, 0x00, 0x00, 0x00, 0x87, 0xbb, 0x01, 0x00, 0x28, 0xf8, 0x29, 0x00, + 0x00, 0x00, 0x00, 0x6f, 0xc4, 0x01, 0x00, 0x02, 0x41, 0x41, 0x41, 0x41, 0x41, 0x49, 0xe8, 0x72, + 0x00, 0x4e, 0xe4, 0x93, 0x93, 0x11, 0x00, 0x20, 0xee, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7f, 0x93, 0xc8, 0x8f, 0x00, 0x00, 0x00, 0x75, 0xae, 0xe3, 0x14, 0x00, 0x00, 0x00, 0x03, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x0d, 0x00, 0xae, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x09, + 0x35, 0x12, 0x1b, 0x8f, 0xd4, 0x03, 0x00, 0x89, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1f, 0xeb, 0xb4, 0x47, 0x13, 0x3e, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x05, 0xf9, 0x33, 0x00, 0x00, 0x00, 0x05, 0xec, 0x96, 0x00, 0x00, 0x00, 0x00, 0x94, 0x96, + 0x00, 0x00, 0x05, 0xcd, 0x78, 0x00, 0x00, 0x37, 0xec, 0x1d, 0x00, 0x00, 0x00, 0x95, 0xa9, 0x00, + 0x00, 0x00, 0x0c, 0xe7, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0xaa, 0x01, + 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc4, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7d, 0x8f, 0x00, 0x00, 0x14, 0xe3, 0x19, 0x7d, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0xe0, 0x76, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x05, 0xef, 0x2b, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xa3, 0xb3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xd2, 0x62, 0x00, 0x00, 0x00, 0x47, 0xee, 0xe7, 0x02, 0x00, 0x00, 0x00, 0xc7, 0x66, + 0x00, 0x00, 0x00, 0x2c, 0xf1, 0x2b, 0x08, 0xd6, 0x5d, 0x00, 0x00, 0x00, 0x00, 0x15, 0xee, 0x35, + 0x00, 0x00, 0x81, 0xa2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0xd9, 0x0d, 0x00, + 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7d, 0x8f, 0x00, 0x00, 0x92, 0x7e, 0x00, 0x0a, 0xdf, 0x27, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0xc2, 0x3d, 0x00, 0x00, 0x00, + 0x03, 0x27, 0x3b, 0x24, 0xd7, 0x44, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xe6, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x9f, 0x96, 0x00, 0x00, 0x00, 0x9c, 0x7a, 0xe1, 0x3e, 0x00, 0x00, 0x04, 0xf5, 0x31, + 0x00, 0x00, 0x00, 0x00, 0x75, 0xcb, 0x8e, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0xcd, + 0x03, 0x23, 0xe7, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0xee, 0x2f, 0x00, 0x00, + 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0xf2, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7d, 0x8f, 0x00, 0x26, 0xe0, 0x0b, 0x00, 0x00, 0x61, 0xb0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x45, + 0xde, 0xd0, 0xb7, 0xce, 0xfc, 0x45, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0xfe, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x6a, 0xca, 0x00, 0x00, 0x05, 0xec, 0x22, 0x8d, 0x92, 0x00, 0x00, 0x2d, 0xf5, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x03, 0xcb, 0xf2, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xc7, + 0x6e, 0xb5, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xd0, 0x6d, 0x00, 0x00, 0x00, + 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7d, 0x8f, 0x00, 0xaf, 0x62, 0x00, 0x00, 0x00, 0x02, 0xd0, 0x3f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xf0, + 0x54, 0x00, 0x00, 0x00, 0xd4, 0x45, 0x00, 0x8a, 0xb6, 0xa9, 0xdd, 0xd7, 0x9c, 0x15, 0x00, 0x00, + 0x00, 0xf0, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x34, 0xf9, 0x09, 0x00, 0x48, 0xc6, 0x00, 0x33, 0xe4, 0x03, 0x00, 0x66, 0xc3, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1e, 0xeb, 0xe9, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2e, + 0xf4, 0xc9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0xb9, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5e, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7d, 0x8f, 0x00, 0x31, 0x03, 0x00, 0x00, 0x00, 0x00, 0x26, 0x0d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0xfc, + 0x04, 0x00, 0x00, 0x00, 0xd4, 0x45, 0x00, 0x8a, 0xdb, 0x56, 0x19, 0x2e, 0x95, 0xe3, 0x11, 0x00, + 0x00, 0xbf, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0xf0, 0x41, 0x00, 0xa7, 0x6a, 0x00, 0x00, 0xd6, 0x4a, 0x00, 0xa5, 0x84, 0x00, + 0x00, 0x00, 0x00, 0x02, 0xbd, 0x79, 0x49, 0xe9, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc0, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0xec, 0x1c, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xf3, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7d, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xf1, + 0x58, 0x00, 0x00, 0x00, 0xd4, 0x45, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0xb4, 0x83, 0x00, + 0x00, 0x42, 0xef, 0x4c, 0x01, 0x00, 0x0b, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xb2, 0x7f, 0x12, 0xef, 0x12, 0x00, 0x00, 0x76, 0xaf, 0x00, 0xe4, 0x41, 0x00, + 0x00, 0x00, 0x00, 0x6f, 0xc7, 0x03, 0x00, 0x9b, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xbe, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xcf, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x6e, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7d, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4b, + 0xe4, 0xdf, 0xce, 0xdb, 0xf0, 0x39, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x5b, 0xc7, 0x00, + 0x00, 0x00, 0x50, 0xe1, 0xf1, 0xda, 0xee, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x6d, 0xc7, 0x6c, 0xa2, 0x00, 0x00, 0x00, 0x17, 0xf6, 0x3e, 0xf4, 0x08, 0x00, + 0x00, 0x00, 0x1c, 0xed, 0x29, 0x00, 0x00, 0x0f, 0xe4, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xbe, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xc3, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0xc8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7d, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x1f, 0x2c, 0x1a, 0x01, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x44, 0xe8, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x15, 0x23, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1c, 0xfb, 0xd9, 0x3a, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xd7, 0xad, 0x00, 0x00, + 0x00, 0x00, 0xa8, 0x82, 0x00, 0x00, 0x00, 0x00, 0x53, 0xdf, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xbe, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0xec, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xef, 0x23, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7d, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x80, 0xcd, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc6, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0xff, 0x5c, 0x00, 0x00, + 0x00, 0x40, 0xe4, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xbe, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, + 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9e, 0x7d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7d, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x96, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xd6, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7d, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x26, 0x8e, 0xf0, 0x19, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe7, 0x31, 0x00, 0x00, + 0x00, 0x00, 0x7d, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0xf4, 0xdc, 0xd5, 0xfd, 0xc8, 0x2d, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x91, 0x8b, 0x00, 0x00, + 0x00, 0x00, 0x7d, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x1d, 0x2d, 0x0f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0xe3, 0x02, 0x00, + 0x00, 0x00, 0x7d, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x4e, 0xec, 0xb6, 0xb6, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xdc, 0x40, 0x00, + 0x9d, 0xb6, 0xda, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0b, 0x23, 0x23, 0x23, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x10, 0x00, + 0x1e, 0x23, 0x23, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0c, 0x00, 0x00, 0x00, 0x24, 0xae, 0xe4, + 0xa9, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x11, 0x00, 0x00, 0x00, 0x00, 0x17, 0x98, + 0xd1, 0xe4, 0xce, 0xa5, 0x20, 0x00, 0x02, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x14, 0x14, 0x00, 0x02, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x0e, 0x00, 0x00, 0x48, 0xb3, 0xd3, 0xe5, 0xa4, 0x3e, 0x2f, 0xa4, 0xdd, 0xdb, 0xa5, + 0x17, 0x00, 0x00, 0x48, 0xb1, 0xd2, 0xe6, 0xcf, 0x8e, 0x09, 0x00, 0x00, 0x00, 0x00, 0x1b, 0xa3, + 0xdf, 0xd8, 0x94, 0x0e, 0x00, 0x00, 0x49, 0xb7, 0xd7, 0xe7, 0xc5, 0x80, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x11, 0x8f, 0xcb, 0xe7, 0xd4, 0xb1, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0x6c, 0x00, 0x00, 0x1f, 0xed, 0x72, 0x18, + 0x55, 0xd2, 0x8a, 0x00, 0x00, 0x00, 0x56, 0xbc, 0xe7, 0xec, 0x4e, 0x00, 0x00, 0x1d, 0xe9, 0x93, + 0x30, 0x18, 0x2f, 0xe3, 0x41, 0x00, 0x89, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb6, + 0xb6, 0x00, 0x00, 0x00, 0x00, 0xb6, 0xb6, 0x00, 0x89, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x8d, 0x8a, 0x00, 0x00, 0x8a, 0xab, 0x24, 0x18, 0x43, 0xe0, 0xe3, 0x5f, 0x20, 0x29, 0xa4, + 0xc8, 0x00, 0x00, 0x8a, 0xac, 0x25, 0x17, 0x35, 0xbd, 0xab, 0x00, 0x00, 0x00, 0x1b, 0xea, 0xb6, + 0x3e, 0x4d, 0xc7, 0xd6, 0x09, 0x00, 0x8a, 0xac, 0x25, 0x1c, 0x6c, 0xd3, 0xcf, 0x07, 0x00, 0x00, + 0x15, 0xe3, 0xc8, 0x60, 0x19, 0x29, 0xc3, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0x6c, 0x00, 0x00, 0xa5, 0x8c, 0x00, 0x00, + 0x00, 0x46, 0xf7, 0x0d, 0x00, 0x0a, 0xf1, 0x45, 0x03, 0x01, 0x04, 0x00, 0x00, 0xa4, 0xa1, 0x00, + 0x00, 0x00, 0x00, 0xd8, 0x41, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, + 0x33, 0x00, 0x00, 0x00, 0x00, 0x33, 0x33, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x8f, 0x8a, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x68, 0xbf, 0x00, 0x00, 0x00, 0x11, + 0xfb, 0x1f, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x1d, 0xfa, 0x18, 0x00, 0x00, 0x9f, 0xb8, 0x00, + 0x00, 0x00, 0x00, 0xdb, 0x78, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x03, 0xe8, 0x78, 0x00, 0x00, + 0x9b, 0xcd, 0x00, 0x00, 0x00, 0x00, 0xad, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0x6c, 0x00, 0x00, 0xe3, 0x4b, 0x16, 0x16, + 0x16, 0x1c, 0xf5, 0x2f, 0x00, 0x4d, 0xc5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x3e, 0x00, + 0x00, 0x00, 0x00, 0xd8, 0x41, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x8f, 0x8a, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x3d, 0xe0, 0x00, 0x00, 0x00, 0x00, + 0xe2, 0x3a, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0xe4, 0x3a, 0x00, 0x00, 0xe4, 0x6e, 0x00, + 0x00, 0x00, 0x00, 0x90, 0xbf, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x98, 0xc1, 0x00, 0x00, + 0xe2, 0x79, 0x00, 0x00, 0x00, 0x00, 0xad, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0x6c, 0x00, 0x08, 0xfe, 0xdd, 0xd8, 0xd8, + 0xd8, 0xd8, 0xd8, 0x3a, 0x00, 0x87, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xfe, 0x24, 0x00, + 0x00, 0x00, 0x00, 0xd8, 0x41, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, + 0x6d, 0x00, 0x00, 0x00, 0x00, 0x69, 0x6d, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x8f, 0x8a, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x31, 0xe9, 0x00, 0x00, 0x00, 0x00, + 0xd5, 0x45, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0xd2, 0x49, 0x00, 0x08, 0xfe, 0x2e, 0x00, + 0x00, 0x00, 0x00, 0x50, 0xe3, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x4b, 0xe7, 0x00, 0x08, + 0xfe, 0x2d, 0x00, 0x00, 0x00, 0x00, 0xad, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x22, 0xa8, 0xdd, 0xd8, 0x9d, 0xc7, 0x6c, 0x00, 0x00, 0xf3, 0x4f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0xe4, 0xc2, 0xc2, 0xc2, 0x10, 0x00, 0x00, 0xe3, 0x45, 0x00, + 0x00, 0x00, 0x00, 0xd8, 0x41, 0x00, 0x8a, 0xcf, 0xc2, 0xe4, 0xce, 0x8e, 0x0a, 0x00, 0x00, 0x8a, + 0x8f, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x0d, 0xad, 0x43, 0x00, + 0x00, 0x8f, 0x8a, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x30, 0xe9, 0x00, 0x00, 0x00, 0x00, + 0xd4, 0x45, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0xcf, 0x49, 0x00, 0x00, 0xee, 0x5b, 0x00, + 0x00, 0x00, 0x00, 0x7e, 0xca, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x50, 0xd2, 0x00, 0x00, + 0xf2, 0x32, 0x00, 0x00, 0x00, 0x00, 0xad, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x24, 0xed, 0x7f, 0x28, 0x1d, 0x62, 0xe8, 0x6c, 0x00, 0x00, 0xc3, 0xa2, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0xa2, 0x2b, 0x2b, 0x2b, 0x04, 0x00, 0x00, 0xa3, 0xaf, 0x00, + 0x00, 0x00, 0x00, 0xd9, 0x41, 0x00, 0x8a, 0xba, 0x37, 0x18, 0x35, 0xbd, 0xae, 0x00, 0x00, 0x8a, + 0x8f, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x8a, 0x8f, 0x00, 0x09, 0xbd, 0x7e, 0x00, 0x00, + 0x00, 0x8f, 0x8a, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x30, 0xe9, 0x00, 0x00, 0x00, 0x00, + 0xd4, 0x45, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0xcf, 0x49, 0x00, 0x00, 0xba, 0xa5, 0x00, + 0x00, 0x00, 0x00, 0xc8, 0x94, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x93, 0xa0, 0x00, 0x00, + 0xc2, 0x74, 0x00, 0x00, 0x00, 0x00, 0xad, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xa5, 0x95, 0x00, 0x00, 0x00, 0x00, 0xad, 0x6c, 0x00, 0x00, 0x46, 0xf1, 0x75, 0x1b, + 0x00, 0x03, 0x18, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xe1, 0xb6, + 0x62, 0x6e, 0xb6, 0xff, 0x41, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x1d, 0xfb, 0x18, 0x00, 0x8a, + 0x8f, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x8a, 0x8f, 0x08, 0xb8, 0x85, 0x00, 0x00, 0x00, + 0x00, 0x8f, 0x8a, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x30, 0xe9, 0x00, 0x00, 0x00, 0x00, + 0xd4, 0x45, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0xcf, 0x49, 0x00, 0x00, 0x36, 0xf2, 0x77, + 0x0f, 0x18, 0x87, 0xf1, 0x1a, 0x00, 0x8a, 0xbf, 0x18, 0x00, 0x00, 0x4f, 0xf2, 0x28, 0x00, 0x00, + 0x44, 0xeb, 0x39, 0x00, 0x00, 0x22, 0xd4, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xe8, 0x3d, 0x00, 0x00, 0x00, 0x00, 0xad, 0x6c, 0x00, 0x00, 0x00, 0x4d, 0xdd, 0xfc, + 0xdb, 0xf2, 0x7a, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x6f, + 0x9e, 0x9c, 0x55, 0xde, 0x3d, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0xe4, 0x3a, 0x00, 0x8a, + 0x8f, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x8a, 0x97, 0xb6, 0x88, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x8f, 0x8a, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x30, 0xe9, 0x00, 0x00, 0x00, 0x00, + 0xd4, 0x45, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0xcf, 0x49, 0x00, 0x00, 0x00, 0x48, 0xe3, + 0xf0, 0xf7, 0xd4, 0x32, 0x00, 0x00, 0x8a, 0xd3, 0xe7, 0xd5, 0xea, 0xdd, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x56, 0xe8, 0xe4, 0xd9, 0xdf, 0xdc, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0xff, 0x25, 0x00, 0x00, 0x00, 0x00, 0xad, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x25, 0x09, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x27, 0xff, 0x1c, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0xd2, 0x49, 0x00, 0x8a, + 0x8f, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x8a, 0xdf, 0xf4, 0x38, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x8f, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1e, 0x17, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x02, 0x20, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1f, 0x1b, 0x01, 0xad, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xed, 0x62, 0x00, 0x00, 0x00, 0x00, 0xad, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x09, 0x00, + 0x00, 0x1f, 0x9e, 0xcc, 0x01, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0xcf, 0x49, 0x00, 0x8a, + 0x8f, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x8a, 0x8f, 0x4f, 0xed, 0x48, 0x00, 0x00, 0x00, + 0x00, 0x8f, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xb8, 0xb5, 0x00, 0x00, 0x00, 0x00, 0xad, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0xf2, 0xd8, + 0xdc, 0xd3, 0x8e, 0x22, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0xcf, 0x49, 0x00, 0x8a, + 0x8f, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x8a, 0x8f, 0x00, 0x3e, 0xed, 0x3b, 0x00, 0x00, + 0x00, 0x8e, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x31, 0xf6, 0x7f, 0x1c, 0x00, 0x00, 0xae, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x22, + 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0xcf, 0x49, 0x00, 0x8a, + 0x8f, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8e, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x52, 0xe7, 0x1d, 0x00, + 0x00, 0x7b, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x3f, 0xd8, 0xfc, 0xd2, 0xe0, 0xf1, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0xcf, 0x49, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x69, 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x92, 0xbc, 0x01, + 0x00, 0x1f, 0xdf, 0xa9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x2d, 0x1a, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1c, 0xe0, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0xd1, 0xe1, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1e, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3d, 0xac, 0xd3, 0xe8, 0xd1, 0x0b, 0x00, 0x00, 0x2a, 0x91, 0xdc, 0xe5, 0xbd, 0x0a, 0x00, + 0x3e, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x59, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x24, 0x00, + 0x6b, 0x76, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x86, 0x00, 0x62, 0x7e, 0x00, 0x00, 0x00, 0x01, 0xb6, + 0x24, 0x00, 0x00, 0x00, 0x54, 0x7c, 0x00, 0x17, 0xbc, 0x26, 0x00, 0x00, 0x00, 0x92, 0x57, 0x00, + 0x6b, 0x77, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x6a, 0x00, 0x14, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xaf, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x07, 0x00, 0x15, 0x34, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x15, 0x1a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x1c, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x8a, 0xab, 0x1f, 0x13, 0x2a, 0x00, 0x00, 0x00, 0xc2, 0x66, 0x0a, 0x16, 0x44, 0x00, 0x00, + 0x9c, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x75, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x30, 0x00, + 0x4d, 0xd4, 0x00, 0x00, 0x00, 0x00, 0xa3, 0x6f, 0x00, 0x41, 0xda, 0x00, 0x00, 0x00, 0x27, 0xff, + 0x5f, 0x00, 0x00, 0x00, 0xa5, 0x63, 0x00, 0x00, 0x7b, 0xc6, 0x03, 0x00, 0x5c, 0xd0, 0x06, 0x00, + 0x4d, 0xd8, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x51, 0x00, 0x04, 0x2b, 0x2b, 0x2b, 0x2d, 0xcd, 0x96, + 0x00, 0x00, 0x00, 0x00, 0x11, 0xba, 0xd8, 0x35, 0x00, 0x4e, 0xbe, 0x00, 0xb0, 0xdc, 0x48, 0x00, + 0x00, 0x00, 0x00, 0x71, 0xdc, 0xde, 0xb6, 0x21, 0x00, 0x60, 0x8a, 0x00, 0x27, 0xbc, 0x23, 0x23, + 0x23, 0x23, 0x53, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0xfa, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x9c, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x75, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x30, 0x00, + 0x0b, 0xf6, 0x1c, 0x00, 0x00, 0x02, 0xe8, 0x25, 0x00, 0x06, 0xf2, 0x1d, 0x00, 0x00, 0x6a, 0xe9, + 0x9d, 0x00, 0x00, 0x01, 0xe5, 0x1b, 0x00, 0x00, 0x03, 0xc6, 0x76, 0x1c, 0xe7, 0x2d, 0x00, 0x00, + 0x0b, 0xf5, 0x26, 0x00, 0x00, 0x07, 0xf4, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xc1, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x78, 0xaa, 0x00, 0x00, 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x46, 0xdb, 0x00, + 0x00, 0x00, 0x0a, 0xdb, 0x0c, 0x02, 0x69, 0xe5, 0xc0, 0xdb, 0x21, 0x00, 0x27, 0xb1, 0x00, 0x00, + 0x00, 0x00, 0x38, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd4, 0xa4, 0x1b, 0x00, 0x00, 0x00, 0x00, + 0x9c, 0xe0, 0xc2, 0xc2, 0xbf, 0x00, 0x00, 0xa4, 0x75, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x30, 0x00, + 0x00, 0xad, 0x6b, 0x00, 0x00, 0x3c, 0xce, 0x00, 0x00, 0x00, 0xb1, 0x67, 0x00, 0x00, 0xb1, 0x67, + 0xe4, 0x01, 0x00, 0x31, 0xd1, 0x00, 0x00, 0x00, 0x00, 0x25, 0xec, 0xc9, 0x75, 0x00, 0x00, 0x00, + 0x00, 0xa8, 0x7b, 0x00, 0x00, 0x3e, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0xe4, 0x19, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x9e, 0x70, 0x00, 0x00, 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x08, 0xfe, 0x06, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x2e, 0x0a, 0x00, 0x00, 0x27, 0xb1, 0x00, 0x00, + 0x00, 0x00, 0x38, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x99, 0xf2, 0xa7, 0x26, 0x00, 0x00, + 0x9c, 0x93, 0x2b, 0x2b, 0x2a, 0x00, 0x00, 0xa4, 0x75, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x30, 0x00, + 0x00, 0x56, 0xc1, 0x00, 0x00, 0x91, 0x75, 0x00, 0x00, 0x00, 0x6a, 0xb5, 0x00, 0x0a, 0xe7, 0x08, + 0xd7, 0x32, 0x00, 0x7d, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb2, 0xfc, 0x24, 0x00, 0x00, 0x00, + 0x00, 0x4d, 0xd9, 0x01, 0x00, 0x81, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x13, 0xe4, 0x44, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xa4, 0x69, 0x00, 0x00, 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x01, 0xff, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0xb1, 0x00, 0x00, + 0x00, 0x00, 0x38, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x84, 0xf1, 0x2b, 0x00, + 0x9c, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x84, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x30, 0x00, + 0x00, 0x08, 0xec, 0x22, 0x05, 0xe4, 0x19, 0x00, 0x00, 0x00, 0x17, 0xf4, 0x11, 0x51, 0xa7, 0x00, + 0x84, 0x84, 0x00, 0xd1, 0x31, 0x00, 0x00, 0x00, 0x00, 0x50, 0xd4, 0x80, 0xc8, 0x04, 0x00, 0x00, + 0x00, 0x04, 0xe1, 0x40, 0x00, 0xc9, 0x3c, 0x00, 0x00, 0x00, 0x00, 0xab, 0x8e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xa4, 0x68, 0x00, 0x00, 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0xff, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0xb1, 0x00, 0x00, + 0x00, 0x00, 0x38, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x7f, 0x00, + 0x9c, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xab, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x30, 0x00, + 0x00, 0x00, 0x8f, 0x86, 0x52, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0xad, 0x66, 0xa8, 0x50, 0x00, + 0x2d, 0xdc, 0x2c, 0xcd, 0x00, 0x00, 0x00, 0x00, 0x16, 0xe4, 0x2f, 0x02, 0xc3, 0x7d, 0x00, 0x00, + 0x00, 0x00, 0x77, 0xad, 0x1a, 0xdd, 0x01, 0x00, 0x00, 0x00, 0x57, 0xda, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xa4, 0x64, 0x00, 0x00, 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0xfc, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0xb1, 0x00, 0x00, + 0x00, 0x00, 0x38, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x14, 0x00, 0x00, 0x10, 0xe4, 0x39, 0x00, + 0x9c, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0xf7, 0x3f, 0x00, 0x00, 0x02, 0xeb, 0x30, 0x00, + 0x00, 0x00, 0x25, 0xea, 0xc8, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0xd3, 0xe2, 0x06, 0x00, + 0x00, 0xce, 0xc6, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x9f, 0x81, 0x00, 0x00, 0x24, 0xef, 0x27, 0x00, + 0x00, 0x00, 0x10, 0xed, 0x92, 0x86, 0x00, 0x00, 0x00, 0x0c, 0xe3, 0x45, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x49, 0x00, 0x00, 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0xe3, 0x26, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0xb1, 0x00, 0x00, + 0x00, 0x00, 0x38, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x8a, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0xf2, 0xd7, 0xd5, 0xdf, 0x8c, 0x00, 0x00, + 0x9b, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b, 0xeb, 0xe6, 0xdc, 0xf1, 0xda, 0x22, 0x00, + 0x00, 0x00, 0x00, 0xae, 0xcf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe1, 0x8d, 0x00, 0x00, + 0x00, 0x68, 0xf6, 0x10, 0x00, 0x00, 0x00, 0x3b, 0xdc, 0x08, 0x00, 0x00, 0x00, 0x7e, 0xb3, 0x00, + 0x00, 0x00, 0x00, 0x7f, 0xff, 0x2c, 0x00, 0x00, 0x00, 0x58, 0xfe, 0xee, 0xee, 0xee, 0xee, 0xee, + 0x0c, 0x00, 0x17, 0x8c, 0xd4, 0x09, 0x00, 0x00, 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0x76, 0xc9, + 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0xb1, 0x00, 0x00, + 0x00, 0x00, 0x38, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x20, 0x21, 0x00, 0x00, 0x00, 0x00, + 0x8a, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x1d, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x53, 0xc7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x26, 0xc3, 0xaf, 0x02, 0x00, 0x00, 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0x4e, 0xe4, + 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0xb1, 0x00, 0x00, + 0x00, 0x00, 0x38, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x5c, 0xd5, 0x0b, 0x00, 0x0f, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x17, 0xd9, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0xd3, 0x3d, 0x00, 0x00, 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0xd7, 0x3b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0xb1, 0x00, 0x00, + 0x00, 0x00, 0x38, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0xb5, 0xef, 0xd9, 0xec, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc2, 0xc7, 0xe5, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xa5, 0x60, 0x00, 0x00, 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0xf9, 0x0e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0xb1, 0x00, 0x00, + 0x00, 0x00, 0x38, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x14, 0x24, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x25, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xa4, 0x68, 0x00, 0x00, 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0xff, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0xf3, 0xd8, 0xd8, + 0xd8, 0xd8, 0xe1, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xa4, 0x68, 0x00, 0x00, 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x00, 0xff, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xa1, 0x6d, 0x00, 0x00, 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x05, 0xff, 0x09, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x87, 0x8f, 0x00, 0x00, 0x00, 0x4e, 0xbe, 0x00, 0x00, 0x2a, 0xeb, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x24, 0xe1, 0xa3, 0x26, 0x00, 0x4e, 0xbe, 0x00, 0x80, 0xd9, 0x71, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x3c, 0x16, 0x00, 0x0b, 0x1a, 0x00, 0x3e, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + diff --git a/third_party/webrender/webrender/src/debug_render.rs b/third_party/webrender/webrender/src/debug_render.rs new file mode 100644 index 00000000000..c43bcd1d832 --- /dev/null +++ b/third_party/webrender/webrender/src/debug_render.rs @@ -0,0 +1,389 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{ColorU, ColorF, ImageFormat, TextureTarget}; +use api::units::*; +use crate::debug_font_data; +use crate::device::{Device, Program, Texture, TextureSlot, VertexDescriptor, ShaderError, VAO}; +use crate::device::{TextureFilter, VertexAttribute, VertexAttributeKind, VertexUsageHint}; +use euclid::{Point2D, Rect, Size2D, Transform3D, default}; +use crate::internal_types::Swizzle; +use std::f32; + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum DebugItem { + Text { + msg: String, + color: ColorF, + position: DevicePoint, + }, + Rect { + outer_color: ColorF, + inner_color: ColorF, + rect: DeviceRect, + }, +} + +#[derive(Debug, Copy, Clone)] +enum DebugSampler { + Font, +} + +impl Into for DebugSampler { + fn into(self) -> TextureSlot { + match self { + DebugSampler::Font => TextureSlot(0), + } + } +} + +const DESC_FONT: VertexDescriptor = VertexDescriptor { + vertex_attributes: &[ + VertexAttribute { + name: "aPosition", + count: 2, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aColor", + count: 4, + kind: VertexAttributeKind::U8Norm, + }, + VertexAttribute { + name: "aColorTexCoord", + count: 2, + kind: VertexAttributeKind::F32, + }, + ], + instance_attributes: &[], +}; + +const DESC_COLOR: VertexDescriptor = VertexDescriptor { + vertex_attributes: &[ + VertexAttribute { + name: "aPosition", + count: 2, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aColor", + count: 4, + kind: VertexAttributeKind::U8Norm, + }, + ], + instance_attributes: &[], +}; + +#[repr(C)] +pub struct DebugFontVertex { + pub x: f32, + pub y: f32, + pub color: ColorU, + pub u: f32, + pub v: f32, +} + +impl DebugFontVertex { + pub fn new(x: f32, y: f32, u: f32, v: f32, color: ColorU) -> DebugFontVertex { + DebugFontVertex { x, y, color, u, v } + } +} + +#[repr(C)] +pub struct DebugColorVertex { + pub x: f32, + pub y: f32, + pub color: ColorU, +} + +impl DebugColorVertex { + pub fn new(x: f32, y: f32, color: ColorU) -> DebugColorVertex { + DebugColorVertex { x, y, color } + } +} + +pub struct DebugRenderer { + font_vertices: Vec, + font_indices: Vec, + font_program: Program, + font_vao: VAO, + font_texture: Texture, + + tri_vertices: Vec, + tri_indices: Vec, + tri_vao: VAO, + line_vertices: Vec, + line_vao: VAO, + color_program: Program, +} + +impl DebugRenderer { + pub fn new(device: &mut Device) -> Result { + let font_program = device.create_program_linked( + "debug_font", + &[], + &DESC_FONT, + )?; + device.bind_program(&font_program); + device.bind_shader_samplers(&font_program, &[("sColor0", DebugSampler::Font)]); + + let color_program = device.create_program_linked( + "debug_color", + &[], + &DESC_COLOR, + )?; + + let font_vao = device.create_vao(&DESC_FONT); + let line_vao = device.create_vao(&DESC_COLOR); + let tri_vao = device.create_vao(&DESC_COLOR); + + let font_texture = device.create_texture( + TextureTarget::Array, + ImageFormat::R8, + debug_font_data::BMP_WIDTH, + debug_font_data::BMP_HEIGHT, + TextureFilter::Linear, + None, + 1, + ); + device.upload_texture_immediate( + &font_texture, + &debug_font_data::FONT_BITMAP + ); + + Ok(DebugRenderer { + font_vertices: Vec::new(), + font_indices: Vec::new(), + line_vertices: Vec::new(), + tri_vao, + tri_vertices: Vec::new(), + tri_indices: Vec::new(), + font_program, + color_program, + font_vao, + line_vao, + font_texture, + }) + } + + pub fn deinit(self, device: &mut Device) { + device.delete_texture(self.font_texture); + device.delete_program(self.font_program); + device.delete_program(self.color_program); + device.delete_vao(self.tri_vao); + device.delete_vao(self.line_vao); + device.delete_vao(self.font_vao); + } + + pub fn line_height(&self) -> f32 { + debug_font_data::FONT_SIZE as f32 * 1.1 + } + + /// Draws a line of text at the provided starting coordinates. + /// + /// If |bounds| is specified, glyphs outside the bounds are discarded. + /// + /// Y-coordinates is relative to screen top, along with everything else in + /// this file. + pub fn add_text( + &mut self, + x: f32, + y: f32, + text: &str, + color: ColorU, + bounds: Option, + ) -> default::Rect { + let mut x_start = x; + let ipw = 1.0 / debug_font_data::BMP_WIDTH as f32; + let iph = 1.0 / debug_font_data::BMP_HEIGHT as f32; + + let mut min_x = f32::MAX; + let mut max_x = -f32::MAX; + let mut min_y = f32::MAX; + let mut max_y = -f32::MAX; + + for c in text.chars() { + let c = c as usize - debug_font_data::FIRST_GLYPH_INDEX as usize; + if c < debug_font_data::GLYPHS.len() { + let glyph = &debug_font_data::GLYPHS[c]; + + let x0 = (x_start + glyph.xo + 0.5).floor(); + let y0 = (y + glyph.yo + 0.5).floor(); + + let x1 = x0 + glyph.x1 as f32 - glyph.x0 as f32; + let y1 = y0 + glyph.y1 as f32 - glyph.y0 as f32; + + // If either corner of the glyph will end up out of bounds, drop it. + if let Some(b) = bounds { + let rect = DeviceRect::new( + DevicePoint::new(x0, y0), + DeviceSize::new(x1 - x0, y1 - y0), + ); + if !b.contains_rect(&rect) { + continue; + } + } + + let s0 = glyph.x0 as f32 * ipw; + let t0 = glyph.y0 as f32 * iph; + let s1 = glyph.x1 as f32 * ipw; + let t1 = glyph.y1 as f32 * iph; + + x_start += glyph.xa; + + let vertex_count = self.font_vertices.len() as u32; + + self.font_vertices + .push(DebugFontVertex::new(x0, y0, s0, t0, color)); + self.font_vertices + .push(DebugFontVertex::new(x1, y0, s1, t0, color)); + self.font_vertices + .push(DebugFontVertex::new(x0, y1, s0, t1, color)); + self.font_vertices + .push(DebugFontVertex::new(x1, y1, s1, t1, color)); + + self.font_indices.push(vertex_count + 0); + self.font_indices.push(vertex_count + 1); + self.font_indices.push(vertex_count + 2); + self.font_indices.push(vertex_count + 2); + self.font_indices.push(vertex_count + 1); + self.font_indices.push(vertex_count + 3); + + min_x = min_x.min(x0); + max_x = max_x.max(x1); + min_y = min_y.min(y0); + max_y = max_y.max(y1); + } + } + + Rect::new( + Point2D::new(min_x, min_y), + Size2D::new(max_x - min_x, max_y - min_y), + ) + } + + pub fn add_quad( + &mut self, + x0: f32, + y0: f32, + x1: f32, + y1: f32, + color_top: ColorU, + color_bottom: ColorU, + ) { + let vertex_count = self.tri_vertices.len() as u32; + + self.tri_vertices + .push(DebugColorVertex::new(x0, y0, color_top)); + self.tri_vertices + .push(DebugColorVertex::new(x1, y0, color_top)); + self.tri_vertices + .push(DebugColorVertex::new(x0, y1, color_bottom)); + self.tri_vertices + .push(DebugColorVertex::new(x1, y1, color_bottom)); + + self.tri_indices.push(vertex_count + 0); + self.tri_indices.push(vertex_count + 1); + self.tri_indices.push(vertex_count + 2); + self.tri_indices.push(vertex_count + 2); + self.tri_indices.push(vertex_count + 1); + self.tri_indices.push(vertex_count + 3); + } + + #[allow(dead_code)] + pub fn add_line(&mut self, x0: i32, y0: i32, color0: ColorU, x1: i32, y1: i32, color1: ColorU) { + self.line_vertices + .push(DebugColorVertex::new(x0 as f32, y0 as f32, color0)); + self.line_vertices + .push(DebugColorVertex::new(x1 as f32, y1 as f32, color1)); + } + + + pub fn add_rect(&mut self, rect: &DeviceIntRect, color: ColorU) { + let p0 = rect.origin; + let p1 = p0 + rect.size; + self.add_line(p0.x, p0.y, color, p1.x, p0.y, color); + self.add_line(p1.x, p0.y, color, p1.x, p1.y, color); + self.add_line(p1.x, p1.y, color, p0.x, p1.y, color); + self.add_line(p0.x, p1.y, color, p0.x, p0.y, color); + } + + pub fn render( + &mut self, + device: &mut Device, + viewport_size: Option, + scale: f32, + surface_origin_is_top_left: bool, + ) { + if let Some(viewport_size) = viewport_size { + device.disable_depth(); + device.set_blend(true); + device.set_blend_mode_premultiplied_alpha(); + + let (bottom, top) = if surface_origin_is_top_left { + (0.0, viewport_size.height as f32 * scale) + } else { + (viewport_size.height as f32 * scale, 0.0) + }; + + let projection = Transform3D::ortho( + 0.0, + viewport_size.width as f32 * scale, + bottom, + top, + device.ortho_near_plane(), + device.ortho_far_plane(), + ); + + // Triangles + if !self.tri_vertices.is_empty() { + device.bind_program(&self.color_program); + device.set_uniforms(&self.color_program, &projection); + device.bind_vao(&self.tri_vao); + device.update_vao_indices(&self.tri_vao, &self.tri_indices, VertexUsageHint::Dynamic); + device.update_vao_main_vertices( + &self.tri_vao, + &self.tri_vertices, + VertexUsageHint::Dynamic, + ); + device.draw_triangles_u32(0, self.tri_indices.len() as i32); + } + + // Lines + if !self.line_vertices.is_empty() { + device.bind_program(&self.color_program); + device.set_uniforms(&self.color_program, &projection); + device.bind_vao(&self.line_vao); + device.update_vao_main_vertices( + &self.line_vao, + &self.line_vertices, + VertexUsageHint::Dynamic, + ); + device.draw_nonindexed_lines(0, self.line_vertices.len() as i32); + } + + // Glyph + if !self.font_indices.is_empty() { + device.bind_program(&self.font_program); + device.set_uniforms(&self.font_program, &projection); + device.bind_texture(DebugSampler::Font, &self.font_texture, Swizzle::default()); + device.bind_vao(&self.font_vao); + device.update_vao_indices(&self.font_vao, &self.font_indices, VertexUsageHint::Dynamic); + device.update_vao_main_vertices( + &self.font_vao, + &self.font_vertices, + VertexUsageHint::Dynamic, + ); + device.draw_triangles_u32(0, self.font_indices.len() as i32); + } + } + + self.font_indices.clear(); + self.font_vertices.clear(); + self.line_vertices.clear(); + self.tri_vertices.clear(); + self.tri_indices.clear(); + } +} diff --git a/third_party/webrender/webrender/src/debug_server.rs b/third_party/webrender/webrender/src/debug_server.rs new file mode 100644 index 00000000000..c3cd29549ad --- /dev/null +++ b/third_party/webrender/webrender/src/debug_server.rs @@ -0,0 +1,402 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{ApiMsg, DebugCommand, DebugFlags}; +use api::units::DeviceIntSize; +use crate::print_tree::PrintTreePrinter; +use crate::renderer; +use std::sync::mpsc::{channel, Receiver}; +use std::sync::mpsc::Sender; +use std::thread; +use ws; +use base64::encode; +use image_loader; + +// Messages that are sent from the render backend to the renderer +// debug command queue. These are sent in a separate queue so +// that none of these types are exposed to the RenderApi interfaces. +// We can't use select!() as it's not stable... +enum DebugMsg { + AddSender(ws::Sender), + RemoveSender(ws::util::Token), +} + +// Represents a connection to a client. +struct Server { + ws: ws::Sender, + debug_tx: Sender, + api_tx: Sender, + debug_flags: DebugFlags, +} + +impl ws::Handler for Server { + fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> { + self.debug_tx + .send(DebugMsg::AddSender(self.ws.clone())) + .ok(); + + Ok(()) + } + + fn on_close(&mut self, _: ws::CloseCode, _: &str) { + self.debug_tx + .send(DebugMsg::RemoveSender(self.ws.token())) + .ok(); + } + + fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> { + match msg { + ws::Message::Text(string) => { + // First, check for flag change commands. + let mut set_flags = true; + match string.as_str() { + "enable_profiler" => self.debug_flags.insert(DebugFlags::PROFILER_DBG), + "disable_profiler" => self.debug_flags.remove(DebugFlags::PROFILER_DBG), + "enable_texture_cache_debug" => self.debug_flags.insert(DebugFlags::TEXTURE_CACHE_DBG), + "disable_texture_cache_debug" => self.debug_flags.remove(DebugFlags::TEXTURE_CACHE_DBG), + "enable_render_target_debug" => self.debug_flags.insert(DebugFlags::RENDER_TARGET_DBG), + "disable_render_target_debug" => self.debug_flags.remove(DebugFlags::RENDER_TARGET_DBG), + "enable_gpu_time_queries" => self.debug_flags.insert(DebugFlags::GPU_TIME_QUERIES), + "disable_gpu_time_queries" => self.debug_flags.remove(DebugFlags::GPU_TIME_QUERIES), + "enable_gpu_sample_queries" => self.debug_flags.insert(DebugFlags::GPU_SAMPLE_QUERIES), + "disable_gpu_sample_queries" => self.debug_flags.remove(DebugFlags::GPU_SAMPLE_QUERIES), + "disable_opaque_pass" => self.debug_flags.insert(DebugFlags::DISABLE_OPAQUE_PASS), + "enable_opaque_pass" => self.debug_flags.remove(DebugFlags::DISABLE_OPAQUE_PASS), + "disable_alpha_pass" => self.debug_flags.insert(DebugFlags::DISABLE_ALPHA_PASS), + "enable_alpha_pass" => self.debug_flags.remove(DebugFlags::DISABLE_ALPHA_PASS), + "disable_clip_masks" => self.debug_flags.insert(DebugFlags::DISABLE_CLIP_MASKS), + "enable_clip_masks" => self.debug_flags.remove(DebugFlags::DISABLE_CLIP_MASKS), + "disable_text_prims" => self.debug_flags.insert(DebugFlags::DISABLE_TEXT_PRIMS), + "enable_text_prims" => self.debug_flags.remove(DebugFlags::DISABLE_TEXT_PRIMS), + "disable_gradient_prims" => self.debug_flags.insert(DebugFlags::DISABLE_GRADIENT_PRIMS), + "enable_gradient_prims" => self.debug_flags.remove(DebugFlags::DISABLE_GRADIENT_PRIMS), + _ => set_flags = false, + }; + + let cmd = if set_flags { + DebugCommand::SetFlags(self.debug_flags) + } else { + match string.as_str() { + "fetch_passes" => DebugCommand::FetchPasses, + "fetch_screenshot" => DebugCommand::FetchScreenshot, + "fetch_documents" => DebugCommand::FetchDocuments, + "fetch_spatial_tree" => DebugCommand::FetchClipScrollTree, + "fetch_render_tasks" => DebugCommand::FetchRenderTasks, + msg => { + error!("unknown msg {}", msg); + return Ok(()); + } + } + }; + + let msg = ApiMsg::DebugCommand(cmd); + self.api_tx.send(msg).unwrap(); + } + ws::Message::Binary(..) => {} + } + + Ok(()) + } +} + +// Spawn a thread for a given renderer, and wait for +// client connections. +pub struct DebugServerImpl { + join_handle: Option>, + broadcaster: ws::Sender, + debug_rx: Receiver, + senders: Vec, +} + +impl DebugServerImpl { + pub fn new(api_tx: Sender) -> DebugServerImpl { + let (debug_tx, debug_rx) = channel(); + + let socket = ws::Builder::new() + .build(move |out| { + Server { + ws: out, + debug_tx: debug_tx.clone(), + api_tx: api_tx.clone(), + debug_flags: DebugFlags::empty(), + } + }) + .unwrap(); + + let broadcaster = socket.broadcaster(); + + let join_handle = Some(thread::spawn(move || { + let address = "127.0.0.1:3583"; + debug!("WebRender debug server started: {}", address); + if let Err(..) = socket.listen(address) { + error!("ERROR: Unable to bind debugger websocket (port may be in use)."); + } + })); + + DebugServerImpl { + join_handle, + broadcaster, + debug_rx, + senders: Vec::new(), + } + } +} + +impl renderer::DebugServer for DebugServerImpl { + fn send(&mut self, message: String) { + // Add any new connections that have been queued. + while let Ok(msg) = self.debug_rx.try_recv() { + match msg { + DebugMsg::AddSender(sender) => { + self.senders.push(sender); + } + DebugMsg::RemoveSender(token) => { + self.senders.retain(|sender| sender.token() != token); + } + } + } + + // Broadcast the message to all senders. Keep + // track of the ones that failed, so they can + // be removed from the active sender list. + let mut disconnected_senders = Vec::new(); + + for (i, sender) in self.senders.iter().enumerate() { + if let Err(..) = sender.send(message.clone()) { + disconnected_senders.push(i); + } + } + + // Remove the broken senders from the list + // for next broadcast. Remove in reverse + // order so the indices are valid for the + // entire loop. + for i in disconnected_senders.iter().rev() { + self.senders.remove(*i); + } + } +} + +impl Drop for DebugServerImpl { + fn drop(&mut self) { + self.broadcaster.shutdown().ok(); + self.join_handle.take().unwrap().join().ok(); + } +} + +// A serializable list of debug information about passes +// that can be sent to the client. + +#[derive(Serialize)] +pub enum BatchKind { + Clip, + Cache, + Opaque, + Alpha, +} + +#[derive(Serialize)] +pub struct PassList { + kind: &'static str, + passes: Vec, +} + +impl PassList { + pub fn new() -> PassList { + PassList { + kind: "passes", + passes: Vec::new(), + } + } + + pub fn add(&mut self, pass: Pass) { + self.passes.push(pass); + } +} + +#[derive(Serialize)] +pub struct Pass { + pub targets: Vec, +} + +#[derive(Serialize)] +pub struct Target { + kind: &'static str, + batches: Vec, +} + +impl Target { + pub fn new(kind: &'static str) -> Target { + Target { + kind, + batches: Vec::new(), + } + } + + pub fn add(&mut self, kind: BatchKind, description: &str, count: usize) { + if count > 0 { + self.batches.push(Batch { + kind, + description: description.to_owned(), + count, + }); + } + } +} + +#[derive(Serialize)] +struct Batch { + kind: BatchKind, + description: String, + count: usize, +} + +#[derive(Serialize)] +pub struct TreeNode { + description: String, + children: Vec, +} + +impl TreeNode { + pub fn new(description: &str) -> TreeNode { + TreeNode { + description: description.to_owned(), + children: Vec::new(), + } + } + + pub fn add_child(&mut self, child: TreeNode) { + self.children.push(child); + } + + pub fn add_item(&mut self, description: &str) { + self.children.push(TreeNode::new(description)); + } +} + +#[derive(Serialize)] +pub struct DocumentList { + kind: &'static str, + root: TreeNode, +} + +impl DocumentList { + pub fn new() -> Self { + DocumentList { + kind: "documents", + root: TreeNode::new("root"), + } + } + + pub fn add(&mut self, item: TreeNode) { + self.root.add_child(item); + } +} + +#[derive(Serialize)] +pub struct Screenshot { + kind: &'static str, + data: String +} + +impl Screenshot { + pub fn new(size: DeviceIntSize, data: Vec) -> Self { + let mut output = Vec::with_capacity((size.width * size.height) as usize); + { + let encoder = image_loader::png::PNGEncoder::new(&mut output); + encoder.encode( + &data, + size.width as u32, + size.height as u32, + image_loader::ColorType::Rgba8, + ).unwrap(); + } + + let data = encode(&output); + Screenshot { + kind: "screenshot", + data + } + } +} + +// A serializable list of debug information about spatial trees +// that can be sent to the client + +#[derive(Serialize)] +pub struct SpatialTreeList { + kind: &'static str, + root: TreeNode, +} + +impl SpatialTreeList { + pub fn new() -> Self { + SpatialTreeList { + kind: "spatial_tree", + root: TreeNode::new("root"), + } + } + + pub fn add(&mut self, item: TreeNode) { + self.root.add_child(item); + } +} + +#[derive(Serialize)] +pub struct RenderTaskList { + kind: &'static str, + root: TreeNode, +} + +impl RenderTaskList { + pub fn new() -> Self { + RenderTaskList { + kind: "render_tasks", + root: TreeNode::new("root"), + } + } + + pub fn add(&mut self, item: TreeNode) { + self.root.add_child(item); + } +} + +// A TreeNode-based PrintTreePrinter to serialize pretty-printed +// trees as json +pub struct TreeNodeBuilder { + levels: Vec, +} + +impl TreeNodeBuilder { + pub fn new(root: TreeNode) -> TreeNodeBuilder { + TreeNodeBuilder { levels: vec![root] } + } + + fn current_level_mut(&mut self) -> &mut TreeNode { + assert!(!self.levels.is_empty()); + self.levels.last_mut().unwrap() + } + + pub fn build(mut self) -> TreeNode { + assert!(self.levels.len() == 1); + self.levels.pop().unwrap() + } +} + +impl PrintTreePrinter for TreeNodeBuilder { + fn new_level(&mut self, title: String) { + let level = TreeNode::new(&title); + self.levels.push(level); + } + + fn end_level(&mut self) { + assert!(!self.levels.is_empty()); + let last_level = self.levels.pop().unwrap(); + self.current_level_mut().add_child(last_level); + } + + fn add_item(&mut self, text: String) { + self.current_level_mut().add_item(&text); + } +} diff --git a/third_party/webrender/webrender/src/device/gl.rs b/third_party/webrender/webrender/src/device/gl.rs new file mode 100644 index 00000000000..6ad0e98eef3 --- /dev/null +++ b/third_party/webrender/webrender/src/device/gl.rs @@ -0,0 +1,4046 @@ +/* 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 super::super::shader_source::{OPTIMIZED_SHADERS, UNOPTIMIZED_SHADERS}; +use api::{ColorF, ImageDescriptor, ImageFormat, MemoryReport}; +use api::{MixBlendMode, TextureTarget, VoidPtrToSizeFn}; +use api::units::*; +use euclid::default::Transform3D; +use gleam::gl; +use crate::internal_types::{FastHashMap, LayerIndex, RenderTargetInfo, Swizzle, SwizzleSettings}; +use crate::util::round_up_to_multiple; +use crate::profiler; +use log::Level; +use smallvec::SmallVec; +use std::{ + borrow::Cow, + cell::{Cell, RefCell}, + cmp, + collections::hash_map::Entry, + marker::PhantomData, + mem, + num::NonZeroUsize, + os::raw::c_void, + ops::Add, + path::PathBuf, + ptr, + rc::Rc, + slice, + sync::Arc, + thread, + time::Duration, +}; +use webrender_build::shader::{ + ProgramSourceDigest, ShaderKind, ShaderVersion, build_shader_main_string, + build_shader_prefix_string, do_build_shader_string, shader_source_from_file, +}; + +/// Sequence number for frames, as tracked by the device layer. +#[derive(Debug, Copy, Clone, PartialEq, Ord, Eq, PartialOrd)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct GpuFrameId(usize); + +impl GpuFrameId { + pub fn new(value: usize) -> Self { + GpuFrameId(value) + } +} + +impl Add for GpuFrameId { + type Output = GpuFrameId; + + fn add(self, other: usize) -> GpuFrameId { + GpuFrameId(self.0 + other) + } +} + +pub struct TextureSlot(pub usize); + +// In some places we need to temporarily bind a texture to any slot. +const DEFAULT_TEXTURE: TextureSlot = TextureSlot(0); + +#[repr(u32)] +pub enum DepthFunction { + Always = gl::ALWAYS, + Less = gl::LESS, + LessEqual = gl::LEQUAL, +} + +#[repr(u32)] +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum TextureFilter { + Nearest, + Linear, + Trilinear, +} + +/// A structure defining a particular workflow of texture transfers. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct TextureFormatPair { + /// Format the GPU natively stores texels in. + pub internal: T, + /// Format we expect the users to provide the texels in. + pub external: T, +} + +impl From for TextureFormatPair { + fn from(value: T) -> Self { + TextureFormatPair { + internal: value, + external: value, + } + } +} + +#[derive(Debug)] +pub enum VertexAttributeKind { + F32, + U8Norm, + U16Norm, + I32, + U16, +} + +#[derive(Debug)] +pub struct VertexAttribute { + pub name: &'static str, + pub count: u32, + pub kind: VertexAttributeKind, +} + +#[derive(Debug)] +pub struct VertexDescriptor { + pub vertex_attributes: &'static [VertexAttribute], + pub instance_attributes: &'static [VertexAttribute], +} + +enum FBOTarget { + Read, + Draw, +} + +/// Method of uploading texel data from CPU to GPU. +#[derive(Debug, Clone)] +pub enum UploadMethod { + /// Just call `glTexSubImage` directly with the CPU data pointer + Immediate, + /// Accumulate the changes in PBO first before transferring to a texture. + PixelBuffer(VertexUsageHint), +} + +/// Plain old data that can be used to initialize a texture. +pub unsafe trait Texel: Copy {} +unsafe impl Texel for u8 {} +unsafe impl Texel for f32 {} + +/// Returns the size in bytes of a depth target with the given dimensions. +fn depth_target_size_in_bytes(dimensions: &DeviceIntSize) -> usize { + // DEPTH24 textures generally reserve 3 bytes for depth and 1 byte + // for stencil, so we measure them as 32 bits. + let pixels = dimensions.width * dimensions.height; + (pixels as usize) * 4 +} + +pub fn get_gl_target(target: TextureTarget) -> gl::GLuint { + match target { + TextureTarget::Default => gl::TEXTURE_2D, + TextureTarget::Array => gl::TEXTURE_2D_ARRAY, + TextureTarget::Rect => gl::TEXTURE_RECTANGLE, + TextureTarget::External => gl::TEXTURE_EXTERNAL_OES, + } +} + +fn supports_extension(extensions: &[String], extension: &str) -> bool { + extensions.iter().any(|s| s == extension) +} + +fn get_shader_version(gl: &dyn gl::Gl) -> ShaderVersion { + match gl.get_type() { + gl::GlType::Gl => ShaderVersion::Gl, + gl::GlType::Gles => ShaderVersion::Gles, + } +} + +// Get an unoptimized shader string by name, from the built in resources or +// an override path, if supplied. +pub fn get_unoptimized_shader_source(shader_name: &str, base_path: Option<&PathBuf>) -> Cow<'static, str> { + if let Some(ref base) = base_path { + let shader_path = base.join(&format!("{}.glsl", shader_name)); + Cow::Owned(shader_source_from_file(&shader_path)) + } else { + Cow::Borrowed( + UNOPTIMIZED_SHADERS + .get(shader_name) + .expect("Shader not found") + .source + ) + } +} + +pub trait FileWatcherHandler: Send { + fn file_changed(&self, path: PathBuf); +} + +impl VertexAttributeKind { + fn size_in_bytes(&self) -> u32 { + match *self { + VertexAttributeKind::F32 => 4, + VertexAttributeKind::U8Norm => 1, + VertexAttributeKind::U16Norm => 2, + VertexAttributeKind::I32 => 4, + VertexAttributeKind::U16 => 2, + } + } +} + +impl VertexAttribute { + fn size_in_bytes(&self) -> u32 { + self.count * self.kind.size_in_bytes() + } + + fn bind_to_vao( + &self, + attr_index: gl::GLuint, + divisor: gl::GLuint, + stride: gl::GLint, + offset: gl::GLuint, + gl: &dyn gl::Gl, + ) { + gl.enable_vertex_attrib_array(attr_index); + gl.vertex_attrib_divisor(attr_index, divisor); + + match self.kind { + VertexAttributeKind::F32 => { + gl.vertex_attrib_pointer( + attr_index, + self.count as gl::GLint, + gl::FLOAT, + false, + stride, + offset, + ); + } + VertexAttributeKind::U8Norm => { + gl.vertex_attrib_pointer( + attr_index, + self.count as gl::GLint, + gl::UNSIGNED_BYTE, + true, + stride, + offset, + ); + } + VertexAttributeKind::U16Norm => { + gl.vertex_attrib_pointer( + attr_index, + self.count as gl::GLint, + gl::UNSIGNED_SHORT, + true, + stride, + offset, + ); + } + VertexAttributeKind::I32 => { + gl.vertex_attrib_i_pointer( + attr_index, + self.count as gl::GLint, + gl::INT, + stride, + offset, + ); + } + VertexAttributeKind::U16 => { + gl.vertex_attrib_i_pointer( + attr_index, + self.count as gl::GLint, + gl::UNSIGNED_SHORT, + stride, + offset, + ); + } + } + } +} + +impl VertexDescriptor { + fn instance_stride(&self) -> u32 { + self.instance_attributes + .iter() + .map(|attr| attr.size_in_bytes()) + .sum() + } + + fn bind_attributes( + attributes: &[VertexAttribute], + start_index: usize, + divisor: u32, + gl: &dyn gl::Gl, + vbo: VBOId, + ) { + vbo.bind(gl); + + let stride: u32 = attributes + .iter() + .map(|attr| attr.size_in_bytes()) + .sum(); + + let mut offset = 0; + for (i, attr) in attributes.iter().enumerate() { + let attr_index = (start_index + i) as gl::GLuint; + attr.bind_to_vao(attr_index, divisor, stride as _, offset, gl); + offset += attr.size_in_bytes(); + } + } + + fn bind(&self, gl: &dyn gl::Gl, main: VBOId, instance: VBOId) { + Self::bind_attributes(self.vertex_attributes, 0, 0, gl, main); + + if !self.instance_attributes.is_empty() { + Self::bind_attributes( + self.instance_attributes, + self.vertex_attributes.len(), + 1, gl, instance, + ); + } + } +} + +impl VBOId { + fn bind(&self, gl: &dyn gl::Gl) { + gl.bind_buffer(gl::ARRAY_BUFFER, self.0); + } +} + +impl IBOId { + fn bind(&self, gl: &dyn gl::Gl) { + gl.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, self.0); + } +} + +impl FBOId { + fn bind(&self, gl: &dyn gl::Gl, target: FBOTarget) { + let target = match target { + FBOTarget::Read => gl::READ_FRAMEBUFFER, + FBOTarget::Draw => gl::DRAW_FRAMEBUFFER, + }; + gl.bind_framebuffer(target, self.0); + } +} + +pub struct Stream<'a> { + attributes: &'a [VertexAttribute], + vbo: VBOId, +} + +pub struct VBO { + id: gl::GLuint, + target: gl::GLenum, + allocated_count: usize, + marker: PhantomData, +} + +impl VBO { + pub fn allocated_count(&self) -> usize { + self.allocated_count + } + + pub fn stream_with<'a>(&self, attributes: &'a [VertexAttribute]) -> Stream<'a> { + debug_assert_eq!( + mem::size_of::(), + attributes.iter().map(|a| a.size_in_bytes() as usize).sum::() + ); + Stream { + attributes, + vbo: VBOId(self.id), + } + } +} + +impl Drop for VBO { + fn drop(&mut self) { + debug_assert!(thread::panicking() || self.id == 0); + } +} + +#[cfg_attr(feature = "replay", derive(Clone))] +#[derive(Debug)] +pub struct ExternalTexture { + id: gl::GLuint, + target: gl::GLuint, + swizzle: Swizzle, + uv_rect: TexelRect, +} + +impl ExternalTexture { + pub fn new( + id: u32, + target: TextureTarget, + swizzle: Swizzle, + uv_rect: TexelRect, + ) -> Self { + ExternalTexture { + id, + target: get_gl_target(target), + swizzle, + uv_rect, + } + } + + #[cfg(feature = "replay")] + pub fn internal_id(&self) -> gl::GLuint { + self.id + } + + pub fn get_uv_rect(&self) -> TexelRect { + self.uv_rect + } +} + +bitflags! { + #[derive(Default)] + pub struct TextureFlags: u32 { + /// This texture corresponds to one of the shared texture caches. + const IS_SHARED_TEXTURE_CACHE = 1 << 0; + } +} + +/// WebRender interface to an OpenGL texture. +/// +/// Because freeing a texture requires various device handles that are not +/// reachable from this struct, manual destruction via `Device` is required. +/// Our `Drop` implementation asserts that this has happened. +#[derive(Debug)] +pub struct Texture { + id: gl::GLuint, + target: gl::GLuint, + layer_count: i32, + format: ImageFormat, + size: DeviceIntSize, + filter: TextureFilter, + flags: TextureFlags, + /// An internally mutable swizzling state that may change between batches. + active_swizzle: Cell, + /// Framebuffer Objects, one for each layer of the texture, allowing this + /// texture to be rendered to. Empty if this texture is not used as a render + /// target. + fbos: Vec, + /// Same as the above, but with a depth buffer attached. + /// + /// FBOs are cheap to create but expensive to reconfigure (since doing so + /// invalidates framebuffer completeness caching). Moreover, rendering with + /// a depth buffer attached but the depth write+test disabled relies on the + /// driver to optimize it out of the rendering pass, which most drivers + /// probably do but, according to jgilbert, is best not to rely on. + /// + /// So we lazily generate a second list of FBOs with depth. This list is + /// empty if this texture is not used as a render target _or_ if it is, but + /// the depth buffer has never been requested. + /// + /// Note that we always fill fbos, and then lazily create fbos_with_depth + /// when needed. We could make both lazy (i.e. render targets would have one + /// or the other, but not both, unless they were actually used in both + /// configurations). But that would complicate a lot of logic in this module, + /// and FBOs are cheap enough to create. + fbos_with_depth: Vec, + /// If we are unable to blit directly to a texture array then we need + /// an intermediate renderbuffer. + blit_workaround_buffer: Option<(RBOId, FBOId)>, + last_frame_used: GpuFrameId, +} + +impl Texture { + pub fn get_dimensions(&self) -> DeviceIntSize { + self.size + } + + pub fn get_layer_count(&self) -> i32 { + self.layer_count + } + + pub fn get_format(&self) -> ImageFormat { + self.format + } + + pub fn get_filter(&self) -> TextureFilter { + self.filter + } + + pub fn supports_depth(&self) -> bool { + !self.fbos_with_depth.is_empty() + } + + pub fn last_frame_used(&self) -> GpuFrameId { + self.last_frame_used + } + + pub fn used_in_frame(&self, frame_id: GpuFrameId) -> bool { + self.last_frame_used == frame_id + } + + /// Returns true if this texture was used within `threshold` frames of + /// the current frame. + pub fn used_recently(&self, current_frame_id: GpuFrameId, threshold: usize) -> bool { + self.last_frame_used + threshold >= current_frame_id + } + + /// Returns the flags for this texture. + pub fn flags(&self) -> &TextureFlags { + &self.flags + } + + /// Returns a mutable borrow of the flags for this texture. + pub fn flags_mut(&mut self) -> &mut TextureFlags { + &mut self.flags + } + + /// Returns the number of bytes (generally in GPU memory) that each layer of + /// this texture consumes. + pub fn layer_size_in_bytes(&self) -> usize { + assert!(self.layer_count > 0 || self.size.width + self.size.height == 0); + let bpp = self.format.bytes_per_pixel() as usize; + let w = self.size.width as usize; + let h = self.size.height as usize; + bpp * w * h + } + + /// Returns the number of bytes (generally in GPU memory) that this texture + /// consumes. + pub fn size_in_bytes(&self) -> usize { + self.layer_size_in_bytes() * (self.layer_count as usize) + } + + #[cfg(feature = "replay")] + pub fn into_external(mut self) -> ExternalTexture { + let ext = ExternalTexture { + id: self.id, + target: self.target, + swizzle: Swizzle::default(), + // TODO(gw): Support custom UV rect for external textures during captures + uv_rect: TexelRect::new( + 0.0, + 0.0, + self.size.width as f32, + self.size.height as f32, + ), + }; + self.id = 0; // don't complain, moved out + ext + } +} + +impl Drop for Texture { + fn drop(&mut self) { + debug_assert!(thread::panicking() || self.id == 0); + } +} + +pub struct Program { + id: gl::GLuint, + u_transform: gl::GLint, + u_mode: gl::GLint, + source_info: ProgramSourceInfo, + is_initialized: bool, +} + +impl Program { + pub fn is_initialized(&self) -> bool { + self.is_initialized + } +} + +impl Drop for Program { + fn drop(&mut self) { + debug_assert!( + thread::panicking() || self.id == 0, + "renderer::deinit not called" + ); + } +} + +pub struct CustomVAO { + id: gl::GLuint, +} + +impl Drop for CustomVAO { + fn drop(&mut self) { + debug_assert!( + thread::panicking() || self.id == 0, + "renderer::deinit not called" + ); + } +} + +pub struct VAO { + id: gl::GLuint, + ibo_id: IBOId, + main_vbo_id: VBOId, + instance_vbo_id: VBOId, + instance_stride: usize, + owns_vertices_and_indices: bool, +} + +impl Drop for VAO { + fn drop(&mut self) { + debug_assert!( + thread::panicking() || self.id == 0, + "renderer::deinit not called" + ); + } +} + +pub struct PBO { + id: gl::GLuint, + reserved_size: usize, +} + +impl PBO { + pub fn get_reserved_size(&self) -> usize { + self.reserved_size + } +} + +impl Drop for PBO { + fn drop(&mut self) { + debug_assert!( + thread::panicking() || self.id == 0, + "renderer::deinit not called" + ); + } +} + +pub struct BoundPBO<'a> { + device: &'a mut Device, + pub data: &'a [u8] +} + +impl<'a> Drop for BoundPBO<'a> { + fn drop(&mut self) { + self.device.gl.unmap_buffer(gl::PIXEL_PACK_BUFFER); + self.device.gl.bind_buffer(gl::PIXEL_PACK_BUFFER, 0); + } +} + +#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] +pub struct FBOId(gl::GLuint); + +#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] +pub struct RBOId(gl::GLuint); + +#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] +pub struct VBOId(gl::GLuint); + +#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] +struct IBOId(gl::GLuint); + +#[derive(Clone, Debug)] +enum ProgramSourceType { + Unoptimized, + Optimized(ShaderVersion), +} + +#[derive(Clone, Debug)] +pub struct ProgramSourceInfo { + base_filename: &'static str, + features: Vec<&'static str>, + source_type: ProgramSourceType, + digest: ProgramSourceDigest, +} + +impl ProgramSourceInfo { + fn new( + device: &Device, + name: &'static str, + features: &[&'static str], + ) -> Self { + + // Compute the digest. Assuming the device has a `ProgramCache`, this + // will always be needed, whereas the source is rarely needed. + + use std::collections::hash_map::DefaultHasher; + use std::hash::Hasher; + + // Setup. + let mut hasher = DefaultHasher::new(); + let gl_version = get_shader_version(&*device.gl()); + + // Hash the renderer name. + hasher.write(device.capabilities.renderer_name.as_bytes()); + + let full_name = &Self::full_name(name, features); + + let optimized_source = if device.use_optimized_shaders { + OPTIMIZED_SHADERS.get(&(gl_version, full_name)).or_else(|| { + warn!("Missing optimized shader source for {}", full_name); + None + }) + } else { + None + }; + + let source_type = match optimized_source { + Some(source_and_digest) => { + // Optimized shader sources are used as-is, without any run-time processing. + // The vertex and fragment shaders are different, so must both be hashed. + // We use the hashes that were computed at build time, and verify it in debug builds. + if cfg!(debug_assertions) { + let mut h = DefaultHasher::new(); + h.write(source_and_digest.vert_source.as_bytes()); + h.write(source_and_digest.frag_source.as_bytes()); + let d: ProgramSourceDigest = h.into(); + let digest = d.to_string(); + debug_assert_eq!(digest, source_and_digest.digest); + hasher.write(digest.as_bytes()); + } else { + hasher.write(source_and_digest.digest.as_bytes()); + } + + ProgramSourceType::Optimized(gl_version) + } + None => { + // For non-optimized sources we compute the hash by walking the static strings + // in the same order as we would when concatenating the source, to avoid + // heap-allocating in the common case. + // + // Note that we cheat a bit to make the hashing more efficient. First, the only + // difference between the vertex and fragment shader is a single deterministic + // define, so we don't need to hash both. Second, we precompute the digest of the + // expanded source file at build time, and then just hash that digest here. + let override_path = device.resource_override_path.as_ref(); + let source_and_digest = UNOPTIMIZED_SHADERS.get(&name).expect("Shader not found"); + + // Hash the prefix string. + build_shader_prefix_string( + gl_version, + &features, + ShaderKind::Vertex, + &name, + &mut |s| hasher.write(s.as_bytes()), + ); + + // Hash the shader file contents. We use a precomputed digest, and + // verify it in debug builds. + if override_path.is_some() || cfg!(debug_assertions) { + let mut h = DefaultHasher::new(); + build_shader_main_string( + &name, + &|f| get_unoptimized_shader_source(f, override_path), + &mut |s| h.write(s.as_bytes()) + ); + let d: ProgramSourceDigest = h.into(); + let digest = format!("{}", d); + debug_assert!(override_path.is_some() || digest == source_and_digest.digest); + hasher.write(digest.as_bytes()); + } else { + hasher.write(source_and_digest.digest.as_bytes()); + } + + ProgramSourceType::Unoptimized + } + }; + + // Finish. + ProgramSourceInfo { + base_filename: name, + features: features.to_vec(), + source_type, + digest: hasher.into(), + } + } + + fn compute_source(&self, device: &Device, kind: ShaderKind) -> String { + let full_name = Self::full_name(self.base_filename, &self.features); + match self.source_type { + ProgramSourceType::Optimized(gl_version) => { + let shader = OPTIMIZED_SHADERS + .get(&(gl_version, &full_name)) + .unwrap_or_else(|| panic!("Missing optimized shader source for {}", full_name)); + + match kind { + ShaderKind::Vertex => shader.vert_source.to_string(), + ShaderKind::Fragment => shader.frag_source.to_string(), + } + }, + ProgramSourceType::Unoptimized => { + let mut src = String::new(); + device.build_shader_string( + &self.features, + kind, + self.base_filename, + |s| src.push_str(s), + ); + src + } + } + } + + fn full_name(base_filename: &'static str, features: &[&'static str]) -> String { + if features.is_empty() { + base_filename.to_string() + } else { + format!("{}_{}", base_filename, features.join("_")) + } + } +} + +#[cfg_attr(feature = "serialize_program", derive(Deserialize, Serialize))] +pub struct ProgramBinary { + bytes: Vec, + format: gl::GLenum, + source_digest: ProgramSourceDigest, +} + +impl ProgramBinary { + fn new(bytes: Vec, + format: gl::GLenum, + source_digest: ProgramSourceDigest) -> Self { + ProgramBinary { + bytes, + format, + source_digest, + } + } + + /// Returns a reference to the source digest hash. + pub fn source_digest(&self) -> &ProgramSourceDigest { + &self.source_digest + } +} + +/// The interfaces that an application can implement to handle ProgramCache update +pub trait ProgramCacheObserver { + fn save_shaders_to_disk(&self, entries: Vec>); + fn set_startup_shaders(&self, entries: Vec>); + fn try_load_shader_from_disk(&self, digest: &ProgramSourceDigest, program_cache: &Rc); + fn notify_program_binary_failed(&self, program_binary: &Arc); +} + +struct ProgramCacheEntry { + /// The binary. + binary: Arc, + /// True if the binary has been linked, i.e. used for rendering. + linked: bool, +} + +pub struct ProgramCache { + entries: RefCell>, + + /// Optional trait object that allows the client + /// application to handle ProgramCache updating + program_cache_handler: Option>, + + /// Programs that have not yet been cached to disk (by program_cache_handler) + pending_entries: RefCell>>, +} + +impl ProgramCache { + pub fn new(program_cache_observer: Option>) -> Rc { + Rc::new( + ProgramCache { + entries: RefCell::new(FastHashMap::default()), + program_cache_handler: program_cache_observer, + pending_entries: RefCell::new(Vec::default()), + } + ) + } + + /// Save any new program binaries to the disk cache, and if startup has + /// just completed then write the list of shaders to load on next startup. + fn update_disk_cache(&self, startup_complete: bool) { + if let Some(ref handler) = self.program_cache_handler { + if !self.pending_entries.borrow().is_empty() { + let pending_entries = self.pending_entries.replace(Vec::default()); + handler.save_shaders_to_disk(pending_entries); + } + + if startup_complete { + let startup_shaders = self.entries.borrow().values() + .filter(|e| e.linked).map(|e| e.binary.clone()) + .collect::>(); + handler.set_startup_shaders(startup_shaders); + } + } + } + + /// Add a new ProgramBinary to the cache. + /// This function is typically used after compiling and linking a new program. + /// The binary will be saved to disk the next time update_disk_cache() is called. + fn add_new_program_binary(&self, program_binary: Arc) { + self.pending_entries.borrow_mut().push(program_binary.clone()); + + let digest = program_binary.source_digest.clone(); + let entry = ProgramCacheEntry { + binary: program_binary, + linked: true, + }; + self.entries.borrow_mut().insert(digest, entry); + } + + /// Load ProgramBinary to ProgramCache. + /// The function is typically used to load ProgramBinary from disk. + #[cfg(feature = "serialize_program")] + pub fn load_program_binary(&self, program_binary: Arc) { + let digest = program_binary.source_digest.clone(); + let entry = ProgramCacheEntry { + binary: program_binary, + linked: false, + }; + self.entries.borrow_mut().insert(digest, entry); + } + + /// Returns the number of bytes allocated for shaders in the cache. + pub fn report_memory(&self, op: VoidPtrToSizeFn) -> usize { + self.entries.borrow().values() + .map(|e| unsafe { op(e.binary.bytes.as_ptr() as *const c_void ) }) + .sum() + } +} + +#[derive(Debug, Copy, Clone)] +pub enum VertexUsageHint { + Static, + Dynamic, + Stream, +} + +impl VertexUsageHint { + fn to_gl(&self) -> gl::GLuint { + match *self { + VertexUsageHint::Static => gl::STATIC_DRAW, + VertexUsageHint::Dynamic => gl::DYNAMIC_DRAW, + VertexUsageHint::Stream => gl::STREAM_DRAW, + } + } +} + +#[derive(Copy, Clone, Debug)] +pub struct UniformLocation(gl::GLint); + +impl UniformLocation { + pub const INVALID: Self = UniformLocation(-1); +} + +#[derive(Debug)] +pub struct Capabilities { + /// Whether multisampled render targets are supported. + pub supports_multisampling: bool, + /// Whether the function `glCopyImageSubData` is available. + pub supports_copy_image_sub_data: bool, + /// Whether we are able to use `glBlitFramebuffers` with the draw fbo + /// bound to a non-0th layer of a texture array. This is buggy on + /// Adreno devices. + pub supports_blit_to_texture_array: bool, + /// Whether we can use the pixel local storage functionality that + /// is available on some mobile GPUs. This allows fast access to + /// the per-pixel tile memory. + pub supports_pixel_local_storage: bool, + /// Whether advanced blend equations are supported. + pub supports_advanced_blend_equation: bool, + /// Whether dual-source blending is supported. + pub supports_dual_source_blending: bool, + /// Whether KHR_debug is supported for getting debug messages from + /// the driver. + pub supports_khr_debug: bool, + /// Whether we can configure texture units to do swizzling on sampling. + pub supports_texture_swizzle: bool, + /// Whether the driver supports uploading to textures from a non-zero + /// offset within a PBO. + pub supports_nonzero_pbo_offsets: bool, + /// Whether the driver supports specifying the texture usage up front. + pub supports_texture_usage: bool, + /// The name of the renderer, as reported by GL + pub renderer_name: String, +} + +#[derive(Clone, Debug)] +pub enum ShaderError { + Compilation(String, String), // name, error message + Link(String, String), // name, error message +} + +/// A refcounted depth target, which may be shared by multiple textures across +/// the device. +struct SharedDepthTarget { + /// The Render Buffer Object representing the depth target. + rbo_id: RBOId, + /// Reference count. When this drops to zero, the RBO is deleted. + refcount: usize, +} + +#[cfg(debug_assertions)] +impl Drop for SharedDepthTarget { + fn drop(&mut self) { + debug_assert!(thread::panicking() || self.refcount == 0); + } +} + +/// Describes for which texture formats to use the glTexStorage* +/// family of functions. +#[derive(PartialEq, Debug)] +enum TexStorageUsage { + Never, + NonBGRA8, + Always, +} + +/// Describes a required alignment for a stride, +/// which can either be represented in bytes or pixels. +#[derive(Copy, Clone, Debug)] +pub enum StrideAlignment { + Bytes(NonZeroUsize), + Pixels(NonZeroUsize), +} + +impl StrideAlignment { + pub fn num_bytes(&self, format: ImageFormat) -> NonZeroUsize { + match *self { + Self::Bytes(bytes) => bytes, + Self::Pixels(pixels) => { + assert!(format.bytes_per_pixel() > 0); + NonZeroUsize::new(pixels.get() * format.bytes_per_pixel() as usize).unwrap() + } + } + } +} + +// We get 24 bits of Z value - use up 22 bits of it to give us +// 4 bits to account for GPU issues. This seems to manifest on +// some GPUs under certain perspectives due to z interpolation +// precision problems. +const RESERVE_DEPTH_BITS: i32 = 2; + +pub struct Device { + gl: Rc, + + /// If non-None, |gl| points to a profiling wrapper, and this points to the + /// underling Gl instance. + base_gl: Option>, + + // device state + bound_textures: [gl::GLuint; 16], + bound_program: gl::GLuint, + bound_vao: gl::GLuint, + bound_read_fbo: FBOId, + bound_draw_fbo: FBOId, + program_mode_id: UniformLocation, + default_read_fbo: FBOId, + default_draw_fbo: FBOId, + + /// Track depth state for assertions. Note that the default FBO has depth, + /// so this defaults to true. + depth_available: bool, + + upload_method: UploadMethod, + + // HW or API capabilities + capabilities: Capabilities, + + color_formats: TextureFormatPair, + bgra_formats: TextureFormatPair, + swizzle_settings: SwizzleSettings, + depth_format: gl::GLuint, + + /// Map from texture dimensions to shared depth buffers for render targets. + /// + /// Render targets often have the same width/height, so we can save memory + /// by sharing these across targets. + depth_targets: FastHashMap, + + // debug + inside_frame: bool, + + // resources + resource_override_path: Option, + + /// Whether to use shaders that have been optimized at build time. + use_optimized_shaders: bool, + + max_texture_size: i32, + max_texture_layers: u32, + cached_programs: Option>, + + // Frame counter. This is used to map between CPU + // frames and GPU frames. + frame_id: GpuFrameId, + + /// When to use glTexStorage*. We prefer this over glTexImage* because it + /// guarantees that mipmaps won't be generated (which they otherwise are on + /// some drivers, particularly ANGLE). However, it is not always supported + /// at all, or for BGRA8 format. If it's not supported for the required + /// format, we fall back to glTexImage*. + texture_storage_usage: TexStorageUsage, + + optimal_pbo_stride: StrideAlignment, + + /// Whether we must ensure the source strings passed to glShaderSource() + /// are null-terminated, to work around driver bugs. + requires_null_terminated_shader_source: bool, + + /// Whether we must unbind any texture from GL_TEXTURE_EXTERNAL_OES before + /// binding to GL_TEXTURE_2D, to work around an android emulator bug. + requires_texture_external_unbind: bool, + + // GL extensions + extensions: Vec, + + /// Dumps the source of the shader with the given name + dump_shader_source: Option, + + surface_origin_is_top_left: bool, + + /// A debug boolean for tracking if the shader program has been set after + /// a blend mode change. + /// + /// This is needed for compatibility with next-gen + /// GPU APIs that switch states using "pipeline object" that bundles + /// together the blending state with the shader. + /// + /// Having the constraint of always binding the shader last would allow + /// us to have the "pipeline object" bound at that time. Without this + /// constraint, we'd either have to eagerly bind the "pipeline object" + /// on changing either the shader or the blend more, or lazily bind it + /// at draw call time, neither of which is desirable. + #[cfg(debug_assertions)] + shader_is_ready: bool, +} + +/// Contains the parameters necessary to bind a draw target. +#[derive(Clone, Copy, Debug)] +pub enum DrawTarget { + /// Use the device's default draw target, with the provided dimensions, + /// which are used to set the viewport. + Default { + /// Target rectangle to draw. + rect: FramebufferIntRect, + /// Total size of the target. + total_size: FramebufferIntSize, + surface_origin_is_top_left: bool, + }, + /// Use the provided texture. + Texture { + /// Size of the texture in pixels + dimensions: DeviceIntSize, + /// The slice within the texture array to draw to + layer: LayerIndex, + /// Whether to draw with the texture's associated depth target + with_depth: bool, + /// Workaround buffers for devices with broken texture array copy implementation + blit_workaround_buffer: Option<(RBOId, FBOId)>, + /// FBO that corresponds to the selected layer / depth mode + fbo_id: FBOId, + /// Native GL texture ID + id: gl::GLuint, + /// Native GL texture target + target: gl::GLuint, + }, + /// Use an FBO attached to an external texture. + External { + fbo: FBOId, + size: FramebufferIntSize, + }, + /// An OS compositor surface + NativeSurface { + offset: DeviceIntPoint, + external_fbo_id: u32, + dimensions: DeviceIntSize, + }, +} + +impl DrawTarget { + pub fn new_default(size: DeviceIntSize, surface_origin_is_top_left: bool) -> Self { + let total_size = device_size_as_framebuffer_size(size); + DrawTarget::Default { + rect: total_size.into(), + total_size, + surface_origin_is_top_left, + } + } + + /// Returns true if this draw target corresponds to the default framebuffer. + pub fn is_default(&self) -> bool { + match *self { + DrawTarget::Default {..} => true, + _ => false, + } + } + + pub fn from_texture( + texture: &Texture, + layer: usize, + with_depth: bool, + ) -> Self { + let fbo_id = if with_depth { + texture.fbos_with_depth[layer] + } else { + texture.fbos[layer] + }; + + DrawTarget::Texture { + dimensions: texture.get_dimensions(), + fbo_id, + with_depth, + layer, + blit_workaround_buffer: texture.blit_workaround_buffer, + id: texture.id, + target: texture.target, + } + } + + /// Returns the dimensions of this draw-target. + pub fn dimensions(&self) -> DeviceIntSize { + match *self { + DrawTarget::Default { total_size, .. } => total_size.cast_unit(), + DrawTarget::Texture { dimensions, .. } => dimensions, + DrawTarget::External { size, .. } => size.cast_unit(), + DrawTarget::NativeSurface { dimensions, .. } => dimensions, + } + } + + pub fn to_framebuffer_rect(&self, device_rect: DeviceIntRect) -> FramebufferIntRect { + let mut fb_rect = device_rect_as_framebuffer_rect(&device_rect); + match *self { + DrawTarget::Default { ref rect, surface_origin_is_top_left, .. } => { + // perform a Y-flip here + if !surface_origin_is_top_left { + fb_rect.origin.y = rect.origin.y + rect.size.height - fb_rect.origin.y - fb_rect.size.height; + fb_rect.origin.x += rect.origin.x; + } + } + DrawTarget::Texture { .. } | DrawTarget::External { .. } => (), + DrawTarget::NativeSurface { .. } => { + panic!("bug: is this ever used for native surfaces?"); + } + } + fb_rect + } + + /// Given a scissor rect, convert it to the right coordinate space + /// depending on the draw target kind. If no scissor rect was supplied, + /// returns a scissor rect that encloses the entire render target. + pub fn build_scissor_rect( + &self, + scissor_rect: Option, + content_origin: DeviceIntPoint, + ) -> FramebufferIntRect { + let dimensions = self.dimensions(); + + match scissor_rect { + Some(scissor_rect) => match *self { + DrawTarget::Default { ref rect, .. } => { + self.to_framebuffer_rect(scissor_rect.translate(-content_origin.to_vector())) + .intersection(rect) + .unwrap_or_else(FramebufferIntRect::zero) + } + DrawTarget::NativeSurface { offset, .. } => { + device_rect_as_framebuffer_rect(&scissor_rect.translate(offset.to_vector())) + } + DrawTarget::Texture { .. } | DrawTarget::External { .. } => { + device_rect_as_framebuffer_rect(&scissor_rect) + } + } + None => { + FramebufferIntRect::new( + FramebufferIntPoint::zero(), + device_size_as_framebuffer_size(dimensions), + ) + } + } + } +} + +/// Contains the parameters necessary to bind a texture-backed read target. +#[derive(Clone, Copy, Debug)] +pub enum ReadTarget { + /// Use the device's default draw target. + Default, + /// Use the provided texture, + Texture { + /// ID of the FBO to read from. + fbo_id: FBOId, + }, + /// Use an FBO attached to an external texture. + External { + fbo: FBOId, + }, +} + +impl ReadTarget { + pub fn from_texture( + texture: &Texture, + layer: usize, + ) -> Self { + ReadTarget::Texture { + fbo_id: texture.fbos[layer], + } + } +} + +impl From for ReadTarget { + fn from(t: DrawTarget) -> Self { + match t { + DrawTarget::Default { .. } => ReadTarget::Default, + DrawTarget::NativeSurface { .. } => { + unreachable!("bug: native surfaces cannot be read targets"); + } + DrawTarget::Texture { fbo_id, .. } => + ReadTarget::Texture { fbo_id }, + DrawTarget::External { fbo, .. } => + ReadTarget::External { fbo }, + } + } +} + +impl Device { + pub fn new( + mut gl: Rc, + resource_override_path: Option, + use_optimized_shaders: bool, + upload_method: UploadMethod, + cached_programs: Option>, + allow_pixel_local_storage_support: bool, + allow_texture_storage_support: bool, + allow_texture_swizzling: bool, + dump_shader_source: Option, + surface_origin_is_top_left: bool, + panic_on_gl_error: bool, + ) -> Device { + let mut max_texture_size = [0]; + let mut max_texture_layers = [0]; + unsafe { + gl.get_integer_v(gl::MAX_TEXTURE_SIZE, &mut max_texture_size); + gl.get_integer_v(gl::MAX_ARRAY_TEXTURE_LAYERS, &mut max_texture_layers); + } + + let max_texture_size = max_texture_size[0]; + let max_texture_layers = max_texture_layers[0] as u32; + let renderer_name = gl.get_string(gl::RENDERER); + + let mut extension_count = [0]; + unsafe { + gl.get_integer_v(gl::NUM_EXTENSIONS, &mut extension_count); + } + let extension_count = extension_count[0] as gl::GLuint; + let mut extensions = Vec::new(); + for i in 0 .. extension_count { + extensions.push(gl.get_string_i(gl::EXTENSIONS, i)); + } + + // On debug builds, assert that each GL call is error-free. We don't do + // this on release builds because the synchronous call can stall the + // pipeline. + let supports_khr_debug = supports_extension(&extensions, "GL_KHR_debug"); + if panic_on_gl_error || cfg!(debug_assertions) { + gl = gl::ErrorReactingGl::wrap(gl, move |gl, name, code| { + if supports_khr_debug { + Self::log_driver_messages(gl); + } + println!("Caught GL error {:x} at {}", code, name); + panic!("Caught GL error {:x} at {}", code, name); + }); + } + + if supports_extension(&extensions, "GL_ANGLE_provoking_vertex") { + gl.provoking_vertex_angle(gl::FIRST_VERTEX_CONVENTION); + } + + let supports_texture_usage = supports_extension(&extensions, "GL_ANGLE_texture_usage"); + + // Our common-case image data in Firefox is BGRA, so we make an effort + // to use BGRA as the internal texture storage format to avoid the need + // to swizzle during upload. Currently we only do this on GLES (and thus + // for Windows, via ANGLE). + // + // On Mac, Apple docs [1] claim that BGRA is a more efficient internal + // format, but they don't support it with glTextureStorage. As a workaround, + // we pretend that it's RGBA8 for the purposes of texture transfers, + // but swizzle R with B for the texture sampling. + // + // We also need our internal format types to be sized, since glTexStorage* + // will reject non-sized internal format types. + // + // Unfortunately, with GL_EXT_texture_format_BGRA8888, BGRA8 is not a + // valid internal format (for glTexImage* or glTexStorage*) unless + // GL_EXT_texture_storage is also available [2][3], which is usually + // not the case on GLES 3 as the latter's functionality has been + // included by default but the former has not been updated. + // The extension is available on ANGLE, but on Android this usually + // means we must fall back to using unsized BGRA and glTexImage*. + // + // Overall, we have the following factors in play when choosing the formats: + // - with glTexStorage, the internal format needs to match the external format, + // or the driver would have to do the conversion, which is slow + // - on desktop GL, there is no BGRA internal format. However, initializing + // the textures with glTexImage as RGBA appears to use BGRA internally, + // preferring BGRA external data [4]. + // - when glTexStorage + BGRA internal format is not supported, + // and the external data is BGRA, we have the following options: + // 1. use glTexImage with RGBA internal format, this costs us VRAM for mipmaps + // 2. use glTexStorage with RGBA internal format, this costs us the conversion by the driver + // 3. pretend we are uploading RGBA and set up the swizzling of the texture unit - this costs us batch breaks + // + // [1] https://developer.apple.com/library/archive/documentation/ + // GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/ + // opengl_texturedata.html#//apple_ref/doc/uid/TP40001987-CH407-SW22 + // [2] https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_format_BGRA8888.txt + // [3] https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_storage.txt + // [4] http://http.download.nvidia.com/developer/Papers/2005/Fast_Texture_Transfers/Fast_Texture_Transfers.pdf + + // To support BGRA8 with glTexStorage* we specifically need + // GL_EXT_texture_storage and GL_EXT_texture_format_BGRA8888. + let supports_gles_bgra = supports_extension(&extensions, "GL_EXT_texture_format_BGRA8888"); + + // On the android emulator glTexImage fails to create textures larger than 3379. + // So we must use glTexStorage instead. See bug 1591436. + let is_emulator = renderer_name.starts_with("Android Emulator"); + let avoid_tex_image = is_emulator; + let gl_version = gl.get_string(gl::VERSION); + + let supports_texture_storage = allow_texture_storage_support && + match gl.get_type() { + gl::GlType::Gl => supports_extension(&extensions, "GL_ARB_texture_storage"), + // ES 3 technically always supports glTexStorage, but only check here for the extension + // necessary to interact with BGRA. + gl::GlType::Gles => supports_extension(&extensions, "GL_EXT_texture_storage"), + }; + let supports_texture_swizzle = allow_texture_swizzling && + match gl.get_type() { + // see https://www.g-truc.net/post-0734.html + gl::GlType::Gl => gl_version.as_str() >= "3.3" || + supports_extension(&extensions, "GL_ARB_texture_swizzle"), + gl::GlType::Gles => true, + }; + + let (color_formats, bgra_formats, bgra8_sampling_swizzle, texture_storage_usage) = match gl.get_type() { + // There is `glTexStorage`, use it and expect RGBA on the input. + gl::GlType::Gl if supports_texture_storage && supports_texture_swizzle => ( + TextureFormatPair::from(ImageFormat::RGBA8), + TextureFormatPair { internal: gl::RGBA8, external: gl::RGBA }, + Swizzle::Bgra, // pretend it's RGBA, rely on swizzling + TexStorageUsage::Always + ), + // There is no `glTexStorage`, upload as `glTexImage` with BGRA input. + gl::GlType::Gl => ( + TextureFormatPair { internal: ImageFormat::RGBA8, external: ImageFormat::BGRA8 }, + TextureFormatPair { internal: gl::RGBA, external: gl::BGRA }, + Swizzle::Rgba, // converted on uploads by the driver, no swizzling needed + TexStorageUsage::Never + ), + // We can use glTexStorage with BGRA8 as the internal format. + gl::GlType::Gles if supports_gles_bgra && supports_texture_storage => ( + TextureFormatPair::from(ImageFormat::BGRA8), + TextureFormatPair { internal: gl::BGRA8_EXT, external: gl::BGRA_EXT }, + Swizzle::Rgba, // no conversion needed + TexStorageUsage::Always, + ), + // For BGRA8 textures we must use the unsized BGRA internal + // format and glTexImage. If texture storage is supported we can + // use it for other formats, which is always the case for ES 3. + // We can't use glTexStorage with BGRA8 as the internal format. + gl::GlType::Gles if supports_gles_bgra && !avoid_tex_image => ( + TextureFormatPair::from(ImageFormat::RGBA8), + TextureFormatPair::from(gl::BGRA_EXT), + Swizzle::Rgba, // no conversion needed + TexStorageUsage::NonBGRA8, + ), + // BGRA is not supported as an internal format, therefore we will + // use RGBA. The swizzling will happen at the texture unit. + gl::GlType::Gles if supports_texture_swizzle => ( + TextureFormatPair::from(ImageFormat::RGBA8), + TextureFormatPair { internal: gl::RGBA8, external: gl::RGBA }, + Swizzle::Bgra, // pretend it's RGBA, rely on swizzling + TexStorageUsage::Always, + ), + // BGRA and swizzling are not supported. We force the conversion done by the driver. + gl::GlType::Gles => ( + TextureFormatPair::from(ImageFormat::RGBA8), + TextureFormatPair { internal: gl::RGBA8, external: gl::BGRA }, + Swizzle::Rgba, + TexStorageUsage::Always, + ), + }; + + let is_software_webrender = renderer_name.starts_with("Software WebRender"); + let (depth_format, upload_method) = if is_software_webrender { + (gl::DEPTH_COMPONENT16, UploadMethod::Immediate) + } else { + (gl::DEPTH_COMPONENT24, upload_method) + }; + + info!("GL texture cache {:?}, bgra {:?} swizzle {:?}, texture storage {:?}, depth {:?}", + color_formats, bgra_formats, bgra8_sampling_swizzle, texture_storage_usage, depth_format); + let supports_copy_image_sub_data = supports_extension(&extensions, "GL_EXT_copy_image") || + supports_extension(&extensions, "GL_ARB_copy_image"); + + // Due to a bug on Adreno devices, blitting to an fbo bound to + // a non-0th layer of a texture array is not supported. + let supports_blit_to_texture_array = !renderer_name.starts_with("Adreno"); + + // Check if the device supports the two extensions needed in order to use + // pixel local storage. + // TODO(gw): Consider if we can remove fb fetch / init, by using PLS for opaque pass too. + // TODO(gw): Support EXT_shader_framebuffer_fetch as well. + let ext_pixel_local_storage = supports_extension(&extensions, "GL_EXT_shader_pixel_local_storage"); + let ext_framebuffer_fetch = supports_extension(&extensions, "GL_ARM_shader_framebuffer_fetch"); + let supports_pixel_local_storage = + allow_pixel_local_storage_support && + ext_framebuffer_fetch && + ext_pixel_local_storage; + + let is_adreno = renderer_name.starts_with("Adreno"); + + // KHR_blend_equation_advanced renders incorrectly on Adreno + // devices. This has only been confirmed up to Adreno 5xx, and has been + // fixed for Android 9, so this condition could be made more specific. + let supports_advanced_blend_equation = + supports_extension(&extensions, "GL_KHR_blend_equation_advanced") && + !is_adreno; + + let supports_dual_source_blending = match gl.get_type() { + gl::GlType::Gl => supports_extension(&extensions,"GL_ARB_blend_func_extended") && + supports_extension(&extensions,"GL_ARB_explicit_attrib_location"), + gl::GlType::Gles => supports_extension(&extensions,"GL_EXT_blend_func_extended"), + }; + + // Software webrender relies on the unoptimized shader source. + let use_optimized_shaders = use_optimized_shaders && !is_software_webrender; + + // On the android emulator, glShaderSource can crash if the source + // strings are not null-terminated. See bug 1591945. + let requires_null_terminated_shader_source = is_emulator; + + // The android emulator gets confused if you don't explicitly unbind any texture + // from GL_TEXTURE_EXTERNAL_OES before binding another to GL_TEXTURE_2D. See bug 1636085. + let requires_texture_external_unbind = is_emulator; + + let is_amd_macos = cfg!(target_os = "macos") && renderer_name.starts_with("AMD"); + + // On certain GPUs PBO texture upload is only performed asynchronously + // if the stride of the data is a multiple of a certain value. + // On Adreno it must be a multiple of 64 pixels, meaning value in bytes + // varies with the texture format. + // On AMD Mac, it must always be a multiple of 256 bytes. + // Other platforms may have similar requirements and should be added + // here. + // The default value should be 4 bytes. + let optimal_pbo_stride = if is_adreno { + StrideAlignment::Pixels(NonZeroUsize::new(64).unwrap()) + } else if is_amd_macos { + StrideAlignment::Bytes(NonZeroUsize::new(256).unwrap()) + } else { + StrideAlignment::Bytes(NonZeroUsize::new(4).unwrap()) + }; + + // On AMD Macs there is a driver bug which causes some texture uploads + // from a non-zero offset within a PBO to fail. See bug 1603783. + let supports_nonzero_pbo_offsets = !is_amd_macos; + + Device { + gl, + base_gl: None, + resource_override_path, + use_optimized_shaders, + upload_method, + inside_frame: false, + + capabilities: Capabilities { + supports_multisampling: false, //TODO + supports_copy_image_sub_data, + supports_blit_to_texture_array, + supports_pixel_local_storage, + supports_advanced_blend_equation, + supports_dual_source_blending, + supports_khr_debug, + supports_texture_swizzle, + supports_nonzero_pbo_offsets, + supports_texture_usage, + renderer_name, + }, + + color_formats, + bgra_formats, + swizzle_settings: SwizzleSettings { + bgra8_sampling_swizzle, + }, + depth_format, + + depth_targets: FastHashMap::default(), + + bound_textures: [0; 16], + bound_program: 0, + bound_vao: 0, + bound_read_fbo: FBOId(0), + bound_draw_fbo: FBOId(0), + program_mode_id: UniformLocation::INVALID, + default_read_fbo: FBOId(0), + default_draw_fbo: FBOId(0), + + depth_available: true, + + max_texture_size, + max_texture_layers, + cached_programs, + frame_id: GpuFrameId(0), + extensions, + texture_storage_usage, + requires_null_terminated_shader_source, + requires_texture_external_unbind, + optimal_pbo_stride, + dump_shader_source, + surface_origin_is_top_left, + + #[cfg(debug_assertions)] + shader_is_ready: false, + } + } + + pub fn gl(&self) -> &dyn gl::Gl { + &*self.gl + } + + pub fn rc_gl(&self) -> &Rc { + &self.gl + } + + /// Ensures that the maximum texture size is less than or equal to the + /// provided value. If the provided value is less than the value supported + /// by the driver, the latter is used. + pub fn clamp_max_texture_size(&mut self, size: i32) { + self.max_texture_size = self.max_texture_size.min(size); + } + + /// Returns the limit on texture dimensions (width or height). + pub fn max_texture_size(&self) -> i32 { + self.max_texture_size + } + + pub fn surface_origin_is_top_left(&self) -> bool { + self.surface_origin_is_top_left + } + + /// Returns the limit on texture array layers. + pub fn max_texture_layers(&self) -> usize { + self.max_texture_layers as usize + } + + pub fn get_capabilities(&self) -> &Capabilities { + &self.capabilities + } + + pub fn preferred_color_formats(&self) -> TextureFormatPair { + self.color_formats.clone() + } + + pub fn swizzle_settings(&self) -> Option { + if self.capabilities.supports_texture_swizzle { + Some(self.swizzle_settings) + } else { + None + } + } + + pub fn depth_bits(&self) -> i32 { + match self.depth_format { + gl::DEPTH_COMPONENT16 => 16, + gl::DEPTH_COMPONENT24 => 24, + _ => panic!("Unknown depth format {:?}", self.depth_format), + } + } + + // See gpu_types.rs where we declare the number of possible documents and + // number of items per document. This should match up with that. + pub fn max_depth_ids(&self) -> i32 { + return 1 << (self.depth_bits() - RESERVE_DEPTH_BITS); + } + + pub fn ortho_near_plane(&self) -> f32 { + return -self.max_depth_ids() as f32; + } + + pub fn ortho_far_plane(&self) -> f32 { + return (self.max_depth_ids() - 1) as f32; + } + + pub fn optimal_pbo_stride(&self) -> StrideAlignment { + self.optimal_pbo_stride + } + + pub fn reset_state(&mut self) { + for i in 0 .. self.bound_textures.len() { + self.bound_textures[i] = 0; + self.gl.active_texture(gl::TEXTURE0 + i as gl::GLuint); + self.gl.bind_texture(gl::TEXTURE_2D, 0); + } + + self.bound_vao = 0; + self.gl.bind_vertex_array(0); + + self.bound_read_fbo = self.default_read_fbo; + self.gl.bind_framebuffer(gl::READ_FRAMEBUFFER, self.bound_read_fbo.0); + + self.bound_draw_fbo = self.default_draw_fbo; + self.gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, self.bound_draw_fbo.0); + } + + #[cfg(debug_assertions)] + fn print_shader_errors(source: &str, log: &str) { + // hacky way to extract the offending lines + if !log.starts_with("0:") && !log.starts_with("0(") { + return; + } + let end_pos = match log[2..].chars().position(|c| !c.is_digit(10)) { + Some(pos) => 2 + pos, + None => return, + }; + let base_line_number = match log[2 .. end_pos].parse::() { + Ok(number) if number >= 2 => number - 2, + _ => return, + }; + for (line, prefix) in source.lines().skip(base_line_number).zip(&["|",">","|"]) { + error!("{}\t{}", prefix, line); + } + } + + pub fn compile_shader( + gl: &dyn gl::Gl, + name: &str, + shader_type: gl::GLenum, + source: &String, + requires_null_terminated_shader_source: bool, + ) -> Result { + debug!("compile {}", name); + let id = gl.create_shader(shader_type); + if requires_null_terminated_shader_source { + // Ensure the source strings we pass to glShaderSource are + // null-terminated on buggy platforms. + use std::ffi::CString; + let terminated_source = CString::new(source.as_bytes()).unwrap(); + gl.shader_source(id, &[terminated_source.as_bytes_with_nul()]); + } else { + gl.shader_source(id, &[source.as_bytes()]); + } + gl.compile_shader(id); + let log = gl.get_shader_info_log(id); + let mut status = [0]; + unsafe { + gl.get_shader_iv(id, gl::COMPILE_STATUS, &mut status); + } + if status[0] == 0 { + error!("Failed to compile shader: {}\n{}", name, log); + #[cfg(debug_assertions)] + Self::print_shader_errors(source, &log); + Err(ShaderError::Compilation(name.to_string(), log)) + } else { + if !log.is_empty() { + warn!("Warnings detected on shader: {}\n{}", name, log); + } + Ok(id) + } + } + + pub fn begin_frame(&mut self) -> GpuFrameId { + debug_assert!(!self.inside_frame); + self.inside_frame = true; + #[cfg(debug_assertions)] + { + self.shader_is_ready = false; + } + + // If our profiler state has changed, apply or remove the profiling + // wrapper from our GL context. + let being_profiled = profiler::thread_is_being_profiled(); + let using_wrapper = self.base_gl.is_some(); + if being_profiled && !using_wrapper { + fn note(name: &str, duration: Duration) { + profiler::add_text_marker(cstr!("OpenGL Calls"), name, duration); + } + let threshold = Duration::from_millis(1); + let wrapped = gl::ProfilingGl::wrap(self.gl.clone(), threshold, note); + let base = mem::replace(&mut self.gl, wrapped); + self.base_gl = Some(base); + } else if !being_profiled && using_wrapper { + self.gl = self.base_gl.take().unwrap(); + } + + // Retrieve the currently set FBO. + let mut default_read_fbo = [0]; + unsafe { + self.gl.get_integer_v(gl::READ_FRAMEBUFFER_BINDING, &mut default_read_fbo); + } + self.default_read_fbo = FBOId(default_read_fbo[0] as gl::GLuint); + let mut default_draw_fbo = [0]; + unsafe { + self.gl.get_integer_v(gl::DRAW_FRAMEBUFFER_BINDING, &mut default_draw_fbo); + } + self.default_draw_fbo = FBOId(default_draw_fbo[0] as gl::GLuint); + + // Shader state + self.bound_program = 0; + self.program_mode_id = UniformLocation::INVALID; + self.gl.use_program(0); + + // Reset common state + self.reset_state(); + + // Pixel op state + self.gl.pixel_store_i(gl::UNPACK_ALIGNMENT, 1); + self.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0); + + // Default is sampler 0, always + self.gl.active_texture(gl::TEXTURE0); + + self.frame_id + } + + fn bind_texture_impl( + &mut self, slot: TextureSlot, id: gl::GLuint, target: gl::GLenum, set_swizzle: Option + ) { + debug_assert!(self.inside_frame); + + if self.bound_textures[slot.0] != id || set_swizzle.is_some() { + self.gl.active_texture(gl::TEXTURE0 + slot.0 as gl::GLuint); + // The android emulator gets confused if you don't explicitly unbind any texture + // from GL_TEXTURE_EXTERNAL_OES before binding to GL_TEXTURE_2D. See bug 1636085. + if target == gl::TEXTURE_2D && self.requires_texture_external_unbind { + self.gl.bind_texture(gl::TEXTURE_EXTERNAL_OES, 0); + } + self.gl.bind_texture(target, id); + if let Some(swizzle) = set_swizzle { + if self.capabilities.supports_texture_swizzle { + let components = match swizzle { + Swizzle::Rgba => [gl::RED, gl::GREEN, gl::BLUE, gl::ALPHA], + Swizzle::Bgra => [gl::BLUE, gl::GREEN, gl::RED, gl::ALPHA], + }; + self.gl.tex_parameter_i(target, gl::TEXTURE_SWIZZLE_R, components[0] as i32); + self.gl.tex_parameter_i(target, gl::TEXTURE_SWIZZLE_G, components[1] as i32); + self.gl.tex_parameter_i(target, gl::TEXTURE_SWIZZLE_B, components[2] as i32); + self.gl.tex_parameter_i(target, gl::TEXTURE_SWIZZLE_A, components[3] as i32); + } else { + debug_assert_eq!(swizzle, Swizzle::default()); + } + } + self.gl.active_texture(gl::TEXTURE0); + self.bound_textures[slot.0] = id; + } + } + + pub fn bind_texture(&mut self, slot: S, texture: &Texture, swizzle: Swizzle) + where + S: Into, + { + let old_swizzle = texture.active_swizzle.replace(swizzle); + let set_swizzle = if old_swizzle != swizzle { + Some(swizzle) + } else { + None + }; + self.bind_texture_impl(slot.into(), texture.id, texture.target, set_swizzle); + } + + pub fn bind_external_texture(&mut self, slot: S, external_texture: &ExternalTexture) + where + S: Into, + { + self.bind_texture_impl(slot.into(), external_texture.id, external_texture.target, None); + } + + pub fn bind_read_target_impl(&mut self, fbo_id: FBOId) { + debug_assert!(self.inside_frame); + + if self.bound_read_fbo != fbo_id { + self.bound_read_fbo = fbo_id; + fbo_id.bind(self.gl(), FBOTarget::Read); + } + } + + pub fn bind_read_target(&mut self, target: ReadTarget) { + let fbo_id = match target { + ReadTarget::Default => self.default_read_fbo, + ReadTarget::Texture { fbo_id } => fbo_id, + ReadTarget::External { fbo } => fbo, + }; + + self.bind_read_target_impl(fbo_id) + } + + fn bind_draw_target_impl(&mut self, fbo_id: FBOId) { + debug_assert!(self.inside_frame); + + if self.bound_draw_fbo != fbo_id { + self.bound_draw_fbo = fbo_id; + fbo_id.bind(self.gl(), FBOTarget::Draw); + } + } + + pub fn reset_read_target(&mut self) { + let fbo = self.default_read_fbo; + self.bind_read_target_impl(fbo); + } + + + pub fn reset_draw_target(&mut self) { + let fbo = self.default_draw_fbo; + self.bind_draw_target_impl(fbo); + self.depth_available = true; + } + + pub fn bind_draw_target( + &mut self, + target: DrawTarget, + ) { + let (fbo_id, rect, depth_available) = match target { + DrawTarget::Default { rect, .. } => { + (self.default_draw_fbo, rect, true) + } + DrawTarget::Texture { dimensions, fbo_id, with_depth, .. } => { + let rect = FramebufferIntRect::new( + FramebufferIntPoint::zero(), + device_size_as_framebuffer_size(dimensions), + ); + (fbo_id, rect, with_depth) + }, + DrawTarget::External { fbo, size } => { + (fbo, size.into(), false) + } + DrawTarget::NativeSurface { external_fbo_id, offset, dimensions, .. } => { + ( + FBOId(external_fbo_id), + device_rect_as_framebuffer_rect(&DeviceIntRect::new(offset, dimensions)), + true + ) + } + }; + + self.depth_available = depth_available; + self.bind_draw_target_impl(fbo_id); + self.gl.viewport( + rect.origin.x, + rect.origin.y, + rect.size.width, + rect.size.height, + ); + } + + /// Creates an unbound FBO object. Additional attachment API calls are + /// required to make it complete. + pub fn create_fbo(&mut self) -> FBOId { + FBOId(self.gl.gen_framebuffers(1)[0]) + } + + /// Creates an FBO with the given texture bound as the color attachment. + pub fn create_fbo_for_external_texture(&mut self, texture_id: u32) -> FBOId { + let fbo = self.create_fbo(); + fbo.bind(self.gl(), FBOTarget::Draw); + self.gl.framebuffer_texture_2d( + gl::DRAW_FRAMEBUFFER, + gl::COLOR_ATTACHMENT0, + gl::TEXTURE_2D, + texture_id, + 0, + ); + debug_assert_eq!( + self.gl.check_frame_buffer_status(gl::DRAW_FRAMEBUFFER), + gl::FRAMEBUFFER_COMPLETE, + "Incomplete framebuffer", + ); + self.bound_draw_fbo.bind(self.gl(), FBOTarget::Draw); + fbo + } + + pub fn delete_fbo(&mut self, fbo: FBOId) { + self.gl.delete_framebuffers(&[fbo.0]); + } + + pub fn bind_external_draw_target(&mut self, fbo_id: FBOId) { + debug_assert!(self.inside_frame); + + if self.bound_draw_fbo != fbo_id { + self.bound_draw_fbo = fbo_id; + fbo_id.bind(self.gl(), FBOTarget::Draw); + } + } + + /// Link a program, attaching the supplied vertex format. + /// + /// If `create_program()` finds a binary shader on disk, it will kick + /// off linking immediately, which some drivers (notably ANGLE) run + /// in parallel on background threads. As such, this function should + /// ideally be run sometime later, to give the driver time to do that + /// before blocking due to an API call accessing the shader. + /// + /// This generally means that the first run of the application will have + /// to do a bunch of blocking work to compile the shader from source, but + /// subsequent runs should load quickly. + pub fn link_program( + &mut self, + program: &mut Program, + descriptor: &VertexDescriptor, + ) -> Result<(), ShaderError> { + assert!(!program.is_initialized()); + let mut build_program = true; + let info = &program.source_info; + + // See if we hit the binary shader cache + if let Some(ref cached_programs) = self.cached_programs { + // If the shader is not in the cache, attempt to load it from disk + if cached_programs.entries.borrow().get(&program.source_info.digest).is_none() { + if let Some(ref handler) = cached_programs.program_cache_handler { + handler.try_load_shader_from_disk(&program.source_info.digest, cached_programs); + if let Some(entry) = cached_programs.entries.borrow().get(&program.source_info.digest) { + self.gl.program_binary(program.id, entry.binary.format, &entry.binary.bytes); + } + } + } + + if let Some(entry) = cached_programs.entries.borrow_mut().get_mut(&info.digest) { + let mut link_status = [0]; + unsafe { + self.gl.get_program_iv(program.id, gl::LINK_STATUS, &mut link_status); + } + if link_status[0] == 0 { + let error_log = self.gl.get_program_info_log(program.id); + error!( + "Failed to load a program object with a program binary: {} renderer {}\n{}", + &info.base_filename, + self.capabilities.renderer_name, + error_log + ); + if let Some(ref program_cache_handler) = cached_programs.program_cache_handler { + program_cache_handler.notify_program_binary_failed(&entry.binary); + } + } else { + entry.linked = true; + build_program = false; + } + } + } + + // If not, we need to do a normal compile + link pass. + if build_program { + // Compile the vertex shader + let vs_source = info.compute_source(self, ShaderKind::Vertex); + let vs_id = match Device::compile_shader(&*self.gl, &info.base_filename, gl::VERTEX_SHADER, &vs_source, self.requires_null_terminated_shader_source) { + Ok(vs_id) => vs_id, + Err(err) => return Err(err), + }; + + // Compile the fragment shader + let fs_source = info.compute_source(self, ShaderKind::Fragment); + let fs_id = + match Device::compile_shader(&*self.gl, &info.base_filename, gl::FRAGMENT_SHADER, &fs_source, self.requires_null_terminated_shader_source) { + Ok(fs_id) => fs_id, + Err(err) => { + self.gl.delete_shader(vs_id); + return Err(err); + } + }; + + // Check if shader source should be dumped + if Some(info.base_filename) == self.dump_shader_source.as_ref().map(String::as_ref) { + let path = std::path::Path::new(info.base_filename); + std::fs::write(path.with_extension("vert"), vs_source).unwrap(); + std::fs::write(path.with_extension("frag"), fs_source).unwrap(); + } + + // Attach shaders + self.gl.attach_shader(program.id, vs_id); + self.gl.attach_shader(program.id, fs_id); + + // Bind vertex attributes + for (i, attr) in descriptor + .vertex_attributes + .iter() + .chain(descriptor.instance_attributes.iter()) + .enumerate() + { + self.gl + .bind_attrib_location(program.id, i as gl::GLuint, attr.name); + } + + if self.cached_programs.is_some() { + self.gl.program_parameter_i(program.id, gl::PROGRAM_BINARY_RETRIEVABLE_HINT, gl::TRUE as gl::GLint); + } + + // Link! + self.gl.link_program(program.id); + + if cfg!(debug_assertions) { + // Check that all our overrides worked + for (i, attr) in descriptor + .vertex_attributes + .iter() + .chain(descriptor.instance_attributes.iter()) + .enumerate() + { + //Note: we can't assert here because the driver may optimize out some of the + // vertex attributes legitimately, returning their location to be -1. + let location = self.gl.get_attrib_location(program.id, attr.name); + if location != i as gl::GLint { + warn!("Attribute {:?} is not found in the shader {}. Expected at {}, found at {}", + attr, program.source_info.base_filename, i, location); + } + } + } + + // GL recommends detaching and deleting shaders once the link + // is complete (whether successful or not). This allows the driver + // to free any memory associated with the parsing and compilation. + self.gl.detach_shader(program.id, vs_id); + self.gl.detach_shader(program.id, fs_id); + self.gl.delete_shader(vs_id); + self.gl.delete_shader(fs_id); + + let mut link_status = [0]; + unsafe { + self.gl.get_program_iv(program.id, gl::LINK_STATUS, &mut link_status); + } + if link_status[0] == 0 { + let error_log = self.gl.get_program_info_log(program.id); + error!( + "Failed to link shader program: {}\n{}", + &info.base_filename, + error_log + ); + self.gl.delete_program(program.id); + return Err(ShaderError::Link(info.base_filename.to_owned(), error_log)); + } + + if let Some(ref cached_programs) = self.cached_programs { + if !cached_programs.entries.borrow().contains_key(&info.digest) { + let (buffer, format) = self.gl.get_program_binary(program.id); + if buffer.len() > 0 { + let binary = Arc::new(ProgramBinary::new(buffer, format, info.digest.clone())); + cached_programs.add_new_program_binary(binary); + } + } + } + } + + // If we get here, the link succeeded, so get the uniforms. + program.is_initialized = true; + program.u_transform = self.gl.get_uniform_location(program.id, "uTransform"); + program.u_mode = self.gl.get_uniform_location(program.id, "uMode"); + + Ok(()) + } + + pub fn bind_program(&mut self, program: &Program) { + debug_assert!(self.inside_frame); + debug_assert!(program.is_initialized()); + #[cfg(debug_assertions)] + { + self.shader_is_ready = true; + } + + if self.bound_program != program.id { + self.gl.use_program(program.id); + self.bound_program = program.id; + self.program_mode_id = UniformLocation(program.u_mode); + } + } + + pub fn create_texture( + &mut self, + target: TextureTarget, + format: ImageFormat, + mut width: i32, + mut height: i32, + filter: TextureFilter, + render_target: Option, + layer_count: i32, + ) -> Texture { + debug_assert!(self.inside_frame); + + if width > self.max_texture_size || height > self.max_texture_size { + error!("Attempting to allocate a texture of size {}x{} above the limit, trimming", width, height); + width = width.min(self.max_texture_size); + height = height.min(self.max_texture_size); + } + + // Set up the texture book-keeping. + let mut texture = Texture { + id: self.gl.gen_textures(1)[0], + target: get_gl_target(target), + size: DeviceIntSize::new(width, height), + layer_count, + format, + filter, + active_swizzle: Cell::default(), + fbos: vec![], + fbos_with_depth: vec![], + blit_workaround_buffer: None, + last_frame_used: self.frame_id, + flags: TextureFlags::default(), + }; + self.bind_texture(DEFAULT_TEXTURE, &texture, Swizzle::default()); + self.set_texture_parameters(texture.target, filter); + + if self.capabilities.supports_texture_usage && render_target.is_some() { + self.gl.tex_parameter_i(texture.target, gl::TEXTURE_USAGE_ANGLE, gl::FRAMEBUFFER_ATTACHMENT_ANGLE as gl::GLint); + } + + // Allocate storage. + let desc = self.gl_describe_format(texture.format); + let is_array = match texture.target { + gl::TEXTURE_2D_ARRAY => true, + gl::TEXTURE_2D | gl::TEXTURE_RECTANGLE | gl::TEXTURE_EXTERNAL_OES => false, + _ => panic!("BUG: Unexpected texture target!"), + }; + assert!(is_array || texture.layer_count == 1); + + // Firefox doesn't use mipmaps, but Servo uses them for standalone image + // textures images larger than 512 pixels. This is the only case where + // we set the filter to trilinear. + let mipmap_levels = if texture.filter == TextureFilter::Trilinear { + let max_dimension = cmp::max(width, height); + ((max_dimension) as f64).log2() as gl::GLint + 1 + } else { + 1 + }; + + // Use glTexStorage where available, since it avoids allocating + // unnecessary mipmap storage and generally improves performance with + // stronger invariants. + let use_texture_storage = match self.texture_storage_usage { + TexStorageUsage::Always => true, + TexStorageUsage::NonBGRA8 => texture.format != ImageFormat::BGRA8, + TexStorageUsage::Never => false, + }; + match (use_texture_storage, is_array) { + (true, true) => + self.gl.tex_storage_3d( + gl::TEXTURE_2D_ARRAY, + mipmap_levels, + desc.internal, + texture.size.width as gl::GLint, + texture.size.height as gl::GLint, + texture.layer_count, + ), + (true, false) => + self.gl.tex_storage_2d( + texture.target, + mipmap_levels, + desc.internal, + texture.size.width as gl::GLint, + texture.size.height as gl::GLint, + ), + (false, true) => + self.gl.tex_image_3d( + gl::TEXTURE_2D_ARRAY, + 0, + desc.internal as gl::GLint, + texture.size.width as gl::GLint, + texture.size.height as gl::GLint, + texture.layer_count, + 0, + desc.external, + desc.pixel_type, + None, + ), + (false, false) => + self.gl.tex_image_2d( + texture.target, + 0, + desc.internal as gl::GLint, + texture.size.width as gl::GLint, + texture.size.height as gl::GLint, + 0, + desc.external, + desc.pixel_type, + None, + ), + } + + // Set up FBOs, if required. + if let Some(rt_info) = render_target { + self.init_fbos(&mut texture, false); + if rt_info.has_depth { + self.init_fbos(&mut texture, true); + } + } + + // Set up intermediate buffer for blitting to texture, if required. + if texture.layer_count > 1 && !self.capabilities.supports_blit_to_texture_array { + let rbo = RBOId(self.gl.gen_renderbuffers(1)[0]); + let fbo = FBOId(self.gl.gen_framebuffers(1)[0]); + self.gl.bind_renderbuffer(gl::RENDERBUFFER, rbo.0); + self.gl.renderbuffer_storage( + gl::RENDERBUFFER, + self.matching_renderbuffer_format(texture.format), + texture.size.width as _, + texture.size.height as _ + ); + + self.bind_draw_target_impl(fbo); + self.gl.framebuffer_renderbuffer( + gl::DRAW_FRAMEBUFFER, + gl::COLOR_ATTACHMENT0, + gl::RENDERBUFFER, + rbo.0 + ); + texture.blit_workaround_buffer = Some((rbo, fbo)); + } + + texture + } + + fn set_texture_parameters(&mut self, target: gl::GLuint, filter: TextureFilter) { + let mag_filter = match filter { + TextureFilter::Nearest => gl::NEAREST, + TextureFilter::Linear | TextureFilter::Trilinear => gl::LINEAR, + }; + + let min_filter = match filter { + TextureFilter::Nearest => gl::NEAREST, + TextureFilter::Linear => gl::LINEAR, + TextureFilter::Trilinear => gl::LINEAR_MIPMAP_LINEAR, + }; + + self.gl + .tex_parameter_i(target, gl::TEXTURE_MAG_FILTER, mag_filter as gl::GLint); + self.gl + .tex_parameter_i(target, gl::TEXTURE_MIN_FILTER, min_filter as gl::GLint); + + self.gl + .tex_parameter_i(target, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as gl::GLint); + self.gl + .tex_parameter_i(target, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as gl::GLint); + } + + /// Copies the contents from one renderable texture to another. + pub fn blit_renderable_texture( + &mut self, + dst: &mut Texture, + src: &Texture, + ) { + debug_assert!(self.inside_frame); + debug_assert!(dst.size.width >= src.size.width); + debug_assert!(dst.size.height >= src.size.height); + debug_assert!(dst.layer_count >= src.layer_count); + + if self.capabilities.supports_copy_image_sub_data { + assert_ne!(src.id, dst.id, + "glCopyImageSubData's behaviour is undefined if src and dst images are identical and the rectangles overlap."); + unsafe { + self.gl.copy_image_sub_data(src.id, src.target, 0, + 0, 0, 0, + dst.id, dst.target, 0, + 0, 0, 0, + src.size.width as _, src.size.height as _, src.layer_count); + } + } else { + let rect = FramebufferIntRect::new( + FramebufferIntPoint::zero(), + device_size_as_framebuffer_size(src.get_dimensions()), + ); + for layer in 0..src.layer_count.min(dst.layer_count) as LayerIndex { + self.blit_render_target( + ReadTarget::from_texture(src, layer), + rect, + DrawTarget::from_texture(dst, layer, false), + rect, + TextureFilter::Linear + ); + } + self.reset_draw_target(); + self.reset_read_target(); + } + } + + /// Notifies the device that the contents of a render target are no longer + /// needed. + /// + /// FIXME(bholley): We could/should invalidate the depth targets earlier + /// than the color targets, i.e. immediately after each pass. + pub fn invalidate_render_target(&mut self, texture: &Texture) { + let (fbos, attachments) = if texture.supports_depth() { + (&texture.fbos_with_depth, + &[gl::COLOR_ATTACHMENT0, gl::DEPTH_ATTACHMENT] as &[gl::GLenum]) + } else { + (&texture.fbos, &[gl::COLOR_ATTACHMENT0] as &[gl::GLenum]) + }; + + let original_bound_fbo = self.bound_draw_fbo; + for fbo_id in fbos.iter() { + // Note: The invalidate extension may not be supported, in which + // case this is a no-op. That's ok though, because it's just a + // hint. + self.bind_external_draw_target(*fbo_id); + self.gl.invalidate_framebuffer(gl::FRAMEBUFFER, attachments); + } + self.bind_external_draw_target(original_bound_fbo); + } + + /// Notifies the device that a render target is about to be reused. + /// + /// This method adds or removes a depth target as necessary. + pub fn reuse_render_target( + &mut self, + texture: &mut Texture, + rt_info: RenderTargetInfo, + ) { + texture.last_frame_used = self.frame_id; + + // Add depth support if needed. + if rt_info.has_depth && !texture.supports_depth() { + self.init_fbos(texture, true); + } + } + + fn init_fbos(&mut self, texture: &mut Texture, with_depth: bool) { + let (fbos, depth_rb) = if with_depth { + let depth_target = self.acquire_depth_target(texture.get_dimensions()); + (&mut texture.fbos_with_depth, Some(depth_target)) + } else { + (&mut texture.fbos, None) + }; + + // Generate the FBOs. + assert!(fbos.is_empty()); + fbos.extend(self.gl.gen_framebuffers(texture.layer_count).into_iter().map(FBOId)); + + // Bind the FBOs. + let original_bound_fbo = self.bound_draw_fbo; + for (fbo_index, &fbo_id) in fbos.iter().enumerate() { + self.bind_external_draw_target(fbo_id); + match texture.target { + gl::TEXTURE_2D_ARRAY => { + self.gl.framebuffer_texture_layer( + gl::DRAW_FRAMEBUFFER, + gl::COLOR_ATTACHMENT0, + texture.id, + 0, + fbo_index as _, + ) + } + _ => { + assert_eq!(fbo_index, 0); + self.gl.framebuffer_texture_2d( + gl::DRAW_FRAMEBUFFER, + gl::COLOR_ATTACHMENT0, + texture.target, + texture.id, + 0, + ) + } + } + + if let Some(depth_rb) = depth_rb { + self.gl.framebuffer_renderbuffer( + gl::DRAW_FRAMEBUFFER, + gl::DEPTH_ATTACHMENT, + gl::RENDERBUFFER, + depth_rb.0, + ); + } + + debug_assert_eq!( + self.gl.check_frame_buffer_status(gl::DRAW_FRAMEBUFFER), + gl::FRAMEBUFFER_COMPLETE, + "Incomplete framebuffer", + ); + } + self.bind_external_draw_target(original_bound_fbo); + } + + fn deinit_fbos(&mut self, fbos: &mut Vec) { + if !fbos.is_empty() { + let fbo_ids: SmallVec<[gl::GLuint; 8]> = fbos + .drain(..) + .map(|FBOId(fbo_id)| fbo_id) + .collect(); + self.gl.delete_framebuffers(&fbo_ids[..]); + } + } + + fn acquire_depth_target(&mut self, dimensions: DeviceIntSize) -> RBOId { + let gl = &self.gl; + let depth_format = self.depth_format; + let target = self.depth_targets.entry(dimensions).or_insert_with(|| { + let renderbuffer_ids = gl.gen_renderbuffers(1); + let depth_rb = renderbuffer_ids[0]; + gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb); + gl.renderbuffer_storage( + gl::RENDERBUFFER, + depth_format, + dimensions.width as _, + dimensions.height as _, + ); + SharedDepthTarget { + rbo_id: RBOId(depth_rb), + refcount: 0, + } + }); + target.refcount += 1; + target.rbo_id + } + + fn release_depth_target(&mut self, dimensions: DeviceIntSize) { + let mut entry = match self.depth_targets.entry(dimensions) { + Entry::Occupied(x) => x, + Entry::Vacant(..) => panic!("Releasing unknown depth target"), + }; + debug_assert!(entry.get().refcount != 0); + entry.get_mut().refcount -= 1; + if entry.get().refcount == 0 { + let (_, target) = entry.remove_entry(); + self.gl.delete_renderbuffers(&[target.rbo_id.0]); + } + } + + /// Perform a blit between self.bound_read_fbo and self.bound_draw_fbo. + fn blit_render_target_impl( + &mut self, + src_rect: FramebufferIntRect, + dest_rect: FramebufferIntRect, + filter: TextureFilter, + ) { + debug_assert!(self.inside_frame); + + let filter = match filter { + TextureFilter::Nearest => gl::NEAREST, + TextureFilter::Linear | TextureFilter::Trilinear => gl::LINEAR, + }; + + self.gl.blit_framebuffer( + src_rect.origin.x, + src_rect.origin.y, + src_rect.origin.x + src_rect.size.width, + src_rect.origin.y + src_rect.size.height, + dest_rect.origin.x, + dest_rect.origin.y, + dest_rect.origin.x + dest_rect.size.width, + dest_rect.origin.y + dest_rect.size.height, + gl::COLOR_BUFFER_BIT, + filter, + ); + } + + /// Perform a blit between src_target and dest_target. + /// This will overwrite self.bound_read_fbo and self.bound_draw_fbo. + pub fn blit_render_target( + &mut self, + src_target: ReadTarget, + src_rect: FramebufferIntRect, + dest_target: DrawTarget, + dest_rect: FramebufferIntRect, + filter: TextureFilter, + ) { + debug_assert!(self.inside_frame); + + self.bind_read_target(src_target); + + match dest_target { + DrawTarget::Texture { layer, blit_workaround_buffer, dimensions, id, target, .. } if layer != 0 && + !self.capabilities.supports_blit_to_texture_array => + { + // This should have been initialized in create_texture(). + let (_rbo, fbo) = blit_workaround_buffer.expect("Blit workaround buffer has not been initialized."); + + // Blit from read target to intermediate buffer. + self.bind_draw_target_impl(fbo); + self.blit_render_target_impl( + src_rect, + dest_rect, + filter + ); + + // dest_rect may be inverted, so min_x/y() might actually be the + // bottom-right, max_x/y() might actually be the top-left, + // and width/height might be negative. See servo/euclid#321. + // Calculate the non-inverted rect here. + let dest_bounds = DeviceIntRect::new( + DeviceIntPoint::new( + dest_rect.min_x().min(dest_rect.max_x()), + dest_rect.min_y().min(dest_rect.max_y()), + ), + DeviceIntSize::new( + dest_rect.size.width.abs(), + dest_rect.size.height.abs(), + ), + ).intersection(&dimensions.into()).unwrap_or_else(DeviceIntRect::zero); + + self.bind_read_target_impl(fbo); + self.bind_texture_impl( + DEFAULT_TEXTURE, + id, + target, + None, // not depending on swizzle + ); + + // Copy from intermediate buffer to the texture layer. + self.gl.copy_tex_sub_image_3d( + target, 0, + dest_bounds.origin.x, dest_bounds.origin.y, + layer as _, + dest_bounds.origin.x, dest_bounds.origin.y, + dest_bounds.size.width, dest_bounds.size.height, + ); + + } + _ => { + self.bind_draw_target(dest_target); + + self.blit_render_target_impl(src_rect, dest_rect, filter); + } + } + } + + /// Performs a blit while flipping vertically. Useful for blitting textures + /// (which use origin-bottom-left) to the main framebuffer (which uses + /// origin-top-left). + pub fn blit_render_target_invert_y( + &mut self, + src_target: ReadTarget, + src_rect: FramebufferIntRect, + dest_target: DrawTarget, + dest_rect: FramebufferIntRect, + ) { + debug_assert!(self.inside_frame); + + let mut inverted_dest_rect = dest_rect; + inverted_dest_rect.origin.y = dest_rect.max_y(); + inverted_dest_rect.size.height *= -1; + + self.blit_render_target( + src_target, + src_rect, + dest_target, + inverted_dest_rect, + TextureFilter::Linear, + ); + } + + pub fn delete_texture(&mut self, mut texture: Texture) { + debug_assert!(self.inside_frame); + let had_depth = texture.supports_depth(); + self.deinit_fbos(&mut texture.fbos); + self.deinit_fbos(&mut texture.fbos_with_depth); + if had_depth { + self.release_depth_target(texture.get_dimensions()); + } + if let Some((rbo, fbo)) = texture.blit_workaround_buffer { + self.gl.delete_framebuffers(&[fbo.0]); + self.gl.delete_renderbuffers(&[rbo.0]); + } + + self.gl.delete_textures(&[texture.id]); + + for bound_texture in &mut self.bound_textures { + if *bound_texture == texture.id { + *bound_texture = 0; + } + } + + // Disarm the assert in Texture::drop(). + texture.id = 0; + } + + #[cfg(feature = "replay")] + pub fn delete_external_texture(&mut self, mut external: ExternalTexture) { + self.gl.delete_textures(&[external.id]); + external.id = 0; + } + + pub fn delete_program(&mut self, mut program: Program) { + self.gl.delete_program(program.id); + program.id = 0; + } + + /// Create a shader program and link it immediately. + pub fn create_program_linked( + &mut self, + base_filename: &'static str, + features: &[&'static str], + descriptor: &VertexDescriptor, + ) -> Result { + let mut program = self.create_program(base_filename, features)?; + self.link_program(&mut program, descriptor)?; + Ok(program) + } + + /// Create a shader program. This does minimal amount of work to start + /// loading a binary shader. If a binary shader is found, we invoke + /// glProgramBinary, which, at least on ANGLE, will load and link the + /// binary on a background thread. This can speed things up later when + /// we invoke `link_program()`. + pub fn create_program( + &mut self, + base_filename: &'static str, + features: &[&'static str], + ) -> Result { + debug_assert!(self.inside_frame); + + let source_info = ProgramSourceInfo::new(self, base_filename, features); + + // Create program + let pid = self.gl.create_program(); + + // Attempt to load a cached binary if possible. + if let Some(ref cached_programs) = self.cached_programs { + if let Some(entry) = cached_programs.entries.borrow().get(&source_info.digest) { + self.gl.program_binary(pid, entry.binary.format, &entry.binary.bytes); + } + } + + // Use 0 for the uniforms as they are initialized by link_program. + let program = Program { + id: pid, + u_transform: 0, + u_mode: 0, + source_info, + is_initialized: false, + }; + + Ok(program) + } + + fn build_shader_string( + &self, + features: &[&'static str], + kind: ShaderKind, + base_filename: &str, + output: F, + ) { + do_build_shader_string( + get_shader_version(&*self.gl), + features, + kind, + base_filename, + &|f| get_unoptimized_shader_source(f, self.resource_override_path.as_ref()), + output, + ) + } + + pub fn bind_shader_samplers(&mut self, program: &Program, bindings: &[(&'static str, S)]) + where + S: Into + Copy, + { + // bind_program() must be called before calling bind_shader_samplers + assert_eq!(self.bound_program, program.id); + + for binding in bindings { + let u_location = self.gl.get_uniform_location(program.id, binding.0); + if u_location != -1 { + self.bind_program(program); + self.gl + .uniform_1i(u_location, binding.1.into().0 as gl::GLint); + } + } + } + + pub fn get_uniform_location(&self, program: &Program, name: &str) -> UniformLocation { + UniformLocation(self.gl.get_uniform_location(program.id, name)) + } + + pub fn set_uniforms( + &self, + program: &Program, + transform: &Transform3D, + ) { + debug_assert!(self.inside_frame); + #[cfg(debug_assertions)] + debug_assert!(self.shader_is_ready); + + self.gl + .uniform_matrix_4fv(program.u_transform, false, &transform.to_array()); + } + + pub fn switch_mode(&self, mode: i32) { + debug_assert!(self.inside_frame); + #[cfg(debug_assertions)] + debug_assert!(self.shader_is_ready); + + self.gl.uniform_1i(self.program_mode_id.0, mode); + } + + pub fn create_pbo(&mut self) -> PBO { + let id = self.gl.gen_buffers(1)[0]; + PBO { + id, + reserved_size: 0, + } + } + + pub fn create_pbo_with_size(&mut self, size: usize) -> PBO { + let mut pbo = self.create_pbo(); + + self.gl.bind_buffer(gl::PIXEL_PACK_BUFFER, pbo.id); + self.gl.pixel_store_i(gl::PACK_ALIGNMENT, 1); + self.gl.buffer_data_untyped( + gl::PIXEL_PACK_BUFFER, + size as _, + ptr::null(), + gl::STREAM_READ, + ); + self.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0); + + pbo.reserved_size = size; + pbo + } + + pub fn read_pixels_into_pbo( + &mut self, + read_target: ReadTarget, + rect: DeviceIntRect, + format: ImageFormat, + pbo: &PBO, + ) { + let byte_size = rect.size.area() as usize * format.bytes_per_pixel() as usize; + + assert!(byte_size <= pbo.reserved_size); + + self.bind_read_target(read_target); + + self.gl.bind_buffer(gl::PIXEL_PACK_BUFFER, pbo.id); + self.gl.pixel_store_i(gl::PACK_ALIGNMENT, 1); + + let gl_format = self.gl_describe_format(format); + + unsafe { + self.gl.read_pixels_into_pbo( + rect.origin.x as _, + rect.origin.y as _, + rect.size.width as _, + rect.size.height as _, + gl_format.read, + gl_format.pixel_type, + ); + } + + self.gl.bind_buffer(gl::PIXEL_PACK_BUFFER, 0); + } + + pub fn map_pbo_for_readback<'a>(&'a mut self, pbo: &'a PBO) -> Option> { + self.gl.bind_buffer(gl::PIXEL_PACK_BUFFER, pbo.id); + + let buf_ptr = match self.gl.get_type() { + gl::GlType::Gl => { + self.gl.map_buffer(gl::PIXEL_PACK_BUFFER, gl::READ_ONLY) + } + + gl::GlType::Gles => { + self.gl.map_buffer_range( + gl::PIXEL_PACK_BUFFER, + 0, + pbo.reserved_size as _, + gl::MAP_READ_BIT) + } + }; + + if buf_ptr.is_null() { + return None; + } + + let buffer = unsafe { slice::from_raw_parts(buf_ptr as *const u8, pbo.reserved_size) }; + + Some(BoundPBO { + device: self, + data: buffer, + }) + } + + pub fn delete_pbo(&mut self, mut pbo: PBO) { + self.gl.delete_buffers(&[pbo.id]); + pbo.id = 0; + pbo.reserved_size = 0 + } + + /// Returns the size and stride in bytes required to upload an area of pixels + /// of the specified size, to a texture of the specified format. + pub fn required_upload_size_and_stride(&self, size: DeviceIntSize, format: ImageFormat) -> (usize, usize) { + assert!(size.width >= 0); + assert!(size.height >= 0); + + let bytes_pp = format.bytes_per_pixel() as usize; + let width_bytes = size.width as usize * bytes_pp; + + let dst_stride = round_up_to_multiple(width_bytes, self.optimal_pbo_stride.num_bytes(format)); + + // The size of the chunk should only need to be (height - 1) * dst_stride + width_bytes, + // however, the android emulator will error unless it is height * dst_stride. + // See bug 1587047 for details. + // Using the full final row also ensures that the offset of the next chunk is + // optimally aligned. + let dst_size = dst_stride * size.height as usize; + + (dst_size, dst_stride) + } + + /// (Re)allocates and maps a PBO, returning a `PixelBuffer` if successful. + /// The contents can be written to using the `mapping` field. + /// The buffer must be bound to `GL_PIXEL_UNPACK_BUFFER` before calling this function, + /// and must be unmapped using `glUnmapBuffer` prior to uploading the contents to a texture. + fn create_upload_buffer<'a>(&mut self, hint: VertexUsageHint, size: usize) -> Result, ()> { + self.gl.buffer_data_untyped( + gl::PIXEL_UNPACK_BUFFER, + size as _, + ptr::null(), + hint.to_gl(), + ); + let ptr = self.gl.map_buffer_range( + gl::PIXEL_UNPACK_BUFFER, + 0, + size as _, + gl::MAP_WRITE_BIT | gl::MAP_INVALIDATE_BUFFER_BIT, + ); + + if ptr != ptr::null_mut() { + let mapping = unsafe { + slice::from_raw_parts_mut(ptr as *mut _, size) + }; + Ok(PixelBuffer::new(size, mapping)) + } else { + error!("Failed to map PBO of size {} bytes", size); + Err(()) + } + } + + /// Returns a `TextureUploader` which can be used to upload texture data to `texture`. + /// The total size in bytes is specified by `upload_size`, and must be greater than zero + /// and at least as large as the sum of the sizes returned from + /// `required_upload_size_and_stride()` for each subsequent call to `TextureUploader.upload()`. + pub fn upload_texture<'a, T>( + &'a mut self, + texture: &'a Texture, + pbo: &PBO, + upload_size: usize, + ) -> TextureUploader<'a, T> { + debug_assert!(self.inside_frame); + assert_ne!(upload_size, 0, "Must specify valid upload size"); + + self.bind_texture(DEFAULT_TEXTURE, texture, Swizzle::default()); + + let uploader_type = match self.upload_method { + UploadMethod::Immediate => TextureUploaderType::Immediate, + UploadMethod::PixelBuffer(hint) => { + self.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, pbo.id); + if self.capabilities.supports_nonzero_pbo_offsets { + match self.create_upload_buffer(hint, upload_size) { + Ok(buffer) => TextureUploaderType::MutliUseBuffer(buffer), + Err(_) => { + // If allocating the buffer failed, fall back to immediate uploads + self.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0); + TextureUploaderType::Immediate + } + } + } else { + // If we cannot upload from non-zero offsets, then we must + // reallocate a new buffer for each upload. + TextureUploaderType::SingleUseBuffers(hint) + } + }, + }; + + TextureUploader { + target: UploadTarget { + device: self, + texture, + }, + uploader_type, + marker: PhantomData, + } + } + + /// Performs an immediate (non-PBO) texture upload. + pub fn upload_texture_immediate( + &mut self, + texture: &Texture, + pixels: &[T] + ) { + self.bind_texture(DEFAULT_TEXTURE, texture, Swizzle::default()); + let desc = self.gl_describe_format(texture.format); + match texture.target { + gl::TEXTURE_2D | gl::TEXTURE_RECTANGLE | gl::TEXTURE_EXTERNAL_OES => + self.gl.tex_sub_image_2d( + texture.target, + 0, + 0, + 0, + texture.size.width as gl::GLint, + texture.size.height as gl::GLint, + desc.external, + desc.pixel_type, + texels_to_u8_slice(pixels), + ), + gl::TEXTURE_2D_ARRAY => + self.gl.tex_sub_image_3d( + texture.target, + 0, + 0, + 0, + 0, + texture.size.width as gl::GLint, + texture.size.height as gl::GLint, + texture.layer_count as gl::GLint, + desc.external, + desc.pixel_type, + texels_to_u8_slice(pixels), + ), + _ => panic!("BUG: Unexpected texture target!"), + } + } + + pub fn read_pixels(&mut self, img_desc: &ImageDescriptor) -> Vec { + let desc = self.gl_describe_format(img_desc.format); + self.gl.read_pixels( + 0, 0, + img_desc.size.width as i32, + img_desc.size.height as i32, + desc.read, + desc.pixel_type, + ) + } + + /// Read rectangle of pixels into the specified output slice. + pub fn read_pixels_into( + &mut self, + rect: FramebufferIntRect, + format: ImageFormat, + output: &mut [u8], + ) { + let bytes_per_pixel = format.bytes_per_pixel(); + let desc = self.gl_describe_format(format); + let size_in_bytes = (bytes_per_pixel * rect.size.width * rect.size.height) as usize; + assert_eq!(output.len(), size_in_bytes); + + self.gl.flush(); + self.gl.read_pixels_into_buffer( + rect.origin.x as _, + rect.origin.y as _, + rect.size.width as _, + rect.size.height as _, + desc.read, + desc.pixel_type, + output, + ); + } + + /// Get texels of a texture into the specified output slice. + pub fn get_tex_image_into( + &mut self, + texture: &Texture, + format: ImageFormat, + output: &mut [u8], + ) { + self.bind_texture(DEFAULT_TEXTURE, texture, Swizzle::default()); + let desc = self.gl_describe_format(format); + self.gl.get_tex_image_into_buffer( + texture.target, + 0, + desc.external, + desc.pixel_type, + output, + ); + } + + /// Attaches the provided texture to the current Read FBO binding. + fn attach_read_texture_raw( + &mut self, texture_id: gl::GLuint, target: gl::GLuint, layer_id: i32 + ) { + match target { + gl::TEXTURE_2D_ARRAY => { + self.gl.framebuffer_texture_layer( + gl::READ_FRAMEBUFFER, + gl::COLOR_ATTACHMENT0, + texture_id, + 0, + layer_id, + ) + } + _ => { + assert_eq!(layer_id, 0); + self.gl.framebuffer_texture_2d( + gl::READ_FRAMEBUFFER, + gl::COLOR_ATTACHMENT0, + target, + texture_id, + 0, + ) + } + } + } + + pub fn attach_read_texture_external( + &mut self, texture_id: gl::GLuint, target: TextureTarget, layer_id: i32 + ) { + self.attach_read_texture_raw(texture_id, get_gl_target(target), layer_id) + } + + pub fn attach_read_texture(&mut self, texture: &Texture, layer_id: i32) { + self.attach_read_texture_raw(texture.id, texture.target, layer_id) + } + + fn bind_vao_impl(&mut self, id: gl::GLuint) { + debug_assert!(self.inside_frame); + + if self.bound_vao != id { + self.bound_vao = id; + self.gl.bind_vertex_array(id); + } + } + + pub fn bind_vao(&mut self, vao: &VAO) { + self.bind_vao_impl(vao.id) + } + + pub fn bind_custom_vao(&mut self, vao: &CustomVAO) { + self.bind_vao_impl(vao.id) + } + + fn create_vao_with_vbos( + &mut self, + descriptor: &VertexDescriptor, + main_vbo_id: VBOId, + instance_vbo_id: VBOId, + ibo_id: IBOId, + owns_vertices_and_indices: bool, + ) -> VAO { + let instance_stride = descriptor.instance_stride() as usize; + let vao_id = self.gl.gen_vertex_arrays(1)[0]; + + self.bind_vao_impl(vao_id); + + descriptor.bind(self.gl(), main_vbo_id, instance_vbo_id); + ibo_id.bind(self.gl()); // force it to be a part of VAO + + VAO { + id: vao_id, + ibo_id, + main_vbo_id, + instance_vbo_id, + instance_stride, + owns_vertices_and_indices, + } + } + + pub fn create_custom_vao( + &mut self, + streams: &[Stream], + ) -> CustomVAO { + debug_assert!(self.inside_frame); + + let vao_id = self.gl.gen_vertex_arrays(1)[0]; + self.bind_vao_impl(vao_id); + + let mut attrib_index = 0; + for stream in streams { + VertexDescriptor::bind_attributes( + stream.attributes, + attrib_index, + 0, + self.gl(), + stream.vbo, + ); + attrib_index += stream.attributes.len(); + } + + CustomVAO { + id: vao_id, + } + } + + pub fn delete_custom_vao(&mut self, mut vao: CustomVAO) { + self.gl.delete_vertex_arrays(&[vao.id]); + vao.id = 0; + } + + pub fn create_vbo(&mut self) -> VBO { + let ids = self.gl.gen_buffers(1); + VBO { + id: ids[0], + target: gl::ARRAY_BUFFER, + allocated_count: 0, + marker: PhantomData, + } + } + + pub fn delete_vbo(&mut self, mut vbo: VBO) { + self.gl.delete_buffers(&[vbo.id]); + vbo.id = 0; + } + + pub fn create_vao(&mut self, descriptor: &VertexDescriptor) -> VAO { + debug_assert!(self.inside_frame); + + let buffer_ids = self.gl.gen_buffers(3); + let ibo_id = IBOId(buffer_ids[0]); + let main_vbo_id = VBOId(buffer_ids[1]); + let intance_vbo_id = VBOId(buffer_ids[2]); + + self.create_vao_with_vbos(descriptor, main_vbo_id, intance_vbo_id, ibo_id, true) + } + + pub fn delete_vao(&mut self, mut vao: VAO) { + self.gl.delete_vertex_arrays(&[vao.id]); + vao.id = 0; + + if vao.owns_vertices_and_indices { + self.gl.delete_buffers(&[vao.ibo_id.0]); + self.gl.delete_buffers(&[vao.main_vbo_id.0]); + } + + self.gl.delete_buffers(&[vao.instance_vbo_id.0]) + } + + pub fn allocate_vbo( + &mut self, + vbo: &mut VBO, + count: usize, + usage_hint: VertexUsageHint, + ) { + debug_assert!(self.inside_frame); + vbo.allocated_count = count; + + self.gl.bind_buffer(vbo.target, vbo.id); + self.gl.buffer_data_untyped( + vbo.target, + (count * mem::size_of::()) as _, + ptr::null(), + usage_hint.to_gl(), + ); + } + + pub fn fill_vbo( + &mut self, + vbo: &VBO, + data: &[V], + offset: usize, + ) { + debug_assert!(self.inside_frame); + assert!(offset + data.len() <= vbo.allocated_count); + let stride = mem::size_of::(); + + self.gl.bind_buffer(vbo.target, vbo.id); + self.gl.buffer_sub_data_untyped( + vbo.target, + (offset * stride) as _, + (data.len() * stride) as _, + data.as_ptr() as _, + ); + } + + fn update_vbo_data( + &mut self, + vbo: VBOId, + vertices: &[V], + usage_hint: VertexUsageHint, + ) { + debug_assert!(self.inside_frame); + + vbo.bind(self.gl()); + gl::buffer_data(self.gl(), gl::ARRAY_BUFFER, vertices, usage_hint.to_gl()); + } + + pub fn create_vao_with_new_instances( + &mut self, + descriptor: &VertexDescriptor, + base_vao: &VAO, + ) -> VAO { + debug_assert!(self.inside_frame); + + let buffer_ids = self.gl.gen_buffers(1); + let intance_vbo_id = VBOId(buffer_ids[0]); + + self.create_vao_with_vbos( + descriptor, + base_vao.main_vbo_id, + intance_vbo_id, + base_vao.ibo_id, + false, + ) + } + + pub fn update_vao_main_vertices( + &mut self, + vao: &VAO, + vertices: &[V], + usage_hint: VertexUsageHint, + ) { + debug_assert_eq!(self.bound_vao, vao.id); + self.update_vbo_data(vao.main_vbo_id, vertices, usage_hint) + } + + pub fn update_vao_instances( + &mut self, + vao: &VAO, + instances: &[V], + usage_hint: VertexUsageHint, + ) { + debug_assert_eq!(self.bound_vao, vao.id); + debug_assert_eq!(vao.instance_stride as usize, mem::size_of::()); + + self.update_vbo_data(vao.instance_vbo_id, instances, usage_hint) + } + + pub fn update_vao_indices(&mut self, vao: &VAO, indices: &[I], usage_hint: VertexUsageHint) { + debug_assert!(self.inside_frame); + debug_assert_eq!(self.bound_vao, vao.id); + + vao.ibo_id.bind(self.gl()); + gl::buffer_data( + self.gl(), + gl::ELEMENT_ARRAY_BUFFER, + indices, + usage_hint.to_gl(), + ); + } + + pub fn draw_triangles_u16(&mut self, first_vertex: i32, index_count: i32) { + debug_assert!(self.inside_frame); + #[cfg(debug_assertions)] + debug_assert!(self.shader_is_ready); + + self.gl.draw_elements( + gl::TRIANGLES, + index_count, + gl::UNSIGNED_SHORT, + first_vertex as u32 * 2, + ); + } + + pub fn draw_triangles_u32(&mut self, first_vertex: i32, index_count: i32) { + debug_assert!(self.inside_frame); + #[cfg(debug_assertions)] + debug_assert!(self.shader_is_ready); + + self.gl.draw_elements( + gl::TRIANGLES, + index_count, + gl::UNSIGNED_INT, + first_vertex as u32 * 4, + ); + } + + pub fn draw_nonindexed_points(&mut self, first_vertex: i32, vertex_count: i32) { + debug_assert!(self.inside_frame); + #[cfg(debug_assertions)] + debug_assert!(self.shader_is_ready); + + self.gl.draw_arrays(gl::POINTS, first_vertex, vertex_count); + } + + pub fn draw_nonindexed_lines(&mut self, first_vertex: i32, vertex_count: i32) { + debug_assert!(self.inside_frame); + #[cfg(debug_assertions)] + debug_assert!(self.shader_is_ready); + + self.gl.draw_arrays(gl::LINES, first_vertex, vertex_count); + } + + pub fn draw_indexed_triangles_instanced_u16(&mut self, index_count: i32, instance_count: i32) { + debug_assert!(self.inside_frame); + #[cfg(debug_assertions)] + debug_assert!(self.shader_is_ready); + + self.gl.draw_elements_instanced( + gl::TRIANGLES, + index_count, + gl::UNSIGNED_SHORT, + 0, + instance_count, + ); + } + + pub fn end_frame(&mut self) { + self.reset_draw_target(); + self.reset_read_target(); + + debug_assert!(self.inside_frame); + self.inside_frame = false; + + self.gl.bind_texture(gl::TEXTURE_2D, 0); + self.gl.use_program(0); + + for i in 0 .. self.bound_textures.len() { + self.gl.active_texture(gl::TEXTURE0 + i as gl::GLuint); + self.gl.bind_texture(gl::TEXTURE_2D, 0); + } + + self.gl.active_texture(gl::TEXTURE0); + + self.frame_id.0 += 1; + + // Save any shaders compiled this frame to disk. + // If this is the tenth frame then treat startup as complete, meaning the + // current set of in-use shaders are the ones to load on the next startup. + if let Some(ref cache) = self.cached_programs { + cache.update_disk_cache(self.frame_id.0 == 10); + } + } + + pub fn clear_target( + &self, + color: Option<[f32; 4]>, + depth: Option, + rect: Option, + ) { + let mut clear_bits = 0; + + if let Some(color) = color { + self.gl.clear_color(color[0], color[1], color[2], color[3]); + clear_bits |= gl::COLOR_BUFFER_BIT; + } + + if let Some(depth) = depth { + if cfg!(debug_assertions) { + let mut mask = [0]; + unsafe { + self.gl.get_boolean_v(gl::DEPTH_WRITEMASK, &mut mask); + } + assert_ne!(mask[0], 0); + } + self.gl.clear_depth(depth as f64); + clear_bits |= gl::DEPTH_BUFFER_BIT; + } + + if clear_bits != 0 { + match rect { + Some(rect) => { + self.gl.enable(gl::SCISSOR_TEST); + self.gl.scissor( + rect.origin.x, + rect.origin.y, + rect.size.width, + rect.size.height, + ); + self.gl.clear(clear_bits); + self.gl.disable(gl::SCISSOR_TEST); + } + None => { + self.gl.clear(clear_bits); + } + } + } + } + + pub fn enable_depth(&self, depth_func: DepthFunction) { + assert!(self.depth_available, "Enabling depth test without depth target"); + self.gl.enable(gl::DEPTH_TEST); + self.gl.depth_func(depth_func as gl::GLuint); + } + + pub fn disable_depth(&self) { + self.gl.disable(gl::DEPTH_TEST); + } + + pub fn enable_depth_write(&self) { + assert!(self.depth_available, "Enabling depth write without depth target"); + self.gl.depth_mask(true); + } + + pub fn disable_depth_write(&self) { + self.gl.depth_mask(false); + } + + pub fn disable_stencil(&self) { + self.gl.disable(gl::STENCIL_TEST); + } + + pub fn set_scissor_rect(&self, rect: FramebufferIntRect) { + self.gl.scissor( + rect.origin.x, + rect.origin.y, + rect.size.width, + rect.size.height, + ); + } + + pub fn enable_scissor(&self) { + self.gl.enable(gl::SCISSOR_TEST); + } + + pub fn disable_scissor(&self) { + self.gl.disable(gl::SCISSOR_TEST); + } + + pub fn enable_color_write(&self) { + self.gl.color_mask(true, true, true, true); + } + + pub fn disable_color_write(&self) { + self.gl.color_mask(false, false, false, false); + } + + pub fn set_blend(&mut self, enable: bool) { + if enable { + self.gl.enable(gl::BLEND); + } else { + self.gl.disable(gl::BLEND); + } + #[cfg(debug_assertions)] + { + self.shader_is_ready = false; + } + } + + fn set_blend_factors( + &mut self, + color: (gl::GLenum, gl::GLenum), + alpha: (gl::GLenum, gl::GLenum), + ) { + self.gl.blend_equation(gl::FUNC_ADD); + if color == alpha { + self.gl.blend_func(color.0, color.1); + } else { + self.gl.blend_func_separate(color.0, color.1, alpha.0, alpha.1); + } + #[cfg(debug_assertions)] + { + self.shader_is_ready = false; + } + } + + pub fn set_blend_mode_alpha(&mut self) { + self.set_blend_factors( + (gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA), + (gl::ONE, gl::ONE), + ); + } + + pub fn set_blend_mode_premultiplied_alpha(&mut self) { + self.set_blend_factors( + (gl::ONE, gl::ONE_MINUS_SRC_ALPHA), + (gl::ONE, gl::ONE_MINUS_SRC_ALPHA), + ); + } + + pub fn set_blend_mode_premultiplied_dest_out(&mut self) { + self.set_blend_factors( + (gl::ZERO, gl::ONE_MINUS_SRC_ALPHA), + (gl::ZERO, gl::ONE_MINUS_SRC_ALPHA), + ); + } + + pub fn set_blend_mode_multiply(&mut self) { + self.set_blend_factors( + (gl::ZERO, gl::SRC_COLOR), + (gl::ZERO, gl::SRC_ALPHA), + ); + } + pub fn set_blend_mode_subpixel_pass0(&mut self) { + self.set_blend_factors( + (gl::ZERO, gl::ONE_MINUS_SRC_COLOR), + (gl::ZERO, gl::ONE_MINUS_SRC_ALPHA), + ); + } + pub fn set_blend_mode_subpixel_pass1(&mut self) { + self.set_blend_factors( + (gl::ONE, gl::ONE), + (gl::ONE, gl::ONE), + ); + } + pub fn set_blend_mode_subpixel_with_bg_color_pass0(&mut self) { + self.set_blend_factors( + (gl::ZERO, gl::ONE_MINUS_SRC_COLOR), + (gl::ZERO, gl::ONE), + ); + } + pub fn set_blend_mode_subpixel_with_bg_color_pass1(&mut self) { + self.set_blend_factors( + (gl::ONE_MINUS_DST_ALPHA, gl::ONE), + (gl::ZERO, gl::ONE), + ); + } + pub fn set_blend_mode_subpixel_with_bg_color_pass2(&mut self) { + self.set_blend_factors( + (gl::ONE, gl::ONE), + (gl::ONE, gl::ONE_MINUS_SRC_ALPHA), + ); + } + pub fn set_blend_mode_subpixel_constant_text_color(&mut self, color: ColorF) { + // color is an unpremultiplied color. + self.gl.blend_color(color.r, color.g, color.b, 1.0); + self.set_blend_factors( + (gl::CONSTANT_COLOR, gl::ONE_MINUS_SRC_COLOR), + (gl::CONSTANT_ALPHA, gl::ONE_MINUS_SRC_ALPHA), + ); + } + pub fn set_blend_mode_subpixel_dual_source(&mut self) { + self.set_blend_factors( + (gl::ONE, gl::ONE_MINUS_SRC1_COLOR), + (gl::ONE, gl::ONE_MINUS_SRC1_ALPHA), + ); + } + pub fn set_blend_mode_show_overdraw(&mut self) { + self.set_blend_factors( + (gl::ONE, gl::ONE_MINUS_SRC_ALPHA), + (gl::ONE, gl::ONE_MINUS_SRC_ALPHA), + ); + } + + pub fn set_blend_mode_max(&mut self) { + self.gl + .blend_func_separate(gl::ONE, gl::ONE, gl::ONE, gl::ONE); + self.gl.blend_equation_separate(gl::MAX, gl::FUNC_ADD); + #[cfg(debug_assertions)] + { + self.shader_is_ready = false; + } + } + pub fn set_blend_mode_min(&mut self) { + self.gl + .blend_func_separate(gl::ONE, gl::ONE, gl::ONE, gl::ONE); + self.gl.blend_equation_separate(gl::MIN, gl::FUNC_ADD); + #[cfg(debug_assertions)] + { + self.shader_is_ready = false; + } + } + pub fn set_blend_mode_advanced(&mut self, mode: MixBlendMode) { + self.gl.blend_equation(match mode { + MixBlendMode::Normal => { + // blend factor only make sense for the normal mode + self.gl.blend_func_separate(gl::ZERO, gl::SRC_COLOR, gl::ZERO, gl::SRC_ALPHA); + gl::FUNC_ADD + }, + MixBlendMode::Multiply => gl::MULTIPLY_KHR, + MixBlendMode::Screen => gl::SCREEN_KHR, + MixBlendMode::Overlay => gl::OVERLAY_KHR, + MixBlendMode::Darken => gl::DARKEN_KHR, + MixBlendMode::Lighten => gl::LIGHTEN_KHR, + MixBlendMode::ColorDodge => gl::COLORDODGE_KHR, + MixBlendMode::ColorBurn => gl::COLORBURN_KHR, + MixBlendMode::HardLight => gl::HARDLIGHT_KHR, + MixBlendMode::SoftLight => gl::SOFTLIGHT_KHR, + MixBlendMode::Difference => gl::DIFFERENCE_KHR, + MixBlendMode::Exclusion => gl::EXCLUSION_KHR, + MixBlendMode::Hue => gl::HSL_HUE_KHR, + MixBlendMode::Saturation => gl::HSL_SATURATION_KHR, + MixBlendMode::Color => gl::HSL_COLOR_KHR, + MixBlendMode::Luminosity => gl::HSL_LUMINOSITY_KHR, + }); + #[cfg(debug_assertions)] + { + self.shader_is_ready = false; + } + } + + pub fn supports_extension(&self, extension: &str) -> bool { + supports_extension(&self.extensions, extension) + } + + /// Enable the pixel local storage functionality. Caller must + /// have already confirmed the device supports this. + pub fn enable_pixel_local_storage(&mut self, enable: bool) { + debug_assert!(self.capabilities.supports_pixel_local_storage); + + if enable { + self.gl.enable(gl::SHADER_PIXEL_LOCAL_STORAGE_EXT); + } else { + self.gl.disable(gl::SHADER_PIXEL_LOCAL_STORAGE_EXT); + } + } + + pub fn echo_driver_messages(&self) { + if self.capabilities.supports_khr_debug { + Device::log_driver_messages(self.gl()); + } + } + + fn log_driver_messages(gl: &dyn gl::Gl) { + for msg in gl.get_debug_messages() { + let level = match msg.severity { + gl::DEBUG_SEVERITY_HIGH => Level::Error, + gl::DEBUG_SEVERITY_MEDIUM => Level::Warn, + gl::DEBUG_SEVERITY_LOW => Level::Info, + gl::DEBUG_SEVERITY_NOTIFICATION => Level::Debug, + _ => Level::Trace, + }; + let ty = match msg.ty { + gl::DEBUG_TYPE_ERROR => "error", + gl::DEBUG_TYPE_DEPRECATED_BEHAVIOR => "deprecated", + gl::DEBUG_TYPE_UNDEFINED_BEHAVIOR => "undefined", + gl::DEBUG_TYPE_PORTABILITY => "portability", + gl::DEBUG_TYPE_PERFORMANCE => "perf", + gl::DEBUG_TYPE_MARKER => "marker", + gl::DEBUG_TYPE_PUSH_GROUP => "group push", + gl::DEBUG_TYPE_POP_GROUP => "group pop", + gl::DEBUG_TYPE_OTHER => "other", + _ => "?", + }; + log!(level, "({}) {}", ty, msg.message); + } + } + + pub fn gl_describe_format(&self, format: ImageFormat) -> FormatDesc { + match format { + ImageFormat::R8 => FormatDesc { + internal: gl::R8, + external: gl::RED, + read: gl::RED, + pixel_type: gl::UNSIGNED_BYTE, + }, + ImageFormat::R16 => FormatDesc { + internal: gl::R16, + external: gl::RED, + read: gl::RED, + pixel_type: gl::UNSIGNED_SHORT, + }, + ImageFormat::BGRA8 => { + FormatDesc { + internal: self.bgra_formats.internal, + external: self.bgra_formats.external, + read: gl::BGRA, + pixel_type: gl::UNSIGNED_BYTE, + } + }, + ImageFormat::RGBA8 => { + FormatDesc { + internal: gl::RGBA8, + external: gl::RGBA, + read: gl::RGBA, + pixel_type: gl::UNSIGNED_BYTE, + } + }, + ImageFormat::RGBAF32 => FormatDesc { + internal: gl::RGBA32F, + external: gl::RGBA, + read: gl::RGBA, + pixel_type: gl::FLOAT, + }, + ImageFormat::RGBAI32 => FormatDesc { + internal: gl::RGBA32I, + external: gl::RGBA_INTEGER, + read: gl::RGBA_INTEGER, + pixel_type: gl::INT, + }, + ImageFormat::RG8 => FormatDesc { + internal: gl::RG8, + external: gl::RG, + read: gl::RG, + pixel_type: gl::UNSIGNED_BYTE, + }, + ImageFormat::RG16 => FormatDesc { + internal: gl::RG16, + external: gl::RG, + read: gl::RG, + pixel_type: gl::UNSIGNED_SHORT, + }, + } + } + + /// Returns a GL format matching an ImageFormat suitable for a renderbuffer. + fn matching_renderbuffer_format(&self, format: ImageFormat) -> gl::GLenum { + match format { + ImageFormat::R8 => gl::R8, + ImageFormat::R16 => gl::R16UI, + ImageFormat::BGRA8 => panic!("Unable to render to BGRA format!"), + ImageFormat::RGBAF32 => gl::RGBA32F, + ImageFormat::RG8 => gl::RG8, + ImageFormat::RG16 => gl::RG16, + ImageFormat::RGBAI32 => gl::RGBA32I, + ImageFormat::RGBA8 => gl::RGBA8, + } + } + + /// Generates a memory report for the resources managed by the device layer. + pub fn report_memory(&self) -> MemoryReport { + let mut report = MemoryReport::default(); + for dim in self.depth_targets.keys() { + report.depth_target_textures += depth_target_size_in_bytes(dim); + } + report + } +} + +pub struct FormatDesc { + /// Format the texel data is internally stored in within a texture. + pub internal: gl::GLenum, + /// Format that we expect the data to be provided when filling the texture. + pub external: gl::GLuint, + /// Format to read the texels as, so that they can be uploaded as `external` + /// later on. + pub read: gl::GLuint, + /// Associated pixel type. + pub pixel_type: gl::GLuint, +} + +struct UploadChunk { + rect: DeviceIntRect, + layer_index: i32, + stride: Option, + offset: usize, + format_override: Option, +} + +struct PixelBuffer<'a> { + size_allocated: usize, + size_used: usize, + // small vector avoids heap allocation for a single chunk + chunks: SmallVec<[UploadChunk; 1]>, + mapping: &'a mut [mem::MaybeUninit], +} + +impl<'a> PixelBuffer<'a> { + fn new( + size_allocated: usize, + mapping: &'a mut [mem::MaybeUninit], + ) -> Self { + PixelBuffer { + size_allocated, + size_used: 0, + chunks: SmallVec::new(), + mapping, + } + } + + fn flush_chunks(&mut self, target: &mut UploadTarget) { + for chunk in self.chunks.drain(..) { + target.update_impl(chunk); + } + self.size_used = 0; + } +} + +impl<'a> Drop for PixelBuffer<'a> { + fn drop(&mut self) { + assert_eq!(self.chunks.len(), 0, "PixelBuffer must be flushed before dropping."); + } +} + +struct UploadTarget<'a> { + device: &'a mut Device, + texture: &'a Texture, +} + +enum TextureUploaderType<'a> { + Immediate, + SingleUseBuffers(VertexUsageHint), + MutliUseBuffer(PixelBuffer<'a>) +} + +pub struct TextureUploader<'a, T> { + target: UploadTarget<'a>, + uploader_type: TextureUploaderType<'a>, + marker: PhantomData, +} + +impl<'a, T> Drop for TextureUploader<'a, T> { + fn drop(&mut self) { + match self.uploader_type { + TextureUploaderType::MutliUseBuffer(ref mut buffer) => { + self.target.device.gl.unmap_buffer(gl::PIXEL_UNPACK_BUFFER); + buffer.flush_chunks(&mut self.target); + self.target.device.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0); + } + TextureUploaderType::SingleUseBuffers(_) => { + self.target.device.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0); + } + TextureUploaderType::Immediate => {} + } + } +} + +impl<'a, T> TextureUploader<'a, T> { + pub fn upload( + &mut self, + mut rect: DeviceIntRect, + layer_index: i32, + stride: Option, + format_override: Option, + data: *const T, + len: usize, + ) -> usize { + // Textures dimensions may have been clamped by the hardware. Crop the + // upload region to match. + let cropped = rect.intersection( + &DeviceIntRect::new(DeviceIntPoint::zero(), self.target.texture.get_dimensions()) + ); + if cfg!(debug_assertions) && cropped.map_or(true, |r| r != rect) { + warn!("Cropping texture upload {:?} to {:?}", rect, cropped); + } + rect = match cropped { + None => return 0, + Some(r) => r, + }; + + let bytes_pp = self.target.texture.format.bytes_per_pixel() as usize; + let width_bytes = rect.size.width as usize * bytes_pp; + + let src_stride = stride.map_or(width_bytes, |stride| { + assert!(stride >= 0); + stride as usize + }); + let src_size = (rect.size.height as usize - 1) * src_stride + width_bytes; + assert!(src_size <= len * mem::size_of::()); + + // for optimal PBO texture uploads the offset and stride of the data in + // the buffer may have to be a multiple of a certain value. + let (dst_size, dst_stride) = self.target.device.required_upload_size_and_stride( + rect.size, + self.target.texture.format, + ); + + // Choose the buffer to use, if any, allocating a new single-use buffer if required. + let mut single_use_buffer = None; + let mut buffer = match self.uploader_type { + TextureUploaderType::MutliUseBuffer(ref mut buffer) => Some(buffer), + TextureUploaderType::SingleUseBuffers(hint) => { + match self.target.device.create_upload_buffer(hint, dst_size) { + Ok(buffer) => { + single_use_buffer = Some(buffer); + single_use_buffer.as_mut() + } + Err(_) => { + // If allocating the buffer failed, fall back to immediate uploads + self.target.device.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0); + self.uploader_type = TextureUploaderType::Immediate; + None + } + } + } + TextureUploaderType::Immediate => None, + }; + + match buffer { + Some(ref mut buffer) => { + if !self.target.device.capabilities.supports_nonzero_pbo_offsets { + assert_eq!(buffer.size_used, 0, "PBO uploads from non-zero offset are not supported."); + } + assert!(buffer.size_used + dst_size <= buffer.size_allocated, "PixelBuffer is too small"); + + unsafe { + let src: &[mem::MaybeUninit] = slice::from_raw_parts(data as *const _, src_size); + + if src_stride == dst_stride { + // the stride is already optimal, so simply copy + // the data as-is in to the buffer + let dst_start = buffer.size_used; + let dst_end = dst_start + src_size; + + buffer.mapping[dst_start..dst_end].copy_from_slice(src); + } else { + // copy the data line-by-line in to the buffer so + // that it has an optimal stride + for y in 0..rect.size.height as usize { + let src_start = y * src_stride; + let src_end = src_start + width_bytes; + let dst_start = buffer.size_used + y * dst_stride; + let dst_end = dst_start + width_bytes; + + buffer.mapping[dst_start..dst_end].copy_from_slice(&src[src_start..src_end]) + } + } + } + + buffer.chunks.push(UploadChunk { + rect, + layer_index, + stride: Some(dst_stride as i32), + offset: buffer.size_used, + format_override, + }); + buffer.size_used += dst_size; + } + None => { + if cfg!(debug_assertions) { + let mut bound_buffer = [0]; + unsafe { + self.target.device.gl.get_integer_v(gl::PIXEL_UNPACK_BUFFER_BINDING, &mut bound_buffer); + } + assert_eq!(bound_buffer[0], 0, "GL_PIXEL_UNPACK_BUFFER must not be bound for immediate uploads."); + } + + self.target.update_impl(UploadChunk { + rect, + layer_index, + stride, + offset: data as _, + format_override, + }); + } + } + + // Flush the buffer if it is for single-use. + if let Some(ref mut buffer) = single_use_buffer { + self.target.device.gl.unmap_buffer(gl::PIXEL_UNPACK_BUFFER); + buffer.flush_chunks(&mut self.target); + } + + dst_size + } +} + +impl<'a> UploadTarget<'a> { + fn update_impl(&mut self, chunk: UploadChunk) { + let format = chunk.format_override.unwrap_or(self.texture.format); + let (gl_format, bpp, data_type) = match format { + ImageFormat::R8 => (gl::RED, 1, gl::UNSIGNED_BYTE), + ImageFormat::R16 => (gl::RED, 2, gl::UNSIGNED_SHORT), + ImageFormat::BGRA8 => (self.device.bgra_formats.external, 4, gl::UNSIGNED_BYTE), + ImageFormat::RGBA8 => (gl::RGBA, 4, gl::UNSIGNED_BYTE), + ImageFormat::RG8 => (gl::RG, 2, gl::UNSIGNED_BYTE), + ImageFormat::RG16 => (gl::RG, 4, gl::UNSIGNED_SHORT), + ImageFormat::RGBAF32 => (gl::RGBA, 16, gl::FLOAT), + ImageFormat::RGBAI32 => (gl::RGBA_INTEGER, 16, gl::INT), + }; + + let row_length = match chunk.stride { + Some(value) => value / bpp, + None => self.texture.size.width, + }; + + if chunk.stride.is_some() { + self.device.gl.pixel_store_i( + gl::UNPACK_ROW_LENGTH, + row_length as _, + ); + } + + let pos = chunk.rect.origin; + let size = chunk.rect.size; + + match self.texture.target { + gl::TEXTURE_2D_ARRAY => { + self.device.gl.tex_sub_image_3d_pbo( + self.texture.target, + 0, + pos.x as _, + pos.y as _, + chunk.layer_index, + size.width as _, + size.height as _, + 1, + gl_format, + data_type, + chunk.offset, + ); + } + gl::TEXTURE_2D | gl::TEXTURE_RECTANGLE | gl::TEXTURE_EXTERNAL_OES => { + self.device.gl.tex_sub_image_2d_pbo( + self.texture.target, + 0, + pos.x as _, + pos.y as _, + size.width as _, + size.height as _, + gl_format, + data_type, + chunk.offset, + ); + } + _ => panic!("BUG: Unexpected texture target!"), + } + + // If using tri-linear filtering, build the mip-map chain for this texture. + if self.texture.filter == TextureFilter::Trilinear { + self.device.gl.generate_mipmap(self.texture.target); + } + + // Reset row length to 0, otherwise the stride would apply to all texture uploads. + if chunk.stride.is_some() { + self.device.gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, 0 as _); + } + } +} + +fn texels_to_u8_slice(texels: &[T]) -> &[u8] { + unsafe { + slice::from_raw_parts(texels.as_ptr() as *const u8, texels.len() * mem::size_of::()) + } +} diff --git a/third_party/webrender/webrender/src/device/mod.rs b/third_party/webrender/webrender/src/device/mod.rs new file mode 100644 index 00000000000..21684dea3e9 --- /dev/null +++ b/third_party/webrender/webrender/src/device/mod.rs @@ -0,0 +1,9 @@ +/* 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/. */ + +mod gl; +pub mod query_gl; + +pub use self::gl::*; +pub use self::query_gl as query; diff --git a/third_party/webrender/webrender/src/device/query_gl.rs b/third_party/webrender/webrender/src/device/query_gl.rs new file mode 100644 index 00000000000..95d515dc5ec --- /dev/null +++ b/third_party/webrender/webrender/src/device/query_gl.rs @@ -0,0 +1,325 @@ +/* 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 gleam::gl; +use std::mem; +use std::rc::Rc; + +use crate::device::GpuFrameId; + +#[derive(Copy, Clone, Debug)] +pub enum GpuDebugMethod { + None, + MarkerEXT, + KHR, +} + +pub trait NamedTag { + fn get_label(&self) -> &str; +} + +#[derive(Debug, Clone)] +pub struct GpuTimer { + pub tag: T, + pub time_ns: u64, +} + +#[derive(Debug, Clone)] +pub struct GpuSampler { + pub tag: T, + pub count: u64, +} + +pub struct QuerySet { + set: Vec, + data: Vec, + pending: gl::GLuint, +} + +impl QuerySet { + fn new() -> Self { + QuerySet { + set: Vec::new(), + data: Vec::new(), + pending: 0, + } + } + + fn reset(&mut self) { + self.data.clear(); + self.pending = 0; + } + + fn add(&mut self, value: T) -> Option { + assert_eq!(self.pending, 0); + self.set.get(self.data.len()).cloned().map(|query_id| { + self.data.push(value); + self.pending = query_id; + query_id + }) + } + + fn take(&mut self, fun: F) -> Vec { + let mut data = mem::replace(&mut self.data, Vec::new()); + for (value, &query) in data.iter_mut().zip(self.set.iter()) { + fun(value, query) + } + data + } +} + +pub struct GpuFrameProfile { + gl: Rc, + timers: QuerySet>, + samplers: QuerySet>, + frame_id: GpuFrameId, + inside_frame: bool, + debug_method: GpuDebugMethod, +} + +impl GpuFrameProfile { + fn new(gl: Rc, debug_method: GpuDebugMethod) -> Self { + GpuFrameProfile { + gl, + timers: QuerySet::new(), + samplers: QuerySet::new(), + frame_id: GpuFrameId::new(0), + inside_frame: false, + debug_method + } + } + + fn enable_timers(&mut self, count: i32) { + self.timers.set = self.gl.gen_queries(count); + } + + fn disable_timers(&mut self) { + if !self.timers.set.is_empty() { + self.gl.delete_queries(&self.timers.set); + } + self.timers.set = Vec::new(); + } + + fn enable_samplers(&mut self, count: i32) { + self.samplers.set = self.gl.gen_queries(count); + } + + fn disable_samplers(&mut self) { + if !self.samplers.set.is_empty() { + self.gl.delete_queries(&self.samplers.set); + } + self.samplers.set = Vec::new(); + } + + fn begin_frame(&mut self, frame_id: GpuFrameId) { + self.frame_id = frame_id; + self.timers.reset(); + self.samplers.reset(); + self.inside_frame = true; + } + + fn end_frame(&mut self) { + self.finish_timer(); + self.finish_sampler(); + self.inside_frame = false; + } + + fn finish_timer(&mut self) { + debug_assert!(self.inside_frame); + if self.timers.pending != 0 { + self.gl.end_query(gl::TIME_ELAPSED); + self.timers.pending = 0; + } + } + + fn finish_sampler(&mut self) { + debug_assert!(self.inside_frame); + if self.samplers.pending != 0 { + self.gl.end_query(gl::SAMPLES_PASSED); + self.samplers.pending = 0; + } + } +} + +impl GpuFrameProfile { + fn start_timer(&mut self, tag: T) -> GpuTimeQuery { + self.finish_timer(); + + let marker = GpuMarker::new(&self.gl, tag.get_label(), self.debug_method); + + if let Some(query) = self.timers.add(GpuTimer { tag, time_ns: 0 }) { + self.gl.begin_query(gl::TIME_ELAPSED, query); + } + + GpuTimeQuery(marker) + } + + fn start_sampler(&mut self, tag: T) -> GpuSampleQuery { + self.finish_sampler(); + + if let Some(query) = self.samplers.add(GpuSampler { tag, count: 0 }) { + self.gl.begin_query(gl::SAMPLES_PASSED, query); + } + + GpuSampleQuery + } + + fn build_samples(&mut self) -> (GpuFrameId, Vec>, Vec>) { + debug_assert!(!self.inside_frame); + let gl = &self.gl; + + ( + self.frame_id, + self.timers.take(|timer, query| { + timer.time_ns = gl.get_query_object_ui64v(query, gl::QUERY_RESULT) + }), + self.samplers.take(|sampler, query| { + sampler.count = gl.get_query_object_ui64v(query, gl::QUERY_RESULT) + }), + ) + } +} + +impl Drop for GpuFrameProfile { + fn drop(&mut self) { + self.disable_timers(); + self.disable_samplers(); + } +} + +pub struct GpuProfiler { + gl: Rc, + frames: Vec>, + next_frame: usize, + debug_method: GpuDebugMethod +} + +impl GpuProfiler { + pub fn new(gl: Rc, debug_method: GpuDebugMethod) -> Self { + const MAX_PROFILE_FRAMES: usize = 4; + let frames = (0 .. MAX_PROFILE_FRAMES) + .map(|_| GpuFrameProfile::new(Rc::clone(&gl), debug_method)) + .collect(); + + GpuProfiler { + gl, + next_frame: 0, + frames, + debug_method + } + } + + pub fn enable_timers(&mut self) { + const MAX_TIMERS_PER_FRAME: i32 = 256; + + for frame in &mut self.frames { + frame.enable_timers(MAX_TIMERS_PER_FRAME); + } + } + + pub fn disable_timers(&mut self) { + for frame in &mut self.frames { + frame.disable_timers(); + } + } + + pub fn enable_samplers(&mut self) { + const MAX_SAMPLERS_PER_FRAME: i32 = 16; + if cfg!(target_os = "macos") { + warn!("Expect macOS driver bugs related to sample queries") + } + + for frame in &mut self.frames { + frame.enable_samplers(MAX_SAMPLERS_PER_FRAME); + } + } + + pub fn disable_samplers(&mut self) { + for frame in &mut self.frames { + frame.disable_samplers(); + } + } +} + +impl GpuProfiler { + pub fn build_samples(&mut self) -> (GpuFrameId, Vec>, Vec>) { + self.frames[self.next_frame].build_samples() + } + + pub fn begin_frame(&mut self, frame_id: GpuFrameId) { + self.frames[self.next_frame].begin_frame(frame_id); + } + + pub fn end_frame(&mut self) { + self.frames[self.next_frame].end_frame(); + self.next_frame = (self.next_frame + 1) % self.frames.len(); + } + + pub fn start_timer(&mut self, tag: T) -> GpuTimeQuery { + self.frames[self.next_frame].start_timer(tag) + } + + pub fn start_sampler(&mut self, tag: T) -> GpuSampleQuery { + self.frames[self.next_frame].start_sampler(tag) + } + + pub fn finish_sampler(&mut self, _sampler: GpuSampleQuery) { + self.frames[self.next_frame].finish_sampler() + } + + pub fn start_marker(&mut self, label: &str) -> GpuMarker { + GpuMarker::new(&self.gl, label, self.debug_method) + } + + pub fn place_marker(&mut self, label: &str) { + GpuMarker::fire(&self.gl, label, self.debug_method) + } +} + +#[must_use] +pub struct GpuMarker { + gl: Option<(Rc, GpuDebugMethod)>, +} + +impl GpuMarker { + fn new(gl: &Rc, message: &str, debug_method: GpuDebugMethod) -> Self { + let gl = match debug_method { + GpuDebugMethod::KHR => { + gl.push_debug_group_khr(gl::DEBUG_SOURCE_APPLICATION, 0, message); + Some((Rc::clone(gl), debug_method)) + }, + GpuDebugMethod::MarkerEXT => { + gl.push_group_marker_ext(message); + Some((Rc::clone(gl), debug_method)) + }, + GpuDebugMethod::None => None, + }; + GpuMarker { gl } + } + + fn fire(gl: &Rc, message: &str, debug_method: GpuDebugMethod) { + match debug_method { + GpuDebugMethod::KHR => gl.debug_message_insert_khr(gl::DEBUG_SOURCE_APPLICATION, gl::DEBUG_TYPE_MARKER, 0, gl::DEBUG_SEVERITY_NOTIFICATION, message), + GpuDebugMethod::MarkerEXT => gl.insert_event_marker_ext(message), + GpuDebugMethod::None => {} + }; + } +} + +impl Drop for GpuMarker { + fn drop(&mut self) { + if let Some((ref gl, debug_method)) = self.gl { + match debug_method { + GpuDebugMethod::KHR => gl.pop_debug_group_khr(), + GpuDebugMethod::MarkerEXT => gl.pop_group_marker_ext(), + GpuDebugMethod::None => {} + }; + } + } +} + +#[must_use] +pub struct GpuTimeQuery(GpuMarker); +#[must_use] +pub struct GpuSampleQuery; diff --git a/third_party/webrender/webrender/src/ellipse.rs b/third_party/webrender/webrender/src/ellipse.rs new file mode 100644 index 00000000000..fac67659849 --- /dev/null +++ b/third_party/webrender/webrender/src/ellipse.rs @@ -0,0 +1,187 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::units::*; +use euclid::Size2D; +use std::f32::consts::FRAC_PI_2; + + +/// Number of steps to integrate arc length over. +const STEP_COUNT: usize = 20; + +/// Represents an ellipse centred at a local space origin. +#[derive(Debug, Clone)] +pub struct Ellipse { + pub radius: Size2D, + pub total_arc_length: f32, +} + +impl Ellipse { + pub fn new(radius: Size2D) -> Ellipse { + // Approximate the total length of the first quadrant of this ellipse. + let total_arc_length = get_simpson_length(FRAC_PI_2, radius.width, radius.height); + + Ellipse { + radius, + total_arc_length, + } + } + + /// Binary search to estimate the angle of an ellipse + /// for a given arc length. This only searches over the + /// first quadrant of an ellipse. + pub fn find_angle_for_arc_length(&self, arc_length: f32) -> f32 { + // Clamp arc length to [0, pi]. + let arc_length = arc_length.max(0.0).min(self.total_arc_length); + + let epsilon = 0.01; + let mut low = 0.0; + let mut high = FRAC_PI_2; + let mut theta = 0.0; + let mut new_low = 0.0; + let mut new_high = FRAC_PI_2; + + while low <= high { + theta = 0.5 * (low + high); + let length = get_simpson_length(theta, self.radius.width, self.radius.height); + + if (length - arc_length).abs() < epsilon { + break; + } else if length < arc_length { + new_low = theta; + } else { + new_high = theta; + } + + // If we have stopped moving down the arc, the answer that we have is as good as + // it is going to get. We break to avoid going into an infinite loop. + if new_low == low && new_high == high { + break; + } + + high = new_high; + low = new_low; + } + + theta + } + + /// Get a point and tangent on this ellipse from a given angle. + /// This only works for the first quadrant of the ellipse. + pub fn get_point_and_tangent(&self, theta: f32) -> (LayoutPoint, LayoutPoint) { + let (sin_theta, cos_theta) = theta.sin_cos(); + let point = LayoutPoint::new( + self.radius.width * cos_theta, + self.radius.height * sin_theta, + ); + let tangent = LayoutPoint::new( + -self.radius.width * sin_theta, + self.radius.height * cos_theta, + ); + (point, tangent) + } + + pub fn contains(&self, point: LayoutPoint) -> bool { + self.signed_distance(point.to_vector()) <= 0.0 + } + + /// Find the signed distance from this ellipse given a point. + /// Taken from http://www.iquilezles.org/www/articles/ellipsedist/ellipsedist.htm + fn signed_distance(&self, point: LayoutVector2D) -> f32 { + // This algorithm fails for circles, so we handle them here. + if self.radius.width == self.radius.height { + return point.length() - self.radius.width; + } + + let mut p = LayoutVector2D::new(point.x.abs(), point.y.abs()); + let mut ab = self.radius.to_vector(); + if p.x > p.y { + p = p.yx(); + ab = ab.yx(); + } + + let l = ab.y * ab.y - ab.x * ab.x; + + let m = ab.x * p.x / l; + let n = ab.y * p.y / l; + let m2 = m * m; + let n2 = n * n; + + let c = (m2 + n2 - 1.0) / 3.0; + let c3 = c * c * c; + + let q = c3 + m2 * n2 * 2.0; + let d = c3 + m2 * n2; + let g = m + m * n2; + + let co = if d < 0.0 { + let p = (q / c3).acos() / 3.0; + let s = p.cos(); + let t = p.sin() * (3.0_f32).sqrt(); + let rx = (-c * (s + t + 2.0) + m2).sqrt(); + let ry = (-c * (s - t + 2.0) + m2).sqrt(); + (ry + l.signum() * rx + g.abs() / (rx * ry) - m) / 2.0 + } else { + let h = 2.0 * m * n * d.sqrt(); + let s = (q + h).signum() * (q + h).abs().powf(1.0 / 3.0); + let u = (q - h).signum() * (q - h).abs().powf(1.0 / 3.0); + let rx = -s - u - c * 4.0 + 2.0 * m2; + let ry = (s - u) * (3.0_f32).sqrt(); + let rm = (rx * rx + ry * ry).sqrt(); + let p = ry / (rm - rx).sqrt(); + (p + 2.0 * g / rm - m) / 2.0 + }; + + let si = (1.0 - co * co).sqrt(); + let r = LayoutVector2D::new(ab.x * co, ab.y * si); + (r - p).length() * (p.y - r.y).signum() + } +} + +/// Use Simpsons rule to approximate the arc length of +/// part of an ellipse. Note that this only works over +/// the range of [0, pi/2]. +// TODO(gw): This is a simplistic way to estimate the +// arc length of an ellipse segment. We can probably use +// a faster / more accurate method! +fn get_simpson_length(theta: f32, rx: f32, ry: f32) -> f32 { + let df = theta / STEP_COUNT as f32; + let mut sum = 0.0; + + for i in 0 .. (STEP_COUNT + 1) { + let (sin_theta, cos_theta) = (i as f32 * df).sin_cos(); + let a = rx * sin_theta; + let b = ry * cos_theta; + let y = (a * a + b * b).sqrt(); + let q = if i == 0 || i == STEP_COUNT { + 1.0 + } else if i % 2 == 0 { + 2.0 + } else { + 4.0 + }; + + sum += q * y; + } + + (df / 3.0) * sum +} + +#[cfg(test)] +pub mod test { + use super::*; + + #[test] + fn find_angle_for_arc_length_for_long_eclipse() { + // Ensure that finding the angle on giant ellipses produces and answer and + // doesn't send us into an infinite loop. + let ellipse = Ellipse::new(LayoutSize::new(57500.0, 25.0)); + let _ = ellipse.find_angle_for_arc_length(55674.53); + assert!(true); + + let ellipse = Ellipse::new(LayoutSize::new(25.0, 57500.0)); + let _ = ellipse.find_angle_for_arc_length(55674.53); + assert!(true); + } +} diff --git a/third_party/webrender/webrender/src/filterdata.rs b/third_party/webrender/webrender/src/filterdata.rs new file mode 100644 index 00000000000..3bbfcebea5e --- /dev/null +++ b/third_party/webrender/webrender/src/filterdata.rs @@ -0,0 +1,214 @@ +/* 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 std::{hash}; +use crate::gpu_cache::{GpuCacheHandle}; +use crate::frame_builder::FrameBuildingState; +use crate::gpu_cache::GpuDataRequest; +use crate::intern; +use api::{FilterDataIntern, ComponentTransferFuncType}; + + +pub type FilterDataHandle = intern::Handle; + +#[derive(Debug, Clone, MallocSizeOf, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum SFilterDataComponent { + Identity, + Table(Vec), + Discrete(Vec), + Linear(f32, f32), + Gamma(f32, f32, f32), +} + +impl Eq for SFilterDataComponent {} + +impl hash::Hash for SFilterDataComponent { + fn hash(&self, state: &mut H) { + match self { + SFilterDataComponent::Identity => { + 0.hash(state); + } + SFilterDataComponent::Table(values) => { + 1.hash(state); + values.len().hash(state); + for val in values { + val.to_bits().hash(state); + } + } + SFilterDataComponent::Discrete(values) => { + 2.hash(state); + values.len().hash(state); + for val in values { + val.to_bits().hash(state); + } + } + SFilterDataComponent::Linear(a, b) => { + 3.hash(state); + a.to_bits().hash(state); + b.to_bits().hash(state); + } + SFilterDataComponent::Gamma(a, b, c) => { + 4.hash(state); + a.to_bits().hash(state); + b.to_bits().hash(state); + c.to_bits().hash(state); + } + } + } +} + +impl SFilterDataComponent { + pub fn to_int(&self) -> u32 { + match self { + SFilterDataComponent::Identity => 0, + SFilterDataComponent::Table(_) => 1, + SFilterDataComponent::Discrete(_) => 2, + SFilterDataComponent::Linear(_, _) => 3, + SFilterDataComponent::Gamma(_, _, _) => 4, + } + } + + pub fn from_functype_values( + func_type: ComponentTransferFuncType, + values: &[f32], + ) -> SFilterDataComponent { + match func_type { + ComponentTransferFuncType::Identity => SFilterDataComponent::Identity, + ComponentTransferFuncType::Table => SFilterDataComponent::Table(values.to_vec()), + ComponentTransferFuncType::Discrete => SFilterDataComponent::Discrete(values.to_vec()), + ComponentTransferFuncType::Linear => SFilterDataComponent::Linear(values[0], values[1]), + ComponentTransferFuncType::Gamma => SFilterDataComponent::Gamma(values[0], values[1], values[2]), + } + } +} + +#[derive(Debug, Clone, MallocSizeOf, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct SFilterData { + pub r_func: SFilterDataComponent, + pub g_func: SFilterDataComponent, + pub b_func: SFilterDataComponent, + pub a_func: SFilterDataComponent, +} + +#[derive(Debug, Clone, MallocSizeOf, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct SFilterDataKey { + pub data: SFilterData, +} + +impl intern::InternDebug for SFilterDataKey {} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct SFilterDataTemplate { + pub data: SFilterData, + pub gpu_cache_handle: GpuCacheHandle, +} + +impl From for SFilterDataTemplate { + fn from(item: SFilterDataKey) -> Self { + SFilterDataTemplate { + data: item.data, + gpu_cache_handle: GpuCacheHandle::new(), + } + } +} + +impl SFilterData { + pub fn is_identity(&self) -> bool { + self.r_func == SFilterDataComponent::Identity + && self.g_func == SFilterDataComponent::Identity + && self.b_func == SFilterDataComponent::Identity + && self.a_func == SFilterDataComponent::Identity + } + + pub fn update(&self, mut request: GpuDataRequest) { + push_component_transfer_data(&self.r_func, &mut request); + push_component_transfer_data(&self.g_func, &mut request); + push_component_transfer_data(&self.b_func, &mut request); + push_component_transfer_data(&self.a_func, &mut request); + assert!(!self.is_identity()); + } +} + +impl SFilterDataTemplate { + /// Update the GPU cache for a given filter data template. This may be called multiple + /// times per frame, by each primitive reference that refers to this interned + /// template. The initial request call to the GPU cache ensures that work is only + /// done if the cache entry is invalid (due to first use or eviction). + pub fn update( + &mut self, + frame_state: &mut FrameBuildingState, + ) { + if let Some(request) = frame_state.gpu_cache.request(&mut self.gpu_cache_handle) { + self.data.update(request); + } + } +} + +impl intern::Internable for FilterDataIntern { + type Key = SFilterDataKey; + type StoreData = SFilterDataTemplate; + type InternData = (); +} + +fn push_component_transfer_data( + func_comp: &SFilterDataComponent, + request: &mut GpuDataRequest, +) { + match func_comp { + SFilterDataComponent::Identity => {} + SFilterDataComponent::Table(values) | + SFilterDataComponent::Discrete(values) => { + // Push a 256 entry lookup table. + assert!(values.len() > 0); + for i in 0 .. 64 { + let mut arr = [0.0 ; 4]; + for j in 0 .. 4 { + if (values.len() == 1) || (i == 63 && j == 3) { + arr[j] = values[values.len()-1]; + } else { + let c = ((4*i + j) as f32)/255.0; + match func_comp { + SFilterDataComponent::Table(_) => { + let n = (values.len()-1) as f32; + let k = (n * c).floor() as u32; + let ku = k as usize; + assert!(ku < values.len()-1); + arr[j] = values[ku] + (c*n - (k as f32)) * (values[ku+1] - values[ku]); + } + SFilterDataComponent::Discrete(_) => { + let n = values.len() as f32; + let k = (n * c).floor() as usize; + assert!(k < values.len()); + arr[j] = values[k]; + } + SFilterDataComponent::Identity | + SFilterDataComponent::Linear(_,_) | + SFilterDataComponent::Gamma(_,_,_) => { + unreachable!(); + } + } + + } + } + + request.push(arr); + } + } + SFilterDataComponent::Linear(a, b) => { + request.push([*a, *b, 0.0, 0.0]); + } + SFilterDataComponent::Gamma(a, b, c) => { + request.push([*a, *b, *c, 0.0]); + } + } +} diff --git a/third_party/webrender/webrender/src/frame_builder.rs b/third_party/webrender/webrender/src/frame_builder.rs new file mode 100644 index 00000000000..afb56778c95 --- /dev/null +++ b/third_party/webrender/webrender/src/frame_builder.rs @@ -0,0 +1,1097 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{ColorF, DebugFlags, DocumentLayer, FontRenderMode, PremultipliedColorF}; +use api::units::*; +use crate::batch::{BatchBuilder, AlphaBatchBuilder, AlphaBatchContainer}; +use crate::clip::{ClipStore, ClipChainStack, ClipInstance}; +use crate::spatial_tree::{SpatialTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex}; +use crate::composite::{CompositorKind, CompositeState}; +use crate::debug_render::DebugItem; +use crate::gpu_cache::{GpuCache, GpuCacheHandle}; +use crate::gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind, ZBufferIdGenerator}; +use crate::gpu_types::TransformData; +use crate::internal_types::{FastHashMap, PlaneSplitter, SavedTargetIndex}; +use crate::picture::{PictureUpdateState, SurfaceInfo, ROOT_SURFACE_INDEX, SurfaceIndex, RecordedDirtyRegion}; +use crate::picture::{RetainedTiles, TileCacheInstance, DirtyRegion, SurfaceRenderTasks, SubpixelMode}; +use crate::picture::{BackdropKind, TileCacheLogger}; +use crate::prim_store::{SpaceMapper, PictureIndex, PrimitiveDebugId, PrimitiveScratchBuffer}; +use crate::prim_store::{DeferredResolve, PrimitiveVisibilityMask}; +use crate::profiler::{FrameProfileCounters, TextureCacheProfileCounters, ResourceProfileCounters}; +use crate::render_backend::{DataStores, FrameStamp, FrameId}; +use crate::render_target::{RenderTarget, PictureCacheTarget, TextureCacheRenderTarget}; +use crate::render_target::{RenderTargetContext, RenderTargetKind}; +use crate::render_task_graph::{RenderTaskId, RenderTaskGraph, RenderTaskGraphCounters}; +use crate::render_task_graph::{RenderPassKind, RenderPass}; +use crate::render_task::{RenderTask, RenderTaskLocation, RenderTaskKind}; +use crate::resource_cache::{ResourceCache}; +use crate::scene::{BuiltScene, SceneProperties}; +use crate::segment::SegmentBuilder; +use std::{f32, mem}; +use crate::util::MaxRect; + + +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum ChasePrimitive { + Nothing, + Id(PrimitiveDebugId), + LocalRect(LayoutRect), +} + +impl Default for ChasePrimitive { + fn default() -> Self { + ChasePrimitive::Nothing + } +} + +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct FrameBuilderConfig { + pub default_font_render_mode: FontRenderMode, + pub dual_source_blending_is_supported: bool, + pub dual_source_blending_is_enabled: bool, + pub chase_primitive: ChasePrimitive, + /// The immutable global picture caching enable from `RendererOptions` + pub global_enable_picture_caching: bool, + /// True if we're running tests (i.e. via wrench). + pub testing: bool, + pub gpu_supports_fast_clears: bool, + pub gpu_supports_advanced_blend: bool, + pub advanced_blend_is_coherent: bool, + pub batch_lookback_count: usize, + pub background_color: Option, + pub compositor_kind: CompositorKind, + pub tile_size_override: Option, + pub max_depth_ids: i32, + pub max_target_size: i32, +} + +/// A set of common / global resources that are retained between +/// new display lists, such that any GPU cache handles can be +/// persisted even when a new display list arrives. +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct FrameGlobalResources { + /// The image shader block for the most common / default + /// set of image parameters (color white, stretch == rect.size). + pub default_image_handle: GpuCacheHandle, + + /// A GPU cache config for drawing transparent rectangle primitives. + /// This is used to 'cut out' overlay tiles where a compositor + /// surface exists. + pub default_transparent_rect_handle: GpuCacheHandle, +} + +impl FrameGlobalResources { + pub fn empty() -> Self { + FrameGlobalResources { + default_image_handle: GpuCacheHandle::new(), + default_transparent_rect_handle: GpuCacheHandle::new(), + } + } + + pub fn update( + &mut self, + gpu_cache: &mut GpuCache, + ) { + if let Some(mut request) = gpu_cache.request(&mut self.default_image_handle) { + request.push(PremultipliedColorF::WHITE); + request.push(PremultipliedColorF::WHITE); + request.push([ + -1.0, // -ve means use prim rect for stretch size + 0.0, + 0.0, + 0.0, + ]); + } + + if let Some(mut request) = gpu_cache.request(&mut self.default_transparent_rect_handle) { + request.push(PremultipliedColorF::TRANSPARENT); + } + } +} + +/// Produces the frames that are sent to the renderer. +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct FrameBuilder { + /// Cache of surface tiles from the previous frame builder + /// that can optionally be consumed by this frame builder. + pending_retained_tiles: RetainedTiles, + pub globals: FrameGlobalResources, +} + +pub struct FrameVisibilityContext<'a> { + pub spatial_tree: &'a SpatialTree, + pub global_screen_world_rect: WorldRect, + pub global_device_pixel_scale: DevicePixelScale, + pub surfaces: &'a [SurfaceInfo], + pub debug_flags: DebugFlags, + pub scene_properties: &'a SceneProperties, + pub config: FrameBuilderConfig, +} + +pub struct FrameVisibilityState<'a> { + pub clip_store: &'a mut ClipStore, + pub resource_cache: &'a mut ResourceCache, + pub gpu_cache: &'a mut GpuCache, + pub scratch: &'a mut PrimitiveScratchBuffer, + pub tile_cache: Option>, + pub retained_tiles: &'a mut RetainedTiles, + pub data_stores: &'a mut DataStores, + pub clip_chain_stack: ClipChainStack, + pub render_tasks: &'a mut RenderTaskGraph, + pub composite_state: &'a mut CompositeState, + /// A stack of currently active off-screen surfaces during the + /// visibility frame traversal. + pub surface_stack: Vec, +} + +impl<'a> FrameVisibilityState<'a> { + pub fn push_surface( + &mut self, + surface_index: SurfaceIndex, + shared_clips: &[ClipInstance], + spatial_tree: &SpatialTree, + ) { + self.surface_stack.push(surface_index); + self.clip_chain_stack.push_surface(shared_clips, spatial_tree); + } + + pub fn pop_surface(&mut self) { + self.surface_stack.pop().unwrap(); + self.clip_chain_stack.pop_surface(); + } +} + +pub struct FrameBuildingContext<'a> { + pub global_device_pixel_scale: DevicePixelScale, + pub scene_properties: &'a SceneProperties, + pub global_screen_world_rect: WorldRect, + pub spatial_tree: &'a SpatialTree, + pub max_local_clip: LayoutRect, + pub debug_flags: DebugFlags, + pub fb_config: &'a FrameBuilderConfig, +} + +pub struct FrameBuildingState<'a> { + pub render_tasks: &'a mut RenderTaskGraph, + pub profile_counters: &'a mut FrameProfileCounters, + pub clip_store: &'a mut ClipStore, + pub resource_cache: &'a mut ResourceCache, + pub gpu_cache: &'a mut GpuCache, + pub transforms: &'a mut TransformPalette, + pub segment_builder: SegmentBuilder, + pub surfaces: &'a mut Vec, + pub dirty_region_stack: Vec, + pub composite_state: &'a mut CompositeState, +} + +impl<'a> FrameBuildingState<'a> { + /// Retrieve the current dirty region during primitive traversal. + pub fn current_dirty_region(&self) -> &DirtyRegion { + self.dirty_region_stack.last().unwrap() + } + + /// Push a new dirty region for child primitives to cull / clip against. + pub fn push_dirty_region(&mut self, region: DirtyRegion) { + self.dirty_region_stack.push(region); + } + + /// Pop the top dirty region from the stack. + pub fn pop_dirty_region(&mut self) { + self.dirty_region_stack.pop().unwrap(); + } +} + +/// Immutable context of a picture when processing children. +#[derive(Debug)] +pub struct PictureContext { + pub pic_index: PictureIndex, + pub apply_local_clip_rect: bool, + pub is_passthrough: bool, + pub surface_spatial_node_index: SpatialNodeIndex, + pub raster_spatial_node_index: SpatialNodeIndex, + /// The surface that this picture will render on. + pub surface_index: SurfaceIndex, + pub dirty_region_count: usize, + pub subpixel_mode: SubpixelMode, +} + +/// Mutable state of a picture that gets modified when +/// the children are processed. +pub struct PictureState { + pub map_local_to_pic: SpaceMapper, + pub map_pic_to_world: SpaceMapper, + pub map_pic_to_raster: SpaceMapper, + pub map_raster_to_world: SpaceMapper, + /// If the plane splitter, the primitives get added to it instead of + /// batching into their parent pictures. + pub plane_splitter: Option, +} + +impl FrameBuilder { + pub fn new() -> Self { + FrameBuilder { + pending_retained_tiles: RetainedTiles::new(), + globals: FrameGlobalResources::empty(), + } + } + + /// Provide any cached surface tiles from the previous frame builder + /// to a new frame builder. These will be consumed or dropped the + /// first time a new frame builder creates a frame. + pub fn set_retained_resources(&mut self, retained_tiles: RetainedTiles) { + // In general, the pending retained tiles are consumed by the frame + // builder the first time a frame is built after a new scene has + // arrived. However, if two scenes arrive in quick succession, the + // frame builder may not have had a chance to build a frame and + // consume the pending tiles. In this case, the pending tiles will + // be lost, causing a full invalidation of the entire screen. To + // avoid this, if there are still pending tiles, include them in + // the retained tiles passed to the next frame builder. + self.pending_retained_tiles.merge(retained_tiles); + } + + /// Compute the contribution (bounding rectangles, and resources) of layers and their + /// primitives in screen space. + fn build_layer_screen_rects_and_cull_layers( + &mut self, + scene: &mut BuiltScene, + global_screen_world_rect: WorldRect, + resource_cache: &mut ResourceCache, + gpu_cache: &mut GpuCache, + render_tasks: &mut RenderTaskGraph, + profile_counters: &mut FrameProfileCounters, + global_device_pixel_scale: DevicePixelScale, + scene_properties: &SceneProperties, + transform_palette: &mut TransformPalette, + data_stores: &mut DataStores, + surfaces: &mut Vec, + scratch: &mut PrimitiveScratchBuffer, + debug_flags: DebugFlags, + texture_cache_profile: &mut TextureCacheProfileCounters, + composite_state: &mut CompositeState, + tile_cache_logger: &mut TileCacheLogger, + ) -> Option { + profile_scope!("build_layer_screen_rects_and_cull_layers"); + + if scene.prim_store.pictures.is_empty() { + return None + } + + scratch.begin_frame(); + + let root_spatial_node_index = scene.spatial_tree.root_reference_frame_index(); + + const MAX_CLIP_COORD: f32 = 1.0e9; + + let frame_context = FrameBuildingContext { + global_device_pixel_scale, + scene_properties, + global_screen_world_rect, + spatial_tree: &scene.spatial_tree, + max_local_clip: LayoutRect::new( + LayoutPoint::new(-MAX_CLIP_COORD, -MAX_CLIP_COORD), + LayoutSize::new(2.0 * MAX_CLIP_COORD, 2.0 * MAX_CLIP_COORD), + ), + debug_flags, + fb_config: &scene.config, + }; + + let root_render_task_id = render_tasks.add().init( + RenderTask::new_picture( + RenderTaskLocation::Fixed(scene.output_rect), + scene.output_rect.size.to_f32(), + scene.root_pic_index, + DeviceIntPoint::zero(), + UvRectKind::Rect, + ROOT_SPATIAL_NODE_INDEX, + global_device_pixel_scale, + PrimitiveVisibilityMask::all(), + None, + None, + ) + ); + + // Construct a dummy root surface, that represents the + // main framebuffer surface. + let root_surface = SurfaceInfo::new( + ROOT_SPATIAL_NODE_INDEX, + ROOT_SPATIAL_NODE_INDEX, + 0.0, + global_screen_world_rect, + &scene.spatial_tree, + global_device_pixel_scale, + (1.0, 1.0), + ); + surfaces.push(root_surface); + + let mut retained_tiles = mem::replace( + &mut self.pending_retained_tiles, + RetainedTiles::new(), + ); + + // The first major pass of building a frame is to walk the picture + // tree. This pass must be quick (it should never touch individual + // primitives). For now, all we do here is determine which pictures + // will create surfaces. In the future, this will be expanded to + // set up render tasks, determine scaling of surfaces, and detect + // which surfaces have valid cached surfaces that don't need to + // be rendered this frame. + PictureUpdateState::update_all( + surfaces, + scene.root_pic_index, + &mut scene.prim_store.pictures, + &frame_context, + gpu_cache, + &scene.clip_store, + data_stores, + composite_state, + ); + + { + profile_scope!("UpdateVisibility"); + profile_marker!("UpdateVisibility"); + + let visibility_context = FrameVisibilityContext { + global_device_pixel_scale, + spatial_tree: &scene.spatial_tree, + global_screen_world_rect, + surfaces, + debug_flags, + scene_properties, + config: scene.config, + }; + + let mut visibility_state = FrameVisibilityState { + resource_cache, + gpu_cache, + clip_store: &mut scene.clip_store, + scratch, + tile_cache: None, + retained_tiles: &mut retained_tiles, + data_stores, + clip_chain_stack: ClipChainStack::new(), + render_tasks, + composite_state, + /// Try to avoid allocating during frame traversal - it's unlikely to have a + /// surface stack depth of > 16 in most cases. + surface_stack: Vec::with_capacity(16), + }; + + scene.prim_store.update_visibility( + scene.root_pic_index, + ROOT_SURFACE_INDEX, + &global_screen_world_rect, + &visibility_context, + &mut visibility_state, + ); + + // When there are tiles that are left remaining in the `retained_tiles`, + // dirty rects are not valid. + if !visibility_state.retained_tiles.caches.is_empty() { + visibility_state.composite_state.dirty_rects_are_valid = false; + } + + // When a new display list is processed by WR, the existing tiles from + // any picture cache are stored in the `retained_tiles` field above. This + // allows the first frame of a new display list to reuse any existing tiles + // and surfaces that match. Once the `update_visibility` call above is + // complete, any tiles that are left remaining in the `retained_tiles` + // map are not needed and will be dropped. For simple compositing mode, + // this is fine, since texture cache handles are garbage collected at + // the end of each frame. However, if we're in native compositor mode, + // we need to manually clean up any native compositor surfaces that were + // allocated by these tiles. + for (_, mut cache_state) in visibility_state.retained_tiles.caches.drain() { + if let Some(native_surface) = cache_state.native_surface.take() { + visibility_state.resource_cache.destroy_compositor_surface(native_surface.opaque); + visibility_state.resource_cache.destroy_compositor_surface(native_surface.alpha); + } + + for (_, external_surface) in cache_state.external_native_surface_cache.drain() { + visibility_state.resource_cache.destroy_compositor_surface(external_surface.native_surface_id) + } + } + } + + let mut frame_state = FrameBuildingState { + render_tasks, + profile_counters, + clip_store: &mut scene.clip_store, + resource_cache, + gpu_cache, + transforms: transform_palette, + segment_builder: SegmentBuilder::new(), + surfaces, + dirty_region_stack: Vec::new(), + composite_state, + }; + + frame_state + .surfaces + .first_mut() + .unwrap() + .render_tasks = Some(SurfaceRenderTasks { + root: root_render_task_id, + port: root_render_task_id, + }); + + // Push a default dirty region which culls primitives + // against the screen world rect, in absence of any + // other dirty regions. + let mut default_dirty_region = DirtyRegion::new(); + default_dirty_region.push( + frame_context.global_screen_world_rect, + PrimitiveVisibilityMask::all(), + ); + frame_state.push_dirty_region(default_dirty_region); + + let (pic_context, mut pic_state, mut prim_list) = scene + .prim_store + .pictures[scene.root_pic_index.0] + .take_context( + scene.root_pic_index, + WorldRect::max_rect(), + root_spatial_node_index, + root_spatial_node_index, + ROOT_SURFACE_INDEX, + &SubpixelMode::Allow, + &mut frame_state, + &frame_context, + scratch, + tile_cache_logger + ) + .unwrap(); + + tile_cache_logger.advance(); + + { + profile_marker!("PreparePrims"); + + scene.prim_store.prepare_primitives( + &mut prim_list, + &pic_context, + &mut pic_state, + &frame_context, + &mut frame_state, + data_stores, + scratch, + tile_cache_logger, + ); + } + + let pic = &mut scene.prim_store.pictures[scene.root_pic_index.0]; + pic.restore_context( + ROOT_SURFACE_INDEX, + prim_list, + pic_context, + pic_state, + &mut frame_state, + ); + + frame_state.pop_dirty_region(); + + { + profile_marker!("BlockOnResources"); + + resource_cache.block_until_all_resources_added(gpu_cache, + render_tasks, + texture_cache_profile); + } + + Some(root_render_task_id) + } + + pub fn build( + &mut self, + scene: &mut BuiltScene, + resource_cache: &mut ResourceCache, + gpu_cache: &mut GpuCache, + stamp: FrameStamp, + global_device_pixel_scale: DevicePixelScale, + layer: DocumentLayer, + device_origin: DeviceIntPoint, + pan: WorldPoint, + resource_profile: &mut ResourceProfileCounters, + scene_properties: &SceneProperties, + data_stores: &mut DataStores, + scratch: &mut PrimitiveScratchBuffer, + render_task_counters: &mut RenderTaskGraphCounters, + debug_flags: DebugFlags, + tile_cache_logger: &mut TileCacheLogger, + ) -> Frame { + profile_scope!("build"); + profile_marker!("BuildFrame"); + + let mut profile_counters = FrameProfileCounters::new(); + profile_counters + .total_primitives + .set(scene.prim_store.prim_count()); + resource_profile.content_slices.set(scene.content_slice_count); + resource_cache.begin_frame(stamp); + gpu_cache.begin_frame(stamp); + + self.globals.update(gpu_cache); + + scene.spatial_tree.update_tree( + pan, + global_device_pixel_scale, + scene_properties, + ); + let mut transform_palette = scene.spatial_tree.build_transform_palette(); + scene.clip_store.clear_old_instances(); + + let mut render_tasks = RenderTaskGraph::new( + stamp.frame_id(), + render_task_counters, + ); + let mut surfaces = Vec::new(); + + let output_size = scene.output_rect.size.to_i32(); + let screen_world_rect = (scene.output_rect.to_f32() / global_device_pixel_scale).round_out(); + + // Determine if we will draw this frame with picture caching enabled. This depends on: + // (1) If globally enabled when WR was initialized + // (2) If current debug flags allow picture caching + // (3) Whether we are currently pinch zooming + // (4) If any picture cache spatial nodes are not in the root coordinate system + let picture_caching_is_enabled = + scene.config.global_enable_picture_caching && + !debug_flags.contains(DebugFlags::DISABLE_PICTURE_CACHING) && + !scene.picture_cache_spatial_nodes.iter().any(|spatial_node_index| { + let spatial_node = &scene + .spatial_tree + .spatial_nodes[spatial_node_index.0 as usize]; + spatial_node.is_ancestor_or_self_zooming + }); + + let mut composite_state = CompositeState::new( + scene.config.compositor_kind, + picture_caching_is_enabled, + global_device_pixel_scale, + scene.config.max_depth_ids, + ); + + let main_render_task_id = self.build_layer_screen_rects_and_cull_layers( + scene, + screen_world_rect, + resource_cache, + gpu_cache, + &mut render_tasks, + &mut profile_counters, + global_device_pixel_scale, + scene_properties, + &mut transform_palette, + data_stores, + &mut surfaces, + scratch, + debug_flags, + &mut resource_profile.texture_cache, + &mut composite_state, + tile_cache_logger, + ); + + let mut passes; + let mut deferred_resolves = vec![]; + let mut has_texture_cache_tasks = false; + let mut prim_headers = PrimitiveHeaders::new(); + + { + profile_marker!("Batching"); + + passes = render_tasks.generate_passes( + main_render_task_id, + output_size, + scene.config.gpu_supports_fast_clears, + ); + + // Used to generated a unique z-buffer value per primitive. + let mut z_generator = ZBufferIdGenerator::new(layer, scene.config.max_depth_ids); + let use_dual_source_blending = scene.config.dual_source_blending_is_enabled && + scene.config.dual_source_blending_is_supported; + + for pass in &mut passes { + let mut ctx = RenderTargetContext { + global_device_pixel_scale, + prim_store: &scene.prim_store, + resource_cache, + use_dual_source_blending, + use_advanced_blending: scene.config.gpu_supports_advanced_blend, + break_advanced_blend_batches: !scene.config.advanced_blend_is_coherent, + batch_lookback_count: scene.config.batch_lookback_count, + spatial_tree: &scene.spatial_tree, + data_stores, + surfaces: &surfaces, + scratch, + screen_world_rect, + globals: &self.globals, + }; + + build_render_pass( + pass, + &mut ctx, + gpu_cache, + &mut render_tasks, + &mut deferred_resolves, + &scene.clip_store, + &mut transform_palette, + &mut prim_headers, + &mut z_generator, + &mut composite_state, + ); + + match pass.kind { + RenderPassKind::MainFramebuffer { .. } => {} + RenderPassKind::OffScreen { + ref texture_cache, + ref picture_cache, + .. + } => { + has_texture_cache_tasks |= !texture_cache.is_empty(); + has_texture_cache_tasks |= !picture_cache.is_empty(); + } + } + } + } + + let gpu_cache_frame_id = gpu_cache.end_frame(&mut resource_profile.gpu_cache).frame_id(); + + render_tasks.write_task_data(); + *render_task_counters = render_tasks.counters(); + resource_cache.end_frame(&mut resource_profile.texture_cache); + + Frame { + content_origin: scene.output_rect.origin, + device_rect: DeviceIntRect::new( + device_origin, + scene.output_rect.size, + ), + layer, + profile_counters, + passes, + transform_palette: transform_palette.finish(), + render_tasks, + deferred_resolves, + gpu_cache_frame_id, + has_been_rendered: false, + has_texture_cache_tasks, + prim_headers, + recorded_dirty_regions: mem::replace(&mut scratch.recorded_dirty_regions, Vec::new()), + debug_items: mem::replace(&mut scratch.debug_items, Vec::new()), + composite_state, + } + } +} + +/// Processes this pass to prepare it for rendering. +/// +/// Among other things, this allocates output regions for each of our tasks +/// (added via `add_render_task`) in a RenderTarget and assigns it into that +/// target. +pub fn build_render_pass( + pass: &mut RenderPass, + ctx: &mut RenderTargetContext, + gpu_cache: &mut GpuCache, + render_tasks: &mut RenderTaskGraph, + deferred_resolves: &mut Vec, + clip_store: &ClipStore, + transforms: &mut TransformPalette, + prim_headers: &mut PrimitiveHeaders, + z_generator: &mut ZBufferIdGenerator, + composite_state: &mut CompositeState, +) { + profile_scope!("build_render_pass"); + + match pass.kind { + RenderPassKind::MainFramebuffer { ref mut main_target, .. } => { + profile_scope!("MainFrameBuffer"); + for &task_id in &pass.tasks { + profile_scope!("task"); + assert_eq!(render_tasks[task_id].target_kind(), RenderTargetKind::Color); + main_target.add_task( + task_id, + ctx, + gpu_cache, + render_tasks, + clip_store, + transforms, + deferred_resolves, + ); + } + main_target.build( + ctx, + gpu_cache, + render_tasks, + deferred_resolves, + prim_headers, + transforms, + z_generator, + composite_state, + ); + } + RenderPassKind::OffScreen { + ref mut color, + ref mut alpha, + ref mut texture_cache, + ref mut picture_cache, + } => { + profile_scope!("OffScreen"); + let saved_color = if pass.tasks.iter().any(|&task_id| { + let t = &render_tasks[task_id]; + t.target_kind() == RenderTargetKind::Color && t.saved_index.is_some() + }) { + Some(render_tasks.save_target()) + } else { + None + }; + let saved_alpha = if pass.tasks.iter().any(|&task_id| { + let t = &render_tasks[task_id]; + t.target_kind() == RenderTargetKind::Alpha && t.saved_index.is_some() + }) { + Some(render_tasks.save_target()) + } else { + None + }; + + // Collect a list of picture cache tasks, keyed by picture index. + // This allows us to only walk that picture root once, adding the + // primitives to all relevant batches at the same time. + let mut picture_cache_tasks = FastHashMap::default(); + + // Step through each task, adding to batches as appropriate. + for &task_id in &pass.tasks { + let (target_kind, texture_target, layer) = { + let task = &mut render_tasks[task_id]; + let target_kind = task.target_kind(); + + // Find a target to assign this task to, or create a new + // one if required. + let (texture_target, layer) = match task.location { + RenderTaskLocation::TextureCache { texture, layer, .. } => { + (Some(texture), layer) + } + RenderTaskLocation::Fixed(..) => { + (None, 0) + } + RenderTaskLocation::Dynamic(ref mut origin, size) => { + let (target_index, alloc_origin) = match target_kind { + RenderTargetKind::Color => color.allocate(size), + RenderTargetKind::Alpha => alpha.allocate(size), + }; + *origin = Some((alloc_origin, target_index)); + (None, target_index.0) + } + RenderTaskLocation::PictureCache { .. } => { + // For picture cache tiles, just store them in the map + // of picture cache tasks, to be handled below. + let pic_index = match task.kind { + RenderTaskKind::Picture(ref info) => { + info.pic_index + } + _ => { + unreachable!(); + } + }; + + picture_cache_tasks + .entry(pic_index) + .or_insert_with(Vec::new) + .push(task_id); + + continue; + } + }; + + // Replace the pending saved index with a real one + if let Some(index) = task.saved_index { + assert_eq!(index, SavedTargetIndex::PENDING); + task.saved_index = match target_kind { + RenderTargetKind::Color => saved_color, + RenderTargetKind::Alpha => saved_alpha, + }; + } + + // Give the render task an opportunity to add any + // information to the GPU cache, if appropriate. + task.write_gpu_blocks(gpu_cache); + + (target_kind, texture_target, layer) + }; + + match texture_target { + Some(texture_target) => { + let texture = texture_cache + .entry((texture_target, layer)) + .or_insert_with(|| + TextureCacheRenderTarget::new(target_kind) + ); + texture.add_task(task_id, render_tasks); + } + None => { + match target_kind { + RenderTargetKind::Color => { + color.targets[layer].add_task( + task_id, + ctx, + gpu_cache, + render_tasks, + clip_store, + transforms, + deferred_resolves, + ) + } + RenderTargetKind::Alpha => { + alpha.targets[layer].add_task( + task_id, + ctx, + gpu_cache, + render_tasks, + clip_store, + transforms, + deferred_resolves, + ) + } + } + } + } + } + + // For each picture in this pass that has picture cache tiles, create + // a batcher per task, and then build batches for each of the tasks + // at the same time. + for (pic_index, task_ids) in picture_cache_tasks { + profile_scope!("picture_cache_task"); + let pic = &ctx.prim_store.pictures[pic_index.0]; + let tile_cache = pic.tile_cache.as_ref().expect("bug"); + + // Extract raster/surface spatial nodes for this surface. + let (root_spatial_node_index, surface_spatial_node_index) = match pic.raster_config { + Some(ref rc) => { + let surface = &ctx.surfaces[rc.surface_index.0]; + (surface.raster_spatial_node_index, surface.surface_spatial_node_index) + } + None => { + unreachable!(); + } + }; + + // Determine the clear color for this picture cache. + // If the entire tile cache is opaque, we can skip clear completely. + // If it's the first layer, clear it to white to allow subpixel AA on that + // first layer even if it's technically transparent. + // Otherwise, clear to transparent and composite with alpha. + // TODO(gw): We can detect per-tile opacity for the clear color here + // which might be a significant win on some pages? + let forced_opaque = match tile_cache.background_color { + Some(color) => color.a >= 1.0, + None => false, + }; + let mut clear_color = if forced_opaque { + Some(ColorF::WHITE) + } else { + Some(ColorF::TRANSPARENT) + }; + + // If this picture cache has a valid color backdrop, we will use + // that as the clear color, skipping the draw of the backdrop + // primitive (and anything prior to it) during batching. + if let Some(BackdropKind::Color { color }) = tile_cache.backdrop.kind { + clear_color = Some(color); + } + + // Create an alpha batcher for each of the tasks of this picture. + let mut batchers = Vec::new(); + for task_id in &task_ids { + let task_id = *task_id; + let vis_mask = match render_tasks[task_id].kind { + RenderTaskKind::Picture(ref info) => info.vis_mask, + _ => unreachable!(), + }; + batchers.push(AlphaBatchBuilder::new( + pass.screen_size, + ctx.break_advanced_blend_batches, + ctx.batch_lookback_count, + task_id, + render_tasks.get_task_address(task_id), + vis_mask, + )); + } + + // Run the batch creation code for this picture, adding items to + // all relevant per-task batchers. + let mut batch_builder = BatchBuilder::new(batchers); + { + profile_scope!("add_pic_to_batch"); + batch_builder.add_pic_to_batch( + pic, + ctx, + gpu_cache, + render_tasks, + deferred_resolves, + prim_headers, + transforms, + root_spatial_node_index, + surface_spatial_node_index, + z_generator, + composite_state, + ); + } + + // Create picture cache targets, one per render task, and assign + // the correct batcher to them. + let batchers = batch_builder.finalize(); + for (task_id, batcher) in task_ids.into_iter().zip(batchers.into_iter()) { + profile_scope!("task"); + let task = &render_tasks[task_id]; + let (target_rect, _) = task.get_target_rect(); + + match task.location { + RenderTaskLocation::PictureCache { ref surface, .. } => { + // TODO(gw): The interface here is a bit untidy since it's + // designed to support batch merging, which isn't + // relevant for picture cache targets. We + // can restructure / tidy this up a bit. + let (scissor_rect, valid_rect) = match render_tasks[task_id].kind { + RenderTaskKind::Picture(ref info) => { + ( + info.scissor_rect.expect("bug: must be set for cache tasks"), + info.valid_rect.expect("bug: must be set for cache tasks"), + ) + } + _ => unreachable!(), + }; + let mut batch_containers = Vec::new(); + let mut alpha_batch_container = AlphaBatchContainer::new(Some(scissor_rect)); + batcher.build( + &mut batch_containers, + &mut alpha_batch_container, + target_rect, + None, + ); + debug_assert!(batch_containers.is_empty()); + + let target = PictureCacheTarget { + surface: surface.clone(), + clear_color, + alpha_batch_container, + dirty_rect: scissor_rect, + valid_rect, + }; + + picture_cache.push(target); + } + _ => { + unreachable!() + } + } + } + } + + color.build( + ctx, + gpu_cache, + render_tasks, + deferred_resolves, + saved_color, + prim_headers, + transforms, + z_generator, + composite_state, + ); + alpha.build( + ctx, + gpu_cache, + render_tasks, + deferred_resolves, + saved_alpha, + prim_headers, + transforms, + z_generator, + composite_state, + ); + } + } +} + +/// A rendering-oriented representation of the frame built by the render backend +/// and presented to the renderer. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct Frame { + /// The origin on content produced by the render tasks. + pub content_origin: DeviceIntPoint, + /// The rectangle to show the frame in, on screen. + pub device_rect: DeviceIntRect, + pub layer: DocumentLayer, + pub passes: Vec, + #[cfg_attr(any(feature = "capture", feature = "replay"), serde(default = "FrameProfileCounters::new", skip))] + pub profile_counters: FrameProfileCounters, + + pub transform_palette: Vec, + pub render_tasks: RenderTaskGraph, + pub prim_headers: PrimitiveHeaders, + + /// The GPU cache frame that the contents of Self depend on + pub gpu_cache_frame_id: FrameId, + + /// List of textures that we don't know about yet + /// from the backend thread. The render thread + /// will use a callback to resolve these and + /// patch the data structures. + pub deferred_resolves: Vec, + + /// True if this frame contains any render tasks + /// that write to the texture cache. + pub has_texture_cache_tasks: bool, + + /// True if this frame has been drawn by the + /// renderer. + pub has_been_rendered: bool, + + /// Dirty regions recorded when generating this frame. Empty when not in + /// testing. + #[cfg_attr(feature = "serde", serde(skip))] + pub recorded_dirty_regions: Vec, + + /// Debugging information to overlay for this frame. + pub debug_items: Vec, + + /// Contains picture cache tiles, and associated information. + /// Used by the renderer to composite tiles into the framebuffer, + /// or hand them off to an OS compositor. + pub composite_state: CompositeState, +} + +impl Frame { + // This frame must be flushed if it writes to the + // texture cache, and hasn't been drawn yet. + pub fn must_be_drawn(&self) -> bool { + self.has_texture_cache_tasks && !self.has_been_rendered + } + + // Returns true if this frame doesn't alter what is on screen currently. + pub fn is_nop(&self) -> bool { + // If picture caching is disabled, we don't have enough information + // to know if this frame is a nop, so it gets drawn unconditionally. + if !self.composite_state.picture_caching_is_enabled { + return false; + } + + // When picture caching is enabled, the first (main framebuffer) pass + // consists of compositing tiles only (whether via the simple compositor + // or the native OS compositor). If there are no other passes, that + // implies that none of the picture cache tiles were updated, and thus + // the frame content must be exactly the same as last frame. If this is + // true, drawing this frame is a no-op and can be skipped. + + if self.passes.len() > 1 { + return false; + } + + true + } +} diff --git a/third_party/webrender/webrender/src/freelist.rs b/third_party/webrender/webrender/src/freelist.rs new file mode 100644 index 00000000000..5ca196191cc --- /dev/null +++ b/third_party/webrender/webrender/src/freelist.rs @@ -0,0 +1,263 @@ +/* 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/. */ + +//! A generic backing store for caches. +//! +//! `FreeList` is a simple vector-backed data structure where each entry in the +//! vector contains an Option. It maintains an index-based (rather than +//! pointer-based) free list to efficiently locate the next unused entry. If all +//! entries are occupied, insertion appends a new element to the vector. +//! +//! It also supports both strong and weak handle semantics. There is exactly one +//! (non-Clonable) strong handle per occupied entry, which must be passed by +//! value into `free()` to release an entry. Strong handles can produce an +//! unlimited number of (Clonable) weak handles, which are used to perform +//! lookups which may fail of the entry has been freed. A per-entry epoch ensures +//! that weak handle lookups properly fail even if the entry has been freed and +//! reused. +//! +//! TODO(gw): Add an occupied list head, for fast iteration of the occupied list +//! to implement retain() style functionality. + +use std::{fmt, u32}; +use std::marker::PhantomData; + +#[derive(Debug, Copy, Clone, MallocSizeOf, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct Epoch(u32); + +impl Epoch { + /// Mints a new epoch. + /// + /// We start at 1 so that 0 is always an invalid epoch. + fn new() -> Self { + Epoch(1) + } + + /// Returns an always-invalid epoch. + fn invalid() -> Self { + Epoch(0) + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct FreeListHandle { + index: u32, + epoch: Epoch, + _marker: PhantomData, +} + +/// More-compact textual representation for debug logging. +impl fmt::Debug for FreeListHandle { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("StrongHandle") + .field("index", &self.index) + .field("epoch", &self.epoch.0) + .finish() + } +} + +impl FreeListHandle { + pub fn weak(&self) -> WeakFreeListHandle { + WeakFreeListHandle { + index: self.index, + epoch: self.epoch, + _marker: PhantomData, + } + } + + pub fn invalid() -> Self { + Self { + index: 0, + epoch: Epoch::invalid(), + _marker: PhantomData, + } + } + + /// Returns true if this handle and the supplied weak handle reference + /// the same underlying location in the freelist. + pub fn matches(&self, weak_handle: &WeakFreeListHandle) -> bool { + self.index == weak_handle.index && + self.epoch == weak_handle.epoch + } +} + +impl Clone for WeakFreeListHandle { + fn clone(&self) -> Self { + WeakFreeListHandle { + index: self.index, + epoch: self.epoch, + _marker: PhantomData, + } + } +} + +impl PartialEq for WeakFreeListHandle { + fn eq(&self, other: &Self) -> bool { + self.index == other.index && self.epoch == other.epoch + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct WeakFreeListHandle { + index: u32, + epoch: Epoch, + _marker: PhantomData, +} + +/// More-compact textual representation for debug logging. +impl fmt::Debug for WeakFreeListHandle { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("WeakHandle") + .field("index", &self.index) + .field("epoch", &self.epoch.0) + .finish() + } +} + +impl WeakFreeListHandle { + /// Returns an always-invalid handle. + pub fn invalid() -> Self { + Self { + index: 0, + epoch: Epoch::invalid(), + _marker: PhantomData, + } + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct Slot { + next: Option, + epoch: Epoch, + value: Option, +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct FreeList { + slots: Vec>, + free_list_head: Option, + active_count: usize, + _marker: PhantomData, +} + +impl FreeList { + /// Mints a new `FreeList` with no entries. + /// + /// Triggers a 1-entry allocation. + pub fn new() -> Self { + // We guarantee that we never have zero entries by starting with one + // free entry. This allows WeakFreeListHandle::invalid() to work + // without adding any additional branches. + let first_slot = Slot { + next: None, + epoch: Epoch::new(), + value: None, + }; + FreeList { + slots: vec![first_slot], + free_list_head: Some(0), + active_count: 0, + _marker: PhantomData, + } + } + + pub fn clear(&mut self) { + self.slots.truncate(1); + self.slots[0] = Slot { + next: None, + epoch: Epoch::new(), + value: None, + }; + self.free_list_head = Some(0); + self.active_count = 0; + } + + #[allow(dead_code)] + pub fn get(&self, id: &FreeListHandle) -> &T { + self.slots[id.index as usize].value.as_ref().unwrap() + } + + #[allow(dead_code)] + pub fn get_mut(&mut self, id: &FreeListHandle) -> &mut T { + self.slots[id.index as usize].value.as_mut().unwrap() + } + + pub fn get_opt(&self, id: &WeakFreeListHandle) -> Option<&T> { + let slot = &self.slots[id.index as usize]; + if slot.epoch == id.epoch { + slot.value.as_ref() + } else { + None + } + } + + pub fn get_opt_mut(&mut self, id: &WeakFreeListHandle) -> Option<&mut T> { + let slot = &mut self.slots[id.index as usize]; + if slot.epoch == id.epoch { + slot.value.as_mut() + } else { + None + } + } + + pub fn insert(&mut self, item: T) -> FreeListHandle { + self.active_count += 1; + + match self.free_list_head { + Some(free_index) => { + let slot = &mut self.slots[free_index as usize]; + + // Remove from free list. + self.free_list_head = slot.next; + slot.next = None; + slot.value = Some(item); + + FreeListHandle { + index: free_index, + epoch: slot.epoch, + _marker: PhantomData, + } + } + None => { + let index = self.slots.len() as u32; + let epoch = Epoch::new(); + + self.slots.push(Slot { + next: None, + epoch, + value: Some(item), + }); + + FreeListHandle { + index, + epoch, + _marker: PhantomData, + } + } + } + } + + pub fn free(&mut self, id: FreeListHandle) -> T { + self.active_count -= 1; + let slot = &mut self.slots[id.index as usize]; + slot.next = self.free_list_head; + slot.epoch = Epoch(slot.epoch.0 + 1); + self.free_list_head = Some(id.index); + slot.value.take().unwrap() + } + + #[allow(dead_code)] + pub fn len(&self) -> usize { + self.active_count + } +} diff --git a/third_party/webrender/webrender/src/gamma_lut.rs b/third_party/webrender/webrender/src/gamma_lut.rs new file mode 100644 index 00000000000..b5df7029e60 --- /dev/null +++ b/third_party/webrender/webrender/src/gamma_lut.rs @@ -0,0 +1,414 @@ +/* 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/. */ + +/*! +Gamma correction lookup tables. + +This is a port of Skia gamma LUT logic into Rust, used by WebRender. +*/ +//#![warn(missing_docs)] //TODO +#![allow(dead_code)] + +use api::ColorU; +use std::cmp::max; + +/// Color space responsible for converting between lumas and luminances. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum LuminanceColorSpace { + /// Linear space - no conversion involved. + Linear, + /// Simple gamma space - uses the `luminance ^ gamma` function. + Gamma(f32), + /// Srgb space. + Srgb, +} + +impl LuminanceColorSpace { + pub fn new(gamma: f32) -> LuminanceColorSpace { + if gamma == 1.0 { + LuminanceColorSpace::Linear + } else if gamma == 0.0 { + LuminanceColorSpace::Srgb + } else { + LuminanceColorSpace::Gamma(gamma) + } + } + + pub fn to_luma(&self, luminance: f32) -> f32 { + match *self { + LuminanceColorSpace::Linear => luminance, + LuminanceColorSpace::Gamma(gamma) => luminance.powf(gamma), + LuminanceColorSpace::Srgb => { + //The magic numbers are derived from the sRGB specification. + //See http://www.color.org/chardata/rgb/srgb.xalter . + if luminance <= 0.04045 { + luminance / 12.92 + } else { + ((luminance + 0.055) / 1.055).powf(2.4) + } + } + } + } + + pub fn from_luma(&self, luma: f32) -> f32 { + match *self { + LuminanceColorSpace::Linear => luma, + LuminanceColorSpace::Gamma(gamma) => luma.powf(1. / gamma), + LuminanceColorSpace::Srgb => { + //The magic numbers are derived from the sRGB specification. + //See http://www.color.org/chardata/rgb/srgb.xalter . + if luma <= 0.0031308 { + luma * 12.92 + } else { + 1.055 * luma.powf(1./2.4) - 0.055 + } + } + } + } +} + +//TODO: tests +fn round_to_u8(x : f32) -> u8 { + let v = (x + 0.5).floor() as i32; + assert!(0 <= v && v < 0x100); + v as u8 +} + +//TODO: tests +/* + * Scales base <= 2^N-1 to 2^8-1 + * @param N [1, 8] the number of bits used by base. + * @param base the number to be scaled to [0, 255]. + */ +fn scale255(n: u8, mut base: u8) -> u8 { + base <<= 8 - n; + let mut lum = base; + let mut i = n; + + while i < 8 { + lum |= base >> i; + i += n; + } + + lum +} + +// Computes the luminance from the given r, g, and b in accordance with +// SK_LUM_COEFF_X. For correct results, r, g, and b should be in linear space. +fn compute_luminance(r: u8, g: u8, b: u8) -> u8 { + // The following is + // r * SK_LUM_COEFF_R + g * SK_LUM_COEFF_G + b * SK_LUM_COEFF_B + // with SK_LUM_COEFF_X in 1.8 fixed point (rounding adjusted to sum to 256). + let val: u32 = r as u32 * 54 + g as u32 * 183 + b as u32 * 19; + assert!(val < 0x10000); + (val >> 8) as u8 +} + +// Skia uses 3 bits per channel for luminance. +const LUM_BITS: u8 = 3; +// Mask of the highest used bits. +const LUM_MASK: u8 = ((1 << LUM_BITS) - 1) << (8 - LUM_BITS); + +pub trait ColorLut { + fn quantize(&self) -> ColorU; + fn quantized_floor(&self) -> ColorU; + fn quantized_ceil(&self) -> ColorU; + fn luminance(&self) -> u8; + fn luminance_color(&self) -> ColorU; +} + +impl ColorLut for ColorU { + // Compute a canonical color that is equivalent to the input color + // for preblend table lookups. The alpha channel is never used for + // preblending, so overwrite it with opaque. + fn quantize(&self) -> ColorU { + ColorU::new( + scale255(LUM_BITS, self.r >> (8 - LUM_BITS)), + scale255(LUM_BITS, self.g >> (8 - LUM_BITS)), + scale255(LUM_BITS, self.b >> (8 - LUM_BITS)), + 255, + ) + } + + // Quantize to the smallest value that yields the same table index. + fn quantized_floor(&self) -> ColorU { + ColorU::new( + self.r & LUM_MASK, + self.g & LUM_MASK, + self.b & LUM_MASK, + 255, + ) + } + + // Quantize to the largest value that yields the same table index. + fn quantized_ceil(&self) -> ColorU { + ColorU::new( + self.r | !LUM_MASK, + self.g | !LUM_MASK, + self.b | !LUM_MASK, + 255, + ) + } + + // Compute a luminance value suitable for grayscale preblend table + // lookups. + fn luminance(&self) -> u8 { + compute_luminance(self.r, self.g, self.b) + } + + // Make a grayscale color from the computed luminance. + fn luminance_color(&self) -> ColorU { + let lum = self.luminance(); + ColorU::new(lum, lum, lum, self.a) + } +} + +// This will invert the gamma applied by CoreGraphics, +// so we can get linear values. +// CoreGraphics obscurely defaults to 2.0 as the smoothing gamma value. +// The color space used does not appear to affect this choice. +#[cfg(target_os="macos")] +fn get_inverse_gamma_table_coregraphics_smoothing() -> [u8; 256] { + let mut table = [0u8; 256]; + + for (i, v) in table.iter_mut().enumerate() { + let x = i as f32 / 255.0; + *v = round_to_u8(x * x * 255.0); + } + + table +} + +// A value of 0.5 for SK_GAMMA_CONTRAST appears to be a good compromise. +// With lower values small text appears washed out (though correctly so). +// With higher values lcd fringing is worse and the smoothing effect of +// partial coverage is diminished. +fn apply_contrast(srca: f32, contrast: f32) -> f32 { + srca + ((1.0 - srca) * contrast * srca) +} + +// The approach here is not necessarily the one with the lowest error +// See https://bel.fi/alankila/lcd/alpcor.html for a similar kind of thing +// that just search for the adjusted alpha value +pub fn build_gamma_correcting_lut(table: &mut [u8; 256], src: u8, contrast: f32, + src_space: LuminanceColorSpace, + dst_convert: LuminanceColorSpace) { + + let src = src as f32 / 255.0; + let lin_src = src_space.to_luma(src); + // Guess at the dst. The perceptual inverse provides smaller visual + // discontinuities when slight changes to desaturated colors cause a channel + // to map to a different correcting lut with neighboring srcI. + // See https://code.google.com/p/chromium/issues/detail?id=141425#c59 . + let dst = 1.0 - src; + let lin_dst = dst_convert.to_luma(dst); + + // Contrast value tapers off to 0 as the src luminance becomes white + let adjusted_contrast = contrast * lin_dst; + + // Remove discontinuity and instability when src is close to dst. + // The value 1/256 is arbitrary and appears to contain the instability. + if (src - dst).abs() < (1.0 / 256.0) { + let mut ii : f32 = 0.0; + for v in table.iter_mut() { + let raw_srca = ii / 255.0; + let srca = apply_contrast(raw_srca, adjusted_contrast); + + *v = round_to_u8(255.0 * srca); + ii += 1.0; + } + } else { + // Avoid slow int to float conversion. + let mut ii : f32 = 0.0; + for v in table.iter_mut() { + // 'raw_srca += 1.0f / 255.0f' and even + // 'raw_srca = i * (1.0f / 255.0f)' can add up to more than 1.0f. + // When this happens the table[255] == 0x0 instead of 0xff. + // See http://code.google.com/p/chromium/issues/detail?id=146466 + let raw_srca = ii / 255.0; + let srca = apply_contrast(raw_srca, adjusted_contrast); + assert!(srca <= 1.0); + let dsta = 1.0 - srca; + + // Calculate the output we want. + let lin_out = lin_src * srca + dsta * lin_dst; + assert!(lin_out <= 1.0); + let out = dst_convert.from_luma(lin_out); + + // Undo what the blit blend will do. + // i.e. given the formula for OVER: out = src * result + (1 - result) * dst + // solving for result gives: + let result = (out - dst) / (src - dst); + + *v = round_to_u8(255.0 * result); + debug!("Setting {:?} to {:?}", ii as u8, *v); + + ii += 1.0; + } + } +} + +pub struct GammaLut { + tables: [[u8; 256]; 1 << LUM_BITS], + #[cfg(target_os="macos")] + cg_inverse_gamma: [u8; 256], +} + +impl GammaLut { + // Skia actually makes 9 gamma tables, then based on the luminance color, + // fetches the RGB gamma table for that color. + fn generate_tables(&mut self, contrast: f32, paint_gamma: f32, device_gamma: f32) { + let paint_color_space = LuminanceColorSpace::new(paint_gamma); + let device_color_space = LuminanceColorSpace::new(device_gamma); + + for (i, entry) in self.tables.iter_mut().enumerate() { + let luminance = scale255(LUM_BITS, i as u8); + build_gamma_correcting_lut(entry, + luminance, + contrast, + paint_color_space, + device_color_space); + } + } + + pub fn table_count(&self) -> usize { + self.tables.len() + } + + pub fn get_table(&self, color: u8) -> &[u8; 256] { + &self.tables[(color >> (8 - LUM_BITS)) as usize] + } + + pub fn new(contrast: f32, paint_gamma: f32, device_gamma: f32) -> GammaLut { + #[cfg(target_os="macos")] + let mut table = GammaLut { + tables: [[0; 256]; 1 << LUM_BITS], + cg_inverse_gamma: get_inverse_gamma_table_coregraphics_smoothing(), + }; + #[cfg(not(target_os="macos"))] + let mut table = GammaLut { + tables: [[0; 256]; 1 << LUM_BITS], + }; + + table.generate_tables(contrast, paint_gamma, device_gamma); + + table + } + + // Assumes pixels are in BGRA format. Assumes pixel values are in linear space already. + pub fn preblend(&self, pixels: &mut [u8], color: ColorU) { + let table_r = self.get_table(color.r); + let table_g = self.get_table(color.g); + let table_b = self.get_table(color.b); + + for pixel in pixels.chunks_mut(4) { + let (b, g, r) = (table_b[pixel[0] as usize], table_g[pixel[1] as usize], table_r[pixel[2] as usize]); + pixel[0] = b; + pixel[1] = g; + pixel[2] = r; + pixel[3] = max(max(b, g), r); + } + } + + // Assumes pixels are in BGRA format. Assumes pixel values are in linear space already. + pub fn preblend_scaled(&self, pixels: &mut [u8], color: ColorU, percent: u8) { + if percent >= 100 { + self.preblend(pixels, color); + return; + } + + let table_r = self.get_table(color.r); + let table_g = self.get_table(color.g); + let table_b = self.get_table(color.b); + let scale = (percent as i32 * 256) / 100; + + for pixel in pixels.chunks_mut(4) { + let (mut b, g, mut r) = ( + table_b[pixel[0] as usize] as i32, + table_g[pixel[1] as usize] as i32, + table_r[pixel[2] as usize] as i32, + ); + b = g + (((b - g) * scale) >> 8); + r = g + (((r - g) * scale) >> 8); + pixel[0] = b as u8; + pixel[1] = g as u8; + pixel[2] = r as u8; + pixel[3] = max(max(b, g), r) as u8; + } + } + + #[cfg(target_os="macos")] + pub fn coregraphics_convert_to_linear(&self, pixels: &mut [u8]) { + for pixel in pixels.chunks_mut(4) { + pixel[0] = self.cg_inverse_gamma[pixel[0] as usize]; + pixel[1] = self.cg_inverse_gamma[pixel[1] as usize]; + pixel[2] = self.cg_inverse_gamma[pixel[2] as usize]; + } + } + + // Assumes pixels are in BGRA format. Assumes pixel values are in linear space already. + pub fn preblend_grayscale(&self, pixels: &mut [u8], color: ColorU) { + let table_g = self.get_table(color.g); + + for pixel in pixels.chunks_mut(4) { + let luminance = compute_luminance(pixel[2], pixel[1], pixel[0]); + let alpha = table_g[luminance as usize]; + pixel[0] = alpha; + pixel[1] = alpha; + pixel[2] = alpha; + pixel[3] = alpha; + } + } + +} // end impl GammaLut + +#[cfg(test)] +mod tests { + use super::*; + + fn over(dst: u32, src: u32, alpha: u32) -> u32 { + (src * alpha + dst * (255 - alpha))/255 + } + + fn overf(dst: f32, src: f32, alpha: f32) -> f32 { + ((src * alpha + dst * (255. - alpha))/255.) as f32 + } + + + fn absdiff(a: u32, b: u32) -> u32 { + if a < b { b - a } else { a - b } + } + + #[test] + fn gamma() { + let mut table = [0u8; 256]; + let g = 2.0; + let space = LuminanceColorSpace::Gamma(g); + let mut src : u32 = 131; + while src < 256 { + build_gamma_correcting_lut(&mut table, src as u8, 0., space, space); + let mut max_diff = 0; + let mut dst = 0; + while dst < 256 { + for alpha in 0u32..256 { + let preblend = table[alpha as usize]; + let lin_dst = (dst as f32 / 255.).powf(g) * 255.; + let lin_src = (src as f32 / 255.).powf(g) * 255.; + + let preblend_result = over(dst, src, preblend as u32); + let true_result = ((overf(lin_dst, lin_src, alpha as f32) / 255.).powf(1. / g) * 255.) as u32; + let diff = absdiff(preblend_result, true_result); + //println!("{} -- {} {} = {}", alpha, preblend_result, true_result, diff); + max_diff = max(max_diff, diff); + } + + //println!("{} {} max {}", src, dst, max_diff); + assert!(max_diff <= 33); + dst += 1; + + } + src += 1; + } + } +} // end mod diff --git a/third_party/webrender/webrender/src/glyph_cache.rs b/third_party/webrender/webrender/src/glyph_cache.rs new file mode 100644 index 00000000000..b0b9ecf65c7 --- /dev/null +++ b/third_party/webrender/webrender/src/glyph_cache.rs @@ -0,0 +1,284 @@ +/* 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 crate::glyph_rasterizer::{FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer}; +use crate::internal_types::FastHashMap; +use crate::render_backend::{FrameId, FrameStamp}; +use crate::render_task_cache::RenderTaskCache; +use crate::resource_cache::ResourceClassCache; +use std::sync::Arc; +use crate::texture_cache::{EvictionNotice, TextureCache}; +use crate::texture_cache::TextureCacheHandle; + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Clone, Debug)] +pub struct CachedGlyphInfo { + pub format: GlyphFormat, + pub texture_cache_handle: TextureCacheHandle, +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum GlyphCacheEntry { + // A glyph that has been successfully rasterized. + Cached(CachedGlyphInfo), + // A glyph that should not be rasterized (i.e. a space). + Blank, + // A glyph that has been submitted to the font backend for rasterization, + // but is still pending a result. + #[allow(dead_code)] + Pending, +} + +impl GlyphCacheEntry { + fn get_allocated_size(&self, texture_cache: &TextureCache, _: &RenderTaskCache) + -> Option { + match *self { + GlyphCacheEntry::Cached(ref glyph) => { + texture_cache.get_allocated_size(&glyph.texture_cache_handle) + } + GlyphCacheEntry::Pending | GlyphCacheEntry::Blank => Some(0), + } + } + + fn is_recently_used(&self, texture_cache: &mut TextureCache) -> bool { + if let GlyphCacheEntry::Cached(ref glyph) = *self { + texture_cache.is_recently_used(&glyph.texture_cache_handle, 1) + } else { + false + } + } +} + +#[allow(dead_code)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Clone)] +pub enum CachedGlyphData { + Memory(Arc>), + Gpu, +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Default)] +pub struct GlyphKeyCacheInfo { + eviction_notice: EvictionNotice, + last_frame_used: FrameId, + bytes_used: usize, +} + +pub type GlyphKeyCache = ResourceClassCache; + +impl GlyphKeyCache { + const DIRTY: usize = !0; + + pub fn eviction_notice(&self) -> &EvictionNotice { + &self.user_data.eviction_notice + } + + fn is_recently_used(&self, current_frame: FrameId) -> bool { + self.user_data.last_frame_used + 1 >= current_frame + } + + fn clear_glyphs(&mut self) -> usize { + let pruned = self.user_data.bytes_used; + self.clear(); + self.user_data.bytes_used = 0; + pruned + } + + fn prune_glyphs( + &mut self, + skip_recent: bool, + excess_bytes_used: usize, + texture_cache: &mut TextureCache, + render_task_cache: &RenderTaskCache, + ) -> usize { + let mut pruned = 0; + self.retain(|_, entry| { + if pruned <= excess_bytes_used && + (!skip_recent || !entry.is_recently_used(texture_cache)) { + match entry.get_allocated_size(texture_cache, render_task_cache) { + Some(size) => { + pruned += size; + false + } + None => true, + } + } else { + true + } + }); + self.user_data.bytes_used -= pruned; + pruned + } + + pub fn add_glyph(&mut self, key: GlyphKey, value: GlyphCacheEntry) { + self.insert(key, value); + self.user_data.bytes_used = Self::DIRTY; + } + + fn clear_evicted( + &mut self, + texture_cache: &TextureCache, + render_task_cache: &RenderTaskCache, + ) { + if self.eviction_notice().check() || self.user_data.bytes_used == Self::DIRTY { + // If there are evictions, filter out any glyphs evicted from the + // texture cache from the glyph key cache. + let mut usage = 0; + self.retain(|_, entry| { + let size = entry.get_allocated_size(texture_cache, render_task_cache); + usage += size.unwrap_or(0); + size.is_some() + }); + self.user_data.bytes_used = usage; + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct GlyphCache { + glyph_key_caches: FastHashMap, + current_frame: FrameId, + bytes_used: usize, + max_bytes_used: usize, +} + +impl GlyphCache { + /// The default space usage threshold, in bytes, after which to start pruning away old fonts. + pub const DEFAULT_MAX_BYTES_USED: usize = 6 * 1024 * 1024; + + pub fn new(max_bytes_used: usize) -> Self { + GlyphCache { + glyph_key_caches: FastHashMap::default(), + current_frame: Default::default(), + bytes_used: 0, + max_bytes_used, + } + } + + pub fn get_glyph_key_cache_for_font_mut(&mut self, font: FontInstance) -> &mut GlyphKeyCache { + let cache = self.glyph_key_caches + .entry(font) + .or_insert_with(GlyphKeyCache::new); + cache.user_data.last_frame_used = self.current_frame; + cache + } + + pub fn get_glyph_key_cache_for_font(&self, font: &FontInstance) -> &GlyphKeyCache { + self.glyph_key_caches + .get(font) + .expect("BUG: Unable to find glyph key cache!") + } + + pub fn clear(&mut self) { + for (_, glyph_key_cache) in &mut self.glyph_key_caches { + glyph_key_cache.clear() + } + // We use this in on_memory_pressure where retaining memory allocations + // isn't desirable, so we completely remove the hash map instead of clearing it. + self.glyph_key_caches = FastHashMap::default(); + } + + pub fn clear_fonts(&mut self, key_fun: F) + where + for<'r> F: Fn(&'r &FontInstance) -> bool, + { + self.glyph_key_caches.retain(|k, cache| { + let should_clear = key_fun(&k); + if !should_clear { + return true; + } + + cache.clear_glyphs(); + false + }) + } + + /// Clear out evicted entries from glyph key caches. + fn clear_evicted( + &mut self, + texture_cache: &TextureCache, + render_task_cache: &RenderTaskCache, + ) { + let mut usage = 0; + for cache in self.glyph_key_caches.values_mut() { + // Scan for any glyph key caches that have evictions. + cache.clear_evicted(texture_cache, render_task_cache); + usage += cache.user_data.bytes_used; + } + self.bytes_used = usage; + } + + /// If possible, remove entirely any empty glyph key caches. + fn clear_empty_caches(&mut self, glyph_rasterizer: &mut GlyphRasterizer) { + self.glyph_key_caches.retain(|key, cache| { + // Discard the glyph key cache if it has no valid glyphs. + if cache.is_empty() { + glyph_rasterizer.delete_font_instance(key); + false + } else { + true + } + }); + } + + /// Check the total space usage of the glyph cache. If it exceeds the maximum usage threshold, + /// then start clearing the oldest glyphs until below the threshold. + fn prune_excess_usage( + &mut self, + texture_cache: &mut TextureCache, + render_task_cache: &RenderTaskCache, + ) { + if self.bytes_used < self.max_bytes_used { + return; + } + // Usage is above the threshold. Get a last-recently-used ordered list of caches to clear. + let mut caches: Vec<_> = self.glyph_key_caches.values_mut().collect(); + caches.sort_unstable_by(|a, b| { + a.user_data.last_frame_used.cmp(&b.user_data.last_frame_used) + }); + // Clear out the oldest caches until below the threshold. + for cache in caches { + if self.bytes_used < self.max_bytes_used { + break; + } + let recent = cache.is_recently_used(self.current_frame); + let excess = self.bytes_used - self.max_bytes_used; + if !recent && excess >= cache.user_data.bytes_used { + // If the excess is greater than the cache's size, just clear the whole thing. + self.bytes_used -= cache.clear_glyphs(); + } else { + // Otherwise, just clear as little of the cache as needed to remove the excess + // and avoid rematerialization costs. + self.bytes_used -= cache.prune_glyphs( + recent, + excess, + texture_cache, + render_task_cache, + ); + } + } + } + + pub fn begin_frame( + &mut self, + stamp: FrameStamp, + texture_cache: &mut TextureCache, + render_task_cache: &RenderTaskCache, + glyph_rasterizer: &mut GlyphRasterizer, + ) { + profile_scope!("begin_frame"); + self.current_frame = stamp.frame_id(); + self.clear_evicted(texture_cache, render_task_cache); + self.prune_excess_usage(texture_cache, render_task_cache); + // Clearing evicted glyphs and pruning excess usage might have produced empty caches, + // so get rid of them if possible. + self.clear_empty_caches(glyph_rasterizer); + } +} diff --git a/third_party/webrender/webrender/src/glyph_rasterizer/mod.rs b/third_party/webrender/webrender/src/glyph_rasterizer/mod.rs new file mode 100644 index 00000000000..0a60cc77d6c --- /dev/null +++ b/third_party/webrender/webrender/src/glyph_rasterizer/mod.rs @@ -0,0 +1,1193 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{FontInstanceFlags, FontSize, BaseFontInstance}; +use api::{FontKey, FontRenderMode, FontTemplate}; +use api::{ColorU, GlyphIndex, GlyphDimensions, SyntheticItalics}; +use api::units::*; +use api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat, DirtyRect}; +use crate::internal_types::ResourceCacheError; +use crate::platform::font::FontContext; +use crate::device::TextureFilter; +use crate::gpu_types::UvRectKind; +use crate::glyph_cache::{GlyphCache, CachedGlyphInfo, GlyphCacheEntry}; +use crate::resource_cache::CachedImageData; +use crate::texture_cache::{TextureCache, TextureCacheHandle, Eviction}; +use crate::gpu_cache::GpuCache; +use crate::render_task_graph::RenderTaskGraph; +use crate::render_task_cache::RenderTaskCache; +use crate::profiler::TextureCacheProfileCounters; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use rayon::ThreadPool; +use rayon::prelude::*; +use euclid::approxeq::ApproxEq; +use euclid::size2; +use std::cmp; +use std::cell::Cell; +use std::hash::{Hash, Hasher}; +use std::mem; +use std::ops::Deref; +use std::sync::{Arc, Condvar, Mutex, MutexGuard}; +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::sync::atomic::{AtomicBool, Ordering}; + +pub static GLYPH_FLASHING: AtomicBool = AtomicBool::new(false); + +impl FontContexts { + /// Get access to the font context associated to the current thread. + pub fn lock_current_context(&self) -> MutexGuard { + let id = self.current_worker_id(); + self.lock_context(id) + } + + pub(in super) fn current_worker_id(&self) -> Option { + self.workers.current_thread_index() + } +} + +thread_local! { + pub static SEED: Cell = Cell::new(0); +} + +// super simple random to avoid dependency on rand +fn random() -> u32 { + SEED.with(|seed| { + seed.set(seed.get().wrapping_mul(22695477).wrapping_add(1)); + seed.get() + }) +} + +impl GlyphRasterizer { + pub fn request_glyphs( + &mut self, + glyph_cache: &mut GlyphCache, + font: FontInstance, + glyph_keys: &[GlyphKey], + texture_cache: &mut TextureCache, + gpu_cache: &mut GpuCache, + _: &mut RenderTaskCache, + _: &mut RenderTaskGraph, + ) { + assert!( + self.font_contexts + .lock_shared_context() + .has_font(&font.font_key) + ); + let mut new_glyphs = Vec::new(); + + let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(font.clone()); + + // select glyphs that have not been requested yet. + for key in glyph_keys { + if let Some(entry) = glyph_key_cache.try_get(key) { + match entry { + GlyphCacheEntry::Cached(ref glyph) => { + // Skip the glyph if it is already has a valid texture cache handle. + if !texture_cache.request(&glyph.texture_cache_handle, gpu_cache) { + continue; + } + // This case gets hit when we already rasterized the glyph, but the + // glyph has been evicted from the texture cache. Just force it to + // pending so it gets rematerialized. + } + // Otherwise, skip the entry if it is blank or pending. + GlyphCacheEntry::Blank | GlyphCacheEntry::Pending => continue, + } + } + new_glyphs.push(key.clone()); + glyph_key_cache.add_glyph(key.clone(), GlyphCacheEntry::Pending); + } + + if new_glyphs.is_empty() { + return; + } + + self.pending_glyphs += 1; + + self.request_glyphs_from_backend(font, new_glyphs); + } + + pub fn enable_multithreading(&mut self, enable: bool) { + self.enable_multithreading = enable; + } + + pub(in super) fn request_glyphs_from_backend(&mut self, font: FontInstance, glyphs: Vec) { + let font_contexts = Arc::clone(&self.font_contexts); + let glyph_tx = self.glyph_tx.clone(); + + fn process_glyph(key: &GlyphKey, font_contexts: &FontContexts, font: &FontInstance) -> GlyphRasterJob { + profile_scope!("glyph-raster"); + let mut context = font_contexts.lock_current_context(); + let mut job = GlyphRasterJob { + key: key.clone(), + result: context.rasterize_glyph(&font, key), + }; + + 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 + ); + + // a quick-and-dirty monochrome over + fn over(dst: u8, src: u8) -> u8 { + let a = src as u32; + let a = 256 - a; + let dst = ((dst as u32 * a) >> 8) as u8; + src + dst + } + + if GLYPH_FLASHING.load(Ordering::Relaxed) { + let color = (random() & 0xff) as u8; + for i in &mut glyph.bytes { + *i = over(*i, color); + } + } + + 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 + } + + // if the number of glyphs is small, do it inline to avoid the threading overhead; + // send the result into glyph_tx so downstream code can't tell the difference. + if !self.enable_multithreading || glyphs.len() < 8 { + let jobs = glyphs.iter() + .map(|key: &GlyphKey| process_glyph(key, &font_contexts, &font)) + .collect(); + glyph_tx.send(GlyphRasterJobs { font, jobs }).unwrap(); + } else { + // spawn an async task to get off of the render backend thread as early as + // possible and in that task use rayon's fork join dispatch to rasterize the + // glyphs in the thread pool. + profile_scope!("spawning process_glyph jobs"); + self.workers.spawn(move || { + let jobs = glyphs + .par_iter() + .map(|key: &GlyphKey| process_glyph(key, &font_contexts, &font)) + .collect(); + + glyph_tx.send(GlyphRasterJobs { font, jobs }).unwrap(); + }); + } + } + + pub fn resolve_glyphs( + &mut self, + glyph_cache: &mut GlyphCache, + texture_cache: &mut TextureCache, + gpu_cache: &mut GpuCache, + _: &mut RenderTaskCache, + _: &mut RenderTaskGraph, + _: &mut TextureCacheProfileCounters, + ) { + profile_scope!("resolve_glyphs"); + // Pull rasterized glyphs from the queue and update the caches. + while self.pending_glyphs > 0 { + self.pending_glyphs -= 1; + + // TODO: rather than blocking until all pending glyphs are available + // we could try_recv and steal work from the thread pool to take advantage + // of the fact that this thread is alive and we avoid the added latency + // of blocking it. + + let GlyphRasterJobs { font, mut jobs } = { + profile_scope!("blocking wait on glyph_rx"); + self.glyph_rx + .recv() + .expect("BUG: Should be glyphs pending!") + }; + + // Ensure that the glyphs are always processed in the same + // order for a given text run (since iterating a hash set doesn't + // guarantee order). This can show up as very small float inaccuracy + // differences in rasterizers due to the different coordinates + // that text runs get associated with by the texture cache allocator. + jobs.sort_by(|a, b| a.key.cmp(&b.key)); + + let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(font); + + 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) => { + 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: FORMAT, + flags: ImageDescriptorFlags::empty(), + offset: 0, + }, + TextureFilter::Linear, + Some(CachedImageData::Raw(Arc::new(glyph.bytes))), + [glyph.left, -glyph.top, glyph.scale], + DirtyRect::All, + gpu_cache, + Some(glyph_key_cache.eviction_notice()), + UvRectKind::Rect, + Eviction::Auto, + ); + GlyphCacheEntry::Cached(CachedGlyphInfo { + texture_cache_handle, + format: glyph.format, + }) + } + }; + glyph_key_cache.insert(key, glyph_info); + } + } + + // Now that we are done with the critical path (rendering the glyphs), + // we can schedule removing the fonts if needed. + self.remove_dead_fonts(); + } +} + +#[allow(dead_code)] +pub const FORMAT: ImageFormat = ImageFormat::BGRA8; + +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct FontTransform { + pub scale_x: f32, + pub skew_x: f32, + pub skew_y: f32, + pub scale_y: f32, +} + +// Floats don't impl Hash/Eq/Ord... +impl Eq for FontTransform {} +impl Ord for FontTransform { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.partial_cmp(other).unwrap_or(cmp::Ordering::Equal) + } +} +impl Hash for FontTransform { + fn hash(&self, state: &mut H) { + // Note: this is inconsistent with the Eq impl for -0.0 (don't care). + self.scale_x.to_bits().hash(state); + self.skew_x.to_bits().hash(state); + self.skew_y.to_bits().hash(state); + self.scale_y.to_bits().hash(state); + } +} + +impl FontTransform { + const QUANTIZE_SCALE: f32 = 1024.0; + + pub fn new(scale_x: f32, skew_x: f32, skew_y: f32, scale_y: f32) -> Self { + FontTransform { scale_x, skew_x, skew_y, scale_y } + } + + pub fn identity() -> Self { + FontTransform::new(1.0, 0.0, 0.0, 1.0) + } + + #[allow(dead_code)] + pub fn is_identity(&self) -> bool { + *self == FontTransform::identity() + } + + pub fn quantize(&self) -> Self { + FontTransform::new( + (self.scale_x * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE, + (self.skew_x * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE, + (self.skew_y * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE, + (self.scale_y * Self::QUANTIZE_SCALE).round() / Self::QUANTIZE_SCALE, + ) + } + + #[allow(dead_code)] + pub fn determinant(&self) -> f64 { + self.scale_x as f64 * self.scale_y as f64 - self.skew_y as f64 * self.skew_x as f64 + } + + #[allow(dead_code)] + pub fn compute_scale(&self) -> Option<(f64, f64)> { + let det = self.determinant(); + if det != 0.0 { + let x_scale = (self.scale_x as f64).hypot(self.skew_y as f64); + let y_scale = det.abs() / x_scale; + Some((x_scale, y_scale)) + } else { + None + } + } + + #[allow(dead_code)] + pub fn pre_scale(&self, scale_x: f32, scale_y: f32) -> Self { + FontTransform::new( + self.scale_x * scale_x, + self.skew_x * scale_y, + self.skew_y * scale_x, + self.scale_y * scale_y, + ) + } + + #[allow(dead_code)] + pub fn scale(&self, scale: f32) -> Self { self.pre_scale(scale, scale) } + + #[allow(dead_code)] + pub fn invert_scale(&self, x_scale: f64, y_scale: f64) -> Self { + self.pre_scale(x_scale.recip() as f32, y_scale.recip() as f32) + } + + pub fn synthesize_italics(&self, angle: SyntheticItalics, size: f64, vertical: bool) -> (Self, (f64, f64)) { + let skew_factor = angle.to_skew(); + if vertical { + // origin delta to be applied so that we effectively skew around + // the middle rather than edge of the glyph + let (tx, ty) = (0.0, -size * 0.5 * skew_factor as f64); + (FontTransform::new( + self.scale_x + self.skew_x * skew_factor, + self.skew_x, + self.skew_y + self.scale_y * skew_factor, + self.scale_y, + ), (self.scale_x as f64 * tx + self.skew_x as f64 * ty, + self.skew_y as f64 * tx + self.scale_y as f64 * ty)) + } else { + (FontTransform::new( + self.scale_x, + self.skew_x - self.scale_x * skew_factor, + self.skew_y, + self.scale_y - self.skew_y * skew_factor, + ), (0.0, 0.0)) + } + } + + pub fn swap_xy(&self) -> Self { + FontTransform::new(self.skew_x, self.scale_x, self.scale_y, self.skew_y) + } + + pub fn flip_x(&self) -> Self { + FontTransform::new(-self.scale_x, self.skew_x, -self.skew_y, self.scale_y) + } + + pub fn flip_y(&self) -> Self { + FontTransform::new(self.scale_x, -self.skew_x, self.skew_y, -self.scale_y) + } + + pub fn transform(&self, point: &LayoutPoint) -> DevicePoint { + DevicePoint::new( + self.scale_x * point.x + self.skew_x * point.y, + self.skew_y * point.x + self.scale_y * point.y, + ) + } + + pub fn get_subpx_dir(&self) -> SubpixelDirection { + if self.skew_y.approx_eq(&0.0) { + // The X axis is not projected onto the Y axis + SubpixelDirection::Horizontal + } else if self.scale_x.approx_eq(&0.0) { + // The X axis has been swapped with the Y axis + SubpixelDirection::Vertical + } else { + // Use subpixel precision on all axes + SubpixelDirection::Mixed + } + } +} + +impl<'a> From<&'a LayoutToWorldTransform> for FontTransform { + fn from(xform: &'a LayoutToWorldTransform) -> Self { + FontTransform::new(xform.m11, xform.m21, xform.m12, xform.m22) + } +} + +// Some platforms (i.e. Windows) may have trouble rasterizing glyphs above this size. +// Ensure glyph sizes are reasonably limited to avoid that scenario. +pub const FONT_SIZE_LIMIT: f32 = 320.0; + +/// A mutable font instance description. +/// +/// Performance is sensitive to the size of this structure, so it should only contain +/// the fields that we need to modify from the original base font instance. +#[derive(Clone, PartialEq, Eq, Debug, Ord, PartialOrd)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct FontInstance { + pub base: Arc, + pub transform: FontTransform, + pub render_mode: FontRenderMode, + pub flags: FontInstanceFlags, + pub color: ColorU, + // The font size is in *device/raster* pixels, not logical pixels. + // It is stored as an f32 since we need sub-pixel sizes. + pub size: FontSize, +} + +impl Hash for FontInstance { + fn hash(&self, state: &mut H) { + // Hash only the base instance's key to avoid the cost of hashing + // the rest. + self.base.instance_key.hash(state); + self.transform.hash(state); + self.render_mode.hash(state); + self.flags.hash(state); + self.color.hash(state); + self.size.hash(state); + } +} + +impl Deref for FontInstance { + type Target = BaseFontInstance; + fn deref(&self) -> &BaseFontInstance { + self.base.as_ref() + } +} + +impl MallocSizeOf for FontInstance { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { 0 } +} + +impl FontInstance { + pub fn new( + base: Arc, + color: ColorU, + render_mode: FontRenderMode, + flags: FontInstanceFlags, + ) -> Self { + FontInstance { + transform: FontTransform::identity(), + color, + size: base.size, + base, + render_mode, + flags, + } + } + + pub fn from_base( + base: Arc, + ) -> Self { + FontInstance { + transform: FontTransform::identity(), + color: ColorU::new(0, 0, 0, 255), + size: base.size, + render_mode: base.render_mode, + flags: base.flags, + base, + } + } + + pub fn use_texture_padding(&self) -> bool { + self.flags.contains(FontInstanceFlags::TEXTURE_PADDING) + } + + pub fn use_transform_glyphs(&self) -> bool { + self.flags.contains(FontInstanceFlags::TRANSFORM_GLYPHS) + } + + pub fn get_alpha_glyph_format(&self) -> GlyphFormat { + if self.use_transform_glyphs() { GlyphFormat::TransformedAlpha } else { GlyphFormat::Alpha } + } + + pub fn get_subpixel_glyph_format(&self) -> GlyphFormat { + if self.use_transform_glyphs() { GlyphFormat::TransformedSubpixel } else { GlyphFormat::Subpixel } + } + + pub fn disable_subpixel_aa(&mut self) { + self.render_mode = self.render_mode.limit_by(FontRenderMode::Alpha); + } + + pub fn disable_subpixel_position(&mut self) { + self.flags.remove(FontInstanceFlags::SUBPIXEL_POSITION); + } + + pub fn use_subpixel_position(&self) -> bool { + self.flags.contains(FontInstanceFlags::SUBPIXEL_POSITION) && + self.render_mode != FontRenderMode::Mono + } + + pub fn get_subpx_dir(&self) -> SubpixelDirection { + if self.use_subpixel_position() { + let mut subpx_dir = self.transform.get_subpx_dir(); + if self.flags.contains(FontInstanceFlags::TRANSPOSE) { + subpx_dir = subpx_dir.swap_xy(); + } + subpx_dir + } else { + SubpixelDirection::None + } + } + + #[allow(dead_code)] + pub fn get_subpx_offset(&self, glyph: &GlyphKey) -> (f64, f64) { + if self.use_subpixel_position() { + let (dx, dy) = glyph.subpixel_offset(); + (dx.into(), dy.into()) + } else { + (0.0, 0.0) + } + } + + #[allow(dead_code)] + pub fn get_glyph_format(&self) -> GlyphFormat { + match self.render_mode { + FontRenderMode::Mono | FontRenderMode::Alpha => self.get_alpha_glyph_format(), + FontRenderMode::Subpixel => self.get_subpixel_glyph_format(), + } + } + + #[allow(dead_code)] + pub fn get_extra_strikes(&self, x_scale: f64) -> usize { + if self.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) { + let mut bold_offset = self.size.to_f64_px() / 48.0; + if bold_offset < 1.0 { + bold_offset = 0.25 + 0.75 * bold_offset; + } + (bold_offset * x_scale).max(1.0).round() as usize + } else { + 0 + } + } + + pub fn synthesize_italics(&self, transform: FontTransform, size: f64) -> (FontTransform, (f64, f64)) { + transform.synthesize_italics(self.synthetic_italics, size, self.flags.contains(FontInstanceFlags::VERTICAL)) + } + + #[allow(dead_code)] + pub fn get_transformed_size(&self) -> f64 { + let (_, y_scale) = self.transform.compute_scale().unwrap_or((1.0, 1.0)); + self.size.to_f64_px() * y_scale + } +} + +#[repr(u32)] +#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)] +pub enum SubpixelDirection { + None = 0, + Horizontal, + Vertical, + Mixed, +} + +impl SubpixelDirection { + // Limit the subpixel direction to what is supported by the glyph format. + pub fn limit_by(self, glyph_format: GlyphFormat) -> Self { + match glyph_format { + GlyphFormat::Bitmap | + GlyphFormat::ColorBitmap => SubpixelDirection::None, + _ => self, + } + } + + pub fn swap_xy(self) -> Self { + match self { + SubpixelDirection::None | SubpixelDirection::Mixed => self, + SubpixelDirection::Horizontal => SubpixelDirection::Vertical, + SubpixelDirection::Vertical => SubpixelDirection::Horizontal, + } + } +} + +#[repr(u8)] +#[derive(Hash, Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum SubpixelOffset { + Zero = 0, + Quarter = 1, + Half = 2, + ThreeQuarters = 3, +} + +impl SubpixelOffset { + // Skia quantizes subpixel offsets into 1/4 increments. + // Given the absolute position, return the quantized increment + fn quantize(pos: f32) -> Self { + // Following the conventions of Gecko and Skia, we want + // to quantize the subpixel position, such that abs(pos) gives: + // [0.0, 0.125) -> Zero + // [0.125, 0.375) -> Quarter + // [0.375, 0.625) -> Half + // [0.625, 0.875) -> ThreeQuarters, + // [0.875, 1.0) -> Zero + // The unit tests below check for this. + let apos = ((pos - pos.floor()) * 8.0) as i32; + + match apos { + 1..=2 => SubpixelOffset::Quarter, + 3..=4 => SubpixelOffset::Half, + 5..=6 => SubpixelOffset::ThreeQuarters, + _ => SubpixelOffset::Zero, + } + } +} + +impl Into for SubpixelOffset { + fn into(self) -> f64 { + match self { + SubpixelOffset::Zero => 0.0, + SubpixelOffset::Quarter => 0.25, + SubpixelOffset::Half => 0.5, + SubpixelOffset::ThreeQuarters => 0.75, + } + } +} + +#[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct GlyphKey(u32); + +impl GlyphKey { + pub fn new( + index: u32, + point: DevicePoint, + subpx_dir: SubpixelDirection, + ) -> Self { + let (dx, dy) = match subpx_dir { + SubpixelDirection::None => (0.0, 0.0), + SubpixelDirection::Horizontal => (point.x, 0.0), + SubpixelDirection::Vertical => (0.0, point.y), + SubpixelDirection::Mixed => (point.x, point.y), + }; + let sox = SubpixelOffset::quantize(dx); + let soy = SubpixelOffset::quantize(dy); + assert_eq!(0, index & 0xF0000000); + + GlyphKey(index | (sox as u32) << 28 | (soy as u32) << 30) + } + + pub fn index(&self) -> GlyphIndex { + self.0 & 0x0FFFFFFF + } + + fn subpixel_offset(&self) -> (SubpixelOffset, SubpixelOffset) { + let x = (self.0 >> 28) as u8 & 3; + let y = (self.0 >> 30) as u8 & 3; + unsafe { + (mem::transmute(x), mem::transmute(y)) + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[allow(dead_code)] +pub enum GlyphFormat { + Alpha, + TransformedAlpha, + Subpixel, + TransformedSubpixel, + Bitmap, + ColorBitmap, +} + +impl GlyphFormat { + pub fn ignore_color(self) -> Self { + match self { + GlyphFormat::ColorBitmap => GlyphFormat::Bitmap, + _ => self, + } + } +} + +pub struct RasterizedGlyph { + pub top: f32, + pub left: f32, + pub width: i32, + pub height: i32, + pub scale: f32, + pub format: GlyphFormat, + pub bytes: Vec, +} + +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 = 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>, + // 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, + // Stored here as a convenience to get the current thread index. + #[allow(dead_code)] + workers: Arc, + locked_mutex: Mutex, + locked_cond: Condvar, +} + +impl FontContexts { + + /// Get access to any particular font context. + /// + /// The id is ```Some(i)``` where i is an index between 0 and num_worker_contexts + /// for font contexts associated to the thread pool, and None for the shared + /// global font context for use outside of the thread pool. + pub fn lock_context(&self, id: Option) -> MutexGuard { + match id { + Some(index) => self.worker_contexts[index].lock().unwrap(), + None => self.shared_context.lock().unwrap(), + } + } + + /// Get access to the font context usable outside of the thread pool. + pub fn lock_shared_context(&self) -> MutexGuard { + self.shared_context.lock().unwrap() + } + + // number of contexts associated to workers + pub fn num_worker_contexts(&self) -> usize { + self.worker_contexts.len() + } +} + +pub trait AsyncForEach { + fn async_for_each) + Send + 'static>(&self, f: F); +} + +impl AsyncForEach for Arc { + fn async_for_each) + Send + 'static>(&self, f: F) { + // Reset the locked condition. + let mut locked = self.locked_mutex.lock().unwrap(); + *locked = false; + + // Arc that can be safely moved into a spawn closure. + let font_contexts = self.clone(); + // Spawn a new thread on which to run the for-each off the main thread. + self.workers.spawn(move || { + // Lock the shared and worker contexts up front. + let mut locks = Vec::with_capacity(font_contexts.num_worker_contexts() + 1); + locks.push(font_contexts.lock_shared_context()); + for i in 0 .. font_contexts.num_worker_contexts() { + locks.push(font_contexts.lock_context(Some(i))); + } + + // Signal the locked condition now that all contexts are locked. + *font_contexts.locked_mutex.lock().unwrap() = true; + font_contexts.locked_cond.notify_all(); + + // Now that everything is locked, proceed to processing each locked context. + for context in locks { + f(context); + } + }); + + // Wait for locked condition before resuming. Safe to proceed thereafter + // since any other thread that needs to use a FontContext will try to lock + // it first. + while !*locked { + locked = self.locked_cond.wait(locked).unwrap(); + } + } +} + +pub struct GlyphRasterizer { + #[allow(dead_code)] + workers: Arc, + font_contexts: Arc, + + // Maintain a set of glyphs that have been requested this + // frame. This ensures the glyph thread won't rasterize + // the same glyph more than once in a frame. This is required + // because the glyph cache hash table is not updated + // until the end of the frame when we wait for glyph requests + // to be resolved. + #[allow(dead_code)] + pending_glyphs: usize, + + // Receives the rendered glyphs. + #[allow(dead_code)] + glyph_rx: Receiver, + #[allow(dead_code)] + glyph_tx: Sender, + + // We defer removing fonts to the end of the frame so that: + // - this work is done outside of the critical path, + // - we don't have to worry about the ordering of events if a font is used on + // a frame where it is used (although it seems unlikely). + fonts_to_remove: Vec, + // Defer removal of font instances, as for fonts. + font_instances_to_remove: Vec, + + #[allow(dead_code)] + next_gpu_glyph_cache_key: GpuGlyphCacheKey, + + // Whether to parallelize glyph rasterization with rayon. + enable_multithreading: bool, +} + +impl GlyphRasterizer { + pub fn new(workers: Arc) -> Result { + let (glyph_tx, glyph_rx) = channel(); + + let num_workers = workers.current_num_threads(); + let mut contexts = Vec::with_capacity(num_workers); + + let shared_context = FontContext::new()?; + + for _ in 0 .. num_workers { + contexts.push(Mutex::new(FontContext::new()?)); + } + + let font_context = FontContexts { + worker_contexts: contexts, + shared_context: Mutex::new(shared_context), + workers: Arc::clone(&workers), + locked_mutex: Mutex::new(false), + locked_cond: Condvar::new(), + }; + + Ok(GlyphRasterizer { + font_contexts: Arc::new(font_context), + pending_glyphs: 0, + glyph_rx, + glyph_tx, + workers, + fonts_to_remove: Vec::new(), + font_instances_to_remove: Vec::new(), + next_gpu_glyph_cache_key: GpuGlyphCacheKey(0), + enable_multithreading: true, + }) + } + + pub fn add_font(&mut self, font_key: FontKey, template: FontTemplate) { + self.font_contexts.async_for_each(move |mut context| { + context.add_font(&font_key, &template); + }); + } + + pub fn delete_font(&mut self, font_key: FontKey) { + self.fonts_to_remove.push(font_key); + } + + pub fn delete_font_instance(&mut self, instance: &FontInstance) { + self.font_instances_to_remove.push(instance.clone()); + } + + pub fn prepare_font(&self, font: &mut FontInstance) { + FontContext::prepare_font(font); + + // Quantize the transform to minimize thrashing of the glyph cache, but + // only quantize the transform when preparing to access the glyph cache. + // This way, the glyph subpixel positions, which are calculated before + // this, can still use the precise transform which is required to match + // the subpixel positions computed for glyphs in the text run shader. + font.transform = font.transform.quantize(); + } + + pub fn get_glyph_dimensions( + &mut self, + font: &FontInstance, + glyph_index: GlyphIndex, + ) -> Option { + let glyph_key = GlyphKey::new( + glyph_index, + DevicePoint::zero(), + SubpixelDirection::None, + ); + + self.font_contexts + .lock_shared_context() + .get_glyph_dimensions(font, &glyph_key) + } + + pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option { + self.font_contexts + .lock_shared_context() + .get_glyph_index(font_key, ch) + } + + fn remove_dead_fonts(&mut self) { + if self.fonts_to_remove.is_empty() && self.font_instances_to_remove.is_empty() { + return + } + + profile_scope!("remove_dead_fonts"); + let fonts_to_remove = mem::replace(&mut self.fonts_to_remove, Vec::new()); + let font_instances_to_remove = mem::replace(& mut self.font_instances_to_remove, Vec::new()); + self.font_contexts.async_for_each(move |mut context| { + for font_key in &fonts_to_remove { + context.delete_font(font_key); + } + for instance in &font_instances_to_remove { + context.delete_font_instance(instance); + } + }); + } + + #[cfg(feature = "replay")] + pub fn reset(&mut self) { + //TODO: any signals need to be sent to the workers? + self.pending_glyphs = 0; + self.fonts_to_remove.clear(); + self.font_instances_to_remove.clear(); + } +} + +trait AddFont { + fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate); +} + +impl AddFont for FontContext { + fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate) { + match *template { + FontTemplate::Raw(ref bytes, index) => { + self.add_raw_font(font_key, bytes.clone(), index); + } + FontTemplate::Native(ref native_font_handle) => { + self.add_native_font(font_key, (*native_font_handle).clone()); + } + } + } +} + +#[allow(dead_code)] +pub(in crate::glyph_rasterizer) struct GlyphRasterJob { + key: GlyphKey, + result: GlyphRasterResult, +} + +#[allow(dead_code)] +pub enum GlyphRasterError { + LoadFailed, +} + +#[allow(dead_code)] +pub type GlyphRasterResult = Result; + +#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct GpuGlyphCacheKey(pub u32); + +#[allow(dead_code)] +struct GlyphRasterJobs { + font: FontInstance, + jobs: Vec, +} + +#[cfg(test)] +mod test_glyph_rasterizer { + #[test] + fn rasterize_200_glyphs() { + // This test loads a font from disc, the renders 4 requests containing + // 50 glyphs each, deletes the font and waits for the result. + + use rayon::ThreadPoolBuilder; + use std::fs::File; + use std::io::Read; + use crate::texture_cache::TextureCache; + use crate::glyph_cache::GlyphCache; + use crate::gpu_cache::GpuCache; + use crate::render_task_cache::RenderTaskCache; + use crate::render_task_graph::{RenderTaskGraph, RenderTaskGraphCounters}; + use crate::profiler::TextureCacheProfileCounters; + use api::{FontKey, FontInstanceKey, FontSize, FontTemplate, FontRenderMode, + IdNamespace, ColorU}; + use api::units::DevicePoint; + use crate::render_backend::FrameId; + use std::sync::Arc; + use crate::glyph_rasterizer::{FORMAT, FontInstance, BaseFontInstance, GlyphKey, GlyphRasterizer}; + + let worker = ThreadPoolBuilder::new() + .thread_name(|idx|{ format!("WRWorker#{}", idx) }) + .build(); + let workers = Arc::new(worker.unwrap()); + let mut glyph_rasterizer = GlyphRasterizer::new(workers).unwrap(); + let mut glyph_cache = GlyphCache::new(GlyphCache::DEFAULT_MAX_BYTES_USED); + let mut gpu_cache = GpuCache::new_for_testing(); + let mut texture_cache = TextureCache::new_for_testing(2048, 1024, FORMAT); + let mut render_task_cache = RenderTaskCache::new(); + let mut render_task_tree = RenderTaskGraph::new(FrameId::INVALID, &RenderTaskGraphCounters::new()); + let mut font_file = + File::open("../wrench/reftests/text/VeraBd.ttf").expect("Couldn't open font file"); + let mut font_data = vec![]; + font_file + .read_to_end(&mut font_data) + .expect("failed to read font file"); + + let font_key = FontKey::new(IdNamespace(0), 0); + glyph_rasterizer.add_font(font_key, FontTemplate::Raw(Arc::new(font_data), 0)); + + let font = FontInstance::from_base(Arc::new(BaseFontInstance { + instance_key: FontInstanceKey(IdNamespace(0), 0), + font_key, + size: FontSize::from_f32_px(32.0), + bg_color: ColorU::new(0, 0, 0, 0), + render_mode: FontRenderMode::Subpixel, + flags: Default::default(), + synthetic_italics: Default::default(), + platform_options: None, + variations: Vec::new(), + })); + + let subpx_dir = font.get_subpx_dir(); + + let mut glyph_keys = Vec::with_capacity(200); + for i in 0 .. 200 { + glyph_keys.push(GlyphKey::new( + i, + DevicePoint::zero(), + subpx_dir, + )); + } + + for i in 0 .. 4 { + glyph_rasterizer.request_glyphs( + &mut glyph_cache, + font.clone(), + &glyph_keys[(50 * i) .. (50 * (i + 1))], + &mut texture_cache, + &mut gpu_cache, + &mut render_task_cache, + &mut render_task_tree, + ); + } + + glyph_rasterizer.delete_font(font_key); + + glyph_rasterizer.resolve_glyphs( + &mut glyph_cache, + &mut TextureCache::new_for_testing(4096, 1024, FORMAT), + &mut gpu_cache, + &mut render_task_cache, + &mut render_task_tree, + &mut TextureCacheProfileCounters::new(), + ); + } + + #[test] + fn test_subpx_quantize() { + use crate::glyph_rasterizer::SubpixelOffset; + + assert_eq!(SubpixelOffset::quantize(0.0), SubpixelOffset::Zero); + assert_eq!(SubpixelOffset::quantize(-0.0), SubpixelOffset::Zero); + + assert_eq!(SubpixelOffset::quantize(0.1), SubpixelOffset::Zero); + assert_eq!(SubpixelOffset::quantize(0.01), SubpixelOffset::Zero); + assert_eq!(SubpixelOffset::quantize(0.05), SubpixelOffset::Zero); + assert_eq!(SubpixelOffset::quantize(0.12), SubpixelOffset::Zero); + assert_eq!(SubpixelOffset::quantize(0.124), SubpixelOffset::Zero); + + assert_eq!(SubpixelOffset::quantize(0.125), SubpixelOffset::Quarter); + assert_eq!(SubpixelOffset::quantize(0.2), SubpixelOffset::Quarter); + assert_eq!(SubpixelOffset::quantize(0.25), SubpixelOffset::Quarter); + assert_eq!(SubpixelOffset::quantize(0.33), SubpixelOffset::Quarter); + assert_eq!(SubpixelOffset::quantize(0.374), SubpixelOffset::Quarter); + + assert_eq!(SubpixelOffset::quantize(0.375), SubpixelOffset::Half); + assert_eq!(SubpixelOffset::quantize(0.4), SubpixelOffset::Half); + assert_eq!(SubpixelOffset::quantize(0.5), SubpixelOffset::Half); + assert_eq!(SubpixelOffset::quantize(0.58), SubpixelOffset::Half); + assert_eq!(SubpixelOffset::quantize(0.624), SubpixelOffset::Half); + + assert_eq!(SubpixelOffset::quantize(0.625), SubpixelOffset::ThreeQuarters); + assert_eq!(SubpixelOffset::quantize(0.67), SubpixelOffset::ThreeQuarters); + assert_eq!(SubpixelOffset::quantize(0.7), SubpixelOffset::ThreeQuarters); + assert_eq!(SubpixelOffset::quantize(0.78), SubpixelOffset::ThreeQuarters); + assert_eq!(SubpixelOffset::quantize(0.874), SubpixelOffset::ThreeQuarters); + + assert_eq!(SubpixelOffset::quantize(0.875), SubpixelOffset::Zero); + assert_eq!(SubpixelOffset::quantize(0.89), SubpixelOffset::Zero); + assert_eq!(SubpixelOffset::quantize(0.91), SubpixelOffset::Zero); + assert_eq!(SubpixelOffset::quantize(0.967), SubpixelOffset::Zero); + assert_eq!(SubpixelOffset::quantize(0.999), SubpixelOffset::Zero); + + assert_eq!(SubpixelOffset::quantize(-1.0), SubpixelOffset::Zero); + assert_eq!(SubpixelOffset::quantize(1.0), SubpixelOffset::Zero); + assert_eq!(SubpixelOffset::quantize(1.5), SubpixelOffset::Half); + assert_eq!(SubpixelOffset::quantize(-1.625), SubpixelOffset::Half); + assert_eq!(SubpixelOffset::quantize(-4.33), SubpixelOffset::ThreeQuarters); + } +} diff --git a/third_party/webrender/webrender/src/gpu_cache.rs b/third_party/webrender/webrender/src/gpu_cache.rs new file mode 100644 index 00000000000..c34efd09d8b --- /dev/null +++ b/third_party/webrender/webrender/src/gpu_cache.rs @@ -0,0 +1,924 @@ +/* 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/. */ + +//! Overview of the GPU cache. +//! +//! The main goal of the GPU cache is to allow on-demand +//! allocation and construction of GPU resources for the +//! vertex shaders to consume. +//! +//! Every item that wants to be stored in the GPU cache +//! should create a GpuCacheHandle that is used to refer +//! to a cached GPU resource. Creating a handle is a +//! cheap operation, that does *not* allocate room in the +//! cache. +//! +//! On any frame when that data is required, the caller +//! must request that handle, via ```request```. If the +//! data is not in the cache, the user provided closure +//! will be invoked to build the data. +//! +//! After ```end_frame``` has occurred, callers can +//! use the ```get_address``` API to get the allocated +//! address in the GPU cache of a given resource slot +//! for this frame. + +use api::{DebugFlags, DocumentId, PremultipliedColorF}; +#[cfg(test)] +use api::IdNamespace; +use api::units::TexelRect; +use euclid::{HomogeneousVector, Rect}; +use crate::internal_types::{FastHashMap, FastHashSet}; +use crate::profiler::GpuCacheProfileCounters; +use crate::render_backend::{FrameStamp, FrameId}; +use crate::renderer::MAX_VERTEX_TEXTURE_WIDTH; +use std::{mem, u16, u32}; +use std::num::NonZeroU32; +use std::ops::Add; +use std::time::{Duration, Instant}; + + +/// At the time of this writing, Firefox uses about 15 GPU cache rows on +/// startup, and then gradually works its way up to the mid-30s with normal +/// browsing. +pub const GPU_CACHE_INITIAL_HEIGHT: i32 = 20; +const NEW_ROWS_PER_RESIZE: i32 = 10; + +/// The number of frames an entry can go unused before being evicted. +const FRAMES_BEFORE_EVICTION: usize = 10; + +/// The ratio of utilized blocks to total blocks for which we start the clock +/// on reclaiming memory. +const RECLAIM_THRESHOLD: f32 = 0.2; + +/// The amount of time utilization must be below the above threshold before we +/// blow away the cache and rebuild it. +const RECLAIM_DELAY_S: u64 = 5; + +#[derive(Debug, Copy, Clone, Eq, MallocSizeOf, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct Epoch(u32); + +impl Epoch { + fn next(&mut self) { + *self = Epoch(self.0.wrapping_add(1)); + } +} + +#[derive(Debug, Copy, Clone, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct CacheLocation { + block_index: BlockIndex, + epoch: Epoch, +} + +/// A single texel in RGBAF32 texture - 16 bytes. +#[derive(Copy, Clone, Debug, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct GpuBlockData { + data: [f32; 4], +} + +impl GpuBlockData { + pub const EMPTY: Self = GpuBlockData { data: [0.0; 4] }; +} + +/// Conversion helpers for GpuBlockData +impl From for GpuBlockData { + fn from(c: PremultipliedColorF) -> Self { + GpuBlockData { + data: [c.r, c.g, c.b, c.a], + } + } +} + +impl From<[f32; 4]> for GpuBlockData { + fn from(data: [f32; 4]) -> Self { + GpuBlockData { data } + } +} + +impl

    From> for GpuBlockData { + fn from(r: Rect) -> Self { + GpuBlockData { + data: [ + r.origin.x, + r.origin.y, + r.size.width, + r.size.height, + ], + } + } +} + +impl

    From> for GpuBlockData { + fn from(v: HomogeneousVector) -> Self { + GpuBlockData { + data: [ + v.x, + v.y, + v.z, + v.w, + ], + } + } +} + +impl From for GpuBlockData { + fn from(tr: TexelRect) -> Self { + GpuBlockData { + data: [tr.uv0.x, tr.uv0.y, tr.uv1.x, tr.uv1.y], + } + } +} + + +// Any data type that can be stored in the GPU cache should +// implement this trait. +pub trait ToGpuBlocks { + // Request an arbitrary number of GPU data blocks. + fn write_gpu_blocks(&self, _: GpuDataRequest); +} + +// A handle to a GPU resource. +#[derive(Debug, Copy, Clone, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct GpuCacheHandle { + location: Option, +} + +impl GpuCacheHandle { + pub fn new() -> Self { + GpuCacheHandle { location: None } + } +} + +// A unique address in the GPU cache. These are uploaded +// as part of the primitive instances, to allow the vertex +// shader to fetch the specific data. +#[derive(Copy, Debug, Clone, MallocSizeOf, Eq, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct GpuCacheAddress { + pub u: u16, + pub v: u16, +} + +impl GpuCacheAddress { + fn new(u: usize, v: usize) -> Self { + GpuCacheAddress { + u: u as u16, + v: v as u16, + } + } + + pub const INVALID: GpuCacheAddress = GpuCacheAddress { + u: u16::MAX, + v: u16::MAX, + }; +} + +impl Add for GpuCacheAddress { + type Output = GpuCacheAddress; + + fn add(self, other: usize) -> GpuCacheAddress { + GpuCacheAddress { + u: self.u + other as u16, + v: self.v, + } + } +} + +// An entry in a free-list of blocks in the GPU cache. +#[derive(Debug, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct Block { + // The location in the cache of this block. + address: GpuCacheAddress, + // The current epoch (generation) of this block. + epoch: Epoch, + // Index of the next free block in the list it + // belongs to (either a free-list or the + // occupied list). + next: Option, + // The last frame this block was referenced. + last_access_time: FrameId, +} + +impl Block { + fn new( + address: GpuCacheAddress, + next: Option, + frame_id: FrameId, + epoch: Epoch, + ) -> Self { + Block { + address, + next, + last_access_time: frame_id, + epoch, + } + } + + fn advance_epoch(&mut self, max_epoch: &mut Epoch) { + self.epoch.next(); + if max_epoch.0 < self.epoch.0 { + max_epoch.0 = self.epoch.0; + } + } + + /// Creates an invalid dummy block ID. + pub const INVALID: Block = Block { + address: GpuCacheAddress { u: 0, v: 0 }, + epoch: Epoch(0), + next: None, + last_access_time: FrameId::INVALID, + }; +} + +/// Represents the index of a Block in the block array. We only create such +/// structs for blocks that represent the start of a chunk. +/// +/// Because we use Option in a lot of places, we use a NonZeroU32 +/// here and avoid ever using the index zero. +#[derive(Debug, Copy, Clone, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct BlockIndex(NonZeroU32); + +impl BlockIndex { + fn new(idx: usize) -> Self { + debug_assert!(idx <= u32::MAX as usize); + BlockIndex(NonZeroU32::new(idx as u32).expect("Index zero forbidden")) + } + + fn get(&self) -> usize { + self.0.get() as usize + } +} + +// A row in the cache texture. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +struct Row { + // The fixed size of blocks that this row supports. + // Each row becomes a slab allocator for a fixed block size. + // This means no dealing with fragmentation within a cache + // row as items are allocated and freed. + block_count_per_item: usize, +} + +impl Row { + fn new(block_count_per_item: usize) -> Self { + Row { + block_count_per_item, + } + } +} + +// A list of update operations that can be applied on the cache +// this frame. The list of updates is created by the render backend +// during frame construction. It's passed to the render thread +// where GL commands can be applied. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub enum GpuCacheUpdate { + Copy { + block_index: usize, + block_count: usize, + address: GpuCacheAddress, + }, +} + +/// Command to inform the debug display in the renderer when chunks are allocated +/// or freed. +#[derive(MallocSizeOf)] +pub enum GpuCacheDebugCmd { + /// Describes an allocated chunk. + Alloc(GpuCacheDebugChunk), + /// Describes a freed chunk. + Free(GpuCacheAddress), +} + +#[derive(Clone, MallocSizeOf)] +pub struct GpuCacheDebugChunk { + pub address: GpuCacheAddress, + pub size: usize, +} + +#[must_use] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct GpuCacheUpdateList { + /// The frame current update list was generated from. + pub frame_id: FrameId, + /// Whether the texture should be cleared before updates + /// are applied. + pub clear: bool, + /// The current height of the texture. The render thread + /// should resize the texture if required. + pub height: i32, + /// List of updates to apply. + pub updates: Vec, + /// A flat list of GPU blocks that are pending upload + /// to GPU memory. + pub blocks: Vec, + /// Whole state GPU block metadata for debugging. + #[cfg_attr(feature = "serde", serde(skip))] + pub debug_commands: Vec, +} + +// Holds the free lists of fixed size blocks. Mostly +// just serves to work around the borrow checker. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +struct FreeBlockLists { + free_list_1: Option, + free_list_2: Option, + free_list_4: Option, + free_list_8: Option, + free_list_16: Option, + free_list_32: Option, + free_list_64: Option, + free_list_128: Option, + free_list_256: Option, + free_list_341: Option, + free_list_512: Option, + free_list_1024: Option, +} + +impl FreeBlockLists { + fn new() -> Self { + FreeBlockLists { + free_list_1: None, + free_list_2: None, + free_list_4: None, + free_list_8: None, + free_list_16: None, + free_list_32: None, + free_list_64: None, + free_list_128: None, + free_list_256: None, + free_list_341: None, + free_list_512: None, + free_list_1024: None, + } + } + + fn get_actual_block_count_and_free_list( + &mut self, + block_count: usize, + ) -> (usize, &mut Option) { + // Find the appropriate free list to use based on the block size. + // + // Note that we cheat a bit with the 341 bucket, since it's not quite + // a divisor of 1024, because purecss-francine allocates many 260-block + // chunks, and there's no reason we shouldn't pack these three to a row. + // This means the allocation statistics will under-report by one block + // for each row using 341-block buckets, which is fine. + debug_assert_eq!(MAX_VERTEX_TEXTURE_WIDTH, 1024, "Need to update bucketing"); + match block_count { + 0 => panic!("Can't allocate zero sized blocks!"), + 1 => (1, &mut self.free_list_1), + 2 => (2, &mut self.free_list_2), + 3..=4 => (4, &mut self.free_list_4), + 5..=8 => (8, &mut self.free_list_8), + 9..=16 => (16, &mut self.free_list_16), + 17..=32 => (32, &mut self.free_list_32), + 33..=64 => (64, &mut self.free_list_64), + 65..=128 => (128, &mut self.free_list_128), + 129..=256 => (256, &mut self.free_list_256), + 257..=341 => (341, &mut self.free_list_341), + 342..=512 => (512, &mut self.free_list_512), + 513..=1024 => (1024, &mut self.free_list_1024), + _ => panic!("Can't allocate > MAX_VERTEX_TEXTURE_WIDTH per resource!"), + } + } +} + +// CPU-side representation of the GPU resource cache texture. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +struct Texture { + // Current texture height + height: i32, + // All blocks that have been created for this texture + blocks: Vec, + // Metadata about each allocated row. + rows: Vec, + // The base Epoch for this texture. + base_epoch: Epoch, + // The maximum epoch reached. We track this along with the above so + // that we can rebuild the Texture and avoid collisions with handles + // allocated for the old texture. + max_epoch: Epoch, + // Free lists of available blocks for each supported + // block size in the texture. These are intrusive + // linked lists. + free_lists: FreeBlockLists, + // Linked list of currently occupied blocks. This + // makes it faster to iterate blocks looking for + // candidates to be evicted from the cache. + occupied_list_heads: FastHashMap, + // Pending blocks that have been written this frame + // and will need to be sent to the GPU. + pending_blocks: Vec, + // Pending update commands. + updates: Vec, + // Profile stats + allocated_block_count: usize, + // The stamp at which we first reached our threshold for reclaiming `GpuCache` + // memory, or `None` if the threshold hasn't been reached. + #[cfg_attr(feature = "serde", serde(skip))] + reached_reclaim_threshold: Option, + // List of debug commands to be sent to the renderer when the GPU cache + // debug display is enabled. + #[cfg_attr(feature = "serde", serde(skip))] + debug_commands: Vec, + // The current debug flags for the system. + debug_flags: DebugFlags, +} + +impl Texture { + fn new(base_epoch: Epoch, debug_flags: DebugFlags) -> Self { + // Pre-fill the block array with one invalid block so that we never use + // 0 for a BlockIndex. This lets us use NonZeroU32 for BlockIndex, which + // saves memory. + let blocks = vec![Block::INVALID]; + + Texture { + height: GPU_CACHE_INITIAL_HEIGHT, + blocks, + rows: Vec::new(), + base_epoch, + max_epoch: base_epoch, + free_lists: FreeBlockLists::new(), + pending_blocks: Vec::new(), + updates: Vec::new(), + occupied_list_heads: FastHashMap::default(), + allocated_block_count: 0, + reached_reclaim_threshold: None, + debug_commands: Vec::new(), + debug_flags, + } + } + + // Push new data into the cache. The ```pending_block_index``` field represents + // where the data was pushed into the texture ```pending_blocks``` array. + // Return the allocated address for this data. + fn push_data( + &mut self, + pending_block_index: Option, + block_count: usize, + frame_stamp: FrameStamp + ) -> CacheLocation { + debug_assert!(frame_stamp.is_valid()); + // Find the appropriate free list to use based on the block size. + let (alloc_size, free_list) = self.free_lists + .get_actual_block_count_and_free_list(block_count); + + // See if we need a new row (if free-list has nothing available) + if free_list.is_none() { + if self.rows.len() as i32 == self.height { + self.height += NEW_ROWS_PER_RESIZE; + } + + // Create a new row. + let items_per_row = MAX_VERTEX_TEXTURE_WIDTH / alloc_size; + let row_index = self.rows.len(); + self.rows.push(Row::new(alloc_size)); + + // Create a ```Block``` for each possible allocation address + // in this row, and link it in to the free-list for this + // block size. + let mut prev_block_index = None; + for i in 0 .. items_per_row { + let address = GpuCacheAddress::new(i * alloc_size, row_index); + let block_index = BlockIndex::new(self.blocks.len()); + let block = Block::new(address, prev_block_index, frame_stamp.frame_id(), self.base_epoch); + self.blocks.push(block); + prev_block_index = Some(block_index); + } + + *free_list = prev_block_index; + } + + // Given the code above, it's now guaranteed that there is a block + // available in the appropriate free-list. Pull a block from the + // head of the list. + let free_block_index = free_list.take().unwrap(); + let block = &mut self.blocks[free_block_index.get()]; + *free_list = block.next; + + // Add the block to the occupied linked list. + block.next = self.occupied_list_heads.get(&frame_stamp.document_id()).cloned(); + block.last_access_time = frame_stamp.frame_id(); + self.occupied_list_heads.insert(frame_stamp.document_id(), free_block_index); + self.allocated_block_count += alloc_size; + + if let Some(pending_block_index) = pending_block_index { + // Add this update to the pending list of blocks that need + // to be updated on the GPU. + self.updates.push(GpuCacheUpdate::Copy { + block_index: pending_block_index, + block_count, + address: block.address, + }); + } + + // If we're using the debug display, communicate the allocation to the + // renderer thread. Note that we do this regardless of whether or not + // pending_block_index is None (if it is, the renderer thread will fill + // in the data via a deferred resolve, but the block is still considered + // allocated). + if self.debug_flags.contains(DebugFlags::GPU_CACHE_DBG) { + self.debug_commands.push(GpuCacheDebugCmd::Alloc(GpuCacheDebugChunk { + address: block.address, + size: block_count, + })); + } + + CacheLocation { + block_index: free_block_index, + epoch: block.epoch, + } + } + + // Run through the list of occupied cache blocks and evict + // any old blocks that haven't been referenced for a while. + fn evict_old_blocks(&mut self, frame_stamp: FrameStamp) { + debug_assert!(frame_stamp.is_valid()); + // Prune any old items from the list to make room. + // Traverse the occupied linked list and see + // which items have not been used for a long time. + let mut current_block = self.occupied_list_heads.get(&frame_stamp.document_id()).map(|x| *x); + let mut prev_block: Option = None; + + while let Some(index) = current_block { + let (next_block, should_unlink) = { + let block = &mut self.blocks[index.get()]; + + let next_block = block.next; + let mut should_unlink = false; + + // If this resource has not been used in the last + // few frames, free it from the texture and mark + // as empty. + if block.last_access_time + FRAMES_BEFORE_EVICTION < frame_stamp.frame_id() { + should_unlink = true; + + // Get the row metadata from the address. + let row = &mut self.rows[block.address.v as usize]; + + // Use the row metadata to determine which free-list + // this block belongs to. + let (_, free_list) = self.free_lists + .get_actual_block_count_and_free_list(row.block_count_per_item); + + block.advance_epoch(&mut self.max_epoch); + block.next = *free_list; + *free_list = Some(index); + + self.allocated_block_count -= row.block_count_per_item; + + if self.debug_flags.contains(DebugFlags::GPU_CACHE_DBG) { + let cmd = GpuCacheDebugCmd::Free(block.address); + self.debug_commands.push(cmd); + } + }; + + (next_block, should_unlink) + }; + + // If the block was released, we will need to remove it + // from the occupied linked list. + if should_unlink { + match prev_block { + Some(prev_block) => { + self.blocks[prev_block.get()].next = next_block; + } + None => { + match next_block { + Some(next_block) => { + self.occupied_list_heads.insert(frame_stamp.document_id(), next_block); + } + None => { + self.occupied_list_heads.remove(&frame_stamp.document_id()); + } + } + } + } + } else { + prev_block = current_block; + } + + current_block = next_block; + } + } + + /// Returns the ratio of utilized blocks. + fn utilization(&self) -> f32 { + let total_blocks = self.rows.len() * MAX_VERTEX_TEXTURE_WIDTH; + debug_assert!(total_blocks > 0); + let ratio = self.allocated_block_count as f32 / total_blocks as f32; + debug_assert!(0.0 <= ratio && ratio <= 1.0, "Bad ratio: {}", ratio); + ratio + } +} + + +/// A wrapper object for GPU data requests, +/// works as a container that can only grow. +#[must_use] +pub struct GpuDataRequest<'a> { + handle: &'a mut GpuCacheHandle, + frame_stamp: FrameStamp, + start_index: usize, + max_block_count: usize, + texture: &'a mut Texture, +} + +impl<'a> GpuDataRequest<'a> { + pub fn push(&mut self, block: B) + where + B: Into, + { + self.texture.pending_blocks.push(block.into()); + } + + pub fn current_used_block_num(&self) -> usize { + self.texture.pending_blocks.len() - self.start_index + } +} + +impl<'a> Drop for GpuDataRequest<'a> { + fn drop(&mut self) { + // Push the data to the texture pending updates list. + let block_count = self.current_used_block_num(); + debug_assert!(block_count <= self.max_block_count); + + let location = self.texture + .push_data(Some(self.start_index), block_count, self.frame_stamp); + self.handle.location = Some(location); + } +} + + +/// The main LRU cache interface. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct GpuCache { + /// Current FrameId. + now: FrameStamp, + /// CPU-side texture allocator. + texture: Texture, + /// Number of blocks requested this frame that don't + /// need to be re-uploaded. + saved_block_count: usize, + /// The current debug flags for the system. + debug_flags: DebugFlags, + /// Whether there is a pending clear to send with the + /// next update. + pending_clear: bool, + /// Indicates that prepare_for_frames has been called for this group of frames. + /// Used for sanity checks. + prepared_for_frames: bool, + /// This indicates that we performed a cleanup operation which requires all + /// documents to build a frame. + requires_frame_build: bool, + /// The set of documents which have had frames built in this update. Used for + /// sanity checks. + document_frames_to_build: FastHashSet, +} + +impl GpuCache { + pub fn new() -> Self { + let debug_flags = DebugFlags::empty(); + GpuCache { + now: FrameStamp::INVALID, + texture: Texture::new(Epoch(0), debug_flags), + saved_block_count: 0, + debug_flags, + pending_clear: false, + prepared_for_frames: false, + requires_frame_build: false, + document_frames_to_build: FastHashSet::default(), + } + } + + /// Creates a GpuCache and sets it up with a valid `FrameStamp`, which + /// is useful for avoiding panics when instantiating the `GpuCache` + /// directly from unit test code. + #[cfg(test)] + pub fn new_for_testing() -> Self { + let mut cache = Self::new(); + let mut now = FrameStamp::first(DocumentId::new(IdNamespace(1), 1)); + now.advance(); + cache.prepared_for_frames = true; + cache.begin_frame(now); + cache + } + + /// Drops everything in the GPU cache. Must not be called once gpu cache entries + /// for the next frame have already been requested. + pub fn clear(&mut self) { + assert!(self.texture.updates.is_empty(), "Clearing with pending updates"); + let mut next_base_epoch = self.texture.max_epoch; + next_base_epoch.next(); + self.texture = Texture::new(next_base_epoch, self.debug_flags); + self.saved_block_count = 0; + self.pending_clear = true; + self.requires_frame_build = true; + } + + pub fn requires_frame_build(&self) -> bool { + self.requires_frame_build + } + + pub fn prepare_for_frames(&mut self) { + self.prepared_for_frames = true; + if self.should_reclaim_memory() { + self.clear(); + debug_assert!(self.document_frames_to_build.is_empty()); + for &document_id in self.texture.occupied_list_heads.keys() { + self.document_frames_to_build.insert(document_id); + } + } + } + + pub fn bookkeep_after_frames(&mut self) { + assert!(self.document_frames_to_build.is_empty()); + assert!(self.prepared_for_frames); + self.requires_frame_build = false; + self.prepared_for_frames = false; + } + + /// Begin a new frame. + pub fn begin_frame(&mut self, stamp: FrameStamp) { + debug_assert!(self.texture.pending_blocks.is_empty()); + assert!(self.prepared_for_frames); + profile_scope!("begin_frame"); + self.now = stamp; + self.texture.evict_old_blocks(self.now); + self.saved_block_count = 0; + } + + // Invalidate a (possibly) existing block in the cache. + // This means the next call to request() for this location + // will rebuild the data and upload it to the GPU. + pub fn invalidate(&mut self, handle: &GpuCacheHandle) { + if let Some(ref location) = handle.location { + // don't invalidate blocks that are already re-assigned + if let Some(block) = self.texture.blocks.get_mut(location.block_index.get()) { + if block.epoch == location.epoch { + block.advance_epoch(&mut self.texture.max_epoch); + } + } + } + } + + /// Request a resource be added to the cache. If the resource + /// is already in the cache, `None` will be returned. + pub fn request<'a>(&'a mut self, handle: &'a mut GpuCacheHandle) -> Option> { + let mut max_block_count = MAX_VERTEX_TEXTURE_WIDTH; + // Check if the allocation for this handle is still valid. + if let Some(ref location) = handle.location { + if let Some(block) = self.texture.blocks.get_mut(location.block_index.get()) { + if block.epoch == location.epoch { + max_block_count = self.texture.rows[block.address.v as usize].block_count_per_item; + if block.last_access_time != self.now.frame_id() { + // Mark last access time to avoid evicting this block. + block.last_access_time = self.now.frame_id(); + self.saved_block_count += max_block_count; + } + return None; + } + } + } + + debug_assert!(self.now.is_valid()); + Some(GpuDataRequest { + handle, + frame_stamp: self.now, + start_index: self.texture.pending_blocks.len(), + texture: &mut self.texture, + max_block_count, + }) + } + + // Push an array of data blocks to be uploaded to the GPU + // unconditionally for this frame. The cache handle will + // assert if the caller tries to retrieve the address + // of this handle on a subsequent frame. This is typically + // used for uploading data that changes every frame, and + // therefore makes no sense to try and cache. + pub fn push_per_frame_blocks(&mut self, blocks: &[GpuBlockData]) -> GpuCacheHandle { + let start_index = self.texture.pending_blocks.len(); + self.texture.pending_blocks.extend_from_slice(blocks); + let location = self.texture + .push_data(Some(start_index), blocks.len(), self.now); + GpuCacheHandle { + location: Some(location), + } + } + + // Reserve space in the cache for per-frame blocks that + // will be resolved by the render thread via the + // external image callback. + pub fn push_deferred_per_frame_blocks(&mut self, block_count: usize) -> GpuCacheHandle { + let location = self.texture.push_data(None, block_count, self.now); + GpuCacheHandle { + location: Some(location), + } + } + + /// End the frame. Return the list of updates to apply to the + /// device specific cache texture. + pub fn end_frame( + &mut self, + profile_counters: &mut GpuCacheProfileCounters, + ) -> FrameStamp { + profile_scope!("end_frame"); + profile_counters + .allocated_rows + .set(self.texture.rows.len()); + profile_counters + .allocated_blocks + .set(self.texture.allocated_block_count); + profile_counters + .saved_blocks + .set(self.saved_block_count); + + let reached_threshold = + self.texture.rows.len() > (GPU_CACHE_INITIAL_HEIGHT as usize) && + self.texture.utilization() < RECLAIM_THRESHOLD; + if reached_threshold { + self.texture.reached_reclaim_threshold.get_or_insert_with(Instant::now); + } else { + self.texture.reached_reclaim_threshold = None; + } + + self.document_frames_to_build.remove(&self.now.document_id()); + self.now + } + + /// Returns true if utilization has been low enough for long enough that we + /// should blow the cache away and rebuild it. + pub fn should_reclaim_memory(&self) -> bool { + self.texture.reached_reclaim_threshold + .map_or(false, |t| t.elapsed() > Duration::from_secs(RECLAIM_DELAY_S)) + } + + /// Extract the pending updates from the cache. + pub fn extract_updates(&mut self) -> GpuCacheUpdateList { + let clear = self.pending_clear; + self.pending_clear = false; + GpuCacheUpdateList { + frame_id: self.now.frame_id(), + clear, + height: self.texture.height, + debug_commands: mem::replace(&mut self.texture.debug_commands, Vec::new()), + updates: mem::replace(&mut self.texture.updates, Vec::new()), + blocks: mem::replace(&mut self.texture.pending_blocks, Vec::new()), + } + } + + /// Sets the current debug flags for the system. + pub fn set_debug_flags(&mut self, flags: DebugFlags) { + self.debug_flags = flags; + self.texture.debug_flags = flags; + } + + /// Get the actual GPU address in the texture for a given slot ID. + /// It's assumed at this point that the given slot has been requested + /// and built for this frame. Attempting to get the address for a + /// freed or pending slot will panic! + pub fn get_address(&self, id: &GpuCacheHandle) -> GpuCacheAddress { + let location = id.location.expect("handle not requested or allocated!"); + let block = &self.texture.blocks[location.block_index.get()]; + debug_assert_eq!(block.epoch, location.epoch); + debug_assert_eq!(block.last_access_time, self.now.frame_id()); + block.address + } +} + +#[test] +#[cfg(target_pointer_width = "64")] +fn test_struct_sizes() { + use std::mem; + // We can end up with a lot of blocks stored in the global vec, and keeping + // them small helps reduce memory overhead. + assert_eq!(mem::size_of::(), 24, "Block size changed"); +} diff --git a/third_party/webrender/webrender/src/gpu_types.rs b/third_party/webrender/webrender/src/gpu_types.rs new file mode 100644 index 00000000000..8e85b4dff06 --- /dev/null +++ b/third_party/webrender/webrender/src/gpu_types.rs @@ -0,0 +1,832 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{AlphaType, DocumentLayer, PremultipliedColorF, YuvFormat, YuvColorSpace}; +use api::EdgeAaSegmentMask; +use api::units::*; +use crate::spatial_tree::{SpatialTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex}; +use crate::gpu_cache::{GpuCacheAddress, GpuDataRequest}; +use crate::internal_types::FastHashMap; +use crate::render_task::RenderTaskAddress; +use crate::renderer::ShaderColorMode; +use std::i32; +use crate::util::{TransformedRectKind, MatrixHelpers}; +use crate::glyph_rasterizer::SubpixelDirection; +use crate::util::pack_as_float; + +// Contains type that must exactly match the same structures declared in GLSL. + +pub const VECS_PER_TRANSFORM: usize = 8; + +#[derive(Copy, Clone, Debug, PartialEq)] +#[repr(C)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ZBufferId(pub i32); + +const MAX_DOCUMENT_LAYERS : i8 = 1 << 3; +const MAX_DOCUMENT_LAYER_VALUE : i8 = MAX_DOCUMENT_LAYERS / 2 - 1; +const MIN_DOCUMENT_LAYER_VALUE : i8 = -MAX_DOCUMENT_LAYERS / 2; + +impl ZBufferId { + pub fn invalid() -> Self { + ZBufferId(i32::MAX) + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ZBufferIdGenerator { + base: i32, + next: i32, + max_items_per_document_layer: i32, +} + +impl ZBufferIdGenerator { + pub fn new(layer: DocumentLayer, max_depth_ids: i32) -> Self { + debug_assert!(layer >= MIN_DOCUMENT_LAYER_VALUE); + debug_assert!(layer <= MAX_DOCUMENT_LAYER_VALUE); + let max_items_per_document_layer = max_depth_ids / MAX_DOCUMENT_LAYERS as i32; + ZBufferIdGenerator { + base: layer as i32 * max_items_per_document_layer, + next: 0, + max_items_per_document_layer, + } + } + + pub fn next(&mut self) -> ZBufferId { + debug_assert!(self.next < self.max_items_per_document_layer); + let id = ZBufferId(self.next + self.base); + self.next += 1; + id + } +} + +/// A shader kind identifier that can be used by a generic-shader to select the behavior at runtime. +/// +/// Not all brush kinds need to be present in this enum, only those we want to support in the generic +/// brush shader. +/// Do not use the 24 lowest bits. This will be packed with other information in the vertex attributes. +/// The constants must match the corresponding defines in brush_multi.glsl. +#[repr(i32)] +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum BrushShaderKind { + None = 0, + Solid = 1, + Image = 2, + Text = 3, + LinearGradient = 4, + RadialGradient = 5, + ConicGradient = 6, + Blend = 7, + MixBlend = 8, + Yuv = 9, + Opacity = 10, +} + +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[repr(C)] +pub enum RasterizationSpace { + Local = 0, + Screen = 1, +} + +#[derive(Debug, Copy, Clone, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[repr(C)] +pub enum BoxShadowStretchMode { + Stretch = 0, + Simple = 1, +} + +#[repr(i32)] +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum BlurDirection { + Horizontal = 0, + Vertical, +} + +#[derive(Debug)] +#[repr(C)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct BlurInstance { + pub task_address: RenderTaskAddress, + pub src_task_address: RenderTaskAddress, + pub blur_direction: BlurDirection, +} + +#[derive(Debug)] +#[repr(C)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ScalingInstance { + pub target_rect: DeviceRect, + pub source_rect: DeviceIntRect, + pub source_layer: i32, +} + +#[derive(Debug)] +#[repr(C)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct SvgFilterInstance { + pub task_address: RenderTaskAddress, + pub input_1_task_address: RenderTaskAddress, + pub input_2_task_address: RenderTaskAddress, + pub kind: u16, + pub input_count: u16, + pub generic_int: u16, + pub extra_data_address: GpuCacheAddress, +} + +#[derive(Copy, Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)] +#[repr(C)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum BorderSegment { + TopLeft, + TopRight, + BottomRight, + BottomLeft, + Left, + Top, + Right, + Bottom, +} + +#[derive(Debug, Clone)] +#[repr(C)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct BorderInstance { + pub task_origin: DevicePoint, + pub local_rect: DeviceRect, + pub color0: PremultipliedColorF, + pub color1: PremultipliedColorF, + pub flags: i32, + pub widths: DeviceSize, + pub radius: DeviceSize, + pub clip_params: [f32; 8], +} + +/// A clipping primitive drawn into the clipping mask. +/// Could be an image or a rectangle, which defines the +/// way `address` is treated. +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[repr(C)] +pub struct ClipMaskInstance { + pub clip_transform_id: TransformPaletteId, + pub prim_transform_id: TransformPaletteId, + pub clip_data_address: GpuCacheAddress, + pub resource_address: GpuCacheAddress, + pub local_pos: LayoutPoint, + pub tile_rect: LayoutRect, + pub sub_rect: DeviceRect, + pub task_origin: DevicePoint, + pub screen_origin: DevicePoint, + pub device_pixel_scale: f32, +} + +/// A border corner dot or dash drawn into the clipping mask. +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[repr(C)] +pub struct ClipMaskBorderCornerDotDash { + pub clip_mask_instance: ClipMaskInstance, + pub dot_dash_data: [f32; 8], +} + +// 16 bytes per instance should be enough for anyone! +#[derive(Debug, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct PrimitiveInstanceData { + data: [i32; 4], +} + +/// Vertex format for resolve style operations with pixel local storage. +#[derive(Debug, Clone)] +#[repr(C)] +pub struct ResolveInstanceData { + rect: [f32; 4], +} + +impl ResolveInstanceData { + pub fn new(rect: DeviceIntRect) -> Self { + ResolveInstanceData { + rect: [ + rect.origin.x as f32, + rect.origin.y as f32, + rect.size.width as f32, + rect.size.height as f32, + ], + } + } +} + +/// Vertex format for picture cache composite shader. +/// When editing the members, update desc::COMPOSITE +/// so its list of instance_attributes matches: +#[derive(Debug, Clone)] +#[repr(C)] +pub struct CompositeInstance { + // Device space rectangle of surface + rect: DeviceRect, + // Device space clip rect for this surface + clip_rect: DeviceRect, + // Color for solid color tiles, white otherwise + color: PremultipliedColorF, + + // Packed into a single vec4 (aParams) + z_id: f32, + color_space_or_uv_type: f32, // YuvColorSpace for YUV; + // UV coordinate space for RGB + yuv_format: f32, // YuvFormat + yuv_rescale: f32, + + // UV rectangles (pixel space) for color / yuv texture planes + uv_rects: [TexelRect; 3], + + // Texture array layers for color / yuv texture planes + texture_layers: [f32; 3], +} + +impl CompositeInstance { + pub fn new( + rect: DeviceRect, + clip_rect: DeviceRect, + color: PremultipliedColorF, + layer: f32, + z_id: ZBufferId, + ) -> Self { + let uv = TexelRect::new(0.0, 0.0, 1.0, 1.0); + CompositeInstance { + rect, + clip_rect, + color, + z_id: z_id.0 as f32, + color_space_or_uv_type: pack_as_float(0u32), + yuv_format: 0.0, + yuv_rescale: 0.0, + texture_layers: [layer, 0.0, 0.0], + uv_rects: [uv, uv, uv], + } + } + + pub fn new_rgb( + rect: DeviceRect, + clip_rect: DeviceRect, + color: PremultipliedColorF, + layer: f32, + z_id: ZBufferId, + uv_rect: TexelRect, + ) -> Self { + CompositeInstance { + rect, + clip_rect, + color, + z_id: z_id.0 as f32, + color_space_or_uv_type: pack_as_float(1u32), + yuv_format: 0.0, + yuv_rescale: 0.0, + texture_layers: [layer, 0.0, 0.0], + uv_rects: [uv_rect, uv_rect, uv_rect], + } + } + + pub fn new_yuv( + rect: DeviceRect, + clip_rect: DeviceRect, + z_id: ZBufferId, + yuv_color_space: YuvColorSpace, + yuv_format: YuvFormat, + yuv_rescale: f32, + texture_layers: [f32; 3], + uv_rects: [TexelRect; 3], + ) -> Self { + CompositeInstance { + rect, + clip_rect, + color: PremultipliedColorF::WHITE, + z_id: z_id.0 as f32, + color_space_or_uv_type: pack_as_float(yuv_color_space as u32), + yuv_format: pack_as_float(yuv_format as u32), + yuv_rescale, + texture_layers, + uv_rects, + } + } +} + +/// Vertex format for issuing colored quads. +#[derive(Debug, Clone)] +#[repr(C)] +pub struct ClearInstance { + pub rect: [f32; 4], + pub color: [f32; 4], +} + +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct PrimitiveHeaderIndex(pub i32); + +#[derive(Debug)] +#[repr(C)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct PrimitiveHeaders { + // The integer-type headers for a primitive. + pub headers_int: Vec, + // The float-type headers for a primitive. + pub headers_float: Vec, +} + +impl PrimitiveHeaders { + pub fn new() -> PrimitiveHeaders { + PrimitiveHeaders { + headers_int: Vec::new(), + headers_float: Vec::new(), + } + } + + // Add a new primitive header. + pub fn push( + &mut self, + prim_header: &PrimitiveHeader, + z: ZBufferId, + user_data: [i32; 4], + ) -> PrimitiveHeaderIndex { + debug_assert_eq!(self.headers_int.len(), self.headers_float.len()); + let id = self.headers_float.len(); + + self.headers_float.push(PrimitiveHeaderF { + local_rect: prim_header.local_rect, + local_clip_rect: prim_header.local_clip_rect, + }); + + self.headers_int.push(PrimitiveHeaderI { + z, + unused: 0, + specific_prim_address: prim_header.specific_prim_address.as_int(), + transform_id: prim_header.transform_id, + user_data, + }); + + PrimitiveHeaderIndex(id as i32) + } +} + +// This is a convenience type used to make it easier to pass +// the common parts around during batching. +#[derive(Debug)] +pub struct PrimitiveHeader { + pub local_rect: LayoutRect, + pub local_clip_rect: LayoutRect, + pub specific_prim_address: GpuCacheAddress, + pub transform_id: TransformPaletteId, +} + +// f32 parts of a primitive header +#[derive(Debug)] +#[repr(C)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct PrimitiveHeaderF { + pub local_rect: LayoutRect, + pub local_clip_rect: LayoutRect, +} + +// i32 parts of a primitive header +// TODO(gw): Compress parts of these down to u16 +#[derive(Debug)] +#[repr(C)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct PrimitiveHeaderI { + pub z: ZBufferId, + pub specific_prim_address: i32, + pub transform_id: TransformPaletteId, + pub unused: i32, // To ensure required 16 byte alignment of vertex textures + pub user_data: [i32; 4], +} + +pub struct GlyphInstance { + pub prim_header_index: PrimitiveHeaderIndex, +} + +impl GlyphInstance { + pub fn new( + prim_header_index: PrimitiveHeaderIndex, + ) -> Self { + GlyphInstance { + prim_header_index, + } + } + + // TODO(gw): Some of these fields can be moved to the primitive + // header since they are constant, and some can be + // compressed to a smaller size. + pub fn build(&self, + render_task: RenderTaskAddress, + clip_task: RenderTaskAddress, + subpx_dir: SubpixelDirection, + glyph_index_in_text_run: i32, + glyph_uv_rect: GpuCacheAddress, + color_mode: ShaderColorMode, + ) -> PrimitiveInstanceData { + PrimitiveInstanceData { + data: [ + self.prim_header_index.0 as i32, + ((render_task.0 as i32) << 16) + | clip_task.0 as i32, + (subpx_dir as u32 as i32) << 24 + | (color_mode as u32 as i32) << 16 + | glyph_index_in_text_run, + glyph_uv_rect.as_int() + | ((BrushShaderKind::Text as i32) << 24), + ], + } + } +} + +pub struct SplitCompositeInstance { + pub prim_header_index: PrimitiveHeaderIndex, + pub polygons_address: GpuCacheAddress, + pub z: ZBufferId, + pub render_task_address: RenderTaskAddress, +} + +impl From for PrimitiveInstanceData { + fn from(instance: SplitCompositeInstance) -> Self { + PrimitiveInstanceData { + data: [ + instance.prim_header_index.0, + instance.polygons_address.as_int(), + instance.z.0, + instance.render_task_address.0 as i32, + ], + } + } +} + +bitflags! { + /// Flags that define how the common brush shader + /// code should process this instance. + #[cfg_attr(feature = "capture", derive(Serialize))] + #[cfg_attr(feature = "replay", derive(Deserialize))] + #[derive(MallocSizeOf)] + pub struct BrushFlags: u8 { + /// Apply perspective interpolation to UVs + const PERSPECTIVE_INTERPOLATION = 1; + /// Do interpolation relative to segment rect, + /// rather than primitive rect. + const SEGMENT_RELATIVE = 2; + /// Repeat UVs horizontally. + const SEGMENT_REPEAT_X = 4; + /// Repeat UVs vertically. + const SEGMENT_REPEAT_Y = 8; + /// Horizontally follow border-image-repeat: round. + const SEGMENT_REPEAT_X_ROUND = 16; + /// Vertically follow border-image-repeat: round. + const SEGMENT_REPEAT_Y_ROUND = 32; + /// Middle (fill) area of a border-image-repeat. + const SEGMENT_NINEPATCH_MIDDLE = 64; + /// The extra segment data is a texel rect. + const SEGMENT_TEXEL_RECT = 128; + } +} + +/// Convenience structure to encode into PrimitiveInstanceData. +pub struct BrushInstance { + pub prim_header_index: PrimitiveHeaderIndex, + pub render_task_address: RenderTaskAddress, + pub clip_task_address: RenderTaskAddress, + pub segment_index: i32, + pub edge_flags: EdgeAaSegmentMask, + pub brush_flags: BrushFlags, + pub resource_address: i32, + pub brush_kind: BrushShaderKind, +} + +impl From for PrimitiveInstanceData { + fn from(instance: BrushInstance) -> Self { + PrimitiveInstanceData { + data: [ + instance.prim_header_index.0, + ((instance.render_task_address.0 as i32) << 16) + | instance.clip_task_address.0 as i32, + instance.segment_index + | ((instance.edge_flags.bits() as i32) << 16) + | ((instance.brush_flags.bits() as i32) << 24), + instance.resource_address + | ((instance.brush_kind as i32) << 24), + ] + } + } +} + +/// Convenience structure to encode into the image brush's user data. +#[derive(Copy, Clone, Debug)] +pub struct ImageBrushData { + pub color_mode: ShaderColorMode, + pub alpha_type: AlphaType, + pub raster_space: RasterizationSpace, + pub opacity: f32, +} + +impl ImageBrushData { + #[inline] + pub fn encode(&self) -> [i32; 4] { + [ + self.color_mode as i32 | ((self.alpha_type as i32) << 16), + self.raster_space as i32, + get_shader_opacity(self.opacity), + 0, + ] + } +} + +// Represents the information about a transform palette +// entry that is passed to shaders. It includes an index +// into the transform palette, and a set of flags. The +// only flag currently used determines whether the +// transform is axis-aligned (and this should have +// pixel snapping applied). +#[derive(Copy, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[repr(C)] +pub struct TransformPaletteId(pub u32); + +impl TransformPaletteId { + /// Identity transform ID. + pub const IDENTITY: Self = TransformPaletteId(0); + + /// Extract the transform kind from the id. + pub fn transform_kind(&self) -> TransformedRectKind { + if (self.0 >> 24) == 0 { + TransformedRectKind::AxisAligned + } else { + TransformedRectKind::Complex + } + } +} + +/// The GPU data payload for a transform palette entry. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[repr(C)] +pub struct TransformData { + transform: LayoutToPictureTransform, + inv_transform: PictureToLayoutTransform, +} + +impl TransformData { + fn invalid() -> Self { + TransformData { + transform: LayoutToPictureTransform::identity(), + inv_transform: PictureToLayoutTransform::identity(), + } + } +} + +// Extra data stored about each transform palette entry. +#[derive(Clone)] +pub struct TransformMetadata { + transform_kind: TransformedRectKind, +} + +impl TransformMetadata { + pub fn invalid() -> Self { + TransformMetadata { + transform_kind: TransformedRectKind::AxisAligned, + } + } +} + +#[derive(Debug, Hash, Eq, PartialEq)] +struct RelativeTransformKey { + from_index: SpatialNodeIndex, + to_index: SpatialNodeIndex, +} + +// Stores a contiguous list of TransformData structs, that +// are ready for upload to the GPU. +// TODO(gw): For now, this only stores the complete local +// to world transform for each spatial node. In +// the future, the transform palette will support +// specifying a coordinate system that the transform +// should be relative to. +pub struct TransformPalette { + transforms: Vec, + metadata: Vec, + map: FastHashMap, +} + +impl TransformPalette { + pub fn new(count: usize) -> Self { + let _ = VECS_PER_TRANSFORM; + TransformPalette { + transforms: vec![TransformData::invalid(); count], + metadata: vec![TransformMetadata::invalid(); count], + map: FastHashMap::default(), + } + } + + pub fn finish(self) -> Vec { + self.transforms + } + + pub fn set_world_transform( + &mut self, + index: SpatialNodeIndex, + transform: LayoutToWorldTransform, + ) { + register_transform( + &mut self.metadata, + &mut self.transforms, + index, + ROOT_SPATIAL_NODE_INDEX, + // We know the root picture space == world space + transform.with_destination::(), + ); + } + + fn get_index( + &mut self, + child_index: SpatialNodeIndex, + parent_index: SpatialNodeIndex, + spatial_tree: &SpatialTree, + ) -> usize { + if parent_index == ROOT_SPATIAL_NODE_INDEX { + child_index.0 as usize + } else if child_index == parent_index { + 0 + } else { + let key = RelativeTransformKey { + from_index: child_index, + to_index: parent_index, + }; + + let metadata = &mut self.metadata; + let transforms = &mut self.transforms; + + *self.map + .entry(key) + .or_insert_with(|| { + let transform = spatial_tree.get_relative_transform( + child_index, + parent_index, + ) + .into_transform() + .with_destination::(); + + register_transform( + metadata, + transforms, + child_index, + parent_index, + transform, + ) + }) + } + } + + // Get a transform palette id for the given spatial node. + // TODO(gw): In the future, it will be possible to specify + // a coordinate system id here, to allow retrieving + // transforms in the local space of a given spatial node. + pub fn get_id( + &mut self, + from_index: SpatialNodeIndex, + to_index: SpatialNodeIndex, + spatial_tree: &SpatialTree, + ) -> TransformPaletteId { + let index = self.get_index( + from_index, + to_index, + spatial_tree, + ); + let transform_kind = self.metadata[index].transform_kind as u32; + TransformPaletteId( + (index as u32) | + (transform_kind << 24) + ) + } +} + +// Texture cache resources can be either a simple rect, or define +// a polygon within a rect by specifying a UV coordinate for each +// corner. This is useful for rendering screen-space rasterized +// off-screen surfaces. +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum UvRectKind { + // The 2d bounds of the texture cache entry define the + // valid UV space for this texture cache entry. + Rect, + // The four vertices below define a quad within + // the texture cache entry rect. The shader can + // use a bilerp() to correctly interpolate a + // UV coord in the vertex shader. + Quad { + top_left: DeviceHomogeneousVector, + top_right: DeviceHomogeneousVector, + bottom_left: DeviceHomogeneousVector, + bottom_right: DeviceHomogeneousVector, + }, +} + +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ImageSource { + pub p0: DevicePoint, + pub p1: DevicePoint, + pub texture_layer: f32, + pub user_data: [f32; 3], + pub uv_rect_kind: UvRectKind, +} + +impl ImageSource { + pub fn write_gpu_blocks(&self, request: &mut GpuDataRequest) { + // see fetch_image_resource in GLSL + // has to be VECS_PER_IMAGE_RESOURCE vectors + request.push([ + self.p0.x, + self.p0.y, + self.p1.x, + self.p1.y, + ]); + request.push([ + self.texture_layer, + self.user_data[0], + self.user_data[1], + self.user_data[2], + ]); + + // If this is a polygon uv kind, then upload the four vertices. + if let UvRectKind::Quad { top_left, top_right, bottom_left, bottom_right } = self.uv_rect_kind { + // see fetch_image_resource_extra in GLSL + //Note: we really need only 3 components per point here: X, Y, and W + request.push(top_left); + request.push(top_right); + request.push(bottom_left); + request.push(bottom_right); + } + } +} + +// Set the local -> world transform for a given spatial +// node in the transform palette. +fn register_transform( + metadatas: &mut Vec, + transforms: &mut Vec, + from_index: SpatialNodeIndex, + to_index: SpatialNodeIndex, + transform: LayoutToPictureTransform, +) -> usize { + // TODO: refactor the calling code to not even try + // registering a non-invertible transform. + let inv_transform = transform + .inverse() + .unwrap_or_else(PictureToLayoutTransform::identity); + + let metadata = TransformMetadata { + transform_kind: transform.transform_kind() + }; + let data = TransformData { + transform, + inv_transform, + }; + + if to_index == ROOT_SPATIAL_NODE_INDEX { + let index = from_index.0 as usize; + metadatas[index] = metadata; + transforms[index] = data; + index + } else { + let index = transforms.len(); + metadatas.push(metadata); + transforms.push(data); + index + } +} + +pub fn get_shader_opacity(opacity: f32) -> i32 { + (opacity * 65535.0).round() as i32 +} diff --git a/third_party/webrender/webrender/src/hit_test.rs b/third_party/webrender/webrender/src/hit_test.rs new file mode 100644 index 00000000000..4ab02b4124d --- /dev/null +++ b/third_party/webrender/webrender/src/hit_test.rs @@ -0,0 +1,620 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{BorderRadius, ClipMode, HitTestFlags, HitTestItem, HitTestResult, ItemTag, PrimitiveFlags}; +use api::{PipelineId, ApiHitTester}; +use api::units::*; +use crate::clip::{ClipChainId, ClipDataStore, ClipNode, ClipItemKind, ClipStore}; +use crate::clip::{rounded_rectangle_contains_point}; +use crate::spatial_tree::{SpatialNodeIndex, SpatialTree}; +use crate::internal_types::{FastHashMap, LayoutPrimitiveInfo}; +use std::{ops, u32}; +use std::sync::{Arc, Mutex}; +use crate::util::LayoutToWorldFastTransform; + +pub struct SharedHitTester { + // We don't really need a mutex here. We could do with some sort of + // atomic-atomic-ref-counted pointer (an Arc which would let the pointer + // be swapped atomically like an AtomicPtr). + // In practive this shouldn't cause performance issues, though. + hit_tester: Mutex>, +} + +impl SharedHitTester { + pub fn new() -> Self { + SharedHitTester { + hit_tester: Mutex::new(Arc::new(HitTester::empty())), + } + } + + pub fn get_ref(&self) -> Arc { + let guard = self.hit_tester.lock().unwrap(); + Arc::clone(&*guard) + } + + pub(crate) fn update(&self, new_hit_tester: Arc) { + let mut guard = self.hit_tester.lock().unwrap(); + *guard = new_hit_tester; + } +} + +impl ApiHitTester for SharedHitTester { + fn hit_test(&self, + pipeline_id: Option, + point: WorldPoint, + flags: HitTestFlags + ) -> HitTestResult { + self.get_ref().hit_test(HitTest::new(pipeline_id, point, flags)) + } +} + +/// A copy of important spatial node data to use during hit testing. This a copy of +/// data from the SpatialTree that will persist as a new frame is under construction, +/// allowing hit tests consistent with the currently rendered frame. +#[derive(MallocSizeOf)] +pub struct HitTestSpatialNode { + /// The pipeline id of this node. + pipeline_id: PipelineId, + + /// World transform for content transformed by this node. + world_content_transform: LayoutToWorldFastTransform, + + /// World viewport transform for content transformed by this node. + world_viewport_transform: LayoutToWorldFastTransform, + + /// The accumulated external scroll offset for this spatial node. + external_scroll_offset: LayoutVector2D, +} + +#[derive(MallocSizeOf)] +pub struct HitTestClipNode { + /// A particular point must be inside all of these regions to be considered clipped in + /// for the purposes of a hit test. + region: HitTestRegion, +} + +impl HitTestClipNode { + fn new(node: &ClipNode) -> Self { + let region = match node.item.kind { + ClipItemKind::Rectangle { rect, mode } => { + HitTestRegion::Rectangle(rect, mode) + } + ClipItemKind::RoundedRectangle { rect, radius, mode } => { + HitTestRegion::RoundedRectangle(rect, radius, mode) + } + ClipItemKind::Image { rect, .. } => { + HitTestRegion::Rectangle(rect, ClipMode::Clip) + } + ClipItemKind::BoxShadow { .. } => HitTestRegion::Invalid, + }; + + HitTestClipNode { + region, + } + } +} + +#[derive(Debug, Copy, Clone, MallocSizeOf, PartialEq, Eq, Hash)] +pub struct HitTestClipChainId(u32); + +impl HitTestClipChainId { + pub const NONE: Self = HitTestClipChainId(u32::MAX); +} + +/// A hit testing clip chain node is the same as a +/// normal clip chain node, except that the clip +/// node is embedded inside the clip chain, rather +/// than referenced. This means we don't need to +/// copy the complete interned clip data store for +/// hit testing. +#[derive(MallocSizeOf)] +pub struct HitTestClipChainNode { + pub region: HitTestClipNode, + pub spatial_node_index: SpatialNodeIndex, + pub parent_clip_chain_id: HitTestClipChainId, +} + +#[derive(Copy, Clone, Debug, MallocSizeOf)] +pub struct HitTestingClipChainIndex(u32); + +#[derive(Clone, MallocSizeOf)] +pub struct HitTestingItem { + rect: LayoutRect, + clip_rect: LayoutRect, + tag: ItemTag, + is_backface_visible: bool, + #[ignore_malloc_size_of = "simple"] + clip_chain_range: ops::Range, + spatial_node_index: SpatialNodeIndex, +} + +impl HitTestingItem { + pub fn new( + tag: ItemTag, + info: &LayoutPrimitiveInfo, + spatial_node_index: SpatialNodeIndex, + clip_chain_range: ops::Range, + ) -> HitTestingItem { + HitTestingItem { + rect: info.rect, + clip_rect: info.clip_rect, + tag, + is_backface_visible: info.flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE), + spatial_node_index, + clip_chain_range, + } + } +} + +/// Statistics about allocation sizes of current hit tester, +/// used to pre-allocate size of the next hit tester. +pub struct HitTestingSceneStats { + pub clip_chain_roots_count: usize, + pub items_count: usize, +} + +impl HitTestingSceneStats { + pub fn empty() -> Self { + HitTestingSceneStats { + clip_chain_roots_count: 0, + items_count: 0, + } + } +} + +/// Defines the immutable part of a hit tester for a given scene. +/// The hit tester is recreated each time a frame is built, since +/// it relies on the current values of the spatial tree. +/// However, the clip chain and item definitions don't change, +/// so they are created once per scene, and shared between +/// hit tester instances via Arc. +#[derive(MallocSizeOf)] +pub struct HitTestingScene { + /// The list of variable clip chain roots referenced by the items. + pub clip_chain_roots: Vec, + + /// List of hit testing primitives. + pub items: Vec, +} + +impl HitTestingScene { + /// Construct a new hit testing scene, pre-allocating to size + /// provided by previous scene stats. + pub fn new(stats: &HitTestingSceneStats) -> Self { + HitTestingScene { + clip_chain_roots: Vec::with_capacity(stats.clip_chain_roots_count), + items: Vec::with_capacity(stats.items_count), + } + } + + /// Get stats about the current scene allocation sizes. + pub fn get_stats(&self) -> HitTestingSceneStats { + HitTestingSceneStats { + clip_chain_roots_count: self.clip_chain_roots.len(), + items_count: self.items.len(), + } + } + + /// Add a hit testing primitive. + pub fn add_item(&mut self, item: HitTestingItem) { + self.items.push(item); + } + + /// Add a clip chain to the clip chain roots list. + pub fn add_clip_chain(&mut self, clip_chain_id: ClipChainId) { + if clip_chain_id != ClipChainId::INVALID { + self.clip_chain_roots.push(HitTestClipChainId(clip_chain_id.0)); + } + } + + /// Get the slice of clip chain roots for a given hit test primitive. + fn get_clip_chains_for_item(&self, item: &HitTestingItem) -> &[HitTestClipChainId] { + &self.clip_chain_roots[item.clip_chain_range.start.0 as usize .. item.clip_chain_range.end.0 as usize] + } + + /// Get the next index of the clip chain roots list. + pub fn next_clip_chain_index(&self) -> HitTestingClipChainIndex { + HitTestingClipChainIndex(self.clip_chain_roots.len() as u32) + } +} + +#[derive(MallocSizeOf)] +enum HitTestRegion { + Invalid, + Rectangle(LayoutRect, ClipMode), + RoundedRectangle(LayoutRect, BorderRadius, ClipMode), +} + +impl HitTestRegion { + pub fn contains(&self, point: &LayoutPoint) -> bool { + match *self { + HitTestRegion::Rectangle(ref rectangle, ClipMode::Clip) => + rectangle.contains(*point), + HitTestRegion::Rectangle(ref rectangle, ClipMode::ClipOut) => + !rectangle.contains(*point), + HitTestRegion::RoundedRectangle(rect, radii, ClipMode::Clip) => + rounded_rectangle_contains_point(point, &rect, &radii), + HitTestRegion::RoundedRectangle(rect, radii, ClipMode::ClipOut) => + !rounded_rectangle_contains_point(point, &rect, &radii), + HitTestRegion::Invalid => true, + } + } +} + +#[derive(MallocSizeOf)] +pub struct HitTester { + #[ignore_malloc_size_of = "Arc"] + scene: Arc, + spatial_nodes: Vec, + clip_chains: Vec, + pipeline_root_nodes: FastHashMap, +} + +impl HitTester { + pub fn empty() -> Self { + HitTester { + scene: Arc::new(HitTestingScene::new(&HitTestingSceneStats::empty())), + spatial_nodes: Vec::new(), + clip_chains: Vec::new(), + pipeline_root_nodes: FastHashMap::default(), + } + } + + pub fn new( + scene: Arc, + spatial_tree: &SpatialTree, + clip_store: &ClipStore, + clip_data_store: &ClipDataStore, + ) -> HitTester { + let mut hit_tester = HitTester { + scene, + spatial_nodes: Vec::new(), + clip_chains: Vec::new(), + pipeline_root_nodes: FastHashMap::default(), + }; + hit_tester.read_spatial_tree( + spatial_tree, + clip_store, + clip_data_store, + ); + hit_tester + } + + fn read_spatial_tree( + &mut self, + spatial_tree: &SpatialTree, + clip_store: &ClipStore, + clip_data_store: &ClipDataStore, + ) { + self.spatial_nodes.clear(); + self.clip_chains.clear(); + + self.spatial_nodes.reserve(spatial_tree.spatial_nodes.len()); + for (index, node) in spatial_tree.spatial_nodes.iter().enumerate() { + let index = SpatialNodeIndex::new(index); + + // If we haven't already seen a node for this pipeline, record this one as the root + // node. + self.pipeline_root_nodes.entry(node.pipeline_id).or_insert(index); + + //TODO: avoid inverting more than necessary: + // - if the coordinate system is non-invertible, no need to try any of these concrete transforms + // - if there are other places where inversion is needed, let's not repeat the step + + self.spatial_nodes.push(HitTestSpatialNode { + pipeline_id: node.pipeline_id, + world_content_transform: spatial_tree + .get_world_transform(index) + .into_fast_transform(), + world_viewport_transform: spatial_tree + .get_world_viewport_transform(index) + .into_fast_transform(), + external_scroll_offset: spatial_tree.external_scroll_offset(index), + }); + } + + // For each clip chain node, extract the clip node from the clip + // data store, and store it inline with the clip chain node. + self.clip_chains.reserve(clip_store.clip_chain_nodes.len()); + for node in &clip_store.clip_chain_nodes { + let clip_node = &clip_data_store[node.handle]; + self.clip_chains.push(HitTestClipChainNode { + region: HitTestClipNode::new(clip_node), + spatial_node_index: node.spatial_node_index, + parent_clip_chain_id: HitTestClipChainId(node.parent_clip_chain_id.0), + }); + } + } + + fn is_point_clipped_in_for_clip_chain( + &self, + point: WorldPoint, + clip_chain_id: HitTestClipChainId, + test: &mut HitTest + ) -> bool { + if clip_chain_id == HitTestClipChainId::NONE { + return true; + } + + if let Some(result) = test.get_from_clip_chain_cache(clip_chain_id) { + return result == ClippedIn::ClippedIn; + } + + let descriptor = &self.clip_chains[clip_chain_id.0 as usize]; + let parent_clipped_in = self.is_point_clipped_in_for_clip_chain( + point, + descriptor.parent_clip_chain_id, + test, + ); + + if !parent_clipped_in { + test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::NotClippedIn); + return false; + } + + if !self.is_point_clipped_in_for_clip_node( + point, + clip_chain_id, + descriptor.spatial_node_index, + test, + ) { + test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::NotClippedIn); + return false; + } + + test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::ClippedIn); + true + } + + fn is_point_clipped_in_for_clip_node( + &self, + point: WorldPoint, + clip_chain_node_id: HitTestClipChainId, + spatial_node_index: SpatialNodeIndex, + test: &mut HitTest + ) -> bool { + if let Some(clipped_in) = test.node_cache.get(&clip_chain_node_id) { + return *clipped_in == ClippedIn::ClippedIn; + } + + let node = &self.clip_chains[clip_chain_node_id.0 as usize].region; + let transform = self + .spatial_nodes[spatial_node_index.0 as usize] + .world_content_transform; + let transformed_point = match transform + .inverse() + .and_then(|inverted| inverted.transform_point2d(point)) + { + Some(point) => point, + None => { + test.node_cache.insert(clip_chain_node_id, ClippedIn::NotClippedIn); + return false; + } + }; + + if !node.region.contains(&transformed_point) { + test.node_cache.insert(clip_chain_node_id, ClippedIn::NotClippedIn); + return false; + } + + test.node_cache.insert(clip_chain_node_id, ClippedIn::ClippedIn); + true + } + + pub fn find_node_under_point(&self, mut test: HitTest) -> Option { + let point = test.get_absolute_point(self); + let mut current_spatial_node_index = SpatialNodeIndex::INVALID; + let mut point_in_layer = None; + + // For each hit test primitive + for item in self.scene.items.iter().rev() { + let scroll_node = &self.spatial_nodes[item.spatial_node_index.0 as usize]; + + // Update the cached point in layer space, if the spatial node + // changed since last primitive. + if item.spatial_node_index != current_spatial_node_index { + point_in_layer = scroll_node + .world_content_transform + .inverse() + .and_then(|inverted| inverted.transform_point2d(point)); + + current_spatial_node_index = item.spatial_node_index; + } + + // Only consider hit tests on transformable layers. + if let Some(point_in_layer) = point_in_layer { + // If the item's rect or clip rect don't contain this point, + // it's not a valid hit. + if !item.rect.contains(point_in_layer) { + continue; + } + if !item.clip_rect.contains(point_in_layer) { + continue; + } + + // See if any of the clip chain roots for this primitive + // cull out the item. + let clip_chains = self.scene.get_clip_chains_for_item(item); + let mut is_valid = true; + for clip_chain_id in clip_chains { + if !self.is_point_clipped_in_for_clip_chain(point, *clip_chain_id, &mut test) { + is_valid = false; + break; + } + } + + // Found a valid hit test result! + if is_valid { + return Some(item.spatial_node_index); + } + } + } + + None + } + + pub fn hit_test(&self, mut test: HitTest) -> HitTestResult { + let point = test.get_absolute_point(self); + + let mut result = HitTestResult::default(); + let mut current_spatial_node_index = SpatialNodeIndex::INVALID; + let mut point_in_layer = None; + let mut current_root_spatial_node_index = SpatialNodeIndex::INVALID; + let mut point_in_viewport = None; + + // For each hit test primitive + println!("WR: Hit test against {}", self.scene.items.len()); + for item in self.scene.items.iter().rev() { + let scroll_node = &self.spatial_nodes[item.spatial_node_index.0 as usize]; + let pipeline_id = scroll_node.pipeline_id; + match (test.pipeline_id, pipeline_id) { + (Some(id), node_id) if node_id != id => continue, + _ => {}, + } + + // Update the cached point in layer space, if the spatial node + // changed since last primitive. + if item.spatial_node_index != current_spatial_node_index { + point_in_layer = scroll_node + .world_content_transform + .inverse() + .and_then(|inverted| inverted.transform_point2d(point)); + current_spatial_node_index = item.spatial_node_index; + } + + // Only consider hit tests on transformable layers. + if let Some(point_in_layer) = point_in_layer { + // If the item's rect or clip rect don't contain this point, + // it's not a valid hit. + if !item.rect.contains(point_in_layer) { + continue; + } + if !item.clip_rect.contains(point_in_layer) { + continue; + } + + // See if any of the clip chain roots for this primitive + // cull out the item. + let clip_chains = self.scene.get_clip_chains_for_item(item); + let mut is_valid = true; + for clip_chain_id in clip_chains { + if !self.is_point_clipped_in_for_clip_chain(point, *clip_chain_id, &mut test) { + is_valid = false; + break; + } + } + if !is_valid { + continue; + } + + // Don't hit items with backface-visibility:hidden if they are facing the back. + if !item.is_backface_visible && scroll_node.world_content_transform.is_backface_visible() { + continue; + } + + // We need to calculate the position of the test point relative to the origin of + // the pipeline of the hit item. If we cannot get a transformed point, we are + // in a situation with an uninvertible transformation so we should just skip this + // result. + let root_spatial_node_index = self.pipeline_root_nodes[&pipeline_id]; + if root_spatial_node_index != current_root_spatial_node_index { + let root_node = &self.spatial_nodes[root_spatial_node_index.0 as usize]; + point_in_viewport = root_node + .world_viewport_transform + .inverse() + .and_then(|inverted| inverted.transform_point2d(point)) + .map(|pt| pt - scroll_node.external_scroll_offset); + + current_root_spatial_node_index = root_spatial_node_index; + } + + if let Some(point_in_viewport) = point_in_viewport { + result.items.push(HitTestItem { + pipeline: pipeline_id, + tag: item.tag, + point_in_viewport, + point_relative_to_item: point_in_layer - item.rect.origin.to_vector(), + }); + + if !test.flags.contains(HitTestFlags::FIND_ALL) { + return result; + } + } + } + } + + result.items.dedup(); + result + } + + pub fn get_pipeline_root(&self, pipeline_id: PipelineId) -> &HitTestSpatialNode { + &self.spatial_nodes[self.pipeline_root_nodes[&pipeline_id].0 as usize] + } +} + +#[derive(Clone, Copy, MallocSizeOf, PartialEq)] +enum ClippedIn { + ClippedIn, + NotClippedIn, +} + +#[derive(MallocSizeOf)] +pub struct HitTest { + pipeline_id: Option, + point: WorldPoint, + flags: HitTestFlags, + node_cache: FastHashMap, + clip_chain_cache: Vec>, +} + +impl HitTest { + pub fn new( + pipeline_id: Option, + point: WorldPoint, + flags: HitTestFlags, + ) -> HitTest { + HitTest { + pipeline_id, + point, + flags, + node_cache: FastHashMap::default(), + clip_chain_cache: Vec::new(), + } + } + + fn get_from_clip_chain_cache(&mut self, index: HitTestClipChainId) -> Option { + let index = index.0 as usize; + if index >= self.clip_chain_cache.len() { + None + } else { + self.clip_chain_cache[index] + } + } + + fn set_in_clip_chain_cache(&mut self, index: HitTestClipChainId, value: ClippedIn) { + let index = index.0 as usize; + if index >= self.clip_chain_cache.len() { + self.clip_chain_cache.resize(index + 1, None); + } + self.clip_chain_cache[index] = Some(value); + } + + fn get_absolute_point(&self, hit_tester: &HitTester) -> WorldPoint { + if !self.flags.contains(HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT) { + return self.point; + } + + let point = LayoutPoint::new(self.point.x, self.point.y); + self.pipeline_id + .and_then(|id| + hit_tester + .get_pipeline_root(id) + .world_viewport_transform + .transform_point2d(point) + ) + .unwrap_or_else(|| { + WorldPoint::new(self.point.x, self.point.y) + }) + } +} diff --git a/third_party/webrender/webrender/src/intern.rs b/third_party/webrender/webrender/src/intern.rs new file mode 100644 index 00000000000..db7fd0c1c9d --- /dev/null +++ b/third_party/webrender/webrender/src/intern.rs @@ -0,0 +1,377 @@ +/* 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/. */ + +//! The interning module provides a generic data structure +//! interning container. It is similar in concept to a +//! traditional string interning container, but it is +//! specialized to the WR thread model. +//! +//! There is an Interner structure, that lives in the +//! scene builder thread, and a DataStore structure +//! that lives in the frame builder thread. +//! +//! Hashing, interning and handle creation is done by +//! the interner structure during scene building. +//! +//! Delta changes for the interner are pushed during +//! a transaction to the frame builder. The frame builder +//! is then able to access the content of the interned +//! handles quickly, via array indexing. +//! +//! Epoch tracking ensures that the garbage collection +//! step which the interner uses to remove items is +//! only invoked on items that the frame builder thread +//! is no longer referencing. +//! +//! Items in the data store are stored in a traditional +//! free-list structure, for content access and memory +//! usage efficiency. +//! +//! The epoch is incremented each time a scene is +//! built. The most recently used scene epoch is +//! stored inside each handle. This is then used for +//! cache invalidation. + +use crate::internal_types::FastHashMap; +use malloc_size_of::MallocSizeOf; +use crate::profiler::ResourceProfileCounter; +use std::fmt::Debug; +use std::hash::Hash; +use std::marker::PhantomData; +use std::{mem, ops, u64}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use crate::util::VecHelper; + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Copy, Clone, MallocSizeOf, PartialEq)] +struct Epoch(u64); + +/// A list of updates to be applied to the data store, +/// provided by the interning structure. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct UpdateList { + /// Items to insert. + pub insertions: Vec>, + + /// Items to remove. + pub removals: Vec, +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct Insertion { + pub index: usize, + pub uid: ItemUid, + pub value: S, +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct Removal { + pub index: usize, + pub uid: ItemUid, +} + +impl UpdateList { + fn new() -> UpdateList { + UpdateList { + insertions: Vec::new(), + removals: Vec::new(), + } + } + + fn take_and_preallocate(&mut self) -> UpdateList { + UpdateList { + insertions: self.insertions.take_and_preallocate(), + removals: self.removals.take_and_preallocate(), + } + } +} + +lazy_static! { + static ref NEXT_UID: AtomicUsize = AtomicUsize::new(0); +} + +/// A globally, unique identifier +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Copy, Clone, Eq, Hash, MallocSizeOf, PartialEq)] +pub struct ItemUid { + uid: usize, +} + +impl ItemUid { + pub fn next_uid() -> ItemUid { + let uid = NEXT_UID.fetch_add(1, Ordering::Relaxed); + ItemUid { uid } + } + + // Intended for debug usage only + pub fn get_uid(&self) -> usize { + self.uid + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, MallocSizeOf)] +pub struct Handle { + index: u32, + epoch: Epoch, + uid: ItemUid, + _marker: PhantomData, +} + +impl Clone for Handle { + fn clone(&self) -> Self { + Handle { + index: self.index, + epoch: self.epoch, + uid: self.uid, + _marker: self._marker, + } + } +} + +impl Copy for Handle {} + +impl Handle { + pub fn uid(&self) -> ItemUid { + self.uid + } +} + +pub trait InternDebug { + fn on_interned(&self, _uid: ItemUid) {} +} + +/// The data store lives in the frame builder thread. It +/// contains a free-list of items for fast access. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct DataStore { + items: Vec>, +} + +impl Default for DataStore { + fn default() -> Self { + DataStore { + items: Vec::new(), + } + } +} + +impl DataStore { + /// Apply any updates from the scene builder thread to + /// this data store. + pub fn apply_updates( + &mut self, + update_list: UpdateList, + profile_counter: &mut ResourceProfileCounter, + ) { + for insertion in update_list.insertions { + self.items + .entry(insertion.index) + .set(Some(insertion.value.into())); + } + + for removal in update_list.removals { + self.items[removal.index] = None; + } + + let per_item_size = mem::size_of::() + mem::size_of::(); + profile_counter.set(self.items.len(), per_item_size * self.items.len()); + } +} + +/// Retrieve an item from the store via handle +impl ops::Index> for DataStore { + type Output = I::StoreData; + fn index(&self, handle: Handle) -> &I::StoreData { + self.items[handle.index as usize].as_ref().expect("Bad datastore lookup") + } +} + +/// Retrieve a mutable item from the store via handle +/// Retrieve an item from the store via handle +impl ops::IndexMut> for DataStore { + fn index_mut(&mut self, handle: Handle) -> &mut I::StoreData { + self.items[handle.index as usize].as_mut().expect("Bad datastore lookup") + } +} + +/// The main interning data structure. This lives in the +/// scene builder thread, and handles hashing and interning +/// unique data structures. It also manages a free-list for +/// the items in the data store, which is synchronized via +/// an update list of additions / removals. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct Interner { + /// Uniquely map an interning key to a handle + map: FastHashMap>, + /// List of free slots in the data store for re-use. + free_list: Vec, + /// Pending list of updates that need to be applied. + update_list: UpdateList, + /// The current epoch for the interner. + current_epoch: Epoch, + /// The information associated with each interned + /// item that can be accessed by the interner. + local_data: Vec, +} + +impl Default for Interner { + fn default() -> Self { + Interner { + map: FastHashMap::default(), + free_list: Vec::new(), + update_list: UpdateList::new(), + current_epoch: Epoch(1), + local_data: Vec::new(), + } + } +} + +impl Interner { + /// Intern a data structure, and return a handle to + /// that data. The handle can then be stored in the + /// frame builder, and safely accessed via the data + /// store that lives in the frame builder thread. + /// The provided closure is invoked to build the + /// local data about an interned structure if the + /// key isn't already interned. + pub fn intern( + &mut self, + data: &I::Key, + fun: F, + ) -> Handle where F: FnOnce() -> I::InternData { + // Use get_mut rather than entry here to avoid + // cloning the (sometimes large) key in the common + // case, where the data already exists in the interner. + if let Some(handle) = self.map.get_mut(data) { + handle.epoch = self.current_epoch; + return *handle; + } + + // We need to intern a new data item. First, find out + // if there is a spare slot in the free-list that we + // can use. Otherwise, append to the end of the list. + let index = match self.free_list.pop() { + Some(index) => index, + None => self.local_data.len(), + }; + + let uid = ItemUid::next_uid(); + + // Add a pending update to insert the new data. + self.update_list.insertions.push(Insertion { + index, + uid, + value: data.clone(), + }); + + // Generate a handle for access via the data store. + let handle = Handle { + index: index as u32, + epoch: self.current_epoch, + uid, + _marker: PhantomData, + }; + + #[cfg(debug_assertions)] + data.on_interned(handle.uid); + + // Store this handle so the next time it is + // interned, it gets re-used. + self.map.insert(data.clone(), handle); + + // Create the local data for this item that is + // being interned. + self.local_data.entry(index).set(fun()); + + handle + } + + /// Retrieve the pending list of updates for an interner + /// that need to be applied to the data store. Also run + /// a GC step that removes old entries. + pub fn end_frame_and_get_pending_updates(&mut self) -> UpdateList { + let mut update_list = self.update_list.take_and_preallocate(); + + let free_list = &mut self.free_list; + let current_epoch = self.current_epoch.0; + + // First, run a GC step. Walk through the handles, and + // if we find any that haven't been used for some time, + // remove them. If this ever shows up in profiles, we + // can make the GC step partial (scan only part of the + // map each frame). It also might make sense in the + // future to adjust how long items remain in the cache + // based on the current size of the list. + self.map.retain(|_, handle| { + if handle.epoch.0 + 10 < current_epoch { + // To expire an item: + // - Add index to the free-list for re-use. + // - Add an update to the data store to invalidate this slot. + // - Remove from the hash map. + free_list.push(handle.index as usize); + update_list.removals.push(Removal { + index: handle.index as usize, + uid: handle.uid, + }); + return false; + } + + true + }); + + // Begin the next epoch + self.current_epoch = Epoch(self.current_epoch.0 + 1); + + update_list + } +} + +/// Retrieve the local data for an item from the interner via handle +impl ops::Index> for Interner { + type Output = I::InternData; + fn index(&self, handle: Handle) -> &I::InternData { + &self.local_data[handle.index as usize] + } +} + +// The trick to make trait bounds configurable by features. +mod dummy { + #[cfg(not(feature = "capture"))] + pub trait Serialize {} + #[cfg(not(feature = "capture"))] + impl Serialize for T {} + #[cfg(not(feature = "replay"))] + pub trait Deserialize<'a> {} + #[cfg(not(feature = "replay"))] + impl<'a, T> Deserialize<'a> for T {} +} +#[cfg(feature = "capture")] +use serde::Serialize as InternSerialize; +#[cfg(not(feature = "capture"))] +use self::dummy::Serialize as InternSerialize; +#[cfg(feature = "replay")] +use serde::Deserialize as InternDeserialize; +#[cfg(not(feature = "replay"))] +use self::dummy::Deserialize as InternDeserialize; + +/// Implement `Internable` for a type that wants to participate in interning. +pub trait Internable: MallocSizeOf { + type Key: Eq + Hash + Clone + Debug + MallocSizeOf + InternDebug + InternSerialize + for<'a> InternDeserialize<'a>; + type StoreData: From + MallocSizeOf + InternSerialize + for<'a> InternDeserialize<'a>; + type InternData: MallocSizeOf + InternSerialize + for<'a> InternDeserialize<'a>; +} diff --git a/third_party/webrender/webrender/src/internal_types.rs b/third_party/webrender/webrender/src/internal_types.rs new file mode 100644 index 00000000000..bae74efcfa7 --- /dev/null +++ b/third_party/webrender/webrender/src/internal_types.rs @@ -0,0 +1,602 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{ColorF, DebugCommand, DocumentId, ExternalImageData, ExternalImageId, PrimitiveFlags}; +use api::{ImageFormat, ItemTag, NotificationRequest, Shadow, FilterOp}; +use api::units::*; +use api; +use crate::composite::NativeSurfaceOperation; +use crate::device::TextureFilter; +use crate::renderer::PipelineInfo; +use crate::gpu_cache::GpuCacheUpdateList; +use crate::frame_builder::Frame; +use fxhash::FxHasher; +use plane_split::BspSplitter; +use crate::profiler::BackendProfileCounters; +use smallvec::SmallVec; +use std::{usize, i32}; +use std::collections::{HashMap, HashSet}; +use std::f32; +use std::hash::BuildHasherDefault; +use std::path::PathBuf; +use std::sync::Arc; + +#[cfg(any(feature = "capture", feature = "replay"))] +use crate::capture::CaptureConfig; +#[cfg(feature = "capture")] +use crate::capture::ExternalCaptureImage; +#[cfg(feature = "replay")] +use crate::capture::PlainExternalImage; + +pub type FastHashMap = HashMap>; +pub type FastHashSet = HashSet>; + +/// Custom field embedded inside the Polygon struct of the plane-split crate. +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct PlaneSplitAnchor { + pub cluster_index: usize, + pub instance_index: usize, +} + +impl PlaneSplitAnchor { + pub fn new(cluster_index: usize, instance_index: usize) -> Self { + PlaneSplitAnchor { + cluster_index, + instance_index, + } + } +} + +impl Default for PlaneSplitAnchor { + fn default() -> Self { + PlaneSplitAnchor { + cluster_index: 0, + instance_index: 0, + } + } +} + +/// A concrete plane splitter type used in WebRender. +pub type PlaneSplitter = BspSplitter; + +/// An arbitrary number which we assume opacity is invisible below. +const OPACITY_EPSILON: f32 = 0.001; + +/// Equivalent to api::FilterOp with added internal information +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum Filter { + Identity, + Blur(f32), + Brightness(f32), + Contrast(f32), + Grayscale(f32), + HueRotate(f32), + Invert(f32), + Opacity(api::PropertyBinding, f32), + Saturate(f32), + Sepia(f32), + DropShadows(SmallVec<[Shadow; 1]>), + ColorMatrix(Box<[f32; 20]>), + SrgbToLinear, + LinearToSrgb, + ComponentTransfer, + Flood(ColorF), +} + +impl Filter { + pub fn is_visible(&self) -> bool { + match *self { + Filter::Identity | + Filter::Blur(..) | + Filter::Brightness(..) | + Filter::Contrast(..) | + Filter::Grayscale(..) | + Filter::HueRotate(..) | + Filter::Invert(..) | + Filter::Saturate(..) | + Filter::Sepia(..) | + Filter::DropShadows(..) | + Filter::ColorMatrix(..) | + Filter::SrgbToLinear | + Filter::LinearToSrgb | + Filter::ComponentTransfer => true, + Filter::Opacity(_, amount) => { + amount > OPACITY_EPSILON + }, + Filter::Flood(color) => { + color.a > OPACITY_EPSILON + } + } + } + + pub fn is_noop(&self) -> bool { + match *self { + Filter::Identity => false, // this is intentional + Filter::Blur(length) => length == 0.0, + Filter::Brightness(amount) => amount == 1.0, + Filter::Contrast(amount) => amount == 1.0, + Filter::Grayscale(amount) => amount == 0.0, + Filter::HueRotate(amount) => amount == 0.0, + Filter::Invert(amount) => amount == 0.0, + Filter::Opacity(_, amount) => amount >= 1.0, + Filter::Saturate(amount) => amount == 1.0, + Filter::Sepia(amount) => amount == 0.0, + Filter::DropShadows(ref shadows) => { + for shadow in shadows { + if shadow.offset.x != 0.0 || shadow.offset.y != 0.0 || shadow.blur_radius != 0.0 { + return false; + } + } + + true + } + Filter::ColorMatrix(ref matrix) => { + **matrix == [ + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0, + 0.0, 0.0, 0.0, 0.0 + ] + } + Filter::SrgbToLinear | + Filter::LinearToSrgb | + Filter::ComponentTransfer | + Filter::Flood(..) => false, + } + } + + + pub fn as_int(&self) -> i32 { + // Must be kept in sync with brush_blend.glsl + match *self { + Filter::Identity => 0, // matches `Contrast(1)` + Filter::Contrast(..) => 0, + Filter::Grayscale(..) => 1, + Filter::HueRotate(..) => 2, + Filter::Invert(..) => 3, + Filter::Saturate(..) => 4, + Filter::Sepia(..) => 5, + Filter::Brightness(..) => 6, + Filter::ColorMatrix(..) => 7, + Filter::SrgbToLinear => 8, + Filter::LinearToSrgb => 9, + Filter::Flood(..) => 10, + Filter::ComponentTransfer => 11, + Filter::Blur(..) => 12, + Filter::DropShadows(..) => 13, + Filter::Opacity(..) => 14, + } + } +} + +impl From for Filter { + fn from(op: FilterOp) -> Self { + match op { + FilterOp::Identity => Filter::Identity, + FilterOp::Blur(r) => Filter::Blur(r), + FilterOp::Brightness(b) => Filter::Brightness(b), + FilterOp::Contrast(c) => Filter::Contrast(c), + FilterOp::Grayscale(g) => Filter::Grayscale(g), + FilterOp::HueRotate(h) => Filter::HueRotate(h), + FilterOp::Invert(i) => Filter::Invert(i), + FilterOp::Opacity(binding, opacity) => Filter::Opacity(binding, opacity), + FilterOp::Saturate(s) => Filter::Saturate(s), + FilterOp::Sepia(s) => Filter::Sepia(s), + FilterOp::ColorMatrix(mat) => Filter::ColorMatrix(Box::new(mat)), + FilterOp::SrgbToLinear => Filter::SrgbToLinear, + FilterOp::LinearToSrgb => Filter::LinearToSrgb, + FilterOp::ComponentTransfer => Filter::ComponentTransfer, + FilterOp::DropShadow(shadow) => Filter::DropShadows(smallvec![shadow]), + FilterOp::Flood(color) => Filter::Flood(color), + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)] +pub enum Swizzle { + Rgba, + Bgra, +} + +impl Default for Swizzle { + fn default() -> Self { + Swizzle::Rgba + } +} + +/// Swizzle settings of the texture cache. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)] +pub struct SwizzleSettings { + /// Swizzle required on sampling a texture with BGRA8 format. + pub bgra8_sampling_swizzle: Swizzle, +} + +/// An ID for a texture that is owned by the `texture_cache` module. +/// +/// This can include atlases or standalone textures allocated via the texture +/// cache (e.g. if an image is too large to be added to an atlas). The texture +/// cache manages the allocation and freeing of these IDs, and the rendering +/// thread maintains a map from cache texture ID to native texture. +/// +/// We never reuse IDs, so we use a u64 here to be safe. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct CacheTextureId(pub u64); + +/// Canonical type for texture layer indices. +/// +/// WebRender is currently not very consistent about layer index types. Some +/// places use i32 (since that's the type used in various OpenGL APIs), some +/// places use u32 (since having it be signed is non-sensical, but the +/// underlying graphics APIs generally operate on 32-bit integers) and some +/// places use usize (since that's most natural in Rust). +/// +/// Going forward, we aim to us usize throughout the codebase, since that allows +/// operations like indexing without a cast, and convert to the required type in +/// the device module when making calls into the platform layer. +pub type LayerIndex = usize; + +/// Identifies a render pass target that is persisted until the end of the frame. +/// +/// By default, only the targets of the immediately-preceding pass are bound as +/// inputs to the next pass. However, tasks can opt into having their target +/// preserved in a list until the end of the frame, and this type specifies the +/// index in that list. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct SavedTargetIndex(pub usize); + +impl SavedTargetIndex { + pub const PENDING: Self = SavedTargetIndex(!0); +} + +/// Identifies the source of an input texture to a shader. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum TextureSource { + /// Equivalent to `None`, allowing us to avoid using `Option`s everywhere. + Invalid, + /// An entry in the texture cache. + TextureCache(CacheTextureId, Swizzle), + /// An external image texture, mananged by the embedding. + External(ExternalImageData), + /// The alpha target of the immediately-preceding pass. + PrevPassAlpha, + /// The color target of the immediately-preceding pass. + PrevPassColor, + /// A render target from an earlier pass. Unlike the immediately-preceding + /// passes, these are not made available automatically, but are instead + /// opt-in by the `RenderTask` (see `mark_for_saving()`). + RenderTaskCache(SavedTargetIndex, Swizzle), + /// Select a dummy 1x1 white texture. This can be used by image + /// shaders that want to draw a solid color. + Dummy, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct RenderTargetInfo { + pub has_depth: bool, +} + +#[derive(Debug)] +pub enum TextureUpdateSource { + External { + id: ExternalImageId, + channel_index: u8, + }, + Bytes { data: Arc> }, + /// Clears the target area, rather than uploading any pixels. Used when the + /// texture cache debug display is active. + DebugClear, +} + +/// Command to allocate, reallocate, or free a texture for the texture cache. +#[derive(Debug)] +pub struct TextureCacheAllocation { + /// The virtual ID (i.e. distinct from device ID) of the texture. + pub id: CacheTextureId, + /// Details corresponding to the operation in question. + pub kind: TextureCacheAllocationKind, +} + +/// Information used when allocating / reallocating. +#[derive(Debug)] +pub struct TextureCacheAllocInfo { + pub width: i32, + pub height: i32, + pub layer_count: i32, + pub format: ImageFormat, + pub filter: TextureFilter, + /// Indicates whether this corresponds to one of the shared texture caches. + pub is_shared_cache: bool, + /// If true, this texture requires a depth target. + pub has_depth: bool, +} + +/// Sub-operation-specific information for allocation operations. +#[derive(Debug)] +pub enum TextureCacheAllocationKind { + /// Performs an initial texture allocation. + Alloc(TextureCacheAllocInfo), + /// Reallocates the texture. The existing live texture with the same id + /// will be deallocated and its contents blitted over. The new size must + /// be greater than the old size. + Realloc(TextureCacheAllocInfo), + /// Reallocates the texture without preserving its contents. + Reset(TextureCacheAllocInfo), + /// Frees the texture and the corresponding cache ID. + Free, +} + +/// Command to update the contents of the texture cache. +#[derive(Debug)] +pub struct TextureCacheUpdate { + pub rect: DeviceIntRect, + pub stride: Option, + pub offset: i32, + pub layer_index: i32, + pub format_override: Option, + pub source: TextureUpdateSource, +} + +/// Atomic set of commands to manipulate the texture cache, generated on the +/// RenderBackend thread and executed on the Renderer thread. +/// +/// The list of allocation operations is processed before the updates. This is +/// important to allow coalescing of certain allocation operations. +#[derive(Default)] +pub struct TextureUpdateList { + /// Indicates that there was some kind of cleanup clear operation. Used for + /// sanity checks. + pub clears_shared_cache: bool, + /// Commands to alloc/realloc/free the textures. Processed first. + pub allocations: Vec, + /// Commands to update the contents of the textures. Processed second. + pub updates: FastHashMap>, +} + +impl TextureUpdateList { + /// Mints a new `TextureUpdateList`. + pub fn new() -> Self { + TextureUpdateList { + clears_shared_cache: false, + allocations: Vec::new(), + updates: FastHashMap::default(), + } + } + + /// Returns true if this is a no-op (no updates to be applied). + pub fn is_nop(&self) -> bool { + self.allocations.is_empty() && self.updates.is_empty() + } + + /// Sets the clears_shared_cache flag for renderer-side sanity checks. + #[inline] + pub fn note_clear(&mut self) { + self.clears_shared_cache = true; + } + + /// Pushes an update operation onto the list. + #[inline] + pub fn push_update(&mut self, id: CacheTextureId, update: TextureCacheUpdate) { + self.updates + .entry(id) + .or_default() + .push(update); + } + + /// Sends a command to the Renderer to clear the portion of the shared region + /// we just freed. Used when the texture cache debugger is enabled. + #[cold] + pub fn push_debug_clear( + &mut self, + id: CacheTextureId, + origin: DeviceIntPoint, + width: i32, + height: i32, + layer_index: usize + ) { + let size = DeviceIntSize::new(width, height); + let rect = DeviceIntRect::new(origin, size); + self.push_update(id, TextureCacheUpdate { + rect, + stride: None, + offset: 0, + layer_index: layer_index as i32, + format_override: None, + source: TextureUpdateSource::DebugClear, + }); + } + + + /// Pushes an allocation operation onto the list. + pub fn push_alloc(&mut self, id: CacheTextureId, info: TextureCacheAllocInfo) { + debug_assert!(!self.allocations.iter().any(|x| x.id == id)); + self.allocations.push(TextureCacheAllocation { + id, + kind: TextureCacheAllocationKind::Alloc(info), + }); + } + + /// Pushes a reallocation operation onto the list, potentially coalescing + /// with previous operations. + pub fn push_realloc(&mut self, id: CacheTextureId, info: TextureCacheAllocInfo) { + self.debug_assert_coalesced(id); + + // Coallesce this realloc into a previous alloc or realloc, if available. + if let Some(cur) = self.allocations.iter_mut().find(|x| x.id == id) { + match cur.kind { + TextureCacheAllocationKind::Alloc(ref mut i) => *i = info, + TextureCacheAllocationKind::Realloc(ref mut i) => *i = info, + TextureCacheAllocationKind::Reset(ref mut i) => *i = info, + TextureCacheAllocationKind::Free => panic!("Reallocating freed texture"), + } + return + } + + self.allocations.push(TextureCacheAllocation { + id, + kind: TextureCacheAllocationKind::Realloc(info), + }); + } + + /// Pushes a reallocation operation onto the list, potentially coalescing + /// with previous operations. + pub fn push_reset(&mut self, id: CacheTextureId, info: TextureCacheAllocInfo) { + self.debug_assert_coalesced(id); + + // Coallesce this realloc into a previous alloc or realloc, if available. + if let Some(cur) = self.allocations.iter_mut().find(|x| x.id == id) { + match cur.kind { + TextureCacheAllocationKind::Alloc(ref mut i) => *i = info, + TextureCacheAllocationKind::Reset(ref mut i) => *i = info, + TextureCacheAllocationKind::Free => panic!("Resetting freed texture"), + TextureCacheAllocationKind::Realloc(_) => { + // Reset takes precedence over realloc + cur.kind = TextureCacheAllocationKind::Reset(info); + } + } + return + } + + self.allocations.push(TextureCacheAllocation { + id, + kind: TextureCacheAllocationKind::Reset(info), + }); + } + + /// Pushes a free operation onto the list, potentially coalescing with + /// previous operations. + pub fn push_free(&mut self, id: CacheTextureId) { + self.debug_assert_coalesced(id); + + // Drop any unapplied updates to the to-be-freed texture. + self.updates.remove(&id); + + // Drop any allocations for it as well. If we happen to be allocating and + // freeing in the same batch, we can collapse them to a no-op. + let idx = self.allocations.iter().position(|x| x.id == id); + let removed_kind = idx.map(|i| self.allocations.remove(i).kind); + match removed_kind { + Some(TextureCacheAllocationKind::Alloc(..)) => { /* no-op! */ }, + Some(TextureCacheAllocationKind::Free) => panic!("Double free"), + Some(TextureCacheAllocationKind::Realloc(..)) | + Some(TextureCacheAllocationKind::Reset(..)) | + None => { + self.allocations.push(TextureCacheAllocation { + id, + kind: TextureCacheAllocationKind::Free, + }); + } + }; + } + + fn debug_assert_coalesced(&self, id: CacheTextureId) { + debug_assert!( + self.allocations.iter().filter(|x| x.id == id).count() <= 1, + "Allocations should have been coalesced", + ); + } +} + +/// A list of updates built by the render backend that should be applied +/// by the renderer thread. +pub struct ResourceUpdateList { + /// List of OS native surface create / destroy operations to apply. + pub native_surface_updates: Vec, + + /// Atomic set of texture cache updates to apply. + pub texture_updates: TextureUpdateList, +} + +impl ResourceUpdateList { + /// Returns true if this update list has no effect. + pub fn is_nop(&self) -> bool { + self.texture_updates.is_nop() && self.native_surface_updates.is_empty() + } +} + +/// Wraps a frame_builder::Frame, but conceptually could hold more information +pub struct RenderedDocument { + pub frame: Frame, + pub is_new_scene: bool, +} + +pub enum DebugOutput { + FetchDocuments(String), + FetchClipScrollTree(String), + #[cfg(feature = "capture")] + SaveCapture(CaptureConfig, Vec), + #[cfg(feature = "replay")] + LoadCapture(CaptureConfig, Vec), +} + +#[allow(dead_code)] +pub enum ResultMsg { + DebugCommand(DebugCommand), + DebugOutput(DebugOutput), + RefreshShader(PathBuf), + UpdateGpuCache(GpuCacheUpdateList), + UpdateResources { + resource_updates: ResourceUpdateList, + memory_pressure: bool, + }, + PublishPipelineInfo(PipelineInfo), + PublishDocument( + DocumentId, + RenderedDocument, + ResourceUpdateList, + BackendProfileCounters, + ), + AppendNotificationRequests(Vec), + ForceRedraw, +} + +#[derive(Clone, Debug)] +pub struct ResourceCacheError { + description: String, +} + +impl ResourceCacheError { + pub fn new(description: String) -> ResourceCacheError { + ResourceCacheError { + description, + } + } +} + +/// Primitive metadata we pass around in a bunch of places +#[derive(Copy, Clone, Debug)] +pub struct LayoutPrimitiveInfo { + /// NOTE: this is *ideally* redundant with the clip_rect + /// but that's an ongoing project, so for now it exists and is used :( + pub rect: LayoutRect, + pub clip_rect: LayoutRect, + pub flags: PrimitiveFlags, + pub hit_info: Option, +} + +impl LayoutPrimitiveInfo { + pub fn with_clip_rect(rect: LayoutRect, clip_rect: LayoutRect) -> Self { + Self { + rect, + clip_rect, + flags: PrimitiveFlags::default(), + hit_info: None, + } + } +} diff --git a/third_party/webrender/webrender/src/lib.rs b/third_party/webrender/webrender/src/lib.rs new file mode 100644 index 00000000000..965f7dbc89a --- /dev/null +++ b/third_party/webrender/webrender/src/lib.rs @@ -0,0 +1,226 @@ +/* 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/. */ + +/*! +A GPU based renderer for the web. + +It serves as an experimental render backend for [Servo](https://servo.org/), +but it can also be used as such in a standalone application. + +# External dependencies +WebRender currently depends on [FreeType](https://www.freetype.org/) + +# Api Structure +The main entry point to WebRender is the [`crate::Renderer`]. + +By calling [`Renderer::new(...)`](crate::Renderer::new) you get a [`Renderer`], as well as +a [`RenderApiSender`](api::RenderApiSender). Your [`Renderer`] is responsible to render the +previously processed frames onto the screen. + +By calling [`yourRenderApiSender.create_api()`](api::RenderApiSender::create_api), you'll +get a [`RenderApi`](api::RenderApi) instance, which is responsible for managing resources +and documents. A worker thread is used internally to untie the workload from the application +thread and therefore be able to make better use of multicore systems. + +## Frame + +What is referred to as a `frame`, is the current geometry on the screen. +A new Frame is created by calling [`set_display_list()`](api::Transaction::set_display_list) +on the [`RenderApi`](api::RenderApi). When the geometry is processed, the application will be +informed via a [`RenderNotifier`](api::RenderNotifier), a callback which you pass to +[`Renderer::new`]. +More information about [stacking contexts][stacking_contexts]. + +[`set_display_list()`](api::Transaction::set_display_list) also needs to be supplied with +[`BuiltDisplayList`](api::BuiltDisplayList)s. These are obtained by finalizing a +[`DisplayListBuilder`](api::DisplayListBuilder). These are used to draw your geometry. But it +doesn't only contain trivial geometry, it can also store another +[`StackingContext`](api::StackingContext), as they're nestable. + +[stacking_contexts]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context +*/ + +#![cfg_attr(feature = "cargo-clippy", allow(clippy::unreadable_literal, clippy::new_without_default, clippy::too_many_arguments))] + + +// Cribbed from the |matches| crate, for simplicity. +macro_rules! matches { + ($expression:expr, $($pattern:tt)+) => { + match $expression { + $($pattern)+ => true, + _ => false + } + } +} + +#[macro_use] +extern crate bitflags; +#[macro_use] +extern crate cfg_if; +#[macro_use] +extern crate cstr; +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate log; +#[macro_use] +extern crate malloc_size_of_derive; +#[cfg(any(feature = "serde"))] +#[macro_use] +extern crate serde; +#[macro_use] +extern crate tracy_rs; + +extern crate malloc_size_of; +extern crate svg_fmt; + +#[macro_use] +mod profiler; + +mod batch; +mod border; +mod box_shadow; +#[cfg(any(feature = "capture", feature = "replay"))] +mod capture; +mod clip; +mod spatial_tree; +mod composite; +mod debug_colors; +mod debug_font_data; +mod debug_render; +#[cfg(feature = "debugger")] +mod debug_server; +mod device; +mod ellipse; +mod filterdata; +mod frame_builder; +mod freelist; +#[cfg(any(target_os = "macos", target_os = "windows"))] +mod gamma_lut; +mod glyph_cache; +mod glyph_rasterizer; +mod gpu_cache; +mod gpu_types; +mod hit_test; +mod intern; +mod internal_types; +mod lru_cache; +mod picture; +mod prim_store; +mod print_tree; +mod render_backend; +mod render_target; +mod render_task_graph; +mod render_task_cache; +mod render_task; +mod renderer; +mod resource_cache; +mod scene; +mod scene_builder_thread; +mod scene_building; +mod screen_capture; +mod segment; +mod shade; +mod spatial_node; +mod storage; +mod texture_allocator; +mod texture_cache; +mod util; + +mod shader_source { + include!(concat!(env!("OUT_DIR"), "/shaders.rs")); +} + +mod platform { + #[cfg(target_os = "macos")] + pub use crate::platform::macos::font; + #[cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))] + pub use crate::platform::unix::font; + #[cfg(target_os = "windows")] + pub use crate::platform::windows::font; + + #[cfg(target_os = "macos")] + pub mod macos { + pub mod font; + } + #[cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))] + pub mod unix { + pub mod font; + } + #[cfg(target_os = "windows")] + pub mod windows { + pub mod font; + } +} + +#[cfg(target_os = "macos")] +extern crate core_foundation; +#[cfg(target_os = "macos")] +extern crate core_graphics; +#[cfg(target_os = "macos")] +extern crate core_text; + +#[cfg(all(unix, not(target_os = "macos")))] +extern crate freetype; +#[cfg(all(unix, not(target_os = "macos")))] +extern crate libc; + +#[cfg(target_os = "windows")] +extern crate dwrote; + +extern crate bincode; +extern crate byteorder; +pub extern crate euclid; +extern crate fxhash; +extern crate gleam; +extern crate num_traits; +extern crate plane_split; +extern crate rayon; +#[cfg(feature = "ron")] +extern crate ron; +#[cfg(feature = "debugger")] +extern crate serde_json; +#[macro_use] +extern crate smallvec; +extern crate time; +#[cfg(feature = "debugger")] +extern crate ws; +#[cfg(feature = "debugger")] +extern crate image_loader; +#[cfg(feature = "debugger")] +extern crate base64; +#[cfg(all(feature = "capture", feature = "png"))] +extern crate png; +#[cfg(test)] +extern crate rand; + +#[macro_use] +pub extern crate api; +extern crate webrender_build; + +#[doc(hidden)] +pub use crate::composite::{CompositorConfig, Compositor, CompositorCapabilities}; +pub use crate::composite::{NativeSurfaceId, NativeTileId, NativeSurfaceInfo}; +pub use crate::device::{UploadMethod, VertexUsageHint, get_gl_target, get_unoptimized_shader_source}; +pub use crate::device::{ProgramBinary, ProgramCache, ProgramCacheObserver, FormatDesc}; +pub use crate::device::Device; +pub use crate::frame_builder::ChasePrimitive; +pub use crate::prim_store::PrimitiveDebugId; +pub use crate::profiler::{ProfilerHooks, set_profiler_hooks}; +pub use crate::renderer::{ + AsyncPropertySampler, CpuProfile, DebugFlags, GpuProfile, GraphicsApi, + GraphicsApiInfo, PipelineInfo, Renderer, RendererError, RendererOptions, RenderResults, + RendererStats, SceneBuilderHooks, ThreadListener, ShaderPrecacheFlags, + MAX_VERTEX_TEXTURE_WIDTH, +}; +pub use crate::hit_test::SharedHitTester; +pub use crate::internal_types::FastHashMap; +pub use crate::screen_capture::{AsyncScreenshotHandle, RecordedFrameHandle}; +pub use crate::shade::{Shaders, WrShaders}; +pub use api as webrender_api; +pub use webrender_build::shader::ProgramSourceDigest; +pub use crate::picture::{TileDescriptor, TileId, InvalidationReason}; +pub use crate::picture::{PrimitiveCompareResult, PrimitiveCompareResultDetail, CompareHelperResult}; +pub use crate::picture::{TileNode, TileNodeKind, TileSerializer, TileCacheInstanceSerializer, TileOffset, TileCacheLoggerUpdateLists}; +pub use crate::intern::ItemUid; diff --git a/third_party/webrender/webrender/src/lru_cache.rs b/third_party/webrender/webrender/src/lru_cache.rs new file mode 100644 index 00000000000..f741f9fb6ff --- /dev/null +++ b/third_party/webrender/webrender/src/lru_cache.rs @@ -0,0 +1,675 @@ +/* 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 crate::freelist::{FreeList, FreeListHandle, WeakFreeListHandle}; +use std::{mem, num}; + +/* + This module implements a least recently used cache structure, which is + used by the texture cache to manage the lifetime of items inside the + texture cache. It has a few special pieces of functionality that the + texture cache requires, but should be usable as a general LRU cache + type if useful in other areas. + + The cache is implemented with two backing freelists. These allow + random access to the underlying data, while being efficient in both + memory access and allocation patterns. + + The first freelist stores the elements being cached (for example, the + CacheEntry structure for the texture cache). These elements are stored + in arbitrary order, reusing empty slots in the freelist where possible. + + The second freelist stores the LRU tracking information. Although the + tracking elements are stored in arbitrary order inside a freelist for + efficiency, they use next/prev links to represent a doubly-linked list, + kept sorted in order of recent use. The next link is also used to store + the current freelist within the array when the element is not occupied. + */ + +/// Stores the data supplied by the user to be cached, and an index +/// into the LRU tracking freelist for this element. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct LRUCacheEntry { + /// The location of the LRU tracking element for this cache entry. + /// This is None if the entry has manual eviction policy enabled. + lru_index: Option, + /// The cached data provided by the caller for this element. + value: T, +} + +/// The main public interface to the LRU cache +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct LRUCache { + /// A free list of cache entries, and indices into the LRU tracking list + entries: FreeList, M>, + /// The LRU tracking list, allowing O(1) access to the oldest element + lru: LRUTracker>, +} + +impl LRUCache { + /// Construct a new LRU cache + pub fn new() -> Self { + LRUCache { + entries: FreeList::new(), + lru: LRUTracker::new(), + } + } + + /// Insert a new element into the cache. Returns a weak handle for callers to + /// access the data, since the lifetime is managed by the LRU algorithm and it + /// may be evicted at any time. + pub fn push_new( + &mut self, + value: T, + ) -> WeakFreeListHandle { + // It's a slightly awkward process to insert an element, since we don't know + // the index of the LRU tracking element until we've got a handle for the + // underlying cached data. + + // Insert the data provided by the caller + let handle = self.entries.insert(LRUCacheEntry { + lru_index: None, + value, + }); + + // Get a weak handle to return to the caller + let weak_handle = handle.weak(); + + // Add an LRU tracking node that owns the strong handle, and store the location + // of this inside the cache entry. + let entry = self.entries.get_mut(&handle); + entry.lru_index = Some(self.lru.push_new(handle)); + + weak_handle + } + + /// Get immutable access to the data at a given slot. Since this takes a strong + /// handle, it's guaranteed to be valid. + pub fn get( + &self, + handle: &FreeListHandle, + ) -> &T { + &self.entries + .get(handle) + .value + } + + /// Get immutable access to the data at a given slot. Since this takes a weak + /// handle, it may have been evicted, so returns an Option. + pub fn get_opt( + &self, + handle: &WeakFreeListHandle, + ) -> Option<&T> { + self.entries + .get_opt(handle) + .map(|entry| { + &entry.value + }) + } + + /// Get mutable access to the data at a given slot. Since this takes a weak + /// handle, it may have been evicted, so returns an Option. + pub fn get_opt_mut( + &mut self, + handle: &WeakFreeListHandle, + ) -> Option<&mut T> { + self.entries + .get_opt_mut(handle) + .map(|entry| { + &mut entry.value + }) + } + + /// Remove the oldest item from the cache. This is used to select elements to + /// be evicted. If the cache is empty, or all elements in the cache have manual + /// eviction enabled, this will return None + pub fn pop_oldest( + &mut self, + ) -> Option { + self.lru + .pop_front() + .map(|handle| { + let entry = self.entries.free(handle); + // We should only find elements in this list with valid LRU location + debug_assert!(entry.lru_index.is_some()); + entry.value + }) + } + + /// This is a special case of `push_new`, which is a requirement for the texture + /// cache. Sometimes, we want to replace the content of an existing handle if it + /// exists, or insert a new element if the handle is invalid (for example, if an + /// image is resized and it moves to a new location in the texture atlas). This + /// method returns the old cache entry if it existed, so it can be freed by the caller. + #[must_use] + pub fn replace_or_insert( + &mut self, + handle: &mut WeakFreeListHandle, + data: T, + ) -> Option { + match self.entries.get_opt_mut(handle) { + Some(entry) => { + Some(mem::replace(&mut entry.value, data)) + } + None => { + *handle = self.push_new(data); + None + } + } + } + + /// This is used by the calling code to signal that the element that this handle + /// references has been used on this frame. Internally, it updates the links in + /// the LRU tracking element to move this item to the end of the LRU list. Returns + /// the underlying data in case the client wants to mutate it. + pub fn touch( + &mut self, + handle: &WeakFreeListHandle + ) -> Option<&mut T> { + let lru = &mut self.lru; + + self.entries + .get_opt_mut(handle) + .map(|entry| { + // Only have a valid LRU index if eviction mode is auto + if let Some(lru_index) = entry.lru_index { + lru.mark_used(lru_index); + } + + &mut entry.value + }) + } + + /// In some special cases, the caller may want to manually manage the + /// lifetime of a resource. This method removes the LRU tracking information + /// for an element, and returns the strong handle to the caller to manage. + #[must_use] + pub fn set_manual_eviction( + &mut self, + handle: &WeakFreeListHandle, + ) -> Option> { + let entry = self.entries + .get_opt_mut(handle) + .expect("bug: trying to set manual eviction on an invalid handle"); + + // Remove the LRU tracking information from this element, if it exists. + // (it may be None if manual eviction was already enabled for this element). + entry.lru_index.take().map(|lru_index| { + self.lru.remove(lru_index) + }) + } + + /// Remove an element that is in manual eviction mode. This takes the caller + /// managed strong handle, and removes this element from the freelist. + pub fn remove_manual_handle( + &mut self, + handle: FreeListHandle, + ) -> T { + let entry = self.entries.free(handle); + debug_assert_eq!(entry.lru_index, None, "Must be manual eviction mode!"); + entry.value + } + + /// Try to validate that the state of the cache is consistent + #[cfg(test)] + fn validate(&self) { + self.lru.validate(); + } +} + +/// Index of an LRU tracking element +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +struct ItemIndex(num::NonZeroU32); + +impl ItemIndex { + fn as_usize(&self) -> usize { + self.0.get() as usize + } +} + +/// Stores a strong handle controlling the lifetime of the data in the LRU +/// cache, and a doubly-linked list node specifying where in the current LRU +/// order this element exists. These items are themselves backed by a freelist +/// to minimize heap allocations and improve cache access patterns. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug)] +struct Item { + prev: Option, + next: Option, + handle: Option, +} + +/// Internal implementation of the LRU tracking list +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct LRUTracker { + /// Current head of the list - this is the oldest item that will be evicted next. + head: Option, + /// Current tail of the list - this is the most recently used element. + tail: Option, + /// As tracking items are removed, they are stored in a freelist, to minimize heap allocations + free_list_head: Option, + /// The freelist that stores all the LRU tracking items + items: Vec>, +} + +impl LRUTracker where H: std::fmt::Debug { + /// Construct a new LRU tracker + fn new() -> Self { + // Push a dummy entry in the vec that is never used. This ensures the NonZeroU32 + // property is respected, and we never create an ItemIndex(0). + let items = vec![ + Item { + prev: None, + next: None, + handle: None, + }, + ]; + + LRUTracker { + head: None, + tail: None, + free_list_head: None, + items, + } + } + + /// Internal function that takes an item index, and links it to the + /// end of the tracker list (makes it the newest item). + fn link_as_new_tail( + &mut self, + item_index: ItemIndex, + ) { + match (self.head, self.tail) { + (Some(..), Some(tail)) => { + // Both a head and a tail + self.items[item_index.as_usize()].prev = Some(tail); + self.items[item_index.as_usize()].next = None; + + self.items[tail.as_usize()].next = Some(item_index); + self.tail = Some(item_index); + } + (None, None) => { + // No head/tail, currently empty list + self.items[item_index.as_usize()].prev = None; + self.items[item_index.as_usize()].next = None; + + self.head = Some(item_index); + self.tail = Some(item_index); + } + (Some(..), None) | (None, Some(..)) => { + // Invalid state + unreachable!(); + } + } + } + + /// Internal function that takes an LRU item index, and removes it from + /// the current doubly linked list. Used during removal of items, and also + /// when items are moved to the back of the list as they're touched. + fn unlink( + &mut self, + item_index: ItemIndex, + ) { + let (next, prev) = { + let item = &self.items[item_index.as_usize()]; + (item.next, item.prev) + }; + + match next { + Some(next) => { + self.items[next.as_usize()].prev = prev; + } + None => { + debug_assert_eq!(self.tail, Some(item_index)); + self.tail = prev; + } + } + + match prev { + Some(prev) => { + self.items[prev.as_usize()].next = next; + } + None => { + debug_assert_eq!(self.head, Some(item_index)); + self.head = next; + } + } + } + + /// Push a new LRU tracking item on to the back of the list, marking + /// it as the most recent item. + fn push_new( + &mut self, + handle: H, + ) -> ItemIndex { + // See if there is a slot available in the current free list + let item_index = match self.free_list_head { + Some(index) => { + // Reuse an existing slot + let item = &mut self.items[index.as_usize()]; + + assert!(item.handle.is_none()); + item.handle = Some(handle); + + self.free_list_head = item.next; + + index + } + None => { + // No free slots available, push to the end of the array + let index = ItemIndex(num::NonZeroU32::new(self.items.len() as u32).unwrap()); + + self.items.push(Item { + prev: None, + next: None, + handle: Some(handle), + }); + + index + } + }; + + // Now link this element into the LRU list + self.link_as_new_tail(item_index); + + item_index + } + + /// Remove the oldest element from the front of the LRU list. Returns None + /// if the list is empty. + fn pop_front( + &mut self, + ) -> Option { + let handle = match (self.head, self.tail) { + (Some(head), Some(tail)) => { + let item_index = head; + + // Head and tail are the same - removing the only element + if head == tail { + self.head = None; + self.tail = None; + } else { + // Update the head of the list, popping the first element off + let new_head = self.items[head.as_usize()].next.unwrap(); + self.head = Some(new_head); + self.items[new_head.as_usize()].prev = None; + } + + // Add this item to the freelist for later use + self.items[item_index.as_usize()].next = self.free_list_head; + self.free_list_head = Some(item_index); + + // Return the handle to the user + Some(self.items[item_index.as_usize()].handle.take().unwrap()) + } + (None, None) => { + // List is empty + None + } + (Some(..), None) | (None, Some(..)) => { + // Invalid state + unreachable!(); + } + }; + + handle + } + + /// Manually remove an item from the LRU tracking list. This is used + /// when an element switches from having its lifetime managed by the LRU + /// algorithm to having a manual eviction policy. + fn remove( + &mut self, + index: ItemIndex, + ) -> H { + // Remove from the LRU list + self.unlink(index); + + let handle = self.items[index.as_usize()].handle.take().unwrap(); + + // Add LRU item to the freelist for future use. + self.items[index.as_usize()].next = self.free_list_head; + self.free_list_head = Some(index); + + handle + } + + /// Called to mark that an item was used on this frame. It unlinks the + /// tracking item, and then re-links it to the back of the list. + fn mark_used( + &mut self, + index: ItemIndex, + ) { + self.unlink(index); + self.link_as_new_tail(index); + } + + /// Try to validate that the state of the linked lists are consistent + #[cfg(test)] + fn validate(&self) { + use std::collections::HashSet; + + // Must have a valid head/tail or be empty + assert!((self.head.is_none() && self.tail.is_none()) || (self.head.is_some() && self.tail.is_some())); + + // If there is a head, the prev of the head must be none + if let Some(head) = self.head { + assert!(self.items[head.as_usize()].prev.is_none()); + } + + // If there is a tail, the next of the tail must be none + if let Some(tail) = self.tail { + assert!(self.items[tail.as_usize()].next.is_none()); + } + + // Collect all free and valid items, both in forwards and reverse order + let mut free_items = Vec::new(); + let mut free_items_set = HashSet::new(); + let mut valid_items_front = Vec::new(); + let mut valid_items_front_set = HashSet::new(); + let mut valid_items_reverse = Vec::new(); + let mut valid_items_reverse_set = HashSet::new(); + + let mut current = self.free_list_head; + while let Some(index) = current { + let item = &self.items[index.as_usize()]; + free_items.push(index); + assert!(free_items_set.insert(index)); + current = item.next; + } + + current = self.head; + while let Some(index) = current { + let item = &self.items[index.as_usize()]; + valid_items_front.push(index); + assert!(valid_items_front_set.insert(index)); + current = item.next; + } + + current = self.tail; + while let Some(index) = current { + let item = &self.items[index.as_usize()]; + valid_items_reverse.push(index); + assert!(!valid_items_reverse_set.contains(&index)); + valid_items_reverse_set.insert(index); + current = item.prev; + } + + // Ensure set lengths match the vec lengths (should be enforced by the assert check during insert anyway) + assert_eq!(valid_items_front.len(), valid_items_front_set.len()); + assert_eq!(valid_items_reverse.len(), valid_items_reverse_set.len()); + + // Length of the array should equal free + valid items count + 1 (dummy entry) + assert_eq!(free_items.len() + valid_items_front.len() + 1, self.items.len()); + + // Should be same number of items whether iterating forwards or reverse + assert_eq!(valid_items_front.len(), valid_items_reverse.len()); + + // Ensure there are no items considered in the free list that are also in the valid list + assert!(free_items_set.intersection(&valid_items_reverse_set).collect::>().is_empty()); + assert!(free_items_set.intersection(&valid_items_front_set).collect::>().is_empty()); + + // Should be the same number of items regardless of iteration direction + assert_eq!(valid_items_front_set.len(), valid_items_reverse_set.len()); + + // Ensure that the ordering is exactly the same, regardless of iteration direction + for (i0, i1) in valid_items_front.iter().zip(valid_items_reverse.iter().rev()) { + assert_eq!(i0, i1); + } + } +} + +#[test] +fn test_lru_tracker_push_pop() { + // Push elements, pop them all off and ensure: + // - Returned in oldest order + // - pop_oldest returns None after last element popped + struct CacheMarker; + const NUM_ELEMENTS: usize = 50; + + let mut cache: LRUCache = LRUCache::new(); + cache.validate(); + + for i in 0 .. NUM_ELEMENTS { + cache.push_new(i); + } + cache.validate(); + + for i in 0 .. NUM_ELEMENTS { + assert_eq!(cache.pop_oldest(), Some(i)); + } + cache.validate(); + + assert_eq!(cache.pop_oldest(), None); +} + +#[test] +fn test_lru_tracker_push_touch_pop() { + // Push elements, touch even handles, pop them all off and ensure: + // - Returned in correct order + // - pop_oldest returns None after last element popped + struct CacheMarker; + const NUM_ELEMENTS: usize = 50; + + let mut cache: LRUCache = LRUCache::new(); + let mut handles = Vec::new(); + cache.validate(); + + for i in 0 .. NUM_ELEMENTS { + handles.push(cache.push_new(i)); + } + cache.validate(); + + for i in 0 .. NUM_ELEMENTS/2 { + cache.touch(&handles[i*2]); + } + cache.validate(); + + for i in 0 .. NUM_ELEMENTS/2 { + assert_eq!(cache.pop_oldest(), Some(i*2+1)); + } + cache.validate(); + for i in 0 .. NUM_ELEMENTS/2 { + assert_eq!(cache.pop_oldest(), Some(i*2)); + } + cache.validate(); + + assert_eq!(cache.pop_oldest(), None); +} + +#[test] +fn test_lru_tracker_push_get() { + // Push elements, ensure: + // - get access via weak handles works + struct CacheMarker; + const NUM_ELEMENTS: usize = 50; + + let mut cache: LRUCache = LRUCache::new(); + let mut handles = Vec::new(); + cache.validate(); + + for i in 0 .. NUM_ELEMENTS { + handles.push(cache.push_new(i)); + } + cache.validate(); + + for i in 0 .. NUM_ELEMENTS/2 { + assert!(cache.get_opt(&handles[i]) == Some(&i)); + } + cache.validate(); +} + +#[test] +fn test_lru_tracker_push_replace_get() { + // Push elements, replace contents, ensure: + // - each element was replaced with new data correctly + // - replace_or_insert works for invalid handles + struct CacheMarker; + const NUM_ELEMENTS: usize = 50; + + let mut cache: LRUCache = LRUCache::new(); + let mut handles = Vec::new(); + cache.validate(); + + for i in 0 .. NUM_ELEMENTS { + handles.push(cache.push_new(i)); + } + cache.validate(); + + for i in 0 .. NUM_ELEMENTS { + assert_eq!(cache.replace_or_insert(&mut handles[i], i * 2), Some(i)); + } + cache.validate(); + + for i in 0 .. NUM_ELEMENTS/2 { + assert!(cache.get_opt(&handles[i]) == Some(&(i * 2))); + } + cache.validate(); + + let mut empty_handle = WeakFreeListHandle::invalid(); + assert_eq!(cache.replace_or_insert(&mut empty_handle, 100), None); + assert_eq!(cache.get_opt(&empty_handle), Some(&100)); +} + +#[test] +fn test_lru_tracker_manual_evict() { + // Push elements, set even as manual eviction, ensure: + // - correctly pop auto handles in correct order + // - correctly remove manual handles, and have expected value + struct CacheMarker; + const NUM_ELEMENTS: usize = 50; + + let mut cache: LRUCache = LRUCache::new(); + let mut handles = Vec::new(); + let mut manual_handles = Vec::new(); + cache.validate(); + + for i in 0 .. NUM_ELEMENTS { + handles.push(cache.push_new(i)); + } + cache.validate(); + + for i in 0 .. NUM_ELEMENTS/2 { + manual_handles.push(cache.set_manual_eviction(&handles[i*2]).unwrap()); + } + cache.validate(); + + for i in 0 .. NUM_ELEMENTS/2 { + assert!(cache.pop_oldest() == Some(i*2 + 1)); + } + cache.validate(); + + assert!(cache.pop_oldest().is_none()); + + for (i, manual_handle) in manual_handles.drain(..).enumerate() { + assert_eq!(*cache.get(&manual_handle), i*2); + assert_eq!(cache.remove_manual_handle(manual_handle), i*2); + } +} diff --git a/third_party/webrender/webrender/src/picture.rs b/third_party/webrender/webrender/src/picture.rs new file mode 100644 index 00000000000..5b71479b0a5 --- /dev/null +++ b/third_party/webrender/webrender/src/picture.rs @@ -0,0 +1,7185 @@ +/* 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/. */ + +//! A picture represents a dynamically rendered image. +//! +//! # Overview +//! +//! Pictures consists of: +//! +//! - A number of primitives that are drawn onto the picture. +//! - A composite operation describing how to composite this +//! picture into its parent. +//! - A configuration describing how to draw the primitives on +//! this picture (e.g. in screen space or local space). +//! +//! The tree of pictures are generated during scene building. +//! +//! Depending on their composite operations pictures can be rendered into +//! intermediate targets or folded into their parent picture. +//! +//! ## Picture caching +//! +//! Pictures can be cached to reduce the amount of rasterization happening per +//! frame. +//! +//! When picture caching is enabled, the scene is cut into a small number of slices, +//! typically: +//! +//! - content slice +//! - UI slice +//! - background UI slice which is hidden by the other two slices most of the time. +//! +//! Each of these slice is made up of fixed-size large tiles of 2048x512 pixels +//! (or 128x128 for the UI slice). +//! +//! Tiles can be either cached rasterized content into a texture or "clear tiles" +//! that contain only a solid color rectangle rendered directly during the composite +//! pass. +//! +//! ## Invalidation +//! +//! Each tile keeps track of the elements that affect it, which can be: +//! +//! - primitives +//! - clips +//! - image keys +//! - opacity bindings +//! - transforms +//! +//! These dependency lists are built each frame and compared to the previous frame to +//! see if the tile changed. +//! +//! The tile's primitive dependency information is organized in a quadtree, each node +//! storing an index buffer of tile primitive dependencies. +//! +//! The union of the invalidated leaves of each quadtree produces a per-tile dirty rect +//! which defines the scissor rect used when replaying the tile's drawing commands and +//! can be used for partial present. +//! +//! ## Display List shape +//! +//! WR will first look for an iframe item in the root stacking context to apply +//! picture caching to. If that's not found, it will apply to the entire root +//! stacking context of the display list. Apart from that, the format of the +//! display list is not important to picture caching. Each time a new scroll root +//! is encountered, a new picture cache slice will be created. If the display +//! list contains more than some arbitrary number of slices (currently 8), the +//! content will all be squashed into a single slice, in order to save GPU memory +//! and compositing performance. +//! +//! ## Compositor Surfaces +//! +//! Sometimes, a primitive would prefer to exist as a native compositor surface. +//! This allows a large and/or regularly changing primitive (such as a video, or +//! webgl canvas) to be updated each frame without invalidating the content of +//! tiles, and can provide a significant performance win and battery saving. +//! +//! Since drawing a primitive as a compositor surface alters the ordering of +//! primitives in a tile, we use 'overlay tiles' to ensure correctness. If a +//! tile has a compositor surface, _and_ that tile has primitives that overlap +//! the compositor surface rect, the tile switches to be drawn in alpha mode. +//! +//! We rely on only promoting compositor surfaces that are opaque primitives. +//! With this assumption, the tile(s) that intersect the compositor surface get +//! a 'cutout' in the rectangle where the compositor surface exists (not the +//! entire tile), allowing that tile to be drawn as an alpha tile after the +//! compositor surface. +//! +//! Tiles are only drawn in overlay mode if there is content that exists on top +//! of the compositor surface. Otherwise, we can draw the tiles in the normal fast +//! path before the compositor surface is drawn. Use of the per-tile valid and +//! dirty rects ensure that we do a minimal amount of per-pixel work here to +//! blend the overlay tile (this is not always optimal right now, but will be +//! improved as a follow up). + +use api::{MixBlendMode, PipelineId, PremultipliedColorF, FilterPrimitiveKind}; +use api::{PropertyBinding, PropertyBindingId, FilterPrimitive}; +use api::{DebugFlags, RasterSpace, ImageKey, ColorF, ColorU, PrimitiveFlags}; +use api::{ImageRendering, ColorDepth, YuvColorSpace, YuvFormat}; +use api::units::*; +use crate::box_shadow::BLUR_SAMPLE_SCALE; +use crate::clip::{ClipStore, ClipChainInstance, ClipChainId, ClipInstance}; +use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX, + SpatialTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace +}; +use crate::composite::{CompositorKind, CompositeState, NativeSurfaceId, NativeTileId}; +use crate::composite::{ExternalSurfaceDescriptor, ExternalSurfaceDependency}; +use crate::debug_colors; +use euclid::{vec2, vec3, Point2D, Scale, Size2D, Vector2D, Rect, Transform3D, SideOffsets2D}; +use euclid::approxeq::ApproxEq; +use crate::filterdata::SFilterData; +use crate::frame_builder::{FrameBuilderConfig, FrameVisibilityContext, FrameVisibilityState}; +use crate::intern::ItemUid; +use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter, Filter, PlaneSplitAnchor, TextureSource}; +use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext}; +use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle}; +use crate::gpu_types::{UvRectKind, ZBufferId}; +use plane_split::{Clipper, Polygon, Splitter}; +use crate::prim_store::{SpaceMapper, PrimitiveVisibilityMask, PrimitiveTemplateKind}; +use crate::prim_store::{SpaceSnapper, PictureIndex, PrimitiveInstance, PrimitiveInstanceKind}; +use crate::prim_store::{get_raster_rects, PrimitiveScratchBuffer}; +use crate::prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex}; +use crate::prim_store::{ColorBindingStorage, ColorBindingIndex, PrimitiveVisibilityFlags}; +use crate::print_tree::{PrintTree, PrintTreePrinter}; +use crate::render_backend::{DataStores, FrameId}; +use crate::render_task_graph::RenderTaskId; +use crate::render_target::RenderTargetKind; +use crate::render_task::{RenderTask, RenderTaskLocation, BlurTaskCache, ClearMode}; +use crate::resource_cache::{ResourceCache, ImageGeneration}; +use crate::scene::SceneProperties; +use smallvec::SmallVec; +use std::{mem, u8, marker, u32}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::collections::hash_map::Entry; +use crate::texture_cache::TextureCacheHandle; +use crate::util::{MaxRect, VecHelper, RectHelpers, MatrixHelpers}; +use crate::filterdata::{FilterDataHandle}; +#[cfg(any(feature = "capture", feature = "replay"))] +use ron; +#[cfg(feature = "capture")] +use crate::scene_builder_thread::InternerUpdates; +#[cfg(any(feature = "capture", feature = "replay"))] +use crate::intern::{Internable, UpdateList}; +#[cfg(any(feature = "capture", feature = "replay"))] +use api::{ClipIntern, FilterDataIntern, PrimitiveKeyKind}; +#[cfg(any(feature = "capture", feature = "replay"))] +use crate::prim_store::backdrop::Backdrop; +#[cfg(any(feature = "capture", feature = "replay"))] +use crate::prim_store::borders::{ImageBorder, NormalBorderPrim}; +#[cfg(any(feature = "capture", feature = "replay"))] +use crate::prim_store::gradient::{LinearGradient, RadialGradient, ConicGradient}; +#[cfg(any(feature = "capture", feature = "replay"))] +use crate::prim_store::image::{Image, YuvImage}; +#[cfg(any(feature = "capture", feature = "replay"))] +use crate::prim_store::line_dec::LineDecoration; +#[cfg(any(feature = "capture", feature = "replay"))] +use crate::prim_store::picture::Picture; +#[cfg(any(feature = "capture", feature = "replay"))] +use crate::prim_store::text_run::TextRun; + +#[cfg(feature = "capture")] +use std::fs::File; +#[cfg(feature = "capture")] +use std::io::prelude::*; +#[cfg(feature = "capture")] +use std::path::PathBuf; +use crate::scene_building::{SliceFlags}; + +#[cfg(feature = "replay")] +// used by tileview so don't use an internal_types FastHashMap +use std::collections::HashMap; + +// Maximum blur radius for blur filter (different than box-shadow blur). +// Taken from FilterNodeSoftware.cpp in Gecko. +pub const MAX_BLUR_RADIUS: f32 = 100.; + +/// Specify whether a surface allows subpixel AA text rendering. +#[derive(Debug, Clone, PartialEq)] +pub enum SubpixelMode { + /// This surface allows subpixel AA text + Allow, + /// Subpixel AA text cannot be drawn on this surface + Deny, + /// Subpixel AA can be drawn on this surface, if not intersecting + /// with the excluded regions, and inside the allowed rect. + Conditional { + allowed_rect: PictureRect, + excluded_rects: Vec, + }, +} + +/// A comparable transform matrix, that compares with epsilon checks. +#[derive(Debug, Clone)] +struct MatrixKey { + m: [f32; 16], +} + +impl PartialEq for MatrixKey { + fn eq(&self, other: &Self) -> bool { + const EPSILON: f32 = 0.001; + + // TODO(gw): It's possible that we may need to adjust the epsilon + // to be tighter on most of the matrix, except the + // translation parts? + for (i, j) in self.m.iter().zip(other.m.iter()) { + if !i.approx_eq_eps(j, &EPSILON) { + return false; + } + } + + true + } +} + +/// A comparable / hashable version of a coordinate space mapping. Used to determine +/// if a transform dependency for a tile has changed. +#[derive(Debug, PartialEq, Clone)] +enum TransformKey { + Local, + ScaleOffset { + scale_x: f32, + scale_y: f32, + offset_x: f32, + offset_y: f32, + }, + Transform { + m: MatrixKey, + } +} + +impl From> for TransformKey { + fn from(transform: CoordinateSpaceMapping) -> TransformKey { + match transform { + CoordinateSpaceMapping::Local => { + TransformKey::Local + } + CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => { + TransformKey::ScaleOffset { + scale_x: scale_offset.scale.x, + scale_y: scale_offset.scale.y, + offset_x: scale_offset.offset.x, + offset_y: scale_offset.offset.y, + } + } + CoordinateSpaceMapping::Transform(ref m) => { + TransformKey::Transform { + m: MatrixKey { + m: m.to_array(), + }, + } + } + } + } +} + +/// Information about a picture that is pushed / popped on the +/// PictureUpdateState during picture traversal pass. +struct PictureInfo { + /// The spatial node for this picture. + _spatial_node_index: SpatialNodeIndex, +} + +/// Picture-caching state to keep between scenes. +pub struct PictureCacheState { + /// The tiles retained by this picture cache. + pub tiles: FastHashMap>, + /// State of the spatial nodes from previous frame + spatial_node_comparer: SpatialNodeComparer, + /// State of opacity bindings from previous frame + opacity_bindings: FastHashMap, + /// State of color bindings from previous frame + color_bindings: FastHashMap, + /// The current transform of the picture cache root spatial node + root_transform: TransformKey, + /// The current tile size in device pixels + current_tile_size: DeviceIntSize, + /// Various allocations we want to avoid re-doing. + allocations: PictureCacheRecycledAllocations, + /// Currently allocated native compositor surface for this picture cache. + pub native_surface: Option, + /// A cache of compositor surfaces that are retained between display lists + pub external_native_surface_cache: FastHashMap, + /// The retained virtual offset for this slice between display lists. + virtual_offset: DeviceIntPoint, + /// Current frame ID of this picture cache + frame_id: FrameId, +} + +pub struct PictureCacheRecycledAllocations { + old_opacity_bindings: FastHashMap, + old_color_bindings: FastHashMap, + compare_cache: FastHashMap, +} + +/// Stores a list of cached picture tiles that are retained +/// between new scenes. +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct RetainedTiles { + /// The tiles retained between display lists. + #[cfg_attr(feature = "capture", serde(skip))] //TODO + pub caches: FastHashMap, +} + +impl RetainedTiles { + pub fn new() -> Self { + RetainedTiles { + caches: FastHashMap::default(), + } + } + + /// Merge items from one retained tiles into another. + pub fn merge(&mut self, other: RetainedTiles) { + assert!(self.caches.is_empty() || other.caches.is_empty()); + if self.caches.is_empty() { + self.caches = other.caches; + } + } +} + +/// Unit for tile coordinates. +#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub struct TileCoordinate; + +// Geometry types for tile coordinates. +pub type TileOffset = Point2D; +pub type TileSize = Size2D; +pub type TileRect = Rect; + +/// The maximum number of compositor surfaces that are allowed per picture cache. This +/// is an arbitrary number that should be enough for common cases, but low enough to +/// prevent performance and memory usage drastically degrading in pathological cases. +const MAX_COMPOSITOR_SURFACES: usize = 4; + +/// The size in device pixels of a normal cached tile. +pub const TILE_SIZE_DEFAULT: DeviceIntSize = DeviceIntSize { + width: 1024, + height: 512, + _unit: marker::PhantomData, +}; + +/// The size in device pixels of a tile for horizontal scroll bars +pub const TILE_SIZE_SCROLLBAR_HORIZONTAL: DeviceIntSize = DeviceIntSize { + width: 512, + height: 16, + _unit: marker::PhantomData, +}; + +/// The size in device pixels of a tile for vertical scroll bars +pub const TILE_SIZE_SCROLLBAR_VERTICAL: DeviceIntSize = DeviceIntSize { + width: 16, + height: 512, + _unit: marker::PhantomData, +}; + +const TILE_SIZE_FOR_TESTS: [DeviceIntSize; 6] = [ + DeviceIntSize { + width: 128, + height: 128, + _unit: marker::PhantomData, + }, + DeviceIntSize { + width: 256, + height: 256, + _unit: marker::PhantomData, + }, + DeviceIntSize { + width: 512, + height: 512, + _unit: marker::PhantomData, + }, + TILE_SIZE_DEFAULT, + TILE_SIZE_SCROLLBAR_VERTICAL, + TILE_SIZE_SCROLLBAR_HORIZONTAL, +]; + +// Return the list of tile sizes for the renderer to allocate texture arrays for. +pub fn tile_cache_sizes(testing: bool) -> &'static [DeviceIntSize] { + if testing { + &TILE_SIZE_FOR_TESTS + } else { + &[ + TILE_SIZE_DEFAULT, + TILE_SIZE_SCROLLBAR_HORIZONTAL, + TILE_SIZE_SCROLLBAR_VERTICAL, + ] + } +} + +/// The maximum size per axis of a surface, +/// in WorldPixel coordinates. +const MAX_SURFACE_SIZE: f32 = 4096.0; +/// Maximum size of a compositor surface. +const MAX_COMPOSITOR_SURFACES_SIZE: f32 = 8192.0; + +/// The maximum number of sub-dependencies (e.g. clips, transforms) we can handle +/// per-primitive. If a primitive has more than this, it will invalidate every frame. +const MAX_PRIM_SUB_DEPS: usize = u8::MAX as usize; + +/// Used to get unique tile IDs, even when the tile cache is +/// destroyed between display lists / scenes. +static NEXT_TILE_ID: AtomicUsize = AtomicUsize::new(0); + +fn clamp(value: i32, low: i32, high: i32) -> i32 { + value.max(low).min(high) +} + +fn clampf(value: f32, low: f32, high: f32) -> f32 { + value.max(low).min(high) +} + +/// Clamps the blur radius depending on scale factors. +fn clamp_blur_radius(blur_radius: f32, scale_factors: (f32, f32)) -> f32 { + // Clamping must occur after scale factors are applied, but scale factors are not applied + // until later on. To clamp the blur radius, we first apply the scale factors and then clamp + // and finally revert the scale factors. + + // TODO: the clamping should be done on a per-axis basis, but WR currently only supports + // having a single value for both x and y blur. + let largest_scale_factor = f32::max(scale_factors.0, scale_factors.1); + let scaled_blur_radius = blur_radius * largest_scale_factor; + + if scaled_blur_radius > MAX_BLUR_RADIUS { + MAX_BLUR_RADIUS / largest_scale_factor + } else { + // Return the original blur radius to avoid any rounding errors + blur_radius + } +} + +/// An index into the prims array in a TileDescriptor. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct PrimitiveDependencyIndex(pub u32); + +/// Information about the state of a binding. +#[derive(Debug)] +pub struct BindingInfo { + /// The current value retrieved from dynamic scene properties. + value: T, + /// True if it was changed (or is new) since the last frame build. + changed: bool, +} + +/// Information stored in a tile descriptor for a binding. +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum Binding { + Value(T), + Binding(PropertyBindingId), +} + +impl From> for Binding { + fn from(binding: PropertyBinding) -> Binding { + match binding { + PropertyBinding::Binding(key, _) => Binding::Binding(key.id), + PropertyBinding::Value(value) => Binding::Value(value), + } + } +} + +pub type OpacityBinding = Binding; +pub type OpacityBindingInfo = BindingInfo; + +pub type ColorBinding = Binding; +pub type ColorBindingInfo = BindingInfo; + +/// A dependency for a transform is defined by the spatial node index + frame it was used +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct SpatialNodeKey { + spatial_node_index: SpatialNodeIndex, + frame_id: FrameId, +} + +/// A helper for comparing spatial nodes between frames. The comparisons +/// are done by-value, so that if the shape of the spatial node tree +/// changes, invalidations aren't done simply due to the spatial node +/// index changing between display lists. +struct SpatialNodeComparer { + /// The root spatial node index of the tile cache + ref_spatial_node_index: SpatialNodeIndex, + /// Maintains a map of currently active transform keys + spatial_nodes: FastHashMap, + /// A cache of recent comparisons between prev and current spatial nodes + compare_cache: FastHashMap<(SpatialNodeKey, SpatialNodeKey), bool>, + /// A set of frames that we need to retain spatial node entries for + referenced_frames: FastHashSet, +} + +impl SpatialNodeComparer { + /// Construct a new comparer + fn new() -> Self { + SpatialNodeComparer { + ref_spatial_node_index: ROOT_SPATIAL_NODE_INDEX, + spatial_nodes: FastHashMap::default(), + compare_cache: FastHashMap::default(), + referenced_frames: FastHashSet::default(), + } + } + + /// Advance to the next frame + fn next_frame( + &mut self, + ref_spatial_node_index: SpatialNodeIndex, + ) { + // Drop any node information for unreferenced frames, to ensure that the + // hashmap doesn't grow indefinitely! + let referenced_frames = &self.referenced_frames; + self.spatial_nodes.retain(|key, _| { + referenced_frames.contains(&key.frame_id) + }); + + // Update the root spatial node for this comparer + self.ref_spatial_node_index = ref_spatial_node_index; + self.compare_cache.clear(); + self.referenced_frames.clear(); + } + + /// Register a transform that is used, and build the transform key for it if new. + fn register_used_transform( + &mut self, + spatial_node_index: SpatialNodeIndex, + frame_id: FrameId, + spatial_tree: &SpatialTree, + ) { + let key = SpatialNodeKey { + spatial_node_index, + frame_id, + }; + + if let Entry::Vacant(entry) = self.spatial_nodes.entry(key) { + entry.insert( + get_transform_key( + spatial_node_index, + self.ref_spatial_node_index, + spatial_tree, + ) + ); + } + } + + /// Return true if the transforms for two given spatial nodes are considered equivalent + fn are_transforms_equivalent( + &mut self, + prev_spatial_node_key: &SpatialNodeKey, + curr_spatial_node_key: &SpatialNodeKey, + ) -> bool { + let key = (*prev_spatial_node_key, *curr_spatial_node_key); + let spatial_nodes = &self.spatial_nodes; + + *self.compare_cache + .entry(key) + .or_insert_with(|| { + let prev = &spatial_nodes[&prev_spatial_node_key]; + let curr = &spatial_nodes[&curr_spatial_node_key]; + curr == prev + }) + } + + /// Ensure that the comparer won't GC any nodes for a given frame id + fn retain_for_frame(&mut self, frame_id: FrameId) { + self.referenced_frames.insert(frame_id); + } +} + +// Immutable context passed to picture cache tiles during pre_update +struct TilePreUpdateContext { + /// Maps from picture cache coords -> world space coords. + pic_to_world_mapper: SpaceMapper, + + /// The fractional position of the picture cache, which may + /// require invalidation of all tiles. + fract_offset: PictureVector2D, + + /// The optional background color of the picture cache instance + background_color: Option, + + /// The visible part of the screen in world coords. + global_screen_world_rect: WorldRect, + + /// Current size of tiles in picture units. + tile_size: PictureSize, + + /// The current frame id for this picture cache + frame_id: FrameId, +} + +// Immutable context passed to picture cache tiles during post_update +struct TilePostUpdateContext<'a> { + /// Maps from picture cache coords -> world space coords. + pic_to_world_mapper: SpaceMapper, + + /// Global scale factor from world -> device pixels. + global_device_pixel_scale: DevicePixelScale, + + /// The local clip rect (in picture space) of the entire picture cache + local_clip_rect: PictureRect, + + /// The calculated backdrop information for this cache instance. + backdrop: BackdropInfo, + + /// Information about opacity bindings from the picture cache. + opacity_bindings: &'a FastHashMap, + + /// Information about color bindings from the picture cache. + color_bindings: &'a FastHashMap, + + /// Current size in device pixels of tiles for this cache + current_tile_size: DeviceIntSize, + + /// The local rect of the overall picture cache + local_rect: PictureRect, + + /// A list of the external surfaces that are present on this slice + external_surfaces: &'a [ExternalSurfaceDescriptor], + + /// Pre-allocated z-id to assign to opaque tiles during post_update. We + /// use a different z-id for opaque/alpha tiles, so that compositor + /// surfaces (such as videos) can have a z-id between these values, + /// which allows compositor surfaces to occlude opaque tiles, but not + /// alpha tiles. + z_id_opaque: ZBufferId, + + /// Pre-allocated z-id to assign to alpha tiles during post_update + z_id_alpha: ZBufferId, +} + +// Mutable state passed to picture cache tiles during post_update +struct TilePostUpdateState<'a> { + /// Allow access to the texture cache for requesting tiles + resource_cache: &'a mut ResourceCache, + + /// Current configuration and setup for compositing all the picture cache tiles in renderer. + composite_state: &'a mut CompositeState, + + /// A cache of comparison results to avoid re-computation during invalidation. + compare_cache: &'a mut FastHashMap, + + /// Information about transform node differences from last frame. + spatial_node_comparer: &'a mut SpatialNodeComparer, +} + +/// Information about the dependencies of a single primitive instance. +struct PrimitiveDependencyInfo { + /// Unique content identifier of the primitive. + prim_uid: ItemUid, + + /// The (conservative) clipped area in picture space this primitive occupies. + prim_clip_box: PictureBox2D, + + /// Image keys this primitive depends on. + images: SmallVec<[ImageDependency; 8]>, + + /// Opacity bindings this primitive depends on. + opacity_bindings: SmallVec<[OpacityBinding; 4]>, + + /// Color binding this primitive depends on. + color_binding: Option, + + /// Clips that this primitive depends on. + clips: SmallVec<[ItemUid; 8]>, + + /// Spatial nodes references by the clip dependencies of this primitive. + spatial_nodes: SmallVec<[SpatialNodeIndex; 4]>, + + /// If true, this primitive has been promoted to be a compositor surface. + is_compositor_surface: bool, +} + +impl PrimitiveDependencyInfo { + /// Construct dependency info for a new primitive. + fn new( + prim_uid: ItemUid, + prim_clip_box: PictureBox2D, + ) -> Self { + PrimitiveDependencyInfo { + prim_uid, + images: SmallVec::new(), + opacity_bindings: SmallVec::new(), + color_binding: None, + prim_clip_box, + clips: SmallVec::new(), + spatial_nodes: SmallVec::new(), + is_compositor_surface: false, + } + } +} + +/// A stable ID for a given tile, to help debugging. These are also used +/// as unique identifiers for tile surfaces when using a native compositor. +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct TileId(pub usize); + +/// A descriptor for the kind of texture that a picture cache tile will +/// be drawn into. +#[derive(Debug)] +pub enum SurfaceTextureDescriptor { + /// When using the WR compositor, the tile is drawn into an entry + /// in the WR texture cache. + TextureCache { + handle: TextureCacheHandle + }, + /// When using an OS compositor, the tile is drawn into a native + /// surface identified by arbitrary id. + Native { + /// The arbitrary id of this tile. + id: Option, + }, +} + +/// This is the same as a `SurfaceTextureDescriptor` but has been resolved +/// into a texture cache handle (if appropriate) that can be used by the +/// batching and compositing code in the renderer. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum ResolvedSurfaceTexture { + TextureCache { + /// The texture ID to draw to. + texture: TextureSource, + /// Slice index in the texture array to draw to. + layer: i32, + }, + Native { + /// The arbitrary id of this tile. + id: NativeTileId, + /// The size of the tile in device pixels. + size: DeviceIntSize, + } +} + +impl SurfaceTextureDescriptor { + /// Create a resolved surface texture for this descriptor + pub fn resolve( + &self, + resource_cache: &ResourceCache, + size: DeviceIntSize, + ) -> ResolvedSurfaceTexture { + match self { + SurfaceTextureDescriptor::TextureCache { handle } => { + let cache_item = resource_cache.texture_cache.get(handle); + + ResolvedSurfaceTexture::TextureCache { + texture: cache_item.texture_id, + layer: cache_item.texture_layer, + } + } + SurfaceTextureDescriptor::Native { id } => { + ResolvedSurfaceTexture::Native { + id: id.expect("bug: native surface not allocated"), + size, + } + } + } + } +} + +/// The backing surface for this tile. +#[derive(Debug)] +pub enum TileSurface { + Texture { + /// Descriptor for the surface that this tile draws into. + descriptor: SurfaceTextureDescriptor, + /// Bitfield specifying the dirty region(s) that are relevant to this tile. + visibility_mask: PrimitiveVisibilityMask, + }, + Color { + color: ColorF, + }, + Clear, +} + +impl TileSurface { + fn kind(&self) -> &'static str { + match *self { + TileSurface::Color { .. } => "Color", + TileSurface::Texture { .. } => "Texture", + TileSurface::Clear => "Clear", + } + } +} + +/// Optional extra information returned by is_same when +/// logging is enabled. +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum CompareHelperResult { + /// Primitives match + Equal, + /// Counts differ + Count { + prev_count: u8, + curr_count: u8, + }, + /// Sentinel + Sentinel, + /// Two items are not equal + NotEqual { + prev: T, + curr: T, + }, + /// User callback returned true on item + PredicateTrue { + curr: T + }, +} + +/// The result of a primitive dependency comparison. Size is a u8 +/// since this is a hot path in the code, and keeping the data small +/// is a performance win. +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[repr(u8)] +pub enum PrimitiveCompareResult { + /// Primitives match + Equal, + /// Something in the PrimitiveDescriptor was different + Descriptor, + /// The clip node content or spatial node changed + Clip, + /// The value of the transform changed + Transform, + /// An image dependency was dirty + Image, + /// The value of an opacity binding changed + OpacityBinding, + /// The value of a color binding changed + ColorBinding, +} + +/// A more detailed version of PrimitiveCompareResult used when +/// debug logging is enabled. +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum PrimitiveCompareResultDetail { + /// Primitives match + Equal, + /// Something in the PrimitiveDescriptor was different + Descriptor { + old: PrimitiveDescriptor, + new: PrimitiveDescriptor, + }, + /// The clip node content or spatial node changed + Clip { + detail: CompareHelperResult, + }, + /// The value of the transform changed + Transform { + detail: CompareHelperResult, + }, + /// An image dependency was dirty + Image { + detail: CompareHelperResult, + }, + /// The value of an opacity binding changed + OpacityBinding { + detail: CompareHelperResult, + }, + /// The value of a color binding changed + ColorBinding { + detail: CompareHelperResult, + }, +} + +/// Debugging information about why a tile was invalidated +#[derive(Debug,Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum InvalidationReason { + /// The fractional offset changed + FractionalOffset { + old: PictureVector2D, + new: PictureVector2D, + }, + /// The background color changed + BackgroundColor { + old: Option, + new: Option, + }, + /// The opaque state of the backing native surface changed + SurfaceOpacityChanged{ + became_opaque: bool + }, + /// There was no backing texture (evicted or never rendered) + NoTexture, + /// There was no backing native surface (never rendered, or recreated) + NoSurface, + /// The primitive count in the dependency list was different + PrimCount { + old: Option>, + new: Option>, + }, + /// The content of one of the primitives was different + Content { + /// What changed in the primitive that was different + prim_compare_result: PrimitiveCompareResult, + prim_compare_result_detail: Option, + }, + // The compositor type changed + CompositorKindChanged, + // The valid region of the tile changed + ValidRectChanged, +} + +/// A minimal subset of Tile for debug capturing +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct TileSerializer { + pub rect: PictureRect, + pub current_descriptor: TileDescriptor, + pub fract_offset: PictureVector2D, + pub id: TileId, + pub root: TileNode, + pub background_color: Option, + pub invalidation_reason: Option +} + +/// A minimal subset of TileCacheInstance for debug capturing +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct TileCacheInstanceSerializer { + pub slice: usize, + pub tiles: FastHashMap, + pub background_color: Option, + pub fract_offset: PictureVector2D, +} + +/// Information about a cached tile. +pub struct Tile { + /// The grid position of this tile within the picture cache + pub tile_offset: TileOffset, + /// The current world rect of this tile. + pub world_tile_rect: WorldRect, + /// The current local rect of this tile. + pub local_tile_rect: PictureRect, + /// Same as local_tile_rect, but in min/max form as an optimization + pub local_tile_box: PictureBox2D, + /// The picture space dirty rect for this tile. + local_dirty_rect: PictureRect, + /// The device space dirty rect for this tile. + /// TODO(gw): We have multiple dirty rects available due to the quadtree above. In future, + /// expose these as multiple dirty rects, which will help in some cases. + pub device_dirty_rect: DeviceRect, + /// Device space rect that contains valid pixels region of this tile. + pub device_valid_rect: DeviceRect, + /// Uniquely describes the content of this tile, in a way that can be + /// (reasonably) efficiently hashed and compared. + pub current_descriptor: TileDescriptor, + /// The content descriptor for this tile from the previous frame. + pub prev_descriptor: TileDescriptor, + /// Handle to the backing surface for this tile. + pub surface: Option, + /// If true, this tile is marked valid, and the existing texture + /// cache handle can be used. Tiles are invalidated during the + /// build_dirty_regions method. + pub is_valid: bool, + /// If true, this tile intersects with the currently visible screen + /// rect, and will be drawn. + pub is_visible: bool, + /// The current fractional offset of the cache transform root. If this changes, + /// all tiles need to be invalidated and redrawn, since snapping differences are + /// likely to occur. + fract_offset: PictureVector2D, + /// The tile id is stable between display lists and / or frames, + /// if the tile is retained. Useful for debugging tile evictions. + pub id: TileId, + /// If true, the tile was determined to be opaque, which means blending + /// can be disabled when drawing it. + pub is_opaque: bool, + /// Root node of the quadtree dirty rect tracker. + root: TileNode, + /// The last rendered background color on this tile. + background_color: Option, + /// The first reason the tile was invalidated this frame. + invalidation_reason: Option, + /// If true, this tile has one or more compositor surfaces affecting it. + pub has_compositor_surface: bool, + /// The local space valid rect for any primitives found prior to the first compositor + /// surface that affects this tile. + bg_local_valid_rect: PictureBox2D, + /// The local space valid rect for any primitives found after the first compositor + /// surface that affects this tile. + fg_local_valid_rect: PictureBox2D, + /// z-buffer id for this tile, which is one of z_id_opaque or z_id_alpha, depending on tile opacity + pub z_id: ZBufferId, + /// The last frame this tile had its dependencies updated (dependency updating is + /// skipped if a tile is off-screen). + pub last_updated_frame_id: FrameId, +} + +impl Tile { + /// Construct a new, invalid tile. + fn new(tile_offset: TileOffset) -> Self { + let id = TileId(NEXT_TILE_ID.fetch_add(1, Ordering::Relaxed)); + + Tile { + tile_offset, + local_tile_rect: PictureRect::zero(), + local_tile_box: PictureBox2D::zero(), + world_tile_rect: WorldRect::zero(), + device_valid_rect: DeviceRect::zero(), + local_dirty_rect: PictureRect::zero(), + device_dirty_rect: DeviceRect::zero(), + surface: None, + current_descriptor: TileDescriptor::new(), + prev_descriptor: TileDescriptor::new(), + is_valid: false, + is_visible: false, + fract_offset: PictureVector2D::zero(), + id, + is_opaque: false, + root: TileNode::new_leaf(Vec::new()), + background_color: None, + invalidation_reason: None, + has_compositor_surface: false, + bg_local_valid_rect: PictureBox2D::zero(), + fg_local_valid_rect: PictureBox2D::zero(), + z_id: ZBufferId::invalid(), + last_updated_frame_id: FrameId::INVALID, + } + } + + /// Print debug information about this tile to a tree printer. + fn print(&self, pt: &mut dyn PrintTreePrinter) { + pt.new_level(format!("Tile {:?}", self.id)); + pt.add_item(format!("local_tile_rect: {:?}", self.local_tile_rect)); + pt.add_item(format!("fract_offset: {:?}", self.fract_offset)); + pt.add_item(format!("background_color: {:?}", self.background_color)); + pt.add_item(format!("invalidation_reason: {:?}", self.invalidation_reason)); + self.current_descriptor.print(pt); + pt.end_level(); + } + + /// Check if the content of the previous and current tile descriptors match + fn update_dirty_rects( + &mut self, + ctx: &TilePostUpdateContext, + state: &mut TilePostUpdateState, + invalidation_reason: &mut Option, + frame_context: &FrameVisibilityContext, + ) -> PictureRect { + let mut prim_comparer = PrimitiveComparer::new( + &self.prev_descriptor, + &self.current_descriptor, + state.resource_cache, + state.spatial_node_comparer, + ctx.opacity_bindings, + ctx.color_bindings, + ); + + let mut dirty_rect = PictureBox2D::zero(); + self.root.update_dirty_rects( + &self.prev_descriptor.prims, + &self.current_descriptor.prims, + &mut prim_comparer, + &mut dirty_rect, + state.compare_cache, + invalidation_reason, + frame_context, + ); + + dirty_rect.to_rect() + } + + /// Invalidate a tile based on change in content. This + /// must be called even if the tile is not currently + /// visible on screen. We might be able to improve this + /// later by changing how ComparableVec is used. + fn update_content_validity( + &mut self, + ctx: &TilePostUpdateContext, + state: &mut TilePostUpdateState, + frame_context: &FrameVisibilityContext, + ) { + // Check if the contents of the primitives, clips, and + // other dependencies are the same. + state.compare_cache.clear(); + let mut invalidation_reason = None; + let dirty_rect = self.update_dirty_rects( + ctx, + state, + &mut invalidation_reason, + frame_context, + ); + if !dirty_rect.is_empty() { + self.invalidate( + Some(dirty_rect), + invalidation_reason.expect("bug: no invalidation_reason"), + ); + } + // TODO(gw): We can avoid invalidating the whole tile in some cases here, + // but it should be a fairly rare invalidation case. + if self.current_descriptor.local_valid_rect != self.prev_descriptor.local_valid_rect { + self.invalidate(None, InvalidationReason::ValidRectChanged); + state.composite_state.dirty_rects_are_valid = false; + } + } + + /// Invalidate this tile. If `invalidation_rect` is None, the entire + /// tile is invalidated. + fn invalidate( + &mut self, + invalidation_rect: Option, + reason: InvalidationReason, + ) { + self.is_valid = false; + + match invalidation_rect { + Some(rect) => { + self.local_dirty_rect = self.local_dirty_rect.union(&rect); + } + None => { + self.local_dirty_rect = self.local_tile_rect; + } + } + + if self.invalidation_reason.is_none() { + self.invalidation_reason = Some(reason); + } + } + + /// Called during pre_update of a tile cache instance. Allows the + /// tile to setup state before primitive dependency calculations. + fn pre_update( + &mut self, + ctx: &TilePreUpdateContext, + ) { + // Ensure each tile is offset by the appropriate amount from the + // origin, such that the content origin will be a whole number and + // the snapping will be consistent. + self.local_tile_rect = PictureRect::new( + PicturePoint::new( + self.tile_offset.x as f32 * ctx.tile_size.width + ctx.fract_offset.x, + self.tile_offset.y as f32 * ctx.tile_size.height + ctx.fract_offset.y, + ), + ctx.tile_size, + ); + self.local_tile_box = PictureBox2D::new( + self.local_tile_rect.origin, + self.local_tile_rect.bottom_right(), + ); + self.bg_local_valid_rect = PictureBox2D::zero(); + self.fg_local_valid_rect = PictureBox2D::zero(); + self.invalidation_reason = None; + self.has_compositor_surface = false; + + self.world_tile_rect = ctx.pic_to_world_mapper + .map(&self.local_tile_rect) + .expect("bug: map local tile rect"); + + // Check if this tile is currently on screen. + self.is_visible = self.world_tile_rect.intersects(&ctx.global_screen_world_rect); + + // If the tile isn't visible, early exit, skipping the normal set up to + // validate dependencies. Instead, we will only compare the current tile + // dependencies the next time it comes into view. + if !self.is_visible { + return; + } + + // Determine if the fractional offset of the transform is different this frame + // from the currently cached tile set. + let fract_changed = (self.fract_offset.x - ctx.fract_offset.x).abs() > 0.01 || + (self.fract_offset.y - ctx.fract_offset.y).abs() > 0.01; + if fract_changed { + self.invalidate(None, InvalidationReason::FractionalOffset { + old: self.fract_offset, + new: ctx.fract_offset }); + self.fract_offset = ctx.fract_offset; + } + + if ctx.background_color != self.background_color { + self.invalidate(None, InvalidationReason::BackgroundColor { + old: self.background_color, + new: ctx.background_color }); + self.background_color = ctx.background_color; + } + + // Clear any dependencies so that when we rebuild them we + // can compare if the tile has the same content. + mem::swap( + &mut self.current_descriptor, + &mut self.prev_descriptor, + ); + self.current_descriptor.clear(); + self.root.clear(self.local_tile_rect.to_box2d()); + + // Since this tile is determined to be visible, it will get updated + // dependencies, so update the frame id we are storing dependencies for. + self.last_updated_frame_id = ctx.frame_id; + } + + /// Add dependencies for a given primitive to this tile. + fn add_prim_dependency( + &mut self, + info: &PrimitiveDependencyInfo, + ) { + // If this tile isn't currently visible, we don't want to update the dependencies + // for this tile, as an optimization, since it won't be drawn anyway. + if !self.is_visible { + return; + } + + // If this primitive is a compositor surface, any tile it affects must be + // drawn as an overlay tile. + if info.is_compositor_surface { + self.has_compositor_surface = true; + } else { + // Incorporate the bounding rect of the primitive in the local valid rect + // for this tile. This is used to minimize the size of the scissor rect + // during rasterization and the draw rect during composition of partial tiles. + + // Once we have encountered 1+ compositor surfaces affecting this tile, include + // this bounding rect in the foreground. Otherwise, include in the background rect. + // This allows us to determine if we found any primitives that are on top of the + // compositor surface(s) for this tile. If so, we need to draw the tile with alpha + // blending as an overlay. + if self.has_compositor_surface { + self.fg_local_valid_rect = self.fg_local_valid_rect.union(&info.prim_clip_box); + } else { + self.bg_local_valid_rect = self.bg_local_valid_rect.union(&info.prim_clip_box); + } + } + + // Include any image keys this tile depends on. + self.current_descriptor.images.extend_from_slice(&info.images); + + // Include any opacity bindings this primitive depends on. + self.current_descriptor.opacity_bindings.extend_from_slice(&info.opacity_bindings); + + // Include any clip nodes that this primitive depends on. + self.current_descriptor.clips.extend_from_slice(&info.clips); + + // Include any transforms that this primitive depends on. + for spatial_node_index in &info.spatial_nodes { + self.current_descriptor.transforms.push( + SpatialNodeKey { + spatial_node_index: *spatial_node_index, + frame_id: self.last_updated_frame_id, + } + ); + } + + // Include any color bindings this primitive depends on. + if info.color_binding.is_some() { + self.current_descriptor.color_bindings.insert( + self.current_descriptor.color_bindings.len(), info.color_binding.unwrap()); + } + + // TODO(gw): The prim_clip_rect can be impacted by the clip rect of the display port, + // which can cause invalidations when a new display list with changed + // display port is received. To work around this, clamp the prim clip rect + // to the tile boundaries - if the clip hasn't affected the tile, then the + // changed clip can't affect the content of the primitive on this tile. + // In future, we could consider supplying the display port clip from Gecko + // in a different way (e.g. as a scroll frame clip) which still provides + // the desired clip for checkerboarding, but doesn't require this extra + // work below. + + // TODO(gw): This is a hot part of the code - we could probably optimize further by: + // - Using min/max instead of clamps below (if we guarantee the rects are well formed) + + let tile_p0 = self.local_tile_box.min; + let tile_p1 = self.local_tile_box.max; + + let prim_clip_box = PictureBox2D::new( + PicturePoint::new( + clampf(info.prim_clip_box.min.x, tile_p0.x, tile_p1.x), + clampf(info.prim_clip_box.min.y, tile_p0.y, tile_p1.y), + ), + PicturePoint::new( + clampf(info.prim_clip_box.max.x, tile_p0.x, tile_p1.x), + clampf(info.prim_clip_box.max.y, tile_p0.y, tile_p1.y), + ), + ); + + // Update the tile descriptor, used for tile comparison during scene swaps. + let prim_index = PrimitiveDependencyIndex(self.current_descriptor.prims.len() as u32); + + // We know that the casts below will never overflow because the array lengths are + // truncated to MAX_PRIM_SUB_DEPS during update_prim_dependencies. + debug_assert!(info.spatial_nodes.len() <= MAX_PRIM_SUB_DEPS); + debug_assert!(info.clips.len() <= MAX_PRIM_SUB_DEPS); + debug_assert!(info.images.len() <= MAX_PRIM_SUB_DEPS); + debug_assert!(info.opacity_bindings.len() <= MAX_PRIM_SUB_DEPS); + + self.current_descriptor.prims.push(PrimitiveDescriptor { + prim_uid: info.prim_uid, + prim_clip_box, + transform_dep_count: info.spatial_nodes.len() as u8, + clip_dep_count: info.clips.len() as u8, + image_dep_count: info.images.len() as u8, + opacity_binding_dep_count: info.opacity_bindings.len() as u8, + color_binding_dep_count: if info.color_binding.is_some() { 1 } else { 0 } as u8, + }); + + // Add this primitive to the dirty rect quadtree. + self.root.add_prim(prim_index, &info.prim_clip_box); + } + + /// Called during tile cache instance post_update. Allows invalidation and dirty + /// rect calculation after primitive dependencies have been updated. + fn post_update( + &mut self, + ctx: &TilePostUpdateContext, + state: &mut TilePostUpdateState, + frame_context: &FrameVisibilityContext, + ) -> bool { + // Register the frame id of this tile with the spatial node comparer, to ensure + // that it doesn't GC any spatial nodes from the comparer that are referenced + // by this tile. Must be done before we early exit below, so that we retain + // spatial node info even for tiles that are currently not visible. + state.spatial_node_comparer.retain_for_frame(self.last_updated_frame_id); + + // If tile is not visible, just early out from here - we don't update dependencies + // so don't want to invalidate, merge, split etc. The tile won't need to be drawn + // (and thus updated / invalidated) until it is on screen again. + if !self.is_visible { + return false; + } + + // Calculate the overall valid rect for this tile, including both the foreground + // and background local valid rects. + self.current_descriptor.local_valid_rect = + self.bg_local_valid_rect + .union(&self.fg_local_valid_rect) + .to_rect(); + + // TODO(gw): In theory, the local tile rect should always have an + // intersection with the overall picture rect. In practice, + // due to some accuracy issues with how fract_offset (and + // fp accuracy) are used in the calling method, this isn't + // always true. In this case, it's safe to set the local + // valid rect to zero, which means it will be clipped out + // and not affect the scene. In future, we should fix the + // accuracy issue above, so that this assumption holds, but + // it shouldn't have any noticeable effect on performance + // or memory usage (textures should never get allocated). + self.current_descriptor.local_valid_rect = self.local_tile_rect + .intersection(&ctx.local_rect) + .and_then(|r| r.intersection(&self.current_descriptor.local_valid_rect)) + .unwrap_or_else(PictureRect::zero); + + // Invalidate the tile based on the content changing. + self.update_content_validity(ctx, state, frame_context); + + // If there are no primitives there is no need to draw or cache it. + if self.current_descriptor.prims.is_empty() { + // If there is a native compositor surface allocated for this (now empty) tile + // it must be freed here, otherwise the stale tile with previous contents will + // be composited. If the tile subsequently gets new primitives added to it, the + // surface will be re-allocated when it's added to the composite draw list. + if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { mut id, .. }, .. }) = self.surface.take() { + if let Some(id) = id.take() { + state.resource_cache.destroy_compositor_tile(id); + } + } + + self.is_visible = false; + return false; + } + + let world_valid_rect = ctx.pic_to_world_mapper + .map(&self.current_descriptor.local_valid_rect) + .expect("bug: map local valid rect"); + + // The device rect is guaranteed to be aligned on a device pixel - the round + // is just to deal with float accuracy. However, the valid rect is not + // always aligned to a device pixel. To handle this, round out to get all + // required pixels, and intersect with the tile device rect. + let device_rect = (self.world_tile_rect * ctx.global_device_pixel_scale).round(); + self.device_valid_rect = (world_valid_rect * ctx.global_device_pixel_scale) + .round_out() + .intersection(&device_rect) + .unwrap_or_else(DeviceRect::zero); + + // Check if this tile can be considered opaque. Opacity state must be updated only + // after all early out checks have been performed. Otherwise, we might miss updating + // the native surface next time this tile becomes visible. + let clipped_rect = self.current_descriptor.local_valid_rect + .intersection(&ctx.local_clip_rect) + .unwrap_or_else(PictureRect::zero); + let mut is_opaque = ctx.backdrop.opaque_rect.contains_rect(&clipped_rect); + + if self.has_compositor_surface { + // If we found primitive(s) that are ordered _after_ the first compositor + // surface, _and_ intersect with any compositor surface, then we will need + // to draw this tile with alpha blending, as an overlay to the compositor surface. + let fg_world_valid_rect = ctx.pic_to_world_mapper + .map(&self.fg_local_valid_rect.to_rect()) + .expect("bug: map fg local valid rect"); + let fg_device_valid_rect = fg_world_valid_rect * ctx.global_device_pixel_scale; + + for surface in ctx.external_surfaces { + if surface.device_rect.intersects(&fg_device_valid_rect) { + is_opaque = false; + break; + } + } + } + + // Set the correct z_id for this tile based on opacity + if is_opaque { + self.z_id = ctx.z_id_opaque; + } else { + self.z_id = ctx.z_id_alpha; + } + + if is_opaque != self.is_opaque { + // If opacity changed, the native compositor surface and all tiles get invalidated. + // (this does nothing if not using native compositor mode). + // TODO(gw): This property probably changes very rarely, so it is OK to invalidate + // everything in this case. If it turns out that this isn't true, we could + // consider other options, such as per-tile opacity (natively supported + // on CoreAnimation, and supported if backed by non-virtual surfaces in + // DirectComposition). + if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = self.surface { + if let Some(id) = id.take() { + state.resource_cache.destroy_compositor_tile(id); + } + } + + // Invalidate the entire tile to force a redraw. + self.invalidate(None, InvalidationReason::SurfaceOpacityChanged { became_opaque: is_opaque }); + self.is_opaque = is_opaque; + } + + // Check if the selected composite mode supports dirty rect updates. For Draw composite + // mode, we can always update the content with smaller dirty rects. For native composite + // mode, we can only use dirty rects if the compositor supports partial surface updates. + let (supports_dirty_rects, supports_simple_prims) = match state.composite_state.compositor_kind { + CompositorKind::Draw { .. } => { + (true, true) + } + CompositorKind::Native { max_update_rects, .. } => { + (max_update_rects > 0, false) + } + }; + + // TODO(gw): Consider using smaller tiles and/or tile splits for + // native compositors that don't support dirty rects. + if supports_dirty_rects { + // Only allow splitting for normal content sized tiles + if ctx.current_tile_size == TILE_SIZE_DEFAULT { + let max_split_level = 3; + + // Consider splitting / merging dirty regions + self.root.maybe_merge_or_split( + 0, + &self.current_descriptor.prims, + max_split_level, + ); + } + } + + // The dirty rect will be set correctly by now. If the underlying platform + // doesn't support partial updates, and this tile isn't valid, force the dirty + // rect to be the size of the entire tile. + if !self.is_valid && !supports_dirty_rects { + self.local_dirty_rect = self.local_tile_rect; + } + + // See if this tile is a simple color, in which case we can just draw + // it as a rect, and avoid allocating a texture surface and drawing it. + // TODO(gw): Initial native compositor interface doesn't support simple + // color tiles. We can definitely support this in DC, so this + // should be added as a follow up. + let is_simple_prim = + ctx.backdrop.kind.is_some() && + self.current_descriptor.prims.len() == 1 && + self.is_opaque && + supports_simple_prims; + + // Set up the backing surface for this tile. + let surface = if is_simple_prim { + // If we determine the tile can be represented by a color, set the + // surface unconditionally (this will drop any previously used + // texture cache backing surface). + match ctx.backdrop.kind { + Some(BackdropKind::Color { color }) => { + TileSurface::Color { + color, + } + } + Some(BackdropKind::Clear) => { + TileSurface::Clear + } + None => { + // This should be prevented by the is_simple_prim check above. + unreachable!(); + } + } + } else { + // If this tile will be backed by a surface, we want to retain + // the texture handle from the previous frame, if possible. If + // the tile was previously a color, or not set, then just set + // up a new texture cache handle. + match self.surface.take() { + Some(TileSurface::Texture { descriptor, visibility_mask }) => { + // Reuse the existing descriptor and vis mask + TileSurface::Texture { + descriptor, + visibility_mask, + } + } + Some(TileSurface::Color { .. }) | Some(TileSurface::Clear) | None => { + // This is the case where we are constructing a tile surface that + // involves drawing to a texture. Create the correct surface + // descriptor depending on the compositing mode that will read + // the output. + let descriptor = match state.composite_state.compositor_kind { + CompositorKind::Draw { .. } => { + // For a texture cache entry, create an invalid handle that + // will be allocated when update_picture_cache is called. + SurfaceTextureDescriptor::TextureCache { + handle: TextureCacheHandle::invalid(), + } + } + CompositorKind::Native { .. } => { + // Create a native surface surface descriptor, but don't allocate + // a surface yet. The surface is allocated *after* occlusion + // culling occurs, so that only visible tiles allocate GPU memory. + SurfaceTextureDescriptor::Native { + id: None, + } + } + }; + + TileSurface::Texture { + descriptor, + visibility_mask: PrimitiveVisibilityMask::empty(), + } + } + } + }; + + // Store the current surface backing info for use during batching. + self.surface = Some(surface); + + true + } +} + +/// Defines a key that uniquely identifies a primitive instance. +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct PrimitiveDescriptor { + /// Uniquely identifies the content of the primitive template. + pub prim_uid: ItemUid, + /// The clip rect for this primitive. Included here in + /// dependencies since there is no entry in the clip chain + /// dependencies for the local clip rect. + pub prim_clip_box: PictureBox2D, + /// The number of extra dependencies that this primitive has. + transform_dep_count: u8, + image_dep_count: u8, + opacity_binding_dep_count: u8, + clip_dep_count: u8, + color_binding_dep_count: u8, +} + +impl PartialEq for PrimitiveDescriptor { + fn eq(&self, other: &Self) -> bool { + const EPSILON: f32 = 0.001; + + if self.prim_uid != other.prim_uid { + return false; + } + + if !self.prim_clip_box.min.x.approx_eq_eps(&other.prim_clip_box.min.x, &EPSILON) { + return false; + } + if !self.prim_clip_box.min.y.approx_eq_eps(&other.prim_clip_box.min.y, &EPSILON) { + return false; + } + if !self.prim_clip_box.max.x.approx_eq_eps(&other.prim_clip_box.max.x, &EPSILON) { + return false; + } + if !self.prim_clip_box.max.y.approx_eq_eps(&other.prim_clip_box.max.y, &EPSILON) { + return false; + } + + true + } +} + +/// A small helper to compare two arrays of primitive dependencies. +struct CompareHelper<'a, T> where T: Copy { + offset_curr: usize, + offset_prev: usize, + curr_items: &'a [T], + prev_items: &'a [T], +} + +impl<'a, T> CompareHelper<'a, T> where T: Copy + PartialEq { + /// Construct a new compare helper for a current / previous set of dependency information. + fn new( + prev_items: &'a [T], + curr_items: &'a [T], + ) -> Self { + CompareHelper { + offset_curr: 0, + offset_prev: 0, + curr_items, + prev_items, + } + } + + /// Reset the current position in the dependency array to the start + fn reset(&mut self) { + self.offset_prev = 0; + self.offset_curr = 0; + } + + /// Test if two sections of the dependency arrays are the same, by checking both + /// item equality, and a user closure to see if the content of the item changed. + fn is_same( + &self, + prev_count: u8, + curr_count: u8, + mut f: F, + opt_detail: Option<&mut CompareHelperResult>, + ) -> bool where F: FnMut(&T, &T) -> bool { + // If the number of items is different, trivial reject. + if prev_count != curr_count { + if let Some(detail) = opt_detail { *detail = CompareHelperResult::Count{ prev_count, curr_count }; } + return false; + } + // If both counts are 0, then no need to check these dependencies. + if curr_count == 0 { + if let Some(detail) = opt_detail { *detail = CompareHelperResult::Equal; } + return true; + } + // If both counts are u8::MAX, this is a sentinel that we can't compare these + // deps, so just trivial reject. + if curr_count as usize == MAX_PRIM_SUB_DEPS { + if let Some(detail) = opt_detail { *detail = CompareHelperResult::Sentinel; } + return false; + } + + let end_prev = self.offset_prev + prev_count as usize; + let end_curr = self.offset_curr + curr_count as usize; + + let curr_items = &self.curr_items[self.offset_curr .. end_curr]; + let prev_items = &self.prev_items[self.offset_prev .. end_prev]; + + for (curr, prev) in curr_items.iter().zip(prev_items.iter()) { + if !f(prev, curr) { + if let Some(detail) = opt_detail { *detail = CompareHelperResult::PredicateTrue{ curr: *curr }; } + return false; + } + } + + if let Some(detail) = opt_detail { *detail = CompareHelperResult::Equal; } + true + } + + // Advance the prev dependency array by a given amount + fn advance_prev(&mut self, count: u8) { + self.offset_prev += count as usize; + } + + // Advance the current dependency array by a given amount + fn advance_curr(&mut self, count: u8) { + self.offset_curr += count as usize; + } +} + +/// Uniquely describes the content of this tile, in a way that can be +/// (reasonably) efficiently hashed and compared. +#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct TileDescriptor { + /// List of primitive instance unique identifiers. The uid is guaranteed + /// to uniquely describe the content of the primitive template, while + /// the other parameters describe the clip chain and instance params. + pub prims: Vec, + + /// List of clip node descriptors. + clips: Vec, + + /// List of image keys that this tile depends on. + images: Vec, + + /// The set of opacity bindings that this tile depends on. + // TODO(gw): Ugh, get rid of all opacity binding support! + opacity_bindings: Vec, + + /// List of the effects of transforms that we care about + /// tracking for this tile. + transforms: Vec, + + /// Picture space rect that contains valid pixels region of this tile. + local_valid_rect: PictureRect, + + /// List of the effects of color that we care about + /// tracking for this tile. + color_bindings: Vec, +} + +impl TileDescriptor { + fn new() -> Self { + TileDescriptor { + prims: Vec::new(), + clips: Vec::new(), + opacity_bindings: Vec::new(), + images: Vec::new(), + transforms: Vec::new(), + local_valid_rect: PictureRect::zero(), + color_bindings: Vec::new(), + } + } + + /// Print debug information about this tile descriptor to a tree printer. + fn print(&self, pt: &mut dyn PrintTreePrinter) { + pt.new_level("current_descriptor".to_string()); + + pt.new_level("prims".to_string()); + for prim in &self.prims { + pt.new_level(format!("prim uid={}", prim.prim_uid.get_uid())); + pt.add_item(format!("clip: p0={},{} p1={},{}", + prim.prim_clip_box.min.x, + prim.prim_clip_box.min.y, + prim.prim_clip_box.max.x, + prim.prim_clip_box.max.y, + )); + pt.add_item(format!("deps: t={} i={} o={} c={} color={}", + prim.transform_dep_count, + prim.image_dep_count, + prim.opacity_binding_dep_count, + prim.clip_dep_count, + prim.color_binding_dep_count, + )); + pt.end_level(); + } + pt.end_level(); + + if !self.clips.is_empty() { + pt.new_level("clips".to_string()); + for clip in &self.clips { + pt.new_level(format!("clip uid={}", clip.get_uid())); + pt.end_level(); + } + pt.end_level(); + } + + if !self.images.is_empty() { + pt.new_level("images".to_string()); + for info in &self.images { + pt.new_level(format!("key={:?}", info.key)); + pt.add_item(format!("generation={:?}", info.generation)); + pt.end_level(); + } + pt.end_level(); + } + + if !self.opacity_bindings.is_empty() { + pt.new_level("opacity_bindings".to_string()); + for opacity_binding in &self.opacity_bindings { + pt.new_level(format!("binding={:?}", opacity_binding)); + pt.end_level(); + } + pt.end_level(); + } + + if !self.transforms.is_empty() { + pt.new_level("transforms".to_string()); + for transform in &self.transforms { + pt.new_level(format!("spatial_node={:?}", transform)); + pt.end_level(); + } + pt.end_level(); + } + + if !self.color_bindings.is_empty() { + pt.new_level("color_bindings".to_string()); + for color_binding in &self.color_bindings { + pt.new_level(format!("binding={:?}", color_binding)); + pt.end_level(); + } + pt.end_level(); + } + + pt.end_level(); + } + + /// Clear the dependency information for a tile, when the dependencies + /// are being rebuilt. + fn clear(&mut self) { + self.prims.clear(); + self.clips.clear(); + self.opacity_bindings.clear(); + self.images.clear(); + self.transforms.clear(); + self.local_valid_rect = PictureRect::zero(); + self.color_bindings.clear(); + } +} + +/// Stores both the world and devices rects for a single dirty rect. +#[derive(Debug, Clone)] +pub struct DirtyRegionRect { + /// World rect of this dirty region + pub world_rect: WorldRect, + /// Bitfield for picture render tasks that draw this dirty region. + pub visibility_mask: PrimitiveVisibilityMask, +} + +/// Represents the dirty region of a tile cache picture. +#[derive(Debug, Clone)] +pub struct DirtyRegion { + /// The individual dirty rects of this region. + pub dirty_rects: Vec, + + /// The overall dirty rect, a combination of dirty_rects + pub combined: WorldRect, +} + +impl DirtyRegion { + /// Construct a new dirty region tracker. + pub fn new( + ) -> Self { + DirtyRegion { + dirty_rects: Vec::with_capacity(PrimitiveVisibilityMask::MAX_DIRTY_REGIONS), + combined: WorldRect::zero(), + } + } + + /// Reset the dirty regions back to empty + pub fn clear(&mut self) { + self.dirty_rects.clear(); + self.combined = WorldRect::zero(); + } + + /// Push a dirty rect into this region + pub fn push( + &mut self, + rect: WorldRect, + visibility_mask: PrimitiveVisibilityMask, + ) { + // Include this in the overall dirty rect + self.combined = self.combined.union(&rect); + + // Store the individual dirty rect. + self.dirty_rects.push(DirtyRegionRect { + world_rect: rect, + visibility_mask, + }); + } + + /// Include another rect into an existing dirty region. + pub fn include_rect( + &mut self, + region_index: usize, + rect: WorldRect, + ) { + self.combined = self.combined.union(&rect); + + let region = &mut self.dirty_rects[region_index]; + region.world_rect = region.world_rect.union(&rect); + } + + // TODO(gw): This returns a heap allocated object. Perhaps we can simplify this + // logic? Although - it's only used very rarely so it may not be an issue. + pub fn inflate( + &self, + inflate_amount: f32, + ) -> DirtyRegion { + let mut dirty_rects = Vec::with_capacity(self.dirty_rects.len()); + let mut combined = WorldRect::zero(); + + for rect in &self.dirty_rects { + let world_rect = rect.world_rect.inflate(inflate_amount, inflate_amount); + combined = combined.union(&world_rect); + dirty_rects.push(DirtyRegionRect { + world_rect, + visibility_mask: rect.visibility_mask, + }); + } + + DirtyRegion { + dirty_rects, + combined, + } + } + + /// Creates a record of this dirty region for exporting to test infrastructure. + pub fn record(&self) -> RecordedDirtyRegion { + let mut rects: Vec = + self.dirty_rects.iter().map(|r| r.world_rect).collect(); + rects.sort_unstable_by_key(|r| (r.origin.y as usize, r.origin.x as usize)); + RecordedDirtyRegion { rects } + } +} + +/// A recorded copy of the dirty region for exporting to test infrastructure. +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct RecordedDirtyRegion { + pub rects: Vec, +} + +impl ::std::fmt::Display for RecordedDirtyRegion { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + for r in self.rects.iter() { + let (x, y, w, h) = (r.origin.x, r.origin.y, r.size.width, r.size.height); + write!(f, "[({},{}):{}x{}]", x, y, w, h)?; + } + Ok(()) + } +} + +impl ::std::fmt::Debug for RecordedDirtyRegion { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(self, f) + } +} + +#[derive(Debug, Copy, Clone)] +pub enum BackdropKind { + Color { + color: ColorF, + }, + Clear, +} + +/// Stores information about the calculated opaque backdrop of this slice. +#[derive(Debug, Copy, Clone)] +pub struct BackdropInfo { + /// The picture space rectangle that is known to be opaque. This is used + /// to determine where subpixel AA can be used, and where alpha blending + /// can be disabled. + pub opaque_rect: PictureRect, + /// Kind of the backdrop + pub kind: Option, +} + +impl BackdropInfo { + fn empty() -> Self { + BackdropInfo { + opaque_rect: PictureRect::zero(), + kind: None, + } + } +} + +#[derive(Clone)] +pub struct TileCacheLoggerSlice { + pub serialized_slice: String, + pub local_to_world_transform: Transform3D, +} + +#[cfg(any(feature = "capture", feature = "replay"))] +macro_rules! declare_tile_cache_logger_updatelists { + ( $( $name:ident : $ty:ty, )+ ) => { + #[cfg_attr(feature = "capture", derive(Serialize))] + #[cfg_attr(feature = "replay", derive(Deserialize))] + struct TileCacheLoggerUpdateListsSerializer { + pub ron_string: Vec, + } + + pub struct TileCacheLoggerUpdateLists { + $( + /// Generate storage, one per interner. + /// the tuple is a workaround to avoid the need for multiple + /// fields that start with $name (macro concatenation). + /// the string is .ron serialized updatelist at capture time; + /// the updates is the list of DataStore updates (avoid UpdateList + /// due to Default() requirements on the Keys) reconstructed at + /// load time. + pub $name: (Vec, Vec::Key>>), + )+ + } + + impl TileCacheLoggerUpdateLists { + pub fn new() -> Self { + TileCacheLoggerUpdateLists { + $( + $name : ( Vec::new(), Vec::new() ), + )+ + } + } + + /// serialize all interners in updates to .ron + #[cfg(feature = "capture")] + fn serialize_updates( + &mut self, + updates: &InternerUpdates + ) { + $( + self.$name.0.push(ron::ser::to_string_pretty(&updates.$name, Default::default()).unwrap()); + )+ + } + + fn is_empty(&self) -> bool { + $( + if !self.$name.0.is_empty() { return false; } + )+ + true + } + + #[cfg(feature = "capture")] + fn to_ron(&self) -> String { + let mut serializer = + TileCacheLoggerUpdateListsSerializer { ron_string: Vec::new() }; + $( + serializer.ron_string.push( + ron::ser::to_string_pretty(&self.$name.0, Default::default()).unwrap()); + )+ + ron::ser::to_string_pretty(&serializer, Default::default()).unwrap() + } + + #[cfg(feature = "replay")] + pub fn from_ron(&mut self, text: &str) { + let serializer : TileCacheLoggerUpdateListsSerializer = + match ron::de::from_str(&text) { + Ok(data) => { data } + Err(e) => { + println!("ERROR: failed to deserialize updatelist: {:?}\n{:?}", &text, e); + return; + } + }; + let mut index = 0; + $( + let ron_lists : Vec = ron::de::from_str(&serializer.ron_string[index]).unwrap(); + self.$name.1 = ron_lists.iter() + .map( |list| ron::de::from_str(&list).unwrap() ) + .collect(); + index = index + 1; + )+ + // error: value assigned to `index` is never read + let _ = index; + } + + /// helper method to add a stringified version of all interned keys into + /// a lookup table based on ItemUid. Use strings as a form of type erasure + /// so all UpdateLists can go into a single map. + /// Then during analysis, when we see an invalidation reason due to + /// "ItemUid such and such was added to the tile primitive list", the lookup + /// allows mapping that back into something readable. + #[cfg(feature = "replay")] + pub fn insert_in_lookup( + &mut self, + itemuid_to_string: &mut HashMap) + { + $( + { + for list in &self.$name.1 { + for insertion in &list.insertions { + itemuid_to_string.insert( + insertion.uid, + format!("{:?}", insertion.value)); + } + } + } + )+ + } + } + } +} + +#[cfg(any(feature = "capture", feature = "replay"))] +enumerate_interners!(declare_tile_cache_logger_updatelists); + +#[cfg(not(any(feature = "capture", feature = "replay")))] +pub struct TileCacheLoggerUpdateLists { +} + +#[cfg(not(any(feature = "capture", feature = "replay")))] +impl TileCacheLoggerUpdateLists { + pub fn new() -> Self { TileCacheLoggerUpdateLists {} } + fn is_empty(&self) -> bool { true } +} + +/// Log tile cache activity for one single frame. +/// Also stores the commands sent to the interning data_stores +/// so we can see which items were created or destroyed this frame, +/// and correlate that with tile invalidation activity. +pub struct TileCacheLoggerFrame { + /// slices in the frame, one per take_context call + pub slices: Vec, + /// interning activity + pub update_lists: TileCacheLoggerUpdateLists +} + +impl TileCacheLoggerFrame { + pub fn new() -> Self { + TileCacheLoggerFrame { + slices: Vec::new(), + update_lists: TileCacheLoggerUpdateLists::new() + } + } + + pub fn is_empty(&self) -> bool { + self.slices.is_empty() && self.update_lists.is_empty() + } +} + +/// Log tile cache activity whenever anything happens in take_context. +pub struct TileCacheLogger { + /// next write pointer + pub write_index : usize, + /// ron serialization of tile caches; + pub frames: Vec +} + +impl TileCacheLogger { + pub fn new( + num_frames: usize + ) -> Self { + let mut frames = Vec::with_capacity(num_frames); + for _i in 0..num_frames { // no Clone so no resize + frames.push(TileCacheLoggerFrame::new()); + } + TileCacheLogger { + write_index: 0, + frames + } + } + + pub fn is_enabled(&self) -> bool { + !self.frames.is_empty() + } + + #[cfg(feature = "capture")] + pub fn add( + &mut self, + serialized_slice: String, + local_to_world_transform: Transform3D + ) { + if !self.is_enabled() { + return; + } + self.frames[self.write_index].slices.push( + TileCacheLoggerSlice { + serialized_slice, + local_to_world_transform }); + } + + #[cfg(feature = "capture")] + pub fn serialize_updates(&mut self, updates: &InternerUpdates) { + if !self.is_enabled() { + return; + } + self.frames[self.write_index].update_lists.serialize_updates(updates); + } + + /// see if anything was written in this frame, and if so, + /// advance the write index in a circular way and clear the + /// recorded string. + pub fn advance(&mut self) { + if !self.is_enabled() || self.frames[self.write_index].is_empty() { + return; + } + self.write_index = self.write_index + 1; + if self.write_index >= self.frames.len() { + self.write_index = 0; + } + self.frames[self.write_index] = TileCacheLoggerFrame::new(); + } + + #[cfg(feature = "capture")] + pub fn save_capture( + &self, root: &PathBuf + ) { + if !self.is_enabled() { + return; + } + use std::fs; + + info!("saving tile cache log"); + let path_tile_cache = root.join("tile_cache"); + if !path_tile_cache.is_dir() { + fs::create_dir(&path_tile_cache).unwrap(); + } + + let mut files_written = 0; + for ix in 0..self.frames.len() { + // ...and start with write_index, since that's the oldest entry + // that we're about to overwrite. However when we get to + // save_capture, we've add()ed entries but haven't advance()d yet, + // so the actual oldest entry is write_index + 1 + let index = (self.write_index + 1 + ix) % self.frames.len(); + if self.frames[index].is_empty() { + continue; + } + + let filename = path_tile_cache.join(format!("frame{:05}.ron", files_written)); + let mut output = File::create(filename).unwrap(); + output.write_all(b"// slice data\n").unwrap(); + output.write_all(b"[\n").unwrap(); + for item in &self.frames[index].slices { + output.write_all(b"( transform:\n").unwrap(); + let transform = + ron::ser::to_string_pretty( + &item.local_to_world_transform, Default::default()).unwrap(); + output.write_all(transform.as_bytes()).unwrap(); + output.write_all(b",\n tile_cache:\n").unwrap(); + output.write_all(item.serialized_slice.as_bytes()).unwrap(); + output.write_all(b"\n),\n").unwrap(); + } + output.write_all(b"]\n\n").unwrap(); + + output.write_all(b"// @@@ chunk @@@\n\n").unwrap(); + + output.write_all(b"// interning data\n").unwrap(); + output.write_all(self.frames[index].update_lists.to_ron().as_bytes()).unwrap(); + + files_written = files_written + 1; + } + } +} + +/// Represents the native surfaces created for a picture cache, if using +/// a native compositor. An opaque and alpha surface is always created, +/// but tiles are added to a surface based on current opacity. If the +/// calculated opacity of a tile changes, the tile is invalidated and +/// attached to a different native surface. This means that we don't +/// need to invalidate the entire surface if only some tiles are changing +/// opacity. It also means we can take advantage of opaque tiles on cache +/// slices where only some of the tiles are opaque. There is an assumption +/// that creating a native surface is cheap, and only when a tile is added +/// to a surface is there a significant cost. This assumption holds true +/// for the current native compositor implementations on Windows and Mac. +pub struct NativeSurface { + /// Native surface for opaque tiles + pub opaque: NativeSurfaceId, + /// Native surface for alpha tiles + pub alpha: NativeSurfaceId, +} + +/// Hash key for an external native compositor surface +#[derive(PartialEq, Eq, Hash)] +pub struct ExternalNativeSurfaceKey { + /// The YUV/RGB image keys that are used to draw this surface. + pub image_keys: [ImageKey; 3], + /// The current device size of the surface. + pub size: DeviceIntSize, +} + +/// Information about a native compositor surface cached between frames. +pub struct ExternalNativeSurface { + /// If true, the surface was used this frame. Used for a simple form + /// of GC to remove old surfaces. + pub used_this_frame: bool, + /// The native compositor surface handle + pub native_surface_id: NativeSurfaceId, + /// List of image keys, and current image generations, that are drawn in this surface. + /// The image generations are used to check if the compositor surface is dirty and + /// needs to be updated. + pub image_dependencies: [ImageDependency; 3], +} + +/// Represents a cache of tiles that make up a picture primitives. +pub struct TileCacheInstance { + /// Index of the tile cache / slice for this frame builder. It's determined + /// by the setup_picture_caching method during flattening, which splits the + /// picture tree into multiple slices. It's used as a simple input to the tile + /// keys. It does mean we invalidate tiles if a new layer gets inserted / removed + /// between display lists - this seems very unlikely to occur on most pages, but + /// can be revisited if we ever notice that. + pub slice: usize, + /// Propagated information about the slice + pub slice_flags: SliceFlags, + /// The currently selected tile size to use for this cache + pub current_tile_size: DeviceIntSize, + /// The positioning node for this tile cache. + pub spatial_node_index: SpatialNodeIndex, + /// Hash of tiles present in this picture. + pub tiles: FastHashMap>, + /// A helper struct to map local rects into surface coords. + map_local_to_surface: SpaceMapper, + /// A helper struct to map child picture rects into picture cache surface coords. + map_child_pic_to_surface: SpaceMapper, + /// List of opacity bindings, with some extra information + /// about whether they changed since last frame. + opacity_bindings: FastHashMap, + /// Switch back and forth between old and new bindings hashmaps to avoid re-allocating. + old_opacity_bindings: FastHashMap, + /// A helper to compare transforms between previous and current frame. + spatial_node_comparer: SpatialNodeComparer, + /// List of color bindings, with some extra information + /// about whether they changed since last frame. + color_bindings: FastHashMap, + /// Switch back and forth between old and new bindings hashmaps to avoid re-allocating. + old_color_bindings: FastHashMap, + /// The current dirty region tracker for this picture. + pub dirty_region: DirtyRegion, + /// Current size of tiles in picture units. + tile_size: PictureSize, + /// Tile coords of the currently allocated grid. + tile_rect: TileRect, + /// Pre-calculated versions of the tile_rect above, used to speed up the + /// calculations in get_tile_coords_for_rect. + tile_bounds_p0: TileOffset, + tile_bounds_p1: TileOffset, + /// Local rect (unclipped) of the picture this cache covers. + pub local_rect: PictureRect, + /// The local clip rect, from the shared clips of this picture. + local_clip_rect: PictureRect, + /// The surface index that this tile cache will be drawn into. + surface_index: SurfaceIndex, + /// The background color from the renderer. If this is set opaque, we know it's + /// fine to clear the tiles to this and allow subpixel text on the first slice. + pub background_color: Option, + /// Information about the calculated backdrop content of this cache. + pub backdrop: BackdropInfo, + /// The allowed subpixel mode for this surface, which depends on the detected + /// opacity of the background. + pub subpixel_mode: SubpixelMode, + /// A list of clip handles that exist on every (top-level) primitive in this picture. + /// It's often the case that these are root / fixed position clips. By handling them + /// here, we can avoid applying them to the items, which reduces work, but more importantly + /// reduces invalidations. + pub shared_clips: Vec, + /// The clip chain that represents the shared_clips above. Used to build the local + /// clip rect for this tile cache. + shared_clip_chain: ClipChainId, + /// The current transform of the picture cache root spatial node + root_transform: TransformKey, + /// The number of frames until this cache next evaluates what tile size to use. + /// If a picture rect size is regularly changing just around a size threshold, + /// we don't want to constantly invalidate and reallocate different tile size + /// configuration each frame. + frames_until_size_eval: usize, + /// The current fractional offset of the cached picture + fract_offset: PictureVector2D, + /// For DirectComposition, virtual surfaces don't support negative coordinates. However, + /// picture cache tile coordinates can be negative. To handle this, we apply an offset + /// to each tile in DirectComposition. We want to change this as little as possible, + /// to avoid invalidating tiles. However, if we have a picture cache tile coordinate + /// which is outside the virtual surface bounds, we must change this to allow + /// correct remapping of the coordinates passed to BeginDraw in DC. + virtual_offset: DeviceIntPoint, + /// keep around the hash map used as compare_cache to avoid reallocating it each + /// frame. + compare_cache: FastHashMap, + /// The allocated compositor surfaces for this picture cache. May be None if + /// not using native compositor, or if the surface was destroyed and needs + /// to be reallocated next time this surface contains valid tiles. + pub native_surface: Option, + /// The current device position of this cache. Used to set the compositor + /// offset of the surface when building the visual tree. + pub device_position: DevicePoint, + /// The currently considered tile size override. Used to check if we should + /// re-evaluate tile size, even if the frame timer hasn't expired. + tile_size_override: Option, + /// List of external surfaces that have been promoted from primitives + /// in this tile cache. + pub external_surfaces: Vec, + /// z-buffer ID assigned to opaque tiles in this slice + pub z_id_opaque: ZBufferId, + /// A cache of compositor surfaces that are retained between frames + pub external_native_surface_cache: FastHashMap, + /// Current frame ID of this tile cache instance. Used for book-keeping / garbage collecting + frame_id: FrameId, +} + +enum SurfacePromotionResult { + Failed, + Success { + flip_y: bool, + } +} + +impl TileCacheInstance { + pub fn new( + slice: usize, + slice_flags: SliceFlags, + spatial_node_index: SpatialNodeIndex, + background_color: Option, + shared_clips: Vec, + shared_clip_chain: ClipChainId, + fb_config: &FrameBuilderConfig, + ) -> Self { + let virtual_surface_size = fb_config.compositor_kind.get_virtual_surface_size(); + + TileCacheInstance { + slice, + slice_flags, + spatial_node_index, + tiles: FastHashMap::default(), + map_local_to_surface: SpaceMapper::new( + ROOT_SPATIAL_NODE_INDEX, + PictureRect::zero(), + ), + map_child_pic_to_surface: SpaceMapper::new( + ROOT_SPATIAL_NODE_INDEX, + PictureRect::zero(), + ), + opacity_bindings: FastHashMap::default(), + old_opacity_bindings: FastHashMap::default(), + spatial_node_comparer: SpatialNodeComparer::new(), + color_bindings: FastHashMap::default(), + old_color_bindings: FastHashMap::default(), + dirty_region: DirtyRegion::new(), + tile_size: PictureSize::zero(), + tile_rect: TileRect::zero(), + tile_bounds_p0: TileOffset::zero(), + tile_bounds_p1: TileOffset::zero(), + local_rect: PictureRect::zero(), + local_clip_rect: PictureRect::zero(), + surface_index: SurfaceIndex(0), + background_color, + backdrop: BackdropInfo::empty(), + subpixel_mode: SubpixelMode::Allow, + root_transform: TransformKey::Local, + shared_clips, + shared_clip_chain, + current_tile_size: DeviceIntSize::zero(), + frames_until_size_eval: 0, + fract_offset: PictureVector2D::zero(), + // Default to centering the virtual offset in the middle of the DC virtual surface + virtual_offset: DeviceIntPoint::new( + virtual_surface_size / 2, + virtual_surface_size / 2, + ), + compare_cache: FastHashMap::default(), + native_surface: None, + device_position: DevicePoint::zero(), + tile_size_override: None, + external_surfaces: Vec::new(), + z_id_opaque: ZBufferId::invalid(), + external_native_surface_cache: FastHashMap::default(), + frame_id: FrameId::INVALID, + } + } + + /// Returns true if this tile cache is considered opaque. + pub fn is_opaque(&self) -> bool { + // If known opaque due to background clear color and being the first slice. + // The background_color will only be Some(..) if this is the first slice. + match self.background_color { + Some(color) => color.a >= 1.0, + None => false + } + } + + /// Get the tile coordinates for a given rectangle. + fn get_tile_coords_for_rect( + &self, + rect: &PictureRect, + ) -> (TileOffset, TileOffset) { + // Get the tile coordinates in the picture space. + let mut p0 = TileOffset::new( + (rect.origin.x / self.tile_size.width).floor() as i32, + (rect.origin.y / self.tile_size.height).floor() as i32, + ); + + let mut p1 = TileOffset::new( + ((rect.origin.x + rect.size.width) / self.tile_size.width).ceil() as i32, + ((rect.origin.y + rect.size.height) / self.tile_size.height).ceil() as i32, + ); + + // Clamp the tile coordinates here to avoid looping over irrelevant tiles later on. + p0.x = clamp(p0.x, self.tile_bounds_p0.x, self.tile_bounds_p1.x); + p0.y = clamp(p0.y, self.tile_bounds_p0.y, self.tile_bounds_p1.y); + p1.x = clamp(p1.x, self.tile_bounds_p0.x, self.tile_bounds_p1.x); + p1.y = clamp(p1.y, self.tile_bounds_p0.y, self.tile_bounds_p1.y); + + (p0, p1) + } + + /// Update transforms, opacity, color bindings and tile rects. + pub fn pre_update( + &mut self, + pic_rect: PictureRect, + surface_index: SurfaceIndex, + frame_context: &FrameVisibilityContext, + frame_state: &mut FrameVisibilityState, + ) -> WorldRect { + self.external_surfaces.clear(); + self.surface_index = surface_index; + self.local_rect = pic_rect; + self.local_clip_rect = PictureRect::max_rect(); + + // Opaque surfaces get the first z_id. Compositor surfaces then get + // allocated a z_id each. After all compositor surfaces are added, + // then we allocate a z_id for alpha tiles. + self.z_id_opaque = frame_state.composite_state.z_generator.next(); + + // Reset the opaque rect + subpixel mode, as they are calculated + // during the prim dependency checks. + self.backdrop = BackdropInfo::empty(); + + self.map_local_to_surface = SpaceMapper::new( + self.spatial_node_index, + pic_rect, + ); + self.map_child_pic_to_surface = SpaceMapper::new( + self.spatial_node_index, + pic_rect, + ); + + let pic_to_world_mapper = SpaceMapper::new_with_target( + ROOT_SPATIAL_NODE_INDEX, + self.spatial_node_index, + frame_context.global_screen_world_rect, + frame_context.spatial_tree, + ); + + // If there is a valid set of shared clips, build a clip chain instance for this, + // which will provide a local clip rect. This is useful for establishing things + // like whether the backdrop rect supplied by Gecko can be considered opaque. + if self.shared_clip_chain != ClipChainId::NONE { + let mut shared_clips = Vec::new(); + let mut current_clip_chain_id = self.shared_clip_chain; + while current_clip_chain_id != ClipChainId::NONE { + shared_clips.push(current_clip_chain_id); + let clip_chain_node = &frame_state.clip_store.clip_chain_nodes[current_clip_chain_id.0 as usize]; + current_clip_chain_id = clip_chain_node.parent_clip_chain_id; + } + + frame_state.clip_store.set_active_clips( + LayoutRect::max_rect(), + self.spatial_node_index, + &shared_clips, + frame_context.spatial_tree, + &mut frame_state.data_stores.clip, + ); + + let clip_chain_instance = frame_state.clip_store.build_clip_chain_instance( + pic_rect.cast_unit(), + &self.map_local_to_surface, + &pic_to_world_mapper, + frame_context.spatial_tree, + frame_state.gpu_cache, + frame_state.resource_cache, + frame_context.global_device_pixel_scale, + &frame_context.global_screen_world_rect, + &mut frame_state.data_stores.clip, + true, + false, + ); + + // Ensure that if the entire picture cache is clipped out, the local + // clip rect is zero. This makes sure we don't register any occluders + // that are actually off-screen. + self.local_clip_rect = clip_chain_instance.map_or(PictureRect::zero(), |clip_chain_instance| { + clip_chain_instance.pic_clip_rect + }); + } + + // If there are pending retained state, retrieve it. + if let Some(prev_state) = frame_state.retained_tiles.caches.remove(&self.slice) { + self.tiles.extend(prev_state.tiles); + self.root_transform = prev_state.root_transform; + self.spatial_node_comparer = prev_state.spatial_node_comparer; + self.opacity_bindings = prev_state.opacity_bindings; + self.color_bindings = prev_state.color_bindings; + self.current_tile_size = prev_state.current_tile_size; + self.native_surface = prev_state.native_surface; + self.external_native_surface_cache = prev_state.external_native_surface_cache; + self.virtual_offset = prev_state.virtual_offset; + self.frame_id = prev_state.frame_id; + + fn recycle_map( + ideal_len: usize, + dest: &mut FastHashMap, + src: FastHashMap, + ) { + if dest.capacity() < src.capacity() { + if src.capacity() < 3 * ideal_len { + *dest = src; + } else { + dest.clear(); + dest.reserve(ideal_len); + } + } + } + recycle_map( + self.opacity_bindings.len(), + &mut self.old_opacity_bindings, + prev_state.allocations.old_opacity_bindings, + ); + recycle_map( + self.color_bindings.len(), + &mut self.old_color_bindings, + prev_state.allocations.old_color_bindings, + ); + recycle_map( + prev_state.allocations.compare_cache.len(), + &mut self.compare_cache, + prev_state.allocations.compare_cache, + ); + } + + // Advance the current frame ID counter for this picture cache (must be done + // after any retained prev state is taken above). + self.frame_id.advance(); + + // Notify the spatial node comparer that a new frame has started, and the + // current reference spatial node for this tile cache. + self.spatial_node_comparer.next_frame(self.spatial_node_index); + + // At the start of the frame, step through each current compositor surface + // and mark it as unused. Later, this is used to free old compositor surfaces. + // TODO(gw): In future, we might make this more sophisticated - for example, + // retaining them for >1 frame if unused, or retaining them in some + // kind of pool to reduce future allocations. + for external_native_surface in self.external_native_surface_cache.values_mut() { + external_native_surface.used_this_frame = false; + } + + // Only evaluate what tile size to use fairly infrequently, so that we don't end + // up constantly invalidating and reallocating tiles if the picture rect size is + // changing near a threshold value. + if self.frames_until_size_eval == 0 || + self.tile_size_override != frame_context.config.tile_size_override { + + // Work out what size tile is appropriate for this picture cache. + let desired_tile_size = match frame_context.config.tile_size_override { + Some(tile_size_override) => { + tile_size_override + } + None => { + if self.slice_flags.contains(SliceFlags::IS_SCROLLBAR) { + if pic_rect.size.width <= pic_rect.size.height { + TILE_SIZE_SCROLLBAR_VERTICAL + } else { + TILE_SIZE_SCROLLBAR_HORIZONTAL + } + } else { + TILE_SIZE_DEFAULT + } + } + }; + + // If the desired tile size has changed, then invalidate and drop any + // existing tiles. + if desired_tile_size != self.current_tile_size { + // Destroy any native surfaces on the tiles that will be dropped due + // to resizing. + if let Some(native_surface) = self.native_surface.take() { + frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque); + frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha); + } + self.tiles.clear(); + self.current_tile_size = desired_tile_size; + } + + // Reset counter until next evaluating the desired tile size. This is an + // arbitrary value. + self.frames_until_size_eval = 120; + self.tile_size_override = frame_context.config.tile_size_override; + } + + // Map an arbitrary point in picture space to world space, to work out + // what the fractional translation is that's applied by this scroll root. + // TODO(gw): I'm not 100% sure this is right. At least, in future, we should + // make a specific API for this, and/or enforce that the picture + // cache transform only includes scale and/or translation (we + // already ensure it doesn't have perspective). + let world_origin = pic_to_world_mapper + .map(&PictureRect::new(PicturePoint::zero(), PictureSize::new(1.0, 1.0))) + .expect("bug: unable to map origin to world space") + .origin; + + // Get the desired integer device coordinate + let device_origin = world_origin * frame_context.global_device_pixel_scale; + let desired_device_origin = device_origin.round(); + self.device_position = desired_device_origin; + + // Unmap from device space to world space rect + let ref_world_rect = WorldRect::new( + desired_device_origin / frame_context.global_device_pixel_scale, + WorldSize::new(1.0, 1.0), + ); + + // Unmap from world space to picture space + let ref_point = pic_to_world_mapper + .unmap(&ref_world_rect) + .expect("bug: unable to unmap ref world rect") + .origin; + + // Extract the fractional offset required in picture space to align in device space + self.fract_offset = PictureVector2D::new( + ref_point.x.fract(), + ref_point.y.fract(), + ); + + // Do a hacky diff of opacity binding values from the last frame. This is + // used later on during tile invalidation tests. + let current_properties = frame_context.scene_properties.float_properties(); + mem::swap(&mut self.opacity_bindings, &mut self.old_opacity_bindings); + + self.opacity_bindings.clear(); + for (id, value) in current_properties { + let changed = match self.old_opacity_bindings.get(id) { + Some(old_property) => !old_property.value.approx_eq(value), + None => true, + }; + self.opacity_bindings.insert(*id, OpacityBindingInfo { + value: *value, + changed, + }); + } + + // Do a hacky diff of color binding values from the last frame. This is + // used later on during tile invalidation tests. + let current_properties = frame_context.scene_properties.color_properties(); + mem::swap(&mut self.color_bindings, &mut self.old_color_bindings); + + self.color_bindings.clear(); + for (id, value) in current_properties { + let changed = match self.old_color_bindings.get(id) { + Some(old_property) => old_property.value != (*value).into(), + None => true, + }; + self.color_bindings.insert(*id, ColorBindingInfo { + value: (*value).into(), + changed, + }); + } + + let world_tile_size = WorldSize::new( + self.current_tile_size.width as f32 / frame_context.global_device_pixel_scale.0, + self.current_tile_size.height as f32 / frame_context.global_device_pixel_scale.0, + ); + + // We know that this is an exact rectangle, since we (for now) only support tile + // caches where the scroll root is in the root coordinate system. + let local_tile_rect = pic_to_world_mapper + .unmap(&WorldRect::new(WorldPoint::zero(), world_tile_size)) + .expect("bug: unable to get local tile rect"); + + self.tile_size = local_tile_rect.size; + + let screen_rect_in_pic_space = pic_to_world_mapper + .unmap(&frame_context.global_screen_world_rect) + .expect("unable to unmap screen rect"); + + // Inflate the needed rect a bit, so that we retain tiles that we have drawn + // but have just recently gone off-screen. This means that we avoid re-drawing + // tiles if the user is scrolling up and down small amounts, at the cost of + // a bit of extra texture memory. + let desired_rect_in_pic_space = screen_rect_in_pic_space + .inflate(0.0, 1.0 * self.tile_size.height); + + let needed_rect_in_pic_space = desired_rect_in_pic_space + .intersection(&pic_rect) + .unwrap_or_else(PictureRect::zero); + + let p0 = needed_rect_in_pic_space.origin; + let p1 = needed_rect_in_pic_space.bottom_right(); + + let x0 = (p0.x / local_tile_rect.size.width).floor() as i32; + let x1 = (p1.x / local_tile_rect.size.width).ceil() as i32; + + let y0 = (p0.y / local_tile_rect.size.height).floor() as i32; + let y1 = (p1.y / local_tile_rect.size.height).ceil() as i32; + + let x_tiles = x1 - x0; + let y_tiles = y1 - y0; + let new_tile_rect = TileRect::new( + TileOffset::new(x0, y0), + TileSize::new(x_tiles, y_tiles), + ); + + // Determine whether the current bounds of the tile grid will exceed the + // bounds of the DC virtual surface, taking into account the current + // virtual offset. If so, we need to invalidate all tiles, and set up + // a new virtual offset, centered around the current tile grid. + + if let CompositorKind::Native { virtual_surface_size, .. } = frame_context.config.compositor_kind { + // We only need to invalidate in this case if the underlying platform + // uses virtual surfaces. + if virtual_surface_size > 0 { + // Get the extremities of the tile grid after virtual offset is applied + let tx0 = self.virtual_offset.x + x0 * self.current_tile_size.width; + let ty0 = self.virtual_offset.y + y0 * self.current_tile_size.height; + let tx1 = self.virtual_offset.x + (x1+1) * self.current_tile_size.width; + let ty1 = self.virtual_offset.y + (y1+1) * self.current_tile_size.height; + + let need_new_virtual_offset = tx0 < 0 || + ty0 < 0 || + tx1 >= virtual_surface_size || + ty1 >= virtual_surface_size; + + if need_new_virtual_offset { + // Calculate a new virtual offset, centered around the middle of the + // current tile grid. This means we won't need to invalidate and get + // a new offset for a long time! + self.virtual_offset = DeviceIntPoint::new( + (virtual_surface_size/2) - ((x0 + x1) / 2) * self.current_tile_size.width, + (virtual_surface_size/2) - ((y0 + y1) / 2) * self.current_tile_size.height, + ); + + // Invalidate all native tile surfaces. They will be re-allocated next time + // they are scheduled to be rasterized. + for tile in self.tiles.values_mut() { + if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface { + if let Some(id) = id.take() { + frame_state.resource_cache.destroy_compositor_tile(id); + tile.surface = None; + // Invalidate the entire tile to force a redraw. + // TODO(gw): Add a new invalidation reason for virtual offset changing + tile.invalidate(None, InvalidationReason::CompositorKindChanged); + } + } + } + + // Destroy the native virtual surfaces. They will be re-allocated next time a tile + // that references them is scheduled to draw. + if let Some(native_surface) = self.native_surface.take() { + frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque); + frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha); + } + } + } + } + + // Rebuild the tile grid if the picture cache rect has changed. + if new_tile_rect != self.tile_rect { + let mut old_tiles = mem::replace(&mut self.tiles, FastHashMap::default()); + self.tiles.reserve(new_tile_rect.size.area() as usize); + + for y in y0 .. y1 { + for x in x0 .. x1 { + let key = TileOffset::new(x, y); + let tile = old_tiles + .remove(&key) + .unwrap_or_else(|| { + Box::new(Tile::new(key)) + }); + self.tiles.insert(key, tile); + } + } + + // When old tiles that remain after the loop, dirty rects are not valid. + if !old_tiles.is_empty() { + frame_state.composite_state.dirty_rects_are_valid = false; + } + + // Any old tiles that remain after the loop above are going to be dropped. For + // simple composite mode, the texture cache handle will expire and be collected + // by the texture cache. For native compositor mode, we need to explicitly + // invoke a callback to the client to destroy that surface. + frame_state.composite_state.destroy_native_tiles( + old_tiles.values_mut(), + frame_state.resource_cache, + ); + } + + // This is duplicated information from tile_rect, but cached here to avoid + // redundant calculations during get_tile_coords_for_rect + self.tile_bounds_p0 = TileOffset::new(x0, y0); + self.tile_bounds_p1 = TileOffset::new(x1, y1); + self.tile_rect = new_tile_rect; + + let mut world_culling_rect = WorldRect::zero(); + + let ctx = TilePreUpdateContext { + pic_to_world_mapper, + fract_offset: self.fract_offset, + background_color: self.background_color, + global_screen_world_rect: frame_context.global_screen_world_rect, + tile_size: self.tile_size, + frame_id: self.frame_id, + }; + + // Pre-update each tile + for tile in self.tiles.values_mut() { + tile.pre_update(&ctx); + + // Only include the tiles that are currently in view into the world culling + // rect. This is a very important optimization for a couple of reasons: + // (1) Primitives that intersect with tiles in the grid that are not currently + // visible can be skipped from primitive preparation, clip chain building + // and tile dependency updates. + // (2) When we need to allocate an off-screen surface for a child picture (for + // example a CSS filter) we clip the size of the GPU surface to the world + // culling rect below (to ensure we draw enough of it to be sampled by any + // tiles that reference it). Making the world culling rect only affected + // by visible tiles (rather than the entire virtual tile display port) can + // result in allocating _much_ smaller GPU surfaces for cases where the + // true off-screen surface size is very large. + if tile.is_visible { + world_culling_rect = world_culling_rect.union(&tile.world_tile_rect); + } + } + + // If compositor mode is changed, need to drop all incompatible tiles. + match frame_context.config.compositor_kind { + CompositorKind::Draw { .. } => { + for tile in self.tiles.values_mut() { + if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface { + if let Some(id) = id.take() { + frame_state.resource_cache.destroy_compositor_tile(id); + } + tile.surface = None; + // Invalidate the entire tile to force a redraw. + tile.invalidate(None, InvalidationReason::CompositorKindChanged); + } + } + + if let Some(native_surface) = self.native_surface.take() { + frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque); + frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha); + } + + for (_, external_surface) in self.external_native_surface_cache.drain() { + frame_state.resource_cache.destroy_compositor_surface(external_surface.native_surface_id) + } + } + CompositorKind::Native { .. } => { + // This could hit even when compositor mode is not changed, + // then we need to check if there are incompatible tiles. + for tile in self.tiles.values_mut() { + if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::TextureCache { .. }, .. }) = tile.surface { + tile.surface = None; + // Invalidate the entire tile to force a redraw. + tile.invalidate(None, InvalidationReason::CompositorKindChanged); + } + } + } + } + + world_culling_rect + } + + fn can_promote_to_surface( + &mut self, + flags: PrimitiveFlags, + prim_clip_chain: &ClipChainInstance, + prim_spatial_node_index: SpatialNodeIndex, + on_picture_surface: bool, + frame_context: &FrameVisibilityContext, + ) -> SurfacePromotionResult { + // Check if this primitive _wants_ to be promoted to a compositor surface. + if !flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) { + return SurfacePromotionResult::Failed; + } + + // For now, only support a small (arbitrary) number of compositor surfaces. + if self.external_surfaces.len() == MAX_COMPOSITOR_SURFACES { + return SurfacePromotionResult::Failed; + } + + // If a complex clip is being applied to this primitive, it can't be + // promoted directly to a compositor surface (we might be able to + // do this in limited cases in future, some native compositors do + // support rounded rect clips, for example) + if prim_clip_chain.needs_mask { + return SurfacePromotionResult::Failed; + } + + // If not on the same surface as the picture cache, it has some kind of + // complex effect (such as a filter, mix-blend-mode or 3d transform). + if !on_picture_surface { + return SurfacePromotionResult::Failed; + } + + let mapper : SpaceMapper = SpaceMapper::new_with_target( + ROOT_SPATIAL_NODE_INDEX, + prim_spatial_node_index, + frame_context.global_screen_world_rect, + &frame_context.spatial_tree); + let transform = mapper.get_transform(); + if !transform.is_2d_scale_translation() { + return SurfacePromotionResult::Failed; + } + if transform.m11 < 0.0 { + return SurfacePromotionResult::Failed; + } + + SurfacePromotionResult::Success { + flip_y: transform.m22 < 0.0, + } + } + + fn setup_compositor_surfaces_yuv( + &mut self, + prim_info: &mut PrimitiveDependencyInfo, + prim_rect: PictureRect, + frame_context: &FrameVisibilityContext, + image_dependencies: &[ImageDependency;3], + api_keys: &[ImageKey; 3], + resource_cache: &mut ResourceCache, + composite_state: &mut CompositeState, + image_rendering: ImageRendering, + color_depth: ColorDepth, + color_space: YuvColorSpace, + format: YuvFormat, + ) -> bool { + self.setup_compositor_surfaces_impl( + prim_info, + prim_rect, + frame_context, + ExternalSurfaceDependency::Yuv { + image_dependencies: *image_dependencies, + color_space, + format, + rescale: color_depth.rescaling_factor(), + }, + api_keys, + resource_cache, + composite_state, + image_rendering, + ) + } + + fn setup_compositor_surfaces_rgb( + &mut self, + prim_info: &mut PrimitiveDependencyInfo, + prim_rect: PictureRect, + frame_context: &FrameVisibilityContext, + image_dependency: ImageDependency, + api_key: ImageKey, + resource_cache: &mut ResourceCache, + composite_state: &mut CompositeState, + image_rendering: ImageRendering, + flip_y: bool, + ) -> bool { + let mut api_keys = [ImageKey::DUMMY; 3]; + api_keys[0] = api_key; + self.setup_compositor_surfaces_impl( + prim_info, + prim_rect, + frame_context, + ExternalSurfaceDependency::Rgb { + image_dependency, + flip_y, + }, + &api_keys, + resource_cache, + composite_state, + image_rendering, + ) + } + + // returns false if composition is not available for this surface, + // and the non-compositor path should be used to draw it instead. + fn setup_compositor_surfaces_impl( + &mut self, + prim_info: &mut PrimitiveDependencyInfo, + prim_rect: PictureRect, + frame_context: &FrameVisibilityContext, + dependency: ExternalSurfaceDependency, + api_keys: &[ImageKey; 3], + resource_cache: &mut ResourceCache, + composite_state: &mut CompositeState, + image_rendering: ImageRendering, + ) -> bool { + prim_info.is_compositor_surface = true; + + let pic_to_world_mapper = SpaceMapper::new_with_target( + ROOT_SPATIAL_NODE_INDEX, + self.spatial_node_index, + frame_context.global_screen_world_rect, + frame_context.spatial_tree, + ); + + let world_rect = pic_to_world_mapper + .map(&prim_rect) + .expect("bug: unable to map the primitive to world space"); + let world_clip_rect = pic_to_world_mapper + .map(&prim_info.prim_clip_box.to_rect()) + .expect("bug: unable to map clip to world space"); + + let is_visible = world_clip_rect.intersects(&frame_context.global_screen_world_rect); + if !is_visible { + return true; + } + + // TODO(gw): Is there any case where if the primitive ends up on a fractional + // boundary we want to _skip_ promoting to a compositor surface and + // draw it as part of the content? + let device_rect = (world_rect * frame_context.global_device_pixel_scale).round(); + let clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round(); + + if device_rect.size.width >= MAX_COMPOSITOR_SURFACES_SIZE || + device_rect.size.height >= MAX_COMPOSITOR_SURFACES_SIZE { + return false; + } + + // When using native compositing, we need to find an existing native surface + // handle to use, or allocate a new one. For existing native surfaces, we can + // also determine whether this needs to be updated, depending on whether the + // image generation(s) of the planes have changed since last composite. + let (native_surface_id, update_params) = match composite_state.compositor_kind { + CompositorKind::Draw { .. } => { + (None, None) + } + CompositorKind::Native { .. } => { + let native_surface_size = device_rect.size.round().to_i32(); + + let key = ExternalNativeSurfaceKey { + image_keys: *api_keys, + size: native_surface_size, + }; + + let native_surface = self.external_native_surface_cache + .entry(key) + .or_insert_with(|| { + // No existing surface, so allocate a new compositor surface and + // a single compositor tile that covers the entire compositor surface. + + let native_surface_id = resource_cache.create_compositor_surface( + DeviceIntPoint::zero(), + native_surface_size, + true, + ); + + let tile_id = NativeTileId { + surface_id: native_surface_id, + x: 0, + y: 0, + }; + + resource_cache.create_compositor_tile(tile_id); + + ExternalNativeSurface { + used_this_frame: true, + native_surface_id, + image_dependencies: [ImageDependency::INVALID; 3], + } + }); + + // Mark that the surface is referenced this frame so that the + // backing native surface handle isn't freed. + native_surface.used_this_frame = true; + + // If the image dependencies match, there is no need to update + // the backing native surface. + let update_params = match dependency { + ExternalSurfaceDependency::Yuv{ image_dependencies, .. } => { + if image_dependencies == native_surface.image_dependencies { + None + } else { + Some(native_surface_size) + } + }, + ExternalSurfaceDependency::Rgb{ image_dependency, .. } => { + if image_dependency == native_surface.image_dependencies[0] { + None + } else { + Some(native_surface_size) + } + }, + }; + + (Some(native_surface.native_surface_id), update_params) + } + }; + + // Each compositor surface allocates a unique z-id + self.external_surfaces.push(ExternalSurfaceDescriptor { + local_rect: prim_info.prim_clip_box.to_rect(), + world_rect, + local_clip_rect: prim_info.prim_clip_box.to_rect(), + dependency, + image_rendering, + device_rect, + clip_rect, + z_id: composite_state.z_generator.next(), + native_surface_id, + update_params, + }); + + true + } + + /// Update the dependencies for each tile for a given primitive instance. + pub fn update_prim_dependencies( + &mut self, + prim_instance: &mut PrimitiveInstance, + prim_spatial_node_index: SpatialNodeIndex, + prim_clip_chain: Option<&ClipChainInstance>, + local_prim_rect: LayoutRect, + frame_context: &FrameVisibilityContext, + data_stores: &DataStores, + clip_store: &ClipStore, + pictures: &[PicturePrimitive], + resource_cache: &mut ResourceCache, + opacity_binding_store: &OpacityBindingStorage, + color_bindings: &ColorBindingStorage, + image_instances: &ImageInstanceStorage, + surface_stack: &[SurfaceIndex], + composite_state: &mut CompositeState, + ) -> Option { + // This primitive exists on the last element on the current surface stack. + profile_scope!("update_prim_dependencies"); + let prim_surface_index = *surface_stack.last().unwrap(); + + // If the primitive is completely clipped out by the clip chain, there + // is no need to add it to any primitive dependencies. + let prim_clip_chain = match prim_clip_chain { + Some(prim_clip_chain) => prim_clip_chain, + None => return None, + }; + + self.map_local_to_surface.set_target_spatial_node( + prim_spatial_node_index, + frame_context.spatial_tree, + ); + + // Map the primitive local rect into picture space. + let prim_rect = match self.map_local_to_surface.map(&local_prim_rect) { + Some(rect) => rect, + None => return None, + }; + + // If the rect is invalid, no need to create dependencies. + if prim_rect.size.is_empty() { + return None; + } + + // If the primitive is directly drawn onto this picture cache surface, then + // the pic_clip_rect is in the same space. If not, we need to map it from + // the surface space into the picture cache space. + let on_picture_surface = prim_surface_index == self.surface_index; + let pic_clip_rect = if on_picture_surface { + prim_clip_chain.pic_clip_rect + } else { + // We want to get the rect in the tile cache surface space that this primitive + // occupies, in order to enable correct invalidation regions. Each surface + // that exists in the chain between this primitive and the tile cache surface + // may have an arbitrary inflation factor (for example, in the case of a series + // of nested blur elements). To account for this, step through the current + // surface stack, mapping the primitive rect into each surface space, including + // the inflation factor from each intermediate surface. + let mut current_pic_clip_rect = prim_clip_chain.pic_clip_rect; + let mut current_spatial_node_index = frame_context + .surfaces[prim_surface_index.0] + .surface_spatial_node_index; + + for surface_index in surface_stack.iter().rev() { + let surface = &frame_context.surfaces[surface_index.0]; + + let map_local_to_surface = SpaceMapper::new_with_target( + surface.surface_spatial_node_index, + current_spatial_node_index, + surface.rect, + frame_context.spatial_tree, + ); + + // Map the rect into the parent surface, and inflate if this surface requires + // it. If the rect can't be mapping (e.g. due to an invalid transform) then + // just bail out from the dependencies and cull this primitive. + current_pic_clip_rect = match map_local_to_surface.map(¤t_pic_clip_rect) { + Some(rect) => { + rect.inflate(surface.inflation_factor, surface.inflation_factor) + } + None => { + return None; + } + }; + + current_spatial_node_index = surface.surface_spatial_node_index; + } + + current_pic_clip_rect + }; + + // Get the tile coordinates in the picture space. + let (p0, p1) = self.get_tile_coords_for_rect(&pic_clip_rect); + + // If the primitive is outside the tiling rects, it's known to not + // be visible. + if p0.x == p1.x || p0.y == p1.y { + return None; + } + + // Build the list of resources that this primitive has dependencies on. + let mut prim_info = PrimitiveDependencyInfo::new( + prim_instance.uid(), + pic_clip_rect.to_box2d(), + ); + + // Include the prim spatial node, if differs relative to cache root. + if prim_spatial_node_index != self.spatial_node_index { + prim_info.spatial_nodes.push(prim_spatial_node_index); + } + + // If there was a clip chain, add any clip dependencies to the list for this tile. + let clip_instances = &clip_store + .clip_node_instances[prim_clip_chain.clips_range.to_range()]; + for clip_instance in clip_instances { + prim_info.clips.push(clip_instance.handle.uid()); + + // If the clip has the same spatial node, the relative transform + // will always be the same, so there's no need to depend on it. + if clip_instance.spatial_node_index != self.spatial_node_index + && !prim_info.spatial_nodes.contains(&clip_instance.spatial_node_index) { + prim_info.spatial_nodes.push(clip_instance.spatial_node_index); + } + } + + // Certain primitives may select themselves to be a backdrop candidate, which is + // then applied below. + let mut backdrop_candidate = None; + + + // For pictures, we don't (yet) know the valid clip rect, so we can't correctly + // use it to calculate the local bounding rect for the tiles. If we include them + // then we may calculate a bounding rect that is too large, since it won't include + // the clip bounds of the picture. Excluding them from the bounding rect here + // fixes any correctness issues (the clips themselves are considered when we + // consider the bounds of the primitives that are *children* of the picture), + // however it does potentially result in some un-necessary invalidations of a + // tile (in cases where the picture local rect affects the tile, but the clip + // rect eventually means it doesn't affect that tile). + // TODO(gw): Get picture clips earlier (during the initial picture traversal + // pass) so that we can calculate these correctly. + match prim_instance.kind { + PrimitiveInstanceKind::Picture { pic_index,.. } => { + // Pictures can depend on animated opacity bindings. + let pic = &pictures[pic_index.0]; + if let Some(PictureCompositeMode::Filter(Filter::Opacity(binding, _))) = pic.requested_composite_mode { + prim_info.opacity_bindings.push(binding.into()); + } + } + PrimitiveInstanceKind::Rectangle { data_handle, opacity_binding_index, color_binding_index, .. } => { + if opacity_binding_index == OpacityBindingIndex::INVALID { + // Rectangles can only form a backdrop candidate if they are known opaque. + // TODO(gw): We could resolve the opacity binding here, but the common + // case for background rects is that they don't have animated opacity. + let color = match data_stores.prim[data_handle].kind { + PrimitiveTemplateKind::Rectangle { color, .. } => { + frame_context.scene_properties.resolve_color(&color) + } + _ => unreachable!(), + }; + if color.a >= 1.0 { + backdrop_candidate = Some(BackdropInfo { + opaque_rect: pic_clip_rect, + kind: Some(BackdropKind::Color { color }), + }); + } + } else { + let opacity_binding = &opacity_binding_store[opacity_binding_index]; + for binding in &opacity_binding.bindings { + prim_info.opacity_bindings.push(OpacityBinding::from(*binding)); + } + } + + if color_binding_index != ColorBindingIndex::INVALID { + prim_info.color_binding = Some(color_bindings[color_binding_index].into()); + } + } + PrimitiveInstanceKind::Image { data_handle, image_instance_index, ref mut is_compositor_surface, .. } => { + let image_key = &data_stores.image[data_handle]; + let image_data = &image_key.kind; + let image_instance = &image_instances[image_instance_index]; + let opacity_binding_index = image_instance.opacity_binding_index; + + let mut promote_to_surface = false; + let mut promote_with_flip_y = false; + // If picture caching is disabled, we can't support any compositor surfaces. + if composite_state.picture_caching_is_enabled { + match self.can_promote_to_surface(image_key.common.flags, + prim_clip_chain, + prim_spatial_node_index, + on_picture_surface, + frame_context) { + SurfacePromotionResult::Failed => { + } + SurfacePromotionResult::Success{flip_y} => { + promote_to_surface = true; + promote_with_flip_y = flip_y; + } + } + } + + if opacity_binding_index == OpacityBindingIndex::INVALID { + if let Some(image_properties) = resource_cache.get_image_properties(image_data.key) { + // For an image to be a possible opaque backdrop, it must: + // - Have a valid, opaque image descriptor + // - Not use tiling (since they can fail to draw) + // - Not having any spacing / padding + if image_properties.descriptor.is_opaque() && + image_properties.tiling.is_none() && + image_data.tile_spacing == LayoutSize::zero() { + backdrop_candidate = Some(BackdropInfo { + opaque_rect: pic_clip_rect, + kind: None, + }); + } + } + } else { + let opacity_binding = &opacity_binding_store[opacity_binding_index]; + for binding in &opacity_binding.bindings { + prim_info.opacity_bindings.push(OpacityBinding::from(*binding)); + } + } + + if promote_to_surface { + promote_to_surface = self.setup_compositor_surfaces_rgb( + &mut prim_info, + prim_rect, + frame_context, + ImageDependency { + key: image_data.key, + generation: resource_cache.get_image_generation(image_data.key), + }, + image_data.key, + resource_cache, + composite_state, + image_data.image_rendering, + promote_with_flip_y, + ); + } + + if !promote_to_surface { + prim_info.images.push(ImageDependency { + key: image_data.key, + generation: resource_cache.get_image_generation(image_data.key), + }); + } + + *is_compositor_surface = promote_to_surface; + } + PrimitiveInstanceKind::YuvImage { data_handle, ref mut is_compositor_surface, .. } => { + let prim_data = &data_stores.yuv_image[data_handle]; + // TODO(gw): For now, we only support promoting YUV primitives to be compositor + // surfaces. However, some videos are RGBA images. As a follow up, + // extract the logic below and support RGBA compositor surfaces too. + let mut promote_to_surface = false; + + // If picture caching is disabled, we can't support any compositor surfaces. + if composite_state.picture_caching_is_enabled { + promote_to_surface = match self.can_promote_to_surface( + prim_data.common.flags, + prim_clip_chain, + prim_spatial_node_index, + on_picture_surface, + frame_context) { + SurfacePromotionResult::Failed => false, + SurfacePromotionResult::Success{flip_y} => !flip_y, + }; + + // TODO(gw): When we support RGBA images for external surfaces, we also + // need to check if opaque (YUV images are implicitly opaque). + } + + // If this primitive is being promoted to a surface, construct an external + // surface descriptor for use later during batching and compositing. We only + // add the image keys for this primitive as a dependency if this is _not_ + // a promoted surface, since we don't want the tiles to invalidate when the + // video content changes, if it's a compositor surface! + if promote_to_surface { + // Build dependency for each YUV plane, with current image generation for + // later detection of when the composited surface has changed. + let mut image_dependencies = [ImageDependency::INVALID; 3]; + for (key, dep) in prim_data.kind.yuv_key.iter().cloned().zip(image_dependencies.iter_mut()) { + *dep = ImageDependency { + key, + generation: resource_cache.get_image_generation(key), + } + } + + promote_to_surface = self.setup_compositor_surfaces_yuv( + &mut prim_info, + prim_rect, + frame_context, + &image_dependencies, + &prim_data.kind.yuv_key, + resource_cache, + composite_state, + prim_data.kind.image_rendering, + prim_data.kind.color_depth, + prim_data.kind.color_space, + prim_data.kind.format, + ); + } + + if !promote_to_surface { + prim_info.images.extend( + prim_data.kind.yuv_key.iter().map(|key| { + ImageDependency { + key: *key, + generation: resource_cache.get_image_generation(*key), + } + }) + ); + } + + // Store on the YUV primitive instance whether this is a promoted surface. + // This is used by the batching code to determine whether to draw the + // image to the content tiles, or just a transparent z-write. + *is_compositor_surface = promote_to_surface; + + } + PrimitiveInstanceKind::ImageBorder { data_handle, .. } => { + let border_data = &data_stores.image_border[data_handle].kind; + prim_info.images.push(ImageDependency { + key: border_data.request.key, + generation: resource_cache.get_image_generation(border_data.request.key), + }); + } + PrimitiveInstanceKind::Clear { .. } => { + backdrop_candidate = Some(BackdropInfo { + opaque_rect: pic_clip_rect, + kind: Some(BackdropKind::Clear), + }); + } + PrimitiveInstanceKind::LinearGradient { data_handle, .. } => { + let gradient_data = &data_stores.linear_grad[data_handle]; + if gradient_data.stops_opacity.is_opaque + && gradient_data.tile_spacing == LayoutSize::zero() + { + backdrop_candidate = Some(BackdropInfo { + opaque_rect: pic_clip_rect, + kind: None, + }); + } + } + PrimitiveInstanceKind::ConicGradient { data_handle, .. } => { + let gradient_data = &data_stores.conic_grad[data_handle]; + if gradient_data.stops_opacity.is_opaque + && gradient_data.tile_spacing == LayoutSize::zero() + { + backdrop_candidate = Some(BackdropInfo { + opaque_rect: pic_clip_rect, + kind: None, + }); + } + } + PrimitiveInstanceKind::RadialGradient { data_handle, .. } => { + let gradient_data = &data_stores.radial_grad[data_handle]; + if gradient_data.stops_opacity.is_opaque + && gradient_data.tile_spacing == LayoutSize::zero() + { + backdrop_candidate = Some(BackdropInfo { + opaque_rect: pic_clip_rect, + kind: None, + }); + } + } + PrimitiveInstanceKind::LineDecoration { .. } | + PrimitiveInstanceKind::NormalBorder { .. } | + PrimitiveInstanceKind::TextRun { .. } | + PrimitiveInstanceKind::Backdrop { .. } => { + // These don't contribute dependencies + } + }; + + // If this primitive considers itself a backdrop candidate, apply further + // checks to see if it matches all conditions to be a backdrop. + let mut vis_flags = PrimitiveVisibilityFlags::empty(); + + if let Some(backdrop_candidate) = backdrop_candidate { + let is_suitable_backdrop = match backdrop_candidate.kind { + Some(BackdropKind::Clear) => { + // Clear prims are special - they always end up in their own slice, + // and always set the backdrop. In future, we hope to completely + // remove clear prims, since they don't integrate with the compositing + // system cleanly. + true + } + Some(BackdropKind::Color { .. }) | None => { + // Check a number of conditions to see if we can consider this + // primitive as an opaque backdrop rect. Several of these are conservative + // checks and could be relaxed in future. However, these checks + // are quick and capture the common cases of background rects and images. + // Specifically, we currently require: + // - The primitive is on the main picture cache surface. + // - Same coord system as picture cache (ensures rects are axis-aligned). + // - No clip masks exist. + let same_coord_system = { + let prim_spatial_node = &frame_context.spatial_tree + .spatial_nodes[prim_spatial_node_index.0 as usize]; + let surface_spatial_node = &frame_context.spatial_tree + .spatial_nodes[self.spatial_node_index.0 as usize]; + + prim_spatial_node.coordinate_system_id == surface_spatial_node.coordinate_system_id + }; + + same_coord_system && on_picture_surface + } + }; + + if is_suitable_backdrop + && self.external_surfaces.is_empty() + && !prim_clip_chain.needs_mask { + + if backdrop_candidate.opaque_rect.contains_rect(&self.backdrop.opaque_rect) { + self.backdrop.opaque_rect = backdrop_candidate.opaque_rect; + } + + if let Some(kind) = backdrop_candidate.kind { + if backdrop_candidate.opaque_rect.contains_rect(&self.local_rect) { + // If we have a color backdrop, mark the visibility flags + // of the primitive so it is skipped during batching (and + // also clears any previous primitives). + if let BackdropKind::Color { .. } = kind { + vis_flags |= PrimitiveVisibilityFlags::IS_BACKDROP; + } + + self.backdrop.kind = Some(kind); + } + } + } + } + + // Record any new spatial nodes in the used list. + for spatial_node_index in &prim_info.spatial_nodes { + self.spatial_node_comparer.register_used_transform( + *spatial_node_index, + self.frame_id, + frame_context.spatial_tree, + ); + } + + // Truncate the lengths of dependency arrays to the max size we can handle. + // Any arrays this size or longer will invalidate every frame. + prim_info.clips.truncate(MAX_PRIM_SUB_DEPS); + prim_info.opacity_bindings.truncate(MAX_PRIM_SUB_DEPS); + prim_info.spatial_nodes.truncate(MAX_PRIM_SUB_DEPS); + prim_info.images.truncate(MAX_PRIM_SUB_DEPS); + + // Normalize the tile coordinates before adding to tile dependencies. + // For each affected tile, mark any of the primitive dependencies. + for y in p0.y .. p1.y { + for x in p0.x .. p1.x { + // TODO(gw): Convert to 2d array temporarily to avoid hash lookups per-tile? + let key = TileOffset::new(x, y); + let tile = self.tiles.get_mut(&key).expect("bug: no tile"); + + tile.add_prim_dependency(&prim_info); + } + } + + Some(vis_flags) + } + + /// Print debug information about this picture cache to a tree printer. + fn print(&self) { + // TODO(gw): This initial implementation is very basic - just printing + // the picture cache state to stdout. In future, we can + // make this dump each frame to a file, and produce a report + // stating which frames had invalidations. This will allow + // diff'ing the invalidation states in a visual tool. + let mut pt = PrintTree::new("Picture Cache"); + + pt.new_level(format!("Slice {}", self.slice)); + + pt.add_item(format!("fract_offset: {:?}", self.fract_offset)); + pt.add_item(format!("background_color: {:?}", self.background_color)); + + for y in self.tile_bounds_p0.y .. self.tile_bounds_p1.y { + for x in self.tile_bounds_p0.x .. self.tile_bounds_p1.x { + let key = TileOffset::new(x, y); + let tile = &self.tiles[&key]; + tile.print(&mut pt); + } + } + + pt.end_level(); + } + + fn calculate_subpixel_mode(&self) -> SubpixelMode { + // If the overall tile cache is known opaque, subpixel AA is allowed everywhere + if self.is_opaque() { + return SubpixelMode::Allow; + } + + // If we didn't find any valid opaque backdrop, no subpixel AA allowed + if !self.backdrop.opaque_rect.is_well_formed_and_nonempty() { + return SubpixelMode::Deny; + } + + // If the opaque backdrop rect covers the entire tile cache surface, + // we can allow subpixel AA anywhere, skipping the per-text-run tests + // later on during primitive preparation. + if self.backdrop.opaque_rect.contains_rect(&self.local_rect) { + return SubpixelMode::Allow; + } + + // If none of the simple cases above match, we need to build a list + // of excluded rects (compositor surfaces) and a valid inclusion rect + // (known opaque area) where we can support subpixel AA. + // TODO(gw): In future, it may make sense to have > 1 inclusion rect, + // but this handles the common cases. + // TODO(gw): If a text run gets animated such that it's moving in a way that is + // sometimes intersecting with the video rect, this can result in subpixel + // AA flicking on/off for that text run. It's probably very rare, but + // something we should handle in future. + + let excluded_rects = self.external_surfaces + .iter() + .map(|s| { + s.local_rect + }) + .collect(); + + SubpixelMode::Conditional { + allowed_rect: self.backdrop.opaque_rect, + excluded_rects, + } + } + + /// Apply any updates after prim dependency updates. This applies + /// any late tile invalidations, and sets up the dirty rect and + /// set of tile blits. + pub fn post_update( + &mut self, + frame_context: &FrameVisibilityContext, + frame_state: &mut FrameVisibilityState, + ) { + self.dirty_region.clear(); + self.subpixel_mode = self.calculate_subpixel_mode(); + + let map_pic_to_world = SpaceMapper::new_with_target( + ROOT_SPATIAL_NODE_INDEX, + self.spatial_node_index, + frame_context.global_screen_world_rect, + frame_context.spatial_tree, + ); + + // Register the opaque region of this tile cache as an occluder, which + // is used later in the frame to occlude other tiles. + if self.backdrop.opaque_rect.is_well_formed_and_nonempty() { + let backdrop_rect = self.backdrop.opaque_rect + .intersection(&self.local_rect) + .and_then(|r| { + r.intersection(&self.local_clip_rect) + }); + + if let Some(backdrop_rect) = backdrop_rect { + let world_backdrop_rect = map_pic_to_world + .map(&backdrop_rect) + .expect("bug: unable to map backdrop to world space"); + + // Since we register the entire backdrop rect, use the opaque z-id for the + // picture cache slice. + frame_state.composite_state.register_occluder( + self.z_id_opaque, + world_backdrop_rect, + ); + } + } + + // Register any external compositor surfaces as potential occluders. This + // is especially useful when viewing video in full-screen mode, as it is + // able to occlude every background tile (avoiding allocation, rasterizion + // and compositing). + for external_surface in &self.external_surfaces { + let local_surface_rect = external_surface.local_rect + .intersection(&external_surface.local_clip_rect) + .and_then(|r| { + r.intersection(&self.local_clip_rect) + }); + + if let Some(local_surface_rect) = local_surface_rect { + let world_surface_rect = map_pic_to_world + .map(&local_surface_rect) + .expect("bug: unable to map external surface to world space"); + + frame_state.composite_state.register_occluder( + external_surface.z_id, + world_surface_rect, + ); + } + } + + // A simple GC of the native external surface cache, to remove and free any + // surfaces that were not referenced during the update_prim_dependencies pass. + self.external_native_surface_cache.retain(|_, surface| { + if !surface.used_this_frame { + frame_state.resource_cache.destroy_compositor_surface(surface.native_surface_id); + } + + surface.used_this_frame + }); + + // Detect if the picture cache was scrolled or scaled. In this case, + // the device space dirty rects aren't applicable (until we properly + // integrate with OS compositors that can handle scrolling slices). + let root_transform = frame_context + .spatial_tree + .get_relative_transform( + self.spatial_node_index, + ROOT_SPATIAL_NODE_INDEX, + ) + .into(); + let root_transform_changed = root_transform != self.root_transform; + if root_transform_changed { + self.root_transform = root_transform; + frame_state.composite_state.dirty_rects_are_valid = false; + } + + let pic_to_world_mapper = SpaceMapper::new_with_target( + ROOT_SPATIAL_NODE_INDEX, + self.spatial_node_index, + frame_context.global_screen_world_rect, + frame_context.spatial_tree, + ); + + // All compositor surfaces have allocated a z_id, so reserve a z_id for alpha tiles. + let z_id_alpha = frame_state.composite_state.z_generator.next(); + + let ctx = TilePostUpdateContext { + pic_to_world_mapper, + global_device_pixel_scale: frame_context.global_device_pixel_scale, + local_clip_rect: self.local_clip_rect, + backdrop: self.backdrop, + opacity_bindings: &self.opacity_bindings, + color_bindings: &self.color_bindings, + current_tile_size: self.current_tile_size, + local_rect: self.local_rect, + external_surfaces: &self.external_surfaces, + z_id_opaque: self.z_id_opaque, + z_id_alpha, + }; + + let mut state = TilePostUpdateState { + resource_cache: frame_state.resource_cache, + composite_state: frame_state.composite_state, + compare_cache: &mut self.compare_cache, + spatial_node_comparer: &mut self.spatial_node_comparer, + }; + + // Step through each tile and invalidate if the dependencies have changed. Determine + // the current opacity setting and whether it's changed. + for tile in self.tiles.values_mut() { + tile.post_update(&ctx, &mut state, frame_context); + } + + // When under test, record a copy of the dirty region to support + // invalidation testing in wrench. + if frame_context.config.testing { + frame_state.scratch.recorded_dirty_regions.push(self.dirty_region.record()); + } + } +} + +/// Maintains a stack of picture and surface information, that +/// is used during the initial picture traversal. +pub struct PictureUpdateState<'a> { + surfaces: &'a mut Vec, + surface_stack: Vec, + picture_stack: Vec, + are_raster_roots_assigned: bool, + composite_state: &'a CompositeState, +} + +impl<'a> PictureUpdateState<'a> { + pub fn update_all( + surfaces: &'a mut Vec, + pic_index: PictureIndex, + picture_primitives: &mut [PicturePrimitive], + frame_context: &FrameBuildingContext, + gpu_cache: &mut GpuCache, + clip_store: &ClipStore, + data_stores: &mut DataStores, + composite_state: &CompositeState, + ) { + profile_scope!("UpdatePictures"); + profile_marker!("UpdatePictures"); + + let mut state = PictureUpdateState { + surfaces, + surface_stack: vec![SurfaceIndex(0)], + picture_stack: Vec::new(), + are_raster_roots_assigned: true, + composite_state, + }; + + state.update( + pic_index, + picture_primitives, + frame_context, + gpu_cache, + clip_store, + data_stores, + ); + + if !state.are_raster_roots_assigned { + state.assign_raster_roots( + pic_index, + picture_primitives, + ROOT_SPATIAL_NODE_INDEX, + ); + } + } + + /// Return the current surface + fn current_surface(&self) -> &SurfaceInfo { + &self.surfaces[self.surface_stack.last().unwrap().0] + } + + /// Return the current surface (mutable) + fn current_surface_mut(&mut self) -> &mut SurfaceInfo { + &mut self.surfaces[self.surface_stack.last().unwrap().0] + } + + /// Push a new surface onto the update stack. + fn push_surface( + &mut self, + surface: SurfaceInfo, + ) -> SurfaceIndex { + let surface_index = SurfaceIndex(self.surfaces.len()); + self.surfaces.push(surface); + self.surface_stack.push(surface_index); + surface_index + } + + /// Pop a surface on the way up the picture traversal + fn pop_surface(&mut self) -> SurfaceIndex{ + self.surface_stack.pop().unwrap() + } + + /// Push information about a picture on the update stack + fn push_picture( + &mut self, + info: PictureInfo, + ) { + self.picture_stack.push(info); + } + + /// Pop the picture info off, on the way up the picture traversal + fn pop_picture( + &mut self, + ) -> PictureInfo { + self.picture_stack.pop().unwrap() + } + + /// Update a picture, determining surface configuration, + /// rasterization roots, and (in future) whether there + /// are cached surfaces that can be used by this picture. + fn update( + &mut self, + pic_index: PictureIndex, + picture_primitives: &mut [PicturePrimitive], + frame_context: &FrameBuildingContext, + gpu_cache: &mut GpuCache, + clip_store: &ClipStore, + data_stores: &mut DataStores, + ) { + if let Some(prim_list) = picture_primitives[pic_index.0].pre_update( + self, + frame_context, + ) { + for cluster in &prim_list.clusters { + if cluster.flags.contains(ClusterFlags::IS_PICTURE) { + for prim_instance in &cluster.prim_instances { + let child_pic_index = match prim_instance.kind { + PrimitiveInstanceKind::Picture { pic_index, .. } => pic_index, + _ => unreachable!(), + }; + + self.update( + child_pic_index, + picture_primitives, + frame_context, + gpu_cache, + clip_store, + data_stores, + ); + } + } + } + + picture_primitives[pic_index.0].post_update( + prim_list, + self, + frame_context, + data_stores, + ); + } + } + + /// Process the picture tree again in a depth-first order, + /// and adjust the raster roots of the pictures that want to establish + /// their own roots but are not able to due to the size constraints. + fn assign_raster_roots( + &mut self, + pic_index: PictureIndex, + picture_primitives: &[PicturePrimitive], + fallback_raster_spatial_node: SpatialNodeIndex, + ) { + let picture = &picture_primitives[pic_index.0]; + if !picture.is_visible() { + return + } + + let new_fallback = match picture.raster_config { + Some(ref config) => { + let surface = &mut self.surfaces[config.surface_index.0]; + if !config.establishes_raster_root { + surface.raster_spatial_node_index = fallback_raster_spatial_node; + } + surface.raster_spatial_node_index + } + None => fallback_raster_spatial_node, + }; + + for cluster in &picture.prim_list.clusters { + if cluster.flags.contains(ClusterFlags::IS_PICTURE) { + for instance in &cluster.prim_instances { + let child_pic_index = match instance.kind { + PrimitiveInstanceKind::Picture { pic_index, .. } => pic_index, + _ => unreachable!(), + }; + self.assign_raster_roots( + child_pic_index, + picture_primitives, + new_fallback, + ); + } + } + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct SurfaceIndex(pub usize); + +pub const ROOT_SURFACE_INDEX: SurfaceIndex = SurfaceIndex(0); + +#[derive(Debug, Copy, Clone)] +pub struct SurfaceRenderTasks { + /// The root of the render task chain for this surface. This + /// is attached to parent tasks, and also the surface that + /// gets added during batching. + pub root: RenderTaskId, + /// The port of the render task change for this surface. This + /// is where child tasks for this surface get attached to. + pub port: RenderTaskId, +} + +/// Information about an offscreen surface. For now, +/// it contains information about the size and coordinate +/// system of the surface. In the future, it will contain +/// information about the contents of the surface, which +/// will allow surfaces to be cached / retained between +/// frames and display lists. +#[derive(Debug)] +pub struct SurfaceInfo { + /// A local rect defining the size of this surface, in the + /// coordinate system of the surface itself. + pub rect: PictureRect, + /// Helper structs for mapping local rects in different + /// coordinate systems into the surface coordinates. + pub map_local_to_surface: SpaceMapper, + /// Defines the positioning node for the surface itself, + /// and the rasterization root for this surface. + pub raster_spatial_node_index: SpatialNodeIndex, + pub surface_spatial_node_index: SpatialNodeIndex, + /// This is set when the render task is created. + pub render_tasks: Option, + /// How much the local surface rect should be inflated (for blur radii). + pub inflation_factor: f32, + /// The device pixel ratio specific to this surface. + pub device_pixel_scale: DevicePixelScale, + /// The scale factors of the surface to raster transform. + pub scale_factors: (f32, f32), +} + +impl SurfaceInfo { + pub fn new( + surface_spatial_node_index: SpatialNodeIndex, + raster_spatial_node_index: SpatialNodeIndex, + inflation_factor: f32, + world_rect: WorldRect, + spatial_tree: &SpatialTree, + device_pixel_scale: DevicePixelScale, + scale_factors: (f32, f32), + ) -> Self { + let map_surface_to_world = SpaceMapper::new_with_target( + ROOT_SPATIAL_NODE_INDEX, + surface_spatial_node_index, + world_rect, + spatial_tree, + ); + + let pic_bounds = map_surface_to_world + .unmap(&map_surface_to_world.bounds) + .unwrap_or_else(PictureRect::max_rect); + + let map_local_to_surface = SpaceMapper::new( + surface_spatial_node_index, + pic_bounds, + ); + + SurfaceInfo { + rect: PictureRect::zero(), + map_local_to_surface, + render_tasks: None, + raster_spatial_node_index, + surface_spatial_node_index, + inflation_factor, + device_pixel_scale, + scale_factors, + } + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct RasterConfig { + /// How this picture should be composited into + /// the parent surface. + pub composite_mode: PictureCompositeMode, + /// Index to the surface descriptor for this + /// picture. + pub surface_index: SurfaceIndex, + /// Whether this picture establishes a rasterization root. + pub establishes_raster_root: bool, + /// Scaling factor applied to fit within MAX_SURFACE_SIZE when + /// establishing a raster root. + /// Most code doesn't need to know about it, since it is folded + /// into device_pixel_scale when the rendertask is set up. + /// However e.g. text rasterization uses it to ensure consistent + /// on-screen font size. + pub root_scaling_factor: f32, +} + +bitflags! { + /// A set of flags describing why a picture may need a backing surface. + #[cfg_attr(feature = "capture", derive(Serialize))] + pub struct BlitReason: u32 { + /// Mix-blend-mode on a child that requires isolation. + const ISOLATE = 1; + /// Clip node that _might_ require a surface. + const CLIP = 2; + /// Preserve-3D requires a surface for plane-splitting. + const PRESERVE3D = 4; + /// A backdrop that is reused which requires a surface. + const BACKDROP = 8; + } +} + +/// Specifies how this Picture should be composited +/// onto the target it belongs to. +#[allow(dead_code)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub enum PictureCompositeMode { + /// Apply CSS mix-blend-mode effect. + MixBlend(MixBlendMode), + /// Apply a CSS filter (except component transfer). + Filter(Filter), + /// Apply a component transfer filter. + ComponentTransferFilter(FilterDataHandle), + /// Draw to intermediate surface, copy straight across. This + /// is used for CSS isolation, and plane splitting. + Blit(BlitReason), + /// Used to cache a picture as a series of tiles. + TileCache { + }, + /// Apply an SVG filter + SvgFilter(Vec, Vec), +} + +impl PictureCompositeMode { + pub fn inflate_picture_rect(&self, picture_rect: PictureRect, scale_factors: (f32, f32)) -> PictureRect { + let mut result_rect = picture_rect; + match self { + PictureCompositeMode::Filter(filter) => match filter { + Filter::Blur(blur_radius) => { + let inflation_factor = clamp_blur_radius(*blur_radius, scale_factors).ceil() * BLUR_SAMPLE_SCALE; + result_rect = picture_rect.inflate(inflation_factor, inflation_factor); + }, + Filter::DropShadows(shadows) => { + let mut max_inflation: f32 = 0.0; + for shadow in shadows { + max_inflation = max_inflation.max(shadow.blur_radius); + } + max_inflation = clamp_blur_radius(max_inflation, scale_factors).ceil() * BLUR_SAMPLE_SCALE; + result_rect = picture_rect.inflate(max_inflation, max_inflation); + }, + _ => {} + } + PictureCompositeMode::SvgFilter(primitives, _) => { + let mut output_rects = Vec::with_capacity(primitives.len()); + for (cur_index, primitive) in primitives.iter().enumerate() { + let output_rect = match primitive.kind { + FilterPrimitiveKind::Blur(ref primitive) => { + let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect); + let inflation_factor = primitive.radius.round() * BLUR_SAMPLE_SCALE; + input.inflate(inflation_factor, inflation_factor) + } + FilterPrimitiveKind::DropShadow(ref primitive) => { + let inflation_factor = primitive.shadow.blur_radius.ceil() * BLUR_SAMPLE_SCALE; + let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect); + let shadow_rect = input.inflate(inflation_factor, inflation_factor); + input.union(&shadow_rect.translate(primitive.shadow.offset * Scale::new(1.0))) + } + FilterPrimitiveKind::Blend(ref primitive) => { + primitive.input1.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect) + .union(&primitive.input2.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect)) + } + FilterPrimitiveKind::Composite(ref primitive) => { + primitive.input1.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect) + .union(&primitive.input2.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect)) + } + FilterPrimitiveKind::Identity(ref primitive) => + primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect), + FilterPrimitiveKind::Opacity(ref primitive) => + primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect), + FilterPrimitiveKind::ColorMatrix(ref primitive) => + primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect), + FilterPrimitiveKind::ComponentTransfer(ref primitive) => + primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect), + FilterPrimitiveKind::Offset(ref primitive) => { + let input_rect = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect); + input_rect.translate(primitive.offset * Scale::new(1.0)) + }, + + FilterPrimitiveKind::Flood(..) => picture_rect, + }; + output_rects.push(output_rect); + result_rect = result_rect.union(&output_rect); + } + } + _ => {}, + } + result_rect + } +} + +/// Enum value describing the place of a picture in a 3D context. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub enum Picture3DContext { + /// The picture is not a part of 3D context sub-hierarchy. + Out, + /// The picture is a part of 3D context. + In { + /// Additional data per child for the case of this a root of 3D hierarchy. + root_data: Option>, + /// The spatial node index of an "ancestor" element, i.e. one + /// that establishes the transformed element's containing block. + /// + /// See CSS spec draft for more details: + /// https://drafts.csswg.org/css-transforms-2/#accumulated-3d-transformation-matrix-computation + ancestor_index: SpatialNodeIndex, + }, +} + +/// Information about a preserve-3D hierarchy child that has been plane-split +/// and ordered according to the view direction. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct OrderedPictureChild { + pub anchor: PlaneSplitAnchor, + pub spatial_node_index: SpatialNodeIndex, + pub gpu_address: GpuCacheAddress, +} + +bitflags! { + /// A set of flags describing why a picture may need a backing surface. + #[cfg_attr(feature = "capture", derive(Serialize))] + pub struct ClusterFlags: u32 { + /// This cluster is a picture + const IS_PICTURE = 1; + /// Whether this cluster is visible when the position node is a backface. + const IS_BACKFACE_VISIBLE = 2; + /// This flag is set during the first pass picture traversal, depending on whether + /// the cluster is visible or not. It's read during the second pass when primitives + /// consult their owning clusters to see if the primitive itself is visible. + const IS_VISIBLE = 4; + /// Is a backdrop-filter cluster that requires special handling during post_update. + const IS_BACKDROP_FILTER = 8; + /// Force creation of a picture caching slice before this cluster. + const CREATE_PICTURE_CACHE_PRE = 16; + /// Force creation of a picture caching slice after this cluster. + const CREATE_PICTURE_CACHE_POST = 32; + /// If set, this cluster represents a scroll bar container. + const SCROLLBAR_CONTAINER = 64; + /// If set, this cluster contains clear rectangle primitives. + const IS_CLEAR_PRIMITIVE = 128; + /// This is used as a performance hint - this primitive may be promoted to a native + /// compositor surface under certain (implementation specific) conditions. This + /// is typically used for large videos, and canvas elements. + const PREFER_COMPOSITOR_SURFACE = 256; + } +} + +/// Descriptor for a cluster of primitives. For now, this is quite basic but will be +/// extended to handle more spatial clustering of primitives. +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct PrimitiveCluster { + /// The positioning node for this cluster. + pub spatial_node_index: SpatialNodeIndex, + /// The bounding rect of the cluster, in the local space of the spatial node. + /// This is used to quickly determine the overall bounding rect for a picture + /// during the first picture traversal, which is needed for local scale + /// determination, and render task size calculations. + bounding_rect: LayoutRect, + /// The list of primitive instances in this cluster. + pub prim_instances: Vec, + /// Various flags / state for this cluster. + pub flags: ClusterFlags, + /// An optional scroll root to use if this cluster establishes a picture cache slice. + pub cache_scroll_root: Option, +} + +/// Where to insert a prim instance in a primitive list. +#[derive(Debug, Copy, Clone)] +enum PrimitiveListPosition { + Begin, + End, +} + +impl PrimitiveCluster { + /// Construct a new primitive cluster for a given positioning node. + fn new( + spatial_node_index: SpatialNodeIndex, + flags: ClusterFlags, + ) -> Self { + PrimitiveCluster { + bounding_rect: LayoutRect::zero(), + spatial_node_index, + flags, + prim_instances: Vec::new(), + cache_scroll_root: None, + } + } + + /// Return true if this cluster is compatible with the given params + pub fn is_compatible( + &self, + spatial_node_index: SpatialNodeIndex, + flags: ClusterFlags, + ) -> bool { + // If this cluster is a scrollbar, ensure that a matching scrollbar + // container that follows is split up, so we don't combine the + // scrollbars into a single slice. + if self.flags.contains(ClusterFlags::SCROLLBAR_CONTAINER) { + return false; + } + + self.flags == flags && self.spatial_node_index == spatial_node_index + } + + /// Add a primitive instance to this cluster, at the start or end + fn push( + &mut self, + prim_instance: PrimitiveInstance, + prim_rect: LayoutRect, + ) { + let culling_rect = prim_instance.local_clip_rect + .intersection(&prim_rect) + .unwrap_or_else(LayoutRect::zero); + + self.bounding_rect = self.bounding_rect.union(&culling_rect); + self.prim_instances.push(prim_instance); + } +} + +/// A list of primitive instances that are added to a picture +/// This ensures we can keep a list of primitives that +/// are pictures, for a fast initial traversal of the picture +/// tree without walking the instance list. +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct PrimitiveList { + /// List of primitives grouped into clusters. + pub clusters: Vec, +} + +impl PrimitiveList { + /// Construct an empty primitive list. This is + /// just used during the take_context / restore_context + /// borrow check dance, which will be removed as the + /// picture traversal pass is completed. + pub fn empty() -> Self { + PrimitiveList { + clusters: Vec::new(), + } + } + + /// Add a primitive instance to this list, at the start or end + fn push( + &mut self, + prim_instance: PrimitiveInstance, + prim_rect: LayoutRect, + spatial_node_index: SpatialNodeIndex, + prim_flags: PrimitiveFlags, + insert_position: PrimitiveListPosition, + ) { + let mut flags = ClusterFlags::empty(); + + // Pictures are always put into a new cluster, to make it faster to + // iterate all pictures in a given primitive list. + match prim_instance.kind { + PrimitiveInstanceKind::Picture { .. } => { + flags.insert(ClusterFlags::IS_PICTURE); + } + PrimitiveInstanceKind::Backdrop { .. } => { + flags.insert(ClusterFlags::IS_BACKDROP_FILTER); + } + PrimitiveInstanceKind::Clear { .. } => { + flags.insert(ClusterFlags::IS_CLEAR_PRIMITIVE); + } + _ => {} + } + + if prim_flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE) { + flags.insert(ClusterFlags::IS_BACKFACE_VISIBLE); + } + + if prim_flags.contains(PrimitiveFlags::IS_SCROLLBAR_CONTAINER) { + flags.insert(ClusterFlags::SCROLLBAR_CONTAINER); + } + + if prim_flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) { + flags.insert(ClusterFlags::PREFER_COMPOSITOR_SURFACE); + } + + // Insert the primitive into the first or last cluster as required + match insert_position { + PrimitiveListPosition::Begin => { + let mut cluster = PrimitiveCluster::new( + spatial_node_index, + flags, + ); + cluster.push(prim_instance, prim_rect); + self.clusters.insert(0, cluster); + } + PrimitiveListPosition::End => { + if let Some(cluster) = self.clusters.last_mut() { + if cluster.is_compatible(spatial_node_index, flags) { + cluster.push(prim_instance, prim_rect); + return; + } + } + + let mut cluster = PrimitiveCluster::new( + spatial_node_index, + flags, + ); + cluster.push(prim_instance, prim_rect); + self.clusters.push(cluster); + } + } + } + + /// Add a primitive instance to the start of the list + pub fn add_prim_to_start( + &mut self, + prim_instance: PrimitiveInstance, + prim_rect: LayoutRect, + spatial_node_index: SpatialNodeIndex, + flags: PrimitiveFlags, + ) { + self.push( + prim_instance, + prim_rect, + spatial_node_index, + flags, + PrimitiveListPosition::Begin, + ) + } + + /// Add a primitive instance to the end of the list + pub fn add_prim( + &mut self, + prim_instance: PrimitiveInstance, + prim_rect: LayoutRect, + spatial_node_index: SpatialNodeIndex, + flags: PrimitiveFlags, + ) { + self.push( + prim_instance, + prim_rect, + spatial_node_index, + flags, + PrimitiveListPosition::End, + ) + } + + /// Returns true if there are no clusters (and thus primitives) + pub fn is_empty(&self) -> bool { + self.clusters.is_empty() + } + + /// Add an existing cluster to this prim list + pub fn add_cluster(&mut self, cluster: PrimitiveCluster) { + self.clusters.push(cluster); + } + + /// Merge another primitive list into this one + pub fn extend(&mut self, prim_list: PrimitiveList) { + self.clusters.extend(prim_list.clusters); + } +} + +/// Defines configuration options for a given picture primitive. +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct PictureOptions { + /// If true, WR should inflate the bounding rect of primitives when + /// using a filter effect that requires inflation. + pub inflate_if_required: bool, +} + +impl Default for PictureOptions { + fn default() -> Self { + PictureOptions { + inflate_if_required: true, + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct PicturePrimitive { + /// List of primitives, and associated info for this picture. + pub prim_list: PrimitiveList, + + #[cfg_attr(feature = "capture", serde(skip))] + pub state: Option, + + /// If true, apply the local clip rect to primitive drawn + /// in this picture. + pub apply_local_clip_rect: bool, + /// If false and transform ends up showing the back of the picture, + /// it will be considered invisible. + pub is_backface_visible: bool, + + // If a mix-blend-mode, contains the render task for + // the readback of the framebuffer that we use to sample + // from in the mix-blend-mode shader. + // For drop-shadow filter, this will store the original + // picture task which would be rendered on screen after + // blur pass. + pub secondary_render_task_id: Option, + /// How this picture should be composited. + /// If None, don't composite - just draw directly on parent surface. + pub requested_composite_mode: Option, + /// Requested rasterization space for this picture. It is + /// a performance hint only. + pub requested_raster_space: RasterSpace, + + pub raster_config: Option, + pub context_3d: Picture3DContext, + + // If requested as a frame output (for rendering + // pages to a texture), this is the pipeline this + // picture is the root of. + pub frame_output_pipeline_id: Option, + // Optional cache handles for storing extra data + // in the GPU cache, depending on the type of + // picture. + pub extra_gpu_data_handles: SmallVec<[GpuCacheHandle; 1]>, + + /// The spatial node index of this picture when it is + /// composited into the parent picture. + pub spatial_node_index: SpatialNodeIndex, + + /// The conservative local rect of this picture. It is + /// built dynamically during the first picture traversal. + /// It is composed of already snapped primitives. + pub estimated_local_rect: LayoutRect, + + /// The local rect of this picture. It is built + /// dynamically during the frame visibility update. It + /// differs from the estimated_local_rect because it + /// will not contain culled primitives, takes into + /// account surface inflation and the whole clip chain. + /// It is frequently the same, but may be quite + /// different depending on how much was culled. + pub precise_local_rect: LayoutRect, + + /// If false, this picture needs to (re)build segments + /// if it supports segment rendering. This can occur + /// if the local rect of the picture changes due to + /// transform animation and/or scrolling. + pub segments_are_valid: bool, + + /// If Some(..) the tile cache that is associated with this picture. + #[cfg_attr(feature = "capture", serde(skip))] //TODO + pub tile_cache: Option>, + + /// The config options for this picture. + pub options: PictureOptions, + + /// Keep track of the number of render tasks dependencies to pre-allocate + /// the dependency array next frame. + num_render_tasks: usize, +} + +impl PicturePrimitive { + pub fn print( + &self, + pictures: &[Self], + self_index: PictureIndex, + pt: &mut T, + ) { + pt.new_level(format!("{:?}", self_index)); + pt.add_item(format!("cluster_count: {:?}", self.prim_list.clusters.len())); + pt.add_item(format!("estimated_local_rect: {:?}", self.estimated_local_rect)); + pt.add_item(format!("precise_local_rect: {:?}", self.precise_local_rect)); + pt.add_item(format!("spatial_node_index: {:?}", self.spatial_node_index)); + pt.add_item(format!("raster_config: {:?}", self.raster_config)); + pt.add_item(format!("requested_composite_mode: {:?}", self.requested_composite_mode)); + + for cluster in &self.prim_list.clusters { + if cluster.flags.contains(ClusterFlags::IS_PICTURE) { + for instance in &cluster.prim_instances { + let index = match instance.kind { + PrimitiveInstanceKind::Picture { pic_index, .. } => pic_index, + _ => unreachable!(), + }; + pictures[index.0].print(pictures, index, pt); + } + } + } + + pt.end_level(); + } + + /// Returns true if this picture supports segmented rendering. + pub fn can_use_segments(&self) -> bool { + match self.raster_config { + // TODO(gw): Support brush segment rendering for filter and mix-blend + // shaders. It's possible this already works, but I'm just + // applying this optimization to Blit mode for now. + Some(RasterConfig { composite_mode: PictureCompositeMode::MixBlend(..), .. }) | + Some(RasterConfig { composite_mode: PictureCompositeMode::Filter(..), .. }) | + Some(RasterConfig { composite_mode: PictureCompositeMode::ComponentTransferFilter(..), .. }) | + Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { .. }, .. }) | + Some(RasterConfig { composite_mode: PictureCompositeMode::SvgFilter(..), .. }) | + None => { + false + } + Some(RasterConfig { composite_mode: PictureCompositeMode::Blit(reason), ..}) => { + reason == BlitReason::CLIP + } + } + } + + fn resolve_scene_properties(&mut self, properties: &SceneProperties) -> bool { + match self.requested_composite_mode { + Some(PictureCompositeMode::Filter(ref mut filter)) => { + match *filter { + Filter::Opacity(ref binding, ref mut value) => { + *value = properties.resolve_float(binding); + } + _ => {} + } + + filter.is_visible() + } + _ => true, + } + } + + pub fn is_visible(&self) -> bool { + match self.requested_composite_mode { + Some(PictureCompositeMode::Filter(ref filter)) => { + filter.is_visible() + } + _ => true, + } + } + + /// Destroy an existing picture. This is called just before + /// a frame builder is replaced with a newly built scene. It + /// gives a picture a chance to retain any cached tiles that + /// may be useful during the next scene build. + pub fn destroy( + &mut self, + retained_tiles: &mut RetainedTiles, + ) { + if let Some(tile_cache) = self.tile_cache.take() { + if !tile_cache.tiles.is_empty() { + retained_tiles.caches.insert( + tile_cache.slice, + PictureCacheState { + tiles: tile_cache.tiles, + spatial_node_comparer: tile_cache.spatial_node_comparer, + opacity_bindings: tile_cache.opacity_bindings, + color_bindings: tile_cache.color_bindings, + root_transform: tile_cache.root_transform, + current_tile_size: tile_cache.current_tile_size, + native_surface: tile_cache.native_surface, + external_native_surface_cache: tile_cache.external_native_surface_cache, + virtual_offset: tile_cache.virtual_offset, + frame_id: tile_cache.frame_id, + allocations: PictureCacheRecycledAllocations { + old_opacity_bindings: tile_cache.old_opacity_bindings, + old_color_bindings: tile_cache.old_color_bindings, + compare_cache: tile_cache.compare_cache, + }, + }, + ); + } + } + } + + // TODO(gw): We have the PictureOptions struct available. We + // should move some of the parameter list in this + // method to be part of the PictureOptions, and + // avoid adding new parameters here. + pub fn new_image( + requested_composite_mode: Option, + context_3d: Picture3DContext, + frame_output_pipeline_id: Option, + apply_local_clip_rect: bool, + flags: PrimitiveFlags, + requested_raster_space: RasterSpace, + prim_list: PrimitiveList, + spatial_node_index: SpatialNodeIndex, + tile_cache: Option>, + options: PictureOptions, + ) -> Self { + PicturePrimitive { + prim_list, + state: None, + secondary_render_task_id: None, + requested_composite_mode, + raster_config: None, + context_3d, + frame_output_pipeline_id, + extra_gpu_data_handles: SmallVec::new(), + apply_local_clip_rect, + is_backface_visible: flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE), + requested_raster_space, + spatial_node_index, + estimated_local_rect: LayoutRect::zero(), + precise_local_rect: LayoutRect::zero(), + tile_cache, + options, + segments_are_valid: false, + num_render_tasks: 0, + } + } + + /// Gets the raster space to use when rendering the picture. + /// Usually this would be the requested raster space. However, if the + /// picture's spatial node or one of its ancestors is being pinch zoomed + /// then we round it. This prevents us rasterizing glyphs for every minor + /// change in zoom level, as that would be too expensive. + pub fn get_raster_space(&self, spatial_tree: &SpatialTree) -> RasterSpace { + let spatial_node = &spatial_tree.spatial_nodes[self.spatial_node_index.0 as usize]; + if spatial_node.is_ancestor_or_self_zooming { + let scale_factors = spatial_tree + .get_relative_transform(self.spatial_node_index, ROOT_SPATIAL_NODE_INDEX) + .scale_factors(); + + // Round the scale up to the nearest power of 2, but don't exceed 8. + let scale = scale_factors.0.max(scale_factors.1).min(8.0); + let rounded_up = 2.0f32.powf(scale.log2().ceil()); + + RasterSpace::Local(rounded_up) + } else { + self.requested_raster_space + } + } + + pub fn take_context( + &mut self, + pic_index: PictureIndex, + clipped_prim_bounding_rect: WorldRect, + surface_spatial_node_index: SpatialNodeIndex, + raster_spatial_node_index: SpatialNodeIndex, + parent_surface_index: SurfaceIndex, + parent_subpixel_mode: &SubpixelMode, + frame_state: &mut FrameBuildingState, + frame_context: &FrameBuildingContext, + scratch: &mut PrimitiveScratchBuffer, + tile_cache_logger: &mut TileCacheLogger, + ) -> Option<(PictureContext, PictureState, PrimitiveList)> { + if !self.is_visible() { + return None; + } + + profile_scope!("take_context"); + let task_id = frame_state.surfaces[parent_surface_index.0].render_tasks.unwrap().port; + frame_state.render_tasks[task_id].children.reserve(self.num_render_tasks); + + // Extract the raster and surface spatial nodes from the raster + // config, if this picture establishes a surface. Otherwise just + // pass in the spatial node indices from the parent context. + let (raster_spatial_node_index, surface_spatial_node_index, surface_index, inflation_factor) = match self.raster_config { + Some(ref raster_config) => { + let surface = &frame_state.surfaces[raster_config.surface_index.0]; + + ( + surface.raster_spatial_node_index, + self.spatial_node_index, + raster_config.surface_index, + surface.inflation_factor, + ) + } + None => { + ( + raster_spatial_node_index, + surface_spatial_node_index, + parent_surface_index, + 0.0, + ) + } + }; + + let map_pic_to_world = SpaceMapper::new_with_target( + ROOT_SPATIAL_NODE_INDEX, + surface_spatial_node_index, + frame_context.global_screen_world_rect, + frame_context.spatial_tree, + ); + + let pic_bounds = map_pic_to_world.unmap(&map_pic_to_world.bounds) + .unwrap_or_else(PictureRect::max_rect); + + let map_local_to_pic = SpaceMapper::new( + surface_spatial_node_index, + pic_bounds, + ); + + let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers( + surface_spatial_node_index, + raster_spatial_node_index, + frame_context.global_screen_world_rect, + frame_context.spatial_tree, + ); + + let plane_splitter = match self.context_3d { + Picture3DContext::Out => { + None + } + Picture3DContext::In { root_data: Some(_), .. } => { + Some(PlaneSplitter::new()) + } + Picture3DContext::In { root_data: None, .. } => { + None + } + }; + + match self.raster_config { + Some(ref mut raster_config) => { + let pic_rect = self.precise_local_rect.cast_unit(); + + let mut device_pixel_scale = frame_state + .surfaces[raster_config.surface_index.0] + .device_pixel_scale; + + let scale_factors = frame_state + .surfaces[raster_config.surface_index.0] + .scale_factors; + + // If the primitive has a filter that can sample with an offset, the clip rect has + // to take it into account. + let clip_inflation = match raster_config.composite_mode { + PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => { + let mut max_offset = vec2(0.0, 0.0); + let mut min_offset = vec2(0.0, 0.0); + for shadow in shadows { + let offset = layout_vector_as_picture_vector(shadow.offset); + max_offset = max_offset.max(offset); + min_offset = min_offset.min(offset); + } + + // Get the shadow offsets in world space. + let raster_min = map_pic_to_raster.map_vector(min_offset); + let raster_max = map_pic_to_raster.map_vector(max_offset); + let world_min = map_raster_to_world.map_vector(raster_min); + let world_max = map_raster_to_world.map_vector(raster_max); + + // Grow the clip in the opposite direction of the shadow's offset. + SideOffsets2D::from_vectors_outer( + -world_max.max(vec2(0.0, 0.0)), + -world_min.min(vec2(0.0, 0.0)), + ) + } + _ => SideOffsets2D::zero(), + }; + + let (mut clipped, mut unclipped) = match get_raster_rects( + pic_rect, + &map_pic_to_raster, + &map_raster_to_world, + clipped_prim_bounding_rect.outer_rect(clip_inflation), + device_pixel_scale, + ) { + Some(info) => info, + None => { + return None + } + }; + let transform = map_pic_to_raster.get_transform(); + + /// If the picture (raster_config) establishes a raster root, + /// its requested resolution won't be clipped by the parent or + /// viewport; so we need to make sure the requested resolution is + /// "reasonable", ie. <= MAX_SURFACE_SIZE. If not, scale the + /// picture down until it fits that limit. This results in a new + /// device_rect, a new unclipped rect, and a new device_pixel_scale. + /// + /// Since the adjusted device_pixel_scale is passed into the + /// RenderTask (and then the shader via RenderTaskData) this mostly + /// works transparently, reusing existing support for variable DPI + /// support. The on-the-fly scaling can be seen as on-the-fly, + /// per-task DPI adjustment. Logical pixels are unaffected. + /// + /// The scaling factor is returned to the caller; blur radius, + /// font size, etc. need to be scaled accordingly. + fn adjust_scale_for_max_surface_size( + raster_config: &RasterConfig, + max_target_size: i32, + pic_rect: PictureRect, + map_pic_to_raster: &SpaceMapper, + map_raster_to_world: &SpaceMapper, + clipped_prim_bounding_rect: WorldRect, + device_pixel_scale : &mut DevicePixelScale, + device_rect: &mut DeviceRect, + unclipped: &mut DeviceRect) -> Option + { + let limit = if raster_config.establishes_raster_root { + MAX_SURFACE_SIZE + } else { + max_target_size as f32 + }; + if device_rect.size.width > limit || device_rect.size.height > limit { + // round_out will grow by 1 integer pixel if origin is on a + // fractional position, so keep that margin for error with -1: + let scale = (limit as f32 - 1.0) / + (f32::max(device_rect.size.width, device_rect.size.height)); + *device_pixel_scale = *device_pixel_scale * Scale::new(scale); + let new_device_rect = device_rect.to_f32() * Scale::new(scale); + *device_rect = new_device_rect.round_out(); + + *unclipped = match get_raster_rects( + pic_rect, + &map_pic_to_raster, + &map_raster_to_world, + clipped_prim_bounding_rect, + *device_pixel_scale + ) { + Some(info) => info.1, + None => { + return None + } + }; + Some(scale) + } + else + { + None + } + } + + let dep_info = match raster_config.composite_mode { + PictureCompositeMode::Filter(Filter::Blur(blur_radius)) => { + let blur_std_deviation = clamp_blur_radius(blur_radius, scale_factors) * device_pixel_scale.0; + let mut blur_std_deviation = DeviceSize::new( + blur_std_deviation * scale_factors.0, + blur_std_deviation * scale_factors.1 + ); + let mut device_rect = if self.options.inflate_if_required { + let inflation_factor = frame_state.surfaces[raster_config.surface_index.0].inflation_factor; + let inflation_factor = inflation_factor * device_pixel_scale.0; + + // The clipped field is the part of the picture that is visible + // on screen. The unclipped field is the screen-space rect of + // the complete picture, if no screen / clip-chain was applied + // (this includes the extra space for blur region). To ensure + // that we draw a large enough part of the picture to get correct + // blur results, inflate that clipped area by the blur range, and + // then intersect with the total screen rect, to minimize the + // allocation size. + clipped + .inflate(inflation_factor * scale_factors.0, inflation_factor * scale_factors.1) + .intersection(&unclipped) + .unwrap() + } else { + clipped + }; + + let mut original_size = device_rect.size; + + // Adjust the size to avoid introducing sampling errors during the down-scaling passes. + // what would be even better is to rasterize the picture at the down-scaled size + // directly. + device_rect.size = RenderTask::adjusted_blur_source_size( + device_rect.size, + blur_std_deviation, + ); + + if let Some(scale) = adjust_scale_for_max_surface_size( + raster_config, frame_context.fb_config.max_target_size, + pic_rect, &map_pic_to_raster, &map_raster_to_world, + clipped_prim_bounding_rect, + &mut device_pixel_scale, &mut device_rect, &mut unclipped, + ) { + blur_std_deviation = blur_std_deviation * scale; + original_size = original_size.to_f32() * scale; + raster_config.root_scaling_factor = scale; + } + + let device_rect = device_rect.to_i32(); + + let uv_rect_kind = calculate_uv_rect_kind( + &pic_rect, + &transform, + &device_rect, + device_pixel_scale, + ); + + let picture_task_id = frame_state.render_tasks.add().init( + RenderTask::new_picture( + RenderTaskLocation::Dynamic(None, device_rect.size), + unclipped.size, + pic_index, + device_rect.origin, + uv_rect_kind, + surface_spatial_node_index, + device_pixel_scale, + PrimitiveVisibilityMask::all(), + None, + None, + ) + ); + + let blur_render_task_id = RenderTask::new_blur( + blur_std_deviation, + picture_task_id, + frame_state.render_tasks, + RenderTargetKind::Color, + ClearMode::Transparent, + None, + original_size.to_i32(), + ); + + Some((blur_render_task_id, picture_task_id)) + } + PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => { + let mut max_std_deviation = 0.0; + for shadow in shadows { + max_std_deviation = f32::max(max_std_deviation, shadow.blur_radius); + } + max_std_deviation = clamp_blur_radius(max_std_deviation, scale_factors) * device_pixel_scale.0; + let max_blur_range = max_std_deviation * BLUR_SAMPLE_SCALE; + + // We cast clipped to f32 instead of casting unclipped to i32 + // because unclipped can overflow an i32. + let mut device_rect = clipped + .inflate(max_blur_range * scale_factors.0, max_blur_range * scale_factors.1) + .intersection(&unclipped) + .unwrap(); + + device_rect.size = RenderTask::adjusted_blur_source_size( + device_rect.size, + DeviceSize::new( + max_std_deviation * scale_factors.0, + max_std_deviation * scale_factors.1 + ), + ); + + if let Some(scale) = adjust_scale_for_max_surface_size( + raster_config, frame_context.fb_config.max_target_size, + pic_rect, &map_pic_to_raster, &map_raster_to_world, + clipped_prim_bounding_rect, + &mut device_pixel_scale, &mut device_rect, &mut unclipped, + ) { + // std_dev adjusts automatically from using device_pixel_scale + raster_config.root_scaling_factor = scale; + } + + let device_rect = device_rect.to_i32(); + + let uv_rect_kind = calculate_uv_rect_kind( + &pic_rect, + &transform, + &device_rect, + device_pixel_scale, + ); + + let picture_task_id = frame_state.render_tasks.add().init({ + let mut picture_task = RenderTask::new_picture( + RenderTaskLocation::Dynamic(None, device_rect.size), + unclipped.size, + pic_index, + device_rect.origin, + uv_rect_kind, + surface_spatial_node_index, + device_pixel_scale, + PrimitiveVisibilityMask::all(), + None, + None, + ); + picture_task.mark_for_saving(); + + picture_task + }); + + self.secondary_render_task_id = Some(picture_task_id); + + let mut blur_tasks = BlurTaskCache::default(); + + self.extra_gpu_data_handles.resize(shadows.len(), GpuCacheHandle::new()); + + let mut blur_render_task_id = picture_task_id; + for shadow in shadows { + let blur_radius = clamp_blur_radius(shadow.blur_radius, scale_factors) * device_pixel_scale.0; + blur_render_task_id = RenderTask::new_blur( + DeviceSize::new( + blur_radius * scale_factors.0, + blur_radius * scale_factors.1, + ), + picture_task_id, + frame_state.render_tasks, + RenderTargetKind::Color, + ClearMode::Transparent, + Some(&mut blur_tasks), + device_rect.size, + ); + } + + // TODO(nical) the second one should to be the blur's task id but we have several blurs now + Some((blur_render_task_id, picture_task_id)) + } + PictureCompositeMode::MixBlend(..) if !frame_context.fb_config.gpu_supports_advanced_blend => { + if let Some(scale) = adjust_scale_for_max_surface_size( + raster_config, frame_context.fb_config.max_target_size, + pic_rect, &map_pic_to_raster, &map_raster_to_world, + clipped_prim_bounding_rect, + &mut device_pixel_scale, &mut clipped, &mut unclipped, + ) { + raster_config.root_scaling_factor = scale; + } + + let clipped = clipped.to_i32(); + + let uv_rect_kind = calculate_uv_rect_kind( + &pic_rect, + &transform, + &clipped, + device_pixel_scale, + ); + + let readback_task_id = frame_state.render_tasks.add().init( + RenderTask::new_readback(clipped) + ); + + frame_state.render_tasks.add_dependency( + frame_state.surfaces[parent_surface_index.0].render_tasks.unwrap().port, + readback_task_id, + ); + + self.secondary_render_task_id = Some(readback_task_id); + + let render_task_id = frame_state.render_tasks.add().init( + RenderTask::new_picture( + RenderTaskLocation::Dynamic(None, clipped.size), + unclipped.size, + pic_index, + clipped.origin, + uv_rect_kind, + surface_spatial_node_index, + device_pixel_scale, + PrimitiveVisibilityMask::all(), + None, + None, + ) + ); + + Some((render_task_id, render_task_id)) + } + PictureCompositeMode::Filter(..) => { + + if let Some(scale) = adjust_scale_for_max_surface_size( + raster_config, frame_context.fb_config.max_target_size, + pic_rect, &map_pic_to_raster, &map_raster_to_world, + clipped_prim_bounding_rect, + &mut device_pixel_scale, &mut clipped, &mut unclipped, + ) { + raster_config.root_scaling_factor = scale; + } + + let clipped = clipped.to_i32(); + + let uv_rect_kind = calculate_uv_rect_kind( + &pic_rect, + &transform, + &clipped, + device_pixel_scale, + ); + + let render_task_id = frame_state.render_tasks.add().init( + RenderTask::new_picture( + RenderTaskLocation::Dynamic(None, clipped.size), + unclipped.size, + pic_index, + clipped.origin, + uv_rect_kind, + surface_spatial_node_index, + device_pixel_scale, + PrimitiveVisibilityMask::all(), + None, + None, + ) + ); + + Some((render_task_id, render_task_id)) + } + PictureCompositeMode::ComponentTransferFilter(..) => { + if let Some(scale) = adjust_scale_for_max_surface_size( + raster_config, frame_context.fb_config.max_target_size, + pic_rect, &map_pic_to_raster, &map_raster_to_world, + clipped_prim_bounding_rect, + &mut device_pixel_scale, &mut clipped, &mut unclipped, + ) { + raster_config.root_scaling_factor = scale; + } + + let clipped = clipped.to_i32(); + + let uv_rect_kind = calculate_uv_rect_kind( + &pic_rect, + &transform, + &clipped, + device_pixel_scale, + ); + + let render_task_id = frame_state.render_tasks.add().init( + RenderTask::new_picture( + RenderTaskLocation::Dynamic(None, clipped.size), + unclipped.size, + pic_index, + clipped.origin, + uv_rect_kind, + surface_spatial_node_index, + device_pixel_scale, + PrimitiveVisibilityMask::all(), + None, + None, + ) + ); + + Some((render_task_id, render_task_id)) + } + PictureCompositeMode::TileCache { .. } => { + let tile_cache = self.tile_cache.as_mut().unwrap(); + let mut first = true; + + // Get the overall world space rect of the picture cache. Used to clip + // the tile rects below for occlusion testing to the relevant area. + let world_clip_rect = map_pic_to_world + .map(&tile_cache.local_clip_rect) + .expect("bug: unable to map clip rect"); + let device_clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round(); + + for tile in tile_cache.tiles.values_mut() { + + if tile.is_visible { + // Get the world space rect that this tile will actually occupy on screem + let device_draw_rect = device_clip_rect.intersection(&tile.device_valid_rect); + + // If that draw rect is occluded by some set of tiles in front of it, + // then mark it as not visible and skip drawing. When it's not occluded + // it will fail this test, and get rasterized by the render task setup + // code below. + match device_draw_rect { + Some(device_draw_rect) => { + // Only check for occlusion on visible tiles that are fixed position. + if tile_cache.spatial_node_index == ROOT_SPATIAL_NODE_INDEX && + frame_state.composite_state.is_tile_occluded(tile.z_id, device_draw_rect) { + // If this tile has an allocated native surface, free it, since it's completely + // occluded. We will need to re-allocate this surface if it becomes visible, + // but that's likely to be rare (e.g. when there is no content display list + // for a frame or two during a tab switch). + let surface = tile.surface.as_mut().expect("no tile surface set!"); + + if let TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { id, .. }, .. } = surface { + if let Some(id) = id.take() { + frame_state.resource_cache.destroy_compositor_tile(id); + } + } + + tile.is_visible = false; + continue; + } + } + None => { + tile.is_visible = false; + } + } + } + + // If we get here, we want to ensure that the surface remains valid in the texture + // cache, _even if_ it's not visible due to clipping or being scrolled off-screen. + // This ensures that we retain valid tiles that are off-screen, but still in the + // display port of this tile cache instance. + if let Some(TileSurface::Texture { descriptor, .. }) = tile.surface.as_ref() { + if let SurfaceTextureDescriptor::TextureCache { ref handle, .. } = descriptor { + frame_state.resource_cache.texture_cache.request( + handle, + frame_state.gpu_cache, + ); + } + } + + // If the tile has been found to be off-screen / clipped, skip any further processing. + if !tile.is_visible { + continue; + } + + if frame_context.debug_flags.contains(DebugFlags::PICTURE_CACHING_DBG) { + tile.root.draw_debug_rects( + &map_pic_to_world, + tile.is_opaque, + tile.current_descriptor.local_valid_rect, + scratch, + frame_context.global_device_pixel_scale, + ); + + let label_offset = DeviceVector2D::new(20.0, 30.0); + let tile_device_rect = tile.world_tile_rect * frame_context.global_device_pixel_scale; + if tile_device_rect.size.height >= label_offset.y { + let surface = tile.surface.as_ref().expect("no tile surface set!"); + + scratch.push_debug_string( + tile_device_rect.origin + label_offset, + debug_colors::RED, + format!("{:?}: s={} is_opaque={} surface={}", + tile.id, + tile_cache.slice, + tile.is_opaque, + surface.kind(), + ), + ); + } + } + + if let TileSurface::Texture { descriptor, .. } = tile.surface.as_mut().unwrap() { + match descriptor { + SurfaceTextureDescriptor::TextureCache { ref handle, .. } => { + // Invalidate if the backing texture was evicted. + if frame_state.resource_cache.texture_cache.is_allocated(handle) { + // Request the backing texture so it won't get evicted this frame. + // We specifically want to mark the tile texture as used, even + // if it's detected not visible below and skipped. This is because + // we maintain the set of tiles we care about based on visibility + // during pre_update. If a tile still exists after that, we are + // assuming that it's either visible or we want to retain it for + // a while in case it gets scrolled back onto screen soon. + // TODO(gw): Consider switching to manual eviction policy? + frame_state.resource_cache.texture_cache.request(handle, frame_state.gpu_cache); + } else { + // If the texture was evicted on a previous frame, we need to assume + // that the entire tile rect is dirty. + tile.invalidate(None, InvalidationReason::NoTexture); + } + } + SurfaceTextureDescriptor::Native { id, .. } => { + if id.is_none() { + // There is no current surface allocation, so ensure the entire tile is invalidated + tile.invalidate(None, InvalidationReason::NoSurface); + } + } + } + } + + // Ensure that the dirty rect doesn't extend outside the local valid rect. + tile.local_dirty_rect = tile.local_dirty_rect + .intersection(&tile.current_descriptor.local_valid_rect) + .unwrap_or_else(PictureRect::zero); + + // Update the world/device dirty rect + let world_dirty_rect = map_pic_to_world.map(&tile.local_dirty_rect).expect("bug"); + + let device_rect = (tile.world_tile_rect * frame_context.global_device_pixel_scale).round(); + tile.device_dirty_rect = (world_dirty_rect * frame_context.global_device_pixel_scale) + .round_out() + .intersection(&device_rect) + .unwrap_or_else(DeviceRect::zero); + + if tile.is_valid { + continue; + } + + // Ensure that this texture is allocated. + if let TileSurface::Texture { ref mut descriptor, ref mut visibility_mask } = tile.surface.as_mut().unwrap() { + match descriptor { + SurfaceTextureDescriptor::TextureCache { ref mut handle } => { + if !frame_state.resource_cache.texture_cache.is_allocated(handle) { + frame_state.resource_cache.texture_cache.update_picture_cache( + tile_cache.current_tile_size, + handle, + frame_state.gpu_cache, + ); + } + } + SurfaceTextureDescriptor::Native { id } => { + if id.is_none() { + // Allocate a native surface id if we're in native compositing mode, + // and we don't have a surface yet (due to first frame, or destruction + // due to tile size changing etc). + if tile_cache.native_surface.is_none() { + let opaque = frame_state + .resource_cache + .create_compositor_surface( + tile_cache.virtual_offset, + tile_cache.current_tile_size, + true, + ); + + let alpha = frame_state + .resource_cache + .create_compositor_surface( + tile_cache.virtual_offset, + tile_cache.current_tile_size, + false, + ); + + tile_cache.native_surface = Some(NativeSurface { + opaque, + alpha, + }); + } + + // Create the tile identifier and allocate it. + let surface_id = if tile.is_opaque { + tile_cache.native_surface.as_ref().unwrap().opaque + } else { + tile_cache.native_surface.as_ref().unwrap().alpha + }; + + let tile_id = NativeTileId { + surface_id, + x: tile.tile_offset.x, + y: tile.tile_offset.y, + }; + + frame_state.resource_cache.create_compositor_tile(tile_id); + + *id = Some(tile_id); + } + } + } + + *visibility_mask = PrimitiveVisibilityMask::empty(); + let dirty_region_index = tile_cache.dirty_region.dirty_rects.len(); + + // If we run out of dirty regions, then force the last dirty region to + // be a union of any remaining regions. This is an inefficiency, in that + // we'll add items to batches later on that are redundant / outside this + // tile, but it's really rare except in pathological cases (even on a + // 4k screen, the typical dirty region count is < 16). + if dirty_region_index < PrimitiveVisibilityMask::MAX_DIRTY_REGIONS { + visibility_mask.set_visible(dirty_region_index); + + tile_cache.dirty_region.push( + world_dirty_rect, + *visibility_mask, + ); + } else { + visibility_mask.set_visible(PrimitiveVisibilityMask::MAX_DIRTY_REGIONS - 1); + + tile_cache.dirty_region.include_rect( + PrimitiveVisibilityMask::MAX_DIRTY_REGIONS - 1, + world_dirty_rect, + ); + } + + let content_origin_f = tile.world_tile_rect.origin * device_pixel_scale; + let content_origin = content_origin_f.round(); + debug_assert!((content_origin_f.x - content_origin.x).abs() < 0.01); + debug_assert!((content_origin_f.y - content_origin.y).abs() < 0.01); + + let surface = descriptor.resolve( + frame_state.resource_cache, + tile_cache.current_tile_size, + ); + + let scissor_rect = tile.device_dirty_rect + .translate(-device_rect.origin.to_vector()) + .round() + .to_i32(); + + let valid_rect = tile.device_valid_rect + .translate(-device_rect.origin.to_vector()) + .round() + .to_i32(); + + let render_task_id = frame_state.render_tasks.add().init( + RenderTask::new_picture( + RenderTaskLocation::PictureCache { + size: tile_cache.current_tile_size, + surface, + }, + tile_cache.current_tile_size.to_f32(), + pic_index, + content_origin.to_i32(), + UvRectKind::Rect, + surface_spatial_node_index, + device_pixel_scale, + *visibility_mask, + Some(scissor_rect), + Some(valid_rect), + ) + ); + + frame_state.render_tasks.add_dependency( + frame_state.surfaces[parent_surface_index.0].render_tasks.unwrap().port, + render_task_id, + ); + + if first { + // TODO(gw): Maybe we can restructure this code to avoid the + // first hack here. Or at least explain it with a follow up + // bug. + frame_state.surfaces[raster_config.surface_index.0].render_tasks = Some(SurfaceRenderTasks { + root: render_task_id, + port: render_task_id, + }); + + first = false; + } + } + + // Now that the tile is valid, reset the dirty rect. + tile.local_dirty_rect = PictureRect::zero(); + tile.is_valid = true; + } + + // If invalidation debugging is enabled, dump the picture cache state to a tree printer. + if frame_context.debug_flags.contains(DebugFlags::INVALIDATION_DBG) { + tile_cache.print(); + } + + None + } + PictureCompositeMode::MixBlend(..) | + PictureCompositeMode::Blit(_) => { + if let Some(scale) = adjust_scale_for_max_surface_size( + raster_config, frame_context.fb_config.max_target_size, + pic_rect, &map_pic_to_raster, &map_raster_to_world, + clipped_prim_bounding_rect, + &mut device_pixel_scale, &mut clipped, &mut unclipped, + ) { + raster_config.root_scaling_factor = scale; + } + + let clipped = clipped.to_i32(); + + let uv_rect_kind = calculate_uv_rect_kind( + &pic_rect, + &transform, + &clipped, + device_pixel_scale, + ); + + let render_task_id = frame_state.render_tasks.add().init( + RenderTask::new_picture( + RenderTaskLocation::Dynamic(None, clipped.size), + unclipped.size, + pic_index, + clipped.origin, + uv_rect_kind, + surface_spatial_node_index, + device_pixel_scale, + PrimitiveVisibilityMask::all(), + None, + None, + ) + ); + + Some((render_task_id, render_task_id)) + } + PictureCompositeMode::SvgFilter(ref primitives, ref filter_datas) => { + + if let Some(scale) = adjust_scale_for_max_surface_size( + raster_config, frame_context.fb_config.max_target_size, + pic_rect, &map_pic_to_raster, &map_raster_to_world, + clipped_prim_bounding_rect, + &mut device_pixel_scale, &mut clipped, &mut unclipped, + ) { + raster_config.root_scaling_factor = scale; + } + + let clipped = clipped.to_i32(); + + let uv_rect_kind = calculate_uv_rect_kind( + &pic_rect, + &transform, + &clipped, + device_pixel_scale, + ); + + let picture_task_id = frame_state.render_tasks.add().init( + RenderTask::new_picture( + RenderTaskLocation::Dynamic(None, clipped.size), + unclipped.size, + pic_index, + clipped.origin, + uv_rect_kind, + surface_spatial_node_index, + device_pixel_scale, + PrimitiveVisibilityMask::all(), + None, + None, + ) + ); + + let filter_task_id = RenderTask::new_svg_filter( + primitives, + filter_datas, + &mut frame_state.render_tasks, + clipped.size, + uv_rect_kind, + picture_task_id, + device_pixel_scale, + ); + + Some((filter_task_id, picture_task_id)) + } + }; + + if let Some((root, port)) = dep_info { + frame_state.surfaces[raster_config.surface_index.0].render_tasks = Some(SurfaceRenderTasks { + root, + port, + }); + + frame_state.render_tasks.add_dependency( + frame_state.surfaces[parent_surface_index.0].render_tasks.unwrap().port, + root, + ); + } + } + None => {} + }; + + #[cfg(feature = "capture")] + { + if frame_context.debug_flags.contains(DebugFlags::TILE_CACHE_LOGGING_DBG) { + if let Some(ref tile_cache) = self.tile_cache + { + // extract just the fields that we're interested in + let mut tile_cache_tiny = TileCacheInstanceSerializer { + slice: tile_cache.slice, + tiles: FastHashMap::default(), + background_color: tile_cache.background_color, + fract_offset: tile_cache.fract_offset + }; + for (key, tile) in &tile_cache.tiles { + tile_cache_tiny.tiles.insert(*key, TileSerializer { + rect: tile.local_tile_rect, + current_descriptor: tile.current_descriptor.clone(), + fract_offset: tile.fract_offset, + id: tile.id, + root: tile.root.clone(), + background_color: tile.background_color, + invalidation_reason: tile.invalidation_reason.clone() + }); + } + let text = ron::ser::to_string_pretty(&tile_cache_tiny, Default::default()).unwrap(); + tile_cache_logger.add(text, map_pic_to_world.get_transform()); + } + } + } + #[cfg(not(feature = "capture"))] + { + let _tile_cache_logger = tile_cache_logger; // unused variable fix + } + + let state = PictureState { + //TODO: check for MAX_CACHE_SIZE here? + map_local_to_pic, + map_pic_to_world, + map_pic_to_raster, + map_raster_to_world, + plane_splitter, + }; + + let mut dirty_region_count = 0; + + // If this is a picture cache, push the dirty region to ensure any + // child primitives are culled and clipped to the dirty rect(s). + if let Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { .. }, .. }) = self.raster_config { + let dirty_region = self.tile_cache.as_ref().unwrap().dirty_region.clone(); + frame_state.push_dirty_region(dirty_region); + dirty_region_count += 1; + } + + if inflation_factor > 0.0 { + let inflated_region = frame_state.current_dirty_region().inflate(inflation_factor); + frame_state.push_dirty_region(inflated_region); + dirty_region_count += 1; + } + + // Disallow subpixel AA if an intermediate surface is needed. + // TODO(lsalzman): allow overriding parent if intermediate surface is opaque + let (is_passthrough, subpixel_mode) = match self.raster_config { + Some(RasterConfig { ref composite_mode, .. }) => { + let subpixel_mode = match composite_mode { + PictureCompositeMode::TileCache { .. } => { + self.tile_cache.as_ref().unwrap().subpixel_mode.clone() + } + PictureCompositeMode::Blit(..) | + PictureCompositeMode::ComponentTransferFilter(..) | + PictureCompositeMode::Filter(..) | + PictureCompositeMode::MixBlend(..) | + PictureCompositeMode::SvgFilter(..) => { + // TODO(gw): We can take advantage of the same logic that + // exists in the opaque rect detection for tile + // caches, to allow subpixel text on other surfaces + // that can be detected as opaque. + SubpixelMode::Deny + } + }; + + (false, subpixel_mode) + } + None => { + (true, SubpixelMode::Allow) + } + }; + + // Still disable subpixel AA if parent forbids it + let subpixel_mode = match (parent_subpixel_mode, subpixel_mode) { + (SubpixelMode::Allow, SubpixelMode::Allow) => { + // Both parent and this surface unconditionally allow subpixel AA + SubpixelMode::Allow + } + (SubpixelMode::Allow, SubpixelMode::Conditional { allowed_rect, excluded_rects }) => { + // Parent allows, but we are conditional subpixel AA + SubpixelMode::Conditional { + allowed_rect, + excluded_rects, + } + } + (SubpixelMode::Conditional { allowed_rect, excluded_rects }, SubpixelMode::Allow) => { + // Propagate conditional subpixel mode to child pictures that allow subpixel AA + SubpixelMode::Conditional { + allowed_rect: *allowed_rect, + excluded_rects: excluded_rects.clone(), + } + } + (SubpixelMode::Conditional { .. }, SubpixelMode::Conditional { ..}) => { + unreachable!("bug: only top level picture caches have conditional subpixel"); + } + (SubpixelMode::Deny, _) | (_, SubpixelMode::Deny) => { + // Either parent or this surface explicitly deny subpixel, these take precedence + SubpixelMode::Deny + } + }; + + let context = PictureContext { + pic_index, + apply_local_clip_rect: self.apply_local_clip_rect, + is_passthrough, + raster_spatial_node_index, + surface_spatial_node_index, + surface_index, + dirty_region_count, + subpixel_mode, + }; + + let prim_list = mem::replace(&mut self.prim_list, PrimitiveList::empty()); + + Some((context, state, prim_list)) + } + + pub fn restore_context( + &mut self, + parent_surface_index: SurfaceIndex, + prim_list: PrimitiveList, + context: PictureContext, + state: PictureState, + frame_state: &mut FrameBuildingState, + ) { + // Pop any dirty regions this picture set + for _ in 0 .. context.dirty_region_count { + frame_state.pop_dirty_region(); + } + + let task_id = frame_state.surfaces[parent_surface_index.0].render_tasks.unwrap().port; + self.num_render_tasks = frame_state.render_tasks[task_id].children.len(); + + self.prim_list = prim_list; + self.state = Some(state); + } + + pub fn take_state(&mut self) -> PictureState { + self.state.take().expect("bug: no state present!") + } + + /// Add a primitive instance to the plane splitter. The function would generate + /// an appropriate polygon, clip it against the frustum, and register with the + /// given plane splitter. + pub fn add_split_plane( + splitter: &mut PlaneSplitter, + spatial_tree: &SpatialTree, + prim_spatial_node_index: SpatialNodeIndex, + original_local_rect: LayoutRect, + combined_local_clip_rect: &LayoutRect, + world_rect: WorldRect, + plane_split_anchor: PlaneSplitAnchor, + ) -> bool { + let transform = spatial_tree + .get_world_transform(prim_spatial_node_index); + let matrix = transform.clone().into_transform().cast(); + + // Apply the local clip rect here, before splitting. This is + // because the local clip rect can't be applied in the vertex + // shader for split composites, since we are drawing polygons + // rather that rectangles. The interpolation still works correctly + // since we determine the UVs by doing a bilerp with a factor + // from the original local rect. + let local_rect = match original_local_rect + .intersection(combined_local_clip_rect) + { + Some(rect) => rect.cast(), + None => return false, + }; + let world_rect = world_rect.cast(); + + match transform { + CoordinateSpaceMapping::Local => { + let polygon = Polygon::from_rect( + local_rect * Scale::new(1.0), + plane_split_anchor, + ); + splitter.add(polygon); + } + CoordinateSpaceMapping::ScaleOffset(scale_offset) if scale_offset.scale == Vector2D::new(1.0, 1.0) => { + let inv_matrix = scale_offset.inverse().to_transform().cast(); + let polygon = Polygon::from_transformed_rect_with_inverse( + local_rect, + &matrix, + &inv_matrix, + plane_split_anchor, + ).unwrap(); + splitter.add(polygon); + } + CoordinateSpaceMapping::ScaleOffset(_) | + CoordinateSpaceMapping::Transform(_) => { + let mut clipper = Clipper::new(); + let results = clipper.clip_transformed( + Polygon::from_rect( + local_rect, + plane_split_anchor, + ), + &matrix, + Some(world_rect), + ); + if let Ok(results) = results { + for poly in results { + splitter.add(poly); + } + } + } + } + + true + } + + pub fn resolve_split_planes( + &mut self, + splitter: &mut PlaneSplitter, + gpu_cache: &mut GpuCache, + spatial_tree: &SpatialTree, + ) { + let ordered = match self.context_3d { + Picture3DContext::In { root_data: Some(ref mut list), .. } => list, + _ => panic!("Expected to find 3D context root"), + }; + ordered.clear(); + + // Process the accumulated split planes and order them for rendering. + // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order. + for poly in splitter.sort(vec3(0.0, 0.0, 1.0)) { + let cluster = &self.prim_list.clusters[poly.anchor.cluster_index]; + let spatial_node_index = cluster.spatial_node_index; + let transform = match spatial_tree + .get_world_transform(spatial_node_index) + .inverse() + { + Some(transform) => transform.into_transform(), + // logging this would be a bit too verbose + None => continue, + }; + + let local_points = [ + transform.transform_point3d(poly.points[0].cast()), + transform.transform_point3d(poly.points[1].cast()), + transform.transform_point3d(poly.points[2].cast()), + transform.transform_point3d(poly.points[3].cast()), + ]; + + // If any of the points are un-transformable, just drop this + // plane from drawing. + if local_points.iter().any(|p| p.is_none()) { + continue; + } + + let p0 = local_points[0].unwrap(); + let p1 = local_points[1].unwrap(); + let p2 = local_points[2].unwrap(); + let p3 = local_points[3].unwrap(); + let gpu_blocks = [ + [p0.x, p0.y, p1.x, p1.y].into(), + [p2.x, p2.y, p3.x, p3.y].into(), + ]; + let gpu_handle = gpu_cache.push_per_frame_blocks(&gpu_blocks); + let gpu_address = gpu_cache.get_address(&gpu_handle); + + ordered.push(OrderedPictureChild { + anchor: poly.anchor, + spatial_node_index, + gpu_address, + }); + } + } + + /// Called during initial picture traversal, before we know the + /// bounding rect of children. It is possible to determine the + /// surface / raster config now though. + fn pre_update( + &mut self, + state: &mut PictureUpdateState, + frame_context: &FrameBuildingContext, + ) -> Option { + // Reset raster config in case we early out below. + self.raster_config = None; + + // Resolve animation properties, and early out if the filter + // properties make this picture invisible. + if !self.resolve_scene_properties(frame_context.scene_properties) { + return None; + } + + // For out-of-preserve-3d pictures, the backface visibility is determined by + // the local transform only. + // Note: we aren't taking the transform relativce to the parent picture, + // since picture tree can be more dense than the corresponding spatial tree. + if !self.is_backface_visible { + if let Picture3DContext::Out = self.context_3d { + match frame_context.spatial_tree.get_local_visible_face(self.spatial_node_index) { + VisibleFace::Front => {} + VisibleFace::Back => return None, + } + } + } + + // Push information about this pic on stack for children to read. + state.push_picture(PictureInfo { + _spatial_node_index: self.spatial_node_index, + }); + + // See if this picture actually needs a surface for compositing. + let actual_composite_mode = match self.requested_composite_mode { + Some(PictureCompositeMode::Filter(ref filter)) if filter.is_noop() => None, + Some(PictureCompositeMode::TileCache { .. }) => { + // Only allow picture caching composite mode if global picture caching setting + // is enabled this frame. + if state.composite_state.picture_caching_is_enabled { + Some(PictureCompositeMode::TileCache { }) + } else { + None + } + }, + ref mode => mode.clone(), + }; + + if let Some(composite_mode) = actual_composite_mode { + // Retrieve the positioning node information for the parent surface. + let parent_raster_node_index = state.current_surface().raster_spatial_node_index; + let parent_device_pixel_scale = state.current_surface().device_pixel_scale; + let surface_spatial_node_index = self.spatial_node_index; + + // Filters must be applied before transforms, to do this, we can mark this picture as establishing a raster root. + let has_svg_filter = if let PictureCompositeMode::SvgFilter(..) = composite_mode { + true + } else { + false + }; + + let surface_to_parent_transform = frame_context.spatial_tree + .get_relative_transform(surface_spatial_node_index, parent_raster_node_index); + + // Check if there is perspective or if an SVG filter is applied, and thus whether a new + // rasterization root should be established. + let establishes_raster_root = has_svg_filter || surface_to_parent_transform.is_perspective(); + + let (raster_spatial_node_index, device_pixel_scale) = if establishes_raster_root { + // If a raster root is established, this surface should be scaled based on the scale factors of the surface raster to parent raster transform. + // This scaling helps ensure that the content in this surface does not become blurry or pixelated when composited in the parent surface. + let scale_factors = surface_to_parent_transform.scale_factors(); + + // Pick the largest scale factor of the transform for the scaling factor. + // Currently, we ensure that the scaling factor is >= 1.0 as a smaller scale factor can result in blurry output. + let scaling_factor = scale_factors.0.max(scale_factors.1).max(1.0); + + let device_pixel_scale = parent_device_pixel_scale * Scale::new(scaling_factor); + (surface_spatial_node_index, device_pixel_scale) + } else { + (parent_raster_node_index, parent_device_pixel_scale) + }; + + let scale_factors = frame_context + .spatial_tree + .get_relative_transform(surface_spatial_node_index, raster_spatial_node_index) + .scale_factors(); + + // This inflation factor is to be applied to all primitives within the surface. + // Only inflate if the caller hasn't already inflated the bounding rects for this filter. + let mut inflation_factor = 0.0; + if self.options.inflate_if_required { + match composite_mode { + PictureCompositeMode::Filter(Filter::Blur(blur_radius)) => { + let blur_radius = clamp_blur_radius(blur_radius, scale_factors); + // The amount of extra space needed for primitives inside + // this picture to ensure the visibility check is correct. + inflation_factor = blur_radius * BLUR_SAMPLE_SCALE; + } + PictureCompositeMode::SvgFilter(ref primitives, _) => { + let mut max = 0.0; + for primitive in primitives { + if let FilterPrimitiveKind::Blur(ref blur) = primitive.kind { + max = f32::max(max, blur.radius); + } + } + inflation_factor = clamp_blur_radius(max, scale_factors) * BLUR_SAMPLE_SCALE; + } + PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => { + // TODO(gw): This is incorrect, since we don't consider the drop shadow + // offset. However, fixing that is a larger task, so this is + // an improvement on the current case (this at least works where + // the offset of the drop-shadow is ~0, which is often true). + + // Can't use max_by_key here since f32 isn't Ord + let mut max_blur_radius: f32 = 0.0; + for shadow in shadows { + max_blur_radius = max_blur_radius.max(shadow.blur_radius); + } + + inflation_factor = clamp_blur_radius(max_blur_radius, scale_factors) * BLUR_SAMPLE_SCALE; + } + _ => {} + } + } + + let surface = SurfaceInfo::new( + surface_spatial_node_index, + raster_spatial_node_index, + inflation_factor, + frame_context.global_screen_world_rect, + &frame_context.spatial_tree, + device_pixel_scale, + scale_factors, + ); + + self.raster_config = Some(RasterConfig { + composite_mode, + establishes_raster_root, + surface_index: state.push_surface(surface), + root_scaling_factor: 1.0, + }); + } + + Some(mem::replace(&mut self.prim_list, PrimitiveList::empty())) + } + + /// Called after updating child pictures during the initial + /// picture traversal. + fn post_update( + &mut self, + prim_list: PrimitiveList, + state: &mut PictureUpdateState, + frame_context: &FrameBuildingContext, + data_stores: &mut DataStores, + ) { + // Restore the pictures list used during recursion. + self.prim_list = prim_list; + + // Pop the state information about this picture. + state.pop_picture(); + + for cluster in &mut self.prim_list.clusters { + cluster.flags.remove(ClusterFlags::IS_VISIBLE); + + // Skip the cluster if backface culled. + if !cluster.flags.contains(ClusterFlags::IS_BACKFACE_VISIBLE) { + // For in-preserve-3d primitives and pictures, the backface visibility is + // evaluated relative to the containing block. + if let Picture3DContext::In { ancestor_index, .. } = self.context_3d { + match frame_context.spatial_tree + .get_relative_transform(cluster.spatial_node_index, ancestor_index) + .visible_face() + { + VisibleFace::Back => continue, + VisibleFace::Front => (), + } + } + } + + // No point including this cluster if it can't be transformed + let spatial_node = &frame_context + .spatial_tree + .spatial_nodes[cluster.spatial_node_index.0 as usize]; + if !spatial_node.invertible { + continue; + } + + // Update any primitives/cluster bounding rects that can only be done + // with information available during frame building. + if cluster.flags.contains(ClusterFlags::IS_BACKDROP_FILTER) { + let backdrop_to_world_mapper = SpaceMapper::new_with_target( + ROOT_SPATIAL_NODE_INDEX, + cluster.spatial_node_index, + LayoutRect::max_rect(), + frame_context.spatial_tree, + ); + + for prim_instance in &mut cluster.prim_instances { + match prim_instance.kind { + PrimitiveInstanceKind::Backdrop { data_handle, .. } => { + // The actual size and clip rect of this primitive are determined by computing the bounding + // box of the projected rect of the backdrop-filter element onto the backdrop. + let prim_data = &mut data_stores.backdrop[data_handle]; + let spatial_node_index = prim_data.kind.spatial_node_index; + + // We cannot use the relative transform between the backdrop and the element because + // that doesn't take into account any projection transforms that both spatial nodes are children of. + // Instead, we first project from the element to the world space and get a flattened 2D bounding rect + // in the screen space, we then map this rect from the world space to the backdrop space to get the + // proper bounding box where the backdrop-filter needs to be processed. + + let prim_to_world_mapper = SpaceMapper::new_with_target( + ROOT_SPATIAL_NODE_INDEX, + spatial_node_index, + LayoutRect::max_rect(), + frame_context.spatial_tree, + ); + + // First map to the screen and get a flattened rect + let prim_rect = prim_to_world_mapper.map(&prim_data.kind.border_rect).unwrap_or_else(LayoutRect::zero); + // Backwards project the flattened rect onto the backdrop + let prim_rect = backdrop_to_world_mapper.unmap(&prim_rect).unwrap_or_else(LayoutRect::zero); + + // TODO(aosmond): Is this safe? Updating the primitive size during + // frame building is usually problematic since scene building will cache + // the primitive information in the GPU already. + prim_data.common.prim_rect = prim_rect; + prim_instance.local_clip_rect = prim_rect; + + // Update the cluster bounding rect now that we have the backdrop rect. + cluster.bounding_rect = cluster.bounding_rect.union(&prim_rect); + } + _ => { + panic!("BUG: unexpected deferred primitive kind for cluster updates"); + } + } + } + } + + // Map the cluster bounding rect into the space of the surface, and + // include it in the surface bounding rect. + let surface = state.current_surface_mut(); + surface.map_local_to_surface.set_target_spatial_node( + cluster.spatial_node_index, + frame_context.spatial_tree, + ); + + // Mark the cluster visible, since it passed the invertible and + // backface checks. In future, this will include spatial clustering + // which will allow the frame building code to skip most of the + // current per-primitive culling code. + cluster.flags.insert(ClusterFlags::IS_VISIBLE); + if let Some(cluster_rect) = surface.map_local_to_surface.map(&cluster.bounding_rect) { + surface.rect = surface.rect.union(&cluster_rect); + } + } + + // If this picture establishes a surface, then map the surface bounding + // rect into the parent surface coordinate space, and propagate that up + // to the parent. + if let Some(ref mut raster_config) = self.raster_config { + let surface = state.current_surface_mut(); + // Inflate the local bounding rect if required by the filter effect. + // This inflaction factor is to be applied to the surface itself. + if self.options.inflate_if_required { + surface.rect = raster_config.composite_mode.inflate_picture_rect(surface.rect, surface.scale_factors); + + // The picture's local rect is calculated as the union of the + // snapped primitive rects, which should result in a snapped + // local rect, unless it was inflated. This is also done during + // update visibility when calculating the picture's precise + // local rect. + let snap_surface_to_raster = SpaceSnapper::new_with_target( + surface.raster_spatial_node_index, + self.spatial_node_index, + surface.device_pixel_scale, + frame_context.spatial_tree, + ); + + surface.rect = snap_surface_to_raster.snap_rect(&surface.rect); + } + + let mut surface_rect = surface.rect * Scale::new(1.0); + + // Pop this surface from the stack + let surface_index = state.pop_surface(); + debug_assert_eq!(surface_index, raster_config.surface_index); + + // Check if any of the surfaces can't be rasterized in local space but want to. + if raster_config.establishes_raster_root + && (surface_rect.size.width > MAX_SURFACE_SIZE + || surface_rect.size.height > MAX_SURFACE_SIZE) + && frame_context.debug_flags.contains(DebugFlags::DISABLE_RASTER_ROOT_SCALING) + { + raster_config.establishes_raster_root = false; + state.are_raster_roots_assigned = false; + } + + // Set the estimated and precise local rects. The precise local rect + // may be changed again during frame visibility. + self.estimated_local_rect = surface_rect; + self.precise_local_rect = surface_rect; + + // Drop shadows draw both a content and shadow rect, so need to expand the local + // rect of any surfaces to be composited in parent surfaces correctly. + match raster_config.composite_mode { + PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => { + for shadow in shadows { + let shadow_rect = self.estimated_local_rect.translate(shadow.offset); + surface_rect = surface_rect.union(&shadow_rect); + } + } + _ => {} + } + + // Propagate up to parent surface, now that we know this surface's static rect + let parent_surface = state.current_surface_mut(); + parent_surface.map_local_to_surface.set_target_spatial_node( + self.spatial_node_index, + frame_context.spatial_tree, + ); + if let Some(parent_surface_rect) = parent_surface + .map_local_to_surface + .map(&surface_rect) + { + parent_surface.rect = parent_surface.rect.union(&parent_surface_rect); + } + } + } + + pub fn prepare_for_render( + &mut self, + frame_context: &FrameBuildingContext, + frame_state: &mut FrameBuildingState, + data_stores: &mut DataStores, + ) -> bool { + let mut pic_state_for_children = self.take_state(); + + if let Some(ref mut splitter) = pic_state_for_children.plane_splitter { + self.resolve_split_planes( + splitter, + &mut frame_state.gpu_cache, + &frame_context.spatial_tree, + ); + } + + let raster_config = match self.raster_config { + Some(ref mut raster_config) => raster_config, + None => { + return true + } + }; + + // TODO(gw): Almost all of the Picture types below use extra_gpu_cache_data + // to store the same type of data. The exception is the filter + // with a ColorMatrix, which stores the color matrix here. It's + // probably worth tidying this code up to be a bit more consistent. + // Perhaps store the color matrix after the common data, even though + // it's not used by that shader. + + match raster_config.composite_mode { + PictureCompositeMode::TileCache { .. } => {} + PictureCompositeMode::Filter(Filter::Blur(..)) => {} + PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => { + self.extra_gpu_data_handles.resize(shadows.len(), GpuCacheHandle::new()); + for (shadow, extra_handle) in shadows.iter().zip(self.extra_gpu_data_handles.iter_mut()) { + if let Some(mut request) = frame_state.gpu_cache.request(extra_handle) { + // Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs) + // [brush specific data] + // [segment_rect, segment data] + let shadow_rect = self.precise_local_rect.translate(shadow.offset); + + // ImageBrush colors + request.push(shadow.color.premultiplied()); + request.push(PremultipliedColorF::WHITE); + request.push([ + self.precise_local_rect.size.width, + self.precise_local_rect.size.height, + 0.0, + 0.0, + ]); + + // segment rect / extra data + request.push(shadow_rect); + request.push([0.0, 0.0, 0.0, 0.0]); + } + } + } + PictureCompositeMode::MixBlend(..) if !frame_context.fb_config.gpu_supports_advanced_blend => {} + PictureCompositeMode::Filter(ref filter) => { + match *filter { + Filter::ColorMatrix(ref m) => { + if self.extra_gpu_data_handles.is_empty() { + self.extra_gpu_data_handles.push(GpuCacheHandle::new()); + } + if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handles[0]) { + for i in 0..5 { + request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]); + } + } + } + Filter::Flood(ref color) => { + if self.extra_gpu_data_handles.is_empty() { + self.extra_gpu_data_handles.push(GpuCacheHandle::new()); + } + if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handles[0]) { + request.push(color.to_array()); + } + } + _ => {} + } + } + PictureCompositeMode::ComponentTransferFilter(handle) => { + let filter_data = &mut data_stores.filter_data[handle]; + filter_data.update(frame_state); + } + PictureCompositeMode::MixBlend(..) | + PictureCompositeMode::Blit(_) | + PictureCompositeMode::SvgFilter(..) => {} + } + + true + } +} + +// Calculate a single homogeneous screen-space UV for a picture. +fn calculate_screen_uv( + local_pos: &PicturePoint, + transform: &PictureToRasterTransform, + rendered_rect: &DeviceRect, + device_pixel_scale: DevicePixelScale, +) -> DeviceHomogeneousVector { + let raster_pos = transform.transform_point2d_homogeneous(*local_pos); + + DeviceHomogeneousVector::new( + (raster_pos.x * device_pixel_scale.0 - rendered_rect.origin.x * raster_pos.w) / rendered_rect.size.width, + (raster_pos.y * device_pixel_scale.0 - rendered_rect.origin.y * raster_pos.w) / rendered_rect.size.height, + 0.0, + raster_pos.w, + ) +} + +// Calculate a UV rect within an image based on the screen space +// vertex positions of a picture. +fn calculate_uv_rect_kind( + pic_rect: &PictureRect, + transform: &PictureToRasterTransform, + rendered_rect: &DeviceIntRect, + device_pixel_scale: DevicePixelScale, +) -> UvRectKind { + let rendered_rect = rendered_rect.to_f32(); + + let top_left = calculate_screen_uv( + &pic_rect.origin, + transform, + &rendered_rect, + device_pixel_scale, + ); + + let top_right = calculate_screen_uv( + &pic_rect.top_right(), + transform, + &rendered_rect, + device_pixel_scale, + ); + + let bottom_left = calculate_screen_uv( + &pic_rect.bottom_left(), + transform, + &rendered_rect, + device_pixel_scale, + ); + + let bottom_right = calculate_screen_uv( + &pic_rect.bottom_right(), + transform, + &rendered_rect, + device_pixel_scale, + ); + + UvRectKind::Quad { + top_left, + top_right, + bottom_left, + bottom_right, + } +} + +fn create_raster_mappers( + surface_spatial_node_index: SpatialNodeIndex, + raster_spatial_node_index: SpatialNodeIndex, + world_rect: WorldRect, + spatial_tree: &SpatialTree, +) -> (SpaceMapper, SpaceMapper) { + let map_raster_to_world = SpaceMapper::new_with_target( + ROOT_SPATIAL_NODE_INDEX, + raster_spatial_node_index, + world_rect, + spatial_tree, + ); + + let raster_bounds = map_raster_to_world.unmap(&world_rect) + .unwrap_or_else(RasterRect::max_rect); + + let map_pic_to_raster = SpaceMapper::new_with_target( + raster_spatial_node_index, + surface_spatial_node_index, + raster_bounds, + spatial_tree, + ); + + (map_raster_to_world, map_pic_to_raster) +} + +fn get_transform_key( + spatial_node_index: SpatialNodeIndex, + cache_spatial_node_index: SpatialNodeIndex, + spatial_tree: &SpatialTree, +) -> TransformKey { + // Note: this is the only place where we don't know beforehand if the tile-affecting + // spatial node is below or above the current picture. + let transform = if cache_spatial_node_index >= spatial_node_index { + spatial_tree + .get_relative_transform( + cache_spatial_node_index, + spatial_node_index, + ) + } else { + spatial_tree + .get_relative_transform( + spatial_node_index, + cache_spatial_node_index, + ) + }; + transform.into() +} + +/// A key for storing primitive comparison results during tile dependency tests. +#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] +struct PrimitiveComparisonKey { + prev_index: PrimitiveDependencyIndex, + curr_index: PrimitiveDependencyIndex, +} + +/// Information stored an image dependency +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ImageDependency { + pub key: ImageKey, + pub generation: ImageGeneration, +} + +impl ImageDependency { + pub const INVALID: ImageDependency = ImageDependency { + key: ImageKey::DUMMY, + generation: ImageGeneration::INVALID, + }; +} + +/// A helper struct to compare a primitive and all its sub-dependencies. +struct PrimitiveComparer<'a> { + clip_comparer: CompareHelper<'a, ItemUid>, + transform_comparer: CompareHelper<'a, SpatialNodeKey>, + image_comparer: CompareHelper<'a, ImageDependency>, + opacity_comparer: CompareHelper<'a, OpacityBinding>, + color_comparer: CompareHelper<'a, ColorBinding>, + resource_cache: &'a ResourceCache, + spatial_node_comparer: &'a mut SpatialNodeComparer, + opacity_bindings: &'a FastHashMap, + color_bindings: &'a FastHashMap, +} + +impl<'a> PrimitiveComparer<'a> { + fn new( + prev: &'a TileDescriptor, + curr: &'a TileDescriptor, + resource_cache: &'a ResourceCache, + spatial_node_comparer: &'a mut SpatialNodeComparer, + opacity_bindings: &'a FastHashMap, + color_bindings: &'a FastHashMap, + ) -> Self { + let clip_comparer = CompareHelper::new( + &prev.clips, + &curr.clips, + ); + + let transform_comparer = CompareHelper::new( + &prev.transforms, + &curr.transforms, + ); + + let image_comparer = CompareHelper::new( + &prev.images, + &curr.images, + ); + + let opacity_comparer = CompareHelper::new( + &prev.opacity_bindings, + &curr.opacity_bindings, + ); + + let color_comparer = CompareHelper::new( + &prev.color_bindings, + &curr.color_bindings, + ); + + PrimitiveComparer { + clip_comparer, + transform_comparer, + image_comparer, + opacity_comparer, + color_comparer, + resource_cache, + spatial_node_comparer, + opacity_bindings, + color_bindings, + } + } + + fn reset(&mut self) { + self.clip_comparer.reset(); + self.transform_comparer.reset(); + self.image_comparer.reset(); + self.opacity_comparer.reset(); + self.color_comparer.reset(); + } + + fn advance_prev(&mut self, prim: &PrimitiveDescriptor) { + self.clip_comparer.advance_prev(prim.clip_dep_count); + self.transform_comparer.advance_prev(prim.transform_dep_count); + self.image_comparer.advance_prev(prim.image_dep_count); + self.opacity_comparer.advance_prev(prim.opacity_binding_dep_count); + self.color_comparer.advance_prev(prim.color_binding_dep_count); + } + + fn advance_curr(&mut self, prim: &PrimitiveDescriptor) { + self.clip_comparer.advance_curr(prim.clip_dep_count); + self.transform_comparer.advance_curr(prim.transform_dep_count); + self.image_comparer.advance_curr(prim.image_dep_count); + self.opacity_comparer.advance_curr(prim.opacity_binding_dep_count); + self.color_comparer.advance_curr(prim.color_binding_dep_count); + } + + /// Check if two primitive descriptors are the same. + fn compare_prim( + &mut self, + prev: &PrimitiveDescriptor, + curr: &PrimitiveDescriptor, + opt_detail: Option<&mut PrimitiveCompareResultDetail>, + ) -> PrimitiveCompareResult { + let resource_cache = self.resource_cache; + let spatial_node_comparer = &mut self.spatial_node_comparer; + let opacity_bindings = self.opacity_bindings; + let color_bindings = self.color_bindings; + + // Check equality of the PrimitiveDescriptor + if prev != curr { + if let Some(detail) = opt_detail { + *detail = PrimitiveCompareResultDetail::Descriptor{ old: *prev, new: *curr }; + } + return PrimitiveCompareResult::Descriptor; + } + + // Check if any of the clips this prim has are different. + let mut clip_result = CompareHelperResult::Equal; + if !self.clip_comparer.is_same( + prev.clip_dep_count, + curr.clip_dep_count, + |prev, curr| { + prev == curr + }, + if opt_detail.is_some() { Some(&mut clip_result) } else { None } + ) { + if let Some(detail) = opt_detail { *detail = PrimitiveCompareResultDetail::Clip{ detail: clip_result }; } + return PrimitiveCompareResult::Clip; + } + + // Check if any of the transforms this prim has are different. + let mut transform_result = CompareHelperResult::Equal; + if !self.transform_comparer.is_same( + prev.transform_dep_count, + curr.transform_dep_count, + |prev, curr| { + spatial_node_comparer.are_transforms_equivalent(prev, curr) + }, + if opt_detail.is_some() { Some(&mut transform_result) } else { None }, + ) { + if let Some(detail) = opt_detail { + *detail = PrimitiveCompareResultDetail::Transform{ detail: transform_result }; + } + return PrimitiveCompareResult::Transform; + } + + // Check if any of the images this prim has are different. + let mut image_result = CompareHelperResult::Equal; + if !self.image_comparer.is_same( + prev.image_dep_count, + curr.image_dep_count, + |prev, curr| { + prev == curr && + resource_cache.get_image_generation(curr.key) == curr.generation + }, + if opt_detail.is_some() { Some(&mut image_result) } else { None }, + ) { + if let Some(detail) = opt_detail { + *detail = PrimitiveCompareResultDetail::Image{ detail: image_result }; + } + return PrimitiveCompareResult::Image; + } + + // Check if any of the opacity bindings this prim has are different. + let mut bind_result = CompareHelperResult::Equal; + if !self.opacity_comparer.is_same( + prev.opacity_binding_dep_count, + curr.opacity_binding_dep_count, + |prev, curr| { + if prev != curr { + return false; + } + + if let OpacityBinding::Binding(id) = curr { + if opacity_bindings + .get(id) + .map_or(true, |info| info.changed) { + return false; + } + } + + true + }, + if opt_detail.is_some() { Some(&mut bind_result) } else { None }, + ) { + if let Some(detail) = opt_detail { + *detail = PrimitiveCompareResultDetail::OpacityBinding{ detail: bind_result }; + } + return PrimitiveCompareResult::OpacityBinding; + } + + // Check if any of the color bindings this prim has are different. + let mut bind_result = CompareHelperResult::Equal; + if !self.color_comparer.is_same( + prev.color_binding_dep_count, + curr.color_binding_dep_count, + |prev, curr| { + if prev != curr { + return false; + } + + if let ColorBinding::Binding(id) = curr { + if color_bindings + .get(id) + .map_or(true, |info| info.changed) { + return false; + } + } + + true + }, + if opt_detail.is_some() { Some(&mut bind_result) } else { None }, + ) { + if let Some(detail) = opt_detail { + *detail = PrimitiveCompareResultDetail::ColorBinding{ detail: bind_result }; + } + return PrimitiveCompareResult::ColorBinding; + } + + PrimitiveCompareResult::Equal + } +} + +/// Details for a node in a quadtree that tracks dirty rects for a tile. +#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum TileNodeKind { + Leaf { + /// The index buffer of primitives that affected this tile previous frame + #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))] + prev_indices: Vec, + /// The index buffer of primitives that affect this tile on this frame + #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))] + curr_indices: Vec, + /// A bitset of which of the last 64 frames have been dirty for this leaf. + #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))] + dirty_tracker: u64, + /// The number of frames since this node split or merged. + #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))] + frames_since_modified: usize, + }, + Node { + /// The four children of this node + children: Vec, + }, +} + +/// The kind of modification that a tile wants to do +#[derive(Copy, Clone, PartialEq, Debug)] +enum TileModification { + Split, + Merge, +} + +/// A node in the dirty rect tracking quadtree. +#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct TileNode { + /// Leaf or internal node + pub kind: TileNodeKind, + /// Rect of this node in the same space as the tile cache picture + pub rect: PictureBox2D, +} + +impl TileNode { + /// Construct a new leaf node, with the given primitive dependency index buffer + fn new_leaf(curr_indices: Vec) -> Self { + TileNode { + kind: TileNodeKind::Leaf { + prev_indices: Vec::new(), + curr_indices, + dirty_tracker: 0, + frames_since_modified: 0, + }, + rect: PictureBox2D::zero(), + } + } + + /// Draw debug information about this tile node + fn draw_debug_rects( + &self, + pic_to_world_mapper: &SpaceMapper, + is_opaque: bool, + local_valid_rect: PictureRect, + scratch: &mut PrimitiveScratchBuffer, + global_device_pixel_scale: DevicePixelScale, + ) { + match self.kind { + TileNodeKind::Leaf { dirty_tracker, .. } => { + let color = if (dirty_tracker & 1) != 0 { + debug_colors::RED + } else if is_opaque { + debug_colors::GREEN + } else { + debug_colors::YELLOW + }; + + if let Some(local_rect) = local_valid_rect.intersection(&self.rect.to_rect()) { + let world_rect = pic_to_world_mapper + .map(&local_rect) + .unwrap(); + let device_rect = world_rect * global_device_pixel_scale; + + let outer_color = color.scale_alpha(0.3); + let inner_color = outer_color.scale_alpha(0.5); + scratch.push_debug_rect( + device_rect.inflate(-3.0, -3.0), + outer_color, + inner_color + ); + } + } + TileNodeKind::Node { ref children, .. } => { + for child in children.iter() { + child.draw_debug_rects( + pic_to_world_mapper, + is_opaque, + local_valid_rect, + scratch, + global_device_pixel_scale, + ); + } + } + } + } + + /// Calculate the four child rects for a given node + fn get_child_rects( + rect: &PictureBox2D, + result: &mut [PictureBox2D; 4], + ) { + let p0 = rect.min; + let p1 = rect.max; + let pc = p0 + rect.size() * 0.5; + + *result = [ + PictureBox2D::new( + p0, + pc, + ), + PictureBox2D::new( + PicturePoint::new(pc.x, p0.y), + PicturePoint::new(p1.x, pc.y), + ), + PictureBox2D::new( + PicturePoint::new(p0.x, pc.y), + PicturePoint::new(pc.x, p1.y), + ), + PictureBox2D::new( + pc, + p1, + ), + ]; + } + + /// Called during pre_update, to clear the current dependencies + fn clear( + &mut self, + rect: PictureBox2D, + ) { + self.rect = rect; + + match self.kind { + TileNodeKind::Leaf { ref mut prev_indices, ref mut curr_indices, ref mut dirty_tracker, ref mut frames_since_modified } => { + // Swap current dependencies to be the previous frame + mem::swap(prev_indices, curr_indices); + curr_indices.clear(); + // Note that another frame has passed in the dirty bit trackers + *dirty_tracker = *dirty_tracker << 1; + *frames_since_modified += 1; + } + TileNodeKind::Node { ref mut children, .. } => { + let mut child_rects = [PictureBox2D::zero(); 4]; + TileNode::get_child_rects(&rect, &mut child_rects); + assert_eq!(child_rects.len(), children.len()); + + for (child, rect) in children.iter_mut().zip(child_rects.iter()) { + child.clear(*rect); + } + } + } + } + + /// Add a primitive dependency to this node + fn add_prim( + &mut self, + index: PrimitiveDependencyIndex, + prim_rect: &PictureBox2D, + ) { + match self.kind { + TileNodeKind::Leaf { ref mut curr_indices, .. } => { + curr_indices.push(index); + } + TileNodeKind::Node { ref mut children, .. } => { + for child in children.iter_mut() { + if child.rect.intersects(prim_rect) { + child.add_prim(index, prim_rect); + } + } + } + } + } + + /// Apply a merge or split operation to this tile, if desired + fn maybe_merge_or_split( + &mut self, + level: i32, + curr_prims: &[PrimitiveDescriptor], + max_split_levels: i32, + ) { + // Determine if this tile wants to split or merge + let mut tile_mod = None; + + fn get_dirty_frames( + dirty_tracker: u64, + frames_since_modified: usize, + ) -> Option { + // Only consider splitting or merging at least 64 frames since we last changed + if frames_since_modified > 64 { + // Each bit in the tracker is a frame that was recently invalidated + Some(dirty_tracker.count_ones()) + } else { + None + } + } + + match self.kind { + TileNodeKind::Leaf { dirty_tracker, frames_since_modified, .. } => { + // Only consider splitting if the tree isn't too deep. + if level < max_split_levels { + if let Some(dirty_frames) = get_dirty_frames(dirty_tracker, frames_since_modified) { + // If the tile has invalidated > 50% of the recent number of frames, split. + if dirty_frames > 32 { + tile_mod = Some(TileModification::Split); + } + } + } + } + TileNodeKind::Node { ref children, .. } => { + // There's two conditions that cause a node to merge its children: + // (1) If _all_ the child nodes are constantly invalidating, then we are wasting + // CPU time tracking dependencies for each child, so merge them. + // (2) If _none_ of the child nodes are recently invalid, then the page content + // has probably changed, and we no longer need to track fine grained dependencies here. + + let mut static_count = 0; + let mut changing_count = 0; + + for child in children { + // Only consider merging nodes at the edge of the tree. + if let TileNodeKind::Leaf { dirty_tracker, frames_since_modified, .. } = child.kind { + if let Some(dirty_frames) = get_dirty_frames(dirty_tracker, frames_since_modified) { + if dirty_frames == 0 { + // Hasn't been invalidated for some time + static_count += 1; + } else if dirty_frames == 64 { + // Is constantly being invalidated + changing_count += 1; + } + } + } + + // Only merge if all the child tiles are in agreement. Otherwise, we have some + // that are invalidating / static, and it's worthwhile tracking dependencies for + // them individually. + if static_count == 4 || changing_count == 4 { + tile_mod = Some(TileModification::Merge); + } + } + } + } + + match tile_mod { + Some(TileModification::Split) => { + // To split a node, take the current dependency index buffer for this node, and + // split it into child index buffers. + let curr_indices = match self.kind { + TileNodeKind::Node { .. } => { + unreachable!("bug - only leaves can split"); + } + TileNodeKind::Leaf { ref mut curr_indices, .. } => { + curr_indices.take() + } + }; + + let mut child_rects = [PictureBox2D::zero(); 4]; + TileNode::get_child_rects(&self.rect, &mut child_rects); + + let mut child_indices = [ + Vec::new(), + Vec::new(), + Vec::new(), + Vec::new(), + ]; + + // Step through the index buffer, and add primitives to each of the children + // that they intersect. + for index in curr_indices { + let prim = &curr_prims[index.0 as usize]; + for (child_rect, indices) in child_rects.iter().zip(child_indices.iter_mut()) { + if prim.prim_clip_box.intersects(child_rect) { + indices.push(index); + } + } + } + + // Create the child nodes and switch from leaf -> node. + let children = child_indices + .iter_mut() + .map(|i| TileNode::new_leaf(mem::replace(i, Vec::new()))) + .collect(); + + self.kind = TileNodeKind::Node { + children, + }; + } + Some(TileModification::Merge) => { + // Construct a merged index buffer by collecting the dependency index buffers + // from each child, and merging them into a de-duplicated index buffer. + let merged_indices = match self.kind { + TileNodeKind::Node { ref mut children, .. } => { + let mut merged_indices = Vec::new(); + + for child in children.iter() { + let child_indices = match child.kind { + TileNodeKind::Leaf { ref curr_indices, .. } => { + curr_indices + } + TileNodeKind::Node { .. } => { + unreachable!("bug: child is not a leaf"); + } + }; + merged_indices.extend_from_slice(child_indices); + } + + merged_indices.sort(); + merged_indices.dedup(); + + merged_indices + } + TileNodeKind::Leaf { .. } => { + unreachable!("bug - trying to merge a leaf"); + } + }; + + // Switch from a node to a leaf, with the combined index buffer + self.kind = TileNodeKind::Leaf { + prev_indices: Vec::new(), + curr_indices: merged_indices, + dirty_tracker: 0, + frames_since_modified: 0, + }; + } + None => { + // If this node didn't merge / split, then recurse into children + // to see if they want to split / merge. + if let TileNodeKind::Node { ref mut children, .. } = self.kind { + for child in children.iter_mut() { + child.maybe_merge_or_split( + level+1, + curr_prims, + max_split_levels, + ); + } + } + } + } + } + + /// Update the dirty state of this node, building the overall dirty rect + fn update_dirty_rects( + &mut self, + prev_prims: &[PrimitiveDescriptor], + curr_prims: &[PrimitiveDescriptor], + prim_comparer: &mut PrimitiveComparer, + dirty_rect: &mut PictureBox2D, + compare_cache: &mut FastHashMap, + invalidation_reason: &mut Option, + frame_context: &FrameVisibilityContext, + ) { + match self.kind { + TileNodeKind::Node { ref mut children, .. } => { + for child in children.iter_mut() { + child.update_dirty_rects( + prev_prims, + curr_prims, + prim_comparer, + dirty_rect, + compare_cache, + invalidation_reason, + frame_context, + ); + } + } + TileNodeKind::Leaf { ref prev_indices, ref curr_indices, ref mut dirty_tracker, .. } => { + // If the index buffers are of different length, they must be different + if prev_indices.len() == curr_indices.len() { + let mut prev_i0 = 0; + let mut prev_i1 = 0; + prim_comparer.reset(); + + // Walk each index buffer, comparing primitives + for (prev_index, curr_index) in prev_indices.iter().zip(curr_indices.iter()) { + let i0 = prev_index.0 as usize; + let i1 = curr_index.0 as usize; + + // Advance the dependency arrays for each primitive (this handles + // prims that may be skipped by these index buffers). + for i in prev_i0 .. i0 { + prim_comparer.advance_prev(&prev_prims[i]); + } + for i in prev_i1 .. i1 { + prim_comparer.advance_curr(&curr_prims[i]); + } + + // Compare the primitives, caching the result in a hash map + // to save comparisons in other tree nodes. + let key = PrimitiveComparisonKey { + prev_index: *prev_index, + curr_index: *curr_index, + }; + + #[cfg(any(feature = "capture", feature = "replay"))] + let mut compare_detail = PrimitiveCompareResultDetail::Equal; + #[cfg(any(feature = "capture", feature = "replay"))] + let prim_compare_result_detail = + if frame_context.debug_flags.contains(DebugFlags::TILE_CACHE_LOGGING_DBG) { + Some(&mut compare_detail) + } else { + None + }; + + #[cfg(not(any(feature = "capture", feature = "replay")))] + let compare_detail = PrimitiveCompareResultDetail::Equal; + #[cfg(not(any(feature = "capture", feature = "replay")))] + let prim_compare_result_detail = None; + + let prim_compare_result = *compare_cache + .entry(key) + .or_insert_with(|| { + let prev = &prev_prims[i0]; + let curr = &curr_prims[i1]; + prim_comparer.compare_prim(prev, curr, prim_compare_result_detail) + }); + + // If not the same, mark this node as dirty and update the dirty rect + if prim_compare_result != PrimitiveCompareResult::Equal { + if invalidation_reason.is_none() { + *invalidation_reason = Some(InvalidationReason::Content { + prim_compare_result, + prim_compare_result_detail: Some(compare_detail) + }); + } + *dirty_rect = self.rect.union(dirty_rect); + *dirty_tracker = *dirty_tracker | 1; + break; + } + + prev_i0 = i0; + prev_i1 = i1; + } + } else { + if invalidation_reason.is_none() { + // if and only if tile logging is enabled, do the expensive step of + // converting indices back to ItemUids and allocating old and new vectors + // to store them in. + #[cfg(any(feature = "capture", feature = "replay"))] + { + if frame_context.debug_flags.contains(DebugFlags::TILE_CACHE_LOGGING_DBG) { + let old = prev_indices.iter().map( |i| prev_prims[i.0 as usize].prim_uid ).collect(); + let new = curr_indices.iter().map( |i| curr_prims[i.0 as usize].prim_uid ).collect(); + *invalidation_reason = Some(InvalidationReason::PrimCount { + old: Some(old), + new: Some(new) }); + } else { + *invalidation_reason = Some(InvalidationReason::PrimCount { + old: None, + new: None }); + } + } + #[cfg(not(any(feature = "capture", feature = "replay")))] + { + *invalidation_reason = Some(InvalidationReason::PrimCount { + old: None, + new: None }); + } + } + *dirty_rect = self.rect.union(dirty_rect); + *dirty_tracker = *dirty_tracker | 1; + } + } + } + } +} + +impl CompositeState { + // A helper function to destroy all native surfaces for a given list of tiles + pub fn destroy_native_tiles<'a, I: Iterator>>( + &mut self, + tiles_iter: I, + resource_cache: &mut ResourceCache, + ) { + // Any old tiles that remain after the loop above are going to be dropped. For + // simple composite mode, the texture cache handle will expire and be collected + // by the texture cache. For native compositor mode, we need to explicitly + // invoke a callback to the client to destroy that surface. + if let CompositorKind::Native { .. } = self.compositor_kind { + for tile in tiles_iter { + // Only destroy native surfaces that have been allocated. It's + // possible for display port tiles to be created that never + // come on screen, and thus never get a native surface allocated. + if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface { + if let Some(id) = id.take() { + resource_cache.destroy_compositor_tile(id); + } + } + } + } + } +} diff --git a/third_party/webrender/webrender/src/platform/macos/font.rs b/third_party/webrender/webrender/src/platform/macos/font.rs new file mode 100644 index 00000000000..437522d5e9e --- /dev/null +++ b/third_party/webrender/webrender/src/platform/macos/font.rs @@ -0,0 +1,854 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{ColorU, FontKey, FontRenderMode, FontSize, GlyphDimensions}; +use api::{FontInstanceFlags, FontVariation, NativeFontHandle}; +use core_foundation::array::{CFArray, CFArrayRef}; +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, kCGImageAlphaPremultipliedFirst}; +use core_graphics::base::{kCGBitmapByteOrder32Little}; +use core_graphics::color_space::CGColorSpace; +use core_graphics::context::CGContext; +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}; +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::default::Size2D; +use crate::gamma_lut::{ColorLut, GammaLut}; +use crate::glyph_rasterizer::{FontInstance, FontTransform, GlyphKey}; +use crate::glyph_rasterizer::{GlyphFormat, GlyphRasterError, GlyphRasterResult, RasterizedGlyph}; +use crate::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, + ct_fonts: FastHashMap<(FontKey, FontSize, Vec), CTFont>, + #[allow(dead_code)] + graphics_context: GraphicsContext, + #[allow(dead_code)] + gamma_lut: GammaLut, +} + +// core text is safe to use on multiple threads and non-shareable resources are +// all hidden inside their font context. +unsafe impl Send for FontContext {} + +struct GlyphMetrics { + rasterized_left: i32, + #[allow(dead_code)] + rasterized_descent: i32, + rasterized_ascent: i32, + rasterized_width: i32, + rasterized_height: i32, + advance: f32, +} + +// According to the Skia source code, there's no public API to +// determine if subpixel AA is supported. So jrmuizel ported +// this function from Skia which is used to check if a glyph +// can be rendered with subpixel AA. +fn supports_subpixel_aa() -> bool { + let mut cg_context = CGContext::create_bitmap_context( + None, + 1, + 1, + 8, + 4, + &CGColorSpace::create_device_rgb(), + kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little, + ); + let ct_font = core_text::font::new_from_name("Helvetica", 16.).unwrap(); + cg_context.set_should_smooth_fonts(true); + cg_context.set_should_antialias(true); + cg_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0); + let point = CGPoint { x: -1., y: 0. }; + let glyph = '|' as CGGlyph; + ct_font.draw_glyphs(&[glyph], &[point], cg_context.clone()); + let data = cg_context.data(); + data[0] != data[1] || data[1] != data[2] +} + +fn should_use_white_on_black(color: ColorU) -> bool { + let (r, g, b) = (color.r as u32, color.g as u32, color.b as u32); + // These thresholds were determined on 10.12 by observing what CG does. + r >= 85 && g >= 85 && b >= 85 && r + g + b >= 2 * 255 +} + +fn get_glyph_metrics( + ct_font: &CTFont, + transform: Option<&CGAffineTransform>, + glyph: CGGlyph, + x_offset: f64, + y_offset: f64, + extra_width: f64, +) -> GlyphMetrics { + let mut bounds = ct_font.get_bounding_rects_for_glyphs(kCTFontDefaultOrientation, &[glyph]); + + if bounds.origin.x.is_nan() || bounds.origin.y.is_nan() || bounds.size.width.is_nan() || + bounds.size.height.is_nan() + { + // If an unexpected glyph index is requested, core text will return NaN values + // which causes us to do bad thing as the value is cast into an integer and + // overflow when expanding the bounds a few lines below. + // Instead we are better off returning zero-sized metrics because this special + // case is handled by the callers of this method. + return GlyphMetrics { + rasterized_left: 0, + rasterized_width: 0, + rasterized_height: 0, + rasterized_ascent: 0, + rasterized_descent: 0, + advance: 0.0, + }; + } + + let mut advance = CGSize { width: 0.0, height: 0.0 }; + unsafe { + ct_font.get_advances_for_glyphs(kCTFontDefaultOrientation, &glyph, &mut advance, 1); + } + + if bounds.size.width > 0.0 { + bounds.size.width += extra_width; + } + if advance.width > 0.0 { + advance.width += extra_width; + } + + if let Some(transform) = transform { + bounds = bounds.apply_transform(transform); + } + + // First round out to pixel boundaries + // CG Origin is bottom left + let mut left = bounds.origin.x.floor() as i32; + let mut bottom = bounds.origin.y.floor() as i32; + let mut right = (bounds.origin.x + bounds.size.width + x_offset).ceil() as i32; + let mut top = (bounds.origin.y + bounds.size.height + y_offset).ceil() as i32; + + // Expand the bounds by 1 pixel, to give CG room for anti-aliasing. + // Note that this outset is to allow room for LCD smoothed glyphs. However, the correct outset + // is not currently known, as CG dilates the outlines by some percentage. + // This is taken from Skia. + left -= 1; + bottom -= 1; + right += 1; + top += 1; + + let width = right - left; + let height = top - bottom; + + GlyphMetrics { + rasterized_left: left, + rasterized_width: width, + rasterized_height: height, + rasterized_ascent: top, + rasterized_descent: -bottom, + advance: advance.width as f32, + } +} + +#[link(name = "ApplicationServices", kind = "framework")] +extern { + static kCTFontVariationAxisIdentifierKey: CFStringRef; + static kCTFontVariationAxisNameKey: CFStringRef; + static kCTFontVariationAxisMinimumValueKey: CFStringRef; + static kCTFontVariationAxisMaximumValueKey: CFStringRef; + static kCTFontVariationAxisDefaultValueKey: CFStringRef; + + fn CTFontCopyVariationAxes(font: CTFontRef) -> CFArrayRef; +} + +fn new_ct_font_with_variations(cg_font: &CGFont, size: f64, variations: &[FontVariation]) -> CTFont { + unsafe { + let ct_font = core_text::font::new_from_CGFont(cg_font, size); + if variations.is_empty() { + return ct_font; + } + let axes_ref = CTFontCopyVariationAxes(ct_font.as_concrete_TypeRef()); + if axes_ref.is_null() { + return ct_font; + } + let axes: CFArray = TCFType::wrap_under_create_rule(axes_ref); + let mut vals: Vec<(CFString, CFNumber)> = Vec::with_capacity(variations.len() as usize); + for axis in axes.iter() { + if !axis.instance_of::() { + return ct_font; + } + let tag_val = match axis.find(kCTFontVariationAxisIdentifierKey as *const _) { + Some(tag_ptr) => { + let tag: CFNumber = TCFType::wrap_under_get_rule(*tag_ptr as CFNumberRef); + if !tag.instance_of::() { + return ct_font; + } + match tag.to_i64() { + Some(val) => val, + None => return ct_font, + } + } + None => return ct_font, + }; + let mut val = match variations.iter().find(|variation| (variation.tag as i64) == tag_val) { + Some(variation) => variation.value as f64, + None => continue, + }; + + let name: CFString = match axis.find(kCTFontVariationAxisNameKey as *const _) { + Some(name_ptr) => TCFType::wrap_under_get_rule(*name_ptr as CFStringRef), + None => return ct_font, + }; + if !name.instance_of::() { + return ct_font; + } + + let min_val = match axis.find(kCTFontVariationAxisMinimumValueKey as *const _) { + Some(min_ptr) => { + let min: CFNumber = TCFType::wrap_under_get_rule(*min_ptr as CFNumberRef); + if !min.instance_of::() { + return ct_font; + } + match min.to_f64() { + Some(val) => val, + None => return ct_font, + } + } + None => return ct_font, + }; + let max_val = match axis.find(kCTFontVariationAxisMaximumValueKey as *const _) { + Some(max_ptr) => { + let max: CFNumber = TCFType::wrap_under_get_rule(*max_ptr as CFNumberRef); + if !max.instance_of::() { + return ct_font; + } + match max.to_f64() { + Some(val) => val, + None => return ct_font, + } + } + None => return ct_font, + }; + let def_val = match axis.find(kCTFontVariationAxisDefaultValueKey as *const _) { + Some(def_ptr) => { + let def: CFNumber = TCFType::wrap_under_get_rule(*def_ptr as CFNumberRef); + if !def.instance_of::() { + return ct_font; + } + match def.to_f64() { + Some(val) => val, + None => return ct_font, + } + } + None => return ct_font, + }; + + val = val.max(min_val).min(max_val); + if val != def_val { + vals.push((name, CFNumber::from(val))); + } + } + if vals.is_empty() { + return ct_font; + } + let vals_dict = CFDictionary::from_CFType_pairs(&vals); + let cg_var_font = cg_font.create_copy_from_variations(&vals_dict).unwrap(); + core_text::font::new_from_CGFont_with_variations(&cg_var_font, size, &vals_dict) + } +} + +fn is_bitmap_font(ct_font: &CTFont) -> bool { + let traits = ct_font.symbolic_traits(); + (traits & kCTFontColorGlyphsTrait) != 0 +} + +impl FontContext { + pub fn new() -> Result { + debug!("Test for subpixel AA support: {}", supports_subpixel_aa()); + + // Force CG to use sRGB color space to gamma correct. + let contrast = 0.0; + let gamma = 0.0; + + Ok(FontContext { + cg_fonts: FastHashMap::default(), + ct_fonts: FastHashMap::default(), + graphics_context: GraphicsContext::new(), + gamma_lut: GammaLut::new(contrast, gamma, gamma), + }) + } + + pub fn has_font(&self, font_key: &FontKey) -> bool { + self.cg_fonts.contains_key(font_key) + } + + pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: Arc>, index: u32) { + if self.cg_fonts.contains_key(font_key) { + return; + } + + assert_eq!(index, 0); + let data_provider = CGDataProvider::from_buffer(bytes); + let cg_font = match CGFont::from_data_provider(data_provider) { + Err(_) => return, + Ok(cg_font) => cg_font, + }; + self.cg_fonts.insert(*font_key, cg_font); + } + + pub fn add_native_font(&mut self, font_key: &FontKey, native_font_handle: NativeFontHandle) { + if self.cg_fonts.contains_key(font_key) { + return; + } + + self.cg_fonts + .insert(*font_key, native_font_handle.0); + } + + pub fn delete_font(&mut self, font_key: &FontKey) { + if let Some(_) = self.cg_fonts.remove(font_key) { + self.ct_fonts.retain(|k, _| k.0 != *font_key); + } + } + + pub fn delete_font_instance(&mut self, instance: &FontInstance) { + // Remove the CoreText font corresponding to this instance. + let size = FontSize::from_f64_px(instance.get_transformed_size()); + self.ct_fonts.remove(&(instance.font_key, size, instance.variations.clone())); + } + + fn get_ct_font( + &mut self, + font_key: FontKey, + size: f64, + variations: &[FontVariation], + ) -> Option { + match self.ct_fonts.entry((font_key, FontSize::from_f64_px(size), variations.to_vec())) { + Entry::Occupied(entry) => Some((*entry.get()).clone()), + Entry::Vacant(entry) => { + let cg_font = self.cg_fonts.get(&font_key)?; + let ct_font = new_ct_font_with_variations(cg_font, size, variations); + entry.insert(ct_font.clone()); + Some(ct_font) + } + } + } + + pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option { + let character = ch as u16; + let mut glyph = 0; + + self.get_ct_font(font_key, 16.0, &[]) + .and_then(|ref ct_font| { + unsafe { + let result = ct_font.get_glyphs_for_characters(&character, &mut glyph, 1); + + if result { + Some(glyph as u32) + } else { + None + } + } + }) + } + + pub fn get_glyph_dimensions( + &mut self, + font: &FontInstance, + key: &GlyphKey, + ) -> Option { + let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0)); + let size = font.size.to_f64_px() * y_scale; + self.get_ct_font(font.font_key, size, &font.variations) + .and_then(|ref ct_font| { + let glyph = key.index() as CGGlyph; + let bitmap = is_bitmap_font(ct_font); + let (mut shape, (x_offset, y_offset)) = if bitmap { + (FontTransform::identity(), (0.0, 0.0)) + } else { + (font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key)) + }; + if font.flags.contains(FontInstanceFlags::FLIP_X) { + shape = shape.flip_x(); + } + if font.flags.contains(FontInstanceFlags::FLIP_Y) { + shape = shape.flip_y(); + } + if font.flags.contains(FontInstanceFlags::TRANSPOSE) { + shape = shape.swap_xy(); + } + let (mut tx, mut ty) = (0.0, 0.0); + if font.synthetic_italics.is_enabled() { + let (shape_, (tx_, ty_)) = font.synthesize_italics(shape, size); + shape = shape_; + tx = tx_; + ty = ty_; + } + let transform = if !shape.is_identity() || (tx, ty) != (0.0, 0.0) { + Some(CGAffineTransform { + a: shape.scale_x as f64, + b: -shape.skew_y as f64, + c: -shape.skew_x as f64, + d: shape.scale_y as f64, + tx: tx, + ty: -ty, + }) + } else { + None + }; + let (strike_scale, pixel_step) = if bitmap { + (y_scale, 1.0) + } else { + (x_scale, y_scale / x_scale) + }; + let extra_strikes = font.get_extra_strikes(strike_scale); + let metrics = get_glyph_metrics( + ct_font, + transform.as_ref(), + glyph, + x_offset, + y_offset, + extra_strikes as f64 * pixel_step, + ); + if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 { + None + } else { + Some(GlyphDimensions { + left: metrics.rasterized_left, + top: metrics.rasterized_ascent, + width: metrics.rasterized_width, + height: metrics.rasterized_height, + advance: metrics.advance, + }) + } + }) + } + + // Assumes the pixels here are linear values from CG + fn gamma_correct_pixels( + &self, + pixels: &mut Vec, + render_mode: FontRenderMode, + color: ColorU, + ) { + // Then convert back to gamma corrected values. + match render_mode { + FontRenderMode::Alpha => { + self.gamma_lut.preblend_grayscale(pixels, color); + } + FontRenderMode::Subpixel => { + self.gamma_lut.preblend(pixels, color); + } + _ => {} // Again, give mono untouched since only the alpha matters. + } + } + + #[allow(dead_code)] + fn print_glyph_data(&mut self, data: &[u8], width: usize, height: usize) { + // Rust doesn't have step_by support on stable :( + println!("Width is: {:?} height: {:?}", width, height); + for i in 0 .. height { + let current_height = i * width * 4; + + for pixel in data[current_height .. current_height + (width * 4)].chunks(4) { + let b = pixel[0]; + let g = pixel[1]; + let r = pixel[2]; + let a = pixel[3]; + print!("({}, {}, {}, {}) ", r, g, b, a); + } + println!(); + } + } + + pub fn prepare_font(font: &mut FontInstance) { + match font.render_mode { + FontRenderMode::Mono => { + // In mono mode the color of the font is irrelevant. + font.color = ColorU::new(255, 255, 255, 255); + // Subpixel positioning is disabled in mono mode. + font.disable_subpixel_position(); + } + FontRenderMode::Alpha => { + font.color = if font.flags.contains(FontInstanceFlags::FONT_SMOOTHING) { + // Only the G channel is used to index grayscale tables, + // so use R and B to preserve light/dark determination. + let ColorU { g, a, .. } = font.color.luminance_color().quantized_ceil(); + let rb = if should_use_white_on_black(font.color) { 255 } else { 0 }; + ColorU::new(rb, g, rb, a) + } else { + ColorU::new(255, 255, 255, 255) + }; + } + FontRenderMode::Subpixel => { + // Quantization may change the light/dark determination, so quantize in the + // direction necessary to respect the threshold. + font.color = if should_use_white_on_black(font.color) { + font.color.quantized_ceil() + } else { + font.color.quantized_floor() + }; + } + } + } + + pub fn rasterize_glyph(&mut self, font: &FontInstance, key: &GlyphKey) -> GlyphRasterResult { + let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0)); + let size = font.size.to_f64_px() * y_scale; + let ct_font = self.get_ct_font(font.font_key, size, &font.variations).ok_or(GlyphRasterError::LoadFailed)?; + let glyph_type = if is_bitmap_font(&ct_font) { + GlyphType::Bitmap + } else { + 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(); + } + if font.flags.contains(FontInstanceFlags::FLIP_Y) { + shape = shape.flip_y(); + } + if font.flags.contains(FontInstanceFlags::TRANSPOSE) { + shape = shape.swap_xy(); + } + let (mut tx, mut ty) = (0.0, 0.0); + if font.synthetic_italics.is_enabled() { + let (shape_, (tx_, ty_)) = font.synthesize_italics(shape, size); + shape = shape_; + tx = tx_; + ty = ty_; + } + let transform = if !shape.is_identity() || (tx, ty) != (0.0, 0.0) { + Some(CGAffineTransform { + a: shape.scale_x as f64, + b: -shape.skew_y as f64, + c: -shape.skew_x as f64, + d: shape.scale_y as f64, + tx: tx, + ty: -ty, + }) + } else { + None + }; + + let glyph = key.index() as CGGlyph; + 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); + let metrics = get_glyph_metrics( + &ct_font, + transform.as_ref(), + glyph, + x_offset, + y_offset, + extra_strikes as f64 * pixel_step, + ); + if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 { + return Err(GlyphRasterError::LoadFailed); + } + + let raster_size = Size2D::new( + metrics.rasterized_width as u32, + metrics.rasterized_height as u32 + ); + + // If the font render mode is Alpha, we support two different ways to + // compute the grayscale mask, depending on the value of the platform + // options' font_smoothing flag: + // - Alpha + smoothing: + // We will recover a grayscale mask from a subpixel rasterization, in + // such a way that the result looks as close to subpixel text + // blending as we can make it. This involves gamma correction, + // luminance computations and preblending based on the text color, + // just like with the Subpixel render mode. + // - Alpha without smoothing: + // We will ask CoreGraphics to rasterize the text with font_smoothing + // off. This will cause it to use grayscale anti-aliasing with + // comparatively thin text. This method of text rendering is not + // gamma-aware. + // + // For subpixel rasterization, starting with macOS 10.11, CoreGraphics + // uses different glyph dilation based on the text color. Bright text + // uses less font dilation (looks thinner) than dark text. + // As a consequence, when we ask CG to rasterize with subpixel AA, we + // will render white-on-black text as opposed to black-on-white text if + // the text color brightness exceeds a certain threshold. This applies + // to both the Subpixel and the "Alpha + smoothing" modes, but not to + // 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) = 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), + } + } + }; + + { + let cg_context = self.graphics_context.get_context(&raster_size, glyph_type); + + // 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); + + // 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); + + cg_context.set_should_smooth_fonts(smooth); + cg_context.set_should_antialias(antialias); + + // 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, + }, + }; + + // 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); + + // 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); + + // 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 + tx, + y: metrics.rasterized_descent as f64 - y_offset - ty, + }; + + 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); + } + + ct_font.draw_glyphs(&[glyph], &[draw_origin], cg_context.clone()); + + // We'd like to render all the strikes in a single ct_font.draw_glyphs call, + // passing an array of glyph IDs and an array of origins, but unfortunately + // with some fonts, Core Text may inappropriately pixel-snap the rasterization, + // such that the strikes overprint instead of being offset. Rendering the + // strikes with individual draw_glyphs calls avoids this. + // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1633397 for details.) + for i in 1 ..= extra_strikes { + let origin = CGPoint { + x: draw_origin.x + i as f64 * pixel_step, + y: draw_origin.y, + }; + ct_font.draw_glyphs(&[glyph], &[origin], cg_context.clone()); + } + } + + let mut rasterized_pixels = self.graphics_context + .get_rasterized_pixels(&raster_size, glyph_type); + + 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 + // CG functions now. + + if smooth { + // Convert to linear space for subpixel AA. + // We explicitly do not do this for grayscale AA ("Alpha without + // smoothing" or Mono) because those rendering modes are not + // gamma-aware in CoreGraphics. + self.gamma_lut.coregraphics_convert_to_linear( + &mut rasterized_pixels, + ); + } + + for pixel in rasterized_pixels.chunks_mut(4) { + if invert { + pixel[0] = 255 - pixel[0]; + pixel[1] = 255 - pixel[1]; + pixel[2] = 255 - pixel[2]; + } + + // Set alpha to the value of the green channel. For grayscale + // text, all three channels have the same value anyway. + // For subpixel text, the mask's alpha only makes a difference + // when computing the destination alpha on destination pixels + // that are not completely opaque. Picking an alpha value + // that's somehow based on the mask at least ensures that text + // blending doesn't modify the destination alpha on pixels where + // the mask is entirely zero. + pixel[3] = pixel[1]; + } + + if smooth { + // Convert back from linear space into device space, and perform + // some "preblending" based on the text color. + // In Alpha + smoothing mode, this will also convert subpixel AA + // into grayscale AA. + self.gamma_correct_pixels( + &mut rasterized_pixels, + font.render_mode, + font.color, + ); + } + } + + Ok(RasterizedGlyph { + left: metrics.rasterized_left as f32, + top: metrics.rasterized_ascent as f32, + width: metrics.rasterized_width, + height: metrics.rasterized_height, + scale: match glyph_type { + GlyphType::Bitmap => y_scale.recip() as f32, + GlyphType::Vector => 1.0, + }, + format: match glyph_type { + GlyphType::Bitmap => GlyphFormat::ColorBitmap, + GlyphType::Vector => font.get_glyph_format(), + }, + bytes: rasterized_pixels, + }) + } +} + +// Avoids taking locks by recycling Core Graphics contexts. +#[allow(dead_code)] +struct GraphicsContext { + vector_context: CGContext, + vector_context_size: Size2D, + bitmap_context: CGContext, + bitmap_context_size: Size2D, +} + +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, 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, glyph_type: GlyphType) + -> Vec { + 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, 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, +} diff --git a/third_party/webrender/webrender/src/platform/unix/font.rs b/third_party/webrender/webrender/src/platform/unix/font.rs new file mode 100644 index 00000000000..52c0d114101 --- /dev/null +++ b/third_party/webrender/webrender/src/platform/unix/font.rs @@ -0,0 +1,1038 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{ColorU, GlyphDimensions, FontKey, FontRenderMode}; +use api::{FontInstancePlatformOptions, FontLCDFilter, FontHinting}; +use api::{FontInstanceFlags, FontVariation, NativeFontHandle}; +use freetype::freetype::{FT_BBox, FT_Outline_Translate, FT_Pixel_Mode, FT_Render_Mode}; +use freetype::freetype::{FT_Done_Face, FT_Error, FT_Get_Char_Index, FT_Int32}; +use freetype::freetype::{FT_Done_FreeType, FT_Library_SetLcdFilter, FT_Pos}; +use freetype::freetype::{FT_F26Dot6, FT_Face, FT_Glyph_Format, FT_Long, FT_UInt}; +use freetype::freetype::{FT_GlyphSlot, FT_LcdFilter, FT_New_Face, FT_New_Memory_Face}; +use freetype::freetype::{FT_Init_FreeType, FT_Load_Glyph, FT_Render_Glyph}; +use freetype::freetype::{FT_Library, FT_Outline_Get_CBox, FT_Set_Char_Size, FT_Select_Size}; +use freetype::freetype::{FT_Fixed, FT_Matrix, FT_Set_Transform, FT_String, FT_ULong, FT_Vector}; +use freetype::freetype::{FT_Err_Unimplemented_Feature, FT_MulFix, FT_Outline_Embolden}; +use freetype::freetype::{FT_LOAD_COLOR, FT_LOAD_DEFAULT, FT_LOAD_FORCE_AUTOHINT}; +use freetype::freetype::{FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH, FT_LOAD_NO_AUTOHINT}; +use freetype::freetype::{FT_LOAD_NO_BITMAP, FT_LOAD_NO_HINTING}; +use freetype::freetype::{FT_FACE_FLAG_SCALABLE, FT_FACE_FLAG_FIXED_SIZES}; +use freetype::freetype::{FT_FACE_FLAG_MULTIPLE_MASTERS}; +use freetype::succeeded; +use crate::glyph_rasterizer::{FontInstance, GlyphFormat, GlyphKey}; +use crate::glyph_rasterizer::{GlyphRasterError, GlyphRasterResult, RasterizedGlyph}; +use crate::internal_types::{FastHashMap, ResourceCacheError}; +#[cfg(any(not(target_os = "android"), feature = "no_static_freetype"))] +use libc::{dlsym, RTLD_DEFAULT}; +use libc::free; +use std::{cmp, mem, ptr, slice}; +use std::cmp::max; +use std::collections::hash_map::Entry; +use std::ffi::CString; +use std::sync::Arc; + +// These constants are not present in the freetype +// bindings due to bindgen not handling the way +// the macros are defined. +//const FT_LOAD_TARGET_NORMAL: FT_UInt = 0 << 16; +const FT_LOAD_TARGET_LIGHT: FT_UInt = 1 << 16; +const FT_LOAD_TARGET_MONO: FT_UInt = 2 << 16; +const FT_LOAD_TARGET_LCD: FT_UInt = 3 << 16; +const FT_LOAD_TARGET_LCD_V: FT_UInt = 4 << 16; + +#[repr(C)] +struct FT_Var_Axis { + pub name: *mut FT_String, + pub minimum: FT_Fixed, + pub def: FT_Fixed, + pub maximum: FT_Fixed, + pub tag: FT_ULong, + pub strid: FT_UInt, +} + +#[repr(C)] +struct FT_Var_Named_Style { + pub coords: *mut FT_Fixed, + pub strid: FT_UInt, + pub psid: FT_UInt, +} + +#[repr(C)] +struct FT_MM_Var { + pub num_axis: FT_UInt, + pub num_designs: FT_UInt, + pub num_namedstyles: FT_UInt, + pub axis: *mut FT_Var_Axis, + pub namedstyle: *mut FT_Var_Named_Style, +} + +#[inline] +pub fn unimplemented(error: FT_Error) -> bool { + error == FT_Err_Unimplemented_Feature as FT_Error +} + +// Use dlsym to check for symbols. If not available. just return an unimplemented error. +#[cfg(any(not(target_os = "android"), feature = "no_static_freetype"))] +macro_rules! ft_dyn_fn { + ($func_name:ident($($arg_name:ident:$arg_type:ty),*) -> FT_Error) => { + #[allow(non_snake_case)] + unsafe fn $func_name($($arg_name:$arg_type),*) -> FT_Error { + extern "C" fn unimpl_func($(_:$arg_type),*) -> FT_Error { + FT_Err_Unimplemented_Feature as FT_Error + } + lazy_static! { + static ref FUNC: unsafe extern "C" fn($($arg_type),*) -> FT_Error = { + unsafe { + let cname = CString::new(stringify!($func_name)).unwrap(); + let ptr = dlsym(RTLD_DEFAULT, cname.as_ptr()); + if !ptr.is_null() { mem::transmute(ptr) } else { unimpl_func } + } + }; + } + (*FUNC)($($arg_name),*) + } + } +} + +// On Android, just statically link in the symbols... +#[cfg(all(target_os = "android", not(feature = "no_static_freetype")))] +macro_rules! ft_dyn_fn { + ($($proto:tt)+) => { extern "C" { fn $($proto)+; } } +} + +ft_dyn_fn!(FT_Get_MM_Var(face: FT_Face, desc: *mut *mut FT_MM_Var) -> FT_Error); +ft_dyn_fn!(FT_Done_MM_Var(library: FT_Library, desc: *mut FT_MM_Var) -> FT_Error); +ft_dyn_fn!(FT_Set_Var_Design_Coordinates(face: FT_Face, num_vals: FT_UInt, vals: *mut FT_Fixed) -> FT_Error); + +extern "C" { + fn FT_GlyphSlot_Embolden(slot: FT_GlyphSlot); +} + +// Custom version of FT_GlyphSlot_Embolden to be less aggressive with outline +// fonts than the default implementation in FreeType. +#[no_mangle] +pub extern "C" fn mozilla_glyphslot_embolden_less(slot: FT_GlyphSlot) { + if slot.is_null() { + return; + } + + let slot_ = unsafe { &mut *slot }; + let format = slot_.format; + if format != FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE { + // For non-outline glyphs, just fall back to FreeType's function. + unsafe { FT_GlyphSlot_Embolden(slot) }; + return; + } + + let face_ = unsafe { *slot_.face }; + + // FT_GlyphSlot_Embolden uses a divisor of 24 here; we'll be only half as + // bold. + let size_ = unsafe { *face_.size }; + let strength = + unsafe { FT_MulFix(face_.units_per_EM as FT_Long, + size_.metrics.y_scale) / 48 }; + unsafe { FT_Outline_Embolden(&mut slot_.outline, strength) }; + + // Adjust metrics to suit the fattened glyph. + if slot_.advance.x != 0 { + slot_.advance.x += strength; + } + if slot_.advance.y != 0 { + slot_.advance.y += strength; + } + slot_.metrics.width += strength; + slot_.metrics.height += strength; + slot_.metrics.horiAdvance += strength; + slot_.metrics.vertAdvance += strength; + slot_.metrics.horiBearingY += strength; +} + +enum FontFile { + Pathname(CString), + Data(Arc>), +} + +struct FontFace { + // Raw byte data has to live until the font is deleted, according to + // https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_New_Memory_Face + file: FontFile, + index: u32, + face: FT_Face, + mm_var: *mut FT_MM_Var, +} + +impl Drop for FontFace { + fn drop(&mut self) { + unsafe { + if !self.mm_var.is_null() && + unimplemented(FT_Done_MM_Var((*(*self.face).glyph).library, self.mm_var)) { + free(self.mm_var as _); + } + + FT_Done_Face(self.face); + } + } +} + +struct VariationFace(FT_Face); + +impl Drop for VariationFace { + fn drop(&mut self) { + unsafe { FT_Done_Face(self.0) }; + } +} + +fn new_ft_face(font_key: &FontKey, lib: FT_Library, file: &FontFile, index: u32) -> Option { + unsafe { + let mut face: FT_Face = ptr::null_mut(); + let result = match file { + FontFile::Pathname(ref cstr) => FT_New_Face( + lib, + cstr.as_ptr(), + index as FT_Long, + &mut face, + ), + FontFile::Data(ref bytes) => FT_New_Memory_Face( + lib, + bytes.as_ptr(), + bytes.len() as FT_Long, + index as FT_Long, + &mut face, + ), + }; + if succeeded(result) && !face.is_null() { + Some(face) + } else { + warn!("WARN: webrender failed to load font"); + debug!("font={:?}, result={:?}", font_key, result); + None + } + } +} + +pub struct FontContext { + lib: FT_Library, + faces: FastHashMap, + variations: FastHashMap<(FontKey, Vec), VariationFace>, + lcd_extra_pixels: i64, +} + +// FreeType resources are safe to move between threads as long as they +// are not concurrently accessed. In our case, everything is hidden inside +// a given FontContext so it is safe to move the latter between threads. +unsafe impl Send for FontContext {} + +fn get_skew_bounds(bottom: i32, top: i32, skew_factor: f32, _vertical: bool) -> (f32, f32) { + let skew_min = ((bottom as f32 + 0.5) * skew_factor).floor(); + let skew_max = ((top as f32 - 0.5) * skew_factor).ceil(); + (skew_min, skew_max) +} + +fn skew_bitmap( + bitmap: &[u8], + width: usize, + height: usize, + left: i32, + top: i32, + skew_factor: f32, + vertical: bool, // TODO: vertical skew not yet implemented! +) -> (Vec, usize, i32) { + let stride = width * 4; + // Calculate the skewed horizontal offsets of the bottom and top of the glyph. + let (skew_min, skew_max) = get_skew_bounds(top - height as i32, top, skew_factor, vertical); + // Allocate enough extra width for the min/max skew offsets. + let skew_width = width + (skew_max - skew_min) as usize; + let mut skew_buffer = vec![0u8; skew_width * height * 4]; + for y in 0 .. height { + // Calculate a skew offset at the vertical center of the current row. + let offset = (top as f32 - y as f32 - 0.5) * skew_factor - skew_min; + // Get a blend factor in 0..256 constant across all pixels in the row. + let blend = (offset.fract() * 256.0) as u32; + let src_row = y * stride; + let dest_row = (y * skew_width + offset.floor() as usize) * 4; + let mut prev_px = [0u32; 4]; + for (src, dest) in + bitmap[src_row .. src_row + stride].chunks(4).zip( + skew_buffer[dest_row .. dest_row + stride].chunks_mut(4) + ) { + let px = [src[0] as u32, src[1] as u32, src[2] as u32, src[3] as u32]; + // Blend current pixel with previous pixel based on blend factor. + let next_px = [px[0] * blend, px[1] * blend, px[2] * blend, px[3] * blend]; + dest[0] = ((((px[0] << 8) - next_px[0]) + prev_px[0] + 128) >> 8) as u8; + dest[1] = ((((px[1] << 8) - next_px[1]) + prev_px[1] + 128) >> 8) as u8; + dest[2] = ((((px[2] << 8) - next_px[2]) + prev_px[2] + 128) >> 8) as u8; + dest[3] = ((((px[3] << 8) - next_px[3]) + prev_px[3] + 128) >> 8) as u8; + // Save the remainder for blending onto the next pixel. + prev_px = next_px; + } + // If the skew misaligns the final pixel, write out the remainder. + if blend > 0 { + let dest = &mut skew_buffer[dest_row + stride .. dest_row + stride + 4]; + dest[0] = ((prev_px[0] + 128) >> 8) as u8; + dest[1] = ((prev_px[1] + 128) >> 8) as u8; + dest[2] = ((prev_px[2] + 128) >> 8) as u8; + dest[3] = ((prev_px[3] + 128) >> 8) as u8; + } + } + (skew_buffer, skew_width, left + skew_min as i32) +} + +fn transpose_bitmap(bitmap: &[u8], width: usize, height: usize) -> Vec { + let mut transposed = vec![0u8; width * height * 4]; + for (y, row) in bitmap.chunks(width * 4).enumerate() { + let mut offset = y * 4; + for src in row.chunks(4) { + transposed[offset .. offset + 4].copy_from_slice(src); + offset += height * 4; + } + } + transposed +} + +fn flip_bitmap_x(bitmap: &mut [u8], width: usize, height: usize) { + assert!(bitmap.len() == width * height * 4); + let pixels = unsafe { slice::from_raw_parts_mut(bitmap.as_mut_ptr() as *mut u32, width * height) }; + for row in pixels.chunks_mut(width) { + row.reverse(); + } +} + +fn flip_bitmap_y(bitmap: &mut [u8], width: usize, height: usize) { + assert!(bitmap.len() == width * height * 4); + let pixels = unsafe { slice::from_raw_parts_mut(bitmap.as_mut_ptr() as *mut u32, width * height) }; + for y in 0 .. height / 2 { + let low_row = y * width; + let high_row = (height - 1 - y) * width; + for x in 0 .. width { + pixels.swap(low_row + x, high_row + x); + } + } +} + +impl FontContext { + pub fn new() -> Result { + let mut lib: FT_Library = ptr::null_mut(); + + // Using an LCD filter may add one full pixel to each side if support is built in. + // As of FreeType 2.8.1, an LCD filter is always used regardless of settings + // if support for the patent-encumbered LCD filter algorithms is not built in. + // Thus, the only reasonable way to guess padding is to unconditonally add it if + // subpixel AA is used. + let lcd_extra_pixels = 1; + + let result = unsafe { + FT_Init_FreeType(&mut lib) + }; + + if succeeded(result) { + Ok(FontContext { + lib, + faces: FastHashMap::default(), + variations: FastHashMap::default(), + lcd_extra_pixels, + }) + } else { + // TODO(gw): Provide detailed error values. + Err(ResourceCacheError::new( + format!("Failed to initialize FreeType - {}", result) + )) + } + } + + pub fn has_font(&self, font_key: &FontKey) -> bool { + self.faces.contains_key(font_key) + } + + pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: Arc>, index: u32) { + if !self.faces.contains_key(font_key) { + let file = FontFile::Data(bytes); + if let Some(face) = new_ft_face(font_key, self.lib, &file, index) { + self.faces.insert(*font_key, FontFace { file, index, face, mm_var: ptr::null_mut() }); + } + } + } + + pub fn add_native_font(&mut self, font_key: &FontKey, native_font_handle: NativeFontHandle) { + if !self.faces.contains_key(font_key) { + let cstr = CString::new(native_font_handle.path.as_os_str().to_str().unwrap()).unwrap(); + let file = FontFile::Pathname(cstr); + let index = native_font_handle.index; + if let Some(face) = new_ft_face(font_key, self.lib, &file, index) { + self.faces.insert(*font_key, FontFace { file, index, face, mm_var: ptr::null_mut() }); + } + } + } + + pub fn delete_font(&mut self, font_key: &FontKey) { + if self.faces.remove(font_key).is_some() { + self.variations.retain(|k, _| k.0 != *font_key); + } + } + + pub fn delete_font_instance(&mut self, instance: &FontInstance) { + // Ensure we don't keep around excessive amounts of stale variations. + if !instance.variations.is_empty() { + self.variations.remove(&(instance.font_key, instance.variations.clone())); + } + } + + fn get_ft_face(&mut self, font: &FontInstance) -> Option { + if font.variations.is_empty() { + return Some(self.faces.get(&font.font_key)?.face); + } + match self.variations.entry((font.font_key, font.variations.clone())) { + Entry::Occupied(entry) => Some(entry.get().0), + Entry::Vacant(entry) => unsafe { + let normal_face = self.faces.get_mut(&font.font_key)?; + if ((*normal_face.face).face_flags & (FT_FACE_FLAG_MULTIPLE_MASTERS as FT_Long)) == 0 { + return Some(normal_face.face); + } + // Clone a new FT face and attempt to set the variation values on it. + // Leave unspecified values at the defaults. + let var_face = new_ft_face(&font.font_key, self.lib, &normal_face.file, normal_face.index)?; + if !normal_face.mm_var.is_null() || + succeeded(FT_Get_MM_Var(normal_face.face, &mut normal_face.mm_var)) { + let mm_var = normal_face.mm_var; + let num_axis = (*mm_var).num_axis; + let mut coords: Vec = Vec::with_capacity(num_axis as usize); + for i in 0 .. num_axis { + let axis = (*mm_var).axis.offset(i as isize); + let mut value = (*axis).def; + for var in &font.variations { + if var.tag as FT_ULong == (*axis).tag { + value = (var.value * 65536.0 + 0.5) as FT_Fixed; + value = cmp::min(value, (*axis).maximum); + value = cmp::max(value, (*axis).minimum); + break; + } + } + coords.push(value); + } + FT_Set_Var_Design_Coordinates(var_face, num_axis, coords.as_mut_ptr()); + } + entry.insert(VariationFace(var_face)); + Some(var_face) + } + } + } + + fn load_glyph(&mut self, font: &FontInstance, glyph: &GlyphKey) -> Option<(FT_GlyphSlot, f32)> { + let face = self.get_ft_face(font)?; + + let mut load_flags = FT_LOAD_DEFAULT; + let FontInstancePlatformOptions { mut hinting, .. } = font.platform_options.unwrap_or_default(); + // Disable hinting if there is a non-axis-aligned transform. + if font.synthetic_italics.is_enabled() || + ((font.transform.scale_x != 0.0 || font.transform.scale_y != 0.0) && + (font.transform.skew_x != 0.0 || font.transform.skew_y != 0.0)) { + hinting = FontHinting::None; + } + match (hinting, font.render_mode) { + (FontHinting::None, _) => load_flags |= FT_LOAD_NO_HINTING, + (FontHinting::Mono, _) => load_flags = FT_LOAD_TARGET_MONO, + (FontHinting::Light, _) => load_flags = FT_LOAD_TARGET_LIGHT, + (FontHinting::LCD, FontRenderMode::Subpixel) => { + load_flags = if font.flags.contains(FontInstanceFlags::LCD_VERTICAL) { + FT_LOAD_TARGET_LCD_V + } else { + FT_LOAD_TARGET_LCD + }; + if font.flags.contains(FontInstanceFlags::FORCE_AUTOHINT) { + load_flags |= FT_LOAD_FORCE_AUTOHINT; + } + } + _ => { + if font.flags.contains(FontInstanceFlags::FORCE_AUTOHINT) { + load_flags |= FT_LOAD_FORCE_AUTOHINT; + } + } + } + + if font.flags.contains(FontInstanceFlags::NO_AUTOHINT) { + load_flags |= FT_LOAD_NO_AUTOHINT; + } + if !font.flags.contains(FontInstanceFlags::EMBEDDED_BITMAPS) { + load_flags |= FT_LOAD_NO_BITMAP; + } + + load_flags |= FT_LOAD_COLOR; + load_flags |= FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH; + + let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0)); + let req_size = font.size.to_f64_px(); + let face_flags = unsafe { (*face).face_flags }; + let mut result = if (face_flags & (FT_FACE_FLAG_FIXED_SIZES as FT_Long)) != 0 && + (face_flags & (FT_FACE_FLAG_SCALABLE as FT_Long)) == 0 && + (load_flags & FT_LOAD_NO_BITMAP) == 0 { + unsafe { FT_Set_Transform(face, ptr::null_mut(), ptr::null_mut()) }; + self.choose_bitmap_size(face, req_size * y_scale) + } else { + let mut shape = font.transform.invert_scale(x_scale, y_scale); + if font.flags.contains(FontInstanceFlags::FLIP_X) { + shape = shape.flip_x(); + } + if font.flags.contains(FontInstanceFlags::FLIP_Y) { + shape = shape.flip_y(); + } + if font.flags.contains(FontInstanceFlags::TRANSPOSE) { + shape = shape.swap_xy(); + } + let (mut tx, mut ty) = (0.0, 0.0); + if font.synthetic_italics.is_enabled() { + let (shape_, (tx_, ty_)) = font.synthesize_italics(shape, y_scale * req_size); + shape = shape_; + tx = tx_; + ty = ty_; + }; + let mut ft_shape = FT_Matrix { + xx: (shape.scale_x * 65536.0) as FT_Fixed, + xy: (shape.skew_x * -65536.0) as FT_Fixed, + yx: (shape.skew_y * -65536.0) as FT_Fixed, + yy: (shape.scale_y * 65536.0) as FT_Fixed, + }; + // The delta vector for FT_Set_Transform is in units of 1/64 pixel. + let mut ft_delta = FT_Vector { + x: (tx * 64.0) as FT_F26Dot6, + y: (ty * -64.0) as FT_F26Dot6, + }; + unsafe { + FT_Set_Transform(face, &mut ft_shape, &mut ft_delta); + FT_Set_Char_Size( + face, + (req_size * x_scale * 64.0 + 0.5) as FT_F26Dot6, + (req_size * y_scale * 64.0 + 0.5) as FT_F26Dot6, + 0, + 0, + ) + } + }; + + if !succeeded(result) { + error!("Unable to set glyph size and transform: {}", result); + //let raw_error = unsafe { FT_Error_String(result) }; + //if !raw_error.is_ptr() { + // error!("\tcode {:?}", CStr::from_ptr(raw_error)); + //} + debug!( + "\t[{}] for size {:?} and scale {:?} from font {:?}", + glyph.index(), + req_size, + (x_scale, y_scale), + font.font_key, + ); + return None; + } + + result = unsafe { FT_Load_Glyph(face, glyph.index() as FT_UInt, load_flags as FT_Int32) }; + if !succeeded(result) { + error!("Unable to load glyph: {}", result); + //let raw_error = unsafe { FT_Error_String(result) }; + //if !raw_error.is_ptr() { + // error!("\tcode {:?}", CStr::from_ptr(raw_error)); + //} + debug!( + "\t[{}] with flags {:?} from font {:?}", + glyph.index(), + load_flags, + font.font_key, + ); + return None; + } + + let slot = unsafe { (*face).glyph }; + assert!(slot != ptr::null_mut()); + + if font.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) { + mozilla_glyphslot_embolden_less(slot); + } + + let format = unsafe { (*slot).format }; + match format { + FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP => { + let bitmap_size = unsafe { (*(*(*slot).face).size).metrics.y_ppem }; + Some((slot, req_size as f32 / bitmap_size as f32)) + } + FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE => Some((slot, 1.0)), + _ => { + error!("Unsupported format"); + debug!("format={:?}", format); + None + } + } + } + + fn pad_bounding_box(&self, font: &FontInstance, cbox: &mut FT_BBox) { + // Apply extra pixel of padding for subpixel AA, due to the filter. + if font.render_mode == FontRenderMode::Subpixel { + let padding = (self.lcd_extra_pixels * 64) as FT_Pos; + if font.flags.contains(FontInstanceFlags::LCD_VERTICAL) { + cbox.yMin -= padding; + cbox.yMax += padding; + } else { + cbox.xMin -= padding; + cbox.xMax += padding; + } + } + } + + // Get the bounding box for a glyph, accounting for sub-pixel positioning. + fn get_bounding_box( + &self, + slot: FT_GlyphSlot, + font: &FontInstance, + glyph: &GlyphKey, + scale: f32, + ) -> FT_BBox { + // Get the estimated bounding box from FT (control points). + let mut cbox = FT_BBox { xMin: 0, yMin: 0, xMax: 0, yMax: 0 }; + + unsafe { + FT_Outline_Get_CBox(&(*slot).outline, &mut cbox); + } + + // For spaces and other non-printable characters, early out. + if unsafe { (*slot).outline.n_contours } == 0 { + return cbox; + } + + self.pad_bounding_box(font, &mut cbox); + + // Offset the bounding box by subpixel positioning. + // Convert to 26.6 fixed point format for FT. + let (dx, dy) = font.get_subpx_offset(glyph); + let (dx, dy) = ( + (dx / scale as f64 * 64.0 + 0.5) as FT_Pos, + -(dy / scale as f64 * 64.0 + 0.5) as FT_Pos, + ); + cbox.xMin += dx; + cbox.xMax += dx; + cbox.yMin += dy; + cbox.yMax += dy; + + // Outset the box to device pixel boundaries + cbox.xMin &= !63; + cbox.yMin &= !63; + cbox.xMax = (cbox.xMax + 63) & !63; + cbox.yMax = (cbox.yMax + 63) & !63; + + cbox + } + + fn get_glyph_dimensions_impl( + &self, + slot: FT_GlyphSlot, + font: &FontInstance, + glyph: &GlyphKey, + scale: f32, + use_transform: bool, + ) -> Option { + let format = unsafe { (*slot).format }; + let (mut left, mut top, mut width, mut height) = match format { + FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP => { + unsafe { ( + (*slot).bitmap_left as i32, + (*slot).bitmap_top as i32, + (*slot).bitmap.width as i32, + (*slot).bitmap.rows as i32, + ) } + } + FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE => { + let cbox = self.get_bounding_box(slot, font, glyph, scale); + ( + (cbox.xMin >> 6) as i32, + (cbox.yMax >> 6) as i32, + ((cbox.xMax - cbox.xMin) >> 6) as i32, + ((cbox.yMax - cbox.yMin) >> 6) as i32, + ) + } + _ => return None, + }; + let mut advance = unsafe { (*slot).metrics.horiAdvance as f32 / 64.0 }; + if use_transform { + if scale != 1.0 { + let x0 = left as f32 * scale; + let x1 = width as f32 * scale + x0; + let y1 = top as f32 * scale; + let y0 = y1 - height as f32 * scale; + left = x0.round() as i32; + top = y1.round() as i32; + width = (x1.ceil() - x0.floor()) as i32; + height = (y1.ceil() - y0.floor()) as i32; + advance *= scale; + } + // An outline glyph's cbox would have already been transformed inside FT_Load_Glyph, + // so only handle bitmap glyphs which are not handled by FT_Load_Glyph. + if format == FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP { + if font.synthetic_italics.is_enabled() { + let (skew_min, skew_max) = get_skew_bounds( + top - height as i32, + top, + font.synthetic_italics.to_skew(), + font.flags.contains(FontInstanceFlags::VERTICAL), + ); + left += skew_min as i32; + width += (skew_max - skew_min) as i32; + } + if font.flags.contains(FontInstanceFlags::TRANSPOSE) { + mem::swap(&mut width, &mut height); + mem::swap(&mut left, &mut top); + left -= width as i32; + top += height as i32; + } + if font.flags.contains(FontInstanceFlags::FLIP_X) { + left = -(left + width as i32); + } + if font.flags.contains(FontInstanceFlags::FLIP_Y) { + top = -(top - height as i32); + } + } + } + Some(GlyphDimensions { + left, + top, + width, + height, + advance, + }) + } + + pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option { + let face = self.faces.get(&font_key)?.face; + unsafe { + let idx = FT_Get_Char_Index(face, ch as _); + if idx != 0 { + Some(idx) + } else { + None + } + } + } + + pub fn get_glyph_dimensions( + &mut self, + font: &FontInstance, + key: &GlyphKey, + ) -> Option { + let slot = self.load_glyph(font, key); + slot.and_then(|(slot, scale)| self.get_glyph_dimensions_impl(slot, &font, key, scale, true)) + } + + fn choose_bitmap_size(&self, face: FT_Face, requested_size: f64) -> FT_Error { + let mut best_dist = unsafe { *(*face).available_sizes.offset(0) }.y_ppem as f64 / 64.0 - requested_size; + let mut best_size = 0; + let num_fixed_sizes = unsafe { (*face).num_fixed_sizes }; + for i in 1 .. num_fixed_sizes { + // Distance is positive if strike is larger than desired size, + // or negative if smaller. If previously a found smaller strike, + // then prefer a larger strike. Otherwise, minimize distance. + let dist = unsafe { *(*face).available_sizes.offset(i as isize) }.y_ppem as f64 / 64.0 - requested_size; + if (best_dist < 0.0 && dist >= best_dist) || dist.abs() <= best_dist { + best_dist = dist; + best_size = i; + } + } + unsafe { FT_Select_Size(face, best_size) } + } + + pub fn prepare_font(font: &mut FontInstance) { + match font.render_mode { + FontRenderMode::Mono => { + // In mono mode the color of the font is irrelevant. + font.color = ColorU::new(0xFF, 0xFF, 0xFF, 0xFF); + // Subpixel positioning is disabled in mono mode. + font.disable_subpixel_position(); + } + FontRenderMode::Alpha | FontRenderMode::Subpixel => { + // We don't do any preblending with FreeType currently, so the color is not used. + font.color = ColorU::new(0xFF, 0xFF, 0xFF, 0xFF); + } + } + } + + fn rasterize_glyph_outline( + &mut self, + slot: FT_GlyphSlot, + font: &FontInstance, + key: &GlyphKey, + scale: f32, + ) -> bool { + // Get the subpixel offsets in FT 26.6 format. + let (dx, dy) = font.get_subpx_offset(key); + let (dx, dy) = ( + (dx / scale as f64 * 64.0 + 0.5) as FT_Pos, + -(dy / scale as f64 * 64.0 + 0.5) as FT_Pos, + ); + + // Move the outline curves to be at the origin, taking + // into account the subpixel positioning. + unsafe { + let outline = &(*slot).outline; + let mut cbox = FT_BBox { xMin: 0, yMin: 0, xMax: 0, yMax: 0 }; + FT_Outline_Get_CBox(outline, &mut cbox); + self.pad_bounding_box(font, &mut cbox); + FT_Outline_Translate( + outline, + dx - ((cbox.xMin + dx) & !63), + dy - ((cbox.yMin + dy) & !63), + ); + } + + if font.render_mode == FontRenderMode::Subpixel { + let FontInstancePlatformOptions { lcd_filter, .. } = font.platform_options.unwrap_or_default(); + let filter = match lcd_filter { + FontLCDFilter::None => FT_LcdFilter::FT_LCD_FILTER_NONE, + FontLCDFilter::Default => FT_LcdFilter::FT_LCD_FILTER_DEFAULT, + FontLCDFilter::Light => FT_LcdFilter::FT_LCD_FILTER_LIGHT, + FontLCDFilter::Legacy => FT_LcdFilter::FT_LCD_FILTER_LEGACY, + }; + unsafe { FT_Library_SetLcdFilter(self.lib, filter) }; + } + let render_mode = match font.render_mode { + FontRenderMode::Mono => FT_Render_Mode::FT_RENDER_MODE_MONO, + FontRenderMode::Alpha => FT_Render_Mode::FT_RENDER_MODE_NORMAL, + FontRenderMode::Subpixel => if font.flags.contains(FontInstanceFlags::LCD_VERTICAL) { + FT_Render_Mode::FT_RENDER_MODE_LCD_V + } else { + FT_Render_Mode::FT_RENDER_MODE_LCD + }, + }; + let result = unsafe { FT_Render_Glyph(slot, render_mode) }; + if !succeeded(result) { + error!("Unable to rasterize"); + debug!( + "{:?} with {:?}, {:?}", + key, + render_mode, + result + ); + false + } else { + true + } + } + + pub fn rasterize_glyph(&mut self, font: &FontInstance, key: &GlyphKey) -> GlyphRasterResult { + let (slot, scale) = self.load_glyph(font, key).ok_or(GlyphRasterError::LoadFailed)?; + + // Get dimensions of the glyph, to see if we need to rasterize it. + // Don't apply scaling to the dimensions, as the glyph cache needs to know the actual + // footprint of the glyph. + let dimensions = self.get_glyph_dimensions_impl(slot, font, key, scale, false) + .ok_or(GlyphRasterError::LoadFailed)?; + let GlyphDimensions { mut left, mut top, width, height, .. } = dimensions; + + // For spaces and other non-printable characters, early out. + if width == 0 || height == 0 { + return Err(GlyphRasterError::LoadFailed); + } + + let format = unsafe { (*slot).format }; + match format { + FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP => {} + FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE => { + if !self.rasterize_glyph_outline(slot, font, key, scale) { + return Err(GlyphRasterError::LoadFailed); + } + } + _ => { + error!("Unsupported format"); + debug!("format={:?}", format); + return Err(GlyphRasterError::LoadFailed); + } + }; + + debug!( + "Rasterizing {:?} as {:?} with dimensions {:?}", + key, + font.render_mode, + dimensions + ); + + let bitmap = unsafe { &(*slot).bitmap }; + let pixel_mode = unsafe { mem::transmute(bitmap.pixel_mode as u32) }; + let (mut actual_width, mut actual_height) = match pixel_mode { + FT_Pixel_Mode::FT_PIXEL_MODE_LCD => { + assert!(bitmap.width % 3 == 0); + ((bitmap.width / 3) as usize, bitmap.rows as usize) + } + FT_Pixel_Mode::FT_PIXEL_MODE_LCD_V => { + assert!(bitmap.rows % 3 == 0); + (bitmap.width as usize, (bitmap.rows / 3) as usize) + } + FT_Pixel_Mode::FT_PIXEL_MODE_MONO | + FT_Pixel_Mode::FT_PIXEL_MODE_GRAY | + FT_Pixel_Mode::FT_PIXEL_MODE_BGRA => { + (bitmap.width as usize, bitmap.rows as usize) + } + _ => panic!("Unsupported mode"), + }; + + // If we need padding, we will need to expand the buffer size. + let (buffer_width, buffer_height, padding) = if font.use_texture_padding() { + (actual_width + 2, actual_height + 2, 1) + } else { + (actual_width, actual_height, 0) + }; + + let mut final_buffer = vec![0u8; buffer_width * buffer_height * 4]; + + // Extract the final glyph from FT format into BGRA8 format, which is + // what WR expects. + let subpixel_bgr = font.flags.contains(FontInstanceFlags::SUBPIXEL_BGR); + let mut src_row = bitmap.buffer; + let mut dest = 4 * padding * (padding + buffer_width); + let actual_end = final_buffer.len() - 4 * padding * (buffer_width + 1); + while dest < actual_end { + let mut src = src_row; + let row_end = dest + actual_width * 4; + match pixel_mode { + FT_Pixel_Mode::FT_PIXEL_MODE_MONO => { + while dest < row_end { + // Cast the byte to signed so that we can left shift each bit into + // the top bit, then right shift to fill out the bits with 0s or 1s. + let mut byte: i8 = unsafe { *src as i8 }; + src = unsafe { src.offset(1) }; + let byte_end = cmp::min(row_end, dest + 8 * 4); + while dest < byte_end { + let alpha = (byte >> 7) as u8; + final_buffer[dest + 0] = alpha; + final_buffer[dest + 1] = alpha; + final_buffer[dest + 2] = alpha; + final_buffer[dest + 3] = alpha; + dest += 4; + byte <<= 1; + } + } + } + FT_Pixel_Mode::FT_PIXEL_MODE_GRAY => { + while dest < row_end { + let alpha = unsafe { *src }; + final_buffer[dest + 0] = alpha; + final_buffer[dest + 1] = alpha; + final_buffer[dest + 2] = alpha; + final_buffer[dest + 3] = alpha; + src = unsafe { src.offset(1) }; + dest += 4; + } + } + FT_Pixel_Mode::FT_PIXEL_MODE_LCD => { + while dest < row_end { + let (mut r, g, mut b) = unsafe { (*src, *src.offset(1), *src.offset(2)) }; + if subpixel_bgr { + mem::swap(&mut r, &mut b); + } + final_buffer[dest + 0] = b; + final_buffer[dest + 1] = g; + final_buffer[dest + 2] = r; + final_buffer[dest + 3] = max(max(b, g), r); + src = unsafe { src.offset(3) }; + dest += 4; + } + } + FT_Pixel_Mode::FT_PIXEL_MODE_LCD_V => { + while dest < row_end { + let (mut r, g, mut b) = + unsafe { (*src, *src.offset(bitmap.pitch as isize), *src.offset((2 * bitmap.pitch) as isize)) }; + if subpixel_bgr { + mem::swap(&mut r, &mut b); + } + final_buffer[dest + 0] = b; + final_buffer[dest + 1] = g; + final_buffer[dest + 2] = r; + final_buffer[dest + 3] = max(max(b, g), r); + src = unsafe { src.offset(1) }; + dest += 4; + } + src_row = unsafe { src_row.offset((2 * bitmap.pitch) as isize) }; + } + FT_Pixel_Mode::FT_PIXEL_MODE_BGRA => { + // The source is premultiplied BGRA data. + let dest_slice = &mut final_buffer[dest .. row_end]; + let src_slice = unsafe { slice::from_raw_parts(src, dest_slice.len()) }; + dest_slice.copy_from_slice(src_slice); + } + _ => panic!("Unsupported mode"), + } + src_row = unsafe { src_row.offset(bitmap.pitch as isize) }; + dest = row_end + 8 * padding; + } + + if font.use_texture_padding() { + left -= padding as i32; + top += padding as i32; + actual_width = buffer_width; + actual_height = buffer_height; + } + + match format { + FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP => { + if font.synthetic_italics.is_enabled() { + let (skew_buffer, skew_width, skew_left) = skew_bitmap( + &final_buffer, + actual_width, + actual_height, + left, + top, + font.synthetic_italics.to_skew(), + font.flags.contains(FontInstanceFlags::VERTICAL), + ); + final_buffer = skew_buffer; + actual_width = skew_width; + left = skew_left; + } + if font.flags.contains(FontInstanceFlags::TRANSPOSE) { + final_buffer = transpose_bitmap(&final_buffer, actual_width, actual_height); + mem::swap(&mut actual_width, &mut actual_height); + mem::swap(&mut left, &mut top); + left -= actual_width as i32; + top += actual_height as i32; + } + if font.flags.contains(FontInstanceFlags::FLIP_X) { + flip_bitmap_x(&mut final_buffer, actual_width, actual_height); + left = -(left + actual_width as i32); + } + if font.flags.contains(FontInstanceFlags::FLIP_Y) { + flip_bitmap_y(&mut final_buffer, actual_width, actual_height); + top = -(top - actual_height as i32); + } + } + FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE => { + unsafe { + left += (*slot).bitmap_left; + top += (*slot).bitmap_top - height as i32; + } + } + _ => {} + } + + let glyph_format = match (pixel_mode, format) { + (FT_Pixel_Mode::FT_PIXEL_MODE_LCD, _) | + (FT_Pixel_Mode::FT_PIXEL_MODE_LCD_V, _) => font.get_subpixel_glyph_format(), + (FT_Pixel_Mode::FT_PIXEL_MODE_BGRA, _) => GlyphFormat::ColorBitmap, + (_, FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP) => GlyphFormat::Bitmap, + _ => font.get_alpha_glyph_format(), + }; + + Ok(RasterizedGlyph { + left: left as f32, + top: top as f32, + width: actual_width as i32, + height: actual_height as i32, + scale, + format: glyph_format, + bytes: final_buffer, + }) + } +} + +impl Drop for FontContext { + fn drop(&mut self) { + self.variations.clear(); + self.faces.clear(); + unsafe { + FT_Done_FreeType(self.lib); + } + } +} diff --git a/third_party/webrender/webrender/src/platform/windows/font.rs b/third_party/webrender/webrender/src/platform/windows/font.rs new file mode 100644 index 00000000000..49ec039017c --- /dev/null +++ b/third_party/webrender/webrender/src/platform/windows/font.rs @@ -0,0 +1,603 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{FontInstanceFlags, FontKey, FontRenderMode, FontVariation}; +use api::{ColorU, GlyphDimensions, NativeFontHandle}; +use dwrote; +use crate::gamma_lut::ColorLut; +use crate::glyph_rasterizer::{FontInstance, FontTransform, GlyphKey}; +use crate::internal_types::{FastHashMap, FastHashSet, ResourceCacheError}; +use crate::glyph_rasterizer::{GlyphFormat, GlyphRasterError, GlyphRasterResult, RasterizedGlyph}; +use crate::gamma_lut::GammaLut; +use std::borrow::Borrow; +use std::collections::hash_map::Entry; +use std::hash::{Hash, Hasher}; +use std::path::Path; +use std::sync::{Arc, Mutex}; +use api::FontInstancePlatformOptions; +use std::mem; + +lazy_static! { + static ref DEFAULT_FONT_DESCRIPTOR: dwrote::FontDescriptor = dwrote::FontDescriptor { + family_name: "Arial".to_owned(), + weight: dwrote::FontWeight::Regular, + stretch: dwrote::FontStretch::Normal, + style: dwrote::FontStyle::Normal, + }; +} + +type CachedFontKey = Arc; + +// A cached dwrote font file that is shared among all faces. +// Each face holds a CachedFontKey to keep track of how many users of the font there are. +struct CachedFont { + key: CachedFontKey, + file: dwrote::FontFile, +} + +// FontFile contains a ComPtr, but DWrite font files are threadsafe. +unsafe impl Send for CachedFont {} + +impl PartialEq for CachedFont { + fn eq(&self, other: &CachedFont) -> bool { + self.key == other.key + } +} +impl Eq for CachedFont {} + +impl Hash for CachedFont { + fn hash(&self, state: &mut H) { + self.key.hash(state); + } +} + +impl Borrow for CachedFont { + fn borrow(&self) -> &Path { + &*self.key + } +} + +lazy_static! { + // This is effectively a weak map of dwrote FontFiles. CachedFonts are entered into the + // cache when there are any FontFaces using them. CachedFonts are removed from the cache + // when there are no more FontFaces using them at all. + static ref FONT_CACHE: Mutex> = Mutex::new(FastHashSet::default()); +} + +struct FontFace { + cached: Option, + file: dwrote::FontFile, + index: u32, + face: dwrote::FontFace, +} + +pub struct FontContext { + fonts: FastHashMap, + variations: FastHashMap<(FontKey, dwrote::DWRITE_FONT_SIMULATIONS, Vec), dwrote::FontFace>, + gamma_luts: FastHashMap<(u16, u8), GammaLut>, +} + +// DirectWrite is safe to use on multiple threads and non-shareable resources are +// all hidden inside their font context. +unsafe impl Send for FontContext {} + +fn dwrite_texture_type(render_mode: FontRenderMode) -> dwrote::DWRITE_TEXTURE_TYPE { + match render_mode { + FontRenderMode::Mono => dwrote::DWRITE_TEXTURE_ALIASED_1x1, + FontRenderMode::Alpha | + FontRenderMode::Subpixel => dwrote::DWRITE_TEXTURE_CLEARTYPE_3x1, + } +} + +fn dwrite_measure_mode( + font: &FontInstance, + bitmaps: bool, +) -> dwrote::DWRITE_MEASURING_MODE { + if bitmaps || font.flags.contains(FontInstanceFlags::FORCE_GDI) { + dwrote::DWRITE_MEASURING_MODE_GDI_CLASSIC + } else { + match font.render_mode { + FontRenderMode::Mono => dwrote::DWRITE_MEASURING_MODE_GDI_CLASSIC, + FontRenderMode::Alpha | FontRenderMode::Subpixel => dwrote::DWRITE_MEASURING_MODE_NATURAL, + } + } +} + +fn dwrite_render_mode( + font_face: &dwrote::FontFace, + font: &FontInstance, + em_size: f32, + measure_mode: dwrote::DWRITE_MEASURING_MODE, + bitmaps: bool, +) -> dwrote::DWRITE_RENDERING_MODE { + let dwrite_render_mode = match font.render_mode { + FontRenderMode::Mono => dwrote::DWRITE_RENDERING_MODE_ALIASED, + FontRenderMode::Alpha | FontRenderMode::Subpixel => { + if bitmaps || font.flags.contains(FontInstanceFlags::FORCE_GDI) { + dwrote::DWRITE_RENDERING_MODE_GDI_CLASSIC + } else if font.flags.contains(FontInstanceFlags::FORCE_SYMMETRIC) { + dwrote::DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC + } else if font.flags.contains(FontInstanceFlags::NO_SYMMETRIC) { + dwrote::DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL + } else { + font_face.get_recommended_rendering_mode_default_params(em_size, 1.0, measure_mode) + } + } + }; + + if dwrite_render_mode == dwrote::DWRITE_RENDERING_MODE_OUTLINE { + // Outline mode is not supported + return dwrote::DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC; + } + + dwrite_render_mode +} + +fn is_bitmap_font(font: &FontInstance) -> bool { + // If bitmaps are requested, then treat as a bitmap font to disable transforms. + // If mono AA is requested, let that take priority over using bitmaps. + font.render_mode != FontRenderMode::Mono && + font.flags.contains(FontInstanceFlags::EMBEDDED_BITMAPS) +} + +impl FontContext { + pub fn new() -> Result { + Ok(FontContext { + fonts: FastHashMap::default(), + variations: FastHashMap::default(), + gamma_luts: FastHashMap::default(), + }) + } + + pub fn has_font(&self, font_key: &FontKey) -> bool { + self.fonts.contains_key(font_key) + } + + fn add_font_descriptor(&mut self, font_key: &FontKey, desc: &dwrote::FontDescriptor) { + let system_fc = dwrote::FontCollection::get_system(false); + if let Some(font) = system_fc.get_font_from_descriptor(desc) { + let face = font.create_font_face(); + let file = face.get_files().pop().unwrap(); + let index = face.get_index(); + self.fonts.insert(*font_key, FontFace { cached: None, file, index, face }); + } + } + + pub fn add_raw_font(&mut self, font_key: &FontKey, data: Arc>, index: u32) { + if self.fonts.contains_key(font_key) { + return; + } + + if let Some(file) = dwrote::FontFile::new_from_data(data) { + if let Ok(face) = file.create_face(index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) { + self.fonts.insert(*font_key, FontFace { cached: None, file, index, face }); + return; + } + } + // XXX add_raw_font needs to have a way to return an error + debug!("DWrite WR failed to load font from data, using Arial instead"); + self.add_font_descriptor(font_key, &DEFAULT_FONT_DESCRIPTOR); + } + + pub fn add_native_font(&mut self, font_key: &FontKey, font_handle: NativeFontHandle) { + if self.fonts.contains_key(font_key) { + return; + } + + let index = font_handle.index; + let mut cache = FONT_CACHE.lock().unwrap(); + // Check to see if the font is already in the cache. If so, reuse it. + if let Some(font) = cache.get(font_handle.path.as_path()) { + if let Ok(face) = font.file.create_face(index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) { + self.fonts.insert( + *font_key, + FontFace { cached: Some(font.key.clone()), file: font.file.clone(), index, face }, + ); + return; + } + } + if let Some(file) = dwrote::FontFile::new_from_path(&font_handle.path) { + // The font is not in the cache yet, so try to create the font and insert it in the cache. + if let Ok(face) = file.create_face(index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) { + let key: CachedFontKey = font_handle.path.into(); + self.fonts.insert( + *font_key, + FontFace { cached: Some(key.clone()), file: file.clone(), index, face }, + ); + cache.insert(CachedFont { key, file }); + return; + } + } + + // XXX add_native_font needs to have a way to return an error + debug!("DWrite WR failed to load font from path, using Arial instead"); + self.add_font_descriptor(font_key, &DEFAULT_FONT_DESCRIPTOR); + } + + pub fn delete_font(&mut self, font_key: &FontKey) { + if let Some(face) = self.fonts.remove(font_key) { + self.variations.retain(|k, _| k.0 != *font_key); + // Check if this was a cached font. + if let Some(key) = face.cached { + let mut cache = FONT_CACHE.lock().unwrap(); + // If there are only two references left, that means only this face and + // the cache are using the font. So remove it from the cache. + if Arc::strong_count(&key) == 2 { + cache.remove(&*key); + } + } + } + } + + pub fn delete_font_instance(&mut self, instance: &FontInstance) { + // Ensure we don't keep around excessive amounts of stale variations. + if !instance.variations.is_empty() { + let sims = if instance.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) { + dwrote::DWRITE_FONT_SIMULATIONS_BOLD + } else { + dwrote::DWRITE_FONT_SIMULATIONS_NONE + }; + self.variations.remove(&(instance.font_key, sims, instance.variations.clone())); + } + } + + // Assumes RGB format from dwrite, which is 3 bytes per pixel as dwrite + // doesn't output an alpha value via GlyphRunAnalysis::CreateAlphaTexture + #[allow(dead_code)] + fn print_glyph_data(&self, data: &[u8], width: usize, height: usize) { + // Rust doesn't have step_by support on stable :( + for i in 0 .. height { + let current_height = i * width * 3; + + for pixel in data[current_height .. current_height + (width * 3)].chunks(3) { + let r = pixel[0]; + let g = pixel[1]; + let b = pixel[2]; + print!("({}, {}, {}) ", r, g, b,); + } + println!(); + } + } + + fn get_font_face( + &mut self, + font: &FontInstance, + ) -> &dwrote::FontFace { + if !font.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) && + font.variations.is_empty() { + return &self.fonts.get(&font.font_key).unwrap().face; + } + let sims = if font.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) { + dwrote::DWRITE_FONT_SIMULATIONS_BOLD + } else { + dwrote::DWRITE_FONT_SIMULATIONS_NONE + }; + match self.variations.entry((font.font_key, sims, font.variations.clone())) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + let normal_face = self.fonts.get(&font.font_key).unwrap(); + if !font.variations.is_empty() { + if let Some(var_face) = normal_face.face.create_font_face_with_variations( + sims, + &font.variations.iter().map(|var| { + dwrote::DWRITE_FONT_AXIS_VALUE { + // OpenType tags are big-endian, but DWrite wants little-endian. + axisTag: var.tag.swap_bytes(), + value: var.value, + } + }).collect::>(), + ) { + return entry.insert(var_face); + } + } + let var_face = normal_face.file + .create_face(normal_face.index, sims) + .unwrap_or_else(|_| normal_face.face.clone()); + entry.insert(var_face) + } + } + } + + fn create_glyph_analysis( + &mut self, + font: &FontInstance, + key: &GlyphKey, + size: f32, + transform: Option, + bitmaps: bool, + ) -> Result<(dwrote::GlyphRunAnalysis, dwrote::DWRITE_TEXTURE_TYPE, dwrote::RECT), dwrote::HRESULT> { + let face = self.get_font_face(font); + let glyph = key.index() as u16; + let advance = 0.0f32; + let offset = dwrote::GlyphOffset { + advanceOffset: 0.0, + ascenderOffset: 0.0, + }; + + let glyph_run = dwrote::DWRITE_GLYPH_RUN { + fontFace: unsafe { face.as_ptr() }, + fontEmSize: size, // size in DIPs (1/96", same as CSS pixels) + glyphCount: 1, + glyphIndices: &glyph, + glyphAdvances: &advance, + glyphOffsets: &offset, + isSideways: 0, + bidiLevel: 0, + }; + + let dwrite_measure_mode = dwrite_measure_mode(font, bitmaps); + let dwrite_render_mode = dwrite_render_mode( + face, + font, + size, + dwrite_measure_mode, + bitmaps, + ); + + let analysis = dwrote::GlyphRunAnalysis::create( + &glyph_run, + 1.0, + transform, + dwrite_render_mode, + dwrite_measure_mode, + 0.0, + 0.0, + )?; + let texture_type = dwrite_texture_type(font.render_mode); + let bounds = analysis.get_alpha_texture_bounds(texture_type)?; + // If the bounds are empty, then we might not be able to render the glyph with cleartype. + // Try again with aliased rendering to check if that works instead. + if font.render_mode != FontRenderMode::Mono && + (bounds.left == bounds.right || bounds.top == bounds.bottom) { + let analysis2 = dwrote::GlyphRunAnalysis::create( + &glyph_run, + 1.0, + transform, + dwrote::DWRITE_RENDERING_MODE_ALIASED, + dwrite_measure_mode, + 0.0, + 0.0, + )?; + let bounds2 = analysis2.get_alpha_texture_bounds(dwrote::DWRITE_TEXTURE_ALIASED_1x1)?; + if bounds2.left != bounds2.right && bounds2.top != bounds2.bottom { + return Ok((analysis2, dwrote::DWRITE_TEXTURE_ALIASED_1x1, bounds2)); + } + } + Ok((analysis, texture_type, bounds)) + } + + pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option { + let face = &self.fonts.get(&font_key).unwrap().face; + let indices = face.get_glyph_indices(&[ch as u32]); + indices.first().map(|idx| *idx as u32) + } + + pub fn get_glyph_dimensions( + &mut self, + font: &FontInstance, + key: &GlyphKey, + ) -> Option { + let (size, _, bitmaps, transform) = Self::get_glyph_parameters(font, key); + let (_, _, bounds) = self.create_glyph_analysis(font, key, size, transform, bitmaps).ok()?; + + let width = (bounds.right - bounds.left) as i32; + let height = (bounds.bottom - bounds.top) as i32; + + // Alpha texture bounds can sometimes return an empty rect + // Such as for spaces + if width == 0 || height == 0 { + return None; + } + + let face = self.get_font_face(font); + face.get_design_glyph_metrics(&[key.index() as u16], false) + .first() + .map(|metrics| { + let em_size = size / 16.; + let design_units_per_pixel = face.metrics().metrics0().designUnitsPerEm as f32 / 16. as f32; + let scaled_design_units_to_pixels = em_size / design_units_per_pixel; + let advance = metrics.advanceWidth as f32 * scaled_design_units_to_pixels; + + GlyphDimensions { + left: bounds.left, + top: -bounds.top, + width, + height, + advance, + } + }) + } + + // DWrite ClearType gives us values in RGB, but WR expects BGRA. + fn convert_to_bgra( + &self, + pixels: &[u8], + width: usize, + height: usize, + texture_type: dwrote::DWRITE_TEXTURE_TYPE, + render_mode: FontRenderMode, + bitmaps: bool, + subpixel_bgr: bool, + texture_padding: bool, + ) -> Vec { + let (buffer_width, buffer_height, padding) = if texture_padding { + (width + 2, height + 2, 1) + } else { + (width, height, 0) + }; + + let buffer_length = buffer_width * buffer_height * 4; + let mut bgra_pixels: Vec = vec![0; buffer_length]; + + match (texture_type, render_mode, bitmaps) { + (dwrote::DWRITE_TEXTURE_ALIASED_1x1, _, _) => { + assert!(width * height == pixels.len()); + let mut i = 0; + for row in padding .. height + padding { + let row_offset = row * buffer_width; + for col in padding .. width + padding { + let offset = (row_offset + col) * 4; + let alpha = pixels[i]; + i += 1; + bgra_pixels[offset + 0] = alpha; + bgra_pixels[offset + 1] = alpha; + bgra_pixels[offset + 2] = alpha; + bgra_pixels[offset + 3] = alpha; + } + } + } + (_, FontRenderMode::Subpixel, false) => { + assert!(width * height * 3 == pixels.len()); + let mut i = 0; + for row in padding .. height + padding { + let row_offset = row * buffer_width; + for col in padding .. width + padding { + let offset = (row_offset + col) * 4; + let (mut r, g, mut b) = (pixels[i + 0], pixels[i + 1], pixels[i + 2]); + if subpixel_bgr { + mem::swap(&mut r, &mut b); + } + i += 3; + bgra_pixels[offset + 0] = b; + bgra_pixels[offset + 1] = g; + bgra_pixels[offset + 2] = r; + bgra_pixels[offset + 3] = 0xff; + } + } + } + _ => { + assert!(width * height * 3 == pixels.len()); + let mut i = 0; + for row in padding .. height + padding { + let row_offset = row * buffer_width; + for col in padding .. width + padding { + let offset = (row_offset + col) * 4; + // Only take the G channel, as its closest to D2D + let alpha = pixels[i + 1] as u8; + i += 3; + bgra_pixels[offset + 0] = alpha; + bgra_pixels[offset + 1] = alpha; + bgra_pixels[offset + 2] = alpha; + bgra_pixels[offset + 3] = alpha; + } + } + } + }; + bgra_pixels + } + + pub fn prepare_font(font: &mut FontInstance) { + match font.render_mode { + FontRenderMode::Mono => { + // In mono mode the color of the font is irrelevant. + font.color = ColorU::new(255, 255, 255, 255); + // Subpixel positioning is disabled in mono mode. + font.disable_subpixel_position(); + } + FontRenderMode::Alpha => { + font.color = font.color.luminance_color().quantize(); + } + FontRenderMode::Subpixel => { + font.color = font.color.quantize(); + } + } + } + + fn get_glyph_parameters(font: &FontInstance, key: &GlyphKey) -> (f32, f64, bool, Option) { + let (_, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0)); + let scaled_size = font.size.to_f64_px() * y_scale; + let bitmaps = is_bitmap_font(font); + let (mut shape, (mut x_offset, mut y_offset)) = if bitmaps { + (FontTransform::identity(), (0.0, 0.0)) + } else { + (font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key)) + }; + if font.flags.contains(FontInstanceFlags::FLIP_X) { + shape = shape.flip_x(); + } + if font.flags.contains(FontInstanceFlags::FLIP_Y) { + shape = shape.flip_y(); + } + if font.flags.contains(FontInstanceFlags::TRANSPOSE) { + shape = shape.swap_xy(); + } + let (mut tx, mut ty) = (0.0, 0.0); + if font.synthetic_italics.is_enabled() { + let (shape_, (tx_, ty_)) = font.synthesize_italics(shape, scaled_size); + shape = shape_; + tx = tx_; + ty = ty_; + }; + x_offset += tx; + y_offset += ty; + let transform = if !shape.is_identity() || (x_offset, y_offset) != (0.0, 0.0) { + Some(dwrote::DWRITE_MATRIX { + m11: shape.scale_x, + m12: shape.skew_y, + m21: shape.skew_x, + m22: shape.scale_y, + dx: x_offset as f32, + dy: y_offset as f32, + }) + } else { + None + }; + (scaled_size as f32, y_scale, bitmaps, transform) + } + + pub fn rasterize_glyph(&mut self, font: &FontInstance, key: &GlyphKey) -> GlyphRasterResult { + let (size, y_scale, bitmaps, transform) = Self::get_glyph_parameters(font, key); + let (analysis, texture_type, bounds) = self.create_glyph_analysis(font, key, size, transform, bitmaps) + .or(Err(GlyphRasterError::LoadFailed))?; + let width = (bounds.right - bounds.left) as i32; + let height = (bounds.bottom - bounds.top) as i32; + // Alpha texture bounds can sometimes return an empty rect + // Such as for spaces + if width == 0 || height == 0 { + return Err(GlyphRasterError::LoadFailed); + } + + let pixels = analysis.create_alpha_texture(texture_type, bounds).or(Err(GlyphRasterError::LoadFailed))?; + let mut bgra_pixels = self.convert_to_bgra(&pixels, width as usize, height as usize, + texture_type, font.render_mode, bitmaps, + font.flags.contains(FontInstanceFlags::SUBPIXEL_BGR), + font.use_texture_padding()); + + let FontInstancePlatformOptions { gamma, contrast, cleartype_level, .. } = + font.platform_options.unwrap_or_default(); + let gamma_lut = self.gamma_luts + .entry((gamma, contrast)) + .or_insert_with(|| + GammaLut::new( + contrast as f32 / 100.0, + gamma as f32 / 100.0, + gamma as f32 / 100.0, + )); + if bitmaps || texture_type == dwrote::DWRITE_TEXTURE_ALIASED_1x1 || + font.render_mode != FontRenderMode::Subpixel { + gamma_lut.preblend(&mut bgra_pixels, font.color); + } else { + gamma_lut.preblend_scaled(&mut bgra_pixels, font.color, cleartype_level); + } + + let format = if bitmaps { + GlyphFormat::Bitmap + } else if texture_type == dwrote::DWRITE_TEXTURE_ALIASED_1x1 { + font.get_alpha_glyph_format() + } else { + font.get_glyph_format() + }; + + let padding = if font.use_texture_padding() { 1 } else { 0 }; + Ok(RasterizedGlyph { + left: (bounds.left - padding) as f32, + top: (-bounds.top + padding) as f32, + width: width + padding * 2, + height: height + padding * 2, + scale: (if bitmaps { y_scale.recip() } else { 1.0 }) as f32, + format, + bytes: bgra_pixels, + }) + } +} diff --git a/third_party/webrender/webrender/src/prim_store/backdrop.rs b/third_party/webrender/webrender/src/prim_store/backdrop.rs new file mode 100644 index 00000000000..ea033574fb2 --- /dev/null +++ b/third_party/webrender/webrender/src/prim_store/backdrop.rs @@ -0,0 +1,97 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::units::*; +use crate::spatial_tree::SpatialNodeIndex; +use crate::intern::{Internable, InternDebug, Handle as InternHandle}; +use crate::internal_types::LayoutPrimitiveInfo; +use crate::prim_store::{ + InternablePrimitive, PictureIndex, PrimitiveInstanceKind, PrimKey, PrimTemplate, + PrimTemplateCommonData, PrimitiveStore, RectangleKey, +}; + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, Eq, PartialEq, MallocSizeOf, Hash)] +pub struct Backdrop { + pub pic_index: PictureIndex, + pub spatial_node_index: SpatialNodeIndex, + pub border_rect: RectangleKey, +} + +impl From for BackdropData { + fn from(backdrop: Backdrop) -> Self { + BackdropData { + pic_index: backdrop.pic_index, + spatial_node_index: backdrop.spatial_node_index, + border_rect: backdrop.border_rect.into(), + } + } +} + +pub type BackdropKey = PrimKey; + +impl BackdropKey { + pub fn new( + info: &LayoutPrimitiveInfo, + backdrop: Backdrop, + ) -> Self { + BackdropKey { + common: info.into(), + kind: backdrop, + } + } +} + +impl InternDebug for BackdropKey {} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, MallocSizeOf)] +pub struct BackdropData { + pub pic_index: PictureIndex, + pub spatial_node_index: SpatialNodeIndex, + pub border_rect: LayoutRect, +} + +pub type BackdropTemplate = PrimTemplate; + +impl From for BackdropTemplate { + fn from(backdrop: BackdropKey) -> Self { + let common = PrimTemplateCommonData::with_key_common(backdrop.common); + + BackdropTemplate { + common, + kind: backdrop.kind.into(), + } + } +} + +pub type BackdropDataHandle = InternHandle; + +impl Internable for Backdrop { + type Key = BackdropKey; + type StoreData = BackdropTemplate; + type InternData = (); +} + +impl InternablePrimitive for Backdrop { + fn into_key( + self, + info: &LayoutPrimitiveInfo, + ) -> BackdropKey { + BackdropKey::new(info, self) + } + + fn make_instance_kind( + _key: BackdropKey, + data_handle: BackdropDataHandle, + _prim_store: &mut PrimitiveStore, + _reference_frame_relative_offset: LayoutVector2D, + ) -> PrimitiveInstanceKind { + PrimitiveInstanceKind::Backdrop { + data_handle, + } + } +} diff --git a/third_party/webrender/webrender/src/prim_store/borders.rs b/third_party/webrender/webrender/src/prim_store/borders.rs new file mode 100644 index 00000000000..084350c3357 --- /dev/null +++ b/third_party/webrender/webrender/src/prim_store/borders.rs @@ -0,0 +1,368 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{NormalBorder, PremultipliedColorF, Shadow}; +use api::units::*; +use crate::border::create_border_segments; +use crate::border::NormalBorderAu; +use crate::scene_building::{CreateShadow, IsVisible}; +use crate::frame_builder::{FrameBuildingState}; +use crate::gpu_cache::{GpuCache, GpuDataRequest}; +use crate::intern; +use crate::internal_types::LayoutPrimitiveInfo; +use crate::prim_store::{ + BorderSegmentInfo, BrushSegment, NinePatchDescriptor, PrimKey, + PrimTemplate, PrimTemplateCommonData, + PrimitiveInstanceKind, PrimitiveOpacity, + PrimitiveStore, InternablePrimitive, +}; +use crate::resource_cache::{ImageRequest, ResourceCache}; +use crate::storage; + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)] +pub struct NormalBorderPrim { + pub border: NormalBorderAu, + pub widths: LayoutSideOffsetsAu, +} + +pub type NormalBorderKey = PrimKey; + +impl NormalBorderKey { + pub fn new( + info: &LayoutPrimitiveInfo, + normal_border: NormalBorderPrim, + ) -> Self { + NormalBorderKey { + common: info.into(), + kind: normal_border, + } + } +} + +impl intern::InternDebug for NormalBorderKey {} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct NormalBorderData { + pub brush_segments: Vec, + pub border_segments: Vec, + pub border: NormalBorder, + pub widths: LayoutSideOffsets, +} + +impl NormalBorderData { + /// Update the GPU cache for a given primitive template. This may be called multiple + /// times per frame, by each primitive reference that refers to this interned + /// template. The initial request call to the GPU cache ensures that work is only + /// done if the cache entry is invalid (due to first use or eviction). + pub fn update( + &mut self, + common: &mut PrimTemplateCommonData, + frame_state: &mut FrameBuildingState, + ) { + if let Some(ref mut request) = frame_state.gpu_cache.request(&mut common.gpu_cache_handle) { + self.write_prim_gpu_blocks(request, common.prim_rect.size); + self.write_segment_gpu_blocks(request); + } + + common.opacity = PrimitiveOpacity::translucent(); + } + + fn write_prim_gpu_blocks( + &self, + request: &mut GpuDataRequest, + prim_size: LayoutSize + ) { + // Border primitives currently used for + // image borders, and run through the + // normal brush_image shader. + request.push(PremultipliedColorF::WHITE); + request.push(PremultipliedColorF::WHITE); + request.push([ + prim_size.width, + prim_size.height, + 0.0, + 0.0, + ]); + } + + fn write_segment_gpu_blocks( + &self, + request: &mut GpuDataRequest, + ) { + for segment in &self.brush_segments { + // has to match VECS_PER_SEGMENT + request.write_segment( + segment.local_rect, + segment.extra_data, + ); + } + } +} + +pub type NormalBorderTemplate = PrimTemplate; + +impl From for NormalBorderTemplate { + fn from(key: NormalBorderKey) -> Self { + let common = PrimTemplateCommonData::with_key_common(key.common); + + let mut border: NormalBorder = key.kind.border.into(); + let widths = LayoutSideOffsets::from_au(key.kind.widths); + + // FIXME(emilio): Is this the best place to do this? + border.normalize(&widths); + + let mut brush_segments = Vec::new(); + let mut border_segments = Vec::new(); + + create_border_segments( + common.prim_rect.size, + &border, + &widths, + &mut border_segments, + &mut brush_segments, + ); + + NormalBorderTemplate { + common, + kind: NormalBorderData { + brush_segments, + border_segments, + border, + widths, + } + } + } +} + +pub type NormalBorderDataHandle = intern::Handle; + +impl intern::Internable for NormalBorderPrim { + type Key = NormalBorderKey; + type StoreData = NormalBorderTemplate; + type InternData = (); +} + +impl InternablePrimitive for NormalBorderPrim { + fn into_key( + self, + info: &LayoutPrimitiveInfo, + ) -> NormalBorderKey { + NormalBorderKey::new( + info, + self, + ) + } + + fn make_instance_kind( + _key: NormalBorderKey, + data_handle: NormalBorderDataHandle, + _: &mut PrimitiveStore, + _reference_frame_relative_offset: LayoutVector2D, + ) -> PrimitiveInstanceKind { + PrimitiveInstanceKind::NormalBorder { + data_handle, + cache_handles: storage::Range::empty(), + } + } +} + +impl CreateShadow for NormalBorderPrim { + fn create_shadow(&self, shadow: &Shadow) -> Self { + let border = self.border.with_color(shadow.color.into()); + NormalBorderPrim { + border, + widths: self.widths, + } + } +} + +impl IsVisible for NormalBorderPrim { + fn is_visible(&self) -> bool { + true + } +} + +//////////////////////////////////////////////////////////////////////////////// + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)] +pub struct ImageBorder { + #[ignore_malloc_size_of = "Arc"] + pub request: ImageRequest, + pub nine_patch: NinePatchDescriptor, +} + +pub type ImageBorderKey = PrimKey; + +impl ImageBorderKey { + pub fn new( + info: &LayoutPrimitiveInfo, + image_border: ImageBorder, + ) -> Self { + ImageBorderKey { + common: info.into(), + kind: image_border, + } + } +} + +impl intern::InternDebug for ImageBorderKey {} + + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct ImageBorderData { + #[ignore_malloc_size_of = "Arc"] + pub request: ImageRequest, + pub brush_segments: Vec, +} + +impl ImageBorderData { + /// Update the GPU cache for a given primitive template. This may be called multiple + /// times per frame, by each primitive reference that refers to this interned + /// template. The initial request call to the GPU cache ensures that work is only + /// done if the cache entry is invalid (due to first use or eviction). + pub fn update( + &mut self, + common: &mut PrimTemplateCommonData, + frame_state: &mut FrameBuildingState, + ) { + if let Some(ref mut request) = frame_state.gpu_cache.request(&mut common.gpu_cache_handle) { + self.write_prim_gpu_blocks(request, &common.prim_rect.size); + self.write_segment_gpu_blocks(request); + } + + let image_properties = frame_state + .resource_cache + .get_image_properties(self.request.key); + + common.opacity = if let Some(image_properties) = image_properties { + PrimitiveOpacity { + is_opaque: image_properties.descriptor.is_opaque(), + } + } else { + PrimitiveOpacity::opaque() + } + } + + pub fn request_resources( + &mut self, + resource_cache: &mut ResourceCache, + gpu_cache: &mut GpuCache, + ) { + resource_cache.request_image( + self.request, + gpu_cache, + ); + } + + fn write_prim_gpu_blocks( + &self, + request: &mut GpuDataRequest, + prim_size: &LayoutSize, + ) { + // Border primitives currently used for + // image borders, and run through the + // normal brush_image shader. + request.push(PremultipliedColorF::WHITE); + request.push(PremultipliedColorF::WHITE); + request.push([ + prim_size.width, + prim_size.height, + 0.0, + 0.0, + ]); + } + + fn write_segment_gpu_blocks( + &self, + request: &mut GpuDataRequest, + ) { + for segment in &self.brush_segments { + // has to match VECS_PER_SEGMENT + request.write_segment( + segment.local_rect, + segment.extra_data, + ); + } + } +} + +pub type ImageBorderTemplate = PrimTemplate; + +impl From for ImageBorderTemplate { + fn from(key: ImageBorderKey) -> Self { + let common = PrimTemplateCommonData::with_key_common(key.common); + + let brush_segments = key.kind.nine_patch.create_segments(common.prim_rect.size); + ImageBorderTemplate { + common, + kind: ImageBorderData { + request: key.kind.request, + brush_segments, + } + } + } +} + +pub type ImageBorderDataHandle = intern::Handle; + +impl intern::Internable for ImageBorder { + type Key = ImageBorderKey; + type StoreData = ImageBorderTemplate; + type InternData = (); +} + +impl InternablePrimitive for ImageBorder { + fn into_key( + self, + info: &LayoutPrimitiveInfo, + ) -> ImageBorderKey { + ImageBorderKey::new( + info, + self, + ) + } + + fn make_instance_kind( + _key: ImageBorderKey, + data_handle: ImageBorderDataHandle, + _: &mut PrimitiveStore, + _reference_frame_relative_offset: LayoutVector2D, + ) -> PrimitiveInstanceKind { + PrimitiveInstanceKind::ImageBorder { + data_handle + } + } +} + +impl IsVisible for ImageBorder { + fn is_visible(&self) -> bool { + true + } +} + +#[test] +#[cfg(target_pointer_width = "64")] +fn test_struct_sizes() { + use std::mem; + // The sizes of these structures are critical for performance on a number of + // talos stress tests. If you get a failure here on CI, there's two possibilities: + // (a) You made a structure smaller than it currently is. Great work! Update the + // test expectations and move on. + // (b) You made a structure larger. This is not necessarily a problem, but should only + // be done with care, and after checking if talos performance regresses badly. + assert_eq!(mem::size_of::(), 84, "NormalBorderPrim size changed"); + assert_eq!(mem::size_of::(), 216, "NormalBorderTemplate size changed"); + assert_eq!(mem::size_of::(), 104, "NormalBorderKey size changed"); + assert_eq!(mem::size_of::(), 84, "ImageBorder size changed"); + assert_eq!(mem::size_of::(), 80, "ImageBorderTemplate size changed"); + assert_eq!(mem::size_of::(), 104, "ImageBorderKey size changed"); +} diff --git a/third_party/webrender/webrender/src/prim_store/gradient.rs b/third_party/webrender/webrender/src/prim_store/gradient.rs new file mode 100644 index 00000000000..add65ad8cd8 --- /dev/null +++ b/third_party/webrender/webrender/src/prim_store/gradient.rs @@ -0,0 +1,1007 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{ + ColorF, ColorU, ExtendMode, GradientStop, + PremultipliedColorF, LineOrientation, +}; +use api::units::{LayoutPoint, LayoutSize, LayoutVector2D}; +use crate::scene_building::IsVisible; +use euclid::approxeq::ApproxEq; +use crate::frame_builder::FrameBuildingState; +use crate::gpu_cache::{GpuCacheHandle, GpuDataRequest}; +use crate::intern::{Internable, InternDebug, Handle as InternHandle}; +use crate::internal_types::LayoutPrimitiveInfo; +use crate::prim_store::{BrushSegment, CachedGradientSegment, GradientTileRange, VectorKey}; +use crate::prim_store::{PrimitiveInstanceKind, PrimitiveOpacity}; +use crate::prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore}; +use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey, InternablePrimitive}; +use std::{hash, ops::{Deref, DerefMut}}; +use crate::util::pack_as_float; +use crate::texture_cache::TEXTURE_REGION_DIMENSIONS; + +/// The maximum number of stops a gradient may have to use the fast path. +pub const GRADIENT_FP_STOPS: usize = 4; + +/// A hashable gradient stop that can be used in primitive keys. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Copy, Clone, MallocSizeOf, PartialEq)] +pub struct GradientStopKey { + pub offset: f32, + pub color: ColorU, +} + +impl GradientStopKey { + pub fn empty() -> Self { + GradientStopKey { + offset: 0.0, + color: ColorU::new(0, 0, 0, 0), + } + } +} + +impl Into for GradientStop { + fn into(self) -> GradientStopKey { + GradientStopKey { + offset: self.offset, + color: self.color.into(), + } + } +} + +// Convert `stop_keys` into a vector of `GradientStop`s, which is a more +// convenient representation for the current gradient builder. Compute the +// minimum stop alpha along the way. +fn stops_and_min_alpha(stop_keys: &[GradientStopKey]) -> (Vec, f32) { + let mut min_alpha: f32 = 1.0; + let stops = stop_keys.iter().map(|stop_key| { + let color: ColorF = stop_key.color.into(); + min_alpha = min_alpha.min(color.a); + + GradientStop { + offset: stop_key.offset, + color, + } + }).collect(); + + (stops, min_alpha) +} + +impl Eq for GradientStopKey {} + +impl hash::Hash for GradientStopKey { + fn hash(&self, state: &mut H) { + self.offset.to_bits().hash(state); + self.color.hash(state); + } +} + +/// Identifying key for a linear gradient. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)] +pub struct LinearGradientKey { + pub common: PrimKeyCommonData, + pub extend_mode: ExtendMode, + pub start_point: PointKey, + pub end_point: PointKey, + pub stretch_size: SizeKey, + pub tile_spacing: SizeKey, + pub stops: Vec, + pub reverse_stops: bool, + pub nine_patch: Option>, +} + +impl LinearGradientKey { + pub fn new( + info: &LayoutPrimitiveInfo, + linear_grad: LinearGradient, + ) -> Self { + LinearGradientKey { + common: info.into(), + extend_mode: linear_grad.extend_mode, + start_point: linear_grad.start_point, + end_point: linear_grad.end_point, + stretch_size: linear_grad.stretch_size, + tile_spacing: linear_grad.tile_spacing, + stops: linear_grad.stops, + reverse_stops: linear_grad.reverse_stops, + nine_patch: linear_grad.nine_patch, + } + } +} + +impl InternDebug for LinearGradientKey {} + +#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct GradientCacheKey { + pub orientation: LineOrientation, + pub start_stop_point: VectorKey, + pub stops: [GradientStopKey; GRADIENT_FP_STOPS], +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct LinearGradientTemplate { + pub common: PrimTemplateCommonData, + pub extend_mode: ExtendMode, + pub start_point: LayoutPoint, + pub end_point: LayoutPoint, + pub stretch_size: LayoutSize, + pub tile_spacing: LayoutSize, + pub stops_opacity: PrimitiveOpacity, + pub stops: Vec, + pub brush_segments: Vec, + pub reverse_stops: bool, + pub stops_handle: GpuCacheHandle, + /// If true, this gradient can be drawn via the fast path + /// (cache gradient, and draw as image). + pub supports_caching: bool, +} + +impl Deref for LinearGradientTemplate { + type Target = PrimTemplateCommonData; + fn deref(&self) -> &Self::Target { + &self.common + } +} + +impl DerefMut for LinearGradientTemplate { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.common + } +} + +impl From for LinearGradientTemplate { + fn from(item: LinearGradientKey) -> Self { + let common = PrimTemplateCommonData::with_key_common(item.common); + + // Check if we can draw this gradient via a fast path by caching the + // gradient in a smaller task, and drawing as an image. + // TODO(gw): Aim to reduce the constraints on fast path gradients in future, + // although this catches the vast majority of gradients on real pages. + let mut supports_caching = + // Gradient must cover entire primitive + item.tile_spacing.w + item.stretch_size.w >= common.prim_rect.size.width && + item.tile_spacing.h + item.stretch_size.h >= common.prim_rect.size.height && + // Must be a vertical or horizontal gradient + (item.start_point.x.approx_eq(&item.end_point.x) || + item.start_point.y.approx_eq(&item.end_point.y)) && + // Fast path not supported on segmented (border-image) gradients. + item.nine_patch.is_none(); + + // if we support caching and the gradient uses repeat, we might potentially + // emit a lot of quads to cover the primitive. each quad will still cover + // the entire gradient along the other axis, so the effect is linear in + // display resolution, not quadratic (unlike say a tiny background image + // tiling the display). in addition, excessive minification may lead to + // texture trashing. so use the minification as a proxy heuristic for both + // cases. + // + // note that the actual number of quads may be further increased due to + // hard-stops and/or more than GRADIENT_FP_STOPS stops per gradient. + if supports_caching && item.extend_mode == ExtendMode::Repeat { + let single_repeat_size = + if item.start_point.x.approx_eq(&item.end_point.x) { + item.end_point.y - item.start_point.y + } else { + item.end_point.x - item.start_point.x + }; + let downscaling = single_repeat_size as f32 / TEXTURE_REGION_DIMENSIONS as f32; + if downscaling < 0.1 { + // if a single copy of the gradient is this small relative to its baked + // gradient cache, we have bad texture caching and/or too many quads. + supports_caching = false; + } + } + + let (stops, min_alpha) = stops_and_min_alpha(&item.stops); + + let mut brush_segments = Vec::new(); + + if let Some(ref nine_patch) = item.nine_patch { + brush_segments = nine_patch.create_segments(common.prim_rect.size); + } + + // Save opacity of the stops for use in + // selecting which pass this gradient + // should be drawn in. + let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha); + + LinearGradientTemplate { + common, + extend_mode: item.extend_mode, + start_point: item.start_point.into(), + end_point: item.end_point.into(), + stretch_size: item.stretch_size.into(), + tile_spacing: item.tile_spacing.into(), + stops_opacity, + stops, + brush_segments, + reverse_stops: item.reverse_stops, + stops_handle: GpuCacheHandle::new(), + supports_caching, + } + } +} + +impl LinearGradientTemplate { + /// Update the GPU cache for a given primitive template. This may be called multiple + /// times per frame, by each primitive reference that refers to this interned + /// template. The initial request call to the GPU cache ensures that work is only + /// done if the cache entry is invalid (due to first use or eviction). + pub fn update( + &mut self, + frame_state: &mut FrameBuildingState, + ) { + if let Some(mut request) = + frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) { + // write_prim_gpu_blocks + request.push([ + self.start_point.x, + self.start_point.y, + self.end_point.x, + self.end_point.y, + ]); + request.push([ + pack_as_float(self.extend_mode as u32), + self.stretch_size.width, + self.stretch_size.height, + 0.0, + ]); + + // write_segment_gpu_blocks + for segment in &self.brush_segments { + // has to match VECS_PER_SEGMENT + request.write_segment( + segment.local_rect, + segment.extra_data, + ); + } + } + + if let Some(mut request) = frame_state.gpu_cache.request(&mut self.stops_handle) { + GradientGpuBlockBuilder::build( + self.reverse_stops, + &mut request, + &self.stops, + ); + } + + self.opacity = { + // If the coverage of the gradient extends to or beyond + // the primitive rect, then the opacity can be determined + // by the colors of the stops. If we have tiling / spacing + // then we just assume the gradient is translucent for now. + // (In the future we could consider segmenting in some cases). + let stride = self.stretch_size + self.tile_spacing; + if stride.width >= self.common.prim_rect.size.width && + stride.height >= self.common.prim_rect.size.height { + self.stops_opacity + } else { + PrimitiveOpacity::translucent() + } + } + } +} + +pub type LinearGradientDataHandle = InternHandle; + +#[derive(Debug, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct LinearGradient { + pub extend_mode: ExtendMode, + pub start_point: PointKey, + pub end_point: PointKey, + pub stretch_size: SizeKey, + pub tile_spacing: SizeKey, + pub stops: Vec, + pub reverse_stops: bool, + pub nine_patch: Option>, +} + +impl Internable for LinearGradient { + type Key = LinearGradientKey; + type StoreData = LinearGradientTemplate; + type InternData = (); +} + +impl InternablePrimitive for LinearGradient { + fn into_key( + self, + info: &LayoutPrimitiveInfo, + ) -> LinearGradientKey { + LinearGradientKey::new(info, self) + } + + fn make_instance_kind( + _key: LinearGradientKey, + data_handle: LinearGradientDataHandle, + prim_store: &mut PrimitiveStore, + _reference_frame_relative_offset: LayoutVector2D, + ) -> PrimitiveInstanceKind { + let gradient_index = prim_store.linear_gradients.push(LinearGradientPrimitive { + cache_segments: Vec::new(), + visible_tiles_range: GradientTileRange::empty(), + }); + + PrimitiveInstanceKind::LinearGradient { + data_handle, + gradient_index, + } + } +} + +impl IsVisible for LinearGradient { + fn is_visible(&self) -> bool { + true + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct LinearGradientPrimitive { + pub cache_segments: Vec, + pub visible_tiles_range: GradientTileRange, +} + +//////////////////////////////////////////////////////////////////////////////// + +/// Hashable radial gradient parameters, for use during prim interning. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, MallocSizeOf, PartialEq)] +pub struct RadialGradientParams { + pub start_radius: f32, + pub end_radius: f32, + pub ratio_xy: f32, +} + +impl Eq for RadialGradientParams {} + +impl hash::Hash for RadialGradientParams { + fn hash(&self, state: &mut H) { + self.start_radius.to_bits().hash(state); + self.end_radius.to_bits().hash(state); + self.ratio_xy.to_bits().hash(state); + } +} + +/// Identifying key for a radial gradient. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)] +pub struct RadialGradientKey { + pub common: PrimKeyCommonData, + pub extend_mode: ExtendMode, + pub center: PointKey, + pub params: RadialGradientParams, + pub stretch_size: SizeKey, + pub stops: Vec, + pub tile_spacing: SizeKey, + pub nine_patch: Option>, +} + +impl RadialGradientKey { + pub fn new( + info: &LayoutPrimitiveInfo, + radial_grad: RadialGradient, + ) -> Self { + RadialGradientKey { + common: info.into(), + extend_mode: radial_grad.extend_mode, + center: radial_grad.center, + params: radial_grad.params, + stretch_size: radial_grad.stretch_size, + stops: radial_grad.stops, + tile_spacing: radial_grad.tile_spacing, + nine_patch: radial_grad.nine_patch, + } + } +} + +impl InternDebug for RadialGradientKey {} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct RadialGradientTemplate { + pub common: PrimTemplateCommonData, + pub extend_mode: ExtendMode, + pub center: LayoutPoint, + pub params: RadialGradientParams, + pub stretch_size: LayoutSize, + pub tile_spacing: LayoutSize, + pub brush_segments: Vec, + pub stops_opacity: PrimitiveOpacity, + pub stops: Vec, + pub stops_handle: GpuCacheHandle, +} + +impl Deref for RadialGradientTemplate { + type Target = PrimTemplateCommonData; + fn deref(&self) -> &Self::Target { + &self.common + } +} + +impl DerefMut for RadialGradientTemplate { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.common + } +} + +impl From for RadialGradientTemplate { + fn from(item: RadialGradientKey) -> Self { + let common = PrimTemplateCommonData::with_key_common(item.common); + let mut brush_segments = Vec::new(); + + if let Some(ref nine_patch) = item.nine_patch { + brush_segments = nine_patch.create_segments(common.prim_rect.size); + } + + let (stops, min_alpha) = stops_and_min_alpha(&item.stops); + + // Save opacity of the stops for use in + // selecting which pass this gradient + // should be drawn in. + let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha); + + RadialGradientTemplate { + common, + center: item.center.into(), + extend_mode: item.extend_mode, + params: item.params, + stretch_size: item.stretch_size.into(), + tile_spacing: item.tile_spacing.into(), + brush_segments, + stops_opacity, + stops, + stops_handle: GpuCacheHandle::new(), + } + } +} + +impl RadialGradientTemplate { + /// Update the GPU cache for a given primitive template. This may be called multiple + /// times per frame, by each primitive reference that refers to this interned + /// template. The initial request call to the GPU cache ensures that work is only + /// done if the cache entry is invalid (due to first use or eviction). + pub fn update( + &mut self, + frame_state: &mut FrameBuildingState, + ) { + if let Some(mut request) = + frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) { + // write_prim_gpu_blocks + request.push([ + self.center.x, + self.center.y, + self.params.start_radius, + self.params.end_radius, + ]); + request.push([ + self.params.ratio_xy, + pack_as_float(self.extend_mode as u32), + self.stretch_size.width, + self.stretch_size.height, + ]); + + // write_segment_gpu_blocks + for segment in &self.brush_segments { + // has to match VECS_PER_SEGMENT + request.write_segment( + segment.local_rect, + segment.extra_data, + ); + } + } + + if let Some(mut request) = frame_state.gpu_cache.request(&mut self.stops_handle) { + GradientGpuBlockBuilder::build( + false, + &mut request, + &self.stops, + ); + } + + self.opacity = PrimitiveOpacity::translucent(); + } +} + +pub type RadialGradientDataHandle = InternHandle; + +#[derive(Debug, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct RadialGradient { + pub extend_mode: ExtendMode, + pub center: PointKey, + pub params: RadialGradientParams, + pub stretch_size: SizeKey, + pub stops: Vec, + pub tile_spacing: SizeKey, + pub nine_patch: Option>, +} + +impl Internable for RadialGradient { + type Key = RadialGradientKey; + type StoreData = RadialGradientTemplate; + type InternData = (); +} + +impl InternablePrimitive for RadialGradient { + fn into_key( + self, + info: &LayoutPrimitiveInfo, + ) -> RadialGradientKey { + RadialGradientKey::new(info, self) + } + + fn make_instance_kind( + _key: RadialGradientKey, + data_handle: RadialGradientDataHandle, + _prim_store: &mut PrimitiveStore, + _reference_frame_relative_offset: LayoutVector2D, + ) -> PrimitiveInstanceKind { + PrimitiveInstanceKind::RadialGradient { + data_handle, + visible_tiles_range: GradientTileRange::empty(), + } + } +} + +impl IsVisible for RadialGradient { + fn is_visible(&self) -> bool { + true + } +} + +//////////////////////////////////////////////////////////////////////////////// + +/// Conic gradients + +/// Hashable conic gradient parameters, for use during prim interning. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, MallocSizeOf, PartialEq)] +pub struct ConicGradientParams { + pub angle: f32, // in radians + pub start_offset: f32, + pub end_offset: f32, +} + +impl Eq for ConicGradientParams {} + +impl hash::Hash for ConicGradientParams { + fn hash(&self, state: &mut H) { + self.angle.to_bits().hash(state); + self.start_offset.to_bits().hash(state); + self.end_offset.to_bits().hash(state); + } +} + +/// Identifying key for a line decoration. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)] +pub struct ConicGradientKey { + pub common: PrimKeyCommonData, + pub extend_mode: ExtendMode, + pub center: PointKey, + pub params: ConicGradientParams, + pub stretch_size: SizeKey, + pub stops: Vec, + pub tile_spacing: SizeKey, + pub nine_patch: Option>, +} + +impl ConicGradientKey { + pub fn new( + info: &LayoutPrimitiveInfo, + conic_grad: ConicGradient, + ) -> Self { + ConicGradientKey { + common: info.into(), + extend_mode: conic_grad.extend_mode, + center: conic_grad.center, + params: conic_grad.params, + stretch_size: conic_grad.stretch_size, + stops: conic_grad.stops, + tile_spacing: conic_grad.tile_spacing, + nine_patch: conic_grad.nine_patch, + } + } +} + +impl InternDebug for ConicGradientKey {} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct ConicGradientTemplate { + pub common: PrimTemplateCommonData, + pub extend_mode: ExtendMode, + pub center: LayoutPoint, + pub params: ConicGradientParams, + pub stretch_size: LayoutSize, + pub tile_spacing: LayoutSize, + pub brush_segments: Vec, + pub stops_opacity: PrimitiveOpacity, + pub stops: Vec, + pub stops_handle: GpuCacheHandle, +} + +impl Deref for ConicGradientTemplate { + type Target = PrimTemplateCommonData; + fn deref(&self) -> &Self::Target { + &self.common + } +} + +impl DerefMut for ConicGradientTemplate { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.common + } +} + +impl From for ConicGradientTemplate { + fn from(item: ConicGradientKey) -> Self { + let common = PrimTemplateCommonData::with_key_common(item.common); + let mut brush_segments = Vec::new(); + + if let Some(ref nine_patch) = item.nine_patch { + brush_segments = nine_patch.create_segments(common.prim_rect.size); + } + + let (stops, min_alpha) = stops_and_min_alpha(&item.stops); + + // Save opacity of the stops for use in + // selecting which pass this gradient + // should be drawn in. + let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha); + + ConicGradientTemplate { + common, + center: item.center.into(), + extend_mode: item.extend_mode, + params: item.params, + stretch_size: item.stretch_size.into(), + tile_spacing: item.tile_spacing.into(), + brush_segments, + stops_opacity, + stops, + stops_handle: GpuCacheHandle::new(), + } + } +} + +impl ConicGradientTemplate { + /// Update the GPU cache for a given primitive template. This may be called multiple + /// times per frame, by each primitive reference that refers to this interned + /// template. The initial request call to the GPU cache ensures that work is only + /// done if the cache entry is invalid (due to first use or eviction). + pub fn update( + &mut self, + frame_state: &mut FrameBuildingState, + ) { + if let Some(mut request) = + frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) { + // write_prim_gpu_blocks + request.push([ + self.center.x, + self.center.y, + self.params.start_offset, + self.params.end_offset, + ]); + request.push([ + self.params.angle, + pack_as_float(self.extend_mode as u32), + self.stretch_size.width, + self.stretch_size.height, + ]); + + // write_segment_gpu_blocks + for segment in &self.brush_segments { + // has to match VECS_PER_SEGMENT + request.write_segment( + segment.local_rect, + segment.extra_data, + ); + } + } + + if let Some(mut request) = frame_state.gpu_cache.request(&mut self.stops_handle) { + GradientGpuBlockBuilder::build( + false, + &mut request, + &self.stops, + ); + } + + self.opacity = PrimitiveOpacity::translucent(); + } +} + +pub type ConicGradientDataHandle = InternHandle; + +#[derive(Debug, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ConicGradient { + pub extend_mode: ExtendMode, + pub center: PointKey, + pub params: ConicGradientParams, + pub stretch_size: SizeKey, + pub stops: Vec, + pub tile_spacing: SizeKey, + pub nine_patch: Option>, +} + +impl Internable for ConicGradient { + type Key = ConicGradientKey; + type StoreData = ConicGradientTemplate; + type InternData = (); +} + +impl InternablePrimitive for ConicGradient { + fn into_key( + self, + info: &LayoutPrimitiveInfo, + ) -> ConicGradientKey { + ConicGradientKey::new(info, self) + } + + fn make_instance_kind( + _key: ConicGradientKey, + data_handle: ConicGradientDataHandle, + _prim_store: &mut PrimitiveStore, + _reference_frame_relative_offset: LayoutVector2D, + ) -> PrimitiveInstanceKind { + PrimitiveInstanceKind::ConicGradient { + data_handle, + visible_tiles_range: GradientTileRange::empty(), + } + } +} + +impl IsVisible for ConicGradient { + fn is_visible(&self) -> bool { + true + } +} + +//////////////////////////////////////////////////////////////////////////////// + +// The gradient entry index for the first color stop +pub const GRADIENT_DATA_FIRST_STOP: usize = 0; +// The gradient entry index for the last color stop +pub const GRADIENT_DATA_LAST_STOP: usize = GRADIENT_DATA_SIZE - 1; + +// The start of the gradient data table +pub const GRADIENT_DATA_TABLE_BEGIN: usize = GRADIENT_DATA_FIRST_STOP + 1; +// The exclusive bound of the gradient data table +pub const GRADIENT_DATA_TABLE_END: usize = GRADIENT_DATA_LAST_STOP; +// The number of entries in the gradient data table. +pub const GRADIENT_DATA_TABLE_SIZE: usize = 128; + +// The number of entries in a gradient data: GRADIENT_DATA_TABLE_SIZE + first stop entry + last stop entry +pub const GRADIENT_DATA_SIZE: usize = GRADIENT_DATA_TABLE_SIZE + 2; + +/// An entry in a gradient data table representing a segment of the gradient +/// color space. +#[derive(Debug, Copy, Clone)] +#[repr(C)] +struct GradientDataEntry { + start_color: PremultipliedColorF, + end_color: PremultipliedColorF, +} + +impl GradientDataEntry { + fn white() -> Self { + Self { + start_color: PremultipliedColorF::WHITE, + end_color: PremultipliedColorF::WHITE, + } + } +} + +// TODO(gw): Tidy this up to be a free function / module? +struct GradientGpuBlockBuilder {} + +impl GradientGpuBlockBuilder { + /// Generate a color ramp filling the indices in [start_idx, end_idx) and interpolating + /// from start_color to end_color. + fn fill_colors( + start_idx: usize, + end_idx: usize, + start_color: &PremultipliedColorF, + end_color: &PremultipliedColorF, + entries: &mut [GradientDataEntry; GRADIENT_DATA_SIZE], + ) { + // Calculate the color difference for individual steps in the ramp. + let inv_steps = 1.0 / (end_idx - start_idx) as f32; + let step_r = (end_color.r - start_color.r) * inv_steps; + let step_g = (end_color.g - start_color.g) * inv_steps; + let step_b = (end_color.b - start_color.b) * inv_steps; + let step_a = (end_color.a - start_color.a) * inv_steps; + + let mut cur_color = *start_color; + + // Walk the ramp writing start and end colors for each entry. + for index in start_idx .. end_idx { + let entry = &mut entries[index]; + entry.start_color = cur_color; + cur_color.r += step_r; + cur_color.g += step_g; + cur_color.b += step_b; + cur_color.a += step_a; + entry.end_color = cur_color; + } + } + + /// Compute an index into the gradient entry table based on a gradient stop offset. This + /// function maps offsets from [0, 1] to indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END]. + #[inline] + fn get_index(offset: f32) -> usize { + (offset.max(0.0).min(1.0) * GRADIENT_DATA_TABLE_SIZE as f32 + + GRADIENT_DATA_TABLE_BEGIN as f32) + .round() as usize + } + + // Build the gradient data from the supplied stops, reversing them if necessary. + fn build( + reverse_stops: bool, + request: &mut GpuDataRequest, + src_stops: &[GradientStop], + ) { + // Preconditions (should be ensured by DisplayListBuilder): + // * we have at least two stops + // * first stop has offset 0.0 + // * last stop has offset 1.0 + let mut src_stops = src_stops.into_iter(); + let mut cur_color = match src_stops.next() { + Some(stop) => { + debug_assert_eq!(stop.offset, 0.0); + stop.color.premultiplied() + } + None => { + error!("Zero gradient stops found!"); + PremultipliedColorF::BLACK + } + }; + + // A table of gradient entries, with two colors per entry, that specify the start and end color + // within the segment of the gradient space represented by that entry. To lookup a gradient result, + // first the entry index is calculated to determine which two colors to interpolate between, then + // the offset within that entry bucket is used to interpolate between the two colors in that entry. + // This layout preserves hard stops, as the end color for a given entry can differ from the start + // color for the following entry, despite them being adjacent. Colors are stored within in BGRA8 + // format for texture upload. This table requires the gradient color stops to be normalized to the + // range [0, 1]. The first and last entries hold the first and last color stop colors respectively, + // while the entries in between hold the interpolated color stop values for the range [0, 1]. + let mut entries = [GradientDataEntry::white(); GRADIENT_DATA_SIZE]; + + if reverse_stops { + // Fill in the first entry (for reversed stops) with the first color stop + GradientGpuBlockBuilder::fill_colors( + GRADIENT_DATA_LAST_STOP, + GRADIENT_DATA_LAST_STOP + 1, + &cur_color, + &cur_color, + &mut entries, + ); + + // Fill in the center of the gradient table, generating a color ramp between each consecutive pair + // of gradient stops. Each iteration of a loop will fill the indices in [next_idx, cur_idx). The + // loop will then fill indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END). + let mut cur_idx = GRADIENT_DATA_TABLE_END; + for next in src_stops { + let next_color = next.color.premultiplied(); + let next_idx = Self::get_index(1.0 - next.offset); + + if next_idx < cur_idx { + GradientGpuBlockBuilder::fill_colors( + next_idx, + cur_idx, + &next_color, + &cur_color, + &mut entries, + ); + cur_idx = next_idx; + } + + cur_color = next_color; + } + if cur_idx != GRADIENT_DATA_TABLE_BEGIN { + error!("Gradient stops abruptly at {}, auto-completing to white", cur_idx); + } + + // Fill in the last entry (for reversed stops) with the last color stop + GradientGpuBlockBuilder::fill_colors( + GRADIENT_DATA_FIRST_STOP, + GRADIENT_DATA_FIRST_STOP + 1, + &cur_color, + &cur_color, + &mut entries, + ); + } else { + // Fill in the first entry with the first color stop + GradientGpuBlockBuilder::fill_colors( + GRADIENT_DATA_FIRST_STOP, + GRADIENT_DATA_FIRST_STOP + 1, + &cur_color, + &cur_color, + &mut entries, + ); + + // Fill in the center of the gradient table, generating a color ramp between each consecutive pair + // of gradient stops. Each iteration of a loop will fill the indices in [cur_idx, next_idx). The + // loop will then fill indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END). + let mut cur_idx = GRADIENT_DATA_TABLE_BEGIN; + for next in src_stops { + let next_color = next.color.premultiplied(); + let next_idx = Self::get_index(next.offset); + + if next_idx > cur_idx { + GradientGpuBlockBuilder::fill_colors( + cur_idx, + next_idx, + &cur_color, + &next_color, + &mut entries, + ); + cur_idx = next_idx; + } + + cur_color = next_color; + } + if cur_idx != GRADIENT_DATA_TABLE_END { + error!("Gradient stops abruptly at {}, auto-completing to white", cur_idx); + } + + // Fill in the last entry with the last color stop + GradientGpuBlockBuilder::fill_colors( + GRADIENT_DATA_LAST_STOP, + GRADIENT_DATA_LAST_STOP + 1, + &cur_color, + &cur_color, + &mut entries, + ); + } + + for entry in entries.iter() { + request.push(entry.start_color); + request.push(entry.end_color); + } + } +} + +#[test] +#[cfg(target_pointer_width = "64")] +fn test_struct_sizes() { + use std::mem; + // The sizes of these structures are critical for performance on a number of + // talos stress tests. If you get a failure here on CI, there's two possibilities: + // (a) You made a structure smaller than it currently is. Great work! Update the + // test expectations and move on. + // (b) You made a structure larger. This is not necessarily a problem, but should only + // be done with care, and after checking if talos performance regresses badly. + assert_eq!(mem::size_of::(), 72, "LinearGradient size changed"); + assert_eq!(mem::size_of::(), 120, "LinearGradientTemplate size changed"); + assert_eq!(mem::size_of::(), 88, "LinearGradientKey size changed"); + + assert_eq!(mem::size_of::(), 72, "RadialGradient size changed"); + assert_eq!(mem::size_of::(), 128, "RadialGradientTemplate size changed"); + assert_eq!(mem::size_of::(), 96, "RadialGradientKey size changed"); + + assert_eq!(mem::size_of::(), 72, "ConicGradient size changed"); + assert_eq!(mem::size_of::(), 128, "ConicGradientTemplate size changed"); + assert_eq!(mem::size_of::(), 96, "ConicGradientKey size changed"); +} diff --git a/third_party/webrender/webrender/src/prim_store/image.rs b/third_party/webrender/webrender/src/prim_store/image.rs new file mode 100644 index 00000000000..922bd5b80cd --- /dev/null +++ b/third_party/webrender/webrender/src/prim_store/image.rs @@ -0,0 +1,511 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{ + AlphaType, ColorDepth, ColorF, ColorU, + ImageKey as ApiImageKey, ImageRendering, + PremultipliedColorF, Shadow, YuvColorSpace, ColorRange, YuvFormat, +}; +use api::units::*; +use crate::scene_building::{CreateShadow, IsVisible}; +use crate::frame_builder::FrameBuildingState; +use crate::gpu_cache::{GpuCache, GpuDataRequest}; +use crate::intern::{Internable, InternDebug, Handle as InternHandle}; +use crate::internal_types::{LayoutPrimitiveInfo}; +use crate::prim_store::{ + EdgeAaSegmentMask, OpacityBindingIndex, PrimitiveInstanceKind, + PrimitiveOpacity, PrimKey, + PrimTemplate, PrimTemplateCommonData, PrimitiveStore, SegmentInstanceIndex, + SizeKey, InternablePrimitive, +}; +use crate::render_target::RenderTargetKind; +use crate::render_task::{BlitSource, RenderTask}; +use crate::render_task_cache::{ + RenderTaskCacheEntryHandle, RenderTaskCacheKey, RenderTaskCacheKeyKind +}; +use crate::resource_cache::{ImageRequest, ResourceCache}; +use crate::util::pack_as_float; + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct VisibleImageTile { + pub tile_offset: TileOffset, + pub edge_flags: EdgeAaSegmentMask, + pub local_rect: LayoutRect, + pub local_clip_rect: LayoutRect, +} + +// Key that identifies a unique (partial) image that is being +// stored in the render task cache. +#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ImageCacheKey { + pub request: ImageRequest, + pub texel_rect: Option, +} + +/// Instance specific fields for an image primitive. These are +/// currently stored in a separate array to avoid bloating the +/// size of PrimitiveInstance. In the future, we should be able +/// to remove this and store the information inline, by: +/// (a) Removing opacity collapse / binding support completely. +/// Once we have general picture caching, we don't need this. +/// (b) Change visible_tiles to use Storage in the primitive +/// scratch buffer. This will reduce the size of the +/// visible_tiles field here, and save memory allocation +/// when image tiling is used. I've left it as a Vec for +/// now to reduce the number of changes, and because image +/// tiling is very rare on real pages. +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct ImageInstance { + pub opacity_binding_index: OpacityBindingIndex, + pub segment_instance_index: SegmentInstanceIndex, + pub tight_local_clip_rect: LayoutRect, + pub visible_tiles: Vec, +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, Eq, PartialEq, MallocSizeOf, Hash)] +pub struct Image { + pub key: ApiImageKey, + pub stretch_size: SizeKey, + pub tile_spacing: SizeKey, + pub color: ColorU, + pub image_rendering: ImageRendering, + pub alpha_type: AlphaType, +} + +pub type ImageKey = PrimKey; + +impl ImageKey { + pub fn new( + info: &LayoutPrimitiveInfo, + image: Image, + ) -> Self { + ImageKey { + common: info.into(), + kind: image, + } + } +} + +impl InternDebug for ImageKey {} + +// Where to find the texture data for an image primitive. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, MallocSizeOf)] +pub enum ImageSource { + // A normal image - just reference the texture cache. + Default, + // An image that is pre-rendered into the texture cache + // via a render task. + Cache { + size: DeviceIntSize, + handle: Option, + }, +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, MallocSizeOf)] +pub struct ImageData { + pub key: ApiImageKey, + pub stretch_size: LayoutSize, + pub tile_spacing: LayoutSize, + pub color: ColorF, + pub source: ImageSource, + pub image_rendering: ImageRendering, + pub alpha_type: AlphaType, +} + +impl From for ImageData { + fn from(image: Image) -> Self { + ImageData { + key: image.key, + color: image.color.into(), + stretch_size: image.stretch_size.into(), + tile_spacing: image.tile_spacing.into(), + source: ImageSource::Default, + image_rendering: image.image_rendering, + alpha_type: image.alpha_type, + } + } +} + +impl ImageData { + /// Update the GPU cache for a given primitive template. This may be called multiple + /// times per frame, by each primitive reference that refers to this interned + /// template. The initial request call to the GPU cache ensures that work is only + /// done if the cache entry is invalid (due to first use or eviction). + pub fn update( + &mut self, + common: &mut PrimTemplateCommonData, + frame_state: &mut FrameBuildingState, + ) { + if let Some(mut request) = frame_state.gpu_cache.request(&mut common.gpu_cache_handle) { + self.write_prim_gpu_blocks(&mut request); + } + + common.opacity = { + let image_properties = frame_state + .resource_cache + .get_image_properties(self.key); + + match image_properties { + Some(image_properties) => { + let is_tiled = image_properties.tiling.is_some(); + + if self.tile_spacing != LayoutSize::zero() && !is_tiled { + self.source = ImageSource::Cache { + // Size in device-pixels we need to allocate in render task cache. + size: image_properties.descriptor.size.to_i32(), + handle: None, + }; + } + + let mut is_opaque = image_properties.descriptor.is_opaque(); + let request = ImageRequest { + key: self.key, + rendering: self.image_rendering, + tile: None, + }; + + // Every frame, for cached items, we need to request the render + // task cache item. The closure will be invoked on the first + // time through, and any time the render task output has been + // evicted from the texture cache. + match self.source { + ImageSource::Cache { ref mut size, ref mut handle } => { + let padding = DeviceIntSideOffsets::new( + 0, + (self.tile_spacing.width * size.width as f32 / self.stretch_size.width) as i32, + (self.tile_spacing.height * size.height as f32 / self.stretch_size.height) as i32, + 0, + ); + + size.width += padding.horizontal(); + size.height += padding.vertical(); + + is_opaque &= padding == DeviceIntSideOffsets::zero(); + + let image_cache_key = ImageCacheKey { + request, + texel_rect: None, + }; + let target_kind = if image_properties.descriptor.format.bytes_per_pixel() == 1 { + RenderTargetKind::Alpha + } else { + RenderTargetKind::Color + }; + + // Request a pre-rendered image task. + *handle = Some(frame_state.resource_cache.request_render_task( + RenderTaskCacheKey { + size: *size, + kind: RenderTaskCacheKeyKind::Image(image_cache_key), + }, + frame_state.gpu_cache, + frame_state.render_tasks, + None, + image_properties.descriptor.is_opaque(), + |render_tasks| { + // Create a task to blit from the texture cache to + // a normal transient render task surface. This will + // copy only the sub-rect, if specified. + // TODO: figure out if/when we can do a blit instead. + let cache_to_target_task_id = RenderTask::new_scaling_with_padding( + BlitSource::Image { key: image_cache_key }, + render_tasks, + target_kind, + *size, + padding, + ); + + // Create a task to blit the rect from the child render + // task above back into the right spot in the persistent + // render target cache. + render_tasks.add().init(RenderTask::new_blit( + *size, + BlitSource::RenderTask { + task_id: cache_to_target_task_id, + }, + )) + } + )); + } + ImageSource::Default => {} + } + + if is_opaque { + PrimitiveOpacity::from_alpha(self.color.a) + } else { + PrimitiveOpacity::translucent() + } + } + None => { + PrimitiveOpacity::opaque() + } + } + }; + } + + pub fn write_prim_gpu_blocks(&self, request: &mut GpuDataRequest) { + // Images are drawn as a white color, modulated by the total + // opacity coming from any collapsed property bindings. + // Size has to match `VECS_PER_SPECIFIC_BRUSH` from `brush_image.glsl` exactly. + request.push(self.color.premultiplied()); + request.push(PremultipliedColorF::WHITE); + request.push([ + self.stretch_size.width + self.tile_spacing.width, + self.stretch_size.height + self.tile_spacing.height, + 0.0, + 0.0, + ]); + } +} + +pub type ImageTemplate = PrimTemplate; + +impl From for ImageTemplate { + fn from(image: ImageKey) -> Self { + let common = PrimTemplateCommonData::with_key_common(image.common); + + ImageTemplate { + common, + kind: image.kind.into(), + } + } +} + +pub type ImageDataHandle = InternHandle; + +impl Internable for Image { + type Key = ImageKey; + type StoreData = ImageTemplate; + type InternData = (); +} + +impl InternablePrimitive for Image { + fn into_key( + self, + info: &LayoutPrimitiveInfo, + ) -> ImageKey { + ImageKey::new(info, self) + } + + fn make_instance_kind( + _key: ImageKey, + data_handle: ImageDataHandle, + prim_store: &mut PrimitiveStore, + _reference_frame_relative_offset: LayoutVector2D, + ) -> PrimitiveInstanceKind { + // TODO(gw): Refactor this to not need a separate image + // instance (see ImageInstance struct). + let image_instance_index = prim_store.images.push(ImageInstance { + opacity_binding_index: OpacityBindingIndex::INVALID, + segment_instance_index: SegmentInstanceIndex::INVALID, + tight_local_clip_rect: LayoutRect::zero(), + visible_tiles: Vec::new(), + }); + + PrimitiveInstanceKind::Image { + data_handle, + image_instance_index, + is_compositor_surface: false, + } + } +} + +impl CreateShadow for Image { + fn create_shadow(&self, shadow: &Shadow) -> Self { + Image { + tile_spacing: self.tile_spacing, + stretch_size: self.stretch_size, + key: self.key, + image_rendering: self.image_rendering, + alpha_type: self.alpha_type, + color: shadow.color.into(), + } + } +} + +impl IsVisible for Image { + fn is_visible(&self) -> bool { + true + } +} + +//////////////////////////////////////////////////////////////////////////////// + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)] +pub struct YuvImage { + pub color_depth: ColorDepth, + pub yuv_key: [ApiImageKey; 3], + pub format: YuvFormat, + pub color_space: YuvColorSpace, + pub color_range: ColorRange, + pub image_rendering: ImageRendering, +} + +pub type YuvImageKey = PrimKey; + +impl YuvImageKey { + pub fn new( + info: &LayoutPrimitiveInfo, + yuv_image: YuvImage, + ) -> Self { + YuvImageKey { + common: info.into(), + kind: yuv_image, + } + } +} + +impl InternDebug for YuvImageKey {} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct YuvImageData { + pub color_depth: ColorDepth, + pub yuv_key: [ApiImageKey; 3], + pub format: YuvFormat, + pub color_space: YuvColorSpace, + pub color_range: ColorRange, + pub image_rendering: ImageRendering, +} + +impl From for YuvImageData { + fn from(image: YuvImage) -> Self { + YuvImageData { + color_depth: image.color_depth, + yuv_key: image.yuv_key, + format: image.format, + color_space: image.color_space, + color_range: image.color_range, + image_rendering: image.image_rendering, + } + } +} + +impl YuvImageData { + /// Update the GPU cache for a given primitive template. This may be called multiple + /// times per frame, by each primitive reference that refers to this interned + /// template. The initial request call to the GPU cache ensures that work is only + /// done if the cache entry is invalid (due to first use or eviction). + pub fn update( + &mut self, + common: &mut PrimTemplateCommonData, + frame_state: &mut FrameBuildingState, + ) { + if let Some(mut request) = frame_state.gpu_cache.request(&mut common.gpu_cache_handle) { + self.write_prim_gpu_blocks(&mut request); + }; + + // YUV images never have transparency + common.opacity = PrimitiveOpacity::opaque(); + } + + pub fn request_resources( + &mut self, + resource_cache: &mut ResourceCache, + gpu_cache: &mut GpuCache, + ) { + let channel_num = self.format.get_plane_num(); + debug_assert!(channel_num <= 3); + for channel in 0 .. channel_num { + resource_cache.request_image( + ImageRequest { + key: self.yuv_key[channel], + rendering: self.image_rendering, + tile: None, + }, + gpu_cache, + ); + } + } + + pub fn write_prim_gpu_blocks(&self, request: &mut GpuDataRequest) { + request.push([ + self.color_depth.rescaling_factor(), + pack_as_float(self.color_space as u32), + pack_as_float(self.format as u32), + 0.0 + ]); + } +} + +pub type YuvImageTemplate = PrimTemplate; + +impl From for YuvImageTemplate { + fn from(image: YuvImageKey) -> Self { + let common = PrimTemplateCommonData::with_key_common(image.common); + + YuvImageTemplate { + common, + kind: image.kind.into(), + } + } +} + +pub type YuvImageDataHandle = InternHandle; + +impl Internable for YuvImage { + type Key = YuvImageKey; + type StoreData = YuvImageTemplate; + type InternData = (); +} + +impl InternablePrimitive for YuvImage { + fn into_key( + self, + info: &LayoutPrimitiveInfo, + ) -> YuvImageKey { + YuvImageKey::new(info, self) + } + + fn make_instance_kind( + _key: YuvImageKey, + data_handle: YuvImageDataHandle, + _prim_store: &mut PrimitiveStore, + _reference_frame_relative_offset: LayoutVector2D, + ) -> PrimitiveInstanceKind { + PrimitiveInstanceKind::YuvImage { + data_handle, + segment_instance_index: SegmentInstanceIndex::INVALID, + is_compositor_surface: false, + } + } +} + +impl IsVisible for YuvImage { + fn is_visible(&self) -> bool { + true + } +} + +#[test] +#[cfg(target_pointer_width = "64")] +fn test_struct_sizes() { + use std::mem; + // The sizes of these structures are critical for performance on a number of + // talos stress tests. If you get a failure here on CI, there's two possibilities: + // (a) You made a structure smaller than it currently is. Great work! Update the + // test expectations and move on. + // (b) You made a structure larger. This is not necessarily a problem, but should only + // be done with care, and after checking if talos performance regresses badly. + assert_eq!(mem::size_of::(), 32, "Image size changed"); + assert_eq!(mem::size_of::(), 92, "ImageTemplate size changed"); + assert_eq!(mem::size_of::(), 52, "ImageKey size changed"); + assert_eq!(mem::size_of::(), 32, "YuvImage size changed"); + assert_eq!(mem::size_of::(), 60, "YuvImageTemplate size changed"); + assert_eq!(mem::size_of::(), 52, "YuvImageKey size changed"); +} diff --git a/third_party/webrender/webrender/src/prim_store/interned.rs b/third_party/webrender/webrender/src/prim_store/interned.rs new file mode 100644 index 00000000000..50337103c31 --- /dev/null +++ b/third_party/webrender/webrender/src/prim_store/interned.rs @@ -0,0 +1,14 @@ +/* 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/. */ + +// list of all interned primitives to match enumerate_interners! + +pub use crate::prim_store::backdrop::Backdrop; +pub use crate::prim_store::borders::{ImageBorder, NormalBorderPrim}; +pub use crate::prim_store::image::{Image, YuvImage}; +pub use crate::prim_store::line_dec::{LineDecoration}; +pub use crate::prim_store::gradient::{LinearGradient, RadialGradient, ConicGradient}; +pub use crate::prim_store::picture::Picture; +pub use crate::prim_store::text_run::TextRun; + diff --git a/third_party/webrender/webrender/src/prim_store/line_dec.rs b/third_party/webrender/webrender/src/prim_store/line_dec.rs new file mode 100644 index 00000000000..84537454533 --- /dev/null +++ b/third_party/webrender/webrender/src/prim_store/line_dec.rs @@ -0,0 +1,183 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{ + ColorF, ColorU, + LineOrientation, LineStyle, PremultipliedColorF, Shadow, +}; +use api::units::{Au, LayoutSizeAu, LayoutVector2D}; +use crate::scene_building::{CreateShadow, IsVisible}; +use crate::frame_builder::{FrameBuildingState}; +use crate::gpu_cache::GpuDataRequest; +use crate::intern; +use crate::internal_types::LayoutPrimitiveInfo; +use crate::prim_store::{ + PrimKey, PrimTemplate, PrimTemplateCommonData, + InternablePrimitive, PrimitiveStore, +}; +use crate::prim_store::PrimitiveInstanceKind; + +/// Maximum resolution in device pixels at which line decorations are rasterized. +pub const MAX_LINE_DECORATION_RESOLUTION: u32 = 4096; + +#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct LineDecorationCacheKey { + pub style: LineStyle, + pub orientation: LineOrientation, + pub wavy_line_thickness: Au, + pub size: LayoutSizeAu, +} + +/// Identifying key for a line decoration. +#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct LineDecoration { + // If the cache_key is Some(..) it is a line decoration + // that relies on a render task (e.g. wavy). If the + // cache key is None, it uses a fast path to draw the + // line decoration as a solid rect. + pub cache_key: Option, + pub color: ColorU, +} + +pub type LineDecorationKey = PrimKey; + +impl LineDecorationKey { + pub fn new( + info: &LayoutPrimitiveInfo, + line_dec: LineDecoration, + ) -> Self { + LineDecorationKey { + common: info.into(), + kind: line_dec, + } + } +} + +impl intern::InternDebug for LineDecorationKey {} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct LineDecorationData { + pub cache_key: Option, + pub color: ColorF, +} + +impl LineDecorationData { + /// Update the GPU cache for a given primitive template. This may be called multiple + /// times per frame, by each primitive reference that refers to this interned + /// template. The initial request call to the GPU cache ensures that work is only + /// done if the cache entry is invalid (due to first use or eviction). + pub fn update( + &mut self, + common: &mut PrimTemplateCommonData, + frame_state: &mut FrameBuildingState, + ) { + if let Some(ref mut request) = frame_state.gpu_cache.request(&mut common.gpu_cache_handle) { + self.write_prim_gpu_blocks(request); + } + } + + fn write_prim_gpu_blocks( + &self, + request: &mut GpuDataRequest + ) { + match self.cache_key.as_ref() { + Some(cache_key) => { + request.push(self.color.premultiplied()); + request.push(PremultipliedColorF::WHITE); + request.push([ + cache_key.size.width.to_f32_px(), + cache_key.size.height.to_f32_px(), + 0.0, + 0.0, + ]); + } + None => { + request.push(self.color.premultiplied()); + } + } + } +} + +pub type LineDecorationTemplate = PrimTemplate; + +impl From for LineDecorationTemplate { + fn from(line_dec: LineDecorationKey) -> Self { + let common = PrimTemplateCommonData::with_key_common(line_dec.common); + LineDecorationTemplate { + common, + kind: LineDecorationData { + cache_key: line_dec.kind.cache_key, + color: line_dec.kind.color.into(), + } + } + } +} + +pub type LineDecorationDataHandle = intern::Handle; + +impl intern::Internable for LineDecoration { + type Key = LineDecorationKey; + type StoreData = LineDecorationTemplate; + type InternData = (); +} + +impl InternablePrimitive for LineDecoration { + fn into_key( + self, + info: &LayoutPrimitiveInfo, + ) -> LineDecorationKey { + LineDecorationKey::new( + info, + self, + ) + } + + fn make_instance_kind( + _key: LineDecorationKey, + data_handle: LineDecorationDataHandle, + _: &mut PrimitiveStore, + _reference_frame_relative_offset: LayoutVector2D, + ) -> PrimitiveInstanceKind { + PrimitiveInstanceKind::LineDecoration { + data_handle, + cache_handle: None, + } + } +} + +impl CreateShadow for LineDecoration { + fn create_shadow(&self, shadow: &Shadow) -> Self { + LineDecoration { + color: shadow.color.into(), + cache_key: self.cache_key.clone(), + } + } +} + +impl IsVisible for LineDecoration { + fn is_visible(&self) -> bool { + self.color.a > 0 + } +} + +#[test] +#[cfg(target_pointer_width = "64")] +fn test_struct_sizes() { + use std::mem; + // The sizes of these structures are critical for performance on a number of + // talos stress tests. If you get a failure here on CI, there's two possibilities: + // (a) You made a structure smaller than it currently is. Great work! Update the + // test expectations and move on. + // (b) You made a structure larger. This is not necessarily a problem, but should only + // be done with care, and after checking if talos performance regresses badly. + assert_eq!(mem::size_of::(), 20, "LineDecoration size changed"); + assert_eq!(mem::size_of::(), 60, "LineDecorationTemplate size changed"); + assert_eq!(mem::size_of::(), 40, "LineDecorationKey size changed"); +} diff --git a/third_party/webrender/webrender/src/prim_store/mod.rs b/third_party/webrender/webrender/src/prim_store/mod.rs new file mode 100644 index 00000000000..0a1514a9b35 --- /dev/null +++ b/third_party/webrender/webrender/src/prim_store/mod.rs @@ -0,0 +1,4529 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{BorderRadius, ClipMode, ColorF, ColorU}; +use api::{ImageRendering, RepeatMode, PrimitiveFlags}; +use api::{PremultipliedColorF, PropertyBinding, Shadow, GradientStop}; +use api::{BoxShadowClipMode, LineStyle, LineOrientation, BorderStyle}; +use api::{PrimitiveKeyKind, ExtendMode, EdgeAaSegmentMask}; +use api::image_tiling::{self, Repetition}; +use api::units::*; +use crate::border::{get_max_scale_for_border, build_border_instances}; +use crate::border::BorderSegmentCacheKey; +use crate::clip::{ClipStore}; +use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX, SpatialTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace}; +use crate::clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItemKind}; +use crate::debug_colors; +use crate::debug_render::DebugItem; +use crate::scene_building::{CreateShadow, IsVisible}; +use euclid::{SideOffsets2D, Transform3D, Rect, Scale, Size2D, Point2D, Vector2D}; +use euclid::approxeq::ApproxEq; +use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState}; +use crate::frame_builder::{FrameVisibilityContext, FrameVisibilityState}; +use crate::glyph_rasterizer::GlyphKey; +use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest, ToGpuBlocks}; +use crate::gpu_types::{BrushFlags}; +use crate::intern; +use crate::internal_types::PlaneSplitAnchor; +use malloc_size_of::MallocSizeOf; +use crate::picture::{PictureCompositeMode, PicturePrimitive, ClusterFlags, TileCacheLogger}; +use crate::picture::{PrimitiveList, RecordedDirtyRegion, SurfaceIndex, RetainedTiles, RasterConfig}; +use crate::prim_store::backdrop::BackdropDataHandle; +use crate::prim_store::borders::{ImageBorderDataHandle, NormalBorderDataHandle}; +use crate::prim_store::gradient::{GRADIENT_FP_STOPS, GradientCacheKey, GradientStopKey}; +use crate::prim_store::gradient::{LinearGradientPrimitive, LinearGradientDataHandle, RadialGradientDataHandle, ConicGradientDataHandle}; +use crate::prim_store::image::{ImageDataHandle, ImageInstance, VisibleImageTile, YuvImageDataHandle}; +use crate::prim_store::line_dec::{LineDecorationDataHandle,MAX_LINE_DECORATION_RESOLUTION}; +use crate::prim_store::picture::PictureDataHandle; +use crate::prim_store::text_run::{TextRunDataHandle, TextRunPrimitive}; +#[cfg(debug_assertions)] +use crate::render_backend::{FrameId}; +use crate::render_backend::DataStores; +use crate::render_task_graph::RenderTaskId; +use crate::render_task_cache::{RenderTaskCacheKeyKind, RenderTaskCacheEntryHandle, RenderTaskCacheKey, to_cache_size}; +use crate::render_task::RenderTask; +use crate::renderer::{MAX_VERTEX_TEXTURE_WIDTH}; +use crate::resource_cache::{ImageProperties, ImageRequest}; +use crate::scene::SceneProperties; +use crate::segment::SegmentBuilder; +use std::{cmp, fmt, hash, ops, u32, usize, mem}; +#[cfg(debug_assertions)] +use std::sync::atomic::{AtomicUsize, Ordering}; +use crate::storage; +use crate::texture_cache::TEXTURE_REGION_DIMENSIONS; +use crate::util::{MatrixHelpers, MaxRect, Recycler, ScaleOffset, RectHelpers, PointHelpers}; +use crate::util::{clamp_to_scale_factor, pack_as_float, project_rect, raster_rect_to_device_pixels}; +use crate::internal_types::{LayoutPrimitiveInfo, Filter}; +use smallvec::SmallVec; + +pub mod backdrop; +pub mod borders; +pub mod gradient; +pub mod image; +pub mod line_dec; +pub mod picture; +pub mod text_run; +pub mod interned; + +/// Counter for unique primitive IDs for debug tracing. +#[cfg(debug_assertions)] +static NEXT_PRIM_ID: AtomicUsize = AtomicUsize::new(0); + +#[cfg(debug_assertions)] +static PRIM_CHASE_ID: AtomicUsize = AtomicUsize::new(usize::MAX); + +#[cfg(debug_assertions)] +pub fn register_prim_chase_id(id: PrimitiveDebugId) { + PRIM_CHASE_ID.store(id.0, Ordering::SeqCst); +} + +#[cfg(not(debug_assertions))] +pub fn register_prim_chase_id(_: PrimitiveDebugId) { +} + +const MIN_BRUSH_SPLIT_AREA: f32 = 128.0 * 128.0; +pub const VECS_PER_SEGMENT: usize = 2; + +const MAX_MASK_SIZE: f32 = 4096.0; + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Copy, Clone, MallocSizeOf)] +pub struct PrimitiveOpacity { + pub is_opaque: bool, +} + +impl PrimitiveOpacity { + pub fn opaque() -> PrimitiveOpacity { + PrimitiveOpacity { is_opaque: true } + } + + pub fn translucent() -> PrimitiveOpacity { + PrimitiveOpacity { is_opaque: false } + } + + pub fn from_alpha(alpha: f32) -> PrimitiveOpacity { + PrimitiveOpacity { + is_opaque: alpha >= 1.0, + } + } + + pub fn combine(self, other: PrimitiveOpacity) -> PrimitiveOpacity { + PrimitiveOpacity{ + is_opaque: self.is_opaque && other.is_opaque + } + } +} + +#[derive(Clone, Debug)] +pub struct SpaceSnapper { + pub ref_spatial_node_index: SpatialNodeIndex, + current_target_spatial_node_index: SpatialNodeIndex, + snapping_transform: Option, + pub device_pixel_scale: DevicePixelScale, +} + +impl SpaceSnapper { + pub fn new( + ref_spatial_node_index: SpatialNodeIndex, + device_pixel_scale: DevicePixelScale, + ) -> Self { + SpaceSnapper { + ref_spatial_node_index, + current_target_spatial_node_index: SpatialNodeIndex::INVALID, + snapping_transform: None, + device_pixel_scale, + } + } + + pub fn new_with_target( + ref_spatial_node_index: SpatialNodeIndex, + target_node_index: SpatialNodeIndex, + device_pixel_scale: DevicePixelScale, + spatial_tree: &SpatialTree, + ) -> Self { + let mut snapper = SpaceSnapper { + ref_spatial_node_index, + current_target_spatial_node_index: SpatialNodeIndex::INVALID, + snapping_transform: None, + device_pixel_scale, + }; + + snapper.set_target_spatial_node(target_node_index, spatial_tree); + snapper + } + + pub fn set_target_spatial_node( + &mut self, + target_node_index: SpatialNodeIndex, + spatial_tree: &SpatialTree, + ) { + if target_node_index == self.current_target_spatial_node_index { + return + } + + let ref_spatial_node = &spatial_tree.spatial_nodes[self.ref_spatial_node_index.0 as usize]; + let target_spatial_node = &spatial_tree.spatial_nodes[target_node_index.0 as usize]; + + self.current_target_spatial_node_index = target_node_index; + self.snapping_transform = match (ref_spatial_node.snapping_transform, target_spatial_node.snapping_transform) { + (Some(ref ref_scale_offset), Some(ref target_scale_offset)) => { + Some(ref_scale_offset + .inverse() + .accumulate(target_scale_offset) + .scale(self.device_pixel_scale.0)) + } + _ => None, + }; + } + + pub fn snap_rect(&self, rect: &Rect) -> Rect where F: fmt::Debug { + debug_assert!(self.current_target_spatial_node_index != SpatialNodeIndex::INVALID); + match self.snapping_transform { + Some(ref scale_offset) => { + let snapped_device_rect : DeviceRect = scale_offset.map_rect(rect).snap(); + scale_offset.unmap_rect(&snapped_device_rect) + } + None => *rect, + } + } + + pub fn snap_point(&self, point: &Point2D) -> Point2D where F: fmt::Debug { + debug_assert!(self.current_target_spatial_node_index != SpatialNodeIndex::INVALID); + match self.snapping_transform { + Some(ref scale_offset) => { + let snapped_device_vector : DevicePoint = scale_offset.map_point(point).snap(); + scale_offset.unmap_point(&snapped_device_vector) + } + None => *point, + } + } + + pub fn snap_size(&self, size: &Size2D) -> Size2D where F: fmt::Debug { + debug_assert!(self.current_target_spatial_node_index != SpatialNodeIndex::INVALID); + match self.snapping_transform { + Some(ref scale_offset) => { + let rect = Rect::::new(Point2D::::zero(), *size); + let snapped_device_rect : DeviceRect = scale_offset.map_rect(&rect).snap(); + scale_offset.unmap_rect(&snapped_device_rect).size + } + None => *size, + } + } +} + +#[derive(Debug, Clone)] +pub struct SpaceMapper { + kind: CoordinateSpaceMapping, + pub ref_spatial_node_index: SpatialNodeIndex, + pub current_target_spatial_node_index: SpatialNodeIndex, + pub bounds: Rect, + visible_face: VisibleFace, +} + +impl SpaceMapper where F: fmt::Debug { + pub fn new( + ref_spatial_node_index: SpatialNodeIndex, + bounds: Rect, + ) -> Self { + SpaceMapper { + kind: CoordinateSpaceMapping::Local, + ref_spatial_node_index, + current_target_spatial_node_index: ref_spatial_node_index, + bounds, + visible_face: VisibleFace::Front, + } + } + + pub fn new_with_target( + ref_spatial_node_index: SpatialNodeIndex, + target_node_index: SpatialNodeIndex, + bounds: Rect, + spatial_tree: &SpatialTree, + ) -> Self { + let mut mapper = Self::new(ref_spatial_node_index, bounds); + mapper.set_target_spatial_node(target_node_index, spatial_tree); + mapper + } + + pub fn set_target_spatial_node( + &mut self, + target_node_index: SpatialNodeIndex, + spatial_tree: &SpatialTree, + ) { + if target_node_index == self.current_target_spatial_node_index { + return + } + + let ref_spatial_node = &spatial_tree.spatial_nodes[self.ref_spatial_node_index.0 as usize]; + let target_spatial_node = &spatial_tree.spatial_nodes[target_node_index.0 as usize]; + + self.kind = if self.ref_spatial_node_index == target_node_index { + CoordinateSpaceMapping::Local + } else if ref_spatial_node.coordinate_system_id == target_spatial_node.coordinate_system_id { + let scale_offset = ref_spatial_node.content_transform + .inverse() + .accumulate(&target_spatial_node.content_transform); + CoordinateSpaceMapping::ScaleOffset(scale_offset) + } else { + let transform = spatial_tree + .get_relative_transform(target_node_index, self.ref_spatial_node_index) + .into_transform() + .with_source::() + .with_destination::(); + CoordinateSpaceMapping::Transform(transform) + }; + + self.visible_face = self.kind.visible_face(); + self.current_target_spatial_node_index = target_node_index; + } + + pub fn get_transform(&self) -> Transform3D { + match self.kind { + CoordinateSpaceMapping::Local => { + Transform3D::identity() + } + CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => { + scale_offset.to_transform() + } + CoordinateSpaceMapping::Transform(transform) => { + transform + } + } + } + + pub fn unmap(&self, rect: &Rect) -> Option> { + match self.kind { + CoordinateSpaceMapping::Local => { + Some(rect.cast_unit()) + } + CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => { + Some(scale_offset.unmap_rect(rect)) + } + CoordinateSpaceMapping::Transform(ref transform) => { + transform.inverse_rect_footprint(rect) + } + } + } + + pub fn map(&self, rect: &Rect) -> Option> { + match self.kind { + CoordinateSpaceMapping::Local => { + Some(rect.cast_unit()) + } + CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => { + Some(scale_offset.map_rect(rect)) + } + CoordinateSpaceMapping::Transform(ref transform) => { + match project_rect(transform, rect, &self.bounds) { + Some(bounds) => { + Some(bounds) + } + None => { + warn!("parent relative transform can't transform the primitive rect for {:?}", rect); + None + } + } + } + } + } + + pub fn map_vector(&self, v: Vector2D) -> Vector2D { + match self.kind { + CoordinateSpaceMapping::Local => { + v.cast_unit() + } + CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => { + scale_offset.map_vector(&v) + } + CoordinateSpaceMapping::Transform(ref transform) => { + transform.transform_vector2d(v) + } + } + } +} + +/// For external images, it's not possible to know the +/// UV coords of the image (or the image data itself) +/// until the render thread receives the frame and issues +/// callbacks to the client application. For external +/// images that are visible, a DeferredResolve is created +/// that is stored in the frame. This allows the render +/// thread to iterate this list and update any changed +/// texture data and update the UV rect. Any filtering +/// is handled externally for NativeTexture external +/// images. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct DeferredResolve { + pub address: GpuCacheAddress, + pub image_properties: ImageProperties, + pub rendering: ImageRendering, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct ClipTaskIndex(pub u16); + +impl ClipTaskIndex { + pub const INVALID: ClipTaskIndex = ClipTaskIndex(0); +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, MallocSizeOf, Ord, PartialOrd)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct PictureIndex(pub usize); + +impl GpuCacheHandle { + pub fn as_int(self, gpu_cache: &GpuCache) -> i32 { + gpu_cache.get_address(&self).as_int() + } +} + +impl GpuCacheAddress { + pub fn as_int(self) -> i32 { + // TODO(gw): Temporarily encode GPU Cache addresses as a single int. + // In the future, we can change the PrimitiveInstanceData struct + // to use 2x u16 for the vertex attribute instead of an i32. + self.v as i32 * MAX_VERTEX_TEXTURE_WIDTH as i32 + self.u as i32 + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Copy, Debug, Clone, MallocSizeOf, PartialEq)] +pub struct RectangleKey { + pub x: f32, + pub y: f32, + pub w: f32, + pub h: f32, +} + +impl RectangleKey { + pub fn intersects(&self, other: &Self) -> bool { + self.x < other.x + other.w + && other.x < self.x + self.w + && self.y < other.y + other.h + && other.y < self.y + self.h + } +} + +impl Eq for RectangleKey {} + +impl hash::Hash for RectangleKey { + fn hash(&self, state: &mut H) { + self.x.to_bits().hash(state); + self.y.to_bits().hash(state); + self.w.to_bits().hash(state); + self.h.to_bits().hash(state); + } +} + +impl From for LayoutRect { + fn from(key: RectangleKey) -> LayoutRect { + LayoutRect { + origin: LayoutPoint::new(key.x, key.y), + size: LayoutSize::new(key.w, key.h), + } + } +} + +impl From for WorldRect { + fn from(key: RectangleKey) -> WorldRect { + WorldRect { + origin: WorldPoint::new(key.x, key.y), + size: WorldSize::new(key.w, key.h), + } + } +} + +impl From for RectangleKey { + fn from(rect: LayoutRect) -> RectangleKey { + RectangleKey { + x: rect.origin.x, + y: rect.origin.y, + w: rect.size.width, + h: rect.size.height, + } + } +} + +impl From for RectangleKey { + fn from(rect: PictureRect) -> RectangleKey { + RectangleKey { + x: rect.origin.x, + y: rect.origin.y, + w: rect.size.width, + h: rect.size.height, + } + } +} + +impl From for RectangleKey { + fn from(rect: WorldRect) -> RectangleKey { + RectangleKey { + x: rect.origin.x, + y: rect.origin.y, + w: rect.size.width, + h: rect.size.height, + } + } +} + +/// A hashable SideOffset2D that can be used in primitive keys. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, MallocSizeOf, PartialEq)] +pub struct SideOffsetsKey { + pub top: f32, + pub right: f32, + pub bottom: f32, + pub left: f32, +} + +impl Eq for SideOffsetsKey {} + +impl hash::Hash for SideOffsetsKey { + fn hash(&self, state: &mut H) { + self.top.to_bits().hash(state); + self.right.to_bits().hash(state); + self.bottom.to_bits().hash(state); + self.left.to_bits().hash(state); + } +} + +impl From for LayoutSideOffsets { + fn from(key: SideOffsetsKey) -> LayoutSideOffsets { + LayoutSideOffsets::new( + key.top, + key.right, + key.bottom, + key.left, + ) + } +} + +impl From> for SideOffsetsKey { + fn from(offsets: SideOffsets2D) -> SideOffsetsKey { + SideOffsetsKey { + top: offsets.top, + right: offsets.right, + bottom: offsets.bottom, + left: offsets.left, + } + } +} + +/// A hashable size for using as a key during primitive interning. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Copy, Debug, Clone, MallocSizeOf, PartialEq)] +pub struct SizeKey { + w: f32, + h: f32, +} + +impl Eq for SizeKey {} + +impl hash::Hash for SizeKey { + fn hash(&self, state: &mut H) { + self.w.to_bits().hash(state); + self.h.to_bits().hash(state); + } +} + +impl From for LayoutSize { + fn from(key: SizeKey) -> LayoutSize { + LayoutSize::new(key.w, key.h) + } +} + +impl From> for SizeKey { + fn from(size: Size2D) -> SizeKey { + SizeKey { + w: size.width, + h: size.height, + } + } +} + +/// A hashable vec for using as a key during primitive interning. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Copy, Debug, Clone, MallocSizeOf, PartialEq)] +pub struct VectorKey { + pub x: f32, + pub y: f32, +} + +impl Eq for VectorKey {} + +impl hash::Hash for VectorKey { + fn hash(&self, state: &mut H) { + self.x.to_bits().hash(state); + self.y.to_bits().hash(state); + } +} + +impl From for LayoutVector2D { + fn from(key: VectorKey) -> LayoutVector2D { + LayoutVector2D::new(key.x, key.y) + } +} + +impl From for WorldVector2D { + fn from(key: VectorKey) -> WorldVector2D { + WorldVector2D::new(key.x, key.y) + } +} + +impl From for VectorKey { + fn from(vec: LayoutVector2D) -> VectorKey { + VectorKey { + x: vec.x, + y: vec.y, + } + } +} + +impl From for VectorKey { + fn from(vec: WorldVector2D) -> VectorKey { + VectorKey { + x: vec.x, + y: vec.y, + } + } +} + +/// A hashable point for using as a key during primitive interning. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Copy, Clone, MallocSizeOf, PartialEq)] +pub struct PointKey { + pub x: f32, + pub y: f32, +} + +impl Eq for PointKey {} + +impl hash::Hash for PointKey { + fn hash(&self, state: &mut H) { + self.x.to_bits().hash(state); + self.y.to_bits().hash(state); + } +} + +impl From for LayoutPoint { + fn from(key: PointKey) -> LayoutPoint { + LayoutPoint::new(key.x, key.y) + } +} + +impl From for PointKey { + fn from(p: LayoutPoint) -> PointKey { + PointKey { + x: p.x, + y: p.y, + } + } +} + +impl From for PointKey { + fn from(p: PicturePoint) -> PointKey { + PointKey { + x: p.x, + y: p.y, + } + } +} + +impl From for PointKey { + fn from(p: WorldPoint) -> PointKey { + PointKey { + x: p.x, + y: p.y, + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)] +pub struct PrimKeyCommonData { + pub flags: PrimitiveFlags, + pub prim_rect: RectangleKey, +} + +impl From<&LayoutPrimitiveInfo> for PrimKeyCommonData { + fn from(info: &LayoutPrimitiveInfo) -> Self { + PrimKeyCommonData { + flags: info.flags, + prim_rect: info.rect.into(), + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)] +pub struct PrimKey { + pub common: PrimKeyCommonData, + pub kind: T, +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)] +pub struct PrimitiveKey { + pub common: PrimKeyCommonData, + pub kind: PrimitiveKeyKind, +} + +impl PrimitiveKey { + pub fn new( + info: &LayoutPrimitiveInfo, + kind: PrimitiveKeyKind, + ) -> Self { + PrimitiveKey { + common: info.into(), + kind, + } + } +} + +impl intern::InternDebug for PrimitiveKey {} + +/// The shared information for a given primitive. This is interned and retained +/// both across frames and display lists, by comparing the matching PrimitiveKey. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub enum PrimitiveTemplateKind { + Rectangle { + color: PropertyBinding, + }, + Clear, +} + +/// Construct the primitive template data from a primitive key. This +/// is invoked when a primitive key is created and the interner +/// doesn't currently contain a primitive with this key. +impl From for PrimitiveTemplateKind { + fn from(kind: PrimitiveKeyKind) -> Self { + match kind { + PrimitiveKeyKind::Clear => { + PrimitiveTemplateKind::Clear + } + PrimitiveKeyKind::Rectangle { color, .. } => { + PrimitiveTemplateKind::Rectangle { + color: color.into(), + } + } + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct PrimTemplateCommonData { + pub flags: PrimitiveFlags, + pub may_need_repetition: bool, + pub prim_rect: LayoutRect, + pub opacity: PrimitiveOpacity, + /// The GPU cache handle for a primitive template. Since this structure + /// is retained across display lists by interning, this GPU cache handle + /// also remains valid, which reduces the number of updates to the GPU + /// cache when a new display list is processed. + pub gpu_cache_handle: GpuCacheHandle, +} + +impl PrimTemplateCommonData { + pub fn with_key_common(common: PrimKeyCommonData) -> Self { + PrimTemplateCommonData { + flags: common.flags, + may_need_repetition: true, + prim_rect: common.prim_rect.into(), + gpu_cache_handle: GpuCacheHandle::new(), + opacity: PrimitiveOpacity::translucent(), + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct PrimTemplate { + pub common: PrimTemplateCommonData, + pub kind: T, +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct PrimitiveTemplate { + pub common: PrimTemplateCommonData, + pub kind: PrimitiveTemplateKind, +} + +impl ops::Deref for PrimitiveTemplate { + type Target = PrimTemplateCommonData; + fn deref(&self) -> &Self::Target { + &self.common + } +} + +impl ops::DerefMut for PrimitiveTemplate { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.common + } +} + +impl From for PrimitiveTemplate { + fn from(item: PrimitiveKey) -> Self { + PrimitiveTemplate { + common: PrimTemplateCommonData::with_key_common(item.common), + kind: item.kind.into(), + } + } +} + +impl PrimitiveTemplateKind { + /// Write any GPU blocks for the primitive template to the given request object. + fn write_prim_gpu_blocks( + &self, + request: &mut GpuDataRequest, + scene_properties: &SceneProperties, + ) { + match *self { + PrimitiveTemplateKind::Clear => { + // Opaque black with operator dest out + request.push(PremultipliedColorF::BLACK); + } + PrimitiveTemplateKind::Rectangle { ref color, .. } => { + request.push(scene_properties.resolve_color(color).premultiplied()) + } + } + } +} + +impl PrimitiveTemplate { + /// Update the GPU cache for a given primitive template. This may be called multiple + /// times per frame, by each primitive reference that refers to this interned + /// template. The initial request call to the GPU cache ensures that work is only + /// done if the cache entry is invalid (due to first use or eviction). + pub fn update( + &mut self, + frame_state: &mut FrameBuildingState, + scene_properties: &SceneProperties, + ) { + if let Some(mut request) = frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) { + self.kind.write_prim_gpu_blocks(&mut request, scene_properties); + } + + self.opacity = match self.kind { + PrimitiveTemplateKind::Clear => { + PrimitiveOpacity::translucent() + } + PrimitiveTemplateKind::Rectangle { ref color, .. } => { + PrimitiveOpacity::from_alpha(scene_properties.resolve_color(color).a) + } + }; + } +} + +type PrimitiveDataHandle = intern::Handle; + +impl intern::Internable for PrimitiveKeyKind { + type Key = PrimitiveKey; + type StoreData = PrimitiveTemplate; + type InternData = (); +} + +impl InternablePrimitive for PrimitiveKeyKind { + fn into_key( + self, + info: &LayoutPrimitiveInfo, + ) -> PrimitiveKey { + PrimitiveKey::new(info, self) + } + + fn make_instance_kind( + key: PrimitiveKey, + data_handle: PrimitiveDataHandle, + prim_store: &mut PrimitiveStore, + _reference_frame_relative_offset: LayoutVector2D, + ) -> PrimitiveInstanceKind { + match key.kind { + PrimitiveKeyKind::Clear => { + PrimitiveInstanceKind::Clear { + data_handle + } + } + PrimitiveKeyKind::Rectangle { color, .. } => { + let color_binding_index = match color { + PropertyBinding::Binding(..) => { + prim_store.color_bindings.push(color) + } + PropertyBinding::Value(..) => ColorBindingIndex::INVALID, + }; + PrimitiveInstanceKind::Rectangle { + data_handle, + opacity_binding_index: OpacityBindingIndex::INVALID, + segment_instance_index: SegmentInstanceIndex::INVALID, + color_binding_index, + } + } + } + } +} + +// Maintains a list of opacity bindings that have been collapsed into +// the color of a single primitive. This is an important optimization +// that avoids allocating an intermediate surface for most common +// uses of opacity filters. +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct OpacityBinding { + pub bindings: Vec>, + pub current: f32, +} + +impl OpacityBinding { + pub fn new() -> OpacityBinding { + OpacityBinding { + bindings: Vec::new(), + current: 1.0, + } + } + + // Add a new opacity value / binding to the list + pub fn push(&mut self, binding: PropertyBinding) { + self.bindings.push(binding); + } + + // Resolve the current value of each opacity binding, and + // store that as a single combined opacity. + pub fn update(&mut self, scene_properties: &SceneProperties) { + let mut new_opacity = 1.0; + + for binding in &self.bindings { + let opacity = scene_properties.resolve_float(binding); + new_opacity = new_opacity * opacity; + } + + self.current = new_opacity; + } +} + +#[derive(Debug, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct VisibleMaskImageTile { + pub tile_offset: TileOffset, + pub tile_rect: LayoutRect, +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct VisibleGradientTile { + pub handle: GpuCacheHandle, + pub local_rect: LayoutRect, + pub local_clip_rect: LayoutRect, +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct CachedGradientSegment { + pub handle: RenderTaskCacheEntryHandle, + pub local_rect: LayoutRect, +} + +/// Information about how to cache a border segment, +/// along with the current render task cache entry. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, MallocSizeOf)] +pub struct BorderSegmentInfo { + pub local_task_size: LayoutSize, + pub cache_key: BorderSegmentCacheKey, +} + +/// Represents the visibility state of a segment (wrt clip masks). +#[cfg_attr(feature = "capture", derive(Serialize))] +#[derive(Debug, Clone)] +pub enum ClipMaskKind { + /// The segment has a clip mask, specified by the render task. + Mask(RenderTaskId), + /// The segment has no clip mask. + None, + /// The segment is made invisible / clipped completely. + Clipped, +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, MallocSizeOf)] +pub struct BrushSegment { + pub local_rect: LayoutRect, + pub may_need_clip_mask: bool, + pub edge_flags: EdgeAaSegmentMask, + pub extra_data: [f32; 4], + pub brush_flags: BrushFlags, +} + +impl BrushSegment { + pub fn new( + local_rect: LayoutRect, + may_need_clip_mask: bool, + edge_flags: EdgeAaSegmentMask, + extra_data: [f32; 4], + brush_flags: BrushFlags, + ) -> Self { + Self { + local_rect, + may_need_clip_mask, + edge_flags, + extra_data, + brush_flags, + } + } + + /// Write out to the clip mask instances array the correct clip mask + /// config for this segment. + pub fn update_clip_task( + &self, + clip_chain: Option<&ClipChainInstance>, + prim_bounding_rect: WorldRect, + root_spatial_node_index: SpatialNodeIndex, + surface_index: SurfaceIndex, + pic_state: &mut PictureState, + frame_context: &FrameBuildingContext, + frame_state: &mut FrameBuildingState, + clip_data_store: &mut ClipDataStore, + unclipped: &DeviceRect, + device_pixel_scale: DevicePixelScale, + ) -> ClipMaskKind { + match clip_chain { + Some(clip_chain) => { + if !clip_chain.needs_mask || + (!self.may_need_clip_mask && !clip_chain.has_non_local_clips) { + return ClipMaskKind::None; + } + + let segment_world_rect = match pic_state.map_pic_to_world.map(&clip_chain.pic_clip_rect) { + Some(rect) => rect, + None => return ClipMaskKind::Clipped, + }; + + let segment_world_rect = match segment_world_rect.intersection(&prim_bounding_rect) { + Some(rect) => rect, + None => return ClipMaskKind::Clipped, + }; + + // Get a minimal device space rect, clipped to the screen that we + // need to allocate for the clip mask, as well as interpolated + // snap offsets. + let device_rect = match get_clipped_device_rect( + unclipped, + &pic_state.map_raster_to_world, + segment_world_rect, + device_pixel_scale, + ) { + Some(info) => info, + None => { + return ClipMaskKind::Clipped; + } + }; + + let (device_rect, device_pixel_scale) = adjust_mask_scale_for_max_size(device_rect, device_pixel_scale); + + let clip_task_id = RenderTask::new_mask( + device_rect.to_i32(), + clip_chain.clips_range, + root_spatial_node_index, + frame_state.clip_store, + frame_state.gpu_cache, + frame_state.resource_cache, + frame_state.render_tasks, + clip_data_store, + device_pixel_scale, + frame_context.fb_config, + ); + let port = frame_state + .surfaces[surface_index.0] + .render_tasks + .unwrap_or_else(|| panic!("bug: no task for surface {:?}", surface_index)) + .port; + frame_state.render_tasks.add_dependency(port, clip_task_id); + ClipMaskKind::Mask(clip_task_id) + } + None => { + ClipMaskKind::Clipped + } + } + } +} + +#[derive(Debug)] +#[repr(C)] +struct ClipRect { + rect: LayoutRect, + mode: f32, +} + +#[derive(Debug)] +#[repr(C)] +struct ClipCorner { + rect: LayoutRect, + outer_radius_x: f32, + outer_radius_y: f32, + inner_radius_x: f32, + inner_radius_y: f32, +} + +impl ToGpuBlocks for ClipCorner { + fn write_gpu_blocks(&self, mut request: GpuDataRequest) { + self.write(&mut request) + } +} + +impl ClipCorner { + fn write(&self, request: &mut GpuDataRequest) { + request.push(self.rect); + request.push([ + self.outer_radius_x, + self.outer_radius_y, + self.inner_radius_x, + self.inner_radius_y, + ]); + } + + fn uniform(rect: LayoutRect, outer_radius: f32, inner_radius: f32) -> ClipCorner { + ClipCorner { + rect, + outer_radius_x: outer_radius, + outer_radius_y: outer_radius, + inner_radius_x: inner_radius, + inner_radius_y: inner_radius, + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct ImageMaskData { + /// The local size of the whole masked area. + pub local_mask_size: LayoutSize, +} + +impl ToGpuBlocks for ImageMaskData { + fn write_gpu_blocks(&self, mut request: GpuDataRequest) { + request.push([ + self.local_mask_size.width, + self.local_mask_size.height, + 0.0, + 0.0, + ]); + } +} + +#[derive(Debug)] +pub struct ClipData { + rect: ClipRect, + top_left: ClipCorner, + top_right: ClipCorner, + bottom_left: ClipCorner, + bottom_right: ClipCorner, +} + +impl ClipData { + pub fn rounded_rect(size: LayoutSize, radii: &BorderRadius, mode: ClipMode) -> ClipData { + // TODO(gw): For simplicity, keep most of the clip GPU structs the + // same as they were, even though the origin is now always + // zero, since they are in the clip's local space. In future, + // we could reduce the GPU cache size of ClipData. + let rect = LayoutRect::new( + LayoutPoint::zero(), + size, + ); + + ClipData { + rect: ClipRect { + rect, + mode: mode as u32 as f32, + }, + top_left: ClipCorner { + rect: LayoutRect::new( + LayoutPoint::new(rect.origin.x, rect.origin.y), + LayoutSize::new(radii.top_left.width, radii.top_left.height), + ), + outer_radius_x: radii.top_left.width, + outer_radius_y: radii.top_left.height, + inner_radius_x: 0.0, + inner_radius_y: 0.0, + }, + top_right: ClipCorner { + rect: LayoutRect::new( + LayoutPoint::new( + rect.origin.x + rect.size.width - radii.top_right.width, + rect.origin.y, + ), + LayoutSize::new(radii.top_right.width, radii.top_right.height), + ), + outer_radius_x: radii.top_right.width, + outer_radius_y: radii.top_right.height, + inner_radius_x: 0.0, + inner_radius_y: 0.0, + }, + bottom_left: ClipCorner { + rect: LayoutRect::new( + LayoutPoint::new( + rect.origin.x, + rect.origin.y + rect.size.height - radii.bottom_left.height, + ), + LayoutSize::new(radii.bottom_left.width, radii.bottom_left.height), + ), + outer_radius_x: radii.bottom_left.width, + outer_radius_y: radii.bottom_left.height, + inner_radius_x: 0.0, + inner_radius_y: 0.0, + }, + bottom_right: ClipCorner { + rect: LayoutRect::new( + LayoutPoint::new( + rect.origin.x + rect.size.width - radii.bottom_right.width, + rect.origin.y + rect.size.height - radii.bottom_right.height, + ), + LayoutSize::new(radii.bottom_right.width, radii.bottom_right.height), + ), + outer_radius_x: radii.bottom_right.width, + outer_radius_y: radii.bottom_right.height, + inner_radius_x: 0.0, + inner_radius_y: 0.0, + }, + } + } + + pub fn uniform(size: LayoutSize, radius: f32, mode: ClipMode) -> ClipData { + // TODO(gw): For simplicity, keep most of the clip GPU structs the + // same as they were, even though the origin is now always + // zero, since they are in the clip's local space. In future, + // we could reduce the GPU cache size of ClipData. + let rect = LayoutRect::new( + LayoutPoint::zero(), + size, + ); + + ClipData { + rect: ClipRect { + rect, + mode: mode as u32 as f32, + }, + top_left: ClipCorner::uniform( + LayoutRect::new( + LayoutPoint::new(rect.origin.x, rect.origin.y), + LayoutSize::new(radius, radius), + ), + radius, + 0.0, + ), + top_right: ClipCorner::uniform( + LayoutRect::new( + LayoutPoint::new(rect.origin.x + rect.size.width - radius, rect.origin.y), + LayoutSize::new(radius, radius), + ), + radius, + 0.0, + ), + bottom_left: ClipCorner::uniform( + LayoutRect::new( + LayoutPoint::new(rect.origin.x, rect.origin.y + rect.size.height - radius), + LayoutSize::new(radius, radius), + ), + radius, + 0.0, + ), + bottom_right: ClipCorner::uniform( + LayoutRect::new( + LayoutPoint::new( + rect.origin.x + rect.size.width - radius, + rect.origin.y + rect.size.height - radius, + ), + LayoutSize::new(radius, radius), + ), + radius, + 0.0, + ), + } + } + + pub fn write(&self, request: &mut GpuDataRequest) { + request.push(self.rect.rect); + request.push([self.rect.mode, 0.0, 0.0, 0.0]); + for corner in &[ + &self.top_left, + &self.top_right, + &self.bottom_left, + &self.bottom_right, + ] { + corner.write(request); + } + } +} + +/// A hashable descriptor for nine-patches, used by image and +/// gradient borders. +#[derive(Debug, Clone, PartialEq, Eq, Hash, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct NinePatchDescriptor { + pub width: i32, + pub height: i32, + pub slice: DeviceIntSideOffsets, + pub fill: bool, + pub repeat_horizontal: RepeatMode, + pub repeat_vertical: RepeatMode, + pub outset: SideOffsetsKey, + pub widths: SideOffsetsKey, +} + +impl IsVisible for PrimitiveKeyKind { + // Return true if the primary primitive is visible. + // Used to trivially reject non-visible primitives. + // TODO(gw): Currently, primitives other than those + // listed here are handled before the + // add_primitive() call. In the future + // we should move the logic for all other + // primitive types to use this. + fn is_visible(&self) -> bool { + match *self { + PrimitiveKeyKind::Clear => { + true + } + PrimitiveKeyKind::Rectangle { ref color, .. } => { + match *color { + PropertyBinding::Value(value) => value.a > 0, + PropertyBinding::Binding(..) => true, + } + } + } + } +} + +impl CreateShadow for PrimitiveKeyKind { + // Create a clone of this PrimitiveContainer, applying whatever + // changes are necessary to the primitive to support rendering + // it as part of the supplied shadow. + fn create_shadow( + &self, + shadow: &Shadow, + ) -> PrimitiveKeyKind { + match *self { + PrimitiveKeyKind::Rectangle { .. } => { + PrimitiveKeyKind::Rectangle { + color: PropertyBinding::Value(shadow.color.into()), + } + } + PrimitiveKeyKind::Clear => { + panic!("bug: this prim is not supported in shadow contexts"); + } + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct PrimitiveDebugId(pub usize); + +#[derive(Clone, Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub enum PrimitiveInstanceKind { + /// Direct reference to a Picture + Picture { + /// Handle to the common interned data for this primitive. + data_handle: PictureDataHandle, + pic_index: PictureIndex, + segment_instance_index: SegmentInstanceIndex, + }, + /// A run of glyphs, with associated font parameters. + TextRun { + /// Handle to the common interned data for this primitive. + data_handle: TextRunDataHandle, + /// Index to the per instance scratch data for this primitive. + run_index: TextRunIndex, + }, + /// A line decoration. cache_handle refers to a cached render + /// task handle, if this line decoration is not a simple solid. + LineDecoration { + /// Handle to the common interned data for this primitive. + data_handle: LineDecorationDataHandle, + // TODO(gw): For now, we need to store some information in + // the primitive instance that is created during + // prepare_prims and read during the batching pass. + // Once we unify the prepare_prims and batching to + // occur at the same time, we can remove most of + // the things we store here in the instance, and + // use them directly. This will remove cache_handle, + // but also the opacity, clip_task_id etc below. + cache_handle: Option, + }, + NormalBorder { + /// Handle to the common interned data for this primitive. + data_handle: NormalBorderDataHandle, + cache_handles: storage::Range, + }, + ImageBorder { + /// Handle to the common interned data for this primitive. + data_handle: ImageBorderDataHandle, + }, + Rectangle { + /// Handle to the common interned data for this primitive. + data_handle: PrimitiveDataHandle, + opacity_binding_index: OpacityBindingIndex, + segment_instance_index: SegmentInstanceIndex, + color_binding_index: ColorBindingIndex, + }, + YuvImage { + /// Handle to the common interned data for this primitive. + data_handle: YuvImageDataHandle, + segment_instance_index: SegmentInstanceIndex, + is_compositor_surface: bool, + }, + Image { + /// Handle to the common interned data for this primitive. + data_handle: ImageDataHandle, + image_instance_index: ImageInstanceIndex, + is_compositor_surface: bool, + }, + LinearGradient { + /// Handle to the common interned data for this primitive. + data_handle: LinearGradientDataHandle, + gradient_index: LinearGradientIndex, + }, + RadialGradient { + /// Handle to the common interned data for this primitive. + data_handle: RadialGradientDataHandle, + visible_tiles_range: GradientTileRange, + }, + ConicGradient { + /// Handle to the common interned data for this primitive. + data_handle: ConicGradientDataHandle, + visible_tiles_range: GradientTileRange, + }, + /// Clear out a rect, used for special effects. + Clear { + /// Handle to the common interned data for this primitive. + data_handle: PrimitiveDataHandle, + }, + /// Render a portion of a specified backdrop. + Backdrop { + data_handle: BackdropDataHandle, + }, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct PrimitiveVisibilityIndex(pub u32); + +impl PrimitiveVisibilityIndex { + pub const INVALID: PrimitiveVisibilityIndex = PrimitiveVisibilityIndex(u32::MAX); +} + +/// A bit mask describing which dirty regions a primitive is visible in. +/// A value of 0 means not visible in any region, while a mask of 0xffff +/// would be considered visible in all regions. +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct PrimitiveVisibilityMask { + bits: u16, +} + +impl PrimitiveVisibilityMask { + /// Construct a default mask, where no regions are considered visible + pub fn empty() -> Self { + PrimitiveVisibilityMask { + bits: 0, + } + } + + pub fn all() -> Self { + PrimitiveVisibilityMask { + bits: !0, + } + } + + pub fn include(&mut self, other: PrimitiveVisibilityMask) { + self.bits |= other.bits; + } + + pub fn intersects(&self, other: PrimitiveVisibilityMask) -> bool { + (self.bits & other.bits) != 0 + } + + /// Mark a given region index as visible + pub fn set_visible(&mut self, region_index: usize) { + debug_assert!(region_index < PrimitiveVisibilityMask::MAX_DIRTY_REGIONS); + self.bits |= 1 << region_index; + } + + /// Returns true if there are no visible regions + pub fn is_empty(&self) -> bool { + self.bits == 0 + } + + /// The maximum number of supported dirty regions. + pub const MAX_DIRTY_REGIONS: usize = 8 * mem::size_of::(); +} + +bitflags! { + /// A set of bitflags that can be set in the visibility information + /// for a primitive instance. This can be used to control how primitives + /// are treated during batching. + // TODO(gw): We should also move `is_compositor_surface` to be part of + // this flags struct. + #[cfg_attr(feature = "capture", derive(Serialize))] + pub struct PrimitiveVisibilityFlags: u16 { + /// Implies that this primitive covers the entire picture cache slice, + /// and can thus be dropped during batching and drawn with clear color. + const IS_BACKDROP = 1; + } +} + +/// Information stored for a visible primitive about the visible +/// rect and associated clip information. +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct PrimitiveVisibility { + /// The clip chain instance that was built for this primitive. + pub clip_chain: ClipChainInstance, + + /// The current world rect, clipped to screen / dirty rect boundaries. + // TODO(gw): This is only used by a small number of primitives. + // It's probably faster to not store this and recalculate + // on demand in those cases? + pub clipped_world_rect: WorldRect, + + /// An index into the clip task instances array in the primitive + /// store. If this is ClipTaskIndex::INVALID, then the primitive + /// has no clip mask. Otherwise, it may store the offset of the + /// global clip mask task for this primitive, or the first of + /// a list of clip task ids (one per segment). + pub clip_task_index: ClipTaskIndex, + + /// A set of flags that define how this primitive should be handled + /// during batching of visibile primitives. + pub flags: PrimitiveVisibilityFlags, + + /// A mask defining which of the dirty regions this primitive is visible in. + pub visibility_mask: PrimitiveVisibilityMask, + + /// The current combined local clip for this primitive, from + /// the primitive local clip above and the current clip chain. + pub combined_local_clip_rect: LayoutRect, +} + +#[derive(Clone, Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct PrimitiveInstance { + /// Identifies the kind of primitive this + /// instance is, and references to where + /// the relevant information for the primitive + /// can be found. + pub kind: PrimitiveInstanceKind, + + /// Local space clip rect for this instance + pub local_clip_rect: LayoutRect, + + #[cfg(debug_assertions)] + pub id: PrimitiveDebugId, + + /// The last frame ID (of the `RenderTaskGraph`) this primitive + /// was prepared for rendering in. + #[cfg(debug_assertions)] + pub prepared_frame_id: FrameId, + + /// If this primitive is visible, an index into the instance + /// visibility scratch buffer. If not visible, INVALID. + pub visibility_info: PrimitiveVisibilityIndex, + + /// ID of the clip chain that this primitive is clipped by. + pub clip_chain_id: ClipChainId, +} + +impl PrimitiveInstance { + pub fn new( + local_clip_rect: LayoutRect, + kind: PrimitiveInstanceKind, + clip_chain_id: ClipChainId, + ) -> Self { + PrimitiveInstance { + local_clip_rect, + kind, + #[cfg(debug_assertions)] + prepared_frame_id: FrameId::INVALID, + #[cfg(debug_assertions)] + id: PrimitiveDebugId(NEXT_PRIM_ID.fetch_add(1, Ordering::Relaxed)), + visibility_info: PrimitiveVisibilityIndex::INVALID, + clip_chain_id, + } + } + + // Reset any pre-frame state for this primitive. + pub fn reset(&mut self) { + self.visibility_info = PrimitiveVisibilityIndex::INVALID; + } + + #[cfg(debug_assertions)] + pub fn is_chased(&self) -> bool { + PRIM_CHASE_ID.load(Ordering::SeqCst) == self.id.0 + } + + #[cfg(not(debug_assertions))] + pub fn is_chased(&self) -> bool { + false + } + + pub fn uid(&self) -> intern::ItemUid { + match &self.kind { + PrimitiveInstanceKind::Clear { data_handle, .. } | + PrimitiveInstanceKind::Rectangle { data_handle, .. } => { + data_handle.uid() + } + PrimitiveInstanceKind::Image { data_handle, .. } => { + data_handle.uid() + } + PrimitiveInstanceKind::ImageBorder { data_handle, .. } => { + data_handle.uid() + } + PrimitiveInstanceKind::LineDecoration { data_handle, .. } => { + data_handle.uid() + } + PrimitiveInstanceKind::LinearGradient { data_handle, .. } => { + data_handle.uid() + } + PrimitiveInstanceKind::NormalBorder { data_handle, .. } => { + data_handle.uid() + } + PrimitiveInstanceKind::Picture { data_handle, .. } => { + data_handle.uid() + } + PrimitiveInstanceKind::RadialGradient { data_handle, .. } => { + data_handle.uid() + } + PrimitiveInstanceKind::ConicGradient { data_handle, .. } => { + data_handle.uid() + } + PrimitiveInstanceKind::TextRun { data_handle, .. } => { + data_handle.uid() + } + PrimitiveInstanceKind::YuvImage { data_handle, .. } => { + data_handle.uid() + } + PrimitiveInstanceKind::Backdrop { data_handle, .. } => { + data_handle.uid() + } + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[derive(Debug)] +pub struct SegmentedInstance { + pub gpu_cache_handle: GpuCacheHandle, + pub segments_range: SegmentsRange, +} + +pub type GlyphKeyStorage = storage::Storage; +pub type TextRunIndex = storage::Index; +pub type TextRunStorage = storage::Storage; +pub type OpacityBindingIndex = storage::Index; +pub type OpacityBindingStorage = storage::Storage; +pub type ColorBindingIndex = storage::Index>; +pub type ColorBindingStorage = storage::Storage>; +pub type BorderHandleStorage = storage::Storage; +pub type SegmentStorage = storage::Storage; +pub type SegmentsRange = storage::Range; +pub type SegmentInstanceStorage = storage::Storage; +pub type SegmentInstanceIndex = storage::Index; +pub type ImageInstanceStorage = storage::Storage; +pub type ImageInstanceIndex = storage::Index; +pub type GradientTileStorage = storage::Storage; +pub type GradientTileRange = storage::Range; +pub type LinearGradientIndex = storage::Index; +pub type LinearGradientStorage = storage::Storage; + +/// Contains various vecs of data that is used only during frame building, +/// where we want to recycle the memory each new display list, to avoid constantly +/// re-allocating and moving memory around. Written during primitive preparation, +/// and read during batching. +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct PrimitiveScratchBuffer { + /// Contains a list of clip mask instance parameters + /// per segment generated. + pub clip_mask_instances: Vec, + + /// List of glyphs keys that are allocated by each + /// text run instance. + pub glyph_keys: GlyphKeyStorage, + + /// List of render task handles for border segment instances + /// that have been added this frame. + pub border_cache_handles: BorderHandleStorage, + + /// A list of brush segments that have been built for this scene. + pub segments: SegmentStorage, + + /// A list of segment ranges and GPU cache handles for prim instances + /// that have opted into segment building. In future, this should be + /// removed in favor of segment building during primitive interning. + pub segment_instances: SegmentInstanceStorage, + + /// A list of visible tiles that tiled gradients use to store + /// per-tile information. + pub gradient_tiles: GradientTileStorage, + + /// List of the visibility information for currently visible primitives. + pub prim_info: Vec, + + /// List of dirty regions for the cached pictures in this document, used to + /// verify invalidation in wrench reftests. Only collected in testing. + pub recorded_dirty_regions: Vec, + + /// List of debug display items for rendering. + pub debug_items: Vec, +} + +impl PrimitiveScratchBuffer { + pub fn new() -> Self { + PrimitiveScratchBuffer { + clip_mask_instances: Vec::new(), + glyph_keys: GlyphKeyStorage::new(0), + border_cache_handles: BorderHandleStorage::new(0), + segments: SegmentStorage::new(0), + segment_instances: SegmentInstanceStorage::new(0), + gradient_tiles: GradientTileStorage::new(0), + recorded_dirty_regions: Vec::new(), + debug_items: Vec::new(), + prim_info: Vec::new(), + } + } + + pub fn recycle(&mut self, recycler: &mut Recycler) { + recycler.recycle_vec(&mut self.clip_mask_instances); + recycler.recycle_vec(&mut self.prim_info); + self.glyph_keys.recycle(recycler); + self.border_cache_handles.recycle(recycler); + self.segments.recycle(recycler); + self.segment_instances.recycle(recycler); + self.gradient_tiles.recycle(recycler); + recycler.recycle_vec(&mut self.debug_items); + } + + pub fn begin_frame(&mut self) { + // Clear the clip mask tasks for the beginning of the frame. Append + // a single kind representing no clip mask, at the ClipTaskIndex::INVALID + // location. + self.clip_mask_instances.clear(); + self.clip_mask_instances.push(ClipMaskKind::None); + + self.border_cache_handles.clear(); + + // TODO(gw): As in the previous code, the gradient tiles store GPU cache + // handles that are cleared (and thus invalidated + re-uploaded) + // every frame. This maintains the existing behavior, but we + // should fix this in the future to retain handles. + self.gradient_tiles.clear(); + + self.prim_info.clear(); + + self.debug_items.clear(); + + assert!(self.recorded_dirty_regions.is_empty(), "Should have sent to Renderer"); + } + + #[allow(dead_code)] + pub fn push_debug_rect( + &mut self, + rect: DeviceRect, + outer_color: ColorF, + inner_color: ColorF, + ) { + self.debug_items.push(DebugItem::Rect { + rect, + outer_color, + inner_color, + }); + } + + #[allow(dead_code)] + pub fn push_debug_string( + &mut self, + position: DevicePoint, + color: ColorF, + msg: String, + ) { + self.debug_items.push(DebugItem::Text { + position, + color, + msg, + }); + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Clone, Debug)] +pub struct PrimitiveStoreStats { + picture_count: usize, + text_run_count: usize, + opacity_binding_count: usize, + image_count: usize, + linear_gradient_count: usize, + color_binding_count: usize, +} + +impl PrimitiveStoreStats { + pub fn empty() -> Self { + PrimitiveStoreStats { + picture_count: 0, + text_run_count: 0, + opacity_binding_count: 0, + image_count: 0, + linear_gradient_count: 0, + color_binding_count: 0, + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct PrimitiveStore { + pub pictures: Vec, + pub text_runs: TextRunStorage, + pub linear_gradients: LinearGradientStorage, + + /// A list of image instances. These are stored separately as + /// storing them inline in the instance makes the structure bigger + /// for other types. + pub images: ImageInstanceStorage, + + /// List of animated opacity bindings for a primitive. + pub opacity_bindings: OpacityBindingStorage, + /// animated color bindings for this primitive. + pub color_bindings: ColorBindingStorage, +} + +impl PrimitiveStore { + pub fn new(stats: &PrimitiveStoreStats) -> PrimitiveStore { + PrimitiveStore { + pictures: Vec::with_capacity(stats.picture_count), + text_runs: TextRunStorage::new(stats.text_run_count), + images: ImageInstanceStorage::new(stats.image_count), + opacity_bindings: OpacityBindingStorage::new(stats.opacity_binding_count), + color_bindings: ColorBindingStorage::new(stats.color_binding_count), + linear_gradients: LinearGradientStorage::new(stats.linear_gradient_count), + } + } + + pub fn get_stats(&self) -> PrimitiveStoreStats { + PrimitiveStoreStats { + picture_count: self.pictures.len(), + text_run_count: self.text_runs.len(), + image_count: self.images.len(), + opacity_binding_count: self.opacity_bindings.len(), + linear_gradient_count: self.linear_gradients.len(), + color_binding_count: self.color_bindings.len(), + } + } + + #[allow(unused)] + pub fn print_picture_tree(&self, root: PictureIndex) { + use crate::print_tree::PrintTree; + let mut pt = PrintTree::new("picture tree"); + self.pictures[root.0].print(&self.pictures, root, &mut pt); + } + + /// Destroy an existing primitive store. This is called just before + /// a primitive store is replaced with a newly built scene. + pub fn destroy( + &mut self, + retained_tiles: &mut RetainedTiles, + ) { + for pic in &mut self.pictures { + pic.destroy( + retained_tiles, + ); + } + } + + /// Returns the total count of primitive instances contained in pictures. + pub fn prim_count(&self) -> usize { + let mut prim_count = 0; + for pic in &self.pictures { + for cluster in &pic.prim_list.clusters { + prim_count += cluster.prim_instances.len(); + } + } + prim_count + } + + /// Update visibility pass - update each primitive visibility struct, and + /// build the clip chain instance if appropriate. + pub fn update_visibility( + &mut self, + pic_index: PictureIndex, + parent_surface_index: SurfaceIndex, + world_culling_rect: &WorldRect, + frame_context: &FrameVisibilityContext, + frame_state: &mut FrameVisibilityState, + ) -> Option { + profile_scope!("update_visibility"); + let (mut prim_list, surface_index, apply_local_clip_rect, world_culling_rect, is_composite) = { + let pic = &mut self.pictures[pic_index.0]; + let mut world_culling_rect = *world_culling_rect; + + let prim_list = mem::replace(&mut pic.prim_list, PrimitiveList::empty()); + let (surface_index, is_composite) = match pic.raster_config { + Some(ref raster_config) => (raster_config.surface_index, true), + None => (parent_surface_index, false) + }; + + match pic.raster_config { + Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { .. }, .. }) => { + let mut tile_cache = pic.tile_cache.take().unwrap(); + debug_assert!(frame_state.tile_cache.is_none()); + + // If we have a tile cache for this picture, see if any of the + // relative transforms have changed, which means we need to + // re-map the dependencies of any child primitives. + world_culling_rect = tile_cache.pre_update( + layout_rect_as_picture_rect(&pic.estimated_local_rect), + surface_index, + frame_context, + frame_state, + ); + + // Push a new surface, supplying the list of clips that should be + // ignored, since they are handled by clipping when drawing this surface. + frame_state.push_surface( + surface_index, + &tile_cache.shared_clips, + frame_context.spatial_tree, + ); + frame_state.tile_cache = Some(tile_cache); + } + _ => { + if is_composite { + frame_state.push_surface( + surface_index, + &[], + frame_context.spatial_tree, + ); + } + } + } + + (prim_list, surface_index, pic.apply_local_clip_rect, world_culling_rect, is_composite) + }; + + let surface = &frame_context.surfaces[surface_index.0 as usize]; + + let mut map_local_to_surface = surface + .map_local_to_surface + .clone(); + + let map_surface_to_world = SpaceMapper::new_with_target( + ROOT_SPATIAL_NODE_INDEX, + surface.surface_spatial_node_index, + frame_context.global_screen_world_rect, + frame_context.spatial_tree, + ); + + let mut surface_rect = PictureRect::zero(); + + for cluster in &mut prim_list.clusters { + profile_scope!("cluster"); + // Get the cluster and see if is visible + if !cluster.flags.contains(ClusterFlags::IS_VISIBLE) { + // Each prim instance must have reset called each frame, to clear + // indices into various scratch buffers. If this doesn't occur, + // the primitive may incorrectly be considered visible, which can + // cause unexpected conditions to occur later during the frame. + // Primitive instances are normally reset in the main loop below, + // but we must also reset them in the rare case that the cluster + // visibility has changed (due to an invalid transform and/or + // backface visibility changing for this cluster). + // TODO(gw): This is difficult to test for in CI - as a follow up, + // we should add a debug flag that validates the prim + // instance is always reset every frame to catch similar + // issues in future. + for prim_instance in &mut cluster.prim_instances { + prim_instance.reset(); + } + continue; + } + + map_local_to_surface.set_target_spatial_node( + cluster.spatial_node_index, + frame_context.spatial_tree, + ); + + for prim_instance in &mut cluster.prim_instances { + prim_instance.reset(); + + if prim_instance.is_chased() { + #[cfg(debug_assertions)] // needed for ".id" part + println!("\tpreparing {:?} in {:?}", prim_instance.id, pic_index); + println!("\t{:?}", prim_instance.kind); + } + + let (is_passthrough, prim_local_rect, prim_shadowed_rect) = match prim_instance.kind { + PrimitiveInstanceKind::Picture { pic_index, .. } => { + if !self.pictures[pic_index.0].is_visible() { + continue; + } + + frame_state.clip_chain_stack.push_clip( + prim_instance.clip_chain_id, + frame_state.clip_store, + ); + + let pic_surface_rect = self.update_visibility( + pic_index, + surface_index, + &world_culling_rect, + frame_context, + frame_state, + ); + + frame_state.clip_chain_stack.pop_clip(); + + let pic = &self.pictures[pic_index.0]; + + if prim_instance.is_chased() && pic.estimated_local_rect != pic.precise_local_rect { + println!("\testimate {:?} adjusted to {:?}", pic.estimated_local_rect, pic.precise_local_rect); + } + + let mut shadow_rect = pic.precise_local_rect; + match pic.raster_config { + Some(ref rc) => match rc.composite_mode { + // If we have a drop shadow filter, we also need to include the shadow in + // our shadowed local rect for the purpose of calculating the size of the + // picture. + PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => { + for shadow in shadows { + shadow_rect = shadow_rect.union(&pic.precise_local_rect.translate(shadow.offset)); + } + } + _ => {} + } + None => { + // If the primitive does not have its own raster config, we need to + // propogate the surface rect calculation to the parent. + if let Some(ref rect) = pic_surface_rect { + surface_rect = surface_rect.union(rect); + } + } + } + + (pic.raster_config.is_none(), pic.precise_local_rect, shadow_rect) + } + _ => { + let prim_data = &frame_state.data_stores.as_common_data(&prim_instance); + + (false, prim_data.prim_rect, prim_data.prim_rect) + } + }; + + if is_passthrough { + let vis_index = PrimitiveVisibilityIndex(frame_state.scratch.prim_info.len() as u32); + + frame_state.scratch.prim_info.push( + PrimitiveVisibility { + clipped_world_rect: WorldRect::max_rect(), + clip_chain: ClipChainInstance::empty(), + clip_task_index: ClipTaskIndex::INVALID, + combined_local_clip_rect: LayoutRect::zero(), + visibility_mask: PrimitiveVisibilityMask::empty(), + flags: PrimitiveVisibilityFlags::empty(), + } + ); + + prim_instance.visibility_info = vis_index; + } else { + if prim_local_rect.size.width <= 0.0 || prim_local_rect.size.height <= 0.0 { + if prim_instance.is_chased() { + println!("\tculled for zero local rectangle"); + } + continue; + } + + // Inflate the local rect for this primitive by the inflation factor of + // the picture context and include the shadow offset. This ensures that + // even if the primitive itself is not visible, any effects from the + // blur radius or shadow will be correctly taken into account. + let inflation_factor = surface.inflation_factor; + let local_rect = prim_shadowed_rect + .inflate(inflation_factor, inflation_factor) + .intersection(&prim_instance.local_clip_rect); + let local_rect = match local_rect { + Some(local_rect) => local_rect, + None => { + if prim_instance.is_chased() { + println!("\tculled for being out of the local clip rectangle: {:?}", + prim_instance.local_clip_rect); + } + continue; + } + }; + + // Include the clip chain for this primitive in the current stack. + frame_state.clip_chain_stack.push_clip( + prim_instance.clip_chain_id, + frame_state.clip_store, + ); + + frame_state.clip_store.set_active_clips( + prim_instance.local_clip_rect, + cluster.spatial_node_index, + frame_state.clip_chain_stack.current_clips_array(), + &frame_context.spatial_tree, + &frame_state.data_stores.clip, + ); + + let clip_chain = frame_state + .clip_store + .build_clip_chain_instance( + local_rect, + &map_local_to_surface, + &map_surface_to_world, + &frame_context.spatial_tree, + frame_state.gpu_cache, + frame_state.resource_cache, + surface.device_pixel_scale, + &world_culling_rect, + &mut frame_state.data_stores.clip, + true, + prim_instance.is_chased(), + ); + + // Primitive visibility flags default to empty, but may be supplied + // by the `update_prim_dependencies` method below when picture caching + // is active. + let mut vis_flags = PrimitiveVisibilityFlags::empty(); + + if let Some(ref mut tile_cache) = frame_state.tile_cache { + // TODO(gw): Refactor how tile_cache is stored in frame_state + // so that we can pass frame_state directly to + // update_prim_dependencies, rather than splitting borrows. + match tile_cache.update_prim_dependencies( + prim_instance, + cluster.spatial_node_index, + clip_chain.as_ref(), + prim_local_rect, + frame_context, + frame_state.data_stores, + frame_state.clip_store, + &self.pictures, + frame_state.resource_cache, + &self.opacity_bindings, + &self.color_bindings, + &self.images, + &frame_state.surface_stack, + &mut frame_state.composite_state, + ) { + Some(flags) => { + vis_flags = flags; + } + None => { + prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID; + // Ensure the primitive clip is popped - perhaps we can use + // some kind of scope to do this automatically in future. + frame_state.clip_chain_stack.pop_clip(); + continue; + } + } + } + + // Ensure the primitive clip is popped + frame_state.clip_chain_stack.pop_clip(); + + let clip_chain = match clip_chain { + Some(clip_chain) => clip_chain, + None => { + if prim_instance.is_chased() { + println!("\tunable to build the clip chain, skipping"); + } + prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID; + continue; + } + }; + + if prim_instance.is_chased() { + println!("\teffective clip chain from {:?} {}", + clip_chain.clips_range, + if apply_local_clip_rect { "(applied)" } else { "" }, + ); + println!("\tpicture rect {:?} @{:?}", + clip_chain.pic_clip_rect, + clip_chain.pic_spatial_node_index, + ); + } + + // Check if the clip bounding rect (in pic space) is visible on screen + // This includes both the prim bounding rect + local prim clip rect! + let world_rect = match map_surface_to_world.map(&clip_chain.pic_clip_rect) { + Some(world_rect) => world_rect, + None => { + continue; + } + }; + + let clipped_world_rect = match world_rect.intersection(&world_culling_rect) { + Some(rect) => rect, + None => { + continue; + } + }; + + let combined_local_clip_rect = if apply_local_clip_rect { + clip_chain.local_clip_rect + } else { + prim_instance.local_clip_rect + }; + + if combined_local_clip_rect.size.is_empty() { + debug_assert!(combined_local_clip_rect.size.width >= 0.0 && + combined_local_clip_rect.size.height >= 0.0); + if prim_instance.is_chased() { + println!("\tculled for zero local clip rectangle"); + } + prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID; + continue; + } + + // Include the visible area for primitive, including any shadows, in + // the area affected by the surface. + match combined_local_clip_rect.intersection(&local_rect) { + Some(visible_rect) => { + if let Some(rect) = map_local_to_surface.map(&visible_rect) { + surface_rect = surface_rect.union(&rect); + } + } + None => { + if prim_instance.is_chased() { + println!("\tculled for zero visible rectangle"); + } + prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID; + continue; + } + } + + // When the debug display is enabled, paint a colored rectangle around each + // primitive. + if frame_context.debug_flags.contains(::api::DebugFlags::PRIMITIVE_DBG) { + let debug_color = match prim_instance.kind { + PrimitiveInstanceKind::Picture { .. } => ColorF::TRANSPARENT, + PrimitiveInstanceKind::TextRun { .. } => debug_colors::RED, + PrimitiveInstanceKind::LineDecoration { .. } => debug_colors::PURPLE, + PrimitiveInstanceKind::NormalBorder { .. } | + PrimitiveInstanceKind::ImageBorder { .. } => debug_colors::ORANGE, + PrimitiveInstanceKind::Rectangle { .. } => ColorF { r: 0.8, g: 0.8, b: 0.8, a: 0.5 }, + PrimitiveInstanceKind::YuvImage { .. } => debug_colors::BLUE, + PrimitiveInstanceKind::Image { .. } => debug_colors::BLUE, + PrimitiveInstanceKind::LinearGradient { .. } => debug_colors::PINK, + PrimitiveInstanceKind::RadialGradient { .. } => debug_colors::PINK, + PrimitiveInstanceKind::ConicGradient { .. } => debug_colors::PINK, + PrimitiveInstanceKind::Clear { .. } => debug_colors::CYAN, + PrimitiveInstanceKind::Backdrop { .. } => debug_colors::MEDIUMAQUAMARINE, + }; + if debug_color.a != 0.0 { + let debug_rect = clipped_world_rect * frame_context.global_device_pixel_scale; + frame_state.scratch.push_debug_rect(debug_rect, debug_color, debug_color.scale_alpha(0.5)); + } + } else if frame_context.debug_flags.contains(::api::DebugFlags::OBSCURE_IMAGES) { + let is_image = matches!( + prim_instance.kind, + PrimitiveInstanceKind::Image { .. } | PrimitiveInstanceKind::YuvImage { .. } + ); + if is_image { + // We allow "small" images, since they're generally UI elements. + let rect = clipped_world_rect * frame_context.global_device_pixel_scale; + if rect.size.width > 70.0 && rect.size.height > 70.0 { + frame_state.scratch.push_debug_rect(rect, debug_colors::PURPLE, debug_colors::PURPLE); + } + } + } + + let vis_index = PrimitiveVisibilityIndex(frame_state.scratch.prim_info.len() as u32); + if prim_instance.is_chased() { + println!("\tvisible {:?} with {:?}", vis_index, combined_local_clip_rect); + } + + frame_state.scratch.prim_info.push( + PrimitiveVisibility { + clipped_world_rect, + clip_chain, + clip_task_index: ClipTaskIndex::INVALID, + combined_local_clip_rect, + visibility_mask: PrimitiveVisibilityMask::empty(), + flags: vis_flags, + } + ); + + prim_instance.visibility_info = vis_index; + + self.request_resources_for_prim( + prim_instance, + cluster.spatial_node_index, + clipped_world_rect, + frame_context, + frame_state, + ); + } + } + } + + // Similar to above, pop either the clip chain or root entry off the current clip stack. + if is_composite { + frame_state.pop_surface(); + } + + let pic = &mut self.pictures[pic_index.0]; + pic.prim_list = prim_list; + + // If the local rect changed (due to transforms in child primitives) then + // invalidate the GPU cache location to re-upload the new local rect + // and stretch size. Drop shadow filters also depend on the local rect + // size for the extra GPU cache data handle. + // TODO(gw): In future, if we support specifying a flag which gets the + // stretch size from the segment rect in the shaders, we can + // remove this invalidation here completely. + if let Some(ref rc) = pic.raster_config { + // Inflate the local bounding rect if required by the filter effect. + // This inflaction factor is to be applied to the surface itself. + if pic.options.inflate_if_required { + // The picture's local rect is calculated as the union of the + // snapped primitive rects, which should result in a snapped + // local rect, unless it was inflated. This is also done during + // surface configuration when calculating the picture's + // estimated local rect. + let snap_pic_to_raster = SpaceSnapper::new_with_target( + surface.raster_spatial_node_index, + pic.spatial_node_index, + surface.device_pixel_scale, + frame_context.spatial_tree, + ); + + surface_rect = rc.composite_mode.inflate_picture_rect(surface_rect, surface.scale_factors); + surface_rect = snap_pic_to_raster.snap_rect(&surface_rect); + } + + // Layout space for the picture is picture space from the + // perspective of its child primitives. + let pic_local_rect = surface_rect * Scale::new(1.0); + if pic.precise_local_rect != pic_local_rect { + match rc.composite_mode { + PictureCompositeMode::Filter(Filter::DropShadows(..)) => { + for handle in &pic.extra_gpu_data_handles { + frame_state.gpu_cache.invalidate(handle); + } + } + _ => {} + } + // Invalidate any segments built for this picture, since the local + // rect has changed. + pic.segments_are_valid = false; + pic.precise_local_rect = pic_local_rect; + } + + if let PictureCompositeMode::TileCache { .. } = rc.composite_mode { + let mut tile_cache = frame_state.tile_cache.take().unwrap(); + + // Build the dirty region(s) for this tile cache. + tile_cache.post_update( + frame_context, + frame_state, + ); + + pic.tile_cache = Some(tile_cache); + } + + None + } else { + let parent_surface = &frame_context.surfaces[parent_surface_index.0 as usize]; + let map_surface_to_parent_surface = SpaceMapper::new_with_target( + parent_surface.surface_spatial_node_index, + surface.surface_spatial_node_index, + PictureRect::max_rect(), + frame_context.spatial_tree, + ); + map_surface_to_parent_surface.map(&surface_rect) + } + } + + fn request_resources_for_prim( + &mut self, + prim_instance: &mut PrimitiveInstance, + prim_spatial_node_index: SpatialNodeIndex, + prim_world_rect: WorldRect, + frame_context: &FrameVisibilityContext, + frame_state: &mut FrameVisibilityState, + ) { + profile_scope!("request_resources_for_prim"); + match prim_instance.kind { + PrimitiveInstanceKind::TextRun { .. } => { + // Text runs can't request resources early here, as we don't + // know until TileCache::post_update() whether we are drawing + // on an opaque surface. + // TODO(gw): We might be able to detect simple cases of this earlier, + // during the picture traversal. But it's probably not worth it? + } + PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => { + let prim_data = &mut frame_state.data_stores.image[data_handle]; + let common_data = &mut prim_data.common; + let image_data = &mut prim_data.kind; + let image_instance = &mut self.images[image_instance_index]; + + let image_properties = frame_state + .resource_cache + .get_image_properties(image_data.key); + + let request = ImageRequest { + key: image_data.key, + rendering: image_data.image_rendering, + tile: None, + }; + + match image_properties { + Some(ImageProperties { tiling: None, .. }) => { + + frame_state.resource_cache.request_image( + request, + frame_state.gpu_cache, + ); + } + Some(ImageProperties { tiling: Some(tile_size), visible_rect, .. }) => { + image_instance.visible_tiles.clear(); + // TODO: rename the blob's visible_rect into something that doesn't conflict + // with the terminology we use during culling since it's not really the same + // thing. + let active_rect = visible_rect; + + // Tighten the clip rect because decomposing the repeated image can + // produce primitives that are partially covering the original image + // rect and we want to clip these extra parts out. + let prim_info = &frame_state.scratch.prim_info[prim_instance.visibility_info.0 as usize]; + let tight_clip_rect = prim_info + .combined_local_clip_rect + .intersection(&common_data.prim_rect).unwrap(); + image_instance.tight_local_clip_rect = tight_clip_rect; + + let map_local_to_world = SpaceMapper::new_with_target( + ROOT_SPATIAL_NODE_INDEX, + prim_spatial_node_index, + frame_context.global_screen_world_rect, + frame_context.spatial_tree, + ); + + let visible_rect = compute_conservative_visible_rect( + &tight_clip_rect, + prim_world_rect, + &map_local_to_world, + ); + + let base_edge_flags = edge_flags_for_tile_spacing(&image_data.tile_spacing); + + let stride = image_data.stretch_size + image_data.tile_spacing; + + // We are performing the decomposition on the CPU here, no need to + // have it in the shader. + common_data.may_need_repetition = false; + + let repetitions = image_tiling::repetitions( + &common_data.prim_rect, + &visible_rect, + stride, + ); + + for Repetition { origin, edge_flags } in repetitions { + let edge_flags = base_edge_flags | edge_flags; + + let layout_image_rect = LayoutRect { + origin, + size: image_data.stretch_size, + }; + + let tiles = image_tiling::tiles( + &layout_image_rect, + &visible_rect, + &active_rect, + tile_size as i32, + ); + + for tile in tiles { + frame_state.resource_cache.request_image( + request.with_tile(tile.offset), + frame_state.gpu_cache, + ); + + image_instance.visible_tiles.push(VisibleImageTile { + tile_offset: tile.offset, + edge_flags: tile.edge_flags & edge_flags, + local_rect: tile.rect, + local_clip_rect: tight_clip_rect, + }); + } + } + + if image_instance.visible_tiles.is_empty() { + // Mark as invisible + prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID; + } + } + None => {} + } + } + PrimitiveInstanceKind::ImageBorder { data_handle, .. } => { + let prim_data = &mut frame_state.data_stores.image_border[data_handle]; + prim_data.kind.request_resources( + frame_state.resource_cache, + frame_state.gpu_cache, + ); + } + PrimitiveInstanceKind::YuvImage { data_handle, .. } => { + let prim_data = &mut frame_state.data_stores.yuv_image[data_handle]; + prim_data.kind.request_resources( + frame_state.resource_cache, + frame_state.gpu_cache, + ); + } + _ => {} + } + } + + pub fn get_opacity_binding( + &self, + opacity_binding_index: OpacityBindingIndex, + ) -> f32 { + if opacity_binding_index == OpacityBindingIndex::INVALID { + 1.0 + } else { + self.opacity_bindings[opacity_binding_index].current + } + } + + // Internal method that retrieves the primitive index of a primitive + // that can be the target for collapsing parent opacity filters into. + fn get_opacity_collapse_prim( + &self, + pic_index: PictureIndex, + ) -> Option { + let pic = &self.pictures[pic_index.0]; + + // We can only collapse opacity if there is a single primitive, otherwise + // the opacity needs to be applied to the primitives as a group. + if pic.prim_list.clusters.len() != 1 { + return None; + } + + let cluster = &pic.prim_list.clusters[0]; + if cluster.prim_instances.len() != 1 { + return None; + } + + let prim_instance = &cluster.prim_instances[0]; + + // For now, we only support opacity collapse on solid rects and images. + // This covers the most common types of opacity filters that can be + // handled by this optimization. In the future, we can easily extend + // this to other primitives, such as text runs and gradients. + match prim_instance.kind { + // If we find a single rect or image, we can use that + // as the primitive to collapse the opacity into. + PrimitiveInstanceKind::Rectangle { .. } | + PrimitiveInstanceKind::Image { .. } => { + return Some(pic_index); + } + PrimitiveInstanceKind::Clear { .. } | + PrimitiveInstanceKind::TextRun { .. } | + PrimitiveInstanceKind::NormalBorder { .. } | + PrimitiveInstanceKind::ImageBorder { .. } | + PrimitiveInstanceKind::YuvImage { .. } | + PrimitiveInstanceKind::LinearGradient { .. } | + PrimitiveInstanceKind::RadialGradient { .. } | + PrimitiveInstanceKind::ConicGradient { .. } | + PrimitiveInstanceKind::LineDecoration { .. } | + PrimitiveInstanceKind::Backdrop { .. } => { + // These prims don't support opacity collapse + } + PrimitiveInstanceKind::Picture { pic_index, .. } => { + let pic = &self.pictures[pic_index.0]; + + // If we encounter a picture that is a pass-through + // (i.e. no composite mode), then we can recurse into + // that to try and find a primitive to collapse to. + if pic.requested_composite_mode.is_none() { + return self.get_opacity_collapse_prim(pic_index); + } + } + } + + None + } + + // Apply any optimizations to drawing this picture. Currently, + // we just support collapsing pictures with an opacity filter + // by pushing that opacity value into the color of a primitive + // if that picture contains one compatible primitive. + pub fn optimize_picture_if_possible( + &mut self, + pic_index: PictureIndex, + ) { + // Only handle opacity filters for now. + let binding = match self.pictures[pic_index.0].requested_composite_mode { + Some(PictureCompositeMode::Filter(Filter::Opacity(binding, _))) => { + binding + } + _ => { + return; + } + }; + + // See if this picture contains a single primitive that supports + // opacity collapse. + match self.get_opacity_collapse_prim(pic_index) { + Some(pic_index) => { + let pic = &mut self.pictures[pic_index.0]; + let prim_instance = &mut pic.prim_list.clusters[0].prim_instances[0]; + match prim_instance.kind { + PrimitiveInstanceKind::Image { image_instance_index, .. } => { + let image_instance = &mut self.images[image_instance_index]; + // By this point, we know we should only have found a primitive + // that supports opacity collapse. + if image_instance.opacity_binding_index == OpacityBindingIndex::INVALID { + image_instance.opacity_binding_index = self.opacity_bindings.push(OpacityBinding::new()); + } + let opacity_binding = &mut self.opacity_bindings[image_instance.opacity_binding_index]; + opacity_binding.push(binding); + } + PrimitiveInstanceKind::Rectangle { ref mut opacity_binding_index, .. } => { + // By this point, we know we should only have found a primitive + // that supports opacity collapse. + if *opacity_binding_index == OpacityBindingIndex::INVALID { + *opacity_binding_index = self.opacity_bindings.push(OpacityBinding::new()); + } + let opacity_binding = &mut self.opacity_bindings[*opacity_binding_index]; + opacity_binding.push(binding); + } + _ => { + unreachable!(); + } + } + } + None => { + return; + } + } + + // The opacity filter has been collapsed, so mark this picture + // as a pass though. This means it will no longer allocate an + // intermediate surface or incur an extra blend / blit. Instead, + // the collapsed primitive will be drawn directly into the + // parent picture. + self.pictures[pic_index.0].requested_composite_mode = None; + } + + fn prepare_prim_for_render( + &mut self, + prim_instance: &mut PrimitiveInstance, + prim_spatial_node_index: SpatialNodeIndex, + pic_context: &PictureContext, + pic_state: &mut PictureState, + frame_context: &FrameBuildingContext, + frame_state: &mut FrameBuildingState, + plane_split_anchor: PlaneSplitAnchor, + data_stores: &mut DataStores, + scratch: &mut PrimitiveScratchBuffer, + tile_cache_log: &mut TileCacheLogger, + ) -> bool { + profile_scope!("prepare_prim_for_render"); + // If we have dependencies, we need to prepare them first, in order + // to know the actual rect of this primitive. + // For example, scrolling may affect the location of an item in + // local space, which may force us to render this item on a larger + // picture target, if being composited. + let pic_info = { + match prim_instance.kind { + PrimitiveInstanceKind::Picture { pic_index ,.. } => { + let pic = &mut self.pictures[pic_index.0]; + + let clipped_prim_bounding_rect = scratch + .prim_info[prim_instance.visibility_info.0 as usize] + .clipped_world_rect; + + match pic.take_context( + pic_index, + clipped_prim_bounding_rect, + pic_context.surface_spatial_node_index, + pic_context.raster_spatial_node_index, + pic_context.surface_index, + &pic_context.subpixel_mode, + frame_state, + frame_context, + scratch, + tile_cache_log, + ) { + Some(info) => Some(info), + None => { + if prim_instance.is_chased() { + println!("\tculled for carrying an invisible composite filter"); + } + + prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID; + + return false; + } + } + } + PrimitiveInstanceKind::TextRun { .. } | + PrimitiveInstanceKind::Rectangle { .. } | + PrimitiveInstanceKind::LineDecoration { .. } | + PrimitiveInstanceKind::NormalBorder { .. } | + PrimitiveInstanceKind::ImageBorder { .. } | + PrimitiveInstanceKind::YuvImage { .. } | + PrimitiveInstanceKind::Image { .. } | + PrimitiveInstanceKind::LinearGradient { .. } | + PrimitiveInstanceKind::RadialGradient { .. } | + PrimitiveInstanceKind::ConicGradient { .. } | + PrimitiveInstanceKind::Clear { .. } | + PrimitiveInstanceKind::Backdrop { .. } => { + None + } + } + }; + + let is_passthrough = match pic_info { + Some((pic_context_for_children, mut pic_state_for_children, mut prim_list)) => { + let is_passthrough = pic_context_for_children.is_passthrough; + + self.prepare_primitives( + &mut prim_list, + &pic_context_for_children, + &mut pic_state_for_children, + frame_context, + frame_state, + data_stores, + scratch, + tile_cache_log, + ); + + // Restore the dependencies (borrow check dance) + self.pictures[pic_context_for_children.pic_index.0] + .restore_context( + pic_context.surface_index, + prim_list, + pic_context_for_children, + pic_state_for_children, + frame_state, + ); + + is_passthrough + } + None => { + false + } + }; + + let prim_rect = data_stores.get_local_prim_rect( + prim_instance, + self, + ); + + if !is_passthrough { + prim_instance.update_clip_task( + &prim_rect.origin, + prim_spatial_node_index, + pic_context.raster_spatial_node_index, + pic_context, + pic_state, + frame_context, + frame_state, + self, + data_stores, + scratch, + ); + + if prim_instance.is_chased() { + println!("\tconsidered visible and ready with local pos {:?}", prim_rect.origin); + } + } + + #[cfg(debug_assertions)] + { + prim_instance.prepared_frame_id = frame_state.render_tasks.frame_id(); + } + + self.prepare_interned_prim_for_render( + prim_instance, + prim_spatial_node_index, + plane_split_anchor, + pic_context, + pic_state, + frame_context, + frame_state, + data_stores, + scratch, + ); + + true + } + + pub fn prepare_primitives( + &mut self, + prim_list: &mut PrimitiveList, + pic_context: &PictureContext, + pic_state: &mut PictureState, + frame_context: &FrameBuildingContext, + frame_state: &mut FrameBuildingState, + data_stores: &mut DataStores, + scratch: &mut PrimitiveScratchBuffer, + tile_cache_log: &mut TileCacheLogger, + ) { + profile_scope!("prepare_primitives"); + for (cluster_index, cluster) in prim_list.clusters.iter_mut().enumerate() { + profile_scope!("cluster"); + pic_state.map_local_to_pic.set_target_spatial_node( + cluster.spatial_node_index, + frame_context.spatial_tree, + ); + + for (prim_instance_index, prim_instance) in cluster.prim_instances.iter_mut().enumerate() { + if prim_instance.visibility_info == PrimitiveVisibilityIndex::INVALID { + continue; + } + + // The original clipped world rect was calculated during the initial visibility pass. + // However, it's possible that the dirty rect has got smaller, if tiles were not + // dirty. Intersecting with the dirty rect here eliminates preparing any primitives + // outside the dirty rect, and reduces the size of any off-screen surface allocations + // for clip masks / render tasks that we make. + { + let visibility_info = &mut scratch.prim_info[prim_instance.visibility_info.0 as usize]; + let dirty_region = frame_state.current_dirty_region(); + + for dirty_region in &dirty_region.dirty_rects { + if visibility_info.clipped_world_rect.intersects(&dirty_region.world_rect) { + visibility_info.visibility_mask.include(dirty_region.visibility_mask); + } + } + + if visibility_info.visibility_mask.is_empty() { + prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID; + continue; + } + } + + let plane_split_anchor = PlaneSplitAnchor::new(cluster_index, prim_instance_index); + + if self.prepare_prim_for_render( + prim_instance, + cluster.spatial_node_index, + pic_context, + pic_state, + frame_context, + frame_state, + plane_split_anchor, + data_stores, + scratch, + tile_cache_log, + ) { + frame_state.profile_counters.visible_primitives.inc(); + } + } + } + } + + /// Prepare an interned primitive for rendering, by requesting + /// resources, render tasks etc. This is equivalent to the + /// prepare_prim_for_render_inner call for old style primitives. + fn prepare_interned_prim_for_render( + &mut self, + prim_instance: &mut PrimitiveInstance, + prim_spatial_node_index: SpatialNodeIndex, + plane_split_anchor: PlaneSplitAnchor, + pic_context: &PictureContext, + pic_state: &mut PictureState, + frame_context: &FrameBuildingContext, + frame_state: &mut FrameBuildingState, + data_stores: &mut DataStores, + scratch: &mut PrimitiveScratchBuffer, + ) { + let is_chased = prim_instance.is_chased(); + let device_pixel_scale = frame_state.surfaces[pic_context.surface_index.0].device_pixel_scale; + + match &mut prim_instance.kind { + PrimitiveInstanceKind::LineDecoration { data_handle, ref mut cache_handle, .. } => { + profile_scope!("LineDecoration"); + let prim_data = &mut data_stores.line_decoration[*data_handle]; + let common_data = &mut prim_data.common; + let line_dec_data = &mut prim_data.kind; + + // Update the template this instane references, which may refresh the GPU + // cache with any shared template data. + line_dec_data.update(common_data, frame_state); + + // Work out the device pixel size to be used to cache this line decoration. + if is_chased { + println!("\tline decoration key={:?}", line_dec_data.cache_key); + } + + // If we have a cache key, it's a wavy / dashed / dotted line. Otherwise, it's + // a simple solid line. + if let Some(cache_key) = line_dec_data.cache_key.as_ref() { + // TODO(gw): Do we ever need / want to support scales for text decorations + // based on the current transform? + let scale_factor = Scale::new(1.0) * device_pixel_scale; + let mut task_size = (LayoutSize::from_au(cache_key.size) * scale_factor).ceil().to_i32(); + if task_size.width > MAX_LINE_DECORATION_RESOLUTION as i32 || + task_size.height > MAX_LINE_DECORATION_RESOLUTION as i32 { + let max_extent = cmp::max(task_size.width, task_size.height); + let task_scale_factor = Scale::new(MAX_LINE_DECORATION_RESOLUTION as f32 / max_extent as f32); + task_size = (LayoutSize::from_au(cache_key.size) * scale_factor * task_scale_factor) + .ceil().to_i32(); + } + + // Request a pre-rendered image task. + // TODO(gw): This match is a bit untidy, but it should disappear completely + // once the prepare_prims and batching are unified. When that + // happens, we can use the cache handle immediately, and not need + // to temporarily store it in the primitive instance. + *cache_handle = Some(frame_state.resource_cache.request_render_task( + RenderTaskCacheKey { + size: task_size, + kind: RenderTaskCacheKeyKind::LineDecoration(cache_key.clone()), + }, + frame_state.gpu_cache, + frame_state.render_tasks, + None, + false, + |render_tasks| { + render_tasks.add().init(RenderTask::new_line_decoration( + task_size, + cache_key.style, + cache_key.orientation, + cache_key.wavy_line_thickness.to_f32_px(), + LayoutSize::from_au(cache_key.size), + )) + } + )); + } + } + PrimitiveInstanceKind::TextRun { run_index, data_handle, .. } => { + profile_scope!("TextRun"); + let prim_data = &mut data_stores.text_run[*data_handle]; + let run = &mut self.text_runs[*run_index]; + + prim_data.common.may_need_repetition = false; + + // The glyph transform has to match `glyph_transform` in "ps_text_run" shader. + // It's relative to the rasterizing space of a glyph. + let transform = frame_context.spatial_tree + .get_relative_transform( + prim_spatial_node_index, + pic_context.raster_spatial_node_index, + ) + .into_fast_transform(); + let prim_offset = prim_data.common.prim_rect.origin.to_vector() - run.reference_frame_relative_offset; + + let pic = &self.pictures[pic_context.pic_index.0]; + let raster_space = pic.get_raster_space(frame_context.spatial_tree); + let surface = &frame_state.surfaces[pic_context.surface_index.0]; + let prim_info = &scratch.prim_info[prim_instance.visibility_info.0 as usize]; + let root_scaling_factor = match pic.raster_config { + Some(ref raster_config) => raster_config.root_scaling_factor, + None => 1.0 + }; + + run.request_resources( + prim_offset, + prim_info.clip_chain.pic_clip_rect, + &prim_data.font, + &prim_data.glyphs, + &transform.to_transform().with_destination::<_>(), + surface, + prim_spatial_node_index, + raster_space, + root_scaling_factor, + &pic_context.subpixel_mode, + frame_state.resource_cache, + frame_state.gpu_cache, + frame_state.render_tasks, + frame_context.spatial_tree, + scratch, + ); + + // Update the template this instane references, which may refresh the GPU + // cache with any shared template data. + prim_data.update(frame_state); + } + PrimitiveInstanceKind::Clear { data_handle, .. } => { + profile_scope!("Clear"); + let prim_data = &mut data_stores.prim[*data_handle]; + + prim_data.common.may_need_repetition = false; + + // Update the template this instane references, which may refresh the GPU + // cache with any shared template data. + prim_data.update(frame_state, frame_context.scene_properties); + } + PrimitiveInstanceKind::NormalBorder { data_handle, ref mut cache_handles, .. } => { + profile_scope!("NormalBorder"); + let prim_data = &mut data_stores.normal_border[*data_handle]; + let common_data = &mut prim_data.common; + let border_data = &mut prim_data.kind; + + common_data.may_need_repetition = + matches!(border_data.border.top.style, BorderStyle::Dotted | BorderStyle::Dashed) || + matches!(border_data.border.right.style, BorderStyle::Dotted | BorderStyle::Dashed) || + matches!(border_data.border.bottom.style, BorderStyle::Dotted | BorderStyle::Dashed) || + matches!(border_data.border.left.style, BorderStyle::Dotted | BorderStyle::Dashed); + + + // Update the template this instance references, which may refresh the GPU + // cache with any shared template data. + border_data.update(common_data, frame_state); + + // TODO(gw): For now, the scale factors to rasterize borders at are + // based on the true world transform of the primitive. When + // raster roots with local scale are supported in future, + // that will need to be accounted for here. + let scale = frame_context + .spatial_tree + .get_world_transform(prim_spatial_node_index) + .scale_factors(); + + // Scale factors are normalized to a power of 2 to reduce the number of + // resolution changes. + // For frames with a changing scale transform round scale factors up to + // nearest power-of-2 boundary so that we don't keep having to redraw + // the content as it scales up and down. Rounding up to nearest + // power-of-2 boundary ensures we never scale up, only down --- avoiding + // jaggies. It also ensures we never scale down by more than a factor of + // 2, avoiding bad downscaling quality. + let scale_width = clamp_to_scale_factor(scale.0, false); + let scale_height = clamp_to_scale_factor(scale.1, false); + // Pick the maximum dimension as scale + let world_scale = LayoutToWorldScale::new(scale_width.max(scale_height)); + let mut scale = world_scale * device_pixel_scale; + let max_scale = get_max_scale_for_border(border_data); + scale.0 = scale.0.min(max_scale.0); + + // For each edge and corner, request the render task by content key + // from the render task cache. This ensures that the render task for + // this segment will be available for batching later in the frame. + let mut handles: SmallVec<[RenderTaskCacheEntryHandle; 8]> = SmallVec::new(); + + for segment in &border_data.border_segments { + // Update the cache key device size based on requested scale. + let cache_size = to_cache_size(segment.local_task_size * scale); + let cache_key = RenderTaskCacheKey { + kind: RenderTaskCacheKeyKind::BorderSegment(segment.cache_key.clone()), + size: cache_size, + }; + + handles.push(frame_state.resource_cache.request_render_task( + cache_key, + frame_state.gpu_cache, + frame_state.render_tasks, + None, + false, // TODO(gw): We don't calculate opacity for borders yet! + |render_tasks| { + render_tasks.add().init(RenderTask::new_border_segment( + cache_size, + build_border_instances( + &segment.cache_key, + cache_size, + &border_data.border, + scale, + ), + )) + } + )); + } + + *cache_handles = scratch + .border_cache_handles + .extend(handles); + } + PrimitiveInstanceKind::ImageBorder { data_handle, .. } => { + profile_scope!("ImageBorder"); + let prim_data = &mut data_stores.image_border[*data_handle]; + + // TODO: get access to the ninepatch and to check whwther we need support + // for repetitions in the shader. + + // Update the template this instane references, which may refresh the GPU + // cache with any shared template data. + prim_data.kind.update(&mut prim_data.common, frame_state); + } + PrimitiveInstanceKind::Rectangle { data_handle, segment_instance_index, opacity_binding_index, color_binding_index, .. } => { + profile_scope!("Rectangle"); + let prim_data = &mut data_stores.prim[*data_handle]; + prim_data.common.may_need_repetition = false; + + if *color_binding_index != ColorBindingIndex::INVALID { + match self.color_bindings[*color_binding_index] { + PropertyBinding::Binding(..) => { + // We explicitly invalidate the gpu cache + // if the color is animating. + let gpu_cache_handle = + if *segment_instance_index == SegmentInstanceIndex::INVALID { + None + } else if *segment_instance_index == SegmentInstanceIndex::UNUSED { + Some(&prim_data.common.gpu_cache_handle) + } else { + Some(&scratch.segment_instances[*segment_instance_index].gpu_cache_handle) + }; + if let Some(gpu_cache_handle) = gpu_cache_handle { + frame_state.gpu_cache.invalidate(gpu_cache_handle); + } + } + PropertyBinding::Value(..) => {}, + } + } + + // Update the template this instane references, which may refresh the GPU + // cache with any shared template data. + prim_data.update( + frame_state, + frame_context.scene_properties, + ); + + update_opacity_binding( + &mut self.opacity_bindings, + *opacity_binding_index, + frame_context.scene_properties, + ); + + write_segment( + *segment_instance_index, + frame_state, + &mut scratch.segments, + &mut scratch.segment_instances, + |request| { + prim_data.kind.write_prim_gpu_blocks( + request, + frame_context.scene_properties, + ); + } + ); + } + PrimitiveInstanceKind::YuvImage { data_handle, segment_instance_index, .. } => { + profile_scope!("YuvImage"); + let prim_data = &mut data_stores.yuv_image[*data_handle]; + let common_data = &mut prim_data.common; + let yuv_image_data = &mut prim_data.kind; + + common_data.may_need_repetition = false; + + // Update the template this instane references, which may refresh the GPU + // cache with any shared template data. + yuv_image_data.update(common_data, frame_state); + + write_segment( + *segment_instance_index, + frame_state, + &mut scratch.segments, + &mut scratch.segment_instances, + |request| { + yuv_image_data.write_prim_gpu_blocks(request); + } + ); + } + PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => { + profile_scope!("Image"); + let prim_data = &mut data_stores.image[*data_handle]; + let common_data = &mut prim_data.common; + let image_data = &mut prim_data.kind; + + if image_data.stretch_size.width >= common_data.prim_rect.size.width && + image_data.stretch_size.height >= common_data.prim_rect.size.height { + + common_data.may_need_repetition = false; + } + + // Update the template this instane references, which may refresh the GPU + // cache with any shared template data. + image_data.update(common_data, frame_state); + + let image_instance = &mut self.images[*image_instance_index]; + + update_opacity_binding( + &mut self.opacity_bindings, + image_instance.opacity_binding_index, + frame_context.scene_properties, + ); + + write_segment( + image_instance.segment_instance_index, + frame_state, + &mut scratch.segments, + &mut scratch.segment_instances, + |request| { + image_data.write_prim_gpu_blocks(request); + }, + ); + } + PrimitiveInstanceKind::LinearGradient { data_handle, gradient_index, .. } => { + profile_scope!("LinearGradient"); + let prim_data = &mut data_stores.linear_grad[*data_handle]; + let gradient = &mut self.linear_gradients[*gradient_index]; + + // Update the template this instane references, which may refresh the GPU + // cache with any shared template data. + prim_data.update(frame_state); + + if prim_data.stretch_size.width >= prim_data.common.prim_rect.size.width && + prim_data.stretch_size.height >= prim_data.common.prim_rect.size.height { + + prim_data.common.may_need_repetition = false; + } + + if prim_data.supports_caching { + let gradient_size = (prim_data.end_point - prim_data.start_point).to_size(); + + // Calculate what the range of the gradient is that covers this + // primitive. These values are included in the cache key. The + // size of the gradient task is the length of a texture cache + // region, for maximum accuracy, and a minimal size on the + // axis that doesn't matter. + let (size, orientation, prim_start_offset, prim_end_offset) = + if prim_data.start_point.x.approx_eq(&prim_data.end_point.x) { + let prim_start_offset = -prim_data.start_point.y / gradient_size.height; + let prim_end_offset = (prim_data.common.prim_rect.size.height - prim_data.start_point.y) + / gradient_size.height; + let size = DeviceIntSize::new(16, TEXTURE_REGION_DIMENSIONS); + (size, LineOrientation::Vertical, prim_start_offset, prim_end_offset) + } else { + let prim_start_offset = -prim_data.start_point.x / gradient_size.width; + let prim_end_offset = (prim_data.common.prim_rect.size.width - prim_data.start_point.x) + / gradient_size.width; + let size = DeviceIntSize::new(TEXTURE_REGION_DIMENSIONS, 16); + (size, LineOrientation::Horizontal, prim_start_offset, prim_end_offset) + }; + + // Build the cache key, including information about the stops. + let mut stops = vec![GradientStopKey::empty(); prim_data.stops.len()]; + + // Reverse the stops as required, same as the gradient builder does + // for the slow path. + if prim_data.reverse_stops { + for (src, dest) in prim_data.stops.iter().rev().zip(stops.iter_mut()) { + let stop = GradientStop { + offset: 1.0 - src.offset, + color: src.color, + }; + *dest = stop.into(); + } + } else { + for (src, dest) in prim_data.stops.iter().zip(stops.iter_mut()) { + *dest = (*src).into(); + } + } + + gradient.cache_segments.clear(); + + // emit render task caches and image rectangles to draw a gradient + // with offsets from start_offset to end_offset. + // + // the primitive is covered by a gradient that ranges from + // prim_start_offset to prim_end_offset. + // + // when clamping, these two pairs of offsets will always be the same. + // when repeating, however, we march across the primitive, blitting + // copies of the gradient along the way. each copy has a range from + // 0.0 to 1.0 (assuming it's fully visible), but where it appears on + // the primitive changes as we go. this position is also expressed + // as an offset: gradient_offset_base. that is, in terms of stops, + // we draw a gradient from start_offset to end_offset. its actual + // location on the primitive is at start_offset + gradient_offset_base. + // + // either way, we need a while-loop to draw the gradient as well + // because it might have more than 4 stops (the maximum of a cached + // segment) and/or hard stops. so we have a walk-within-the-walk from + // start_offset to end_offset caching up to GRADIENT_FP_STOPS stops at a + // time. + fn emit_segments(start_offset: f32, // start and end offset together are + end_offset: f32, // always a subrange of 0..1 + gradient_offset_base: f32, + prim_start_offset: f32, // the offsets of the entire gradient as it + prim_end_offset: f32, // covers the entire primitive. + prim_origin_in: LayoutPoint, + prim_size_in: LayoutSize, + task_size: DeviceIntSize, + is_opaque: bool, + stops: &[GradientStopKey], + orientation: LineOrientation, + frame_state: &mut FrameBuildingState, + gradient: &mut LinearGradientPrimitive) + { + // these prints are used to generate documentation examples, so + // leaving them in but commented out: + //println!("emit_segments call:"); + //println!("\tstart_offset: {}, end_offset: {}", start_offset, end_offset); + //println!("\tprim_start_offset: {}, prim_end_offset: {}", prim_start_offset, prim_end_offset); + //println!("\tgradient_offset_base: {}", gradient_offset_base); + let mut first_stop = 0; + // look for an inclusive range of stops [first_stop, last_stop]. + // once first_stop points at (or past) the last stop, we're done. + while first_stop < stops.len()-1 { + + // if the entire sub-gradient starts at an offset that's past the + // segment's end offset, we're done. + if stops[first_stop].offset > end_offset { + return; + } + + // accumulate stops until we have GRADIENT_FP_STOPS of them, or we hit + // a hard stop: + let mut last_stop = first_stop; + let mut hard_stop = false; // did we stop on a hard stop? + while last_stop < stops.len()-1 && + last_stop - first_stop + 1 < GRADIENT_FP_STOPS + { + if stops[last_stop+1].offset == stops[last_stop].offset { + hard_stop = true; + break; + } + + last_stop = last_stop + 1; + } + + let num_stops = last_stop - first_stop + 1; + + // repeated hard stops at the same offset, skip + if num_stops == 0 { + first_stop = last_stop + 1; + continue; + } + + // if the last_stop offset is before start_offset, the segment's not visible: + if stops[last_stop].offset < start_offset { + first_stop = if hard_stop { last_stop+1 } else { last_stop }; + continue; + } + + let segment_start_point = start_offset.max(stops[first_stop].offset); + let segment_end_point = end_offset .min(stops[last_stop ].offset); + + let mut segment_stops = [GradientStopKey::empty(); GRADIENT_FP_STOPS]; + for i in 0..num_stops { + segment_stops[i] = stops[first_stop + i]; + } + + let cache_key = GradientCacheKey { + orientation, + start_stop_point: VectorKey { + x: segment_start_point, + y: segment_end_point, + }, + stops: segment_stops, + }; + + let mut prim_origin = prim_origin_in; + let mut prim_size = prim_size_in; + + // the primitive is covered by a segment from overall_start to + // overall_end; scale and shift based on the length of the actual + // segment that we're drawing: + let inv_length = 1.0 / ( prim_end_offset - prim_start_offset ); + if orientation == LineOrientation::Horizontal { + prim_origin.x += ( segment_start_point + gradient_offset_base - prim_start_offset ) + * inv_length * prim_size.width; + prim_size.width *= ( segment_end_point - segment_start_point ) + * inv_length; // 2 gradient_offset_bases cancel out + } else { + prim_origin.y += ( segment_start_point + gradient_offset_base - prim_start_offset ) + * inv_length * prim_size.height; + prim_size.height *= ( segment_end_point - segment_start_point ) + * inv_length; // 2 gradient_offset_bases cancel out + } + + // <= 0 can happen if a hardstop lands exactly on an edge + if prim_size.area() > 0.0 { + let local_rect = LayoutRect::new( prim_origin, prim_size ); + + // documentation example traces: + //println!("\t\tcaching from offset {} to {}", segment_start_point, segment_end_point); + //println!("\t\tand blitting to {:?}", local_rect); + + // Request the render task each frame. + gradient.cache_segments.push( + CachedGradientSegment { + handle: frame_state.resource_cache.request_render_task( + RenderTaskCacheKey { + size: task_size, + kind: RenderTaskCacheKeyKind::Gradient(cache_key), + }, + frame_state.gpu_cache, + frame_state.render_tasks, + None, + is_opaque, + |render_tasks| { + render_tasks.add().init(RenderTask::new_gradient( + task_size, + segment_stops, + orientation, + segment_start_point, + segment_end_point, + )) + }), + local_rect: local_rect, + } + ); + } + + // if ending on a hardstop, skip past it for the start of the next run: + first_stop = if hard_stop { last_stop + 1 } else { last_stop }; + } + } + + if prim_data.extend_mode == ExtendMode::Clamp || + ( prim_start_offset >= 0.0 && prim_end_offset <= 1.0 ) // repeat doesn't matter + { + // To support clamping, we need to make sure that quads are emitted for the + // segments before and after the 0.0...1.0 range of offsets. emit_segments + // can handle that by duplicating the first and last point if necessary: + if prim_start_offset < 0.0 { + stops.insert(0, GradientStopKey { + offset: prim_start_offset, + color : stops[0].color + }); + } + + if prim_end_offset > 1.0 { + stops.push( GradientStopKey { + offset: prim_end_offset, + color : stops[stops.len()-1].color + }); + } + + emit_segments(prim_start_offset, prim_end_offset, + 0.0, + prim_start_offset, prim_end_offset, + prim_data.common.prim_rect.origin, + prim_data.common.prim_rect.size, + size, + prim_data.stops_opacity.is_opaque, + &stops, + orientation, + frame_state, + gradient); + } + else + { + let mut segment_start_point = prim_start_offset; + while segment_start_point < prim_end_offset { + + // gradient stops are expressed in the range 0.0 ... 1.0, so to blit + // a copy of the gradient, snap to the integer just before the offset + // we want ... + let gradient_offset_base = segment_start_point.floor(); + // .. and then draw from a start offset in range 0 to 1 ... + let repeat_start = segment_start_point - gradient_offset_base; + // .. up to the next integer, but clamped to the primitive's real + // end offset: + let repeat_end = (gradient_offset_base + 1.0).min(prim_end_offset) - gradient_offset_base; + + emit_segments(repeat_start, repeat_end, + gradient_offset_base, + prim_start_offset, prim_end_offset, + prim_data.common.prim_rect.origin, + prim_data.common.prim_rect.size, + size, + prim_data.stops_opacity.is_opaque, + &stops, + orientation, + frame_state, + gradient); + + segment_start_point = repeat_end + gradient_offset_base; + } + } + } + + if prim_data.tile_spacing != LayoutSize::zero() { + // We are performing the decomposition on the CPU here, no need to + // have it in the shader. + prim_data.common.may_need_repetition = false; + + let prim_info = &scratch.prim_info[prim_instance.visibility_info.0 as usize]; + + let map_local_to_world = SpaceMapper::new_with_target( + ROOT_SPATIAL_NODE_INDEX, + prim_spatial_node_index, + frame_context.global_screen_world_rect, + frame_context.spatial_tree, + ); + + gradient.visible_tiles_range = decompose_repeated_primitive( + &prim_info.combined_local_clip_rect, + &prim_data.common.prim_rect, + prim_info.clipped_world_rect, + &prim_data.stretch_size, + &prim_data.tile_spacing, + frame_state, + &mut scratch.gradient_tiles, + &map_local_to_world, + &mut |_, mut request| { + request.push([ + prim_data.start_point.x, + prim_data.start_point.y, + prim_data.end_point.x, + prim_data.end_point.y, + ]); + request.push([ + pack_as_float(prim_data.extend_mode as u32), + prim_data.stretch_size.width, + prim_data.stretch_size.height, + 0.0, + ]); + } + ); + + if gradient.visible_tiles_range.is_empty() { + prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID; + } + } + + // TODO(gw): Consider whether it's worth doing segment building + // for gradient primitives. + } + PrimitiveInstanceKind::RadialGradient { data_handle, ref mut visible_tiles_range, .. } => { + profile_scope!("RadialGradient"); + let prim_data = &mut data_stores.radial_grad[*data_handle]; + + if prim_data.stretch_size.width >= prim_data.common.prim_rect.size.width && + prim_data.stretch_size.height >= prim_data.common.prim_rect.size.height { + + // We are performing the decomposition on the CPU here, no need to + // have it in the shader. + prim_data.common.may_need_repetition = false; + } + + // Update the template this instane references, which may refresh the GPU + // cache with any shared template data. + prim_data.update(frame_state); + + if prim_data.tile_spacing != LayoutSize::zero() { + let prim_info = &scratch.prim_info[prim_instance.visibility_info.0 as usize]; + + let map_local_to_world = SpaceMapper::new_with_target( + ROOT_SPATIAL_NODE_INDEX, + prim_spatial_node_index, + frame_context.global_screen_world_rect, + frame_context.spatial_tree, + ); + + prim_data.common.may_need_repetition = false; + + *visible_tiles_range = decompose_repeated_primitive( + &prim_info.combined_local_clip_rect, + &prim_data.common.prim_rect, + prim_info.clipped_world_rect, + &prim_data.stretch_size, + &prim_data.tile_spacing, + frame_state, + &mut scratch.gradient_tiles, + &map_local_to_world, + &mut |_, mut request| { + request.push([ + prim_data.center.x, + prim_data.center.y, + prim_data.params.start_radius, + prim_data.params.end_radius, + ]); + request.push([ + prim_data.params.ratio_xy, + pack_as_float(prim_data.extend_mode as u32), + prim_data.stretch_size.width, + prim_data.stretch_size.height, + ]); + }, + ); + + if visible_tiles_range.is_empty() { + prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID; + } + } + + // TODO(gw): Consider whether it's worth doing segment building + // for gradient primitives. + } + PrimitiveInstanceKind::ConicGradient { data_handle, ref mut visible_tiles_range, .. } => { + profile_scope!("ConicGradient"); + let prim_data = &mut data_stores.conic_grad[*data_handle]; + + if prim_data.stretch_size.width >= prim_data.common.prim_rect.size.width && + prim_data.stretch_size.height >= prim_data.common.prim_rect.size.height { + + // We are performing the decomposition on the CPU here, no need to + // have it in the shader. + prim_data.common.may_need_repetition = false; + } + + // Update the template this instane references, which may refresh the GPU + // cache with any shared template data. + prim_data.update(frame_state); + + if prim_data.tile_spacing != LayoutSize::zero() { + let prim_info = &scratch.prim_info[prim_instance.visibility_info.0 as usize]; + + let map_local_to_world = SpaceMapper::new_with_target( + ROOT_SPATIAL_NODE_INDEX, + prim_spatial_node_index, + frame_context.global_screen_world_rect, + frame_context.spatial_tree, + ); + + prim_data.common.may_need_repetition = false; + + *visible_tiles_range = decompose_repeated_primitive( + &prim_info.combined_local_clip_rect, + &prim_data.common.prim_rect, + prim_info.clipped_world_rect, + &prim_data.stretch_size, + &prim_data.tile_spacing, + frame_state, + &mut scratch.gradient_tiles, + &map_local_to_world, + &mut |_, mut request| { + request.push([ + prim_data.center.x, + prim_data.center.y, + prim_data.params.start_offset, + prim_data.params.end_offset, + ]); + request.push([ + prim_data.params.angle, + pack_as_float(prim_data.extend_mode as u32), + prim_data.stretch_size.width, + prim_data.stretch_size.height, + ]); + }, + ); + + if visible_tiles_range.is_empty() { + prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID; + } + } + + // TODO(gw): Consider whether it's worth doing segment building + // for gradient primitives. + } + PrimitiveInstanceKind::Picture { pic_index, segment_instance_index, .. } => { + profile_scope!("Picture"); + let pic = &mut self.pictures[pic_index.0]; + let prim_info = &scratch.prim_info[prim_instance.visibility_info.0 as usize]; + + if pic.prepare_for_render( + frame_context, + frame_state, + data_stores, + ) { + if let Some(ref mut splitter) = pic_state.plane_splitter { + PicturePrimitive::add_split_plane( + splitter, + frame_context.spatial_tree, + prim_spatial_node_index, + pic.precise_local_rect, + &prim_info.combined_local_clip_rect, + frame_state.current_dirty_region().combined, + plane_split_anchor, + ); + } + + // If this picture uses segments, ensure the GPU cache is + // up to date with segment local rects. + // TODO(gw): This entire match statement above can now be + // refactored into prepare_interned_prim_for_render. + if pic.can_use_segments() { + write_segment( + *segment_instance_index, + frame_state, + &mut scratch.segments, + &mut scratch.segment_instances, + |request| { + request.push(PremultipliedColorF::WHITE); + request.push(PremultipliedColorF::WHITE); + request.push([ + -1.0, // -ve means use prim rect for stretch size + 0.0, + 0.0, + 0.0, + ]); + } + ); + } + } else { + prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID; + } + } + PrimitiveInstanceKind::Backdrop { data_handle } => { + profile_scope!("Backdrop"); + let backdrop_pic_index = data_stores.backdrop[*data_handle].kind.pic_index; + + // Setup a dependency on the backdrop picture to ensure it is rendered prior to rendering this primitive. + let backdrop_surface_index = self.pictures[backdrop_pic_index.0].raster_config.as_ref().unwrap().surface_index; + if let Some(backdrop_tasks) = frame_state.surfaces[backdrop_surface_index.0].render_tasks { + let picture_task_id = frame_state.surfaces[pic_context.surface_index.0].render_tasks.as_ref().unwrap().port; + frame_state.render_tasks.add_dependency(picture_task_id, backdrop_tasks.root); + } else { + if prim_instance.is_chased() { + println!("\tBackdrop primitive culled because backdrop task was not assigned render tasks"); + } + prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID; + } + } + }; + } +} + +fn write_segment( + segment_instance_index: SegmentInstanceIndex, + frame_state: &mut FrameBuildingState, + segments: &mut SegmentStorage, + segment_instances: &mut SegmentInstanceStorage, + f: F, +) where F: Fn(&mut GpuDataRequest) { + debug_assert_ne!(segment_instance_index, SegmentInstanceIndex::INVALID); + if segment_instance_index != SegmentInstanceIndex::UNUSED { + let segment_instance = &mut segment_instances[segment_instance_index]; + + if let Some(mut request) = frame_state.gpu_cache.request(&mut segment_instance.gpu_cache_handle) { + let segments = &segments[segment_instance.segments_range]; + + f(&mut request); + + for segment in segments { + request.write_segment( + segment.local_rect, + [0.0; 4], + ); + } + } + } +} + +fn decompose_repeated_primitive( + combined_local_clip_rect: &LayoutRect, + prim_local_rect: &LayoutRect, + prim_world_rect: WorldRect, + stretch_size: &LayoutSize, + tile_spacing: &LayoutSize, + frame_state: &mut FrameBuildingState, + gradient_tiles: &mut GradientTileStorage, + map_local_to_world: &SpaceMapper, + callback: &mut dyn FnMut(&LayoutRect, GpuDataRequest), +) -> GradientTileRange { + let mut visible_tiles = Vec::new(); + + // Tighten the clip rect because decomposing the repeated image can + // produce primitives that are partially covering the original image + // rect and we want to clip these extra parts out. + let tight_clip_rect = combined_local_clip_rect + .intersection(prim_local_rect).unwrap(); + + let visible_rect = compute_conservative_visible_rect( + &tight_clip_rect, + prim_world_rect, + map_local_to_world, + ); + let stride = *stretch_size + *tile_spacing; + + let repetitions = image_tiling::repetitions(prim_local_rect, &visible_rect, stride); + for Repetition { origin, .. } in repetitions { + let mut handle = GpuCacheHandle::new(); + let rect = LayoutRect { + origin, + size: *stretch_size, + }; + + if let Some(request) = frame_state.gpu_cache.request(&mut handle) { + callback(&rect, request); + } + + visible_tiles.push(VisibleGradientTile { + local_rect: rect, + local_clip_rect: tight_clip_rect, + handle + }); + } + + // At this point if we don't have tiles to show it means we could probably + // have done a better a job at culling during an earlier stage. + // Clearing the screen rect has the effect of "culling out" the primitive + // from the point of view of the batch builder, and ensures we don't hit + // assertions later on because we didn't request any image. + if visible_tiles.is_empty() { + GradientTileRange::empty() + } else { + gradient_tiles.extend(visible_tiles) + } +} + +fn compute_conservative_visible_rect( + local_clip_rect: &LayoutRect, + world_culling_rect: WorldRect, + map_local_to_world: &SpaceMapper, +) -> LayoutRect { + if let Some(local_bounds) = map_local_to_world.unmap(&world_culling_rect) { + return local_clip_rect.intersection(&local_bounds).unwrap_or_else(LayoutRect::zero) + } + + *local_clip_rect +} + +fn edge_flags_for_tile_spacing(tile_spacing: &LayoutSize) -> EdgeAaSegmentMask { + let mut flags = EdgeAaSegmentMask::empty(); + + if tile_spacing.width > 0.0 { + flags |= EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT; + } + if tile_spacing.height > 0.0 { + flags |= EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM; + } + + flags +} + +impl<'a> GpuDataRequest<'a> { + // Write the GPU cache data for an individual segment. + fn write_segment( + &mut self, + local_rect: LayoutRect, + extra_data: [f32; 4], + ) { + let _ = VECS_PER_SEGMENT; + self.push(local_rect); + self.push(extra_data); + } +} + + fn write_brush_segment_description( + prim_local_rect: LayoutRect, + prim_local_clip_rect: LayoutRect, + clip_chain: &ClipChainInstance, + segment_builder: &mut SegmentBuilder, + clip_store: &ClipStore, + data_stores: &DataStores, + ) -> bool { + // If the brush is small, we want to skip building segments + // and just draw it as a single primitive with clip mask. + if prim_local_rect.size.area() < MIN_BRUSH_SPLIT_AREA { + return false; + } + + segment_builder.initialize( + prim_local_rect, + None, + prim_local_clip_rect + ); + + // Segment the primitive on all the local-space clip sources that we can. + for i in 0 .. clip_chain.clips_range.count { + let clip_instance = clip_store + .get_instance_from_range(&clip_chain.clips_range, i); + let clip_node = &data_stores.clip[clip_instance.handle]; + + // If this clip item is positioned by another positioning node, its relative position + // could change during scrolling. This means that we would need to resegment. Instead + // of doing that, only segment with clips that have the same positioning node. + // TODO(mrobinson, #2858): It may make sense to include these nodes, resegmenting only + // when necessary while scrolling. + if !clip_instance.flags.contains(ClipNodeFlags::SAME_SPATIAL_NODE) { + continue; + } + + let (local_clip_rect, radius, mode) = match clip_node.item.kind { + ClipItemKind::RoundedRectangle { rect, radius, mode } => { + (rect, Some(radius), mode) + } + ClipItemKind::Rectangle { rect, mode } => { + (rect, None, mode) + } + ClipItemKind::BoxShadow { ref source } => { + // For inset box shadows, we can clip out any + // pixels that are inside the shadow region + // and are beyond the inner rect, as they can't + // be affected by the blur radius. + let inner_clip_mode = match source.clip_mode { + BoxShadowClipMode::Outset => None, + BoxShadowClipMode::Inset => Some(ClipMode::ClipOut), + }; + + // Push a region into the segment builder where the + // box-shadow can have an effect on the result. This + // ensures clip-mask tasks get allocated for these + // pixel regions, even if no other clips affect them. + segment_builder.push_mask_region( + source.prim_shadow_rect, + source.prim_shadow_rect.inflate( + -0.5 * source.original_alloc_size.width, + -0.5 * source.original_alloc_size.height, + ), + inner_clip_mode, + ); + + continue; + } + ClipItemKind::Image { .. } => { + // If we encounter an image mask, bail out from segment building. + // It's not possible to know which parts of the primitive are affected + // by the mask (without inspecting the pixels). We could do something + // better here in the future if it ever shows up as a performance issue + // (for instance, at least segment based on the bounding rect of the + // image mask if it's non-repeating). + return false; + } + }; + + segment_builder.push_clip_rect(local_clip_rect, radius, mode); + } + + true + } + +impl PrimitiveInstance { + fn build_segments_if_needed( + &mut self, + prim_info: &PrimitiveVisibility, + frame_state: &mut FrameBuildingState, + prim_store: &mut PrimitiveStore, + data_stores: &DataStores, + segments_store: &mut SegmentStorage, + segment_instances_store: &mut SegmentInstanceStorage, + ) { + let prim_clip_chain = &prim_info.clip_chain; + + // Usually, the primitive rect can be found from information + // in the instance and primitive template. + let prim_local_rect = data_stores.get_local_prim_rect( + self, + prim_store, + ); + + let segment_instance_index = match self.kind { + PrimitiveInstanceKind::Rectangle { ref mut segment_instance_index, .. } | + PrimitiveInstanceKind::YuvImage { ref mut segment_instance_index, .. } => { + segment_instance_index + } + PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => { + let image_data = &data_stores.image[data_handle].kind; + let image_instance = &mut prim_store.images[image_instance_index]; + //Note: tiled images don't support automatic segmentation, + // they strictly produce one segment per visible tile instead. + if frame_state + .resource_cache + .get_image_properties(image_data.key) + .and_then(|properties| properties.tiling) + .is_some() + { + image_instance.segment_instance_index = SegmentInstanceIndex::UNUSED; + return; + } + &mut image_instance.segment_instance_index + } + PrimitiveInstanceKind::Picture { ref mut segment_instance_index, pic_index, .. } => { + let pic = &mut prim_store.pictures[pic_index.0]; + + // If this picture supports segment rendering + if pic.can_use_segments() { + // If the segments have been invalidated, ensure the current + // index of segments is invalid. This ensures that the segment + // building logic below will be run. + if !pic.segments_are_valid { + *segment_instance_index = SegmentInstanceIndex::INVALID; + pic.segments_are_valid = true; + } + + segment_instance_index + } else { + return; + } + } + PrimitiveInstanceKind::TextRun { .. } | + PrimitiveInstanceKind::NormalBorder { .. } | + PrimitiveInstanceKind::ImageBorder { .. } | + PrimitiveInstanceKind::Clear { .. } | + PrimitiveInstanceKind::LinearGradient { .. } | + PrimitiveInstanceKind::RadialGradient { .. } | + PrimitiveInstanceKind::ConicGradient { .. } | + PrimitiveInstanceKind::LineDecoration { .. } | + PrimitiveInstanceKind::Backdrop { .. } => { + // These primitives don't support / need segments. + return; + } + }; + + if *segment_instance_index == SegmentInstanceIndex::INVALID { + let mut segments: SmallVec<[BrushSegment; 8]> = SmallVec::new(); + + if write_brush_segment_description( + prim_local_rect, + self.local_clip_rect, + prim_clip_chain, + &mut frame_state.segment_builder, + frame_state.clip_store, + data_stores, + ) { + frame_state.segment_builder.build(|segment| { + segments.push( + BrushSegment::new( + segment.rect.translate(-prim_local_rect.origin.to_vector()), + segment.has_mask, + segment.edge_flags, + [0.0; 4], + BrushFlags::PERSPECTIVE_INTERPOLATION, + ), + ); + }); + } + + // If only a single segment is produced, there is no benefit to writing + // a segment instance array. Instead, just use the main primitive rect + // written into the GPU cache. + // TODO(gw): This is (sortof) a bandaid - due to a limitation in the current + // brush encoding, we can only support a total of up to 2^16 segments. + // This should be (more than) enough for any real world case, so for + // now we can handle this by skipping cases where we were generating + // segments where there is no benefit. The long term / robust fix + // for this is to move the segment building to be done as a more + // limited nine-patch system during scene building, removing arbitrary + // segmentation during frame-building (see bug #1617491). + if segments.len() <= 1 { + *segment_instance_index = SegmentInstanceIndex::UNUSED; + } else { + let segments_range = segments_store.extend(segments); + + let instance = SegmentedInstance { + segments_range, + gpu_cache_handle: GpuCacheHandle::new(), + }; + + *segment_instance_index = segment_instances_store.push(instance); + }; + } + } + + fn update_clip_task_for_brush( + &self, + prim_origin: &LayoutPoint, + prim_info: &mut PrimitiveVisibility, + prim_spatial_node_index: SpatialNodeIndex, + root_spatial_node_index: SpatialNodeIndex, + pic_context: &PictureContext, + pic_state: &mut PictureState, + frame_context: &FrameBuildingContext, + frame_state: &mut FrameBuildingState, + prim_store: &PrimitiveStore, + data_stores: &mut DataStores, + segments_store: &mut SegmentStorage, + segment_instances_store: &mut SegmentInstanceStorage, + clip_mask_instances: &mut Vec, + unclipped: &DeviceRect, + device_pixel_scale: DevicePixelScale, + ) -> bool { + let segments = match self.kind { + PrimitiveInstanceKind::TextRun { .. } | + PrimitiveInstanceKind::Clear { .. } | + PrimitiveInstanceKind::LineDecoration { .. } | + PrimitiveInstanceKind::Backdrop { .. } => { + return false; + } + PrimitiveInstanceKind::Image { image_instance_index, .. } => { + let segment_instance_index = prim_store + .images[image_instance_index] + .segment_instance_index; + + if segment_instance_index == SegmentInstanceIndex::UNUSED { + return false; + } + + let segment_instance = &segment_instances_store[segment_instance_index]; + + &segments_store[segment_instance.segments_range] + } + PrimitiveInstanceKind::Picture { segment_instance_index, .. } => { + // Pictures may not support segment rendering at all (INVALID) + // or support segment rendering but choose not to due to size + // or some other factor (UNUSED). + if segment_instance_index == SegmentInstanceIndex::UNUSED || + segment_instance_index == SegmentInstanceIndex::INVALID { + return false; + } + + let segment_instance = &segment_instances_store[segment_instance_index]; + &segments_store[segment_instance.segments_range] + } + PrimitiveInstanceKind::YuvImage { segment_instance_index, .. } | + PrimitiveInstanceKind::Rectangle { segment_instance_index, .. } => { + debug_assert!(segment_instance_index != SegmentInstanceIndex::INVALID); + + if segment_instance_index == SegmentInstanceIndex::UNUSED { + return false; + } + + let segment_instance = &segment_instances_store[segment_instance_index]; + + &segments_store[segment_instance.segments_range] + } + PrimitiveInstanceKind::ImageBorder { data_handle, .. } => { + let border_data = &data_stores.image_border[data_handle].kind; + + // TODO: This is quite messy - once we remove legacy primitives we + // can change this to be a tuple match on (instance, template) + border_data.brush_segments.as_slice() + } + PrimitiveInstanceKind::NormalBorder { data_handle, .. } => { + let border_data = &data_stores.normal_border[data_handle].kind; + + // TODO: This is quite messy - once we remove legacy primitives we + // can change this to be a tuple match on (instance, template) + border_data.brush_segments.as_slice() + } + PrimitiveInstanceKind::LinearGradient { data_handle, .. } => { + let prim_data = &data_stores.linear_grad[data_handle]; + + // TODO: This is quite messy - once we remove legacy primitives we + // can change this to be a tuple match on (instance, template) + if prim_data.brush_segments.is_empty() { + return false; + } + + prim_data.brush_segments.as_slice() + } + PrimitiveInstanceKind::RadialGradient { data_handle, .. } => { + let prim_data = &data_stores.radial_grad[data_handle]; + + // TODO: This is quite messy - once we remove legacy primitives we + // can change this to be a tuple match on (instance, template) + if prim_data.brush_segments.is_empty() { + return false; + } + + prim_data.brush_segments.as_slice() + } + PrimitiveInstanceKind::ConicGradient { data_handle, .. } => { + let prim_data = &data_stores.conic_grad[data_handle]; + + // TODO: This is quite messy - once we remove legacy primitives we + // can change this to be a tuple match on (instance, template) + if prim_data.brush_segments.is_empty() { + return false; + } + + prim_data.brush_segments.as_slice() + } + }; + + // If there are no segments, early out to avoid setting a valid + // clip task instance location below. + if segments.is_empty() { + return true; + } + + // Set where in the clip mask instances array the clip mask info + // can be found for this primitive. Each segment will push the + // clip mask information for itself in update_clip_task below. + prim_info.clip_task_index = ClipTaskIndex(clip_mask_instances.len() as _); + + // If we only built 1 segment, there is no point in re-running + // the clip chain builder. Instead, just use the clip chain + // instance that was built for the main primitive. This is a + // significant optimization for the common case. + if segments.len() == 1 { + let clip_mask_kind = segments[0].update_clip_task( + Some(&prim_info.clip_chain), + prim_info.clipped_world_rect, + root_spatial_node_index, + pic_context.surface_index, + pic_state, + frame_context, + frame_state, + &mut data_stores.clip, + unclipped, + device_pixel_scale, + ); + clip_mask_instances.push(clip_mask_kind); + } else { + let dirty_world_rect = frame_state.current_dirty_region().combined; + + for segment in segments { + // Build a clip chain for the smaller segment rect. This will + // often manage to eliminate most/all clips, and sometimes + // clip the segment completely. + frame_state.clip_store.set_active_clips_from_clip_chain( + &prim_info.clip_chain, + prim_spatial_node_index, + &frame_context.spatial_tree, + ); + + let segment_clip_chain = frame_state + .clip_store + .build_clip_chain_instance( + segment.local_rect.translate(prim_origin.to_vector()), + &pic_state.map_local_to_pic, + &pic_state.map_pic_to_world, + &frame_context.spatial_tree, + frame_state.gpu_cache, + frame_state.resource_cache, + device_pixel_scale, + &dirty_world_rect, + &mut data_stores.clip, + false, + self.is_chased(), + ); + + let clip_mask_kind = segment.update_clip_task( + segment_clip_chain.as_ref(), + prim_info.clipped_world_rect, + root_spatial_node_index, + pic_context.surface_index, + pic_state, + frame_context, + frame_state, + &mut data_stores.clip, + unclipped, + device_pixel_scale, + ); + clip_mask_instances.push(clip_mask_kind); + } + } + + true + } + + fn update_clip_task( + &mut self, + prim_origin: &LayoutPoint, + prim_spatial_node_index: SpatialNodeIndex, + root_spatial_node_index: SpatialNodeIndex, + pic_context: &PictureContext, + pic_state: &mut PictureState, + frame_context: &FrameBuildingContext, + frame_state: &mut FrameBuildingState, + prim_store: &mut PrimitiveStore, + data_stores: &mut DataStores, + scratch: &mut PrimitiveScratchBuffer, + ) { + let prim_info = &mut scratch.prim_info[self.visibility_info.0 as usize]; + let device_pixel_scale = frame_state.surfaces[pic_context.surface_index.0].device_pixel_scale; + + if self.is_chased() { + println!("\tupdating clip task with pic rect {:?}", prim_info.clip_chain.pic_clip_rect); + } + + // Get the device space rect for the primitive if it was unclipped. + let unclipped = match get_unclipped_device_rect( + prim_info.clip_chain.pic_clip_rect, + &pic_state.map_pic_to_raster, + device_pixel_scale, + ) { + Some(rect) => rect, + None => return, + }; + + self.build_segments_if_needed( + &prim_info, + frame_state, + prim_store, + data_stores, + &mut scratch.segments, + &mut scratch.segment_instances, + ); + + // First try to render this primitive's mask using optimized brush rendering. + if self.update_clip_task_for_brush( + prim_origin, + prim_info, + prim_spatial_node_index, + root_spatial_node_index, + pic_context, + pic_state, + frame_context, + frame_state, + prim_store, + data_stores, + &mut scratch.segments, + &mut scratch.segment_instances, + &mut scratch.clip_mask_instances, + &unclipped, + device_pixel_scale, + ) { + if self.is_chased() { + println!("\tsegment tasks have been created for clipping"); + } + return; + } + + if prim_info.clip_chain.needs_mask { + // Get a minimal device space rect, clipped to the screen that we + // need to allocate for the clip mask, as well as interpolated + // snap offsets. + if let Some(device_rect) = get_clipped_device_rect( + &unclipped, + &pic_state.map_raster_to_world, + prim_info.clipped_world_rect, + device_pixel_scale, + ) { + let (device_rect, device_pixel_scale) = adjust_mask_scale_for_max_size(device_rect, device_pixel_scale); + + let clip_task_id = RenderTask::new_mask( + device_rect, + prim_info.clip_chain.clips_range, + root_spatial_node_index, + frame_state.clip_store, + frame_state.gpu_cache, + frame_state.resource_cache, + frame_state.render_tasks, + &mut data_stores.clip, + device_pixel_scale, + frame_context.fb_config, + ); + if self.is_chased() { + println!("\tcreated task {:?} with device rect {:?}", + clip_task_id, device_rect); + } + // Set the global clip mask instance for this primitive. + let clip_task_index = ClipTaskIndex(scratch.clip_mask_instances.len() as _); + scratch.clip_mask_instances.push(ClipMaskKind::Mask(clip_task_id)); + prim_info.clip_task_index = clip_task_index; + frame_state.render_tasks.add_dependency( + frame_state.surfaces[pic_context.surface_index.0].render_tasks.unwrap().port, + clip_task_id, + ); + } + } + } +} + +// Ensures that the size of mask render tasks are within MAX_MASK_SIZE. +fn adjust_mask_scale_for_max_size(device_rect: DeviceRect, device_pixel_scale: DevicePixelScale) -> (DeviceIntRect, DevicePixelScale) { + if device_rect.width() > MAX_MASK_SIZE || device_rect.height() > MAX_MASK_SIZE { + // round_out will grow by 1 integer pixel if origin is on a + // fractional position, so keep that margin for error with -1: + let scale = (MAX_MASK_SIZE - 1.0) / + f32::max(device_rect.width(), device_rect.height()); + let new_device_pixel_scale = device_pixel_scale * Scale::new(scale); + let new_device_rect = (device_rect.to_f32() * Scale::new(scale)) + .round_out() + .to_i32(); + (new_device_rect, new_device_pixel_scale) + } else { + (device_rect.to_i32(), device_pixel_scale) + } +} + +/// Retrieve the exact unsnapped device space rectangle for a primitive. +fn get_unclipped_device_rect( + prim_rect: PictureRect, + map_to_raster: &SpaceMapper, + device_pixel_scale: DevicePixelScale, +) -> Option { + let raster_rect = map_to_raster.map(&prim_rect)?; + let world_rect = raster_rect * Scale::new(1.0); + Some(world_rect * device_pixel_scale) +} + +/// Given an unclipped device rect, try to find a minimal device space +/// rect to allocate a clip mask for, by clipping to the screen. This +/// function is very similar to get_raster_rects below. It is far from +/// ideal, and should be refactored as part of the support for setting +/// scale per-raster-root. +fn get_clipped_device_rect( + unclipped: &DeviceRect, + map_to_world: &SpaceMapper, + prim_bounding_rect: WorldRect, + device_pixel_scale: DevicePixelScale, +) -> Option { + let unclipped_raster_rect = { + let world_rect = *unclipped * Scale::new(1.0); + let raster_rect = world_rect * device_pixel_scale.inverse(); + + raster_rect.cast_unit() + }; + + let unclipped_world_rect = map_to_world.map(&unclipped_raster_rect)?; + + let clipped_world_rect = unclipped_world_rect.intersection(&prim_bounding_rect)?; + + let clipped_raster_rect = map_to_world.unmap(&clipped_world_rect)?; + + let clipped_raster_rect = clipped_raster_rect.intersection(&unclipped_raster_rect)?; + + // Ensure that we won't try to allocate a zero-sized clip render task. + if clipped_raster_rect.is_empty() { + return None; + } + + let clipped = raster_rect_to_device_pixels( + clipped_raster_rect, + device_pixel_scale, + ); + + Some(clipped) +} + +pub fn get_raster_rects( + pic_rect: PictureRect, + map_to_raster: &SpaceMapper, + map_to_world: &SpaceMapper, + prim_bounding_rect: WorldRect, + device_pixel_scale: DevicePixelScale, +) -> Option<(DeviceRect, DeviceRect)> { + let unclipped_raster_rect = map_to_raster.map(&pic_rect)?; + + let unclipped = raster_rect_to_device_pixels( + unclipped_raster_rect, + device_pixel_scale, + ); + + let unclipped_world_rect = map_to_world.map(&unclipped_raster_rect)?; + + let clipped_world_rect = unclipped_world_rect.intersection(&prim_bounding_rect)?; + + let clipped_raster_rect = map_to_world.unmap(&clipped_world_rect)?; + + let clipped_raster_rect = clipped_raster_rect.intersection(&unclipped_raster_rect)?; + + let clipped = raster_rect_to_device_pixels( + clipped_raster_rect, + device_pixel_scale, + ); + + // Ensure that we won't try to allocate a zero-sized clip render task. + if clipped.is_empty() { + return None; + } + + Some((clipped, unclipped)) +} + +/// Choose the decoration mask tile size for a given line. +/// +/// Given a line with overall size `rect_size` and the given `orientation`, +/// return the dimensions of a single mask tile for the decoration pattern +/// described by `style` and `wavy_line_thickness`. +/// +/// If `style` is `Solid`, no mask tile is necessary; return `None`. The other +/// styles each have their own characteristic periods of repetition, so for each +/// one, this function returns a `LayoutSize` with the right aspect ratio and +/// whose specific size is convenient for the `cs_line_decoration.glsl` fragment +/// shader to work with. The shader uses a local coordinate space in which the +/// tile fills a rectangle with one corner at the origin, and with the size this +/// function returns. +/// +/// The returned size is not necessarily in pixels; device scaling and other +/// concerns can still affect the actual task size. +/// +/// Regardless of whether `orientation` is `Vertical` or `Horizontal`, the +/// `width` and `height` of the returned size are always horizontal and +/// vertical, respectively. +pub fn get_line_decoration_size( + rect_size: &LayoutSize, + orientation: LineOrientation, + style: LineStyle, + wavy_line_thickness: f32, +) -> Option { + let h = match orientation { + LineOrientation::Horizontal => rect_size.height, + LineOrientation::Vertical => rect_size.width, + }; + + // TODO(gw): The formulae below are based on the existing gecko and line + // shader code. They give reasonable results for most inputs, + // but could definitely do with a detailed pass to get better + // quality on a wider range of inputs! + // See nsCSSRendering::PaintDecorationLine in Gecko. + + let (parallel, perpendicular) = match style { + LineStyle::Solid => { + return None; + } + LineStyle::Dashed => { + let dash_length = (3.0 * h).min(64.0).max(1.0); + + (2.0 * dash_length, 4.0) + } + LineStyle::Dotted => { + let diameter = h.min(64.0).max(1.0); + let period = 2.0 * diameter; + + (period, diameter) + } + LineStyle::Wavy => { + let line_thickness = wavy_line_thickness.max(1.0); + let slope_length = h - line_thickness; + let flat_length = ((line_thickness - 1.0) * 2.0).max(1.0); + let approx_period = 2.0 * (slope_length + flat_length); + + (approx_period, h) + } + }; + + Some(match orientation { + LineOrientation::Horizontal => LayoutSize::new(parallel, perpendicular), + LineOrientation::Vertical => LayoutSize::new(perpendicular, parallel), + }) +} + +fn update_opacity_binding( + opacity_bindings: &mut OpacityBindingStorage, + opacity_binding_index: OpacityBindingIndex, + scene_properties: &SceneProperties, +) { + if opacity_binding_index != OpacityBindingIndex::INVALID { + let binding = &mut opacity_bindings[opacity_binding_index]; + binding.update(scene_properties); + } +} + +/// Trait for primitives that are directly internable. +/// see SceneBuilder::add_primitive

    +pub trait InternablePrimitive: intern::Internable + Sized { + /// Build a new key from self with `info`. + fn into_key( + self, + info: &LayoutPrimitiveInfo, + ) -> Self::Key; + + fn make_instance_kind( + key: Self::Key, + data_handle: intern::Handle, + prim_store: &mut PrimitiveStore, + reference_frame_relative_offset: LayoutVector2D, + ) -> PrimitiveInstanceKind; +} + + +#[test] +#[cfg(target_pointer_width = "64")] +fn test_struct_sizes() { + use std::mem; + // The sizes of these structures are critical for performance on a number of + // talos stress tests. If you get a failure here on CI, there's two possibilities: + // (a) You made a structure smaller than it currently is. Great work! Update the + // test expectations and move on. + // (b) You made a structure larger. This is not necessarily a problem, but should only + // be done with care, and after checking if talos performance regresses badly. + assert_eq!(mem::size_of::(), 80, "PrimitiveInstance size changed"); + assert_eq!(mem::size_of::(), 40, "PrimitiveInstanceKind size changed"); + assert_eq!(mem::size_of::(), 56, "PrimitiveTemplate size changed"); + assert_eq!(mem::size_of::(), 28, "PrimitiveTemplateKind size changed"); + assert_eq!(mem::size_of::(), 36, "PrimitiveKey size changed"); + assert_eq!(mem::size_of::(), 16, "PrimitiveKeyKind size changed"); +} diff --git a/third_party/webrender/webrender/src/prim_store/picture.rs b/third_party/webrender/webrender/src/prim_store/picture.rs new file mode 100644 index 00000000000..46a52e0a807 --- /dev/null +++ b/third_party/webrender/webrender/src/prim_store/picture.rs @@ -0,0 +1,319 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{ + ColorU, MixBlendMode, FilterPrimitiveInput, FilterPrimitiveKind, ColorSpace, + PropertyBinding, PropertyBindingId, CompositeOperator, +}; +use api::units::{Au, LayoutVector2D}; +use crate::scene_building::IsVisible; +use crate::filterdata::SFilterData; +use crate::intern::ItemUid; +use crate::intern::{Internable, InternDebug, Handle as InternHandle}; +use crate::internal_types::{LayoutPrimitiveInfo, Filter}; +use crate::picture::PictureCompositeMode; +use crate::prim_store::{ + PrimitiveInstanceKind, PrimitiveStore, VectorKey, + InternablePrimitive, +}; + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, MallocSizeOf, PartialEq, Hash, Eq)] +pub enum CompositeOperatorKey { + Over, + In, + Out, + Atop, + Xor, + Lighter, + Arithmetic([Au; 4]), +} + +impl From for CompositeOperatorKey { + fn from(operator: CompositeOperator) -> Self { + match operator { + CompositeOperator::Over => CompositeOperatorKey::Over, + CompositeOperator::In => CompositeOperatorKey::In, + CompositeOperator::Out => CompositeOperatorKey::Out, + CompositeOperator::Atop => CompositeOperatorKey::Atop, + CompositeOperator::Xor => CompositeOperatorKey::Xor, + CompositeOperator::Lighter => CompositeOperatorKey::Lighter, + CompositeOperator::Arithmetic(k_vals) => { + let k_vals = [ + Au::from_f32_px(k_vals[0]), + Au::from_f32_px(k_vals[1]), + Au::from_f32_px(k_vals[2]), + Au::from_f32_px(k_vals[3]), + ]; + CompositeOperatorKey::Arithmetic(k_vals) + } + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, MallocSizeOf, PartialEq, Hash, Eq)] +pub enum FilterPrimitiveKey { + Identity(ColorSpace, FilterPrimitiveInput), + Flood(ColorSpace, ColorU), + Blend(ColorSpace, MixBlendMode, FilterPrimitiveInput, FilterPrimitiveInput), + Blur(ColorSpace, Au, FilterPrimitiveInput), + Opacity(ColorSpace, Au, FilterPrimitiveInput), + ColorMatrix(ColorSpace, [Au; 20], FilterPrimitiveInput), + DropShadow(ColorSpace, (VectorKey, Au, ColorU), FilterPrimitiveInput), + ComponentTransfer(ColorSpace, FilterPrimitiveInput, Vec), + Offset(ColorSpace, FilterPrimitiveInput, VectorKey), + Composite(ColorSpace, FilterPrimitiveInput, FilterPrimitiveInput, CompositeOperatorKey), +} + +/// Represents a hashable description of how a picture primitive +/// will be composited into its parent. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, MallocSizeOf, PartialEq, Hash, Eq)] +pub enum PictureCompositeKey { + // No visual compositing effect + Identity, + + // FilterOp + Blur(Au), + Brightness(Au), + Contrast(Au), + Grayscale(Au), + HueRotate(Au), + Invert(Au), + Opacity(Au), + OpacityBinding(PropertyBindingId, Au), + Saturate(Au), + Sepia(Au), + DropShadows(Vec<(VectorKey, Au, ColorU)>), + ColorMatrix([Au; 20]), + SrgbToLinear, + LinearToSrgb, + ComponentTransfer(ItemUid), + Flood(ColorU), + SvgFilter(Vec), + + // MixBlendMode + Multiply, + Screen, + Overlay, + Darken, + Lighten, + ColorDodge, + ColorBurn, + HardLight, + SoftLight, + Difference, + Exclusion, + Hue, + Saturation, + Color, + Luminosity, +} + +impl From> for PictureCompositeKey { + fn from(mode: Option) -> Self { + match mode { + Some(PictureCompositeMode::MixBlend(mode)) => { + match mode { + MixBlendMode::Normal => PictureCompositeKey::Identity, + MixBlendMode::Multiply => PictureCompositeKey::Multiply, + MixBlendMode::Screen => PictureCompositeKey::Screen, + MixBlendMode::Overlay => PictureCompositeKey::Overlay, + MixBlendMode::Darken => PictureCompositeKey::Darken, + MixBlendMode::Lighten => PictureCompositeKey::Lighten, + MixBlendMode::ColorDodge => PictureCompositeKey::ColorDodge, + MixBlendMode::ColorBurn => PictureCompositeKey::ColorBurn, + MixBlendMode::HardLight => PictureCompositeKey::HardLight, + MixBlendMode::SoftLight => PictureCompositeKey::SoftLight, + MixBlendMode::Difference => PictureCompositeKey::Difference, + MixBlendMode::Exclusion => PictureCompositeKey::Exclusion, + MixBlendMode::Hue => PictureCompositeKey::Hue, + MixBlendMode::Saturation => PictureCompositeKey::Saturation, + MixBlendMode::Color => PictureCompositeKey::Color, + MixBlendMode::Luminosity => PictureCompositeKey::Luminosity, + } + } + Some(PictureCompositeMode::Filter(op)) => { + match op { + Filter::Blur(value) => PictureCompositeKey::Blur(Au::from_f32_px(value)), + Filter::Brightness(value) => PictureCompositeKey::Brightness(Au::from_f32_px(value)), + Filter::Contrast(value) => PictureCompositeKey::Contrast(Au::from_f32_px(value)), + Filter::Grayscale(value) => PictureCompositeKey::Grayscale(Au::from_f32_px(value)), + Filter::HueRotate(value) => PictureCompositeKey::HueRotate(Au::from_f32_px(value)), + Filter::Invert(value) => PictureCompositeKey::Invert(Au::from_f32_px(value)), + Filter::Saturate(value) => PictureCompositeKey::Saturate(Au::from_f32_px(value)), + Filter::Sepia(value) => PictureCompositeKey::Sepia(Au::from_f32_px(value)), + Filter::SrgbToLinear => PictureCompositeKey::SrgbToLinear, + Filter::LinearToSrgb => PictureCompositeKey::LinearToSrgb, + Filter::Identity => PictureCompositeKey::Identity, + Filter::DropShadows(ref shadows) => { + PictureCompositeKey::DropShadows( + shadows.iter().map(|shadow| { + (shadow.offset.into(), Au::from_f32_px(shadow.blur_radius), shadow.color.into()) + }).collect() + ) + } + Filter::Opacity(binding, _) => { + match binding { + PropertyBinding::Value(value) => { + PictureCompositeKey::Opacity(Au::from_f32_px(value)) + } + PropertyBinding::Binding(key, default) => { + PictureCompositeKey::OpacityBinding(key.id, Au::from_f32_px(default)) + } + } + } + Filter::ColorMatrix(values) => { + let mut quantized_values: [Au; 20] = [Au(0); 20]; + for (value, result) in values.iter().zip(quantized_values.iter_mut()) { + *result = Au::from_f32_px(*value); + } + PictureCompositeKey::ColorMatrix(quantized_values) + } + Filter::ComponentTransfer => unreachable!(), + Filter::Flood(color) => PictureCompositeKey::Flood(color.into()), + } + } + Some(PictureCompositeMode::ComponentTransferFilter(handle)) => { + PictureCompositeKey::ComponentTransfer(handle.uid()) + } + Some(PictureCompositeMode::SvgFilter(filter_primitives, filter_data)) => { + PictureCompositeKey::SvgFilter(filter_primitives.into_iter().map(|primitive| { + match primitive.kind { + FilterPrimitiveKind::Identity(identity) => FilterPrimitiveKey::Identity(primitive.color_space, identity.input), + FilterPrimitiveKind::Blend(blend) => FilterPrimitiveKey::Blend(primitive.color_space, blend.mode, blend.input1, blend.input2), + FilterPrimitiveKind::Flood(flood) => FilterPrimitiveKey::Flood(primitive.color_space, flood.color.into()), + FilterPrimitiveKind::Blur(blur) => FilterPrimitiveKey::Blur(primitive.color_space, Au::from_f32_px(blur.radius), blur.input), + FilterPrimitiveKind::Opacity(opacity) => + FilterPrimitiveKey::Opacity(primitive.color_space, Au::from_f32_px(opacity.opacity), opacity.input), + FilterPrimitiveKind::ColorMatrix(color_matrix) => { + let mut quantized_values: [Au; 20] = [Au(0); 20]; + for (value, result) in color_matrix.matrix.iter().zip(quantized_values.iter_mut()) { + *result = Au::from_f32_px(*value); + } + FilterPrimitiveKey::ColorMatrix(primitive.color_space, quantized_values, color_matrix.input) + } + FilterPrimitiveKind::DropShadow(drop_shadow) => { + FilterPrimitiveKey::DropShadow( + primitive.color_space, + ( + drop_shadow.shadow.offset.into(), + Au::from_f32_px(drop_shadow.shadow.blur_radius), + drop_shadow.shadow.color.into(), + ), + drop_shadow.input, + ) + } + FilterPrimitiveKind::ComponentTransfer(component_transfer) => + FilterPrimitiveKey::ComponentTransfer(primitive.color_space, component_transfer.input, filter_data.clone()), + FilterPrimitiveKind::Offset(info) => + FilterPrimitiveKey::Offset(primitive.color_space, info.input, info.offset.into()), + FilterPrimitiveKind::Composite(info) => + FilterPrimitiveKey::Composite(primitive.color_space, info.input1, info.input2, info.operator.into()), + } + }).collect()) + } + Some(PictureCompositeMode::Blit(_)) | + Some(PictureCompositeMode::TileCache { .. }) | + None => { + PictureCompositeKey::Identity + } + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)] +pub struct Picture { + pub composite_mode_key: PictureCompositeKey, +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)] +pub struct PictureKey { + pub composite_mode_key: PictureCompositeKey, +} + +impl PictureKey { + pub fn new( + pic: Picture, + ) -> Self { + PictureKey { + composite_mode_key: pic.composite_mode_key, + } + } +} + +impl InternDebug for PictureKey {} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct PictureData; + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct PictureTemplate; + +impl From for PictureTemplate { + fn from(_: PictureKey) -> Self { + PictureTemplate + } +} + +pub type PictureDataHandle = InternHandle; + +impl Internable for Picture { + type Key = PictureKey; + type StoreData = PictureTemplate; + type InternData = (); +} + +impl InternablePrimitive for Picture { + fn into_key( + self, + _: &LayoutPrimitiveInfo, + ) -> PictureKey { + PictureKey::new(self) + } + + fn make_instance_kind( + _key: PictureKey, + _: PictureDataHandle, + _: &mut PrimitiveStore, + _reference_frame_relative_offset: LayoutVector2D, + ) -> PrimitiveInstanceKind { + // Should never be hit as this method should not be + // called for pictures. + unreachable!(); + } +} + +impl IsVisible for Picture { + fn is_visible(&self) -> bool { + true + } +} + +#[test] +#[cfg(target_pointer_width = "64")] +fn test_struct_sizes() { + use std::mem; + // The sizes of these structures are critical for performance on a number of + // talos stress tests. If you get a failure here on CI, there's two possibilities: + // (a) You made a structure smaller than it currently is. Great work! Update the + // test expectations and move on. + // (b) You made a structure larger. This is not necessarily a problem, but should only + // be done with care, and after checking if talos performance regresses badly. + assert_eq!(mem::size_of::(), 88, "Picture size changed"); + assert_eq!(mem::size_of::(), 0, "PictureTemplate size changed"); + assert_eq!(mem::size_of::(), 88, "PictureKey size changed"); +} diff --git a/third_party/webrender/webrender/src/prim_store/text_run.rs b/third_party/webrender/webrender/src/prim_store/text_run.rs new file mode 100644 index 00000000000..a5c96e3c11d --- /dev/null +++ b/third_party/webrender/webrender/src/prim_store/text_run.rs @@ -0,0 +1,444 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{ColorF, FontInstanceFlags, GlyphInstance, RasterSpace, Shadow}; +use api::units::{LayoutToWorldTransform, LayoutVector2D, PictureRect}; +use crate::scene_building::{CreateShadow, IsVisible}; +use crate::frame_builder::FrameBuildingState; +use crate::glyph_rasterizer::{FontInstance, FontTransform, GlyphKey, FONT_SIZE_LIMIT}; +use crate::gpu_cache::GpuCache; +use crate::intern; +use crate::internal_types::LayoutPrimitiveInfo; +use crate::picture::{SubpixelMode, SurfaceInfo}; +use crate::prim_store::{PrimitiveOpacity, PrimitiveScratchBuffer}; +use crate::prim_store::{PrimitiveStore, PrimKeyCommonData, PrimTemplateCommonData}; +use crate::render_task_graph::RenderTaskGraph; +use crate::renderer::{MAX_VERTEX_TEXTURE_WIDTH}; +use crate::resource_cache::{ResourceCache}; +use crate::util::{MatrixHelpers}; +use crate::prim_store::{InternablePrimitive, PrimitiveInstanceKind, SpaceSnapper}; +use crate::spatial_tree::{SpatialTree, SpatialNodeIndex}; +use std::ops; +use std::sync::Arc; +use crate::storage; +use crate::util::PrimaryArc; + +/// A run of glyphs, with associated font information. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)] +pub struct TextRunKey { + pub common: PrimKeyCommonData, + pub font: FontInstance, + pub glyphs: PrimaryArc>, + pub shadow: bool, +} + +impl TextRunKey { + pub fn new( + info: &LayoutPrimitiveInfo, + text_run: TextRun, + ) -> Self { + TextRunKey { + common: info.into(), + font: text_run.font, + glyphs: PrimaryArc(text_run.glyphs), + shadow: text_run.shadow, + } + } +} + +impl intern::InternDebug for TextRunKey {} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(MallocSizeOf)] +pub struct TextRunTemplate { + pub common: PrimTemplateCommonData, + pub font: FontInstance, + #[ignore_malloc_size_of = "Measured via PrimaryArc"] + pub glyphs: Arc>, +} + +impl ops::Deref for TextRunTemplate { + type Target = PrimTemplateCommonData; + fn deref(&self) -> &Self::Target { + &self.common + } +} + +impl ops::DerefMut for TextRunTemplate { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.common + } +} + +impl From for TextRunTemplate { + fn from(item: TextRunKey) -> Self { + let common = PrimTemplateCommonData::with_key_common(item.common); + TextRunTemplate { + common, + font: item.font, + glyphs: item.glyphs.0, + } + } +} + +impl TextRunTemplate { + /// Update the GPU cache for a given primitive template. This may be called multiple + /// times per frame, by each primitive reference that refers to this interned + /// template. The initial request call to the GPU cache ensures that work is only + /// done if the cache entry is invalid (due to first use or eviction). + pub fn update( + &mut self, + frame_state: &mut FrameBuildingState, + ) { + self.write_prim_gpu_blocks(frame_state); + self.opacity = PrimitiveOpacity::translucent(); + } + + fn write_prim_gpu_blocks( + &mut self, + frame_state: &mut FrameBuildingState, + ) { + // corresponds to `fetch_glyph` in the shaders + if let Some(mut request) = frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) { + request.push(ColorF::from(self.font.color).premultiplied()); + // this is the only case where we need to provide plain color to GPU + let bg_color = ColorF::from(self.font.bg_color); + request.push([bg_color.r, bg_color.g, bg_color.b, 1.0]); + + let mut gpu_block = [0.0; 4]; + for (i, src) in self.glyphs.iter().enumerate() { + // Two glyphs are packed per GPU block. + + if (i & 1) == 0 { + gpu_block[0] = src.point.x; + gpu_block[1] = src.point.y; + } else { + gpu_block[2] = src.point.x; + gpu_block[3] = src.point.y; + request.push(gpu_block); + } + } + + // Ensure the last block is added in the case + // of an odd number of glyphs. + if (self.glyphs.len() & 1) != 0 { + request.push(gpu_block); + } + + assert!(request.current_used_block_num() <= MAX_VERTEX_TEXTURE_WIDTH); + } + } +} + +pub type TextRunDataHandle = intern::Handle; + +#[derive(Debug, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct TextRun { + pub font: FontInstance, + #[ignore_malloc_size_of = "Measured via PrimaryArc"] + pub glyphs: Arc>, + pub shadow: bool, +} + +impl intern::Internable for TextRun { + type Key = TextRunKey; + type StoreData = TextRunTemplate; + type InternData = (); +} + +impl InternablePrimitive for TextRun { + fn into_key( + self, + info: &LayoutPrimitiveInfo, + ) -> TextRunKey { + TextRunKey::new( + info, + self, + ) + } + + fn make_instance_kind( + key: TextRunKey, + data_handle: TextRunDataHandle, + prim_store: &mut PrimitiveStore, + reference_frame_relative_offset: LayoutVector2D, + ) -> PrimitiveInstanceKind { + let run_index = prim_store.text_runs.push(TextRunPrimitive { + used_font: key.font.clone(), + glyph_keys_range: storage::Range::empty(), + reference_frame_relative_offset, + snapped_reference_frame_relative_offset: reference_frame_relative_offset, + shadow: key.shadow, + raster_space: RasterSpace::Screen, + }); + + PrimitiveInstanceKind::TextRun{ data_handle, run_index } + } +} + +impl CreateShadow for TextRun { + fn create_shadow(&self, shadow: &Shadow) -> Self { + let mut font = FontInstance { + color: shadow.color.into(), + ..self.font.clone() + }; + if shadow.blur_radius > 0.0 { + font.disable_subpixel_aa(); + } + + TextRun { + font, + glyphs: self.glyphs.clone(), + shadow: true, + } + } +} + +impl IsVisible for TextRun { + fn is_visible(&self) -> bool { + self.font.color.a > 0 + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct TextRunPrimitive { + pub used_font: FontInstance, + pub glyph_keys_range: storage::Range, + pub reference_frame_relative_offset: LayoutVector2D, + pub snapped_reference_frame_relative_offset: LayoutVector2D, + pub shadow: bool, + pub raster_space: RasterSpace, +} + +impl TextRunPrimitive { + pub fn update_font_instance( + &mut self, + specified_font: &FontInstance, + surface: &SurfaceInfo, + spatial_node_index: SpatialNodeIndex, + transform: &LayoutToWorldTransform, + subpixel_mode: &SubpixelMode, + raster_space: RasterSpace, + prim_rect: PictureRect, + root_scaling_factor: f32, + spatial_tree: &SpatialTree, + ) -> bool { + // If local raster space is specified, include that in the scale + // of the glyphs that get rasterized. + // TODO(gw): Once we support proper local space raster modes, this + // will implicitly be part of the device pixel ratio for + // the (cached) local space surface, and so this code + // will no longer be required. + + let raster_scale = raster_space.local_scale().unwrap_or(1.0).max(0.001); + // root_scaling_factor is used to scale very large pictures that establish + // a raster root back to something sane, thus scale the device size accordingly. + // to the shader it looks like a change in DPI which it already supports. + let dps = surface.device_pixel_scale.0 * root_scaling_factor; + let font_size = specified_font.size.to_f32_px(); + let mut device_font_size = font_size * dps * raster_scale; + + // Check there is a valid transform that doesn't exceed the font size limit. + // Ensure the font is supposed to be rasterized in screen-space. + // Only support transforms that can be coerced to simple 2D transforms. + // Add texture padding to the rasterized glyph buffer when one anticipates + // the glyph will need to be scaled when rendered. + let (use_subpixel_aa, transform_glyphs, texture_padding, oversized) = if raster_space != RasterSpace::Screen || + transform.has_perspective_component() || !transform.has_2d_inverse() + { + (false, false, true, device_font_size > FONT_SIZE_LIMIT) + } else if transform.exceeds_2d_scale((FONT_SIZE_LIMIT / device_font_size) as f64) { + (false, false, true, true) + } else { + (true, !transform.is_simple_2d_translation(), false, false) + }; + + let font_transform = if transform_glyphs { + // Get the font transform matrix (skew / scale) from the complete transform. + // Fold in the device pixel scale. + self.raster_space = RasterSpace::Screen; + FontTransform::from(transform) + } else { + if oversized { + // Font sizes larger than the limit need to be scaled, thus can't use subpixels. + // In this case we adjust the font size and raster space to ensure + // we rasterize at the limit, to minimize the amount of scaling. + let limited_raster_scale = FONT_SIZE_LIMIT / (font_size * dps); + device_font_size = FONT_SIZE_LIMIT; + + // Record the raster space the text needs to be snapped in. The original raster + // scale would have been too big. + self.raster_space = RasterSpace::Local(limited_raster_scale); + } else { + // Record the raster space the text needs to be snapped in. We may have changed + // from RasterSpace::Screen due to a transform with perspective or without a 2d + // inverse, or it may have been RasterSpace::Local all along. + self.raster_space = RasterSpace::Local(raster_scale); + } + + // Rasterize the glyph without any transform + FontTransform::identity() + }; + + // TODO(aosmond): Snapping really ought to happen during scene building + // as much as possible. This will allow clips to be already adjusted + // based on the snapping requirements of the primitive. This may affect + // complex clips that create a different task, and when we rasterize + // glyphs without the transform (because the shader doesn't have the + // snap offsets to adjust its clip). These rects are fairly conservative + // to begin with and do not appear to be causing significant issues at + // this time. + self.snapped_reference_frame_relative_offset = if transform_glyphs { + // Don't touch the reference frame relative offset. We'll let the + // shader do the snapping in device pixels. + self.reference_frame_relative_offset + } else { + // There may be an animation, so snap the reference frame relative + // offset such that it excludes the impact, if any. + let snap_to_device = SpaceSnapper::new_with_target( + surface.raster_spatial_node_index, + spatial_node_index, + surface.device_pixel_scale, + spatial_tree, + ); + snap_to_device.snap_point(&self.reference_frame_relative_offset.to_point()).to_vector() + }; + + let mut flags = specified_font.flags; + if transform_glyphs { + flags |= FontInstanceFlags::TRANSFORM_GLYPHS; + } + if texture_padding { + flags |= FontInstanceFlags::TEXTURE_PADDING; + } + + // If the transform or device size is different, then the caller of + // this method needs to know to rebuild the glyphs. + let cache_dirty = + self.used_font.transform != font_transform || + self.used_font.size != device_font_size.into() || + self.used_font.flags != flags; + + // Construct used font instance from the specified font instance + self.used_font = FontInstance { + transform: font_transform, + size: device_font_size.into(), + flags, + ..specified_font.clone() + }; + + // If subpixel AA is disabled due to the backing surface the glyphs + // are being drawn onto, disable it (unless we are using the + // specifial subpixel mode that estimates background color). + let mut allow_subpixel = match subpixel_mode { + SubpixelMode::Allow => true, + SubpixelMode::Deny => false, + SubpixelMode::Conditional { allowed_rect, excluded_rects } => { + // Conditional mode allows subpixel AA to be enabled for this + // text run, so long as it doesn't intersect with any of the + // cutout rectangles in the list, and it's inside the allowed rect. + allowed_rect.contains_rect(&prim_rect) && + excluded_rects.iter().all(|rect| !rect.intersects(&prim_rect)) + } + }; + + // If we are using special estimated background subpixel blending, then + // we can allow it regardless of what the surface says. + allow_subpixel |= self.used_font.bg_color.a != 0; + + // If using local space glyphs, we don't want subpixel AA. + if !allow_subpixel || !use_subpixel_aa { + self.used_font.disable_subpixel_aa(); + + // Disable subpixel positioning for oversized glyphs to avoid + // thrashing the glyph cache with many subpixel variations of + // big glyph textures. A possible subpixel positioning error + // is small relative to the maximum font size and thus should + // not be very noticeable. + if oversized { + self.used_font.disable_subpixel_position(); + } + } + + cache_dirty + } + + pub fn request_resources( + &mut self, + prim_offset: LayoutVector2D, + prim_rect: PictureRect, + specified_font: &FontInstance, + glyphs: &[GlyphInstance], + transform: &LayoutToWorldTransform, + surface: &SurfaceInfo, + spatial_node_index: SpatialNodeIndex, + raster_space: RasterSpace, + root_scaling_factor: f32, + subpixel_mode: &SubpixelMode, + resource_cache: &mut ResourceCache, + gpu_cache: &mut GpuCache, + render_tasks: &mut RenderTaskGraph, + spatial_tree: &SpatialTree, + scratch: &mut PrimitiveScratchBuffer, + ) { + let cache_dirty = self.update_font_instance( + specified_font, + surface, + spatial_node_index, + transform, + subpixel_mode, + raster_space, + prim_rect, + root_scaling_factor, + spatial_tree, + ); + + if self.glyph_keys_range.is_empty() || cache_dirty { + let subpx_dir = self.used_font.get_subpx_dir(); + + let dps = surface.device_pixel_scale.0 * root_scaling_factor; + let transform = match self.raster_space { + RasterSpace::Local(scale) => FontTransform::new(scale * dps, 0.0, 0.0, scale * dps), + RasterSpace::Screen => self.used_font.transform.scale(dps), + }; + + self.glyph_keys_range = scratch.glyph_keys.extend( + glyphs.iter().map(|src| { + let src_point = src.point + prim_offset; + let device_offset = transform.transform(&src_point); + GlyphKey::new(src.index, device_offset, subpx_dir) + })); + } + + resource_cache.request_glyphs( + self.used_font.clone(), + &scratch.glyph_keys[self.glyph_keys_range], + gpu_cache, + render_tasks, + ); + } +} + +/// These are linux only because FontInstancePlatformOptions varies in size by platform. +#[test] +#[cfg(target_os = "linux")] +fn test_struct_sizes() { + use std::mem; + // The sizes of these structures are critical for performance on a number of + // talos stress tests. If you get a failure here on CI, there's two possibilities: + // (a) You made a structure smaller than it currently is. Great work! Update the + // test expectations and move on. + // (b) You made a structure larger. This is not necessarily a problem, but should only + // be done with care, and after checking if talos performance regresses badly. + assert_eq!(mem::size_of::(), 56, "TextRun size changed"); + assert_eq!(mem::size_of::(), 80, "TextRunTemplate size changed"); + assert_eq!(mem::size_of::(), 72, "TextRunKey size changed"); + assert_eq!(mem::size_of::(), 80, "TextRunPrimitive size changed"); +} diff --git a/third_party/webrender/webrender/src/print_tree.rs b/third_party/webrender/webrender/src/print_tree.rs new file mode 100644 index 00000000000..bfcb82fa2a5 --- /dev/null +++ b/third_party/webrender/webrender/src/print_tree.rs @@ -0,0 +1,105 @@ +/* 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 std::io::{stdout, Stdout, Write}; + +/// A struct that makes it easier to print out a pretty tree of data, which +/// can be visually scanned more easily. +pub struct PrintTree +where + W: Write +{ + /// The current level of recursion. + level: u32, + + /// An item which is queued up, so that we can determine if we need + /// a mid-tree prefix or a branch ending prefix. + queued_item: Option, + + /// The sink to print to. + sink: W, +} + +/// A trait that makes it easy to describe a pretty tree of data, +/// regardless of the printing destination, to either print it +/// directly to stdout, or serialize it as in the debugger +pub trait PrintTreePrinter { + fn new_level(&mut self, title: String); + fn end_level(&mut self); + fn add_item(&mut self, text: String); +} + +impl PrintTree { + pub fn new(title: &str) -> Self { + PrintTree::new_with_sink(title, stdout()) + } +} + +impl PrintTree +where + W: Write +{ + pub fn new_with_sink(title: &str, mut sink: W) -> Self { + writeln!(sink, "\u{250c} {}", title).unwrap(); + PrintTree { + level: 1, + queued_item: None, + sink, + } + } + + fn print_level_prefix(&mut self) { + for _ in 0 .. self.level { + write!(self.sink, "\u{2502} ").unwrap(); + } + } + + fn flush_queued_item(&mut self, prefix: &str) { + if let Some(queued_item) = self.queued_item.take() { + self.print_level_prefix(); + writeln!(self.sink, "{} {}", prefix, queued_item).unwrap(); + } + } +} + +// The default `println!` based printer +impl PrintTreePrinter for PrintTree +where + W: Write +{ + /// Descend one level in the tree with the given title. + fn new_level(&mut self, title: String) { + self.flush_queued_item("\u{251C}\u{2500}"); + + self.print_level_prefix(); + writeln!(self.sink, "\u{251C}\u{2500} {}", title).unwrap(); + + self.level = self.level + 1; + } + + /// Ascend one level in the tree. + fn end_level(&mut self) { + self.flush_queued_item("\u{2514}\u{2500}"); + self.level = self.level - 1; + } + + /// Add an item to the current level in the tree. + fn add_item(&mut self, text: String) { + self.flush_queued_item("\u{251C}\u{2500}"); + self.queued_item = Some(text); + } +} + +impl Drop for PrintTree +where + W: Write +{ + fn drop(&mut self) { + self.flush_queued_item("\u{9492}\u{9472}"); + } +} + +pub trait PrintableTree { + fn print_with(&self, pt: &mut T); +} diff --git a/third_party/webrender/webrender/src/profiler.rs b/third_party/webrender/webrender/src/profiler.rs new file mode 100644 index 00000000000..cd4d6f5069c --- /dev/null +++ b/third_party/webrender/webrender/src/profiler.rs @@ -0,0 +1,1901 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{ColorF, ColorU}; +use crate::debug_render::DebugRenderer; +use crate::device::query::{GpuSampler, GpuTimer, NamedTag}; +use euclid::{Point2D, Rect, Size2D, vec2, default}; +use crate::internal_types::FastHashMap; +use crate::renderer::{MAX_VERTEX_TEXTURE_WIDTH, wr_has_been_initialized}; +use std::collections::vec_deque::VecDeque; +use std::{f32, mem}; +use std::ffi::CStr; +use std::ops::Range; +use std::time::Duration; +use time::precise_time_ns; + +pub mod expected { + use std::ops::Range; + pub const AVG_BACKEND_CPU_TIME: Range = 0.0..3.0; + pub const MAX_BACKEND_CPU_TIME: Range = 0.0..6.0; + pub const AVG_RENDERER_CPU_TIME: Range = 0.0..5.0; + pub const MAX_RENDERER_CPU_TIME: Range = 0.0..10.0; + pub const AVG_IPC_TIME: Range = 0.0..2.0; + pub const MAX_IPC_TIME: Range = 0.0..4.0; + pub const AVG_GPU_TIME: Range = 0.0..8.0; + pub const MAX_GPU_TIME: Range = 0.0..15.0; + pub const DRAW_CALLS: Range = 1..100; + pub const VERTICES: Range = 10..25_000; + pub const TOTAL_PRIMITIVES: Range = 1..5000; + pub const VISIBLE_PRIMITIVES: Range = 1..5000; + pub const USED_TARGETS: Range = 1..4; + pub const COLOR_PASSES: Range = 1..4; + pub const ALPHA_PASSES: Range = 0..3; + pub const RENDERED_PICTURE_CACHE_TILES: Range = 0..5; + pub const TOTAL_PICTURE_CACHE_TILES: Range = 0..15; + pub const CREATED_TARGETS: Range = 0..3; + pub const CHANGED_TARGETS: Range = 0..3; + pub const TEXTURE_DATA_UPLOADED: Range = 0..10; + pub const GPU_CACHE_ROWS_TOTAL: Range = 1..50; + pub const GPU_CACHE_ROWS_UPDATED: Range = 0..25; + pub const GPU_CACHE_BLOCKS_TOTAL: Range = 1..65_000; + pub const GPU_CACHE_BLOCKS_UPDATED: Range = 0..1000; + pub const GPU_CACHE_BLOCKS_SAVED: Range = 0..50_000; + pub const DISPLAY_LIST_BUILD_TIME: Range = 0.0..3.0; + pub const MAX_SCENE_BUILD_TIME: Range = 0.0..3.0; + pub const DISPLAY_LIST_SEND_TIME: Range = 0.0..1.0; + pub const DISPLAY_LIST_TOTAL_TIME: Range = 0.0..4.0; + pub const NUM_FONT_TEMPLATES: Range = 0..50; + pub const FONT_TEMPLATES_MB: Range = 0.0..40.0; + pub const NUM_IMAGE_TEMPLATES: Range = 0..20; + pub const IMAGE_TEMPLATES_MB: Range = 0.0..10.0; + pub const DISPLAY_LIST_MB: Range = 0.0..0.2; + pub const NUM_RASTERIZED_BLOBS: Range = 0..25; // in tiles + pub const RASTERIZED_BLOBS_MB: Range = 0.0..4.0; +} + +const GRAPH_WIDTH: f32 = 1024.0; +const GRAPH_HEIGHT: f32 = 320.0; +const GRAPH_PADDING: f32 = 8.0; +const GRAPH_FRAME_HEIGHT: f32 = 16.0; +const PROFILE_PADDING: f32 = 8.0; + +const ONE_SECOND_NS: u64 = 1000000000; +const AVERAGE_OVER_NS: u64 = ONE_SECOND_NS / 2; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum ProfileStyle { + Full, + Compact, + Smart, +} + +/// Defines the interface for hooking up an external profiler to WR. +pub trait ProfilerHooks : Send + Sync { + /// Called at the beginning of a profile scope. The label must + /// be a C string (null terminated). + fn begin_marker(&self, label: &CStr); + + /// Called at the end of a profile scope. The label must + /// be a C string (null terminated). + fn end_marker(&self, label: &CStr); + + /// Called to mark an event happening. The label must + /// be a C string (null terminated). + fn event_marker(&self, label: &CStr); + + /// Called with a duration to indicate a text marker that just ended. Text + /// markers allow different types of entries to be recorded on the same row + /// in the timeline, by adding labels to the entry. + /// + /// This variant is also useful when the caller only wants to record events + /// longer than a certain threshold, and thus they don't know in advance + /// whether the event will qualify. + fn add_text_marker(&self, label: &CStr, text: &str, duration: Duration); + + /// Returns true if the current thread is being profiled. + fn thread_is_being_profiled(&self) -> bool; +} + +/// The current global profiler callbacks, if set by embedder. +pub static mut PROFILER_HOOKS: Option<&'static dyn ProfilerHooks> = None; + +/// Set the profiler callbacks, or None to disable the profiler. +/// This function must only ever be called before any WR instances +/// have been created, or the hooks will not be set. +pub fn set_profiler_hooks(hooks: Option<&'static dyn ProfilerHooks>) { + if !wr_has_been_initialized() { + unsafe { + PROFILER_HOOKS = hooks; + } + } +} + +/// A simple RAII style struct to manage a profile scope. +pub struct ProfileScope { + name: &'static CStr, +} + +/// Records a marker of the given duration that just ended. +pub fn add_text_marker(label: &CStr, text: &str, duration: Duration) { + unsafe { + if let Some(ref hooks) = PROFILER_HOOKS { + hooks.add_text_marker(label, text, duration); + } + } +} + +/// Records a marker of the given duration that just ended. +pub fn add_event_marker(label: &CStr) { + unsafe { + if let Some(ref hooks) = PROFILER_HOOKS { + hooks.event_marker(label); + } + } +} + +/// Returns true if the current thread is being profiled. +pub fn thread_is_being_profiled() -> bool { + unsafe { + PROFILER_HOOKS.map_or(false, |h| h.thread_is_being_profiled()) + } +} + +impl ProfileScope { + /// Begin a new profile scope + pub fn new(name: &'static CStr) -> Self { + unsafe { + if let Some(ref hooks) = PROFILER_HOOKS { + hooks.begin_marker(name); + } + } + + ProfileScope { + name, + } + } +} + +impl Drop for ProfileScope { + fn drop(&mut self) { + unsafe { + if let Some(ref hooks) = PROFILER_HOOKS { + hooks.end_marker(self.name); + } + } + } +} + +/// A helper macro to define profile scopes. +macro_rules! profile_marker { + ($string:expr) => { + let _scope = $crate::profiler::ProfileScope::new(cstr!($string)); + }; +} + +#[derive(Debug, Clone)] +pub struct GpuProfileTag { + pub label: &'static str, + pub color: ColorF, +} + +impl NamedTag for GpuProfileTag { + fn get_label(&self) -> &str { + self.label + } +} + +trait ProfileCounter { + fn description(&self) -> &'static str; + fn value(&self) -> String; + fn is_expected(&self) -> bool; +} + +#[derive(Clone)] +pub struct IntProfileCounter { + description: &'static str, + value: usize, + expect: Option>, +} + +impl IntProfileCounter { + fn new(description: &'static str, expect: Option>) -> Self { + IntProfileCounter { + description, + value: 0, + expect, + } + } + + #[inline(always)] + pub fn inc(&mut self) { + self.value += 1; + } + + pub fn set(&mut self, value: usize) { + self.value = value; + } +} + +impl ProfileCounter for IntProfileCounter { + fn description(&self) -> &'static str { + self.description + } + + fn value(&self) -> String { + format!("{}", self.value) + } + + fn is_expected(&self) -> bool { + self.expect.as_ref().map(|range| range.contains(&(self.value as u64))).unwrap_or(true) + } +} + +/// A profile counter recording average and maximum integer values over time slices +/// of half a second. +#[derive(Clone)] +pub struct AverageIntProfileCounter { + description: &'static str, + /// Start of the current time slice. + start_ns: u64, + /// Sum of the values recorded during the current time slice. + sum: u64, + /// Number of samples in the current time slice. + num_samples: u64, + /// The max value in in-progress time slice. + next_max: u64, + /// The max value of the previous time slice (displayed). + max: u64, + /// The average value of the previous time slice (displayed). + avg: u64, + /// Intermediate accumulator for `add` and `inc`. + accum: u64, + /// Expected average range of values, if any. + expect_avg: Option>, + /// Expected maximum range of values, if any. + expect_max: Option>, +} + +impl AverageIntProfileCounter { + pub fn new( + description: &'static str, + expect_avg: Option>, + expect_max: Option>, + ) -> Self { + AverageIntProfileCounter { + description, + start_ns: precise_time_ns(), + sum: 0, + num_samples: 0, + next_max: 0, + max: 0, + avg: 0, + accum: 0, + expect_avg, + expect_max, + } + } + + pub fn reset(&mut self) { + if self.accum > 0 { + self.set_u64(self.accum); + self.accum = 0; + } + } + + pub fn set(&mut self, val: usize) { + self.set_u64(val as u64); + } + + pub fn set_u64(&mut self, val: u64) { + let now = precise_time_ns(); + if (now - self.start_ns) > AVERAGE_OVER_NS && self.num_samples > 0 { + self.avg = self.sum / self.num_samples; + self.max = self.next_max; + self.start_ns = now; + self.sum = 0; + self.num_samples = 0; + self.next_max = 0; + } + self.next_max = self.next_max.max(val); + self.sum += val; + self.num_samples += 1; + self.accum = 0; + } + + pub fn add(&mut self, val: usize) { + self.accum += val as u64; + } + + pub fn inc(&mut self) { + self.accum += 1; + } + + pub fn get_accum(&mut self) -> u64{ + self.accum + } + + /// Returns either the most up to date value if the counter is updated + /// with add add inc, or the average over the previous time slice. + pub fn get(&self) -> usize { + let result = if self.accum != 0 { + self.accum + } else { + self.avg + }; + + result as usize + } +} + +impl ProfileCounter for AverageIntProfileCounter { + fn description(&self) -> &'static str { + self.description + } + + fn value(&self) -> String { + format!("{:.2} (max {:.2})", self.avg, self.max) + } + + fn is_expected(&self) -> bool { + self.expect_avg.as_ref().map(|range| range.contains(&self.avg)).unwrap_or(true) + && self.expect_max.as_ref().map(|range| range.contains(&self.max)).unwrap_or(true) + } +} + +pub struct PercentageProfileCounter { + description: &'static str, + value: f32, +} + +impl ProfileCounter for PercentageProfileCounter { + fn description(&self) -> &'static str { + self.description + } + + fn value(&self) -> String { + format!("{:.2}%", self.value * 100.0) + } + + fn is_expected(&self) -> bool { true } +} + +#[derive(Clone)] +pub struct ResourceProfileCounter { + description: &'static str, + value: usize, + // in bytes. + size: usize, + expected_count: Option>, + // in MB + expected_size: Option>, +} + +impl ResourceProfileCounter { + fn new( + description: &'static str, + expected_count: Option>, + expected_size: Option> + ) -> Self { + ResourceProfileCounter { + description, + value: 0, + size: 0, + expected_count, + expected_size, + } + } + + #[allow(dead_code)] + fn reset(&mut self) { + self.value = 0; + self.size = 0; + } + + #[inline(always)] + pub fn inc(&mut self, size: usize) { + self.value += 1; + self.size += size; + } + + pub fn set(&mut self, count: usize, size: usize) { + self.value = count; + self.size = size; + } + + pub fn size_mb(&self) -> f32 { + self.size as f32 / (1024.0 * 1024.0) + } +} + +impl ProfileCounter for ResourceProfileCounter { + fn description(&self) -> &'static str { + self.description + } + + fn value(&self) -> String { + format!("{} ({:.2} MB)", self.value, self.size_mb()) + } + + fn is_expected(&self) -> bool { + self.expected_count.as_ref().map(|range| range.contains(&self.value)).unwrap_or(true) + && self.expected_size.as_ref().map(|range| range.contains(&self.size_mb())).unwrap_or(true) + } +} + +#[derive(Clone)] +pub struct TimeProfileCounter { + description: &'static str, + nanoseconds: u64, + invert: bool, + expect_ms: Option>, +} + +pub struct Timer<'a> { + start: u64, + result: &'a mut u64, +} + +impl<'a> Drop for Timer<'a> { + fn drop(&mut self) { + let end = precise_time_ns(); + *self.result += end - self.start; + } +} + +impl TimeProfileCounter { + pub fn new(description: &'static str, invert: bool, expect_ms: Option>) -> Self { + TimeProfileCounter { + description, + nanoseconds: 0, + invert, + expect_ms, + } + } + + fn reset(&mut self) { + self.nanoseconds = 0; + } + + #[allow(dead_code)] + pub fn set(&mut self, ns: u64) { + self.nanoseconds = ns; + } + + pub fn profile(&mut self, callback: F) -> T + where + F: FnOnce() -> T, + { + let t0 = precise_time_ns(); + let val = callback(); + let t1 = precise_time_ns(); + let ns = t1 - t0; + self.nanoseconds += ns; + val + } + + pub fn timer(&mut self) -> Timer { + Timer { + start: precise_time_ns(), + result: &mut self.nanoseconds, + } + } + + pub fn inc(&mut self, ns: u64) { + self.nanoseconds += ns; + } + + pub fn get(&self) -> u64 { + self.nanoseconds + } + + pub fn get_ms(&self) -> f64 { + self.nanoseconds as f64 / 1000000.0 + } +} + +impl ProfileCounter for TimeProfileCounter { + fn description(&self) -> &'static str { + self.description + } + + fn value(&self) -> String { + if self.invert { + format!("{:.2} fps", 1000000000.0 / self.nanoseconds as f64) + } else { + format!("{:.2} ms", self.get_ms()) + } + } + + fn is_expected(&self) -> bool { + self.expect_ms.as_ref() + .map(|range| range.contains(&(self.nanoseconds as f64 / 1000000.0))) + .unwrap_or(true) + } +} + +#[derive(Clone)] +pub struct AverageTimeProfileCounter { + counter: AverageIntProfileCounter, + invert: bool, +} + +impl AverageTimeProfileCounter { + pub fn new( + description: &'static str, + invert: bool, + expect_avg: Option>, + expect_max: Option>, + ) -> Self { + let expect_avg_ns = expect_avg.map( + |range| (range.start * 1000000.0) as u64 .. (range.end * 1000000.0) as u64 + ); + let expect_max_ns = expect_max.map( + |range| (range.start * 1000000.0) as u64 .. (range.end * 1000000.0) as u64 + ); + + AverageTimeProfileCounter { + counter: AverageIntProfileCounter::new( + description, + expect_avg_ns, + expect_max_ns, + ), + invert, + } + } + + pub fn set(&mut self, ns: u64) { + self.counter.set_u64(ns); + } + + #[allow(dead_code)] + pub fn profile(&mut self, callback: F) -> T + where + F: FnOnce() -> T, + { + let t0 = precise_time_ns(); + let val = callback(); + let t1 = precise_time_ns(); + self.counter.set_u64(t1 - t0); + val + } + + pub fn avg_ms(&self) -> f64 { self.counter.avg as f64 / 1000000.0 } + + pub fn max_ms(&self) -> f64 { self.counter.max as f64 / 1000000.0 } +} + +impl ProfileCounter for AverageTimeProfileCounter { + fn description(&self) -> &'static str { + self.counter.description + } + + fn value(&self) -> String { + if self.invert { + format!("{:.2} fps", 1000000000.0 / self.counter.avg as f64) + } else { + format!("{:.2} ms (max {:.2} ms)", self.avg_ms(), self.max_ms()) + } + } + + fn is_expected(&self) -> bool { + self.counter.is_expected() + } +} + + +#[derive(Clone)] +pub struct FrameProfileCounters { + pub total_primitives: AverageIntProfileCounter, + pub visible_primitives: AverageIntProfileCounter, + pub targets_used: AverageIntProfileCounter, + pub targets_changed: AverageIntProfileCounter, + pub targets_created: AverageIntProfileCounter, +} + +impl FrameProfileCounters { + pub fn new() -> Self { + FrameProfileCounters { + total_primitives: AverageIntProfileCounter::new( + "Total Primitives", + None, Some(expected::TOTAL_PRIMITIVES), + ), + visible_primitives: AverageIntProfileCounter::new( + "Visible Primitives", + None, Some(expected::VISIBLE_PRIMITIVES), + ), + targets_used: AverageIntProfileCounter::new( + "Used targets", + None, Some(expected::USED_TARGETS), + ), + targets_changed: AverageIntProfileCounter::new( + "Changed targets", + None, Some(expected::CHANGED_TARGETS), + ), + targets_created: AverageIntProfileCounter::new( + "Created targets", + None, Some(expected::CREATED_TARGETS), + ), + } + } + + pub fn reset_targets(&mut self) { + self.targets_used.reset(); + self.targets_changed.reset(); + self.targets_created.reset(); + } +} + +#[derive(Clone)] +pub struct TextureCacheProfileCounters { + pub pages_alpha8_linear: ResourceProfileCounter, + pub pages_alpha16_linear: ResourceProfileCounter, + pub pages_color8_linear: ResourceProfileCounter, + pub pages_color8_nearest: ResourceProfileCounter, + pub pages_picture: ResourceProfileCounter, + pub rasterized_blob_pixels: ResourceProfileCounter, + pub standalone_bytes: IntProfileCounter, + pub shared_bytes: IntProfileCounter, +} + +impl TextureCacheProfileCounters { + pub fn new() -> Self { + TextureCacheProfileCounters { + pages_alpha8_linear: ResourceProfileCounter::new("Texture A8 cached pages", None, None), + pages_alpha16_linear: ResourceProfileCounter::new("Texture A16 cached pages", None, None), + pages_color8_linear: ResourceProfileCounter::new("Texture RGBA8 cached pages (L)", None, None), + pages_color8_nearest: ResourceProfileCounter::new("Texture RGBA8 cached pages (N)", None, None), + pages_picture: ResourceProfileCounter::new("Picture cached pages", None, None), + rasterized_blob_pixels: ResourceProfileCounter::new( + "Rasterized Blob Pixels", + Some(expected::NUM_RASTERIZED_BLOBS), + Some(expected::RASTERIZED_BLOBS_MB), + ), + standalone_bytes: IntProfileCounter::new("Standalone", None), + shared_bytes: IntProfileCounter::new("Shared", None), + } + } +} + +#[derive(Clone)] +pub struct GpuCacheProfileCounters { + pub allocated_rows: AverageIntProfileCounter, + pub allocated_blocks: AverageIntProfileCounter, + pub updated_rows: AverageIntProfileCounter, + pub updated_blocks: AverageIntProfileCounter, + pub saved_blocks: AverageIntProfileCounter, +} + +impl GpuCacheProfileCounters { + pub fn new() -> Self { + GpuCacheProfileCounters { + allocated_rows: AverageIntProfileCounter::new( + "GPU cache rows: total", + None, Some(expected::GPU_CACHE_ROWS_TOTAL), + ), + updated_rows: AverageIntProfileCounter::new( + "GPU cache rows: updated", + None, Some(expected::GPU_CACHE_ROWS_UPDATED), + ), + allocated_blocks: AverageIntProfileCounter::new( + "GPU cache blocks: total", + None, Some(expected::GPU_CACHE_BLOCKS_TOTAL), + ), + updated_blocks: AverageIntProfileCounter::new( + "GPU cache blocks: updated", + None, Some(expected::GPU_CACHE_BLOCKS_UPDATED), + ), + saved_blocks: AverageIntProfileCounter::new( + "GPU cache blocks: saved", + None, Some(expected::GPU_CACHE_BLOCKS_SAVED), + ), + } + } +} + +#[derive(Clone)] +pub struct BackendProfileCounters { + pub total_time: TimeProfileCounter, + pub resources: ResourceProfileCounters, + pub txn: TransactionProfileCounters, + pub intern: InternProfileCounters, + pub scene_changed: bool, +} + +#[derive(Clone)] +pub struct ResourceProfileCounters { + pub font_templates: ResourceProfileCounter, + pub image_templates: ResourceProfileCounter, + pub texture_cache: TextureCacheProfileCounters, + pub gpu_cache: GpuCacheProfileCounters, + pub content_slices: IntProfileCounter, +} + +#[derive(Clone)] +pub struct TransactionProfileCounters { + pub display_list_build_time: TimeProfileCounter, + pub scene_build_time: TimeProfileCounter, + /// Time between when the display list is built and when it is sent by the API. + pub content_send_time: TimeProfileCounter, + /// Time between sending the SetDisplayList from the API and picking it up on + /// the render scene builder thread. + pub api_send_time: TimeProfileCounter, + /// Sum of content_send_time and api_send_time. + pub total_send_time: TimeProfileCounter, + pub display_lists: ResourceProfileCounter, +} + +macro_rules! declare_intern_profile_counters { + ( $( $name:ident : $ty:ty, )+ ) => { + #[derive(Clone)] + pub struct InternProfileCounters { + $( + pub $name: ResourceProfileCounter, + )+ + } + + impl InternProfileCounters { + fn draw( + &self, + debug_renderer: &mut DebugRenderer, + draw_state: &mut DrawState, + ) { + Profiler::draw_counters( + &[ + $( + &self.$name, + )+ + ], + None, + debug_renderer, + false, + draw_state, + ); + } + } + } +} + +enumerate_interners!(declare_intern_profile_counters); + +impl TransactionProfileCounters { + pub fn set( + &mut self, + dl_build_start: u64, + dl_build_end: u64, + send_start: u64, + scene_build_start: u64, + scene_build_end: u64, + display_len: usize, + ) { + self.display_list_build_time.reset(); + self.content_send_time.reset(); + self.api_send_time.reset(); + self.total_send_time.reset(); + self.scene_build_time.reset(); + self.display_lists.reset(); + + let dl_build_time = dl_build_end - dl_build_start; + let scene_build_time = scene_build_end - scene_build_start; + let content_send_time = send_start - dl_build_end; + let api_send_time = scene_build_start - send_start; + self.display_list_build_time.inc(dl_build_time); + self.scene_build_time.inc(scene_build_time); + self.content_send_time.inc(content_send_time); + self.api_send_time.inc(api_send_time); + self.total_send_time.inc(content_send_time + api_send_time); + self.display_lists.inc(display_len); + } +} + +impl BackendProfileCounters { + pub fn new() -> Self { + BackendProfileCounters { + total_time: TimeProfileCounter::new( + "Backend CPU Time", false, + Some(expected::MAX_BACKEND_CPU_TIME), + ), + resources: ResourceProfileCounters { + font_templates: ResourceProfileCounter::new( + "Font Templates", + Some(expected::NUM_FONT_TEMPLATES), + Some(expected::FONT_TEMPLATES_MB), + ), + image_templates: ResourceProfileCounter::new( + "Image Templates", + Some(expected::NUM_IMAGE_TEMPLATES), + Some(expected::IMAGE_TEMPLATES_MB), + ), + content_slices: IntProfileCounter::new( + "Content Slices", + None, + ), + texture_cache: TextureCacheProfileCounters::new(), + gpu_cache: GpuCacheProfileCounters::new(), + }, + txn: TransactionProfileCounters { + display_list_build_time: TimeProfileCounter::new( + "DisplayList Build Time", false, + Some(expected::DISPLAY_LIST_BUILD_TIME) + ), + scene_build_time: TimeProfileCounter::new( + "Scene build time", false, + Some(expected::MAX_SCENE_BUILD_TIME), + ), + content_send_time: TimeProfileCounter::new( + "Content Send Time", false, + Some(expected::DISPLAY_LIST_SEND_TIME), + ), + api_send_time: TimeProfileCounter::new( + "API Send Time", false, + Some(expected::DISPLAY_LIST_SEND_TIME), + ), + total_send_time: TimeProfileCounter::new( + "Total IPC Time", false, + Some(expected::DISPLAY_LIST_TOTAL_TIME), + ), + display_lists: ResourceProfileCounter::new( + "DisplayLists Sent", + None, Some(expected::DISPLAY_LIST_MB), + ), + }, + //TODO: generate this by a macro + intern: InternProfileCounters { + prim: ResourceProfileCounter::new("Interned primitives", None, None), + conic_grad: ResourceProfileCounter::new("Interned conic gradients", None, None), + image: ResourceProfileCounter::new("Interned images", None, None), + image_border: ResourceProfileCounter::new("Interned image borders", None, None), + line_decoration: ResourceProfileCounter::new("Interned line decorations", None, None), + linear_grad: ResourceProfileCounter::new("Interned linear gradients", None, None), + normal_border: ResourceProfileCounter::new("Interned normal borders", None, None), + picture: ResourceProfileCounter::new("Interned pictures", None, None), + radial_grad: ResourceProfileCounter::new("Interned radial gradients", None, None), + text_run: ResourceProfileCounter::new("Interned text runs", None, None), + yuv_image: ResourceProfileCounter::new("Interned YUV images", None, None), + clip: ResourceProfileCounter::new("Interned clips", None, None), + filter_data: ResourceProfileCounter::new("Interned filter data", None, None), + backdrop: ResourceProfileCounter::new("Interned backdrops", None, None), + }, + scene_changed: false, + } + } + + pub fn reset(&mut self) { + self.total_time.reset(); + self.resources.texture_cache.rasterized_blob_pixels.reset(); + self.scene_changed = false; + } +} + +pub struct RendererProfileCounters { + pub frame_counter: IntProfileCounter, + pub frame_time: AverageTimeProfileCounter, + pub draw_calls: AverageIntProfileCounter, + pub vertices: AverageIntProfileCounter, + pub vao_count_and_size: ResourceProfileCounter, + pub color_passes: AverageIntProfileCounter, + pub alpha_passes: AverageIntProfileCounter, + pub texture_data_uploaded: AverageIntProfileCounter, + pub rendered_picture_cache_tiles: AverageIntProfileCounter, + pub total_picture_cache_tiles: AverageIntProfileCounter, +} + +pub struct RendererProfileTimers { + pub cpu_time: TimeProfileCounter, + pub gpu_graph: TimeProfileCounter, + pub gpu_samples: Vec>, +} + +impl RendererProfileCounters { + pub fn new() -> Self { + RendererProfileCounters { + frame_counter: IntProfileCounter::new("Frame", None), + frame_time: AverageTimeProfileCounter::new( + "FPS", true, None, None, + ), + draw_calls: AverageIntProfileCounter::new( + "Draw Calls", + None, Some(expected::DRAW_CALLS), + ), + vertices: AverageIntProfileCounter::new( + "Vertices", + None, Some(expected::VERTICES), + ), + vao_count_and_size: ResourceProfileCounter::new("VAO", None, None), + color_passes: AverageIntProfileCounter::new( + "Color passes", + None, Some(expected::COLOR_PASSES), + ), + alpha_passes: AverageIntProfileCounter::new( + "Alpha passes", + None, Some(expected::ALPHA_PASSES), + ), + texture_data_uploaded: AverageIntProfileCounter::new( + "Texture data, kb", + None, Some(expected::TEXTURE_DATA_UPLOADED), + ), + rendered_picture_cache_tiles: AverageIntProfileCounter::new( + "Rendered tiles", + None, Some(expected::RENDERED_PICTURE_CACHE_TILES), + ), + total_picture_cache_tiles: AverageIntProfileCounter::new( + "Total tiles", + None, Some(expected::TOTAL_PICTURE_CACHE_TILES), + ), + } + } + + pub fn reset(&mut self) { + self.draw_calls.reset(); + self.vertices.reset(); + self.color_passes.reset(); + self.alpha_passes.reset(); + self.texture_data_uploaded.reset(); + self.rendered_picture_cache_tiles.reset(); + self.total_picture_cache_tiles.reset(); + } +} + +impl RendererProfileTimers { + pub fn new() -> Self { + RendererProfileTimers { + cpu_time: TimeProfileCounter::new("Renderer CPU Time", false, None), + gpu_samples: Vec::new(), + gpu_graph: TimeProfileCounter::new("GPU Time", false, None), + } + } +} + +struct GraphStats { + min_value: f32, + mean_value: f32, + max_value: f32, +} + +struct ProfileGraph { + max_samples: usize, + scale: f32, + values: VecDeque, + short_description: &'static str, + unit_description: &'static str, +} + +impl ProfileGraph { + fn new( + max_samples: usize, + scale: f32, + short_description: &'static str, + unit_description: &'static str, + ) -> Self { + ProfileGraph { + max_samples, + scale, + values: VecDeque::new(), + short_description, + unit_description, + } + } + + fn push(&mut self, ns: u64) { + let val = ns as f64 * self.scale as f64; + if self.values.len() == self.max_samples { + self.values.pop_back(); + } + self.values.push_front(val as f32); + } + + fn stats(&self) -> GraphStats { + let mut stats = GraphStats { + min_value: f32::MAX, + mean_value: 0.0, + max_value: -f32::MAX, + }; + + for value in &self.values { + stats.min_value = stats.min_value.min(*value); + stats.mean_value += *value; + stats.max_value = stats.max_value.max(*value); + } + + if !self.values.is_empty() { + stats.mean_value /= self.values.len() as f32; + } + + stats + } + + fn draw_graph( + &self, + x: f32, + y: f32, + description: &'static str, + debug_renderer: &mut DebugRenderer, + ) -> default::Rect { + let size = Size2D::new(600.0, 100.0); + let line_height = debug_renderer.line_height(); + let graph_rect = Rect::new(Point2D::new(x, y), size); + let mut rect = graph_rect.inflate(10.0, 10.0); + + let stats = self.stats(); + + let text_color = ColorU::new(255, 255, 0, 255); + let text_origin = rect.origin + vec2(rect.size.width, 20.0); + debug_renderer.add_text( + text_origin.x, + text_origin.y, + description, + ColorU::new(0, 255, 0, 255), + None, + ); + debug_renderer.add_text( + text_origin.x, + text_origin.y + line_height, + &format!("Min: {:.2} {}", stats.min_value, self.unit_description), + text_color, + None, + ); + debug_renderer.add_text( + text_origin.x, + text_origin.y + line_height * 2.0, + &format!("Mean: {:.2} {}", stats.mean_value, self.unit_description), + text_color, + None, + ); + debug_renderer.add_text( + text_origin.x, + text_origin.y + line_height * 3.0, + &format!("Max: {:.2} {}", stats.max_value, self.unit_description), + text_color, + None, + ); + + rect.size.width += 140.0; + debug_renderer.add_quad( + rect.origin.x, + rect.origin.y, + rect.origin.x + rect.size.width + 10.0, + rect.origin.y + rect.size.height, + ColorU::new(25, 25, 25, 200), + ColorU::new(51, 51, 51, 200), + ); + + let bx1 = graph_rect.max_x(); + let by1 = graph_rect.max_y(); + + let w = graph_rect.size.width / self.max_samples as f32; + let h = graph_rect.size.height; + + let color_t0 = ColorU::new(0, 255, 0, 255); + let color_b0 = ColorU::new(0, 180, 0, 255); + + let color_t1 = ColorU::new(0, 255, 0, 255); + let color_b1 = ColorU::new(0, 180, 0, 255); + + let color_t2 = ColorU::new(255, 0, 0, 255); + let color_b2 = ColorU::new(180, 0, 0, 255); + + for (index, sample) in self.values.iter().enumerate() { + let sample = *sample; + let x1 = bx1 - index as f32 * w; + let x0 = x1 - w; + + let y0 = by1 - (sample / stats.max_value) as f32 * h; + let y1 = by1; + + let (color_top, color_bottom) = if sample < 1000.0 / 60.0 { + (color_t0, color_b0) + } else if sample < 1000.0 / 30.0 { + (color_t1, color_b1) + } else { + (color_t2, color_b2) + }; + + debug_renderer.add_quad(x0, y0, x1, y1, color_top, color_bottom); + } + + rect + } +} + +impl ProfileCounter for ProfileGraph { + fn description(&self) -> &'static str { + self.short_description + } + + fn value(&self) -> String { + format!("{:.2}ms", self.stats().mean_value) + } + + fn is_expected(&self) -> bool { true } +} + +struct GpuFrame { + total_time: u64, + samples: Vec>, +} + +struct GpuFrameCollection { + frames: VecDeque, +} + +impl GpuFrameCollection { + fn new() -> Self { + GpuFrameCollection { + frames: VecDeque::new(), + } + } + + fn push(&mut self, total_time: u64, samples: Vec>) { + if self.frames.len() == 20 { + self.frames.pop_back(); + } + self.frames.push_front(GpuFrame { + total_time, + samples, + }); + } +} + +impl GpuFrameCollection { + fn draw(&self, x: f32, y: f32, debug_renderer: &mut DebugRenderer) -> default::Rect { + let graph_rect = Rect::new( + Point2D::new(x, y), + Size2D::new(GRAPH_WIDTH, GRAPH_HEIGHT), + ); + let bounding_rect = graph_rect.inflate(GRAPH_PADDING, GRAPH_PADDING); + + debug_renderer.add_quad( + bounding_rect.origin.x, + bounding_rect.origin.y, + bounding_rect.origin.x + bounding_rect.size.width, + bounding_rect.origin.y + bounding_rect.size.height, + ColorU::new(25, 25, 25, 200), + ColorU::new(51, 51, 51, 200), + ); + + let w = graph_rect.size.width; + let mut y0 = graph_rect.origin.y; + + let max_time = self.frames + .iter() + .max_by_key(|f| f.total_time) + .unwrap() + .total_time as f32; + + let mut tags_present = FastHashMap::default(); + + for frame in &self.frames { + let y1 = y0 + GRAPH_FRAME_HEIGHT; + + let mut current_ns = 0; + for sample in &frame.samples { + let x0 = graph_rect.origin.x + w * current_ns as f32 / max_time; + current_ns += sample.time_ns; + let x1 = graph_rect.origin.x + w * current_ns as f32 / max_time; + let mut bottom_color = sample.tag.color; + bottom_color.a *= 0.5; + + debug_renderer.add_quad( + x0, + y0, + x1, + y1, + sample.tag.color.into(), + bottom_color.into(), + ); + + tags_present.insert(sample.tag.label, sample.tag.color); + } + + y0 = y1; + } + + // Add a legend to see which color correspond to what primitive. + const LEGEND_SIZE: f32 = 20.0; + const PADDED_LEGEND_SIZE: f32 = 25.0; + if !tags_present.is_empty() { + debug_renderer.add_quad( + bounding_rect.max_x() + GRAPH_PADDING, + bounding_rect.origin.y, + bounding_rect.max_x() + GRAPH_PADDING + 200.0, + bounding_rect.origin.y + tags_present.len() as f32 * PADDED_LEGEND_SIZE + GRAPH_PADDING, + ColorU::new(25, 25, 25, 200), + ColorU::new(51, 51, 51, 200), + ); + } + + for (i, (label, &color)) in tags_present.iter().enumerate() { + let x0 = bounding_rect.origin.x + bounding_rect.size.width + GRAPH_PADDING * 2.0; + let y0 = bounding_rect.origin.y + GRAPH_PADDING + i as f32 * PADDED_LEGEND_SIZE; + + debug_renderer.add_quad( + x0, y0, x0 + LEGEND_SIZE, y0 + LEGEND_SIZE, + color.into(), + color.into(), + ); + + debug_renderer.add_text( + x0 + PADDED_LEGEND_SIZE, + y0 + LEGEND_SIZE * 0.75, + label, + ColorU::new(255, 255, 0, 255), + None, + ); + } + + bounding_rect + } +} + +struct DrawState { + x_left: f32, + y_left: f32, + x_right: f32, + y_right: f32, +} + +pub struct Profiler { + draw_state: DrawState, + backend_graph: ProfileGraph, + renderer_graph: ProfileGraph, + gpu_graph: ProfileGraph, + ipc_graph: ProfileGraph, + display_list_build_graph: ProfileGraph, + scene_build_graph: ProfileGraph, + blob_raster_graph: ProfileGraph, + backend_time: AverageTimeProfileCounter, + renderer_time: AverageTimeProfileCounter, + gpu_time: AverageTimeProfileCounter, + ipc_time: AverageTimeProfileCounter, + gpu_frames: GpuFrameCollection, + cooldowns: Vec, +} + +impl Profiler { + pub fn new() -> Self { + let to_ms_scale = 1.0 / 1000000.0; + Profiler { + draw_state: DrawState { + x_left: 0.0, + y_left: 0.0, + x_right: 0.0, + y_right: 0.0, + }, + backend_graph: ProfileGraph::new(600, to_ms_scale, "Backend:", "ms"), + renderer_graph: ProfileGraph::new(600, to_ms_scale, "Renderer:", "ms"), + gpu_graph: ProfileGraph::new(600, to_ms_scale, "GPU:", "ms"), + ipc_graph: ProfileGraph::new(600, to_ms_scale, "IPC:", "ms"), + display_list_build_graph: ProfileGraph::new(600, to_ms_scale, "DisplayList build", "ms"), + scene_build_graph: ProfileGraph::new(600, to_ms_scale, "Scene build:", "ms"), + blob_raster_graph: ProfileGraph::new(600, 1.0, "Rasterized blob pixels:", "px"), + gpu_frames: GpuFrameCollection::new(), + backend_time: AverageTimeProfileCounter::new( + "Backend:", false, + Some(expected::AVG_BACKEND_CPU_TIME), + Some(expected::MAX_BACKEND_CPU_TIME), + ), + renderer_time: AverageTimeProfileCounter::new( + "Renderer:", false, + Some(expected::AVG_RENDERER_CPU_TIME), + Some(expected::MAX_RENDERER_CPU_TIME), + ), + ipc_time: AverageTimeProfileCounter::new( + "IPC:", false, + Some(expected::AVG_IPC_TIME), + Some(expected::MAX_IPC_TIME), + ), + gpu_time: AverageTimeProfileCounter::new( + "GPU:", false, + Some(expected::AVG_GPU_TIME), + Some(expected::MAX_GPU_TIME), + ), + cooldowns: Vec::new(), + } + } + + // If we have an array of "cooldown" counters, then only display profiles that + // are out of the ordinary and keep displaying them until the cooldown is over. + fn draw_counters( + counters: &[&T], + mut cooldowns: Option<&mut [i32]>, + debug_renderer: &mut DebugRenderer, + left: bool, + draw_state: &mut DrawState, + ) { + let mut label_rect = Rect::zero(); + let mut value_rect = Rect::zero(); + let (mut current_x, mut current_y) = if left { + (draw_state.x_left, draw_state.y_left) + } else { + (draw_state.x_right, draw_state.y_right) + }; + let mut color_index = 0; + let line_height = debug_renderer.line_height(); + + let colors = [ + // Regular values, + ColorU::new(255, 255, 255, 255), + ColorU::new(255, 255, 0, 255), + // Unexpected values, + ColorU::new(255, 80, 0, 255), + ColorU::new(255, 0, 0, 255), + ]; + + for (idx, counter) in counters.iter().enumerate() { + if let Some(cooldowns) = cooldowns.as_mut() { + if !counter.is_expected() { + cooldowns[idx] = 40; + } + if cooldowns[idx] == 0 { + continue; + } + } + let rect = debug_renderer.add_text( + current_x, + current_y, + counter.description(), + colors[color_index], + None, + ); + color_index = (color_index + 1) % 2; + + label_rect = label_rect.union(&rect); + current_y += line_height; + } + + color_index = 0; + current_x = label_rect.origin.x + label_rect.size.width + 60.0; + current_y = if left { draw_state.y_left } else { draw_state.y_right }; + + for (idx, counter) in counters.iter().enumerate() { + let expected_offset = if counter.is_expected() || cooldowns.is_some() { 0 } else { 2 }; + if let Some(cooldowns) = cooldowns.as_mut() { + if cooldowns[idx] > 0 { + cooldowns[idx] -= 1; + } else { + continue; + } + } + let rect = debug_renderer.add_text( + current_x, + current_y, + &counter.value(), + colors[color_index + expected_offset], + None, + ); + color_index = (color_index + 1) % 2; + + value_rect = value_rect.union(&rect); + current_y += line_height; + } + + let total_rect = label_rect.union(&value_rect).inflate(10.0, 10.0); + debug_renderer.add_quad( + total_rect.origin.x, + total_rect.origin.y, + total_rect.origin.x + total_rect.size.width, + total_rect.origin.y + total_rect.size.height, + ColorF::new(0.1, 0.1, 0.1, 0.8).into(), + ColorF::new(0.2, 0.2, 0.2, 0.8).into(), + ); + let new_y = total_rect.origin.y + total_rect.size.height + 30.0; + if left { + draw_state.y_left = new_y; + } else { + draw_state.y_right = new_y; + } + } + + fn draw_bar( + &mut self, + label: &str, + label_color: ColorU, + counters: &[(ColorU, &AverageIntProfileCounter)], + debug_renderer: &mut DebugRenderer, + ) -> default::Rect { + let mut rect = debug_renderer.add_text( + self.draw_state.x_left, + self.draw_state.y_left, + label, + label_color, + None, + ); + + let x_base = rect.origin.x + rect.size.width + 10.0; + let height = debug_renderer.line_height(); + let width = (self.draw_state.x_right - 30.0 - x_base).max(0.0); + let total_value = counters.last().unwrap().1.get(); + let scale = width / total_value as f32; + let mut x_current = x_base; + + for &(color, counter) in counters { + let x_stop = x_base + counter.get() as f32 * scale; + debug_renderer.add_quad( + x_current, + rect.origin.y, + x_stop, + rect.origin.y + height, + color, + color, + ); + x_current = x_stop; + } + + self.draw_state.y_left += height; + + rect.size.width += width + 10.0; + rect + } + + fn draw_gpu_cache_bars( + &mut self, + counters: &GpuCacheProfileCounters, + debug_renderer: &mut DebugRenderer, + ) { + let color_updated = ColorU::new(0xFF, 0, 0, 0xFF); + let color_free = ColorU::new(0, 0, 0xFF, 0xFF); + let color_saved = ColorU::new(0, 0xFF, 0, 0xFF); + + let mut requested_blocks = AverageIntProfileCounter::new("", None, None); + requested_blocks.set(counters.updated_blocks.get() + counters.saved_blocks.get()); + + let mut total_blocks = AverageIntProfileCounter::new("", None, None); + total_blocks.set(counters.allocated_rows.get() * MAX_VERTEX_TEXTURE_WIDTH); + + let rect0 = self.draw_bar( + &format!("GPU cache rows ({}):", counters.allocated_rows.get()), + ColorU::new(0xFF, 0xFF, 0xFF, 0xFF), + &[ + (color_updated, &counters.updated_rows), + (color_free, &counters.allocated_rows), + ], + debug_renderer, + ); + + let rect1 = self.draw_bar( + "GPU cache blocks", + ColorU::new(0xFF, 0xFF, 0, 0xFF), + &[ + (color_updated, &counters.updated_blocks), + (color_saved, &requested_blocks), + (color_free, &counters.allocated_blocks), + (ColorU::new(0, 0, 0, 0xFF), &total_blocks), + ], + debug_renderer, + ); + + let total_rect = rect0.union(&rect1).inflate(10.0, 10.0); + debug_renderer.add_quad( + total_rect.origin.x, + total_rect.origin.y, + total_rect.origin.x + total_rect.size.width, + total_rect.origin.y + total_rect.size.height, + ColorF::new(0.1, 0.1, 0.1, 0.8).into(), + ColorF::new(0.2, 0.2, 0.2, 0.8).into(), + ); + + self.draw_state.y_left = total_rect.origin.y + total_rect.size.height + 30.0; + } + + fn draw_frame_bars( + &mut self, + counters: &FrameProfileCounters, + debug_renderer: &mut DebugRenderer, + ) { + let rect0 = self.draw_bar( + &format!("primitives ({}):", counters.total_primitives.get()), + ColorU::new(0xFF, 0xFF, 0xFF, 0xFF), + &[ + (ColorU::new(0, 0, 0xFF, 0xFF), &counters.visible_primitives), + (ColorU::new(0, 0, 0, 0xFF), &counters.total_primitives), + ], + debug_renderer, + ); + + let rect1 = self.draw_bar( + &format!("GPU targets ({}):", &counters.targets_used.get()), + ColorU::new(0xFF, 0xFF, 0, 0xFF), + &[ + (ColorU::new(0, 0, 0xFF, 0xFF), &counters.targets_created), + (ColorU::new(0xFF, 0, 0, 0xFF), &counters.targets_changed), + (ColorU::new(0, 0xFF, 0, 0xFF), &counters.targets_used), + ], + debug_renderer, + ); + + let total_rect = rect0.union(&rect1).inflate(10.0, 10.0); + debug_renderer.add_quad( + total_rect.origin.x, + total_rect.origin.y, + total_rect.origin.x + total_rect.size.width, + total_rect.origin.y + total_rect.size.height, + ColorF::new(0.1, 0.1, 0.1, 0.8).into(), + ColorF::new(0.2, 0.2, 0.2, 0.8).into(), + ); + + self.draw_state.y_left = total_rect.origin.y + total_rect.size.height + 30.0; + } + + fn draw_compact_profile( + &mut self, + backend_profile: &BackendProfileCounters, + renderer_profile: &RendererProfileCounters, + debug_renderer: &mut DebugRenderer, + ) { + Profiler::draw_counters( + &[ + &renderer_profile.frame_time as &dyn ProfileCounter, + &renderer_profile.color_passes, + &renderer_profile.alpha_passes, + &renderer_profile.draw_calls, + &renderer_profile.vertices, + &renderer_profile.rendered_picture_cache_tiles, + &renderer_profile.texture_data_uploaded, + &backend_profile.resources.content_slices, + &self.ipc_time, + &self.backend_time, + &self.renderer_time, + &self.gpu_time, + ], + None, + debug_renderer, + true, + &mut self.draw_state, + ); + } + + fn draw_full_profile( + &mut self, + frame_profiles: &[FrameProfileCounters], + backend_profile: &BackendProfileCounters, + renderer_profile: &RendererProfileCounters, + renderer_timers: &mut RendererProfileTimers, + gpu_samplers: &[GpuSampler], + screen_fraction: f32, + debug_renderer: &mut DebugRenderer, + ) { + Profiler::draw_counters( + &[ + &renderer_profile.frame_time as &dyn ProfileCounter, + &renderer_profile.frame_counter, + &renderer_profile.color_passes, + &renderer_profile.alpha_passes, + &renderer_profile.rendered_picture_cache_tiles, + &renderer_profile.total_picture_cache_tiles, + &renderer_profile.texture_data_uploaded, + &backend_profile.resources.content_slices, + &backend_profile.resources.texture_cache.shared_bytes, + &backend_profile.resources.texture_cache.standalone_bytes, + ], + None, + debug_renderer, + true, + &mut self.draw_state + ); + + self.draw_gpu_cache_bars( + &backend_profile.resources.gpu_cache, + debug_renderer, + ); + + Profiler::draw_counters( + &[ + &backend_profile.resources.font_templates, + &backend_profile.resources.image_templates, + ], + None, + debug_renderer, + true, + &mut self.draw_state + ); + + backend_profile.intern.draw(debug_renderer, &mut self.draw_state); + + Profiler::draw_counters( + &[ + &backend_profile.resources.texture_cache.pages_alpha8_linear, + &backend_profile.resources.texture_cache.pages_color8_linear, + &backend_profile.resources.texture_cache.pages_color8_nearest, + &backend_profile.txn.display_lists, + ], + None, + debug_renderer, + true, + &mut self.draw_state + ); + + Profiler::draw_counters( + &[ + &backend_profile.txn.display_list_build_time, + &backend_profile.txn.scene_build_time, + &backend_profile.txn.content_send_time, + &backend_profile.txn.api_send_time, + &backend_profile.txn.total_send_time, + ], + None, + debug_renderer, + true, + &mut self.draw_state + ); + + for frame_profile in frame_profiles { + self.draw_frame_bars(frame_profile, debug_renderer); + } + + Profiler::draw_counters( + &[&renderer_profile.draw_calls, &renderer_profile.vertices], + None, + debug_renderer, + true, + &mut self.draw_state + ); + + Profiler::draw_counters( + &[ + &backend_profile.total_time, + &renderer_timers.cpu_time, + &renderer_timers.gpu_graph, + ], + None, + debug_renderer, + false, + &mut self.draw_state + ); + + if !gpu_samplers.is_empty() { + let mut samplers = Vec::::new(); + // Gathering unique GPU samplers. This has O(N^2) complexity, + // but we only have a few samplers per target. + let mut total = 0.0; + for sampler in gpu_samplers { + let value = sampler.count as f32 * screen_fraction; + total += value; + match samplers.iter().position(|s| { + s.description as *const _ == sampler.tag.label as *const _ + }) { + Some(pos) => samplers[pos].value += value, + None => samplers.push(PercentageProfileCounter { + description: sampler.tag.label, + value, + }), + } + } + samplers.push(PercentageProfileCounter { + description: "Total", + value: total, + }); + let samplers: Vec<&dyn ProfileCounter> = samplers.iter().map(|sampler| { + sampler as &dyn ProfileCounter + }).collect(); + Profiler::draw_counters( + &samplers, + None, + debug_renderer, + false, + &mut self.draw_state, + ); + } + + let rect = + self.backend_graph + .draw_graph(self.draw_state.x_right, self.draw_state.y_right, "CPU (backend)", debug_renderer); + self.draw_state.y_right += rect.size.height + PROFILE_PADDING; + let rect = self.renderer_graph.draw_graph( + self.draw_state.x_right, + self.draw_state.y_right, + "CPU (renderer)", + debug_renderer, + ); + self.draw_state.y_right += rect.size.height + PROFILE_PADDING; + let rect = + self.ipc_graph + .draw_graph(self.draw_state.x_right, self.draw_state.y_right, "DisplayList IPC", debug_renderer); + self.draw_state.y_right += rect.size.height + PROFILE_PADDING; + + let rect = self.display_list_build_graph + .draw_graph(self.draw_state.x_right, self.draw_state.y_right, "DisplayList build", debug_renderer); + self.draw_state.y_right += rect.size.height + PROFILE_PADDING; + + let rect = self.scene_build_graph + .draw_graph(self.draw_state.x_right, self.draw_state.y_right, "Scene build", debug_renderer); + self.draw_state.y_right += rect.size.height + PROFILE_PADDING; + + let rect = self.gpu_graph + .draw_graph(self.draw_state.x_right, self.draw_state.y_right, "GPU", debug_renderer); + self.draw_state.y_right += rect.size.height + PROFILE_PADDING; + + let rect = self.blob_raster_graph + .draw_graph(self.draw_state.x_right, self.draw_state.y_right, "Blob pixels", debug_renderer); + self.draw_state.y_right += rect.size.height + PROFILE_PADDING; + + let rect = self.gpu_frames + .draw(self.draw_state.x_left, f32::max(self.draw_state.y_left, self.draw_state.y_right), debug_renderer); + self.draw_state.y_right += rect.size.height + PROFILE_PADDING; + } + + fn draw_smart_profile( + &mut self, + backend_profile: &BackendProfileCounters, + renderer_profile: &RendererProfileCounters, + debug_renderer: &mut DebugRenderer, + ) { + while self.cooldowns.len() < 18 { + self.cooldowns.push(0); + } + + // Always show the fps counter. + Profiler::draw_counters( + &[ + &renderer_profile.frame_time, + ], + None, + debug_renderer, + true, + &mut self.draw_state, + ); + + let mut start = 0; + let counters: &[&[&dyn ProfileCounter]] = &[ + &[ + &self.backend_time, + &self.renderer_time, + &self.gpu_time, + ], + &[ + &renderer_profile.color_passes, + &renderer_profile.alpha_passes, + &renderer_profile.draw_calls, + &renderer_profile.vertices, + &renderer_profile.rendered_picture_cache_tiles, + &renderer_profile.total_picture_cache_tiles, + ], + &[ + &backend_profile.resources.gpu_cache.allocated_rows, + &backend_profile.resources.gpu_cache.updated_rows, + &backend_profile.resources.gpu_cache.allocated_blocks, + &backend_profile.resources.gpu_cache.updated_blocks, + &backend_profile.resources.gpu_cache.saved_blocks, + ], + &[ + &backend_profile.resources.image_templates, + &backend_profile.resources.font_templates, + &backend_profile.resources.texture_cache.rasterized_blob_pixels, + &backend_profile.txn.display_lists, + ], + ]; + + for group in counters { + let end = start + group.len(); + Profiler::draw_counters( + &group[..], + Some(&mut self.cooldowns[start..end]), + debug_renderer, + true, + &mut self.draw_state, + ); + start = end; + } + } + + pub fn draw_profile( + &mut self, + frame_profiles: &[FrameProfileCounters], + backend_profile: &BackendProfileCounters, + renderer_profile: &RendererProfileCounters, + renderer_timers: &mut RendererProfileTimers, + gpu_samplers: &[GpuSampler], + screen_fraction: f32, + debug_renderer: &mut DebugRenderer, + style: ProfileStyle, + ) { + self.draw_state.x_left = 20.0; + self.draw_state.y_left = 50.0; + self.draw_state.x_right = 450.0; + self.draw_state.y_right = 40.0; + + let mut gpu_graph = 0; + let gpu_graphrs = mem::replace(&mut renderer_timers.gpu_samples, Vec::new()); + for sample in &gpu_graphrs { + gpu_graph += sample.time_ns; + } + renderer_timers.gpu_graph.set(gpu_graph); + + self.backend_graph + .push(backend_profile.total_time.nanoseconds); + self.backend_time.set(backend_profile.total_time.nanoseconds); + self.renderer_graph + .push(renderer_timers.cpu_time.nanoseconds); + self.renderer_time.set(renderer_timers.cpu_time.nanoseconds); + self.ipc_graph + .push(backend_profile.txn.total_send_time.nanoseconds); + self.display_list_build_graph + .push(backend_profile.txn.display_list_build_time.nanoseconds); + self.scene_build_graph + .push(backend_profile.txn.scene_build_time.nanoseconds); + self.blob_raster_graph + .push(backend_profile.resources.texture_cache.rasterized_blob_pixels.size as u64); + self.ipc_time.set(backend_profile.txn.total_send_time.nanoseconds); + self.gpu_graph.push(gpu_graph); + self.gpu_time.set(gpu_graph); + self.gpu_frames.push(gpu_graph, gpu_graphrs); + + match style { + ProfileStyle::Full => { + self.draw_full_profile( + frame_profiles, + backend_profile, + renderer_profile, + renderer_timers, + gpu_samplers, + screen_fraction, + debug_renderer, + ); + } + ProfileStyle::Compact => { + self.draw_compact_profile( + backend_profile, + renderer_profile, + debug_renderer, + ); + } + ProfileStyle::Smart => { + self.draw_smart_profile( + backend_profile, + renderer_profile, + debug_renderer, + ); + } + } + } +} + +pub struct ChangeIndicator { + counter: u32, +} + +impl ChangeIndicator { + pub fn new() -> Self { + ChangeIndicator { + counter: 0 + } + } + + pub fn changed(&mut self) { + self.counter = (self.counter + 1) % 15; + } + + const WIDTH : f32 = 20.0; + const HEIGHT: f32 = 10.0; + + pub fn width() -> f32 { + ChangeIndicator::WIDTH * 16.0 + } + + pub fn draw( + &self, + x: f32, y: f32, + color: ColorU, + debug_renderer: &mut DebugRenderer + ) { + let margin = 0.0; + let tx = self.counter as f32 * ChangeIndicator::WIDTH; + debug_renderer.add_quad( + x - margin, + y - margin, + x + 15.0 * ChangeIndicator::WIDTH + margin, + y + ChangeIndicator::HEIGHT + margin, + ColorU::new(0, 0, 0, 150), + ColorU::new(0, 0, 0, 150), + ); + + debug_renderer.add_quad( + x + tx, + y, + x + tx + ChangeIndicator::WIDTH, + y + ChangeIndicator::HEIGHT, + color, + ColorU::new(25, 25, 25, 255), + ); + } +} diff --git a/third_party/webrender/webrender/src/render_backend.rs b/third_party/webrender/webrender/src/render_backend.rs new file mode 100644 index 00000000000..e22596a1bab --- /dev/null +++ b/third_party/webrender/webrender/src/render_backend.rs @@ -0,0 +1,2009 @@ +/* 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/. */ + +//! The high-level module responsible for managing the pipeline and preparing +//! commands to be issued by the `Renderer`. +//! +//! See the comment at the top of the `renderer` module for a description of +//! how these two pieces interact. + +use api::{ApiMsg, ClearCache, DebugCommand, DebugFlags, BlobImageHandler}; +use api::{DocumentId, DocumentLayer, ExternalScrollId, FrameMsg, HitTestFlags, HitTestResult}; +use api::{IdNamespace, MemoryReport, PipelineId, RenderNotifier, ScrollClamping}; +use api::{ScrollLocation, TransactionMsg, ResourceUpdate}; +use api::{NotificationRequest, Checkpoint, QualitySettings}; +use api::{ClipIntern, FilterDataIntern, PrimitiveKeyKind}; +use api::units::*; +#[cfg(any(feature = "capture", feature = "replay"))] +use api::CaptureBits; +#[cfg(feature = "replay")] +use api::CapturedDocument; +use crate::spatial_tree::SpatialNodeIndex; +#[cfg(any(feature = "capture", feature = "replay"))] +use crate::capture::CaptureConfig; +use crate::composite::{CompositorKind, CompositeDescriptor}; +#[cfg(feature = "debugger")] +use crate::debug_server; +use crate::frame_builder::{FrameBuilder, FrameBuilderConfig}; +use crate::glyph_rasterizer::{FontInstance}; +use crate::gpu_cache::GpuCache; +use crate::hit_test::{HitTest, HitTester, SharedHitTester}; +use crate::intern::DataStore; +use crate::internal_types::{DebugOutput, FastHashMap, RenderedDocument, ResultMsg}; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use crate::picture::{RetainedTiles, TileCacheLogger}; +use crate::prim_store::{PrimitiveScratchBuffer, PrimitiveInstance}; +use crate::prim_store::{PrimitiveInstanceKind, PrimTemplateCommonData, PrimitiveStore}; +use crate::prim_store::interned::*; +use crate::profiler::{BackendProfileCounters, ResourceProfileCounters}; +use crate::render_task_graph::RenderTaskGraphCounters; +use crate::renderer::{AsyncPropertySampler, PipelineInfo}; +use crate::resource_cache::ResourceCache; +#[cfg(feature = "replay")] +use crate::resource_cache::PlainCacheOwn; +#[cfg(feature = "replay")] +use crate::resource_cache::PlainResources; +#[cfg(feature = "replay")] +use crate::scene::Scene; +use crate::scene::{BuiltScene, SceneProperties}; +use crate::scene_builder_thread::*; +#[cfg(feature = "serialize")] +use serde::{Serialize, Deserialize}; +#[cfg(feature = "debugger")] +use serde_json; +#[cfg(feature = "replay")] +use std::collections::hash_map::Entry::{Occupied, Vacant}; +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::mpsc::{channel, Sender, Receiver}; +use std::time::{UNIX_EPOCH, SystemTime}; +use std::u32; +#[cfg(feature = "capture")] +use std::path::PathBuf; +#[cfg(feature = "replay")] +use crate::frame_builder::Frame; +use time::precise_time_ns; +use crate::util::{Recycler, VecHelper, drain_filter}; + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Copy, Clone)] +pub struct DocumentView { + scene: SceneView, + frame: FrameView, +} + +/// Some rendering parameters applying at the scene level. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Copy, Clone)] +pub struct SceneView { + pub device_rect: DeviceIntRect, + pub layer: DocumentLayer, + pub device_pixel_ratio: f32, + pub page_zoom_factor: f32, + pub quality_settings: QualitySettings, +} + +impl SceneView { + pub fn accumulated_scale_factor_for_snapping(&self) -> DevicePixelScale { + DevicePixelScale::new( + self.device_pixel_ratio * + self.page_zoom_factor + ) + } +} + +/// Some rendering parameters applying at the frame level. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Copy, Clone)] +pub struct FrameView { + pan: DeviceIntPoint, + pinch_zoom_factor: f32, +} + +impl DocumentView { + pub fn accumulated_scale_factor(&self) -> DevicePixelScale { + DevicePixelScale::new( + self.scene.device_pixel_ratio * + self.scene.page_zoom_factor * + self.frame.pinch_zoom_factor + ) + } +} + +#[derive(Copy, Clone, Hash, MallocSizeOf, PartialEq, PartialOrd, Debug, Eq, Ord)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct FrameId(usize); + +impl FrameId { + /// Returns a FrameId corresponding to the first frame. + /// + /// Note that we use 0 as the internal id here because the current code + /// increments the frame id at the beginning of the frame, rather than + /// at the end, and we want the first frame to be 1. It would probably + /// be sensible to move the advance() call to after frame-building, and + /// then make this method return FrameId(1). + pub fn first() -> Self { + FrameId(0) + } + + /// Returns the backing usize for this FrameId. + pub fn as_usize(&self) -> usize { + self.0 + } + + /// Advances this FrameId to the next frame. + pub fn advance(&mut self) { + self.0 += 1; + } + + /// An invalid sentinel FrameId, which will always compare less than + /// any valid FrameId. + pub const INVALID: FrameId = FrameId(0); +} + +impl Default for FrameId { + fn default() -> Self { + FrameId::INVALID + } +} + +impl ::std::ops::Add for FrameId { + type Output = Self; + fn add(self, other: usize) -> FrameId { + FrameId(self.0 + other) + } +} + +impl ::std::ops::Sub for FrameId { + type Output = Self; + fn sub(self, other: usize) -> FrameId { + assert!(self.0 >= other, "Underflow subtracting FrameIds"); + FrameId(self.0 - other) + } +} + +enum RenderBackendStatus { + Continue, + ShutDown(Option>), +} + +/// Identifier to track a sequence of frames. +/// +/// This is effectively a `FrameId` with a ridealong timestamp corresponding +/// to when advance() was called, which allows for more nuanced cache eviction +/// decisions. As such, we use the `FrameId` for equality and comparison, since +/// we should never have two `FrameStamps` with the same id but different +/// timestamps. +#[derive(Copy, Clone, Debug, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct FrameStamp { + id: FrameId, + time: SystemTime, + document_id: DocumentId, +} + +impl Eq for FrameStamp {} + +impl PartialEq for FrameStamp { + fn eq(&self, other: &Self) -> bool { + // We should not be checking equality unless the documents are the same + debug_assert!(self.document_id == other.document_id); + self.id == other.id + } +} + +impl PartialOrd for FrameStamp { + fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> { + self.id.partial_cmp(&other.id) + } +} + +impl FrameStamp { + /// Gets the FrameId in this stamp. + pub fn frame_id(&self) -> FrameId { + self.id + } + + /// Gets the time associated with this FrameStamp. + pub fn time(&self) -> SystemTime { + self.time + } + + /// Gets the DocumentId in this stamp. + pub fn document_id(&self) -> DocumentId { + self.document_id + } + + pub fn is_valid(&self) -> bool { + // If any fields are their default values, the whole struct should equal INVALID + debug_assert!((self.time != UNIX_EPOCH && self.id != FrameId(0) && self.document_id != DocumentId::INVALID) || + *self == Self::INVALID); + self.document_id != DocumentId::INVALID + } + + /// Returns a FrameStamp corresponding to the first frame. + pub fn first(document_id: DocumentId) -> Self { + FrameStamp { + id: FrameId::first(), + time: SystemTime::now(), + document_id, + } + } + + /// Advances to a new frame. + pub fn advance(&mut self) { + self.id.advance(); + self.time = SystemTime::now(); + } + + /// An invalid sentinel FrameStamp. + pub const INVALID: FrameStamp = FrameStamp { + id: FrameId(0), + time: UNIX_EPOCH, + document_id: DocumentId::INVALID, + }; +} + +macro_rules! declare_data_stores { + ( $( $name:ident : $ty:ty, )+ ) => { + /// A collection of resources that are shared by clips, primitives + /// between display lists. + #[cfg_attr(feature = "capture", derive(Serialize))] + #[cfg_attr(feature = "replay", derive(Deserialize))] + #[derive(Default)] + pub struct DataStores { + $( + pub $name: DataStore<$ty>, + )+ + } + + impl DataStores { + /// Reports CPU heap usage. + fn report_memory(&self, ops: &mut MallocSizeOfOps, r: &mut MemoryReport) { + $( + r.interning.data_stores.$name += self.$name.size_of(ops); + )+ + } + + fn apply_updates( + &mut self, + updates: InternerUpdates, + profile_counters: &mut BackendProfileCounters, + ) { + $( + self.$name.apply_updates( + updates.$name, + &mut profile_counters.intern.$name, + ); + )+ + } + } + } +} + +enumerate_interners!(declare_data_stores); + +impl DataStores { + /// Returns the local rect for a primitive. For most primitives, this is + /// stored in the template. For pictures, this is stored inside the picture + /// primitive instance itself, since this is determined during frame building. + pub fn get_local_prim_rect( + &self, + prim_instance: &PrimitiveInstance, + prim_store: &PrimitiveStore, + ) -> LayoutRect { + match prim_instance.kind { + PrimitiveInstanceKind::Picture { pic_index, .. } => { + let pic = &prim_store.pictures[pic_index.0]; + pic.precise_local_rect + } + _ => { + self.as_common_data(prim_instance).prim_rect + } + } + } + + /// Returns true if this primitive might need repition. + // TODO(gw): This seems like the wrong place for this - maybe this flag should + // not be in the common prim template data? + pub fn prim_may_need_repetition( + &self, + prim_instance: &PrimitiveInstance, + ) -> bool { + match prim_instance.kind { + PrimitiveInstanceKind::Picture { .. } => { + false + } + _ => { + self.as_common_data(prim_instance).may_need_repetition + } + } + } + + pub fn as_common_data( + &self, + prim_inst: &PrimitiveInstance + ) -> &PrimTemplateCommonData { + match prim_inst.kind { + PrimitiveInstanceKind::Rectangle { data_handle, .. } | + PrimitiveInstanceKind::Clear { data_handle, .. } => { + let prim_data = &self.prim[data_handle]; + &prim_data.common + } + PrimitiveInstanceKind::Image { data_handle, .. } => { + let prim_data = &self.image[data_handle]; + &prim_data.common + } + PrimitiveInstanceKind::ImageBorder { data_handle, .. } => { + let prim_data = &self.image_border[data_handle]; + &prim_data.common + } + PrimitiveInstanceKind::LineDecoration { data_handle, .. } => { + let prim_data = &self.line_decoration[data_handle]; + &prim_data.common + } + PrimitiveInstanceKind::LinearGradient { data_handle, .. } => { + let prim_data = &self.linear_grad[data_handle]; + &prim_data.common + } + PrimitiveInstanceKind::NormalBorder { data_handle, .. } => { + let prim_data = &self.normal_border[data_handle]; + &prim_data.common + } + PrimitiveInstanceKind::Picture { .. } => { + panic!("BUG: picture prims don't have common data!"); + } + PrimitiveInstanceKind::RadialGradient { data_handle, .. } => { + let prim_data = &self.radial_grad[data_handle]; + &prim_data.common + } + PrimitiveInstanceKind::ConicGradient { data_handle, .. } => { + let prim_data = &self.conic_grad[data_handle]; + &prim_data.common + } + PrimitiveInstanceKind::TextRun { data_handle, .. } => { + let prim_data = &self.text_run[data_handle]; + &prim_data.common + } + PrimitiveInstanceKind::YuvImage { data_handle, .. } => { + let prim_data = &self.yuv_image[data_handle]; + &prim_data.common + } + PrimitiveInstanceKind::Backdrop { data_handle, .. } => { + let prim_data = &self.backdrop[data_handle]; + &prim_data.common + } + } + } +} + +struct Document { + /// The id of this document + id: DocumentId, + + /// Temporary list of removed pipelines received from the scene builder + /// thread and forwarded to the renderer. + removed_pipelines: Vec<(PipelineId, DocumentId)>, + + view: DocumentView, + + /// The id and time of the current frame. + stamp: FrameStamp, + + /// The latest built scene, usable to build frames. + /// received from the scene builder thread. + scene: BuiltScene, + + /// The builder object that prodces frames, kept around to preserve some retained state. + frame_builder: FrameBuilder, + + /// A data structure to allow hit testing against rendered frames. This is updated + /// every time we produce a fully rendered frame. + hit_tester: Option>, + /// To avoid synchronous messaging we update a shared hit-tester that other threads + /// can query. + shared_hit_tester: Arc, + + /// Properties that are resolved during frame building and can be changed at any time + /// without requiring the scene to be re-built. + dynamic_properties: SceneProperties, + + /// Track whether the last built frame is up to date or if it will need to be re-built + /// before rendering again. + frame_is_valid: bool, + hit_tester_is_valid: bool, + rendered_frame_is_valid: bool, + /// We track this information to be able to display debugging information from the + /// renderer. + has_built_scene: bool, + + data_stores: DataStores, + + /// Contains various vecs of data that is used only during frame building, + /// where we want to recycle the memory each new display list, to avoid constantly + /// re-allocating and moving memory around. + scratch: PrimitiveScratchBuffer, + /// Keep track of the size of render task graph to pre-allocate memory up-front + /// the next frame. + render_task_counters: RenderTaskGraphCounters, + + #[cfg(feature = "replay")] + loaded_scene: Scene, + + /// Tracks the state of the picture cache tiles that were composited on the previous frame. + prev_composite_descriptor: CompositeDescriptor, +} + +impl Document { + pub fn new( + id: DocumentId, + size: DeviceIntSize, + layer: DocumentLayer, + default_device_pixel_ratio: f32, + ) -> Self { + Document { + id, + removed_pipelines: Vec::new(), + view: DocumentView { + scene: SceneView { + device_rect: size.into(), + layer, + page_zoom_factor: 1.0, + device_pixel_ratio: default_device_pixel_ratio, + quality_settings: QualitySettings::default(), + }, + frame: FrameView { + pan: DeviceIntPoint::new(0, 0), + pinch_zoom_factor: 1.0, + }, + }, + stamp: FrameStamp::first(id), + scene: BuiltScene::empty(), + frame_builder: FrameBuilder::new(), + hit_tester: None, + shared_hit_tester: Arc::new(SharedHitTester::new()), + dynamic_properties: SceneProperties::new(), + frame_is_valid: false, + hit_tester_is_valid: false, + rendered_frame_is_valid: false, + has_built_scene: false, + data_stores: DataStores::default(), + scratch: PrimitiveScratchBuffer::new(), + render_task_counters: RenderTaskGraphCounters::new(), + #[cfg(feature = "replay")] + loaded_scene: Scene::new(), + prev_composite_descriptor: CompositeDescriptor::empty(), + } + } + + fn can_render(&self) -> bool { + self.scene.has_root_pipeline + } + + fn has_pixels(&self) -> bool { + !self.view.scene.device_rect.size.is_empty() + } + + fn process_frame_msg( + &mut self, + message: FrameMsg, + ) -> DocumentOps { + match message { + FrameMsg::UpdateEpoch(pipeline_id, epoch) => { + self.scene.pipeline_epochs.insert(pipeline_id, epoch); + } + FrameMsg::Scroll(delta, cursor) => { + profile_scope!("Scroll"); + + let node_index = match self.hit_tester { + Some(ref hit_tester) => { + // Ideally we would call self.scroll_nearest_scrolling_ancestor here, but + // we need have to avoid a double-borrow. + let test = HitTest::new(None, cursor, HitTestFlags::empty()); + hit_tester.find_node_under_point(test) + } + None => { + None + } + }; + + if self.hit_tester.is_some() + && self.scroll_nearest_scrolling_ancestor(delta, node_index) { + self.hit_tester_is_valid = false; + self.frame_is_valid = false; + } + + return DocumentOps { + // TODO: Does it make sense to track this as a scrolling even if we + // ended up not scrolling anything? + scroll: true, + ..DocumentOps::nop() + }; + } + FrameMsg::HitTest(pipeline_id, point, flags, tx) => { + if !self.hit_tester_is_valid { + self.rebuild_hit_tester(); + } + + let result = match self.hit_tester { + Some(ref hit_tester) => { + hit_tester.hit_test(HitTest::new(pipeline_id, point, flags)) + } + None => HitTestResult { items: Vec::new() }, + }; + + tx.send(result).unwrap(); + } + FrameMsg::RequestHitTester(tx) => { + tx.send(self.shared_hit_tester.clone()).unwrap(); + } + FrameMsg::SetPan(pan) => { + if self.view.frame.pan != pan { + self.view.frame.pan = pan; + self.hit_tester_is_valid = false; + self.frame_is_valid = false; + } + } + FrameMsg::ScrollNodeWithId(origin, id, clamp) => { + profile_scope!("ScrollNodeWithScrollId"); + + if self.scroll_node(origin, id, clamp) { + self.hit_tester_is_valid = false; + self.frame_is_valid = false; + } + + return DocumentOps { + scroll: true, + ..DocumentOps::nop() + }; + } + FrameMsg::GetScrollNodeState(tx) => { + profile_scope!("GetScrollNodeState"); + tx.send(self.scene.spatial_tree.get_scroll_node_state()).unwrap(); + } + FrameMsg::UpdateDynamicProperties(property_bindings) => { + self.dynamic_properties.set_properties(property_bindings); + } + FrameMsg::AppendDynamicTransformProperties(property_bindings) => { + self.dynamic_properties.add_transforms(property_bindings); + } + FrameMsg::SetPinchZoom(factor) => { + if self.view.frame.pinch_zoom_factor != factor.get() { + self.view.frame.pinch_zoom_factor = factor.get(); + self.frame_is_valid = false; + } + } + FrameMsg::SetIsTransformAsyncZooming(is_zooming, animation_id) => { + let node = self.scene.spatial_tree.spatial_nodes.iter_mut() + .find(|node| node.is_transform_bound_to_property(animation_id)); + if let Some(node) = node { + if node.is_async_zooming != is_zooming { + node.is_async_zooming = is_zooming; + self.frame_is_valid = false; + } + } + } + } + + DocumentOps::nop() + } + + fn build_frame( + &mut self, + resource_cache: &mut ResourceCache, + gpu_cache: &mut GpuCache, + resource_profile: &mut ResourceProfileCounters, + debug_flags: DebugFlags, + tile_cache_logger: &mut TileCacheLogger, + ) -> RenderedDocument { + let accumulated_scale_factor = self.view.accumulated_scale_factor(); + let pan = self.view.frame.pan.to_f32() / accumulated_scale_factor; + + // Advance to the next frame. + self.stamp.advance(); + + assert!(self.stamp.frame_id() != FrameId::INVALID, + "First frame increment must happen before build_frame()"); + + let frame = { + let frame = self.frame_builder.build( + &mut self.scene, + resource_cache, + gpu_cache, + self.stamp, + accumulated_scale_factor, + self.view.scene.layer, + self.view.scene.device_rect.origin, + pan, + resource_profile, + &self.dynamic_properties, + &mut self.data_stores, + &mut self.scratch, + &mut self.render_task_counters, + debug_flags, + tile_cache_logger, + ); + + frame + }; + + self.frame_is_valid = true; + + let is_new_scene = self.has_built_scene; + self.has_built_scene = false; + + RenderedDocument { + frame, + is_new_scene, + } + } + + fn rebuild_hit_tester(&mut self) { + let accumulated_scale_factor = self.view.accumulated_scale_factor(); + let pan = self.view.frame.pan.to_f32() / accumulated_scale_factor; + + self.scene.spatial_tree.update_tree( + pan, + accumulated_scale_factor, + &self.dynamic_properties, + ); + + let hit_tester = Arc::new(self.scene.create_hit_tester(&self.data_stores.clip)); + self.hit_tester = Some(Arc::clone(&hit_tester)); + self.shared_hit_tester.update(hit_tester); + self.hit_tester_is_valid = true; + } + + pub fn updated_pipeline_info(&mut self) -> PipelineInfo { + let removed_pipelines = self.removed_pipelines.take_and_preallocate(); + PipelineInfo { + epochs: self.scene.pipeline_epochs.iter() + .map(|(&pipeline_id, &epoch)| ((pipeline_id, self.id), epoch)).collect(), + removed_pipelines, + } + } + + /// Returns true if any nodes actually changed position or false otherwise. + pub fn scroll_nearest_scrolling_ancestor( + &mut self, + scroll_location: ScrollLocation, + scroll_node_index: Option, + ) -> bool { + self.scene.spatial_tree.scroll_nearest_scrolling_ancestor(scroll_location, scroll_node_index) + } + + /// Returns true if the node actually changed position or false otherwise. + pub fn scroll_node( + &mut self, + origin: LayoutPoint, + id: ExternalScrollId, + clamp: ScrollClamping + ) -> bool { + self.scene.spatial_tree.scroll_node(origin, id, clamp) + } + + pub fn new_async_scene_ready( + &mut self, + built_scene: BuiltScene, + recycler: &mut Recycler, + ) { + self.frame_is_valid = false; + self.hit_tester_is_valid = false; + + // Give the old scene a chance to destroy any resources. + // Right now, all this does is build a hash map of any cached + // surface tiles, that can be provided to the next scene. + // TODO(nical) - It's a bit awkward how these retained tiles live + // in the scene's prim store then temporarily in the frame builder + // and then presumably back in the prim store during the next frame + // build. + let mut retained_tiles = RetainedTiles::new(); + self.scene.prim_store.destroy(&mut retained_tiles); + let old_scrolling_states = self.scene.spatial_tree.drain(); + + self.scene = built_scene; + + // Provide any cached tiles from the previous scene to + // the newly built one. + self.frame_builder.set_retained_resources(retained_tiles); + + self.scratch.recycle(recycler); + + self.scene.spatial_tree.finalize_and_apply_pending_scroll_offsets(old_scrolling_states); + } +} + +struct DocumentOps { + scroll: bool, +} + +impl DocumentOps { + fn nop() -> Self { + DocumentOps { + scroll: false, + } + } +} + +/// The unique id for WR resource identification. +/// The namespace_id should start from 1. +static NEXT_NAMESPACE_ID: AtomicUsize = AtomicUsize::new(1); + +#[cfg(any(feature = "capture", feature = "replay"))] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct PlainRenderBackend { + default_device_pixel_ratio: f32, + frame_config: FrameBuilderConfig, + documents: FastHashMap, + resource_sequence_id: u32, +} + +/// The render backend is responsible for transforming high level display lists into +/// GPU-friendly work which is then submitted to the renderer in the form of a frame::Frame. +/// +/// The render backend operates on its own thread. +pub struct RenderBackend { + api_rx: Receiver, + result_tx: Sender, + scene_tx: Sender, + low_priority_scene_tx: Sender, + backend_scene_tx: Sender, + scene_rx: Receiver, + + default_device_pixel_ratio: f32, + + gpu_cache: GpuCache, + resource_cache: ResourceCache, + + frame_config: FrameBuilderConfig, + default_compositor_kind: CompositorKind, + documents: FastHashMap, + + notifier: Box, + tile_cache_logger: TileCacheLogger, + sampler: Option>, + size_of_ops: Option, + debug_flags: DebugFlags, + namespace_alloc_by_client: bool, + + // We keep one around to be able to call clear_namespace + // after the api object is deleted. For most purposes the + // api object's blob handler should be used instead. + blob_image_handler: Option>, + + recycler: Recycler, + #[cfg(feature = "capture")] + capture_config: Option, + #[cfg(feature = "replay")] + loaded_resource_sequence_id: u32, +} + +impl RenderBackend { + pub fn new( + api_rx: Receiver, + result_tx: Sender, + scene_tx: Sender, + low_priority_scene_tx: Sender, + backend_scene_tx: Sender, + scene_rx: Receiver, + default_device_pixel_ratio: f32, + resource_cache: ResourceCache, + notifier: Box, + blob_image_handler: Option>, + frame_config: FrameBuilderConfig, + sampler: Option>, + size_of_ops: Option, + debug_flags: DebugFlags, + namespace_alloc_by_client: bool, + ) -> RenderBackend { + RenderBackend { + api_rx, + result_tx, + scene_tx, + low_priority_scene_tx, + backend_scene_tx, + scene_rx, + default_device_pixel_ratio, + resource_cache, + gpu_cache: GpuCache::new(), + frame_config, + default_compositor_kind : frame_config.compositor_kind, + documents: FastHashMap::default(), + notifier, + tile_cache_logger: TileCacheLogger::new(500usize), + sampler, + size_of_ops, + debug_flags, + namespace_alloc_by_client, + recycler: Recycler::new(), + blob_image_handler, + #[cfg(feature = "capture")] + capture_config: None, + #[cfg(feature = "replay")] + loaded_resource_sequence_id: 0, + } + } + + fn next_namespace_id(&self) -> IdNamespace { + IdNamespace(NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed) as u32) + } + + pub fn run(&mut self, mut profile_counters: BackendProfileCounters) { + let mut frame_counter: u32 = 0; + let mut status = RenderBackendStatus::Continue; + + if let Some(ref sampler) = self.sampler { + sampler.register(); + } + + while let RenderBackendStatus::Continue = status { + while let Ok(msg) = self.scene_rx.try_recv() { + profile_scope!("rb_msg"); + + match msg { + SceneBuilderResult::Transactions(txns, result_tx) => { + self.process_transaction( + txns, + result_tx, + &mut frame_counter, + &mut profile_counters, + ); + self.bookkeep_after_frames(); + }, + #[cfg(feature = "capture")] + SceneBuilderResult::CapturedTransactions(txns, capture_config, result_tx) => { + if let Some(ref mut old_config) = self.capture_config { + assert!(old_config.scene_id <= capture_config.scene_id); + if old_config.scene_id < capture_config.scene_id { + old_config.scene_id = capture_config.scene_id; + old_config.frame_id = 0; + } + } else { + self.capture_config = Some(capture_config); + } + + let built_frame = self.process_transaction( + txns, + result_tx, + &mut frame_counter, + &mut profile_counters, + ); + + if built_frame { + self.save_capture_sequence(); + } + + self.bookkeep_after_frames(); + }, + SceneBuilderResult::GetGlyphDimensions(request) => { + let mut glyph_dimensions = Vec::with_capacity(request.glyph_indices.len()); + if let Some(base) = self.resource_cache.get_font_instance(request.key) { + let font = FontInstance::from_base(Arc::clone(&base)); + for glyph_index in &request.glyph_indices { + let glyph_dim = self.resource_cache.get_glyph_dimensions(&font, *glyph_index); + glyph_dimensions.push(glyph_dim); + } + } + request.sender.send(glyph_dimensions).unwrap(); + } + SceneBuilderResult::GetGlyphIndices(request) => { + let mut glyph_indices = Vec::with_capacity(request.text.len()); + for ch in request.text.chars() { + let index = self.resource_cache.get_glyph_index(request.key, ch); + glyph_indices.push(index); + } + request.sender.send(glyph_indices).unwrap(); + } + SceneBuilderResult::FlushComplete(tx) => { + tx.send(()).ok(); + } + SceneBuilderResult::ExternalEvent(evt) => { + self.notifier.external_event(evt); + } + SceneBuilderResult::ClearNamespace(id) => { + self.resource_cache.clear_namespace(id); + self.documents.retain(|doc_id, _doc| doc_id.namespace_id != id); + if let Some(handler) = &mut self.blob_image_handler { + handler.clear_namespace(id); + } + } + SceneBuilderResult::Stopped => { + panic!("We haven't sent a Stop yet, how did we get a Stopped back?"); + } + SceneBuilderResult::DocumentsForDebugger(json) => { + let msg = ResultMsg::DebugOutput(DebugOutput::FetchDocuments(json)); + self.result_tx.send(msg).unwrap(); + self.notifier.wake_up(); + } + } + } + + status = match self.api_rx.recv() { + Ok(msg) => { + self.process_api_msg(msg, &mut profile_counters, &mut frame_counter) + } + Err(..) => { RenderBackendStatus::ShutDown(None) } + }; + } + + let _ = self.low_priority_scene_tx.send(SceneBuilderRequest::Stop); + // Ensure we read everything the scene builder is sending us from + // inflight messages, otherwise the scene builder might panic. + while let Ok(msg) = self.scene_rx.recv() { + match msg { + SceneBuilderResult::FlushComplete(tx) => { + // If somebody's blocked waiting for a flush, how did they + // trigger the RB thread to shut down? This shouldn't happen + // but handle it gracefully anyway. + debug_assert!(false); + tx.send(()).ok(); + } + SceneBuilderResult::Stopped => break, + _ => continue, + } + } + + self.documents.clear(); + + self.notifier.shut_down(); + + if let Some(ref sampler) = self.sampler { + sampler.deregister(); + } + + + if let RenderBackendStatus::ShutDown(Some(sender)) = status { + let _ = sender.send(()); + } + } + + fn process_transaction( + &mut self, + mut txns: Vec>, + result_tx: Option>, + frame_counter: &mut u32, + profile_counters: &mut BackendProfileCounters, + ) -> bool { + self.prepare_for_frames(); + self.maybe_force_nop_documents( + frame_counter, + profile_counters, + |document_id| txns.iter().any(|txn| txn.document_id == document_id)); + + let mut built_frame = false; + for mut txn in txns.drain(..) { + let has_built_scene = txn.built_scene.is_some(); + + if let Some(timings) = txn.timings { + if has_built_scene { + profile_counters.scene_changed = true; + } + + profile_counters.txn.set( + timings.builder_start_time_ns, + timings.builder_end_time_ns, + timings.send_time_ns, + timings.scene_build_start_time_ns, + timings.scene_build_end_time_ns, + timings.display_list_len, + ); + } + + if let Some(doc) = self.documents.get_mut(&txn.document_id) { + doc.removed_pipelines.append(&mut txn.removed_pipelines); + doc.view.scene = txn.view; + + if let Some(built_scene) = txn.built_scene.take() { + doc.new_async_scene_ready( + built_scene, + &mut self.recycler, + ); + } + + // If there are any additions or removals of clip modes + // during the scene build, apply them to the data store now. + // This needs to happen before we build the hit tester. + if let Some(updates) = txn.interner_updates.take() { + #[cfg(feature = "capture")] + { + if self.debug_flags.contains(DebugFlags::TILE_CACHE_LOGGING_DBG) { + self.tile_cache_logger.serialize_updates(&updates); + } + } + doc.data_stores.apply_updates(updates, profile_counters); + } + + // Build the hit tester while the APZ lock is held so that its content + // is in sync with the gecko APZ tree. + if !doc.hit_tester_is_valid { + doc.rebuild_hit_tester(); + } + + if let Some(ref tx) = result_tx { + let (resume_tx, resume_rx) = channel(); + tx.send(SceneSwapResult::Complete(resume_tx)).unwrap(); + // Block until the post-swap hook has completed on + // the scene builder thread. We need to do this before + // we can sample from the sampler hook which might happen + // in the update_document call below. + resume_rx.recv().ok(); + } + + for pipeline_id in &txn.discard_frame_state_for_pipelines { + doc.scene + .spatial_tree + .discard_frame_state_for_pipeline(*pipeline_id); + } + } else { + // The document was removed while we were building it, skip it. + // TODO: we might want to just ensure that removed documents are + // always forwarded to the scene builder thread to avoid this case. + if let Some(ref tx) = result_tx { + tx.send(SceneSwapResult::Aborted).unwrap(); + } + continue; + } + + self.resource_cache.add_rasterized_blob_images( + txn.rasterized_blobs.take(), + &mut profile_counters.resources.texture_cache, + ); + + built_frame |= self.update_document( + txn.document_id, + txn.resource_updates.take(), + txn.frame_ops.take(), + txn.notifications.take(), + txn.render_frame, + txn.invalidate_rendered_frame, + frame_counter, + profile_counters, + has_built_scene, + ); + } + + built_frame + } + + fn process_api_msg( + &mut self, + msg: ApiMsg, + profile_counters: &mut BackendProfileCounters, + frame_counter: &mut u32, + ) -> RenderBackendStatus { + match msg { + ApiMsg::WakeUp => {} + ApiMsg::WakeSceneBuilder => { + self.scene_tx.send(SceneBuilderRequest::WakeUp).unwrap(); + } + ApiMsg::FlushSceneBuilder(tx) => { + self.low_priority_scene_tx.send(SceneBuilderRequest::Flush(tx)).unwrap(); + } + ApiMsg::GetGlyphDimensions(request) => { + self.scene_tx.send(SceneBuilderRequest::GetGlyphDimensions(request)).unwrap(); + } + ApiMsg::GetGlyphIndices(request) => { + self.scene_tx.send(SceneBuilderRequest::GetGlyphIndices(request)).unwrap(); + } + ApiMsg::CloneApi(sender) => { + assert!(!self.namespace_alloc_by_client); + sender.send(self.next_namespace_id()).unwrap(); + } + ApiMsg::CloneApiByClient(namespace_id) => { + assert!(self.namespace_alloc_by_client); + debug_assert!(!self.documents.iter().any(|(did, _doc)| did.namespace_id == namespace_id)); + } + ApiMsg::AddDocument(document_id, initial_size, layer) => { + let document = Document::new( + document_id, + initial_size, + layer, + self.default_device_pixel_ratio, + ); + let old = self.documents.insert(document_id, document); + debug_assert!(old.is_none()); + + self.scene_tx.send( + SceneBuilderRequest::AddDocument(document_id, initial_size, layer) + ).unwrap(); + + } + ApiMsg::DeleteDocument(document_id) => { + self.documents.remove(&document_id); + self.low_priority_scene_tx.send( + SceneBuilderRequest::DeleteDocument(document_id) + ).unwrap(); + } + ApiMsg::ExternalEvent(evt) => { + self.low_priority_scene_tx.send(SceneBuilderRequest::ExternalEvent(evt)).unwrap(); + } + ApiMsg::ClearNamespace(id) => { + self.low_priority_scene_tx.send(SceneBuilderRequest::ClearNamespace(id)).unwrap(); + } + ApiMsg::MemoryPressure => { + // This is drastic. It will basically flush everything out of the cache, + // and the next frame will have to rebuild all of its resources. + // We may want to look into something less extreme, but on the other hand this + // should only be used in situations where are running low enough on memory + // that we risk crashing if we don't do something about it. + // The advantage of clearing the cache completely is that it gets rid of any + // remaining fragmentation that could have persisted if we kept around the most + // recently used resources. + self.resource_cache.clear(ClearCache::all()); + + self.gpu_cache.clear(); + + let resource_updates = self.resource_cache.pending_updates(); + let msg = ResultMsg::UpdateResources { + resource_updates, + memory_pressure: true, + }; + self.result_tx.send(msg).unwrap(); + self.notifier.wake_up(); + } + ApiMsg::ReportMemory(tx) => { + self.report_memory(tx); + } + ApiMsg::DebugCommand(option) => { + let msg = match option { + DebugCommand::EnableDualSourceBlending(enable) => { + // Set in the config used for any future documents + // that are created. + self.frame_config + .dual_source_blending_is_enabled = enable; + self.update_frame_builder_config(); + + // We don't want to forward this message to the renderer. + return RenderBackendStatus::Continue; + } + DebugCommand::SetPictureTileSize(tile_size) => { + self.frame_config.tile_size_override = tile_size; + self.update_frame_builder_config(); + + return RenderBackendStatus::Continue; + } + DebugCommand::FetchDocuments => { + // Ask SceneBuilderThread to send JSON presentation of the documents, + // that will be forwarded to Renderer. + self.send_backend_message(BackendSceneBuilderRequest::DocumentsForDebugger); + return RenderBackendStatus::Continue; + } + DebugCommand::FetchClipScrollTree => { + let json = self.get_spatial_tree_for_debugger(); + ResultMsg::DebugOutput(DebugOutput::FetchClipScrollTree(json)) + } + #[cfg(feature = "capture")] + DebugCommand::SaveCapture(root, bits) => { + let output = self.save_capture(root, bits, profile_counters); + ResultMsg::DebugOutput(output) + }, + #[cfg(feature = "capture")] + DebugCommand::StartCaptureSequence(root, bits) => { + self.start_capture_sequence(root, bits); + return RenderBackendStatus::Continue; + }, + #[cfg(feature = "capture")] + DebugCommand::StopCaptureSequence => { + self.stop_capture_sequence(); + return RenderBackendStatus::Continue; + }, + #[cfg(feature = "replay")] + DebugCommand::LoadCapture(path, ids, tx) => { + NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed); + *frame_counter += 1; + + let mut config = CaptureConfig::new(path, CaptureBits::all()); + if let Some((scene_id, frame_id)) = ids { + config.scene_id = scene_id; + config.frame_id = frame_id; + } + + self.load_capture(config, profile_counters); + + for (id, doc) in &self.documents { + let captured = CapturedDocument { + document_id: *id, + root_pipeline_id: doc.loaded_scene.root_pipeline_id, + }; + tx.send(captured).unwrap(); + } + + // Note: we can't pass `LoadCapture` here since it needs to arrive + // before the `PublishDocument` messages sent by `load_capture`. + return RenderBackendStatus::Continue; + } + DebugCommand::ClearCaches(mask) => { + self.resource_cache.clear(mask); + return RenderBackendStatus::Continue; + } + DebugCommand::EnableNativeCompositor(enable) => { + // Default CompositorKind should be Native + if let CompositorKind::Draw { .. } = self.default_compositor_kind { + unreachable!(); + } + + let compositor_kind = if enable { + self.default_compositor_kind + } else { + CompositorKind::default() + }; + + for (_, doc) in &mut self.documents { + doc.scene.config.compositor_kind = compositor_kind; + doc.frame_is_valid = false; + } + + self.frame_config.compositor_kind = compositor_kind; + self.update_frame_builder_config(); + + // We don't want to forward this message to the renderer. + return RenderBackendStatus::Continue; + } + DebugCommand::EnableMultithreading(enable) => { + self.resource_cache.enable_multithreading(enable); + return RenderBackendStatus::Continue; + } + DebugCommand::SetBatchingLookback(count) => { + self.frame_config.batch_lookback_count = count as usize; + self.update_frame_builder_config(); + + return RenderBackendStatus::Continue; + } + DebugCommand::SimulateLongSceneBuild(time_ms) => { + self.scene_tx.send(SceneBuilderRequest::SimulateLongSceneBuild(time_ms)).unwrap(); + return RenderBackendStatus::Continue; + } + DebugCommand::SimulateLongLowPrioritySceneBuild(time_ms) => { + self.low_priority_scene_tx.send( + SceneBuilderRequest::SimulateLongLowPrioritySceneBuild(time_ms) + ).unwrap(); + return RenderBackendStatus::Continue; + } + DebugCommand::SetFlags(flags) => { + self.resource_cache.set_debug_flags(flags); + self.gpu_cache.set_debug_flags(flags); + + // If we're toggling on the GPU cache debug display, we + // need to blow away the cache. This is because we only + // send allocation/free notifications to the renderer + // thread when the debug display is enabled, and thus + // enabling it when the cache is partially populated will + // give the renderer an incomplete view of the world. + // And since we might as well drop all the debugging state + // from the renderer when we disable the debug display, + // we just clear the cache on toggle. + let changed = self.debug_flags ^ flags; + if changed.contains(DebugFlags::GPU_CACHE_DBG) { + self.gpu_cache.clear(); + } + self.debug_flags = flags; + + ResultMsg::DebugCommand(option) + } + _ => ResultMsg::DebugCommand(option), + }; + self.result_tx.send(msg).unwrap(); + self.notifier.wake_up(); + } + ApiMsg::ShutDown(sender) => { + info!("Recycling stats: {:?}", self.recycler); + return RenderBackendStatus::ShutDown(sender); + } + ApiMsg::UpdateDocuments(transaction_msgs) => { + self.prepare_transactions( + transaction_msgs, + frame_counter, + profile_counters, + ); + } + } + + RenderBackendStatus::Continue + } + + fn update_frame_builder_config(&self) { + self.send_backend_message( + BackendSceneBuilderRequest::SetFrameBuilderConfig( + self.frame_config.clone() + ) + ); + } + + fn prepare_for_frames(&mut self) { + self.gpu_cache.prepare_for_frames(); + } + + fn bookkeep_after_frames(&mut self) { + self.gpu_cache.bookkeep_after_frames(); + } + + fn requires_frame_build(&mut self) -> bool { + self.gpu_cache.requires_frame_build() + } + + fn prepare_transactions( + &mut self, + txns: Vec>, + frame_counter: &mut u32, + profile_counters: &mut BackendProfileCounters, + ) { + let mut use_scene_builder = txns.iter() + .any(|transaction_msg| transaction_msg.use_scene_builder_thread); + let use_high_priority = txns.iter() + .any(|transaction_msg| !transaction_msg.low_priority); + + use_scene_builder = use_scene_builder || txns.iter().any(|txn| { + !txn.scene_ops.is_empty() + || !txn.blob_requests.is_empty() + || txn.blob_rasterizer.is_some() + }); + + if !use_scene_builder { + self.prepare_for_frames(); + self.maybe_force_nop_documents( + frame_counter, + profile_counters, + |document_id| txns.iter().any(|txn| txn.document_id == document_id)); + + let mut built_frame = false; + for mut txn in txns { + built_frame |= self.update_document( + txn.document_id, + txn.resource_updates.take(), + txn.frame_ops.take(), + txn.notifications.take(), + txn.generate_frame, + txn.invalidate_rendered_frame, + frame_counter, + profile_counters, + false + ); + } + if built_frame { + #[cfg(feature = "capture")] + self.save_capture_sequence(); + } + self.bookkeep_after_frames(); + return; + } + + let tx = if use_high_priority { + &self.scene_tx + } else { + &self.low_priority_scene_tx + }; + + tx.send(SceneBuilderRequest::Transactions(txns)).unwrap(); + } + + /// In certain cases, resources shared by multiple documents have to run + /// maintenance operations, like cleaning up unused cache items. In those + /// cases, we are forced to build frames for all documents, however we + /// may not have a transaction ready for every document - this method + /// calls update_document with the details of a fake, nop transaction just + /// to force a frame build. + fn maybe_force_nop_documents(&mut self, + frame_counter: &mut u32, + profile_counters: &mut BackendProfileCounters, + document_already_present: F) where + F: Fn(DocumentId) -> bool { + if self.requires_frame_build() { + let nop_documents : Vec = self.documents.keys() + .cloned() + .filter(|key| !document_already_present(*key)) + .collect(); + #[allow(unused_variables)] + let mut built_frame = false; + for &document_id in &nop_documents { + built_frame |= self.update_document( + document_id, + Vec::default(), + Vec::default(), + Vec::default(), + false, + false, + frame_counter, + profile_counters, + false); + } + #[cfg(feature = "capture")] + match built_frame { + true => self.save_capture_sequence(), + _ => {}, + } + } + } + + fn update_document( + &mut self, + document_id: DocumentId, + resource_updates: Vec, + mut frame_ops: Vec, + mut notifications: Vec, + mut render_frame: bool, + invalidate_rendered_frame: bool, + frame_counter: &mut u32, + profile_counters: &mut BackendProfileCounters, + has_built_scene: bool, + ) -> bool { + let requested_frame = render_frame; + + let requires_frame_build = self.requires_frame_build(); + let doc = self.documents.get_mut(&document_id).unwrap(); + // If we have a sampler, get more frame ops from it and add them + // to the transaction. This is a hook to allow the WR user code to + // fiddle with things after a potentially long scene build, but just + // before rendering. This is useful for rendering with the latest + // async transforms. + if requested_frame || has_built_scene { + if let Some(ref sampler) = self.sampler { + frame_ops.append(&mut sampler.sample(document_id, + &doc.scene.pipeline_epochs)); + } + } + + doc.has_built_scene |= has_built_scene; + + // TODO: this scroll variable doesn't necessarily mean we scrolled. It is only used + // for something wrench specific and we should remove it. + let mut scroll = false; + for frame_msg in frame_ops { + let _timer = profile_counters.total_time.timer(); + let op = doc.process_frame_msg(frame_msg); + scroll |= op.scroll; + } + + for update in &resource_updates { + if let ResourceUpdate::UpdateImage(..) = update { + doc.frame_is_valid = false; + } + } + + self.resource_cache.post_scene_building_update( + resource_updates, + &mut profile_counters.resources, + ); + + if doc.dynamic_properties.flush_pending_updates() { + doc.frame_is_valid = false; + doc.hit_tester_is_valid = false; + } + + if !doc.can_render() { + // TODO: this happens if we are building the first scene asynchronously and + // scroll at the same time. we should keep track of the fact that we skipped + // composition here and do it as soon as we receive the scene. + render_frame = false; + } + + // Avoid re-building the frame if the current built frame is still valid. + // However, if the resource_cache requires a frame build, _always_ do that, unless + // doc.can_render() is false, as in that case a frame build can't happen anyway. + // We want to ensure we do this because even if the doc doesn't have pixels it + // can still try to access stale texture cache items. + let build_frame = (render_frame && !doc.frame_is_valid && doc.has_pixels()) || + (requires_frame_build && doc.can_render()); + + // Request composite is true when we want to composite frame even when + // there is no frame update. This happens when video frame is updated under + // external image with NativeTexture or when platform requested to composite frame. + if invalidate_rendered_frame { + doc.rendered_frame_is_valid = false; + if let CompositorKind::Draw { max_partial_present_rects, .. } = doc.scene.config.compositor_kind { + + // When partial present is enabled, we need to force redraw. + if max_partial_present_rects > 0 { + let msg = ResultMsg::ForceRedraw; + self.result_tx.send(msg).unwrap(); + } + } + } + + let mut frame_build_time = None; + if build_frame { + profile_scope!("generate frame"); + + *frame_counter += 1; + + // borrow ck hack for profile_counters + let (pending_update, rendered_document) = { + let _timer = profile_counters.total_time.timer(); + let frame_build_start_time = precise_time_ns(); + + let rendered_document = doc.build_frame( + &mut self.resource_cache, + &mut self.gpu_cache, + &mut profile_counters.resources, + self.debug_flags, + &mut self.tile_cache_logger, + ); + + debug!("generated frame for document {:?} with {} passes", + document_id, rendered_document.frame.passes.len()); + + let msg = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates()); + self.result_tx.send(msg).unwrap(); + + frame_build_time = Some(precise_time_ns() - frame_build_start_time); + + let pending_update = self.resource_cache.pending_updates(); + (pending_update, rendered_document) + }; + + // Build a small struct that represents the state of the tiles to be composited. + let composite_descriptor = rendered_document + .frame + .composite_state + .descriptor + .clone(); + + // If there are texture cache updates to apply, or if the produced + // frame is not a no-op, or the compositor state has changed, + // then we cannot skip compositing this frame. + if !pending_update.is_nop() || + !rendered_document.frame.is_nop() || + composite_descriptor != doc.prev_composite_descriptor { + doc.rendered_frame_is_valid = false; + } + doc.prev_composite_descriptor = composite_descriptor; + + #[cfg(feature = "capture")] + match self.capture_config { + Some(ref mut config) => { + // FIXME(aosmond): document splitting causes multiple prepare frames + config.prepare_frame(); + + if config.bits.contains(CaptureBits::FRAME) { + let file_name = format!("frame-{}-{}", document_id.namespace_id.0, document_id.id); + config.serialize_for_frame(&rendered_document.frame, file_name); + } + + let data_stores_name = format!("data-stores-{}-{}", document_id.namespace_id.0, document_id.id); + config.serialize_for_frame(&doc.data_stores, data_stores_name); + + let properties_name = format!("properties-{}-{}", document_id.namespace_id.0, document_id.id); + config.serialize_for_frame(&doc.dynamic_properties, properties_name); + }, + None => {}, + } + + let msg = ResultMsg::PublishPipelineInfo(doc.updated_pipeline_info()); + self.result_tx.send(msg).unwrap(); + + // Publish the frame + let msg = ResultMsg::PublishDocument( + document_id, + rendered_document, + pending_update, + profile_counters.clone() + ); + self.result_tx.send(msg).unwrap(); + profile_counters.reset(); + } else if requested_frame { + // WR-internal optimization to avoid doing a bunch of render work if + // there's no pixels. We still want to pretend to render and request + // a render to make sure that the callbacks (particularly the + // new_frame_ready callback below) has the right flags. + let msg = ResultMsg::PublishPipelineInfo(doc.updated_pipeline_info()); + self.result_tx.send(msg).unwrap(); + } + + drain_filter( + &mut notifications, + |n| { n.when() == Checkpoint::FrameBuilt }, + |n| { n.notify(); }, + ); + + if !notifications.is_empty() { + self.result_tx.send(ResultMsg::AppendNotificationRequests(notifications)).unwrap(); + } + + // Always forward the transaction to the renderer if a frame was requested, + // otherwise gecko can get into a state where it waits (forever) for the + // transaction to complete before sending new work. + if requested_frame { + // If rendered frame is already valid, there is no need to render frame. + if doc.rendered_frame_is_valid { + render_frame = false; + } else if render_frame { + doc.rendered_frame_is_valid = true; + } + self.notifier.new_frame_ready(document_id, scroll, render_frame, frame_build_time); + } + + if !doc.hit_tester_is_valid { + doc.rebuild_hit_tester(); + } + + build_frame + } + + fn send_backend_message(&self, msg: BackendSceneBuilderRequest) { + self.backend_scene_tx.send(msg).unwrap(); + self.low_priority_scene_tx.send(SceneBuilderRequest::BackendMessage).unwrap(); + } + + #[cfg(not(feature = "debugger"))] + fn get_spatial_tree_for_debugger(&self) -> String { + String::new() + } + + #[cfg(feature = "debugger")] + fn get_spatial_tree_for_debugger(&self) -> String { + use crate::print_tree::PrintableTree; + + let mut debug_root = debug_server::SpatialTreeList::new(); + + for (_, doc) in &self.documents { + let debug_node = debug_server::TreeNode::new("document spatial tree"); + let mut builder = debug_server::TreeNodeBuilder::new(debug_node); + + doc.scene.spatial_tree.print_with(&mut builder); + + debug_root.add(builder.build()); + } + + serde_json::to_string(&debug_root).unwrap() + } + + fn report_memory(&mut self, tx: Sender>) { + let mut report = Box::new(MemoryReport::default()); + let ops = self.size_of_ops.as_mut().unwrap(); + let op = ops.size_of_op; + report.gpu_cache_metadata = self.gpu_cache.size_of(ops); + for doc in self.documents.values() { + report.clip_stores += doc.scene.clip_store.size_of(ops); + report.hit_testers += match &doc.hit_tester { + Some(hit_tester) => hit_tester.size_of(ops), + None => 0, + }; + + doc.data_stores.report_memory(ops, &mut report) + } + + (*report) += self.resource_cache.report_memory(op); + + // Send a message to report memory on the scene-builder thread, which + // will add its report to this one and send the result back to the original + // thread waiting on the request. + self.send_backend_message( + BackendSceneBuilderRequest::ReportMemory(report, tx) + ); + } + + #[cfg(feature = "capture")] + fn save_capture_sequence(&mut self) { + if let Some(ref mut config) = self.capture_config { + let deferred = self.resource_cache.save_capture_sequence(config); + + let backend = PlainRenderBackend { + default_device_pixel_ratio: self.default_device_pixel_ratio, + frame_config: self.frame_config.clone(), + resource_sequence_id: config.resource_id, + documents: self.documents + .iter() + .map(|(id, doc)| (*id, doc.view)) + .collect(), + }; + config.serialize_for_frame(&backend, "backend"); + + if !deferred.is_empty() { + let msg = ResultMsg::DebugOutput(DebugOutput::SaveCapture(config.clone(), deferred)); + self.result_tx.send(msg).unwrap(); + } + } + } +} + +impl RenderBackend { + #[cfg(feature = "capture")] + // Note: the mutable `self` is only needed here for resolving blob images + fn save_capture( + &mut self, + root: PathBuf, + bits: CaptureBits, + profile_counters: &mut BackendProfileCounters, + ) -> DebugOutput { + use std::fs; + use crate::render_task_graph::dump_render_tasks_as_svg; + + debug!("capture: saving {:?}", root); + if !root.is_dir() { + if let Err(e) = fs::create_dir_all(&root) { + panic!("Unable to create capture dir: {:?}", e); + } + } + let config = CaptureConfig::new(root, bits); + + if config.bits.contains(CaptureBits::FRAME) { + self.prepare_for_frames(); + } + + for (&id, doc) in &mut self.documents { + debug!("\tdocument {:?}", id); + if config.bits.contains(CaptureBits::FRAME) { + let rendered_document = doc.build_frame( + &mut self.resource_cache, + &mut self.gpu_cache, + &mut profile_counters.resources, + self.debug_flags, + &mut self.tile_cache_logger, + ); + // After we rendered the frames, there are pending updates to both + // GPU cache and resources. Instead of serializing them, we are going to make sure + // they are applied on the `Renderer` side. + let msg_update_gpu_cache = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates()); + self.result_tx.send(msg_update_gpu_cache).unwrap(); + //TODO: write down doc's pipeline info? + // it has `pipeline_epoch_map`, + // which may capture necessary details for some cases. + let file_name = format!("frame-{}-{}", id.namespace_id.0, id.id); + config.serialize_for_frame(&rendered_document.frame, file_name); + let file_name = format!("spatial-{}-{}", id.namespace_id.0, id.id); + config.serialize_tree_for_frame(&doc.scene.spatial_tree, file_name); + let file_name = format!("built-primitives-{}-{}", id.namespace_id.0, id.id); + config.serialize_for_frame(&doc.scene.prim_store, file_name); + let file_name = format!("built-clips-{}-{}", id.namespace_id.0, id.id); + config.serialize_for_frame(&doc.scene.clip_store, file_name); + let file_name = format!("scratch-{}-{}", id.namespace_id.0, id.id); + config.serialize_for_frame(&doc.scratch, file_name); + let file_name = format!("render-tasks-{}-{}.svg", id.namespace_id.0, id.id); + let mut svg_file = fs::File::create(&config.file_path_for_frame(file_name, "svg")) + .expect("Failed to open the SVG file."); + dump_render_tasks_as_svg( + &rendered_document.frame.render_tasks, + &rendered_document.frame.passes, + &mut svg_file + ).unwrap(); + } + + let data_stores_name = format!("data-stores-{}-{}", id.namespace_id.0, id.id); + config.serialize_for_frame(&doc.data_stores, data_stores_name); + + let properties_name = format!("properties-{}-{}", id.namespace_id.0, id.id); + config.serialize_for_frame(&doc.dynamic_properties, properties_name); + } + + if config.bits.contains(CaptureBits::FRAME) { + // TODO: there is no guarantee that we won't hit this case, but we want to + // report it here if we do. If we don't, it will simply crash in + // Renderer::render_impl and give us less information about the source. + assert!(!self.requires_frame_build(), "Caches were cleared during a capture."); + self.bookkeep_after_frames(); + } + + debug!("\tscene builder"); + self.send_backend_message( + BackendSceneBuilderRequest::SaveScene(config.clone()) + ); + + debug!("\tresource cache"); + let (resources, deferred) = self.resource_cache.save_capture(&config.root); + + if config.bits.contains(CaptureBits::TILE_CACHE) { + debug!("\ttile cache"); + self.tile_cache_logger.save_capture(&config.root); + } + + info!("\tbackend"); + let backend = PlainRenderBackend { + default_device_pixel_ratio: self.default_device_pixel_ratio, + frame_config: self.frame_config.clone(), + resource_sequence_id: 0, + documents: self.documents + .iter() + .map(|(id, doc)| (*id, doc.view)) + .collect(), + }; + + config.serialize_for_frame(&backend, "backend"); + config.serialize_for_frame(&resources, "plain-resources"); + + if config.bits.contains(CaptureBits::FRAME) { + let msg_update_resources = ResultMsg::UpdateResources { + resource_updates: self.resource_cache.pending_updates(), + memory_pressure: false, + }; + self.result_tx.send(msg_update_resources).unwrap(); + // Save the texture/glyph/image caches. + info!("\tresource cache"); + let caches = self.resource_cache.save_caches(&config.root); + config.serialize_for_resource(&caches, "resource_cache"); + info!("\tgpu cache"); + config.serialize_for_resource(&self.gpu_cache, "gpu_cache"); + } + + DebugOutput::SaveCapture(config, deferred) + } + + #[cfg(feature = "capture")] + fn start_capture_sequence( + &mut self, + root: PathBuf, + bits: CaptureBits, + ) { + self.send_backend_message( + BackendSceneBuilderRequest::StartCaptureSequence(CaptureConfig::new(root, bits)) + ); + } + + #[cfg(feature = "capture")] + fn stop_capture_sequence( + &mut self, + ) { + self.send_backend_message( + BackendSceneBuilderRequest::StopCaptureSequence + ); + } + + #[cfg(feature = "replay")] + fn load_capture( + &mut self, + mut config: CaptureConfig, + profile_counters: &mut BackendProfileCounters, + ) { + debug!("capture: loading {:?}", config.frame_root()); + let backend = config.deserialize_for_frame::("backend") + .expect("Unable to open backend.ron"); + + // If this is a capture sequence, then the ID will be non-zero, and won't + // match what is loaded, but for still captures, the ID will be zero. + let first_load = backend.resource_sequence_id == 0; + if self.loaded_resource_sequence_id != backend.resource_sequence_id || first_load { + // FIXME(aosmond): We clear the documents because when we update the + // resource cache, we actually wipe and reload, because we don't + // know what is the same and what has changed. If we were to keep as + // much of the resource cache state as possible, we could avoid + // flushing the document state (which has its own dependecies on the + // cache). + // + // FIXME(aosmond): If we try to load the next capture in the + // sequence too quickly, we may lose resources we depend on in the + // current frame. This can cause panics. Ideally we would not + // advance to the next frame until the FrameRendered event for all + // of the pipelines. + self.documents.clear(); + + config.resource_id = backend.resource_sequence_id; + self.loaded_resource_sequence_id = backend.resource_sequence_id; + + let plain_resources = config.deserialize_for_resource::("plain-resources") + .expect("Unable to open plain-resources.ron"); + let caches_maybe = config.deserialize_for_resource::("resource_cache"); + + // Note: it would be great to have `RenderBackend` to be split + // rather explicitly on what's used before and after scene building + // so that, for example, we never miss anything in the code below: + + let plain_externals = self.resource_cache.load_capture( + plain_resources, + caches_maybe, + &config, + ); + + let msg_load = ResultMsg::DebugOutput( + DebugOutput::LoadCapture(config.clone(), plain_externals) + ); + self.result_tx.send(msg_load).unwrap(); + + self.gpu_cache = match config.deserialize_for_resource::("gpu_cache") { + Some(gpu_cache) => gpu_cache, + None => GpuCache::new(), + }; + } + + self.default_device_pixel_ratio = backend.default_device_pixel_ratio; + self.frame_config = backend.frame_config; + + let mut scenes_to_build = Vec::new(); + + for (id, view) in backend.documents { + debug!("\tdocument {:?}", id); + let scene_name = format!("scene-{}-{}", id.namespace_id.0, id.id); + let scene = config.deserialize_for_scene::(&scene_name) + .expect(&format!("Unable to open {}.ron", scene_name)); + + let interners_name = format!("interners-{}-{}", id.namespace_id.0, id.id); + let interners = config.deserialize_for_scene::(&interners_name) + .expect(&format!("Unable to open {}.ron", interners_name)); + + let data_stores_name = format!("data-stores-{}-{}", id.namespace_id.0, id.id); + let data_stores = config.deserialize_for_frame::(&data_stores_name) + .expect(&format!("Unable to open {}.ron", data_stores_name)); + + let properties_name = format!("properties-{}-{}", id.namespace_id.0, id.id); + let properties = config.deserialize_for_frame::(&properties_name) + .expect(&format!("Unable to open {}.ron", properties_name)); + + // Update the document if it still exists, rather than replace it entirely. + // This allows us to preserve state information such as the frame stamp, + // which is necessary for cache sanity. + match self.documents.entry(id) { + Occupied(entry) => { + let doc = entry.into_mut(); + doc.view = view; + doc.loaded_scene = scene.clone(); + doc.data_stores = data_stores; + doc.dynamic_properties = properties; + doc.frame_is_valid = false; + doc.rendered_frame_is_valid = false; + doc.has_built_scene = false; + doc.hit_tester_is_valid = false; + } + Vacant(entry) => { + let doc = Document { + id, + scene: BuiltScene::empty(), + removed_pipelines: Vec::new(), + view, + stamp: FrameStamp::first(id), + frame_builder: FrameBuilder::new(), + dynamic_properties: properties, + hit_tester: None, + shared_hit_tester: Arc::new(SharedHitTester::new()), + frame_is_valid: false, + hit_tester_is_valid: false, + rendered_frame_is_valid: false, + has_built_scene: false, + data_stores, + scratch: PrimitiveScratchBuffer::new(), + render_task_counters: RenderTaskGraphCounters::new(), + loaded_scene: scene.clone(), + prev_composite_descriptor: CompositeDescriptor::empty(), + }; + entry.insert(doc); + } + }; + + let frame_name = format!("frame-{}-{}", id.namespace_id.0, id.id); + let frame = config.deserialize_for_frame::(frame_name); + let build_frame = match frame { + Some(frame) => { + info!("\tloaded a built frame with {} passes", frame.passes.len()); + + let msg_update = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates()); + self.result_tx.send(msg_update).unwrap(); + + let msg_publish = ResultMsg::PublishDocument( + id, + RenderedDocument { frame, is_new_scene: true }, + self.resource_cache.pending_updates(), + profile_counters.clone(), + ); + self.result_tx.send(msg_publish).unwrap(); + profile_counters.reset(); + + self.notifier.new_frame_ready(id, false, true, None); + + // We deserialized the state of the frame so we don't want to build + // it (but we do want to update the scene builder's state) + false + } + None => true, + }; + + scenes_to_build.push(LoadScene { + document_id: id, + scene, + view: view.scene.clone(), + config: self.frame_config.clone(), + font_instances: self.resource_cache.get_font_instances(), + build_frame, + interners, + }); + } + + if !scenes_to_build.is_empty() { + self.send_backend_message( + BackendSceneBuilderRequest::LoadScenes(scenes_to_build) + ); + } + } +} diff --git a/third_party/webrender/webrender/src/render_target.rs b/third_party/webrender/webrender/src/render_target.rs new file mode 100644 index 00000000000..9a3c953f42a --- /dev/null +++ b/third_party/webrender/webrender/src/render_target.rs @@ -0,0 +1,1091 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +use api::units::*; +use api::{ColorF, PremultipliedColorF, ImageFormat, LineOrientation, BorderStyle, PipelineId}; +use crate::batch::{AlphaBatchBuilder, AlphaBatchContainer, BatchTextures, resolve_image}; +use crate::batch::{ClipBatcher, BatchBuilder}; +use crate::spatial_tree::{SpatialTree, ROOT_SPATIAL_NODE_INDEX}; +use crate::clip::ClipStore; +use crate::composite::CompositeState; +use crate::device::Texture; +use crate::frame_builder::{FrameGlobalResources}; +use crate::gpu_cache::{GpuCache, GpuCacheAddress}; +use crate::gpu_types::{BorderInstance, SvgFilterInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance}; +use crate::gpu_types::{TransformPalette, ZBufferIdGenerator}; +use crate::internal_types::{FastHashMap, TextureSource, LayerIndex, Swizzle, SavedTargetIndex}; +use crate::picture::{SurfaceInfo, ResolvedSurfaceTexture}; +use crate::prim_store::{PrimitiveStore, DeferredResolve, PrimitiveScratchBuffer, PrimitiveVisibilityMask}; +use crate::prim_store::gradient::GRADIENT_FP_STOPS; +use crate::render_backend::DataStores; +use crate::render_task::{RenderTaskKind, RenderTaskAddress, ClearMode, BlitSource}; +use crate::render_task::{RenderTask, ScalingTask, SvgFilterInfo}; +use crate::render_task_graph::{RenderTaskGraph, RenderTaskId}; +use crate::resource_cache::ResourceCache; +use crate::texture_allocator::{ArrayAllocationTracker, FreeRectSlice}; +use std::{cmp, mem}; + + +const STYLE_SOLID: i32 = ((BorderStyle::Solid as i32) << 8) | ((BorderStyle::Solid as i32) << 16); +const STYLE_MASK: i32 = 0x00FF_FF00; + +/// According to apitrace, textures larger than 2048 break fast clear +/// optimizations on some intel drivers. We sometimes need to go larger, but +/// we try to avoid it. This can go away when proper tiling support lands, +/// since we can then split large primitives across multiple textures. +const IDEAL_MAX_TEXTURE_DIMENSION: i32 = 2048; +/// If we ever need a larger texture than the ideal, we better round it up to a +/// reasonable number in order to have a bit of leeway in placing things inside. +const TEXTURE_DIMENSION_MASK: i32 = 0xFF; + +/// A tag used to identify the output format of a `RenderTarget`. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum RenderTargetKind { + Color, // RGBA8 + Alpha, // R8 +} + +/// Identifies a given `RenderTarget` in a `RenderTargetList`. +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct RenderTargetIndex(pub usize); + +pub struct RenderTargetContext<'a, 'rc> { + pub global_device_pixel_scale: DevicePixelScale, + pub prim_store: &'a PrimitiveStore, + pub resource_cache: &'rc mut ResourceCache, + pub use_dual_source_blending: bool, + pub use_advanced_blending: bool, + pub break_advanced_blend_batches: bool, + pub batch_lookback_count: usize, + pub spatial_tree: &'a SpatialTree, + pub data_stores: &'a DataStores, + pub surfaces: &'a [SurfaceInfo], + pub scratch: &'a PrimitiveScratchBuffer, + pub screen_world_rect: WorldRect, + pub globals: &'a FrameGlobalResources, +} + +/// Represents a number of rendering operations on a surface. +/// +/// In graphics parlance, a "render target" usually means "a surface (texture or +/// framebuffer) bound to the output of a shader". This trait has a slightly +/// different meaning, in that it represents the operations on that surface +/// _before_ it's actually bound and rendered. So a `RenderTarget` is built by +/// the `RenderBackend` by inserting tasks, and then shipped over to the +/// `Renderer` where a device surface is resolved and the tasks are transformed +/// into draw commands on that surface. +/// +/// We express this as a trait to generalize over color and alpha surfaces. +/// a given `RenderTask` will draw to one or the other, depending on its type +/// and sometimes on its parameters. See `RenderTask::target_kind`. +pub trait RenderTarget { + /// Creates a new RenderTarget of the given type. + fn new( + screen_size: DeviceIntSize, + gpu_supports_fast_clears: bool, + ) -> Self; + + /// Optional hook to provide additional processing for the target at the + /// end of the build phase. + fn build( + &mut self, + _ctx: &mut RenderTargetContext, + _gpu_cache: &mut GpuCache, + _render_tasks: &mut RenderTaskGraph, + _deferred_resolves: &mut Vec, + _prim_headers: &mut PrimitiveHeaders, + _transforms: &mut TransformPalette, + _z_generator: &mut ZBufferIdGenerator, + _composite_state: &mut CompositeState, + ) { + } + + /// Associates a `RenderTask` with this target. That task must be assigned + /// to a region returned by invoking `allocate()` on this target. + /// + /// TODO(gw): It's a bit odd that we need the deferred resolves and mutable + /// GPU cache here. They are typically used by the build step above. They + /// are used for the blit jobs to allow resolve_image to be called. It's a + /// bit of extra overhead to store the image key here and the resolve them + /// in the build step separately. BUT: if/when we add more texture cache + /// target jobs, we might want to tidy this up. + fn add_task( + &mut self, + task_id: RenderTaskId, + ctx: &RenderTargetContext, + gpu_cache: &mut GpuCache, + render_tasks: &RenderTaskGraph, + clip_store: &ClipStore, + transforms: &mut TransformPalette, + deferred_resolves: &mut Vec, + ); + + fn needs_depth(&self) -> bool; + + fn used_rect(&self) -> DeviceIntRect; + fn add_used(&mut self, rect: DeviceIntRect); +} + +/// A series of `RenderTarget` instances, serving as the high-level container +/// into which `RenderTasks` are assigned. +/// +/// During the build phase, we iterate over the tasks in each `RenderPass`. For +/// each task, we invoke `allocate()` on the `RenderTargetList`, which in turn +/// attempts to allocate an output region in the last `RenderTarget` in the +/// list. If allocation fails (or if the list is empty), a new `RenderTarget` is +/// created and appended to the list. The build phase then assign the task into +/// the target associated with the final allocation. +/// +/// The result is that each `RenderPass` is associated with one or two +/// `RenderTargetLists`, depending on whether we have all our tasks have the +/// same `RenderTargetKind`. The lists are then shipped to the `Renderer`, which +/// allocates a device texture array, with one slice per render target in the +/// list. +/// +/// The upshot of this scheme is that it maximizes batching. In a given pass, +/// we need to do a separate batch for each individual render target. But with +/// the texture array, we can expose the entirety of the previous pass to each +/// task in the current pass in a single batch, which generally allows each +/// task to be drawn in a single batch regardless of how many results from the +/// previous pass it depends on. +/// +/// Note that in some cases (like drop-shadows), we can depend on the output of +/// a pass earlier than the immediately-preceding pass. See `SavedTargetIndex`. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct RenderTargetList { + screen_size: DeviceIntSize, + pub format: ImageFormat, + /// The maximum width and height of any single primitive we've encountered + /// that will be drawn to a dynamic location. + /// + /// We initially create our per-slice allocators with a width and height of + /// IDEAL_MAX_TEXTURE_DIMENSION. If we encounter a larger primitive, the + /// allocation will fail, but we'll bump max_dynamic_size, which will cause the + /// allocator for the next slice to be just large enough to accomodate it. + pub max_dynamic_size: DeviceIntSize, + pub targets: Vec, + pub saved_index: Option, + pub alloc_tracker: ArrayAllocationTracker, + gpu_supports_fast_clears: bool, +} + +impl RenderTargetList { + pub fn new( + screen_size: DeviceIntSize, + format: ImageFormat, + gpu_supports_fast_clears: bool, + ) -> Self { + RenderTargetList { + screen_size, + format, + max_dynamic_size: DeviceIntSize::new(0, 0), + targets: Vec::new(), + saved_index: None, + alloc_tracker: ArrayAllocationTracker::new(), + gpu_supports_fast_clears, + } + } + + pub fn build( + &mut self, + ctx: &mut RenderTargetContext, + gpu_cache: &mut GpuCache, + render_tasks: &mut RenderTaskGraph, + deferred_resolves: &mut Vec, + saved_index: Option, + prim_headers: &mut PrimitiveHeaders, + transforms: &mut TransformPalette, + z_generator: &mut ZBufferIdGenerator, + composite_state: &mut CompositeState, + ) { + debug_assert_eq!(None, self.saved_index); + self.saved_index = saved_index; + + for target in &mut self.targets { + target.build( + ctx, + gpu_cache, + render_tasks, + deferred_resolves, + prim_headers, + transforms, + z_generator, + composite_state, + ); + } + } + + pub fn allocate( + &mut self, + alloc_size: DeviceIntSize, + ) -> (RenderTargetIndex, DeviceIntPoint) { + let (free_rect_slice, origin) = match self.alloc_tracker.allocate(&alloc_size) { + Some(allocation) => allocation, + None => { + // Have the allocator restrict slice sizes to our max ideal + // dimensions, unless we've already gone bigger on a previous + // slice. + let rounded_dimensions = DeviceIntSize::new( + (self.max_dynamic_size.width + TEXTURE_DIMENSION_MASK) & !TEXTURE_DIMENSION_MASK, + (self.max_dynamic_size.height + TEXTURE_DIMENSION_MASK) & !TEXTURE_DIMENSION_MASK, + ); + let allocator_dimensions = DeviceIntSize::new( + cmp::max(IDEAL_MAX_TEXTURE_DIMENSION, rounded_dimensions.width), + cmp::max(IDEAL_MAX_TEXTURE_DIMENSION, rounded_dimensions.height), + ); + + assert!(alloc_size.width <= allocator_dimensions.width && + alloc_size.height <= allocator_dimensions.height); + let slice = FreeRectSlice(self.targets.len() as u32); + self.targets.push(T::new(self.screen_size, self.gpu_supports_fast_clears)); + + self.alloc_tracker.extend( + slice, + allocator_dimensions, + alloc_size, + ); + + (slice, DeviceIntPoint::zero()) + } + }; + + if alloc_size.is_empty() && self.targets.is_empty() { + // push an unused target here, only if we don't have any + self.targets.push(T::new(self.screen_size, self.gpu_supports_fast_clears)); + } + + self.targets[free_rect_slice.0 as usize] + .add_used(DeviceIntRect::new(origin, alloc_size)); + + (RenderTargetIndex(free_rect_slice.0 as usize), origin) + } + + pub fn needs_depth(&self) -> bool { + self.targets.iter().any(|target| target.needs_depth()) + } + + pub fn check_ready(&self, t: &Texture) { + let dimensions = t.get_dimensions(); + assert!(dimensions.width >= self.max_dynamic_size.width); + assert!(dimensions.height >= self.max_dynamic_size.height); + assert_eq!(t.get_format(), self.format); + assert_eq!(t.get_layer_count() as usize, self.targets.len()); + assert!(t.supports_depth() >= self.needs_depth()); + } +} + + +/// Contains the work (in the form of instance arrays) needed to fill a color +/// color output surface (RGBA8). +/// +/// See `RenderTarget`. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ColorRenderTarget { + pub alpha_batch_containers: Vec, + // List of blur operations to apply for this render target. + pub vertical_blurs: Vec, + pub horizontal_blurs: Vec, + pub readbacks: Vec, + pub scalings: FastHashMap>, + pub svg_filters: Vec<(BatchTextures, Vec)>, + pub blits: Vec, + // List of frame buffer outputs for this render target. + pub outputs: Vec, + alpha_tasks: Vec, + screen_size: DeviceIntSize, + // Track the used rect of the render target, so that + // we can set a scissor rect and only clear to the + // used portion of the target as an optimization. + pub used_rect: DeviceIntRect, +} + +impl RenderTarget for ColorRenderTarget { + fn new( + screen_size: DeviceIntSize, + _: bool, + ) -> Self { + ColorRenderTarget { + alpha_batch_containers: Vec::new(), + vertical_blurs: Vec::new(), + horizontal_blurs: Vec::new(), + readbacks: Vec::new(), + scalings: FastHashMap::default(), + svg_filters: Vec::new(), + blits: Vec::new(), + outputs: Vec::new(), + alpha_tasks: Vec::new(), + screen_size, + used_rect: DeviceIntRect::zero(), + } + } + + fn build( + &mut self, + ctx: &mut RenderTargetContext, + gpu_cache: &mut GpuCache, + render_tasks: &mut RenderTaskGraph, + deferred_resolves: &mut Vec, + prim_headers: &mut PrimitiveHeaders, + transforms: &mut TransformPalette, + z_generator: &mut ZBufferIdGenerator, + composite_state: &mut CompositeState, + ) { + profile_scope!("build"); + let mut merged_batches = AlphaBatchContainer::new(None); + + for task_id in &self.alpha_tasks { + profile_scope!("alpha_task"); + let task = &render_tasks[*task_id]; + + match task.clear_mode { + ClearMode::One | + ClearMode::Zero => { + panic!("bug: invalid clear mode for color task"); + } + ClearMode::DontCare | + ClearMode::Transparent => {} + } + + match task.kind { + RenderTaskKind::Picture(ref pic_task) => { + let pic = &ctx.prim_store.pictures[pic_task.pic_index.0]; + + let raster_spatial_node_index = match pic.raster_config { + Some(ref raster_config) => { + let surface = &ctx.surfaces[raster_config.surface_index.0]; + surface.raster_spatial_node_index + } + None => { + // This must be the main framebuffer + ROOT_SPATIAL_NODE_INDEX + } + }; + + let (target_rect, _) = task.get_target_rect(); + + let scissor_rect = if pic_task.can_merge { + None + } else { + Some(target_rect) + }; + + // TODO(gw): The type names of AlphaBatchBuilder and BatchBuilder + // are still confusing. Once more of the picture caching + // improvement code lands, the AlphaBatchBuilder and + // AlphaBatchList types will be collapsed into one, which + // should simplify coming up with better type names. + let alpha_batch_builder = AlphaBatchBuilder::new( + self.screen_size, + ctx.break_advanced_blend_batches, + ctx.batch_lookback_count, + *task_id, + render_tasks.get_task_address(*task_id), + PrimitiveVisibilityMask::all(), + ); + + let mut batch_builder = BatchBuilder::new( + vec![alpha_batch_builder], + ); + + batch_builder.add_pic_to_batch( + pic, + ctx, + gpu_cache, + render_tasks, + deferred_resolves, + prim_headers, + transforms, + raster_spatial_node_index, + pic_task.surface_spatial_node_index, + z_generator, + composite_state, + ); + + let alpha_batch_builders = batch_builder.finalize(); + + for batcher in alpha_batch_builders { + batcher.build( + &mut self.alpha_batch_containers, + &mut merged_batches, + target_rect, + scissor_rect, + ); + } + } + _ => { + unreachable!(); + } + } + } + + if !merged_batches.is_empty() { + self.alpha_batch_containers.push(merged_batches); + } + } + + fn add_task( + &mut self, + task_id: RenderTaskId, + ctx: &RenderTargetContext, + gpu_cache: &mut GpuCache, + render_tasks: &RenderTaskGraph, + _: &ClipStore, + _: &mut TransformPalette, + deferred_resolves: &mut Vec, + ) { + profile_scope!("add_task"); + let task = &render_tasks[task_id]; + + match task.kind { + RenderTaskKind::VerticalBlur(..) => { + add_blur_instances( + &mut self.vertical_blurs, + BlurDirection::Vertical, + render_tasks.get_task_address(task_id), + render_tasks.get_task_address(task.children[0]), + ); + } + RenderTaskKind::HorizontalBlur(..) => { + add_blur_instances( + &mut self.horizontal_blurs, + BlurDirection::Horizontal, + render_tasks.get_task_address(task_id), + render_tasks.get_task_address(task.children[0]), + ); + } + RenderTaskKind::Picture(ref task_info) => { + let pic = &ctx.prim_store.pictures[task_info.pic_index.0]; + self.alpha_tasks.push(task_id); + + // If this pipeline is registered as a frame output + // store the information necessary to do the copy. + if let Some(pipeline_id) = pic.frame_output_pipeline_id { + self.outputs.push(FrameOutput { + pipeline_id, + task_id, + }); + } + } + RenderTaskKind::SvgFilter(ref task_info) => { + add_svg_filter_instances( + &mut self.svg_filters, + render_tasks, + &task_info.info, + task_id, + task.children.get(0).cloned(), + task.children.get(1).cloned(), + task_info.extra_gpu_cache_handle.map(|handle| gpu_cache.get_address(&handle)), + ) + } + RenderTaskKind::ClipRegion(..) | + RenderTaskKind::Border(..) | + RenderTaskKind::CacheMask(..) | + RenderTaskKind::Gradient(..) | + RenderTaskKind::LineDecoration(..) => { + panic!("Should not be added to color target!"); + } + RenderTaskKind::Readback(device_rect) => { + self.readbacks.push(device_rect); + } + RenderTaskKind::Scaling(ref info) => { + add_scaling_instances( + info, + &mut self.scalings, + task, + task.children.first().map(|&child| &render_tasks[child]), + ctx.resource_cache, + gpu_cache, + deferred_resolves, + ); + } + RenderTaskKind::Blit(ref task_info) => { + let source = match task_info.source { + BlitSource::Image { key } => { + // Get the cache item for the source texture. + let cache_item = resolve_image( + key.request, + ctx.resource_cache, + gpu_cache, + deferred_resolves, + ); + + // Work out a source rect to copy from the texture, depending on whether + // a sub-rect is present or not. + let source_rect = key.texel_rect.map_or(cache_item.uv_rect.to_i32(), |sub_rect| { + DeviceIntRect::new( + DeviceIntPoint::new( + cache_item.uv_rect.origin.x as i32 + sub_rect.origin.x, + cache_item.uv_rect.origin.y as i32 + sub_rect.origin.y, + ), + sub_rect.size, + ) + }); + + // Store the blit job for the renderer to execute, including + // the allocated destination rect within this target. + BlitJobSource::Texture( + cache_item.texture_id, + cache_item.texture_layer, + source_rect, + ) + } + BlitSource::RenderTask { task_id } => { + BlitJobSource::RenderTask(task_id) + } + }; + + let target_rect = task + .get_target_rect() + .0 + .inner_rect(task_info.padding); + self.blits.push(BlitJob { + source, + target_rect, + }); + } + #[cfg(test)] + RenderTaskKind::Test(..) => {} + } + } + + fn needs_depth(&self) -> bool { + self.alpha_batch_containers.iter().any(|ab| { + !ab.opaque_batches.is_empty() + }) + } + + fn used_rect(&self) -> DeviceIntRect { + self.used_rect + } + + fn add_used(&mut self, rect: DeviceIntRect) { + self.used_rect = self.used_rect.union(&rect); + } +} + +/// Contains the work (in the form of instance arrays) needed to fill an alpha +/// output surface (R8). +/// +/// See `RenderTarget`. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct AlphaRenderTarget { + pub clip_batcher: ClipBatcher, + // List of blur operations to apply for this render target. + pub vertical_blurs: Vec, + pub horizontal_blurs: Vec, + pub scalings: FastHashMap>, + pub zero_clears: Vec, + pub one_clears: Vec, + // Track the used rect of the render target, so that + // we can set a scissor rect and only clear to the + // used portion of the target as an optimization. + pub used_rect: DeviceIntRect, +} + +impl RenderTarget for AlphaRenderTarget { + fn new( + _: DeviceIntSize, + gpu_supports_fast_clears: bool, + ) -> Self { + AlphaRenderTarget { + clip_batcher: ClipBatcher::new(gpu_supports_fast_clears), + vertical_blurs: Vec::new(), + horizontal_blurs: Vec::new(), + scalings: FastHashMap::default(), + zero_clears: Vec::new(), + one_clears: Vec::new(), + used_rect: DeviceIntRect::zero(), + } + } + + fn add_task( + &mut self, + task_id: RenderTaskId, + ctx: &RenderTargetContext, + gpu_cache: &mut GpuCache, + render_tasks: &RenderTaskGraph, + clip_store: &ClipStore, + transforms: &mut TransformPalette, + deferred_resolves: &mut Vec, + ) { + profile_scope!("add_task"); + let task = &render_tasks[task_id]; + let (target_rect, _) = task.get_target_rect(); + + match task.clear_mode { + ClearMode::Zero => { + self.zero_clears.push(task_id); + } + ClearMode::One => { + self.one_clears.push(task_id); + } + ClearMode::DontCare => {} + ClearMode::Transparent => { + panic!("bug: invalid clear mode for alpha task"); + } + } + + match task.kind { + RenderTaskKind::Readback(..) | + RenderTaskKind::Picture(..) | + RenderTaskKind::Blit(..) | + RenderTaskKind::Border(..) | + RenderTaskKind::LineDecoration(..) | + RenderTaskKind::Gradient(..) | + RenderTaskKind::SvgFilter(..) => { + panic!("BUG: should not be added to alpha target!"); + } + RenderTaskKind::VerticalBlur(..) => { + add_blur_instances( + &mut self.vertical_blurs, + BlurDirection::Vertical, + render_tasks.get_task_address(task_id), + render_tasks.get_task_address(task.children[0]), + ); + } + RenderTaskKind::HorizontalBlur(..) => { + add_blur_instances( + &mut self.horizontal_blurs, + BlurDirection::Horizontal, + render_tasks.get_task_address(task_id), + render_tasks.get_task_address(task.children[0]), + ); + } + RenderTaskKind::CacheMask(ref task_info) => { + self.clip_batcher.add( + task_info.clip_node_range, + task_info.root_spatial_node_index, + ctx.resource_cache, + gpu_cache, + clip_store, + ctx.spatial_tree, + transforms, + &ctx.data_stores.clip, + task_info.actual_rect, + &ctx.screen_world_rect, + task_info.device_pixel_scale, + target_rect.origin.to_f32(), + task_info.actual_rect.origin.to_f32(), + ); + } + RenderTaskKind::ClipRegion(ref region_task) => { + let device_rect = DeviceRect::new( + DevicePoint::zero(), + target_rect.size.to_f32(), + ); + self.clip_batcher.add_clip_region( + region_task.clip_data_address, + region_task.local_pos, + device_rect, + target_rect.origin.to_f32(), + DevicePoint::zero(), + region_task.device_pixel_scale.0, + ); + } + RenderTaskKind::Scaling(ref info) => { + add_scaling_instances( + info, + &mut self.scalings, + task, + task.children.first().map(|&child| &render_tasks[child]), + ctx.resource_cache, + gpu_cache, + deferred_resolves, + ); + } + #[cfg(test)] + RenderTaskKind::Test(..) => {} + } + } + + fn needs_depth(&self) -> bool { + false + } + + fn used_rect(&self) -> DeviceIntRect { + self.used_rect + } + + fn add_used(&mut self, rect: DeviceIntRect) { + self.used_rect = self.used_rect.union(&rect); + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct PictureCacheTarget { + pub surface: ResolvedSurfaceTexture, + pub alpha_batch_container: AlphaBatchContainer, + pub clear_color: Option, + pub dirty_rect: DeviceIntRect, + pub valid_rect: DeviceIntRect, +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct TextureCacheRenderTarget { + pub target_kind: RenderTargetKind, + pub horizontal_blurs: Vec, + pub blits: Vec, + pub border_segments_complex: Vec, + pub border_segments_solid: Vec, + pub clears: Vec, + pub line_decorations: Vec, + pub gradients: Vec, +} + +impl TextureCacheRenderTarget { + pub fn new(target_kind: RenderTargetKind) -> Self { + TextureCacheRenderTarget { + target_kind, + horizontal_blurs: vec![], + blits: vec![], + border_segments_complex: vec![], + border_segments_solid: vec![], + clears: vec![], + line_decorations: vec![], + gradients: vec![], + } + } + + pub fn add_task( + &mut self, + task_id: RenderTaskId, + render_tasks: &mut RenderTaskGraph, + ) { + profile_scope!("add_task"); + let task_address = render_tasks.get_task_address(task_id); + let src_task_address = render_tasks[task_id].children.get(0).map(|src_task_id| { + render_tasks.get_task_address(*src_task_id) + }); + + let task = &mut render_tasks[task_id]; + let target_rect = task.get_target_rect(); + + match task.kind { + RenderTaskKind::LineDecoration(ref info) => { + self.clears.push(target_rect.0); + + self.line_decorations.push(LineDecorationJob { + task_rect: target_rect.0.to_f32(), + local_size: info.local_size, + style: info.style as i32, + axis_select: match info.orientation { + LineOrientation::Horizontal => 0.0, + LineOrientation::Vertical => 1.0, + }, + wavy_line_thickness: info.wavy_line_thickness, + }); + } + RenderTaskKind::HorizontalBlur(..) => { + add_blur_instances( + &mut self.horizontal_blurs, + BlurDirection::Horizontal, + task_address, + src_task_address.unwrap(), + ); + } + RenderTaskKind::Blit(ref task_info) => { + match task_info.source { + BlitSource::Image { .. } => { + // reading/writing from the texture cache at the same time + // is undefined behavior. + panic!("bug: a single blit cannot be to/from texture cache"); + } + BlitSource::RenderTask { task_id } => { + // Add a blit job to copy from an existing render + // task to this target. + self.blits.push(BlitJob { + source: BlitJobSource::RenderTask(task_id), + target_rect: target_rect.0.inner_rect(task_info.padding), + }); + } + } + } + RenderTaskKind::Border(ref mut task_info) => { + self.clears.push(target_rect.0); + + let task_origin = target_rect.0.origin.to_f32(); + let instances = mem::replace(&mut task_info.instances, Vec::new()); + for mut instance in instances { + // TODO(gw): It may be better to store the task origin in + // the render task data instead of per instance. + instance.task_origin = task_origin; + if instance.flags & STYLE_MASK == STYLE_SOLID { + self.border_segments_solid.push(instance); + } else { + self.border_segments_complex.push(instance); + } + } + } + RenderTaskKind::Gradient(ref task_info) => { + let mut stops = [0.0; 4]; + let mut colors = [PremultipliedColorF::BLACK; 4]; + + let axis_select = match task_info.orientation { + LineOrientation::Horizontal => 0.0, + LineOrientation::Vertical => 1.0, + }; + + for (stop, (offset, color)) in task_info.stops.iter().zip(stops.iter_mut().zip(colors.iter_mut())) { + *offset = stop.offset; + *color = ColorF::from(stop.color).premultiplied(); + } + + self.gradients.push(GradientJob { + task_rect: target_rect.0.to_f32(), + axis_select, + stops, + colors, + start_stop: [task_info.start_point, task_info.end_point], + }); + } + RenderTaskKind::VerticalBlur(..) | + RenderTaskKind::Picture(..) | + RenderTaskKind::ClipRegion(..) | + RenderTaskKind::CacheMask(..) | + RenderTaskKind::Readback(..) | + RenderTaskKind::Scaling(..) | + RenderTaskKind::SvgFilter(..) => { + panic!("BUG: unexpected task kind for texture cache target"); + } + #[cfg(test)] + RenderTaskKind::Test(..) => {} + } + } +} + +fn add_blur_instances( + instances: &mut Vec, + blur_direction: BlurDirection, + task_address: RenderTaskAddress, + src_task_address: RenderTaskAddress, +) { + let instance = BlurInstance { + task_address, + src_task_address, + blur_direction, + }; + + instances.push(instance); +} + +fn add_scaling_instances( + task: &ScalingTask, + instances: &mut FastHashMap>, + target_task: &RenderTask, + source_task: Option<&RenderTask>, + resource_cache: &ResourceCache, + gpu_cache: &mut GpuCache, + deferred_resolves: &mut Vec, +) { + let target_rect = target_task + .get_target_rect() + .0 + .inner_rect(task.padding) + .to_f32(); + + let (source, (source_rect, source_layer)) = match task.image { + Some(key) => { + assert!(source_task.is_none()); + + // Get the cache item for the source texture. + let cache_item = resolve_image( + key.request, + resource_cache, + gpu_cache, + deferred_resolves, + ); + + // Work out a source rect to copy from the texture, depending on whether + // a sub-rect is present or not. + let source_rect = key.texel_rect.map_or(cache_item.uv_rect, |sub_rect| { + DeviceIntRect::new( + DeviceIntPoint::new( + cache_item.uv_rect.origin.x + sub_rect.origin.x, + cache_item.uv_rect.origin.y + sub_rect.origin.y, + ), + sub_rect.size, + ) + }); + + ( + cache_item.texture_id, + (source_rect, cache_item.texture_layer as LayerIndex), + ) + } + None => { + ( + match task.target_kind { + RenderTargetKind::Color => TextureSource::PrevPassColor, + RenderTargetKind::Alpha => TextureSource::PrevPassAlpha, + }, + source_task.unwrap().location.to_source_rect(), + ) + } + }; + + instances + .entry(source) + .or_insert(Vec::new()) + .push(ScalingInstance { + target_rect, + source_rect, + source_layer: source_layer as i32, + }); +} + +fn add_svg_filter_instances( + instances: &mut Vec<(BatchTextures, Vec)>, + render_tasks: &RenderTaskGraph, + filter: &SvgFilterInfo, + task_id: RenderTaskId, + input_1_task: Option, + input_2_task: Option, + extra_data_address: Option, +) { + let mut textures = BatchTextures::no_texture(); + + if let Some(saved_index) = input_1_task.map(|id| &render_tasks[id].saved_index) { + textures.colors[0] = match saved_index { + Some(saved_index) => TextureSource::RenderTaskCache(*saved_index, Swizzle::default()), + None => TextureSource::PrevPassColor, + }; + } + + if let Some(saved_index) = input_2_task.map(|id| &render_tasks[id].saved_index) { + textures.colors[1] = match saved_index { + Some(saved_index) => TextureSource::RenderTaskCache(*saved_index, Swizzle::default()), + None => TextureSource::PrevPassColor, + }; + } + + let kind = match filter { + SvgFilterInfo::Blend(..) => 0, + SvgFilterInfo::Flood(..) => 1, + SvgFilterInfo::LinearToSrgb => 2, + SvgFilterInfo::SrgbToLinear => 3, + SvgFilterInfo::Opacity(..) => 4, + SvgFilterInfo::ColorMatrix(..) => 5, + SvgFilterInfo::DropShadow(..) => 6, + SvgFilterInfo::Offset(..) => 7, + SvgFilterInfo::ComponentTransfer(..) => 8, + SvgFilterInfo::Identity => 9, + SvgFilterInfo::Composite(..) => 10, + }; + + let input_count = match filter { + SvgFilterInfo::Flood(..) => 0, + + SvgFilterInfo::LinearToSrgb | + SvgFilterInfo::SrgbToLinear | + SvgFilterInfo::Opacity(..) | + SvgFilterInfo::ColorMatrix(..) | + SvgFilterInfo::Offset(..) | + SvgFilterInfo::ComponentTransfer(..) | + SvgFilterInfo::Identity => 1, + + // Not techincally a 2 input filter, but we have 2 inputs here: original content & blurred content. + SvgFilterInfo::DropShadow(..) | + SvgFilterInfo::Blend(..) | + SvgFilterInfo::Composite(..) => 2, + }; + + let generic_int = match filter { + SvgFilterInfo::Blend(mode) => *mode as u16, + SvgFilterInfo::ComponentTransfer(data) => + ((data.r_func.to_int() << 12 | + data.g_func.to_int() << 8 | + data.b_func.to_int() << 4 | + data.a_func.to_int()) as u16), + SvgFilterInfo::Composite(operator) => + operator.as_int() as u16, + SvgFilterInfo::LinearToSrgb | + SvgFilterInfo::SrgbToLinear | + SvgFilterInfo::Flood(..) | + SvgFilterInfo::Opacity(..) | + SvgFilterInfo::ColorMatrix(..) | + SvgFilterInfo::DropShadow(..) | + SvgFilterInfo::Offset(..) | + SvgFilterInfo::Identity => 0, + }; + + let instance = SvgFilterInstance { + task_address: render_tasks.get_task_address(task_id), + input_1_task_address: input_1_task.map(|id| render_tasks.get_task_address(id)).unwrap_or(RenderTaskAddress(0)), + input_2_task_address: input_2_task.map(|id| render_tasks.get_task_address(id)).unwrap_or(RenderTaskAddress(0)), + kind, + input_count, + generic_int, + extra_data_address: extra_data_address.unwrap_or(GpuCacheAddress::INVALID), + }; + + for (ref mut batch_textures, ref mut batch) in instances.iter_mut() { + if let Some(combined_textures) = batch_textures.combine_textures(textures) { + batch.push(instance); + // Update the batch textures to the newly combined batch textures + *batch_textures = combined_textures; + return; + } + } + + instances.push((textures, vec![instance])); +} + +// Defines where the source data for a blit job can be found. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum BlitJobSource { + Texture(TextureSource, i32, DeviceIntRect), + RenderTask(RenderTaskId), +} + +// Information required to do a blit from a source to a target. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct BlitJob { + pub source: BlitJobSource, + pub target_rect: DeviceIntRect, +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug)] +pub struct LineDecorationJob { + pub task_rect: DeviceRect, + pub local_size: LayoutSize, + pub wavy_line_thickness: f32, + pub style: i32, + pub axis_select: f32, +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[repr(C)] +pub struct GradientJob { + pub task_rect: DeviceRect, + pub stops: [f32; GRADIENT_FP_STOPS], + pub colors: [PremultipliedColorF; GRADIENT_FP_STOPS], + pub axis_select: f32, + pub start_stop: [f32; 2], +} + +/// Frame output information for a given pipeline ID. +/// Storing the task ID allows the renderer to find +/// the target rect within the render target that this +/// pipeline exists at. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct FrameOutput { + pub task_id: RenderTaskId, + pub pipeline_id: PipelineId, +} diff --git a/third_party/webrender/webrender/src/render_task.rs b/third_party/webrender/webrender/src/render_task.rs new file mode 100644 index 00000000000..acfae0ebae6 --- /dev/null +++ b/third_party/webrender/webrender/src/render_task.rs @@ -0,0 +1,1564 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{CompositeOperator, FilterPrimitive, FilterPrimitiveInput, FilterPrimitiveKind}; +use api::{LineStyle, LineOrientation, ClipMode, MixBlendMode, ColorF, ColorSpace}; +use api::units::*; +use crate::clip::{ClipDataStore, ClipItemKind, ClipStore, ClipNodeRange, ClipNodeFlags}; +use crate::spatial_tree::SpatialNodeIndex; +use crate::filterdata::SFilterData; +use crate::frame_builder::FrameBuilderConfig; +use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle}; +use crate::gpu_types::{BorderInstance, ImageSource, UvRectKind}; +use crate::internal_types::{CacheTextureId, FastHashMap, LayerIndex, SavedTargetIndex}; +use crate::picture::ResolvedSurfaceTexture; +use crate::prim_store::{PictureIndex, PrimitiveVisibilityMask}; +use crate::prim_store::image::ImageCacheKey; +use crate::prim_store::gradient::{GRADIENT_FP_STOPS, GradientStopKey}; +#[cfg(feature = "debugger")] +use crate::print_tree::{PrintTreePrinter}; +use crate::resource_cache::ResourceCache; +use std::{usize, f32, i32, u32}; +use crate::render_target::{RenderTargetIndex, RenderTargetKind}; +use crate::render_task_graph::{RenderTaskGraph, RenderTaskId}; +use crate::render_task_cache::{RenderTaskCacheKey, RenderTaskCacheKeyKind}; +use smallvec::SmallVec; + +const RENDER_TASK_SIZE_SANITY_CHECK: i32 = 16000; +const FLOATS_PER_RENDER_TASK_INFO: usize = 8; +pub const MAX_BLUR_STD_DEVIATION: f32 = 4.0; +pub const MIN_DOWNSCALING_RT_SIZE: i32 = 8; + +fn render_task_sanity_check(size: &DeviceIntSize) { + if size.width > RENDER_TASK_SIZE_SANITY_CHECK || + size.height > RENDER_TASK_SIZE_SANITY_CHECK { + error!("Attempting to create a render task of size {}x{}", size.width, size.height); + panic!(); + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(C)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct RenderTaskAddress(pub u16); + +/// Identifies the output buffer location for a given `RenderTask`. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum RenderTaskLocation { + /// The `RenderTask` should be drawn to a fixed region in a specific render + /// target. This is used for the root `RenderTask`, where the main + /// framebuffer is used as the render target. + Fixed(DeviceIntRect), + /// The `RenderTask` should be drawn to a target provided by the atlas + /// allocator. This is the most common case. + /// + /// The second member specifies the width and height of the task + /// output, and the first member is initially left as `None`. During the + /// build phase, we invoke `RenderTargetList::alloc()` and store the + /// resulting location in the first member. That location identifies the + /// render target and the offset of the allocated region within that target. + Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize), + /// The output of the `RenderTask` will be persisted beyond this frame, and + /// thus should be drawn into the `TextureCache`. + TextureCache { + /// Which texture in the texture cache should be drawn into. + texture: CacheTextureId, + /// The target layer in the above texture. + layer: LayerIndex, + /// The target region within the above layer. + rect: DeviceIntRect, + + }, + /// This render task will be drawn to a picture cache texture that is + /// persisted between both frames and scenes, if the content remains valid. + PictureCache { + /// Describes either a WR texture or a native OS compositor target + surface: ResolvedSurfaceTexture, + /// Size in device pixels of this picture cache tile. + size: DeviceIntSize, + }, +} + +impl RenderTaskLocation { + /// Returns true if this is a dynamic location. + pub fn is_dynamic(&self) -> bool { + match *self { + RenderTaskLocation::Dynamic(..) => true, + _ => false, + } + } + + pub fn size(&self) -> DeviceIntSize { + match self { + RenderTaskLocation::Fixed(rect) => rect.size, + RenderTaskLocation::Dynamic(_, size) => *size, + RenderTaskLocation::TextureCache { rect, .. } => rect.size, + RenderTaskLocation::PictureCache { size, .. } => *size, + } + } + + pub fn to_source_rect(&self) -> (DeviceIntRect, LayerIndex) { + match *self { + RenderTaskLocation::Fixed(rect) => (rect, 0), + RenderTaskLocation::Dynamic(None, _) => panic!("Expected position to be set for the task!"), + RenderTaskLocation::Dynamic(Some((origin, layer)), size) => (DeviceIntRect::new(origin, size), layer.0 as LayerIndex), + RenderTaskLocation::TextureCache { rect, layer, .. } => (rect, layer), + RenderTaskLocation::PictureCache { .. } => { + panic!("bug: picture cache tasks should never be a source!"); + } + } + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct CacheMaskTask { + pub actual_rect: DeviceIntRect, + pub root_spatial_node_index: SpatialNodeIndex, + pub clip_node_range: ClipNodeRange, + pub device_pixel_scale: DevicePixelScale, +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ClipRegionTask { + pub clip_data_address: GpuCacheAddress, + pub local_pos: LayoutPoint, + pub device_pixel_scale: DevicePixelScale, +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct PictureTask { + pub pic_index: PictureIndex, + pub can_merge: bool, + pub content_origin: DeviceIntPoint, + pub uv_rect_handle: GpuCacheHandle, + pub surface_spatial_node_index: SpatialNodeIndex, + uv_rect_kind: UvRectKind, + pub device_pixel_scale: DevicePixelScale, + /// A bitfield that describes which dirty regions should be included + /// in batches built for this picture task. + pub vis_mask: PrimitiveVisibilityMask, + pub scissor_rect: Option, + pub valid_rect: Option, +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct BlurTask { + pub blur_std_deviation: f32, + pub target_kind: RenderTargetKind, + pub uv_rect_handle: GpuCacheHandle, + pub blur_region: DeviceIntSize, + uv_rect_kind: UvRectKind, +} + +impl BlurTask { + #[cfg(feature = "debugger")] + fn print_with(&self, pt: &mut T) { + pt.add_item(format!("std deviation: {}", self.blur_std_deviation)); + pt.add_item(format!("target: {:?}", self.target_kind)); + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ScalingTask { + pub target_kind: RenderTargetKind, + pub image: Option, + uv_rect_kind: UvRectKind, + pub padding: DeviceIntSideOffsets, +} + +// Where the source data for a blit task can be found. +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum BlitSource { + Image { + key: ImageCacheKey, + }, + RenderTask { + task_id: RenderTaskId, + }, +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct BorderTask { + pub instances: Vec, +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct BlitTask { + pub source: BlitSource, + pub padding: DeviceIntSideOffsets, +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct GradientTask { + pub stops: [GradientStopKey; GRADIENT_FP_STOPS], + pub orientation: LineOrientation, + pub start_point: f32, + pub end_point: f32, +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct LineDecorationTask { + pub wavy_line_thickness: f32, + pub style: LineStyle, + pub orientation: LineOrientation, + pub local_size: LayoutSize, +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum SvgFilterInfo { + Blend(MixBlendMode), + Flood(ColorF), + LinearToSrgb, + SrgbToLinear, + Opacity(f32), + ColorMatrix(Box<[f32; 20]>), + DropShadow(ColorF), + Offset(DeviceVector2D), + ComponentTransfer(SFilterData), + Composite(CompositeOperator), + // TODO: This is used as a hack to ensure that a blur task's input is always in the blur's previous pass. + Identity, +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct SvgFilterTask { + pub info: SvgFilterInfo, + pub extra_gpu_cache_handle: Option, + pub uv_rect_handle: GpuCacheHandle, + uv_rect_kind: UvRectKind, +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct RenderTaskData { + pub data: [f32; FLOATS_PER_RENDER_TASK_INFO], +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum RenderTaskKind { + Picture(PictureTask), + CacheMask(CacheMaskTask), + ClipRegion(ClipRegionTask), + VerticalBlur(BlurTask), + HorizontalBlur(BlurTask), + Readback(DeviceIntRect), + Scaling(ScalingTask), + Blit(BlitTask), + Border(BorderTask), + LineDecoration(LineDecorationTask), + Gradient(GradientTask), + SvgFilter(SvgFilterTask), + #[cfg(test)] + Test(RenderTargetKind), +} + +impl RenderTaskKind { + pub fn as_str(&self) -> &'static str { + match *self { + RenderTaskKind::Picture(..) => "Picture", + RenderTaskKind::CacheMask(..) => "CacheMask", + RenderTaskKind::ClipRegion(..) => "ClipRegion", + RenderTaskKind::VerticalBlur(..) => "VerticalBlur", + RenderTaskKind::HorizontalBlur(..) => "HorizontalBlur", + RenderTaskKind::Readback(..) => "Readback", + RenderTaskKind::Scaling(..) => "Scaling", + RenderTaskKind::Blit(..) => "Blit", + RenderTaskKind::Border(..) => "Border", + RenderTaskKind::LineDecoration(..) => "LineDecoration", + RenderTaskKind::Gradient(..) => "Gradient", + RenderTaskKind::SvgFilter(..) => "SvgFilter", + #[cfg(test)] + RenderTaskKind::Test(..) => "Test", + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum ClearMode { + // Applicable to color and alpha targets. + Zero, + One, + /// This task doesn't care what it is cleared to - it will completely overwrite it. + DontCare, + + // Applicable to color targets only. + Transparent, +} + +/// In order to avoid duplicating the down-scaling and blur passes when a picture has several blurs, +/// we use a local (primitive-level) cache of the render tasks generated for a single shadowed primitive +/// in a single frame. +pub type BlurTaskCache = FastHashMap; + +/// Since we only use it within a single primitive, the key only needs to contain the down-scaling level +/// and the blur std deviation. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum BlurTaskKey { + DownScale(u32), + Blur { downscale_level: u32, stddev_x: u32, stddev_y: u32 }, +} + +impl BlurTaskKey { + fn downscale_and_blur(downscale_level: u32, blur_stddev: DeviceSize) -> Self { + // Quantise the std deviations and store it as integers to work around + // Eq and Hash's f32 allergy. + // The blur radius is rounded before RenderTask::new_blur so we don't need + // a lot of precision. + const QUANTIZATION_FACTOR: f32 = 1024.0; + let stddev_x = (blur_stddev.width * QUANTIZATION_FACTOR) as u32; + let stddev_y = (blur_stddev.height * QUANTIZATION_FACTOR) as u32; + BlurTaskKey::Blur { downscale_level, stddev_x, stddev_y } + } +} + +// The majority of render tasks have 0, 1 or 2 dependencies, except for pictures that +// typically have dozens to hundreds of dependencies. SmallVec with 2 inline elements +// avoids many tiny heap allocations in pages with a lot of text shadows and other +// types of render tasks. +pub type TaskDependencies = SmallVec<[RenderTaskId;2]>; + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct RenderTask { + pub location: RenderTaskLocation, + pub children: TaskDependencies, + pub kind: RenderTaskKind, + pub clear_mode: ClearMode, + pub saved_index: Option, +} + +impl RenderTask { + #[inline] + pub fn with_dynamic_location( + size: DeviceIntSize, + children: TaskDependencies, + kind: RenderTaskKind, + clear_mode: ClearMode, + ) -> Self { + render_task_sanity_check(&size); + + RenderTask { + location: RenderTaskLocation::Dynamic(None, size), + children, + kind, + clear_mode, + saved_index: None, + } + } + + #[cfg(test)] + pub fn new_test( + target: RenderTargetKind, + location: RenderTaskLocation, + children: TaskDependencies, + ) -> Self { + RenderTask { + location, + children, + kind: RenderTaskKind::Test(target), + clear_mode: ClearMode::Transparent, + saved_index: None, + } + } + + pub fn new_picture( + location: RenderTaskLocation, + unclipped_size: DeviceSize, + pic_index: PictureIndex, + content_origin: DeviceIntPoint, + uv_rect_kind: UvRectKind, + surface_spatial_node_index: SpatialNodeIndex, + device_pixel_scale: DevicePixelScale, + vis_mask: PrimitiveVisibilityMask, + scissor_rect: Option, + valid_rect: Option, + ) -> Self { + let size = match location { + RenderTaskLocation::Dynamic(_, size) => size, + RenderTaskLocation::Fixed(rect) => rect.size, + RenderTaskLocation::TextureCache { rect, .. } => rect.size, + RenderTaskLocation::PictureCache { size, .. } => size, + }; + + render_task_sanity_check(&size); + + let can_merge = size.width as f32 >= unclipped_size.width && + size.height as f32 >= unclipped_size.height; + + RenderTask { + location, + children: TaskDependencies::new(), + kind: RenderTaskKind::Picture(PictureTask { + pic_index, + content_origin, + can_merge, + uv_rect_handle: GpuCacheHandle::new(), + uv_rect_kind, + surface_spatial_node_index, + device_pixel_scale, + vis_mask, + scissor_rect, + valid_rect, + }), + clear_mode: ClearMode::Transparent, + saved_index: None, + } + } + + pub fn new_gradient( + size: DeviceIntSize, + stops: [GradientStopKey; GRADIENT_FP_STOPS], + orientation: LineOrientation, + start_point: f32, + end_point: f32, + ) -> Self { + RenderTask::with_dynamic_location( + size, + TaskDependencies::new(), + RenderTaskKind::Gradient(GradientTask { + stops, + orientation, + start_point, + end_point, + }), + ClearMode::DontCare, + ) + } + + pub fn new_readback(screen_rect: DeviceIntRect) -> Self { + RenderTask::with_dynamic_location( + screen_rect.size, + TaskDependencies::new(), + RenderTaskKind::Readback(screen_rect), + ClearMode::Transparent, + ) + } + + pub fn new_blit( + size: DeviceIntSize, + source: BlitSource, + ) -> Self { + RenderTask::new_blit_with_padding(size, DeviceIntSideOffsets::zero(), source) + } + + pub fn new_blit_with_padding( + padded_size: DeviceIntSize, + padding: DeviceIntSideOffsets, + source: BlitSource, + ) -> Self { + // If this blit uses a render task as a source, + // ensure it's added as a child task. This will + // ensure it gets allocated in the correct pass + // and made available as an input when this task + // executes. + let children = match source { + BlitSource::RenderTask { task_id } => smallvec![task_id], + BlitSource::Image { .. } => smallvec![], + }; + + RenderTask::with_dynamic_location( + padded_size, + children, + RenderTaskKind::Blit(BlitTask { + source, + padding, + }), + ClearMode::Transparent, + ) + } + + pub fn new_line_decoration( + size: DeviceIntSize, + style: LineStyle, + orientation: LineOrientation, + wavy_line_thickness: f32, + local_size: LayoutSize, + ) -> Self { + RenderTask::with_dynamic_location( + size, + TaskDependencies::new(), + RenderTaskKind::LineDecoration(LineDecorationTask { + style, + orientation, + wavy_line_thickness, + local_size, + }), + ClearMode::Transparent, + ) + } + + pub fn new_mask( + outer_rect: DeviceIntRect, + clip_node_range: ClipNodeRange, + root_spatial_node_index: SpatialNodeIndex, + clip_store: &mut ClipStore, + gpu_cache: &mut GpuCache, + resource_cache: &mut ResourceCache, + render_tasks: &mut RenderTaskGraph, + clip_data_store: &mut ClipDataStore, + device_pixel_scale: DevicePixelScale, + fb_config: &FrameBuilderConfig, + ) -> RenderTaskId { + // Step through the clip sources that make up this mask. If we find + // any box-shadow clip sources, request that image from the render + // task cache. This allows the blurred box-shadow rect to be cached + // in the texture cache across frames. + // TODO(gw): Consider moving this logic outside this function, especially + // as we add more clip sources that depend on render tasks. + // TODO(gw): If this ever shows up in a profile, we could pre-calculate + // whether a ClipSources contains any box-shadows and skip + // this iteration for the majority of cases. + let mut needs_clear = fb_config.gpu_supports_fast_clears; + + for i in 0 .. clip_node_range.count { + let clip_instance = clip_store.get_instance_from_range(&clip_node_range, i); + let clip_node = &mut clip_data_store[clip_instance.handle]; + match clip_node.item.kind { + ClipItemKind::BoxShadow { ref mut source } => { + let (cache_size, cache_key) = source.cache_key + .as_ref() + .expect("bug: no cache key set") + .clone(); + let blur_radius_dp = cache_key.blur_radius_dp as f32; + let clip_data_address = gpu_cache.get_address(&source.clip_data_handle); + + // Request a cacheable render task with a blurred, minimal + // sized box-shadow rect. + source.cache_handle = Some(resource_cache.request_render_task( + RenderTaskCacheKey { + size: cache_size, + kind: RenderTaskCacheKeyKind::BoxShadow(cache_key), + }, + gpu_cache, + render_tasks, + None, + false, + |render_tasks| { + // Draw the rounded rect. + let mask_task_id = render_tasks.add().init(RenderTask::new_rounded_rect_mask( + cache_size, + clip_data_address, + source.minimal_shadow_rect.origin, + device_pixel_scale, + fb_config, + )); + + // Blur it + RenderTask::new_blur( + DeviceSize::new(blur_radius_dp, blur_radius_dp), + mask_task_id, + render_tasks, + RenderTargetKind::Alpha, + ClearMode::Zero, + None, + cache_size, + ) + } + )); + } + ClipItemKind::Rectangle { mode: ClipMode::Clip, .. } => { + if !clip_instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) { + // This is conservative - it's only the case that we actually need + // a clear here if we end up adding this mask via add_tiled_clip_mask, + // but for simplicity we will just clear if any of these are encountered, + // since they are rare. + needs_clear = true; + } + } + ClipItemKind::Rectangle { mode: ClipMode::ClipOut, .. } | + ClipItemKind::RoundedRectangle { .. } | + ClipItemKind::Image { .. } => {} + } + } + + // If we have a potentially tiled clip mask, clear the mask area first. Otherwise, + // the first (primary) clip mask will overwrite all the clip mask pixels with + // blending disabled to set to the initial value. + let clear_mode = if needs_clear { + ClearMode::One + } else { + ClearMode::DontCare + }; + + render_tasks.add().init( + RenderTask::with_dynamic_location( + outer_rect.size, + smallvec![], + RenderTaskKind::CacheMask(CacheMaskTask { + actual_rect: outer_rect, + clip_node_range, + root_spatial_node_index, + device_pixel_scale, + }), + clear_mode, + ) + ) + } + + pub fn new_rounded_rect_mask( + size: DeviceIntSize, + clip_data_address: GpuCacheAddress, + local_pos: LayoutPoint, + device_pixel_scale: DevicePixelScale, + fb_config: &FrameBuilderConfig, + ) -> Self { + let clear_mode = if fb_config.gpu_supports_fast_clears { + ClearMode::One + } else { + ClearMode::DontCare + }; + + RenderTask::with_dynamic_location( + size, + TaskDependencies::new(), + RenderTaskKind::ClipRegion(ClipRegionTask { + clip_data_address, + local_pos, + device_pixel_scale, + }), + clear_mode, + ) + } + + // In order to do the blur down-scaling passes without introducing errors, we need the + // source of each down-scale pass to be a multuple of two. If need be, this inflates + // the source size so that each down-scale pass will sample correctly. + pub fn adjusted_blur_source_size(original_size: DeviceSize, mut std_dev: DeviceSize) -> DeviceSize { + let mut adjusted_size = original_size; + let mut scale_factor = 1.0; + while std_dev.width > MAX_BLUR_STD_DEVIATION && std_dev.height > MAX_BLUR_STD_DEVIATION { + if adjusted_size.width < MIN_DOWNSCALING_RT_SIZE as f32 || + adjusted_size.height < MIN_DOWNSCALING_RT_SIZE as f32 { + break; + } + std_dev = std_dev * 0.5; + scale_factor *= 2.0; + adjusted_size = (original_size.to_f32() / scale_factor).ceil(); + } + + adjusted_size * scale_factor + } + + // Construct a render task to apply a blur to a primitive. + // The render task chain that is constructed looks like: + // + // PrimitiveCacheTask: Draw the primitives. + // ^ + // | + // DownscalingTask(s): Each downscaling task reduces the size of render target to + // ^ half. Also reduce the std deviation to half until the std + // | deviation less than 4.0. + // | + // | + // VerticalBlurTask: Apply the separable vertical blur to the primitive. + // ^ + // | + // HorizontalBlurTask: Apply the separable horizontal blur to the vertical blur. + // | + // +---- This is stored as the input task to the primitive shader. + // + pub fn new_blur( + blur_std_deviation: DeviceSize, + src_task_id: RenderTaskId, + render_tasks: &mut RenderTaskGraph, + target_kind: RenderTargetKind, + clear_mode: ClearMode, + mut blur_cache: Option<&mut BlurTaskCache>, + blur_region: DeviceIntSize, + ) -> RenderTaskId { + // Adjust large std deviation value. + let mut adjusted_blur_std_deviation = blur_std_deviation; + let (blur_target_size, uv_rect_kind) = { + let src_task = &render_tasks[src_task_id]; + (src_task.get_dynamic_size(), src_task.uv_rect_kind()) + }; + let mut adjusted_blur_target_size = blur_target_size; + let mut downscaling_src_task_id = src_task_id; + let mut scale_factor = 1.0; + let mut n_downscales = 1; + while adjusted_blur_std_deviation.width > MAX_BLUR_STD_DEVIATION && + adjusted_blur_std_deviation.height > MAX_BLUR_STD_DEVIATION { + if adjusted_blur_target_size.width < MIN_DOWNSCALING_RT_SIZE || + adjusted_blur_target_size.height < MIN_DOWNSCALING_RT_SIZE { + break; + } + adjusted_blur_std_deviation = adjusted_blur_std_deviation * 0.5; + scale_factor *= 2.0; + adjusted_blur_target_size = (blur_target_size.to_f32() / scale_factor).to_i32(); + + let cached_task = match blur_cache { + Some(ref mut cache) => cache.get(&BlurTaskKey::DownScale(n_downscales)).cloned(), + None => None, + }; + + downscaling_src_task_id = cached_task.unwrap_or_else(|| { + RenderTask::new_scaling( + downscaling_src_task_id, + render_tasks, + target_kind, + adjusted_blur_target_size, + ) + }); + + if let Some(ref mut cache) = blur_cache { + cache.insert(BlurTaskKey::DownScale(n_downscales), downscaling_src_task_id); + } + + n_downscales += 1; + } + + + let blur_key = BlurTaskKey::downscale_and_blur(n_downscales, adjusted_blur_std_deviation); + + let cached_task = match blur_cache { + Some(ref mut cache) => cache.get(&blur_key).cloned(), + None => None, + }; + + let blur_region = blur_region / (scale_factor as i32); + + let blur_task_id = cached_task.unwrap_or_else(|| { + let blur_task_v = render_tasks.add().init(RenderTask::with_dynamic_location( + adjusted_blur_target_size, + smallvec![downscaling_src_task_id], + RenderTaskKind::VerticalBlur(BlurTask { + blur_std_deviation: adjusted_blur_std_deviation.height, + target_kind, + uv_rect_handle: GpuCacheHandle::new(), + blur_region, + uv_rect_kind, + }), + clear_mode, + )); + + render_tasks.add().init(RenderTask::with_dynamic_location( + adjusted_blur_target_size, + smallvec![blur_task_v], + RenderTaskKind::HorizontalBlur(BlurTask { + blur_std_deviation: adjusted_blur_std_deviation.width, + target_kind, + uv_rect_handle: GpuCacheHandle::new(), + blur_region, + uv_rect_kind, + }), + clear_mode, + )) + }); + + if let Some(ref mut cache) = blur_cache { + cache.insert(blur_key, blur_task_id); + } + + blur_task_id + } + + pub fn new_border_segment( + size: DeviceIntSize, + instances: Vec, + ) -> Self { + RenderTask::with_dynamic_location( + size, + TaskDependencies::new(), + RenderTaskKind::Border(BorderTask { + instances, + }), + ClearMode::Transparent, + ) + } + + pub fn new_scaling( + src_task_id: RenderTaskId, + render_tasks: &mut RenderTaskGraph, + target_kind: RenderTargetKind, + size: DeviceIntSize, + ) -> RenderTaskId { + Self::new_scaling_with_padding( + BlitSource::RenderTask { task_id: src_task_id }, + render_tasks, + target_kind, + size, + DeviceIntSideOffsets::zero(), + ) + } + + pub fn new_scaling_with_padding( + source: BlitSource, + render_tasks: &mut RenderTaskGraph, + target_kind: RenderTargetKind, + padded_size: DeviceIntSize, + padding: DeviceIntSideOffsets, + ) -> RenderTaskId { + let (uv_rect_kind, children, image) = match source { + BlitSource::RenderTask { task_id } => (render_tasks[task_id].uv_rect_kind(), smallvec![task_id], None), + BlitSource::Image { key } => (UvRectKind::Rect, smallvec![], Some(key)), + }; + + render_tasks.add().init( + RenderTask::with_dynamic_location( + padded_size, + children, + RenderTaskKind::Scaling(ScalingTask { + target_kind, + image, + uv_rect_kind, + padding, + }), + ClearMode::DontCare, + ) + ) + } + + pub fn new_svg_filter( + filter_primitives: &[FilterPrimitive], + filter_datas: &[SFilterData], + render_tasks: &mut RenderTaskGraph, + content_size: DeviceIntSize, + uv_rect_kind: UvRectKind, + original_task_id: RenderTaskId, + device_pixel_scale: DevicePixelScale, + ) -> RenderTaskId { + + if filter_primitives.is_empty() { + return original_task_id; + } + + // Resolves the input to a filter primitive + let get_task_input = | + input: &FilterPrimitiveInput, + filter_primitives: &[FilterPrimitive], + render_tasks: &mut RenderTaskGraph, + cur_index: usize, + outputs: &[RenderTaskId], + original: RenderTaskId, + color_space: ColorSpace, + | { + // TODO(cbrewster): Not sure we can assume that the original input is sRGB. + let (mut task_id, input_color_space) = match input.to_index(cur_index) { + Some(index) => (outputs[index], filter_primitives[index].color_space), + None => (original, ColorSpace::Srgb), + }; + + match (input_color_space, color_space) { + (ColorSpace::Srgb, ColorSpace::LinearRgb) => { + task_id = render_tasks.add().init(RenderTask::new_svg_filter_primitive( + smallvec![task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::SrgbToLinear, + )); + }, + (ColorSpace::LinearRgb, ColorSpace::Srgb) => { + task_id = render_tasks.add().init(RenderTask::new_svg_filter_primitive( + smallvec![task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::LinearToSrgb, + )); + }, + _ => {}, + } + + task_id + }; + + let mut outputs = vec![]; + let mut cur_filter_data = 0; + for (cur_index, primitive) in filter_primitives.iter().enumerate() { + let render_task_id = match primitive.kind { + FilterPrimitiveKind::Identity(ref identity) => { + // Identity does not create a task, it provides its input's render task + get_task_input( + &identity.input, + filter_primitives, + render_tasks, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ) + } + FilterPrimitiveKind::Blend(ref blend) => { + let input_1_task_id = get_task_input( + &blend.input1, + filter_primitives, + render_tasks, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + let input_2_task_id = get_task_input( + &blend.input2, + filter_primitives, + render_tasks, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + + render_tasks.add().init(RenderTask::new_svg_filter_primitive( + smallvec![input_1_task_id, input_2_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::Blend(blend.mode), + )) + }, + FilterPrimitiveKind::Flood(ref flood) => { + render_tasks.add().init(RenderTask::new_svg_filter_primitive( + smallvec![], + content_size, + uv_rect_kind, + SvgFilterInfo::Flood(flood.color), + )) + } + FilterPrimitiveKind::Blur(ref blur) => { + let blur_std_deviation = blur.radius * device_pixel_scale.0; + let input_task_id = get_task_input( + &blur.input, + filter_primitives, + render_tasks, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + + RenderTask::new_blur( + DeviceSize::new(blur_std_deviation, blur_std_deviation), + // TODO: This is a hack to ensure that a blur task's input is always + // in the blur's previous pass. + render_tasks.add().init(RenderTask::new_svg_filter_primitive( + smallvec![input_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::Identity, + )), + render_tasks, + RenderTargetKind::Color, + ClearMode::Transparent, + None, + content_size, + ) + } + FilterPrimitiveKind::Opacity(ref opacity) => { + let input_task_id = get_task_input( + &opacity.input, + filter_primitives, + render_tasks, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + + render_tasks.add().init(RenderTask::new_svg_filter_primitive( + smallvec![input_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::Opacity(opacity.opacity), + )) + } + FilterPrimitiveKind::ColorMatrix(ref color_matrix) => { + let input_task_id = get_task_input( + &color_matrix.input, + filter_primitives, + render_tasks, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + + render_tasks.add().init(RenderTask::new_svg_filter_primitive( + smallvec![input_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::ColorMatrix(Box::new(color_matrix.matrix)), + )) + } + FilterPrimitiveKind::DropShadow(ref drop_shadow) => { + let input_task_id = get_task_input( + &drop_shadow.input, + filter_primitives, + render_tasks, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + + let blur_std_deviation = drop_shadow.shadow.blur_radius * device_pixel_scale.0; + let offset = drop_shadow.shadow.offset * LayoutToWorldScale::new(1.0) * device_pixel_scale; + + let offset_task_id = render_tasks.add().init( + RenderTask::new_svg_filter_primitive( + smallvec![input_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::Offset(offset), + ) + ); + + let blur_task_id = RenderTask::new_blur( + DeviceSize::new(blur_std_deviation, blur_std_deviation), + offset_task_id, + render_tasks, + RenderTargetKind::Color, + ClearMode::Transparent, + None, + content_size, + ); + + render_tasks.add().init(RenderTask::new_svg_filter_primitive( + smallvec![input_task_id, blur_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::DropShadow(drop_shadow.shadow.color), + )) + } + FilterPrimitiveKind::ComponentTransfer(ref component_transfer) => { + let input_task_id = get_task_input( + &component_transfer.input, + filter_primitives, + render_tasks, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + + let filter_data = &filter_datas[cur_filter_data]; + cur_filter_data += 1; + if filter_data.is_identity() { + input_task_id + } else { + render_tasks.add().init(RenderTask::new_svg_filter_primitive( + smallvec![input_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::ComponentTransfer(filter_data.clone()), + )) + } + } + FilterPrimitiveKind::Offset(ref info) => { + let input_task_id = get_task_input( + &info.input, + filter_primitives, + render_tasks, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + + let offset = info.offset * LayoutToWorldScale::new(1.0) * device_pixel_scale; + render_tasks.add().init(RenderTask::new_svg_filter_primitive( + smallvec![input_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::Offset(offset), + )) + } + FilterPrimitiveKind::Composite(info) => { + let input_1_task_id = get_task_input( + &info.input1, + filter_primitives, + render_tasks, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + let input_2_task_id = get_task_input( + &info.input2, + filter_primitives, + render_tasks, + cur_index, + &outputs, + original_task_id, + primitive.color_space + ); + + render_tasks.add().init(RenderTask::new_svg_filter_primitive( + smallvec![input_1_task_id, input_2_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::Composite(info.operator), + )) + } + }; + outputs.push(render_task_id); + } + + // The output of a filter is the output of the last primitive in the chain. + let mut render_task_id = *outputs.last().unwrap(); + + // Convert to sRGB if needed + if filter_primitives.last().unwrap().color_space == ColorSpace::LinearRgb { + render_task_id = render_tasks.add().init(RenderTask::new_svg_filter_primitive( + smallvec![render_task_id], + content_size, + uv_rect_kind, + SvgFilterInfo::LinearToSrgb, + )); + } + + render_task_id + } + + pub fn new_svg_filter_primitive( + tasks: TaskDependencies, + target_size: DeviceIntSize, + uv_rect_kind: UvRectKind, + info: SvgFilterInfo, + ) -> Self { + RenderTask::with_dynamic_location( + target_size, + tasks, + RenderTaskKind::SvgFilter(SvgFilterTask { + extra_gpu_cache_handle: None, + uv_rect_handle: GpuCacheHandle::new(), + uv_rect_kind, + info, + }), + ClearMode::Transparent, + ) + } + + pub fn uv_rect_kind(&self) -> UvRectKind { + match self.kind { + RenderTaskKind::CacheMask(..) | + RenderTaskKind::Readback(..) => { + unreachable!("bug: unexpected render task"); + } + + RenderTaskKind::Picture(ref task) => { + task.uv_rect_kind + } + + RenderTaskKind::VerticalBlur(ref task) | + RenderTaskKind::HorizontalBlur(ref task) => { + task.uv_rect_kind + } + + RenderTaskKind::Scaling(ref task) => { + task.uv_rect_kind + } + + RenderTaskKind::SvgFilter(ref task) => { + task.uv_rect_kind + } + + RenderTaskKind::ClipRegion(..) | + RenderTaskKind::Border(..) | + RenderTaskKind::Gradient(..) | + RenderTaskKind::LineDecoration(..) | + RenderTaskKind::Blit(..) => { + UvRectKind::Rect + } + + #[cfg(test)] + RenderTaskKind::Test(..) => { + unreachable!("Unexpected render task"); + } + } + } + + // Write (up to) 8 floats of data specific to the type + // of render task that is provided to the GPU shaders + // via a vertex texture. + pub fn write_task_data(&self) -> RenderTaskData { + // NOTE: The ordering and layout of these structures are + // required to match both the GPU structures declared + // in prim_shared.glsl, and also the uses in submit_batch() + // in renderer.rs. + // TODO(gw): Maybe there's a way to make this stuff a bit + // more type-safe. Although, it will always need + // to be kept in sync with the GLSL code anyway. + + let data = match self.kind { + RenderTaskKind::Picture(ref task) => { + // Note: has to match `PICTURE_TYPE_*` in shaders + [ + task.device_pixel_scale.0, + task.content_origin.x as f32, + task.content_origin.y as f32, + ] + } + RenderTaskKind::CacheMask(ref task) => { + [ + task.device_pixel_scale.0, + task.actual_rect.origin.x as f32, + task.actual_rect.origin.y as f32, + ] + } + RenderTaskKind::ClipRegion(ref task) => { + [ + task.device_pixel_scale.0, + 0.0, + 0.0, + ] + } + RenderTaskKind::VerticalBlur(ref task) | + RenderTaskKind::HorizontalBlur(ref task) => { + [ + task.blur_std_deviation, + task.blur_region.width as f32, + task.blur_region.height as f32, + ] + } + RenderTaskKind::Readback(..) | + RenderTaskKind::Scaling(..) | + RenderTaskKind::Border(..) | + RenderTaskKind::LineDecoration(..) | + RenderTaskKind::Gradient(..) | + RenderTaskKind::Blit(..) => { + [0.0; 3] + } + + + RenderTaskKind::SvgFilter(ref task) => { + match task.info { + SvgFilterInfo::Opacity(opacity) => [opacity, 0.0, 0.0], + SvgFilterInfo::Offset(offset) => [offset.x, offset.y, 0.0], + _ => [0.0; 3] + } + } + + #[cfg(test)] + RenderTaskKind::Test(..) => { + unreachable!(); + } + }; + + let (mut target_rect, target_index) = self.get_target_rect(); + // The primitives inside a fixed-location render task + // are already placed to their corresponding positions, + // so the shader doesn't need to shift by the origin. + if let RenderTaskLocation::Fixed(_) = self.location { + target_rect.origin = DeviceIntPoint::origin(); + } + + RenderTaskData { + data: [ + target_rect.origin.x as f32, + target_rect.origin.y as f32, + target_rect.size.width as f32, + target_rect.size.height as f32, + target_index.0 as f32, + data[0], + data[1], + data[2], + ] + } + } + + pub fn get_texture_address(&self, gpu_cache: &GpuCache) -> GpuCacheAddress { + match self.kind { + RenderTaskKind::Picture(ref info) => { + gpu_cache.get_address(&info.uv_rect_handle) + } + RenderTaskKind::VerticalBlur(ref info) | + RenderTaskKind::HorizontalBlur(ref info) => { + gpu_cache.get_address(&info.uv_rect_handle) + } + RenderTaskKind::SvgFilter(ref info) => { + gpu_cache.get_address(&info.uv_rect_handle) + } + RenderTaskKind::ClipRegion(..) | + RenderTaskKind::Readback(..) | + RenderTaskKind::Scaling(..) | + RenderTaskKind::Blit(..) | + RenderTaskKind::Border(..) | + RenderTaskKind::CacheMask(..) | + RenderTaskKind::Gradient(..) | + RenderTaskKind::LineDecoration(..) => { + panic!("texture handle not supported for this task kind"); + } + #[cfg(test)] + RenderTaskKind::Test(..) => { + panic!("RenderTask tests aren't expected to exercise this code"); + } + } + } + + pub fn get_dynamic_size(&self) -> DeviceIntSize { + match self.location { + RenderTaskLocation::Fixed(..) => DeviceIntSize::zero(), + RenderTaskLocation::Dynamic(_, size) => size, + RenderTaskLocation::TextureCache { rect, .. } => rect.size, + RenderTaskLocation::PictureCache { size, .. } => size, + } + } + + pub fn get_target_rect(&self) -> (DeviceIntRect, RenderTargetIndex) { + match self.location { + RenderTaskLocation::Fixed(rect) => { + (rect, RenderTargetIndex(0)) + } + // Previously, we only added render tasks after the entire + // primitive chain was determined visible. This meant that + // we could assert any render task in the list was also + // allocated (assigned to passes). Now, we add render + // tasks earlier, and the picture they belong to may be + // culled out later, so we can't assert that the task + // has been allocated. + // Render tasks that are created but not assigned to + // passes consume a row in the render task texture, but + // don't allocate any space in render targets nor + // draw any pixels. + // TODO(gw): Consider some kind of tag or other method + // to mark a task as unused explicitly. This + // would allow us to restore this debug check. + RenderTaskLocation::Dynamic(Some((origin, target_index)), size) => { + (DeviceIntRect::new(origin, size), target_index) + } + RenderTaskLocation::Dynamic(None, _) => { + (DeviceIntRect::zero(), RenderTargetIndex(0)) + } + RenderTaskLocation::TextureCache {layer, rect, .. } => { + (rect, RenderTargetIndex(layer as usize)) + } + RenderTaskLocation::PictureCache { ref surface, size, .. } => { + let layer = match surface { + ResolvedSurfaceTexture::TextureCache { layer, .. } => *layer, + ResolvedSurfaceTexture::Native { .. } => 0, + }; + + ( + DeviceIntRect::new( + DeviceIntPoint::zero(), + size, + ), + RenderTargetIndex(layer as usize), + ) + } + } + } + + pub fn target_kind(&self) -> RenderTargetKind { + match self.kind { + RenderTaskKind::LineDecoration(..) | + RenderTaskKind::Readback(..) | + RenderTaskKind::Border(..) | + RenderTaskKind::Gradient(..) | + RenderTaskKind::Picture(..) | + RenderTaskKind::Blit(..) | + RenderTaskKind::SvgFilter(..) => { + RenderTargetKind::Color + } + + RenderTaskKind::ClipRegion(..) | + RenderTaskKind::CacheMask(..) => { + RenderTargetKind::Alpha + } + + RenderTaskKind::VerticalBlur(ref task_info) | + RenderTaskKind::HorizontalBlur(ref task_info) => { + task_info.target_kind + } + + RenderTaskKind::Scaling(ref task_info) => { + task_info.target_kind + } + + #[cfg(test)] + RenderTaskKind::Test(kind) => kind, + } + } + + pub fn write_gpu_blocks( + &mut self, + gpu_cache: &mut GpuCache, + ) { + profile_scope!("write_gpu_blocks"); + let (target_rect, target_index) = self.get_target_rect(); + + let (cache_handle, uv_rect_kind) = match self.kind { + RenderTaskKind::HorizontalBlur(ref mut info) | + RenderTaskKind::VerticalBlur(ref mut info) => { + (&mut info.uv_rect_handle, info.uv_rect_kind) + } + RenderTaskKind::Picture(ref mut info) => { + (&mut info.uv_rect_handle, info.uv_rect_kind) + } + RenderTaskKind::SvgFilter(ref mut info) => { + (&mut info.uv_rect_handle, info.uv_rect_kind) + } + RenderTaskKind::Readback(..) | + RenderTaskKind::Scaling(..) | + RenderTaskKind::Blit(..) | + RenderTaskKind::ClipRegion(..) | + RenderTaskKind::Border(..) | + RenderTaskKind::CacheMask(..) | + RenderTaskKind::Gradient(..) | + RenderTaskKind::LineDecoration(..) => { + return; + } + #[cfg(test)] + RenderTaskKind::Test(..) => { + panic!("RenderTask tests aren't expected to exercise this code"); + } + }; + + if let Some(mut request) = gpu_cache.request(cache_handle) { + let p0 = target_rect.min().to_f32(); + let p1 = target_rect.max().to_f32(); + let image_source = ImageSource { + p0, + p1, + texture_layer: target_index.0 as f32, + user_data: [0.0; 3], + uv_rect_kind, + }; + image_source.write_gpu_blocks(&mut request); + } + + if let RenderTaskKind::SvgFilter(ref mut filter_task) = self.kind { + match filter_task.info { + SvgFilterInfo::ColorMatrix(ref matrix) => { + let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new); + if let Some(mut request) = gpu_cache.request(handle) { + for i in 0..5 { + request.push([matrix[i*4], matrix[i*4+1], matrix[i*4+2], matrix[i*4+3]]); + } + } + } + SvgFilterInfo::DropShadow(color) | + SvgFilterInfo::Flood(color) => { + let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new); + if let Some(mut request) = gpu_cache.request(handle) { + request.push(color.to_array()); + } + } + SvgFilterInfo::ComponentTransfer(ref data) => { + let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new); + if let Some(request) = gpu_cache.request(handle) { + data.update(request); + } + } + SvgFilterInfo::Composite(ref operator) => { + if let CompositeOperator::Arithmetic(k_vals) = operator { + let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new); + if let Some(mut request) = gpu_cache.request(handle) { + request.push(*k_vals); + } + } + } + _ => {}, + } + } + } + + #[cfg(feature = "debugger")] + pub fn print_with(&self, pt: &mut T, tree: &RenderTaskGraph) -> bool { + match self.kind { + RenderTaskKind::Picture(ref task) => { + pt.new_level(format!("Picture of {:?}", task.pic_index)); + } + RenderTaskKind::CacheMask(ref task) => { + pt.new_level(format!("CacheMask with {} clips", task.clip_node_range.count)); + pt.add_item(format!("rect: {:?}", task.actual_rect)); + } + RenderTaskKind::LineDecoration(..) => { + pt.new_level("LineDecoration".to_owned()); + } + RenderTaskKind::ClipRegion(..) => { + pt.new_level("ClipRegion".to_owned()); + } + RenderTaskKind::VerticalBlur(ref task) => { + pt.new_level("VerticalBlur".to_owned()); + task.print_with(pt); + } + RenderTaskKind::HorizontalBlur(ref task) => { + pt.new_level("HorizontalBlur".to_owned()); + task.print_with(pt); + } + RenderTaskKind::Readback(ref rect) => { + pt.new_level("Readback".to_owned()); + pt.add_item(format!("rect: {:?}", rect)); + } + RenderTaskKind::Scaling(ref kind) => { + pt.new_level("Scaling".to_owned()); + pt.add_item(format!("kind: {:?}", kind)); + } + RenderTaskKind::Border(..) => { + pt.new_level("Border".to_owned()); + } + RenderTaskKind::Blit(ref task) => { + pt.new_level("Blit".to_owned()); + pt.add_item(format!("source: {:?}", task.source)); + } + RenderTaskKind::Gradient(..) => { + pt.new_level("Gradient".to_owned()); + } + RenderTaskKind::SvgFilter(ref task) => { + pt.new_level("SvgFilter".to_owned()); + pt.add_item(format!("primitive: {:?}", task.info)); + } + #[cfg(test)] + RenderTaskKind::Test(..) => { + pt.new_level("Test".to_owned()); + } + } + + pt.add_item(format!("clear to: {:?}", self.clear_mode)); + pt.add_item(format!("dimensions: {:?}", self.location.size())); + + for &child_id in &self.children { + if tree[child_id].print_with(pt, tree) { + pt.add_item(format!("self: {:?}", child_id)) + } + } + + pt.end_level(); + true + } + + /// Mark this render task for keeping the results alive up until the end of the frame. + #[inline] + pub fn mark_for_saving(&mut self) { + match self.location { + RenderTaskLocation::Fixed(..) | + RenderTaskLocation::Dynamic(..) => { + self.saved_index = Some(SavedTargetIndex::PENDING); + } + RenderTaskLocation::TextureCache { .. } | + RenderTaskLocation::PictureCache { .. } => { + panic!("Unable to mark a permanently cached task for saving!"); + } + } + } +} diff --git a/third_party/webrender/webrender/src/render_task_cache.rs b/third_party/webrender/webrender/src/render_task_cache.rs new file mode 100644 index 00000000000..22ea235e33e --- /dev/null +++ b/third_party/webrender/webrender/src/render_task_cache.rs @@ -0,0 +1,267 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +use api::{ImageDescriptor, ImageDescriptorFlags, DirtyRect}; +use api::units::*; +use crate::border::BorderSegmentCacheKey; +use crate::box_shadow::{BoxShadowCacheKey}; +use crate::device::TextureFilter; +use crate::freelist::{FreeList, FreeListHandle, WeakFreeListHandle}; +use crate::gpu_cache::GpuCache; +use crate::internal_types::FastHashMap; +use crate::prim_store::image::ImageCacheKey; +use crate::prim_store::gradient::GradientCacheKey; +use crate::prim_store::line_dec::LineDecorationCacheKey; +use crate::resource_cache::CacheItem; +use std::{mem, usize, f32, i32}; +use crate::texture_cache::{TextureCache, TextureCacheHandle, Eviction}; +use crate::render_target::RenderTargetKind; +use crate::render_task::{RenderTask, RenderTaskLocation}; +use crate::render_task_graph::{RenderTaskGraph, RenderTaskId}; + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum RenderTaskCacheKeyKind { + BoxShadow(BoxShadowCacheKey), + Image(ImageCacheKey), + BorderSegment(BorderSegmentCacheKey), + LineDecoration(LineDecorationCacheKey), + Gradient(GradientCacheKey), +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct RenderTaskCacheKey { + pub size: DeviceIntSize, + pub kind: RenderTaskCacheKeyKind, +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct RenderTaskCacheEntry { + user_data: Option<[f32; 3]>, + is_opaque: bool, + pub handle: TextureCacheHandle, +} + +#[derive(Debug, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub enum RenderTaskCacheMarker {} + +// A cache of render tasks that are stored in the texture +// cache for usage across frames. +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct RenderTaskCache { + map: FastHashMap>, + cache_entries: FreeList, +} + +pub type RenderTaskCacheEntryHandle = WeakFreeListHandle; + +impl RenderTaskCache { + pub fn new() -> Self { + RenderTaskCache { + map: FastHashMap::default(), + cache_entries: FreeList::new(), + } + } + + pub fn clear(&mut self) { + self.map.clear(); + self.cache_entries.clear(); + } + + pub fn begin_frame( + &mut self, + texture_cache: &mut TextureCache, + ) { + profile_scope!("begin_frame"); + // Drop any items from the cache that have been + // evicted from the texture cache. + // + // This isn't actually necessary for the texture + // cache to be able to evict old render tasks. + // It will evict render tasks as required, since + // the access time in the texture cache entry will + // be stale if this task hasn't been requested + // for a while. + // + // Nonetheless, we should remove stale entries + // from here so that this hash map doesn't + // grow indefinitely! + let cache_entries = &mut self.cache_entries; + + self.map.retain(|_, handle| { + let retain = texture_cache.is_allocated( + &cache_entries.get(handle).handle, + ); + if !retain { + let handle = mem::replace(handle, FreeListHandle::invalid()); + cache_entries.free(handle); + } + retain + }); + } + + fn alloc_render_task( + render_task: &mut RenderTask, + entry: &mut RenderTaskCacheEntry, + gpu_cache: &mut GpuCache, + texture_cache: &mut TextureCache, + ) { + // Find out what size to alloc in the texture cache. + let size = match render_task.location { + RenderTaskLocation::Fixed(..) | + RenderTaskLocation::PictureCache { .. } | + RenderTaskLocation::TextureCache { .. } => { + panic!("BUG: dynamic task was expected"); + } + RenderTaskLocation::Dynamic(_, size) => size, + }; + + // Select the right texture page to allocate from. + let image_format = match render_task.target_kind() { + RenderTargetKind::Color => texture_cache.shared_color_expected_format(), + RenderTargetKind::Alpha => texture_cache.shared_alpha_expected_format(), + }; + + let flags = if entry.is_opaque { + ImageDescriptorFlags::IS_OPAQUE + } else { + ImageDescriptorFlags::empty() + }; + + let descriptor = ImageDescriptor::new( + size.width, + size.height, + image_format, + flags, + ); + + // Allocate space in the texture cache, but don't supply + // and CPU-side data to be uploaded. + texture_cache.update( + &mut entry.handle, + descriptor, + TextureFilter::Linear, + None, + entry.user_data.unwrap_or([0.0; 3]), + DirtyRect::All, + gpu_cache, + None, + render_task.uv_rect_kind(), + Eviction::Auto, + ); + + // Get the allocation details in the texture cache, and store + // this in the render task. The renderer will draw this + // task into the appropriate layer and rect of the texture + // cache on this frame. + let (texture_id, texture_layer, uv_rect, _, _) = + texture_cache.get_cache_location(&entry.handle); + + render_task.location = RenderTaskLocation::TextureCache { + texture: texture_id, + layer: texture_layer, + rect: uv_rect.to_i32(), + }; + } + + pub fn request_render_task( + &mut self, + key: RenderTaskCacheKey, + texture_cache: &mut TextureCache, + gpu_cache: &mut GpuCache, + render_tasks: &mut RenderTaskGraph, + user_data: Option<[f32; 3]>, + is_opaque: bool, + f: F, + ) -> Result + where + F: FnOnce(&mut RenderTaskGraph) -> Result, + { + // Get the texture cache handle for this cache key, + // or create one. + let cache_entries = &mut self.cache_entries; + let entry_handle = self.map.entry(key).or_insert_with(|| { + let entry = RenderTaskCacheEntry { + handle: TextureCacheHandle::invalid(), + user_data, + is_opaque, + }; + cache_entries.insert(entry) + }); + let cache_entry = cache_entries.get_mut(entry_handle); + + // Check if this texture cache handle is valid. + if texture_cache.request(&cache_entry.handle, gpu_cache) { + // Invoke user closure to get render task chain + // to draw this into the texture cache. + let render_task_id = f(render_tasks)?; + render_tasks.cacheable_render_tasks.push(render_task_id); + + cache_entry.user_data = user_data; + cache_entry.is_opaque = is_opaque; + + RenderTaskCache::alloc_render_task( + &mut render_tasks[render_task_id], + cache_entry, + gpu_cache, + texture_cache, + ); + } + + Ok(entry_handle.weak()) + } + + pub fn get_cache_entry( + &self, + handle: &RenderTaskCacheEntryHandle, + ) -> &RenderTaskCacheEntry { + self.cache_entries + .get_opt(handle) + .expect("bug: invalid render task cache handle") + } + + #[allow(dead_code)] + pub fn get_cache_item_for_render_task(&self, + texture_cache: &TextureCache, + key: &RenderTaskCacheKey) + -> CacheItem { + // Get the texture cache handle for this cache key. + let handle = self.map.get(key).unwrap(); + let cache_entry = self.cache_entries.get(handle); + texture_cache.get(&cache_entry.handle) + } + + #[allow(dead_code)] + pub fn get_allocated_size_for_render_task(&self, + texture_cache: &TextureCache, + key: &RenderTaskCacheKey) + -> Option { + let handle = self.map.get(key).unwrap(); + let cache_entry = self.cache_entries.get(handle); + texture_cache.get_allocated_size(&cache_entry.handle) + } +} + +// TODO(gw): Rounding the content rect here to device pixels is not +// technically correct. Ideally we should ceil() here, and ensure that +// the extra part pixel in the case of fractional sizes is correctly +// handled. For now, just use rounding which passes the existing +// Gecko tests. +// Note: zero-square tasks are prohibited in WR task graph, so +// we ensure each dimension to be at least the length of 1 after rounding. +pub fn to_cache_size(size: DeviceSize) -> DeviceIntSize { + DeviceIntSize::new( + 1.max(size.width.round() as i32), + 1.max(size.height.round() as i32), + ) +} diff --git a/third_party/webrender/webrender/src/render_task_graph.rs b/third_party/webrender/webrender/src/render_task_graph.rs new file mode 100644 index 00000000000..3058a988386 --- /dev/null +++ b/third_party/webrender/webrender/src/render_task_graph.rs @@ -0,0 +1,887 @@ +//! This module contains the render task graph. +//! +//! Code associated with creating specific render tasks is in the render_task +//! module. + +use api::ImageFormat; +use api::units::*; +use crate::internal_types::{CacheTextureId, FastHashMap, SavedTargetIndex}; +use crate::render_backend::FrameId; +use crate::render_target::{RenderTarget, RenderTargetKind, RenderTargetList, ColorRenderTarget}; +use crate::render_target::{PictureCacheTarget, TextureCacheRenderTarget, AlphaRenderTarget}; +use crate::render_task::{BlitSource, RenderTask, RenderTaskKind, RenderTaskAddress, RenderTaskData}; +use crate::render_task::{RenderTaskLocation}; +use crate::util::{VecHelper, Allocation}; +use std::{cmp, usize, f32, i32, u32}; + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct RenderTaskGraph { + pub tasks: Vec, + pub task_data: Vec, + /// Tasks that don't have dependencies, and that may be shared between + /// picture tasks. + /// + /// We render these unconditionally before-rendering the rest of the tree. + pub cacheable_render_tasks: Vec, + next_saved: SavedTargetIndex, + frame_id: FrameId, +} + +/// Allows initializing a render task directly into the render task buffer. +/// +/// See utils::VecHelpers. RenderTask is fairly large so avoiding the move when +/// pushing into the vector can save a lot of exensive memcpys on pages with many +/// render tasks. +pub struct RenderTaskAllocation<'a> { + alloc: Allocation<'a, RenderTask>, + #[cfg(debug_assertions)] + frame_id: FrameId, +} + +impl<'l> RenderTaskAllocation<'l> { + #[inline(always)] + pub fn init(self, value: RenderTask) -> RenderTaskId { + RenderTaskId { + index: self.alloc.init(value) as u32, + #[cfg(debug_assertions)] + frame_id: self.frame_id, + } + } +} + +impl RenderTaskGraph { + pub fn new(frame_id: FrameId, counters: &RenderTaskGraphCounters) -> Self { + // Preallocate a little more than what we needed in the previous frame so that small variations + // in the number of items don't cause us to constantly reallocate. + let extra_items = 8; + RenderTaskGraph { + tasks: Vec::with_capacity(counters.tasks_len + extra_items), + task_data: Vec::with_capacity(counters.task_data_len + extra_items), + cacheable_render_tasks: Vec::with_capacity(counters.cacheable_render_tasks_len + extra_items), + next_saved: SavedTargetIndex(0), + frame_id, + } + } + + pub fn counters(&self) -> RenderTaskGraphCounters { + RenderTaskGraphCounters { + tasks_len: self.tasks.len(), + task_data_len: self.task_data.len(), + cacheable_render_tasks_len: self.cacheable_render_tasks.len(), + } + } + + pub fn add(&mut self) -> RenderTaskAllocation { + RenderTaskAllocation { + alloc: self.tasks.alloc(), + #[cfg(debug_assertions)] + frame_id: self.frame_id, + } + } + + /// Express a render task dependency between a parent and child task. + /// This is used to assign tasks to render passes. + pub fn add_dependency( + &mut self, + parent_id: RenderTaskId, + child_id: RenderTaskId, + ) { + let parent = &mut self[parent_id]; + parent.children.push(child_id); + } + + /// Assign this frame's render tasks to render passes ordered so that passes appear + /// earlier than the ones that depend on them. + pub fn generate_passes( + &mut self, + main_render_task: Option, + screen_size: DeviceIntSize, + gpu_supports_fast_clears: bool, + ) -> Vec { + profile_scope!("generate_passes"); + let mut passes = Vec::new(); + + if !self.cacheable_render_tasks.is_empty() { + self.generate_passes_impl( + &self.cacheable_render_tasks[..], + screen_size, + gpu_supports_fast_clears, + false, + &mut passes, + ); + } + + if let Some(main_task) = main_render_task { + self.generate_passes_impl( + &[main_task], + screen_size, + gpu_supports_fast_clears, + true, + &mut passes, + ); + } + + + self.resolve_target_conflicts(&mut passes); + + passes + } + + /// Assign the render tasks from the tree rooted at root_task to render passes and + /// append them to the `passes` vector so that the passes that we depend on end up + /// _earlier_ in the pass list. + fn generate_passes_impl( + &self, + root_tasks: &[RenderTaskId], + screen_size: DeviceIntSize, + gpu_supports_fast_clears: bool, + for_main_framebuffer: bool, + passes: &mut Vec, + ) { + // We recursively visit tasks from the roots (main and cached render tasks), to figure out + // which ones affect the frame and which passes they should be assigned to. + // + // We track the maximum depth of each task (how far it is from the roots) as well as the total + // maximum depth of the graph to determine each tasks' pass index. In a nutshell, depth 0 is + // for the last render pass (for example the main framebuffer), while the highest depth + // corresponds to the first pass. + + fn assign_task_depth( + tasks: &[RenderTask], + task_id: RenderTaskId, + task_depth: i32, + task_max_depths: &mut [i32], + max_depth: &mut i32, + ) { + *max_depth = std::cmp::max(*max_depth, task_depth); + + let task_max_depth = &mut task_max_depths[task_id.index as usize]; + if task_depth > *task_max_depth { + *task_max_depth = task_depth; + } else { + // If this task has already been processed at a larger depth, + // there is no need to process it again. + return; + } + + let task = &tasks[task_id.index as usize]; + for child in &task.children { + assign_task_depth( + tasks, + *child, + task_depth + 1, + task_max_depths, + max_depth, + ); + } + } + + // The maximum depth of each task. Values that are still equal to -1 after recursively visiting + // the nodes correspond to tasks that don't contribute to the frame. + let mut task_max_depths = vec![-1; self.tasks.len()]; + let mut max_depth = 0; + + for root_task in root_tasks { + assign_task_depth( + &self.tasks, + *root_task, + 0, + &mut task_max_depths, + &mut max_depth, + ); + } + + let offset = passes.len(); + + passes.reserve(max_depth as usize + 1); + for _ in 0..max_depth { + passes.alloc().init(RenderPass::new_off_screen(screen_size, gpu_supports_fast_clears)); + } + + if for_main_framebuffer { + passes.alloc().init(RenderPass::new_main_framebuffer(screen_size, gpu_supports_fast_clears)); + } else { + passes.alloc().init(RenderPass::new_off_screen(screen_size, gpu_supports_fast_clears)); + } + + // Assign tasks to their render passes. + for task_index in 0..self.tasks.len() { + if task_max_depths[task_index] < 0 { + // The task wasn't visited, it means it doesn't contribute to this frame. + continue; + } + let pass_index = offset + (max_depth - task_max_depths[task_index]) as usize; + let task_id = RenderTaskId { + index: task_index as u32, + #[cfg(debug_assertions)] + frame_id: self.frame_id, + }; + let task = &self.tasks[task_index]; + passes[pass_index as usize].add_render_task( + task_id, + task.get_dynamic_size(), + task.target_kind(), + &task.location, + ); + } + } + + /// Resolve conflicts between the generated passes and the limitiations of our target + /// allocation scheme. + /// + /// The render task graph operates with a ping-pong target allocation scheme where + /// a set of targets is written to by even passes and a different set of targets is + /// written to by odd passes. + /// Since tasks cannot read and write the same target, we can run into issues if a + /// task pass in N + 2 reads the result of a task in pass N. + /// To avoid such cases have to insert blit tasks to copy the content of the task + /// into pass N + 1 which is readable by pass N + 2. + /// + /// In addition, allocated rects of pass N are currently not tracked and can be + /// overwritten by allocations in later passes on the same target, unless the task + /// has been marked for saving, which perserves the allocated rect until the end of + /// the frame. This is a big hammer, hopefully we won't need to mark many passes + /// for saving. A better solution would be to track allocations through the entire + /// graph, there is a prototype of that in https://github.com/nical/toy-render-graph/ + fn resolve_target_conflicts(&mut self, passes: &mut [RenderPass]) { + // Keep track of blit tasks we inserted to avoid adding several blits for the same + // task. + let mut task_redirects = vec![None; self.tasks.len()]; + + let mut task_passes = vec![-1; self.tasks.len()]; + for pass_index in 0..passes.len() { + for task in &passes[pass_index].tasks { + task_passes[task.index as usize] = pass_index as i32; + } + } + + for task_index in 0..self.tasks.len() { + if task_passes[task_index] < 0 { + // The task doesn't contribute to this frame. + continue; + } + + let pass_index = task_passes[task_index]; + + // Go through each dependency and check whether they belong + // to a pass that uses the same targets and/or are more than + // one pass behind. + for nth_child in 0..self.tasks[task_index].children.len() { + let child_task_index = self.tasks[task_index].children[nth_child].index as usize; + let child_pass_index = task_passes[child_task_index]; + + if child_pass_index == pass_index - 1 { + // This should be the most common case. + continue; + } + + // TODO: Picture tasks don't support having their dependency tasks redirected. + // Pictures store their respective render task(s) on their SurfaceInfo. + // We cannot blit the picture task here because we would need to update the + // surface's render tasks, but we don't have access to that info here. + // Also a surface may be expecting a picture task and not a blit task, so + // even if we could update the surface's render task(s), it might cause other issues. + // For now we mark the task to be saved rather than trying to redirect to a blit task. + let task_is_picture = if let RenderTaskKind::Picture(..) = self.tasks[task_index].kind { + true + } else { + false + }; + + if child_pass_index % 2 != pass_index % 2 || task_is_picture { + // The tasks and its dependency aren't on the same targets, + // but the dependency needs to be kept alive. + self.tasks[child_task_index].mark_for_saving(); + continue; + } + + if let Some(blit_id) = task_redirects[child_task_index] { + // We already resolved a similar conflict with a blit task, + // reuse the same blit instead of creating a new one. + self.tasks[task_index].children[nth_child] = blit_id; + + // Mark for saving if the blit is more than pass appart from + // our task. + if child_pass_index < pass_index - 2 { + self.tasks[blit_id.index as usize].mark_for_saving(); + } + + continue; + } + + // Our dependency is an even number of passes behind, need + // to insert a blit to ensure we don't read and write from + // the same target. + + let child_task_id = RenderTaskId { + index: child_task_index as u32, + #[cfg(debug_assertions)] + frame_id: self.frame_id, + }; + + let mut blit = RenderTask::new_blit( + self.tasks[child_task_index].location.size(), + BlitSource::RenderTask { task_id: child_task_id }, + ); + + // Mark for saving if the blit is more than pass appart from + // our task. + if child_pass_index < pass_index - 2 { + blit.mark_for_saving(); + } + + let blit_id = RenderTaskId { + index: self.tasks.len() as u32, + #[cfg(debug_assertions)] + frame_id: self.frame_id, + }; + + self.tasks.alloc().init(blit); + + passes[child_pass_index as usize + 1].tasks.push(blit_id); + + self.tasks[task_index].children[nth_child] = blit_id; + task_redirects[child_task_index] = Some(blit_id); + } + } + } + + pub fn get_task_address(&self, id: RenderTaskId) -> RenderTaskAddress { + #[cfg(all(debug_assertions, not(feature = "replay")))] + debug_assert_eq!(self.frame_id, id.frame_id); + RenderTaskAddress(id.index as u16) + } + + pub fn write_task_data(&mut self) { + profile_scope!("write_task_data"); + for task in &self.tasks { + self.task_data.push(task.write_task_data()); + } + } + + pub fn save_target(&mut self) -> SavedTargetIndex { + let id = self.next_saved; + self.next_saved.0 += 1; + id + } + + #[cfg(debug_assertions)] + pub fn frame_id(&self) -> FrameId { + self.frame_id + } +} + +impl std::ops::Index for RenderTaskGraph { + type Output = RenderTask; + fn index(&self, id: RenderTaskId) -> &RenderTask { + #[cfg(all(debug_assertions, not(feature = "replay")))] + debug_assert_eq!(self.frame_id, id.frame_id); + &self.tasks[id.index as usize] + } +} + +impl std::ops::IndexMut for RenderTaskGraph { + fn index_mut(&mut self, id: RenderTaskId) -> &mut RenderTask { + #[cfg(all(debug_assertions, not(feature = "replay")))] + debug_assert_eq!(self.frame_id, id.frame_id); + &mut self.tasks[id.index as usize] + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct RenderTaskId { + pub index: u32, + + #[cfg(debug_assertions)] + #[cfg_attr(feature = "replay", serde(default = "FrameId::first"))] + frame_id: FrameId, +} + +#[derive(Debug)] +pub struct RenderTaskGraphCounters { + tasks_len: usize, + task_data_len: usize, + cacheable_render_tasks_len: usize, +} + +impl RenderTaskGraphCounters { + pub fn new() -> Self { + RenderTaskGraphCounters { + tasks_len: 0, + task_data_len: 0, + cacheable_render_tasks_len: 0, + } + } +} + +impl RenderTaskId { + pub const INVALID: RenderTaskId = RenderTaskId { + index: u32::MAX, + #[cfg(debug_assertions)] + frame_id: FrameId::INVALID, + }; +} + +/// Contains the set of `RenderTarget`s specific to the kind of pass. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum RenderPassKind { + /// The final pass to the main frame buffer, where we have a single color + /// target for display to the user. + MainFramebuffer { + main_target: ColorRenderTarget, + }, + /// An intermediate pass, where we may have multiple targets. + OffScreen { + alpha: RenderTargetList, + color: RenderTargetList, + texture_cache: FastHashMap<(CacheTextureId, usize), TextureCacheRenderTarget>, + picture_cache: Vec, + }, +} + +/// A render pass represents a set of rendering operations that don't depend on one +/// another. +/// +/// A render pass can have several render targets if there wasn't enough space in one +/// target to do all of the rendering for that pass. See `RenderTargetList`. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct RenderPass { + /// The kind of pass, as well as the set of targets associated with that + /// kind of pass. + pub kind: RenderPassKind, + /// The set of tasks to be performed in this pass, as indices into the + /// `RenderTaskGraph`. + pub tasks: Vec, + /// Screen size in device pixels - used for opaque alpha batch break threshold. + pub screen_size: DeviceIntSize, +} + +impl RenderPass { + /// Creates a pass for the main framebuffer. There is only one of these, and + /// it is always the last pass. + pub fn new_main_framebuffer( + screen_size: DeviceIntSize, + gpu_supports_fast_clears: bool, + ) -> Self { + let main_target = ColorRenderTarget::new(screen_size, gpu_supports_fast_clears); + RenderPass { + kind: RenderPassKind::MainFramebuffer { + main_target, + }, + tasks: vec![], + screen_size, + } + } + + /// Creates an intermediate off-screen pass. + pub fn new_off_screen( + screen_size: DeviceIntSize, + gpu_supports_fast_clears: bool, + ) -> Self { + RenderPass { + kind: RenderPassKind::OffScreen { + color: RenderTargetList::new( + screen_size, + ImageFormat::RGBA8, + gpu_supports_fast_clears, + ), + alpha: RenderTargetList::new( + screen_size, + ImageFormat::R8, + gpu_supports_fast_clears, + ), + texture_cache: FastHashMap::default(), + picture_cache: Vec::new(), + }, + tasks: vec![], + screen_size, + } + } + + /// Adds a task to this pass. + pub fn add_render_task( + &mut self, + task_id: RenderTaskId, + size: DeviceIntSize, + target_kind: RenderTargetKind, + location: &RenderTaskLocation, + ) { + if let RenderPassKind::OffScreen { ref mut color, ref mut alpha, .. } = self.kind { + // If this will be rendered to a dynamically-allocated region on an + // off-screen render target, update the max-encountered size. We don't + // need to do this for things drawn to the texture cache, since those + // don't affect our render target allocation. + if location.is_dynamic() { + let max_size = match target_kind { + RenderTargetKind::Color => &mut color.max_dynamic_size, + RenderTargetKind::Alpha => &mut alpha.max_dynamic_size, + }; + max_size.width = cmp::max(max_size.width, size.width); + max_size.height = cmp::max(max_size.height, size.height); + } + } + + self.tasks.push(task_id); + } +} + +// Dump an SVG visualization of the render graph for debugging purposes +#[allow(dead_code)] +pub fn dump_render_tasks_as_svg( + render_tasks: &RenderTaskGraph, + passes: &[RenderPass], + output: &mut dyn std::io::Write, +) -> std::io::Result<()> { + use svg_fmt::*; + + let node_width = 80.0; + let node_height = 30.0; + let vertical_spacing = 8.0; + let horizontal_spacing = 20.0; + let margin = 10.0; + let text_size = 10.0; + + let mut pass_rects = Vec::new(); + let mut nodes = vec![None; render_tasks.tasks.len()]; + + let mut x = margin; + let mut max_y: f32 = 0.0; + + #[derive(Clone)] + struct Node { + rect: Rectangle, + label: Text, + size: Text, + } + + for pass in passes { + let mut layout = VerticalLayout::new(x, margin, node_width); + + for task_id in &pass.tasks { + let task_index = task_id.index as usize; + let task = &render_tasks.tasks[task_index]; + + let rect = layout.push_rectangle(node_height); + + let tx = rect.x + rect.w / 2.0; + let ty = rect.y + 10.0; + + let saved = if task.saved_index.is_some() { " (Saved)" } else { "" }; + let label = text(tx, ty, format!("{}{}", task.kind.as_str(), saved)); + let size = text(tx, ty + 12.0, format!("{:?}", task.location.size())); + + nodes[task_index] = Some(Node { rect, label, size }); + + layout.advance(vertical_spacing); + } + + pass_rects.push(layout.total_rectangle()); + + x += node_width + horizontal_spacing; + max_y = max_y.max(layout.y + margin); + } + + let mut links = Vec::new(); + for node_index in 0..nodes.len() { + if nodes[node_index].is_none() { + continue; + } + + let task = &render_tasks.tasks[node_index]; + for dep in &task.children { + let dep_index = dep.index as usize; + + if let (&Some(ref node), &Some(ref dep_node)) = (&nodes[node_index], &nodes[dep_index]) { + links.push(( + dep_node.rect.x + dep_node.rect.w, + dep_node.rect.y + dep_node.rect.h / 2.0, + node.rect.x, + node.rect.y + node.rect.h / 2.0, + )); + } + } + } + + let svg_w = x + margin; + let svg_h = max_y + margin; + writeln!(output, "{}", BeginSvg { w: svg_w, h: svg_h })?; + + // Background. + writeln!(output, + " {}", + rectangle(0.0, 0.0, svg_w, svg_h) + .inflate(1.0, 1.0) + .fill(rgb(50, 50, 50)) + )?; + + // Passes. + for rect in pass_rects { + writeln!(output, + " {}", + rect.inflate(3.0, 3.0) + .border_radius(4.0) + .opacity(0.4) + .fill(black()) + )?; + } + + // Links. + for (x1, y1, x2, y2) in links { + dump_task_dependency_link(output, x1, y1, x2, y2); + } + + // Tasks. + for node in &nodes { + if let Some(node) = node { + writeln!(output, + " {}", + node.rect + .clone() + .fill(black()) + .border_radius(3.0) + .opacity(0.5) + .offset(0.0, 2.0) + )?; + writeln!(output, + " {}", + node.rect + .clone() + .fill(rgb(200, 200, 200)) + .border_radius(3.0) + .opacity(0.8) + )?; + + writeln!(output, + " {}", + node.label + .clone() + .size(text_size) + .align(Align::Center) + .color(rgb(50, 50, 50)) + )?; + writeln!(output, + " {}", + node.size + .clone() + .size(text_size * 0.7) + .align(Align::Center) + .color(rgb(50, 50, 50)) + )?; + } + } + + writeln!(output, "{}", EndSvg) +} + +#[allow(dead_code)] +fn dump_task_dependency_link( + output: &mut dyn std::io::Write, + x1: f32, y1: f32, + x2: f32, y2: f32, +) { + use svg_fmt::*; + + // If the link is a straight horizontal line and spans over multiple passes, it + // is likely to go straight though unrelated nodes in a way that makes it look like + // they are connected, so we bend the line upward a bit to avoid that. + let simple_path = (y1 - y2).abs() > 1.0 || (x2 - x1) < 45.0; + + let mid_x = (x1 + x2) / 2.0; + if simple_path { + write!(output, " {}", + path().move_to(x1, y1) + .cubic_bezier_to(mid_x, y1, mid_x, y2, x2, y2) + .fill(Fill::None) + .stroke(Stroke::Color(rgb(100, 100, 100), 3.0)) + ).unwrap(); + } else { + let ctrl1_x = (mid_x + x1) / 2.0; + let ctrl2_x = (mid_x + x2) / 2.0; + let ctrl_y = y1 - 25.0; + write!(output, " {}", + path().move_to(x1, y1) + .cubic_bezier_to(ctrl1_x, y1, ctrl1_x, ctrl_y, mid_x, ctrl_y) + .cubic_bezier_to(ctrl2_x, ctrl_y, ctrl2_x, y2, x2, y2) + .fill(Fill::None) + .stroke(Stroke::Color(rgb(100, 100, 100), 3.0)) + ).unwrap(); + } +} + +#[cfg(test)] +use euclid::{size2, rect}; +#[cfg(test)] +use smallvec::SmallVec; + +#[cfg(test)] +fn dyn_location(w: i32, h: i32) -> RenderTaskLocation { + RenderTaskLocation::Dynamic(None, size2(w, h)) +} + +#[test] +fn diamond_task_graph() { + // A simple diamon shaped task graph. + // + // [b1] + // / \ + // [a] [main_pic] + // \ / + // [b2] + + let color = RenderTargetKind::Color; + + let counters = RenderTaskGraphCounters::new(); + let mut tasks = RenderTaskGraph::new(FrameId::first(), &counters); + + let a = tasks.add().init(RenderTask::new_test(color, dyn_location(640, 640), SmallVec::new())); + let b1 = tasks.add().init(RenderTask::new_test(color, dyn_location(320, 320), smallvec![a])); + let b2 = tasks.add().init(RenderTask::new_test(color, dyn_location(320, 320), smallvec![a])); + + let main_pic = tasks.add().init(RenderTask::new_test( + color, + RenderTaskLocation::Fixed(rect(0, 0, 3200, 1800)), + smallvec![b1, b2], + )); + + let initial_number_of_tasks = tasks.tasks.len(); + + let passes = tasks.generate_passes(Some(main_pic), size2(3200, 1800), true); + + // We should not have added any blits. + assert_eq!(tasks.tasks.len(), initial_number_of_tasks); + + assert_eq!(passes.len(), 3); + assert_eq!(passes[0].tasks, vec![a]); + + assert_eq!(passes[1].tasks.len(), 2); + assert!(passes[1].tasks.contains(&b1)); + assert!(passes[1].tasks.contains(&b2)); + + assert_eq!(passes[2].tasks, vec![main_pic]); +} + +#[test] +fn blur_task_graph() { + // This test simulates a complicated shadow stack effect with target allocation + // conflicts to resolve. + + let color = RenderTargetKind::Color; + + let counters = RenderTaskGraphCounters::new(); + let mut tasks = RenderTaskGraph::new(FrameId::first(), &counters); + + let pic = tasks.add().init(RenderTask::new_test(color, dyn_location(640, 640), SmallVec::new())); + let scale1 = tasks.add().init(RenderTask::new_test(color, dyn_location(320, 320), smallvec![pic])); + let scale2 = tasks.add().init(RenderTask::new_test(color, dyn_location(160, 160), smallvec![scale1])); + let scale3 = tasks.add().init(RenderTask::new_test(color, dyn_location(80, 80), smallvec![scale2])); + let scale4 = tasks.add().init(RenderTask::new_test(color, dyn_location(40, 40), smallvec![scale3])); + + let vblur1 = tasks.add().init(RenderTask::new_test(color, dyn_location(40, 40), smallvec![scale4])); + let hblur1 = tasks.add().init(RenderTask::new_test(color, dyn_location(40, 40), smallvec![vblur1])); + + let vblur2 = tasks.add().init(RenderTask::new_test(color, dyn_location(40, 40), smallvec![scale4])); + let hblur2 = tasks.add().init(RenderTask::new_test(color, dyn_location(40, 40), smallvec![vblur2])); + + // Insert a task that is an even number of passes away from its dependency. + // This means the source and destination are on the same target and we have to resolve + // this conflict by automatically inserting a blit task. + let vblur3 = tasks.add().init(RenderTask::new_test(color, dyn_location(80, 80), smallvec![scale3])); + let hblur3 = tasks.add().init(RenderTask::new_test(color, dyn_location(80, 80), smallvec![vblur3])); + + // Insert a task that is an odd number > 1 of passes away from its dependency. + // This should force us to mark the dependency "for saving" to keep its content valid + // until the task can access it. + let vblur4 = tasks.add().init(RenderTask::new_test(color, dyn_location(160, 160), smallvec![scale2])); + let hblur4 = tasks.add().init(RenderTask::new_test(color, dyn_location(160, 160), smallvec![vblur4])); + + let main_pic = tasks.add().init(RenderTask::new_test( + color, + RenderTaskLocation::Fixed(rect(0, 0, 3200, 1800)), + smallvec![hblur1, hblur2, hblur3, hblur4], + )); + + let initial_number_of_tasks = tasks.tasks.len(); + + let passes = tasks.generate_passes(Some(main_pic), size2(3200, 1800), true); + + // We should have added a single blit task. + assert_eq!(tasks.tasks.len(), initial_number_of_tasks + 1); + + // vblur3's dependency to scale3 should be replaced by a blit. + let blit = tasks[vblur3].children[0]; + assert!(blit != scale3); + + match tasks[blit].kind { + RenderTaskKind::Blit(..) => {} + _ => { panic!("This should be a blit task."); } + } + + assert_eq!(passes.len(), 8); + + assert_eq!(passes[0].tasks, vec![pic]); + assert_eq!(passes[1].tasks, vec![scale1]); + assert_eq!(passes[2].tasks, vec![scale2]); + assert_eq!(passes[3].tasks, vec![scale3]); + + assert_eq!(passes[4].tasks.len(), 2); + assert!(passes[4].tasks.contains(&scale4)); + assert!(passes[4].tasks.contains(&blit)); + + assert_eq!(passes[5].tasks.len(), 4); + assert!(passes[5].tasks.contains(&vblur1)); + assert!(passes[5].tasks.contains(&vblur2)); + assert!(passes[5].tasks.contains(&vblur3)); + assert!(passes[5].tasks.contains(&vblur4)); + + assert_eq!(passes[6].tasks.len(), 4); + assert!(passes[6].tasks.contains(&hblur1)); + assert!(passes[6].tasks.contains(&hblur2)); + assert!(passes[6].tasks.contains(&hblur3)); + assert!(passes[6].tasks.contains(&hblur4)); + + assert_eq!(passes[7].tasks, vec![main_pic]); + + // See vblur4's comment above. + assert!(tasks[scale2].saved_index.is_some()); +} + +#[test] +fn culled_tasks() { + // This test checks that tasks that do not contribute to the frame don't appear in the + // generated passes. + + let color = RenderTargetKind::Color; + + let counters = RenderTaskGraphCounters::new(); + let mut tasks = RenderTaskGraph::new(FrameId::first(), &counters); + + let a1 = tasks.add().init(RenderTask::new_test(color, dyn_location(640, 640), SmallVec::new())); + let _a2 = tasks.add().init(RenderTask::new_test(color, dyn_location(320, 320), smallvec![a1])); + + let b1 = tasks.add().init(RenderTask::new_test(color, dyn_location(640, 640), SmallVec::new())); + let b2 = tasks.add().init(RenderTask::new_test(color, dyn_location(320, 320), smallvec![b1])); + let _b3 = tasks.add().init(RenderTask::new_test(color, dyn_location(320, 320), smallvec![b2])); + + let main_pic = tasks.add().init(RenderTask::new_test( + color, + RenderTaskLocation::Fixed(rect(0, 0, 3200, 1800)), + smallvec![b2], + )); + + let initial_number_of_tasks = tasks.tasks.len(); + + let passes = tasks.generate_passes(Some(main_pic), size2(3200, 1800), true); + + // We should not have added any blits. + assert_eq!(tasks.tasks.len(), initial_number_of_tasks); + + assert_eq!(passes.len(), 3); + assert_eq!(passes[0].tasks, vec![b1]); + assert_eq!(passes[1].tasks, vec![b2]); + assert_eq!(passes[2].tasks, vec![main_pic]); +} diff --git a/third_party/webrender/webrender/src/renderer.rs b/third_party/webrender/webrender/src/renderer.rs new file mode 100644 index 00000000000..3552a304f76 --- /dev/null +++ b/third_party/webrender/webrender/src/renderer.rs @@ -0,0 +1,7671 @@ +/* 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/. */ + +//! The high-level module responsible for interfacing with the GPU. +//! +//! Much of WebRender's design is driven by separating work into different +//! threads. To avoid the complexities of multi-threaded GPU access, we restrict +//! all communication with the GPU to one thread, the render thread. But since +//! issuing GPU commands is often a bottleneck, we move everything else (i.e. +//! the computation of what commands to issue) to another thread, the +//! RenderBackend thread. The RenderBackend, in turn, may delegate work to other +//! thread (like the SceneBuilder threads or Rayon workers), but the +//! Render-vs-RenderBackend distinction is the most important. +//! +//! The consumer is responsible for initializing the render thread before +//! calling into WebRender, which means that this module also serves as the +//! initial entry point into WebRender, and is responsible for spawning the +//! various other threads discussed above. That said, WebRender initialization +//! returns both the `Renderer` instance as well as a channel for communicating +//! directly with the `RenderBackend`. Aside from a few high-level operations +//! like 'render now', most of interesting commands from the consumer go over +//! that channel and operate on the `RenderBackend`. +//! +//! ## Space conversion guidelines +//! At this stage, we shuld be operating with `DevicePixel` and `FramebufferPixel` only. +//! "Framebuffer" space represents the final destination of our rendeing, +//! and it happens to be Y-flipped on OpenGL. The conversion is done as follows: +//! - for rasterized primitives, the orthographics projection transforms +//! the content rectangle to -1 to 1 +//! - the viewport transformation is setup to map the whole range to +//! the framebuffer rectangle provided by the document view, stored in `DrawTarget` +//! - all the direct framebuffer operations, like blitting, reading pixels, and setting +//! up the scissor, are accepting already transformed coordinates, which we can get by +//! calling `DrawTarget::to_framebuffer_rect` + +use api::{ApiMsg, BlobImageHandler, ColorF, ColorU, MixBlendMode}; +use api::{DocumentId, Epoch, ExternalImageHandler, ExternalImageId}; +use api::{ExternalImageSource, ExternalImageType, FontRenderMode, FrameMsg, ImageFormat}; +use api::{PipelineId, ImageRendering, Checkpoint, NotificationRequest, OutputImageHandler}; +use api::{DebugCommand, MemoryReport, VoidPtrToSizeFn, PremultipliedColorF}; +use api::{RenderApiSender, RenderNotifier, TextureTarget, SharedFontInstanceMap}; +#[cfg(feature = "replay")] +use api::ExternalImage; +use api::units::*; +pub use api::DebugFlags; +use crate::batch::{AlphaBatchContainer, BatchKind, BatchFeatures, BatchTextures, BrushBatchKind, ClipBatchList}; +#[cfg(any(feature = "capture", feature = "replay"))] +use crate::capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage}; +use crate::composite::{CompositeState, CompositeTileSurface, CompositeTile, ResolvedExternalSurface}; +use crate::composite::{CompositorKind, Compositor, NativeTileId, CompositeSurfaceFormat, ResolvedExternalSurfaceColorData}; +use crate::composite::{CompositorConfig, NativeSurfaceOperationDetails, NativeSurfaceId, NativeSurfaceOperation}; +use crate::debug_colors; +use crate::debug_render::{DebugItem, DebugRenderer}; +use crate::device::{DepthFunction, Device, GpuFrameId, Program, UploadMethod, Texture, PBO}; +use crate::device::{DrawTarget, ExternalTexture, FBOId, ReadTarget, TextureSlot}; +use crate::device::{ShaderError, TextureFilter, TextureFlags, + VertexUsageHint, VAO, VBO, CustomVAO}; +use crate::device::ProgramCache; +use crate::device::query::GpuTimer; +use euclid::{rect, Transform3D, Scale, default}; +use crate::frame_builder::{Frame, ChasePrimitive, FrameBuilderConfig}; +use gleam::gl; +use crate::glyph_cache::GlyphCache; +use crate::glyph_rasterizer::{GlyphFormat, GlyphRasterizer}; +use crate::gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList}; +use crate::gpu_cache::{GpuCacheDebugChunk, GpuCacheDebugCmd}; +use crate::gpu_types::{PrimitiveHeaderI, PrimitiveHeaderF, ScalingInstance, SvgFilterInstance, TransformData}; +use crate::gpu_types::{ClearInstance, CompositeInstance, ResolveInstanceData, ZBufferId}; +use crate::internal_types::{TextureSource, ResourceCacheError}; +use crate::internal_types::{CacheTextureId, DebugOutput, FastHashMap, FastHashSet, LayerIndex, RenderedDocument, ResultMsg}; +use crate::internal_types::{TextureCacheAllocationKind, TextureCacheUpdate, TextureUpdateList, TextureUpdateSource}; +use crate::internal_types::{RenderTargetInfo, SavedTargetIndex, Swizzle}; +use malloc_size_of::MallocSizeOfOps; +use crate::picture::{RecordedDirtyRegion, tile_cache_sizes, ResolvedSurfaceTexture}; +use crate::prim_store::DeferredResolve; +use crate::profiler::{BackendProfileCounters, FrameProfileCounters, TimeProfileCounter, + GpuProfileTag, RendererProfileCounters, RendererProfileTimers}; +use crate::profiler::{Profiler, ChangeIndicator, ProfileStyle, add_event_marker, thread_is_being_profiled}; +use crate::device::query::{GpuProfiler, GpuDebugMethod}; +use rayon::{ThreadPool, ThreadPoolBuilder}; +use crate::render_backend::{FrameId, RenderBackend}; +use crate::render_task_graph::RenderTaskGraph; +use crate::render_task::{RenderTask, RenderTaskData, RenderTaskKind}; +use crate::resource_cache::ResourceCache; +use crate::scene_builder_thread::{SceneBuilderThread, SceneBuilderThreadChannels, LowPrioritySceneBuilderThread}; +use crate::screen_capture::AsyncScreenshotGrabber; +use crate::shade::{Shaders, WrShaders}; +use smallvec::SmallVec; +use crate::texture_cache::TextureCache; +use crate::render_target::{AlphaRenderTarget, ColorRenderTarget, PictureCacheTarget}; +use crate::render_target::{RenderTarget, TextureCacheRenderTarget, RenderTargetList}; +use crate::render_target::{RenderTargetKind, BlitJob, BlitJobSource}; +use crate::render_task_graph::RenderPassKind; +use crate::util::drain_filter; +use crate::c_str; + +use std; +use std::cmp; +use std::collections::VecDeque; +use std::collections::hash_map::Entry; +use std::f32; +use std::marker::PhantomData; +use std::mem; +use std::os::raw::c_void; +use std::path::PathBuf; +use std::rc::Rc; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc::{channel, Sender, Receiver}; +use std::thread; +use std::cell::RefCell; +use tracy_rs::register_thread_with_profiler; +use time::precise_time_ns; +use std::ffi::CString; + +cfg_if! { + if #[cfg(feature = "debugger")] { + use serde_json; + use crate::debug_server; + } +} + +const DEFAULT_BATCH_LOOKBACK_COUNT: usize = 10; +const VERTEX_TEXTURE_EXTRA_ROWS: i32 = 10; + +/// The size of the array of each type of vertex data texture that +/// is round-robin-ed each frame during bind_frame_data. Doing this +/// helps avoid driver stalls while updating the texture in some +/// drivers. The size of these textures are typically very small +/// (e.g. < 16 kB) so it's not a huge waste of memory. Despite that, +/// this is a short-term solution - we want to find a better way +/// to provide this frame data, which will likely involve some +/// combination of UBO/SSBO usage. Although this only affects some +/// platforms, it's enabled on all platforms to reduce testing +/// differences between platforms. +const VERTEX_DATA_TEXTURE_COUNT: usize = 3; + +/// Is only false if no WR instances have ever been created. +static HAS_BEEN_INITIALIZED: AtomicBool = AtomicBool::new(false); + +/// Returns true if a WR instance has ever been initialized in this process. +pub fn wr_has_been_initialized() -> bool { + HAS_BEEN_INITIALIZED.load(Ordering::SeqCst) +} + +pub const MAX_VERTEX_TEXTURE_WIDTH: usize = webrender_build::MAX_VERTEX_TEXTURE_WIDTH; +/// Enabling this toggle would force the GPU cache scattered texture to +/// be resized every frame, which enables GPU debuggers to see if this +/// is performed correctly. +const GPU_CACHE_RESIZE_TEST: bool = false; + +/// Number of GPU blocks per UV rectangle provided for an image. +pub const BLOCKS_PER_UV_RECT: usize = 2; + +const GPU_TAG_BRUSH_OPACITY: GpuProfileTag = GpuProfileTag { + label: "B_Opacity", + color: debug_colors::DARKMAGENTA, +}; +const GPU_TAG_BRUSH_LINEAR_GRADIENT: GpuProfileTag = GpuProfileTag { + label: "B_LinearGradient", + color: debug_colors::POWDERBLUE, +}; +const GPU_TAG_BRUSH_RADIAL_GRADIENT: GpuProfileTag = GpuProfileTag { + label: "B_RadialGradient", + color: debug_colors::LIGHTPINK, +}; +const GPU_TAG_BRUSH_CONIC_GRADIENT: GpuProfileTag = GpuProfileTag { + label: "B_ConicGradient", + color: debug_colors::GREEN, +}; +const GPU_TAG_BRUSH_YUV_IMAGE: GpuProfileTag = GpuProfileTag { + label: "B_YuvImage", + color: debug_colors::DARKGREEN, +}; +const GPU_TAG_BRUSH_MIXBLEND: GpuProfileTag = GpuProfileTag { + label: "B_MixBlend", + color: debug_colors::MAGENTA, +}; +const GPU_TAG_BRUSH_BLEND: GpuProfileTag = GpuProfileTag { + label: "B_Blend", + color: debug_colors::ORANGE, +}; +const GPU_TAG_BRUSH_IMAGE: GpuProfileTag = GpuProfileTag { + label: "B_Image", + color: debug_colors::SPRINGGREEN, +}; +const GPU_TAG_BRUSH_SOLID: GpuProfileTag = GpuProfileTag { + label: "B_Solid", + color: debug_colors::RED, +}; +const GPU_TAG_CACHE_CLIP: GpuProfileTag = GpuProfileTag { + label: "C_Clip", + color: debug_colors::PURPLE, +}; +const GPU_TAG_CACHE_BORDER: GpuProfileTag = GpuProfileTag { + label: "C_Border", + color: debug_colors::CORNSILK, +}; +const GPU_TAG_CACHE_LINE_DECORATION: GpuProfileTag = GpuProfileTag { + label: "C_LineDecoration", + color: debug_colors::YELLOWGREEN, +}; +const GPU_TAG_CACHE_GRADIENT: GpuProfileTag = GpuProfileTag { + label: "C_Gradient", + color: debug_colors::BROWN, +}; +const GPU_TAG_SETUP_TARGET: GpuProfileTag = GpuProfileTag { + label: "target init", + color: debug_colors::SLATEGREY, +}; +const GPU_TAG_SETUP_DATA: GpuProfileTag = GpuProfileTag { + label: "data init", + color: debug_colors::LIGHTGREY, +}; +const GPU_TAG_PRIM_SPLIT_COMPOSITE: GpuProfileTag = GpuProfileTag { + label: "SplitComposite", + color: debug_colors::DARKBLUE, +}; +const GPU_TAG_PRIM_TEXT_RUN: GpuProfileTag = GpuProfileTag { + label: "TextRun", + color: debug_colors::BLUE, +}; +const GPU_TAG_BLUR: GpuProfileTag = GpuProfileTag { + label: "Blur", + color: debug_colors::VIOLET, +}; +const GPU_TAG_BLIT: GpuProfileTag = GpuProfileTag { + label: "Blit", + color: debug_colors::LIME, +}; +const GPU_TAG_SCALE: GpuProfileTag = GpuProfileTag { + label: "Scale", + color: debug_colors::GHOSTWHITE, +}; +const GPU_SAMPLER_TAG_ALPHA: GpuProfileTag = GpuProfileTag { + label: "Alpha Targets", + color: debug_colors::BLACK, +}; +const GPU_SAMPLER_TAG_OPAQUE: GpuProfileTag = GpuProfileTag { + label: "Opaque Pass", + color: debug_colors::BLACK, +}; +const GPU_SAMPLER_TAG_TRANSPARENT: GpuProfileTag = GpuProfileTag { + label: "Transparent Pass", + color: debug_colors::BLACK, +}; +const GPU_TAG_SVG_FILTER: GpuProfileTag = GpuProfileTag { + label: "SvgFilter", + color: debug_colors::LEMONCHIFFON, +}; +const GPU_TAG_COMPOSITE: GpuProfileTag = GpuProfileTag { + label: "Composite", + color: debug_colors::TOMATO, +}; +const GPU_TAG_CLEAR: GpuProfileTag = GpuProfileTag { + label: "Clear", + color: debug_colors::CHOCOLATE, +}; + +/// The clear color used for the texture cache when the debug display is enabled. +/// We use a shade of blue so that we can still identify completely blue items in +/// the texture cache. +const TEXTURE_CACHE_DBG_CLEAR_COLOR: [f32; 4] = [0.0, 0.0, 0.8, 1.0]; + +impl BatchKind { + #[cfg(feature = "debugger")] + fn debug_name(&self) -> &'static str { + match *self { + BatchKind::SplitComposite => "SplitComposite", + BatchKind::Brush(kind) => { + match kind { + BrushBatchKind::Solid => "Brush (Solid)", + BrushBatchKind::Image(..) => "Brush (Image)", + BrushBatchKind::Blend => "Brush (Blend)", + BrushBatchKind::MixBlend { .. } => "Brush (Composite)", + BrushBatchKind::YuvImage(..) => "Brush (YuvImage)", + BrushBatchKind::ConicGradient => "Brush (ConicGradient)", + BrushBatchKind::RadialGradient => "Brush (RadialGradient)", + BrushBatchKind::LinearGradient => "Brush (LinearGradient)", + BrushBatchKind::Opacity => "Brush (Opacity)", + } + } + BatchKind::TextRun(_) => "TextRun", + } + } + + fn sampler_tag(&self) -> GpuProfileTag { + match *self { + BatchKind::SplitComposite => GPU_TAG_PRIM_SPLIT_COMPOSITE, + BatchKind::Brush(kind) => { + match kind { + BrushBatchKind::Solid => GPU_TAG_BRUSH_SOLID, + BrushBatchKind::Image(..) => GPU_TAG_BRUSH_IMAGE, + BrushBatchKind::Blend => GPU_TAG_BRUSH_BLEND, + BrushBatchKind::MixBlend { .. } => GPU_TAG_BRUSH_MIXBLEND, + BrushBatchKind::YuvImage(..) => GPU_TAG_BRUSH_YUV_IMAGE, + BrushBatchKind::ConicGradient => GPU_TAG_BRUSH_CONIC_GRADIENT, + BrushBatchKind::RadialGradient => GPU_TAG_BRUSH_RADIAL_GRADIENT, + BrushBatchKind::LinearGradient => GPU_TAG_BRUSH_LINEAR_GRADIENT, + BrushBatchKind::Opacity => GPU_TAG_BRUSH_OPACITY, + } + } + BatchKind::TextRun(_) => GPU_TAG_PRIM_TEXT_RUN, + } + } +} + +fn flag_changed(before: DebugFlags, after: DebugFlags, select: DebugFlags) -> Option { + if before & select != after & select { + Some(after.contains(select)) + } else { + None + } +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub enum ShaderColorMode { + FromRenderPassMode = 0, + Alpha = 1, + SubpixelConstantTextColor = 2, + SubpixelWithBgColorPass0 = 3, + SubpixelWithBgColorPass1 = 4, + SubpixelWithBgColorPass2 = 5, + SubpixelDualSource = 6, + Bitmap = 7, + ColorBitmap = 8, + Image = 9, +} + +impl From for ShaderColorMode { + fn from(format: GlyphFormat) -> ShaderColorMode { + match format { + GlyphFormat::Alpha | GlyphFormat::TransformedAlpha => ShaderColorMode::Alpha, + GlyphFormat::Subpixel | GlyphFormat::TransformedSubpixel => { + panic!("Subpixel glyph formats must be handled separately."); + } + GlyphFormat::Bitmap => ShaderColorMode::Bitmap, + GlyphFormat::ColorBitmap => ShaderColorMode::ColorBitmap, + } + } +} + +/// Enumeration of the texture samplers used across the various WebRender shaders. +/// +/// Each variant corresponds to a uniform declared in shader source. We only bind +/// the variants we need for a given shader, so not every variant is bound for every +/// batch. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(crate) enum TextureSampler { + Color0, + Color1, + Color2, + PrevPassAlpha, + PrevPassColor, + GpuCache, + TransformPalette, + RenderTasks, + Dither, + PrimitiveHeadersF, + PrimitiveHeadersI, +} + +impl TextureSampler { + pub(crate) fn color(n: usize) -> TextureSampler { + match n { + 0 => TextureSampler::Color0, + 1 => TextureSampler::Color1, + 2 => TextureSampler::Color2, + _ => { + panic!("There are only 3 color samplers."); + } + } + } +} + +impl Into for TextureSampler { + fn into(self) -> TextureSlot { + match self { + TextureSampler::Color0 => TextureSlot(0), + TextureSampler::Color1 => TextureSlot(1), + TextureSampler::Color2 => TextureSlot(2), + TextureSampler::PrevPassAlpha => TextureSlot(3), + TextureSampler::PrevPassColor => TextureSlot(4), + TextureSampler::GpuCache => TextureSlot(5), + TextureSampler::TransformPalette => TextureSlot(6), + TextureSampler::RenderTasks => TextureSlot(7), + TextureSampler::Dither => TextureSlot(8), + TextureSampler::PrimitiveHeadersF => TextureSlot(9), + TextureSampler::PrimitiveHeadersI => TextureSlot(10), + } + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct PackedVertex { + pub pos: [f32; 2], +} + +pub(crate) mod desc { + use crate::device::{VertexAttribute, VertexAttributeKind, VertexDescriptor}; + + pub const PRIM_INSTANCES: VertexDescriptor = VertexDescriptor { + vertex_attributes: &[ + VertexAttribute { + name: "aPosition", + count: 2, + kind: VertexAttributeKind::F32, + }, + ], + instance_attributes: &[ + VertexAttribute { + name: "aData", + count: 4, + kind: VertexAttributeKind::I32, + }, + ], + }; + + pub const BLUR: VertexDescriptor = VertexDescriptor { + vertex_attributes: &[ + VertexAttribute { + name: "aPosition", + count: 2, + kind: VertexAttributeKind::F32, + }, + ], + instance_attributes: &[ + VertexAttribute { + name: "aBlurRenderTaskAddress", + count: 1, + kind: VertexAttributeKind::U16, + }, + VertexAttribute { + name: "aBlurSourceTaskAddress", + count: 1, + kind: VertexAttributeKind::U16, + }, + VertexAttribute { + name: "aBlurDirection", + count: 1, + kind: VertexAttributeKind::I32, + }, + ], + }; + + pub const LINE: VertexDescriptor = VertexDescriptor { + vertex_attributes: &[ + VertexAttribute { + name: "aPosition", + count: 2, + kind: VertexAttributeKind::F32, + }, + ], + instance_attributes: &[ + VertexAttribute { + name: "aTaskRect", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aLocalSize", + count: 2, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aWavyLineThickness", + count: 1, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aStyle", + count: 1, + kind: VertexAttributeKind::I32, + }, + VertexAttribute { + name: "aAxisSelect", + count: 1, + kind: VertexAttributeKind::F32, + }, + ], + }; + + pub const GRADIENT: VertexDescriptor = VertexDescriptor { + vertex_attributes: &[ + VertexAttribute { + name: "aPosition", + count: 2, + kind: VertexAttributeKind::F32, + }, + ], + instance_attributes: &[ + VertexAttribute { + name: "aTaskRect", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aStops", + count: 4, + kind: VertexAttributeKind::F32, + }, + // TODO(gw): We should probably pack these as u32 colors instead + // of passing as full float vec4 here. It won't make much + // difference in real world, since these are only invoked + // rarely, when creating the cache. + VertexAttribute { + name: "aColor0", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aColor1", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aColor2", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aColor3", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aAxisSelect", + count: 1, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aStartStop", + count: 2, + kind: VertexAttributeKind::F32, + }, + ], + }; + + pub const BORDER: VertexDescriptor = VertexDescriptor { + vertex_attributes: &[ + VertexAttribute { + name: "aPosition", + count: 2, + kind: VertexAttributeKind::F32, + }, + ], + instance_attributes: &[ + VertexAttribute { + name: "aTaskOrigin", + count: 2, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aRect", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aColor0", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aColor1", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aFlags", + count: 1, + kind: VertexAttributeKind::I32, + }, + VertexAttribute { + name: "aWidths", + count: 2, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aRadii", + count: 2, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aClipParams1", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aClipParams2", + count: 4, + kind: VertexAttributeKind::F32, + }, + ], + }; + + pub const SCALE: VertexDescriptor = VertexDescriptor { + vertex_attributes: &[ + VertexAttribute { + name: "aPosition", + count: 2, + kind: VertexAttributeKind::F32, + }, + ], + instance_attributes: &[ + VertexAttribute { + name: "aScaleTargetRect", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aScaleSourceRect", + count: 4, + kind: VertexAttributeKind::I32, + }, + VertexAttribute { + name: "aScaleSourceLayer", + count: 1, + kind: VertexAttributeKind::I32, + }, + ], + }; + + pub const CLIP: VertexDescriptor = VertexDescriptor { + vertex_attributes: &[ + VertexAttribute { + name: "aPosition", + count: 2, + kind: VertexAttributeKind::F32, + }, + ], + instance_attributes: &[ + VertexAttribute { + name: "aTransformIds", + count: 2, + kind: VertexAttributeKind::I32, + }, + VertexAttribute { + name: "aClipDataResourceAddress", + count: 4, + kind: VertexAttributeKind::U16, + }, + VertexAttribute { + name: "aClipLocalPos", + count: 2, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aClipTileRect", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aClipDeviceArea", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aClipOrigins", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aDevicePixelScale", + count: 1, + kind: VertexAttributeKind::F32, + }, + ], + }; + + pub const GPU_CACHE_UPDATE: VertexDescriptor = VertexDescriptor { + vertex_attributes: &[ + VertexAttribute { + name: "aPosition", + count: 2, + kind: VertexAttributeKind::U16Norm, + }, + VertexAttribute { + name: "aValue", + count: 4, + kind: VertexAttributeKind::F32, + }, + ], + instance_attributes: &[], + }; + + pub const RESOLVE: VertexDescriptor = VertexDescriptor { + vertex_attributes: &[ + VertexAttribute { + name: "aPosition", + count: 2, + kind: VertexAttributeKind::F32, + }, + ], + instance_attributes: &[ + VertexAttribute { + name: "aRect", + count: 4, + kind: VertexAttributeKind::F32, + }, + ], + }; + + pub const SVG_FILTER: VertexDescriptor = VertexDescriptor { + vertex_attributes: &[ + VertexAttribute { + name: "aPosition", + count: 2, + kind: VertexAttributeKind::F32, + }, + ], + instance_attributes: &[ + VertexAttribute { + name: "aFilterRenderTaskAddress", + count: 1, + kind: VertexAttributeKind::U16, + }, + VertexAttribute { + name: "aFilterInput1TaskAddress", + count: 1, + kind: VertexAttributeKind::U16, + }, + VertexAttribute { + name: "aFilterInput2TaskAddress", + count: 1, + kind: VertexAttributeKind::U16, + }, + VertexAttribute { + name: "aFilterKind", + count: 1, + kind: VertexAttributeKind::U16, + }, + VertexAttribute { + name: "aFilterInputCount", + count: 1, + kind: VertexAttributeKind::U16, + }, + VertexAttribute { + name: "aFilterGenericInt", + count: 1, + kind: VertexAttributeKind::U16, + }, + VertexAttribute { + name: "aFilterExtraDataAddress", + count: 2, + kind: VertexAttributeKind::U16, + }, + ], + }; + + pub const VECTOR_STENCIL: VertexDescriptor = VertexDescriptor { + vertex_attributes: &[ + VertexAttribute { + name: "aPosition", + count: 2, + kind: VertexAttributeKind::F32, + }, + ], + instance_attributes: &[ + VertexAttribute { + name: "aFromPosition", + count: 2, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aCtrlPosition", + count: 2, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aToPosition", + count: 2, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aFromNormal", + count: 2, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aCtrlNormal", + count: 2, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aToNormal", + count: 2, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aPathID", + count: 1, + kind: VertexAttributeKind::U16, + }, + VertexAttribute { + name: "aPad", + count: 1, + kind: VertexAttributeKind::U16, + }, + ], + }; + + pub const VECTOR_COVER: VertexDescriptor = VertexDescriptor { + vertex_attributes: &[ + VertexAttribute { + name: "aPosition", + count: 2, + kind: VertexAttributeKind::F32, + }, + ], + instance_attributes: &[ + VertexAttribute { + name: "aTargetRect", + count: 4, + kind: VertexAttributeKind::I32, + }, + VertexAttribute { + name: "aStencilOrigin", + count: 2, + kind: VertexAttributeKind::I32, + }, + VertexAttribute { + name: "aSubpixel", + count: 1, + kind: VertexAttributeKind::U16, + }, + VertexAttribute { + name: "aPad", + count: 1, + kind: VertexAttributeKind::U16, + }, + ], + }; + + pub const COMPOSITE: VertexDescriptor = VertexDescriptor { + vertex_attributes: &[ + VertexAttribute { + name: "aPosition", + count: 2, + kind: VertexAttributeKind::F32, + }, + ], + instance_attributes: &[ + VertexAttribute { + name: "aDeviceRect", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aDeviceClipRect", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aColor", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aParams", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aUvRect0", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aUvRect1", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aUvRect2", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aTextureLayers", + count: 3, + kind: VertexAttributeKind::F32, + }, + ], + }; + + pub const CLEAR: VertexDescriptor = VertexDescriptor { + vertex_attributes: &[ + VertexAttribute { + name: "aPosition", + count: 2, + kind: VertexAttributeKind::F32, + }, + ], + instance_attributes: &[ + VertexAttribute { + name: "aRect", + count: 4, + kind: VertexAttributeKind::F32, + }, + VertexAttribute { + name: "aColor", + count: 4, + kind: VertexAttributeKind::F32, + }, + ], + }; +} + +#[derive(Debug, Copy, Clone)] +pub(crate) enum VertexArrayKind { + Primitive, + Blur, + Clip, + VectorStencil, + VectorCover, + Border, + Scale, + LineDecoration, + Gradient, + Resolve, + SvgFilter, + Composite, + Clear, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum GraphicsApi { + OpenGL, +} + +#[derive(Clone, Debug)] +pub struct GraphicsApiInfo { + pub kind: GraphicsApi, + pub renderer: String, + pub version: String, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum ImageBufferKind { + Texture2D = 0, + TextureRect = 1, + TextureExternal = 2, + Texture2DArray = 3, +} + +//TODO: those types are the same, so let's merge them +impl From for ImageBufferKind { + fn from(target: TextureTarget) -> Self { + match target { + TextureTarget::Default => ImageBufferKind::Texture2D, + TextureTarget::Rect => ImageBufferKind::TextureRect, + TextureTarget::Array => ImageBufferKind::Texture2DArray, + TextureTarget::External => ImageBufferKind::TextureExternal, + } + } +} + +#[derive(Debug)] +pub struct GpuProfile { + pub frame_id: GpuFrameId, + pub paint_time_ns: u64, +} + +impl GpuProfile { + fn new(frame_id: GpuFrameId, timers: &[GpuTimer]) -> GpuProfile { + let mut paint_time_ns = 0; + for timer in timers { + paint_time_ns += timer.time_ns; + } + GpuProfile { + frame_id, + paint_time_ns, + } + } +} + +#[derive(Debug)] +pub struct CpuProfile { + pub frame_id: GpuFrameId, + pub backend_time_ns: u64, + pub composite_time_ns: u64, + pub draw_calls: usize, +} + +impl CpuProfile { + fn new( + frame_id: GpuFrameId, + backend_time_ns: u64, + composite_time_ns: u64, + draw_calls: usize, + ) -> CpuProfile { + CpuProfile { + frame_id, + backend_time_ns, + composite_time_ns, + draw_calls, + } + } +} + +/// The selected partial present mode for a given frame. +#[derive(Debug, Copy, Clone)] +enum PartialPresentMode { + /// The device supports fewer dirty rects than the number of dirty rects + /// that WR produced. In this case, the WR dirty rects are union'ed into + /// a single dirty rect, that is provided to the caller. + Single { + dirty_rect: DeviceRect, + }, +} + +/// A Texture that has been initialized by the `device` module and is ready to +/// be used. +struct ActiveTexture { + texture: Texture, + saved_index: Option, +} + +/// Helper struct for resolving device Textures for use during rendering passes. +/// +/// Manages the mapping between the at-a-distance texture handles used by the +/// `RenderBackend` (which does not directly interface with the GPU) and actual +/// device texture handles. +struct TextureResolver { + /// A map to resolve texture cache IDs to native textures. + texture_cache_map: FastHashMap, + + /// Map of external image IDs to native textures. + external_images: FastHashMap<(ExternalImageId, u8), ExternalTexture>, + + /// A special 1x1 dummy texture used for shaders that expect to work with + /// the output of the previous pass but are actually running in the first + /// pass. + dummy_cache_texture: Texture, + + /// The outputs of the previous pass, if applicable. + prev_pass_color: Option, + prev_pass_alpha: Option, + + /// Saved render targets from previous passes. This is used when a pass + /// needs access to the result of a pass other than the immediately-preceding + /// one. In this case, the `RenderTask` will get a non-`None` `saved_index`, + /// which will cause the resulting render target to be persisted in this list + /// (at that index) until the end of the frame. + saved_targets: Vec, + + /// Pool of idle render target textures ready for re-use. + /// + /// Naively, it would seem like we only ever need two pairs of (color, + /// alpha) render targets: one for the output of the previous pass (serving + /// as input to the current pass), and one for the output of the current + /// pass. However, there are cases where the output of one pass is used as + /// the input to multiple future passes. For example, drop-shadows draw the + /// picture in pass X, then reference it in pass X+1 to create the blurred + /// shadow, and pass the results of both X and X+1 to pass X+2 draw the + /// actual content. + /// + /// See the comments in `allocate_target_texture` for more insight on why + /// reuse is a win. + render_target_pool: Vec, +} + +impl TextureResolver { + fn new(device: &mut Device) -> TextureResolver { + let dummy_cache_texture = device + .create_texture( + TextureTarget::Array, + ImageFormat::RGBA8, + 1, + 1, + TextureFilter::Linear, + None, + 1, + ); + device.upload_texture_immediate( + &dummy_cache_texture, + &[0xff, 0xff, 0xff, 0xff], + ); + + TextureResolver { + texture_cache_map: FastHashMap::default(), + external_images: FastHashMap::default(), + dummy_cache_texture, + prev_pass_alpha: None, + prev_pass_color: None, + saved_targets: Vec::default(), + render_target_pool: Vec::new(), + } + } + + fn deinit(self, device: &mut Device) { + device.delete_texture(self.dummy_cache_texture); + + for (_id, texture) in self.texture_cache_map { + device.delete_texture(texture); + } + + for texture in self.render_target_pool { + device.delete_texture(texture); + } + } + + fn begin_frame(&mut self) { + assert!(self.prev_pass_color.is_none()); + assert!(self.prev_pass_alpha.is_none()); + assert!(self.saved_targets.is_empty()); + } + + fn end_frame(&mut self, device: &mut Device, frame_id: GpuFrameId) { + // return the cached targets to the pool + self.end_pass(device, None, None); + // return the saved targets as well + while let Some(target) = self.saved_targets.pop() { + self.return_to_pool(device, target); + } + + // GC the render target pool, if it's currently > 32 MB in size. + // + // We use a simple scheme whereby we drop any texture that hasn't been used + // in the last 60 frames, until we are below the size threshold. This should + // generally prevent any sustained build-up of unused textures, unless we don't + // generate frames for a long period. This can happen when the window is + // minimized, and we probably want to flush all the WebRender caches in that case [1]. + // There is also a second "red line" memory threshold which prevents + // memory exhaustion if many render targets are allocated within a small + // number of frames. For now this is set at 320 MB (10x the normal memory threshold). + // + // [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1494099 + self.gc_targets( + device, + frame_id, + 32 * 1024 * 1024, + 32 * 1024 * 1024 * 10, + 60, + ); + } + + /// Transfers ownership of a render target back to the pool. + fn return_to_pool(&mut self, device: &mut Device, target: Texture) { + device.invalidate_render_target(&target); + self.render_target_pool.push(target); + } + + /// Frees any memory possible, in the event of a memory pressure signal. + fn on_memory_pressure( + &mut self, + device: &mut Device, + ) { + // Clear all textures in the render target pool + for target in self.render_target_pool.drain(..) { + device.delete_texture(target); + } + } + + /// Drops all targets from the render target pool that do not satisfy the predicate. + pub fn gc_targets( + &mut self, + device: &mut Device, + current_frame_id: GpuFrameId, + total_bytes_threshold: usize, + total_bytes_red_line_threshold: usize, + frames_threshold: usize, + ) { + // Get the total GPU memory size used by the current render target pool + let mut rt_pool_size_in_bytes: usize = self.render_target_pool + .iter() + .map(|t| t.size_in_bytes()) + .sum(); + + // If the total size of the pool is less than the threshold, don't bother + // trying to GC any targets + if rt_pool_size_in_bytes <= total_bytes_threshold { + return; + } + + // Sort the current pool by age, so that we remove oldest textures first + self.render_target_pool.sort_by_key(|t| t.last_frame_used()); + + // We can't just use retain() because `Texture` requires manual cleanup. + let mut retained_targets = SmallVec::<[Texture; 8]>::new(); + + for target in self.render_target_pool.drain(..) { + // Drop oldest textures until we are under the allowed size threshold. + // However, if it's been used in very recently, it is always kept around, + // which ensures we don't thrash texture allocations on pages that do + // require a very large render target pool and are regularly changing. + if (rt_pool_size_in_bytes > total_bytes_red_line_threshold) || + (rt_pool_size_in_bytes > total_bytes_threshold && + !target.used_recently(current_frame_id, frames_threshold)) + { + rt_pool_size_in_bytes -= target.size_in_bytes(); + device.delete_texture(target); + } else { + retained_targets.push(target); + } + } + + self.render_target_pool.extend(retained_targets); + } + + fn end_pass( + &mut self, + device: &mut Device, + a8_texture: Option, + rgba8_texture: Option, + ) { + // If we have cache textures from previous pass, return them to the pool. + // Also assign the pool index of those cache textures to last pass's index because this is + // the result of last pass. + // Note: the order here is important, needs to match the logic in `RenderPass::build()`. + if let Some(at) = self.prev_pass_color.take() { + if let Some(index) = at.saved_index { + assert_eq!(self.saved_targets.len(), index.0); + self.saved_targets.push(at.texture); + } else { + self.return_to_pool(device, at.texture); + } + } + if let Some(at) = self.prev_pass_alpha.take() { + if let Some(index) = at.saved_index { + assert_eq!(self.saved_targets.len(), index.0); + self.saved_targets.push(at.texture); + } else { + self.return_to_pool(device, at.texture); + } + } + + // We have another pass to process, make these textures available + // as inputs to the next pass. + self.prev_pass_color = rgba8_texture; + self.prev_pass_alpha = a8_texture; + } + + // Bind a source texture to the device. + fn bind(&self, texture_id: &TextureSource, sampler: TextureSampler, device: &mut Device) -> Swizzle { + match *texture_id { + TextureSource::Invalid => { + Swizzle::default() + } + TextureSource::Dummy => { + let swizzle = Swizzle::default(); + device.bind_texture(sampler, &self.dummy_cache_texture, swizzle); + swizzle + } + TextureSource::PrevPassAlpha => { + let texture = match self.prev_pass_alpha { + Some(ref at) => &at.texture, + None => &self.dummy_cache_texture, + }; + let swizzle = Swizzle::default(); + device.bind_texture(sampler, texture, swizzle); + swizzle + } + TextureSource::PrevPassColor => { + let texture = match self.prev_pass_color { + Some(ref at) => &at.texture, + None => &self.dummy_cache_texture, + }; + let swizzle = Swizzle::default(); + device.bind_texture(sampler, texture, swizzle); + swizzle + } + TextureSource::External(external_image) => { + let texture = self.external_images + .get(&(external_image.id, external_image.channel_index)) + .expect("BUG: External image should be resolved by now"); + device.bind_external_texture(sampler, texture); + Swizzle::default() + } + TextureSource::TextureCache(index, swizzle) => { + let texture = &self.texture_cache_map[&index]; + device.bind_texture(sampler, texture, swizzle); + swizzle + } + TextureSource::RenderTaskCache(saved_index, swizzle) => { + if saved_index.0 < self.saved_targets.len() { + let texture = &self.saved_targets[saved_index.0]; + device.bind_texture(sampler, texture, swizzle) + } else { + // Check if this saved index is referring to a the prev pass + if Some(saved_index) == self.prev_pass_color.as_ref().and_then(|at| at.saved_index) { + let texture = match self.prev_pass_color { + Some(ref at) => &at.texture, + None => &self.dummy_cache_texture, + }; + device.bind_texture(sampler, texture, swizzle); + } else if Some(saved_index) == self.prev_pass_alpha.as_ref().and_then(|at| at.saved_index) { + let texture = match self.prev_pass_alpha { + Some(ref at) => &at.texture, + None => &self.dummy_cache_texture, + }; + device.bind_texture(sampler, texture, swizzle); + } + } + swizzle + } + } + } + + // Get the real (OpenGL) texture ID for a given source texture. + // For a texture cache texture, the IDs are stored in a vector + // map for fast access. + fn resolve(&self, texture_id: &TextureSource) -> Option<(&Texture, Swizzle)> { + match *texture_id { + TextureSource::Invalid => None, + TextureSource::Dummy => { + Some((&self.dummy_cache_texture, Swizzle::default())) + } + TextureSource::PrevPassAlpha => Some(( + match self.prev_pass_alpha { + Some(ref at) => &at.texture, + None => &self.dummy_cache_texture, + }, + Swizzle::default(), + )), + TextureSource::PrevPassColor => Some(( + match self.prev_pass_color { + Some(ref at) => &at.texture, + None => &self.dummy_cache_texture, + }, + Swizzle::default(), + )), + TextureSource::External(..) => { + panic!("BUG: External textures cannot be resolved, they can only be bound."); + } + TextureSource::TextureCache(index, swizzle) => { + Some((&self.texture_cache_map[&index], swizzle)) + } + TextureSource::RenderTaskCache(saved_index, swizzle) => { + Some((&self.saved_targets[saved_index.0], swizzle)) + } + } + } + + // Retrieve the deferred / resolved UV rect if an external texture, otherwise + // return the default supplied UV rect. + fn get_uv_rect( + &self, + source: &TextureSource, + default_value: TexelRect, + ) -> TexelRect { + match source { + TextureSource::External(ref external_image) => { + let texture = self.external_images + .get(&(external_image.id, external_image.channel_index)) + .expect("BUG: External image should be resolved by now"); + texture.get_uv_rect() + } + _ => { + default_value + } + } + } + + fn report_memory(&self) -> MemoryReport { + let mut report = MemoryReport::default(); + + // We're reporting GPU memory rather than heap-allocations, so we don't + // use size_of_op. + for t in self.texture_cache_map.values() { + report.texture_cache_textures += t.size_in_bytes(); + } + for t in self.render_target_pool.iter() { + report.render_target_textures += t.size_in_bytes(); + } + + report + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum BlendMode { + None, + Alpha, + PremultipliedAlpha, + PremultipliedDestOut, + SubpixelDualSource, + SubpixelConstantTextColor(ColorF), + SubpixelWithBgColor, + Advanced(MixBlendMode), +} + +/// Tracks the state of each row in the GPU cache texture. +struct CacheRow { + /// Mirrored block data on CPU for this row. We store a copy of + /// the data on the CPU side to improve upload batching. + cpu_blocks: Box<[GpuBlockData; MAX_VERTEX_TEXTURE_WIDTH]>, + /// The first offset in this row that is dirty. + min_dirty: u16, + /// The last offset in this row that is dirty. + max_dirty: u16, +} + +impl CacheRow { + fn new() -> Self { + CacheRow { + cpu_blocks: Box::new([GpuBlockData::EMPTY; MAX_VERTEX_TEXTURE_WIDTH]), + min_dirty: MAX_VERTEX_TEXTURE_WIDTH as _, + max_dirty: 0, + } + } + + fn is_dirty(&self) -> bool { + return self.min_dirty < self.max_dirty; + } + + fn clear_dirty(&mut self) { + self.min_dirty = MAX_VERTEX_TEXTURE_WIDTH as _; + self.max_dirty = 0; + } + + fn add_dirty(&mut self, block_offset: usize, block_count: usize) { + self.min_dirty = self.min_dirty.min(block_offset as _); + self.max_dirty = self.max_dirty.max((block_offset + block_count) as _); + } + + fn dirty_blocks(&self) -> &[GpuBlockData] { + return &self.cpu_blocks[self.min_dirty as usize .. self.max_dirty as usize]; + } +} + +/// The bus over which CPU and GPU versions of the GPU cache +/// get synchronized. +enum GpuCacheBus { + /// PBO-based updates, currently operate on a row granularity. + /// Therefore, are subject to fragmentation issues. + PixelBuffer { + /// PBO used for transfers. + buffer: PBO, + /// Per-row data. + rows: Vec, + }, + /// Shader-based scattering updates. Currently rendered by a set + /// of points into the GPU texture, each carrying a `GpuBlockData`. + Scatter { + /// Special program to run the scattered update. + program: Program, + /// VAO containing the source vertex buffers. + vao: CustomVAO, + /// VBO for positional data, supplied as normalized `u16`. + buf_position: VBO<[u16; 2]>, + /// VBO for gpu block data. + buf_value: VBO, + /// Currently stored block count. + count: usize, + }, +} + +/// The device-specific representation of the cache texture in gpu_cache.rs +struct GpuCacheTexture { + texture: Option, + bus: GpuCacheBus, +} + +impl GpuCacheTexture { + + /// Ensures that we have an appropriately-sized texture. Returns true if a + /// new texture was created. + fn ensure_texture(&mut self, device: &mut Device, height: i32) { + // If we already have a texture that works, we're done. + if self.texture.as_ref().map_or(false, |t| t.get_dimensions().height >= height) { + if GPU_CACHE_RESIZE_TEST { + // Special debug mode - resize the texture even though it's fine. + } else { + return; + } + } + + // Take the old texture, if any. + let blit_source = self.texture.take(); + + // Create the new texture. + assert!(height >= 2, "Height is too small for ANGLE"); + let new_size = DeviceIntSize::new(MAX_VERTEX_TEXTURE_WIDTH as _, height); + // If glCopyImageSubData is supported, this texture doesn't need + // to be a render target. This prevents GL errors due to framebuffer + // incompleteness on devices that don't support RGBAF32 render targets. + // TODO(gw): We still need a proper solution for the subset of devices + // that don't support glCopyImageSubData *OR* rendering to a + // RGBAF32 render target. These devices will currently fail + // to resize the GPU cache texture. + let supports_copy_image_sub_data = device.get_capabilities().supports_copy_image_sub_data; + let rt_info = if supports_copy_image_sub_data { + None + } else { + Some(RenderTargetInfo { has_depth: false }) + }; + let mut texture = device.create_texture( + TextureTarget::Default, + ImageFormat::RGBAF32, + new_size.width, + new_size.height, + TextureFilter::Nearest, + rt_info, + 1, + ); + + // Blit the contents of the previous texture, if applicable. + if let Some(blit_source) = blit_source { + device.blit_renderable_texture(&mut texture, &blit_source); + device.delete_texture(blit_source); + } + + self.texture = Some(texture); + } + + fn new(device: &mut Device, use_scatter: bool) -> Result { + let bus = if use_scatter { + let program = device.create_program_linked( + "gpu_cache_update", + &[], + &desc::GPU_CACHE_UPDATE, + )?; + let buf_position = device.create_vbo(); + let buf_value = device.create_vbo(); + //Note: the vertex attributes have to be supplied in the same order + // as for program creation, but each assigned to a different stream. + let vao = device.create_custom_vao(&[ + buf_position.stream_with(&desc::GPU_CACHE_UPDATE.vertex_attributes[0..1]), + buf_value .stream_with(&desc::GPU_CACHE_UPDATE.vertex_attributes[1..2]), + ]); + GpuCacheBus::Scatter { + program, + vao, + buf_position, + buf_value, + count: 0, + } + } else { + let buffer = device.create_pbo(); + GpuCacheBus::PixelBuffer { + buffer, + rows: Vec::new(), + } + }; + + Ok(GpuCacheTexture { + texture: None, + bus, + }) + } + + fn deinit(mut self, device: &mut Device) { + if let Some(t) = self.texture.take() { + device.delete_texture(t); + } + match self.bus { + GpuCacheBus::PixelBuffer { buffer, ..} => { + device.delete_pbo(buffer); + } + GpuCacheBus::Scatter { program, vao, buf_position, buf_value, ..} => { + device.delete_program(program); + device.delete_custom_vao(vao); + device.delete_vbo(buf_position); + device.delete_vbo(buf_value); + } + } + } + + fn get_height(&self) -> i32 { + self.texture.as_ref().map_or(0, |t| t.get_dimensions().height) + } + + fn prepare_for_updates( + &mut self, + device: &mut Device, + total_block_count: usize, + max_height: i32, + ) { + self.ensure_texture(device, max_height); + match self.bus { + GpuCacheBus::PixelBuffer { .. } => {}, + GpuCacheBus::Scatter { + ref mut buf_position, + ref mut buf_value, + ref mut count, + .. + } => { + *count = 0; + if total_block_count > buf_value.allocated_count() { + device.allocate_vbo(buf_position, total_block_count, VertexUsageHint::Stream); + device.allocate_vbo(buf_value, total_block_count, VertexUsageHint::Stream); + } + } + } + } + + fn update(&mut self, device: &mut Device, updates: &GpuCacheUpdateList) { + match self.bus { + GpuCacheBus::PixelBuffer { ref mut rows, .. } => { + for update in &updates.updates { + match *update { + GpuCacheUpdate::Copy { + block_index, + block_count, + address, + } => { + let row = address.v as usize; + + // Ensure that the CPU-side shadow copy of the GPU cache data has enough + // rows to apply this patch. + while rows.len() <= row { + // Add a new row. + rows.push(CacheRow::new()); + } + + // Copy the blocks from the patch array in the shadow CPU copy. + let block_offset = address.u as usize; + let data = &mut rows[row].cpu_blocks; + for i in 0 .. block_count { + data[block_offset + i] = updates.blocks[block_index + i]; + } + + // This row is dirty (needs to be updated in GPU texture). + rows[row].add_dirty(block_offset, block_count); + } + } + } + } + GpuCacheBus::Scatter { + ref buf_position, + ref buf_value, + ref mut count, + .. + } => { + //TODO: re-use this heap allocation + // Unused positions will be left as 0xFFFF, which translates to + // (1.0, 1.0) in the vertex output position and gets culled out + let mut position_data = vec![[!0u16; 2]; updates.blocks.len()]; + let size = self.texture.as_ref().unwrap().get_dimensions().to_usize(); + + for update in &updates.updates { + match *update { + GpuCacheUpdate::Copy { + block_index, + block_count, + address, + } => { + // Convert the absolute texel position into normalized + let y = ((2*address.v as usize + 1) << 15) / size.height; + for i in 0 .. block_count { + let x = ((2*address.u as usize + 2*i + 1) << 15) / size.width; + position_data[block_index + i] = [x as _, y as _]; + } + } + } + } + + device.fill_vbo(buf_value, &updates.blocks, *count); + device.fill_vbo(buf_position, &position_data, *count); + *count += position_data.len(); + } + } + } + + fn flush(&mut self, device: &mut Device) -> usize { + let texture = self.texture.as_ref().unwrap(); + match self.bus { + GpuCacheBus::PixelBuffer { ref buffer, ref mut rows } => { + let rows_dirty = rows + .iter() + .filter(|row| row.is_dirty()) + .count(); + if rows_dirty == 0 { + return 0 + } + + let (upload_size, _) = device.required_upload_size_and_stride( + DeviceIntSize::new(MAX_VERTEX_TEXTURE_WIDTH as i32, 1), + texture.get_format(), + ); + + let mut uploader = device.upload_texture( + texture, + buffer, + rows_dirty * upload_size, + ); + + for (row_index, row) in rows.iter_mut().enumerate() { + if !row.is_dirty() { + continue; + } + + let blocks = row.dirty_blocks(); + let rect = DeviceIntRect::new( + DeviceIntPoint::new(row.min_dirty as i32, row_index as i32), + DeviceIntSize::new(blocks.len() as i32, 1), + ); + + uploader.upload(rect, 0, None, None, blocks.as_ptr(), blocks.len()); + + row.clear_dirty(); + } + + rows_dirty + } + GpuCacheBus::Scatter { ref program, ref vao, count, .. } => { + device.disable_depth(); + device.set_blend(false); + device.bind_program(program); + device.bind_custom_vao(vao); + device.bind_draw_target( + DrawTarget::from_texture( + texture, + 0, + false, + ), + ); + device.draw_nonindexed_points(0, count as _); + 0 + } + } + } +} + +struct VertexDataTexture { + texture: Option, + format: ImageFormat, + pbo: PBO, + _marker: PhantomData, +} + +impl VertexDataTexture { + fn new( + device: &mut Device, + format: ImageFormat, + ) -> Self { + VertexDataTexture { + texture: None, + format, + pbo: device.create_pbo(), + _marker: PhantomData, + } + } + + /// Returns a borrow of the GPU texture. Panics if it hasn't been initialized. + fn texture(&self) -> &Texture { + self.texture.as_ref().unwrap() + } + + /// Returns an estimate of the GPU memory consumed by this VertexDataTexture. + fn size_in_bytes(&self) -> usize { + self.texture.as_ref().map_or(0, |t| t.size_in_bytes()) + } + + fn update(&mut self, device: &mut Device, data: &mut Vec) { + debug_assert!(mem::size_of::() % 16 == 0); + let texels_per_item = mem::size_of::() / 16; + let items_per_row = MAX_VERTEX_TEXTURE_WIDTH / texels_per_item; + debug_assert_ne!(items_per_row, 0); + + // Ensure we always end up with a texture when leaving this method. + let mut len = data.len(); + if len == 0 { + if self.texture.is_some() { + return; + } + data.reserve(items_per_row); + len = items_per_row; + } else { + // Extend the data array to have enough capacity to upload at least + // a multiple of the row size. This ensures memory safety when the + // array is passed to OpenGL to upload to the GPU. + let extra = len % items_per_row; + if extra != 0 { + let padding = items_per_row - extra; + data.reserve(padding); + len += padding; + } + } + + let needed_height = (len / items_per_row) as i32; + let existing_height = self.texture.as_ref().map_or(0, |t| t.get_dimensions().height); + + // Create a new texture if needed. + // + // These textures are generally very small, which is why we don't bother + // with incremental updates and just re-upload every frame. For most pages + // they're one row each, and on stress tests like css-francine they end up + // in the 6-14 range. So we size the texture tightly to what we need (usually + // 1), and shrink it if the waste would be more than `VERTEX_TEXTURE_EXTRA_ROWS` + // rows. This helps with memory overhead, especially because there are several + // instances of these textures per Renderer. + if needed_height > existing_height || needed_height + VERTEX_TEXTURE_EXTRA_ROWS < existing_height { + // Drop the existing texture, if any. + if let Some(t) = self.texture.take() { + device.delete_texture(t); + } + + let texture = device.create_texture( + TextureTarget::Default, + self.format, + MAX_VERTEX_TEXTURE_WIDTH as i32, + // Ensure height is at least two to work around + // https://bugs.chromium.org/p/angleproject/issues/detail?id=3039 + needed_height.max(2), + TextureFilter::Nearest, + None, + 1, + ); + self.texture = Some(texture); + } + + // Note: the actual width can be larger than the logical one, with a few texels + // of each row unused at the tail. This is needed because there is still hardware + // (like Intel iGPUs) that prefers power-of-two sizes of textures ([1]). + // + // [1] https://software.intel.com/en-us/articles/opengl-performance-tips-power-of-two-textures-have-better-performance + let logical_width = if needed_height == 1 { + data.len() * texels_per_item + } else { + MAX_VERTEX_TEXTURE_WIDTH - (MAX_VERTEX_TEXTURE_WIDTH % texels_per_item) + }; + + let rect = DeviceIntRect::new( + DeviceIntPoint::zero(), + DeviceIntSize::new(logical_width as i32, needed_height), + ); + + debug_assert!(len <= data.capacity(), "CPU copy will read out of bounds"); + let (upload_size, _) = device.required_upload_size_and_stride( + rect.size, + self.texture().get_format(), + ); + if upload_size > 0 { + device + .upload_texture(self.texture(), &self.pbo, upload_size) + .upload(rect, 0, None, None, data.as_ptr(), len); + } + } + + fn deinit(mut self, device: &mut Device) { + device.delete_pbo(self.pbo); + if let Some(t) = self.texture.take() { + device.delete_texture(t); + } + } +} + +struct FrameOutput { + last_access: GpuFrameId, + fbo_id: FBOId, +} + +#[derive(PartialEq)] +struct TargetSelector { + size: DeviceIntSize, + num_layers: usize, + format: ImageFormat, +} + +struct LazyInitializedDebugRenderer { + debug_renderer: Option, + failed: bool, +} + +impl LazyInitializedDebugRenderer { + pub fn new() -> Self { + Self { + debug_renderer: None, + failed: false, + } + } + + pub fn get_mut<'a>(&'a mut self, device: &mut Device) -> Option<&'a mut DebugRenderer> { + if self.failed { + return None; + } + if self.debug_renderer.is_none() { + match DebugRenderer::new(device) { + Ok(renderer) => { self.debug_renderer = Some(renderer); } + Err(_) => { + // The shader compilation code already logs errors. + self.failed = true; + } + } + } + + 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); + } + } +} + +// NB: If you add more VAOs here, be sure to deinitialize them in +// `Renderer::deinit()` below. +pub struct RendererVAOs { + prim_vao: VAO, + blur_vao: VAO, + clip_vao: VAO, + border_vao: VAO, + line_vao: VAO, + scale_vao: VAO, + gradient_vao: VAO, + resolve_vao: VAO, + svg_filter_vao: VAO, + composite_vao: VAO, + clear_vao: VAO, +} + +/// Information about the state of the debugging / profiler overlay in native compositing mode. +struct DebugOverlayState { + /// True if any of the current debug flags will result in drawing a debug overlay. + is_enabled: bool, + + /// The current size of the debug overlay surface. None implies that the + /// debug surface isn't currently allocated. + current_size: Option, +} + +impl DebugOverlayState { + fn new() -> Self { + DebugOverlayState { + is_enabled: false, + current_size: None, + } + } +} + +pub struct VertexDataTextures { + prim_header_f_texture: VertexDataTexture, + prim_header_i_texture: VertexDataTexture, + transforms_texture: VertexDataTexture, + render_task_texture: VertexDataTexture, +} + +impl VertexDataTextures { + fn new( + device: &mut Device, + ) -> Self { + VertexDataTextures { + prim_header_f_texture: VertexDataTexture::new(device, ImageFormat::RGBAF32), + prim_header_i_texture: VertexDataTexture::new(device, ImageFormat::RGBAI32), + transforms_texture: VertexDataTexture::new(device, ImageFormat::RGBAF32), + render_task_texture: VertexDataTexture::new(device, ImageFormat::RGBAF32), + } + } + + fn update( + &mut self, + device: &mut Device, + frame: &mut Frame, + ) { + self.prim_header_f_texture.update( + device, + &mut frame.prim_headers.headers_float, + ); + device.bind_texture( + TextureSampler::PrimitiveHeadersF, + &self.prim_header_f_texture.texture(), + Swizzle::default(), + ); + + self.prim_header_i_texture.update( + device, + &mut frame.prim_headers.headers_int, + ); + device.bind_texture( + TextureSampler::PrimitiveHeadersI, + &self.prim_header_i_texture.texture(), + Swizzle::default(), + ); + + self.transforms_texture.update( + device, + &mut frame.transform_palette, + ); + device.bind_texture( + TextureSampler::TransformPalette, + &self.transforms_texture.texture(), + Swizzle::default(), + ); + + self.render_task_texture.update( + device, + &mut frame.render_tasks.task_data, + ); + device.bind_texture( + TextureSampler::RenderTasks, + &self.render_task_texture.texture(), + Swizzle::default(), + ); + } + + fn size_in_bytes(&self) -> usize { + self.prim_header_f_texture.size_in_bytes() + + self.prim_header_i_texture.size_in_bytes() + + self.transforms_texture.size_in_bytes() + + self.render_task_texture.size_in_bytes() + } + + fn deinit( + self, + device: &mut Device, + ) { + self.transforms_texture.deinit(device); + self.prim_header_f_texture.deinit(device); + self.prim_header_i_texture.deinit(device); + self.render_task_texture.deinit(device); + } +} + +/// The renderer is responsible for submitting to the GPU the work prepared by the +/// RenderBackend. +/// +/// We have a separate `Renderer` instance for each instance of WebRender (generally +/// one per OS window), and all instances share the same thread. +pub struct Renderer { + result_rx: Receiver, + debug_server: Box, + pub device: Device, + pending_texture_updates: Vec, + /// True if there are any TextureCacheUpdate pending. + pending_texture_cache_updates: bool, + pending_native_surface_updates: Vec, + pending_gpu_cache_updates: Vec, + pending_gpu_cache_clear: bool, + pending_shader_updates: Vec, + active_documents: Vec<(DocumentId, RenderedDocument)>, + + shaders: Rc>, + + max_recorded_profiles: usize, + + clear_color: Option, + enable_clear_scissor: bool, + enable_advanced_blend_barriers: bool, + clear_caches_with_quads: bool, + + debug: LazyInitializedDebugRenderer, + debug_flags: DebugFlags, + backend_profile_counters: BackendProfileCounters, + profile_counters: RendererProfileCounters, + resource_upload_time: u64, + gpu_cache_upload_time: u64, + profiler: Profiler, + new_frame_indicator: ChangeIndicator, + new_scene_indicator: ChangeIndicator, + slow_frame_indicator: ChangeIndicator, + slow_txn_indicator: ChangeIndicator, + + last_time: u64, + + pub gpu_profile: GpuProfiler, + vaos: RendererVAOs, + + gpu_cache_texture: GpuCacheTexture, + vertex_data_textures: Vec, + current_vertex_data_textures: usize, + + /// When the GPU cache debugger is enabled, we keep track of the live blocks + /// in the GPU cache so that we can use them for the debug display. This + /// member stores those live blocks, indexed by row. + gpu_cache_debug_chunks: Vec>, + + gpu_cache_frame_id: FrameId, + gpu_cache_overflow: bool, + + pipeline_info: PipelineInfo, + + // Manages and resolves source textures IDs to real texture IDs. + texture_resolver: TextureResolver, + + // A PBO used to do asynchronous texture cache uploads. + texture_cache_upload_pbo: PBO, + + dither_matrix_texture: Option, + + /// Optional trait object that allows the client + /// application to provide external buffers for image data. + external_image_handler: Option>, + + /// Optional trait object that allows the client + /// application to provide a texture handle to + /// copy the WR output to. + output_image_handler: Option>, + + /// Optional function pointers for measuring memory used by a given + /// heap-allocated pointer. + size_of_ops: Option, + + // Currently allocated FBOs for output frames. + output_targets: FastHashMap, + + pub renderer_errors: Vec, + + pub(in crate) async_frame_recorder: Option, + pub(in crate) async_screenshots: Option, + + /// List of profile results from previous frames. Can be retrieved + /// via get_frame_profiles(). + cpu_profiles: VecDeque, + gpu_profiles: VecDeque, + + /// Notification requests to be fulfilled after rendering. + notifications: Vec, + + device_size: Option, + + /// A lazily created texture for the zoom debugging widget. + zoom_debug_texture: Option, + + /// The current mouse position. This is used for debugging + /// functionality only, such as the debug zoom widget. + cursor_position: DeviceIntPoint, + + /// Guards to check if we might be rendering a frame with expired texture + /// cache entries. + shared_texture_cache_cleared: bool, + + /// The set of documents which we've seen a publish for since last render. + documents_seen: FastHashSet, + + #[cfg(feature = "capture")] + read_fbo: FBOId, + #[cfg(feature = "replay")] + owned_external_images: FastHashMap<(ExternalImageId, u8), ExternalTexture>, + + /// The compositing config, affecting how WR composites into the final scene. + compositor_config: CompositorConfig, + + current_compositor_kind: CompositorKind, + + /// Maintains a set of allocated native composite surfaces. This allows any + /// currently allocated surfaces to be cleaned up as soon as deinit() is + /// called (the normal bookkeeping for native surfaces exists in the + /// render backend thread). + allocated_native_surfaces: FastHashSet, + + /// If true, partial present state has been reset and everything needs to + /// be drawn on the next render. + force_redraw: bool, + + /// State related to the debug / profiling overlays + debug_overlay_state: DebugOverlayState, + + /// The dirty rectangle from the previous frame, used on platforms that + /// require keeping the front buffer fully correct when doing + /// partial present (e.g. unix desktop with EGL_EXT_buffer_age). + prev_dirty_rect: DeviceRect, +} + +#[derive(Debug)] +pub enum RendererError { + Shader(ShaderError), + Thread(std::io::Error), + Resource(ResourceCacheError), + MaxTextureSize, +} + +impl From for RendererError { + fn from(err: ShaderError) -> Self { + RendererError::Shader(err) + } +} + +impl From for RendererError { + fn from(err: std::io::Error) -> Self { + RendererError::Thread(err) + } +} + +impl From for RendererError { + fn from(err: ResourceCacheError) -> Self { + RendererError::Resource(err) + } +} + +impl Renderer { + /// Initializes WebRender and creates a `Renderer` and `RenderApiSender`. + /// + /// # Examples + /// Initializes a `Renderer` with some reasonable values. For more information see + /// [`RendererOptions`][rendereroptions]. + /// + /// ```rust,ignore + /// # use webrender::renderer::Renderer; + /// # use std::path::PathBuf; + /// let opts = webrender::RendererOptions { + /// device_pixel_ratio: 1.0, + /// resource_override_path: None, + /// enable_aa: false, + /// }; + /// let (renderer, sender) = Renderer::new(opts); + /// ``` + /// [rendereroptions]: struct.RendererOptions.html + pub fn new( + gl: Rc, + notifier: Box, + mut options: RendererOptions, + shaders: Option<&mut WrShaders>, + start_size: DeviceIntSize, + ) -> Result<(Self, RenderApiSender), RendererError> { + if !wr_has_been_initialized() { + // If the profiler feature is enabled, try to load the profiler shared library + // if the path was provided. + #[cfg(feature = "profiler")] + unsafe { + if let Ok(ref tracy_path) = std::env::var("WR_TRACY_PATH") { + let ok = tracy_rs::load(tracy_path); + println!("Load tracy from {} -> {}", tracy_path, ok); + } + } + + register_thread_with_profiler("Compositor".to_owned()); + } + + HAS_BEEN_INITIALIZED.store(true, Ordering::SeqCst); + + let (api_tx, api_rx) = channel(); + let (result_tx, result_rx) = channel(); + let gl_type = gl.get_type(); + + let debug_server = new_debug_server(options.start_debug_server, api_tx.clone()); + + let mut device = Device::new( + gl, + options.resource_override_path.clone(), + options.use_optimized_shaders, + options.upload_method.clone(), + options.cached_programs.take(), + options.allow_pixel_local_storage_support, + options.allow_texture_storage_support, + options.allow_texture_swizzling, + options.dump_shader_source.take(), + options.surface_origin_is_top_left, + options.panic_on_gl_error, + ); + + let color_cache_formats = device.preferred_color_formats(); + let swizzle_settings = device.swizzle_settings(); + let use_dual_source_blending = + device.get_capabilities().supports_dual_source_blending && + options.allow_dual_source_blending && + // If using pixel local storage, subpixel AA isn't supported (we disable it on all + // mobile devices explicitly anyway). + !device.get_capabilities().supports_pixel_local_storage; + let ext_blend_equation_advanced = + options.allow_advanced_blend_equation && + device.get_capabilities().supports_advanced_blend_equation; + let ext_blend_equation_advanced_coherent = + device.supports_extension("GL_KHR_blend_equation_advanced_coherent"); + + // 512 is the minimum that the texture cache can work with. + const MIN_TEXTURE_SIZE: i32 = 512; + if let Some(user_limit) = options.max_texture_size { + assert!(user_limit >= MIN_TEXTURE_SIZE); + device.clamp_max_texture_size(user_limit); + } + if device.max_texture_size() < MIN_TEXTURE_SIZE { + // Broken GL contexts can return a max texture size of zero (See #1260). + // Better to gracefully fail now than panic as soon as a texture is allocated. + error!( + "Device reporting insufficient max texture size ({})", + device.max_texture_size() + ); + return Err(RendererError::MaxTextureSize); + } + let max_texture_size = device.max_texture_size(); + let max_texture_layers = device.max_texture_layers(); + + device.begin_frame(); + + let shaders = match shaders { + Some(shaders) => Rc::clone(&shaders.shaders), + None => Rc::new(RefCell::new(Shaders::new(&mut device, gl_type, &options)?)), + }; + + let backend_profile_counters = BackendProfileCounters::new(); + + let dither_matrix_texture = if options.enable_dithering { + let dither_matrix: [u8; 64] = [ + 0, + 48, + 12, + 60, + 3, + 51, + 15, + 63, + 32, + 16, + 44, + 28, + 35, + 19, + 47, + 31, + 8, + 56, + 4, + 52, + 11, + 59, + 7, + 55, + 40, + 24, + 36, + 20, + 43, + 27, + 39, + 23, + 2, + 50, + 14, + 62, + 1, + 49, + 13, + 61, + 34, + 18, + 46, + 30, + 33, + 17, + 45, + 29, + 10, + 58, + 6, + 54, + 9, + 57, + 5, + 53, + 42, + 26, + 38, + 22, + 41, + 25, + 37, + 21, + ]; + + let texture = device.create_texture( + TextureTarget::Default, + ImageFormat::R8, + 8, + 8, + TextureFilter::Nearest, + None, + 1, + ); + device.upload_texture_immediate(&texture, &dither_matrix); + + Some(texture) + } else { + None + }; + + let x0 = 0.0; + let y0 = 0.0; + let x1 = 1.0; + let y1 = 1.0; + + let quad_indices: [u16; 6] = [0, 1, 2, 2, 1, 3]; + let quad_vertices = [ + PackedVertex { pos: [x0, y0] }, + PackedVertex { pos: [x1, y0] }, + PackedVertex { pos: [x0, y1] }, + PackedVertex { pos: [x1, y1] }, + ]; + + let prim_vao = device.create_vao(&desc::PRIM_INSTANCES); + device.bind_vao(&prim_vao); + device.update_vao_indices(&prim_vao, &quad_indices, VertexUsageHint::Static); + device.update_vao_main_vertices(&prim_vao, &quad_vertices, VertexUsageHint::Static); + + let blur_vao = device.create_vao_with_new_instances(&desc::BLUR, &prim_vao); + let clip_vao = device.create_vao_with_new_instances(&desc::CLIP, &prim_vao); + let border_vao = device.create_vao_with_new_instances(&desc::BORDER, &prim_vao); + let scale_vao = device.create_vao_with_new_instances(&desc::SCALE, &prim_vao); + let line_vao = device.create_vao_with_new_instances(&desc::LINE, &prim_vao); + let gradient_vao = device.create_vao_with_new_instances(&desc::GRADIENT, &prim_vao); + let resolve_vao = device.create_vao_with_new_instances(&desc::RESOLVE, &prim_vao); + let svg_filter_vao = device.create_vao_with_new_instances(&desc::SVG_FILTER, &prim_vao); + let composite_vao = device.create_vao_with_new_instances(&desc::COMPOSITE, &prim_vao); + let clear_vao = device.create_vao_with_new_instances(&desc::CLEAR, &prim_vao); + let texture_cache_upload_pbo = device.create_pbo(); + + let texture_resolver = TextureResolver::new(&mut device); + + let mut vertex_data_textures = Vec::new(); + for _ in 0 .. VERTEX_DATA_TEXTURE_COUNT { + vertex_data_textures.push(VertexDataTextures::new(&mut device)); + } + + // On some (mostly older, integrated) GPUs, the normal GPU texture cache update path + // doesn't work well when running on ANGLE, causing CPU stalls inside D3D and/or the + // GPU driver. See https://bugzilla.mozilla.org/show_bug.cgi?id=1576637 for much + // more detail. To reduce the number of code paths we have active that require testing, + // we will enable the GPU cache scatter update path on all devices running with ANGLE. + // We want a better solution long-term, but for now this is a significant performance + // improvement on HD4600 era GPUs, and shouldn't hurt performance in a noticeable + // way on other systems running under ANGLE. + let is_angle = device.get_capabilities().renderer_name.contains("ANGLE"); + + let gpu_cache_texture = GpuCacheTexture::new( + &mut device, + is_angle, + )?; + + device.end_frame(); + + let backend_notifier = notifier.clone(); + + let prefer_subpixel_aa = options.force_subpixel_aa || (options.enable_subpixel_aa && use_dual_source_blending); + let default_font_render_mode = match (options.enable_aa, prefer_subpixel_aa) { + (true, true) => FontRenderMode::Subpixel, + (true, false) => FontRenderMode::Alpha, + (false, _) => FontRenderMode::Mono, + }; + + let compositor_kind = match options.compositor_config { + CompositorConfig::Draw { max_partial_present_rects, draw_previous_partial_present_regions } => { + CompositorKind::Draw { max_partial_present_rects, draw_previous_partial_present_regions } + } + CompositorConfig::Native { ref compositor, max_update_rects, .. } => { + let capabilities = compositor.get_capabilities(); + + CompositorKind::Native { + max_update_rects, + virtual_surface_size: capabilities.virtual_surface_size, + } + } + }; + + let config = FrameBuilderConfig { + default_font_render_mode, + dual_source_blending_is_enabled: true, + dual_source_blending_is_supported: use_dual_source_blending, + chase_primitive: options.chase_primitive, + global_enable_picture_caching: options.enable_picture_caching, + testing: options.testing, + gpu_supports_fast_clears: options.gpu_supports_fast_clears, + gpu_supports_advanced_blend: ext_blend_equation_advanced, + advanced_blend_is_coherent: ext_blend_equation_advanced_coherent, + batch_lookback_count: options.batch_lookback_count, + background_color: options.clear_color, + compositor_kind, + tile_size_override: None, + max_depth_ids: device.max_depth_ids(), + max_target_size: max_texture_size, + }; + info!("WR {:?}", config); + + let device_pixel_ratio = options.device_pixel_ratio; + let debug_flags = options.debug_flags; + let size_of_op = options.size_of_op; + let enclosing_size_of_op = options.enclosing_size_of_op; + let make_size_of_ops = + move || size_of_op.map(|o| MallocSizeOfOps::new(o, enclosing_size_of_op)); + let thread_listener = Arc::new(options.thread_listener); + let thread_listener_for_rayon_start = thread_listener.clone(); + let thread_listener_for_rayon_end = thread_listener.clone(); + let workers = options + .workers + .take() + .unwrap_or_else(|| { + let worker = ThreadPoolBuilder::new() + .thread_name(|idx|{ format!("WRWorker#{}", idx) }) + .start_handler(move |idx| { + register_thread_with_profiler(format!("WRWorker#{}", idx)); + if let Some(ref thread_listener) = *thread_listener_for_rayon_start { + thread_listener.thread_started(&format!("WRWorker#{}", idx)); + } + }) + .exit_handler(move |idx| { + if let Some(ref thread_listener) = *thread_listener_for_rayon_end { + thread_listener.thread_stopped(&format!("WRWorker#{}", idx)); + } + }) + .build(); + Arc::new(worker.unwrap()) + }); + let sampler = options.sampler; + let namespace_alloc_by_client = options.namespace_alloc_by_client; + let max_glyph_cache_size = options.max_glyph_cache_size.unwrap_or(GlyphCache::DEFAULT_MAX_BYTES_USED); + + let font_instances = SharedFontInstanceMap::new(); + + let blob_image_handler = options.blob_image_handler.take(); + let thread_listener_for_render_backend = thread_listener.clone(); + let thread_listener_for_scene_builder = thread_listener.clone(); + let thread_listener_for_lp_scene_builder = thread_listener.clone(); + let scene_builder_hooks = options.scene_builder_hooks; + let rb_thread_name = format!("WRRenderBackend#{}", options.renderer_id.unwrap_or(0)); + let scene_thread_name = format!("WRSceneBuilder#{}", options.renderer_id.unwrap_or(0)); + let lp_scene_thread_name = format!("WRSceneBuilderLP#{}", options.renderer_id.unwrap_or(0)); + let glyph_rasterizer = GlyphRasterizer::new(workers)?; + + let (scene_builder_channels, scene_tx, backend_scene_tx, scene_rx) = + SceneBuilderThreadChannels::new(api_tx.clone()); + + let sb_font_instances = font_instances.clone(); + + thread::Builder::new().name(scene_thread_name.clone()).spawn(move || { + register_thread_with_profiler(scene_thread_name.clone()); + if let Some(ref thread_listener) = *thread_listener_for_scene_builder { + thread_listener.thread_started(&scene_thread_name); + } + + let mut scene_builder = SceneBuilderThread::new( + config, + device_pixel_ratio, + sb_font_instances, + make_size_of_ops(), + scene_builder_hooks, + scene_builder_channels, + ); + scene_builder.run(); + + if let Some(ref thread_listener) = *thread_listener_for_scene_builder { + thread_listener.thread_stopped(&scene_thread_name); + } + })?; + + let low_priority_scene_tx = if options.support_low_priority_transactions { + let (low_priority_scene_tx, low_priority_scene_rx) = channel(); + let lp_builder = LowPrioritySceneBuilderThread { + rx: low_priority_scene_rx, + tx: scene_tx.clone(), + simulate_slow_ms: 0, + }; + + thread::Builder::new().name(lp_scene_thread_name.clone()).spawn(move || { + register_thread_with_profiler(lp_scene_thread_name.clone()); + if let Some(ref thread_listener) = *thread_listener_for_lp_scene_builder { + thread_listener.thread_started(&lp_scene_thread_name); + } + + let mut scene_builder = lp_builder; + scene_builder.run(); + + if let Some(ref thread_listener) = *thread_listener_for_lp_scene_builder { + thread_listener.thread_stopped(&lp_scene_thread_name); + } + })?; + + low_priority_scene_tx + } else { + scene_tx.clone() + }; + + let backend_blob_handler = blob_image_handler + .as_ref() + .map(|handler| handler.create_similar()); + + let rb_font_instances = font_instances.clone(); + let enable_multithreading = options.enable_multithreading; + let texture_cache_eviction_threshold_bytes = options.texture_cache_eviction_threshold_bytes; + let texture_cache_max_evictions_per_frame = options.texture_cache_max_evictions_per_frame; + thread::Builder::new().name(rb_thread_name.clone()).spawn(move || { + register_thread_with_profiler(rb_thread_name.clone()); + if let Some(ref thread_listener) = *thread_listener_for_render_backend { + thread_listener.thread_started(&rb_thread_name); + } + + let texture_cache = TextureCache::new( + max_texture_size, + max_texture_layers, + if config.global_enable_picture_caching { + tile_cache_sizes(config.testing) + } else { + &[] + }, + start_size, + color_cache_formats, + swizzle_settings, + texture_cache_eviction_threshold_bytes, + texture_cache_max_evictions_per_frame, + ); + + let glyph_cache = GlyphCache::new(max_glyph_cache_size); + + let mut resource_cache = ResourceCache::new( + texture_cache, + glyph_rasterizer, + glyph_cache, + rb_font_instances, + ); + + resource_cache.enable_multithreading(enable_multithreading); + + let mut backend = RenderBackend::new( + api_rx, + result_tx, + scene_tx, + low_priority_scene_tx, + backend_scene_tx, + scene_rx, + device_pixel_ratio, + resource_cache, + backend_notifier, + backend_blob_handler, + config, + sampler, + make_size_of_ops(), + debug_flags, + namespace_alloc_by_client, + ); + backend.run(backend_profile_counters); + if let Some(ref thread_listener) = *thread_listener_for_render_backend { + thread_listener.thread_stopped(&rb_thread_name); + } + })?; + + let debug_method = if !options.enable_gpu_markers { + // The GPU markers are disabled. + GpuDebugMethod::None + } else if device.supports_extension("GL_KHR_debug") { + GpuDebugMethod::KHR + } else if device.supports_extension("GL_EXT_debug_marker") { + GpuDebugMethod::MarkerEXT + } else { + println!("Warning: asking to enable_gpu_markers but no supporting extension was found"); + GpuDebugMethod::None + }; + + info!("using {:?}", debug_method); + + let gpu_profile = GpuProfiler::new(Rc::clone(device.rc_gl()), debug_method); + #[cfg(feature = "capture")] + let read_fbo = device.create_fbo(); + + let mut renderer = Renderer { + result_rx, + debug_server, + device, + active_documents: Vec::new(), + pending_texture_updates: Vec::new(), + pending_texture_cache_updates: false, + pending_native_surface_updates: Vec::new(), + pending_gpu_cache_updates: Vec::new(), + pending_gpu_cache_clear: false, + pending_shader_updates: Vec::new(), + shaders, + debug: LazyInitializedDebugRenderer::new(), + debug_flags: DebugFlags::empty(), + backend_profile_counters: BackendProfileCounters::new(), + profile_counters: RendererProfileCounters::new(), + resource_upload_time: 0, + gpu_cache_upload_time: 0, + profiler: Profiler::new(), + new_frame_indicator: ChangeIndicator::new(), + new_scene_indicator: ChangeIndicator::new(), + slow_frame_indicator: ChangeIndicator::new(), + slow_txn_indicator: ChangeIndicator::new(), + max_recorded_profiles: options.max_recorded_profiles, + clear_color: options.clear_color, + enable_clear_scissor: options.enable_clear_scissor, + enable_advanced_blend_barriers: !ext_blend_equation_advanced_coherent, + clear_caches_with_quads: options.clear_caches_with_quads, + last_time: 0, + gpu_profile, + vaos: RendererVAOs { + prim_vao, + blur_vao, + clip_vao, + border_vao, + scale_vao, + gradient_vao, + resolve_vao, + line_vao, + svg_filter_vao, + composite_vao, + clear_vao, + }, + vertex_data_textures, + current_vertex_data_textures: 0, + pipeline_info: PipelineInfo::default(), + dither_matrix_texture, + external_image_handler: None, + output_image_handler: None, + size_of_ops: make_size_of_ops(), + output_targets: FastHashMap::default(), + cpu_profiles: VecDeque::new(), + gpu_profiles: VecDeque::new(), + gpu_cache_texture, + gpu_cache_debug_chunks: Vec::new(), + gpu_cache_frame_id: FrameId::INVALID, + gpu_cache_overflow: false, + texture_cache_upload_pbo, + texture_resolver, + renderer_errors: Vec::new(), + async_frame_recorder: None, + async_screenshots: None, + #[cfg(feature = "capture")] + read_fbo, + #[cfg(feature = "replay")] + owned_external_images: FastHashMap::default(), + notifications: Vec::new(), + device_size: None, + zoom_debug_texture: None, + cursor_position: DeviceIntPoint::zero(), + shared_texture_cache_cleared: false, + documents_seen: FastHashSet::default(), + force_redraw: true, + compositor_config: options.compositor_config, + current_compositor_kind: compositor_kind, + allocated_native_surfaces: FastHashSet::default(), + debug_overlay_state: DebugOverlayState::new(), + prev_dirty_rect: DeviceRect::zero(), + }; + + // We initially set the flags to default and then now call set_debug_flags + // to ensure any potential transition when enabling a flag is run. + renderer.set_debug_flags(debug_flags); + + let sender = RenderApiSender::new(api_tx, blob_image_handler, font_instances); + Ok((renderer, sender)) + } + + pub fn device_size(&self) -> Option { + self.device_size + } + + /// Update the current position of the debug cursor. + pub fn set_cursor_position( + &mut self, + position: DeviceIntPoint, + ) { + self.cursor_position = position; + } + + pub fn get_max_texture_size(&self) -> i32 { + self.device.max_texture_size() + } + + pub fn get_graphics_api_info(&self) -> GraphicsApiInfo { + GraphicsApiInfo { + kind: GraphicsApi::OpenGL, + version: self.device.gl().get_string(gl::VERSION), + renderer: self.device.gl().get_string(gl::RENDERER), + } + } + + pub fn preferred_color_format(&self) -> ImageFormat { + self.device.preferred_color_formats().external + } + + pub fn optimal_texture_stride_alignment(&self, format: ImageFormat) -> usize { + self.device.optimal_pbo_stride().num_bytes(format).get() + } + + pub fn flush_pipeline_info(&mut self) -> PipelineInfo { + mem::replace(&mut self.pipeline_info, PipelineInfo::default()) + } + + /// Returns the Epoch of the current frame in a pipeline. + pub fn current_epoch(&self, document_id: DocumentId, pipeline_id: PipelineId) -> Option { + self.pipeline_info.epochs.get(&(pipeline_id, document_id)).cloned() + } + + /// Processes the result queue. + /// + /// Should be called before `render()`, as texture cache updates are done here. + pub fn update(&mut self) { + profile_scope!("update"); + // Pull any pending results and return the most recent. + while let Ok(msg) = self.result_rx.try_recv() { + match msg { + ResultMsg::PublishPipelineInfo(mut pipeline_info) => { + for ((pipeline_id, document_id), epoch) in pipeline_info.epochs { + self.pipeline_info.epochs.insert((pipeline_id, document_id), epoch); + } + self.pipeline_info.removed_pipelines.extend(pipeline_info.removed_pipelines.drain(..)); + } + ResultMsg::PublishDocument( + document_id, + doc, + resource_update_list, + profile_counters, + ) => { + if doc.is_new_scene { + self.new_scene_indicator.changed(); + } + + // Add a new document to the active set, expressed as a `Vec` in order + // to re-order based on `DocumentLayer` during rendering. + match self.active_documents.iter().position(|&(id, _)| id == document_id) { + Some(pos) => { + // If the document we are replacing must be drawn + // (in order to update the texture cache), issue + // a render just to off-screen targets. + if self.active_documents[pos].1.frame.must_be_drawn() { + let device_size = self.device_size; + self.render_impl(device_size).ok(); + } + + self.active_documents[pos].1 = doc; + } + None => self.active_documents.push((document_id, doc)), + } + + // IMPORTANT: The pending texture cache updates must be applied + // *after* the previous frame has been rendered above + // (if neceessary for a texture cache update). For + // an example of why this is required: + // 1) Previous frame contains a render task that + // targets Texture X. + // 2) New frame contains a texture cache update which + // frees Texture X. + // 3) bad stuff happens. + + //TODO: associate `document_id` with target window + self.pending_texture_cache_updates |= !resource_update_list.texture_updates.updates.is_empty(); + self.pending_texture_updates.push(resource_update_list.texture_updates); + self.pending_native_surface_updates.extend(resource_update_list.native_surface_updates); + self.backend_profile_counters = profile_counters; + self.documents_seen.insert(document_id); + } + ResultMsg::UpdateGpuCache(mut list) => { + if list.clear { + self.pending_gpu_cache_clear = true; + } + if list.clear { + self.gpu_cache_debug_chunks = Vec::new(); + } + for cmd in mem::replace(&mut list.debug_commands, Vec::new()) { + match cmd { + GpuCacheDebugCmd::Alloc(chunk) => { + let row = chunk.address.v as usize; + if row >= self.gpu_cache_debug_chunks.len() { + self.gpu_cache_debug_chunks.resize(row + 1, Vec::new()); + } + self.gpu_cache_debug_chunks[row].push(chunk); + }, + GpuCacheDebugCmd::Free(address) => { + let chunks = &mut self.gpu_cache_debug_chunks[address.v as usize]; + let pos = chunks.iter() + .position(|x| x.address == address).unwrap(); + chunks.remove(pos); + }, + } + } + self.pending_gpu_cache_updates.push(list); + } + ResultMsg::UpdateResources { + resource_updates, + memory_pressure, + } => { + if memory_pressure { + // If a memory pressure event arrives _after_ a new scene has + // been published that writes persistent targets (i.e. cached + // render tasks to the texture cache, or picture cache tiles) + // but _before_ the next update/render loop, those targets + // will not be updated due to the active_documents list being + // cleared at the end of this message. To work around that, + // if any of the existing documents have not rendered yet, and + // have picture/texture cache targets, force a render so that + // those targets are updated. + let must_be_drawn = self.active_documents + .iter() + .any(|(_, doc)| { + doc.frame.must_be_drawn() + }); + + if must_be_drawn { + let device_size = self.device_size; + self.render_impl(device_size).ok(); + } + } + + self.pending_texture_cache_updates |= !resource_updates.texture_updates.updates.is_empty(); + self.pending_texture_updates.push(resource_updates.texture_updates); + self.pending_native_surface_updates.extend(resource_updates.native_surface_updates); + self.device.begin_frame(); + + self.update_texture_cache(); + self.update_native_surfaces(); + + // Flush the render target pool on memory pressure. + // + // This needs to be separate from the block below because + // the device module asserts if we delete textures while + // not in a frame. + if memory_pressure { + self.texture_resolver.on_memory_pressure( + &mut self.device, + ); + } + + self.device.end_frame(); + // If we receive a `PublishDocument` message followed by this one + // within the same update we need to cancel the frame because we + // might have deleted the resources in use in the frame due to a + // memory pressure event. + if memory_pressure { + self.active_documents.clear(); + } + } + ResultMsg::AppendNotificationRequests(mut notifications) => { + // We need to know specifically if there are any pending + // TextureCacheUpdate updates in any of the entries in + // pending_texture_updates. They may simply be nops, which do not + // need to prevent issuing the notification, and if so, may not + // cause a timely frame render to occur to wake up any listeners. + if !self.pending_texture_cache_updates { + drain_filter( + &mut notifications, + |n| { n.when() == Checkpoint::FrameTexturesUpdated }, + |n| { n.notify(); }, + ); + } + self.notifications.append(&mut notifications); + } + ResultMsg::ForceRedraw => { + self.force_redraw = true; + } + ResultMsg::RefreshShader(path) => { + self.pending_shader_updates.push(path); + } + ResultMsg::DebugOutput(output) => match output { + DebugOutput::FetchDocuments(string) | + DebugOutput::FetchClipScrollTree(string) => { + self.debug_server.send(string); + } + #[cfg(feature = "capture")] + DebugOutput::SaveCapture(config, deferred) => { + self.save_capture(config, deferred); + } + #[cfg(feature = "replay")] + DebugOutput::LoadCapture(config, plain_externals) => { + self.active_documents.clear(); + self.load_capture(config, plain_externals); + } + }, + ResultMsg::DebugCommand(command) => { + self.handle_debug_command(command); + } + } + } + } + + #[cfg(not(feature = "debugger"))] + fn get_screenshot_for_debugger(&mut self) -> String { + // Avoid unused param warning. + let _ = &self.debug_server; + String::new() + } + + #[cfg(feature = "debugger")] + fn get_screenshot_for_debugger(&mut self) -> String { + use api::{ImageDescriptor, ImageDescriptorFlags}; + + let desc = ImageDescriptor::new(1024, 768, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE); + let data = self.device.read_pixels(&desc); + let screenshot = debug_server::Screenshot::new(desc.size, data); + + serde_json::to_string(&screenshot).unwrap() + } + + #[cfg(not(feature = "debugger"))] + fn get_passes_for_debugger(&self) -> String { + // Avoid unused param warning. + let _ = &self.debug_server; + String::new() + } + + #[cfg(feature = "debugger")] + fn debug_alpha_target(target: &AlphaRenderTarget) -> debug_server::Target { + let mut debug_target = debug_server::Target::new("A8"); + + debug_target.add( + debug_server::BatchKind::Cache, + "Scalings", + target.scalings.len(), + ); + debug_target.add( + debug_server::BatchKind::Cache, + "Zero Clears", + target.zero_clears.len(), + ); + debug_target.add( + debug_server::BatchKind::Cache, + "One Clears", + target.one_clears.len(), + ); + debug_target.add( + debug_server::BatchKind::Clip, + "BoxShadows [p]", + target.clip_batcher.primary_clips.box_shadows.len(), + ); + debug_target.add( + debug_server::BatchKind::Clip, + "BoxShadows [s]", + target.clip_batcher.secondary_clips.box_shadows.len(), + ); + debug_target.add( + debug_server::BatchKind::Cache, + "Vertical Blur", + target.vertical_blurs.len(), + ); + debug_target.add( + debug_server::BatchKind::Cache, + "Horizontal Blur", + target.horizontal_blurs.len(), + ); + debug_target.add( + debug_server::BatchKind::Clip, + "Slow Rectangles [p]", + target.clip_batcher.primary_clips.slow_rectangles.len(), + ); + debug_target.add( + debug_server::BatchKind::Clip, + "Fast Rectangles [p]", + target.clip_batcher.primary_clips.fast_rectangles.len(), + ); + debug_target.add( + debug_server::BatchKind::Clip, + "Slow Rectangles [s]", + target.clip_batcher.secondary_clips.slow_rectangles.len(), + ); + debug_target.add( + debug_server::BatchKind::Clip, + "Fast Rectangles [s]", + target.clip_batcher.secondary_clips.fast_rectangles.len(), + ); + for (_, items) in target.clip_batcher.primary_clips.images.iter() { + debug_target.add(debug_server::BatchKind::Clip, "Image mask [p]", items.len()); + } + for (_, items) in target.clip_batcher.secondary_clips.images.iter() { + debug_target.add(debug_server::BatchKind::Clip, "Image mask [s]", items.len()); + } + + debug_target + } + + #[cfg(feature = "debugger")] + fn debug_color_target(target: &ColorRenderTarget) -> debug_server::Target { + let mut debug_target = debug_server::Target::new("RGBA8"); + + debug_target.add( + debug_server::BatchKind::Cache, + "Scalings", + target.scalings.len(), + ); + debug_target.add( + debug_server::BatchKind::Cache, + "Readbacks", + target.readbacks.len(), + ); + debug_target.add( + debug_server::BatchKind::Cache, + "Vertical Blur", + target.vertical_blurs.len(), + ); + debug_target.add( + debug_server::BatchKind::Cache, + "Horizontal Blur", + target.horizontal_blurs.len(), + ); + debug_target.add( + debug_server::BatchKind::Cache, + "SVG Filters", + target.svg_filters.iter().map(|(_, batch)| batch.len()).sum(), + ); + + for alpha_batch_container in &target.alpha_batch_containers { + for batch in alpha_batch_container.opaque_batches.iter().rev() { + debug_target.add( + debug_server::BatchKind::Opaque, + batch.key.kind.debug_name(), + batch.instances.len(), + ); + } + + for batch in &alpha_batch_container.alpha_batches { + debug_target.add( + debug_server::BatchKind::Alpha, + batch.key.kind.debug_name(), + batch.instances.len(), + ); + } + } + + debug_target + } + + #[cfg(feature = "debugger")] + fn debug_texture_cache_target(target: &TextureCacheRenderTarget) -> debug_server::Target { + let mut debug_target = debug_server::Target::new("Texture Cache"); + + debug_target.add( + debug_server::BatchKind::Cache, + "Horizontal Blur", + target.horizontal_blurs.len(), + ); + + debug_target + } + + #[cfg(feature = "debugger")] + fn get_passes_for_debugger(&self) -> String { + let mut debug_passes = debug_server::PassList::new(); + + for &(_, ref render_doc) in &self.active_documents { + for pass in &render_doc.frame.passes { + let mut debug_targets = Vec::new(); + match pass.kind { + RenderPassKind::MainFramebuffer { ref main_target, .. } => { + debug_targets.push(Self::debug_color_target(main_target)); + } + RenderPassKind::OffScreen { ref alpha, ref color, ref texture_cache, .. } => { + debug_targets.extend(alpha.targets.iter().map(Self::debug_alpha_target)); + debug_targets.extend(color.targets.iter().map(Self::debug_color_target)); + debug_targets.extend(texture_cache.iter().map(|(_, target)| Self::debug_texture_cache_target(target))); + } + } + + debug_passes.add(debug_server::Pass { targets: debug_targets }); + } + } + + serde_json::to_string(&debug_passes).unwrap() + } + + #[cfg(not(feature = "debugger"))] + fn get_render_tasks_for_debugger(&self) -> String { + String::new() + } + + #[cfg(feature = "debugger")] + fn get_render_tasks_for_debugger(&self) -> String { + let mut debug_root = debug_server::RenderTaskList::new(); + + for &(_, ref render_doc) in &self.active_documents { + let debug_node = debug_server::TreeNode::new("document render tasks"); + let mut builder = debug_server::TreeNodeBuilder::new(debug_node); + + let render_tasks = &render_doc.frame.render_tasks; + match render_tasks.tasks.first() { + Some(main_task) => main_task.print_with(&mut builder, render_tasks), + None => continue, + }; + + debug_root.add(builder.build()); + } + + serde_json::to_string(&debug_root).unwrap() + } + + fn handle_debug_command(&mut self, command: DebugCommand) { + match command { + DebugCommand::EnableDualSourceBlending(_) | + DebugCommand::SetPictureTileSize(_) => { + panic!("Should be handled by render backend"); + } + DebugCommand::FetchDocuments | + DebugCommand::FetchClipScrollTree => {} + DebugCommand::FetchRenderTasks => { + let json = self.get_render_tasks_for_debugger(); + self.debug_server.send(json); + } + DebugCommand::FetchPasses => { + let json = self.get_passes_for_debugger(); + self.debug_server.send(json); + } + DebugCommand::FetchScreenshot => { + let json = self.get_screenshot_for_debugger(); + self.debug_server.send(json); + } + DebugCommand::SaveCapture(..) | + DebugCommand::LoadCapture(..) | + DebugCommand::StartCaptureSequence(..) | + DebugCommand::StopCaptureSequence => { + panic!("Capture commands are not welcome here! Did you build with 'capture' feature?") + } + DebugCommand::ClearCaches(_) + | DebugCommand::SimulateLongSceneBuild(_) + | DebugCommand::SimulateLongLowPrioritySceneBuild(_) + | DebugCommand::EnableNativeCompositor(_) + | DebugCommand::SetBatchingLookback(_) + | DebugCommand::EnableMultithreading(_) => {} + DebugCommand::InvalidateGpuCache => { + match self.gpu_cache_texture.bus { + GpuCacheBus::PixelBuffer { ref mut rows, .. } => { + info!("Invalidating GPU caches"); + for row in rows { + row.add_dirty(0, MAX_VERTEX_TEXTURE_WIDTH); + } + } + GpuCacheBus::Scatter { .. } => { + warn!("Unable to invalidate scattered GPU cache"); + } + } + } + DebugCommand::SetFlags(flags) => { + self.set_debug_flags(flags); + } + } + } + + /// Set a callback for handling external images. + pub fn set_external_image_handler(&mut self, handler: Box) { + self.external_image_handler = Some(handler); + } + + /// Set a callback for handling external outputs. + pub fn set_output_image_handler(&mut self, handler: Box) { + self.output_image_handler = Some(handler); + } + + /// Retrieve (and clear) the current list of recorded frame profiles. + pub fn get_frame_profiles(&mut self) -> (Vec, Vec) { + let cpu_profiles = self.cpu_profiles.drain(..).collect(); + let gpu_profiles = self.gpu_profiles.drain(..).collect(); + (cpu_profiles, gpu_profiles) + } + + /// Reset the current partial present state. This forces the entire framebuffer + /// to be refreshed next time `render` is called. + pub fn force_redraw(&mut self) { + self.force_redraw = true; + } + + /// Renders the current frame. + /// + /// A Frame is supplied by calling [`generate_frame()`][webrender_api::Transaction::generate_frame]. + pub fn render( + &mut self, + device_size: DeviceIntSize, + ) -> Result> { + self.device_size = Some(device_size); + + let result = self.render_impl(Some(device_size)); + + drain_filter( + &mut self.notifications, + |n| { n.when() == Checkpoint::FrameRendered }, + |n| { n.notify(); }, + ); + + // This is the end of the rendering pipeline. If some notifications are is still there, + // just clear them and they will autimatically fire the Checkpoint::TransactionDropped + // event. Otherwise they would just pile up in this vector forever. + self.notifications.clear(); + + tracy_frame_marker!(); + + result + } + + /// Update the state of any debug / profiler overlays. This is currently only needed + /// when running with the native compositor enabled. + fn update_debug_overlay(&mut self, framebuffer_size: DeviceIntSize) { + // If any of the following debug flags are set, something will be drawn on the debug overlay. + self.debug_overlay_state.is_enabled = self.debug_flags.intersects( + DebugFlags::PROFILER_DBG | + DebugFlags::RENDER_TARGET_DBG | + DebugFlags::TEXTURE_CACHE_DBG | + DebugFlags::EPOCHS | + DebugFlags::NEW_FRAME_INDICATOR | + DebugFlags::NEW_SCENE_INDICATOR | + DebugFlags::GPU_CACHE_DBG | + DebugFlags::SLOW_FRAME_INDICATOR | + DebugFlags::PICTURE_CACHING_DBG | + DebugFlags::PRIMITIVE_DBG | + DebugFlags::ZOOM_DBG + ); + + // Update the debug overlay surface, if we are running in native compositor mode. + if let CompositorKind::Native { .. } = self.current_compositor_kind { + let compositor = self.compositor_config.compositor().unwrap(); + + // If there is a current surface, destroy it if we don't need it for this frame, or if + // the size has changed. + if let Some(current_size) = self.debug_overlay_state.current_size { + if !self.debug_overlay_state.is_enabled || current_size != framebuffer_size { + compositor.destroy_surface(NativeSurfaceId::DEBUG_OVERLAY); + self.debug_overlay_state.current_size = None; + } + } + + // Allocate a new surface, if we need it and there isn't one. + if self.debug_overlay_state.is_enabled && self.debug_overlay_state.current_size.is_none() { + compositor.create_surface( + NativeSurfaceId::DEBUG_OVERLAY, + DeviceIntPoint::zero(), + framebuffer_size, + false, + ); + compositor.create_tile( + NativeTileId::DEBUG_OVERLAY, + ); + self.debug_overlay_state.current_size = Some(framebuffer_size); + } + } + } + + /// Bind a draw target for the debug / profiler overlays, if required. + fn bind_debug_overlay(&mut self) { + // Debug overlay setup are only required in native compositing mode + if self.debug_overlay_state.is_enabled { + if let CompositorKind::Native { .. } = self.current_compositor_kind { + let compositor = self.compositor_config.compositor().unwrap(); + let surface_size = self.debug_overlay_state.current_size.unwrap(); + + // Bind the native surface + let surface_info = compositor.bind( + NativeTileId::DEBUG_OVERLAY, + DeviceIntRect::new( + DeviceIntPoint::zero(), + surface_size, + ), + DeviceIntRect::new( + DeviceIntPoint::zero(), + surface_size, + ), + ); + + // Bind the native surface to current FBO target + let draw_target = DrawTarget::NativeSurface { + offset: surface_info.origin, + external_fbo_id: surface_info.fbo_id, + dimensions: surface_size, + }; + self.device.bind_draw_target(draw_target); + + // When native compositing, clear the debug overlay each frame. + self.device.clear_target( + Some([0.0, 0.0, 0.0, 0.0]), + Some(1.0), + None, + ); + } + } + } + + /// Unbind the draw target for debug / profiler overlays, if required. + fn unbind_debug_overlay(&mut self) { + // Debug overlay setup are only required in native compositing mode + if self.debug_overlay_state.is_enabled { + if let CompositorKind::Native { .. } = self.current_compositor_kind { + let compositor = self.compositor_config.compositor().unwrap(); + // Unbind the draw target and add it to the visual tree to be composited + compositor.unbind(); + + compositor.add_surface( + NativeSurfaceId::DEBUG_OVERLAY, + DeviceIntPoint::zero(), + DeviceIntRect::new( + DeviceIntPoint::zero(), + self.debug_overlay_state.current_size.unwrap(), + ), + ); + } + } + } + + // If device_size is None, don't render + // to the main frame buffer. This is useful + // to update texture cache render tasks but + // avoid doing a full frame render. + fn render_impl( + &mut self, + device_size: Option, + ) -> Result> { + profile_scope!("render"); + let mut results = RenderResults::default(); + if self.active_documents.is_empty() { + self.last_time = precise_time_ns(); + return Ok(results); + } + + let compositor_kind = self.active_documents[0].1.frame.composite_state.compositor_kind; + // CompositorKind is updated + if self.current_compositor_kind != compositor_kind { + let enable = match (self.current_compositor_kind, compositor_kind) { + (CompositorKind::Native { .. }, CompositorKind::Draw { .. }) => { + if self.debug_overlay_state.current_size.is_some() { + self.compositor_config + .compositor() + .unwrap() + .destroy_surface(NativeSurfaceId::DEBUG_OVERLAY); + self.debug_overlay_state.current_size = None; + } + false + } + (CompositorKind::Draw { .. }, CompositorKind::Native { .. }) => { + true + } + (_, _) => { + unreachable!(); + } + }; + + self.compositor_config + .compositor() + .unwrap() + .enable_native_compositor(enable); + self.current_compositor_kind = compositor_kind; + } + + let mut frame_profiles = Vec::new(); + let mut profile_timers = RendererProfileTimers::new(); + + // The texture resolver scope should be outside of any rendering, including + // debug rendering. This ensures that when we return render targets to the + // pool via glInvalidateFramebuffer, we don't do any debug rendering after + // that point. Otherwise, the bind / invalidate / bind logic trips up the + // render pass logic in tiled / mobile GPUs, resulting in an extra copy / + // resolve step when the debug overlay is enabled. + self.texture_resolver.begin_frame(); + + let profile_samplers = { + let _gm = self.gpu_profile.start_marker("build samples"); + // Block CPU waiting for last frame's GPU profiles to arrive. + // In general this shouldn't block unless heavily GPU limited. + let (gpu_frame_id, timers, samplers) = self.gpu_profile.build_samples(); + + if self.max_recorded_profiles > 0 { + while self.gpu_profiles.len() >= self.max_recorded_profiles { + self.gpu_profiles.pop_front(); + } + self.gpu_profiles + .push_back(GpuProfile::new(gpu_frame_id, &timers)); + } + profile_timers.gpu_samples = timers; + samplers + }; + + + let cpu_frame_id = profile_timers.cpu_time.profile(|| { + let _gm = self.gpu_profile.start_marker("begin frame"); + let frame_id = self.device.begin_frame(); + self.gpu_profile.begin_frame(frame_id); + + self.device.disable_scissor(); + self.device.disable_depth(); + self.set_blend(false, FramebufferKind::Main); + //self.update_shaders(); + + self.update_texture_cache(); + self.update_native_surfaces(); + + frame_id + }); + + // Inform the client that we are starting a composition transaction if native + // compositing is enabled. This needs to be done early in the frame, so that + // we can create debug overlays after drawing the main surfaces. + if let CompositorKind::Native { .. } = self.current_compositor_kind { + let compositor = self.compositor_config.compositor().unwrap(); + compositor.begin_frame(); + } + + profile_timers.cpu_time.profile(|| { + //Note: another borrowck dance + let mut active_documents = mem::replace(&mut self.active_documents, Vec::default()); + // sort by the document layer id + active_documents.sort_by_key(|&(_, ref render_doc)| render_doc.frame.layer); + + #[cfg(feature = "replay")] + self.texture_resolver.external_images.extend( + self.owned_external_images.iter().map(|(key, value)| (*key, value.clone())) + ); + + let last_document_index = active_documents.len() - 1; + for (doc_index, (document_id, RenderedDocument { ref mut frame, .. })) in active_documents.iter_mut().enumerate() { + assert!(self.current_compositor_kind == frame.composite_state.compositor_kind); + + if self.shared_texture_cache_cleared { + assert!(self.documents_seen.contains(&document_id), + "Cleared texture cache without sending new document frame."); + } + + frame.profile_counters.reset_targets(); + if let Err(e) = self.prepare_gpu_cache(frame) { + self.renderer_errors.push(e); + continue; + } + assert!(frame.gpu_cache_frame_id <= self.gpu_cache_frame_id, + "Received frame depends on a later GPU cache epoch ({:?}) than one we received last via `UpdateGpuCache` ({:?})", + frame.gpu_cache_frame_id, self.gpu_cache_frame_id); + + { + profile_scope!("gl.flush"); + self.device.gl().flush(); // early start on gpu cache updates + } + + self.draw_frame( + frame, + device_size, + cpu_frame_id, + &mut results, + doc_index == 0, + ); + + // Profile marker for the number of invalidated picture cache + if thread_is_being_profiled() { + let num_invalidated = self.profile_counters.rendered_picture_cache_tiles.get_accum(); + let message = format!("NumPictureCacheInvalidated: {}", num_invalidated); + add_event_marker(&(CString::new(message).unwrap())); + } + + if device_size.is_some() { + self.draw_frame_debug_items(&frame.debug_items); + } + if self.debug_flags.contains(DebugFlags::PROFILER_DBG) { + frame_profiles.push(frame.profile_counters.clone()); + } + + let dirty_regions = + mem::replace(&mut frame.recorded_dirty_regions, Vec::new()); + results.recorded_dirty_regions.extend(dirty_regions); + + // If we're the last document, don't call end_pass here, because we'll + // be moving on to drawing the debug overlays. See the comment above + // the end_pass call in draw_frame about debug draw overlays + // for a bit more context. + if doc_index != last_document_index { + self.texture_resolver.end_pass(&mut self.device, None, None); + } + } + + self.unlock_external_images(); + self.active_documents = active_documents; + + let _gm = self.gpu_profile.start_marker("end frame"); + self.gpu_profile.end_frame(); + }); + + if let Some(device_size) = device_size { + // Update the state of the debug overlay surface, ensuring that + // the compositor mode has a suitable surface to draw to, if required. + self.update_debug_overlay(device_size); + + // Bind a surface to draw the debug / profiler information to. + self.bind_debug_overlay(); + + self.draw_render_target_debug(device_size); + self.draw_texture_cache_debug(device_size); + self.draw_gpu_cache_debug(device_size); + self.draw_zoom_debug(device_size); + self.draw_epoch_debug(); + } + + let current_time = precise_time_ns(); + if device_size.is_some() { + let ns = current_time - self.last_time; + self.profile_counters.frame_time.set(ns); + } + + let frame_cpu_time_ns = self.backend_profile_counters.total_time.get() + + profile_timers.cpu_time.get(); + let frame_cpu_time_ms = frame_cpu_time_ns as f64 / 1000000.0; + if frame_cpu_time_ms > 16.0 { + self.slow_frame_indicator.changed(); + } + + if self.backend_profile_counters.scene_changed { + let txn_time_ns = self.backend_profile_counters.txn.total_send_time.get() + + self.backend_profile_counters.txn.display_list_build_time.get() + + self.backend_profile_counters.txn.scene_build_time.get(); + let txn_time_ms = txn_time_ns as f64 / 1000000.0; + if txn_time_ms > 100.0 { + self.slow_txn_indicator.changed(); + } + } + + if self.max_recorded_profiles > 0 { + while self.cpu_profiles.len() >= self.max_recorded_profiles { + self.cpu_profiles.pop_front(); + } + let cpu_profile = CpuProfile::new( + cpu_frame_id, + self.backend_profile_counters.total_time.get(), + profile_timers.cpu_time.get(), + self.profile_counters.draw_calls.get(), + ); + self.cpu_profiles.push_back(cpu_profile); + } + + if self.debug_flags.contains(DebugFlags::PROFILER_DBG) { + if let Some(device_size) = device_size { + //TODO: take device/pixel ratio into equation? + if let Some(debug_renderer) = self.debug.get_mut(&mut self.device) { + let style = if self.debug_flags.contains(DebugFlags::SMART_PROFILER) { + ProfileStyle::Smart + } else if self.debug_flags.contains(DebugFlags::COMPACT_PROFILER) { + ProfileStyle::Compact + } else { + ProfileStyle::Full + }; + + let screen_fraction = 1.0 / device_size.to_f32().area(); + self.profiler.draw_profile( + &frame_profiles, + &self.backend_profile_counters, + &self.profile_counters, + &mut profile_timers, + &profile_samplers, + screen_fraction, + debug_renderer, + style, + ); + } + } + } + + let mut x = 0.0; + if self.debug_flags.contains(DebugFlags::NEW_FRAME_INDICATOR) { + if let Some(debug_renderer) = self.debug.get_mut(&mut self.device) { + self.new_frame_indicator.changed(); + self.new_frame_indicator.draw( + x, 0.0, + ColorU::new(0, 110, 220, 255), + debug_renderer, + ); + x += ChangeIndicator::width(); + } + } + + if self.debug_flags.contains(DebugFlags::NEW_SCENE_INDICATOR) { + if let Some(debug_renderer) = self.debug.get_mut(&mut self.device) { + self.new_scene_indicator.draw( + x, 0.0, + ColorU::new(0, 220, 110, 255), + debug_renderer, + ); + x += ChangeIndicator::width(); + } + } + + if self.debug_flags.contains(DebugFlags::SLOW_FRAME_INDICATOR) { + if let Some(debug_renderer) = self.debug.get_mut(&mut self.device) { + self.slow_txn_indicator.draw( + x, 0.0, + ColorU::new(250, 80, 80, 255), + debug_renderer, + ); + self.slow_frame_indicator.draw( + x, 10.0, + ColorU::new(220, 30, 10, 255), + debug_renderer, + ); + } + } + + if self.debug_flags.contains(DebugFlags::ECHO_DRIVER_MESSAGES) { + self.device.echo_driver_messages(); + } + + results.stats.texture_upload_kb = self.profile_counters.texture_data_uploaded.get(); + self.backend_profile_counters.reset(); + self.profile_counters.reset(); + self.profile_counters.frame_counter.inc(); + results.stats.resource_upload_time = self.resource_upload_time; + self.resource_upload_time = 0; + results.stats.gpu_cache_upload_time = self.gpu_cache_upload_time; + self.gpu_cache_upload_time = 0; + + profile_timers.cpu_time.profile(|| { + if let Some(debug_renderer) = self.debug.try_get_mut() { + let small_screen = self.debug_flags.contains(DebugFlags::SMALL_SCREEN); + let scale = if small_screen { 1.6 } else { 1.0 }; + // TODO(gw): Tidy this up so that compositor config integrates better + // with the (non-compositor) surface y-flip options. + let surface_origin_is_top_left = match self.current_compositor_kind { + CompositorKind::Native { .. } => true, + CompositorKind::Draw { .. } => self.device.surface_origin_is_top_left(), + }; + debug_renderer.render( + &mut self.device, + device_size, + scale, + surface_origin_is_top_left, + ); + } + // See comment for texture_resolver.begin_frame() for explanation + // of why this must be done after all rendering, including debug + // overlays. The end_frame() call implicitly calls end_pass(), which + // should ensure any left over render targets get invalidated and + // returned to the pool correctly. + self.texture_resolver.end_frame(&mut self.device, cpu_frame_id); + self.device.end_frame(); + }); + + if device_size.is_some() { + self.last_time = current_time; + + // Unbind the target for the debug overlay. No debug or profiler drawing + // can occur afer this point. + self.unbind_debug_overlay(); + } + + // Inform the client that we are finished this composition transaction if native + // compositing is enabled. This must be called after any debug / profiling compositor + // surfaces have been drawn and added to the visual tree. + if let CompositorKind::Native { .. } = self.current_compositor_kind { + profile_scope!("compositor.end_frame"); + let compositor = self.compositor_config.compositor().unwrap(); + compositor.end_frame(); + } + + self.documents_seen.clear(); + self.shared_texture_cache_cleared = false; + + if self.renderer_errors.is_empty() { + Ok(results) + } else { + Err(mem::replace(&mut self.renderer_errors, Vec::new())) + } + } + + fn update_gpu_cache(&mut self) { + let _gm = self.gpu_profile.start_marker("gpu cache update"); + + // For an artificial stress test of GPU cache resizing, + // always pass an extra update list with at least one block in it. + let gpu_cache_height = self.gpu_cache_texture.get_height(); + if gpu_cache_height != 0 && GPU_CACHE_RESIZE_TEST { + self.pending_gpu_cache_updates.push(GpuCacheUpdateList { + frame_id: FrameId::INVALID, + clear: false, + height: gpu_cache_height, + blocks: vec![[1f32; 4].into()], + updates: Vec::new(), + debug_commands: Vec::new(), + }); + } + + let (updated_blocks, max_requested_height) = self + .pending_gpu_cache_updates + .iter() + .fold((0, gpu_cache_height), |(count, height), list| { + (count + list.blocks.len(), cmp::max(height, list.height)) + }); + + if max_requested_height > self.get_max_texture_size() && !self.gpu_cache_overflow { + self.gpu_cache_overflow = true; + self.renderer_errors.push(RendererError::MaxTextureSize); + } + + // Note: if we decide to switch to scatter-style GPU cache update + // permanently, we can have this code nicer with `BufferUploader` kind + // of helper, similarly to how `TextureUploader` API is used. + self.gpu_cache_texture.prepare_for_updates( + &mut self.device, + updated_blocks, + max_requested_height, + ); + + for update_list in self.pending_gpu_cache_updates.drain(..) { + assert!(update_list.height <= max_requested_height); + if update_list.frame_id > self.gpu_cache_frame_id { + self.gpu_cache_frame_id = update_list.frame_id + } + self.gpu_cache_texture + .update(&mut self.device, &update_list); + } + + let mut upload_time = TimeProfileCounter::new("GPU cache upload time", false, Some(0.0..2.0)); + let updated_rows = upload_time.profile(|| { + self.gpu_cache_texture.flush(&mut self.device) + }); + self.gpu_cache_upload_time += upload_time.get(); + + let counters = &mut self.backend_profile_counters.resources.gpu_cache; + counters.updated_rows.set(updated_rows); + counters.updated_blocks.set(updated_blocks); + } + + fn prepare_gpu_cache(&mut self, frame: &Frame) -> Result<(), RendererError> { + if self.pending_gpu_cache_clear { + let use_scatter = + matches!(self.gpu_cache_texture.bus, GpuCacheBus::Scatter { .. }); + let new_cache = GpuCacheTexture::new(&mut self.device, use_scatter)?; + let old_cache = mem::replace(&mut self.gpu_cache_texture, new_cache); + old_cache.deinit(&mut self.device); + self.pending_gpu_cache_clear = false; + } + + let deferred_update_list = self.update_deferred_resolves(&frame.deferred_resolves); + self.pending_gpu_cache_updates.extend(deferred_update_list); + + self.update_gpu_cache(); + + // Note: the texture might have changed during the `update`, + // so we need to bind it here. + self.device.bind_texture( + TextureSampler::GpuCache, + self.gpu_cache_texture.texture.as_ref().unwrap(), + Swizzle::default(), + ); + + Ok(()) + } + + fn update_texture_cache(&mut self) { + profile_scope!("update_texture_cache"); + + let _gm = self.gpu_profile.start_marker("texture cache update"); + let mut pending_texture_updates = mem::replace(&mut self.pending_texture_updates, vec![]); + self.pending_texture_cache_updates = false; + + let mut upload_time = TimeProfileCounter::new("Resource upload time", false, Some(0.0..2.0)); + upload_time.profile(|| { + for update_list in pending_texture_updates.drain(..) { + for allocation in update_list.allocations { + match allocation.kind { + TextureCacheAllocationKind::Alloc(_) => add_event_marker(c_str!("TextureCacheAlloc")), + TextureCacheAllocationKind::Realloc(_) => add_event_marker(c_str!("TextureCacheRealloc")), + TextureCacheAllocationKind::Reset(_) => add_event_marker(c_str!("TextureCacheReset")), + TextureCacheAllocationKind::Free => add_event_marker(c_str!("TextureCacheFree")), + }; + let old = match allocation.kind { + TextureCacheAllocationKind::Alloc(ref info) | + TextureCacheAllocationKind::Realloc(ref info) | + TextureCacheAllocationKind::Reset(ref info) => { + // Create a new native texture, as requested by the texture cache. + // + // Ensure no PBO is bound when creating the texture storage, + // or GL will attempt to read data from there. + let mut texture = self.device.create_texture( + TextureTarget::Array, + info.format, + info.width, + info.height, + info.filter, + // This needs to be a render target because some render + // tasks get rendered into the texture cache. + Some(RenderTargetInfo { has_depth: info.has_depth }), + info.layer_count, + ); + + if info.is_shared_cache { + texture.flags_mut() + .insert(TextureFlags::IS_SHARED_TEXTURE_CACHE); + + // Textures in the cache generally don't need to be cleared, + // but we do so if the debug display is active to make it + // easier to identify unallocated regions. + if self.debug_flags.contains(DebugFlags::TEXTURE_CACHE_DBG) { + self.clear_texture(&texture, TEXTURE_CACHE_DBG_CLEAR_COLOR); + } + } + + self.texture_resolver.texture_cache_map.insert(allocation.id, texture) + } + TextureCacheAllocationKind::Free => { + self.texture_resolver.texture_cache_map.remove(&allocation.id) + } + }; + + match allocation.kind { + TextureCacheAllocationKind::Alloc(_) => { + assert!(old.is_none(), "Renderer and backend disagree!"); + } + TextureCacheAllocationKind::Realloc(_) => { + self.device.blit_renderable_texture( + self.texture_resolver.texture_cache_map.get_mut(&allocation.id).unwrap(), + old.as_ref().unwrap(), + ); + } + TextureCacheAllocationKind::Reset(_) | + TextureCacheAllocationKind::Free => { + assert!(old.is_some(), "Renderer and backend disagree!"); + } + } + + if let Some(old) = old { + self.device.delete_texture(old); + } + } + + for (texture_id, updates) in update_list.updates { + let texture = &self.texture_resolver.texture_cache_map[&texture_id]; + let device = &mut self.device; + + // Calculate the total size of buffer required to upload all updates. + let required_size = updates.iter().map(|update| { + // Perform any debug clears now. As this requires a mutable borrow of device, + // it must be done before all the updates which require a TextureUploader. + if let TextureUpdateSource::DebugClear = update.source { + let draw_target = DrawTarget::from_texture( + texture, + update.layer_index as usize, + false, + ); + device.bind_draw_target(draw_target); + device.clear_target( + Some(TEXTURE_CACHE_DBG_CLEAR_COLOR), + None, + Some(draw_target.to_framebuffer_rect(update.rect.to_i32())) + ); + + 0 + } else { + let (upload_size, _) = device.required_upload_size_and_stride( + update.rect.size, + texture.get_format(), + ); + upload_size + } + }).sum(); + + if required_size == 0 { + continue; + } + + // For best performance we use a single TextureUploader for all uploads. + // Using individual TextureUploaders was causing performance issues on some drivers + // due to allocating too many PBOs. + let mut uploader = device.upload_texture( + texture, + &self.texture_cache_upload_pbo, + required_size + ); + + for update in updates { + let TextureCacheUpdate { rect, stride, offset, layer_index, format_override, source } = update; + + let bytes_uploaded = match source { + TextureUpdateSource::Bytes { data } => { + let data = &data[offset as usize ..]; + uploader.upload( + rect, + layer_index, + stride, + format_override, + data.as_ptr(), + data.len(), + ) + } + TextureUpdateSource::External { id, channel_index } => { + let handler = self.external_image_handler + .as_mut() + .expect("Found external image, but no handler set!"); + // The filter is only relevant for NativeTexture external images. + let dummy_data; + let data = match handler.lock(id, channel_index, ImageRendering::Auto).source { + ExternalImageSource::RawData(data) => { + &data[offset as usize ..] + } + ExternalImageSource::Invalid => { + // Create a local buffer to fill the pbo. + let bpp = texture.get_format().bytes_per_pixel(); + let width = stride.unwrap_or(rect.size.width * bpp); + let total_size = width * rect.size.height; + // WR haven't support RGBAF32 format in texture_cache, so + // we use u8 type here. + dummy_data = vec![0xFFu8; total_size as usize]; + &dummy_data + } + ExternalImageSource::NativeTexture(eid) => { + panic!("Unexpected external texture {:?} for the texture cache update of {:?}", eid, id); + } + }; + let size = uploader.upload( + rect, + layer_index, + stride, + format_override, + data.as_ptr(), + data.len() + ); + handler.unlock(id, channel_index); + size + } + TextureUpdateSource::DebugClear => { + // DebugClear updates are handled separately. + 0 + } + }; + self.profile_counters.texture_data_uploaded.add(bytes_uploaded >> 10); + } + } + + if update_list.clears_shared_cache { + self.shared_texture_cache_cleared = true; + } + } + + drain_filter( + &mut self.notifications, + |n| { n.when() == Checkpoint::FrameTexturesUpdated }, + |n| { n.notify(); }, + ); + }); + self.resource_upload_time += upload_time.get(); + } + + pub(crate) fn draw_instanced_batch( + &mut self, + data: &[T], + vertex_array_kind: VertexArrayKind, + textures: &BatchTextures, + stats: &mut RendererStats, + ) { + let mut swizzles = [Swizzle::default(); 3]; + for i in 0 .. textures.colors.len() { + let swizzle = self.texture_resolver.bind( + &textures.colors[i], + TextureSampler::color(i), + &mut self.device, + ); + if cfg!(debug_assertions) { + swizzles[i] = swizzle; + for j in 0 .. i { + if textures.colors[j] == textures.colors[i] && swizzles[j] != swizzle { + error!("Swizzling conflict in {:?}", textures); + } + } + } + } + + // TODO: this probably isn't the best place for this. + if let Some(ref texture) = self.dither_matrix_texture { + self.device.bind_texture(TextureSampler::Dither, texture, Swizzle::default()); + } + + self.draw_instanced_batch_with_previously_bound_textures(data, vertex_array_kind, stats) + } + + pub(crate) fn draw_instanced_batch_with_previously_bound_textures( + &mut self, + data: &[T], + vertex_array_kind: VertexArrayKind, + stats: &mut RendererStats, + ) { + // If we end up with an empty draw call here, that means we have + // probably introduced unnecessary batch breaks during frame + // building - so we should be catching this earlier and removing + // the batch. + debug_assert!(!data.is_empty()); + + let vao = get_vao(vertex_array_kind, &self.vaos); + + self.device.bind_vao(vao); + + let batched = !self.debug_flags.contains(DebugFlags::DISABLE_BATCHING); + + if batched { + self.device + .update_vao_instances(vao, data, VertexUsageHint::Stream); + self.device + .draw_indexed_triangles_instanced_u16(6, data.len() as i32); + self.profile_counters.draw_calls.inc(); + stats.total_draw_calls += 1; + } else { + for i in 0 .. data.len() { + self.device + .update_vao_instances(vao, &data[i .. i + 1], VertexUsageHint::Stream); + self.device.draw_triangles_u16(0, 6); + self.profile_counters.draw_calls.inc(); + stats.total_draw_calls += 1; + } + } + + self.profile_counters.vertices.add(6 * data.len()); + } + + fn handle_readback_composite( + &mut self, + draw_target: DrawTarget, + uses_scissor: bool, + source: &RenderTask, + backdrop: &RenderTask, + readback: &RenderTask, + ) { + if uses_scissor { + self.device.disable_scissor(); + } + + let (cache_texture, _) = self.texture_resolver + .resolve(&TextureSource::PrevPassColor) + .unwrap(); + + // Before submitting the composite batch, do the + // framebuffer readbacks that are needed for each + // composite operation in this batch. + let (readback_rect, readback_layer) = readback.get_target_rect(); + let (backdrop_rect, _) = backdrop.get_target_rect(); + let (backdrop_screen_origin, backdrop_scale) = match backdrop.kind { + RenderTaskKind::Picture(ref task_info) => (task_info.content_origin, task_info.device_pixel_scale), + _ => panic!("bug: composite on non-picture?"), + }; + let (source_screen_origin, source_scale) = match source.kind { + RenderTaskKind::Picture(ref task_info) => (task_info.content_origin, task_info.device_pixel_scale), + _ => panic!("bug: composite on non-picture?"), + }; + + // Bind the FBO to blit the backdrop to. + // Called per-instance in case the layer (and therefore FBO) + // changes. The device will skip the GL call if the requested + // target is already bound. + let cache_draw_target = DrawTarget::from_texture( + cache_texture, + readback_layer.0 as usize, + false, + ); + + let source_in_backdrop_space = source_screen_origin.to_f32() * (backdrop_scale.0 / source_scale.0); + + let mut src = DeviceIntRect::new( + (source_in_backdrop_space + (backdrop_rect.origin - backdrop_screen_origin).to_f32()).to_i32(), + readback_rect.size, + ); + let mut dest = readback_rect.to_i32(); + let device_to_framebuffer = Scale::new(1i32); + + // Need to invert the y coordinates and flip the image vertically when + // reading back from the framebuffer. + if draw_target.is_default() { + src.origin.y = draw_target.dimensions().height as i32 - src.size.height - src.origin.y; + dest.origin.y += dest.size.height; + dest.size.height = -dest.size.height; + } + + self.device.blit_render_target( + draw_target.into(), + src * device_to_framebuffer, + cache_draw_target, + dest * device_to_framebuffer, + TextureFilter::Linear, + ); + + // Restore draw target to current pass render target + layer, and reset + // the read target. + self.device.bind_draw_target(draw_target); + self.device.reset_read_target(); + + if uses_scissor { + self.device.enable_scissor(); + } + } + + fn handle_blits( + &mut self, + blits: &[BlitJob], + render_tasks: &RenderTaskGraph, + draw_target: DrawTarget, + content_origin: &DeviceIntPoint, + ) { + if blits.is_empty() { + return; + } + + let _timer = self.gpu_profile.start_timer(GPU_TAG_BLIT); + + // TODO(gw): For now, we don't bother batching these by source texture. + // If if ever shows up as an issue, we can easily batch them. + for blit in blits { + let (source, layer, source_rect) = match blit.source { + BlitJobSource::Texture(texture_id, layer, source_rect) => { + // A blit from a texture into this target. + (texture_id, layer as usize, source_rect) + } + BlitJobSource::RenderTask(task_id) => { + // A blit from the child render task into this target. + // TODO(gw): Support R8 format here once we start + // creating mips for alpha masks. + let source = &render_tasks[task_id]; + let (source_rect, layer) = source.get_target_rect(); + (TextureSource::PrevPassColor, layer.0, source_rect) + } + }; + + debug_assert_eq!(source_rect.size, blit.target_rect.size); + let (texture, swizzle) = self.texture_resolver + .resolve(&source) + .expect("BUG: invalid source texture"); + + if swizzle != Swizzle::default() { + error!("Swizzle {:?} can't be handled by a blit", swizzle); + } + + let read_target = DrawTarget::from_texture( + texture, + layer, + false, + ); + + self.device.blit_render_target( + read_target.into(), + read_target.to_framebuffer_rect(source_rect), + draw_target, + draw_target.to_framebuffer_rect(blit.target_rect.translate(-content_origin.to_vector())), + TextureFilter::Linear, + ); + } + } + + fn handle_scaling( + &mut self, + scalings: &FastHashMap>, + projection: &default::Transform3D, + stats: &mut RendererStats, + ) { + if scalings.is_empty() { + return + } + + let _timer = self.gpu_profile.start_timer(GPU_TAG_SCALE); + + self.shaders + .borrow_mut() + .cs_scale + .bind( + &mut self.device, + &projection, + &mut self.renderer_errors, + ); + + for (source, instances) in scalings { + self.draw_instanced_batch( + instances, + VertexArrayKind::Scale, + &BatchTextures::color(*source), + stats, + ); + } + } + + fn handle_svg_filters( + &mut self, + textures: &BatchTextures, + svg_filters: &[SvgFilterInstance], + projection: &default::Transform3D, + stats: &mut RendererStats, + ) { + if svg_filters.is_empty() { + return; + } + + let _timer = self.gpu_profile.start_timer(GPU_TAG_SVG_FILTER); + + self.shaders.borrow_mut().cs_svg_filter.bind( + &mut self.device, + &projection, + &mut self.renderer_errors + ); + + self.draw_instanced_batch( + &svg_filters, + VertexArrayKind::SvgFilter, + textures, + stats, + ); + } + + fn draw_picture_cache_target( + &mut self, + target: &PictureCacheTarget, + draw_target: DrawTarget, + content_origin: DeviceIntPoint, + projection: &default::Transform3D, + render_tasks: &RenderTaskGraph, + stats: &mut RendererStats, + ) { + profile_scope!("draw_picture_cache_target"); + + self.profile_counters.rendered_picture_cache_tiles.inc(); + let _gm = self.gpu_profile.start_marker("picture cache target"); + let framebuffer_kind = FramebufferKind::Other; + + { + let _timer = self.gpu_profile.start_timer(GPU_TAG_SETUP_TARGET); + self.device.bind_draw_target(draw_target); + self.device.enable_depth_write(); + self.set_blend(false, framebuffer_kind); + + let clear_color = target.clear_color.map(|c| c.to_array()); + match target.alpha_batch_container.task_scissor_rect { + // If updating only a dirty rect within a picture cache target, the + // clear must also be scissored to that dirty region. + Some(r) if self.clear_caches_with_quads => { + self.device.enable_depth(DepthFunction::Always); + // Save the draw call count so that our reftests don't get confused... + let old_draw_call_count = stats.total_draw_calls; + if clear_color.is_none() { + self.device.disable_color_write(); + } + let instance = ClearInstance { + rect: [ + r.origin.x as f32, r.origin.y as f32, + r.size.width as f32, r.size.height as f32, + ], + color: clear_color.unwrap_or([0.0; 4]), + }; + self.shaders.borrow_mut().ps_clear.bind( + &mut self.device, + &projection, + &mut self.renderer_errors, + ); + self.draw_instanced_batch( + &[instance], + VertexArrayKind::Clear, + &BatchTextures::no_texture(), + stats, + ); + if clear_color.is_none() { + self.device.enable_color_write(); + } + stats.total_draw_calls = old_draw_call_count; + self.device.disable_depth(); + } + other => { + let scissor_rect = other.map(|rect| { + draw_target.build_scissor_rect( + Some(rect), + content_origin, + ) + }); + self.device.clear_target(clear_color, Some(1.0), scissor_rect); + } + }; + self.device.disable_depth_write(); + } + + self.draw_alpha_batch_container( + &target.alpha_batch_container, + draw_target, + content_origin, + framebuffer_kind, + projection, + render_tasks, + stats, + ); + } + + /// Draw an alpha batch container into a given draw target. This is used + /// by both color and picture cache target kinds. + fn draw_alpha_batch_container( + &mut self, + alpha_batch_container: &AlphaBatchContainer, + draw_target: DrawTarget, + content_origin: DeviceIntPoint, + framebuffer_kind: FramebufferKind, + projection: &default::Transform3D, + render_tasks: &RenderTaskGraph, + stats: &mut RendererStats, + ) { + let uses_scissor = alpha_batch_container.task_scissor_rect.is_some(); + + if uses_scissor { + self.device.enable_scissor(); + let scissor_rect = draw_target.build_scissor_rect( + alpha_batch_container.task_scissor_rect, + content_origin, + ); + self.device.set_scissor_rect(scissor_rect) + } + + if !alpha_batch_container.opaque_batches.is_empty() + && !self.debug_flags.contains(DebugFlags::DISABLE_OPAQUE_PASS) { + let _gl = self.gpu_profile.start_marker("opaque batches"); + let opaque_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_OPAQUE); + self.set_blend(false, framebuffer_kind); + //Note: depth equality is needed for split planes + self.device.enable_depth(DepthFunction::LessEqual); + self.device.enable_depth_write(); + + // Draw opaque batches front-to-back for maximum + // z-buffer efficiency! + for batch in alpha_batch_container + .opaque_batches + .iter() + .rev() + { + if should_skip_batch(&batch.key.kind, self.debug_flags) { + continue; + } + + self.shaders.borrow_mut() + .get(&batch.key, batch.features, self.debug_flags) + .bind( + &mut self.device, projection, + &mut self.renderer_errors, + ); + + let _timer = self.gpu_profile.start_timer(batch.key.kind.sampler_tag()); + self.draw_instanced_batch( + &batch.instances, + VertexArrayKind::Primitive, + &batch.key.textures, + stats + ); + } + + self.device.disable_depth_write(); + self.gpu_profile.finish_sampler(opaque_sampler); + } else { + self.device.disable_depth(); + } + + if !alpha_batch_container.alpha_batches.is_empty() + && !self.debug_flags.contains(DebugFlags::DISABLE_ALPHA_PASS) { + let _gl = self.gpu_profile.start_marker("alpha batches"); + let transparent_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_TRANSPARENT); + self.set_blend(true, framebuffer_kind); + + let mut prev_blend_mode = BlendMode::None; + let shaders_rc = self.shaders.clone(); + + // If the device supports pixel local storage, initialize the PLS buffer for + // the transparent pass. This involves reading the current framebuffer value + // and storing that in PLS. + // TODO(gw): This is quite expensive and relies on framebuffer fetch being + // available. We can probably switch the opaque pass over to use + // PLS too, and remove this pass completely. + if self.device.get_capabilities().supports_pixel_local_storage { + // TODO(gw): If using PLS, the fixed function blender is disabled. It's possible + // we could take advantage of this by skipping batching on the blend + // mode in these cases. + self.init_pixel_local_storage( + alpha_batch_container.task_rect, + projection, + stats, + ); + } + + for batch in &alpha_batch_container.alpha_batches { + if should_skip_batch(&batch.key.kind, self.debug_flags) { + continue; + } + + let mut shaders = shaders_rc.borrow_mut(); + let shader = shaders.get( + &batch.key, + batch.features | BatchFeatures::ALPHA_PASS, + self.debug_flags, + ); + + if batch.key.blend_mode != prev_blend_mode { + match batch.key.blend_mode { + _ if self.debug_flags.contains(DebugFlags::SHOW_OVERDRAW) && + framebuffer_kind == FramebufferKind::Main => { + self.device.set_blend_mode_show_overdraw(); + } + BlendMode::None => { + unreachable!("bug: opaque blend in alpha pass"); + } + BlendMode::Alpha => { + self.device.set_blend_mode_alpha(); + } + BlendMode::PremultipliedAlpha => { + self.device.set_blend_mode_premultiplied_alpha(); + } + BlendMode::PremultipliedDestOut => { + self.device.set_blend_mode_premultiplied_dest_out(); + } + BlendMode::SubpixelDualSource => { + self.device.set_blend_mode_subpixel_dual_source(); + } + BlendMode::SubpixelConstantTextColor(color) => { + self.device.set_blend_mode_subpixel_constant_text_color(color); + } + BlendMode::SubpixelWithBgColor => { + // Using the three pass "component alpha with font smoothing + // background color" rendering technique: + // + // /webrender/doc/text-rendering.md + // + self.device.set_blend_mode_subpixel_with_bg_color_pass0(); + // need to make sure the shader is bound + shader.bind( + &mut self.device, + projection, + &mut self.renderer_errors, + ); + self.device.switch_mode(ShaderColorMode::SubpixelWithBgColorPass0 as _); + } + BlendMode::Advanced(mode) => { + if self.enable_advanced_blend_barriers { + self.device.gl().blend_barrier_khr(); + } + self.device.set_blend_mode_advanced(mode); + } + } + prev_blend_mode = batch.key.blend_mode; + } + + // Handle special case readback for composites. + if let BatchKind::Brush(BrushBatchKind::MixBlend { task_id, source_id, backdrop_id }) = batch.key.kind { + // composites can't be grouped together because + // they may overlap and affect each other. + debug_assert_eq!(batch.instances.len(), 1); + self.handle_readback_composite( + draw_target, + uses_scissor, + &render_tasks[source_id], + &render_tasks[task_id], + &render_tasks[backdrop_id], + ); + } + + let _timer = self.gpu_profile.start_timer(batch.key.kind.sampler_tag()); + shader.bind( + &mut self.device, + projection, + &mut self.renderer_errors, + ); + + self.draw_instanced_batch( + &batch.instances, + VertexArrayKind::Primitive, + &batch.key.textures, + stats + ); + + if batch.key.blend_mode == BlendMode::SubpixelWithBgColor { + self.set_blend_mode_subpixel_with_bg_color_pass1(framebuffer_kind); + // re-binding the shader after the blend mode change + shader.bind( + &mut self.device, + projection, + &mut self.renderer_errors, + ); + self.device.switch_mode(ShaderColorMode::SubpixelWithBgColorPass1 as _); + + // When drawing the 2nd and 3rd passes, we know that the VAO, textures etc + // are all set up from the previous draw_instanced_batch call, + // so just issue a draw call here to avoid re-uploading the + // instances and re-binding textures etc. + self.device + .draw_indexed_triangles_instanced_u16(6, batch.instances.len() as i32); + + self.set_blend_mode_subpixel_with_bg_color_pass2(framebuffer_kind); + // re-binding the shader after the blend mode change + shader.bind( + &mut self.device, + projection, + &mut self.renderer_errors, + ); + self.device.switch_mode(ShaderColorMode::SubpixelWithBgColorPass2 as _); + + self.device + .draw_indexed_triangles_instanced_u16(6, batch.instances.len() as i32); + } + + if batch.key.blend_mode == BlendMode::SubpixelWithBgColor { + prev_blend_mode = BlendMode::None; + } + } + + // If the device supports pixel local storage, resolve the PLS values. + // This pass reads the final PLS color value, and writes it to a normal + // fragment output. + if self.device.get_capabilities().supports_pixel_local_storage { + self.resolve_pixel_local_storage( + alpha_batch_container.task_rect, + projection, + stats, + ); + } + + self.set_blend(false, framebuffer_kind); + self.gpu_profile.finish_sampler(transparent_sampler); + } + + self.device.disable_depth(); + if uses_scissor { + self.device.disable_scissor(); + } + } + + /// Rasterize any external compositor surfaces that require updating + fn update_external_native_surfaces( + &mut self, + external_surfaces: &[ResolvedExternalSurface], + results: &mut RenderResults, + ) { + if external_surfaces.is_empty() { + return; + } + + let opaque_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_OPAQUE); + + self.device.disable_depth(); + self.set_blend(false, FramebufferKind::Main); + + for surface in external_surfaces { + // See if this surface needs to be updated + let (native_surface_id, surface_size) = match surface.update_params { + Some(params) => params, + None => continue, + }; + + // When updating an external surface, the entire surface rect is used + // for all of the draw, dirty, valid and clip rect parameters. + let surface_rect = surface_size.into(); + + // Bind the native compositor surface to update + let surface_info = self.compositor_config + .compositor() + .unwrap() + .bind( + NativeTileId { + surface_id: native_surface_id, + x: 0, + y: 0, + }, + surface_rect, + surface_rect, + ); + + // Bind the native surface to current FBO target + let draw_target = DrawTarget::NativeSurface { + offset: surface_info.origin, + external_fbo_id: surface_info.fbo_id, + dimensions: surface_size, + }; + self.device.bind_draw_target(draw_target); + + let projection = Transform3D::ortho( + 0.0, + surface_size.width as f32, + 0.0, + surface_size.height as f32, + self.device.ortho_near_plane(), + self.device.ortho_far_plane(), + ); + + let ( textures, instance ) = match surface.color_data { + ResolvedExternalSurfaceColorData::Yuv{ + ref planes, color_space, format, rescale, .. } => { + + // Bind an appropriate YUV shader for the texture format kind + self.shaders + .borrow_mut() + .get_composite_shader( + CompositeSurfaceFormat::Yuv, + surface.image_buffer_kind, + ).bind( + &mut self.device, + &projection, + &mut self.renderer_errors + ); + + let textures = BatchTextures { + colors: [ + planes[0].texture, + planes[1].texture, + planes[2].texture, + ], + }; + + // When the texture is an external texture, the UV rect is not known when + // the external surface descriptor is created, because external textures + // are not resolved until the lock() callback is invoked at the start of + // the frame render. To handle this, query the texture resolver for the + // UV rect if it's an external texture, otherwise use the default UV rect. + let uv_rects = [ + self.texture_resolver.get_uv_rect(&textures.colors[0], planes[0].uv_rect), + self.texture_resolver.get_uv_rect(&textures.colors[1], planes[1].uv_rect), + self.texture_resolver.get_uv_rect(&textures.colors[2], planes[2].uv_rect), + ]; + + let instance = CompositeInstance::new_yuv( + surface_rect.to_f32(), + surface_rect.to_f32(), + // z-id is not relevant when updating a native compositor surface. + // TODO(gw): Support compositor surfaces without z-buffer, for memory / perf win here. + ZBufferId(0), + color_space, + format, + rescale, + [ + planes[0].texture_layer as f32, + planes[1].texture_layer as f32, + planes[2].texture_layer as f32, + ], + uv_rects, + ); + + ( textures, instance ) + }, + ResolvedExternalSurfaceColorData::Rgb{ ref plane, flip_y, .. } => { + + self.shaders + .borrow_mut() + .get_composite_shader( + CompositeSurfaceFormat::Rgba, + surface.image_buffer_kind, + ).bind( + &mut self.device, + &projection, + &mut self.renderer_errors + ); + + let textures = BatchTextures::color(plane.texture); + let mut uv_rect = self.texture_resolver.get_uv_rect(&textures.colors[0], plane.uv_rect); + if flip_y { + let y = uv_rect.uv0.y; + uv_rect.uv0.y = uv_rect.uv1.y; + uv_rect.uv1.y = y; + } + + let instance = CompositeInstance::new_rgb( + surface_rect.to_f32(), + surface_rect.to_f32(), + PremultipliedColorF::WHITE, + plane.texture_layer as f32, + ZBufferId(0), + uv_rect, + ); + + ( textures, instance ) + }, + }; + + self.draw_instanced_batch( + &[instance], + VertexArrayKind::Composite, + &textures, + &mut results.stats, + ); + + self.compositor_config + .compositor() + .unwrap() + .unbind(); + } + + self.gpu_profile.finish_sampler(opaque_sampler); + } + + /// Draw a list of tiles to the framebuffer + fn draw_tile_list<'a, I: Iterator>( + &mut self, + tiles_iter: I, + external_surfaces: &[ResolvedExternalSurface], + projection: &default::Transform3D, + partial_present_mode: Option, + stats: &mut RendererStats, + ) { + self.shaders + .borrow_mut() + .get_composite_shader( + CompositeSurfaceFormat::Rgba, + ImageBufferKind::Texture2DArray, + ).bind( + &mut self.device, + projection, + &mut self.renderer_errors + ); + + let mut current_shader_params = (CompositeSurfaceFormat::Rgba, ImageBufferKind::Texture2DArray); + let mut current_textures = BatchTextures::no_texture(); + let mut instances = Vec::new(); + + for tile in tiles_iter { + // Determine a clip rect to apply to this tile, depending on what + // the partial present mode is. + let partial_clip_rect = match partial_present_mode { + Some(PartialPresentMode::Single { dirty_rect }) => dirty_rect, + None => tile.rect, + }; + + let clip_rect = match partial_clip_rect.intersection(&tile.clip_rect) { + Some(rect) => rect, + None => continue, + }; + + // Simple compositor needs the valid rect in device space to match clip rect + let valid_device_rect = tile.valid_rect.translate( + tile.rect.origin.to_vector() + ); + + // Only composite the part of the tile that contains valid pixels + let clip_rect = match clip_rect.intersection(&valid_device_rect) { + Some(rect) => rect, + None => continue, + }; + + // Work out the draw params based on the tile surface + let (instance, textures, shader_params) = match tile.surface { + CompositeTileSurface::Color { color } => { + ( + CompositeInstance::new( + tile.rect, + clip_rect, + color.premultiplied(), + 0.0, + tile.z_id, + ), + BatchTextures::color(TextureSource::Dummy), + (CompositeSurfaceFormat::Rgba, ImageBufferKind::Texture2DArray), + ) + } + CompositeTileSurface::Clear => { + ( + CompositeInstance::new( + tile.rect, + clip_rect, + PremultipliedColorF::BLACK, + 0.0, + tile.z_id, + ), + BatchTextures::color(TextureSource::Dummy), + (CompositeSurfaceFormat::Rgba, ImageBufferKind::Texture2DArray), + ) + } + CompositeTileSurface::Texture { surface: ResolvedSurfaceTexture::TextureCache { texture, layer } } => { + ( + CompositeInstance::new( + tile.rect, + clip_rect, + PremultipliedColorF::WHITE, + layer as f32, + tile.z_id, + ), + BatchTextures::color(texture), + (CompositeSurfaceFormat::Rgba, ImageBufferKind::Texture2DArray), + ) + } + CompositeTileSurface::ExternalSurface { external_surface_index } => { + let surface = &external_surfaces[external_surface_index.0]; + + match surface.color_data { + ResolvedExternalSurfaceColorData::Yuv{ ref planes, color_space, format, rescale, .. } => { + + let textures = BatchTextures { + colors: [ + planes[0].texture, + planes[1].texture, + planes[2].texture, + ], + }; + + // When the texture is an external texture, the UV rect is not known when + // the external surface descriptor is created, because external textures + // are not resolved until the lock() callback is invoked at the start of + // the frame render. To handle this, query the texture resolver for the + // UV rect if it's an external texture, otherwise use the default UV rect. + let uv_rects = [ + self.texture_resolver.get_uv_rect(&textures.colors[0], planes[0].uv_rect), + self.texture_resolver.get_uv_rect(&textures.colors[1], planes[1].uv_rect), + self.texture_resolver.get_uv_rect(&textures.colors[2], planes[2].uv_rect), + ]; + + ( + CompositeInstance::new_yuv( + tile.rect, + clip_rect, + tile.z_id, + color_space, + format, + rescale, + [ + planes[0].texture_layer as f32, + planes[1].texture_layer as f32, + planes[2].texture_layer as f32, + ], + uv_rects, + ), + textures, + (CompositeSurfaceFormat::Yuv, surface.image_buffer_kind), + ) + }, + ResolvedExternalSurfaceColorData::Rgb{ ref plane, flip_y, .. } => { + + let mut uv_rect = self.texture_resolver.get_uv_rect(&plane.texture, plane.uv_rect); + if flip_y { + let y = uv_rect.uv0.y; + uv_rect.uv0.y = uv_rect.uv1.y; + uv_rect.uv1.y = y; + } + + ( + CompositeInstance::new_rgb( + tile.rect, + clip_rect, + PremultipliedColorF::WHITE, + plane.texture_layer as f32, + tile.z_id, + uv_rect, + ), + BatchTextures::color(plane.texture), + (CompositeSurfaceFormat::Rgba, surface.image_buffer_kind), + ) + }, + } + } + CompositeTileSurface::Texture { surface: ResolvedSurfaceTexture::Native { .. } } => { + unreachable!("bug: found native surface in simple composite path"); + } + }; + + // Flush batch if shader params or textures changed + let flush_batch = !current_textures.is_compatible_with(&textures) || + shader_params != current_shader_params; + + if flush_batch { + if !instances.is_empty() { + self.draw_instanced_batch( + &instances, + VertexArrayKind::Composite, + ¤t_textures, + stats, + ); + instances.clear(); + } + } + + if shader_params != current_shader_params { + self.shaders + .borrow_mut() + .get_composite_shader(shader_params.0, shader_params.1) + .bind( + &mut self.device, + projection, + &mut self.renderer_errors + ); + + current_shader_params = shader_params; + } + + current_textures = textures; + + // Add instance to current batch + instances.push(instance); + } + + // Flush the last batch + if !instances.is_empty() { + self.draw_instanced_batch( + &instances, + VertexArrayKind::Composite, + ¤t_textures, + stats, + ); + } + } + + /// Composite picture cache tiles into the framebuffer. This is currently + /// the only way that picture cache tiles get drawn. In future, the tiles + /// will often be handed to the OS compositor, and this method will be + /// rarely used. + fn composite_simple( + &mut self, + composite_state: &CompositeState, + clear_framebuffer: bool, + draw_target: DrawTarget, + projection: &default::Transform3D, + results: &mut RenderResults, + max_partial_present_rects: usize, + draw_previous_partial_present_regions: bool, + ) { + let _gm = self.gpu_profile.start_marker("framebuffer"); + let _timer = self.gpu_profile.start_timer(GPU_TAG_COMPOSITE); + + self.device.bind_draw_target(draw_target); + self.device.enable_depth(DepthFunction::LessEqual); + self.device.enable_depth_write(); + + // Determine the partial present mode for this frame, which is used during + // framebuffer clears and calculating the clip rect for each tile that is drawn. + let mut partial_present_mode = None; + + if max_partial_present_rects > 0 { + // We can only use partial present if we have valid dirty rects and the + // client hasn't reset partial present state since last frame. + if composite_state.dirty_rects_are_valid && !self.force_redraw { + let mut combined_dirty_rect = DeviceRect::zero(); + + // Work out how many dirty rects WR produced, and if that's more than + // what the device supports. + for tile in composite_state.opaque_tiles.iter().chain(composite_state.alpha_tiles.iter()) { + let dirty_rect = tile.dirty_rect.translate(tile.rect.origin.to_vector()); + combined_dirty_rect = combined_dirty_rect.union(&dirty_rect); + } + + let combined_dirty_rect = combined_dirty_rect.round(); + let combined_dirty_rect_i32 = combined_dirty_rect.to_i32(); + // If nothing has changed, don't return any dirty rects at all (the client + // can use this as a signal to skip present completely). + if !combined_dirty_rect.is_empty() { + results.dirty_rects.push(combined_dirty_rect_i32); + } + + // If the implementation requires manually keeping the buffer consistent, + // combine the previous frame's damage for tile clipping. + // (Not for the returned region though, that should be from this frame only) + partial_present_mode = Some(PartialPresentMode::Single { + dirty_rect: if draw_previous_partial_present_regions { + combined_dirty_rect.union(&self.prev_dirty_rect) + } else { combined_dirty_rect }, + }); + + if draw_previous_partial_present_regions { + self.prev_dirty_rect = combined_dirty_rect; + } + } else { + // If we don't have a valid partial present scenario, return a single + // dirty rect to the client that covers the entire framebuffer. + let fb_rect = DeviceIntRect::new( + DeviceIntPoint::zero(), + draw_target.dimensions(), + ); + results.dirty_rects.push(fb_rect); + + if draw_previous_partial_present_regions { + self.prev_dirty_rect = fb_rect.to_f32(); + } + } + + self.force_redraw = false; + } + + // Clear the framebuffer, if required + if clear_framebuffer { + let clear_color = self.clear_color.map(|color| color.to_array()); + + match partial_present_mode { + Some(PartialPresentMode::Single { dirty_rect }) => { + // We have a single dirty rect, so clear only that + self.device.clear_target(clear_color, + Some(1.0), + Some(draw_target.to_framebuffer_rect(dirty_rect.to_i32()))); + } + None => { + // Partial present is disabled, so clear the entire framebuffer + self.device.clear_target(clear_color, + Some(1.0), + None); + } + } + } + + // We are only interested in tiles backed with actual cached pixels so we don't + // count clear tiles here. + let num_tiles = composite_state.opaque_tiles.len() + + composite_state.alpha_tiles.len(); + self.profile_counters.total_picture_cache_tiles.set(num_tiles); + + // Draw opaque tiles first, front-to-back to get maxmum + // z-reject efficiency. + if !composite_state.opaque_tiles.is_empty() { + let opaque_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_OPAQUE); + self.device.enable_depth_write(); + self.set_blend(false, FramebufferKind::Main); + self.draw_tile_list( + composite_state.opaque_tiles.iter().rev(), + &composite_state.external_surfaces, + projection, + partial_present_mode, + &mut results.stats, + ); + self.gpu_profile.finish_sampler(opaque_sampler); + } + + if !composite_state.clear_tiles.is_empty() { + let transparent_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_TRANSPARENT); + self.device.disable_depth_write(); + self.set_blend(true, FramebufferKind::Main); + self.device.set_blend_mode_premultiplied_dest_out(); + self.draw_tile_list( + composite_state.clear_tiles.iter(), + &composite_state.external_surfaces, + projection, + partial_present_mode, + &mut results.stats, + ); + self.gpu_profile.finish_sampler(transparent_sampler); + } + + // Draw alpha tiles + if !composite_state.alpha_tiles.is_empty() { + let transparent_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_TRANSPARENT); + self.device.disable_depth_write(); + self.set_blend(true, FramebufferKind::Main); + self.set_blend_mode_premultiplied_alpha(FramebufferKind::Main); + self.draw_tile_list( + composite_state.alpha_tiles.iter(), + &composite_state.external_surfaces, + projection, + partial_present_mode, + &mut results.stats, + ); + self.gpu_profile.finish_sampler(transparent_sampler); + } + } + + fn draw_color_target( + &mut self, + draw_target: DrawTarget, + target: &ColorRenderTarget, + content_origin: DeviceIntPoint, + clear_color: Option<[f32; 4]>, + clear_depth: Option, + render_tasks: &RenderTaskGraph, + projection: &default::Transform3D, + frame_id: GpuFrameId, + stats: &mut RendererStats, + ) { + profile_scope!("draw_color_target"); + + self.profile_counters.color_passes.inc(); + let _gm = self.gpu_profile.start_marker("color target"); + + // sanity check for the depth buffer + if let DrawTarget::Texture { with_depth, .. } = draw_target { + assert!(with_depth >= target.needs_depth()); + } + + let framebuffer_kind = if draw_target.is_default() { + FramebufferKind::Main + } else { + FramebufferKind::Other + }; + + { + let _timer = self.gpu_profile.start_timer(GPU_TAG_SETUP_TARGET); + self.device.bind_draw_target(draw_target); + self.device.disable_depth(); + self.set_blend(false, framebuffer_kind); + + if clear_depth.is_some() { + self.device.enable_depth_write(); + } + + let clear_rect = match draw_target { + DrawTarget::NativeSurface { .. } => { + unreachable!("bug: native compositor surface in child target"); + } + DrawTarget::Default { rect, total_size, .. } if rect.origin == FramebufferIntPoint::zero() && rect.size == total_size => { + // whole screen is covered, no need for scissor + None + } + DrawTarget::Default { rect, .. } => { + Some(rect) + } + DrawTarget::Texture { .. } if self.enable_clear_scissor => { + // TODO(gw): Applying a scissor rect and minimal clear here + // is a very large performance win on the Intel and nVidia + // GPUs that I have tested with. It's possible it may be a + // performance penalty on other GPU types - we should test this + // and consider different code paths. + // + // Note: The above measurements were taken when render + // target slices were minimum 2048x2048. Now that we size + // them adaptively, this may be less of a win (except perhaps + // on a mostly-unused last slice of a large texture array). + Some(draw_target.to_framebuffer_rect(target.used_rect())) + } + DrawTarget::Texture { .. } | DrawTarget::External { .. } => { + None + } + }; + + self.device.clear_target( + clear_color, + clear_depth, + clear_rect, + ); + + if clear_depth.is_some() { + self.device.disable_depth_write(); + } + } + + // Handle any blits from the texture cache to this target. + self.handle_blits( + &target.blits, render_tasks, draw_target, &content_origin, + ); + + // Draw any blurs for this target. + // Blurs are rendered as a standard 2-pass + // separable implementation. + // TODO(gw): In the future, consider having + // fast path blur shaders for common + // blur radii with fixed weights. + if !target.vertical_blurs.is_empty() || !target.horizontal_blurs.is_empty() { + let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR); + + self.set_blend(false, framebuffer_kind); + self.shaders.borrow_mut().cs_blur_rgba8 + .bind(&mut self.device, projection, &mut self.renderer_errors); + + if !target.vertical_blurs.is_empty() { + self.draw_instanced_batch( + &target.vertical_blurs, + VertexArrayKind::Blur, + &BatchTextures::no_texture(), + stats, + ); + } + + if !target.horizontal_blurs.is_empty() { + self.draw_instanced_batch( + &target.horizontal_blurs, + VertexArrayKind::Blur, + &BatchTextures::no_texture(), + stats, + ); + } + } + + self.handle_scaling( + &target.scalings, + projection, + stats, + ); + + for (ref textures, ref filters) in &target.svg_filters { + self.handle_svg_filters( + textures, + filters, + projection, + stats, + ); + } + + for alpha_batch_container in &target.alpha_batch_containers { + self.draw_alpha_batch_container( + alpha_batch_container, + draw_target, + content_origin, + framebuffer_kind, + projection, + render_tasks, + stats, + ); + } + + // For any registered image outputs on this render target, + // get the texture from caller and blit it. + for output in &target.outputs { + let handler = self.output_image_handler + .as_mut() + .expect("Found output image, but no handler set!"); + if let Some((texture_id, output_size)) = handler.lock(output.pipeline_id) { + let fbo_id = match self.output_targets.entry(texture_id) { + Entry::Vacant(entry) => { + let fbo_id = self.device.create_fbo_for_external_texture(texture_id); + entry.insert(FrameOutput { + fbo_id, + last_access: frame_id, + }); + fbo_id + } + Entry::Occupied(mut entry) => { + let target = entry.get_mut(); + target.last_access = frame_id; + target.fbo_id + } + }; + let (src_rect, _) = render_tasks[output.task_id].get_target_rect(); + if !self.device.surface_origin_is_top_left() { + self.device.blit_render_target_invert_y( + draw_target.into(), + draw_target.to_framebuffer_rect(src_rect.translate(-content_origin.to_vector())), + DrawTarget::External { fbo: fbo_id, size: output_size }, + output_size.into(), + ); + } else { + self.device.blit_render_target( + draw_target.into(), + draw_target.to_framebuffer_rect(src_rect.translate(-content_origin.to_vector())), + DrawTarget::External { fbo: fbo_id, size: output_size }, + output_size.into(), + TextureFilter::Linear, + ); + } + handler.unlock(output.pipeline_id); + } + } + } + + /// Draw all the instances in a clip batcher list to the current target. + fn draw_clip_batch_list( + &mut self, + list: &ClipBatchList, + projection: &default::Transform3D, + stats: &mut RendererStats, + ) { + if self.debug_flags.contains(DebugFlags::DISABLE_CLIP_MASKS) { + return; + } + + // draw rounded cornered rectangles + if !list.slow_rectangles.is_empty() { + let _gm2 = self.gpu_profile.start_marker("slow clip rectangles"); + self.shaders.borrow_mut().cs_clip_rectangle_slow.bind( + &mut self.device, + projection, + &mut self.renderer_errors, + ); + self.draw_instanced_batch( + &list.slow_rectangles, + VertexArrayKind::Clip, + &BatchTextures::no_texture(), + stats, + ); + } + if !list.fast_rectangles.is_empty() { + let _gm2 = self.gpu_profile.start_marker("fast clip rectangles"); + self.shaders.borrow_mut().cs_clip_rectangle_fast.bind( + &mut self.device, + projection, + &mut self.renderer_errors, + ); + self.draw_instanced_batch( + &list.fast_rectangles, + VertexArrayKind::Clip, + &BatchTextures::no_texture(), + stats, + ); + } + // draw box-shadow clips + for (mask_texture_id, items) in list.box_shadows.iter() { + let _gm2 = self.gpu_profile.start_marker("box-shadows"); + let textures = BatchTextures { + colors: [ + *mask_texture_id, + TextureSource::Invalid, + TextureSource::Invalid, + ], + }; + self.shaders.borrow_mut().cs_clip_box_shadow + .bind(&mut self.device, projection, &mut self.renderer_errors); + self.draw_instanced_batch( + items, + VertexArrayKind::Clip, + &textures, + stats, + ); + } + + // draw image masks + for (mask_texture_id, items) in list.images.iter() { + let _gm2 = self.gpu_profile.start_marker("clip images"); + let textures = BatchTextures { + colors: [ + *mask_texture_id, + TextureSource::Invalid, + TextureSource::Invalid, + ], + }; + self.shaders.borrow_mut().cs_clip_image + .bind(&mut self.device, projection, &mut self.renderer_errors); + self.draw_instanced_batch( + items, + VertexArrayKind::Clip, + &textures, + stats, + ); + } + } + + fn draw_alpha_target( + &mut self, + draw_target: DrawTarget, + target: &AlphaRenderTarget, + projection: &default::Transform3D, + render_tasks: &RenderTaskGraph, + stats: &mut RendererStats, + ) { + profile_scope!("draw_alpha_target"); + + self.profile_counters.alpha_passes.inc(); + let _gm = self.gpu_profile.start_marker("alpha target"); + let alpha_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_ALPHA); + + { + let _timer = self.gpu_profile.start_timer(GPU_TAG_SETUP_TARGET); + self.device.bind_draw_target(draw_target); + self.device.disable_depth(); + self.device.disable_depth_write(); + self.set_blend(false, FramebufferKind::Other); + + // TODO(gw): Applying a scissor rect and minimal clear here + // is a very large performance win on the Intel and nVidia + // GPUs that I have tested with. It's possible it may be a + // performance penalty on other GPU types - we should test this + // and consider different code paths. + + let zero_color = [0.0, 0.0, 0.0, 0.0]; + for &task_id in &target.zero_clears { + let (rect, _) = render_tasks[task_id].get_target_rect(); + self.device.clear_target( + Some(zero_color), + None, + Some(draw_target.to_framebuffer_rect(rect)), + ); + } + + let one_color = [1.0, 1.0, 1.0, 1.0]; + for &task_id in &target.one_clears { + let (rect, _) = render_tasks[task_id].get_target_rect(); + self.device.clear_target( + Some(one_color), + None, + Some(draw_target.to_framebuffer_rect(rect)), + ); + } + } + + // Draw any blurs for this target. + // Blurs are rendered as a standard 2-pass + // separable implementation. + // TODO(gw): In the future, consider having + // fast path blur shaders for common + // blur radii with fixed weights. + if !target.vertical_blurs.is_empty() || !target.horizontal_blurs.is_empty() { + let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR); + + self.shaders.borrow_mut().cs_blur_a8 + .bind(&mut self.device, projection, &mut self.renderer_errors); + + if !target.vertical_blurs.is_empty() { + self.draw_instanced_batch( + &target.vertical_blurs, + VertexArrayKind::Blur, + &BatchTextures::no_texture(), + stats, + ); + } + + if !target.horizontal_blurs.is_empty() { + self.draw_instanced_batch( + &target.horizontal_blurs, + VertexArrayKind::Blur, + &BatchTextures::no_texture(), + stats, + ); + } + } + + self.handle_scaling( + &target.scalings, + projection, + stats, + ); + + // Draw the clip items into the tiled alpha mask. + { + let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_CLIP); + + // TODO(gw): Consider grouping multiple clip masks per shader + // invocation here to reduce memory bandwith further? + + // Draw the primary clip mask - since this is the first mask + // for the task, we can disable blending, knowing that it will + // overwrite every pixel in the mask area. + self.set_blend(false, FramebufferKind::Other); + self.draw_clip_batch_list( + &target.clip_batcher.primary_clips, + projection, + stats, + ); + + // switch to multiplicative blending for secondary masks, using + // multiplicative blending to accumulate clips into the mask. + self.set_blend(true, FramebufferKind::Other); + self.set_blend_mode_multiply(FramebufferKind::Other); + self.draw_clip_batch_list( + &target.clip_batcher.secondary_clips, + projection, + stats, + ); + } + + self.gpu_profile.finish_sampler(alpha_sampler); + } + + fn draw_texture_cache_target( + &mut self, + texture: &CacheTextureId, + layer: LayerIndex, + target: &TextureCacheRenderTarget, + render_tasks: &RenderTaskGraph, + stats: &mut RendererStats, + ) { + profile_scope!("draw_texture_cache_target"); + + let texture_source = TextureSource::TextureCache(*texture, Swizzle::default()); + let projection = { + let (texture, _) = self.texture_resolver + .resolve(&texture_source) + .expect("BUG: invalid target texture"); + let target_size = texture.get_dimensions(); + + Transform3D::ortho( + 0.0, + target_size.width as f32, + 0.0, + target_size.height as f32, + self.device.ortho_near_plane(), + self.device.ortho_far_plane(), + ) + }; + + self.device.disable_depth(); + self.device.disable_depth_write(); + + self.set_blend(false, FramebufferKind::Other); + + { + let _timer = self.gpu_profile.start_timer(GPU_TAG_CLEAR); + + let (texture, _) = self.texture_resolver + .resolve(&texture_source) + .expect("BUG: invalid target texture"); + let draw_target = DrawTarget::from_texture( + texture, + layer, + false, + ); + self.device.bind_draw_target(draw_target); + + self.device.disable_depth(); + self.device.disable_depth_write(); + self.set_blend(false, FramebufferKind::Other); + + let color = [0.0, 0.0, 0.0, 0.0]; + if self.clear_caches_with_quads && !target.clears.is_empty() { + let instances = target.clears + .iter() + .map(|r| ClearInstance { + rect: [ + r.origin.x as f32, r.origin.y as f32, + r.size.width as f32, r.size.height as f32, + ], + color, + }) + .collect::>(); + self.shaders.borrow_mut().ps_clear.bind( + &mut self.device, + &projection, + &mut self.renderer_errors, + ); + self.draw_instanced_batch( + &instances, + VertexArrayKind::Clear, + &BatchTextures::no_texture(), + stats, + ); + } else { + for rect in &target.clears { + self.device.clear_target( + Some(color), + None, + Some(draw_target.to_framebuffer_rect(*rect)), + ); + } + } + + // Handle any blits to this texture from child tasks. + self.handle_blits( + &target.blits, render_tasks, draw_target, &DeviceIntPoint::zero(), + ); + } + + // Draw any borders for this target. + if !target.border_segments_solid.is_empty() || + !target.border_segments_complex.is_empty() + { + let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_BORDER); + + self.set_blend(true, FramebufferKind::Other); + self.set_blend_mode_premultiplied_alpha(FramebufferKind::Other); + + if !target.border_segments_solid.is_empty() { + self.shaders.borrow_mut().cs_border_solid.bind( + &mut self.device, + &projection, + &mut self.renderer_errors, + ); + + self.draw_instanced_batch( + &target.border_segments_solid, + VertexArrayKind::Border, + &BatchTextures::no_texture(), + stats, + ); + } + + if !target.border_segments_complex.is_empty() { + self.shaders.borrow_mut().cs_border_segment.bind( + &mut self.device, + &projection, + &mut self.renderer_errors, + ); + + self.draw_instanced_batch( + &target.border_segments_complex, + VertexArrayKind::Border, + &BatchTextures::no_texture(), + stats, + ); + } + + self.set_blend(false, FramebufferKind::Other); + } + + // Draw any line decorations for this target. + if !target.line_decorations.is_empty() { + let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_LINE_DECORATION); + + self.set_blend(true, FramebufferKind::Other); + self.set_blend_mode_premultiplied_alpha(FramebufferKind::Other); + + self.shaders.borrow_mut().cs_line_decoration.bind( + &mut self.device, + &projection, + &mut self.renderer_errors, + ); + + self.draw_instanced_batch( + &target.line_decorations, + VertexArrayKind::LineDecoration, + &BatchTextures::no_texture(), + stats, + ); + + self.set_blend(false, FramebufferKind::Other); + } + + // Draw any gradients for this target. + if !target.gradients.is_empty() { + let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_GRADIENT); + + self.set_blend(false, FramebufferKind::Other); + + self.shaders.borrow_mut().cs_gradient.bind( + &mut self.device, + &projection, + &mut self.renderer_errors, + ); + + self.draw_instanced_batch( + &target.gradients, + VertexArrayKind::Gradient, + &BatchTextures::no_texture(), + stats, + ); + } + + // Draw any blurs for this target. + if !target.horizontal_blurs.is_empty() { + let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR); + + { + let mut shaders = self.shaders.borrow_mut(); + match target.target_kind { + RenderTargetKind::Alpha => &mut shaders.cs_blur_a8, + RenderTargetKind::Color => &mut shaders.cs_blur_rgba8, + }.bind(&mut self.device, &projection, &mut self.renderer_errors); + } + + self.draw_instanced_batch( + &target.horizontal_blurs, + VertexArrayKind::Blur, + &BatchTextures::no_texture(), + stats, + ); + } + } + + fn update_deferred_resolves(&mut self, deferred_resolves: &[DeferredResolve]) -> Option { + // The first thing we do is run through any pending deferred + // resolves, and use a callback to get the UV rect for this + // custom item. Then we patch the resource_rects structure + // here before it's uploaded to the GPU. + if deferred_resolves.is_empty() { + return None; + } + + let handler = self.external_image_handler + .as_mut() + .expect("Found external image, but no handler set!"); + + let mut list = GpuCacheUpdateList { + frame_id: FrameId::INVALID, + clear: false, + height: self.gpu_cache_texture.get_height(), + blocks: Vec::new(), + updates: Vec::new(), + debug_commands: Vec::new(), + }; + + for deferred_resolve in deferred_resolves { + self.gpu_profile.place_marker("deferred resolve"); + let props = &deferred_resolve.image_properties; + let ext_image = props + .external_image + .expect("BUG: Deferred resolves must be external images!"); + // Provide rendering information for NativeTexture external images. + let image = handler.lock(ext_image.id, ext_image.channel_index, deferred_resolve.rendering); + let texture_target = match ext_image.image_type { + ExternalImageType::TextureHandle(target) => target, + ExternalImageType::Buffer => { + panic!("not a suitable image type in update_deferred_resolves()"); + } + }; + + // In order to produce the handle, the external image handler may call into + // the GL context and change some states. + self.device.reset_state(); + + let texture = match image.source { + ExternalImageSource::NativeTexture(texture_id) => { + ExternalTexture::new( + texture_id, + texture_target, + Swizzle::default(), + image.uv, + ) + } + ExternalImageSource::Invalid => { + warn!("Invalid ext-image"); + debug!( + "For ext_id:{:?}, channel:{}.", + ext_image.id, + ext_image.channel_index + ); + // Just use 0 as the gl handle for this failed case. + ExternalTexture::new( + 0, + texture_target, + Swizzle::default(), + image.uv, + ) + } + ExternalImageSource::RawData(_) => { + panic!("Raw external data is not expected for deferred resolves!"); + } + }; + + self.texture_resolver + .external_images + .insert((ext_image.id, ext_image.channel_index), texture); + + list.updates.push(GpuCacheUpdate::Copy { + block_index: list.blocks.len(), + block_count: BLOCKS_PER_UV_RECT, + address: deferred_resolve.address, + }); + list.blocks.push(image.uv.into()); + list.blocks.push([0f32; 4].into()); + } + + Some(list) + } + + fn unlock_external_images(&mut self) { + if !self.texture_resolver.external_images.is_empty() { + let handler = self.external_image_handler + .as_mut() + .expect("Found external image, but no handler set!"); + + for (ext_data, _) in self.texture_resolver.external_images.drain() { + handler.unlock(ext_data.0, ext_data.1); + } + } + } + + /// Allocates a texture to be used as the output for a rendering pass. + /// + /// We make an effort to reuse render targe textures across passes and + /// across frames when the format and dimensions match. Because we use + /// immutable storage, we can't resize textures. + /// + /// We could consider approaches to re-use part of a larger target, if + /// available. However, we'd need to be careful about eviction. Currently, + /// render targets are freed if they haven't been used in 30 frames. If we + /// used partial targets, we'd need to track how _much_ of the target has + /// been used in the last 30 frames, since we could otherwise end up + /// keeping an enormous target alive indefinitely by constantly using it + /// in situations where a much smaller target would suffice. + fn allocate_target_texture( + &mut self, + list: &mut RenderTargetList, + counters: &mut FrameProfileCounters, + ) -> Option { + if list.targets.is_empty() { + return None + } + + // Get a bounding rect of all the layers, and round it up to a multiple + // of 256. This improves render target reuse when resizing the window, + // since we don't need to create a new render target for each slightly- + // larger frame. + let mut bounding_rect = DeviceIntRect::zero(); + for t in list.targets.iter() { + bounding_rect = t.used_rect().union(&bounding_rect); + } + debug_assert_eq!(bounding_rect.origin, DeviceIntPoint::zero()); + let dimensions = DeviceIntSize::new( + (bounding_rect.size.width + 255) & !255, + (bounding_rect.size.height + 255) & !255, + ); + + counters.targets_used.inc(); + + // Try finding a match in the existing pool. If there's no match, we'll + // create a new texture. + let selector = TargetSelector { + size: dimensions, + num_layers: list.targets.len(), + format: list.format, + }; + let index = self.texture_resolver.render_target_pool + .iter() + .position(|texture| { + selector == TargetSelector { + size: texture.get_dimensions(), + num_layers: texture.get_layer_count() as usize, + format: texture.get_format(), + } + }); + + let rt_info = RenderTargetInfo { has_depth: list.needs_depth() }; + let texture = if let Some(idx) = index { + let mut t = self.texture_resolver.render_target_pool.swap_remove(idx); + self.device.reuse_render_target::(&mut t, rt_info); + t + } else { + counters.targets_created.inc(); + self.device.create_texture( + TextureTarget::Array, + list.format, + dimensions.width, + dimensions.height, + TextureFilter::Linear, + Some(rt_info), + list.targets.len() as _, + ) + }; + + list.check_ready(&texture); + Some(ActiveTexture { + texture, + saved_index: list.saved_index.clone(), + }) + } + + fn bind_frame_data(&mut self, frame: &mut Frame) { + profile_scope!("bind_frame_data"); + + let _timer = self.gpu_profile.start_timer(GPU_TAG_SETUP_DATA); + + self.vertex_data_textures[self.current_vertex_data_textures].update( + &mut self.device, + frame, + ); + self.current_vertex_data_textures = + (self.current_vertex_data_textures + 1) % VERTEX_DATA_TEXTURE_COUNT; + + debug_assert!(self.texture_resolver.prev_pass_alpha.is_none()); + debug_assert!(self.texture_resolver.prev_pass_color.is_none()); + } + + fn update_native_surfaces(&mut self) { + profile_scope!("update_native_surfaces"); + + match self.compositor_config { + CompositorConfig::Native { ref mut compositor, .. } => { + for op in self.pending_native_surface_updates.drain(..) { + match op.details { + NativeSurfaceOperationDetails::CreateSurface { id, virtual_offset, tile_size, is_opaque } => { + let _inserted = self.allocated_native_surfaces.insert(id); + debug_assert!(_inserted, "bug: creating existing surface"); + compositor.create_surface( + id, + virtual_offset, + tile_size, + is_opaque, + ); + } + NativeSurfaceOperationDetails::DestroySurface { id } => { + let _existed = self.allocated_native_surfaces.remove(&id); + debug_assert!(_existed, "bug: removing unknown surface"); + compositor.destroy_surface(id); + } + NativeSurfaceOperationDetails::CreateTile { id } => { + compositor.create_tile(id); + } + NativeSurfaceOperationDetails::DestroyTile { id } => { + compositor.destroy_tile(id); + } + } + } + } + CompositorConfig::Draw { .. } => { + // Ensure nothing is added in simple composite mode, since otherwise + // memory will leak as this doesn't get drained + debug_assert!(self.pending_native_surface_updates.is_empty()); + } + } + } + + fn draw_frame( + &mut self, + frame: &mut Frame, + device_size: Option, + frame_id: GpuFrameId, + results: &mut RenderResults, + clear_framebuffer: bool, + ) { + profile_scope!("draw_frame"); + + // These markers seem to crash a lot on Android, see bug 1559834 + #[cfg(not(target_os = "android"))] + let _gm = self.gpu_profile.start_marker("draw frame"); + + if frame.passes.is_empty() { + frame.has_been_rendered = true; + return; + } + + self.device.disable_depth_write(); + self.set_blend(false, FramebufferKind::Other); + self.device.disable_stencil(); + + self.bind_frame_data(frame); + + for (_pass_index, pass) in frame.passes.iter_mut().enumerate() { + #[cfg(not(target_os = "android"))] + let _gm = self.gpu_profile.start_marker(&format!("pass {}", _pass_index)); + + self.texture_resolver.bind( + &TextureSource::PrevPassAlpha, + TextureSampler::PrevPassAlpha, + &mut self.device, + ); + self.texture_resolver.bind( + &TextureSource::PrevPassColor, + TextureSampler::PrevPassColor, + &mut self.device, + ); + + match pass.kind { + RenderPassKind::MainFramebuffer { ref main_target, .. } => { + profile_scope!("main target"); + + if let Some(device_size) = device_size { + results.stats.color_target_count += 1; + + let offset = frame.content_origin.to_f32(); + let size = frame.device_rect.size.to_f32(); + let surface_origin_is_top_left = self.device.surface_origin_is_top_left(); + let (bottom, top) = if surface_origin_is_top_left { + (offset.y, offset.y + size.height) + } else { + (offset.y + size.height, offset.y) + }; + + let projection = Transform3D::ortho( + offset.x, + offset.x + size.width, + bottom, + top, + self.device.ortho_near_plane(), + self.device.ortho_far_plane(), + ); + + let fb_scale = Scale::<_, _, FramebufferPixel>::new(1i32); + let mut fb_rect = frame.device_rect * fb_scale; + + if !surface_origin_is_top_left { + fb_rect.origin.y = device_size.height - fb_rect.origin.y - fb_rect.size.height; + } + + let draw_target = DrawTarget::Default { + rect: fb_rect, + total_size: device_size * fb_scale, + surface_origin_is_top_left, + }; + + // Picture caching can be enabled / disabled dynamically from frame to + // frame. This is determined by what the frame builder selected, and is + // passed to the renderer via the composite state. + if frame.composite_state.picture_caching_is_enabled { + // If we have a native OS compositor, then make use of that interface + // to specify how to composite each of the picture cache surfaces. + match self.current_compositor_kind { + CompositorKind::Native { .. } => { + self.update_external_native_surfaces( + &frame.composite_state.external_surfaces, + results, + ); + let compositor = self.compositor_config.compositor().unwrap(); + frame.composite_state.composite_native(&mut **compositor); + } + CompositorKind::Draw { max_partial_present_rects, draw_previous_partial_present_regions, .. } => { + self.composite_simple( + &frame.composite_state, + clear_framebuffer, + draw_target, + &projection, + results, + max_partial_present_rects, + draw_previous_partial_present_regions, + ); + } + } + } else { + if clear_framebuffer { + let clear_color = self.clear_color.map(|color| color.to_array()); + self.device.bind_draw_target(draw_target); + self.device.enable_depth_write(); + self.device.clear_target(clear_color, + Some(1.0), + None); + } + + // If picture caching is disabled, we will be drawing the entire + // framebuffer. In that case, we need to push a screen size dirty + // rect, in case partial present is enabled (an empty array of + // dirty rects when partial present is enabled is interpreted by + // Gecko as meaning nothing has changed and a swap is not required). + results.dirty_rects.push(frame.device_rect); + + self.draw_color_target( + draw_target, + main_target, + frame.content_origin, + None, + None, + &frame.render_tasks, + &projection, + frame_id, + &mut results.stats, + ); + } + } + } + RenderPassKind::OffScreen { + ref mut alpha, + ref mut color, + ref mut texture_cache, + ref mut picture_cache, + } => { + profile_scope!("offscreen target"); + + let alpha_tex = self.allocate_target_texture(alpha, &mut frame.profile_counters); + let color_tex = self.allocate_target_texture(color, &mut frame.profile_counters); + + // If this frame has already been drawn, then any texture + // cache targets have already been updated and can be + // skipped this time. + if !frame.has_been_rendered { + for (&(texture_id, target_index), target) in texture_cache { + self.draw_texture_cache_target( + &texture_id, + target_index, + target, + &frame.render_tasks, + &mut results.stats, + ); + } + + if !picture_cache.is_empty() { + self.profile_counters.color_passes.inc(); + } + + // Draw picture caching tiles for this pass. + for picture_target in picture_cache { + results.stats.color_target_count += 1; + + let draw_target = match picture_target.surface { + ResolvedSurfaceTexture::TextureCache { ref texture, layer } => { + let (texture, _) = self.texture_resolver + .resolve(texture) + .expect("bug"); + + DrawTarget::from_texture( + texture, + layer as usize, + true, + ) + } + ResolvedSurfaceTexture::Native { id, size } => { + let surface_info = match self.current_compositor_kind { + CompositorKind::Native { .. } => { + let compositor = self.compositor_config.compositor().unwrap(); + compositor.bind( + id, + picture_target.dirty_rect, + picture_target.valid_rect, + ) + } + CompositorKind::Draw { .. } => { + unreachable!(); + } + }; + + DrawTarget::NativeSurface { + offset: surface_info.origin, + external_fbo_id: surface_info.fbo_id, + dimensions: size, + } + } + }; + + let projection = Transform3D::ortho( + 0.0, + draw_target.dimensions().width as f32, + 0.0, + draw_target.dimensions().height as f32, + self.device.ortho_near_plane(), + self.device.ortho_far_plane(), + ); + + self.draw_picture_cache_target( + picture_target, + draw_target, + frame.content_origin, + &projection, + &frame.render_tasks, + &mut results.stats, + ); + + // Native OS surfaces must be unbound at the end of drawing to them + if let ResolvedSurfaceTexture::Native { .. } = picture_target.surface { + match self.current_compositor_kind { + CompositorKind::Native { .. } => { + let compositor = self.compositor_config.compositor().unwrap(); + compositor.unbind(); + } + CompositorKind::Draw { .. } => { + unreachable!(); + } + } + } + } + } + + for (target_index, target) in alpha.targets.iter().enumerate() { + results.stats.alpha_target_count += 1; + let draw_target = DrawTarget::from_texture( + &alpha_tex.as_ref().unwrap().texture, + target_index, + false, + ); + + let projection = Transform3D::ortho( + 0.0, + draw_target.dimensions().width as f32, + 0.0, + draw_target.dimensions().height as f32, + self.device.ortho_near_plane(), + self.device.ortho_far_plane(), + ); + + self.draw_alpha_target( + draw_target, + target, + &projection, + &frame.render_tasks, + &mut results.stats, + ); + } + + for (target_index, target) in color.targets.iter().enumerate() { + results.stats.color_target_count += 1; + let draw_target = DrawTarget::from_texture( + &color_tex.as_ref().unwrap().texture, + target_index, + target.needs_depth(), + ); + + let projection = Transform3D::ortho( + 0.0, + draw_target.dimensions().width as f32, + 0.0, + draw_target.dimensions().height as f32, + self.device.ortho_near_plane(), + self.device.ortho_far_plane(), + ); + + let clear_depth = if target.needs_depth() { + Some(1.0) + } else { + None + }; + + self.draw_color_target( + draw_target, + target, + frame.content_origin, + Some([0.0, 0.0, 0.0, 0.0]), + clear_depth, + &frame.render_tasks, + &projection, + frame_id, + &mut results.stats, + ); + } + + // Only end the pass here and invalidate previous textures for + // off-screen targets. Deferring return of the inputs to the + // frame buffer until the implicit end_pass in end_frame allows + // debug draw overlays to be added without triggering a copy + // resolve stage in mobile / tiled GPUs. + self.texture_resolver.end_pass( + &mut self.device, + alpha_tex, + color_tex, + ); + } + } + { + profile_scope!("gl.flush"); + self.device.gl().flush(); + } + } + + if let Some(device_size) = device_size { + self.draw_frame_debug_items(&frame.debug_items); + self.draw_render_target_debug(device_size); + self.draw_texture_cache_debug(device_size); + self.draw_gpu_cache_debug(device_size); + self.draw_zoom_debug(device_size); + } + self.draw_epoch_debug(); + + // Garbage collect any frame outputs that weren't used this frame. + let device = &mut self.device; + self.output_targets + .retain(|_, target| if target.last_access != frame_id { + device.delete_fbo(target.fbo_id); + false + } else { + true + }); + + frame.has_been_rendered = true; + } + + /// Initialize the PLS block, by reading the current framebuffer color. + pub fn init_pixel_local_storage( + &mut self, + task_rect: DeviceIntRect, + projection: &default::Transform3D, + stats: &mut RendererStats, + ) { + self.device.enable_pixel_local_storage(true); + + self.shaders + .borrow_mut() + .pls_init + .as_mut() + .unwrap() + .bind( + &mut self.device, + projection, + &mut self.renderer_errors, + ); + + let instances = [ + ResolveInstanceData::new(task_rect), + ]; + + self.draw_instanced_batch( + &instances, + VertexArrayKind::Resolve, + &BatchTextures::no_texture(), + stats, + ); + } + + /// Resolve the current PLS structure, writing it to a fragment color output. + pub fn resolve_pixel_local_storage( + &mut self, + task_rect: DeviceIntRect, + projection: &default::Transform3D, + stats: &mut RendererStats, + ) { + self.shaders + .borrow_mut() + .pls_resolve + .as_mut() + .unwrap() + .bind( + &mut self.device, + projection, + &mut self.renderer_errors, + ); + + let instances = [ + ResolveInstanceData::new(task_rect), + ]; + + self.draw_instanced_batch( + &instances, + VertexArrayKind::Resolve, + &BatchTextures::no_texture(), + stats, + ); + + self.device.enable_pixel_local_storage(false); + } + + pub fn debug_renderer(&mut self) -> Option<&mut DebugRenderer> { + self.debug.get_mut(&mut self.device) + } + + pub fn get_debug_flags(&self) -> DebugFlags { + self.debug_flags + } + + pub fn set_debug_flags(&mut self, flags: DebugFlags) { + if let Some(enabled) = flag_changed(self.debug_flags, flags, DebugFlags::GPU_TIME_QUERIES) { + if enabled { + self.gpu_profile.enable_timers(); + } else { + self.gpu_profile.disable_timers(); + } + } + if let Some(enabled) = flag_changed(self.debug_flags, flags, DebugFlags::GPU_SAMPLE_QUERIES) { + if enabled { + self.gpu_profile.enable_samplers(); + } else { + self.gpu_profile.disable_samplers(); + } + } + + self.debug_flags = flags; + } + + fn draw_frame_debug_items(&mut self, items: &[DebugItem]) { + if items.is_empty() { + return; + } + + let debug_renderer = match self.debug.get_mut(&mut self.device) { + Some(render) => render, + None => return, + }; + + for item in items { + match item { + DebugItem::Rect { rect, outer_color, inner_color } => { + debug_renderer.add_quad( + rect.origin.x, + rect.origin.y, + rect.origin.x + rect.size.width, + rect.origin.y + rect.size.height, + (*inner_color).into(), + (*inner_color).into(), + ); + + debug_renderer.add_rect( + &rect.to_i32(), + (*outer_color).into(), + ); + } + DebugItem::Text { ref msg, position, color } => { + debug_renderer.add_text( + position.x, + position.y, + msg, + (*color).into(), + None, + ); + } + } + } + } + + fn draw_render_target_debug(&mut self, device_size: DeviceIntSize) { + if !self.debug_flags.contains(DebugFlags::RENDER_TARGET_DBG) { + return; + } + + let debug_renderer = match self.debug.get_mut(&mut self.device) { + Some(render) => render, + None => return, + }; + + let textures = + self.texture_resolver.render_target_pool.iter().collect::>(); + + Self::do_debug_blit( + &mut self.device, + debug_renderer, + textures, + device_size, + 0, + &|_| [0.0, 1.0, 0.0, 1.0], // Use green for all RTs. + ); + } + + fn draw_zoom_debug( + &mut self, + device_size: DeviceIntSize, + ) { + if !self.debug_flags.contains(DebugFlags::ZOOM_DBG) { + return; + } + + let debug_renderer = match self.debug.get_mut(&mut self.device) { + Some(render) => render, + None => return, + }; + + let source_size = DeviceIntSize::new(64, 64); + let target_size = DeviceIntSize::new(1024, 1024); + + let source_origin = DeviceIntPoint::new( + (self.cursor_position.x - source_size.width / 2) + .min(device_size.width - source_size.width) + .max(0), + (self.cursor_position.y - source_size.height / 2) + .min(device_size.height - source_size.height) + .max(0), + ); + + let source_rect = DeviceIntRect::new( + source_origin, + source_size, + ); + + let target_rect = DeviceIntRect::new( + DeviceIntPoint::new( + device_size.width - target_size.width - 64, + device_size.height - target_size.height - 64, + ), + target_size, + ); + + let texture_rect = FramebufferIntRect::new( + FramebufferIntPoint::zero(), + source_rect.size.cast_unit(), + ); + + debug_renderer.add_rect( + &target_rect.inflate(1, 1), + debug_colors::RED.into(), + ); + + if self.zoom_debug_texture.is_none() { + let texture = self.device.create_texture( + TextureTarget::Default, + ImageFormat::BGRA8, + source_rect.size.width, + source_rect.size.height, + TextureFilter::Nearest, + Some(RenderTargetInfo { has_depth: false }), + 1, + ); + + self.zoom_debug_texture = Some(texture); + } + + // Copy frame buffer into the zoom texture + let read_target = DrawTarget::new_default(device_size, self.device.surface_origin_is_top_left()); + self.device.blit_render_target( + read_target.into(), + read_target.to_framebuffer_rect(source_rect), + DrawTarget::from_texture( + self.zoom_debug_texture.as_ref().unwrap(), + 0, + false, + ), + texture_rect, + TextureFilter::Nearest, + ); + + // Draw the zoom texture back to the framebuffer + self.device.blit_render_target( + ReadTarget::from_texture( + self.zoom_debug_texture.as_ref().unwrap(), + 0, + ), + texture_rect, + read_target, + read_target.to_framebuffer_rect(target_rect), + TextureFilter::Nearest, + ); + } + + fn draw_texture_cache_debug(&mut self, device_size: DeviceIntSize) { + if !self.debug_flags.contains(DebugFlags::TEXTURE_CACHE_DBG) { + return; + } + + let debug_renderer = match self.debug.get_mut(&mut self.device) { + Some(render) => render, + None => return, + }; + + let textures = + self.texture_resolver.texture_cache_map.values().collect::>(); + + fn select_color(texture: &Texture) -> [f32; 4] { + if texture.flags().contains(TextureFlags::IS_SHARED_TEXTURE_CACHE) { + [1.0, 0.5, 0.0, 1.0] // Orange for shared. + } else { + [1.0, 0.0, 1.0, 1.0] // Fuchsia for standalone. + } + } + + Self::do_debug_blit( + &mut self.device, + debug_renderer, + textures, + device_size, + if self.debug_flags.contains(DebugFlags::RENDER_TARGET_DBG) { 544 } else { 0 }, + &select_color, + ); + } + + fn do_debug_blit( + device: &mut Device, + debug_renderer: &mut DebugRenderer, + mut textures: Vec<&Texture>, + device_size: DeviceIntSize, + bottom: i32, + select_color: &dyn Fn(&Texture) -> [f32; 4], + ) { + let mut spacing = 16; + let mut size = 512; + + let fb_width = device_size.width; + let fb_height = device_size.height; + let num_layers: i32 = textures.iter() + .map(|texture| texture.get_layer_count()) + .sum(); + + if num_layers * (size + spacing) > fb_width { + let factor = fb_width as f32 / (num_layers * (size + spacing)) as f32; + size = (size as f32 * factor) as i32; + spacing = (spacing as f32 * factor) as i32; + } + + // Sort the display by layer size (in bytes), so that left-to-right is + // largest-to-smallest. + // + // Note that the vec here is in increasing order, because the elements + // get drawn right-to-left. + textures.sort_by_key(|t| t.layer_size_in_bytes()); + + let mut i = 0; + for texture in textures.iter() { + let y = spacing + bottom; + let dimensions = texture.get_dimensions(); + let src_rect = FramebufferIntRect::new( + FramebufferIntPoint::zero(), + FramebufferIntSize::new(dimensions.width as i32, dimensions.height as i32), + ); + + let layer_count = texture.get_layer_count() as usize; + for layer in 0 .. layer_count { + let x = fb_width - (spacing + size) * (i as i32 + 1); + + // If we have more targets than fit on one row in screen, just early exit. + if x > fb_width { + return; + } + + //TODO: properly use FramebufferPixel coordinates + + // Draw the info tag. + let text_margin = 1; + let text_height = 14; // Visually aproximated. + let tag_height = text_height + text_margin * 2; + let tag_rect = rect(x, y, size, tag_height); + let tag_color = select_color(texture); + device.clear_target( + Some(tag_color), + None, + Some(tag_rect.cast_unit()), + ); + + // Draw the dimensions onto the tag. + let dim = texture.get_dimensions(); + let mut text_rect = tag_rect; + text_rect.origin.y = + fb_height - text_rect.origin.y - text_rect.size.height; // Top-relative. + debug_renderer.add_text( + (x + text_margin) as f32, + (fb_height - y - text_margin) as f32, // Top-relative. + &format!("{}x{}", dim.width, dim.height), + ColorU::new(0, 0, 0, 255), + Some(text_rect.to_f32()) + ); + + // Blit the contents of the layer. We need to invert Y because + // we're blitting from a texture to the main framebuffer, which + // use different conventions. + let dest_rect = rect(x, y + tag_height, size, size); + if !device.surface_origin_is_top_left() { + device.blit_render_target_invert_y( + ReadTarget::from_texture(texture, layer), + src_rect, + DrawTarget::new_default(device_size, device.surface_origin_is_top_left()), + FramebufferIntRect::from_untyped(&dest_rect), + ); + } else { + device.blit_render_target( + ReadTarget::from_texture(texture, layer), + src_rect, + DrawTarget::new_default(device_size, device.surface_origin_is_top_left()), + FramebufferIntRect::from_untyped(&dest_rect), + TextureFilter::Linear, + ); + } + i += 1; + } + } + } + + fn draw_epoch_debug(&mut self) { + if !self.debug_flags.contains(DebugFlags::EPOCHS) { + return; + } + + let debug_renderer = match self.debug.get_mut(&mut self.device) { + Some(render) => render, + None => return, + }; + + let dy = debug_renderer.line_height(); + let x0: f32 = 30.0; + let y0: f32 = 30.0; + let mut y = y0; + let mut text_width = 0.0; + for ((pipeline, document_id), epoch) in &self.pipeline_info.epochs { + y += dy; + let w = debug_renderer.add_text( + x0, y, + &format!("({:?}, {:?}): {:?}", pipeline, document_id, epoch), + ColorU::new(255, 255, 0, 255), + None, + ).size.width; + text_width = f32::max(text_width, w); + } + + let margin = 10.0; + debug_renderer.add_quad( + x0 - margin, + y0 - margin, + x0 + text_width + margin, + y + margin, + ColorU::new(25, 25, 25, 200), + ColorU::new(51, 51, 51, 200), + ); + } + + fn draw_gpu_cache_debug(&mut self, device_size: DeviceIntSize) { + if !self.debug_flags.contains(DebugFlags::GPU_CACHE_DBG) { + return; + } + + let debug_renderer = match self.debug.get_mut(&mut self.device) { + Some(render) => render, + None => return, + }; + + let (x_off, y_off) = (30f32, 30f32); + let height = self.gpu_cache_texture.texture + .as_ref().map_or(0, |t| t.get_dimensions().height) + .min(device_size.height - (y_off as i32) * 2) as usize; + debug_renderer.add_quad( + x_off, + y_off, + x_off + MAX_VERTEX_TEXTURE_WIDTH as f32, + y_off + height as f32, + ColorU::new(80, 80, 80, 80), + ColorU::new(80, 80, 80, 80), + ); + + let upper = self.gpu_cache_debug_chunks.len().min(height); + for chunk in self.gpu_cache_debug_chunks[0..upper].iter().flatten() { + let color = ColorU::new(250, 0, 0, 200); + debug_renderer.add_quad( + x_off + chunk.address.u as f32, + y_off + chunk.address.v as f32, + x_off + chunk.address.u as f32 + chunk.size as f32, + y_off + chunk.address.v as f32 + 1.0, + color, + color, + ); + } + } + + /// Pass-through to `Device::read_pixels_into`, used by Gecko's WR bindings. + pub fn read_pixels_into(&mut self, rect: FramebufferIntRect, format: ImageFormat, output: &mut [u8]) { + self.device.read_pixels_into(rect, format, output); + } + + pub fn read_pixels_rgba8(&mut self, rect: FramebufferIntRect) -> Vec { + let mut pixels = vec![0; (rect.size.width * rect.size.height * 4) as usize]; + self.device.read_pixels_into(rect, ImageFormat::RGBA8, &mut pixels); + pixels + } + + pub fn read_gpu_cache(&mut self) -> (DeviceIntSize, Vec) { + let texture = self.gpu_cache_texture.texture.as_ref().unwrap(); + let size = device_size_as_framebuffer_size(texture.get_dimensions()); + let mut texels = vec![0; (size.width * size.height * 16) as usize]; + self.device.begin_frame(); + self.device.bind_read_target(ReadTarget::from_texture(texture, 0)); + self.device.read_pixels_into( + size.into(), + ImageFormat::RGBAF32, + &mut texels, + ); + self.device.reset_read_target(); + self.device.end_frame(); + (texture.get_dimensions(), texels) + } + + // De-initialize the Renderer safely, assuming the GL is still alive and active. + pub fn deinit(mut self) { + //Note: this is a fake frame, only needed because texture deletion is require to happen inside a frame + self.device.begin_frame(); + // If we are using a native compositor, ensure that any remaining native + // surfaces are freed. + if let CompositorConfig::Native { mut compositor, .. } = self.compositor_config { + for id in self.allocated_native_surfaces.drain() { + compositor.destroy_surface(id); + } + // Destroy the debug overlay surface, if currently allocated. + if self.debug_overlay_state.current_size.is_some() { + compositor.destroy_surface(NativeSurfaceId::DEBUG_OVERLAY); + } + compositor.deinit(); + } + self.gpu_cache_texture.deinit(&mut self.device); + if let Some(dither_matrix_texture) = self.dither_matrix_texture { + self.device.delete_texture(dither_matrix_texture); + } + if let Some(zoom_debug_texture) = self.zoom_debug_texture { + self.device.delete_texture(zoom_debug_texture); + } + for textures in self.vertex_data_textures.drain(..) { + textures.deinit(&mut self.device); + } + self.device.delete_pbo(self.texture_cache_upload_pbo); + self.texture_resolver.deinit(&mut self.device); + self.device.delete_vao(self.vaos.prim_vao); + self.device.delete_vao(self.vaos.resolve_vao); + self.device.delete_vao(self.vaos.clip_vao); + self.device.delete_vao(self.vaos.gradient_vao); + self.device.delete_vao(self.vaos.blur_vao); + self.device.delete_vao(self.vaos.line_vao); + self.device.delete_vao(self.vaos.border_vao); + self.device.delete_vao(self.vaos.scale_vao); + self.device.delete_vao(self.vaos.svg_filter_vao); + self.device.delete_vao(self.vaos.composite_vao); + self.device.delete_vao(self.vaos.clear_vao); + + self.debug.deinit(&mut self.device); + + for (_, target) in self.output_targets { + self.device.delete_fbo(target.fbo_id); + } + if let Ok(shaders) = Rc::try_unwrap(self.shaders) { + shaders.into_inner().deinit(&mut self.device); + } + + if let Some(async_screenshots) = self.async_screenshots.take() { + async_screenshots.deinit(&mut self.device); + } + + if let Some(async_frame_recorder) = self.async_frame_recorder.take() { + async_frame_recorder.deinit(&mut self.device); + } + + #[cfg(feature = "capture")] + self.device.delete_fbo(self.read_fbo); + #[cfg(feature = "replay")] + for (_, ext) in self.owned_external_images { + self.device.delete_external_texture(ext); + } + self.device.end_frame(); + } + + fn size_of(&self, ptr: *const T) -> usize { + let op = self.size_of_ops.as_ref().unwrap().size_of_op; + unsafe { op(ptr as *const c_void) } + } + + /// Collects a memory report. + pub fn report_memory(&self) -> MemoryReport { + let mut report = MemoryReport::default(); + + // GPU cache CPU memory. + if let GpuCacheBus::PixelBuffer{ref rows, ..} = self.gpu_cache_texture.bus { + for row in rows.iter() { + report.gpu_cache_cpu_mirror += self.size_of(&*row.cpu_blocks as *const _); + } + } + + // GPU cache GPU memory. + report.gpu_cache_textures += + self.gpu_cache_texture.texture.as_ref().map_or(0, |t| t.size_in_bytes()); + + // Render task CPU memory. + for (_id, doc) in &self.active_documents { + report.render_tasks += self.size_of(doc.frame.render_tasks.tasks.as_ptr()); + report.render_tasks += self.size_of(doc.frame.render_tasks.task_data.as_ptr()); + } + + // Vertex data GPU memory. + for textures in &self.vertex_data_textures { + report.vertex_data_textures += textures.size_in_bytes(); + } + + // Texture cache and render target GPU memory. + report += self.texture_resolver.report_memory(); + + // Textures held internally within the device layer. + report += self.device.report_memory(); + + report + } + + // Sets the blend mode. Blend is unconditionally set if the "show overdraw" debugging mode is + // enabled. + fn set_blend(&mut self, mut blend: bool, framebuffer_kind: FramebufferKind) { + if framebuffer_kind == FramebufferKind::Main && + self.debug_flags.contains(DebugFlags::SHOW_OVERDRAW) { + blend = true + } + self.device.set_blend(blend) + } + + fn set_blend_mode_multiply(&mut self, framebuffer_kind: FramebufferKind) { + if framebuffer_kind == FramebufferKind::Main && + self.debug_flags.contains(DebugFlags::SHOW_OVERDRAW) { + self.device.set_blend_mode_show_overdraw(); + } else { + self.device.set_blend_mode_multiply(); + } + } + + fn set_blend_mode_premultiplied_alpha(&mut self, framebuffer_kind: FramebufferKind) { + if framebuffer_kind == FramebufferKind::Main && + self.debug_flags.contains(DebugFlags::SHOW_OVERDRAW) { + self.device.set_blend_mode_show_overdraw(); + } else { + self.device.set_blend_mode_premultiplied_alpha(); + } + } + + fn set_blend_mode_subpixel_with_bg_color_pass1(&mut self, framebuffer_kind: FramebufferKind) { + if framebuffer_kind == FramebufferKind::Main && + self.debug_flags.contains(DebugFlags::SHOW_OVERDRAW) { + self.device.set_blend_mode_show_overdraw(); + } else { + self.device.set_blend_mode_subpixel_with_bg_color_pass1(); + } + } + + fn set_blend_mode_subpixel_with_bg_color_pass2(&mut self, framebuffer_kind: FramebufferKind) { + if framebuffer_kind == FramebufferKind::Main && + self.debug_flags.contains(DebugFlags::SHOW_OVERDRAW) { + self.device.set_blend_mode_show_overdraw(); + } else { + self.device.set_blend_mode_subpixel_with_bg_color_pass2(); + } + } + + /// Clears all the layers of a texture with a given color. + fn clear_texture(&mut self, texture: &Texture, color: [f32; 4]) { + for i in 0..texture.get_layer_count() { + self.device.bind_draw_target(DrawTarget::from_texture( + &texture, + i as usize, + false, + )); + self.device.clear_target(Some(color), None, None); + } + } +} + +pub trait ThreadListener { + fn thread_started(&self, thread_name: &str); + fn thread_stopped(&self, thread_name: &str); +} + +/// Allows callers to hook in at certain points of the async scene build. These +/// functions are all called from the scene builder thread. +pub trait SceneBuilderHooks { + /// This is called exactly once, when the scene builder thread is started + /// and before it processes anything. + fn register(&self); + /// This is called before each scene build starts. + fn pre_scene_build(&self); + /// This is called before each scene swap occurs. + fn pre_scene_swap(&self, scenebuild_time: u64); + /// This is called after each scene swap occurs. The PipelineInfo contains + /// the updated epochs and pipelines removed in the new scene compared to + /// the old scene. + fn post_scene_swap(&self, document_id: &Vec, info: PipelineInfo, sceneswap_time: u64); + /// This is called after a resource update operation on the scene builder + /// thread, in the case where resource updates were applied without a scene + /// build. + fn post_resource_update(&self, document_ids: &Vec); + /// This is called after a scene build completes without any changes being + /// made. We guarantee that each pre_scene_build call will be matched with + /// exactly one of post_scene_swap, post_resource_update or + /// post_empty_scene_build. + fn post_empty_scene_build(&self); + /// This is a generic callback which provides an opportunity to run code + /// on the scene builder thread. This is called as part of the main message + /// loop of the scene builder thread, but outside of any specific message + /// handler. + fn poke(&self); + /// This is called exactly once, when the scene builder thread is about to + /// terminate. + fn deregister(&self); +} + +/// Allows callers to hook into the main render_backend loop and provide +/// additional frame ops for generate_frame transactions. These functions +/// are all called from the render backend thread. +pub trait AsyncPropertySampler { + /// This is called exactly once, when the render backend thread is started + /// and before it processes anything. + fn register(&self); + /// This is called for each transaction with the generate_frame flag set + /// (i.e. that will trigger a render). The list of frame messages returned + /// are processed as though they were part of the original transaction. + fn sample(&self, document_id: DocumentId, + doc: &FastHashMap) -> Vec; + /// This is called exactly once, when the render backend thread is about to + /// terminate. + fn deregister(&self); +} + +bitflags! { + /// Flags that control how shaders are pre-cached, if at all. + #[derive(Default)] + pub struct ShaderPrecacheFlags: u32 { + /// Needed for const initialization + const EMPTY = 0; + + /// Only start async compile + const ASYNC_COMPILE = 1 << 2; + + /// Do a full compile/link during startup + const FULL_COMPILE = 1 << 3; + } +} + +pub struct RendererOptions { + pub device_pixel_ratio: f32, + pub resource_override_path: Option, + /// Whether to use shaders that have been optimized at build time. + pub use_optimized_shaders: bool, + pub enable_aa: bool, + pub enable_dithering: bool, + pub max_recorded_profiles: usize, + pub precache_flags: ShaderPrecacheFlags, + /// Enable sub-pixel anti-aliasing if a fast implementation is available. + pub enable_subpixel_aa: bool, + /// Enable sub-pixel anti-aliasing if it requires a slow implementation. + pub force_subpixel_aa: bool, + pub clear_color: Option, + pub enable_clear_scissor: bool, + pub max_texture_size: Option, + pub max_glyph_cache_size: Option, + pub upload_method: UploadMethod, + pub workers: Option>, + pub enable_multithreading: bool, + pub blob_image_handler: Option>, + pub thread_listener: Option>, + pub size_of_op: Option, + pub enclosing_size_of_op: Option, + pub cached_programs: Option>, + pub debug_flags: DebugFlags, + pub renderer_id: Option, + pub scene_builder_hooks: Option>, + pub sampler: Option>, + pub chase_primitive: ChasePrimitive, + pub support_low_priority_transactions: bool, + pub namespace_alloc_by_client: bool, + pub enable_picture_caching: bool, + pub testing: bool, + /// Set to true if this GPU supports hardware fast clears as a performance + /// optimization. Likely requires benchmarking on various GPUs to see if + /// it is a performance win. The default is false, which tends to be best + /// performance on lower end / integrated GPUs. + pub gpu_supports_fast_clears: bool, + pub allow_dual_source_blending: bool, + pub allow_advanced_blend_equation: bool, + /// If true, allow WR to use pixel local storage if the device supports it. + /// For now, this defaults to false since the code is still experimental + /// and not complete. This option will probably be removed once support is + /// complete, and WR can implicitly choose whether to make use of PLS. + pub allow_pixel_local_storage_support: bool, + /// If true, allow textures to be initialized with glTexStorage. + /// This affects VRAM consumption and data upload paths. + pub allow_texture_storage_support: bool, + /// If true, we allow the data uploaded in a different format from the + /// one expected by the driver, pretending the format is matching, and + /// swizzling the components on all the shader sampling. + pub allow_texture_swizzling: bool, + /// Number of batches to look back in history for adding the current + /// transparent instance into. + pub batch_lookback_count: usize, + /// Use `ps_clear` shader with batched quad rendering to clear the rects + /// in texture cache and picture cache tasks. + /// This helps to work around some Intel drivers + /// that incorrectly synchronize clears to following draws. + pub clear_caches_with_quads: bool, + /// Start the debug server for this renderer. + pub start_debug_server: bool, + /// Output the source of the shader with the given name. + pub dump_shader_source: Option, + pub surface_origin_is_top_left: bool, + /// The configuration options defining how WR composites the final scene. + pub compositor_config: CompositorConfig, + pub enable_gpu_markers: bool, + /// If true, panic whenever a GL error occurs. This has a significant + /// performance impact, so only use when debugging specific problems! + pub panic_on_gl_error: bool, + /// If the total bytes allocated in shared / standalone cache is less + /// than this, then allow the cache to grow without forcing an eviction. + pub texture_cache_eviction_threshold_bytes: usize, + /// The maximum number of items that will be evicted per frame. This limit helps avoid jank + /// on frames where we want to evict a large number of items. Instead, we'd prefer to drop + /// the items incrementally over a number of frames, even if that means the total allocated + /// size of the cache is above the desired threshold for a small number of frames. + pub texture_cache_max_evictions_per_frame: usize, +} + +impl Default for RendererOptions { + fn default() -> Self { + RendererOptions { + device_pixel_ratio: 1.0, + resource_override_path: None, + use_optimized_shaders: false, + enable_aa: true, + enable_dithering: false, + debug_flags: DebugFlags::empty(), + max_recorded_profiles: 0, + precache_flags: ShaderPrecacheFlags::empty(), + enable_subpixel_aa: false, + force_subpixel_aa: false, + clear_color: Some(ColorF::new(1.0, 1.0, 1.0, 1.0)), + enable_clear_scissor: true, + max_texture_size: None, + max_glyph_cache_size: None, + // This is best as `Immediate` on Angle, or `Pixelbuffer(Dynamic)` on GL, + // but we are unable to make this decision here, so picking the reasonable medium. + upload_method: UploadMethod::PixelBuffer(VertexUsageHint::Stream), + workers: None, + enable_multithreading: true, + blob_image_handler: None, + thread_listener: None, + size_of_op: None, + enclosing_size_of_op: None, + renderer_id: None, + cached_programs: None, + scene_builder_hooks: None, + sampler: None, + chase_primitive: ChasePrimitive::Nothing, + support_low_priority_transactions: false, + namespace_alloc_by_client: false, + enable_picture_caching: false, + testing: false, + gpu_supports_fast_clears: false, + allow_dual_source_blending: true, + allow_advanced_blend_equation: false, + allow_pixel_local_storage_support: false, + allow_texture_storage_support: true, + allow_texture_swizzling: true, + batch_lookback_count: DEFAULT_BATCH_LOOKBACK_COUNT, + clear_caches_with_quads: true, + // For backwards compatibility we set this to true by default, so + // that if the debugger feature is enabled, the debug server will + // be started automatically. Users can explicitly disable this as + // needed. + start_debug_server: true, + dump_shader_source: None, + surface_origin_is_top_left: false, + compositor_config: CompositorConfig::default(), + enable_gpu_markers: true, + panic_on_gl_error: false, + texture_cache_eviction_threshold_bytes: 64 * 1024 * 1024, + texture_cache_max_evictions_per_frame: 32, + } + } +} + +pub trait DebugServer { + fn send(&mut self, _message: String); +} + +struct NoopDebugServer; + +impl NoopDebugServer { + fn new(_: Sender) -> Self { + NoopDebugServer + } +} + +impl DebugServer for NoopDebugServer { + fn send(&mut self, _: String) {} +} + +#[cfg(feature = "debugger")] +fn new_debug_server(enable: bool, api_tx: Sender) -> Box { + if enable { + Box::new(debug_server::DebugServerImpl::new(api_tx)) + } else { + Box::new(NoopDebugServer::new(api_tx)) + } +} + +#[cfg(not(feature = "debugger"))] +fn new_debug_server(_enable: bool, api_tx: Sender) -> Box { + Box::new(NoopDebugServer::new(api_tx)) +} + +/// Some basic statistics about the rendered scene, used in Gecko, as +/// well as in wrench reftests to ensure that tests are batching and/or +/// allocating on render targets as we expect them to. +#[repr(C)] +#[derive(Debug, Default)] +pub struct RendererStats { + pub total_draw_calls: usize, + pub alpha_target_count: usize, + pub color_target_count: usize, + pub texture_upload_kb: usize, + pub resource_upload_time: u64, + pub gpu_cache_upload_time: u64, +} + +/// Return type from render(), which contains some repr(C) statistics as well as +/// some non-repr(C) data. +#[derive(Debug, Default)] +pub struct RenderResults { + /// Statistics about the frame that was rendered. + pub stats: RendererStats, + + /// A list of dirty world rects. This is only currently + /// useful to test infrastructure. + /// TODO(gw): This needs to be refactored / removed. + pub recorded_dirty_regions: Vec, + + /// A list of the device dirty rects that were updated + /// this frame. + /// TODO(gw): This is an initial interface, likely to change in future. + /// TODO(gw): The dirty rects here are currently only useful when scrolling + /// is not occurring. They are still correct in the case of + /// scrolling, but will be very large (until we expose proper + /// OS compositor support where the dirty rects apply to a + /// specific picture cache slice / OS compositor surface). + pub dirty_rects: Vec, +} + +#[cfg(any(feature = "capture", feature = "replay"))] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct PlainTexture { + data: String, + size: (DeviceIntSize, i32), + format: ImageFormat, + filter: TextureFilter, + has_depth: bool, +} + + +#[cfg(any(feature = "capture", feature = "replay"))] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct PlainRenderer { + device_size: Option, + gpu_cache: PlainTexture, + gpu_cache_frame_id: FrameId, + textures: FastHashMap, +} + +#[cfg(any(feature = "capture", feature = "replay"))] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct PlainExternalResources { + images: Vec +} + +#[cfg(feature = "replay")] +enum CapturedExternalImageData { + NativeTexture(gl::GLuint), + Buffer(Arc>), +} + +#[cfg(feature = "replay")] +struct DummyExternalImageHandler { + data: FastHashMap<(ExternalImageId, u8), (CapturedExternalImageData, TexelRect)>, +} + +#[cfg(feature = "replay")] +impl ExternalImageHandler for DummyExternalImageHandler { + fn lock(&mut self, key: ExternalImageId, channel_index: u8, _rendering: ImageRendering) -> ExternalImage { + let (ref captured_data, ref uv) = self.data[&(key, channel_index)]; + ExternalImage { + uv: *uv, + source: match *captured_data { + CapturedExternalImageData::NativeTexture(tid) => ExternalImageSource::NativeTexture(tid), + CapturedExternalImageData::Buffer(ref arc) => ExternalImageSource::RawData(&*arc), + } + } + } + fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) {} +} + +#[cfg(feature = "replay")] +struct VoidHandler; + +#[cfg(feature = "replay")] +impl OutputImageHandler for VoidHandler { + fn lock(&mut self, _: PipelineId) -> Option<(u32, FramebufferIntSize)> { + None + } + fn unlock(&mut self, _: PipelineId) { + unreachable!() + } +} + +#[derive(Default)] +pub struct PipelineInfo { + pub epochs: FastHashMap<(PipelineId, DocumentId), Epoch>, + pub removed_pipelines: Vec<(PipelineId, DocumentId)>, +} + +impl Renderer { + #[cfg(feature = "capture")] + fn save_texture( + texture: &Texture, name: &str, root: &PathBuf, device: &mut Device + ) -> PlainTexture { + use std::fs; + use std::io::Write; + + let short_path = format!("textures/{}.raw", name); + + let bytes_per_pixel = texture.get_format().bytes_per_pixel(); + let read_format = texture.get_format(); + let rect_size = texture.get_dimensions(); + + let mut file = fs::File::create(root.join(&short_path)) + .expect(&format!("Unable to create {}", short_path)); + let bytes_per_layer = (rect_size.width * rect_size.height * bytes_per_pixel) as usize; + let mut data = vec![0; bytes_per_layer]; + + //TODO: instead of reading from an FBO with `read_pixels*`, we could + // read from textures directly with `get_tex_image*`. + + for layer_id in 0 .. texture.get_layer_count() { + let rect = device_size_as_framebuffer_size(rect_size).into(); + + device.attach_read_texture(texture, layer_id); + #[cfg(feature = "png")] + { + let mut png_data; + let (data_ref, format) = match texture.get_format() { + ImageFormat::RGBAF32 => { + png_data = vec![0; (rect_size.width * rect_size.height * 4) as usize]; + device.read_pixels_into(rect, ImageFormat::RGBA8, &mut png_data); + (&png_data, ImageFormat::RGBA8) + } + fm => (&data, fm), + }; + CaptureConfig::save_png( + root.join(format!("textures/{}-{}.png", name, layer_id)), + rect_size, format, + None, + data_ref, + ); + } + device.read_pixels_into(rect, read_format, &mut data); + file.write_all(&data) + .unwrap(); + } + + PlainTexture { + data: short_path, + size: (rect_size, texture.get_layer_count()), + format: texture.get_format(), + filter: texture.get_filter(), + has_depth: texture.supports_depth(), + } + } + + #[cfg(feature = "replay")] + fn load_texture( + target: TextureTarget, + plain: &PlainTexture, + rt_info: Option, + root: &PathBuf, + device: &mut Device + ) -> (Texture, Vec) + { + use std::fs::File; + use std::io::Read; + + let mut texels = Vec::new(); + File::open(root.join(&plain.data)) + .expect(&format!("Unable to open texture at {}", plain.data)) + .read_to_end(&mut texels) + .unwrap(); + + let texture = device.create_texture( + target, + plain.format, + plain.size.0.width, + plain.size.0.height, + plain.filter, + rt_info, + plain.size.1, + ); + device.upload_texture_immediate(&texture, &texels); + + (texture, texels) + } + + #[cfg(feature = "capture")] + fn save_capture( + &mut self, + config: CaptureConfig, + deferred_images: Vec, + ) { + use std::fs; + use std::io::Write; + use api::{CaptureBits, ExternalImageData}; + + let root = config.resource_root(); + + self.device.begin_frame(); + let _gm = self.gpu_profile.start_marker("read GPU data"); + self.device.bind_read_target_impl(self.read_fbo); + + if config.bits.contains(CaptureBits::EXTERNAL_RESOURCES) && !deferred_images.is_empty() { + info!("saving external images"); + let mut arc_map = FastHashMap::<*const u8, String>::default(); + let mut tex_map = FastHashMap::::default(); + let handler = self.external_image_handler + .as_mut() + .expect("Unable to lock the external image handler!"); + for def in &deferred_images { + info!("\t{}", def.short_path); + let ExternalImageData { id, channel_index, image_type } = def.external; + // The image rendering parameter is irrelevant because no filtering happens during capturing. + let ext_image = handler.lock(id, channel_index, ImageRendering::Auto); + let (data, short_path) = match ext_image.source { + ExternalImageSource::RawData(data) => { + let arc_id = arc_map.len() + 1; + match arc_map.entry(data.as_ptr()) { + Entry::Occupied(e) => { + (None, e.get().clone()) + } + Entry::Vacant(e) => { + let short_path = format!("externals/d{}.raw", arc_id); + (Some(data.to_vec()), e.insert(short_path).clone()) + } + } + } + ExternalImageSource::NativeTexture(gl_id) => { + let tex_id = tex_map.len() + 1; + match tex_map.entry(gl_id) { + Entry::Occupied(e) => { + (None, e.get().clone()) + } + Entry::Vacant(e) => { + let target = match image_type { + ExternalImageType::TextureHandle(target) => target, + ExternalImageType::Buffer => unreachable!(), + }; + info!("\t\tnative texture of target {:?}", target); + let layer_index = 0; //TODO: what about layered textures? + self.device.attach_read_texture_external(gl_id, target, layer_index); + let data = self.device.read_pixels(&def.descriptor); + let short_path = format!("externals/t{}.raw", tex_id); + (Some(data), e.insert(short_path).clone()) + } + } + } + ExternalImageSource::Invalid => { + info!("\t\tinvalid source!"); + (None, String::new()) + } + }; + if let Some(bytes) = data { + fs::File::create(root.join(&short_path)) + .expect(&format!("Unable to create {}", short_path)) + .write_all(&bytes) + .unwrap(); + #[cfg(feature = "png")] + CaptureConfig::save_png( + root.join(&short_path).with_extension("png"), + def.descriptor.size, + def.descriptor.format, + def.descriptor.stride, + &bytes, + ); + } + let plain = PlainExternalImage { + data: short_path, + external: def.external, + uv: ext_image.uv, + }; + config.serialize_for_resource(&plain, &def.short_path); + } + for def in &deferred_images { + handler.unlock(def.external.id, def.external.channel_index); + } + let plain_external = PlainExternalResources { + images: deferred_images, + }; + config.serialize_for_resource(&plain_external, "external_resources"); + } + + if config.bits.contains(CaptureBits::FRAME) { + let path_textures = root.join("textures"); + if !path_textures.is_dir() { + fs::create_dir(&path_textures).unwrap(); + } + + info!("saving GPU cache"); + self.update_gpu_cache(); // flush pending updates + let mut plain_self = PlainRenderer { + device_size: self.device_size, + gpu_cache: Self::save_texture( + &self.gpu_cache_texture.texture.as_ref().unwrap(), + "gpu", &root, &mut self.device, + ), + gpu_cache_frame_id: self.gpu_cache_frame_id, + textures: FastHashMap::default(), + }; + + info!("saving cached textures"); + for (id, texture) in &self.texture_resolver.texture_cache_map { + let file_name = format!("cache-{}", plain_self.textures.len() + 1); + info!("\t{}", file_name); + let plain = Self::save_texture(texture, &file_name, &root, &mut self.device); + plain_self.textures.insert(*id, plain); + } + + config.serialize_for_resource(&plain_self, "renderer"); + } + + self.device.reset_read_target(); + self.device.end_frame(); + info!("done."); + } + + #[cfg(feature = "replay")] + fn load_capture( + &mut self, + config: CaptureConfig, + plain_externals: Vec, + ) { + use std::fs::File; + use std::io::Read; + use std::slice; + + info!("loading external buffer-backed images"); + assert!(self.texture_resolver.external_images.is_empty()); + let mut raw_map = FastHashMap::>>::default(); + let mut image_handler = DummyExternalImageHandler { + data: FastHashMap::default(), + }; + + let root = config.resource_root(); + + // Note: this is a `SCENE` level population of the external image handlers + // It would put both external buffers and texture into the map. + // But latter are going to be overwritten later in this function + // if we are in the `FRAME` level. + for plain_ext in plain_externals { + let data = match raw_map.entry(plain_ext.data) { + Entry::Occupied(e) => e.get().clone(), + Entry::Vacant(e) => { + let mut buffer = Vec::new(); + File::open(root.join(e.key())) + .expect(&format!("Unable to open {}", e.key())) + .read_to_end(&mut buffer) + .unwrap(); + e.insert(Arc::new(buffer)).clone() + } + }; + let ext = plain_ext.external; + let value = (CapturedExternalImageData::Buffer(data), plain_ext.uv); + image_handler.data.insert((ext.id, ext.channel_index), value); + } + + if let Some(external_resources) = config.deserialize_for_resource::("external_resources") { + info!("loading external texture-backed images"); + let mut native_map = FastHashMap::::default(); + for ExternalCaptureImage { short_path, external, descriptor } in external_resources.images { + let target = match external.image_type { + ExternalImageType::TextureHandle(target) => target, + ExternalImageType::Buffer => continue, + }; + let plain_ext = config.deserialize_for_resource::(&short_path) + .expect(&format!("Unable to read {}.ron", short_path)); + let key = (external.id, external.channel_index); + + let tid = match native_map.entry(plain_ext.data) { + Entry::Occupied(e) => e.get().clone(), + Entry::Vacant(e) => { + //TODO: provide a way to query both the layer count and the filter from external images + let (layer_count, filter) = (1, TextureFilter::Linear); + let plain_tex = PlainTexture { + data: e.key().clone(), + size: (descriptor.size, layer_count), + format: descriptor.format, + filter, + has_depth: false, + }; + let t = Self::load_texture( + target, + &plain_tex, + None, + &root, + &mut self.device + ); + let extex = t.0.into_external(); + self.owned_external_images.insert(key, extex.clone()); + e.insert(extex.internal_id()).clone() + } + }; + + let value = (CapturedExternalImageData::NativeTexture(tid), plain_ext.uv); + image_handler.data.insert(key, value); + } + } + + if let Some(renderer) = config.deserialize_for_resource::("renderer") { + info!("loading cached textures"); + self.device_size = renderer.device_size; + self.device.begin_frame(); + + for (_id, texture) in self.texture_resolver.texture_cache_map.drain() { + self.device.delete_texture(texture); + } + for (id, texture) in renderer.textures { + info!("\t{}", texture.data); + let t = Self::load_texture( + TextureTarget::Array, + &texture, + Some(RenderTargetInfo { has_depth: texture.has_depth }), + &root, + &mut self.device + ); + self.texture_resolver.texture_cache_map.insert(id, t.0); + } + + info!("loading gpu cache"); + if let Some(t) = self.gpu_cache_texture.texture.take() { + self.device.delete_texture(t); + } + let (t, gpu_cache_data) = Self::load_texture( + TextureTarget::Default, + &renderer.gpu_cache, + Some(RenderTargetInfo { has_depth: false }), + &root, + &mut self.device, + ); + self.gpu_cache_texture.texture = Some(t); + match self.gpu_cache_texture.bus { + GpuCacheBus::PixelBuffer { ref mut rows, .. } => { + let dim = self.gpu_cache_texture.texture.as_ref().unwrap().get_dimensions(); + let blocks = unsafe { + slice::from_raw_parts( + gpu_cache_data.as_ptr() as *const GpuBlockData, + gpu_cache_data.len() / mem::size_of::(), + ) + }; + // fill up the CPU cache from the contents we just loaded + rows.clear(); + rows.extend((0 .. dim.height).map(|_| CacheRow::new())); + let chunks = blocks.chunks(MAX_VERTEX_TEXTURE_WIDTH); + debug_assert_eq!(chunks.len(), rows.len()); + for (row, chunk) in rows.iter_mut().zip(chunks) { + row.cpu_blocks.copy_from_slice(chunk); + } + } + GpuCacheBus::Scatter { .. } => {} + } + self.gpu_cache_frame_id = renderer.gpu_cache_frame_id; + + self.device.end_frame(); + } else { + info!("loading cached textures"); + self.device.begin_frame(); + for (_id, texture) in self.texture_resolver.texture_cache_map.drain() { + self.device.delete_texture(texture); + } + + info!("loading gpu cache"); + if let Some(t) = self.gpu_cache_texture.texture.take() { + self.device.delete_texture(t); + } + self.device.end_frame(); + } + + self.output_image_handler = Some(Box::new(VoidHandler) as Box<_>); + self.external_image_handler = Some(Box::new(image_handler) as Box<_>); + info!("done."); + } +} + +fn get_vao(vertex_array_kind: VertexArrayKind, vaos: &RendererVAOs) -> &VAO { + match vertex_array_kind { + VertexArrayKind::Primitive => &vaos.prim_vao, + VertexArrayKind::Clip => &vaos.clip_vao, + VertexArrayKind::Blur => &vaos.blur_vao, + VertexArrayKind::VectorStencil | VertexArrayKind::VectorCover => unreachable!(), + VertexArrayKind::Border => &vaos.border_vao, + VertexArrayKind::Scale => &vaos.scale_vao, + VertexArrayKind::LineDecoration => &vaos.line_vao, + VertexArrayKind::Gradient => &vaos.gradient_vao, + VertexArrayKind::Resolve => &vaos.resolve_vao, + VertexArrayKind::SvgFilter => &vaos.svg_filter_vao, + VertexArrayKind::Composite => &vaos.composite_vao, + VertexArrayKind::Clear => &vaos.clear_vao, + } +} +#[derive(Clone, Copy, PartialEq)] +enum FramebufferKind { + Main, + Other, +} + +fn should_skip_batch(kind: &BatchKind, flags: DebugFlags) -> bool { + match kind { + BatchKind::TextRun(_) => { + flags.contains(DebugFlags::DISABLE_TEXT_PRIMS) + } + BatchKind::Brush(BrushBatchKind::ConicGradient) | + BatchKind::Brush(BrushBatchKind::RadialGradient) | + BatchKind::Brush(BrushBatchKind::LinearGradient) => { + flags.contains(DebugFlags::DISABLE_GRADIENT_PRIMS) + } + _ => false, + } +} + +impl CompositeState { + /// Use the client provided native compositor interface to add all picture + /// cache tiles to the OS compositor + fn composite_native( + &self, + compositor: &mut dyn Compositor, + ) { + // Add each surface to the visual tree. z-order is implicit based on + // order added. Offset and clip rect apply to all tiles within this + // surface. + for surface in &self.descriptor.surfaces { + compositor.add_surface( + surface.surface_id.expect("bug: no native surface allocated"), + surface.offset.to_i32(), + surface.clip_rect.to_i32(), + ); + } + } +} diff --git a/third_party/webrender/webrender/src/resource_cache.rs b/third_party/webrender/webrender/src/resource_cache.rs new file mode 100644 index 00000000000..a87d179b08d --- /dev/null +++ b/third_party/webrender/webrender/src/resource_cache.rs @@ -0,0 +1,1829 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{AddFont, BlobImageResources, ResourceUpdate}; +use api::{BlobImageRequest, RasterizedBlobImage}; +use api::{ClearCache, DebugFlags, FontInstanceKey, FontKey, FontTemplate, GlyphIndex}; +use api::{ExternalImageData, ExternalImageType, BlobImageResult, FontInstanceData}; +use api::{DirtyRect, GlyphDimensions, IdNamespace, DEFAULT_TILE_SIZE}; +use api::{ImageData, ImageDescriptor, ImageKey, ImageRendering, TileSize}; +use api::{BlobImageKey, MemoryReport, VoidPtrToSizeFn}; +use api::{SharedFontInstanceMap, BaseFontInstance}; +use api::image_tiling::{compute_tile_size, compute_tile_range}; +use api::units::*; +#[cfg(feature = "capture")] +use crate::capture::ExternalCaptureImage; +#[cfg(feature = "replay")] +use crate::capture::PlainExternalImage; +#[cfg(any(feature = "replay", feature = "png", feature="capture"))] +use crate::capture::CaptureConfig; +use crate::composite::{NativeSurfaceId, NativeSurfaceOperation, NativeTileId, NativeSurfaceOperationDetails}; +use crate::device::TextureFilter; +use crate::glyph_cache::GlyphCache; +use crate::glyph_cache::GlyphCacheEntry; +use crate::glyph_rasterizer::{GLYPH_FLASHING, FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer}; +use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle}; +use crate::gpu_types::UvRectKind; +use crate::internal_types::{FastHashMap, FastHashSet, TextureSource, ResourceUpdateList}; +use crate::profiler::{ResourceProfileCounters, TextureCacheProfileCounters}; +use crate::render_backend::{FrameId, FrameStamp}; +use crate::render_task_graph::{RenderTaskGraph, RenderTaskId}; +use crate::render_task_cache::{RenderTaskCache, RenderTaskCacheKey}; +use crate::render_task_cache::{RenderTaskCacheEntry, RenderTaskCacheEntryHandle}; +use euclid::point2; +use smallvec::SmallVec; +use std::collections::hash_map::Entry::{self, Occupied, Vacant}; +use std::collections::hash_map::{Iter, IterMut}; +use std::collections::VecDeque; +#[cfg(any(feature = "capture", feature = "replay"))] +use std::collections::HashMap; +use std::{cmp, mem}; +use std::fmt::Debug; +use std::hash::Hash; +use std::os::raw::c_void; +#[cfg(any(feature = "capture", feature = "replay"))] +use std::path::PathBuf; +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::u32; +use crate::texture_cache::{TextureCache, TextureCacheHandle, Eviction}; + +// Counter for generating unique native surface ids +static NEXT_NATIVE_SURFACE_ID: AtomicUsize = AtomicUsize::new(0); + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct GlyphFetchResult { + pub index_in_text_run: i32, + pub uv_rect_address: GpuCacheAddress, +} + +// These coordinates are always in texels. +// They are converted to normalized ST +// values in the vertex shader. The reason +// for this is that the texture may change +// dimensions (e.g. the pages in a texture +// atlas can grow). When this happens, by +// storing the coordinates as texel values +// we don't need to go through and update +// various CPU-side structures. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct CacheItem { + pub texture_id: TextureSource, + pub uv_rect_handle: GpuCacheHandle, + pub uv_rect: DeviceIntRect, + pub texture_layer: i32, +} + +impl CacheItem { + pub fn invalid() -> Self { + CacheItem { + texture_id: TextureSource::Invalid, + uv_rect_handle: GpuCacheHandle::new(), + uv_rect: DeviceIntRect::zero(), + texture_layer: 0, + } + } +} + +/// Represents the backing store of an image in the cache. +/// This storage can take several forms. +#[derive(Clone, Debug)] +pub enum CachedImageData { + /// A simple series of bytes, provided by the embedding and owned by WebRender. + /// The format is stored out-of-band, currently in ImageDescriptor. + Raw(Arc>), + /// An series of commands that can be rasterized into an image via an + /// embedding-provided callback. + /// + /// The commands are stored elsewhere and this variant is used as a placeholder. + Blob, + /// An image owned by the embedding, and referenced by WebRender. This may + /// take the form of a texture or a heap-allocated buffer. + External(ExternalImageData), +} + +impl From for CachedImageData { + fn from(img_data: ImageData) -> Self { + match img_data { + ImageData::Raw(data) => CachedImageData::Raw(data), + ImageData::External(data) => CachedImageData::External(data), + } + } +} + +impl CachedImageData { + /// Returns true if this represents a blob. + #[inline] + pub fn is_blob(&self) -> bool { + match *self { + CachedImageData::Blob => true, + _ => false, + } + } + + /// Returns true if this variant of CachedImageData should go through the texture + /// cache. + #[inline] + pub fn uses_texture_cache(&self) -> bool { + match *self { + CachedImageData::External(ref ext_data) => match ext_data.image_type { + ExternalImageType::TextureHandle(_) => false, + ExternalImageType::Buffer => true, + }, + CachedImageData::Blob => true, + CachedImageData::Raw(_) => true, + } + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ImageProperties { + pub descriptor: ImageDescriptor, + pub external_image: Option, + pub tiling: Option, + // Potentially a subset of the image's total rectangle. This rectangle is what + // we map to the (layout space) display item bounds. + pub visible_rect: DeviceIntRect, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +enum State { + Idle, + AddResources, + QueryResources, +} + +/// Post scene building state. +type RasterizedBlob = FastHashMap; + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct ImageGeneration(pub u32); + +impl ImageGeneration { + pub const INVALID: ImageGeneration = ImageGeneration(u32::MAX); +} + +struct ImageResource { + data: CachedImageData, + descriptor: ImageDescriptor, + tiling: Option, + /// This is used to express images that are virtually very large + /// but with only a visible sub-set that is valid at a given time. + visible_rect: DeviceIntRect, + generation: ImageGeneration, +} + +#[derive(Clone, Debug)] +pub struct ImageTiling { + pub image_size: DeviceIntSize, + pub tile_size: TileSize, +} + +#[derive(Default)] +struct ImageTemplates { + images: FastHashMap, +} + +impl ImageTemplates { + fn insert(&mut self, key: ImageKey, resource: ImageResource) { + self.images.insert(key, resource); + } + + fn remove(&mut self, key: ImageKey) -> Option { + self.images.remove(&key) + } + + fn get(&self, key: ImageKey) -> Option<&ImageResource> { + self.images.get(&key) + } + + fn get_mut(&mut self, key: ImageKey) -> Option<&mut ImageResource> { + self.images.get_mut(&key) + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct CachedImageInfo { + texture_cache_handle: TextureCacheHandle, + dirty_rect: ImageDirtyRect, + manual_eviction: bool, +} + +impl CachedImageInfo { + fn mark_unused(&mut self, texture_cache: &mut TextureCache) { + if self.manual_eviction { + texture_cache.evict_manual_handle(&self.texture_cache_handle); + } + self.manual_eviction = false; + } +} + +#[cfg(debug_assertions)] +impl Drop for CachedImageInfo { + fn drop(&mut self) { + debug_assert!(!self.manual_eviction, "Manual eviction requires cleanup"); + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ResourceClassCache { + resources: FastHashMap, + pub user_data: U, +} + +impl ResourceClassCache +where + K: Clone + Hash + Eq + Debug, + U: Default, +{ + pub fn new() -> Self { + ResourceClassCache { + resources: FastHashMap::default(), + user_data: Default::default(), + } + } + + pub fn get(&self, key: &K) -> &V { + self.resources.get(key) + .expect("Didn't find a cached resource with that ID!") + } + + pub fn try_get(&self, key: &K) -> Option<&V> { + self.resources.get(key) + } + + pub fn insert(&mut self, key: K, value: V) { + self.resources.insert(key, value); + } + + pub fn remove(&mut self, key: &K) -> Option { + self.resources.remove(key) + } + + pub fn get_mut(&mut self, key: &K) -> &mut V { + self.resources.get_mut(key) + .expect("Didn't find a cached resource with that ID!") + } + + pub fn try_get_mut(&mut self, key: &K) -> Option<&mut V> { + self.resources.get_mut(key) + } + + pub fn entry(&mut self, key: K) -> Entry { + self.resources.entry(key) + } + + pub fn iter(&self) -> Iter { + self.resources.iter() + } + + pub fn iter_mut(&mut self) -> IterMut { + self.resources.iter_mut() + } + + pub fn is_empty(&mut self) -> bool { + self.resources.is_empty() + } + + pub fn clear(&mut self) { + self.resources.clear(); + } + + pub fn retain(&mut self, f: F) + where + F: FnMut(&K, &mut V) -> bool, + { + self.resources.retain(f); + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct CachedImageKey { + pub rendering: ImageRendering, + pub tile: Option, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ImageRequest { + pub key: ImageKey, + pub rendering: ImageRendering, + pub tile: Option, +} + +impl ImageRequest { + pub fn with_tile(&self, offset: TileOffset) -> Self { + ImageRequest { + key: self.key, + rendering: self.rendering, + tile: Some(offset), + } + } + + pub fn is_untiled_auto(&self) -> bool { + self.tile.is_none() && self.rendering == ImageRendering::Auto + } +} + +impl Into for ImageRequest { + fn into(self) -> BlobImageRequest { + BlobImageRequest { + key: BlobImageKey(self.key), + tile: self.tile.unwrap(), + } + } +} + +impl Into for ImageRequest { + fn into(self) -> CachedImageKey { + CachedImageKey { + rendering: self.rendering, + tile: self.tile, + } + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Clone, Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum ImageCacheError { + OverLimitSize, +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +enum ImageResult { + UntiledAuto(CachedImageInfo), + Multi(ResourceClassCache), + Err(ImageCacheError), +} + +impl ImageResult { + /// Releases any texture cache entries held alive by this ImageResult. + fn drop_from_cache(&mut self, texture_cache: &mut TextureCache) { + match *self { + ImageResult::UntiledAuto(ref mut entry) => { + entry.mark_unused(texture_cache); + }, + ImageResult::Multi(ref mut entries) => { + for entry in entries.resources.values_mut() { + entry.mark_unused(texture_cache); + } + }, + ImageResult::Err(_) => {}, + } + } +} + +type ImageCache = ResourceClassCache; + +struct Resources { + font_templates: FastHashMap, + font_instances: SharedFontInstanceMap, + image_templates: ImageTemplates, +} + +impl BlobImageResources for Resources { + fn get_font_data(&self, key: FontKey) -> &FontTemplate { + self.font_templates.get(&key).unwrap() + } + fn get_font_instance_data(&self, key: FontInstanceKey) -> Option { + self.font_instances.get_font_instance_data(key) + } +} + +// We only use this to report glyph dimensions to the user of the API, so using +// the font instance key should be enough. If we start using it to cache dimensions +// for internal font instances we should change the hash key accordingly. +pub type GlyphDimensionsCache = FastHashMap<(FontInstanceKey, GlyphIndex), Option>; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct BlobImageRasterizerEpoch(usize); + +/// High-level container for resources managed by the `RenderBackend`. +/// +/// This includes a variety of things, including images, fonts, and glyphs, +/// which may be stored as memory buffers, GPU textures, or handles to resources +/// managed by the OS or other parts of WebRender. +pub struct ResourceCache { + cached_glyphs: GlyphCache, + cached_images: ImageCache, + cached_render_tasks: RenderTaskCache, + + resources: Resources, + state: State, + current_frame_id: FrameId, + + #[cfg(feature = "capture")] + /// Used for capture sequences. If the resource cache is updated, then we + /// mark it as dirty. When the next frame is captured in the sequence, we + /// dump the state of the resource cache. + capture_dirty: bool, + + pub texture_cache: TextureCache, + + /// TODO(gw): We should expire (parts of) this cache semi-regularly! + cached_glyph_dimensions: GlyphDimensionsCache, + glyph_rasterizer: GlyphRasterizer, + + /// The set of images that aren't present or valid in the texture cache, + /// and need to be rasterized and/or uploaded this frame. This includes + /// both blobs and regular images. + pending_image_requests: FastHashSet, + + rasterized_blob_images: FastHashMap, + + /// A log of the last three frames worth of deleted image keys kept + /// for debugging purposes. + deleted_blob_keys: VecDeque>, + + /// A list of queued compositor surface updates to apply next frame. + pending_native_surface_updates: Vec, +} + +impl ResourceCache { + pub fn new( + texture_cache: TextureCache, + glyph_rasterizer: GlyphRasterizer, + cached_glyphs: GlyphCache, + font_instances: SharedFontInstanceMap, + ) -> Self { + ResourceCache { + cached_glyphs, + cached_images: ResourceClassCache::new(), + cached_render_tasks: RenderTaskCache::new(), + resources: Resources { + font_instances, + font_templates: FastHashMap::default(), + image_templates: ImageTemplates::default(), + }, + cached_glyph_dimensions: FastHashMap::default(), + texture_cache, + state: State::Idle, + current_frame_id: FrameId::INVALID, + pending_image_requests: FastHashSet::default(), + glyph_rasterizer, + rasterized_blob_images: FastHashMap::default(), + // We want to keep three frames worth of delete blob keys + deleted_blob_keys: vec![Vec::new(), Vec::new(), Vec::new()].into(), + pending_native_surface_updates: Vec::new(), + #[cfg(feature = "capture")] + capture_dirty: true, + } + } + + pub fn max_texture_size(&self) -> i32 { + self.texture_cache.max_texture_size() + } + + pub fn enable_multithreading(&mut self, enable: bool) { + self.glyph_rasterizer.enable_multithreading(enable); + } + + fn should_tile(limit: i32, descriptor: &ImageDescriptor, data: &CachedImageData) -> bool { + let size_check = descriptor.size.width > limit || descriptor.size.height > limit; + match *data { + CachedImageData::Raw(_) | CachedImageData::Blob => size_check, + CachedImageData::External(info) => { + // External handles already represent existing textures so it does + // not make sense to tile them into smaller ones. + info.image_type == ExternalImageType::Buffer && size_check + } + } + } + + // Request the texture cache item for a cacheable render + // task. If the item is already cached, the texture cache + // handle will be returned. Otherwise, the user supplied + // closure will be invoked to generate the render task + // chain that is required to draw this task. + pub fn request_render_task( + &mut self, + key: RenderTaskCacheKey, + gpu_cache: &mut GpuCache, + render_tasks: &mut RenderTaskGraph, + user_data: Option<[f32; 3]>, + is_opaque: bool, + f: F, + ) -> RenderTaskCacheEntryHandle + where + F: FnOnce(&mut RenderTaskGraph) -> RenderTaskId, + { + self.cached_render_tasks.request_render_task( + key, + &mut self.texture_cache, + gpu_cache, + render_tasks, + user_data, + is_opaque, + |render_graph| Ok(f(render_graph)) + ).expect("Failed to request a render task from the resource cache!") + } + + pub fn post_scene_building_update( + &mut self, + updates: Vec, + profile_counters: &mut ResourceProfileCounters, + ) { + // TODO, there is potential for optimization here, by processing updates in + // bulk rather than one by one (for example by sorting allocations by size or + // in a way that reduces fragmentation in the atlas). + #[cfg(feature = "capture")] + match updates.is_empty() { + false => self.capture_dirty = true, + _ => {}, + } + + for update in updates { + match update { + ResourceUpdate::AddImage(img) => { + if let ImageData::Raw(ref bytes) = img.data { + profile_counters.image_templates.inc(bytes.len()); + } + self.add_image_template( + img.key, + img.descriptor, + img.data.into(), + &img.descriptor.size.into(), + img.tiling, + ); + } + ResourceUpdate::UpdateImage(img) => { + self.update_image_template(img.key, img.descriptor, img.data.into(), &img.dirty_rect); + } + ResourceUpdate::AddBlobImage(img) => { + self.add_image_template( + img.key.as_image(), + img.descriptor, + CachedImageData::Blob, + &img.visible_rect, + Some(img.tile_size), + ); + } + ResourceUpdate::UpdateBlobImage(img) => { + self.update_image_template( + img.key.as_image(), + img.descriptor, + CachedImageData::Blob, + &to_image_dirty_rect( + &img.dirty_rect + ), + ); + self.discard_tiles_outside_visible_area(img.key, &img.visible_rect); // TODO: remove? + self.set_image_visible_rect(img.key.as_image(), &img.visible_rect); + } + ResourceUpdate::DeleteImage(img) => { + self.delete_image_template(img); + } + ResourceUpdate::DeleteBlobImage(img) => { + self.delete_image_template(img.as_image()); + } + ResourceUpdate::DeleteFont(font) => { + self.delete_font_template(font); + } + ResourceUpdate::DeleteFontInstance(font) => { + self.delete_font_instance(font); + } + ResourceUpdate::SetBlobImageVisibleArea(key, area) => { + self.discard_tiles_outside_visible_area(key, &area); + self.set_image_visible_rect(key.as_image(), &area); + } + ResourceUpdate::AddFont(font) => { + match font { + AddFont::Raw(id, bytes, index) => { + profile_counters.font_templates.inc(bytes.len()); + self.add_font_template(id, FontTemplate::Raw(bytes, index)); + } + AddFont::Native(id, native_font_handle) => { + self.add_font_template(id, FontTemplate::Native(native_font_handle)); + } + } + } + ResourceUpdate::AddFontInstance(..) => { + // Already added in ApiResources. + } + } + } + } + + pub fn add_rasterized_blob_images( + &mut self, + images: Vec<(BlobImageRequest, BlobImageResult)>, + texture_cache_profile: &mut TextureCacheProfileCounters, + ) { + for (request, result) in images { + let data = match result { + Ok(data) => data, + Err(..) => { + warn!("Failed to rasterize a blob image"); + continue; + } + }; + + texture_cache_profile.rasterized_blob_pixels.inc(data.rasterized_rect.area() as usize); + + // First make sure we have an entry for this key (using a placeholder + // if need be). + let tiles = self.rasterized_blob_images.entry(request.key).or_insert_with( + || { RasterizedBlob::default() } + ); + + tiles.insert(request.tile, data); + + match self.cached_images.try_get_mut(&request.key.as_image()) { + Some(&mut ImageResult::Multi(ref mut entries)) => { + let cached_key = CachedImageKey { + rendering: ImageRendering::Auto, // TODO(nical) + tile: Some(request.tile), + }; + if let Some(entry) = entries.try_get_mut(&cached_key) { + entry.dirty_rect = DirtyRect::All; + } + } + _ => {} + } + } + } + + pub fn add_font_template(&mut self, font_key: FontKey, template: FontTemplate) { + // Push the new font to the font renderer, and also store + // it locally for glyph metric requests. + self.glyph_rasterizer.add_font(font_key, template.clone()); + self.resources.font_templates.insert(font_key, template); + } + + pub fn delete_font_template(&mut self, font_key: FontKey) { + self.glyph_rasterizer.delete_font(font_key); + self.resources.font_templates.remove(&font_key); + self.cached_glyphs + .clear_fonts(|font| font.font_key == font_key); + } + + pub fn delete_font_instance(&mut self, instance_key: FontInstanceKey) { + self.resources.font_instances.delete_font_instance(instance_key); + } + + pub fn get_font_instances(&self) -> SharedFontInstanceMap { + self.resources.font_instances.clone() + } + + pub fn get_font_instance(&self, instance_key: FontInstanceKey) -> Option> { + self.resources.font_instances.get_font_instance(instance_key) + } + + pub fn add_image_template( + &mut self, + image_key: ImageKey, + descriptor: ImageDescriptor, + data: CachedImageData, + visible_rect: &DeviceIntRect, + mut tiling: Option, + ) { + if tiling.is_none() && Self::should_tile(self.max_texture_size(), &descriptor, &data) { + // We aren't going to be able to upload a texture this big, so tile it, even + // if tiling was not requested. + tiling = Some(DEFAULT_TILE_SIZE); + } + + let resource = ImageResource { + descriptor, + data, + tiling, + visible_rect: *visible_rect, + generation: ImageGeneration(0), + }; + + self.resources.image_templates.insert(image_key, resource); + } + + pub fn update_image_template( + &mut self, + image_key: ImageKey, + descriptor: ImageDescriptor, + data: CachedImageData, + dirty_rect: &ImageDirtyRect, + ) { + let max_texture_size = self.max_texture_size(); + let image = match self.resources.image_templates.get_mut(image_key) { + Some(res) => res, + None => panic!("Attempt to update non-existent image"), + }; + + let mut tiling = image.tiling; + if tiling.is_none() && Self::should_tile(max_texture_size, &descriptor, &data) { + tiling = Some(DEFAULT_TILE_SIZE); + } + + // Each cache entry stores its own copy of the image's dirty rect. This allows them to be + // updated independently. + match self.cached_images.try_get_mut(&image_key) { + Some(&mut ImageResult::UntiledAuto(ref mut entry)) => { + entry.dirty_rect = entry.dirty_rect.union(dirty_rect); + } + Some(&mut ImageResult::Multi(ref mut entries)) => { + for (key, entry) in entries.iter_mut() { + // We want the dirty rect relative to the tile and not the whole image. + let local_dirty_rect = match (tiling, key.tile) { + (Some(tile_size), Some(tile)) => { + dirty_rect.map(|mut rect|{ + let tile_offset = DeviceIntPoint::new( + tile.x as i32, + tile.y as i32, + ) * tile_size as i32; + rect.origin -= tile_offset.to_vector(); + + let tile_rect = compute_tile_size( + &descriptor.size.into(), + tile_size, + tile, + ).into(); + + rect.intersection(&tile_rect).unwrap_or_else(DeviceIntRect::zero) + }) + } + (None, Some(..)) => DirtyRect::All, + _ => *dirty_rect, + }; + entry.dirty_rect = entry.dirty_rect.union(&local_dirty_rect); + } + } + _ => {} + } + + if image.descriptor.format != descriptor.format { + // could be a stronger warning/error? + trace!("Format change {:?} -> {:?}", image.descriptor.format, descriptor.format); + } + *image = ImageResource { + descriptor, + data, + tiling, + visible_rect: descriptor.size.into(), + generation: ImageGeneration(image.generation.0 + 1), + }; + } + + pub fn delete_image_template(&mut self, image_key: ImageKey) { + // Remove the template. + let value = self.resources.image_templates.remove(image_key); + + // Release the corresponding texture cache entry, if any. + if let Some(mut cached) = self.cached_images.remove(&image_key) { + cached.drop_from_cache(&mut self.texture_cache); + } + + match value { + Some(image) => if image.data.is_blob() { + let blob_key = BlobImageKey(image_key); + self.deleted_blob_keys.back_mut().unwrap().push(blob_key); + self.rasterized_blob_images.remove(&blob_key); + }, + None => { + warn!("Delete the non-exist key"); + debug!("key={:?}", image_key); + } + } + } + + /// Return the current generation of an image template + pub fn get_image_generation(&self, key: ImageKey) -> ImageGeneration { + self.resources + .image_templates + .get(key) + .map_or(ImageGeneration::INVALID, |template| template.generation) + } + + pub fn request_image( + &mut self, + request: ImageRequest, + gpu_cache: &mut GpuCache, + ) { + debug_assert_eq!(self.state, State::AddResources); + + let template = match self.resources.image_templates.get(request.key) { + Some(template) => template, + None => { + warn!("ERROR: Trying to render deleted / non-existent key"); + debug!("key={:?}", request.key); + return + } + }; + + // Images that don't use the texture cache can early out. + if !template.data.uses_texture_cache() { + return; + } + + let side_size = + template.tiling.map_or(cmp::max(template.descriptor.size.width, template.descriptor.size.height), + |tile_size| tile_size as i32); + if side_size > self.texture_cache.max_texture_size() { + // The image or tiling size is too big for hardware texture size. + warn!("Dropping image, image:(w:{},h:{}, tile:{}) is too big for hardware!", + template.descriptor.size.width, template.descriptor.size.height, template.tiling.unwrap_or(0)); + self.cached_images.insert(request.key, ImageResult::Err(ImageCacheError::OverLimitSize)); + return; + } + + let storage = match self.cached_images.entry(request.key) { + Occupied(e) => { + // We might have an existing untiled entry, and need to insert + // a second entry. In such cases we need to move the old entry + // out first, replacing it with a dummy entry, and then creating + // the tiled/multi-entry variant. + let entry = e.into_mut(); + if !request.is_untiled_auto() { + let untiled_entry = match entry { + &mut ImageResult::UntiledAuto(ref mut entry) => { + Some(mem::replace(entry, CachedImageInfo { + texture_cache_handle: TextureCacheHandle::invalid(), + dirty_rect: DirtyRect::All, + manual_eviction: false, + })) + } + _ => None + }; + + if let Some(untiled_entry) = untiled_entry { + let mut entries = ResourceClassCache::new(); + let untiled_key = CachedImageKey { + rendering: ImageRendering::Auto, + tile: None, + }; + entries.insert(untiled_key, untiled_entry); + *entry = ImageResult::Multi(entries); + } + } + entry + } + Vacant(entry) => { + entry.insert(if request.is_untiled_auto() { + ImageResult::UntiledAuto(CachedImageInfo { + texture_cache_handle: TextureCacheHandle::invalid(), + dirty_rect: DirtyRect::All, + manual_eviction: false, + }) + } else { + ImageResult::Multi(ResourceClassCache::new()) + }) + } + }; + + // If this image exists in the texture cache, *and* the dirty rect + // in the cache is empty, then it is valid to use as-is. + let entry = match *storage { + ImageResult::UntiledAuto(ref mut entry) => entry, + ImageResult::Multi(ref mut entries) => { + entries.entry(request.into()) + .or_insert(CachedImageInfo { + texture_cache_handle: TextureCacheHandle::invalid(), + dirty_rect: DirtyRect::All, + manual_eviction: false, + }) + }, + ImageResult::Err(_) => panic!("Errors should already have been handled"), + }; + + let needs_upload = self.texture_cache.request(&entry.texture_cache_handle, gpu_cache); + + if !needs_upload && entry.dirty_rect.is_empty() { + return + } + + if !self.pending_image_requests.insert(request) { + return + } + + if template.data.is_blob() { + let request: BlobImageRequest = request.into(); + let missing = match self.rasterized_blob_images.get(&request.key) { + Some(tiles) => !tiles.contains_key(&request.tile), + _ => true, + }; + + assert!(!missing); + } + } + + fn discard_tiles_outside_visible_area( + &mut self, + key: BlobImageKey, + area: &DeviceIntRect + ) { + let tile_size = match self.resources.image_templates.get(key.as_image()) { + Some(template) => template.tiling.unwrap(), + None => { + //println!("Missing image template (key={:?})!", key); + return; + } + }; + + let tiles = match self.rasterized_blob_images.get_mut(&key) { + Some(tiles) => tiles, + _ => { return; } + }; + + let tile_range = compute_tile_range( + &area, + tile_size, + ); + + tiles.retain(|tile, _| { tile_range.contains(*tile) }); + + let texture_cache = &mut self.texture_cache; + match self.cached_images.try_get_mut(&key.as_image()) { + Some(&mut ImageResult::Multi(ref mut entries)) => { + entries.retain(|key, entry| { + if key.tile.is_none() || tile_range.contains(key.tile.unwrap()) { + return true; + } + entry.mark_unused(texture_cache); + return false; + }); + } + _ => {} + } + } + + fn set_image_visible_rect(&mut self, key: ImageKey, rect: &DeviceIntRect) { + if let Some(image) = self.resources.image_templates.get_mut(key) { + image.visible_rect = *rect; + image.descriptor.size = rect.size; + } + } + + pub fn request_glyphs( + &mut self, + mut font: FontInstance, + glyph_keys: &[GlyphKey], + gpu_cache: &mut GpuCache, + render_task_tree: &mut RenderTaskGraph, + ) { + debug_assert_eq!(self.state, State::AddResources); + + self.glyph_rasterizer.prepare_font(&mut font); + self.glyph_rasterizer.request_glyphs( + &mut self.cached_glyphs, + font, + glyph_keys, + &mut self.texture_cache, + gpu_cache, + &mut self.cached_render_tasks, + render_task_tree, + ); + } + + pub fn pending_updates(&mut self) -> ResourceUpdateList { + ResourceUpdateList { + texture_updates: self.texture_cache.pending_updates(), + native_surface_updates: mem::replace(&mut self.pending_native_surface_updates, Vec::new()), + } + } + + pub fn fetch_glyphs( + &self, + mut font: FontInstance, + glyph_keys: &[GlyphKey], + fetch_buffer: &mut Vec, + gpu_cache: &mut GpuCache, + mut f: F, + ) where + F: FnMut(TextureSource, GlyphFormat, &[GlyphFetchResult]), + { + debug_assert_eq!(self.state, State::QueryResources); + + self.glyph_rasterizer.prepare_font(&mut font); + let glyph_key_cache = self.cached_glyphs.get_glyph_key_cache_for_font(&font); + + let mut current_texture_id = TextureSource::Invalid; + let mut current_glyph_format = GlyphFormat::Subpixel; + debug_assert!(fetch_buffer.is_empty()); + + for (loop_index, key) in glyph_keys.iter().enumerate() { + let (cache_item, glyph_format) = match *glyph_key_cache.get(key) { + GlyphCacheEntry::Cached(ref glyph) => { + (self.texture_cache.get(&glyph.texture_cache_handle), glyph.format) + } + GlyphCacheEntry::Blank | GlyphCacheEntry::Pending => continue, + }; + if current_texture_id != cache_item.texture_id || + current_glyph_format != glyph_format { + if !fetch_buffer.is_empty() { + f(current_texture_id, current_glyph_format, fetch_buffer); + fetch_buffer.clear(); + } + current_texture_id = cache_item.texture_id; + current_glyph_format = glyph_format; + } + fetch_buffer.push(GlyphFetchResult { + index_in_text_run: loop_index as i32, + uv_rect_address: gpu_cache.get_address(&cache_item.uv_rect_handle), + }); + } + + if !fetch_buffer.is_empty() { + f(current_texture_id, current_glyph_format, fetch_buffer); + fetch_buffer.clear(); + } + } + + pub fn get_glyph_dimensions( + &mut self, + font: &FontInstance, + glyph_index: GlyphIndex, + ) -> Option { + match self.cached_glyph_dimensions.entry((font.instance_key, glyph_index)) { + Occupied(entry) => *entry.get(), + Vacant(entry) => *entry.insert( + self.glyph_rasterizer + .get_glyph_dimensions(font, glyph_index), + ), + } + } + + pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option { + self.glyph_rasterizer.get_glyph_index(font_key, ch) + } + + #[inline] + pub fn get_cached_image(&self, request: ImageRequest) -> Result { + debug_assert_eq!(self.state, State::QueryResources); + let image_info = self.get_image_info(request)?; + Ok(self.get_texture_cache_item(&image_info.texture_cache_handle)) + } + + pub fn get_cached_render_task( + &self, + handle: &RenderTaskCacheEntryHandle, + ) -> &RenderTaskCacheEntry { + self.cached_render_tasks.get_cache_entry(handle) + } + + #[inline] + fn get_image_info(&self, request: ImageRequest) -> Result<&CachedImageInfo, ()> { + // TODO(Jerry): add a debug option to visualize the corresponding area for + // the Err() case of CacheItem. + match *self.cached_images.get(&request.key) { + ImageResult::UntiledAuto(ref image_info) => Ok(image_info), + ImageResult::Multi(ref entries) => Ok(entries.get(&request.into())), + ImageResult::Err(_) => Err(()), + } + } + + #[inline] + pub fn get_texture_cache_item(&self, handle: &TextureCacheHandle) -> CacheItem { + self.texture_cache.get(handle) + } + + pub fn get_image_properties(&self, image_key: ImageKey) -> Option { + let image_template = &self.resources.image_templates.get(image_key); + + image_template.map(|image_template| { + let external_image = match image_template.data { + CachedImageData::External(ext_image) => match ext_image.image_type { + ExternalImageType::TextureHandle(_) => Some(ext_image), + // external buffer uses resource_cache. + ExternalImageType::Buffer => None, + }, + // raw and blob image are all using resource_cache. + CachedImageData::Raw(..) | CachedImageData::Blob => None, + }; + + ImageProperties { + descriptor: image_template.descriptor, + external_image, + tiling: image_template.tiling, + visible_rect: image_template.visible_rect, + } + }) + } + + pub fn begin_frame(&mut self, stamp: FrameStamp) { + profile_scope!("begin_frame"); + debug_assert_eq!(self.state, State::Idle); + self.state = State::AddResources; + self.texture_cache.begin_frame(stamp); + self.cached_glyphs.begin_frame( + stamp, + &mut self.texture_cache, + &self.cached_render_tasks, + &mut self.glyph_rasterizer, + ); + self.cached_render_tasks.begin_frame(&mut self.texture_cache); + self.current_frame_id = stamp.frame_id(); + + // pop the old frame and push a new one + self.deleted_blob_keys.pop_front(); + self.deleted_blob_keys.push_back(Vec::new()); + } + + pub fn block_until_all_resources_added( + &mut self, + gpu_cache: &mut GpuCache, + render_tasks: &mut RenderTaskGraph, + texture_cache_profile: &mut TextureCacheProfileCounters, + ) { + profile_scope!("block_until_all_resources_added"); + + debug_assert_eq!(self.state, State::AddResources); + self.state = State::QueryResources; + + self.glyph_rasterizer.resolve_glyphs( + &mut self.cached_glyphs, + &mut self.texture_cache, + gpu_cache, + &mut self.cached_render_tasks, + render_tasks, + texture_cache_profile, + ); + + // Apply any updates of new / updated images (incl. blobs) to the texture cache. + self.update_texture_cache(gpu_cache); + } + + fn update_texture_cache(&mut self, gpu_cache: &mut GpuCache) { + profile_scope!("update_texture_cache"); + for request in self.pending_image_requests.drain() { + let image_template = self.resources.image_templates.get_mut(request.key).unwrap(); + debug_assert!(image_template.data.uses_texture_cache()); + + let mut updates: SmallVec<[(CachedImageData, Option); 1]> = SmallVec::new(); + + match image_template.data { + CachedImageData::Raw(..) | CachedImageData::External(..) => { + // Safe to clone here since the Raw image data is an + // Arc, and the external image data is small. + updates.push((image_template.data.clone(), None)); + } + CachedImageData::Blob => { + let blob_image = self.rasterized_blob_images.get_mut(&BlobImageKey(request.key)).unwrap(); + let img = &blob_image[&request.tile.unwrap()]; + updates.push(( + CachedImageData::Raw(Arc::clone(&img.data)), + Some(img.rasterized_rect) + )); + } + }; + + 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") + }; + + let mut descriptor = image_template.descriptor.clone(); + let mut dirty_rect = entry.dirty_rect.replace_with_empty(); + + if let Some(tile) = request.tile { + let tile_size = image_template.tiling.unwrap(); + let clipped_tile_size = compute_tile_size(&image_template.visible_rect, tile_size, tile); + // 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 { + // we don't expect to have partial tiles at the top and left of non-blob + // images. + debug_assert_eq!(image_template.visible_rect.origin, point2(0, 0)); + let bpp = descriptor.format.bytes_per_pixel(); + let stride = descriptor.compute_stride(); + descriptor.stride = Some(stride); + descriptor.offset += + tile.y as i32 * tile_size as i32 * stride + + tile.x as i32 * tile_size as i32 * bpp; + } + + descriptor.size = clipped_tile_size; + } + + // 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 let Some(rect) = blob_rasterized_rect { + dirty_rect = DirtyRect::Partial(rect); + } + + 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() { + entry.manual_eviction = true; + 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], + dirty_rect, + gpu_cache, + None, + UvRectKind::Rect, + eviction, + ); + } + } + } + + /// Queue up allocation of a new OS native compositor surface with the + /// specified tile size. + pub fn create_compositor_surface( + &mut self, + virtual_offset: DeviceIntPoint, + tile_size: DeviceIntSize, + is_opaque: bool, + ) -> NativeSurfaceId { + let id = NativeSurfaceId(NEXT_NATIVE_SURFACE_ID.fetch_add(1, Ordering::Relaxed) as u64); + + self.pending_native_surface_updates.push( + NativeSurfaceOperation { + details: NativeSurfaceOperationDetails::CreateSurface { + id, + virtual_offset, + tile_size, + is_opaque, + }, + } + ); + + id + } + + /// Queue up destruction of an existing native OS surface. This is used when + /// a picture cache surface is dropped or resized. + pub fn destroy_compositor_surface( + &mut self, + id: NativeSurfaceId, + ) { + self.pending_native_surface_updates.push( + NativeSurfaceOperation { + details: NativeSurfaceOperationDetails::DestroySurface { + id, + } + } + ); + } + + /// Queue construction of a native compositor tile on a given surface. + pub fn create_compositor_tile( + &mut self, + id: NativeTileId, + ) { + self.pending_native_surface_updates.push( + NativeSurfaceOperation { + details: NativeSurfaceOperationDetails::CreateTile { + id, + }, + } + ); + } + + /// Queue destruction of a native compositor tile. + pub fn destroy_compositor_tile( + &mut self, + id: NativeTileId, + ) { + self.pending_native_surface_updates.push( + NativeSurfaceOperation { + details: NativeSurfaceOperationDetails::DestroyTile { + id, + }, + } + ); + } + + pub fn end_frame(&mut self, texture_cache_profile: &mut TextureCacheProfileCounters) { + debug_assert_eq!(self.state, State::QueryResources); + profile_scope!("end_frame"); + self.state = State::Idle; + self.texture_cache.end_frame(texture_cache_profile); + } + + pub fn set_debug_flags(&mut self, flags: DebugFlags) { + GLYPH_FLASHING.store(flags.contains(DebugFlags::GLYPH_FLASHING), std::sync::atomic::Ordering::Relaxed); + self.texture_cache.set_debug_flags(flags); + } + + pub fn clear(&mut self, what: ClearCache) { + if what.contains(ClearCache::IMAGES) { + for (_key, mut cached) in self.cached_images.resources.drain() { + cached.drop_from_cache(&mut self.texture_cache); + } + } + if what.contains(ClearCache::GLYPHS) { + self.cached_glyphs.clear(); + } + if what.contains(ClearCache::GLYPH_DIMENSIONS) { + self.cached_glyph_dimensions.clear(); + } + if what.contains(ClearCache::RENDER_TASKS) { + self.cached_render_tasks.clear(); + } + if what.contains(ClearCache::TEXTURE_CACHE) { + self.texture_cache.clear_all(); + } + } + + pub fn clear_namespace(&mut self, namespace: IdNamespace) { + self.clear_images(|k| k.0 == namespace); + + self.resources.font_instances.clear_namespace(namespace); + + for &key in self.resources.font_templates.keys().filter(|key| key.0 == namespace) { + self.glyph_rasterizer.delete_font(key); + } + self.resources + .font_templates + .retain(|key, _| key.0 != namespace); + self.cached_glyphs + .clear_fonts(|font| font.font_key.0 == namespace); + } + + /// Reports the CPU heap usage of this ResourceCache. + /// + /// NB: It would be much better to use the derive(MallocSizeOf) machinery + /// here, but the Arcs complicate things. The two ways to handle that would + /// be to either (a) Implement MallocSizeOf manually for the things that own + /// them and manually avoid double-counting, or (b) Use the "seen this pointer + /// yet" machinery from the proper malloc_size_of crate. We can do this if/when + /// more accurate memory reporting on these resources becomes a priority. + pub fn report_memory(&self, op: VoidPtrToSizeFn) -> MemoryReport { + let mut report = MemoryReport::default(); + + // Measure fonts. We only need the templates here, because the instances + // don't have big buffers. + for (_, font) in self.resources.font_templates.iter() { + if let FontTemplate::Raw(ref raw, _) = font { + report.fonts += unsafe { op(raw.as_ptr() as *const c_void) }; + } + } + + // Measure images. + for (_, image) in self.resources.image_templates.images.iter() { + report.images += match image.data { + CachedImageData::Raw(ref v) => unsafe { op(v.as_ptr() as *const c_void) }, + CachedImageData::Blob | CachedImageData::External(..) => 0, + } + } + + // Mesure rasterized blobs. + // TODO(gw): Temporarily disabled while we roll back a crash. We can re-enable + // these when that crash is fixed. + /* + for (_, image) in self.rasterized_blob_images.iter() { + let mut accumulate = |b: &RasterizedBlobImage| { + report.rasterized_blobs += unsafe { op(b.data.as_ptr() as *const c_void) }; + }; + match image { + RasterizedBlob::Tiled(map) => map.values().for_each(&mut accumulate), + RasterizedBlob::NonTiled(vec) => vec.iter().for_each(&mut accumulate), + }; + } + */ + + report + } + + /// Properly deletes all images matching the predicate. + fn clear_images bool>(&mut self, f: F) { + let keys = self.resources.image_templates.images.keys().filter(|k| f(*k)) + .cloned().collect::>(); + + for key in keys { + self.delete_image_template(key); + } + + #[cfg(features="leak_checks")] + let check_leaks = true; + #[cfg(not(features="leak_checks"))] + let check_leaks = false; + + if check_leaks { + let blob_f = |key: &BlobImageKey| { f(&key.as_image()) }; + assert!(!self.resources.image_templates.images.keys().any(&f)); + assert!(!self.cached_images.resources.keys().any(&f)); + assert!(!self.rasterized_blob_images.keys().any(&blob_f)); + } + } +} + +impl Drop for ResourceCache { + fn drop(&mut self) { + self.clear_images(|_| true); + } +} + +#[cfg(any(feature = "capture", feature = "replay"))] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct PlainFontTemplate { + data: String, + index: u32, +} + +#[cfg(any(feature = "capture", feature = "replay"))] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct PlainImageTemplate { + data: String, + descriptor: ImageDescriptor, + tiling: Option, + generation: ImageGeneration, +} + +#[cfg(any(feature = "capture", feature = "replay"))] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct PlainResources { + font_templates: FastHashMap, + font_instances: HashMap>, + image_templates: FastHashMap, +} + +#[cfg(feature = "capture")] +#[derive(Serialize)] +pub struct PlainCacheRef<'a> { + current_frame_id: FrameId, + glyphs: &'a GlyphCache, + glyph_dimensions: &'a GlyphDimensionsCache, + images: &'a ImageCache, + render_tasks: &'a RenderTaskCache, + textures: &'a TextureCache, +} + +#[cfg(feature = "replay")] +#[derive(Deserialize)] +pub struct PlainCacheOwn { + current_frame_id: FrameId, + glyphs: GlyphCache, + glyph_dimensions: GlyphDimensionsCache, + images: ImageCache, + render_tasks: RenderTaskCache, + textures: TextureCache, +} + +#[cfg(feature = "replay")] +const NATIVE_FONT: &'static [u8] = include_bytes!("../res/Proggy.ttf"); + +// This currently only casts the unit but will soon apply an offset +fn to_image_dirty_rect(blob_dirty_rect: &BlobDirtyRect) -> ImageDirtyRect { + match *blob_dirty_rect { + DirtyRect::Partial(rect) => DirtyRect::Partial( + DeviceIntRect { + origin: DeviceIntPoint::new(rect.origin.x, rect.origin.y), + size: DeviceIntSize::new(rect.size.width, rect.size.height), + } + ), + DirtyRect::All => DirtyRect::All, + } +} + +impl ResourceCache { + #[cfg(feature = "capture")] + pub fn save_capture( + &mut self, root: &PathBuf + ) -> (PlainResources, Vec) { + use std::fs; + use std::io::Write; + + info!("saving resource cache"); + let res = &self.resources; + let path_fonts = root.join("fonts"); + if !path_fonts.is_dir() { + fs::create_dir(&path_fonts).unwrap(); + } + let path_images = root.join("images"); + if !path_images.is_dir() { + fs::create_dir(&path_images).unwrap(); + } + let path_blobs = root.join("blobs"); + if !path_blobs.is_dir() { + fs::create_dir(&path_blobs).unwrap(); + } + let path_externals = root.join("externals"); + if !path_externals.is_dir() { + fs::create_dir(&path_externals).unwrap(); + } + + info!("\tfont templates"); + let mut font_paths = FastHashMap::default(); + for template in res.font_templates.values() { + let data: &[u8] = match *template { + FontTemplate::Raw(ref arc, _) => arc, + FontTemplate::Native(_) => continue, + }; + let font_id = res.font_templates.len() + 1; + let entry = match font_paths.entry(data.as_ptr()) { + Entry::Occupied(_) => continue, + Entry::Vacant(e) => e, + }; + let file_name = format!("{}.raw", font_id); + let short_path = format!("fonts/{}", file_name); + fs::File::create(path_fonts.join(file_name)) + .expect(&format!("Unable to create {}", short_path)) + .write_all(data) + .unwrap(); + entry.insert(short_path); + } + + info!("\timage templates"); + let mut image_paths = FastHashMap::default(); + let mut other_paths = FastHashMap::default(); + let mut num_blobs = 0; + let mut external_images = Vec::new(); + for (&key, template) in res.image_templates.images.iter() { + let desc = &template.descriptor; + match template.data { + CachedImageData::Raw(ref arc) => { + let image_id = image_paths.len() + 1; + let entry = match image_paths.entry(arc.as_ptr()) { + Entry::Occupied(_) => continue, + Entry::Vacant(e) => e, + }; + + #[cfg(feature = "png")] + CaptureConfig::save_png( + root.join(format!("images/{}.png", image_id)), + desc.size, + desc.format, + desc.stride, + &arc, + ); + let file_name = format!("{}.raw", image_id); + let short_path = format!("images/{}", file_name); + fs::File::create(path_images.join(file_name)) + .expect(&format!("Unable to create {}", short_path)) + .write_all(&*arc) + .unwrap(); + entry.insert(short_path); + } + CachedImageData::Blob => { + warn!("Tiled blob images aren't supported yet"); + let result = RasterizedBlobImage { + rasterized_rect: desc.size.into(), + data: Arc::new(vec![0; desc.compute_total_size() as usize]) + }; + + assert_eq!(result.rasterized_rect.size, desc.size); + assert_eq!(result.data.len(), desc.compute_total_size() as usize); + + num_blobs += 1; + #[cfg(feature = "png")] + CaptureConfig::save_png( + root.join(format!("blobs/{}.png", num_blobs)), + desc.size, + desc.format, + desc.stride, + &result.data, + ); + let file_name = format!("{}.raw", num_blobs); + let short_path = format!("blobs/{}", file_name); + let full_path = path_blobs.clone().join(&file_name); + fs::File::create(full_path) + .expect(&format!("Unable to create {}", short_path)) + .write_all(&result.data) + .unwrap(); + other_paths.insert(key, short_path); + } + CachedImageData::External(ref ext) => { + let short_path = format!("externals/{}", external_images.len() + 1); + other_paths.insert(key, short_path.clone()); + external_images.push(ExternalCaptureImage { + short_path, + descriptor: desc.clone(), + external: ext.clone(), + }); + } + } + } + + let resources = PlainResources { + font_templates: res.font_templates + .iter() + .map(|(key, template)| { + (*key, match *template { + FontTemplate::Raw(ref arc, index) => { + PlainFontTemplate { + data: font_paths[&arc.as_ptr()].clone(), + index, + } + } + #[cfg(not(target_os = "macos"))] + FontTemplate::Native(ref native) => { + PlainFontTemplate { + data: native.path.to_string_lossy().to_string(), + index: native.index, + } + } + #[cfg(target_os = "macos")] + FontTemplate::Native(ref native) => { + PlainFontTemplate { + data: native.0.postscript_name().to_string(), + index: 0, + } + } + }) + }) + .collect(), + font_instances: res.font_instances.clone_map(), + image_templates: res.image_templates.images + .iter() + .map(|(key, template)| { + (*key, PlainImageTemplate { + data: match template.data { + CachedImageData::Raw(ref arc) => image_paths[&arc.as_ptr()].clone(), + _ => other_paths[key].clone(), + }, + descriptor: template.descriptor.clone(), + tiling: template.tiling, + generation: template.generation, + }) + }) + .collect(), + }; + + (resources, external_images) + } + + #[cfg(feature = "capture")] + pub fn save_caches(&self, _root: &PathBuf) -> PlainCacheRef { + PlainCacheRef { + current_frame_id: self.current_frame_id, + glyphs: &self.cached_glyphs, + glyph_dimensions: &self.cached_glyph_dimensions, + images: &self.cached_images, + render_tasks: &self.cached_render_tasks, + textures: &self.texture_cache, + } + } + + #[cfg(feature = "replay")] + pub fn load_capture( + &mut self, + resources: PlainResources, + caches: Option, + config: &CaptureConfig, + ) -> Vec { + use std::{fs, path::Path}; + + info!("loading resource cache"); + //TODO: instead of filling the local path to Arc map as we process + // each of the resource types, we could go through all of the local paths + // and fill out the map as the first step. + let mut raw_map = FastHashMap::>>::default(); + + self.clear(ClearCache::all()); + self.clear_images(|_| true); + + match caches { + Some(cached) => { + self.current_frame_id = cached.current_frame_id; + self.cached_glyphs = cached.glyphs; + self.cached_glyph_dimensions = cached.glyph_dimensions; + self.cached_images = cached.images; + self.cached_render_tasks = cached.render_tasks; + self.texture_cache = cached.textures; + } + None => { + self.current_frame_id = FrameId::INVALID; + self.texture_cache = TextureCache::new( + self.texture_cache.max_texture_size(), + self.texture_cache.max_texture_layers(), + &self.texture_cache.picture_tile_sizes(), + DeviceIntSize::zero(), + self.texture_cache.color_formats(), + self.texture_cache.swizzle_settings(), + self.texture_cache.eviction_threshold_bytes(), + self.texture_cache.max_evictions_per_frame(), + ); + } + } + + self.glyph_rasterizer.reset(); + let res = &mut self.resources; + res.font_templates.clear(); + res.font_instances.set(resources.font_instances); + res.image_templates.images.clear(); + + info!("\tfont templates..."); + let root = config.resource_root(); + let native_font_replacement = Arc::new(NATIVE_FONT.to_vec()); + for (key, plain_template) in resources.font_templates { + let arc = match raw_map.entry(plain_template.data) { + Entry::Occupied(e) => { + e.get().clone() + } + Entry::Vacant(e) => { + let file_path = if Path::new(e.key()).is_absolute() { + PathBuf::from(e.key()) + } else { + root.join(e.key()) + }; + let arc = match fs::read(file_path) { + Ok(buffer) => Arc::new(buffer), + Err(err) => { + error!("Unable to open font template {:?}: {:?}", e.key(), err); + Arc::clone(&native_font_replacement) + } + }; + e.insert(arc).clone() + } + }; + + let template = FontTemplate::Raw(arc, plain_template.index); + self.glyph_rasterizer.add_font(key, template.clone()); + res.font_templates.insert(key, template); + } + + info!("\timage templates..."); + let mut external_images = Vec::new(); + for (key, template) in resources.image_templates { + let data = match config.deserialize_for_resource::(&template.data) { + Some(plain) => { + let ext_data = plain.external; + external_images.push(plain); + CachedImageData::External(ext_data) + } + None => { + let arc = match raw_map.entry(template.data) { + Entry::Occupied(e) => { + e.get().clone() + } + Entry::Vacant(e) => { + let buffer = fs::read(root.join(e.key())) + .expect(&format!("Unable to open {}", e.key())); + e.insert(Arc::new(buffer)) + .clone() + } + }; + CachedImageData::Raw(arc) + } + }; + + res.image_templates.images.insert(key, ImageResource { + data, + descriptor: template.descriptor, + tiling: template.tiling, + visible_rect: template.descriptor.size.into(), + generation: template.generation, + }); + } + + external_images + } + + #[cfg(feature = "capture")] + pub fn save_capture_sequence(&mut self, config: &mut CaptureConfig) -> Vec { + if self.capture_dirty { + self.capture_dirty = false; + config.prepare_resource(); + let (resources, deferred) = self.save_capture(&config.resource_root()); + config.serialize_for_resource(&resources, "plain-resources.ron"); + deferred + } else { + Vec::new() + } + } +} diff --git a/third_party/webrender/webrender/src/scene.rs b/third_party/webrender/webrender/src/scene.rs new file mode 100644 index 00000000000..3caf68ab603 --- /dev/null +++ b/third_party/webrender/webrender/src/scene.rs @@ -0,0 +1,354 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{BuiltDisplayList, DisplayListWithCache, ColorF, DynamicProperties, Epoch, FontRenderMode}; +use api::{PipelineId, PropertyBinding, PropertyBindingId, PropertyValue, MixBlendMode, StackingContext}; +use api::MemoryReport; +use api::units::*; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use crate::composite::CompositorKind; +use crate::clip::{ClipStore, ClipDataStore}; +use crate::spatial_tree::{SpatialTree, SpatialNodeIndex}; +use crate::frame_builder::{ChasePrimitive, FrameBuilderConfig}; +use crate::hit_test::{HitTester, HitTestingScene, HitTestingSceneStats}; +use crate::internal_types::{FastHashMap, FastHashSet}; +use crate::prim_store::{PrimitiveStore, PrimitiveStoreStats, PictureIndex}; +use std::sync::Arc; + +/// Stores a map of the animated property bindings for the current display list. These +/// can be used to animate the transform and/or opacity of a display list without +/// re-submitting the display list itself. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct SceneProperties { + transform_properties: FastHashMap, + float_properties: FastHashMap, + color_properties: FastHashMap, + current_properties: DynamicProperties, + pending_properties: Option, +} + +impl SceneProperties { + pub fn new() -> Self { + SceneProperties { + transform_properties: FastHashMap::default(), + float_properties: FastHashMap::default(), + color_properties: FastHashMap::default(), + current_properties: DynamicProperties::default(), + pending_properties: None, + } + } + + /// Set the current property list for this display list. + pub fn set_properties(&mut self, properties: DynamicProperties) { + self.pending_properties = Some(properties); + } + + /// Add to the current property list for this display list. + pub fn add_transforms(&mut self, transforms: Vec>) { + let mut pending_properties = self.pending_properties + .take() + .unwrap_or_default(); + + pending_properties.transforms.extend(transforms); + + self.pending_properties = Some(pending_properties); + } + + /// Flush any pending updates to the scene properties. Returns + /// true if the properties have changed since the last flush + /// was called. This code allows properties to be changed by + /// multiple set_properties and add_properties calls during a + /// single transaction, and still correctly determine if any + /// properties have changed. This can have significant power + /// saving implications, allowing a frame build to be skipped + /// if the properties haven't changed in many cases. + pub fn flush_pending_updates(&mut self) -> bool { + let mut properties_changed = false; + + if let Some(ref pending_properties) = self.pending_properties { + if *pending_properties != self.current_properties { + self.transform_properties.clear(); + self.float_properties.clear(); + + for property in &pending_properties.transforms { + self.transform_properties + .insert(property.key.id, property.value); + } + + for property in &pending_properties.floats { + self.float_properties + .insert(property.key.id, property.value); + } + + for property in &pending_properties.colors { + self.color_properties + .insert(property.key.id, property.value); + } + + self.current_properties = pending_properties.clone(); + properties_changed = true; + } + } + + properties_changed + } + + /// Get the current value for a transform property. + pub fn resolve_layout_transform( + &self, + property: &PropertyBinding, + ) -> LayoutTransform { + match *property { + PropertyBinding::Value(value) => value, + PropertyBinding::Binding(ref key, v) => { + self.transform_properties + .get(&key.id) + .cloned() + .unwrap_or(v) + } + } + } + + /// Get the current value for a float property. + pub fn resolve_float( + &self, + property: &PropertyBinding + ) -> f32 { + match *property { + PropertyBinding::Value(value) => value, + PropertyBinding::Binding(ref key, v) => { + self.float_properties + .get(&key.id) + .cloned() + .unwrap_or(v) + } + } + } + + pub fn float_properties(&self) -> &FastHashMap { + &self.float_properties + } + + /// Get the current value for a color property. + pub fn resolve_color( + &self, + property: &PropertyBinding + ) -> ColorF { + match *property { + PropertyBinding::Value(value) => value, + PropertyBinding::Binding(ref key, v) => { + self.color_properties + .get(&key.id) + .cloned() + .unwrap_or(v) + } + } + } + + pub fn color_properties(&self) -> &FastHashMap { + &self.color_properties + } + +} + +/// A representation of the layout within the display port for a given document or iframe. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Clone)] +pub struct ScenePipeline { + pub pipeline_id: PipelineId, + pub viewport_size: LayoutSize, + pub content_size: LayoutSize, + pub background_color: Option, + pub display_list: DisplayListWithCache, +} + +/// A complete representation of the layout bundling visible pipelines together. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Clone)] +pub struct Scene { + pub root_pipeline_id: Option, + pub pipelines: FastHashMap, + pub pipeline_epochs: FastHashMap, +} + +impl Scene { + pub fn new() -> Self { + Scene { + root_pipeline_id: None, + pipelines: FastHashMap::default(), + pipeline_epochs: FastHashMap::default(), + } + } + + pub fn set_root_pipeline_id(&mut self, pipeline_id: PipelineId) { + self.root_pipeline_id = Some(pipeline_id); + } + + pub fn set_display_list( + &mut self, + pipeline_id: PipelineId, + epoch: Epoch, + display_list: BuiltDisplayList, + background_color: Option, + viewport_size: LayoutSize, + content_size: LayoutSize, + ) { + // Adds a cache to the given display list. If this pipeline already had + // a display list before, that display list is updated and used instead. + let display_list = match self.pipelines.remove(&pipeline_id) { + Some(mut pipeline) => { + pipeline.display_list.update(display_list); + pipeline.display_list + } + None => DisplayListWithCache::new_from_list(display_list) + }; + + let new_pipeline = ScenePipeline { + pipeline_id, + viewport_size, + content_size, + background_color, + display_list, + }; + + self.pipelines.insert(pipeline_id, new_pipeline); + self.pipeline_epochs.insert(pipeline_id, epoch); + } + + pub fn remove_pipeline(&mut self, pipeline_id: PipelineId) { + if self.root_pipeline_id == Some(pipeline_id) { + self.root_pipeline_id = None; + } + self.pipelines.remove(&pipeline_id); + self.pipeline_epochs.remove(&pipeline_id); + } + + pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) { + self.pipeline_epochs.insert(pipeline_id, epoch); + } + + pub fn has_root_pipeline(&self) -> bool { + if let Some(ref root_id) = self.root_pipeline_id { + return self.pipelines.contains_key(root_id); + } + + false + } + + pub fn report_memory( + &self, + ops: &mut MallocSizeOfOps, + report: &mut MemoryReport + ) { + for (_, pipeline) in &self.pipelines { + report.display_list += pipeline.display_list.size_of(ops) + } + } +} + +pub trait StackingContextHelpers { + fn mix_blend_mode_for_compositing(&self) -> Option; +} + +impl StackingContextHelpers for StackingContext { + fn mix_blend_mode_for_compositing(&self) -> Option { + match self.mix_blend_mode { + MixBlendMode::Normal => None, + _ => Some(self.mix_blend_mode), + } + } +} + + +/// WebRender's internal representation of the scene. +pub struct BuiltScene { + pub has_root_pipeline: bool, + pub pipeline_epochs: FastHashMap, + pub output_rect: DeviceIntRect, + pub background_color: Option, + pub root_pic_index: PictureIndex, + pub prim_store: PrimitiveStore, + pub clip_store: ClipStore, + pub config: FrameBuilderConfig, + pub spatial_tree: SpatialTree, + pub hit_testing_scene: Arc, + pub content_slice_count: usize, + pub picture_cache_spatial_nodes: FastHashSet, +} + +impl BuiltScene { + pub fn empty() -> Self { + BuiltScene { + has_root_pipeline: false, + pipeline_epochs: FastHashMap::default(), + output_rect: DeviceIntRect::zero(), + background_color: None, + root_pic_index: PictureIndex(0), + prim_store: PrimitiveStore::new(&PrimitiveStoreStats::empty()), + clip_store: ClipStore::new(), + spatial_tree: SpatialTree::new(), + hit_testing_scene: Arc::new(HitTestingScene::new(&HitTestingSceneStats::empty())), + content_slice_count: 0, + picture_cache_spatial_nodes: FastHashSet::default(), + config: FrameBuilderConfig { + default_font_render_mode: FontRenderMode::Mono, + dual_source_blending_is_enabled: true, + dual_source_blending_is_supported: false, + chase_primitive: ChasePrimitive::Nothing, + global_enable_picture_caching: false, + testing: false, + gpu_supports_fast_clears: false, + gpu_supports_advanced_blend: false, + advanced_blend_is_coherent: false, + batch_lookback_count: 0, + background_color: None, + compositor_kind: CompositorKind::default(), + tile_size_override: None, + max_depth_ids: 0, + max_target_size: 0, + }, + } + } + + /// Get the memory usage statistics to pre-allocate for the next scene. + pub fn get_stats(&self) -> SceneStats { + SceneStats { + prim_store_stats: self.prim_store.get_stats(), + hit_test_stats: self.hit_testing_scene.get_stats(), + } + } + + pub fn create_hit_tester( + &mut self, + clip_data_store: &ClipDataStore, + ) -> HitTester { + HitTester::new( + Arc::clone(&self.hit_testing_scene), + &self.spatial_tree, + &self.clip_store, + clip_data_store, + ) + } +} + +/// Stores the allocation sizes of various arrays in the built +/// scene. This is retrieved from the current frame builder +/// and used to reserve an approximately correct capacity of +/// the arrays for the next scene that is getting built. +pub struct SceneStats { + pub prim_store_stats: PrimitiveStoreStats, + pub hit_test_stats: HitTestingSceneStats, +} + +impl SceneStats { + pub fn empty() -> Self { + SceneStats { + prim_store_stats: PrimitiveStoreStats::empty(), + hit_test_stats: HitTestingSceneStats::empty(), + } + } +} diff --git a/third_party/webrender/webrender/src/scene_builder_thread.rs b/third_party/webrender/webrender/src/scene_builder_thread.rs new file mode 100644 index 00000000000..dd599f45f84 --- /dev/null +++ b/third_party/webrender/webrender/src/scene_builder_thread.rs @@ -0,0 +1,911 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{AsyncBlobImageRasterizer, BlobImageRequest, BlobImageResult}; +use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, SceneMsg, ResourceUpdate, ExternalEvent}; +use api::{NotificationRequest, Checkpoint, IdNamespace, QualitySettings, TransactionMsg}; +use api::{ClipIntern, FilterDataIntern, MemoryReport, PrimitiveKeyKind, SharedFontInstanceMap}; +use api::{DocumentLayer, GlyphDimensionRequest, GlyphIndexRequest}; +use api::units::*; +#[cfg(feature = "capture")] +use crate::capture::CaptureConfig; +use crate::frame_builder::FrameBuilderConfig; +use crate::scene_building::SceneBuilder; +use crate::intern::{Internable, Interner, UpdateList}; +use crate::internal_types::{FastHashMap, FastHashSet}; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use crate::prim_store::backdrop::Backdrop; +use crate::prim_store::borders::{ImageBorder, NormalBorderPrim}; +use crate::prim_store::gradient::{LinearGradient, RadialGradient, ConicGradient}; +use crate::prim_store::image::{Image, YuvImage}; +use crate::prim_store::line_dec::LineDecoration; +use crate::prim_store::picture::Picture; +use crate::prim_store::text_run::TextRun; +use crate::render_backend::SceneView; +use crate::renderer::{PipelineInfo, SceneBuilderHooks}; +use crate::scene::{Scene, BuiltScene, SceneStats}; +use std::iter; +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::mem::replace; +use time::precise_time_ns; +use crate::util::drain_filter; +use std::thread; +use std::time::Duration; + +#[cfg(feature = "debugger")] +use crate::debug_server; +#[cfg(feature = "debugger")] +use api::{BuiltDisplayListIter, DisplayItem}; + +/// Various timing information that will be turned into +/// TransactionProfileCounters later down the pipeline. +#[derive(Clone, Debug)] +pub struct TransactionTimings { + pub builder_start_time_ns: u64, + pub builder_end_time_ns: u64, + pub send_time_ns: u64, + pub scene_build_start_time_ns: u64, + pub scene_build_end_time_ns: u64, + pub blob_rasterization_end_time_ns: u64, + pub display_list_len: usize, +} + +fn rasterize_blobs(txn: &mut TransactionMsg, is_low_priority: bool) { + profile_scope!("rasterize_blobs"); + + if let Some(ref mut rasterizer) = txn.blob_rasterizer { + let mut rasterized_blobs = rasterizer.rasterize(&txn.blob_requests, is_low_priority); + // try using the existing allocation if our current list is empty + if txn.rasterized_blobs.is_empty() { + txn.rasterized_blobs = rasterized_blobs; + } else { + txn.rasterized_blobs.append(&mut rasterized_blobs); + } + } +} + +/// Represent the remaining work associated to a transaction after the scene building +/// phase as well as the result of scene building itself if applicable. +pub struct BuiltTransaction { + pub document_id: DocumentId, + pub built_scene: Option, + pub view: SceneView, + pub resource_updates: Vec, + pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>, + pub blob_rasterizer: Option>, + pub frame_ops: Vec, + pub removed_pipelines: Vec<(PipelineId, DocumentId)>, + pub notifications: Vec, + pub interner_updates: Option, + pub scene_build_start_time: u64, + pub scene_build_end_time: u64, + pub render_frame: bool, + pub invalidate_rendered_frame: bool, + pub discard_frame_state_for_pipelines: Vec, + pub timings: Option, +} + +#[cfg(feature = "replay")] +pub struct LoadScene { + pub document_id: DocumentId, + pub scene: Scene, + pub font_instances: SharedFontInstanceMap, + pub view: SceneView, + pub config: FrameBuilderConfig, + pub build_frame: bool, + pub interners: Interners, +} + +// Message to the scene builder thread. +pub enum SceneBuilderRequest { + Transactions(Vec>), + ExternalEvent(ExternalEvent), + AddDocument(DocumentId, DeviceIntSize, DocumentLayer), + DeleteDocument(DocumentId), + GetGlyphDimensions(GlyphDimensionRequest), + GetGlyphIndices(GlyphIndexRequest), + WakeUp, + Stop, + Flush(Sender<()>), + ClearNamespace(IdNamespace), + SimulateLongSceneBuild(u32), + SimulateLongLowPrioritySceneBuild(u32), + /// Enqueue this to inform the scene builder to pick one message from + /// backend_rx. + BackendMessage, +} + +/// Message from render backend to scene builder. +pub enum BackendSceneBuilderRequest { + SetFrameBuilderConfig(FrameBuilderConfig), + ReportMemory(Box, Sender>), + #[cfg(feature = "capture")] + SaveScene(CaptureConfig), + #[cfg(feature = "replay")] + LoadScenes(Vec), + #[cfg(feature = "capture")] + StartCaptureSequence(CaptureConfig), + #[cfg(feature = "capture")] + StopCaptureSequence, + DocumentsForDebugger +} + +// Message from scene builder to render backend. +pub enum SceneBuilderResult { + Transactions(Vec>, Option>), + #[cfg(feature = "capture")] + CapturedTransactions(Vec>, CaptureConfig, Option>), + ExternalEvent(ExternalEvent), + FlushComplete(Sender<()>), + ClearNamespace(IdNamespace), + GetGlyphDimensions(GlyphDimensionRequest), + GetGlyphIndices(GlyphIndexRequest), + Stopped, + DocumentsForDebugger(String) +} + +// Message from render backend to scene builder to indicate the +// scene swap was completed. We need a separate channel for this +// so that they don't get mixed with SceneBuilderRequest messages. +pub enum SceneSwapResult { + Complete(Sender<()>), + Aborted, +} + +macro_rules! declare_interners { + ( $( $name:ident : $ty:ident, )+ ) => { + /// This struct contains all items that can be shared between + /// display lists. We want to intern and share the same clips, + /// primitives and other things between display lists so that: + /// - GPU cache handles remain valid, reducing GPU cache updates. + /// - Comparison of primitives and pictures between two + /// display lists is (a) fast (b) done during scene building. + #[cfg_attr(feature = "capture", derive(Serialize))] + #[cfg_attr(feature = "replay", derive(Deserialize))] + #[derive(Default)] + pub struct Interners { + $( + pub $name: Interner<$ty>, + )+ + } + + $( + impl AsMut> for Interners { + fn as_mut(&mut self) -> &mut Interner<$ty> { + &mut self.$name + } + } + )+ + + pub struct InternerUpdates { + $( + pub $name: UpdateList<<$ty as Internable>::Key>, + )+ + } + + impl Interners { + /// Reports CPU heap memory used by the interners. + fn report_memory( + &self, + ops: &mut MallocSizeOfOps, + r: &mut MemoryReport, + ) { + $( + r.interning.interners.$name += self.$name.size_of(ops); + )+ + } + + fn end_frame_and_get_pending_updates(&mut self) -> InternerUpdates { + InternerUpdates { + $( + $name: self.$name.end_frame_and_get_pending_updates(), + )+ + } + } + } + } +} + +enumerate_interners!(declare_interners); + +// A document in the scene builder contains the current scene, +// as well as a persistent clip interner. This allows clips +// to be de-duplicated, and persisted in the GPU cache between +// display lists. +struct Document { + scene: Scene, + interners: Interners, + stats: SceneStats, + view: SceneView, + /// A set of pipelines that the caller has requested be + /// made available as output textures. + output_pipelines: FastHashSet, +} + +impl Document { + fn new(device_rect: DeviceIntRect, layer: DocumentLayer, device_pixel_ratio: f32) -> Self { + Document { + scene: Scene::new(), + interners: Interners::default(), + stats: SceneStats::empty(), + output_pipelines: FastHashSet::default(), + view: SceneView { + device_rect, + layer, + device_pixel_ratio, + page_zoom_factor: 1.0, + quality_settings: QualitySettings::default(), + }, + } + } +} + +pub struct SceneBuilderThread { + documents: FastHashMap, + rx: Receiver, + backend_rx: Receiver, + tx: Sender, + api_tx: Sender, + config: FrameBuilderConfig, + default_device_pixel_ratio: f32, + font_instances: SharedFontInstanceMap, + size_of_ops: Option, + hooks: Option>, + simulate_slow_ms: u32, + removed_pipelines: FastHashSet, + #[cfg(feature = "capture")] + capture_config: Option, +} + +pub struct SceneBuilderThreadChannels { + rx: Receiver, + backend_rx: Receiver, + tx: Sender, + api_tx: Sender, +} + +impl SceneBuilderThreadChannels { + pub fn new( + api_tx: Sender + ) -> (Self, Sender, Sender, Receiver) { + let (in_tx, in_rx) = channel(); + let (out_tx, out_rx) = channel(); + let (backend_tx, backend_rx) = channel(); + ( + Self { + rx: in_rx, + backend_rx, + tx: out_tx, + api_tx, + }, + in_tx, + backend_tx, + out_rx, + ) + } +} + +impl SceneBuilderThread { + pub fn new( + config: FrameBuilderConfig, + default_device_pixel_ratio: f32, + font_instances: SharedFontInstanceMap, + size_of_ops: Option, + hooks: Option>, + channels: SceneBuilderThreadChannels, + ) -> Self { + let SceneBuilderThreadChannels { rx, backend_rx, tx, api_tx } = channels; + + Self { + documents: Default::default(), + rx, + backend_rx, + tx, + api_tx, + config, + default_device_pixel_ratio, + font_instances, + size_of_ops, + hooks, + simulate_slow_ms: 0, + removed_pipelines: FastHashSet::default(), + #[cfg(feature = "capture")] + capture_config: None, + } + } + + /// Send a message to the render backend thread. + /// + /// We first put something in the result queue and then send a wake-up + /// message to the api queue that the render backend is blocking on. + pub fn send(&self, msg: SceneBuilderResult) { + self.tx.send(msg).unwrap(); + let _ = self.api_tx.send(ApiMsg::WakeUp); + } + + /// The scene builder thread's event loop. + pub fn run(&mut self) { + if let Some(ref hooks) = self.hooks { + hooks.register(); + } + + loop { + tracy_begin_frame!("scene_builder_thread"); + + match self.rx.recv() { + Ok(SceneBuilderRequest::WakeUp) => {} + Ok(SceneBuilderRequest::Flush(tx)) => { + self.send(SceneBuilderResult::FlushComplete(tx)); + } + Ok(SceneBuilderRequest::Transactions(mut txns)) => { + let built_txns : Vec> = txns.iter_mut() + .map(|txn| self.process_transaction(txn)) + .collect(); + #[cfg(feature = "capture")] + match built_txns.iter().any(|txn| txn.built_scene.is_some()) { + true => self.save_capture_sequence(), + _ => {}, + } + self.forward_built_transactions(built_txns); + } + Ok(SceneBuilderRequest::AddDocument(document_id, initial_size, layer)) => { + let old = self.documents.insert(document_id, Document::new( + initial_size.into(), + layer, + self.default_device_pixel_ratio, + )); + debug_assert!(old.is_none()); + } + Ok(SceneBuilderRequest::DeleteDocument(document_id)) => { + self.documents.remove(&document_id); + } + Ok(SceneBuilderRequest::ClearNamespace(id)) => { + self.documents.retain(|doc_id, _doc| doc_id.namespace_id != id); + self.send(SceneBuilderResult::ClearNamespace(id)); + } + Ok(SceneBuilderRequest::ExternalEvent(evt)) => { + self.send(SceneBuilderResult::ExternalEvent(evt)); + } + Ok(SceneBuilderRequest::GetGlyphDimensions(request)) => { + self.send(SceneBuilderResult::GetGlyphDimensions(request)) + } + Ok(SceneBuilderRequest::GetGlyphIndices(request)) => { + self.send(SceneBuilderResult::GetGlyphIndices(request)) + } + Ok(SceneBuilderRequest::Stop) => { + self.tx.send(SceneBuilderResult::Stopped).unwrap(); + // We don't need to send a WakeUp to api_tx because we only + // get the Stop when the RenderBackend loop is exiting. + break; + } + Ok(SceneBuilderRequest::SimulateLongSceneBuild(time_ms)) => { + self.simulate_slow_ms = time_ms + } + Ok(SceneBuilderRequest::SimulateLongLowPrioritySceneBuild(_)) => {} + Ok(SceneBuilderRequest::BackendMessage) => { + let msg = self.backend_rx.try_recv().unwrap(); + match msg { + BackendSceneBuilderRequest::ReportMemory(mut report, tx) => { + (*report) += self.report_memory(); + tx.send(report).unwrap(); + } + BackendSceneBuilderRequest::SetFrameBuilderConfig(cfg) => { + self.config = cfg; + } + #[cfg(feature = "replay")] + BackendSceneBuilderRequest::LoadScenes(msg) => { + self.load_scenes(msg); + } + #[cfg(feature = "capture")] + BackendSceneBuilderRequest::SaveScene(config) => { + self.save_scene(config); + } + #[cfg(feature = "capture")] + BackendSceneBuilderRequest::StartCaptureSequence(config) => { + self.start_capture_sequence(config); + } + #[cfg(feature = "capture")] + BackendSceneBuilderRequest::StopCaptureSequence => { + // FIXME(aosmond): clear config for frames and resource cache without scene + // rebuild? + self.capture_config = None; + } + BackendSceneBuilderRequest::DocumentsForDebugger => { + let json = self.get_docs_for_debugger(); + self.send(SceneBuilderResult::DocumentsForDebugger(json)); + } + } + } + Err(_) => { + break; + } + } + + if let Some(ref hooks) = self.hooks { + hooks.poke(); + } + + tracy_end_frame!("scene_builder_thread"); + } + + if let Some(ref hooks) = self.hooks { + hooks.deregister(); + } + } + + #[cfg(feature = "capture")] + fn save_scene(&mut self, config: CaptureConfig) { + for (id, doc) in &self.documents { + let interners_name = format!("interners-{}-{}", id.namespace_id.0, id.id); + config.serialize_for_scene(&doc.interners, interners_name); + + if config.bits.contains(api::CaptureBits::SCENE) { + let file_name = format!("scene-{}-{}", id.namespace_id.0, id.id); + config.serialize_for_scene(&doc.scene, file_name); + } + } + } + + #[cfg(feature = "replay")] + fn load_scenes(&mut self, scenes: Vec) { + for mut item in scenes { + self.config = item.config; + + let scene_build_start_time = precise_time_ns(); + + let mut built_scene = None; + let mut interner_updates = None; + + let output_pipelines = FastHashSet::default(); + + if item.scene.has_root_pipeline() { + built_scene = Some(SceneBuilder::build( + &item.scene, + item.font_instances, + &item.view, + &output_pipelines, + &self.config, + &mut item.interners, + &SceneStats::empty(), + )); + + interner_updates = Some( + item.interners.end_frame_and_get_pending_updates() + ); + } + + self.documents.insert( + item.document_id, + Document { + scene: item.scene, + interners: item.interners, + stats: SceneStats::empty(), + view: item.view.clone(), + output_pipelines, + }, + ); + + let txns = vec![Box::new(BuiltTransaction { + document_id: item.document_id, + render_frame: item.build_frame, + invalidate_rendered_frame: false, + built_scene, + view: item.view, + resource_updates: Vec::new(), + rasterized_blobs: Vec::new(), + blob_rasterizer: None, + frame_ops: Vec::new(), + removed_pipelines: Vec::new(), + discard_frame_state_for_pipelines: Vec::new(), + notifications: Vec::new(), + scene_build_start_time, + scene_build_end_time: precise_time_ns(), + interner_updates, + timings: None, + })]; + + self.forward_built_transactions(txns); + } + } + + #[cfg(feature = "capture")] + fn save_capture_sequence( + &mut self, + ) { + if let Some(ref mut config) = self.capture_config { + config.prepare_scene(); + for (id, doc) in &self.documents { + let interners_name = format!("interners-{}-{}", id.namespace_id.0, id.id); + config.serialize_for_scene(&doc.interners, interners_name); + + if config.bits.contains(api::CaptureBits::SCENE) { + let file_name = format!("scene-{}-{}", id.namespace_id.0, id.id); + config.serialize_for_scene(&doc.scene, file_name); + } + } + } + } + + #[cfg(feature = "capture")] + fn start_capture_sequence( + &mut self, + config: CaptureConfig, + ) { + self.capture_config = Some(config); + self.save_capture_sequence(); + } + + #[cfg(feature = "debugger")] + fn traverse_items<'a>( + &self, + traversal: &mut BuiltDisplayListIter<'a>, + node: &mut debug_server::TreeNode, + ) { + loop { + let subtraversal = { + let item = match traversal.next() { + Some(item) => item, + None => break, + }; + + match *item.item() { + display_item @ DisplayItem::PushStackingContext(..) => { + let mut subtraversal = item.sub_iter(); + let mut child_node = + debug_server::TreeNode::new(&display_item.debug_name().to_string()); + self.traverse_items(&mut subtraversal, &mut child_node); + node.add_child(child_node); + Some(subtraversal) + } + DisplayItem::PopStackingContext => { + return; + } + display_item => { + node.add_item(&display_item.debug_name().to_string()); + None + } + } + }; + + // If flatten_item created a sub-traversal, we need `traversal` to have the + // same state as the completed subtraversal, so we reinitialize it here. + if let Some(subtraversal) = subtraversal { + *traversal = subtraversal; + } + } + } + + #[cfg(not(feature = "debugger"))] + fn get_docs_for_debugger(&self) -> String { + String::new() + } + + #[cfg(feature = "debugger")] + fn get_docs_for_debugger(&self) -> String { + let mut docs = debug_server::DocumentList::new(); + + for (_, doc) in &self.documents { + let mut debug_doc = debug_server::TreeNode::new("document"); + + for (_, pipeline) in &doc.scene.pipelines { + let mut debug_dl = debug_server::TreeNode::new("display-list"); + self.traverse_items(&mut pipeline.display_list.iter(), &mut debug_dl); + debug_doc.add_child(debug_dl); + } + + docs.add(debug_doc); + } + + serde_json::to_string(&docs).unwrap() + } + + /// Do the bulk of the work of the scene builder thread. + fn process_transaction(&mut self, txn: &mut TransactionMsg) -> Box { + profile_scope!("process_transaction"); + + if let Some(ref hooks) = self.hooks { + hooks.pre_scene_build(); + } + + let scene_build_start_time = precise_time_ns(); + + let doc = self.documents.get_mut(&txn.document_id).unwrap(); + let scene = &mut doc.scene; + + let mut timings = None; + + let mut discard_frame_state_for_pipelines = Vec::new(); + let mut removed_pipelines = Vec::new(); + let mut rebuild_scene = false; + for message in txn.scene_ops.drain(..) { + match message { + SceneMsg::UpdateEpoch(pipeline_id, epoch) => { + scene.update_epoch(pipeline_id, epoch); + } + SceneMsg::SetPageZoom(factor) => { + doc.view.page_zoom_factor = factor.get(); + } + SceneMsg::SetQualitySettings { settings } => { + doc.view.quality_settings = settings; + } + SceneMsg::SetDocumentView { device_rect, device_pixel_ratio } => { + doc.view.device_rect = device_rect; + doc.view.device_pixel_ratio = device_pixel_ratio; + } + SceneMsg::SetDisplayList { + epoch, + pipeline_id, + background, + viewport_size, + content_size, + display_list, + preserve_frame_state, + } => { + let display_list_len = display_list.data().len(); + + let (builder_start_time_ns, builder_end_time_ns, send_time_ns) = + display_list.times(); + + if self.removed_pipelines.contains(&pipeline_id) { + continue; + } + + // Note: We could further reduce the amount of unnecessary scene + // building by keeping track of which pipelines are used by the + // scene (bug 1490751). + rebuild_scene = true; + + scene.set_display_list( + pipeline_id, + epoch, + display_list, + background, + viewport_size, + content_size, + ); + + timings = Some(TransactionTimings { + builder_start_time_ns, + builder_end_time_ns, + send_time_ns, + scene_build_start_time_ns: 0, + scene_build_end_time_ns: 0, + blob_rasterization_end_time_ns: 0, + display_list_len, + }); + + if !preserve_frame_state { + discard_frame_state_for_pipelines.push(pipeline_id); + } + } + SceneMsg::SetRootPipeline(pipeline_id) => { + if scene.root_pipeline_id != Some(pipeline_id) { + rebuild_scene = true; + scene.set_root_pipeline_id(pipeline_id); + } + } + SceneMsg::RemovePipeline(pipeline_id) => { + scene.remove_pipeline(pipeline_id); + self.removed_pipelines.insert(pipeline_id); + removed_pipelines.push((pipeline_id, txn.document_id)); + } + SceneMsg::EnableFrameOutput(pipeline_id, enable) => { + if enable { + doc.output_pipelines.insert(pipeline_id); + } else { + doc.output_pipelines.remove(&pipeline_id); + } + } + } + } + + self.removed_pipelines.clear(); + + let mut built_scene = None; + let mut interner_updates = None; + if scene.has_root_pipeline() && rebuild_scene { + + let built = SceneBuilder::build( + &scene, + self.font_instances.clone(), + &doc.view, + &doc.output_pipelines, + &self.config, + &mut doc.interners, + &doc.stats, + ); + + // Update the allocation stats for next scene + doc.stats = built.get_stats(); + + // Retrieve the list of updates from the clip interner. + interner_updates = Some( + doc.interners.end_frame_and_get_pending_updates() + ); + + built_scene = Some(built); + } + + let scene_build_end_time = precise_time_ns(); + + let is_low_priority = false; + rasterize_blobs(txn, is_low_priority); + + if let Some(timings) = timings.as_mut() { + timings.blob_rasterization_end_time_ns = precise_time_ns(); + timings.scene_build_start_time_ns = scene_build_start_time; + timings.scene_build_end_time_ns = scene_build_end_time; + } + + drain_filter( + &mut txn.notifications, + |n| { n.when() == Checkpoint::SceneBuilt }, + |n| { n.notify(); }, + ); + + if self.simulate_slow_ms > 0 { + thread::sleep(Duration::from_millis(self.simulate_slow_ms as u64)); + } + + Box::new(BuiltTransaction { + document_id: txn.document_id, + render_frame: txn.generate_frame, + invalidate_rendered_frame: txn.invalidate_rendered_frame, + built_scene, + view: doc.view, + rasterized_blobs: replace(&mut txn.rasterized_blobs, Vec::new()), + resource_updates: replace(&mut txn.resource_updates, Vec::new()), + blob_rasterizer: replace(&mut txn.blob_rasterizer, None), + frame_ops: replace(&mut txn.frame_ops, Vec::new()), + removed_pipelines, + discard_frame_state_for_pipelines, + notifications: replace(&mut txn.notifications, Vec::new()), + interner_updates, + scene_build_start_time, + scene_build_end_time, + timings, + }) + } + + /// Send the results of process_transaction back to the render backend. + fn forward_built_transactions(&mut self, txns: Vec>) { + let (pipeline_info, result_tx, result_rx) = match self.hooks { + Some(ref hooks) => { + if txns.iter().any(|txn| txn.built_scene.is_some()) { + let info = PipelineInfo { + epochs: txns.iter() + .filter(|txn| txn.built_scene.is_some()) + .map(|txn| { + txn.built_scene.as_ref().unwrap() + .pipeline_epochs.iter() + .zip(iter::repeat(txn.document_id)) + .map(|((&pipeline_id, &epoch), document_id)| ((pipeline_id, document_id), epoch)) + }).flatten().collect(), + removed_pipelines: txns.iter() + .map(|txn| txn.removed_pipelines.clone()) + .flatten().collect(), + }; + + let (tx, rx) = channel(); + let txn = txns.iter().find(|txn| txn.built_scene.is_some()).unwrap(); + hooks.pre_scene_swap(txn.scene_build_end_time - txn.scene_build_start_time); + + (Some(info), Some(tx), Some(rx)) + } else { + (None, None, None) + } + } + _ => (None, None, None) + }; + + let scene_swap_start_time = precise_time_ns(); + let document_ids = txns.iter().map(|txn| txn.document_id).collect(); + let have_resources_updates : Vec = if pipeline_info.is_none() { + txns.iter() + .filter(|txn| !txn.resource_updates.is_empty() || txn.invalidate_rendered_frame) + .map(|txn| txn.document_id) + .collect() + } else { + Vec::new() + }; + + #[cfg(feature = "capture")] + match self.capture_config { + Some(ref config) => self.tx.send(SceneBuilderResult::CapturedTransactions(txns, config.clone(), result_tx)).unwrap(), + None => self.tx.send(SceneBuilderResult::Transactions(txns, result_tx)).unwrap(), + } + + #[cfg(not(feature = "capture"))] + self.tx.send(SceneBuilderResult::Transactions(txns, result_tx)).unwrap(); + + let _ = self.api_tx.send(ApiMsg::WakeUp); + + if let Some(pipeline_info) = pipeline_info { + // Block until the swap is done, then invoke the hook. + let swap_result = result_rx.unwrap().recv(); + let scene_swap_time = precise_time_ns() - scene_swap_start_time; + self.hooks.as_ref().unwrap().post_scene_swap(&document_ids, + pipeline_info, scene_swap_time); + // Once the hook is done, allow the RB thread to resume + if let Ok(SceneSwapResult::Complete(resume_tx)) = swap_result { + resume_tx.send(()).ok(); + } + } else if !have_resources_updates.is_empty() { + if let Some(ref hooks) = self.hooks { + hooks.post_resource_update(&have_resources_updates); + } + } else if let Some(ref hooks) = self.hooks { + hooks.post_empty_scene_build(); + } + } + + /// Reports CPU heap memory used by the SceneBuilder. + fn report_memory(&mut self) -> MemoryReport { + let ops = self.size_of_ops.as_mut().unwrap(); + let mut report = MemoryReport::default(); + for doc in self.documents.values() { + doc.interners.report_memory(ops, &mut report); + doc.scene.report_memory(ops, &mut report); + } + + report + } +} + +/// A scene builder thread which executes expensive operations such as blob rasterization +/// with a lower priority than the normal scene builder thread. +/// +/// After rasterizing blobs, the secene building request is forwarded to the normal scene +/// builder where the FrameBuilder is generated. +pub struct LowPrioritySceneBuilderThread { + pub rx: Receiver, + pub tx: Sender, + pub simulate_slow_ms: u32, +} + +impl LowPrioritySceneBuilderThread { + pub fn run(&mut self) { + loop { + match self.rx.recv() { + Ok(SceneBuilderRequest::Transactions(mut txns)) => { + let txns : Vec> = txns.drain(..) + .map(|txn| self.process_transaction(txn)) + .collect(); + self.tx.send(SceneBuilderRequest::Transactions(txns)).unwrap(); + } + Ok(SceneBuilderRequest::AddDocument(id, size, layer)) => { + self.tx.send(SceneBuilderRequest::AddDocument(id, size, layer)).unwrap(); + } + Ok(SceneBuilderRequest::DeleteDocument(document_id)) => { + self.tx.send(SceneBuilderRequest::DeleteDocument(document_id)).unwrap(); + } + Ok(SceneBuilderRequest::Stop) => { + self.tx.send(SceneBuilderRequest::Stop).unwrap(); + break; + } + Ok(SceneBuilderRequest::SimulateLongLowPrioritySceneBuild(time_ms)) => { + self.simulate_slow_ms = time_ms; + } + Ok(other) => { + self.tx.send(other).unwrap(); + } + Err(_) => { + break; + } + } + } + } + + fn process_transaction(&mut self, mut txn: Box) -> Box { + let is_low_priority = true; + rasterize_blobs(&mut txn, is_low_priority); + txn.blob_requests = Vec::new(); + + if self.simulate_slow_ms > 0 { + thread::sleep(Duration::from_millis(self.simulate_slow_ms as u64)); + } + + txn + } +} diff --git a/third_party/webrender/webrender/src/scene_building.rs b/third_party/webrender/webrender/src/scene_building.rs new file mode 100644 index 00000000000..bfc466640bc --- /dev/null +++ b/third_party/webrender/webrender/src/scene_building.rs @@ -0,0 +1,4090 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter, PrimitiveFlags}; +use api::{ClipId, ColorF, CommonItemProperties, ComplexClipRegion, ComponentTransferFuncType, RasterSpace}; +use api::{DisplayItem, DisplayItemRef, ExtendMode, ExternalScrollId, FilterData, SharedFontInstanceMap}; +use api::{FilterOp, FilterPrimitive, FontInstanceKey, FontSize, GlyphInstance, GlyphOptions, GradientStop}; +use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, ColorDepth, QualitySettings}; +use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId, MixBlendMode, StackingContextFlags}; +use api::{PropertyBinding, ReferenceFrame, ReferenceFrameKind, ScrollFrameDisplayItem, ScrollSensitivity}; +use api::{Shadow, SpaceAndClipInfo, SpatialId, StackingContext, StickyFrameDisplayItem, ImageMask}; +use api::{ClipMode, PrimitiveKeyKind, TransformStyle, YuvColorSpace, ColorRange, YuvData, TempFilterData}; +use api::image_tiling::simplify_repeated_primitive; +use api::units::*; +use crate::clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore, ClipItemKeyKind}; +use crate::clip::{ClipInternData, ClipNodeKind, ClipInstance}; +use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX, SpatialTree, SpatialNodeIndex}; +use crate::frame_builder::{ChasePrimitive, FrameBuilderConfig}; +use crate::glyph_rasterizer::FontInstance; +use crate::hit_test::{HitTestingItem, HitTestingScene}; +use crate::intern::Interner; +use crate::internal_types::{FastHashMap, FastHashSet, LayoutPrimitiveInfo, Filter}; +use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PictureOptions}; +use crate::picture::{BlitReason, OrderedPictureChild, PrimitiveList, TileCacheInstance, ClusterFlags}; +use crate::prim_store::PrimitiveInstance; +use crate::prim_store::{PrimitiveInstanceKind, NinePatchDescriptor, PrimitiveStore}; +use crate::prim_store::{InternablePrimitive, SegmentInstanceIndex, PictureIndex}; +use crate::prim_store::{register_prim_chase_id, get_line_decoration_size}; +use crate::prim_store::{SpaceSnapper}; +use crate::prim_store::backdrop::Backdrop; +use crate::prim_store::borders::{ImageBorder, NormalBorderPrim}; +use crate::prim_store::gradient::{GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams, ConicGradient, ConicGradientParams}; +use crate::prim_store::image::{Image, YuvImage}; +use crate::prim_store::line_dec::{LineDecoration, LineDecorationCacheKey}; +use crate::prim_store::picture::{Picture, PictureCompositeKey, PictureKey}; +use crate::prim_store::text_run::TextRun; +use crate::render_backend::SceneView; +use crate::resource_cache::ImageRequest; +use crate::scene::{Scene, BuiltScene, SceneStats, StackingContextHelpers}; +use crate::scene_builder_thread::Interners; +use crate::spatial_node::{StickyFrameInfo, ScrollFrameKind}; +use euclid::approxeq::ApproxEq; +use std::{f32, mem, usize, ops}; +use std::collections::vec_deque::VecDeque; +use std::sync::Arc; +use crate::util::{MaxRect, VecHelper}; +use crate::filterdata::{SFilterDataComponent, SFilterData, SFilterDataKey}; +use smallvec::SmallVec; + +/// The offset stack for a given reference frame. +struct ReferenceFrameState { + /// A stack of current offsets from the current reference frame scope. + offsets: Vec, +} + +/// Maps from stacking context layout coordinates into reference frame +/// relative coordinates. +struct ReferenceFrameMapper { + /// A stack of reference frame scopes. + frames: Vec, +} + +impl ReferenceFrameMapper { + fn new() -> Self { + ReferenceFrameMapper { + frames: vec![ + ReferenceFrameState { + offsets: vec![ + LayoutVector2D::zero(), + ], + } + ], + } + } + + /// Push a new scope. This resets the current offset to zero, and is + /// used when a new reference frame or iframe is pushed. + fn push_scope(&mut self) { + self.frames.push(ReferenceFrameState { + offsets: vec![ + LayoutVector2D::zero(), + ], + }); + } + + /// Pop a reference frame scope off the stack. + fn pop_scope(&mut self) { + self.frames.pop().unwrap(); + } + + /// Push a new offset for the current scope. This is used when + /// a new stacking context is pushed. + fn push_offset(&mut self, offset: LayoutVector2D) { + let frame = self.frames.last_mut().unwrap(); + let current_offset = *frame.offsets.last().unwrap(); + frame.offsets.push(current_offset + offset); + } + + /// Pop a local stacking context offset from the current scope. + fn pop_offset(&mut self) { + let frame = self.frames.last_mut().unwrap(); + frame.offsets.pop().unwrap(); + } + + /// Retrieve the current offset to allow converting a stacking context + /// relative coordinate to be relative to the owing reference frame. + /// TODO(gw): We could perhaps have separate coordinate spaces for this, + /// however that's going to either mean a lot of changes to + /// public API code, or a lot of changes to internal code. + /// Before doing that, we should revisit how Gecko would + /// prefer to provide coordinates. + /// TODO(gw): For now, this includes only the reference frame relative + /// offset. Soon, we will expand this to include the initial + /// scroll offsets that are now available on scroll nodes. This + /// will allow normalizing the coordinates even between display + /// lists where APZ has scrolled the content. + fn current_offset(&self) -> LayoutVector2D { + *self.frames.last().unwrap().offsets.last().unwrap() + } +} + +/// Offsets primitives (and clips) by the external scroll offset +/// supplied to scroll nodes. +pub struct ScrollOffsetMapper { + pub current_spatial_node: SpatialNodeIndex, + pub current_offset: LayoutVector2D, +} + +impl ScrollOffsetMapper { + fn new() -> Self { + ScrollOffsetMapper { + current_spatial_node: SpatialNodeIndex::INVALID, + current_offset: LayoutVector2D::zero(), + } + } + + /// Return the accumulated external scroll offset for a spatial + /// node. This caches the last result, which is the common case, + /// or defers to the spatial tree to build the value. + fn external_scroll_offset( + &mut self, + spatial_node_index: SpatialNodeIndex, + spatial_tree: &SpatialTree, + ) -> LayoutVector2D { + if spatial_node_index != self.current_spatial_node { + self.current_spatial_node = spatial_node_index; + self.current_offset = spatial_tree.external_scroll_offset(spatial_node_index); + } + + self.current_offset + } +} + +/// A data structure that keeps track of mapping between API Ids for spatials and the indices +/// used internally in the SpatialTree to avoid having to do HashMap lookups for primitives +/// and clips during frame building. +#[derive(Default)] +pub struct NodeIdToIndexMapper { + spatial_node_map: FastHashMap, +} + +impl NodeIdToIndexMapper { + fn add_spatial_node(&mut self, id: SpatialId, index: SpatialNodeIndex) { + let _old_value = self.spatial_node_map.insert(id, index); + debug_assert!(_old_value.is_none()); + } + + fn get_spatial_node_index(&self, id: SpatialId) -> SpatialNodeIndex { + self.spatial_node_map[&id] + } +} + +#[derive(Debug, Clone, Default)] +pub struct CompositeOps { + // Requires only a single texture as input (e.g. most filters) + pub filters: Vec, + pub filter_datas: Vec, + pub filter_primitives: Vec, + + // Requires two source textures (e.g. mix-blend-mode) + pub mix_blend_mode: Option, +} + +impl CompositeOps { + pub fn new( + filters: Vec, + filter_datas: Vec, + filter_primitives: Vec, + mix_blend_mode: Option + ) -> Self { + CompositeOps { + filters, + filter_datas, + filter_primitives, + mix_blend_mode, + } + } + + pub fn is_empty(&self) -> bool { + self.filters.is_empty() && + self.filter_primitives.is_empty() && + self.mix_blend_mode.is_none() + } +} + +bitflags! { + /// Slice flags + pub struct SliceFlags : u8 { + /// Slice created by a cluster that has ClusterFlags::SCROLLBAR_CONTAINER + const IS_SCROLLBAR = 1; + } +} + +/// Information about a set of primitive clusters that will form a picture cache slice. +struct Slice { + /// The spatial node root of the picture cache. If this is None, the slice + /// will not be cached and instead drawn directly to the parent surface. This + /// is a temporary measure until we enable caching all slices. + cache_scroll_root: Option, + /// List of primitive clusters that make up this slice + prim_list: PrimitiveList, + /// A list of clips that are shared by all primitives in the slice. These can be + /// filtered out and applied when the tile cache is composited rather than per-item. + shared_clips: Option>, + /// Various flags describing properties of this slice + pub flags: SliceFlags, +} + +/// A structure that converts a serialized display list into a form that WebRender +/// can use to later build a frame. This structure produces a BuiltScene. Public +/// members are typically those that are destructured into the BuiltScene. +pub struct SceneBuilder<'a> { + /// The scene that we are currently building. + scene: &'a Scene, + + /// The map of all font instances. + font_instances: SharedFontInstanceMap, + + /// A set of pipelines that the caller has requested be made available as + /// output textures. + output_pipelines: &'a FastHashSet, + + /// The data structure that converts between ClipId/SpatialId and the various + /// index types that the SpatialTree uses. + id_to_index_mapper: NodeIdToIndexMapper, + + /// A stack of stacking context properties. + sc_stack: Vec, + + /// Maintains state for any currently active shadows + pending_shadow_items: VecDeque, + + /// The SpatialTree that we are currently building during building. + pub spatial_tree: SpatialTree, + + /// The store of primitives. + pub prim_store: PrimitiveStore, + + /// Information about all primitives involved in hit testing. + pub hit_testing_scene: HitTestingScene, + + /// The store which holds all complex clipping information. + pub clip_store: ClipStore, + + /// The configuration to use for the FrameBuilder. We consult this in + /// order to determine the default font. + pub config: FrameBuilderConfig, + + /// Reference to the set of data that is interned across display lists. + interners: &'a mut Interners, + + /// The root picture index for this builder. This is the picture + /// to start the culling phase from. + pub root_pic_index: PictureIndex, + + /// Helper struct to map stacking context coords <-> reference frame coords. + rf_mapper: ReferenceFrameMapper, + + /// Helper struct to map spatial nodes to external scroll offsets. + external_scroll_mapper: ScrollOffsetMapper, + + /// If true, picture caching setup has already been completed. + picture_caching_initialized: bool, + + /// The current recursion depth of iframes encountered. Used to restrict picture + /// caching slices to only the top-level content frame. + iframe_depth: usize, + + /// The number of picture cache slices that were created for content. + content_slice_count: usize, + + /// A set of any spatial nodes that are attached to either a picture cache + /// root, or a clip node on the picture cache primitive. These are used + /// to detect cases where picture caching must be disabled. This is mostly + /// a temporary workaround for some existing wrench tests. I don't think + /// Gecko ever produces picture cache slices with complex transforms, so + /// in future we should prevent this in the public API and remove this hack. + picture_cache_spatial_nodes: FastHashSet, + + /// The current quality / performance settings for this scene. + quality_settings: QualitySettings, +} + +impl<'a> SceneBuilder<'a> { + pub fn build( + scene: &Scene, + font_instances: SharedFontInstanceMap, + view: &SceneView, + output_pipelines: &FastHashSet, + frame_builder_config: &FrameBuilderConfig, + interners: &mut Interners, + stats: &SceneStats, + ) -> BuiltScene { + profile_scope!("build_scene"); + + // We checked that the root pipeline is available on the render backend. + let root_pipeline_id = scene.root_pipeline_id.unwrap(); + let root_pipeline = scene.pipelines.get(&root_pipeline_id).unwrap(); + + let background_color = root_pipeline + .background_color + .and_then(|color| if color.a > 0.0 { Some(color) } else { None }); + + let mut builder = SceneBuilder { + scene, + spatial_tree: SpatialTree::new(), + font_instances, + config: *frame_builder_config, + output_pipelines, + id_to_index_mapper: NodeIdToIndexMapper::default(), + hit_testing_scene: HitTestingScene::new(&stats.hit_test_stats), + pending_shadow_items: VecDeque::new(), + sc_stack: Vec::new(), + prim_store: PrimitiveStore::new(&stats.prim_store_stats), + clip_store: ClipStore::new(), + interners, + root_pic_index: PictureIndex(0), + rf_mapper: ReferenceFrameMapper::new(), + external_scroll_mapper: ScrollOffsetMapper::new(), + picture_caching_initialized: false, + iframe_depth: 0, + content_slice_count: 0, + picture_cache_spatial_nodes: FastHashSet::default(), + quality_settings: view.quality_settings, + }; + + let device_pixel_scale = view.accumulated_scale_factor_for_snapping(); + + builder.clip_store.register_clip_template( + ClipId::root(root_pipeline_id), + ClipId::root(root_pipeline_id), + &[], + ); + + builder.clip_store.push_clip_root( + Some(ClipId::root(root_pipeline_id)), + false, + ); + + builder.push_root( + root_pipeline_id, + &root_pipeline.viewport_size, + &root_pipeline.content_size, + device_pixel_scale, + ); + + // In order to ensure we have a single root stacking context for the + // entire display list, we push one here. Gecko _almost_ wraps its + // entire display list within a single stacking context, but sometimes + // appends a few extra items in AddWindowOverlayWebRenderCommands. We + // could fix it there, but it's easier and more robust for WebRender + // to just ensure there's a context on the stack whenever we append + // primitives (since otherwise we'd panic). + // + // Note that we don't do this for iframes, even if they're pipeline + // roots, because they should be entirely contained within a stacking + // context, and we probably wouldn't crash if they weren't. + builder.push_stacking_context( + root_pipeline.pipeline_id, + CompositeOps::default(), + TransformStyle::Flat, + /* prim_flags = */ PrimitiveFlags::IS_BACKFACE_VISIBLE, + ROOT_SPATIAL_NODE_INDEX, + None, + RasterSpace::Screen, + StackingContextFlags::IS_BACKDROP_ROOT, + device_pixel_scale, + ); + + builder.build_items( + &mut root_pipeline.display_list.iter(), + root_pipeline.pipeline_id, + ); + + builder.pop_stacking_context(); + builder.clip_store.pop_clip_root(); + + debug_assert!(builder.sc_stack.is_empty()); + + BuiltScene { + has_root_pipeline: scene.has_root_pipeline(), + pipeline_epochs: scene.pipeline_epochs.clone(), + output_rect: view.device_rect.size.into(), + background_color, + hit_testing_scene: Arc::new(builder.hit_testing_scene), + spatial_tree: builder.spatial_tree, + prim_store: builder.prim_store, + clip_store: builder.clip_store, + root_pic_index: builder.root_pic_index, + config: builder.config, + content_slice_count: builder.content_slice_count, + picture_cache_spatial_nodes: builder.picture_cache_spatial_nodes, + } + } + + /// Retrieve the current offset to allow converting a stacking context + /// relative coordinate to be relative to the owing reference frame, + /// also considering any external scroll offset on the provided + /// spatial node. + fn current_offset( + &mut self, + spatial_node_index: SpatialNodeIndex, + ) -> LayoutVector2D { + // Get the current offset from stacking context <-> reference frame space. + let rf_offset = self.rf_mapper.current_offset(); + + // Get the external scroll offset, if applicable. + let scroll_offset = self + .external_scroll_mapper + .external_scroll_offset( + spatial_node_index, + &self.spatial_tree, + ); + + rf_offset + scroll_offset + } + + /// Figure out the shape of the display list, and wrap various primitive clusters + /// into tile cache primitive instances. + fn setup_picture_caching( + &mut self, + main_prim_list: &mut PrimitiveList, + ) { + if !self.config.global_enable_picture_caching { + return; + } + + // Ensure that setup_picture_caching has executed + debug_assert!(self.picture_caching_initialized); + + // Unconditionally insert a marker to create a picture cache slice on the + // first cluster. This handles implicit picture caches, and also the common + // case, by allowing the root / background primitives to be cached in a slice. + if let Some(cluster) = main_prim_list.clusters.first_mut() { + cluster.flags.insert(ClusterFlags::CREATE_PICTURE_CACHE_PRE); + } + + // List of slices that have been found + let mut slices: Vec = Vec::new(); + // Tracker for whether a new slice should be created + let mut create_slice = true; + // The clips found the last time we traversed a set of clip chains. Stored and cleared + // here to avoid constant allocations. + let mut prim_clips = Vec::new(); + // If true, the cache is out of date and needs to be rebuilt. + let mut update_shared_clips = true; + // The last prim clip chain we build prim_clips for. + let mut last_prim_clip_chain_id = ClipChainId::NONE; + + // Walk the supplied top level of clusters, slicing into slices as appropriate + for cluster in main_prim_list.clusters.drain(..) { + // Check if this cluster requires a new slice + create_slice |= cluster.flags.intersects( + ClusterFlags::CREATE_PICTURE_CACHE_PRE | ClusterFlags::IS_CLEAR_PRIMITIVE + ); + + if create_slice { + let slice_flags = if cluster.flags.contains(ClusterFlags::SCROLLBAR_CONTAINER) { + SliceFlags::IS_SCROLLBAR + } else { + SliceFlags::empty() + }; + let slice = Slice { + cache_scroll_root: cluster.cache_scroll_root, + prim_list: PrimitiveList::empty(), + shared_clips: None, + flags: slice_flags + }; + + // Open up clip chains on the stack on the new slice + slices.push(slice); + create_slice = false; + } + + // Step through each prim instance, in order to collect shared clips for the slice. + for instance in &cluster.prim_instances { + // If the primitive clip chain is different, then we need to rebuild prim_clips. + update_shared_clips |= last_prim_clip_chain_id != instance.clip_chain_id; + last_prim_clip_chain_id = instance.clip_chain_id; + + if update_shared_clips { + prim_clips.clear(); + // Update the list of clips that apply to this primitive instance + add_clips( + instance.clip_chain_id, + &mut prim_clips, + &self.clip_store, + &self.interners, + ); + } + + // If there are no shared clips set for this slice, the shared clips are just + // the current clips set. Otherwise, the shared clips are those that are + // in both the current shared list and the clips list for this primitive. + match slices.last_mut().unwrap().shared_clips { + Some(ref mut shared_clips) => { + if update_shared_clips { + shared_clips.retain(|h1: &ClipInstance| { + let uid = h1.handle.uid(); + prim_clips.iter().any(|h2| { + uid == h2.handle.uid() && + h1.spatial_node_index == h2.spatial_node_index + }) + }); + } + } + ref mut shared_clips @ None => { + *shared_clips = Some(prim_clips.clone()); + } + } + + update_shared_clips = false; + } + + // If this cluster creates a slice after, then note that for next cluster + create_slice |= cluster.flags.intersects( + ClusterFlags::CREATE_PICTURE_CACHE_POST | ClusterFlags::IS_CLEAR_PRIMITIVE + ); + + // Finally, add this cluster to the current slice + slices.last_mut().unwrap().prim_list.add_cluster(cluster); + } + + // Step through the slices, creating picture cache wrapper instances. + for (slice_index, slice) in slices.drain(..).enumerate() { + let background_color = if slice_index == 0 { + self.config.background_color + } else { + None + }; + + // If the cluster specifies a scroll root, use it. Otherwise, + // just cache assuming no scrolling takes place. Even if that's + // not true, we still get caching benefits for any changes that + // occur while not scrolling (such as animation, video etc); + let scroll_root = slice.cache_scroll_root.unwrap_or(ROOT_SPATIAL_NODE_INDEX); + + let instance = create_tile_cache( + slice_index, + slice.flags, + scroll_root, + slice.prim_list, + background_color, + slice.shared_clips.unwrap_or_else(Vec::new), + &mut self.interners, + &mut self.prim_store, + &mut self.clip_store, + &mut self.picture_cache_spatial_nodes, + &self.config, + ); + + main_prim_list.add_prim( + instance, + LayoutRect::zero(), + scroll_root, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + } + } + + fn build_items( + &mut self, + traversal: &mut BuiltDisplayListIter<'a>, + pipeline_id: PipelineId, + ) { + loop { + let item = match traversal.next() { + Some(item) => item, + None => break, + }; + + let subtraversal = match item.item() { + DisplayItem::PushStackingContext(ref info) => { + let space = self.get_space(info.spatial_id); + let mut subtraversal = item.sub_iter(); + self.build_stacking_context( + &mut subtraversal, + pipeline_id, + &info.stacking_context, + space, + info.origin, + item.filters(), + &item.filter_datas(), + item.filter_primitives(), + info.prim_flags, + ); + Some(subtraversal) + } + DisplayItem::PushReferenceFrame(ref info) => { + let parent_space = self.get_space(info.parent_spatial_id); + let mut subtraversal = item.sub_iter(); + self.build_reference_frame( + &mut subtraversal, + pipeline_id, + parent_space, + info.origin, + &info.reference_frame, + ); + Some(subtraversal) + } + DisplayItem::PopReferenceFrame | + DisplayItem::PopStackingContext => return, + _ => None, + }; + + // If build_item created a sub-traversal, we need `traversal` to have the + // same state as the completed subtraversal, so we reinitialize it here. + if let Some(mut subtraversal) = subtraversal { + subtraversal.merge_debug_stats_from(traversal); + *traversal = subtraversal; + } else { + self.build_item(item, pipeline_id); + } + } + + // TODO: factor this out to be part of capture + if cfg!(feature = "display_list_stats") { + let stats = traversal.debug_stats(); + let total_bytes: usize = stats.iter().map(|(_, stats)| stats.num_bytes).sum(); + println!("item, total count, total bytes, % of DL bytes, bytes per item"); + for (label, stats) in stats { + println!("{}, {}, {}kb, {}%, {}", + label, + stats.total_count, + stats.num_bytes / 1000, + ((stats.num_bytes as f32 / total_bytes.max(1) as f32) * 100.0) as usize, + stats.num_bytes / stats.total_count.max(1)); + } + println!(); + } + } + + fn build_sticky_frame( + &mut self, + info: &StickyFrameDisplayItem, + parent_node_index: SpatialNodeIndex, + ) { + let current_offset = self.current_offset(parent_node_index); + let frame_rect = info.bounds.translate(current_offset); + let sticky_frame_info = StickyFrameInfo::new( + frame_rect, + info.margins, + info.vertical_offset_bounds, + info.horizontal_offset_bounds, + info.previously_applied_offset, + ); + + let index = self.spatial_tree.add_sticky_frame( + parent_node_index, + sticky_frame_info, + info.id.pipeline_id(), + ); + self.id_to_index_mapper.add_spatial_node(info.id, index); + } + + fn build_scroll_frame( + &mut self, + info: &ScrollFrameDisplayItem, + parent_node_index: SpatialNodeIndex, + pipeline_id: PipelineId, + ) { + let current_offset = self.current_offset(parent_node_index); + let clip_region = ClipRegion::create_for_clip_node_with_local_clip( + &info.clip_rect, + ¤t_offset, + ); + // Just use clip rectangle as the frame rect for this scroll frame. + // This is useful when calculating scroll extents for the + // SpatialNode::scroll(..) API as well as for properly setting sticky + // positioning offsets. + let frame_rect = clip_region.main; + let content_size = info.content_rect.size; + + self.add_clip_node(info.clip_id, &info.parent_space_and_clip, clip_region); + + self.add_scroll_frame( + info.scroll_frame_id, + parent_node_index, + info.external_id, + pipeline_id, + &frame_rect, + &content_size, + info.scroll_sensitivity, + ScrollFrameKind::Explicit, + info.external_scroll_offset, + ); + } + + fn build_reference_frame( + &mut self, + traversal: &mut BuiltDisplayListIter<'a>, + pipeline_id: PipelineId, + parent_spatial_node: SpatialNodeIndex, + origin: LayoutPoint, + reference_frame: &ReferenceFrame, + ) { + profile_scope!("build_reference_frame"); + let current_offset = self.current_offset(parent_spatial_node); + self.push_reference_frame( + reference_frame.id, + Some(parent_spatial_node), + pipeline_id, + reference_frame.transform_style, + reference_frame.transform, + reference_frame.kind, + current_offset + origin.to_vector(), + ); + + self.rf_mapper.push_scope(); + self.build_items( + traversal, + pipeline_id, + ); + self.rf_mapper.pop_scope(); + } + + + fn build_stacking_context( + &mut self, + traversal: &mut BuiltDisplayListIter<'a>, + pipeline_id: PipelineId, + stacking_context: &StackingContext, + spatial_node_index: SpatialNodeIndex, + origin: LayoutPoint, + filters: ItemRange, + filter_datas: &[TempFilterData], + filter_primitives: ItemRange, + prim_flags: PrimitiveFlags, + ) { + profile_scope!("build_stacking_context"); + // Avoid doing unnecessary work for empty stacking contexts. + if traversal.current_stacking_context_empty() { + traversal.skip_current_stacking_context(); + return; + } + + let composition_operations = { + CompositeOps::new( + filter_ops_for_compositing(filters), + filter_datas_for_compositing(filter_datas), + filter_primitives_for_compositing(filter_primitives), + stacking_context.mix_blend_mode_for_compositing(), + ) + }; + + self.push_stacking_context( + pipeline_id, + composition_operations, + stacking_context.transform_style, + prim_flags, + spatial_node_index, + stacking_context.clip_id, + stacking_context.raster_space, + stacking_context.flags, + self.sc_stack.last().unwrap().snap_to_device.device_pixel_scale, + ); + + self.rf_mapper.push_offset(origin.to_vector()); + self.build_items( + traversal, + pipeline_id, + ); + self.rf_mapper.pop_offset(); + + self.pop_stacking_context(); + } + + fn build_iframe( + &mut self, + info: &IframeDisplayItem, + spatial_node_index: SpatialNodeIndex, + ) { + let iframe_pipeline_id = info.pipeline_id; + let pipeline = match self.scene.pipelines.get(&iframe_pipeline_id) { + Some(pipeline) => pipeline, + None => { + debug_assert!(info.ignore_missing_pipeline); + return + }, + }; + + let current_offset = self.current_offset(spatial_node_index); + self.add_clip_node( + ClipId::root(iframe_pipeline_id), + &info.space_and_clip, + ClipRegion::create_for_clip_node_with_local_clip( + &info.clip_rect, + ¤t_offset, + ), + ); + + self.clip_store.push_clip_root( + Some(ClipId::root(iframe_pipeline_id)), + true, + ); + + let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device; + snap_to_device.set_target_spatial_node( + spatial_node_index, + &self.spatial_tree, + ); + + let bounds = snap_to_device.snap_rect( + &info.bounds.translate(current_offset), + ); + + let content_size = snap_to_device.snap_size(&pipeline.content_size); + + let spatial_node_index = self.push_reference_frame( + SpatialId::root_reference_frame(iframe_pipeline_id), + Some(spatial_node_index), + iframe_pipeline_id, + TransformStyle::Flat, + PropertyBinding::Value(LayoutTransform::identity()), + ReferenceFrameKind::Transform, + bounds.origin.to_vector(), + ); + + let iframe_rect = LayoutRect::new(LayoutPoint::zero(), bounds.size); + self.add_scroll_frame( + SpatialId::root_scroll_node(iframe_pipeline_id), + spatial_node_index, + Some(ExternalScrollId(0, iframe_pipeline_id)), + iframe_pipeline_id, + &iframe_rect, + &content_size, + ScrollSensitivity::ScriptAndInputEvents, + ScrollFrameKind::PipelineRoot, + LayoutVector2D::zero(), + ); + + self.rf_mapper.push_scope(); + self.iframe_depth += 1; + + self.build_items( + &mut pipeline.display_list.iter(), + pipeline.pipeline_id, + ); + self.iframe_depth -= 1; + self.rf_mapper.pop_scope(); + + self.clip_store.pop_clip_root(); + } + + fn get_space( + &self, + spatial_id: SpatialId, + ) -> SpatialNodeIndex { + self.id_to_index_mapper.get_spatial_node_index(spatial_id) + } + + fn get_clip_chain( + &mut self, + clip_id: ClipId, + ) -> ClipChainId { + self.clip_store.get_or_build_clip_chain_id(clip_id) + } + + fn process_common_properties( + &mut self, + common: &CommonItemProperties, + bounds: Option<&LayoutRect>, + ) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipChainId) { + let spatial_node_index = self.get_space(common.spatial_id); + let clip_chain_id = self.get_clip_chain(common.clip_id); + + let current_offset = self.current_offset(spatial_node_index); + + let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device; + snap_to_device.set_target_spatial_node( + spatial_node_index, + &self.spatial_tree + ); + + let unsnapped_clip_rect = common.clip_rect.translate(current_offset); + let clip_rect = snap_to_device.snap_rect(&unsnapped_clip_rect); + + let unsnapped_rect = bounds.map(|bounds| { + bounds.translate(current_offset) + }); + + // If no bounds rect is given, default to clip rect. + let rect = unsnapped_rect.map_or(clip_rect, |bounds| { + snap_to_device.snap_rect(&bounds) + }); + + let layout = LayoutPrimitiveInfo { + rect, + clip_rect, + flags: common.flags, + hit_info: common.hit_info, + }; + + (layout, unsnapped_rect.unwrap_or(unsnapped_clip_rect), spatial_node_index, clip_chain_id) + } + + fn process_common_properties_with_bounds( + &mut self, + common: &CommonItemProperties, + bounds: &LayoutRect, + ) -> (LayoutPrimitiveInfo, LayoutRect, SpatialNodeIndex, ClipChainId) { + self.process_common_properties( + common, + Some(bounds), + ) + } + + pub fn snap_rect( + &mut self, + rect: &LayoutRect, + target_spatial_node: SpatialNodeIndex, + ) -> LayoutRect { + let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device; + snap_to_device.set_target_spatial_node( + target_spatial_node, + &self.spatial_tree + ); + snap_to_device.snap_rect(rect) + } + + fn build_item<'b>( + &'b mut self, + item: DisplayItemRef, + pipeline_id: PipelineId, + ) { + match *item.item() { + DisplayItem::Image(ref info) => { + let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + &info.common, + &info.bounds, + ); + + self.add_image( + spatial_node_index, + clip_chain_id, + &layout, + layout.rect.size, + LayoutSize::zero(), + info.image_key, + info.image_rendering, + info.alpha_type, + info.color, + ); + } + DisplayItem::RepeatingImage(ref info) => { + let (layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + &info.common, + &info.bounds, + ); + + let stretch_size = process_repeat_size( + &layout.rect, + &unsnapped_rect, + info.stretch_size, + ); + + self.add_image( + spatial_node_index, + clip_chain_id, + &layout, + stretch_size, + info.tile_spacing, + info.image_key, + info.image_rendering, + info.alpha_type, + info.color, + ); + } + DisplayItem::YuvImage(ref info) => { + let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + &info.common, + &info.bounds, + ); + + self.add_yuv_image( + spatial_node_index, + clip_chain_id, + &layout, + info.yuv_data, + info.color_depth, + info.color_space, + info.color_range, + info.image_rendering, + ); + } + DisplayItem::Text(ref info) => { + // TODO(aosmond): Snapping text primitives does not make much sense, given the + // primitive bounds and clip are supposed to be conservative, not definitive. + // E.g. they should be able to grow and not impact the output. However there + // are subtle interactions between the primitive origin and the glyph offset + // which appear to be significant (presumably due to some sort of accumulated + // error throughout the layers). We should fix this at some point. + let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + &info.common, + &info.bounds, + ); + + self.add_text( + spatial_node_index, + clip_chain_id, + &layout, + &info.font_key, + &info.color, + item.glyphs(), + info.glyph_options, + ); + } + DisplayItem::Rectangle(ref info) => { + let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + &info.common, + &info.bounds, + ); + + self.add_solid_rectangle( + spatial_node_index, + clip_chain_id, + &layout, + info.color, + ); + } + DisplayItem::HitTest(ref info) => { + let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties( + &info.common, + None, + ); + + self.add_solid_rectangle( + spatial_node_index, + clip_chain_id, + &layout, + PropertyBinding::Value(ColorF::TRANSPARENT), + ); + } + DisplayItem::ClearRectangle(ref info) => { + let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + &info.common, + &info.bounds, + ); + + self.add_clear_rectangle( + spatial_node_index, + clip_chain_id, + &layout, + ); + } + DisplayItem::Line(ref info) => { + let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + &info.common, + &info.area, + ); + + self.add_line( + spatial_node_index, + clip_chain_id, + &layout, + info.wavy_line_thickness, + info.orientation, + info.color, + info.style, + ); + } + DisplayItem::Gradient(ref info) => { + let (layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + &info.common, + &info.bounds, + ); + + let tile_size = process_repeat_size( + &layout.rect, + &unsnapped_rect, + info.tile_size, + ); + + if let Some(prim_key_kind) = self.create_linear_gradient_prim( + &layout, + info.gradient.start_point, + info.gradient.end_point, + item.gradient_stops(), + info.gradient.extend_mode, + tile_size, + info.tile_spacing, + None, + ) { + self.add_nonshadowable_primitive( + spatial_node_index, + clip_chain_id, + &layout, + Vec::new(), + prim_key_kind, + ); + } + } + DisplayItem::RadialGradient(ref info) => { + let (layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + &info.common, + &info.bounds, + ); + + let tile_size = process_repeat_size( + &layout.rect, + &unsnapped_rect, + info.tile_size, + ); + + let prim_key_kind = self.create_radial_gradient_prim( + &layout, + info.gradient.center, + info.gradient.start_offset * info.gradient.radius.width, + info.gradient.end_offset * info.gradient.radius.width, + info.gradient.radius.width / info.gradient.radius.height, + item.gradient_stops(), + info.gradient.extend_mode, + tile_size, + info.tile_spacing, + None, + ); + + self.add_nonshadowable_primitive( + spatial_node_index, + clip_chain_id, + &layout, + Vec::new(), + prim_key_kind, + ); + } + DisplayItem::ConicGradient(ref info) => { + let (layout, unsnapped_rect, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + &info.common, + &info.bounds, + ); + + let tile_size = process_repeat_size( + &layout.rect, + &unsnapped_rect, + info.tile_size, + ); + + let prim_key_kind = self.create_conic_gradient_prim( + &layout, + info.gradient.center, + info.gradient.angle, + info.gradient.start_offset, + info.gradient.end_offset, + item.gradient_stops(), + info.gradient.extend_mode, + tile_size, + info.tile_spacing, + None, + ); + + self.add_nonshadowable_primitive( + spatial_node_index, + clip_chain_id, + &layout, + Vec::new(), + prim_key_kind, + ); + } + DisplayItem::BoxShadow(ref info) => { + let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + &info.common, + &info.box_bounds, + ); + + self.add_box_shadow( + spatial_node_index, + clip_chain_id, + &layout, + &info.offset, + info.color, + info.blur_radius, + info.spread_radius, + info.border_radius, + info.clip_mode, + ); + } + DisplayItem::Border(ref info) => { + let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties_with_bounds( + &info.common, + &info.bounds, + ); + + self.add_border( + spatial_node_index, + clip_chain_id, + &layout, + info, + item.gradient_stops(), + ); + } + DisplayItem::Iframe(ref info) => { + let space = self.get_space(info.space_and_clip.spatial_id); + self.build_iframe( + info, + space, + ); + } + DisplayItem::ImageMaskClip(ref info) => { + let parent_space = self.get_space(info.parent_space_and_clip.spatial_id); + let current_offset = self.current_offset(parent_space); + + let image_mask = ImageMask { + rect: info.image_mask.rect.translate(current_offset), + ..info.image_mask + }; + + self.add_image_mask_clip_node( + info.id, + &info.parent_space_and_clip, + &image_mask, + ); + } + DisplayItem::RoundedRectClip(ref info) => { + let parent_space = self.get_space(info.parent_space_and_clip.spatial_id); + let current_offset = self.current_offset(parent_space); + + self.add_rounded_rect_clip_node( + info.id, + &info.parent_space_and_clip, + &info.clip, + current_offset, + ); + } + DisplayItem::RectClip(ref info) => { + let parent_space = self.get_space(info.parent_space_and_clip.spatial_id); + let current_offset = self.current_offset(parent_space); + let clip_rect = info.clip_rect.translate(current_offset); + + self.add_rect_clip_node( + info.id, + &info.parent_space_and_clip, + &clip_rect, + ); + } + DisplayItem::Clip(ref info) => { + let parent_space = self.get_space(info.parent_space_and_clip.spatial_id); + let current_offset = self.current_offset(parent_space); + let clip_region = ClipRegion::create_for_clip_node( + info.clip_rect, + item.complex_clip().iter(), + ¤t_offset, + ); + self.add_clip_node(info.id, &info.parent_space_and_clip, clip_region); + } + DisplayItem::ClipChain(ref info) => { + let parent = info.parent.map_or(ClipId::root(pipeline_id), |id| ClipId::ClipChain(id)); + let mut instances: SmallVec<[ClipInstance; 4]> = SmallVec::new(); + + for clip_item in item.clip_chain_items() { + let template = self.clip_store.get_template(clip_item); + instances.extend_from_slice(&template.instances); + } + + self.clip_store.register_clip_template( + ClipId::ClipChain(info.id), + parent, + &instances, + ); + }, + DisplayItem::ScrollFrame(ref info) => { + let parent_space = self.get_space(info.parent_space_and_clip.spatial_id); + self.build_scroll_frame( + info, + parent_space, + pipeline_id, + ); + } + DisplayItem::StickyFrame(ref info) => { + let parent_space = self.get_space(info.parent_spatial_id); + self.build_sticky_frame( + info, + parent_space, + ); + } + DisplayItem::BackdropFilter(ref info) => { + let (layout, _, spatial_node_index, clip_chain_id) = self.process_common_properties( + &info.common, + None, + ); + + let filters = filter_ops_for_compositing(item.filters()); + let filter_datas = filter_datas_for_compositing(item.filter_datas()); + let filter_primitives = filter_primitives_for_compositing(item.filter_primitives()); + + self.add_backdrop_filter( + spatial_node_index, + clip_chain_id, + &layout, + filters, + filter_datas, + filter_primitives, + ); + } + + // Do nothing; these are dummy items for the display list parser + DisplayItem::SetGradientStops | + DisplayItem::SetFilterOps | + DisplayItem::SetFilterData | + DisplayItem::SetFilterPrimitives => {} + + // Special items that are handled in the parent method + DisplayItem::PushStackingContext(..) | + DisplayItem::PushReferenceFrame(..) | + DisplayItem::PopReferenceFrame | + DisplayItem::PopStackingContext => { + unreachable!("Should have returned in parent method.") + } + + DisplayItem::ReuseItems(key) | + DisplayItem::RetainedItems(key) => { + unreachable!("Iterator logic error: {:?}", key); + } + + DisplayItem::PushShadow(info) => { + let spatial_node_index = self.get_space(info.space_and_clip.spatial_id); + let clip_chain_id = self.get_clip_chain( + info.space_and_clip.clip_id, + ); + + self.push_shadow( + info.shadow, + spatial_node_index, + clip_chain_id, + info.should_inflate, + ); + } + DisplayItem::PopAllShadows => { + self.pop_all_shadows(); + } + } + } + + // Given a list of clip sources, a positioning node and + // a parent clip chain, return a new clip chain entry. + // If the supplied list of clip sources is empty, then + // just return the parent clip chain id directly. + fn build_clip_chain( + &mut self, + clip_items: Vec, + spatial_node_index: SpatialNodeIndex, + parent_clip_chain_id: ClipChainId, + ) -> ClipChainId { + if clip_items.is_empty() { + parent_clip_chain_id + } else { + let mut clip_chain_id = parent_clip_chain_id; + + for item in clip_items { + // Intern this clip item, and store the handle + // in the clip chain node. + let handle = self.interners + .clip + .intern(&item, || { + ClipInternData { + clip_node_kind: item.kind.node_kind(), + } + }); + + clip_chain_id = self.clip_store.add_clip_chain_node( + handle, + spatial_node_index, + clip_chain_id, + ); + } + + clip_chain_id + } + } + + /// Create a primitive and add it to the prim store. This method doesn't + /// add the primitive to the draw list, so can be used for creating + /// sub-primitives. + /// + /// TODO(djg): Can this inline into `add_interned_prim_to_draw_list` + fn create_primitive

    ( + &mut self, + info: &LayoutPrimitiveInfo, + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, + prim: P, + ) -> PrimitiveInstance + where + P: InternablePrimitive, + Interners: AsMut>, + { + // Build a primitive key. + let prim_key = prim.into_key(info); + + let current_offset = self.current_offset(spatial_node_index); + let interner = self.interners.as_mut(); + let prim_data_handle = interner + .intern(&prim_key, || ()); + + let instance_kind = P::make_instance_kind( + prim_key, + prim_data_handle, + &mut self.prim_store, + current_offset, + ); + + PrimitiveInstance::new( + info.clip_rect, + instance_kind, + clip_chain_id, + ) + } + + pub fn add_primitive_to_hit_testing_list( + &mut self, + info: &LayoutPrimitiveInfo, + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, + ) { + let tag = match info.hit_info { + Some(tag) => tag, + None => return, + }; + + // We want to get a range of clip chain roots that apply to this + // hit testing primitive. + + // Get the start index for the clip chain root range for this primitive. + let start = self.hit_testing_scene.next_clip_chain_index(); + + // Add the clip chain root for the primitive itself. + self.hit_testing_scene.add_clip_chain(clip_chain_id); + + // Append any clip chain roots from enclosing stacking contexts. + for sc in &self.sc_stack { + self.hit_testing_scene.add_clip_chain(sc.clip_chain_id); + } + + // Construct a clip chain roots range to be stored with the item. + let clip_chain_range = ops::Range { + start, + end: self.hit_testing_scene.next_clip_chain_index(), + }; + + // Create and store the hit testing primitive itself. + let new_item = HitTestingItem::new( + tag, + info, + spatial_node_index, + clip_chain_range, + ); + self.hit_testing_scene.add_item(new_item); + } + + /// Add an already created primitive to the draw lists. + pub fn add_primitive_to_draw_list( + &mut self, + prim_instance: PrimitiveInstance, + prim_rect: LayoutRect, + spatial_node_index: SpatialNodeIndex, + flags: PrimitiveFlags, + ) { + // Add primitive to the top-most stacking context on the stack. + if prim_instance.is_chased() { + println!("\tadded to stacking context at {}", self.sc_stack.len()); + } + + let stacking_context = self.sc_stack.last_mut().unwrap(); + stacking_context.prim_list.add_prim( + prim_instance, + prim_rect, + spatial_node_index, + flags, + ); + } + + /// Convenience interface that creates a primitive entry and adds it + /// to the draw list. + fn add_nonshadowable_primitive

    ( + &mut self, + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, + info: &LayoutPrimitiveInfo, + clip_items: Vec, + prim: P, + ) + where + P: InternablePrimitive + IsVisible, + Interners: AsMut>, + { + if prim.is_visible() { + let clip_chain_id = self.build_clip_chain( + clip_items, + spatial_node_index, + clip_chain_id, + ); + self.add_prim_to_draw_list( + info, + spatial_node_index, + clip_chain_id, + prim, + ); + } + } + + pub fn add_primitive

    ( + &mut self, + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, + info: &LayoutPrimitiveInfo, + clip_items: Vec, + prim: P, + ) + where + P: InternablePrimitive + IsVisible, + Interners: AsMut>, + ShadowItem: From> + { + // If a shadow context is not active, then add the primitive + // directly to the parent picture. + if self.pending_shadow_items.is_empty() { + self.add_nonshadowable_primitive( + spatial_node_index, + clip_chain_id, + info, + clip_items, + prim, + ); + } else { + debug_assert!(clip_items.is_empty(), "No per-prim clips expected for shadowed primitives"); + + // There is an active shadow context. Store as a pending primitive + // for processing during pop_all_shadows. + self.pending_shadow_items.push_back(PendingPrimitive { + spatial_node_index, + clip_chain_id, + info: *info, + prim, + }.into()); + } + } + + fn add_prim_to_draw_list

    ( + &mut self, + info: &LayoutPrimitiveInfo, + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, + prim: P, + ) + where + P: InternablePrimitive, + Interners: AsMut>, + { + let prim_instance = self.create_primitive( + info, + spatial_node_index, + clip_chain_id, + prim, + ); + self.register_chase_primitive_by_rect( + &info.rect, + &prim_instance, + ); + self.add_primitive_to_hit_testing_list( + info, + spatial_node_index, + clip_chain_id, + ); + self.add_primitive_to_draw_list( + prim_instance, + info.rect, + spatial_node_index, + info.flags, + ); + } + + pub fn push_stacking_context( + &mut self, + pipeline_id: PipelineId, + composite_ops: CompositeOps, + transform_style: TransformStyle, + prim_flags: PrimitiveFlags, + spatial_node_index: SpatialNodeIndex, + clip_id: Option, + requested_raster_space: RasterSpace, + flags: StackingContextFlags, + device_pixel_scale: DevicePixelScale, + ) { + // Check if this stacking context is the root of a pipeline, and the caller + // has requested it as an output frame. + let is_pipeline_root = + self.sc_stack.last().map_or(true, |sc| sc.pipeline_id != pipeline_id); + let frame_output_pipeline_id = if is_pipeline_root && self.output_pipelines.contains(&pipeline_id) { + Some(pipeline_id) + } else { + None + }; + + let clip_chain_id = match clip_id { + Some(clip_id) => self.clip_store.get_or_build_clip_chain_id(clip_id), + None => ClipChainId::NONE, + }; + + // Get the transform-style of the parent stacking context, + // which determines if we *might* need to draw this on + // an intermediate surface for plane splitting purposes. + let (parent_is_3d, extra_3d_instance) = match self.sc_stack.last_mut() { + Some(ref mut sc) if sc.is_3d() => { + let flat_items_context_3d = match sc.context_3d { + Picture3DContext::In { ancestor_index, .. } => Picture3DContext::In { + root_data: None, + ancestor_index, + }, + Picture3DContext::Out => panic!("Unexpected out of 3D context"), + }; + // Cut the sequence of flat children before starting a child stacking context, + // so that the relative order between them and our current SC is preserved. + let extra_instance = sc.cut_item_sequence( + &mut self.prim_store, + &mut self.interners, + Some(PictureCompositeMode::Blit(BlitReason::PRESERVE3D)), + flat_items_context_3d, + ); + let extra_instance = extra_instance.map(|(_, instance)| { + ExtendedPrimitiveInstance { + instance, + spatial_node_index: sc.spatial_node_index, + flags: sc.prim_flags, + } + }); + (true, extra_instance) + }, + _ => (false, None), + }; + + if let Some(instance) = extra_3d_instance { + self.add_primitive_instance_to_3d_root(instance); + } + + // If this is preserve-3d *or* the parent is, then this stacking + // context is participating in the 3d rendering context. In that + // case, hoist the picture up to the 3d rendering context + // container, so that it's rendered as a sibling with other + // elements in this context. + let participating_in_3d_context = + composite_ops.is_empty() && + (parent_is_3d || transform_style == TransformStyle::Preserve3D); + + let context_3d = if participating_in_3d_context { + // Find the spatial node index of the containing block, which + // defines the context of backface-visibility. + let ancestor_context = self.sc_stack + .iter() + .rfind(|sc| !sc.is_3d()); + Picture3DContext::In { + root_data: if parent_is_3d { + None + } else { + Some(Vec::new()) + }, + ancestor_index: match ancestor_context { + Some(sc) => sc.spatial_node_index, + None => ROOT_SPATIAL_NODE_INDEX, + }, + } + } else { + Picture3DContext::Out + }; + + // Force an intermediate surface if the stacking context has a + // complex clip node. In the future, we may decide during + // prepare step to skip the intermediate surface if the + // clip node doesn't affect the stacking context rect. + let mut blit_reason = BlitReason::empty(); + let mut current_clip_chain_id = clip_chain_id; + + if flags.contains(StackingContextFlags::IS_BLEND_CONTAINER) { + blit_reason |= BlitReason::ISOLATE; + } + + // Walk each clip in this chain, to see whether any of the clips + // require that we draw this to an intermediate surface. + while current_clip_chain_id != ClipChainId::NONE { + let clip_chain_node = &self + .clip_store + .clip_chain_nodes[current_clip_chain_id.0 as usize]; + + let clip_node_data = &self.interners.clip[clip_chain_node.handle]; + + if let ClipNodeKind::Complex = clip_node_data.clip_node_kind { + blit_reason = BlitReason::CLIP; + break; + } + + current_clip_chain_id = clip_chain_node.parent_clip_chain_id; + } + + let snap_to_device = self.sc_stack.last().map_or( + SpaceSnapper::new( + ROOT_SPATIAL_NODE_INDEX, + device_pixel_scale, + ), + |sc| sc.snap_to_device.clone(), + ); + + let is_redundant = match self.sc_stack.last() { + Some(parent) => { + FlattenedStackingContext::is_redundant( + &context_3d, + &composite_ops, + prim_flags, + blit_reason, + requested_raster_space, + parent, + ) + } + None => { + false + } + }; + + if let Some(clip_id) = clip_id { + // If this stacking context is redundant (prims will be pushed into + // the parent during pop) but it has a valid clip, then we need to + // add that clip to the current clip chain builder, so it's correctly + // applied to any primitives within this redundant stacking context. + // For the normal case, we start a new clip root, knowing that the + // clip on this stacking context will be pushed onto the stack during + // frame building. + if is_redundant { + self.clip_store.push_clip_root(Some(clip_id), true); + } else { + self.clip_store.push_clip_root(None, false); + } + } + + // Push the SC onto the stack, so we know how to handle things in + // pop_stacking_context. + self.sc_stack.push(FlattenedStackingContext { + prim_list: PrimitiveList::empty(), + pipeline_id, + prim_flags, + requested_raster_space, + spatial_node_index, + clip_id, + clip_chain_id, + frame_output_pipeline_id, + composite_ops, + blit_reason, + transform_style, + context_3d, + is_redundant, + is_backdrop_root: flags.contains(StackingContextFlags::IS_BACKDROP_ROOT), + snap_to_device, + }); + } + + pub fn pop_stacking_context(&mut self) { + let mut stacking_context = self.sc_stack.pop().unwrap(); + + if stacking_context.clip_id.is_some() { + self.clip_store.pop_clip_root(); + } + + // If we encounter a stacking context that is effectively a no-op, then instead + // of creating a picture, just append the primitive list to the parent stacking + // context as a short cut. This serves two purposes: + // (a) It's an optimization to reduce picture count and allocations, as display lists + // often contain a lot of these stacking contexts that don't require pictures or + // off-screen surfaces. + // (b) It's useful for the initial version of picture caching in gecko, by enabling + // is to just look for interesting scroll roots on the root stacking context, + // without having to consider cuts at stacking context boundaries. + let parent_is_empty = match self.sc_stack.last_mut() { + Some(parent_sc) => { + if stacking_context.is_redundant { + if !stacking_context.prim_list.is_empty() { + // If popping a redundant stacking context that is from a different pipeline, + // we want to insert flags where the picture cache slices should be created + // for this iframe. For now, we want to match existing behavior, that is: + // - Only cache content that is within the main scroll root, and: + // - Skip caching fixed position content before / after the scroll root. + // This means that we don't add scrollbars, which cause lots of extra + // invalidations. There is ongoing work to add tags to primitives that + // are scrollbars. Once this lands, we can simplify this logic considerably + // (and add a separate picture cache slice / OS layer for scroll bars). + if parent_sc.pipeline_id != stacking_context.pipeline_id && self.iframe_depth == 1 { + self.content_slice_count = stacking_context.init_picture_caching( + &self.spatial_tree, + &self.clip_store, + &self.quality_settings, + ); + + // Mark that a user supplied tile cache was specified. + self.picture_caching_initialized = true; + } + + // If the parent context primitives list is empty, it's faster + // to assign the storage of the popped context instead of paying + // the copying cost for extend. + if parent_sc.prim_list.is_empty() { + parent_sc.prim_list = stacking_context.prim_list; + } else { + parent_sc.prim_list.extend(stacking_context.prim_list); + } + } + + return; + } + parent_sc.prim_list.is_empty() + }, + None => true, + }; + + if self.sc_stack.is_empty() { + // If we didn't encounter a content iframe, then set up picture caching slice markers + // on the root stacking context. This can happen in Gecko when the parent process + // provides the content display list (e.g. about:support, about:config etc). + if !self.picture_caching_initialized { + self.content_slice_count = stacking_context.init_picture_caching( + &self.spatial_tree, + &self.clip_store, + &self.quality_settings, + ); + self.picture_caching_initialized = true; + } + + self.setup_picture_caching( + &mut stacking_context.prim_list, + ); + } + + let (leaf_context_3d, leaf_composite_mode, leaf_output_pipeline_id) = match stacking_context.context_3d { + // TODO(gw): For now, as soon as this picture is in + // a 3D context, we draw it to an intermediate + // surface and apply plane splitting. However, + // there is a large optimization opportunity here. + // During culling, we can check if there is actually + // perspective present, and skip the plane splitting + // completely when that is not the case. + Picture3DContext::In { ancestor_index, .. } => ( + Picture3DContext::In { root_data: None, ancestor_index }, + Some(PictureCompositeMode::Blit(BlitReason::PRESERVE3D | stacking_context.blit_reason)), + None, + ), + Picture3DContext::Out => ( + Picture3DContext::Out, + if stacking_context.blit_reason.is_empty() { + // By default, this picture will be collapsed into + // the owning target. + None + } else { + // Add a dummy composite filter if the SC has to be isolated. + Some(PictureCompositeMode::Blit(stacking_context.blit_reason)) + }, + stacking_context.frame_output_pipeline_id + ), + }; + + // Add picture for this actual stacking context contents to render into. + let leaf_pic_index = PictureIndex(self.prim_store.pictures + .alloc() + .init(PicturePrimitive::new_image( + leaf_composite_mode.clone(), + leaf_context_3d, + leaf_output_pipeline_id, + true, + stacking_context.prim_flags, + stacking_context.requested_raster_space, + stacking_context.prim_list, + stacking_context.spatial_node_index, + None, + PictureOptions::default(), + )) + ); + + // Create a chain of pictures based on presence of filters, + // mix-blend-mode and/or 3d rendering context containers. + + let mut current_pic_index = leaf_pic_index; + let mut cur_instance = create_prim_instance( + leaf_pic_index, + leaf_composite_mode.into(), + ClipChainId::NONE, + &mut self.interners, + ); + + if cur_instance.is_chased() { + println!("\tis a leaf primitive for a stacking context"); + } + + // If establishing a 3d context, the `cur_instance` represents + // a picture with all the *trailing* immediate children elements. + // We append this to the preserve-3D picture set and make a container picture of them. + if let Picture3DContext::In { root_data: Some(mut prims), ancestor_index } = stacking_context.context_3d { + prims.push(ExtendedPrimitiveInstance { + instance: cur_instance, + spatial_node_index: stacking_context.spatial_node_index, + flags: stacking_context.prim_flags, + }); + + let mut prim_list = PrimitiveList::empty(); + for ext_prim in prims.drain(..) { + prim_list.add_prim( + ext_prim.instance, + LayoutRect::zero(), + ext_prim.spatial_node_index, + ext_prim.flags, + ); + } + + // This is the acttual picture representing our 3D hierarchy root. + current_pic_index = PictureIndex(self.prim_store.pictures + .alloc() + .init(PicturePrimitive::new_image( + None, + Picture3DContext::In { + root_data: Some(Vec::new()), + ancestor_index, + }, + stacking_context.frame_output_pipeline_id, + true, + stacking_context.prim_flags, + stacking_context.requested_raster_space, + prim_list, + stacking_context.spatial_node_index, + None, + PictureOptions::default(), + )) + ); + + cur_instance = create_prim_instance( + current_pic_index, + PictureCompositeKey::Identity, + ClipChainId::NONE, + &mut self.interners, + ); + } + + let (filtered_pic_index, filtered_instance) = self.wrap_prim_with_filters( + cur_instance, + current_pic_index, + stacking_context.composite_ops.filters, + stacking_context.composite_ops.filter_primitives, + stacking_context.composite_ops.filter_datas, + stacking_context.prim_flags, + stacking_context.requested_raster_space, + stacking_context.spatial_node_index, + true, + ); + + let has_filters = current_pic_index != filtered_pic_index; + + current_pic_index = filtered_pic_index; + cur_instance = filtered_instance; + + // Same for mix-blend-mode, except we can skip if this primitive is the first in the parent + // stacking context. + // From https://drafts.fxtf.org/compositing-1/#generalformula, the formula for blending is: + // Cs = (1 - ab) x Cs + ab x Blend(Cb, Cs) + // where + // Cs = Source color + // ab = Backdrop alpha + // Cb = Backdrop color + // + // If we're the first primitive within a stacking context, then we can guarantee that the + // backdrop alpha will be 0, and then the blend equation collapses to just + // Cs = Cs, and the blend mode isn't taken into account at all. + if let (Some(mix_blend_mode), false) = (stacking_context.composite_ops.mix_blend_mode, parent_is_empty) { + if self.sc_stack.last().unwrap().blit_reason.contains(BlitReason::ISOLATE) { + let composite_mode = Some(PictureCompositeMode::MixBlend(mix_blend_mode)); + + let mut prim_list = PrimitiveList::empty(); + prim_list.add_prim( + cur_instance.clone(), + LayoutRect::zero(), + stacking_context.spatial_node_index, + stacking_context.prim_flags, + ); + + let blend_pic_index = PictureIndex(self.prim_store.pictures + .alloc() + .init(PicturePrimitive::new_image( + composite_mode.clone(), + Picture3DContext::Out, + None, + true, + stacking_context.prim_flags, + stacking_context.requested_raster_space, + prim_list, + stacking_context.spatial_node_index, + None, + PictureOptions::default(), + )) + ); + + current_pic_index = blend_pic_index; + cur_instance = create_prim_instance( + blend_pic_index, + composite_mode.into(), + ClipChainId::NONE, + &mut self.interners, + ); + + if cur_instance.is_chased() { + println!("\tis a mix-blend picture for a stacking context with {:?}", mix_blend_mode); + } + } else { + // If we have a mix-blend-mode, the stacking context needs to be isolated + // to blend correctly as per the CSS spec. + // If not already isolated, we can't correctly blend. + warn!("found a mix-blend-mode outside a blend container, ignoring"); + } + } + + // Set the stacking context clip on the outermost picture in the chain, + // unless we already set it on the leaf picture. + cur_instance.clip_chain_id = stacking_context.clip_chain_id; + + // The primitive instance for the remainder of flat children of this SC + // if it's a part of 3D hierarchy but not the root of it. + let trailing_children_instance = match self.sc_stack.last_mut() { + // Preserve3D path (only relevant if there are no filters/mix-blend modes) + Some(ref parent_sc) if !has_filters && parent_sc.is_3d() => { + Some(cur_instance) + } + // Regular parenting path + Some(ref mut parent_sc) => { + parent_sc.prim_list.add_prim( + cur_instance, + LayoutRect::zero(), + stacking_context.spatial_node_index, + stacking_context.prim_flags, + ); + None + } + // This must be the root stacking context + None => { + self.root_pic_index = current_pic_index; + None + } + }; + + // finally, if there any outstanding 3D primitive instances, + // find the 3D hierarchy root and add them there. + if let Some(instance) = trailing_children_instance { + self.add_primitive_instance_to_3d_root(ExtendedPrimitiveInstance { + instance, + spatial_node_index: stacking_context.spatial_node_index, + flags: stacking_context.prim_flags, + }); + } + + assert!( + self.pending_shadow_items.is_empty(), + "Found unpopped shadows when popping stacking context!" + ); + } + + pub fn push_reference_frame( + &mut self, + reference_frame_id: SpatialId, + parent_index: Option, + pipeline_id: PipelineId, + transform_style: TransformStyle, + source_transform: PropertyBinding, + kind: ReferenceFrameKind, + origin_in_parent_reference_frame: LayoutVector2D, + ) -> SpatialNodeIndex { + let index = self.spatial_tree.add_reference_frame( + parent_index, + transform_style, + source_transform, + kind, + origin_in_parent_reference_frame, + pipeline_id, + ); + self.id_to_index_mapper.add_spatial_node(reference_frame_id, index); + + index + } + + pub fn push_root( + &mut self, + pipeline_id: PipelineId, + viewport_size: &LayoutSize, + content_size: &LayoutSize, + device_pixel_scale: DevicePixelScale, + ) { + if let ChasePrimitive::Id(id) = self.config.chase_primitive { + println!("Chasing {:?} by index", id); + register_prim_chase_id(id); + } + + let spatial_node_index = self.push_reference_frame( + SpatialId::root_reference_frame(pipeline_id), + None, + pipeline_id, + TransformStyle::Flat, + PropertyBinding::Value(LayoutTransform::identity()), + ReferenceFrameKind::Transform, + LayoutVector2D::zero(), + ); + + // We can't use this with the stacking context because it does not exist + // yet. Just create a dedicated snapper for the root. + let snap_to_device = SpaceSnapper::new_with_target( + spatial_node_index, + ROOT_SPATIAL_NODE_INDEX, + device_pixel_scale, + &self.spatial_tree, + ); + + let content_size = snap_to_device.snap_size(content_size); + let viewport_rect = snap_to_device.snap_rect( + &LayoutRect::new(LayoutPoint::zero(), *viewport_size), + ); + + self.add_scroll_frame( + SpatialId::root_scroll_node(pipeline_id), + spatial_node_index, + Some(ExternalScrollId(0, pipeline_id)), + pipeline_id, + &viewport_rect, + &content_size, + ScrollSensitivity::ScriptAndInputEvents, + ScrollFrameKind::PipelineRoot, + LayoutVector2D::zero(), + ); + } + + fn add_image_mask_clip_node( + &mut self, + new_node_id: ClipId, + space_and_clip: &SpaceAndClipInfo, + image_mask: &ImageMask, + ) { + let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id); + + let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device; + snap_to_device.set_target_spatial_node( + spatial_node_index, + &self.spatial_tree, + ); + + let snapped_mask_rect = snap_to_device.snap_rect(&image_mask.rect); + let item = ClipItemKey { + kind: ClipItemKeyKind::image_mask(image_mask, snapped_mask_rect), + }; + + let handle = self + .interners + .clip + .intern(&item, || { + ClipInternData { + clip_node_kind: ClipNodeKind::Complex, + } + }); + + let instance = ClipInstance::new(handle, spatial_node_index); + + self.clip_store.register_clip_template( + new_node_id, + space_and_clip.clip_id, + &[instance], + ); + } + + /// Add a new rectangle clip, positioned by the spatial node in the `space_and_clip`. + pub fn add_rect_clip_node( + &mut self, + new_node_id: ClipId, + space_and_clip: &SpaceAndClipInfo, + clip_rect: &LayoutRect, + ) { + let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id); + + let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device; + snap_to_device.set_target_spatial_node( + spatial_node_index, + &self.spatial_tree, + ); + + let snapped_clip_rect = snap_to_device.snap_rect(clip_rect); + + let item = ClipItemKey { + kind: ClipItemKeyKind::rectangle(snapped_clip_rect, ClipMode::Clip), + }; + let handle = self + .interners + .clip + .intern(&item, || { + ClipInternData { + clip_node_kind: ClipNodeKind::Rectangle, + } + }); + + let instance = ClipInstance::new(handle, spatial_node_index); + + self.clip_store.register_clip_template( + new_node_id, + space_and_clip.clip_id, + &[instance], + ); + } + + pub fn add_rounded_rect_clip_node( + &mut self, + new_node_id: ClipId, + space_and_clip: &SpaceAndClipInfo, + clip: &ComplexClipRegion, + current_offset: LayoutVector2D, + ) { + let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id); + + let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device; + snap_to_device.set_target_spatial_node( + spatial_node_index, + &self.spatial_tree, + ); + + let snapped_region_rect = snap_to_device.snap_rect(&clip.rect.translate(current_offset)); + let item = ClipItemKey { + kind: ClipItemKeyKind::rounded_rect( + snapped_region_rect, + clip.radii, + clip.mode, + ), + }; + + let handle = self + .interners + .clip + .intern(&item, || { + ClipInternData { + clip_node_kind: ClipNodeKind::Complex, + } + }); + + let instance = ClipInstance::new(handle, spatial_node_index); + + self.clip_store.register_clip_template( + new_node_id, + space_and_clip.clip_id, + &[instance], + ); + } + + pub fn add_clip_node( + &mut self, + new_node_id: ClipId, + space_and_clip: &SpaceAndClipInfo, + clip_region: ClipRegion, + ) + where + I: IntoIterator + { + // Map the ClipId for the positioning node to a spatial node index. + let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(space_and_clip.spatial_id); + + let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device; + snap_to_device.set_target_spatial_node( + spatial_node_index, + &self.spatial_tree, + ); + + let snapped_clip_rect = snap_to_device.snap_rect(&clip_region.main); + let mut instances: SmallVec<[ClipInstance; 4]> = SmallVec::new(); + + // Intern each clip item in this clip node, and add the interned + // handle to a clip chain node, parented to form a chain. + // TODO(gw): We could re-structure this to share some of the + // interning and chaining code. + + // Build the clip sources from the supplied region. + let item = ClipItemKey { + kind: ClipItemKeyKind::rectangle(snapped_clip_rect, ClipMode::Clip), + }; + let handle = self + .interners + .clip + .intern(&item, || { + ClipInternData { + clip_node_kind: ClipNodeKind::Rectangle, + } + }); + instances.push(ClipInstance::new(handle, spatial_node_index)); + + for region in clip_region.complex_clips { + let snapped_region_rect = snap_to_device.snap_rect(®ion.rect); + let item = ClipItemKey { + kind: ClipItemKeyKind::rounded_rect( + snapped_region_rect, + region.radii, + region.mode, + ), + }; + + let handle = self + .interners + .clip + .intern(&item, || { + ClipInternData { + clip_node_kind: ClipNodeKind::Complex, + } + }); + + instances.push(ClipInstance::new(handle, spatial_node_index)); + } + + self.clip_store.register_clip_template( + new_node_id, + space_and_clip.clip_id, + &instances, + ); + } + + pub fn add_scroll_frame( + &mut self, + new_node_id: SpatialId, + parent_node_index: SpatialNodeIndex, + external_id: Option, + pipeline_id: PipelineId, + frame_rect: &LayoutRect, + content_size: &LayoutSize, + scroll_sensitivity: ScrollSensitivity, + frame_kind: ScrollFrameKind, + external_scroll_offset: LayoutVector2D, + ) -> SpatialNodeIndex { + let node_index = self.spatial_tree.add_scroll_frame( + parent_node_index, + external_id, + pipeline_id, + frame_rect, + content_size, + scroll_sensitivity, + frame_kind, + external_scroll_offset, + ); + self.id_to_index_mapper.add_spatial_node(new_node_id, node_index); + node_index + } + + pub fn push_shadow( + &mut self, + shadow: Shadow, + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, + should_inflate: bool, + ) { + // Store this shadow in the pending list, for processing + // during pop_all_shadows. + self.pending_shadow_items.push_back(ShadowItem::Shadow(PendingShadow { + shadow, + spatial_node_index, + clip_chain_id, + should_inflate, + })); + } + + pub fn pop_all_shadows( + &mut self, + ) { + assert!(!self.pending_shadow_items.is_empty(), "popped shadows, but none were present"); + + let mut items = mem::replace(&mut self.pending_shadow_items, VecDeque::new()); + + // + // The pending_shadow_items queue contains a list of shadows and primitives + // that were pushed during the active shadow context. To process these, we: + // + // Iterate the list, popping an item from the front each iteration. + // + // If the item is a shadow: + // - Create a shadow picture primitive. + // - Add *any* primitives that remain in the item list to this shadow. + // If the item is a primitive: + // - Add that primitive as a normal item (if alpha > 0) + // + + while let Some(item) = items.pop_front() { + match item { + ShadowItem::Shadow(pending_shadow) => { + // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur + // "the image that would be generated by applying to the shadow a + // Gaussian blur with a standard deviation equal to half the blur radius." + let std_deviation = pending_shadow.shadow.blur_radius * 0.5; + + // If the shadow has no blur, any elements will get directly rendered + // into the parent picture surface, instead of allocating and drawing + // into an intermediate surface. In this case, we will need to apply + // the local clip rect to primitives. + let is_passthrough = pending_shadow.shadow.blur_radius == 0.0; + + // shadows always rasterize in local space. + // TODO(gw): expose API for clients to specify a raster scale + let raster_space = if is_passthrough { + self.sc_stack.last().unwrap().requested_raster_space + } else { + RasterSpace::Local(1.0) + }; + + // Add any primitives that come after this shadow in the item + // list to this shadow. + let mut prim_list = PrimitiveList::empty(); + + for item in &items { + match item { + ShadowItem::Image(ref pending_image) => { + self.add_shadow_prim( + &pending_shadow, + pending_image, + &mut prim_list, + ) + } + ShadowItem::LineDecoration(ref pending_line_dec) => { + self.add_shadow_prim( + &pending_shadow, + pending_line_dec, + &mut prim_list, + ) + } + ShadowItem::NormalBorder(ref pending_border) => { + self.add_shadow_prim( + &pending_shadow, + pending_border, + &mut prim_list, + ) + } + ShadowItem::Primitive(ref pending_primitive) => { + self.add_shadow_prim( + &pending_shadow, + pending_primitive, + &mut prim_list, + ) + } + ShadowItem::TextRun(ref pending_text_run) => { + self.add_shadow_prim( + &pending_shadow, + pending_text_run, + &mut prim_list, + ) + } + _ => {} + } + } + + // No point in adding a shadow here if there were no primitives + // added to the shadow. + if !prim_list.is_empty() { + // Create a picture that the shadow primitives will be added to. If the + // blur radius is 0, the code in Picture::prepare_for_render will + // detect this and mark the picture to be drawn directly into the + // parent picture, which avoids an intermediate surface and blur. + let blur_filter = Filter::Blur(std_deviation); + let composite_mode = PictureCompositeMode::Filter(blur_filter); + let composite_mode_key = Some(composite_mode.clone()).into(); + + // Pass through configuration information about whether WR should + // do the bounding rect inflation for text shadows. + let options = PictureOptions { + inflate_if_required: pending_shadow.should_inflate, + }; + + // Create the primitive to draw the shadow picture into the scene. + let shadow_pic_index = PictureIndex(self.prim_store.pictures + .alloc() + .init(PicturePrimitive::new_image( + Some(composite_mode), + Picture3DContext::Out, + None, + is_passthrough, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + raster_space, + prim_list, + pending_shadow.spatial_node_index, + None, + options, + )) + ); + + let shadow_pic_key = PictureKey::new( + Picture { composite_mode_key }, + ); + + let shadow_prim_data_handle = self.interners + .picture + .intern(&shadow_pic_key, || ()); + + let shadow_prim_instance = PrimitiveInstance::new( + LayoutRect::max_rect(), + PrimitiveInstanceKind::Picture { + data_handle: shadow_prim_data_handle, + pic_index: shadow_pic_index, + segment_instance_index: SegmentInstanceIndex::INVALID, + }, + pending_shadow.clip_chain_id, + ); + + // Add the shadow primitive. This must be done before pushing this + // picture on to the shadow stack, to avoid infinite recursion! + self.add_primitive_to_draw_list( + shadow_prim_instance, + LayoutRect::zero(), + pending_shadow.spatial_node_index, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + ); + } + } + ShadowItem::Image(pending_image) => { + self.add_shadow_prim_to_draw_list( + pending_image, + ) + }, + ShadowItem::LineDecoration(pending_line_dec) => { + self.add_shadow_prim_to_draw_list( + pending_line_dec, + ) + }, + ShadowItem::NormalBorder(pending_border) => { + self.add_shadow_prim_to_draw_list( + pending_border, + ) + }, + ShadowItem::Primitive(pending_primitive) => { + self.add_shadow_prim_to_draw_list( + pending_primitive, + ) + }, + ShadowItem::TextRun(pending_text_run) => { + self.add_shadow_prim_to_draw_list( + pending_text_run, + ) + }, + } + } + + debug_assert!(items.is_empty()); + self.pending_shadow_items = items; + } + + fn add_shadow_prim

    ( + &mut self, + pending_shadow: &PendingShadow, + pending_primitive: &PendingPrimitive

    , + prim_list: &mut PrimitiveList, + ) + where + P: InternablePrimitive + CreateShadow, + Interners: AsMut>, + { + let snap_to_device = &mut self.sc_stack.last_mut().unwrap().snap_to_device; + snap_to_device.set_target_spatial_node( + pending_primitive.spatial_node_index, + &self.spatial_tree, + ); + + // Offset the local rect and clip rect by the shadow offset. The pending + // primitive has already been snapped, but we will need to snap the + // shadow after translation. We don't need to worry about the size + // changing because the shadow has the same raster space as the + // primitive, and thus we know the size is already rounded. + let mut info = pending_primitive.info.clone(); + info.rect = snap_to_device.snap_rect( + &info.rect.translate(pending_shadow.shadow.offset), + ); + info.clip_rect = snap_to_device.snap_rect( + &info.clip_rect.translate(pending_shadow.shadow.offset), + ); + + // Construct and add a primitive for the given shadow. + let shadow_prim_instance = self.create_primitive( + &info, + pending_primitive.spatial_node_index, + pending_primitive.clip_chain_id, + pending_primitive.prim.create_shadow(&pending_shadow.shadow), + ); + + // Add the new primitive to the shadow picture. + prim_list.add_prim( + shadow_prim_instance, + info.rect, + pending_primitive.spatial_node_index, + info.flags, + ); + } + + fn add_shadow_prim_to_draw_list

    ( + &mut self, + pending_primitive: PendingPrimitive

    , + ) where + P: InternablePrimitive + IsVisible, + Interners: AsMut>, + { + // For a normal primitive, if it has alpha > 0, then we add this + // as a normal primitive to the parent picture. + if pending_primitive.prim.is_visible() { + self.add_prim_to_draw_list( + &pending_primitive.info, + pending_primitive.spatial_node_index, + pending_primitive.clip_chain_id, + pending_primitive.prim, + ); + } + } + + #[cfg(debug_assertions)] + fn register_chase_primitive_by_rect( + &mut self, + rect: &LayoutRect, + prim_instance: &PrimitiveInstance, + ) { + if ChasePrimitive::LocalRect(*rect) == self.config.chase_primitive { + println!("Chasing {:?} by local rect", prim_instance.id); + register_prim_chase_id(prim_instance.id); + } + } + + #[cfg(not(debug_assertions))] + fn register_chase_primitive_by_rect( + &mut self, + _rect: &LayoutRect, + _prim_instance: &PrimitiveInstance, + ) { + } + + pub fn add_solid_rectangle( + &mut self, + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, + info: &LayoutPrimitiveInfo, + color: PropertyBinding, + ) { + match color { + PropertyBinding::Value(value) => { + if value.a == 0.0 { + // Don't add transparent rectangles to the draw list, + // but do consider them for hit testing. This allows + // specifying invisible hit testing areas. + self.add_primitive_to_hit_testing_list( + info, + spatial_node_index, + clip_chain_id, + ); + return; + } + }, + PropertyBinding::Binding(..) => {}, + } + + self.add_primitive( + spatial_node_index, + clip_chain_id, + info, + Vec::new(), + PrimitiveKeyKind::Rectangle { + color: color.into(), + }, + ); + } + + pub fn add_clear_rectangle( + &mut self, + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, + info: &LayoutPrimitiveInfo, + ) { + self.add_primitive( + spatial_node_index, + clip_chain_id, + info, + Vec::new(), + PrimitiveKeyKind::Clear, + ); + } + + pub fn add_line( + &mut self, + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, + info: &LayoutPrimitiveInfo, + wavy_line_thickness: f32, + orientation: LineOrientation, + color: ColorF, + style: LineStyle, + ) { + // For line decorations, we can construct the render task cache key + // here during scene building, since it doesn't depend on device + // pixel ratio or transform. + let mut info = info.clone(); + + let size = get_line_decoration_size( + &info.rect.size, + orientation, + style, + wavy_line_thickness, + ); + + let cache_key = size.map(|size| { + // If dotted, adjust the clip rect to ensure we don't draw a final + // partial dot. + if style == LineStyle::Dotted { + let clip_size = match orientation { + LineOrientation::Horizontal => { + LayoutSize::new( + size.width * (info.rect.size.width / size.width).floor(), + info.rect.size.height, + ) + } + LineOrientation::Vertical => { + LayoutSize::new( + info.rect.size.width, + size.height * (info.rect.size.height / size.height).floor(), + ) + } + }; + let clip_rect = LayoutRect::new( + info.rect.origin, + clip_size, + ); + info.clip_rect = clip_rect + .intersection(&info.clip_rect) + .unwrap_or_else(LayoutRect::zero); + } + + LineDecorationCacheKey { + style, + orientation, + wavy_line_thickness: Au::from_f32_px(wavy_line_thickness), + size: size.to_au(), + } + }); + + self.add_primitive( + spatial_node_index, + clip_chain_id, + &info, + Vec::new(), + LineDecoration { + cache_key, + color: color.into(), + }, + ); + } + + pub fn add_border( + &mut self, + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, + info: &LayoutPrimitiveInfo, + border_item: &BorderDisplayItem, + gradient_stops: ItemRange, + ) { + match border_item.details { + BorderDetails::NinePatch(ref border) => { + let nine_patch = NinePatchDescriptor { + width: border.width, + height: border.height, + slice: border.slice, + fill: border.fill, + repeat_horizontal: border.repeat_horizontal, + repeat_vertical: border.repeat_vertical, + outset: border.outset.into(), + widths: border_item.widths.into(), + }; + + match border.source { + NinePatchBorderSource::Image(image_key) => { + let prim = ImageBorder { + request: ImageRequest { + key: image_key, + rendering: ImageRendering::Auto, + tile: None, + }, + nine_patch, + }; + + self.add_nonshadowable_primitive( + spatial_node_index, + clip_chain_id, + info, + Vec::new(), + prim, + ); + } + NinePatchBorderSource::Gradient(gradient) => { + let prim = match self.create_linear_gradient_prim( + &info, + gradient.start_point, + gradient.end_point, + gradient_stops, + gradient.extend_mode, + LayoutSize::new(border.height as f32, border.width as f32), + LayoutSize::zero(), + Some(Box::new(nine_patch)), + ) { + Some(prim) => prim, + None => return, + }; + + self.add_nonshadowable_primitive( + spatial_node_index, + clip_chain_id, + info, + Vec::new(), + prim, + ); + } + NinePatchBorderSource::RadialGradient(gradient) => { + let prim = self.create_radial_gradient_prim( + &info, + gradient.center, + gradient.start_offset * gradient.radius.width, + gradient.end_offset * gradient.radius.width, + gradient.radius.width / gradient.radius.height, + gradient_stops, + gradient.extend_mode, + LayoutSize::new(border.height as f32, border.width as f32), + LayoutSize::zero(), + Some(Box::new(nine_patch)), + ); + + self.add_nonshadowable_primitive( + spatial_node_index, + clip_chain_id, + info, + Vec::new(), + prim, + ); + } + NinePatchBorderSource::ConicGradient(gradient) => { + let prim = self.create_conic_gradient_prim( + &info, + gradient.center, + gradient.angle, + gradient.start_offset, + gradient.end_offset, + gradient_stops, + gradient.extend_mode, + LayoutSize::new(border.height as f32, border.width as f32), + LayoutSize::zero(), + Some(Box::new(nine_patch)), + ); + + self.add_nonshadowable_primitive( + spatial_node_index, + clip_chain_id, + info, + Vec::new(), + prim, + ); + } + }; + } + BorderDetails::Normal(ref border) => { + self.add_normal_border( + info, + border, + border_item.widths, + spatial_node_index, + clip_chain_id, + ); + } + } + } + + pub fn create_linear_gradient_prim( + &mut self, + info: &LayoutPrimitiveInfo, + start_point: LayoutPoint, + end_point: LayoutPoint, + stops: ItemRange, + extend_mode: ExtendMode, + stretch_size: LayoutSize, + mut tile_spacing: LayoutSize, + nine_patch: Option>, + ) -> Option { + let mut prim_rect = info.rect; + simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect); + + let mut max_alpha: f32 = 0.0; + + let stops = stops.iter().map(|stop| { + max_alpha = max_alpha.max(stop.color.a); + GradientStopKey { + offset: stop.offset, + color: stop.color.into(), + } + }).collect(); + + // If all the stops have no alpha, then this + // gradient can't contribute to the scene. + if max_alpha <= 0.0 { + return None; + } + + // Try to ensure that if the gradient is specified in reverse, then so long as the stops + // are also supplied in reverse that the rendered result will be equivalent. To do this, + // a reference orientation for the gradient line must be chosen, somewhat arbitrarily, so + // just designate the reference orientation as start < end. Aligned gradient rendering + // manages to produce the same result regardless of orientation, so don't worry about + // reversing in that case. + let reverse_stops = start_point.x > end_point.x || + (start_point.x == end_point.x && start_point.y > end_point.y); + + // To get reftests exactly matching with reverse start/end + // points, it's necessary to reverse the gradient + // line in some cases. + let (sp, ep) = if reverse_stops { + (end_point, start_point) + } else { + (start_point, end_point) + }; + + Some(LinearGradient { + extend_mode, + start_point: sp.into(), + end_point: ep.into(), + stretch_size: stretch_size.into(), + tile_spacing: tile_spacing.into(), + stops, + reverse_stops, + nine_patch, + }) + } + + pub fn create_radial_gradient_prim( + &mut self, + info: &LayoutPrimitiveInfo, + center: LayoutPoint, + start_radius: f32, + end_radius: f32, + ratio_xy: f32, + stops: ItemRange, + extend_mode: ExtendMode, + stretch_size: LayoutSize, + mut tile_spacing: LayoutSize, + nine_patch: Option>, + ) -> RadialGradient { + let mut prim_rect = info.rect; + simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect); + + let params = RadialGradientParams { + start_radius, + end_radius, + ratio_xy, + }; + + let stops = stops.iter().map(|stop| { + GradientStopKey { + offset: stop.offset, + color: stop.color.into(), + } + }).collect(); + + RadialGradient { + extend_mode, + center: center.into(), + params, + stretch_size: stretch_size.into(), + tile_spacing: tile_spacing.into(), + nine_patch, + stops, + } + } + + pub fn create_conic_gradient_prim( + &mut self, + info: &LayoutPrimitiveInfo, + center: LayoutPoint, + angle: f32, + start_offset: f32, + end_offset: f32, + stops: ItemRange, + extend_mode: ExtendMode, + stretch_size: LayoutSize, + mut tile_spacing: LayoutSize, + nine_patch: Option>, + ) -> ConicGradient { + let mut prim_rect = info.rect; + simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect); + + let stops = stops.iter().map(|stop| { + GradientStopKey { + offset: stop.offset, + color: stop.color.into(), + } + }).collect(); + + ConicGradient { + extend_mode, + center: center.into(), + params: ConicGradientParams { angle, start_offset, end_offset }, + stretch_size: stretch_size.into(), + tile_spacing: tile_spacing.into(), + nine_patch, + stops, + } + } + + pub fn add_text( + &mut self, + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, + prim_info: &LayoutPrimitiveInfo, + font_instance_key: &FontInstanceKey, + text_color: &ColorF, + glyph_range: ItemRange, + glyph_options: Option, + ) { + let offset = self.current_offset(spatial_node_index); + + let text_run = { + let instance_map = self.font_instances.lock().unwrap(); + let font_instance = match instance_map.get(font_instance_key) { + Some(instance) => instance, + None => { + warn!("Unknown font instance key"); + debug!("key={:?}", font_instance_key); + return; + } + }; + + // Trivial early out checks + if font_instance.size <= FontSize::zero() { + return; + } + + // TODO(gw): Use a proper algorithm to select + // whether this item should be rendered with + // subpixel AA! + let mut render_mode = self.config + .default_font_render_mode + .limit_by(font_instance.render_mode); + let mut flags = font_instance.flags; + if let Some(options) = glyph_options { + render_mode = render_mode.limit_by(options.render_mode); + flags |= options.flags; + } + + let font = FontInstance::new( + Arc::clone(font_instance), + (*text_color).into(), + render_mode, + flags, + ); + + // TODO(gw): It'd be nice not to have to allocate here for creating + // the primitive key, when the common case is that the + // hash will match and we won't end up creating a new + // primitive template. + let prim_offset = prim_info.rect.origin.to_vector() - offset; + let glyphs = glyph_range + .iter() + .map(|glyph| { + GlyphInstance { + index: glyph.index, + point: glyph.point - prim_offset, + } + }) + .collect(); + + TextRun { + glyphs: Arc::new(glyphs), + font, + shadow: false, + } + }; + + self.add_primitive( + spatial_node_index, + clip_chain_id, + prim_info, + Vec::new(), + text_run, + ); + } + + pub fn add_image( + &mut self, + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, + info: &LayoutPrimitiveInfo, + stretch_size: LayoutSize, + mut tile_spacing: LayoutSize, + image_key: ImageKey, + image_rendering: ImageRendering, + alpha_type: AlphaType, + color: ColorF, + ) { + let mut prim_rect = info.rect; + simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect); + let info = LayoutPrimitiveInfo { + rect: prim_rect, + .. *info + }; + + self.add_primitive( + spatial_node_index, + clip_chain_id, + &info, + Vec::new(), + Image { + key: image_key, + tile_spacing: tile_spacing.into(), + stretch_size: stretch_size.into(), + color: color.into(), + image_rendering, + alpha_type, + }, + ); + } + + pub fn add_yuv_image( + &mut self, + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, + info: &LayoutPrimitiveInfo, + yuv_data: YuvData, + color_depth: ColorDepth, + color_space: YuvColorSpace, + color_range: ColorRange, + image_rendering: ImageRendering, + ) { + let format = yuv_data.get_format(); + let yuv_key = match yuv_data { + YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::DUMMY], + YuvData::PlanarYCbCr(plane_0, plane_1, plane_2) => [plane_0, plane_1, plane_2], + YuvData::InterleavedYCbCr(plane_0) => [plane_0, ImageKey::DUMMY, ImageKey::DUMMY], + }; + + self.add_nonshadowable_primitive( + spatial_node_index, + clip_chain_id, + info, + Vec::new(), + YuvImage { + color_depth, + yuv_key, + format, + color_space, + color_range, + image_rendering, + }, + ); + } + + fn add_primitive_instance_to_3d_root( + &mut self, + prim: ExtendedPrimitiveInstance, + ) { + // find the 3D root and append to the children list + for sc in self.sc_stack.iter_mut().rev() { + match sc.context_3d { + Picture3DContext::In { root_data: Some(ref mut prims), .. } => { + prims.push(prim); + break; + } + Picture3DContext::In { .. } => {} + Picture3DContext::Out => panic!("Unable to find 3D root"), + } + } + } + + pub fn add_backdrop_filter( + &mut self, + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, + info: &LayoutPrimitiveInfo, + filters: Vec, + filter_datas: Vec, + filter_primitives: Vec, + ) { + let mut backdrop_pic_index = match self.cut_backdrop_picture() { + // Backdrop contains no content, so no need to add backdrop-filter + None => return, + Some(backdrop_pic_index) => backdrop_pic_index, + }; + + let backdrop_spatial_node_index = self.prim_store.pictures[backdrop_pic_index.0].spatial_node_index; + let requested_raster_space = self.sc_stack.last().expect("no active stacking context").requested_raster_space; + + let mut instance = self.create_primitive( + info, + // TODO(cbrewster): This is a bit of a hack to help figure out the correct sizing of the backdrop + // region. By makings sure to include this, the clip chain instance computes the correct clip rect, + // but we don't actually apply the filtered backdrop clip yet (this is done to the last instance in + // the filter chain below). + backdrop_spatial_node_index, + clip_chain_id, + Backdrop { + pic_index: backdrop_pic_index, + spatial_node_index, + border_rect: info.rect.into(), + }, + ); + + // We will append the filtered backdrop to the backdrop root, but we need to + // make sure all clips between the current stacking context and backdrop root + // are taken into account. So we wrap the backdrop filter instance with a picture with + // a clip for each stacking context. + for stacking_context in self.sc_stack.iter().rev().take_while(|sc| !sc.is_backdrop_root) { + let clip_chain_id = stacking_context.clip_chain_id; + let prim_flags = stacking_context.prim_flags; + let composite_mode = None; + + let mut prim_list = PrimitiveList::empty(); + prim_list.add_prim( + instance, + LayoutRect::zero(), + backdrop_spatial_node_index, + prim_flags, + ); + + backdrop_pic_index = PictureIndex(self.prim_store.pictures + .alloc() + .init(PicturePrimitive::new_image( + composite_mode.clone(), + Picture3DContext::Out, + None, + true, + prim_flags, + requested_raster_space, + prim_list, + backdrop_spatial_node_index, + None, + PictureOptions { + inflate_if_required: false, + }, + )) + ); + + instance = create_prim_instance( + backdrop_pic_index, + composite_mode.into(), + clip_chain_id, + &mut self.interners, + ); + } + + let (mut filtered_pic_index, mut filtered_instance) = self.wrap_prim_with_filters( + instance, + backdrop_pic_index, + filters, + filter_primitives, + filter_datas, + info.flags, + requested_raster_space, + backdrop_spatial_node_index, + false, + ); + + // Apply filters from all stacking contexts up to, but not including the backdrop root. + // Gecko pushes separate stacking contexts for filters and opacity, + // so we must iterate through multiple stacking contexts to find all effects + // that need to be applied to the filtered backdrop. + let backdrop_root_pos = self.sc_stack.iter().rposition(|sc| sc.is_backdrop_root).expect("no backdrop root?"); + for i in ((backdrop_root_pos + 1)..self.sc_stack.len()).rev() { + let stacking_context = &self.sc_stack[i]; + let filters = stacking_context.composite_ops.filters.clone(); + let filter_primitives = stacking_context.composite_ops.filter_primitives.clone(); + let filter_datas = stacking_context.composite_ops.filter_datas.clone(); + + let (pic_index, instance) = self.wrap_prim_with_filters( + filtered_instance, + filtered_pic_index, + filters, + filter_primitives, + filter_datas, + info.flags, + requested_raster_space, + backdrop_spatial_node_index, + false, + ); + + filtered_instance = instance; + filtered_pic_index = pic_index; + } + + filtered_instance.clip_chain_id = clip_chain_id; + + self.sc_stack + .iter_mut() + .rev() + .find(|sc| sc.is_backdrop_root) + .unwrap() + .prim_list + .add_prim( + filtered_instance, + LayoutRect::zero(), + backdrop_spatial_node_index, + info.flags, + ); + } + + pub fn cut_backdrop_picture(&mut self) -> Option { + let mut flattened_items = None; + let mut backdrop_root = None; + let mut spatial_node_index = SpatialNodeIndex::INVALID; + let mut prim_flags = PrimitiveFlags::default(); + for sc in self.sc_stack.iter_mut().rev() { + // Add child contents to parent stacking context + if let Some((_, flattened_instance)) = flattened_items.take() { + sc.prim_list.add_prim( + flattened_instance, + LayoutRect::zero(), + spatial_node_index, + prim_flags, + ); + } + flattened_items = sc.cut_item_sequence( + &mut self.prim_store, + &mut self.interners, + None, + Picture3DContext::Out, + ); + spatial_node_index = sc.spatial_node_index; + prim_flags = sc.prim_flags; + if sc.is_backdrop_root { + backdrop_root = Some(sc); + break; + } + } + + let (pic_index, instance) = flattened_items?; + self.prim_store.pictures[pic_index.0].requested_composite_mode = Some(PictureCompositeMode::Blit(BlitReason::BACKDROP)); + backdrop_root.expect("no backdrop root found") + .prim_list + .add_prim( + instance, + LayoutRect::zero(), + spatial_node_index, + prim_flags, + ); + + Some(pic_index) + } + + fn wrap_prim_with_filters( + &mut self, + mut cur_instance: PrimitiveInstance, + mut current_pic_index: PictureIndex, + mut filter_ops: Vec, + mut filter_primitives: Vec, + filter_datas: Vec, + flags: PrimitiveFlags, + requested_raster_space: RasterSpace, + spatial_node_index: SpatialNodeIndex, + inflate_if_required: bool, + ) -> (PictureIndex, PrimitiveInstance) { + // TODO(cbrewster): Currently CSS and SVG filters live side by side in WebRender, but unexpected results will + // happen if they are used simulataneously. Gecko only provides either filter ops or filter primitives. + // At some point, these two should be combined and CSS filters should be expressed in terms of SVG filters. + assert!(filter_ops.is_empty() || filter_primitives.is_empty(), + "Filter ops and filter primitives are not allowed on the same stacking context."); + + // For each filter, create a new image with that composite mode. + let mut current_filter_data_index = 0; + for filter in &mut filter_ops { + let composite_mode = Some(match *filter { + Filter::ComponentTransfer => { + let filter_data = + &filter_datas[current_filter_data_index]; + let filter_data = filter_data.sanitize(); + current_filter_data_index = current_filter_data_index + 1; + if filter_data.is_identity() { + continue + } else { + let filter_data_key = SFilterDataKey { + data: + SFilterData { + r_func: SFilterDataComponent::from_functype_values( + filter_data.func_r_type, &filter_data.r_values), + g_func: SFilterDataComponent::from_functype_values( + filter_data.func_g_type, &filter_data.g_values), + b_func: SFilterDataComponent::from_functype_values( + filter_data.func_b_type, &filter_data.b_values), + a_func: SFilterDataComponent::from_functype_values( + filter_data.func_a_type, &filter_data.a_values), + }, + }; + + let handle = self.interners + .filter_data + .intern(&filter_data_key, || ()); + PictureCompositeMode::ComponentTransferFilter(handle) + } + } + _ => PictureCompositeMode::Filter(filter.clone()), + }); + + let mut prim_list = PrimitiveList::empty(); + prim_list.add_prim( + cur_instance.clone(), + LayoutRect::zero(), + spatial_node_index, + flags, + ); + + let filter_pic_index = PictureIndex(self.prim_store.pictures + .alloc() + .init(PicturePrimitive::new_image( + composite_mode.clone(), + Picture3DContext::Out, + None, + true, + flags, + requested_raster_space, + prim_list, + spatial_node_index, + None, + PictureOptions { + inflate_if_required, + }, + )) + ); + + current_pic_index = filter_pic_index; + cur_instance = create_prim_instance( + current_pic_index, + composite_mode.into(), + ClipChainId::NONE, + &mut self.interners, + ); + + if cur_instance.is_chased() { + println!("\tis a composite picture for a stacking context with {:?}", filter); + } + + // Run the optimize pass on this picture, to see if we can + // collapse opacity and avoid drawing to an off-screen surface. + self.prim_store.optimize_picture_if_possible(current_pic_index); + } + + if !filter_primitives.is_empty() { + let filter_datas = filter_datas.iter() + .map(|filter_data| filter_data.sanitize()) + .map(|filter_data| { + SFilterData { + r_func: SFilterDataComponent::from_functype_values( + filter_data.func_r_type, &filter_data.r_values), + g_func: SFilterDataComponent::from_functype_values( + filter_data.func_g_type, &filter_data.g_values), + b_func: SFilterDataComponent::from_functype_values( + filter_data.func_b_type, &filter_data.b_values), + a_func: SFilterDataComponent::from_functype_values( + filter_data.func_a_type, &filter_data.a_values), + } + }) + .collect(); + + // Sanitize filter inputs + for primitive in &mut filter_primitives { + primitive.sanitize(); + } + + let composite_mode = PictureCompositeMode::SvgFilter( + filter_primitives, + filter_datas, + ); + + let mut prim_list = PrimitiveList::empty(); + prim_list.add_prim( + cur_instance.clone(), + LayoutRect::zero(), + spatial_node_index, + flags, + ); + + let filter_pic_index = PictureIndex(self.prim_store.pictures + .alloc() + .init(PicturePrimitive::new_image( + Some(composite_mode.clone()), + Picture3DContext::Out, + None, + true, + flags, + requested_raster_space, + prim_list, + spatial_node_index, + None, + PictureOptions { + inflate_if_required, + }, + )) + ); + + current_pic_index = filter_pic_index; + cur_instance = create_prim_instance( + current_pic_index, + Some(composite_mode).into(), + ClipChainId::NONE, + &mut self.interners, + ); + + if cur_instance.is_chased() { + println!("\tis a composite picture for a stacking context with an SVG filter"); + } + + // Run the optimize pass on this picture, to see if we can + // collapse opacity and avoid drawing to an off-screen surface. + self.prim_store.optimize_picture_if_possible(current_pic_index); + } + (current_pic_index, cur_instance) + } +} + + +pub trait CreateShadow { + fn create_shadow(&self, shadow: &Shadow) -> Self; +} + +pub trait IsVisible { + fn is_visible(&self) -> bool; +} + +/// A primitive instance + some extra information about the primitive. This is +/// stored when constructing 3d rendering contexts, which involve cutting +/// primitive lists. +struct ExtendedPrimitiveInstance { + instance: PrimitiveInstance, + spatial_node_index: SpatialNodeIndex, + flags: PrimitiveFlags, +} + +/// Properties of a stacking context that are maintained +/// during creation of the scene. These structures are +/// not persisted after the initial scene build. +struct FlattenedStackingContext { + /// The list of primitive instances added to this stacking context. + prim_list: PrimitiveList, + + /// Primitive instance flags for compositing this stacking context + prim_flags: PrimitiveFlags, + + /// Whether or not the caller wants this drawn in + /// screen space (quality) or local space (performance) + requested_raster_space: RasterSpace, + + /// The positioning node for this stacking context + spatial_node_index: SpatialNodeIndex, + + /// The clip chain for this stacking context + clip_chain_id: ClipChainId, + clip_id: Option, + + /// If set, this should be provided to caller + /// as an output texture. + frame_output_pipeline_id: Option, + + /// The list of filters / mix-blend-mode for this + /// stacking context. + composite_ops: CompositeOps, + + /// Bitfield of reasons this stacking context needs to + /// be an offscreen surface. + blit_reason: BlitReason, + + /// Pipeline this stacking context belongs to. + pipeline_id: PipelineId, + + /// CSS transform-style property. + transform_style: TransformStyle, + + /// Defines the relationship to a preserve-3D hiearachy. + context_3d: Picture3DContext, + + /// True if this stacking context is a backdrop root. + is_backdrop_root: bool, + + /// True if this stacking context is redundant (i.e. doesn't require a surface) + is_redundant: bool, + + /// A helper struct to snap local rects in device space. During frame + /// building we may establish new raster roots, however typically that is in + /// cases where we won't be applying snapping (e.g. has perspective), or in + /// edge cases (e.g. SVG filter) where we can accept slightly incorrect + /// behaviour in favour of getting the common case right. + snap_to_device: SpaceSnapper, +} + +impl FlattenedStackingContext { + /// Return true if the stacking context has a valid preserve-3d property + pub fn is_3d(&self) -> bool { + self.transform_style == TransformStyle::Preserve3D && self.composite_ops.is_empty() + } + + /// Set up appropriate cluster flags for picture caching on this stacking context. + fn init_picture_caching( + &mut self, + spatial_tree: &SpatialTree, + clip_store: &ClipStore, + quality_settings: &QualitySettings, + ) -> usize { + struct SliceInfo { + cluster_index: usize, + scroll_root: SpatialNodeIndex, + cluster_flags: ClusterFlags, + } + + let mut content_slice_count = 0; + let mut slices: Vec = Vec::new(); + + // Step through each cluster, and work out where the slice boundaries should be. + for (cluster_index, cluster) in self.prim_list.clusters.iter().enumerate() { + let scroll_root = spatial_tree.find_scroll_root( + cluster.spatial_node_index, + ); + + // We want to create a slice in the following conditions: + // (1) This cluster is a scrollbar + // (2) Certain conditions when the scroll root changes (see below) + // (3) No slice exists yet + let mut cluster_flags = ClusterFlags::empty(); + + if cluster.flags.contains(ClusterFlags::SCROLLBAR_CONTAINER) { + // Scrollbar containers need to ensure that a new slice is + // created both before and after the scrollbar, so that no + // other prims with the same scroll root sneak into this slice. + cluster_flags.insert( + ClusterFlags::CREATE_PICTURE_CACHE_PRE | + ClusterFlags::CREATE_PICTURE_CACHE_POST + ); + } + + let create_new_slice_for_scroll_root = + slices.last().map(|slice| { + match (slice.scroll_root, scroll_root) { + (ROOT_SPATIAL_NODE_INDEX, ROOT_SPATIAL_NODE_INDEX) => { + // Both current slice and this cluster are fixed position, no need to cut + false + } + (ROOT_SPATIAL_NODE_INDEX, _) => { + // A real scroll root is being established, so create a cache slice + true + } + (_, ROOT_SPATIAL_NODE_INDEX) => { + // If quality settings force subpixel AA over performance, skip creating + // a slice for the fixed position element(s) here. + if quality_settings.force_subpixel_aa_where_possible { + return false; + } + + // A fixed position slice is encountered within a scroll root. Only create + // a slice in this case if all the clips referenced by this cluster are also + // fixed position. There's no real point in creating slices for these cases, + // since we'll have to rasterize them as the scrolling clip moves anyway. It + // also allows us to retain subpixel AA in these cases. For these types of + // slices, the intra-slice dirty rect handling typically works quite well + // (a common case is parallax scrolling effects). + for prim_instance in &cluster.prim_instances { + let mut current_clip_chain_id = prim_instance.clip_chain_id; + + while current_clip_chain_id != ClipChainId::NONE { + let clip_chain_node = &clip_store + .clip_chain_nodes[current_clip_chain_id.0 as usize]; + let spatial_root = spatial_tree.find_scroll_root(clip_chain_node.spatial_node_index); + if spatial_root != ROOT_SPATIAL_NODE_INDEX { + return false; + } + current_clip_chain_id = clip_chain_node.parent_clip_chain_id; + } + } + + true + } + (curr_scroll_root, scroll_root) => { + // Two scrolling roots - only need a new slice if they differ + curr_scroll_root != scroll_root + } + } + }).unwrap_or(true); + + if create_new_slice_for_scroll_root { + cluster_flags.insert(ClusterFlags::CREATE_PICTURE_CACHE_PRE); + } + + // Create a new slice if required + if !cluster_flags.is_empty() { + slices.push(SliceInfo { + cluster_index, + scroll_root, + cluster_flags, + }); + } + } + + // If the page would create too many slices (an arbitrary definition where + // it's assumed the GPU memory + compositing overhead would be too high) + // then just create a single picture cache for the entire content. This at + // least means that we can cache small content changes efficiently when + // scrolling isn't occurring. Scrolling regions will be handled reasonably + // efficiently by the dirty rect tracking (since it's likely that if the + // page has so many slices there isn't a single major scroll region). + const MAX_CONTENT_SLICES: usize = 8; + + if slices.len() > MAX_CONTENT_SLICES { + if let Some(cluster) = self.prim_list.clusters.first_mut() { + content_slice_count = 1; + cluster.flags.insert(ClusterFlags::CREATE_PICTURE_CACHE_PRE); + cluster.cache_scroll_root = None; + } + } else { + // Walk the list of slices, setting appropriate flags on the clusters which are + // later used during setup_picture_caching. + for slice in slices.drain(..) { + content_slice_count += 1; + let cluster = &mut self.prim_list.clusters[slice.cluster_index]; + // Mark that this cluster creates a picture cache slice + cluster.flags.insert(slice.cluster_flags); + cluster.cache_scroll_root = Some(slice.scroll_root); + } + } + + // Always end the cache at the end of the stacking context, so that we don't + // cache anything from primitives outside this pipeline in the same slice. + if let Some(cluster) = self.prim_list.clusters.last_mut() { + cluster.flags.insert(ClusterFlags::CREATE_PICTURE_CACHE_POST); + } + + content_slice_count + } + + /// Return true if the stacking context isn't needed. + pub fn is_redundant( + context_3d: &Picture3DContext, + composite_ops: &CompositeOps, + prim_flags: PrimitiveFlags, + blit_reason: BlitReason, + requested_raster_space: RasterSpace, + parent: &FlattenedStackingContext, + ) -> bool { + // Any 3d context is required + if let Picture3DContext::In { .. } = context_3d { + return false; + } + + // If there are filters / mix-blend-mode + if !composite_ops.filters.is_empty() { + return false; + } + + // If there are svg filters + if !composite_ops.filter_primitives.is_empty() { + return false; + } + + // We can skip mix-blend modes if they are the first primitive in a stacking context, + // see pop_stacking_context for a full explanation. + if composite_ops.mix_blend_mode.is_some() && + !parent.prim_list.is_empty() { + return false; + } + + // If backface visibility is explicitly set. + if !prim_flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE) { + return false; + } + + // If rasterization space is different + if requested_raster_space != parent.requested_raster_space { + return false; + } + + // If need to isolate in surface due to clipping / mix-blend-mode + if !blit_reason.is_empty() { + return false; + } + + // If this stacking context is a scrollbar, retain it so it can form a picture cache slice + if prim_flags.contains(PrimitiveFlags::IS_SCROLLBAR_CONTAINER) { + return false; + } + + // It is redundant! + true + } + + /// Cut the sequence of the immediate children recorded so far and generate a picture from them. + pub fn cut_item_sequence( + &mut self, + prim_store: &mut PrimitiveStore, + interners: &mut Interners, + composite_mode: Option, + flat_items_context_3d: Picture3DContext, + ) -> Option<(PictureIndex, PrimitiveInstance)> { + if self.prim_list.is_empty() { + return None + } + + let pic_index = PictureIndex(prim_store.pictures + .alloc() + .init(PicturePrimitive::new_image( + composite_mode.clone(), + flat_items_context_3d, + None, + true, + self.prim_flags, + self.requested_raster_space, + mem::replace(&mut self.prim_list, PrimitiveList::empty()), + self.spatial_node_index, + None, + PictureOptions::default(), + )) + ); + + let prim_instance = create_prim_instance( + pic_index, + composite_mode.into(), + self.clip_chain_id, + interners, + ); + + Some((pic_index, prim_instance)) + } +} + +/// A primitive that is added while a shadow context is +/// active is stored as a pending primitive and only +/// added to pictures during pop_all_shadows. +pub struct PendingPrimitive { + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, + info: LayoutPrimitiveInfo, + prim: T, +} + +/// As shadows are pushed, they are stored as pending +/// shadows, and handled at once during pop_all_shadows. +pub struct PendingShadow { + shadow: Shadow, + should_inflate: bool, + spatial_node_index: SpatialNodeIndex, + clip_chain_id: ClipChainId, +} + +pub enum ShadowItem { + Shadow(PendingShadow), + Image(PendingPrimitive), + LineDecoration(PendingPrimitive), + NormalBorder(PendingPrimitive), + Primitive(PendingPrimitive), + TextRun(PendingPrimitive), +} + +impl From> for ShadowItem { + fn from(image: PendingPrimitive) -> Self { + ShadowItem::Image(image) + } +} + +impl From> for ShadowItem { + fn from(line_dec: PendingPrimitive) -> Self { + ShadowItem::LineDecoration(line_dec) + } +} + +impl From> for ShadowItem { + fn from(border: PendingPrimitive) -> Self { + ShadowItem::NormalBorder(border) + } +} + +impl From> for ShadowItem { + fn from(container: PendingPrimitive) -> Self { + ShadowItem::Primitive(container) + } +} + +impl From> for ShadowItem { + fn from(text_run: PendingPrimitive) -> Self { + ShadowItem::TextRun(text_run) + } +} + +fn create_prim_instance( + pic_index: PictureIndex, + composite_mode_key: PictureCompositeKey, + clip_chain_id: ClipChainId, + interners: &mut Interners, +) -> PrimitiveInstance { + let pic_key = PictureKey::new( + Picture { composite_mode_key }, + ); + + let data_handle = interners + .picture + .intern(&pic_key, || ()); + + PrimitiveInstance::new( + LayoutRect::max_rect(), + PrimitiveInstanceKind::Picture { + data_handle, + pic_index, + segment_instance_index: SegmentInstanceIndex::INVALID, + }, + clip_chain_id, + ) +} + +fn filter_ops_for_compositing( + input_filters: ItemRange, +) -> Vec { + // TODO(gw): Now that we resolve these later on, + // we could probably make it a bit + // more efficient than cloning these here. + input_filters.iter().map(|filter| filter.into()).collect() +} + +fn filter_datas_for_compositing( + input_filter_datas: &[TempFilterData], +) -> Vec { + // TODO(gw): Now that we resolve these later on, + // we could probably make it a bit + // more efficient than cloning these here. + let mut filter_datas = vec![]; + for temp_filter_data in input_filter_datas { + let func_types : Vec = temp_filter_data.func_types.iter().collect(); + debug_assert!(func_types.len() == 4); + filter_datas.push( FilterData { + func_r_type: func_types[0], + r_values: temp_filter_data.r_values.iter().collect(), + func_g_type: func_types[1], + g_values: temp_filter_data.g_values.iter().collect(), + func_b_type: func_types[2], + b_values: temp_filter_data.b_values.iter().collect(), + func_a_type: func_types[3], + a_values: temp_filter_data.a_values.iter().collect(), + }); + } + filter_datas +} + +fn filter_primitives_for_compositing( + input_filter_primitives: ItemRange, +) -> Vec { + // Resolve these in the flattener? + // TODO(gw): Now that we resolve these later on, + // we could probably make it a bit + // more efficient than cloning these here. + input_filter_primitives.iter().map(|primitive| primitive).collect() +} + +fn process_repeat_size( + snapped_rect: &LayoutRect, + unsnapped_rect: &LayoutRect, + repeat_size: LayoutSize, +) -> LayoutSize { + // FIXME(aosmond): The tile size is calculated based on several parameters + // during display list building. It may produce a slightly different result + // than the bounds due to floating point error accumulation, even though in + // theory they should be the same. We do a fuzzy check here to paper over + // that. It may make more sense to push the original parameters into scene + // building and let it do a saner calculation with more information (e.g. + // the snapped values). + const EPSILON: f32 = 0.001; + LayoutSize::new( + if repeat_size.width.approx_eq_eps(&unsnapped_rect.size.width, &EPSILON) { + snapped_rect.size.width + } else { + repeat_size.width + }, + if repeat_size.height.approx_eq_eps(&unsnapped_rect.size.height, &EPSILON) { + snapped_rect.size.height + } else { + repeat_size.height + }, + ) +} + +/// Given a PrimitiveList and scroll root, construct a tile cache primitive instance +/// that wraps the primitive list. +fn create_tile_cache( + slice: usize, + slice_flags: SliceFlags, + scroll_root: SpatialNodeIndex, + prim_list: PrimitiveList, + background_color: Option, + shared_clips: Vec, + interners: &mut Interners, + prim_store: &mut PrimitiveStore, + clip_store: &mut ClipStore, + picture_cache_spatial_nodes: &mut FastHashSet, + frame_builder_config: &FrameBuilderConfig, +) -> PrimitiveInstance { + // Add this spatial node to the list to check for complex transforms + // at the start of a frame build. + picture_cache_spatial_nodes.insert(scroll_root); + + // Now, create a picture with tile caching enabled that will hold all + // of the primitives selected as belonging to the main scroll root. + let pic_key = PictureKey::new( + Picture { + composite_mode_key: PictureCompositeKey::Identity, + }, + ); + + let pic_data_handle = interners + .picture + .intern(&pic_key, || ()); + + // Build a clip-chain for the tile cache, that contains any of the shared clips + // we will apply when drawing the tiles. In all cases provided by Gecko, these + // are rectangle clips with a scale/offset transform only, and get handled as + // a simple local clip rect in the vertex shader. However, this should in theory + // also work with any complex clips, such as rounded rects and image masks, by + // producing a clip mask that is applied to the picture cache tiles. + let mut parent_clip_chain_id = ClipChainId::NONE; + for clip_instance in &shared_clips { + // Add this spatial node to the list to check for complex transforms + // at the start of a frame build. + picture_cache_spatial_nodes.insert(clip_instance.spatial_node_index); + + parent_clip_chain_id = clip_store.add_clip_chain_node( + clip_instance.handle, + clip_instance.spatial_node_index, + parent_clip_chain_id, + ); + } + + let tile_cache = Box::new(TileCacheInstance::new( + slice, + slice_flags, + scroll_root, + background_color, + shared_clips, + parent_clip_chain_id, + frame_builder_config, + )); + + let pic_index = prim_store.pictures.alloc().init(PicturePrimitive::new_image( + Some(PictureCompositeMode::TileCache { }), + Picture3DContext::Out, + None, + true, + PrimitiveFlags::IS_BACKFACE_VISIBLE, + RasterSpace::Screen, + prim_list, + scroll_root, + Some(tile_cache), + PictureOptions::default(), + )); + + PrimitiveInstance::new( + LayoutRect::max_rect(), + PrimitiveInstanceKind::Picture { + data_handle: pic_data_handle, + pic_index: PictureIndex(pic_index), + segment_instance_index: SegmentInstanceIndex::INVALID, + }, + parent_clip_chain_id, + ) +} + +// Helper fn to collect clip handles from a given clip chain. +fn add_clips( + clip_chain_id: ClipChainId, + prim_clips: &mut Vec, + clip_store: &ClipStore, + interners: &Interners, +) { + let mut current_clip_chain_id = clip_chain_id; + + while current_clip_chain_id != ClipChainId::NONE { + let clip_chain_node = &clip_store + .clip_chain_nodes[current_clip_chain_id.0 as usize]; + + let clip_node_data = &interners.clip[clip_chain_node.handle]; + if let ClipNodeKind::Rectangle = clip_node_data.clip_node_kind { + prim_clips.push(ClipInstance::new(clip_chain_node.handle, clip_chain_node.spatial_node_index)); + } + + current_clip_chain_id = clip_chain_node.parent_clip_chain_id; + } +} diff --git a/third_party/webrender/webrender/src/screen_capture.rs b/third_party/webrender/webrender/src/screen_capture.rs new file mode 100644 index 00000000000..56fdf458674 --- /dev/null +++ b/third_party/webrender/webrender/src/screen_capture.rs @@ -0,0 +1,493 @@ +/* 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/. */ + +//! Screen capture infrastructure for the Gecko Profiler and Composition Recorder. + +use std::collections::HashMap; + +use api::{ImageFormat, TextureTarget}; +use api::units::*; +use gleam::gl::GlType; + +use crate::device::{Device, PBO, DrawTarget, ReadTarget, Texture, TextureFilter}; +use crate::internal_types::RenderTargetInfo; +use crate::renderer::Renderer; +use crate::util::round_up_to_multiple; + +/// A handle to a screenshot that is being asynchronously captured and scaled. +#[repr(C)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct AsyncScreenshotHandle(usize); + +/// A handle to a recorded frame that was captured. +#[repr(C)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct RecordedFrameHandle(usize); + +/// An asynchronously captured screenshot bound to a PBO which has not yet been mapped for copying. +struct AsyncScreenshot { + /// The PBO that will contain the screenshot data. + pbo: PBO, + /// The size of the screenshot. + screenshot_size: DeviceIntSize, + /// The stride of the data in the PBO. + buffer_stride: usize, + /// Thge image format of the screenshot. + image_format: ImageFormat, +} + +/// How the `AsyncScreenshotGrabber` captures frames. +#[derive(Debug, Eq, PartialEq)] +enum AsyncScreenshotGrabberMode { + /// Capture screenshots for the Gecko profiler. + /// + /// This mode will asynchronously scale the screenshots captured. + ProfilerScreenshots, + + /// Capture screenshots for the CompositionRecorder. + /// + /// This mode does not scale the captured screenshots. + CompositionRecorder, +} + +/// Renderer infrastructure for capturing screenshots and scaling them asynchronously. +pub(in crate) struct AsyncScreenshotGrabber { + /// The textures used to scale screenshots. + scaling_textures: Vec, + /// PBOs available to be used for screenshot readback. + available_pbos: Vec, + /// PBOs containing screenshots that are awaiting readback. + awaiting_readback: HashMap, + /// The handle for the net PBO that will be inserted into `in_use_pbos`. + next_pbo_handle: usize, + /// The mode the grabber operates in. + mode: AsyncScreenshotGrabberMode, +} + +impl Default for AsyncScreenshotGrabber { + fn default() -> Self { + AsyncScreenshotGrabber { + scaling_textures: Vec::new(), + available_pbos: Vec::new(), + awaiting_readback: HashMap::new(), + next_pbo_handle: 1, + mode: AsyncScreenshotGrabberMode::ProfilerScreenshots, + } + } +} + +impl AsyncScreenshotGrabber { + /// Create a new AsyncScreenshotGrabber for the composition recorder. + pub fn new_composition_recorder() -> Self { + let mut recorder = Self::default(); + recorder.mode = AsyncScreenshotGrabberMode::CompositionRecorder; + + recorder + } + + /// Deinitialize the allocated textures and PBOs. + pub fn deinit(self, device: &mut Device) { + for texture in self.scaling_textures { + device.delete_texture(texture); + } + + for pbo in self.available_pbos { + device.delete_pbo(pbo); + } + + for (_, async_screenshot) in self.awaiting_readback { + device.delete_pbo(async_screenshot.pbo); + } + } + + /// Take a screenshot and scale it asynchronously. + /// + /// The returned handle can be used to access the mapped screenshot data via + /// `map_and_recycle_screenshot`. + /// The returned size is the size of the screenshot. + pub fn get_screenshot( + &mut self, + device: &mut Device, + window_rect: DeviceIntRect, + buffer_size: DeviceIntSize, + image_format: ImageFormat, + ) -> (AsyncScreenshotHandle, DeviceIntSize) { + let screenshot_size = match self.mode { + AsyncScreenshotGrabberMode::ProfilerScreenshots => { + let scale = (buffer_size.width as f32 / window_rect.size.width as f32) + .min(buffer_size.height as f32 / window_rect.size.height as f32); + + (window_rect.size.to_f32() * scale).round().to_i32() + } + + AsyncScreenshotGrabberMode::CompositionRecorder => { + assert_eq!(buffer_size, window_rect.size); + buffer_size + } + }; + + assert!(screenshot_size.width <= buffer_size.width); + assert!(screenshot_size.height <= buffer_size.height); + + // To ensure that we hit the fast path when reading from a + // framebuffer we must ensure that the width of the area we read + // is a multiple of the device's optimal pixel-transfer stride. + // The read_size should therefore be the screenshot_size with the width + // increased to a suitable value. We will also pass this value to + // scale_screenshot() as the min_texture_size, to ensure the texture is + // large enough to read from. In CompositionRecorder mode we read + // directly from the default framebuffer so are unable choose this size. + let read_size = match self.mode { + AsyncScreenshotGrabberMode::ProfilerScreenshots => { + let stride = (screenshot_size.width * image_format.bytes_per_pixel()) as usize; + let rounded = round_up_to_multiple(stride, device.optimal_pbo_stride().num_bytes(image_format)); + let optimal_width = rounded as i32 / image_format.bytes_per_pixel(); + + DeviceIntSize::new( + optimal_width, + screenshot_size.height, + ) + } + AsyncScreenshotGrabberMode::CompositionRecorder => buffer_size, + }; + let required_size = read_size.area() as usize * image_format.bytes_per_pixel() as usize; + + // Find an available PBO with the required size, creating a new one if necessary. + let pbo = { + let mut reusable_pbo = None; + while let Some(pbo) = self.available_pbos.pop() { + if pbo.get_reserved_size() != required_size { + device.delete_pbo(pbo); + } else { + reusable_pbo = Some(pbo); + break; + } + }; + + reusable_pbo.unwrap_or_else(|| device.create_pbo_with_size(required_size)) + }; + assert_eq!(pbo.get_reserved_size(), required_size); + + let read_target = match self.mode { + AsyncScreenshotGrabberMode::ProfilerScreenshots => { + self.scale_screenshot( + device, + ReadTarget::Default, + window_rect, + buffer_size, + read_size, + screenshot_size, + image_format, + 0, + ); + + ReadTarget::from_texture(&self.scaling_textures[0], 0) + } + + AsyncScreenshotGrabberMode::CompositionRecorder => ReadTarget::Default, + }; + + device.read_pixels_into_pbo( + read_target, + DeviceIntRect::new(DeviceIntPoint::new(0, 0), read_size), + image_format, + &pbo, + ); + + let handle = AsyncScreenshotHandle(self.next_pbo_handle); + self.next_pbo_handle += 1; + + self.awaiting_readback.insert( + handle, + AsyncScreenshot { + pbo, + screenshot_size, + buffer_stride: (read_size.width * image_format.bytes_per_pixel()) as usize, + image_format, + }, + ); + + (handle, screenshot_size) + } + + /// Take the screenshot in the given `ReadTarget` and scale it to `dest_size` recursively. + /// + /// Each scaling operation scales only by a factor of two to preserve quality. + /// + /// Textures are scaled such that `scaling_textures[n]` is half the size of + /// `scaling_textures[n+1]`. + /// + /// After the scaling completes, the final screenshot will be in + /// `scaling_textures[0]`. + /// + /// The size of `scaling_textures[0]` will be increased to `min_texture_size` + /// so that an optimally-sized area can be read from it. + fn scale_screenshot( + &mut self, + device: &mut Device, + read_target: ReadTarget, + read_target_rect: DeviceIntRect, + buffer_size: DeviceIntSize, + min_texture_size: DeviceIntSize, + dest_size: DeviceIntSize, + image_format: ImageFormat, + level: usize, + ) { + assert_eq!(self.mode, AsyncScreenshotGrabberMode::ProfilerScreenshots); + + let texture_size = { + let size = buffer_size * (1 << level); + DeviceIntSize::new( + size.width.max(min_texture_size.width), + size.height.max(min_texture_size.height), + ) + }; + + // If we haven't created a texture for this level, or the existing + // texture is the wrong size, then create a new one. + if level == self.scaling_textures.len() || self.scaling_textures[level].get_dimensions() != texture_size { + let texture = device.create_texture( + TextureTarget::Default, + image_format, + texture_size.width, + texture_size.height, + TextureFilter::Linear, + Some(RenderTargetInfo { has_depth: false }), + 1, + ); + if level == self.scaling_textures.len() { + self.scaling_textures.push(texture); + } else { + let old_texture = std::mem::replace(&mut self.scaling_textures[level], texture); + device.delete_texture(old_texture); + } + } + assert_eq!(self.scaling_textures[level].get_dimensions(), texture_size); + + let (read_target, read_target_rect) = if read_target_rect.size.width > 2 * dest_size.width { + self.scale_screenshot( + device, + read_target, + read_target_rect, + buffer_size, + min_texture_size, + dest_size * 2, + image_format, + level + 1, + ); + + ( + ReadTarget::from_texture(&self.scaling_textures[level + 1], 0), + DeviceIntRect::new(DeviceIntPoint::new(0, 0), dest_size * 2), + ) + } else { + (read_target, read_target_rect) + }; + + let draw_target = DrawTarget::from_texture(&self.scaling_textures[level], 0 as _, false); + + let draw_target_rect = draw_target + .to_framebuffer_rect(DeviceIntRect::new(DeviceIntPoint::new(0, 0), dest_size)); + + let read_target_rect = device_rect_as_framebuffer_rect(&read_target_rect); + + if level == 0 && !device.surface_origin_is_top_left() { + device.blit_render_target_invert_y( + read_target, + read_target_rect, + draw_target, + draw_target_rect, + ); + } else { + device.blit_render_target( + read_target, + read_target_rect, + draw_target, + draw_target_rect, + TextureFilter::Linear, + ); + } + } + + /// Map the contents of the screenshot given by the handle and copy it into + /// the given buffer. + pub fn map_and_recycle_screenshot( + &mut self, + device: &mut Device, + handle: AsyncScreenshotHandle, + dst_buffer: &mut [u8], + dst_stride: usize, + ) -> bool { + let AsyncScreenshot { + pbo, + screenshot_size, + buffer_stride, + image_format, + } = match self.awaiting_readback.remove(&handle) { + Some(screenshot) => screenshot, + None => return false, + }; + + let gl_type = device.gl().get_type(); + + let success = if let Some(bound_pbo) = device.map_pbo_for_readback(&pbo) { + let src_buffer = &bound_pbo.data; + let src_stride = buffer_stride; + let src_width = + screenshot_size.width as usize * image_format.bytes_per_pixel() as usize; + + for (src_slice, dst_slice) in self + .iter_src_buffer_chunked(gl_type, src_buffer, src_stride) + .zip(dst_buffer.chunks_mut(dst_stride)) + .take(screenshot_size.height as usize) + { + dst_slice[.. src_width].copy_from_slice(&src_slice[.. src_width]); + } + + true + } else { + false + }; + + match self.mode { + AsyncScreenshotGrabberMode::ProfilerScreenshots => self.available_pbos.push(pbo), + AsyncScreenshotGrabberMode::CompositionRecorder => device.delete_pbo(pbo), + } + + success + } + + fn iter_src_buffer_chunked<'a>( + &self, + gl_type: GlType, + src_buffer: &'a [u8], + src_stride: usize, + ) -> Box + 'a> { + use AsyncScreenshotGrabberMode::*; + + let is_angle = cfg!(windows) && gl_type == GlType::Gles; + + if self.mode == CompositionRecorder && !is_angle { + // This is a non-ANGLE configuration. in this case, the recorded frames were captured + // upside down, so we have to flip them right side up. + Box::new(src_buffer.chunks(src_stride).rev()) + } else { + // This is either an ANGLE configuration in the `CompositionRecorder` mode or a + // non-ANGLE configuration in the `ProfilerScreenshots` mode. In either case, the + // captured frames are right-side up. + Box::new(src_buffer.chunks(src_stride)) + } + } +} + +// Screen-capture specific Renderer impls. +impl Renderer { + /// Record a frame for the Composition Recorder. + /// + /// The returned handle can be passed to `map_recorded_frame` to copy it into + /// a buffer. + /// The returned size is the size of the frame. + pub fn record_frame( + &mut self, + image_format: ImageFormat, + ) -> Option<(RecordedFrameHandle, DeviceIntSize)> { + let device_size = self.device_size()?; + self.device.begin_frame(); + + let (handle, _) = self + .async_frame_recorder + .get_or_insert_with(AsyncScreenshotGrabber::new_composition_recorder) + .get_screenshot( + &mut self.device, + DeviceIntRect::new(DeviceIntPoint::new(0, 0), device_size), + device_size, + image_format, + ); + + self.device.end_frame(); + + Some((RecordedFrameHandle(handle.0), device_size)) + } + + /// Map a frame captured for the composition recorder into the given buffer. + pub fn map_recorded_frame( + &mut self, + handle: RecordedFrameHandle, + dst_buffer: &mut [u8], + dst_stride: usize, + ) -> bool { + if let Some(async_frame_recorder) = self.async_frame_recorder.as_mut() { + async_frame_recorder.map_and_recycle_screenshot( + &mut self.device, + AsyncScreenshotHandle(handle.0), + dst_buffer, + dst_stride, + ) + } else { + false + } + } + + /// Free the data structures used by the composition recorder. + pub fn release_composition_recorder_structures(&mut self) { + if let Some(async_frame_recorder) = self.async_frame_recorder.take() { + self.device.begin_frame(); + async_frame_recorder.deinit(&mut self.device); + self.device.end_frame(); + } + } + + /// Take a screenshot and scale it asynchronously. + /// + /// The returned handle can be used to access the mapped screenshot data via + /// `map_and_recycle_screenshot`. + /// + /// The returned size is the size of the screenshot. + pub fn get_screenshot_async( + &mut self, + window_rect: DeviceIntRect, + buffer_size: DeviceIntSize, + image_format: ImageFormat, + ) -> (AsyncScreenshotHandle, DeviceIntSize) { + self.device.begin_frame(); + + let handle = self + .async_screenshots + .get_or_insert_with(AsyncScreenshotGrabber::default) + .get_screenshot(&mut self.device, window_rect, buffer_size, image_format); + + self.device.end_frame(); + + handle + } + + /// Map the contents of the screenshot given by the handle and copy it into + /// the given buffer. + pub fn map_and_recycle_screenshot( + &mut self, + handle: AsyncScreenshotHandle, + dst_buffer: &mut [u8], + dst_stride: usize, + ) -> bool { + if let Some(async_screenshots) = self.async_screenshots.as_mut() { + async_screenshots.map_and_recycle_screenshot( + &mut self.device, + handle, + dst_buffer, + dst_stride, + ) + } else { + false + } + } + + /// Release the screenshot grabbing structures that the profiler was using. + pub fn release_profiler_structures(&mut self) { + if let Some(async_screenshots) = self.async_screenshots.take() { + self.device.begin_frame(); + async_screenshots.deinit(&mut self.device); + self.device.end_frame(); + } + } +} diff --git a/third_party/webrender/webrender/src/segment.rs b/third_party/webrender/webrender/src/segment.rs new file mode 100644 index 00000000000..77bca528215 --- /dev/null +++ b/third_party/webrender/webrender/src/segment.rs @@ -0,0 +1,1326 @@ +/* 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/. */ + +//! Primitive segmentation +//! +//! # Overview +//! +//! Segmenting is the process of breaking rectangular primitives into smaller rectangular +//! primitives in order to extract parts that could benefit from a fast paths. +//! +//! Typically this is used to allow fully opaque segments to be rendered in the opaque +//! pass. For example when an opaque rectangle has a non-axis-aligned transform applied, +//! we usually have to apply some anti-aliasing around the edges which requires alpha +//! blending. By segmenting the edges out of the center of the primitive, we can keep a +//! large amount of pixels in the opaque pass. +//! Segmenting also lets us avoids rasterizing parts of clip masks that we know to have +//! no effect or to be fully masking. For example by segmenting the corners of a rounded +//! rectangle clip, we can optimize both rendering the mask and the primitive by only +//! rasterize the corners in the mask and not applying any clipping to the segments of +//! the primitive that don't overlap the borders. +//! +//! It is a flexible system in the sense that different sources of segmentation (for +//! example two rounded rectangle clips) can affect the segmentation, and the possibility +//! to segment some effects such as specific clip kinds does not necessarily mean the +//! primitive will actually be segmented. +//! +//! ## Segments and clipping +//! +//! Segments of a primitive can be either not clipped, fully clipped, or partially clipped. +//! In the first two case we don't need a clip mask. For each partially masked segments, a +//! mask is rasterized using a render task. All of the interesting steps happen during frame +//! building. +//! +//! - The first step is to determine the segmentation and write the associated GPU data. +//! See `PrimitiveInstance::build_segments_if_needed` and `write_brush_segment_description` +//! in `prim_store/mod.rs` which uses the segment builder of this module. +//! - The second step is to generate the mask render tasks. +//! See `BrushSegment::update_clip_task` and `RenderTask::new_mask`. For each segment that +//! needs a mask, the contribution of all clips that affect the segment is added to the +//! mask's render task. +//! - Segments are assigned to batches (See `batch.rs`). Segments of a given primitive can +//! be assigned to different batches. +//! +//! See also the [`clip` module documentation][clip.rs] for details about how clipping +//! information is represented. +//! +//! +//! [clip.rs]: ../clip/index.html +//! + +use api::{BorderRadius, ClipMode, EdgeAaSegmentMask}; +use api::units::*; +use std::{cmp, usize}; +use crate::util::{extract_inner_rect_safe, RectHelpers}; +use smallvec::SmallVec; + +bitflags! { + pub struct ItemFlags: u8 { + const X_ACTIVE = 0x1; + const Y_ACTIVE = 0x2; + const HAS_MASK = 0x4; + } +} + +// The segment builder outputs a list of these segments. +#[derive(Debug, PartialEq)] +pub struct Segment { + pub rect: LayoutRect, + pub has_mask: bool, + pub edge_flags: EdgeAaSegmentMask, + pub region_x: usize, + pub region_y: usize, +} + +// The segment builder creates a list of x/y axis events +// that are used to build a segment list. Right now, we +// don't bother providing a list of *which* clip regions +// are active for a given segment. Instead, if there is +// any clip mask present in a segment, we will just end +// up drawing each of the masks to that segment clip. +// This is a fairly rare case, but we can detect this +// in the future and only apply clip masks that are +// relevant to each segment region. +// TODO(gw): Provide clip region info with each segment. +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd)] +enum EventKind { + // Beginning of a clip (rounded) rect. + BeginClip, + // End of a clip (rounded) rect. + EndClip, + // Begin the next region in the primitive. + BeginRegion, +} + +// Events must be ordered such that when the coordinates +// of two events are the same, the end events are processed +// before the begin events. This ensures that we're able +// to detect which regions are active for a given segment. +impl Ord for EventKind { + fn cmp(&self, other: &EventKind) -> cmp::Ordering { + match (*self, *other) { + (EventKind::BeginRegion, EventKind::BeginRegion) => { + panic!("bug: regions must be non-overlapping") + } + (EventKind::EndClip, EventKind::BeginRegion) | + (EventKind::BeginRegion, EventKind::BeginClip) => { + cmp::Ordering::Less + } + (EventKind::BeginClip, EventKind::BeginRegion) | + (EventKind::BeginRegion, EventKind::EndClip) => { + cmp::Ordering::Greater + } + (EventKind::BeginClip, EventKind::BeginClip) | + (EventKind::EndClip, EventKind::EndClip) => { + cmp::Ordering::Equal + } + (EventKind::BeginClip, EventKind::EndClip) => { + cmp::Ordering::Greater + } + (EventKind::EndClip, EventKind::BeginClip) => { + cmp::Ordering::Less + } + } + } +} + +// A x/y event where we will create a vertex in the +// segment builder. +#[derive(Debug, Eq, PartialEq, PartialOrd)] +struct Event { + value: Au, + item_index: ItemIndex, + kind: EventKind, +} + +impl Ord for Event { + fn cmp(&self, other: &Event) -> cmp::Ordering { + self.value + .cmp(&other.value) + .then(self.kind.cmp(&other.kind)) + } +} + +impl Event { + fn begin(value: f32, index: usize) -> Event { + Event { + value: Au::from_f32_px(value), + item_index: ItemIndex(index), + kind: EventKind::BeginClip, + } + } + + fn end(value: f32, index: usize) -> Event { + Event { + value: Au::from_f32_px(value), + item_index: ItemIndex(index), + kind: EventKind::EndClip, + } + } + + fn region(value: f32) -> Event { + Event { + value: Au::from_f32_px(value), + kind: EventKind::BeginRegion, + item_index: ItemIndex(usize::MAX), + } + } + + fn update( + &self, + flag: ItemFlags, + items: &mut [Item], + region: &mut usize, + ) { + let is_active = match self.kind { + EventKind::BeginClip => true, + EventKind::EndClip => false, + EventKind::BeginRegion => { + *region += 1; + return; + } + }; + + items[self.item_index.0].flags.set(flag, is_active); + } +} + +// An item that provides some kind of clip region (either +// a clip in/out rect, or a mask region). +#[derive(Debug)] +struct Item { + rect: LayoutRect, + mode: Option, + flags: ItemFlags, +} + +impl Item { + fn new( + rect: LayoutRect, + mode: Option, + has_mask: bool, + ) -> Item { + let flags = if has_mask { + ItemFlags::HAS_MASK + } else { + ItemFlags::empty() + }; + + Item { + rect, + mode, + flags, + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd)] +struct ItemIndex(usize); + +// The main public interface to the segment module. +pub struct SegmentBuilder { + items: Vec, + inner_rect: Option, + bounding_rect: Option, + has_interesting_clips: bool, + + #[cfg(debug_assertions)] + initialized: bool, +} + +impl SegmentBuilder { + // Create a new segment builder, supplying the primitive + // local rect and associated local clip rect. + pub fn new() -> SegmentBuilder { + SegmentBuilder { + items: Vec::with_capacity(4), + bounding_rect: None, + inner_rect: None, + has_interesting_clips: false, + #[cfg(debug_assertions)] + initialized: false, + } + } + + pub fn initialize( + &mut self, + local_rect: LayoutRect, + inner_rect: Option, + local_clip_rect: LayoutRect, + ) { + self.items.clear(); + self.inner_rect = inner_rect; + self.bounding_rect = Some(local_rect); + + self.push_clip_rect(local_rect, None, ClipMode::Clip); + self.push_clip_rect(local_clip_rect, None, ClipMode::Clip); + + // This must be set after the push_clip_rect calls above, since we + // want to skip segment building if those are the only clips. + self.has_interesting_clips = false; + + #[cfg(debug_assertions)] + { + self.initialized = true; + } + } + + // Push a region defined by an inner and outer rect where there + // is a mask required. This ensures that segments which intersect + // with these areas will get a clip mask task allocated. This + // is currently used to mark where a box-shadow region can affect + // the pixels of a clip-mask. It might be useful for other types + // such as dashed and dotted borders in the future. + pub fn push_mask_region( + &mut self, + outer_rect: LayoutRect, + inner_rect: LayoutRect, + inner_clip_mode: Option, + ) { + self.has_interesting_clips = true; + + if !inner_rect.is_well_formed_and_nonempty() { + self.items.push(Item::new( + outer_rect, + None, + true + )); + return; + } + + debug_assert!(outer_rect.contains_rect(&inner_rect)); + + let p0 = outer_rect.origin; + let p1 = inner_rect.origin; + let p2 = inner_rect.bottom_right(); + let p3 = outer_rect.bottom_right(); + + let segments = &[ + LayoutRect::new( + LayoutPoint::new(p0.x, p0.y), + LayoutSize::new(p1.x - p0.x, p1.y - p0.y), + ), + LayoutRect::new( + LayoutPoint::new(p2.x, p0.y), + LayoutSize::new(p3.x - p2.x, p1.y - p0.y), + ), + LayoutRect::new( + LayoutPoint::new(p2.x, p2.y), + LayoutSize::new(p3.x - p2.x, p3.y - p2.y), + ), + LayoutRect::new( + LayoutPoint::new(p0.x, p2.y), + LayoutSize::new(p1.x - p0.x, p3.y - p2.y), + ), + LayoutRect::new( + LayoutPoint::new(p1.x, p0.y), + LayoutSize::new(p2.x - p1.x, p1.y - p0.y), + ), + LayoutRect::new( + LayoutPoint::new(p2.x, p1.y), + LayoutSize::new(p3.x - p2.x, p2.y - p1.y), + ), + LayoutRect::new( + LayoutPoint::new(p1.x, p2.y), + LayoutSize::new(p2.x - p1.x, p3.y - p2.y), + ), + LayoutRect::new( + LayoutPoint::new(p0.x, p1.y), + LayoutSize::new(p1.x - p0.x, p2.y - p1.y), + ), + ]; + + for segment in segments { + self.items.push(Item::new( + *segment, + None, + true + )); + } + + if inner_clip_mode.is_some() { + self.items.push(Item::new( + inner_rect, + inner_clip_mode, + false, + )); + } + } + + // Push some kind of clipping region into the segment builder. + // If radius is None, it's a simple rect. + pub fn push_clip_rect( + &mut self, + rect: LayoutRect, + radius: Option, + mode: ClipMode, + ) { + self.has_interesting_clips = true; + + // Keep track of a minimal bounding rect for the set of + // segments that will be generated. + if mode == ClipMode::Clip { + self.bounding_rect = self.bounding_rect.and_then(|bounding_rect| { + bounding_rect.intersection(&rect) + }); + } + let mode = Some(mode); + + match radius { + Some(radius) => { + // For a rounded rect, try to create a nine-patch where there + // is a clip item for each corner, inner and edge region. + match extract_inner_rect_safe(&rect, &radius) { + Some(inner) => { + let p0 = rect.origin; + let p1 = inner.origin; + let p2 = inner.bottom_right(); + let p3 = rect.bottom_right(); + + let corner_segments = &[ + LayoutRect::new( + LayoutPoint::new(p0.x, p0.y), + LayoutSize::new(p1.x - p0.x, p1.y - p0.y), + ), + LayoutRect::new( + LayoutPoint::new(p2.x, p0.y), + LayoutSize::new(p3.x - p2.x, p1.y - p0.y), + ), + LayoutRect::new( + LayoutPoint::new(p2.x, p2.y), + LayoutSize::new(p3.x - p2.x, p3.y - p2.y), + ), + LayoutRect::new( + LayoutPoint::new(p0.x, p2.y), + LayoutSize::new(p1.x - p0.x, p3.y - p2.y), + ), + ]; + + for segment in corner_segments { + self.items.push(Item::new( + *segment, + mode, + true + )); + } + + let other_segments = &[ + LayoutRect::new( + LayoutPoint::new(p1.x, p0.y), + LayoutSize::new(p2.x - p1.x, p1.y - p0.y), + ), + LayoutRect::new( + LayoutPoint::new(p2.x, p1.y), + LayoutSize::new(p3.x - p2.x, p2.y - p1.y), + ), + LayoutRect::new( + LayoutPoint::new(p1.x, p2.y), + LayoutSize::new(p2.x - p1.x, p3.y - p2.y), + ), + LayoutRect::new( + LayoutPoint::new(p0.x, p1.y), + LayoutSize::new(p1.x - p0.x, p2.y - p1.y), + ), + LayoutRect::new( + LayoutPoint::new(p1.x, p1.y), + LayoutSize::new(p2.x - p1.x, p2.y - p1.y), + ), + ]; + + for segment in other_segments { + self.items.push(Item::new( + *segment, + mode, + false, + )); + } + } + None => { + // If we get here, we could not extract an inner rectangle + // for this clip region. This can occur in cases such as + // a rounded rect where the top-left and bottom-left radii + // result in overlapping rects. In that case, just create + // a single clip region for the entire rounded rect. + self.items.push(Item::new( + rect, + mode, + true, + )) + } + } + } + None => { + // For a simple rect, just create one clipping item. + self.items.push(Item::new( + rect, + mode, + false, + )) + } + } + } + + // Consume this segment builder and produce a list of segments. + pub fn build(&mut self, mut f: F) where F: FnMut(&Segment) { + #[cfg(debug_assertions)] + debug_assert!(self.initialized); + + #[cfg(debug_assertions)] + { + self.initialized = false; + } + + let bounding_rect = match self.bounding_rect { + Some(bounding_rect) => bounding_rect, + None => return, + }; + + if !self.has_interesting_clips { + // There were no additional clips added, so don't bother building segments. + // Just emit a single segment for the bounding rect of the primitive. + f(&Segment { + edge_flags: EdgeAaSegmentMask::all(), + region_x: 0, + region_y: 0, + has_mask: false, + rect: bounding_rect, + }); + return + } + + // First, filter out any items that don't intersect + // with the visible bounding rect. + self.items.retain(|item| item.rect.intersects(&bounding_rect)); + + // Create events for each item + let mut x_events : SmallVec<[Event; 4]> = SmallVec::new(); + let mut y_events : SmallVec<[Event; 4]> = SmallVec::new(); + + for (item_index, item) in self.items.iter().enumerate() { + let p0 = item.rect.origin; + let p1 = item.rect.bottom_right(); + + x_events.push(Event::begin(p0.x, item_index)); + x_events.push(Event::end(p1.x, item_index)); + y_events.push(Event::begin(p0.y, item_index)); + y_events.push(Event::end(p1.y, item_index)); + } + + // Add the region events, if provided. + if let Some(inner_rect) = self.inner_rect { + x_events.push(Event::region(inner_rect.origin.x)); + x_events.push(Event::region(inner_rect.origin.x + inner_rect.size.width)); + + y_events.push(Event::region(inner_rect.origin.y)); + y_events.push(Event::region(inner_rect.origin.y + inner_rect.size.height)); + } + + // Get the minimal bounding rect in app units. We will + // work in fixed point in order to avoid float precision + // error while handling events. + let p0 = LayoutPointAu::new( + Au::from_f32_px(bounding_rect.origin.x), + Au::from_f32_px(bounding_rect.origin.y), + ); + + let p1 = LayoutPointAu::new( + Au::from_f32_px(bounding_rect.origin.x + bounding_rect.size.width), + Au::from_f32_px(bounding_rect.origin.y + bounding_rect.size.height), + ); + + // Sort the events in ascending order. + x_events.sort(); + y_events.sort(); + + // Generate segments from the event lists, by sweeping the y-axis + // and then the x-axis for each event. This can generate a significant + // number of segments, but most importantly, it ensures that there are + // no t-junctions in the generated segments. It's probably possible + // to come up with more efficient segmentation algorithms, at least + // for simple / common cases. + + // Each coordinate is clamped to the bounds of the minimal + // bounding rect. This ensures that we don't generate segments + // outside that bounding rect, but does allow correctly handling + // clips where the clip region starts outside the minimal + // rect but still intersects with it. + + let mut prev_y = clamp(p0.y, y_events[0].value, p1.y); + let mut region_y = 0; + let mut segments : SmallVec<[_; 4]> = SmallVec::new(); + let mut x_count = 0; + let mut y_count = 0; + + for ey in &y_events { + let cur_y = clamp(p0.y, ey.value, p1.y); + + if cur_y != prev_y { + let mut prev_x = clamp(p0.x, x_events[0].value, p1.x); + let mut region_x = 0; + + for ex in &x_events { + let cur_x = clamp(p0.x, ex.value, p1.x); + + if cur_x != prev_x { + segments.push(emit_segment_if_needed( + prev_x, + prev_y, + cur_x, + cur_y, + region_x, + region_y, + &self.items, + )); + + prev_x = cur_x; + if y_count == 0 { + x_count += 1; + } + } + + ex.update( + ItemFlags::X_ACTIVE, + &mut self.items, + &mut region_x, + ); + } + + prev_y = cur_y; + y_count += 1; + } + + ey.update( + ItemFlags::Y_ACTIVE, + &mut self.items, + &mut region_y, + ); + } + + // Run user supplied closure for each valid segment. + debug_assert_eq!(segments.len(), x_count * y_count); + for y in 0 .. y_count { + for x in 0 .. x_count { + let mut edge_flags = EdgeAaSegmentMask::empty(); + + if x == 0 || segments[y * x_count + x - 1].is_none() { + edge_flags |= EdgeAaSegmentMask::LEFT; + } + if x == x_count-1 || segments[y * x_count + x + 1].is_none() { + edge_flags |= EdgeAaSegmentMask::RIGHT; + } + if y == 0 || segments[(y-1) * x_count + x].is_none() { + edge_flags |= EdgeAaSegmentMask::TOP; + } + if y == y_count-1 || segments[(y+1) * x_count + x].is_none() { + edge_flags |= EdgeAaSegmentMask::BOTTOM; + } + + if let Some(ref mut segment) = segments[y * x_count + x] { + segment.edge_flags = edge_flags; + f(segment); + } + } + } + } +} + +fn clamp(low: Au, value: Au, high: Au) -> Au { + value.max(low).min(high) +} + +fn emit_segment_if_needed( + x0: Au, + y0: Au, + x1: Au, + y1: Au, + region_x: usize, + region_y: usize, + items: &[Item], +) -> Option { + debug_assert!(x1 > x0); + debug_assert!(y1 > y0); + + // TODO(gw): Don't scan the whole list of items for + // each segment rect. Store active list + // in a hash set or similar if this ever + // shows up in a profile. + let mut has_clip_mask = false; + + for item in items { + if item.flags.contains(ItemFlags::X_ACTIVE | ItemFlags::Y_ACTIVE) { + has_clip_mask |= item.flags.contains(ItemFlags::HAS_MASK); + + if item.mode == Some(ClipMode::ClipOut) && !item.flags.contains(ItemFlags::HAS_MASK) { + return None; + } + } + } + + let segment_rect = LayoutRect::new( + LayoutPoint::new( + x0.to_f32_px(), + y0.to_f32_px(), + ), + LayoutSize::new( + (x1 - x0).to_f32_px(), + (y1 - y0).to_f32_px(), + ), + ); + + Some(Segment { + rect: segment_rect, + has_mask: has_clip_mask, + edge_flags: EdgeAaSegmentMask::empty(), + region_x, + region_y, + }) +} + +#[cfg(test)] +mod test { + use api::{BorderRadius, ClipMode, EdgeAaSegmentMask}; + use api::units::{LayoutPoint, LayoutRect, LayoutSize}; + use super::{Segment, SegmentBuilder}; + use std::cmp; + + fn rect(x0: f32, y0: f32, x1: f32, y1: f32) -> LayoutRect { + LayoutRect::new( + LayoutPoint::new(x0, y0), + LayoutSize::new(x1-x0, y1-y0), + ) + } + + fn seg( + x0: f32, + y0: f32, + x1: f32, + y1: f32, + has_mask: bool, + edge_flags: Option, + ) -> Segment { + seg_region(x0, y0, x1, y1, 0, 0, has_mask, edge_flags) + } + + fn seg_region( + x0: f32, + y0: f32, + x1: f32, + y1: f32, + region_x: usize, + region_y: usize, + has_mask: bool, + edge_flags: Option, + ) -> Segment { + Segment { + rect: LayoutRect::new( + LayoutPoint::new(x0, y0), + LayoutSize::new(x1-x0, y1-y0), + ), + has_mask, + edge_flags: edge_flags.unwrap_or(EdgeAaSegmentMask::empty()), + region_x, + region_y, + } + } + + fn segment_sorter(s0: &Segment, s1: &Segment) -> cmp::Ordering { + let r0 = &s0.rect; + let r1 = &s1.rect; + + ( + (r0.origin.x, r0.origin.y, r0.size.width, r0.size.height) + ).partial_cmp(& + (r1.origin.x, r1.origin.y, r1.size.width, r1.size.height) + ).unwrap() + } + + fn seg_test( + local_rect: LayoutRect, + inner_rect: Option, + local_clip_rect: LayoutRect, + clips: &[(LayoutRect, Option, ClipMode)], + expected_segments: &mut [Segment] + ) { + let mut sb = SegmentBuilder::new(); + sb.initialize( + local_rect, + inner_rect, + local_clip_rect, + ); + sb.push_clip_rect(local_rect, None, ClipMode::Clip); + sb.push_clip_rect(local_clip_rect, None, ClipMode::Clip); + let mut segments = Vec::new(); + for &(rect, radius, mode) in clips { + sb.push_clip_rect(rect, radius, mode); + } + sb.build(|segment| { + segments.push(Segment { + ..*segment + }); + }); + segments.sort_by(segment_sorter); + expected_segments.sort_by(segment_sorter); + assert_eq!( + segments.len(), + expected_segments.len(), + "segments\n{:?}\nexpected\n{:?}\n", + segments, + expected_segments + ); + for (segment, expected) in segments.iter().zip(expected_segments.iter()) { + assert_eq!(segment, expected); + } + } + + #[test] + fn segment_empty() { + seg_test( + rect(0.0, 0.0, 0.0, 0.0), + None, + rect(0.0, 0.0, 0.0, 0.0), + &[], + &mut [], + ); + } + + #[test] + fn segment_single() { + seg_test( + rect(10.0, 20.0, 30.0, 40.0), + None, + rect(10.0, 20.0, 30.0, 40.0), + &[], + &mut [ + seg(10.0, 20.0, 30.0, 40.0, false, + Some(EdgeAaSegmentMask::LEFT | + EdgeAaSegmentMask::TOP | + EdgeAaSegmentMask::RIGHT | + EdgeAaSegmentMask::BOTTOM + ) + ), + ], + ); + } + + #[test] + fn segment_single_clip() { + seg_test( + rect(10.0, 20.0, 30.0, 40.0), + None, + rect(10.0, 20.0, 25.0, 35.0), + &[], + &mut [ + seg(10.0, 20.0, 25.0, 35.0, false, + Some(EdgeAaSegmentMask::LEFT | + EdgeAaSegmentMask::TOP | + EdgeAaSegmentMask::RIGHT | + EdgeAaSegmentMask::BOTTOM + ) + ), + ], + ); + } + + #[test] + fn segment_inner_clip() { + seg_test( + rect(10.0, 20.0, 30.0, 40.0), + None, + rect(15.0, 25.0, 25.0, 35.0), + &[], + &mut [ + seg(15.0, 25.0, 25.0, 35.0, false, + Some(EdgeAaSegmentMask::LEFT | + EdgeAaSegmentMask::TOP | + EdgeAaSegmentMask::RIGHT | + EdgeAaSegmentMask::BOTTOM + ) + ), + ], + ); + } + + #[test] + fn segment_outer_clip() { + seg_test( + rect(15.0, 25.0, 25.0, 35.0), + None, + rect(10.0, 20.0, 30.0, 40.0), + &[], + &mut [ + seg(15.0, 25.0, 25.0, 35.0, false, + Some(EdgeAaSegmentMask::LEFT | + EdgeAaSegmentMask::TOP | + EdgeAaSegmentMask::RIGHT | + EdgeAaSegmentMask::BOTTOM + ) + ), + ], + ); + } + + #[test] + fn segment_clip_int() { + seg_test( + rect(10.0, 20.0, 30.0, 40.0), + None, + rect(20.0, 10.0, 40.0, 30.0), + &[], + &mut [ + seg(20.0, 20.0, 30.0, 30.0, false, + Some(EdgeAaSegmentMask::LEFT | + EdgeAaSegmentMask::TOP | + EdgeAaSegmentMask::RIGHT | + EdgeAaSegmentMask::BOTTOM + ) + ), + ], + ); + } + + #[test] + fn segment_clip_disjoint() { + seg_test( + rect(10.0, 20.0, 30.0, 40.0), + None, + rect(30.0, 20.0, 50.0, 40.0), + &[], + &mut [], + ); + } + + #[test] + fn segment_clips() { + seg_test( + rect(0.0, 0.0, 100.0, 100.0), + None, + rect(-1000.0, -1000.0, 1000.0, 1000.0), + &[ + (rect(20.0, 20.0, 40.0, 40.0), None, ClipMode::Clip), + (rect(40.0, 20.0, 60.0, 40.0), None, ClipMode::Clip), + ], + &mut [ + ], + ); + } + + #[test] + fn segment_rounded_clip() { + seg_test( + rect(0.0, 0.0, 100.0, 100.0), + None, + rect(-1000.0, -1000.0, 1000.0, 1000.0), + &[ + (rect(20.0, 20.0, 60.0, 60.0), Some(BorderRadius::uniform(10.0)), ClipMode::Clip), + ], + &mut [ + // corners + seg(20.0, 20.0, 30.0, 30.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)), + seg(20.0, 50.0, 30.0, 60.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)), + seg(50.0, 20.0, 60.0, 30.0, true, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::TOP)), + seg(50.0, 50.0, 60.0, 60.0, true, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)), + + // inner + seg(30.0, 30.0, 50.0, 50.0, false, None), + + // edges + seg(30.0, 20.0, 50.0, 30.0, false, Some(EdgeAaSegmentMask::TOP)), + seg(30.0, 50.0, 50.0, 60.0, false, Some(EdgeAaSegmentMask::BOTTOM)), + seg(20.0, 30.0, 30.0, 50.0, false, Some(EdgeAaSegmentMask::LEFT)), + seg(50.0, 30.0, 60.0, 50.0, false, Some(EdgeAaSegmentMask::RIGHT)), + ], + ); + } + + #[test] + fn segment_clip_out() { + seg_test( + rect(0.0, 0.0, 100.0, 100.0), + None, + rect(-1000.0, -1000.0, 2000.0, 2000.0), + &[ + (rect(20.0, 20.0, 60.0, 60.0), None, ClipMode::ClipOut), + ], + &mut [ + seg(0.0, 0.0, 20.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT)), + seg(20.0, 0.0, 60.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM)), + seg(60.0, 0.0, 100.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT)), + + seg(0.0, 20.0, 20.0, 60.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT)), + seg(60.0, 20.0, 100.0, 60.0, false, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT)), + + seg(0.0, 60.0, 20.0, 100.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)), + seg(20.0, 60.0, 60.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP)), + seg(60.0, 60.0, 100.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT)), + ], + ); + } + + #[test] + fn segment_rounded_clip_out() { + seg_test( + rect(0.0, 0.0, 100.0, 100.0), + None, + rect(-1000.0, -1000.0, 2000.0, 2000.0), + &[ + (rect(20.0, 20.0, 60.0, 60.0), Some(BorderRadius::uniform(10.0)), ClipMode::ClipOut), + ], + &mut [ + // top row + seg(0.0, 0.0, 20.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT)), + seg(20.0, 0.0, 30.0, 20.0, false, Some(EdgeAaSegmentMask::TOP)), + seg(30.0, 0.0, 50.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM)), + seg(50.0, 0.0, 60.0, 20.0, false, Some(EdgeAaSegmentMask::TOP)), + seg(60.0, 0.0, 100.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT)), + + // left + seg(0.0, 20.0, 20.0, 30.0, false, Some(EdgeAaSegmentMask::LEFT)), + seg(0.0, 30.0, 20.0, 50.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT)), + seg(0.0, 50.0, 20.0, 60.0, false, Some(EdgeAaSegmentMask::LEFT)), + + // right + seg(60.0, 20.0, 100.0, 30.0, false, Some(EdgeAaSegmentMask::RIGHT)), + seg(60.0, 30.0, 100.0, 50.0, false, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT)), + seg(60.0, 50.0, 100.0, 60.0, false, Some(EdgeAaSegmentMask::RIGHT)), + + // bottom row + seg(0.0, 60.0, 20.0, 100.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)), + seg(20.0, 60.0, 30.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM)), + seg(30.0, 60.0, 50.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP)), + seg(50.0, 60.0, 60.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM)), + seg(60.0, 60.0, 100.0, 100.0, false, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)), + + // inner corners + seg(20.0, 20.0, 30.0, 30.0, true, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)), + seg(20.0, 50.0, 30.0, 60.0, true, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT)), + seg(50.0, 20.0, 60.0, 30.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)), + seg(50.0, 50.0, 60.0, 60.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)), + ], + ); + } + + #[test] + fn segment_clip_in_clip_out() { + seg_test( + rect(0.0, 0.0, 100.0, 100.0), + None, + rect(-1000.0, -1000.0, 2000.0, 2000.0), + &[ + (rect(20.0, 20.0, 60.0, 60.0), None, ClipMode::Clip), + (rect(50.0, 50.0, 80.0, 80.0), None, ClipMode::ClipOut), + ], + &mut [ + seg(20.0, 20.0, 50.0, 50.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)), + seg(50.0, 20.0, 60.0, 50.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)), + seg(20.0, 50.0, 50.0, 60.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT)), + ], + ); + } + + #[test] + fn segment_rounded_clip_overlap() { + seg_test( + rect(0.0, 0.0, 100.0, 100.0), + None, + rect(0.0, 0.0, 100.0, 100.0), + &[ + (rect(0.0, 0.0, 10.0, 10.0), None, ClipMode::ClipOut), + (rect(0.0, 0.0, 100.0, 100.0), Some(BorderRadius::uniform(10.0)), ClipMode::Clip), + ], + &mut [ + // corners + seg(0.0, 90.0, 10.0, 100.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)), + seg(90.0, 0.0, 100.0, 10.0, true, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::TOP)), + seg(90.0, 90.0, 100.0, 100.0, true, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)), + + // inner + seg(10.0, 10.0, 90.0, 90.0, false, None), + + // edges + seg(10.0, 0.0, 90.0, 10.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT)), + seg(10.0, 90.0, 90.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM)), + seg(0.0, 10.0, 10.0, 90.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)), + seg(90.0, 10.0, 100.0, 90.0, false, Some(EdgeAaSegmentMask::RIGHT)), + ], + ); + } + + #[test] + fn segment_rounded_clip_overlap_reverse() { + seg_test( + rect(0.0, 0.0, 100.0, 100.0), + None, + rect(0.0, 0.0, 100.0, 100.0), + &[ + (rect(10.0, 10.0, 90.0, 90.0), None, ClipMode::Clip), + (rect(0.0, 0.0, 100.0, 100.0), Some(BorderRadius::uniform(10.0)), ClipMode::Clip), + ], + &mut [ + seg(10.0, 10.0, 90.0, 90.0, false, + Some(EdgeAaSegmentMask::LEFT | + EdgeAaSegmentMask::TOP | + EdgeAaSegmentMask::RIGHT | + EdgeAaSegmentMask::BOTTOM + ) + ), + ], + ); + } + + #[test] + fn segment_clip_in_clip_out_overlap() { + seg_test( + rect(0.0, 0.0, 100.0, 100.0), + None, + rect(0.0, 0.0, 100.0, 100.0), + &[ + (rect(10.0, 10.0, 90.0, 90.0), None, ClipMode::Clip), + (rect(10.0, 10.0, 90.0, 90.0), None, ClipMode::ClipOut), + ], + &mut [ + ], + ); + } + + #[test] + fn segment_event_order() { + seg_test( + rect(0.0, 0.0, 100.0, 100.0), + None, + rect(0.0, 0.0, 100.0, 100.0), + &[ + (rect(0.0, 0.0, 100.0, 90.0), None, ClipMode::ClipOut), + ], + &mut [ + seg(0.0, 90.0, 100.0, 100.0, false, Some( + EdgeAaSegmentMask::LEFT | + EdgeAaSegmentMask::RIGHT | + EdgeAaSegmentMask::BOTTOM | + EdgeAaSegmentMask::TOP + )), + ], + ); + } + + #[test] + fn segment_region_simple() { + seg_test( + rect(0.0, 0.0, 100.0, 100.0), + Some(rect(20.0, 40.0, 60.0, 80.0)), + rect(0.0, 0.0, 100.0, 100.0), + &[ + ], + &mut [ + seg_region( + 0.0, 0.0, + 20.0, 40.0, + 0, 0, + false, + Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP) + ), + + seg_region( + 20.0, 0.0, + 60.0, 40.0, + 1, 0, + false, + Some(EdgeAaSegmentMask::TOP) + ), + + seg_region( + 60.0, 0.0, + 100.0, 40.0, + 2, 0, + false, + Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT) + ), + + seg_region( + 0.0, 40.0, + 20.0, 80.0, + 0, 1, + false, + Some(EdgeAaSegmentMask::LEFT) + ), + + seg_region( + 20.0, 40.0, + 60.0, 80.0, + 1, 1, + false, + None, + ), + + seg_region( + 60.0, 40.0, + 100.0, 80.0, + 2, 1, + false, + Some(EdgeAaSegmentMask::RIGHT) + ), + + seg_region( + 0.0, 80.0, + 20.0, 100.0, + 0, 2, + false, + Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM) + ), + + seg_region( + 20.0, 80.0, + 60.0, 100.0, + 1, 2, + false, + Some(EdgeAaSegmentMask::BOTTOM), + ), + + seg_region( + 60.0, 80.0, + 100.0, 100.0, + 2, 2, + false, + Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM) + ), + + ], + ); + } + + #[test] + fn segment_region_clip() { + seg_test( + rect(0.0, 0.0, 100.0, 100.0), + Some(rect(20.0, 40.0, 60.0, 80.0)), + rect(0.0, 0.0, 100.0, 100.0), + &[ + (rect(0.0, 0.0, 100.0, 90.0), None, ClipMode::ClipOut), + ], + &mut [ + seg_region( + 0.0, 90.0, + 20.0, 100.0, + 0, 2, + false, + Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP) + ), + + seg_region( + 20.0, 90.0, + 60.0, 100.0, + 1, 2, + false, + Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP), + ), + + seg_region( + 60.0, 90.0, + 100.0, 100.0, + 2, 2, + false, + Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP) + ), + + ], + ); + } + + #[test] + fn segment_region_clip2() { + seg_test( + rect(0.0, 0.0, 100.0, 100.0), + Some(rect(20.0, 20.0, 80.0, 80.0)), + rect(0.0, 0.0, 100.0, 100.0), + &[ + (rect(20.0, 20.0, 100.0, 100.0), None, ClipMode::ClipOut), + ], + &mut [ + seg_region( + 0.0, 0.0, + 20.0, 20.0, + 0, 0, + false, + Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP) + ), + + seg_region( + 20.0, 0.0, + 80.0, 20.0, + 1, 0, + false, + Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM), + ), + + seg_region( + 80.0, 0.0, + 100.0, 20.0, + 2, 0, + false, + Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM) + ), + + seg_region( + 0.0, 20.0, + 20.0, 80.0, + 0, 1, + false, + Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT) + ), + + seg_region( + 0.0, 80.0, + 20.0, 100.0, + 0, 2, + false, + Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT) + ), + ], + ); + } + + #[test] + fn segment_region_clip3() { + seg_test( + rect(0.0, 0.0, 100.0, 100.0), + Some(rect(20.0, 20.0, 80.0, 80.0)), + rect(0.0, 0.0, 100.0, 100.0), + &[ + (rect(10.0, 10.0, 30.0, 30.0), None, ClipMode::Clip), + ], + &mut [ + seg_region( + 10.0, 10.0, + 20.0, 20.0, + 0, 0, + false, + Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT), + ), + + seg_region( + 20.0, 10.0, + 30.0, 20.0, + 1, 0, + false, + Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT), + ), + + seg_region( + 10.0, 20.0, + 20.0, 30.0, + 0, 1, + false, + Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::LEFT), + ), + + seg_region( + 20.0, 20.0, + 30.0, 30.0, + 1, 1, + false, + Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT), + ), + ], + ); + } +} diff --git a/third_party/webrender/webrender/src/shade.rs b/third_party/webrender/webrender/src/shade.rs new file mode 100644 index 00000000000..cce58259015 --- /dev/null +++ b/third_party/webrender/webrender/src/shade.rs @@ -0,0 +1,1189 @@ +/* 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 crate::batch::{BatchKey, BatchKind, BrushBatchKind, BatchFeatures}; +use crate::composite::CompositeSurfaceFormat; +use crate::device::{Device, Program, ShaderError}; +use euclid::default::Transform3D; +use crate::glyph_rasterizer::GlyphFormat; +use crate::renderer::{ + desc, + BlendMode, DebugFlags, ImageBufferKind, RendererError, RendererOptions, + TextureSampler, VertexArrayKind, ShaderPrecacheFlags, +}; + +use gleam::gl::GlType; +use time::precise_time_ns; + +use std::cell::RefCell; +use std::rc::Rc; + +use webrender_build::shader::{ShaderFeatures, ShaderFeatureFlags, get_shader_features}; + +impl ImageBufferKind { + pub(crate) fn get_feature_string(&self) -> &'static str { + match *self { + ImageBufferKind::Texture2D => "TEXTURE_2D", + ImageBufferKind::Texture2DArray => "", + ImageBufferKind::TextureRect => "TEXTURE_RECT", + ImageBufferKind::TextureExternal => "TEXTURE_EXTERNAL", + } + } + + fn has_platform_support(&self, gl_type: &GlType) -> bool { + match (*self, gl_type) { + (ImageBufferKind::Texture2D, _) => true, + (ImageBufferKind::Texture2DArray, _) => true, + (ImageBufferKind::TextureRect, &GlType::Gles) => false, + (ImageBufferKind::TextureRect, &GlType::Gl) => true, + (ImageBufferKind::TextureExternal, &GlType::Gles) => true, + (ImageBufferKind::TextureExternal, &GlType::Gl) => false, + } + } +} + +pub const IMAGE_BUFFER_KINDS: [ImageBufferKind; 4] = [ + ImageBufferKind::Texture2D, + ImageBufferKind::TextureRect, + ImageBufferKind::TextureExternal, + ImageBufferKind::Texture2DArray, +]; + +const ADVANCED_BLEND_FEATURE: &str = "ADVANCED_BLEND"; +const ALPHA_FEATURE: &str = "ALPHA_PASS"; +const DEBUG_OVERDRAW_FEATURE: &str = "DEBUG_OVERDRAW"; +const DITHERING_FEATURE: &str = "DITHERING"; +const DUAL_SOURCE_FEATURE: &str = "DUAL_SOURCE_BLENDING"; +const FAST_PATH_FEATURE: &str = "FAST_PATH"; +const PIXEL_LOCAL_STORAGE_FEATURE: &str = "PIXEL_LOCAL_STORAGE"; + +pub(crate) enum ShaderKind { + Primitive, + Cache(VertexArrayKind), + ClipCache, + Brush, + Text, + #[allow(dead_code)] + VectorStencil, + #[allow(dead_code)] + VectorCover, + Resolve, + Composite, + Clear, +} + +pub struct LazilyCompiledShader { + program: Option, + name: &'static str, + kind: ShaderKind, + cached_projection: Transform3D, + features: Vec<&'static str>, +} + +impl LazilyCompiledShader { + pub(crate) fn new( + kind: ShaderKind, + name: &'static str, + features: &[&'static str], + device: &mut Device, + precache_flags: ShaderPrecacheFlags, + shader_list: &ShaderFeatures, + ) -> Result { + let mut shader = LazilyCompiledShader { + program: None, + name, + kind, + //Note: this isn't really the default state, but there is no chance + // an actual projection passed here would accidentally match. + cached_projection: Transform3D::identity(), + features: features.to_vec(), + }; + + // Ensure this shader config is in the available shader list so that we get + // alerted if the list gets out-of-date when shaders or features are added. + let config = features.join(","); + assert!( + shader_list.get(name).map_or(false, |f| f.contains(&config)), + "shader \"{}\" with features \"{}\" not in available shader list", + name, + config, + ); + + if precache_flags.intersects(ShaderPrecacheFlags::ASYNC_COMPILE | ShaderPrecacheFlags::FULL_COMPILE) { + let t0 = precise_time_ns(); + shader.get_internal(device, precache_flags)?; + let t1 = precise_time_ns(); + debug!("[C: {:.1} ms ] Precache {} {:?}", + (t1 - t0) as f64 / 1000000.0, + name, + features + ); + } + + Ok(shader) + } + + pub fn bind( + &mut self, + device: &mut Device, + projection: &Transform3D, + renderer_errors: &mut Vec, + ) { + let update_projection = self.cached_projection != *projection; + let program = match self.get_internal(device, ShaderPrecacheFlags::FULL_COMPILE) { + Ok(program) => program, + Err(e) => { + renderer_errors.push(RendererError::from(e)); + return; + } + }; + device.bind_program(program); + if update_projection { + device.set_uniforms(program, projection); + // thanks NLL for this (`program` technically borrows `self`) + self.cached_projection = *projection; + } + } + + fn get_internal( + &mut self, + device: &mut Device, + precache_flags: ShaderPrecacheFlags, + ) -> Result<&mut Program, ShaderError> { + if self.program.is_none() { + let program = match self.kind { + ShaderKind::Primitive | ShaderKind::Brush | ShaderKind::Text | ShaderKind::Resolve | ShaderKind::Clear => { + create_prim_shader( + self.name, + device, + &self.features, + ) + } + ShaderKind::Cache(..) => { + create_prim_shader( + self.name, + device, + &self.features, + ) + } + ShaderKind::VectorStencil => { + create_prim_shader( + self.name, + device, + &self.features, + ) + } + ShaderKind::VectorCover => { + create_prim_shader( + self.name, + device, + &self.features, + ) + } + ShaderKind::Composite => { + create_prim_shader( + self.name, + device, + &self.features, + ) + } + ShaderKind::ClipCache => { + create_clip_shader( + self.name, + device, + &self.features, + ) + } + }; + self.program = Some(program?); + } + + let program = self.program.as_mut().unwrap(); + + if precache_flags.contains(ShaderPrecacheFlags::FULL_COMPILE) && !program.is_initialized() { + let vertex_format = match self.kind { + ShaderKind::Primitive | + ShaderKind::Brush | + ShaderKind::Text => VertexArrayKind::Primitive, + ShaderKind::Cache(format) => format, + ShaderKind::VectorStencil => VertexArrayKind::VectorStencil, + ShaderKind::VectorCover => VertexArrayKind::VectorCover, + ShaderKind::ClipCache => VertexArrayKind::Clip, + ShaderKind::Resolve => VertexArrayKind::Resolve, + ShaderKind::Composite => VertexArrayKind::Composite, + ShaderKind::Clear => VertexArrayKind::Clear, + }; + + let vertex_descriptor = match vertex_format { + VertexArrayKind::Primitive => &desc::PRIM_INSTANCES, + VertexArrayKind::LineDecoration => &desc::LINE, + VertexArrayKind::Gradient => &desc::GRADIENT, + VertexArrayKind::Blur => &desc::BLUR, + VertexArrayKind::Clip => &desc::CLIP, + VertexArrayKind::VectorStencil => &desc::VECTOR_STENCIL, + VertexArrayKind::VectorCover => &desc::VECTOR_COVER, + VertexArrayKind::Border => &desc::BORDER, + VertexArrayKind::Scale => &desc::SCALE, + VertexArrayKind::Resolve => &desc::RESOLVE, + VertexArrayKind::SvgFilter => &desc::SVG_FILTER, + VertexArrayKind::Composite => &desc::COMPOSITE, + VertexArrayKind::Clear => &desc::CLEAR, + }; + + device.link_program(program, vertex_descriptor)?; + device.bind_program(program); + match self.kind { + ShaderKind::ClipCache => { + device.bind_shader_samplers( + &program, + &[ + ("sColor0", TextureSampler::Color0), + ("sTransformPalette", TextureSampler::TransformPalette), + ("sRenderTasks", TextureSampler::RenderTasks), + ("sGpuCache", TextureSampler::GpuCache), + ("sPrimitiveHeadersF", TextureSampler::PrimitiveHeadersF), + ("sPrimitiveHeadersI", TextureSampler::PrimitiveHeadersI), + ], + ); + } + _ => { + device.bind_shader_samplers( + &program, + &[ + ("sColor0", TextureSampler::Color0), + ("sColor1", TextureSampler::Color1), + ("sColor2", TextureSampler::Color2), + ("sDither", TextureSampler::Dither), + ("sPrevPassAlpha", TextureSampler::PrevPassAlpha), + ("sPrevPassColor", TextureSampler::PrevPassColor), + ("sTransformPalette", TextureSampler::TransformPalette), + ("sRenderTasks", TextureSampler::RenderTasks), + ("sGpuCache", TextureSampler::GpuCache), + ("sPrimitiveHeadersF", TextureSampler::PrimitiveHeadersF), + ("sPrimitiveHeadersI", TextureSampler::PrimitiveHeadersI), + ], + ); + } + } + } + + Ok(program) + } + + fn deinit(self, device: &mut Device) { + if let Some(program) = self.program { + device.delete_program(program); + } + } +} + +// A brush shader supports two modes: +// opaque: +// Used for completely opaque primitives, +// or inside segments of partially +// opaque primitives. Assumes no need +// for clip masks, AA etc. +// alpha: +// Used for brush primitives in the alpha +// pass. Assumes that AA should be applied +// along the primitive edge, and also that +// clip mask is present. +struct BrushShader { + opaque: LazilyCompiledShader, + alpha: LazilyCompiledShader, + advanced_blend: Option, + dual_source: Option, + debug_overdraw: LazilyCompiledShader, +} + +impl BrushShader { + fn new( + name: &'static str, + device: &mut Device, + features: &[&'static str], + precache_flags: ShaderPrecacheFlags, + shader_list: &ShaderFeatures, + use_advanced_blend: bool, + use_dual_source: bool, + use_pixel_local_storage: bool, + ) -> Result { + let opaque = LazilyCompiledShader::new( + ShaderKind::Brush, + name, + features, + device, + precache_flags, + &shader_list, + )?; + + let mut alpha_features = features.to_vec(); + alpha_features.push(ALPHA_FEATURE); + if use_pixel_local_storage { + alpha_features.push(PIXEL_LOCAL_STORAGE_FEATURE); + } + + let alpha = LazilyCompiledShader::new( + ShaderKind::Brush, + name, + &alpha_features, + device, + precache_flags, + &shader_list, + )?; + + let advanced_blend = if use_advanced_blend { + let mut advanced_blend_features = alpha_features.to_vec(); + advanced_blend_features.push(ADVANCED_BLEND_FEATURE); + + let shader = LazilyCompiledShader::new( + ShaderKind::Brush, + name, + &advanced_blend_features, + device, + precache_flags, + &shader_list, + )?; + + Some(shader) + } else { + None + }; + + let dual_source = if use_dual_source { + let mut dual_source_features = alpha_features.to_vec(); + dual_source_features.push(DUAL_SOURCE_FEATURE); + + let shader = LazilyCompiledShader::new( + ShaderKind::Brush, + name, + &dual_source_features, + device, + precache_flags, + &shader_list, + )?; + + Some(shader) + } else { + None + }; + + let mut debug_overdraw_features = features.to_vec(); + debug_overdraw_features.push(DEBUG_OVERDRAW_FEATURE); + + let debug_overdraw = LazilyCompiledShader::new( + ShaderKind::Brush, + name, + &debug_overdraw_features, + device, + precache_flags, + &shader_list, + )?; + + Ok(BrushShader { + opaque, + alpha, + advanced_blend, + dual_source, + debug_overdraw, + }) + } + + fn get(&mut self, blend_mode: BlendMode, debug_flags: DebugFlags) + -> &mut LazilyCompiledShader { + match blend_mode { + _ if debug_flags.contains(DebugFlags::SHOW_OVERDRAW) => &mut self.debug_overdraw, + BlendMode::None => &mut self.opaque, + BlendMode::Alpha | + BlendMode::PremultipliedAlpha | + BlendMode::PremultipliedDestOut | + BlendMode::SubpixelConstantTextColor(..) | + BlendMode::SubpixelWithBgColor => &mut self.alpha, + BlendMode::Advanced(_) => { + self.advanced_blend + .as_mut() + .expect("bug: no advanced blend shader loaded") + } + BlendMode::SubpixelDualSource => { + self.dual_source + .as_mut() + .expect("bug: no dual source shader loaded") + } + } + } + + fn deinit(self, device: &mut Device) { + self.opaque.deinit(device); + self.alpha.deinit(device); + if let Some(advanced_blend) = self.advanced_blend { + advanced_blend.deinit(device); + } + if let Some(dual_source) = self.dual_source { + dual_source.deinit(device); + } + self.debug_overdraw.deinit(device); + } +} + +pub struct TextShader { + simple: LazilyCompiledShader, + glyph_transform: LazilyCompiledShader, + debug_overdraw: LazilyCompiledShader, +} + +impl TextShader { + fn new( + name: &'static str, + device: &mut Device, + features: &[&'static str], + precache_flags: ShaderPrecacheFlags, + shader_list: &ShaderFeatures, + ) -> Result { + let mut simple_features = features.to_vec(); + simple_features.push("ALPHA_PASS"); + + let simple = LazilyCompiledShader::new( + ShaderKind::Text, + name, + &simple_features, + device, + precache_flags, + &shader_list, + )?; + + let mut glyph_transform_features = features.to_vec(); + glyph_transform_features.push("GLYPH_TRANSFORM"); + glyph_transform_features.push("ALPHA_PASS"); + + let glyph_transform = LazilyCompiledShader::new( + ShaderKind::Text, + name, + &glyph_transform_features, + device, + precache_flags, + &shader_list, + )?; + + let mut debug_overdraw_features = features.to_vec(); + debug_overdraw_features.push("DEBUG_OVERDRAW"); + + let debug_overdraw = LazilyCompiledShader::new( + ShaderKind::Text, + name, + &debug_overdraw_features, + device, + precache_flags, + &shader_list, + )?; + + Ok(TextShader { simple, glyph_transform, debug_overdraw }) + } + + pub fn get( + &mut self, + glyph_format: GlyphFormat, + debug_flags: DebugFlags, + ) -> &mut LazilyCompiledShader { + match glyph_format { + _ if debug_flags.contains(DebugFlags::SHOW_OVERDRAW) => &mut self.debug_overdraw, + GlyphFormat::Alpha | + GlyphFormat::Subpixel | + GlyphFormat::Bitmap | + GlyphFormat::ColorBitmap => &mut self.simple, + GlyphFormat::TransformedAlpha | + GlyphFormat::TransformedSubpixel => &mut self.glyph_transform, + } + } + + fn deinit(self, device: &mut Device) { + self.simple.deinit(device); + self.glyph_transform.deinit(device); + self.debug_overdraw.deinit(device); + } +} + +fn create_prim_shader( + name: &'static str, + device: &mut Device, + features: &[&'static str], +) -> Result { + debug!("PrimShader {}", name); + + device.create_program(name, features) +} + +fn create_clip_shader( + name: &'static str, + device: &mut Device, + features: &[&'static str], +) -> Result { + debug!("ClipShader {}", name); + + device.create_program(name, features) +} + +// NB: If you add a new shader here, make sure to deinitialize it +// in `Shaders::deinit()` below. +pub struct Shaders { + // These are "cache shaders". These shaders are used to + // draw intermediate results to cache targets. The results + // of these shaders are then used by the primitive shaders. + pub cs_blur_a8: LazilyCompiledShader, + pub cs_blur_rgba8: LazilyCompiledShader, + pub cs_border_segment: LazilyCompiledShader, + pub cs_border_solid: LazilyCompiledShader, + pub cs_scale: LazilyCompiledShader, + pub cs_line_decoration: LazilyCompiledShader, + pub cs_gradient: LazilyCompiledShader, + pub cs_svg_filter: LazilyCompiledShader, + + // Brush shaders + brush_solid: BrushShader, + brush_image: Vec>, + brush_fast_image: Vec>, + brush_blend: BrushShader, + brush_mix_blend: BrushShader, + brush_yuv_image: Vec>, + brush_conic_gradient: BrushShader, + brush_radial_gradient: BrushShader, + brush_linear_gradient: BrushShader, + brush_opacity: BrushShader, + + /// These are "cache clip shaders". These shaders are used to + /// draw clip instances into the cached clip mask. The results + /// of these shaders are also used by the primitive shaders. + pub cs_clip_rectangle_slow: LazilyCompiledShader, + pub cs_clip_rectangle_fast: LazilyCompiledShader, + pub cs_clip_box_shadow: LazilyCompiledShader, + pub cs_clip_image: LazilyCompiledShader, + + // The are "primitive shaders". These shaders draw and blend + // final results on screen. They are aware of tile boundaries. + // Most draw directly to the framebuffer, but some use inputs + // from the cache shaders to draw. Specifically, the box + // shadow primitive shader stretches the box shadow cache + // output, and the cache_image shader blits the results of + // a cache shader (e.g. blur) to the screen. + pub ps_text_run: TextShader, + pub ps_text_run_dual_source: Option, + + // Helper shaders for pixel local storage render paths. + // pls_init: Initialize pixel local storage, based on current framebuffer value. + // pls_resolve: Convert pixel local storage, writing out to fragment value. + pub pls_init: Option, + pub pls_resolve: Option, + + ps_split_composite: LazilyCompiledShader, + pub ps_clear: LazilyCompiledShader, + + // Composite shaders. These are very simple shaders used to composite + // picture cache tiles into the framebuffer on platforms that do not have an + // OS Compositor (or we cannot use it). Such an OS Compositor (such as + // DirectComposite or CoreAnimation) handles the composition of the picture + // cache tiles at a lower level (e.g. in DWM for Windows); in that case we + // directly hand the picture cache surfaces over to the OS Compositor, and + // our own Composite shaders below never run. + // To composite external (RGB) surfaces we need various permutations of + // shaders with WR_FEATURE flags on or off based on the type of image + // buffer we're sourcing from (see IMAGE_BUFFER_KINDS). + pub composite_rgba: Vec>, + // The same set of composite shaders but with WR_FEATURE_YUV added. + pub composite_yuv: Vec>, +} + +impl Shaders { + pub fn new( + device: &mut Device, + gl_type: GlType, + options: &RendererOptions, + ) -> Result { + let use_pixel_local_storage = device + .get_capabilities() + .supports_pixel_local_storage; + // If using PLS, we disable all subpixel AA implicitly. Subpixel AA is always + // disabled on mobile devices anyway, due to uncertainty over the subpixel + // layout configuration. + let use_dual_source_blending = + device.get_capabilities().supports_dual_source_blending && + options.allow_dual_source_blending && + !use_pixel_local_storage; + let use_advanced_blend_equation = + device.get_capabilities().supports_advanced_blend_equation && + options.allow_advanced_blend_equation; + + let mut shader_flags = match gl_type { + GlType::Gl => ShaderFeatureFlags::GL, + GlType::Gles => ShaderFeatureFlags::GLES | ShaderFeatureFlags::TEXTURE_EXTERNAL, + }; + shader_flags.set(ShaderFeatureFlags::PIXEL_LOCAL_STORAGE, use_pixel_local_storage); + shader_flags.set(ShaderFeatureFlags::ADVANCED_BLEND_EQUATION, use_advanced_blend_equation); + shader_flags.set(ShaderFeatureFlags::DUAL_SOURCE_BLENDING, use_dual_source_blending); + shader_flags.set(ShaderFeatureFlags::DITHERING, options.enable_dithering); + let shader_list = get_shader_features(shader_flags); + + let brush_solid = BrushShader::new( + "brush_solid", + device, + &[], + options.precache_flags, + &shader_list, + false /* advanced blend */, + false /* dual source */, + use_pixel_local_storage, + )?; + + let brush_blend = BrushShader::new( + "brush_blend", + device, + &[], + options.precache_flags, + &shader_list, + false /* advanced blend */, + false /* dual source */, + use_pixel_local_storage, + )?; + + let brush_mix_blend = BrushShader::new( + "brush_mix_blend", + device, + &[], + options.precache_flags, + &shader_list, + false /* advanced blend */, + false /* dual source */, + use_pixel_local_storage, + )?; + + let brush_conic_gradient = BrushShader::new( + "brush_conic_gradient", + device, + if options.enable_dithering { + &[DITHERING_FEATURE] + } else { + &[] + }, + options.precache_flags, + &shader_list, + false /* advanced blend */, + false /* dual source */, + use_pixel_local_storage, + )?; + + let brush_radial_gradient = BrushShader::new( + "brush_radial_gradient", + device, + if options.enable_dithering { + &[DITHERING_FEATURE] + } else { + &[] + }, + options.precache_flags, + &shader_list, + false /* advanced blend */, + false /* dual source */, + use_pixel_local_storage, + )?; + + let brush_linear_gradient = BrushShader::new( + "brush_linear_gradient", + device, + if options.enable_dithering { + &[DITHERING_FEATURE] + } else { + &[] + }, + options.precache_flags, + &shader_list, + false /* advanced blend */, + false /* dual source */, + use_pixel_local_storage, + )?; + + let brush_opacity = BrushShader::new( + "brush_opacity", + device, + &[], + options.precache_flags, + &shader_list, + false /* advanced blend */, + false /* dual source */, + use_pixel_local_storage, + )?; + + let cs_blur_a8 = LazilyCompiledShader::new( + ShaderKind::Cache(VertexArrayKind::Blur), + "cs_blur", + &["ALPHA_TARGET"], + device, + options.precache_flags, + &shader_list, + )?; + + let cs_blur_rgba8 = LazilyCompiledShader::new( + ShaderKind::Cache(VertexArrayKind::Blur), + "cs_blur", + &["COLOR_TARGET"], + device, + options.precache_flags, + &shader_list, + )?; + + let cs_svg_filter = LazilyCompiledShader::new( + ShaderKind::Cache(VertexArrayKind::SvgFilter), + "cs_svg_filter", + &[], + device, + options.precache_flags, + &shader_list, + )?; + + let cs_clip_rectangle_slow = LazilyCompiledShader::new( + ShaderKind::ClipCache, + "cs_clip_rectangle", + &[], + device, + options.precache_flags, + &shader_list, + )?; + + let cs_clip_rectangle_fast = LazilyCompiledShader::new( + ShaderKind::ClipCache, + "cs_clip_rectangle", + &[FAST_PATH_FEATURE], + device, + options.precache_flags, + &shader_list, + )?; + + let cs_clip_box_shadow = LazilyCompiledShader::new( + ShaderKind::ClipCache, + "cs_clip_box_shadow", + &[], + device, + options.precache_flags, + &shader_list, + )?; + + let cs_clip_image = LazilyCompiledShader::new( + ShaderKind::ClipCache, + "cs_clip_image", + &[], + device, + options.precache_flags, + &shader_list, + )?; + + let pls_init = if use_pixel_local_storage { + Some(LazilyCompiledShader::new( + ShaderKind::Resolve, + "pls_init", + &[PIXEL_LOCAL_STORAGE_FEATURE], + device, + options.precache_flags, + &shader_list, + )?) + } else { + None + }; + + let pls_resolve = if use_pixel_local_storage { + Some(LazilyCompiledShader::new( + ShaderKind::Resolve, + "pls_resolve", + &[PIXEL_LOCAL_STORAGE_FEATURE], + device, + options.precache_flags, + &shader_list, + )?) + } else { + None + }; + + let cs_scale = LazilyCompiledShader::new( + ShaderKind::Cache(VertexArrayKind::Scale), + "cs_scale", + &[], + device, + options.precache_flags, + &shader_list, + )?; + + // TODO(gw): The split composite + text shader are special cases - the only + // shaders used during normal scene rendering that aren't a brush + // shader. Perhaps we can unify these in future? + let mut extra_features = Vec::new(); + if use_pixel_local_storage { + extra_features.push(PIXEL_LOCAL_STORAGE_FEATURE); + } + + let ps_text_run = TextShader::new("ps_text_run", + device, + &extra_features, + options.precache_flags, + &shader_list, + )?; + + let ps_text_run_dual_source = if use_dual_source_blending { + Some(TextShader::new("ps_text_run", + device, + &[DUAL_SOURCE_FEATURE], + options.precache_flags, + &shader_list, + )?) + } else { + None + }; + + let ps_split_composite = LazilyCompiledShader::new( + ShaderKind::Primitive, + "ps_split_composite", + &extra_features, + device, + options.precache_flags, + &shader_list, + )?; + + let ps_clear = LazilyCompiledShader::new( + ShaderKind::Clear, + "ps_clear", + &extra_features, + device, + options.precache_flags, + &shader_list, + )?; + + // All image configuration. + let mut image_features = Vec::new(); + let mut brush_image = Vec::new(); + let mut brush_fast_image = Vec::new(); + // PrimitiveShader is not clonable. Use push() to initialize the vec. + for _ in 0 .. IMAGE_BUFFER_KINDS.len() { + brush_image.push(None); + brush_fast_image.push(None); + } + for buffer_kind in 0 .. IMAGE_BUFFER_KINDS.len() { + if !IMAGE_BUFFER_KINDS[buffer_kind].has_platform_support(&gl_type) { + continue; + } + + let feature_string = IMAGE_BUFFER_KINDS[buffer_kind].get_feature_string(); + if feature_string != "" { + image_features.push(feature_string); + } + + brush_fast_image[buffer_kind] = Some(BrushShader::new( + "brush_image", + device, + &image_features, + options.precache_flags, + &shader_list, + use_advanced_blend_equation, + use_dual_source_blending, + use_pixel_local_storage, + )?); + + image_features.push("REPETITION"); + image_features.push("ANTIALIASING"); + + brush_image[buffer_kind] = Some(BrushShader::new( + "brush_image", + device, + &image_features, + options.precache_flags, + &shader_list, + use_advanced_blend_equation, + use_dual_source_blending, + use_pixel_local_storage, + )?); + + image_features.clear(); + } + + // All yuv_image configuration. + let mut yuv_features = Vec::new(); + let mut rgba_features = Vec::new(); + let yuv_shader_num = IMAGE_BUFFER_KINDS.len(); + let mut brush_yuv_image = Vec::new(); + let mut composite_yuv = Vec::new(); + let mut composite_rgba = Vec::new(); + // PrimitiveShader is not clonable. Use push() to initialize the vec. + for _ in 0 .. yuv_shader_num { + brush_yuv_image.push(None); + composite_yuv.push(None); + composite_rgba.push(None); + } + for image_buffer_kind in &IMAGE_BUFFER_KINDS { + if image_buffer_kind.has_platform_support(&gl_type) { + yuv_features.push("YUV"); + + let feature_string = image_buffer_kind.get_feature_string(); + if feature_string != "" { + yuv_features.push(feature_string); + rgba_features.push(feature_string); + } + + let brush_shader = BrushShader::new( + "brush_yuv_image", + device, + &yuv_features, + options.precache_flags, + &shader_list, + false /* advanced blend */, + false /* dual source */, + use_pixel_local_storage, + )?; + + let composite_yuv_shader = LazilyCompiledShader::new( + ShaderKind::Composite, + "composite", + &yuv_features, + device, + options.precache_flags, + &shader_list, + )?; + + let composite_rgba_shader = LazilyCompiledShader::new( + ShaderKind::Composite, + "composite", + &rgba_features, + device, + options.precache_flags, + &shader_list, + )?; + + let index = Self::get_compositing_shader_index( + *image_buffer_kind, + ); + brush_yuv_image[index] = Some(brush_shader); + composite_yuv[index] = Some(composite_yuv_shader); + composite_rgba[index] = Some(composite_rgba_shader); + + yuv_features.clear(); + rgba_features.clear() + } + } + + let cs_line_decoration = LazilyCompiledShader::new( + ShaderKind::Cache(VertexArrayKind::LineDecoration), + "cs_line_decoration", + &[], + device, + options.precache_flags, + &shader_list, + )?; + + let cs_gradient = LazilyCompiledShader::new( + ShaderKind::Cache(VertexArrayKind::Gradient), + "cs_gradient", + &[], + device, + options.precache_flags, + &shader_list, + )?; + + let cs_border_segment = LazilyCompiledShader::new( + ShaderKind::Cache(VertexArrayKind::Border), + "cs_border_segment", + &[], + device, + options.precache_flags, + &shader_list, + )?; + + let cs_border_solid = LazilyCompiledShader::new( + ShaderKind::Cache(VertexArrayKind::Border), + "cs_border_solid", + &[], + device, + options.precache_flags, + &shader_list, + )?; + + Ok(Shaders { + cs_blur_a8, + cs_blur_rgba8, + cs_border_segment, + cs_line_decoration, + cs_gradient, + cs_border_solid, + cs_scale, + cs_svg_filter, + brush_solid, + brush_image, + brush_fast_image, + brush_blend, + brush_mix_blend, + brush_yuv_image, + brush_conic_gradient, + brush_radial_gradient, + brush_linear_gradient, + brush_opacity, + cs_clip_rectangle_slow, + cs_clip_rectangle_fast, + cs_clip_box_shadow, + cs_clip_image, + pls_init, + pls_resolve, + ps_text_run, + ps_text_run_dual_source, + ps_split_composite, + ps_clear, + composite_rgba, + composite_yuv, + }) + } + + fn get_compositing_shader_index(buffer_kind: ImageBufferKind) -> usize { + buffer_kind as usize + } + + pub fn get_composite_shader( + &mut self, + format: CompositeSurfaceFormat, + buffer_kind: ImageBufferKind, + ) -> &mut LazilyCompiledShader { + match format { + CompositeSurfaceFormat::Rgba => { + let shader_index = Self::get_compositing_shader_index(buffer_kind); + self.composite_rgba[shader_index] + .as_mut() + .expect("bug: unsupported rgba shader requested") + } + CompositeSurfaceFormat::Yuv => { + let shader_index = Self::get_compositing_shader_index(buffer_kind); + self.composite_yuv[shader_index] + .as_mut() + .expect("bug: unsupported yuv shader requested") + } + } + } + + pub fn get(&mut self, key: &BatchKey, features: BatchFeatures, debug_flags: DebugFlags) -> &mut LazilyCompiledShader { + match key.kind { + BatchKind::SplitComposite => { + &mut self.ps_split_composite + } + BatchKind::Brush(brush_kind) => { + let brush_shader = match brush_kind { + BrushBatchKind::Solid => { + &mut self.brush_solid + } + BrushBatchKind::Image(image_buffer_kind) => { + if features.contains(BatchFeatures::ANTIALIASING) || + features.contains(BatchFeatures::REPETITION) { + + self.brush_image[image_buffer_kind as usize] + .as_mut() + .expect("Unsupported image shader kind") + } else { + self.brush_fast_image[image_buffer_kind as usize] + .as_mut() + .expect("Unsupported image shader kind") + } + } + BrushBatchKind::Blend => { + &mut self.brush_blend + } + BrushBatchKind::MixBlend { .. } => { + &mut self.brush_mix_blend + } + BrushBatchKind::ConicGradient => { + &mut self.brush_conic_gradient + } + BrushBatchKind::RadialGradient => { + &mut self.brush_radial_gradient + } + BrushBatchKind::LinearGradient => { + &mut self.brush_linear_gradient + } + BrushBatchKind::YuvImage(image_buffer_kind, ..) => { + let shader_index = + Self::get_compositing_shader_index(image_buffer_kind); + self.brush_yuv_image[shader_index] + .as_mut() + .expect("Unsupported YUV shader kind") + } + BrushBatchKind::Opacity => { + &mut self.brush_opacity + } + }; + brush_shader.get(key.blend_mode, debug_flags) + } + BatchKind::TextRun(glyph_format) => { + let text_shader = match key.blend_mode { + BlendMode::SubpixelDualSource => self.ps_text_run_dual_source.as_mut().unwrap(), + _ => &mut self.ps_text_run, + }; + text_shader.get(glyph_format, debug_flags) + } + } + } + + pub fn deinit(self, device: &mut Device) { + self.cs_scale.deinit(device); + self.cs_blur_a8.deinit(device); + self.cs_blur_rgba8.deinit(device); + self.cs_svg_filter.deinit(device); + self.brush_solid.deinit(device); + self.brush_blend.deinit(device); + self.brush_mix_blend.deinit(device); + self.brush_conic_gradient.deinit(device); + self.brush_radial_gradient.deinit(device); + self.brush_linear_gradient.deinit(device); + self.brush_opacity.deinit(device); + self.cs_clip_rectangle_slow.deinit(device); + self.cs_clip_rectangle_fast.deinit(device); + self.cs_clip_box_shadow.deinit(device); + self.cs_clip_image.deinit(device); + if let Some(shader) = self.pls_init { + shader.deinit(device); + } + if let Some(shader) = self.pls_resolve { + shader.deinit(device); + } + self.ps_text_run.deinit(device); + if let Some(shader) = self.ps_text_run_dual_source { + shader.deinit(device); + } + for shader in self.brush_image { + if let Some(shader) = shader { + shader.deinit(device); + } + } + for shader in self.brush_fast_image { + if let Some(shader) = shader { + shader.deinit(device); + } + } + for shader in self.brush_yuv_image { + if let Some(shader) = shader { + shader.deinit(device); + } + } + self.cs_border_solid.deinit(device); + self.cs_gradient.deinit(device); + self.cs_line_decoration.deinit(device); + self.cs_border_segment.deinit(device); + self.ps_split_composite.deinit(device); + self.ps_clear.deinit(device); + + for shader in self.composite_rgba { + if let Some(shader) = shader { + shader.deinit(device); + } + } + for shader in self.composite_yuv { + if let Some(shader) = shader { + shader.deinit(device); + } + } + } +} + +// A wrapper around a strong reference to a Shaders +// object. We have this so that external (ffi) +// consumers can own a reference to a shared Shaders +// instance without understanding rust's refcounting. +pub struct WrShaders { + pub shaders: Rc>, +} diff --git a/third_party/webrender/webrender/src/spatial_node.rs b/third_party/webrender/webrender/src/spatial_node.rs new file mode 100644 index 00000000000..2283b0fe1a9 --- /dev/null +++ b/third_party/webrender/webrender/src/spatial_node.rs @@ -0,0 +1,959 @@ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{ExternalScrollId, PipelineId, PropertyBinding, PropertyBindingId, ReferenceFrameKind, ScrollClamping, ScrollLocation}; +use api::{TransformStyle, ScrollSensitivity, StickyOffsetBounds}; +use api::units::*; +use crate::spatial_tree::{CoordinateSystem, CoordinateSystemId, SpatialNodeIndex, TransformUpdateState}; +use euclid::{Point2D, Vector2D, SideOffsets2D}; +use crate::scene::SceneProperties; +use crate::util::{LayoutFastTransform, MatrixHelpers, ScaleOffset, TransformedRectKind, PointHelpers}; + +#[derive(Clone, Debug)] +pub enum SpatialNodeType { + /// A special kind of node that adjusts its position based on the position + /// of its parent node and a given set of sticky positioning offset bounds. + /// Sticky positioned is described in the CSS Positioned Layout Module Level 3 here: + /// https://www.w3.org/TR/css-position-3/#sticky-pos + StickyFrame(StickyFrameInfo), + + /// Transforms it's content, but doesn't clip it. Can also be adjusted + /// by scroll events or setting scroll offsets. + ScrollFrame(ScrollFrameInfo), + + /// A reference frame establishes a new coordinate space in the tree. + ReferenceFrame(ReferenceFrameInfo), +} + +/// Contains information common among all types of SpatialTree nodes. +#[derive(Clone, Debug)] +pub struct SpatialNode { + /// The scale/offset of the viewport for this spatial node, relative to the + /// coordinate system. Includes any accumulated scrolling offsets from nodes + /// between our reference frame and this node. + pub viewport_transform: ScaleOffset, + + /// Content scale/offset relative to the coordinate system. + pub content_transform: ScaleOffset, + + /// Snapping scale/offset relative to the coordinate system. If None, then + /// we should not snap entities bound to this spatial node. + pub snapping_transform: Option, + + /// The axis-aligned coordinate system id of this node. + pub coordinate_system_id: CoordinateSystemId, + + /// The current transform kind of this node. + pub transform_kind: TransformedRectKind, + + /// Pipeline that this layer belongs to + pub pipeline_id: PipelineId, + + /// Parent layer. If this is None, we are the root node. + pub parent: Option, + + /// Child layers + pub children: Vec, + + /// The type of this node and any data associated with that node type. + pub node_type: SpatialNodeType, + + /// True if this node is transformed by an invertible transform. If not, display items + /// transformed by this node will not be displayed and display items not transformed by this + /// node will not be clipped by clips that are transformed by this node. + pub invertible: bool, + + /// Whether this specific node is currently being async zoomed. + /// Should be set when a SetIsTransformAsyncZooming FrameMsg is received. + pub is_async_zooming: bool, + + /// Whether this node or any of its ancestors is being pinch zoomed. + /// This is calculated in update(). This will be used to decide whether + /// to override corresponding picture's raster space as an optimisation. + pub is_ancestor_or_self_zooming: bool, +} + +fn compute_offset_from( + mut current: Option, + external_id: ExternalScrollId, + previous_spatial_nodes: &[SpatialNode], +) -> LayoutVector2D { + let mut offset = LayoutVector2D::zero(); + while let Some(parent_index) = current { + let ancestor = &previous_spatial_nodes[parent_index.0 as usize]; + match ancestor.node_type { + SpatialNodeType::ReferenceFrame(..) => { + // We don't want to scroll across reference frames. + break; + }, + SpatialNodeType::ScrollFrame(ref info) => { + if info.external_id == Some(external_id) { + break; + } + + // External scroll offsets are not propagated across + // reference frame boundaries, so undo them here. + offset += info.offset + info.external_scroll_offset; + }, + SpatialNodeType::StickyFrame(ref info) => { + offset += info.current_offset; + }, + } + current = ancestor.parent; + } + offset +} + +/// Snap an offset to be incorporated into a transform, where the local space +/// may be considered the world space. We convert from world space to device +/// space using the global device pixel scale, which may not always be correct +/// if there are intermediate surfaces used, however those are either cases +/// where snapping is not important (e.g. has perspective or is not axis +/// aligned), or an edge case (e.g. SVG filters) which we can accept +/// imperfection for now. +fn snap_offset( + offset: Vector2D, + scale: Vector2D, + global_device_pixel_scale: DevicePixelScale, +) -> Vector2D { + let world_offset = Point2D::new(offset.x * scale.x, offset.y * scale.y); + let snapped_device_offset = (world_offset * global_device_pixel_scale).snap(); + let snapped_world_offset = snapped_device_offset / global_device_pixel_scale; + Vector2D::new( + if scale.x != 0.0 { snapped_world_offset.x / scale.x } else { offset.x }, + if scale.y != 0.0 { snapped_world_offset.y / scale.y } else { offset.y }, + ) +} + +impl SpatialNode { + pub fn new( + pipeline_id: PipelineId, + parent_index: Option, + node_type: SpatialNodeType, + ) -> Self { + SpatialNode { + viewport_transform: ScaleOffset::identity(), + content_transform: ScaleOffset::identity(), + snapping_transform: None, + coordinate_system_id: CoordinateSystemId(0), + transform_kind: TransformedRectKind::AxisAligned, + parent: parent_index, + children: Vec::new(), + pipeline_id, + node_type, + invertible: true, + is_async_zooming: false, + is_ancestor_or_self_zooming: false, + } + } + + pub fn new_scroll_frame( + pipeline_id: PipelineId, + parent_index: SpatialNodeIndex, + external_id: Option, + frame_rect: &LayoutRect, + content_size: &LayoutSize, + scroll_sensitivity: ScrollSensitivity, + frame_kind: ScrollFrameKind, + external_scroll_offset: LayoutVector2D, + ) -> Self { + let node_type = SpatialNodeType::ScrollFrame(ScrollFrameInfo::new( + *frame_rect, + scroll_sensitivity, + LayoutSize::new( + (content_size.width - frame_rect.size.width).max(0.0), + (content_size.height - frame_rect.size.height).max(0.0) + ), + external_id, + frame_kind, + external_scroll_offset, + ) + ); + + Self::new(pipeline_id, Some(parent_index), node_type) + } + + pub fn new_reference_frame( + parent_index: Option, + transform_style: TransformStyle, + source_transform: PropertyBinding, + kind: ReferenceFrameKind, + origin_in_parent_reference_frame: LayoutVector2D, + pipeline_id: PipelineId, + ) -> Self { + let info = ReferenceFrameInfo { + transform_style, + source_transform, + kind, + origin_in_parent_reference_frame, + invertible: true, + }; + Self::new(pipeline_id, parent_index, SpatialNodeType::ReferenceFrame(info)) + } + + pub fn new_sticky_frame( + parent_index: SpatialNodeIndex, + sticky_frame_info: StickyFrameInfo, + pipeline_id: PipelineId, + ) -> Self { + Self::new(pipeline_id, Some(parent_index), SpatialNodeType::StickyFrame(sticky_frame_info)) + } + + pub fn add_child(&mut self, child: SpatialNodeIndex) { + self.children.push(child); + } + + pub fn apply_old_scrolling_state(&mut self, old_scroll_info: &ScrollFrameInfo) { + match self.node_type { + SpatialNodeType::ScrollFrame(ref mut scrolling) => { + *scrolling = scrolling.combine_with_old_scroll_info(old_scroll_info); + } + _ if old_scroll_info.offset != LayoutVector2D::zero() => { + warn!("Tried to scroll a non-scroll node.") + } + _ => {} + } + } + + pub fn set_scroll_origin(&mut self, origin: &LayoutPoint, clamp: ScrollClamping) -> bool { + let scrolling = match self.node_type { + SpatialNodeType::ScrollFrame(ref mut scrolling) => scrolling, + _ => { + warn!("Tried to scroll a non-scroll node."); + return false; + } + }; + + let normalized_offset = match clamp { + ScrollClamping::ToContentBounds => { + let scrollable_size = scrolling.scrollable_size; + let scrollable_width = scrollable_size.width; + let scrollable_height = scrollable_size.height; + + if scrollable_height <= 0. && scrollable_width <= 0. { + return false; + } + + let origin = LayoutPoint::new(origin.x.max(0.0), origin.y.max(0.0)); + LayoutVector2D::new( + (-origin.x).max(-scrollable_width).min(0.0), + (-origin.y).max(-scrollable_height).min(0.0), + ) + } + ScrollClamping::NoClamping => LayoutPoint::zero() - *origin, + }; + + let new_offset = normalized_offset - scrolling.external_scroll_offset; + + if new_offset == scrolling.offset { + return false; + } + + scrolling.offset = new_offset; + true + } + + pub fn mark_uninvertible( + &mut self, + state: &TransformUpdateState, + ) { + self.invertible = false; + self.viewport_transform = ScaleOffset::identity(); + self.content_transform = ScaleOffset::identity(); + self.coordinate_system_id = state.current_coordinate_system_id; + } + + pub fn update( + &mut self, + state: &mut TransformUpdateState, + coord_systems: &mut Vec, + global_device_pixel_scale: DevicePixelScale, + scene_properties: &SceneProperties, + previous_spatial_nodes: &[SpatialNode], + ) { + // If any of our parents was not rendered, we are not rendered either and can just + // quit here. + if !state.invertible { + self.mark_uninvertible(state); + return; + } + + self.update_transform(state, coord_systems, global_device_pixel_scale, scene_properties, previous_spatial_nodes); + //TODO: remove the field entirely? + self.transform_kind = if self.coordinate_system_id.0 == 0 { + TransformedRectKind::AxisAligned + } else { + TransformedRectKind::Complex + }; + + let is_parent_zooming = match self.parent { + Some(parent) => previous_spatial_nodes[parent.0 as usize].is_ancestor_or_self_zooming, + _ => false, + }; + self.is_ancestor_or_self_zooming = self.is_async_zooming | is_parent_zooming; + + // If this node is a reference frame, we check if it has a non-invertible matrix. + // For non-reference-frames we assume that they will produce only additional + // translations which should be invertible. + match self.node_type { + SpatialNodeType::ReferenceFrame(info) if !info.invertible => { + self.mark_uninvertible(state); + } + _ => self.invertible = true, + } + } + + pub fn update_transform( + &mut self, + state: &mut TransformUpdateState, + coord_systems: &mut Vec, + global_device_pixel_scale: DevicePixelScale, + scene_properties: &SceneProperties, + previous_spatial_nodes: &[SpatialNode], + ) { + match self.node_type { + SpatialNodeType::ReferenceFrame(ref mut info) => { + let mut cs_scale_offset = ScaleOffset::identity(); + + if info.invertible { + // Resolve the transform against any property bindings. + let source_transform = LayoutFastTransform::from( + scene_properties.resolve_layout_transform(&info.source_transform) + ); + + // Do a change-basis operation on the perspective matrix using + // the scroll offset. + let source_transform = match info.kind { + ReferenceFrameKind::Perspective { scrolling_relative_to: Some(external_id) } => { + let scroll_offset = compute_offset_from( + self.parent, + external_id, + previous_spatial_nodes, + ); + + // Do a change-basis operation on the + // perspective matrix using the scroll offset. + source_transform + .pre_translate(scroll_offset) + .then_translate(-scroll_offset) + } + ReferenceFrameKind::Perspective { scrolling_relative_to: None } | + ReferenceFrameKind::Transform | ReferenceFrameKind::Zoom => source_transform, + }; + + let resolved_transform = + LayoutFastTransform::with_vector(info.origin_in_parent_reference_frame) + .pre_transform(&source_transform); + + // The transformation for this viewport in world coordinates is the transformation for + // our parent reference frame, plus any accumulated scrolling offsets from nodes + // between our reference frame and this node. Finally, we also include + // whatever local transformation this reference frame provides. + let relative_transform = resolved_transform + .then_translate(snap_offset(state.parent_accumulated_scroll_offset, state.coordinate_system_relative_scale_offset.scale, global_device_pixel_scale)) + .to_transform() + .with_destination::(); + + let mut reset_cs_id = match info.transform_style { + TransformStyle::Preserve3D => !state.preserves_3d, + TransformStyle::Flat => state.preserves_3d, + }; + + // We reset the coordinate system upon either crossing the preserve-3d context boundary, + // or simply a 3D transformation. + if !reset_cs_id { + // Try to update our compatible coordinate system transform. If we cannot, start a new + // incompatible coordinate system. + match ScaleOffset::from_transform(&relative_transform) { + Some(ref scale_offset) => { + // We generally do not want to snap animated transforms as it causes jitter. + // However, we do want to snap the visual viewport offset when scrolling. + // Therefore only snap the transform for Zoom reference frames. This may still + // cause jitter when zooming, unfortunately. + let mut maybe_snapped = scale_offset.clone(); + if info.kind == ReferenceFrameKind::Zoom { + maybe_snapped.offset = snap_offset( + scale_offset.offset, + state.coordinate_system_relative_scale_offset.scale, + global_device_pixel_scale + ); + } + cs_scale_offset = + state.coordinate_system_relative_scale_offset.accumulate(&maybe_snapped); + } + None => reset_cs_id = true, + } + } + if reset_cs_id { + // If we break 2D axis alignment or have a perspective component, we need to start a + // new incompatible coordinate system with which we cannot share clips without masking. + let transform = relative_transform.then( + &state.coordinate_system_relative_scale_offset.to_transform() + ); + + // Push that new coordinate system and record the new id. + let coord_system = { + let parent_system = &coord_systems[state.current_coordinate_system_id.0 as usize]; + let mut cur_transform = transform; + if parent_system.should_flatten { + cur_transform.flatten_z_output(); + } + let world_transform = cur_transform.then(&parent_system.world_transform); + let determinant = world_transform.determinant(); + info.invertible = determinant != 0.0 && !determinant.is_nan(); + + CoordinateSystem { + transform, + world_transform, + should_flatten: match (info.transform_style, info.kind) { + (TransformStyle::Flat, ReferenceFrameKind::Transform) => true, + (_, _) => false, + }, + parent: Some(state.current_coordinate_system_id), + } + }; + state.current_coordinate_system_id = CoordinateSystemId(coord_systems.len() as u32); + coord_systems.push(coord_system); + } + } + + // Ensure that the current coordinate system ID is propagated to child + // nodes, even if we encounter a node that is not invertible. This ensures + // that the invariant in get_relative_transform is not violated. + self.coordinate_system_id = state.current_coordinate_system_id; + self.viewport_transform = cs_scale_offset; + self.content_transform = cs_scale_offset; + self.invertible = info.invertible; + } + _ => { + // We calculate this here to avoid a double-borrow later. + let sticky_offset = self.calculate_sticky_offset( + &state.nearest_scrolling_ancestor_offset, + &state.nearest_scrolling_ancestor_viewport, + ); + + // The transformation for the bounds of our viewport is the parent reference frame + // transform, plus any accumulated scroll offset from our parents, plus any offset + // provided by our own sticky positioning. + let accumulated_offset = state.parent_accumulated_scroll_offset + sticky_offset; + self.viewport_transform = state.coordinate_system_relative_scale_offset + .offset(snap_offset(accumulated_offset, state.coordinate_system_relative_scale_offset.scale, global_device_pixel_scale).to_untyped()); + + // The transformation for any content inside of us is the viewport transformation, plus + // whatever scrolling offset we supply as well. + let added_offset = accumulated_offset + self.scroll_offset(); + self.content_transform = state.coordinate_system_relative_scale_offset + .offset(snap_offset(added_offset, state.coordinate_system_relative_scale_offset.scale, global_device_pixel_scale).to_untyped()); + + if let SpatialNodeType::StickyFrame(ref mut info) = self.node_type { + info.current_offset = sticky_offset; + } + + self.coordinate_system_id = state.current_coordinate_system_id; + } + } + } + + fn calculate_sticky_offset( + &self, + viewport_scroll_offset: &LayoutVector2D, + viewport_rect: &LayoutRect, + ) -> LayoutVector2D { + let info = match self.node_type { + SpatialNodeType::StickyFrame(ref info) => info, + _ => return LayoutVector2D::zero(), + }; + + if info.margins.top.is_none() && info.margins.bottom.is_none() && + info.margins.left.is_none() && info.margins.right.is_none() { + return LayoutVector2D::zero(); + } + + // The viewport and margins of the item establishes the maximum amount that it can + // be offset in order to keep it on screen. Since we care about the relationship + // between the scrolled content and unscrolled viewport we adjust the viewport's + // position by the scroll offset in order to work with their relative positions on the + // page. + let mut sticky_rect = info.frame_rect.translate(*viewport_scroll_offset); + + let mut sticky_offset = LayoutVector2D::zero(); + if let Some(margin) = info.margins.top { + let top_viewport_edge = viewport_rect.min_y() + margin; + if sticky_rect.min_y() < top_viewport_edge { + // If the sticky rect is positioned above the top edge of the viewport (plus margin) + // we move it down so that it is fully inside the viewport. + sticky_offset.y = top_viewport_edge - sticky_rect.min_y(); + } else if info.previously_applied_offset.y > 0.0 && + sticky_rect.min_y() > top_viewport_edge { + // However, if the sticky rect is positioned *below* the top edge of the viewport + // and there is already some offset applied to the sticky rect's position, then + // we need to move it up so that it remains at the correct position. This + // makes sticky_offset.y negative and effectively reduces the amount of the + // offset that was already applied. We limit the reduction so that it can, at most, + // cancel out the already-applied offset, but should never end up adjusting the + // position the other way. + sticky_offset.y = top_viewport_edge - sticky_rect.min_y(); + sticky_offset.y = sticky_offset.y.max(-info.previously_applied_offset.y); + } + } + + // If we don't have a sticky-top offset (sticky_offset.y + info.previously_applied_offset.y + // == 0), or if we have a previously-applied bottom offset (previously_applied_offset.y < 0) + // then we check for handling the bottom margin case. Note that the "don't have a sticky-top + // offset" case includes the case where we *had* a sticky-top offset but we reduced it to + // zero in the above block. + if sticky_offset.y + info.previously_applied_offset.y <= 0.0 { + if let Some(margin) = info.margins.bottom { + // If sticky_offset.y is nonzero that means we must have set it + // in the sticky-top handling code above, so this item must have + // both top and bottom sticky margins. We adjust the item's rect + // by the top-sticky offset, and then combine any offset from + // the bottom-sticky calculation into sticky_offset below. + sticky_rect.origin.y += sticky_offset.y; + + // Same as the above case, but inverted for bottom-sticky items. Here + // we adjust items upwards, resulting in a negative sticky_offset.y, + // or reduce the already-present upward adjustment, resulting in a positive + // sticky_offset.y. + let bottom_viewport_edge = viewport_rect.max_y() - margin; + if sticky_rect.max_y() > bottom_viewport_edge { + sticky_offset.y += bottom_viewport_edge - sticky_rect.max_y(); + } else if info.previously_applied_offset.y < 0.0 && + sticky_rect.max_y() < bottom_viewport_edge { + sticky_offset.y += bottom_viewport_edge - sticky_rect.max_y(); + sticky_offset.y = sticky_offset.y.min(-info.previously_applied_offset.y); + } + } + } + + // Same as above, but for the x-axis. + if let Some(margin) = info.margins.left { + let left_viewport_edge = viewport_rect.min_x() + margin; + if sticky_rect.min_x() < left_viewport_edge { + sticky_offset.x = left_viewport_edge - sticky_rect.min_x(); + } else if info.previously_applied_offset.x > 0.0 && + sticky_rect.min_x() > left_viewport_edge { + sticky_offset.x = left_viewport_edge - sticky_rect.min_x(); + sticky_offset.x = sticky_offset.x.max(-info.previously_applied_offset.x); + } + } + + if sticky_offset.x + info.previously_applied_offset.x <= 0.0 { + if let Some(margin) = info.margins.right { + sticky_rect.origin.x += sticky_offset.x; + let right_viewport_edge = viewport_rect.max_x() - margin; + if sticky_rect.max_x() > right_viewport_edge { + sticky_offset.x += right_viewport_edge - sticky_rect.max_x(); + } else if info.previously_applied_offset.x < 0.0 && + sticky_rect.max_x() < right_viewport_edge { + sticky_offset.x += right_viewport_edge - sticky_rect.max_x(); + sticky_offset.x = sticky_offset.x.min(-info.previously_applied_offset.x); + } + } + } + + // The total "sticky offset" (which is the sum that was already applied by + // the calling code, stored in info.previously_applied_offset, and the extra amount we + // computed as a result of scrolling, stored in sticky_offset) needs to be + // clamped to the provided bounds. + let clamp_adjusted = |value: f32, adjust: f32, bounds: &StickyOffsetBounds| { + (value + adjust).max(bounds.min).min(bounds.max) - adjust + }; + sticky_offset.y = clamp_adjusted(sticky_offset.y, + info.previously_applied_offset.y, + &info.vertical_offset_bounds); + sticky_offset.x = clamp_adjusted(sticky_offset.x, + info.previously_applied_offset.x, + &info.horizontal_offset_bounds); + + sticky_offset + } + + pub fn prepare_state_for_children(&self, state: &mut TransformUpdateState) { + if !self.invertible { + state.invertible = false; + return; + } + + // The transformation we are passing is the transformation of the parent + // reference frame and the offset is the accumulated offset of all the nodes + // between us and the parent reference frame. If we are a reference frame, + // we need to reset both these values. + match self.node_type { + SpatialNodeType::StickyFrame(ref info) => { + // We don't translate the combined rect by the sticky offset, because sticky + // offsets actually adjust the node position itself, whereas scroll offsets + // only apply to contents inside the node. + state.parent_accumulated_scroll_offset += info.current_offset; + // We want nested sticky items to take into account the shift + // we applied as well. + state.nearest_scrolling_ancestor_offset += info.current_offset; + state.preserves_3d = false; + } + SpatialNodeType::ScrollFrame(ref scrolling) => { + state.parent_accumulated_scroll_offset += scrolling.offset; + state.nearest_scrolling_ancestor_offset = scrolling.offset; + state.nearest_scrolling_ancestor_viewport = scrolling.viewport_rect; + state.preserves_3d = false; + } + SpatialNodeType::ReferenceFrame(ref info) => { + state.preserves_3d = info.transform_style == TransformStyle::Preserve3D; + state.parent_accumulated_scroll_offset = LayoutVector2D::zero(); + state.coordinate_system_relative_scale_offset = self.content_transform; + let translation = -info.origin_in_parent_reference_frame; + state.nearest_scrolling_ancestor_viewport = + state.nearest_scrolling_ancestor_viewport + .translate(translation); + } + } + } + + pub fn scroll(&mut self, scroll_location: ScrollLocation) -> bool { + // TODO(gw): This scroll method doesn't currently support + // scroll nodes with non-zero external scroll + // offsets. However, it's never used by Gecko, + // which is the only client that requires + // non-zero external scroll offsets. + + let scrolling = match self.node_type { + SpatialNodeType::ScrollFrame(ref mut scrolling) => scrolling, + _ => return false, + }; + + let delta = match scroll_location { + ScrollLocation::Delta(delta) => delta, + ScrollLocation::Start => { + if scrolling.offset.y.round() >= 0.0 { + // Nothing to do on this layer. + return false; + } + + scrolling.offset.y = 0.0; + return true; + } + ScrollLocation::End => { + let end_pos = -scrolling.scrollable_size.height; + if scrolling.offset.y.round() <= end_pos { + // Nothing to do on this layer. + return false; + } + + scrolling.offset.y = end_pos; + return true; + } + }; + + let scrollable_width = scrolling.scrollable_size.width; + let scrollable_height = scrolling.scrollable_size.height; + let original_layer_scroll_offset = scrolling.offset; + + if scrollable_width > 0. { + scrolling.offset.x = (scrolling.offset.x + delta.x) + .min(0.0) + .max(-scrollable_width); + } + + if scrollable_height > 0. { + scrolling.offset.y = (scrolling.offset.y + delta.y) + .min(0.0) + .max(-scrollable_height); + } + + scrolling.offset != original_layer_scroll_offset + } + + pub fn scroll_offset(&self) -> LayoutVector2D { + match self.node_type { + SpatialNodeType::ScrollFrame(ref scrolling) => scrolling.offset, + _ => LayoutVector2D::zero(), + } + } + + pub fn matches_external_id(&self, external_id: ExternalScrollId) -> bool { + match self.node_type { + SpatialNodeType::ScrollFrame(info) if info.external_id == Some(external_id) => true, + _ => false, + } + } + + /// Updates the snapping transform. + pub fn update_snapping( + &mut self, + parent: Option<&SpatialNode>, + ) { + // Reset in case of an early return. + self.snapping_transform = None; + + // We need to incorporate the parent scale/offset with the child. + // If the parent does not have a scale/offset, then we know we are + // not 2d axis aligned and thus do not need to snap its children + // either. + let parent_scale_offset = match parent { + Some(parent) => { + match parent.snapping_transform { + Some(scale_offset) => scale_offset, + None => return, + } + }, + _ => ScaleOffset::identity(), + }; + + let scale_offset = match self.node_type { + SpatialNodeType::ReferenceFrame(ref info) => { + match info.source_transform { + PropertyBinding::Value(ref value) => { + // We can only get a ScaleOffset if the transform is 2d axis + // aligned. + match ScaleOffset::from_transform(value) { + Some(scale_offset) => { + let origin_offset = info.origin_in_parent_reference_frame; + ScaleOffset::from_offset(origin_offset.to_untyped()) + .accumulate(&scale_offset) + } + None => return, + } + } + + // Assume animations start at the identity transform for snapping purposes. + // We still want to incorporate the reference frame offset however. + // TODO(aosmond): Is there a better known starting point? + PropertyBinding::Binding(..) => { + let origin_offset = info.origin_in_parent_reference_frame; + ScaleOffset::from_offset(origin_offset.to_untyped()) + } + } + } + _ => ScaleOffset::identity(), + }; + + self.snapping_transform = Some(parent_scale_offset.accumulate(&scale_offset)); + } + + /// Returns true for ReferenceFrames whose source_transform is + /// bound to the property binding id. + pub fn is_transform_bound_to_property(&self, id: PropertyBindingId) -> bool { + if let SpatialNodeType::ReferenceFrame(ref info) = self.node_type { + if let PropertyBinding::Binding(key, _) = info.source_transform { + id == key.id + } else { + false + } + } else { + false + } + } +} + +/// Defines whether we have an implicit scroll frame for a pipeline root, +/// or an explicitly defined scroll frame from the display list. +#[derive(Copy, Clone, Debug)] +pub enum ScrollFrameKind { + PipelineRoot, + Explicit, +} + +#[derive(Copy, Clone, Debug)] +pub struct ScrollFrameInfo { + /// The rectangle of the viewport of this scroll frame. This is important for + /// positioning of items inside child StickyFrames. + pub viewport_rect: LayoutRect, + + pub scroll_sensitivity: ScrollSensitivity, + + /// Amount that this ScrollFrame can scroll in both directions. + pub scrollable_size: LayoutSize, + + /// An external id to identify this scroll frame to API clients. This + /// allows setting scroll positions via the API without relying on ClipsIds + /// which may change between frames. + pub external_id: Option, + + /// Stores whether this is a scroll frame added implicitly by WR when adding + /// a pipeline (either the root or an iframe). We need to exclude these + /// when searching for scroll roots we care about for picture caching. + /// TODO(gw): I think we can actually completely remove the implicit + /// scroll frame being added by WR, and rely on the embedder + /// to define scroll frames. However, that involves API changes + /// so we will use this as a temporary hack! + pub frame_kind: ScrollFrameKind, + + /// Amount that visual components attached to this scroll node have been + /// pre-scrolled in their local coordinates. + pub external_scroll_offset: LayoutVector2D, + + /// The current offset of this scroll node. + pub offset: LayoutVector2D, +} + +/// Manages scrolling offset. +impl ScrollFrameInfo { + pub fn new( + viewport_rect: LayoutRect, + scroll_sensitivity: ScrollSensitivity, + scrollable_size: LayoutSize, + external_id: Option, + frame_kind: ScrollFrameKind, + external_scroll_offset: LayoutVector2D, + ) -> ScrollFrameInfo { + ScrollFrameInfo { + viewport_rect, + offset: -external_scroll_offset, + scroll_sensitivity, + scrollable_size, + external_id, + frame_kind, + external_scroll_offset, + } + } + + pub fn sensitive_to_input_events(&self) -> bool { + match self.scroll_sensitivity { + ScrollSensitivity::ScriptAndInputEvents => true, + ScrollSensitivity::Script => false, + } + } + + pub fn combine_with_old_scroll_info( + self, + old_scroll_info: &ScrollFrameInfo + ) -> ScrollFrameInfo { + let offset = + old_scroll_info.offset + + self.external_scroll_offset - + old_scroll_info.external_scroll_offset; + + ScrollFrameInfo { + viewport_rect: self.viewport_rect, + offset, + scroll_sensitivity: self.scroll_sensitivity, + scrollable_size: self.scrollable_size, + external_id: self.external_id, + frame_kind: self.frame_kind, + external_scroll_offset: self.external_scroll_offset, + } + } +} + +/// Contains information about reference frames. +#[derive(Copy, Clone, Debug)] +pub struct ReferenceFrameInfo { + /// The source transform and perspective matrices provided by the stacking context + /// that forms this reference frame. We maintain the property binding information + /// here so that we can resolve the animated transform and update the tree each + /// frame. + pub source_transform: PropertyBinding, + pub transform_style: TransformStyle, + pub kind: ReferenceFrameKind, + + /// The original, not including the transform and relative to the parent reference frame, + /// origin of this reference frame. This is already rolled into the `transform' property, but + /// we also store it here to properly transform the viewport for sticky positioning. + pub origin_in_parent_reference_frame: LayoutVector2D, + + /// True if the resolved transform is invertible. + pub invertible: bool, +} + +#[derive(Clone, Debug)] +pub struct StickyFrameInfo { + pub frame_rect: LayoutRect, + pub margins: SideOffsets2D, LayoutPixel>, + pub vertical_offset_bounds: StickyOffsetBounds, + pub horizontal_offset_bounds: StickyOffsetBounds, + pub previously_applied_offset: LayoutVector2D, + pub current_offset: LayoutVector2D, +} + +impl StickyFrameInfo { + pub fn new( + frame_rect: LayoutRect, + margins: SideOffsets2D, LayoutPixel>, + vertical_offset_bounds: StickyOffsetBounds, + horizontal_offset_bounds: StickyOffsetBounds, + previously_applied_offset: LayoutVector2D + ) -> StickyFrameInfo { + StickyFrameInfo { + frame_rect, + margins, + vertical_offset_bounds, + horizontal_offset_bounds, + previously_applied_offset, + current_offset: LayoutVector2D::zero(), + } + } +} + +#[test] +fn test_cst_perspective_relative_scroll() { + // Verify that when computing the offset from a perspective transform + // to a relative scroll node that any external scroll offset is + // ignored. This is because external scroll offsets are not + // propagated across reference frame boundaries. + + // It's not currently possible to verify this with a wrench reftest, + // since wrench doesn't understand external scroll ids. When wrench + // supports this, we could also verify with a reftest. + + use crate::spatial_tree::SpatialTree; + use euclid::approxeq::ApproxEq; + + let mut cst = SpatialTree::new(); + let pipeline_id = PipelineId::dummy(); + let ext_scroll_id = ExternalScrollId(1, pipeline_id); + let transform = LayoutTransform::perspective(100.0); + + let root = cst.add_reference_frame( + None, + TransformStyle::Flat, + PropertyBinding::Value(LayoutTransform::identity()), + ReferenceFrameKind::Transform, + LayoutVector2D::zero(), + pipeline_id, + ); + + let scroll_frame_1 = cst.add_scroll_frame( + root, + Some(ext_scroll_id), + pipeline_id, + &LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(100.0, 100.0)), + &LayoutSize::new(100.0, 500.0), + ScrollSensitivity::Script, + ScrollFrameKind::Explicit, + LayoutVector2D::zero(), + ); + + let scroll_frame_2 = cst.add_scroll_frame( + scroll_frame_1, + None, + pipeline_id, + &LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(100.0, 100.0)), + &LayoutSize::new(100.0, 500.0), + ScrollSensitivity::Script, + ScrollFrameKind::Explicit, + LayoutVector2D::new(0.0, 50.0), + ); + + let ref_frame = cst.add_reference_frame( + Some(scroll_frame_2), + TransformStyle::Preserve3D, + PropertyBinding::Value(transform), + ReferenceFrameKind::Perspective { + scrolling_relative_to: Some(ext_scroll_id), + }, + LayoutVector2D::zero(), + pipeline_id, + ); + + cst.update_tree(WorldPoint::zero(), DevicePixelScale::new(1.0), &SceneProperties::new()); + + let scroll_offset = compute_offset_from( + cst.spatial_nodes[ref_frame.0 as usize].parent, + ext_scroll_id, + &cst.spatial_nodes, + ); + + assert!(scroll_offset.x.approx_eq(&0.0)); + assert!(scroll_offset.y.approx_eq(&0.0)); +} diff --git a/third_party/webrender/webrender/src/spatial_tree.rs b/third_party/webrender/webrender/src/spatial_tree.rs new file mode 100644 index 00000000000..d1eaca9d8fc --- /dev/null +++ b/third_party/webrender/webrender/src/spatial_tree.rs @@ -0,0 +1,965 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{ExternalScrollId, PropertyBinding, ReferenceFrameKind, TransformStyle}; +use api::{PipelineId, ScrollClamping, ScrollNodeState, ScrollLocation, ScrollSensitivity}; +use api::units::*; +use euclid::Transform3D; +use crate::gpu_types::TransformPalette; +use crate::internal_types::{FastHashMap, FastHashSet}; +use crate::print_tree::{PrintableTree, PrintTree, PrintTreePrinter}; +use crate::scene::SceneProperties; +use crate::spatial_node::{ScrollFrameInfo, SpatialNode, SpatialNodeType, StickyFrameInfo, ScrollFrameKind}; +use std::{ops, u32}; +use crate::util::{FastTransform, LayoutToWorldFastTransform, MatrixHelpers, ScaleOffset, scale_factors}; + +pub type ScrollStates = FastHashMap; + +/// An id that identifies coordinate systems in the SpatialTree. Each +/// coordinate system has an id and those ids will be shared when the coordinates +/// system are the same or are in the same axis-aligned space. This allows +/// for optimizing mask generation. +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct CoordinateSystemId(pub u32); + +/// A node in the hierarchy of coordinate system +/// transforms. +#[derive(Debug)] +pub struct CoordinateSystem { + pub transform: LayoutTransform, + pub world_transform: LayoutToWorldTransform, + pub should_flatten: bool, + pub parent: Option, +} + +impl CoordinateSystem { + fn root() -> Self { + CoordinateSystem { + transform: LayoutTransform::identity(), + world_transform: LayoutToWorldTransform::identity(), + should_flatten: false, + parent: None, + } + } +} + +#[derive(Debug, Copy, Clone, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd, Ord)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct SpatialNodeIndex(pub u32); + +impl SpatialNodeIndex { + pub const INVALID: SpatialNodeIndex = SpatialNodeIndex(u32::MAX); +} + +//Note: these have to match ROOT_REFERENCE_FRAME_SPATIAL_ID and ROOT_SCROLL_NODE_SPATIAL_ID +pub const ROOT_SPATIAL_NODE_INDEX: SpatialNodeIndex = SpatialNodeIndex(0); +const TOPMOST_SCROLL_NODE_INDEX: SpatialNodeIndex = SpatialNodeIndex(1); + +impl SpatialNodeIndex { + pub fn new(index: usize) -> Self { + debug_assert!(index < ::std::u32::MAX as usize); + SpatialNodeIndex(index as u32) + } +} + +impl CoordinateSystemId { + pub fn root() -> Self { + CoordinateSystemId(0) + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum VisibleFace { + Front, + Back, +} + +impl Default for VisibleFace { + fn default() -> Self { + VisibleFace::Front + } +} + +impl ops::Not for VisibleFace { + type Output = Self; + fn not(self) -> Self { + match self { + VisibleFace::Front => VisibleFace::Back, + VisibleFace::Back => VisibleFace::Front, + } + } +} + +pub struct SpatialTree { + /// Nodes which determine the positions (offsets and transforms) for primitives + /// and clips. + pub spatial_nodes: Vec, + + /// A list of transforms that establish new coordinate systems. + /// Spatial nodes only establish a new coordinate system when + /// they have a transform that is not a simple 2d translation. + coord_systems: Vec, + + pub pending_scroll_offsets: FastHashMap, + + /// A set of pipelines which should be discarded the next time this + /// tree is drained. + pub pipelines_to_discard: FastHashSet, + + /// Temporary stack of nodes to update when traversing the tree. + nodes_to_update: Vec<(SpatialNodeIndex, TransformUpdateState)>, +} + +#[derive(Clone)] +pub struct TransformUpdateState { + pub parent_reference_frame_transform: LayoutToWorldFastTransform, + pub parent_accumulated_scroll_offset: LayoutVector2D, + pub nearest_scrolling_ancestor_offset: LayoutVector2D, + pub nearest_scrolling_ancestor_viewport: LayoutRect, + + /// An id for keeping track of the axis-aligned space of this node. This is used in + /// order to to track what kinds of clip optimizations can be done for a particular + /// display list item, since optimizations can usually only be done among + /// coordinate systems which are relatively axis aligned. + pub current_coordinate_system_id: CoordinateSystemId, + + /// Scale and offset from the coordinate system that started this compatible coordinate system. + pub coordinate_system_relative_scale_offset: ScaleOffset, + + /// True if this node is transformed by an invertible transform. If not, display items + /// transformed by this node will not be displayed and display items not transformed by this + /// node will not be clipped by clips that are transformed by this node. + pub invertible: bool, + + /// True if this node is a part of Preserve3D hierarchy. + pub preserves_3d: bool, +} + + +/// Transformation between two nodes in the spatial tree that can sometimes be +/// encoded more efficiently than with a full matrix. +#[derive(Debug, Clone)] +pub enum CoordinateSpaceMapping { + Local, + ScaleOffset(ScaleOffset), + Transform(Transform3D), +} + +impl CoordinateSpaceMapping { + pub fn into_transform(self) -> Transform3D { + match self { + CoordinateSpaceMapping::Local => Transform3D::identity(), + CoordinateSpaceMapping::ScaleOffset(scale_offset) => scale_offset.to_transform(), + CoordinateSpaceMapping::Transform(transform) => transform, + } + } + + pub fn into_fast_transform(self) -> FastTransform { + match self { + CoordinateSpaceMapping::Local => FastTransform::identity(), + CoordinateSpaceMapping::ScaleOffset(scale_offset) => FastTransform::with_scale_offset(scale_offset), + CoordinateSpaceMapping::Transform(transform) => FastTransform::with_transform(transform), + } + } + + pub fn visible_face(&self) -> VisibleFace { + match *self { + CoordinateSpaceMapping::Transform(ref transform) if transform.is_backface_visible() => VisibleFace::Back, + CoordinateSpaceMapping::Local | + CoordinateSpaceMapping::Transform(_) | + CoordinateSpaceMapping::ScaleOffset(_) => VisibleFace::Front, + + } + } + + pub fn is_perspective(&self) -> bool { + match *self { + CoordinateSpaceMapping::Local | + CoordinateSpaceMapping::ScaleOffset(_) => false, + CoordinateSpaceMapping::Transform(ref transform) => transform.has_perspective_component(), + } + } + + pub fn is_2d_axis_aligned(&self) -> bool { + match *self { + CoordinateSpaceMapping::Local | + CoordinateSpaceMapping::ScaleOffset(_) => true, + CoordinateSpaceMapping::Transform(ref transform) => transform.preserves_2d_axis_alignment(), + } + } + + pub fn scale_factors(&self) -> (f32, f32) { + match *self { + CoordinateSpaceMapping::Local => (1.0, 1.0), + CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => (scale_offset.scale.x, scale_offset.scale.y), + CoordinateSpaceMapping::Transform(ref transform) => scale_factors(transform), + } + } + + pub fn inverse(&self) -> Option> { + match *self { + CoordinateSpaceMapping::Local => Some(CoordinateSpaceMapping::Local), + CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => { + Some(CoordinateSpaceMapping::ScaleOffset(scale_offset.inverse())) + } + CoordinateSpaceMapping::Transform(ref transform) => { + transform.inverse().map(CoordinateSpaceMapping::Transform) + } + } + } +} + +enum TransformScroll { + Scrolled, + Unscrolled, +} + +impl SpatialTree { + pub fn new() -> Self { + SpatialTree { + spatial_nodes: Vec::new(), + coord_systems: Vec::new(), + pending_scroll_offsets: FastHashMap::default(), + pipelines_to_discard: FastHashSet::default(), + nodes_to_update: Vec::new(), + } + } + + /// Calculate the accumulated external scroll offset for + /// a given spatial node. + pub fn external_scroll_offset(&self, node_index: SpatialNodeIndex) -> LayoutVector2D { + let mut offset = LayoutVector2D::zero(); + let mut current_node = Some(node_index); + + while let Some(node_index) = current_node { + let node = &self.spatial_nodes[node_index.0 as usize]; + + match node.node_type { + SpatialNodeType::ScrollFrame(ref scrolling) => { + offset += scrolling.external_scroll_offset; + } + SpatialNodeType::StickyFrame(..) => { + // Doesn't provide any external scroll offset + } + SpatialNodeType::ReferenceFrame(..) => { + // External scroll offsets are not propagated across + // reference frames. + break; + } + } + + current_node = node.parent; + } + + offset + } + + /// Calculate the relative transform from `child_index` to `parent_index`. + /// This method will panic if the nodes are not connected! + pub fn get_relative_transform( + &self, + child_index: SpatialNodeIndex, + parent_index: SpatialNodeIndex, + ) -> CoordinateSpaceMapping { + if child_index == parent_index { + return CoordinateSpaceMapping::Local; + } + + let child = &self.spatial_nodes[child_index.0 as usize]; + let parent = &self.spatial_nodes[parent_index.0 as usize]; + + if child.coordinate_system_id == parent.coordinate_system_id { + let scale_offset = parent.content_transform + .inverse() + .accumulate(&child.content_transform); + return CoordinateSpaceMapping::ScaleOffset(scale_offset); + } + + if child_index.0 < parent_index.0 { + warn!("Unexpected transform queried from {:?} to {:?}, please call the graphics team!", child_index, parent_index); + let child_cs = &self.coord_systems[child.coordinate_system_id.0 as usize]; + let child_transform = child.content_transform + .to_transform::() + .then(&child_cs.world_transform); + let parent_cs = &self.coord_systems[parent.coordinate_system_id.0 as usize]; + let parent_transform = parent.content_transform + .to_transform() + .then(&parent_cs.world_transform); + + let result = parent_transform + .inverse() + .unwrap_or_default() + .then(&child_transform) + .with_source::() + .with_destination::(); + return CoordinateSpaceMapping::Transform(result); + } + + let mut coordinate_system_id = child.coordinate_system_id; + let mut transform = child.content_transform.to_transform(); + + // we need to update the associated parameters of a transform in two cases: + // 1) when the flattening happens, so that we don't lose that original 3D aspects + // 2) when we reach the end of iteration, so that our result is up to date + + while coordinate_system_id != parent.coordinate_system_id { + let coord_system = &self.coord_systems[coordinate_system_id.0 as usize]; + + if coord_system.should_flatten { + transform.flatten_z_output(); + } + + coordinate_system_id = coord_system.parent.expect("invalid parent!"); + transform = transform.then(&coord_system.transform); + } + + transform = transform.then( + &parent.content_transform + .inverse() + .to_transform(), + ); + + CoordinateSpaceMapping::Transform(transform) + } + + fn get_world_transform_impl( + &self, + index: SpatialNodeIndex, + scroll: TransformScroll, + ) -> CoordinateSpaceMapping { + let child = &self.spatial_nodes[index.0 as usize]; + + if child.coordinate_system_id.0 == 0 { + if index == ROOT_SPATIAL_NODE_INDEX { + CoordinateSpaceMapping::Local + } else { + CoordinateSpaceMapping::ScaleOffset(child.content_transform) + } + } else { + let system = &self.coord_systems[child.coordinate_system_id.0 as usize]; + let scale_offset = match scroll { + TransformScroll::Scrolled => &child.content_transform, + TransformScroll::Unscrolled => &child.viewport_transform, + }; + let transform = scale_offset + .to_transform() + .then(&system.world_transform); + + CoordinateSpaceMapping::Transform(transform) + } + } + + /// Calculate the relative transform from `index` to the root. + pub fn get_world_transform( + &self, + index: SpatialNodeIndex, + ) -> CoordinateSpaceMapping { + self.get_world_transform_impl(index, TransformScroll::Scrolled) + } + + /// Calculate the relative transform from `index` to the root. + /// Unlike `get_world_transform`, this variant doesn't account for the local scroll offset. + pub fn get_world_viewport_transform( + &self, + index: SpatialNodeIndex, + ) -> CoordinateSpaceMapping { + self.get_world_transform_impl(index, TransformScroll::Unscrolled) + } + + /// The root reference frame, which is the true root of the SpatialTree. Initially + /// this ID is not valid, which is indicated by ```spatial_nodes``` being empty. + pub fn root_reference_frame_index(&self) -> SpatialNodeIndex { + // TODO(mrobinson): We should eventually make this impossible to misuse. + debug_assert!(!self.spatial_nodes.is_empty()); + ROOT_SPATIAL_NODE_INDEX + } + + /// The root scroll node which is the first child of the root reference frame. + /// Initially this ID is not valid, which is indicated by ```spatial_nodes``` being empty. + pub fn topmost_scroll_node_index(&self) -> SpatialNodeIndex { + // TODO(mrobinson): We should eventually make this impossible to misuse. + debug_assert!(self.spatial_nodes.len() >= 1); + TOPMOST_SCROLL_NODE_INDEX + } + + pub fn get_scroll_node_state(&self) -> Vec { + let mut result = vec![]; + for node in &self.spatial_nodes { + if let SpatialNodeType::ScrollFrame(info) = node.node_type { + if let Some(id) = info.external_id { + result.push(ScrollNodeState { + id, + scroll_offset: info.offset - info.external_scroll_offset, + }) + } + } + } + result + } + + pub fn drain(&mut self) -> ScrollStates { + let mut scroll_states = FastHashMap::default(); + for old_node in &mut self.spatial_nodes.drain(..) { + if self.pipelines_to_discard.contains(&old_node.pipeline_id) { + continue; + } + + match old_node.node_type { + SpatialNodeType::ScrollFrame(info) if info.external_id.is_some() => { + scroll_states.insert(info.external_id.unwrap(), info); + } + _ => {} + } + } + + self.coord_systems.clear(); + self.pipelines_to_discard.clear(); + scroll_states + } + + pub fn scroll_node( + &mut self, + origin: LayoutPoint, + id: ExternalScrollId, + clamp: ScrollClamping + ) -> bool { + for node in &mut self.spatial_nodes { + if node.matches_external_id(id) { + return node.set_scroll_origin(&origin, clamp); + } + } + + self.pending_scroll_offsets.insert(id, (origin, clamp)); + false + } + + fn find_nearest_scrolling_ancestor( + &self, + index: Option + ) -> SpatialNodeIndex { + let index = match index { + Some(index) => index, + None => return self.topmost_scroll_node_index(), + }; + + let node = &self.spatial_nodes[index.0 as usize]; + match node.node_type { + SpatialNodeType::ScrollFrame(state) if state.sensitive_to_input_events() => index, + _ => self.find_nearest_scrolling_ancestor(node.parent) + } + } + + pub fn scroll_nearest_scrolling_ancestor( + &mut self, + scroll_location: ScrollLocation, + node_index: Option, + ) -> bool { + if self.spatial_nodes.is_empty() { + return false; + } + let node_index = self.find_nearest_scrolling_ancestor(node_index); + self.spatial_nodes[node_index.0 as usize].scroll(scroll_location) + } + + pub fn update_tree( + &mut self, + pan: WorldPoint, + global_device_pixel_scale: DevicePixelScale, + scene_properties: &SceneProperties, + ) { + if self.spatial_nodes.is_empty() { + return; + } + + profile_scope!("update_tree"); + self.coord_systems.clear(); + self.coord_systems.push(CoordinateSystem::root()); + + let root_node_index = self.root_reference_frame_index(); + let state = TransformUpdateState { + parent_reference_frame_transform: LayoutVector2D::new(pan.x, pan.y).into(), + parent_accumulated_scroll_offset: LayoutVector2D::zero(), + nearest_scrolling_ancestor_offset: LayoutVector2D::zero(), + nearest_scrolling_ancestor_viewport: LayoutRect::zero(), + current_coordinate_system_id: CoordinateSystemId::root(), + coordinate_system_relative_scale_offset: ScaleOffset::identity(), + invertible: true, + preserves_3d: false, + }; + debug_assert!(self.nodes_to_update.is_empty()); + self.nodes_to_update.push((root_node_index, state)); + + while let Some((node_index, mut state)) = self.nodes_to_update.pop() { + let (previous, following) = self.spatial_nodes.split_at_mut(node_index.0 as usize); + let node = match following.get_mut(0) { + Some(node) => node, + None => continue, + }; + + node.update(&mut state, &mut self.coord_systems, global_device_pixel_scale, scene_properties, &*previous); + + if !node.children.is_empty() { + node.prepare_state_for_children(&mut state); + self.nodes_to_update.extend(node.children + .iter() + .rev() + .map(|child_index| (*child_index, state.clone())) + ); + } + } + } + + pub fn build_transform_palette(&self) -> TransformPalette { + profile_scope!("build_transform_palette"); + let mut palette = TransformPalette::new(self.spatial_nodes.len()); + //Note: getting the world transform of a node is O(1) operation + for i in 0 .. self.spatial_nodes.len() { + let index = SpatialNodeIndex(i as u32); + let world_transform = self.get_world_transform(index).into_transform(); + palette.set_world_transform(index, world_transform); + } + palette + } + + pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) { + for node in &mut self.spatial_nodes { + let external_id = match node.node_type { + SpatialNodeType::ScrollFrame(ScrollFrameInfo { external_id: Some(id), ..} ) => id, + _ => continue, + }; + + if let Some(scrolling_state) = old_states.get(&external_id) { + node.apply_old_scrolling_state(scrolling_state); + } + + if let Some((offset, clamping)) = self.pending_scroll_offsets.remove(&external_id) { + node.set_scroll_origin(&offset, clamping); + } + } + } + + pub fn add_scroll_frame( + &mut self, + parent_index: SpatialNodeIndex, + external_id: Option, + pipeline_id: PipelineId, + frame_rect: &LayoutRect, + content_size: &LayoutSize, + scroll_sensitivity: ScrollSensitivity, + frame_kind: ScrollFrameKind, + external_scroll_offset: LayoutVector2D, + ) -> SpatialNodeIndex { + let node = SpatialNode::new_scroll_frame( + pipeline_id, + parent_index, + external_id, + frame_rect, + content_size, + scroll_sensitivity, + frame_kind, + external_scroll_offset, + ); + self.add_spatial_node(node) + } + + pub fn add_reference_frame( + &mut self, + parent_index: Option, + transform_style: TransformStyle, + source_transform: PropertyBinding, + kind: ReferenceFrameKind, + origin_in_parent_reference_frame: LayoutVector2D, + pipeline_id: PipelineId, + ) -> SpatialNodeIndex { + let node = SpatialNode::new_reference_frame( + parent_index, + transform_style, + source_transform, + kind, + origin_in_parent_reference_frame, + pipeline_id, + ); + self.add_spatial_node(node) + } + + pub fn add_sticky_frame( + &mut self, + parent_index: SpatialNodeIndex, + sticky_frame_info: StickyFrameInfo, + pipeline_id: PipelineId, + ) -> SpatialNodeIndex { + let node = SpatialNode::new_sticky_frame( + parent_index, + sticky_frame_info, + pipeline_id, + ); + self.add_spatial_node(node) + } + + pub fn add_spatial_node(&mut self, mut node: SpatialNode) -> SpatialNodeIndex { + let index = SpatialNodeIndex::new(self.spatial_nodes.len()); + + // When the parent node is None this means we are adding the root. + if let Some(parent_index) = node.parent { + let parent_node = &mut self.spatial_nodes[parent_index.0 as usize]; + parent_node.add_child(index); + node.update_snapping(Some(parent_node)); + } else { + node.update_snapping(None); + } + + self.spatial_nodes.push(node); + index + } + + pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) { + self.pipelines_to_discard.insert(pipeline_id); + } + + /// Find the spatial node that is the scroll root for a given spatial node. + /// A scroll root is the first spatial node when found travelling up the + /// spatial node tree that is an explicit scroll frame. + pub fn find_scroll_root( + &self, + spatial_node_index: SpatialNodeIndex, + ) -> SpatialNodeIndex { + let mut scroll_root = ROOT_SPATIAL_NODE_INDEX; + let mut node_index = spatial_node_index; + + while node_index != ROOT_SPATIAL_NODE_INDEX { + let node = &self.spatial_nodes[node_index.0 as usize]; + match node.node_type { + SpatialNodeType::ReferenceFrame(ref info) => { + match info.kind { + ReferenceFrameKind::Zoom => { + // We can handle scroll nodes that pass through a zoom node + } + ReferenceFrameKind::Transform | + ReferenceFrameKind::Perspective { .. } => { + // When a reference frame is encountered, forget any scroll roots + // we have encountered, as they may end up with a non-axis-aligned transform. + scroll_root = ROOT_SPATIAL_NODE_INDEX; + } + } + } + SpatialNodeType::StickyFrame(..) => {} + SpatialNodeType::ScrollFrame(ref info) => { + match info.frame_kind { + ScrollFrameKind::PipelineRoot => { + // Once we encounter a pipeline root, there is no need to look further + break; + } + ScrollFrameKind::Explicit => { + // If the scroll root has no scrollable area, we don't want to + // consider it. This helps pages that have a nested scroll root + // within a redundant scroll root to avoid selecting the wrong + // reference spatial node for a picture cache. + if info.scrollable_size.width > 0.0 || + info.scrollable_size.height > 0.0 { + // Since we are skipping redundant scroll roots, we may end up + // selecting inner scroll roots that are very small. There is + // no performance benefit to creating a slice for these roots, + // as they are cheap to rasterize. The size comparison is in + // local-space, but makes for a reasonable estimate. The value + // is arbitrary, but is generally small enough to ignore things + // like scroll roots around text input elements. + if info.viewport_rect.size.width > 128.0 && + info.viewport_rect.size.height > 128.0 { + // If we've found a root that is scrollable, and a reasonable + // size, select that as the current root for this node + scroll_root = node_index; + } + } + } + } + } + } + node_index = node.parent.expect("unable to find parent node"); + } + + scroll_root + } + + fn print_node( + &self, + index: SpatialNodeIndex, + pt: &mut T, + ) { + let node = &self.spatial_nodes[index.0 as usize]; + match node.node_type { + SpatialNodeType::StickyFrame(ref sticky_frame_info) => { + pt.new_level(format!("StickyFrame")); + pt.add_item(format!("sticky info: {:?}", sticky_frame_info)); + } + SpatialNodeType::ScrollFrame(scrolling_info) => { + pt.new_level(format!("ScrollFrame")); + pt.add_item(format!("viewport: {:?}", scrolling_info.viewport_rect)); + pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size)); + pt.add_item(format!("scroll offset: {:?}", scrolling_info.offset)); + pt.add_item(format!("external_scroll_offset: {:?}", scrolling_info.external_scroll_offset)); + pt.add_item(format!("kind: {:?}", scrolling_info.frame_kind)); + } + SpatialNodeType::ReferenceFrame(ref info) => { + pt.new_level(format!("ReferenceFrame")); + pt.add_item(format!("kind: {:?}", info.kind)); + pt.add_item(format!("transform_style: {:?}", info.transform_style)); + pt.add_item(format!("source_transform: {:?}", info.source_transform)); + pt.add_item(format!("origin_in_parent_reference_frame: {:?}", info.origin_in_parent_reference_frame)); + } + } + + pt.add_item(format!("index: {:?}", index)); + pt.add_item(format!("content_transform: {:?}", node.content_transform)); + pt.add_item(format!("viewport_transform: {:?}", node.viewport_transform)); + pt.add_item(format!("snapping_transform: {:?}", node.snapping_transform)); + pt.add_item(format!("coordinate_system_id: {:?}", node.coordinate_system_id)); + + for child_index in &node.children { + self.print_node(*child_index, pt); + } + + pt.end_level(); + } + + /// Get the visible face of the transfrom from the specified node to its parent. + pub fn get_local_visible_face(&self, node_index: SpatialNodeIndex) -> VisibleFace { + let node = &self.spatial_nodes[node_index.0 as usize]; + let parent_index = match node.parent { + Some(index) => index, + None => return VisibleFace::Front + }; + self.get_relative_transform(node_index, parent_index) + .visible_face() + } + + #[allow(dead_code)] + pub fn print(&self) { + if !self.spatial_nodes.is_empty() { + let mut buf = Vec::::new(); + { + let mut pt = PrintTree::new_with_sink("spatial tree", &mut buf); + self.print_with(&mut pt); + } + // If running in Gecko, set RUST_LOG=webrender::spatial_tree=debug + // to get this logging to be emitted to stderr/logcat. + debug!("{}", std::str::from_utf8(&buf).unwrap_or("(Tree printer emitted non-utf8)")); + } + } +} + +impl PrintableTree for SpatialTree { + fn print_with(&self, pt: &mut T) { + if !self.spatial_nodes.is_empty() { + self.print_node(self.root_reference_frame_index(), pt); + } + } +} + +#[cfg(test)] +fn add_reference_frame( + cst: &mut SpatialTree, + parent: Option, + transform: LayoutTransform, + origin_in_parent_reference_frame: LayoutVector2D, +) -> SpatialNodeIndex { + cst.add_reference_frame( + parent, + TransformStyle::Preserve3D, + PropertyBinding::Value(transform), + ReferenceFrameKind::Transform, + origin_in_parent_reference_frame, + PipelineId::dummy(), + ) +} + +#[cfg(test)] +fn test_pt( + px: f32, + py: f32, + cst: &SpatialTree, + child: SpatialNodeIndex, + parent: SpatialNodeIndex, + expected_x: f32, + expected_y: f32, +) { + use euclid::approxeq::ApproxEq; + const EPSILON: f32 = 0.0001; + + let p = LayoutPoint::new(px, py); + let m = cst.get_relative_transform(child, parent).into_transform(); + let pt = m.transform_point2d(p).unwrap(); + assert!(pt.x.approx_eq_eps(&expected_x, &EPSILON) && + pt.y.approx_eq_eps(&expected_y, &EPSILON), + "p: {:?} -> {:?}\nm={:?}", + p, pt, m, + ); +} + +#[test] +fn test_cst_simple_translation() { + // Basic translations only + + let mut cst = SpatialTree::new(); + + let root = add_reference_frame( + &mut cst, + None, + LayoutTransform::identity(), + LayoutVector2D::zero(), + ); + + let child1 = add_reference_frame( + &mut cst, + Some(root), + LayoutTransform::translation(100.0, 0.0, 0.0), + LayoutVector2D::zero(), + ); + + let child2 = add_reference_frame( + &mut cst, + Some(child1), + LayoutTransform::translation(0.0, 50.0, 0.0), + LayoutVector2D::zero(), + ); + + let child3 = add_reference_frame( + &mut cst, + Some(child2), + LayoutTransform::translation(200.0, 200.0, 0.0), + LayoutVector2D::zero(), + ); + + cst.update_tree(WorldPoint::zero(), DevicePixelScale::new(1.0), &SceneProperties::new()); + + test_pt(100.0, 100.0, &cst, child1, root, 200.0, 100.0); + test_pt(100.0, 100.0, &cst, child2, root, 200.0, 150.0); + test_pt(100.0, 100.0, &cst, child2, child1, 100.0, 150.0); + test_pt(100.0, 100.0, &cst, child3, root, 400.0, 350.0); +} + +#[test] +fn test_cst_simple_scale() { + // Basic scale only + + let mut cst = SpatialTree::new(); + + let root = add_reference_frame( + &mut cst, + None, + LayoutTransform::identity(), + LayoutVector2D::zero(), + ); + + let child1 = add_reference_frame( + &mut cst, + Some(root), + LayoutTransform::scale(4.0, 1.0, 1.0), + LayoutVector2D::zero(), + ); + + let child2 = add_reference_frame( + &mut cst, + Some(child1), + LayoutTransform::scale(1.0, 2.0, 1.0), + LayoutVector2D::zero(), + ); + + let child3 = add_reference_frame( + &mut cst, + Some(child2), + LayoutTransform::scale(2.0, 2.0, 1.0), + LayoutVector2D::zero(), + ); + + cst.update_tree(WorldPoint::zero(), DevicePixelScale::new(1.0), &SceneProperties::new()); + + test_pt(100.0, 100.0, &cst, child1, root, 400.0, 100.0); + test_pt(100.0, 100.0, &cst, child2, root, 400.0, 200.0); + test_pt(100.0, 100.0, &cst, child3, root, 800.0, 400.0); + test_pt(100.0, 100.0, &cst, child2, child1, 100.0, 200.0); + test_pt(100.0, 100.0, &cst, child3, child1, 200.0, 400.0); +} + +#[test] +fn test_cst_scale_translation() { + // Scale + translation + + let mut cst = SpatialTree::new(); + + let root = add_reference_frame( + &mut cst, + None, + LayoutTransform::identity(), + LayoutVector2D::zero(), + ); + + let child1 = add_reference_frame( + &mut cst, + Some(root), + LayoutTransform::translation(100.0, 50.0, 0.0), + LayoutVector2D::zero(), + ); + + let child2 = add_reference_frame( + &mut cst, + Some(child1), + LayoutTransform::scale(2.0, 4.0, 1.0), + LayoutVector2D::zero(), + ); + + let child3 = add_reference_frame( + &mut cst, + Some(child2), + LayoutTransform::translation(200.0, -100.0, 0.0), + LayoutVector2D::zero(), + ); + + let child4 = add_reference_frame( + &mut cst, + Some(child3), + LayoutTransform::scale(3.0, 2.0, 1.0), + LayoutVector2D::zero(), + ); + + cst.update_tree(WorldPoint::zero(), DevicePixelScale::new(1.0), &SceneProperties::new()); + + test_pt(100.0, 100.0, &cst, child1, root, 200.0, 150.0); + test_pt(100.0, 100.0, &cst, child2, root, 300.0, 450.0); + test_pt(100.0, 100.0, &cst, child4, root, 1100.0, 450.0); + + test_pt(0.0, 0.0, &cst, child4, child1, 400.0, -400.0); + test_pt(100.0, 100.0, &cst, child4, child1, 1000.0, 400.0); + test_pt(100.0, 100.0, &cst, child2, child1, 200.0, 400.0); + + test_pt(100.0, 100.0, &cst, child3, child1, 600.0, 0.0); +} + +#[test] +fn test_cst_translation_rotate() { + // Rotation + translation + use euclid::Angle; + + let mut cst = SpatialTree::new(); + + let root = add_reference_frame( + &mut cst, + None, + LayoutTransform::identity(), + LayoutVector2D::zero(), + ); + + let child1 = add_reference_frame( + &mut cst, + Some(root), + LayoutTransform::rotation(0.0, 0.0, 1.0, Angle::degrees(-90.0)), + LayoutVector2D::zero(), + ); + + cst.update_tree(WorldPoint::zero(), DevicePixelScale::new(1.0), &SceneProperties::new()); + + test_pt(100.0, 0.0, &cst, child1, root, 0.0, -100.0); +} diff --git a/third_party/webrender/webrender/src/storage.rs b/third_party/webrender/webrender/src/storage.rs new file mode 100644 index 00000000000..a928192cd99 --- /dev/null +++ b/third_party/webrender/webrender/src/storage.rs @@ -0,0 +1,134 @@ +/* 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 std::{iter::Extend, ops, marker::PhantomData, u32}; +use crate::util::Recycler; + +#[derive(Debug, Hash)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct Index(u32, PhantomData); + +// We explicitly implement Copy + Clone instead of using #[derive(Copy, Clone)] +// because we don't want to require that T implements Clone + Copy. +impl Clone for Index { + fn clone(&self) -> Self { *self } +} + +impl Copy for Index {} + +impl PartialEq for Index { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Index { + fn new(idx: usize) -> Self { + debug_assert!(idx < u32::max_value() as usize); + Index(idx as u32, PhantomData) + } + + pub const INVALID: Index = Index(u32::MAX, PhantomData); + pub const UNUSED: Index = Index(u32::MAX-1, PhantomData); +} + +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct Range { + pub start: Index, + pub end: Index, +} + +// We explicitly implement Copy + Clone instead of using #[derive(Copy, Clone)] +// because we don't want to require that T implements Clone + Copy. +impl Clone for Range { + fn clone(&self) -> Self { + Range { start: self.start, end: self.end } + } +} +impl Copy for Range {} + +impl Range { + /// Create an empty `Range` + pub fn empty() -> Self { + Range { + start: Index::new(0), + end: Index::new(0), + } + } + + /// Check for an empty `Range` + pub fn is_empty(self) -> bool { + self.start.0 >= self.end.0 + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct Storage { + data: Vec, +} + +impl Storage { + pub fn new(initial_capacity: usize) -> Self { + Storage { + data: Vec::with_capacity(initial_capacity), + } + } + + pub fn len(&self) -> usize { + self.data.len() + } + + pub fn clear(&mut self) { + self.data.clear(); + } + + pub fn push(&mut self, t: T) -> Index { + let index = self.data.len(); + self.data.push(t); + Index(index as u32, PhantomData) + } + + pub fn recycle(&mut self, recycler: &mut Recycler) { + recycler.recycle_vec(&mut self.data); + } + + pub fn extend>(&mut self, iter: II) -> Range { + let start = Index::new(self.data.len()); + self.data.extend(iter); + let end = Index::new(self.data.len()); + Range { start, end } + } +} + +impl ops::Index> for Storage { + type Output = T; + fn index(&self, index: Index) -> &Self::Output { + &self.data[index.0 as usize] + } +} + +impl ops::IndexMut> for Storage { + fn index_mut(&mut self, index: Index) -> &mut Self::Output { + &mut self.data[index.0 as usize] + } +} + +impl ops::Index> for Storage { + type Output = [T]; + fn index(&self, index: Range) -> &Self::Output { + let start = index.start.0 as _; + let end = index.end.0 as _; + &self.data[start..end] + } +} + +impl ops::IndexMut> for Storage { + fn index_mut(&mut self, index: Range) -> &mut Self::Output { + let start = index.start.0 as _; + let end = index.end.0 as _; + &mut self.data[start..end] + } +} diff --git a/third_party/webrender/webrender/src/texture_allocator.rs b/third_party/webrender/webrender/src/texture_allocator.rs new file mode 100644 index 00000000000..1f7902b2191 --- /dev/null +++ b/third_party/webrender/webrender/src/texture_allocator.rs @@ -0,0 +1,270 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize}; + +//TODO: gather real-world statistics on the bin usage in order to assist the decision +// on where to place the size thresholds. + +/// This is an optimization tweak to enable looking through all the free rectangles in a bin +/// and choosing the smallest, as opposed to picking the first match. +const FIND_SMALLEST_AREA: bool = false; + +const NUM_BINS: usize = 3; +/// The minimum number of pixels on each side that we require for rects to be classified as +/// particular bin of freelists. +const MIN_RECT_AXIS_SIZES: [i32; NUM_BINS] = [1, 16, 32]; + +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +struct FreeListBin(u8); + +#[derive(Debug, Clone, Copy)] +struct FreeListIndex(usize); + +impl FreeListBin { + fn for_size(size: &DeviceIntSize) -> Self { + MIN_RECT_AXIS_SIZES + .iter() + .enumerate() + .rev() + .find(|(_, &min_size)| min_size <= size.width && min_size <= size.height) + .map(|(id, _)| FreeListBin(id as u8)) + .expect("Unable to find a bin!") + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct FreeRectSlice(pub u32); + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct FreeRect { + slice: FreeRectSlice, + rect: DeviceIntRect, +} + +/// A texture allocator using the guillotine algorithm with the rectangle merge improvement. See +/// sections 2.2 and 2.2.5 in "A Thousand Ways to Pack the Bin - A Practical Approach to Two- +/// Dimensional Rectangle Bin Packing": +/// +/// http://clb.demon.fi/files/RectangleBinPack.pdf +/// +/// This approach was chosen because of its simplicity, good performance, and easy support for +/// dynamic texture deallocation. +/// +/// Note: the allocations are spread across multiple textures, and also are binned +/// orthogonally in order to speed up the search. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ArrayAllocationTracker { + bins: [Vec; NUM_BINS], +} + +impl ArrayAllocationTracker { + pub fn new() -> Self { + ArrayAllocationTracker { + bins: [ + Vec::new(), + Vec::new(), + Vec::new(), + ], + } + } + + fn push(&mut self, slice: FreeRectSlice, rect: DeviceIntRect) { + let id = FreeListBin::for_size(&rect.size).0 as usize; + self.bins[id].push(FreeRect { + slice, + rect, + }) + } + + /// Find a suitable rect in the free list. We choose the smallest such rect + /// in terms of area (Best-Area-Fit, BAF). + fn find_index_of_best_rect( + &self, + requested_dimensions: &DeviceIntSize, + ) -> Option<(FreeListBin, FreeListIndex)> { + let start_bin = FreeListBin::for_size(requested_dimensions); + (start_bin.0 .. NUM_BINS as u8) + .find_map(|id| if FIND_SMALLEST_AREA { + let mut smallest_index_and_area = None; + for (candidate_index, candidate) in self.bins[id as usize].iter().enumerate() { + if requested_dimensions.width > candidate.rect.size.width || + requested_dimensions.height > candidate.rect.size.height + { + continue; + } + + let candidate_area = candidate.rect.size.area(); + match smallest_index_and_area { + Some((_, area)) if candidate_area >= area => continue, + _ => smallest_index_and_area = Some((candidate_index, candidate_area)), + } + } + + smallest_index_and_area + .map(|(index, _)| (FreeListBin(id), FreeListIndex(index))) + } else { + self.bins[id as usize] + .iter() + .position(|candidate| { + requested_dimensions.width <= candidate.rect.size.width && + requested_dimensions.height <= candidate.rect.size.height + }) + .map(|index| (FreeListBin(id), FreeListIndex(index))) + }) + } + + // Split that results in the single largest area (Min Area Split Rule, MINAS). + fn split_guillotine(&mut self, chosen: &FreeRect, requested_dimensions: &DeviceIntSize) { + let candidate_free_rect_to_right = DeviceIntRect::new( + DeviceIntPoint::new( + chosen.rect.origin.x + requested_dimensions.width, + chosen.rect.origin.y, + ), + DeviceIntSize::new( + chosen.rect.size.width - requested_dimensions.width, + requested_dimensions.height, + ), + ); + let candidate_free_rect_to_bottom = DeviceIntRect::new( + DeviceIntPoint::new( + chosen.rect.origin.x, + chosen.rect.origin.y + requested_dimensions.height, + ), + DeviceIntSize::new( + requested_dimensions.width, + chosen.rect.size.height - requested_dimensions.height, + ), + ); + + // Guillotine the rectangle. + let new_free_rect_to_right; + let new_free_rect_to_bottom; + if candidate_free_rect_to_right.size.area() > candidate_free_rect_to_bottom.size.area() { + new_free_rect_to_right = DeviceIntRect::new( + candidate_free_rect_to_right.origin, + DeviceIntSize::new( + candidate_free_rect_to_right.size.width, + chosen.rect.size.height, + ), + ); + new_free_rect_to_bottom = candidate_free_rect_to_bottom + } else { + new_free_rect_to_right = candidate_free_rect_to_right; + new_free_rect_to_bottom = DeviceIntRect::new( + candidate_free_rect_to_bottom.origin, + DeviceIntSize::new( + chosen.rect.size.width, + candidate_free_rect_to_bottom.size.height, + ), + ) + } + + // Add the guillotined rects back to the free list. + if !new_free_rect_to_right.is_empty() { + self.push(chosen.slice, new_free_rect_to_right); + } + if !new_free_rect_to_bottom.is_empty() { + self.push(chosen.slice, new_free_rect_to_bottom); + } + } + + pub fn allocate( + &mut self, requested_dimensions: &DeviceIntSize + ) -> Option<(FreeRectSlice, DeviceIntPoint)> { + if requested_dimensions.width == 0 || requested_dimensions.height == 0 { + return Some((FreeRectSlice(0), DeviceIntPoint::new(0, 0))); + } + let (bin, index) = self.find_index_of_best_rect(requested_dimensions)?; + + // Remove the rect from the free list and decide how to guillotine it. + let chosen = self.bins[bin.0 as usize].swap_remove(index.0); + self.split_guillotine(&chosen, requested_dimensions); + + // Return the result. + Some((chosen.slice, chosen.rect.origin)) + } + + /// Add a new slice to the allocator, and immediately allocate a rect from it. + pub fn extend( + &mut self, + slice: FreeRectSlice, + total_size: DeviceIntSize, + requested_dimensions: DeviceIntSize, + ) { + self.split_guillotine( + &FreeRect { slice, rect: total_size.into() }, + &requested_dimensions + ); + } +} + +#[cfg(test)] +fn random_fill(count: usize, texture_size: i32) -> f32 { + use rand::{thread_rng, Rng}; + + let total_rect = DeviceIntRect::new( + DeviceIntPoint::zero(), + DeviceIntSize::new(texture_size, texture_size), + ); + let mut rng = thread_rng(); + let mut allocator = ArrayAllocationTracker::new(); + + // check for empty allocation + assert_eq!( + allocator.allocate(&DeviceIntSize::new(0, 12)), + Some((FreeRectSlice(0), DeviceIntPoint::zero())), + ); + + let mut slices: Vec> = Vec::new(); + let mut requested_area = 0f32; + // fill up the allocator + for _ in 0 .. count { + let size = DeviceIntSize::new( + rng.gen_range(1, texture_size), + rng.gen_range(1, texture_size), + ); + requested_area += size.area() as f32; + + match allocator.allocate(&size) { + Some((slice, origin)) => { + let rect = DeviceIntRect::new(origin, size); + assert_eq!(None, slices[slice.0 as usize].iter().find(|r| r.intersects(&rect))); + assert!(total_rect.contains_rect(&rect)); + slices[slice.0 as usize].push(rect); + } + None => { + allocator.extend(FreeRectSlice(slices.len() as u32), total_rect.size, size); + let rect = DeviceIntRect::new(DeviceIntPoint::zero(), size); + slices.push(vec![rect]); + } + } + } + // validate the free rects + for (i, free_vecs) in allocator.bins.iter().enumerate() { + for fr in free_vecs { + assert_eq!(FreeListBin(i as u8), FreeListBin::for_size(&fr.rect.size)); + assert_eq!(None, slices[fr.slice.0 as usize].iter().find(|r| r.intersects(&fr.rect))); + assert!(total_rect.contains_rect(&fr.rect)); + slices[fr.slice.0 as usize].push(fr.rect); + } + } + + let allocated_area = slices.len() as f32 * (texture_size * texture_size) as f32; + requested_area / allocated_area +} + +#[test] +fn test_small() { + random_fill(100, 100); +} + +#[test] +fn test_large() { + random_fill(1000, 10000); +} diff --git a/third_party/webrender/webrender/src/texture_cache.rs b/third_party/webrender/webrender/src/texture_cache.rs new file mode 100644 index 00000000000..9b244656805 --- /dev/null +++ b/third_party/webrender/webrender/src/texture_cache.rs @@ -0,0 +1,1782 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::{DirtyRect, ExternalImageType, ImageFormat}; +use api::{DebugFlags, ImageDescriptor}; +use api::units::*; +#[cfg(test)] +use api::{DocumentId, IdNamespace}; +use crate::device::{TextureFilter, TextureFormatPair}; +use crate::freelist::{FreeListHandle, WeakFreeListHandle}; +use crate::gpu_cache::{GpuCache, GpuCacheHandle}; +use crate::gpu_types::{ImageSource, UvRectKind}; +use crate::internal_types::{ + CacheTextureId, LayerIndex, Swizzle, SwizzleSettings, + TextureUpdateList, TextureUpdateSource, TextureSource, + TextureCacheAllocInfo, TextureCacheUpdate, +}; +use crate::lru_cache::LRUCache; +use crate::profiler::{ResourceProfileCounter, TextureCacheProfileCounters}; +use crate::render_backend::FrameStamp; +use crate::resource_cache::{CacheItem, CachedImageData}; +use smallvec::SmallVec; +use std::cell::Cell; +use std::cmp; +use std::mem; +use std::rc::Rc; + +/// The size of each region/layer in shared cache texture arrays. +pub const TEXTURE_REGION_DIMENSIONS: i32 = 512; + +const PICTURE_TEXTURE_ADD_SLICES: usize = 4; + +/// The chosen image format for picture tiles. +const PICTURE_TILE_FORMAT: ImageFormat = ImageFormat::RGBA8; + +/// The number of pixels in a region. Derived from the above. +const TEXTURE_REGION_PIXELS: usize = + (TEXTURE_REGION_DIMENSIONS as usize) * (TEXTURE_REGION_DIMENSIONS as usize); + +/// Items in the texture cache can either be standalone textures, +/// or a sub-rect inside the shared cache. +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +enum EntryDetails { + Standalone { + /// Number of bytes this entry allocates + size_in_bytes: usize, + }, + Picture { + // Index in the picture_textures array + texture_index: usize, + // Slice in the texture array + layer_index: usize, + }, + Cache { + /// Origin within the texture layer where this item exists. + origin: DeviceIntPoint, + /// The layer index of the texture array. + layer_index: usize, + }, +} + +impl EntryDetails { + fn describe(&self) -> (LayerIndex, DeviceIntPoint) { + match *self { + EntryDetails::Standalone { .. } => (0, DeviceIntPoint::zero()), + EntryDetails::Picture { layer_index, .. } => (layer_index, DeviceIntPoint::zero()), + EntryDetails::Cache { origin, layer_index, .. } => (layer_index, origin), + } + } +} + +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum CacheEntryMarker {} + +// Stores information related to a single entry in the texture +// cache. This is stored for each item whether it's in the shared +// cache or a standalone texture. +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct CacheEntry { + /// Size the requested item, in device pixels. + size: DeviceIntSize, + /// Details specific to standalone or shared items. + details: EntryDetails, + /// Arbitrary user data associated with this item. + user_data: [f32; 3], + /// The last frame this item was requested for rendering. + // TODO(gw): This stamp is only used for picture cache tiles, and some checks + // in the glyph cache eviction code. We could probably remove it + // entirely in future (or move to EntryDetails::Picture). + last_access: FrameStamp, + /// Handle to the resource rect in the GPU cache. + uv_rect_handle: GpuCacheHandle, + /// Image format of the data that the entry expects. + input_format: ImageFormat, + filter: TextureFilter, + swizzle: Swizzle, + /// The actual device texture ID this is part of. + texture_id: CacheTextureId, + /// Optional notice when the entry is evicted from the cache. + eviction_notice: Option, + /// The type of UV rect this entry specifies. + uv_rect_kind: UvRectKind, +} + +impl CacheEntry { + // Create a new entry for a standalone texture. + fn new_standalone( + texture_id: CacheTextureId, + last_access: FrameStamp, + params: &CacheAllocParams, + swizzle: Swizzle, + size_in_bytes: usize, + ) -> Self { + CacheEntry { + size: params.descriptor.size, + user_data: params.user_data, + last_access, + details: EntryDetails::Standalone { + size_in_bytes, + }, + texture_id, + input_format: params.descriptor.format, + filter: params.filter, + swizzle, + uv_rect_handle: GpuCacheHandle::new(), + eviction_notice: None, + uv_rect_kind: params.uv_rect_kind, + } + } + + // Update the GPU cache for this texture cache entry. + // This ensures that the UV rect, and texture layer index + // are up to date in the GPU cache for vertex shaders + // to fetch from. + fn update_gpu_cache(&mut self, gpu_cache: &mut GpuCache) { + if let Some(mut request) = gpu_cache.request(&mut self.uv_rect_handle) { + let (layer_index, origin) = self.details.describe(); + let image_source = ImageSource { + p0: origin.to_f32(), + p1: (origin + self.size).to_f32(), + texture_layer: layer_index as f32, + user_data: self.user_data, + uv_rect_kind: self.uv_rect_kind, + }; + image_source.write_gpu_blocks(&mut request); + } + } + + fn evict(&self) { + if let Some(eviction_notice) = self.eviction_notice.as_ref() { + eviction_notice.notify(); + } + } + + fn alternative_input_format(&self) -> ImageFormat { + match self.input_format { + ImageFormat::RGBA8 => ImageFormat::BGRA8, + ImageFormat::BGRA8 => ImageFormat::RGBA8, + other => other, + } + } +} + + +/// A texture cache handle is a weak reference to a cache entry. +/// +/// If the handle has not been inserted into the cache yet, or if the entry was +/// previously inserted and then evicted, lookup of the handle will fail, and +/// the cache handle needs to re-upload this item to the texture cache (see +/// request() below). +pub type TextureCacheHandle = WeakFreeListHandle; + +/// Describes the eviction policy for a given entry in the texture cache. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum Eviction { + /// The entry will be evicted under the normal rules (which differ between + /// standalone and shared entries). + Auto, + /// The entry will not be evicted until the policy is explicitly set to a + /// different value. + Manual, +} + +// An eviction notice is a shared condition useful for detecting +// when a TextureCacheHandle gets evicted from the TextureCache. +// It is optionally installed to the TextureCache when an update() +// is scheduled. A single notice may be shared among any number of +// TextureCacheHandle updates. The notice may then be subsequently +// checked to see if any of the updates using it have been evicted. +#[derive(Clone, Debug, Default)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct EvictionNotice { + evicted: Rc>, +} + +impl EvictionNotice { + fn notify(&self) { + self.evicted.set(true); + } + + pub fn check(&self) -> bool { + if self.evicted.get() { + self.evicted.set(false); + true + } else { + false + } + } +} + +/// A set of lazily allocated, fixed size, texture arrays for each format the +/// texture cache supports. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct SharedTextures { + array_color8_nearest: TextureArray, + array_alpha8_linear: TextureArray, + array_alpha16_linear: TextureArray, + array_color8_linear: TextureArray, +} + +impl SharedTextures { + /// Mints a new set of shared textures. + fn new(color_formats: TextureFormatPair) -> Self { + Self { + // Used primarily for cached shadow masks. There can be lots of + // these on some pages like francine, but most pages don't use it + // much. + array_alpha8_linear: TextureArray::new( + TextureFormatPair::from(ImageFormat::R8), + TextureFilter::Linear, + 4, + ), + // Used for experimental hdr yuv texture support, but not used in + // production Firefox. + array_alpha16_linear: TextureArray::new( + TextureFormatPair::from(ImageFormat::R16), + TextureFilter::Linear, + 1, + ), + // The primary cache for images, glyphs, etc. + array_color8_linear: TextureArray::new( + color_formats.clone(), + TextureFilter::Linear, + 16, + ), + // Used for image-rendering: crisp. This is mostly favicons, which + // are small. Some other images use it too, but those tend to be + // larger than 512x512 and thus don't use the shared cache anyway. + array_color8_nearest: TextureArray::new( + color_formats, + TextureFilter::Nearest, + 1, + ), + } + } + + /// Clears each texture in the set, with the given set of pending updates. + fn clear(&mut self, updates: &mut TextureUpdateList) { + self.array_alpha8_linear.clear(updates); + self.array_alpha16_linear.clear(updates); + self.array_color8_linear.clear(updates); + self.array_color8_nearest.clear(updates); + } + + /// Returns a mutable borrow for the shared texture array matching the parameters. + fn select( + &mut self, external_format: ImageFormat, filter: TextureFilter + ) -> &mut TextureArray { + match external_format { + ImageFormat::R8 => { + assert_eq!(filter, TextureFilter::Linear); + &mut self.array_alpha8_linear + } + ImageFormat::R16 => { + assert_eq!(filter, TextureFilter::Linear); + &mut self.array_alpha16_linear + } + ImageFormat::RGBA8 | + ImageFormat::BGRA8 => { + match filter { + TextureFilter::Linear => &mut self.array_color8_linear, + TextureFilter::Nearest => &mut self.array_color8_nearest, + _ => panic!("Unexpexcted filter {:?}", filter), + } + } + _ => panic!("Unexpected format {:?}", external_format), + } + } +} + +/// The texture arrays used to hold picture cache tiles. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct PictureTextures { + textures: Vec, +} + +impl PictureTextures { + fn new( + initial_window_size: DeviceIntSize, + picture_tile_sizes: &[DeviceIntSize], + next_texture_id: &mut CacheTextureId, + pending_updates: &mut TextureUpdateList, + ) -> Self { + let mut textures = Vec::new(); + for tile_size in picture_tile_sizes { + // TODO(gw): The way initial size is used here may allocate a lot of memory once + // we are using multiple slice sizes. Do some measurements once we + // have multiple slices here and adjust the calculations as required. + let num_x = (initial_window_size.width + tile_size.width - 1) / tile_size.width; + let num_y = (initial_window_size.height + tile_size.height - 1) / tile_size.height; + let mut slice_count = (num_x * num_y).max(1).min(16) as usize; + if slice_count < 4 { + // On some platforms we get bogus (1x1) initial window size. The first real frame will then + // reallocate many more picture cache slices. Don't bother preallocating in that case. + slice_count = 0; + } + + if slice_count == 0 { + continue; + } + + let texture = WholeTextureArray { + size: *tile_size, + filter: TextureFilter::Nearest, + format: PICTURE_TILE_FORMAT, + texture_id: *next_texture_id, + slices: vec![WholeTextureSlice { uv_rect_handle: None }; slice_count], + has_depth: true, + }; + + next_texture_id.0 += 1; + + pending_updates.push_alloc(texture.texture_id, texture.to_info()); + + textures.push(texture); + } + + PictureTextures { textures } + } + + fn get_or_allocate_tile( + &mut self, + tile_size: DeviceIntSize, + now: FrameStamp, + next_texture_id: &mut CacheTextureId, + pending_updates: &mut TextureUpdateList, + ) -> CacheEntry { + let texture_index = self.textures + .iter() + .position(|texture| { texture.size == tile_size }) + .unwrap_or(self.textures.len()); + + if texture_index == self.textures.len() { + self.textures.push(WholeTextureArray { + size: tile_size, + filter: TextureFilter::Nearest, + format: PICTURE_TILE_FORMAT, + texture_id: *next_texture_id, + slices: Vec::new(), + has_depth: true, + }); + next_texture_id.0 += 1; + } + + let texture = &mut self.textures[texture_index]; + + let layer_index = match texture.find_free() { + Some(index) => index, + None => { + let was_empty = texture.slices.is_empty(); + let index = texture.grow(PICTURE_TEXTURE_ADD_SLICES); + let info = texture.to_info(); + if was_empty { + pending_updates.push_alloc(texture.texture_id, info); + } else { + pending_updates.push_realloc(texture.texture_id, info); + } + + index + }, + }; + + texture.occupy(texture_index, layer_index, now) + } + + fn get(&mut self, index: usize) -> &mut WholeTextureArray { + &mut self.textures[index] + } + + fn clear(&mut self, pending_updates: &mut TextureUpdateList) { + for texture in &mut self.textures { + if texture.slices.is_empty() { + continue; + } + + if let Some(texture_id) = texture.reset(PICTURE_TEXTURE_ADD_SLICES) { + pending_updates.push_reset(texture_id, texture.to_info()); + } + } + } + + fn update_profile(&self, profile: &mut ResourceProfileCounter) { + // For now, this profile counter just accumulates the slices and bytes + // from all picture cache texture arrays. + let mut picture_slices = 0; + let mut picture_bytes = 0; + for texture in &self.textures { + picture_slices += texture.slices.len(); + picture_bytes += texture.size_in_bytes(); + } + profile.set(picture_slices, picture_bytes); + } + + #[cfg(feature = "replay")] + fn tile_sizes(&self) -> Vec { + self.textures.iter().map(|pt| pt.size).collect() + } +} + +/// Container struct for the various parameters used in cache allocation. +struct CacheAllocParams { + descriptor: ImageDescriptor, + filter: TextureFilter, + user_data: [f32; 3], + uv_rect_kind: UvRectKind, +} + +/// General-purpose manager for images in GPU memory. This includes images, +/// rasterized glyphs, rasterized blobs, cached render tasks, etc. +/// +/// The texture cache is owned and managed by the RenderBackend thread, and +/// produces a series of commands to manipulate the textures on the Renderer +/// thread. These commands are executed before any rendering is performed for +/// a given frame. +/// +/// Entries in the texture cache are not guaranteed to live past the end of the +/// frame in which they are requested, and may be evicted. The API supports +/// querying whether an entry is still available. +/// +/// The TextureCache is different from the GpuCache in that the former stores +/// images, whereas the latter stores data and parameters for use in the shaders. +/// This means that the texture cache can be visualized, which is a good way to +/// understand how it works. Enabling gfx.webrender.debug.texture-cache shows a +/// live view of its contents in Firefox. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct TextureCache { + /// Set of texture arrays in different formats used for the shared cache. + shared_textures: SharedTextures, + + /// A texture array per tile size for picture caching. + picture_textures: PictureTextures, + + /// Maximum texture size supported by hardware. + max_texture_size: i32, + + /// Maximum number of texture layers supported by hardware. + max_texture_layers: usize, + + /// Settings on using texture unit swizzling. + swizzle: Option, + + /// The current set of debug flags. + debug_flags: DebugFlags, + + /// The next unused virtual texture ID. Monotonically increasing. + next_id: CacheTextureId, + + /// A list of allocations and updates that need to be applied to the texture + /// cache in the rendering thread this frame. + #[cfg_attr(all(feature = "serde", any(feature = "capture", feature = "replay")), serde(skip))] + pending_updates: TextureUpdateList, + + /// The current `FrameStamp`. Used for cache eviction policies. + now: FrameStamp, + + /// List of picture cache entries. These are maintained separately from regular + /// texture cache entries. + picture_cache_handles: Vec>, + + /// Cache of texture cache handles with automatic lifetime management, evicted + /// in a least-recently-used order (except those entries with manual eviction enabled). + lru_cache: LRUCache, + + /// A list of texture cache handles that have been set to explicitly have manual + /// eviction policy enabled. The handles reference cache entries in the lru_cache + /// above, but have opted in to manual lifetime management. + manual_handles: Vec>, + + /// Estimated memory usage of allocated entries in all of the shared textures. This + /// is used to decide when to evict old items from the cache. + shared_bytes_allocated: usize, + + /// Number of bytes allocated in standalone textures. Used as an input to deciding + /// when to run texture cache eviction. + standalone_bytes_allocated: usize, + + /// If the total bytes allocated in shared / standalone cache is less + /// than this, then allow the cache to grow without forcing an eviction. + // TODO(gw): In future, it's probably reasonable to make this higher again, perhaps 64-128 MB. + eviction_threshold_bytes: usize, + + /// The maximum number of items that will be evicted per frame. This limit helps avoid jank + /// on frames where we want to evict a large number of items. Instead, we'd prefer to drop + /// the items incrementally over a number of frames, even if that means the total allocated + /// size of the cache is above the desired threshold for a small number of frames. + max_evictions_per_frame: usize, +} + +impl TextureCache { + pub fn new( + max_texture_size: i32, + mut max_texture_layers: usize, + picture_tile_sizes: &[DeviceIntSize], + initial_size: DeviceIntSize, + color_formats: TextureFormatPair, + swizzle: Option, + eviction_threshold_bytes: usize, + max_evictions_per_frame: usize, + ) -> Self { + // On MBP integrated Intel GPUs, texture arrays appear to be + // implemented as a single texture of stacked layers, and that + // texture appears to be subject to the texture size limit. As such, + // allocating more than 32 512x512 regions results in a dimension + // longer than 16k (the max texture size), causing incorrect behavior. + // + // So we clamp the number of layers on mac. This results in maximum + // texture array size of 32MB, which isn't ideal but isn't terrible + // either. OpenGL on mac is not long for this earth, so this may be + // good enough until we have WebRender on gfx-rs (on Metal). + // + // On all platforms, we also clamp the number of textures per layer to 16 + // to avoid the cost of resizing large texture arrays (at the expense + // of batching efficiency). + // + // Note that we could also define this more generally in terms of + // |max_texture_size / TEXTURE_REGION_DIMENSION|, except: + // * max_texture_size is actually clamped beyond the device limit + // by Gecko to 8192, so we'd need to thread the raw device value + // here, and: + // * The bug we're working around is likely specific to a single + // driver family, and those drivers are also likely to share + // the same max texture size of 16k. If we do encounter a driver + // with the same bug but a lower max texture size, we might need + // to rethink our strategy anyway, since a limit below 32MB might + // start to introduce performance issues. + max_texture_layers = max_texture_layers.min(16); + + let mut pending_updates = TextureUpdateList::new(); + + // Shared texture cache controls swizzling on a per-entry basis, assuming that + // the texture as a whole doesn't need to be swizzled (but only some entries do). + // It would be possible to support this, but not needed at the moment. + assert!(color_formats.internal != ImageFormat::BGRA8 || + swizzle.map_or(true, |s| s.bgra8_sampling_swizzle == Swizzle::default()) + ); + + let mut next_texture_id = CacheTextureId(1); + + TextureCache { + shared_textures: SharedTextures::new(color_formats), + picture_textures: PictureTextures::new( + initial_size, + picture_tile_sizes, + &mut next_texture_id, + &mut pending_updates, + ), + max_texture_size, + max_texture_layers, + swizzle, + debug_flags: DebugFlags::empty(), + next_id: next_texture_id, + pending_updates, + now: FrameStamp::INVALID, + lru_cache: LRUCache::new(), + shared_bytes_allocated: 0, + standalone_bytes_allocated: 0, + picture_cache_handles: Vec::new(), + manual_handles: Vec::new(), + eviction_threshold_bytes, + max_evictions_per_frame, + } + } + + /// Creates a TextureCache and sets it up with a valid `FrameStamp`, which + /// is useful for avoiding panics when instantiating the `TextureCache` + /// directly from unit test code. + #[cfg(test)] + pub fn new_for_testing( + max_texture_size: i32, + max_texture_layers: usize, + image_format: ImageFormat, + ) -> Self { + let mut cache = Self::new( + max_texture_size, + max_texture_layers, + &[], + DeviceIntSize::zero(), + TextureFormatPair::from(image_format), + None, + 64 * 1024 * 1024, + 32, + ); + let mut now = FrameStamp::first(DocumentId::new(IdNamespace(1), 1)); + now.advance(); + cache.begin_frame(now); + cache + } + + pub fn set_debug_flags(&mut self, flags: DebugFlags) { + self.debug_flags = flags; + } + + /// Clear all entries in the texture cache. This is a fairly drastic + /// step that should only be called very rarely. + pub fn clear_all(&mut self) { + // Evict all manual eviction handles + let manual_handles = mem::replace( + &mut self.manual_handles, + Vec::new(), + ); + for handle in manual_handles { + self.evict_impl(handle); + } + + // Evict all picture cache handles + let picture_handles = mem::replace( + &mut self.picture_cache_handles, + Vec::new(), + ); + for handle in picture_handles { + self.evict_impl(handle); + } + + // Evict all auto (LRU) cache handles + while let Some(entry) = self.lru_cache.pop_oldest() { + entry.evict(); + self.free(&entry); + } + + // Free the picture and shared textures + self.picture_textures.clear(&mut self.pending_updates); + self.shared_textures.clear(&mut self.pending_updates); + self.pending_updates.note_clear(); + } + + /// Called at the beginning of each frame. + pub fn begin_frame(&mut self, stamp: FrameStamp) { + debug_assert!(!self.now.is_valid()); + profile_scope!("begin_frame"); + self.now = stamp; + + // Texture cache eviction is done at the start of the frame. This ensures that + // we won't evict items that have been requested on this frame. + self.evict_items_from_cache_if_required(); + } + + pub fn end_frame(&mut self, texture_cache_profile: &mut TextureCacheProfileCounters) { + debug_assert!(self.now.is_valid()); + self.expire_old_picture_cache_tiles(); + + // Release of empty shared textures is done at the end of the frame. That way, if the + // eviction at the start of the frame frees up a texture, that is then subsequently + // used during the frame, we avoid doing a free/alloc for it. + self.shared_textures.array_alpha8_linear.release_empty_textures(&mut self.pending_updates); + self.shared_textures.array_alpha16_linear.release_empty_textures(&mut self.pending_updates); + self.shared_textures.array_color8_linear.release_empty_textures(&mut self.pending_updates); + self.shared_textures.array_color8_nearest.release_empty_textures(&mut self.pending_updates); + + self.shared_textures.array_alpha8_linear + .update_profile(&mut texture_cache_profile.pages_alpha8_linear); + self.shared_textures.array_alpha16_linear + .update_profile(&mut texture_cache_profile.pages_alpha16_linear); + self.shared_textures.array_color8_linear + .update_profile(&mut texture_cache_profile.pages_color8_linear); + self.shared_textures.array_color8_nearest + .update_profile(&mut texture_cache_profile.pages_color8_nearest); + self.picture_textures + .update_profile(&mut texture_cache_profile.pages_picture); + texture_cache_profile.shared_bytes.set(self.shared_bytes_allocated); + texture_cache_profile.standalone_bytes.set(self.standalone_bytes_allocated); + + self.now = FrameStamp::INVALID; + } + + // Request an item in the texture cache. All images that will + // be used on a frame *must* have request() called on their + // handle, to update the last used timestamp and ensure + // that resources are not flushed from the cache too early. + // + // Returns true if the image needs to be uploaded to the + // texture cache (either never uploaded, or has been + // evicted on a previous frame). + pub fn request(&mut self, handle: &TextureCacheHandle, gpu_cache: &mut GpuCache) -> bool { + match self.lru_cache.touch(handle) { + // If an image is requested that is already in the cache, + // refresh the GPU cache data associated with this item. + Some(entry) => { + entry.last_access = self.now; + entry.update_gpu_cache(gpu_cache); + false + } + None => true, + } + } + + // Returns true if the image needs to be uploaded to the + // texture cache (either never uploaded, or has been + // evicted on a previous frame). + pub fn needs_upload(&self, handle: &TextureCacheHandle) -> bool { + self.lru_cache.get_opt(handle).is_none() + } + + pub fn max_texture_size(&self) -> i32 { + self.max_texture_size + } + + #[cfg(feature = "replay")] + pub fn max_texture_layers(&self) -> usize { + self.max_texture_layers + } + + #[cfg(feature = "replay")] + pub fn picture_tile_sizes(&self) -> Vec { + self.picture_textures.tile_sizes() + } + + #[cfg(feature = "replay")] + pub fn color_formats(&self) -> TextureFormatPair { + self.shared_textures.array_color8_linear.formats.clone() + } + + #[cfg(feature = "replay")] + pub fn swizzle_settings(&self) -> Option { + self.swizzle + } + + #[cfg(feature = "replay")] + pub fn eviction_threshold_bytes(&self) -> usize { + self.eviction_threshold_bytes + } + + #[cfg(feature = "replay")] + pub fn max_evictions_per_frame(&self) -> usize { + self.max_evictions_per_frame + } + + pub fn pending_updates(&mut self) -> TextureUpdateList { + mem::replace(&mut self.pending_updates, TextureUpdateList::new()) + } + + // Update the data stored by a given texture cache handle. + pub fn update( + &mut self, + handle: &mut TextureCacheHandle, + descriptor: ImageDescriptor, + filter: TextureFilter, + data: Option, + user_data: [f32; 3], + mut dirty_rect: ImageDirtyRect, + gpu_cache: &mut GpuCache, + eviction_notice: Option<&EvictionNotice>, + uv_rect_kind: UvRectKind, + eviction: Eviction, + ) { + debug_assert!(self.now.is_valid()); + + // Determine if we need to allocate texture cache memory + // for this item. We need to reallocate if any of the following + // is true: + // - Never been in the cache + // - Has been in the cache but was evicted. + // - Exists in the cache but dimensions / format have changed. + let realloc = match self.lru_cache.get_opt(handle) { + Some(entry) => { + entry.size != descriptor.size || (entry.input_format != descriptor.format && + entry.alternative_input_format() != descriptor.format) + } + None => { + // Not allocated, or was previously allocated but has been evicted. + true + } + }; + + if realloc { + let params = CacheAllocParams { descriptor, filter, user_data, uv_rect_kind }; + self.allocate(¶ms, handle); + + // If we reallocated, we need to upload the whole item again. + dirty_rect = DirtyRect::All; + } + + // Update eviction policy (this is a no-op if it hasn't changed) + if eviction == Eviction::Manual { + if let Some(manual_handle) = self.lru_cache.set_manual_eviction(handle) { + self.manual_handles.push(manual_handle); + } + } + + let entry = self.lru_cache.get_opt_mut(handle) + .expect("BUG: handle must be valid now"); + + // Install the new eviction notice for this update, if applicable. + entry.eviction_notice = eviction_notice.cloned(); + entry.uv_rect_kind = uv_rect_kind; + + // Invalidate the contents of the resource rect in the GPU cache. + // This ensures that the update_gpu_cache below will add + // the new information to the GPU cache. + //TODO: only invalidate if the parameters change? + gpu_cache.invalidate(&entry.uv_rect_handle); + + // Upload the resource rect and texture array layer. + entry.update_gpu_cache(gpu_cache); + + // Create an update command, which the render thread processes + // to upload the new image data into the correct location + // in GPU memory. + if let Some(data) = data { + // If the swizzling is supported, we always upload in the internal + // texture format (thus avoiding the conversion by the driver). + // Otherwise, pass the external format to the driver. + let use_upload_format = self.swizzle.is_none(); + let (layer_index, origin) = entry.details.describe(); + let op = TextureCacheUpdate::new_update( + data, + &descriptor, + origin, + entry.size, + layer_index as i32, + use_upload_format, + &dirty_rect, + ); + self.pending_updates.push_update(entry.texture_id, op); + } + } + + // Check if a given texture handle has a valid allocation + // in the texture cache. + pub fn is_allocated(&self, handle: &TextureCacheHandle) -> bool { + self.lru_cache.get_opt(handle).is_some() + } + + // Check if a given texture handle was last used as recently + // as the specified number of previous frames. + pub fn is_recently_used(&self, handle: &TextureCacheHandle, margin: usize) -> bool { + self.lru_cache.get_opt(handle).map_or(false, |entry| { + entry.last_access.frame_id() + margin >= self.now.frame_id() + }) + } + + // Return the allocated size of the texture handle's associated data, + // or otherwise indicate the handle is invalid. + pub fn get_allocated_size(&self, handle: &TextureCacheHandle) -> Option { + self.lru_cache.get_opt(handle).map(|entry| { + (entry.input_format.bytes_per_pixel() * entry.size.area()) as usize + }) + } + + // Retrieve the details of an item in the cache. This is used + // during batch creation to provide the resource rect address + // to the shaders and texture ID to the batching logic. + // This function will assert in debug modes if the caller + // tries to get a handle that was not requested this frame. + pub fn get(&self, handle: &TextureCacheHandle) -> CacheItem { + let (texture_id, layer_index, uv_rect, swizzle, uv_rect_handle) = self.get_cache_location(handle); + CacheItem { + uv_rect_handle, + texture_id: TextureSource::TextureCache(texture_id, swizzle), + uv_rect, + texture_layer: layer_index as i32, + } + } + + /// A more detailed version of get(). This allows access to the actual + /// device rect of the cache allocation. + /// + /// Returns a tuple identifying the texture, the layer, the region, + /// and its GPU handle. + pub fn get_cache_location( + &self, + handle: &TextureCacheHandle, + ) -> (CacheTextureId, LayerIndex, DeviceIntRect, Swizzle, GpuCacheHandle) { + let entry = self.lru_cache + .get_opt(handle) + .expect("BUG: was dropped from cache or not updated!"); + debug_assert_eq!(entry.last_access, self.now); + let (layer_index, origin) = entry.details.describe(); + (entry.texture_id, + layer_index as usize, + DeviceIntRect::new(origin, entry.size), + entry.swizzle, + entry.uv_rect_handle) + } + + /// Internal helper function to evict a strong texture cache handle + fn evict_impl( + &mut self, + handle: FreeListHandle, + ) { + let entry = self.lru_cache.remove_manual_handle(handle); + entry.evict(); + self.free(&entry); + } + + /// Evict a texture cache handle that was previously set to be in manual + /// eviction mode. + pub fn evict_manual_handle(&mut self, handle: &TextureCacheHandle) { + // Find the strong handle that matches this weak handle. If this + // ever shows up in profiles, we can make it a hash (but the number + // of manual eviction handles is typically small). + let index = self.manual_handles.iter().position(|strong_handle| { + strong_handle.matches(handle) + }); + + if let Some(index) = index { + let handle = self.manual_handles.swap_remove(index); + self.evict_impl(handle); + } + } + + /// Expire picture cache tiles that haven't been referenced in the last frame. + /// The picture cache code manually keeps tiles alive by calling `request` on + /// them if it wants to retain a tile that is currently not visible. + fn expire_old_picture_cache_tiles(&mut self) { + for i in (0 .. self.picture_cache_handles.len()).rev() { + let evict = { + let entry = self.lru_cache.get(&self.picture_cache_handles[i]); + + // Texture cache entries can be evicted at the start of + // a frame, or at any time during the frame when a cache + // allocation is occurring. This means that entries tagged + // with eager eviction may get evicted before they have a + // chance to be requested on the current frame. Instead, + // advance the frame id of the entry by one before + // comparison. This ensures that an eager entry will + // not be evicted until it is not used for at least + // one complete frame. + let mut entry_frame_id = entry.last_access.frame_id(); + entry_frame_id.advance(); + + entry_frame_id < self.now.frame_id() + }; + + if evict { + let handle = self.picture_cache_handles.swap_remove(i); + self.evict_impl(handle); + } + } + } + + /// Evict old items from the shared and standalone caches, if we're over a + /// threshold memory usage value + fn evict_items_from_cache_if_required(&mut self) { + let mut eviction_count = 0; + + // Keep evicting while memory is above the threshold, and we haven't + // reached a maximum number of evictions this frame. + while self.current_memory_estimate() > self.eviction_threshold_bytes && eviction_count < self.max_evictions_per_frame { + match self.lru_cache.pop_oldest() { + Some(entry) => { + entry.evict(); + self.free(&entry); + eviction_count += 1; + } + None => { + // It's possible that we could fail to pop an item from the LRU list to evict, if every + // item in the cache is set to manual eviction mode. In this case, just break out of the + // loop as there's nothing we can do until the calling code manually evicts items to + // reduce the allocated cache size. + break; + } + } + } + } + + /// Return the total used bytes in standalone and shared textures. This is + /// used to determine how many textures need to be evicted to keep texture + /// cache memory usage under a reasonable limit. Note that this does not + /// include memory allocated to picture cache tiles, which are considered + /// separately for the purposes of texture cache eviction. + fn current_memory_estimate(&self) -> usize { + self.standalone_bytes_allocated + self.shared_bytes_allocated + } + + // Free a cache entry from the standalone list or shared cache. + fn free(&mut self, entry: &CacheEntry) { + match entry.details { + EntryDetails::Picture { texture_index, layer_index } => { + let picture_texture = self.picture_textures.get(texture_index); + picture_texture.slices[layer_index].uv_rect_handle = None; + if self.debug_flags.contains( + DebugFlags::TEXTURE_CACHE_DBG | + DebugFlags::TEXTURE_CACHE_DBG_CLEAR_EVICTED) + { + self.pending_updates.push_debug_clear( + entry.texture_id, + DeviceIntPoint::zero(), + picture_texture.size.width, + picture_texture.size.height, + layer_index, + ); + } + } + EntryDetails::Standalone { size_in_bytes, .. } => { + self.standalone_bytes_allocated -= size_in_bytes; + + // This is a standalone texture allocation. Free it directly. + self.pending_updates.push_free(entry.texture_id); + } + EntryDetails::Cache { origin, layer_index, .. } => { + // Free the block in the given region. + let texture_array = self.shared_textures.select(entry.input_format, entry.filter); + let unit = texture_array.units + .iter_mut() + .find(|unit| unit.texture_id == entry.texture_id) + .expect("Unable to find the associated texture array unit"); + let region = &mut unit.regions[layer_index]; + + self.shared_bytes_allocated -= region.slab_size.size_in_bytes(texture_array.formats.internal); + + if self.debug_flags.contains( + DebugFlags::TEXTURE_CACHE_DBG | + DebugFlags::TEXTURE_CACHE_DBG_CLEAR_EVICTED) + { + self.pending_updates.push_debug_clear( + entry.texture_id, + origin, + region.slab_size.width, + region.slab_size.height, + layer_index, + ); + } + region.free(origin, &mut unit.empty_regions); + } + } + } + + /// Allocate a block from the shared cache. + fn allocate_from_shared_cache( + &mut self, + params: &CacheAllocParams, + ) -> CacheEntry { + // Mutably borrow the correct texture. + let texture_array = self.shared_textures.select( + params.descriptor.format, + params.filter, + ); + let swizzle = if texture_array.formats.external == params.descriptor.format { + Swizzle::default() + } else { + match self.swizzle { + Some(_) => Swizzle::Bgra, + None => Swizzle::default(), + } + }; + + let max_texture_layers = self.max_texture_layers; + let slab_size = SlabSize::new(params.descriptor.size); + + let mut info = TextureCacheAllocInfo { + width: TEXTURE_REGION_DIMENSIONS, + height: TEXTURE_REGION_DIMENSIONS, + format: texture_array.formats.internal, + filter: texture_array.filter, + layer_count: 1, + is_shared_cache: true, + has_depth: false, + }; + + let unit_index = if let Some(index) = texture_array.units + .iter() + .position(|unit| unit.can_alloc(slab_size)) + { + index + } else if let Some(index) = texture_array.units + .iter() + .position(|unit| unit.regions.len() < max_texture_layers) + { + let unit = &mut texture_array.units[index]; + + unit.push_regions(texture_array.layers_per_allocation); + + info.layer_count = unit.regions.len() as i32; + self.pending_updates.push_realloc(unit.texture_id, info); + + index + } else { + let index = texture_array.units.len(); + texture_array.units.push(TextureArrayUnit { + texture_id: self.next_id, + regions: Vec::new(), + empty_regions: 0, + }); + + let unit = &mut texture_array.units[index]; + + unit.push_regions(texture_array.layers_per_allocation); + + info.layer_count = unit.regions.len() as i32; + self.pending_updates.push_alloc(self.next_id, info); + self.next_id.0 += 1; + index + }; + + self.shared_bytes_allocated += slab_size.size_in_bytes(texture_array.formats.internal); + + // Do the allocation. This can fail and return None + // if there are no free slots or regions available. + texture_array.alloc(params, unit_index, self.now, swizzle) + } + + // Returns true if the given image descriptor *may* be + // placed in the shared texture cache. + pub fn is_allowed_in_shared_cache( + &self, + filter: TextureFilter, + descriptor: &ImageDescriptor, + ) -> bool { + let mut allowed_in_shared_cache = true; + + // Anything larger than TEXTURE_REGION_DIMENSIONS goes in a standalone texture. + // TODO(gw): If we find pages that suffer from batch breaks in this + // case, add support for storing these in a standalone + // texture array. + if descriptor.size.width > TEXTURE_REGION_DIMENSIONS || + descriptor.size.height > TEXTURE_REGION_DIMENSIONS + { + allowed_in_shared_cache = false; + } + + // TODO(gw): For now, alpha formats of the texture cache can only be linearly sampled. + // Nearest sampling gets a standalone texture. + // This is probably rare enough that it can be fixed up later. + if filter == TextureFilter::Nearest && + descriptor.format.bytes_per_pixel() <= 2 + { + allowed_in_shared_cache = false; + } + + allowed_in_shared_cache + } + + /// Allocates a new standalone cache entry. + fn allocate_standalone_entry( + &mut self, + params: &CacheAllocParams, + ) -> CacheEntry { + let texture_id = self.next_id; + self.next_id.0 += 1; + + // Push a command to allocate device storage of the right size / format. + let info = TextureCacheAllocInfo { + width: params.descriptor.size.width, + height: params.descriptor.size.height, + format: params.descriptor.format, + filter: params.filter, + layer_count: 1, + is_shared_cache: false, + has_depth: false, + }; + + let size_in_bytes = (info.width * info.height * info.format.bytes_per_pixel()) as usize; + self.standalone_bytes_allocated += size_in_bytes; + + self.pending_updates.push_alloc(texture_id, info); + + // Special handing for BGRA8 textures that may need to be swizzled. + let swizzle = if params.descriptor.format == ImageFormat::BGRA8 { + self.swizzle.map(|s| s.bgra8_sampling_swizzle) + } else { + None + }; + + CacheEntry::new_standalone( + texture_id, + self.now, + params, + swizzle.unwrap_or_default(), + size_in_bytes, + ) + } + + /// Allocates a cache entry appropriate for the given parameters. + /// + /// This allocates from the shared cache unless the parameters do not meet + /// the shared cache requirements, in which case a standalone texture is + /// used. + fn allocate_cache_entry( + &mut self, + params: &CacheAllocParams, + ) -> CacheEntry { + assert!(!params.descriptor.size.is_empty()); + + // If this image doesn't qualify to go in the shared (batching) cache, + // allocate a standalone entry. + if self.is_allowed_in_shared_cache(params.filter, ¶ms.descriptor) { + self.allocate_from_shared_cache(params) + } else { + self.allocate_standalone_entry(params) + } + } + + /// Allocates a cache entry for the given parameters, and updates the + /// provided handle to point to the new entry. + fn allocate(&mut self, params: &CacheAllocParams, handle: &mut TextureCacheHandle) { + debug_assert!(self.now.is_valid()); + let new_cache_entry = self.allocate_cache_entry(params); + + // If the handle points to a valid cache entry, we want to replace the + // cache entry with our newly updated location. We also need to ensure + // that the storage (region or standalone) associated with the previous + // entry here gets freed. + // + // If the handle is invalid, we need to insert the data, and append the + // result to the corresponding vector. + if let Some(old_entry) = self.lru_cache.replace_or_insert(handle, new_cache_entry) { + old_entry.evict(); + self.free(&old_entry); + } + } + + // Update the data stored by a given texture cache handle for picture caching specifically. + pub fn update_picture_cache( + &mut self, + tile_size: DeviceIntSize, + handle: &mut TextureCacheHandle, + gpu_cache: &mut GpuCache, + ) { + debug_assert!(self.now.is_valid()); + debug_assert!(tile_size.width > 0 && tile_size.height > 0); + + if self.lru_cache.get_opt(handle).is_none() { + let cache_entry = self.picture_textures.get_or_allocate_tile( + tile_size, + self.now, + &mut self.next_id, + &mut self.pending_updates, + ); + + // Add the cache entry to the LRU cache, then mark it for manual eviction + // so that the lifetime is controlled by the texture cache. + + *handle = self.lru_cache.push_new(cache_entry); + + let strong_handle = self.lru_cache + .set_manual_eviction(handle) + .expect("bug: handle must be valid here"); + self.picture_cache_handles.push(strong_handle); + } + + // Upload the resource rect and texture array layer. + self.lru_cache + .get_opt_mut(handle) + .expect("BUG: handle must be valid now") + .update_gpu_cache(gpu_cache); + } + + pub fn shared_alpha_expected_format(&self) -> ImageFormat { + self.shared_textures.array_alpha8_linear.formats.external + } + + pub fn shared_color_expected_format(&self) -> ImageFormat { + self.shared_textures.array_color8_linear.formats.external + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Copy, Clone, PartialEq)] +struct SlabSize { + width: i32, + height: i32, +} + +impl SlabSize { + fn new(size: DeviceIntSize) -> Self { + let x_size = quantize_dimension(size.width); + let y_size = quantize_dimension(size.height); + + assert!(x_size > 0 && x_size <= TEXTURE_REGION_DIMENSIONS); + assert!(y_size > 0 && y_size <= TEXTURE_REGION_DIMENSIONS); + + let (width, height) = match (x_size, y_size) { + // Special cased rectangular slab pages. + (512, 0..=64) => (512, 64), + (512, 128) => (512, 128), + (512, 256) => (512, 256), + (0..=64, 512) => (64, 512), + (128, 512) => (128, 512), + (256, 512) => (256, 512), + + // If none of those fit, use a square slab size. + (x_size, y_size) => { + let square_size = cmp::max(x_size, y_size); + (square_size, square_size) + } + }; + + SlabSize { + width, + height, + } + } + + fn size_in_bytes(&self, format: ImageFormat) -> usize { + let bpp = format.bytes_per_pixel(); + (self.width * self.height * bpp) as usize + } + + fn invalid() -> SlabSize { + SlabSize { + width: 0, + height: 0, + } + } +} + +// The x/y location within a texture region of an allocation. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct TextureLocation(u8, u8); + +impl TextureLocation { + fn new(x: i32, y: i32) -> Self { + debug_assert!(x >= 0 && y >= 0 && x < 0x100 && y < 0x100); + TextureLocation(x as u8, y as u8) + } +} + +/// A region corresponds to a layer in a shared cache texture. +/// +/// All allocations within a region are of the same size. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct TextureRegion { + layer_index: usize, + slab_size: SlabSize, + free_slots: Vec, + total_slot_count: usize, +} + +impl TextureRegion { + fn new(layer_index: usize) -> Self { + TextureRegion { + layer_index, + slab_size: SlabSize::invalid(), + free_slots: Vec::new(), + total_slot_count: 0, + } + } + + // Initialize a region to be an allocator for a specific slab size. + fn init(&mut self, slab_size: SlabSize, empty_regions: &mut usize) { + debug_assert!(self.slab_size == SlabSize::invalid()); + debug_assert!(self.free_slots.is_empty()); + + self.slab_size = slab_size; + let slots_per_x_axis = TEXTURE_REGION_DIMENSIONS / self.slab_size.width; + let slots_per_y_axis = TEXTURE_REGION_DIMENSIONS / self.slab_size.height; + + // Add each block to a freelist. + for y in 0 .. slots_per_y_axis { + for x in 0 .. slots_per_x_axis { + self.free_slots.push(TextureLocation::new(x, y)); + } + } + + self.total_slot_count = self.free_slots.len(); + *empty_regions -= 1; + } + + // Deinit a region, allowing it to become a region with + // a different allocator size. + fn deinit(&mut self, empty_regions: &mut usize) { + self.slab_size = SlabSize::invalid(); + self.free_slots.clear(); + self.total_slot_count = 0; + *empty_regions += 1; + } + + fn is_empty(&self) -> bool { + self.slab_size == SlabSize::invalid() + } + + // Attempt to allocate a fixed size block from this region. + fn alloc(&mut self) -> Option { + debug_assert!(self.slab_size != SlabSize::invalid()); + + self.free_slots.pop().map(|location| { + DeviceIntPoint::new( + self.slab_size.width * location.0 as i32, + self.slab_size.height * location.1 as i32, + ) + }) + } + + // Free a block in this region. + fn free(&mut self, point: DeviceIntPoint, empty_regions: &mut usize) { + let x = point.x / self.slab_size.width; + let y = point.y / self.slab_size.height; + self.free_slots.push(TextureLocation::new(x, y)); + + // If this region is completely unused, deinit it + // so that it can become a different slab size + // as required. + if self.free_slots.len() == self.total_slot_count { + self.deinit(empty_regions); + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct TextureArrayUnit { + texture_id: CacheTextureId, + regions: Vec, + empty_regions: usize, +} + +impl TextureArrayUnit { + /// Adds a new empty region to the array. + fn push_regions(&mut self, count: i32) { + assert!(self.empty_regions <= self.regions.len()); + for _ in 0..count { + let index = self.regions.len(); + self.regions.push(TextureRegion::new(index)); + self.empty_regions += 1; + } + } + + /// Returns true if we can allocate the given entry. + fn can_alloc(&self, slab_size: SlabSize) -> bool { + self.empty_regions != 0 || self.regions.iter().any(|region| { + region.slab_size == slab_size && !region.free_slots.is_empty() + }) + } + + fn is_empty(&self) -> bool { + self.empty_regions == self.regions.len() + } +} + +/// A texture array contains a number of textures, each with a number of +/// layers, where each layer contains a region that can act as a slab allocator. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct TextureArray { + filter: TextureFilter, + formats: TextureFormatPair, + units: SmallVec<[TextureArrayUnit; 1]>, + layers_per_allocation: i32, +} + +impl TextureArray { + fn new( + formats: TextureFormatPair, + filter: TextureFilter, + layers_per_allocation: i32, + ) -> Self { + TextureArray { + formats, + filter, + units: SmallVec::new(), + layers_per_allocation, + } + } + + /// Returns the number of GPU bytes consumed by this texture array. + fn size_in_bytes(&self) -> usize { + let bpp = self.formats.internal.bytes_per_pixel() as usize; + let num_regions: usize = self.units.iter().map(|u| u.regions.len()).sum(); + num_regions * TEXTURE_REGION_PIXELS * bpp + } + + fn clear(&mut self, updates: &mut TextureUpdateList) { + for unit in self.units.drain(..) { + updates.push_free(unit.texture_id); + } + } + + fn release_empty_textures(&mut self, updates: &mut TextureUpdateList) { + self.units.retain(|unit| { + if unit.is_empty() { + updates.push_free(unit.texture_id); + + false + } else { + true + } + }); + } + + fn update_profile(&self, counter: &mut ResourceProfileCounter) { + let num_regions: usize = self.units.iter().map(|u| u.regions.len()).sum(); + counter.set(num_regions, self.size_in_bytes()); + } + + /// Allocate space in this texture array. + fn alloc( + &mut self, + params: &CacheAllocParams, + unit_index: usize, + now: FrameStamp, + swizzle: Swizzle, + ) -> CacheEntry { + // Quantize the size of the allocation to select a region to + // allocate from. + let slab_size = SlabSize::new(params.descriptor.size); + let unit = &mut self.units[unit_index]; + + // TODO(gw): For simplicity, the initial implementation just + // has a single vec<> of regions. We could easily + // make this more efficient by storing a list of + // regions for each slab size specifically... + + // Keep track of the location of an empty region, + // in case we need to select a new empty region + // after the loop. + let mut empty_region_index = None; + let mut entry_details = None; + + // Run through the existing regions of this size, and see if + // we can find a free block in any of them. + for (i, region) in unit.regions.iter_mut().enumerate() { + if region.is_empty() { + empty_region_index = Some(i); + } else if region.slab_size == slab_size { + if let Some(location) = region.alloc() { + entry_details = Some(EntryDetails::Cache { + layer_index: region.layer_index, + origin: location, + }); + break; + } + } + } + + // Find a region of the right size and try to allocate from it. + let details = match entry_details { + Some(details) => details, + None => { + let region = &mut unit.regions[empty_region_index.unwrap()]; + region.init(slab_size, &mut unit.empty_regions); + EntryDetails::Cache { + layer_index: region.layer_index, + origin: region.alloc().unwrap(), + } + } + }; + + CacheEntry { + size: params.descriptor.size, + user_data: params.user_data, + last_access: now, + details, + uv_rect_handle: GpuCacheHandle::new(), + input_format: params.descriptor.format, + filter: self.filter, + swizzle, + texture_id: unit.texture_id, + eviction_notice: None, + uv_rect_kind: params.uv_rect_kind, + } + } +} + + +/// A tracking structure for each slice in `WholeTextureArray`. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Clone, Copy, Debug)] +struct WholeTextureSlice { + uv_rect_handle: Option, +} + +/// A texture array that allocates whole slices and doesn't do any region tracking. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct WholeTextureArray { + size: DeviceIntSize, + filter: TextureFilter, + format: ImageFormat, + texture_id: CacheTextureId, + slices: Vec, + has_depth: bool, +} + +impl WholeTextureArray { + fn to_info(&self) -> TextureCacheAllocInfo { + TextureCacheAllocInfo { + width: self.size.width, + height: self.size.height, + format: self.format, + filter: self.filter, + layer_count: self.slices.len() as i32, + is_shared_cache: true, //TODO: reconsider + has_depth: self.has_depth, + } + } + + /// Returns the number of GPU bytes consumed by this texture array. + fn size_in_bytes(&self) -> usize { + let bpp = self.format.bytes_per_pixel() as usize; + self.slices.len() * (self.size.width * self.size.height) as usize * bpp + } + + /// Find an free slice. + fn find_free(&self) -> Option { + self.slices.iter().position(|slice| slice.uv_rect_handle.is_none()) + } + + /// Grow the array by the specified number of slices + fn grow(&mut self, count: usize) -> LayerIndex { + let index = self.slices.len(); + for _ in 0 .. count { + self.slices.push(WholeTextureSlice { + uv_rect_handle: None, + }); + } + index + } + + fn cache_entry_impl( + &self, + texture_index: usize, + layer_index: usize, + now: FrameStamp, + uv_rect_handle: GpuCacheHandle, + texture_id: CacheTextureId, + ) -> CacheEntry { + CacheEntry { + size: self.size, + user_data: [0.0; 3], + last_access: now, + details: EntryDetails::Picture { + texture_index, + layer_index, + }, + uv_rect_handle, + input_format: self.format, + filter: self.filter, + swizzle: Swizzle::default(), + texture_id, + eviction_notice: None, + uv_rect_kind: UvRectKind::Rect, + } + } + + /// Occupy a specified slice by a cache entry. + fn occupy( + &mut self, + texture_index: usize, + layer_index: usize, + now: FrameStamp, + ) -> CacheEntry { + let uv_rect_handle = GpuCacheHandle::new(); + assert!(self.slices[layer_index].uv_rect_handle.is_none()); + self.slices[layer_index].uv_rect_handle = Some(uv_rect_handle); + self.cache_entry_impl( + texture_index, + layer_index, + now, + uv_rect_handle, + self.texture_id, + ) + } + + /// Reset the texture array to the specified number of slices, if it's larger. + fn reset( + &mut self, num_slices: usize + ) -> Option { + if self.slices.len() <= num_slices { + None + } else { + self.slices.truncate(num_slices); + Some(self.texture_id) + } + } +} + + +impl TextureCacheUpdate { + // Constructs a TextureCacheUpdate operation to be passed to the + // rendering thread in order to do an upload to the right + // location in the texture cache. + fn new_update( + data: CachedImageData, + descriptor: &ImageDescriptor, + origin: DeviceIntPoint, + size: DeviceIntSize, + layer_index: i32, + use_upload_format: bool, + dirty_rect: &ImageDirtyRect, + ) -> TextureCacheUpdate { + let source = match data { + CachedImageData::Blob => { + panic!("The vector image should have been rasterized."); + } + CachedImageData::External(ext_image) => match ext_image.image_type { + ExternalImageType::TextureHandle(_) => { + panic!("External texture handle should not go through texture_cache."); + } + ExternalImageType::Buffer => TextureUpdateSource::External { + id: ext_image.id, + channel_index: ext_image.channel_index, + }, + }, + CachedImageData::Raw(bytes) => { + let finish = descriptor.offset + + descriptor.size.width * descriptor.format.bytes_per_pixel() + + (descriptor.size.height - 1) * descriptor.compute_stride(); + assert!(bytes.len() >= finish as usize); + + TextureUpdateSource::Bytes { data: bytes } + } + }; + let format_override = if use_upload_format { + Some(descriptor.format) + } else { + None + }; + + match *dirty_rect { + DirtyRect::Partial(dirty) => { + // the dirty rectangle doesn't have to be within the area but has to intersect it, at least + let stride = descriptor.compute_stride(); + let offset = descriptor.offset + dirty.origin.y * stride + dirty.origin.x * descriptor.format.bytes_per_pixel(); + + TextureCacheUpdate { + rect: DeviceIntRect::new( + DeviceIntPoint::new(origin.x + dirty.origin.x, origin.y + dirty.origin.y), + DeviceIntSize::new( + dirty.size.width.min(size.width - dirty.origin.x), + dirty.size.height.min(size.height - dirty.origin.y), + ), + ), + source, + stride: Some(stride), + offset, + format_override, + layer_index, + } + } + DirtyRect::All => { + TextureCacheUpdate { + rect: DeviceIntRect::new(origin, size), + source, + stride: descriptor.stride, + offset: descriptor.offset, + format_override, + layer_index, + } + } + } + } +} + +fn quantize_dimension(size: i32) -> i32 { + match size { + 0 => unreachable!(), + 1..=16 => 16, + 17..=32 => 32, + 33..=64 => 64, + 65..=128 => 128, + 129..=256 => 256, + 257..=512 => 512, + _ => panic!("Invalid dimensions for cache!"), + } +} diff --git a/third_party/webrender/webrender/src/util.rs b/third_party/webrender/webrender/src/util.rs new file mode 100644 index 00000000000..c6a10386432 --- /dev/null +++ b/third_party/webrender/webrender/src/util.rs @@ -0,0 +1,1221 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use api::BorderRadius; +use api::units::*; +use euclid::{Point2D, Rect, Size2D, Vector2D}; +use euclid::{default, Transform2D, Transform3D, Scale}; +use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf, MallocSizeOfOps}; +use plane_split::{Clipper, Polygon}; +use std::{i32, f32, fmt, ptr}; +use std::borrow::Cow; +use std::num::NonZeroUsize; +use std::os::raw::c_void; +use std::sync::Arc; +use std::mem::replace; + + +// Matches the definition of SK_ScalarNearlyZero in Skia. +const NEARLY_ZERO: f32 = 1.0 / 4096.0; + +/// A typesafe helper that separates new value construction from +/// vector growing, allowing LLVM to ideally construct the element in place. +pub struct Allocation<'a, T: 'a> { + vec: &'a mut Vec, + index: usize, +} + +impl<'a, T> Allocation<'a, T> { + // writing is safe because alloc() ensured enough capacity + // and `Allocation` holds a mutable borrow to prevent anyone else + // from breaking this invariant. + #[inline(always)] + pub fn init(self, value: T) -> usize { + unsafe { + ptr::write(self.vec.as_mut_ptr().add(self.index), value); + self.vec.set_len(self.index + 1); + } + self.index + } +} + +/// An entry into a vector, similar to `std::collections::hash_map::Entry`. +pub enum VecEntry<'a, T: 'a> { + Vacant(Allocation<'a, T>), + Occupied(&'a mut T), +} + +impl<'a, T> VecEntry<'a, T> { + #[inline(always)] + pub fn set(self, value: T) { + match self { + VecEntry::Vacant(alloc) => { alloc.init(value); } + VecEntry::Occupied(slot) => { *slot = value; } + } + } +} + +pub trait VecHelper { + /// Growns the vector by a single entry, returning the allocation. + fn alloc(&mut self) -> Allocation; + /// Either returns an existing elemenet, or grows the vector by one. + /// Doesn't expect indices to be higher than the current length. + fn entry(&mut self, index: usize) -> VecEntry; + + /// Equivalent to `mem::replace(&mut vec, Vec::new())` + fn take(&mut self) -> Self; + + /// Functionally equivalent to `mem::replace(&mut vec, Vec::new())` but tries + /// to keep the allocation in the caller if it is empty or replace it with a + /// pre-allocated vector. + fn take_and_preallocate(&mut self) -> Self; +} + +impl VecHelper for Vec { + fn alloc(&mut self) -> Allocation { + let index = self.len(); + if self.capacity() == index { + self.reserve(1); + } + Allocation { + vec: self, + index, + } + } + + fn entry(&mut self, index: usize) -> VecEntry { + if index < self.len() { + VecEntry::Occupied(unsafe { + self.get_unchecked_mut(index) + }) + } else { + assert_eq!(index, self.len()); + VecEntry::Vacant(self.alloc()) + } + } + + fn take(&mut self) -> Self { + replace(self, Vec::new()) + } + + fn take_and_preallocate(&mut self) -> Self { + let len = self.len(); + if len == 0 { + self.clear(); + return Vec::new(); + } + replace(self, Vec::with_capacity(len + 8)) + } +} + + +// Represents an optimized transform where there is only +// a scale and translation (which are guaranteed to maintain +// an axis align rectangle under transformation). The +// scaling is applied first, followed by the translation. +// TODO(gw): We should try and incorporate F <-> T units here, +// but it's a bit tricky to do that now with the +// way the current spatial tree works. +#[derive(Debug, Clone, Copy, MallocSizeOf)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct ScaleOffset { + pub scale: default::Vector2D, + pub offset: default::Vector2D, +} + +impl ScaleOffset { + pub fn identity() -> Self { + ScaleOffset { + scale: Vector2D::new(1.0, 1.0), + offset: Vector2D::zero(), + } + } + + // Construct a ScaleOffset from a transform. Returns + // None if the matrix is not a pure scale / translation. + pub fn from_transform( + m: &Transform3D, + ) -> Option { + + // To check that we have a pure scale / translation: + // Every field must match an identity matrix, except: + // - Any value present in tx,ty + // - Any non-neg value present in sx,sy (avoid negative for reflection/rotation) + + if m.m11 < 0.0 || + m.m12.abs() > NEARLY_ZERO || + m.m13.abs() > NEARLY_ZERO || + m.m14.abs() > NEARLY_ZERO || + m.m21.abs() > NEARLY_ZERO || + m.m22 < 0.0 || + m.m23.abs() > NEARLY_ZERO || + m.m24.abs() > NEARLY_ZERO || + m.m31.abs() > NEARLY_ZERO || + m.m32.abs() > NEARLY_ZERO || + (m.m33 - 1.0).abs() > NEARLY_ZERO || + m.m34.abs() > NEARLY_ZERO || + m.m43.abs() > NEARLY_ZERO || + (m.m44 - 1.0).abs() > NEARLY_ZERO { + return None; + } + + Some(ScaleOffset { + scale: Vector2D::new(m.m11, m.m22), + offset: Vector2D::new(m.m41, m.m42), + }) + } + + pub fn from_offset(offset: default::Vector2D) -> Self { + ScaleOffset { + scale: Vector2D::new(1.0, 1.0), + offset, + } + } + + pub fn inverse(&self) -> Self { + ScaleOffset { + scale: Vector2D::new( + 1.0 / self.scale.x, + 1.0 / self.scale.y, + ), + offset: Vector2D::new( + -self.offset.x / self.scale.x, + -self.offset.y / self.scale.y, + ), + } + } + + pub fn offset(&self, offset: default::Vector2D) -> Self { + self.accumulate( + &ScaleOffset { + scale: Vector2D::new(1.0, 1.0), + offset, + } + ) + } + + pub fn scale(&self, scale: f32) -> Self { + self.accumulate( + &ScaleOffset { + scale: Vector2D::new(scale, scale), + offset: Vector2D::zero(), + } + ) + } + + /// Produce a ScaleOffset that includes both self and other. + /// The 'self' ScaleOffset is applied after other. + /// This is equivalent to `Transform3D::pre_transform`. + pub fn accumulate(&self, other: &ScaleOffset) -> Self { + ScaleOffset { + scale: Vector2D::new( + self.scale.x * other.scale.x, + self.scale.y * other.scale.y, + ), + offset: Vector2D::new( + self.offset.x + self.scale.x * other.offset.x, + self.offset.y + self.scale.y * other.offset.y, + ), + } + } + + pub fn map_rect(&self, rect: &Rect) -> Rect { + Rect::new( + Point2D::new( + rect.origin.x * self.scale.x + self.offset.x, + rect.origin.y * self.scale.y + self.offset.y, + ), + Size2D::new( + rect.size.width * self.scale.x, + rect.size.height * self.scale.y, + ) + ) + } + + pub fn unmap_rect(&self, rect: &Rect) -> Rect { + Rect::new( + Point2D::new( + (rect.origin.x - self.offset.x) / self.scale.x, + (rect.origin.y - self.offset.y) / self.scale.y, + ), + Size2D::new( + rect.size.width / self.scale.x, + rect.size.height / self.scale.y, + ) + ) + } + + pub fn map_vector(&self, vector: &Vector2D) -> Vector2D { + Vector2D::new( + vector.x * self.scale.x, + vector.y * self.scale.y, + ) + } + + pub fn unmap_vector(&self, vector: &Vector2D) -> Vector2D { + Vector2D::new( + vector.x / self.scale.x, + vector.y / self.scale.y, + ) + } + + pub fn map_point(&self, point: &Point2D) -> Point2D { + Point2D::new( + point.x * self.scale.x + self.offset.x, + point.y * self.scale.y + self.offset.y, + ) + } + + pub fn unmap_point(&self, point: &Point2D) -> Point2D { + Point2D::new( + (point.x - self.offset.x) / self.scale.x, + (point.y - self.offset.y) / self.scale.y, + ) + } + + pub fn to_transform(&self) -> Transform3D { + Transform3D::new( + self.scale.x, + 0.0, + 0.0, + 0.0, + + 0.0, + self.scale.y, + 0.0, + 0.0, + + 0.0, + 0.0, + 1.0, + 0.0, + + self.offset.x, + self.offset.y, + 0.0, + 1.0, + ) + } +} + +// TODO: Implement these in euclid! +pub trait MatrixHelpers { + /// A port of the preserves2dAxisAlignment function in Skia. + /// Defined in the SkMatrix44 class. + fn preserves_2d_axis_alignment(&self) -> bool; + fn has_perspective_component(&self) -> bool; + fn has_2d_inverse(&self) -> bool; + /// Check if the matrix post-scaling on either the X or Y axes could cause geometry + /// transformed by this matrix to have scaling exceeding the supplied limit. + fn exceeds_2d_scale(&self, limit: f64) -> bool; + fn inverse_project(&self, target: &Point2D) -> Option>; + fn inverse_rect_footprint(&self, rect: &Rect) -> Option>; + fn transform_kind(&self) -> TransformedRectKind; + fn is_simple_translation(&self) -> bool; + fn is_simple_2d_translation(&self) -> bool; + fn is_2d_scale_translation(&self) -> bool; + /// Return the determinant of the 2D part of the matrix. + fn determinant_2d(&self) -> f32; + /// This function returns a point in the `Src` space that projects into zero XY. + /// It ignores the Z coordinate and is usable for "flattened" transformations, + /// since they are not generally inversible. + fn inverse_project_2d_origin(&self) -> Option>; + /// Turn Z transformation into identity. This is useful when crossing "flat" + /// transform styled stacking contexts upon traversing the coordinate systems. + fn flatten_z_output(&mut self); +} + +impl MatrixHelpers for Transform3D { + fn preserves_2d_axis_alignment(&self) -> bool { + if self.m14 != 0.0 || self.m24 != 0.0 { + return false; + } + + let mut col0 = 0; + let mut col1 = 0; + let mut row0 = 0; + let mut row1 = 0; + + if self.m11.abs() > NEARLY_ZERO { + col0 += 1; + row0 += 1; + } + if self.m12.abs() > NEARLY_ZERO { + col1 += 1; + row0 += 1; + } + if self.m21.abs() > NEARLY_ZERO { + col0 += 1; + row1 += 1; + } + if self.m22.abs() > NEARLY_ZERO { + col1 += 1; + row1 += 1; + } + + col0 < 2 && col1 < 2 && row0 < 2 && row1 < 2 + } + + fn has_perspective_component(&self) -> bool { + self.m14.abs() > NEARLY_ZERO || + self.m24.abs() > NEARLY_ZERO || + self.m34.abs() > NEARLY_ZERO || + (self.m44 - 1.0).abs() > NEARLY_ZERO + } + + fn has_2d_inverse(&self) -> bool { + self.determinant_2d() != 0.0 + } + + fn exceeds_2d_scale(&self, limit: f64) -> bool { + let limit2 = (limit * limit) as f32; + self.m11 * self.m11 + self.m12 * self.m12 > limit2 || + self.m21 * self.m21 + self.m22 * self.m22 > limit2 + } + + fn inverse_project(&self, target: &Point2D) -> Option> { + let m: Transform2D; + m = Transform2D::new( + self.m11 - target.x * self.m14, self.m12 - target.y * self.m14, + self.m21 - target.x * self.m24, self.m22 - target.y * self.m24, + self.m41 - target.x * self.m44, self.m42 - target.y * self.m44, + ); + m.inverse().map(|inv| Point2D::new(inv.m31, inv.m32)) + } + + fn inverse_rect_footprint(&self, rect: &Rect) -> Option> { + Some(Rect::from_points(&[ + self.inverse_project(&rect.origin)?, + self.inverse_project(&rect.top_right())?, + self.inverse_project(&rect.bottom_left())?, + self.inverse_project(&rect.bottom_right())?, + ])) + } + + fn transform_kind(&self) -> TransformedRectKind { + if self.preserves_2d_axis_alignment() { + TransformedRectKind::AxisAligned + } else { + TransformedRectKind::Complex + } + } + + fn is_simple_translation(&self) -> bool { + if (self.m11 - 1.0).abs() > NEARLY_ZERO || + (self.m22 - 1.0).abs() > NEARLY_ZERO || + (self.m33 - 1.0).abs() > NEARLY_ZERO || + (self.m44 - 1.0).abs() > NEARLY_ZERO { + return false; + } + + self.m12.abs() < NEARLY_ZERO && self.m13.abs() < NEARLY_ZERO && + self.m14.abs() < NEARLY_ZERO && self.m21.abs() < NEARLY_ZERO && + self.m23.abs() < NEARLY_ZERO && self.m24.abs() < NEARLY_ZERO && + self.m31.abs() < NEARLY_ZERO && self.m32.abs() < NEARLY_ZERO && + self.m34.abs() < NEARLY_ZERO + } + + fn is_simple_2d_translation(&self) -> bool { + if !self.is_simple_translation() { + return false; + } + + self.m43.abs() < NEARLY_ZERO + } + + /* is this... + * X 0 0 0 + * 0 Y 0 0 + * 0 0 1 0 + * a b 0 1 + */ + fn is_2d_scale_translation(&self) -> bool { + (self.m33 - 1.0).abs() < NEARLY_ZERO && + (self.m44 - 1.0).abs() < NEARLY_ZERO && + self.m12.abs() < NEARLY_ZERO && self.m13.abs() < NEARLY_ZERO && self.m14.abs() < NEARLY_ZERO && + self.m21.abs() < NEARLY_ZERO && self.m23.abs() < NEARLY_ZERO && self.m24.abs() < NEARLY_ZERO && + self.m31.abs() < NEARLY_ZERO && self.m32.abs() < NEARLY_ZERO && self.m34.abs() < NEARLY_ZERO && + self.m43.abs() < NEARLY_ZERO + } + + fn determinant_2d(&self) -> f32 { + self.m11 * self.m22 - self.m12 * self.m21 + } + + fn inverse_project_2d_origin(&self) -> Option> { + let det = self.determinant_2d(); + if det != 0.0 { + let x = (self.m21 * self.m42 - self.m41 * self.m22) / det; + let y = (self.m12 * self.m41 - self.m11 * self.m42) / det; + Some(Point2D::new(x, y)) + } else { + None + } + } + + fn flatten_z_output(&mut self) { + self.m13 = 0.0; + self.m23 = 0.0; + self.m33 = 1.0; + self.m43 = 0.0; + } +} + +pub trait PointHelpers +where + Self: Sized, +{ + fn snap(&self) -> Self; +} + +impl PointHelpers for Point2D { + fn snap(&self) -> Self { + Point2D::new( + (self.x + 0.5).floor(), + (self.y + 0.5).floor(), + ) + } +} + +pub trait RectHelpers +where + Self: Sized, +{ + fn from_floats(x0: f32, y0: f32, x1: f32, y1: f32) -> Self; + fn is_well_formed_and_nonempty(&self) -> bool; + fn snap(&self) -> Self; +} + +impl RectHelpers for Rect { + fn from_floats(x0: f32, y0: f32, x1: f32, y1: f32) -> Self { + Rect::new( + Point2D::new(x0, y0), + Size2D::new(x1 - x0, y1 - y0), + ) + } + + fn is_well_formed_and_nonempty(&self) -> bool { + self.size.width > 0.0 && self.size.height > 0.0 + } + + fn snap(&self) -> Self { + let origin = Point2D::new( + (self.origin.x + 0.5).floor(), + (self.origin.y + 0.5).floor(), + ); + Rect::new( + origin, + Size2D::new( + (self.origin.x + self.size.width + 0.5).floor() - origin.x, + (self.origin.y + self.size.height + 0.5).floor() - origin.y, + ), + ) + } +} + +pub trait VectorHelpers +where + Self: Sized, +{ + fn snap(&self) -> Self; +} + +impl VectorHelpers for Vector2D { + fn snap(&self) -> Self { + Vector2D::new( + (self.x + 0.5).floor(), + (self.y + 0.5).floor(), + ) + } +} + +pub fn lerp(a: f32, b: f32, t: f32) -> f32 { + (b - a) * t + a +} + +#[repr(u32)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum TransformedRectKind { + AxisAligned = 0, + Complex = 1, +} + +#[inline(always)] +pub fn pack_as_float(value: u32) -> f32 { + value as f32 + 0.5 +} + +#[inline] +fn extract_inner_rect_impl( + rect: &Rect, + radii: &BorderRadius, + k: f32, +) -> Option> { + // `k` defines how much border is taken into account + // We enforce the offsets to be rounded to pixel boundaries + // by `ceil`-ing and `floor`-ing them + + let xl = (k * radii.top_left.width.max(radii.bottom_left.width)).ceil(); + let xr = (rect.size.width - k * radii.top_right.width.max(radii.bottom_right.width)).floor(); + let yt = (k * radii.top_left.height.max(radii.top_right.height)).ceil(); + let yb = + (rect.size.height - k * radii.bottom_left.height.max(radii.bottom_right.height)).floor(); + + if xl <= xr && yt <= yb { + Some(Rect::new( + Point2D::new(rect.origin.x + xl, rect.origin.y + yt), + Size2D::new(xr - xl, yb - yt), + )) + } else { + None + } +} + +/// Return an aligned rectangle that is inside the clip region and doesn't intersect +/// any of the bounding rectangles of the rounded corners. +pub fn extract_inner_rect_safe( + rect: &Rect, + radii: &BorderRadius, +) -> Option> { + // value of `k==1.0` is used for extraction of the corner rectangles + // see `SEGMENT_CORNER_*` in `clip_shared.glsl` + extract_inner_rect_impl(rect, radii, 1.0) +} + +#[cfg(test)] +use euclid::vec3; + +#[cfg(test)] +pub mod test { + use super::*; + use euclid::default::{Point2D, Transform3D}; + use euclid::Angle; + use std::f32::consts::PI; + + #[test] + fn inverse_project() { + let m0 = Transform3D::identity(); + let p0 = Point2D::new(1.0, 2.0); + // an identical transform doesn't need any inverse projection + assert_eq!(m0.inverse_project(&p0), Some(p0)); + let m1 = Transform3D::rotation(0.0, 1.0, 0.0, Angle::radians(-PI / 3.0)); + // rotation by 60 degrees would imply scaling of X component by a factor of 2 + assert_eq!(m1.inverse_project(&p0), Some(Point2D::new(2.0, 2.0))); + } + + fn validate_convert(xref: &LayoutTransform) { + let so = ScaleOffset::from_transform(xref).unwrap(); + let xf = so.to_transform(); + assert!(xref.approx_eq(&xf)); + } + + #[test] + fn scale_offset_convert() { + let xref = LayoutTransform::translation(130.0, 200.0, 0.0); + validate_convert(&xref); + + let xref = LayoutTransform::scale(13.0, 8.0, 1.0); + validate_convert(&xref); + + let xref = LayoutTransform::scale(0.5, 0.5, 1.0) + .pre_translate(LayoutVector3D::new(124.0, 38.0, 0.0)); + validate_convert(&xref); + + let xref = LayoutTransform::scale(30.0, 11.0, 1.0) + .then_translate(vec3(50.0, 240.0, 0.0)); + validate_convert(&xref); + } + + fn validate_inverse(xref: &LayoutTransform) { + let s0 = ScaleOffset::from_transform(xref).unwrap(); + let s1 = s0.inverse().accumulate(&s0); + assert!((s1.scale.x - 1.0).abs() < NEARLY_ZERO && + (s1.scale.y - 1.0).abs() < NEARLY_ZERO && + s1.offset.x.abs() < NEARLY_ZERO && + s1.offset.y.abs() < NEARLY_ZERO, + "{:?}", + s1); + } + + #[test] + fn scale_offset_inverse() { + let xref = LayoutTransform::translation(130.0, 200.0, 0.0); + validate_inverse(&xref); + + let xref = LayoutTransform::scale(13.0, 8.0, 1.0); + validate_inverse(&xref); + + let xref = LayoutTransform::translation(124.0, 38.0, 0.0). + then_scale(0.5, 0.5, 1.0); + + validate_inverse(&xref); + + let xref = LayoutTransform::scale(30.0, 11.0, 1.0) + .then_translate(vec3(50.0, 240.0, 0.0)); + validate_inverse(&xref); + } + + fn validate_accumulate(x0: &LayoutTransform, x1: &LayoutTransform) { + let x = x1.then(&x0); + + let s0 = ScaleOffset::from_transform(x0).unwrap(); + let s1 = ScaleOffset::from_transform(x1).unwrap(); + + let s = s0.accumulate(&s1).to_transform(); + + assert!(x.approx_eq(&s), "{:?}\n{:?}", x, s); + } + + #[test] + fn scale_offset_accumulate() { + let x0 = LayoutTransform::translation(130.0, 200.0, 0.0); + let x1 = LayoutTransform::scale(7.0, 3.0, 1.0); + + validate_accumulate(&x0, &x1); + } + + #[test] + fn inverse_project_2d_origin() { + let mut m = Transform3D::identity(); + assert_eq!(m.inverse_project_2d_origin(), Some(Point2D::zero())); + m.m11 = 0.0; + assert_eq!(m.inverse_project_2d_origin(), None); + m.m21 = -2.0; + m.m22 = 0.0; + m.m12 = -0.5; + m.m41 = 1.0; + m.m42 = 0.5; + let origin = m.inverse_project_2d_origin().unwrap(); + assert_eq!(origin, Point2D::new(1.0, 0.5)); + assert_eq!(m.transform_point2d(origin), Some(Point2D::zero())); + } +} + +pub trait MaxRect { + fn max_rect() -> Self; +} + +impl MaxRect for DeviceIntRect { + fn max_rect() -> Self { + DeviceIntRect::new( + DeviceIntPoint::new(i32::MIN / 2, i32::MIN / 2), + DeviceIntSize::new(i32::MAX, i32::MAX), + ) + } +} + +impl MaxRect for Rect { + fn max_rect() -> Self { + // Having an unlimited bounding box is fine up until we try + // to cast it to `i32`, where we get `-2147483648` for any + // values larger than or equal to 2^31. + // + // Note: clamping to i32::MIN and i32::MAX is not a solution, + // with explanation left as an exercise for the reader. + const MAX_COORD: f32 = 1.0e9; + + Rect::new( + Point2D::new(-MAX_COORD, -MAX_COORD), + Size2D::new(2.0 * MAX_COORD, 2.0 * MAX_COORD), + ) + } +} + +/// An enum that tries to avoid expensive transformation matrix calculations +/// when possible when dealing with non-perspective axis-aligned transformations. +#[derive(Debug, MallocSizeOf)] +pub enum FastTransform { + /// A simple offset, which can be used without doing any matrix math. + Offset(Vector2D), + + /// A 2D transformation with an inverse. + Transform { + transform: Transform3D, + inverse: Option>, + is_2d: bool, + }, +} + +impl Clone for FastTransform { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for FastTransform { } + +impl FastTransform { + pub fn identity() -> Self { + FastTransform::Offset(Vector2D::zero()) + } + + pub fn with_vector(offset: Vector2D) -> Self { + FastTransform::Offset(offset) + } + + pub fn with_scale_offset(scale_offset: ScaleOffset) -> Self { + if scale_offset.scale == Vector2D::new(1.0, 1.0) { + FastTransform::Offset(Vector2D::from_untyped(scale_offset.offset)) + } else { + FastTransform::Transform { + transform: scale_offset.to_transform(), + inverse: Some(scale_offset.inverse().to_transform()), + is_2d: true, + } + } + } + + #[inline(always)] + pub fn with_transform(transform: Transform3D) -> Self { + if transform.is_simple_2d_translation() { + return FastTransform::Offset(Vector2D::new(transform.m41, transform.m42)); + } + let inverse = transform.inverse(); + let is_2d = transform.is_2d(); + FastTransform::Transform { transform, inverse, is_2d} + } + + pub fn to_transform(&self) -> Cow> { + match *self { + FastTransform::Offset(offset) => Cow::Owned( + Transform3D::translation(offset.x, offset.y, 0.0) + ), + FastTransform::Transform { ref transform, .. } => Cow::Borrowed(transform), + } + } + + /// Return true if this is an identity transform + #[allow(unused)] + pub fn is_identity(&self)-> bool { + match *self { + FastTransform::Offset(offset) => { + offset == Vector2D::zero() + } + FastTransform::Transform { ref transform, .. } => { + *transform == Transform3D::identity() + } + } + } + + pub fn then(&self, other: &FastTransform) -> FastTransform { + match *self { + FastTransform::Offset(offset) => match *other { + FastTransform::Offset(other_offset) => { + FastTransform::Offset(offset + other_offset * Scale::<_, _, Src>::new(1.0)) + } + FastTransform::Transform { transform: ref other_transform, .. } => { + FastTransform::with_transform( + other_transform + .with_source::() + .pre_translate(offset.to_3d()) + ) + } + } + FastTransform::Transform { ref transform, ref inverse, is_2d } => match *other { + FastTransform::Offset(other_offset) => { + FastTransform::with_transform( + transform + .then_translate(other_offset.to_3d()) + .with_destination::() + ) + } + FastTransform::Transform { transform: ref other_transform, inverse: ref other_inverse, is_2d: other_is_2d } => { + FastTransform::Transform { + transform: transform.then(other_transform), + inverse: inverse.as_ref().and_then(|self_inv| + other_inverse.as_ref().map(|other_inv| other_inv.then(self_inv)) + ), + is_2d: is_2d & other_is_2d, + } + } + } + } + } + + pub fn pre_transform( + &self, + other: &FastTransform + ) -> FastTransform { + other.then(self) + } + + pub fn pre_translate(&self, other_offset: Vector2D) -> Self { + match *self { + FastTransform::Offset(offset) => + FastTransform::Offset(offset + other_offset), + FastTransform::Transform { transform, .. } => + FastTransform::with_transform(transform.pre_translate(other_offset.to_3d())) + } + } + + pub fn then_translate(&self, other_offset: Vector2D) -> Self { + match *self { + FastTransform::Offset(offset) => { + FastTransform::Offset(offset + other_offset * Scale::<_, _, Src>::new(1.0)) + } + FastTransform::Transform { ref transform, .. } => { + let transform = transform.then_translate(other_offset.to_3d()); + FastTransform::with_transform(transform) + } + } + } + + #[inline(always)] + pub fn is_backface_visible(&self) -> bool { + match *self { + FastTransform::Offset(..) => false, + FastTransform::Transform { inverse: None, .. } => false, + //TODO: fix this properly by taking "det|M33| * det|M34| > 0" + // see https://www.w3.org/Bugs/Public/show_bug.cgi?id=23014 + FastTransform::Transform { inverse: Some(ref inverse), .. } => inverse.m33 < 0.0, + } + } + + #[inline(always)] + pub fn transform_point2d(&self, point: Point2D) -> Option> { + match *self { + FastTransform::Offset(offset) => { + let new_point = point + offset; + Some(Point2D::from_untyped(new_point.to_untyped())) + } + FastTransform::Transform { ref transform, .. } => transform.transform_point2d(point), + } + } + + #[inline(always)] + pub fn inverse(&self) -> Option> { + match *self { + FastTransform::Offset(offset) => + Some(FastTransform::Offset(Vector2D::new(-offset.x, -offset.y))), + FastTransform::Transform { transform, inverse: Some(inverse), is_2d, } => + Some(FastTransform::Transform { + transform: inverse, + inverse: Some(transform), + is_2d + }), + FastTransform::Transform { inverse: None, .. } => None, + + } + } +} + +impl From> for FastTransform { + fn from(transform: Transform3D) -> Self { + FastTransform::with_transform(transform) + } +} + +impl From> for FastTransform { + fn from(vector: Vector2D) -> Self { + FastTransform::with_vector(vector) + } +} + +pub type LayoutFastTransform = FastTransform; +pub type LayoutToWorldFastTransform = FastTransform; + +pub fn project_rect( + transform: &Transform3D, + rect: &Rect, + bounds: &Rect, +) -> Option> + where F: fmt::Debug +{ + let homogens = [ + transform.transform_point2d_homogeneous(rect.origin), + transform.transform_point2d_homogeneous(rect.top_right()), + transform.transform_point2d_homogeneous(rect.bottom_left()), + transform.transform_point2d_homogeneous(rect.bottom_right()), + ]; + + // Note: we only do the full frustum collision when the polygon approaches the camera plane. + // Otherwise, it will be clamped to the screen bounds anyway. + if homogens.iter().any(|h| h.w <= 0.0 || h.w.is_nan()) { + let mut clipper = Clipper::new(); + let polygon = Polygon::from_rect(*rect, 1); + + let planes = match Clipper::<_, _, usize>::frustum_planes( + transform, + Some(*bounds), + ) { + Ok(planes) => planes, + Err(..) => return None, + }; + + for plane in planes { + clipper.add(plane); + } + + let results = clipper.clip(polygon); + if results.is_empty() { + return None + } + + Some(Rect::from_points(results + .into_iter() + // filter out parts behind the view plane + .flat_map(|poly| &poly.points) + .map(|p| { + let mut homo = transform.transform_point2d_homogeneous(p.to_2d()); + homo.w = homo.w.max(0.00000001); // avoid infinite values + homo.to_point2d().unwrap() + }) + )) + } else { + // we just checked for all the points to be in positive hemisphere, so `unwrap` is valid + Some(Rect::from_points(&[ + homogens[0].to_point2d().unwrap(), + homogens[1].to_point2d().unwrap(), + homogens[2].to_point2d().unwrap(), + homogens[3].to_point2d().unwrap(), + ])) + } +} + +pub fn raster_rect_to_device_pixels( + rect: RasterRect, + device_pixel_scale: DevicePixelScale, +) -> DeviceRect { + let world_rect = rect * Scale::new(1.0); + let device_rect = world_rect * device_pixel_scale; + device_rect.round_out() +} + +/// Run the first callback over all elements in the array. If the callback returns true, +/// the element is removed from the array and moved to a second callback. +/// +/// This is a simple implementation waiting for Vec::drain_filter to be stable. +/// When that happens, code like: +/// +/// let filter = |op| { +/// match *op { +/// Enum::Foo | Enum::Bar => true, +/// Enum::Baz => false, +/// } +/// }; +/// drain_filter( +/// &mut ops, +/// filter, +/// |op| { +/// match op { +/// Enum::Foo => { foo(); } +/// Enum::Bar => { bar(); } +/// Enum::Baz => { unreachable!(); } +/// } +/// }, +/// ); +/// +/// Can be rewritten as: +/// +/// let filter = |op| { +/// match *op { +/// Enum::Foo | Enum::Bar => true, +/// Enum::Baz => false, +/// } +/// }; +/// for op in ops.drain_filter(filter) { +/// match op { +/// Enum::Foo => { foo(); } +/// Enum::Bar => { bar(); } +/// Enum::Baz => { unreachable!(); } +/// } +/// } +/// +/// See https://doc.rust-lang.org/std/vec/struct.Vec.html#method.drain_filter +pub fn drain_filter( + vec: &mut Vec, + mut filter: Filter, + mut action: Action, +) +where + Filter: FnMut(&mut T) -> bool, + Action: FnMut(T) +{ + let mut i = 0; + while i != vec.len() { + if filter(&mut vec[i]) { + action(vec.remove(i)); + } else { + i += 1; + } + } +} + + +#[derive(Debug)] +pub struct Recycler { + pub num_allocations: usize, +} + +impl Recycler { + /// Maximum extra capacity that a recycled vector is allowed to have. If the actual capacity + /// is larger, we re-allocate the vector storage with lower capacity. + const MAX_EXTRA_CAPACITY_PERCENT: usize = 200; + /// Minimum extra capacity to keep when re-allocating the vector storage. + const MIN_EXTRA_CAPACITY_PERCENT: usize = 20; + /// Minimum sensible vector length to consider for re-allocation. + const MIN_VECTOR_LENGTH: usize = 16; + + pub fn new() -> Self { + Recycler { + num_allocations: 0, + } + } + + /// Clear a vector for re-use, while retaining the backing memory buffer. May shrink the buffer + /// if it's currently much larger than was actually used. + pub fn recycle_vec(&mut self, vec: &mut Vec) { + let extra_capacity = (vec.capacity() - vec.len()) * 100 / vec.len().max(Self::MIN_VECTOR_LENGTH); + + if extra_capacity > Self::MAX_EXTRA_CAPACITY_PERCENT { + // Reduce capacity of the buffer if it is a lot larger than it needs to be. This prevents + // a frame with exceptionally large allocations to cause subsequent frames to retain + // more memory than they need. + //TODO: use `shrink_to` when it's stable + *vec = Vec::with_capacity(vec.len() + vec.len() * Self::MIN_EXTRA_CAPACITY_PERCENT / 100); + self.num_allocations += 1; + } else { + vec.clear(); + } + } +} + +/// Arc wrapper to support measurement via MallocSizeOf. +/// +/// Memory reporting for Arcs is tricky because of the risk of double-counting. +/// One way to measure them is to keep a table of pointers that have already been +/// traversed. The other way is to use knowledge of the program structure to +/// identify which Arc instances should be measured and which should be skipped to +/// avoid double-counting. +/// +/// This struct implements the second approach. It identifies the "main" pointer +/// to the Arc-ed resource, and measures the buffer as if it were an owned pointer. +/// The programmer should ensure that there is at most one PrimaryArc for a given +/// underlying ArcInner. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct PrimaryArc(pub Arc); + +impl ::std::ops::Deref for PrimaryArc { + type Target = Arc; + + #[inline] + fn deref(&self) -> &Arc { + &self.0 + } +} + +impl MallocShallowSizeOf for PrimaryArc { + fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + unsafe { + // This is a bit sketchy, but std::sync::Arc doesn't expose the + // base pointer. + let raw_arc_ptr: *const Arc = &self.0; + let raw_ptr_ptr: *const *const c_void = raw_arc_ptr as _; + let raw_ptr = *raw_ptr_ptr; + (ops.size_of_op)(raw_ptr) + } + } +} + +impl MallocSizeOf for PrimaryArc { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.shallow_size_of(ops) + (**self).size_of(ops) + } +} + +/// Computes the scale factors of this matrix; that is, +/// the amounts each basis vector is scaled by. +/// +/// This code comes from gecko gfx/2d/Matrix.h with the following +/// modifications: +/// +/// * Removed `xMajor` parameter. +pub fn scale_factors( + mat: &Transform3D +) -> (f32, f32) { + // Determinant is just of the 2D component. + let det = mat.m11 * mat.m22 - mat.m12 * mat.m21; + if det == 0.0 { + return (0.0, 0.0); + } + + // ignore mirroring + let det = det.abs(); + + let major = (mat.m11 * mat.m11 + mat.m12 * mat.m12).sqrt(); + let minor = if major != 0.0 { det / major } else { 0.0 }; + + (major, minor) +} + +/// Clamp scaling factor to a power of two. +/// +/// This code comes from gecko gfx/thebes/gfxUtils.cpp with the following +/// modification: +/// +/// * logs are taken in base 2 instead of base e. +pub fn clamp_to_scale_factor(val: f32, round_down: bool) -> f32 { + // Arbitary scale factor limitation. We can increase this + // for better scaling performance at the cost of worse + // quality. + const SCALE_RESOLUTION: f32 = 2.0; + + // Negative scaling is just a flip and irrelevant to + // our resolution calculation. + let val = val.abs(); + + let (val, inverse) = if val < 1.0 { + (1.0 / val, true) + } else { + (val, false) + }; + + let power = val.log2() / SCALE_RESOLUTION.log2(); + + // If power is within 1e-5 of an integer, round to nearest to + // prevent floating point errors, otherwise round up to the + // next integer value. + let power = if (power - power.round()).abs() < 1e-5 { + power.round() + } else if inverse != round_down { + // Use floor when we are either inverted or rounding down, but + // not both. + power.floor() + } else { + // Otherwise, ceil when we are not inverted and not rounding + // down, or we are inverted and rounding down. + power.ceil() + }; + + let scale = SCALE_RESOLUTION.powf(power); + + if inverse { + 1.0 / scale + } else { + scale + } +} + +/// Rounds a value up to the nearest multiple of mul +pub fn round_up_to_multiple(val: usize, mul: NonZeroUsize) -> usize { + match val % mul.get() { + 0 => val, + rem => val - rem + mul.get(), + } +} + + +#[macro_export] +macro_rules! c_str { + ($lit:expr) => { + unsafe { + std::ffi::CStr::from_ptr(concat!($lit, "\0").as_ptr() + as *const std::os::raw::c_char) + } + } +} diff --git a/third_party/webrender/webrender/tests/angle_shader_validation.rs b/third_party/webrender/webrender/tests/angle_shader_validation.rs new file mode 100644 index 00000000000..0a099d0b045 --- /dev/null +++ b/third_party/webrender/webrender/tests/angle_shader_validation.rs @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate mozangle; +extern crate webrender; +extern crate webrender_build; + +use mozangle::shaders::{BuiltInResources, Output, ShaderSpec, ShaderValidator}; +use webrender_build::shader::{ShaderFeatureFlags, ShaderVersion, build_shader_strings, get_shader_features}; + +// from glslang +const FRAGMENT_SHADER: u32 = 0x8B30; +const VERTEX_SHADER: u32 = 0x8B31; + +#[test] +fn validate_shaders() { + mozangle::shaders::initialize().unwrap(); + + let resources = BuiltInResources::default(); + let vs_validator = + ShaderValidator::new(VERTEX_SHADER, ShaderSpec::Gles3, Output::Essl, &resources).unwrap(); + + let fs_validator = + ShaderValidator::new(FRAGMENT_SHADER, ShaderSpec::Gles3, Output::Essl, &resources).unwrap(); + + for (shader, configs) in get_shader_features(ShaderFeatureFlags::GLES) { + for config in configs { + let features = config.split(",").filter(|f| !f.is_empty()).collect::>(); + + let (vs, fs) = build_shader_strings( + ShaderVersion::Gles, + &features, + shader, + &|f| webrender::get_unoptimized_shader_source(f, None) + ); + + validate(&vs_validator, shader, vs); + validate(&fs_validator, shader, fs); + } + } +} + +fn validate(validator: &ShaderValidator, name: &str, source: String) { + // Check for each `switch` to have a `default`, see + // https://github.com/servo/webrender/wiki/Driver-issues#lack-of-default-case-in-a-switch + assert_eq!(source.matches("switch").count(), source.matches("default:").count(), + "Shader '{}' doesn't have all `switch` covered with `default` cases", name); + // Run Angle validator + match validator.compile_and_translate(&[&source]) { + Ok(_) => { + println!("Shader translated succesfully: {}", name); + } + Err(_) => { + panic!( + "Shader compilation failed: {}\n{}", + name, + validator.info_log() + ); + } + } +} diff --git a/third_party/webrender/webrender/tests/bug_124.html b/third_party/webrender/webrender/tests/bug_124.html new file mode 100644 index 00000000000..9f15bbd01a0 --- /dev/null +++ b/third_party/webrender/webrender/tests/bug_124.html @@ -0,0 +1,5 @@ + +

    +
    +
    + diff --git a/third_party/webrender/webrender/tests/bug_134.html b/third_party/webrender/webrender/tests/bug_134.html new file mode 100644 index 00000000000..2d1c177a517 --- /dev/null +++ b/third_party/webrender/webrender/tests/bug_134.html @@ -0,0 +1,29 @@ + + +
    + +
    diff --git a/third_party/webrender/webrender/tests/bug_137.html b/third_party/webrender/webrender/tests/bug_137.html new file mode 100644 index 00000000000..bc76802de74 --- /dev/null +++ b/third_party/webrender/webrender/tests/bug_137.html @@ -0,0 +1,25 @@ + + +
    diff --git a/third_party/webrender/webrender/tests/bug_143.html b/third_party/webrender/webrender/tests/bug_143.html new file mode 100644 index 00000000000..7ae5c0c0339 --- /dev/null +++ b/third_party/webrender/webrender/tests/bug_143.html @@ -0,0 +1,11 @@ + +
    diff --git a/third_party/webrender/webrender/tests/bug_159.html b/third_party/webrender/webrender/tests/bug_159.html new file mode 100644 index 00000000000..325b99dc922 --- /dev/null +++ b/third_party/webrender/webrender/tests/bug_159.html @@ -0,0 +1,2 @@ +

    foobar

    +
    diff --git a/third_party/webrender/webrender/tests/bug_166.html b/third_party/webrender/webrender/tests/bug_166.html new file mode 100644 index 00000000000..c1c52b1eaa5 --- /dev/null +++ b/third_party/webrender/webrender/tests/bug_166.html @@ -0,0 +1,10 @@ + + +
    FOOBAR
    +
    FOOBAR
    diff --git a/third_party/webrender/webrender/tests/bug_176.html b/third_party/webrender/webrender/tests/bug_176.html new file mode 100644 index 00000000000..3650dad8205 --- /dev/null +++ b/third_party/webrender/webrender/tests/bug_176.html @@ -0,0 +1 @@ +
    FOOBAR
    diff --git a/third_party/webrender/webrender/tests/bug_177.html b/third_party/webrender/webrender/tests/bug_177.html new file mode 100644 index 00000000000..188f0b3d93a --- /dev/null +++ b/third_party/webrender/webrender/tests/bug_177.html @@ -0,0 +1,23 @@ + + +
    +
    +
    diff --git a/third_party/webrender/webrender/tests/bug_178.html b/third_party/webrender/webrender/tests/bug_178.html new file mode 100644 index 00000000000..e8d341f231b --- /dev/null +++ b/third_party/webrender/webrender/tests/bug_178.html @@ -0,0 +1,24 @@ + + +
    +
    +
    diff --git a/third_party/webrender/webrender/tests/bug_203a.html b/third_party/webrender/webrender/tests/bug_203a.html new file mode 100644 index 00000000000..dc46d06772c --- /dev/null +++ b/third_party/webrender/webrender/tests/bug_203a.html @@ -0,0 +1,13 @@ + + diff --git a/third_party/webrender/webrender/tests/bug_203b.html b/third_party/webrender/webrender/tests/bug_203b.html new file mode 100644 index 00000000000..72f99618898 --- /dev/null +++ b/third_party/webrender/webrender/tests/bug_203b.html @@ -0,0 +1,14 @@ + + +
    +

    content

    + diff --git a/third_party/webrender/webrender/tests/bug_servo_10136.html b/third_party/webrender/webrender/tests/bug_servo_10136.html new file mode 100644 index 00000000000..bca2e3dc29f --- /dev/null +++ b/third_party/webrender/webrender/tests/bug_servo_10136.html @@ -0,0 +1,11 @@ + + +
    whoa
    diff --git a/third_party/webrender/webrender/tests/bug_servo_10164.html b/third_party/webrender/webrender/tests/bug_servo_10164.html new file mode 100644 index 00000000000..8377818cdde --- /dev/null +++ b/third_party/webrender/webrender/tests/bug_servo_10164.html @@ -0,0 +1,19 @@ + + +
    +
    +
    diff --git a/third_party/webrender/webrender/tests/bug_servo_10307.html b/third_party/webrender/webrender/tests/bug_servo_10307.html new file mode 100644 index 00000000000..e1775939f11 --- /dev/null +++ b/third_party/webrender/webrender/tests/bug_servo_10307.html @@ -0,0 +1,12 @@ + +There should be a red square with a black border around it. +
    diff --git a/third_party/webrender/webrender/tests/bug_servo_11358.html b/third_party/webrender/webrender/tests/bug_servo_11358.html new file mode 100644 index 00000000000..2e99b6ff860 --- /dev/null +++ b/third_party/webrender/webrender/tests/bug_servo_11358.html @@ -0,0 +1,12 @@ + + diff --git a/third_party/webrender/webrender/tests/bug_servo_9983a.html b/third_party/webrender/webrender/tests/bug_servo_9983a.html new file mode 100644 index 00000000000..92830ed9b1f --- /dev/null +++ b/third_party/webrender/webrender/tests/bug_servo_9983a.html @@ -0,0 +1,17 @@ + +Test passes if there is a single 100x100px orange square. +
    diff --git a/third_party/webrender/webrender/tests/color_pattern_1.png b/third_party/webrender/webrender/tests/color_pattern_1.png new file mode 100644 index 0000000000000000000000000000000000000000..61b47eb268503c923ceb1461cb968a99895415e1 GIT binary patch literal 1693 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$6%N?Bp530R%N1DIE+9Y$2X5 zjv*Cu-rhKv>*Ofla_}!-?-9X?Yx$=oS9ruuJz#d3iTCrJ>i3;@s?R_F9Bf)PbDnyBfBoI__V@o=F8}>)Zt=Y5n)B`7zK)-uAm8vW`z1fa1+RLBL-q3Y@0YKy zcWkO*__h8fL&6N{14X0k(Qp_|1EXnRGz|>XH1L-xo-0(=PyG1>V12{j>FVdQ&MBb@ E0GEd#p#T5? literal 0 HcmV?d00001 diff --git a/third_party/webrender/webrender/tests/color_pattern_2.png b/third_party/webrender/webrender/tests/color_pattern_2.png new file mode 100644 index 0000000000000000000000000000000000000000..e8605692b4bf1785945e327ceef74192897a34a3 GIT binary patch literal 49144 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 + + + test - fixed position + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/third_party/webrender/webrender/tests/mix-blend-mode-2.html b/third_party/webrender/webrender/tests/mix-blend-mode-2.html new file mode 100644 index 00000000000..8c7b028b420 --- /dev/null +++ b/third_party/webrender/webrender/tests/mix-blend-mode-2.html @@ -0,0 +1,85 @@ + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + diff --git a/third_party/webrender/webrender/tests/mix-blend-mode.html b/third_party/webrender/webrender/tests/mix-blend-mode.html new file mode 100644 index 00000000000..565feb74fef --- /dev/null +++ b/third_party/webrender/webrender/tests/mix-blend-mode.html @@ -0,0 +1,47 @@ + + + + + + +
    +
    +
    +
    +
    + + diff --git a/third_party/webrender/webrender/tests/nav-1.html b/third_party/webrender/webrender/tests/nav-1.html new file mode 100644 index 00000000000..7c3e21bb804 --- /dev/null +++ b/third_party/webrender/webrender/tests/nav-1.html @@ -0,0 +1,15 @@ + + + + nav1 + + + + Goto nav2 + + diff --git a/third_party/webrender/webrender/tests/nav-2.html b/third_party/webrender/webrender/tests/nav-2.html new file mode 100644 index 00000000000..f05399f329b --- /dev/null +++ b/third_party/webrender/webrender/tests/nav-2.html @@ -0,0 +1,15 @@ + + + + nav2 + + + + Goto nav1 + + diff --git a/third_party/webrender/webrender_api/Cargo.toml b/third_party/webrender/webrender_api/Cargo.toml new file mode 100644 index 00000000000..1a48083a10b --- /dev/null +++ b/third_party/webrender/webrender_api/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "webrender_api" +version = "0.61.0" +authors = ["Glenn Watson "] +license = "MPL-2.0" +repository = "https://github.com/servo/webrender" +description = "Public API for WebRender" +edition = "2018" + +[features] +nightly = ["euclid/unstable", "serde/unstable"] +serialize = [] +deserialize = [] +display_list_stats = [] + +[dependencies] +app_units = "0.7" +bitflags = "1.2" +byteorder = "1.2.1" +derive_more = "0.99" +euclid = { version = "0.22.0", features = ["serde"] } +malloc_size_of_derive = "0.1" +serde = { version = "1.0", features = ["rc"] } +serde_derive = "1.0" +serde_bytes = "0.11" +time = "0.1" +malloc_size_of = { version = "0.0.1", path = "../wr_malloc_size_of", package = "wr_malloc_size_of" } +peek-poke = { version = "0.2", path = "../peek-poke", features = ["extras"] } + +[target.'cfg(target_os = "macos")'.dependencies] +core-foundation = "0.9" +core-graphics = "0.22" diff --git a/third_party/webrender/webrender_api/src/api.rs b/third_party/webrender/webrender_api/src/api.rs new file mode 100644 index 00000000000..3e8a99e9215 --- /dev/null +++ b/third_party/webrender/webrender_api/src/api.rs @@ -0,0 +1,2123 @@ +/* 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/. */ + +#![deny(missing_docs)] + +extern crate serde_bytes; + +use peek_poke::PeekPoke; +use std::cell::Cell; +use std::fmt; +use std::marker::PhantomData; +use std::os::raw::c_void; +use std::path::PathBuf; +use std::sync::Arc; +use std::u32; +use std::sync::mpsc::{Sender, Receiver, channel}; +use time::precise_time_ns; +// local imports +use crate::{display_item as di, font}; +use crate::color::{ColorU, ColorF}; +use crate::display_list::BuiltDisplayList; +use crate::font::SharedFontInstanceMap; +use crate::image::{BlobImageData, BlobImageKey, ImageData, ImageDescriptor, ImageKey}; +use crate::image::{BlobImageParams, BlobImageRequest, BlobImageResult, AsyncBlobImageRasterizer, BlobImageHandler}; +use crate::image::DEFAULT_TILE_SIZE; +use crate::resources::ApiResources; +use crate::units::*; + +/// Width and height in device pixels of image tiles. +pub type TileSize = u16; + +/// Documents are rendered in the ascending order of their associated layer values. +pub type DocumentLayer = i8; + +/// Various settings that the caller can select based on desired tradeoffs +/// between rendering quality and performance / power usage. +#[derive(Copy, Clone, Deserialize, Serialize)] +pub struct QualitySettings { + /// If true, disable creating separate picture cache slices when the + /// scroll root changes. This gives maximum opportunity to find an + /// opaque background, which enables subpixel AA. However, it is + /// usually significantly more expensive to render when scrolling. + pub force_subpixel_aa_where_possible: bool, +} + +impl Default for QualitySettings { + fn default() -> Self { + QualitySettings { + // Prefer performance over maximum subpixel AA quality, since WR + // already enables subpixel AA in more situations than other browsers. + force_subpixel_aa_where_possible: false, + } + } +} + +/// Update of a persistent resource in WebRender. +/// +/// ResourceUpdate changes keep theirs effect across display list changes. +#[derive(Clone, Deserialize, Serialize)] +pub enum ResourceUpdate { + /// See `AddImage`. + AddImage(AddImage), + /// See `UpdateImage`. + UpdateImage(UpdateImage), + /// Delete an existing image resource. + /// + /// It is invalid to continue referring to the image key in any display list + /// in the transaction that contains the `DeleteImage` message and subsequent + /// transactions. + DeleteImage(ImageKey), + /// See `AddBlobImage`. + AddBlobImage(AddBlobImage), + /// See `UpdateBlobImage`. + UpdateBlobImage(UpdateBlobImage), + /// Delete existing blob image resource. + DeleteBlobImage(BlobImageKey), + /// See `AddBlobImage::visible_area`. + SetBlobImageVisibleArea(BlobImageKey, DeviceIntRect), + /// See `AddFont`. + AddFont(AddFont), + /// Deletes an already existing font resource. + /// + /// It is invalid to continue referring to the font key in any display list + /// in the transaction that contains the `DeleteImage` message and subsequent + /// transactions. + DeleteFont(font::FontKey), + /// See `AddFontInstance`. + AddFontInstance(AddFontInstance), + /// Deletes an already existing font instance resource. + /// + /// It is invalid to continue referring to the font instance in any display + /// list in the transaction that contains the `DeleteImage` message and + /// subsequent transactions. + DeleteFontInstance(font::FontInstanceKey), +} + +impl fmt::Debug for ResourceUpdate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ResourceUpdate::AddImage(ref i) => f.write_fmt(format_args!( + "ResourceUpdate::AddImage size({:?})", + &i.descriptor.size + )), + ResourceUpdate::UpdateImage(ref i) => f.write_fmt(format_args!( + "ResourceUpdate::UpdateImage size({:?})", + &i.descriptor.size + )), + ResourceUpdate::AddBlobImage(ref i) => f.write_fmt(format_args!( + "ResourceUFpdate::AddBlobImage size({:?})", + &i.descriptor.size + )), + ResourceUpdate::UpdateBlobImage(i) => f.write_fmt(format_args!( + "ResourceUpdate::UpdateBlobImage size({:?})", + &i.descriptor.size + )), + ResourceUpdate::DeleteImage(..) => f.write_str("ResourceUpdate::DeleteImage"), + ResourceUpdate::DeleteBlobImage(..) => f.write_str("ResourceUpdate::DeleteBlobImage"), + ResourceUpdate::SetBlobImageVisibleArea(..) => f.write_str("ResourceUpdate::SetBlobImageVisibleArea"), + ResourceUpdate::AddFont(..) => f.write_str("ResourceUpdate::AddFont"), + ResourceUpdate::DeleteFont(..) => f.write_str("ResourceUpdate::DeleteFont"), + ResourceUpdate::AddFontInstance(..) => f.write_str("ResourceUpdate::AddFontInstance"), + ResourceUpdate::DeleteFontInstance(..) => f.write_str("ResourceUpdate::DeleteFontInstance"), + } + } +} + +/// A Transaction is a group of commands to apply atomically to a document. +/// +/// This mechanism ensures that: +/// - no other message can be interleaved between two commands that need to be applied together. +/// - no redundant work is performed if two commands in the same transaction cause the scene or +/// the frame to be rebuilt. +pub struct Transaction { + /// Operations affecting the scene (applied before scene building). + scene_ops: Vec, + /// Operations affecting the generation of frames (applied after scene building). + frame_ops: Vec, + + notifications: Vec, + + /// Persistent resource updates to apply as part of this transaction. + pub resource_updates: Vec, + + /// If true the transaction is piped through the scene building thread, if false + /// it will be applied directly on the render backend. + use_scene_builder_thread: bool, + + generate_frame: bool, + + /// Set to true in order to force re-rendering even if WebRender can't internally + /// detect that something has changed. + pub invalidate_rendered_frame: bool, + + low_priority: bool, +} + +impl Transaction { + /// Constructor. + pub fn new() -> Self { + Transaction { + scene_ops: Vec::new(), + frame_ops: Vec::new(), + resource_updates: Vec::new(), + notifications: Vec::new(), + use_scene_builder_thread: true, + generate_frame: false, + invalidate_rendered_frame: false, + low_priority: false, + } + } + + /// Marks this transaction to allow it to skip going through the scene builder + /// thread. + /// + /// This is useful to avoid jank in transaction associated with animated + /// property updates, panning and zooming. + /// + /// Note that transactions that skip the scene builder thread can race ahead of + /// transactions that don't skip it. + pub fn skip_scene_builder(&mut self) { + self.use_scene_builder_thread = false; + } + + /// Marks this transaction to enforce going through the scene builder thread. + pub fn use_scene_builder_thread(&mut self) { + self.use_scene_builder_thread = true; + } + + /// Returns true if the transaction has no effect. + pub fn is_empty(&self) -> bool { + !self.generate_frame && + !self.invalidate_rendered_frame && + self.scene_ops.is_empty() && + self.frame_ops.is_empty() && + self.resource_updates.is_empty() && + self.notifications.is_empty() + } + + /// Update a pipeline's epoch. + pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) { + // We track epochs before and after scene building. + // This one will be applied to the pending scene right away: + self.scene_ops.push(SceneMsg::UpdateEpoch(pipeline_id, epoch)); + // And this one will be applied to the currently built scene at the end + // of the transaction (potentially long after the scene_ops one). + self.frame_ops.push(FrameMsg::UpdateEpoch(pipeline_id, epoch)); + // We could avoid the duplication here by storing the epoch updates in a + // separate array and let the render backend schedule the updates at the + // proper times, but it wouldn't make things simpler. + } + + /// Sets the root pipeline. + /// + /// # Examples + /// + /// ``` + /// # use webrender_api::{PipelineId, RenderApiSender, Transaction}; + /// # use webrender_api::units::{DeviceIntSize}; + /// # fn example() { + /// let pipeline_id = PipelineId(0, 0); + /// let mut txn = Transaction::new(); + /// txn.set_root_pipeline(pipeline_id); + /// # } + /// ``` + pub fn set_root_pipeline(&mut self, pipeline_id: PipelineId) { + self.scene_ops.push(SceneMsg::SetRootPipeline(pipeline_id)); + } + + /// Removes data associated with a pipeline from the internal data structures. + /// If the specified `pipeline_id` is for the root pipeline, the root pipeline + /// is reset back to `None`. + pub fn remove_pipeline(&mut self, pipeline_id: PipelineId) { + self.scene_ops.push(SceneMsg::RemovePipeline(pipeline_id)); + } + + /// Supplies a new frame to WebRender. + /// + /// Non-blocking, it notifies a worker process which processes the display list. + /// + /// Note: Scrolling doesn't require an own Frame. + /// + /// Arguments: + /// + /// * `epoch`: The unique Frame ID, monotonically increasing. + /// * `background`: The background color of this pipeline. + /// * `viewport_size`: The size of the viewport for this frame. + /// * `pipeline_id`: The ID of the pipeline that is supplying this display list. + /// * `content_size`: The total screen space size of this display list's display items. + /// * `display_list`: The root Display list used in this frame. + /// * `preserve_frame_state`: If a previous frame exists which matches this pipeline + /// id, this setting determines if frame state (such as scrolling + /// position) should be preserved for this new display list. + pub fn set_display_list( + &mut self, + epoch: Epoch, + background: Option, + viewport_size: LayoutSize, + (pipeline_id, content_size, mut display_list): (PipelineId, LayoutSize, BuiltDisplayList), + preserve_frame_state: bool, + ) { + display_list.set_send_time_ns(precise_time_ns()); + self.scene_ops.push( + SceneMsg::SetDisplayList { + display_list, + epoch, + pipeline_id, + background, + viewport_size, + content_size, + preserve_frame_state, + } + ); + } + + /// Add a set of persistent resource updates to apply as part of this transaction. + pub fn update_resources(&mut self, mut resources: Vec) { + self.resource_updates.append(&mut resources); + } + + // Note: Gecko uses this to get notified when a transaction that contains + // potentially long blob rasterization or scene build is ready to be rendered. + // so that the tab-switching integration can react adequately when tab + // switching takes too long. For this use case when matters is that the + // notification doesn't fire before scene building and blob rasterization. + + /// Trigger a notification at a certain stage of the rendering pipeline. + /// + /// Not that notification requests are skipped during serialization, so is is + /// best to use them for synchronization purposes and not for things that could + /// affect the WebRender's state. + pub fn notify(&mut self, event: NotificationRequest) { + self.notifications.push(event); + } + + /// Setup the output region in the framebuffer for a given document. + pub fn set_document_view( + &mut self, + device_rect: DeviceIntRect, + device_pixel_ratio: f32, + ) { + self.scene_ops.push( + SceneMsg::SetDocumentView { + device_rect, + device_pixel_ratio, + }, + ); + } + + /// Enable copying of the output of this pipeline id to + /// an external texture for callers to consume. + pub fn enable_frame_output(&mut self, pipeline_id: PipelineId, enable: bool) { + self.scene_ops.push(SceneMsg::EnableFrameOutput(pipeline_id, enable)); + } + + /// Scrolls the scrolling layer under the `cursor` + /// + /// WebRender looks for the layer closest to the user + /// which has `ScrollPolicy::Scrollable` set. + pub fn scroll(&mut self, scroll_location: ScrollLocation, cursor: WorldPoint) { + self.frame_ops.push(FrameMsg::Scroll(scroll_location, cursor)); + } + + /// + pub fn scroll_node_with_id( + &mut self, + origin: LayoutPoint, + id: di::ExternalScrollId, + clamp: ScrollClamping, + ) { + self.frame_ops.push(FrameMsg::ScrollNodeWithId(origin, id, clamp)); + } + + /// Set the current quality / performance settings for this document. + pub fn set_quality_settings(&mut self, settings: QualitySettings) { + self.scene_ops.push(SceneMsg::SetQualitySettings { settings }); + } + + /// + pub fn set_page_zoom(&mut self, page_zoom: ZoomFactor) { + self.scene_ops.push(SceneMsg::SetPageZoom(page_zoom)); + } + + /// + pub fn set_pinch_zoom(&mut self, pinch_zoom: ZoomFactor) { + self.frame_ops.push(FrameMsg::SetPinchZoom(pinch_zoom)); + } + + /// + pub fn set_is_transform_async_zooming(&mut self, is_zooming: bool, animation_id: PropertyBindingId) { + self.frame_ops.push(FrameMsg::SetIsTransformAsyncZooming(is_zooming, animation_id)); + } + + /// + pub fn set_pan(&mut self, pan: DeviceIntPoint) { + self.frame_ops.push(FrameMsg::SetPan(pan)); + } + + /// Generate a new frame. When it's done and a RenderNotifier has been set + /// in `webrender::Renderer`, [new_frame_ready()][notifier] gets called. + /// Note that the notifier is called even if the frame generation was a + /// no-op; the arguments passed to `new_frame_ready` will provide information + /// as to when happened. + /// + /// [notifier]: trait.RenderNotifier.html#tymethod.new_frame_ready + pub fn generate_frame(&mut self) { + self.generate_frame = true; + } + + /// Invalidate rendered frame. It ensure that frame will be rendered during + /// next frame generation. WebRender could skip frame rendering if there + /// is no update. + /// But there are cases that needs to force rendering. + /// - Content of image is updated by reusing same ExternalImageId. + /// - Platform requests it if pixels become stale (like wakeup from standby). + pub fn invalidate_rendered_frame(&mut self) { + self.invalidate_rendered_frame = true; + } + + /// Supply a list of animated property bindings that should be used to resolve + /// bindings in the current display list. + pub fn update_dynamic_properties(&mut self, properties: DynamicProperties) { + self.frame_ops.push(FrameMsg::UpdateDynamicProperties(properties)); + } + + /// Add to the list of animated property bindings that should be used to + /// resolve bindings in the current display list. This is a convenience method + /// so the caller doesn't have to figure out all the dynamic properties before + /// setting them on the transaction but can do them incrementally. + pub fn append_dynamic_transform_properties(&mut self, transforms: Vec>) { + self.frame_ops.push(FrameMsg::AppendDynamicTransformProperties(transforms)); + } + + /// Consumes this object and just returns the frame ops. + pub fn get_frame_ops(self) -> Vec { + self.frame_ops + } + + fn finalize(self, document_id: DocumentId) -> Box { + Box::new(TransactionMsg { + document_id, + scene_ops: self.scene_ops, + frame_ops: self.frame_ops, + resource_updates: self.resource_updates, + notifications: self.notifications, + use_scene_builder_thread: self.use_scene_builder_thread, + generate_frame: self.generate_frame, + invalidate_rendered_frame: self.invalidate_rendered_frame, + low_priority: self.low_priority, + blob_rasterizer: None, + blob_requests: Vec::new(), + rasterized_blobs: Vec::new(), + }) + } + + /// See `ResourceUpdate::AddImage`. + pub fn add_image( + &mut self, + key: ImageKey, + descriptor: ImageDescriptor, + data: ImageData, + tiling: Option, + ) { + self.resource_updates.push(ResourceUpdate::AddImage(AddImage { + key, + descriptor, + data, + tiling, + })); + } + + /// See `ResourceUpdate::UpdateImage`. + pub fn update_image( + &mut self, + key: ImageKey, + descriptor: ImageDescriptor, + data: ImageData, + dirty_rect: &ImageDirtyRect, + ) { + self.resource_updates.push(ResourceUpdate::UpdateImage(UpdateImage { + key, + descriptor, + data, + dirty_rect: *dirty_rect, + })); + } + + /// See `ResourceUpdate::DeleteImage`. + pub fn delete_image(&mut self, key: ImageKey) { + self.resource_updates.push(ResourceUpdate::DeleteImage(key)); + } + + /// See `ResourceUpdate::AddBlobImage`. + pub fn add_blob_image( + &mut self, + key: BlobImageKey, + descriptor: ImageDescriptor, + data: Arc, + visible_rect: DeviceIntRect, + tile_size: Option, + ) { + self.resource_updates.push( + ResourceUpdate::AddBlobImage(AddBlobImage { + key, + descriptor, + data, + visible_rect, + tile_size: tile_size.unwrap_or(DEFAULT_TILE_SIZE), + }) + ); + } + + /// See `ResourceUpdate::UpdateBlobImage`. + pub fn update_blob_image( + &mut self, + key: BlobImageKey, + descriptor: ImageDescriptor, + data: Arc, + visible_rect: DeviceIntRect, + dirty_rect: &BlobDirtyRect, + ) { + self.resource_updates.push( + ResourceUpdate::UpdateBlobImage(UpdateBlobImage { + key, + descriptor, + data, + visible_rect, + dirty_rect: *dirty_rect, + }) + ); + } + + /// See `ResourceUpdate::DeleteBlobImage`. + pub fn delete_blob_image(&mut self, key: BlobImageKey) { + self.resource_updates.push(ResourceUpdate::DeleteBlobImage(key)); + } + + /// See `ResourceUpdate::SetBlobImageVisibleArea`. + pub fn set_blob_image_visible_area(&mut self, key: BlobImageKey, area: DeviceIntRect) { + self.resource_updates.push(ResourceUpdate::SetBlobImageVisibleArea(key, area)) + } + + /// See `ResourceUpdate::AddFont`. + pub fn add_raw_font(&mut self, key: font::FontKey, bytes: Vec, index: u32) { + self.resource_updates + .push(ResourceUpdate::AddFont(AddFont::Raw(key, Arc::new(bytes), index))); + } + + /// See `ResourceUpdate::AddFont`. + pub fn add_native_font(&mut self, key: font::FontKey, native_handle: font::NativeFontHandle) { + self.resource_updates + .push(ResourceUpdate::AddFont(AddFont::Native(key, native_handle))); + } + + /// See `ResourceUpdate::DeleteFont`. + pub fn delete_font(&mut self, key: font::FontKey) { + self.resource_updates.push(ResourceUpdate::DeleteFont(key)); + } + + /// See `ResourceUpdate::AddFontInstance`. + pub fn add_font_instance( + &mut self, + key: font::FontInstanceKey, + font_key: font::FontKey, + glyph_size: f32, + options: Option, + platform_options: Option, + variations: Vec, + ) { + self.resource_updates + .push(ResourceUpdate::AddFontInstance(AddFontInstance { + key, + font_key, + glyph_size, + options, + platform_options, + variations, + })); + } + + /// See `ResourceUpdate::DeleteFontInstance`. + pub fn delete_font_instance(&mut self, key: font::FontInstanceKey) { + self.resource_updates.push(ResourceUpdate::DeleteFontInstance(key)); + } + + /// A hint that this transaction can be processed at a lower priority. High- + /// priority transactions can jump ahead of regular-priority transactions, + /// but both high- and regular-priority transactions are processed in order + /// relative to other transactions of the same priority. + pub fn set_low_priority(&mut self, low_priority: bool) { + self.low_priority = low_priority; + } + + /// Returns whether this transaction is marked as low priority. + pub fn is_low_priority(&self) -> bool { + self.low_priority + } +} + +/// +pub struct DocumentTransaction { + /// + pub document_id: DocumentId, + /// + pub transaction: Transaction, +} + +/// Represents a transaction in the format sent through the channel. +pub struct TransactionMsg { + /// + pub document_id: DocumentId, + /// Changes that require re-building the scene. + pub scene_ops: Vec, + /// Changes to animated properties that do not require re-building the scene. + pub frame_ops: Vec, + /// Updates to resources that persist across display lists. + pub resource_updates: Vec, + /// Whether to trigger frame building and rendering if something has changed. + pub generate_frame: bool, + /// Whether to force frame building and rendering even if no changes are internally + /// observed. + pub invalidate_rendered_frame: bool, + /// Whether to enforce that this transaction go through the scene builder. + pub use_scene_builder_thread: bool, + /// + pub low_priority: bool, + + /// Handlers to notify at certain points of the pipeline. + pub notifications: Vec, + /// + pub blob_rasterizer: Option>, + /// + pub blob_requests: Vec, + /// + pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>, +} + +impl fmt::Debug for TransactionMsg { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "threaded={}, genframe={}, invalidate={}, low_priority={}", + self.use_scene_builder_thread, + self.generate_frame, + self.invalidate_rendered_frame, + self.low_priority, + ).unwrap(); + for scene_op in &self.scene_ops { + writeln!(f, "\t\t{:?}", scene_op).unwrap(); + } + + for frame_op in &self.frame_ops { + writeln!(f, "\t\t{:?}", frame_op).unwrap(); + } + + for resource_update in &self.resource_updates { + writeln!(f, "\t\t{:?}", resource_update).unwrap(); + } + Ok(()) + } +} + +impl TransactionMsg { + /// Returns true if this transaction has no effect. + pub fn is_empty(&self) -> bool { + !self.generate_frame && + !self.invalidate_rendered_frame && + self.scene_ops.is_empty() && + self.frame_ops.is_empty() && + self.resource_updates.is_empty() && + self.notifications.is_empty() + } +} + +/// Creates an image resource with provided parameters. +/// +/// Must be matched with a `DeleteImage` at some point to prevent memory leaks. +#[derive(Clone, Deserialize, Serialize)] +pub struct AddImage { + /// A key to identify the image resource. + pub key: ImageKey, + /// Properties of the image. + pub descriptor: ImageDescriptor, + /// The pixels of the image. + pub data: ImageData, + /// An optional tiling scheme to apply when storing the image's data + /// on the GPU. Applies to both width and heights of the tiles. + /// + /// Note that WebRender may internally chose to tile large images + /// even if this member is set to `None`. + pub tiling: Option, +} + +/// Updates an already existing image resource. +#[derive(Clone, Deserialize, Serialize)] +pub struct UpdateImage { + /// The key identfying the image resource to update. + pub key: ImageKey, + /// Properties of the image. + pub descriptor: ImageDescriptor, + /// The pixels of the image. + pub data: ImageData, + /// An optional dirty rect that lets WebRender optimize the amount of + /// data to transfer to the GPU. + /// + /// The data provided must still represent the entire image. + pub dirty_rect: ImageDirtyRect, +} + +/// Creates a blob-image resource with provided parameters. +/// +/// Must be matched with a `DeleteImage` at some point to prevent memory leaks. +#[derive(Clone, Deserialize, Serialize)] +pub struct AddBlobImage { + /// A key to identify the blob-image resource. + pub key: BlobImageKey, + /// Properties of the image. + pub descriptor: ImageDescriptor, + /// The blob-image's serialized commands. + pub data: Arc, + /// The portion of the plane in the blob-image's internal coordinate + /// system that is stretched to fill the image display item. + /// + /// Unlike regular images, blob images are not limited in size. The + /// top-left corner of their internal coordinate system is also not + /// necessary at (0, 0). + /// This means that blob images can be updated to insert/remove content + /// in any direction to support panning and zooming. + pub visible_rect: DeviceIntRect, + /// The blob image's tile size to apply when rasterizing the blob-image + /// and when storing its rasterized data on the GPU. + /// Applies to both width and heights of the tiles. + /// + /// All blob images are tiled. + pub tile_size: TileSize, +} + +/// Updates an already existing blob-image resource. +#[derive(Clone, Deserialize, Serialize)] +pub struct UpdateBlobImage { + /// The key identfying the blob-image resource to update. + pub key: BlobImageKey, + /// Properties of the image. + pub descriptor: ImageDescriptor, + /// The blob-image's serialized commands. + pub data: Arc, + /// See `AddBlobImage::visible_rect`. + pub visible_rect: DeviceIntRect, + /// An optional dirty rect that lets WebRender optimize the amount of + /// data to to rasterize and transfer to the GPU. + pub dirty_rect: BlobDirtyRect, +} + +/// Creates a font resource. +/// +/// Must be matched with a corresponding `ResourceUpdate::DeleteFont` at some point to prevent +/// memory leaks. +#[derive(Clone, Deserialize, Serialize)] +pub enum AddFont { + /// + Raw(font::FontKey, Arc>, u32), + /// + Native(font::FontKey, font::NativeFontHandle), +} + +/// Describe an item that matched a hit-test query. +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct HitTestItem { + /// The pipeline that the display item that was hit belongs to. + pub pipeline: PipelineId, + + /// The tag of the hit display item. + pub tag: di::ItemTag, + + /// The hit point in the coordinate space of the "viewport" of the display item. The + /// viewport is the scroll node formed by the root reference frame of the display item's + /// pipeline. + pub point_in_viewport: LayoutPoint, + + /// The coordinates of the original hit test point relative to the origin of this item. + /// This is useful for calculating things like text offsets in the client. + pub point_relative_to_item: LayoutPoint, +} + +/// Returned by `RenderApi::hit_test`. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct HitTestResult { + /// List of items that are match the hit-test query. + pub items: Vec, +} + +bitflags! { + #[derive(Deserialize, MallocSizeOf, Serialize)] + /// + pub struct HitTestFlags: u8 { + /// + const FIND_ALL = 0b00000001; + /// + const POINT_RELATIVE_TO_PIPELINE_VIEWPORT = 0b00000010; + } +} + +/// Creates a font instance resource. +/// +/// Must be matched with a corresponding `DeleteFontInstance` at some point +/// to prevent memory leaks. +#[derive(Clone, Deserialize, Serialize)] +pub struct AddFontInstance { + /// A key to identify the font instance. + pub key: font::FontInstanceKey, + /// The font resource's key. + pub font_key: font::FontKey, + /// Glyph size in app units. + pub glyph_size: f32, + /// + pub options: Option, + /// + pub platform_options: Option, + /// + pub variations: Vec, +} + +/// Frame messages affect building the scene. +pub enum SceneMsg { + /// + UpdateEpoch(PipelineId, Epoch), + /// + SetPageZoom(ZoomFactor), + /// + SetRootPipeline(PipelineId), + /// + RemovePipeline(PipelineId), + /// + EnableFrameOutput(PipelineId, bool), + /// + SetDisplayList { + /// + display_list: BuiltDisplayList, + /// + epoch: Epoch, + /// + pipeline_id: PipelineId, + /// + background: Option, + /// + viewport_size: LayoutSize, + /// + content_size: LayoutSize, + /// + preserve_frame_state: bool, + }, + /// + SetDocumentView { + /// + device_rect: DeviceIntRect, + /// + device_pixel_ratio: f32, + }, + /// Set the current quality / performance configuration for this document. + SetQualitySettings { + /// The set of available quality / performance config values. + settings: QualitySettings, + }, +} + +/// Frame messages affect frame generation (applied after building the scene). +pub enum FrameMsg { + /// + UpdateEpoch(PipelineId, Epoch), + /// + HitTest(Option, WorldPoint, HitTestFlags, Sender), + /// + RequestHitTester(Sender>), + /// + SetPan(DeviceIntPoint), + /// + Scroll(ScrollLocation, WorldPoint), + /// + ScrollNodeWithId(LayoutPoint, di::ExternalScrollId, ScrollClamping), + /// + GetScrollNodeState(Sender>), + /// + UpdateDynamicProperties(DynamicProperties), + /// + AppendDynamicTransformProperties(Vec>), + /// + SetPinchZoom(ZoomFactor), + /// + SetIsTransformAsyncZooming(bool, PropertyBindingId), +} + +impl fmt::Debug for SceneMsg { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match *self { + SceneMsg::UpdateEpoch(..) => "SceneMsg::UpdateEpoch", + SceneMsg::SetDisplayList { .. } => "SceneMsg::SetDisplayList", + SceneMsg::SetPageZoom(..) => "SceneMsg::SetPageZoom", + SceneMsg::RemovePipeline(..) => "SceneMsg::RemovePipeline", + SceneMsg::EnableFrameOutput(..) => "SceneMsg::EnableFrameOutput", + SceneMsg::SetDocumentView { .. } => "SceneMsg::SetDocumentView", + SceneMsg::SetRootPipeline(..) => "SceneMsg::SetRootPipeline", + SceneMsg::SetQualitySettings { .. } => "SceneMsg::SetQualitySettings", + }) + } +} + +impl fmt::Debug for FrameMsg { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match *self { + FrameMsg::UpdateEpoch(..) => "FrameMsg::UpdateEpoch", + FrameMsg::HitTest(..) => "FrameMsg::HitTest", + FrameMsg::RequestHitTester(..) => "FrameMsg::RequestHitTester", + FrameMsg::SetPan(..) => "FrameMsg::SetPan", + FrameMsg::Scroll(..) => "FrameMsg::Scroll", + FrameMsg::ScrollNodeWithId(..) => "FrameMsg::ScrollNodeWithId", + FrameMsg::GetScrollNodeState(..) => "FrameMsg::GetScrollNodeState", + FrameMsg::UpdateDynamicProperties(..) => "FrameMsg::UpdateDynamicProperties", + FrameMsg::AppendDynamicTransformProperties(..) => "FrameMsg::AppendDynamicTransformProperties", + FrameMsg::SetPinchZoom(..) => "FrameMsg::SetPinchZoom", + FrameMsg::SetIsTransformAsyncZooming(..) => "FrameMsg::SetIsTransformAsyncZooming", + }) + } +} + +bitflags!{ + /// Bit flags for WR stages to store in a capture. + // Note: capturing `FRAME` without `SCENE` is not currently supported. + pub struct CaptureBits: u8 { + /// + const SCENE = 0x1; + /// + const FRAME = 0x2; + /// + const TILE_CACHE = 0x4; + /// + const EXTERNAL_RESOURCES = 0x8; + } +} + +bitflags!{ + /// Mask for clearing caches in debug commands. + pub struct ClearCache: u8 { + /// + const IMAGES = 0b1; + /// + const GLYPHS = 0b01; + /// + const GLYPH_DIMENSIONS = 0b001; + /// + const RENDER_TASKS = 0b0001; + /// + const TEXTURE_CACHE = 0b00001; + } +} + +/// Information about a loaded capture of each document +/// that is returned by `RenderBackend`. +#[derive(Clone, Debug)] +pub struct CapturedDocument { + /// + pub document_id: DocumentId, + /// + pub root_pipeline_id: Option, +} + +/// Update of the state of built-in debugging facilities. +#[derive(Clone)] +pub enum DebugCommand { + /// Sets the provided debug flags. + SetFlags(DebugFlags), + /// Configure if dual-source blending is used, if available. + EnableDualSourceBlending(bool), + /// Fetch current documents and display lists. + FetchDocuments, + /// Fetch current passes and batches. + FetchPasses, + // TODO: This should be called FetchClipScrollTree. However, that requires making + // changes to webrender's web debugger ui, touching a 4Mb minified file that + // is too big to submit through the conventional means. + /// Fetch the spatial tree. + FetchClipScrollTree, + /// Fetch render tasks. + FetchRenderTasks, + /// Fetch screenshot. + FetchScreenshot, + /// Save a capture of all the documents state. + SaveCapture(PathBuf, CaptureBits), + /// Load a capture of all the documents state. + LoadCapture(PathBuf, Option<(u32, u32)>, Sender), + /// Start capturing a sequence of scene/frame changes. + StartCaptureSequence(PathBuf, CaptureBits), + /// Stop capturing a sequence of scene/frame changes. + StopCaptureSequence, + /// Clear cached resources, forcing them to be re-uploaded from templates. + ClearCaches(ClearCache), + /// Enable/disable native compositor usage + EnableNativeCompositor(bool), + /// Enable/disable parallel job execution with rayon. + EnableMultithreading(bool), + /// Sets the maximum amount of existing batches to visit before creating a new one. + SetBatchingLookback(u32), + /// Invalidate GPU cache, forcing the update from the CPU mirror. + InvalidateGpuCache, + /// Causes the scene builder to pause for a given amount of milliseconds each time it + /// processes a transaction. + SimulateLongSceneBuild(u32), + /// Causes the low priority scene builder to pause for a given amount of milliseconds + /// each time it processes a transaction. + SimulateLongLowPrioritySceneBuild(u32), + /// Set an override tile size to use for picture caches + SetPictureTileSize(Option), +} + +/// Message sent by the `RenderApi` to the render backend thread. +pub enum ApiMsg { + /// Gets the glyph dimensions + GetGlyphDimensions(font::GlyphDimensionRequest), + /// Gets the glyph indices from a string + GetGlyphIndices(font::GlyphIndexRequest), + /// Adds a new document namespace. + CloneApi(Sender), + /// Adds a new document namespace. + CloneApiByClient(IdNamespace), + /// Adds a new document with given initial size. + AddDocument(DocumentId, DeviceIntSize, DocumentLayer), + /// A message targeted at a particular document. + UpdateDocuments(Vec>), + /// Deletes an existing document. + DeleteDocument(DocumentId), + /// An opaque handle that must be passed to the render notifier. It is used by Gecko + /// to forward gecko-specific messages to the render thread preserving the ordering + /// within the other messages. + ExternalEvent(ExternalEvent), + /// Removes all resources associated with a namespace. + ClearNamespace(IdNamespace), + /// Flush from the caches anything that isn't necessary, to free some memory. + MemoryPressure, + /// Collects a memory report. + ReportMemory(Sender>), + /// Change debugging options. + DebugCommand(DebugCommand), + /// Wakes the render backend's event loop up. Needed when an event is communicated + /// through another channel. + WakeUp, + /// See `RenderApi::wake_scene_builder`. + WakeSceneBuilder, + /// Block until a round-trip to the scene builder thread has completed. This + /// ensures that any transactions (including ones deferred to the scene + /// builder thread) have been processed. + FlushSceneBuilder(Sender<()>), + /// Shut the WebRender instance down. + ShutDown(Option>), +} + +impl fmt::Debug for ApiMsg { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match *self { + ApiMsg::GetGlyphDimensions(..) => "ApiMsg::GetGlyphDimensions", + ApiMsg::GetGlyphIndices(..) => "ApiMsg::GetGlyphIndices", + ApiMsg::CloneApi(..) => "ApiMsg::CloneApi", + ApiMsg::CloneApiByClient(..) => "ApiMsg::CloneApiByClient", + ApiMsg::AddDocument(..) => "ApiMsg::AddDocument", + ApiMsg::UpdateDocuments(..) => "ApiMsg::UpdateDocuments", + ApiMsg::DeleteDocument(..) => "ApiMsg::DeleteDocument", + ApiMsg::ExternalEvent(..) => "ApiMsg::ExternalEvent", + ApiMsg::ClearNamespace(..) => "ApiMsg::ClearNamespace", + ApiMsg::MemoryPressure => "ApiMsg::MemoryPressure", + ApiMsg::ReportMemory(..) => "ApiMsg::ReportMemory", + ApiMsg::DebugCommand(..) => "ApiMsg::DebugCommand", + ApiMsg::ShutDown(..) => "ApiMsg::ShutDown", + ApiMsg::WakeUp => "ApiMsg::WakeUp", + ApiMsg::WakeSceneBuilder => "ApiMsg::WakeSceneBuilder", + ApiMsg::FlushSceneBuilder(..) => "ApiMsg::FlushSceneBuilder", + }) + } +} + +/// An epoch identifies the state of a pipeline in time. +/// +/// This is mostly used as a synchronization mechanism to observe how/when particular pipeline +/// updates propagate through WebRender and are applied at various stages. +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] +pub struct Epoch(pub u32); + +impl Epoch { + /// Magic invalid epoch value. + pub fn invalid() -> Epoch { + Epoch(u32::MAX) + } +} + +/// ID namespaces uniquely identify different users of WebRender's API. +/// +/// For example in Gecko each content process uses a separate id namespace. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Eq, MallocSizeOf, PartialEq, Hash, Ord, PartialOrd, PeekPoke)] +#[derive(Deserialize, Serialize)] +pub struct IdNamespace(pub u32); + +/// A key uniquely identifying a WebRender document. +/// +/// Instances can manage one or several documents (using the same render backend thread). +/// Each document will internally correspond to a single scene, and scenes are made of +/// one or several pipelines. +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub struct DocumentId { + /// + pub namespace_id: IdNamespace, + /// + pub id: u32, +} + +impl DocumentId { + /// + pub fn new(namespace_id: IdNamespace, id: u32) -> Self { + DocumentId { + namespace_id, + id, + } + } + + /// + pub const INVALID: DocumentId = DocumentId { namespace_id: IdNamespace(0), id: 0 }; +} + +/// This type carries no valuable semantics for WR. However, it reflects the fact that +/// clients (Servo) may generate pipelines by different semi-independent sources. +/// These pipelines still belong to the same `IdNamespace` and the same `DocumentId`. +/// Having this extra Id field enables them to generate `PipelineId` without collision. +pub type PipelineSourceId = u32; + +/// From the point of view of WR, `PipelineId` is completely opaque and generic as long as +/// it's clonable, serializable, comparable, and hashable. +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub struct PipelineId(pub PipelineSourceId, pub u32); + +impl Default for PipelineId { + fn default() -> Self { + PipelineId::dummy() + } +} + +impl PipelineId { + /// + pub fn dummy() -> Self { + PipelineId(0, 0) + } +} + +/// +#[derive(Copy, Clone, Debug, MallocSizeOf, Serialize, Deserialize)] +pub enum ClipIntern {} + +/// +#[derive(Copy, Clone, Debug, MallocSizeOf, Serialize, Deserialize)] +pub enum FilterDataIntern {} + +/// Information specific to a primitive type that +/// uniquely identifies a primitive template by key. +#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash, Serialize, Deserialize)] +pub enum PrimitiveKeyKind { + /// Clear an existing rect, used for special effects on some platforms. + Clear, + /// + Rectangle { + /// + color: PropertyBinding, + }, +} + +/// Meta-macro to enumerate the various interner identifiers and types. +/// +/// IMPORTANT: Keep this synchronized with the list in mozilla-central located at +/// gfx/webrender_bindings/webrender_ffi.h +/// +/// Note that this could be a lot less verbose if concat_idents! were stable. :-( +#[macro_export] +macro_rules! enumerate_interners { + ($macro_name: ident) => { + $macro_name! { + clip: ClipIntern, + prim: PrimitiveKeyKind, + normal_border: NormalBorderPrim, + image_border: ImageBorder, + image: Image, + yuv_image: YuvImage, + line_decoration: LineDecoration, + linear_grad: LinearGradient, + radial_grad: RadialGradient, + conic_grad: ConicGradient, + picture: Picture, + text_run: TextRun, + filter_data: FilterDataIntern, + backdrop: Backdrop, + } + } +} + +macro_rules! declare_interning_memory_report { + ( $( $name:ident: $ty:ident, )+ ) => { + /// + #[repr(C)] + #[derive(AddAssign, Clone, Debug, Default)] + pub struct InternerSubReport { + $( + /// + pub $name: usize, + )+ + } + } +} + +enumerate_interners!(declare_interning_memory_report); + +/// Memory report for interning-related data structures. +/// cbindgen:derive-eq=false +#[repr(C)] +#[derive(Clone, Debug, Default)] +pub struct InterningMemoryReport { + /// + pub interners: InternerSubReport, + /// + pub data_stores: InternerSubReport, +} + +impl ::std::ops::AddAssign for InterningMemoryReport { + fn add_assign(&mut self, other: InterningMemoryReport) { + self.interners += other.interners; + self.data_stores += other.data_stores; + } +} + +/// Collection of heap sizes, in bytes. +/// cbindgen:derive-eq=false +#[repr(C)] +#[allow(missing_docs)] +#[derive(AddAssign, Clone, Debug, Default)] +pub struct MemoryReport { + // + // CPU Memory. + // + pub clip_stores: usize, + pub gpu_cache_metadata: usize, + pub gpu_cache_cpu_mirror: usize, + pub render_tasks: usize, + pub hit_testers: usize, + pub fonts: usize, + pub images: usize, + pub rasterized_blobs: usize, + pub shader_cache: usize, + pub interning: InterningMemoryReport, + pub display_list: usize, + + // + // GPU memory. + // + pub gpu_cache_textures: usize, + pub vertex_data_textures: usize, + pub render_target_textures: usize, + pub texture_cache_textures: usize, + pub depth_target_textures: usize, + pub swap_chain: usize, +} + +/// A C function that takes a pointer to a heap allocation and returns its size. +/// +/// This is borrowed from the malloc_size_of crate, upon which we want to avoid +/// a dependency from WebRender. +pub type VoidPtrToSizeFn = unsafe extern "C" fn(ptr: *const c_void) -> usize; + +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +struct ResourceId(pub u32); + +/// An opaque pointer-sized value. +#[repr(C)] +#[derive(Clone)] +pub struct ExternalEvent { + raw: usize, +} + +unsafe impl Send for ExternalEvent {} + +impl ExternalEvent { + /// Creates the event from an opaque pointer-sized value. + pub fn from_raw(raw: usize) -> Self { + ExternalEvent { raw } + } + /// Consumes self to make it obvious that the event should be forwarded only once. + pub fn unwrap(self) -> usize { + self.raw + } +} + +/// Describe whether or not scrolling should be clamped by the content bounds. +#[derive(Clone, Deserialize, Serialize)] +pub enum ScrollClamping { + /// + ToContentBounds, + /// + NoClamping, +} + +/// Allows the API to communicate with WebRender. +/// +/// This object is created along with the `Renderer` and it's main use from a +/// user perspective is to create one or several `RenderApi` objects. +pub struct RenderApiSender { + api_sender: Sender, + blob_image_handler: Option>, + shared_font_instances: SharedFontInstanceMap, +} + +impl RenderApiSender { + /// Used internally by the `Renderer`. + pub fn new( + api_sender: Sender, + blob_image_handler: Option>, + shared_font_instances: SharedFontInstanceMap, + ) -> Self { + RenderApiSender { + api_sender, + blob_image_handler, + shared_font_instances, + } + } + + /// Creates a new resource API object with a dedicated namespace. + pub fn create_api(&self) -> RenderApi { + let (sync_tx, sync_rx) = channel(); + let msg = ApiMsg::CloneApi(sync_tx); + self.api_sender.send(msg).expect("Failed to send CloneApi message"); + let namespace_id = match sync_rx.recv() { + Ok(id) => id, + Err(e) => { + // This is used to discover the underlying cause of https://github.com/servo/servo/issues/13480. + let webrender_is_alive = self.api_sender.send(ApiMsg::WakeUp); + if webrender_is_alive.is_err() { + panic!("WebRender was shut down before processing CloneApi: {}", e); + } else { + panic!("CloneApi message response was dropped while WebRender was still alive: {}", e); + } + } + }; + RenderApi { + api_sender: self.api_sender.clone(), + namespace_id, + next_id: Cell::new(ResourceId(0)), + resources: ApiResources::new( + self.blob_image_handler.as_ref().map(|handler| handler.create_similar()), + self.shared_font_instances.clone(), + ), + } + } + + /// Creates a new resource API object with a dedicated namespace. + /// Namespace id is allocated by client. + /// + /// The function could be used only when RendererOptions::namespace_alloc_by_client is true. + /// When the option is true, create_api() could not be used to prevent namespace id conflict. + pub fn create_api_by_client(&self, namespace_id: IdNamespace) -> RenderApi { + let msg = ApiMsg::CloneApiByClient(namespace_id); + self.api_sender.send(msg).expect("Failed to send CloneApiByClient message"); + RenderApi { + api_sender: self.api_sender.clone(), + namespace_id, + next_id: Cell::new(ResourceId(0)), + resources: ApiResources::new( + self.blob_image_handler.as_ref().map(|handler| handler.create_similar()), + self.shared_font_instances.clone(), + ), + } + } +} + +bitflags! { + /// Flags to enable/disable various builtin debugging tools. + #[repr(C)] + #[derive(Default, Deserialize, MallocSizeOf, Serialize)] + pub struct DebugFlags: u32 { + /// Display the frame profiler on screen. + const PROFILER_DBG = 1 << 0; + /// Display intermediate render targets on screen. + const RENDER_TARGET_DBG = 1 << 1; + /// Display all texture cache pages on screen. + const TEXTURE_CACHE_DBG = 1 << 2; + /// Display GPU timing results. + const GPU_TIME_QUERIES = 1 << 3; + /// Query the number of pixels that pass the depth test divided and show it + /// in the profiler as a percentage of the number of pixels in the screen + /// (window width times height). + const GPU_SAMPLE_QUERIES = 1 << 4; + /// Render each quad with their own draw call. + /// + /// Terrible for performance but can help with understanding the drawing + /// order when inspecting renderdoc or apitrace recordings. + const DISABLE_BATCHING = 1 << 5; + /// Display the pipeline epochs. + const EPOCHS = 1 << 6; + /// Reduce the amount of information displayed by the profiler so that + /// it occupies less screen real-estate. + const COMPACT_PROFILER = 1 << 7; + /// Print driver messages to stdout. + const ECHO_DRIVER_MESSAGES = 1 << 8; + /// Show an indicator that moves every time a frame is rendered. + const NEW_FRAME_INDICATOR = 1 << 9; + /// Show an indicator that moves every time a scene is built. + const NEW_SCENE_INDICATOR = 1 << 10; + /// Show an overlay displaying overdraw amount. + const SHOW_OVERDRAW = 1 << 11; + /// Display the contents of GPU cache. + const GPU_CACHE_DBG = 1 << 12; + /// Show a red bar that moves each time a slow frame is detected. + const SLOW_FRAME_INDICATOR = 1 << 13; + /// Clear evicted parts of the texture cache for debugging purposes. + const TEXTURE_CACHE_DBG_CLEAR_EVICTED = 1 << 14; + /// Show picture caching debug overlay + const PICTURE_CACHING_DBG = 1 << 15; + /// Highlight all primitives with colors based on kind. + const PRIMITIVE_DBG = 1 << 16; + /// Draw a zoom widget showing part of the framebuffer zoomed in. + const ZOOM_DBG = 1 << 17; + /// Scale the debug renderer down for a smaller screen. This will disrupt + /// any mapping between debug display items and page content, so shouldn't + /// be used with overlays like the picture caching or primitive display. + const SMALL_SCREEN = 1 << 18; + /// Disable various bits of the WebRender pipeline, to help narrow + /// down where slowness might be coming from. + const DISABLE_OPAQUE_PASS = 1 << 19; + /// + const DISABLE_ALPHA_PASS = 1 << 20; + /// + const DISABLE_CLIP_MASKS = 1 << 21; + /// + const DISABLE_TEXT_PRIMS = 1 << 22; + /// + const DISABLE_GRADIENT_PRIMS = 1 << 23; + /// + const OBSCURE_IMAGES = 1 << 24; + /// Taint the transparent area of the glyphs with a random opacity to easily + /// see when glyphs are re-rasterized. + const GLYPH_FLASHING = 1 << 25; + /// The profiler only displays information that is out of the ordinary. + const SMART_PROFILER = 1 << 26; + /// Dynamically control whether picture caching is enabled. + const DISABLE_PICTURE_CACHING = 1 << 27; + /// If set, dump picture cache invalidation debug to console. + const INVALIDATION_DBG = 1 << 28; + /// Log tile cache to memory for later saving as part of wr-capture + const TILE_CACHE_LOGGING_DBG = 1 << 29; + /// For debugging, force-disable automatic scaling of establishes_raster_root + /// pictures that are too large (ie go back to old behavior that prevents those + /// large pictures from establishing a raster root). + const DISABLE_RASTER_ROOT_SCALING = 1 << 30; + } +} + +/// The main entry point to interact with WebRender. +pub struct RenderApi { + api_sender: Sender, + namespace_id: IdNamespace, + next_id: Cell, + resources: ApiResources, +} + +impl RenderApi { + /// Returns the namespace ID used by this API object. + pub fn get_namespace_id(&self) -> IdNamespace { + self.namespace_id + } + + /// + pub fn create_sender(&self) -> RenderApiSender { + RenderApiSender::new( + self.api_sender.clone(), + self.resources.blob_image_handler.as_ref().map(|handler| handler.create_similar()), + self.resources.get_shared_font_instances(), + ) + } + + /// Add a document to the WebRender instance. + /// + /// Instances can manage one or several documents (using the same render backend thread). + /// Each document will internally correspond to a single scene, and scenes are made of + /// one or several pipelines. + pub fn add_document(&self, initial_size: DeviceIntSize, layer: DocumentLayer) -> DocumentId { + let new_id = self.next_unique_id(); + self.add_document_with_id(initial_size, layer, new_id) + } + + /// See `add_document` + pub fn add_document_with_id(&self, + initial_size: DeviceIntSize, + layer: DocumentLayer, + id: u32) -> DocumentId { + let document_id = DocumentId::new(self.namespace_id, id); + + let msg = ApiMsg::AddDocument(document_id, initial_size, layer); + self.api_sender.send(msg).unwrap(); + + document_id + } + + /// Delete a document. + pub fn delete_document(&self, document_id: DocumentId) { + let msg = ApiMsg::DeleteDocument(document_id); + self.api_sender.send(msg).unwrap(); + } + + /// Generate a new font key + pub fn generate_font_key(&self) -> font::FontKey { + let new_id = self.next_unique_id(); + font::FontKey::new(self.namespace_id, new_id) + } + + /// Generate a new font instance key + pub fn generate_font_instance_key(&self) -> font::FontInstanceKey { + let new_id = self.next_unique_id(); + font::FontInstanceKey::new(self.namespace_id, new_id) + } + + /// Gets the dimensions for the supplied glyph keys + /// + /// Note: Internally, the internal texture cache doesn't store + /// 'empty' textures (height or width = 0) + /// This means that glyph dimensions e.g. for spaces (' ') will mostly be None. + pub fn get_glyph_dimensions( + &self, + key: font::FontInstanceKey, + glyph_indices: Vec, + ) -> Vec> { + let (sender, rx) = channel(); + let msg = ApiMsg::GetGlyphDimensions(font::GlyphDimensionRequest { + key, + glyph_indices, + sender + }); + self.api_sender.send(msg).unwrap(); + rx.recv().unwrap() + } + + /// Gets the glyph indices for the supplied string. These + /// can be used to construct GlyphKeys. + pub fn get_glyph_indices(&self, key: font::FontKey, text: &str) -> Vec> { + let (sender, rx) = channel(); + let msg = ApiMsg::GetGlyphIndices(font::GlyphIndexRequest { + key, + text: text.to_string(), + sender, + }); + self.api_sender.send(msg).unwrap(); + rx.recv().unwrap() + } + + /// Creates an `ImageKey`. + pub fn generate_image_key(&self) -> ImageKey { + let new_id = self.next_unique_id(); + ImageKey::new(self.namespace_id, new_id) + } + + /// Creates a `BlobImageKey`. + pub fn generate_blob_image_key(&self) -> BlobImageKey { + BlobImageKey(self.generate_image_key()) + } + + /// A Gecko-specific notification mechanism to get some code executed on the + /// `Renderer`'s thread, mostly replaced by `NotificationHandler`. You should + /// probably use the latter instead. + pub fn send_external_event(&self, evt: ExternalEvent) { + let msg = ApiMsg::ExternalEvent(evt); + self.api_sender.send(msg).unwrap(); + } + + /// Notify WebRender that now is a good time to flush caches and release + /// as much memory as possible. + pub fn notify_memory_pressure(&self) { + self.api_sender.send(ApiMsg::MemoryPressure).unwrap(); + } + + /// Synchronously requests memory report. + pub fn report_memory(&self) -> MemoryReport { + let (tx, rx) = channel(); + self.api_sender.send(ApiMsg::ReportMemory(tx)).unwrap(); + *rx.recv().unwrap() + } + + /// Update debugging flags. + pub fn set_debug_flags(&self, flags: DebugFlags) { + let cmd = DebugCommand::SetFlags(flags); + self.api_sender.send(ApiMsg::DebugCommand(cmd)).unwrap(); + } + + /// Shut the WebRender instance down. + pub fn shut_down(&self, synchronously: bool) { + if synchronously { + let (tx, rx) = channel(); + self.api_sender.send(ApiMsg::ShutDown(Some(tx))).unwrap(); + rx.recv().unwrap(); + } else { + self.api_sender.send(ApiMsg::ShutDown(None)).unwrap(); + } + } + + /// Create a new unique key that can be used for + /// animated property bindings. + pub fn generate_property_binding_key(&self) -> PropertyBindingKey { + let new_id = self.next_unique_id(); + PropertyBindingKey { + id: PropertyBindingId { + namespace: self.namespace_id, + uid: new_id, + }, + _phantom: PhantomData, + } + } + + #[inline] + fn next_unique_id(&self) -> u32 { + let ResourceId(id) = self.next_id.get(); + self.next_id.set(ResourceId(id + 1)); + id + } + + // For use in Wrench only + #[doc(hidden)] + pub fn send_message(&self, msg: ApiMsg) { + self.api_sender.send(msg).unwrap(); + } + + /// Creates a transaction message from a single frame message. + fn frame_message(&self, msg: FrameMsg, document_id: DocumentId) -> Box { + Box::new(TransactionMsg { + document_id, + scene_ops: Vec::new(), + frame_ops: vec![msg], + resource_updates: Vec::new(), + notifications: Vec::new(), + generate_frame: false, + invalidate_rendered_frame: false, + use_scene_builder_thread: false, + low_priority: false, + blob_rasterizer: None, + blob_requests: Vec::new(), + rasterized_blobs: Vec::new(), + }) + } + + /// Creates a transaction message from a single scene message. + fn scene_message(&self, msg: SceneMsg, document_id: DocumentId) -> Box { + Box::new(TransactionMsg { + document_id, + scene_ops: vec![msg], + frame_ops: Vec::new(), + resource_updates: Vec::new(), + notifications: Vec::new(), + generate_frame: false, + invalidate_rendered_frame: false, + use_scene_builder_thread: false, + low_priority: false, + blob_rasterizer: None, + blob_requests: Vec::new(), + rasterized_blobs: Vec::new(), + }) + } + + /// A helper method to send document messages. + fn send_scene_msg(&self, document_id: DocumentId, msg: SceneMsg) { + // This assertion fails on Servo use-cases, because it creates different + // `RenderApi` instances for layout and compositor. + //assert_eq!(document_id.0, self.namespace_id); + self.api_sender + .send(ApiMsg::UpdateDocuments(vec![self.scene_message(msg, document_id)])) + .unwrap() + } + + /// A helper method to send document messages. + fn send_frame_msg(&self, document_id: DocumentId, msg: FrameMsg) { + // This assertion fails on Servo use-cases, because it creates different + // `RenderApi` instances for layout and compositor. + //assert_eq!(document_id.0, self.namespace_id); + self.api_sender + .send(ApiMsg::UpdateDocuments(vec![self.frame_message(msg, document_id)])) + .unwrap() + } + + /// Send a transaction to WebRender. + pub fn send_transaction(&mut self, document_id: DocumentId, transaction: Transaction) { + let mut transaction = transaction.finalize(document_id); + + self.resources.update(&mut transaction); + + self.api_sender.send(ApiMsg::UpdateDocuments(vec![transaction])).unwrap(); + } + + /// Send multiple transactions. + pub fn send_transactions(&mut self, document_ids: Vec, mut transactions: Vec) { + debug_assert!(document_ids.len() == transactions.len()); + let msgs = transactions.drain(..).zip(document_ids) + .map(|(txn, id)| { + let mut txn = txn.finalize(id); + self.resources.update(&mut txn); + + txn + }) + .collect(); + + self.api_sender.send(ApiMsg::UpdateDocuments(msgs)).unwrap(); + } + + /// Does a hit test on display items in the specified document, at the given + /// point. If a pipeline_id is specified, it is used to further restrict the + /// hit results so that only items inside that pipeline are matched. If the + /// HitTestFlags argument contains the FIND_ALL flag, then the vector of hit + /// results will contain all display items that match, ordered from front + /// to back. + pub fn hit_test(&self, + document_id: DocumentId, + pipeline_id: Option, + point: WorldPoint, + flags: HitTestFlags) + -> HitTestResult { + let (tx, rx) = channel(); + + self.send_frame_msg( + document_id, + FrameMsg::HitTest(pipeline_id, point, flags, tx) + ); + rx.recv().unwrap() + } + + /// Synchronously request an object that can perform fast hit testing queries. + pub fn request_hit_tester(&self, document_id: DocumentId) -> HitTesterRequest { + let (tx, rx) = channel(); + self.send_frame_msg( + document_id, + FrameMsg::RequestHitTester(tx) + ); + + HitTesterRequest { rx } + } + + /// Setup the output region in the framebuffer for a given document. + pub fn set_document_view( + &self, + document_id: DocumentId, + device_rect: DeviceIntRect, + device_pixel_ratio: f32, + ) { + self.send_scene_msg( + document_id, + SceneMsg::SetDocumentView { device_rect, device_pixel_ratio }, + ); + } + + /// Setup the output region in the framebuffer for a given document. + /// Enable copying of the output of this pipeline id to + /// an external texture for callers to consume. + pub fn enable_frame_output( + &self, + document_id: DocumentId, + pipeline_id: PipelineId, + enable: bool, + ) { + self.send_scene_msg( + document_id, + SceneMsg::EnableFrameOutput(pipeline_id, enable), + ); + } + + /// + pub fn get_scroll_node_state(&self, document_id: DocumentId) -> Vec { + let (tx, rx) = channel(); + self.send_frame_msg(document_id, FrameMsg::GetScrollNodeState(tx)); + rx.recv().unwrap() + } + + // Some internal scheduling magic that leaked into the API. + // Buckle up and see APZUpdater.cpp for more info about what this is about. + #[doc(hidden)] + pub fn wake_scene_builder(&self) { + self.send_message(ApiMsg::WakeSceneBuilder); + } + + /// Block until a round-trip to the scene builder thread has completed. This + /// ensures that any transactions (including ones deferred to the scene + /// builder thread) have been processed. + pub fn flush_scene_builder(&self) { + let (tx, rx) = channel(); + self.send_message(ApiMsg::FlushSceneBuilder(tx)); + rx.recv().unwrap(); // block until done + } + + /// Save a capture of the current frame state for debugging. + pub fn save_capture(&self, path: PathBuf, bits: CaptureBits) { + let msg = ApiMsg::DebugCommand(DebugCommand::SaveCapture(path, bits)); + self.send_message(msg); + } + + /// Load a capture of the current frame state for debugging. + pub fn load_capture(&self, path: PathBuf, ids: Option<(u32, u32)>) -> Vec { + // First flush the scene builder otherwise async scenes might clobber + // the capture we are about to load. + self.flush_scene_builder(); + + let (tx, rx) = channel(); + let msg = ApiMsg::DebugCommand(DebugCommand::LoadCapture(path, ids, tx)); + self.send_message(msg); + + let mut documents = Vec::new(); + while let Ok(captured_doc) = rx.recv() { + documents.push(captured_doc); + } + documents + } + + /// Start capturing a sequence of frames. + pub fn start_capture_sequence(&self, path: PathBuf, bits: CaptureBits) { + let msg = ApiMsg::DebugCommand(DebugCommand::StartCaptureSequence(path, bits)); + self.send_message(msg); + } + + /// Stop capturing sequences of frames. + pub fn stop_capture_sequence(&self) { + let msg = ApiMsg::DebugCommand(DebugCommand::StopCaptureSequence); + self.send_message(msg); + } + + /// Update the state of builtin debugging facilities. + pub fn send_debug_cmd(&mut self, cmd: DebugCommand) { + if let DebugCommand::EnableMultithreading(enable) = cmd { + // TODO(nical) we should enable it for all RenderApis. + self.resources.enable_multithreading(enable); + } + let msg = ApiMsg::DebugCommand(cmd); + self.send_message(msg); + } +} + +impl Drop for RenderApi { + fn drop(&mut self) { + let msg = ApiMsg::ClearNamespace(self.namespace_id); + let _ = self.api_sender.send(msg); + } +} + +/// A hit tester requested to the render backend thread but not necessarily ready yet. +/// +/// The request should be resolved as late as possible to reduce the likelihood of blocking. +pub struct HitTesterRequest { + rx: Receiver>, +} + +impl HitTesterRequest { + /// Block until the hit tester is available and return it, consuming teh request. + pub fn resolve(self) -> Arc { + self.rx.recv().unwrap() + } +} + +/// +#[derive(Clone)] +pub struct ScrollNodeState { + /// + pub id: di::ExternalScrollId, + /// + pub scroll_offset: LayoutVector2D, +} + +/// +#[derive(Clone, Copy, Debug)] +pub enum ScrollLocation { + /// Scroll by a certain amount. + Delta(LayoutVector2D), + /// Scroll to very top of element. + Start, + /// Scroll to very bottom of element. + End, +} + +/// Represents a zoom factor. +#[derive(Clone, Copy, Debug)] +pub struct ZoomFactor(f32); + +impl ZoomFactor { + /// Construct a new zoom factor. + pub fn new(scale: f32) -> Self { + ZoomFactor(scale) + } + + /// Get the zoom factor as an untyped float. + pub fn get(self) -> f32 { + self.0 + } +} + +/// A key to identify an animated property binding. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize, Eq, Hash, PeekPoke)] +pub struct PropertyBindingId { + namespace: IdNamespace, + uid: u32, +} + +impl PropertyBindingId { + /// Constructor. + pub fn new(value: u64) -> Self { + PropertyBindingId { + namespace: IdNamespace((value >> 32) as u32), + uid: value as u32, + } + } +} + +/// A unique key that is used for connecting animated property +/// values to bindings in the display list. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub struct PropertyBindingKey { + /// + pub id: PropertyBindingId, + _phantom: PhantomData, +} + +/// Construct a property value from a given key and value. +impl PropertyBindingKey { + /// + pub fn with(self, value: T) -> PropertyValue { + PropertyValue { key: self, value } + } +} + +impl PropertyBindingKey { + /// Constructor. + pub fn new(value: u64) -> Self { + PropertyBindingKey { + id: PropertyBindingId::new(value), + _phantom: PhantomData, + } + } +} + +/// A binding property can either be a specific value +/// (the normal, non-animated case) or point to a binding location +/// to fetch the current value from. +/// Note that Binding has also a non-animated value, the value is +/// used for the case where the animation is still in-delay phase +/// (i.e. the animation doesn't produce any animation values). +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub enum PropertyBinding { + /// Non-animated value. + Value(T), + /// Animated binding. + Binding(PropertyBindingKey, T), +} + +impl Default for PropertyBinding { + fn default() -> Self { + PropertyBinding::Value(Default::default()) + } +} + +impl From for PropertyBinding { + fn from(value: T) -> PropertyBinding { + PropertyBinding::Value(value) + } +} + +impl From> for PropertyBindingKey { + fn from(key: PropertyBindingKey) -> PropertyBindingKey { + PropertyBindingKey { + id: key.id.clone(), + _phantom: PhantomData, + } + } +} + +impl From> for PropertyBindingKey { + fn from(key: PropertyBindingKey) -> PropertyBindingKey { + PropertyBindingKey { + id: key.id.clone(), + _phantom: PhantomData, + } + } +} + +impl From> for PropertyBinding { + fn from(value: PropertyBinding) -> PropertyBinding { + match value { + PropertyBinding::Value(value) => PropertyBinding::Value(value.into()), + PropertyBinding::Binding(k, v) => { + PropertyBinding::Binding(k.into(), v.into()) + } + } + } +} + +impl From> for PropertyBinding { + fn from(value: PropertyBinding) -> PropertyBinding { + match value { + PropertyBinding::Value(value) => PropertyBinding::Value(value.into()), + PropertyBinding::Binding(k, v) => { + PropertyBinding::Binding(k.into(), v.into()) + } + } + } +} + +/// The current value of an animated property. This is +/// supplied by the calling code. +#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)] +pub struct PropertyValue { + /// + pub key: PropertyBindingKey, + /// + pub value: T, +} + +/// When using `generate_frame()`, a list of `PropertyValue` structures +/// can optionally be supplied to provide the current value of any +/// animated properties. +#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Default)] +pub struct DynamicProperties { + /// + pub transforms: Vec>, + /// opacity + pub floats: Vec>, + /// background color + pub colors: Vec>, +} + +/// A handler to integrate WebRender with the thread that contains the `Renderer`. +pub trait RenderNotifier: Send { + /// + fn clone(&self) -> Box; + /// Wake the thread containing the `Renderer` up (after updates have been put + /// in the renderer's queue). + fn wake_up(&self); + /// Notify the thread containing the `Renderer` that a new frame is ready. + fn new_frame_ready(&self, _: DocumentId, scrolled: bool, composite_needed: bool, render_time_ns: Option); + /// A Gecko-specific notification mechanism to get some code executed on the + /// `Renderer`'s thread, mostly replaced by `NotificationHandler`. You should + /// probably use the latter instead. + fn external_event(&self, _evt: ExternalEvent) { + unimplemented!() + } + /// Notify the thread containing the `Renderer` that the render backend has been + /// shut down. + fn shut_down(&self) {} +} + +/// A stage of the rendering pipeline. +#[repr(u32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Checkpoint { + /// + SceneBuilt, + /// + FrameBuilt, + /// + FrameTexturesUpdated, + /// + FrameRendered, + /// NotificationRequests get notified with this if they get dropped without having been + /// notified. This provides the guarantee that if a request is created it will get notified. + TransactionDropped, +} + +/// A handler to notify when a transaction reaches certain stages of the rendering +/// pipeline. +pub trait NotificationHandler : Send + Sync { + /// Entry point of the handler to implement. Invoked by WebRender. + fn notify(&self, when: Checkpoint); +} + +/// A request to notify a handler when the transaction reaches certain stages of the +/// rendering pipeline. +/// +/// The request is guaranteed to be notified once and only once, even if the transaction +/// is dropped before the requested check-point. +pub struct NotificationRequest { + handler: Option>, + when: Checkpoint, +} + +impl NotificationRequest { + /// Constructor. + pub fn new(when: Checkpoint, handler: Box) -> Self { + NotificationRequest { + handler: Some(handler), + when, + } + } + + /// The specified stage at which point the handler should be notified. + pub fn when(&self) -> Checkpoint { self.when } + + /// Called by WebRender at specified stages to notify the registered handler. + pub fn notify(mut self) { + if let Some(handler) = self.handler.take() { + handler.notify(self.when); + } + } +} + +/// An object that can perform hit-testing without doing synchronous queries to +/// the RenderBackendThread. +pub trait ApiHitTester: Send + Sync { + /// Does a hit test on display items in the specified document, at the given + /// point. If a pipeline_id is specified, it is used to further restrict the + /// hit results so that only items inside that pipeline are matched. If the + /// HitTestFlags argument contains the FIND_ALL flag, then the vector of hit + /// results will contain all display items that match, ordered from front + /// to back. + fn hit_test(&self, pipeline_id: Option, point: WorldPoint, flags: HitTestFlags) -> HitTestResult; +} + +impl Drop for NotificationRequest { + fn drop(&mut self) { + if let Some(ref mut handler) = self.handler { + handler.notify(Checkpoint::TransactionDropped); + } + } +} + +// This Clone impl yields an "empty" request because we don't want the requests +// to be notified twice so the request is owned by only one of the API messages +// (the original one) after the clone. +// This works in practice because the notifications requests are used for +// synchronization so we don't need to include them in the recording mechanism +// in wrench that clones the messages. +impl Clone for NotificationRequest { + fn clone(&self) -> Self { + NotificationRequest { + when: self.when, + handler: None, + } + } +} + + +bitflags! { + /// Each bit of the edge AA mask is: + /// 0, when the edge of the primitive needs to be considered for AA + /// 1, when the edge of the segment needs to be considered for AA + /// + /// *Note*: the bit values have to match the shader logic in + /// `write_transform_vertex()` function. + #[cfg_attr(feature = "serialize", derive(Serialize))] + #[cfg_attr(feature = "deserialize", derive(Deserialize))] + #[derive(MallocSizeOf)] + pub struct EdgeAaSegmentMask: u8 { + /// + const LEFT = 0x1; + /// + const TOP = 0x2; + /// + const RIGHT = 0x4; + /// + const BOTTOM = 0x8; + } +} diff --git a/third_party/webrender/webrender_api/src/channel.rs b/third_party/webrender/webrender_api/src/channel.rs new file mode 100644 index 00000000000..2bc4a5f16b8 --- /dev/null +++ b/third_party/webrender/webrender_api/src/channel.rs @@ -0,0 +1,131 @@ +/* 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 crate::api::{Epoch, PipelineId}; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::io::{self, Cursor, Error, ErrorKind, Read}; +use std::mem; +use std::sync::mpsc; + +#[derive(Clone)] +pub struct Payload { + /// An epoch used to get the proper payload for a pipeline id frame request. + /// + /// TODO(emilio): Is this still relevant? We send the messages for the same + /// pipeline in order, so we shouldn't need it. Seems like this was only + /// wallpapering (in most cases) the underlying problem in #991. + pub epoch: Epoch, + /// A pipeline id to key the payload with, along with the epoch. + pub pipeline_id: PipelineId, + pub display_list_data: Vec, +} + +impl Payload { + /// Convert the payload to a raw byte vector, in order for it to be + /// efficiently shared via shmem, for example. + /// This is a helper static method working on a slice. + pub fn construct_data(epoch: Epoch, pipeline_id: PipelineId, dl_data: &[u8]) -> Vec { + let mut data = Vec::with_capacity( + mem::size_of::() + 2 * mem::size_of::() + mem::size_of::() + dl_data.len(), + ); + data.write_u32::(epoch.0).unwrap(); + data.write_u32::(pipeline_id.0).unwrap(); + data.write_u32::(pipeline_id.1).unwrap(); + data.write_u64::(dl_data.len() as u64) + .unwrap(); + data.extend_from_slice(dl_data); + data + } + /// Convert the payload to a raw byte vector, in order for it to be + /// efficiently shared via shmem, for example. + pub fn to_data(&self) -> Vec { + Self::construct_data(self.epoch, self.pipeline_id, &self.display_list_data) + } + + /// Deserializes the given payload from a raw byte vector. + pub fn from_data(data: &[u8]) -> Payload { + let mut payload_reader = Cursor::new(data); + let epoch = Epoch(payload_reader.read_u32::().unwrap()); + let pipeline_id = PipelineId( + payload_reader.read_u32::().unwrap(), + payload_reader.read_u32::().unwrap(), + ); + + let dl_size = payload_reader.read_u64::().unwrap() as usize; + let mut built_display_list_data = vec![0; dl_size]; + payload_reader + .read_exact(&mut built_display_list_data[..]) + .unwrap(); + + assert_eq!(payload_reader.position(), data.len() as u64); + + Payload { + epoch, + pipeline_id, + display_list_data: built_display_list_data, + } + } +} + +pub type PayloadSender = MsgSender; + +pub type PayloadReceiver = MsgReceiver; + +pub struct MsgReceiver { + rx: mpsc::Receiver, +} + +impl MsgReceiver { + pub fn recv(&self) -> Result { + self.rx.recv().map_err(|e| io::Error::new(ErrorKind::Other, e.to_string())) + } + + pub fn to_mpsc_receiver(self) -> mpsc::Receiver { + self.rx + } +} + +#[derive(Clone)] +pub struct MsgSender { + tx: mpsc::Sender, +} + +impl MsgSender { + pub fn send(&self, data: T) -> Result<(), Error> { + self.tx.send(data).map_err(|_| Error::new(ErrorKind::Other, "cannot send on closed channel")) + } +} + +pub fn payload_channel() -> Result<(PayloadSender, PayloadReceiver), Error> { + let (tx, rx) = mpsc::channel(); + Ok((PayloadSender { tx }, PayloadReceiver { rx })) +} + +pub fn msg_channel() -> Result<(MsgSender, MsgReceiver), Error> { + let (tx, rx) = mpsc::channel(); + Ok((MsgSender { tx }, MsgReceiver { rx })) +} + +/// +/// These serialize methods are needed to satisfy the compiler +/// which uses these implementations for the recording tool. +/// The recording tool only outputs messages that don't contain +/// Senders or Receivers, so in theory these should never be +/// called in the in-process config. If they are called, +/// there may be a bug in the messages that the replay tool is writing. +/// + +impl Serialize for MsgSender { + fn serialize(&self, _: S) -> Result { + unreachable!(); + } +} + +impl<'de, T> Deserialize<'de> for MsgSender { + fn deserialize(_: D) -> Result, D::Error> + where D: Deserializer<'de> { + unreachable!(); + } +} diff --git a/third_party/webrender/webrender_api/src/color.rs b/third_party/webrender/webrender_api/src/color.rs new file mode 100644 index 00000000000..f19f83bb3d0 --- /dev/null +++ b/third_party/webrender/webrender_api/src/color.rs @@ -0,0 +1,160 @@ +/* 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 peek_poke::PeekPoke; +use std::cmp; +use std::hash::{Hash, Hasher}; + +/// Represents pre-multiplied RGBA colors with floating point numbers. +/// +/// All components must be between 0.0 and 1.0. +/// An alpha value of 1.0 is opaque while 0.0 is fully transparent. +/// +/// In premultiplied colors transitions to transparent always look "nice" +/// therefore they are used in CSS gradients. +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, PartialOrd, Serialize)] +pub struct PremultipliedColorF { + pub r: f32, + pub g: f32, + pub b: f32, + pub a: f32, +} + +#[allow(missing_docs)] +impl PremultipliedColorF { + pub const BLACK: PremultipliedColorF = PremultipliedColorF { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }; + pub const TRANSPARENT: PremultipliedColorF = PremultipliedColorF { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }; + pub const WHITE: PremultipliedColorF = PremultipliedColorF { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }; + + pub fn to_array(&self) -> [f32; 4] { + [self.r, self.g, self.b, self.a] + } +} + +/// Represents RGBA screen colors with floating point numbers. +/// +/// All components must be between 0.0 and 1.0. +/// An alpha value of 1.0 is opaque while 0.0 is fully transparent. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub struct ColorF { + pub r: f32, + pub g: f32, + pub b: f32, + pub a: f32, +} + +#[allow(missing_docs)] +impl ColorF { + pub const BLACK: ColorF = ColorF { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }; + pub const TRANSPARENT: ColorF = ColorF { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }; + pub const WHITE: ColorF = ColorF { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }; + + /// Constructs a new `ColorF` from its components. + pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self { + ColorF { r, g, b, a } + } + + /// Multiply the RGB channels (but not alpha) with a given factor. + pub fn scale_rgb(&self, scale: f32) -> Self { + ColorF { + r: self.r * scale, + g: self.g * scale, + b: self.b * scale, + a: self.a, + } + } + + // Scale the alpha by a given factor. + pub fn scale_alpha(&self, scale: f32) -> Self { + ColorF { + r: self.r, + g: self.g, + b: self.b, + a: self.a * scale, + } + } + + pub fn to_array(&self) -> [f32; 4] { + [self.r, self.g, self.b, self.a] + } + + /// Multiply the RGB components with the alpha channel. + pub fn premultiplied(&self) -> PremultipliedColorF { + let c = self.scale_rgb(self.a); + PremultipliedColorF { r: c.r, g: c.g, b: c.b, a: c.a } + } +} + +// Floats don't impl Hash/Eq/Ord... +impl Eq for PremultipliedColorF {} +impl Ord for PremultipliedColorF { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.partial_cmp(other).unwrap_or(cmp::Ordering::Equal) + } +} + +#[cfg_attr(feature = "cargo-clippy", allow(clippy::derive_hash_xor_eq))] +impl Hash for PremultipliedColorF { + fn hash(&self, state: &mut H) { + // Note: this is inconsistent with the Eq impl for -0.0 (don't care). + self.r.to_bits().hash(state); + self.g.to_bits().hash(state); + self.b.to_bits().hash(state); + self.a.to_bits().hash(state); + } +} + +/// Represents RGBA screen colors with one byte per channel. +/// +/// If the alpha value `a` is 255 the color is opaque. +#[repr(C)] +#[derive(Clone, Copy, Hash, Eq, Debug, Deserialize, MallocSizeOf, PartialEq, PartialOrd, Ord, Serialize)] +pub struct ColorU { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +impl ColorU { + /// Constructs a new additive `ColorU` from its components. + pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { + ColorU { r, g, b, a } + } +} + +fn round_to_int(x: f32) -> u8 { + debug_assert!((0.0 <= x) && (x <= 1.0), "{} should be between 0 and 1", x); + let f = (255.0 * x) + 0.5; + let val = f.floor(); + debug_assert!(val <= 255.0); + val as u8 +} + +// TODO: We shouldn't really convert back to `ColorU` ever, +// since it's lossy. One of the blockers is that all of our debug colors +// are specified in `ColorF`. Changing it to `ColorU` would be nice. +impl From for ColorU { + fn from(color: ColorF) -> Self { + ColorU { + r: round_to_int(color.r), + g: round_to_int(color.g), + b: round_to_int(color.b), + a: round_to_int(color.a), + } + } +} + +impl From for ColorF { + fn from(color: ColorU) -> Self { + ColorF { + r: color.r as f32 / 255.0, + g: color.g as f32 / 255.0, + b: color.b as f32 / 255.0, + a: color.a as f32 / 255.0, + } + } +} diff --git a/third_party/webrender/webrender_api/src/display_item.rs b/third_party/webrender/webrender_api/src/display_item.rs new file mode 100644 index 00000000000..e6b8e036e46 --- /dev/null +++ b/third_party/webrender/webrender_api/src/display_item.rs @@ -0,0 +1,1589 @@ +/* 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::SideOffsets2D; +use peek_poke::PeekPoke; +use std::ops::Not; +// local imports +use crate::font; +use crate::api::{PipelineId, PropertyBinding}; +use crate::color::ColorF; +use crate::image::{ColorDepth, ImageKey}; +use crate::units::*; + +// ****************************************************************** +// * NOTE: some of these structs have an "IMPLICIT" comment. * +// * This indicates that the BuiltDisplayList will have serialized * +// * a list of values nearby that this item consumes. The traversal * +// * iterator should handle finding these. DebugDisplayItem should * +// * make them explicit. * +// ****************************************************************** + +/// A tag that can be used to identify items during hit testing. If the tag +/// is missing then the item doesn't take part in hit testing at all. This +/// is composed of two numbers. In Servo, the first is an identifier while the +/// second is used to select the cursor that should be used during mouse +/// movement. In Gecko, the first is a scrollframe identifier, while the second +/// is used to store various flags that APZ needs to properly process input +/// events. +pub type ItemTag = (u64, u16); + +/// An identifier used to refer to previously sent display items. Currently it +/// refers to individual display items, but this may change later. +pub type ItemKey = u16; + +bitflags! { + #[repr(C)] + #[derive(Deserialize, MallocSizeOf, Serialize, PeekPoke)] + pub struct PrimitiveFlags: u8 { + /// The CSS backface-visibility property (yes, it can be really granular) + const IS_BACKFACE_VISIBLE = 1 << 0; + /// If set, this primitive represents a scroll bar container + const IS_SCROLLBAR_CONTAINER = 1 << 1; + /// If set, this primitive represents a scroll bar thumb + const IS_SCROLLBAR_THUMB = 1 << 2; + /// This is used as a performance hint - this primitive may be promoted to a native + /// compositor surface under certain (implementation specific) conditions. This + /// is typically used for large videos, and canvas elements. + const PREFER_COMPOSITOR_SURFACE = 1 << 3; + } +} + +impl Default for PrimitiveFlags { + fn default() -> Self { + PrimitiveFlags::IS_BACKFACE_VISIBLE + } +} + +/// A grouping of fields a lot of display items need, just to avoid +/// repeating these over and over in this file. +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct CommonItemProperties { + /// Bounds of the display item to clip to. Many items are logically + /// infinite, and rely on this clip_rect to define their bounds + /// (solid colors, background-images, gradients, etc). + pub clip_rect: LayoutRect, + /// Additional clips + pub clip_id: ClipId, + /// The coordinate-space the item is in (yes, it can be really granular) + pub spatial_id: SpatialId, + /// Opaque bits for our clients to use for hit-testing. This is the most + /// dubious "common" field, but because it's an Option, it usually only + /// wastes a single byte (for None). + pub hit_info: Option, + /// Various flags describing properties of this primitive. + pub flags: PrimitiveFlags, +} + +impl CommonItemProperties { + /// Convenience for tests. + pub fn new( + clip_rect: LayoutRect, + space_and_clip: SpaceAndClipInfo, + ) -> Self { + Self { + clip_rect, + spatial_id: space_and_clip.spatial_id, + clip_id: space_and_clip.clip_id, + hit_info: None, + flags: PrimitiveFlags::default(), + } + } +} + +/// Per-primitive information about the nodes in the clip tree and +/// the spatial tree that the primitive belongs to. +/// +/// Note: this is a separate struct from `PrimitiveInfo` because +/// it needs indirectional mapping during the DL flattening phase, +/// turning into `ScrollNodeAndClipChain`. +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct SpaceAndClipInfo { + pub spatial_id: SpatialId, + pub clip_id: ClipId, +} + +impl SpaceAndClipInfo { + /// Create a new space/clip info associated with the root + /// scroll frame. + pub fn root_scroll(pipeline_id: PipelineId) -> Self { + SpaceAndClipInfo { + spatial_id: SpatialId::root_scroll_node(pipeline_id), + clip_id: ClipId::root(pipeline_id), + } + } +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)] +pub enum DisplayItem { + // These are the "real content" display items + Rectangle(RectangleDisplayItem), + ClearRectangle(ClearRectangleDisplayItem), + HitTest(HitTestDisplayItem), + Text(TextDisplayItem), + Line(LineDisplayItem), + Border(BorderDisplayItem), + BoxShadow(BoxShadowDisplayItem), + PushShadow(PushShadowDisplayItem), + Gradient(GradientDisplayItem), + RadialGradient(RadialGradientDisplayItem), + ConicGradient(ConicGradientDisplayItem), + Image(ImageDisplayItem), + RepeatingImage(RepeatingImageDisplayItem), + YuvImage(YuvImageDisplayItem), + BackdropFilter(BackdropFilterDisplayItem), + + // Clips + RectClip(RectClipDisplayItem), + RoundedRectClip(RoundedRectClipDisplayItem), + ImageMaskClip(ImageMaskClipDisplayItem), + Clip(ClipDisplayItem), + ClipChain(ClipChainItem), + + // Spaces and Frames that content can be scoped under. + ScrollFrame(ScrollFrameDisplayItem), + StickyFrame(StickyFrameDisplayItem), + Iframe(IframeDisplayItem), + PushReferenceFrame(ReferenceFrameDisplayListItem), + PushStackingContext(PushStackingContextDisplayItem), + + // These marker items indicate an array of data follows, to be used for the + // next non-marker item. + SetGradientStops, + SetFilterOps, + SetFilterData, + SetFilterPrimitives, + + // These marker items terminate a scope introduced by a previous item. + PopReferenceFrame, + PopStackingContext, + PopAllShadows, + + ReuseItems(ItemKey), + RetainedItems(ItemKey), +} + +/// This is a "complete" version of the DisplayItem, with all implicit trailing +/// arrays included, for debug serialization (captures). +#[cfg(any(feature = "serialize", feature = "deserialize"))] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +pub enum DebugDisplayItem { + Rectangle(RectangleDisplayItem), + ClearRectangle(ClearRectangleDisplayItem), + HitTest(HitTestDisplayItem), + Text(TextDisplayItem, Vec), + Line(LineDisplayItem), + Border(BorderDisplayItem), + BoxShadow(BoxShadowDisplayItem), + PushShadow(PushShadowDisplayItem), + Gradient(GradientDisplayItem), + RadialGradient(RadialGradientDisplayItem), + ConicGradient(ConicGradientDisplayItem), + Image(ImageDisplayItem), + RepeatingImage(RepeatingImageDisplayItem), + YuvImage(YuvImageDisplayItem), + BackdropFilter(BackdropFilterDisplayItem), + + ImageMaskClip(ImageMaskClipDisplayItem), + RoundedRectClip(RoundedRectClipDisplayItem), + RectClip(RectClipDisplayItem), + Clip(ClipDisplayItem, Vec), + ClipChain(ClipChainItem, Vec), + + ScrollFrame(ScrollFrameDisplayItem), + StickyFrame(StickyFrameDisplayItem), + Iframe(IframeDisplayItem), + PushReferenceFrame(ReferenceFrameDisplayListItem), + PushStackingContext(PushStackingContextDisplayItem), + + SetGradientStops(Vec), + SetFilterOps(Vec), + SetFilterData(FilterData), + SetFilterPrimitives(Vec), + + PopReferenceFrame, + PopStackingContext, + PopAllShadows, +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct ImageMaskClipDisplayItem { + pub id: ClipId, + pub parent_space_and_clip: SpaceAndClipInfo, + pub image_mask: ImageMask, +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct RectClipDisplayItem { + pub id: ClipId, + pub parent_space_and_clip: SpaceAndClipInfo, + pub clip_rect: LayoutRect, +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct RoundedRectClipDisplayItem { + pub id: ClipId, + pub parent_space_and_clip: SpaceAndClipInfo, + pub clip: ComplexClipRegion, +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct ClipDisplayItem { + pub id: ClipId, + pub parent_space_and_clip: SpaceAndClipInfo, + pub clip_rect: LayoutRect, +} // IMPLICIT: complex_clips: Vec + +/// The minimum and maximum allowable offset for a sticky frame in a single dimension. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct StickyOffsetBounds { + /// The minimum offset for this frame, typically a negative value, which specifies how + /// far in the negative direction the sticky frame can offset its contents in this + /// dimension. + pub min: f32, + + /// The maximum offset for this frame, typically a positive value, which specifies how + /// far in the positive direction the sticky frame can offset its contents in this + /// dimension. + pub max: f32, +} + +impl StickyOffsetBounds { + pub fn new(min: f32, max: f32) -> StickyOffsetBounds { + StickyOffsetBounds { min, max } + } +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct StickyFrameDisplayItem { + pub id: SpatialId, + pub parent_spatial_id: SpatialId, + pub bounds: LayoutRect, + + /// The margins that should be maintained between the edge of the parent viewport and this + /// sticky frame. A margin of None indicates that the sticky frame should not stick at all + /// to that particular edge of the viewport. + pub margins: SideOffsets2D, LayoutPixel>, + + /// The minimum and maximum vertical offsets for this sticky frame. Ignoring these constraints, + /// the sticky frame will continue to stick to the edge of the viewport as its original + /// position is scrolled out of view. Constraints specify a maximum and minimum offset from the + /// original position relative to non-sticky content within the same scrolling frame. + pub vertical_offset_bounds: StickyOffsetBounds, + + /// The minimum and maximum horizontal offsets for this sticky frame. Ignoring these constraints, + /// the sticky frame will continue to stick to the edge of the viewport as its original + /// position is scrolled out of view. Constraints specify a maximum and minimum offset from the + /// original position relative to non-sticky content within the same scrolling frame. + pub horizontal_offset_bounds: StickyOffsetBounds, + + /// The amount of offset that has already been applied to the sticky frame. A positive y + /// component this field means that a top-sticky item was in a scrollframe that has been + /// scrolled down, such that the sticky item's position needed to be offset downwards by + /// `previously_applied_offset.y`. A negative y component corresponds to the upward offset + /// applied due to bottom-stickiness. The x-axis works analogously. + pub previously_applied_offset: LayoutVector2D, +} + +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)] +pub enum ScrollSensitivity { + ScriptAndInputEvents, + Script, +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct ScrollFrameDisplayItem { + /// The id of the clip this scroll frame creates + pub clip_id: ClipId, + /// The id of the space this scroll frame creates + pub scroll_frame_id: SpatialId, + /// The size of the contents this contains (so the backend knows how far it can scroll). + // FIXME: this can *probably* just be a size? Origin seems to just get thrown out. + pub content_rect: LayoutRect, + pub clip_rect: LayoutRect, + pub parent_space_and_clip: SpaceAndClipInfo, + pub external_id: Option, + pub scroll_sensitivity: ScrollSensitivity, + /// The amount this scrollframe has already been scrolled by, in the caller. + /// This means that all the display items that are inside the scrollframe + /// will have their coordinates shifted by this amount, and this offset + /// should be added to those display item coordinates in order to get a + /// normalized value that is consistent across display lists. + pub external_scroll_offset: LayoutVector2D, +} + +/// A solid or an animating color to draw (may not actually be a rectangle due to complex clips) +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct RectangleDisplayItem { + pub common: CommonItemProperties, + pub bounds: LayoutRect, + pub color: PropertyBinding, +} + +/// Clears all colors from the area, making it possible to cut holes in the window. +/// (useful for things like the macos frosted-glass effect). +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct ClearRectangleDisplayItem { + pub common: CommonItemProperties, + pub bounds: LayoutRect, +} + +/// A minimal hit-testable item for the parent browser's convenience, and is +/// slimmer than a RectangleDisplayItem (no color). The existence of this as a +/// distinct item also makes it easier to inspect/debug display items. +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct HitTestDisplayItem { + pub common: CommonItemProperties, +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct LineDisplayItem { + pub common: CommonItemProperties, + /// We need a separate rect from common.clip_rect to encode cute + /// tricks that firefox does to make a series of text-decorations seamlessly + /// line up -- snapping the decorations to a multiple of their period, and + /// then clipping them to their "proper" area. This rect is that "logical" + /// snapped area that may be clipped to the right size by the clip_rect. + pub area: LayoutRect, + /// Whether the rect is interpretted as vertical or horizontal + pub orientation: LineOrientation, + /// This could potentially be implied from area, but we currently prefer + /// that this is the responsibility of the layout engine. Value irrelevant + /// for non-wavy lines. + // FIXME: this was done before we could use tagged unions in enums, but now + // it should just be part of LineStyle::Wavy. + pub wavy_line_thickness: f32, + pub color: ColorF, + pub style: LineStyle, +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, Eq, Hash, PeekPoke)] +pub enum LineOrientation { + Vertical, + Horizontal, +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, Eq, Hash, PeekPoke)] +pub enum LineStyle { + Solid, + Dotted, + Dashed, + Wavy, +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct TextDisplayItem { + pub common: CommonItemProperties, + /// The area all the glyphs should be found in. Strictly speaking this isn't + /// necessarily needed, but layout engines should already "know" this, and we + /// use it cull and size things quickly before glyph layout is done. Currently + /// the glyphs *can* be outside these bounds, but that should imply they + /// can be cut off. + // FIXME: these are currently sometimes ignored to keep some old wrench tests + // working, but we should really just fix the tests! + pub bounds: LayoutRect, + pub font_key: font::FontInstanceKey, + pub color: ColorF, + pub glyph_options: Option, +} // IMPLICIT: glyphs: Vec + +#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub struct NormalBorder { + pub left: BorderSide, + pub right: BorderSide, + pub top: BorderSide, + pub bottom: BorderSide, + pub radius: BorderRadius, + /// Whether to apply anti-aliasing on the border corners. + /// + /// Note that for this to be `false` and work, this requires the borders to + /// be solid, and no border-radius. + pub do_aa: bool, +} + +impl NormalBorder { + fn can_disable_antialiasing(&self) -> bool { + fn is_valid(style: BorderStyle) -> bool { + style == BorderStyle::Solid || style == BorderStyle::None + } + + self.radius.is_zero() && + is_valid(self.top.style) && + is_valid(self.left.style) && + is_valid(self.bottom.style) && + is_valid(self.right.style) + } + + /// Normalizes a border so that we don't render disallowed stuff, like inset + /// borders that are less than two pixels wide. + #[inline] + pub fn normalize(&mut self, widths: &LayoutSideOffsets) { + debug_assert!( + self.do_aa || self.can_disable_antialiasing(), + "Unexpected disabled-antialiasing in a border, likely won't work or will be ignored" + ); + + #[inline] + fn renders_small_border_solid(style: BorderStyle) -> bool { + match style { + BorderStyle::Groove | + BorderStyle::Ridge => true, + _ => false, + } + } + + let normalize_side = |side: &mut BorderSide, width: f32| { + if renders_small_border_solid(side.style) && width < 2. { + side.style = BorderStyle::Solid; + } + }; + + normalize_side(&mut self.left, widths.left); + normalize_side(&mut self.right, widths.right); + normalize_side(&mut self.top, widths.top); + normalize_side(&mut self.bottom, widths.bottom); + } +} + +#[repr(u8)] +#[derive(Debug, Copy, Clone, MallocSizeOf, PartialEq, Serialize, Deserialize, Eq, Hash, PeekPoke)] +pub enum RepeatMode { + Stretch, + Repeat, + Round, + Space, +} + +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)] +pub enum NinePatchBorderSource { + Image(ImageKey), + Gradient(Gradient), + RadialGradient(RadialGradient), + ConicGradient(ConicGradient), +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct NinePatchBorder { + /// Describes what to use as the 9-patch source image. If this is an image, + /// it will be stretched to fill the size given by width x height. + pub source: NinePatchBorderSource, + + /// The width of the 9-part image. + pub width: i32, + + /// The height of the 9-part image. + pub height: i32, + + /// Distances from each edge where the image should be sliced up. These + /// values are in 9-part-image space (the same space as width and height), + /// and the resulting image parts will be used to fill the corresponding + /// parts of the border as given by the border widths. This can lead to + /// stretching. + /// Slices can be overlapping. In that case, the same pixels from the + /// 9-part image will show up in multiple parts of the resulting border. + pub slice: DeviceIntSideOffsets, + + /// Controls whether the center of the 9 patch image is rendered or + /// ignored. The center is never rendered if the slices are overlapping. + pub fill: bool, + + /// Determines what happens if the horizontal side parts of the 9-part + /// image have a different size than the horizontal parts of the border. + pub repeat_horizontal: RepeatMode, + + /// Determines what happens if the vertical side parts of the 9-part + /// image have a different size than the vertical parts of the border. + pub repeat_vertical: RepeatMode, + + /// The outset for the border. + /// TODO(mrobinson): This should be removed and handled by the client. + pub outset: LayoutSideOffsets, // TODO: what unit is this in? +} + +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)] +pub enum BorderDetails { + Normal(NormalBorder), + NinePatch(NinePatchBorder), +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct BorderDisplayItem { + pub common: CommonItemProperties, + pub bounds: LayoutRect, + pub widths: LayoutSideOffsets, + pub details: BorderDetails, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)] +pub enum BorderRadiusKind { + Uniform, + NonUniform, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub struct BorderRadius { + pub top_left: LayoutSize, + pub top_right: LayoutSize, + pub bottom_left: LayoutSize, + pub bottom_right: LayoutSize, +} + +impl Default for BorderRadius { + fn default() -> Self { + BorderRadius { + top_left: LayoutSize::zero(), + top_right: LayoutSize::zero(), + bottom_left: LayoutSize::zero(), + bottom_right: LayoutSize::zero(), + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub struct BorderSide { + pub color: ColorF, + pub style: BorderStyle, +} + +#[repr(u32)] +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, Hash, Eq, PeekPoke)] +pub enum BorderStyle { + None = 0, + Solid = 1, + Double = 2, + Dotted = 3, + Dashed = 4, + Hidden = 5, + Groove = 6, + Ridge = 7, + Inset = 8, + Outset = 9, +} + +impl BorderStyle { + pub fn is_hidden(self) -> bool { + self == BorderStyle::Hidden || self == BorderStyle::None + } +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub enum BoxShadowClipMode { + Outset = 0, + Inset = 1, +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct BoxShadowDisplayItem { + pub common: CommonItemProperties, + pub box_bounds: LayoutRect, + pub offset: LayoutVector2D, + pub color: ColorF, + pub blur_radius: f32, + pub spread_radius: f32, + pub border_radius: BorderRadius, + pub clip_mode: BoxShadowClipMode, +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct PushShadowDisplayItem { + pub space_and_clip: SpaceAndClipInfo, + pub shadow: Shadow, + pub should_inflate: bool, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct Shadow { + pub offset: LayoutVector2D, + pub color: ColorF, + pub blur_radius: f32, +} + +#[repr(u8)] +#[derive(Debug, Copy, Clone, Hash, Eq, MallocSizeOf, PartialEq, Serialize, Deserialize, Ord, PartialOrd, PeekPoke)] +pub enum ExtendMode { + Clamp, + Repeat, +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct Gradient { + pub start_point: LayoutPoint, + pub end_point: LayoutPoint, + pub extend_mode: ExtendMode, +} // IMPLICIT: stops: Vec + +/// The area +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct GradientDisplayItem { + /// NOTE: common.clip_rect is the area the gradient covers + pub common: CommonItemProperties, + /// The area to tile the gradient over (first tile starts at origin of this rect) + // FIXME: this should ideally just be `tile_origin` here, with the clip_rect + // defining the bounds of the item. Needs non-trivial backend changes. + pub bounds: LayoutRect, + /// How big a tile of the of the gradient should be (common case: bounds.size) + pub tile_size: LayoutSize, + /// The space between tiles of the gradient (common case: 0) + pub tile_spacing: LayoutSize, + pub gradient: Gradient, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub struct GradientStop { + pub offset: f32, + pub color: ColorF, +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct RadialGradient { + pub center: LayoutPoint, + pub radius: LayoutSize, + pub start_offset: f32, + pub end_offset: f32, + pub extend_mode: ExtendMode, +} // IMPLICIT stops: Vec + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct ConicGradient { + pub center: LayoutPoint, + pub angle: f32, + pub start_offset: f32, + pub end_offset: f32, + pub extend_mode: ExtendMode, +} // IMPLICIT stops: Vec + +/// Just an abstraction for bundling up a bunch of clips into a "super clip". +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct ClipChainItem { + pub id: ClipChainId, + pub parent: Option, +} // IMPLICIT clip_ids: Vec + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct RadialGradientDisplayItem { + pub common: CommonItemProperties, + /// The area to tile the gradient over (first tile starts at origin of this rect) + // FIXME: this should ideally just be `tile_origin` here, with the clip_rect + // defining the bounds of the item. Needs non-trivial backend changes. + pub bounds: LayoutRect, + pub gradient: RadialGradient, + pub tile_size: LayoutSize, + pub tile_spacing: LayoutSize, +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct ConicGradientDisplayItem { + pub common: CommonItemProperties, + /// The area to tile the gradient over (first tile starts at origin of this rect) + // FIXME: this should ideally just be `tile_origin` here, with the clip_rect + // defining the bounds of the item. Needs non-trivial backend changes. + pub bounds: LayoutRect, + pub gradient: ConicGradient, + pub tile_size: LayoutSize, + pub tile_spacing: LayoutSize, +} + +/// Renders a filtered region of its backdrop +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct BackdropFilterDisplayItem { + pub common: CommonItemProperties, +} +// IMPLICIT: filters: Vec, filter_datas: Vec, filter_primitives: Vec + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct ReferenceFrameDisplayListItem { + pub origin: LayoutPoint, + pub parent_spatial_id: SpatialId, + pub reference_frame: ReferenceFrame, +} + +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)] +pub enum ReferenceFrameKind { + /// Zoom reference frames must be a scale + translation only + Zoom, + /// A normal transform matrix, may contain perspective (the CSS transform property) + Transform, + /// A perspective transform, that optionally scrolls relative to a specific scroll node + Perspective { + scrolling_relative_to: Option, + } +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct ReferenceFrame { + pub kind: ReferenceFrameKind, + pub transform_style: TransformStyle, + /// The transform matrix, either the perspective matrix or the transform + /// matrix. + pub transform: PropertyBinding, + pub id: SpatialId, +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct PushStackingContextDisplayItem { + pub origin: LayoutPoint, + pub spatial_id: SpatialId, + pub prim_flags: PrimitiveFlags, + pub stacking_context: StackingContext, +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct StackingContext { + pub transform_style: TransformStyle, + pub mix_blend_mode: MixBlendMode, + pub clip_id: Option, + pub raster_space: RasterSpace, + pub flags: StackingContextFlags, +} +// IMPLICIT: filters: Vec, filter_datas: Vec, filter_primitives: Vec + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, PeekPoke)] +pub enum TransformStyle { + Flat = 0, + Preserve3D = 1, +} + +/// Configure whether the contents of a stacking context +/// should be rasterized in local space or screen space. +/// Local space rasterized pictures are typically used +/// when we want to cache the output, and performance is +/// important. Note that this is a performance hint only, +/// which WR may choose to ignore. +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)] +#[repr(u8)] +pub enum RasterSpace { + // Rasterize in local-space, applying supplied scale to primitives. + // Best performance, but lower quality. + Local(f32), + + // Rasterize the picture in screen-space, including rotation / skew etc in + // the rasterized element. Best quality, but slower performance. Note that + // any stacking context with a perspective transform will be rasterized + // in local-space, even if this is set. + Screen, +} + +impl RasterSpace { + pub fn local_scale(self) -> Option { + match self { + RasterSpace::Local(scale) => Some(scale), + RasterSpace::Screen => None, + } + } +} + +bitflags! { + #[repr(C)] + #[derive(Deserialize, MallocSizeOf, Serialize, PeekPoke)] + pub struct StackingContextFlags: u8 { + /// If true, this stacking context represents a backdrop root, per the CSS + /// filter-effects specification (see https://drafts.fxtf.org/filter-effects-2/#BackdropRoot). + const IS_BACKDROP_ROOT = 1 << 0; + /// If true, this stacking context is a blend container than contains + /// mix-blend-mode children (and should thus be isolated). + const IS_BLEND_CONTAINER = 1 << 1; + } +} + +impl Default for StackingContextFlags { + fn default() -> Self { + StackingContextFlags::empty() + } +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub enum MixBlendMode { + Normal = 0, + Multiply = 1, + Screen = 2, + Overlay = 3, + Darken = 4, + Lighten = 5, + ColorDodge = 6, + ColorBurn = 7, + HardLight = 8, + SoftLight = 9, + Difference = 10, + Exclusion = 11, + Hue = 12, + Saturation = 13, + Color = 14, + Luminosity = 15, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub enum ColorSpace { + Srgb, + LinearRgb, +} + +/// Available composite operoations for the composite filter primitive +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub enum CompositeOperator { + Over, + In, + Atop, + Out, + Xor, + Lighter, + Arithmetic([f32; 4]), +} + +impl CompositeOperator { + // This must stay in sync with the composite operator defines in cs_svg_filter.glsl + pub fn as_int(&self) -> u32 { + match self { + CompositeOperator::Over => 0, + CompositeOperator::In => 1, + CompositeOperator::Out => 2, + CompositeOperator::Atop => 3, + CompositeOperator::Xor => 4, + CompositeOperator::Lighter => 5, + CompositeOperator::Arithmetic(..) => 6, + } + } +} + +/// An input to a SVG filter primitive. +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub enum FilterPrimitiveInput { + /// The input is the original graphic that the filter is being applied to. + Original, + /// The input is the output of the previous filter primitive in the filter primitive chain. + Previous, + /// The input is the output of the filter primitive at the given index in the filter primitive chain. + OutputOfPrimitiveIndex(usize), +} + +impl FilterPrimitiveInput { + /// Gets the index of the input. + /// Returns `None` if the source graphic is the input. + pub fn to_index(self, cur_index: usize) -> Option { + match self { + FilterPrimitiveInput::Previous if cur_index > 0 => Some(cur_index - 1), + FilterPrimitiveInput::OutputOfPrimitiveIndex(index) => Some(index), + _ => None, + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct BlendPrimitive { + pub input1: FilterPrimitiveInput, + pub input2: FilterPrimitiveInput, + pub mode: MixBlendMode, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct FloodPrimitive { + pub color: ColorF, +} + +impl FloodPrimitive { + pub fn sanitize(&mut self) { + self.color.r = self.color.r.min(1.0).max(0.0); + self.color.g = self.color.g.min(1.0).max(0.0); + self.color.b = self.color.b.min(1.0).max(0.0); + self.color.a = self.color.a.min(1.0).max(0.0); + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct BlurPrimitive { + pub input: FilterPrimitiveInput, + pub radius: f32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct OpacityPrimitive { + pub input: FilterPrimitiveInput, + pub opacity: f32, +} + +impl OpacityPrimitive { + pub fn sanitize(&mut self) { + self.opacity = self.opacity.min(1.0).max(0.0); + } +} + +/// cbindgen:derive-eq=false +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct ColorMatrixPrimitive { + pub input: FilterPrimitiveInput, + pub matrix: [f32; 20], +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct DropShadowPrimitive { + pub input: FilterPrimitiveInput, + pub shadow: Shadow, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct ComponentTransferPrimitive { + pub input: FilterPrimitiveInput, + // Component transfer data is stored in FilterData. +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct IdentityPrimitive { + pub input: FilterPrimitiveInput, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct OffsetPrimitive { + pub input: FilterPrimitiveInput, + pub offset: LayoutVector2D, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct CompositePrimitive { + pub input1: FilterPrimitiveInput, + pub input2: FilterPrimitiveInput, + pub operator: CompositeOperator, +} + +/// See: https://github.com/eqrion/cbindgen/issues/9 +/// cbindgen:derive-eq=false +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)] +pub enum FilterPrimitiveKind { + Identity(IdentityPrimitive), + Blend(BlendPrimitive), + Flood(FloodPrimitive), + Blur(BlurPrimitive), + // TODO: Support animated opacity? + Opacity(OpacityPrimitive), + /// cbindgen:derive-eq=false + ColorMatrix(ColorMatrixPrimitive), + DropShadow(DropShadowPrimitive), + ComponentTransfer(ComponentTransferPrimitive), + Offset(OffsetPrimitive), + Composite(CompositePrimitive), +} + +impl Default for FilterPrimitiveKind { + fn default() -> Self { + FilterPrimitiveKind::Identity(IdentityPrimitive::default()) + } +} + +impl FilterPrimitiveKind { + pub fn sanitize(&mut self) { + match self { + FilterPrimitiveKind::Flood(flood) => flood.sanitize(), + FilterPrimitiveKind::Opacity(opacity) => opacity.sanitize(), + + // No sanitization needed. + FilterPrimitiveKind::Identity(..) | + FilterPrimitiveKind::Blend(..) | + FilterPrimitiveKind::ColorMatrix(..) | + FilterPrimitiveKind::Offset(..) | + FilterPrimitiveKind::Composite(..) | + FilterPrimitiveKind::Blur(..) | + FilterPrimitiveKind::DropShadow(..) | + // Component transfer's filter data is sanitized separately. + FilterPrimitiveKind::ComponentTransfer(..) => {} + } + } +} + +/// SVG Filter Primitive. +/// See: https://github.com/eqrion/cbindgen/issues/9 +/// cbindgen:derive-eq=false +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct FilterPrimitive { + pub kind: FilterPrimitiveKind, + pub color_space: ColorSpace, +} + +impl FilterPrimitive { + pub fn sanitize(&mut self) { + self.kind.sanitize(); + } +} + +/// CSS filter. +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize, PeekPoke)] +pub enum FilterOp { + /// Filter that does no transformation of the colors, needed for + /// debug purposes only. + Identity, + Blur(f32), + Brightness(f32), + Contrast(f32), + Grayscale(f32), + HueRotate(f32), + Invert(f32), + Opacity(PropertyBinding, f32), + Saturate(f32), + Sepia(f32), + DropShadow(Shadow), + ColorMatrix([f32; 20]), + SrgbToLinear, + LinearToSrgb, + ComponentTransfer, + Flood(ColorF), +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize, PeekPoke)] +pub enum ComponentTransferFuncType { + Identity = 0, + Table = 1, + Discrete = 2, + Linear = 3, + Gamma = 4, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct FilterData { + pub func_r_type: ComponentTransferFuncType, + pub r_values: Vec, + pub func_g_type: ComponentTransferFuncType, + pub g_values: Vec, + pub func_b_type: ComponentTransferFuncType, + pub b_values: Vec, + pub func_a_type: ComponentTransferFuncType, + pub a_values: Vec, +} + +fn sanitize_func_type( + func_type: ComponentTransferFuncType, + values: &[f32], +) -> ComponentTransferFuncType { + if values.is_empty() { + return ComponentTransferFuncType::Identity; + } + if values.len() < 2 && func_type == ComponentTransferFuncType::Linear { + return ComponentTransferFuncType::Identity; + } + if values.len() < 3 && func_type == ComponentTransferFuncType::Gamma { + return ComponentTransferFuncType::Identity; + } + func_type +} + +fn sanitize_values( + func_type: ComponentTransferFuncType, + values: &[f32], +) -> bool { + if values.len() < 2 && func_type == ComponentTransferFuncType::Linear { + return false; + } + if values.len() < 3 && func_type == ComponentTransferFuncType::Gamma { + return false; + } + true +} + +impl FilterData { + /// Ensure that the number of values matches up with the function type. + pub fn sanitize(&self) -> FilterData { + FilterData { + func_r_type: sanitize_func_type(self.func_r_type, &self.r_values), + r_values: + if sanitize_values(self.func_r_type, &self.r_values) { + self.r_values.clone() + } else { + Vec::new() + }, + func_g_type: sanitize_func_type(self.func_g_type, &self.g_values), + g_values: + if sanitize_values(self.func_g_type, &self.g_values) { + self.g_values.clone() + } else { + Vec::new() + }, + + func_b_type: sanitize_func_type(self.func_b_type, &self.b_values), + b_values: + if sanitize_values(self.func_b_type, &self.b_values) { + self.b_values.clone() + } else { + Vec::new() + }, + + func_a_type: sanitize_func_type(self.func_a_type, &self.a_values), + a_values: + if sanitize_values(self.func_a_type, &self.a_values) { + self.a_values.clone() + } else { + Vec::new() + }, + + } + } + + pub fn is_identity(&self) -> bool { + self.func_r_type == ComponentTransferFuncType::Identity && + self.func_g_type == ComponentTransferFuncType::Identity && + self.func_b_type == ComponentTransferFuncType::Identity && + self.func_a_type == ComponentTransferFuncType::Identity + } +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct IframeDisplayItem { + pub bounds: LayoutRect, + pub clip_rect: LayoutRect, + pub space_and_clip: SpaceAndClipInfo, + pub pipeline_id: PipelineId, + pub ignore_missing_pipeline: bool, +} + +/// This describes an image that fills the specified area. It stretches or shrinks +/// the image as necessary. While RepeatingImageDisplayItem could otherwise provide +/// a superset of the functionality, it has been problematic inferring the desired +/// repetition properties when snapping changes the size of the primitive. +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct ImageDisplayItem { + pub common: CommonItemProperties, + /// The area to tile the image over (first tile starts at origin of this rect) + // FIXME: this should ideally just be `tile_origin` here, with the clip_rect + // defining the bounds of the item. Needs non-trivial backend changes. + pub bounds: LayoutRect, + pub image_key: ImageKey, + pub image_rendering: ImageRendering, + pub alpha_type: AlphaType, + /// A hack used by gecko to color a simple bitmap font used for tofu glyphs + pub color: ColorF, +} + +/// This describes a background-image and its tiling. It repeats in a grid to fill +/// the specified area. +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct RepeatingImageDisplayItem { + pub common: CommonItemProperties, + /// The area to tile the image over (first tile starts at origin of this rect) + // FIXME: this should ideally just be `tile_origin` here, with the clip_rect + // defining the bounds of the item. Needs non-trivial backend changes. + pub bounds: LayoutRect, + /// How large to make a single tile of the image (common case: bounds.size) + pub stretch_size: LayoutSize, + /// The space between tiles (common case: 0) + pub tile_spacing: LayoutSize, + pub image_key: ImageKey, + pub image_rendering: ImageRendering, + pub alpha_type: AlphaType, + /// A hack used by gecko to color a simple bitmap font used for tofu glyphs + pub color: ColorF, +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub enum ImageRendering { + Auto = 0, + CrispEdges = 1, + Pixelated = 2, +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub enum AlphaType { + Alpha = 0, + PremultipliedAlpha = 1, +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct YuvImageDisplayItem { + pub common: CommonItemProperties, + pub bounds: LayoutRect, + pub yuv_data: YuvData, + pub color_depth: ColorDepth, + pub color_space: YuvColorSpace, + pub color_range: ColorRange, + pub image_rendering: ImageRendering, +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub enum YuvColorSpace { + Rec601 = 0, + Rec709 = 1, + Rec2020 = 2, +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub enum ColorRange { + Limited = 0, + Full = 1, +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, PeekPoke)] +pub enum YuvData { + NV12(ImageKey, ImageKey), // (Y channel, CbCr interleaved channel) + PlanarYCbCr(ImageKey, ImageKey, ImageKey), // (Y channel, Cb channel, Cr Channel) + InterleavedYCbCr(ImageKey), // (YCbCr interleaved channel) +} + +impl YuvData { + pub fn get_format(&self) -> YuvFormat { + match *self { + YuvData::NV12(..) => YuvFormat::NV12, + YuvData::PlanarYCbCr(..) => YuvFormat::PlanarYCbCr, + YuvData::InterleavedYCbCr(..) => YuvFormat::InterleavedYCbCr, + } + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub enum YuvFormat { + NV12 = 0, + PlanarYCbCr = 1, + InterleavedYCbCr = 2, +} + +impl YuvFormat { + pub fn get_plane_num(self) -> usize { + match self { + YuvFormat::NV12 => 2, + YuvFormat::PlanarYCbCr => 3, + YuvFormat::InterleavedYCbCr => 1, + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct ImageMask { + pub image: ImageKey, + pub rect: LayoutRect, + pub repeat: bool, +} + +impl ImageMask { + /// Get a local clipping rect contributed by this mask. + pub fn get_local_clip_rect(&self) -> Option { + if self.repeat { + None + } else { + Some(self.rect) + } + } +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, Serialize, Deserialize, Eq, Hash, PeekPoke)] +pub enum ClipMode { + Clip, // Pixels inside the region are visible. + ClipOut, // Pixels outside the region are visible. +} + +impl Not for ClipMode { + type Output = ClipMode; + + fn not(self) -> ClipMode { + match self { + ClipMode::Clip => ClipMode::ClipOut, + ClipMode::ClipOut => ClipMode::Clip, + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, PeekPoke)] +pub struct ComplexClipRegion { + /// The boundaries of the rectangle. + pub rect: LayoutRect, + /// Border radii of this rectangle. + pub radii: BorderRadius, + /// Whether we are clipping inside or outside + /// the region. + pub mode: ClipMode, +} + +impl BorderRadius { + pub fn zero() -> BorderRadius { + BorderRadius { + top_left: LayoutSize::new(0.0, 0.0), + top_right: LayoutSize::new(0.0, 0.0), + bottom_left: LayoutSize::new(0.0, 0.0), + bottom_right: LayoutSize::new(0.0, 0.0), + } + } + + pub fn uniform(radius: f32) -> BorderRadius { + BorderRadius { + top_left: LayoutSize::new(radius, radius), + top_right: LayoutSize::new(radius, radius), + bottom_left: LayoutSize::new(radius, radius), + bottom_right: LayoutSize::new(radius, radius), + } + } + + pub fn uniform_size(radius: LayoutSize) -> BorderRadius { + BorderRadius { + top_left: radius, + top_right: radius, + bottom_left: radius, + bottom_right: radius, + } + } + + pub fn is_uniform(&self) -> Option { + match self.is_uniform_size() { + Some(radius) if radius.width == radius.height => Some(radius.width), + _ => None, + } + } + + pub fn is_uniform_size(&self) -> Option { + let uniform_radius = self.top_left; + if self.top_right == uniform_radius && self.bottom_left == uniform_radius && + self.bottom_right == uniform_radius + { + Some(uniform_radius) + } else { + None + } + } + + /// Return whether, in each corner, the radius in *either* direction is zero. + /// This means that none of the corners are rounded. + pub fn is_zero(&self) -> bool { + let corner_is_zero = |corner: &LayoutSize| corner.width == 0.0 || corner.height == 0.0; + corner_is_zero(&self.top_left) && + corner_is_zero(&self.top_right) && + corner_is_zero(&self.bottom_right) && + corner_is_zero(&self.bottom_left) + } +} + +impl ComplexClipRegion { + /// Create a new complex clip region. + pub fn new( + rect: LayoutRect, + radii: BorderRadius, + mode: ClipMode, + ) -> Self { + ComplexClipRegion { rect, radii, mode } + } +} + +impl ComplexClipRegion { + /// Get a local clipping rect contributed by this clip region. + pub fn get_local_clip_rect(&self) -> Option { + match self.mode { + ClipMode::Clip => { + Some(self.rect) + } + ClipMode::ClipOut => { + None + } + } + } +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize, PeekPoke)] +pub struct ClipChainId(pub u64, pub PipelineId); + +/// A reference to a clipping node defining how an item is clipped. +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, PeekPoke)] +pub enum ClipId { + Clip(usize, PipelineId), + ClipChain(ClipChainId), +} + +const ROOT_CLIP_ID: usize = 0; + +impl ClipId { + /// Return the root clip ID - effectively doing no clipping. + pub fn root(pipeline_id: PipelineId) -> Self { + ClipId::Clip(ROOT_CLIP_ID, pipeline_id) + } + + /// Return an invalid clip ID - needed in places where we carry + /// one but need to not attempt to use it. + pub fn invalid() -> Self { + ClipId::Clip(!0, PipelineId::dummy()) + } + + pub fn pipeline_id(&self) -> PipelineId { + match *self { + ClipId::Clip(_, pipeline_id) | + ClipId::ClipChain(ClipChainId(_, pipeline_id)) => pipeline_id, + } + } + + pub fn is_root(&self) -> bool { + match *self { + ClipId::Clip(id, _) => id == ROOT_CLIP_ID, + ClipId::ClipChain(_) => false, + } + } + + pub fn is_valid(&self) -> bool { + match *self { + ClipId::Clip(id, _) => id != !0, + _ => true, + } + } +} + +/// A reference to a spatial node defining item positioning. +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize, PeekPoke)] +pub struct SpatialId(pub usize, PipelineId); + +const ROOT_REFERENCE_FRAME_SPATIAL_ID: usize = 0; +const ROOT_SCROLL_NODE_SPATIAL_ID: usize = 1; + +impl SpatialId { + pub fn new(spatial_node_index: usize, pipeline_id: PipelineId) -> Self { + SpatialId(spatial_node_index, pipeline_id) + } + + pub fn root_reference_frame(pipeline_id: PipelineId) -> Self { + SpatialId(ROOT_REFERENCE_FRAME_SPATIAL_ID, pipeline_id) + } + + pub fn root_scroll_node(pipeline_id: PipelineId) -> Self { + SpatialId(ROOT_SCROLL_NODE_SPATIAL_ID, pipeline_id) + } + + pub fn pipeline_id(&self) -> PipelineId { + self.1 + } + + pub fn is_root_reference_frame(&self) -> bool { + self.0 == ROOT_REFERENCE_FRAME_SPATIAL_ID + } + + pub fn is_root_scroll_node(&self) -> bool { + self.0 == ROOT_SCROLL_NODE_SPATIAL_ID + } +} + +/// An external identifier that uniquely identifies a scroll frame independent of its ClipId, which +/// may change from frame to frame. This should be unique within a pipeline. WebRender makes no +/// attempt to ensure uniqueness. The zero value is reserved for use by the root scroll node of +/// every pipeline, which always has an external id. +/// +/// When setting display lists with the `preserve_frame_state` this id is used to preserve scroll +/// offsets between different sets of SpatialNodes which are ScrollFrames. +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize, PeekPoke)] +#[repr(C)] +pub struct ExternalScrollId(pub u64, pub PipelineId); + +impl ExternalScrollId { + pub fn pipeline_id(&self) -> PipelineId { + self.1 + } + + pub fn is_root(&self) -> bool { + self.0 == 0 + } +} + +impl DisplayItem { + pub fn debug_name(&self) -> &'static str { + match *self { + DisplayItem::Border(..) => "border", + DisplayItem::BoxShadow(..) => "box_shadow", + DisplayItem::ClearRectangle(..) => "clear_rectangle", + DisplayItem::HitTest(..) => "hit_test", + DisplayItem::RectClip(..) => "rect_clip", + DisplayItem::RoundedRectClip(..) => "rounded_rect_clip", + DisplayItem::ImageMaskClip(..) => "image_mask_clip", + DisplayItem::Clip(..) => "clip", + DisplayItem::ClipChain(..) => "clip_chain", + DisplayItem::ConicGradient(..) => "conic_gradient", + DisplayItem::Gradient(..) => "gradient", + DisplayItem::Iframe(..) => "iframe", + DisplayItem::Image(..) => "image", + DisplayItem::RepeatingImage(..) => "repeating_image", + DisplayItem::Line(..) => "line", + DisplayItem::PopAllShadows => "pop_all_shadows", + DisplayItem::PopReferenceFrame => "pop_reference_frame", + DisplayItem::PopStackingContext => "pop_stacking_context", + DisplayItem::PushShadow(..) => "push_shadow", + DisplayItem::PushReferenceFrame(..) => "push_reference_frame", + DisplayItem::PushStackingContext(..) => "push_stacking_context", + DisplayItem::SetFilterOps => "set_filter_ops", + DisplayItem::SetFilterData => "set_filter_data", + DisplayItem::SetFilterPrimitives => "set_filter_primitives", + DisplayItem::RadialGradient(..) => "radial_gradient", + DisplayItem::Rectangle(..) => "rectangle", + DisplayItem::ScrollFrame(..) => "scroll_frame", + DisplayItem::SetGradientStops => "set_gradient_stops", + DisplayItem::ReuseItems(..) => "reuse_item", + DisplayItem::RetainedItems(..) => "retained_items", + DisplayItem::StickyFrame(..) => "sticky_frame", + DisplayItem::Text(..) => "text", + DisplayItem::YuvImage(..) => "yuv_image", + DisplayItem::BackdropFilter(..) => "backdrop_filter", + } + } +} + +macro_rules! impl_default_for_enums { + ($($enum:ident => $init:expr ),+) => { + $(impl Default for $enum { + #[allow(unused_imports)] + fn default() -> Self { + use $enum::*; + $init + } + })* + } +} + +impl_default_for_enums! { + DisplayItem => PopStackingContext, + ScrollSensitivity => ScriptAndInputEvents, + LineOrientation => Vertical, + LineStyle => Solid, + RepeatMode => Stretch, + NinePatchBorderSource => Image(ImageKey::default()), + BorderDetails => Normal(NormalBorder::default()), + BorderRadiusKind => Uniform, + BorderStyle => None, + BoxShadowClipMode => Outset, + ExtendMode => Clamp, + FilterOp => Identity, + ComponentTransferFuncType => Identity, + ClipMode => Clip, + ClipId => ClipId::invalid(), + ReferenceFrameKind => Transform, + TransformStyle => Flat, + RasterSpace => Local(f32::default()), + MixBlendMode => Normal, + ImageRendering => Auto, + AlphaType => Alpha, + YuvColorSpace => Rec601, + ColorRange => Limited, + YuvData => NV12(ImageKey::default(), ImageKey::default()), + YuvFormat => NV12, + FilterPrimitiveInput => Original, + ColorSpace => Srgb, + CompositeOperator => Over +} diff --git a/third_party/webrender/webrender_api/src/display_item_cache.rs b/third_party/webrender/webrender_api/src/display_item_cache.rs new file mode 100644 index 00000000000..169e54797a9 --- /dev/null +++ b/third_party/webrender/webrender_api/src/display_item_cache.rs @@ -0,0 +1,115 @@ +/* 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 crate::display_item::*; +use crate::display_list::*; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct CachedDisplayItem { + item: DisplayItem, + data: Vec, +} + +impl CachedDisplayItem { + pub fn display_item(&self) -> &DisplayItem { + &self.item + } + + pub fn data_as_item_range(&self) -> ItemRange { + ItemRange::new(&self.data) + } +} + +impl MallocSizeOf for CachedDisplayItem { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.data.size_of(ops) + } +} + +impl From> for CachedDisplayItem { + fn from(item_ref: DisplayItemRef) -> Self { + let item = item_ref.item(); + + match item { + DisplayItem::Text(..) => CachedDisplayItem { + item: *item, + data: item_ref.glyphs().bytes().to_vec(), + }, + _ => CachedDisplayItem { + item: *item, + data: Vec::new(), + }, + } + } +} + +#[derive(Clone, Deserialize, MallocSizeOf, Serialize)] +struct CacheEntry { + items: Vec, + occupied: bool, +} + +#[derive(Clone, Deserialize, MallocSizeOf, Serialize)] +pub struct DisplayItemCache { + entries: Vec, +} + +impl DisplayItemCache { + fn add_item(&mut self, key: ItemKey, item: CachedDisplayItem) { + let mut entry = &mut self.entries[key as usize]; + entry.items.push(item); + entry.occupied = true; + } + + fn clear_entry(&mut self, key: ItemKey) { + let mut entry = &mut self.entries[key as usize]; + entry.items.clear(); + entry.occupied = false; + } + + fn grow_if_needed(&mut self, capacity: usize) { + if capacity > self.entries.len() { + self.entries.resize_with(capacity, || CacheEntry { + items: Vec::new(), + occupied: false, + }); + } + } + + pub fn get_items(&self, key: ItemKey) -> &[CachedDisplayItem] { + let entry = &self.entries[key as usize]; + debug_assert!(entry.occupied); + entry.items.as_slice() + } + + pub fn new() -> Self { + Self { + entries: Vec::new(), + } + } + + pub fn update(&mut self, display_list: &BuiltDisplayList) { + self.grow_if_needed(display_list.cache_size()); + + let mut iter = display_list.extra_data_iter(); + let mut current_key: Option = None; + loop { + let item = match iter.next() { + Some(item) => item, + None => break, + }; + + if let DisplayItem::RetainedItems(key) = item.item() { + current_key = Some(*key); + self.clear_entry(*key); + continue; + } + + let key = current_key.expect("Missing RetainedItems marker"); + let cached_item = CachedDisplayItem::from(item); + self.add_item(key, cached_item); + } + } +} diff --git a/third_party/webrender/webrender_api/src/display_list.rs b/third_party/webrender/webrender_api/src/display_list.rs new file mode 100644 index 00000000000..0680a9875d3 --- /dev/null +++ b/third_party/webrender/webrender_api/src/display_list.rs @@ -0,0 +1,1969 @@ +/* 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::SideOffsets2D; +use peek_poke::{ensure_red_zone, peek_from_slice, poke_extend_vec}; +use peek_poke::{poke_inplace_slice, poke_into_vec, Poke}; +#[cfg(feature = "deserialize")] +use serde::de::Deserializer; +#[cfg(feature = "serialize")] +use serde::ser::{Serializer, SerializeSeq}; +use serde::{Deserialize, Serialize}; +use std::io::Write; +use std::marker::PhantomData; +use std::ops::Range; +use std::mem; +use std::collections::HashMap; +use time::precise_time_ns; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +// local imports +use crate::display_item as di; +use crate::display_item_cache::*; +use crate::api::{PipelineId, PropertyBinding}; +use crate::gradient_builder::GradientBuilder; +use crate::color::ColorF; +use crate::font::{FontInstanceKey, GlyphInstance, GlyphOptions}; +use crate::image::{ColorDepth, ImageKey}; +use crate::units::*; + + +// We don't want to push a long text-run. If a text-run is too long, split it into several parts. +// This needs to be set to (renderer::MAX_VERTEX_TEXTURE_WIDTH - VECS_PER_TEXT_RUN) * 2 +pub const MAX_TEXT_RUN_LENGTH: usize = 2040; + +// See ROOT_REFERENCE_FRAME_SPATIAL_ID and ROOT_SCROLL_NODE_SPATIAL_ID +// TODO(mrobinson): It would be a good idea to eliminate the root scroll frame which is only +// used by Servo. +const FIRST_SPATIAL_NODE_INDEX: usize = 2; + +// See ROOT_SCROLL_NODE_SPATIAL_ID +const FIRST_CLIP_NODE_INDEX: usize = 1; + +#[repr(C)] +#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct ItemRange<'a, T> { + bytes: &'a [u8], + _boo: PhantomData, +} + +impl<'a, T> Copy for ItemRange<'a, T> {} +impl<'a, T> Clone for ItemRange<'a, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T> Default for ItemRange<'a, T> { + fn default() -> Self { + ItemRange { + bytes: Default::default(), + _boo: PhantomData, + } + } +} + +impl<'a, T> ItemRange<'a, T> { + pub fn new(bytes: &'a [u8]) -> Self { + Self { + bytes, + _boo: PhantomData + } + } + + pub fn is_empty(&self) -> bool { + // Nothing more than space for a length (0). + self.bytes.len() <= mem::size_of::() + } + + pub fn bytes(&self) -> &[u8] { + &self.bytes + } +} + +impl<'a, T: Default> ItemRange<'a, T> { + pub fn iter(&self) -> AuxIter<'a, T> { + AuxIter::new(T::default(), self.bytes) + } +} + +impl<'a, T> IntoIterator for ItemRange<'a, T> +where + T: Copy + Default + peek_poke::Peek, +{ + type Item = T; + type IntoIter = AuxIter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +#[derive(Copy, Clone)] +pub struct TempFilterData<'a> { + pub func_types: ItemRange<'a, di::ComponentTransferFuncType>, + pub r_values: ItemRange<'a, f32>, + pub g_values: ItemRange<'a, f32>, + pub b_values: ItemRange<'a, f32>, + pub a_values: ItemRange<'a, f32>, +} + +/// A display list. +#[derive(Clone, Default)] +pub struct BuiltDisplayList { + /// Serde encoded bytes. Mostly DisplayItems, but some mixed in slices. + data: Vec, + descriptor: BuiltDisplayListDescriptor, +} + +/// Describes the memory layout of a display list. +/// +/// A display list consists of some number of display list items, followed by a number of display +/// items. +#[repr(C)] +#[derive(Copy, Clone, Default, Deserialize, Serialize)] +pub struct BuiltDisplayListDescriptor { + /// The first IPC time stamp: before any work has been done + builder_start_time: u64, + /// The second IPC time stamp: after serialization + builder_finish_time: u64, + /// The third IPC time stamp: just before sending + send_start_time: u64, + /// The amount of clipping nodes created while building this display list. + total_clip_nodes: usize, + /// The amount of spatial nodes created while building this display list. + total_spatial_nodes: usize, + /// The size of the cache for this display list. + cache_size: usize, + /// The offset for additional display list data. + extra_data_offset: usize, +} + +#[derive(Clone)] +pub struct DisplayListWithCache { + display_list: BuiltDisplayList, + cache: DisplayItemCache, +} + +impl DisplayListWithCache { + pub fn iter(&self) -> BuiltDisplayListIter { + self.display_list.iter_with_cache(&self.cache) + } + + pub fn new_from_list(display_list: BuiltDisplayList) -> Self { + let mut cache = DisplayItemCache::new(); + cache.update(&display_list); + + DisplayListWithCache { + display_list, + cache + } + } + + pub fn update(&mut self, display_list: BuiltDisplayList) { + self.cache.update(&display_list); + self.display_list = display_list; + } + + pub fn descriptor(&self) -> &BuiltDisplayListDescriptor { + self.display_list.descriptor() + } + + pub fn data(&self) -> &[u8] { + self.display_list.data() + } +} + +impl MallocSizeOf for DisplayListWithCache { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.display_list.data.size_of(ops) + self.cache.size_of(ops) + } +} + +#[cfg(feature = "serialize")] +impl Serialize for DisplayListWithCache { + fn serialize( + &self, + serializer: S + ) -> Result { + BuiltDisplayList::serialize_with_iterator(serializer, self.iter()) + } +} + +#[cfg(feature = "deserialize")] +impl<'de> Deserialize<'de> for DisplayListWithCache { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let display_list = BuiltDisplayList::deserialize(deserializer)?; + let cache = DisplayItemCache::new(); + + Ok(DisplayListWithCache { + display_list, + cache, + }) + } +} + +impl BuiltDisplayListDescriptor {} + +pub struct BuiltDisplayListIter<'a> { + list: &'a BuiltDisplayList, + data: &'a [u8], + cache: Option<&'a DisplayItemCache>, + pending_items: std::slice::Iter<'a, CachedDisplayItem>, + cur_cached_item: Option<&'a CachedDisplayItem>, + cur_item: di::DisplayItem, + cur_stops: ItemRange<'a, di::GradientStop>, + cur_glyphs: ItemRange<'a, GlyphInstance>, + cur_filters: ItemRange<'a, di::FilterOp>, + cur_filter_data: Vec>, + cur_filter_primitives: ItemRange<'a, di::FilterPrimitive>, + cur_clip_chain_items: ItemRange<'a, di::ClipId>, + cur_complex_clip: ItemRange<'a, di::ComplexClipRegion>, + peeking: Peek, + /// Should just be initialized but never populated in release builds + debug_stats: DebugStats, +} + +/// Internal info used for more detailed analysis of serialized display lists +#[allow(dead_code)] +struct DebugStats { + /// Last address in the buffer we pointed to, for computing serialized sizes + last_addr: usize, + stats: HashMap<&'static str, ItemStats>, +} + +impl DebugStats { + #[cfg(feature = "display_list_stats")] + fn _update_entry(&mut self, name: &'static str, item_count: usize, byte_count: usize) { + let entry = self.stats.entry(name).or_default(); + entry.total_count += item_count; + entry.num_bytes += byte_count; + } + + /// Computes the number of bytes we've processed since we last called + /// this method, so we can compute the serialized size of a display item. + #[cfg(feature = "display_list_stats")] + fn debug_num_bytes(&mut self, data: &[u8]) -> usize { + let old_addr = self.last_addr; + let new_addr = data.as_ptr() as usize; + let delta = new_addr - old_addr; + self.last_addr = new_addr; + + delta + } + + /// Logs stats for the last deserialized display item + #[cfg(feature = "display_list_stats")] + fn log_item(&mut self, data: &[u8], item: &di::DisplayItem) { + let num_bytes = self.debug_num_bytes(data); + self._update_entry(item.debug_name(), 1, num_bytes); + } + + /// Logs the stats for the given serialized slice + #[cfg(feature = "display_list_stats")] + fn log_slice( + &mut self, + slice_name: &'static str, + range: &ItemRange, + ) { + // Run this so log_item_stats is accurate, but ignore its result + // because log_slice_stats may be called after multiple slices have been + // processed, and the `range` has everything we need. + self.last_addr = range.bytes.as_ptr() as usize + range.bytes.len(); + + self._update_entry(slice_name, range.iter().len(), range.bytes.len()); + } + + #[cfg(not(feature = "display_list_stats"))] + fn log_slice(&mut self, _slice_name: &str, _range: &ItemRange) { + /* no-op */ + } +} + +/// Stats for an individual item +#[derive(Copy, Clone, Debug, Default)] +pub struct ItemStats { + /// How many instances of this kind of item we deserialized + pub total_count: usize, + /// How many bytes we processed for this kind of item + pub num_bytes: usize, +} + +pub struct DisplayItemRef<'a: 'b, 'b> { + iter: &'b BuiltDisplayListIter<'a>, +} + +// Some of these might just become ItemRanges +impl<'a, 'b> DisplayItemRef<'a, 'b> { + pub fn display_list(&self) -> &BuiltDisplayList { + self.iter.display_list() + } + + // Creates a new iterator where this element's iterator is, to hack around borrowck. + pub fn sub_iter(&self) -> BuiltDisplayListIter<'a> { + self.iter.sub_iter() + } + + pub fn item(&self) -> &di::DisplayItem { + self.iter.current_item() + } + + pub fn clip_chain_items(&self) -> ItemRange { + self.iter.cur_clip_chain_items + } + + pub fn complex_clip(&self) -> ItemRange { + self.iter.cur_complex_clip + } + + pub fn glyphs(&self) -> ItemRange { + self.iter.glyphs() + } + + pub fn gradient_stops(&self) -> ItemRange { + self.iter.gradient_stops() + } + + pub fn filters(&self) -> ItemRange { + self.iter.cur_filters + } + + pub fn filter_datas(&self) -> &Vec { + &self.iter.cur_filter_data + } + + pub fn filter_primitives(&self) -> ItemRange { + self.iter.cur_filter_primitives + } +} + +#[derive(PartialEq)] +enum Peek { + StartPeeking, + IsPeeking, + NotPeeking, +} + +#[derive(Clone)] +pub struct AuxIter<'a, T> { + item: T, + data: &'a [u8], + size: usize, +// _boo: PhantomData, +} + +impl BuiltDisplayList { + pub fn from_data(data: Vec, descriptor: BuiltDisplayListDescriptor) -> Self { + BuiltDisplayList { data, descriptor } + } + + pub fn into_data(self) -> (Vec, BuiltDisplayListDescriptor) { + (self.data, self.descriptor) + } + + pub fn data(&self) -> &[u8] { + &self.data[..] + } + + pub fn item_slice(&self) -> &[u8] { + &self.data[..self.descriptor.extra_data_offset] + } + + pub fn extra_slice(&self) -> &[u8] { + &self.data[self.descriptor.extra_data_offset..] + } + + pub fn descriptor(&self) -> &BuiltDisplayListDescriptor { + &self.descriptor + } + + pub fn set_send_time_ns(&mut self, time: u64) { + self.descriptor.send_start_time = time; + } + + pub fn times(&self) -> (u64, u64, u64) { + ( + self.descriptor.builder_start_time, + self.descriptor.builder_finish_time, + self.descriptor.send_start_time, + ) + } + + pub fn total_clip_nodes(&self) -> usize { + self.descriptor.total_clip_nodes + } + + pub fn total_spatial_nodes(&self) -> usize { + self.descriptor.total_spatial_nodes + } + + pub fn iter(&self) -> BuiltDisplayListIter { + BuiltDisplayListIter::new(self, self.item_slice(), None) + } + + pub fn extra_data_iter(&self) -> BuiltDisplayListIter { + BuiltDisplayListIter::new(self, self.extra_slice(), None) + } + + pub fn iter_with_cache<'a>( + &'a self, + cache: &'a DisplayItemCache + ) -> BuiltDisplayListIter<'a> { + BuiltDisplayListIter::new(self, self.item_slice(), Some(cache)) + } + + pub fn cache_size(&self) -> usize { + self.descriptor.cache_size + } + + #[cfg(feature = "serialize")] + pub fn serialize_with_iterator( + serializer: S, + mut iterator: BuiltDisplayListIter, + ) -> Result { + use crate::display_item::DisplayItem as Real; + use crate::display_item::DebugDisplayItem as Debug; + + let mut seq = serializer.serialize_seq(None)?; + + while let Some(item) = iterator.next_raw() { + let serial_di = match *item.item() { + Real::Clip(v) => Debug::Clip( + v, + item.iter.cur_complex_clip.iter().collect() + ), + Real::ClipChain(v) => Debug::ClipChain( + v, + item.iter.cur_clip_chain_items.iter().collect() + ), + Real::ScrollFrame(v) => Debug::ScrollFrame(v), + Real::Text(v) => Debug::Text( + v, + item.iter.cur_glyphs.iter().collect() + ), + Real::SetFilterOps => Debug::SetFilterOps( + item.iter.cur_filters.iter().collect() + ), + Real::SetFilterData => { + debug_assert!(!item.iter.cur_filter_data.is_empty(), + "next_raw should have populated cur_filter_data"); + let temp_filter_data = &item.iter.cur_filter_data[item.iter.cur_filter_data.len()-1]; + + let func_types: Vec = + temp_filter_data.func_types.iter().collect(); + debug_assert!(func_types.len() == 4, + "someone changed the number of filter funcs without updating this code"); + Debug::SetFilterData(di::FilterData { + func_r_type: func_types[0], + r_values: temp_filter_data.r_values.iter().collect(), + func_g_type: func_types[1], + g_values: temp_filter_data.g_values.iter().collect(), + func_b_type: func_types[2], + b_values: temp_filter_data.b_values.iter().collect(), + func_a_type: func_types[3], + a_values: temp_filter_data.a_values.iter().collect(), + }) + }, + Real::SetFilterPrimitives => Debug::SetFilterPrimitives( + item.iter.cur_filter_primitives.iter().collect() + ), + Real::SetGradientStops => Debug::SetGradientStops( + item.iter.cur_stops.iter().collect() + ), + Real::RectClip(v) => Debug::RectClip(v), + Real::RoundedRectClip(v) => Debug::RoundedRectClip(v), + Real::ImageMaskClip(v) => Debug::ImageMaskClip(v), + Real::StickyFrame(v) => Debug::StickyFrame(v), + Real::Rectangle(v) => Debug::Rectangle(v), + Real::ClearRectangle(v) => Debug::ClearRectangle(v), + Real::HitTest(v) => Debug::HitTest(v), + Real::Line(v) => Debug::Line(v), + Real::Image(v) => Debug::Image(v), + Real::RepeatingImage(v) => Debug::RepeatingImage(v), + Real::YuvImage(v) => Debug::YuvImage(v), + Real::Border(v) => Debug::Border(v), + Real::BoxShadow(v) => Debug::BoxShadow(v), + Real::Gradient(v) => Debug::Gradient(v), + Real::RadialGradient(v) => Debug::RadialGradient(v), + Real::ConicGradient(v) => Debug::ConicGradient(v), + Real::Iframe(v) => Debug::Iframe(v), + Real::PushReferenceFrame(v) => Debug::PushReferenceFrame(v), + Real::PushStackingContext(v) => Debug::PushStackingContext(v), + Real::PushShadow(v) => Debug::PushShadow(v), + Real::BackdropFilter(v) => Debug::BackdropFilter(v), + + Real::PopReferenceFrame => Debug::PopReferenceFrame, + Real::PopStackingContext => Debug::PopStackingContext, + Real::PopAllShadows => Debug::PopAllShadows, + Real::ReuseItems(_) | + Real::RetainedItems(_) => unreachable!("Unexpected item"), + }; + seq.serialize_element(&serial_di)? + } + seq.end() + } +} + +/// Returns the byte-range the slice occupied. +fn skip_slice<'a, T: peek_poke::Peek>(data: &mut &'a [u8]) -> ItemRange<'a, T> { + let mut skip_offset = 0usize; + *data = peek_from_slice(data, &mut skip_offset); + let (skip, rest) = data.split_at(skip_offset); + + // Adjust data pointer to skip read values + *data = rest; + + ItemRange { + bytes: skip, + _boo: PhantomData, + } +} + +impl<'a> BuiltDisplayListIter<'a> { + pub fn new( + list: &'a BuiltDisplayList, + data: &'a [u8], + cache: Option<&'a DisplayItemCache>, + ) -> Self { + Self { + list, + data, + cache, + pending_items: [].iter(), + cur_cached_item: None, + cur_item: di::DisplayItem::PopStackingContext, + cur_stops: ItemRange::default(), + cur_glyphs: ItemRange::default(), + cur_filters: ItemRange::default(), + cur_filter_data: Vec::new(), + cur_filter_primitives: ItemRange::default(), + cur_clip_chain_items: ItemRange::default(), + cur_complex_clip: ItemRange::default(), + peeking: Peek::NotPeeking, + debug_stats: DebugStats { + last_addr: data.as_ptr() as usize, + stats: HashMap::default(), + }, + } + } + + pub fn sub_iter(&self) -> Self { + let mut iter = BuiltDisplayListIter::new( + self.list, self.data, self.cache + ); + iter.pending_items = self.pending_items.clone(); + iter + } + + pub fn display_list(&self) -> &'a BuiltDisplayList { + self.list + } + + pub fn current_item(&self) -> &di::DisplayItem { + match self.cur_cached_item { + Some(cached_item) => cached_item.display_item(), + None => &self.cur_item + } + } + + fn cached_item_range_or( + &self, + data: ItemRange<'a, T> + ) -> ItemRange<'a, T> { + match self.cur_cached_item { + Some(cached_item) => cached_item.data_as_item_range(), + None => data, + } + } + + pub fn glyphs(&self) -> ItemRange { + self.cached_item_range_or(self.cur_glyphs) + } + + pub fn gradient_stops(&self) -> ItemRange { + self.cached_item_range_or(self.cur_stops) + } + + fn advance_pending_items(&mut self) -> bool { + self.cur_cached_item = self.pending_items.next(); + self.cur_cached_item.is_some() + } + + pub fn next<'b>(&'b mut self) -> Option> { + use crate::DisplayItem::*; + + match self.peeking { + Peek::IsPeeking => { + self.peeking = Peek::NotPeeking; + return Some(self.as_ref()); + } + Peek::StartPeeking => { + self.peeking = Peek::IsPeeking; + } + Peek::NotPeeking => { /* do nothing */ } + } + + // Don't let these bleed into another item + self.cur_stops = ItemRange::default(); + self.cur_complex_clip = ItemRange::default(); + self.cur_clip_chain_items = ItemRange::default(); + self.cur_filters = ItemRange::default(); + self.cur_filter_primitives = ItemRange::default(); + self.cur_filter_data.clear(); + + loop { + self.next_raw()?; + match self.cur_item { + SetGradientStops | + SetFilterOps | + SetFilterData | + SetFilterPrimitives => { + // These are marker items for populating other display items, don't yield them. + continue; + } + _ => { + break; + } + } + } + + Some(self.as_ref()) + } + + /// Gets the next display item, even if it's a dummy. Also doesn't handle peeking + /// and may leave irrelevant ranges live (so a Clip may have GradientStops if + /// for some reason you ask). + pub fn next_raw<'b>(&'b mut self) -> Option> { + use crate::DisplayItem::*; + + if self.advance_pending_items() { + return Some(self.as_ref()); + } + + // A "red zone" of DisplayItem::max_size() bytes has been added to the + // end of the serialized display list. If this amount, or less, is + // remaining then we've reached the end of the display list. + if self.data.len() <= di::DisplayItem::max_size() { + return None; + } + + self.data = peek_from_slice(self.data, &mut self.cur_item); + self.log_item_stats(); + + match self.cur_item { + SetGradientStops => { + self.cur_stops = skip_slice::(&mut self.data); + self.debug_stats.log_slice("set_gradient_stops.stops", &self.cur_stops); + } + SetFilterOps => { + self.cur_filters = skip_slice::(&mut self.data); + self.debug_stats.log_slice("set_filter_ops.ops", &self.cur_filters); + } + SetFilterData => { + self.cur_filter_data.push(TempFilterData { + func_types: skip_slice::(&mut self.data), + r_values: skip_slice::(&mut self.data), + g_values: skip_slice::(&mut self.data), + b_values: skip_slice::(&mut self.data), + a_values: skip_slice::(&mut self.data), + }); + + let data = *self.cur_filter_data.last().unwrap(); + self.debug_stats.log_slice("set_filter_data.func_types", &data.func_types); + self.debug_stats.log_slice("set_filter_data.r_values", &data.r_values); + self.debug_stats.log_slice("set_filter_data.g_values", &data.g_values); + self.debug_stats.log_slice("set_filter_data.b_values", &data.b_values); + self.debug_stats.log_slice("set_filter_data.a_values", &data.a_values); + } + SetFilterPrimitives => { + self.cur_filter_primitives = skip_slice::(&mut self.data); + self.debug_stats.log_slice("set_filter_primitives.primitives", &self.cur_filter_primitives); + } + ClipChain(_) => { + self.cur_clip_chain_items = skip_slice::(&mut self.data); + self.debug_stats.log_slice("clip_chain.clip_ids", &self.cur_clip_chain_items); + } + Clip(_) => { + self.cur_complex_clip = skip_slice::(&mut self.data); + self.debug_stats.log_slice("clip.complex_clips", &self.cur_complex_clip); + } + Text(_) => { + self.cur_glyphs = skip_slice::(&mut self.data); + self.debug_stats.log_slice("text.glyphs", &self.cur_glyphs); + } + ReuseItems(key) => { + match self.cache { + Some(cache) => { + self.pending_items = cache.get_items(key).iter(); + self.advance_pending_items(); + } + None => { + unreachable!("Cache marker without cache!"); + } + } + } + _ => { /* do nothing */ } + } + + Some(self.as_ref()) + } + + pub fn as_ref<'b>(&'b self) -> DisplayItemRef<'a, 'b> { + DisplayItemRef { + iter: self, + } + } + + pub fn skip_current_stacking_context(&mut self) { + let mut depth = 0; + while let Some(item) = self.next() { + match *item.item() { + di::DisplayItem::PushStackingContext(..) => depth += 1, + di::DisplayItem::PopStackingContext if depth == 0 => return, + di::DisplayItem::PopStackingContext => depth -= 1, + _ => {} + } + } + } + + pub fn current_stacking_context_empty(&mut self) -> bool { + match self.peek() { + Some(item) => *item.item() == di::DisplayItem::PopStackingContext, + None => true, + } + } + + pub fn peek<'b>(&'b mut self) -> Option> { + if self.peeking == Peek::NotPeeking { + self.peeking = Peek::StartPeeking; + self.next() + } else { + Some(self.as_ref()) + } + } + + /// Get the debug stats for what this iterator has deserialized. + /// Should always be empty in release builds. + pub fn debug_stats(&mut self) -> Vec<(&'static str, ItemStats)> { + let mut result = self.debug_stats.stats.drain().collect::>(); + result.sort_by_key(|stats| stats.0); + result + } + + /// Adds the debug stats from another to our own, assuming we are a sub-iter of the other + /// (so we can ignore where they were in the traversal). + pub fn merge_debug_stats_from(&mut self, other: &mut Self) { + for (key, other_entry) in other.debug_stats.stats.iter() { + let entry = self.debug_stats.stats.entry(key).or_default(); + + entry.total_count += other_entry.total_count; + entry.num_bytes += other_entry.num_bytes; + } + } + + /// Logs stats for the last deserialized display item + #[cfg(feature = "display_list_stats")] + fn log_item_stats(&mut self) { + self.debug_stats.log_item(self.data, &self.cur_item); + } + + #[cfg(not(feature = "display_list_stats"))] + fn log_item_stats(&mut self) { /* no-op */ } +} + +impl<'a, T> AuxIter<'a, T> { + pub fn new(item: T, mut data: &'a [u8]) -> Self { + let mut size = 0usize; + if !data.is_empty() { + data = peek_from_slice(data, &mut size); + }; + + AuxIter { + item, + data, + size, +// _boo: PhantomData, + } + } +} + +impl<'a, T: Copy + peek_poke::Peek> Iterator for AuxIter<'a, T> { + type Item = T; + + fn next(&mut self) -> Option { + if self.size == 0 { + None + } else { + self.size -= 1; + self.data = peek_from_slice(self.data, &mut self.item); + Some(self.item) + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.size, Some(self.size)) + } +} + +impl<'a, T: Copy + peek_poke::Peek> ::std::iter::ExactSizeIterator for AuxIter<'a, T> {} + +#[cfg(feature = "serialize")] +impl Serialize for BuiltDisplayList { + fn serialize( + &self, + serializer: S + ) -> Result { + Self::serialize_with_iterator(serializer, self.iter()) + } +} + +// The purpose of this implementation is to deserialize +// a display list from one format just to immediately +// serialize then into a "built" `Vec`. + +#[cfg(feature = "deserialize")] +impl<'de> Deserialize<'de> for BuiltDisplayList { + fn deserialize>( + deserializer: D + ) -> Result { + use crate::display_item::DisplayItem as Real; + use crate::display_item::DebugDisplayItem as Debug; + + let list = Vec::::deserialize(deserializer)?; + + let mut data = Vec::new(); + let mut temp = Vec::new(); + let mut total_clip_nodes = FIRST_CLIP_NODE_INDEX; + let mut total_spatial_nodes = FIRST_SPATIAL_NODE_INDEX; + for complete in list { + let item = match complete { + Debug::Clip(v, complex_clips) => { + total_clip_nodes += 1; + DisplayListBuilder::push_iter_impl(&mut temp, complex_clips); + Real::Clip(v) + }, + Debug::ClipChain(v, clip_chain_ids) => { + DisplayListBuilder::push_iter_impl(&mut temp, clip_chain_ids); + Real::ClipChain(v) + } + Debug::ScrollFrame(v) => { + total_spatial_nodes += 1; + total_clip_nodes += 1; + Real::ScrollFrame(v) + } + Debug::StickyFrame(v) => { + total_spatial_nodes += 1; + Real::StickyFrame(v) + } + Debug::Text(v, glyphs) => { + DisplayListBuilder::push_iter_impl(&mut temp, glyphs); + Real::Text(v) + }, + Debug::Iframe(v) => { + total_clip_nodes += 1; + Real::Iframe(v) + } + Debug::PushReferenceFrame(v) => { + total_spatial_nodes += 1; + Real::PushReferenceFrame(v) + } + Debug::SetFilterOps(filters) => { + DisplayListBuilder::push_iter_impl(&mut temp, filters); + Real::SetFilterOps + }, + Debug::SetFilterData(filter_data) => { + let func_types: Vec = + [filter_data.func_r_type, + filter_data.func_g_type, + filter_data.func_b_type, + filter_data.func_a_type].to_vec(); + DisplayListBuilder::push_iter_impl(&mut temp, func_types); + DisplayListBuilder::push_iter_impl(&mut temp, filter_data.r_values); + DisplayListBuilder::push_iter_impl(&mut temp, filter_data.g_values); + DisplayListBuilder::push_iter_impl(&mut temp, filter_data.b_values); + DisplayListBuilder::push_iter_impl(&mut temp, filter_data.a_values); + Real::SetFilterData + }, + Debug::SetFilterPrimitives(filter_primitives) => { + DisplayListBuilder::push_iter_impl(&mut temp, filter_primitives); + Real::SetFilterPrimitives + } + Debug::SetGradientStops(stops) => { + DisplayListBuilder::push_iter_impl(&mut temp, stops); + Real::SetGradientStops + }, + Debug::RectClip(v) => Real::RectClip(v), + Debug::RoundedRectClip(v) => Real::RoundedRectClip(v), + Debug::ImageMaskClip(v) => Real::ImageMaskClip(v), + Debug::Rectangle(v) => Real::Rectangle(v), + Debug::ClearRectangle(v) => Real::ClearRectangle(v), + Debug::HitTest(v) => Real::HitTest(v), + Debug::Line(v) => Real::Line(v), + Debug::Image(v) => Real::Image(v), + Debug::RepeatingImage(v) => Real::RepeatingImage(v), + Debug::YuvImage(v) => Real::YuvImage(v), + Debug::Border(v) => Real::Border(v), + Debug::BoxShadow(v) => Real::BoxShadow(v), + Debug::Gradient(v) => Real::Gradient(v), + Debug::RadialGradient(v) => Real::RadialGradient(v), + Debug::ConicGradient(v) => Real::ConicGradient(v), + Debug::PushStackingContext(v) => Real::PushStackingContext(v), + Debug::PushShadow(v) => Real::PushShadow(v), + Debug::BackdropFilter(v) => Real::BackdropFilter(v), + + Debug::PopStackingContext => Real::PopStackingContext, + Debug::PopReferenceFrame => Real::PopReferenceFrame, + Debug::PopAllShadows => Real::PopAllShadows, + }; + poke_into_vec(&item, &mut data); + // the aux data is serialized after the item, hence the temporary + data.extend(temp.drain(..)); + } + + // Add `DisplayItem::max_size` zone of zeroes to the end of display list + // so there is at least this amount available in the display list during + // serialization. + ensure_red_zone::(&mut data); + let extra_data_offset = data.len(); + + Ok(BuiltDisplayList { + data, + descriptor: BuiltDisplayListDescriptor { + builder_start_time: 0, + builder_finish_time: 1, + send_start_time: 1, + total_clip_nodes, + total_spatial_nodes, + extra_data_offset, + cache_size: 0, + }, + }) + } +} + +#[derive(Clone, Debug)] +pub struct SaveState { + dl_len: usize, + next_clip_index: usize, + next_spatial_index: usize, + next_clip_chain_id: u64, +} + +/// DisplayListSection determines the target buffer for the display items. +pub enum DisplayListSection { + /// The main/default buffer: contains item data and item group markers. + Data, + /// Auxiliary buffer: contains the item data for item groups. + ExtraData, + /// Temporary buffer: contains the data for pending item group. Flushed to + /// one of the buffers above, after item grouping finishes. + Chunk, +} + +#[derive(Clone)] +pub struct DisplayListBuilder { + pub data: Vec, + pub pipeline_id: PipelineId, + + extra_data: Vec, + pending_chunk: Vec, + writing_to_chunk: bool, + + next_clip_index: usize, + next_spatial_index: usize, + next_clip_chain_id: u64, + builder_start_time: u64, + + /// The size of the content of this display list. This is used to allow scrolling + /// outside the bounds of the display list items themselves. + content_size: LayoutSize, + save_state: Option, + + cache_size: usize, + serialized_content_buffer: Option, +} + +impl DisplayListBuilder { + pub fn new(pipeline_id: PipelineId, content_size: LayoutSize) -> Self { + Self::with_capacity(pipeline_id, content_size, 0) + } + + pub fn with_capacity( + pipeline_id: PipelineId, + content_size: LayoutSize, + capacity: usize, + ) -> Self { + let start_time = precise_time_ns(); + + DisplayListBuilder { + data: Vec::with_capacity(capacity), + pipeline_id, + + extra_data: Vec::new(), + pending_chunk: Vec::new(), + writing_to_chunk: false, + + next_clip_index: FIRST_CLIP_NODE_INDEX, + next_spatial_index: FIRST_SPATIAL_NODE_INDEX, + next_clip_chain_id: 0, + builder_start_time: start_time, + content_size, + save_state: None, + cache_size: 0, + serialized_content_buffer: None, + } + } + + /// Return the content size for this display list + pub fn content_size(&self) -> LayoutSize { + self.content_size + } + + /// Saves the current display list state, so it may be `restore()`'d. + /// + /// # Conditions: + /// + /// * Doesn't support popping clips that were pushed before the save. + /// * Doesn't support nested saves. + /// * Must call `clear_save()` if the restore becomes unnecessary. + pub fn save(&mut self) { + assert!(self.save_state.is_none(), "DisplayListBuilder doesn't support nested saves"); + + self.save_state = Some(SaveState { + dl_len: self.data.len(), + next_clip_index: self.next_clip_index, + next_spatial_index: self.next_spatial_index, + next_clip_chain_id: self.next_clip_chain_id, + }); + } + + /// Restores the state of the builder to when `save()` was last called. + pub fn restore(&mut self) { + let state = self.save_state.take().expect("No save to restore DisplayListBuilder from"); + + self.data.truncate(state.dl_len); + self.next_clip_index = state.next_clip_index; + self.next_spatial_index = state.next_spatial_index; + self.next_clip_chain_id = state.next_clip_chain_id; + } + + /// Discards the builder's save (indicating the attempted operation was successful). + pub fn clear_save(&mut self) { + self.save_state.take().expect("No save to clear in DisplayListBuilder"); + } + + /// Emits a debug representation of display items in the list, for debugging + /// purposes. If the range's start parameter is specified, only display + /// items starting at that index (inclusive) will be printed. If the range's + /// end parameter is specified, only display items before that index + /// (exclusive) will be printed. Calling this function with end <= start is + /// allowed but is just a waste of CPU cycles. The function emits the + /// debug representation of the selected display items, one per line, with + /// the given indent, to the provided sink object. The return value is + /// the total number of items in the display list, which allows the + /// caller to subsequently invoke this function to only dump the newly-added + /// items. + pub fn emit_display_list( + &mut self, + indent: usize, + range: Range>, + mut sink: W, + ) -> usize + where + W: Write + { + let mut temp = BuiltDisplayList::default(); + mem::swap(&mut temp.data, &mut self.data); + + let mut index: usize = 0; + { + let mut iter = temp.iter(); + while let Some(item) = iter.next_raw() { + if index >= range.start.unwrap_or(0) && range.end.map_or(true, |e| index < e) { + writeln!(sink, "{}{:?}", " ".repeat(indent), item.item()).unwrap(); + } + index += 1; + } + } + + self.data = temp.data; + index + } + + /// Print the display items in the list to stdout. + pub fn dump_serialized_display_list(&mut self) { + self.serialized_content_buffer = Some(String::new()); + } + + fn add_to_display_list_dump(&mut self, item: T) { + if let Some(ref mut content) = self.serialized_content_buffer { + use std::fmt::Write; + write!(content, "{:?}\n", item).expect("DL dump write failed."); + } + } + + /// Returns the default section that DisplayListBuilder will write to, + /// if no section is specified explicitly. + fn default_section(&self) -> DisplayListSection { + if self.writing_to_chunk { + DisplayListSection::Chunk + } else { + DisplayListSection::Data + } + } + + fn buffer_from_section( + &mut self, + section: DisplayListSection + ) -> &mut Vec { + match section { + DisplayListSection::Data => &mut self.data, + DisplayListSection::ExtraData => &mut self.extra_data, + DisplayListSection::Chunk => &mut self.pending_chunk, + } + } + + #[inline] + pub fn push_item_to_section( + &mut self, + item: &di::DisplayItem, + section: DisplayListSection, + ) { + poke_into_vec(item, self.buffer_from_section(section)); + self.add_to_display_list_dump(item); + } + + /// Add an item to the display list. + /// + /// NOTE: It is usually preferable to use the specialized methods to push + /// display items. Pushing unexpected or invalid items here may + /// result in WebRender panicking or behaving in unexpected ways. + #[inline] + pub fn push_item(&mut self, item: &di::DisplayItem) { + self.push_item_to_section(item, self.default_section()); + } + + fn push_iter_impl(data: &mut Vec, iter_source: I) + where + I: IntoIterator, + I::IntoIter: ExactSizeIterator, + I::Item: Poke, + { + let iter = iter_source.into_iter(); + let len = iter.len(); + // Format: + // payload_byte_size: usize, item_count: usize, [I; item_count] + + // Track the the location of where to write byte size with offsets + // instead of pointers because data may be moved in memory during + // `serialize_iter_fast`. + let byte_size_offset = data.len(); + + // We write a dummy value so there's room for later + poke_into_vec(&0usize, data); + poke_into_vec(&len, data); + let count = poke_extend_vec(iter, data); + debug_assert_eq!(len, count, "iterator.len() returned two different values"); + + // Add red zone + ensure_red_zone::(data); + + // Now write the actual byte_size + let final_offset = data.len(); + debug_assert!(final_offset >= (byte_size_offset + mem::size_of::()), + "space was never allocated for this array's byte_size"); + let byte_size = final_offset - byte_size_offset - mem::size_of::(); + poke_inplace_slice(&byte_size, &mut data[byte_size_offset..]); + } + + /// Push items from an iterator to the display list. + /// + /// NOTE: Pushing unexpected or invalid items to the display list + /// may result in panic and confusion. + pub fn push_iter(&mut self, iter: I) + where + I: IntoIterator, + I::IntoIter: ExactSizeIterator, + I::Item: Poke, + { + let mut buffer = self.buffer_from_section(self.default_section()); + Self::push_iter_impl(&mut buffer, iter); + } + + pub fn push_rect( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + color: ColorF, + ) { + let item = di::DisplayItem::Rectangle(di::RectangleDisplayItem { + common: *common, + color: PropertyBinding::Value(color), + bounds, + }); + self.push_item(&item); + } + + pub fn push_rect_with_animation( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + color: PropertyBinding, + ) { + let item = di::DisplayItem::Rectangle(di::RectangleDisplayItem { + common: *common, + color, + bounds, + }); + self.push_item(&item); + } + + pub fn push_clear_rect( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + ) { + let item = di::DisplayItem::ClearRectangle(di::ClearRectangleDisplayItem { + common: *common, + bounds, + }); + self.push_item(&item); + } + + pub fn push_hit_test( + &mut self, + common: &di::CommonItemProperties, + ) { + let item = di::DisplayItem::HitTest(di::HitTestDisplayItem { + common: *common, + }); + self.push_item(&item); + } + + pub fn push_line( + &mut self, + common: &di::CommonItemProperties, + area: &LayoutRect, + wavy_line_thickness: f32, + orientation: di::LineOrientation, + color: &ColorF, + style: di::LineStyle, + ) { + let item = di::DisplayItem::Line(di::LineDisplayItem { + common: *common, + area: *area, + wavy_line_thickness, + orientation, + color: *color, + style, + }); + + self.push_item(&item); + } + + pub fn push_image( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + image_rendering: di::ImageRendering, + alpha_type: di::AlphaType, + key: ImageKey, + color: ColorF, + ) { + let item = di::DisplayItem::Image(di::ImageDisplayItem { + common: *common, + bounds, + image_key: key, + image_rendering, + alpha_type, + color, + }); + + self.push_item(&item); + } + + pub fn push_repeating_image( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + stretch_size: LayoutSize, + tile_spacing: LayoutSize, + image_rendering: di::ImageRendering, + alpha_type: di::AlphaType, + key: ImageKey, + color: ColorF, + ) { + let item = di::DisplayItem::RepeatingImage(di::RepeatingImageDisplayItem { + common: *common, + bounds, + image_key: key, + stretch_size, + tile_spacing, + image_rendering, + alpha_type, + color, + }); + + self.push_item(&item); + } + + /// Push a yuv image. All planar data in yuv image should use the same buffer type. + pub fn push_yuv_image( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + yuv_data: di::YuvData, + color_depth: ColorDepth, + color_space: di::YuvColorSpace, + color_range: di::ColorRange, + image_rendering: di::ImageRendering, + ) { + let item = di::DisplayItem::YuvImage(di::YuvImageDisplayItem { + common: *common, + bounds, + yuv_data, + color_depth, + color_space, + color_range, + image_rendering, + }); + self.push_item(&item); + } + + pub fn push_text( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + glyphs: &[GlyphInstance], + font_key: FontInstanceKey, + color: ColorF, + glyph_options: Option, + ) { + let item = di::DisplayItem::Text(di::TextDisplayItem { + common: *common, + bounds, + color, + font_key, + glyph_options, + }); + + for split_glyphs in glyphs.chunks(MAX_TEXT_RUN_LENGTH) { + self.push_item(&item); + self.push_iter(split_glyphs); + } + } + + /// NOTE: gradients must be pushed in the order they're created + /// because create_gradient stores the stops in anticipation. + pub fn create_gradient( + &mut self, + start_point: LayoutPoint, + end_point: LayoutPoint, + stops: Vec, + extend_mode: di::ExtendMode, + ) -> di::Gradient { + let mut builder = GradientBuilder::with_stops(stops); + let gradient = builder.gradient(start_point, end_point, extend_mode); + self.push_stops(builder.stops()); + gradient + } + + /// NOTE: gradients must be pushed in the order they're created + /// because create_gradient stores the stops in anticipation. + pub fn create_radial_gradient( + &mut self, + center: LayoutPoint, + radius: LayoutSize, + stops: Vec, + extend_mode: di::ExtendMode, + ) -> di::RadialGradient { + let mut builder = GradientBuilder::with_stops(stops); + let gradient = builder.radial_gradient(center, radius, extend_mode); + self.push_stops(builder.stops()); + gradient + } + + /// NOTE: gradients must be pushed in the order they're created + /// because create_gradient stores the stops in anticipation. + pub fn create_conic_gradient( + &mut self, + center: LayoutPoint, + angle: f32, + stops: Vec, + extend_mode: di::ExtendMode, + ) -> di::ConicGradient { + let mut builder = GradientBuilder::with_stops(stops); + let gradient = builder.conic_gradient(center, angle, extend_mode); + self.push_stops(builder.stops()); + gradient + } + + pub fn push_border( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + widths: LayoutSideOffsets, + details: di::BorderDetails, + ) { + let item = di::DisplayItem::Border(di::BorderDisplayItem { + common: *common, + bounds, + details, + widths, + }); + + self.push_item(&item); + } + + pub fn push_box_shadow( + &mut self, + common: &di::CommonItemProperties, + box_bounds: LayoutRect, + offset: LayoutVector2D, + color: ColorF, + blur_radius: f32, + spread_radius: f32, + border_radius: di::BorderRadius, + clip_mode: di::BoxShadowClipMode, + ) { + let item = di::DisplayItem::BoxShadow(di::BoxShadowDisplayItem { + common: *common, + box_bounds, + offset, + color, + blur_radius, + spread_radius, + border_radius, + clip_mode, + }); + + self.push_item(&item); + } + + /// Pushes a linear gradient to be displayed. + /// + /// The gradient itself is described in the + /// `gradient` parameter. It is drawn on + /// a "tile" with the dimensions from `tile_size`. + /// These tiles are now repeated to the right and + /// to the bottom infinitely. If `tile_spacing` + /// is not zero spacers with the given dimensions + /// are inserted between the tiles as seams. + /// + /// The origin of the tiles is given in `layout.rect.origin`. + /// If the gradient should only be displayed once limit + /// the `layout.rect.size` to a single tile. + /// The gradient is only visible within the local clip. + pub fn push_gradient( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + gradient: di::Gradient, + tile_size: LayoutSize, + tile_spacing: LayoutSize, + ) { + let item = di::DisplayItem::Gradient(di::GradientDisplayItem { + common: *common, + bounds, + gradient, + tile_size, + tile_spacing, + }); + + self.push_item(&item); + } + + /// Pushes a radial gradient to be displayed. + /// + /// See [`push_gradient`](#method.push_gradient) for explanation. + pub fn push_radial_gradient( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + gradient: di::RadialGradient, + tile_size: LayoutSize, + tile_spacing: LayoutSize, + ) { + let item = di::DisplayItem::RadialGradient(di::RadialGradientDisplayItem { + common: *common, + bounds, + gradient, + tile_size, + tile_spacing, + }); + + self.push_item(&item); + } + + /// Pushes a conic gradient to be displayed. + /// + /// See [`push_gradient`](#method.push_gradient) for explanation. + pub fn push_conic_gradient( + &mut self, + common: &di::CommonItemProperties, + bounds: LayoutRect, + gradient: di::ConicGradient, + tile_size: LayoutSize, + tile_spacing: LayoutSize, + ) { + let item = di::DisplayItem::ConicGradient(di::ConicGradientDisplayItem { + common: *common, + bounds, + gradient, + tile_size, + tile_spacing, + }); + + self.push_item(&item); + } + + pub fn push_reference_frame( + &mut self, + origin: LayoutPoint, + parent_spatial_id: di::SpatialId, + transform_style: di::TransformStyle, + transform: PropertyBinding, + kind: di::ReferenceFrameKind, + ) -> di::SpatialId { + let id = self.generate_spatial_index(); + + let item = di::DisplayItem::PushReferenceFrame(di::ReferenceFrameDisplayListItem { + parent_spatial_id, + origin, + reference_frame: di::ReferenceFrame { + transform_style, + transform, + kind, + id, + }, + }); + + self.push_item(&item); + id + } + + pub fn pop_reference_frame(&mut self) { + self.push_item(&di::DisplayItem::PopReferenceFrame); + } + + pub fn push_stacking_context( + &mut self, + origin: LayoutPoint, + spatial_id: di::SpatialId, + prim_flags: di::PrimitiveFlags, + clip_id: Option, + transform_style: di::TransformStyle, + mix_blend_mode: di::MixBlendMode, + filters: &[di::FilterOp], + filter_datas: &[di::FilterData], + filter_primitives: &[di::FilterPrimitive], + raster_space: di::RasterSpace, + flags: di::StackingContextFlags, + ) { + self.push_filters(filters, filter_datas, filter_primitives); + + let item = di::DisplayItem::PushStackingContext(di::PushStackingContextDisplayItem { + origin, + spatial_id, + prim_flags, + stacking_context: di::StackingContext { + transform_style, + mix_blend_mode, + clip_id, + raster_space, + flags, + }, + }); + + self.push_item(&item); + } + + /// Helper for examples/ code. + pub fn push_simple_stacking_context( + &mut self, + origin: LayoutPoint, + spatial_id: di::SpatialId, + prim_flags: di::PrimitiveFlags, + ) { + self.push_simple_stacking_context_with_filters( + origin, + spatial_id, + prim_flags, + &[], + &[], + &[], + ); + } + + /// Helper for examples/ code. + pub fn push_simple_stacking_context_with_filters( + &mut self, + origin: LayoutPoint, + spatial_id: di::SpatialId, + prim_flags: di::PrimitiveFlags, + filters: &[di::FilterOp], + filter_datas: &[di::FilterData], + filter_primitives: &[di::FilterPrimitive], + ) { + self.push_stacking_context( + origin, + spatial_id, + prim_flags, + None, + di::TransformStyle::Flat, + di::MixBlendMode::Normal, + filters, + filter_datas, + filter_primitives, + di::RasterSpace::Screen, + di::StackingContextFlags::empty(), + ); + } + + pub fn pop_stacking_context(&mut self) { + self.push_item(&di::DisplayItem::PopStackingContext); + } + + pub fn push_stops(&mut self, stops: &[di::GradientStop]) { + if stops.is_empty() { + return; + } + self.push_item(&di::DisplayItem::SetGradientStops); + self.push_iter(stops); + } + + pub fn push_backdrop_filter( + &mut self, + common: &di::CommonItemProperties, + filters: &[di::FilterOp], + filter_datas: &[di::FilterData], + filter_primitives: &[di::FilterPrimitive], + ) { + self.push_filters(filters, filter_datas, filter_primitives); + + let item = di::DisplayItem::BackdropFilter(di::BackdropFilterDisplayItem { + common: *common, + }); + self.push_item(&item); + } + + pub fn push_filters( + &mut self, + filters: &[di::FilterOp], + filter_datas: &[di::FilterData], + filter_primitives: &[di::FilterPrimitive], + ) { + if !filters.is_empty() { + self.push_item(&di::DisplayItem::SetFilterOps); + self.push_iter(filters); + } + + for filter_data in filter_datas { + let func_types = [ + filter_data.func_r_type, filter_data.func_g_type, + filter_data.func_b_type, filter_data.func_a_type]; + self.push_item(&di::DisplayItem::SetFilterData); + self.push_iter(&func_types); + self.push_iter(&filter_data.r_values); + self.push_iter(&filter_data.g_values); + self.push_iter(&filter_data.b_values); + self.push_iter(&filter_data.a_values); + } + + if !filter_primitives.is_empty() { + self.push_item(&di::DisplayItem::SetFilterPrimitives); + self.push_iter(filter_primitives); + } + } + + fn generate_clip_index(&mut self) -> di::ClipId { + self.next_clip_index += 1; + di::ClipId::Clip(self.next_clip_index - 1, self.pipeline_id) + } + + fn generate_spatial_index(&mut self) -> di::SpatialId { + self.next_spatial_index += 1; + di::SpatialId::new(self.next_spatial_index - 1, self.pipeline_id) + } + + fn generate_clip_chain_id(&mut self) -> di::ClipChainId { + self.next_clip_chain_id += 1; + di::ClipChainId(self.next_clip_chain_id - 1, self.pipeline_id) + } + + pub fn define_scroll_frame( + &mut self, + parent_space_and_clip: &di::SpaceAndClipInfo, + external_id: Option, + content_rect: LayoutRect, + clip_rect: LayoutRect, + scroll_sensitivity: di::ScrollSensitivity, + external_scroll_offset: LayoutVector2D, + ) -> di::SpaceAndClipInfo { + let clip_id = self.generate_clip_index(); + let scroll_frame_id = self.generate_spatial_index(); + let item = di::DisplayItem::ScrollFrame(di::ScrollFrameDisplayItem { + content_rect, + clip_rect, + parent_space_and_clip: *parent_space_and_clip, + clip_id, + scroll_frame_id, + external_id, + scroll_sensitivity, + external_scroll_offset, + }); + + self.push_item(&item); + + di::SpaceAndClipInfo { + spatial_id: scroll_frame_id, + clip_id, + } + } + + pub fn define_clip_chain( + &mut self, + parent: Option, + clips: I, + ) -> di::ClipChainId + where + I: IntoIterator, + I::IntoIter: ExactSizeIterator + Clone, + { + let id = self.generate_clip_chain_id(); + self.push_item(&di::DisplayItem::ClipChain(di::ClipChainItem { id, parent })); + self.push_iter(clips); + id + } + + pub fn define_clip_image_mask( + &mut self, + parent_space_and_clip: &di::SpaceAndClipInfo, + image_mask: di::ImageMask, + ) -> di::ClipId { + let id = self.generate_clip_index(); + let item = di::DisplayItem::ImageMaskClip(di::ImageMaskClipDisplayItem { + id, + parent_space_and_clip: *parent_space_and_clip, + image_mask, + }); + + self.push_item(&item); + id + } + + pub fn define_clip_rect( + &mut self, + parent_space_and_clip: &di::SpaceAndClipInfo, + clip_rect: LayoutRect, + ) -> di::ClipId { + let id = self.generate_clip_index(); + let item = di::DisplayItem::RectClip(di::RectClipDisplayItem { + id, + parent_space_and_clip: *parent_space_and_clip, + clip_rect, + }); + + self.push_item(&item); + id + } + + pub fn define_clip_rounded_rect( + &mut self, + parent_space_and_clip: &di::SpaceAndClipInfo, + clip: di::ComplexClipRegion, + ) -> di::ClipId { + let id = self.generate_clip_index(); + let item = di::DisplayItem::RoundedRectClip(di::RoundedRectClipDisplayItem { + id, + parent_space_and_clip: *parent_space_and_clip, + clip, + }); + + self.push_item(&item); + id + } + + pub fn define_clip( + &mut self, + parent_space_and_clip: &di::SpaceAndClipInfo, + clip_rect: LayoutRect, + complex_clips: I, + ) -> di::ClipId + where + I: IntoIterator, + I::IntoIter: ExactSizeIterator + Clone, + { + let id = self.generate_clip_index(); + let item = di::DisplayItem::Clip(di::ClipDisplayItem { + id, + parent_space_and_clip: *parent_space_and_clip, + clip_rect, + }); + + self.push_item(&item); + self.push_iter(complex_clips); + id + } + + pub fn define_sticky_frame( + &mut self, + parent_spatial_id: di::SpatialId, + frame_rect: LayoutRect, + margins: SideOffsets2D, LayoutPixel>, + vertical_offset_bounds: di::StickyOffsetBounds, + horizontal_offset_bounds: di::StickyOffsetBounds, + previously_applied_offset: LayoutVector2D, + ) -> di::SpatialId { + let id = self.generate_spatial_index(); + let item = di::DisplayItem::StickyFrame(di::StickyFrameDisplayItem { + parent_spatial_id, + id, + bounds: frame_rect, + margins, + vertical_offset_bounds, + horizontal_offset_bounds, + previously_applied_offset, + }); + + self.push_item(&item); + id + } + + pub fn push_iframe( + &mut self, + bounds: LayoutRect, + clip_rect: LayoutRect, + space_and_clip: &di::SpaceAndClipInfo, + pipeline_id: PipelineId, + ignore_missing_pipeline: bool + ) { + let item = di::DisplayItem::Iframe(di::IframeDisplayItem { + bounds, + clip_rect, + space_and_clip: *space_and_clip, + pipeline_id, + ignore_missing_pipeline, + }); + self.push_item(&item); + } + + pub fn push_shadow( + &mut self, + space_and_clip: &di::SpaceAndClipInfo, + shadow: di::Shadow, + should_inflate: bool, + ) { + let item = di::DisplayItem::PushShadow(di::PushShadowDisplayItem { + space_and_clip: *space_and_clip, + shadow, + should_inflate, + }); + self.push_item(&item); + } + + pub fn pop_all_shadows(&mut self) { + self.push_item(&di::DisplayItem::PopAllShadows); + } + + pub fn start_item_group(&mut self) { + debug_assert!(!self.writing_to_chunk); + debug_assert!(self.pending_chunk.is_empty()); + + self.writing_to_chunk = true; + } + + fn flush_pending_item_group(&mut self, key: di::ItemKey) { + // Push RetainedItems-marker to extra_data section. + self.push_retained_items(key); + + // Push pending chunk to extra_data section. + self.extra_data.append(&mut self.pending_chunk); + + // Push ReuseItems-marker to data section. + self.push_reuse_items(key); + } + + pub fn finish_item_group(&mut self, key: di::ItemKey) -> bool { + debug_assert!(self.writing_to_chunk); + self.writing_to_chunk = false; + + if self.pending_chunk.is_empty() { + return false; + } + + self.flush_pending_item_group(key); + true + } + + pub fn cancel_item_group(&mut self, discard: bool) { + debug_assert!(self.writing_to_chunk); + self.writing_to_chunk = false; + + if discard { + self.pending_chunk.clear(); + } else { + // Push pending chunk to data section. + self.data.append(&mut self.pending_chunk); + } + } + + pub fn push_reuse_items(&mut self, key: di::ItemKey) { + self.push_item_to_section( + &di::DisplayItem::ReuseItems(key), + DisplayListSection::Data + ); + } + + fn push_retained_items(&mut self, key: di::ItemKey) { + self.push_item_to_section( + &di::DisplayItem::RetainedItems(key), + DisplayListSection::ExtraData + ); + } + + pub fn set_cache_size(&mut self, cache_size: usize) { + self.cache_size = cache_size; + } + + pub fn finalize(mut self) -> (PipelineId, LayoutSize, BuiltDisplayList) { + assert!(self.save_state.is_none(), "Finalized DisplayListBuilder with a pending save"); + + if let Some(content) = self.serialized_content_buffer.take() { + println!("-- WebRender display list for {:?} --\n{}", + self.pipeline_id, content); + } + + // Add `DisplayItem::max_size` zone of zeroes to the end of display list + // so there is at least this amount available in the display list during + // serialization. + ensure_red_zone::(&mut self.data); + + let extra_data_offset = self.data.len(); + + if self.extra_data.len() > 0 { + ensure_red_zone::(&mut self.extra_data); + self.data.extend(self.extra_data); + } + + let end_time = precise_time_ns(); + ( + self.pipeline_id, + self.content_size, + BuiltDisplayList { + descriptor: BuiltDisplayListDescriptor { + builder_start_time: self.builder_start_time, + builder_finish_time: end_time, + send_start_time: end_time, + total_clip_nodes: self.next_clip_index, + total_spatial_nodes: self.next_spatial_index, + cache_size: self.cache_size, + extra_data_offset, + }, + data: self.data, + }, + ) + } +} diff --git a/third_party/webrender/webrender_api/src/font.rs b/third_party/webrender/webrender_api/src/font.rs new file mode 100644 index 00000000000..7f24736f09e --- /dev/null +++ b/third_party/webrender/webrender_api/src/font.rs @@ -0,0 +1,604 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#[cfg(target_os = "macos")] +use core_foundation::string::CFString; +#[cfg(target_os = "macos")] +use core_graphics::font::CGFont; +use peek_poke::PeekPoke; +#[cfg(target_os = "macos")] +use serde::de::{self, Deserialize, Deserializer}; +#[cfg(target_os = "macos")] +use serde::ser::{Serialize, Serializer}; +use std::cmp::Ordering; +use std::hash::{Hash, Hasher}; +#[cfg(not(target_os = "macos"))] +use std::path::PathBuf; +use std::sync::{Arc, RwLock, RwLockReadGuard, mpsc::Sender}; +use std::collections::HashMap; +// local imports +use crate::api::IdNamespace; +use crate::color::ColorU; +use crate::units::LayoutPoint; + +/// Hashable floating-point storage for font size. +#[repr(C)] +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, Deserialize, Serialize)] +pub struct FontSize(pub f32); + +impl Ord for FontSize { + fn cmp(&self, other: &FontSize) -> Ordering { + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +impl Eq for FontSize {} + +impl Hash for FontSize { + fn hash(&self, state: &mut H) { + self.0.to_bits().hash(state); + } +} + +impl From for FontSize { + fn from(size: f32) -> Self { FontSize(size) } +} + +impl From for f32 { + fn from(size: FontSize) -> Self { size.0 } +} + +impl FontSize { + pub fn zero() -> Self { FontSize(0.0) } + + pub fn from_f32_px(size: f32) -> Self { FontSize(size) } + + pub fn to_f32_px(&self) -> f32 { self.0 } + + pub fn from_f64_px(size: f64) -> Self { FontSize(size as f32) } + + pub fn to_f64_px(&self) -> f64 { self.0 as f64 } +} + +/// Immutable description of a font instance requested by the user of the API. +/// +/// `BaseFontInstance` can be identified by a `FontInstanceKey` so we should +/// never need to hash it. +#[derive(Clone, PartialEq, Eq, Debug, Ord, PartialOrd, MallocSizeOf)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +pub struct BaseFontInstance { + /// + pub instance_key: FontInstanceKey, + /// + pub font_key: FontKey, + /// + pub size: FontSize, + /// + pub bg_color: ColorU, + /// + pub render_mode: FontRenderMode, + /// + pub flags: FontInstanceFlags, + /// + pub synthetic_italics: SyntheticItalics, + /// + #[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(skip))] + pub platform_options: Option, + /// + pub variations: Vec, +} + +pub type FontInstanceMap = HashMap>; +/// A map of font instance data accessed concurrently from multiple threads. +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +pub struct SharedFontInstanceMap { + map: Arc>, +} + +impl SharedFontInstanceMap { + /// Creates an empty shared map. + pub fn new() -> Self { + SharedFontInstanceMap { + map: Arc::new(RwLock::new(HashMap::default())) + } + } + + /// Acquires a write lock on the shared map. + pub fn lock(&mut self) -> Option> { + self.map.read().ok() + } + + /// + pub fn get_font_instance_data(&self, key: FontInstanceKey) -> Option { + match self.map.read().unwrap().get(&key) { + Some(instance) => Some(FontInstanceData { + font_key: instance.font_key, + size: instance.size.into(), + options: Some(FontInstanceOptions { + render_mode: instance.render_mode, + flags: instance.flags, + bg_color: instance.bg_color, + synthetic_italics: instance.synthetic_italics, + }), + platform_options: instance.platform_options, + variations: instance.variations.clone(), + }), + None => None, + } + } + + /// Replace the shared map with the provided map. + pub fn set(&mut self, map: FontInstanceMap) { + *self.map.write().unwrap() = map; + } + + /// + pub fn get_font_instance(&self, instance_key: FontInstanceKey) -> Option> { + let instance_map = self.map.read().unwrap(); + instance_map.get(&instance_key).map(|instance| { Arc::clone(instance) }) + } + + /// + pub fn add_font_instance( + &mut self, + instance_key: FontInstanceKey, + font_key: FontKey, + size: f32, + options: Option, + platform_options: Option, + variations: Vec, + ) { + let FontInstanceOptions { + render_mode, + flags, + bg_color, + synthetic_italics, + .. + } = options.unwrap_or_default(); + + let instance = Arc::new(BaseFontInstance { + instance_key, + font_key, + size: size.into(), + bg_color, + render_mode, + flags, + synthetic_italics, + platform_options, + variations, + }); + + self.map + .write() + .unwrap() + .insert(instance_key, instance); + } + + /// + pub fn delete_font_instance(&mut self, instance_key: FontInstanceKey) { + self.map.write().unwrap().remove(&instance_key); + } + + /// + pub fn clear_namespace(&mut self, namespace: IdNamespace) { + self.map + .write() + .unwrap() + .retain(|key, _| key.0 != namespace); + } + + /// + pub fn clone_map(&self) -> FontInstanceMap { + self.map.read().unwrap().clone() + } +} + +#[cfg(not(target_os = "macos"))] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct NativeFontHandle { + pub path: PathBuf, + pub index: u32, +} + +#[cfg(target_os = "macos")] +#[derive(Clone)] +pub struct NativeFontHandle(pub CGFont); + +#[cfg(target_os = "macos")] +impl Serialize for NativeFontHandle { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.0 + .postscript_name() + .to_string() + .serialize(serializer) + } +} + +#[cfg(target_os = "macos")] +impl<'de> Deserialize<'de> for NativeFontHandle { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let postscript_name: String = Deserialize::deserialize(deserializer)?; + + match CGFont::from_name(&CFString::new(&*postscript_name)) { + Ok(font) => Ok(NativeFontHandle(font)), + Err(_) => Err(de::Error::custom( + "Couldn't find a font with that PostScript name!", + )), + } + } +} + +#[repr(C)] +#[derive(Copy, Clone, Deserialize, Serialize, Debug)] +pub struct GlyphDimensions { + pub left: i32, + pub top: i32, + pub width: i32, + pub height: i32, + pub advance: f32, +} + +pub struct GlyphDimensionRequest { + pub key: FontInstanceKey, + pub glyph_indices: Vec, + pub sender: Sender>>, +} + +pub struct GlyphIndexRequest { + pub key: FontKey, + pub text: String, + pub sender: Sender>>, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, Ord, PartialOrd)] +pub struct FontKey(pub IdNamespace, pub u32); + +impl FontKey { + pub fn new(namespace: IdNamespace, key: u32) -> FontKey { + FontKey(namespace, key) + } +} + +/// Container for the raw data describing a font. This might be a stream of +/// bytes corresponding to a downloaded font, or a handle to a native font from +/// the operating system. +/// +/// Note that fonts need to be instantiated before being used, which involves +/// assigning size and various other options. The word 'template' here is +/// intended to distinguish this data from instance-specific data. +#[derive(Clone)] +pub enum FontTemplate { + Raw(Arc>, u32), + Native(NativeFontHandle), +} + +#[repr(u8)] +#[derive(Debug, Copy, Clone, Hash, Eq, MallocSizeOf, PartialEq, Serialize, Deserialize, Ord, PartialOrd, PeekPoke)] +pub enum FontRenderMode { + Mono = 0, + Alpha, + Subpixel, +} + +impl Default for FontRenderMode { + fn default() -> Self { + FontRenderMode::Mono + } +} + +impl FontRenderMode { + // Combine two font render modes such that the lesser amount of AA limits the AA of the result. + pub fn limit_by(self, other: FontRenderMode) -> FontRenderMode { + match (self, other) { + (FontRenderMode::Subpixel, _) | (_, FontRenderMode::Mono) => other, + _ => self, + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialOrd, Deserialize, Serialize)] +pub struct FontVariation { + pub tag: u32, + pub value: f32, +} + +impl Ord for FontVariation { + fn cmp(&self, other: &FontVariation) -> Ordering { + self.tag.cmp(&other.tag) + .then(self.value.to_bits().cmp(&other.value.to_bits())) + } +} + +impl PartialEq for FontVariation { + fn eq(&self, other: &FontVariation) -> bool { + self.tag == other.tag && + self.value.to_bits() == other.value.to_bits() + } +} + +impl Eq for FontVariation {} + +impl Hash for FontVariation { + fn hash(&self, state: &mut H) { + self.tag.hash(state); + self.value.to_bits().hash(state); + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize, PeekPoke)] +pub struct GlyphOptions { + pub render_mode: FontRenderMode, + pub flags: FontInstanceFlags, +} + +impl Default for GlyphOptions { + fn default() -> Self { + GlyphOptions { + render_mode: FontRenderMode::Subpixel, + flags: FontInstanceFlags::empty(), + } + } +} + +bitflags! { + #[repr(C)] + #[derive(Deserialize, MallocSizeOf, Serialize, PeekPoke)] + pub struct FontInstanceFlags: u32 { + // Common flags + const SYNTHETIC_BOLD = 1 << 1; + const EMBEDDED_BITMAPS = 1 << 2; + const SUBPIXEL_BGR = 1 << 3; + const TRANSPOSE = 1 << 4; + const FLIP_X = 1 << 5; + const FLIP_Y = 1 << 6; + const SUBPIXEL_POSITION = 1 << 7; + const VERTICAL = 1 << 8; + + // Internal flags + const TRANSFORM_GLYPHS = 1 << 12; + const TEXTURE_PADDING = 1 << 13; + + // Windows flags + const FORCE_GDI = 1 << 16; + const FORCE_SYMMETRIC = 1 << 17; + const NO_SYMMETRIC = 1 << 18; + + // Mac flags + const FONT_SMOOTHING = 1 << 16; + + // FreeType flags + const FORCE_AUTOHINT = 1 << 16; + const NO_AUTOHINT = 1 << 17; + const VERTICAL_LAYOUT = 1 << 18; + const LCD_VERTICAL = 1 << 19; + } +} + +impl Default for FontInstanceFlags { + #[cfg(target_os = "windows")] + fn default() -> FontInstanceFlags { + FontInstanceFlags::SUBPIXEL_POSITION + } + + #[cfg(target_os = "macos")] + fn default() -> FontInstanceFlags { + FontInstanceFlags::SUBPIXEL_POSITION | + FontInstanceFlags::FONT_SMOOTHING + } + + #[cfg(not(any(target_os = "macos", target_os = "windows")))] + fn default() -> FontInstanceFlags { + FontInstanceFlags::SUBPIXEL_POSITION + } +} + + +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord, Serialize)] +pub struct SyntheticItalics { + // Angle in degrees (-90..90) for synthetic italics in 8.8 fixed-point. + pub angle: i16, +} + +impl SyntheticItalics { + pub const ANGLE_SCALE: f32 = 256.0; + + pub fn from_degrees(degrees: f32) -> Self { + SyntheticItalics { angle: (degrees.max(-89.0).min(89.0) * Self::ANGLE_SCALE) as i16 } + } + + pub fn to_degrees(self) -> f32 { + self.angle as f32 / Self::ANGLE_SCALE + } + + pub fn to_radians(self) -> f32 { + self.to_degrees().to_radians() + } + + pub fn to_skew(self) -> f32 { + self.to_radians().tan() + } + + pub fn enabled() -> Self { + Self::from_degrees(14.0) + } + + pub fn disabled() -> Self { + SyntheticItalics { angle: 0 } + } + + pub fn is_enabled(self) -> bool { + self.angle != 0 + } +} + +impl Default for SyntheticItalics { + fn default() -> Self { + SyntheticItalics::disabled() + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)] +pub struct FontInstanceOptions { + pub render_mode: FontRenderMode, + pub flags: FontInstanceFlags, + /// When bg_color.a is != 0 and render_mode is FontRenderMode::Subpixel, + /// the text will be rendered with bg_color.r/g/b as an opaque estimated + /// background color. + pub bg_color: ColorU, + pub synthetic_italics: SyntheticItalics, +} + +impl Default for FontInstanceOptions { + fn default() -> FontInstanceOptions { + FontInstanceOptions { + render_mode: FontRenderMode::Subpixel, + flags: Default::default(), + bg_color: ColorU::new(0, 0, 0, 0), + synthetic_italics: SyntheticItalics::disabled(), + } + } +} + +#[cfg(target_os = "windows")] +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord, Serialize)] +pub struct FontInstancePlatformOptions { + pub gamma: u16, // percent + pub contrast: u8, // percent + pub cleartype_level: u8, // percent +} + +#[cfg(target_os = "windows")] +impl Default for FontInstancePlatformOptions { + fn default() -> FontInstancePlatformOptions { + FontInstancePlatformOptions { + gamma: 180, // Default DWrite gamma + contrast: 100, + cleartype_level: 100, + } + } +} + +#[cfg(target_os = "macos")] +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord, Serialize)] +pub struct FontInstancePlatformOptions { + pub unused: u32, +} + +#[cfg(target_os = "macos")] +impl Default for FontInstancePlatformOptions { + fn default() -> FontInstancePlatformOptions { + FontInstancePlatformOptions { + unused: 0, + } + } +} + +#[cfg(not(any(target_os = "macos", target_os = "windows")))] +#[repr(u8)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd, Ord, Serialize)] +pub enum FontLCDFilter { + None, + Default, + Light, + Legacy, +} + +#[cfg(not(any(target_os = "macos", target_os = "windows")))] +#[repr(u8)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd, Ord, Serialize)] +pub enum FontHinting { + None, + Mono, + Light, + Normal, + LCD, +} + +#[cfg(not(any(target_os = "macos", target_os = "windows")))] +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord, Serialize)] +pub struct FontInstancePlatformOptions { + pub lcd_filter: FontLCDFilter, + pub hinting: FontHinting, +} + +#[cfg(not(any(target_os = "macos", target_os = "windows")))] +impl Default for FontInstancePlatformOptions { + fn default() -> FontInstancePlatformOptions { + FontInstancePlatformOptions { + lcd_filter: FontLCDFilter::Default, + hinting: FontHinting::LCD, + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Ord, PartialOrd, MallocSizeOf, PeekPoke)] +#[derive(Deserialize, Serialize)] +pub struct FontInstanceKey(pub IdNamespace, pub u32); + +impl FontInstanceKey { + pub fn new(namespace: IdNamespace, key: u32) -> FontInstanceKey { + FontInstanceKey(namespace, key) + } +} + +/// Data corresponding to an instantiation of a font, with size and +/// other options specified. +/// +/// Note that the actual font is stored out-of-band in `FontTemplate`. +#[derive(Clone)] +pub struct FontInstanceData { + pub font_key: FontKey, + pub size: f32, + pub options: Option, + pub platform_options: Option, + pub variations: Vec, +} + +pub type GlyphIndex = u32; + +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub struct GlyphInstance { + pub index: GlyphIndex, + pub point: LayoutPoint, +} + +impl Default for GlyphInstance { + fn default() -> Self { + GlyphInstance { + index: 0, + point: LayoutPoint::zero(), + } + } +} + +impl Eq for GlyphInstance {} + +#[cfg_attr(feature = "cargo-clippy", allow(clippy::derive_hash_xor_eq))] +impl Hash for GlyphInstance { + fn hash(&self, state: &mut H) { + // Note: this is inconsistent with the Eq impl for -0.0 (don't care). + self.index.hash(state); + self.point.x.to_bits().hash(state); + self.point.y.to_bits().hash(state); + } +} diff --git a/third_party/webrender/webrender_api/src/gradient_builder.rs b/third_party/webrender/webrender_api/src/gradient_builder.rs new file mode 100644 index 00000000000..883acbafa38 --- /dev/null +++ b/third_party/webrender/webrender_api/src/gradient_builder.rs @@ -0,0 +1,178 @@ +/* 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 crate::display_item as di; +use crate::units::*; + + +/// Construct a gradient to be used in display lists. +/// +/// Each gradient needs at least two stops. +pub struct GradientBuilder { + stops: Vec, +} + +impl GradientBuilder { + /// Create a new gradient builder. + pub fn new() -> Self { + GradientBuilder { + stops: Vec::new(), + } + } + + /// Create a gradient builder with a list of stops. + pub fn with_stops(stops: Vec) -> GradientBuilder { + GradientBuilder { stops } + } + + /// Push an additional stop for the gradient. + pub fn push(&mut self, stop: di::GradientStop) { + self.stops.push(stop); + } + + /// Get a reference to the list of stops. + pub fn stops(&self) -> &[di::GradientStop] { + self.stops.as_ref() + } + + /// Return the gradient stops vector. + pub fn into_stops(self) -> Vec { + self.stops + } + + /// Produce a linear gradient, normalize the stops. + pub fn gradient( + &mut self, + start_point: LayoutPoint, + end_point: LayoutPoint, + extend_mode: di::ExtendMode, + ) -> di::Gradient { + let (start_offset, end_offset) = self.normalize(extend_mode); + let start_to_end = end_point - start_point; + + di::Gradient { + start_point: start_point + start_to_end * start_offset, + end_point: start_point + start_to_end * end_offset, + extend_mode, + } + } + + /// Produce a radial gradient, normalize the stops. + /// + /// Will replace the gradient with a single color + /// if the radius negative. + pub fn radial_gradient( + &mut self, + center: LayoutPoint, + radius: LayoutSize, + extend_mode: di::ExtendMode, + ) -> di::RadialGradient { + if radius.width <= 0.0 || radius.height <= 0.0 { + // The shader cannot handle a non positive radius. So + // reuse the stops vector and construct an equivalent + // gradient. + let last_color = self.stops.last().unwrap().color; + + self.stops.clear(); + self.stops.push(di::GradientStop { offset: 0.0, color: last_color, }); + self.stops.push(di::GradientStop { offset: 1.0, color: last_color, }); + + return di::RadialGradient { + center, + radius: LayoutSize::new(1.0, 1.0), + start_offset: 0.0, + end_offset: 1.0, + extend_mode, + }; + } + + let (start_offset, end_offset) = + self.normalize(extend_mode); + + di::RadialGradient { + center, + radius, + start_offset, + end_offset, + extend_mode, + } + } + + /// Produce a conic gradient, normalize the stops. + pub fn conic_gradient( + &mut self, + center: LayoutPoint, + angle: f32, + extend_mode: di::ExtendMode, + ) -> di::ConicGradient { + let (start_offset, end_offset) = + self.normalize(extend_mode); + + di::ConicGradient { + center, + angle, + start_offset, + end_offset, + extend_mode, + } + } + + /// Gradients can be defined with stops outside the range of [0, 1] + /// when this happens the gradient needs to be normalized by adjusting + /// the gradient stops and gradient line into an equivalent gradient + /// with stops in the range [0, 1]. this is done by moving the beginning + /// of the gradient line to where stop[0] and the end of the gradient line + /// to stop[n-1]. this function adjusts the stops in place, and returns + /// the amount to adjust the gradient line start and stop. + fn normalize(&mut self, extend_mode: di::ExtendMode) -> (f32, f32) { + let stops = &mut self.stops; + assert!(stops.len() >= 2); + + let first = *stops.first().unwrap(); + let last = *stops.last().unwrap(); + + assert!(first.offset <= last.offset); + + let stops_delta = last.offset - first.offset; + + if stops_delta > 0.000001 { + for stop in stops { + stop.offset = (stop.offset - first.offset) / stops_delta; + } + + (first.offset, last.offset) + } else { + // We have a degenerate gradient and can't accurately transform the stops + // what happens here depends on the repeat behavior, but in any case + // we reconstruct the gradient stops to something simpler and equivalent + stops.clear(); + + match extend_mode { + di::ExtendMode::Clamp => { + // This gradient is two colors split at the offset of the stops, + // so create a gradient with two colors split at 0.5 and adjust + // the gradient line so 0.5 is at the offset of the stops + stops.push(di::GradientStop { color: first.color, offset: 0.0, }); + stops.push(di::GradientStop { color: first.color, offset: 0.5, }); + stops.push(di::GradientStop { color: last.color, offset: 0.5, }); + stops.push(di::GradientStop { color: last.color, offset: 1.0, }); + + let offset = last.offset; + + (offset - 0.5, offset + 0.5) + } + di::ExtendMode::Repeat => { + // A repeating gradient with stops that are all in the same + // position should just display the last color. I believe the + // spec says that it should be the average color of the gradient, + // but this matches what Gecko and Blink does + stops.push(di::GradientStop { color: last.color, offset: 0.0, }); + stops.push(di::GradientStop { color: last.color, offset: 1.0, }); + + (0.0, 1.0) + } + } + } + } +} diff --git a/third_party/webrender/webrender_api/src/image.rs b/third_party/webrender/webrender_api/src/image.rs new file mode 100644 index 00000000000..deaeb92aeb1 --- /dev/null +++ b/third_party/webrender/webrender_api/src/image.rs @@ -0,0 +1,595 @@ +/* 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/. */ + +#![deny(missing_docs)] + +use euclid::{size2, Rect, num::Zero}; +use peek_poke::PeekPoke; +use std::ops::{Add, Sub}; +use std::sync::Arc; +// local imports +use crate::api::{IdNamespace, PipelineId, TileSize}; +use crate::display_item::ImageRendering; +use crate::font::{FontInstanceKey, FontInstanceData, FontKey, FontTemplate}; +use crate::units::*; + +/// The default tile size for blob images and regular images larger than +/// the maximum texture size. +pub const DEFAULT_TILE_SIZE: TileSize = 512; + +/// An opaque identifier describing an image registered with WebRender. +/// This is used as a handle to reference images, and is used as the +/// hash map key for the actual image storage in the `ResourceCache`. +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub struct ImageKey(pub IdNamespace, pub u32); + +impl Default for ImageKey { + fn default() -> Self { + ImageKey::DUMMY + } +} + +impl ImageKey { + /// Placeholder Image key, used to represent None. + pub const DUMMY: Self = ImageKey(IdNamespace(0), 0); + + /// Mints a new ImageKey. The given ID must be unique. + pub fn new(namespace: IdNamespace, key: u32) -> Self { + ImageKey(namespace, key) + } +} + +/// An opaque identifier describing a blob image registered with WebRender. +/// This is used as a handle to reference blob images, and can be used as an +/// image in display items. +#[repr(C)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct BlobImageKey(pub ImageKey); + +impl BlobImageKey { + /// Interpret this blob image as an image for a display item. + pub fn as_image(self) -> ImageKey { + self.0 + } +} + +/// An arbitrary identifier for an external image provided by the +/// application. It must be a unique identifier for each external +/// image. +#[repr(C)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub struct ExternalImageId(pub u64); + +/// The source for an external image. +pub enum ExternalImageSource<'a> { + /// A raw pixel buffer. + RawData(&'a [u8]), + /// A gl::GLuint texture handle. + NativeTexture(u32), + /// An invalid source. + Invalid, +} + +/// The data that an external client should provide about +/// an external image. For instance, if providing video frames, +/// the application could call wr.render() whenever a new +/// video frame is ready. Note that the UV coords are supplied +/// in texel-space! +pub struct ExternalImage<'a> { + /// UV coordinates for the image. + pub uv: TexelRect, + /// The source for this image's contents. + pub source: ExternalImageSource<'a>, +} + +/// The interfaces that an application can implement to support providing +/// external image buffers. +/// When the application passes an external image to WR, it should keep that +/// external image life time. People could check the epoch id in RenderNotifier +/// at the client side to make sure that the external image is not used by WR. +/// Then, do the clean up for that external image. +pub trait ExternalImageHandler { + /// Lock the external image. Then, WR could start to read the image content. + /// The WR client should not change the image content until the unlock() + /// call. Provide ImageRendering for NativeTexture external images. + fn lock(&mut self, key: ExternalImageId, channel_index: u8, rendering: ImageRendering) -> ExternalImage; + /// Unlock the external image. WR should not read the image content + /// after this call. + fn unlock(&mut self, key: ExternalImageId, channel_index: u8); +} + +/// Allows callers to receive a texture with the contents of a specific +/// pipeline copied to it. +pub trait OutputImageHandler { + /// Return the native texture handle and the size of the texture. + fn lock(&mut self, pipeline_id: PipelineId) -> Option<(u32, FramebufferIntSize)>; + /// Unlock will only be called if the lock() call succeeds, when WR has issued + /// the GL commands to copy the output to the texture handle. + fn unlock(&mut self, pipeline_id: PipelineId); +} + +/// Specifies the type of texture target in driver terms. +#[repr(u8)] +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub enum TextureTarget { + /// Standard texture. This maps to GL_TEXTURE_2D in OpenGL. + Default = 0, + /// Array texture. This maps to GL_TEXTURE_2D_ARRAY in OpenGL. See + /// https://www.khronos.org/opengl/wiki/Array_Texture for background + /// on Array textures. + Array = 1, + /// Rectangle texture. This maps to GL_TEXTURE_RECTANGLE in OpenGL. This + /// is similar to a standard texture, with a few subtle differences + /// (no mipmaps, non-power-of-two dimensions, different coordinate space) + /// that make it useful for representing the kinds of textures we use + /// in WebRender. See https://www.khronos.org/opengl/wiki/Rectangle_Texture + /// for background on Rectangle textures. + Rect = 2, + /// External texture. This maps to GL_TEXTURE_EXTERNAL_OES in OpenGL, which + /// is an extension. This is used for image formats that OpenGL doesn't + /// understand, particularly YUV. See + /// https://www.khronos.org/registry/OpenGL/extensions/OES/OES_EGL_image_external.txt + External = 3, +} + +/// Storage format identifier for externally-managed images. +#[repr(u8)] +#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub enum ExternalImageType { + /// The image is texture-backed. + TextureHandle(TextureTarget), + /// The image is heap-allocated by the embedding. + Buffer, +} + +/// Descriptor for external image resources. See `ImageData`. +#[repr(C)] +#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct ExternalImageData { + /// The identifier of this external image, provided by the embedding. + pub id: ExternalImageId, + /// For multi-plane images (i.e. YUV), indicates the plane of the + /// original image that this struct represents. 0 for single-plane images. + pub channel_index: u8, + /// Storage format identifier. + pub image_type: ExternalImageType, +} + +/// Specifies the format of a series of pixels, in driver terms. +#[repr(u8)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub enum ImageFormat { + /// One-channel, byte storage. The "red" doesn't map to the color + /// red per se, and is just the way that OpenGL has historically referred + /// to single-channel buffers. + R8 = 1, + /// One-channel, short storage + R16 = 2, + /// Four channels, byte storage. + BGRA8 = 3, + /// Four channels, float storage. + RGBAF32 = 4, + /// Two-channels, byte storage. Similar to `R8`, this just means + /// "two channels" rather than "red and green". + RG8 = 5, + /// Two-channels, byte storage. Similar to `R16`, this just means + /// "two channels" rather than "red and green". + RG16 = 6, + + /// Four channels, signed integer storage. + RGBAI32 = 7, + /// Four channels, byte storage. + RGBA8 = 8, +} + +impl ImageFormat { + /// Returns the number of bytes per pixel for the given format. + pub fn bytes_per_pixel(self) -> i32 { + match self { + ImageFormat::R8 => 1, + ImageFormat::R16 => 2, + ImageFormat::BGRA8 => 4, + ImageFormat::RGBAF32 => 16, + ImageFormat::RG8 => 2, + ImageFormat::RG16 => 4, + ImageFormat::RGBAI32 => 16, + ImageFormat::RGBA8 => 4, + } + } +} + +/// Specifies the color depth of an image. Currently only used for YUV images. +#[repr(u8)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, PeekPoke)] +pub enum ColorDepth { + /// 8 bits image (most common) + Color8, + /// 10 bits image + Color10, + /// 12 bits image + Color12, + /// 16 bits image + Color16, +} + +impl Default for ColorDepth { + fn default() -> Self { + ColorDepth::Color8 + } +} + +impl ColorDepth { + /// Return the numerical bit depth value for the type. + pub fn bit_depth(self) -> u32 { + match self { + ColorDepth::Color8 => 8, + ColorDepth::Color10 => 10, + ColorDepth::Color12 => 12, + ColorDepth::Color16 => 16, + } + } + /// 10 and 12 bits images are encoded using 16 bits integer, we need to + /// rescale the 10 or 12 bits value to extend to 16 bits. + pub fn rescaling_factor(self) -> f32 { + match self { + ColorDepth::Color8 => 1.0, + ColorDepth::Color10 => 64.0, + ColorDepth::Color12 => 16.0, + ColorDepth::Color16 => 1.0, + } + } +} + +bitflags! { + /// Various flags that are part of an image descriptor. + #[derive(Deserialize, Serialize)] + pub struct ImageDescriptorFlags: u32 { + /// Whether this image is opaque, or has an alpha channel. Avoiding blending + /// for opaque surfaces is an important optimization. + const IS_OPAQUE = 1; + /// Whether to allow the driver to automatically generate mipmaps. If images + /// are already downscaled appropriately, mipmap generation can be wasted + /// work, and cause performance problems on some cards/drivers. + /// + /// See https://github.com/servo/webrender/pull/2555/ + const ALLOW_MIPMAPS = 2; + } +} + +/// Metadata (but not storage) describing an image In WebRender. +#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct ImageDescriptor { + /// Format of the image data. + pub format: ImageFormat, + /// Width and length of the image data, in pixels. + pub size: DeviceIntSize, + /// The number of bytes from the start of one row to the next. If non-None, + /// `compute_stride` will return this value, otherwise it returns + /// `width * bpp`. Different source of images have different alignment + /// constraints for rows, so the stride isn't always equal to width * bpp. + pub stride: Option, + /// Offset in bytes of the first pixel of this image in its backing buffer. + /// This is used for tiling, wherein WebRender extracts chunks of input images + /// in order to cache, manipulate, and render them individually. This offset + /// tells the texture upload machinery where to find the bytes to upload for + /// this tile. Non-tiled images generally set this to zero. + pub offset: i32, + /// Various bool flags related to this descriptor. + pub flags: ImageDescriptorFlags, +} + +impl ImageDescriptor { + /// Mints a new ImageDescriptor. + pub fn new( + width: i32, + height: i32, + format: ImageFormat, + flags: ImageDescriptorFlags, + ) -> Self { + ImageDescriptor { + size: size2(width, height), + format, + stride: None, + offset: 0, + flags, + } + } + + /// Returns the stride, either via an explicit stride stashed on the object + /// or by the default computation. + pub fn compute_stride(&self) -> i32 { + self.stride.unwrap_or(self.size.width * self.format.bytes_per_pixel()) + } + + /// Computes the total size of the image, in bytes. + pub fn compute_total_size(&self) -> i32 { + self.compute_stride() * self.size.height + } + + /// Computes the bounding rectangle for the image, rooted at (0, 0). + pub fn full_rect(&self) -> DeviceIntRect { + DeviceIntRect::new( + DeviceIntPoint::zero(), + self.size, + ) + } + + /// Returns true if this descriptor is opaque + pub fn is_opaque(&self) -> bool { + self.flags.contains(ImageDescriptorFlags::IS_OPAQUE) + } + + /// Returns true if this descriptor allows mipmaps + pub fn allow_mipmaps(&self) -> bool { + self.flags.contains(ImageDescriptorFlags::ALLOW_MIPMAPS) + } +} + +/// Represents the backing store of an arbitrary series of pixels for display by +/// WebRender. This storage can take several forms. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum ImageData { + /// A simple series of bytes, provided by the embedding and owned by WebRender. + /// The format is stored out-of-band, currently in ImageDescriptor. + Raw(#[serde(with = "serde_image_data_raw")] Arc>), + /// An image owned by the embedding, and referenced by WebRender. This may + /// take the form of a texture or a heap-allocated buffer. + External(ExternalImageData), +} + +mod serde_image_data_raw { + extern crate serde_bytes; + + use std::sync::Arc; + use serde::{Deserializer, Serializer}; + + pub fn serialize(bytes: &Arc>, serializer: S) -> Result { + serde_bytes::serialize(bytes.as_slice(), serializer) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result>, D::Error> { + serde_bytes::deserialize(deserializer).map(Arc::new) + } +} + +impl ImageData { + /// Mints a new raw ImageData, taking ownership of the bytes. + pub fn new(bytes: Vec) -> Self { + ImageData::Raw(Arc::new(bytes)) + } + + /// Mints a new raw ImageData from Arc-ed bytes. + pub fn new_shared(bytes: Arc>) -> Self { + ImageData::Raw(bytes) + } +} + +/// The resources exposed by the resource cache available for use by the blob rasterizer. +pub trait BlobImageResources { + /// Returns the `FontTemplate` for the given key. + fn get_font_data(&self, key: FontKey) -> &FontTemplate; + /// Returns the `FontInstanceData` for the given key, if found. + fn get_font_instance_data(&self, key: FontInstanceKey) -> Option; +} + +/// A handler on the render backend that can create rasterizer objects which will +/// be sent to the scene builder thread to execute the rasterization. +/// +/// The handler is responsible for collecting resources, managing/updating blob commands +/// and creating the rasterizer objects, but isn't expected to do any rasterization itself. +pub trait BlobImageHandler: Send { + /// Creates a snapshot of the current state of blob images in the handler. + fn create_blob_rasterizer(&mut self) -> Box; + + /// Creates an empty blob handler of the same type. + /// + /// This is used to allow creating new API endpoints with blob handlers installed on them. + fn create_similar(&self) -> Box; + + /// A hook to let the blob image handler update any state related to resources that + /// are not bundled in the blob recording itself. + fn prepare_resources( + &mut self, + services: &dyn BlobImageResources, + requests: &[BlobImageParams], + ); + + /// Register a blob image. + fn add(&mut self, key: BlobImageKey, data: Arc, visible_rect: &DeviceIntRect, + tile_size: TileSize); + + /// Update an already registered blob image. + fn update(&mut self, key: BlobImageKey, data: Arc, visible_rect: &DeviceIntRect, + dirty_rect: &BlobDirtyRect); + + /// Delete an already registered blob image. + fn delete(&mut self, key: BlobImageKey); + + /// A hook to let the handler clean up any state related to a font which the resource + /// cache is about to delete. + fn delete_font(&mut self, key: FontKey); + + /// A hook to let the handler clean up any state related to a font instance which the + /// resource cache is about to delete. + fn delete_font_instance(&mut self, key: FontInstanceKey); + + /// A hook to let the handler clean up any state related a given namespace before the + /// resource cache deletes them. + fn clear_namespace(&mut self, namespace: IdNamespace); + + /// Whether to allow rendering blobs on multiple threads. + fn enable_multithreading(&mut self, enable: bool); +} + +/// A group of rasterization requests to execute synchronously on the scene builder thread. +pub trait AsyncBlobImageRasterizer : Send { + /// Rasterize the requests. + /// + /// Gecko uses te priority hint to schedule work in a way that minimizes the risk + /// of high priority work being blocked by (or enqued behind) low priority work. + fn rasterize( + &mut self, + requests: &[BlobImageParams], + low_priority: bool + ) -> Vec<(BlobImageRequest, BlobImageResult)>; +} + + +/// Input parameters for the BlobImageRasterizer. +#[derive(Copy, Clone, Debug)] +pub struct BlobImageParams { + /// A key that identifies the blob image rasterization request. + pub request: BlobImageRequest, + /// Description of the format of the blob's output image. + pub descriptor: BlobImageDescriptor, + /// An optional sub-rectangle of the image to avoid re-rasterizing + /// the entire image when only a portion is updated. + /// + /// If set to None the entire image is rasterized. + pub dirty_rect: BlobDirtyRect, +} + +/// The possible states of a Dirty rect. +/// +/// This exists because people kept getting confused with `Option`. +#[derive(Debug, Serialize, Deserialize)] +pub enum DirtyRect { + /// Everything is Dirty, equivalent to Partial(image_bounds) + All, + /// Some specific amount is dirty + Partial(Rect) +} + +impl DirtyRect +where + T: Copy + Clone + + PartialOrd + PartialEq + + Add + + Sub + + Zero +{ + /// Creates an empty DirtyRect (indicating nothing is invalid) + pub fn empty() -> Self { + DirtyRect::Partial(Rect::zero()) + } + + /// Returns whether the dirty rect is empty + pub fn is_empty(&self) -> bool { + match self { + DirtyRect::All => false, + DirtyRect::Partial(rect) => rect.is_empty(), + } + } + + /// Replaces self with the empty rect and returns the old value. + pub fn replace_with_empty(&mut self) -> Self { + ::std::mem::replace(self, DirtyRect::empty()) + } + + /// Maps over the contents of Partial. + pub fn map(self, func: F) -> Self + where F: FnOnce(Rect) -> Rect, + { + use crate::DirtyRect::*; + + match self { + All => All, + Partial(rect) => Partial(func(rect)), + } + } + + /// Unions the dirty rects. + pub fn union(&self, other: &Self) -> Self { + use crate::DirtyRect::*; + + match (*self, *other) { + (All, _) | (_, All) => All, + (Partial(rect1), Partial(rect2)) => Partial(rect1.union(&rect2)), + } + } + + /// Intersects the dirty rects. + pub fn intersection(&self, other: &Self) -> Self { + use crate::DirtyRect::*; + + match (*self, *other) { + (All, rect) | (rect, All) => rect, + (Partial(rect1), Partial(rect2)) => { + Partial(rect1.intersection(&rect2).unwrap_or_else(Rect::zero)) + } + } + } + + /// Converts the dirty rect into a subrect of the given one via intersection. + pub fn to_subrect_of(&self, rect: &Rect) -> Rect { + use crate::DirtyRect::*; + + match *self { + All => *rect, + Partial(dirty_rect) => { + dirty_rect.intersection(rect).unwrap_or_else(Rect::zero) + } + } + } +} + +impl Copy for DirtyRect {} +impl Clone for DirtyRect { + fn clone(&self) -> Self { *self } +} + +impl From> for DirtyRect { + fn from(rect: Rect) -> Self { + DirtyRect::Partial(rect) + } +} + +/// Backing store for blob image command streams. +pub type BlobImageData = Vec; + +/// Result type for blob raserization. +pub type BlobImageResult = Result; + +/// Metadata (but not storage) for a blob image. +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct BlobImageDescriptor { + /// Surface of the image or tile to render in the same coordinate space as + /// the drawing commands. + pub rect: LayoutIntRect, + /// Format for the data in the backing store. + pub format: ImageFormat, +} + +/// Representation of a rasterized blob image. This is obtained by passing +/// `BlobImageData` to the embedding via the rasterization callback. +pub struct RasterizedBlobImage { + /// The rectangle that was rasterized in device pixels, relative to the + /// image or tile. + pub rasterized_rect: DeviceIntRect, + /// Backing store. The format is stored out of band in `BlobImageDescriptor`. + pub data: Arc>, +} + +/// Error code for when blob rasterization failed. +#[derive(Clone, Debug)] +pub enum BlobImageError { + /// Out of memory. + Oom, + /// Other failure, embedding-specified. + Other(String), +} + + + +/// A key identifying blob image rasterization work requested from the blob +/// image rasterizer. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct BlobImageRequest { + /// Unique handle to the image. + pub key: BlobImageKey, + /// Tiling offset in number of tiles. + pub tile: TileOffset, +} diff --git a/third_party/webrender/webrender_api/src/image_tiling.rs b/third_party/webrender/webrender_api/src/image_tiling.rs new file mode 100644 index 00000000000..8fdc82ef24d --- /dev/null +++ b/third_party/webrender/webrender_api/src/image_tiling.rs @@ -0,0 +1,815 @@ +/* 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 crate::{TileSize, EdgeAaSegmentMask}; +use crate::units::*; +use euclid::{point2, size2}; +use std::i32; +use std::ops::Range; + +/// If repetitions are far enough apart that only one is within +/// the primitive rect, then we can simplify the parameters and +/// treat the primitive as not repeated. +/// This can let us avoid unnecessary work later to handle some +/// of the parameters. +pub fn simplify_repeated_primitive( + stretch_size: &LayoutSize, + tile_spacing: &mut LayoutSize, + prim_rect: &mut LayoutRect, +) { + let stride = *stretch_size + *tile_spacing; + + if stride.width >= prim_rect.size.width { + tile_spacing.width = 0.0; + prim_rect.size.width = f32::min(prim_rect.size.width, stretch_size.width); + } + if stride.height >= prim_rect.size.height { + tile_spacing.height = 0.0; + prim_rect.size.height = f32::min(prim_rect.size.height, stretch_size.height); + } +} + +pub struct Repetition { + pub origin: LayoutPoint, + pub edge_flags: EdgeAaSegmentMask, +} + +pub struct RepetitionIterator { + current_x: i32, + x_count: i32, + current_y: i32, + y_count: i32, + row_flags: EdgeAaSegmentMask, + current_origin: LayoutPoint, + initial_origin: LayoutPoint, + stride: LayoutSize, +} + +impl Iterator for RepetitionIterator { + type Item = Repetition; + + fn next(&mut self) -> Option { + if self.current_x == self.x_count { + self.current_y += 1; + if self.current_y >= self.y_count { + return None; + } + self.current_x = 0; + + self.row_flags = EdgeAaSegmentMask::empty(); + if self.current_y == self.y_count - 1 { + self.row_flags |= EdgeAaSegmentMask::BOTTOM; + } + + self.current_origin.x = self.initial_origin.x; + self.current_origin.y += self.stride.height; + } + + let mut edge_flags = self.row_flags; + if self.current_x == 0 { + edge_flags |= EdgeAaSegmentMask::LEFT; + } + + if self.current_x == self.x_count - 1 { + edge_flags |= EdgeAaSegmentMask::RIGHT; + } + + let repetition = Repetition { + origin: self.current_origin, + edge_flags, + }; + + self.current_origin.x += self.stride.width; + self.current_x += 1; + + Some(repetition) + } +} + +pub fn repetitions( + prim_rect: &LayoutRect, + visible_rect: &LayoutRect, + stride: LayoutSize, +) -> RepetitionIterator { + assert!(stride.width > 0.0); + assert!(stride.height > 0.0); + + let visible_rect = match prim_rect.intersection(&visible_rect) { + Some(rect) => rect, + None => { + return RepetitionIterator { + current_origin: LayoutPoint::zero(), + initial_origin: LayoutPoint::zero(), + current_x: 0, + current_y: 0, + x_count: 0, + y_count: 0, + stride, + row_flags: EdgeAaSegmentMask::empty(), + } + } + }; + + let nx = if visible_rect.origin.x > prim_rect.origin.x { + f32::floor((visible_rect.origin.x - prim_rect.origin.x) / stride.width) + } else { + 0.0 + }; + + let ny = if visible_rect.origin.y > prim_rect.origin.y { + f32::floor((visible_rect.origin.y - prim_rect.origin.y) / stride.height) + } else { + 0.0 + }; + + let x0 = prim_rect.origin.x + nx * stride.width; + let y0 = prim_rect.origin.y + ny * stride.height; + + let x_most = visible_rect.max_x(); + let y_most = visible_rect.max_y(); + + let x_count = f32::ceil((x_most - x0) / stride.width) as i32; + let y_count = f32::ceil((y_most - y0) / stride.height) as i32; + + let mut row_flags = EdgeAaSegmentMask::TOP; + if y_count == 1 { + row_flags |= EdgeAaSegmentMask::BOTTOM; + } + + RepetitionIterator { + current_origin: LayoutPoint::new(x0, y0), + initial_origin: LayoutPoint::new(x0, y0), + current_x: 0, + current_y: 0, + x_count, + y_count, + row_flags, + stride, + } +} + +#[derive(Debug)] +pub struct Tile { + pub rect: LayoutRect, + pub offset: TileOffset, + pub edge_flags: EdgeAaSegmentMask, +} + +#[derive(Debug)] +pub struct TileIteratorExtent { + /// Range of visible tiles to iterate over in number of tiles. + tile_range: Range, + /// Range of tiles of the full image including tiles that are culled out. + image_tiles: Range, + /// Size of the first tile in layout space. + first_tile_layout_size: f32, + /// Size of the last tile in layout space. + last_tile_layout_size: f32, + /// Position of blob point (0, 0) in layout space. + layout_tiling_origin: f32, + /// Position of the top-left corner of the primitive rect in layout space. + layout_prim_start: f32, +} + +#[derive(Debug)] +pub struct TileIterator { + current_tile: TileOffset, + x: TileIteratorExtent, + y: TileIteratorExtent, + regular_tile_size: LayoutSize, +} + +impl Iterator for TileIterator { + type Item = Tile; + + fn next(&mut self) -> Option { + // If we reach the end of a row, reset to the beginning of the next row. + if self.current_tile.x >= self.x.tile_range.end { + self.current_tile.y += 1; + self.current_tile.x = self.x.tile_range.start; + } + + // Stop iterating if we reach the last tile. We may start here if there + // were no tiles to iterate over. + if self.current_tile.x >= self.x.tile_range.end || self.current_tile.y >= self.y.tile_range.end { + return None; + } + + let tile_offset = self.current_tile; + + let mut segment_rect = LayoutRect { + origin: LayoutPoint::new( + self.x.layout_tiling_origin + tile_offset.x as f32 * self.regular_tile_size.width, + self.y.layout_tiling_origin + tile_offset.y as f32 * self.regular_tile_size.height, + ), + size: self.regular_tile_size, + }; + + let mut edge_flags = EdgeAaSegmentMask::empty(); + + if tile_offset.x == self.x.image_tiles.start { + edge_flags |= EdgeAaSegmentMask::LEFT; + segment_rect.size.width = self.x.first_tile_layout_size; + segment_rect.origin.x = self.x.layout_prim_start; + } + if tile_offset.x == self.x.image_tiles.end - 1 { + edge_flags |= EdgeAaSegmentMask::RIGHT; + segment_rect.size.width = self.x.last_tile_layout_size; + } + + if tile_offset.y == self.y.image_tiles.start { + segment_rect.size.height = self.y.first_tile_layout_size; + segment_rect.origin.y = self.y.layout_prim_start; + edge_flags |= EdgeAaSegmentMask::TOP; + } + if tile_offset.y == self.y.image_tiles.end - 1 { + segment_rect.size.height = self.y.last_tile_layout_size; + edge_flags |= EdgeAaSegmentMask::BOTTOM; + } + + assert!(tile_offset.y < self.y.tile_range.end); + let tile = Tile { + rect: segment_rect, + offset: tile_offset, + edge_flags, + }; + + self.current_tile.x += 1; + + Some(tile) + } +} + +pub fn tiles( + prim_rect: &LayoutRect, + visible_rect: &LayoutRect, + image_rect: &DeviceIntRect, + device_tile_size: i32, +) -> TileIterator { + // The image resource is tiled. We have to generate an image primitive + // for each tile. + // We need to do this because the image is broken up into smaller tiles in the texture + // cache and the image shader is not able to work with this type of sparse representation. + + // The tiling logic works as follows: + // + // +-#################-+ -+ + // | #//| | |//# | | image size + // | #//| | |//# | | + // +-#--+----+----+--#-+ | -+ + // | #//| | |//# | | | regular tile size + // | #//| | |//# | | | + // +-#--+----+----+--#-+ | -+-+ + // | #//|////|////|//# | | | "leftover" height + // | ################# | -+ ---+ + // +----+----+----+----+ + // + // In the ascii diagram above, a large image is split into tiles of almost regular size. + // The tiles on the edges (hatched in the diagram) can be smaller than the regular tiles + // and are handled separately in the code (we'll call them boundary tiles). + // + // Each generated segment corresponds to a tile in the texture cache, with the + // assumption that the boundary tiles are sized to fit their own irregular size in the + // texture cache. + // + // Because we can have very large virtual images we iterate over the visible portion of + // the image in layer space instead of iterating over all device tiles. + + let visible_rect = match prim_rect.intersection(&visible_rect) { + Some(rect) => rect, + None => { + return TileIterator { + current_tile: TileOffset::zero(), + x: TileIteratorExtent { + tile_range: 0..0, + image_tiles: 0..0, + first_tile_layout_size: 0.0, + last_tile_layout_size: 0.0, + layout_tiling_origin: 0.0, + layout_prim_start: prim_rect.origin.x, + }, + y: TileIteratorExtent { + tile_range: 0..0, + image_tiles: 0..0, + first_tile_layout_size: 0.0, + last_tile_layout_size: 0.0, + layout_tiling_origin: 0.0, + layout_prim_start: prim_rect.origin.y, + }, + regular_tile_size: LayoutSize::zero(), + } + } + }; + + // Size of regular tiles in layout space. + let layout_tile_size = LayoutSize::new( + device_tile_size as f32 / image_rect.size.width as f32 * prim_rect.size.width, + device_tile_size as f32 / image_rect.size.height as f32 * prim_rect.size.height, + ); + + // The decomposition logic is exactly the same on each axis so we reduce + // this to a 1-dimensional problem in an attempt to make the code simpler. + + let x_extent = tiles_1d( + layout_tile_size.width, + visible_rect.x_range(), + prim_rect.min_x(), + image_rect.x_range(), + device_tile_size, + ); + + let y_extent = tiles_1d( + layout_tile_size.height, + visible_rect.y_range(), + prim_rect.min_y(), + image_rect.y_range(), + device_tile_size, + ); + + TileIterator { + current_tile: point2( + x_extent.tile_range.start, + y_extent.tile_range.start, + ), + x: x_extent, + y: y_extent, + regular_tile_size: layout_tile_size, + } +} + +/// Decompose tiles along an arbitrary axis. +/// +/// This does most of the heavy lifting needed for `tiles` but in a single dimension for +/// the sake of simplicity since the problem is independent on the x and y axes. +fn tiles_1d( + layout_tile_size: f32, + layout_visible_range: Range, + layout_prim_start: f32, + device_image_range: Range, + device_tile_size: i32, +) -> TileIteratorExtent { + // A few sanity checks. + debug_assert!(layout_tile_size > 0.0); + debug_assert!(layout_visible_range.end >= layout_visible_range.start); + debug_assert!(device_image_range.end > device_image_range.start); + debug_assert!(device_tile_size > 0); + + // Sizes of the boundary tiles in pixels. + let first_tile_device_size = first_tile_size_1d(&device_image_range, device_tile_size); + let last_tile_device_size = last_tile_size_1d(&device_image_range, device_tile_size); + + // [start..end[ Range of tiles of this row/column (in number of tiles) without + // taking culling into account. + let image_tiles = tile_range_1d(&device_image_range, device_tile_size); + + // Layout offset of tile (0, 0) with respect to the top-left corner of the display item. + let layout_offset = device_image_range.start as f32 * layout_tile_size / device_tile_size as f32; + // Position in layout space of tile (0, 0). + let layout_tiling_origin = layout_prim_start - layout_offset; + + // [start..end[ Range of the visible tiles (because of culling). + let visible_tiles_start = f32::floor((layout_visible_range.start - layout_tiling_origin) / layout_tile_size) as i32; + let visible_tiles_end = f32::ceil((layout_visible_range.end - layout_tiling_origin) / layout_tile_size) as i32; + + // Combine the above two to get the tiles in the image that are visible this frame. + let mut tiles_start = i32::max(image_tiles.start, visible_tiles_start); + let tiles_end = i32::min(image_tiles.end, visible_tiles_end); + if tiles_start > tiles_end { + tiles_start = tiles_end; + } + + // The size in layout space of the boundary tiles. + let first_tile_layout_size = if tiles_start == image_tiles.start { + first_tile_device_size as f32 * layout_tile_size / device_tile_size as f32 + } else { + // boundary tile was culled out, so the new first tile is a regularly sized tile. + layout_tile_size + }; + + // Same here. + let last_tile_layout_size = if tiles_end == image_tiles.end { + last_tile_device_size as f32 * layout_tile_size / device_tile_size as f32 + } else { + layout_tile_size + }; + + TileIteratorExtent { + tile_range: tiles_start..tiles_end, + image_tiles, + first_tile_layout_size, + last_tile_layout_size, + layout_tiling_origin, + layout_prim_start, + } +} + +/// Compute the range of tiles (in number of tiles) that intersect the provided +/// image range (in pixels) in an arbitrary dimension. +/// +/// ```ignore +/// +/// 0 +/// : +/// #-+---+---+---+---+---+--# +/// # | | | | | | # +/// #-+---+---+---+---+---+--# +/// ^ : ^ +/// +/// +------------------------+ image_range +/// +---+ regular_tile_size +/// +/// ``` +fn tile_range_1d( + image_range: &Range, + regular_tile_size: i32, +) -> Range { + // Integer division truncates towards zero so with negative values if the first/last + // tile isn't a full tile we can get offset by one which we account for here. + + let mut start = image_range.start / regular_tile_size; + if image_range.start % regular_tile_size < 0 { + start -= 1; + } + + let mut end = image_range.end / regular_tile_size; + if image_range.end % regular_tile_size > 0 { + end += 1; + } + + start..end +} + +// Sizes of the first boundary tile in pixels. +// +// It can be smaller than the regular tile size if the image is not a multiple +// of the regular tile size. +fn first_tile_size_1d( + image_range: &Range, + regular_tile_size: i32, +) -> i32 { + // We have to account for how the % operation behaves for negative values. + let image_size = image_range.end - image_range.start; + i32::min( + match image_range.start % regular_tile_size { + // . #------+------+ . + // . #//////| | . + 0 => regular_tile_size, + // (zero) -> 0 . #--+------+ . + // . . #//| | . + // %(m): ~~> + m if m > 0 => regular_tile_size - m, + // . . #--+------+ 0 <- (zero) + // . . #//| | . + // %(m): <~~ + m => -m, + }, + image_size + ) +} + +// Sizes of the last boundary tile in pixels. +// +// It can be smaller than the regular tile size if the image is not a multiple +// of the regular tile size. +fn last_tile_size_1d( + image_range: &Range, + regular_tile_size: i32, +) -> i32 { + // We have to account for how the modulo operation behaves for negative values. + let image_size = image_range.end - image_range.start; + i32::min( + match image_range.end % regular_tile_size { + // +------+------# . + // tiles: . | |//////# . + 0 => regular_tile_size, + // . +------+--# . 0 <- (zero) + // . | |//# . . + // modulo (m): <~~ + m if m < 0 => regular_tile_size + m, + // (zero) -> 0 +------+--# . . + // . | |//# . . + // modulo (m): ~~> + m => m, + }, + image_size, + ) +} + +pub fn compute_tile_rect( + image_rect: &DeviceIntRect, + regular_tile_size: TileSize, + tile: TileOffset, +) -> DeviceIntRect { + let regular_tile_size = regular_tile_size as i32; + DeviceIntRect { + origin: point2( + compute_tile_origin_1d(image_rect.x_range(), regular_tile_size, tile.x as i32), + compute_tile_origin_1d(image_rect.y_range(), regular_tile_size, tile.y as i32), + ), + size: size2( + compute_tile_size_1d(image_rect.x_range(), regular_tile_size, tile.x as i32), + compute_tile_size_1d(image_rect.y_range(), regular_tile_size, tile.y as i32), + ), + } +} + +fn compute_tile_origin_1d( + img_range: Range, + regular_tile_size: i32, + tile_offset: i32, +) -> i32 { + let tile_range = tile_range_1d(&img_range, regular_tile_size); + if tile_offset == tile_range.start { + img_range.start + } else { + tile_offset * regular_tile_size + } +} + +// Compute the width and height in pixels of a tile depending on its position in the image. +pub fn compute_tile_size( + image_rect: &DeviceIntRect, + regular_tile_size: TileSize, + tile: TileOffset, +) -> DeviceIntSize { + let regular_tile_size = regular_tile_size as i32; + size2( + compute_tile_size_1d(image_rect.x_range(), regular_tile_size, tile.x as i32), + compute_tile_size_1d(image_rect.y_range(), regular_tile_size, tile.y as i32), + ) +} + +fn compute_tile_size_1d( + img_range: Range, + regular_tile_size: i32, + tile_offset: i32, +) -> i32 { + let tile_range = tile_range_1d(&img_range, regular_tile_size); + + // Most tiles are going to have base_size as width and height, + // except for tiles around the edges that are shrunk to fit the image data. + let actual_size = if tile_offset == tile_range.start { + first_tile_size_1d(&img_range, regular_tile_size) + } else if tile_offset == tile_range.end - 1 { + last_tile_size_1d(&img_range, regular_tile_size) + } else { + regular_tile_size + }; + + assert!(actual_size > 0); + + actual_size +} + +pub fn compute_tile_range( + visible_area: &DeviceIntRect, + tile_size: u16, +) -> TileRange { + let tile_size = tile_size as i32; + let x_range = tile_range_1d(&visible_area.x_range(), tile_size); + let y_range = tile_range_1d(&visible_area.y_range(), tile_size); + + TileRange { + origin: point2(x_range.start, y_range.start), + size: size2(x_range.end - x_range.start, y_range.end - y_range.start), + } +} + +pub fn for_each_tile_in_range( + range: &TileRange, + mut callback: impl FnMut(TileOffset), +) { + for y in range.y_range() { + for x in range.x_range() { + callback(point2(x, y)); + } + } +} + +pub fn compute_valid_tiles_if_bounds_change( + prev_rect: &DeviceIntRect, + new_rect: &DeviceIntRect, + tile_size: u16, +) -> Option { + let intersection = match prev_rect.intersection(new_rect) { + Some(rect) => rect, + None => { + return Some(TileRange::zero()); + } + }; + + let left = prev_rect.min_x() != new_rect.min_x(); + let right = prev_rect.max_x() != new_rect.max_x(); + let top = prev_rect.min_y() != new_rect.min_y(); + let bottom = prev_rect.max_y() != new_rect.max_y(); + + if !left && !right && !top && !bottom { + // Bounds have not changed. + return None; + } + + let tw = 1.0 / (tile_size as f32); + let th = 1.0 / (tile_size as f32); + + let tiles = intersection + .cast::() + .scale(tw, th); + + let min_x = if left { f32::ceil(tiles.min_x()) } else { f32::floor(tiles.min_x()) }; + let min_y = if top { f32::ceil(tiles.min_y()) } else { f32::floor(tiles.min_y()) }; + let max_x = if right { f32::floor(tiles.max_x()) } else { f32::ceil(tiles.max_x()) }; + let max_y = if bottom { f32::floor(tiles.max_y()) } else { f32::ceil(tiles.max_y()) }; + + Some(TileRange { + origin: point2(min_x as i32, min_y as i32), + size: size2((max_x - min_x) as i32, (max_y - min_y) as i32), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashSet; + use euclid::rect; + + // this checks some additional invariants + fn checked_for_each_tile( + prim_rect: &LayoutRect, + visible_rect: &LayoutRect, + device_image_rect: &DeviceIntRect, + device_tile_size: i32, + callback: &mut dyn FnMut(&LayoutRect, TileOffset, EdgeAaSegmentMask), + ) { + let mut coverage = LayoutRect::zero(); + let mut seen_tiles = HashSet::new(); + for tile in tiles( + prim_rect, + visible_rect, + device_image_rect, + device_tile_size, + ) { + // make sure we don't get sent duplicate tiles + assert!(!seen_tiles.contains(&tile.offset)); + seen_tiles.insert(tile.offset); + coverage = coverage.union(&tile.rect); + assert!(prim_rect.contains_rect(&tile.rect)); + callback(&tile.rect, tile.offset, tile.edge_flags); + } + assert!(prim_rect.contains_rect(&coverage)); + assert!(coverage.contains_rect(&visible_rect.intersection(&prim_rect).unwrap_or(LayoutRect::zero()))); + } + + #[test] + fn basic() { + let mut count = 0; + checked_for_each_tile(&rect(0., 0., 1000., 1000.), + &rect(75., 75., 400., 400.), + &rect(0, 0, 400, 400), + 36, + &mut |_tile_rect, _tile_offset, _tile_flags| { + count += 1; + }, + ); + assert_eq!(count, 36); + } + + #[test] + fn empty() { + let mut count = 0; + checked_for_each_tile(&rect(0., 0., 74., 74.), + &rect(75., 75., 400., 400.), + &rect(0, 0, 400, 400), + 36, + &mut |_tile_rect, _tile_offset, _tile_flags| { + count += 1; + }, + ); + assert_eq!(count, 0); + } + + #[test] + fn test_tiles_1d() { + // Exactly one full tile at positive offset. + let result = tiles_1d(64.0, -10000.0..10000.0, 0.0, 0..64, 64); + assert_eq!(result.tile_range.start, 0); + assert_eq!(result.tile_range.end, 1); + assert_eq!(result.first_tile_layout_size, 64.0); + assert_eq!(result.last_tile_layout_size, 64.0); + + // Exactly one full tile at negative offset. + let result = tiles_1d(64.0, -10000.0..10000.0, -64.0, -64..0, 64); + assert_eq!(result.tile_range.start, -1); + assert_eq!(result.tile_range.end, 0); + assert_eq!(result.first_tile_layout_size, 64.0); + assert_eq!(result.last_tile_layout_size, 64.0); + + // Two full tiles at negative and positive offsets. + let result = tiles_1d(64.0, -10000.0..10000.0, -64.0, -64..64, 64); + assert_eq!(result.tile_range.start, -1); + assert_eq!(result.tile_range.end, 1); + assert_eq!(result.first_tile_layout_size, 64.0); + assert_eq!(result.last_tile_layout_size, 64.0); + + // One partial tile at positive offset, non-zero origin, culled out. + let result = tiles_1d(64.0, -100.0..10.0, 64.0, 64..310, 64); + assert_eq!(result.tile_range.start, result.tile_range.end); + + // Two tiles at negative and positive offsets, one of which is culled out. + // The remaining tile is partially culled but it should still generate a full tile. + let result = tiles_1d(64.0, 10.0..10000.0, -64.0, -64..64, 64); + assert_eq!(result.tile_range.start, 0); + assert_eq!(result.tile_range.end, 1); + assert_eq!(result.first_tile_layout_size, 64.0); + assert_eq!(result.last_tile_layout_size, 64.0); + let result = tiles_1d(64.0, -10000.0..-10.0, -64.0, -64..64, 64); + assert_eq!(result.tile_range.start, -1); + assert_eq!(result.tile_range.end, 0); + assert_eq!(result.first_tile_layout_size, 64.0); + assert_eq!(result.last_tile_layout_size, 64.0); + + // Stretched tile in layout space device tile size is 64 and layout tile size is 128. + // So the resulting tile sizes in layout space should be multiplied by two. + let result = tiles_1d(128.0, -10000.0..10000.0, -64.0, -64..32, 64); + assert_eq!(result.tile_range.start, -1); + assert_eq!(result.tile_range.end, 1); + assert_eq!(result.first_tile_layout_size, 128.0); + assert_eq!(result.last_tile_layout_size, 64.0); + + // Two visible tiles (the rest is culled out). + let result = tiles_1d(10.0, 0.0..20.0, 0.0, 0..64, 64); + assert_eq!(result.tile_range.start, 0); + assert_eq!(result.tile_range.end, 1); + assert_eq!(result.first_tile_layout_size, 10.0); + assert_eq!(result.last_tile_layout_size, 10.0); + + // Two visible tiles at negative layout offsets (the rest is culled out). + let result = tiles_1d(10.0, -20.0..0.0, -20.0, 0..64, 64); + assert_eq!(result.tile_range.start, 0); + assert_eq!(result.tile_range.end, 1); + assert_eq!(result.first_tile_layout_size, 10.0); + assert_eq!(result.last_tile_layout_size, 10.0); + } + + #[test] + fn test_tile_range_1d() { + assert_eq!(tile_range_1d(&(0..256), 256), 0..1); + assert_eq!(tile_range_1d(&(0..257), 256), 0..2); + assert_eq!(tile_range_1d(&(-1..257), 256), -1..2); + assert_eq!(tile_range_1d(&(-256..256), 256), -1..1); + assert_eq!(tile_range_1d(&(-20..-10), 6), -4..-1); + assert_eq!(tile_range_1d(&(20..100), 256), 0..1); + } + + #[test] + fn test_first_last_tile_size_1d() { + assert_eq!(first_tile_size_1d(&(0..10), 64), 10); + assert_eq!(first_tile_size_1d(&(-20..0), 64), 20); + + assert_eq!(last_tile_size_1d(&(0..10), 64), 10); + assert_eq!(last_tile_size_1d(&(-20..0), 64), 20); + } + + #[test] + fn doubly_partial_tiles() { + // In the following tests the image is a single tile and none of the sides of the tile + // align with the tile grid. + // This can only happen when we have a single non-aligned partial tile and no regular + // tiles. + assert_eq!(first_tile_size_1d(&(300..310), 64), 10); + assert_eq!(first_tile_size_1d(&(-20..-10), 64), 10); + + assert_eq!(last_tile_size_1d(&(300..310), 64), 10); + assert_eq!(last_tile_size_1d(&(-20..-10), 64), 10); + + + // One partial tile at positve offset, non-zero origin. + let result = tiles_1d(64.0, -10000.0..10000.0, 0.0, 300..310, 64); + assert_eq!(result.tile_range.start, 4); + assert_eq!(result.tile_range.end, 5); + assert_eq!(result.first_tile_layout_size, 10.0); + assert_eq!(result.last_tile_layout_size, 10.0); + } + + #[test] + fn smaller_than_tile_size_at_origin() { + let r = compute_tile_rect( + &rect(0, 0, 80, 80), + 256, + point2(0, 0), + ); + + assert_eq!(r, rect(0, 0, 80, 80)); + } + + #[test] + fn smaller_than_tile_size_with_offset() { + let r = compute_tile_rect( + &rect(20, 20, 80, 80), + 256, + point2(0, 0), + ); + + assert_eq!(r, rect(20, 20, 80, 80)); + } +} diff --git a/third_party/webrender/webrender_api/src/lib.rs b/third_party/webrender/webrender_api/src/lib.rs new file mode 100644 index 00000000000..5f274753e8f --- /dev/null +++ b/third_party/webrender/webrender_api/src/lib.rs @@ -0,0 +1,64 @@ +/* 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/. */ + +//! The `webrender_api` crate contains an assortment types and functions used +//! by WebRender consumers as well as, in many cases, WebRender itself. +//! +//! This separation allows Servo to parallelize compilation across `webrender` +//! and other crates that depend on `webrender_api`. So in practice, we put +//! things in this crate when Servo needs to use them. Firefox depends on the +//! `webrender` crate directly, and so this distinction is not really relevant +//! there. + +#![cfg_attr(feature = "nightly", feature(nonzero))] +#![cfg_attr(feature = "cargo-clippy", allow(clippy::float_cmp, clippy::too_many_arguments))] +#![cfg_attr(feature = "cargo-clippy", allow(clippy::unreadable_literal, clippy::new_without_default))] + +extern crate app_units; +#[macro_use] +extern crate bitflags; +extern crate byteorder; +#[cfg(feature = "nightly")] +extern crate core; +#[cfg(target_os = "macos")] +extern crate core_foundation; +#[cfg(target_os = "macos")] +extern crate core_graphics; +#[macro_use] +extern crate derive_more; +pub extern crate euclid; +#[macro_use] +extern crate malloc_size_of_derive; +extern crate serde; +#[macro_use] +extern crate serde_derive; +extern crate time; + +extern crate malloc_size_of; +extern crate peek_poke; + +mod api; +pub mod channel; +mod color; +mod display_item; +mod display_item_cache; +mod display_list; +mod font; +mod gradient_builder; +mod image; +mod resources; +pub mod units; + +#[doc(hidden)] +pub mod image_tiling; + +pub use crate::api::*; +pub use crate::color::*; +pub use crate::display_item::*; +pub use crate::display_item_cache::DisplayItemCache; +pub use crate::display_list::*; +pub use crate::font::*; +pub use crate::gradient_builder::*; +pub use crate::image::*; +pub use crate::resources::DEFAULT_TILE_SIZE; diff --git a/third_party/webrender/webrender_api/src/resources.rs b/third_party/webrender/webrender_api/src/resources.rs new file mode 100644 index 00000000000..c41fdc3009e --- /dev/null +++ b/third_party/webrender/webrender_api/src/resources.rs @@ -0,0 +1,327 @@ +/* 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 crate::{BlobImageKey, ImageDescriptor, DirtyRect, TileSize, ResourceUpdate}; +use crate::{BlobImageHandler, AsyncBlobImageRasterizer, BlobImageData, BlobImageParams}; +use crate::{BlobImageRequest, BlobImageDescriptor, BlobImageResources, TransactionMsg}; +use crate::{FontKey, FontTemplate, FontInstanceData, FontInstanceKey, AddFont}; +use crate::image_tiling::*; +use crate::units::*; +use crate::font::SharedFontInstanceMap; +use crate::euclid::{point2, size2}; + +pub const DEFAULT_TILE_SIZE: TileSize = 512; + +use std::collections::HashMap; +use std::sync::Arc; + +/// We use this to generate the async blob rendering requests. +struct BlobImageTemplate { + descriptor: ImageDescriptor, + tile_size: TileSize, + dirty_rect: BlobDirtyRect, + /// See ImageResource::visible_rect. + visible_rect: DeviceIntRect, + // If the active rect of the blob changes, this represents the + // range of tiles that remain valid. This must be taken into + // account in addition to the valid rect when submitting blob + // rasterization requests. + // `None` means the bounds have not changed (tiles are still valid). + // `Some(TileRange::zero())` means all of the tiles are invalid. + valid_tiles_after_bounds_change: Option, +} + +struct FontResources { + templates: HashMap, + instances: SharedFontInstanceMap, +} + +pub struct ApiResources { + blob_image_templates: HashMap, + pub blob_image_handler: Option>, + fonts: FontResources, +} + +impl BlobImageResources for FontResources { + fn get_font_data(&self, key: FontKey) -> &FontTemplate { + self.templates.get(&key).unwrap() + } + fn get_font_instance_data(&self, key: FontInstanceKey) -> Option { + self.instances.get_font_instance_data(key) + } +} + +impl ApiResources { + pub fn new( + blob_image_handler: Option>, + instances: SharedFontInstanceMap, + ) -> Self { + ApiResources { + blob_image_templates: HashMap::new(), + blob_image_handler, + fonts: FontResources { + templates: HashMap::new(), + instances, + } + } + } + + pub fn get_shared_font_instances(&self) -> SharedFontInstanceMap { + self.fonts.instances.clone() + } + + pub fn update(&mut self, transaction: &mut TransactionMsg) { + let mut blobs_to_rasterize = Vec::new(); + for update in &transaction.resource_updates { + match *update { + ResourceUpdate::AddBlobImage(ref img) => { + self.blob_image_handler + .as_mut() + .unwrap() + .add(img.key, Arc::clone(&img.data), &img.visible_rect, img.tile_size); + + self.blob_image_templates.insert( + img.key, + BlobImageTemplate { + descriptor: img.descriptor, + tile_size: img.tile_size, + dirty_rect: DirtyRect::All, + valid_tiles_after_bounds_change: None, + visible_rect: img.visible_rect, + }, + ); + blobs_to_rasterize.push(img.key); + } + ResourceUpdate::UpdateBlobImage(ref img) => { + debug_assert_eq!(img.visible_rect.size, img.descriptor.size); + self.update_blob_image( + img.key, + Some(&img.descriptor), + Some(&img.dirty_rect), + Some(Arc::clone(&img.data)), + &img.visible_rect, + ); + blobs_to_rasterize.push(img.key); + } + ResourceUpdate::DeleteBlobImage(key) => { + self.blob_image_templates.remove(&key); + } + ResourceUpdate::SetBlobImageVisibleArea(ref key, ref area) => { + self.update_blob_image(*key, None, None, None, area); + blobs_to_rasterize.push(*key); + } + ResourceUpdate::AddFont(ref font) => { + match font { + AddFont::Raw(key, bytes, index) => { + self.fonts.templates.insert( + *key, + FontTemplate::Raw(Arc::clone(bytes), *index), + ); + } + AddFont::Native(key, native_font_handle) => { + self.fonts.templates.insert( + *key, + FontTemplate::Native(native_font_handle.clone()), + ); + } + } + } + ResourceUpdate::AddFontInstance(ref instance) => { + // TODO(nical): Don't clone these. + self.fonts.instances.add_font_instance( + instance.key, + instance.font_key, + instance.glyph_size, + instance.options.clone(), + instance.platform_options.clone(), + instance.variations.clone(), + ); + } + ResourceUpdate::DeleteFont(key) => { + self.fonts.templates.remove(&key); + if let Some(ref mut handler) = self.blob_image_handler { + handler.delete_font(key); + } + } + ResourceUpdate::DeleteFontInstance(key) => { + // We will delete from the shared font instance map in the resource cache + // after scene swap. + + if let Some(ref mut r) = self.blob_image_handler { + r.delete_font_instance(key); + } + } + _ => {} + } + } + + let (rasterizer, requests) = self.create_blob_scene_builder_requests(&blobs_to_rasterize); + transaction.blob_rasterizer = rasterizer; + transaction.blob_requests = requests; + } + + pub fn enable_multithreading(&mut self, enable: bool) { + if let Some(ref mut handler) = self.blob_image_handler { + handler.enable_multithreading(enable); + } + } + + fn update_blob_image( + &mut self, + key: BlobImageKey, + descriptor: Option<&ImageDescriptor>, + dirty_rect: Option<&BlobDirtyRect>, + data: Option>, + visible_rect: &DeviceIntRect, + ) { + if let Some(data) = data { + let dirty_rect = dirty_rect.unwrap(); + self.blob_image_handler.as_mut().unwrap().update(key, data, visible_rect, dirty_rect); + } + + let image = self.blob_image_templates + .get_mut(&key) + .expect("Attempt to update non-existent blob image"); + + let mut valid_tiles_after_bounds_change = compute_valid_tiles_if_bounds_change( + &image.visible_rect, + visible_rect, + image.tile_size, + ); + + match (image.valid_tiles_after_bounds_change, valid_tiles_after_bounds_change) { + (Some(old), Some(ref mut new)) => { + *new = new.intersection(&old).unwrap_or_else(TileRange::zero); + } + (Some(old), None) => { + valid_tiles_after_bounds_change = Some(old); + } + _ => {} + } + + let blob_size = visible_rect.size; + + if let Some(descriptor) = descriptor { + image.descriptor = *descriptor; + } else { + // make sure the descriptor size matches the visible rect. + // This might not be necessary but let's stay on the safe side. + image.descriptor.size = blob_size; + } + + if let Some(dirty_rect) = dirty_rect { + image.dirty_rect = image.dirty_rect.union(dirty_rect); + } + + image.valid_tiles_after_bounds_change = valid_tiles_after_bounds_change; + image.visible_rect = *visible_rect; + } + + pub fn create_blob_scene_builder_requests( + &mut self, + keys: &[BlobImageKey] + ) -> (Option>, Vec) { + if self.blob_image_handler.is_none() || keys.is_empty() { + return (None, Vec::new()); + } + + let mut blob_request_params = Vec::new(); + for key in keys { + let template = self.blob_image_templates.get_mut(key).unwrap(); + + // If we know that only a portion of the blob image is in the viewport, + // only request these visible tiles since blob images can be huge. + let tiles = compute_tile_range( + &template.visible_rect, + template.tile_size, + ); + + // Don't request tiles that weren't invalidated. + let dirty_tiles = match template.dirty_rect { + DirtyRect::Partial(dirty_rect) => { + compute_tile_range( + &dirty_rect.cast_unit(), + template.tile_size, + ) + } + DirtyRect::All => tiles, + }; + + for_each_tile_in_range(&tiles, |tile| { + let still_valid = template.valid_tiles_after_bounds_change + .map(|valid_tiles| valid_tiles.contains(tile)) + .unwrap_or(true); + + if still_valid && !dirty_tiles.contains(tile) { + return; + } + + let descriptor = BlobImageDescriptor { + rect: compute_tile_rect( + &template.visible_rect, + template.tile_size, + tile, + ).cast_unit(), + format: template.descriptor.format, + }; + + assert!(descriptor.rect.size.width > 0 && descriptor.rect.size.height > 0); + blob_request_params.push( + BlobImageParams { + request: BlobImageRequest { key: *key, tile }, + descriptor, + dirty_rect: DirtyRect::All, + } + ); + }); + + template.dirty_rect = DirtyRect::empty(); + template.valid_tiles_after_bounds_change = None; + } + + let handler = self.blob_image_handler.as_mut().unwrap(); + handler.prepare_resources(&self.fonts, &blob_request_params); + (Some(handler.create_blob_rasterizer()), blob_request_params) + } +} + +fn compute_valid_tiles_if_bounds_change( + prev_rect: &DeviceIntRect, + new_rect: &DeviceIntRect, + tile_size: u16, +) -> Option { + let intersection = match prev_rect.intersection(new_rect) { + Some(rect) => rect, + None => { + return Some(TileRange::zero()); + } + }; + + let left = prev_rect.min_x() != new_rect.min_x(); + let right = prev_rect.max_x() != new_rect.max_x(); + let top = prev_rect.min_y() != new_rect.min_y(); + let bottom = prev_rect.max_y() != new_rect.max_y(); + + if !left && !right && !top && !bottom { + // Bounds have not changed. + return None; + } + + let tw = 1.0 / (tile_size as f32); + let th = 1.0 / (tile_size as f32); + + let tiles = intersection + .cast::() + .scale(tw, th); + + let min_x = if left { f32::ceil(tiles.min_x()) } else { f32::floor(tiles.min_x()) }; + let min_y = if top { f32::ceil(tiles.min_y()) } else { f32::floor(tiles.min_y()) }; + let max_x = if right { f32::floor(tiles.max_x()) } else { f32::ceil(tiles.max_x()) }; + let max_y = if bottom { f32::floor(tiles.max_y()) } else { f32::ceil(tiles.max_y()) }; + + Some(TileRange { + origin: point2(min_x as i32, min_y as i32), + size: size2((max_x - min_x) as i32, (max_y - min_y) as i32), + }) +} diff --git a/third_party/webrender/webrender_api/src/units.rs b/third_party/webrender/webrender_api/src/units.rs new file mode 100644 index 00000000000..9913e4e6647 --- /dev/null +++ b/third_party/webrender/webrender_api/src/units.rs @@ -0,0 +1,324 @@ +/* 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/. */ + +//! A collection of coordinate spaces and their corresponding Point, Size and Rect types. +//! +//! Physical pixels take into account the device pixel ratio and their dimensions tend +//! to correspond to the allocated size of resources in memory, while logical pixels +//! don't have the device pixel ratio applied which means they are agnostic to the usage +//! of hidpi screens and the like. +//! +//! The terms "layer" and "stacking context" can be used interchangeably +//! in the context of coordinate systems. + +pub use app_units::Au; +use euclid::{Length, Rect, Scale, Size2D, Transform3D, Translation2D}; +use euclid::{Point2D, Point3D, Vector2D, Vector3D, SideOffsets2D, Box2D}; +use euclid::HomogeneousVector; +use peek_poke::PeekPoke; +// local imports +use crate::image::DirtyRect; + +/// Geometry in the coordinate system of the render target (screen or intermediate +/// surface) in physical pixels. +#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] +pub struct DevicePixel; + +pub type DeviceIntRect = Rect; +pub type DeviceIntPoint = Point2D; +pub type DeviceIntSize = Size2D; +pub type DeviceIntLength = Length; +pub type DeviceIntSideOffsets = SideOffsets2D; + +pub type DeviceRect = Rect; +pub type DevicePoint = Point2D; +pub type DeviceVector2D = Vector2D; +pub type DeviceSize = Size2D; +pub type DeviceHomogeneousVector = HomogeneousVector; + +/// Geometry in the coordinate system of the framebuffer in physical pixels. +/// It's Y-flipped comparing to DevicePixel. +#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] +pub struct FramebufferPixel; + +pub type FramebufferIntPoint = Point2D; +pub type FramebufferIntSize = Size2D; +pub type FramebufferIntRect = Rect; + +/// Geometry in the coordinate system of a Picture (intermediate +/// surface) in physical pixels. +#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub struct PicturePixel; + +pub type PictureIntRect = Rect; +pub type PictureIntPoint = Point2D; +pub type PictureIntSize = Size2D; +pub type PictureRect = Rect; +pub type PicturePoint = Point2D; +pub type PictureSize = Size2D; +pub type PicturePoint3D = Point3D; +pub type PictureVector2D = Vector2D; +pub type PictureVector3D = Vector3D; +pub type PictureBox2D = Box2D; + +/// Geometry gets rasterized in a given root coordinate space. This +/// is often the root spatial node (world space), but may be a local +/// space for a variety of reasons (e.g. perspective). +#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub struct RasterPixel; + +pub type RasterIntRect = Rect; +pub type RasterIntPoint = Point2D; +pub type RasterIntSize = Size2D; +pub type RasterRect = Rect; +pub type RasterPoint = Point2D; +pub type RasterSize = Size2D; +pub type RasterPoint3D = Point3D; +pub type RasterVector2D = Vector2D; +pub type RasterVector3D = Vector3D; + +/// Geometry in a stacking context's local coordinate space (logical pixels). +#[derive(Hash, Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, Ord, PartialOrd, Deserialize, Serialize, PeekPoke)] +pub struct LayoutPixel; + +pub type LayoutRect = Rect; +pub type LayoutPoint = Point2D; +pub type LayoutPoint3D = Point3D; +pub type LayoutVector2D = Vector2D; +pub type LayoutVector3D = Vector3D; +pub type LayoutSize = Size2D; +pub type LayoutSideOffsets = SideOffsets2D; + +pub type LayoutIntRect = Rect; +pub type LayoutIntPoint = Point2D; +pub type LayoutIntSize = Size2D; + +/// Geometry in the document's coordinate space (logical pixels). +#[derive(Hash, Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, Ord, PartialOrd)] +pub struct WorldPixel; + +pub type WorldRect = Rect; +pub type WorldPoint = Point2D; +pub type WorldSize = Size2D; +pub type WorldPoint3D = Point3D; +pub type WorldVector2D = Vector2D; +pub type WorldVector3D = Vector3D; + +/// Offset in number of tiles. +#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub struct Tiles; +pub type TileOffset = Point2D; +pub type TileRange = Rect; + +/// Scaling ratio from world pixels to device pixels. +pub type DevicePixelScale = Scale; +/// Scaling ratio from layout to world. Used for cases where we know the layout +/// is in world space, or specifically want to treat it this way. +pub type LayoutToWorldScale = Scale; +/// A complete scaling ratio from layout space to device pixel space. +pub type LayoutToDeviceScale = Scale; + +pub type LayoutTransform = Transform3D; +pub type LayoutToWorldTransform = Transform3D; +pub type WorldToLayoutTransform = Transform3D; + +pub type LayoutToPictureTransform = Transform3D; +pub type PictureToLayoutTransform = Transform3D; + +pub type LayoutToRasterTransform = Transform3D; +pub type RasterToLayoutTransform = Transform3D; + +pub type PictureToRasterTransform = Transform3D; +pub type RasterToPictureTransform = Transform3D; + +// Fixed position coordinates, to avoid float precision errors. +pub type LayoutPointAu = Point2D; +pub type LayoutRectAu = Rect; +pub type LayoutSizeAu = Size2D; +pub type LayoutVector2DAu = Vector2D; +pub type LayoutSideOffsetsAu = SideOffsets2D; + +pub type ImageDirtyRect = DirtyRect; +pub type BlobDirtyRect = DirtyRect; + +pub type BlobToDeviceTranslation = Translation2D; + +/// Stores two coordinates in texel space. The coordinates +/// are stored in texel coordinates because the texture atlas +/// may grow. Storing them as texel coords and normalizing +/// the UVs in the vertex shader means nothing needs to be +/// updated on the CPU when the texture size changes. +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct TexelRect { + pub uv0: DevicePoint, + pub uv1: DevicePoint, +} + +impl TexelRect { + pub fn new(u0: f32, v0: f32, u1: f32, v1: f32) -> Self { + TexelRect { + uv0: DevicePoint::new(u0, v0), + uv1: DevicePoint::new(u1, v1), + } + } + + pub fn invalid() -> Self { + TexelRect { + uv0: DevicePoint::new(-1.0, -1.0), + uv1: DevicePoint::new(-1.0, -1.0), + } + } +} + +impl Into for DeviceIntRect { + fn into(self) -> TexelRect { + TexelRect { + uv0: self.min().to_f32(), + uv1: self.max().to_f32(), + } + } +} + +const MAX_AU_FLOAT: f32 = 1.0e6; + +pub trait AuHelpers { + fn from_au(data: T) -> Self; + fn to_au(&self) -> T; +} + +impl AuHelpers for LayoutSize { + fn from_au(size: LayoutSizeAu) -> Self { + LayoutSize::new( + size.width.to_f32_px(), + size.height.to_f32_px(), + ) + } + + fn to_au(&self) -> LayoutSizeAu { + let width = self.width.min(2.0 * MAX_AU_FLOAT); + let height = self.height.min(2.0 * MAX_AU_FLOAT); + + LayoutSizeAu::new( + Au::from_f32_px(width), + Au::from_f32_px(height), + ) + } +} + +impl AuHelpers for LayoutVector2D { + fn from_au(size: LayoutVector2DAu) -> Self { + LayoutVector2D::new( + size.x.to_f32_px(), + size.y.to_f32_px(), + ) + } + + fn to_au(&self) -> LayoutVector2DAu { + LayoutVector2DAu::new( + Au::from_f32_px(self.x), + Au::from_f32_px(self.y), + ) + } +} + +impl AuHelpers for LayoutPoint { + fn from_au(point: LayoutPointAu) -> Self { + LayoutPoint::new( + point.x.to_f32_px(), + point.y.to_f32_px(), + ) + } + + fn to_au(&self) -> LayoutPointAu { + let x = self.x.min(MAX_AU_FLOAT).max(-MAX_AU_FLOAT); + let y = self.y.min(MAX_AU_FLOAT).max(-MAX_AU_FLOAT); + + LayoutPointAu::new( + Au::from_f32_px(x), + Au::from_f32_px(y), + ) + } +} + +impl AuHelpers for LayoutRect { + fn from_au(rect: LayoutRectAu) -> Self { + LayoutRect::new( + LayoutPoint::from_au(rect.origin), + LayoutSize::from_au(rect.size), + ) + } + + fn to_au(&self) -> LayoutRectAu { + LayoutRectAu::new( + self.origin.to_au(), + self.size.to_au(), + ) + } +} + +impl AuHelpers for LayoutSideOffsets { + fn from_au(offsets: LayoutSideOffsetsAu) -> Self { + LayoutSideOffsets::new( + offsets.top.to_f32_px(), + offsets.right.to_f32_px(), + offsets.bottom.to_f32_px(), + offsets.left.to_f32_px(), + ) + } + + fn to_au(&self) -> LayoutSideOffsetsAu { + LayoutSideOffsetsAu::new( + Au::from_f32_px(self.top), + Au::from_f32_px(self.right), + Au::from_f32_px(self.bottom), + Au::from_f32_px(self.left), + ) + } +} + +pub trait RectExt { + type Point; + fn top_left(&self) -> Self::Point; + fn top_right(&self) -> Self::Point; + fn bottom_left(&self) -> Self::Point; + fn bottom_right(&self) -> Self::Point; +} + +impl RectExt for Rect { + type Point = Point2D; + fn top_left(&self) -> Self::Point { + self.min() + } + fn top_right(&self) -> Self::Point { + Point2D::new(self.max_x(), self.min_y()) + } + fn bottom_left(&self) -> Self::Point { + Point2D::new(self.min_x(), self.max_y()) + } + fn bottom_right(&self) -> Self::Point { + self.max() + } +} + +// A few helpers to convert to cast between coordinate spaces that are often equivalent. + +#[inline] +pub fn layout_rect_as_picture_rect(layout_rect: &LayoutRect) -> PictureRect { + layout_rect.cast_unit() +} + +#[inline] +pub fn layout_vector_as_picture_vector(layout_vector: LayoutVector2D) -> PictureVector2D { + layout_vector.cast_unit() +} + +#[inline] +pub fn device_size_as_framebuffer_size(framebuffer_size: DeviceIntSize) -> FramebufferIntSize { + framebuffer_size.cast_unit() +} + +#[inline] +pub fn device_rect_as_framebuffer_rect(framebuffer_rect: &DeviceIntRect) -> FramebufferIntRect { + framebuffer_rect.cast_unit() +} diff --git a/third_party/webrender/webrender_build/Cargo.toml b/third_party/webrender/webrender_build/Cargo.toml new file mode 100644 index 00000000000..862d2a77b14 --- /dev/null +++ b/third_party/webrender/webrender_build/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "webrender_build" +authors = ["The Servo Project Developers"] +version = "0.0.1" +license = "MPL-2.0" +repository = "https://github.com/servo/webrender" +description = "Code shared between precompilation (build.rs) and the rest of WebRender" +edition = "2018" + +[features] +serialize_program = ["serde"] + +[dependencies] +bitflags = "1.2" +lazy_static = "1" +serde = { optional = true, version = "1.0", features = ["serde_derive"] } diff --git a/third_party/webrender/webrender_build/src/lib.rs b/third_party/webrender/webrender_build/src/lib.rs new file mode 100644 index 00000000000..23438a478e6 --- /dev/null +++ b/third_party/webrender/webrender_build/src/lib.rs @@ -0,0 +1,19 @@ +/* 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/. */ + +#[macro_use] +extern crate bitflags; + +#[macro_use] +extern crate lazy_static; + +#[cfg(any(feature = "serde"))] +#[macro_use] +extern crate serde; + +pub mod shader; +pub mod shader_features; + +/// This must be known at build-time as the shaders depend on it. +pub const MAX_VERTEX_TEXTURE_WIDTH: usize = 1024; diff --git a/third_party/webrender/webrender_build/src/shader.rs b/third_party/webrender/webrender_build/src/shader.rs new file mode 100644 index 00000000000..3c15f4f4148 --- /dev/null +++ b/third_party/webrender/webrender_build/src/shader.rs @@ -0,0 +1,222 @@ +/* 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/. */ + +//! Functionality for managing source code for shaders. +//! +//! This module is used during precompilation (build.rs) and regular compilation, +//! so it has minimal dependencies. + +use std::borrow::Cow; +use std::fs::File; +use std::io::Read; +use std::path::Path; +use std::collections::HashSet; +use std::collections::hash_map::DefaultHasher; +use crate::MAX_VERTEX_TEXTURE_WIDTH; + +pub use crate::shader_features::*; + +lazy_static! { + static ref MAX_VERTEX_TEXTURE_WIDTH_STRING: String = MAX_VERTEX_TEXTURE_WIDTH.to_string(); +} + +#[derive(Clone, Copy, Debug)] +pub enum ShaderKind { + Vertex, + Fragment, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum ShaderVersion { + Gl, + Gles, +} + +impl ShaderVersion { + /// Return the full variant name, for use in code generation. + pub fn variant_name(&self) -> &'static str { + match self { + ShaderVersion::Gl => "ShaderVersion::Gl", + ShaderVersion::Gles => "ShaderVersion::Gles", + } + } +} + +#[derive(PartialEq, Eq, Hash, Debug, Clone, Default)] +#[cfg_attr(feature = "serialize_program", derive(Deserialize, Serialize))] +pub struct ProgramSourceDigest(u64); + +impl ::std::fmt::Display for ProgramSourceDigest { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "{:02x}", self.0) + } +} + +impl From for ProgramSourceDigest { + fn from(hasher: DefaultHasher) -> Self { + use std::hash::Hasher; + ProgramSourceDigest(hasher.finish()) + } +} + +const SHADER_IMPORT: &str = "#include "; + +pub struct ShaderSourceParser { + included: HashSet, +} + +impl ShaderSourceParser { + pub fn new() -> Self { + ShaderSourceParser { + included: HashSet::new(), + } + } + + /// Parses a shader string for imports. Imports are recursively processed, and + /// prepended to the output stream. + pub fn parse Cow<'static, str>>( + &mut self, + source: Cow<'static, str>, + get_source: &G, + output: &mut F, + ) { + for line in source.lines() { + if line.starts_with(SHADER_IMPORT) { + let imports = line[SHADER_IMPORT.len() ..].split(','); + + // For each import, get the source, and recurse. + for import in imports { + if self.included.insert(import.into()) { + let include = get_source(import); + self.parse(include, get_source, output); + } else { + output(&format!("// {} is already included\n", import)); + } + } + } else { + output(line); + output("\n"); + } + } + } +} + +/// Reads a shader source file from disk into a String. +pub fn shader_source_from_file(shader_path: &Path) -> String { + assert!(shader_path.exists(), "Shader not found {:?}", shader_path); + let mut source = String::new(); + File::open(&shader_path) + .expect("Shader not found") + .read_to_string(&mut source) + .unwrap(); + source +} + +/// Creates heap-allocated strings for both vertex and fragment shaders. +pub fn build_shader_strings Cow<'static, str>>( + gl_version: ShaderVersion, + features: &[&str], + base_filename: &str, + get_source: &G, +) -> (String, String) { + let mut vs_source = String::new(); + do_build_shader_string( + gl_version, + features, + ShaderKind::Vertex, + base_filename, + get_source, + |s| vs_source.push_str(s), + ); + + let mut fs_source = String::new(); + do_build_shader_string( + gl_version, + features, + ShaderKind::Fragment, + base_filename, + get_source, + |s| fs_source.push_str(s), + ); + + (vs_source, fs_source) +} + +/// Walks the given shader string and applies the output to the provided +/// callback. Assuming an override path is not used, does no heap allocation +/// and no I/O. +pub fn do_build_shader_string Cow<'static, str>>( + gl_version: ShaderVersion, + features: &[&str], + kind: ShaderKind, + base_filename: &str, + get_source: &G, + mut output: F, +) { + build_shader_prefix_string(gl_version, features, kind, base_filename, &mut output); + build_shader_main_string(base_filename, get_source, &mut output); +} + +/// Walks the prefix section of the shader string, which manages the various +/// defines for features etc. +pub fn build_shader_prefix_string( + gl_version: ShaderVersion, + features: &[&str], + kind: ShaderKind, + base_filename: &str, + output: &mut F, +) { + // GLSL requires that the version number comes first. + let gl_version_string = match gl_version { + ShaderVersion::Gl => "#version 150\n", + ShaderVersion::Gles => "#version 300 es\n", + }; + output(gl_version_string); + + // Insert the shader name to make debugging easier. + output("// shader: "); + output(base_filename); + output(" "); + for (i, feature) in features.iter().enumerate() { + output(feature); + if i != features.len() - 1 { + output(","); + } + } + output("\n"); + + // Define a constant depending on whether we are compiling VS or FS. + let kind_string = match kind { + ShaderKind::Vertex => "#define WR_VERTEX_SHADER\n", + ShaderKind::Fragment => "#define WR_FRAGMENT_SHADER\n", + }; + output(kind_string); + + // Define a constant for the vertex texture width. + output("#define WR_MAX_VERTEX_TEXTURE_WIDTH "); + output(&MAX_VERTEX_TEXTURE_WIDTH_STRING); + output("U\n"); + + // Add any defines for features that were passed by the caller. + for feature in features { + assert!(!feature.is_empty()); + output("#define WR_FEATURE_"); + output(feature); + output("\n"); + } +} + +/// Walks the main .glsl file, including any imports. +pub fn build_shader_main_string Cow<'static, str>>( + base_filename: &str, + get_source: &G, + output: &mut F, +) { + let shared_source = get_source(base_filename); + ShaderSourceParser::new().parse( + shared_source, + &|f| get_source(f), + output + ); +} diff --git a/third_party/webrender/webrender_build/src/shader_features.rs b/third_party/webrender/webrender_build/src/shader_features.rs new file mode 100644 index 00000000000..06e6b0da8a7 --- /dev/null +++ b/third_party/webrender/webrender_build/src/shader_features.rs @@ -0,0 +1,152 @@ +/* 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 std::collections::HashMap; + +bitflags! { + #[derive(Default)] + pub struct ShaderFeatureFlags: u32 { + const GL = 1 << 0; + const GLES = 1 << 1; + + const ADVANCED_BLEND_EQUATION = 1 << 8; + const DUAL_SOURCE_BLENDING = 1 << 9; + const PIXEL_LOCAL_STORAGE = 1 << 10; + const DITHERING = 1 << 11; + const TEXTURE_EXTERNAL = 1 << 12; + } +} + +pub type ShaderFeatures = HashMap<&'static str, Vec>; + +macro_rules! features { + ($($str:expr),*) => { vec![$(String::from($str),)*] as Vec }; +} + +fn concat_features(a: &str, b: &str) -> String { + if a.is_empty() { + b.to_string() + } else if b.is_empty() { + a.to_string() + } else { + [a, b].join(",") + } +} + +/// Computes available shaders and their features for the given feature flags. +pub fn get_shader_features(flags: ShaderFeatureFlags) -> ShaderFeatures { + let mut shaders = ShaderFeatures::new(); + + // Clip shaders + shaders.insert("cs_clip_rectangle", features!["", "FAST_PATH"]); + for name in &["cs_clip_image", "cs_clip_box_shadow"] { + shaders.insert(name, features![""]); + } + + // Cache shaders + shaders.insert("cs_blur", features!["ALPHA_TARGET", "COLOR_TARGET"]); + + for name in &["cs_line_decoration", "cs_gradient", "cs_border_segment", "cs_border_solid", "cs_svg_filter"] { + shaders.insert(name, features![""]); + } + + shaders.insert("cs_scale", features![""]); + + // Pixel local storage shaders + let pls_feature = if flags.contains(ShaderFeatureFlags::PIXEL_LOCAL_STORAGE) { + for name in &["pls_init", "pls_resolve"] { + shaders.insert(name, features!["PIXEL_LOCAL_STORAGE"]); + } + + "PIXEL_LOCAL_STORAGE" + } else { + "" + }; + + // Brush shaders + let brush_alpha_features = concat_features("ALPHA_PASS", pls_feature); + for name in &["brush_solid", "brush_blend", "brush_mix_blend", "brush_opacity"] { + shaders.insert(name, features!["", &brush_alpha_features, "DEBUG_OVERDRAW"]); + } + for name in &["brush_conic_gradient", "brush_radial_gradient", "brush_linear_gradient"] { + let mut features: Vec = Vec::new(); + let base = if flags.contains(ShaderFeatureFlags::DITHERING) { + "DITHERING" + } else { + "" + }; + features.push(base.to_string()); + features.push(concat_features(base, &brush_alpha_features)); + features.push(concat_features(base, "DEBUG_OVERDRAW")); + shaders.insert(name, features); + } + + // Image brush shaders + let mut texture_types = vec!["", "TEXTURE_2D"]; + if flags.contains(ShaderFeatureFlags::GL) { + texture_types.push("TEXTURE_RECT"); + } + if flags.contains(ShaderFeatureFlags::TEXTURE_EXTERNAL) { + texture_types.push("TEXTURE_EXTERNAL"); + } + let mut image_features: Vec = Vec::new(); + for texture_type in &texture_types { + let fast = texture_type.to_string(); + image_features.push(fast.clone()); + image_features.push(concat_features(&fast, &brush_alpha_features)); + image_features.push(concat_features(&fast, "DEBUG_OVERDRAW")); + let slow = concat_features(texture_type, "REPETITION,ANTIALIASING"); + image_features.push(slow.clone()); + image_features.push(concat_features(&slow, &brush_alpha_features)); + image_features.push(concat_features(&slow, "DEBUG_OVERDRAW")); + if flags.contains(ShaderFeatureFlags::ADVANCED_BLEND_EQUATION) { + let advanced_blend_features = concat_features(&brush_alpha_features, "ADVANCED_BLEND"); + image_features.push(concat_features(&fast, &advanced_blend_features)); + image_features.push(concat_features(&slow, &advanced_blend_features)); + } + if flags.contains(ShaderFeatureFlags::DUAL_SOURCE_BLENDING) { + let dual_source_features = concat_features(&brush_alpha_features, "DUAL_SOURCE_BLENDING"); + image_features.push(concat_features(&fast, &dual_source_features)); + image_features.push(concat_features(&slow, &dual_source_features)); + } + } + shaders.insert("brush_image", image_features); + + let mut composite_features: Vec = Vec::new(); + for texture_type in &texture_types { + let base = concat_features("", texture_type); + composite_features.push(base.clone()); + } + // YUV image brush shaders + let mut yuv_features: Vec = Vec::new(); + for texture_type in &texture_types { + let base = concat_features("YUV", texture_type); + composite_features.push(base.clone()); + yuv_features.push(base.clone()); + yuv_features.push(concat_features(&base, &brush_alpha_features)); + yuv_features.push(concat_features(&base, "DEBUG_OVERDRAW")); + } + shaders.insert("composite", composite_features); + shaders.insert("brush_yuv_image", yuv_features); + + // Prim shaders + let mut text_types = vec![pls_feature]; + if flags.contains(ShaderFeatureFlags::DUAL_SOURCE_BLENDING) { + text_types.push("DUAL_SOURCE_BLENDING"); + } + let mut text_features: Vec = Vec::new(); + for text_type in &text_types { + text_features.push(concat_features(text_type, "ALPHA_PASS")); + text_features.push(concat_features(text_type, "GLYPH_TRANSFORM,ALPHA_PASS")); + text_features.push(concat_features(text_type, "DEBUG_OVERDRAW")); + } + shaders.insert("ps_text_run", text_features); + + shaders.insert("ps_split_composite", features![pls_feature]); + + shaders.insert("ps_clear", features![""]); + + shaders +} + diff --git a/third_party/webrender/wr_malloc_size_of/Cargo.toml b/third_party/webrender/wr_malloc_size_of/Cargo.toml new file mode 100644 index 00000000000..a1f92126fc4 --- /dev/null +++ b/third_party/webrender/wr_malloc_size_of/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors = ["The Servo Project Developers"] +description = "Internal utility to measure memory usage in WebRender." +name = "wr_malloc_size_of" +version = "0.0.1" +license = "MIT/Apache-2.0" +edition = "2018" + +[lib] +path = "lib.rs" + +[dependencies] +app_units = "0.7" +euclid = "0.22" diff --git a/third_party/webrender/wr_malloc_size_of/LICENSE-APACHE b/third_party/webrender/wr_malloc_size_of/LICENSE-APACHE new file mode 100644 index 00000000000..16fe87b06e8 --- /dev/null +++ b/third_party/webrender/wr_malloc_size_of/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/webrender/wr_malloc_size_of/LICENSE-MIT b/third_party/webrender/wr_malloc_size_of/LICENSE-MIT new file mode 100644 index 00000000000..31aa79387f2 --- /dev/null +++ b/third_party/webrender/wr_malloc_size_of/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/third_party/webrender/wr_malloc_size_of/lib.rs b/third_party/webrender/wr_malloc_size_of/lib.rs new file mode 100644 index 00000000000..49a9666342a --- /dev/null +++ b/third_party/webrender/wr_malloc_size_of/lib.rs @@ -0,0 +1,435 @@ +// Copyright 2016-2017 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! A reduced fork of Firefox's malloc_size_of crate, for bundling with WebRender. + +extern crate app_units; +extern crate euclid; + +use std::hash::{BuildHasher, Hash}; +use std::mem::size_of; +use std::ops::Range; +use std::os::raw::c_void; + +/// A C function that takes a pointer to a heap allocation and returns its size. +type VoidPtrToSizeFn = unsafe extern "C" fn(ptr: *const c_void) -> usize; + +/// Operations used when measuring heap usage of data structures. +pub struct MallocSizeOfOps { + /// A function that returns the size of a heap allocation. + pub size_of_op: VoidPtrToSizeFn, + + /// Like `size_of_op`, but can take an interior pointer. Optional because + /// not all allocators support this operation. If it's not provided, some + /// memory measurements will actually be computed estimates rather than + /// real and accurate measurements. + pub enclosing_size_of_op: Option, +} + +impl MallocSizeOfOps { + pub fn new( + size_of: VoidPtrToSizeFn, + malloc_enclosing_size_of: Option, + ) -> Self { + MallocSizeOfOps { + size_of_op: size_of, + enclosing_size_of_op: malloc_enclosing_size_of, + } + } + + /// Check if an allocation is empty. This relies on knowledge of how Rust + /// handles empty allocations, which may change in the future. + fn is_empty(ptr: *const T) -> bool { + // The correct condition is this: + // `ptr as usize <= ::std::mem::align_of::()` + // But we can't call align_of() on a ?Sized T. So we approximate it + // with the following. 256 is large enough that it should always be + // larger than the required alignment, but small enough that it is + // always in the first page of memory and therefore not a legitimate + // address. + ptr as *const usize as usize <= 256 + } + + /// Call `size_of_op` on `ptr`, first checking that the allocation isn't + /// empty, because some types (such as `Vec`) utilize empty allocations. + pub unsafe fn malloc_size_of(&self, ptr: *const T) -> usize { + if MallocSizeOfOps::is_empty(ptr) { + 0 + } else { + (self.size_of_op)(ptr as *const c_void) + } + } + + /// Is an `enclosing_size_of_op` available? + pub fn has_malloc_enclosing_size_of(&self) -> bool { + self.enclosing_size_of_op.is_some() + } + + /// Call `enclosing_size_of_op`, which must be available, on `ptr`, which + /// must not be empty. + pub unsafe fn malloc_enclosing_size_of(&self, ptr: *const T) -> usize { + assert!(!MallocSizeOfOps::is_empty(ptr)); + (self.enclosing_size_of_op.unwrap())(ptr as *const c_void) + } +} + +/// Trait for measuring the "deep" heap usage of a data structure. This is the +/// most commonly-used of the traits. +pub trait MallocSizeOf { + /// Measure the heap usage of all descendant heap-allocated structures, but + /// not the space taken up by the value itself. + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize; +} + +/// Trait for measuring the "shallow" heap usage of a container. +pub trait MallocShallowSizeOf { + /// Measure the heap usage of immediate heap-allocated descendant + /// structures, but not the space taken up by the value itself. Anything + /// beyond the immediate descendants must be measured separately, using + /// iteration. + fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize; +} + +impl MallocSizeOf for String { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + unsafe { ops.malloc_size_of(self.as_ptr()) } + } +} + +impl MallocShallowSizeOf for Box { + fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + unsafe { ops.malloc_size_of(&**self) } + } +} + +impl MallocSizeOf for Box { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.shallow_size_of(ops) + (**self).size_of(ops) + } +} + +impl MallocSizeOf for () { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { + 0 + } +} + +impl MallocSizeOf for (T1, T2) +where + T1: MallocSizeOf, + T2: MallocSizeOf, +{ + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.0.size_of(ops) + self.1.size_of(ops) + } +} + +impl MallocSizeOf for (T1, T2, T3) +where + T1: MallocSizeOf, + T2: MallocSizeOf, + T3: MallocSizeOf, +{ + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.0.size_of(ops) + self.1.size_of(ops) + self.2.size_of(ops) + } +} + +impl MallocSizeOf for (T1, T2, T3, T4) +where + T1: MallocSizeOf, + T2: MallocSizeOf, + T3: MallocSizeOf, + T4: MallocSizeOf, +{ + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.0.size_of(ops) + self.1.size_of(ops) + self.2.size_of(ops) + self.3.size_of(ops) + } +} + +impl MallocSizeOf for Option { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + if let Some(val) = self.as_ref() { + val.size_of(ops) + } else { + 0 + } + } +} + +impl MallocSizeOf for Result { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + match *self { + Ok(ref x) => x.size_of(ops), + Err(ref e) => e.size_of(ops), + } + } +} + +impl MallocSizeOf for std::cell::Cell { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.get().size_of(ops) + } +} + +impl MallocSizeOf for std::cell::RefCell { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.borrow().size_of(ops) + } +} + +impl<'a, B: ?Sized + ToOwned> MallocSizeOf for std::borrow::Cow<'a, B> +where + B::Owned: MallocSizeOf, +{ + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + match *self { + std::borrow::Cow::Borrowed(_) => 0, + std::borrow::Cow::Owned(ref b) => b.size_of(ops), + } + } +} + +impl MallocSizeOf for [T] { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + let mut n = 0; + for elem in self.iter() { + n += elem.size_of(ops); + } + n + } +} + +impl MallocShallowSizeOf for Vec { + fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + unsafe { ops.malloc_size_of(self.as_ptr()) } + } +} + +impl MallocSizeOf for Vec { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + let mut n = self.shallow_size_of(ops); + for elem in self.iter() { + n += elem.size_of(ops); + } + n + } +} + +macro_rules! malloc_size_of_hash_set { + ($ty:ty) => { + impl MallocShallowSizeOf for $ty + where + T: Eq + Hash, + S: BuildHasher, + { + fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + if ops.has_malloc_enclosing_size_of() { + // The first value from the iterator gives us an interior pointer. + // `ops.malloc_enclosing_size_of()` then gives us the storage size. + // This assumes that the `HashSet`'s contents (values and hashes) + // are all stored in a single contiguous heap allocation. + self.iter() + .next() + .map_or(0, |t| unsafe { ops.malloc_enclosing_size_of(t) }) + } else { + // An estimate. + self.capacity() * (size_of::() + size_of::()) + } + } + } + + impl MallocSizeOf for $ty + where + T: Eq + Hash + MallocSizeOf, + S: BuildHasher, + { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + let mut n = self.shallow_size_of(ops); + for t in self.iter() { + n += t.size_of(ops); + } + n + } + } + }; +} + +malloc_size_of_hash_set!(std::collections::HashSet); + +macro_rules! malloc_size_of_hash_map { + ($ty:ty) => { + impl MallocShallowSizeOf for $ty + where + K: Eq + Hash, + S: BuildHasher, + { + fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + // See the implementation for std::collections::HashSet for details. + if ops.has_malloc_enclosing_size_of() { + self.values() + .next() + .map_or(0, |v| unsafe { ops.malloc_enclosing_size_of(v) }) + } else { + self.capacity() * (size_of::() + size_of::() + size_of::()) + } + } + } + + impl MallocSizeOf for $ty + where + K: Eq + Hash + MallocSizeOf, + V: MallocSizeOf, + S: BuildHasher, + { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + let mut n = self.shallow_size_of(ops); + for (k, v) in self.iter() { + n += k.size_of(ops); + n += v.size_of(ops); + } + n + } + } + }; +} + +malloc_size_of_hash_map!(std::collections::HashMap); + +// PhantomData is always 0. +impl MallocSizeOf for std::marker::PhantomData { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { + 0 + } +} + +impl MallocSizeOf for euclid::Length { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.0.size_of(ops) + } +} + +impl MallocSizeOf for euclid::Scale { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.0.size_of(ops) + } +} + +impl MallocSizeOf for euclid::Point2D { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.x.size_of(ops) + self.y.size_of(ops) + } +} + +impl MallocSizeOf for euclid::Rect { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.origin.size_of(ops) + self.size.size_of(ops) + } +} + +impl MallocSizeOf for euclid::SideOffsets2D { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.top.size_of(ops) + + self.right.size_of(ops) + + self.bottom.size_of(ops) + + self.left.size_of(ops) + } +} + +impl MallocSizeOf for euclid::Size2D { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.width.size_of(ops) + self.height.size_of(ops) + } +} + +impl MallocSizeOf for euclid::Transform2D { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.m11.size_of(ops) + + self.m12.size_of(ops) + + self.m21.size_of(ops) + + self.m22.size_of(ops) + + self.m31.size_of(ops) + + self.m32.size_of(ops) + } +} + +impl MallocSizeOf for euclid::Transform3D { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.m11.size_of(ops) + + self.m12.size_of(ops) + + self.m13.size_of(ops) + + self.m14.size_of(ops) + + self.m21.size_of(ops) + + self.m22.size_of(ops) + + self.m23.size_of(ops) + + self.m24.size_of(ops) + + self.m31.size_of(ops) + + self.m32.size_of(ops) + + self.m33.size_of(ops) + + self.m34.size_of(ops) + + self.m41.size_of(ops) + + self.m42.size_of(ops) + + self.m43.size_of(ops) + + self.m44.size_of(ops) + } +} + +impl MallocSizeOf for euclid::Vector2D { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.x.size_of(ops) + self.y.size_of(ops) + } +} + +/// For use on types where size_of() returns 0. +#[macro_export] +macro_rules! malloc_size_of_is_0( + ($($ty:ty),+) => ( + $( + impl $crate::MallocSizeOf for $ty { + #[inline(always)] + fn size_of(&self, _: &mut $crate::MallocSizeOfOps) -> usize { + 0 + } + } + )+ + ); + ($($ty:ident<$($gen:ident),+>),+) => ( + $( + impl<$($gen: $crate::MallocSizeOf),+> $crate::MallocSizeOf for $ty<$($gen),+> { + #[inline(always)] + fn size_of(&self, _: &mut $crate::MallocSizeOfOps) -> usize { + 0 + } + } + )+ + ); +); + +malloc_size_of_is_0!(bool, char, str); +malloc_size_of_is_0!(u8, u16, u32, u64, u128, usize); +malloc_size_of_is_0!(i8, i16, i32, i64, i128, isize); +malloc_size_of_is_0!(f32, f64); + +malloc_size_of_is_0!(std::sync::atomic::AtomicBool); +malloc_size_of_is_0!(std::sync::atomic::AtomicIsize); +malloc_size_of_is_0!(std::sync::atomic::AtomicUsize); + +malloc_size_of_is_0!(std::num::NonZeroUsize); +malloc_size_of_is_0!(std::num::NonZeroU32); + +malloc_size_of_is_0!(std::time::Duration); +malloc_size_of_is_0!(std::time::Instant); +malloc_size_of_is_0!(std::time::SystemTime); + +malloc_size_of_is_0!(Range, Range, Range, Range, Range); +malloc_size_of_is_0!(Range, Range, Range, Range, Range); +malloc_size_of_is_0!(Range, Range); + +malloc_size_of_is_0!(app_units::Au); diff --git a/third_party/webrender/wrench/.gitignore b/third_party/webrender/wrench/.gitignore new file mode 100644 index 00000000000..341d13d0f75 --- /dev/null +++ b/third_party/webrender/wrench/.gitignore @@ -0,0 +1,7 @@ +Cargo.lock +target/ +*# +*~ +yaml_frames/ +json_frames/ +bin_frames/ diff --git a/third_party/webrender/wrench/Cargo.toml b/third_party/webrender/wrench/Cargo.toml new file mode 100644 index 00000000000..5595ced68e2 --- /dev/null +++ b/third_party/webrender/wrench/Cargo.toml @@ -0,0 +1,73 @@ +[package] +name = "wrench" +version = "0.3.0" +authors = ["Vladimir Vukicevic "] +build = "build.rs" +license = "MPL-2.0" +edition = "2018" + +[dependencies] +base64 = "0.10" +bincode = "1.0" +byteorder = "1.0" +env_logger = { version = "0.5", optional = true } +euclid = "0.22" +gleam = "0.12" +glutin = "0.21" +app_units = "0.7" +clap = { version = "2", features = ["yaml"] } +log = "0.4" +yaml-rust = "0.4" +serde_json = "1.0" +ron = "0.5" +time = "0.1" +chrono = "0.2" +crossbeam = "0.2" +osmesa-sys = { version = "0.1.2", optional = true } +osmesa-src = { git = "https://github.com/servo/osmesa-src", optional = true } +webrender = {path = "../webrender", features=["capture","replay","debugger","png","profiler","no_static_freetype", "leak_checks"]} +webrender_api = {path = "../webrender_api", features=["serialize","deserialize"]} +winit = "0.19" +serde = {version = "1.0", features = ["derive"] } +semver = "0.9.0" +swgl = { path = "../swgl", optional = true } + +[dependencies.image] +version = "0.23" +default-features = false +features = ["png"] + +[target.'cfg(target_os = "macos")'.dependencies] +core-graphics = "0.22" +core-foundation = "0.9" + +[features] +default = [ "env_logger" ] +headless = [ "osmesa-sys", "osmesa-src" ] +software = [ "swgl" ] + +[target.'cfg(target_os = "windows")'.dependencies] +dwrote = "0.11" +mozangle = {version = "0.3.1", features = ["egl"]} + +[target.'cfg(all(unix, not(target_os = "android")))'.dependencies] +font-loader = "0.11" + +# Configuration information used when building wrench as an APK. +[package.metadata.android] +package_name = "org.mozilla.wrench" +label = "Wrench" +android_version = 29 +target_sdk_version = 18 +min_sdk_version = 18 +fullscreen = true +build_targets = [ "armv7-linux-androideabi", "i686-linux-android" ] +opengles_version_major = 3 +opengles_version_minor = 0 +[package.metadata.android.application_attributes] +"android:hardwareAccelerated" = "true" +[package.metadata.android.activity_attributes] +"android:screenOrientation" = "unspecified" +"android:uiOptions" = "none" +[[package.metadata.android.permission]] +name = "android.permission.READ_EXTERNAL_STORAGE" diff --git a/third_party/webrender/wrench/README.md b/third_party/webrender/wrench/README.md new file mode 100644 index 00000000000..1119a27112e --- /dev/null +++ b/third_party/webrender/wrench/README.md @@ -0,0 +1,35 @@ +# wrench + +`wrench` is a tool for debugging webrender outside of a browser engine. + +## headless + +`wrench` has an optional headless mode for use in continuous integration. To run in headless mode, instead of using `cargo run -- args`, use `./headless.py args`. + +## `replay` and `show` + +Binary recordings can be generated by webrender and replayed with `wrench replay`. Enable binary recording in `RendererOptions`. + +```rust +RendererOptions { + ... + recorder: Some(Box::new(BinaryRecorder::new("wr-frame.bin"))), + ... +} +``` + +If you are working on gecko integration you can enable recording in `webrender_bindings/src/bindings.rs` by setting + +```rust +static ENABLE_RECORDING: bool = true; +``` + +`wrench replay --save yaml` will convert the recording into frames described in yaml. Frames can then be replayed with `wrench show`. + +## `reftest` + +Wrench also has a reftest system for catching regressions. +* To run all reftests, run `script/headless.py reftest` +* To run specific reftests, run `script/headless.py reftest path/to/test/or/dir` +* To examine test failures, use the [reftest analyzer](https://hg.mozilla.org/mozilla-central/raw-file/tip/layout/tools/reftest/reftest-analyzer.xhtml) +* To add a new reftest, create an example frame and a reference frame in `reftests/` and then add an entry to `reftests/reftest.list` diff --git a/third_party/webrender/wrench/android.txt b/third_party/webrender/wrench/android.txt new file mode 100644 index 00000000000..7ff0ac0eb1d --- /dev/null +++ b/third_party/webrender/wrench/android.txt @@ -0,0 +1,93 @@ +Running Wrench on Android devices. +================================== + +Setting up the environment: +--------------------------- + +Follow the steps at https://github.com/rust-windowing/android-rs-glue#setting-up-your-environment, with exceptions: + - No need to download the Android NDK and SDK, we will use the ones downloaded by Gecko in ~/.mozbuild/ + + - Install both the i686-linux-android and armv7-linux-androideabi rust + targets, as the APK will include native libraries with both architectures. + + - Don't install currently published version of cargo-apk (it doesn't support newer NDKs). + Instead, install the git master version like so: + cargo install --git https://github.com/rust-windowing/android-rs-glue cargo-apk + + - Consider adding ~/.mozbuild/android-sdk-linux/platform-tools to your path, for the adb commands below. + +Compiling and running: +---------------------- + + Compile wrench: + cd wrench + export ANDROID_HOME=$HOME/.mozbuild/android-sdk-linux # exact path may vary + export NDK_HOME=$HOME/.mozbuild/android-ndk-r17b # exact path may vary + cargo apk build + + Install the APK: + adb install -r ../target/android-artifacts/debug/apk/wrench.apk + + Set command line arguments and env vars for wrench: + adb shell + mkdir /sdcard/wrench + echo "load reftests/aa/rounded-rects.yaml" >/sdcard/wrench/args + echo "env: WRENCH_REFTEST_CONDITION_EMULATOR=1" >>/sdcard/wrench/args # If you're using the emulator + echo "env: WRENCH_REFTEST_CONDITION_DEVICE=1" >>/sdcard/wrench/args # If you're using a device + exit + + Push reftests (if you need these files for what you're doing): + adb push reftests /sdcard/wrench/ + + Run the application: + adb shell am start -n org.mozilla.wrench/android.app.NativeActivity + (or click the icon in the launcher) + +Release mode: +------------- + + Building in release does work as well. Use the following steps to compile wrench: + cd wrench + export ANDROID_HOME=$HOME/.mozbuild/android-sdk-linux # exact path may vary + export NDK_HOME=$HOME/.mozbuild/android-ndk # exact path may vary + cargo apk build --release + + Now the APK at ../target/android-artifacts/release/apk/wrench.apk + should be signed and installable (you may need to uninstall the debug APK first if you + have that installed). + +Running reftests like a boss (on a local emulator): +--------------------------------------------------- + + First, compile wrench as described above (debug mode). + Then, from the root gecko source dir, run: + ./mach python testing/mozharness/scripts/android_wrench.py --config testing/mozharness/configs/android/wrench.py + This will automatically do the following: + - Download the blessed android AVDs from taskcluster + - Start the emulator (using your ~/.mozbuild/android-sdk-linux emulator binaries) + - Install the debug APK (from gfx/wr/wrench/target/android-artifacts/debug/apk/wrench.apk) + - Copy the reftests to the sdcard + - Write an args file to the sdcard + - Run wrench + - Wait for wrench to finish running + - Scrape the logcat for reftest output + Other logs (e.g. full logcat) can be found in your ~/.wrench/logs folder. Note that + this will also leave the android emulator running, so repeating the command will be + even faster the next time around as it won't need to redownload the AVDs or restart + the emulator. It will reinstall the APK and re-push the reftests folder though. + + If you want to use a release APK (runs much faster), build it as per the "Release mode" + instructions above and set the WRENCH_APK env var to point to the APK: + to point to it: + export WRENCH_APK=gfx/wr/target/android-artifacts/release/apk/wrench.apk + ./mach python testing/mozharness/scripts/android_wrench.py --config testing/mozharness/configs/android/wrench.py + +Running reftests like a boss (on a local device): +------------------------------------------------- + + Same steps as running on a local emulator, except you need to do this: + export DEVICE_SERIAL= + before running the `./mach python` command. You can get the serial of + your device by running `adb devices` with the device plugged in. When running + on a device, the android_emulator_wrench.py script will skip the steps to + download the AVDs and start the emulator. diff --git a/third_party/webrender/wrench/benchmarks/aligned-gradient.yaml b/third_party/webrender/wrench/benchmarks/aligned-gradient.yaml new file mode 100644 index 00000000000..b0e2f0a6452 --- /dev/null +++ b/third_party/webrender/wrench/benchmarks/aligned-gradient.yaml @@ -0,0 +1,62 @@ +root: + items: + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 0, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 0, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 0, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 0, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 0, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 0, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 0, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 0, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 0, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 0, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false diff --git a/third_party/webrender/wrench/benchmarks/benchmarks.list b/third_party/webrender/wrench/benchmarks/benchmarks.list new file mode 100644 index 00000000000..c7235dcfd28 --- /dev/null +++ b/third_party/webrender/wrench/benchmarks/benchmarks.list @@ -0,0 +1,11 @@ +aligned-gradient.yaml +unaligned-gradient.yaml +simple-batching.yaml +large-boxshadow-ellipse.yaml +large-boxshadow-ellipse-2.yaml +large-clip-rect.yaml +transforms-simple.yaml +text-rendering.yaml +many-images.yaml +large-blur-radius.yaml + diff --git a/third_party/webrender/wrench/benchmarks/box-shadow-large.yaml b/third_party/webrender/wrench/benchmarks/box-shadow-large.yaml new file mode 100644 index 00000000000..83c8ce53269 --- /dev/null +++ b/third_party/webrender/wrench/benchmarks/box-shadow-large.yaml @@ -0,0 +1,17 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + - type: box-shadow + bounds: [ 100, 100, 800, 800 ] + blur-radius: 20 + border-radius: { + top-left: [20, 20], + top-right: [10, 10], + bottom-left: [25, 25], + bottom-right: [100, 100], + } + color: blue + clip-mode: outset diff --git a/third_party/webrender/wrench/benchmarks/clip-clear.yaml b/third_party/webrender/wrench/benchmarks/clip-clear.yaml new file mode 100644 index 00000000000..f23440a3f11 --- /dev/null +++ b/third_party/webrender/wrench/benchmarks/clip-clear.yaml @@ -0,0 +1,48 @@ +# Benchmark to test the cost of clears on a clip mask target that +# is large but has low usage. +--- +root: + items: + - + bounds: 0 0 1000 1000 + type: stacking-context + items: + - type: clip + bounds: [50, 50, 300, 300] + complex: + - rect: [50, 50, 300, 300] + radius: 50 + items: + - type: rect + bounds: 50 50 300 300 + color: red + - type: rect + bounds: 50 50 300 300 + color: red + - type: rect + bounds: 50 50 300 300 + color: red + - type: rect + bounds: 50 50 300 300 + color: red + - type: rect + bounds: 50 50 300 300 + color: red + - type: rect + bounds: 50 50 300 300 + color: red + - type: rect + bounds: 50 50 300 300 + color: red + - type: rect + bounds: 50 50 300 300 + color: red + - type: rect + bounds: 50 50 300 300 + color: red + - type: rect + bounds: 50 50 300 300 + color: red + - type: rect + bounds: 50 50 300 300 + color: red diff --git a/third_party/webrender/wrench/benchmarks/large-blur-radius.yaml b/third_party/webrender/wrench/benchmarks/large-blur-radius.yaml new file mode 100644 index 00000000000..927e4237656 --- /dev/null +++ b/third_party/webrender/wrench/benchmarks/large-blur-radius.yaml @@ -0,0 +1,10 @@ +--- +root: + items: + - type: stacking-context + bounds: 100 100 1024 1024 + filters: blur(100) + items: + - type: rect + bounds: 0 0 1024 1024 + color: red diff --git a/third_party/webrender/wrench/benchmarks/large-boxshadow-ellipse-2.yaml b/third_party/webrender/wrench/benchmarks/large-boxshadow-ellipse-2.yaml new file mode 100644 index 00000000000..bcb57434ece --- /dev/null +++ b/third_party/webrender/wrench/benchmarks/large-boxshadow-ellipse-2.yaml @@ -0,0 +1,15 @@ +--- +root: + items: + - type: box-shadow + bounds: [ 0, 0, 1024, 1024 ] + color: green + clip-mode: inset + blur-radius: 10000 + border-radius: { + top-left: [500, 700], + top-right: [500, 700], + bottom-left: [600, 400], + bottom-right: [600, 400], + } + diff --git a/third_party/webrender/wrench/benchmarks/large-boxshadow-ellipse.yaml b/third_party/webrender/wrench/benchmarks/large-boxshadow-ellipse.yaml new file mode 100644 index 00000000000..a9670f78fab --- /dev/null +++ b/third_party/webrender/wrench/benchmarks/large-boxshadow-ellipse.yaml @@ -0,0 +1,15 @@ +--- +root: + items: + - type: box-shadow + bounds: [ 0, 0, 1024, 1024 ] + color: green + clip-mode: outset + blur-radius: 10 + border-radius: { + top-left: [10, 30], + top-right: [10, 30], + bottom-left: [30, 10], + bottom-right: [30, 10], + } + diff --git a/third_party/webrender/wrench/benchmarks/large-clip-rect.yaml b/third_party/webrender/wrench/benchmarks/large-clip-rect.yaml new file mode 100644 index 00000000000..7bd8f1f6a55 --- /dev/null +++ b/third_party/webrender/wrench/benchmarks/large-clip-rect.yaml @@ -0,0 +1,33 @@ +--- +root: + items: + - type: clip + bounds: [0, 0, 1024, 1024] + complex: + - rect: [0, 0, 1024, 1024] + radius: 16 + items: + - type: rect + bounds: [0, 0, 1024, 1024] + color: red + - type: rect + bounds: [0, 0, 1024, 1024] + color: red + - type: rect + bounds: [0, 0, 1024, 1024] + color: red + - type: rect + bounds: [0, 0, 1024, 1024] + color: red + - type: rect + bounds: [0, 0, 1024, 1024] + color: red + - type: rect + bounds: [0, 0, 1024, 1024] + color: red + - type: rect + bounds: [0, 0, 1024, 1024] + color: red + - type: rect + bounds: [0, 0, 1024, 1024] + color: red diff --git a/third_party/webrender/wrench/benchmarks/many-box-shadows.yaml b/third_party/webrender/wrench/benchmarks/many-box-shadows.yaml new file mode 100644 index 00000000000..424c8379a23 --- /dev/null +++ b/third_party/webrender/wrench/benchmarks/many-box-shadows.yaml @@ -0,0 +1,125 @@ +--- +root: + items: + - + type: "stacking-context" + items: + - + bounds: [68, 431, 539, 805] + "clip-rect": [5, 390, 665, 932] + "clip-and-scroll": 0 + "backface-visible": true + type: "box-shadow" + "box-bounds": [68, 431, 539, 805] + offset: [0, 22.5] + color: 0 0 0 0.1020 + "blur-radius": 45 + "spread-radius": 0 + "clip-mode": outset + - + bounds: [668, 431, 539, 805] + "clip-rect": [605, 390, 665, 932] + "clip-and-scroll": 0 + "backface-visible": true + type: "box-shadow" + "box-bounds": [668, 431, 539, 805] + offset: [0, 22.5] + color: 0 0 0 0.1020 + "blur-radius": 45 + "spread-radius": 0 + "clip-mode": outset + - + bounds: [1268, 431, 540, 805] + "clip-rect": [1205, 390, 666, 932] + "clip-and-scroll": 0 + "backface-visible": true + type: "box-shadow" + "box-bounds": [1268, 431, 540, 805] + offset: [0, 22.5] + color: 0 0 0 0.1020 + "blur-radius": 45 + "spread-radius": 0 + "clip-mode": outset + - + bounds: [68, 1284, 539, 771] + "clip-rect": [5, 1244, 665, 897] + "clip-and-scroll": 0 + "backface-visible": true + type: "box-shadow" + "box-bounds": [68, 1284, 539, 771] + offset: [0, 22.5] + color: 0 0 0 0.1020 + "blur-radius": 45 + "spread-radius": 0 + "clip-mode": outset + - + bounds: [668, 1284, 539, 771] + "clip-rect": [605, 1244, 665, 897] + "clip-and-scroll": 0 + "backface-visible": true + type: "box-shadow" + "box-bounds": [668, 1284, 539, 771] + offset: [0, 22.5] + color: 0 0 0 0.1020 + "blur-radius": 45 + "spread-radius": 0 + "clip-mode": outset + - + bounds: [1268, 1284, 540, 771] + "clip-rect": [1205, 1244, 666, 897] + "clip-and-scroll": 0 + "backface-visible": true + type: "box-shadow" + "box-bounds": [1268, 1284, 540, 771] + offset: [0, 22.5] + color: 0 0 0 0.1020 + "blur-radius": 45 + "spread-radius": 0 + "clip-mode": outset + - + bounds: [68, 2103, 539, 839] + "clip-rect": [5, 2063, 665, 965] + "clip-and-scroll": 0 + "backface-visible": true + type: "box-shadow" + "box-bounds": [68, 2103, 539, 839] + offset: [0, 22.5] + color: 0 0 0 0.1020 + "blur-radius": 45 + "spread-radius": 0 + "clip-mode": outset + - + bounds: [668, 2103, 539, 839] + "clip-rect": [605, 2063, 665, 965] + "clip-and-scroll": 0 + "backface-visible": true + type: "box-shadow" + "box-bounds": [668, 2103, 539, 839] + offset: [0, 22.5] + color: 0 0 0 0.1020 + "blur-radius": 45 + "spread-radius": 0 + "clip-mode": outset + - + bounds: [1268, 2103, 540, 839] + "clip-rect": [1205, 2063, 666, 965] + "clip-and-scroll": 0 + "backface-visible": true + type: "box-shadow" + "box-bounds": [1268, 2103, 540, 839] + offset: [0, 22.5] + color: 0 0 0 0.1020 + "blur-radius": 45 + "spread-radius": 0 + "clip-mode": outset + - + bounds: [0, 0, 1875, 154] + "clip-rect": [-2, 0, 1879, 158] + "clip-and-scroll": 0 + type: "box-shadow" + "box-bounds": [0, 0, 1875, 154] + offset: [0, 1.5] + color: 0 0 0 0.4000 + "blur-radius": 1.5 + "spread-radius": 0 + "clip-mode": outset diff --git a/third_party/webrender/wrench/benchmarks/many-images.yaml b/third_party/webrender/wrench/benchmarks/many-images.yaml new file mode 100644 index 00000000000..963fdef8814 --- /dev/null +++ b/third_party/webrender/wrench/benchmarks/many-images.yaml @@ -0,0 +1,16386 @@ +root: + items: + - image: solid-color(0, 0, 0, 255, 8, 8) + bounds: 0 0 8 8 + - image: solid-color(1, 0, 0, 255, 8, 8) + bounds: 8 0 8 8 + - image: solid-color(2, 0, 0, 255, 8, 8) + bounds: 16 0 8 8 + - image: solid-color(3, 0, 0, 255, 8, 8) + bounds: 24 0 8 8 + - image: solid-color(4, 0, 0, 255, 8, 8) + bounds: 32 0 8 8 + - image: solid-color(5, 0, 0, 255, 8, 8) + bounds: 40 0 8 8 + - image: solid-color(6, 0, 0, 255, 8, 8) + bounds: 48 0 8 8 + - image: solid-color(7, 0, 0, 255, 8, 8) + bounds: 56 0 8 8 + - image: solid-color(8, 0, 0, 255, 8, 8) + bounds: 64 0 8 8 + - image: solid-color(9, 0, 0, 255, 8, 8) + bounds: 72 0 8 8 + - image: solid-color(10, 0, 0, 255, 8, 8) + bounds: 80 0 8 8 + - image: solid-color(11, 0, 0, 255, 8, 8) + bounds: 88 0 8 8 + - image: solid-color(12, 0, 0, 255, 8, 8) + bounds: 96 0 8 8 + - image: solid-color(13, 0, 0, 255, 8, 8) + bounds: 104 0 8 8 + - image: solid-color(14, 0, 0, 255, 8, 8) + bounds: 112 0 8 8 + - image: solid-color(15, 0, 0, 255, 8, 8) + bounds: 120 0 8 8 + - image: solid-color(16, 0, 0, 255, 8, 8) + bounds: 128 0 8 8 + - image: solid-color(17, 0, 0, 255, 8, 8) + bounds: 136 0 8 8 + - image: solid-color(18, 0, 0, 255, 8, 8) + bounds: 144 0 8 8 + - image: solid-color(19, 0, 0, 255, 8, 8) + bounds: 152 0 8 8 + - image: solid-color(20, 0, 0, 255, 8, 8) + bounds: 160 0 8 8 + - image: solid-color(21, 0, 0, 255, 8, 8) + bounds: 168 0 8 8 + - image: solid-color(22, 0, 0, 255, 8, 8) + bounds: 176 0 8 8 + - image: solid-color(23, 0, 0, 255, 8, 8) + bounds: 184 0 8 8 + - image: solid-color(24, 0, 0, 255, 8, 8) + bounds: 192 0 8 8 + - image: solid-color(25, 0, 0, 255, 8, 8) + bounds: 200 0 8 8 + - image: solid-color(26, 0, 0, 255, 8, 8) + bounds: 208 0 8 8 + - image: solid-color(27, 0, 0, 255, 8, 8) + bounds: 216 0 8 8 + - image: solid-color(28, 0, 0, 255, 8, 8) + bounds: 224 0 8 8 + - image: solid-color(29, 0, 0, 255, 8, 8) + bounds: 232 0 8 8 + - image: solid-color(30, 0, 0, 255, 8, 8) + bounds: 240 0 8 8 + - image: solid-color(31, 0, 0, 255, 8, 8) + bounds: 248 0 8 8 + - image: solid-color(32, 0, 0, 255, 8, 8) + bounds: 256 0 8 8 + - image: solid-color(33, 0, 0, 255, 8, 8) + bounds: 264 0 8 8 + - image: solid-color(34, 0, 0, 255, 8, 8) + bounds: 272 0 8 8 + - image: solid-color(35, 0, 0, 255, 8, 8) + bounds: 280 0 8 8 + - image: solid-color(36, 0, 0, 255, 8, 8) + bounds: 288 0 8 8 + - image: solid-color(37, 0, 0, 255, 8, 8) + bounds: 296 0 8 8 + - image: solid-color(38, 0, 0, 255, 8, 8) + bounds: 304 0 8 8 + - image: solid-color(39, 0, 0, 255, 8, 8) + bounds: 312 0 8 8 + - image: solid-color(40, 0, 0, 255, 8, 8) + bounds: 320 0 8 8 + - image: solid-color(41, 0, 0, 255, 8, 8) + bounds: 328 0 8 8 + - image: solid-color(42, 0, 0, 255, 8, 8) + bounds: 336 0 8 8 + - image: solid-color(43, 0, 0, 255, 8, 8) + bounds: 344 0 8 8 + - image: solid-color(44, 0, 0, 255, 8, 8) + bounds: 352 0 8 8 + - image: solid-color(45, 0, 0, 255, 8, 8) + bounds: 360 0 8 8 + - image: solid-color(46, 0, 0, 255, 8, 8) + bounds: 368 0 8 8 + - image: solid-color(47, 0, 0, 255, 8, 8) + bounds: 376 0 8 8 + - image: solid-color(48, 0, 0, 255, 8, 8) + bounds: 384 0 8 8 + - image: solid-color(49, 0, 0, 255, 8, 8) + bounds: 392 0 8 8 + - image: solid-color(50, 0, 0, 255, 8, 8) + bounds: 400 0 8 8 + - image: solid-color(51, 0, 0, 255, 8, 8) + bounds: 408 0 8 8 + - image: solid-color(52, 0, 0, 255, 8, 8) + bounds: 416 0 8 8 + - image: solid-color(53, 0, 0, 255, 8, 8) + bounds: 424 0 8 8 + - image: solid-color(54, 0, 0, 255, 8, 8) + bounds: 432 0 8 8 + - image: solid-color(55, 0, 0, 255, 8, 8) + bounds: 440 0 8 8 + - image: solid-color(56, 0, 0, 255, 8, 8) + bounds: 448 0 8 8 + - image: solid-color(57, 0, 0, 255, 8, 8) + bounds: 456 0 8 8 + - image: solid-color(58, 0, 0, 255, 8, 8) + bounds: 464 0 8 8 + - image: solid-color(59, 0, 0, 255, 8, 8) + bounds: 472 0 8 8 + - image: solid-color(60, 0, 0, 255, 8, 8) + bounds: 480 0 8 8 + - image: solid-color(61, 0, 0, 255, 8, 8) + bounds: 488 0 8 8 + - image: solid-color(62, 0, 0, 255, 8, 8) + bounds: 496 0 8 8 + - image: solid-color(63, 0, 0, 255, 8, 8) + bounds: 504 0 8 8 + - image: solid-color(64, 0, 0, 255, 8, 8) + bounds: 512 0 8 8 + - image: solid-color(65, 0, 0, 255, 8, 8) + bounds: 520 0 8 8 + - image: solid-color(66, 0, 0, 255, 8, 8) + bounds: 528 0 8 8 + - image: solid-color(67, 0, 0, 255, 8, 8) + bounds: 536 0 8 8 + - image: solid-color(68, 0, 0, 255, 8, 8) + bounds: 544 0 8 8 + - image: solid-color(69, 0, 0, 255, 8, 8) + bounds: 552 0 8 8 + - image: solid-color(70, 0, 0, 255, 8, 8) + bounds: 560 0 8 8 + - image: solid-color(71, 0, 0, 255, 8, 8) + bounds: 568 0 8 8 + - image: solid-color(72, 0, 0, 255, 8, 8) + bounds: 576 0 8 8 + - image: solid-color(73, 0, 0, 255, 8, 8) + bounds: 584 0 8 8 + - image: solid-color(74, 0, 0, 255, 8, 8) + bounds: 592 0 8 8 + - image: solid-color(75, 0, 0, 255, 8, 8) + bounds: 600 0 8 8 + - image: solid-color(76, 0, 0, 255, 8, 8) + bounds: 608 0 8 8 + - image: solid-color(77, 0, 0, 255, 8, 8) + bounds: 616 0 8 8 + - image: solid-color(78, 0, 0, 255, 8, 8) + bounds: 624 0 8 8 + - image: solid-color(79, 0, 0, 255, 8, 8) + bounds: 632 0 8 8 + - image: solid-color(80, 0, 0, 255, 8, 8) + bounds: 640 0 8 8 + - image: solid-color(81, 0, 0, 255, 8, 8) + bounds: 648 0 8 8 + - image: solid-color(82, 0, 0, 255, 8, 8) + bounds: 656 0 8 8 + - image: solid-color(83, 0, 0, 255, 8, 8) + bounds: 664 0 8 8 + - image: solid-color(84, 0, 0, 255, 8, 8) + bounds: 672 0 8 8 + - image: solid-color(85, 0, 0, 255, 8, 8) + bounds: 680 0 8 8 + - image: solid-color(86, 0, 0, 255, 8, 8) + bounds: 688 0 8 8 + - image: solid-color(87, 0, 0, 255, 8, 8) + bounds: 696 0 8 8 + - image: solid-color(88, 0, 0, 255, 8, 8) + bounds: 704 0 8 8 + - image: solid-color(89, 0, 0, 255, 8, 8) + bounds: 712 0 8 8 + - image: solid-color(90, 0, 0, 255, 8, 8) + bounds: 720 0 8 8 + - image: solid-color(91, 0, 0, 255, 8, 8) + bounds: 728 0 8 8 + - image: solid-color(92, 0, 0, 255, 8, 8) + bounds: 736 0 8 8 + - image: solid-color(93, 0, 0, 255, 8, 8) + bounds: 744 0 8 8 + - image: solid-color(94, 0, 0, 255, 8, 8) + bounds: 752 0 8 8 + - image: solid-color(95, 0, 0, 255, 8, 8) + bounds: 760 0 8 8 + - image: solid-color(96, 0, 0, 255, 8, 8) + bounds: 768 0 8 8 + - image: solid-color(97, 0, 0, 255, 8, 8) + bounds: 776 0 8 8 + - image: solid-color(98, 0, 0, 255, 8, 8) + bounds: 784 0 8 8 + - image: solid-color(99, 0, 0, 255, 8, 8) + bounds: 792 0 8 8 + - image: solid-color(100, 0, 0, 255, 8, 8) + bounds: 800 0 8 8 + - image: solid-color(101, 0, 0, 255, 8, 8) + bounds: 808 0 8 8 + - image: solid-color(102, 0, 0, 255, 8, 8) + bounds: 816 0 8 8 + - image: solid-color(103, 0, 0, 255, 8, 8) + bounds: 824 0 8 8 + - image: solid-color(104, 0, 0, 255, 8, 8) + bounds: 832 0 8 8 + - image: solid-color(105, 0, 0, 255, 8, 8) + bounds: 840 0 8 8 + - image: solid-color(106, 0, 0, 255, 8, 8) + bounds: 848 0 8 8 + - image: solid-color(107, 0, 0, 255, 8, 8) + bounds: 856 0 8 8 + - image: solid-color(108, 0, 0, 255, 8, 8) + bounds: 864 0 8 8 + - image: solid-color(109, 0, 0, 255, 8, 8) + bounds: 872 0 8 8 + - image: solid-color(110, 0, 0, 255, 8, 8) + bounds: 880 0 8 8 + - image: solid-color(111, 0, 0, 255, 8, 8) + bounds: 888 0 8 8 + - image: solid-color(112, 0, 0, 255, 8, 8) + bounds: 896 0 8 8 + - image: solid-color(113, 0, 0, 255, 8, 8) + bounds: 904 0 8 8 + - image: solid-color(114, 0, 0, 255, 8, 8) + bounds: 912 0 8 8 + - image: solid-color(115, 0, 0, 255, 8, 8) + bounds: 920 0 8 8 + - image: solid-color(116, 0, 0, 255, 8, 8) + bounds: 928 0 8 8 + - image: solid-color(117, 0, 0, 255, 8, 8) + bounds: 936 0 8 8 + - image: solid-color(118, 0, 0, 255, 8, 8) + bounds: 944 0 8 8 + - image: solid-color(119, 0, 0, 255, 8, 8) + bounds: 952 0 8 8 + - image: solid-color(120, 0, 0, 255, 8, 8) + bounds: 960 0 8 8 + - image: solid-color(121, 0, 0, 255, 8, 8) + bounds: 968 0 8 8 + - image: solid-color(122, 0, 0, 255, 8, 8) + bounds: 976 0 8 8 + - image: solid-color(123, 0, 0, 255, 8, 8) + bounds: 984 0 8 8 + - image: solid-color(124, 0, 0, 255, 8, 8) + bounds: 992 0 8 8 + - image: solid-color(125, 0, 0, 255, 8, 8) + bounds: 1000 0 8 8 + - image: solid-color(126, 0, 0, 255, 8, 8) + bounds: 1008 0 8 8 + - image: solid-color(127, 0, 0, 255, 8, 8) + bounds: 1016 0 8 8 + - image: solid-color(0, 1, 0, 255, 8, 8) + bounds: 0 8 8 8 + - image: solid-color(1, 1, 0, 255, 8, 8) + bounds: 8 8 8 8 + - image: solid-color(2, 1, 0, 255, 8, 8) + bounds: 16 8 8 8 + - image: solid-color(3, 1, 0, 255, 8, 8) + bounds: 24 8 8 8 + - image: solid-color(4, 1, 0, 255, 8, 8) + bounds: 32 8 8 8 + - image: solid-color(5, 1, 0, 255, 8, 8) + bounds: 40 8 8 8 + - image: solid-color(6, 1, 0, 255, 8, 8) + bounds: 48 8 8 8 + - image: solid-color(7, 1, 0, 255, 8, 8) + bounds: 56 8 8 8 + - image: solid-color(8, 1, 0, 255, 8, 8) + bounds: 64 8 8 8 + - image: solid-color(9, 1, 0, 255, 8, 8) + bounds: 72 8 8 8 + - image: solid-color(10, 1, 0, 255, 8, 8) + bounds: 80 8 8 8 + - image: solid-color(11, 1, 0, 255, 8, 8) + bounds: 88 8 8 8 + - image: solid-color(12, 1, 0, 255, 8, 8) + bounds: 96 8 8 8 + - image: solid-color(13, 1, 0, 255, 8, 8) + bounds: 104 8 8 8 + - image: solid-color(14, 1, 0, 255, 8, 8) + bounds: 112 8 8 8 + - image: solid-color(15, 1, 0, 255, 8, 8) + bounds: 120 8 8 8 + - image: solid-color(16, 1, 0, 255, 8, 8) + bounds: 128 8 8 8 + - image: solid-color(17, 1, 0, 255, 8, 8) + bounds: 136 8 8 8 + - image: solid-color(18, 1, 0, 255, 8, 8) + bounds: 144 8 8 8 + - image: solid-color(19, 1, 0, 255, 8, 8) + bounds: 152 8 8 8 + - image: solid-color(20, 1, 0, 255, 8, 8) + bounds: 160 8 8 8 + - image: solid-color(21, 1, 0, 255, 8, 8) + bounds: 168 8 8 8 + - image: solid-color(22, 1, 0, 255, 8, 8) + bounds: 176 8 8 8 + - image: solid-color(23, 1, 0, 255, 8, 8) + bounds: 184 8 8 8 + - image: solid-color(24, 1, 0, 255, 8, 8) + bounds: 192 8 8 8 + - image: solid-color(25, 1, 0, 255, 8, 8) + bounds: 200 8 8 8 + - image: solid-color(26, 1, 0, 255, 8, 8) + bounds: 208 8 8 8 + - image: solid-color(27, 1, 0, 255, 8, 8) + bounds: 216 8 8 8 + - image: solid-color(28, 1, 0, 255, 8, 8) + bounds: 224 8 8 8 + - image: solid-color(29, 1, 0, 255, 8, 8) + bounds: 232 8 8 8 + - image: solid-color(30, 1, 0, 255, 8, 8) + bounds: 240 8 8 8 + - image: solid-color(31, 1, 0, 255, 8, 8) + bounds: 248 8 8 8 + - image: solid-color(32, 1, 0, 255, 8, 8) + bounds: 256 8 8 8 + - image: solid-color(33, 1, 0, 255, 8, 8) + bounds: 264 8 8 8 + - image: solid-color(34, 1, 0, 255, 8, 8) + bounds: 272 8 8 8 + - image: solid-color(35, 1, 0, 255, 8, 8) + bounds: 280 8 8 8 + - image: solid-color(36, 1, 0, 255, 8, 8) + bounds: 288 8 8 8 + - image: solid-color(37, 1, 0, 255, 8, 8) + bounds: 296 8 8 8 + - image: solid-color(38, 1, 0, 255, 8, 8) + bounds: 304 8 8 8 + - image: solid-color(39, 1, 0, 255, 8, 8) + bounds: 312 8 8 8 + - image: solid-color(40, 1, 0, 255, 8, 8) + bounds: 320 8 8 8 + - image: solid-color(41, 1, 0, 255, 8, 8) + bounds: 328 8 8 8 + - image: solid-color(42, 1, 0, 255, 8, 8) + bounds: 336 8 8 8 + - image: solid-color(43, 1, 0, 255, 8, 8) + bounds: 344 8 8 8 + - image: solid-color(44, 1, 0, 255, 8, 8) + bounds: 352 8 8 8 + - image: solid-color(45, 1, 0, 255, 8, 8) + bounds: 360 8 8 8 + - image: solid-color(46, 1, 0, 255, 8, 8) + bounds: 368 8 8 8 + - image: solid-color(47, 1, 0, 255, 8, 8) + bounds: 376 8 8 8 + - image: solid-color(48, 1, 0, 255, 8, 8) + bounds: 384 8 8 8 + - image: solid-color(49, 1, 0, 255, 8, 8) + bounds: 392 8 8 8 + - image: solid-color(50, 1, 0, 255, 8, 8) + bounds: 400 8 8 8 + - image: solid-color(51, 1, 0, 255, 8, 8) + bounds: 408 8 8 8 + - image: solid-color(52, 1, 0, 255, 8, 8) + bounds: 416 8 8 8 + - image: solid-color(53, 1, 0, 255, 8, 8) + bounds: 424 8 8 8 + - image: solid-color(54, 1, 0, 255, 8, 8) + bounds: 432 8 8 8 + - image: solid-color(55, 1, 0, 255, 8, 8) + bounds: 440 8 8 8 + - image: solid-color(56, 1, 0, 255, 8, 8) + bounds: 448 8 8 8 + - image: solid-color(57, 1, 0, 255, 8, 8) + bounds: 456 8 8 8 + - image: solid-color(58, 1, 0, 255, 8, 8) + bounds: 464 8 8 8 + - image: solid-color(59, 1, 0, 255, 8, 8) + bounds: 472 8 8 8 + - image: solid-color(60, 1, 0, 255, 8, 8) + bounds: 480 8 8 8 + - image: solid-color(61, 1, 0, 255, 8, 8) + bounds: 488 8 8 8 + - image: solid-color(62, 1, 0, 255, 8, 8) + bounds: 496 8 8 8 + - image: solid-color(63, 1, 0, 255, 8, 8) + bounds: 504 8 8 8 + - image: solid-color(64, 1, 0, 255, 8, 8) + bounds: 512 8 8 8 + - image: solid-color(65, 1, 0, 255, 8, 8) + bounds: 520 8 8 8 + - image: solid-color(66, 1, 0, 255, 8, 8) + bounds: 528 8 8 8 + - image: solid-color(67, 1, 0, 255, 8, 8) + bounds: 536 8 8 8 + - image: solid-color(68, 1, 0, 255, 8, 8) + bounds: 544 8 8 8 + - image: solid-color(69, 1, 0, 255, 8, 8) + bounds: 552 8 8 8 + - image: solid-color(70, 1, 0, 255, 8, 8) + bounds: 560 8 8 8 + - image: solid-color(71, 1, 0, 255, 8, 8) + bounds: 568 8 8 8 + - image: solid-color(72, 1, 0, 255, 8, 8) + bounds: 576 8 8 8 + - image: solid-color(73, 1, 0, 255, 8, 8) + bounds: 584 8 8 8 + - image: solid-color(74, 1, 0, 255, 8, 8) + bounds: 592 8 8 8 + - image: solid-color(75, 1, 0, 255, 8, 8) + bounds: 600 8 8 8 + - image: solid-color(76, 1, 0, 255, 8, 8) + bounds: 608 8 8 8 + - image: solid-color(77, 1, 0, 255, 8, 8) + bounds: 616 8 8 8 + - image: solid-color(78, 1, 0, 255, 8, 8) + bounds: 624 8 8 8 + - image: solid-color(79, 1, 0, 255, 8, 8) + bounds: 632 8 8 8 + - image: solid-color(80, 1, 0, 255, 8, 8) + bounds: 640 8 8 8 + - image: solid-color(81, 1, 0, 255, 8, 8) + bounds: 648 8 8 8 + - image: solid-color(82, 1, 0, 255, 8, 8) + bounds: 656 8 8 8 + - image: solid-color(83, 1, 0, 255, 8, 8) + bounds: 664 8 8 8 + - image: solid-color(84, 1, 0, 255, 8, 8) + bounds: 672 8 8 8 + - image: solid-color(85, 1, 0, 255, 8, 8) + bounds: 680 8 8 8 + - image: solid-color(86, 1, 0, 255, 8, 8) + bounds: 688 8 8 8 + - image: solid-color(87, 1, 0, 255, 8, 8) + bounds: 696 8 8 8 + - image: solid-color(88, 1, 0, 255, 8, 8) + bounds: 704 8 8 8 + - image: solid-color(89, 1, 0, 255, 8, 8) + bounds: 712 8 8 8 + - image: solid-color(90, 1, 0, 255, 8, 8) + bounds: 720 8 8 8 + - image: solid-color(91, 1, 0, 255, 8, 8) + bounds: 728 8 8 8 + - image: solid-color(92, 1, 0, 255, 8, 8) + bounds: 736 8 8 8 + - image: solid-color(93, 1, 0, 255, 8, 8) + bounds: 744 8 8 8 + - image: solid-color(94, 1, 0, 255, 8, 8) + bounds: 752 8 8 8 + - image: solid-color(95, 1, 0, 255, 8, 8) + bounds: 760 8 8 8 + - image: solid-color(96, 1, 0, 255, 8, 8) + bounds: 768 8 8 8 + - image: solid-color(97, 1, 0, 255, 8, 8) + bounds: 776 8 8 8 + - image: solid-color(98, 1, 0, 255, 8, 8) + bounds: 784 8 8 8 + - image: solid-color(99, 1, 0, 255, 8, 8) + bounds: 792 8 8 8 + - image: solid-color(100, 1, 0, 255, 8, 8) + bounds: 800 8 8 8 + - image: solid-color(101, 1, 0, 255, 8, 8) + bounds: 808 8 8 8 + - image: solid-color(102, 1, 0, 255, 8, 8) + bounds: 816 8 8 8 + - image: solid-color(103, 1, 0, 255, 8, 8) + bounds: 824 8 8 8 + - image: solid-color(104, 1, 0, 255, 8, 8) + bounds: 832 8 8 8 + - image: solid-color(105, 1, 0, 255, 8, 8) + bounds: 840 8 8 8 + - image: solid-color(106, 1, 0, 255, 8, 8) + bounds: 848 8 8 8 + - image: solid-color(107, 1, 0, 255, 8, 8) + bounds: 856 8 8 8 + - image: solid-color(108, 1, 0, 255, 8, 8) + bounds: 864 8 8 8 + - image: solid-color(109, 1, 0, 255, 8, 8) + bounds: 872 8 8 8 + - image: solid-color(110, 1, 0, 255, 8, 8) + bounds: 880 8 8 8 + - image: solid-color(111, 1, 0, 255, 8, 8) + bounds: 888 8 8 8 + - image: solid-color(112, 1, 0, 255, 8, 8) + bounds: 896 8 8 8 + - image: solid-color(113, 1, 0, 255, 8, 8) + bounds: 904 8 8 8 + - image: solid-color(114, 1, 0, 255, 8, 8) + bounds: 912 8 8 8 + - image: solid-color(115, 1, 0, 255, 8, 8) + bounds: 920 8 8 8 + - image: solid-color(116, 1, 0, 255, 8, 8) + bounds: 928 8 8 8 + - image: solid-color(117, 1, 0, 255, 8, 8) + bounds: 936 8 8 8 + - image: solid-color(118, 1, 0, 255, 8, 8) + bounds: 944 8 8 8 + - image: solid-color(119, 1, 0, 255, 8, 8) + bounds: 952 8 8 8 + - image: solid-color(120, 1, 0, 255, 8, 8) + bounds: 960 8 8 8 + - image: solid-color(121, 1, 0, 255, 8, 8) + bounds: 968 8 8 8 + - image: solid-color(122, 1, 0, 255, 8, 8) + bounds: 976 8 8 8 + - image: solid-color(123, 1, 0, 255, 8, 8) + bounds: 984 8 8 8 + - image: solid-color(124, 1, 0, 255, 8, 8) + bounds: 992 8 8 8 + - image: solid-color(125, 1, 0, 255, 8, 8) + bounds: 1000 8 8 8 + - image: solid-color(126, 1, 0, 255, 8, 8) + bounds: 1008 8 8 8 + - image: solid-color(127, 1, 0, 255, 8, 8) + bounds: 1016 8 8 8 + - image: solid-color(0, 2, 0, 255, 8, 8) + bounds: 0 16 8 8 + - image: solid-color(1, 2, 0, 255, 8, 8) + bounds: 8 16 8 8 + - image: solid-color(2, 2, 0, 255, 8, 8) + bounds: 16 16 8 8 + - image: solid-color(3, 2, 0, 255, 8, 8) + bounds: 24 16 8 8 + - image: solid-color(4, 2, 0, 255, 8, 8) + bounds: 32 16 8 8 + - image: solid-color(5, 2, 0, 255, 8, 8) + bounds: 40 16 8 8 + - image: solid-color(6, 2, 0, 255, 8, 8) + bounds: 48 16 8 8 + - image: solid-color(7, 2, 0, 255, 8, 8) + bounds: 56 16 8 8 + - image: solid-color(8, 2, 0, 255, 8, 8) + bounds: 64 16 8 8 + - image: solid-color(9, 2, 0, 255, 8, 8) + bounds: 72 16 8 8 + - image: solid-color(10, 2, 0, 255, 8, 8) + bounds: 80 16 8 8 + - image: solid-color(11, 2, 0, 255, 8, 8) + bounds: 88 16 8 8 + - image: solid-color(12, 2, 0, 255, 8, 8) + bounds: 96 16 8 8 + - image: solid-color(13, 2, 0, 255, 8, 8) + bounds: 104 16 8 8 + - image: solid-color(14, 2, 0, 255, 8, 8) + bounds: 112 16 8 8 + - image: solid-color(15, 2, 0, 255, 8, 8) + bounds: 120 16 8 8 + - image: solid-color(16, 2, 0, 255, 8, 8) + bounds: 128 16 8 8 + - image: solid-color(17, 2, 0, 255, 8, 8) + bounds: 136 16 8 8 + - image: solid-color(18, 2, 0, 255, 8, 8) + bounds: 144 16 8 8 + - image: solid-color(19, 2, 0, 255, 8, 8) + bounds: 152 16 8 8 + - image: solid-color(20, 2, 0, 255, 8, 8) + bounds: 160 16 8 8 + - image: solid-color(21, 2, 0, 255, 8, 8) + bounds: 168 16 8 8 + - image: solid-color(22, 2, 0, 255, 8, 8) + bounds: 176 16 8 8 + - image: solid-color(23, 2, 0, 255, 8, 8) + bounds: 184 16 8 8 + - image: solid-color(24, 2, 0, 255, 8, 8) + bounds: 192 16 8 8 + - image: solid-color(25, 2, 0, 255, 8, 8) + bounds: 200 16 8 8 + - image: solid-color(26, 2, 0, 255, 8, 8) + bounds: 208 16 8 8 + - image: solid-color(27, 2, 0, 255, 8, 8) + bounds: 216 16 8 8 + - image: solid-color(28, 2, 0, 255, 8, 8) + bounds: 224 16 8 8 + - image: solid-color(29, 2, 0, 255, 8, 8) + bounds: 232 16 8 8 + - image: solid-color(30, 2, 0, 255, 8, 8) + bounds: 240 16 8 8 + - image: solid-color(31, 2, 0, 255, 8, 8) + bounds: 248 16 8 8 + - image: solid-color(32, 2, 0, 255, 8, 8) + bounds: 256 16 8 8 + - image: solid-color(33, 2, 0, 255, 8, 8) + bounds: 264 16 8 8 + - image: solid-color(34, 2, 0, 255, 8, 8) + bounds: 272 16 8 8 + - image: solid-color(35, 2, 0, 255, 8, 8) + bounds: 280 16 8 8 + - image: solid-color(36, 2, 0, 255, 8, 8) + bounds: 288 16 8 8 + - image: solid-color(37, 2, 0, 255, 8, 8) + bounds: 296 16 8 8 + - image: solid-color(38, 2, 0, 255, 8, 8) + bounds: 304 16 8 8 + - image: solid-color(39, 2, 0, 255, 8, 8) + bounds: 312 16 8 8 + - image: solid-color(40, 2, 0, 255, 8, 8) + bounds: 320 16 8 8 + - image: solid-color(41, 2, 0, 255, 8, 8) + bounds: 328 16 8 8 + - image: solid-color(42, 2, 0, 255, 8, 8) + bounds: 336 16 8 8 + - image: solid-color(43, 2, 0, 255, 8, 8) + bounds: 344 16 8 8 + - image: solid-color(44, 2, 0, 255, 8, 8) + bounds: 352 16 8 8 + - image: solid-color(45, 2, 0, 255, 8, 8) + bounds: 360 16 8 8 + - image: solid-color(46, 2, 0, 255, 8, 8) + bounds: 368 16 8 8 + - image: solid-color(47, 2, 0, 255, 8, 8) + bounds: 376 16 8 8 + - image: solid-color(48, 2, 0, 255, 8, 8) + bounds: 384 16 8 8 + - image: solid-color(49, 2, 0, 255, 8, 8) + bounds: 392 16 8 8 + - image: solid-color(50, 2, 0, 255, 8, 8) + bounds: 400 16 8 8 + - image: solid-color(51, 2, 0, 255, 8, 8) + bounds: 408 16 8 8 + - image: solid-color(52, 2, 0, 255, 8, 8) + bounds: 416 16 8 8 + - image: solid-color(53, 2, 0, 255, 8, 8) + bounds: 424 16 8 8 + - image: solid-color(54, 2, 0, 255, 8, 8) + bounds: 432 16 8 8 + - image: solid-color(55, 2, 0, 255, 8, 8) + bounds: 440 16 8 8 + - image: solid-color(56, 2, 0, 255, 8, 8) + bounds: 448 16 8 8 + - image: solid-color(57, 2, 0, 255, 8, 8) + bounds: 456 16 8 8 + - image: solid-color(58, 2, 0, 255, 8, 8) + bounds: 464 16 8 8 + - image: solid-color(59, 2, 0, 255, 8, 8) + bounds: 472 16 8 8 + - image: solid-color(60, 2, 0, 255, 8, 8) + bounds: 480 16 8 8 + - image: solid-color(61, 2, 0, 255, 8, 8) + bounds: 488 16 8 8 + - image: solid-color(62, 2, 0, 255, 8, 8) + bounds: 496 16 8 8 + - image: solid-color(63, 2, 0, 255, 8, 8) + bounds: 504 16 8 8 + - image: solid-color(64, 2, 0, 255, 8, 8) + bounds: 512 16 8 8 + - image: solid-color(65, 2, 0, 255, 8, 8) + bounds: 520 16 8 8 + - image: solid-color(66, 2, 0, 255, 8, 8) + bounds: 528 16 8 8 + - image: solid-color(67, 2, 0, 255, 8, 8) + bounds: 536 16 8 8 + - image: solid-color(68, 2, 0, 255, 8, 8) + bounds: 544 16 8 8 + - image: solid-color(69, 2, 0, 255, 8, 8) + bounds: 552 16 8 8 + - image: solid-color(70, 2, 0, 255, 8, 8) + bounds: 560 16 8 8 + - image: solid-color(71, 2, 0, 255, 8, 8) + bounds: 568 16 8 8 + - image: solid-color(72, 2, 0, 255, 8, 8) + bounds: 576 16 8 8 + - image: solid-color(73, 2, 0, 255, 8, 8) + bounds: 584 16 8 8 + - image: solid-color(74, 2, 0, 255, 8, 8) + bounds: 592 16 8 8 + - image: solid-color(75, 2, 0, 255, 8, 8) + bounds: 600 16 8 8 + - image: solid-color(76, 2, 0, 255, 8, 8) + bounds: 608 16 8 8 + - image: solid-color(77, 2, 0, 255, 8, 8) + bounds: 616 16 8 8 + - image: solid-color(78, 2, 0, 255, 8, 8) + bounds: 624 16 8 8 + - image: solid-color(79, 2, 0, 255, 8, 8) + bounds: 632 16 8 8 + - image: solid-color(80, 2, 0, 255, 8, 8) + bounds: 640 16 8 8 + - image: solid-color(81, 2, 0, 255, 8, 8) + bounds: 648 16 8 8 + - image: solid-color(82, 2, 0, 255, 8, 8) + bounds: 656 16 8 8 + - image: solid-color(83, 2, 0, 255, 8, 8) + bounds: 664 16 8 8 + - image: solid-color(84, 2, 0, 255, 8, 8) + bounds: 672 16 8 8 + - image: solid-color(85, 2, 0, 255, 8, 8) + bounds: 680 16 8 8 + - image: solid-color(86, 2, 0, 255, 8, 8) + bounds: 688 16 8 8 + - image: solid-color(87, 2, 0, 255, 8, 8) + bounds: 696 16 8 8 + - image: solid-color(88, 2, 0, 255, 8, 8) + bounds: 704 16 8 8 + - image: solid-color(89, 2, 0, 255, 8, 8) + bounds: 712 16 8 8 + - image: solid-color(90, 2, 0, 255, 8, 8) + bounds: 720 16 8 8 + - image: solid-color(91, 2, 0, 255, 8, 8) + bounds: 728 16 8 8 + - image: solid-color(92, 2, 0, 255, 8, 8) + bounds: 736 16 8 8 + - image: solid-color(93, 2, 0, 255, 8, 8) + bounds: 744 16 8 8 + - image: solid-color(94, 2, 0, 255, 8, 8) + bounds: 752 16 8 8 + - image: solid-color(95, 2, 0, 255, 8, 8) + bounds: 760 16 8 8 + - image: solid-color(96, 2, 0, 255, 8, 8) + bounds: 768 16 8 8 + - image: solid-color(97, 2, 0, 255, 8, 8) + bounds: 776 16 8 8 + - image: solid-color(98, 2, 0, 255, 8, 8) + bounds: 784 16 8 8 + - image: solid-color(99, 2, 0, 255, 8, 8) + bounds: 792 16 8 8 + - image: solid-color(100, 2, 0, 255, 8, 8) + bounds: 800 16 8 8 + - image: solid-color(101, 2, 0, 255, 8, 8) + bounds: 808 16 8 8 + - image: solid-color(102, 2, 0, 255, 8, 8) + bounds: 816 16 8 8 + - image: solid-color(103, 2, 0, 255, 8, 8) + bounds: 824 16 8 8 + - image: solid-color(104, 2, 0, 255, 8, 8) + bounds: 832 16 8 8 + - image: solid-color(105, 2, 0, 255, 8, 8) + bounds: 840 16 8 8 + - image: solid-color(106, 2, 0, 255, 8, 8) + bounds: 848 16 8 8 + - image: solid-color(107, 2, 0, 255, 8, 8) + bounds: 856 16 8 8 + - image: solid-color(108, 2, 0, 255, 8, 8) + bounds: 864 16 8 8 + - image: solid-color(109, 2, 0, 255, 8, 8) + bounds: 872 16 8 8 + - image: solid-color(110, 2, 0, 255, 8, 8) + bounds: 880 16 8 8 + - image: solid-color(111, 2, 0, 255, 8, 8) + bounds: 888 16 8 8 + - image: solid-color(112, 2, 0, 255, 8, 8) + bounds: 896 16 8 8 + - image: solid-color(113, 2, 0, 255, 8, 8) + bounds: 904 16 8 8 + - image: solid-color(114, 2, 0, 255, 8, 8) + bounds: 912 16 8 8 + - image: solid-color(115, 2, 0, 255, 8, 8) + bounds: 920 16 8 8 + - image: solid-color(116, 2, 0, 255, 8, 8) + bounds: 928 16 8 8 + - image: solid-color(117, 2, 0, 255, 8, 8) + bounds: 936 16 8 8 + - image: solid-color(118, 2, 0, 255, 8, 8) + bounds: 944 16 8 8 + - image: solid-color(119, 2, 0, 255, 8, 8) + bounds: 952 16 8 8 + - image: solid-color(120, 2, 0, 255, 8, 8) + bounds: 960 16 8 8 + - image: solid-color(121, 2, 0, 255, 8, 8) + bounds: 968 16 8 8 + - image: solid-color(122, 2, 0, 255, 8, 8) + bounds: 976 16 8 8 + - image: solid-color(123, 2, 0, 255, 8, 8) + bounds: 984 16 8 8 + - image: solid-color(124, 2, 0, 255, 8, 8) + bounds: 992 16 8 8 + - image: solid-color(125, 2, 0, 255, 8, 8) + bounds: 1000 16 8 8 + - image: solid-color(126, 2, 0, 255, 8, 8) + bounds: 1008 16 8 8 + - image: solid-color(127, 2, 0, 255, 8, 8) + bounds: 1016 16 8 8 + - image: solid-color(0, 3, 0, 255, 8, 8) + bounds: 0 24 8 8 + - image: solid-color(1, 3, 0, 255, 8, 8) + bounds: 8 24 8 8 + - image: solid-color(2, 3, 0, 255, 8, 8) + bounds: 16 24 8 8 + - image: solid-color(3, 3, 0, 255, 8, 8) + bounds: 24 24 8 8 + - image: solid-color(4, 3, 0, 255, 8, 8) + bounds: 32 24 8 8 + - image: solid-color(5, 3, 0, 255, 8, 8) + bounds: 40 24 8 8 + - image: solid-color(6, 3, 0, 255, 8, 8) + bounds: 48 24 8 8 + - image: solid-color(7, 3, 0, 255, 8, 8) + bounds: 56 24 8 8 + - image: solid-color(8, 3, 0, 255, 8, 8) + bounds: 64 24 8 8 + - image: solid-color(9, 3, 0, 255, 8, 8) + bounds: 72 24 8 8 + - image: solid-color(10, 3, 0, 255, 8, 8) + bounds: 80 24 8 8 + - image: solid-color(11, 3, 0, 255, 8, 8) + bounds: 88 24 8 8 + - image: solid-color(12, 3, 0, 255, 8, 8) + bounds: 96 24 8 8 + - image: solid-color(13, 3, 0, 255, 8, 8) + bounds: 104 24 8 8 + - image: solid-color(14, 3, 0, 255, 8, 8) + bounds: 112 24 8 8 + - image: solid-color(15, 3, 0, 255, 8, 8) + bounds: 120 24 8 8 + - image: solid-color(16, 3, 0, 255, 8, 8) + bounds: 128 24 8 8 + - image: solid-color(17, 3, 0, 255, 8, 8) + bounds: 136 24 8 8 + - image: solid-color(18, 3, 0, 255, 8, 8) + bounds: 144 24 8 8 + - image: solid-color(19, 3, 0, 255, 8, 8) + bounds: 152 24 8 8 + - image: solid-color(20, 3, 0, 255, 8, 8) + bounds: 160 24 8 8 + - image: solid-color(21, 3, 0, 255, 8, 8) + bounds: 168 24 8 8 + - image: solid-color(22, 3, 0, 255, 8, 8) + bounds: 176 24 8 8 + - image: solid-color(23, 3, 0, 255, 8, 8) + bounds: 184 24 8 8 + - image: solid-color(24, 3, 0, 255, 8, 8) + bounds: 192 24 8 8 + - image: solid-color(25, 3, 0, 255, 8, 8) + bounds: 200 24 8 8 + - image: solid-color(26, 3, 0, 255, 8, 8) + bounds: 208 24 8 8 + - image: solid-color(27, 3, 0, 255, 8, 8) + bounds: 216 24 8 8 + - image: solid-color(28, 3, 0, 255, 8, 8) + bounds: 224 24 8 8 + - image: solid-color(29, 3, 0, 255, 8, 8) + bounds: 232 24 8 8 + - image: solid-color(30, 3, 0, 255, 8, 8) + bounds: 240 24 8 8 + - image: solid-color(31, 3, 0, 255, 8, 8) + bounds: 248 24 8 8 + - image: solid-color(32, 3, 0, 255, 8, 8) + bounds: 256 24 8 8 + - image: solid-color(33, 3, 0, 255, 8, 8) + bounds: 264 24 8 8 + - image: solid-color(34, 3, 0, 255, 8, 8) + bounds: 272 24 8 8 + - image: solid-color(35, 3, 0, 255, 8, 8) + bounds: 280 24 8 8 + - image: solid-color(36, 3, 0, 255, 8, 8) + bounds: 288 24 8 8 + - image: solid-color(37, 3, 0, 255, 8, 8) + bounds: 296 24 8 8 + - image: solid-color(38, 3, 0, 255, 8, 8) + bounds: 304 24 8 8 + - image: solid-color(39, 3, 0, 255, 8, 8) + bounds: 312 24 8 8 + - image: solid-color(40, 3, 0, 255, 8, 8) + bounds: 320 24 8 8 + - image: solid-color(41, 3, 0, 255, 8, 8) + bounds: 328 24 8 8 + - image: solid-color(42, 3, 0, 255, 8, 8) + bounds: 336 24 8 8 + - image: solid-color(43, 3, 0, 255, 8, 8) + bounds: 344 24 8 8 + - image: solid-color(44, 3, 0, 255, 8, 8) + bounds: 352 24 8 8 + - image: solid-color(45, 3, 0, 255, 8, 8) + bounds: 360 24 8 8 + - image: solid-color(46, 3, 0, 255, 8, 8) + bounds: 368 24 8 8 + - image: solid-color(47, 3, 0, 255, 8, 8) + bounds: 376 24 8 8 + - image: solid-color(48, 3, 0, 255, 8, 8) + bounds: 384 24 8 8 + - image: solid-color(49, 3, 0, 255, 8, 8) + bounds: 392 24 8 8 + - image: solid-color(50, 3, 0, 255, 8, 8) + bounds: 400 24 8 8 + - image: solid-color(51, 3, 0, 255, 8, 8) + bounds: 408 24 8 8 + - image: solid-color(52, 3, 0, 255, 8, 8) + bounds: 416 24 8 8 + - image: solid-color(53, 3, 0, 255, 8, 8) + bounds: 424 24 8 8 + - image: solid-color(54, 3, 0, 255, 8, 8) + bounds: 432 24 8 8 + - image: solid-color(55, 3, 0, 255, 8, 8) + bounds: 440 24 8 8 + - image: solid-color(56, 3, 0, 255, 8, 8) + bounds: 448 24 8 8 + - image: solid-color(57, 3, 0, 255, 8, 8) + bounds: 456 24 8 8 + - image: solid-color(58, 3, 0, 255, 8, 8) + bounds: 464 24 8 8 + - image: solid-color(59, 3, 0, 255, 8, 8) + bounds: 472 24 8 8 + - image: solid-color(60, 3, 0, 255, 8, 8) + bounds: 480 24 8 8 + - image: solid-color(61, 3, 0, 255, 8, 8) + bounds: 488 24 8 8 + - image: solid-color(62, 3, 0, 255, 8, 8) + bounds: 496 24 8 8 + - image: solid-color(63, 3, 0, 255, 8, 8) + bounds: 504 24 8 8 + - image: solid-color(64, 3, 0, 255, 8, 8) + bounds: 512 24 8 8 + - image: solid-color(65, 3, 0, 255, 8, 8) + bounds: 520 24 8 8 + - image: solid-color(66, 3, 0, 255, 8, 8) + bounds: 528 24 8 8 + - image: solid-color(67, 3, 0, 255, 8, 8) + bounds: 536 24 8 8 + - image: solid-color(68, 3, 0, 255, 8, 8) + bounds: 544 24 8 8 + - image: solid-color(69, 3, 0, 255, 8, 8) + bounds: 552 24 8 8 + - image: solid-color(70, 3, 0, 255, 8, 8) + bounds: 560 24 8 8 + - image: solid-color(71, 3, 0, 255, 8, 8) + bounds: 568 24 8 8 + - image: solid-color(72, 3, 0, 255, 8, 8) + bounds: 576 24 8 8 + - image: solid-color(73, 3, 0, 255, 8, 8) + bounds: 584 24 8 8 + - image: solid-color(74, 3, 0, 255, 8, 8) + bounds: 592 24 8 8 + - image: solid-color(75, 3, 0, 255, 8, 8) + bounds: 600 24 8 8 + - image: solid-color(76, 3, 0, 255, 8, 8) + bounds: 608 24 8 8 + - image: solid-color(77, 3, 0, 255, 8, 8) + bounds: 616 24 8 8 + - image: solid-color(78, 3, 0, 255, 8, 8) + bounds: 624 24 8 8 + - image: solid-color(79, 3, 0, 255, 8, 8) + bounds: 632 24 8 8 + - image: solid-color(80, 3, 0, 255, 8, 8) + bounds: 640 24 8 8 + - image: solid-color(81, 3, 0, 255, 8, 8) + bounds: 648 24 8 8 + - image: solid-color(82, 3, 0, 255, 8, 8) + bounds: 656 24 8 8 + - image: solid-color(83, 3, 0, 255, 8, 8) + bounds: 664 24 8 8 + - image: solid-color(84, 3, 0, 255, 8, 8) + bounds: 672 24 8 8 + - image: solid-color(85, 3, 0, 255, 8, 8) + bounds: 680 24 8 8 + - image: solid-color(86, 3, 0, 255, 8, 8) + bounds: 688 24 8 8 + - image: solid-color(87, 3, 0, 255, 8, 8) + bounds: 696 24 8 8 + - image: solid-color(88, 3, 0, 255, 8, 8) + bounds: 704 24 8 8 + - image: solid-color(89, 3, 0, 255, 8, 8) + bounds: 712 24 8 8 + - image: solid-color(90, 3, 0, 255, 8, 8) + bounds: 720 24 8 8 + - image: solid-color(91, 3, 0, 255, 8, 8) + bounds: 728 24 8 8 + - image: solid-color(92, 3, 0, 255, 8, 8) + bounds: 736 24 8 8 + - image: solid-color(93, 3, 0, 255, 8, 8) + bounds: 744 24 8 8 + - image: solid-color(94, 3, 0, 255, 8, 8) + bounds: 752 24 8 8 + - image: solid-color(95, 3, 0, 255, 8, 8) + bounds: 760 24 8 8 + - image: solid-color(96, 3, 0, 255, 8, 8) + bounds: 768 24 8 8 + - image: solid-color(97, 3, 0, 255, 8, 8) + bounds: 776 24 8 8 + - image: solid-color(98, 3, 0, 255, 8, 8) + bounds: 784 24 8 8 + - image: solid-color(99, 3, 0, 255, 8, 8) + bounds: 792 24 8 8 + - image: solid-color(100, 3, 0, 255, 8, 8) + bounds: 800 24 8 8 + - image: solid-color(101, 3, 0, 255, 8, 8) + bounds: 808 24 8 8 + - image: solid-color(102, 3, 0, 255, 8, 8) + bounds: 816 24 8 8 + - image: solid-color(103, 3, 0, 255, 8, 8) + bounds: 824 24 8 8 + - image: solid-color(104, 3, 0, 255, 8, 8) + bounds: 832 24 8 8 + - image: solid-color(105, 3, 0, 255, 8, 8) + bounds: 840 24 8 8 + - image: solid-color(106, 3, 0, 255, 8, 8) + bounds: 848 24 8 8 + - image: solid-color(107, 3, 0, 255, 8, 8) + bounds: 856 24 8 8 + - image: solid-color(108, 3, 0, 255, 8, 8) + bounds: 864 24 8 8 + - image: solid-color(109, 3, 0, 255, 8, 8) + bounds: 872 24 8 8 + - image: solid-color(110, 3, 0, 255, 8, 8) + bounds: 880 24 8 8 + - image: solid-color(111, 3, 0, 255, 8, 8) + bounds: 888 24 8 8 + - image: solid-color(112, 3, 0, 255, 8, 8) + bounds: 896 24 8 8 + - image: solid-color(113, 3, 0, 255, 8, 8) + bounds: 904 24 8 8 + - image: solid-color(114, 3, 0, 255, 8, 8) + bounds: 912 24 8 8 + - image: solid-color(115, 3, 0, 255, 8, 8) + bounds: 920 24 8 8 + - image: solid-color(116, 3, 0, 255, 8, 8) + bounds: 928 24 8 8 + - image: solid-color(117, 3, 0, 255, 8, 8) + bounds: 936 24 8 8 + - image: solid-color(118, 3, 0, 255, 8, 8) + bounds: 944 24 8 8 + - image: solid-color(119, 3, 0, 255, 8, 8) + bounds: 952 24 8 8 + - image: solid-color(120, 3, 0, 255, 8, 8) + bounds: 960 24 8 8 + - image: solid-color(121, 3, 0, 255, 8, 8) + bounds: 968 24 8 8 + - image: solid-color(122, 3, 0, 255, 8, 8) + bounds: 976 24 8 8 + - image: solid-color(123, 3, 0, 255, 8, 8) + bounds: 984 24 8 8 + - image: solid-color(124, 3, 0, 255, 8, 8) + bounds: 992 24 8 8 + - image: solid-color(125, 3, 0, 255, 8, 8) + bounds: 1000 24 8 8 + - image: solid-color(126, 3, 0, 255, 8, 8) + bounds: 1008 24 8 8 + - image: solid-color(127, 3, 0, 255, 8, 8) + bounds: 1016 24 8 8 + - image: solid-color(0, 4, 0, 255, 8, 8) + bounds: 0 32 8 8 + - image: solid-color(1, 4, 0, 255, 8, 8) + bounds: 8 32 8 8 + - image: solid-color(2, 4, 0, 255, 8, 8) + bounds: 16 32 8 8 + - image: solid-color(3, 4, 0, 255, 8, 8) + bounds: 24 32 8 8 + - image: solid-color(4, 4, 0, 255, 8, 8) + bounds: 32 32 8 8 + - image: solid-color(5, 4, 0, 255, 8, 8) + bounds: 40 32 8 8 + - image: solid-color(6, 4, 0, 255, 8, 8) + bounds: 48 32 8 8 + - image: solid-color(7, 4, 0, 255, 8, 8) + bounds: 56 32 8 8 + - image: solid-color(8, 4, 0, 255, 8, 8) + bounds: 64 32 8 8 + - image: solid-color(9, 4, 0, 255, 8, 8) + bounds: 72 32 8 8 + - image: solid-color(10, 4, 0, 255, 8, 8) + bounds: 80 32 8 8 + - image: solid-color(11, 4, 0, 255, 8, 8) + bounds: 88 32 8 8 + - image: solid-color(12, 4, 0, 255, 8, 8) + bounds: 96 32 8 8 + - image: solid-color(13, 4, 0, 255, 8, 8) + bounds: 104 32 8 8 + - image: solid-color(14, 4, 0, 255, 8, 8) + bounds: 112 32 8 8 + - image: solid-color(15, 4, 0, 255, 8, 8) + bounds: 120 32 8 8 + - image: solid-color(16, 4, 0, 255, 8, 8) + bounds: 128 32 8 8 + - image: solid-color(17, 4, 0, 255, 8, 8) + bounds: 136 32 8 8 + - image: solid-color(18, 4, 0, 255, 8, 8) + bounds: 144 32 8 8 + - image: solid-color(19, 4, 0, 255, 8, 8) + bounds: 152 32 8 8 + - image: solid-color(20, 4, 0, 255, 8, 8) + bounds: 160 32 8 8 + - image: solid-color(21, 4, 0, 255, 8, 8) + bounds: 168 32 8 8 + - image: solid-color(22, 4, 0, 255, 8, 8) + bounds: 176 32 8 8 + - image: solid-color(23, 4, 0, 255, 8, 8) + bounds: 184 32 8 8 + - image: solid-color(24, 4, 0, 255, 8, 8) + bounds: 192 32 8 8 + - image: solid-color(25, 4, 0, 255, 8, 8) + bounds: 200 32 8 8 + - image: solid-color(26, 4, 0, 255, 8, 8) + bounds: 208 32 8 8 + - image: solid-color(27, 4, 0, 255, 8, 8) + bounds: 216 32 8 8 + - image: solid-color(28, 4, 0, 255, 8, 8) + bounds: 224 32 8 8 + - image: solid-color(29, 4, 0, 255, 8, 8) + bounds: 232 32 8 8 + - image: solid-color(30, 4, 0, 255, 8, 8) + bounds: 240 32 8 8 + - image: solid-color(31, 4, 0, 255, 8, 8) + bounds: 248 32 8 8 + - image: solid-color(32, 4, 0, 255, 8, 8) + bounds: 256 32 8 8 + - image: solid-color(33, 4, 0, 255, 8, 8) + bounds: 264 32 8 8 + - image: solid-color(34, 4, 0, 255, 8, 8) + bounds: 272 32 8 8 + - image: solid-color(35, 4, 0, 255, 8, 8) + bounds: 280 32 8 8 + - image: solid-color(36, 4, 0, 255, 8, 8) + bounds: 288 32 8 8 + - image: solid-color(37, 4, 0, 255, 8, 8) + bounds: 296 32 8 8 + - image: solid-color(38, 4, 0, 255, 8, 8) + bounds: 304 32 8 8 + - image: solid-color(39, 4, 0, 255, 8, 8) + bounds: 312 32 8 8 + - image: solid-color(40, 4, 0, 255, 8, 8) + bounds: 320 32 8 8 + - image: solid-color(41, 4, 0, 255, 8, 8) + bounds: 328 32 8 8 + - image: solid-color(42, 4, 0, 255, 8, 8) + bounds: 336 32 8 8 + - image: solid-color(43, 4, 0, 255, 8, 8) + bounds: 344 32 8 8 + - image: solid-color(44, 4, 0, 255, 8, 8) + bounds: 352 32 8 8 + - image: solid-color(45, 4, 0, 255, 8, 8) + bounds: 360 32 8 8 + - image: solid-color(46, 4, 0, 255, 8, 8) + bounds: 368 32 8 8 + - image: solid-color(47, 4, 0, 255, 8, 8) + bounds: 376 32 8 8 + - image: solid-color(48, 4, 0, 255, 8, 8) + bounds: 384 32 8 8 + - image: solid-color(49, 4, 0, 255, 8, 8) + bounds: 392 32 8 8 + - image: solid-color(50, 4, 0, 255, 8, 8) + bounds: 400 32 8 8 + - image: solid-color(51, 4, 0, 255, 8, 8) + bounds: 408 32 8 8 + - image: solid-color(52, 4, 0, 255, 8, 8) + bounds: 416 32 8 8 + - image: solid-color(53, 4, 0, 255, 8, 8) + bounds: 424 32 8 8 + - image: solid-color(54, 4, 0, 255, 8, 8) + bounds: 432 32 8 8 + - image: solid-color(55, 4, 0, 255, 8, 8) + bounds: 440 32 8 8 + - image: solid-color(56, 4, 0, 255, 8, 8) + bounds: 448 32 8 8 + - image: solid-color(57, 4, 0, 255, 8, 8) + bounds: 456 32 8 8 + - image: solid-color(58, 4, 0, 255, 8, 8) + bounds: 464 32 8 8 + - image: solid-color(59, 4, 0, 255, 8, 8) + bounds: 472 32 8 8 + - image: solid-color(60, 4, 0, 255, 8, 8) + bounds: 480 32 8 8 + - image: solid-color(61, 4, 0, 255, 8, 8) + bounds: 488 32 8 8 + - image: solid-color(62, 4, 0, 255, 8, 8) + bounds: 496 32 8 8 + - image: solid-color(63, 4, 0, 255, 8, 8) + bounds: 504 32 8 8 + - image: solid-color(64, 4, 0, 255, 8, 8) + bounds: 512 32 8 8 + - image: solid-color(65, 4, 0, 255, 8, 8) + bounds: 520 32 8 8 + - image: solid-color(66, 4, 0, 255, 8, 8) + bounds: 528 32 8 8 + - image: solid-color(67, 4, 0, 255, 8, 8) + bounds: 536 32 8 8 + - image: solid-color(68, 4, 0, 255, 8, 8) + bounds: 544 32 8 8 + - image: solid-color(69, 4, 0, 255, 8, 8) + bounds: 552 32 8 8 + - image: solid-color(70, 4, 0, 255, 8, 8) + bounds: 560 32 8 8 + - image: solid-color(71, 4, 0, 255, 8, 8) + bounds: 568 32 8 8 + - image: solid-color(72, 4, 0, 255, 8, 8) + bounds: 576 32 8 8 + - image: solid-color(73, 4, 0, 255, 8, 8) + bounds: 584 32 8 8 + - image: solid-color(74, 4, 0, 255, 8, 8) + bounds: 592 32 8 8 + - image: solid-color(75, 4, 0, 255, 8, 8) + bounds: 600 32 8 8 + - image: solid-color(76, 4, 0, 255, 8, 8) + bounds: 608 32 8 8 + - image: solid-color(77, 4, 0, 255, 8, 8) + bounds: 616 32 8 8 + - image: solid-color(78, 4, 0, 255, 8, 8) + bounds: 624 32 8 8 + - image: solid-color(79, 4, 0, 255, 8, 8) + bounds: 632 32 8 8 + - image: solid-color(80, 4, 0, 255, 8, 8) + bounds: 640 32 8 8 + - image: solid-color(81, 4, 0, 255, 8, 8) + bounds: 648 32 8 8 + - image: solid-color(82, 4, 0, 255, 8, 8) + bounds: 656 32 8 8 + - image: solid-color(83, 4, 0, 255, 8, 8) + bounds: 664 32 8 8 + - image: solid-color(84, 4, 0, 255, 8, 8) + bounds: 672 32 8 8 + - image: solid-color(85, 4, 0, 255, 8, 8) + bounds: 680 32 8 8 + - image: solid-color(86, 4, 0, 255, 8, 8) + bounds: 688 32 8 8 + - image: solid-color(87, 4, 0, 255, 8, 8) + bounds: 696 32 8 8 + - image: solid-color(88, 4, 0, 255, 8, 8) + bounds: 704 32 8 8 + - image: solid-color(89, 4, 0, 255, 8, 8) + bounds: 712 32 8 8 + - image: solid-color(90, 4, 0, 255, 8, 8) + bounds: 720 32 8 8 + - image: solid-color(91, 4, 0, 255, 8, 8) + bounds: 728 32 8 8 + - image: solid-color(92, 4, 0, 255, 8, 8) + bounds: 736 32 8 8 + - image: solid-color(93, 4, 0, 255, 8, 8) + bounds: 744 32 8 8 + - image: solid-color(94, 4, 0, 255, 8, 8) + bounds: 752 32 8 8 + - image: solid-color(95, 4, 0, 255, 8, 8) + bounds: 760 32 8 8 + - image: solid-color(96, 4, 0, 255, 8, 8) + bounds: 768 32 8 8 + - image: solid-color(97, 4, 0, 255, 8, 8) + bounds: 776 32 8 8 + - image: solid-color(98, 4, 0, 255, 8, 8) + bounds: 784 32 8 8 + - image: solid-color(99, 4, 0, 255, 8, 8) + bounds: 792 32 8 8 + - image: solid-color(100, 4, 0, 255, 8, 8) + bounds: 800 32 8 8 + - image: solid-color(101, 4, 0, 255, 8, 8) + bounds: 808 32 8 8 + - image: solid-color(102, 4, 0, 255, 8, 8) + bounds: 816 32 8 8 + - image: solid-color(103, 4, 0, 255, 8, 8) + bounds: 824 32 8 8 + - image: solid-color(104, 4, 0, 255, 8, 8) + bounds: 832 32 8 8 + - image: solid-color(105, 4, 0, 255, 8, 8) + bounds: 840 32 8 8 + - image: solid-color(106, 4, 0, 255, 8, 8) + bounds: 848 32 8 8 + - image: solid-color(107, 4, 0, 255, 8, 8) + bounds: 856 32 8 8 + - image: solid-color(108, 4, 0, 255, 8, 8) + bounds: 864 32 8 8 + - image: solid-color(109, 4, 0, 255, 8, 8) + bounds: 872 32 8 8 + - image: solid-color(110, 4, 0, 255, 8, 8) + bounds: 880 32 8 8 + - image: solid-color(111, 4, 0, 255, 8, 8) + bounds: 888 32 8 8 + - image: solid-color(112, 4, 0, 255, 8, 8) + bounds: 896 32 8 8 + - image: solid-color(113, 4, 0, 255, 8, 8) + bounds: 904 32 8 8 + - image: solid-color(114, 4, 0, 255, 8, 8) + bounds: 912 32 8 8 + - image: solid-color(115, 4, 0, 255, 8, 8) + bounds: 920 32 8 8 + - image: solid-color(116, 4, 0, 255, 8, 8) + bounds: 928 32 8 8 + - image: solid-color(117, 4, 0, 255, 8, 8) + bounds: 936 32 8 8 + - image: solid-color(118, 4, 0, 255, 8, 8) + bounds: 944 32 8 8 + - image: solid-color(119, 4, 0, 255, 8, 8) + bounds: 952 32 8 8 + - image: solid-color(120, 4, 0, 255, 8, 8) + bounds: 960 32 8 8 + - image: solid-color(121, 4, 0, 255, 8, 8) + bounds: 968 32 8 8 + - image: solid-color(122, 4, 0, 255, 8, 8) + bounds: 976 32 8 8 + - image: solid-color(123, 4, 0, 255, 8, 8) + bounds: 984 32 8 8 + - image: solid-color(124, 4, 0, 255, 8, 8) + bounds: 992 32 8 8 + - image: solid-color(125, 4, 0, 255, 8, 8) + bounds: 1000 32 8 8 + - image: solid-color(126, 4, 0, 255, 8, 8) + bounds: 1008 32 8 8 + - image: solid-color(127, 4, 0, 255, 8, 8) + bounds: 1016 32 8 8 + - image: solid-color(0, 5, 0, 255, 8, 8) + bounds: 0 40 8 8 + - image: solid-color(1, 5, 0, 255, 8, 8) + bounds: 8 40 8 8 + - image: solid-color(2, 5, 0, 255, 8, 8) + bounds: 16 40 8 8 + - image: solid-color(3, 5, 0, 255, 8, 8) + bounds: 24 40 8 8 + - image: solid-color(4, 5, 0, 255, 8, 8) + bounds: 32 40 8 8 + - image: solid-color(5, 5, 0, 255, 8, 8) + bounds: 40 40 8 8 + - image: solid-color(6, 5, 0, 255, 8, 8) + bounds: 48 40 8 8 + - image: solid-color(7, 5, 0, 255, 8, 8) + bounds: 56 40 8 8 + - image: solid-color(8, 5, 0, 255, 8, 8) + bounds: 64 40 8 8 + - image: solid-color(9, 5, 0, 255, 8, 8) + bounds: 72 40 8 8 + - image: solid-color(10, 5, 0, 255, 8, 8) + bounds: 80 40 8 8 + - image: solid-color(11, 5, 0, 255, 8, 8) + bounds: 88 40 8 8 + - image: solid-color(12, 5, 0, 255, 8, 8) + bounds: 96 40 8 8 + - image: solid-color(13, 5, 0, 255, 8, 8) + bounds: 104 40 8 8 + - image: solid-color(14, 5, 0, 255, 8, 8) + bounds: 112 40 8 8 + - image: solid-color(15, 5, 0, 255, 8, 8) + bounds: 120 40 8 8 + - image: solid-color(16, 5, 0, 255, 8, 8) + bounds: 128 40 8 8 + - image: solid-color(17, 5, 0, 255, 8, 8) + bounds: 136 40 8 8 + - image: solid-color(18, 5, 0, 255, 8, 8) + bounds: 144 40 8 8 + - image: solid-color(19, 5, 0, 255, 8, 8) + bounds: 152 40 8 8 + - image: solid-color(20, 5, 0, 255, 8, 8) + bounds: 160 40 8 8 + - image: solid-color(21, 5, 0, 255, 8, 8) + bounds: 168 40 8 8 + - image: solid-color(22, 5, 0, 255, 8, 8) + bounds: 176 40 8 8 + - image: solid-color(23, 5, 0, 255, 8, 8) + bounds: 184 40 8 8 + - image: solid-color(24, 5, 0, 255, 8, 8) + bounds: 192 40 8 8 + - image: solid-color(25, 5, 0, 255, 8, 8) + bounds: 200 40 8 8 + - image: solid-color(26, 5, 0, 255, 8, 8) + bounds: 208 40 8 8 + - image: solid-color(27, 5, 0, 255, 8, 8) + bounds: 216 40 8 8 + - image: solid-color(28, 5, 0, 255, 8, 8) + bounds: 224 40 8 8 + - image: solid-color(29, 5, 0, 255, 8, 8) + bounds: 232 40 8 8 + - image: solid-color(30, 5, 0, 255, 8, 8) + bounds: 240 40 8 8 + - image: solid-color(31, 5, 0, 255, 8, 8) + bounds: 248 40 8 8 + - image: solid-color(32, 5, 0, 255, 8, 8) + bounds: 256 40 8 8 + - image: solid-color(33, 5, 0, 255, 8, 8) + bounds: 264 40 8 8 + - image: solid-color(34, 5, 0, 255, 8, 8) + bounds: 272 40 8 8 + - image: solid-color(35, 5, 0, 255, 8, 8) + bounds: 280 40 8 8 + - image: solid-color(36, 5, 0, 255, 8, 8) + bounds: 288 40 8 8 + - image: solid-color(37, 5, 0, 255, 8, 8) + bounds: 296 40 8 8 + - image: solid-color(38, 5, 0, 255, 8, 8) + bounds: 304 40 8 8 + - image: solid-color(39, 5, 0, 255, 8, 8) + bounds: 312 40 8 8 + - image: solid-color(40, 5, 0, 255, 8, 8) + bounds: 320 40 8 8 + - image: solid-color(41, 5, 0, 255, 8, 8) + bounds: 328 40 8 8 + - image: solid-color(42, 5, 0, 255, 8, 8) + bounds: 336 40 8 8 + - image: solid-color(43, 5, 0, 255, 8, 8) + bounds: 344 40 8 8 + - image: solid-color(44, 5, 0, 255, 8, 8) + bounds: 352 40 8 8 + - image: solid-color(45, 5, 0, 255, 8, 8) + bounds: 360 40 8 8 + - image: solid-color(46, 5, 0, 255, 8, 8) + bounds: 368 40 8 8 + - image: solid-color(47, 5, 0, 255, 8, 8) + bounds: 376 40 8 8 + - image: solid-color(48, 5, 0, 255, 8, 8) + bounds: 384 40 8 8 + - image: solid-color(49, 5, 0, 255, 8, 8) + bounds: 392 40 8 8 + - image: solid-color(50, 5, 0, 255, 8, 8) + bounds: 400 40 8 8 + - image: solid-color(51, 5, 0, 255, 8, 8) + bounds: 408 40 8 8 + - image: solid-color(52, 5, 0, 255, 8, 8) + bounds: 416 40 8 8 + - image: solid-color(53, 5, 0, 255, 8, 8) + bounds: 424 40 8 8 + - image: solid-color(54, 5, 0, 255, 8, 8) + bounds: 432 40 8 8 + - image: solid-color(55, 5, 0, 255, 8, 8) + bounds: 440 40 8 8 + - image: solid-color(56, 5, 0, 255, 8, 8) + bounds: 448 40 8 8 + - image: solid-color(57, 5, 0, 255, 8, 8) + bounds: 456 40 8 8 + - image: solid-color(58, 5, 0, 255, 8, 8) + bounds: 464 40 8 8 + - image: solid-color(59, 5, 0, 255, 8, 8) + bounds: 472 40 8 8 + - image: solid-color(60, 5, 0, 255, 8, 8) + bounds: 480 40 8 8 + - image: solid-color(61, 5, 0, 255, 8, 8) + bounds: 488 40 8 8 + - image: solid-color(62, 5, 0, 255, 8, 8) + bounds: 496 40 8 8 + - image: solid-color(63, 5, 0, 255, 8, 8) + bounds: 504 40 8 8 + - image: solid-color(64, 5, 0, 255, 8, 8) + bounds: 512 40 8 8 + - image: solid-color(65, 5, 0, 255, 8, 8) + bounds: 520 40 8 8 + - image: solid-color(66, 5, 0, 255, 8, 8) + bounds: 528 40 8 8 + - image: solid-color(67, 5, 0, 255, 8, 8) + bounds: 536 40 8 8 + - image: solid-color(68, 5, 0, 255, 8, 8) + bounds: 544 40 8 8 + - image: solid-color(69, 5, 0, 255, 8, 8) + bounds: 552 40 8 8 + - image: solid-color(70, 5, 0, 255, 8, 8) + bounds: 560 40 8 8 + - image: solid-color(71, 5, 0, 255, 8, 8) + bounds: 568 40 8 8 + - image: solid-color(72, 5, 0, 255, 8, 8) + bounds: 576 40 8 8 + - image: solid-color(73, 5, 0, 255, 8, 8) + bounds: 584 40 8 8 + - image: solid-color(74, 5, 0, 255, 8, 8) + bounds: 592 40 8 8 + - image: solid-color(75, 5, 0, 255, 8, 8) + bounds: 600 40 8 8 + - image: solid-color(76, 5, 0, 255, 8, 8) + bounds: 608 40 8 8 + - image: solid-color(77, 5, 0, 255, 8, 8) + bounds: 616 40 8 8 + - image: solid-color(78, 5, 0, 255, 8, 8) + bounds: 624 40 8 8 + - image: solid-color(79, 5, 0, 255, 8, 8) + bounds: 632 40 8 8 + - image: solid-color(80, 5, 0, 255, 8, 8) + bounds: 640 40 8 8 + - image: solid-color(81, 5, 0, 255, 8, 8) + bounds: 648 40 8 8 + - image: solid-color(82, 5, 0, 255, 8, 8) + bounds: 656 40 8 8 + - image: solid-color(83, 5, 0, 255, 8, 8) + bounds: 664 40 8 8 + - image: solid-color(84, 5, 0, 255, 8, 8) + bounds: 672 40 8 8 + - image: solid-color(85, 5, 0, 255, 8, 8) + bounds: 680 40 8 8 + - image: solid-color(86, 5, 0, 255, 8, 8) + bounds: 688 40 8 8 + - image: solid-color(87, 5, 0, 255, 8, 8) + bounds: 696 40 8 8 + - image: solid-color(88, 5, 0, 255, 8, 8) + bounds: 704 40 8 8 + - image: solid-color(89, 5, 0, 255, 8, 8) + bounds: 712 40 8 8 + - image: solid-color(90, 5, 0, 255, 8, 8) + bounds: 720 40 8 8 + - image: solid-color(91, 5, 0, 255, 8, 8) + bounds: 728 40 8 8 + - image: solid-color(92, 5, 0, 255, 8, 8) + bounds: 736 40 8 8 + - image: solid-color(93, 5, 0, 255, 8, 8) + bounds: 744 40 8 8 + - image: solid-color(94, 5, 0, 255, 8, 8) + bounds: 752 40 8 8 + - image: solid-color(95, 5, 0, 255, 8, 8) + bounds: 760 40 8 8 + - image: solid-color(96, 5, 0, 255, 8, 8) + bounds: 768 40 8 8 + - image: solid-color(97, 5, 0, 255, 8, 8) + bounds: 776 40 8 8 + - image: solid-color(98, 5, 0, 255, 8, 8) + bounds: 784 40 8 8 + - image: solid-color(99, 5, 0, 255, 8, 8) + bounds: 792 40 8 8 + - image: solid-color(100, 5, 0, 255, 8, 8) + bounds: 800 40 8 8 + - image: solid-color(101, 5, 0, 255, 8, 8) + bounds: 808 40 8 8 + - image: solid-color(102, 5, 0, 255, 8, 8) + bounds: 816 40 8 8 + - image: solid-color(103, 5, 0, 255, 8, 8) + bounds: 824 40 8 8 + - image: solid-color(104, 5, 0, 255, 8, 8) + bounds: 832 40 8 8 + - image: solid-color(105, 5, 0, 255, 8, 8) + bounds: 840 40 8 8 + - image: solid-color(106, 5, 0, 255, 8, 8) + bounds: 848 40 8 8 + - image: solid-color(107, 5, 0, 255, 8, 8) + bounds: 856 40 8 8 + - image: solid-color(108, 5, 0, 255, 8, 8) + bounds: 864 40 8 8 + - image: solid-color(109, 5, 0, 255, 8, 8) + bounds: 872 40 8 8 + - image: solid-color(110, 5, 0, 255, 8, 8) + bounds: 880 40 8 8 + - image: solid-color(111, 5, 0, 255, 8, 8) + bounds: 888 40 8 8 + - image: solid-color(112, 5, 0, 255, 8, 8) + bounds: 896 40 8 8 + - image: solid-color(113, 5, 0, 255, 8, 8) + bounds: 904 40 8 8 + - image: solid-color(114, 5, 0, 255, 8, 8) + bounds: 912 40 8 8 + - image: solid-color(115, 5, 0, 255, 8, 8) + bounds: 920 40 8 8 + - image: solid-color(116, 5, 0, 255, 8, 8) + bounds: 928 40 8 8 + - image: solid-color(117, 5, 0, 255, 8, 8) + bounds: 936 40 8 8 + - image: solid-color(118, 5, 0, 255, 8, 8) + bounds: 944 40 8 8 + - image: solid-color(119, 5, 0, 255, 8, 8) + bounds: 952 40 8 8 + - image: solid-color(120, 5, 0, 255, 8, 8) + bounds: 960 40 8 8 + - image: solid-color(121, 5, 0, 255, 8, 8) + bounds: 968 40 8 8 + - image: solid-color(122, 5, 0, 255, 8, 8) + bounds: 976 40 8 8 + - image: solid-color(123, 5, 0, 255, 8, 8) + bounds: 984 40 8 8 + - image: solid-color(124, 5, 0, 255, 8, 8) + bounds: 992 40 8 8 + - image: solid-color(125, 5, 0, 255, 8, 8) + bounds: 1000 40 8 8 + - image: solid-color(126, 5, 0, 255, 8, 8) + bounds: 1008 40 8 8 + - image: solid-color(127, 5, 0, 255, 8, 8) + bounds: 1016 40 8 8 + - image: solid-color(0, 6, 0, 255, 8, 8) + bounds: 0 48 8 8 + - image: solid-color(1, 6, 0, 255, 8, 8) + bounds: 8 48 8 8 + - image: solid-color(2, 6, 0, 255, 8, 8) + bounds: 16 48 8 8 + - image: solid-color(3, 6, 0, 255, 8, 8) + bounds: 24 48 8 8 + - image: solid-color(4, 6, 0, 255, 8, 8) + bounds: 32 48 8 8 + - image: solid-color(5, 6, 0, 255, 8, 8) + bounds: 40 48 8 8 + - image: solid-color(6, 6, 0, 255, 8, 8) + bounds: 48 48 8 8 + - image: solid-color(7, 6, 0, 255, 8, 8) + bounds: 56 48 8 8 + - image: solid-color(8, 6, 0, 255, 8, 8) + bounds: 64 48 8 8 + - image: solid-color(9, 6, 0, 255, 8, 8) + bounds: 72 48 8 8 + - image: solid-color(10, 6, 0, 255, 8, 8) + bounds: 80 48 8 8 + - image: solid-color(11, 6, 0, 255, 8, 8) + bounds: 88 48 8 8 + - image: solid-color(12, 6, 0, 255, 8, 8) + bounds: 96 48 8 8 + - image: solid-color(13, 6, 0, 255, 8, 8) + bounds: 104 48 8 8 + - image: solid-color(14, 6, 0, 255, 8, 8) + bounds: 112 48 8 8 + - image: solid-color(15, 6, 0, 255, 8, 8) + bounds: 120 48 8 8 + - image: solid-color(16, 6, 0, 255, 8, 8) + bounds: 128 48 8 8 + - image: solid-color(17, 6, 0, 255, 8, 8) + bounds: 136 48 8 8 + - image: solid-color(18, 6, 0, 255, 8, 8) + bounds: 144 48 8 8 + - image: solid-color(19, 6, 0, 255, 8, 8) + bounds: 152 48 8 8 + - image: solid-color(20, 6, 0, 255, 8, 8) + bounds: 160 48 8 8 + - image: solid-color(21, 6, 0, 255, 8, 8) + bounds: 168 48 8 8 + - image: solid-color(22, 6, 0, 255, 8, 8) + bounds: 176 48 8 8 + - image: solid-color(23, 6, 0, 255, 8, 8) + bounds: 184 48 8 8 + - image: solid-color(24, 6, 0, 255, 8, 8) + bounds: 192 48 8 8 + - image: solid-color(25, 6, 0, 255, 8, 8) + bounds: 200 48 8 8 + - image: solid-color(26, 6, 0, 255, 8, 8) + bounds: 208 48 8 8 + - image: solid-color(27, 6, 0, 255, 8, 8) + bounds: 216 48 8 8 + - image: solid-color(28, 6, 0, 255, 8, 8) + bounds: 224 48 8 8 + - image: solid-color(29, 6, 0, 255, 8, 8) + bounds: 232 48 8 8 + - image: solid-color(30, 6, 0, 255, 8, 8) + bounds: 240 48 8 8 + - image: solid-color(31, 6, 0, 255, 8, 8) + bounds: 248 48 8 8 + - image: solid-color(32, 6, 0, 255, 8, 8) + bounds: 256 48 8 8 + - image: solid-color(33, 6, 0, 255, 8, 8) + bounds: 264 48 8 8 + - image: solid-color(34, 6, 0, 255, 8, 8) + bounds: 272 48 8 8 + - image: solid-color(35, 6, 0, 255, 8, 8) + bounds: 280 48 8 8 + - image: solid-color(36, 6, 0, 255, 8, 8) + bounds: 288 48 8 8 + - image: solid-color(37, 6, 0, 255, 8, 8) + bounds: 296 48 8 8 + - image: solid-color(38, 6, 0, 255, 8, 8) + bounds: 304 48 8 8 + - image: solid-color(39, 6, 0, 255, 8, 8) + bounds: 312 48 8 8 + - image: solid-color(40, 6, 0, 255, 8, 8) + bounds: 320 48 8 8 + - image: solid-color(41, 6, 0, 255, 8, 8) + bounds: 328 48 8 8 + - image: solid-color(42, 6, 0, 255, 8, 8) + bounds: 336 48 8 8 + - image: solid-color(43, 6, 0, 255, 8, 8) + bounds: 344 48 8 8 + - image: solid-color(44, 6, 0, 255, 8, 8) + bounds: 352 48 8 8 + - image: solid-color(45, 6, 0, 255, 8, 8) + bounds: 360 48 8 8 + - image: solid-color(46, 6, 0, 255, 8, 8) + bounds: 368 48 8 8 + - image: solid-color(47, 6, 0, 255, 8, 8) + bounds: 376 48 8 8 + - image: solid-color(48, 6, 0, 255, 8, 8) + bounds: 384 48 8 8 + - image: solid-color(49, 6, 0, 255, 8, 8) + bounds: 392 48 8 8 + - image: solid-color(50, 6, 0, 255, 8, 8) + bounds: 400 48 8 8 + - image: solid-color(51, 6, 0, 255, 8, 8) + bounds: 408 48 8 8 + - image: solid-color(52, 6, 0, 255, 8, 8) + bounds: 416 48 8 8 + - image: solid-color(53, 6, 0, 255, 8, 8) + bounds: 424 48 8 8 + - image: solid-color(54, 6, 0, 255, 8, 8) + bounds: 432 48 8 8 + - image: solid-color(55, 6, 0, 255, 8, 8) + bounds: 440 48 8 8 + - image: solid-color(56, 6, 0, 255, 8, 8) + bounds: 448 48 8 8 + - image: solid-color(57, 6, 0, 255, 8, 8) + bounds: 456 48 8 8 + - image: solid-color(58, 6, 0, 255, 8, 8) + bounds: 464 48 8 8 + - image: solid-color(59, 6, 0, 255, 8, 8) + bounds: 472 48 8 8 + - image: solid-color(60, 6, 0, 255, 8, 8) + bounds: 480 48 8 8 + - image: solid-color(61, 6, 0, 255, 8, 8) + bounds: 488 48 8 8 + - image: solid-color(62, 6, 0, 255, 8, 8) + bounds: 496 48 8 8 + - image: solid-color(63, 6, 0, 255, 8, 8) + bounds: 504 48 8 8 + - image: solid-color(64, 6, 0, 255, 8, 8) + bounds: 512 48 8 8 + - image: solid-color(65, 6, 0, 255, 8, 8) + bounds: 520 48 8 8 + - image: solid-color(66, 6, 0, 255, 8, 8) + bounds: 528 48 8 8 + - image: solid-color(67, 6, 0, 255, 8, 8) + bounds: 536 48 8 8 + - image: solid-color(68, 6, 0, 255, 8, 8) + bounds: 544 48 8 8 + - image: solid-color(69, 6, 0, 255, 8, 8) + bounds: 552 48 8 8 + - image: solid-color(70, 6, 0, 255, 8, 8) + bounds: 560 48 8 8 + - image: solid-color(71, 6, 0, 255, 8, 8) + bounds: 568 48 8 8 + - image: solid-color(72, 6, 0, 255, 8, 8) + bounds: 576 48 8 8 + - image: solid-color(73, 6, 0, 255, 8, 8) + bounds: 584 48 8 8 + - image: solid-color(74, 6, 0, 255, 8, 8) + bounds: 592 48 8 8 + - image: solid-color(75, 6, 0, 255, 8, 8) + bounds: 600 48 8 8 + - image: solid-color(76, 6, 0, 255, 8, 8) + bounds: 608 48 8 8 + - image: solid-color(77, 6, 0, 255, 8, 8) + bounds: 616 48 8 8 + - image: solid-color(78, 6, 0, 255, 8, 8) + bounds: 624 48 8 8 + - image: solid-color(79, 6, 0, 255, 8, 8) + bounds: 632 48 8 8 + - image: solid-color(80, 6, 0, 255, 8, 8) + bounds: 640 48 8 8 + - image: solid-color(81, 6, 0, 255, 8, 8) + bounds: 648 48 8 8 + - image: solid-color(82, 6, 0, 255, 8, 8) + bounds: 656 48 8 8 + - image: solid-color(83, 6, 0, 255, 8, 8) + bounds: 664 48 8 8 + - image: solid-color(84, 6, 0, 255, 8, 8) + bounds: 672 48 8 8 + - image: solid-color(85, 6, 0, 255, 8, 8) + bounds: 680 48 8 8 + - image: solid-color(86, 6, 0, 255, 8, 8) + bounds: 688 48 8 8 + - image: solid-color(87, 6, 0, 255, 8, 8) + bounds: 696 48 8 8 + - image: solid-color(88, 6, 0, 255, 8, 8) + bounds: 704 48 8 8 + - image: solid-color(89, 6, 0, 255, 8, 8) + bounds: 712 48 8 8 + - image: solid-color(90, 6, 0, 255, 8, 8) + bounds: 720 48 8 8 + - image: solid-color(91, 6, 0, 255, 8, 8) + bounds: 728 48 8 8 + - image: solid-color(92, 6, 0, 255, 8, 8) + bounds: 736 48 8 8 + - image: solid-color(93, 6, 0, 255, 8, 8) + bounds: 744 48 8 8 + - image: solid-color(94, 6, 0, 255, 8, 8) + bounds: 752 48 8 8 + - image: solid-color(95, 6, 0, 255, 8, 8) + bounds: 760 48 8 8 + - image: solid-color(96, 6, 0, 255, 8, 8) + bounds: 768 48 8 8 + - image: solid-color(97, 6, 0, 255, 8, 8) + bounds: 776 48 8 8 + - image: solid-color(98, 6, 0, 255, 8, 8) + bounds: 784 48 8 8 + - image: solid-color(99, 6, 0, 255, 8, 8) + bounds: 792 48 8 8 + - image: solid-color(100, 6, 0, 255, 8, 8) + bounds: 800 48 8 8 + - image: solid-color(101, 6, 0, 255, 8, 8) + bounds: 808 48 8 8 + - image: solid-color(102, 6, 0, 255, 8, 8) + bounds: 816 48 8 8 + - image: solid-color(103, 6, 0, 255, 8, 8) + bounds: 824 48 8 8 + - image: solid-color(104, 6, 0, 255, 8, 8) + bounds: 832 48 8 8 + - image: solid-color(105, 6, 0, 255, 8, 8) + bounds: 840 48 8 8 + - image: solid-color(106, 6, 0, 255, 8, 8) + bounds: 848 48 8 8 + - image: solid-color(107, 6, 0, 255, 8, 8) + bounds: 856 48 8 8 + - image: solid-color(108, 6, 0, 255, 8, 8) + bounds: 864 48 8 8 + - image: solid-color(109, 6, 0, 255, 8, 8) + bounds: 872 48 8 8 + - image: solid-color(110, 6, 0, 255, 8, 8) + bounds: 880 48 8 8 + - image: solid-color(111, 6, 0, 255, 8, 8) + bounds: 888 48 8 8 + - image: solid-color(112, 6, 0, 255, 8, 8) + bounds: 896 48 8 8 + - image: solid-color(113, 6, 0, 255, 8, 8) + bounds: 904 48 8 8 + - image: solid-color(114, 6, 0, 255, 8, 8) + bounds: 912 48 8 8 + - image: solid-color(115, 6, 0, 255, 8, 8) + bounds: 920 48 8 8 + - image: solid-color(116, 6, 0, 255, 8, 8) + bounds: 928 48 8 8 + - image: solid-color(117, 6, 0, 255, 8, 8) + bounds: 936 48 8 8 + - image: solid-color(118, 6, 0, 255, 8, 8) + bounds: 944 48 8 8 + - image: solid-color(119, 6, 0, 255, 8, 8) + bounds: 952 48 8 8 + - image: solid-color(120, 6, 0, 255, 8, 8) + bounds: 960 48 8 8 + - image: solid-color(121, 6, 0, 255, 8, 8) + bounds: 968 48 8 8 + - image: solid-color(122, 6, 0, 255, 8, 8) + bounds: 976 48 8 8 + - image: solid-color(123, 6, 0, 255, 8, 8) + bounds: 984 48 8 8 + - image: solid-color(124, 6, 0, 255, 8, 8) + bounds: 992 48 8 8 + - image: solid-color(125, 6, 0, 255, 8, 8) + bounds: 1000 48 8 8 + - image: solid-color(126, 6, 0, 255, 8, 8) + bounds: 1008 48 8 8 + - image: solid-color(127, 6, 0, 255, 8, 8) + bounds: 1016 48 8 8 + - image: solid-color(0, 7, 0, 255, 8, 8) + bounds: 0 56 8 8 + - image: solid-color(1, 7, 0, 255, 8, 8) + bounds: 8 56 8 8 + - image: solid-color(2, 7, 0, 255, 8, 8) + bounds: 16 56 8 8 + - image: solid-color(3, 7, 0, 255, 8, 8) + bounds: 24 56 8 8 + - image: solid-color(4, 7, 0, 255, 8, 8) + bounds: 32 56 8 8 + - image: solid-color(5, 7, 0, 255, 8, 8) + bounds: 40 56 8 8 + - image: solid-color(6, 7, 0, 255, 8, 8) + bounds: 48 56 8 8 + - image: solid-color(7, 7, 0, 255, 8, 8) + bounds: 56 56 8 8 + - image: solid-color(8, 7, 0, 255, 8, 8) + bounds: 64 56 8 8 + - image: solid-color(9, 7, 0, 255, 8, 8) + bounds: 72 56 8 8 + - image: solid-color(10, 7, 0, 255, 8, 8) + bounds: 80 56 8 8 + - image: solid-color(11, 7, 0, 255, 8, 8) + bounds: 88 56 8 8 + - image: solid-color(12, 7, 0, 255, 8, 8) + bounds: 96 56 8 8 + - image: solid-color(13, 7, 0, 255, 8, 8) + bounds: 104 56 8 8 + - image: solid-color(14, 7, 0, 255, 8, 8) + bounds: 112 56 8 8 + - image: solid-color(15, 7, 0, 255, 8, 8) + bounds: 120 56 8 8 + - image: solid-color(16, 7, 0, 255, 8, 8) + bounds: 128 56 8 8 + - image: solid-color(17, 7, 0, 255, 8, 8) + bounds: 136 56 8 8 + - image: solid-color(18, 7, 0, 255, 8, 8) + bounds: 144 56 8 8 + - image: solid-color(19, 7, 0, 255, 8, 8) + bounds: 152 56 8 8 + - image: solid-color(20, 7, 0, 255, 8, 8) + bounds: 160 56 8 8 + - image: solid-color(21, 7, 0, 255, 8, 8) + bounds: 168 56 8 8 + - image: solid-color(22, 7, 0, 255, 8, 8) + bounds: 176 56 8 8 + - image: solid-color(23, 7, 0, 255, 8, 8) + bounds: 184 56 8 8 + - image: solid-color(24, 7, 0, 255, 8, 8) + bounds: 192 56 8 8 + - image: solid-color(25, 7, 0, 255, 8, 8) + bounds: 200 56 8 8 + - image: solid-color(26, 7, 0, 255, 8, 8) + bounds: 208 56 8 8 + - image: solid-color(27, 7, 0, 255, 8, 8) + bounds: 216 56 8 8 + - image: solid-color(28, 7, 0, 255, 8, 8) + bounds: 224 56 8 8 + - image: solid-color(29, 7, 0, 255, 8, 8) + bounds: 232 56 8 8 + - image: solid-color(30, 7, 0, 255, 8, 8) + bounds: 240 56 8 8 + - image: solid-color(31, 7, 0, 255, 8, 8) + bounds: 248 56 8 8 + - image: solid-color(32, 7, 0, 255, 8, 8) + bounds: 256 56 8 8 + - image: solid-color(33, 7, 0, 255, 8, 8) + bounds: 264 56 8 8 + - image: solid-color(34, 7, 0, 255, 8, 8) + bounds: 272 56 8 8 + - image: solid-color(35, 7, 0, 255, 8, 8) + bounds: 280 56 8 8 + - image: solid-color(36, 7, 0, 255, 8, 8) + bounds: 288 56 8 8 + - image: solid-color(37, 7, 0, 255, 8, 8) + bounds: 296 56 8 8 + - image: solid-color(38, 7, 0, 255, 8, 8) + bounds: 304 56 8 8 + - image: solid-color(39, 7, 0, 255, 8, 8) + bounds: 312 56 8 8 + - image: solid-color(40, 7, 0, 255, 8, 8) + bounds: 320 56 8 8 + - image: solid-color(41, 7, 0, 255, 8, 8) + bounds: 328 56 8 8 + - image: solid-color(42, 7, 0, 255, 8, 8) + bounds: 336 56 8 8 + - image: solid-color(43, 7, 0, 255, 8, 8) + bounds: 344 56 8 8 + - image: solid-color(44, 7, 0, 255, 8, 8) + bounds: 352 56 8 8 + - image: solid-color(45, 7, 0, 255, 8, 8) + bounds: 360 56 8 8 + - image: solid-color(46, 7, 0, 255, 8, 8) + bounds: 368 56 8 8 + - image: solid-color(47, 7, 0, 255, 8, 8) + bounds: 376 56 8 8 + - image: solid-color(48, 7, 0, 255, 8, 8) + bounds: 384 56 8 8 + - image: solid-color(49, 7, 0, 255, 8, 8) + bounds: 392 56 8 8 + - image: solid-color(50, 7, 0, 255, 8, 8) + bounds: 400 56 8 8 + - image: solid-color(51, 7, 0, 255, 8, 8) + bounds: 408 56 8 8 + - image: solid-color(52, 7, 0, 255, 8, 8) + bounds: 416 56 8 8 + - image: solid-color(53, 7, 0, 255, 8, 8) + bounds: 424 56 8 8 + - image: solid-color(54, 7, 0, 255, 8, 8) + bounds: 432 56 8 8 + - image: solid-color(55, 7, 0, 255, 8, 8) + bounds: 440 56 8 8 + - image: solid-color(56, 7, 0, 255, 8, 8) + bounds: 448 56 8 8 + - image: solid-color(57, 7, 0, 255, 8, 8) + bounds: 456 56 8 8 + - image: solid-color(58, 7, 0, 255, 8, 8) + bounds: 464 56 8 8 + - image: solid-color(59, 7, 0, 255, 8, 8) + bounds: 472 56 8 8 + - image: solid-color(60, 7, 0, 255, 8, 8) + bounds: 480 56 8 8 + - image: solid-color(61, 7, 0, 255, 8, 8) + bounds: 488 56 8 8 + - image: solid-color(62, 7, 0, 255, 8, 8) + bounds: 496 56 8 8 + - image: solid-color(63, 7, 0, 255, 8, 8) + bounds: 504 56 8 8 + - image: solid-color(64, 7, 0, 255, 8, 8) + bounds: 512 56 8 8 + - image: solid-color(65, 7, 0, 255, 8, 8) + bounds: 520 56 8 8 + - image: solid-color(66, 7, 0, 255, 8, 8) + bounds: 528 56 8 8 + - image: solid-color(67, 7, 0, 255, 8, 8) + bounds: 536 56 8 8 + - image: solid-color(68, 7, 0, 255, 8, 8) + bounds: 544 56 8 8 + - image: solid-color(69, 7, 0, 255, 8, 8) + bounds: 552 56 8 8 + - image: solid-color(70, 7, 0, 255, 8, 8) + bounds: 560 56 8 8 + - image: solid-color(71, 7, 0, 255, 8, 8) + bounds: 568 56 8 8 + - image: solid-color(72, 7, 0, 255, 8, 8) + bounds: 576 56 8 8 + - image: solid-color(73, 7, 0, 255, 8, 8) + bounds: 584 56 8 8 + - image: solid-color(74, 7, 0, 255, 8, 8) + bounds: 592 56 8 8 + - image: solid-color(75, 7, 0, 255, 8, 8) + bounds: 600 56 8 8 + - image: solid-color(76, 7, 0, 255, 8, 8) + bounds: 608 56 8 8 + - image: solid-color(77, 7, 0, 255, 8, 8) + bounds: 616 56 8 8 + - image: solid-color(78, 7, 0, 255, 8, 8) + bounds: 624 56 8 8 + - image: solid-color(79, 7, 0, 255, 8, 8) + bounds: 632 56 8 8 + - image: solid-color(80, 7, 0, 255, 8, 8) + bounds: 640 56 8 8 + - image: solid-color(81, 7, 0, 255, 8, 8) + bounds: 648 56 8 8 + - image: solid-color(82, 7, 0, 255, 8, 8) + bounds: 656 56 8 8 + - image: solid-color(83, 7, 0, 255, 8, 8) + bounds: 664 56 8 8 + - image: solid-color(84, 7, 0, 255, 8, 8) + bounds: 672 56 8 8 + - image: solid-color(85, 7, 0, 255, 8, 8) + bounds: 680 56 8 8 + - image: solid-color(86, 7, 0, 255, 8, 8) + bounds: 688 56 8 8 + - image: solid-color(87, 7, 0, 255, 8, 8) + bounds: 696 56 8 8 + - image: solid-color(88, 7, 0, 255, 8, 8) + bounds: 704 56 8 8 + - image: solid-color(89, 7, 0, 255, 8, 8) + bounds: 712 56 8 8 + - image: solid-color(90, 7, 0, 255, 8, 8) + bounds: 720 56 8 8 + - image: solid-color(91, 7, 0, 255, 8, 8) + bounds: 728 56 8 8 + - image: solid-color(92, 7, 0, 255, 8, 8) + bounds: 736 56 8 8 + - image: solid-color(93, 7, 0, 255, 8, 8) + bounds: 744 56 8 8 + - image: solid-color(94, 7, 0, 255, 8, 8) + bounds: 752 56 8 8 + - image: solid-color(95, 7, 0, 255, 8, 8) + bounds: 760 56 8 8 + - image: solid-color(96, 7, 0, 255, 8, 8) + bounds: 768 56 8 8 + - image: solid-color(97, 7, 0, 255, 8, 8) + bounds: 776 56 8 8 + - image: solid-color(98, 7, 0, 255, 8, 8) + bounds: 784 56 8 8 + - image: solid-color(99, 7, 0, 255, 8, 8) + bounds: 792 56 8 8 + - image: solid-color(100, 7, 0, 255, 8, 8) + bounds: 800 56 8 8 + - image: solid-color(101, 7, 0, 255, 8, 8) + bounds: 808 56 8 8 + - image: solid-color(102, 7, 0, 255, 8, 8) + bounds: 816 56 8 8 + - image: solid-color(103, 7, 0, 255, 8, 8) + bounds: 824 56 8 8 + - image: solid-color(104, 7, 0, 255, 8, 8) + bounds: 832 56 8 8 + - image: solid-color(105, 7, 0, 255, 8, 8) + bounds: 840 56 8 8 + - image: solid-color(106, 7, 0, 255, 8, 8) + bounds: 848 56 8 8 + - image: solid-color(107, 7, 0, 255, 8, 8) + bounds: 856 56 8 8 + - image: solid-color(108, 7, 0, 255, 8, 8) + bounds: 864 56 8 8 + - image: solid-color(109, 7, 0, 255, 8, 8) + bounds: 872 56 8 8 + - image: solid-color(110, 7, 0, 255, 8, 8) + bounds: 880 56 8 8 + - image: solid-color(111, 7, 0, 255, 8, 8) + bounds: 888 56 8 8 + - image: solid-color(112, 7, 0, 255, 8, 8) + bounds: 896 56 8 8 + - image: solid-color(113, 7, 0, 255, 8, 8) + bounds: 904 56 8 8 + - image: solid-color(114, 7, 0, 255, 8, 8) + bounds: 912 56 8 8 + - image: solid-color(115, 7, 0, 255, 8, 8) + bounds: 920 56 8 8 + - image: solid-color(116, 7, 0, 255, 8, 8) + bounds: 928 56 8 8 + - image: solid-color(117, 7, 0, 255, 8, 8) + bounds: 936 56 8 8 + - image: solid-color(118, 7, 0, 255, 8, 8) + bounds: 944 56 8 8 + - image: solid-color(119, 7, 0, 255, 8, 8) + bounds: 952 56 8 8 + - image: solid-color(120, 7, 0, 255, 8, 8) + bounds: 960 56 8 8 + - image: solid-color(121, 7, 0, 255, 8, 8) + bounds: 968 56 8 8 + - image: solid-color(122, 7, 0, 255, 8, 8) + bounds: 976 56 8 8 + - image: solid-color(123, 7, 0, 255, 8, 8) + bounds: 984 56 8 8 + - image: solid-color(124, 7, 0, 255, 8, 8) + bounds: 992 56 8 8 + - image: solid-color(125, 7, 0, 255, 8, 8) + bounds: 1000 56 8 8 + - image: solid-color(126, 7, 0, 255, 8, 8) + bounds: 1008 56 8 8 + - image: solid-color(127, 7, 0, 255, 8, 8) + bounds: 1016 56 8 8 + - image: solid-color(0, 8, 0, 255, 8, 8) + bounds: 0 64 8 8 + - image: solid-color(1, 8, 0, 255, 8, 8) + bounds: 8 64 8 8 + - image: solid-color(2, 8, 0, 255, 8, 8) + bounds: 16 64 8 8 + - image: solid-color(3, 8, 0, 255, 8, 8) + bounds: 24 64 8 8 + - image: solid-color(4, 8, 0, 255, 8, 8) + bounds: 32 64 8 8 + - image: solid-color(5, 8, 0, 255, 8, 8) + bounds: 40 64 8 8 + - image: solid-color(6, 8, 0, 255, 8, 8) + bounds: 48 64 8 8 + - image: solid-color(7, 8, 0, 255, 8, 8) + bounds: 56 64 8 8 + - image: solid-color(8, 8, 0, 255, 8, 8) + bounds: 64 64 8 8 + - image: solid-color(9, 8, 0, 255, 8, 8) + bounds: 72 64 8 8 + - image: solid-color(10, 8, 0, 255, 8, 8) + bounds: 80 64 8 8 + - image: solid-color(11, 8, 0, 255, 8, 8) + bounds: 88 64 8 8 + - image: solid-color(12, 8, 0, 255, 8, 8) + bounds: 96 64 8 8 + - image: solid-color(13, 8, 0, 255, 8, 8) + bounds: 104 64 8 8 + - image: solid-color(14, 8, 0, 255, 8, 8) + bounds: 112 64 8 8 + - image: solid-color(15, 8, 0, 255, 8, 8) + bounds: 120 64 8 8 + - image: solid-color(16, 8, 0, 255, 8, 8) + bounds: 128 64 8 8 + - image: solid-color(17, 8, 0, 255, 8, 8) + bounds: 136 64 8 8 + - image: solid-color(18, 8, 0, 255, 8, 8) + bounds: 144 64 8 8 + - image: solid-color(19, 8, 0, 255, 8, 8) + bounds: 152 64 8 8 + - image: solid-color(20, 8, 0, 255, 8, 8) + bounds: 160 64 8 8 + - image: solid-color(21, 8, 0, 255, 8, 8) + bounds: 168 64 8 8 + - image: solid-color(22, 8, 0, 255, 8, 8) + bounds: 176 64 8 8 + - image: solid-color(23, 8, 0, 255, 8, 8) + bounds: 184 64 8 8 + - image: solid-color(24, 8, 0, 255, 8, 8) + bounds: 192 64 8 8 + - image: solid-color(25, 8, 0, 255, 8, 8) + bounds: 200 64 8 8 + - image: solid-color(26, 8, 0, 255, 8, 8) + bounds: 208 64 8 8 + - image: solid-color(27, 8, 0, 255, 8, 8) + bounds: 216 64 8 8 + - image: solid-color(28, 8, 0, 255, 8, 8) + bounds: 224 64 8 8 + - image: solid-color(29, 8, 0, 255, 8, 8) + bounds: 232 64 8 8 + - image: solid-color(30, 8, 0, 255, 8, 8) + bounds: 240 64 8 8 + - image: solid-color(31, 8, 0, 255, 8, 8) + bounds: 248 64 8 8 + - image: solid-color(32, 8, 0, 255, 8, 8) + bounds: 256 64 8 8 + - image: solid-color(33, 8, 0, 255, 8, 8) + bounds: 264 64 8 8 + - image: solid-color(34, 8, 0, 255, 8, 8) + bounds: 272 64 8 8 + - image: solid-color(35, 8, 0, 255, 8, 8) + bounds: 280 64 8 8 + - image: solid-color(36, 8, 0, 255, 8, 8) + bounds: 288 64 8 8 + - image: solid-color(37, 8, 0, 255, 8, 8) + bounds: 296 64 8 8 + - image: solid-color(38, 8, 0, 255, 8, 8) + bounds: 304 64 8 8 + - image: solid-color(39, 8, 0, 255, 8, 8) + bounds: 312 64 8 8 + - image: solid-color(40, 8, 0, 255, 8, 8) + bounds: 320 64 8 8 + - image: solid-color(41, 8, 0, 255, 8, 8) + bounds: 328 64 8 8 + - image: solid-color(42, 8, 0, 255, 8, 8) + bounds: 336 64 8 8 + - image: solid-color(43, 8, 0, 255, 8, 8) + bounds: 344 64 8 8 + - image: solid-color(44, 8, 0, 255, 8, 8) + bounds: 352 64 8 8 + - image: solid-color(45, 8, 0, 255, 8, 8) + bounds: 360 64 8 8 + - image: solid-color(46, 8, 0, 255, 8, 8) + bounds: 368 64 8 8 + - image: solid-color(47, 8, 0, 255, 8, 8) + bounds: 376 64 8 8 + - image: solid-color(48, 8, 0, 255, 8, 8) + bounds: 384 64 8 8 + - image: solid-color(49, 8, 0, 255, 8, 8) + bounds: 392 64 8 8 + - image: solid-color(50, 8, 0, 255, 8, 8) + bounds: 400 64 8 8 + - image: solid-color(51, 8, 0, 255, 8, 8) + bounds: 408 64 8 8 + - image: solid-color(52, 8, 0, 255, 8, 8) + bounds: 416 64 8 8 + - image: solid-color(53, 8, 0, 255, 8, 8) + bounds: 424 64 8 8 + - image: solid-color(54, 8, 0, 255, 8, 8) + bounds: 432 64 8 8 + - image: solid-color(55, 8, 0, 255, 8, 8) + bounds: 440 64 8 8 + - image: solid-color(56, 8, 0, 255, 8, 8) + bounds: 448 64 8 8 + - image: solid-color(57, 8, 0, 255, 8, 8) + bounds: 456 64 8 8 + - image: solid-color(58, 8, 0, 255, 8, 8) + bounds: 464 64 8 8 + - image: solid-color(59, 8, 0, 255, 8, 8) + bounds: 472 64 8 8 + - image: solid-color(60, 8, 0, 255, 8, 8) + bounds: 480 64 8 8 + - image: solid-color(61, 8, 0, 255, 8, 8) + bounds: 488 64 8 8 + - image: solid-color(62, 8, 0, 255, 8, 8) + bounds: 496 64 8 8 + - image: solid-color(63, 8, 0, 255, 8, 8) + bounds: 504 64 8 8 + - image: solid-color(64, 8, 0, 255, 8, 8) + bounds: 512 64 8 8 + - image: solid-color(65, 8, 0, 255, 8, 8) + bounds: 520 64 8 8 + - image: solid-color(66, 8, 0, 255, 8, 8) + bounds: 528 64 8 8 + - image: solid-color(67, 8, 0, 255, 8, 8) + bounds: 536 64 8 8 + - image: solid-color(68, 8, 0, 255, 8, 8) + bounds: 544 64 8 8 + - image: solid-color(69, 8, 0, 255, 8, 8) + bounds: 552 64 8 8 + - image: solid-color(70, 8, 0, 255, 8, 8) + bounds: 560 64 8 8 + - image: solid-color(71, 8, 0, 255, 8, 8) + bounds: 568 64 8 8 + - image: solid-color(72, 8, 0, 255, 8, 8) + bounds: 576 64 8 8 + - image: solid-color(73, 8, 0, 255, 8, 8) + bounds: 584 64 8 8 + - image: solid-color(74, 8, 0, 255, 8, 8) + bounds: 592 64 8 8 + - image: solid-color(75, 8, 0, 255, 8, 8) + bounds: 600 64 8 8 + - image: solid-color(76, 8, 0, 255, 8, 8) + bounds: 608 64 8 8 + - image: solid-color(77, 8, 0, 255, 8, 8) + bounds: 616 64 8 8 + - image: solid-color(78, 8, 0, 255, 8, 8) + bounds: 624 64 8 8 + - image: solid-color(79, 8, 0, 255, 8, 8) + bounds: 632 64 8 8 + - image: solid-color(80, 8, 0, 255, 8, 8) + bounds: 640 64 8 8 + - image: solid-color(81, 8, 0, 255, 8, 8) + bounds: 648 64 8 8 + - image: solid-color(82, 8, 0, 255, 8, 8) + bounds: 656 64 8 8 + - image: solid-color(83, 8, 0, 255, 8, 8) + bounds: 664 64 8 8 + - image: solid-color(84, 8, 0, 255, 8, 8) + bounds: 672 64 8 8 + - image: solid-color(85, 8, 0, 255, 8, 8) + bounds: 680 64 8 8 + - image: solid-color(86, 8, 0, 255, 8, 8) + bounds: 688 64 8 8 + - image: solid-color(87, 8, 0, 255, 8, 8) + bounds: 696 64 8 8 + - image: solid-color(88, 8, 0, 255, 8, 8) + bounds: 704 64 8 8 + - image: solid-color(89, 8, 0, 255, 8, 8) + bounds: 712 64 8 8 + - image: solid-color(90, 8, 0, 255, 8, 8) + bounds: 720 64 8 8 + - image: solid-color(91, 8, 0, 255, 8, 8) + bounds: 728 64 8 8 + - image: solid-color(92, 8, 0, 255, 8, 8) + bounds: 736 64 8 8 + - image: solid-color(93, 8, 0, 255, 8, 8) + bounds: 744 64 8 8 + - image: solid-color(94, 8, 0, 255, 8, 8) + bounds: 752 64 8 8 + - image: solid-color(95, 8, 0, 255, 8, 8) + bounds: 760 64 8 8 + - image: solid-color(96, 8, 0, 255, 8, 8) + bounds: 768 64 8 8 + - image: solid-color(97, 8, 0, 255, 8, 8) + bounds: 776 64 8 8 + - image: solid-color(98, 8, 0, 255, 8, 8) + bounds: 784 64 8 8 + - image: solid-color(99, 8, 0, 255, 8, 8) + bounds: 792 64 8 8 + - image: solid-color(100, 8, 0, 255, 8, 8) + bounds: 800 64 8 8 + - image: solid-color(101, 8, 0, 255, 8, 8) + bounds: 808 64 8 8 + - image: solid-color(102, 8, 0, 255, 8, 8) + bounds: 816 64 8 8 + - image: solid-color(103, 8, 0, 255, 8, 8) + bounds: 824 64 8 8 + - image: solid-color(104, 8, 0, 255, 8, 8) + bounds: 832 64 8 8 + - image: solid-color(105, 8, 0, 255, 8, 8) + bounds: 840 64 8 8 + - image: solid-color(106, 8, 0, 255, 8, 8) + bounds: 848 64 8 8 + - image: solid-color(107, 8, 0, 255, 8, 8) + bounds: 856 64 8 8 + - image: solid-color(108, 8, 0, 255, 8, 8) + bounds: 864 64 8 8 + - image: solid-color(109, 8, 0, 255, 8, 8) + bounds: 872 64 8 8 + - image: solid-color(110, 8, 0, 255, 8, 8) + bounds: 880 64 8 8 + - image: solid-color(111, 8, 0, 255, 8, 8) + bounds: 888 64 8 8 + - image: solid-color(112, 8, 0, 255, 8, 8) + bounds: 896 64 8 8 + - image: solid-color(113, 8, 0, 255, 8, 8) + bounds: 904 64 8 8 + - image: solid-color(114, 8, 0, 255, 8, 8) + bounds: 912 64 8 8 + - image: solid-color(115, 8, 0, 255, 8, 8) + bounds: 920 64 8 8 + - image: solid-color(116, 8, 0, 255, 8, 8) + bounds: 928 64 8 8 + - image: solid-color(117, 8, 0, 255, 8, 8) + bounds: 936 64 8 8 + - image: solid-color(118, 8, 0, 255, 8, 8) + bounds: 944 64 8 8 + - image: solid-color(119, 8, 0, 255, 8, 8) + bounds: 952 64 8 8 + - image: solid-color(120, 8, 0, 255, 8, 8) + bounds: 960 64 8 8 + - image: solid-color(121, 8, 0, 255, 8, 8) + bounds: 968 64 8 8 + - image: solid-color(122, 8, 0, 255, 8, 8) + bounds: 976 64 8 8 + - image: solid-color(123, 8, 0, 255, 8, 8) + bounds: 984 64 8 8 + - image: solid-color(124, 8, 0, 255, 8, 8) + bounds: 992 64 8 8 + - image: solid-color(125, 8, 0, 255, 8, 8) + bounds: 1000 64 8 8 + - image: solid-color(126, 8, 0, 255, 8, 8) + bounds: 1008 64 8 8 + - image: solid-color(127, 8, 0, 255, 8, 8) + bounds: 1016 64 8 8 + - image: solid-color(0, 9, 0, 255, 8, 8) + bounds: 0 72 8 8 + - image: solid-color(1, 9, 0, 255, 8, 8) + bounds: 8 72 8 8 + - image: solid-color(2, 9, 0, 255, 8, 8) + bounds: 16 72 8 8 + - image: solid-color(3, 9, 0, 255, 8, 8) + bounds: 24 72 8 8 + - image: solid-color(4, 9, 0, 255, 8, 8) + bounds: 32 72 8 8 + - image: solid-color(5, 9, 0, 255, 8, 8) + bounds: 40 72 8 8 + - image: solid-color(6, 9, 0, 255, 8, 8) + bounds: 48 72 8 8 + - image: solid-color(7, 9, 0, 255, 8, 8) + bounds: 56 72 8 8 + - image: solid-color(8, 9, 0, 255, 8, 8) + bounds: 64 72 8 8 + - image: solid-color(9, 9, 0, 255, 8, 8) + bounds: 72 72 8 8 + - image: solid-color(10, 9, 0, 255, 8, 8) + bounds: 80 72 8 8 + - image: solid-color(11, 9, 0, 255, 8, 8) + bounds: 88 72 8 8 + - image: solid-color(12, 9, 0, 255, 8, 8) + bounds: 96 72 8 8 + - image: solid-color(13, 9, 0, 255, 8, 8) + bounds: 104 72 8 8 + - image: solid-color(14, 9, 0, 255, 8, 8) + bounds: 112 72 8 8 + - image: solid-color(15, 9, 0, 255, 8, 8) + bounds: 120 72 8 8 + - image: solid-color(16, 9, 0, 255, 8, 8) + bounds: 128 72 8 8 + - image: solid-color(17, 9, 0, 255, 8, 8) + bounds: 136 72 8 8 + - image: solid-color(18, 9, 0, 255, 8, 8) + bounds: 144 72 8 8 + - image: solid-color(19, 9, 0, 255, 8, 8) + bounds: 152 72 8 8 + - image: solid-color(20, 9, 0, 255, 8, 8) + bounds: 160 72 8 8 + - image: solid-color(21, 9, 0, 255, 8, 8) + bounds: 168 72 8 8 + - image: solid-color(22, 9, 0, 255, 8, 8) + bounds: 176 72 8 8 + - image: solid-color(23, 9, 0, 255, 8, 8) + bounds: 184 72 8 8 + - image: solid-color(24, 9, 0, 255, 8, 8) + bounds: 192 72 8 8 + - image: solid-color(25, 9, 0, 255, 8, 8) + bounds: 200 72 8 8 + - image: solid-color(26, 9, 0, 255, 8, 8) + bounds: 208 72 8 8 + - image: solid-color(27, 9, 0, 255, 8, 8) + bounds: 216 72 8 8 + - image: solid-color(28, 9, 0, 255, 8, 8) + bounds: 224 72 8 8 + - image: solid-color(29, 9, 0, 255, 8, 8) + bounds: 232 72 8 8 + - image: solid-color(30, 9, 0, 255, 8, 8) + bounds: 240 72 8 8 + - image: solid-color(31, 9, 0, 255, 8, 8) + bounds: 248 72 8 8 + - image: solid-color(32, 9, 0, 255, 8, 8) + bounds: 256 72 8 8 + - image: solid-color(33, 9, 0, 255, 8, 8) + bounds: 264 72 8 8 + - image: solid-color(34, 9, 0, 255, 8, 8) + bounds: 272 72 8 8 + - image: solid-color(35, 9, 0, 255, 8, 8) + bounds: 280 72 8 8 + - image: solid-color(36, 9, 0, 255, 8, 8) + bounds: 288 72 8 8 + - image: solid-color(37, 9, 0, 255, 8, 8) + bounds: 296 72 8 8 + - image: solid-color(38, 9, 0, 255, 8, 8) + bounds: 304 72 8 8 + - image: solid-color(39, 9, 0, 255, 8, 8) + bounds: 312 72 8 8 + - image: solid-color(40, 9, 0, 255, 8, 8) + bounds: 320 72 8 8 + - image: solid-color(41, 9, 0, 255, 8, 8) + bounds: 328 72 8 8 + - image: solid-color(42, 9, 0, 255, 8, 8) + bounds: 336 72 8 8 + - image: solid-color(43, 9, 0, 255, 8, 8) + bounds: 344 72 8 8 + - image: solid-color(44, 9, 0, 255, 8, 8) + bounds: 352 72 8 8 + - image: solid-color(45, 9, 0, 255, 8, 8) + bounds: 360 72 8 8 + - image: solid-color(46, 9, 0, 255, 8, 8) + bounds: 368 72 8 8 + - image: solid-color(47, 9, 0, 255, 8, 8) + bounds: 376 72 8 8 + - image: solid-color(48, 9, 0, 255, 8, 8) + bounds: 384 72 8 8 + - image: solid-color(49, 9, 0, 255, 8, 8) + bounds: 392 72 8 8 + - image: solid-color(50, 9, 0, 255, 8, 8) + bounds: 400 72 8 8 + - image: solid-color(51, 9, 0, 255, 8, 8) + bounds: 408 72 8 8 + - image: solid-color(52, 9, 0, 255, 8, 8) + bounds: 416 72 8 8 + - image: solid-color(53, 9, 0, 255, 8, 8) + bounds: 424 72 8 8 + - image: solid-color(54, 9, 0, 255, 8, 8) + bounds: 432 72 8 8 + - image: solid-color(55, 9, 0, 255, 8, 8) + bounds: 440 72 8 8 + - image: solid-color(56, 9, 0, 255, 8, 8) + bounds: 448 72 8 8 + - image: solid-color(57, 9, 0, 255, 8, 8) + bounds: 456 72 8 8 + - image: solid-color(58, 9, 0, 255, 8, 8) + bounds: 464 72 8 8 + - image: solid-color(59, 9, 0, 255, 8, 8) + bounds: 472 72 8 8 + - image: solid-color(60, 9, 0, 255, 8, 8) + bounds: 480 72 8 8 + - image: solid-color(61, 9, 0, 255, 8, 8) + bounds: 488 72 8 8 + - image: solid-color(62, 9, 0, 255, 8, 8) + bounds: 496 72 8 8 + - image: solid-color(63, 9, 0, 255, 8, 8) + bounds: 504 72 8 8 + - image: solid-color(64, 9, 0, 255, 8, 8) + bounds: 512 72 8 8 + - image: solid-color(65, 9, 0, 255, 8, 8) + bounds: 520 72 8 8 + - image: solid-color(66, 9, 0, 255, 8, 8) + bounds: 528 72 8 8 + - image: solid-color(67, 9, 0, 255, 8, 8) + bounds: 536 72 8 8 + - image: solid-color(68, 9, 0, 255, 8, 8) + bounds: 544 72 8 8 + - image: solid-color(69, 9, 0, 255, 8, 8) + bounds: 552 72 8 8 + - image: solid-color(70, 9, 0, 255, 8, 8) + bounds: 560 72 8 8 + - image: solid-color(71, 9, 0, 255, 8, 8) + bounds: 568 72 8 8 + - image: solid-color(72, 9, 0, 255, 8, 8) + bounds: 576 72 8 8 + - image: solid-color(73, 9, 0, 255, 8, 8) + bounds: 584 72 8 8 + - image: solid-color(74, 9, 0, 255, 8, 8) + bounds: 592 72 8 8 + - image: solid-color(75, 9, 0, 255, 8, 8) + bounds: 600 72 8 8 + - image: solid-color(76, 9, 0, 255, 8, 8) + bounds: 608 72 8 8 + - image: solid-color(77, 9, 0, 255, 8, 8) + bounds: 616 72 8 8 + - image: solid-color(78, 9, 0, 255, 8, 8) + bounds: 624 72 8 8 + - image: solid-color(79, 9, 0, 255, 8, 8) + bounds: 632 72 8 8 + - image: solid-color(80, 9, 0, 255, 8, 8) + bounds: 640 72 8 8 + - image: solid-color(81, 9, 0, 255, 8, 8) + bounds: 648 72 8 8 + - image: solid-color(82, 9, 0, 255, 8, 8) + bounds: 656 72 8 8 + - image: solid-color(83, 9, 0, 255, 8, 8) + bounds: 664 72 8 8 + - image: solid-color(84, 9, 0, 255, 8, 8) + bounds: 672 72 8 8 + - image: solid-color(85, 9, 0, 255, 8, 8) + bounds: 680 72 8 8 + - image: solid-color(86, 9, 0, 255, 8, 8) + bounds: 688 72 8 8 + - image: solid-color(87, 9, 0, 255, 8, 8) + bounds: 696 72 8 8 + - image: solid-color(88, 9, 0, 255, 8, 8) + bounds: 704 72 8 8 + - image: solid-color(89, 9, 0, 255, 8, 8) + bounds: 712 72 8 8 + - image: solid-color(90, 9, 0, 255, 8, 8) + bounds: 720 72 8 8 + - image: solid-color(91, 9, 0, 255, 8, 8) + bounds: 728 72 8 8 + - image: solid-color(92, 9, 0, 255, 8, 8) + bounds: 736 72 8 8 + - image: solid-color(93, 9, 0, 255, 8, 8) + bounds: 744 72 8 8 + - image: solid-color(94, 9, 0, 255, 8, 8) + bounds: 752 72 8 8 + - image: solid-color(95, 9, 0, 255, 8, 8) + bounds: 760 72 8 8 + - image: solid-color(96, 9, 0, 255, 8, 8) + bounds: 768 72 8 8 + - image: solid-color(97, 9, 0, 255, 8, 8) + bounds: 776 72 8 8 + - image: solid-color(98, 9, 0, 255, 8, 8) + bounds: 784 72 8 8 + - image: solid-color(99, 9, 0, 255, 8, 8) + bounds: 792 72 8 8 + - image: solid-color(100, 9, 0, 255, 8, 8) + bounds: 800 72 8 8 + - image: solid-color(101, 9, 0, 255, 8, 8) + bounds: 808 72 8 8 + - image: solid-color(102, 9, 0, 255, 8, 8) + bounds: 816 72 8 8 + - image: solid-color(103, 9, 0, 255, 8, 8) + bounds: 824 72 8 8 + - image: solid-color(104, 9, 0, 255, 8, 8) + bounds: 832 72 8 8 + - image: solid-color(105, 9, 0, 255, 8, 8) + bounds: 840 72 8 8 + - image: solid-color(106, 9, 0, 255, 8, 8) + bounds: 848 72 8 8 + - image: solid-color(107, 9, 0, 255, 8, 8) + bounds: 856 72 8 8 + - image: solid-color(108, 9, 0, 255, 8, 8) + bounds: 864 72 8 8 + - image: solid-color(109, 9, 0, 255, 8, 8) + bounds: 872 72 8 8 + - image: solid-color(110, 9, 0, 255, 8, 8) + bounds: 880 72 8 8 + - image: solid-color(111, 9, 0, 255, 8, 8) + bounds: 888 72 8 8 + - image: solid-color(112, 9, 0, 255, 8, 8) + bounds: 896 72 8 8 + - image: solid-color(113, 9, 0, 255, 8, 8) + bounds: 904 72 8 8 + - image: solid-color(114, 9, 0, 255, 8, 8) + bounds: 912 72 8 8 + - image: solid-color(115, 9, 0, 255, 8, 8) + bounds: 920 72 8 8 + - image: solid-color(116, 9, 0, 255, 8, 8) + bounds: 928 72 8 8 + - image: solid-color(117, 9, 0, 255, 8, 8) + bounds: 936 72 8 8 + - image: solid-color(118, 9, 0, 255, 8, 8) + bounds: 944 72 8 8 + - image: solid-color(119, 9, 0, 255, 8, 8) + bounds: 952 72 8 8 + - image: solid-color(120, 9, 0, 255, 8, 8) + bounds: 960 72 8 8 + - image: solid-color(121, 9, 0, 255, 8, 8) + bounds: 968 72 8 8 + - image: solid-color(122, 9, 0, 255, 8, 8) + bounds: 976 72 8 8 + - image: solid-color(123, 9, 0, 255, 8, 8) + bounds: 984 72 8 8 + - image: solid-color(124, 9, 0, 255, 8, 8) + bounds: 992 72 8 8 + - image: solid-color(125, 9, 0, 255, 8, 8) + bounds: 1000 72 8 8 + - image: solid-color(126, 9, 0, 255, 8, 8) + bounds: 1008 72 8 8 + - image: solid-color(127, 9, 0, 255, 8, 8) + bounds: 1016 72 8 8 + - image: solid-color(0, 10, 0, 255, 8, 8) + bounds: 0 80 8 8 + - image: solid-color(1, 10, 0, 255, 8, 8) + bounds: 8 80 8 8 + - image: solid-color(2, 10, 0, 255, 8, 8) + bounds: 16 80 8 8 + - image: solid-color(3, 10, 0, 255, 8, 8) + bounds: 24 80 8 8 + - image: solid-color(4, 10, 0, 255, 8, 8) + bounds: 32 80 8 8 + - image: solid-color(5, 10, 0, 255, 8, 8) + bounds: 40 80 8 8 + - image: solid-color(6, 10, 0, 255, 8, 8) + bounds: 48 80 8 8 + - image: solid-color(7, 10, 0, 255, 8, 8) + bounds: 56 80 8 8 + - image: solid-color(8, 10, 0, 255, 8, 8) + bounds: 64 80 8 8 + - image: solid-color(9, 10, 0, 255, 8, 8) + bounds: 72 80 8 8 + - image: solid-color(10, 10, 0, 255, 8, 8) + bounds: 80 80 8 8 + - image: solid-color(11, 10, 0, 255, 8, 8) + bounds: 88 80 8 8 + - image: solid-color(12, 10, 0, 255, 8, 8) + bounds: 96 80 8 8 + - image: solid-color(13, 10, 0, 255, 8, 8) + bounds: 104 80 8 8 + - image: solid-color(14, 10, 0, 255, 8, 8) + bounds: 112 80 8 8 + - image: solid-color(15, 10, 0, 255, 8, 8) + bounds: 120 80 8 8 + - image: solid-color(16, 10, 0, 255, 8, 8) + bounds: 128 80 8 8 + - image: solid-color(17, 10, 0, 255, 8, 8) + bounds: 136 80 8 8 + - image: solid-color(18, 10, 0, 255, 8, 8) + bounds: 144 80 8 8 + - image: solid-color(19, 10, 0, 255, 8, 8) + bounds: 152 80 8 8 + - image: solid-color(20, 10, 0, 255, 8, 8) + bounds: 160 80 8 8 + - image: solid-color(21, 10, 0, 255, 8, 8) + bounds: 168 80 8 8 + - image: solid-color(22, 10, 0, 255, 8, 8) + bounds: 176 80 8 8 + - image: solid-color(23, 10, 0, 255, 8, 8) + bounds: 184 80 8 8 + - image: solid-color(24, 10, 0, 255, 8, 8) + bounds: 192 80 8 8 + - image: solid-color(25, 10, 0, 255, 8, 8) + bounds: 200 80 8 8 + - image: solid-color(26, 10, 0, 255, 8, 8) + bounds: 208 80 8 8 + - image: solid-color(27, 10, 0, 255, 8, 8) + bounds: 216 80 8 8 + - image: solid-color(28, 10, 0, 255, 8, 8) + bounds: 224 80 8 8 + - image: solid-color(29, 10, 0, 255, 8, 8) + bounds: 232 80 8 8 + - image: solid-color(30, 10, 0, 255, 8, 8) + bounds: 240 80 8 8 + - image: solid-color(31, 10, 0, 255, 8, 8) + bounds: 248 80 8 8 + - image: solid-color(32, 10, 0, 255, 8, 8) + bounds: 256 80 8 8 + - image: solid-color(33, 10, 0, 255, 8, 8) + bounds: 264 80 8 8 + - image: solid-color(34, 10, 0, 255, 8, 8) + bounds: 272 80 8 8 + - image: solid-color(35, 10, 0, 255, 8, 8) + bounds: 280 80 8 8 + - image: solid-color(36, 10, 0, 255, 8, 8) + bounds: 288 80 8 8 + - image: solid-color(37, 10, 0, 255, 8, 8) + bounds: 296 80 8 8 + - image: solid-color(38, 10, 0, 255, 8, 8) + bounds: 304 80 8 8 + - image: solid-color(39, 10, 0, 255, 8, 8) + bounds: 312 80 8 8 + - image: solid-color(40, 10, 0, 255, 8, 8) + bounds: 320 80 8 8 + - image: solid-color(41, 10, 0, 255, 8, 8) + bounds: 328 80 8 8 + - image: solid-color(42, 10, 0, 255, 8, 8) + bounds: 336 80 8 8 + - image: solid-color(43, 10, 0, 255, 8, 8) + bounds: 344 80 8 8 + - image: solid-color(44, 10, 0, 255, 8, 8) + bounds: 352 80 8 8 + - image: solid-color(45, 10, 0, 255, 8, 8) + bounds: 360 80 8 8 + - image: solid-color(46, 10, 0, 255, 8, 8) + bounds: 368 80 8 8 + - image: solid-color(47, 10, 0, 255, 8, 8) + bounds: 376 80 8 8 + - image: solid-color(48, 10, 0, 255, 8, 8) + bounds: 384 80 8 8 + - image: solid-color(49, 10, 0, 255, 8, 8) + bounds: 392 80 8 8 + - image: solid-color(50, 10, 0, 255, 8, 8) + bounds: 400 80 8 8 + - image: solid-color(51, 10, 0, 255, 8, 8) + bounds: 408 80 8 8 + - image: solid-color(52, 10, 0, 255, 8, 8) + bounds: 416 80 8 8 + - image: solid-color(53, 10, 0, 255, 8, 8) + bounds: 424 80 8 8 + - image: solid-color(54, 10, 0, 255, 8, 8) + bounds: 432 80 8 8 + - image: solid-color(55, 10, 0, 255, 8, 8) + bounds: 440 80 8 8 + - image: solid-color(56, 10, 0, 255, 8, 8) + bounds: 448 80 8 8 + - image: solid-color(57, 10, 0, 255, 8, 8) + bounds: 456 80 8 8 + - image: solid-color(58, 10, 0, 255, 8, 8) + bounds: 464 80 8 8 + - image: solid-color(59, 10, 0, 255, 8, 8) + bounds: 472 80 8 8 + - image: solid-color(60, 10, 0, 255, 8, 8) + bounds: 480 80 8 8 + - image: solid-color(61, 10, 0, 255, 8, 8) + bounds: 488 80 8 8 + - image: solid-color(62, 10, 0, 255, 8, 8) + bounds: 496 80 8 8 + - image: solid-color(63, 10, 0, 255, 8, 8) + bounds: 504 80 8 8 + - image: solid-color(64, 10, 0, 255, 8, 8) + bounds: 512 80 8 8 + - image: solid-color(65, 10, 0, 255, 8, 8) + bounds: 520 80 8 8 + - image: solid-color(66, 10, 0, 255, 8, 8) + bounds: 528 80 8 8 + - image: solid-color(67, 10, 0, 255, 8, 8) + bounds: 536 80 8 8 + - image: solid-color(68, 10, 0, 255, 8, 8) + bounds: 544 80 8 8 + - image: solid-color(69, 10, 0, 255, 8, 8) + bounds: 552 80 8 8 + - image: solid-color(70, 10, 0, 255, 8, 8) + bounds: 560 80 8 8 + - image: solid-color(71, 10, 0, 255, 8, 8) + bounds: 568 80 8 8 + - image: solid-color(72, 10, 0, 255, 8, 8) + bounds: 576 80 8 8 + - image: solid-color(73, 10, 0, 255, 8, 8) + bounds: 584 80 8 8 + - image: solid-color(74, 10, 0, 255, 8, 8) + bounds: 592 80 8 8 + - image: solid-color(75, 10, 0, 255, 8, 8) + bounds: 600 80 8 8 + - image: solid-color(76, 10, 0, 255, 8, 8) + bounds: 608 80 8 8 + - image: solid-color(77, 10, 0, 255, 8, 8) + bounds: 616 80 8 8 + - image: solid-color(78, 10, 0, 255, 8, 8) + bounds: 624 80 8 8 + - image: solid-color(79, 10, 0, 255, 8, 8) + bounds: 632 80 8 8 + - image: solid-color(80, 10, 0, 255, 8, 8) + bounds: 640 80 8 8 + - image: solid-color(81, 10, 0, 255, 8, 8) + bounds: 648 80 8 8 + - image: solid-color(82, 10, 0, 255, 8, 8) + bounds: 656 80 8 8 + - image: solid-color(83, 10, 0, 255, 8, 8) + bounds: 664 80 8 8 + - image: solid-color(84, 10, 0, 255, 8, 8) + bounds: 672 80 8 8 + - image: solid-color(85, 10, 0, 255, 8, 8) + bounds: 680 80 8 8 + - image: solid-color(86, 10, 0, 255, 8, 8) + bounds: 688 80 8 8 + - image: solid-color(87, 10, 0, 255, 8, 8) + bounds: 696 80 8 8 + - image: solid-color(88, 10, 0, 255, 8, 8) + bounds: 704 80 8 8 + - image: solid-color(89, 10, 0, 255, 8, 8) + bounds: 712 80 8 8 + - image: solid-color(90, 10, 0, 255, 8, 8) + bounds: 720 80 8 8 + - image: solid-color(91, 10, 0, 255, 8, 8) + bounds: 728 80 8 8 + - image: solid-color(92, 10, 0, 255, 8, 8) + bounds: 736 80 8 8 + - image: solid-color(93, 10, 0, 255, 8, 8) + bounds: 744 80 8 8 + - image: solid-color(94, 10, 0, 255, 8, 8) + bounds: 752 80 8 8 + - image: solid-color(95, 10, 0, 255, 8, 8) + bounds: 760 80 8 8 + - image: solid-color(96, 10, 0, 255, 8, 8) + bounds: 768 80 8 8 + - image: solid-color(97, 10, 0, 255, 8, 8) + bounds: 776 80 8 8 + - image: solid-color(98, 10, 0, 255, 8, 8) + bounds: 784 80 8 8 + - image: solid-color(99, 10, 0, 255, 8, 8) + bounds: 792 80 8 8 + - image: solid-color(100, 10, 0, 255, 8, 8) + bounds: 800 80 8 8 + - image: solid-color(101, 10, 0, 255, 8, 8) + bounds: 808 80 8 8 + - image: solid-color(102, 10, 0, 255, 8, 8) + bounds: 816 80 8 8 + - image: solid-color(103, 10, 0, 255, 8, 8) + bounds: 824 80 8 8 + - image: solid-color(104, 10, 0, 255, 8, 8) + bounds: 832 80 8 8 + - image: solid-color(105, 10, 0, 255, 8, 8) + bounds: 840 80 8 8 + - image: solid-color(106, 10, 0, 255, 8, 8) + bounds: 848 80 8 8 + - image: solid-color(107, 10, 0, 255, 8, 8) + bounds: 856 80 8 8 + - image: solid-color(108, 10, 0, 255, 8, 8) + bounds: 864 80 8 8 + - image: solid-color(109, 10, 0, 255, 8, 8) + bounds: 872 80 8 8 + - image: solid-color(110, 10, 0, 255, 8, 8) + bounds: 880 80 8 8 + - image: solid-color(111, 10, 0, 255, 8, 8) + bounds: 888 80 8 8 + - image: solid-color(112, 10, 0, 255, 8, 8) + bounds: 896 80 8 8 + - image: solid-color(113, 10, 0, 255, 8, 8) + bounds: 904 80 8 8 + - image: solid-color(114, 10, 0, 255, 8, 8) + bounds: 912 80 8 8 + - image: solid-color(115, 10, 0, 255, 8, 8) + bounds: 920 80 8 8 + - image: solid-color(116, 10, 0, 255, 8, 8) + bounds: 928 80 8 8 + - image: solid-color(117, 10, 0, 255, 8, 8) + bounds: 936 80 8 8 + - image: solid-color(118, 10, 0, 255, 8, 8) + bounds: 944 80 8 8 + - image: solid-color(119, 10, 0, 255, 8, 8) + bounds: 952 80 8 8 + - image: solid-color(120, 10, 0, 255, 8, 8) + bounds: 960 80 8 8 + - image: solid-color(121, 10, 0, 255, 8, 8) + bounds: 968 80 8 8 + - image: solid-color(122, 10, 0, 255, 8, 8) + bounds: 976 80 8 8 + - image: solid-color(123, 10, 0, 255, 8, 8) + bounds: 984 80 8 8 + - image: solid-color(124, 10, 0, 255, 8, 8) + bounds: 992 80 8 8 + - image: solid-color(125, 10, 0, 255, 8, 8) + bounds: 1000 80 8 8 + - image: solid-color(126, 10, 0, 255, 8, 8) + bounds: 1008 80 8 8 + - image: solid-color(127, 10, 0, 255, 8, 8) + bounds: 1016 80 8 8 + - image: solid-color(0, 11, 0, 255, 8, 8) + bounds: 0 88 8 8 + - image: solid-color(1, 11, 0, 255, 8, 8) + bounds: 8 88 8 8 + - image: solid-color(2, 11, 0, 255, 8, 8) + bounds: 16 88 8 8 + - image: solid-color(3, 11, 0, 255, 8, 8) + bounds: 24 88 8 8 + - image: solid-color(4, 11, 0, 255, 8, 8) + bounds: 32 88 8 8 + - image: solid-color(5, 11, 0, 255, 8, 8) + bounds: 40 88 8 8 + - image: solid-color(6, 11, 0, 255, 8, 8) + bounds: 48 88 8 8 + - image: solid-color(7, 11, 0, 255, 8, 8) + bounds: 56 88 8 8 + - image: solid-color(8, 11, 0, 255, 8, 8) + bounds: 64 88 8 8 + - image: solid-color(9, 11, 0, 255, 8, 8) + bounds: 72 88 8 8 + - image: solid-color(10, 11, 0, 255, 8, 8) + bounds: 80 88 8 8 + - image: solid-color(11, 11, 0, 255, 8, 8) + bounds: 88 88 8 8 + - image: solid-color(12, 11, 0, 255, 8, 8) + bounds: 96 88 8 8 + - image: solid-color(13, 11, 0, 255, 8, 8) + bounds: 104 88 8 8 + - image: solid-color(14, 11, 0, 255, 8, 8) + bounds: 112 88 8 8 + - image: solid-color(15, 11, 0, 255, 8, 8) + bounds: 120 88 8 8 + - image: solid-color(16, 11, 0, 255, 8, 8) + bounds: 128 88 8 8 + - image: solid-color(17, 11, 0, 255, 8, 8) + bounds: 136 88 8 8 + - image: solid-color(18, 11, 0, 255, 8, 8) + bounds: 144 88 8 8 + - image: solid-color(19, 11, 0, 255, 8, 8) + bounds: 152 88 8 8 + - image: solid-color(20, 11, 0, 255, 8, 8) + bounds: 160 88 8 8 + - image: solid-color(21, 11, 0, 255, 8, 8) + bounds: 168 88 8 8 + - image: solid-color(22, 11, 0, 255, 8, 8) + bounds: 176 88 8 8 + - image: solid-color(23, 11, 0, 255, 8, 8) + bounds: 184 88 8 8 + - image: solid-color(24, 11, 0, 255, 8, 8) + bounds: 192 88 8 8 + - image: solid-color(25, 11, 0, 255, 8, 8) + bounds: 200 88 8 8 + - image: solid-color(26, 11, 0, 255, 8, 8) + bounds: 208 88 8 8 + - image: solid-color(27, 11, 0, 255, 8, 8) + bounds: 216 88 8 8 + - image: solid-color(28, 11, 0, 255, 8, 8) + bounds: 224 88 8 8 + - image: solid-color(29, 11, 0, 255, 8, 8) + bounds: 232 88 8 8 + - image: solid-color(30, 11, 0, 255, 8, 8) + bounds: 240 88 8 8 + - image: solid-color(31, 11, 0, 255, 8, 8) + bounds: 248 88 8 8 + - image: solid-color(32, 11, 0, 255, 8, 8) + bounds: 256 88 8 8 + - image: solid-color(33, 11, 0, 255, 8, 8) + bounds: 264 88 8 8 + - image: solid-color(34, 11, 0, 255, 8, 8) + bounds: 272 88 8 8 + - image: solid-color(35, 11, 0, 255, 8, 8) + bounds: 280 88 8 8 + - image: solid-color(36, 11, 0, 255, 8, 8) + bounds: 288 88 8 8 + - image: solid-color(37, 11, 0, 255, 8, 8) + bounds: 296 88 8 8 + - image: solid-color(38, 11, 0, 255, 8, 8) + bounds: 304 88 8 8 + - image: solid-color(39, 11, 0, 255, 8, 8) + bounds: 312 88 8 8 + - image: solid-color(40, 11, 0, 255, 8, 8) + bounds: 320 88 8 8 + - image: solid-color(41, 11, 0, 255, 8, 8) + bounds: 328 88 8 8 + - image: solid-color(42, 11, 0, 255, 8, 8) + bounds: 336 88 8 8 + - image: solid-color(43, 11, 0, 255, 8, 8) + bounds: 344 88 8 8 + - image: solid-color(44, 11, 0, 255, 8, 8) + bounds: 352 88 8 8 + - image: solid-color(45, 11, 0, 255, 8, 8) + bounds: 360 88 8 8 + - image: solid-color(46, 11, 0, 255, 8, 8) + bounds: 368 88 8 8 + - image: solid-color(47, 11, 0, 255, 8, 8) + bounds: 376 88 8 8 + - image: solid-color(48, 11, 0, 255, 8, 8) + bounds: 384 88 8 8 + - image: solid-color(49, 11, 0, 255, 8, 8) + bounds: 392 88 8 8 + - image: solid-color(50, 11, 0, 255, 8, 8) + bounds: 400 88 8 8 + - image: solid-color(51, 11, 0, 255, 8, 8) + bounds: 408 88 8 8 + - image: solid-color(52, 11, 0, 255, 8, 8) + bounds: 416 88 8 8 + - image: solid-color(53, 11, 0, 255, 8, 8) + bounds: 424 88 8 8 + - image: solid-color(54, 11, 0, 255, 8, 8) + bounds: 432 88 8 8 + - image: solid-color(55, 11, 0, 255, 8, 8) + bounds: 440 88 8 8 + - image: solid-color(56, 11, 0, 255, 8, 8) + bounds: 448 88 8 8 + - image: solid-color(57, 11, 0, 255, 8, 8) + bounds: 456 88 8 8 + - image: solid-color(58, 11, 0, 255, 8, 8) + bounds: 464 88 8 8 + - image: solid-color(59, 11, 0, 255, 8, 8) + bounds: 472 88 8 8 + - image: solid-color(60, 11, 0, 255, 8, 8) + bounds: 480 88 8 8 + - image: solid-color(61, 11, 0, 255, 8, 8) + bounds: 488 88 8 8 + - image: solid-color(62, 11, 0, 255, 8, 8) + bounds: 496 88 8 8 + - image: solid-color(63, 11, 0, 255, 8, 8) + bounds: 504 88 8 8 + - image: solid-color(64, 11, 0, 255, 8, 8) + bounds: 512 88 8 8 + - image: solid-color(65, 11, 0, 255, 8, 8) + bounds: 520 88 8 8 + - image: solid-color(66, 11, 0, 255, 8, 8) + bounds: 528 88 8 8 + - image: solid-color(67, 11, 0, 255, 8, 8) + bounds: 536 88 8 8 + - image: solid-color(68, 11, 0, 255, 8, 8) + bounds: 544 88 8 8 + - image: solid-color(69, 11, 0, 255, 8, 8) + bounds: 552 88 8 8 + - image: solid-color(70, 11, 0, 255, 8, 8) + bounds: 560 88 8 8 + - image: solid-color(71, 11, 0, 255, 8, 8) + bounds: 568 88 8 8 + - image: solid-color(72, 11, 0, 255, 8, 8) + bounds: 576 88 8 8 + - image: solid-color(73, 11, 0, 255, 8, 8) + bounds: 584 88 8 8 + - image: solid-color(74, 11, 0, 255, 8, 8) + bounds: 592 88 8 8 + - image: solid-color(75, 11, 0, 255, 8, 8) + bounds: 600 88 8 8 + - image: solid-color(76, 11, 0, 255, 8, 8) + bounds: 608 88 8 8 + - image: solid-color(77, 11, 0, 255, 8, 8) + bounds: 616 88 8 8 + - image: solid-color(78, 11, 0, 255, 8, 8) + bounds: 624 88 8 8 + - image: solid-color(79, 11, 0, 255, 8, 8) + bounds: 632 88 8 8 + - image: solid-color(80, 11, 0, 255, 8, 8) + bounds: 640 88 8 8 + - image: solid-color(81, 11, 0, 255, 8, 8) + bounds: 648 88 8 8 + - image: solid-color(82, 11, 0, 255, 8, 8) + bounds: 656 88 8 8 + - image: solid-color(83, 11, 0, 255, 8, 8) + bounds: 664 88 8 8 + - image: solid-color(84, 11, 0, 255, 8, 8) + bounds: 672 88 8 8 + - image: solid-color(85, 11, 0, 255, 8, 8) + bounds: 680 88 8 8 + - image: solid-color(86, 11, 0, 255, 8, 8) + bounds: 688 88 8 8 + - image: solid-color(87, 11, 0, 255, 8, 8) + bounds: 696 88 8 8 + - image: solid-color(88, 11, 0, 255, 8, 8) + bounds: 704 88 8 8 + - image: solid-color(89, 11, 0, 255, 8, 8) + bounds: 712 88 8 8 + - image: solid-color(90, 11, 0, 255, 8, 8) + bounds: 720 88 8 8 + - image: solid-color(91, 11, 0, 255, 8, 8) + bounds: 728 88 8 8 + - image: solid-color(92, 11, 0, 255, 8, 8) + bounds: 736 88 8 8 + - image: solid-color(93, 11, 0, 255, 8, 8) + bounds: 744 88 8 8 + - image: solid-color(94, 11, 0, 255, 8, 8) + bounds: 752 88 8 8 + - image: solid-color(95, 11, 0, 255, 8, 8) + bounds: 760 88 8 8 + - image: solid-color(96, 11, 0, 255, 8, 8) + bounds: 768 88 8 8 + - image: solid-color(97, 11, 0, 255, 8, 8) + bounds: 776 88 8 8 + - image: solid-color(98, 11, 0, 255, 8, 8) + bounds: 784 88 8 8 + - image: solid-color(99, 11, 0, 255, 8, 8) + bounds: 792 88 8 8 + - image: solid-color(100, 11, 0, 255, 8, 8) + bounds: 800 88 8 8 + - image: solid-color(101, 11, 0, 255, 8, 8) + bounds: 808 88 8 8 + - image: solid-color(102, 11, 0, 255, 8, 8) + bounds: 816 88 8 8 + - image: solid-color(103, 11, 0, 255, 8, 8) + bounds: 824 88 8 8 + - image: solid-color(104, 11, 0, 255, 8, 8) + bounds: 832 88 8 8 + - image: solid-color(105, 11, 0, 255, 8, 8) + bounds: 840 88 8 8 + - image: solid-color(106, 11, 0, 255, 8, 8) + bounds: 848 88 8 8 + - image: solid-color(107, 11, 0, 255, 8, 8) + bounds: 856 88 8 8 + - image: solid-color(108, 11, 0, 255, 8, 8) + bounds: 864 88 8 8 + - image: solid-color(109, 11, 0, 255, 8, 8) + bounds: 872 88 8 8 + - image: solid-color(110, 11, 0, 255, 8, 8) + bounds: 880 88 8 8 + - image: solid-color(111, 11, 0, 255, 8, 8) + bounds: 888 88 8 8 + - image: solid-color(112, 11, 0, 255, 8, 8) + bounds: 896 88 8 8 + - image: solid-color(113, 11, 0, 255, 8, 8) + bounds: 904 88 8 8 + - image: solid-color(114, 11, 0, 255, 8, 8) + bounds: 912 88 8 8 + - image: solid-color(115, 11, 0, 255, 8, 8) + bounds: 920 88 8 8 + - image: solid-color(116, 11, 0, 255, 8, 8) + bounds: 928 88 8 8 + - image: solid-color(117, 11, 0, 255, 8, 8) + bounds: 936 88 8 8 + - image: solid-color(118, 11, 0, 255, 8, 8) + bounds: 944 88 8 8 + - image: solid-color(119, 11, 0, 255, 8, 8) + bounds: 952 88 8 8 + - image: solid-color(120, 11, 0, 255, 8, 8) + bounds: 960 88 8 8 + - image: solid-color(121, 11, 0, 255, 8, 8) + bounds: 968 88 8 8 + - image: solid-color(122, 11, 0, 255, 8, 8) + bounds: 976 88 8 8 + - image: solid-color(123, 11, 0, 255, 8, 8) + bounds: 984 88 8 8 + - image: solid-color(124, 11, 0, 255, 8, 8) + bounds: 992 88 8 8 + - image: solid-color(125, 11, 0, 255, 8, 8) + bounds: 1000 88 8 8 + - image: solid-color(126, 11, 0, 255, 8, 8) + bounds: 1008 88 8 8 + - image: solid-color(127, 11, 0, 255, 8, 8) + bounds: 1016 88 8 8 + - image: solid-color(0, 12, 0, 255, 8, 8) + bounds: 0 96 8 8 + - image: solid-color(1, 12, 0, 255, 8, 8) + bounds: 8 96 8 8 + - image: solid-color(2, 12, 0, 255, 8, 8) + bounds: 16 96 8 8 + - image: solid-color(3, 12, 0, 255, 8, 8) + bounds: 24 96 8 8 + - image: solid-color(4, 12, 0, 255, 8, 8) + bounds: 32 96 8 8 + - image: solid-color(5, 12, 0, 255, 8, 8) + bounds: 40 96 8 8 + - image: solid-color(6, 12, 0, 255, 8, 8) + bounds: 48 96 8 8 + - image: solid-color(7, 12, 0, 255, 8, 8) + bounds: 56 96 8 8 + - image: solid-color(8, 12, 0, 255, 8, 8) + bounds: 64 96 8 8 + - image: solid-color(9, 12, 0, 255, 8, 8) + bounds: 72 96 8 8 + - image: solid-color(10, 12, 0, 255, 8, 8) + bounds: 80 96 8 8 + - image: solid-color(11, 12, 0, 255, 8, 8) + bounds: 88 96 8 8 + - image: solid-color(12, 12, 0, 255, 8, 8) + bounds: 96 96 8 8 + - image: solid-color(13, 12, 0, 255, 8, 8) + bounds: 104 96 8 8 + - image: solid-color(14, 12, 0, 255, 8, 8) + bounds: 112 96 8 8 + - image: solid-color(15, 12, 0, 255, 8, 8) + bounds: 120 96 8 8 + - image: solid-color(16, 12, 0, 255, 8, 8) + bounds: 128 96 8 8 + - image: solid-color(17, 12, 0, 255, 8, 8) + bounds: 136 96 8 8 + - image: solid-color(18, 12, 0, 255, 8, 8) + bounds: 144 96 8 8 + - image: solid-color(19, 12, 0, 255, 8, 8) + bounds: 152 96 8 8 + - image: solid-color(20, 12, 0, 255, 8, 8) + bounds: 160 96 8 8 + - image: solid-color(21, 12, 0, 255, 8, 8) + bounds: 168 96 8 8 + - image: solid-color(22, 12, 0, 255, 8, 8) + bounds: 176 96 8 8 + - image: solid-color(23, 12, 0, 255, 8, 8) + bounds: 184 96 8 8 + - image: solid-color(24, 12, 0, 255, 8, 8) + bounds: 192 96 8 8 + - image: solid-color(25, 12, 0, 255, 8, 8) + bounds: 200 96 8 8 + - image: solid-color(26, 12, 0, 255, 8, 8) + bounds: 208 96 8 8 + - image: solid-color(27, 12, 0, 255, 8, 8) + bounds: 216 96 8 8 + - image: solid-color(28, 12, 0, 255, 8, 8) + bounds: 224 96 8 8 + - image: solid-color(29, 12, 0, 255, 8, 8) + bounds: 232 96 8 8 + - image: solid-color(30, 12, 0, 255, 8, 8) + bounds: 240 96 8 8 + - image: solid-color(31, 12, 0, 255, 8, 8) + bounds: 248 96 8 8 + - image: solid-color(32, 12, 0, 255, 8, 8) + bounds: 256 96 8 8 + - image: solid-color(33, 12, 0, 255, 8, 8) + bounds: 264 96 8 8 + - image: solid-color(34, 12, 0, 255, 8, 8) + bounds: 272 96 8 8 + - image: solid-color(35, 12, 0, 255, 8, 8) + bounds: 280 96 8 8 + - image: solid-color(36, 12, 0, 255, 8, 8) + bounds: 288 96 8 8 + - image: solid-color(37, 12, 0, 255, 8, 8) + bounds: 296 96 8 8 + - image: solid-color(38, 12, 0, 255, 8, 8) + bounds: 304 96 8 8 + - image: solid-color(39, 12, 0, 255, 8, 8) + bounds: 312 96 8 8 + - image: solid-color(40, 12, 0, 255, 8, 8) + bounds: 320 96 8 8 + - image: solid-color(41, 12, 0, 255, 8, 8) + bounds: 328 96 8 8 + - image: solid-color(42, 12, 0, 255, 8, 8) + bounds: 336 96 8 8 + - image: solid-color(43, 12, 0, 255, 8, 8) + bounds: 344 96 8 8 + - image: solid-color(44, 12, 0, 255, 8, 8) + bounds: 352 96 8 8 + - image: solid-color(45, 12, 0, 255, 8, 8) + bounds: 360 96 8 8 + - image: solid-color(46, 12, 0, 255, 8, 8) + bounds: 368 96 8 8 + - image: solid-color(47, 12, 0, 255, 8, 8) + bounds: 376 96 8 8 + - image: solid-color(48, 12, 0, 255, 8, 8) + bounds: 384 96 8 8 + - image: solid-color(49, 12, 0, 255, 8, 8) + bounds: 392 96 8 8 + - image: solid-color(50, 12, 0, 255, 8, 8) + bounds: 400 96 8 8 + - image: solid-color(51, 12, 0, 255, 8, 8) + bounds: 408 96 8 8 + - image: solid-color(52, 12, 0, 255, 8, 8) + bounds: 416 96 8 8 + - image: solid-color(53, 12, 0, 255, 8, 8) + bounds: 424 96 8 8 + - image: solid-color(54, 12, 0, 255, 8, 8) + bounds: 432 96 8 8 + - image: solid-color(55, 12, 0, 255, 8, 8) + bounds: 440 96 8 8 + - image: solid-color(56, 12, 0, 255, 8, 8) + bounds: 448 96 8 8 + - image: solid-color(57, 12, 0, 255, 8, 8) + bounds: 456 96 8 8 + - image: solid-color(58, 12, 0, 255, 8, 8) + bounds: 464 96 8 8 + - image: solid-color(59, 12, 0, 255, 8, 8) + bounds: 472 96 8 8 + - image: solid-color(60, 12, 0, 255, 8, 8) + bounds: 480 96 8 8 + - image: solid-color(61, 12, 0, 255, 8, 8) + bounds: 488 96 8 8 + - image: solid-color(62, 12, 0, 255, 8, 8) + bounds: 496 96 8 8 + - image: solid-color(63, 12, 0, 255, 8, 8) + bounds: 504 96 8 8 + - image: solid-color(64, 12, 0, 255, 8, 8) + bounds: 512 96 8 8 + - image: solid-color(65, 12, 0, 255, 8, 8) + bounds: 520 96 8 8 + - image: solid-color(66, 12, 0, 255, 8, 8) + bounds: 528 96 8 8 + - image: solid-color(67, 12, 0, 255, 8, 8) + bounds: 536 96 8 8 + - image: solid-color(68, 12, 0, 255, 8, 8) + bounds: 544 96 8 8 + - image: solid-color(69, 12, 0, 255, 8, 8) + bounds: 552 96 8 8 + - image: solid-color(70, 12, 0, 255, 8, 8) + bounds: 560 96 8 8 + - image: solid-color(71, 12, 0, 255, 8, 8) + bounds: 568 96 8 8 + - image: solid-color(72, 12, 0, 255, 8, 8) + bounds: 576 96 8 8 + - image: solid-color(73, 12, 0, 255, 8, 8) + bounds: 584 96 8 8 + - image: solid-color(74, 12, 0, 255, 8, 8) + bounds: 592 96 8 8 + - image: solid-color(75, 12, 0, 255, 8, 8) + bounds: 600 96 8 8 + - image: solid-color(76, 12, 0, 255, 8, 8) + bounds: 608 96 8 8 + - image: solid-color(77, 12, 0, 255, 8, 8) + bounds: 616 96 8 8 + - image: solid-color(78, 12, 0, 255, 8, 8) + bounds: 624 96 8 8 + - image: solid-color(79, 12, 0, 255, 8, 8) + bounds: 632 96 8 8 + - image: solid-color(80, 12, 0, 255, 8, 8) + bounds: 640 96 8 8 + - image: solid-color(81, 12, 0, 255, 8, 8) + bounds: 648 96 8 8 + - image: solid-color(82, 12, 0, 255, 8, 8) + bounds: 656 96 8 8 + - image: solid-color(83, 12, 0, 255, 8, 8) + bounds: 664 96 8 8 + - image: solid-color(84, 12, 0, 255, 8, 8) + bounds: 672 96 8 8 + - image: solid-color(85, 12, 0, 255, 8, 8) + bounds: 680 96 8 8 + - image: solid-color(86, 12, 0, 255, 8, 8) + bounds: 688 96 8 8 + - image: solid-color(87, 12, 0, 255, 8, 8) + bounds: 696 96 8 8 + - image: solid-color(88, 12, 0, 255, 8, 8) + bounds: 704 96 8 8 + - image: solid-color(89, 12, 0, 255, 8, 8) + bounds: 712 96 8 8 + - image: solid-color(90, 12, 0, 255, 8, 8) + bounds: 720 96 8 8 + - image: solid-color(91, 12, 0, 255, 8, 8) + bounds: 728 96 8 8 + - image: solid-color(92, 12, 0, 255, 8, 8) + bounds: 736 96 8 8 + - image: solid-color(93, 12, 0, 255, 8, 8) + bounds: 744 96 8 8 + - image: solid-color(94, 12, 0, 255, 8, 8) + bounds: 752 96 8 8 + - image: solid-color(95, 12, 0, 255, 8, 8) + bounds: 760 96 8 8 + - image: solid-color(96, 12, 0, 255, 8, 8) + bounds: 768 96 8 8 + - image: solid-color(97, 12, 0, 255, 8, 8) + bounds: 776 96 8 8 + - image: solid-color(98, 12, 0, 255, 8, 8) + bounds: 784 96 8 8 + - image: solid-color(99, 12, 0, 255, 8, 8) + bounds: 792 96 8 8 + - image: solid-color(100, 12, 0, 255, 8, 8) + bounds: 800 96 8 8 + - image: solid-color(101, 12, 0, 255, 8, 8) + bounds: 808 96 8 8 + - image: solid-color(102, 12, 0, 255, 8, 8) + bounds: 816 96 8 8 + - image: solid-color(103, 12, 0, 255, 8, 8) + bounds: 824 96 8 8 + - image: solid-color(104, 12, 0, 255, 8, 8) + bounds: 832 96 8 8 + - image: solid-color(105, 12, 0, 255, 8, 8) + bounds: 840 96 8 8 + - image: solid-color(106, 12, 0, 255, 8, 8) + bounds: 848 96 8 8 + - image: solid-color(107, 12, 0, 255, 8, 8) + bounds: 856 96 8 8 + - image: solid-color(108, 12, 0, 255, 8, 8) + bounds: 864 96 8 8 + - image: solid-color(109, 12, 0, 255, 8, 8) + bounds: 872 96 8 8 + - image: solid-color(110, 12, 0, 255, 8, 8) + bounds: 880 96 8 8 + - image: solid-color(111, 12, 0, 255, 8, 8) + bounds: 888 96 8 8 + - image: solid-color(112, 12, 0, 255, 8, 8) + bounds: 896 96 8 8 + - image: solid-color(113, 12, 0, 255, 8, 8) + bounds: 904 96 8 8 + - image: solid-color(114, 12, 0, 255, 8, 8) + bounds: 912 96 8 8 + - image: solid-color(115, 12, 0, 255, 8, 8) + bounds: 920 96 8 8 + - image: solid-color(116, 12, 0, 255, 8, 8) + bounds: 928 96 8 8 + - image: solid-color(117, 12, 0, 255, 8, 8) + bounds: 936 96 8 8 + - image: solid-color(118, 12, 0, 255, 8, 8) + bounds: 944 96 8 8 + - image: solid-color(119, 12, 0, 255, 8, 8) + bounds: 952 96 8 8 + - image: solid-color(120, 12, 0, 255, 8, 8) + bounds: 960 96 8 8 + - image: solid-color(121, 12, 0, 255, 8, 8) + bounds: 968 96 8 8 + - image: solid-color(122, 12, 0, 255, 8, 8) + bounds: 976 96 8 8 + - image: solid-color(123, 12, 0, 255, 8, 8) + bounds: 984 96 8 8 + - image: solid-color(124, 12, 0, 255, 8, 8) + bounds: 992 96 8 8 + - image: solid-color(125, 12, 0, 255, 8, 8) + bounds: 1000 96 8 8 + - image: solid-color(126, 12, 0, 255, 8, 8) + bounds: 1008 96 8 8 + - image: solid-color(127, 12, 0, 255, 8, 8) + bounds: 1016 96 8 8 + - image: solid-color(0, 13, 0, 255, 8, 8) + bounds: 0 104 8 8 + - image: solid-color(1, 13, 0, 255, 8, 8) + bounds: 8 104 8 8 + - image: solid-color(2, 13, 0, 255, 8, 8) + bounds: 16 104 8 8 + - image: solid-color(3, 13, 0, 255, 8, 8) + bounds: 24 104 8 8 + - image: solid-color(4, 13, 0, 255, 8, 8) + bounds: 32 104 8 8 + - image: solid-color(5, 13, 0, 255, 8, 8) + bounds: 40 104 8 8 + - image: solid-color(6, 13, 0, 255, 8, 8) + bounds: 48 104 8 8 + - image: solid-color(7, 13, 0, 255, 8, 8) + bounds: 56 104 8 8 + - image: solid-color(8, 13, 0, 255, 8, 8) + bounds: 64 104 8 8 + - image: solid-color(9, 13, 0, 255, 8, 8) + bounds: 72 104 8 8 + - image: solid-color(10, 13, 0, 255, 8, 8) + bounds: 80 104 8 8 + - image: solid-color(11, 13, 0, 255, 8, 8) + bounds: 88 104 8 8 + - image: solid-color(12, 13, 0, 255, 8, 8) + bounds: 96 104 8 8 + - image: solid-color(13, 13, 0, 255, 8, 8) + bounds: 104 104 8 8 + - image: solid-color(14, 13, 0, 255, 8, 8) + bounds: 112 104 8 8 + - image: solid-color(15, 13, 0, 255, 8, 8) + bounds: 120 104 8 8 + - image: solid-color(16, 13, 0, 255, 8, 8) + bounds: 128 104 8 8 + - image: solid-color(17, 13, 0, 255, 8, 8) + bounds: 136 104 8 8 + - image: solid-color(18, 13, 0, 255, 8, 8) + bounds: 144 104 8 8 + - image: solid-color(19, 13, 0, 255, 8, 8) + bounds: 152 104 8 8 + - image: solid-color(20, 13, 0, 255, 8, 8) + bounds: 160 104 8 8 + - image: solid-color(21, 13, 0, 255, 8, 8) + bounds: 168 104 8 8 + - image: solid-color(22, 13, 0, 255, 8, 8) + bounds: 176 104 8 8 + - image: solid-color(23, 13, 0, 255, 8, 8) + bounds: 184 104 8 8 + - image: solid-color(24, 13, 0, 255, 8, 8) + bounds: 192 104 8 8 + - image: solid-color(25, 13, 0, 255, 8, 8) + bounds: 200 104 8 8 + - image: solid-color(26, 13, 0, 255, 8, 8) + bounds: 208 104 8 8 + - image: solid-color(27, 13, 0, 255, 8, 8) + bounds: 216 104 8 8 + - image: solid-color(28, 13, 0, 255, 8, 8) + bounds: 224 104 8 8 + - image: solid-color(29, 13, 0, 255, 8, 8) + bounds: 232 104 8 8 + - image: solid-color(30, 13, 0, 255, 8, 8) + bounds: 240 104 8 8 + - image: solid-color(31, 13, 0, 255, 8, 8) + bounds: 248 104 8 8 + - image: solid-color(32, 13, 0, 255, 8, 8) + bounds: 256 104 8 8 + - image: solid-color(33, 13, 0, 255, 8, 8) + bounds: 264 104 8 8 + - image: solid-color(34, 13, 0, 255, 8, 8) + bounds: 272 104 8 8 + - image: solid-color(35, 13, 0, 255, 8, 8) + bounds: 280 104 8 8 + - image: solid-color(36, 13, 0, 255, 8, 8) + bounds: 288 104 8 8 + - image: solid-color(37, 13, 0, 255, 8, 8) + bounds: 296 104 8 8 + - image: solid-color(38, 13, 0, 255, 8, 8) + bounds: 304 104 8 8 + - image: solid-color(39, 13, 0, 255, 8, 8) + bounds: 312 104 8 8 + - image: solid-color(40, 13, 0, 255, 8, 8) + bounds: 320 104 8 8 + - image: solid-color(41, 13, 0, 255, 8, 8) + bounds: 328 104 8 8 + - image: solid-color(42, 13, 0, 255, 8, 8) + bounds: 336 104 8 8 + - image: solid-color(43, 13, 0, 255, 8, 8) + bounds: 344 104 8 8 + - image: solid-color(44, 13, 0, 255, 8, 8) + bounds: 352 104 8 8 + - image: solid-color(45, 13, 0, 255, 8, 8) + bounds: 360 104 8 8 + - image: solid-color(46, 13, 0, 255, 8, 8) + bounds: 368 104 8 8 + - image: solid-color(47, 13, 0, 255, 8, 8) + bounds: 376 104 8 8 + - image: solid-color(48, 13, 0, 255, 8, 8) + bounds: 384 104 8 8 + - image: solid-color(49, 13, 0, 255, 8, 8) + bounds: 392 104 8 8 + - image: solid-color(50, 13, 0, 255, 8, 8) + bounds: 400 104 8 8 + - image: solid-color(51, 13, 0, 255, 8, 8) + bounds: 408 104 8 8 + - image: solid-color(52, 13, 0, 255, 8, 8) + bounds: 416 104 8 8 + - image: solid-color(53, 13, 0, 255, 8, 8) + bounds: 424 104 8 8 + - image: solid-color(54, 13, 0, 255, 8, 8) + bounds: 432 104 8 8 + - image: solid-color(55, 13, 0, 255, 8, 8) + bounds: 440 104 8 8 + - image: solid-color(56, 13, 0, 255, 8, 8) + bounds: 448 104 8 8 + - image: solid-color(57, 13, 0, 255, 8, 8) + bounds: 456 104 8 8 + - image: solid-color(58, 13, 0, 255, 8, 8) + bounds: 464 104 8 8 + - image: solid-color(59, 13, 0, 255, 8, 8) + bounds: 472 104 8 8 + - image: solid-color(60, 13, 0, 255, 8, 8) + bounds: 480 104 8 8 + - image: solid-color(61, 13, 0, 255, 8, 8) + bounds: 488 104 8 8 + - image: solid-color(62, 13, 0, 255, 8, 8) + bounds: 496 104 8 8 + - image: solid-color(63, 13, 0, 255, 8, 8) + bounds: 504 104 8 8 + - image: solid-color(64, 13, 0, 255, 8, 8) + bounds: 512 104 8 8 + - image: solid-color(65, 13, 0, 255, 8, 8) + bounds: 520 104 8 8 + - image: solid-color(66, 13, 0, 255, 8, 8) + bounds: 528 104 8 8 + - image: solid-color(67, 13, 0, 255, 8, 8) + bounds: 536 104 8 8 + - image: solid-color(68, 13, 0, 255, 8, 8) + bounds: 544 104 8 8 + - image: solid-color(69, 13, 0, 255, 8, 8) + bounds: 552 104 8 8 + - image: solid-color(70, 13, 0, 255, 8, 8) + bounds: 560 104 8 8 + - image: solid-color(71, 13, 0, 255, 8, 8) + bounds: 568 104 8 8 + - image: solid-color(72, 13, 0, 255, 8, 8) + bounds: 576 104 8 8 + - image: solid-color(73, 13, 0, 255, 8, 8) + bounds: 584 104 8 8 + - image: solid-color(74, 13, 0, 255, 8, 8) + bounds: 592 104 8 8 + - image: solid-color(75, 13, 0, 255, 8, 8) + bounds: 600 104 8 8 + - image: solid-color(76, 13, 0, 255, 8, 8) + bounds: 608 104 8 8 + - image: solid-color(77, 13, 0, 255, 8, 8) + bounds: 616 104 8 8 + - image: solid-color(78, 13, 0, 255, 8, 8) + bounds: 624 104 8 8 + - image: solid-color(79, 13, 0, 255, 8, 8) + bounds: 632 104 8 8 + - image: solid-color(80, 13, 0, 255, 8, 8) + bounds: 640 104 8 8 + - image: solid-color(81, 13, 0, 255, 8, 8) + bounds: 648 104 8 8 + - image: solid-color(82, 13, 0, 255, 8, 8) + bounds: 656 104 8 8 + - image: solid-color(83, 13, 0, 255, 8, 8) + bounds: 664 104 8 8 + - image: solid-color(84, 13, 0, 255, 8, 8) + bounds: 672 104 8 8 + - image: solid-color(85, 13, 0, 255, 8, 8) + bounds: 680 104 8 8 + - image: solid-color(86, 13, 0, 255, 8, 8) + bounds: 688 104 8 8 + - image: solid-color(87, 13, 0, 255, 8, 8) + bounds: 696 104 8 8 + - image: solid-color(88, 13, 0, 255, 8, 8) + bounds: 704 104 8 8 + - image: solid-color(89, 13, 0, 255, 8, 8) + bounds: 712 104 8 8 + - image: solid-color(90, 13, 0, 255, 8, 8) + bounds: 720 104 8 8 + - image: solid-color(91, 13, 0, 255, 8, 8) + bounds: 728 104 8 8 + - image: solid-color(92, 13, 0, 255, 8, 8) + bounds: 736 104 8 8 + - image: solid-color(93, 13, 0, 255, 8, 8) + bounds: 744 104 8 8 + - image: solid-color(94, 13, 0, 255, 8, 8) + bounds: 752 104 8 8 + - image: solid-color(95, 13, 0, 255, 8, 8) + bounds: 760 104 8 8 + - image: solid-color(96, 13, 0, 255, 8, 8) + bounds: 768 104 8 8 + - image: solid-color(97, 13, 0, 255, 8, 8) + bounds: 776 104 8 8 + - image: solid-color(98, 13, 0, 255, 8, 8) + bounds: 784 104 8 8 + - image: solid-color(99, 13, 0, 255, 8, 8) + bounds: 792 104 8 8 + - image: solid-color(100, 13, 0, 255, 8, 8) + bounds: 800 104 8 8 + - image: solid-color(101, 13, 0, 255, 8, 8) + bounds: 808 104 8 8 + - image: solid-color(102, 13, 0, 255, 8, 8) + bounds: 816 104 8 8 + - image: solid-color(103, 13, 0, 255, 8, 8) + bounds: 824 104 8 8 + - image: solid-color(104, 13, 0, 255, 8, 8) + bounds: 832 104 8 8 + - image: solid-color(105, 13, 0, 255, 8, 8) + bounds: 840 104 8 8 + - image: solid-color(106, 13, 0, 255, 8, 8) + bounds: 848 104 8 8 + - image: solid-color(107, 13, 0, 255, 8, 8) + bounds: 856 104 8 8 + - image: solid-color(108, 13, 0, 255, 8, 8) + bounds: 864 104 8 8 + - image: solid-color(109, 13, 0, 255, 8, 8) + bounds: 872 104 8 8 + - image: solid-color(110, 13, 0, 255, 8, 8) + bounds: 880 104 8 8 + - image: solid-color(111, 13, 0, 255, 8, 8) + bounds: 888 104 8 8 + - image: solid-color(112, 13, 0, 255, 8, 8) + bounds: 896 104 8 8 + - image: solid-color(113, 13, 0, 255, 8, 8) + bounds: 904 104 8 8 + - image: solid-color(114, 13, 0, 255, 8, 8) + bounds: 912 104 8 8 + - image: solid-color(115, 13, 0, 255, 8, 8) + bounds: 920 104 8 8 + - image: solid-color(116, 13, 0, 255, 8, 8) + bounds: 928 104 8 8 + - image: solid-color(117, 13, 0, 255, 8, 8) + bounds: 936 104 8 8 + - image: solid-color(118, 13, 0, 255, 8, 8) + bounds: 944 104 8 8 + - image: solid-color(119, 13, 0, 255, 8, 8) + bounds: 952 104 8 8 + - image: solid-color(120, 13, 0, 255, 8, 8) + bounds: 960 104 8 8 + - image: solid-color(121, 13, 0, 255, 8, 8) + bounds: 968 104 8 8 + - image: solid-color(122, 13, 0, 255, 8, 8) + bounds: 976 104 8 8 + - image: solid-color(123, 13, 0, 255, 8, 8) + bounds: 984 104 8 8 + - image: solid-color(124, 13, 0, 255, 8, 8) + bounds: 992 104 8 8 + - image: solid-color(125, 13, 0, 255, 8, 8) + bounds: 1000 104 8 8 + - image: solid-color(126, 13, 0, 255, 8, 8) + bounds: 1008 104 8 8 + - image: solid-color(127, 13, 0, 255, 8, 8) + bounds: 1016 104 8 8 + - image: solid-color(0, 14, 0, 255, 8, 8) + bounds: 0 112 8 8 + - image: solid-color(1, 14, 0, 255, 8, 8) + bounds: 8 112 8 8 + - image: solid-color(2, 14, 0, 255, 8, 8) + bounds: 16 112 8 8 + - image: solid-color(3, 14, 0, 255, 8, 8) + bounds: 24 112 8 8 + - image: solid-color(4, 14, 0, 255, 8, 8) + bounds: 32 112 8 8 + - image: solid-color(5, 14, 0, 255, 8, 8) + bounds: 40 112 8 8 + - image: solid-color(6, 14, 0, 255, 8, 8) + bounds: 48 112 8 8 + - image: solid-color(7, 14, 0, 255, 8, 8) + bounds: 56 112 8 8 + - image: solid-color(8, 14, 0, 255, 8, 8) + bounds: 64 112 8 8 + - image: solid-color(9, 14, 0, 255, 8, 8) + bounds: 72 112 8 8 + - image: solid-color(10, 14, 0, 255, 8, 8) + bounds: 80 112 8 8 + - image: solid-color(11, 14, 0, 255, 8, 8) + bounds: 88 112 8 8 + - image: solid-color(12, 14, 0, 255, 8, 8) + bounds: 96 112 8 8 + - image: solid-color(13, 14, 0, 255, 8, 8) + bounds: 104 112 8 8 + - image: solid-color(14, 14, 0, 255, 8, 8) + bounds: 112 112 8 8 + - image: solid-color(15, 14, 0, 255, 8, 8) + bounds: 120 112 8 8 + - image: solid-color(16, 14, 0, 255, 8, 8) + bounds: 128 112 8 8 + - image: solid-color(17, 14, 0, 255, 8, 8) + bounds: 136 112 8 8 + - image: solid-color(18, 14, 0, 255, 8, 8) + bounds: 144 112 8 8 + - image: solid-color(19, 14, 0, 255, 8, 8) + bounds: 152 112 8 8 + - image: solid-color(20, 14, 0, 255, 8, 8) + bounds: 160 112 8 8 + - image: solid-color(21, 14, 0, 255, 8, 8) + bounds: 168 112 8 8 + - image: solid-color(22, 14, 0, 255, 8, 8) + bounds: 176 112 8 8 + - image: solid-color(23, 14, 0, 255, 8, 8) + bounds: 184 112 8 8 + - image: solid-color(24, 14, 0, 255, 8, 8) + bounds: 192 112 8 8 + - image: solid-color(25, 14, 0, 255, 8, 8) + bounds: 200 112 8 8 + - image: solid-color(26, 14, 0, 255, 8, 8) + bounds: 208 112 8 8 + - image: solid-color(27, 14, 0, 255, 8, 8) + bounds: 216 112 8 8 + - image: solid-color(28, 14, 0, 255, 8, 8) + bounds: 224 112 8 8 + - image: solid-color(29, 14, 0, 255, 8, 8) + bounds: 232 112 8 8 + - image: solid-color(30, 14, 0, 255, 8, 8) + bounds: 240 112 8 8 + - image: solid-color(31, 14, 0, 255, 8, 8) + bounds: 248 112 8 8 + - image: solid-color(32, 14, 0, 255, 8, 8) + bounds: 256 112 8 8 + - image: solid-color(33, 14, 0, 255, 8, 8) + bounds: 264 112 8 8 + - image: solid-color(34, 14, 0, 255, 8, 8) + bounds: 272 112 8 8 + - image: solid-color(35, 14, 0, 255, 8, 8) + bounds: 280 112 8 8 + - image: solid-color(36, 14, 0, 255, 8, 8) + bounds: 288 112 8 8 + - image: solid-color(37, 14, 0, 255, 8, 8) + bounds: 296 112 8 8 + - image: solid-color(38, 14, 0, 255, 8, 8) + bounds: 304 112 8 8 + - image: solid-color(39, 14, 0, 255, 8, 8) + bounds: 312 112 8 8 + - image: solid-color(40, 14, 0, 255, 8, 8) + bounds: 320 112 8 8 + - image: solid-color(41, 14, 0, 255, 8, 8) + bounds: 328 112 8 8 + - image: solid-color(42, 14, 0, 255, 8, 8) + bounds: 336 112 8 8 + - image: solid-color(43, 14, 0, 255, 8, 8) + bounds: 344 112 8 8 + - image: solid-color(44, 14, 0, 255, 8, 8) + bounds: 352 112 8 8 + - image: solid-color(45, 14, 0, 255, 8, 8) + bounds: 360 112 8 8 + - image: solid-color(46, 14, 0, 255, 8, 8) + bounds: 368 112 8 8 + - image: solid-color(47, 14, 0, 255, 8, 8) + bounds: 376 112 8 8 + - image: solid-color(48, 14, 0, 255, 8, 8) + bounds: 384 112 8 8 + - image: solid-color(49, 14, 0, 255, 8, 8) + bounds: 392 112 8 8 + - image: solid-color(50, 14, 0, 255, 8, 8) + bounds: 400 112 8 8 + - image: solid-color(51, 14, 0, 255, 8, 8) + bounds: 408 112 8 8 + - image: solid-color(52, 14, 0, 255, 8, 8) + bounds: 416 112 8 8 + - image: solid-color(53, 14, 0, 255, 8, 8) + bounds: 424 112 8 8 + - image: solid-color(54, 14, 0, 255, 8, 8) + bounds: 432 112 8 8 + - image: solid-color(55, 14, 0, 255, 8, 8) + bounds: 440 112 8 8 + - image: solid-color(56, 14, 0, 255, 8, 8) + bounds: 448 112 8 8 + - image: solid-color(57, 14, 0, 255, 8, 8) + bounds: 456 112 8 8 + - image: solid-color(58, 14, 0, 255, 8, 8) + bounds: 464 112 8 8 + - image: solid-color(59, 14, 0, 255, 8, 8) + bounds: 472 112 8 8 + - image: solid-color(60, 14, 0, 255, 8, 8) + bounds: 480 112 8 8 + - image: solid-color(61, 14, 0, 255, 8, 8) + bounds: 488 112 8 8 + - image: solid-color(62, 14, 0, 255, 8, 8) + bounds: 496 112 8 8 + - image: solid-color(63, 14, 0, 255, 8, 8) + bounds: 504 112 8 8 + - image: solid-color(64, 14, 0, 255, 8, 8) + bounds: 512 112 8 8 + - image: solid-color(65, 14, 0, 255, 8, 8) + bounds: 520 112 8 8 + - image: solid-color(66, 14, 0, 255, 8, 8) + bounds: 528 112 8 8 + - image: solid-color(67, 14, 0, 255, 8, 8) + bounds: 536 112 8 8 + - image: solid-color(68, 14, 0, 255, 8, 8) + bounds: 544 112 8 8 + - image: solid-color(69, 14, 0, 255, 8, 8) + bounds: 552 112 8 8 + - image: solid-color(70, 14, 0, 255, 8, 8) + bounds: 560 112 8 8 + - image: solid-color(71, 14, 0, 255, 8, 8) + bounds: 568 112 8 8 + - image: solid-color(72, 14, 0, 255, 8, 8) + bounds: 576 112 8 8 + - image: solid-color(73, 14, 0, 255, 8, 8) + bounds: 584 112 8 8 + - image: solid-color(74, 14, 0, 255, 8, 8) + bounds: 592 112 8 8 + - image: solid-color(75, 14, 0, 255, 8, 8) + bounds: 600 112 8 8 + - image: solid-color(76, 14, 0, 255, 8, 8) + bounds: 608 112 8 8 + - image: solid-color(77, 14, 0, 255, 8, 8) + bounds: 616 112 8 8 + - image: solid-color(78, 14, 0, 255, 8, 8) + bounds: 624 112 8 8 + - image: solid-color(79, 14, 0, 255, 8, 8) + bounds: 632 112 8 8 + - image: solid-color(80, 14, 0, 255, 8, 8) + bounds: 640 112 8 8 + - image: solid-color(81, 14, 0, 255, 8, 8) + bounds: 648 112 8 8 + - image: solid-color(82, 14, 0, 255, 8, 8) + bounds: 656 112 8 8 + - image: solid-color(83, 14, 0, 255, 8, 8) + bounds: 664 112 8 8 + - image: solid-color(84, 14, 0, 255, 8, 8) + bounds: 672 112 8 8 + - image: solid-color(85, 14, 0, 255, 8, 8) + bounds: 680 112 8 8 + - image: solid-color(86, 14, 0, 255, 8, 8) + bounds: 688 112 8 8 + - image: solid-color(87, 14, 0, 255, 8, 8) + bounds: 696 112 8 8 + - image: solid-color(88, 14, 0, 255, 8, 8) + bounds: 704 112 8 8 + - image: solid-color(89, 14, 0, 255, 8, 8) + bounds: 712 112 8 8 + - image: solid-color(90, 14, 0, 255, 8, 8) + bounds: 720 112 8 8 + - image: solid-color(91, 14, 0, 255, 8, 8) + bounds: 728 112 8 8 + - image: solid-color(92, 14, 0, 255, 8, 8) + bounds: 736 112 8 8 + - image: solid-color(93, 14, 0, 255, 8, 8) + bounds: 744 112 8 8 + - image: solid-color(94, 14, 0, 255, 8, 8) + bounds: 752 112 8 8 + - image: solid-color(95, 14, 0, 255, 8, 8) + bounds: 760 112 8 8 + - image: solid-color(96, 14, 0, 255, 8, 8) + bounds: 768 112 8 8 + - image: solid-color(97, 14, 0, 255, 8, 8) + bounds: 776 112 8 8 + - image: solid-color(98, 14, 0, 255, 8, 8) + bounds: 784 112 8 8 + - image: solid-color(99, 14, 0, 255, 8, 8) + bounds: 792 112 8 8 + - image: solid-color(100, 14, 0, 255, 8, 8) + bounds: 800 112 8 8 + - image: solid-color(101, 14, 0, 255, 8, 8) + bounds: 808 112 8 8 + - image: solid-color(102, 14, 0, 255, 8, 8) + bounds: 816 112 8 8 + - image: solid-color(103, 14, 0, 255, 8, 8) + bounds: 824 112 8 8 + - image: solid-color(104, 14, 0, 255, 8, 8) + bounds: 832 112 8 8 + - image: solid-color(105, 14, 0, 255, 8, 8) + bounds: 840 112 8 8 + - image: solid-color(106, 14, 0, 255, 8, 8) + bounds: 848 112 8 8 + - image: solid-color(107, 14, 0, 255, 8, 8) + bounds: 856 112 8 8 + - image: solid-color(108, 14, 0, 255, 8, 8) + bounds: 864 112 8 8 + - image: solid-color(109, 14, 0, 255, 8, 8) + bounds: 872 112 8 8 + - image: solid-color(110, 14, 0, 255, 8, 8) + bounds: 880 112 8 8 + - image: solid-color(111, 14, 0, 255, 8, 8) + bounds: 888 112 8 8 + - image: solid-color(112, 14, 0, 255, 8, 8) + bounds: 896 112 8 8 + - image: solid-color(113, 14, 0, 255, 8, 8) + bounds: 904 112 8 8 + - image: solid-color(114, 14, 0, 255, 8, 8) + bounds: 912 112 8 8 + - image: solid-color(115, 14, 0, 255, 8, 8) + bounds: 920 112 8 8 + - image: solid-color(116, 14, 0, 255, 8, 8) + bounds: 928 112 8 8 + - image: solid-color(117, 14, 0, 255, 8, 8) + bounds: 936 112 8 8 + - image: solid-color(118, 14, 0, 255, 8, 8) + bounds: 944 112 8 8 + - image: solid-color(119, 14, 0, 255, 8, 8) + bounds: 952 112 8 8 + - image: solid-color(120, 14, 0, 255, 8, 8) + bounds: 960 112 8 8 + - image: solid-color(121, 14, 0, 255, 8, 8) + bounds: 968 112 8 8 + - image: solid-color(122, 14, 0, 255, 8, 8) + bounds: 976 112 8 8 + - image: solid-color(123, 14, 0, 255, 8, 8) + bounds: 984 112 8 8 + - image: solid-color(124, 14, 0, 255, 8, 8) + bounds: 992 112 8 8 + - image: solid-color(125, 14, 0, 255, 8, 8) + bounds: 1000 112 8 8 + - image: solid-color(126, 14, 0, 255, 8, 8) + bounds: 1008 112 8 8 + - image: solid-color(127, 14, 0, 255, 8, 8) + bounds: 1016 112 8 8 + - image: solid-color(0, 15, 0, 255, 8, 8) + bounds: 0 120 8 8 + - image: solid-color(1, 15, 0, 255, 8, 8) + bounds: 8 120 8 8 + - image: solid-color(2, 15, 0, 255, 8, 8) + bounds: 16 120 8 8 + - image: solid-color(3, 15, 0, 255, 8, 8) + bounds: 24 120 8 8 + - image: solid-color(4, 15, 0, 255, 8, 8) + bounds: 32 120 8 8 + - image: solid-color(5, 15, 0, 255, 8, 8) + bounds: 40 120 8 8 + - image: solid-color(6, 15, 0, 255, 8, 8) + bounds: 48 120 8 8 + - image: solid-color(7, 15, 0, 255, 8, 8) + bounds: 56 120 8 8 + - image: solid-color(8, 15, 0, 255, 8, 8) + bounds: 64 120 8 8 + - image: solid-color(9, 15, 0, 255, 8, 8) + bounds: 72 120 8 8 + - image: solid-color(10, 15, 0, 255, 8, 8) + bounds: 80 120 8 8 + - image: solid-color(11, 15, 0, 255, 8, 8) + bounds: 88 120 8 8 + - image: solid-color(12, 15, 0, 255, 8, 8) + bounds: 96 120 8 8 + - image: solid-color(13, 15, 0, 255, 8, 8) + bounds: 104 120 8 8 + - image: solid-color(14, 15, 0, 255, 8, 8) + bounds: 112 120 8 8 + - image: solid-color(15, 15, 0, 255, 8, 8) + bounds: 120 120 8 8 + - image: solid-color(16, 15, 0, 255, 8, 8) + bounds: 128 120 8 8 + - image: solid-color(17, 15, 0, 255, 8, 8) + bounds: 136 120 8 8 + - image: solid-color(18, 15, 0, 255, 8, 8) + bounds: 144 120 8 8 + - image: solid-color(19, 15, 0, 255, 8, 8) + bounds: 152 120 8 8 + - image: solid-color(20, 15, 0, 255, 8, 8) + bounds: 160 120 8 8 + - image: solid-color(21, 15, 0, 255, 8, 8) + bounds: 168 120 8 8 + - image: solid-color(22, 15, 0, 255, 8, 8) + bounds: 176 120 8 8 + - image: solid-color(23, 15, 0, 255, 8, 8) + bounds: 184 120 8 8 + - image: solid-color(24, 15, 0, 255, 8, 8) + bounds: 192 120 8 8 + - image: solid-color(25, 15, 0, 255, 8, 8) + bounds: 200 120 8 8 + - image: solid-color(26, 15, 0, 255, 8, 8) + bounds: 208 120 8 8 + - image: solid-color(27, 15, 0, 255, 8, 8) + bounds: 216 120 8 8 + - image: solid-color(28, 15, 0, 255, 8, 8) + bounds: 224 120 8 8 + - image: solid-color(29, 15, 0, 255, 8, 8) + bounds: 232 120 8 8 + - image: solid-color(30, 15, 0, 255, 8, 8) + bounds: 240 120 8 8 + - image: solid-color(31, 15, 0, 255, 8, 8) + bounds: 248 120 8 8 + - image: solid-color(32, 15, 0, 255, 8, 8) + bounds: 256 120 8 8 + - image: solid-color(33, 15, 0, 255, 8, 8) + bounds: 264 120 8 8 + - image: solid-color(34, 15, 0, 255, 8, 8) + bounds: 272 120 8 8 + - image: solid-color(35, 15, 0, 255, 8, 8) + bounds: 280 120 8 8 + - image: solid-color(36, 15, 0, 255, 8, 8) + bounds: 288 120 8 8 + - image: solid-color(37, 15, 0, 255, 8, 8) + bounds: 296 120 8 8 + - image: solid-color(38, 15, 0, 255, 8, 8) + bounds: 304 120 8 8 + - image: solid-color(39, 15, 0, 255, 8, 8) + bounds: 312 120 8 8 + - image: solid-color(40, 15, 0, 255, 8, 8) + bounds: 320 120 8 8 + - image: solid-color(41, 15, 0, 255, 8, 8) + bounds: 328 120 8 8 + - image: solid-color(42, 15, 0, 255, 8, 8) + bounds: 336 120 8 8 + - image: solid-color(43, 15, 0, 255, 8, 8) + bounds: 344 120 8 8 + - image: solid-color(44, 15, 0, 255, 8, 8) + bounds: 352 120 8 8 + - image: solid-color(45, 15, 0, 255, 8, 8) + bounds: 360 120 8 8 + - image: solid-color(46, 15, 0, 255, 8, 8) + bounds: 368 120 8 8 + - image: solid-color(47, 15, 0, 255, 8, 8) + bounds: 376 120 8 8 + - image: solid-color(48, 15, 0, 255, 8, 8) + bounds: 384 120 8 8 + - image: solid-color(49, 15, 0, 255, 8, 8) + bounds: 392 120 8 8 + - image: solid-color(50, 15, 0, 255, 8, 8) + bounds: 400 120 8 8 + - image: solid-color(51, 15, 0, 255, 8, 8) + bounds: 408 120 8 8 + - image: solid-color(52, 15, 0, 255, 8, 8) + bounds: 416 120 8 8 + - image: solid-color(53, 15, 0, 255, 8, 8) + bounds: 424 120 8 8 + - image: solid-color(54, 15, 0, 255, 8, 8) + bounds: 432 120 8 8 + - image: solid-color(55, 15, 0, 255, 8, 8) + bounds: 440 120 8 8 + - image: solid-color(56, 15, 0, 255, 8, 8) + bounds: 448 120 8 8 + - image: solid-color(57, 15, 0, 255, 8, 8) + bounds: 456 120 8 8 + - image: solid-color(58, 15, 0, 255, 8, 8) + bounds: 464 120 8 8 + - image: solid-color(59, 15, 0, 255, 8, 8) + bounds: 472 120 8 8 + - image: solid-color(60, 15, 0, 255, 8, 8) + bounds: 480 120 8 8 + - image: solid-color(61, 15, 0, 255, 8, 8) + bounds: 488 120 8 8 + - image: solid-color(62, 15, 0, 255, 8, 8) + bounds: 496 120 8 8 + - image: solid-color(63, 15, 0, 255, 8, 8) + bounds: 504 120 8 8 + - image: solid-color(64, 15, 0, 255, 8, 8) + bounds: 512 120 8 8 + - image: solid-color(65, 15, 0, 255, 8, 8) + bounds: 520 120 8 8 + - image: solid-color(66, 15, 0, 255, 8, 8) + bounds: 528 120 8 8 + - image: solid-color(67, 15, 0, 255, 8, 8) + bounds: 536 120 8 8 + - image: solid-color(68, 15, 0, 255, 8, 8) + bounds: 544 120 8 8 + - image: solid-color(69, 15, 0, 255, 8, 8) + bounds: 552 120 8 8 + - image: solid-color(70, 15, 0, 255, 8, 8) + bounds: 560 120 8 8 + - image: solid-color(71, 15, 0, 255, 8, 8) + bounds: 568 120 8 8 + - image: solid-color(72, 15, 0, 255, 8, 8) + bounds: 576 120 8 8 + - image: solid-color(73, 15, 0, 255, 8, 8) + bounds: 584 120 8 8 + - image: solid-color(74, 15, 0, 255, 8, 8) + bounds: 592 120 8 8 + - image: solid-color(75, 15, 0, 255, 8, 8) + bounds: 600 120 8 8 + - image: solid-color(76, 15, 0, 255, 8, 8) + bounds: 608 120 8 8 + - image: solid-color(77, 15, 0, 255, 8, 8) + bounds: 616 120 8 8 + - image: solid-color(78, 15, 0, 255, 8, 8) + bounds: 624 120 8 8 + - image: solid-color(79, 15, 0, 255, 8, 8) + bounds: 632 120 8 8 + - image: solid-color(80, 15, 0, 255, 8, 8) + bounds: 640 120 8 8 + - image: solid-color(81, 15, 0, 255, 8, 8) + bounds: 648 120 8 8 + - image: solid-color(82, 15, 0, 255, 8, 8) + bounds: 656 120 8 8 + - image: solid-color(83, 15, 0, 255, 8, 8) + bounds: 664 120 8 8 + - image: solid-color(84, 15, 0, 255, 8, 8) + bounds: 672 120 8 8 + - image: solid-color(85, 15, 0, 255, 8, 8) + bounds: 680 120 8 8 + - image: solid-color(86, 15, 0, 255, 8, 8) + bounds: 688 120 8 8 + - image: solid-color(87, 15, 0, 255, 8, 8) + bounds: 696 120 8 8 + - image: solid-color(88, 15, 0, 255, 8, 8) + bounds: 704 120 8 8 + - image: solid-color(89, 15, 0, 255, 8, 8) + bounds: 712 120 8 8 + - image: solid-color(90, 15, 0, 255, 8, 8) + bounds: 720 120 8 8 + - image: solid-color(91, 15, 0, 255, 8, 8) + bounds: 728 120 8 8 + - image: solid-color(92, 15, 0, 255, 8, 8) + bounds: 736 120 8 8 + - image: solid-color(93, 15, 0, 255, 8, 8) + bounds: 744 120 8 8 + - image: solid-color(94, 15, 0, 255, 8, 8) + bounds: 752 120 8 8 + - image: solid-color(95, 15, 0, 255, 8, 8) + bounds: 760 120 8 8 + - image: solid-color(96, 15, 0, 255, 8, 8) + bounds: 768 120 8 8 + - image: solid-color(97, 15, 0, 255, 8, 8) + bounds: 776 120 8 8 + - image: solid-color(98, 15, 0, 255, 8, 8) + bounds: 784 120 8 8 + - image: solid-color(99, 15, 0, 255, 8, 8) + bounds: 792 120 8 8 + - image: solid-color(100, 15, 0, 255, 8, 8) + bounds: 800 120 8 8 + - image: solid-color(101, 15, 0, 255, 8, 8) + bounds: 808 120 8 8 + - image: solid-color(102, 15, 0, 255, 8, 8) + bounds: 816 120 8 8 + - image: solid-color(103, 15, 0, 255, 8, 8) + bounds: 824 120 8 8 + - image: solid-color(104, 15, 0, 255, 8, 8) + bounds: 832 120 8 8 + - image: solid-color(105, 15, 0, 255, 8, 8) + bounds: 840 120 8 8 + - image: solid-color(106, 15, 0, 255, 8, 8) + bounds: 848 120 8 8 + - image: solid-color(107, 15, 0, 255, 8, 8) + bounds: 856 120 8 8 + - image: solid-color(108, 15, 0, 255, 8, 8) + bounds: 864 120 8 8 + - image: solid-color(109, 15, 0, 255, 8, 8) + bounds: 872 120 8 8 + - image: solid-color(110, 15, 0, 255, 8, 8) + bounds: 880 120 8 8 + - image: solid-color(111, 15, 0, 255, 8, 8) + bounds: 888 120 8 8 + - image: solid-color(112, 15, 0, 255, 8, 8) + bounds: 896 120 8 8 + - image: solid-color(113, 15, 0, 255, 8, 8) + bounds: 904 120 8 8 + - image: solid-color(114, 15, 0, 255, 8, 8) + bounds: 912 120 8 8 + - image: solid-color(115, 15, 0, 255, 8, 8) + bounds: 920 120 8 8 + - image: solid-color(116, 15, 0, 255, 8, 8) + bounds: 928 120 8 8 + - image: solid-color(117, 15, 0, 255, 8, 8) + bounds: 936 120 8 8 + - image: solid-color(118, 15, 0, 255, 8, 8) + bounds: 944 120 8 8 + - image: solid-color(119, 15, 0, 255, 8, 8) + bounds: 952 120 8 8 + - image: solid-color(120, 15, 0, 255, 8, 8) + bounds: 960 120 8 8 + - image: solid-color(121, 15, 0, 255, 8, 8) + bounds: 968 120 8 8 + - image: solid-color(122, 15, 0, 255, 8, 8) + bounds: 976 120 8 8 + - image: solid-color(123, 15, 0, 255, 8, 8) + bounds: 984 120 8 8 + - image: solid-color(124, 15, 0, 255, 8, 8) + bounds: 992 120 8 8 + - image: solid-color(125, 15, 0, 255, 8, 8) + bounds: 1000 120 8 8 + - image: solid-color(126, 15, 0, 255, 8, 8) + bounds: 1008 120 8 8 + - image: solid-color(127, 15, 0, 255, 8, 8) + bounds: 1016 120 8 8 + - image: solid-color(0, 16, 0, 255, 8, 8) + bounds: 0 128 8 8 + - image: solid-color(1, 16, 0, 255, 8, 8) + bounds: 8 128 8 8 + - image: solid-color(2, 16, 0, 255, 8, 8) + bounds: 16 128 8 8 + - image: solid-color(3, 16, 0, 255, 8, 8) + bounds: 24 128 8 8 + - image: solid-color(4, 16, 0, 255, 8, 8) + bounds: 32 128 8 8 + - image: solid-color(5, 16, 0, 255, 8, 8) + bounds: 40 128 8 8 + - image: solid-color(6, 16, 0, 255, 8, 8) + bounds: 48 128 8 8 + - image: solid-color(7, 16, 0, 255, 8, 8) + bounds: 56 128 8 8 + - image: solid-color(8, 16, 0, 255, 8, 8) + bounds: 64 128 8 8 + - image: solid-color(9, 16, 0, 255, 8, 8) + bounds: 72 128 8 8 + - image: solid-color(10, 16, 0, 255, 8, 8) + bounds: 80 128 8 8 + - image: solid-color(11, 16, 0, 255, 8, 8) + bounds: 88 128 8 8 + - image: solid-color(12, 16, 0, 255, 8, 8) + bounds: 96 128 8 8 + - image: solid-color(13, 16, 0, 255, 8, 8) + bounds: 104 128 8 8 + - image: solid-color(14, 16, 0, 255, 8, 8) + bounds: 112 128 8 8 + - image: solid-color(15, 16, 0, 255, 8, 8) + bounds: 120 128 8 8 + - image: solid-color(16, 16, 0, 255, 8, 8) + bounds: 128 128 8 8 + - image: solid-color(17, 16, 0, 255, 8, 8) + bounds: 136 128 8 8 + - image: solid-color(18, 16, 0, 255, 8, 8) + bounds: 144 128 8 8 + - image: solid-color(19, 16, 0, 255, 8, 8) + bounds: 152 128 8 8 + - image: solid-color(20, 16, 0, 255, 8, 8) + bounds: 160 128 8 8 + - image: solid-color(21, 16, 0, 255, 8, 8) + bounds: 168 128 8 8 + - image: solid-color(22, 16, 0, 255, 8, 8) + bounds: 176 128 8 8 + - image: solid-color(23, 16, 0, 255, 8, 8) + bounds: 184 128 8 8 + - image: solid-color(24, 16, 0, 255, 8, 8) + bounds: 192 128 8 8 + - image: solid-color(25, 16, 0, 255, 8, 8) + bounds: 200 128 8 8 + - image: solid-color(26, 16, 0, 255, 8, 8) + bounds: 208 128 8 8 + - image: solid-color(27, 16, 0, 255, 8, 8) + bounds: 216 128 8 8 + - image: solid-color(28, 16, 0, 255, 8, 8) + bounds: 224 128 8 8 + - image: solid-color(29, 16, 0, 255, 8, 8) + bounds: 232 128 8 8 + - image: solid-color(30, 16, 0, 255, 8, 8) + bounds: 240 128 8 8 + - image: solid-color(31, 16, 0, 255, 8, 8) + bounds: 248 128 8 8 + - image: solid-color(32, 16, 0, 255, 8, 8) + bounds: 256 128 8 8 + - image: solid-color(33, 16, 0, 255, 8, 8) + bounds: 264 128 8 8 + - image: solid-color(34, 16, 0, 255, 8, 8) + bounds: 272 128 8 8 + - image: solid-color(35, 16, 0, 255, 8, 8) + bounds: 280 128 8 8 + - image: solid-color(36, 16, 0, 255, 8, 8) + bounds: 288 128 8 8 + - image: solid-color(37, 16, 0, 255, 8, 8) + bounds: 296 128 8 8 + - image: solid-color(38, 16, 0, 255, 8, 8) + bounds: 304 128 8 8 + - image: solid-color(39, 16, 0, 255, 8, 8) + bounds: 312 128 8 8 + - image: solid-color(40, 16, 0, 255, 8, 8) + bounds: 320 128 8 8 + - image: solid-color(41, 16, 0, 255, 8, 8) + bounds: 328 128 8 8 + - image: solid-color(42, 16, 0, 255, 8, 8) + bounds: 336 128 8 8 + - image: solid-color(43, 16, 0, 255, 8, 8) + bounds: 344 128 8 8 + - image: solid-color(44, 16, 0, 255, 8, 8) + bounds: 352 128 8 8 + - image: solid-color(45, 16, 0, 255, 8, 8) + bounds: 360 128 8 8 + - image: solid-color(46, 16, 0, 255, 8, 8) + bounds: 368 128 8 8 + - image: solid-color(47, 16, 0, 255, 8, 8) + bounds: 376 128 8 8 + - image: solid-color(48, 16, 0, 255, 8, 8) + bounds: 384 128 8 8 + - image: solid-color(49, 16, 0, 255, 8, 8) + bounds: 392 128 8 8 + - image: solid-color(50, 16, 0, 255, 8, 8) + bounds: 400 128 8 8 + - image: solid-color(51, 16, 0, 255, 8, 8) + bounds: 408 128 8 8 + - image: solid-color(52, 16, 0, 255, 8, 8) + bounds: 416 128 8 8 + - image: solid-color(53, 16, 0, 255, 8, 8) + bounds: 424 128 8 8 + - image: solid-color(54, 16, 0, 255, 8, 8) + bounds: 432 128 8 8 + - image: solid-color(55, 16, 0, 255, 8, 8) + bounds: 440 128 8 8 + - image: solid-color(56, 16, 0, 255, 8, 8) + bounds: 448 128 8 8 + - image: solid-color(57, 16, 0, 255, 8, 8) + bounds: 456 128 8 8 + - image: solid-color(58, 16, 0, 255, 8, 8) + bounds: 464 128 8 8 + - image: solid-color(59, 16, 0, 255, 8, 8) + bounds: 472 128 8 8 + - image: solid-color(60, 16, 0, 255, 8, 8) + bounds: 480 128 8 8 + - image: solid-color(61, 16, 0, 255, 8, 8) + bounds: 488 128 8 8 + - image: solid-color(62, 16, 0, 255, 8, 8) + bounds: 496 128 8 8 + - image: solid-color(63, 16, 0, 255, 8, 8) + bounds: 504 128 8 8 + - image: solid-color(64, 16, 0, 255, 8, 8) + bounds: 512 128 8 8 + - image: solid-color(65, 16, 0, 255, 8, 8) + bounds: 520 128 8 8 + - image: solid-color(66, 16, 0, 255, 8, 8) + bounds: 528 128 8 8 + - image: solid-color(67, 16, 0, 255, 8, 8) + bounds: 536 128 8 8 + - image: solid-color(68, 16, 0, 255, 8, 8) + bounds: 544 128 8 8 + - image: solid-color(69, 16, 0, 255, 8, 8) + bounds: 552 128 8 8 + - image: solid-color(70, 16, 0, 255, 8, 8) + bounds: 560 128 8 8 + - image: solid-color(71, 16, 0, 255, 8, 8) + bounds: 568 128 8 8 + - image: solid-color(72, 16, 0, 255, 8, 8) + bounds: 576 128 8 8 + - image: solid-color(73, 16, 0, 255, 8, 8) + bounds: 584 128 8 8 + - image: solid-color(74, 16, 0, 255, 8, 8) + bounds: 592 128 8 8 + - image: solid-color(75, 16, 0, 255, 8, 8) + bounds: 600 128 8 8 + - image: solid-color(76, 16, 0, 255, 8, 8) + bounds: 608 128 8 8 + - image: solid-color(77, 16, 0, 255, 8, 8) + bounds: 616 128 8 8 + - image: solid-color(78, 16, 0, 255, 8, 8) + bounds: 624 128 8 8 + - image: solid-color(79, 16, 0, 255, 8, 8) + bounds: 632 128 8 8 + - image: solid-color(80, 16, 0, 255, 8, 8) + bounds: 640 128 8 8 + - image: solid-color(81, 16, 0, 255, 8, 8) + bounds: 648 128 8 8 + - image: solid-color(82, 16, 0, 255, 8, 8) + bounds: 656 128 8 8 + - image: solid-color(83, 16, 0, 255, 8, 8) + bounds: 664 128 8 8 + - image: solid-color(84, 16, 0, 255, 8, 8) + bounds: 672 128 8 8 + - image: solid-color(85, 16, 0, 255, 8, 8) + bounds: 680 128 8 8 + - image: solid-color(86, 16, 0, 255, 8, 8) + bounds: 688 128 8 8 + - image: solid-color(87, 16, 0, 255, 8, 8) + bounds: 696 128 8 8 + - image: solid-color(88, 16, 0, 255, 8, 8) + bounds: 704 128 8 8 + - image: solid-color(89, 16, 0, 255, 8, 8) + bounds: 712 128 8 8 + - image: solid-color(90, 16, 0, 255, 8, 8) + bounds: 720 128 8 8 + - image: solid-color(91, 16, 0, 255, 8, 8) + bounds: 728 128 8 8 + - image: solid-color(92, 16, 0, 255, 8, 8) + bounds: 736 128 8 8 + - image: solid-color(93, 16, 0, 255, 8, 8) + bounds: 744 128 8 8 + - image: solid-color(94, 16, 0, 255, 8, 8) + bounds: 752 128 8 8 + - image: solid-color(95, 16, 0, 255, 8, 8) + bounds: 760 128 8 8 + - image: solid-color(96, 16, 0, 255, 8, 8) + bounds: 768 128 8 8 + - image: solid-color(97, 16, 0, 255, 8, 8) + bounds: 776 128 8 8 + - image: solid-color(98, 16, 0, 255, 8, 8) + bounds: 784 128 8 8 + - image: solid-color(99, 16, 0, 255, 8, 8) + bounds: 792 128 8 8 + - image: solid-color(100, 16, 0, 255, 8, 8) + bounds: 800 128 8 8 + - image: solid-color(101, 16, 0, 255, 8, 8) + bounds: 808 128 8 8 + - image: solid-color(102, 16, 0, 255, 8, 8) + bounds: 816 128 8 8 + - image: solid-color(103, 16, 0, 255, 8, 8) + bounds: 824 128 8 8 + - image: solid-color(104, 16, 0, 255, 8, 8) + bounds: 832 128 8 8 + - image: solid-color(105, 16, 0, 255, 8, 8) + bounds: 840 128 8 8 + - image: solid-color(106, 16, 0, 255, 8, 8) + bounds: 848 128 8 8 + - image: solid-color(107, 16, 0, 255, 8, 8) + bounds: 856 128 8 8 + - image: solid-color(108, 16, 0, 255, 8, 8) + bounds: 864 128 8 8 + - image: solid-color(109, 16, 0, 255, 8, 8) + bounds: 872 128 8 8 + - image: solid-color(110, 16, 0, 255, 8, 8) + bounds: 880 128 8 8 + - image: solid-color(111, 16, 0, 255, 8, 8) + bounds: 888 128 8 8 + - image: solid-color(112, 16, 0, 255, 8, 8) + bounds: 896 128 8 8 + - image: solid-color(113, 16, 0, 255, 8, 8) + bounds: 904 128 8 8 + - image: solid-color(114, 16, 0, 255, 8, 8) + bounds: 912 128 8 8 + - image: solid-color(115, 16, 0, 255, 8, 8) + bounds: 920 128 8 8 + - image: solid-color(116, 16, 0, 255, 8, 8) + bounds: 928 128 8 8 + - image: solid-color(117, 16, 0, 255, 8, 8) + bounds: 936 128 8 8 + - image: solid-color(118, 16, 0, 255, 8, 8) + bounds: 944 128 8 8 + - image: solid-color(119, 16, 0, 255, 8, 8) + bounds: 952 128 8 8 + - image: solid-color(120, 16, 0, 255, 8, 8) + bounds: 960 128 8 8 + - image: solid-color(121, 16, 0, 255, 8, 8) + bounds: 968 128 8 8 + - image: solid-color(122, 16, 0, 255, 8, 8) + bounds: 976 128 8 8 + - image: solid-color(123, 16, 0, 255, 8, 8) + bounds: 984 128 8 8 + - image: solid-color(124, 16, 0, 255, 8, 8) + bounds: 992 128 8 8 + - image: solid-color(125, 16, 0, 255, 8, 8) + bounds: 1000 128 8 8 + - image: solid-color(126, 16, 0, 255, 8, 8) + bounds: 1008 128 8 8 + - image: solid-color(127, 16, 0, 255, 8, 8) + bounds: 1016 128 8 8 + - image: solid-color(0, 17, 0, 255, 8, 8) + bounds: 0 136 8 8 + - image: solid-color(1, 17, 0, 255, 8, 8) + bounds: 8 136 8 8 + - image: solid-color(2, 17, 0, 255, 8, 8) + bounds: 16 136 8 8 + - image: solid-color(3, 17, 0, 255, 8, 8) + bounds: 24 136 8 8 + - image: solid-color(4, 17, 0, 255, 8, 8) + bounds: 32 136 8 8 + - image: solid-color(5, 17, 0, 255, 8, 8) + bounds: 40 136 8 8 + - image: solid-color(6, 17, 0, 255, 8, 8) + bounds: 48 136 8 8 + - image: solid-color(7, 17, 0, 255, 8, 8) + bounds: 56 136 8 8 + - image: solid-color(8, 17, 0, 255, 8, 8) + bounds: 64 136 8 8 + - image: solid-color(9, 17, 0, 255, 8, 8) + bounds: 72 136 8 8 + - image: solid-color(10, 17, 0, 255, 8, 8) + bounds: 80 136 8 8 + - image: solid-color(11, 17, 0, 255, 8, 8) + bounds: 88 136 8 8 + - image: solid-color(12, 17, 0, 255, 8, 8) + bounds: 96 136 8 8 + - image: solid-color(13, 17, 0, 255, 8, 8) + bounds: 104 136 8 8 + - image: solid-color(14, 17, 0, 255, 8, 8) + bounds: 112 136 8 8 + - image: solid-color(15, 17, 0, 255, 8, 8) + bounds: 120 136 8 8 + - image: solid-color(16, 17, 0, 255, 8, 8) + bounds: 128 136 8 8 + - image: solid-color(17, 17, 0, 255, 8, 8) + bounds: 136 136 8 8 + - image: solid-color(18, 17, 0, 255, 8, 8) + bounds: 144 136 8 8 + - image: solid-color(19, 17, 0, 255, 8, 8) + bounds: 152 136 8 8 + - image: solid-color(20, 17, 0, 255, 8, 8) + bounds: 160 136 8 8 + - image: solid-color(21, 17, 0, 255, 8, 8) + bounds: 168 136 8 8 + - image: solid-color(22, 17, 0, 255, 8, 8) + bounds: 176 136 8 8 + - image: solid-color(23, 17, 0, 255, 8, 8) + bounds: 184 136 8 8 + - image: solid-color(24, 17, 0, 255, 8, 8) + bounds: 192 136 8 8 + - image: solid-color(25, 17, 0, 255, 8, 8) + bounds: 200 136 8 8 + - image: solid-color(26, 17, 0, 255, 8, 8) + bounds: 208 136 8 8 + - image: solid-color(27, 17, 0, 255, 8, 8) + bounds: 216 136 8 8 + - image: solid-color(28, 17, 0, 255, 8, 8) + bounds: 224 136 8 8 + - image: solid-color(29, 17, 0, 255, 8, 8) + bounds: 232 136 8 8 + - image: solid-color(30, 17, 0, 255, 8, 8) + bounds: 240 136 8 8 + - image: solid-color(31, 17, 0, 255, 8, 8) + bounds: 248 136 8 8 + - image: solid-color(32, 17, 0, 255, 8, 8) + bounds: 256 136 8 8 + - image: solid-color(33, 17, 0, 255, 8, 8) + bounds: 264 136 8 8 + - image: solid-color(34, 17, 0, 255, 8, 8) + bounds: 272 136 8 8 + - image: solid-color(35, 17, 0, 255, 8, 8) + bounds: 280 136 8 8 + - image: solid-color(36, 17, 0, 255, 8, 8) + bounds: 288 136 8 8 + - image: solid-color(37, 17, 0, 255, 8, 8) + bounds: 296 136 8 8 + - image: solid-color(38, 17, 0, 255, 8, 8) + bounds: 304 136 8 8 + - image: solid-color(39, 17, 0, 255, 8, 8) + bounds: 312 136 8 8 + - image: solid-color(40, 17, 0, 255, 8, 8) + bounds: 320 136 8 8 + - image: solid-color(41, 17, 0, 255, 8, 8) + bounds: 328 136 8 8 + - image: solid-color(42, 17, 0, 255, 8, 8) + bounds: 336 136 8 8 + - image: solid-color(43, 17, 0, 255, 8, 8) + bounds: 344 136 8 8 + - image: solid-color(44, 17, 0, 255, 8, 8) + bounds: 352 136 8 8 + - image: solid-color(45, 17, 0, 255, 8, 8) + bounds: 360 136 8 8 + - image: solid-color(46, 17, 0, 255, 8, 8) + bounds: 368 136 8 8 + - image: solid-color(47, 17, 0, 255, 8, 8) + bounds: 376 136 8 8 + - image: solid-color(48, 17, 0, 255, 8, 8) + bounds: 384 136 8 8 + - image: solid-color(49, 17, 0, 255, 8, 8) + bounds: 392 136 8 8 + - image: solid-color(50, 17, 0, 255, 8, 8) + bounds: 400 136 8 8 + - image: solid-color(51, 17, 0, 255, 8, 8) + bounds: 408 136 8 8 + - image: solid-color(52, 17, 0, 255, 8, 8) + bounds: 416 136 8 8 + - image: solid-color(53, 17, 0, 255, 8, 8) + bounds: 424 136 8 8 + - image: solid-color(54, 17, 0, 255, 8, 8) + bounds: 432 136 8 8 + - image: solid-color(55, 17, 0, 255, 8, 8) + bounds: 440 136 8 8 + - image: solid-color(56, 17, 0, 255, 8, 8) + bounds: 448 136 8 8 + - image: solid-color(57, 17, 0, 255, 8, 8) + bounds: 456 136 8 8 + - image: solid-color(58, 17, 0, 255, 8, 8) + bounds: 464 136 8 8 + - image: solid-color(59, 17, 0, 255, 8, 8) + bounds: 472 136 8 8 + - image: solid-color(60, 17, 0, 255, 8, 8) + bounds: 480 136 8 8 + - image: solid-color(61, 17, 0, 255, 8, 8) + bounds: 488 136 8 8 + - image: solid-color(62, 17, 0, 255, 8, 8) + bounds: 496 136 8 8 + - image: solid-color(63, 17, 0, 255, 8, 8) + bounds: 504 136 8 8 + - image: solid-color(64, 17, 0, 255, 8, 8) + bounds: 512 136 8 8 + - image: solid-color(65, 17, 0, 255, 8, 8) + bounds: 520 136 8 8 + - image: solid-color(66, 17, 0, 255, 8, 8) + bounds: 528 136 8 8 + - image: solid-color(67, 17, 0, 255, 8, 8) + bounds: 536 136 8 8 + - image: solid-color(68, 17, 0, 255, 8, 8) + bounds: 544 136 8 8 + - image: solid-color(69, 17, 0, 255, 8, 8) + bounds: 552 136 8 8 + - image: solid-color(70, 17, 0, 255, 8, 8) + bounds: 560 136 8 8 + - image: solid-color(71, 17, 0, 255, 8, 8) + bounds: 568 136 8 8 + - image: solid-color(72, 17, 0, 255, 8, 8) + bounds: 576 136 8 8 + - image: solid-color(73, 17, 0, 255, 8, 8) + bounds: 584 136 8 8 + - image: solid-color(74, 17, 0, 255, 8, 8) + bounds: 592 136 8 8 + - image: solid-color(75, 17, 0, 255, 8, 8) + bounds: 600 136 8 8 + - image: solid-color(76, 17, 0, 255, 8, 8) + bounds: 608 136 8 8 + - image: solid-color(77, 17, 0, 255, 8, 8) + bounds: 616 136 8 8 + - image: solid-color(78, 17, 0, 255, 8, 8) + bounds: 624 136 8 8 + - image: solid-color(79, 17, 0, 255, 8, 8) + bounds: 632 136 8 8 + - image: solid-color(80, 17, 0, 255, 8, 8) + bounds: 640 136 8 8 + - image: solid-color(81, 17, 0, 255, 8, 8) + bounds: 648 136 8 8 + - image: solid-color(82, 17, 0, 255, 8, 8) + bounds: 656 136 8 8 + - image: solid-color(83, 17, 0, 255, 8, 8) + bounds: 664 136 8 8 + - image: solid-color(84, 17, 0, 255, 8, 8) + bounds: 672 136 8 8 + - image: solid-color(85, 17, 0, 255, 8, 8) + bounds: 680 136 8 8 + - image: solid-color(86, 17, 0, 255, 8, 8) + bounds: 688 136 8 8 + - image: solid-color(87, 17, 0, 255, 8, 8) + bounds: 696 136 8 8 + - image: solid-color(88, 17, 0, 255, 8, 8) + bounds: 704 136 8 8 + - image: solid-color(89, 17, 0, 255, 8, 8) + bounds: 712 136 8 8 + - image: solid-color(90, 17, 0, 255, 8, 8) + bounds: 720 136 8 8 + - image: solid-color(91, 17, 0, 255, 8, 8) + bounds: 728 136 8 8 + - image: solid-color(92, 17, 0, 255, 8, 8) + bounds: 736 136 8 8 + - image: solid-color(93, 17, 0, 255, 8, 8) + bounds: 744 136 8 8 + - image: solid-color(94, 17, 0, 255, 8, 8) + bounds: 752 136 8 8 + - image: solid-color(95, 17, 0, 255, 8, 8) + bounds: 760 136 8 8 + - image: solid-color(96, 17, 0, 255, 8, 8) + bounds: 768 136 8 8 + - image: solid-color(97, 17, 0, 255, 8, 8) + bounds: 776 136 8 8 + - image: solid-color(98, 17, 0, 255, 8, 8) + bounds: 784 136 8 8 + - image: solid-color(99, 17, 0, 255, 8, 8) + bounds: 792 136 8 8 + - image: solid-color(100, 17, 0, 255, 8, 8) + bounds: 800 136 8 8 + - image: solid-color(101, 17, 0, 255, 8, 8) + bounds: 808 136 8 8 + - image: solid-color(102, 17, 0, 255, 8, 8) + bounds: 816 136 8 8 + - image: solid-color(103, 17, 0, 255, 8, 8) + bounds: 824 136 8 8 + - image: solid-color(104, 17, 0, 255, 8, 8) + bounds: 832 136 8 8 + - image: solid-color(105, 17, 0, 255, 8, 8) + bounds: 840 136 8 8 + - image: solid-color(106, 17, 0, 255, 8, 8) + bounds: 848 136 8 8 + - image: solid-color(107, 17, 0, 255, 8, 8) + bounds: 856 136 8 8 + - image: solid-color(108, 17, 0, 255, 8, 8) + bounds: 864 136 8 8 + - image: solid-color(109, 17, 0, 255, 8, 8) + bounds: 872 136 8 8 + - image: solid-color(110, 17, 0, 255, 8, 8) + bounds: 880 136 8 8 + - image: solid-color(111, 17, 0, 255, 8, 8) + bounds: 888 136 8 8 + - image: solid-color(112, 17, 0, 255, 8, 8) + bounds: 896 136 8 8 + - image: solid-color(113, 17, 0, 255, 8, 8) + bounds: 904 136 8 8 + - image: solid-color(114, 17, 0, 255, 8, 8) + bounds: 912 136 8 8 + - image: solid-color(115, 17, 0, 255, 8, 8) + bounds: 920 136 8 8 + - image: solid-color(116, 17, 0, 255, 8, 8) + bounds: 928 136 8 8 + - image: solid-color(117, 17, 0, 255, 8, 8) + bounds: 936 136 8 8 + - image: solid-color(118, 17, 0, 255, 8, 8) + bounds: 944 136 8 8 + - image: solid-color(119, 17, 0, 255, 8, 8) + bounds: 952 136 8 8 + - image: solid-color(120, 17, 0, 255, 8, 8) + bounds: 960 136 8 8 + - image: solid-color(121, 17, 0, 255, 8, 8) + bounds: 968 136 8 8 + - image: solid-color(122, 17, 0, 255, 8, 8) + bounds: 976 136 8 8 + - image: solid-color(123, 17, 0, 255, 8, 8) + bounds: 984 136 8 8 + - image: solid-color(124, 17, 0, 255, 8, 8) + bounds: 992 136 8 8 + - image: solid-color(125, 17, 0, 255, 8, 8) + bounds: 1000 136 8 8 + - image: solid-color(126, 17, 0, 255, 8, 8) + bounds: 1008 136 8 8 + - image: solid-color(127, 17, 0, 255, 8, 8) + bounds: 1016 136 8 8 + - image: solid-color(0, 18, 0, 255, 8, 8) + bounds: 0 144 8 8 + - image: solid-color(1, 18, 0, 255, 8, 8) + bounds: 8 144 8 8 + - image: solid-color(2, 18, 0, 255, 8, 8) + bounds: 16 144 8 8 + - image: solid-color(3, 18, 0, 255, 8, 8) + bounds: 24 144 8 8 + - image: solid-color(4, 18, 0, 255, 8, 8) + bounds: 32 144 8 8 + - image: solid-color(5, 18, 0, 255, 8, 8) + bounds: 40 144 8 8 + - image: solid-color(6, 18, 0, 255, 8, 8) + bounds: 48 144 8 8 + - image: solid-color(7, 18, 0, 255, 8, 8) + bounds: 56 144 8 8 + - image: solid-color(8, 18, 0, 255, 8, 8) + bounds: 64 144 8 8 + - image: solid-color(9, 18, 0, 255, 8, 8) + bounds: 72 144 8 8 + - image: solid-color(10, 18, 0, 255, 8, 8) + bounds: 80 144 8 8 + - image: solid-color(11, 18, 0, 255, 8, 8) + bounds: 88 144 8 8 + - image: solid-color(12, 18, 0, 255, 8, 8) + bounds: 96 144 8 8 + - image: solid-color(13, 18, 0, 255, 8, 8) + bounds: 104 144 8 8 + - image: solid-color(14, 18, 0, 255, 8, 8) + bounds: 112 144 8 8 + - image: solid-color(15, 18, 0, 255, 8, 8) + bounds: 120 144 8 8 + - image: solid-color(16, 18, 0, 255, 8, 8) + bounds: 128 144 8 8 + - image: solid-color(17, 18, 0, 255, 8, 8) + bounds: 136 144 8 8 + - image: solid-color(18, 18, 0, 255, 8, 8) + bounds: 144 144 8 8 + - image: solid-color(19, 18, 0, 255, 8, 8) + bounds: 152 144 8 8 + - image: solid-color(20, 18, 0, 255, 8, 8) + bounds: 160 144 8 8 + - image: solid-color(21, 18, 0, 255, 8, 8) + bounds: 168 144 8 8 + - image: solid-color(22, 18, 0, 255, 8, 8) + bounds: 176 144 8 8 + - image: solid-color(23, 18, 0, 255, 8, 8) + bounds: 184 144 8 8 + - image: solid-color(24, 18, 0, 255, 8, 8) + bounds: 192 144 8 8 + - image: solid-color(25, 18, 0, 255, 8, 8) + bounds: 200 144 8 8 + - image: solid-color(26, 18, 0, 255, 8, 8) + bounds: 208 144 8 8 + - image: solid-color(27, 18, 0, 255, 8, 8) + bounds: 216 144 8 8 + - image: solid-color(28, 18, 0, 255, 8, 8) + bounds: 224 144 8 8 + - image: solid-color(29, 18, 0, 255, 8, 8) + bounds: 232 144 8 8 + - image: solid-color(30, 18, 0, 255, 8, 8) + bounds: 240 144 8 8 + - image: solid-color(31, 18, 0, 255, 8, 8) + bounds: 248 144 8 8 + - image: solid-color(32, 18, 0, 255, 8, 8) + bounds: 256 144 8 8 + - image: solid-color(33, 18, 0, 255, 8, 8) + bounds: 264 144 8 8 + - image: solid-color(34, 18, 0, 255, 8, 8) + bounds: 272 144 8 8 + - image: solid-color(35, 18, 0, 255, 8, 8) + bounds: 280 144 8 8 + - image: solid-color(36, 18, 0, 255, 8, 8) + bounds: 288 144 8 8 + - image: solid-color(37, 18, 0, 255, 8, 8) + bounds: 296 144 8 8 + - image: solid-color(38, 18, 0, 255, 8, 8) + bounds: 304 144 8 8 + - image: solid-color(39, 18, 0, 255, 8, 8) + bounds: 312 144 8 8 + - image: solid-color(40, 18, 0, 255, 8, 8) + bounds: 320 144 8 8 + - image: solid-color(41, 18, 0, 255, 8, 8) + bounds: 328 144 8 8 + - image: solid-color(42, 18, 0, 255, 8, 8) + bounds: 336 144 8 8 + - image: solid-color(43, 18, 0, 255, 8, 8) + bounds: 344 144 8 8 + - image: solid-color(44, 18, 0, 255, 8, 8) + bounds: 352 144 8 8 + - image: solid-color(45, 18, 0, 255, 8, 8) + bounds: 360 144 8 8 + - image: solid-color(46, 18, 0, 255, 8, 8) + bounds: 368 144 8 8 + - image: solid-color(47, 18, 0, 255, 8, 8) + bounds: 376 144 8 8 + - image: solid-color(48, 18, 0, 255, 8, 8) + bounds: 384 144 8 8 + - image: solid-color(49, 18, 0, 255, 8, 8) + bounds: 392 144 8 8 + - image: solid-color(50, 18, 0, 255, 8, 8) + bounds: 400 144 8 8 + - image: solid-color(51, 18, 0, 255, 8, 8) + bounds: 408 144 8 8 + - image: solid-color(52, 18, 0, 255, 8, 8) + bounds: 416 144 8 8 + - image: solid-color(53, 18, 0, 255, 8, 8) + bounds: 424 144 8 8 + - image: solid-color(54, 18, 0, 255, 8, 8) + bounds: 432 144 8 8 + - image: solid-color(55, 18, 0, 255, 8, 8) + bounds: 440 144 8 8 + - image: solid-color(56, 18, 0, 255, 8, 8) + bounds: 448 144 8 8 + - image: solid-color(57, 18, 0, 255, 8, 8) + bounds: 456 144 8 8 + - image: solid-color(58, 18, 0, 255, 8, 8) + bounds: 464 144 8 8 + - image: solid-color(59, 18, 0, 255, 8, 8) + bounds: 472 144 8 8 + - image: solid-color(60, 18, 0, 255, 8, 8) + bounds: 480 144 8 8 + - image: solid-color(61, 18, 0, 255, 8, 8) + bounds: 488 144 8 8 + - image: solid-color(62, 18, 0, 255, 8, 8) + bounds: 496 144 8 8 + - image: solid-color(63, 18, 0, 255, 8, 8) + bounds: 504 144 8 8 + - image: solid-color(64, 18, 0, 255, 8, 8) + bounds: 512 144 8 8 + - image: solid-color(65, 18, 0, 255, 8, 8) + bounds: 520 144 8 8 + - image: solid-color(66, 18, 0, 255, 8, 8) + bounds: 528 144 8 8 + - image: solid-color(67, 18, 0, 255, 8, 8) + bounds: 536 144 8 8 + - image: solid-color(68, 18, 0, 255, 8, 8) + bounds: 544 144 8 8 + - image: solid-color(69, 18, 0, 255, 8, 8) + bounds: 552 144 8 8 + - image: solid-color(70, 18, 0, 255, 8, 8) + bounds: 560 144 8 8 + - image: solid-color(71, 18, 0, 255, 8, 8) + bounds: 568 144 8 8 + - image: solid-color(72, 18, 0, 255, 8, 8) + bounds: 576 144 8 8 + - image: solid-color(73, 18, 0, 255, 8, 8) + bounds: 584 144 8 8 + - image: solid-color(74, 18, 0, 255, 8, 8) + bounds: 592 144 8 8 + - image: solid-color(75, 18, 0, 255, 8, 8) + bounds: 600 144 8 8 + - image: solid-color(76, 18, 0, 255, 8, 8) + bounds: 608 144 8 8 + - image: solid-color(77, 18, 0, 255, 8, 8) + bounds: 616 144 8 8 + - image: solid-color(78, 18, 0, 255, 8, 8) + bounds: 624 144 8 8 + - image: solid-color(79, 18, 0, 255, 8, 8) + bounds: 632 144 8 8 + - image: solid-color(80, 18, 0, 255, 8, 8) + bounds: 640 144 8 8 + - image: solid-color(81, 18, 0, 255, 8, 8) + bounds: 648 144 8 8 + - image: solid-color(82, 18, 0, 255, 8, 8) + bounds: 656 144 8 8 + - image: solid-color(83, 18, 0, 255, 8, 8) + bounds: 664 144 8 8 + - image: solid-color(84, 18, 0, 255, 8, 8) + bounds: 672 144 8 8 + - image: solid-color(85, 18, 0, 255, 8, 8) + bounds: 680 144 8 8 + - image: solid-color(86, 18, 0, 255, 8, 8) + bounds: 688 144 8 8 + - image: solid-color(87, 18, 0, 255, 8, 8) + bounds: 696 144 8 8 + - image: solid-color(88, 18, 0, 255, 8, 8) + bounds: 704 144 8 8 + - image: solid-color(89, 18, 0, 255, 8, 8) + bounds: 712 144 8 8 + - image: solid-color(90, 18, 0, 255, 8, 8) + bounds: 720 144 8 8 + - image: solid-color(91, 18, 0, 255, 8, 8) + bounds: 728 144 8 8 + - image: solid-color(92, 18, 0, 255, 8, 8) + bounds: 736 144 8 8 + - image: solid-color(93, 18, 0, 255, 8, 8) + bounds: 744 144 8 8 + - image: solid-color(94, 18, 0, 255, 8, 8) + bounds: 752 144 8 8 + - image: solid-color(95, 18, 0, 255, 8, 8) + bounds: 760 144 8 8 + - image: solid-color(96, 18, 0, 255, 8, 8) + bounds: 768 144 8 8 + - image: solid-color(97, 18, 0, 255, 8, 8) + bounds: 776 144 8 8 + - image: solid-color(98, 18, 0, 255, 8, 8) + bounds: 784 144 8 8 + - image: solid-color(99, 18, 0, 255, 8, 8) + bounds: 792 144 8 8 + - image: solid-color(100, 18, 0, 255, 8, 8) + bounds: 800 144 8 8 + - image: solid-color(101, 18, 0, 255, 8, 8) + bounds: 808 144 8 8 + - image: solid-color(102, 18, 0, 255, 8, 8) + bounds: 816 144 8 8 + - image: solid-color(103, 18, 0, 255, 8, 8) + bounds: 824 144 8 8 + - image: solid-color(104, 18, 0, 255, 8, 8) + bounds: 832 144 8 8 + - image: solid-color(105, 18, 0, 255, 8, 8) + bounds: 840 144 8 8 + - image: solid-color(106, 18, 0, 255, 8, 8) + bounds: 848 144 8 8 + - image: solid-color(107, 18, 0, 255, 8, 8) + bounds: 856 144 8 8 + - image: solid-color(108, 18, 0, 255, 8, 8) + bounds: 864 144 8 8 + - image: solid-color(109, 18, 0, 255, 8, 8) + bounds: 872 144 8 8 + - image: solid-color(110, 18, 0, 255, 8, 8) + bounds: 880 144 8 8 + - image: solid-color(111, 18, 0, 255, 8, 8) + bounds: 888 144 8 8 + - image: solid-color(112, 18, 0, 255, 8, 8) + bounds: 896 144 8 8 + - image: solid-color(113, 18, 0, 255, 8, 8) + bounds: 904 144 8 8 + - image: solid-color(114, 18, 0, 255, 8, 8) + bounds: 912 144 8 8 + - image: solid-color(115, 18, 0, 255, 8, 8) + bounds: 920 144 8 8 + - image: solid-color(116, 18, 0, 255, 8, 8) + bounds: 928 144 8 8 + - image: solid-color(117, 18, 0, 255, 8, 8) + bounds: 936 144 8 8 + - image: solid-color(118, 18, 0, 255, 8, 8) + bounds: 944 144 8 8 + - image: solid-color(119, 18, 0, 255, 8, 8) + bounds: 952 144 8 8 + - image: solid-color(120, 18, 0, 255, 8, 8) + bounds: 960 144 8 8 + - image: solid-color(121, 18, 0, 255, 8, 8) + bounds: 968 144 8 8 + - image: solid-color(122, 18, 0, 255, 8, 8) + bounds: 976 144 8 8 + - image: solid-color(123, 18, 0, 255, 8, 8) + bounds: 984 144 8 8 + - image: solid-color(124, 18, 0, 255, 8, 8) + bounds: 992 144 8 8 + - image: solid-color(125, 18, 0, 255, 8, 8) + bounds: 1000 144 8 8 + - image: solid-color(126, 18, 0, 255, 8, 8) + bounds: 1008 144 8 8 + - image: solid-color(127, 18, 0, 255, 8, 8) + bounds: 1016 144 8 8 + - image: solid-color(0, 19, 0, 255, 8, 8) + bounds: 0 152 8 8 + - image: solid-color(1, 19, 0, 255, 8, 8) + bounds: 8 152 8 8 + - image: solid-color(2, 19, 0, 255, 8, 8) + bounds: 16 152 8 8 + - image: solid-color(3, 19, 0, 255, 8, 8) + bounds: 24 152 8 8 + - image: solid-color(4, 19, 0, 255, 8, 8) + bounds: 32 152 8 8 + - image: solid-color(5, 19, 0, 255, 8, 8) + bounds: 40 152 8 8 + - image: solid-color(6, 19, 0, 255, 8, 8) + bounds: 48 152 8 8 + - image: solid-color(7, 19, 0, 255, 8, 8) + bounds: 56 152 8 8 + - image: solid-color(8, 19, 0, 255, 8, 8) + bounds: 64 152 8 8 + - image: solid-color(9, 19, 0, 255, 8, 8) + bounds: 72 152 8 8 + - image: solid-color(10, 19, 0, 255, 8, 8) + bounds: 80 152 8 8 + - image: solid-color(11, 19, 0, 255, 8, 8) + bounds: 88 152 8 8 + - image: solid-color(12, 19, 0, 255, 8, 8) + bounds: 96 152 8 8 + - image: solid-color(13, 19, 0, 255, 8, 8) + bounds: 104 152 8 8 + - image: solid-color(14, 19, 0, 255, 8, 8) + bounds: 112 152 8 8 + - image: solid-color(15, 19, 0, 255, 8, 8) + bounds: 120 152 8 8 + - image: solid-color(16, 19, 0, 255, 8, 8) + bounds: 128 152 8 8 + - image: solid-color(17, 19, 0, 255, 8, 8) + bounds: 136 152 8 8 + - image: solid-color(18, 19, 0, 255, 8, 8) + bounds: 144 152 8 8 + - image: solid-color(19, 19, 0, 255, 8, 8) + bounds: 152 152 8 8 + - image: solid-color(20, 19, 0, 255, 8, 8) + bounds: 160 152 8 8 + - image: solid-color(21, 19, 0, 255, 8, 8) + bounds: 168 152 8 8 + - image: solid-color(22, 19, 0, 255, 8, 8) + bounds: 176 152 8 8 + - image: solid-color(23, 19, 0, 255, 8, 8) + bounds: 184 152 8 8 + - image: solid-color(24, 19, 0, 255, 8, 8) + bounds: 192 152 8 8 + - image: solid-color(25, 19, 0, 255, 8, 8) + bounds: 200 152 8 8 + - image: solid-color(26, 19, 0, 255, 8, 8) + bounds: 208 152 8 8 + - image: solid-color(27, 19, 0, 255, 8, 8) + bounds: 216 152 8 8 + - image: solid-color(28, 19, 0, 255, 8, 8) + bounds: 224 152 8 8 + - image: solid-color(29, 19, 0, 255, 8, 8) + bounds: 232 152 8 8 + - image: solid-color(30, 19, 0, 255, 8, 8) + bounds: 240 152 8 8 + - image: solid-color(31, 19, 0, 255, 8, 8) + bounds: 248 152 8 8 + - image: solid-color(32, 19, 0, 255, 8, 8) + bounds: 256 152 8 8 + - image: solid-color(33, 19, 0, 255, 8, 8) + bounds: 264 152 8 8 + - image: solid-color(34, 19, 0, 255, 8, 8) + bounds: 272 152 8 8 + - image: solid-color(35, 19, 0, 255, 8, 8) + bounds: 280 152 8 8 + - image: solid-color(36, 19, 0, 255, 8, 8) + bounds: 288 152 8 8 + - image: solid-color(37, 19, 0, 255, 8, 8) + bounds: 296 152 8 8 + - image: solid-color(38, 19, 0, 255, 8, 8) + bounds: 304 152 8 8 + - image: solid-color(39, 19, 0, 255, 8, 8) + bounds: 312 152 8 8 + - image: solid-color(40, 19, 0, 255, 8, 8) + bounds: 320 152 8 8 + - image: solid-color(41, 19, 0, 255, 8, 8) + bounds: 328 152 8 8 + - image: solid-color(42, 19, 0, 255, 8, 8) + bounds: 336 152 8 8 + - image: solid-color(43, 19, 0, 255, 8, 8) + bounds: 344 152 8 8 + - image: solid-color(44, 19, 0, 255, 8, 8) + bounds: 352 152 8 8 + - image: solid-color(45, 19, 0, 255, 8, 8) + bounds: 360 152 8 8 + - image: solid-color(46, 19, 0, 255, 8, 8) + bounds: 368 152 8 8 + - image: solid-color(47, 19, 0, 255, 8, 8) + bounds: 376 152 8 8 + - image: solid-color(48, 19, 0, 255, 8, 8) + bounds: 384 152 8 8 + - image: solid-color(49, 19, 0, 255, 8, 8) + bounds: 392 152 8 8 + - image: solid-color(50, 19, 0, 255, 8, 8) + bounds: 400 152 8 8 + - image: solid-color(51, 19, 0, 255, 8, 8) + bounds: 408 152 8 8 + - image: solid-color(52, 19, 0, 255, 8, 8) + bounds: 416 152 8 8 + - image: solid-color(53, 19, 0, 255, 8, 8) + bounds: 424 152 8 8 + - image: solid-color(54, 19, 0, 255, 8, 8) + bounds: 432 152 8 8 + - image: solid-color(55, 19, 0, 255, 8, 8) + bounds: 440 152 8 8 + - image: solid-color(56, 19, 0, 255, 8, 8) + bounds: 448 152 8 8 + - image: solid-color(57, 19, 0, 255, 8, 8) + bounds: 456 152 8 8 + - image: solid-color(58, 19, 0, 255, 8, 8) + bounds: 464 152 8 8 + - image: solid-color(59, 19, 0, 255, 8, 8) + bounds: 472 152 8 8 + - image: solid-color(60, 19, 0, 255, 8, 8) + bounds: 480 152 8 8 + - image: solid-color(61, 19, 0, 255, 8, 8) + bounds: 488 152 8 8 + - image: solid-color(62, 19, 0, 255, 8, 8) + bounds: 496 152 8 8 + - image: solid-color(63, 19, 0, 255, 8, 8) + bounds: 504 152 8 8 + - image: solid-color(64, 19, 0, 255, 8, 8) + bounds: 512 152 8 8 + - image: solid-color(65, 19, 0, 255, 8, 8) + bounds: 520 152 8 8 + - image: solid-color(66, 19, 0, 255, 8, 8) + bounds: 528 152 8 8 + - image: solid-color(67, 19, 0, 255, 8, 8) + bounds: 536 152 8 8 + - image: solid-color(68, 19, 0, 255, 8, 8) + bounds: 544 152 8 8 + - image: solid-color(69, 19, 0, 255, 8, 8) + bounds: 552 152 8 8 + - image: solid-color(70, 19, 0, 255, 8, 8) + bounds: 560 152 8 8 + - image: solid-color(71, 19, 0, 255, 8, 8) + bounds: 568 152 8 8 + - image: solid-color(72, 19, 0, 255, 8, 8) + bounds: 576 152 8 8 + - image: solid-color(73, 19, 0, 255, 8, 8) + bounds: 584 152 8 8 + - image: solid-color(74, 19, 0, 255, 8, 8) + bounds: 592 152 8 8 + - image: solid-color(75, 19, 0, 255, 8, 8) + bounds: 600 152 8 8 + - image: solid-color(76, 19, 0, 255, 8, 8) + bounds: 608 152 8 8 + - image: solid-color(77, 19, 0, 255, 8, 8) + bounds: 616 152 8 8 + - image: solid-color(78, 19, 0, 255, 8, 8) + bounds: 624 152 8 8 + - image: solid-color(79, 19, 0, 255, 8, 8) + bounds: 632 152 8 8 + - image: solid-color(80, 19, 0, 255, 8, 8) + bounds: 640 152 8 8 + - image: solid-color(81, 19, 0, 255, 8, 8) + bounds: 648 152 8 8 + - image: solid-color(82, 19, 0, 255, 8, 8) + bounds: 656 152 8 8 + - image: solid-color(83, 19, 0, 255, 8, 8) + bounds: 664 152 8 8 + - image: solid-color(84, 19, 0, 255, 8, 8) + bounds: 672 152 8 8 + - image: solid-color(85, 19, 0, 255, 8, 8) + bounds: 680 152 8 8 + - image: solid-color(86, 19, 0, 255, 8, 8) + bounds: 688 152 8 8 + - image: solid-color(87, 19, 0, 255, 8, 8) + bounds: 696 152 8 8 + - image: solid-color(88, 19, 0, 255, 8, 8) + bounds: 704 152 8 8 + - image: solid-color(89, 19, 0, 255, 8, 8) + bounds: 712 152 8 8 + - image: solid-color(90, 19, 0, 255, 8, 8) + bounds: 720 152 8 8 + - image: solid-color(91, 19, 0, 255, 8, 8) + bounds: 728 152 8 8 + - image: solid-color(92, 19, 0, 255, 8, 8) + bounds: 736 152 8 8 + - image: solid-color(93, 19, 0, 255, 8, 8) + bounds: 744 152 8 8 + - image: solid-color(94, 19, 0, 255, 8, 8) + bounds: 752 152 8 8 + - image: solid-color(95, 19, 0, 255, 8, 8) + bounds: 760 152 8 8 + - image: solid-color(96, 19, 0, 255, 8, 8) + bounds: 768 152 8 8 + - image: solid-color(97, 19, 0, 255, 8, 8) + bounds: 776 152 8 8 + - image: solid-color(98, 19, 0, 255, 8, 8) + bounds: 784 152 8 8 + - image: solid-color(99, 19, 0, 255, 8, 8) + bounds: 792 152 8 8 + - image: solid-color(100, 19, 0, 255, 8, 8) + bounds: 800 152 8 8 + - image: solid-color(101, 19, 0, 255, 8, 8) + bounds: 808 152 8 8 + - image: solid-color(102, 19, 0, 255, 8, 8) + bounds: 816 152 8 8 + - image: solid-color(103, 19, 0, 255, 8, 8) + bounds: 824 152 8 8 + - image: solid-color(104, 19, 0, 255, 8, 8) + bounds: 832 152 8 8 + - image: solid-color(105, 19, 0, 255, 8, 8) + bounds: 840 152 8 8 + - image: solid-color(106, 19, 0, 255, 8, 8) + bounds: 848 152 8 8 + - image: solid-color(107, 19, 0, 255, 8, 8) + bounds: 856 152 8 8 + - image: solid-color(108, 19, 0, 255, 8, 8) + bounds: 864 152 8 8 + - image: solid-color(109, 19, 0, 255, 8, 8) + bounds: 872 152 8 8 + - image: solid-color(110, 19, 0, 255, 8, 8) + bounds: 880 152 8 8 + - image: solid-color(111, 19, 0, 255, 8, 8) + bounds: 888 152 8 8 + - image: solid-color(112, 19, 0, 255, 8, 8) + bounds: 896 152 8 8 + - image: solid-color(113, 19, 0, 255, 8, 8) + bounds: 904 152 8 8 + - image: solid-color(114, 19, 0, 255, 8, 8) + bounds: 912 152 8 8 + - image: solid-color(115, 19, 0, 255, 8, 8) + bounds: 920 152 8 8 + - image: solid-color(116, 19, 0, 255, 8, 8) + bounds: 928 152 8 8 + - image: solid-color(117, 19, 0, 255, 8, 8) + bounds: 936 152 8 8 + - image: solid-color(118, 19, 0, 255, 8, 8) + bounds: 944 152 8 8 + - image: solid-color(119, 19, 0, 255, 8, 8) + bounds: 952 152 8 8 + - image: solid-color(120, 19, 0, 255, 8, 8) + bounds: 960 152 8 8 + - image: solid-color(121, 19, 0, 255, 8, 8) + bounds: 968 152 8 8 + - image: solid-color(122, 19, 0, 255, 8, 8) + bounds: 976 152 8 8 + - image: solid-color(123, 19, 0, 255, 8, 8) + bounds: 984 152 8 8 + - image: solid-color(124, 19, 0, 255, 8, 8) + bounds: 992 152 8 8 + - image: solid-color(125, 19, 0, 255, 8, 8) + bounds: 1000 152 8 8 + - image: solid-color(126, 19, 0, 255, 8, 8) + bounds: 1008 152 8 8 + - image: solid-color(127, 19, 0, 255, 8, 8) + bounds: 1016 152 8 8 + - image: solid-color(0, 20, 0, 255, 8, 8) + bounds: 0 160 8 8 + - image: solid-color(1, 20, 0, 255, 8, 8) + bounds: 8 160 8 8 + - image: solid-color(2, 20, 0, 255, 8, 8) + bounds: 16 160 8 8 + - image: solid-color(3, 20, 0, 255, 8, 8) + bounds: 24 160 8 8 + - image: solid-color(4, 20, 0, 255, 8, 8) + bounds: 32 160 8 8 + - image: solid-color(5, 20, 0, 255, 8, 8) + bounds: 40 160 8 8 + - image: solid-color(6, 20, 0, 255, 8, 8) + bounds: 48 160 8 8 + - image: solid-color(7, 20, 0, 255, 8, 8) + bounds: 56 160 8 8 + - image: solid-color(8, 20, 0, 255, 8, 8) + bounds: 64 160 8 8 + - image: solid-color(9, 20, 0, 255, 8, 8) + bounds: 72 160 8 8 + - image: solid-color(10, 20, 0, 255, 8, 8) + bounds: 80 160 8 8 + - image: solid-color(11, 20, 0, 255, 8, 8) + bounds: 88 160 8 8 + - image: solid-color(12, 20, 0, 255, 8, 8) + bounds: 96 160 8 8 + - image: solid-color(13, 20, 0, 255, 8, 8) + bounds: 104 160 8 8 + - image: solid-color(14, 20, 0, 255, 8, 8) + bounds: 112 160 8 8 + - image: solid-color(15, 20, 0, 255, 8, 8) + bounds: 120 160 8 8 + - image: solid-color(16, 20, 0, 255, 8, 8) + bounds: 128 160 8 8 + - image: solid-color(17, 20, 0, 255, 8, 8) + bounds: 136 160 8 8 + - image: solid-color(18, 20, 0, 255, 8, 8) + bounds: 144 160 8 8 + - image: solid-color(19, 20, 0, 255, 8, 8) + bounds: 152 160 8 8 + - image: solid-color(20, 20, 0, 255, 8, 8) + bounds: 160 160 8 8 + - image: solid-color(21, 20, 0, 255, 8, 8) + bounds: 168 160 8 8 + - image: solid-color(22, 20, 0, 255, 8, 8) + bounds: 176 160 8 8 + - image: solid-color(23, 20, 0, 255, 8, 8) + bounds: 184 160 8 8 + - image: solid-color(24, 20, 0, 255, 8, 8) + bounds: 192 160 8 8 + - image: solid-color(25, 20, 0, 255, 8, 8) + bounds: 200 160 8 8 + - image: solid-color(26, 20, 0, 255, 8, 8) + bounds: 208 160 8 8 + - image: solid-color(27, 20, 0, 255, 8, 8) + bounds: 216 160 8 8 + - image: solid-color(28, 20, 0, 255, 8, 8) + bounds: 224 160 8 8 + - image: solid-color(29, 20, 0, 255, 8, 8) + bounds: 232 160 8 8 + - image: solid-color(30, 20, 0, 255, 8, 8) + bounds: 240 160 8 8 + - image: solid-color(31, 20, 0, 255, 8, 8) + bounds: 248 160 8 8 + - image: solid-color(32, 20, 0, 255, 8, 8) + bounds: 256 160 8 8 + - image: solid-color(33, 20, 0, 255, 8, 8) + bounds: 264 160 8 8 + - image: solid-color(34, 20, 0, 255, 8, 8) + bounds: 272 160 8 8 + - image: solid-color(35, 20, 0, 255, 8, 8) + bounds: 280 160 8 8 + - image: solid-color(36, 20, 0, 255, 8, 8) + bounds: 288 160 8 8 + - image: solid-color(37, 20, 0, 255, 8, 8) + bounds: 296 160 8 8 + - image: solid-color(38, 20, 0, 255, 8, 8) + bounds: 304 160 8 8 + - image: solid-color(39, 20, 0, 255, 8, 8) + bounds: 312 160 8 8 + - image: solid-color(40, 20, 0, 255, 8, 8) + bounds: 320 160 8 8 + - image: solid-color(41, 20, 0, 255, 8, 8) + bounds: 328 160 8 8 + - image: solid-color(42, 20, 0, 255, 8, 8) + bounds: 336 160 8 8 + - image: solid-color(43, 20, 0, 255, 8, 8) + bounds: 344 160 8 8 + - image: solid-color(44, 20, 0, 255, 8, 8) + bounds: 352 160 8 8 + - image: solid-color(45, 20, 0, 255, 8, 8) + bounds: 360 160 8 8 + - image: solid-color(46, 20, 0, 255, 8, 8) + bounds: 368 160 8 8 + - image: solid-color(47, 20, 0, 255, 8, 8) + bounds: 376 160 8 8 + - image: solid-color(48, 20, 0, 255, 8, 8) + bounds: 384 160 8 8 + - image: solid-color(49, 20, 0, 255, 8, 8) + bounds: 392 160 8 8 + - image: solid-color(50, 20, 0, 255, 8, 8) + bounds: 400 160 8 8 + - image: solid-color(51, 20, 0, 255, 8, 8) + bounds: 408 160 8 8 + - image: solid-color(52, 20, 0, 255, 8, 8) + bounds: 416 160 8 8 + - image: solid-color(53, 20, 0, 255, 8, 8) + bounds: 424 160 8 8 + - image: solid-color(54, 20, 0, 255, 8, 8) + bounds: 432 160 8 8 + - image: solid-color(55, 20, 0, 255, 8, 8) + bounds: 440 160 8 8 + - image: solid-color(56, 20, 0, 255, 8, 8) + bounds: 448 160 8 8 + - image: solid-color(57, 20, 0, 255, 8, 8) + bounds: 456 160 8 8 + - image: solid-color(58, 20, 0, 255, 8, 8) + bounds: 464 160 8 8 + - image: solid-color(59, 20, 0, 255, 8, 8) + bounds: 472 160 8 8 + - image: solid-color(60, 20, 0, 255, 8, 8) + bounds: 480 160 8 8 + - image: solid-color(61, 20, 0, 255, 8, 8) + bounds: 488 160 8 8 + - image: solid-color(62, 20, 0, 255, 8, 8) + bounds: 496 160 8 8 + - image: solid-color(63, 20, 0, 255, 8, 8) + bounds: 504 160 8 8 + - image: solid-color(64, 20, 0, 255, 8, 8) + bounds: 512 160 8 8 + - image: solid-color(65, 20, 0, 255, 8, 8) + bounds: 520 160 8 8 + - image: solid-color(66, 20, 0, 255, 8, 8) + bounds: 528 160 8 8 + - image: solid-color(67, 20, 0, 255, 8, 8) + bounds: 536 160 8 8 + - image: solid-color(68, 20, 0, 255, 8, 8) + bounds: 544 160 8 8 + - image: solid-color(69, 20, 0, 255, 8, 8) + bounds: 552 160 8 8 + - image: solid-color(70, 20, 0, 255, 8, 8) + bounds: 560 160 8 8 + - image: solid-color(71, 20, 0, 255, 8, 8) + bounds: 568 160 8 8 + - image: solid-color(72, 20, 0, 255, 8, 8) + bounds: 576 160 8 8 + - image: solid-color(73, 20, 0, 255, 8, 8) + bounds: 584 160 8 8 + - image: solid-color(74, 20, 0, 255, 8, 8) + bounds: 592 160 8 8 + - image: solid-color(75, 20, 0, 255, 8, 8) + bounds: 600 160 8 8 + - image: solid-color(76, 20, 0, 255, 8, 8) + bounds: 608 160 8 8 + - image: solid-color(77, 20, 0, 255, 8, 8) + bounds: 616 160 8 8 + - image: solid-color(78, 20, 0, 255, 8, 8) + bounds: 624 160 8 8 + - image: solid-color(79, 20, 0, 255, 8, 8) + bounds: 632 160 8 8 + - image: solid-color(80, 20, 0, 255, 8, 8) + bounds: 640 160 8 8 + - image: solid-color(81, 20, 0, 255, 8, 8) + bounds: 648 160 8 8 + - image: solid-color(82, 20, 0, 255, 8, 8) + bounds: 656 160 8 8 + - image: solid-color(83, 20, 0, 255, 8, 8) + bounds: 664 160 8 8 + - image: solid-color(84, 20, 0, 255, 8, 8) + bounds: 672 160 8 8 + - image: solid-color(85, 20, 0, 255, 8, 8) + bounds: 680 160 8 8 + - image: solid-color(86, 20, 0, 255, 8, 8) + bounds: 688 160 8 8 + - image: solid-color(87, 20, 0, 255, 8, 8) + bounds: 696 160 8 8 + - image: solid-color(88, 20, 0, 255, 8, 8) + bounds: 704 160 8 8 + - image: solid-color(89, 20, 0, 255, 8, 8) + bounds: 712 160 8 8 + - image: solid-color(90, 20, 0, 255, 8, 8) + bounds: 720 160 8 8 + - image: solid-color(91, 20, 0, 255, 8, 8) + bounds: 728 160 8 8 + - image: solid-color(92, 20, 0, 255, 8, 8) + bounds: 736 160 8 8 + - image: solid-color(93, 20, 0, 255, 8, 8) + bounds: 744 160 8 8 + - image: solid-color(94, 20, 0, 255, 8, 8) + bounds: 752 160 8 8 + - image: solid-color(95, 20, 0, 255, 8, 8) + bounds: 760 160 8 8 + - image: solid-color(96, 20, 0, 255, 8, 8) + bounds: 768 160 8 8 + - image: solid-color(97, 20, 0, 255, 8, 8) + bounds: 776 160 8 8 + - image: solid-color(98, 20, 0, 255, 8, 8) + bounds: 784 160 8 8 + - image: solid-color(99, 20, 0, 255, 8, 8) + bounds: 792 160 8 8 + - image: solid-color(100, 20, 0, 255, 8, 8) + bounds: 800 160 8 8 + - image: solid-color(101, 20, 0, 255, 8, 8) + bounds: 808 160 8 8 + - image: solid-color(102, 20, 0, 255, 8, 8) + bounds: 816 160 8 8 + - image: solid-color(103, 20, 0, 255, 8, 8) + bounds: 824 160 8 8 + - image: solid-color(104, 20, 0, 255, 8, 8) + bounds: 832 160 8 8 + - image: solid-color(105, 20, 0, 255, 8, 8) + bounds: 840 160 8 8 + - image: solid-color(106, 20, 0, 255, 8, 8) + bounds: 848 160 8 8 + - image: solid-color(107, 20, 0, 255, 8, 8) + bounds: 856 160 8 8 + - image: solid-color(108, 20, 0, 255, 8, 8) + bounds: 864 160 8 8 + - image: solid-color(109, 20, 0, 255, 8, 8) + bounds: 872 160 8 8 + - image: solid-color(110, 20, 0, 255, 8, 8) + bounds: 880 160 8 8 + - image: solid-color(111, 20, 0, 255, 8, 8) + bounds: 888 160 8 8 + - image: solid-color(112, 20, 0, 255, 8, 8) + bounds: 896 160 8 8 + - image: solid-color(113, 20, 0, 255, 8, 8) + bounds: 904 160 8 8 + - image: solid-color(114, 20, 0, 255, 8, 8) + bounds: 912 160 8 8 + - image: solid-color(115, 20, 0, 255, 8, 8) + bounds: 920 160 8 8 + - image: solid-color(116, 20, 0, 255, 8, 8) + bounds: 928 160 8 8 + - image: solid-color(117, 20, 0, 255, 8, 8) + bounds: 936 160 8 8 + - image: solid-color(118, 20, 0, 255, 8, 8) + bounds: 944 160 8 8 + - image: solid-color(119, 20, 0, 255, 8, 8) + bounds: 952 160 8 8 + - image: solid-color(120, 20, 0, 255, 8, 8) + bounds: 960 160 8 8 + - image: solid-color(121, 20, 0, 255, 8, 8) + bounds: 968 160 8 8 + - image: solid-color(122, 20, 0, 255, 8, 8) + bounds: 976 160 8 8 + - image: solid-color(123, 20, 0, 255, 8, 8) + bounds: 984 160 8 8 + - image: solid-color(124, 20, 0, 255, 8, 8) + bounds: 992 160 8 8 + - image: solid-color(125, 20, 0, 255, 8, 8) + bounds: 1000 160 8 8 + - image: solid-color(126, 20, 0, 255, 8, 8) + bounds: 1008 160 8 8 + - image: solid-color(127, 20, 0, 255, 8, 8) + bounds: 1016 160 8 8 + - image: solid-color(0, 21, 0, 255, 8, 8) + bounds: 0 168 8 8 + - image: solid-color(1, 21, 0, 255, 8, 8) + bounds: 8 168 8 8 + - image: solid-color(2, 21, 0, 255, 8, 8) + bounds: 16 168 8 8 + - image: solid-color(3, 21, 0, 255, 8, 8) + bounds: 24 168 8 8 + - image: solid-color(4, 21, 0, 255, 8, 8) + bounds: 32 168 8 8 + - image: solid-color(5, 21, 0, 255, 8, 8) + bounds: 40 168 8 8 + - image: solid-color(6, 21, 0, 255, 8, 8) + bounds: 48 168 8 8 + - image: solid-color(7, 21, 0, 255, 8, 8) + bounds: 56 168 8 8 + - image: solid-color(8, 21, 0, 255, 8, 8) + bounds: 64 168 8 8 + - image: solid-color(9, 21, 0, 255, 8, 8) + bounds: 72 168 8 8 + - image: solid-color(10, 21, 0, 255, 8, 8) + bounds: 80 168 8 8 + - image: solid-color(11, 21, 0, 255, 8, 8) + bounds: 88 168 8 8 + - image: solid-color(12, 21, 0, 255, 8, 8) + bounds: 96 168 8 8 + - image: solid-color(13, 21, 0, 255, 8, 8) + bounds: 104 168 8 8 + - image: solid-color(14, 21, 0, 255, 8, 8) + bounds: 112 168 8 8 + - image: solid-color(15, 21, 0, 255, 8, 8) + bounds: 120 168 8 8 + - image: solid-color(16, 21, 0, 255, 8, 8) + bounds: 128 168 8 8 + - image: solid-color(17, 21, 0, 255, 8, 8) + bounds: 136 168 8 8 + - image: solid-color(18, 21, 0, 255, 8, 8) + bounds: 144 168 8 8 + - image: solid-color(19, 21, 0, 255, 8, 8) + bounds: 152 168 8 8 + - image: solid-color(20, 21, 0, 255, 8, 8) + bounds: 160 168 8 8 + - image: solid-color(21, 21, 0, 255, 8, 8) + bounds: 168 168 8 8 + - image: solid-color(22, 21, 0, 255, 8, 8) + bounds: 176 168 8 8 + - image: solid-color(23, 21, 0, 255, 8, 8) + bounds: 184 168 8 8 + - image: solid-color(24, 21, 0, 255, 8, 8) + bounds: 192 168 8 8 + - image: solid-color(25, 21, 0, 255, 8, 8) + bounds: 200 168 8 8 + - image: solid-color(26, 21, 0, 255, 8, 8) + bounds: 208 168 8 8 + - image: solid-color(27, 21, 0, 255, 8, 8) + bounds: 216 168 8 8 + - image: solid-color(28, 21, 0, 255, 8, 8) + bounds: 224 168 8 8 + - image: solid-color(29, 21, 0, 255, 8, 8) + bounds: 232 168 8 8 + - image: solid-color(30, 21, 0, 255, 8, 8) + bounds: 240 168 8 8 + - image: solid-color(31, 21, 0, 255, 8, 8) + bounds: 248 168 8 8 + - image: solid-color(32, 21, 0, 255, 8, 8) + bounds: 256 168 8 8 + - image: solid-color(33, 21, 0, 255, 8, 8) + bounds: 264 168 8 8 + - image: solid-color(34, 21, 0, 255, 8, 8) + bounds: 272 168 8 8 + - image: solid-color(35, 21, 0, 255, 8, 8) + bounds: 280 168 8 8 + - image: solid-color(36, 21, 0, 255, 8, 8) + bounds: 288 168 8 8 + - image: solid-color(37, 21, 0, 255, 8, 8) + bounds: 296 168 8 8 + - image: solid-color(38, 21, 0, 255, 8, 8) + bounds: 304 168 8 8 + - image: solid-color(39, 21, 0, 255, 8, 8) + bounds: 312 168 8 8 + - image: solid-color(40, 21, 0, 255, 8, 8) + bounds: 320 168 8 8 + - image: solid-color(41, 21, 0, 255, 8, 8) + bounds: 328 168 8 8 + - image: solid-color(42, 21, 0, 255, 8, 8) + bounds: 336 168 8 8 + - image: solid-color(43, 21, 0, 255, 8, 8) + bounds: 344 168 8 8 + - image: solid-color(44, 21, 0, 255, 8, 8) + bounds: 352 168 8 8 + - image: solid-color(45, 21, 0, 255, 8, 8) + bounds: 360 168 8 8 + - image: solid-color(46, 21, 0, 255, 8, 8) + bounds: 368 168 8 8 + - image: solid-color(47, 21, 0, 255, 8, 8) + bounds: 376 168 8 8 + - image: solid-color(48, 21, 0, 255, 8, 8) + bounds: 384 168 8 8 + - image: solid-color(49, 21, 0, 255, 8, 8) + bounds: 392 168 8 8 + - image: solid-color(50, 21, 0, 255, 8, 8) + bounds: 400 168 8 8 + - image: solid-color(51, 21, 0, 255, 8, 8) + bounds: 408 168 8 8 + - image: solid-color(52, 21, 0, 255, 8, 8) + bounds: 416 168 8 8 + - image: solid-color(53, 21, 0, 255, 8, 8) + bounds: 424 168 8 8 + - image: solid-color(54, 21, 0, 255, 8, 8) + bounds: 432 168 8 8 + - image: solid-color(55, 21, 0, 255, 8, 8) + bounds: 440 168 8 8 + - image: solid-color(56, 21, 0, 255, 8, 8) + bounds: 448 168 8 8 + - image: solid-color(57, 21, 0, 255, 8, 8) + bounds: 456 168 8 8 + - image: solid-color(58, 21, 0, 255, 8, 8) + bounds: 464 168 8 8 + - image: solid-color(59, 21, 0, 255, 8, 8) + bounds: 472 168 8 8 + - image: solid-color(60, 21, 0, 255, 8, 8) + bounds: 480 168 8 8 + - image: solid-color(61, 21, 0, 255, 8, 8) + bounds: 488 168 8 8 + - image: solid-color(62, 21, 0, 255, 8, 8) + bounds: 496 168 8 8 + - image: solid-color(63, 21, 0, 255, 8, 8) + bounds: 504 168 8 8 + - image: solid-color(64, 21, 0, 255, 8, 8) + bounds: 512 168 8 8 + - image: solid-color(65, 21, 0, 255, 8, 8) + bounds: 520 168 8 8 + - image: solid-color(66, 21, 0, 255, 8, 8) + bounds: 528 168 8 8 + - image: solid-color(67, 21, 0, 255, 8, 8) + bounds: 536 168 8 8 + - image: solid-color(68, 21, 0, 255, 8, 8) + bounds: 544 168 8 8 + - image: solid-color(69, 21, 0, 255, 8, 8) + bounds: 552 168 8 8 + - image: solid-color(70, 21, 0, 255, 8, 8) + bounds: 560 168 8 8 + - image: solid-color(71, 21, 0, 255, 8, 8) + bounds: 568 168 8 8 + - image: solid-color(72, 21, 0, 255, 8, 8) + bounds: 576 168 8 8 + - image: solid-color(73, 21, 0, 255, 8, 8) + bounds: 584 168 8 8 + - image: solid-color(74, 21, 0, 255, 8, 8) + bounds: 592 168 8 8 + - image: solid-color(75, 21, 0, 255, 8, 8) + bounds: 600 168 8 8 + - image: solid-color(76, 21, 0, 255, 8, 8) + bounds: 608 168 8 8 + - image: solid-color(77, 21, 0, 255, 8, 8) + bounds: 616 168 8 8 + - image: solid-color(78, 21, 0, 255, 8, 8) + bounds: 624 168 8 8 + - image: solid-color(79, 21, 0, 255, 8, 8) + bounds: 632 168 8 8 + - image: solid-color(80, 21, 0, 255, 8, 8) + bounds: 640 168 8 8 + - image: solid-color(81, 21, 0, 255, 8, 8) + bounds: 648 168 8 8 + - image: solid-color(82, 21, 0, 255, 8, 8) + bounds: 656 168 8 8 + - image: solid-color(83, 21, 0, 255, 8, 8) + bounds: 664 168 8 8 + - image: solid-color(84, 21, 0, 255, 8, 8) + bounds: 672 168 8 8 + - image: solid-color(85, 21, 0, 255, 8, 8) + bounds: 680 168 8 8 + - image: solid-color(86, 21, 0, 255, 8, 8) + bounds: 688 168 8 8 + - image: solid-color(87, 21, 0, 255, 8, 8) + bounds: 696 168 8 8 + - image: solid-color(88, 21, 0, 255, 8, 8) + bounds: 704 168 8 8 + - image: solid-color(89, 21, 0, 255, 8, 8) + bounds: 712 168 8 8 + - image: solid-color(90, 21, 0, 255, 8, 8) + bounds: 720 168 8 8 + - image: solid-color(91, 21, 0, 255, 8, 8) + bounds: 728 168 8 8 + - image: solid-color(92, 21, 0, 255, 8, 8) + bounds: 736 168 8 8 + - image: solid-color(93, 21, 0, 255, 8, 8) + bounds: 744 168 8 8 + - image: solid-color(94, 21, 0, 255, 8, 8) + bounds: 752 168 8 8 + - image: solid-color(95, 21, 0, 255, 8, 8) + bounds: 760 168 8 8 + - image: solid-color(96, 21, 0, 255, 8, 8) + bounds: 768 168 8 8 + - image: solid-color(97, 21, 0, 255, 8, 8) + bounds: 776 168 8 8 + - image: solid-color(98, 21, 0, 255, 8, 8) + bounds: 784 168 8 8 + - image: solid-color(99, 21, 0, 255, 8, 8) + bounds: 792 168 8 8 + - image: solid-color(100, 21, 0, 255, 8, 8) + bounds: 800 168 8 8 + - image: solid-color(101, 21, 0, 255, 8, 8) + bounds: 808 168 8 8 + - image: solid-color(102, 21, 0, 255, 8, 8) + bounds: 816 168 8 8 + - image: solid-color(103, 21, 0, 255, 8, 8) + bounds: 824 168 8 8 + - image: solid-color(104, 21, 0, 255, 8, 8) + bounds: 832 168 8 8 + - image: solid-color(105, 21, 0, 255, 8, 8) + bounds: 840 168 8 8 + - image: solid-color(106, 21, 0, 255, 8, 8) + bounds: 848 168 8 8 + - image: solid-color(107, 21, 0, 255, 8, 8) + bounds: 856 168 8 8 + - image: solid-color(108, 21, 0, 255, 8, 8) + bounds: 864 168 8 8 + - image: solid-color(109, 21, 0, 255, 8, 8) + bounds: 872 168 8 8 + - image: solid-color(110, 21, 0, 255, 8, 8) + bounds: 880 168 8 8 + - image: solid-color(111, 21, 0, 255, 8, 8) + bounds: 888 168 8 8 + - image: solid-color(112, 21, 0, 255, 8, 8) + bounds: 896 168 8 8 + - image: solid-color(113, 21, 0, 255, 8, 8) + bounds: 904 168 8 8 + - image: solid-color(114, 21, 0, 255, 8, 8) + bounds: 912 168 8 8 + - image: solid-color(115, 21, 0, 255, 8, 8) + bounds: 920 168 8 8 + - image: solid-color(116, 21, 0, 255, 8, 8) + bounds: 928 168 8 8 + - image: solid-color(117, 21, 0, 255, 8, 8) + bounds: 936 168 8 8 + - image: solid-color(118, 21, 0, 255, 8, 8) + bounds: 944 168 8 8 + - image: solid-color(119, 21, 0, 255, 8, 8) + bounds: 952 168 8 8 + - image: solid-color(120, 21, 0, 255, 8, 8) + bounds: 960 168 8 8 + - image: solid-color(121, 21, 0, 255, 8, 8) + bounds: 968 168 8 8 + - image: solid-color(122, 21, 0, 255, 8, 8) + bounds: 976 168 8 8 + - image: solid-color(123, 21, 0, 255, 8, 8) + bounds: 984 168 8 8 + - image: solid-color(124, 21, 0, 255, 8, 8) + bounds: 992 168 8 8 + - image: solid-color(125, 21, 0, 255, 8, 8) + bounds: 1000 168 8 8 + - image: solid-color(126, 21, 0, 255, 8, 8) + bounds: 1008 168 8 8 + - image: solid-color(127, 21, 0, 255, 8, 8) + bounds: 1016 168 8 8 + - image: solid-color(0, 22, 0, 255, 8, 8) + bounds: 0 176 8 8 + - image: solid-color(1, 22, 0, 255, 8, 8) + bounds: 8 176 8 8 + - image: solid-color(2, 22, 0, 255, 8, 8) + bounds: 16 176 8 8 + - image: solid-color(3, 22, 0, 255, 8, 8) + bounds: 24 176 8 8 + - image: solid-color(4, 22, 0, 255, 8, 8) + bounds: 32 176 8 8 + - image: solid-color(5, 22, 0, 255, 8, 8) + bounds: 40 176 8 8 + - image: solid-color(6, 22, 0, 255, 8, 8) + bounds: 48 176 8 8 + - image: solid-color(7, 22, 0, 255, 8, 8) + bounds: 56 176 8 8 + - image: solid-color(8, 22, 0, 255, 8, 8) + bounds: 64 176 8 8 + - image: solid-color(9, 22, 0, 255, 8, 8) + bounds: 72 176 8 8 + - image: solid-color(10, 22, 0, 255, 8, 8) + bounds: 80 176 8 8 + - image: solid-color(11, 22, 0, 255, 8, 8) + bounds: 88 176 8 8 + - image: solid-color(12, 22, 0, 255, 8, 8) + bounds: 96 176 8 8 + - image: solid-color(13, 22, 0, 255, 8, 8) + bounds: 104 176 8 8 + - image: solid-color(14, 22, 0, 255, 8, 8) + bounds: 112 176 8 8 + - image: solid-color(15, 22, 0, 255, 8, 8) + bounds: 120 176 8 8 + - image: solid-color(16, 22, 0, 255, 8, 8) + bounds: 128 176 8 8 + - image: solid-color(17, 22, 0, 255, 8, 8) + bounds: 136 176 8 8 + - image: solid-color(18, 22, 0, 255, 8, 8) + bounds: 144 176 8 8 + - image: solid-color(19, 22, 0, 255, 8, 8) + bounds: 152 176 8 8 + - image: solid-color(20, 22, 0, 255, 8, 8) + bounds: 160 176 8 8 + - image: solid-color(21, 22, 0, 255, 8, 8) + bounds: 168 176 8 8 + - image: solid-color(22, 22, 0, 255, 8, 8) + bounds: 176 176 8 8 + - image: solid-color(23, 22, 0, 255, 8, 8) + bounds: 184 176 8 8 + - image: solid-color(24, 22, 0, 255, 8, 8) + bounds: 192 176 8 8 + - image: solid-color(25, 22, 0, 255, 8, 8) + bounds: 200 176 8 8 + - image: solid-color(26, 22, 0, 255, 8, 8) + bounds: 208 176 8 8 + - image: solid-color(27, 22, 0, 255, 8, 8) + bounds: 216 176 8 8 + - image: solid-color(28, 22, 0, 255, 8, 8) + bounds: 224 176 8 8 + - image: solid-color(29, 22, 0, 255, 8, 8) + bounds: 232 176 8 8 + - image: solid-color(30, 22, 0, 255, 8, 8) + bounds: 240 176 8 8 + - image: solid-color(31, 22, 0, 255, 8, 8) + bounds: 248 176 8 8 + - image: solid-color(32, 22, 0, 255, 8, 8) + bounds: 256 176 8 8 + - image: solid-color(33, 22, 0, 255, 8, 8) + bounds: 264 176 8 8 + - image: solid-color(34, 22, 0, 255, 8, 8) + bounds: 272 176 8 8 + - image: solid-color(35, 22, 0, 255, 8, 8) + bounds: 280 176 8 8 + - image: solid-color(36, 22, 0, 255, 8, 8) + bounds: 288 176 8 8 + - image: solid-color(37, 22, 0, 255, 8, 8) + bounds: 296 176 8 8 + - image: solid-color(38, 22, 0, 255, 8, 8) + bounds: 304 176 8 8 + - image: solid-color(39, 22, 0, 255, 8, 8) + bounds: 312 176 8 8 + - image: solid-color(40, 22, 0, 255, 8, 8) + bounds: 320 176 8 8 + - image: solid-color(41, 22, 0, 255, 8, 8) + bounds: 328 176 8 8 + - image: solid-color(42, 22, 0, 255, 8, 8) + bounds: 336 176 8 8 + - image: solid-color(43, 22, 0, 255, 8, 8) + bounds: 344 176 8 8 + - image: solid-color(44, 22, 0, 255, 8, 8) + bounds: 352 176 8 8 + - image: solid-color(45, 22, 0, 255, 8, 8) + bounds: 360 176 8 8 + - image: solid-color(46, 22, 0, 255, 8, 8) + bounds: 368 176 8 8 + - image: solid-color(47, 22, 0, 255, 8, 8) + bounds: 376 176 8 8 + - image: solid-color(48, 22, 0, 255, 8, 8) + bounds: 384 176 8 8 + - image: solid-color(49, 22, 0, 255, 8, 8) + bounds: 392 176 8 8 + - image: solid-color(50, 22, 0, 255, 8, 8) + bounds: 400 176 8 8 + - image: solid-color(51, 22, 0, 255, 8, 8) + bounds: 408 176 8 8 + - image: solid-color(52, 22, 0, 255, 8, 8) + bounds: 416 176 8 8 + - image: solid-color(53, 22, 0, 255, 8, 8) + bounds: 424 176 8 8 + - image: solid-color(54, 22, 0, 255, 8, 8) + bounds: 432 176 8 8 + - image: solid-color(55, 22, 0, 255, 8, 8) + bounds: 440 176 8 8 + - image: solid-color(56, 22, 0, 255, 8, 8) + bounds: 448 176 8 8 + - image: solid-color(57, 22, 0, 255, 8, 8) + bounds: 456 176 8 8 + - image: solid-color(58, 22, 0, 255, 8, 8) + bounds: 464 176 8 8 + - image: solid-color(59, 22, 0, 255, 8, 8) + bounds: 472 176 8 8 + - image: solid-color(60, 22, 0, 255, 8, 8) + bounds: 480 176 8 8 + - image: solid-color(61, 22, 0, 255, 8, 8) + bounds: 488 176 8 8 + - image: solid-color(62, 22, 0, 255, 8, 8) + bounds: 496 176 8 8 + - image: solid-color(63, 22, 0, 255, 8, 8) + bounds: 504 176 8 8 + - image: solid-color(64, 22, 0, 255, 8, 8) + bounds: 512 176 8 8 + - image: solid-color(65, 22, 0, 255, 8, 8) + bounds: 520 176 8 8 + - image: solid-color(66, 22, 0, 255, 8, 8) + bounds: 528 176 8 8 + - image: solid-color(67, 22, 0, 255, 8, 8) + bounds: 536 176 8 8 + - image: solid-color(68, 22, 0, 255, 8, 8) + bounds: 544 176 8 8 + - image: solid-color(69, 22, 0, 255, 8, 8) + bounds: 552 176 8 8 + - image: solid-color(70, 22, 0, 255, 8, 8) + bounds: 560 176 8 8 + - image: solid-color(71, 22, 0, 255, 8, 8) + bounds: 568 176 8 8 + - image: solid-color(72, 22, 0, 255, 8, 8) + bounds: 576 176 8 8 + - image: solid-color(73, 22, 0, 255, 8, 8) + bounds: 584 176 8 8 + - image: solid-color(74, 22, 0, 255, 8, 8) + bounds: 592 176 8 8 + - image: solid-color(75, 22, 0, 255, 8, 8) + bounds: 600 176 8 8 + - image: solid-color(76, 22, 0, 255, 8, 8) + bounds: 608 176 8 8 + - image: solid-color(77, 22, 0, 255, 8, 8) + bounds: 616 176 8 8 + - image: solid-color(78, 22, 0, 255, 8, 8) + bounds: 624 176 8 8 + - image: solid-color(79, 22, 0, 255, 8, 8) + bounds: 632 176 8 8 + - image: solid-color(80, 22, 0, 255, 8, 8) + bounds: 640 176 8 8 + - image: solid-color(81, 22, 0, 255, 8, 8) + bounds: 648 176 8 8 + - image: solid-color(82, 22, 0, 255, 8, 8) + bounds: 656 176 8 8 + - image: solid-color(83, 22, 0, 255, 8, 8) + bounds: 664 176 8 8 + - image: solid-color(84, 22, 0, 255, 8, 8) + bounds: 672 176 8 8 + - image: solid-color(85, 22, 0, 255, 8, 8) + bounds: 680 176 8 8 + - image: solid-color(86, 22, 0, 255, 8, 8) + bounds: 688 176 8 8 + - image: solid-color(87, 22, 0, 255, 8, 8) + bounds: 696 176 8 8 + - image: solid-color(88, 22, 0, 255, 8, 8) + bounds: 704 176 8 8 + - image: solid-color(89, 22, 0, 255, 8, 8) + bounds: 712 176 8 8 + - image: solid-color(90, 22, 0, 255, 8, 8) + bounds: 720 176 8 8 + - image: solid-color(91, 22, 0, 255, 8, 8) + bounds: 728 176 8 8 + - image: solid-color(92, 22, 0, 255, 8, 8) + bounds: 736 176 8 8 + - image: solid-color(93, 22, 0, 255, 8, 8) + bounds: 744 176 8 8 + - image: solid-color(94, 22, 0, 255, 8, 8) + bounds: 752 176 8 8 + - image: solid-color(95, 22, 0, 255, 8, 8) + bounds: 760 176 8 8 + - image: solid-color(96, 22, 0, 255, 8, 8) + bounds: 768 176 8 8 + - image: solid-color(97, 22, 0, 255, 8, 8) + bounds: 776 176 8 8 + - image: solid-color(98, 22, 0, 255, 8, 8) + bounds: 784 176 8 8 + - image: solid-color(99, 22, 0, 255, 8, 8) + bounds: 792 176 8 8 + - image: solid-color(100, 22, 0, 255, 8, 8) + bounds: 800 176 8 8 + - image: solid-color(101, 22, 0, 255, 8, 8) + bounds: 808 176 8 8 + - image: solid-color(102, 22, 0, 255, 8, 8) + bounds: 816 176 8 8 + - image: solid-color(103, 22, 0, 255, 8, 8) + bounds: 824 176 8 8 + - image: solid-color(104, 22, 0, 255, 8, 8) + bounds: 832 176 8 8 + - image: solid-color(105, 22, 0, 255, 8, 8) + bounds: 840 176 8 8 + - image: solid-color(106, 22, 0, 255, 8, 8) + bounds: 848 176 8 8 + - image: solid-color(107, 22, 0, 255, 8, 8) + bounds: 856 176 8 8 + - image: solid-color(108, 22, 0, 255, 8, 8) + bounds: 864 176 8 8 + - image: solid-color(109, 22, 0, 255, 8, 8) + bounds: 872 176 8 8 + - image: solid-color(110, 22, 0, 255, 8, 8) + bounds: 880 176 8 8 + - image: solid-color(111, 22, 0, 255, 8, 8) + bounds: 888 176 8 8 + - image: solid-color(112, 22, 0, 255, 8, 8) + bounds: 896 176 8 8 + - image: solid-color(113, 22, 0, 255, 8, 8) + bounds: 904 176 8 8 + - image: solid-color(114, 22, 0, 255, 8, 8) + bounds: 912 176 8 8 + - image: solid-color(115, 22, 0, 255, 8, 8) + bounds: 920 176 8 8 + - image: solid-color(116, 22, 0, 255, 8, 8) + bounds: 928 176 8 8 + - image: solid-color(117, 22, 0, 255, 8, 8) + bounds: 936 176 8 8 + - image: solid-color(118, 22, 0, 255, 8, 8) + bounds: 944 176 8 8 + - image: solid-color(119, 22, 0, 255, 8, 8) + bounds: 952 176 8 8 + - image: solid-color(120, 22, 0, 255, 8, 8) + bounds: 960 176 8 8 + - image: solid-color(121, 22, 0, 255, 8, 8) + bounds: 968 176 8 8 + - image: solid-color(122, 22, 0, 255, 8, 8) + bounds: 976 176 8 8 + - image: solid-color(123, 22, 0, 255, 8, 8) + bounds: 984 176 8 8 + - image: solid-color(124, 22, 0, 255, 8, 8) + bounds: 992 176 8 8 + - image: solid-color(125, 22, 0, 255, 8, 8) + bounds: 1000 176 8 8 + - image: solid-color(126, 22, 0, 255, 8, 8) + bounds: 1008 176 8 8 + - image: solid-color(127, 22, 0, 255, 8, 8) + bounds: 1016 176 8 8 + - image: solid-color(0, 23, 0, 255, 8, 8) + bounds: 0 184 8 8 + - image: solid-color(1, 23, 0, 255, 8, 8) + bounds: 8 184 8 8 + - image: solid-color(2, 23, 0, 255, 8, 8) + bounds: 16 184 8 8 + - image: solid-color(3, 23, 0, 255, 8, 8) + bounds: 24 184 8 8 + - image: solid-color(4, 23, 0, 255, 8, 8) + bounds: 32 184 8 8 + - image: solid-color(5, 23, 0, 255, 8, 8) + bounds: 40 184 8 8 + - image: solid-color(6, 23, 0, 255, 8, 8) + bounds: 48 184 8 8 + - image: solid-color(7, 23, 0, 255, 8, 8) + bounds: 56 184 8 8 + - image: solid-color(8, 23, 0, 255, 8, 8) + bounds: 64 184 8 8 + - image: solid-color(9, 23, 0, 255, 8, 8) + bounds: 72 184 8 8 + - image: solid-color(10, 23, 0, 255, 8, 8) + bounds: 80 184 8 8 + - image: solid-color(11, 23, 0, 255, 8, 8) + bounds: 88 184 8 8 + - image: solid-color(12, 23, 0, 255, 8, 8) + bounds: 96 184 8 8 + - image: solid-color(13, 23, 0, 255, 8, 8) + bounds: 104 184 8 8 + - image: solid-color(14, 23, 0, 255, 8, 8) + bounds: 112 184 8 8 + - image: solid-color(15, 23, 0, 255, 8, 8) + bounds: 120 184 8 8 + - image: solid-color(16, 23, 0, 255, 8, 8) + bounds: 128 184 8 8 + - image: solid-color(17, 23, 0, 255, 8, 8) + bounds: 136 184 8 8 + - image: solid-color(18, 23, 0, 255, 8, 8) + bounds: 144 184 8 8 + - image: solid-color(19, 23, 0, 255, 8, 8) + bounds: 152 184 8 8 + - image: solid-color(20, 23, 0, 255, 8, 8) + bounds: 160 184 8 8 + - image: solid-color(21, 23, 0, 255, 8, 8) + bounds: 168 184 8 8 + - image: solid-color(22, 23, 0, 255, 8, 8) + bounds: 176 184 8 8 + - image: solid-color(23, 23, 0, 255, 8, 8) + bounds: 184 184 8 8 + - image: solid-color(24, 23, 0, 255, 8, 8) + bounds: 192 184 8 8 + - image: solid-color(25, 23, 0, 255, 8, 8) + bounds: 200 184 8 8 + - image: solid-color(26, 23, 0, 255, 8, 8) + bounds: 208 184 8 8 + - image: solid-color(27, 23, 0, 255, 8, 8) + bounds: 216 184 8 8 + - image: solid-color(28, 23, 0, 255, 8, 8) + bounds: 224 184 8 8 + - image: solid-color(29, 23, 0, 255, 8, 8) + bounds: 232 184 8 8 + - image: solid-color(30, 23, 0, 255, 8, 8) + bounds: 240 184 8 8 + - image: solid-color(31, 23, 0, 255, 8, 8) + bounds: 248 184 8 8 + - image: solid-color(32, 23, 0, 255, 8, 8) + bounds: 256 184 8 8 + - image: solid-color(33, 23, 0, 255, 8, 8) + bounds: 264 184 8 8 + - image: solid-color(34, 23, 0, 255, 8, 8) + bounds: 272 184 8 8 + - image: solid-color(35, 23, 0, 255, 8, 8) + bounds: 280 184 8 8 + - image: solid-color(36, 23, 0, 255, 8, 8) + bounds: 288 184 8 8 + - image: solid-color(37, 23, 0, 255, 8, 8) + bounds: 296 184 8 8 + - image: solid-color(38, 23, 0, 255, 8, 8) + bounds: 304 184 8 8 + - image: solid-color(39, 23, 0, 255, 8, 8) + bounds: 312 184 8 8 + - image: solid-color(40, 23, 0, 255, 8, 8) + bounds: 320 184 8 8 + - image: solid-color(41, 23, 0, 255, 8, 8) + bounds: 328 184 8 8 + - image: solid-color(42, 23, 0, 255, 8, 8) + bounds: 336 184 8 8 + - image: solid-color(43, 23, 0, 255, 8, 8) + bounds: 344 184 8 8 + - image: solid-color(44, 23, 0, 255, 8, 8) + bounds: 352 184 8 8 + - image: solid-color(45, 23, 0, 255, 8, 8) + bounds: 360 184 8 8 + - image: solid-color(46, 23, 0, 255, 8, 8) + bounds: 368 184 8 8 + - image: solid-color(47, 23, 0, 255, 8, 8) + bounds: 376 184 8 8 + - image: solid-color(48, 23, 0, 255, 8, 8) + bounds: 384 184 8 8 + - image: solid-color(49, 23, 0, 255, 8, 8) + bounds: 392 184 8 8 + - image: solid-color(50, 23, 0, 255, 8, 8) + bounds: 400 184 8 8 + - image: solid-color(51, 23, 0, 255, 8, 8) + bounds: 408 184 8 8 + - image: solid-color(52, 23, 0, 255, 8, 8) + bounds: 416 184 8 8 + - image: solid-color(53, 23, 0, 255, 8, 8) + bounds: 424 184 8 8 + - image: solid-color(54, 23, 0, 255, 8, 8) + bounds: 432 184 8 8 + - image: solid-color(55, 23, 0, 255, 8, 8) + bounds: 440 184 8 8 + - image: solid-color(56, 23, 0, 255, 8, 8) + bounds: 448 184 8 8 + - image: solid-color(57, 23, 0, 255, 8, 8) + bounds: 456 184 8 8 + - image: solid-color(58, 23, 0, 255, 8, 8) + bounds: 464 184 8 8 + - image: solid-color(59, 23, 0, 255, 8, 8) + bounds: 472 184 8 8 + - image: solid-color(60, 23, 0, 255, 8, 8) + bounds: 480 184 8 8 + - image: solid-color(61, 23, 0, 255, 8, 8) + bounds: 488 184 8 8 + - image: solid-color(62, 23, 0, 255, 8, 8) + bounds: 496 184 8 8 + - image: solid-color(63, 23, 0, 255, 8, 8) + bounds: 504 184 8 8 + - image: solid-color(64, 23, 0, 255, 8, 8) + bounds: 512 184 8 8 + - image: solid-color(65, 23, 0, 255, 8, 8) + bounds: 520 184 8 8 + - image: solid-color(66, 23, 0, 255, 8, 8) + bounds: 528 184 8 8 + - image: solid-color(67, 23, 0, 255, 8, 8) + bounds: 536 184 8 8 + - image: solid-color(68, 23, 0, 255, 8, 8) + bounds: 544 184 8 8 + - image: solid-color(69, 23, 0, 255, 8, 8) + bounds: 552 184 8 8 + - image: solid-color(70, 23, 0, 255, 8, 8) + bounds: 560 184 8 8 + - image: solid-color(71, 23, 0, 255, 8, 8) + bounds: 568 184 8 8 + - image: solid-color(72, 23, 0, 255, 8, 8) + bounds: 576 184 8 8 + - image: solid-color(73, 23, 0, 255, 8, 8) + bounds: 584 184 8 8 + - image: solid-color(74, 23, 0, 255, 8, 8) + bounds: 592 184 8 8 + - image: solid-color(75, 23, 0, 255, 8, 8) + bounds: 600 184 8 8 + - image: solid-color(76, 23, 0, 255, 8, 8) + bounds: 608 184 8 8 + - image: solid-color(77, 23, 0, 255, 8, 8) + bounds: 616 184 8 8 + - image: solid-color(78, 23, 0, 255, 8, 8) + bounds: 624 184 8 8 + - image: solid-color(79, 23, 0, 255, 8, 8) + bounds: 632 184 8 8 + - image: solid-color(80, 23, 0, 255, 8, 8) + bounds: 640 184 8 8 + - image: solid-color(81, 23, 0, 255, 8, 8) + bounds: 648 184 8 8 + - image: solid-color(82, 23, 0, 255, 8, 8) + bounds: 656 184 8 8 + - image: solid-color(83, 23, 0, 255, 8, 8) + bounds: 664 184 8 8 + - image: solid-color(84, 23, 0, 255, 8, 8) + bounds: 672 184 8 8 + - image: solid-color(85, 23, 0, 255, 8, 8) + bounds: 680 184 8 8 + - image: solid-color(86, 23, 0, 255, 8, 8) + bounds: 688 184 8 8 + - image: solid-color(87, 23, 0, 255, 8, 8) + bounds: 696 184 8 8 + - image: solid-color(88, 23, 0, 255, 8, 8) + bounds: 704 184 8 8 + - image: solid-color(89, 23, 0, 255, 8, 8) + bounds: 712 184 8 8 + - image: solid-color(90, 23, 0, 255, 8, 8) + bounds: 720 184 8 8 + - image: solid-color(91, 23, 0, 255, 8, 8) + bounds: 728 184 8 8 + - image: solid-color(92, 23, 0, 255, 8, 8) + bounds: 736 184 8 8 + - image: solid-color(93, 23, 0, 255, 8, 8) + bounds: 744 184 8 8 + - image: solid-color(94, 23, 0, 255, 8, 8) + bounds: 752 184 8 8 + - image: solid-color(95, 23, 0, 255, 8, 8) + bounds: 760 184 8 8 + - image: solid-color(96, 23, 0, 255, 8, 8) + bounds: 768 184 8 8 + - image: solid-color(97, 23, 0, 255, 8, 8) + bounds: 776 184 8 8 + - image: solid-color(98, 23, 0, 255, 8, 8) + bounds: 784 184 8 8 + - image: solid-color(99, 23, 0, 255, 8, 8) + bounds: 792 184 8 8 + - image: solid-color(100, 23, 0, 255, 8, 8) + bounds: 800 184 8 8 + - image: solid-color(101, 23, 0, 255, 8, 8) + bounds: 808 184 8 8 + - image: solid-color(102, 23, 0, 255, 8, 8) + bounds: 816 184 8 8 + - image: solid-color(103, 23, 0, 255, 8, 8) + bounds: 824 184 8 8 + - image: solid-color(104, 23, 0, 255, 8, 8) + bounds: 832 184 8 8 + - image: solid-color(105, 23, 0, 255, 8, 8) + bounds: 840 184 8 8 + - image: solid-color(106, 23, 0, 255, 8, 8) + bounds: 848 184 8 8 + - image: solid-color(107, 23, 0, 255, 8, 8) + bounds: 856 184 8 8 + - image: solid-color(108, 23, 0, 255, 8, 8) + bounds: 864 184 8 8 + - image: solid-color(109, 23, 0, 255, 8, 8) + bounds: 872 184 8 8 + - image: solid-color(110, 23, 0, 255, 8, 8) + bounds: 880 184 8 8 + - image: solid-color(111, 23, 0, 255, 8, 8) + bounds: 888 184 8 8 + - image: solid-color(112, 23, 0, 255, 8, 8) + bounds: 896 184 8 8 + - image: solid-color(113, 23, 0, 255, 8, 8) + bounds: 904 184 8 8 + - image: solid-color(114, 23, 0, 255, 8, 8) + bounds: 912 184 8 8 + - image: solid-color(115, 23, 0, 255, 8, 8) + bounds: 920 184 8 8 + - image: solid-color(116, 23, 0, 255, 8, 8) + bounds: 928 184 8 8 + - image: solid-color(117, 23, 0, 255, 8, 8) + bounds: 936 184 8 8 + - image: solid-color(118, 23, 0, 255, 8, 8) + bounds: 944 184 8 8 + - image: solid-color(119, 23, 0, 255, 8, 8) + bounds: 952 184 8 8 + - image: solid-color(120, 23, 0, 255, 8, 8) + bounds: 960 184 8 8 + - image: solid-color(121, 23, 0, 255, 8, 8) + bounds: 968 184 8 8 + - image: solid-color(122, 23, 0, 255, 8, 8) + bounds: 976 184 8 8 + - image: solid-color(123, 23, 0, 255, 8, 8) + bounds: 984 184 8 8 + - image: solid-color(124, 23, 0, 255, 8, 8) + bounds: 992 184 8 8 + - image: solid-color(125, 23, 0, 255, 8, 8) + bounds: 1000 184 8 8 + - image: solid-color(126, 23, 0, 255, 8, 8) + bounds: 1008 184 8 8 + - image: solid-color(127, 23, 0, 255, 8, 8) + bounds: 1016 184 8 8 + - image: solid-color(0, 24, 0, 255, 8, 8) + bounds: 0 192 8 8 + - image: solid-color(1, 24, 0, 255, 8, 8) + bounds: 8 192 8 8 + - image: solid-color(2, 24, 0, 255, 8, 8) + bounds: 16 192 8 8 + - image: solid-color(3, 24, 0, 255, 8, 8) + bounds: 24 192 8 8 + - image: solid-color(4, 24, 0, 255, 8, 8) + bounds: 32 192 8 8 + - image: solid-color(5, 24, 0, 255, 8, 8) + bounds: 40 192 8 8 + - image: solid-color(6, 24, 0, 255, 8, 8) + bounds: 48 192 8 8 + - image: solid-color(7, 24, 0, 255, 8, 8) + bounds: 56 192 8 8 + - image: solid-color(8, 24, 0, 255, 8, 8) + bounds: 64 192 8 8 + - image: solid-color(9, 24, 0, 255, 8, 8) + bounds: 72 192 8 8 + - image: solid-color(10, 24, 0, 255, 8, 8) + bounds: 80 192 8 8 + - image: solid-color(11, 24, 0, 255, 8, 8) + bounds: 88 192 8 8 + - image: solid-color(12, 24, 0, 255, 8, 8) + bounds: 96 192 8 8 + - image: solid-color(13, 24, 0, 255, 8, 8) + bounds: 104 192 8 8 + - image: solid-color(14, 24, 0, 255, 8, 8) + bounds: 112 192 8 8 + - image: solid-color(15, 24, 0, 255, 8, 8) + bounds: 120 192 8 8 + - image: solid-color(16, 24, 0, 255, 8, 8) + bounds: 128 192 8 8 + - image: solid-color(17, 24, 0, 255, 8, 8) + bounds: 136 192 8 8 + - image: solid-color(18, 24, 0, 255, 8, 8) + bounds: 144 192 8 8 + - image: solid-color(19, 24, 0, 255, 8, 8) + bounds: 152 192 8 8 + - image: solid-color(20, 24, 0, 255, 8, 8) + bounds: 160 192 8 8 + - image: solid-color(21, 24, 0, 255, 8, 8) + bounds: 168 192 8 8 + - image: solid-color(22, 24, 0, 255, 8, 8) + bounds: 176 192 8 8 + - image: solid-color(23, 24, 0, 255, 8, 8) + bounds: 184 192 8 8 + - image: solid-color(24, 24, 0, 255, 8, 8) + bounds: 192 192 8 8 + - image: solid-color(25, 24, 0, 255, 8, 8) + bounds: 200 192 8 8 + - image: solid-color(26, 24, 0, 255, 8, 8) + bounds: 208 192 8 8 + - image: solid-color(27, 24, 0, 255, 8, 8) + bounds: 216 192 8 8 + - image: solid-color(28, 24, 0, 255, 8, 8) + bounds: 224 192 8 8 + - image: solid-color(29, 24, 0, 255, 8, 8) + bounds: 232 192 8 8 + - image: solid-color(30, 24, 0, 255, 8, 8) + bounds: 240 192 8 8 + - image: solid-color(31, 24, 0, 255, 8, 8) + bounds: 248 192 8 8 + - image: solid-color(32, 24, 0, 255, 8, 8) + bounds: 256 192 8 8 + - image: solid-color(33, 24, 0, 255, 8, 8) + bounds: 264 192 8 8 + - image: solid-color(34, 24, 0, 255, 8, 8) + bounds: 272 192 8 8 + - image: solid-color(35, 24, 0, 255, 8, 8) + bounds: 280 192 8 8 + - image: solid-color(36, 24, 0, 255, 8, 8) + bounds: 288 192 8 8 + - image: solid-color(37, 24, 0, 255, 8, 8) + bounds: 296 192 8 8 + - image: solid-color(38, 24, 0, 255, 8, 8) + bounds: 304 192 8 8 + - image: solid-color(39, 24, 0, 255, 8, 8) + bounds: 312 192 8 8 + - image: solid-color(40, 24, 0, 255, 8, 8) + bounds: 320 192 8 8 + - image: solid-color(41, 24, 0, 255, 8, 8) + bounds: 328 192 8 8 + - image: solid-color(42, 24, 0, 255, 8, 8) + bounds: 336 192 8 8 + - image: solid-color(43, 24, 0, 255, 8, 8) + bounds: 344 192 8 8 + - image: solid-color(44, 24, 0, 255, 8, 8) + bounds: 352 192 8 8 + - image: solid-color(45, 24, 0, 255, 8, 8) + bounds: 360 192 8 8 + - image: solid-color(46, 24, 0, 255, 8, 8) + bounds: 368 192 8 8 + - image: solid-color(47, 24, 0, 255, 8, 8) + bounds: 376 192 8 8 + - image: solid-color(48, 24, 0, 255, 8, 8) + bounds: 384 192 8 8 + - image: solid-color(49, 24, 0, 255, 8, 8) + bounds: 392 192 8 8 + - image: solid-color(50, 24, 0, 255, 8, 8) + bounds: 400 192 8 8 + - image: solid-color(51, 24, 0, 255, 8, 8) + bounds: 408 192 8 8 + - image: solid-color(52, 24, 0, 255, 8, 8) + bounds: 416 192 8 8 + - image: solid-color(53, 24, 0, 255, 8, 8) + bounds: 424 192 8 8 + - image: solid-color(54, 24, 0, 255, 8, 8) + bounds: 432 192 8 8 + - image: solid-color(55, 24, 0, 255, 8, 8) + bounds: 440 192 8 8 + - image: solid-color(56, 24, 0, 255, 8, 8) + bounds: 448 192 8 8 + - image: solid-color(57, 24, 0, 255, 8, 8) + bounds: 456 192 8 8 + - image: solid-color(58, 24, 0, 255, 8, 8) + bounds: 464 192 8 8 + - image: solid-color(59, 24, 0, 255, 8, 8) + bounds: 472 192 8 8 + - image: solid-color(60, 24, 0, 255, 8, 8) + bounds: 480 192 8 8 + - image: solid-color(61, 24, 0, 255, 8, 8) + bounds: 488 192 8 8 + - image: solid-color(62, 24, 0, 255, 8, 8) + bounds: 496 192 8 8 + - image: solid-color(63, 24, 0, 255, 8, 8) + bounds: 504 192 8 8 + - image: solid-color(64, 24, 0, 255, 8, 8) + bounds: 512 192 8 8 + - image: solid-color(65, 24, 0, 255, 8, 8) + bounds: 520 192 8 8 + - image: solid-color(66, 24, 0, 255, 8, 8) + bounds: 528 192 8 8 + - image: solid-color(67, 24, 0, 255, 8, 8) + bounds: 536 192 8 8 + - image: solid-color(68, 24, 0, 255, 8, 8) + bounds: 544 192 8 8 + - image: solid-color(69, 24, 0, 255, 8, 8) + bounds: 552 192 8 8 + - image: solid-color(70, 24, 0, 255, 8, 8) + bounds: 560 192 8 8 + - image: solid-color(71, 24, 0, 255, 8, 8) + bounds: 568 192 8 8 + - image: solid-color(72, 24, 0, 255, 8, 8) + bounds: 576 192 8 8 + - image: solid-color(73, 24, 0, 255, 8, 8) + bounds: 584 192 8 8 + - image: solid-color(74, 24, 0, 255, 8, 8) + bounds: 592 192 8 8 + - image: solid-color(75, 24, 0, 255, 8, 8) + bounds: 600 192 8 8 + - image: solid-color(76, 24, 0, 255, 8, 8) + bounds: 608 192 8 8 + - image: solid-color(77, 24, 0, 255, 8, 8) + bounds: 616 192 8 8 + - image: solid-color(78, 24, 0, 255, 8, 8) + bounds: 624 192 8 8 + - image: solid-color(79, 24, 0, 255, 8, 8) + bounds: 632 192 8 8 + - image: solid-color(80, 24, 0, 255, 8, 8) + bounds: 640 192 8 8 + - image: solid-color(81, 24, 0, 255, 8, 8) + bounds: 648 192 8 8 + - image: solid-color(82, 24, 0, 255, 8, 8) + bounds: 656 192 8 8 + - image: solid-color(83, 24, 0, 255, 8, 8) + bounds: 664 192 8 8 + - image: solid-color(84, 24, 0, 255, 8, 8) + bounds: 672 192 8 8 + - image: solid-color(85, 24, 0, 255, 8, 8) + bounds: 680 192 8 8 + - image: solid-color(86, 24, 0, 255, 8, 8) + bounds: 688 192 8 8 + - image: solid-color(87, 24, 0, 255, 8, 8) + bounds: 696 192 8 8 + - image: solid-color(88, 24, 0, 255, 8, 8) + bounds: 704 192 8 8 + - image: solid-color(89, 24, 0, 255, 8, 8) + bounds: 712 192 8 8 + - image: solid-color(90, 24, 0, 255, 8, 8) + bounds: 720 192 8 8 + - image: solid-color(91, 24, 0, 255, 8, 8) + bounds: 728 192 8 8 + - image: solid-color(92, 24, 0, 255, 8, 8) + bounds: 736 192 8 8 + - image: solid-color(93, 24, 0, 255, 8, 8) + bounds: 744 192 8 8 + - image: solid-color(94, 24, 0, 255, 8, 8) + bounds: 752 192 8 8 + - image: solid-color(95, 24, 0, 255, 8, 8) + bounds: 760 192 8 8 + - image: solid-color(96, 24, 0, 255, 8, 8) + bounds: 768 192 8 8 + - image: solid-color(97, 24, 0, 255, 8, 8) + bounds: 776 192 8 8 + - image: solid-color(98, 24, 0, 255, 8, 8) + bounds: 784 192 8 8 + - image: solid-color(99, 24, 0, 255, 8, 8) + bounds: 792 192 8 8 + - image: solid-color(100, 24, 0, 255, 8, 8) + bounds: 800 192 8 8 + - image: solid-color(101, 24, 0, 255, 8, 8) + bounds: 808 192 8 8 + - image: solid-color(102, 24, 0, 255, 8, 8) + bounds: 816 192 8 8 + - image: solid-color(103, 24, 0, 255, 8, 8) + bounds: 824 192 8 8 + - image: solid-color(104, 24, 0, 255, 8, 8) + bounds: 832 192 8 8 + - image: solid-color(105, 24, 0, 255, 8, 8) + bounds: 840 192 8 8 + - image: solid-color(106, 24, 0, 255, 8, 8) + bounds: 848 192 8 8 + - image: solid-color(107, 24, 0, 255, 8, 8) + bounds: 856 192 8 8 + - image: solid-color(108, 24, 0, 255, 8, 8) + bounds: 864 192 8 8 + - image: solid-color(109, 24, 0, 255, 8, 8) + bounds: 872 192 8 8 + - image: solid-color(110, 24, 0, 255, 8, 8) + bounds: 880 192 8 8 + - image: solid-color(111, 24, 0, 255, 8, 8) + bounds: 888 192 8 8 + - image: solid-color(112, 24, 0, 255, 8, 8) + bounds: 896 192 8 8 + - image: solid-color(113, 24, 0, 255, 8, 8) + bounds: 904 192 8 8 + - image: solid-color(114, 24, 0, 255, 8, 8) + bounds: 912 192 8 8 + - image: solid-color(115, 24, 0, 255, 8, 8) + bounds: 920 192 8 8 + - image: solid-color(116, 24, 0, 255, 8, 8) + bounds: 928 192 8 8 + - image: solid-color(117, 24, 0, 255, 8, 8) + bounds: 936 192 8 8 + - image: solid-color(118, 24, 0, 255, 8, 8) + bounds: 944 192 8 8 + - image: solid-color(119, 24, 0, 255, 8, 8) + bounds: 952 192 8 8 + - image: solid-color(120, 24, 0, 255, 8, 8) + bounds: 960 192 8 8 + - image: solid-color(121, 24, 0, 255, 8, 8) + bounds: 968 192 8 8 + - image: solid-color(122, 24, 0, 255, 8, 8) + bounds: 976 192 8 8 + - image: solid-color(123, 24, 0, 255, 8, 8) + bounds: 984 192 8 8 + - image: solid-color(124, 24, 0, 255, 8, 8) + bounds: 992 192 8 8 + - image: solid-color(125, 24, 0, 255, 8, 8) + bounds: 1000 192 8 8 + - image: solid-color(126, 24, 0, 255, 8, 8) + bounds: 1008 192 8 8 + - image: solid-color(127, 24, 0, 255, 8, 8) + bounds: 1016 192 8 8 + - image: solid-color(0, 25, 0, 255, 8, 8) + bounds: 0 200 8 8 + - image: solid-color(1, 25, 0, 255, 8, 8) + bounds: 8 200 8 8 + - image: solid-color(2, 25, 0, 255, 8, 8) + bounds: 16 200 8 8 + - image: solid-color(3, 25, 0, 255, 8, 8) + bounds: 24 200 8 8 + - image: solid-color(4, 25, 0, 255, 8, 8) + bounds: 32 200 8 8 + - image: solid-color(5, 25, 0, 255, 8, 8) + bounds: 40 200 8 8 + - image: solid-color(6, 25, 0, 255, 8, 8) + bounds: 48 200 8 8 + - image: solid-color(7, 25, 0, 255, 8, 8) + bounds: 56 200 8 8 + - image: solid-color(8, 25, 0, 255, 8, 8) + bounds: 64 200 8 8 + - image: solid-color(9, 25, 0, 255, 8, 8) + bounds: 72 200 8 8 + - image: solid-color(10, 25, 0, 255, 8, 8) + bounds: 80 200 8 8 + - image: solid-color(11, 25, 0, 255, 8, 8) + bounds: 88 200 8 8 + - image: solid-color(12, 25, 0, 255, 8, 8) + bounds: 96 200 8 8 + - image: solid-color(13, 25, 0, 255, 8, 8) + bounds: 104 200 8 8 + - image: solid-color(14, 25, 0, 255, 8, 8) + bounds: 112 200 8 8 + - image: solid-color(15, 25, 0, 255, 8, 8) + bounds: 120 200 8 8 + - image: solid-color(16, 25, 0, 255, 8, 8) + bounds: 128 200 8 8 + - image: solid-color(17, 25, 0, 255, 8, 8) + bounds: 136 200 8 8 + - image: solid-color(18, 25, 0, 255, 8, 8) + bounds: 144 200 8 8 + - image: solid-color(19, 25, 0, 255, 8, 8) + bounds: 152 200 8 8 + - image: solid-color(20, 25, 0, 255, 8, 8) + bounds: 160 200 8 8 + - image: solid-color(21, 25, 0, 255, 8, 8) + bounds: 168 200 8 8 + - image: solid-color(22, 25, 0, 255, 8, 8) + bounds: 176 200 8 8 + - image: solid-color(23, 25, 0, 255, 8, 8) + bounds: 184 200 8 8 + - image: solid-color(24, 25, 0, 255, 8, 8) + bounds: 192 200 8 8 + - image: solid-color(25, 25, 0, 255, 8, 8) + bounds: 200 200 8 8 + - image: solid-color(26, 25, 0, 255, 8, 8) + bounds: 208 200 8 8 + - image: solid-color(27, 25, 0, 255, 8, 8) + bounds: 216 200 8 8 + - image: solid-color(28, 25, 0, 255, 8, 8) + bounds: 224 200 8 8 + - image: solid-color(29, 25, 0, 255, 8, 8) + bounds: 232 200 8 8 + - image: solid-color(30, 25, 0, 255, 8, 8) + bounds: 240 200 8 8 + - image: solid-color(31, 25, 0, 255, 8, 8) + bounds: 248 200 8 8 + - image: solid-color(32, 25, 0, 255, 8, 8) + bounds: 256 200 8 8 + - image: solid-color(33, 25, 0, 255, 8, 8) + bounds: 264 200 8 8 + - image: solid-color(34, 25, 0, 255, 8, 8) + bounds: 272 200 8 8 + - image: solid-color(35, 25, 0, 255, 8, 8) + bounds: 280 200 8 8 + - image: solid-color(36, 25, 0, 255, 8, 8) + bounds: 288 200 8 8 + - image: solid-color(37, 25, 0, 255, 8, 8) + bounds: 296 200 8 8 + - image: solid-color(38, 25, 0, 255, 8, 8) + bounds: 304 200 8 8 + - image: solid-color(39, 25, 0, 255, 8, 8) + bounds: 312 200 8 8 + - image: solid-color(40, 25, 0, 255, 8, 8) + bounds: 320 200 8 8 + - image: solid-color(41, 25, 0, 255, 8, 8) + bounds: 328 200 8 8 + - image: solid-color(42, 25, 0, 255, 8, 8) + bounds: 336 200 8 8 + - image: solid-color(43, 25, 0, 255, 8, 8) + bounds: 344 200 8 8 + - image: solid-color(44, 25, 0, 255, 8, 8) + bounds: 352 200 8 8 + - image: solid-color(45, 25, 0, 255, 8, 8) + bounds: 360 200 8 8 + - image: solid-color(46, 25, 0, 255, 8, 8) + bounds: 368 200 8 8 + - image: solid-color(47, 25, 0, 255, 8, 8) + bounds: 376 200 8 8 + - image: solid-color(48, 25, 0, 255, 8, 8) + bounds: 384 200 8 8 + - image: solid-color(49, 25, 0, 255, 8, 8) + bounds: 392 200 8 8 + - image: solid-color(50, 25, 0, 255, 8, 8) + bounds: 400 200 8 8 + - image: solid-color(51, 25, 0, 255, 8, 8) + bounds: 408 200 8 8 + - image: solid-color(52, 25, 0, 255, 8, 8) + bounds: 416 200 8 8 + - image: solid-color(53, 25, 0, 255, 8, 8) + bounds: 424 200 8 8 + - image: solid-color(54, 25, 0, 255, 8, 8) + bounds: 432 200 8 8 + - image: solid-color(55, 25, 0, 255, 8, 8) + bounds: 440 200 8 8 + - image: solid-color(56, 25, 0, 255, 8, 8) + bounds: 448 200 8 8 + - image: solid-color(57, 25, 0, 255, 8, 8) + bounds: 456 200 8 8 + - image: solid-color(58, 25, 0, 255, 8, 8) + bounds: 464 200 8 8 + - image: solid-color(59, 25, 0, 255, 8, 8) + bounds: 472 200 8 8 + - image: solid-color(60, 25, 0, 255, 8, 8) + bounds: 480 200 8 8 + - image: solid-color(61, 25, 0, 255, 8, 8) + bounds: 488 200 8 8 + - image: solid-color(62, 25, 0, 255, 8, 8) + bounds: 496 200 8 8 + - image: solid-color(63, 25, 0, 255, 8, 8) + bounds: 504 200 8 8 + - image: solid-color(64, 25, 0, 255, 8, 8) + bounds: 512 200 8 8 + - image: solid-color(65, 25, 0, 255, 8, 8) + bounds: 520 200 8 8 + - image: solid-color(66, 25, 0, 255, 8, 8) + bounds: 528 200 8 8 + - image: solid-color(67, 25, 0, 255, 8, 8) + bounds: 536 200 8 8 + - image: solid-color(68, 25, 0, 255, 8, 8) + bounds: 544 200 8 8 + - image: solid-color(69, 25, 0, 255, 8, 8) + bounds: 552 200 8 8 + - image: solid-color(70, 25, 0, 255, 8, 8) + bounds: 560 200 8 8 + - image: solid-color(71, 25, 0, 255, 8, 8) + bounds: 568 200 8 8 + - image: solid-color(72, 25, 0, 255, 8, 8) + bounds: 576 200 8 8 + - image: solid-color(73, 25, 0, 255, 8, 8) + bounds: 584 200 8 8 + - image: solid-color(74, 25, 0, 255, 8, 8) + bounds: 592 200 8 8 + - image: solid-color(75, 25, 0, 255, 8, 8) + bounds: 600 200 8 8 + - image: solid-color(76, 25, 0, 255, 8, 8) + bounds: 608 200 8 8 + - image: solid-color(77, 25, 0, 255, 8, 8) + bounds: 616 200 8 8 + - image: solid-color(78, 25, 0, 255, 8, 8) + bounds: 624 200 8 8 + - image: solid-color(79, 25, 0, 255, 8, 8) + bounds: 632 200 8 8 + - image: solid-color(80, 25, 0, 255, 8, 8) + bounds: 640 200 8 8 + - image: solid-color(81, 25, 0, 255, 8, 8) + bounds: 648 200 8 8 + - image: solid-color(82, 25, 0, 255, 8, 8) + bounds: 656 200 8 8 + - image: solid-color(83, 25, 0, 255, 8, 8) + bounds: 664 200 8 8 + - image: solid-color(84, 25, 0, 255, 8, 8) + bounds: 672 200 8 8 + - image: solid-color(85, 25, 0, 255, 8, 8) + bounds: 680 200 8 8 + - image: solid-color(86, 25, 0, 255, 8, 8) + bounds: 688 200 8 8 + - image: solid-color(87, 25, 0, 255, 8, 8) + bounds: 696 200 8 8 + - image: solid-color(88, 25, 0, 255, 8, 8) + bounds: 704 200 8 8 + - image: solid-color(89, 25, 0, 255, 8, 8) + bounds: 712 200 8 8 + - image: solid-color(90, 25, 0, 255, 8, 8) + bounds: 720 200 8 8 + - image: solid-color(91, 25, 0, 255, 8, 8) + bounds: 728 200 8 8 + - image: solid-color(92, 25, 0, 255, 8, 8) + bounds: 736 200 8 8 + - image: solid-color(93, 25, 0, 255, 8, 8) + bounds: 744 200 8 8 + - image: solid-color(94, 25, 0, 255, 8, 8) + bounds: 752 200 8 8 + - image: solid-color(95, 25, 0, 255, 8, 8) + bounds: 760 200 8 8 + - image: solid-color(96, 25, 0, 255, 8, 8) + bounds: 768 200 8 8 + - image: solid-color(97, 25, 0, 255, 8, 8) + bounds: 776 200 8 8 + - image: solid-color(98, 25, 0, 255, 8, 8) + bounds: 784 200 8 8 + - image: solid-color(99, 25, 0, 255, 8, 8) + bounds: 792 200 8 8 + - image: solid-color(100, 25, 0, 255, 8, 8) + bounds: 800 200 8 8 + - image: solid-color(101, 25, 0, 255, 8, 8) + bounds: 808 200 8 8 + - image: solid-color(102, 25, 0, 255, 8, 8) + bounds: 816 200 8 8 + - image: solid-color(103, 25, 0, 255, 8, 8) + bounds: 824 200 8 8 + - image: solid-color(104, 25, 0, 255, 8, 8) + bounds: 832 200 8 8 + - image: solid-color(105, 25, 0, 255, 8, 8) + bounds: 840 200 8 8 + - image: solid-color(106, 25, 0, 255, 8, 8) + bounds: 848 200 8 8 + - image: solid-color(107, 25, 0, 255, 8, 8) + bounds: 856 200 8 8 + - image: solid-color(108, 25, 0, 255, 8, 8) + bounds: 864 200 8 8 + - image: solid-color(109, 25, 0, 255, 8, 8) + bounds: 872 200 8 8 + - image: solid-color(110, 25, 0, 255, 8, 8) + bounds: 880 200 8 8 + - image: solid-color(111, 25, 0, 255, 8, 8) + bounds: 888 200 8 8 + - image: solid-color(112, 25, 0, 255, 8, 8) + bounds: 896 200 8 8 + - image: solid-color(113, 25, 0, 255, 8, 8) + bounds: 904 200 8 8 + - image: solid-color(114, 25, 0, 255, 8, 8) + bounds: 912 200 8 8 + - image: solid-color(115, 25, 0, 255, 8, 8) + bounds: 920 200 8 8 + - image: solid-color(116, 25, 0, 255, 8, 8) + bounds: 928 200 8 8 + - image: solid-color(117, 25, 0, 255, 8, 8) + bounds: 936 200 8 8 + - image: solid-color(118, 25, 0, 255, 8, 8) + bounds: 944 200 8 8 + - image: solid-color(119, 25, 0, 255, 8, 8) + bounds: 952 200 8 8 + - image: solid-color(120, 25, 0, 255, 8, 8) + bounds: 960 200 8 8 + - image: solid-color(121, 25, 0, 255, 8, 8) + bounds: 968 200 8 8 + - image: solid-color(122, 25, 0, 255, 8, 8) + bounds: 976 200 8 8 + - image: solid-color(123, 25, 0, 255, 8, 8) + bounds: 984 200 8 8 + - image: solid-color(124, 25, 0, 255, 8, 8) + bounds: 992 200 8 8 + - image: solid-color(125, 25, 0, 255, 8, 8) + bounds: 1000 200 8 8 + - image: solid-color(126, 25, 0, 255, 8, 8) + bounds: 1008 200 8 8 + - image: solid-color(127, 25, 0, 255, 8, 8) + bounds: 1016 200 8 8 + - image: solid-color(0, 26, 0, 255, 8, 8) + bounds: 0 208 8 8 + - image: solid-color(1, 26, 0, 255, 8, 8) + bounds: 8 208 8 8 + - image: solid-color(2, 26, 0, 255, 8, 8) + bounds: 16 208 8 8 + - image: solid-color(3, 26, 0, 255, 8, 8) + bounds: 24 208 8 8 + - image: solid-color(4, 26, 0, 255, 8, 8) + bounds: 32 208 8 8 + - image: solid-color(5, 26, 0, 255, 8, 8) + bounds: 40 208 8 8 + - image: solid-color(6, 26, 0, 255, 8, 8) + bounds: 48 208 8 8 + - image: solid-color(7, 26, 0, 255, 8, 8) + bounds: 56 208 8 8 + - image: solid-color(8, 26, 0, 255, 8, 8) + bounds: 64 208 8 8 + - image: solid-color(9, 26, 0, 255, 8, 8) + bounds: 72 208 8 8 + - image: solid-color(10, 26, 0, 255, 8, 8) + bounds: 80 208 8 8 + - image: solid-color(11, 26, 0, 255, 8, 8) + bounds: 88 208 8 8 + - image: solid-color(12, 26, 0, 255, 8, 8) + bounds: 96 208 8 8 + - image: solid-color(13, 26, 0, 255, 8, 8) + bounds: 104 208 8 8 + - image: solid-color(14, 26, 0, 255, 8, 8) + bounds: 112 208 8 8 + - image: solid-color(15, 26, 0, 255, 8, 8) + bounds: 120 208 8 8 + - image: solid-color(16, 26, 0, 255, 8, 8) + bounds: 128 208 8 8 + - image: solid-color(17, 26, 0, 255, 8, 8) + bounds: 136 208 8 8 + - image: solid-color(18, 26, 0, 255, 8, 8) + bounds: 144 208 8 8 + - image: solid-color(19, 26, 0, 255, 8, 8) + bounds: 152 208 8 8 + - image: solid-color(20, 26, 0, 255, 8, 8) + bounds: 160 208 8 8 + - image: solid-color(21, 26, 0, 255, 8, 8) + bounds: 168 208 8 8 + - image: solid-color(22, 26, 0, 255, 8, 8) + bounds: 176 208 8 8 + - image: solid-color(23, 26, 0, 255, 8, 8) + bounds: 184 208 8 8 + - image: solid-color(24, 26, 0, 255, 8, 8) + bounds: 192 208 8 8 + - image: solid-color(25, 26, 0, 255, 8, 8) + bounds: 200 208 8 8 + - image: solid-color(26, 26, 0, 255, 8, 8) + bounds: 208 208 8 8 + - image: solid-color(27, 26, 0, 255, 8, 8) + bounds: 216 208 8 8 + - image: solid-color(28, 26, 0, 255, 8, 8) + bounds: 224 208 8 8 + - image: solid-color(29, 26, 0, 255, 8, 8) + bounds: 232 208 8 8 + - image: solid-color(30, 26, 0, 255, 8, 8) + bounds: 240 208 8 8 + - image: solid-color(31, 26, 0, 255, 8, 8) + bounds: 248 208 8 8 + - image: solid-color(32, 26, 0, 255, 8, 8) + bounds: 256 208 8 8 + - image: solid-color(33, 26, 0, 255, 8, 8) + bounds: 264 208 8 8 + - image: solid-color(34, 26, 0, 255, 8, 8) + bounds: 272 208 8 8 + - image: solid-color(35, 26, 0, 255, 8, 8) + bounds: 280 208 8 8 + - image: solid-color(36, 26, 0, 255, 8, 8) + bounds: 288 208 8 8 + - image: solid-color(37, 26, 0, 255, 8, 8) + bounds: 296 208 8 8 + - image: solid-color(38, 26, 0, 255, 8, 8) + bounds: 304 208 8 8 + - image: solid-color(39, 26, 0, 255, 8, 8) + bounds: 312 208 8 8 + - image: solid-color(40, 26, 0, 255, 8, 8) + bounds: 320 208 8 8 + - image: solid-color(41, 26, 0, 255, 8, 8) + bounds: 328 208 8 8 + - image: solid-color(42, 26, 0, 255, 8, 8) + bounds: 336 208 8 8 + - image: solid-color(43, 26, 0, 255, 8, 8) + bounds: 344 208 8 8 + - image: solid-color(44, 26, 0, 255, 8, 8) + bounds: 352 208 8 8 + - image: solid-color(45, 26, 0, 255, 8, 8) + bounds: 360 208 8 8 + - image: solid-color(46, 26, 0, 255, 8, 8) + bounds: 368 208 8 8 + - image: solid-color(47, 26, 0, 255, 8, 8) + bounds: 376 208 8 8 + - image: solid-color(48, 26, 0, 255, 8, 8) + bounds: 384 208 8 8 + - image: solid-color(49, 26, 0, 255, 8, 8) + bounds: 392 208 8 8 + - image: solid-color(50, 26, 0, 255, 8, 8) + bounds: 400 208 8 8 + - image: solid-color(51, 26, 0, 255, 8, 8) + bounds: 408 208 8 8 + - image: solid-color(52, 26, 0, 255, 8, 8) + bounds: 416 208 8 8 + - image: solid-color(53, 26, 0, 255, 8, 8) + bounds: 424 208 8 8 + - image: solid-color(54, 26, 0, 255, 8, 8) + bounds: 432 208 8 8 + - image: solid-color(55, 26, 0, 255, 8, 8) + bounds: 440 208 8 8 + - image: solid-color(56, 26, 0, 255, 8, 8) + bounds: 448 208 8 8 + - image: solid-color(57, 26, 0, 255, 8, 8) + bounds: 456 208 8 8 + - image: solid-color(58, 26, 0, 255, 8, 8) + bounds: 464 208 8 8 + - image: solid-color(59, 26, 0, 255, 8, 8) + bounds: 472 208 8 8 + - image: solid-color(60, 26, 0, 255, 8, 8) + bounds: 480 208 8 8 + - image: solid-color(61, 26, 0, 255, 8, 8) + bounds: 488 208 8 8 + - image: solid-color(62, 26, 0, 255, 8, 8) + bounds: 496 208 8 8 + - image: solid-color(63, 26, 0, 255, 8, 8) + bounds: 504 208 8 8 + - image: solid-color(64, 26, 0, 255, 8, 8) + bounds: 512 208 8 8 + - image: solid-color(65, 26, 0, 255, 8, 8) + bounds: 520 208 8 8 + - image: solid-color(66, 26, 0, 255, 8, 8) + bounds: 528 208 8 8 + - image: solid-color(67, 26, 0, 255, 8, 8) + bounds: 536 208 8 8 + - image: solid-color(68, 26, 0, 255, 8, 8) + bounds: 544 208 8 8 + - image: solid-color(69, 26, 0, 255, 8, 8) + bounds: 552 208 8 8 + - image: solid-color(70, 26, 0, 255, 8, 8) + bounds: 560 208 8 8 + - image: solid-color(71, 26, 0, 255, 8, 8) + bounds: 568 208 8 8 + - image: solid-color(72, 26, 0, 255, 8, 8) + bounds: 576 208 8 8 + - image: solid-color(73, 26, 0, 255, 8, 8) + bounds: 584 208 8 8 + - image: solid-color(74, 26, 0, 255, 8, 8) + bounds: 592 208 8 8 + - image: solid-color(75, 26, 0, 255, 8, 8) + bounds: 600 208 8 8 + - image: solid-color(76, 26, 0, 255, 8, 8) + bounds: 608 208 8 8 + - image: solid-color(77, 26, 0, 255, 8, 8) + bounds: 616 208 8 8 + - image: solid-color(78, 26, 0, 255, 8, 8) + bounds: 624 208 8 8 + - image: solid-color(79, 26, 0, 255, 8, 8) + bounds: 632 208 8 8 + - image: solid-color(80, 26, 0, 255, 8, 8) + bounds: 640 208 8 8 + - image: solid-color(81, 26, 0, 255, 8, 8) + bounds: 648 208 8 8 + - image: solid-color(82, 26, 0, 255, 8, 8) + bounds: 656 208 8 8 + - image: solid-color(83, 26, 0, 255, 8, 8) + bounds: 664 208 8 8 + - image: solid-color(84, 26, 0, 255, 8, 8) + bounds: 672 208 8 8 + - image: solid-color(85, 26, 0, 255, 8, 8) + bounds: 680 208 8 8 + - image: solid-color(86, 26, 0, 255, 8, 8) + bounds: 688 208 8 8 + - image: solid-color(87, 26, 0, 255, 8, 8) + bounds: 696 208 8 8 + - image: solid-color(88, 26, 0, 255, 8, 8) + bounds: 704 208 8 8 + - image: solid-color(89, 26, 0, 255, 8, 8) + bounds: 712 208 8 8 + - image: solid-color(90, 26, 0, 255, 8, 8) + bounds: 720 208 8 8 + - image: solid-color(91, 26, 0, 255, 8, 8) + bounds: 728 208 8 8 + - image: solid-color(92, 26, 0, 255, 8, 8) + bounds: 736 208 8 8 + - image: solid-color(93, 26, 0, 255, 8, 8) + bounds: 744 208 8 8 + - image: solid-color(94, 26, 0, 255, 8, 8) + bounds: 752 208 8 8 + - image: solid-color(95, 26, 0, 255, 8, 8) + bounds: 760 208 8 8 + - image: solid-color(96, 26, 0, 255, 8, 8) + bounds: 768 208 8 8 + - image: solid-color(97, 26, 0, 255, 8, 8) + bounds: 776 208 8 8 + - image: solid-color(98, 26, 0, 255, 8, 8) + bounds: 784 208 8 8 + - image: solid-color(99, 26, 0, 255, 8, 8) + bounds: 792 208 8 8 + - image: solid-color(100, 26, 0, 255, 8, 8) + bounds: 800 208 8 8 + - image: solid-color(101, 26, 0, 255, 8, 8) + bounds: 808 208 8 8 + - image: solid-color(102, 26, 0, 255, 8, 8) + bounds: 816 208 8 8 + - image: solid-color(103, 26, 0, 255, 8, 8) + bounds: 824 208 8 8 + - image: solid-color(104, 26, 0, 255, 8, 8) + bounds: 832 208 8 8 + - image: solid-color(105, 26, 0, 255, 8, 8) + bounds: 840 208 8 8 + - image: solid-color(106, 26, 0, 255, 8, 8) + bounds: 848 208 8 8 + - image: solid-color(107, 26, 0, 255, 8, 8) + bounds: 856 208 8 8 + - image: solid-color(108, 26, 0, 255, 8, 8) + bounds: 864 208 8 8 + - image: solid-color(109, 26, 0, 255, 8, 8) + bounds: 872 208 8 8 + - image: solid-color(110, 26, 0, 255, 8, 8) + bounds: 880 208 8 8 + - image: solid-color(111, 26, 0, 255, 8, 8) + bounds: 888 208 8 8 + - image: solid-color(112, 26, 0, 255, 8, 8) + bounds: 896 208 8 8 + - image: solid-color(113, 26, 0, 255, 8, 8) + bounds: 904 208 8 8 + - image: solid-color(114, 26, 0, 255, 8, 8) + bounds: 912 208 8 8 + - image: solid-color(115, 26, 0, 255, 8, 8) + bounds: 920 208 8 8 + - image: solid-color(116, 26, 0, 255, 8, 8) + bounds: 928 208 8 8 + - image: solid-color(117, 26, 0, 255, 8, 8) + bounds: 936 208 8 8 + - image: solid-color(118, 26, 0, 255, 8, 8) + bounds: 944 208 8 8 + - image: solid-color(119, 26, 0, 255, 8, 8) + bounds: 952 208 8 8 + - image: solid-color(120, 26, 0, 255, 8, 8) + bounds: 960 208 8 8 + - image: solid-color(121, 26, 0, 255, 8, 8) + bounds: 968 208 8 8 + - image: solid-color(122, 26, 0, 255, 8, 8) + bounds: 976 208 8 8 + - image: solid-color(123, 26, 0, 255, 8, 8) + bounds: 984 208 8 8 + - image: solid-color(124, 26, 0, 255, 8, 8) + bounds: 992 208 8 8 + - image: solid-color(125, 26, 0, 255, 8, 8) + bounds: 1000 208 8 8 + - image: solid-color(126, 26, 0, 255, 8, 8) + bounds: 1008 208 8 8 + - image: solid-color(127, 26, 0, 255, 8, 8) + bounds: 1016 208 8 8 + - image: solid-color(0, 27, 0, 255, 8, 8) + bounds: 0 216 8 8 + - image: solid-color(1, 27, 0, 255, 8, 8) + bounds: 8 216 8 8 + - image: solid-color(2, 27, 0, 255, 8, 8) + bounds: 16 216 8 8 + - image: solid-color(3, 27, 0, 255, 8, 8) + bounds: 24 216 8 8 + - image: solid-color(4, 27, 0, 255, 8, 8) + bounds: 32 216 8 8 + - image: solid-color(5, 27, 0, 255, 8, 8) + bounds: 40 216 8 8 + - image: solid-color(6, 27, 0, 255, 8, 8) + bounds: 48 216 8 8 + - image: solid-color(7, 27, 0, 255, 8, 8) + bounds: 56 216 8 8 + - image: solid-color(8, 27, 0, 255, 8, 8) + bounds: 64 216 8 8 + - image: solid-color(9, 27, 0, 255, 8, 8) + bounds: 72 216 8 8 + - image: solid-color(10, 27, 0, 255, 8, 8) + bounds: 80 216 8 8 + - image: solid-color(11, 27, 0, 255, 8, 8) + bounds: 88 216 8 8 + - image: solid-color(12, 27, 0, 255, 8, 8) + bounds: 96 216 8 8 + - image: solid-color(13, 27, 0, 255, 8, 8) + bounds: 104 216 8 8 + - image: solid-color(14, 27, 0, 255, 8, 8) + bounds: 112 216 8 8 + - image: solid-color(15, 27, 0, 255, 8, 8) + bounds: 120 216 8 8 + - image: solid-color(16, 27, 0, 255, 8, 8) + bounds: 128 216 8 8 + - image: solid-color(17, 27, 0, 255, 8, 8) + bounds: 136 216 8 8 + - image: solid-color(18, 27, 0, 255, 8, 8) + bounds: 144 216 8 8 + - image: solid-color(19, 27, 0, 255, 8, 8) + bounds: 152 216 8 8 + - image: solid-color(20, 27, 0, 255, 8, 8) + bounds: 160 216 8 8 + - image: solid-color(21, 27, 0, 255, 8, 8) + bounds: 168 216 8 8 + - image: solid-color(22, 27, 0, 255, 8, 8) + bounds: 176 216 8 8 + - image: solid-color(23, 27, 0, 255, 8, 8) + bounds: 184 216 8 8 + - image: solid-color(24, 27, 0, 255, 8, 8) + bounds: 192 216 8 8 + - image: solid-color(25, 27, 0, 255, 8, 8) + bounds: 200 216 8 8 + - image: solid-color(26, 27, 0, 255, 8, 8) + bounds: 208 216 8 8 + - image: solid-color(27, 27, 0, 255, 8, 8) + bounds: 216 216 8 8 + - image: solid-color(28, 27, 0, 255, 8, 8) + bounds: 224 216 8 8 + - image: solid-color(29, 27, 0, 255, 8, 8) + bounds: 232 216 8 8 + - image: solid-color(30, 27, 0, 255, 8, 8) + bounds: 240 216 8 8 + - image: solid-color(31, 27, 0, 255, 8, 8) + bounds: 248 216 8 8 + - image: solid-color(32, 27, 0, 255, 8, 8) + bounds: 256 216 8 8 + - image: solid-color(33, 27, 0, 255, 8, 8) + bounds: 264 216 8 8 + - image: solid-color(34, 27, 0, 255, 8, 8) + bounds: 272 216 8 8 + - image: solid-color(35, 27, 0, 255, 8, 8) + bounds: 280 216 8 8 + - image: solid-color(36, 27, 0, 255, 8, 8) + bounds: 288 216 8 8 + - image: solid-color(37, 27, 0, 255, 8, 8) + bounds: 296 216 8 8 + - image: solid-color(38, 27, 0, 255, 8, 8) + bounds: 304 216 8 8 + - image: solid-color(39, 27, 0, 255, 8, 8) + bounds: 312 216 8 8 + - image: solid-color(40, 27, 0, 255, 8, 8) + bounds: 320 216 8 8 + - image: solid-color(41, 27, 0, 255, 8, 8) + bounds: 328 216 8 8 + - image: solid-color(42, 27, 0, 255, 8, 8) + bounds: 336 216 8 8 + - image: solid-color(43, 27, 0, 255, 8, 8) + bounds: 344 216 8 8 + - image: solid-color(44, 27, 0, 255, 8, 8) + bounds: 352 216 8 8 + - image: solid-color(45, 27, 0, 255, 8, 8) + bounds: 360 216 8 8 + - image: solid-color(46, 27, 0, 255, 8, 8) + bounds: 368 216 8 8 + - image: solid-color(47, 27, 0, 255, 8, 8) + bounds: 376 216 8 8 + - image: solid-color(48, 27, 0, 255, 8, 8) + bounds: 384 216 8 8 + - image: solid-color(49, 27, 0, 255, 8, 8) + bounds: 392 216 8 8 + - image: solid-color(50, 27, 0, 255, 8, 8) + bounds: 400 216 8 8 + - image: solid-color(51, 27, 0, 255, 8, 8) + bounds: 408 216 8 8 + - image: solid-color(52, 27, 0, 255, 8, 8) + bounds: 416 216 8 8 + - image: solid-color(53, 27, 0, 255, 8, 8) + bounds: 424 216 8 8 + - image: solid-color(54, 27, 0, 255, 8, 8) + bounds: 432 216 8 8 + - image: solid-color(55, 27, 0, 255, 8, 8) + bounds: 440 216 8 8 + - image: solid-color(56, 27, 0, 255, 8, 8) + bounds: 448 216 8 8 + - image: solid-color(57, 27, 0, 255, 8, 8) + bounds: 456 216 8 8 + - image: solid-color(58, 27, 0, 255, 8, 8) + bounds: 464 216 8 8 + - image: solid-color(59, 27, 0, 255, 8, 8) + bounds: 472 216 8 8 + - image: solid-color(60, 27, 0, 255, 8, 8) + bounds: 480 216 8 8 + - image: solid-color(61, 27, 0, 255, 8, 8) + bounds: 488 216 8 8 + - image: solid-color(62, 27, 0, 255, 8, 8) + bounds: 496 216 8 8 + - image: solid-color(63, 27, 0, 255, 8, 8) + bounds: 504 216 8 8 + - image: solid-color(64, 27, 0, 255, 8, 8) + bounds: 512 216 8 8 + - image: solid-color(65, 27, 0, 255, 8, 8) + bounds: 520 216 8 8 + - image: solid-color(66, 27, 0, 255, 8, 8) + bounds: 528 216 8 8 + - image: solid-color(67, 27, 0, 255, 8, 8) + bounds: 536 216 8 8 + - image: solid-color(68, 27, 0, 255, 8, 8) + bounds: 544 216 8 8 + - image: solid-color(69, 27, 0, 255, 8, 8) + bounds: 552 216 8 8 + - image: solid-color(70, 27, 0, 255, 8, 8) + bounds: 560 216 8 8 + - image: solid-color(71, 27, 0, 255, 8, 8) + bounds: 568 216 8 8 + - image: solid-color(72, 27, 0, 255, 8, 8) + bounds: 576 216 8 8 + - image: solid-color(73, 27, 0, 255, 8, 8) + bounds: 584 216 8 8 + - image: solid-color(74, 27, 0, 255, 8, 8) + bounds: 592 216 8 8 + - image: solid-color(75, 27, 0, 255, 8, 8) + bounds: 600 216 8 8 + - image: solid-color(76, 27, 0, 255, 8, 8) + bounds: 608 216 8 8 + - image: solid-color(77, 27, 0, 255, 8, 8) + bounds: 616 216 8 8 + - image: solid-color(78, 27, 0, 255, 8, 8) + bounds: 624 216 8 8 + - image: solid-color(79, 27, 0, 255, 8, 8) + bounds: 632 216 8 8 + - image: solid-color(80, 27, 0, 255, 8, 8) + bounds: 640 216 8 8 + - image: solid-color(81, 27, 0, 255, 8, 8) + bounds: 648 216 8 8 + - image: solid-color(82, 27, 0, 255, 8, 8) + bounds: 656 216 8 8 + - image: solid-color(83, 27, 0, 255, 8, 8) + bounds: 664 216 8 8 + - image: solid-color(84, 27, 0, 255, 8, 8) + bounds: 672 216 8 8 + - image: solid-color(85, 27, 0, 255, 8, 8) + bounds: 680 216 8 8 + - image: solid-color(86, 27, 0, 255, 8, 8) + bounds: 688 216 8 8 + - image: solid-color(87, 27, 0, 255, 8, 8) + bounds: 696 216 8 8 + - image: solid-color(88, 27, 0, 255, 8, 8) + bounds: 704 216 8 8 + - image: solid-color(89, 27, 0, 255, 8, 8) + bounds: 712 216 8 8 + - image: solid-color(90, 27, 0, 255, 8, 8) + bounds: 720 216 8 8 + - image: solid-color(91, 27, 0, 255, 8, 8) + bounds: 728 216 8 8 + - image: solid-color(92, 27, 0, 255, 8, 8) + bounds: 736 216 8 8 + - image: solid-color(93, 27, 0, 255, 8, 8) + bounds: 744 216 8 8 + - image: solid-color(94, 27, 0, 255, 8, 8) + bounds: 752 216 8 8 + - image: solid-color(95, 27, 0, 255, 8, 8) + bounds: 760 216 8 8 + - image: solid-color(96, 27, 0, 255, 8, 8) + bounds: 768 216 8 8 + - image: solid-color(97, 27, 0, 255, 8, 8) + bounds: 776 216 8 8 + - image: solid-color(98, 27, 0, 255, 8, 8) + bounds: 784 216 8 8 + - image: solid-color(99, 27, 0, 255, 8, 8) + bounds: 792 216 8 8 + - image: solid-color(100, 27, 0, 255, 8, 8) + bounds: 800 216 8 8 + - image: solid-color(101, 27, 0, 255, 8, 8) + bounds: 808 216 8 8 + - image: solid-color(102, 27, 0, 255, 8, 8) + bounds: 816 216 8 8 + - image: solid-color(103, 27, 0, 255, 8, 8) + bounds: 824 216 8 8 + - image: solid-color(104, 27, 0, 255, 8, 8) + bounds: 832 216 8 8 + - image: solid-color(105, 27, 0, 255, 8, 8) + bounds: 840 216 8 8 + - image: solid-color(106, 27, 0, 255, 8, 8) + bounds: 848 216 8 8 + - image: solid-color(107, 27, 0, 255, 8, 8) + bounds: 856 216 8 8 + - image: solid-color(108, 27, 0, 255, 8, 8) + bounds: 864 216 8 8 + - image: solid-color(109, 27, 0, 255, 8, 8) + bounds: 872 216 8 8 + - image: solid-color(110, 27, 0, 255, 8, 8) + bounds: 880 216 8 8 + - image: solid-color(111, 27, 0, 255, 8, 8) + bounds: 888 216 8 8 + - image: solid-color(112, 27, 0, 255, 8, 8) + bounds: 896 216 8 8 + - image: solid-color(113, 27, 0, 255, 8, 8) + bounds: 904 216 8 8 + - image: solid-color(114, 27, 0, 255, 8, 8) + bounds: 912 216 8 8 + - image: solid-color(115, 27, 0, 255, 8, 8) + bounds: 920 216 8 8 + - image: solid-color(116, 27, 0, 255, 8, 8) + bounds: 928 216 8 8 + - image: solid-color(117, 27, 0, 255, 8, 8) + bounds: 936 216 8 8 + - image: solid-color(118, 27, 0, 255, 8, 8) + bounds: 944 216 8 8 + - image: solid-color(119, 27, 0, 255, 8, 8) + bounds: 952 216 8 8 + - image: solid-color(120, 27, 0, 255, 8, 8) + bounds: 960 216 8 8 + - image: solid-color(121, 27, 0, 255, 8, 8) + bounds: 968 216 8 8 + - image: solid-color(122, 27, 0, 255, 8, 8) + bounds: 976 216 8 8 + - image: solid-color(123, 27, 0, 255, 8, 8) + bounds: 984 216 8 8 + - image: solid-color(124, 27, 0, 255, 8, 8) + bounds: 992 216 8 8 + - image: solid-color(125, 27, 0, 255, 8, 8) + bounds: 1000 216 8 8 + - image: solid-color(126, 27, 0, 255, 8, 8) + bounds: 1008 216 8 8 + - image: solid-color(127, 27, 0, 255, 8, 8) + bounds: 1016 216 8 8 + - image: solid-color(0, 28, 0, 255, 8, 8) + bounds: 0 224 8 8 + - image: solid-color(1, 28, 0, 255, 8, 8) + bounds: 8 224 8 8 + - image: solid-color(2, 28, 0, 255, 8, 8) + bounds: 16 224 8 8 + - image: solid-color(3, 28, 0, 255, 8, 8) + bounds: 24 224 8 8 + - image: solid-color(4, 28, 0, 255, 8, 8) + bounds: 32 224 8 8 + - image: solid-color(5, 28, 0, 255, 8, 8) + bounds: 40 224 8 8 + - image: solid-color(6, 28, 0, 255, 8, 8) + bounds: 48 224 8 8 + - image: solid-color(7, 28, 0, 255, 8, 8) + bounds: 56 224 8 8 + - image: solid-color(8, 28, 0, 255, 8, 8) + bounds: 64 224 8 8 + - image: solid-color(9, 28, 0, 255, 8, 8) + bounds: 72 224 8 8 + - image: solid-color(10, 28, 0, 255, 8, 8) + bounds: 80 224 8 8 + - image: solid-color(11, 28, 0, 255, 8, 8) + bounds: 88 224 8 8 + - image: solid-color(12, 28, 0, 255, 8, 8) + bounds: 96 224 8 8 + - image: solid-color(13, 28, 0, 255, 8, 8) + bounds: 104 224 8 8 + - image: solid-color(14, 28, 0, 255, 8, 8) + bounds: 112 224 8 8 + - image: solid-color(15, 28, 0, 255, 8, 8) + bounds: 120 224 8 8 + - image: solid-color(16, 28, 0, 255, 8, 8) + bounds: 128 224 8 8 + - image: solid-color(17, 28, 0, 255, 8, 8) + bounds: 136 224 8 8 + - image: solid-color(18, 28, 0, 255, 8, 8) + bounds: 144 224 8 8 + - image: solid-color(19, 28, 0, 255, 8, 8) + bounds: 152 224 8 8 + - image: solid-color(20, 28, 0, 255, 8, 8) + bounds: 160 224 8 8 + - image: solid-color(21, 28, 0, 255, 8, 8) + bounds: 168 224 8 8 + - image: solid-color(22, 28, 0, 255, 8, 8) + bounds: 176 224 8 8 + - image: solid-color(23, 28, 0, 255, 8, 8) + bounds: 184 224 8 8 + - image: solid-color(24, 28, 0, 255, 8, 8) + bounds: 192 224 8 8 + - image: solid-color(25, 28, 0, 255, 8, 8) + bounds: 200 224 8 8 + - image: solid-color(26, 28, 0, 255, 8, 8) + bounds: 208 224 8 8 + - image: solid-color(27, 28, 0, 255, 8, 8) + bounds: 216 224 8 8 + - image: solid-color(28, 28, 0, 255, 8, 8) + bounds: 224 224 8 8 + - image: solid-color(29, 28, 0, 255, 8, 8) + bounds: 232 224 8 8 + - image: solid-color(30, 28, 0, 255, 8, 8) + bounds: 240 224 8 8 + - image: solid-color(31, 28, 0, 255, 8, 8) + bounds: 248 224 8 8 + - image: solid-color(32, 28, 0, 255, 8, 8) + bounds: 256 224 8 8 + - image: solid-color(33, 28, 0, 255, 8, 8) + bounds: 264 224 8 8 + - image: solid-color(34, 28, 0, 255, 8, 8) + bounds: 272 224 8 8 + - image: solid-color(35, 28, 0, 255, 8, 8) + bounds: 280 224 8 8 + - image: solid-color(36, 28, 0, 255, 8, 8) + bounds: 288 224 8 8 + - image: solid-color(37, 28, 0, 255, 8, 8) + bounds: 296 224 8 8 + - image: solid-color(38, 28, 0, 255, 8, 8) + bounds: 304 224 8 8 + - image: solid-color(39, 28, 0, 255, 8, 8) + bounds: 312 224 8 8 + - image: solid-color(40, 28, 0, 255, 8, 8) + bounds: 320 224 8 8 + - image: solid-color(41, 28, 0, 255, 8, 8) + bounds: 328 224 8 8 + - image: solid-color(42, 28, 0, 255, 8, 8) + bounds: 336 224 8 8 + - image: solid-color(43, 28, 0, 255, 8, 8) + bounds: 344 224 8 8 + - image: solid-color(44, 28, 0, 255, 8, 8) + bounds: 352 224 8 8 + - image: solid-color(45, 28, 0, 255, 8, 8) + bounds: 360 224 8 8 + - image: solid-color(46, 28, 0, 255, 8, 8) + bounds: 368 224 8 8 + - image: solid-color(47, 28, 0, 255, 8, 8) + bounds: 376 224 8 8 + - image: solid-color(48, 28, 0, 255, 8, 8) + bounds: 384 224 8 8 + - image: solid-color(49, 28, 0, 255, 8, 8) + bounds: 392 224 8 8 + - image: solid-color(50, 28, 0, 255, 8, 8) + bounds: 400 224 8 8 + - image: solid-color(51, 28, 0, 255, 8, 8) + bounds: 408 224 8 8 + - image: solid-color(52, 28, 0, 255, 8, 8) + bounds: 416 224 8 8 + - image: solid-color(53, 28, 0, 255, 8, 8) + bounds: 424 224 8 8 + - image: solid-color(54, 28, 0, 255, 8, 8) + bounds: 432 224 8 8 + - image: solid-color(55, 28, 0, 255, 8, 8) + bounds: 440 224 8 8 + - image: solid-color(56, 28, 0, 255, 8, 8) + bounds: 448 224 8 8 + - image: solid-color(57, 28, 0, 255, 8, 8) + bounds: 456 224 8 8 + - image: solid-color(58, 28, 0, 255, 8, 8) + bounds: 464 224 8 8 + - image: solid-color(59, 28, 0, 255, 8, 8) + bounds: 472 224 8 8 + - image: solid-color(60, 28, 0, 255, 8, 8) + bounds: 480 224 8 8 + - image: solid-color(61, 28, 0, 255, 8, 8) + bounds: 488 224 8 8 + - image: solid-color(62, 28, 0, 255, 8, 8) + bounds: 496 224 8 8 + - image: solid-color(63, 28, 0, 255, 8, 8) + bounds: 504 224 8 8 + - image: solid-color(64, 28, 0, 255, 8, 8) + bounds: 512 224 8 8 + - image: solid-color(65, 28, 0, 255, 8, 8) + bounds: 520 224 8 8 + - image: solid-color(66, 28, 0, 255, 8, 8) + bounds: 528 224 8 8 + - image: solid-color(67, 28, 0, 255, 8, 8) + bounds: 536 224 8 8 + - image: solid-color(68, 28, 0, 255, 8, 8) + bounds: 544 224 8 8 + - image: solid-color(69, 28, 0, 255, 8, 8) + bounds: 552 224 8 8 + - image: solid-color(70, 28, 0, 255, 8, 8) + bounds: 560 224 8 8 + - image: solid-color(71, 28, 0, 255, 8, 8) + bounds: 568 224 8 8 + - image: solid-color(72, 28, 0, 255, 8, 8) + bounds: 576 224 8 8 + - image: solid-color(73, 28, 0, 255, 8, 8) + bounds: 584 224 8 8 + - image: solid-color(74, 28, 0, 255, 8, 8) + bounds: 592 224 8 8 + - image: solid-color(75, 28, 0, 255, 8, 8) + bounds: 600 224 8 8 + - image: solid-color(76, 28, 0, 255, 8, 8) + bounds: 608 224 8 8 + - image: solid-color(77, 28, 0, 255, 8, 8) + bounds: 616 224 8 8 + - image: solid-color(78, 28, 0, 255, 8, 8) + bounds: 624 224 8 8 + - image: solid-color(79, 28, 0, 255, 8, 8) + bounds: 632 224 8 8 + - image: solid-color(80, 28, 0, 255, 8, 8) + bounds: 640 224 8 8 + - image: solid-color(81, 28, 0, 255, 8, 8) + bounds: 648 224 8 8 + - image: solid-color(82, 28, 0, 255, 8, 8) + bounds: 656 224 8 8 + - image: solid-color(83, 28, 0, 255, 8, 8) + bounds: 664 224 8 8 + - image: solid-color(84, 28, 0, 255, 8, 8) + bounds: 672 224 8 8 + - image: solid-color(85, 28, 0, 255, 8, 8) + bounds: 680 224 8 8 + - image: solid-color(86, 28, 0, 255, 8, 8) + bounds: 688 224 8 8 + - image: solid-color(87, 28, 0, 255, 8, 8) + bounds: 696 224 8 8 + - image: solid-color(88, 28, 0, 255, 8, 8) + bounds: 704 224 8 8 + - image: solid-color(89, 28, 0, 255, 8, 8) + bounds: 712 224 8 8 + - image: solid-color(90, 28, 0, 255, 8, 8) + bounds: 720 224 8 8 + - image: solid-color(91, 28, 0, 255, 8, 8) + bounds: 728 224 8 8 + - image: solid-color(92, 28, 0, 255, 8, 8) + bounds: 736 224 8 8 + - image: solid-color(93, 28, 0, 255, 8, 8) + bounds: 744 224 8 8 + - image: solid-color(94, 28, 0, 255, 8, 8) + bounds: 752 224 8 8 + - image: solid-color(95, 28, 0, 255, 8, 8) + bounds: 760 224 8 8 + - image: solid-color(96, 28, 0, 255, 8, 8) + bounds: 768 224 8 8 + - image: solid-color(97, 28, 0, 255, 8, 8) + bounds: 776 224 8 8 + - image: solid-color(98, 28, 0, 255, 8, 8) + bounds: 784 224 8 8 + - image: solid-color(99, 28, 0, 255, 8, 8) + bounds: 792 224 8 8 + - image: solid-color(100, 28, 0, 255, 8, 8) + bounds: 800 224 8 8 + - image: solid-color(101, 28, 0, 255, 8, 8) + bounds: 808 224 8 8 + - image: solid-color(102, 28, 0, 255, 8, 8) + bounds: 816 224 8 8 + - image: solid-color(103, 28, 0, 255, 8, 8) + bounds: 824 224 8 8 + - image: solid-color(104, 28, 0, 255, 8, 8) + bounds: 832 224 8 8 + - image: solid-color(105, 28, 0, 255, 8, 8) + bounds: 840 224 8 8 + - image: solid-color(106, 28, 0, 255, 8, 8) + bounds: 848 224 8 8 + - image: solid-color(107, 28, 0, 255, 8, 8) + bounds: 856 224 8 8 + - image: solid-color(108, 28, 0, 255, 8, 8) + bounds: 864 224 8 8 + - image: solid-color(109, 28, 0, 255, 8, 8) + bounds: 872 224 8 8 + - image: solid-color(110, 28, 0, 255, 8, 8) + bounds: 880 224 8 8 + - image: solid-color(111, 28, 0, 255, 8, 8) + bounds: 888 224 8 8 + - image: solid-color(112, 28, 0, 255, 8, 8) + bounds: 896 224 8 8 + - image: solid-color(113, 28, 0, 255, 8, 8) + bounds: 904 224 8 8 + - image: solid-color(114, 28, 0, 255, 8, 8) + bounds: 912 224 8 8 + - image: solid-color(115, 28, 0, 255, 8, 8) + bounds: 920 224 8 8 + - image: solid-color(116, 28, 0, 255, 8, 8) + bounds: 928 224 8 8 + - image: solid-color(117, 28, 0, 255, 8, 8) + bounds: 936 224 8 8 + - image: solid-color(118, 28, 0, 255, 8, 8) + bounds: 944 224 8 8 + - image: solid-color(119, 28, 0, 255, 8, 8) + bounds: 952 224 8 8 + - image: solid-color(120, 28, 0, 255, 8, 8) + bounds: 960 224 8 8 + - image: solid-color(121, 28, 0, 255, 8, 8) + bounds: 968 224 8 8 + - image: solid-color(122, 28, 0, 255, 8, 8) + bounds: 976 224 8 8 + - image: solid-color(123, 28, 0, 255, 8, 8) + bounds: 984 224 8 8 + - image: solid-color(124, 28, 0, 255, 8, 8) + bounds: 992 224 8 8 + - image: solid-color(125, 28, 0, 255, 8, 8) + bounds: 1000 224 8 8 + - image: solid-color(126, 28, 0, 255, 8, 8) + bounds: 1008 224 8 8 + - image: solid-color(127, 28, 0, 255, 8, 8) + bounds: 1016 224 8 8 + - image: solid-color(0, 29, 0, 255, 8, 8) + bounds: 0 232 8 8 + - image: solid-color(1, 29, 0, 255, 8, 8) + bounds: 8 232 8 8 + - image: solid-color(2, 29, 0, 255, 8, 8) + bounds: 16 232 8 8 + - image: solid-color(3, 29, 0, 255, 8, 8) + bounds: 24 232 8 8 + - image: solid-color(4, 29, 0, 255, 8, 8) + bounds: 32 232 8 8 + - image: solid-color(5, 29, 0, 255, 8, 8) + bounds: 40 232 8 8 + - image: solid-color(6, 29, 0, 255, 8, 8) + bounds: 48 232 8 8 + - image: solid-color(7, 29, 0, 255, 8, 8) + bounds: 56 232 8 8 + - image: solid-color(8, 29, 0, 255, 8, 8) + bounds: 64 232 8 8 + - image: solid-color(9, 29, 0, 255, 8, 8) + bounds: 72 232 8 8 + - image: solid-color(10, 29, 0, 255, 8, 8) + bounds: 80 232 8 8 + - image: solid-color(11, 29, 0, 255, 8, 8) + bounds: 88 232 8 8 + - image: solid-color(12, 29, 0, 255, 8, 8) + bounds: 96 232 8 8 + - image: solid-color(13, 29, 0, 255, 8, 8) + bounds: 104 232 8 8 + - image: solid-color(14, 29, 0, 255, 8, 8) + bounds: 112 232 8 8 + - image: solid-color(15, 29, 0, 255, 8, 8) + bounds: 120 232 8 8 + - image: solid-color(16, 29, 0, 255, 8, 8) + bounds: 128 232 8 8 + - image: solid-color(17, 29, 0, 255, 8, 8) + bounds: 136 232 8 8 + - image: solid-color(18, 29, 0, 255, 8, 8) + bounds: 144 232 8 8 + - image: solid-color(19, 29, 0, 255, 8, 8) + bounds: 152 232 8 8 + - image: solid-color(20, 29, 0, 255, 8, 8) + bounds: 160 232 8 8 + - image: solid-color(21, 29, 0, 255, 8, 8) + bounds: 168 232 8 8 + - image: solid-color(22, 29, 0, 255, 8, 8) + bounds: 176 232 8 8 + - image: solid-color(23, 29, 0, 255, 8, 8) + bounds: 184 232 8 8 + - image: solid-color(24, 29, 0, 255, 8, 8) + bounds: 192 232 8 8 + - image: solid-color(25, 29, 0, 255, 8, 8) + bounds: 200 232 8 8 + - image: solid-color(26, 29, 0, 255, 8, 8) + bounds: 208 232 8 8 + - image: solid-color(27, 29, 0, 255, 8, 8) + bounds: 216 232 8 8 + - image: solid-color(28, 29, 0, 255, 8, 8) + bounds: 224 232 8 8 + - image: solid-color(29, 29, 0, 255, 8, 8) + bounds: 232 232 8 8 + - image: solid-color(30, 29, 0, 255, 8, 8) + bounds: 240 232 8 8 + - image: solid-color(31, 29, 0, 255, 8, 8) + bounds: 248 232 8 8 + - image: solid-color(32, 29, 0, 255, 8, 8) + bounds: 256 232 8 8 + - image: solid-color(33, 29, 0, 255, 8, 8) + bounds: 264 232 8 8 + - image: solid-color(34, 29, 0, 255, 8, 8) + bounds: 272 232 8 8 + - image: solid-color(35, 29, 0, 255, 8, 8) + bounds: 280 232 8 8 + - image: solid-color(36, 29, 0, 255, 8, 8) + bounds: 288 232 8 8 + - image: solid-color(37, 29, 0, 255, 8, 8) + bounds: 296 232 8 8 + - image: solid-color(38, 29, 0, 255, 8, 8) + bounds: 304 232 8 8 + - image: solid-color(39, 29, 0, 255, 8, 8) + bounds: 312 232 8 8 + - image: solid-color(40, 29, 0, 255, 8, 8) + bounds: 320 232 8 8 + - image: solid-color(41, 29, 0, 255, 8, 8) + bounds: 328 232 8 8 + - image: solid-color(42, 29, 0, 255, 8, 8) + bounds: 336 232 8 8 + - image: solid-color(43, 29, 0, 255, 8, 8) + bounds: 344 232 8 8 + - image: solid-color(44, 29, 0, 255, 8, 8) + bounds: 352 232 8 8 + - image: solid-color(45, 29, 0, 255, 8, 8) + bounds: 360 232 8 8 + - image: solid-color(46, 29, 0, 255, 8, 8) + bounds: 368 232 8 8 + - image: solid-color(47, 29, 0, 255, 8, 8) + bounds: 376 232 8 8 + - image: solid-color(48, 29, 0, 255, 8, 8) + bounds: 384 232 8 8 + - image: solid-color(49, 29, 0, 255, 8, 8) + bounds: 392 232 8 8 + - image: solid-color(50, 29, 0, 255, 8, 8) + bounds: 400 232 8 8 + - image: solid-color(51, 29, 0, 255, 8, 8) + bounds: 408 232 8 8 + - image: solid-color(52, 29, 0, 255, 8, 8) + bounds: 416 232 8 8 + - image: solid-color(53, 29, 0, 255, 8, 8) + bounds: 424 232 8 8 + - image: solid-color(54, 29, 0, 255, 8, 8) + bounds: 432 232 8 8 + - image: solid-color(55, 29, 0, 255, 8, 8) + bounds: 440 232 8 8 + - image: solid-color(56, 29, 0, 255, 8, 8) + bounds: 448 232 8 8 + - image: solid-color(57, 29, 0, 255, 8, 8) + bounds: 456 232 8 8 + - image: solid-color(58, 29, 0, 255, 8, 8) + bounds: 464 232 8 8 + - image: solid-color(59, 29, 0, 255, 8, 8) + bounds: 472 232 8 8 + - image: solid-color(60, 29, 0, 255, 8, 8) + bounds: 480 232 8 8 + - image: solid-color(61, 29, 0, 255, 8, 8) + bounds: 488 232 8 8 + - image: solid-color(62, 29, 0, 255, 8, 8) + bounds: 496 232 8 8 + - image: solid-color(63, 29, 0, 255, 8, 8) + bounds: 504 232 8 8 + - image: solid-color(64, 29, 0, 255, 8, 8) + bounds: 512 232 8 8 + - image: solid-color(65, 29, 0, 255, 8, 8) + bounds: 520 232 8 8 + - image: solid-color(66, 29, 0, 255, 8, 8) + bounds: 528 232 8 8 + - image: solid-color(67, 29, 0, 255, 8, 8) + bounds: 536 232 8 8 + - image: solid-color(68, 29, 0, 255, 8, 8) + bounds: 544 232 8 8 + - image: solid-color(69, 29, 0, 255, 8, 8) + bounds: 552 232 8 8 + - image: solid-color(70, 29, 0, 255, 8, 8) + bounds: 560 232 8 8 + - image: solid-color(71, 29, 0, 255, 8, 8) + bounds: 568 232 8 8 + - image: solid-color(72, 29, 0, 255, 8, 8) + bounds: 576 232 8 8 + - image: solid-color(73, 29, 0, 255, 8, 8) + bounds: 584 232 8 8 + - image: solid-color(74, 29, 0, 255, 8, 8) + bounds: 592 232 8 8 + - image: solid-color(75, 29, 0, 255, 8, 8) + bounds: 600 232 8 8 + - image: solid-color(76, 29, 0, 255, 8, 8) + bounds: 608 232 8 8 + - image: solid-color(77, 29, 0, 255, 8, 8) + bounds: 616 232 8 8 + - image: solid-color(78, 29, 0, 255, 8, 8) + bounds: 624 232 8 8 + - image: solid-color(79, 29, 0, 255, 8, 8) + bounds: 632 232 8 8 + - image: solid-color(80, 29, 0, 255, 8, 8) + bounds: 640 232 8 8 + - image: solid-color(81, 29, 0, 255, 8, 8) + bounds: 648 232 8 8 + - image: solid-color(82, 29, 0, 255, 8, 8) + bounds: 656 232 8 8 + - image: solid-color(83, 29, 0, 255, 8, 8) + bounds: 664 232 8 8 + - image: solid-color(84, 29, 0, 255, 8, 8) + bounds: 672 232 8 8 + - image: solid-color(85, 29, 0, 255, 8, 8) + bounds: 680 232 8 8 + - image: solid-color(86, 29, 0, 255, 8, 8) + bounds: 688 232 8 8 + - image: solid-color(87, 29, 0, 255, 8, 8) + bounds: 696 232 8 8 + - image: solid-color(88, 29, 0, 255, 8, 8) + bounds: 704 232 8 8 + - image: solid-color(89, 29, 0, 255, 8, 8) + bounds: 712 232 8 8 + - image: solid-color(90, 29, 0, 255, 8, 8) + bounds: 720 232 8 8 + - image: solid-color(91, 29, 0, 255, 8, 8) + bounds: 728 232 8 8 + - image: solid-color(92, 29, 0, 255, 8, 8) + bounds: 736 232 8 8 + - image: solid-color(93, 29, 0, 255, 8, 8) + bounds: 744 232 8 8 + - image: solid-color(94, 29, 0, 255, 8, 8) + bounds: 752 232 8 8 + - image: solid-color(95, 29, 0, 255, 8, 8) + bounds: 760 232 8 8 + - image: solid-color(96, 29, 0, 255, 8, 8) + bounds: 768 232 8 8 + - image: solid-color(97, 29, 0, 255, 8, 8) + bounds: 776 232 8 8 + - image: solid-color(98, 29, 0, 255, 8, 8) + bounds: 784 232 8 8 + - image: solid-color(99, 29, 0, 255, 8, 8) + bounds: 792 232 8 8 + - image: solid-color(100, 29, 0, 255, 8, 8) + bounds: 800 232 8 8 + - image: solid-color(101, 29, 0, 255, 8, 8) + bounds: 808 232 8 8 + - image: solid-color(102, 29, 0, 255, 8, 8) + bounds: 816 232 8 8 + - image: solid-color(103, 29, 0, 255, 8, 8) + bounds: 824 232 8 8 + - image: solid-color(104, 29, 0, 255, 8, 8) + bounds: 832 232 8 8 + - image: solid-color(105, 29, 0, 255, 8, 8) + bounds: 840 232 8 8 + - image: solid-color(106, 29, 0, 255, 8, 8) + bounds: 848 232 8 8 + - image: solid-color(107, 29, 0, 255, 8, 8) + bounds: 856 232 8 8 + - image: solid-color(108, 29, 0, 255, 8, 8) + bounds: 864 232 8 8 + - image: solid-color(109, 29, 0, 255, 8, 8) + bounds: 872 232 8 8 + - image: solid-color(110, 29, 0, 255, 8, 8) + bounds: 880 232 8 8 + - image: solid-color(111, 29, 0, 255, 8, 8) + bounds: 888 232 8 8 + - image: solid-color(112, 29, 0, 255, 8, 8) + bounds: 896 232 8 8 + - image: solid-color(113, 29, 0, 255, 8, 8) + bounds: 904 232 8 8 + - image: solid-color(114, 29, 0, 255, 8, 8) + bounds: 912 232 8 8 + - image: solid-color(115, 29, 0, 255, 8, 8) + bounds: 920 232 8 8 + - image: solid-color(116, 29, 0, 255, 8, 8) + bounds: 928 232 8 8 + - image: solid-color(117, 29, 0, 255, 8, 8) + bounds: 936 232 8 8 + - image: solid-color(118, 29, 0, 255, 8, 8) + bounds: 944 232 8 8 + - image: solid-color(119, 29, 0, 255, 8, 8) + bounds: 952 232 8 8 + - image: solid-color(120, 29, 0, 255, 8, 8) + bounds: 960 232 8 8 + - image: solid-color(121, 29, 0, 255, 8, 8) + bounds: 968 232 8 8 + - image: solid-color(122, 29, 0, 255, 8, 8) + bounds: 976 232 8 8 + - image: solid-color(123, 29, 0, 255, 8, 8) + bounds: 984 232 8 8 + - image: solid-color(124, 29, 0, 255, 8, 8) + bounds: 992 232 8 8 + - image: solid-color(125, 29, 0, 255, 8, 8) + bounds: 1000 232 8 8 + - image: solid-color(126, 29, 0, 255, 8, 8) + bounds: 1008 232 8 8 + - image: solid-color(127, 29, 0, 255, 8, 8) + bounds: 1016 232 8 8 + - image: solid-color(0, 30, 0, 255, 8, 8) + bounds: 0 240 8 8 + - image: solid-color(1, 30, 0, 255, 8, 8) + bounds: 8 240 8 8 + - image: solid-color(2, 30, 0, 255, 8, 8) + bounds: 16 240 8 8 + - image: solid-color(3, 30, 0, 255, 8, 8) + bounds: 24 240 8 8 + - image: solid-color(4, 30, 0, 255, 8, 8) + bounds: 32 240 8 8 + - image: solid-color(5, 30, 0, 255, 8, 8) + bounds: 40 240 8 8 + - image: solid-color(6, 30, 0, 255, 8, 8) + bounds: 48 240 8 8 + - image: solid-color(7, 30, 0, 255, 8, 8) + bounds: 56 240 8 8 + - image: solid-color(8, 30, 0, 255, 8, 8) + bounds: 64 240 8 8 + - image: solid-color(9, 30, 0, 255, 8, 8) + bounds: 72 240 8 8 + - image: solid-color(10, 30, 0, 255, 8, 8) + bounds: 80 240 8 8 + - image: solid-color(11, 30, 0, 255, 8, 8) + bounds: 88 240 8 8 + - image: solid-color(12, 30, 0, 255, 8, 8) + bounds: 96 240 8 8 + - image: solid-color(13, 30, 0, 255, 8, 8) + bounds: 104 240 8 8 + - image: solid-color(14, 30, 0, 255, 8, 8) + bounds: 112 240 8 8 + - image: solid-color(15, 30, 0, 255, 8, 8) + bounds: 120 240 8 8 + - image: solid-color(16, 30, 0, 255, 8, 8) + bounds: 128 240 8 8 + - image: solid-color(17, 30, 0, 255, 8, 8) + bounds: 136 240 8 8 + - image: solid-color(18, 30, 0, 255, 8, 8) + bounds: 144 240 8 8 + - image: solid-color(19, 30, 0, 255, 8, 8) + bounds: 152 240 8 8 + - image: solid-color(20, 30, 0, 255, 8, 8) + bounds: 160 240 8 8 + - image: solid-color(21, 30, 0, 255, 8, 8) + bounds: 168 240 8 8 + - image: solid-color(22, 30, 0, 255, 8, 8) + bounds: 176 240 8 8 + - image: solid-color(23, 30, 0, 255, 8, 8) + bounds: 184 240 8 8 + - image: solid-color(24, 30, 0, 255, 8, 8) + bounds: 192 240 8 8 + - image: solid-color(25, 30, 0, 255, 8, 8) + bounds: 200 240 8 8 + - image: solid-color(26, 30, 0, 255, 8, 8) + bounds: 208 240 8 8 + - image: solid-color(27, 30, 0, 255, 8, 8) + bounds: 216 240 8 8 + - image: solid-color(28, 30, 0, 255, 8, 8) + bounds: 224 240 8 8 + - image: solid-color(29, 30, 0, 255, 8, 8) + bounds: 232 240 8 8 + - image: solid-color(30, 30, 0, 255, 8, 8) + bounds: 240 240 8 8 + - image: solid-color(31, 30, 0, 255, 8, 8) + bounds: 248 240 8 8 + - image: solid-color(32, 30, 0, 255, 8, 8) + bounds: 256 240 8 8 + - image: solid-color(33, 30, 0, 255, 8, 8) + bounds: 264 240 8 8 + - image: solid-color(34, 30, 0, 255, 8, 8) + bounds: 272 240 8 8 + - image: solid-color(35, 30, 0, 255, 8, 8) + bounds: 280 240 8 8 + - image: solid-color(36, 30, 0, 255, 8, 8) + bounds: 288 240 8 8 + - image: solid-color(37, 30, 0, 255, 8, 8) + bounds: 296 240 8 8 + - image: solid-color(38, 30, 0, 255, 8, 8) + bounds: 304 240 8 8 + - image: solid-color(39, 30, 0, 255, 8, 8) + bounds: 312 240 8 8 + - image: solid-color(40, 30, 0, 255, 8, 8) + bounds: 320 240 8 8 + - image: solid-color(41, 30, 0, 255, 8, 8) + bounds: 328 240 8 8 + - image: solid-color(42, 30, 0, 255, 8, 8) + bounds: 336 240 8 8 + - image: solid-color(43, 30, 0, 255, 8, 8) + bounds: 344 240 8 8 + - image: solid-color(44, 30, 0, 255, 8, 8) + bounds: 352 240 8 8 + - image: solid-color(45, 30, 0, 255, 8, 8) + bounds: 360 240 8 8 + - image: solid-color(46, 30, 0, 255, 8, 8) + bounds: 368 240 8 8 + - image: solid-color(47, 30, 0, 255, 8, 8) + bounds: 376 240 8 8 + - image: solid-color(48, 30, 0, 255, 8, 8) + bounds: 384 240 8 8 + - image: solid-color(49, 30, 0, 255, 8, 8) + bounds: 392 240 8 8 + - image: solid-color(50, 30, 0, 255, 8, 8) + bounds: 400 240 8 8 + - image: solid-color(51, 30, 0, 255, 8, 8) + bounds: 408 240 8 8 + - image: solid-color(52, 30, 0, 255, 8, 8) + bounds: 416 240 8 8 + - image: solid-color(53, 30, 0, 255, 8, 8) + bounds: 424 240 8 8 + - image: solid-color(54, 30, 0, 255, 8, 8) + bounds: 432 240 8 8 + - image: solid-color(55, 30, 0, 255, 8, 8) + bounds: 440 240 8 8 + - image: solid-color(56, 30, 0, 255, 8, 8) + bounds: 448 240 8 8 + - image: solid-color(57, 30, 0, 255, 8, 8) + bounds: 456 240 8 8 + - image: solid-color(58, 30, 0, 255, 8, 8) + bounds: 464 240 8 8 + - image: solid-color(59, 30, 0, 255, 8, 8) + bounds: 472 240 8 8 + - image: solid-color(60, 30, 0, 255, 8, 8) + bounds: 480 240 8 8 + - image: solid-color(61, 30, 0, 255, 8, 8) + bounds: 488 240 8 8 + - image: solid-color(62, 30, 0, 255, 8, 8) + bounds: 496 240 8 8 + - image: solid-color(63, 30, 0, 255, 8, 8) + bounds: 504 240 8 8 + - image: solid-color(64, 30, 0, 255, 8, 8) + bounds: 512 240 8 8 + - image: solid-color(65, 30, 0, 255, 8, 8) + bounds: 520 240 8 8 + - image: solid-color(66, 30, 0, 255, 8, 8) + bounds: 528 240 8 8 + - image: solid-color(67, 30, 0, 255, 8, 8) + bounds: 536 240 8 8 + - image: solid-color(68, 30, 0, 255, 8, 8) + bounds: 544 240 8 8 + - image: solid-color(69, 30, 0, 255, 8, 8) + bounds: 552 240 8 8 + - image: solid-color(70, 30, 0, 255, 8, 8) + bounds: 560 240 8 8 + - image: solid-color(71, 30, 0, 255, 8, 8) + bounds: 568 240 8 8 + - image: solid-color(72, 30, 0, 255, 8, 8) + bounds: 576 240 8 8 + - image: solid-color(73, 30, 0, 255, 8, 8) + bounds: 584 240 8 8 + - image: solid-color(74, 30, 0, 255, 8, 8) + bounds: 592 240 8 8 + - image: solid-color(75, 30, 0, 255, 8, 8) + bounds: 600 240 8 8 + - image: solid-color(76, 30, 0, 255, 8, 8) + bounds: 608 240 8 8 + - image: solid-color(77, 30, 0, 255, 8, 8) + bounds: 616 240 8 8 + - image: solid-color(78, 30, 0, 255, 8, 8) + bounds: 624 240 8 8 + - image: solid-color(79, 30, 0, 255, 8, 8) + bounds: 632 240 8 8 + - image: solid-color(80, 30, 0, 255, 8, 8) + bounds: 640 240 8 8 + - image: solid-color(81, 30, 0, 255, 8, 8) + bounds: 648 240 8 8 + - image: solid-color(82, 30, 0, 255, 8, 8) + bounds: 656 240 8 8 + - image: solid-color(83, 30, 0, 255, 8, 8) + bounds: 664 240 8 8 + - image: solid-color(84, 30, 0, 255, 8, 8) + bounds: 672 240 8 8 + - image: solid-color(85, 30, 0, 255, 8, 8) + bounds: 680 240 8 8 + - image: solid-color(86, 30, 0, 255, 8, 8) + bounds: 688 240 8 8 + - image: solid-color(87, 30, 0, 255, 8, 8) + bounds: 696 240 8 8 + - image: solid-color(88, 30, 0, 255, 8, 8) + bounds: 704 240 8 8 + - image: solid-color(89, 30, 0, 255, 8, 8) + bounds: 712 240 8 8 + - image: solid-color(90, 30, 0, 255, 8, 8) + bounds: 720 240 8 8 + - image: solid-color(91, 30, 0, 255, 8, 8) + bounds: 728 240 8 8 + - image: solid-color(92, 30, 0, 255, 8, 8) + bounds: 736 240 8 8 + - image: solid-color(93, 30, 0, 255, 8, 8) + bounds: 744 240 8 8 + - image: solid-color(94, 30, 0, 255, 8, 8) + bounds: 752 240 8 8 + - image: solid-color(95, 30, 0, 255, 8, 8) + bounds: 760 240 8 8 + - image: solid-color(96, 30, 0, 255, 8, 8) + bounds: 768 240 8 8 + - image: solid-color(97, 30, 0, 255, 8, 8) + bounds: 776 240 8 8 + - image: solid-color(98, 30, 0, 255, 8, 8) + bounds: 784 240 8 8 + - image: solid-color(99, 30, 0, 255, 8, 8) + bounds: 792 240 8 8 + - image: solid-color(100, 30, 0, 255, 8, 8) + bounds: 800 240 8 8 + - image: solid-color(101, 30, 0, 255, 8, 8) + bounds: 808 240 8 8 + - image: solid-color(102, 30, 0, 255, 8, 8) + bounds: 816 240 8 8 + - image: solid-color(103, 30, 0, 255, 8, 8) + bounds: 824 240 8 8 + - image: solid-color(104, 30, 0, 255, 8, 8) + bounds: 832 240 8 8 + - image: solid-color(105, 30, 0, 255, 8, 8) + bounds: 840 240 8 8 + - image: solid-color(106, 30, 0, 255, 8, 8) + bounds: 848 240 8 8 + - image: solid-color(107, 30, 0, 255, 8, 8) + bounds: 856 240 8 8 + - image: solid-color(108, 30, 0, 255, 8, 8) + bounds: 864 240 8 8 + - image: solid-color(109, 30, 0, 255, 8, 8) + bounds: 872 240 8 8 + - image: solid-color(110, 30, 0, 255, 8, 8) + bounds: 880 240 8 8 + - image: solid-color(111, 30, 0, 255, 8, 8) + bounds: 888 240 8 8 + - image: solid-color(112, 30, 0, 255, 8, 8) + bounds: 896 240 8 8 + - image: solid-color(113, 30, 0, 255, 8, 8) + bounds: 904 240 8 8 + - image: solid-color(114, 30, 0, 255, 8, 8) + bounds: 912 240 8 8 + - image: solid-color(115, 30, 0, 255, 8, 8) + bounds: 920 240 8 8 + - image: solid-color(116, 30, 0, 255, 8, 8) + bounds: 928 240 8 8 + - image: solid-color(117, 30, 0, 255, 8, 8) + bounds: 936 240 8 8 + - image: solid-color(118, 30, 0, 255, 8, 8) + bounds: 944 240 8 8 + - image: solid-color(119, 30, 0, 255, 8, 8) + bounds: 952 240 8 8 + - image: solid-color(120, 30, 0, 255, 8, 8) + bounds: 960 240 8 8 + - image: solid-color(121, 30, 0, 255, 8, 8) + bounds: 968 240 8 8 + - image: solid-color(122, 30, 0, 255, 8, 8) + bounds: 976 240 8 8 + - image: solid-color(123, 30, 0, 255, 8, 8) + bounds: 984 240 8 8 + - image: solid-color(124, 30, 0, 255, 8, 8) + bounds: 992 240 8 8 + - image: solid-color(125, 30, 0, 255, 8, 8) + bounds: 1000 240 8 8 + - image: solid-color(126, 30, 0, 255, 8, 8) + bounds: 1008 240 8 8 + - image: solid-color(127, 30, 0, 255, 8, 8) + bounds: 1016 240 8 8 + - image: solid-color(0, 31, 0, 255, 8, 8) + bounds: 0 248 8 8 + - image: solid-color(1, 31, 0, 255, 8, 8) + bounds: 8 248 8 8 + - image: solid-color(2, 31, 0, 255, 8, 8) + bounds: 16 248 8 8 + - image: solid-color(3, 31, 0, 255, 8, 8) + bounds: 24 248 8 8 + - image: solid-color(4, 31, 0, 255, 8, 8) + bounds: 32 248 8 8 + - image: solid-color(5, 31, 0, 255, 8, 8) + bounds: 40 248 8 8 + - image: solid-color(6, 31, 0, 255, 8, 8) + bounds: 48 248 8 8 + - image: solid-color(7, 31, 0, 255, 8, 8) + bounds: 56 248 8 8 + - image: solid-color(8, 31, 0, 255, 8, 8) + bounds: 64 248 8 8 + - image: solid-color(9, 31, 0, 255, 8, 8) + bounds: 72 248 8 8 + - image: solid-color(10, 31, 0, 255, 8, 8) + bounds: 80 248 8 8 + - image: solid-color(11, 31, 0, 255, 8, 8) + bounds: 88 248 8 8 + - image: solid-color(12, 31, 0, 255, 8, 8) + bounds: 96 248 8 8 + - image: solid-color(13, 31, 0, 255, 8, 8) + bounds: 104 248 8 8 + - image: solid-color(14, 31, 0, 255, 8, 8) + bounds: 112 248 8 8 + - image: solid-color(15, 31, 0, 255, 8, 8) + bounds: 120 248 8 8 + - image: solid-color(16, 31, 0, 255, 8, 8) + bounds: 128 248 8 8 + - image: solid-color(17, 31, 0, 255, 8, 8) + bounds: 136 248 8 8 + - image: solid-color(18, 31, 0, 255, 8, 8) + bounds: 144 248 8 8 + - image: solid-color(19, 31, 0, 255, 8, 8) + bounds: 152 248 8 8 + - image: solid-color(20, 31, 0, 255, 8, 8) + bounds: 160 248 8 8 + - image: solid-color(21, 31, 0, 255, 8, 8) + bounds: 168 248 8 8 + - image: solid-color(22, 31, 0, 255, 8, 8) + bounds: 176 248 8 8 + - image: solid-color(23, 31, 0, 255, 8, 8) + bounds: 184 248 8 8 + - image: solid-color(24, 31, 0, 255, 8, 8) + bounds: 192 248 8 8 + - image: solid-color(25, 31, 0, 255, 8, 8) + bounds: 200 248 8 8 + - image: solid-color(26, 31, 0, 255, 8, 8) + bounds: 208 248 8 8 + - image: solid-color(27, 31, 0, 255, 8, 8) + bounds: 216 248 8 8 + - image: solid-color(28, 31, 0, 255, 8, 8) + bounds: 224 248 8 8 + - image: solid-color(29, 31, 0, 255, 8, 8) + bounds: 232 248 8 8 + - image: solid-color(30, 31, 0, 255, 8, 8) + bounds: 240 248 8 8 + - image: solid-color(31, 31, 0, 255, 8, 8) + bounds: 248 248 8 8 + - image: solid-color(32, 31, 0, 255, 8, 8) + bounds: 256 248 8 8 + - image: solid-color(33, 31, 0, 255, 8, 8) + bounds: 264 248 8 8 + - image: solid-color(34, 31, 0, 255, 8, 8) + bounds: 272 248 8 8 + - image: solid-color(35, 31, 0, 255, 8, 8) + bounds: 280 248 8 8 + - image: solid-color(36, 31, 0, 255, 8, 8) + bounds: 288 248 8 8 + - image: solid-color(37, 31, 0, 255, 8, 8) + bounds: 296 248 8 8 + - image: solid-color(38, 31, 0, 255, 8, 8) + bounds: 304 248 8 8 + - image: solid-color(39, 31, 0, 255, 8, 8) + bounds: 312 248 8 8 + - image: solid-color(40, 31, 0, 255, 8, 8) + bounds: 320 248 8 8 + - image: solid-color(41, 31, 0, 255, 8, 8) + bounds: 328 248 8 8 + - image: solid-color(42, 31, 0, 255, 8, 8) + bounds: 336 248 8 8 + - image: solid-color(43, 31, 0, 255, 8, 8) + bounds: 344 248 8 8 + - image: solid-color(44, 31, 0, 255, 8, 8) + bounds: 352 248 8 8 + - image: solid-color(45, 31, 0, 255, 8, 8) + bounds: 360 248 8 8 + - image: solid-color(46, 31, 0, 255, 8, 8) + bounds: 368 248 8 8 + - image: solid-color(47, 31, 0, 255, 8, 8) + bounds: 376 248 8 8 + - image: solid-color(48, 31, 0, 255, 8, 8) + bounds: 384 248 8 8 + - image: solid-color(49, 31, 0, 255, 8, 8) + bounds: 392 248 8 8 + - image: solid-color(50, 31, 0, 255, 8, 8) + bounds: 400 248 8 8 + - image: solid-color(51, 31, 0, 255, 8, 8) + bounds: 408 248 8 8 + - image: solid-color(52, 31, 0, 255, 8, 8) + bounds: 416 248 8 8 + - image: solid-color(53, 31, 0, 255, 8, 8) + bounds: 424 248 8 8 + - image: solid-color(54, 31, 0, 255, 8, 8) + bounds: 432 248 8 8 + - image: solid-color(55, 31, 0, 255, 8, 8) + bounds: 440 248 8 8 + - image: solid-color(56, 31, 0, 255, 8, 8) + bounds: 448 248 8 8 + - image: solid-color(57, 31, 0, 255, 8, 8) + bounds: 456 248 8 8 + - image: solid-color(58, 31, 0, 255, 8, 8) + bounds: 464 248 8 8 + - image: solid-color(59, 31, 0, 255, 8, 8) + bounds: 472 248 8 8 + - image: solid-color(60, 31, 0, 255, 8, 8) + bounds: 480 248 8 8 + - image: solid-color(61, 31, 0, 255, 8, 8) + bounds: 488 248 8 8 + - image: solid-color(62, 31, 0, 255, 8, 8) + bounds: 496 248 8 8 + - image: solid-color(63, 31, 0, 255, 8, 8) + bounds: 504 248 8 8 + - image: solid-color(64, 31, 0, 255, 8, 8) + bounds: 512 248 8 8 + - image: solid-color(65, 31, 0, 255, 8, 8) + bounds: 520 248 8 8 + - image: solid-color(66, 31, 0, 255, 8, 8) + bounds: 528 248 8 8 + - image: solid-color(67, 31, 0, 255, 8, 8) + bounds: 536 248 8 8 + - image: solid-color(68, 31, 0, 255, 8, 8) + bounds: 544 248 8 8 + - image: solid-color(69, 31, 0, 255, 8, 8) + bounds: 552 248 8 8 + - image: solid-color(70, 31, 0, 255, 8, 8) + bounds: 560 248 8 8 + - image: solid-color(71, 31, 0, 255, 8, 8) + bounds: 568 248 8 8 + - image: solid-color(72, 31, 0, 255, 8, 8) + bounds: 576 248 8 8 + - image: solid-color(73, 31, 0, 255, 8, 8) + bounds: 584 248 8 8 + - image: solid-color(74, 31, 0, 255, 8, 8) + bounds: 592 248 8 8 + - image: solid-color(75, 31, 0, 255, 8, 8) + bounds: 600 248 8 8 + - image: solid-color(76, 31, 0, 255, 8, 8) + bounds: 608 248 8 8 + - image: solid-color(77, 31, 0, 255, 8, 8) + bounds: 616 248 8 8 + - image: solid-color(78, 31, 0, 255, 8, 8) + bounds: 624 248 8 8 + - image: solid-color(79, 31, 0, 255, 8, 8) + bounds: 632 248 8 8 + - image: solid-color(80, 31, 0, 255, 8, 8) + bounds: 640 248 8 8 + - image: solid-color(81, 31, 0, 255, 8, 8) + bounds: 648 248 8 8 + - image: solid-color(82, 31, 0, 255, 8, 8) + bounds: 656 248 8 8 + - image: solid-color(83, 31, 0, 255, 8, 8) + bounds: 664 248 8 8 + - image: solid-color(84, 31, 0, 255, 8, 8) + bounds: 672 248 8 8 + - image: solid-color(85, 31, 0, 255, 8, 8) + bounds: 680 248 8 8 + - image: solid-color(86, 31, 0, 255, 8, 8) + bounds: 688 248 8 8 + - image: solid-color(87, 31, 0, 255, 8, 8) + bounds: 696 248 8 8 + - image: solid-color(88, 31, 0, 255, 8, 8) + bounds: 704 248 8 8 + - image: solid-color(89, 31, 0, 255, 8, 8) + bounds: 712 248 8 8 + - image: solid-color(90, 31, 0, 255, 8, 8) + bounds: 720 248 8 8 + - image: solid-color(91, 31, 0, 255, 8, 8) + bounds: 728 248 8 8 + - image: solid-color(92, 31, 0, 255, 8, 8) + bounds: 736 248 8 8 + - image: solid-color(93, 31, 0, 255, 8, 8) + bounds: 744 248 8 8 + - image: solid-color(94, 31, 0, 255, 8, 8) + bounds: 752 248 8 8 + - image: solid-color(95, 31, 0, 255, 8, 8) + bounds: 760 248 8 8 + - image: solid-color(96, 31, 0, 255, 8, 8) + bounds: 768 248 8 8 + - image: solid-color(97, 31, 0, 255, 8, 8) + bounds: 776 248 8 8 + - image: solid-color(98, 31, 0, 255, 8, 8) + bounds: 784 248 8 8 + - image: solid-color(99, 31, 0, 255, 8, 8) + bounds: 792 248 8 8 + - image: solid-color(100, 31, 0, 255, 8, 8) + bounds: 800 248 8 8 + - image: solid-color(101, 31, 0, 255, 8, 8) + bounds: 808 248 8 8 + - image: solid-color(102, 31, 0, 255, 8, 8) + bounds: 816 248 8 8 + - image: solid-color(103, 31, 0, 255, 8, 8) + bounds: 824 248 8 8 + - image: solid-color(104, 31, 0, 255, 8, 8) + bounds: 832 248 8 8 + - image: solid-color(105, 31, 0, 255, 8, 8) + bounds: 840 248 8 8 + - image: solid-color(106, 31, 0, 255, 8, 8) + bounds: 848 248 8 8 + - image: solid-color(107, 31, 0, 255, 8, 8) + bounds: 856 248 8 8 + - image: solid-color(108, 31, 0, 255, 8, 8) + bounds: 864 248 8 8 + - image: solid-color(109, 31, 0, 255, 8, 8) + bounds: 872 248 8 8 + - image: solid-color(110, 31, 0, 255, 8, 8) + bounds: 880 248 8 8 + - image: solid-color(111, 31, 0, 255, 8, 8) + bounds: 888 248 8 8 + - image: solid-color(112, 31, 0, 255, 8, 8) + bounds: 896 248 8 8 + - image: solid-color(113, 31, 0, 255, 8, 8) + bounds: 904 248 8 8 + - image: solid-color(114, 31, 0, 255, 8, 8) + bounds: 912 248 8 8 + - image: solid-color(115, 31, 0, 255, 8, 8) + bounds: 920 248 8 8 + - image: solid-color(116, 31, 0, 255, 8, 8) + bounds: 928 248 8 8 + - image: solid-color(117, 31, 0, 255, 8, 8) + bounds: 936 248 8 8 + - image: solid-color(118, 31, 0, 255, 8, 8) + bounds: 944 248 8 8 + - image: solid-color(119, 31, 0, 255, 8, 8) + bounds: 952 248 8 8 + - image: solid-color(120, 31, 0, 255, 8, 8) + bounds: 960 248 8 8 + - image: solid-color(121, 31, 0, 255, 8, 8) + bounds: 968 248 8 8 + - image: solid-color(122, 31, 0, 255, 8, 8) + bounds: 976 248 8 8 + - image: solid-color(123, 31, 0, 255, 8, 8) + bounds: 984 248 8 8 + - image: solid-color(124, 31, 0, 255, 8, 8) + bounds: 992 248 8 8 + - image: solid-color(125, 31, 0, 255, 8, 8) + bounds: 1000 248 8 8 + - image: solid-color(126, 31, 0, 255, 8, 8) + bounds: 1008 248 8 8 + - image: solid-color(127, 31, 0, 255, 8, 8) + bounds: 1016 248 8 8 + - image: solid-color(0, 32, 0, 255, 8, 8) + bounds: 0 256 8 8 + - image: solid-color(1, 32, 0, 255, 8, 8) + bounds: 8 256 8 8 + - image: solid-color(2, 32, 0, 255, 8, 8) + bounds: 16 256 8 8 + - image: solid-color(3, 32, 0, 255, 8, 8) + bounds: 24 256 8 8 + - image: solid-color(4, 32, 0, 255, 8, 8) + bounds: 32 256 8 8 + - image: solid-color(5, 32, 0, 255, 8, 8) + bounds: 40 256 8 8 + - image: solid-color(6, 32, 0, 255, 8, 8) + bounds: 48 256 8 8 + - image: solid-color(7, 32, 0, 255, 8, 8) + bounds: 56 256 8 8 + - image: solid-color(8, 32, 0, 255, 8, 8) + bounds: 64 256 8 8 + - image: solid-color(9, 32, 0, 255, 8, 8) + bounds: 72 256 8 8 + - image: solid-color(10, 32, 0, 255, 8, 8) + bounds: 80 256 8 8 + - image: solid-color(11, 32, 0, 255, 8, 8) + bounds: 88 256 8 8 + - image: solid-color(12, 32, 0, 255, 8, 8) + bounds: 96 256 8 8 + - image: solid-color(13, 32, 0, 255, 8, 8) + bounds: 104 256 8 8 + - image: solid-color(14, 32, 0, 255, 8, 8) + bounds: 112 256 8 8 + - image: solid-color(15, 32, 0, 255, 8, 8) + bounds: 120 256 8 8 + - image: solid-color(16, 32, 0, 255, 8, 8) + bounds: 128 256 8 8 + - image: solid-color(17, 32, 0, 255, 8, 8) + bounds: 136 256 8 8 + - image: solid-color(18, 32, 0, 255, 8, 8) + bounds: 144 256 8 8 + - image: solid-color(19, 32, 0, 255, 8, 8) + bounds: 152 256 8 8 + - image: solid-color(20, 32, 0, 255, 8, 8) + bounds: 160 256 8 8 + - image: solid-color(21, 32, 0, 255, 8, 8) + bounds: 168 256 8 8 + - image: solid-color(22, 32, 0, 255, 8, 8) + bounds: 176 256 8 8 + - image: solid-color(23, 32, 0, 255, 8, 8) + bounds: 184 256 8 8 + - image: solid-color(24, 32, 0, 255, 8, 8) + bounds: 192 256 8 8 + - image: solid-color(25, 32, 0, 255, 8, 8) + bounds: 200 256 8 8 + - image: solid-color(26, 32, 0, 255, 8, 8) + bounds: 208 256 8 8 + - image: solid-color(27, 32, 0, 255, 8, 8) + bounds: 216 256 8 8 + - image: solid-color(28, 32, 0, 255, 8, 8) + bounds: 224 256 8 8 + - image: solid-color(29, 32, 0, 255, 8, 8) + bounds: 232 256 8 8 + - image: solid-color(30, 32, 0, 255, 8, 8) + bounds: 240 256 8 8 + - image: solid-color(31, 32, 0, 255, 8, 8) + bounds: 248 256 8 8 + - image: solid-color(32, 32, 0, 255, 8, 8) + bounds: 256 256 8 8 + - image: solid-color(33, 32, 0, 255, 8, 8) + bounds: 264 256 8 8 + - image: solid-color(34, 32, 0, 255, 8, 8) + bounds: 272 256 8 8 + - image: solid-color(35, 32, 0, 255, 8, 8) + bounds: 280 256 8 8 + - image: solid-color(36, 32, 0, 255, 8, 8) + bounds: 288 256 8 8 + - image: solid-color(37, 32, 0, 255, 8, 8) + bounds: 296 256 8 8 + - image: solid-color(38, 32, 0, 255, 8, 8) + bounds: 304 256 8 8 + - image: solid-color(39, 32, 0, 255, 8, 8) + bounds: 312 256 8 8 + - image: solid-color(40, 32, 0, 255, 8, 8) + bounds: 320 256 8 8 + - image: solid-color(41, 32, 0, 255, 8, 8) + bounds: 328 256 8 8 + - image: solid-color(42, 32, 0, 255, 8, 8) + bounds: 336 256 8 8 + - image: solid-color(43, 32, 0, 255, 8, 8) + bounds: 344 256 8 8 + - image: solid-color(44, 32, 0, 255, 8, 8) + bounds: 352 256 8 8 + - image: solid-color(45, 32, 0, 255, 8, 8) + bounds: 360 256 8 8 + - image: solid-color(46, 32, 0, 255, 8, 8) + bounds: 368 256 8 8 + - image: solid-color(47, 32, 0, 255, 8, 8) + bounds: 376 256 8 8 + - image: solid-color(48, 32, 0, 255, 8, 8) + bounds: 384 256 8 8 + - image: solid-color(49, 32, 0, 255, 8, 8) + bounds: 392 256 8 8 + - image: solid-color(50, 32, 0, 255, 8, 8) + bounds: 400 256 8 8 + - image: solid-color(51, 32, 0, 255, 8, 8) + bounds: 408 256 8 8 + - image: solid-color(52, 32, 0, 255, 8, 8) + bounds: 416 256 8 8 + - image: solid-color(53, 32, 0, 255, 8, 8) + bounds: 424 256 8 8 + - image: solid-color(54, 32, 0, 255, 8, 8) + bounds: 432 256 8 8 + - image: solid-color(55, 32, 0, 255, 8, 8) + bounds: 440 256 8 8 + - image: solid-color(56, 32, 0, 255, 8, 8) + bounds: 448 256 8 8 + - image: solid-color(57, 32, 0, 255, 8, 8) + bounds: 456 256 8 8 + - image: solid-color(58, 32, 0, 255, 8, 8) + bounds: 464 256 8 8 + - image: solid-color(59, 32, 0, 255, 8, 8) + bounds: 472 256 8 8 + - image: solid-color(60, 32, 0, 255, 8, 8) + bounds: 480 256 8 8 + - image: solid-color(61, 32, 0, 255, 8, 8) + bounds: 488 256 8 8 + - image: solid-color(62, 32, 0, 255, 8, 8) + bounds: 496 256 8 8 + - image: solid-color(63, 32, 0, 255, 8, 8) + bounds: 504 256 8 8 + - image: solid-color(64, 32, 0, 255, 8, 8) + bounds: 512 256 8 8 + - image: solid-color(65, 32, 0, 255, 8, 8) + bounds: 520 256 8 8 + - image: solid-color(66, 32, 0, 255, 8, 8) + bounds: 528 256 8 8 + - image: solid-color(67, 32, 0, 255, 8, 8) + bounds: 536 256 8 8 + - image: solid-color(68, 32, 0, 255, 8, 8) + bounds: 544 256 8 8 + - image: solid-color(69, 32, 0, 255, 8, 8) + bounds: 552 256 8 8 + - image: solid-color(70, 32, 0, 255, 8, 8) + bounds: 560 256 8 8 + - image: solid-color(71, 32, 0, 255, 8, 8) + bounds: 568 256 8 8 + - image: solid-color(72, 32, 0, 255, 8, 8) + bounds: 576 256 8 8 + - image: solid-color(73, 32, 0, 255, 8, 8) + bounds: 584 256 8 8 + - image: solid-color(74, 32, 0, 255, 8, 8) + bounds: 592 256 8 8 + - image: solid-color(75, 32, 0, 255, 8, 8) + bounds: 600 256 8 8 + - image: solid-color(76, 32, 0, 255, 8, 8) + bounds: 608 256 8 8 + - image: solid-color(77, 32, 0, 255, 8, 8) + bounds: 616 256 8 8 + - image: solid-color(78, 32, 0, 255, 8, 8) + bounds: 624 256 8 8 + - image: solid-color(79, 32, 0, 255, 8, 8) + bounds: 632 256 8 8 + - image: solid-color(80, 32, 0, 255, 8, 8) + bounds: 640 256 8 8 + - image: solid-color(81, 32, 0, 255, 8, 8) + bounds: 648 256 8 8 + - image: solid-color(82, 32, 0, 255, 8, 8) + bounds: 656 256 8 8 + - image: solid-color(83, 32, 0, 255, 8, 8) + bounds: 664 256 8 8 + - image: solid-color(84, 32, 0, 255, 8, 8) + bounds: 672 256 8 8 + - image: solid-color(85, 32, 0, 255, 8, 8) + bounds: 680 256 8 8 + - image: solid-color(86, 32, 0, 255, 8, 8) + bounds: 688 256 8 8 + - image: solid-color(87, 32, 0, 255, 8, 8) + bounds: 696 256 8 8 + - image: solid-color(88, 32, 0, 255, 8, 8) + bounds: 704 256 8 8 + - image: solid-color(89, 32, 0, 255, 8, 8) + bounds: 712 256 8 8 + - image: solid-color(90, 32, 0, 255, 8, 8) + bounds: 720 256 8 8 + - image: solid-color(91, 32, 0, 255, 8, 8) + bounds: 728 256 8 8 + - image: solid-color(92, 32, 0, 255, 8, 8) + bounds: 736 256 8 8 + - image: solid-color(93, 32, 0, 255, 8, 8) + bounds: 744 256 8 8 + - image: solid-color(94, 32, 0, 255, 8, 8) + bounds: 752 256 8 8 + - image: solid-color(95, 32, 0, 255, 8, 8) + bounds: 760 256 8 8 + - image: solid-color(96, 32, 0, 255, 8, 8) + bounds: 768 256 8 8 + - image: solid-color(97, 32, 0, 255, 8, 8) + bounds: 776 256 8 8 + - image: solid-color(98, 32, 0, 255, 8, 8) + bounds: 784 256 8 8 + - image: solid-color(99, 32, 0, 255, 8, 8) + bounds: 792 256 8 8 + - image: solid-color(100, 32, 0, 255, 8, 8) + bounds: 800 256 8 8 + - image: solid-color(101, 32, 0, 255, 8, 8) + bounds: 808 256 8 8 + - image: solid-color(102, 32, 0, 255, 8, 8) + bounds: 816 256 8 8 + - image: solid-color(103, 32, 0, 255, 8, 8) + bounds: 824 256 8 8 + - image: solid-color(104, 32, 0, 255, 8, 8) + bounds: 832 256 8 8 + - image: solid-color(105, 32, 0, 255, 8, 8) + bounds: 840 256 8 8 + - image: solid-color(106, 32, 0, 255, 8, 8) + bounds: 848 256 8 8 + - image: solid-color(107, 32, 0, 255, 8, 8) + bounds: 856 256 8 8 + - image: solid-color(108, 32, 0, 255, 8, 8) + bounds: 864 256 8 8 + - image: solid-color(109, 32, 0, 255, 8, 8) + bounds: 872 256 8 8 + - image: solid-color(110, 32, 0, 255, 8, 8) + bounds: 880 256 8 8 + - image: solid-color(111, 32, 0, 255, 8, 8) + bounds: 888 256 8 8 + - image: solid-color(112, 32, 0, 255, 8, 8) + bounds: 896 256 8 8 + - image: solid-color(113, 32, 0, 255, 8, 8) + bounds: 904 256 8 8 + - image: solid-color(114, 32, 0, 255, 8, 8) + bounds: 912 256 8 8 + - image: solid-color(115, 32, 0, 255, 8, 8) + bounds: 920 256 8 8 + - image: solid-color(116, 32, 0, 255, 8, 8) + bounds: 928 256 8 8 + - image: solid-color(117, 32, 0, 255, 8, 8) + bounds: 936 256 8 8 + - image: solid-color(118, 32, 0, 255, 8, 8) + bounds: 944 256 8 8 + - image: solid-color(119, 32, 0, 255, 8, 8) + bounds: 952 256 8 8 + - image: solid-color(120, 32, 0, 255, 8, 8) + bounds: 960 256 8 8 + - image: solid-color(121, 32, 0, 255, 8, 8) + bounds: 968 256 8 8 + - image: solid-color(122, 32, 0, 255, 8, 8) + bounds: 976 256 8 8 + - image: solid-color(123, 32, 0, 255, 8, 8) + bounds: 984 256 8 8 + - image: solid-color(124, 32, 0, 255, 8, 8) + bounds: 992 256 8 8 + - image: solid-color(125, 32, 0, 255, 8, 8) + bounds: 1000 256 8 8 + - image: solid-color(126, 32, 0, 255, 8, 8) + bounds: 1008 256 8 8 + - image: solid-color(127, 32, 0, 255, 8, 8) + bounds: 1016 256 8 8 + - image: solid-color(0, 33, 0, 255, 8, 8) + bounds: 0 264 8 8 + - image: solid-color(1, 33, 0, 255, 8, 8) + bounds: 8 264 8 8 + - image: solid-color(2, 33, 0, 255, 8, 8) + bounds: 16 264 8 8 + - image: solid-color(3, 33, 0, 255, 8, 8) + bounds: 24 264 8 8 + - image: solid-color(4, 33, 0, 255, 8, 8) + bounds: 32 264 8 8 + - image: solid-color(5, 33, 0, 255, 8, 8) + bounds: 40 264 8 8 + - image: solid-color(6, 33, 0, 255, 8, 8) + bounds: 48 264 8 8 + - image: solid-color(7, 33, 0, 255, 8, 8) + bounds: 56 264 8 8 + - image: solid-color(8, 33, 0, 255, 8, 8) + bounds: 64 264 8 8 + - image: solid-color(9, 33, 0, 255, 8, 8) + bounds: 72 264 8 8 + - image: solid-color(10, 33, 0, 255, 8, 8) + bounds: 80 264 8 8 + - image: solid-color(11, 33, 0, 255, 8, 8) + bounds: 88 264 8 8 + - image: solid-color(12, 33, 0, 255, 8, 8) + bounds: 96 264 8 8 + - image: solid-color(13, 33, 0, 255, 8, 8) + bounds: 104 264 8 8 + - image: solid-color(14, 33, 0, 255, 8, 8) + bounds: 112 264 8 8 + - image: solid-color(15, 33, 0, 255, 8, 8) + bounds: 120 264 8 8 + - image: solid-color(16, 33, 0, 255, 8, 8) + bounds: 128 264 8 8 + - image: solid-color(17, 33, 0, 255, 8, 8) + bounds: 136 264 8 8 + - image: solid-color(18, 33, 0, 255, 8, 8) + bounds: 144 264 8 8 + - image: solid-color(19, 33, 0, 255, 8, 8) + bounds: 152 264 8 8 + - image: solid-color(20, 33, 0, 255, 8, 8) + bounds: 160 264 8 8 + - image: solid-color(21, 33, 0, 255, 8, 8) + bounds: 168 264 8 8 + - image: solid-color(22, 33, 0, 255, 8, 8) + bounds: 176 264 8 8 + - image: solid-color(23, 33, 0, 255, 8, 8) + bounds: 184 264 8 8 + - image: solid-color(24, 33, 0, 255, 8, 8) + bounds: 192 264 8 8 + - image: solid-color(25, 33, 0, 255, 8, 8) + bounds: 200 264 8 8 + - image: solid-color(26, 33, 0, 255, 8, 8) + bounds: 208 264 8 8 + - image: solid-color(27, 33, 0, 255, 8, 8) + bounds: 216 264 8 8 + - image: solid-color(28, 33, 0, 255, 8, 8) + bounds: 224 264 8 8 + - image: solid-color(29, 33, 0, 255, 8, 8) + bounds: 232 264 8 8 + - image: solid-color(30, 33, 0, 255, 8, 8) + bounds: 240 264 8 8 + - image: solid-color(31, 33, 0, 255, 8, 8) + bounds: 248 264 8 8 + - image: solid-color(32, 33, 0, 255, 8, 8) + bounds: 256 264 8 8 + - image: solid-color(33, 33, 0, 255, 8, 8) + bounds: 264 264 8 8 + - image: solid-color(34, 33, 0, 255, 8, 8) + bounds: 272 264 8 8 + - image: solid-color(35, 33, 0, 255, 8, 8) + bounds: 280 264 8 8 + - image: solid-color(36, 33, 0, 255, 8, 8) + bounds: 288 264 8 8 + - image: solid-color(37, 33, 0, 255, 8, 8) + bounds: 296 264 8 8 + - image: solid-color(38, 33, 0, 255, 8, 8) + bounds: 304 264 8 8 + - image: solid-color(39, 33, 0, 255, 8, 8) + bounds: 312 264 8 8 + - image: solid-color(40, 33, 0, 255, 8, 8) + bounds: 320 264 8 8 + - image: solid-color(41, 33, 0, 255, 8, 8) + bounds: 328 264 8 8 + - image: solid-color(42, 33, 0, 255, 8, 8) + bounds: 336 264 8 8 + - image: solid-color(43, 33, 0, 255, 8, 8) + bounds: 344 264 8 8 + - image: solid-color(44, 33, 0, 255, 8, 8) + bounds: 352 264 8 8 + - image: solid-color(45, 33, 0, 255, 8, 8) + bounds: 360 264 8 8 + - image: solid-color(46, 33, 0, 255, 8, 8) + bounds: 368 264 8 8 + - image: solid-color(47, 33, 0, 255, 8, 8) + bounds: 376 264 8 8 + - image: solid-color(48, 33, 0, 255, 8, 8) + bounds: 384 264 8 8 + - image: solid-color(49, 33, 0, 255, 8, 8) + bounds: 392 264 8 8 + - image: solid-color(50, 33, 0, 255, 8, 8) + bounds: 400 264 8 8 + - image: solid-color(51, 33, 0, 255, 8, 8) + bounds: 408 264 8 8 + - image: solid-color(52, 33, 0, 255, 8, 8) + bounds: 416 264 8 8 + - image: solid-color(53, 33, 0, 255, 8, 8) + bounds: 424 264 8 8 + - image: solid-color(54, 33, 0, 255, 8, 8) + bounds: 432 264 8 8 + - image: solid-color(55, 33, 0, 255, 8, 8) + bounds: 440 264 8 8 + - image: solid-color(56, 33, 0, 255, 8, 8) + bounds: 448 264 8 8 + - image: solid-color(57, 33, 0, 255, 8, 8) + bounds: 456 264 8 8 + - image: solid-color(58, 33, 0, 255, 8, 8) + bounds: 464 264 8 8 + - image: solid-color(59, 33, 0, 255, 8, 8) + bounds: 472 264 8 8 + - image: solid-color(60, 33, 0, 255, 8, 8) + bounds: 480 264 8 8 + - image: solid-color(61, 33, 0, 255, 8, 8) + bounds: 488 264 8 8 + - image: solid-color(62, 33, 0, 255, 8, 8) + bounds: 496 264 8 8 + - image: solid-color(63, 33, 0, 255, 8, 8) + bounds: 504 264 8 8 + - image: solid-color(64, 33, 0, 255, 8, 8) + bounds: 512 264 8 8 + - image: solid-color(65, 33, 0, 255, 8, 8) + bounds: 520 264 8 8 + - image: solid-color(66, 33, 0, 255, 8, 8) + bounds: 528 264 8 8 + - image: solid-color(67, 33, 0, 255, 8, 8) + bounds: 536 264 8 8 + - image: solid-color(68, 33, 0, 255, 8, 8) + bounds: 544 264 8 8 + - image: solid-color(69, 33, 0, 255, 8, 8) + bounds: 552 264 8 8 + - image: solid-color(70, 33, 0, 255, 8, 8) + bounds: 560 264 8 8 + - image: solid-color(71, 33, 0, 255, 8, 8) + bounds: 568 264 8 8 + - image: solid-color(72, 33, 0, 255, 8, 8) + bounds: 576 264 8 8 + - image: solid-color(73, 33, 0, 255, 8, 8) + bounds: 584 264 8 8 + - image: solid-color(74, 33, 0, 255, 8, 8) + bounds: 592 264 8 8 + - image: solid-color(75, 33, 0, 255, 8, 8) + bounds: 600 264 8 8 + - image: solid-color(76, 33, 0, 255, 8, 8) + bounds: 608 264 8 8 + - image: solid-color(77, 33, 0, 255, 8, 8) + bounds: 616 264 8 8 + - image: solid-color(78, 33, 0, 255, 8, 8) + bounds: 624 264 8 8 + - image: solid-color(79, 33, 0, 255, 8, 8) + bounds: 632 264 8 8 + - image: solid-color(80, 33, 0, 255, 8, 8) + bounds: 640 264 8 8 + - image: solid-color(81, 33, 0, 255, 8, 8) + bounds: 648 264 8 8 + - image: solid-color(82, 33, 0, 255, 8, 8) + bounds: 656 264 8 8 + - image: solid-color(83, 33, 0, 255, 8, 8) + bounds: 664 264 8 8 + - image: solid-color(84, 33, 0, 255, 8, 8) + bounds: 672 264 8 8 + - image: solid-color(85, 33, 0, 255, 8, 8) + bounds: 680 264 8 8 + - image: solid-color(86, 33, 0, 255, 8, 8) + bounds: 688 264 8 8 + - image: solid-color(87, 33, 0, 255, 8, 8) + bounds: 696 264 8 8 + - image: solid-color(88, 33, 0, 255, 8, 8) + bounds: 704 264 8 8 + - image: solid-color(89, 33, 0, 255, 8, 8) + bounds: 712 264 8 8 + - image: solid-color(90, 33, 0, 255, 8, 8) + bounds: 720 264 8 8 + - image: solid-color(91, 33, 0, 255, 8, 8) + bounds: 728 264 8 8 + - image: solid-color(92, 33, 0, 255, 8, 8) + bounds: 736 264 8 8 + - image: solid-color(93, 33, 0, 255, 8, 8) + bounds: 744 264 8 8 + - image: solid-color(94, 33, 0, 255, 8, 8) + bounds: 752 264 8 8 + - image: solid-color(95, 33, 0, 255, 8, 8) + bounds: 760 264 8 8 + - image: solid-color(96, 33, 0, 255, 8, 8) + bounds: 768 264 8 8 + - image: solid-color(97, 33, 0, 255, 8, 8) + bounds: 776 264 8 8 + - image: solid-color(98, 33, 0, 255, 8, 8) + bounds: 784 264 8 8 + - image: solid-color(99, 33, 0, 255, 8, 8) + bounds: 792 264 8 8 + - image: solid-color(100, 33, 0, 255, 8, 8) + bounds: 800 264 8 8 + - image: solid-color(101, 33, 0, 255, 8, 8) + bounds: 808 264 8 8 + - image: solid-color(102, 33, 0, 255, 8, 8) + bounds: 816 264 8 8 + - image: solid-color(103, 33, 0, 255, 8, 8) + bounds: 824 264 8 8 + - image: solid-color(104, 33, 0, 255, 8, 8) + bounds: 832 264 8 8 + - image: solid-color(105, 33, 0, 255, 8, 8) + bounds: 840 264 8 8 + - image: solid-color(106, 33, 0, 255, 8, 8) + bounds: 848 264 8 8 + - image: solid-color(107, 33, 0, 255, 8, 8) + bounds: 856 264 8 8 + - image: solid-color(108, 33, 0, 255, 8, 8) + bounds: 864 264 8 8 + - image: solid-color(109, 33, 0, 255, 8, 8) + bounds: 872 264 8 8 + - image: solid-color(110, 33, 0, 255, 8, 8) + bounds: 880 264 8 8 + - image: solid-color(111, 33, 0, 255, 8, 8) + bounds: 888 264 8 8 + - image: solid-color(112, 33, 0, 255, 8, 8) + bounds: 896 264 8 8 + - image: solid-color(113, 33, 0, 255, 8, 8) + bounds: 904 264 8 8 + - image: solid-color(114, 33, 0, 255, 8, 8) + bounds: 912 264 8 8 + - image: solid-color(115, 33, 0, 255, 8, 8) + bounds: 920 264 8 8 + - image: solid-color(116, 33, 0, 255, 8, 8) + bounds: 928 264 8 8 + - image: solid-color(117, 33, 0, 255, 8, 8) + bounds: 936 264 8 8 + - image: solid-color(118, 33, 0, 255, 8, 8) + bounds: 944 264 8 8 + - image: solid-color(119, 33, 0, 255, 8, 8) + bounds: 952 264 8 8 + - image: solid-color(120, 33, 0, 255, 8, 8) + bounds: 960 264 8 8 + - image: solid-color(121, 33, 0, 255, 8, 8) + bounds: 968 264 8 8 + - image: solid-color(122, 33, 0, 255, 8, 8) + bounds: 976 264 8 8 + - image: solid-color(123, 33, 0, 255, 8, 8) + bounds: 984 264 8 8 + - image: solid-color(124, 33, 0, 255, 8, 8) + bounds: 992 264 8 8 + - image: solid-color(125, 33, 0, 255, 8, 8) + bounds: 1000 264 8 8 + - image: solid-color(126, 33, 0, 255, 8, 8) + bounds: 1008 264 8 8 + - image: solid-color(127, 33, 0, 255, 8, 8) + bounds: 1016 264 8 8 + - image: solid-color(0, 34, 0, 255, 8, 8) + bounds: 0 272 8 8 + - image: solid-color(1, 34, 0, 255, 8, 8) + bounds: 8 272 8 8 + - image: solid-color(2, 34, 0, 255, 8, 8) + bounds: 16 272 8 8 + - image: solid-color(3, 34, 0, 255, 8, 8) + bounds: 24 272 8 8 + - image: solid-color(4, 34, 0, 255, 8, 8) + bounds: 32 272 8 8 + - image: solid-color(5, 34, 0, 255, 8, 8) + bounds: 40 272 8 8 + - image: solid-color(6, 34, 0, 255, 8, 8) + bounds: 48 272 8 8 + - image: solid-color(7, 34, 0, 255, 8, 8) + bounds: 56 272 8 8 + - image: solid-color(8, 34, 0, 255, 8, 8) + bounds: 64 272 8 8 + - image: solid-color(9, 34, 0, 255, 8, 8) + bounds: 72 272 8 8 + - image: solid-color(10, 34, 0, 255, 8, 8) + bounds: 80 272 8 8 + - image: solid-color(11, 34, 0, 255, 8, 8) + bounds: 88 272 8 8 + - image: solid-color(12, 34, 0, 255, 8, 8) + bounds: 96 272 8 8 + - image: solid-color(13, 34, 0, 255, 8, 8) + bounds: 104 272 8 8 + - image: solid-color(14, 34, 0, 255, 8, 8) + bounds: 112 272 8 8 + - image: solid-color(15, 34, 0, 255, 8, 8) + bounds: 120 272 8 8 + - image: solid-color(16, 34, 0, 255, 8, 8) + bounds: 128 272 8 8 + - image: solid-color(17, 34, 0, 255, 8, 8) + bounds: 136 272 8 8 + - image: solid-color(18, 34, 0, 255, 8, 8) + bounds: 144 272 8 8 + - image: solid-color(19, 34, 0, 255, 8, 8) + bounds: 152 272 8 8 + - image: solid-color(20, 34, 0, 255, 8, 8) + bounds: 160 272 8 8 + - image: solid-color(21, 34, 0, 255, 8, 8) + bounds: 168 272 8 8 + - image: solid-color(22, 34, 0, 255, 8, 8) + bounds: 176 272 8 8 + - image: solid-color(23, 34, 0, 255, 8, 8) + bounds: 184 272 8 8 + - image: solid-color(24, 34, 0, 255, 8, 8) + bounds: 192 272 8 8 + - image: solid-color(25, 34, 0, 255, 8, 8) + bounds: 200 272 8 8 + - image: solid-color(26, 34, 0, 255, 8, 8) + bounds: 208 272 8 8 + - image: solid-color(27, 34, 0, 255, 8, 8) + bounds: 216 272 8 8 + - image: solid-color(28, 34, 0, 255, 8, 8) + bounds: 224 272 8 8 + - image: solid-color(29, 34, 0, 255, 8, 8) + bounds: 232 272 8 8 + - image: solid-color(30, 34, 0, 255, 8, 8) + bounds: 240 272 8 8 + - image: solid-color(31, 34, 0, 255, 8, 8) + bounds: 248 272 8 8 + - image: solid-color(32, 34, 0, 255, 8, 8) + bounds: 256 272 8 8 + - image: solid-color(33, 34, 0, 255, 8, 8) + bounds: 264 272 8 8 + - image: solid-color(34, 34, 0, 255, 8, 8) + bounds: 272 272 8 8 + - image: solid-color(35, 34, 0, 255, 8, 8) + bounds: 280 272 8 8 + - image: solid-color(36, 34, 0, 255, 8, 8) + bounds: 288 272 8 8 + - image: solid-color(37, 34, 0, 255, 8, 8) + bounds: 296 272 8 8 + - image: solid-color(38, 34, 0, 255, 8, 8) + bounds: 304 272 8 8 + - image: solid-color(39, 34, 0, 255, 8, 8) + bounds: 312 272 8 8 + - image: solid-color(40, 34, 0, 255, 8, 8) + bounds: 320 272 8 8 + - image: solid-color(41, 34, 0, 255, 8, 8) + bounds: 328 272 8 8 + - image: solid-color(42, 34, 0, 255, 8, 8) + bounds: 336 272 8 8 + - image: solid-color(43, 34, 0, 255, 8, 8) + bounds: 344 272 8 8 + - image: solid-color(44, 34, 0, 255, 8, 8) + bounds: 352 272 8 8 + - image: solid-color(45, 34, 0, 255, 8, 8) + bounds: 360 272 8 8 + - image: solid-color(46, 34, 0, 255, 8, 8) + bounds: 368 272 8 8 + - image: solid-color(47, 34, 0, 255, 8, 8) + bounds: 376 272 8 8 + - image: solid-color(48, 34, 0, 255, 8, 8) + bounds: 384 272 8 8 + - image: solid-color(49, 34, 0, 255, 8, 8) + bounds: 392 272 8 8 + - image: solid-color(50, 34, 0, 255, 8, 8) + bounds: 400 272 8 8 + - image: solid-color(51, 34, 0, 255, 8, 8) + bounds: 408 272 8 8 + - image: solid-color(52, 34, 0, 255, 8, 8) + bounds: 416 272 8 8 + - image: solid-color(53, 34, 0, 255, 8, 8) + bounds: 424 272 8 8 + - image: solid-color(54, 34, 0, 255, 8, 8) + bounds: 432 272 8 8 + - image: solid-color(55, 34, 0, 255, 8, 8) + bounds: 440 272 8 8 + - image: solid-color(56, 34, 0, 255, 8, 8) + bounds: 448 272 8 8 + - image: solid-color(57, 34, 0, 255, 8, 8) + bounds: 456 272 8 8 + - image: solid-color(58, 34, 0, 255, 8, 8) + bounds: 464 272 8 8 + - image: solid-color(59, 34, 0, 255, 8, 8) + bounds: 472 272 8 8 + - image: solid-color(60, 34, 0, 255, 8, 8) + bounds: 480 272 8 8 + - image: solid-color(61, 34, 0, 255, 8, 8) + bounds: 488 272 8 8 + - image: solid-color(62, 34, 0, 255, 8, 8) + bounds: 496 272 8 8 + - image: solid-color(63, 34, 0, 255, 8, 8) + bounds: 504 272 8 8 + - image: solid-color(64, 34, 0, 255, 8, 8) + bounds: 512 272 8 8 + - image: solid-color(65, 34, 0, 255, 8, 8) + bounds: 520 272 8 8 + - image: solid-color(66, 34, 0, 255, 8, 8) + bounds: 528 272 8 8 + - image: solid-color(67, 34, 0, 255, 8, 8) + bounds: 536 272 8 8 + - image: solid-color(68, 34, 0, 255, 8, 8) + bounds: 544 272 8 8 + - image: solid-color(69, 34, 0, 255, 8, 8) + bounds: 552 272 8 8 + - image: solid-color(70, 34, 0, 255, 8, 8) + bounds: 560 272 8 8 + - image: solid-color(71, 34, 0, 255, 8, 8) + bounds: 568 272 8 8 + - image: solid-color(72, 34, 0, 255, 8, 8) + bounds: 576 272 8 8 + - image: solid-color(73, 34, 0, 255, 8, 8) + bounds: 584 272 8 8 + - image: solid-color(74, 34, 0, 255, 8, 8) + bounds: 592 272 8 8 + - image: solid-color(75, 34, 0, 255, 8, 8) + bounds: 600 272 8 8 + - image: solid-color(76, 34, 0, 255, 8, 8) + bounds: 608 272 8 8 + - image: solid-color(77, 34, 0, 255, 8, 8) + bounds: 616 272 8 8 + - image: solid-color(78, 34, 0, 255, 8, 8) + bounds: 624 272 8 8 + - image: solid-color(79, 34, 0, 255, 8, 8) + bounds: 632 272 8 8 + - image: solid-color(80, 34, 0, 255, 8, 8) + bounds: 640 272 8 8 + - image: solid-color(81, 34, 0, 255, 8, 8) + bounds: 648 272 8 8 + - image: solid-color(82, 34, 0, 255, 8, 8) + bounds: 656 272 8 8 + - image: solid-color(83, 34, 0, 255, 8, 8) + bounds: 664 272 8 8 + - image: solid-color(84, 34, 0, 255, 8, 8) + bounds: 672 272 8 8 + - image: solid-color(85, 34, 0, 255, 8, 8) + bounds: 680 272 8 8 + - image: solid-color(86, 34, 0, 255, 8, 8) + bounds: 688 272 8 8 + - image: solid-color(87, 34, 0, 255, 8, 8) + bounds: 696 272 8 8 + - image: solid-color(88, 34, 0, 255, 8, 8) + bounds: 704 272 8 8 + - image: solid-color(89, 34, 0, 255, 8, 8) + bounds: 712 272 8 8 + - image: solid-color(90, 34, 0, 255, 8, 8) + bounds: 720 272 8 8 + - image: solid-color(91, 34, 0, 255, 8, 8) + bounds: 728 272 8 8 + - image: solid-color(92, 34, 0, 255, 8, 8) + bounds: 736 272 8 8 + - image: solid-color(93, 34, 0, 255, 8, 8) + bounds: 744 272 8 8 + - image: solid-color(94, 34, 0, 255, 8, 8) + bounds: 752 272 8 8 + - image: solid-color(95, 34, 0, 255, 8, 8) + bounds: 760 272 8 8 + - image: solid-color(96, 34, 0, 255, 8, 8) + bounds: 768 272 8 8 + - image: solid-color(97, 34, 0, 255, 8, 8) + bounds: 776 272 8 8 + - image: solid-color(98, 34, 0, 255, 8, 8) + bounds: 784 272 8 8 + - image: solid-color(99, 34, 0, 255, 8, 8) + bounds: 792 272 8 8 + - image: solid-color(100, 34, 0, 255, 8, 8) + bounds: 800 272 8 8 + - image: solid-color(101, 34, 0, 255, 8, 8) + bounds: 808 272 8 8 + - image: solid-color(102, 34, 0, 255, 8, 8) + bounds: 816 272 8 8 + - image: solid-color(103, 34, 0, 255, 8, 8) + bounds: 824 272 8 8 + - image: solid-color(104, 34, 0, 255, 8, 8) + bounds: 832 272 8 8 + - image: solid-color(105, 34, 0, 255, 8, 8) + bounds: 840 272 8 8 + - image: solid-color(106, 34, 0, 255, 8, 8) + bounds: 848 272 8 8 + - image: solid-color(107, 34, 0, 255, 8, 8) + bounds: 856 272 8 8 + - image: solid-color(108, 34, 0, 255, 8, 8) + bounds: 864 272 8 8 + - image: solid-color(109, 34, 0, 255, 8, 8) + bounds: 872 272 8 8 + - image: solid-color(110, 34, 0, 255, 8, 8) + bounds: 880 272 8 8 + - image: solid-color(111, 34, 0, 255, 8, 8) + bounds: 888 272 8 8 + - image: solid-color(112, 34, 0, 255, 8, 8) + bounds: 896 272 8 8 + - image: solid-color(113, 34, 0, 255, 8, 8) + bounds: 904 272 8 8 + - image: solid-color(114, 34, 0, 255, 8, 8) + bounds: 912 272 8 8 + - image: solid-color(115, 34, 0, 255, 8, 8) + bounds: 920 272 8 8 + - image: solid-color(116, 34, 0, 255, 8, 8) + bounds: 928 272 8 8 + - image: solid-color(117, 34, 0, 255, 8, 8) + bounds: 936 272 8 8 + - image: solid-color(118, 34, 0, 255, 8, 8) + bounds: 944 272 8 8 + - image: solid-color(119, 34, 0, 255, 8, 8) + bounds: 952 272 8 8 + - image: solid-color(120, 34, 0, 255, 8, 8) + bounds: 960 272 8 8 + - image: solid-color(121, 34, 0, 255, 8, 8) + bounds: 968 272 8 8 + - image: solid-color(122, 34, 0, 255, 8, 8) + bounds: 976 272 8 8 + - image: solid-color(123, 34, 0, 255, 8, 8) + bounds: 984 272 8 8 + - image: solid-color(124, 34, 0, 255, 8, 8) + bounds: 992 272 8 8 + - image: solid-color(125, 34, 0, 255, 8, 8) + bounds: 1000 272 8 8 + - image: solid-color(126, 34, 0, 255, 8, 8) + bounds: 1008 272 8 8 + - image: solid-color(127, 34, 0, 255, 8, 8) + bounds: 1016 272 8 8 + - image: solid-color(0, 35, 0, 255, 8, 8) + bounds: 0 280 8 8 + - image: solid-color(1, 35, 0, 255, 8, 8) + bounds: 8 280 8 8 + - image: solid-color(2, 35, 0, 255, 8, 8) + bounds: 16 280 8 8 + - image: solid-color(3, 35, 0, 255, 8, 8) + bounds: 24 280 8 8 + - image: solid-color(4, 35, 0, 255, 8, 8) + bounds: 32 280 8 8 + - image: solid-color(5, 35, 0, 255, 8, 8) + bounds: 40 280 8 8 + - image: solid-color(6, 35, 0, 255, 8, 8) + bounds: 48 280 8 8 + - image: solid-color(7, 35, 0, 255, 8, 8) + bounds: 56 280 8 8 + - image: solid-color(8, 35, 0, 255, 8, 8) + bounds: 64 280 8 8 + - image: solid-color(9, 35, 0, 255, 8, 8) + bounds: 72 280 8 8 + - image: solid-color(10, 35, 0, 255, 8, 8) + bounds: 80 280 8 8 + - image: solid-color(11, 35, 0, 255, 8, 8) + bounds: 88 280 8 8 + - image: solid-color(12, 35, 0, 255, 8, 8) + bounds: 96 280 8 8 + - image: solid-color(13, 35, 0, 255, 8, 8) + bounds: 104 280 8 8 + - image: solid-color(14, 35, 0, 255, 8, 8) + bounds: 112 280 8 8 + - image: solid-color(15, 35, 0, 255, 8, 8) + bounds: 120 280 8 8 + - image: solid-color(16, 35, 0, 255, 8, 8) + bounds: 128 280 8 8 + - image: solid-color(17, 35, 0, 255, 8, 8) + bounds: 136 280 8 8 + - image: solid-color(18, 35, 0, 255, 8, 8) + bounds: 144 280 8 8 + - image: solid-color(19, 35, 0, 255, 8, 8) + bounds: 152 280 8 8 + - image: solid-color(20, 35, 0, 255, 8, 8) + bounds: 160 280 8 8 + - image: solid-color(21, 35, 0, 255, 8, 8) + bounds: 168 280 8 8 + - image: solid-color(22, 35, 0, 255, 8, 8) + bounds: 176 280 8 8 + - image: solid-color(23, 35, 0, 255, 8, 8) + bounds: 184 280 8 8 + - image: solid-color(24, 35, 0, 255, 8, 8) + bounds: 192 280 8 8 + - image: solid-color(25, 35, 0, 255, 8, 8) + bounds: 200 280 8 8 + - image: solid-color(26, 35, 0, 255, 8, 8) + bounds: 208 280 8 8 + - image: solid-color(27, 35, 0, 255, 8, 8) + bounds: 216 280 8 8 + - image: solid-color(28, 35, 0, 255, 8, 8) + bounds: 224 280 8 8 + - image: solid-color(29, 35, 0, 255, 8, 8) + bounds: 232 280 8 8 + - image: solid-color(30, 35, 0, 255, 8, 8) + bounds: 240 280 8 8 + - image: solid-color(31, 35, 0, 255, 8, 8) + bounds: 248 280 8 8 + - image: solid-color(32, 35, 0, 255, 8, 8) + bounds: 256 280 8 8 + - image: solid-color(33, 35, 0, 255, 8, 8) + bounds: 264 280 8 8 + - image: solid-color(34, 35, 0, 255, 8, 8) + bounds: 272 280 8 8 + - image: solid-color(35, 35, 0, 255, 8, 8) + bounds: 280 280 8 8 + - image: solid-color(36, 35, 0, 255, 8, 8) + bounds: 288 280 8 8 + - image: solid-color(37, 35, 0, 255, 8, 8) + bounds: 296 280 8 8 + - image: solid-color(38, 35, 0, 255, 8, 8) + bounds: 304 280 8 8 + - image: solid-color(39, 35, 0, 255, 8, 8) + bounds: 312 280 8 8 + - image: solid-color(40, 35, 0, 255, 8, 8) + bounds: 320 280 8 8 + - image: solid-color(41, 35, 0, 255, 8, 8) + bounds: 328 280 8 8 + - image: solid-color(42, 35, 0, 255, 8, 8) + bounds: 336 280 8 8 + - image: solid-color(43, 35, 0, 255, 8, 8) + bounds: 344 280 8 8 + - image: solid-color(44, 35, 0, 255, 8, 8) + bounds: 352 280 8 8 + - image: solid-color(45, 35, 0, 255, 8, 8) + bounds: 360 280 8 8 + - image: solid-color(46, 35, 0, 255, 8, 8) + bounds: 368 280 8 8 + - image: solid-color(47, 35, 0, 255, 8, 8) + bounds: 376 280 8 8 + - image: solid-color(48, 35, 0, 255, 8, 8) + bounds: 384 280 8 8 + - image: solid-color(49, 35, 0, 255, 8, 8) + bounds: 392 280 8 8 + - image: solid-color(50, 35, 0, 255, 8, 8) + bounds: 400 280 8 8 + - image: solid-color(51, 35, 0, 255, 8, 8) + bounds: 408 280 8 8 + - image: solid-color(52, 35, 0, 255, 8, 8) + bounds: 416 280 8 8 + - image: solid-color(53, 35, 0, 255, 8, 8) + bounds: 424 280 8 8 + - image: solid-color(54, 35, 0, 255, 8, 8) + bounds: 432 280 8 8 + - image: solid-color(55, 35, 0, 255, 8, 8) + bounds: 440 280 8 8 + - image: solid-color(56, 35, 0, 255, 8, 8) + bounds: 448 280 8 8 + - image: solid-color(57, 35, 0, 255, 8, 8) + bounds: 456 280 8 8 + - image: solid-color(58, 35, 0, 255, 8, 8) + bounds: 464 280 8 8 + - image: solid-color(59, 35, 0, 255, 8, 8) + bounds: 472 280 8 8 + - image: solid-color(60, 35, 0, 255, 8, 8) + bounds: 480 280 8 8 + - image: solid-color(61, 35, 0, 255, 8, 8) + bounds: 488 280 8 8 + - image: solid-color(62, 35, 0, 255, 8, 8) + bounds: 496 280 8 8 + - image: solid-color(63, 35, 0, 255, 8, 8) + bounds: 504 280 8 8 + - image: solid-color(64, 35, 0, 255, 8, 8) + bounds: 512 280 8 8 + - image: solid-color(65, 35, 0, 255, 8, 8) + bounds: 520 280 8 8 + - image: solid-color(66, 35, 0, 255, 8, 8) + bounds: 528 280 8 8 + - image: solid-color(67, 35, 0, 255, 8, 8) + bounds: 536 280 8 8 + - image: solid-color(68, 35, 0, 255, 8, 8) + bounds: 544 280 8 8 + - image: solid-color(69, 35, 0, 255, 8, 8) + bounds: 552 280 8 8 + - image: solid-color(70, 35, 0, 255, 8, 8) + bounds: 560 280 8 8 + - image: solid-color(71, 35, 0, 255, 8, 8) + bounds: 568 280 8 8 + - image: solid-color(72, 35, 0, 255, 8, 8) + bounds: 576 280 8 8 + - image: solid-color(73, 35, 0, 255, 8, 8) + bounds: 584 280 8 8 + - image: solid-color(74, 35, 0, 255, 8, 8) + bounds: 592 280 8 8 + - image: solid-color(75, 35, 0, 255, 8, 8) + bounds: 600 280 8 8 + - image: solid-color(76, 35, 0, 255, 8, 8) + bounds: 608 280 8 8 + - image: solid-color(77, 35, 0, 255, 8, 8) + bounds: 616 280 8 8 + - image: solid-color(78, 35, 0, 255, 8, 8) + bounds: 624 280 8 8 + - image: solid-color(79, 35, 0, 255, 8, 8) + bounds: 632 280 8 8 + - image: solid-color(80, 35, 0, 255, 8, 8) + bounds: 640 280 8 8 + - image: solid-color(81, 35, 0, 255, 8, 8) + bounds: 648 280 8 8 + - image: solid-color(82, 35, 0, 255, 8, 8) + bounds: 656 280 8 8 + - image: solid-color(83, 35, 0, 255, 8, 8) + bounds: 664 280 8 8 + - image: solid-color(84, 35, 0, 255, 8, 8) + bounds: 672 280 8 8 + - image: solid-color(85, 35, 0, 255, 8, 8) + bounds: 680 280 8 8 + - image: solid-color(86, 35, 0, 255, 8, 8) + bounds: 688 280 8 8 + - image: solid-color(87, 35, 0, 255, 8, 8) + bounds: 696 280 8 8 + - image: solid-color(88, 35, 0, 255, 8, 8) + bounds: 704 280 8 8 + - image: solid-color(89, 35, 0, 255, 8, 8) + bounds: 712 280 8 8 + - image: solid-color(90, 35, 0, 255, 8, 8) + bounds: 720 280 8 8 + - image: solid-color(91, 35, 0, 255, 8, 8) + bounds: 728 280 8 8 + - image: solid-color(92, 35, 0, 255, 8, 8) + bounds: 736 280 8 8 + - image: solid-color(93, 35, 0, 255, 8, 8) + bounds: 744 280 8 8 + - image: solid-color(94, 35, 0, 255, 8, 8) + bounds: 752 280 8 8 + - image: solid-color(95, 35, 0, 255, 8, 8) + bounds: 760 280 8 8 + - image: solid-color(96, 35, 0, 255, 8, 8) + bounds: 768 280 8 8 + - image: solid-color(97, 35, 0, 255, 8, 8) + bounds: 776 280 8 8 + - image: solid-color(98, 35, 0, 255, 8, 8) + bounds: 784 280 8 8 + - image: solid-color(99, 35, 0, 255, 8, 8) + bounds: 792 280 8 8 + - image: solid-color(100, 35, 0, 255, 8, 8) + bounds: 800 280 8 8 + - image: solid-color(101, 35, 0, 255, 8, 8) + bounds: 808 280 8 8 + - image: solid-color(102, 35, 0, 255, 8, 8) + bounds: 816 280 8 8 + - image: solid-color(103, 35, 0, 255, 8, 8) + bounds: 824 280 8 8 + - image: solid-color(104, 35, 0, 255, 8, 8) + bounds: 832 280 8 8 + - image: solid-color(105, 35, 0, 255, 8, 8) + bounds: 840 280 8 8 + - image: solid-color(106, 35, 0, 255, 8, 8) + bounds: 848 280 8 8 + - image: solid-color(107, 35, 0, 255, 8, 8) + bounds: 856 280 8 8 + - image: solid-color(108, 35, 0, 255, 8, 8) + bounds: 864 280 8 8 + - image: solid-color(109, 35, 0, 255, 8, 8) + bounds: 872 280 8 8 + - image: solid-color(110, 35, 0, 255, 8, 8) + bounds: 880 280 8 8 + - image: solid-color(111, 35, 0, 255, 8, 8) + bounds: 888 280 8 8 + - image: solid-color(112, 35, 0, 255, 8, 8) + bounds: 896 280 8 8 + - image: solid-color(113, 35, 0, 255, 8, 8) + bounds: 904 280 8 8 + - image: solid-color(114, 35, 0, 255, 8, 8) + bounds: 912 280 8 8 + - image: solid-color(115, 35, 0, 255, 8, 8) + bounds: 920 280 8 8 + - image: solid-color(116, 35, 0, 255, 8, 8) + bounds: 928 280 8 8 + - image: solid-color(117, 35, 0, 255, 8, 8) + bounds: 936 280 8 8 + - image: solid-color(118, 35, 0, 255, 8, 8) + bounds: 944 280 8 8 + - image: solid-color(119, 35, 0, 255, 8, 8) + bounds: 952 280 8 8 + - image: solid-color(120, 35, 0, 255, 8, 8) + bounds: 960 280 8 8 + - image: solid-color(121, 35, 0, 255, 8, 8) + bounds: 968 280 8 8 + - image: solid-color(122, 35, 0, 255, 8, 8) + bounds: 976 280 8 8 + - image: solid-color(123, 35, 0, 255, 8, 8) + bounds: 984 280 8 8 + - image: solid-color(124, 35, 0, 255, 8, 8) + bounds: 992 280 8 8 + - image: solid-color(125, 35, 0, 255, 8, 8) + bounds: 1000 280 8 8 + - image: solid-color(126, 35, 0, 255, 8, 8) + bounds: 1008 280 8 8 + - image: solid-color(127, 35, 0, 255, 8, 8) + bounds: 1016 280 8 8 + - image: solid-color(0, 36, 0, 255, 8, 8) + bounds: 0 288 8 8 + - image: solid-color(1, 36, 0, 255, 8, 8) + bounds: 8 288 8 8 + - image: solid-color(2, 36, 0, 255, 8, 8) + bounds: 16 288 8 8 + - image: solid-color(3, 36, 0, 255, 8, 8) + bounds: 24 288 8 8 + - image: solid-color(4, 36, 0, 255, 8, 8) + bounds: 32 288 8 8 + - image: solid-color(5, 36, 0, 255, 8, 8) + bounds: 40 288 8 8 + - image: solid-color(6, 36, 0, 255, 8, 8) + bounds: 48 288 8 8 + - image: solid-color(7, 36, 0, 255, 8, 8) + bounds: 56 288 8 8 + - image: solid-color(8, 36, 0, 255, 8, 8) + bounds: 64 288 8 8 + - image: solid-color(9, 36, 0, 255, 8, 8) + bounds: 72 288 8 8 + - image: solid-color(10, 36, 0, 255, 8, 8) + bounds: 80 288 8 8 + - image: solid-color(11, 36, 0, 255, 8, 8) + bounds: 88 288 8 8 + - image: solid-color(12, 36, 0, 255, 8, 8) + bounds: 96 288 8 8 + - image: solid-color(13, 36, 0, 255, 8, 8) + bounds: 104 288 8 8 + - image: solid-color(14, 36, 0, 255, 8, 8) + bounds: 112 288 8 8 + - image: solid-color(15, 36, 0, 255, 8, 8) + bounds: 120 288 8 8 + - image: solid-color(16, 36, 0, 255, 8, 8) + bounds: 128 288 8 8 + - image: solid-color(17, 36, 0, 255, 8, 8) + bounds: 136 288 8 8 + - image: solid-color(18, 36, 0, 255, 8, 8) + bounds: 144 288 8 8 + - image: solid-color(19, 36, 0, 255, 8, 8) + bounds: 152 288 8 8 + - image: solid-color(20, 36, 0, 255, 8, 8) + bounds: 160 288 8 8 + - image: solid-color(21, 36, 0, 255, 8, 8) + bounds: 168 288 8 8 + - image: solid-color(22, 36, 0, 255, 8, 8) + bounds: 176 288 8 8 + - image: solid-color(23, 36, 0, 255, 8, 8) + bounds: 184 288 8 8 + - image: solid-color(24, 36, 0, 255, 8, 8) + bounds: 192 288 8 8 + - image: solid-color(25, 36, 0, 255, 8, 8) + bounds: 200 288 8 8 + - image: solid-color(26, 36, 0, 255, 8, 8) + bounds: 208 288 8 8 + - image: solid-color(27, 36, 0, 255, 8, 8) + bounds: 216 288 8 8 + - image: solid-color(28, 36, 0, 255, 8, 8) + bounds: 224 288 8 8 + - image: solid-color(29, 36, 0, 255, 8, 8) + bounds: 232 288 8 8 + - image: solid-color(30, 36, 0, 255, 8, 8) + bounds: 240 288 8 8 + - image: solid-color(31, 36, 0, 255, 8, 8) + bounds: 248 288 8 8 + - image: solid-color(32, 36, 0, 255, 8, 8) + bounds: 256 288 8 8 + - image: solid-color(33, 36, 0, 255, 8, 8) + bounds: 264 288 8 8 + - image: solid-color(34, 36, 0, 255, 8, 8) + bounds: 272 288 8 8 + - image: solid-color(35, 36, 0, 255, 8, 8) + bounds: 280 288 8 8 + - image: solid-color(36, 36, 0, 255, 8, 8) + bounds: 288 288 8 8 + - image: solid-color(37, 36, 0, 255, 8, 8) + bounds: 296 288 8 8 + - image: solid-color(38, 36, 0, 255, 8, 8) + bounds: 304 288 8 8 + - image: solid-color(39, 36, 0, 255, 8, 8) + bounds: 312 288 8 8 + - image: solid-color(40, 36, 0, 255, 8, 8) + bounds: 320 288 8 8 + - image: solid-color(41, 36, 0, 255, 8, 8) + bounds: 328 288 8 8 + - image: solid-color(42, 36, 0, 255, 8, 8) + bounds: 336 288 8 8 + - image: solid-color(43, 36, 0, 255, 8, 8) + bounds: 344 288 8 8 + - image: solid-color(44, 36, 0, 255, 8, 8) + bounds: 352 288 8 8 + - image: solid-color(45, 36, 0, 255, 8, 8) + bounds: 360 288 8 8 + - image: solid-color(46, 36, 0, 255, 8, 8) + bounds: 368 288 8 8 + - image: solid-color(47, 36, 0, 255, 8, 8) + bounds: 376 288 8 8 + - image: solid-color(48, 36, 0, 255, 8, 8) + bounds: 384 288 8 8 + - image: solid-color(49, 36, 0, 255, 8, 8) + bounds: 392 288 8 8 + - image: solid-color(50, 36, 0, 255, 8, 8) + bounds: 400 288 8 8 + - image: solid-color(51, 36, 0, 255, 8, 8) + bounds: 408 288 8 8 + - image: solid-color(52, 36, 0, 255, 8, 8) + bounds: 416 288 8 8 + - image: solid-color(53, 36, 0, 255, 8, 8) + bounds: 424 288 8 8 + - image: solid-color(54, 36, 0, 255, 8, 8) + bounds: 432 288 8 8 + - image: solid-color(55, 36, 0, 255, 8, 8) + bounds: 440 288 8 8 + - image: solid-color(56, 36, 0, 255, 8, 8) + bounds: 448 288 8 8 + - image: solid-color(57, 36, 0, 255, 8, 8) + bounds: 456 288 8 8 + - image: solid-color(58, 36, 0, 255, 8, 8) + bounds: 464 288 8 8 + - image: solid-color(59, 36, 0, 255, 8, 8) + bounds: 472 288 8 8 + - image: solid-color(60, 36, 0, 255, 8, 8) + bounds: 480 288 8 8 + - image: solid-color(61, 36, 0, 255, 8, 8) + bounds: 488 288 8 8 + - image: solid-color(62, 36, 0, 255, 8, 8) + bounds: 496 288 8 8 + - image: solid-color(63, 36, 0, 255, 8, 8) + bounds: 504 288 8 8 + - image: solid-color(64, 36, 0, 255, 8, 8) + bounds: 512 288 8 8 + - image: solid-color(65, 36, 0, 255, 8, 8) + bounds: 520 288 8 8 + - image: solid-color(66, 36, 0, 255, 8, 8) + bounds: 528 288 8 8 + - image: solid-color(67, 36, 0, 255, 8, 8) + bounds: 536 288 8 8 + - image: solid-color(68, 36, 0, 255, 8, 8) + bounds: 544 288 8 8 + - image: solid-color(69, 36, 0, 255, 8, 8) + bounds: 552 288 8 8 + - image: solid-color(70, 36, 0, 255, 8, 8) + bounds: 560 288 8 8 + - image: solid-color(71, 36, 0, 255, 8, 8) + bounds: 568 288 8 8 + - image: solid-color(72, 36, 0, 255, 8, 8) + bounds: 576 288 8 8 + - image: solid-color(73, 36, 0, 255, 8, 8) + bounds: 584 288 8 8 + - image: solid-color(74, 36, 0, 255, 8, 8) + bounds: 592 288 8 8 + - image: solid-color(75, 36, 0, 255, 8, 8) + bounds: 600 288 8 8 + - image: solid-color(76, 36, 0, 255, 8, 8) + bounds: 608 288 8 8 + - image: solid-color(77, 36, 0, 255, 8, 8) + bounds: 616 288 8 8 + - image: solid-color(78, 36, 0, 255, 8, 8) + bounds: 624 288 8 8 + - image: solid-color(79, 36, 0, 255, 8, 8) + bounds: 632 288 8 8 + - image: solid-color(80, 36, 0, 255, 8, 8) + bounds: 640 288 8 8 + - image: solid-color(81, 36, 0, 255, 8, 8) + bounds: 648 288 8 8 + - image: solid-color(82, 36, 0, 255, 8, 8) + bounds: 656 288 8 8 + - image: solid-color(83, 36, 0, 255, 8, 8) + bounds: 664 288 8 8 + - image: solid-color(84, 36, 0, 255, 8, 8) + bounds: 672 288 8 8 + - image: solid-color(85, 36, 0, 255, 8, 8) + bounds: 680 288 8 8 + - image: solid-color(86, 36, 0, 255, 8, 8) + bounds: 688 288 8 8 + - image: solid-color(87, 36, 0, 255, 8, 8) + bounds: 696 288 8 8 + - image: solid-color(88, 36, 0, 255, 8, 8) + bounds: 704 288 8 8 + - image: solid-color(89, 36, 0, 255, 8, 8) + bounds: 712 288 8 8 + - image: solid-color(90, 36, 0, 255, 8, 8) + bounds: 720 288 8 8 + - image: solid-color(91, 36, 0, 255, 8, 8) + bounds: 728 288 8 8 + - image: solid-color(92, 36, 0, 255, 8, 8) + bounds: 736 288 8 8 + - image: solid-color(93, 36, 0, 255, 8, 8) + bounds: 744 288 8 8 + - image: solid-color(94, 36, 0, 255, 8, 8) + bounds: 752 288 8 8 + - image: solid-color(95, 36, 0, 255, 8, 8) + bounds: 760 288 8 8 + - image: solid-color(96, 36, 0, 255, 8, 8) + bounds: 768 288 8 8 + - image: solid-color(97, 36, 0, 255, 8, 8) + bounds: 776 288 8 8 + - image: solid-color(98, 36, 0, 255, 8, 8) + bounds: 784 288 8 8 + - image: solid-color(99, 36, 0, 255, 8, 8) + bounds: 792 288 8 8 + - image: solid-color(100, 36, 0, 255, 8, 8) + bounds: 800 288 8 8 + - image: solid-color(101, 36, 0, 255, 8, 8) + bounds: 808 288 8 8 + - image: solid-color(102, 36, 0, 255, 8, 8) + bounds: 816 288 8 8 + - image: solid-color(103, 36, 0, 255, 8, 8) + bounds: 824 288 8 8 + - image: solid-color(104, 36, 0, 255, 8, 8) + bounds: 832 288 8 8 + - image: solid-color(105, 36, 0, 255, 8, 8) + bounds: 840 288 8 8 + - image: solid-color(106, 36, 0, 255, 8, 8) + bounds: 848 288 8 8 + - image: solid-color(107, 36, 0, 255, 8, 8) + bounds: 856 288 8 8 + - image: solid-color(108, 36, 0, 255, 8, 8) + bounds: 864 288 8 8 + - image: solid-color(109, 36, 0, 255, 8, 8) + bounds: 872 288 8 8 + - image: solid-color(110, 36, 0, 255, 8, 8) + bounds: 880 288 8 8 + - image: solid-color(111, 36, 0, 255, 8, 8) + bounds: 888 288 8 8 + - image: solid-color(112, 36, 0, 255, 8, 8) + bounds: 896 288 8 8 + - image: solid-color(113, 36, 0, 255, 8, 8) + bounds: 904 288 8 8 + - image: solid-color(114, 36, 0, 255, 8, 8) + bounds: 912 288 8 8 + - image: solid-color(115, 36, 0, 255, 8, 8) + bounds: 920 288 8 8 + - image: solid-color(116, 36, 0, 255, 8, 8) + bounds: 928 288 8 8 + - image: solid-color(117, 36, 0, 255, 8, 8) + bounds: 936 288 8 8 + - image: solid-color(118, 36, 0, 255, 8, 8) + bounds: 944 288 8 8 + - image: solid-color(119, 36, 0, 255, 8, 8) + bounds: 952 288 8 8 + - image: solid-color(120, 36, 0, 255, 8, 8) + bounds: 960 288 8 8 + - image: solid-color(121, 36, 0, 255, 8, 8) + bounds: 968 288 8 8 + - image: solid-color(122, 36, 0, 255, 8, 8) + bounds: 976 288 8 8 + - image: solid-color(123, 36, 0, 255, 8, 8) + bounds: 984 288 8 8 + - image: solid-color(124, 36, 0, 255, 8, 8) + bounds: 992 288 8 8 + - image: solid-color(125, 36, 0, 255, 8, 8) + bounds: 1000 288 8 8 + - image: solid-color(126, 36, 0, 255, 8, 8) + bounds: 1008 288 8 8 + - image: solid-color(127, 36, 0, 255, 8, 8) + bounds: 1016 288 8 8 + - image: solid-color(0, 37, 0, 255, 8, 8) + bounds: 0 296 8 8 + - image: solid-color(1, 37, 0, 255, 8, 8) + bounds: 8 296 8 8 + - image: solid-color(2, 37, 0, 255, 8, 8) + bounds: 16 296 8 8 + - image: solid-color(3, 37, 0, 255, 8, 8) + bounds: 24 296 8 8 + - image: solid-color(4, 37, 0, 255, 8, 8) + bounds: 32 296 8 8 + - image: solid-color(5, 37, 0, 255, 8, 8) + bounds: 40 296 8 8 + - image: solid-color(6, 37, 0, 255, 8, 8) + bounds: 48 296 8 8 + - image: solid-color(7, 37, 0, 255, 8, 8) + bounds: 56 296 8 8 + - image: solid-color(8, 37, 0, 255, 8, 8) + bounds: 64 296 8 8 + - image: solid-color(9, 37, 0, 255, 8, 8) + bounds: 72 296 8 8 + - image: solid-color(10, 37, 0, 255, 8, 8) + bounds: 80 296 8 8 + - image: solid-color(11, 37, 0, 255, 8, 8) + bounds: 88 296 8 8 + - image: solid-color(12, 37, 0, 255, 8, 8) + bounds: 96 296 8 8 + - image: solid-color(13, 37, 0, 255, 8, 8) + bounds: 104 296 8 8 + - image: solid-color(14, 37, 0, 255, 8, 8) + bounds: 112 296 8 8 + - image: solid-color(15, 37, 0, 255, 8, 8) + bounds: 120 296 8 8 + - image: solid-color(16, 37, 0, 255, 8, 8) + bounds: 128 296 8 8 + - image: solid-color(17, 37, 0, 255, 8, 8) + bounds: 136 296 8 8 + - image: solid-color(18, 37, 0, 255, 8, 8) + bounds: 144 296 8 8 + - image: solid-color(19, 37, 0, 255, 8, 8) + bounds: 152 296 8 8 + - image: solid-color(20, 37, 0, 255, 8, 8) + bounds: 160 296 8 8 + - image: solid-color(21, 37, 0, 255, 8, 8) + bounds: 168 296 8 8 + - image: solid-color(22, 37, 0, 255, 8, 8) + bounds: 176 296 8 8 + - image: solid-color(23, 37, 0, 255, 8, 8) + bounds: 184 296 8 8 + - image: solid-color(24, 37, 0, 255, 8, 8) + bounds: 192 296 8 8 + - image: solid-color(25, 37, 0, 255, 8, 8) + bounds: 200 296 8 8 + - image: solid-color(26, 37, 0, 255, 8, 8) + bounds: 208 296 8 8 + - image: solid-color(27, 37, 0, 255, 8, 8) + bounds: 216 296 8 8 + - image: solid-color(28, 37, 0, 255, 8, 8) + bounds: 224 296 8 8 + - image: solid-color(29, 37, 0, 255, 8, 8) + bounds: 232 296 8 8 + - image: solid-color(30, 37, 0, 255, 8, 8) + bounds: 240 296 8 8 + - image: solid-color(31, 37, 0, 255, 8, 8) + bounds: 248 296 8 8 + - image: solid-color(32, 37, 0, 255, 8, 8) + bounds: 256 296 8 8 + - image: solid-color(33, 37, 0, 255, 8, 8) + bounds: 264 296 8 8 + - image: solid-color(34, 37, 0, 255, 8, 8) + bounds: 272 296 8 8 + - image: solid-color(35, 37, 0, 255, 8, 8) + bounds: 280 296 8 8 + - image: solid-color(36, 37, 0, 255, 8, 8) + bounds: 288 296 8 8 + - image: solid-color(37, 37, 0, 255, 8, 8) + bounds: 296 296 8 8 + - image: solid-color(38, 37, 0, 255, 8, 8) + bounds: 304 296 8 8 + - image: solid-color(39, 37, 0, 255, 8, 8) + bounds: 312 296 8 8 + - image: solid-color(40, 37, 0, 255, 8, 8) + bounds: 320 296 8 8 + - image: solid-color(41, 37, 0, 255, 8, 8) + bounds: 328 296 8 8 + - image: solid-color(42, 37, 0, 255, 8, 8) + bounds: 336 296 8 8 + - image: solid-color(43, 37, 0, 255, 8, 8) + bounds: 344 296 8 8 + - image: solid-color(44, 37, 0, 255, 8, 8) + bounds: 352 296 8 8 + - image: solid-color(45, 37, 0, 255, 8, 8) + bounds: 360 296 8 8 + - image: solid-color(46, 37, 0, 255, 8, 8) + bounds: 368 296 8 8 + - image: solid-color(47, 37, 0, 255, 8, 8) + bounds: 376 296 8 8 + - image: solid-color(48, 37, 0, 255, 8, 8) + bounds: 384 296 8 8 + - image: solid-color(49, 37, 0, 255, 8, 8) + bounds: 392 296 8 8 + - image: solid-color(50, 37, 0, 255, 8, 8) + bounds: 400 296 8 8 + - image: solid-color(51, 37, 0, 255, 8, 8) + bounds: 408 296 8 8 + - image: solid-color(52, 37, 0, 255, 8, 8) + bounds: 416 296 8 8 + - image: solid-color(53, 37, 0, 255, 8, 8) + bounds: 424 296 8 8 + - image: solid-color(54, 37, 0, 255, 8, 8) + bounds: 432 296 8 8 + - image: solid-color(55, 37, 0, 255, 8, 8) + bounds: 440 296 8 8 + - image: solid-color(56, 37, 0, 255, 8, 8) + bounds: 448 296 8 8 + - image: solid-color(57, 37, 0, 255, 8, 8) + bounds: 456 296 8 8 + - image: solid-color(58, 37, 0, 255, 8, 8) + bounds: 464 296 8 8 + - image: solid-color(59, 37, 0, 255, 8, 8) + bounds: 472 296 8 8 + - image: solid-color(60, 37, 0, 255, 8, 8) + bounds: 480 296 8 8 + - image: solid-color(61, 37, 0, 255, 8, 8) + bounds: 488 296 8 8 + - image: solid-color(62, 37, 0, 255, 8, 8) + bounds: 496 296 8 8 + - image: solid-color(63, 37, 0, 255, 8, 8) + bounds: 504 296 8 8 + - image: solid-color(64, 37, 0, 255, 8, 8) + bounds: 512 296 8 8 + - image: solid-color(65, 37, 0, 255, 8, 8) + bounds: 520 296 8 8 + - image: solid-color(66, 37, 0, 255, 8, 8) + bounds: 528 296 8 8 + - image: solid-color(67, 37, 0, 255, 8, 8) + bounds: 536 296 8 8 + - image: solid-color(68, 37, 0, 255, 8, 8) + bounds: 544 296 8 8 + - image: solid-color(69, 37, 0, 255, 8, 8) + bounds: 552 296 8 8 + - image: solid-color(70, 37, 0, 255, 8, 8) + bounds: 560 296 8 8 + - image: solid-color(71, 37, 0, 255, 8, 8) + bounds: 568 296 8 8 + - image: solid-color(72, 37, 0, 255, 8, 8) + bounds: 576 296 8 8 + - image: solid-color(73, 37, 0, 255, 8, 8) + bounds: 584 296 8 8 + - image: solid-color(74, 37, 0, 255, 8, 8) + bounds: 592 296 8 8 + - image: solid-color(75, 37, 0, 255, 8, 8) + bounds: 600 296 8 8 + - image: solid-color(76, 37, 0, 255, 8, 8) + bounds: 608 296 8 8 + - image: solid-color(77, 37, 0, 255, 8, 8) + bounds: 616 296 8 8 + - image: solid-color(78, 37, 0, 255, 8, 8) + bounds: 624 296 8 8 + - image: solid-color(79, 37, 0, 255, 8, 8) + bounds: 632 296 8 8 + - image: solid-color(80, 37, 0, 255, 8, 8) + bounds: 640 296 8 8 + - image: solid-color(81, 37, 0, 255, 8, 8) + bounds: 648 296 8 8 + - image: solid-color(82, 37, 0, 255, 8, 8) + bounds: 656 296 8 8 + - image: solid-color(83, 37, 0, 255, 8, 8) + bounds: 664 296 8 8 + - image: solid-color(84, 37, 0, 255, 8, 8) + bounds: 672 296 8 8 + - image: solid-color(85, 37, 0, 255, 8, 8) + bounds: 680 296 8 8 + - image: solid-color(86, 37, 0, 255, 8, 8) + bounds: 688 296 8 8 + - image: solid-color(87, 37, 0, 255, 8, 8) + bounds: 696 296 8 8 + - image: solid-color(88, 37, 0, 255, 8, 8) + bounds: 704 296 8 8 + - image: solid-color(89, 37, 0, 255, 8, 8) + bounds: 712 296 8 8 + - image: solid-color(90, 37, 0, 255, 8, 8) + bounds: 720 296 8 8 + - image: solid-color(91, 37, 0, 255, 8, 8) + bounds: 728 296 8 8 + - image: solid-color(92, 37, 0, 255, 8, 8) + bounds: 736 296 8 8 + - image: solid-color(93, 37, 0, 255, 8, 8) + bounds: 744 296 8 8 + - image: solid-color(94, 37, 0, 255, 8, 8) + bounds: 752 296 8 8 + - image: solid-color(95, 37, 0, 255, 8, 8) + bounds: 760 296 8 8 + - image: solid-color(96, 37, 0, 255, 8, 8) + bounds: 768 296 8 8 + - image: solid-color(97, 37, 0, 255, 8, 8) + bounds: 776 296 8 8 + - image: solid-color(98, 37, 0, 255, 8, 8) + bounds: 784 296 8 8 + - image: solid-color(99, 37, 0, 255, 8, 8) + bounds: 792 296 8 8 + - image: solid-color(100, 37, 0, 255, 8, 8) + bounds: 800 296 8 8 + - image: solid-color(101, 37, 0, 255, 8, 8) + bounds: 808 296 8 8 + - image: solid-color(102, 37, 0, 255, 8, 8) + bounds: 816 296 8 8 + - image: solid-color(103, 37, 0, 255, 8, 8) + bounds: 824 296 8 8 + - image: solid-color(104, 37, 0, 255, 8, 8) + bounds: 832 296 8 8 + - image: solid-color(105, 37, 0, 255, 8, 8) + bounds: 840 296 8 8 + - image: solid-color(106, 37, 0, 255, 8, 8) + bounds: 848 296 8 8 + - image: solid-color(107, 37, 0, 255, 8, 8) + bounds: 856 296 8 8 + - image: solid-color(108, 37, 0, 255, 8, 8) + bounds: 864 296 8 8 + - image: solid-color(109, 37, 0, 255, 8, 8) + bounds: 872 296 8 8 + - image: solid-color(110, 37, 0, 255, 8, 8) + bounds: 880 296 8 8 + - image: solid-color(111, 37, 0, 255, 8, 8) + bounds: 888 296 8 8 + - image: solid-color(112, 37, 0, 255, 8, 8) + bounds: 896 296 8 8 + - image: solid-color(113, 37, 0, 255, 8, 8) + bounds: 904 296 8 8 + - image: solid-color(114, 37, 0, 255, 8, 8) + bounds: 912 296 8 8 + - image: solid-color(115, 37, 0, 255, 8, 8) + bounds: 920 296 8 8 + - image: solid-color(116, 37, 0, 255, 8, 8) + bounds: 928 296 8 8 + - image: solid-color(117, 37, 0, 255, 8, 8) + bounds: 936 296 8 8 + - image: solid-color(118, 37, 0, 255, 8, 8) + bounds: 944 296 8 8 + - image: solid-color(119, 37, 0, 255, 8, 8) + bounds: 952 296 8 8 + - image: solid-color(120, 37, 0, 255, 8, 8) + bounds: 960 296 8 8 + - image: solid-color(121, 37, 0, 255, 8, 8) + bounds: 968 296 8 8 + - image: solid-color(122, 37, 0, 255, 8, 8) + bounds: 976 296 8 8 + - image: solid-color(123, 37, 0, 255, 8, 8) + bounds: 984 296 8 8 + - image: solid-color(124, 37, 0, 255, 8, 8) + bounds: 992 296 8 8 + - image: solid-color(125, 37, 0, 255, 8, 8) + bounds: 1000 296 8 8 + - image: solid-color(126, 37, 0, 255, 8, 8) + bounds: 1008 296 8 8 + - image: solid-color(127, 37, 0, 255, 8, 8) + bounds: 1016 296 8 8 + - image: solid-color(0, 38, 0, 255, 8, 8) + bounds: 0 304 8 8 + - image: solid-color(1, 38, 0, 255, 8, 8) + bounds: 8 304 8 8 + - image: solid-color(2, 38, 0, 255, 8, 8) + bounds: 16 304 8 8 + - image: solid-color(3, 38, 0, 255, 8, 8) + bounds: 24 304 8 8 + - image: solid-color(4, 38, 0, 255, 8, 8) + bounds: 32 304 8 8 + - image: solid-color(5, 38, 0, 255, 8, 8) + bounds: 40 304 8 8 + - image: solid-color(6, 38, 0, 255, 8, 8) + bounds: 48 304 8 8 + - image: solid-color(7, 38, 0, 255, 8, 8) + bounds: 56 304 8 8 + - image: solid-color(8, 38, 0, 255, 8, 8) + bounds: 64 304 8 8 + - image: solid-color(9, 38, 0, 255, 8, 8) + bounds: 72 304 8 8 + - image: solid-color(10, 38, 0, 255, 8, 8) + bounds: 80 304 8 8 + - image: solid-color(11, 38, 0, 255, 8, 8) + bounds: 88 304 8 8 + - image: solid-color(12, 38, 0, 255, 8, 8) + bounds: 96 304 8 8 + - image: solid-color(13, 38, 0, 255, 8, 8) + bounds: 104 304 8 8 + - image: solid-color(14, 38, 0, 255, 8, 8) + bounds: 112 304 8 8 + - image: solid-color(15, 38, 0, 255, 8, 8) + bounds: 120 304 8 8 + - image: solid-color(16, 38, 0, 255, 8, 8) + bounds: 128 304 8 8 + - image: solid-color(17, 38, 0, 255, 8, 8) + bounds: 136 304 8 8 + - image: solid-color(18, 38, 0, 255, 8, 8) + bounds: 144 304 8 8 + - image: solid-color(19, 38, 0, 255, 8, 8) + bounds: 152 304 8 8 + - image: solid-color(20, 38, 0, 255, 8, 8) + bounds: 160 304 8 8 + - image: solid-color(21, 38, 0, 255, 8, 8) + bounds: 168 304 8 8 + - image: solid-color(22, 38, 0, 255, 8, 8) + bounds: 176 304 8 8 + - image: solid-color(23, 38, 0, 255, 8, 8) + bounds: 184 304 8 8 + - image: solid-color(24, 38, 0, 255, 8, 8) + bounds: 192 304 8 8 + - image: solid-color(25, 38, 0, 255, 8, 8) + bounds: 200 304 8 8 + - image: solid-color(26, 38, 0, 255, 8, 8) + bounds: 208 304 8 8 + - image: solid-color(27, 38, 0, 255, 8, 8) + bounds: 216 304 8 8 + - image: solid-color(28, 38, 0, 255, 8, 8) + bounds: 224 304 8 8 + - image: solid-color(29, 38, 0, 255, 8, 8) + bounds: 232 304 8 8 + - image: solid-color(30, 38, 0, 255, 8, 8) + bounds: 240 304 8 8 + - image: solid-color(31, 38, 0, 255, 8, 8) + bounds: 248 304 8 8 + - image: solid-color(32, 38, 0, 255, 8, 8) + bounds: 256 304 8 8 + - image: solid-color(33, 38, 0, 255, 8, 8) + bounds: 264 304 8 8 + - image: solid-color(34, 38, 0, 255, 8, 8) + bounds: 272 304 8 8 + - image: solid-color(35, 38, 0, 255, 8, 8) + bounds: 280 304 8 8 + - image: solid-color(36, 38, 0, 255, 8, 8) + bounds: 288 304 8 8 + - image: solid-color(37, 38, 0, 255, 8, 8) + bounds: 296 304 8 8 + - image: solid-color(38, 38, 0, 255, 8, 8) + bounds: 304 304 8 8 + - image: solid-color(39, 38, 0, 255, 8, 8) + bounds: 312 304 8 8 + - image: solid-color(40, 38, 0, 255, 8, 8) + bounds: 320 304 8 8 + - image: solid-color(41, 38, 0, 255, 8, 8) + bounds: 328 304 8 8 + - image: solid-color(42, 38, 0, 255, 8, 8) + bounds: 336 304 8 8 + - image: solid-color(43, 38, 0, 255, 8, 8) + bounds: 344 304 8 8 + - image: solid-color(44, 38, 0, 255, 8, 8) + bounds: 352 304 8 8 + - image: solid-color(45, 38, 0, 255, 8, 8) + bounds: 360 304 8 8 + - image: solid-color(46, 38, 0, 255, 8, 8) + bounds: 368 304 8 8 + - image: solid-color(47, 38, 0, 255, 8, 8) + bounds: 376 304 8 8 + - image: solid-color(48, 38, 0, 255, 8, 8) + bounds: 384 304 8 8 + - image: solid-color(49, 38, 0, 255, 8, 8) + bounds: 392 304 8 8 + - image: solid-color(50, 38, 0, 255, 8, 8) + bounds: 400 304 8 8 + - image: solid-color(51, 38, 0, 255, 8, 8) + bounds: 408 304 8 8 + - image: solid-color(52, 38, 0, 255, 8, 8) + bounds: 416 304 8 8 + - image: solid-color(53, 38, 0, 255, 8, 8) + bounds: 424 304 8 8 + - image: solid-color(54, 38, 0, 255, 8, 8) + bounds: 432 304 8 8 + - image: solid-color(55, 38, 0, 255, 8, 8) + bounds: 440 304 8 8 + - image: solid-color(56, 38, 0, 255, 8, 8) + bounds: 448 304 8 8 + - image: solid-color(57, 38, 0, 255, 8, 8) + bounds: 456 304 8 8 + - image: solid-color(58, 38, 0, 255, 8, 8) + bounds: 464 304 8 8 + - image: solid-color(59, 38, 0, 255, 8, 8) + bounds: 472 304 8 8 + - image: solid-color(60, 38, 0, 255, 8, 8) + bounds: 480 304 8 8 + - image: solid-color(61, 38, 0, 255, 8, 8) + bounds: 488 304 8 8 + - image: solid-color(62, 38, 0, 255, 8, 8) + bounds: 496 304 8 8 + - image: solid-color(63, 38, 0, 255, 8, 8) + bounds: 504 304 8 8 + - image: solid-color(64, 38, 0, 255, 8, 8) + bounds: 512 304 8 8 + - image: solid-color(65, 38, 0, 255, 8, 8) + bounds: 520 304 8 8 + - image: solid-color(66, 38, 0, 255, 8, 8) + bounds: 528 304 8 8 + - image: solid-color(67, 38, 0, 255, 8, 8) + bounds: 536 304 8 8 + - image: solid-color(68, 38, 0, 255, 8, 8) + bounds: 544 304 8 8 + - image: solid-color(69, 38, 0, 255, 8, 8) + bounds: 552 304 8 8 + - image: solid-color(70, 38, 0, 255, 8, 8) + bounds: 560 304 8 8 + - image: solid-color(71, 38, 0, 255, 8, 8) + bounds: 568 304 8 8 + - image: solid-color(72, 38, 0, 255, 8, 8) + bounds: 576 304 8 8 + - image: solid-color(73, 38, 0, 255, 8, 8) + bounds: 584 304 8 8 + - image: solid-color(74, 38, 0, 255, 8, 8) + bounds: 592 304 8 8 + - image: solid-color(75, 38, 0, 255, 8, 8) + bounds: 600 304 8 8 + - image: solid-color(76, 38, 0, 255, 8, 8) + bounds: 608 304 8 8 + - image: solid-color(77, 38, 0, 255, 8, 8) + bounds: 616 304 8 8 + - image: solid-color(78, 38, 0, 255, 8, 8) + bounds: 624 304 8 8 + - image: solid-color(79, 38, 0, 255, 8, 8) + bounds: 632 304 8 8 + - image: solid-color(80, 38, 0, 255, 8, 8) + bounds: 640 304 8 8 + - image: solid-color(81, 38, 0, 255, 8, 8) + bounds: 648 304 8 8 + - image: solid-color(82, 38, 0, 255, 8, 8) + bounds: 656 304 8 8 + - image: solid-color(83, 38, 0, 255, 8, 8) + bounds: 664 304 8 8 + - image: solid-color(84, 38, 0, 255, 8, 8) + bounds: 672 304 8 8 + - image: solid-color(85, 38, 0, 255, 8, 8) + bounds: 680 304 8 8 + - image: solid-color(86, 38, 0, 255, 8, 8) + bounds: 688 304 8 8 + - image: solid-color(87, 38, 0, 255, 8, 8) + bounds: 696 304 8 8 + - image: solid-color(88, 38, 0, 255, 8, 8) + bounds: 704 304 8 8 + - image: solid-color(89, 38, 0, 255, 8, 8) + bounds: 712 304 8 8 + - image: solid-color(90, 38, 0, 255, 8, 8) + bounds: 720 304 8 8 + - image: solid-color(91, 38, 0, 255, 8, 8) + bounds: 728 304 8 8 + - image: solid-color(92, 38, 0, 255, 8, 8) + bounds: 736 304 8 8 + - image: solid-color(93, 38, 0, 255, 8, 8) + bounds: 744 304 8 8 + - image: solid-color(94, 38, 0, 255, 8, 8) + bounds: 752 304 8 8 + - image: solid-color(95, 38, 0, 255, 8, 8) + bounds: 760 304 8 8 + - image: solid-color(96, 38, 0, 255, 8, 8) + bounds: 768 304 8 8 + - image: solid-color(97, 38, 0, 255, 8, 8) + bounds: 776 304 8 8 + - image: solid-color(98, 38, 0, 255, 8, 8) + bounds: 784 304 8 8 + - image: solid-color(99, 38, 0, 255, 8, 8) + bounds: 792 304 8 8 + - image: solid-color(100, 38, 0, 255, 8, 8) + bounds: 800 304 8 8 + - image: solid-color(101, 38, 0, 255, 8, 8) + bounds: 808 304 8 8 + - image: solid-color(102, 38, 0, 255, 8, 8) + bounds: 816 304 8 8 + - image: solid-color(103, 38, 0, 255, 8, 8) + bounds: 824 304 8 8 + - image: solid-color(104, 38, 0, 255, 8, 8) + bounds: 832 304 8 8 + - image: solid-color(105, 38, 0, 255, 8, 8) + bounds: 840 304 8 8 + - image: solid-color(106, 38, 0, 255, 8, 8) + bounds: 848 304 8 8 + - image: solid-color(107, 38, 0, 255, 8, 8) + bounds: 856 304 8 8 + - image: solid-color(108, 38, 0, 255, 8, 8) + bounds: 864 304 8 8 + - image: solid-color(109, 38, 0, 255, 8, 8) + bounds: 872 304 8 8 + - image: solid-color(110, 38, 0, 255, 8, 8) + bounds: 880 304 8 8 + - image: solid-color(111, 38, 0, 255, 8, 8) + bounds: 888 304 8 8 + - image: solid-color(112, 38, 0, 255, 8, 8) + bounds: 896 304 8 8 + - image: solid-color(113, 38, 0, 255, 8, 8) + bounds: 904 304 8 8 + - image: solid-color(114, 38, 0, 255, 8, 8) + bounds: 912 304 8 8 + - image: solid-color(115, 38, 0, 255, 8, 8) + bounds: 920 304 8 8 + - image: solid-color(116, 38, 0, 255, 8, 8) + bounds: 928 304 8 8 + - image: solid-color(117, 38, 0, 255, 8, 8) + bounds: 936 304 8 8 + - image: solid-color(118, 38, 0, 255, 8, 8) + bounds: 944 304 8 8 + - image: solid-color(119, 38, 0, 255, 8, 8) + bounds: 952 304 8 8 + - image: solid-color(120, 38, 0, 255, 8, 8) + bounds: 960 304 8 8 + - image: solid-color(121, 38, 0, 255, 8, 8) + bounds: 968 304 8 8 + - image: solid-color(122, 38, 0, 255, 8, 8) + bounds: 976 304 8 8 + - image: solid-color(123, 38, 0, 255, 8, 8) + bounds: 984 304 8 8 + - image: solid-color(124, 38, 0, 255, 8, 8) + bounds: 992 304 8 8 + - image: solid-color(125, 38, 0, 255, 8, 8) + bounds: 1000 304 8 8 + - image: solid-color(126, 38, 0, 255, 8, 8) + bounds: 1008 304 8 8 + - image: solid-color(127, 38, 0, 255, 8, 8) + bounds: 1016 304 8 8 + - image: solid-color(0, 39, 0, 255, 8, 8) + bounds: 0 312 8 8 + - image: solid-color(1, 39, 0, 255, 8, 8) + bounds: 8 312 8 8 + - image: solid-color(2, 39, 0, 255, 8, 8) + bounds: 16 312 8 8 + - image: solid-color(3, 39, 0, 255, 8, 8) + bounds: 24 312 8 8 + - image: solid-color(4, 39, 0, 255, 8, 8) + bounds: 32 312 8 8 + - image: solid-color(5, 39, 0, 255, 8, 8) + bounds: 40 312 8 8 + - image: solid-color(6, 39, 0, 255, 8, 8) + bounds: 48 312 8 8 + - image: solid-color(7, 39, 0, 255, 8, 8) + bounds: 56 312 8 8 + - image: solid-color(8, 39, 0, 255, 8, 8) + bounds: 64 312 8 8 + - image: solid-color(9, 39, 0, 255, 8, 8) + bounds: 72 312 8 8 + - image: solid-color(10, 39, 0, 255, 8, 8) + bounds: 80 312 8 8 + - image: solid-color(11, 39, 0, 255, 8, 8) + bounds: 88 312 8 8 + - image: solid-color(12, 39, 0, 255, 8, 8) + bounds: 96 312 8 8 + - image: solid-color(13, 39, 0, 255, 8, 8) + bounds: 104 312 8 8 + - image: solid-color(14, 39, 0, 255, 8, 8) + bounds: 112 312 8 8 + - image: solid-color(15, 39, 0, 255, 8, 8) + bounds: 120 312 8 8 + - image: solid-color(16, 39, 0, 255, 8, 8) + bounds: 128 312 8 8 + - image: solid-color(17, 39, 0, 255, 8, 8) + bounds: 136 312 8 8 + - image: solid-color(18, 39, 0, 255, 8, 8) + bounds: 144 312 8 8 + - image: solid-color(19, 39, 0, 255, 8, 8) + bounds: 152 312 8 8 + - image: solid-color(20, 39, 0, 255, 8, 8) + bounds: 160 312 8 8 + - image: solid-color(21, 39, 0, 255, 8, 8) + bounds: 168 312 8 8 + - image: solid-color(22, 39, 0, 255, 8, 8) + bounds: 176 312 8 8 + - image: solid-color(23, 39, 0, 255, 8, 8) + bounds: 184 312 8 8 + - image: solid-color(24, 39, 0, 255, 8, 8) + bounds: 192 312 8 8 + - image: solid-color(25, 39, 0, 255, 8, 8) + bounds: 200 312 8 8 + - image: solid-color(26, 39, 0, 255, 8, 8) + bounds: 208 312 8 8 + - image: solid-color(27, 39, 0, 255, 8, 8) + bounds: 216 312 8 8 + - image: solid-color(28, 39, 0, 255, 8, 8) + bounds: 224 312 8 8 + - image: solid-color(29, 39, 0, 255, 8, 8) + bounds: 232 312 8 8 + - image: solid-color(30, 39, 0, 255, 8, 8) + bounds: 240 312 8 8 + - image: solid-color(31, 39, 0, 255, 8, 8) + bounds: 248 312 8 8 + - image: solid-color(32, 39, 0, 255, 8, 8) + bounds: 256 312 8 8 + - image: solid-color(33, 39, 0, 255, 8, 8) + bounds: 264 312 8 8 + - image: solid-color(34, 39, 0, 255, 8, 8) + bounds: 272 312 8 8 + - image: solid-color(35, 39, 0, 255, 8, 8) + bounds: 280 312 8 8 + - image: solid-color(36, 39, 0, 255, 8, 8) + bounds: 288 312 8 8 + - image: solid-color(37, 39, 0, 255, 8, 8) + bounds: 296 312 8 8 + - image: solid-color(38, 39, 0, 255, 8, 8) + bounds: 304 312 8 8 + - image: solid-color(39, 39, 0, 255, 8, 8) + bounds: 312 312 8 8 + - image: solid-color(40, 39, 0, 255, 8, 8) + bounds: 320 312 8 8 + - image: solid-color(41, 39, 0, 255, 8, 8) + bounds: 328 312 8 8 + - image: solid-color(42, 39, 0, 255, 8, 8) + bounds: 336 312 8 8 + - image: solid-color(43, 39, 0, 255, 8, 8) + bounds: 344 312 8 8 + - image: solid-color(44, 39, 0, 255, 8, 8) + bounds: 352 312 8 8 + - image: solid-color(45, 39, 0, 255, 8, 8) + bounds: 360 312 8 8 + - image: solid-color(46, 39, 0, 255, 8, 8) + bounds: 368 312 8 8 + - image: solid-color(47, 39, 0, 255, 8, 8) + bounds: 376 312 8 8 + - image: solid-color(48, 39, 0, 255, 8, 8) + bounds: 384 312 8 8 + - image: solid-color(49, 39, 0, 255, 8, 8) + bounds: 392 312 8 8 + - image: solid-color(50, 39, 0, 255, 8, 8) + bounds: 400 312 8 8 + - image: solid-color(51, 39, 0, 255, 8, 8) + bounds: 408 312 8 8 + - image: solid-color(52, 39, 0, 255, 8, 8) + bounds: 416 312 8 8 + - image: solid-color(53, 39, 0, 255, 8, 8) + bounds: 424 312 8 8 + - image: solid-color(54, 39, 0, 255, 8, 8) + bounds: 432 312 8 8 + - image: solid-color(55, 39, 0, 255, 8, 8) + bounds: 440 312 8 8 + - image: solid-color(56, 39, 0, 255, 8, 8) + bounds: 448 312 8 8 + - image: solid-color(57, 39, 0, 255, 8, 8) + bounds: 456 312 8 8 + - image: solid-color(58, 39, 0, 255, 8, 8) + bounds: 464 312 8 8 + - image: solid-color(59, 39, 0, 255, 8, 8) + bounds: 472 312 8 8 + - image: solid-color(60, 39, 0, 255, 8, 8) + bounds: 480 312 8 8 + - image: solid-color(61, 39, 0, 255, 8, 8) + bounds: 488 312 8 8 + - image: solid-color(62, 39, 0, 255, 8, 8) + bounds: 496 312 8 8 + - image: solid-color(63, 39, 0, 255, 8, 8) + bounds: 504 312 8 8 + - image: solid-color(64, 39, 0, 255, 8, 8) + bounds: 512 312 8 8 + - image: solid-color(65, 39, 0, 255, 8, 8) + bounds: 520 312 8 8 + - image: solid-color(66, 39, 0, 255, 8, 8) + bounds: 528 312 8 8 + - image: solid-color(67, 39, 0, 255, 8, 8) + bounds: 536 312 8 8 + - image: solid-color(68, 39, 0, 255, 8, 8) + bounds: 544 312 8 8 + - image: solid-color(69, 39, 0, 255, 8, 8) + bounds: 552 312 8 8 + - image: solid-color(70, 39, 0, 255, 8, 8) + bounds: 560 312 8 8 + - image: solid-color(71, 39, 0, 255, 8, 8) + bounds: 568 312 8 8 + - image: solid-color(72, 39, 0, 255, 8, 8) + bounds: 576 312 8 8 + - image: solid-color(73, 39, 0, 255, 8, 8) + bounds: 584 312 8 8 + - image: solid-color(74, 39, 0, 255, 8, 8) + bounds: 592 312 8 8 + - image: solid-color(75, 39, 0, 255, 8, 8) + bounds: 600 312 8 8 + - image: solid-color(76, 39, 0, 255, 8, 8) + bounds: 608 312 8 8 + - image: solid-color(77, 39, 0, 255, 8, 8) + bounds: 616 312 8 8 + - image: solid-color(78, 39, 0, 255, 8, 8) + bounds: 624 312 8 8 + - image: solid-color(79, 39, 0, 255, 8, 8) + bounds: 632 312 8 8 + - image: solid-color(80, 39, 0, 255, 8, 8) + bounds: 640 312 8 8 + - image: solid-color(81, 39, 0, 255, 8, 8) + bounds: 648 312 8 8 + - image: solid-color(82, 39, 0, 255, 8, 8) + bounds: 656 312 8 8 + - image: solid-color(83, 39, 0, 255, 8, 8) + bounds: 664 312 8 8 + - image: solid-color(84, 39, 0, 255, 8, 8) + bounds: 672 312 8 8 + - image: solid-color(85, 39, 0, 255, 8, 8) + bounds: 680 312 8 8 + - image: solid-color(86, 39, 0, 255, 8, 8) + bounds: 688 312 8 8 + - image: solid-color(87, 39, 0, 255, 8, 8) + bounds: 696 312 8 8 + - image: solid-color(88, 39, 0, 255, 8, 8) + bounds: 704 312 8 8 + - image: solid-color(89, 39, 0, 255, 8, 8) + bounds: 712 312 8 8 + - image: solid-color(90, 39, 0, 255, 8, 8) + bounds: 720 312 8 8 + - image: solid-color(91, 39, 0, 255, 8, 8) + bounds: 728 312 8 8 + - image: solid-color(92, 39, 0, 255, 8, 8) + bounds: 736 312 8 8 + - image: solid-color(93, 39, 0, 255, 8, 8) + bounds: 744 312 8 8 + - image: solid-color(94, 39, 0, 255, 8, 8) + bounds: 752 312 8 8 + - image: solid-color(95, 39, 0, 255, 8, 8) + bounds: 760 312 8 8 + - image: solid-color(96, 39, 0, 255, 8, 8) + bounds: 768 312 8 8 + - image: solid-color(97, 39, 0, 255, 8, 8) + bounds: 776 312 8 8 + - image: solid-color(98, 39, 0, 255, 8, 8) + bounds: 784 312 8 8 + - image: solid-color(99, 39, 0, 255, 8, 8) + bounds: 792 312 8 8 + - image: solid-color(100, 39, 0, 255, 8, 8) + bounds: 800 312 8 8 + - image: solid-color(101, 39, 0, 255, 8, 8) + bounds: 808 312 8 8 + - image: solid-color(102, 39, 0, 255, 8, 8) + bounds: 816 312 8 8 + - image: solid-color(103, 39, 0, 255, 8, 8) + bounds: 824 312 8 8 + - image: solid-color(104, 39, 0, 255, 8, 8) + bounds: 832 312 8 8 + - image: solid-color(105, 39, 0, 255, 8, 8) + bounds: 840 312 8 8 + - image: solid-color(106, 39, 0, 255, 8, 8) + bounds: 848 312 8 8 + - image: solid-color(107, 39, 0, 255, 8, 8) + bounds: 856 312 8 8 + - image: solid-color(108, 39, 0, 255, 8, 8) + bounds: 864 312 8 8 + - image: solid-color(109, 39, 0, 255, 8, 8) + bounds: 872 312 8 8 + - image: solid-color(110, 39, 0, 255, 8, 8) + bounds: 880 312 8 8 + - image: solid-color(111, 39, 0, 255, 8, 8) + bounds: 888 312 8 8 + - image: solid-color(112, 39, 0, 255, 8, 8) + bounds: 896 312 8 8 + - image: solid-color(113, 39, 0, 255, 8, 8) + bounds: 904 312 8 8 + - image: solid-color(114, 39, 0, 255, 8, 8) + bounds: 912 312 8 8 + - image: solid-color(115, 39, 0, 255, 8, 8) + bounds: 920 312 8 8 + - image: solid-color(116, 39, 0, 255, 8, 8) + bounds: 928 312 8 8 + - image: solid-color(117, 39, 0, 255, 8, 8) + bounds: 936 312 8 8 + - image: solid-color(118, 39, 0, 255, 8, 8) + bounds: 944 312 8 8 + - image: solid-color(119, 39, 0, 255, 8, 8) + bounds: 952 312 8 8 + - image: solid-color(120, 39, 0, 255, 8, 8) + bounds: 960 312 8 8 + - image: solid-color(121, 39, 0, 255, 8, 8) + bounds: 968 312 8 8 + - image: solid-color(122, 39, 0, 255, 8, 8) + bounds: 976 312 8 8 + - image: solid-color(123, 39, 0, 255, 8, 8) + bounds: 984 312 8 8 + - image: solid-color(124, 39, 0, 255, 8, 8) + bounds: 992 312 8 8 + - image: solid-color(125, 39, 0, 255, 8, 8) + bounds: 1000 312 8 8 + - image: solid-color(126, 39, 0, 255, 8, 8) + bounds: 1008 312 8 8 + - image: solid-color(127, 39, 0, 255, 8, 8) + bounds: 1016 312 8 8 + - image: solid-color(0, 40, 0, 255, 8, 8) + bounds: 0 320 8 8 + - image: solid-color(1, 40, 0, 255, 8, 8) + bounds: 8 320 8 8 + - image: solid-color(2, 40, 0, 255, 8, 8) + bounds: 16 320 8 8 + - image: solid-color(3, 40, 0, 255, 8, 8) + bounds: 24 320 8 8 + - image: solid-color(4, 40, 0, 255, 8, 8) + bounds: 32 320 8 8 + - image: solid-color(5, 40, 0, 255, 8, 8) + bounds: 40 320 8 8 + - image: solid-color(6, 40, 0, 255, 8, 8) + bounds: 48 320 8 8 + - image: solid-color(7, 40, 0, 255, 8, 8) + bounds: 56 320 8 8 + - image: solid-color(8, 40, 0, 255, 8, 8) + bounds: 64 320 8 8 + - image: solid-color(9, 40, 0, 255, 8, 8) + bounds: 72 320 8 8 + - image: solid-color(10, 40, 0, 255, 8, 8) + bounds: 80 320 8 8 + - image: solid-color(11, 40, 0, 255, 8, 8) + bounds: 88 320 8 8 + - image: solid-color(12, 40, 0, 255, 8, 8) + bounds: 96 320 8 8 + - image: solid-color(13, 40, 0, 255, 8, 8) + bounds: 104 320 8 8 + - image: solid-color(14, 40, 0, 255, 8, 8) + bounds: 112 320 8 8 + - image: solid-color(15, 40, 0, 255, 8, 8) + bounds: 120 320 8 8 + - image: solid-color(16, 40, 0, 255, 8, 8) + bounds: 128 320 8 8 + - image: solid-color(17, 40, 0, 255, 8, 8) + bounds: 136 320 8 8 + - image: solid-color(18, 40, 0, 255, 8, 8) + bounds: 144 320 8 8 + - image: solid-color(19, 40, 0, 255, 8, 8) + bounds: 152 320 8 8 + - image: solid-color(20, 40, 0, 255, 8, 8) + bounds: 160 320 8 8 + - image: solid-color(21, 40, 0, 255, 8, 8) + bounds: 168 320 8 8 + - image: solid-color(22, 40, 0, 255, 8, 8) + bounds: 176 320 8 8 + - image: solid-color(23, 40, 0, 255, 8, 8) + bounds: 184 320 8 8 + - image: solid-color(24, 40, 0, 255, 8, 8) + bounds: 192 320 8 8 + - image: solid-color(25, 40, 0, 255, 8, 8) + bounds: 200 320 8 8 + - image: solid-color(26, 40, 0, 255, 8, 8) + bounds: 208 320 8 8 + - image: solid-color(27, 40, 0, 255, 8, 8) + bounds: 216 320 8 8 + - image: solid-color(28, 40, 0, 255, 8, 8) + bounds: 224 320 8 8 + - image: solid-color(29, 40, 0, 255, 8, 8) + bounds: 232 320 8 8 + - image: solid-color(30, 40, 0, 255, 8, 8) + bounds: 240 320 8 8 + - image: solid-color(31, 40, 0, 255, 8, 8) + bounds: 248 320 8 8 + - image: solid-color(32, 40, 0, 255, 8, 8) + bounds: 256 320 8 8 + - image: solid-color(33, 40, 0, 255, 8, 8) + bounds: 264 320 8 8 + - image: solid-color(34, 40, 0, 255, 8, 8) + bounds: 272 320 8 8 + - image: solid-color(35, 40, 0, 255, 8, 8) + bounds: 280 320 8 8 + - image: solid-color(36, 40, 0, 255, 8, 8) + bounds: 288 320 8 8 + - image: solid-color(37, 40, 0, 255, 8, 8) + bounds: 296 320 8 8 + - image: solid-color(38, 40, 0, 255, 8, 8) + bounds: 304 320 8 8 + - image: solid-color(39, 40, 0, 255, 8, 8) + bounds: 312 320 8 8 + - image: solid-color(40, 40, 0, 255, 8, 8) + bounds: 320 320 8 8 + - image: solid-color(41, 40, 0, 255, 8, 8) + bounds: 328 320 8 8 + - image: solid-color(42, 40, 0, 255, 8, 8) + bounds: 336 320 8 8 + - image: solid-color(43, 40, 0, 255, 8, 8) + bounds: 344 320 8 8 + - image: solid-color(44, 40, 0, 255, 8, 8) + bounds: 352 320 8 8 + - image: solid-color(45, 40, 0, 255, 8, 8) + bounds: 360 320 8 8 + - image: solid-color(46, 40, 0, 255, 8, 8) + bounds: 368 320 8 8 + - image: solid-color(47, 40, 0, 255, 8, 8) + bounds: 376 320 8 8 + - image: solid-color(48, 40, 0, 255, 8, 8) + bounds: 384 320 8 8 + - image: solid-color(49, 40, 0, 255, 8, 8) + bounds: 392 320 8 8 + - image: solid-color(50, 40, 0, 255, 8, 8) + bounds: 400 320 8 8 + - image: solid-color(51, 40, 0, 255, 8, 8) + bounds: 408 320 8 8 + - image: solid-color(52, 40, 0, 255, 8, 8) + bounds: 416 320 8 8 + - image: solid-color(53, 40, 0, 255, 8, 8) + bounds: 424 320 8 8 + - image: solid-color(54, 40, 0, 255, 8, 8) + bounds: 432 320 8 8 + - image: solid-color(55, 40, 0, 255, 8, 8) + bounds: 440 320 8 8 + - image: solid-color(56, 40, 0, 255, 8, 8) + bounds: 448 320 8 8 + - image: solid-color(57, 40, 0, 255, 8, 8) + bounds: 456 320 8 8 + - image: solid-color(58, 40, 0, 255, 8, 8) + bounds: 464 320 8 8 + - image: solid-color(59, 40, 0, 255, 8, 8) + bounds: 472 320 8 8 + - image: solid-color(60, 40, 0, 255, 8, 8) + bounds: 480 320 8 8 + - image: solid-color(61, 40, 0, 255, 8, 8) + bounds: 488 320 8 8 + - image: solid-color(62, 40, 0, 255, 8, 8) + bounds: 496 320 8 8 + - image: solid-color(63, 40, 0, 255, 8, 8) + bounds: 504 320 8 8 + - image: solid-color(64, 40, 0, 255, 8, 8) + bounds: 512 320 8 8 + - image: solid-color(65, 40, 0, 255, 8, 8) + bounds: 520 320 8 8 + - image: solid-color(66, 40, 0, 255, 8, 8) + bounds: 528 320 8 8 + - image: solid-color(67, 40, 0, 255, 8, 8) + bounds: 536 320 8 8 + - image: solid-color(68, 40, 0, 255, 8, 8) + bounds: 544 320 8 8 + - image: solid-color(69, 40, 0, 255, 8, 8) + bounds: 552 320 8 8 + - image: solid-color(70, 40, 0, 255, 8, 8) + bounds: 560 320 8 8 + - image: solid-color(71, 40, 0, 255, 8, 8) + bounds: 568 320 8 8 + - image: solid-color(72, 40, 0, 255, 8, 8) + bounds: 576 320 8 8 + - image: solid-color(73, 40, 0, 255, 8, 8) + bounds: 584 320 8 8 + - image: solid-color(74, 40, 0, 255, 8, 8) + bounds: 592 320 8 8 + - image: solid-color(75, 40, 0, 255, 8, 8) + bounds: 600 320 8 8 + - image: solid-color(76, 40, 0, 255, 8, 8) + bounds: 608 320 8 8 + - image: solid-color(77, 40, 0, 255, 8, 8) + bounds: 616 320 8 8 + - image: solid-color(78, 40, 0, 255, 8, 8) + bounds: 624 320 8 8 + - image: solid-color(79, 40, 0, 255, 8, 8) + bounds: 632 320 8 8 + - image: solid-color(80, 40, 0, 255, 8, 8) + bounds: 640 320 8 8 + - image: solid-color(81, 40, 0, 255, 8, 8) + bounds: 648 320 8 8 + - image: solid-color(82, 40, 0, 255, 8, 8) + bounds: 656 320 8 8 + - image: solid-color(83, 40, 0, 255, 8, 8) + bounds: 664 320 8 8 + - image: solid-color(84, 40, 0, 255, 8, 8) + bounds: 672 320 8 8 + - image: solid-color(85, 40, 0, 255, 8, 8) + bounds: 680 320 8 8 + - image: solid-color(86, 40, 0, 255, 8, 8) + bounds: 688 320 8 8 + - image: solid-color(87, 40, 0, 255, 8, 8) + bounds: 696 320 8 8 + - image: solid-color(88, 40, 0, 255, 8, 8) + bounds: 704 320 8 8 + - image: solid-color(89, 40, 0, 255, 8, 8) + bounds: 712 320 8 8 + - image: solid-color(90, 40, 0, 255, 8, 8) + bounds: 720 320 8 8 + - image: solid-color(91, 40, 0, 255, 8, 8) + bounds: 728 320 8 8 + - image: solid-color(92, 40, 0, 255, 8, 8) + bounds: 736 320 8 8 + - image: solid-color(93, 40, 0, 255, 8, 8) + bounds: 744 320 8 8 + - image: solid-color(94, 40, 0, 255, 8, 8) + bounds: 752 320 8 8 + - image: solid-color(95, 40, 0, 255, 8, 8) + bounds: 760 320 8 8 + - image: solid-color(96, 40, 0, 255, 8, 8) + bounds: 768 320 8 8 + - image: solid-color(97, 40, 0, 255, 8, 8) + bounds: 776 320 8 8 + - image: solid-color(98, 40, 0, 255, 8, 8) + bounds: 784 320 8 8 + - image: solid-color(99, 40, 0, 255, 8, 8) + bounds: 792 320 8 8 + - image: solid-color(100, 40, 0, 255, 8, 8) + bounds: 800 320 8 8 + - image: solid-color(101, 40, 0, 255, 8, 8) + bounds: 808 320 8 8 + - image: solid-color(102, 40, 0, 255, 8, 8) + bounds: 816 320 8 8 + - image: solid-color(103, 40, 0, 255, 8, 8) + bounds: 824 320 8 8 + - image: solid-color(104, 40, 0, 255, 8, 8) + bounds: 832 320 8 8 + - image: solid-color(105, 40, 0, 255, 8, 8) + bounds: 840 320 8 8 + - image: solid-color(106, 40, 0, 255, 8, 8) + bounds: 848 320 8 8 + - image: solid-color(107, 40, 0, 255, 8, 8) + bounds: 856 320 8 8 + - image: solid-color(108, 40, 0, 255, 8, 8) + bounds: 864 320 8 8 + - image: solid-color(109, 40, 0, 255, 8, 8) + bounds: 872 320 8 8 + - image: solid-color(110, 40, 0, 255, 8, 8) + bounds: 880 320 8 8 + - image: solid-color(111, 40, 0, 255, 8, 8) + bounds: 888 320 8 8 + - image: solid-color(112, 40, 0, 255, 8, 8) + bounds: 896 320 8 8 + - image: solid-color(113, 40, 0, 255, 8, 8) + bounds: 904 320 8 8 + - image: solid-color(114, 40, 0, 255, 8, 8) + bounds: 912 320 8 8 + - image: solid-color(115, 40, 0, 255, 8, 8) + bounds: 920 320 8 8 + - image: solid-color(116, 40, 0, 255, 8, 8) + bounds: 928 320 8 8 + - image: solid-color(117, 40, 0, 255, 8, 8) + bounds: 936 320 8 8 + - image: solid-color(118, 40, 0, 255, 8, 8) + bounds: 944 320 8 8 + - image: solid-color(119, 40, 0, 255, 8, 8) + bounds: 952 320 8 8 + - image: solid-color(120, 40, 0, 255, 8, 8) + bounds: 960 320 8 8 + - image: solid-color(121, 40, 0, 255, 8, 8) + bounds: 968 320 8 8 + - image: solid-color(122, 40, 0, 255, 8, 8) + bounds: 976 320 8 8 + - image: solid-color(123, 40, 0, 255, 8, 8) + bounds: 984 320 8 8 + - image: solid-color(124, 40, 0, 255, 8, 8) + bounds: 992 320 8 8 + - image: solid-color(125, 40, 0, 255, 8, 8) + bounds: 1000 320 8 8 + - image: solid-color(126, 40, 0, 255, 8, 8) + bounds: 1008 320 8 8 + - image: solid-color(127, 40, 0, 255, 8, 8) + bounds: 1016 320 8 8 + - image: solid-color(0, 41, 0, 255, 8, 8) + bounds: 0 328 8 8 + - image: solid-color(1, 41, 0, 255, 8, 8) + bounds: 8 328 8 8 + - image: solid-color(2, 41, 0, 255, 8, 8) + bounds: 16 328 8 8 + - image: solid-color(3, 41, 0, 255, 8, 8) + bounds: 24 328 8 8 + - image: solid-color(4, 41, 0, 255, 8, 8) + bounds: 32 328 8 8 + - image: solid-color(5, 41, 0, 255, 8, 8) + bounds: 40 328 8 8 + - image: solid-color(6, 41, 0, 255, 8, 8) + bounds: 48 328 8 8 + - image: solid-color(7, 41, 0, 255, 8, 8) + bounds: 56 328 8 8 + - image: solid-color(8, 41, 0, 255, 8, 8) + bounds: 64 328 8 8 + - image: solid-color(9, 41, 0, 255, 8, 8) + bounds: 72 328 8 8 + - image: solid-color(10, 41, 0, 255, 8, 8) + bounds: 80 328 8 8 + - image: solid-color(11, 41, 0, 255, 8, 8) + bounds: 88 328 8 8 + - image: solid-color(12, 41, 0, 255, 8, 8) + bounds: 96 328 8 8 + - image: solid-color(13, 41, 0, 255, 8, 8) + bounds: 104 328 8 8 + - image: solid-color(14, 41, 0, 255, 8, 8) + bounds: 112 328 8 8 + - image: solid-color(15, 41, 0, 255, 8, 8) + bounds: 120 328 8 8 + - image: solid-color(16, 41, 0, 255, 8, 8) + bounds: 128 328 8 8 + - image: solid-color(17, 41, 0, 255, 8, 8) + bounds: 136 328 8 8 + - image: solid-color(18, 41, 0, 255, 8, 8) + bounds: 144 328 8 8 + - image: solid-color(19, 41, 0, 255, 8, 8) + bounds: 152 328 8 8 + - image: solid-color(20, 41, 0, 255, 8, 8) + bounds: 160 328 8 8 + - image: solid-color(21, 41, 0, 255, 8, 8) + bounds: 168 328 8 8 + - image: solid-color(22, 41, 0, 255, 8, 8) + bounds: 176 328 8 8 + - image: solid-color(23, 41, 0, 255, 8, 8) + bounds: 184 328 8 8 + - image: solid-color(24, 41, 0, 255, 8, 8) + bounds: 192 328 8 8 + - image: solid-color(25, 41, 0, 255, 8, 8) + bounds: 200 328 8 8 + - image: solid-color(26, 41, 0, 255, 8, 8) + bounds: 208 328 8 8 + - image: solid-color(27, 41, 0, 255, 8, 8) + bounds: 216 328 8 8 + - image: solid-color(28, 41, 0, 255, 8, 8) + bounds: 224 328 8 8 + - image: solid-color(29, 41, 0, 255, 8, 8) + bounds: 232 328 8 8 + - image: solid-color(30, 41, 0, 255, 8, 8) + bounds: 240 328 8 8 + - image: solid-color(31, 41, 0, 255, 8, 8) + bounds: 248 328 8 8 + - image: solid-color(32, 41, 0, 255, 8, 8) + bounds: 256 328 8 8 + - image: solid-color(33, 41, 0, 255, 8, 8) + bounds: 264 328 8 8 + - image: solid-color(34, 41, 0, 255, 8, 8) + bounds: 272 328 8 8 + - image: solid-color(35, 41, 0, 255, 8, 8) + bounds: 280 328 8 8 + - image: solid-color(36, 41, 0, 255, 8, 8) + bounds: 288 328 8 8 + - image: solid-color(37, 41, 0, 255, 8, 8) + bounds: 296 328 8 8 + - image: solid-color(38, 41, 0, 255, 8, 8) + bounds: 304 328 8 8 + - image: solid-color(39, 41, 0, 255, 8, 8) + bounds: 312 328 8 8 + - image: solid-color(40, 41, 0, 255, 8, 8) + bounds: 320 328 8 8 + - image: solid-color(41, 41, 0, 255, 8, 8) + bounds: 328 328 8 8 + - image: solid-color(42, 41, 0, 255, 8, 8) + bounds: 336 328 8 8 + - image: solid-color(43, 41, 0, 255, 8, 8) + bounds: 344 328 8 8 + - image: solid-color(44, 41, 0, 255, 8, 8) + bounds: 352 328 8 8 + - image: solid-color(45, 41, 0, 255, 8, 8) + bounds: 360 328 8 8 + - image: solid-color(46, 41, 0, 255, 8, 8) + bounds: 368 328 8 8 + - image: solid-color(47, 41, 0, 255, 8, 8) + bounds: 376 328 8 8 + - image: solid-color(48, 41, 0, 255, 8, 8) + bounds: 384 328 8 8 + - image: solid-color(49, 41, 0, 255, 8, 8) + bounds: 392 328 8 8 + - image: solid-color(50, 41, 0, 255, 8, 8) + bounds: 400 328 8 8 + - image: solid-color(51, 41, 0, 255, 8, 8) + bounds: 408 328 8 8 + - image: solid-color(52, 41, 0, 255, 8, 8) + bounds: 416 328 8 8 + - image: solid-color(53, 41, 0, 255, 8, 8) + bounds: 424 328 8 8 + - image: solid-color(54, 41, 0, 255, 8, 8) + bounds: 432 328 8 8 + - image: solid-color(55, 41, 0, 255, 8, 8) + bounds: 440 328 8 8 + - image: solid-color(56, 41, 0, 255, 8, 8) + bounds: 448 328 8 8 + - image: solid-color(57, 41, 0, 255, 8, 8) + bounds: 456 328 8 8 + - image: solid-color(58, 41, 0, 255, 8, 8) + bounds: 464 328 8 8 + - image: solid-color(59, 41, 0, 255, 8, 8) + bounds: 472 328 8 8 + - image: solid-color(60, 41, 0, 255, 8, 8) + bounds: 480 328 8 8 + - image: solid-color(61, 41, 0, 255, 8, 8) + bounds: 488 328 8 8 + - image: solid-color(62, 41, 0, 255, 8, 8) + bounds: 496 328 8 8 + - image: solid-color(63, 41, 0, 255, 8, 8) + bounds: 504 328 8 8 + - image: solid-color(64, 41, 0, 255, 8, 8) + bounds: 512 328 8 8 + - image: solid-color(65, 41, 0, 255, 8, 8) + bounds: 520 328 8 8 + - image: solid-color(66, 41, 0, 255, 8, 8) + bounds: 528 328 8 8 + - image: solid-color(67, 41, 0, 255, 8, 8) + bounds: 536 328 8 8 + - image: solid-color(68, 41, 0, 255, 8, 8) + bounds: 544 328 8 8 + - image: solid-color(69, 41, 0, 255, 8, 8) + bounds: 552 328 8 8 + - image: solid-color(70, 41, 0, 255, 8, 8) + bounds: 560 328 8 8 + - image: solid-color(71, 41, 0, 255, 8, 8) + bounds: 568 328 8 8 + - image: solid-color(72, 41, 0, 255, 8, 8) + bounds: 576 328 8 8 + - image: solid-color(73, 41, 0, 255, 8, 8) + bounds: 584 328 8 8 + - image: solid-color(74, 41, 0, 255, 8, 8) + bounds: 592 328 8 8 + - image: solid-color(75, 41, 0, 255, 8, 8) + bounds: 600 328 8 8 + - image: solid-color(76, 41, 0, 255, 8, 8) + bounds: 608 328 8 8 + - image: solid-color(77, 41, 0, 255, 8, 8) + bounds: 616 328 8 8 + - image: solid-color(78, 41, 0, 255, 8, 8) + bounds: 624 328 8 8 + - image: solid-color(79, 41, 0, 255, 8, 8) + bounds: 632 328 8 8 + - image: solid-color(80, 41, 0, 255, 8, 8) + bounds: 640 328 8 8 + - image: solid-color(81, 41, 0, 255, 8, 8) + bounds: 648 328 8 8 + - image: solid-color(82, 41, 0, 255, 8, 8) + bounds: 656 328 8 8 + - image: solid-color(83, 41, 0, 255, 8, 8) + bounds: 664 328 8 8 + - image: solid-color(84, 41, 0, 255, 8, 8) + bounds: 672 328 8 8 + - image: solid-color(85, 41, 0, 255, 8, 8) + bounds: 680 328 8 8 + - image: solid-color(86, 41, 0, 255, 8, 8) + bounds: 688 328 8 8 + - image: solid-color(87, 41, 0, 255, 8, 8) + bounds: 696 328 8 8 + - image: solid-color(88, 41, 0, 255, 8, 8) + bounds: 704 328 8 8 + - image: solid-color(89, 41, 0, 255, 8, 8) + bounds: 712 328 8 8 + - image: solid-color(90, 41, 0, 255, 8, 8) + bounds: 720 328 8 8 + - image: solid-color(91, 41, 0, 255, 8, 8) + bounds: 728 328 8 8 + - image: solid-color(92, 41, 0, 255, 8, 8) + bounds: 736 328 8 8 + - image: solid-color(93, 41, 0, 255, 8, 8) + bounds: 744 328 8 8 + - image: solid-color(94, 41, 0, 255, 8, 8) + bounds: 752 328 8 8 + - image: solid-color(95, 41, 0, 255, 8, 8) + bounds: 760 328 8 8 + - image: solid-color(96, 41, 0, 255, 8, 8) + bounds: 768 328 8 8 + - image: solid-color(97, 41, 0, 255, 8, 8) + bounds: 776 328 8 8 + - image: solid-color(98, 41, 0, 255, 8, 8) + bounds: 784 328 8 8 + - image: solid-color(99, 41, 0, 255, 8, 8) + bounds: 792 328 8 8 + - image: solid-color(100, 41, 0, 255, 8, 8) + bounds: 800 328 8 8 + - image: solid-color(101, 41, 0, 255, 8, 8) + bounds: 808 328 8 8 + - image: solid-color(102, 41, 0, 255, 8, 8) + bounds: 816 328 8 8 + - image: solid-color(103, 41, 0, 255, 8, 8) + bounds: 824 328 8 8 + - image: solid-color(104, 41, 0, 255, 8, 8) + bounds: 832 328 8 8 + - image: solid-color(105, 41, 0, 255, 8, 8) + bounds: 840 328 8 8 + - image: solid-color(106, 41, 0, 255, 8, 8) + bounds: 848 328 8 8 + - image: solid-color(107, 41, 0, 255, 8, 8) + bounds: 856 328 8 8 + - image: solid-color(108, 41, 0, 255, 8, 8) + bounds: 864 328 8 8 + - image: solid-color(109, 41, 0, 255, 8, 8) + bounds: 872 328 8 8 + - image: solid-color(110, 41, 0, 255, 8, 8) + bounds: 880 328 8 8 + - image: solid-color(111, 41, 0, 255, 8, 8) + bounds: 888 328 8 8 + - image: solid-color(112, 41, 0, 255, 8, 8) + bounds: 896 328 8 8 + - image: solid-color(113, 41, 0, 255, 8, 8) + bounds: 904 328 8 8 + - image: solid-color(114, 41, 0, 255, 8, 8) + bounds: 912 328 8 8 + - image: solid-color(115, 41, 0, 255, 8, 8) + bounds: 920 328 8 8 + - image: solid-color(116, 41, 0, 255, 8, 8) + bounds: 928 328 8 8 + - image: solid-color(117, 41, 0, 255, 8, 8) + bounds: 936 328 8 8 + - image: solid-color(118, 41, 0, 255, 8, 8) + bounds: 944 328 8 8 + - image: solid-color(119, 41, 0, 255, 8, 8) + bounds: 952 328 8 8 + - image: solid-color(120, 41, 0, 255, 8, 8) + bounds: 960 328 8 8 + - image: solid-color(121, 41, 0, 255, 8, 8) + bounds: 968 328 8 8 + - image: solid-color(122, 41, 0, 255, 8, 8) + bounds: 976 328 8 8 + - image: solid-color(123, 41, 0, 255, 8, 8) + bounds: 984 328 8 8 + - image: solid-color(124, 41, 0, 255, 8, 8) + bounds: 992 328 8 8 + - image: solid-color(125, 41, 0, 255, 8, 8) + bounds: 1000 328 8 8 + - image: solid-color(126, 41, 0, 255, 8, 8) + bounds: 1008 328 8 8 + - image: solid-color(127, 41, 0, 255, 8, 8) + bounds: 1016 328 8 8 + - image: solid-color(0, 42, 0, 255, 8, 8) + bounds: 0 336 8 8 + - image: solid-color(1, 42, 0, 255, 8, 8) + bounds: 8 336 8 8 + - image: solid-color(2, 42, 0, 255, 8, 8) + bounds: 16 336 8 8 + - image: solid-color(3, 42, 0, 255, 8, 8) + bounds: 24 336 8 8 + - image: solid-color(4, 42, 0, 255, 8, 8) + bounds: 32 336 8 8 + - image: solid-color(5, 42, 0, 255, 8, 8) + bounds: 40 336 8 8 + - image: solid-color(6, 42, 0, 255, 8, 8) + bounds: 48 336 8 8 + - image: solid-color(7, 42, 0, 255, 8, 8) + bounds: 56 336 8 8 + - image: solid-color(8, 42, 0, 255, 8, 8) + bounds: 64 336 8 8 + - image: solid-color(9, 42, 0, 255, 8, 8) + bounds: 72 336 8 8 + - image: solid-color(10, 42, 0, 255, 8, 8) + bounds: 80 336 8 8 + - image: solid-color(11, 42, 0, 255, 8, 8) + bounds: 88 336 8 8 + - image: solid-color(12, 42, 0, 255, 8, 8) + bounds: 96 336 8 8 + - image: solid-color(13, 42, 0, 255, 8, 8) + bounds: 104 336 8 8 + - image: solid-color(14, 42, 0, 255, 8, 8) + bounds: 112 336 8 8 + - image: solid-color(15, 42, 0, 255, 8, 8) + bounds: 120 336 8 8 + - image: solid-color(16, 42, 0, 255, 8, 8) + bounds: 128 336 8 8 + - image: solid-color(17, 42, 0, 255, 8, 8) + bounds: 136 336 8 8 + - image: solid-color(18, 42, 0, 255, 8, 8) + bounds: 144 336 8 8 + - image: solid-color(19, 42, 0, 255, 8, 8) + bounds: 152 336 8 8 + - image: solid-color(20, 42, 0, 255, 8, 8) + bounds: 160 336 8 8 + - image: solid-color(21, 42, 0, 255, 8, 8) + bounds: 168 336 8 8 + - image: solid-color(22, 42, 0, 255, 8, 8) + bounds: 176 336 8 8 + - image: solid-color(23, 42, 0, 255, 8, 8) + bounds: 184 336 8 8 + - image: solid-color(24, 42, 0, 255, 8, 8) + bounds: 192 336 8 8 + - image: solid-color(25, 42, 0, 255, 8, 8) + bounds: 200 336 8 8 + - image: solid-color(26, 42, 0, 255, 8, 8) + bounds: 208 336 8 8 + - image: solid-color(27, 42, 0, 255, 8, 8) + bounds: 216 336 8 8 + - image: solid-color(28, 42, 0, 255, 8, 8) + bounds: 224 336 8 8 + - image: solid-color(29, 42, 0, 255, 8, 8) + bounds: 232 336 8 8 + - image: solid-color(30, 42, 0, 255, 8, 8) + bounds: 240 336 8 8 + - image: solid-color(31, 42, 0, 255, 8, 8) + bounds: 248 336 8 8 + - image: solid-color(32, 42, 0, 255, 8, 8) + bounds: 256 336 8 8 + - image: solid-color(33, 42, 0, 255, 8, 8) + bounds: 264 336 8 8 + - image: solid-color(34, 42, 0, 255, 8, 8) + bounds: 272 336 8 8 + - image: solid-color(35, 42, 0, 255, 8, 8) + bounds: 280 336 8 8 + - image: solid-color(36, 42, 0, 255, 8, 8) + bounds: 288 336 8 8 + - image: solid-color(37, 42, 0, 255, 8, 8) + bounds: 296 336 8 8 + - image: solid-color(38, 42, 0, 255, 8, 8) + bounds: 304 336 8 8 + - image: solid-color(39, 42, 0, 255, 8, 8) + bounds: 312 336 8 8 + - image: solid-color(40, 42, 0, 255, 8, 8) + bounds: 320 336 8 8 + - image: solid-color(41, 42, 0, 255, 8, 8) + bounds: 328 336 8 8 + - image: solid-color(42, 42, 0, 255, 8, 8) + bounds: 336 336 8 8 + - image: solid-color(43, 42, 0, 255, 8, 8) + bounds: 344 336 8 8 + - image: solid-color(44, 42, 0, 255, 8, 8) + bounds: 352 336 8 8 + - image: solid-color(45, 42, 0, 255, 8, 8) + bounds: 360 336 8 8 + - image: solid-color(46, 42, 0, 255, 8, 8) + bounds: 368 336 8 8 + - image: solid-color(47, 42, 0, 255, 8, 8) + bounds: 376 336 8 8 + - image: solid-color(48, 42, 0, 255, 8, 8) + bounds: 384 336 8 8 + - image: solid-color(49, 42, 0, 255, 8, 8) + bounds: 392 336 8 8 + - image: solid-color(50, 42, 0, 255, 8, 8) + bounds: 400 336 8 8 + - image: solid-color(51, 42, 0, 255, 8, 8) + bounds: 408 336 8 8 + - image: solid-color(52, 42, 0, 255, 8, 8) + bounds: 416 336 8 8 + - image: solid-color(53, 42, 0, 255, 8, 8) + bounds: 424 336 8 8 + - image: solid-color(54, 42, 0, 255, 8, 8) + bounds: 432 336 8 8 + - image: solid-color(55, 42, 0, 255, 8, 8) + bounds: 440 336 8 8 + - image: solid-color(56, 42, 0, 255, 8, 8) + bounds: 448 336 8 8 + - image: solid-color(57, 42, 0, 255, 8, 8) + bounds: 456 336 8 8 + - image: solid-color(58, 42, 0, 255, 8, 8) + bounds: 464 336 8 8 + - image: solid-color(59, 42, 0, 255, 8, 8) + bounds: 472 336 8 8 + - image: solid-color(60, 42, 0, 255, 8, 8) + bounds: 480 336 8 8 + - image: solid-color(61, 42, 0, 255, 8, 8) + bounds: 488 336 8 8 + - image: solid-color(62, 42, 0, 255, 8, 8) + bounds: 496 336 8 8 + - image: solid-color(63, 42, 0, 255, 8, 8) + bounds: 504 336 8 8 + - image: solid-color(64, 42, 0, 255, 8, 8) + bounds: 512 336 8 8 + - image: solid-color(65, 42, 0, 255, 8, 8) + bounds: 520 336 8 8 + - image: solid-color(66, 42, 0, 255, 8, 8) + bounds: 528 336 8 8 + - image: solid-color(67, 42, 0, 255, 8, 8) + bounds: 536 336 8 8 + - image: solid-color(68, 42, 0, 255, 8, 8) + bounds: 544 336 8 8 + - image: solid-color(69, 42, 0, 255, 8, 8) + bounds: 552 336 8 8 + - image: solid-color(70, 42, 0, 255, 8, 8) + bounds: 560 336 8 8 + - image: solid-color(71, 42, 0, 255, 8, 8) + bounds: 568 336 8 8 + - image: solid-color(72, 42, 0, 255, 8, 8) + bounds: 576 336 8 8 + - image: solid-color(73, 42, 0, 255, 8, 8) + bounds: 584 336 8 8 + - image: solid-color(74, 42, 0, 255, 8, 8) + bounds: 592 336 8 8 + - image: solid-color(75, 42, 0, 255, 8, 8) + bounds: 600 336 8 8 + - image: solid-color(76, 42, 0, 255, 8, 8) + bounds: 608 336 8 8 + - image: solid-color(77, 42, 0, 255, 8, 8) + bounds: 616 336 8 8 + - image: solid-color(78, 42, 0, 255, 8, 8) + bounds: 624 336 8 8 + - image: solid-color(79, 42, 0, 255, 8, 8) + bounds: 632 336 8 8 + - image: solid-color(80, 42, 0, 255, 8, 8) + bounds: 640 336 8 8 + - image: solid-color(81, 42, 0, 255, 8, 8) + bounds: 648 336 8 8 + - image: solid-color(82, 42, 0, 255, 8, 8) + bounds: 656 336 8 8 + - image: solid-color(83, 42, 0, 255, 8, 8) + bounds: 664 336 8 8 + - image: solid-color(84, 42, 0, 255, 8, 8) + bounds: 672 336 8 8 + - image: solid-color(85, 42, 0, 255, 8, 8) + bounds: 680 336 8 8 + - image: solid-color(86, 42, 0, 255, 8, 8) + bounds: 688 336 8 8 + - image: solid-color(87, 42, 0, 255, 8, 8) + bounds: 696 336 8 8 + - image: solid-color(88, 42, 0, 255, 8, 8) + bounds: 704 336 8 8 + - image: solid-color(89, 42, 0, 255, 8, 8) + bounds: 712 336 8 8 + - image: solid-color(90, 42, 0, 255, 8, 8) + bounds: 720 336 8 8 + - image: solid-color(91, 42, 0, 255, 8, 8) + bounds: 728 336 8 8 + - image: solid-color(92, 42, 0, 255, 8, 8) + bounds: 736 336 8 8 + - image: solid-color(93, 42, 0, 255, 8, 8) + bounds: 744 336 8 8 + - image: solid-color(94, 42, 0, 255, 8, 8) + bounds: 752 336 8 8 + - image: solid-color(95, 42, 0, 255, 8, 8) + bounds: 760 336 8 8 + - image: solid-color(96, 42, 0, 255, 8, 8) + bounds: 768 336 8 8 + - image: solid-color(97, 42, 0, 255, 8, 8) + bounds: 776 336 8 8 + - image: solid-color(98, 42, 0, 255, 8, 8) + bounds: 784 336 8 8 + - image: solid-color(99, 42, 0, 255, 8, 8) + bounds: 792 336 8 8 + - image: solid-color(100, 42, 0, 255, 8, 8) + bounds: 800 336 8 8 + - image: solid-color(101, 42, 0, 255, 8, 8) + bounds: 808 336 8 8 + - image: solid-color(102, 42, 0, 255, 8, 8) + bounds: 816 336 8 8 + - image: solid-color(103, 42, 0, 255, 8, 8) + bounds: 824 336 8 8 + - image: solid-color(104, 42, 0, 255, 8, 8) + bounds: 832 336 8 8 + - image: solid-color(105, 42, 0, 255, 8, 8) + bounds: 840 336 8 8 + - image: solid-color(106, 42, 0, 255, 8, 8) + bounds: 848 336 8 8 + - image: solid-color(107, 42, 0, 255, 8, 8) + bounds: 856 336 8 8 + - image: solid-color(108, 42, 0, 255, 8, 8) + bounds: 864 336 8 8 + - image: solid-color(109, 42, 0, 255, 8, 8) + bounds: 872 336 8 8 + - image: solid-color(110, 42, 0, 255, 8, 8) + bounds: 880 336 8 8 + - image: solid-color(111, 42, 0, 255, 8, 8) + bounds: 888 336 8 8 + - image: solid-color(112, 42, 0, 255, 8, 8) + bounds: 896 336 8 8 + - image: solid-color(113, 42, 0, 255, 8, 8) + bounds: 904 336 8 8 + - image: solid-color(114, 42, 0, 255, 8, 8) + bounds: 912 336 8 8 + - image: solid-color(115, 42, 0, 255, 8, 8) + bounds: 920 336 8 8 + - image: solid-color(116, 42, 0, 255, 8, 8) + bounds: 928 336 8 8 + - image: solid-color(117, 42, 0, 255, 8, 8) + bounds: 936 336 8 8 + - image: solid-color(118, 42, 0, 255, 8, 8) + bounds: 944 336 8 8 + - image: solid-color(119, 42, 0, 255, 8, 8) + bounds: 952 336 8 8 + - image: solid-color(120, 42, 0, 255, 8, 8) + bounds: 960 336 8 8 + - image: solid-color(121, 42, 0, 255, 8, 8) + bounds: 968 336 8 8 + - image: solid-color(122, 42, 0, 255, 8, 8) + bounds: 976 336 8 8 + - image: solid-color(123, 42, 0, 255, 8, 8) + bounds: 984 336 8 8 + - image: solid-color(124, 42, 0, 255, 8, 8) + bounds: 992 336 8 8 + - image: solid-color(125, 42, 0, 255, 8, 8) + bounds: 1000 336 8 8 + - image: solid-color(126, 42, 0, 255, 8, 8) + bounds: 1008 336 8 8 + - image: solid-color(127, 42, 0, 255, 8, 8) + bounds: 1016 336 8 8 + - image: solid-color(0, 43, 0, 255, 8, 8) + bounds: 0 344 8 8 + - image: solid-color(1, 43, 0, 255, 8, 8) + bounds: 8 344 8 8 + - image: solid-color(2, 43, 0, 255, 8, 8) + bounds: 16 344 8 8 + - image: solid-color(3, 43, 0, 255, 8, 8) + bounds: 24 344 8 8 + - image: solid-color(4, 43, 0, 255, 8, 8) + bounds: 32 344 8 8 + - image: solid-color(5, 43, 0, 255, 8, 8) + bounds: 40 344 8 8 + - image: solid-color(6, 43, 0, 255, 8, 8) + bounds: 48 344 8 8 + - image: solid-color(7, 43, 0, 255, 8, 8) + bounds: 56 344 8 8 + - image: solid-color(8, 43, 0, 255, 8, 8) + bounds: 64 344 8 8 + - image: solid-color(9, 43, 0, 255, 8, 8) + bounds: 72 344 8 8 + - image: solid-color(10, 43, 0, 255, 8, 8) + bounds: 80 344 8 8 + - image: solid-color(11, 43, 0, 255, 8, 8) + bounds: 88 344 8 8 + - image: solid-color(12, 43, 0, 255, 8, 8) + bounds: 96 344 8 8 + - image: solid-color(13, 43, 0, 255, 8, 8) + bounds: 104 344 8 8 + - image: solid-color(14, 43, 0, 255, 8, 8) + bounds: 112 344 8 8 + - image: solid-color(15, 43, 0, 255, 8, 8) + bounds: 120 344 8 8 + - image: solid-color(16, 43, 0, 255, 8, 8) + bounds: 128 344 8 8 + - image: solid-color(17, 43, 0, 255, 8, 8) + bounds: 136 344 8 8 + - image: solid-color(18, 43, 0, 255, 8, 8) + bounds: 144 344 8 8 + - image: solid-color(19, 43, 0, 255, 8, 8) + bounds: 152 344 8 8 + - image: solid-color(20, 43, 0, 255, 8, 8) + bounds: 160 344 8 8 + - image: solid-color(21, 43, 0, 255, 8, 8) + bounds: 168 344 8 8 + - image: solid-color(22, 43, 0, 255, 8, 8) + bounds: 176 344 8 8 + - image: solid-color(23, 43, 0, 255, 8, 8) + bounds: 184 344 8 8 + - image: solid-color(24, 43, 0, 255, 8, 8) + bounds: 192 344 8 8 + - image: solid-color(25, 43, 0, 255, 8, 8) + bounds: 200 344 8 8 + - image: solid-color(26, 43, 0, 255, 8, 8) + bounds: 208 344 8 8 + - image: solid-color(27, 43, 0, 255, 8, 8) + bounds: 216 344 8 8 + - image: solid-color(28, 43, 0, 255, 8, 8) + bounds: 224 344 8 8 + - image: solid-color(29, 43, 0, 255, 8, 8) + bounds: 232 344 8 8 + - image: solid-color(30, 43, 0, 255, 8, 8) + bounds: 240 344 8 8 + - image: solid-color(31, 43, 0, 255, 8, 8) + bounds: 248 344 8 8 + - image: solid-color(32, 43, 0, 255, 8, 8) + bounds: 256 344 8 8 + - image: solid-color(33, 43, 0, 255, 8, 8) + bounds: 264 344 8 8 + - image: solid-color(34, 43, 0, 255, 8, 8) + bounds: 272 344 8 8 + - image: solid-color(35, 43, 0, 255, 8, 8) + bounds: 280 344 8 8 + - image: solid-color(36, 43, 0, 255, 8, 8) + bounds: 288 344 8 8 + - image: solid-color(37, 43, 0, 255, 8, 8) + bounds: 296 344 8 8 + - image: solid-color(38, 43, 0, 255, 8, 8) + bounds: 304 344 8 8 + - image: solid-color(39, 43, 0, 255, 8, 8) + bounds: 312 344 8 8 + - image: solid-color(40, 43, 0, 255, 8, 8) + bounds: 320 344 8 8 + - image: solid-color(41, 43, 0, 255, 8, 8) + bounds: 328 344 8 8 + - image: solid-color(42, 43, 0, 255, 8, 8) + bounds: 336 344 8 8 + - image: solid-color(43, 43, 0, 255, 8, 8) + bounds: 344 344 8 8 + - image: solid-color(44, 43, 0, 255, 8, 8) + bounds: 352 344 8 8 + - image: solid-color(45, 43, 0, 255, 8, 8) + bounds: 360 344 8 8 + - image: solid-color(46, 43, 0, 255, 8, 8) + bounds: 368 344 8 8 + - image: solid-color(47, 43, 0, 255, 8, 8) + bounds: 376 344 8 8 + - image: solid-color(48, 43, 0, 255, 8, 8) + bounds: 384 344 8 8 + - image: solid-color(49, 43, 0, 255, 8, 8) + bounds: 392 344 8 8 + - image: solid-color(50, 43, 0, 255, 8, 8) + bounds: 400 344 8 8 + - image: solid-color(51, 43, 0, 255, 8, 8) + bounds: 408 344 8 8 + - image: solid-color(52, 43, 0, 255, 8, 8) + bounds: 416 344 8 8 + - image: solid-color(53, 43, 0, 255, 8, 8) + bounds: 424 344 8 8 + - image: solid-color(54, 43, 0, 255, 8, 8) + bounds: 432 344 8 8 + - image: solid-color(55, 43, 0, 255, 8, 8) + bounds: 440 344 8 8 + - image: solid-color(56, 43, 0, 255, 8, 8) + bounds: 448 344 8 8 + - image: solid-color(57, 43, 0, 255, 8, 8) + bounds: 456 344 8 8 + - image: solid-color(58, 43, 0, 255, 8, 8) + bounds: 464 344 8 8 + - image: solid-color(59, 43, 0, 255, 8, 8) + bounds: 472 344 8 8 + - image: solid-color(60, 43, 0, 255, 8, 8) + bounds: 480 344 8 8 + - image: solid-color(61, 43, 0, 255, 8, 8) + bounds: 488 344 8 8 + - image: solid-color(62, 43, 0, 255, 8, 8) + bounds: 496 344 8 8 + - image: solid-color(63, 43, 0, 255, 8, 8) + bounds: 504 344 8 8 + - image: solid-color(64, 43, 0, 255, 8, 8) + bounds: 512 344 8 8 + - image: solid-color(65, 43, 0, 255, 8, 8) + bounds: 520 344 8 8 + - image: solid-color(66, 43, 0, 255, 8, 8) + bounds: 528 344 8 8 + - image: solid-color(67, 43, 0, 255, 8, 8) + bounds: 536 344 8 8 + - image: solid-color(68, 43, 0, 255, 8, 8) + bounds: 544 344 8 8 + - image: solid-color(69, 43, 0, 255, 8, 8) + bounds: 552 344 8 8 + - image: solid-color(70, 43, 0, 255, 8, 8) + bounds: 560 344 8 8 + - image: solid-color(71, 43, 0, 255, 8, 8) + bounds: 568 344 8 8 + - image: solid-color(72, 43, 0, 255, 8, 8) + bounds: 576 344 8 8 + - image: solid-color(73, 43, 0, 255, 8, 8) + bounds: 584 344 8 8 + - image: solid-color(74, 43, 0, 255, 8, 8) + bounds: 592 344 8 8 + - image: solid-color(75, 43, 0, 255, 8, 8) + bounds: 600 344 8 8 + - image: solid-color(76, 43, 0, 255, 8, 8) + bounds: 608 344 8 8 + - image: solid-color(77, 43, 0, 255, 8, 8) + bounds: 616 344 8 8 + - image: solid-color(78, 43, 0, 255, 8, 8) + bounds: 624 344 8 8 + - image: solid-color(79, 43, 0, 255, 8, 8) + bounds: 632 344 8 8 + - image: solid-color(80, 43, 0, 255, 8, 8) + bounds: 640 344 8 8 + - image: solid-color(81, 43, 0, 255, 8, 8) + bounds: 648 344 8 8 + - image: solid-color(82, 43, 0, 255, 8, 8) + bounds: 656 344 8 8 + - image: solid-color(83, 43, 0, 255, 8, 8) + bounds: 664 344 8 8 + - image: solid-color(84, 43, 0, 255, 8, 8) + bounds: 672 344 8 8 + - image: solid-color(85, 43, 0, 255, 8, 8) + bounds: 680 344 8 8 + - image: solid-color(86, 43, 0, 255, 8, 8) + bounds: 688 344 8 8 + - image: solid-color(87, 43, 0, 255, 8, 8) + bounds: 696 344 8 8 + - image: solid-color(88, 43, 0, 255, 8, 8) + bounds: 704 344 8 8 + - image: solid-color(89, 43, 0, 255, 8, 8) + bounds: 712 344 8 8 + - image: solid-color(90, 43, 0, 255, 8, 8) + bounds: 720 344 8 8 + - image: solid-color(91, 43, 0, 255, 8, 8) + bounds: 728 344 8 8 + - image: solid-color(92, 43, 0, 255, 8, 8) + bounds: 736 344 8 8 + - image: solid-color(93, 43, 0, 255, 8, 8) + bounds: 744 344 8 8 + - image: solid-color(94, 43, 0, 255, 8, 8) + bounds: 752 344 8 8 + - image: solid-color(95, 43, 0, 255, 8, 8) + bounds: 760 344 8 8 + - image: solid-color(96, 43, 0, 255, 8, 8) + bounds: 768 344 8 8 + - image: solid-color(97, 43, 0, 255, 8, 8) + bounds: 776 344 8 8 + - image: solid-color(98, 43, 0, 255, 8, 8) + bounds: 784 344 8 8 + - image: solid-color(99, 43, 0, 255, 8, 8) + bounds: 792 344 8 8 + - image: solid-color(100, 43, 0, 255, 8, 8) + bounds: 800 344 8 8 + - image: solid-color(101, 43, 0, 255, 8, 8) + bounds: 808 344 8 8 + - image: solid-color(102, 43, 0, 255, 8, 8) + bounds: 816 344 8 8 + - image: solid-color(103, 43, 0, 255, 8, 8) + bounds: 824 344 8 8 + - image: solid-color(104, 43, 0, 255, 8, 8) + bounds: 832 344 8 8 + - image: solid-color(105, 43, 0, 255, 8, 8) + bounds: 840 344 8 8 + - image: solid-color(106, 43, 0, 255, 8, 8) + bounds: 848 344 8 8 + - image: solid-color(107, 43, 0, 255, 8, 8) + bounds: 856 344 8 8 + - image: solid-color(108, 43, 0, 255, 8, 8) + bounds: 864 344 8 8 + - image: solid-color(109, 43, 0, 255, 8, 8) + bounds: 872 344 8 8 + - image: solid-color(110, 43, 0, 255, 8, 8) + bounds: 880 344 8 8 + - image: solid-color(111, 43, 0, 255, 8, 8) + bounds: 888 344 8 8 + - image: solid-color(112, 43, 0, 255, 8, 8) + bounds: 896 344 8 8 + - image: solid-color(113, 43, 0, 255, 8, 8) + bounds: 904 344 8 8 + - image: solid-color(114, 43, 0, 255, 8, 8) + bounds: 912 344 8 8 + - image: solid-color(115, 43, 0, 255, 8, 8) + bounds: 920 344 8 8 + - image: solid-color(116, 43, 0, 255, 8, 8) + bounds: 928 344 8 8 + - image: solid-color(117, 43, 0, 255, 8, 8) + bounds: 936 344 8 8 + - image: solid-color(118, 43, 0, 255, 8, 8) + bounds: 944 344 8 8 + - image: solid-color(119, 43, 0, 255, 8, 8) + bounds: 952 344 8 8 + - image: solid-color(120, 43, 0, 255, 8, 8) + bounds: 960 344 8 8 + - image: solid-color(121, 43, 0, 255, 8, 8) + bounds: 968 344 8 8 + - image: solid-color(122, 43, 0, 255, 8, 8) + bounds: 976 344 8 8 + - image: solid-color(123, 43, 0, 255, 8, 8) + bounds: 984 344 8 8 + - image: solid-color(124, 43, 0, 255, 8, 8) + bounds: 992 344 8 8 + - image: solid-color(125, 43, 0, 255, 8, 8) + bounds: 1000 344 8 8 + - image: solid-color(126, 43, 0, 255, 8, 8) + bounds: 1008 344 8 8 + - image: solid-color(127, 43, 0, 255, 8, 8) + bounds: 1016 344 8 8 + - image: solid-color(0, 44, 0, 255, 8, 8) + bounds: 0 352 8 8 + - image: solid-color(1, 44, 0, 255, 8, 8) + bounds: 8 352 8 8 + - image: solid-color(2, 44, 0, 255, 8, 8) + bounds: 16 352 8 8 + - image: solid-color(3, 44, 0, 255, 8, 8) + bounds: 24 352 8 8 + - image: solid-color(4, 44, 0, 255, 8, 8) + bounds: 32 352 8 8 + - image: solid-color(5, 44, 0, 255, 8, 8) + bounds: 40 352 8 8 + - image: solid-color(6, 44, 0, 255, 8, 8) + bounds: 48 352 8 8 + - image: solid-color(7, 44, 0, 255, 8, 8) + bounds: 56 352 8 8 + - image: solid-color(8, 44, 0, 255, 8, 8) + bounds: 64 352 8 8 + - image: solid-color(9, 44, 0, 255, 8, 8) + bounds: 72 352 8 8 + - image: solid-color(10, 44, 0, 255, 8, 8) + bounds: 80 352 8 8 + - image: solid-color(11, 44, 0, 255, 8, 8) + bounds: 88 352 8 8 + - image: solid-color(12, 44, 0, 255, 8, 8) + bounds: 96 352 8 8 + - image: solid-color(13, 44, 0, 255, 8, 8) + bounds: 104 352 8 8 + - image: solid-color(14, 44, 0, 255, 8, 8) + bounds: 112 352 8 8 + - image: solid-color(15, 44, 0, 255, 8, 8) + bounds: 120 352 8 8 + - image: solid-color(16, 44, 0, 255, 8, 8) + bounds: 128 352 8 8 + - image: solid-color(17, 44, 0, 255, 8, 8) + bounds: 136 352 8 8 + - image: solid-color(18, 44, 0, 255, 8, 8) + bounds: 144 352 8 8 + - image: solid-color(19, 44, 0, 255, 8, 8) + bounds: 152 352 8 8 + - image: solid-color(20, 44, 0, 255, 8, 8) + bounds: 160 352 8 8 + - image: solid-color(21, 44, 0, 255, 8, 8) + bounds: 168 352 8 8 + - image: solid-color(22, 44, 0, 255, 8, 8) + bounds: 176 352 8 8 + - image: solid-color(23, 44, 0, 255, 8, 8) + bounds: 184 352 8 8 + - image: solid-color(24, 44, 0, 255, 8, 8) + bounds: 192 352 8 8 + - image: solid-color(25, 44, 0, 255, 8, 8) + bounds: 200 352 8 8 + - image: solid-color(26, 44, 0, 255, 8, 8) + bounds: 208 352 8 8 + - image: solid-color(27, 44, 0, 255, 8, 8) + bounds: 216 352 8 8 + - image: solid-color(28, 44, 0, 255, 8, 8) + bounds: 224 352 8 8 + - image: solid-color(29, 44, 0, 255, 8, 8) + bounds: 232 352 8 8 + - image: solid-color(30, 44, 0, 255, 8, 8) + bounds: 240 352 8 8 + - image: solid-color(31, 44, 0, 255, 8, 8) + bounds: 248 352 8 8 + - image: solid-color(32, 44, 0, 255, 8, 8) + bounds: 256 352 8 8 + - image: solid-color(33, 44, 0, 255, 8, 8) + bounds: 264 352 8 8 + - image: solid-color(34, 44, 0, 255, 8, 8) + bounds: 272 352 8 8 + - image: solid-color(35, 44, 0, 255, 8, 8) + bounds: 280 352 8 8 + - image: solid-color(36, 44, 0, 255, 8, 8) + bounds: 288 352 8 8 + - image: solid-color(37, 44, 0, 255, 8, 8) + bounds: 296 352 8 8 + - image: solid-color(38, 44, 0, 255, 8, 8) + bounds: 304 352 8 8 + - image: solid-color(39, 44, 0, 255, 8, 8) + bounds: 312 352 8 8 + - image: solid-color(40, 44, 0, 255, 8, 8) + bounds: 320 352 8 8 + - image: solid-color(41, 44, 0, 255, 8, 8) + bounds: 328 352 8 8 + - image: solid-color(42, 44, 0, 255, 8, 8) + bounds: 336 352 8 8 + - image: solid-color(43, 44, 0, 255, 8, 8) + bounds: 344 352 8 8 + - image: solid-color(44, 44, 0, 255, 8, 8) + bounds: 352 352 8 8 + - image: solid-color(45, 44, 0, 255, 8, 8) + bounds: 360 352 8 8 + - image: solid-color(46, 44, 0, 255, 8, 8) + bounds: 368 352 8 8 + - image: solid-color(47, 44, 0, 255, 8, 8) + bounds: 376 352 8 8 + - image: solid-color(48, 44, 0, 255, 8, 8) + bounds: 384 352 8 8 + - image: solid-color(49, 44, 0, 255, 8, 8) + bounds: 392 352 8 8 + - image: solid-color(50, 44, 0, 255, 8, 8) + bounds: 400 352 8 8 + - image: solid-color(51, 44, 0, 255, 8, 8) + bounds: 408 352 8 8 + - image: solid-color(52, 44, 0, 255, 8, 8) + bounds: 416 352 8 8 + - image: solid-color(53, 44, 0, 255, 8, 8) + bounds: 424 352 8 8 + - image: solid-color(54, 44, 0, 255, 8, 8) + bounds: 432 352 8 8 + - image: solid-color(55, 44, 0, 255, 8, 8) + bounds: 440 352 8 8 + - image: solid-color(56, 44, 0, 255, 8, 8) + bounds: 448 352 8 8 + - image: solid-color(57, 44, 0, 255, 8, 8) + bounds: 456 352 8 8 + - image: solid-color(58, 44, 0, 255, 8, 8) + bounds: 464 352 8 8 + - image: solid-color(59, 44, 0, 255, 8, 8) + bounds: 472 352 8 8 + - image: solid-color(60, 44, 0, 255, 8, 8) + bounds: 480 352 8 8 + - image: solid-color(61, 44, 0, 255, 8, 8) + bounds: 488 352 8 8 + - image: solid-color(62, 44, 0, 255, 8, 8) + bounds: 496 352 8 8 + - image: solid-color(63, 44, 0, 255, 8, 8) + bounds: 504 352 8 8 + - image: solid-color(64, 44, 0, 255, 8, 8) + bounds: 512 352 8 8 + - image: solid-color(65, 44, 0, 255, 8, 8) + bounds: 520 352 8 8 + - image: solid-color(66, 44, 0, 255, 8, 8) + bounds: 528 352 8 8 + - image: solid-color(67, 44, 0, 255, 8, 8) + bounds: 536 352 8 8 + - image: solid-color(68, 44, 0, 255, 8, 8) + bounds: 544 352 8 8 + - image: solid-color(69, 44, 0, 255, 8, 8) + bounds: 552 352 8 8 + - image: solid-color(70, 44, 0, 255, 8, 8) + bounds: 560 352 8 8 + - image: solid-color(71, 44, 0, 255, 8, 8) + bounds: 568 352 8 8 + - image: solid-color(72, 44, 0, 255, 8, 8) + bounds: 576 352 8 8 + - image: solid-color(73, 44, 0, 255, 8, 8) + bounds: 584 352 8 8 + - image: solid-color(74, 44, 0, 255, 8, 8) + bounds: 592 352 8 8 + - image: solid-color(75, 44, 0, 255, 8, 8) + bounds: 600 352 8 8 + - image: solid-color(76, 44, 0, 255, 8, 8) + bounds: 608 352 8 8 + - image: solid-color(77, 44, 0, 255, 8, 8) + bounds: 616 352 8 8 + - image: solid-color(78, 44, 0, 255, 8, 8) + bounds: 624 352 8 8 + - image: solid-color(79, 44, 0, 255, 8, 8) + bounds: 632 352 8 8 + - image: solid-color(80, 44, 0, 255, 8, 8) + bounds: 640 352 8 8 + - image: solid-color(81, 44, 0, 255, 8, 8) + bounds: 648 352 8 8 + - image: solid-color(82, 44, 0, 255, 8, 8) + bounds: 656 352 8 8 + - image: solid-color(83, 44, 0, 255, 8, 8) + bounds: 664 352 8 8 + - image: solid-color(84, 44, 0, 255, 8, 8) + bounds: 672 352 8 8 + - image: solid-color(85, 44, 0, 255, 8, 8) + bounds: 680 352 8 8 + - image: solid-color(86, 44, 0, 255, 8, 8) + bounds: 688 352 8 8 + - image: solid-color(87, 44, 0, 255, 8, 8) + bounds: 696 352 8 8 + - image: solid-color(88, 44, 0, 255, 8, 8) + bounds: 704 352 8 8 + - image: solid-color(89, 44, 0, 255, 8, 8) + bounds: 712 352 8 8 + - image: solid-color(90, 44, 0, 255, 8, 8) + bounds: 720 352 8 8 + - image: solid-color(91, 44, 0, 255, 8, 8) + bounds: 728 352 8 8 + - image: solid-color(92, 44, 0, 255, 8, 8) + bounds: 736 352 8 8 + - image: solid-color(93, 44, 0, 255, 8, 8) + bounds: 744 352 8 8 + - image: solid-color(94, 44, 0, 255, 8, 8) + bounds: 752 352 8 8 + - image: solid-color(95, 44, 0, 255, 8, 8) + bounds: 760 352 8 8 + - image: solid-color(96, 44, 0, 255, 8, 8) + bounds: 768 352 8 8 + - image: solid-color(97, 44, 0, 255, 8, 8) + bounds: 776 352 8 8 + - image: solid-color(98, 44, 0, 255, 8, 8) + bounds: 784 352 8 8 + - image: solid-color(99, 44, 0, 255, 8, 8) + bounds: 792 352 8 8 + - image: solid-color(100, 44, 0, 255, 8, 8) + bounds: 800 352 8 8 + - image: solid-color(101, 44, 0, 255, 8, 8) + bounds: 808 352 8 8 + - image: solid-color(102, 44, 0, 255, 8, 8) + bounds: 816 352 8 8 + - image: solid-color(103, 44, 0, 255, 8, 8) + bounds: 824 352 8 8 + - image: solid-color(104, 44, 0, 255, 8, 8) + bounds: 832 352 8 8 + - image: solid-color(105, 44, 0, 255, 8, 8) + bounds: 840 352 8 8 + - image: solid-color(106, 44, 0, 255, 8, 8) + bounds: 848 352 8 8 + - image: solid-color(107, 44, 0, 255, 8, 8) + bounds: 856 352 8 8 + - image: solid-color(108, 44, 0, 255, 8, 8) + bounds: 864 352 8 8 + - image: solid-color(109, 44, 0, 255, 8, 8) + bounds: 872 352 8 8 + - image: solid-color(110, 44, 0, 255, 8, 8) + bounds: 880 352 8 8 + - image: solid-color(111, 44, 0, 255, 8, 8) + bounds: 888 352 8 8 + - image: solid-color(112, 44, 0, 255, 8, 8) + bounds: 896 352 8 8 + - image: solid-color(113, 44, 0, 255, 8, 8) + bounds: 904 352 8 8 + - image: solid-color(114, 44, 0, 255, 8, 8) + bounds: 912 352 8 8 + - image: solid-color(115, 44, 0, 255, 8, 8) + bounds: 920 352 8 8 + - image: solid-color(116, 44, 0, 255, 8, 8) + bounds: 928 352 8 8 + - image: solid-color(117, 44, 0, 255, 8, 8) + bounds: 936 352 8 8 + - image: solid-color(118, 44, 0, 255, 8, 8) + bounds: 944 352 8 8 + - image: solid-color(119, 44, 0, 255, 8, 8) + bounds: 952 352 8 8 + - image: solid-color(120, 44, 0, 255, 8, 8) + bounds: 960 352 8 8 + - image: solid-color(121, 44, 0, 255, 8, 8) + bounds: 968 352 8 8 + - image: solid-color(122, 44, 0, 255, 8, 8) + bounds: 976 352 8 8 + - image: solid-color(123, 44, 0, 255, 8, 8) + bounds: 984 352 8 8 + - image: solid-color(124, 44, 0, 255, 8, 8) + bounds: 992 352 8 8 + - image: solid-color(125, 44, 0, 255, 8, 8) + bounds: 1000 352 8 8 + - image: solid-color(126, 44, 0, 255, 8, 8) + bounds: 1008 352 8 8 + - image: solid-color(127, 44, 0, 255, 8, 8) + bounds: 1016 352 8 8 + - image: solid-color(0, 45, 0, 255, 8, 8) + bounds: 0 360 8 8 + - image: solid-color(1, 45, 0, 255, 8, 8) + bounds: 8 360 8 8 + - image: solid-color(2, 45, 0, 255, 8, 8) + bounds: 16 360 8 8 + - image: solid-color(3, 45, 0, 255, 8, 8) + bounds: 24 360 8 8 + - image: solid-color(4, 45, 0, 255, 8, 8) + bounds: 32 360 8 8 + - image: solid-color(5, 45, 0, 255, 8, 8) + bounds: 40 360 8 8 + - image: solid-color(6, 45, 0, 255, 8, 8) + bounds: 48 360 8 8 + - image: solid-color(7, 45, 0, 255, 8, 8) + bounds: 56 360 8 8 + - image: solid-color(8, 45, 0, 255, 8, 8) + bounds: 64 360 8 8 + - image: solid-color(9, 45, 0, 255, 8, 8) + bounds: 72 360 8 8 + - image: solid-color(10, 45, 0, 255, 8, 8) + bounds: 80 360 8 8 + - image: solid-color(11, 45, 0, 255, 8, 8) + bounds: 88 360 8 8 + - image: solid-color(12, 45, 0, 255, 8, 8) + bounds: 96 360 8 8 + - image: solid-color(13, 45, 0, 255, 8, 8) + bounds: 104 360 8 8 + - image: solid-color(14, 45, 0, 255, 8, 8) + bounds: 112 360 8 8 + - image: solid-color(15, 45, 0, 255, 8, 8) + bounds: 120 360 8 8 + - image: solid-color(16, 45, 0, 255, 8, 8) + bounds: 128 360 8 8 + - image: solid-color(17, 45, 0, 255, 8, 8) + bounds: 136 360 8 8 + - image: solid-color(18, 45, 0, 255, 8, 8) + bounds: 144 360 8 8 + - image: solid-color(19, 45, 0, 255, 8, 8) + bounds: 152 360 8 8 + - image: solid-color(20, 45, 0, 255, 8, 8) + bounds: 160 360 8 8 + - image: solid-color(21, 45, 0, 255, 8, 8) + bounds: 168 360 8 8 + - image: solid-color(22, 45, 0, 255, 8, 8) + bounds: 176 360 8 8 + - image: solid-color(23, 45, 0, 255, 8, 8) + bounds: 184 360 8 8 + - image: solid-color(24, 45, 0, 255, 8, 8) + bounds: 192 360 8 8 + - image: solid-color(25, 45, 0, 255, 8, 8) + bounds: 200 360 8 8 + - image: solid-color(26, 45, 0, 255, 8, 8) + bounds: 208 360 8 8 + - image: solid-color(27, 45, 0, 255, 8, 8) + bounds: 216 360 8 8 + - image: solid-color(28, 45, 0, 255, 8, 8) + bounds: 224 360 8 8 + - image: solid-color(29, 45, 0, 255, 8, 8) + bounds: 232 360 8 8 + - image: solid-color(30, 45, 0, 255, 8, 8) + bounds: 240 360 8 8 + - image: solid-color(31, 45, 0, 255, 8, 8) + bounds: 248 360 8 8 + - image: solid-color(32, 45, 0, 255, 8, 8) + bounds: 256 360 8 8 + - image: solid-color(33, 45, 0, 255, 8, 8) + bounds: 264 360 8 8 + - image: solid-color(34, 45, 0, 255, 8, 8) + bounds: 272 360 8 8 + - image: solid-color(35, 45, 0, 255, 8, 8) + bounds: 280 360 8 8 + - image: solid-color(36, 45, 0, 255, 8, 8) + bounds: 288 360 8 8 + - image: solid-color(37, 45, 0, 255, 8, 8) + bounds: 296 360 8 8 + - image: solid-color(38, 45, 0, 255, 8, 8) + bounds: 304 360 8 8 + - image: solid-color(39, 45, 0, 255, 8, 8) + bounds: 312 360 8 8 + - image: solid-color(40, 45, 0, 255, 8, 8) + bounds: 320 360 8 8 + - image: solid-color(41, 45, 0, 255, 8, 8) + bounds: 328 360 8 8 + - image: solid-color(42, 45, 0, 255, 8, 8) + bounds: 336 360 8 8 + - image: solid-color(43, 45, 0, 255, 8, 8) + bounds: 344 360 8 8 + - image: solid-color(44, 45, 0, 255, 8, 8) + bounds: 352 360 8 8 + - image: solid-color(45, 45, 0, 255, 8, 8) + bounds: 360 360 8 8 + - image: solid-color(46, 45, 0, 255, 8, 8) + bounds: 368 360 8 8 + - image: solid-color(47, 45, 0, 255, 8, 8) + bounds: 376 360 8 8 + - image: solid-color(48, 45, 0, 255, 8, 8) + bounds: 384 360 8 8 + - image: solid-color(49, 45, 0, 255, 8, 8) + bounds: 392 360 8 8 + - image: solid-color(50, 45, 0, 255, 8, 8) + bounds: 400 360 8 8 + - image: solid-color(51, 45, 0, 255, 8, 8) + bounds: 408 360 8 8 + - image: solid-color(52, 45, 0, 255, 8, 8) + bounds: 416 360 8 8 + - image: solid-color(53, 45, 0, 255, 8, 8) + bounds: 424 360 8 8 + - image: solid-color(54, 45, 0, 255, 8, 8) + bounds: 432 360 8 8 + - image: solid-color(55, 45, 0, 255, 8, 8) + bounds: 440 360 8 8 + - image: solid-color(56, 45, 0, 255, 8, 8) + bounds: 448 360 8 8 + - image: solid-color(57, 45, 0, 255, 8, 8) + bounds: 456 360 8 8 + - image: solid-color(58, 45, 0, 255, 8, 8) + bounds: 464 360 8 8 + - image: solid-color(59, 45, 0, 255, 8, 8) + bounds: 472 360 8 8 + - image: solid-color(60, 45, 0, 255, 8, 8) + bounds: 480 360 8 8 + - image: solid-color(61, 45, 0, 255, 8, 8) + bounds: 488 360 8 8 + - image: solid-color(62, 45, 0, 255, 8, 8) + bounds: 496 360 8 8 + - image: solid-color(63, 45, 0, 255, 8, 8) + bounds: 504 360 8 8 + - image: solid-color(64, 45, 0, 255, 8, 8) + bounds: 512 360 8 8 + - image: solid-color(65, 45, 0, 255, 8, 8) + bounds: 520 360 8 8 + - image: solid-color(66, 45, 0, 255, 8, 8) + bounds: 528 360 8 8 + - image: solid-color(67, 45, 0, 255, 8, 8) + bounds: 536 360 8 8 + - image: solid-color(68, 45, 0, 255, 8, 8) + bounds: 544 360 8 8 + - image: solid-color(69, 45, 0, 255, 8, 8) + bounds: 552 360 8 8 + - image: solid-color(70, 45, 0, 255, 8, 8) + bounds: 560 360 8 8 + - image: solid-color(71, 45, 0, 255, 8, 8) + bounds: 568 360 8 8 + - image: solid-color(72, 45, 0, 255, 8, 8) + bounds: 576 360 8 8 + - image: solid-color(73, 45, 0, 255, 8, 8) + bounds: 584 360 8 8 + - image: solid-color(74, 45, 0, 255, 8, 8) + bounds: 592 360 8 8 + - image: solid-color(75, 45, 0, 255, 8, 8) + bounds: 600 360 8 8 + - image: solid-color(76, 45, 0, 255, 8, 8) + bounds: 608 360 8 8 + - image: solid-color(77, 45, 0, 255, 8, 8) + bounds: 616 360 8 8 + - image: solid-color(78, 45, 0, 255, 8, 8) + bounds: 624 360 8 8 + - image: solid-color(79, 45, 0, 255, 8, 8) + bounds: 632 360 8 8 + - image: solid-color(80, 45, 0, 255, 8, 8) + bounds: 640 360 8 8 + - image: solid-color(81, 45, 0, 255, 8, 8) + bounds: 648 360 8 8 + - image: solid-color(82, 45, 0, 255, 8, 8) + bounds: 656 360 8 8 + - image: solid-color(83, 45, 0, 255, 8, 8) + bounds: 664 360 8 8 + - image: solid-color(84, 45, 0, 255, 8, 8) + bounds: 672 360 8 8 + - image: solid-color(85, 45, 0, 255, 8, 8) + bounds: 680 360 8 8 + - image: solid-color(86, 45, 0, 255, 8, 8) + bounds: 688 360 8 8 + - image: solid-color(87, 45, 0, 255, 8, 8) + bounds: 696 360 8 8 + - image: solid-color(88, 45, 0, 255, 8, 8) + bounds: 704 360 8 8 + - image: solid-color(89, 45, 0, 255, 8, 8) + bounds: 712 360 8 8 + - image: solid-color(90, 45, 0, 255, 8, 8) + bounds: 720 360 8 8 + - image: solid-color(91, 45, 0, 255, 8, 8) + bounds: 728 360 8 8 + - image: solid-color(92, 45, 0, 255, 8, 8) + bounds: 736 360 8 8 + - image: solid-color(93, 45, 0, 255, 8, 8) + bounds: 744 360 8 8 + - image: solid-color(94, 45, 0, 255, 8, 8) + bounds: 752 360 8 8 + - image: solid-color(95, 45, 0, 255, 8, 8) + bounds: 760 360 8 8 + - image: solid-color(96, 45, 0, 255, 8, 8) + bounds: 768 360 8 8 + - image: solid-color(97, 45, 0, 255, 8, 8) + bounds: 776 360 8 8 + - image: solid-color(98, 45, 0, 255, 8, 8) + bounds: 784 360 8 8 + - image: solid-color(99, 45, 0, 255, 8, 8) + bounds: 792 360 8 8 + - image: solid-color(100, 45, 0, 255, 8, 8) + bounds: 800 360 8 8 + - image: solid-color(101, 45, 0, 255, 8, 8) + bounds: 808 360 8 8 + - image: solid-color(102, 45, 0, 255, 8, 8) + bounds: 816 360 8 8 + - image: solid-color(103, 45, 0, 255, 8, 8) + bounds: 824 360 8 8 + - image: solid-color(104, 45, 0, 255, 8, 8) + bounds: 832 360 8 8 + - image: solid-color(105, 45, 0, 255, 8, 8) + bounds: 840 360 8 8 + - image: solid-color(106, 45, 0, 255, 8, 8) + bounds: 848 360 8 8 + - image: solid-color(107, 45, 0, 255, 8, 8) + bounds: 856 360 8 8 + - image: solid-color(108, 45, 0, 255, 8, 8) + bounds: 864 360 8 8 + - image: solid-color(109, 45, 0, 255, 8, 8) + bounds: 872 360 8 8 + - image: solid-color(110, 45, 0, 255, 8, 8) + bounds: 880 360 8 8 + - image: solid-color(111, 45, 0, 255, 8, 8) + bounds: 888 360 8 8 + - image: solid-color(112, 45, 0, 255, 8, 8) + bounds: 896 360 8 8 + - image: solid-color(113, 45, 0, 255, 8, 8) + bounds: 904 360 8 8 + - image: solid-color(114, 45, 0, 255, 8, 8) + bounds: 912 360 8 8 + - image: solid-color(115, 45, 0, 255, 8, 8) + bounds: 920 360 8 8 + - image: solid-color(116, 45, 0, 255, 8, 8) + bounds: 928 360 8 8 + - image: solid-color(117, 45, 0, 255, 8, 8) + bounds: 936 360 8 8 + - image: solid-color(118, 45, 0, 255, 8, 8) + bounds: 944 360 8 8 + - image: solid-color(119, 45, 0, 255, 8, 8) + bounds: 952 360 8 8 + - image: solid-color(120, 45, 0, 255, 8, 8) + bounds: 960 360 8 8 + - image: solid-color(121, 45, 0, 255, 8, 8) + bounds: 968 360 8 8 + - image: solid-color(122, 45, 0, 255, 8, 8) + bounds: 976 360 8 8 + - image: solid-color(123, 45, 0, 255, 8, 8) + bounds: 984 360 8 8 + - image: solid-color(124, 45, 0, 255, 8, 8) + bounds: 992 360 8 8 + - image: solid-color(125, 45, 0, 255, 8, 8) + bounds: 1000 360 8 8 + - image: solid-color(126, 45, 0, 255, 8, 8) + bounds: 1008 360 8 8 + - image: solid-color(127, 45, 0, 255, 8, 8) + bounds: 1016 360 8 8 + - image: solid-color(0, 46, 0, 255, 8, 8) + bounds: 0 368 8 8 + - image: solid-color(1, 46, 0, 255, 8, 8) + bounds: 8 368 8 8 + - image: solid-color(2, 46, 0, 255, 8, 8) + bounds: 16 368 8 8 + - image: solid-color(3, 46, 0, 255, 8, 8) + bounds: 24 368 8 8 + - image: solid-color(4, 46, 0, 255, 8, 8) + bounds: 32 368 8 8 + - image: solid-color(5, 46, 0, 255, 8, 8) + bounds: 40 368 8 8 + - image: solid-color(6, 46, 0, 255, 8, 8) + bounds: 48 368 8 8 + - image: solid-color(7, 46, 0, 255, 8, 8) + bounds: 56 368 8 8 + - image: solid-color(8, 46, 0, 255, 8, 8) + bounds: 64 368 8 8 + - image: solid-color(9, 46, 0, 255, 8, 8) + bounds: 72 368 8 8 + - image: solid-color(10, 46, 0, 255, 8, 8) + bounds: 80 368 8 8 + - image: solid-color(11, 46, 0, 255, 8, 8) + bounds: 88 368 8 8 + - image: solid-color(12, 46, 0, 255, 8, 8) + bounds: 96 368 8 8 + - image: solid-color(13, 46, 0, 255, 8, 8) + bounds: 104 368 8 8 + - image: solid-color(14, 46, 0, 255, 8, 8) + bounds: 112 368 8 8 + - image: solid-color(15, 46, 0, 255, 8, 8) + bounds: 120 368 8 8 + - image: solid-color(16, 46, 0, 255, 8, 8) + bounds: 128 368 8 8 + - image: solid-color(17, 46, 0, 255, 8, 8) + bounds: 136 368 8 8 + - image: solid-color(18, 46, 0, 255, 8, 8) + bounds: 144 368 8 8 + - image: solid-color(19, 46, 0, 255, 8, 8) + bounds: 152 368 8 8 + - image: solid-color(20, 46, 0, 255, 8, 8) + bounds: 160 368 8 8 + - image: solid-color(21, 46, 0, 255, 8, 8) + bounds: 168 368 8 8 + - image: solid-color(22, 46, 0, 255, 8, 8) + bounds: 176 368 8 8 + - image: solid-color(23, 46, 0, 255, 8, 8) + bounds: 184 368 8 8 + - image: solid-color(24, 46, 0, 255, 8, 8) + bounds: 192 368 8 8 + - image: solid-color(25, 46, 0, 255, 8, 8) + bounds: 200 368 8 8 + - image: solid-color(26, 46, 0, 255, 8, 8) + bounds: 208 368 8 8 + - image: solid-color(27, 46, 0, 255, 8, 8) + bounds: 216 368 8 8 + - image: solid-color(28, 46, 0, 255, 8, 8) + bounds: 224 368 8 8 + - image: solid-color(29, 46, 0, 255, 8, 8) + bounds: 232 368 8 8 + - image: solid-color(30, 46, 0, 255, 8, 8) + bounds: 240 368 8 8 + - image: solid-color(31, 46, 0, 255, 8, 8) + bounds: 248 368 8 8 + - image: solid-color(32, 46, 0, 255, 8, 8) + bounds: 256 368 8 8 + - image: solid-color(33, 46, 0, 255, 8, 8) + bounds: 264 368 8 8 + - image: solid-color(34, 46, 0, 255, 8, 8) + bounds: 272 368 8 8 + - image: solid-color(35, 46, 0, 255, 8, 8) + bounds: 280 368 8 8 + - image: solid-color(36, 46, 0, 255, 8, 8) + bounds: 288 368 8 8 + - image: solid-color(37, 46, 0, 255, 8, 8) + bounds: 296 368 8 8 + - image: solid-color(38, 46, 0, 255, 8, 8) + bounds: 304 368 8 8 + - image: solid-color(39, 46, 0, 255, 8, 8) + bounds: 312 368 8 8 + - image: solid-color(40, 46, 0, 255, 8, 8) + bounds: 320 368 8 8 + - image: solid-color(41, 46, 0, 255, 8, 8) + bounds: 328 368 8 8 + - image: solid-color(42, 46, 0, 255, 8, 8) + bounds: 336 368 8 8 + - image: solid-color(43, 46, 0, 255, 8, 8) + bounds: 344 368 8 8 + - image: solid-color(44, 46, 0, 255, 8, 8) + bounds: 352 368 8 8 + - image: solid-color(45, 46, 0, 255, 8, 8) + bounds: 360 368 8 8 + - image: solid-color(46, 46, 0, 255, 8, 8) + bounds: 368 368 8 8 + - image: solid-color(47, 46, 0, 255, 8, 8) + bounds: 376 368 8 8 + - image: solid-color(48, 46, 0, 255, 8, 8) + bounds: 384 368 8 8 + - image: solid-color(49, 46, 0, 255, 8, 8) + bounds: 392 368 8 8 + - image: solid-color(50, 46, 0, 255, 8, 8) + bounds: 400 368 8 8 + - image: solid-color(51, 46, 0, 255, 8, 8) + bounds: 408 368 8 8 + - image: solid-color(52, 46, 0, 255, 8, 8) + bounds: 416 368 8 8 + - image: solid-color(53, 46, 0, 255, 8, 8) + bounds: 424 368 8 8 + - image: solid-color(54, 46, 0, 255, 8, 8) + bounds: 432 368 8 8 + - image: solid-color(55, 46, 0, 255, 8, 8) + bounds: 440 368 8 8 + - image: solid-color(56, 46, 0, 255, 8, 8) + bounds: 448 368 8 8 + - image: solid-color(57, 46, 0, 255, 8, 8) + bounds: 456 368 8 8 + - image: solid-color(58, 46, 0, 255, 8, 8) + bounds: 464 368 8 8 + - image: solid-color(59, 46, 0, 255, 8, 8) + bounds: 472 368 8 8 + - image: solid-color(60, 46, 0, 255, 8, 8) + bounds: 480 368 8 8 + - image: solid-color(61, 46, 0, 255, 8, 8) + bounds: 488 368 8 8 + - image: solid-color(62, 46, 0, 255, 8, 8) + bounds: 496 368 8 8 + - image: solid-color(63, 46, 0, 255, 8, 8) + bounds: 504 368 8 8 + - image: solid-color(64, 46, 0, 255, 8, 8) + bounds: 512 368 8 8 + - image: solid-color(65, 46, 0, 255, 8, 8) + bounds: 520 368 8 8 + - image: solid-color(66, 46, 0, 255, 8, 8) + bounds: 528 368 8 8 + - image: solid-color(67, 46, 0, 255, 8, 8) + bounds: 536 368 8 8 + - image: solid-color(68, 46, 0, 255, 8, 8) + bounds: 544 368 8 8 + - image: solid-color(69, 46, 0, 255, 8, 8) + bounds: 552 368 8 8 + - image: solid-color(70, 46, 0, 255, 8, 8) + bounds: 560 368 8 8 + - image: solid-color(71, 46, 0, 255, 8, 8) + bounds: 568 368 8 8 + - image: solid-color(72, 46, 0, 255, 8, 8) + bounds: 576 368 8 8 + - image: solid-color(73, 46, 0, 255, 8, 8) + bounds: 584 368 8 8 + - image: solid-color(74, 46, 0, 255, 8, 8) + bounds: 592 368 8 8 + - image: solid-color(75, 46, 0, 255, 8, 8) + bounds: 600 368 8 8 + - image: solid-color(76, 46, 0, 255, 8, 8) + bounds: 608 368 8 8 + - image: solid-color(77, 46, 0, 255, 8, 8) + bounds: 616 368 8 8 + - image: solid-color(78, 46, 0, 255, 8, 8) + bounds: 624 368 8 8 + - image: solid-color(79, 46, 0, 255, 8, 8) + bounds: 632 368 8 8 + - image: solid-color(80, 46, 0, 255, 8, 8) + bounds: 640 368 8 8 + - image: solid-color(81, 46, 0, 255, 8, 8) + bounds: 648 368 8 8 + - image: solid-color(82, 46, 0, 255, 8, 8) + bounds: 656 368 8 8 + - image: solid-color(83, 46, 0, 255, 8, 8) + bounds: 664 368 8 8 + - image: solid-color(84, 46, 0, 255, 8, 8) + bounds: 672 368 8 8 + - image: solid-color(85, 46, 0, 255, 8, 8) + bounds: 680 368 8 8 + - image: solid-color(86, 46, 0, 255, 8, 8) + bounds: 688 368 8 8 + - image: solid-color(87, 46, 0, 255, 8, 8) + bounds: 696 368 8 8 + - image: solid-color(88, 46, 0, 255, 8, 8) + bounds: 704 368 8 8 + - image: solid-color(89, 46, 0, 255, 8, 8) + bounds: 712 368 8 8 + - image: solid-color(90, 46, 0, 255, 8, 8) + bounds: 720 368 8 8 + - image: solid-color(91, 46, 0, 255, 8, 8) + bounds: 728 368 8 8 + - image: solid-color(92, 46, 0, 255, 8, 8) + bounds: 736 368 8 8 + - image: solid-color(93, 46, 0, 255, 8, 8) + bounds: 744 368 8 8 + - image: solid-color(94, 46, 0, 255, 8, 8) + bounds: 752 368 8 8 + - image: solid-color(95, 46, 0, 255, 8, 8) + bounds: 760 368 8 8 + - image: solid-color(96, 46, 0, 255, 8, 8) + bounds: 768 368 8 8 + - image: solid-color(97, 46, 0, 255, 8, 8) + bounds: 776 368 8 8 + - image: solid-color(98, 46, 0, 255, 8, 8) + bounds: 784 368 8 8 + - image: solid-color(99, 46, 0, 255, 8, 8) + bounds: 792 368 8 8 + - image: solid-color(100, 46, 0, 255, 8, 8) + bounds: 800 368 8 8 + - image: solid-color(101, 46, 0, 255, 8, 8) + bounds: 808 368 8 8 + - image: solid-color(102, 46, 0, 255, 8, 8) + bounds: 816 368 8 8 + - image: solid-color(103, 46, 0, 255, 8, 8) + bounds: 824 368 8 8 + - image: solid-color(104, 46, 0, 255, 8, 8) + bounds: 832 368 8 8 + - image: solid-color(105, 46, 0, 255, 8, 8) + bounds: 840 368 8 8 + - image: solid-color(106, 46, 0, 255, 8, 8) + bounds: 848 368 8 8 + - image: solid-color(107, 46, 0, 255, 8, 8) + bounds: 856 368 8 8 + - image: solid-color(108, 46, 0, 255, 8, 8) + bounds: 864 368 8 8 + - image: solid-color(109, 46, 0, 255, 8, 8) + bounds: 872 368 8 8 + - image: solid-color(110, 46, 0, 255, 8, 8) + bounds: 880 368 8 8 + - image: solid-color(111, 46, 0, 255, 8, 8) + bounds: 888 368 8 8 + - image: solid-color(112, 46, 0, 255, 8, 8) + bounds: 896 368 8 8 + - image: solid-color(113, 46, 0, 255, 8, 8) + bounds: 904 368 8 8 + - image: solid-color(114, 46, 0, 255, 8, 8) + bounds: 912 368 8 8 + - image: solid-color(115, 46, 0, 255, 8, 8) + bounds: 920 368 8 8 + - image: solid-color(116, 46, 0, 255, 8, 8) + bounds: 928 368 8 8 + - image: solid-color(117, 46, 0, 255, 8, 8) + bounds: 936 368 8 8 + - image: solid-color(118, 46, 0, 255, 8, 8) + bounds: 944 368 8 8 + - image: solid-color(119, 46, 0, 255, 8, 8) + bounds: 952 368 8 8 + - image: solid-color(120, 46, 0, 255, 8, 8) + bounds: 960 368 8 8 + - image: solid-color(121, 46, 0, 255, 8, 8) + bounds: 968 368 8 8 + - image: solid-color(122, 46, 0, 255, 8, 8) + bounds: 976 368 8 8 + - image: solid-color(123, 46, 0, 255, 8, 8) + bounds: 984 368 8 8 + - image: solid-color(124, 46, 0, 255, 8, 8) + bounds: 992 368 8 8 + - image: solid-color(125, 46, 0, 255, 8, 8) + bounds: 1000 368 8 8 + - image: solid-color(126, 46, 0, 255, 8, 8) + bounds: 1008 368 8 8 + - image: solid-color(127, 46, 0, 255, 8, 8) + bounds: 1016 368 8 8 + - image: solid-color(0, 47, 0, 255, 8, 8) + bounds: 0 376 8 8 + - image: solid-color(1, 47, 0, 255, 8, 8) + bounds: 8 376 8 8 + - image: solid-color(2, 47, 0, 255, 8, 8) + bounds: 16 376 8 8 + - image: solid-color(3, 47, 0, 255, 8, 8) + bounds: 24 376 8 8 + - image: solid-color(4, 47, 0, 255, 8, 8) + bounds: 32 376 8 8 + - image: solid-color(5, 47, 0, 255, 8, 8) + bounds: 40 376 8 8 + - image: solid-color(6, 47, 0, 255, 8, 8) + bounds: 48 376 8 8 + - image: solid-color(7, 47, 0, 255, 8, 8) + bounds: 56 376 8 8 + - image: solid-color(8, 47, 0, 255, 8, 8) + bounds: 64 376 8 8 + - image: solid-color(9, 47, 0, 255, 8, 8) + bounds: 72 376 8 8 + - image: solid-color(10, 47, 0, 255, 8, 8) + bounds: 80 376 8 8 + - image: solid-color(11, 47, 0, 255, 8, 8) + bounds: 88 376 8 8 + - image: solid-color(12, 47, 0, 255, 8, 8) + bounds: 96 376 8 8 + - image: solid-color(13, 47, 0, 255, 8, 8) + bounds: 104 376 8 8 + - image: solid-color(14, 47, 0, 255, 8, 8) + bounds: 112 376 8 8 + - image: solid-color(15, 47, 0, 255, 8, 8) + bounds: 120 376 8 8 + - image: solid-color(16, 47, 0, 255, 8, 8) + bounds: 128 376 8 8 + - image: solid-color(17, 47, 0, 255, 8, 8) + bounds: 136 376 8 8 + - image: solid-color(18, 47, 0, 255, 8, 8) + bounds: 144 376 8 8 + - image: solid-color(19, 47, 0, 255, 8, 8) + bounds: 152 376 8 8 + - image: solid-color(20, 47, 0, 255, 8, 8) + bounds: 160 376 8 8 + - image: solid-color(21, 47, 0, 255, 8, 8) + bounds: 168 376 8 8 + - image: solid-color(22, 47, 0, 255, 8, 8) + bounds: 176 376 8 8 + - image: solid-color(23, 47, 0, 255, 8, 8) + bounds: 184 376 8 8 + - image: solid-color(24, 47, 0, 255, 8, 8) + bounds: 192 376 8 8 + - image: solid-color(25, 47, 0, 255, 8, 8) + bounds: 200 376 8 8 + - image: solid-color(26, 47, 0, 255, 8, 8) + bounds: 208 376 8 8 + - image: solid-color(27, 47, 0, 255, 8, 8) + bounds: 216 376 8 8 + - image: solid-color(28, 47, 0, 255, 8, 8) + bounds: 224 376 8 8 + - image: solid-color(29, 47, 0, 255, 8, 8) + bounds: 232 376 8 8 + - image: solid-color(30, 47, 0, 255, 8, 8) + bounds: 240 376 8 8 + - image: solid-color(31, 47, 0, 255, 8, 8) + bounds: 248 376 8 8 + - image: solid-color(32, 47, 0, 255, 8, 8) + bounds: 256 376 8 8 + - image: solid-color(33, 47, 0, 255, 8, 8) + bounds: 264 376 8 8 + - image: solid-color(34, 47, 0, 255, 8, 8) + bounds: 272 376 8 8 + - image: solid-color(35, 47, 0, 255, 8, 8) + bounds: 280 376 8 8 + - image: solid-color(36, 47, 0, 255, 8, 8) + bounds: 288 376 8 8 + - image: solid-color(37, 47, 0, 255, 8, 8) + bounds: 296 376 8 8 + - image: solid-color(38, 47, 0, 255, 8, 8) + bounds: 304 376 8 8 + - image: solid-color(39, 47, 0, 255, 8, 8) + bounds: 312 376 8 8 + - image: solid-color(40, 47, 0, 255, 8, 8) + bounds: 320 376 8 8 + - image: solid-color(41, 47, 0, 255, 8, 8) + bounds: 328 376 8 8 + - image: solid-color(42, 47, 0, 255, 8, 8) + bounds: 336 376 8 8 + - image: solid-color(43, 47, 0, 255, 8, 8) + bounds: 344 376 8 8 + - image: solid-color(44, 47, 0, 255, 8, 8) + bounds: 352 376 8 8 + - image: solid-color(45, 47, 0, 255, 8, 8) + bounds: 360 376 8 8 + - image: solid-color(46, 47, 0, 255, 8, 8) + bounds: 368 376 8 8 + - image: solid-color(47, 47, 0, 255, 8, 8) + bounds: 376 376 8 8 + - image: solid-color(48, 47, 0, 255, 8, 8) + bounds: 384 376 8 8 + - image: solid-color(49, 47, 0, 255, 8, 8) + bounds: 392 376 8 8 + - image: solid-color(50, 47, 0, 255, 8, 8) + bounds: 400 376 8 8 + - image: solid-color(51, 47, 0, 255, 8, 8) + bounds: 408 376 8 8 + - image: solid-color(52, 47, 0, 255, 8, 8) + bounds: 416 376 8 8 + - image: solid-color(53, 47, 0, 255, 8, 8) + bounds: 424 376 8 8 + - image: solid-color(54, 47, 0, 255, 8, 8) + bounds: 432 376 8 8 + - image: solid-color(55, 47, 0, 255, 8, 8) + bounds: 440 376 8 8 + - image: solid-color(56, 47, 0, 255, 8, 8) + bounds: 448 376 8 8 + - image: solid-color(57, 47, 0, 255, 8, 8) + bounds: 456 376 8 8 + - image: solid-color(58, 47, 0, 255, 8, 8) + bounds: 464 376 8 8 + - image: solid-color(59, 47, 0, 255, 8, 8) + bounds: 472 376 8 8 + - image: solid-color(60, 47, 0, 255, 8, 8) + bounds: 480 376 8 8 + - image: solid-color(61, 47, 0, 255, 8, 8) + bounds: 488 376 8 8 + - image: solid-color(62, 47, 0, 255, 8, 8) + bounds: 496 376 8 8 + - image: solid-color(63, 47, 0, 255, 8, 8) + bounds: 504 376 8 8 + - image: solid-color(64, 47, 0, 255, 8, 8) + bounds: 512 376 8 8 + - image: solid-color(65, 47, 0, 255, 8, 8) + bounds: 520 376 8 8 + - image: solid-color(66, 47, 0, 255, 8, 8) + bounds: 528 376 8 8 + - image: solid-color(67, 47, 0, 255, 8, 8) + bounds: 536 376 8 8 + - image: solid-color(68, 47, 0, 255, 8, 8) + bounds: 544 376 8 8 + - image: solid-color(69, 47, 0, 255, 8, 8) + bounds: 552 376 8 8 + - image: solid-color(70, 47, 0, 255, 8, 8) + bounds: 560 376 8 8 + - image: solid-color(71, 47, 0, 255, 8, 8) + bounds: 568 376 8 8 + - image: solid-color(72, 47, 0, 255, 8, 8) + bounds: 576 376 8 8 + - image: solid-color(73, 47, 0, 255, 8, 8) + bounds: 584 376 8 8 + - image: solid-color(74, 47, 0, 255, 8, 8) + bounds: 592 376 8 8 + - image: solid-color(75, 47, 0, 255, 8, 8) + bounds: 600 376 8 8 + - image: solid-color(76, 47, 0, 255, 8, 8) + bounds: 608 376 8 8 + - image: solid-color(77, 47, 0, 255, 8, 8) + bounds: 616 376 8 8 + - image: solid-color(78, 47, 0, 255, 8, 8) + bounds: 624 376 8 8 + - image: solid-color(79, 47, 0, 255, 8, 8) + bounds: 632 376 8 8 + - image: solid-color(80, 47, 0, 255, 8, 8) + bounds: 640 376 8 8 + - image: solid-color(81, 47, 0, 255, 8, 8) + bounds: 648 376 8 8 + - image: solid-color(82, 47, 0, 255, 8, 8) + bounds: 656 376 8 8 + - image: solid-color(83, 47, 0, 255, 8, 8) + bounds: 664 376 8 8 + - image: solid-color(84, 47, 0, 255, 8, 8) + bounds: 672 376 8 8 + - image: solid-color(85, 47, 0, 255, 8, 8) + bounds: 680 376 8 8 + - image: solid-color(86, 47, 0, 255, 8, 8) + bounds: 688 376 8 8 + - image: solid-color(87, 47, 0, 255, 8, 8) + bounds: 696 376 8 8 + - image: solid-color(88, 47, 0, 255, 8, 8) + bounds: 704 376 8 8 + - image: solid-color(89, 47, 0, 255, 8, 8) + bounds: 712 376 8 8 + - image: solid-color(90, 47, 0, 255, 8, 8) + bounds: 720 376 8 8 + - image: solid-color(91, 47, 0, 255, 8, 8) + bounds: 728 376 8 8 + - image: solid-color(92, 47, 0, 255, 8, 8) + bounds: 736 376 8 8 + - image: solid-color(93, 47, 0, 255, 8, 8) + bounds: 744 376 8 8 + - image: solid-color(94, 47, 0, 255, 8, 8) + bounds: 752 376 8 8 + - image: solid-color(95, 47, 0, 255, 8, 8) + bounds: 760 376 8 8 + - image: solid-color(96, 47, 0, 255, 8, 8) + bounds: 768 376 8 8 + - image: solid-color(97, 47, 0, 255, 8, 8) + bounds: 776 376 8 8 + - image: solid-color(98, 47, 0, 255, 8, 8) + bounds: 784 376 8 8 + - image: solid-color(99, 47, 0, 255, 8, 8) + bounds: 792 376 8 8 + - image: solid-color(100, 47, 0, 255, 8, 8) + bounds: 800 376 8 8 + - image: solid-color(101, 47, 0, 255, 8, 8) + bounds: 808 376 8 8 + - image: solid-color(102, 47, 0, 255, 8, 8) + bounds: 816 376 8 8 + - image: solid-color(103, 47, 0, 255, 8, 8) + bounds: 824 376 8 8 + - image: solid-color(104, 47, 0, 255, 8, 8) + bounds: 832 376 8 8 + - image: solid-color(105, 47, 0, 255, 8, 8) + bounds: 840 376 8 8 + - image: solid-color(106, 47, 0, 255, 8, 8) + bounds: 848 376 8 8 + - image: solid-color(107, 47, 0, 255, 8, 8) + bounds: 856 376 8 8 + - image: solid-color(108, 47, 0, 255, 8, 8) + bounds: 864 376 8 8 + - image: solid-color(109, 47, 0, 255, 8, 8) + bounds: 872 376 8 8 + - image: solid-color(110, 47, 0, 255, 8, 8) + bounds: 880 376 8 8 + - image: solid-color(111, 47, 0, 255, 8, 8) + bounds: 888 376 8 8 + - image: solid-color(112, 47, 0, 255, 8, 8) + bounds: 896 376 8 8 + - image: solid-color(113, 47, 0, 255, 8, 8) + bounds: 904 376 8 8 + - image: solid-color(114, 47, 0, 255, 8, 8) + bounds: 912 376 8 8 + - image: solid-color(115, 47, 0, 255, 8, 8) + bounds: 920 376 8 8 + - image: solid-color(116, 47, 0, 255, 8, 8) + bounds: 928 376 8 8 + - image: solid-color(117, 47, 0, 255, 8, 8) + bounds: 936 376 8 8 + - image: solid-color(118, 47, 0, 255, 8, 8) + bounds: 944 376 8 8 + - image: solid-color(119, 47, 0, 255, 8, 8) + bounds: 952 376 8 8 + - image: solid-color(120, 47, 0, 255, 8, 8) + bounds: 960 376 8 8 + - image: solid-color(121, 47, 0, 255, 8, 8) + bounds: 968 376 8 8 + - image: solid-color(122, 47, 0, 255, 8, 8) + bounds: 976 376 8 8 + - image: solid-color(123, 47, 0, 255, 8, 8) + bounds: 984 376 8 8 + - image: solid-color(124, 47, 0, 255, 8, 8) + bounds: 992 376 8 8 + - image: solid-color(125, 47, 0, 255, 8, 8) + bounds: 1000 376 8 8 + - image: solid-color(126, 47, 0, 255, 8, 8) + bounds: 1008 376 8 8 + - image: solid-color(127, 47, 0, 255, 8, 8) + bounds: 1016 376 8 8 + - image: solid-color(0, 48, 0, 255, 8, 8) + bounds: 0 384 8 8 + - image: solid-color(1, 48, 0, 255, 8, 8) + bounds: 8 384 8 8 + - image: solid-color(2, 48, 0, 255, 8, 8) + bounds: 16 384 8 8 + - image: solid-color(3, 48, 0, 255, 8, 8) + bounds: 24 384 8 8 + - image: solid-color(4, 48, 0, 255, 8, 8) + bounds: 32 384 8 8 + - image: solid-color(5, 48, 0, 255, 8, 8) + bounds: 40 384 8 8 + - image: solid-color(6, 48, 0, 255, 8, 8) + bounds: 48 384 8 8 + - image: solid-color(7, 48, 0, 255, 8, 8) + bounds: 56 384 8 8 + - image: solid-color(8, 48, 0, 255, 8, 8) + bounds: 64 384 8 8 + - image: solid-color(9, 48, 0, 255, 8, 8) + bounds: 72 384 8 8 + - image: solid-color(10, 48, 0, 255, 8, 8) + bounds: 80 384 8 8 + - image: solid-color(11, 48, 0, 255, 8, 8) + bounds: 88 384 8 8 + - image: solid-color(12, 48, 0, 255, 8, 8) + bounds: 96 384 8 8 + - image: solid-color(13, 48, 0, 255, 8, 8) + bounds: 104 384 8 8 + - image: solid-color(14, 48, 0, 255, 8, 8) + bounds: 112 384 8 8 + - image: solid-color(15, 48, 0, 255, 8, 8) + bounds: 120 384 8 8 + - image: solid-color(16, 48, 0, 255, 8, 8) + bounds: 128 384 8 8 + - image: solid-color(17, 48, 0, 255, 8, 8) + bounds: 136 384 8 8 + - image: solid-color(18, 48, 0, 255, 8, 8) + bounds: 144 384 8 8 + - image: solid-color(19, 48, 0, 255, 8, 8) + bounds: 152 384 8 8 + - image: solid-color(20, 48, 0, 255, 8, 8) + bounds: 160 384 8 8 + - image: solid-color(21, 48, 0, 255, 8, 8) + bounds: 168 384 8 8 + - image: solid-color(22, 48, 0, 255, 8, 8) + bounds: 176 384 8 8 + - image: solid-color(23, 48, 0, 255, 8, 8) + bounds: 184 384 8 8 + - image: solid-color(24, 48, 0, 255, 8, 8) + bounds: 192 384 8 8 + - image: solid-color(25, 48, 0, 255, 8, 8) + bounds: 200 384 8 8 + - image: solid-color(26, 48, 0, 255, 8, 8) + bounds: 208 384 8 8 + - image: solid-color(27, 48, 0, 255, 8, 8) + bounds: 216 384 8 8 + - image: solid-color(28, 48, 0, 255, 8, 8) + bounds: 224 384 8 8 + - image: solid-color(29, 48, 0, 255, 8, 8) + bounds: 232 384 8 8 + - image: solid-color(30, 48, 0, 255, 8, 8) + bounds: 240 384 8 8 + - image: solid-color(31, 48, 0, 255, 8, 8) + bounds: 248 384 8 8 + - image: solid-color(32, 48, 0, 255, 8, 8) + bounds: 256 384 8 8 + - image: solid-color(33, 48, 0, 255, 8, 8) + bounds: 264 384 8 8 + - image: solid-color(34, 48, 0, 255, 8, 8) + bounds: 272 384 8 8 + - image: solid-color(35, 48, 0, 255, 8, 8) + bounds: 280 384 8 8 + - image: solid-color(36, 48, 0, 255, 8, 8) + bounds: 288 384 8 8 + - image: solid-color(37, 48, 0, 255, 8, 8) + bounds: 296 384 8 8 + - image: solid-color(38, 48, 0, 255, 8, 8) + bounds: 304 384 8 8 + - image: solid-color(39, 48, 0, 255, 8, 8) + bounds: 312 384 8 8 + - image: solid-color(40, 48, 0, 255, 8, 8) + bounds: 320 384 8 8 + - image: solid-color(41, 48, 0, 255, 8, 8) + bounds: 328 384 8 8 + - image: solid-color(42, 48, 0, 255, 8, 8) + bounds: 336 384 8 8 + - image: solid-color(43, 48, 0, 255, 8, 8) + bounds: 344 384 8 8 + - image: solid-color(44, 48, 0, 255, 8, 8) + bounds: 352 384 8 8 + - image: solid-color(45, 48, 0, 255, 8, 8) + bounds: 360 384 8 8 + - image: solid-color(46, 48, 0, 255, 8, 8) + bounds: 368 384 8 8 + - image: solid-color(47, 48, 0, 255, 8, 8) + bounds: 376 384 8 8 + - image: solid-color(48, 48, 0, 255, 8, 8) + bounds: 384 384 8 8 + - image: solid-color(49, 48, 0, 255, 8, 8) + bounds: 392 384 8 8 + - image: solid-color(50, 48, 0, 255, 8, 8) + bounds: 400 384 8 8 + - image: solid-color(51, 48, 0, 255, 8, 8) + bounds: 408 384 8 8 + - image: solid-color(52, 48, 0, 255, 8, 8) + bounds: 416 384 8 8 + - image: solid-color(53, 48, 0, 255, 8, 8) + bounds: 424 384 8 8 + - image: solid-color(54, 48, 0, 255, 8, 8) + bounds: 432 384 8 8 + - image: solid-color(55, 48, 0, 255, 8, 8) + bounds: 440 384 8 8 + - image: solid-color(56, 48, 0, 255, 8, 8) + bounds: 448 384 8 8 + - image: solid-color(57, 48, 0, 255, 8, 8) + bounds: 456 384 8 8 + - image: solid-color(58, 48, 0, 255, 8, 8) + bounds: 464 384 8 8 + - image: solid-color(59, 48, 0, 255, 8, 8) + bounds: 472 384 8 8 + - image: solid-color(60, 48, 0, 255, 8, 8) + bounds: 480 384 8 8 + - image: solid-color(61, 48, 0, 255, 8, 8) + bounds: 488 384 8 8 + - image: solid-color(62, 48, 0, 255, 8, 8) + bounds: 496 384 8 8 + - image: solid-color(63, 48, 0, 255, 8, 8) + bounds: 504 384 8 8 + - image: solid-color(64, 48, 0, 255, 8, 8) + bounds: 512 384 8 8 + - image: solid-color(65, 48, 0, 255, 8, 8) + bounds: 520 384 8 8 + - image: solid-color(66, 48, 0, 255, 8, 8) + bounds: 528 384 8 8 + - image: solid-color(67, 48, 0, 255, 8, 8) + bounds: 536 384 8 8 + - image: solid-color(68, 48, 0, 255, 8, 8) + bounds: 544 384 8 8 + - image: solid-color(69, 48, 0, 255, 8, 8) + bounds: 552 384 8 8 + - image: solid-color(70, 48, 0, 255, 8, 8) + bounds: 560 384 8 8 + - image: solid-color(71, 48, 0, 255, 8, 8) + bounds: 568 384 8 8 + - image: solid-color(72, 48, 0, 255, 8, 8) + bounds: 576 384 8 8 + - image: solid-color(73, 48, 0, 255, 8, 8) + bounds: 584 384 8 8 + - image: solid-color(74, 48, 0, 255, 8, 8) + bounds: 592 384 8 8 + - image: solid-color(75, 48, 0, 255, 8, 8) + bounds: 600 384 8 8 + - image: solid-color(76, 48, 0, 255, 8, 8) + bounds: 608 384 8 8 + - image: solid-color(77, 48, 0, 255, 8, 8) + bounds: 616 384 8 8 + - image: solid-color(78, 48, 0, 255, 8, 8) + bounds: 624 384 8 8 + - image: solid-color(79, 48, 0, 255, 8, 8) + bounds: 632 384 8 8 + - image: solid-color(80, 48, 0, 255, 8, 8) + bounds: 640 384 8 8 + - image: solid-color(81, 48, 0, 255, 8, 8) + bounds: 648 384 8 8 + - image: solid-color(82, 48, 0, 255, 8, 8) + bounds: 656 384 8 8 + - image: solid-color(83, 48, 0, 255, 8, 8) + bounds: 664 384 8 8 + - image: solid-color(84, 48, 0, 255, 8, 8) + bounds: 672 384 8 8 + - image: solid-color(85, 48, 0, 255, 8, 8) + bounds: 680 384 8 8 + - image: solid-color(86, 48, 0, 255, 8, 8) + bounds: 688 384 8 8 + - image: solid-color(87, 48, 0, 255, 8, 8) + bounds: 696 384 8 8 + - image: solid-color(88, 48, 0, 255, 8, 8) + bounds: 704 384 8 8 + - image: solid-color(89, 48, 0, 255, 8, 8) + bounds: 712 384 8 8 + - image: solid-color(90, 48, 0, 255, 8, 8) + bounds: 720 384 8 8 + - image: solid-color(91, 48, 0, 255, 8, 8) + bounds: 728 384 8 8 + - image: solid-color(92, 48, 0, 255, 8, 8) + bounds: 736 384 8 8 + - image: solid-color(93, 48, 0, 255, 8, 8) + bounds: 744 384 8 8 + - image: solid-color(94, 48, 0, 255, 8, 8) + bounds: 752 384 8 8 + - image: solid-color(95, 48, 0, 255, 8, 8) + bounds: 760 384 8 8 + - image: solid-color(96, 48, 0, 255, 8, 8) + bounds: 768 384 8 8 + - image: solid-color(97, 48, 0, 255, 8, 8) + bounds: 776 384 8 8 + - image: solid-color(98, 48, 0, 255, 8, 8) + bounds: 784 384 8 8 + - image: solid-color(99, 48, 0, 255, 8, 8) + bounds: 792 384 8 8 + - image: solid-color(100, 48, 0, 255, 8, 8) + bounds: 800 384 8 8 + - image: solid-color(101, 48, 0, 255, 8, 8) + bounds: 808 384 8 8 + - image: solid-color(102, 48, 0, 255, 8, 8) + bounds: 816 384 8 8 + - image: solid-color(103, 48, 0, 255, 8, 8) + bounds: 824 384 8 8 + - image: solid-color(104, 48, 0, 255, 8, 8) + bounds: 832 384 8 8 + - image: solid-color(105, 48, 0, 255, 8, 8) + bounds: 840 384 8 8 + - image: solid-color(106, 48, 0, 255, 8, 8) + bounds: 848 384 8 8 + - image: solid-color(107, 48, 0, 255, 8, 8) + bounds: 856 384 8 8 + - image: solid-color(108, 48, 0, 255, 8, 8) + bounds: 864 384 8 8 + - image: solid-color(109, 48, 0, 255, 8, 8) + bounds: 872 384 8 8 + - image: solid-color(110, 48, 0, 255, 8, 8) + bounds: 880 384 8 8 + - image: solid-color(111, 48, 0, 255, 8, 8) + bounds: 888 384 8 8 + - image: solid-color(112, 48, 0, 255, 8, 8) + bounds: 896 384 8 8 + - image: solid-color(113, 48, 0, 255, 8, 8) + bounds: 904 384 8 8 + - image: solid-color(114, 48, 0, 255, 8, 8) + bounds: 912 384 8 8 + - image: solid-color(115, 48, 0, 255, 8, 8) + bounds: 920 384 8 8 + - image: solid-color(116, 48, 0, 255, 8, 8) + bounds: 928 384 8 8 + - image: solid-color(117, 48, 0, 255, 8, 8) + bounds: 936 384 8 8 + - image: solid-color(118, 48, 0, 255, 8, 8) + bounds: 944 384 8 8 + - image: solid-color(119, 48, 0, 255, 8, 8) + bounds: 952 384 8 8 + - image: solid-color(120, 48, 0, 255, 8, 8) + bounds: 960 384 8 8 + - image: solid-color(121, 48, 0, 255, 8, 8) + bounds: 968 384 8 8 + - image: solid-color(122, 48, 0, 255, 8, 8) + bounds: 976 384 8 8 + - image: solid-color(123, 48, 0, 255, 8, 8) + bounds: 984 384 8 8 + - image: solid-color(124, 48, 0, 255, 8, 8) + bounds: 992 384 8 8 + - image: solid-color(125, 48, 0, 255, 8, 8) + bounds: 1000 384 8 8 + - image: solid-color(126, 48, 0, 255, 8, 8) + bounds: 1008 384 8 8 + - image: solid-color(127, 48, 0, 255, 8, 8) + bounds: 1016 384 8 8 + - image: solid-color(0, 49, 0, 255, 8, 8) + bounds: 0 392 8 8 + - image: solid-color(1, 49, 0, 255, 8, 8) + bounds: 8 392 8 8 + - image: solid-color(2, 49, 0, 255, 8, 8) + bounds: 16 392 8 8 + - image: solid-color(3, 49, 0, 255, 8, 8) + bounds: 24 392 8 8 + - image: solid-color(4, 49, 0, 255, 8, 8) + bounds: 32 392 8 8 + - image: solid-color(5, 49, 0, 255, 8, 8) + bounds: 40 392 8 8 + - image: solid-color(6, 49, 0, 255, 8, 8) + bounds: 48 392 8 8 + - image: solid-color(7, 49, 0, 255, 8, 8) + bounds: 56 392 8 8 + - image: solid-color(8, 49, 0, 255, 8, 8) + bounds: 64 392 8 8 + - image: solid-color(9, 49, 0, 255, 8, 8) + bounds: 72 392 8 8 + - image: solid-color(10, 49, 0, 255, 8, 8) + bounds: 80 392 8 8 + - image: solid-color(11, 49, 0, 255, 8, 8) + bounds: 88 392 8 8 + - image: solid-color(12, 49, 0, 255, 8, 8) + bounds: 96 392 8 8 + - image: solid-color(13, 49, 0, 255, 8, 8) + bounds: 104 392 8 8 + - image: solid-color(14, 49, 0, 255, 8, 8) + bounds: 112 392 8 8 + - image: solid-color(15, 49, 0, 255, 8, 8) + bounds: 120 392 8 8 + - image: solid-color(16, 49, 0, 255, 8, 8) + bounds: 128 392 8 8 + - image: solid-color(17, 49, 0, 255, 8, 8) + bounds: 136 392 8 8 + - image: solid-color(18, 49, 0, 255, 8, 8) + bounds: 144 392 8 8 + - image: solid-color(19, 49, 0, 255, 8, 8) + bounds: 152 392 8 8 + - image: solid-color(20, 49, 0, 255, 8, 8) + bounds: 160 392 8 8 + - image: solid-color(21, 49, 0, 255, 8, 8) + bounds: 168 392 8 8 + - image: solid-color(22, 49, 0, 255, 8, 8) + bounds: 176 392 8 8 + - image: solid-color(23, 49, 0, 255, 8, 8) + bounds: 184 392 8 8 + - image: solid-color(24, 49, 0, 255, 8, 8) + bounds: 192 392 8 8 + - image: solid-color(25, 49, 0, 255, 8, 8) + bounds: 200 392 8 8 + - image: solid-color(26, 49, 0, 255, 8, 8) + bounds: 208 392 8 8 + - image: solid-color(27, 49, 0, 255, 8, 8) + bounds: 216 392 8 8 + - image: solid-color(28, 49, 0, 255, 8, 8) + bounds: 224 392 8 8 + - image: solid-color(29, 49, 0, 255, 8, 8) + bounds: 232 392 8 8 + - image: solid-color(30, 49, 0, 255, 8, 8) + bounds: 240 392 8 8 + - image: solid-color(31, 49, 0, 255, 8, 8) + bounds: 248 392 8 8 + - image: solid-color(32, 49, 0, 255, 8, 8) + bounds: 256 392 8 8 + - image: solid-color(33, 49, 0, 255, 8, 8) + bounds: 264 392 8 8 + - image: solid-color(34, 49, 0, 255, 8, 8) + bounds: 272 392 8 8 + - image: solid-color(35, 49, 0, 255, 8, 8) + bounds: 280 392 8 8 + - image: solid-color(36, 49, 0, 255, 8, 8) + bounds: 288 392 8 8 + - image: solid-color(37, 49, 0, 255, 8, 8) + bounds: 296 392 8 8 + - image: solid-color(38, 49, 0, 255, 8, 8) + bounds: 304 392 8 8 + - image: solid-color(39, 49, 0, 255, 8, 8) + bounds: 312 392 8 8 + - image: solid-color(40, 49, 0, 255, 8, 8) + bounds: 320 392 8 8 + - image: solid-color(41, 49, 0, 255, 8, 8) + bounds: 328 392 8 8 + - image: solid-color(42, 49, 0, 255, 8, 8) + bounds: 336 392 8 8 + - image: solid-color(43, 49, 0, 255, 8, 8) + bounds: 344 392 8 8 + - image: solid-color(44, 49, 0, 255, 8, 8) + bounds: 352 392 8 8 + - image: solid-color(45, 49, 0, 255, 8, 8) + bounds: 360 392 8 8 + - image: solid-color(46, 49, 0, 255, 8, 8) + bounds: 368 392 8 8 + - image: solid-color(47, 49, 0, 255, 8, 8) + bounds: 376 392 8 8 + - image: solid-color(48, 49, 0, 255, 8, 8) + bounds: 384 392 8 8 + - image: solid-color(49, 49, 0, 255, 8, 8) + bounds: 392 392 8 8 + - image: solid-color(50, 49, 0, 255, 8, 8) + bounds: 400 392 8 8 + - image: solid-color(51, 49, 0, 255, 8, 8) + bounds: 408 392 8 8 + - image: solid-color(52, 49, 0, 255, 8, 8) + bounds: 416 392 8 8 + - image: solid-color(53, 49, 0, 255, 8, 8) + bounds: 424 392 8 8 + - image: solid-color(54, 49, 0, 255, 8, 8) + bounds: 432 392 8 8 + - image: solid-color(55, 49, 0, 255, 8, 8) + bounds: 440 392 8 8 + - image: solid-color(56, 49, 0, 255, 8, 8) + bounds: 448 392 8 8 + - image: solid-color(57, 49, 0, 255, 8, 8) + bounds: 456 392 8 8 + - image: solid-color(58, 49, 0, 255, 8, 8) + bounds: 464 392 8 8 + - image: solid-color(59, 49, 0, 255, 8, 8) + bounds: 472 392 8 8 + - image: solid-color(60, 49, 0, 255, 8, 8) + bounds: 480 392 8 8 + - image: solid-color(61, 49, 0, 255, 8, 8) + bounds: 488 392 8 8 + - image: solid-color(62, 49, 0, 255, 8, 8) + bounds: 496 392 8 8 + - image: solid-color(63, 49, 0, 255, 8, 8) + bounds: 504 392 8 8 + - image: solid-color(64, 49, 0, 255, 8, 8) + bounds: 512 392 8 8 + - image: solid-color(65, 49, 0, 255, 8, 8) + bounds: 520 392 8 8 + - image: solid-color(66, 49, 0, 255, 8, 8) + bounds: 528 392 8 8 + - image: solid-color(67, 49, 0, 255, 8, 8) + bounds: 536 392 8 8 + - image: solid-color(68, 49, 0, 255, 8, 8) + bounds: 544 392 8 8 + - image: solid-color(69, 49, 0, 255, 8, 8) + bounds: 552 392 8 8 + - image: solid-color(70, 49, 0, 255, 8, 8) + bounds: 560 392 8 8 + - image: solid-color(71, 49, 0, 255, 8, 8) + bounds: 568 392 8 8 + - image: solid-color(72, 49, 0, 255, 8, 8) + bounds: 576 392 8 8 + - image: solid-color(73, 49, 0, 255, 8, 8) + bounds: 584 392 8 8 + - image: solid-color(74, 49, 0, 255, 8, 8) + bounds: 592 392 8 8 + - image: solid-color(75, 49, 0, 255, 8, 8) + bounds: 600 392 8 8 + - image: solid-color(76, 49, 0, 255, 8, 8) + bounds: 608 392 8 8 + - image: solid-color(77, 49, 0, 255, 8, 8) + bounds: 616 392 8 8 + - image: solid-color(78, 49, 0, 255, 8, 8) + bounds: 624 392 8 8 + - image: solid-color(79, 49, 0, 255, 8, 8) + bounds: 632 392 8 8 + - image: solid-color(80, 49, 0, 255, 8, 8) + bounds: 640 392 8 8 + - image: solid-color(81, 49, 0, 255, 8, 8) + bounds: 648 392 8 8 + - image: solid-color(82, 49, 0, 255, 8, 8) + bounds: 656 392 8 8 + - image: solid-color(83, 49, 0, 255, 8, 8) + bounds: 664 392 8 8 + - image: solid-color(84, 49, 0, 255, 8, 8) + bounds: 672 392 8 8 + - image: solid-color(85, 49, 0, 255, 8, 8) + bounds: 680 392 8 8 + - image: solid-color(86, 49, 0, 255, 8, 8) + bounds: 688 392 8 8 + - image: solid-color(87, 49, 0, 255, 8, 8) + bounds: 696 392 8 8 + - image: solid-color(88, 49, 0, 255, 8, 8) + bounds: 704 392 8 8 + - image: solid-color(89, 49, 0, 255, 8, 8) + bounds: 712 392 8 8 + - image: solid-color(90, 49, 0, 255, 8, 8) + bounds: 720 392 8 8 + - image: solid-color(91, 49, 0, 255, 8, 8) + bounds: 728 392 8 8 + - image: solid-color(92, 49, 0, 255, 8, 8) + bounds: 736 392 8 8 + - image: solid-color(93, 49, 0, 255, 8, 8) + bounds: 744 392 8 8 + - image: solid-color(94, 49, 0, 255, 8, 8) + bounds: 752 392 8 8 + - image: solid-color(95, 49, 0, 255, 8, 8) + bounds: 760 392 8 8 + - image: solid-color(96, 49, 0, 255, 8, 8) + bounds: 768 392 8 8 + - image: solid-color(97, 49, 0, 255, 8, 8) + bounds: 776 392 8 8 + - image: solid-color(98, 49, 0, 255, 8, 8) + bounds: 784 392 8 8 + - image: solid-color(99, 49, 0, 255, 8, 8) + bounds: 792 392 8 8 + - image: solid-color(100, 49, 0, 255, 8, 8) + bounds: 800 392 8 8 + - image: solid-color(101, 49, 0, 255, 8, 8) + bounds: 808 392 8 8 + - image: solid-color(102, 49, 0, 255, 8, 8) + bounds: 816 392 8 8 + - image: solid-color(103, 49, 0, 255, 8, 8) + bounds: 824 392 8 8 + - image: solid-color(104, 49, 0, 255, 8, 8) + bounds: 832 392 8 8 + - image: solid-color(105, 49, 0, 255, 8, 8) + bounds: 840 392 8 8 + - image: solid-color(106, 49, 0, 255, 8, 8) + bounds: 848 392 8 8 + - image: solid-color(107, 49, 0, 255, 8, 8) + bounds: 856 392 8 8 + - image: solid-color(108, 49, 0, 255, 8, 8) + bounds: 864 392 8 8 + - image: solid-color(109, 49, 0, 255, 8, 8) + bounds: 872 392 8 8 + - image: solid-color(110, 49, 0, 255, 8, 8) + bounds: 880 392 8 8 + - image: solid-color(111, 49, 0, 255, 8, 8) + bounds: 888 392 8 8 + - image: solid-color(112, 49, 0, 255, 8, 8) + bounds: 896 392 8 8 + - image: solid-color(113, 49, 0, 255, 8, 8) + bounds: 904 392 8 8 + - image: solid-color(114, 49, 0, 255, 8, 8) + bounds: 912 392 8 8 + - image: solid-color(115, 49, 0, 255, 8, 8) + bounds: 920 392 8 8 + - image: solid-color(116, 49, 0, 255, 8, 8) + bounds: 928 392 8 8 + - image: solid-color(117, 49, 0, 255, 8, 8) + bounds: 936 392 8 8 + - image: solid-color(118, 49, 0, 255, 8, 8) + bounds: 944 392 8 8 + - image: solid-color(119, 49, 0, 255, 8, 8) + bounds: 952 392 8 8 + - image: solid-color(120, 49, 0, 255, 8, 8) + bounds: 960 392 8 8 + - image: solid-color(121, 49, 0, 255, 8, 8) + bounds: 968 392 8 8 + - image: solid-color(122, 49, 0, 255, 8, 8) + bounds: 976 392 8 8 + - image: solid-color(123, 49, 0, 255, 8, 8) + bounds: 984 392 8 8 + - image: solid-color(124, 49, 0, 255, 8, 8) + bounds: 992 392 8 8 + - image: solid-color(125, 49, 0, 255, 8, 8) + bounds: 1000 392 8 8 + - image: solid-color(126, 49, 0, 255, 8, 8) + bounds: 1008 392 8 8 + - image: solid-color(127, 49, 0, 255, 8, 8) + bounds: 1016 392 8 8 + - image: solid-color(0, 50, 0, 255, 8, 8) + bounds: 0 400 8 8 + - image: solid-color(1, 50, 0, 255, 8, 8) + bounds: 8 400 8 8 + - image: solid-color(2, 50, 0, 255, 8, 8) + bounds: 16 400 8 8 + - image: solid-color(3, 50, 0, 255, 8, 8) + bounds: 24 400 8 8 + - image: solid-color(4, 50, 0, 255, 8, 8) + bounds: 32 400 8 8 + - image: solid-color(5, 50, 0, 255, 8, 8) + bounds: 40 400 8 8 + - image: solid-color(6, 50, 0, 255, 8, 8) + bounds: 48 400 8 8 + - image: solid-color(7, 50, 0, 255, 8, 8) + bounds: 56 400 8 8 + - image: solid-color(8, 50, 0, 255, 8, 8) + bounds: 64 400 8 8 + - image: solid-color(9, 50, 0, 255, 8, 8) + bounds: 72 400 8 8 + - image: solid-color(10, 50, 0, 255, 8, 8) + bounds: 80 400 8 8 + - image: solid-color(11, 50, 0, 255, 8, 8) + bounds: 88 400 8 8 + - image: solid-color(12, 50, 0, 255, 8, 8) + bounds: 96 400 8 8 + - image: solid-color(13, 50, 0, 255, 8, 8) + bounds: 104 400 8 8 + - image: solid-color(14, 50, 0, 255, 8, 8) + bounds: 112 400 8 8 + - image: solid-color(15, 50, 0, 255, 8, 8) + bounds: 120 400 8 8 + - image: solid-color(16, 50, 0, 255, 8, 8) + bounds: 128 400 8 8 + - image: solid-color(17, 50, 0, 255, 8, 8) + bounds: 136 400 8 8 + - image: solid-color(18, 50, 0, 255, 8, 8) + bounds: 144 400 8 8 + - image: solid-color(19, 50, 0, 255, 8, 8) + bounds: 152 400 8 8 + - image: solid-color(20, 50, 0, 255, 8, 8) + bounds: 160 400 8 8 + - image: solid-color(21, 50, 0, 255, 8, 8) + bounds: 168 400 8 8 + - image: solid-color(22, 50, 0, 255, 8, 8) + bounds: 176 400 8 8 + - image: solid-color(23, 50, 0, 255, 8, 8) + bounds: 184 400 8 8 + - image: solid-color(24, 50, 0, 255, 8, 8) + bounds: 192 400 8 8 + - image: solid-color(25, 50, 0, 255, 8, 8) + bounds: 200 400 8 8 + - image: solid-color(26, 50, 0, 255, 8, 8) + bounds: 208 400 8 8 + - image: solid-color(27, 50, 0, 255, 8, 8) + bounds: 216 400 8 8 + - image: solid-color(28, 50, 0, 255, 8, 8) + bounds: 224 400 8 8 + - image: solid-color(29, 50, 0, 255, 8, 8) + bounds: 232 400 8 8 + - image: solid-color(30, 50, 0, 255, 8, 8) + bounds: 240 400 8 8 + - image: solid-color(31, 50, 0, 255, 8, 8) + bounds: 248 400 8 8 + - image: solid-color(32, 50, 0, 255, 8, 8) + bounds: 256 400 8 8 + - image: solid-color(33, 50, 0, 255, 8, 8) + bounds: 264 400 8 8 + - image: solid-color(34, 50, 0, 255, 8, 8) + bounds: 272 400 8 8 + - image: solid-color(35, 50, 0, 255, 8, 8) + bounds: 280 400 8 8 + - image: solid-color(36, 50, 0, 255, 8, 8) + bounds: 288 400 8 8 + - image: solid-color(37, 50, 0, 255, 8, 8) + bounds: 296 400 8 8 + - image: solid-color(38, 50, 0, 255, 8, 8) + bounds: 304 400 8 8 + - image: solid-color(39, 50, 0, 255, 8, 8) + bounds: 312 400 8 8 + - image: solid-color(40, 50, 0, 255, 8, 8) + bounds: 320 400 8 8 + - image: solid-color(41, 50, 0, 255, 8, 8) + bounds: 328 400 8 8 + - image: solid-color(42, 50, 0, 255, 8, 8) + bounds: 336 400 8 8 + - image: solid-color(43, 50, 0, 255, 8, 8) + bounds: 344 400 8 8 + - image: solid-color(44, 50, 0, 255, 8, 8) + bounds: 352 400 8 8 + - image: solid-color(45, 50, 0, 255, 8, 8) + bounds: 360 400 8 8 + - image: solid-color(46, 50, 0, 255, 8, 8) + bounds: 368 400 8 8 + - image: solid-color(47, 50, 0, 255, 8, 8) + bounds: 376 400 8 8 + - image: solid-color(48, 50, 0, 255, 8, 8) + bounds: 384 400 8 8 + - image: solid-color(49, 50, 0, 255, 8, 8) + bounds: 392 400 8 8 + - image: solid-color(50, 50, 0, 255, 8, 8) + bounds: 400 400 8 8 + - image: solid-color(51, 50, 0, 255, 8, 8) + bounds: 408 400 8 8 + - image: solid-color(52, 50, 0, 255, 8, 8) + bounds: 416 400 8 8 + - image: solid-color(53, 50, 0, 255, 8, 8) + bounds: 424 400 8 8 + - image: solid-color(54, 50, 0, 255, 8, 8) + bounds: 432 400 8 8 + - image: solid-color(55, 50, 0, 255, 8, 8) + bounds: 440 400 8 8 + - image: solid-color(56, 50, 0, 255, 8, 8) + bounds: 448 400 8 8 + - image: solid-color(57, 50, 0, 255, 8, 8) + bounds: 456 400 8 8 + - image: solid-color(58, 50, 0, 255, 8, 8) + bounds: 464 400 8 8 + - image: solid-color(59, 50, 0, 255, 8, 8) + bounds: 472 400 8 8 + - image: solid-color(60, 50, 0, 255, 8, 8) + bounds: 480 400 8 8 + - image: solid-color(61, 50, 0, 255, 8, 8) + bounds: 488 400 8 8 + - image: solid-color(62, 50, 0, 255, 8, 8) + bounds: 496 400 8 8 + - image: solid-color(63, 50, 0, 255, 8, 8) + bounds: 504 400 8 8 + - image: solid-color(64, 50, 0, 255, 8, 8) + bounds: 512 400 8 8 + - image: solid-color(65, 50, 0, 255, 8, 8) + bounds: 520 400 8 8 + - image: solid-color(66, 50, 0, 255, 8, 8) + bounds: 528 400 8 8 + - image: solid-color(67, 50, 0, 255, 8, 8) + bounds: 536 400 8 8 + - image: solid-color(68, 50, 0, 255, 8, 8) + bounds: 544 400 8 8 + - image: solid-color(69, 50, 0, 255, 8, 8) + bounds: 552 400 8 8 + - image: solid-color(70, 50, 0, 255, 8, 8) + bounds: 560 400 8 8 + - image: solid-color(71, 50, 0, 255, 8, 8) + bounds: 568 400 8 8 + - image: solid-color(72, 50, 0, 255, 8, 8) + bounds: 576 400 8 8 + - image: solid-color(73, 50, 0, 255, 8, 8) + bounds: 584 400 8 8 + - image: solid-color(74, 50, 0, 255, 8, 8) + bounds: 592 400 8 8 + - image: solid-color(75, 50, 0, 255, 8, 8) + bounds: 600 400 8 8 + - image: solid-color(76, 50, 0, 255, 8, 8) + bounds: 608 400 8 8 + - image: solid-color(77, 50, 0, 255, 8, 8) + bounds: 616 400 8 8 + - image: solid-color(78, 50, 0, 255, 8, 8) + bounds: 624 400 8 8 + - image: solid-color(79, 50, 0, 255, 8, 8) + bounds: 632 400 8 8 + - image: solid-color(80, 50, 0, 255, 8, 8) + bounds: 640 400 8 8 + - image: solid-color(81, 50, 0, 255, 8, 8) + bounds: 648 400 8 8 + - image: solid-color(82, 50, 0, 255, 8, 8) + bounds: 656 400 8 8 + - image: solid-color(83, 50, 0, 255, 8, 8) + bounds: 664 400 8 8 + - image: solid-color(84, 50, 0, 255, 8, 8) + bounds: 672 400 8 8 + - image: solid-color(85, 50, 0, 255, 8, 8) + bounds: 680 400 8 8 + - image: solid-color(86, 50, 0, 255, 8, 8) + bounds: 688 400 8 8 + - image: solid-color(87, 50, 0, 255, 8, 8) + bounds: 696 400 8 8 + - image: solid-color(88, 50, 0, 255, 8, 8) + bounds: 704 400 8 8 + - image: solid-color(89, 50, 0, 255, 8, 8) + bounds: 712 400 8 8 + - image: solid-color(90, 50, 0, 255, 8, 8) + bounds: 720 400 8 8 + - image: solid-color(91, 50, 0, 255, 8, 8) + bounds: 728 400 8 8 + - image: solid-color(92, 50, 0, 255, 8, 8) + bounds: 736 400 8 8 + - image: solid-color(93, 50, 0, 255, 8, 8) + bounds: 744 400 8 8 + - image: solid-color(94, 50, 0, 255, 8, 8) + bounds: 752 400 8 8 + - image: solid-color(95, 50, 0, 255, 8, 8) + bounds: 760 400 8 8 + - image: solid-color(96, 50, 0, 255, 8, 8) + bounds: 768 400 8 8 + - image: solid-color(97, 50, 0, 255, 8, 8) + bounds: 776 400 8 8 + - image: solid-color(98, 50, 0, 255, 8, 8) + bounds: 784 400 8 8 + - image: solid-color(99, 50, 0, 255, 8, 8) + bounds: 792 400 8 8 + - image: solid-color(100, 50, 0, 255, 8, 8) + bounds: 800 400 8 8 + - image: solid-color(101, 50, 0, 255, 8, 8) + bounds: 808 400 8 8 + - image: solid-color(102, 50, 0, 255, 8, 8) + bounds: 816 400 8 8 + - image: solid-color(103, 50, 0, 255, 8, 8) + bounds: 824 400 8 8 + - image: solid-color(104, 50, 0, 255, 8, 8) + bounds: 832 400 8 8 + - image: solid-color(105, 50, 0, 255, 8, 8) + bounds: 840 400 8 8 + - image: solid-color(106, 50, 0, 255, 8, 8) + bounds: 848 400 8 8 + - image: solid-color(107, 50, 0, 255, 8, 8) + bounds: 856 400 8 8 + - image: solid-color(108, 50, 0, 255, 8, 8) + bounds: 864 400 8 8 + - image: solid-color(109, 50, 0, 255, 8, 8) + bounds: 872 400 8 8 + - image: solid-color(110, 50, 0, 255, 8, 8) + bounds: 880 400 8 8 + - image: solid-color(111, 50, 0, 255, 8, 8) + bounds: 888 400 8 8 + - image: solid-color(112, 50, 0, 255, 8, 8) + bounds: 896 400 8 8 + - image: solid-color(113, 50, 0, 255, 8, 8) + bounds: 904 400 8 8 + - image: solid-color(114, 50, 0, 255, 8, 8) + bounds: 912 400 8 8 + - image: solid-color(115, 50, 0, 255, 8, 8) + bounds: 920 400 8 8 + - image: solid-color(116, 50, 0, 255, 8, 8) + bounds: 928 400 8 8 + - image: solid-color(117, 50, 0, 255, 8, 8) + bounds: 936 400 8 8 + - image: solid-color(118, 50, 0, 255, 8, 8) + bounds: 944 400 8 8 + - image: solid-color(119, 50, 0, 255, 8, 8) + bounds: 952 400 8 8 + - image: solid-color(120, 50, 0, 255, 8, 8) + bounds: 960 400 8 8 + - image: solid-color(121, 50, 0, 255, 8, 8) + bounds: 968 400 8 8 + - image: solid-color(122, 50, 0, 255, 8, 8) + bounds: 976 400 8 8 + - image: solid-color(123, 50, 0, 255, 8, 8) + bounds: 984 400 8 8 + - image: solid-color(124, 50, 0, 255, 8, 8) + bounds: 992 400 8 8 + - image: solid-color(125, 50, 0, 255, 8, 8) + bounds: 1000 400 8 8 + - image: solid-color(126, 50, 0, 255, 8, 8) + bounds: 1008 400 8 8 + - image: solid-color(127, 50, 0, 255, 8, 8) + bounds: 1016 400 8 8 + - image: solid-color(0, 51, 0, 255, 8, 8) + bounds: 0 408 8 8 + - image: solid-color(1, 51, 0, 255, 8, 8) + bounds: 8 408 8 8 + - image: solid-color(2, 51, 0, 255, 8, 8) + bounds: 16 408 8 8 + - image: solid-color(3, 51, 0, 255, 8, 8) + bounds: 24 408 8 8 + - image: solid-color(4, 51, 0, 255, 8, 8) + bounds: 32 408 8 8 + - image: solid-color(5, 51, 0, 255, 8, 8) + bounds: 40 408 8 8 + - image: solid-color(6, 51, 0, 255, 8, 8) + bounds: 48 408 8 8 + - image: solid-color(7, 51, 0, 255, 8, 8) + bounds: 56 408 8 8 + - image: solid-color(8, 51, 0, 255, 8, 8) + bounds: 64 408 8 8 + - image: solid-color(9, 51, 0, 255, 8, 8) + bounds: 72 408 8 8 + - image: solid-color(10, 51, 0, 255, 8, 8) + bounds: 80 408 8 8 + - image: solid-color(11, 51, 0, 255, 8, 8) + bounds: 88 408 8 8 + - image: solid-color(12, 51, 0, 255, 8, 8) + bounds: 96 408 8 8 + - image: solid-color(13, 51, 0, 255, 8, 8) + bounds: 104 408 8 8 + - image: solid-color(14, 51, 0, 255, 8, 8) + bounds: 112 408 8 8 + - image: solid-color(15, 51, 0, 255, 8, 8) + bounds: 120 408 8 8 + - image: solid-color(16, 51, 0, 255, 8, 8) + bounds: 128 408 8 8 + - image: solid-color(17, 51, 0, 255, 8, 8) + bounds: 136 408 8 8 + - image: solid-color(18, 51, 0, 255, 8, 8) + bounds: 144 408 8 8 + - image: solid-color(19, 51, 0, 255, 8, 8) + bounds: 152 408 8 8 + - image: solid-color(20, 51, 0, 255, 8, 8) + bounds: 160 408 8 8 + - image: solid-color(21, 51, 0, 255, 8, 8) + bounds: 168 408 8 8 + - image: solid-color(22, 51, 0, 255, 8, 8) + bounds: 176 408 8 8 + - image: solid-color(23, 51, 0, 255, 8, 8) + bounds: 184 408 8 8 + - image: solid-color(24, 51, 0, 255, 8, 8) + bounds: 192 408 8 8 + - image: solid-color(25, 51, 0, 255, 8, 8) + bounds: 200 408 8 8 + - image: solid-color(26, 51, 0, 255, 8, 8) + bounds: 208 408 8 8 + - image: solid-color(27, 51, 0, 255, 8, 8) + bounds: 216 408 8 8 + - image: solid-color(28, 51, 0, 255, 8, 8) + bounds: 224 408 8 8 + - image: solid-color(29, 51, 0, 255, 8, 8) + bounds: 232 408 8 8 + - image: solid-color(30, 51, 0, 255, 8, 8) + bounds: 240 408 8 8 + - image: solid-color(31, 51, 0, 255, 8, 8) + bounds: 248 408 8 8 + - image: solid-color(32, 51, 0, 255, 8, 8) + bounds: 256 408 8 8 + - image: solid-color(33, 51, 0, 255, 8, 8) + bounds: 264 408 8 8 + - image: solid-color(34, 51, 0, 255, 8, 8) + bounds: 272 408 8 8 + - image: solid-color(35, 51, 0, 255, 8, 8) + bounds: 280 408 8 8 + - image: solid-color(36, 51, 0, 255, 8, 8) + bounds: 288 408 8 8 + - image: solid-color(37, 51, 0, 255, 8, 8) + bounds: 296 408 8 8 + - image: solid-color(38, 51, 0, 255, 8, 8) + bounds: 304 408 8 8 + - image: solid-color(39, 51, 0, 255, 8, 8) + bounds: 312 408 8 8 + - image: solid-color(40, 51, 0, 255, 8, 8) + bounds: 320 408 8 8 + - image: solid-color(41, 51, 0, 255, 8, 8) + bounds: 328 408 8 8 + - image: solid-color(42, 51, 0, 255, 8, 8) + bounds: 336 408 8 8 + - image: solid-color(43, 51, 0, 255, 8, 8) + bounds: 344 408 8 8 + - image: solid-color(44, 51, 0, 255, 8, 8) + bounds: 352 408 8 8 + - image: solid-color(45, 51, 0, 255, 8, 8) + bounds: 360 408 8 8 + - image: solid-color(46, 51, 0, 255, 8, 8) + bounds: 368 408 8 8 + - image: solid-color(47, 51, 0, 255, 8, 8) + bounds: 376 408 8 8 + - image: solid-color(48, 51, 0, 255, 8, 8) + bounds: 384 408 8 8 + - image: solid-color(49, 51, 0, 255, 8, 8) + bounds: 392 408 8 8 + - image: solid-color(50, 51, 0, 255, 8, 8) + bounds: 400 408 8 8 + - image: solid-color(51, 51, 0, 255, 8, 8) + bounds: 408 408 8 8 + - image: solid-color(52, 51, 0, 255, 8, 8) + bounds: 416 408 8 8 + - image: solid-color(53, 51, 0, 255, 8, 8) + bounds: 424 408 8 8 + - image: solid-color(54, 51, 0, 255, 8, 8) + bounds: 432 408 8 8 + - image: solid-color(55, 51, 0, 255, 8, 8) + bounds: 440 408 8 8 + - image: solid-color(56, 51, 0, 255, 8, 8) + bounds: 448 408 8 8 + - image: solid-color(57, 51, 0, 255, 8, 8) + bounds: 456 408 8 8 + - image: solid-color(58, 51, 0, 255, 8, 8) + bounds: 464 408 8 8 + - image: solid-color(59, 51, 0, 255, 8, 8) + bounds: 472 408 8 8 + - image: solid-color(60, 51, 0, 255, 8, 8) + bounds: 480 408 8 8 + - image: solid-color(61, 51, 0, 255, 8, 8) + bounds: 488 408 8 8 + - image: solid-color(62, 51, 0, 255, 8, 8) + bounds: 496 408 8 8 + - image: solid-color(63, 51, 0, 255, 8, 8) + bounds: 504 408 8 8 + - image: solid-color(64, 51, 0, 255, 8, 8) + bounds: 512 408 8 8 + - image: solid-color(65, 51, 0, 255, 8, 8) + bounds: 520 408 8 8 + - image: solid-color(66, 51, 0, 255, 8, 8) + bounds: 528 408 8 8 + - image: solid-color(67, 51, 0, 255, 8, 8) + bounds: 536 408 8 8 + - image: solid-color(68, 51, 0, 255, 8, 8) + bounds: 544 408 8 8 + - image: solid-color(69, 51, 0, 255, 8, 8) + bounds: 552 408 8 8 + - image: solid-color(70, 51, 0, 255, 8, 8) + bounds: 560 408 8 8 + - image: solid-color(71, 51, 0, 255, 8, 8) + bounds: 568 408 8 8 + - image: solid-color(72, 51, 0, 255, 8, 8) + bounds: 576 408 8 8 + - image: solid-color(73, 51, 0, 255, 8, 8) + bounds: 584 408 8 8 + - image: solid-color(74, 51, 0, 255, 8, 8) + bounds: 592 408 8 8 + - image: solid-color(75, 51, 0, 255, 8, 8) + bounds: 600 408 8 8 + - image: solid-color(76, 51, 0, 255, 8, 8) + bounds: 608 408 8 8 + - image: solid-color(77, 51, 0, 255, 8, 8) + bounds: 616 408 8 8 + - image: solid-color(78, 51, 0, 255, 8, 8) + bounds: 624 408 8 8 + - image: solid-color(79, 51, 0, 255, 8, 8) + bounds: 632 408 8 8 + - image: solid-color(80, 51, 0, 255, 8, 8) + bounds: 640 408 8 8 + - image: solid-color(81, 51, 0, 255, 8, 8) + bounds: 648 408 8 8 + - image: solid-color(82, 51, 0, 255, 8, 8) + bounds: 656 408 8 8 + - image: solid-color(83, 51, 0, 255, 8, 8) + bounds: 664 408 8 8 + - image: solid-color(84, 51, 0, 255, 8, 8) + bounds: 672 408 8 8 + - image: solid-color(85, 51, 0, 255, 8, 8) + bounds: 680 408 8 8 + - image: solid-color(86, 51, 0, 255, 8, 8) + bounds: 688 408 8 8 + - image: solid-color(87, 51, 0, 255, 8, 8) + bounds: 696 408 8 8 + - image: solid-color(88, 51, 0, 255, 8, 8) + bounds: 704 408 8 8 + - image: solid-color(89, 51, 0, 255, 8, 8) + bounds: 712 408 8 8 + - image: solid-color(90, 51, 0, 255, 8, 8) + bounds: 720 408 8 8 + - image: solid-color(91, 51, 0, 255, 8, 8) + bounds: 728 408 8 8 + - image: solid-color(92, 51, 0, 255, 8, 8) + bounds: 736 408 8 8 + - image: solid-color(93, 51, 0, 255, 8, 8) + bounds: 744 408 8 8 + - image: solid-color(94, 51, 0, 255, 8, 8) + bounds: 752 408 8 8 + - image: solid-color(95, 51, 0, 255, 8, 8) + bounds: 760 408 8 8 + - image: solid-color(96, 51, 0, 255, 8, 8) + bounds: 768 408 8 8 + - image: solid-color(97, 51, 0, 255, 8, 8) + bounds: 776 408 8 8 + - image: solid-color(98, 51, 0, 255, 8, 8) + bounds: 784 408 8 8 + - image: solid-color(99, 51, 0, 255, 8, 8) + bounds: 792 408 8 8 + - image: solid-color(100, 51, 0, 255, 8, 8) + bounds: 800 408 8 8 + - image: solid-color(101, 51, 0, 255, 8, 8) + bounds: 808 408 8 8 + - image: solid-color(102, 51, 0, 255, 8, 8) + bounds: 816 408 8 8 + - image: solid-color(103, 51, 0, 255, 8, 8) + bounds: 824 408 8 8 + - image: solid-color(104, 51, 0, 255, 8, 8) + bounds: 832 408 8 8 + - image: solid-color(105, 51, 0, 255, 8, 8) + bounds: 840 408 8 8 + - image: solid-color(106, 51, 0, 255, 8, 8) + bounds: 848 408 8 8 + - image: solid-color(107, 51, 0, 255, 8, 8) + bounds: 856 408 8 8 + - image: solid-color(108, 51, 0, 255, 8, 8) + bounds: 864 408 8 8 + - image: solid-color(109, 51, 0, 255, 8, 8) + bounds: 872 408 8 8 + - image: solid-color(110, 51, 0, 255, 8, 8) + bounds: 880 408 8 8 + - image: solid-color(111, 51, 0, 255, 8, 8) + bounds: 888 408 8 8 + - image: solid-color(112, 51, 0, 255, 8, 8) + bounds: 896 408 8 8 + - image: solid-color(113, 51, 0, 255, 8, 8) + bounds: 904 408 8 8 + - image: solid-color(114, 51, 0, 255, 8, 8) + bounds: 912 408 8 8 + - image: solid-color(115, 51, 0, 255, 8, 8) + bounds: 920 408 8 8 + - image: solid-color(116, 51, 0, 255, 8, 8) + bounds: 928 408 8 8 + - image: solid-color(117, 51, 0, 255, 8, 8) + bounds: 936 408 8 8 + - image: solid-color(118, 51, 0, 255, 8, 8) + bounds: 944 408 8 8 + - image: solid-color(119, 51, 0, 255, 8, 8) + bounds: 952 408 8 8 + - image: solid-color(120, 51, 0, 255, 8, 8) + bounds: 960 408 8 8 + - image: solid-color(121, 51, 0, 255, 8, 8) + bounds: 968 408 8 8 + - image: solid-color(122, 51, 0, 255, 8, 8) + bounds: 976 408 8 8 + - image: solid-color(123, 51, 0, 255, 8, 8) + bounds: 984 408 8 8 + - image: solid-color(124, 51, 0, 255, 8, 8) + bounds: 992 408 8 8 + - image: solid-color(125, 51, 0, 255, 8, 8) + bounds: 1000 408 8 8 + - image: solid-color(126, 51, 0, 255, 8, 8) + bounds: 1008 408 8 8 + - image: solid-color(127, 51, 0, 255, 8, 8) + bounds: 1016 408 8 8 + - image: solid-color(0, 52, 0, 255, 8, 8) + bounds: 0 416 8 8 + - image: solid-color(1, 52, 0, 255, 8, 8) + bounds: 8 416 8 8 + - image: solid-color(2, 52, 0, 255, 8, 8) + bounds: 16 416 8 8 + - image: solid-color(3, 52, 0, 255, 8, 8) + bounds: 24 416 8 8 + - image: solid-color(4, 52, 0, 255, 8, 8) + bounds: 32 416 8 8 + - image: solid-color(5, 52, 0, 255, 8, 8) + bounds: 40 416 8 8 + - image: solid-color(6, 52, 0, 255, 8, 8) + bounds: 48 416 8 8 + - image: solid-color(7, 52, 0, 255, 8, 8) + bounds: 56 416 8 8 + - image: solid-color(8, 52, 0, 255, 8, 8) + bounds: 64 416 8 8 + - image: solid-color(9, 52, 0, 255, 8, 8) + bounds: 72 416 8 8 + - image: solid-color(10, 52, 0, 255, 8, 8) + bounds: 80 416 8 8 + - image: solid-color(11, 52, 0, 255, 8, 8) + bounds: 88 416 8 8 + - image: solid-color(12, 52, 0, 255, 8, 8) + bounds: 96 416 8 8 + - image: solid-color(13, 52, 0, 255, 8, 8) + bounds: 104 416 8 8 + - image: solid-color(14, 52, 0, 255, 8, 8) + bounds: 112 416 8 8 + - image: solid-color(15, 52, 0, 255, 8, 8) + bounds: 120 416 8 8 + - image: solid-color(16, 52, 0, 255, 8, 8) + bounds: 128 416 8 8 + - image: solid-color(17, 52, 0, 255, 8, 8) + bounds: 136 416 8 8 + - image: solid-color(18, 52, 0, 255, 8, 8) + bounds: 144 416 8 8 + - image: solid-color(19, 52, 0, 255, 8, 8) + bounds: 152 416 8 8 + - image: solid-color(20, 52, 0, 255, 8, 8) + bounds: 160 416 8 8 + - image: solid-color(21, 52, 0, 255, 8, 8) + bounds: 168 416 8 8 + - image: solid-color(22, 52, 0, 255, 8, 8) + bounds: 176 416 8 8 + - image: solid-color(23, 52, 0, 255, 8, 8) + bounds: 184 416 8 8 + - image: solid-color(24, 52, 0, 255, 8, 8) + bounds: 192 416 8 8 + - image: solid-color(25, 52, 0, 255, 8, 8) + bounds: 200 416 8 8 + - image: solid-color(26, 52, 0, 255, 8, 8) + bounds: 208 416 8 8 + - image: solid-color(27, 52, 0, 255, 8, 8) + bounds: 216 416 8 8 + - image: solid-color(28, 52, 0, 255, 8, 8) + bounds: 224 416 8 8 + - image: solid-color(29, 52, 0, 255, 8, 8) + bounds: 232 416 8 8 + - image: solid-color(30, 52, 0, 255, 8, 8) + bounds: 240 416 8 8 + - image: solid-color(31, 52, 0, 255, 8, 8) + bounds: 248 416 8 8 + - image: solid-color(32, 52, 0, 255, 8, 8) + bounds: 256 416 8 8 + - image: solid-color(33, 52, 0, 255, 8, 8) + bounds: 264 416 8 8 + - image: solid-color(34, 52, 0, 255, 8, 8) + bounds: 272 416 8 8 + - image: solid-color(35, 52, 0, 255, 8, 8) + bounds: 280 416 8 8 + - image: solid-color(36, 52, 0, 255, 8, 8) + bounds: 288 416 8 8 + - image: solid-color(37, 52, 0, 255, 8, 8) + bounds: 296 416 8 8 + - image: solid-color(38, 52, 0, 255, 8, 8) + bounds: 304 416 8 8 + - image: solid-color(39, 52, 0, 255, 8, 8) + bounds: 312 416 8 8 + - image: solid-color(40, 52, 0, 255, 8, 8) + bounds: 320 416 8 8 + - image: solid-color(41, 52, 0, 255, 8, 8) + bounds: 328 416 8 8 + - image: solid-color(42, 52, 0, 255, 8, 8) + bounds: 336 416 8 8 + - image: solid-color(43, 52, 0, 255, 8, 8) + bounds: 344 416 8 8 + - image: solid-color(44, 52, 0, 255, 8, 8) + bounds: 352 416 8 8 + - image: solid-color(45, 52, 0, 255, 8, 8) + bounds: 360 416 8 8 + - image: solid-color(46, 52, 0, 255, 8, 8) + bounds: 368 416 8 8 + - image: solid-color(47, 52, 0, 255, 8, 8) + bounds: 376 416 8 8 + - image: solid-color(48, 52, 0, 255, 8, 8) + bounds: 384 416 8 8 + - image: solid-color(49, 52, 0, 255, 8, 8) + bounds: 392 416 8 8 + - image: solid-color(50, 52, 0, 255, 8, 8) + bounds: 400 416 8 8 + - image: solid-color(51, 52, 0, 255, 8, 8) + bounds: 408 416 8 8 + - image: solid-color(52, 52, 0, 255, 8, 8) + bounds: 416 416 8 8 + - image: solid-color(53, 52, 0, 255, 8, 8) + bounds: 424 416 8 8 + - image: solid-color(54, 52, 0, 255, 8, 8) + bounds: 432 416 8 8 + - image: solid-color(55, 52, 0, 255, 8, 8) + bounds: 440 416 8 8 + - image: solid-color(56, 52, 0, 255, 8, 8) + bounds: 448 416 8 8 + - image: solid-color(57, 52, 0, 255, 8, 8) + bounds: 456 416 8 8 + - image: solid-color(58, 52, 0, 255, 8, 8) + bounds: 464 416 8 8 + - image: solid-color(59, 52, 0, 255, 8, 8) + bounds: 472 416 8 8 + - image: solid-color(60, 52, 0, 255, 8, 8) + bounds: 480 416 8 8 + - image: solid-color(61, 52, 0, 255, 8, 8) + bounds: 488 416 8 8 + - image: solid-color(62, 52, 0, 255, 8, 8) + bounds: 496 416 8 8 + - image: solid-color(63, 52, 0, 255, 8, 8) + bounds: 504 416 8 8 + - image: solid-color(64, 52, 0, 255, 8, 8) + bounds: 512 416 8 8 + - image: solid-color(65, 52, 0, 255, 8, 8) + bounds: 520 416 8 8 + - image: solid-color(66, 52, 0, 255, 8, 8) + bounds: 528 416 8 8 + - image: solid-color(67, 52, 0, 255, 8, 8) + bounds: 536 416 8 8 + - image: solid-color(68, 52, 0, 255, 8, 8) + bounds: 544 416 8 8 + - image: solid-color(69, 52, 0, 255, 8, 8) + bounds: 552 416 8 8 + - image: solid-color(70, 52, 0, 255, 8, 8) + bounds: 560 416 8 8 + - image: solid-color(71, 52, 0, 255, 8, 8) + bounds: 568 416 8 8 + - image: solid-color(72, 52, 0, 255, 8, 8) + bounds: 576 416 8 8 + - image: solid-color(73, 52, 0, 255, 8, 8) + bounds: 584 416 8 8 + - image: solid-color(74, 52, 0, 255, 8, 8) + bounds: 592 416 8 8 + - image: solid-color(75, 52, 0, 255, 8, 8) + bounds: 600 416 8 8 + - image: solid-color(76, 52, 0, 255, 8, 8) + bounds: 608 416 8 8 + - image: solid-color(77, 52, 0, 255, 8, 8) + bounds: 616 416 8 8 + - image: solid-color(78, 52, 0, 255, 8, 8) + bounds: 624 416 8 8 + - image: solid-color(79, 52, 0, 255, 8, 8) + bounds: 632 416 8 8 + - image: solid-color(80, 52, 0, 255, 8, 8) + bounds: 640 416 8 8 + - image: solid-color(81, 52, 0, 255, 8, 8) + bounds: 648 416 8 8 + - image: solid-color(82, 52, 0, 255, 8, 8) + bounds: 656 416 8 8 + - image: solid-color(83, 52, 0, 255, 8, 8) + bounds: 664 416 8 8 + - image: solid-color(84, 52, 0, 255, 8, 8) + bounds: 672 416 8 8 + - image: solid-color(85, 52, 0, 255, 8, 8) + bounds: 680 416 8 8 + - image: solid-color(86, 52, 0, 255, 8, 8) + bounds: 688 416 8 8 + - image: solid-color(87, 52, 0, 255, 8, 8) + bounds: 696 416 8 8 + - image: solid-color(88, 52, 0, 255, 8, 8) + bounds: 704 416 8 8 + - image: solid-color(89, 52, 0, 255, 8, 8) + bounds: 712 416 8 8 + - image: solid-color(90, 52, 0, 255, 8, 8) + bounds: 720 416 8 8 + - image: solid-color(91, 52, 0, 255, 8, 8) + bounds: 728 416 8 8 + - image: solid-color(92, 52, 0, 255, 8, 8) + bounds: 736 416 8 8 + - image: solid-color(93, 52, 0, 255, 8, 8) + bounds: 744 416 8 8 + - image: solid-color(94, 52, 0, 255, 8, 8) + bounds: 752 416 8 8 + - image: solid-color(95, 52, 0, 255, 8, 8) + bounds: 760 416 8 8 + - image: solid-color(96, 52, 0, 255, 8, 8) + bounds: 768 416 8 8 + - image: solid-color(97, 52, 0, 255, 8, 8) + bounds: 776 416 8 8 + - image: solid-color(98, 52, 0, 255, 8, 8) + bounds: 784 416 8 8 + - image: solid-color(99, 52, 0, 255, 8, 8) + bounds: 792 416 8 8 + - image: solid-color(100, 52, 0, 255, 8, 8) + bounds: 800 416 8 8 + - image: solid-color(101, 52, 0, 255, 8, 8) + bounds: 808 416 8 8 + - image: solid-color(102, 52, 0, 255, 8, 8) + bounds: 816 416 8 8 + - image: solid-color(103, 52, 0, 255, 8, 8) + bounds: 824 416 8 8 + - image: solid-color(104, 52, 0, 255, 8, 8) + bounds: 832 416 8 8 + - image: solid-color(105, 52, 0, 255, 8, 8) + bounds: 840 416 8 8 + - image: solid-color(106, 52, 0, 255, 8, 8) + bounds: 848 416 8 8 + - image: solid-color(107, 52, 0, 255, 8, 8) + bounds: 856 416 8 8 + - image: solid-color(108, 52, 0, 255, 8, 8) + bounds: 864 416 8 8 + - image: solid-color(109, 52, 0, 255, 8, 8) + bounds: 872 416 8 8 + - image: solid-color(110, 52, 0, 255, 8, 8) + bounds: 880 416 8 8 + - image: solid-color(111, 52, 0, 255, 8, 8) + bounds: 888 416 8 8 + - image: solid-color(112, 52, 0, 255, 8, 8) + bounds: 896 416 8 8 + - image: solid-color(113, 52, 0, 255, 8, 8) + bounds: 904 416 8 8 + - image: solid-color(114, 52, 0, 255, 8, 8) + bounds: 912 416 8 8 + - image: solid-color(115, 52, 0, 255, 8, 8) + bounds: 920 416 8 8 + - image: solid-color(116, 52, 0, 255, 8, 8) + bounds: 928 416 8 8 + - image: solid-color(117, 52, 0, 255, 8, 8) + bounds: 936 416 8 8 + - image: solid-color(118, 52, 0, 255, 8, 8) + bounds: 944 416 8 8 + - image: solid-color(119, 52, 0, 255, 8, 8) + bounds: 952 416 8 8 + - image: solid-color(120, 52, 0, 255, 8, 8) + bounds: 960 416 8 8 + - image: solid-color(121, 52, 0, 255, 8, 8) + bounds: 968 416 8 8 + - image: solid-color(122, 52, 0, 255, 8, 8) + bounds: 976 416 8 8 + - image: solid-color(123, 52, 0, 255, 8, 8) + bounds: 984 416 8 8 + - image: solid-color(124, 52, 0, 255, 8, 8) + bounds: 992 416 8 8 + - image: solid-color(125, 52, 0, 255, 8, 8) + bounds: 1000 416 8 8 + - image: solid-color(126, 52, 0, 255, 8, 8) + bounds: 1008 416 8 8 + - image: solid-color(127, 52, 0, 255, 8, 8) + bounds: 1016 416 8 8 + - image: solid-color(0, 53, 0, 255, 8, 8) + bounds: 0 424 8 8 + - image: solid-color(1, 53, 0, 255, 8, 8) + bounds: 8 424 8 8 + - image: solid-color(2, 53, 0, 255, 8, 8) + bounds: 16 424 8 8 + - image: solid-color(3, 53, 0, 255, 8, 8) + bounds: 24 424 8 8 + - image: solid-color(4, 53, 0, 255, 8, 8) + bounds: 32 424 8 8 + - image: solid-color(5, 53, 0, 255, 8, 8) + bounds: 40 424 8 8 + - image: solid-color(6, 53, 0, 255, 8, 8) + bounds: 48 424 8 8 + - image: solid-color(7, 53, 0, 255, 8, 8) + bounds: 56 424 8 8 + - image: solid-color(8, 53, 0, 255, 8, 8) + bounds: 64 424 8 8 + - image: solid-color(9, 53, 0, 255, 8, 8) + bounds: 72 424 8 8 + - image: solid-color(10, 53, 0, 255, 8, 8) + bounds: 80 424 8 8 + - image: solid-color(11, 53, 0, 255, 8, 8) + bounds: 88 424 8 8 + - image: solid-color(12, 53, 0, 255, 8, 8) + bounds: 96 424 8 8 + - image: solid-color(13, 53, 0, 255, 8, 8) + bounds: 104 424 8 8 + - image: solid-color(14, 53, 0, 255, 8, 8) + bounds: 112 424 8 8 + - image: solid-color(15, 53, 0, 255, 8, 8) + bounds: 120 424 8 8 + - image: solid-color(16, 53, 0, 255, 8, 8) + bounds: 128 424 8 8 + - image: solid-color(17, 53, 0, 255, 8, 8) + bounds: 136 424 8 8 + - image: solid-color(18, 53, 0, 255, 8, 8) + bounds: 144 424 8 8 + - image: solid-color(19, 53, 0, 255, 8, 8) + bounds: 152 424 8 8 + - image: solid-color(20, 53, 0, 255, 8, 8) + bounds: 160 424 8 8 + - image: solid-color(21, 53, 0, 255, 8, 8) + bounds: 168 424 8 8 + - image: solid-color(22, 53, 0, 255, 8, 8) + bounds: 176 424 8 8 + - image: solid-color(23, 53, 0, 255, 8, 8) + bounds: 184 424 8 8 + - image: solid-color(24, 53, 0, 255, 8, 8) + bounds: 192 424 8 8 + - image: solid-color(25, 53, 0, 255, 8, 8) + bounds: 200 424 8 8 + - image: solid-color(26, 53, 0, 255, 8, 8) + bounds: 208 424 8 8 + - image: solid-color(27, 53, 0, 255, 8, 8) + bounds: 216 424 8 8 + - image: solid-color(28, 53, 0, 255, 8, 8) + bounds: 224 424 8 8 + - image: solid-color(29, 53, 0, 255, 8, 8) + bounds: 232 424 8 8 + - image: solid-color(30, 53, 0, 255, 8, 8) + bounds: 240 424 8 8 + - image: solid-color(31, 53, 0, 255, 8, 8) + bounds: 248 424 8 8 + - image: solid-color(32, 53, 0, 255, 8, 8) + bounds: 256 424 8 8 + - image: solid-color(33, 53, 0, 255, 8, 8) + bounds: 264 424 8 8 + - image: solid-color(34, 53, 0, 255, 8, 8) + bounds: 272 424 8 8 + - image: solid-color(35, 53, 0, 255, 8, 8) + bounds: 280 424 8 8 + - image: solid-color(36, 53, 0, 255, 8, 8) + bounds: 288 424 8 8 + - image: solid-color(37, 53, 0, 255, 8, 8) + bounds: 296 424 8 8 + - image: solid-color(38, 53, 0, 255, 8, 8) + bounds: 304 424 8 8 + - image: solid-color(39, 53, 0, 255, 8, 8) + bounds: 312 424 8 8 + - image: solid-color(40, 53, 0, 255, 8, 8) + bounds: 320 424 8 8 + - image: solid-color(41, 53, 0, 255, 8, 8) + bounds: 328 424 8 8 + - image: solid-color(42, 53, 0, 255, 8, 8) + bounds: 336 424 8 8 + - image: solid-color(43, 53, 0, 255, 8, 8) + bounds: 344 424 8 8 + - image: solid-color(44, 53, 0, 255, 8, 8) + bounds: 352 424 8 8 + - image: solid-color(45, 53, 0, 255, 8, 8) + bounds: 360 424 8 8 + - image: solid-color(46, 53, 0, 255, 8, 8) + bounds: 368 424 8 8 + - image: solid-color(47, 53, 0, 255, 8, 8) + bounds: 376 424 8 8 + - image: solid-color(48, 53, 0, 255, 8, 8) + bounds: 384 424 8 8 + - image: solid-color(49, 53, 0, 255, 8, 8) + bounds: 392 424 8 8 + - image: solid-color(50, 53, 0, 255, 8, 8) + bounds: 400 424 8 8 + - image: solid-color(51, 53, 0, 255, 8, 8) + bounds: 408 424 8 8 + - image: solid-color(52, 53, 0, 255, 8, 8) + bounds: 416 424 8 8 + - image: solid-color(53, 53, 0, 255, 8, 8) + bounds: 424 424 8 8 + - image: solid-color(54, 53, 0, 255, 8, 8) + bounds: 432 424 8 8 + - image: solid-color(55, 53, 0, 255, 8, 8) + bounds: 440 424 8 8 + - image: solid-color(56, 53, 0, 255, 8, 8) + bounds: 448 424 8 8 + - image: solid-color(57, 53, 0, 255, 8, 8) + bounds: 456 424 8 8 + - image: solid-color(58, 53, 0, 255, 8, 8) + bounds: 464 424 8 8 + - image: solid-color(59, 53, 0, 255, 8, 8) + bounds: 472 424 8 8 + - image: solid-color(60, 53, 0, 255, 8, 8) + bounds: 480 424 8 8 + - image: solid-color(61, 53, 0, 255, 8, 8) + bounds: 488 424 8 8 + - image: solid-color(62, 53, 0, 255, 8, 8) + bounds: 496 424 8 8 + - image: solid-color(63, 53, 0, 255, 8, 8) + bounds: 504 424 8 8 + - image: solid-color(64, 53, 0, 255, 8, 8) + bounds: 512 424 8 8 + - image: solid-color(65, 53, 0, 255, 8, 8) + bounds: 520 424 8 8 + - image: solid-color(66, 53, 0, 255, 8, 8) + bounds: 528 424 8 8 + - image: solid-color(67, 53, 0, 255, 8, 8) + bounds: 536 424 8 8 + - image: solid-color(68, 53, 0, 255, 8, 8) + bounds: 544 424 8 8 + - image: solid-color(69, 53, 0, 255, 8, 8) + bounds: 552 424 8 8 + - image: solid-color(70, 53, 0, 255, 8, 8) + bounds: 560 424 8 8 + - image: solid-color(71, 53, 0, 255, 8, 8) + bounds: 568 424 8 8 + - image: solid-color(72, 53, 0, 255, 8, 8) + bounds: 576 424 8 8 + - image: solid-color(73, 53, 0, 255, 8, 8) + bounds: 584 424 8 8 + - image: solid-color(74, 53, 0, 255, 8, 8) + bounds: 592 424 8 8 + - image: solid-color(75, 53, 0, 255, 8, 8) + bounds: 600 424 8 8 + - image: solid-color(76, 53, 0, 255, 8, 8) + bounds: 608 424 8 8 + - image: solid-color(77, 53, 0, 255, 8, 8) + bounds: 616 424 8 8 + - image: solid-color(78, 53, 0, 255, 8, 8) + bounds: 624 424 8 8 + - image: solid-color(79, 53, 0, 255, 8, 8) + bounds: 632 424 8 8 + - image: solid-color(80, 53, 0, 255, 8, 8) + bounds: 640 424 8 8 + - image: solid-color(81, 53, 0, 255, 8, 8) + bounds: 648 424 8 8 + - image: solid-color(82, 53, 0, 255, 8, 8) + bounds: 656 424 8 8 + - image: solid-color(83, 53, 0, 255, 8, 8) + bounds: 664 424 8 8 + - image: solid-color(84, 53, 0, 255, 8, 8) + bounds: 672 424 8 8 + - image: solid-color(85, 53, 0, 255, 8, 8) + bounds: 680 424 8 8 + - image: solid-color(86, 53, 0, 255, 8, 8) + bounds: 688 424 8 8 + - image: solid-color(87, 53, 0, 255, 8, 8) + bounds: 696 424 8 8 + - image: solid-color(88, 53, 0, 255, 8, 8) + bounds: 704 424 8 8 + - image: solid-color(89, 53, 0, 255, 8, 8) + bounds: 712 424 8 8 + - image: solid-color(90, 53, 0, 255, 8, 8) + bounds: 720 424 8 8 + - image: solid-color(91, 53, 0, 255, 8, 8) + bounds: 728 424 8 8 + - image: solid-color(92, 53, 0, 255, 8, 8) + bounds: 736 424 8 8 + - image: solid-color(93, 53, 0, 255, 8, 8) + bounds: 744 424 8 8 + - image: solid-color(94, 53, 0, 255, 8, 8) + bounds: 752 424 8 8 + - image: solid-color(95, 53, 0, 255, 8, 8) + bounds: 760 424 8 8 + - image: solid-color(96, 53, 0, 255, 8, 8) + bounds: 768 424 8 8 + - image: solid-color(97, 53, 0, 255, 8, 8) + bounds: 776 424 8 8 + - image: solid-color(98, 53, 0, 255, 8, 8) + bounds: 784 424 8 8 + - image: solid-color(99, 53, 0, 255, 8, 8) + bounds: 792 424 8 8 + - image: solid-color(100, 53, 0, 255, 8, 8) + bounds: 800 424 8 8 + - image: solid-color(101, 53, 0, 255, 8, 8) + bounds: 808 424 8 8 + - image: solid-color(102, 53, 0, 255, 8, 8) + bounds: 816 424 8 8 + - image: solid-color(103, 53, 0, 255, 8, 8) + bounds: 824 424 8 8 + - image: solid-color(104, 53, 0, 255, 8, 8) + bounds: 832 424 8 8 + - image: solid-color(105, 53, 0, 255, 8, 8) + bounds: 840 424 8 8 + - image: solid-color(106, 53, 0, 255, 8, 8) + bounds: 848 424 8 8 + - image: solid-color(107, 53, 0, 255, 8, 8) + bounds: 856 424 8 8 + - image: solid-color(108, 53, 0, 255, 8, 8) + bounds: 864 424 8 8 + - image: solid-color(109, 53, 0, 255, 8, 8) + bounds: 872 424 8 8 + - image: solid-color(110, 53, 0, 255, 8, 8) + bounds: 880 424 8 8 + - image: solid-color(111, 53, 0, 255, 8, 8) + bounds: 888 424 8 8 + - image: solid-color(112, 53, 0, 255, 8, 8) + bounds: 896 424 8 8 + - image: solid-color(113, 53, 0, 255, 8, 8) + bounds: 904 424 8 8 + - image: solid-color(114, 53, 0, 255, 8, 8) + bounds: 912 424 8 8 + - image: solid-color(115, 53, 0, 255, 8, 8) + bounds: 920 424 8 8 + - image: solid-color(116, 53, 0, 255, 8, 8) + bounds: 928 424 8 8 + - image: solid-color(117, 53, 0, 255, 8, 8) + bounds: 936 424 8 8 + - image: solid-color(118, 53, 0, 255, 8, 8) + bounds: 944 424 8 8 + - image: solid-color(119, 53, 0, 255, 8, 8) + bounds: 952 424 8 8 + - image: solid-color(120, 53, 0, 255, 8, 8) + bounds: 960 424 8 8 + - image: solid-color(121, 53, 0, 255, 8, 8) + bounds: 968 424 8 8 + - image: solid-color(122, 53, 0, 255, 8, 8) + bounds: 976 424 8 8 + - image: solid-color(123, 53, 0, 255, 8, 8) + bounds: 984 424 8 8 + - image: solid-color(124, 53, 0, 255, 8, 8) + bounds: 992 424 8 8 + - image: solid-color(125, 53, 0, 255, 8, 8) + bounds: 1000 424 8 8 + - image: solid-color(126, 53, 0, 255, 8, 8) + bounds: 1008 424 8 8 + - image: solid-color(127, 53, 0, 255, 8, 8) + bounds: 1016 424 8 8 + - image: solid-color(0, 54, 0, 255, 8, 8) + bounds: 0 432 8 8 + - image: solid-color(1, 54, 0, 255, 8, 8) + bounds: 8 432 8 8 + - image: solid-color(2, 54, 0, 255, 8, 8) + bounds: 16 432 8 8 + - image: solid-color(3, 54, 0, 255, 8, 8) + bounds: 24 432 8 8 + - image: solid-color(4, 54, 0, 255, 8, 8) + bounds: 32 432 8 8 + - image: solid-color(5, 54, 0, 255, 8, 8) + bounds: 40 432 8 8 + - image: solid-color(6, 54, 0, 255, 8, 8) + bounds: 48 432 8 8 + - image: solid-color(7, 54, 0, 255, 8, 8) + bounds: 56 432 8 8 + - image: solid-color(8, 54, 0, 255, 8, 8) + bounds: 64 432 8 8 + - image: solid-color(9, 54, 0, 255, 8, 8) + bounds: 72 432 8 8 + - image: solid-color(10, 54, 0, 255, 8, 8) + bounds: 80 432 8 8 + - image: solid-color(11, 54, 0, 255, 8, 8) + bounds: 88 432 8 8 + - image: solid-color(12, 54, 0, 255, 8, 8) + bounds: 96 432 8 8 + - image: solid-color(13, 54, 0, 255, 8, 8) + bounds: 104 432 8 8 + - image: solid-color(14, 54, 0, 255, 8, 8) + bounds: 112 432 8 8 + - image: solid-color(15, 54, 0, 255, 8, 8) + bounds: 120 432 8 8 + - image: solid-color(16, 54, 0, 255, 8, 8) + bounds: 128 432 8 8 + - image: solid-color(17, 54, 0, 255, 8, 8) + bounds: 136 432 8 8 + - image: solid-color(18, 54, 0, 255, 8, 8) + bounds: 144 432 8 8 + - image: solid-color(19, 54, 0, 255, 8, 8) + bounds: 152 432 8 8 + - image: solid-color(20, 54, 0, 255, 8, 8) + bounds: 160 432 8 8 + - image: solid-color(21, 54, 0, 255, 8, 8) + bounds: 168 432 8 8 + - image: solid-color(22, 54, 0, 255, 8, 8) + bounds: 176 432 8 8 + - image: solid-color(23, 54, 0, 255, 8, 8) + bounds: 184 432 8 8 + - image: solid-color(24, 54, 0, 255, 8, 8) + bounds: 192 432 8 8 + - image: solid-color(25, 54, 0, 255, 8, 8) + bounds: 200 432 8 8 + - image: solid-color(26, 54, 0, 255, 8, 8) + bounds: 208 432 8 8 + - image: solid-color(27, 54, 0, 255, 8, 8) + bounds: 216 432 8 8 + - image: solid-color(28, 54, 0, 255, 8, 8) + bounds: 224 432 8 8 + - image: solid-color(29, 54, 0, 255, 8, 8) + bounds: 232 432 8 8 + - image: solid-color(30, 54, 0, 255, 8, 8) + bounds: 240 432 8 8 + - image: solid-color(31, 54, 0, 255, 8, 8) + bounds: 248 432 8 8 + - image: solid-color(32, 54, 0, 255, 8, 8) + bounds: 256 432 8 8 + - image: solid-color(33, 54, 0, 255, 8, 8) + bounds: 264 432 8 8 + - image: solid-color(34, 54, 0, 255, 8, 8) + bounds: 272 432 8 8 + - image: solid-color(35, 54, 0, 255, 8, 8) + bounds: 280 432 8 8 + - image: solid-color(36, 54, 0, 255, 8, 8) + bounds: 288 432 8 8 + - image: solid-color(37, 54, 0, 255, 8, 8) + bounds: 296 432 8 8 + - image: solid-color(38, 54, 0, 255, 8, 8) + bounds: 304 432 8 8 + - image: solid-color(39, 54, 0, 255, 8, 8) + bounds: 312 432 8 8 + - image: solid-color(40, 54, 0, 255, 8, 8) + bounds: 320 432 8 8 + - image: solid-color(41, 54, 0, 255, 8, 8) + bounds: 328 432 8 8 + - image: solid-color(42, 54, 0, 255, 8, 8) + bounds: 336 432 8 8 + - image: solid-color(43, 54, 0, 255, 8, 8) + bounds: 344 432 8 8 + - image: solid-color(44, 54, 0, 255, 8, 8) + bounds: 352 432 8 8 + - image: solid-color(45, 54, 0, 255, 8, 8) + bounds: 360 432 8 8 + - image: solid-color(46, 54, 0, 255, 8, 8) + bounds: 368 432 8 8 + - image: solid-color(47, 54, 0, 255, 8, 8) + bounds: 376 432 8 8 + - image: solid-color(48, 54, 0, 255, 8, 8) + bounds: 384 432 8 8 + - image: solid-color(49, 54, 0, 255, 8, 8) + bounds: 392 432 8 8 + - image: solid-color(50, 54, 0, 255, 8, 8) + bounds: 400 432 8 8 + - image: solid-color(51, 54, 0, 255, 8, 8) + bounds: 408 432 8 8 + - image: solid-color(52, 54, 0, 255, 8, 8) + bounds: 416 432 8 8 + - image: solid-color(53, 54, 0, 255, 8, 8) + bounds: 424 432 8 8 + - image: solid-color(54, 54, 0, 255, 8, 8) + bounds: 432 432 8 8 + - image: solid-color(55, 54, 0, 255, 8, 8) + bounds: 440 432 8 8 + - image: solid-color(56, 54, 0, 255, 8, 8) + bounds: 448 432 8 8 + - image: solid-color(57, 54, 0, 255, 8, 8) + bounds: 456 432 8 8 + - image: solid-color(58, 54, 0, 255, 8, 8) + bounds: 464 432 8 8 + - image: solid-color(59, 54, 0, 255, 8, 8) + bounds: 472 432 8 8 + - image: solid-color(60, 54, 0, 255, 8, 8) + bounds: 480 432 8 8 + - image: solid-color(61, 54, 0, 255, 8, 8) + bounds: 488 432 8 8 + - image: solid-color(62, 54, 0, 255, 8, 8) + bounds: 496 432 8 8 + - image: solid-color(63, 54, 0, 255, 8, 8) + bounds: 504 432 8 8 + - image: solid-color(64, 54, 0, 255, 8, 8) + bounds: 512 432 8 8 + - image: solid-color(65, 54, 0, 255, 8, 8) + bounds: 520 432 8 8 + - image: solid-color(66, 54, 0, 255, 8, 8) + bounds: 528 432 8 8 + - image: solid-color(67, 54, 0, 255, 8, 8) + bounds: 536 432 8 8 + - image: solid-color(68, 54, 0, 255, 8, 8) + bounds: 544 432 8 8 + - image: solid-color(69, 54, 0, 255, 8, 8) + bounds: 552 432 8 8 + - image: solid-color(70, 54, 0, 255, 8, 8) + bounds: 560 432 8 8 + - image: solid-color(71, 54, 0, 255, 8, 8) + bounds: 568 432 8 8 + - image: solid-color(72, 54, 0, 255, 8, 8) + bounds: 576 432 8 8 + - image: solid-color(73, 54, 0, 255, 8, 8) + bounds: 584 432 8 8 + - image: solid-color(74, 54, 0, 255, 8, 8) + bounds: 592 432 8 8 + - image: solid-color(75, 54, 0, 255, 8, 8) + bounds: 600 432 8 8 + - image: solid-color(76, 54, 0, 255, 8, 8) + bounds: 608 432 8 8 + - image: solid-color(77, 54, 0, 255, 8, 8) + bounds: 616 432 8 8 + - image: solid-color(78, 54, 0, 255, 8, 8) + bounds: 624 432 8 8 + - image: solid-color(79, 54, 0, 255, 8, 8) + bounds: 632 432 8 8 + - image: solid-color(80, 54, 0, 255, 8, 8) + bounds: 640 432 8 8 + - image: solid-color(81, 54, 0, 255, 8, 8) + bounds: 648 432 8 8 + - image: solid-color(82, 54, 0, 255, 8, 8) + bounds: 656 432 8 8 + - image: solid-color(83, 54, 0, 255, 8, 8) + bounds: 664 432 8 8 + - image: solid-color(84, 54, 0, 255, 8, 8) + bounds: 672 432 8 8 + - image: solid-color(85, 54, 0, 255, 8, 8) + bounds: 680 432 8 8 + - image: solid-color(86, 54, 0, 255, 8, 8) + bounds: 688 432 8 8 + - image: solid-color(87, 54, 0, 255, 8, 8) + bounds: 696 432 8 8 + - image: solid-color(88, 54, 0, 255, 8, 8) + bounds: 704 432 8 8 + - image: solid-color(89, 54, 0, 255, 8, 8) + bounds: 712 432 8 8 + - image: solid-color(90, 54, 0, 255, 8, 8) + bounds: 720 432 8 8 + - image: solid-color(91, 54, 0, 255, 8, 8) + bounds: 728 432 8 8 + - image: solid-color(92, 54, 0, 255, 8, 8) + bounds: 736 432 8 8 + - image: solid-color(93, 54, 0, 255, 8, 8) + bounds: 744 432 8 8 + - image: solid-color(94, 54, 0, 255, 8, 8) + bounds: 752 432 8 8 + - image: solid-color(95, 54, 0, 255, 8, 8) + bounds: 760 432 8 8 + - image: solid-color(96, 54, 0, 255, 8, 8) + bounds: 768 432 8 8 + - image: solid-color(97, 54, 0, 255, 8, 8) + bounds: 776 432 8 8 + - image: solid-color(98, 54, 0, 255, 8, 8) + bounds: 784 432 8 8 + - image: solid-color(99, 54, 0, 255, 8, 8) + bounds: 792 432 8 8 + - image: solid-color(100, 54, 0, 255, 8, 8) + bounds: 800 432 8 8 + - image: solid-color(101, 54, 0, 255, 8, 8) + bounds: 808 432 8 8 + - image: solid-color(102, 54, 0, 255, 8, 8) + bounds: 816 432 8 8 + - image: solid-color(103, 54, 0, 255, 8, 8) + bounds: 824 432 8 8 + - image: solid-color(104, 54, 0, 255, 8, 8) + bounds: 832 432 8 8 + - image: solid-color(105, 54, 0, 255, 8, 8) + bounds: 840 432 8 8 + - image: solid-color(106, 54, 0, 255, 8, 8) + bounds: 848 432 8 8 + - image: solid-color(107, 54, 0, 255, 8, 8) + bounds: 856 432 8 8 + - image: solid-color(108, 54, 0, 255, 8, 8) + bounds: 864 432 8 8 + - image: solid-color(109, 54, 0, 255, 8, 8) + bounds: 872 432 8 8 + - image: solid-color(110, 54, 0, 255, 8, 8) + bounds: 880 432 8 8 + - image: solid-color(111, 54, 0, 255, 8, 8) + bounds: 888 432 8 8 + - image: solid-color(112, 54, 0, 255, 8, 8) + bounds: 896 432 8 8 + - image: solid-color(113, 54, 0, 255, 8, 8) + bounds: 904 432 8 8 + - image: solid-color(114, 54, 0, 255, 8, 8) + bounds: 912 432 8 8 + - image: solid-color(115, 54, 0, 255, 8, 8) + bounds: 920 432 8 8 + - image: solid-color(116, 54, 0, 255, 8, 8) + bounds: 928 432 8 8 + - image: solid-color(117, 54, 0, 255, 8, 8) + bounds: 936 432 8 8 + - image: solid-color(118, 54, 0, 255, 8, 8) + bounds: 944 432 8 8 + - image: solid-color(119, 54, 0, 255, 8, 8) + bounds: 952 432 8 8 + - image: solid-color(120, 54, 0, 255, 8, 8) + bounds: 960 432 8 8 + - image: solid-color(121, 54, 0, 255, 8, 8) + bounds: 968 432 8 8 + - image: solid-color(122, 54, 0, 255, 8, 8) + bounds: 976 432 8 8 + - image: solid-color(123, 54, 0, 255, 8, 8) + bounds: 984 432 8 8 + - image: solid-color(124, 54, 0, 255, 8, 8) + bounds: 992 432 8 8 + - image: solid-color(125, 54, 0, 255, 8, 8) + bounds: 1000 432 8 8 + - image: solid-color(126, 54, 0, 255, 8, 8) + bounds: 1008 432 8 8 + - image: solid-color(127, 54, 0, 255, 8, 8) + bounds: 1016 432 8 8 + - image: solid-color(0, 55, 0, 255, 8, 8) + bounds: 0 440 8 8 + - image: solid-color(1, 55, 0, 255, 8, 8) + bounds: 8 440 8 8 + - image: solid-color(2, 55, 0, 255, 8, 8) + bounds: 16 440 8 8 + - image: solid-color(3, 55, 0, 255, 8, 8) + bounds: 24 440 8 8 + - image: solid-color(4, 55, 0, 255, 8, 8) + bounds: 32 440 8 8 + - image: solid-color(5, 55, 0, 255, 8, 8) + bounds: 40 440 8 8 + - image: solid-color(6, 55, 0, 255, 8, 8) + bounds: 48 440 8 8 + - image: solid-color(7, 55, 0, 255, 8, 8) + bounds: 56 440 8 8 + - image: solid-color(8, 55, 0, 255, 8, 8) + bounds: 64 440 8 8 + - image: solid-color(9, 55, 0, 255, 8, 8) + bounds: 72 440 8 8 + - image: solid-color(10, 55, 0, 255, 8, 8) + bounds: 80 440 8 8 + - image: solid-color(11, 55, 0, 255, 8, 8) + bounds: 88 440 8 8 + - image: solid-color(12, 55, 0, 255, 8, 8) + bounds: 96 440 8 8 + - image: solid-color(13, 55, 0, 255, 8, 8) + bounds: 104 440 8 8 + - image: solid-color(14, 55, 0, 255, 8, 8) + bounds: 112 440 8 8 + - image: solid-color(15, 55, 0, 255, 8, 8) + bounds: 120 440 8 8 + - image: solid-color(16, 55, 0, 255, 8, 8) + bounds: 128 440 8 8 + - image: solid-color(17, 55, 0, 255, 8, 8) + bounds: 136 440 8 8 + - image: solid-color(18, 55, 0, 255, 8, 8) + bounds: 144 440 8 8 + - image: solid-color(19, 55, 0, 255, 8, 8) + bounds: 152 440 8 8 + - image: solid-color(20, 55, 0, 255, 8, 8) + bounds: 160 440 8 8 + - image: solid-color(21, 55, 0, 255, 8, 8) + bounds: 168 440 8 8 + - image: solid-color(22, 55, 0, 255, 8, 8) + bounds: 176 440 8 8 + - image: solid-color(23, 55, 0, 255, 8, 8) + bounds: 184 440 8 8 + - image: solid-color(24, 55, 0, 255, 8, 8) + bounds: 192 440 8 8 + - image: solid-color(25, 55, 0, 255, 8, 8) + bounds: 200 440 8 8 + - image: solid-color(26, 55, 0, 255, 8, 8) + bounds: 208 440 8 8 + - image: solid-color(27, 55, 0, 255, 8, 8) + bounds: 216 440 8 8 + - image: solid-color(28, 55, 0, 255, 8, 8) + bounds: 224 440 8 8 + - image: solid-color(29, 55, 0, 255, 8, 8) + bounds: 232 440 8 8 + - image: solid-color(30, 55, 0, 255, 8, 8) + bounds: 240 440 8 8 + - image: solid-color(31, 55, 0, 255, 8, 8) + bounds: 248 440 8 8 + - image: solid-color(32, 55, 0, 255, 8, 8) + bounds: 256 440 8 8 + - image: solid-color(33, 55, 0, 255, 8, 8) + bounds: 264 440 8 8 + - image: solid-color(34, 55, 0, 255, 8, 8) + bounds: 272 440 8 8 + - image: solid-color(35, 55, 0, 255, 8, 8) + bounds: 280 440 8 8 + - image: solid-color(36, 55, 0, 255, 8, 8) + bounds: 288 440 8 8 + - image: solid-color(37, 55, 0, 255, 8, 8) + bounds: 296 440 8 8 + - image: solid-color(38, 55, 0, 255, 8, 8) + bounds: 304 440 8 8 + - image: solid-color(39, 55, 0, 255, 8, 8) + bounds: 312 440 8 8 + - image: solid-color(40, 55, 0, 255, 8, 8) + bounds: 320 440 8 8 + - image: solid-color(41, 55, 0, 255, 8, 8) + bounds: 328 440 8 8 + - image: solid-color(42, 55, 0, 255, 8, 8) + bounds: 336 440 8 8 + - image: solid-color(43, 55, 0, 255, 8, 8) + bounds: 344 440 8 8 + - image: solid-color(44, 55, 0, 255, 8, 8) + bounds: 352 440 8 8 + - image: solid-color(45, 55, 0, 255, 8, 8) + bounds: 360 440 8 8 + - image: solid-color(46, 55, 0, 255, 8, 8) + bounds: 368 440 8 8 + - image: solid-color(47, 55, 0, 255, 8, 8) + bounds: 376 440 8 8 + - image: solid-color(48, 55, 0, 255, 8, 8) + bounds: 384 440 8 8 + - image: solid-color(49, 55, 0, 255, 8, 8) + bounds: 392 440 8 8 + - image: solid-color(50, 55, 0, 255, 8, 8) + bounds: 400 440 8 8 + - image: solid-color(51, 55, 0, 255, 8, 8) + bounds: 408 440 8 8 + - image: solid-color(52, 55, 0, 255, 8, 8) + bounds: 416 440 8 8 + - image: solid-color(53, 55, 0, 255, 8, 8) + bounds: 424 440 8 8 + - image: solid-color(54, 55, 0, 255, 8, 8) + bounds: 432 440 8 8 + - image: solid-color(55, 55, 0, 255, 8, 8) + bounds: 440 440 8 8 + - image: solid-color(56, 55, 0, 255, 8, 8) + bounds: 448 440 8 8 + - image: solid-color(57, 55, 0, 255, 8, 8) + bounds: 456 440 8 8 + - image: solid-color(58, 55, 0, 255, 8, 8) + bounds: 464 440 8 8 + - image: solid-color(59, 55, 0, 255, 8, 8) + bounds: 472 440 8 8 + - image: solid-color(60, 55, 0, 255, 8, 8) + bounds: 480 440 8 8 + - image: solid-color(61, 55, 0, 255, 8, 8) + bounds: 488 440 8 8 + - image: solid-color(62, 55, 0, 255, 8, 8) + bounds: 496 440 8 8 + - image: solid-color(63, 55, 0, 255, 8, 8) + bounds: 504 440 8 8 + - image: solid-color(64, 55, 0, 255, 8, 8) + bounds: 512 440 8 8 + - image: solid-color(65, 55, 0, 255, 8, 8) + bounds: 520 440 8 8 + - image: solid-color(66, 55, 0, 255, 8, 8) + bounds: 528 440 8 8 + - image: solid-color(67, 55, 0, 255, 8, 8) + bounds: 536 440 8 8 + - image: solid-color(68, 55, 0, 255, 8, 8) + bounds: 544 440 8 8 + - image: solid-color(69, 55, 0, 255, 8, 8) + bounds: 552 440 8 8 + - image: solid-color(70, 55, 0, 255, 8, 8) + bounds: 560 440 8 8 + - image: solid-color(71, 55, 0, 255, 8, 8) + bounds: 568 440 8 8 + - image: solid-color(72, 55, 0, 255, 8, 8) + bounds: 576 440 8 8 + - image: solid-color(73, 55, 0, 255, 8, 8) + bounds: 584 440 8 8 + - image: solid-color(74, 55, 0, 255, 8, 8) + bounds: 592 440 8 8 + - image: solid-color(75, 55, 0, 255, 8, 8) + bounds: 600 440 8 8 + - image: solid-color(76, 55, 0, 255, 8, 8) + bounds: 608 440 8 8 + - image: solid-color(77, 55, 0, 255, 8, 8) + bounds: 616 440 8 8 + - image: solid-color(78, 55, 0, 255, 8, 8) + bounds: 624 440 8 8 + - image: solid-color(79, 55, 0, 255, 8, 8) + bounds: 632 440 8 8 + - image: solid-color(80, 55, 0, 255, 8, 8) + bounds: 640 440 8 8 + - image: solid-color(81, 55, 0, 255, 8, 8) + bounds: 648 440 8 8 + - image: solid-color(82, 55, 0, 255, 8, 8) + bounds: 656 440 8 8 + - image: solid-color(83, 55, 0, 255, 8, 8) + bounds: 664 440 8 8 + - image: solid-color(84, 55, 0, 255, 8, 8) + bounds: 672 440 8 8 + - image: solid-color(85, 55, 0, 255, 8, 8) + bounds: 680 440 8 8 + - image: solid-color(86, 55, 0, 255, 8, 8) + bounds: 688 440 8 8 + - image: solid-color(87, 55, 0, 255, 8, 8) + bounds: 696 440 8 8 + - image: solid-color(88, 55, 0, 255, 8, 8) + bounds: 704 440 8 8 + - image: solid-color(89, 55, 0, 255, 8, 8) + bounds: 712 440 8 8 + - image: solid-color(90, 55, 0, 255, 8, 8) + bounds: 720 440 8 8 + - image: solid-color(91, 55, 0, 255, 8, 8) + bounds: 728 440 8 8 + - image: solid-color(92, 55, 0, 255, 8, 8) + bounds: 736 440 8 8 + - image: solid-color(93, 55, 0, 255, 8, 8) + bounds: 744 440 8 8 + - image: solid-color(94, 55, 0, 255, 8, 8) + bounds: 752 440 8 8 + - image: solid-color(95, 55, 0, 255, 8, 8) + bounds: 760 440 8 8 + - image: solid-color(96, 55, 0, 255, 8, 8) + bounds: 768 440 8 8 + - image: solid-color(97, 55, 0, 255, 8, 8) + bounds: 776 440 8 8 + - image: solid-color(98, 55, 0, 255, 8, 8) + bounds: 784 440 8 8 + - image: solid-color(99, 55, 0, 255, 8, 8) + bounds: 792 440 8 8 + - image: solid-color(100, 55, 0, 255, 8, 8) + bounds: 800 440 8 8 + - image: solid-color(101, 55, 0, 255, 8, 8) + bounds: 808 440 8 8 + - image: solid-color(102, 55, 0, 255, 8, 8) + bounds: 816 440 8 8 + - image: solid-color(103, 55, 0, 255, 8, 8) + bounds: 824 440 8 8 + - image: solid-color(104, 55, 0, 255, 8, 8) + bounds: 832 440 8 8 + - image: solid-color(105, 55, 0, 255, 8, 8) + bounds: 840 440 8 8 + - image: solid-color(106, 55, 0, 255, 8, 8) + bounds: 848 440 8 8 + - image: solid-color(107, 55, 0, 255, 8, 8) + bounds: 856 440 8 8 + - image: solid-color(108, 55, 0, 255, 8, 8) + bounds: 864 440 8 8 + - image: solid-color(109, 55, 0, 255, 8, 8) + bounds: 872 440 8 8 + - image: solid-color(110, 55, 0, 255, 8, 8) + bounds: 880 440 8 8 + - image: solid-color(111, 55, 0, 255, 8, 8) + bounds: 888 440 8 8 + - image: solid-color(112, 55, 0, 255, 8, 8) + bounds: 896 440 8 8 + - image: solid-color(113, 55, 0, 255, 8, 8) + bounds: 904 440 8 8 + - image: solid-color(114, 55, 0, 255, 8, 8) + bounds: 912 440 8 8 + - image: solid-color(115, 55, 0, 255, 8, 8) + bounds: 920 440 8 8 + - image: solid-color(116, 55, 0, 255, 8, 8) + bounds: 928 440 8 8 + - image: solid-color(117, 55, 0, 255, 8, 8) + bounds: 936 440 8 8 + - image: solid-color(118, 55, 0, 255, 8, 8) + bounds: 944 440 8 8 + - image: solid-color(119, 55, 0, 255, 8, 8) + bounds: 952 440 8 8 + - image: solid-color(120, 55, 0, 255, 8, 8) + bounds: 960 440 8 8 + - image: solid-color(121, 55, 0, 255, 8, 8) + bounds: 968 440 8 8 + - image: solid-color(122, 55, 0, 255, 8, 8) + bounds: 976 440 8 8 + - image: solid-color(123, 55, 0, 255, 8, 8) + bounds: 984 440 8 8 + - image: solid-color(124, 55, 0, 255, 8, 8) + bounds: 992 440 8 8 + - image: solid-color(125, 55, 0, 255, 8, 8) + bounds: 1000 440 8 8 + - image: solid-color(126, 55, 0, 255, 8, 8) + bounds: 1008 440 8 8 + - image: solid-color(127, 55, 0, 255, 8, 8) + bounds: 1016 440 8 8 + - image: solid-color(0, 56, 0, 255, 8, 8) + bounds: 0 448 8 8 + - image: solid-color(1, 56, 0, 255, 8, 8) + bounds: 8 448 8 8 + - image: solid-color(2, 56, 0, 255, 8, 8) + bounds: 16 448 8 8 + - image: solid-color(3, 56, 0, 255, 8, 8) + bounds: 24 448 8 8 + - image: solid-color(4, 56, 0, 255, 8, 8) + bounds: 32 448 8 8 + - image: solid-color(5, 56, 0, 255, 8, 8) + bounds: 40 448 8 8 + - image: solid-color(6, 56, 0, 255, 8, 8) + bounds: 48 448 8 8 + - image: solid-color(7, 56, 0, 255, 8, 8) + bounds: 56 448 8 8 + - image: solid-color(8, 56, 0, 255, 8, 8) + bounds: 64 448 8 8 + - image: solid-color(9, 56, 0, 255, 8, 8) + bounds: 72 448 8 8 + - image: solid-color(10, 56, 0, 255, 8, 8) + bounds: 80 448 8 8 + - image: solid-color(11, 56, 0, 255, 8, 8) + bounds: 88 448 8 8 + - image: solid-color(12, 56, 0, 255, 8, 8) + bounds: 96 448 8 8 + - image: solid-color(13, 56, 0, 255, 8, 8) + bounds: 104 448 8 8 + - image: solid-color(14, 56, 0, 255, 8, 8) + bounds: 112 448 8 8 + - image: solid-color(15, 56, 0, 255, 8, 8) + bounds: 120 448 8 8 + - image: solid-color(16, 56, 0, 255, 8, 8) + bounds: 128 448 8 8 + - image: solid-color(17, 56, 0, 255, 8, 8) + bounds: 136 448 8 8 + - image: solid-color(18, 56, 0, 255, 8, 8) + bounds: 144 448 8 8 + - image: solid-color(19, 56, 0, 255, 8, 8) + bounds: 152 448 8 8 + - image: solid-color(20, 56, 0, 255, 8, 8) + bounds: 160 448 8 8 + - image: solid-color(21, 56, 0, 255, 8, 8) + bounds: 168 448 8 8 + - image: solid-color(22, 56, 0, 255, 8, 8) + bounds: 176 448 8 8 + - image: solid-color(23, 56, 0, 255, 8, 8) + bounds: 184 448 8 8 + - image: solid-color(24, 56, 0, 255, 8, 8) + bounds: 192 448 8 8 + - image: solid-color(25, 56, 0, 255, 8, 8) + bounds: 200 448 8 8 + - image: solid-color(26, 56, 0, 255, 8, 8) + bounds: 208 448 8 8 + - image: solid-color(27, 56, 0, 255, 8, 8) + bounds: 216 448 8 8 + - image: solid-color(28, 56, 0, 255, 8, 8) + bounds: 224 448 8 8 + - image: solid-color(29, 56, 0, 255, 8, 8) + bounds: 232 448 8 8 + - image: solid-color(30, 56, 0, 255, 8, 8) + bounds: 240 448 8 8 + - image: solid-color(31, 56, 0, 255, 8, 8) + bounds: 248 448 8 8 + - image: solid-color(32, 56, 0, 255, 8, 8) + bounds: 256 448 8 8 + - image: solid-color(33, 56, 0, 255, 8, 8) + bounds: 264 448 8 8 + - image: solid-color(34, 56, 0, 255, 8, 8) + bounds: 272 448 8 8 + - image: solid-color(35, 56, 0, 255, 8, 8) + bounds: 280 448 8 8 + - image: solid-color(36, 56, 0, 255, 8, 8) + bounds: 288 448 8 8 + - image: solid-color(37, 56, 0, 255, 8, 8) + bounds: 296 448 8 8 + - image: solid-color(38, 56, 0, 255, 8, 8) + bounds: 304 448 8 8 + - image: solid-color(39, 56, 0, 255, 8, 8) + bounds: 312 448 8 8 + - image: solid-color(40, 56, 0, 255, 8, 8) + bounds: 320 448 8 8 + - image: solid-color(41, 56, 0, 255, 8, 8) + bounds: 328 448 8 8 + - image: solid-color(42, 56, 0, 255, 8, 8) + bounds: 336 448 8 8 + - image: solid-color(43, 56, 0, 255, 8, 8) + bounds: 344 448 8 8 + - image: solid-color(44, 56, 0, 255, 8, 8) + bounds: 352 448 8 8 + - image: solid-color(45, 56, 0, 255, 8, 8) + bounds: 360 448 8 8 + - image: solid-color(46, 56, 0, 255, 8, 8) + bounds: 368 448 8 8 + - image: solid-color(47, 56, 0, 255, 8, 8) + bounds: 376 448 8 8 + - image: solid-color(48, 56, 0, 255, 8, 8) + bounds: 384 448 8 8 + - image: solid-color(49, 56, 0, 255, 8, 8) + bounds: 392 448 8 8 + - image: solid-color(50, 56, 0, 255, 8, 8) + bounds: 400 448 8 8 + - image: solid-color(51, 56, 0, 255, 8, 8) + bounds: 408 448 8 8 + - image: solid-color(52, 56, 0, 255, 8, 8) + bounds: 416 448 8 8 + - image: solid-color(53, 56, 0, 255, 8, 8) + bounds: 424 448 8 8 + - image: solid-color(54, 56, 0, 255, 8, 8) + bounds: 432 448 8 8 + - image: solid-color(55, 56, 0, 255, 8, 8) + bounds: 440 448 8 8 + - image: solid-color(56, 56, 0, 255, 8, 8) + bounds: 448 448 8 8 + - image: solid-color(57, 56, 0, 255, 8, 8) + bounds: 456 448 8 8 + - image: solid-color(58, 56, 0, 255, 8, 8) + bounds: 464 448 8 8 + - image: solid-color(59, 56, 0, 255, 8, 8) + bounds: 472 448 8 8 + - image: solid-color(60, 56, 0, 255, 8, 8) + bounds: 480 448 8 8 + - image: solid-color(61, 56, 0, 255, 8, 8) + bounds: 488 448 8 8 + - image: solid-color(62, 56, 0, 255, 8, 8) + bounds: 496 448 8 8 + - image: solid-color(63, 56, 0, 255, 8, 8) + bounds: 504 448 8 8 + - image: solid-color(64, 56, 0, 255, 8, 8) + bounds: 512 448 8 8 + - image: solid-color(65, 56, 0, 255, 8, 8) + bounds: 520 448 8 8 + - image: solid-color(66, 56, 0, 255, 8, 8) + bounds: 528 448 8 8 + - image: solid-color(67, 56, 0, 255, 8, 8) + bounds: 536 448 8 8 + - image: solid-color(68, 56, 0, 255, 8, 8) + bounds: 544 448 8 8 + - image: solid-color(69, 56, 0, 255, 8, 8) + bounds: 552 448 8 8 + - image: solid-color(70, 56, 0, 255, 8, 8) + bounds: 560 448 8 8 + - image: solid-color(71, 56, 0, 255, 8, 8) + bounds: 568 448 8 8 + - image: solid-color(72, 56, 0, 255, 8, 8) + bounds: 576 448 8 8 + - image: solid-color(73, 56, 0, 255, 8, 8) + bounds: 584 448 8 8 + - image: solid-color(74, 56, 0, 255, 8, 8) + bounds: 592 448 8 8 + - image: solid-color(75, 56, 0, 255, 8, 8) + bounds: 600 448 8 8 + - image: solid-color(76, 56, 0, 255, 8, 8) + bounds: 608 448 8 8 + - image: solid-color(77, 56, 0, 255, 8, 8) + bounds: 616 448 8 8 + - image: solid-color(78, 56, 0, 255, 8, 8) + bounds: 624 448 8 8 + - image: solid-color(79, 56, 0, 255, 8, 8) + bounds: 632 448 8 8 + - image: solid-color(80, 56, 0, 255, 8, 8) + bounds: 640 448 8 8 + - image: solid-color(81, 56, 0, 255, 8, 8) + bounds: 648 448 8 8 + - image: solid-color(82, 56, 0, 255, 8, 8) + bounds: 656 448 8 8 + - image: solid-color(83, 56, 0, 255, 8, 8) + bounds: 664 448 8 8 + - image: solid-color(84, 56, 0, 255, 8, 8) + bounds: 672 448 8 8 + - image: solid-color(85, 56, 0, 255, 8, 8) + bounds: 680 448 8 8 + - image: solid-color(86, 56, 0, 255, 8, 8) + bounds: 688 448 8 8 + - image: solid-color(87, 56, 0, 255, 8, 8) + bounds: 696 448 8 8 + - image: solid-color(88, 56, 0, 255, 8, 8) + bounds: 704 448 8 8 + - image: solid-color(89, 56, 0, 255, 8, 8) + bounds: 712 448 8 8 + - image: solid-color(90, 56, 0, 255, 8, 8) + bounds: 720 448 8 8 + - image: solid-color(91, 56, 0, 255, 8, 8) + bounds: 728 448 8 8 + - image: solid-color(92, 56, 0, 255, 8, 8) + bounds: 736 448 8 8 + - image: solid-color(93, 56, 0, 255, 8, 8) + bounds: 744 448 8 8 + - image: solid-color(94, 56, 0, 255, 8, 8) + bounds: 752 448 8 8 + - image: solid-color(95, 56, 0, 255, 8, 8) + bounds: 760 448 8 8 + - image: solid-color(96, 56, 0, 255, 8, 8) + bounds: 768 448 8 8 + - image: solid-color(97, 56, 0, 255, 8, 8) + bounds: 776 448 8 8 + - image: solid-color(98, 56, 0, 255, 8, 8) + bounds: 784 448 8 8 + - image: solid-color(99, 56, 0, 255, 8, 8) + bounds: 792 448 8 8 + - image: solid-color(100, 56, 0, 255, 8, 8) + bounds: 800 448 8 8 + - image: solid-color(101, 56, 0, 255, 8, 8) + bounds: 808 448 8 8 + - image: solid-color(102, 56, 0, 255, 8, 8) + bounds: 816 448 8 8 + - image: solid-color(103, 56, 0, 255, 8, 8) + bounds: 824 448 8 8 + - image: solid-color(104, 56, 0, 255, 8, 8) + bounds: 832 448 8 8 + - image: solid-color(105, 56, 0, 255, 8, 8) + bounds: 840 448 8 8 + - image: solid-color(106, 56, 0, 255, 8, 8) + bounds: 848 448 8 8 + - image: solid-color(107, 56, 0, 255, 8, 8) + bounds: 856 448 8 8 + - image: solid-color(108, 56, 0, 255, 8, 8) + bounds: 864 448 8 8 + - image: solid-color(109, 56, 0, 255, 8, 8) + bounds: 872 448 8 8 + - image: solid-color(110, 56, 0, 255, 8, 8) + bounds: 880 448 8 8 + - image: solid-color(111, 56, 0, 255, 8, 8) + bounds: 888 448 8 8 + - image: solid-color(112, 56, 0, 255, 8, 8) + bounds: 896 448 8 8 + - image: solid-color(113, 56, 0, 255, 8, 8) + bounds: 904 448 8 8 + - image: solid-color(114, 56, 0, 255, 8, 8) + bounds: 912 448 8 8 + - image: solid-color(115, 56, 0, 255, 8, 8) + bounds: 920 448 8 8 + - image: solid-color(116, 56, 0, 255, 8, 8) + bounds: 928 448 8 8 + - image: solid-color(117, 56, 0, 255, 8, 8) + bounds: 936 448 8 8 + - image: solid-color(118, 56, 0, 255, 8, 8) + bounds: 944 448 8 8 + - image: solid-color(119, 56, 0, 255, 8, 8) + bounds: 952 448 8 8 + - image: solid-color(120, 56, 0, 255, 8, 8) + bounds: 960 448 8 8 + - image: solid-color(121, 56, 0, 255, 8, 8) + bounds: 968 448 8 8 + - image: solid-color(122, 56, 0, 255, 8, 8) + bounds: 976 448 8 8 + - image: solid-color(123, 56, 0, 255, 8, 8) + bounds: 984 448 8 8 + - image: solid-color(124, 56, 0, 255, 8, 8) + bounds: 992 448 8 8 + - image: solid-color(125, 56, 0, 255, 8, 8) + bounds: 1000 448 8 8 + - image: solid-color(126, 56, 0, 255, 8, 8) + bounds: 1008 448 8 8 + - image: solid-color(127, 56, 0, 255, 8, 8) + bounds: 1016 448 8 8 + - image: solid-color(0, 57, 0, 255, 8, 8) + bounds: 0 456 8 8 + - image: solid-color(1, 57, 0, 255, 8, 8) + bounds: 8 456 8 8 + - image: solid-color(2, 57, 0, 255, 8, 8) + bounds: 16 456 8 8 + - image: solid-color(3, 57, 0, 255, 8, 8) + bounds: 24 456 8 8 + - image: solid-color(4, 57, 0, 255, 8, 8) + bounds: 32 456 8 8 + - image: solid-color(5, 57, 0, 255, 8, 8) + bounds: 40 456 8 8 + - image: solid-color(6, 57, 0, 255, 8, 8) + bounds: 48 456 8 8 + - image: solid-color(7, 57, 0, 255, 8, 8) + bounds: 56 456 8 8 + - image: solid-color(8, 57, 0, 255, 8, 8) + bounds: 64 456 8 8 + - image: solid-color(9, 57, 0, 255, 8, 8) + bounds: 72 456 8 8 + - image: solid-color(10, 57, 0, 255, 8, 8) + bounds: 80 456 8 8 + - image: solid-color(11, 57, 0, 255, 8, 8) + bounds: 88 456 8 8 + - image: solid-color(12, 57, 0, 255, 8, 8) + bounds: 96 456 8 8 + - image: solid-color(13, 57, 0, 255, 8, 8) + bounds: 104 456 8 8 + - image: solid-color(14, 57, 0, 255, 8, 8) + bounds: 112 456 8 8 + - image: solid-color(15, 57, 0, 255, 8, 8) + bounds: 120 456 8 8 + - image: solid-color(16, 57, 0, 255, 8, 8) + bounds: 128 456 8 8 + - image: solid-color(17, 57, 0, 255, 8, 8) + bounds: 136 456 8 8 + - image: solid-color(18, 57, 0, 255, 8, 8) + bounds: 144 456 8 8 + - image: solid-color(19, 57, 0, 255, 8, 8) + bounds: 152 456 8 8 + - image: solid-color(20, 57, 0, 255, 8, 8) + bounds: 160 456 8 8 + - image: solid-color(21, 57, 0, 255, 8, 8) + bounds: 168 456 8 8 + - image: solid-color(22, 57, 0, 255, 8, 8) + bounds: 176 456 8 8 + - image: solid-color(23, 57, 0, 255, 8, 8) + bounds: 184 456 8 8 + - image: solid-color(24, 57, 0, 255, 8, 8) + bounds: 192 456 8 8 + - image: solid-color(25, 57, 0, 255, 8, 8) + bounds: 200 456 8 8 + - image: solid-color(26, 57, 0, 255, 8, 8) + bounds: 208 456 8 8 + - image: solid-color(27, 57, 0, 255, 8, 8) + bounds: 216 456 8 8 + - image: solid-color(28, 57, 0, 255, 8, 8) + bounds: 224 456 8 8 + - image: solid-color(29, 57, 0, 255, 8, 8) + bounds: 232 456 8 8 + - image: solid-color(30, 57, 0, 255, 8, 8) + bounds: 240 456 8 8 + - image: solid-color(31, 57, 0, 255, 8, 8) + bounds: 248 456 8 8 + - image: solid-color(32, 57, 0, 255, 8, 8) + bounds: 256 456 8 8 + - image: solid-color(33, 57, 0, 255, 8, 8) + bounds: 264 456 8 8 + - image: solid-color(34, 57, 0, 255, 8, 8) + bounds: 272 456 8 8 + - image: solid-color(35, 57, 0, 255, 8, 8) + bounds: 280 456 8 8 + - image: solid-color(36, 57, 0, 255, 8, 8) + bounds: 288 456 8 8 + - image: solid-color(37, 57, 0, 255, 8, 8) + bounds: 296 456 8 8 + - image: solid-color(38, 57, 0, 255, 8, 8) + bounds: 304 456 8 8 + - image: solid-color(39, 57, 0, 255, 8, 8) + bounds: 312 456 8 8 + - image: solid-color(40, 57, 0, 255, 8, 8) + bounds: 320 456 8 8 + - image: solid-color(41, 57, 0, 255, 8, 8) + bounds: 328 456 8 8 + - image: solid-color(42, 57, 0, 255, 8, 8) + bounds: 336 456 8 8 + - image: solid-color(43, 57, 0, 255, 8, 8) + bounds: 344 456 8 8 + - image: solid-color(44, 57, 0, 255, 8, 8) + bounds: 352 456 8 8 + - image: solid-color(45, 57, 0, 255, 8, 8) + bounds: 360 456 8 8 + - image: solid-color(46, 57, 0, 255, 8, 8) + bounds: 368 456 8 8 + - image: solid-color(47, 57, 0, 255, 8, 8) + bounds: 376 456 8 8 + - image: solid-color(48, 57, 0, 255, 8, 8) + bounds: 384 456 8 8 + - image: solid-color(49, 57, 0, 255, 8, 8) + bounds: 392 456 8 8 + - image: solid-color(50, 57, 0, 255, 8, 8) + bounds: 400 456 8 8 + - image: solid-color(51, 57, 0, 255, 8, 8) + bounds: 408 456 8 8 + - image: solid-color(52, 57, 0, 255, 8, 8) + bounds: 416 456 8 8 + - image: solid-color(53, 57, 0, 255, 8, 8) + bounds: 424 456 8 8 + - image: solid-color(54, 57, 0, 255, 8, 8) + bounds: 432 456 8 8 + - image: solid-color(55, 57, 0, 255, 8, 8) + bounds: 440 456 8 8 + - image: solid-color(56, 57, 0, 255, 8, 8) + bounds: 448 456 8 8 + - image: solid-color(57, 57, 0, 255, 8, 8) + bounds: 456 456 8 8 + - image: solid-color(58, 57, 0, 255, 8, 8) + bounds: 464 456 8 8 + - image: solid-color(59, 57, 0, 255, 8, 8) + bounds: 472 456 8 8 + - image: solid-color(60, 57, 0, 255, 8, 8) + bounds: 480 456 8 8 + - image: solid-color(61, 57, 0, 255, 8, 8) + bounds: 488 456 8 8 + - image: solid-color(62, 57, 0, 255, 8, 8) + bounds: 496 456 8 8 + - image: solid-color(63, 57, 0, 255, 8, 8) + bounds: 504 456 8 8 + - image: solid-color(64, 57, 0, 255, 8, 8) + bounds: 512 456 8 8 + - image: solid-color(65, 57, 0, 255, 8, 8) + bounds: 520 456 8 8 + - image: solid-color(66, 57, 0, 255, 8, 8) + bounds: 528 456 8 8 + - image: solid-color(67, 57, 0, 255, 8, 8) + bounds: 536 456 8 8 + - image: solid-color(68, 57, 0, 255, 8, 8) + bounds: 544 456 8 8 + - image: solid-color(69, 57, 0, 255, 8, 8) + bounds: 552 456 8 8 + - image: solid-color(70, 57, 0, 255, 8, 8) + bounds: 560 456 8 8 + - image: solid-color(71, 57, 0, 255, 8, 8) + bounds: 568 456 8 8 + - image: solid-color(72, 57, 0, 255, 8, 8) + bounds: 576 456 8 8 + - image: solid-color(73, 57, 0, 255, 8, 8) + bounds: 584 456 8 8 + - image: solid-color(74, 57, 0, 255, 8, 8) + bounds: 592 456 8 8 + - image: solid-color(75, 57, 0, 255, 8, 8) + bounds: 600 456 8 8 + - image: solid-color(76, 57, 0, 255, 8, 8) + bounds: 608 456 8 8 + - image: solid-color(77, 57, 0, 255, 8, 8) + bounds: 616 456 8 8 + - image: solid-color(78, 57, 0, 255, 8, 8) + bounds: 624 456 8 8 + - image: solid-color(79, 57, 0, 255, 8, 8) + bounds: 632 456 8 8 + - image: solid-color(80, 57, 0, 255, 8, 8) + bounds: 640 456 8 8 + - image: solid-color(81, 57, 0, 255, 8, 8) + bounds: 648 456 8 8 + - image: solid-color(82, 57, 0, 255, 8, 8) + bounds: 656 456 8 8 + - image: solid-color(83, 57, 0, 255, 8, 8) + bounds: 664 456 8 8 + - image: solid-color(84, 57, 0, 255, 8, 8) + bounds: 672 456 8 8 + - image: solid-color(85, 57, 0, 255, 8, 8) + bounds: 680 456 8 8 + - image: solid-color(86, 57, 0, 255, 8, 8) + bounds: 688 456 8 8 + - image: solid-color(87, 57, 0, 255, 8, 8) + bounds: 696 456 8 8 + - image: solid-color(88, 57, 0, 255, 8, 8) + bounds: 704 456 8 8 + - image: solid-color(89, 57, 0, 255, 8, 8) + bounds: 712 456 8 8 + - image: solid-color(90, 57, 0, 255, 8, 8) + bounds: 720 456 8 8 + - image: solid-color(91, 57, 0, 255, 8, 8) + bounds: 728 456 8 8 + - image: solid-color(92, 57, 0, 255, 8, 8) + bounds: 736 456 8 8 + - image: solid-color(93, 57, 0, 255, 8, 8) + bounds: 744 456 8 8 + - image: solid-color(94, 57, 0, 255, 8, 8) + bounds: 752 456 8 8 + - image: solid-color(95, 57, 0, 255, 8, 8) + bounds: 760 456 8 8 + - image: solid-color(96, 57, 0, 255, 8, 8) + bounds: 768 456 8 8 + - image: solid-color(97, 57, 0, 255, 8, 8) + bounds: 776 456 8 8 + - image: solid-color(98, 57, 0, 255, 8, 8) + bounds: 784 456 8 8 + - image: solid-color(99, 57, 0, 255, 8, 8) + bounds: 792 456 8 8 + - image: solid-color(100, 57, 0, 255, 8, 8) + bounds: 800 456 8 8 + - image: solid-color(101, 57, 0, 255, 8, 8) + bounds: 808 456 8 8 + - image: solid-color(102, 57, 0, 255, 8, 8) + bounds: 816 456 8 8 + - image: solid-color(103, 57, 0, 255, 8, 8) + bounds: 824 456 8 8 + - image: solid-color(104, 57, 0, 255, 8, 8) + bounds: 832 456 8 8 + - image: solid-color(105, 57, 0, 255, 8, 8) + bounds: 840 456 8 8 + - image: solid-color(106, 57, 0, 255, 8, 8) + bounds: 848 456 8 8 + - image: solid-color(107, 57, 0, 255, 8, 8) + bounds: 856 456 8 8 + - image: solid-color(108, 57, 0, 255, 8, 8) + bounds: 864 456 8 8 + - image: solid-color(109, 57, 0, 255, 8, 8) + bounds: 872 456 8 8 + - image: solid-color(110, 57, 0, 255, 8, 8) + bounds: 880 456 8 8 + - image: solid-color(111, 57, 0, 255, 8, 8) + bounds: 888 456 8 8 + - image: solid-color(112, 57, 0, 255, 8, 8) + bounds: 896 456 8 8 + - image: solid-color(113, 57, 0, 255, 8, 8) + bounds: 904 456 8 8 + - image: solid-color(114, 57, 0, 255, 8, 8) + bounds: 912 456 8 8 + - image: solid-color(115, 57, 0, 255, 8, 8) + bounds: 920 456 8 8 + - image: solid-color(116, 57, 0, 255, 8, 8) + bounds: 928 456 8 8 + - image: solid-color(117, 57, 0, 255, 8, 8) + bounds: 936 456 8 8 + - image: solid-color(118, 57, 0, 255, 8, 8) + bounds: 944 456 8 8 + - image: solid-color(119, 57, 0, 255, 8, 8) + bounds: 952 456 8 8 + - image: solid-color(120, 57, 0, 255, 8, 8) + bounds: 960 456 8 8 + - image: solid-color(121, 57, 0, 255, 8, 8) + bounds: 968 456 8 8 + - image: solid-color(122, 57, 0, 255, 8, 8) + bounds: 976 456 8 8 + - image: solid-color(123, 57, 0, 255, 8, 8) + bounds: 984 456 8 8 + - image: solid-color(124, 57, 0, 255, 8, 8) + bounds: 992 456 8 8 + - image: solid-color(125, 57, 0, 255, 8, 8) + bounds: 1000 456 8 8 + - image: solid-color(126, 57, 0, 255, 8, 8) + bounds: 1008 456 8 8 + - image: solid-color(127, 57, 0, 255, 8, 8) + bounds: 1016 456 8 8 + - image: solid-color(0, 58, 0, 255, 8, 8) + bounds: 0 464 8 8 + - image: solid-color(1, 58, 0, 255, 8, 8) + bounds: 8 464 8 8 + - image: solid-color(2, 58, 0, 255, 8, 8) + bounds: 16 464 8 8 + - image: solid-color(3, 58, 0, 255, 8, 8) + bounds: 24 464 8 8 + - image: solid-color(4, 58, 0, 255, 8, 8) + bounds: 32 464 8 8 + - image: solid-color(5, 58, 0, 255, 8, 8) + bounds: 40 464 8 8 + - image: solid-color(6, 58, 0, 255, 8, 8) + bounds: 48 464 8 8 + - image: solid-color(7, 58, 0, 255, 8, 8) + bounds: 56 464 8 8 + - image: solid-color(8, 58, 0, 255, 8, 8) + bounds: 64 464 8 8 + - image: solid-color(9, 58, 0, 255, 8, 8) + bounds: 72 464 8 8 + - image: solid-color(10, 58, 0, 255, 8, 8) + bounds: 80 464 8 8 + - image: solid-color(11, 58, 0, 255, 8, 8) + bounds: 88 464 8 8 + - image: solid-color(12, 58, 0, 255, 8, 8) + bounds: 96 464 8 8 + - image: solid-color(13, 58, 0, 255, 8, 8) + bounds: 104 464 8 8 + - image: solid-color(14, 58, 0, 255, 8, 8) + bounds: 112 464 8 8 + - image: solid-color(15, 58, 0, 255, 8, 8) + bounds: 120 464 8 8 + - image: solid-color(16, 58, 0, 255, 8, 8) + bounds: 128 464 8 8 + - image: solid-color(17, 58, 0, 255, 8, 8) + bounds: 136 464 8 8 + - image: solid-color(18, 58, 0, 255, 8, 8) + bounds: 144 464 8 8 + - image: solid-color(19, 58, 0, 255, 8, 8) + bounds: 152 464 8 8 + - image: solid-color(20, 58, 0, 255, 8, 8) + bounds: 160 464 8 8 + - image: solid-color(21, 58, 0, 255, 8, 8) + bounds: 168 464 8 8 + - image: solid-color(22, 58, 0, 255, 8, 8) + bounds: 176 464 8 8 + - image: solid-color(23, 58, 0, 255, 8, 8) + bounds: 184 464 8 8 + - image: solid-color(24, 58, 0, 255, 8, 8) + bounds: 192 464 8 8 + - image: solid-color(25, 58, 0, 255, 8, 8) + bounds: 200 464 8 8 + - image: solid-color(26, 58, 0, 255, 8, 8) + bounds: 208 464 8 8 + - image: solid-color(27, 58, 0, 255, 8, 8) + bounds: 216 464 8 8 + - image: solid-color(28, 58, 0, 255, 8, 8) + bounds: 224 464 8 8 + - image: solid-color(29, 58, 0, 255, 8, 8) + bounds: 232 464 8 8 + - image: solid-color(30, 58, 0, 255, 8, 8) + bounds: 240 464 8 8 + - image: solid-color(31, 58, 0, 255, 8, 8) + bounds: 248 464 8 8 + - image: solid-color(32, 58, 0, 255, 8, 8) + bounds: 256 464 8 8 + - image: solid-color(33, 58, 0, 255, 8, 8) + bounds: 264 464 8 8 + - image: solid-color(34, 58, 0, 255, 8, 8) + bounds: 272 464 8 8 + - image: solid-color(35, 58, 0, 255, 8, 8) + bounds: 280 464 8 8 + - image: solid-color(36, 58, 0, 255, 8, 8) + bounds: 288 464 8 8 + - image: solid-color(37, 58, 0, 255, 8, 8) + bounds: 296 464 8 8 + - image: solid-color(38, 58, 0, 255, 8, 8) + bounds: 304 464 8 8 + - image: solid-color(39, 58, 0, 255, 8, 8) + bounds: 312 464 8 8 + - image: solid-color(40, 58, 0, 255, 8, 8) + bounds: 320 464 8 8 + - image: solid-color(41, 58, 0, 255, 8, 8) + bounds: 328 464 8 8 + - image: solid-color(42, 58, 0, 255, 8, 8) + bounds: 336 464 8 8 + - image: solid-color(43, 58, 0, 255, 8, 8) + bounds: 344 464 8 8 + - image: solid-color(44, 58, 0, 255, 8, 8) + bounds: 352 464 8 8 + - image: solid-color(45, 58, 0, 255, 8, 8) + bounds: 360 464 8 8 + - image: solid-color(46, 58, 0, 255, 8, 8) + bounds: 368 464 8 8 + - image: solid-color(47, 58, 0, 255, 8, 8) + bounds: 376 464 8 8 + - image: solid-color(48, 58, 0, 255, 8, 8) + bounds: 384 464 8 8 + - image: solid-color(49, 58, 0, 255, 8, 8) + bounds: 392 464 8 8 + - image: solid-color(50, 58, 0, 255, 8, 8) + bounds: 400 464 8 8 + - image: solid-color(51, 58, 0, 255, 8, 8) + bounds: 408 464 8 8 + - image: solid-color(52, 58, 0, 255, 8, 8) + bounds: 416 464 8 8 + - image: solid-color(53, 58, 0, 255, 8, 8) + bounds: 424 464 8 8 + - image: solid-color(54, 58, 0, 255, 8, 8) + bounds: 432 464 8 8 + - image: solid-color(55, 58, 0, 255, 8, 8) + bounds: 440 464 8 8 + - image: solid-color(56, 58, 0, 255, 8, 8) + bounds: 448 464 8 8 + - image: solid-color(57, 58, 0, 255, 8, 8) + bounds: 456 464 8 8 + - image: solid-color(58, 58, 0, 255, 8, 8) + bounds: 464 464 8 8 + - image: solid-color(59, 58, 0, 255, 8, 8) + bounds: 472 464 8 8 + - image: solid-color(60, 58, 0, 255, 8, 8) + bounds: 480 464 8 8 + - image: solid-color(61, 58, 0, 255, 8, 8) + bounds: 488 464 8 8 + - image: solid-color(62, 58, 0, 255, 8, 8) + bounds: 496 464 8 8 + - image: solid-color(63, 58, 0, 255, 8, 8) + bounds: 504 464 8 8 + - image: solid-color(64, 58, 0, 255, 8, 8) + bounds: 512 464 8 8 + - image: solid-color(65, 58, 0, 255, 8, 8) + bounds: 520 464 8 8 + - image: solid-color(66, 58, 0, 255, 8, 8) + bounds: 528 464 8 8 + - image: solid-color(67, 58, 0, 255, 8, 8) + bounds: 536 464 8 8 + - image: solid-color(68, 58, 0, 255, 8, 8) + bounds: 544 464 8 8 + - image: solid-color(69, 58, 0, 255, 8, 8) + bounds: 552 464 8 8 + - image: solid-color(70, 58, 0, 255, 8, 8) + bounds: 560 464 8 8 + - image: solid-color(71, 58, 0, 255, 8, 8) + bounds: 568 464 8 8 + - image: solid-color(72, 58, 0, 255, 8, 8) + bounds: 576 464 8 8 + - image: solid-color(73, 58, 0, 255, 8, 8) + bounds: 584 464 8 8 + - image: solid-color(74, 58, 0, 255, 8, 8) + bounds: 592 464 8 8 + - image: solid-color(75, 58, 0, 255, 8, 8) + bounds: 600 464 8 8 + - image: solid-color(76, 58, 0, 255, 8, 8) + bounds: 608 464 8 8 + - image: solid-color(77, 58, 0, 255, 8, 8) + bounds: 616 464 8 8 + - image: solid-color(78, 58, 0, 255, 8, 8) + bounds: 624 464 8 8 + - image: solid-color(79, 58, 0, 255, 8, 8) + bounds: 632 464 8 8 + - image: solid-color(80, 58, 0, 255, 8, 8) + bounds: 640 464 8 8 + - image: solid-color(81, 58, 0, 255, 8, 8) + bounds: 648 464 8 8 + - image: solid-color(82, 58, 0, 255, 8, 8) + bounds: 656 464 8 8 + - image: solid-color(83, 58, 0, 255, 8, 8) + bounds: 664 464 8 8 + - image: solid-color(84, 58, 0, 255, 8, 8) + bounds: 672 464 8 8 + - image: solid-color(85, 58, 0, 255, 8, 8) + bounds: 680 464 8 8 + - image: solid-color(86, 58, 0, 255, 8, 8) + bounds: 688 464 8 8 + - image: solid-color(87, 58, 0, 255, 8, 8) + bounds: 696 464 8 8 + - image: solid-color(88, 58, 0, 255, 8, 8) + bounds: 704 464 8 8 + - image: solid-color(89, 58, 0, 255, 8, 8) + bounds: 712 464 8 8 + - image: solid-color(90, 58, 0, 255, 8, 8) + bounds: 720 464 8 8 + - image: solid-color(91, 58, 0, 255, 8, 8) + bounds: 728 464 8 8 + - image: solid-color(92, 58, 0, 255, 8, 8) + bounds: 736 464 8 8 + - image: solid-color(93, 58, 0, 255, 8, 8) + bounds: 744 464 8 8 + - image: solid-color(94, 58, 0, 255, 8, 8) + bounds: 752 464 8 8 + - image: solid-color(95, 58, 0, 255, 8, 8) + bounds: 760 464 8 8 + - image: solid-color(96, 58, 0, 255, 8, 8) + bounds: 768 464 8 8 + - image: solid-color(97, 58, 0, 255, 8, 8) + bounds: 776 464 8 8 + - image: solid-color(98, 58, 0, 255, 8, 8) + bounds: 784 464 8 8 + - image: solid-color(99, 58, 0, 255, 8, 8) + bounds: 792 464 8 8 + - image: solid-color(100, 58, 0, 255, 8, 8) + bounds: 800 464 8 8 + - image: solid-color(101, 58, 0, 255, 8, 8) + bounds: 808 464 8 8 + - image: solid-color(102, 58, 0, 255, 8, 8) + bounds: 816 464 8 8 + - image: solid-color(103, 58, 0, 255, 8, 8) + bounds: 824 464 8 8 + - image: solid-color(104, 58, 0, 255, 8, 8) + bounds: 832 464 8 8 + - image: solid-color(105, 58, 0, 255, 8, 8) + bounds: 840 464 8 8 + - image: solid-color(106, 58, 0, 255, 8, 8) + bounds: 848 464 8 8 + - image: solid-color(107, 58, 0, 255, 8, 8) + bounds: 856 464 8 8 + - image: solid-color(108, 58, 0, 255, 8, 8) + bounds: 864 464 8 8 + - image: solid-color(109, 58, 0, 255, 8, 8) + bounds: 872 464 8 8 + - image: solid-color(110, 58, 0, 255, 8, 8) + bounds: 880 464 8 8 + - image: solid-color(111, 58, 0, 255, 8, 8) + bounds: 888 464 8 8 + - image: solid-color(112, 58, 0, 255, 8, 8) + bounds: 896 464 8 8 + - image: solid-color(113, 58, 0, 255, 8, 8) + bounds: 904 464 8 8 + - image: solid-color(114, 58, 0, 255, 8, 8) + bounds: 912 464 8 8 + - image: solid-color(115, 58, 0, 255, 8, 8) + bounds: 920 464 8 8 + - image: solid-color(116, 58, 0, 255, 8, 8) + bounds: 928 464 8 8 + - image: solid-color(117, 58, 0, 255, 8, 8) + bounds: 936 464 8 8 + - image: solid-color(118, 58, 0, 255, 8, 8) + bounds: 944 464 8 8 + - image: solid-color(119, 58, 0, 255, 8, 8) + bounds: 952 464 8 8 + - image: solid-color(120, 58, 0, 255, 8, 8) + bounds: 960 464 8 8 + - image: solid-color(121, 58, 0, 255, 8, 8) + bounds: 968 464 8 8 + - image: solid-color(122, 58, 0, 255, 8, 8) + bounds: 976 464 8 8 + - image: solid-color(123, 58, 0, 255, 8, 8) + bounds: 984 464 8 8 + - image: solid-color(124, 58, 0, 255, 8, 8) + bounds: 992 464 8 8 + - image: solid-color(125, 58, 0, 255, 8, 8) + bounds: 1000 464 8 8 + - image: solid-color(126, 58, 0, 255, 8, 8) + bounds: 1008 464 8 8 + - image: solid-color(127, 58, 0, 255, 8, 8) + bounds: 1016 464 8 8 + - image: solid-color(0, 59, 0, 255, 8, 8) + bounds: 0 472 8 8 + - image: solid-color(1, 59, 0, 255, 8, 8) + bounds: 8 472 8 8 + - image: solid-color(2, 59, 0, 255, 8, 8) + bounds: 16 472 8 8 + - image: solid-color(3, 59, 0, 255, 8, 8) + bounds: 24 472 8 8 + - image: solid-color(4, 59, 0, 255, 8, 8) + bounds: 32 472 8 8 + - image: solid-color(5, 59, 0, 255, 8, 8) + bounds: 40 472 8 8 + - image: solid-color(6, 59, 0, 255, 8, 8) + bounds: 48 472 8 8 + - image: solid-color(7, 59, 0, 255, 8, 8) + bounds: 56 472 8 8 + - image: solid-color(8, 59, 0, 255, 8, 8) + bounds: 64 472 8 8 + - image: solid-color(9, 59, 0, 255, 8, 8) + bounds: 72 472 8 8 + - image: solid-color(10, 59, 0, 255, 8, 8) + bounds: 80 472 8 8 + - image: solid-color(11, 59, 0, 255, 8, 8) + bounds: 88 472 8 8 + - image: solid-color(12, 59, 0, 255, 8, 8) + bounds: 96 472 8 8 + - image: solid-color(13, 59, 0, 255, 8, 8) + bounds: 104 472 8 8 + - image: solid-color(14, 59, 0, 255, 8, 8) + bounds: 112 472 8 8 + - image: solid-color(15, 59, 0, 255, 8, 8) + bounds: 120 472 8 8 + - image: solid-color(16, 59, 0, 255, 8, 8) + bounds: 128 472 8 8 + - image: solid-color(17, 59, 0, 255, 8, 8) + bounds: 136 472 8 8 + - image: solid-color(18, 59, 0, 255, 8, 8) + bounds: 144 472 8 8 + - image: solid-color(19, 59, 0, 255, 8, 8) + bounds: 152 472 8 8 + - image: solid-color(20, 59, 0, 255, 8, 8) + bounds: 160 472 8 8 + - image: solid-color(21, 59, 0, 255, 8, 8) + bounds: 168 472 8 8 + - image: solid-color(22, 59, 0, 255, 8, 8) + bounds: 176 472 8 8 + - image: solid-color(23, 59, 0, 255, 8, 8) + bounds: 184 472 8 8 + - image: solid-color(24, 59, 0, 255, 8, 8) + bounds: 192 472 8 8 + - image: solid-color(25, 59, 0, 255, 8, 8) + bounds: 200 472 8 8 + - image: solid-color(26, 59, 0, 255, 8, 8) + bounds: 208 472 8 8 + - image: solid-color(27, 59, 0, 255, 8, 8) + bounds: 216 472 8 8 + - image: solid-color(28, 59, 0, 255, 8, 8) + bounds: 224 472 8 8 + - image: solid-color(29, 59, 0, 255, 8, 8) + bounds: 232 472 8 8 + - image: solid-color(30, 59, 0, 255, 8, 8) + bounds: 240 472 8 8 + - image: solid-color(31, 59, 0, 255, 8, 8) + bounds: 248 472 8 8 + - image: solid-color(32, 59, 0, 255, 8, 8) + bounds: 256 472 8 8 + - image: solid-color(33, 59, 0, 255, 8, 8) + bounds: 264 472 8 8 + - image: solid-color(34, 59, 0, 255, 8, 8) + bounds: 272 472 8 8 + - image: solid-color(35, 59, 0, 255, 8, 8) + bounds: 280 472 8 8 + - image: solid-color(36, 59, 0, 255, 8, 8) + bounds: 288 472 8 8 + - image: solid-color(37, 59, 0, 255, 8, 8) + bounds: 296 472 8 8 + - image: solid-color(38, 59, 0, 255, 8, 8) + bounds: 304 472 8 8 + - image: solid-color(39, 59, 0, 255, 8, 8) + bounds: 312 472 8 8 + - image: solid-color(40, 59, 0, 255, 8, 8) + bounds: 320 472 8 8 + - image: solid-color(41, 59, 0, 255, 8, 8) + bounds: 328 472 8 8 + - image: solid-color(42, 59, 0, 255, 8, 8) + bounds: 336 472 8 8 + - image: solid-color(43, 59, 0, 255, 8, 8) + bounds: 344 472 8 8 + - image: solid-color(44, 59, 0, 255, 8, 8) + bounds: 352 472 8 8 + - image: solid-color(45, 59, 0, 255, 8, 8) + bounds: 360 472 8 8 + - image: solid-color(46, 59, 0, 255, 8, 8) + bounds: 368 472 8 8 + - image: solid-color(47, 59, 0, 255, 8, 8) + bounds: 376 472 8 8 + - image: solid-color(48, 59, 0, 255, 8, 8) + bounds: 384 472 8 8 + - image: solid-color(49, 59, 0, 255, 8, 8) + bounds: 392 472 8 8 + - image: solid-color(50, 59, 0, 255, 8, 8) + bounds: 400 472 8 8 + - image: solid-color(51, 59, 0, 255, 8, 8) + bounds: 408 472 8 8 + - image: solid-color(52, 59, 0, 255, 8, 8) + bounds: 416 472 8 8 + - image: solid-color(53, 59, 0, 255, 8, 8) + bounds: 424 472 8 8 + - image: solid-color(54, 59, 0, 255, 8, 8) + bounds: 432 472 8 8 + - image: solid-color(55, 59, 0, 255, 8, 8) + bounds: 440 472 8 8 + - image: solid-color(56, 59, 0, 255, 8, 8) + bounds: 448 472 8 8 + - image: solid-color(57, 59, 0, 255, 8, 8) + bounds: 456 472 8 8 + - image: solid-color(58, 59, 0, 255, 8, 8) + bounds: 464 472 8 8 + - image: solid-color(59, 59, 0, 255, 8, 8) + bounds: 472 472 8 8 + - image: solid-color(60, 59, 0, 255, 8, 8) + bounds: 480 472 8 8 + - image: solid-color(61, 59, 0, 255, 8, 8) + bounds: 488 472 8 8 + - image: solid-color(62, 59, 0, 255, 8, 8) + bounds: 496 472 8 8 + - image: solid-color(63, 59, 0, 255, 8, 8) + bounds: 504 472 8 8 + - image: solid-color(64, 59, 0, 255, 8, 8) + bounds: 512 472 8 8 + - image: solid-color(65, 59, 0, 255, 8, 8) + bounds: 520 472 8 8 + - image: solid-color(66, 59, 0, 255, 8, 8) + bounds: 528 472 8 8 + - image: solid-color(67, 59, 0, 255, 8, 8) + bounds: 536 472 8 8 + - image: solid-color(68, 59, 0, 255, 8, 8) + bounds: 544 472 8 8 + - image: solid-color(69, 59, 0, 255, 8, 8) + bounds: 552 472 8 8 + - image: solid-color(70, 59, 0, 255, 8, 8) + bounds: 560 472 8 8 + - image: solid-color(71, 59, 0, 255, 8, 8) + bounds: 568 472 8 8 + - image: solid-color(72, 59, 0, 255, 8, 8) + bounds: 576 472 8 8 + - image: solid-color(73, 59, 0, 255, 8, 8) + bounds: 584 472 8 8 + - image: solid-color(74, 59, 0, 255, 8, 8) + bounds: 592 472 8 8 + - image: solid-color(75, 59, 0, 255, 8, 8) + bounds: 600 472 8 8 + - image: solid-color(76, 59, 0, 255, 8, 8) + bounds: 608 472 8 8 + - image: solid-color(77, 59, 0, 255, 8, 8) + bounds: 616 472 8 8 + - image: solid-color(78, 59, 0, 255, 8, 8) + bounds: 624 472 8 8 + - image: solid-color(79, 59, 0, 255, 8, 8) + bounds: 632 472 8 8 + - image: solid-color(80, 59, 0, 255, 8, 8) + bounds: 640 472 8 8 + - image: solid-color(81, 59, 0, 255, 8, 8) + bounds: 648 472 8 8 + - image: solid-color(82, 59, 0, 255, 8, 8) + bounds: 656 472 8 8 + - image: solid-color(83, 59, 0, 255, 8, 8) + bounds: 664 472 8 8 + - image: solid-color(84, 59, 0, 255, 8, 8) + bounds: 672 472 8 8 + - image: solid-color(85, 59, 0, 255, 8, 8) + bounds: 680 472 8 8 + - image: solid-color(86, 59, 0, 255, 8, 8) + bounds: 688 472 8 8 + - image: solid-color(87, 59, 0, 255, 8, 8) + bounds: 696 472 8 8 + - image: solid-color(88, 59, 0, 255, 8, 8) + bounds: 704 472 8 8 + - image: solid-color(89, 59, 0, 255, 8, 8) + bounds: 712 472 8 8 + - image: solid-color(90, 59, 0, 255, 8, 8) + bounds: 720 472 8 8 + - image: solid-color(91, 59, 0, 255, 8, 8) + bounds: 728 472 8 8 + - image: solid-color(92, 59, 0, 255, 8, 8) + bounds: 736 472 8 8 + - image: solid-color(93, 59, 0, 255, 8, 8) + bounds: 744 472 8 8 + - image: solid-color(94, 59, 0, 255, 8, 8) + bounds: 752 472 8 8 + - image: solid-color(95, 59, 0, 255, 8, 8) + bounds: 760 472 8 8 + - image: solid-color(96, 59, 0, 255, 8, 8) + bounds: 768 472 8 8 + - image: solid-color(97, 59, 0, 255, 8, 8) + bounds: 776 472 8 8 + - image: solid-color(98, 59, 0, 255, 8, 8) + bounds: 784 472 8 8 + - image: solid-color(99, 59, 0, 255, 8, 8) + bounds: 792 472 8 8 + - image: solid-color(100, 59, 0, 255, 8, 8) + bounds: 800 472 8 8 + - image: solid-color(101, 59, 0, 255, 8, 8) + bounds: 808 472 8 8 + - image: solid-color(102, 59, 0, 255, 8, 8) + bounds: 816 472 8 8 + - image: solid-color(103, 59, 0, 255, 8, 8) + bounds: 824 472 8 8 + - image: solid-color(104, 59, 0, 255, 8, 8) + bounds: 832 472 8 8 + - image: solid-color(105, 59, 0, 255, 8, 8) + bounds: 840 472 8 8 + - image: solid-color(106, 59, 0, 255, 8, 8) + bounds: 848 472 8 8 + - image: solid-color(107, 59, 0, 255, 8, 8) + bounds: 856 472 8 8 + - image: solid-color(108, 59, 0, 255, 8, 8) + bounds: 864 472 8 8 + - image: solid-color(109, 59, 0, 255, 8, 8) + bounds: 872 472 8 8 + - image: solid-color(110, 59, 0, 255, 8, 8) + bounds: 880 472 8 8 + - image: solid-color(111, 59, 0, 255, 8, 8) + bounds: 888 472 8 8 + - image: solid-color(112, 59, 0, 255, 8, 8) + bounds: 896 472 8 8 + - image: solid-color(113, 59, 0, 255, 8, 8) + bounds: 904 472 8 8 + - image: solid-color(114, 59, 0, 255, 8, 8) + bounds: 912 472 8 8 + - image: solid-color(115, 59, 0, 255, 8, 8) + bounds: 920 472 8 8 + - image: solid-color(116, 59, 0, 255, 8, 8) + bounds: 928 472 8 8 + - image: solid-color(117, 59, 0, 255, 8, 8) + bounds: 936 472 8 8 + - image: solid-color(118, 59, 0, 255, 8, 8) + bounds: 944 472 8 8 + - image: solid-color(119, 59, 0, 255, 8, 8) + bounds: 952 472 8 8 + - image: solid-color(120, 59, 0, 255, 8, 8) + bounds: 960 472 8 8 + - image: solid-color(121, 59, 0, 255, 8, 8) + bounds: 968 472 8 8 + - image: solid-color(122, 59, 0, 255, 8, 8) + bounds: 976 472 8 8 + - image: solid-color(123, 59, 0, 255, 8, 8) + bounds: 984 472 8 8 + - image: solid-color(124, 59, 0, 255, 8, 8) + bounds: 992 472 8 8 + - image: solid-color(125, 59, 0, 255, 8, 8) + bounds: 1000 472 8 8 + - image: solid-color(126, 59, 0, 255, 8, 8) + bounds: 1008 472 8 8 + - image: solid-color(127, 59, 0, 255, 8, 8) + bounds: 1016 472 8 8 + - image: solid-color(0, 60, 0, 255, 8, 8) + bounds: 0 480 8 8 + - image: solid-color(1, 60, 0, 255, 8, 8) + bounds: 8 480 8 8 + - image: solid-color(2, 60, 0, 255, 8, 8) + bounds: 16 480 8 8 + - image: solid-color(3, 60, 0, 255, 8, 8) + bounds: 24 480 8 8 + - image: solid-color(4, 60, 0, 255, 8, 8) + bounds: 32 480 8 8 + - image: solid-color(5, 60, 0, 255, 8, 8) + bounds: 40 480 8 8 + - image: solid-color(6, 60, 0, 255, 8, 8) + bounds: 48 480 8 8 + - image: solid-color(7, 60, 0, 255, 8, 8) + bounds: 56 480 8 8 + - image: solid-color(8, 60, 0, 255, 8, 8) + bounds: 64 480 8 8 + - image: solid-color(9, 60, 0, 255, 8, 8) + bounds: 72 480 8 8 + - image: solid-color(10, 60, 0, 255, 8, 8) + bounds: 80 480 8 8 + - image: solid-color(11, 60, 0, 255, 8, 8) + bounds: 88 480 8 8 + - image: solid-color(12, 60, 0, 255, 8, 8) + bounds: 96 480 8 8 + - image: solid-color(13, 60, 0, 255, 8, 8) + bounds: 104 480 8 8 + - image: solid-color(14, 60, 0, 255, 8, 8) + bounds: 112 480 8 8 + - image: solid-color(15, 60, 0, 255, 8, 8) + bounds: 120 480 8 8 + - image: solid-color(16, 60, 0, 255, 8, 8) + bounds: 128 480 8 8 + - image: solid-color(17, 60, 0, 255, 8, 8) + bounds: 136 480 8 8 + - image: solid-color(18, 60, 0, 255, 8, 8) + bounds: 144 480 8 8 + - image: solid-color(19, 60, 0, 255, 8, 8) + bounds: 152 480 8 8 + - image: solid-color(20, 60, 0, 255, 8, 8) + bounds: 160 480 8 8 + - image: solid-color(21, 60, 0, 255, 8, 8) + bounds: 168 480 8 8 + - image: solid-color(22, 60, 0, 255, 8, 8) + bounds: 176 480 8 8 + - image: solid-color(23, 60, 0, 255, 8, 8) + bounds: 184 480 8 8 + - image: solid-color(24, 60, 0, 255, 8, 8) + bounds: 192 480 8 8 + - image: solid-color(25, 60, 0, 255, 8, 8) + bounds: 200 480 8 8 + - image: solid-color(26, 60, 0, 255, 8, 8) + bounds: 208 480 8 8 + - image: solid-color(27, 60, 0, 255, 8, 8) + bounds: 216 480 8 8 + - image: solid-color(28, 60, 0, 255, 8, 8) + bounds: 224 480 8 8 + - image: solid-color(29, 60, 0, 255, 8, 8) + bounds: 232 480 8 8 + - image: solid-color(30, 60, 0, 255, 8, 8) + bounds: 240 480 8 8 + - image: solid-color(31, 60, 0, 255, 8, 8) + bounds: 248 480 8 8 + - image: solid-color(32, 60, 0, 255, 8, 8) + bounds: 256 480 8 8 + - image: solid-color(33, 60, 0, 255, 8, 8) + bounds: 264 480 8 8 + - image: solid-color(34, 60, 0, 255, 8, 8) + bounds: 272 480 8 8 + - image: solid-color(35, 60, 0, 255, 8, 8) + bounds: 280 480 8 8 + - image: solid-color(36, 60, 0, 255, 8, 8) + bounds: 288 480 8 8 + - image: solid-color(37, 60, 0, 255, 8, 8) + bounds: 296 480 8 8 + - image: solid-color(38, 60, 0, 255, 8, 8) + bounds: 304 480 8 8 + - image: solid-color(39, 60, 0, 255, 8, 8) + bounds: 312 480 8 8 + - image: solid-color(40, 60, 0, 255, 8, 8) + bounds: 320 480 8 8 + - image: solid-color(41, 60, 0, 255, 8, 8) + bounds: 328 480 8 8 + - image: solid-color(42, 60, 0, 255, 8, 8) + bounds: 336 480 8 8 + - image: solid-color(43, 60, 0, 255, 8, 8) + bounds: 344 480 8 8 + - image: solid-color(44, 60, 0, 255, 8, 8) + bounds: 352 480 8 8 + - image: solid-color(45, 60, 0, 255, 8, 8) + bounds: 360 480 8 8 + - image: solid-color(46, 60, 0, 255, 8, 8) + bounds: 368 480 8 8 + - image: solid-color(47, 60, 0, 255, 8, 8) + bounds: 376 480 8 8 + - image: solid-color(48, 60, 0, 255, 8, 8) + bounds: 384 480 8 8 + - image: solid-color(49, 60, 0, 255, 8, 8) + bounds: 392 480 8 8 + - image: solid-color(50, 60, 0, 255, 8, 8) + bounds: 400 480 8 8 + - image: solid-color(51, 60, 0, 255, 8, 8) + bounds: 408 480 8 8 + - image: solid-color(52, 60, 0, 255, 8, 8) + bounds: 416 480 8 8 + - image: solid-color(53, 60, 0, 255, 8, 8) + bounds: 424 480 8 8 + - image: solid-color(54, 60, 0, 255, 8, 8) + bounds: 432 480 8 8 + - image: solid-color(55, 60, 0, 255, 8, 8) + bounds: 440 480 8 8 + - image: solid-color(56, 60, 0, 255, 8, 8) + bounds: 448 480 8 8 + - image: solid-color(57, 60, 0, 255, 8, 8) + bounds: 456 480 8 8 + - image: solid-color(58, 60, 0, 255, 8, 8) + bounds: 464 480 8 8 + - image: solid-color(59, 60, 0, 255, 8, 8) + bounds: 472 480 8 8 + - image: solid-color(60, 60, 0, 255, 8, 8) + bounds: 480 480 8 8 + - image: solid-color(61, 60, 0, 255, 8, 8) + bounds: 488 480 8 8 + - image: solid-color(62, 60, 0, 255, 8, 8) + bounds: 496 480 8 8 + - image: solid-color(63, 60, 0, 255, 8, 8) + bounds: 504 480 8 8 + - image: solid-color(64, 60, 0, 255, 8, 8) + bounds: 512 480 8 8 + - image: solid-color(65, 60, 0, 255, 8, 8) + bounds: 520 480 8 8 + - image: solid-color(66, 60, 0, 255, 8, 8) + bounds: 528 480 8 8 + - image: solid-color(67, 60, 0, 255, 8, 8) + bounds: 536 480 8 8 + - image: solid-color(68, 60, 0, 255, 8, 8) + bounds: 544 480 8 8 + - image: solid-color(69, 60, 0, 255, 8, 8) + bounds: 552 480 8 8 + - image: solid-color(70, 60, 0, 255, 8, 8) + bounds: 560 480 8 8 + - image: solid-color(71, 60, 0, 255, 8, 8) + bounds: 568 480 8 8 + - image: solid-color(72, 60, 0, 255, 8, 8) + bounds: 576 480 8 8 + - image: solid-color(73, 60, 0, 255, 8, 8) + bounds: 584 480 8 8 + - image: solid-color(74, 60, 0, 255, 8, 8) + bounds: 592 480 8 8 + - image: solid-color(75, 60, 0, 255, 8, 8) + bounds: 600 480 8 8 + - image: solid-color(76, 60, 0, 255, 8, 8) + bounds: 608 480 8 8 + - image: solid-color(77, 60, 0, 255, 8, 8) + bounds: 616 480 8 8 + - image: solid-color(78, 60, 0, 255, 8, 8) + bounds: 624 480 8 8 + - image: solid-color(79, 60, 0, 255, 8, 8) + bounds: 632 480 8 8 + - image: solid-color(80, 60, 0, 255, 8, 8) + bounds: 640 480 8 8 + - image: solid-color(81, 60, 0, 255, 8, 8) + bounds: 648 480 8 8 + - image: solid-color(82, 60, 0, 255, 8, 8) + bounds: 656 480 8 8 + - image: solid-color(83, 60, 0, 255, 8, 8) + bounds: 664 480 8 8 + - image: solid-color(84, 60, 0, 255, 8, 8) + bounds: 672 480 8 8 + - image: solid-color(85, 60, 0, 255, 8, 8) + bounds: 680 480 8 8 + - image: solid-color(86, 60, 0, 255, 8, 8) + bounds: 688 480 8 8 + - image: solid-color(87, 60, 0, 255, 8, 8) + bounds: 696 480 8 8 + - image: solid-color(88, 60, 0, 255, 8, 8) + bounds: 704 480 8 8 + - image: solid-color(89, 60, 0, 255, 8, 8) + bounds: 712 480 8 8 + - image: solid-color(90, 60, 0, 255, 8, 8) + bounds: 720 480 8 8 + - image: solid-color(91, 60, 0, 255, 8, 8) + bounds: 728 480 8 8 + - image: solid-color(92, 60, 0, 255, 8, 8) + bounds: 736 480 8 8 + - image: solid-color(93, 60, 0, 255, 8, 8) + bounds: 744 480 8 8 + - image: solid-color(94, 60, 0, 255, 8, 8) + bounds: 752 480 8 8 + - image: solid-color(95, 60, 0, 255, 8, 8) + bounds: 760 480 8 8 + - image: solid-color(96, 60, 0, 255, 8, 8) + bounds: 768 480 8 8 + - image: solid-color(97, 60, 0, 255, 8, 8) + bounds: 776 480 8 8 + - image: solid-color(98, 60, 0, 255, 8, 8) + bounds: 784 480 8 8 + - image: solid-color(99, 60, 0, 255, 8, 8) + bounds: 792 480 8 8 + - image: solid-color(100, 60, 0, 255, 8, 8) + bounds: 800 480 8 8 + - image: solid-color(101, 60, 0, 255, 8, 8) + bounds: 808 480 8 8 + - image: solid-color(102, 60, 0, 255, 8, 8) + bounds: 816 480 8 8 + - image: solid-color(103, 60, 0, 255, 8, 8) + bounds: 824 480 8 8 + - image: solid-color(104, 60, 0, 255, 8, 8) + bounds: 832 480 8 8 + - image: solid-color(105, 60, 0, 255, 8, 8) + bounds: 840 480 8 8 + - image: solid-color(106, 60, 0, 255, 8, 8) + bounds: 848 480 8 8 + - image: solid-color(107, 60, 0, 255, 8, 8) + bounds: 856 480 8 8 + - image: solid-color(108, 60, 0, 255, 8, 8) + bounds: 864 480 8 8 + - image: solid-color(109, 60, 0, 255, 8, 8) + bounds: 872 480 8 8 + - image: solid-color(110, 60, 0, 255, 8, 8) + bounds: 880 480 8 8 + - image: solid-color(111, 60, 0, 255, 8, 8) + bounds: 888 480 8 8 + - image: solid-color(112, 60, 0, 255, 8, 8) + bounds: 896 480 8 8 + - image: solid-color(113, 60, 0, 255, 8, 8) + bounds: 904 480 8 8 + - image: solid-color(114, 60, 0, 255, 8, 8) + bounds: 912 480 8 8 + - image: solid-color(115, 60, 0, 255, 8, 8) + bounds: 920 480 8 8 + - image: solid-color(116, 60, 0, 255, 8, 8) + bounds: 928 480 8 8 + - image: solid-color(117, 60, 0, 255, 8, 8) + bounds: 936 480 8 8 + - image: solid-color(118, 60, 0, 255, 8, 8) + bounds: 944 480 8 8 + - image: solid-color(119, 60, 0, 255, 8, 8) + bounds: 952 480 8 8 + - image: solid-color(120, 60, 0, 255, 8, 8) + bounds: 960 480 8 8 + - image: solid-color(121, 60, 0, 255, 8, 8) + bounds: 968 480 8 8 + - image: solid-color(122, 60, 0, 255, 8, 8) + bounds: 976 480 8 8 + - image: solid-color(123, 60, 0, 255, 8, 8) + bounds: 984 480 8 8 + - image: solid-color(124, 60, 0, 255, 8, 8) + bounds: 992 480 8 8 + - image: solid-color(125, 60, 0, 255, 8, 8) + bounds: 1000 480 8 8 + - image: solid-color(126, 60, 0, 255, 8, 8) + bounds: 1008 480 8 8 + - image: solid-color(127, 60, 0, 255, 8, 8) + bounds: 1016 480 8 8 + - image: solid-color(0, 61, 0, 255, 8, 8) + bounds: 0 488 8 8 + - image: solid-color(1, 61, 0, 255, 8, 8) + bounds: 8 488 8 8 + - image: solid-color(2, 61, 0, 255, 8, 8) + bounds: 16 488 8 8 + - image: solid-color(3, 61, 0, 255, 8, 8) + bounds: 24 488 8 8 + - image: solid-color(4, 61, 0, 255, 8, 8) + bounds: 32 488 8 8 + - image: solid-color(5, 61, 0, 255, 8, 8) + bounds: 40 488 8 8 + - image: solid-color(6, 61, 0, 255, 8, 8) + bounds: 48 488 8 8 + - image: solid-color(7, 61, 0, 255, 8, 8) + bounds: 56 488 8 8 + - image: solid-color(8, 61, 0, 255, 8, 8) + bounds: 64 488 8 8 + - image: solid-color(9, 61, 0, 255, 8, 8) + bounds: 72 488 8 8 + - image: solid-color(10, 61, 0, 255, 8, 8) + bounds: 80 488 8 8 + - image: solid-color(11, 61, 0, 255, 8, 8) + bounds: 88 488 8 8 + - image: solid-color(12, 61, 0, 255, 8, 8) + bounds: 96 488 8 8 + - image: solid-color(13, 61, 0, 255, 8, 8) + bounds: 104 488 8 8 + - image: solid-color(14, 61, 0, 255, 8, 8) + bounds: 112 488 8 8 + - image: solid-color(15, 61, 0, 255, 8, 8) + bounds: 120 488 8 8 + - image: solid-color(16, 61, 0, 255, 8, 8) + bounds: 128 488 8 8 + - image: solid-color(17, 61, 0, 255, 8, 8) + bounds: 136 488 8 8 + - image: solid-color(18, 61, 0, 255, 8, 8) + bounds: 144 488 8 8 + - image: solid-color(19, 61, 0, 255, 8, 8) + bounds: 152 488 8 8 + - image: solid-color(20, 61, 0, 255, 8, 8) + bounds: 160 488 8 8 + - image: solid-color(21, 61, 0, 255, 8, 8) + bounds: 168 488 8 8 + - image: solid-color(22, 61, 0, 255, 8, 8) + bounds: 176 488 8 8 + - image: solid-color(23, 61, 0, 255, 8, 8) + bounds: 184 488 8 8 + - image: solid-color(24, 61, 0, 255, 8, 8) + bounds: 192 488 8 8 + - image: solid-color(25, 61, 0, 255, 8, 8) + bounds: 200 488 8 8 + - image: solid-color(26, 61, 0, 255, 8, 8) + bounds: 208 488 8 8 + - image: solid-color(27, 61, 0, 255, 8, 8) + bounds: 216 488 8 8 + - image: solid-color(28, 61, 0, 255, 8, 8) + bounds: 224 488 8 8 + - image: solid-color(29, 61, 0, 255, 8, 8) + bounds: 232 488 8 8 + - image: solid-color(30, 61, 0, 255, 8, 8) + bounds: 240 488 8 8 + - image: solid-color(31, 61, 0, 255, 8, 8) + bounds: 248 488 8 8 + - image: solid-color(32, 61, 0, 255, 8, 8) + bounds: 256 488 8 8 + - image: solid-color(33, 61, 0, 255, 8, 8) + bounds: 264 488 8 8 + - image: solid-color(34, 61, 0, 255, 8, 8) + bounds: 272 488 8 8 + - image: solid-color(35, 61, 0, 255, 8, 8) + bounds: 280 488 8 8 + - image: solid-color(36, 61, 0, 255, 8, 8) + bounds: 288 488 8 8 + - image: solid-color(37, 61, 0, 255, 8, 8) + bounds: 296 488 8 8 + - image: solid-color(38, 61, 0, 255, 8, 8) + bounds: 304 488 8 8 + - image: solid-color(39, 61, 0, 255, 8, 8) + bounds: 312 488 8 8 + - image: solid-color(40, 61, 0, 255, 8, 8) + bounds: 320 488 8 8 + - image: solid-color(41, 61, 0, 255, 8, 8) + bounds: 328 488 8 8 + - image: solid-color(42, 61, 0, 255, 8, 8) + bounds: 336 488 8 8 + - image: solid-color(43, 61, 0, 255, 8, 8) + bounds: 344 488 8 8 + - image: solid-color(44, 61, 0, 255, 8, 8) + bounds: 352 488 8 8 + - image: solid-color(45, 61, 0, 255, 8, 8) + bounds: 360 488 8 8 + - image: solid-color(46, 61, 0, 255, 8, 8) + bounds: 368 488 8 8 + - image: solid-color(47, 61, 0, 255, 8, 8) + bounds: 376 488 8 8 + - image: solid-color(48, 61, 0, 255, 8, 8) + bounds: 384 488 8 8 + - image: solid-color(49, 61, 0, 255, 8, 8) + bounds: 392 488 8 8 + - image: solid-color(50, 61, 0, 255, 8, 8) + bounds: 400 488 8 8 + - image: solid-color(51, 61, 0, 255, 8, 8) + bounds: 408 488 8 8 + - image: solid-color(52, 61, 0, 255, 8, 8) + bounds: 416 488 8 8 + - image: solid-color(53, 61, 0, 255, 8, 8) + bounds: 424 488 8 8 + - image: solid-color(54, 61, 0, 255, 8, 8) + bounds: 432 488 8 8 + - image: solid-color(55, 61, 0, 255, 8, 8) + bounds: 440 488 8 8 + - image: solid-color(56, 61, 0, 255, 8, 8) + bounds: 448 488 8 8 + - image: solid-color(57, 61, 0, 255, 8, 8) + bounds: 456 488 8 8 + - image: solid-color(58, 61, 0, 255, 8, 8) + bounds: 464 488 8 8 + - image: solid-color(59, 61, 0, 255, 8, 8) + bounds: 472 488 8 8 + - image: solid-color(60, 61, 0, 255, 8, 8) + bounds: 480 488 8 8 + - image: solid-color(61, 61, 0, 255, 8, 8) + bounds: 488 488 8 8 + - image: solid-color(62, 61, 0, 255, 8, 8) + bounds: 496 488 8 8 + - image: solid-color(63, 61, 0, 255, 8, 8) + bounds: 504 488 8 8 + - image: solid-color(64, 61, 0, 255, 8, 8) + bounds: 512 488 8 8 + - image: solid-color(65, 61, 0, 255, 8, 8) + bounds: 520 488 8 8 + - image: solid-color(66, 61, 0, 255, 8, 8) + bounds: 528 488 8 8 + - image: solid-color(67, 61, 0, 255, 8, 8) + bounds: 536 488 8 8 + - image: solid-color(68, 61, 0, 255, 8, 8) + bounds: 544 488 8 8 + - image: solid-color(69, 61, 0, 255, 8, 8) + bounds: 552 488 8 8 + - image: solid-color(70, 61, 0, 255, 8, 8) + bounds: 560 488 8 8 + - image: solid-color(71, 61, 0, 255, 8, 8) + bounds: 568 488 8 8 + - image: solid-color(72, 61, 0, 255, 8, 8) + bounds: 576 488 8 8 + - image: solid-color(73, 61, 0, 255, 8, 8) + bounds: 584 488 8 8 + - image: solid-color(74, 61, 0, 255, 8, 8) + bounds: 592 488 8 8 + - image: solid-color(75, 61, 0, 255, 8, 8) + bounds: 600 488 8 8 + - image: solid-color(76, 61, 0, 255, 8, 8) + bounds: 608 488 8 8 + - image: solid-color(77, 61, 0, 255, 8, 8) + bounds: 616 488 8 8 + - image: solid-color(78, 61, 0, 255, 8, 8) + bounds: 624 488 8 8 + - image: solid-color(79, 61, 0, 255, 8, 8) + bounds: 632 488 8 8 + - image: solid-color(80, 61, 0, 255, 8, 8) + bounds: 640 488 8 8 + - image: solid-color(81, 61, 0, 255, 8, 8) + bounds: 648 488 8 8 + - image: solid-color(82, 61, 0, 255, 8, 8) + bounds: 656 488 8 8 + - image: solid-color(83, 61, 0, 255, 8, 8) + bounds: 664 488 8 8 + - image: solid-color(84, 61, 0, 255, 8, 8) + bounds: 672 488 8 8 + - image: solid-color(85, 61, 0, 255, 8, 8) + bounds: 680 488 8 8 + - image: solid-color(86, 61, 0, 255, 8, 8) + bounds: 688 488 8 8 + - image: solid-color(87, 61, 0, 255, 8, 8) + bounds: 696 488 8 8 + - image: solid-color(88, 61, 0, 255, 8, 8) + bounds: 704 488 8 8 + - image: solid-color(89, 61, 0, 255, 8, 8) + bounds: 712 488 8 8 + - image: solid-color(90, 61, 0, 255, 8, 8) + bounds: 720 488 8 8 + - image: solid-color(91, 61, 0, 255, 8, 8) + bounds: 728 488 8 8 + - image: solid-color(92, 61, 0, 255, 8, 8) + bounds: 736 488 8 8 + - image: solid-color(93, 61, 0, 255, 8, 8) + bounds: 744 488 8 8 + - image: solid-color(94, 61, 0, 255, 8, 8) + bounds: 752 488 8 8 + - image: solid-color(95, 61, 0, 255, 8, 8) + bounds: 760 488 8 8 + - image: solid-color(96, 61, 0, 255, 8, 8) + bounds: 768 488 8 8 + - image: solid-color(97, 61, 0, 255, 8, 8) + bounds: 776 488 8 8 + - image: solid-color(98, 61, 0, 255, 8, 8) + bounds: 784 488 8 8 + - image: solid-color(99, 61, 0, 255, 8, 8) + bounds: 792 488 8 8 + - image: solid-color(100, 61, 0, 255, 8, 8) + bounds: 800 488 8 8 + - image: solid-color(101, 61, 0, 255, 8, 8) + bounds: 808 488 8 8 + - image: solid-color(102, 61, 0, 255, 8, 8) + bounds: 816 488 8 8 + - image: solid-color(103, 61, 0, 255, 8, 8) + bounds: 824 488 8 8 + - image: solid-color(104, 61, 0, 255, 8, 8) + bounds: 832 488 8 8 + - image: solid-color(105, 61, 0, 255, 8, 8) + bounds: 840 488 8 8 + - image: solid-color(106, 61, 0, 255, 8, 8) + bounds: 848 488 8 8 + - image: solid-color(107, 61, 0, 255, 8, 8) + bounds: 856 488 8 8 + - image: solid-color(108, 61, 0, 255, 8, 8) + bounds: 864 488 8 8 + - image: solid-color(109, 61, 0, 255, 8, 8) + bounds: 872 488 8 8 + - image: solid-color(110, 61, 0, 255, 8, 8) + bounds: 880 488 8 8 + - image: solid-color(111, 61, 0, 255, 8, 8) + bounds: 888 488 8 8 + - image: solid-color(112, 61, 0, 255, 8, 8) + bounds: 896 488 8 8 + - image: solid-color(113, 61, 0, 255, 8, 8) + bounds: 904 488 8 8 + - image: solid-color(114, 61, 0, 255, 8, 8) + bounds: 912 488 8 8 + - image: solid-color(115, 61, 0, 255, 8, 8) + bounds: 920 488 8 8 + - image: solid-color(116, 61, 0, 255, 8, 8) + bounds: 928 488 8 8 + - image: solid-color(117, 61, 0, 255, 8, 8) + bounds: 936 488 8 8 + - image: solid-color(118, 61, 0, 255, 8, 8) + bounds: 944 488 8 8 + - image: solid-color(119, 61, 0, 255, 8, 8) + bounds: 952 488 8 8 + - image: solid-color(120, 61, 0, 255, 8, 8) + bounds: 960 488 8 8 + - image: solid-color(121, 61, 0, 255, 8, 8) + bounds: 968 488 8 8 + - image: solid-color(122, 61, 0, 255, 8, 8) + bounds: 976 488 8 8 + - image: solid-color(123, 61, 0, 255, 8, 8) + bounds: 984 488 8 8 + - image: solid-color(124, 61, 0, 255, 8, 8) + bounds: 992 488 8 8 + - image: solid-color(125, 61, 0, 255, 8, 8) + bounds: 1000 488 8 8 + - image: solid-color(126, 61, 0, 255, 8, 8) + bounds: 1008 488 8 8 + - image: solid-color(127, 61, 0, 255, 8, 8) + bounds: 1016 488 8 8 + - image: solid-color(0, 62, 0, 255, 8, 8) + bounds: 0 496 8 8 + - image: solid-color(1, 62, 0, 255, 8, 8) + bounds: 8 496 8 8 + - image: solid-color(2, 62, 0, 255, 8, 8) + bounds: 16 496 8 8 + - image: solid-color(3, 62, 0, 255, 8, 8) + bounds: 24 496 8 8 + - image: solid-color(4, 62, 0, 255, 8, 8) + bounds: 32 496 8 8 + - image: solid-color(5, 62, 0, 255, 8, 8) + bounds: 40 496 8 8 + - image: solid-color(6, 62, 0, 255, 8, 8) + bounds: 48 496 8 8 + - image: solid-color(7, 62, 0, 255, 8, 8) + bounds: 56 496 8 8 + - image: solid-color(8, 62, 0, 255, 8, 8) + bounds: 64 496 8 8 + - image: solid-color(9, 62, 0, 255, 8, 8) + bounds: 72 496 8 8 + - image: solid-color(10, 62, 0, 255, 8, 8) + bounds: 80 496 8 8 + - image: solid-color(11, 62, 0, 255, 8, 8) + bounds: 88 496 8 8 + - image: solid-color(12, 62, 0, 255, 8, 8) + bounds: 96 496 8 8 + - image: solid-color(13, 62, 0, 255, 8, 8) + bounds: 104 496 8 8 + - image: solid-color(14, 62, 0, 255, 8, 8) + bounds: 112 496 8 8 + - image: solid-color(15, 62, 0, 255, 8, 8) + bounds: 120 496 8 8 + - image: solid-color(16, 62, 0, 255, 8, 8) + bounds: 128 496 8 8 + - image: solid-color(17, 62, 0, 255, 8, 8) + bounds: 136 496 8 8 + - image: solid-color(18, 62, 0, 255, 8, 8) + bounds: 144 496 8 8 + - image: solid-color(19, 62, 0, 255, 8, 8) + bounds: 152 496 8 8 + - image: solid-color(20, 62, 0, 255, 8, 8) + bounds: 160 496 8 8 + - image: solid-color(21, 62, 0, 255, 8, 8) + bounds: 168 496 8 8 + - image: solid-color(22, 62, 0, 255, 8, 8) + bounds: 176 496 8 8 + - image: solid-color(23, 62, 0, 255, 8, 8) + bounds: 184 496 8 8 + - image: solid-color(24, 62, 0, 255, 8, 8) + bounds: 192 496 8 8 + - image: solid-color(25, 62, 0, 255, 8, 8) + bounds: 200 496 8 8 + - image: solid-color(26, 62, 0, 255, 8, 8) + bounds: 208 496 8 8 + - image: solid-color(27, 62, 0, 255, 8, 8) + bounds: 216 496 8 8 + - image: solid-color(28, 62, 0, 255, 8, 8) + bounds: 224 496 8 8 + - image: solid-color(29, 62, 0, 255, 8, 8) + bounds: 232 496 8 8 + - image: solid-color(30, 62, 0, 255, 8, 8) + bounds: 240 496 8 8 + - image: solid-color(31, 62, 0, 255, 8, 8) + bounds: 248 496 8 8 + - image: solid-color(32, 62, 0, 255, 8, 8) + bounds: 256 496 8 8 + - image: solid-color(33, 62, 0, 255, 8, 8) + bounds: 264 496 8 8 + - image: solid-color(34, 62, 0, 255, 8, 8) + bounds: 272 496 8 8 + - image: solid-color(35, 62, 0, 255, 8, 8) + bounds: 280 496 8 8 + - image: solid-color(36, 62, 0, 255, 8, 8) + bounds: 288 496 8 8 + - image: solid-color(37, 62, 0, 255, 8, 8) + bounds: 296 496 8 8 + - image: solid-color(38, 62, 0, 255, 8, 8) + bounds: 304 496 8 8 + - image: solid-color(39, 62, 0, 255, 8, 8) + bounds: 312 496 8 8 + - image: solid-color(40, 62, 0, 255, 8, 8) + bounds: 320 496 8 8 + - image: solid-color(41, 62, 0, 255, 8, 8) + bounds: 328 496 8 8 + - image: solid-color(42, 62, 0, 255, 8, 8) + bounds: 336 496 8 8 + - image: solid-color(43, 62, 0, 255, 8, 8) + bounds: 344 496 8 8 + - image: solid-color(44, 62, 0, 255, 8, 8) + bounds: 352 496 8 8 + - image: solid-color(45, 62, 0, 255, 8, 8) + bounds: 360 496 8 8 + - image: solid-color(46, 62, 0, 255, 8, 8) + bounds: 368 496 8 8 + - image: solid-color(47, 62, 0, 255, 8, 8) + bounds: 376 496 8 8 + - image: solid-color(48, 62, 0, 255, 8, 8) + bounds: 384 496 8 8 + - image: solid-color(49, 62, 0, 255, 8, 8) + bounds: 392 496 8 8 + - image: solid-color(50, 62, 0, 255, 8, 8) + bounds: 400 496 8 8 + - image: solid-color(51, 62, 0, 255, 8, 8) + bounds: 408 496 8 8 + - image: solid-color(52, 62, 0, 255, 8, 8) + bounds: 416 496 8 8 + - image: solid-color(53, 62, 0, 255, 8, 8) + bounds: 424 496 8 8 + - image: solid-color(54, 62, 0, 255, 8, 8) + bounds: 432 496 8 8 + - image: solid-color(55, 62, 0, 255, 8, 8) + bounds: 440 496 8 8 + - image: solid-color(56, 62, 0, 255, 8, 8) + bounds: 448 496 8 8 + - image: solid-color(57, 62, 0, 255, 8, 8) + bounds: 456 496 8 8 + - image: solid-color(58, 62, 0, 255, 8, 8) + bounds: 464 496 8 8 + - image: solid-color(59, 62, 0, 255, 8, 8) + bounds: 472 496 8 8 + - image: solid-color(60, 62, 0, 255, 8, 8) + bounds: 480 496 8 8 + - image: solid-color(61, 62, 0, 255, 8, 8) + bounds: 488 496 8 8 + - image: solid-color(62, 62, 0, 255, 8, 8) + bounds: 496 496 8 8 + - image: solid-color(63, 62, 0, 255, 8, 8) + bounds: 504 496 8 8 + - image: solid-color(64, 62, 0, 255, 8, 8) + bounds: 512 496 8 8 + - image: solid-color(65, 62, 0, 255, 8, 8) + bounds: 520 496 8 8 + - image: solid-color(66, 62, 0, 255, 8, 8) + bounds: 528 496 8 8 + - image: solid-color(67, 62, 0, 255, 8, 8) + bounds: 536 496 8 8 + - image: solid-color(68, 62, 0, 255, 8, 8) + bounds: 544 496 8 8 + - image: solid-color(69, 62, 0, 255, 8, 8) + bounds: 552 496 8 8 + - image: solid-color(70, 62, 0, 255, 8, 8) + bounds: 560 496 8 8 + - image: solid-color(71, 62, 0, 255, 8, 8) + bounds: 568 496 8 8 + - image: solid-color(72, 62, 0, 255, 8, 8) + bounds: 576 496 8 8 + - image: solid-color(73, 62, 0, 255, 8, 8) + bounds: 584 496 8 8 + - image: solid-color(74, 62, 0, 255, 8, 8) + bounds: 592 496 8 8 + - image: solid-color(75, 62, 0, 255, 8, 8) + bounds: 600 496 8 8 + - image: solid-color(76, 62, 0, 255, 8, 8) + bounds: 608 496 8 8 + - image: solid-color(77, 62, 0, 255, 8, 8) + bounds: 616 496 8 8 + - image: solid-color(78, 62, 0, 255, 8, 8) + bounds: 624 496 8 8 + - image: solid-color(79, 62, 0, 255, 8, 8) + bounds: 632 496 8 8 + - image: solid-color(80, 62, 0, 255, 8, 8) + bounds: 640 496 8 8 + - image: solid-color(81, 62, 0, 255, 8, 8) + bounds: 648 496 8 8 + - image: solid-color(82, 62, 0, 255, 8, 8) + bounds: 656 496 8 8 + - image: solid-color(83, 62, 0, 255, 8, 8) + bounds: 664 496 8 8 + - image: solid-color(84, 62, 0, 255, 8, 8) + bounds: 672 496 8 8 + - image: solid-color(85, 62, 0, 255, 8, 8) + bounds: 680 496 8 8 + - image: solid-color(86, 62, 0, 255, 8, 8) + bounds: 688 496 8 8 + - image: solid-color(87, 62, 0, 255, 8, 8) + bounds: 696 496 8 8 + - image: solid-color(88, 62, 0, 255, 8, 8) + bounds: 704 496 8 8 + - image: solid-color(89, 62, 0, 255, 8, 8) + bounds: 712 496 8 8 + - image: solid-color(90, 62, 0, 255, 8, 8) + bounds: 720 496 8 8 + - image: solid-color(91, 62, 0, 255, 8, 8) + bounds: 728 496 8 8 + - image: solid-color(92, 62, 0, 255, 8, 8) + bounds: 736 496 8 8 + - image: solid-color(93, 62, 0, 255, 8, 8) + bounds: 744 496 8 8 + - image: solid-color(94, 62, 0, 255, 8, 8) + bounds: 752 496 8 8 + - image: solid-color(95, 62, 0, 255, 8, 8) + bounds: 760 496 8 8 + - image: solid-color(96, 62, 0, 255, 8, 8) + bounds: 768 496 8 8 + - image: solid-color(97, 62, 0, 255, 8, 8) + bounds: 776 496 8 8 + - image: solid-color(98, 62, 0, 255, 8, 8) + bounds: 784 496 8 8 + - image: solid-color(99, 62, 0, 255, 8, 8) + bounds: 792 496 8 8 + - image: solid-color(100, 62, 0, 255, 8, 8) + bounds: 800 496 8 8 + - image: solid-color(101, 62, 0, 255, 8, 8) + bounds: 808 496 8 8 + - image: solid-color(102, 62, 0, 255, 8, 8) + bounds: 816 496 8 8 + - image: solid-color(103, 62, 0, 255, 8, 8) + bounds: 824 496 8 8 + - image: solid-color(104, 62, 0, 255, 8, 8) + bounds: 832 496 8 8 + - image: solid-color(105, 62, 0, 255, 8, 8) + bounds: 840 496 8 8 + - image: solid-color(106, 62, 0, 255, 8, 8) + bounds: 848 496 8 8 + - image: solid-color(107, 62, 0, 255, 8, 8) + bounds: 856 496 8 8 + - image: solid-color(108, 62, 0, 255, 8, 8) + bounds: 864 496 8 8 + - image: solid-color(109, 62, 0, 255, 8, 8) + bounds: 872 496 8 8 + - image: solid-color(110, 62, 0, 255, 8, 8) + bounds: 880 496 8 8 + - image: solid-color(111, 62, 0, 255, 8, 8) + bounds: 888 496 8 8 + - image: solid-color(112, 62, 0, 255, 8, 8) + bounds: 896 496 8 8 + - image: solid-color(113, 62, 0, 255, 8, 8) + bounds: 904 496 8 8 + - image: solid-color(114, 62, 0, 255, 8, 8) + bounds: 912 496 8 8 + - image: solid-color(115, 62, 0, 255, 8, 8) + bounds: 920 496 8 8 + - image: solid-color(116, 62, 0, 255, 8, 8) + bounds: 928 496 8 8 + - image: solid-color(117, 62, 0, 255, 8, 8) + bounds: 936 496 8 8 + - image: solid-color(118, 62, 0, 255, 8, 8) + bounds: 944 496 8 8 + - image: solid-color(119, 62, 0, 255, 8, 8) + bounds: 952 496 8 8 + - image: solid-color(120, 62, 0, 255, 8, 8) + bounds: 960 496 8 8 + - image: solid-color(121, 62, 0, 255, 8, 8) + bounds: 968 496 8 8 + - image: solid-color(122, 62, 0, 255, 8, 8) + bounds: 976 496 8 8 + - image: solid-color(123, 62, 0, 255, 8, 8) + bounds: 984 496 8 8 + - image: solid-color(124, 62, 0, 255, 8, 8) + bounds: 992 496 8 8 + - image: solid-color(125, 62, 0, 255, 8, 8) + bounds: 1000 496 8 8 + - image: solid-color(126, 62, 0, 255, 8, 8) + bounds: 1008 496 8 8 + - image: solid-color(127, 62, 0, 255, 8, 8) + bounds: 1016 496 8 8 + - image: solid-color(0, 63, 0, 255, 8, 8) + bounds: 0 504 8 8 + - image: solid-color(1, 63, 0, 255, 8, 8) + bounds: 8 504 8 8 + - image: solid-color(2, 63, 0, 255, 8, 8) + bounds: 16 504 8 8 + - image: solid-color(3, 63, 0, 255, 8, 8) + bounds: 24 504 8 8 + - image: solid-color(4, 63, 0, 255, 8, 8) + bounds: 32 504 8 8 + - image: solid-color(5, 63, 0, 255, 8, 8) + bounds: 40 504 8 8 + - image: solid-color(6, 63, 0, 255, 8, 8) + bounds: 48 504 8 8 + - image: solid-color(7, 63, 0, 255, 8, 8) + bounds: 56 504 8 8 + - image: solid-color(8, 63, 0, 255, 8, 8) + bounds: 64 504 8 8 + - image: solid-color(9, 63, 0, 255, 8, 8) + bounds: 72 504 8 8 + - image: solid-color(10, 63, 0, 255, 8, 8) + bounds: 80 504 8 8 + - image: solid-color(11, 63, 0, 255, 8, 8) + bounds: 88 504 8 8 + - image: solid-color(12, 63, 0, 255, 8, 8) + bounds: 96 504 8 8 + - image: solid-color(13, 63, 0, 255, 8, 8) + bounds: 104 504 8 8 + - image: solid-color(14, 63, 0, 255, 8, 8) + bounds: 112 504 8 8 + - image: solid-color(15, 63, 0, 255, 8, 8) + bounds: 120 504 8 8 + - image: solid-color(16, 63, 0, 255, 8, 8) + bounds: 128 504 8 8 + - image: solid-color(17, 63, 0, 255, 8, 8) + bounds: 136 504 8 8 + - image: solid-color(18, 63, 0, 255, 8, 8) + bounds: 144 504 8 8 + - image: solid-color(19, 63, 0, 255, 8, 8) + bounds: 152 504 8 8 + - image: solid-color(20, 63, 0, 255, 8, 8) + bounds: 160 504 8 8 + - image: solid-color(21, 63, 0, 255, 8, 8) + bounds: 168 504 8 8 + - image: solid-color(22, 63, 0, 255, 8, 8) + bounds: 176 504 8 8 + - image: solid-color(23, 63, 0, 255, 8, 8) + bounds: 184 504 8 8 + - image: solid-color(24, 63, 0, 255, 8, 8) + bounds: 192 504 8 8 + - image: solid-color(25, 63, 0, 255, 8, 8) + bounds: 200 504 8 8 + - image: solid-color(26, 63, 0, 255, 8, 8) + bounds: 208 504 8 8 + - image: solid-color(27, 63, 0, 255, 8, 8) + bounds: 216 504 8 8 + - image: solid-color(28, 63, 0, 255, 8, 8) + bounds: 224 504 8 8 + - image: solid-color(29, 63, 0, 255, 8, 8) + bounds: 232 504 8 8 + - image: solid-color(30, 63, 0, 255, 8, 8) + bounds: 240 504 8 8 + - image: solid-color(31, 63, 0, 255, 8, 8) + bounds: 248 504 8 8 + - image: solid-color(32, 63, 0, 255, 8, 8) + bounds: 256 504 8 8 + - image: solid-color(33, 63, 0, 255, 8, 8) + bounds: 264 504 8 8 + - image: solid-color(34, 63, 0, 255, 8, 8) + bounds: 272 504 8 8 + - image: solid-color(35, 63, 0, 255, 8, 8) + bounds: 280 504 8 8 + - image: solid-color(36, 63, 0, 255, 8, 8) + bounds: 288 504 8 8 + - image: solid-color(37, 63, 0, 255, 8, 8) + bounds: 296 504 8 8 + - image: solid-color(38, 63, 0, 255, 8, 8) + bounds: 304 504 8 8 + - image: solid-color(39, 63, 0, 255, 8, 8) + bounds: 312 504 8 8 + - image: solid-color(40, 63, 0, 255, 8, 8) + bounds: 320 504 8 8 + - image: solid-color(41, 63, 0, 255, 8, 8) + bounds: 328 504 8 8 + - image: solid-color(42, 63, 0, 255, 8, 8) + bounds: 336 504 8 8 + - image: solid-color(43, 63, 0, 255, 8, 8) + bounds: 344 504 8 8 + - image: solid-color(44, 63, 0, 255, 8, 8) + bounds: 352 504 8 8 + - image: solid-color(45, 63, 0, 255, 8, 8) + bounds: 360 504 8 8 + - image: solid-color(46, 63, 0, 255, 8, 8) + bounds: 368 504 8 8 + - image: solid-color(47, 63, 0, 255, 8, 8) + bounds: 376 504 8 8 + - image: solid-color(48, 63, 0, 255, 8, 8) + bounds: 384 504 8 8 + - image: solid-color(49, 63, 0, 255, 8, 8) + bounds: 392 504 8 8 + - image: solid-color(50, 63, 0, 255, 8, 8) + bounds: 400 504 8 8 + - image: solid-color(51, 63, 0, 255, 8, 8) + bounds: 408 504 8 8 + - image: solid-color(52, 63, 0, 255, 8, 8) + bounds: 416 504 8 8 + - image: solid-color(53, 63, 0, 255, 8, 8) + bounds: 424 504 8 8 + - image: solid-color(54, 63, 0, 255, 8, 8) + bounds: 432 504 8 8 + - image: solid-color(55, 63, 0, 255, 8, 8) + bounds: 440 504 8 8 + - image: solid-color(56, 63, 0, 255, 8, 8) + bounds: 448 504 8 8 + - image: solid-color(57, 63, 0, 255, 8, 8) + bounds: 456 504 8 8 + - image: solid-color(58, 63, 0, 255, 8, 8) + bounds: 464 504 8 8 + - image: solid-color(59, 63, 0, 255, 8, 8) + bounds: 472 504 8 8 + - image: solid-color(60, 63, 0, 255, 8, 8) + bounds: 480 504 8 8 + - image: solid-color(61, 63, 0, 255, 8, 8) + bounds: 488 504 8 8 + - image: solid-color(62, 63, 0, 255, 8, 8) + bounds: 496 504 8 8 + - image: solid-color(63, 63, 0, 255, 8, 8) + bounds: 504 504 8 8 + - image: solid-color(64, 63, 0, 255, 8, 8) + bounds: 512 504 8 8 + - image: solid-color(65, 63, 0, 255, 8, 8) + bounds: 520 504 8 8 + - image: solid-color(66, 63, 0, 255, 8, 8) + bounds: 528 504 8 8 + - image: solid-color(67, 63, 0, 255, 8, 8) + bounds: 536 504 8 8 + - image: solid-color(68, 63, 0, 255, 8, 8) + bounds: 544 504 8 8 + - image: solid-color(69, 63, 0, 255, 8, 8) + bounds: 552 504 8 8 + - image: solid-color(70, 63, 0, 255, 8, 8) + bounds: 560 504 8 8 + - image: solid-color(71, 63, 0, 255, 8, 8) + bounds: 568 504 8 8 + - image: solid-color(72, 63, 0, 255, 8, 8) + bounds: 576 504 8 8 + - image: solid-color(73, 63, 0, 255, 8, 8) + bounds: 584 504 8 8 + - image: solid-color(74, 63, 0, 255, 8, 8) + bounds: 592 504 8 8 + - image: solid-color(75, 63, 0, 255, 8, 8) + bounds: 600 504 8 8 + - image: solid-color(76, 63, 0, 255, 8, 8) + bounds: 608 504 8 8 + - image: solid-color(77, 63, 0, 255, 8, 8) + bounds: 616 504 8 8 + - image: solid-color(78, 63, 0, 255, 8, 8) + bounds: 624 504 8 8 + - image: solid-color(79, 63, 0, 255, 8, 8) + bounds: 632 504 8 8 + - image: solid-color(80, 63, 0, 255, 8, 8) + bounds: 640 504 8 8 + - image: solid-color(81, 63, 0, 255, 8, 8) + bounds: 648 504 8 8 + - image: solid-color(82, 63, 0, 255, 8, 8) + bounds: 656 504 8 8 + - image: solid-color(83, 63, 0, 255, 8, 8) + bounds: 664 504 8 8 + - image: solid-color(84, 63, 0, 255, 8, 8) + bounds: 672 504 8 8 + - image: solid-color(85, 63, 0, 255, 8, 8) + bounds: 680 504 8 8 + - image: solid-color(86, 63, 0, 255, 8, 8) + bounds: 688 504 8 8 + - image: solid-color(87, 63, 0, 255, 8, 8) + bounds: 696 504 8 8 + - image: solid-color(88, 63, 0, 255, 8, 8) + bounds: 704 504 8 8 + - image: solid-color(89, 63, 0, 255, 8, 8) + bounds: 712 504 8 8 + - image: solid-color(90, 63, 0, 255, 8, 8) + bounds: 720 504 8 8 + - image: solid-color(91, 63, 0, 255, 8, 8) + bounds: 728 504 8 8 + - image: solid-color(92, 63, 0, 255, 8, 8) + bounds: 736 504 8 8 + - image: solid-color(93, 63, 0, 255, 8, 8) + bounds: 744 504 8 8 + - image: solid-color(94, 63, 0, 255, 8, 8) + bounds: 752 504 8 8 + - image: solid-color(95, 63, 0, 255, 8, 8) + bounds: 760 504 8 8 + - image: solid-color(96, 63, 0, 255, 8, 8) + bounds: 768 504 8 8 + - image: solid-color(97, 63, 0, 255, 8, 8) + bounds: 776 504 8 8 + - image: solid-color(98, 63, 0, 255, 8, 8) + bounds: 784 504 8 8 + - image: solid-color(99, 63, 0, 255, 8, 8) + bounds: 792 504 8 8 + - image: solid-color(100, 63, 0, 255, 8, 8) + bounds: 800 504 8 8 + - image: solid-color(101, 63, 0, 255, 8, 8) + bounds: 808 504 8 8 + - image: solid-color(102, 63, 0, 255, 8, 8) + bounds: 816 504 8 8 + - image: solid-color(103, 63, 0, 255, 8, 8) + bounds: 824 504 8 8 + - image: solid-color(104, 63, 0, 255, 8, 8) + bounds: 832 504 8 8 + - image: solid-color(105, 63, 0, 255, 8, 8) + bounds: 840 504 8 8 + - image: solid-color(106, 63, 0, 255, 8, 8) + bounds: 848 504 8 8 + - image: solid-color(107, 63, 0, 255, 8, 8) + bounds: 856 504 8 8 + - image: solid-color(108, 63, 0, 255, 8, 8) + bounds: 864 504 8 8 + - image: solid-color(109, 63, 0, 255, 8, 8) + bounds: 872 504 8 8 + - image: solid-color(110, 63, 0, 255, 8, 8) + bounds: 880 504 8 8 + - image: solid-color(111, 63, 0, 255, 8, 8) + bounds: 888 504 8 8 + - image: solid-color(112, 63, 0, 255, 8, 8) + bounds: 896 504 8 8 + - image: solid-color(113, 63, 0, 255, 8, 8) + bounds: 904 504 8 8 + - image: solid-color(114, 63, 0, 255, 8, 8) + bounds: 912 504 8 8 + - image: solid-color(115, 63, 0, 255, 8, 8) + bounds: 920 504 8 8 + - image: solid-color(116, 63, 0, 255, 8, 8) + bounds: 928 504 8 8 + - image: solid-color(117, 63, 0, 255, 8, 8) + bounds: 936 504 8 8 + - image: solid-color(118, 63, 0, 255, 8, 8) + bounds: 944 504 8 8 + - image: solid-color(119, 63, 0, 255, 8, 8) + bounds: 952 504 8 8 + - image: solid-color(120, 63, 0, 255, 8, 8) + bounds: 960 504 8 8 + - image: solid-color(121, 63, 0, 255, 8, 8) + bounds: 968 504 8 8 + - image: solid-color(122, 63, 0, 255, 8, 8) + bounds: 976 504 8 8 + - image: solid-color(123, 63, 0, 255, 8, 8) + bounds: 984 504 8 8 + - image: solid-color(124, 63, 0, 255, 8, 8) + bounds: 992 504 8 8 + - image: solid-color(125, 63, 0, 255, 8, 8) + bounds: 1000 504 8 8 + - image: solid-color(126, 63, 0, 255, 8, 8) + bounds: 1008 504 8 8 + - image: solid-color(127, 63, 0, 255, 8, 8) + bounds: 1016 504 8 8 diff --git a/third_party/webrender/wrench/benchmarks/overlapping-text-shadows.yaml b/third_party/webrender/wrench/benchmarks/overlapping-text-shadows.yaml new file mode 100644 index 00000000000..d32d4aa45c3 --- /dev/null +++ b/third_party/webrender/wrench/benchmarks/overlapping-text-shadows.yaml @@ -0,0 +1,807 @@ +root: + items: + - + type: "shadow" + offset: [0, 0] + color: red + - + type: "shadow" + offset: [1, 1] + color: red + - + type: "shadow" + offset: [2, 2] + color: red + - + type: "shadow" + offset: [3, 3] + color: red + - + type: "shadow" + offset: [4, 4] + color: red + - + type: "shadow" + offset: [5, 5] + color: red + - + type: "shadow" + offset: [6, 6] + color: red + - + type: "shadow" + offset: [7, 7] + color: red + - + type: "shadow" + offset: [8, 8] + color: red + - + type: "shadow" + offset: [9, 9] + color: red + - + type: "shadow" + offset: [10, 10] + color: red + - + type: "shadow" + offset: [11, 11] + color: red + - + type: "shadow" + offset: [12, 12] + color: red + - + type: "shadow" + offset: [13, 13] + color: red + - + type: "shadow" + offset: [14, 14] + color: red + - + type: "shadow" + offset: [15, 15] + color: red + - + type: "shadow" + offset: [16, 16] + color: red + - + type: "shadow" + offset: [17, 17] + color: red + - + type: "shadow" + offset: [18, 18] + color: red + - + type: "shadow" + offset: [19, 19] + color: red + - + type: "shadow" + offset: [20, 20] + color: red + - + type: "shadow" + offset: [21, 21] + color: red + - + type: "shadow" + offset: [22, 22] + color: red + - + type: "shadow" + offset: [23, 23] + color: red + - + type: "shadow" + offset: [24, 24] + color: red + - + type: "shadow" + offset: [25, 25] + color: red + - + type: "shadow" + offset: [26, 26] + color: red + - + type: "shadow" + offset: [27, 27] + color: red + - + type: "shadow" + offset: [28, 28] + color: red + - + type: "shadow" + offset: [29, 29] + color: red + - + type: "shadow" + offset: [30, 30] + color: red + - + type: "shadow" + offset: [31, 31] + color: red + - + type: "shadow" + offset: [32, 32] + color: red + - + type: "shadow" + offset: [33, 33] + color: red + - + type: "shadow" + offset: [34, 34] + color: red + - + type: "shadow" + offset: [35, 35] + color: red + - + type: "shadow" + offset: [36, 36] + color: red + - + type: "shadow" + offset: [37, 37] + color: red + - + type: "shadow" + offset: [38, 38] + color: red + - + type: "shadow" + offset: [39, 39] + color: red + - + type: "shadow" + offset: [40, 40] + color: red + - + type: "shadow" + offset: [41, 41] + color: red + - + type: "shadow" + offset: [42, 42] + color: red + - + type: "shadow" + offset: [43, 43] + color: red + - + type: "shadow" + offset: [44, 44] + color: red + - + type: "shadow" + offset: [45, 45] + color: red + - + type: "shadow" + offset: [46, 46] + color: red + - + type: "shadow" + offset: [47, 47] + color: red + - + type: "shadow" + offset: [48, 48] + color: red + - + type: "shadow" + offset: [49, 49] + color: red + - + type: "shadow" + offset: [50, 50] + color: red + - + type: "shadow" + offset: [51, 51] + color: red + - + type: "shadow" + offset: [52, 52] + color: red + - + type: "shadow" + offset: [53, 53] + color: red + - + type: "shadow" + offset: [54, 54] + color: red + - + type: "shadow" + offset: [55, 55] + color: red + - + type: "shadow" + offset: [56, 56] + color: red + - + type: "shadow" + offset: [57, 57] + color: red + - + type: "shadow" + offset: [58, 58] + color: red + - + type: "shadow" + offset: [59, 59] + color: red + - + type: "shadow" + offset: [60, 60] + color: red + - + type: "shadow" + offset: [61, 61] + color: red + - + type: "shadow" + offset: [62, 62] + color: red + - + type: "shadow" + offset: [63, 63] + color: red + - + type: "shadow" + offset: [64, 64] + color: red + - + type: "shadow" + offset: [65, 65] + color: red + - + type: "shadow" + offset: [66, 66] + color: red + - + type: "shadow" + offset: [67, 67] + color: red + - + type: "shadow" + offset: [68, 68] + color: red + - + type: "shadow" + offset: [69, 69] + color: red + - + type: "shadow" + offset: [70, 70] + color: red + - + type: "shadow" + offset: [71, 71] + color: red + - + type: "shadow" + offset: [72, 72] + color: red + - + type: "shadow" + offset: [73, 73] + color: red + - + type: "shadow" + offset: [74, 74] + color: red + - + type: "shadow" + offset: [75, 75] + color: red + - + type: "shadow" + offset: [76, 76] + color: red + - + type: "shadow" + offset: [77, 77] + color: red + - + type: "shadow" + offset: [78, 78] + color: red + - + type: "shadow" + offset: [79, 79] + color: red + - + type: "shadow" + offset: [80, 80] + color: red + - + type: "shadow" + offset: [81, 81] + color: red + - + type: "shadow" + offset: [82, 82] + color: red + - + type: "shadow" + offset: [83, 83] + color: red + - + type: "shadow" + offset: [84, 84] + color: red + - + type: "shadow" + offset: [85, 85] + color: red + - + type: "shadow" + offset: [86, 86] + color: red + - + type: "shadow" + offset: [87, 87] + color: red + - + type: "shadow" + offset: [88, 88] + color: red + - + type: "shadow" + offset: [89, 89] + color: red + - + type: "shadow" + offset: [90, 90] + color: red + - + type: "shadow" + offset: [91, 91] + color: red + - + type: "shadow" + offset: [92, 92] + color: red + - + type: "shadow" + offset: [93, 93] + color: red + - + type: "shadow" + offset: [94, 94] + color: red + - + type: "shadow" + offset: [95, 95] + color: red + - + type: "shadow" + offset: [96, 96] + color: red + - + type: "shadow" + offset: [97, 97] + color: red + - + type: "shadow" + offset: [98, 98] + color: red + - + type: "shadow" + offset: [99, 99] + color: red + - + type: "shadow" + offset: [100, 100] + color: red + - + type: "shadow" + offset: [101, 101] + color: red + - + type: "shadow" + offset: [102, 102] + color: red + - + type: "shadow" + offset: [103, 103] + color: red + - + type: "shadow" + offset: [104, 104] + color: red + - + type: "shadow" + offset: [105, 105] + color: red + - + type: "shadow" + offset: [106, 106] + color: red + - + type: "shadow" + offset: [107, 107] + color: red + - + type: "shadow" + offset: [108, 108] + color: red + - + type: "shadow" + offset: [109, 109] + color: red + - + type: "shadow" + offset: [110, 110] + color: red + - + type: "shadow" + offset: [111, 111] + color: red + - + type: "shadow" + offset: [112, 112] + color: red + - + type: "shadow" + offset: [113, 113] + color: red + - + type: "shadow" + offset: [114, 114] + color: red + - + type: "shadow" + offset: [115, 115] + color: red + - + type: "shadow" + offset: [116, 116] + color: red + - + type: "shadow" + offset: [117, 117] + color: red + - + type: "shadow" + offset: [118, 118] + color: red + - + type: "shadow" + offset: [119, 119] + color: red + - + type: "shadow" + offset: [120, 120] + color: red + - + type: "shadow" + offset: [121, 121] + color: red + - + type: "shadow" + offset: [122, 122] + color: red + - + type: "shadow" + offset: [123, 123] + color: red + - + type: "shadow" + offset: [124, 124] + color: red + - + type: "shadow" + offset: [125, 125] + color: red + - + type: "shadow" + offset: [126, 126] + color: red + - + type: "shadow" + offset: [127, 127] + color: red + - + type: "shadow" + offset: [128, 128] + color: red + - + type: "shadow" + offset: [129, 129] + color: red + - + type: "shadow" + offset: [130, 130] + color: red + - + type: "shadow" + offset: [131, 131] + color: red + - + type: "shadow" + offset: [132, 132] + color: red + - + type: "shadow" + offset: [133, 133] + color: red + - + type: "shadow" + offset: [134, 134] + color: red + - + type: "shadow" + offset: [135, 135] + color: red + - + type: "shadow" + offset: [136, 136] + color: red + - + type: "shadow" + offset: [137, 137] + color: red + - + type: "shadow" + offset: [138, 138] + color: red + - + type: "shadow" + offset: [139, 139] + color: red + - + type: "shadow" + offset: [140, 140] + color: red + - + type: "shadow" + offset: [141, 141] + color: red + - + type: "shadow" + offset: [142, 142] + color: red + - + type: "shadow" + offset: [143, 143] + color: red + - + type: "shadow" + offset: [144, 144] + color: red + - + type: "shadow" + offset: [145, 145] + color: red + - + type: "shadow" + offset: [146, 146] + color: red + - + type: "shadow" + offset: [147, 147] + color: red + - + type: "shadow" + offset: [148, 148] + color: red + - + type: "shadow" + offset: [149, 149] + color: red + - + type: "shadow" + offset: [150, 150] + color: red + - + type: "shadow" + offset: [151, 151] + color: red + - + type: "shadow" + offset: [152, 152] + color: red + - + type: "shadow" + offset: [153, 153] + color: red + - + type: "shadow" + offset: [154, 154] + color: red + - + type: "shadow" + offset: [155, 155] + color: red + - + type: "shadow" + offset: [156, 156] + color: red + - + type: "shadow" + offset: [157, 157] + color: red + - + type: "shadow" + offset: [158, 158] + color: red + - + type: "shadow" + offset: [159, 159] + color: red + - + type: "shadow" + offset: [160, 160] + color: red + - + type: "shadow" + offset: [161, 161] + color: red + - + type: "shadow" + offset: [162, 162] + color: red + - + type: "shadow" + offset: [163, 163] + color: red + - + type: "shadow" + offset: [164, 164] + color: red + - + type: "shadow" + offset: [165, 165] + color: red + - + type: "shadow" + offset: [166, 166] + color: red + - + type: "shadow" + offset: [167, 167] + color: red + - + type: "shadow" + offset: [168, 168] + color: red + - + type: "shadow" + offset: [169, 169] + color: red + - + type: "shadow" + offset: [170, 170] + color: red + - + type: "shadow" + offset: [171, 171] + color: red + - + type: "shadow" + offset: [172, 172] + color: red + - + type: "shadow" + offset: [173, 173] + color: red + - + type: "shadow" + offset: [174, 174] + color: red + - + type: "shadow" + offset: [175, 175] + color: red + - + type: "shadow" + offset: [176, 176] + color: red + - + type: "shadow" + offset: [177, 177] + color: red + - + type: "shadow" + offset: [178, 178] + color: red + - + type: "shadow" + offset: [179, 179] + color: red + - + type: "shadow" + offset: [180, 180] + color: red + - + type: "shadow" + offset: [181, 181] + color: red + - + type: "shadow" + offset: [182, 182] + color: red + - + type: "shadow" + offset: [183, 183] + color: red + - + type: "shadow" + offset: [184, 184] + color: red + - + type: "shadow" + offset: [185, 185] + color: red + - + type: "shadow" + offset: [186, 186] + color: red + - + type: "shadow" + offset: [187, 187] + color: red + - + type: "shadow" + offset: [188, 188] + color: red + - + type: "shadow" + offset: [189, 189] + color: red + - + type: "shadow" + offset: [190, 190] + color: red + - + type: "shadow" + offset: [191, 191] + color: red + - + type: "shadow" + offset: [192, 192] + color: red + - + type: "shadow" + offset: [193, 193] + color: red + - + type: "shadow" + offset: [194, 194] + color: red + - + type: "shadow" + offset: [195, 195] + color: red + - + type: "shadow" + offset: [196, 196] + color: red + - + type: "shadow" + offset: [197, 197] + color: red + - + type: "shadow" + offset: [198, 198] + color: red + - + type: "shadow" + offset: [199, 199] + color: red + - text: "Much overdraw many pixels" + origin: 20 70 + size: 60 + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/benchmarks/radial-gradient.yaml b/third_party/webrender/wrench/benchmarks/radial-gradient.yaml new file mode 100644 index 00000000000..e71d3013718 --- /dev/null +++ b/third_party/webrender/wrench/benchmarks/radial-gradient.yaml @@ -0,0 +1,52 @@ +--- +root: + items: + - type: radial-gradient + bounds: [ 0, 0, 1980, 1080] + center: [ 990, 540 ] + start-radius: 5 + end-radius: 8000 + stops: [ 0.0, black, 1.0, white ] + repeat: false + - type: radial-gradient + bounds: [ 0, 0, 1980, 1080] + center: [ 990, 540 ] + start-radius: 5 + end-radius: 8000 + stops: [ 0.0, black, 1.0, white ] + repeat: false + - type: radial-gradient + bounds: [ 0, 0, 1980, 1080] + center: [ 990, 540 ] + start-radius: 5 + end-radius: 8000 + stops: [ 0.0, black, 1.0, white ] + repeat: false + - type: radial-gradient + bounds: [ 0, 0, 1980, 1080] + center: [ 990, 540 ] + start-radius: 5 + end-radius: 8000 + stops: [ 0.0, black, 1.0, white ] + repeat: false + - type: radial-gradient + bounds: [ 0, 0, 1980, 1080] + center: [ 990, 540 ] + start-radius: 5 + end-radius: 8000 + stops: [ 0.0, black, 1.0, white ] + repeat: false + - type: radial-gradient + bounds: [ 0, 0, 1980, 1080] + center: [ 990, 540 ] + start-radius: 5 + end-radius: 8000 + stops: [ 0.0, black, 1.0, white ] + repeat: false + - type: radial-gradient + bounds: [ 0, 0, 1980, 1080] + center: [ 990, 540 ] + start-radius: 5 + end-radius: 8000 + stops: [ 0.0, black, 1.0, white ] + repeat: false diff --git a/third_party/webrender/wrench/benchmarks/simple-batching.yaml b/third_party/webrender/wrench/benchmarks/simple-batching.yaml new file mode 100644 index 00000000000..4f2cf019b21 --- /dev/null +++ b/third_party/webrender/wrench/benchmarks/simple-batching.yaml @@ -0,0 +1,45 @@ +--- +root: + items: + - type: rect + bounds: [0, 0, 512, 512] + color: green + - type: rect + bounds: [0, 0, 512, 512] + color: green + - type: rect + bounds: [0, 0, 512, 512] + color: green + - type: rect + bounds: [0, 0, 512, 512] + color: green + - type: rect + bounds: [0, 0, 512, 512] + color: green + - type: rect + bounds: [0, 0, 512, 512] + color: green + - type: rect + bounds: [0, 0, 512, 512] + color: green + - type: rect + bounds: [0, 0, 512, 512] + color: green + - type: rect + bounds: [0, 0, 512, 512] + color: green + - type: rect + bounds: [0, 0, 512, 512] + color: green + - type: rect + bounds: [0, 0, 512, 512] + color: green + - type: rect + bounds: [0, 0, 512, 512] + color: green + - type: rect + bounds: [0, 0, 512, 512] + color: green + - type: rect + bounds: [0, 0, 512, 512] + color: green diff --git a/third_party/webrender/wrench/benchmarks/text-rendering.yaml b/third_party/webrender/wrench/benchmarks/text-rendering.yaml new file mode 100644 index 00000000000..6b7ac3fb3ac --- /dev/null +++ b/third_party/webrender/wrench/benchmarks/text-rendering.yaml @@ -0,0 +1,260 @@ +root: + items: + - text: "Cats making all the muffins knock over christmas tree" + origin: 20 30 + size: 20 + - text: "Cat ipsum dolor sit amet, poop in the plant pot and demand to be let outside at once, and expect owner to wait for me as i think about it." + origin: 20 50 + size: 9 + - text: "Slap owner's face at 5am until human fills food dish licks your face." + origin: 20 80 + size: 18 + - text: "Play riveting piece on synthesizer keyboard cat snacks, yet meow go back to sleep owner brings food and water tries to" + origin: 20 100 + size: 10 + - text: "pet on head, so scratch get sprayed by water because bad cat nap all day, kitty scratches couch bad kitty." + origin: 20 112 + size: 10 + - text: "Touch water with paw then recoil in horror climb a tree, wait for a fireman jump to fireman then" + origin: 20 130 + size: 12 + - text: "scratch his face and mark territory when in doubt, wash." + origin: 20 150 + size: 12 + - text: "Friends are not food warm up laptop with butt lick butt fart rainbows until owner yells pee in litter box hiss at cats if it fits," + origin: 20 170 + size: 10 + - text: "i sits swat at dog, or chase the pig around the house yet thug cat but hiss and stare at nothing then run suddenly away." + origin: 20 180 + size: 10 + - text: "Howl uncontrollably for no reason pushes butt to face eat a plant, kill a hand for cough hairball on conveniently placed pants." + origin: 20 200 + size: 10 + - text: "Wake up human for food at 4am. Decide to want nothing to do with my owner today pelt around the house and up and down stairs chasing phantoms." + origin: 20 220 + size: 8 + - text: "Poop on grasses." + origin: 20 240 + size: 16 + - text: "Hunt by meowing loudly at 5am next to human slave food dispenser bleghbleghvomit my" + origin: 20 260 + size: 10 + - text: "furball really tie the room together and throwup on your pillow, so scamper." + origin: 20 270 + size: 10 + - text: "Spread kitty litter all over house." + origin: 20 290 + size: 18 + - text: "Friends are not food curl into a furry donut or going to catch the red" + origin: 20 320 + size: 16 + - text: "dot today going to catch the red dot today or destroy the blinds or purr." + origin: 20 340 + size: 16 + + - text: "Cats making all the muffins knock over christmas tree" + origin: 850 30 + size: 20 + color: red + - text: "Cat ipsum dolor sit amet, poop in the plant pot and demand to be let outside at once, and expect owner to wait for me as i think about it." + origin: 850 50 + size: 9 + color: red + - text: "Slap owner's face at 5am until human fills food dish licks your face." + origin: 850 80 + size: 18 + color: red + - text: "Play riveting piece on synthesizer keyboard cat snacks, yet meow go back to sleep owner brings food and water tries to" + origin: 850 100 + size: 10 + color: red + - text: "pet on head, so scratch get sprayed by water because bad cat nap all day, kitty scratches couch bad kitty." + origin: 850 112 + size: 10 + color: red + - text: "Touch water with paw then recoil in horror climb a tree, wait for a fireman jump to fireman then" + origin: 850 130 + size: 12 + color: red + - text: "scratch his face and mark territory when in doubt, wash." + origin: 850 150 + size: 12 + color: red + - text: "Friends are not food warm up laptop with butt lick butt fart rainbows until owner yells pee in litter box hiss at cats if it fits," + origin: 850 170 + size: 10 + color: red + - text: "i sits swat at dog, or chase the pig around the house yet thug cat but hiss and stare at nothing then run suddenly away." + origin: 850 180 + size: 10 + color: red + - text: "Howl uncontrollably for no reason pushes butt to face eat a plant, kill a hand for cough hairball on conveniently placed pants." + origin: 850 200 + size: 10 + color: red + - text: "Wake up human for food at 4am. Decide to want nothing to do with my owner today pelt around the house and up and down stairs chasing phantoms." + origin: 850 220 + size: 8 + color: red + - text: "Poop on grasses." + origin: 850 240 + size: 16 + color: red + - text: "Hunt by meowing loudly at 5am next to human slave food dispenser bleghbleghvomit my" + origin: 850 260 + size: 10 + color: red + - text: "furball really tie the room together and throwup on your pillow, so scamper." + origin: 850 270 + size: 10 + color: red + - text: "Spread kitty litter all over house." + origin: 850 290 + size: 18 + color: red + - text: "Friends are not food curl into a furry donut or going to catch the red" + origin: 850 320 + size: 16 + color: red + - text: "dot today going to catch the red dot today or destroy the blinds or purr." + origin: 850 340 + size: 16 + color: red + + - text: "Cats making all the muffins knock over christmas tree" + origin: 20 430 + size: 20 + color: green + - text: "Cat ipsum dolor sit amet, poop in the plant pot and demand to be let outside at once, and expect owner to wait for me as i think about it." + origin: 20 450 + size: 9 + color: green + - text: "Slap owner's face at 5am until human fills food dish licks your face." + origin: 20 480 + size: 18 + color: green + - text: "Play riveting piece on synthesizer keyboard cat snacks, yet meow go back to sleep owner brings food and water tries to" + origin: 20 500 + size: 10 + color: green + - text: "pet on head, so scratch get sprayed by water because bad cat nap all day, kitty scratches couch bad kitty." + origin: 20 512 + size: 10 + color: green + - text: "Touch water with paw then recoil in horror climb a tree, wait for a fireman jump to fireman then" + origin: 20 530 + size: 12 + color: green + - text: "scratch his face and mark territory when in doubt, wash." + origin: 20 550 + size: 12 + color: green + - text: "Friends are not food warm up laptop with butt lick butt fart rainbows until owner yells pee in litter box hiss at cats if it fits," + origin: 20 570 + size: 10 + color: green + - text: "i sits swat at dog, or chase the pig around the house yet thug cat but hiss and stare at nothing then run suddenly away." + origin: 20 580 + size: 10 + color: green + - text: "Howl uncontrollably for no reason pushes butt to face eat a plant, kill a hand for cough hairball on conveniently placed pants." + origin: 20 600 + size: 10 + color: green + - text: "Wake up human for food at 4am. Decide to want nothing to do with my owner today pelt around the house and up and down stairs chasing phantoms." + origin: 20 620 + size: 8 + color: green + - text: "Poop on grasses." + origin: 20 640 + size: 16 + color: green + - text: "Hunt by meowing loudly at 5am next to human slave food dispenser bleghbleghvomit my" + origin: 20 660 + size: 10 + color: green + - text: "furball really tie the room together and throwup on your pillow, so scamper." + origin: 20 670 + size: 10 + color: green + - text: "Spread kitty litter all over house." + origin: 20 690 + size: 18 + color: green + - text: "Friends are not food curl into a furry donut or going to catch the red" + origin: 20 720 + size: 16 + color: green + - text: "dot today going to catch the red dot today or destroy the blinds or purr." + origin: 20 740 + size: 16 + color: green + + - text: "Cats making all the muffins knock over christmas tree" + origin: 850 430 + size: 20 + color: blue + - text: "Cat ipsum dolor sit amet, poop in the plant pot and demand to be let outside at once, and expect owner to wait for me as i think about it." + origin: 850 450 + size: 9 + color: blue + - text: "Slap owner's face at 5am until human fills food dish licks your face." + origin: 850 480 + size: 18 + color: blue + - text: "Play riveting piece on synthesizer keyboard cat snacks, yet meow go back to sleep owner brings food and water tries to" + origin: 850 500 + size: 10 + color: blue + - text: "pet on head, so scratch get sprayed by water because bad cat nap all day, kitty scratches couch bad kitty." + origin: 850 512 + size: 10 + color: blue + - text: "Touch water with paw then recoil in horror climb a tree, wait for a fireman jump to fireman then" + origin: 850 530 + size: 12 + color: blue + - text: "scratch his face and mark territory when in doubt, wash." + origin: 850 550 + size: 12 + color: blue + - text: "Friends are not food warm up laptop with butt lick butt fart rainbows until owner yells pee in litter box hiss at cats if it fits," + origin: 850 570 + size: 10 + color: blue + - text: "i sits swat at dog, or chase the pig around the house yet thug cat but hiss and stare at nothing then run suddenly away." + origin: 850 580 + size: 10 + color: blue + - text: "Howl uncontrollably for no reason pushes butt to face eat a plant, kill a hand for cough hairball on conveniently placed pants." + origin: 850 600 + size: 10 + color: blue + - text: "Wake up human for food at 4am. Decide to want nothing to do with my owner today pelt around the house and up and down stairs chasing phantoms." + origin: 850 620 + size: 8 + color: blue + - text: "Poop on grasses." + origin: 850 640 + size: 16 + color: blue + - text: "Hunt by meowing loudly at 5am next to human slave food dispenser bleghbleghvomit my" + origin: 850 660 + size: 10 + color: blue + - text: "furball really tie the room together and throwup on your pillow, so scamper." + origin: 850 670 + size: 10 + color: blue + - text: "Spread kitty litter all over house." + origin: 850 690 + size: 18 + color: blue + - text: "Friends are not food curl into a furry donut or going to catch the red" + origin: 850 720 + size: 16 + color: blue + - text: "dot today going to catch the red dot today or destroy the blinds or purr." + origin: 850 740 + size: 16 + color: blue diff --git a/third_party/webrender/wrench/benchmarks/transforms-simple.yaml b/third_party/webrender/wrench/benchmarks/transforms-simple.yaml new file mode 100644 index 00000000000..53956da4015 --- /dev/null +++ b/third_party/webrender/wrench/benchmarks/transforms-simple.yaml @@ -0,0 +1,44 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1024, 1024] + items: + - type: stacking-context + bounds: [0, 0, 1024, 1024] + transform: rotate(45) + items: + - type: rect + bounds: [0, 0, 1024, 1024] + color: [255, 0, 0, 0.5] + - type: rect + bounds: [0, 0, 1024, 1024] + color: [255, 0, 0, 0.5] + - type: rect + bounds: [0, 0, 1024, 1024] + color: [255, 0, 0, 0.5] + - type: rect + bounds: [0, 0, 1024, 1024] + color: [255, 0, 0, 0.5] + - type: rect + bounds: [0, 0, 1024, 1024] + color: [255, 0, 0, 0.5] + - type: rect + bounds: [0, 0, 1024, 1024] + color: [255, 0, 0, 0.5] + - type: rect + bounds: [0, 0, 1024, 1024] + color: [255, 0, 0, 0.5] + - type: rect + bounds: [0, 0, 1024, 1024] + color: [255, 0, 0, 0.5] + - type: rect + bounds: [0, 0, 1024, 1024] + color: [255, 0, 0, 0.5] + - type: rect + bounds: [0, 0, 1024, 1024] + color: [255, 0, 0, 0.5] + - type: rect + bounds: [0, 0, 1024, 1024] + color: [255, 0, 0, 0.5] + diff --git a/third_party/webrender/wrench/benchmarks/unaligned-gradient.yaml b/third_party/webrender/wrench/benchmarks/unaligned-gradient.yaml new file mode 100644 index 00000000000..ea7738202d8 --- /dev/null +++ b/third_party/webrender/wrench/benchmarks/unaligned-gradient.yaml @@ -0,0 +1,62 @@ +root: + items: + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 1, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 1, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 1, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 1, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 1, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 1, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 1, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 1, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 1, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 1, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false diff --git a/third_party/webrender/wrench/build.rs b/third_party/webrender/wrench/build.rs new file mode 100644 index 00000000000..974ef3180cc --- /dev/null +++ b/third_party/webrender/wrench/build.rs @@ -0,0 +1,28 @@ +/* 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 std::env; +use std::fs; +use std::path::PathBuf; + +fn main() { + let target = env::var("TARGET").unwrap(); + let out_dir = env::var_os("OUT_DIR").unwrap(); + let out_dir = PathBuf::from(out_dir); + + println!("cargo:rerun-if-changed=res/wrench.exe.manifest"); + if target.contains("windows") { + let src = PathBuf::from("res/wrench.exe.manifest"); + let mut dst = out_dir + .parent() + .unwrap() + .parent() + .unwrap() + .parent() + .unwrap() + .to_owned(); + dst.push("wrench.exe.manifest"); + fs::copy(&src, &dst).unwrap(); + } +} diff --git a/third_party/webrender/wrench/examples/animated.anim b/third_party/webrender/wrench/examples/animated.anim new file mode 100644 index 00000000000..ee834e6cc2c --- /dev/null +++ b/third_party/webrender/wrench/examples/animated.anim @@ -0,0 +1,13 @@ +--- + anim.color: + - red + - green + - blue + rect.pos: + - [100, 100, 100, 100] + - [100, 100, 120, 100] + - [100, 100, 140, 100] + bs.offset: + - [0, 0] + - [2, 0] + - [4, 0] diff --git a/third_party/webrender/wrench/examples/animated.yaml b/third_party/webrender/wrench/examples/animated.yaml new file mode 100644 index 00000000000..7f9026ad6ed --- /dev/null +++ b/third_party/webrender/wrench/examples/animated.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: rect + bounds: key(rect.pos) + color: key(anim.color) + - type: box-shadow + bounds: [ 100, 300, 100, 100 ] + blur-radius: 3 + border-radius: 10 + color: blue + clip-mode: outset + offset: key(bs.offset) diff --git a/third_party/webrender/wrench/reftests/aa/aa-dist-bug-ref.yaml b/third_party/webrender/wrench/reftests/aa/aa-dist-bug-ref.yaml new file mode 100644 index 00000000000..c83fff2a0bd --- /dev/null +++ b/third_party/webrender/wrench/reftests/aa/aa-dist-bug-ref.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - type: rect + color: red + bounds: [340, 184, 50, 20] diff --git a/third_party/webrender/wrench/reftests/aa/aa-dist-bug.yaml b/third_party/webrender/wrench/reftests/aa/aa-dist-bug.yaml new file mode 100644 index 00000000000..19b747aabce --- /dev/null +++ b/third_party/webrender/wrench/reftests/aa/aa-dist-bug.yaml @@ -0,0 +1,43 @@ +# Test that when the AA range is > 1, the AA is correctly applied to +# an ellipse clip. This is a regression test for issue #2576. +# The rectangles below mask out most of the box shadow and rounded +# corners, which are not relevant to what is being tested here. +--- +root: + items: + - type: stacking-context + transform: rotate-z(-45) rotate-x(-60) + transform-origin: 300 300 + items: + - type: box-shadow + bounds: [0, 0, 150, 150] + color: black + offset: [150, 50] + border-radius: 8 + + - type: clip + bounds: [90, 0, 150, 150] + complex: + - rect: [90, 0, 150, 150] + radius: 8 + items: + - type: rect + color: red + border-radius: 8 + bounds: [90, 0, 150, 150] + + - type: rect + color: white + bounds: [250, 100, 240, 84] + + - type: rect + color: white + bounds: [250, 204, 240, 70] + + - type: rect + color: white + bounds: [240, 100, 100, 150] + + - type: rect + color: white + bounds: [390, 100, 100, 150] diff --git a/third_party/webrender/wrench/reftests/aa/fractional-radii-ref.yaml b/third_party/webrender/wrench/reftests/aa/fractional-radii-ref.yaml new file mode 100644 index 00000000000..364d6e75295 --- /dev/null +++ b/third_party/webrender/wrench/reftests/aa/fractional-radii-ref.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - type: rect + bounds: 20 50 100 100 + color: blue + + - type: rect + bounds: 140 50 100 100 + color: blue + + - type: rect + bounds: 260 50 100 100 + color: blue + + - type: rect + bounds: 380 50 100 100 + color: blue diff --git a/third_party/webrender/wrench/reftests/aa/fractional-radii.yaml b/third_party/webrender/wrench/reftests/aa/fractional-radii.yaml new file mode 100644 index 00000000000..9293c1b1e9f --- /dev/null +++ b/third_party/webrender/wrench/reftests/aa/fractional-radii.yaml @@ -0,0 +1,42 @@ +--- +root: + items: + - type: clip + bounds: [20, 50, 100, 100] + complex: + - rect: [20, 50, 100, 100] + radius: 0 + items: + - type: rect + bounds: 20 50 100 100 + color: blue + + - type: clip + bounds: [140, 50, 100, 100] + complex: + - rect: [140, 50, 100, 100] + radius: 0.001 + items: + - type: rect + bounds: 140 50 100 100 + color: blue + + - type: clip + bounds: [260, 50, 100, 100] + complex: + - rect: [260, 50, 100, 100] + radius: 0.01 + items: + - type: rect + bounds: 260 50 100 100 + color: blue + + - type: clip + bounds: [380, 50, 100, 100] + complex: + - rect: [380, 50, 100, 100] + radius: 0.1 + items: + - type: rect + bounds: 380 50 100 100 + color: blue diff --git a/third_party/webrender/wrench/reftests/aa/reftest.list b/third_party/webrender/wrench/reftests/aa/reftest.list new file mode 100644 index 00000000000..af7f1da1b36 --- /dev/null +++ b/third_party/webrender/wrench/reftests/aa/reftest.list @@ -0,0 +1,3 @@ +skip_on(android) == rounded-rects.yaml rounded-rects-ref.png # Too wide for Android +== aa-dist-bug.yaml aa-dist-bug-ref.yaml +== fractional-radii.yaml fractional-radii-ref.yaml diff --git a/third_party/webrender/wrench/reftests/aa/rounded-rects-ref.png b/third_party/webrender/wrench/reftests/aa/rounded-rects-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..441f2ac93d9a5945f493c298b72fac41c5623b1e GIT binary patch literal 10881 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 A6aWAK literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/aa/rounded-rects.yaml b/third_party/webrender/wrench/reftests/aa/rounded-rects.yaml new file mode 100644 index 00000000000..c82349a0081 --- /dev/null +++ b/third_party/webrender/wrench/reftests/aa/rounded-rects.yaml @@ -0,0 +1,41 @@ +--- +root: + items: + - + bounds: 0 0 1000 1000 + type: stacking-context + items: + - type: clip + bounds: [50, 50, 200, 200] + complex: + - rect: [50, 50, 200, 200] + radius: 8 + items: + - type: rect + bounds: 50 50 200 200 + color: red + + - type: clip + bounds: [270, 50, 200, 200] + complex: + - rect: [270, 50, 200, 200] + radius: [16, 32, 48, 64] + items: + - type: rect + bounds: 270 50 200 200 + color: green + + - type: clip + bounds: [490, 50, 500, 500] + complex: + - rect: [490, 50, 500, 500] + radius: { + top-left: [32, 16], + top-right: [40, 24], + bottom-left: [48, 64], + bottom-right: [52, 80], + } + items: + - type: rect + bounds: 490 50 500 500 + color: blue diff --git a/third_party/webrender/wrench/reftests/backface/backface-3d-leaf.yaml b/third_party/webrender/wrench/reftests/backface/backface-3d-leaf.yaml new file mode 100644 index 00000000000..1a7be55eaea --- /dev/null +++ b/third_party/webrender/wrench/reftests/backface/backface-3d-leaf.yaml @@ -0,0 +1,18 @@ +# In this test, the leaf green rectangle has only its back visible +# due to rotate-x(180) transformation, preserve-3d style, +# and "backface-visible = false", so it's completely hidden. +--- +root: + items: + - type: rect + color: red + bounds: 0 0 1024 768 + - type: stacking-context + bounds: 0 0 1024 768 + transform-style: preserve-3d + transform: rotate-x(180) + items: + - type: rect + bounds: 0 0 200 200 + color: green + backface-visible: false diff --git a/third_party/webrender/wrench/reftests/backface/backface-both-sides-ref.yaml b/third_party/webrender/wrench/reftests/backface/backface-both-sides-ref.yaml new file mode 100644 index 00000000000..aa0bd8e57cc --- /dev/null +++ b/third_party/webrender/wrench/reftests/backface/backface-both-sides-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: stacking-context + items: + - type: rect + color: red + bounds: 100 0 100 100 diff --git a/third_party/webrender/wrench/reftests/backface/backface-both-sides.yaml b/third_party/webrender/wrench/reftests/backface/backface-both-sides.yaml new file mode 100644 index 00000000000..0212c3002db --- /dev/null +++ b/third_party/webrender/wrench/reftests/backface/backface-both-sides.yaml @@ -0,0 +1,25 @@ +# In this test, both rectangles have invisible back faces and are +# parented to a preserve-3d context that is rotated by 180 degrees. +# The red one is also rotated 180 degrees, and should be visible. +--- +root: + items: + - type: stacking-context + items: + - type: stacking-context + transform: rotate-y(180) + transform-style: preserve-3d + transform-origin: 50 50 + items: + - type: rect + color: green + bounds: 0 0 100 100 + backface-visible: false + - type: stacking-context + transform: rotate-y(180) + transform-origin: 0 0 + backface-visible: false + items: + - type: rect + color: red + bounds: 0 0 100 100 diff --git a/third_party/webrender/wrench/reftests/backface/backface-double-flip.yaml b/third_party/webrender/wrench/reftests/backface/backface-double-flip.yaml new file mode 100644 index 00000000000..b242e956279 --- /dev/null +++ b/third_party/webrender/wrench/reftests/backface/backface-double-flip.yaml @@ -0,0 +1,21 @@ +# In this test, the red rectangle has backface visibility turned off. +# However its world transformation makes it front-facing. +# However it's still invisible because it's back-facing in the picture space +# of the transformed stacking context. +--- +root: + items: + - type: stacking-context + items: + - type: stacking-context + transform: rotate-y(180) + transform-style: preserve-3d + transform-origin: 50 50 + items: + - type: stacking-context + transform: rotate-y(180) + items: + - type: rect + color: red + backface-visible: false + bounds: 0 0 100 100 diff --git a/third_party/webrender/wrench/reftests/backface/backface-hidden.yaml b/third_party/webrender/wrench/reftests/backface/backface-hidden.yaml new file mode 100644 index 00000000000..afa9d653b87 --- /dev/null +++ b/third_party/webrender/wrench/reftests/backface/backface-hidden.yaml @@ -0,0 +1,18 @@ +# In this test, the leaf green rectangle has only its back visible +# due to rotate-x(180) transformation, +# and "backface-visible = false", so it's completely hidden. +--- +root: + items: + - type: rect + color: red + bounds: 0 0 1024 768 + - type: stacking-context + bounds: 0 0 1024 768 + transform: rotate-x(180) + transform-style: preserve-3d + items: + - type: rect + bounds: 0 0 1024 768 + color: green + backface-visible: false diff --git a/third_party/webrender/wrench/reftests/backface/backface-leaf-ref.yaml b/third_party/webrender/wrench/reftests/backface/backface-leaf-ref.yaml new file mode 100644 index 00000000000..17d39b4d61b --- /dev/null +++ b/third_party/webrender/wrench/reftests/backface/backface-leaf-ref.yaml @@ -0,0 +1,12 @@ +--- +root: + items: + - type: rect + color: red + bounds: 0 0 1024 768 + - type: stacking-context + bounds: 0 0 1024 768 + items: + - type: rect + bounds: 0 568 200 200 + color: green diff --git a/third_party/webrender/wrench/reftests/backface/backface-leaf.yaml b/third_party/webrender/wrench/reftests/backface/backface-leaf.yaml new file mode 100644 index 00000000000..d1c1b893be7 --- /dev/null +++ b/third_party/webrender/wrench/reftests/backface/backface-leaf.yaml @@ -0,0 +1,16 @@ +# In this test, the leaf green rectangle "backface-visible = false" which is ignored +# because it doesn't have any transform, and it's not in preserve-3d context. +--- +root: + items: + - type: rect + color: red + bounds: 0 0 1024 768 + - type: stacking-context + bounds: 0 0 1024 768 + transform: rotate-x(180) + items: + - type: rect + bounds: 0 0 200 200 + color: green + backface-visible: false diff --git a/third_party/webrender/wrench/reftests/backface/backface-picture-ref.yaml b/third_party/webrender/wrench/reftests/backface/backface-picture-ref.yaml new file mode 100644 index 00000000000..3013f72f207 --- /dev/null +++ b/third_party/webrender/wrench/reftests/backface/backface-picture-ref.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - type: rect + color: red + bounds: 0 0 100 100 diff --git a/third_party/webrender/wrench/reftests/backface/backface-picture.yaml b/third_party/webrender/wrench/reftests/backface/backface-picture.yaml new file mode 100644 index 00000000000..189c23d5b54 --- /dev/null +++ b/third_party/webrender/wrench/reftests/backface/backface-picture.yaml @@ -0,0 +1,21 @@ +# In this test we ensure that "backface-visiblity" property +# is not inherited, and the red rect rotated by 180 degrees +# is still rendered. +--- +root: + items: + - type: stacking-context + items: + - type: stacking-context + transform-style: preserve-3d + backface-visible: false + items: + - type: stacking-context + transform: rotate-y(180) + transform-origin: 50 50 + items: + - type: stacking-context + items: + - type: rect + color: red + bounds: 0 0 100 100 diff --git a/third_party/webrender/wrench/reftests/backface/backface-ref.yaml b/third_party/webrender/wrench/reftests/backface/backface-ref.yaml new file mode 100644 index 00000000000..614cf5f4652 --- /dev/null +++ b/third_party/webrender/wrench/reftests/backface/backface-ref.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - type: rect + color: red + bounds: 0 0 1024 768 diff --git a/third_party/webrender/wrench/reftests/backface/backface-sc.yaml b/third_party/webrender/wrench/reftests/backface/backface-sc.yaml new file mode 100644 index 00000000000..350baca592b --- /dev/null +++ b/third_party/webrender/wrench/reftests/backface/backface-sc.yaml @@ -0,0 +1,17 @@ +# This test is similar to "backface-leaf.yaml" but with the whole +# stacking context (containing a green rect) turning invisible +# because of the "rotate-x(180)" transform. +--- +root: + items: + - type: rect + color: red + bounds: 0 0 1024 768 + - type: stacking-context + bounds: 0 0 1024 768 + transform: rotate-x(180) + backface-visible: false + items: + - type: rect + bounds: 0 0 200 200 + color: green diff --git a/third_party/webrender/wrench/reftests/backface/blank.yaml b/third_party/webrender/wrench/reftests/backface/blank.yaml new file mode 100644 index 00000000000..c4eb3ab6730 --- /dev/null +++ b/third_party/webrender/wrench/reftests/backface/blank.yaml @@ -0,0 +1,2 @@ +--- +root: diff --git a/third_party/webrender/wrench/reftests/backface/reftest.list b/third_party/webrender/wrench/reftests/backface/reftest.list new file mode 100644 index 00000000000..ec21edb22f9 --- /dev/null +++ b/third_party/webrender/wrench/reftests/backface/reftest.list @@ -0,0 +1,7 @@ +== backface-leaf.yaml backface-leaf-ref.yaml +== backface-3d-leaf.yaml backface-ref.yaml +== backface-hidden.yaml backface-ref.yaml +== backface-sc.yaml backface-ref.yaml +== backface-picture.yaml backface-picture-ref.yaml +== backface-double-flip.yaml blank.yaml +== backface-both-sides.yaml backface-both-sides-ref.yaml diff --git a/third_party/webrender/wrench/reftests/blend/blank.yaml b/third_party/webrender/wrench/reftests/blend/blank.yaml new file mode 100644 index 00000000000..c4eb3ab6730 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/blank.yaml @@ -0,0 +1,2 @@ +--- +root: diff --git a/third_party/webrender/wrench/reftests/blend/darken-ref.yaml b/third_party/webrender/wrench/reftests/blend/darken-ref.yaml new file mode 100644 index 00000000000..a83cf682444 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/darken-ref.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [10, 20, 10] diff --git a/third_party/webrender/wrench/reftests/blend/darken.yaml b/third_party/webrender/wrench/reftests/blend/darken.yaml new file mode 100644 index 00000000000..2de68d87240 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/darken.yaml @@ -0,0 +1,16 @@ +--- +root: + items: + - type: stacking-context + blend-container: true + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [10, 20, 30] + - type: stacking-context + bounds: [0, 0, 100, 100] + mix-blend-mode: darken + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [30, 20, 10] diff --git a/third_party/webrender/wrench/reftests/blend/difference-ref.yaml b/third_party/webrender/wrench/reftests/blend/difference-ref.yaml new file mode 100644 index 00000000000..b913384f24b --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/difference-ref.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [0, 0, 0] diff --git a/third_party/webrender/wrench/reftests/blend/difference-transparent-ref.yaml b/third_party/webrender/wrench/reftests/blend/difference-transparent-ref.yaml new file mode 100644 index 00000000000..8ed801fb15b --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/difference-transparent-ref.yaml @@ -0,0 +1,23 @@ +--- +root: + items: + - type: rect + bounds: [0, 0, 100, 100] + + # First blend black rect with green in place + # Cs = (1 - αb) x Cs + αb x B(Cb, Cs) + # B(Cb, Cs) = | Cb - Cs | = (0, 0, 0) + # 0.5 * (0, 255, 0) + 0.5 * (0, 0, 0) = (0, 127.5, 0) + # + # Now, composite the resulting color with src-over; the alpha is the original alpha for the top layer + # while the color is the blending result + # co = αs x Fa x Cs + αb x Fb x Cb - this is premultiplied + # αo = αs + αb x (1 – αs) + # Source over: Fa = 1; Fb = 1 – αs + # co = 0.5 * 1 * (0, 127.5, 0) + 0.5 * 0.5 * (0, 255, 0) = (0, 63.5, 0) + 0.25 * (0, 255, 0) = (0, 127.5, 0) + # ao = 0.5 + 0.5 * 0.5 = 0.75 + # Co = co/ao = (0, 127.5, 0) / 0.75 + # + # Now alpha composite on white background + # co = 0.75 * 1 * (0, 127.5, 0) / 0.75 + 1 * 0.25 * (255, 255, 255) = (0, 127.5, 0) + (63.75, 63.75, 63.75) = (63.75, 159, 63.75) = (64, 191, 64) + color: [64, 191, 64] diff --git a/third_party/webrender/wrench/reftests/blend/difference-transparent.yaml b/third_party/webrender/wrench/reftests/blend/difference-transparent.yaml new file mode 100644 index 00000000000..bd828d454aa --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/difference-transparent.yaml @@ -0,0 +1,22 @@ +--- +root: + items: + - type: rect + bounds: [0, 0, 100, 100] + color: white + # this stacking context should create an isolated group for its children + # causing the yellow rect to not blend with the green backdrop + - type: stacking-context + blend-container: true + bounds: [0, 0, 100, 100] + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [0, 255, 0, 0.5] + - type: stacking-context + bounds: [0, 0, 100, 100] + mix-blend-mode: difference + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [0, 255, 0, 0.5] diff --git a/third_party/webrender/wrench/reftests/blend/difference.yaml b/third_party/webrender/wrench/reftests/blend/difference.yaml new file mode 100644 index 00000000000..0c8d0fcd025 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/difference.yaml @@ -0,0 +1,16 @@ +--- +root: + items: + - type: stacking-context + blend-container: true + items: + - type: rect + bounds: [0, 0, 100, 100] + color: green + - type: stacking-context + bounds: [0, 0, 100, 100] + mix-blend-mode: difference + items: + - type: rect + bounds: [0, 0, 100, 100] + color: green diff --git a/third_party/webrender/wrench/reftests/blend/isolated-2-ref.yaml b/third_party/webrender/wrench/reftests/blend/isolated-2-ref.yaml new file mode 100644 index 00000000000..3226b6edda1 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/isolated-2-ref.yaml @@ -0,0 +1,21 @@ +--- +root: + items: + - type: rect + bounds: [10, 10, 130, 130] + color: [255, 255, 0] + - type: stacking-context + bounds: [10, 10, 130, 130] + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [0, 255, 0] + - type: stacking-context + bounds: [20, 20, 100, 100] + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [255, 0, 0] + - type: rect + bounds: [0, 0, 80, 80] + color: [0, 0, 0] diff --git a/third_party/webrender/wrench/reftests/blend/isolated-2.yaml b/third_party/webrender/wrench/reftests/blend/isolated-2.yaml new file mode 100644 index 00000000000..87bbac3caa7 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/isolated-2.yaml @@ -0,0 +1,25 @@ +# translation of wpt/css-tests/compositing-1_dev/html/mix-blend-mode-stacking-context-creates-isolation.htm +--- +root: + items: + - type: rect + bounds: [10, 10, 130, 130] + color: [255, 255, 0] + # this stacking context should create an isolated group for its children + # inside there should be overlapping red and green rects + # where they intersect should be a black rect + # the rects should not blend with the yellow backdrop + - type: stacking-context + blend-container: true + bounds: [10, 10, 130, 130] + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [0, 255, 0] + - type: stacking-context + bounds: [20, 20, 100, 100] + mix-blend-mode: multiply + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [255, 0, 0] diff --git a/third_party/webrender/wrench/reftests/blend/isolated-premultiplied-2-ref.yaml b/third_party/webrender/wrench/reftests/blend/isolated-premultiplied-2-ref.yaml new file mode 100644 index 00000000000..f5de6dc33d8 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/isolated-premultiplied-2-ref.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [229, 239, 229] diff --git a/third_party/webrender/wrench/reftests/blend/isolated-premultiplied-2.yaml b/third_party/webrender/wrench/reftests/blend/isolated-premultiplied-2.yaml new file mode 100644 index 00000000000..4ad5737d8cb --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/isolated-premultiplied-2.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 100, 100] + items: + - type: stacking-context + bounds: [0, 0, 100, 100] + mix-blend-mode: lighten + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [0, 100, 0, 0.1] diff --git a/third_party/webrender/wrench/reftests/blend/isolated-premultiplied.yaml b/third_party/webrender/wrench/reftests/blend/isolated-premultiplied.yaml new file mode 100644 index 00000000000..a8d738a389f --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/isolated-premultiplied.yaml @@ -0,0 +1,24 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 100, 100] + items: + # this stacking context should force its parent to be an isolated group + # we don't want it to actually draw anything so just make it draw a white rect + - type: stacking-context + bounds: [0, 0, 100, 100] + mix-blend-mode: lighten + items: + - type: rect + bounds: [500, 500, 100, 100] + color: [255, 255, 255] + + # transparent white, should be invisible + - type: rect + bounds: [0, 0, 100, 100] + color: [255, 255, 255, 0.5] + # transparent white, should be invisible + - type: image + bounds: [200, 0, 100, 100] + src: "transparent-white.png" diff --git a/third_party/webrender/wrench/reftests/blend/isolated-ref.yaml b/third_party/webrender/wrench/reftests/blend/isolated-ref.yaml new file mode 100644 index 00000000000..0f9061997ac --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/isolated-ref.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [255, 255, 0] diff --git a/third_party/webrender/wrench/reftests/blend/isolated-with-filter.yaml b/third_party/webrender/wrench/reftests/blend/isolated-with-filter.yaml new file mode 100644 index 00000000000..483d802cc36 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/isolated-with-filter.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [255, 0, 0] + # the presence of this filter shouldn't break isolated groups + - type: stacking-context + bounds: [0, 0, 100, 100] + filters: opacity(1.0) + items: + - type: stacking-context + bounds: [0, 0, 100, 100] + mix-blend-mode: difference + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [255, 255, 0] diff --git a/third_party/webrender/wrench/reftests/blend/isolated.yaml b/third_party/webrender/wrench/reftests/blend/isolated.yaml new file mode 100644 index 00000000000..456adba4b94 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/isolated.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [255, 0, 0] + # this stacking context should create an isolated group for its children + # causing the yellow rect to not blend with the green backdrop + - type: stacking-context + bounds: [0, 0, 100, 100] + items: + - type: stacking-context + bounds: [0, 0, 100, 100] + mix-blend-mode: difference + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [255, 255, 0] diff --git a/third_party/webrender/wrench/reftests/blend/large-ref.yaml b/third_party/webrender/wrench/reftests/blend/large-ref.yaml new file mode 100644 index 00000000000..c7e8433414e --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/large-ref.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: stacking-context + bounds: 0 0 2000 2000 + items: + - type: rect + bounds: 0 0 2000 2000 + color: [0, 128, 0, 1] diff --git a/third_party/webrender/wrench/reftests/blend/large.yaml b/third_party/webrender/wrench/reftests/blend/large.yaml new file mode 100644 index 00000000000..81672652364 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/large.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: stacking-context + bounds: 0 0 2000 2000 + items: + - type: stacking-context + bounds: 0 0 2000 2000 + mix-blend-mode: screen + items: + - type: rect + bounds: 0 0 2000 2000 + color: [0, 128, 0, 1] diff --git a/third_party/webrender/wrench/reftests/blend/lighten-ref.yaml b/third_party/webrender/wrench/reftests/blend/lighten-ref.yaml new file mode 100644 index 00000000000..5720f140a66 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/lighten-ref.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [30, 20, 30] diff --git a/third_party/webrender/wrench/reftests/blend/lighten.yaml b/third_party/webrender/wrench/reftests/blend/lighten.yaml new file mode 100644 index 00000000000..426e2323b9c --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/lighten.yaml @@ -0,0 +1,16 @@ +--- +root: + items: + - type: stacking-context + blend-container: true + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [10, 20, 30] + - type: stacking-context + bounds: [0, 0, 100, 100] + mix-blend-mode: lighten + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [30, 20, 10] diff --git a/third_party/webrender/wrench/reftests/blend/multi-mix-blend-mode-ref.yaml b/third_party/webrender/wrench/reftests/blend/multi-mix-blend-mode-ref.yaml new file mode 100644 index 00000000000..ae366a09e01 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/multi-mix-blend-mode-ref.yaml @@ -0,0 +1,14 @@ +--- +root: + items: + - type: rect + bounds: 0 0 800 200 + color: [255, 0, 0, 1] + + - type: rect + bounds: 50 50 100 100 + color: [255, 255, 0, 1] + + - type: rect + bounds: 300 50 400 100 + color: [255, 0, 255, 1] diff --git a/third_party/webrender/wrench/reftests/blend/multi-mix-blend-mode.yaml b/third_party/webrender/wrench/reftests/blend/multi-mix-blend-mode.yaml new file mode 100644 index 00000000000..d3ba8fcaf01 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/multi-mix-blend-mode.yaml @@ -0,0 +1,25 @@ +--- +root: + items: + - type: stacking-context + blend-container: true + items: + - type: rect + bounds: 0 0 800 200 + color: [255, 0, 0, 1] + + - type: stacking-context + bounds: 50 50 100 100 + mix-blend-mode: difference + items: + - type: rect + bounds: 0 0 100 100 + color: [0, 255, 0, 1] + + - type: stacking-context + bounds: 300 50 100 100 + mix-blend-mode: difference + items: + - type: rect + bounds: 0 0 400 100 + color: [0, 0, 255, 1] diff --git a/third_party/webrender/wrench/reftests/blend/multiply-2-ref.yaml b/third_party/webrender/wrench/reftests/blend/multiply-2-ref.yaml new file mode 100644 index 00000000000..6d4679a79d5 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/multiply-2-ref.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [0, 128, 0] diff --git a/third_party/webrender/wrench/reftests/blend/multiply-2.yaml b/third_party/webrender/wrench/reftests/blend/multiply-2.yaml new file mode 100644 index 00000000000..0ad1ef35d6f --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/multiply-2.yaml @@ -0,0 +1,16 @@ +--- +root: + items: + - type: stacking-context + blend-container: true + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [0, 255, 0] + - type: stacking-context + bounds: [0, 0, 100, 100] + mix-blend-mode: multiply + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [255, 128, 0] diff --git a/third_party/webrender/wrench/reftests/blend/multiply-3.yaml b/third_party/webrender/wrench/reftests/blend/multiply-3.yaml new file mode 100644 index 00000000000..05e1aa20f64 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/multiply-3.yaml @@ -0,0 +1,20 @@ +--- +root: + items: + - type: stacking-context + blend-container: true + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [0, 255, 0] + - type: stacking-context + bounds: [0, 0, 100, 100] + mix-blend-mode: multiply + items: + - type: stacking-context + bounds: [0, 0, 100, 100] + mix-blend-mode: multiply + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [255, 128, 0] diff --git a/third_party/webrender/wrench/reftests/blend/multiply-ref.yaml b/third_party/webrender/wrench/reftests/blend/multiply-ref.yaml new file mode 100644 index 00000000000..0b9aeed9068 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/multiply-ref.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - type: rect + bounds: [0, 0, 100, 100] + color: green diff --git a/third_party/webrender/wrench/reftests/blend/multiply.yaml b/third_party/webrender/wrench/reftests/blend/multiply.yaml new file mode 100644 index 00000000000..f9fa6cbb752 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/multiply.yaml @@ -0,0 +1,16 @@ +--- +root: + items: + - type: stacking-context + blend-container: true + items: + - type: rect + bounds: [0, 0, 100, 100] + color: green + - type: stacking-context + bounds: [25, 25, 50, 50] + mix-blend-mode: multiply + items: + - type: rect + bounds: [0, 0, 50, 50] + color: green diff --git a/third_party/webrender/wrench/reftests/blend/reftest.list b/third_party/webrender/wrench/reftests/blend/reftest.list new file mode 100644 index 00000000000..e533e36ce06 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/reftest.list @@ -0,0 +1,24 @@ +== multiply.yaml multiply-ref.yaml +fuzzy(1,32) == multiply-2.yaml multiply-2-ref.yaml +fuzzy(1,32) == color_targets(4) alpha_targets(0) multiply-3.yaml multiply-2-ref.yaml +== difference.yaml difference-ref.yaml +fuzzy(1,30000) == difference-transparent.yaml difference-transparent-ref.yaml +== darken.yaml darken-ref.yaml +== lighten.yaml lighten-ref.yaml + +fuzzy(1,32) == repeated-difference.yaml repeated-difference-ref.yaml + +== isolated.yaml isolated-ref.yaml +fuzzy(3,397) == isolated-2.yaml isolated-2-ref.yaml +== isolated-with-filter.yaml isolated-ref.yaml +== isolated-premultiplied.yaml blank.yaml +== isolated-premultiplied-2.yaml isolated-premultiplied-2-ref.yaml + +== large.yaml large-ref.yaml + +# fuzzy because dithering is different for gradients +# drawn in different render targets +fuzzy(1,2502) == transparent-composite-1.yaml transparent-composite-1-ref.yaml +fuzzy(1,2502) == transparent-composite-2.yaml transparent-composite-2-ref.yaml + +fuzzy(2,324) == multi-mix-blend-mode.yaml multi-mix-blend-mode-ref.yaml diff --git a/third_party/webrender/wrench/reftests/blend/repeated-difference-ref.yaml b/third_party/webrender/wrench/reftests/blend/repeated-difference-ref.yaml new file mode 100644 index 00000000000..30d53325bab --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/repeated-difference-ref.yaml @@ -0,0 +1,7 @@ +--- +root: + items: + - + bounds: [0, 0, 100, 100] + type: rect + color: [0, 255, 0] diff --git a/third_party/webrender/wrench/reftests/blend/repeated-difference.yaml b/third_party/webrender/wrench/reftests/blend/repeated-difference.yaml new file mode 100644 index 00000000000..a04e6cb2742 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/repeated-difference.yaml @@ -0,0 +1,31 @@ +--- +root: + items: + - + bounds: [0, 0, 100, 100] + type: rect + color: [255, 255, 255] + - type: stacking-context + blend-container: true + items: + - + bounds: [0, 0, 100, 100] + type: stacking-context + mix-blend-mode: difference + items: + - + bounds: [0, 0, 100, 100] + type: rect + color: [255, 255, 255] + - type: stacking-context + blend-container: true + items: + - + bounds: [0, 0, 100, 100] + type: stacking-context + mix-blend-mode: difference + items: + - + bounds: [0, 0, 100, 100] + type: rect + color: [0, 255, 0] diff --git a/third_party/webrender/wrench/reftests/blend/transparent-composite-1-ref.yaml b/third_party/webrender/wrench/reftests/blend/transparent-composite-1-ref.yaml new file mode 100644 index 00000000000..235801c5e1e --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/transparent-composite-1-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: gradient + bounds: [0, 0, 100, 100] + start: [0, 0] + end: [0, 100] + stops: [0.0, [0,0,0,0], 1.0, green] diff --git a/third_party/webrender/wrench/reftests/blend/transparent-composite-1.yaml b/third_party/webrender/wrench/reftests/blend/transparent-composite-1.yaml new file mode 100644 index 00000000000..319b6368437 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/transparent-composite-1.yaml @@ -0,0 +1,12 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 100, 100] + mix-blend-mode: darken + items: + - type: gradient + bounds: [0, 0, 100, 100] + start: [0, 0] + end: [0, 100] + stops: [0.0, [0,0,0,0], 1.0, green] diff --git a/third_party/webrender/wrench/reftests/blend/transparent-composite-2-ref.yaml b/third_party/webrender/wrench/reftests/blend/transparent-composite-2-ref.yaml new file mode 100644 index 00000000000..235801c5e1e --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/transparent-composite-2-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: gradient + bounds: [0, 0, 100, 100] + start: [0, 0] + end: [0, 100] + stops: [0.0, [0,0,0,0], 1.0, green] diff --git a/third_party/webrender/wrench/reftests/blend/transparent-composite-2.yaml b/third_party/webrender/wrench/reftests/blend/transparent-composite-2.yaml new file mode 100644 index 00000000000..e064f68f958 --- /dev/null +++ b/third_party/webrender/wrench/reftests/blend/transparent-composite-2.yaml @@ -0,0 +1,16 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 100, 100] + mix-blend-mode: darken + items: + - type: stacking-context + bounds: [0, 0, 100, 100] + mix-blend-mode: darken + items: + - type: gradient + bounds: [0, 0, 100, 100] + start: [0, 0] + end: [0, 100] + stops: [0.0, [0,0,0,0], 1.0, green] diff --git a/third_party/webrender/wrench/reftests/blend/transparent-white.png b/third_party/webrender/wrench/reftests/blend/transparent-white.png new file mode 100644 index 0000000000000000000000000000000000000000..6019b2b817266391029487da85b1c5d9c0e290d7 GIT binary patch literal 303 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�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 Kj4ra>cKCliYL0*a literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/border/border-dashed-dotted-caching.yaml b/third_party/webrender/wrench/reftests/border/border-dashed-dotted-caching.yaml new file mode 100644 index 00000000000..ca224852fd3 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-dashed-dotted-caching.yaml @@ -0,0 +1,76 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 500, 500] + items: + + - type: border + bounds: [ 0, 10, 25, 30 ] + width: [ 10 ] + border-type: normal + style: [ dashed ] + color: [ blue ] + + - type: border + bounds: [ 0, 60, 200, 30 ] + width: [ 10 ] + border-type: normal + style: [ dashed ] + color: [ blue ] + + - type: border + bounds: [ 0, 110, 300, 30 ] + width: [ 10 ] + border-type: normal + style: [ dashed ] + color: [ blue ] + + - type: border + bounds: [ 0, 160, 400, 30 ] + width: [ 10 ] + border-type: normal + style: [ dashed ] + color: [ blue ] + + - type: border + bounds: [ 0, 210, 500, 30 ] + width: [ 10 ] + border-type: normal + style: [ dashed ] + color: [ blue ] + + - type: border + bounds: [ 0, 260, 25, 30 ] + width: [ 10 ] + border-type: normal + style: [ dotted ] + color: [ blue ] + + - type: border + bounds: [ 0, 310, 200, 30 ] + width: [ 10 ] + border-type: normal + style: [ dotted ] + color: [ blue ] + + - type: border + bounds: [ 0, 360, 300, 30 ] + width: [ 10 ] + border-type: normal + style: [ dotted ] + color: [ blue ] + + - type: border + bounds: [ 0, 410, 400, 30 ] + width: [ 10 ] + border-type: normal + style: [ dotted ] + color: [ blue ] + + - type: border + bounds: [ 0, 460, 500, 30 ] + width: [ 10 ] + border-type: normal + style: [ dotted ] + color: [ blue ] diff --git a/third_party/webrender/wrench/reftests/border/border-double-1px-ref.yaml b/third_party/webrender/wrench/reftests/border/border-double-1px-ref.yaml new file mode 100644 index 00000000000..f0c9eca1ca3 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-double-1px-ref.yaml @@ -0,0 +1,17 @@ +--- +root: + items: + - type: stacking-context + items: + - type: border + bounds: [ 10, 10, 100, 20 ] + width: 1 + border-type: normal + style: solid + color: red + - type: border + bounds: [ 12, 12, 96, 16 ] + width: 1 + border-type: normal + style: solid + color: red diff --git a/third_party/webrender/wrench/reftests/border/border-double-1px.yaml b/third_party/webrender/wrench/reftests/border/border-double-1px.yaml new file mode 100644 index 00000000000..3a5f0841080 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-double-1px.yaml @@ -0,0 +1,11 @@ +--- +root: + items: + - type: stacking-context + items: + - type: border + bounds: [ 10, 10, 100, 20 ] + width: 3 + border-type: normal + style: double + color: red diff --git a/third_party/webrender/wrench/reftests/border/border-double-simple-2-ref.yaml b/third_party/webrender/wrench/reftests/border/border-double-simple-2-ref.yaml new file mode 100644 index 00000000000..e7fa39f125b --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-double-simple-2-ref.yaml @@ -0,0 +1,12 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 500, 500] + items: + - type: border + bounds: [ 0, 0, 500, 500 ] + width: [ 1 ] + border-type: normal + style: [ solid ] + color: [ black ] diff --git a/third_party/webrender/wrench/reftests/border/border-double-simple-2.yaml b/third_party/webrender/wrench/reftests/border/border-double-simple-2.yaml new file mode 100644 index 00000000000..6fa6d634cd2 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-double-simple-2.yaml @@ -0,0 +1,12 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 500, 500] + items: + - type: border + bounds: [ 0, 0, 500, 500 ] + width: [ 1 ] + border-type: normal + style: [ double ] + color: [ black ] diff --git a/third_party/webrender/wrench/reftests/border/border-double-simple-ref.yaml b/third_party/webrender/wrench/reftests/border/border-double-simple-ref.yaml new file mode 100644 index 00000000000..564e4df1631 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-double-simple-ref.yaml @@ -0,0 +1,21 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 50, 50] + items: + - type: border + bounds: [ 0, 0, 50, 50 ] + width: [ 4, 4, 4, 4 ] + border-type: normal + style: [ solid, solid, solid, solid ] + color: [ blue, blue, blue, blue ] + - type: stacking-context + bounds: [8, 8, 34, 34] + items: + - type: border + bounds: [ 0, 0, 34, 34 ] + width: [ 4, 4, 4, 4 ] + border-type: normal + style: [ solid, solid, solid, solid ] + color: [ blue, blue, blue, blue ] diff --git a/third_party/webrender/wrench/reftests/border/border-double-simple.yaml b/third_party/webrender/wrench/reftests/border/border-double-simple.yaml new file mode 100644 index 00000000000..4830485a8f3 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-double-simple.yaml @@ -0,0 +1,12 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 500, 500] + items: + - type: border + bounds: [ 0, 0, 50, 50 ] + width: [ 12, 12, 12, 12 ] + border-type: normal + style: [ double, double, double, double ] + color: [ blue, blue, blue, blue ] diff --git a/third_party/webrender/wrench/reftests/border/border-gradient-nine-patch.png b/third_party/webrender/wrench/reftests/border/border-gradient-nine-patch.png new file mode 100644 index 0000000000000000000000000000000000000000..7ab8c3df6017fe99bf526cdc506139e6e6e687a4 GIT binary patch literal 1051 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST~P>f%%cAi(^Q|oVQmRgI;+Ev|W7t z+kT68y!mRqAKp%BlV;8d_EMbi&Qo-L{`>lWcj_A4@7=qf-?-=QJjQZ6CQYSA>-mi$ z6Azf*X9lwW-(e4OVXC*~ThhVeq~NL&69Z(maR0WQpd_UCYNtb&$BqThfeIfcU1JS$ zIn=s_A1K{@L(D^vQ+xwRk=pktpf;a3Ux7+zT(N7^X*ra_!gO#S&r$G zCNjMRkp;ORk%YG(GN2YD;_w$l8q|VB6u{~Qjh*0?IyVv$h`H3JmRRIn@-X%3QWmjqym4!K2+S!Bb=9gY5m0S$uFf~39X`J%D z-tQ<3pobHLO>4hl0W}Cd1vvYhJj;ej@5)ucb13Us8uiq+OnK~iOv6$>Tj%iZgR3T zDKcrkwv&Bh)}alw@GwizwRpz$mdpBk8>i%kA1q^c*=o&S}(nw%537 lhpSP!Lif-26WsnWa+W>{JD|k58kp%BJYD@<);T3K0RZPeYB~S_ literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/border/border-gradient-nine-patch.yaml b/third_party/webrender/wrench/reftests/border/border-gradient-nine-patch.yaml new file mode 100644 index 00000000000..9a2d6991a0a --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-gradient-nine-patch.yaml @@ -0,0 +1,12 @@ +--- +root: + items: + - type: border + bounds: [ 0, 0, 200, 200 ] + width: 30 + border-type: gradient + start: [ 0, 200 ] + end: [ 200, 0 ] + stops: [ 0.0, red, 0.177, red, 0.177, yellow, 0.50, yellow, 0.50, red ] + slice: [ 50 ] + outset: 0 diff --git a/third_party/webrender/wrench/reftests/border/border-gradient-simple-ref.yaml b/third_party/webrender/wrench/reftests/border/border-gradient-simple-ref.yaml new file mode 100644 index 00000000000..d961c120411 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-gradient-simple-ref.yaml @@ -0,0 +1,55 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 50, 50] + items: + - type: gradient # top left + bounds: [ 0, 0, 10, 10] + start: [ 25, 0 ] + end: [ 25, 50 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient # top right + bounds: [ 40, 0, 10, 10] + start: [ -15, 0 ] + end: [ -15, 50 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient # bottom left + bounds: [ 0, 40, 10, 10] + start: [ 25, -40 ] + end: [ 25, 10 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient # bottom right + bounds: [ 40, 40, 10, 10] + start: [ -15, -40 ] + end: [ -15, 10 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient # top + bounds: [ 10, 0, 30, 10] + start: [ 15, 0 ] + end: [ 15, 50 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient # right + bounds: [ 40, 10, 10, 30] + start: [ -15, -10 ] + end: [ -15, 40 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient # bottom + bounds: [ 10, 40, 30, 10] + start: [ 15, -40 ] + end: [ 15, 10 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: gradient # left + bounds: [ 0, 10, 10, 30] + start: [ 25, -10 ] + end: [ 25, 40 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false + diff --git a/third_party/webrender/wrench/reftests/border/border-gradient-simple.yaml b/third_party/webrender/wrench/reftests/border/border-gradient-simple.yaml new file mode 100644 index 00000000000..ef2d5a033a2 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-gradient-simple.yaml @@ -0,0 +1,14 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 50, 50] + items: + - type: border + bounds: [ 0, 0, 50, 50 ] + width: [ 10, 10, 10, 10 ] + border-type: gradient + start: [ 25, 0 ] + end: [ 25, 50 ] + stops: [ 0.0, red, 1.0, green ] + outset: [ 0, 0, 0, 0 ] diff --git a/third_party/webrender/wrench/reftests/border/border-groove-simple-ref.yaml b/third_party/webrender/wrench/reftests/border/border-groove-simple-ref.yaml new file mode 100644 index 00000000000..9dfe5f3358e --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-groove-simple-ref.yaml @@ -0,0 +1,21 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 50, 50] + items: + - type: border + bounds: [ 0, 0, 50, 50 ] + width: [ 6, 6, 6, 6 ] + border-type: normal + style: [ solid, solid, solid, solid ] + color: [ 0 0 170 1.0, 0 0 255 1.0, 0 0 255 1.0, 0 0 170 1.0 ] + - type: stacking-context + bounds: [6, 6, 38, 38] + items: + - type: border + bounds: [ 0, 0, 38, 38 ] + width: [ 6, 6, 6, 6 ] + border-type: normal + style: [ solid, solid, solid, solid ] + color: [ 0 0 255 1.0, 0 0 170 1.0, 0 0 170 1.0, 0 0 255 1.0 ] diff --git a/third_party/webrender/wrench/reftests/border/border-groove-simple.yaml b/third_party/webrender/wrench/reftests/border/border-groove-simple.yaml new file mode 100644 index 00000000000..0d992d5dc68 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-groove-simple.yaml @@ -0,0 +1,12 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 500, 500] + items: + - type: border + bounds: [ 0, 0, 50, 50 ] + width: [ 12, 12, 12, 12 ] + border-type: normal + style: [ groove, groove, groove, groove ] + color: [ blue, blue, blue, blue ] diff --git a/third_party/webrender/wrench/reftests/border/border-image-crash-ref.yaml b/third_party/webrender/wrench/reftests/border/border-image-crash-ref.yaml new file mode 100644 index 00000000000..9ea9184348a --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-image-crash-ref.yaml @@ -0,0 +1,21 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 500, 500] + items: + - type: border + bounds: [ 100, 100, 192, 192 ] + width: 32 + border-type: image + image-source: "border-image-src.png" + image-width: 32 + image-height: 32 + slice: [ 3, 0, 1, 36 ] + outset: 0 + repeat-vertical: stretch + repeat-horizontal: stretch + fill: true + - type: rect + bounds: [ 100, 100, 192, 192 ] + color: white diff --git a/third_party/webrender/wrench/reftests/border/border-image-crash.yaml b/third_party/webrender/wrench/reftests/border/border-image-crash.yaml new file mode 100644 index 00000000000..9ea9184348a --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-image-crash.yaml @@ -0,0 +1,21 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 500, 500] + items: + - type: border + bounds: [ 100, 100, 192, 192 ] + width: 32 + border-type: image + image-source: "border-image-src.png" + image-width: 32 + image-height: 32 + slice: [ 3, 0, 1, 36 ] + outset: 0 + repeat-vertical: stretch + repeat-horizontal: stretch + fill: true + - type: rect + bounds: [ 100, 100, 192, 192 ] + color: white diff --git a/third_party/webrender/wrench/reftests/border/border-image-empty-slice-ref.png b/third_party/webrender/wrench/reftests/border/border-image-empty-slice-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..103fdb57f6512c923bcd620d42da4c22c7085338 GIT binary patch literal 16010 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/border/border-image-empty-slice.yaml b/third_party/webrender/wrench/reftests/border/border-image-empty-slice.yaml new file mode 100644 index 00000000000..16f22b32f44 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-image-empty-slice.yaml @@ -0,0 +1,50 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 500, 500] + items: + - type: border + bounds: [ 20, 20, 192, 192 ] + width: 32 + border-type: image + image-source: "border-image-src.png" + image-width: 96 + image-height: 96 + slice: [ 32, 0, 32, 0 ] + outset: 0 + repeat-vertical: round + repeat-horizontal: round + - type: border + bounds: [ 240, 20, 192, 192 ] + width: 32 + border-type: image + image-source: "border-image-src.png" + image-width: 96 + image-height: 96 + slice: [ 0, 32, 0, 32 ] + outset: 0 + repeat-vertical: round + repeat-horizontal: round + - type: border + bounds: [ 20, 240, 192, 192 ] + width: 32 + border-type: image + image-source: "border-image-src.png" + image-width: 96 + image-height: 96 + slice: [ 32, 0, 0, 32 ] + outset: 0 + repeat-vertical: round + repeat-horizontal: round + - type: border + bounds: [ 240, 240, 192, 192 ] + width: 32 + border-type: image + image-source: "border-image-src.png" + image-width: 96 + image-height: 96 + slice: [ 0, 32, 32, 0 ] + outset: 0 + repeat-vertical: round + repeat-horizontal: round diff --git a/third_party/webrender/wrench/reftests/border/border-image-fill-ref.png b/third_party/webrender/wrench/reftests/border/border-image-fill-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..41627d9455727fb6d3f3b22197092ebb354c8926 GIT binary patch literal 28383 zcmbTd1#ny4^Cc)VGqcPPGcz+YGc!}n%@;;H}uIMqKFEc9Q*YEDEF0N~2^QP*}? zHSq#EyE<9g*joVIzdBn0Exc_k0RZof?OdI7s?OxR-9~suFy@)*LGJ|5!sE;C+likb z)8uXb#njB1xXM6xn3-#1=7GG=KmB!Y<3b;3HSRa6b+x}#`9I|ZJP6(w-=F$FK9B@^ z19v}byx+e5QcCRJRl-G)2pGGG=Itvmx%K|2=X!rlVE%mXuoL>O{q|B=Kph~sBjw*5 zup8JTP27HSmlPmaS#XoFsQ2fvY_0%BSBS|kmOAm)?Ys=L?`;)|{^EOFrM)`w{!% z3&szP#+u&a(E8u6Fi1a-@1Ec6ekK|b1{x**d5P{h5ZaylzU%AN{p9DxrD`HRtt8$% zh}3 zz;gP)w`SCKyF>Sc_FSn$(=3sX^Tc)CCA-2vi5o^sN#*R^pts?L%76$tD_nu>}f&Pzo`TkF_cT~`falr6a$Kd|y z6B=9OXH4z$3D14k%y*{nkm|Qn2wbv0J~tLpR*1f^Ofk5=>=jhp@i9oGx-HYDNyM;R z2e;&OQ~DDS$CB4WKK_m!gC2_jA}0%4mOW@jAu)7PmKH;ZgTvJNC{t~&^C&xKS|AZ! zj;`~YYOJv1vbNf6>$0xil}F1D)#=)n4c$uz_e(UynciO;dN=-vZlO&0eye+rp;G0u z+)FdfGrXHW-ROs`%F?uSJX%-QezYAzQ+cj?v~NFg4-zFEl%^{2Jhm)-?-<#L=i2t0 zu5I5A7{AeZ%UTJ1eIS|9ea_el%u@nrU+JIgJ*>l26VG_xx( zzr8=Jl_6ixX{?WaIrCNe_g%Kh?A@C0$4md8m(N`oq{~xGq1eqo%dI{gy}^i$5gNE} zmNR5`+SB$tr3&&Jgpj!?Get&RiL%;>izw?)JN0b?ObJ| z0pu6nhZ2EXr6_lK#(4T_)TQ(sr=tqfkx-AQ#sP@z=rYY}B>PFuVQV%oYXp)AiAb+D zw7FSxur6&x6?`!p3tcW1+jJzN9TCZ!NEo;LouWKP%fB0cHIh;Z^4$42^1U{RYx<`++*qrik4UAK}+ABwzk4LcgdX%9c zHCO^%!>}Zg;43zzZMx#;2o4TlnndSK1>;?;KMH>gS;z?&E}H9$);!5(=Pn{2I#jK- z;S6IX9B!x)!G&2o2YaG#f|KB|qlV&Xl+uT>o39lOL7U@kchj9GfPDaLj^VzgAwX{ z8gvB74y2P$O~w2Eyqr_Td$xGXHoa(@^}ZD$hdPOp(}{GyQ`zP01dnn?qpnCHjXe9A z_L7s*q?4!WL;E#Ru8S2dPO;TbbiFQJZIvhLPSJ2R zB3F3(`wE;LYnFA*Bd$o`SFM~lTy<34=T3AL;lb!C&AvZ=m-(sTNk)mUxAwuqkMp8?i9yZ zG>!LDS&k|zc5#k16v4nx-h_ObQc{fX1|fz=LLhFqWm39Hs#y(UlTeas*P@vjXhC#$ z*l2mwnp}$d=v;^xOLocf&YXfLWzzV_RQI&zL#Z=|N z3sBcce%>Ol=Le$`U$qXH$}K3n2Pz~WMj(`Pv#!yY+>*-(Hdk??5EJk!@pjdl~;Btq^~Y!y}*m*b3qp3g;Y36{8iyBGdd|4yE7{2mJ0XtySi3Q9oPFVK+I8?>6IOjs@BINSy(Eh|}Dx4kXWegk-- z3g(fE2TI6AA}=R0@CVakL$u0o8H6cayjitLRWxB|#*&mwZW*B+v4_8;03(5?-3z07+YZazODDZ~4N?hM}KI94&iKqz-aAHhO#^@twoT@nNu$Ji7=uN^K2L4yUJl*nn1YFb)%5VgGvcu3S6A$kWEBMs%+&eJ zlvUyq((GOnV;NleV(B^9_e-qn@d$~7xMV60Xx?h49{HqP;RDZJw1%COU{q9m80uY? z)QVSomV;4P8ybSG{(wP_d9<&Hxs?%#yUV}YqThTuh&3qqv<#JEMwr-11XuxWm$1EU ze{Vep;l9J2gWa9Ii8KTy;zyGb1AjUY1XWmBNtaB44c_!%H8MDOiFTFVf@XT8mQLM! zWF^o$K6}wMX;Z^cQbU_1zg;)zwD%G@TQD83qw+)1y;w7WF;O%zChSWWQloFZ0Dm9FhjycLBOp|2g_m=#HUZ2 zXexyjI?e}$ozntNf6b1jwMY-+T+}WCH2YN`)W!g1!M4C}(U-t4WB2iUlsg;d*hr5% zKzx`HfZujOggY`mz#jJ+oeJ!P9a#h&S;O44crr=hIU7yV$0QKRs=~E7HYSNO)f;ZO zDN8d5mwI%3vatsk1idHB{B?{fnyYzC1Q%tgP^1nNFyNZ2FLhXprU<(kS{j5%2gTw~ z=SsK~4HnV{zdCfgt|3XZFe^2;qw(AMvltou0>~8<6@GGarpLNhjE9fP{0VN`%sSGo4((f!b@W9w3rK~Qf0r>( z=K5tnh~H#MB|za@aB}#rr?8ABqe|hA#!&0PaT;(VSfBUZoJ>OgGjUGFYd0i!$`5zc zfv1605T(;EM($>rHl(*u)G}!5P%d~U;}w~&rQe%1QK<)!E>z{Hu8 zbGrGMqeufKZ)hF=)U(+mv{V{}jq~-QfsFN?V`id;Gt@$UwtjUBkn^@ zKAO=dnl(gRO75)bkRRQOmYI{o+eDDuC(o5b#J66M4Yoe|F)ZAT zlEN_O7ZP2FvLL~OYFDuSVr4XQGtr-w-Z9g{^G~<%)_SX{I%fGIHAG=L6?^mm64$?j zuD+wA3yzvsq%_cnJn+D2Zo$)tDr*&rT!^C;RwEf7UgSZwi_~i1&mMyCqXOm}=_X9w7*# z(Cee!h9*J^zPjJ@m{(`)^H*TDZgnTcDP`mIO-N`D-6q8ua}DC9(K~o|baJNNI@I%e#jtKMb9AmF!Ny=MQ*{N*={#a#C{J9~ z52g1Y%{=K@vfo;bkc)49XHwVSsA0>2)gw84#ad7DHEvZ5EyAyNcxU#O7_)*|q*$2| z9!(j`F!BtLs7OyhYq2MmB*brYPDAp~ac4jKyrQnTEibds?cLj9jAEsJ0xM)lD=A~6 z$>U-ym|){9_r&7593p`3K#kxNx|bQgoLj;{eT^W}B6wb&YIG4t47xakF~DM_&A#da zSTES*wAjH%NI&Y)42a1Hy49`2X&%hYn_$)d!ksije*FN)$`WSo5@dD}sRP8KgD%~L#k2D{-6sP$ zG1>jrY-B&i>F%cWwb+cZZzc}NvHT4_h22~Td|49sF7SUQ?<43OAPcc?t4zCS8W2? zpdG3Y!0-^>59!AIag>Ww@b7sjkcMHM%s?{F1~N2}4baNdX@$v*X~8pYe$cEXh0Z@K zrdl9a{Mb&=9qyy}Au|AHozz^$}j!EZ+G6AGcoZ2rYImLwxF@2K&6UNK0B zj_Ef90|XmGDRRKBf9sEC6XG&td-wyXOKIWj?#W){FjQBUpm_z#(R0)()F^z7Qi5JW zaufRIq$3bWABx2ZQt$~oM3cDK+dymfdqRK+FQr#wH*AXz@RWjPjHr+w?HRRcwNqY~ zRefHk`te6p3&By=4t~09PqMz!i7`TayC#p7l`6k2Y&KuqFwbm` z6g=aCbQLK-0lbLzao+=!pnS3?5BZqOv3ACeR$B>U>{@|hpE~zosTHyvR)~aCDP>Yl z4J&BKmH?!zzjNv5>OzLag_iJ;D>4Os*i=FbXeDBem(G#}-4X%a6fj4cblVaS!$HqwXp*0fgHqgTD4Roi0Ng zq&5~zKC*?>gNBujl?irTj2|b+NB(0Saw?=}fM44C@Z8tN{GP)2Eg@ro-;uIrXbO6i zDCwzOBS}?`J<9gZ%8#&;LvYrn9V*SN$RkJJqJsKaIIyty`1Vb<&`AANG-T4>fRbD_+ASfa*#hL@XH~%+;Qseos+ngElRr#;@bWrZa|p*O`z*c>Xi1x+ zl|YL4i^XhOg|zNnK4j6+6@}lbN{gL`*I^ltvY_`_7b)%lqGFbYG`O%?RZWo}dsHCs zCY0$FcToCrr&_UXAU(X?(+<-?e0N;~8pS7*C&Jfcl^bvqc=I^9;);8coj&>%V^<39 z-j{H4CRi8&CltI^9}}l2t%+DUhhy`gKw@_99q3!Oj^W6Bk1M zWCy1i?>@95nC@HaM7AW(sG_)4b;sYzE#y2}9oT~qg?NiiCQW1}z`ExGtD50CM*@by zqQfW{LyMYu?TvzS+Lu>ZZ?ffS4R!NLBs#Q2HyvmnFVZyw;cB@Bvcid_bq7h%3@J3k zZ**V3yGjh0C`g8QTf(f#)MUnlAmmv&V$qB@jQ;SE%zPHEE2- za#*K0tX}0*$+(Tk^&F|kB7L}sb91;@0B=B?jYf{gm{GFAt3zZW+a$DzMVW{m8P3H8 z+WQ!LP&*N5h}r0Kh+_4qdjv6Ikl1iQ<4`eFYfwk&#q#cerbyq^m^gywak}Re086@B zGF{0<;DAEz4&|oP0hQp$iem6DQLW1iCe6Y;hhzj?5GkF!Jrur+3Z#}+1j z#QH%qJcFtr;4r06>ko|*Z!`<_%vn@f1zm+N7vCH~5i1_^cUpGIP8=jkz8cT@W<@8- zsS;N~((GZEs*vvDtQq{eh9RjdEDPFwearUaNe@oc_O&Dx7I4t0cZ?ba`J`f@g%`%5 zOeo7}gYm+0rzQ5^&`wIN*Ohos8*a`@iZy9fU!*mD=r-?9Pcq#}Nr=18o@RGyURPIM zNYi1@CwV6iL{+RzM|vzp z>jZaY1q-4si|~L_$+^S5vDYmhD*>r9H!yIr{~;Pm)wa~^CWkwkPQa$T;jAsd!)C|8 zM9c|#j*%ZM(zw6eB3mt`B-jyN^~NH;nXPI>84k8lgNuk3N4J4OmK?@I7Wx85i6|gZ zgj7YuA>fQ5n?^P)BK!MV*eg-t z9PEB*%-v@&Va*>fDy=2unXPri<^dWNB)Lj3sQ_CE@7SL(lFvtx!C%=B@QX24)V)l* zCp!PsURG9r38B$V%duULMA&-kt?G4bt-E*n34A3T&eX$7l*dPJlP&C>UF#0!pVtKf zs&3C0Oc`3-4;)IPZhWao&8m$le z?y--_|>>hwIQHGFevdb z(ZOe1!W&I9s?2prF(IyjA8h;P;NBcjvN*Z~e!C#J;L1ma7FzWRD7rg(WJstV?H4o# zT#08n=&{G=hRp%P|u7N9Cilh&*J|8bMOigQD0-3tYJGXJPYf-!sMb?8@I4*Oi zZS~2E*F)8Qau)IgDaNeGL^O`7X}ZoL4#gP46w9ct654!#Ff!pdx}Uu_nvII|rG$}_ zud7WgO;DlrvRJ64Tgpgazfy6&wNePED_rRHc42AE^g48WdOd-3fd{+4;_$(%tN5@+ zGn)`8&Gx1*sX>(CUylL)v9%*H1+Vb`$MZR`0$b zFN&S4Ub@CjOgfOK7-16LtN5&V1dU;n5tI=M0nSff?Wj?{)8Doh(zf%zE>1y(L0$GM zXY3k)1S7eN3QIUmEOmFMooZkhyAMY+`SdBgYVz6$b7T2POeU5L;h_#~MLKf@o=`+2 zw85OtkzTbNm8)!AagIv2GMFKxxpoXFKcC0HTvsErg8S63w}r?SZ|pHF$o%jLbR@Y; zUPS)GVOnQEGc81c8?jDEbE0z2T2naPv@oLAk_0_QLGE(%_uRssW6?a_rxwieF_BwQ4aI%t zLyAnuiq`u}6vocU@F^NL8I2X3y2!JRUSpwjh-^GPcH;fKfyg`T2`svI6&!ATK}V=%$wrUOeBXpz#oy@Lbf&6yoEwZ9JbJ?GOS=YNqU+Ape-YIWhP zJ3(k=t~5tns&QjC$P`nGNpP!#Ap;QXThY=wN`DpE+Vm*hCm=^(!~!}3eq+<4E=Suoisu+N0$hm!tu>Du_YUatjk@=Fsr|WHU5RwS%byZLFqY zXM>@|ws;1(U#By@<6B<&_L^1xEcuvsZ((ZjS4e4KJd4?dmKJZE#Mlm9Xu89}OUBjF z5vP0Xl4{gGg}7GoMKAPg{ z$sj(9lYQlW>%^PRM4;RLLd_yU1z;}(bZ?=qF;m>3l;f{H%4)_z8*7dkEnb1P zXgIjkA%q8o*=ZvxC1@nW2P1&$&sj&~mip54j64}-5{rLys|F|P9t{iohCOuQ_Kvq! zTBc`_;12Y1imW5moyg>Vwi9Ttd%yZ&9KTTRJKv zMv;FA1{gh`XeXJA`RdFEk92kw9>UkVa;rqe{{uWUZ(3V>B2 z^r-7hM7ljsXoaZSxkVG;pA(fcTwn(qu3XiL>|iV7$GzA^+=kf_h~R+yP0|||%LS!Y zii#u;n5tKkh};t9?Jgp^2xdY4>EeZ2UNITX6LHexrjNCn0>28dg;V%BroKGf6W`iw zVm(sZj@QOPgqA64TVu)8q(wEdgniF}sBbsT=xn;^pBJ2mQcAg*;+40_ocTbA- zNY2w`_O@$@na@Q_Y%5`!5 zOg#Wqu9w?+z?+0?iiaR#<_&YQdUGq{NqcBivM=qkfU_v*^ivUw7=uFkF_M5HjmN^r zJ|!`u9L4Y54Td7|Vh$97x+G_m;C6Mo=pxP-ak$iVlJVUgqQ+`v6Ry9Qic+1;(RDBA*nWzQ=s3~n$I(Ye z@H^FOaa0RwETrr<+4>VH_1G&sr7t8eZarVg57FX(FY}b(h(fN#b*Pm+OJ^>drqE$} zh|Cam&Dbz;0@kPhb@T9lB~3P-EMB^~1itJ!8Lf`C}KSKQdT z)umQ;MhK(FCV@lTZ8;tcgNu_AxCQ7_{EqK-bLz}EC~bOntB&y%XKwFqeF@Hal8DE6 zVu(t4-&0+&E$@Pl3OISa>Y#Nhkn(JBP~8XbWOuyedGhH-haQFME?QMdKRa zKT_MxCkHs1#Ld_h&EBGsx-q7m$P#^VhLRT$7&yvw9_>pd8p3-vA z%x7zuvV-^#TM%DE+}(`;7|+{GQ9#IvhHQ|ixw%oX>^Iyb3CsApm9l&^-D3uLKhArZ6Fv4l|wVH6FvfNWy9xnsWqfVIg1=^(%T4vL5ldCVAf7-UW26CAB zXA%5-18UryK#D{3-Ll@Dw$!ZCL`AZCyd<0D(=#{$l#A){ns|=n`TkcR!d0^_uODz< z9yHYl@+0~oO)!FcU3I0mp&bIMJ%XT_$SiCbkV(Npi40k7TbeR=8*a9FB9d7_K1w*g zC9*&`|2|b}rLN|T+U1G*y0%BWEP>aWqGvMsb_FoyG~&Q6h`%Gbn3^2{1ER?9X|2HH zVhngD=-*kbA&l)gdC~VQcEphDlj<@UBkf(bZt&W^#SZtT`;$XIv#R}EKyrug2b#FiMefd%S=syjvG?Q z5&?qLI9SFU{8QCP+~aj$rE>{q9QxbDo8hQ}uQiK5HR7T~nF9*TXE_zonFK|+Hvznr zBzWZFmoxEZJYsLZEbzxjDL#xkON$>;5lKtX$8bX1OAs*BxO(b}vlVji9r>Qm7c!u~2`{TJnDW=m=Mvx5Fzz+z-A%%R!P;oGUQOC-Vn24_G7F1{_~c{5tNrce zYX^p|A$0?Y1lbTi>AyQ4s{F*YAtdQ^W5V6VUKM+S(uZ5Et2D(t?5n5Abqu4OU-sZ}bc5U+%Y)AW4Lhv;s+;1@)M@VBta%-3Y1ou1MF94zQ`|y#4ZEbS@qQ0u zIM2*m)1iv~03!-nHE4~r;G8a^`KXO1U%(ADE?jN-YEy}Rw;E|B)oi;ryfHKfn#-v$ z(50vtE%<1^STD;dYo&O{=$5N6a=GCfPG-Ef{CAt6dfkaATXA{Vm?C1Nfl|wXJQEHV zbUHq3l44Gcfq@GF$}ZHVYx}F@UCSdGVWj2a40}<+ z`D0#%M?TVuW#raUda%oSzAKdQ{AOW2@;IVijw!Q;b?=)52zTw&j;eclEp#&q7H*Y^ zv3&lsb|yZ4q+_BA%>bllu?=bsTFQWnAwwAVq19E3SrB3QtF#!$qZr+1?8YgRsl+G` z#}T)d#2#79MyHd> znt+JA^H+!yhjxj-J18Ih4cas^$h)vF50^`U7VcfrSx({QE~d%46!Fei6$%)WmNW2 zn*vSDS(V+^8mkqnQyrnc3?JVRu_H~Ltfsiz)sx=bI#QRfE>FuJj!ux!qc2uF!W~CA zk@qee7HPb2LdIACXNjx+yRY_lI~`9_8`2g+y%HK|kvys`^FtcQ1MO{>Au-FHwi>o=B-4Cpj8Sa?O6{{%s@B9y zKwd?);UbDIj%NlL3^Cf)aql*PWz0UsMI97$h#5+y997XBNw^MMN^~c9DDV>6VL4E& zQR$s@!$ea3L00wFQFq6k*Kj!bB3Ff=7<(dNXHg6ClmgyqYkl5bGEhJ}LjG8GC_0eD zV~x8#(_ub&6{O1QqQ~8qeYL5dU*tqv#AWcqB6iB)R`)X(0LLzfHI zQNrqRBgD!sVW~MJM}t2=*a<47$29D(**CKF?Y9oA#;C1kVA1ChU8}A3W89mmUZm1F zkx}aXS+Rg0w}Galj}t@Ssv*`lHGd2@>{>y*#VsmWSE0whae%&W>70h-Q^ex6fK-bP zsH>kKTr%S)56sbu-HEm4(3Y^A4Fg6~l@3+{%>vi~hj6WpyrBI44V2gy;NzEh8>y?~ z=PK@HyJ*+nNI4QIoMod6=Y;z0QQx}B5;T`jP|$_Hw8_!_{=LC4lHR4IDV)Pa6gG4W z-nwxTx?*MDRMW1>uCBkz_?fVNw*NSnoAFg;74nf%=eI+w7DOuuJi!@ggeS4nGTsg< zw=HiAt9p`;EFOe$s*v#MZP7OfT3MdjtBqYK>NsbOE|EAd6fh5a)HUn<^9+oEDo4Vj z0{g<^-j%Hg{8Zp~BM2iFiw8H?C(M>4xFY+VIgaW=XK71rn)z&>r>l}N6$&EsJR$g% zR!lb3C`5MWzT%!t^nM$JI2z{3Z~?zI?Gw1Y#N8VnVlz@V_ujE)ZuO-cn)P*wtmN&` zmb?<_(+-9}Q9Y8(kt63Aomjw(e&A(c;JGY;xdzozP`h&P)!p`I5SRo;R7S+hVs@@3 zBWk=Z=29ms2!`-oB*py^wxvWcr}n}?LTs>S10KSlbP;U z^$s>0ob<@p;?&Cg3x(=%Yvud_D*7mUiub1XWjj{R0E(>u&qWI3cC~7SZUq0%hKxZ+ zr->k>G9-J|nn7;42CV~CibO8mYo%vvW^RgdYZQhvHJoFsk|Mo`VdW|;D|u*#f2~co zz#HFBp~JR#gGu4o3h#EKoX}YCN#PO7CFc>6IeM-yG7tL67!Jg`+mT- zOU5fV`f>^|+7*VL^q6vyH~I)oMdsTj1GqPl^70~2*N&bm?FA~FAEXcUbB9Sf6bBk# zKCHq0f<1aK6t;~gmU4Zd)QK>Gw!KtZrmN#sY^4`6LQY|3a!$60^I+&eAa|`J+E~FZ zD#3z{ifa`)@ejvh1QF^rRbmrFtmW2L!<EaUi*ve`XE48?QrPBR+IQR`RO^dllWy2O}e9w-QXl?jsi(tyj*pkzF z49hRR&}8R1<%gJ!sX6(LE5=|XOoo+NaaqML{z(e?M-Ibqe}K;q9@}=F zl#aveo+|!efz56^-$|Ay-J^uPTxahu#$S6l4hQFZhr!%q$lWzOWD^+;s!4&+smePM zkz30f#|O+<&)0&$db-0(U-ynV3}I6$GiVj_jS3ZaAh?jRiM_f;376kupY4-Nk3tCi zdhaspVQXp1iYFrkJPMgATx{X*Y5Hae);%coSnys$O1@a*ro*OJ4u^LZaIW4zJG<-5 z!X1juQ@ua<6sQNrq8Os8dS4b9z0bcr&aFA)9#Mo}u7KZw*(fX9Va}I!P1a#=@wcGw zpc|^2*jATmuUG%6l0Ok1N5YYR0jsELf-Lgjz#D~Quesx;n{dqUx!EFjCB9Jwz0<7T zZZcsmLt+r2&)azoD3{og#5t@@@?113#%F~<%fldGUPbLquY7Xs+qUvT=w*(1^+~uN zi7g3A5Q$1srL^mcuj+C{<*N@ggKVO2Cu^vvxft-CoAP*E*hYSXHMA6j zh_Wpsjnd83v+`A}b-HZ8R;cd+a?-M=A{WNW4g}M6D^~C%XwlF2HPs$=cVKa_UhY-a zooF!3_NQTAtuLG=ASbd*jnkL2)5ufdE3rku zV-Fc`z0X@VE2-6g(5To_;EgA>yG*BodI6T(;wu-z34CoaL;N7M+mSTQb92kl4IJXT zp4CbfP$RYV8%K2E2}=(?r7$Ox3t}U3bcJ?FHGInQ6heE{bsrI6KHDg(1F>xNw|aOZ z^{6j_2nAJQXQqo2wz9wsC8db3oWJk_;rpv4yogM~ES%aww&j+xT<&!lK7qJ@BDy<>FgZ(cEX009?Ksq zgY+A<);hj&;#EDI33V%ol^PQC?Z&f9S^L>3`0NS}uon`%_)I%_n>X=vNAYzeE-cQocMOil#@8GBsabId2!{pB2$-WMM zmIMzNArO2GhMV^HG_3lT_5+J1p2KL7x@!Ogd!lawUd<==+ zw&@>I3i{JBox|!3a!EWk4vH$-Kdvc(6Fpu5@vl;?bJ4D?Z)$Q{e~0N*s~5mv>r}N* znyZ*@i`wv7Ts+5i_j`^J(&HW0R^+9NOG{H`x#c0m{#X{y7_hbM-E|$0?D7k_ofpw& zwMCef_f7q(koz%0C8D9wnn46q%Y3d)BpBKN<~cskSh$=jmMFHZ7CWXbW;wmw_ief8 z&Xme7H#CN*Q6vuW^5tcyaOx##Ed;$uF9KpO!F5ehVIBVrLZz^HTz{ylDj((18lyg` zE#;vCNuuOFc4gbhTG0GTGX&mZ_e9T~q);M>4cC?H{^*RHA)Bm7!9ZJnXJU>Q+kaq@ z0PK03a?7meM9(0UUnHtd(+O_v8APRPy?9ZmE?{!tj&5gfi$E>jV&Y-`qu=Ha8oSt} zK`$0BG*Qcn*0?nT(Nm#l*zfj9*HQKD#||<=qPh0VGfo3$=6W~I)^U+>2y4m~YrMbT zH#9Pd{PZ<;D-ukPwTKdr4}AFMT`w{hq2%ICzxL@Vl45CO_pm_`71(H41%>SJB9Vt;cwO*o z)0UM=$X?;Y7d$Vm`nQhUv>4u$xBXI#eB#X8mTmdo;6Zk`7@&BPgy5}mnnC1U3aMc& zgNnImxKU97p@wu-hP7O3nzww-7pjsV(*1WE@~{d(?$V{S>}RUh{$;@9b5B`#+XaLk zOlOq~Tn>kOO^`mBNk!y0Qd<;d59;&6A)hfj`O{A$g9;fXDWVfN)E3BHpj{KQVJdv8 zpmD|xOw+MhHFBHihLp#ve-7OWYcctmxq1RJhe*FaAByzvawx$>ByL@ApX#hLXoEaO z@oqXf2ZcOE{7tc(%$8TxP*9%8S6N8}QV-U+Do&M2rC~%Vn-mdK4O&hQ_O^E)QmoA+ zW6P-)wHG~Zb%}oU8XA%iY~RQAj^Qt>*5OdF3yX}`!g8`mWnjUV;#5Svy7OEr{^HKq z30N{hdGG`&xgmtZsOO^X%w;M3h`snD!HMm{i=Xk6&Yu_2pTAB6A4Gsbih8d?wBov@ z+##at003B+jhL9SjF{MeKdJZkd|s}9vViokFyW|?nw*3#(NEw(QW38XYQOU`9cGbe zC6=xSbLTIFY<3D-#)J^N!NJ5)P-tv(9dca=@EPpj=&baNjPiqtp(W~WuY=^~THtyI zOtg1~z1j|O-hES^^1zg;n-1CtO>~Q&inDorPLP#yg?j_xafe=TK+o^n=iZ!-{2q z8vQ%#a7#2-R0Wqq=!2k37?wrabFTMtDFV8XtAWiu!D+}e`1iQX^JRXQFl~DGhQ+}e zYul035fzOkTB8Yeo#UQSoiAECm-+AEMY(-VGalJu(#B|VNkCB%EXr>t>X(DFlostA7@9zOhXj3^Ualq$4ufpDn z)V~Ovv$T#I0Dyq;&jkj^&c*!;!nn&QNWdIHAt7^;G1%yU0{}pPjJSxp_r}$BZvw?y zUin?BKyL6z5q9f<@H9BIu??7s#Z(U1>RBHq{;}Onq0Ua1T{WU1{CO>ZmM)JTB*iyL zTw?<33kY)AMoG^UjrHWLJD0wL++A|^Z}ytwn>90^(G`k)c#Q-RCa`}6VBHS2|9wOK zs|sNHmp%&a?*|qz0AK_X00_$b4=ODG9~4OSAM_uGf6;#&{z3o$Is9*U|JMxv8;5`7 z|38HOE9w8l;op4!*S!D7rTCwD|37N?KRf)tx8OgSm72G2e$rumUT3@;K0JN0krMNt}&bi+o3#fngC@lxQC~=+1fez1N%FRd^M6*nhMU zoPc9F*N|mp?CTEyR1Em?(k<~h=jZL8jp8aQ?~|H>7<PEU%l90{Pc?sUX?N?t^ie<5KHtB)fHmd4+`h7t9ED{jrA+HE z>TLT4%*nOg%tWqLya1_3!oKCsxu@3eJjiVwIa&$!2+qRrtbaZs4>L0d&y^j$APl+9 zKOr<&zoJ4vfIT$23*rwZ{JYq>%SOZ&ex?Wt_PkN{n|~-X2QvS|ZZ_+Oios4vt;8BB zD#p~G{$3>fVf=4cT2iW`BllHh>$Bsv6e05&lK&=wawIHQe-0NOsrOy?J`(XI^k2E9 zf90m0j}!j~4*Re0%Q)eGV95UpACKez2afu$@Z|%~RTWnpQw)2$_njocNZvR)+TS<%Yu6Wm z8%W)`tTi2TEX>x6vrq|aBl1n=ih)v`f<^4wn-`U zuB^u+UQGQf@3aRhvAKRP#~CGw4|%x#g#U)C?|ZfV#S$gsrJACyc#?a2ecYZdB*kr; zguzF3FL$!ZLP}Li(V9!ov#>qwL>U-u{q0+7WMt%%BBG8GvXM~O?QfZ#&Bz`Bm=}qM z-JY)xhU4Pj!!q5OC{>QlXW_05KUN>sU?pjJ@HR;&yX;thW4xC99p-V0A9Zjnq?C;b z`y)FZ0f>z>q||14DK!GAjf%tn8t_v3Q2kxoBi!ljqFr-|w3wa8BMH&(DKDP#QE;>} zF7n3IMHuNJ`-t|LwB**9cx5nTMyHEyZH( zXiMoN@m}w{5Ou$&!rw4DDf{3S2B)W}k;{0eT@*S#RHt2t{~D}OF0lR0+}tr;uFEU0 zpcy8Gb^8CNk4`(M@-{flcs`PknUNDnITA=!`x60C<%@hDdt!8ve3tdk1arLg=?8RV z81eQWh0mK>DXpW){H?$-^}WzXq_C9yc@o7{mi#$0|CAF)plswn+U-M3 zaHlMH)2C6|@8wSW1O9Yr`~9^49}PL8u(XU$J*@pZZ*mhdKWD1`B%2GQ%Kf9j=N|=1 zSBd{9aAl?bayogZ8Q}23b4($znwytI(M{zRfFAg5SNDT(z12=sequ5=j*xm>@1^zR zG$~ab&#jAG#)rzS3*m1SrVEyc{;9%|`E{dOKB3d`T7H45qAKy`|0o2HjQZw5Hj zzv~{>q9p2+UVpG88?o*t{9R4dJY|2E+?*cE>=6@MoW5#P3ln;+6>IyhGf?xpNWj8- znLnBUhUsEnF2##^;=T3Si<>z4DTm7HPe1W>4xNY8r;_*D)B0q+f5Z^*$oMc|lC)p( z2F5ra^~Lh_OzB8)HtZRFod7K&1Q7I}G5GhFv0+C_~2q*?20fPn!6K&mGk9nfF`I`qua7`{P>Pp8K4A_TJ|@ zdtcWb2etLzhTXG~BL4V?JkbqvM)sJ|qY=S2Si}hw@~a^f$NV{b3D*IGZp|W#OoU#CUddXJ{>CB(LOb9Ou3w$qG_V3EV9Nx0Kk}B z0lGBu7=ARi0u|@XfzIGh$r7jW(TqXxK9!BpB2;2uF14%Z+CHbexm_Rv4f}b68Pg!F zG(qw-zLD{1yw6$J|G@DAhK;cXtwr0kbFdYh3RCYNQG)NNK+ z_Eo6(B(lS!0T7smG`eZ!&D+@-)2_bNo;Qw%P`J;dmLm)SPJj@ zDuhfwO?S-x&NJ3=PIsEzDZvS!Rk@}gQE|Qk3vhsALOLoc4+P0#dE-^mwtV7 z^1xCaK%po*ZQ<-lQ+dz8<+kq*jHU?#I@eS?1APDG$Kx^9)mY$;n@>gV$XGGT1nw}v zzs(_wj(>ELIX;@y*Q|<8n2kmGYncF#Gjz7=j>n7BYWsNCkB5msY1*}d+i+iInVMr2 z&Yv>M;YrujwJZ;2ft1sZreQgFFLF_ooeu?vwsQR*)NV7XwJ(Q=V1kz#LEF!%{)j@y z#XRe}`RSF&pz%(la8Z01D%#)hgig!lR2H1qi08-Z3#}f5-NQWgdAIGChcmg{r}kxs z(4CcC*C@ZDgI(@3mZ|mC909*hqffd9RnH$xhiszK_7ti?8I>s!dbUvQ+d&e??n|Dt zDB9BCz9RWo5DZqZ1e@sV2lvdO85yuA)W`YN_akUZNu#HB0i+1KQZ|}OZ&vk+UOua) zf4CCGv?C8Br$X8CilJ1zH3Uj3eZxaU|KwB*n`bp=Um*Cr2LzX!VPppF>;;2V;Qf3( z-JR)=KTPngZvF`qh5ot7v7Tzyp82h zQmvRvAieGpzw|Pn(El4Q{ttzQ1)1#XYJR=e$;ruJ;V7`{-~5#6%#4hX@|bEk{H!X> zs*l$$20&$mKd?qAkWu74`?UFavG=TUzzCV&eU36D=pmY#zPN}9zVo0oPg2;G z)yM0riYf0O1Aod}$5d@<0tWj}aS`P`CYPn8Ftq;w4+-}Wd^3Q~PsjMxxN$Dr>@0WYeTDZEw3&6p-$iXKE;gTjuDX?m1O0X>8%?u=~J zd3k^^qruO0qrZVifL7l}D~<&Sw0=2<(j=qlS;s3bYS+DYjBP;K!Y^3bRXl4FgiR%A zj5O;xwYgI&pS{XJd(_&{>Yp?8bM2}!RrT|?&4b#_0tdhJ>WEbnAFZa!)>3q+BWdk* zr~^I(H?@wOXqEv2`9KwPebjle6JS=~lF}GEZhG*q5p8zaiG}{l{ljsLYVJqZL*tee znF{)r6OTAx2lB89hc4jic_=rEG(WeG$`ryKG|7Sw6jgSkz?#^u2w?`dUK5=V`DoxK z^iZZFeAg1JjAd~5;+_P*^J@C-AbW+2vm?Yw*PO9$fKFq?!SG@Epu|SWBTO{R=S|gi z>ATl;TZmkcwj@_PXA&faMl++PdAIhm^8^fXHhgbi2A5@@Do9&CM;YU%mA|qCWiFX~ zK#>DgC+U1Nk96aBWg;|Y6bgl|{-s}7O;9MT@o-t4K4{b!AK9n$HtRb}2pm%v03_@v z0k{T6V$;0gwD5$d12@y`M3iR$pnkjnj9ED| zWghHO2f&L*`lwEXpwM~0{@s(TW)DPbzWG226ev&G&mZ-kh!LmZc}iS_58bG+Hc4>@ zTF*nqwTV=pMO*=4@pt5BmA~=~!uFD>Gw1d|>9exAyTbipUaT(7kQ374`YIY+ud(2M zgLdVD=8|X6O79ub7BX(}ioBnHOHxzui0AWdF4*~uY0es@`DG8{tm#%c#F@H_PNEQH zdCM(I zR<xnlT|m%J^+=U8wQ$0`agRXa^-gyJ$0cDqx-dI;ZbQRx{+7r z+pJn$&$eYo*cq-gBnCkVhQ}pWa^t1ii=oO76piF;(tzTyY={k11Emmu1=ZqrfN_tZ zG89r))D-4ZGs9%F_CgMM9YsCOnL1+(5G7C{m+^Nw3PXpb9d31}tcx^8B3zdg>K9?Z z^v}rH z8Pg`#I6aU451qy{6z=Ub-FwZ;>-eX6W@8TqW1uz#_N`6%z`Sh^d=9kn|hNjXy9ksvDLQ9>3r88 z1jkwRKsXRBkR($TrV0K^R&3s%DT&9Pijn~}YXssbhK@`48!o6CBkf_v4INaZexzNM zv#yZ3?RP*W6l_O}aHpnD9+9S5>uyY)Gbz#K7hBIw@D4`$XB>mv3F;2})WhJy2N05S zh$k-h?S%4_KB}9G4meCd@CeWQ*`P(Zk{7TgE%Df}$QH9%W05VvE`P&?WI=|H=SP5g!XDXpuZiEW?mMnSd zI-_~1)=qJ2N=78{TVNP0I(0wdkaM{fjg8rdvwb!8%8Z(BM+Rk1#4{0k_38yffknvI zf5Qt{3h&SNvef5=Fnj%nc2yIq!n5~}n!b*vv6>7fp*}Ztt+eqQ&Bi_V_ zXaM0$b5WK?(^M?O-Jx=|F4#zwAbAHM#wAfrOqSH>`qrt$vsfWgZ{FkI7)|+9>5s?$ z7VCyU7>=JDh5B9nEs?FPHGk$cZ!oMS8qc`x`oOBX#Sctc={R~{#LbP>80j{vU&Z6X zT%?uh4nQ7=f0inqBvv{y#-JYmkK|INsI-w06jq|-@^lG4ek_4><& zm%dPT4k+m4kz&6-KV)aG$4l5GPIF=@bPq3CTjg!EP%yE_nKz3BrDj64 zy03g)4&dt+*Kw*)f|-d*fz>J>q=kSrlQ*5P+&A`OEn14g0(NWWPMfB=JdErtXO!i)Z3vP8jH$|9~hV^f0BLbW5^(W&t zWtu@FnREu1%pJ~{_6$U|+<@3KBQ{_mU&l}*O%L*+#TVcXA_vTj{hRKS!>6;C~RhOkj`$fg-tF}QDQ`s731^46$XuJNm1zvGqh)8Sv z-Qk`-Y09EzKdMS&b!(5v8WuYtk0;6d+uVid%^I*Pnh8@E;8yiiAPKOhG!WcSUj znq&B9qhWP>3BW@B>M-d)R(JB(Wgck| zZcFJ-Wq*bht}c4AnDJ{zITT;%5^fx1UF*ezzm}Z%;fFsqj{bMj|Am#_XVoh~9Zo`_ z+6mXld*Y?i@&Tx>W#OB8Hd{=-Xq|kK@4WC}$)y-O%0=Pg&e98d)xYE&klXK(st`_ z*sXWTF!Y=Rez^eUTr@SZyvd+#!hqpZ&nyO3#Db*T$l?`B2Mqi%A$_5KCR8--l|*UV zDtm9po`H?GF~KY~Rl(uMJlOA| zt$qEhxAp%{2gsX!tv**gzu;Z;SzntA8&;o5`a#cf-t}Ai+E6=zrwQQ3z(yN385E5L z9^mwhz$yK2d?O?Qz=x>5-r6$;esn0^MFM#*is6Od$%LWz9ZLq*y#@)y$rB}mUxIi* z39-}r>3O@L{Oosct$lM;UxR6&?QN*wzD?>+N0ptoy<)FY(jsjGf)2?S<>ZD>=GIFF z?X3odmnT*8zRsjC=a-)V8mazWs&A3Z%i>vpjzH)el20?L~D33pY5t07MkbBRj- zpp4kGX;a;iV6oK%`#?l^ANCOpr6!#_oH*5_fA4c@ z*O>qEr00^C_q6|9xNvb`GE>+)F=r=Lx-xlB?%wBRPqwWu&&T`tegDPxNsEugX)7$2 z7n-Z8fVmV+{{TnWQTTm1oHC%b6!AaW0?xg;>TObO5I2dE?=O&ZHdyFo2i~0RniC2( zS;`A^)x9b$?fhm()d5T1fy0`U2?;@NYRcv;795T4RR1eZHlj;7v0S9<=Qu!*6>t(J zl=^qW7A>VcO^<-9n_5%JZN#t?PsJ(1_E&JkPb-X%CTa;6^!7fpt`9c=ALglG;Gl$UD~HS4VP6B~?zk z;nl{Y4=?LF4~WyoQ&`&Qp%a)L;!*nv1Oh&B&LnYVmwH@1GS0h5+zlUgs}<$7=u-x9 zb`4|$%mGzpbry@xV6>_a#TPKCX~bk#4KL0bK*{r>r4$B)Eu|CwS;EJDFN&M;5pzOvKSeM!0`K7kmJ2)fRDY zYFO?zOp28RQWb&Ji_~;Lu!0k@H<(nUf7e|dgsTTco{FKZ6kwPXF9{?Jk){VfoT}4M zHfu_@OI2#@Iv4=Ybk#`J=@IepWf79e4XhwuG>tBOCZW(i+w0m0WZ-cymB>Ksb)>v< zO^~q~8jFOPFTpr=?6gGe7_^=c3=%zoTnXyVQO~V#c^e6E$2Yu z`I{*+X)vAL-f%{|%~c@I!=Y~hAbuYS%)I2PK*iz7?Wb;WbFl}SG@)09{ms@UOfXam z4g@EHrtA##V=2yD4cx z*s`(GDzPNW)zdS-fP$^I$8^aLJ~Y=jZK^!ZC~H;!7gV=ba1A61)|`q{0^J7b25{-2 z79V&qIBdTDVAOM9v0Umr2+`G(5E8e`NyC*9DXaiSS^LnQ22B7a!sVw=TqRvoj{y}+ zYcH&(3VTEP?8r%r$JQ5AV;E(lcQ+S+tu-NoXP4JK3>kr*yn^OChl{7u(ru_EE~nyL zv5PWfNgobtaN}=yuA6!q%N6E&PLeFl`ubB(+wAS$25dIDhPL@4f8Bo-Vr57X@BfJ}2q zGt+f$c*0oh%&!o2n9UWFxO&`zh{Ik-2X_EtoL)jpo?GXRC(fIMg||6E^5`BImKPAD zg;YkelpZr!?JYsAU_~zbcj`k|DkLyK$G`%2rV31EYeB$aRnS;3=Y%J^QcS9V2o0L* zJfJ%dD1Ht>!vr5>Y?(|wh9g#Bhh=F>`hShkc~Z18Chda`ycp0{F;p6W^w(RW7=-NO zc)~0&gZ;!I*_;slE0bmcd2qg2q&5^V1nsWxQl7}H>CR8uvb47*iY8-u#ikwDmKM5( zNt4mp0dxi1Tjf7CD$aa^^vH0}_dZM<#{Sm#K{H`-vQu1|Smi9DC9k zVTUHq)Q+6&PiKgij^>leW3kUy#B5SoyVQf5(cD}hmc2`76j@T12jKy7{;!3G7V%pm z&F@5<$6p(Xkr}Wa1;PDrf>(}MVOL~E}?N*{@fAd@@)b&F7kNhSH{ z?Afc0KznL&nt2X7hCezc!=%o6oP>MeaibSz8iBW& zT&x7Z+61Xc3$kQySSfl5W~OOWj!9+7s>d~>kuEnPI{{|16wztNaY2=E47e@JBB)dy zIKscQ5qZ_f6z+z{fwWtrK7_$Pji&CxTzaG+X@VzWvE#r~<2CxlOAqt~$5UA_HFx}7 zXM?r!Mf`qcM>g*WJ z(3f7?JNrSiWkXG3!D2U^;kNwY@z$t~hP=(GJf%E(fOu&*W#$oaBEPG>JwUjm-?jW^ zk`pMLdl2{pX6w6e(WP5BTPQrm18HOM>hCm+12Z%@)CQgk=PgZ6^bu25ImXMH;+}HH^cTeFTg4P3w%1ld3OT((n zvNZ`&RJA^(c}`$(B^HTAA~J1@*CqL{ctr{uEi-KcZ!V9^B~q*x%-C&qjP+xmJ-e31 z_0Vz@!5Amdxx`b7!mqN6ma-?_Q8mF%lL>7t!*@TUN8x>5S< z`iuU|P_{S?Ud|gvEcx^o75>?Z5Dw9ZL?EgpiA!C#`%fs1B~J~1d`1-3Ko)fm!G<;w zre=>F`JFh`&GA9Sv+2U|6}7i zUc^$z5x*Dydk4aWa~&JH-0*o-E~&gn2?OlTVD9NX?==fvziv=JdY+IihQ7M&RQe#O zvO#A(hfb5!rOkU>OI*Romab&ga`QZm0cz;IZqd(|qZA1}ji~WUV~j)(z~>qf2u#Kd zTwy~{zq)wdp-FnH#g?Wc}*gWfjb&UZ=E%=EZjnnP)T`qQRS zlfTxjO3@7ZAo#CfV||6a#dH}w@fT!0y5(`~;W4_e+q58^Sz0I!cKVV|qJ1r8w}C1Q z{72|)r2s!fcezm)<-S(HAFkOjxCid-p&4EBtf}`1#NekW1%It-%OYqDT^d$LOB>PF zCLh;~M8lpHbk+{t$Oizg0(;g603M|bK8i>OKoeKgghG3Q9}EqUOo$rb+C5$i?!jeA zVnYGFn)tgtt$;k{k*slVcPrO~7+*zO`D|&n(4{@m(a=WlJ&hGSad@ud z(?o1$A@q5f3j5C;r>^2KI4;1`n&8%pE=-yRG5a$pxUgQjG+Rg#aYYAvW4)8Jy(GA* zh^%ofM#!KJTpQt4u0&2%n$Nf^D+QQLu5*u(#Q~&30Z-{JU1saVW?kCpWvaRxzJUtG z1T0tw#UzbEV(08T)j|&fNB%5f%FRJ>t_{uApM2yhDO`$sXimlv?b*!t(@r9|4>1-y z)xrn@-z+o(Oa!&tlp|}9!QxZk*Og#7AgcKu}pUZ8L#-%~P=|EVvfi zKH+c|kIm@VpaUN?_#VdzSnfrM@D|PBPRMnn<@wAbsigbOx-^jjB!zBa&|mIW-Vj`I z4yx77JP#3znY8KJ@<;lU_x^Cz=vzAd1lW1mzL@!b39|DDK0bn~8iear$OcJdj9u&E z14oblqoia-UfZtgk*x6O6)vA z5~_uXpxFktYB!cJ%}M#ZhOi@RwGmP7AT`HY^Ib=yi>6OPotOBdodlL0c^uAcYh-s<9Y$~zi;p?TMXi(4GIAi*>MYZ6iy|}>=lePhzC+=87VsHmYm?F9s3XyAb zA^q5?IP&e>c5-&VH~iy_NiFlqvB6%jLz`5RvSWsWh>DM(KIV}eV_44C7X8*#y;RVE z%s!!TSgEz#pS6Lfbw^_%o|}|IZfHFr*dZAOJ2+VdLzwu$PtTgXy;AI&{TGN7_$wsj z=^$GI7%wRYyN_co4KV{|j6NH3VK6m$U6>6@dZ6tb_L3YLcz@JQTo4&2l9+%V(%OLIxbd4Ngk zLqMPVR_bm<$F*sumDp*#ZPY@i?i zw$7}`Fz}rpgn>m*fH3lbtdZ)FT|fcfHu|k?8)RsG00F^^0un(@f$CVvC>a8kfbSo@ zqm^Tyog$hlw{W-U!@xLY1cr6~6c-6gQ*CrTR?O>8?UnW74r(Elf#w3%) z1G*?@Ml&~`$3LDijc*2_Rg^H`*J68tX$?L@kZFj+jrl(u2{j!G)2c_I5kFe$i*?@< zW((bCX=K^3))l?&v5*qBof9Vd*yef@-~eP@K-Jsyu=+e&K0ra2_Sn<(+Au>4%BxDJ zAX(^xn#VZo+NHqW{{+g-N!0}(G_O9k(>Oj8Jc76-H^C>3$jP`!)mJeb--;i6Bim9KNh?_lG(7PU63 z4gi&#fkpi0NWeUd;NO_6*yIqY&s-ZWrjo`pR_!C;#7UKi zXAIKslb?#RxB~ROpnBa3+JCAEE^++648X&WPTUmP2jY#McsE>a<>X`=8eHpQXr$`5 zw%-K1wpW*yu?QQ$C>|I04ZPJAtM=@C@(LPZZ#1<^d?v8H@oz+mxM*N{U; zEe; z2SE*(vX3Q(Y`OD9OHIL~kM%QBtlO)OwM5|})#Khn1IynhLNeUYIXw7OZU-jim_*!O zM6NM+QY~1Prb*Pubxk(c@Zpd~!S}P4@)*jti4E*&;FGDi9++9t0Y~?Ju)HopLFW0LM>nW@=9|A-QwlKT**79oARB8-u#sFE6$NTk@YExCH zm8W0dctIzf1=>T6N?9QW#lE#Sd{ulnf>S^N@c}N~W78AUwfK>X;hn}5PCxcV2!ZNCtv z#`cz)fBI;7zv@{eSndi_+V&bGO8o1keQ8zF!h+`}xjX5@(f?E?Y=JUixvEU;O#G@$ zfX)7gGT{VNh=c!7Cj6mi|5PRtA!TCdAId}}q)gO^lnJ-JVgFPnx<$$ayY)-tz9|z2 zMasl?@%gGu>=Y>ze9P_sR3@55%EWg$@I{%B6)6)_&q7z1{#%(C5-Af7m(>5MOjw0F z`#lT!GW?q|A&n}T&DgV=F5nk0`+k=$`RTv@DChs-hb3Qr_VfS!$3XuVKbiS$o4|j| X)f(mfx_x=T>>sd(cua=Dg**QPp_mo+ literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/border/border-image-fill.yaml b/third_party/webrender/wrench/reftests/border/border-image-fill.yaml new file mode 100644 index 00000000000..0e27b993c52 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-image-fill.yaml @@ -0,0 +1,54 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 500, 500] + items: + - type: border + bounds: [ 100, 100, 192, 192 ] + width: 32 + border-type: image + image-source: "border-image-src.png" + image-width: 96 + image-height: 96 + slice: [ 32 ] + outset: 0 + repeat-vertical: repeat + repeat-horizontal: repeat + fill: true + - type: border + bounds: [ 300, 100, 192, 192 ] + width: 32 + border-type: image + image-source: "border-image-src.png" + image-width: 96 + image-height: 96 + slice: [ 32 ] + outset: 0 + repeat-vertical: stretch + repeat-horizontal: repeat + fill: true + - type: border + bounds: [ 100, 300, 192, 192 ] + width: 32 + border-type: image + image-source: "border-image-src.png" + image-width: 96 + image-height: 96 + slice: [ 32 ] + outset: 0 + repeat-vertical: repeat + repeat-horizontal: stretch + fill: true + - type: border + bounds: [ 300, 300, 192, 192 ] + width: 32 + border-type: image + image-source: "border-image-src.png" + image-width: 96 + image-height: 96 + slice: [ 32 ] + outset: 0 + repeat-vertical: stretch + repeat-horizontal: stretch + fill: true diff --git a/third_party/webrender/wrench/reftests/border/border-image-ref.png b/third_party/webrender/wrench/reftests/border/border-image-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..0ae931a77774106dfff241fdd81b6dd3d89c6578 GIT binary patch literal 4044 zcmeHKX;f3!7QP7)5(NXnJ|s*LWUwk&5riNi3@sp-rVfZkP>GhxpkW4~5Wxol4G~cg z4G;@$vn+$rGK17Iq$m=GFa-n!5eQ)t0?7DI;5}FS1GN zCOIl?#%_inNXgmB;V1;bIN)U=~$;uXUkbR1&(5)YcBb+L70K*E>*nT#paoFD&KWJPRjfI_L64c-rvX?(IfKi@A+*N z?b4ya!NDBp&AGM1F)-*%Dg;yBL4+KT%8&{iLA?I=@_*}%<&;%;S97{a%-YB4lodRF zpIbZY(c;G?!SwrT^a*Lzqh~9tK^E_R5%;7oE=lLV@r=ov85*p4E#CIig6o-%nC)W$ zv@}J1Czk7U21D5w*{t4M?;ZHo#i}Fn`KkSPa7Qi=nyTzbLy{*a>bwI}URrfL19YKT zT(Aj2Rb@a0Chk5OWnfrYOT8HP^a6KA;nFS{T^lBD7cIErs~f3BT5Ns$%-v4DGDYu^ zWL6eakelOT?bZ(z9j~^%YAzEO7Hx2|230ns|*AR$%W z_@jI0)~4GJMO#`cWTvXZ#C2}d8Jbbud<;8G@vc%USTU4Oz=a95R%L%yt*rX>;L{t;kA7H-`DN ze>0u2r&6&V!_HEYp1GBrByhN1;7mUq92{kk?YWtXYRXUwv2k+b10y^2i22TS-ix!I zTh-FPUrB5(3b)5Yx@`LCcNCh$&!|6E?`!($p#aAg zlUdDpvDyCP4eqQX{py>O(=@74byC7AifppHztu(Kg^Au!6oPBmm;82r{D)9Qb~Yrf z*J#abC690NPCl4Rb%6`@t~9)^dbo&e(!A2}$_>FiDfo&z^YV~ezb*Dr|C`1LbGv~W zO%N*)GII*UY7e~?k3UI!pU1qlKx0AD%e(B<$|^ww4+uZTnR@R6oAuzq^7FdEvP5`h zixuC0o)KgM$QT&@Mh~(qZ*5;kq%HO^O}h-JcpML9Hf-CG9^B&2iaT)U%^}|;Km_%AQntgSA^t+ArjgKITfWv#;Si{wEGpLYFhb@Z!fMHaHz zd3j-)I!CpLRls=y_VmoKpqup;8M7kol<=n*Q08@2WoHtx(vD6#r^#O{gO#FacYySY zK>mV)ml6zrtxT15OxZCbvTGWu#lnN(K@V73jGRV)LMX07nWIXB>o%YmCsE|#iE-;t zTr%y-Bu|bP4~0y$IP-Tgs;sa60R%BHac@pOe`{@@wm;YfDqxsRcM60LXu*ZNA2R_X zD=k#pO4$X$rTUUANVbxTuWFbIIBgY9a(;KXgb3F4R0Uau+}kkNg^n)@?FUCC6fGrd zF&q)xV*<7xJGM(?iZ;`xmpan%(#B${f|swr&ngr*p~4|q!cyKLYX5=Y+6yM|p}68% zNdZs^imQ3IA@*ln@rM^RHx+Q8s1iK8gC#`2p}R+bVIa`A){pf2l8&DdX91O43MVW> zWgETwEE_B~O&5e|dmVxTfJrk=X+WjNq#xj4Mv=YOFX;tRDkUsT(@J?VJdDqilWlxb zhpOMjIO#t5L`WqpG7Fnv#2f}$eY5%jEy6Y(DA(XXh8amGJY@=M^NRE{2~Ga zHV>Sl5Pl7ZymX0~U4a!mx#_&UB^p+;GTw-%#FmbKEWHc_)hUzap*RD$@R~Jg56L#K zh+mVWfO~}CZi(~*j1Cd?DNpVjeWJ%kAghq%!8wtygqX+c^rR~MaF~9-WZ1cG7ehai zkQ)x!(Xl1NZomZ%Srn!g66V8Rw^QOw#}?0%z;P*6VOT3=xbU$a$%tgz8&q49sKD`6 z<-}|8TDq>44l|hbTZ8@K!hMFg+iZH*1ISFw{l0!{EjwpU-Kx0=Mb1e+lwq+cVC;_s z44T-fPh{c)9{`t){oxM;J>Wv4bwP_*$ZTZ90|QDl8%JIaljX*iZ%K+2mKcorDNc$ z4j0~=?5{riA*f9rP1FQ7%#8PxxI|wTO$}$qczy^<-SKCg@#h)Ktb2=xN!|6@U7^g#HP{GC!M!;2Do0rNKI@* z&udDd+7J}ECb{cdi46#uId!cJ^zw~DlN^zSmOx#5n^H9o)WU_O)?XWuZ1!|7!5AR0 z_2XqNT`}%YhQFe&+0wDY2#4FM%8?jAFKPpHYGG#ms{LIke zfd#Ll|Z`#DTppZ0*clHl!M z)(yOZBpYs27ceO?KU8B0UD)MQ!dsilfI=?ZNWIa9E)~rks@XiV}QKsd6rV@X(rlPN#2Hb+-G;LU!2KE(B zSj_1ZPl=O(mB~=Z$lcVNZD>k_6ov#v54uO7#0GSay+Hae-D6gg1q56K6P9y2vW8?G z^h3bnd0znmdJgspO&=t$B;;E^9qo#dwXdOdS5lNYr2N+*5*x|+@d6M9&;h&dr!w2n zk*Ag*#5|JB@9?vl)0unnZ4bB#0}5$ezg?d`QHvr917eph_4C#qJJ9j-Bi9FKYx>f( z>2H9N-W(<{547V$b2@)%$7PXLtd#`4-ku1FO&%B-`@&iS7T+4pYtzpqLYcqyb^s%c zf+%2*8Da#nk&-9CTquc@iM^h%p3~{Ta8ehd27uUiC1|2-pw$k)0;jbQt`Ii3|DGCP zK9$051y{hHV*$LaO=p2;R_(dlv*pz`eUAhG4Sk8y`%P@Zwt-{X76NA`jmK#bbwJ>hOoC%rW%q;5GY;`MC-I0rFW(zD>&d&*sx__{(ZyWvaYN%tjr?*?+Z zVrvy(c!WF9%Zs;VH=s zZwVLv=nWVnM_KJ*fYI)M3t+L@%yfnO!Mda5XPqrlLr#hviC5Y;V1H#*{{@bjogRE>TJt--FHpD160QBsi41HL{qEOavvgwuK|qq zi!6&@1gp`U)tFKXCwo*=>QBhBoX2V4m+Y;~JIa3^41X{GkKS0_a*38?aPFXg1o%CN NoWCSF6xf}M{TI2ky0*aKcS?6RNOyOA6W#lqecp4f z|NpK}FNe>waILxKoMYVM9`~4jm5~xbe1`K30s;b2OjJ+~0s@){0s@j69u{2TpqXTc zfI$D`_(9QH?z0_{xuuz&fw3-;wS&1Xk*>Xg9t4E_Okuo13@)3k&x1C)IuwB(HQWl@ zf=BGZfoGJmT*cnLyp+N#r51RYLthS})d9D=4V#A+>B=b8o{DJofDMYXv9=|Fla!SE z`*`VtgPW%dod?5$q}hCA@s`R;uJMcFo!CV194mL}^~s$3MnQs6lf!^fRl) zi{ndLs`@ugaf4?K?8WPT8kx>FYZS%0^H54P0}&>;wtMv#x`CA0=KO11-srzLCbST_ z3Svt$W9MbGLpk0>RJ2-T(smqL(Vp+mF5bm3@7D**d(nTRTzeSpy))Ce=Xh&(Am=a9 zLY<;WU!3aBh^Q;&#un&3b+=B_1o2?+GJ4=fN&PWz{(9Z~w2B}09_i}iTJ zgSjw)r|i$)h5Jjee6$>0AyT5$-OkAtMhwQY>S2pjwX&+&kkt|+kCih-?Fz;*`d$;G zY?R*+f}vtq6Rn_P)DTSI2m@{Pw&8rjiIB9qB^Xv4aexpn?(@$$y=2{qX}uI}S#GMr z`kwFkg^fRvI7~IQV%6+sT|%|@-O(tLoO|+el3aggO_g^?rH`U(< z7fiDlE$hDD7*A1J$<06OM0v&9-Ghyd(A{PK5%%?FYT^~F5{#}C+X>eY8XH}A!CbXT z4;H0FCx7EPquoQ-TvHTF>}*q1)uEg20Hw_XVVqUwezC(1h0^S=nlpy;E&h%%k=jrz zVf_$|gR2v}#fJ%Ai;kKAe29@$V{S6@v{h`aJRPHy`CHGoJG&Qit;+K8b{lc>#d&>- z{;CC$i4&Dt%2raCmdSPluJ8E1Q>@QU;8WUpTE+O-jDEApDfu!(=3HDK$EQKo58WR7 zdYnCERBbfw%6*D?arEM;!_AMuHJP^*1(xslI~)&9DmZrGb%^FnCCvI7&(X(1rj);F zzE?a^?3Jjn+`-pd2*OiTnV+*vR+@~{mG_pZ6e^;4v2mJ3wRNv%qH!5EQa8Px=aMZ4JP5HUF4F+|w6za4qk>`dzSxRe@ywLsrgJJW1>F%%{ zZWpq9>p{}3YWb6AY3gO>-3!d~NW4l}QlA+8WzThn1!Hn$Vv|}w^Yh{uY`+}Cc3Mn$ zSIVIphBD#)@k_(=Tjj!<8OLQWM|0nK6DV&5Q>8{S8H-}49hep2saS)Tx!s7@ed2EH zr7*IhgfLgq@h0*ihVRlNO*u|y(=(ZmtW#mmRGO?9gW_bxvm)+O`h}CX?}v?HVP$$M z+bEfEM|n)-^TH1dDtb7*=TSI28Q*vJladvea5Jl07W?NtWzyi)T4m5cHJpp4Y@H6c=kdb9!6T-1hsjWBLg*dK zexzwjoJYIX6kkrCOAKkZOmYqwg+-G>E(SJVeBc-_-#EOGxQ2#TjJ0kvdg84_*Og^T zYge=MApnkn>UD?9Svt9%FB5HqKAhqSto@6L%u%_(F)6DjJ;(JWnbMCj!lgc1FqWFMThL9Fp7m}TGk z?QH|uUR1{nYW<|>cgL*7NpQ>v5;JVB!>_Gaw0H}0v-RUzu&W4R3oU{Xe)f$psKFP~ z#1rTxvUV9Q&D3j-f4*g)L0})@A3?Mx%)mg|;N~VDNewv=F&u(ji<{&%9u((e5PlLe zib#Y){>nH$GJ5p9UtfvN$8+x9Vp>M>o!ebnQ@aqAB(Gxvw3Cxk*u{3RJEjD7y3c}+ zkClW%${n&W?r6JyGjfz1QxAl_41*2YW-in={y5}Fd$~$B#yFl2lDOuR{%59NieMav zen7Hz_F8)i=V8kck6UK<@>i1Z*A+P5h@ih@fj$}tmr*^7()|8px4n2iYyHh;&9^{e zBdJD|by+o;m;er%Ck&B?1(#oMn^R%R@b&$zmLf^|uu716i37_utwx(Xqv<{Gzm;l~ zkn}0oc^?=wn^+BVQKl`o>zUB=e1})+J_s0ceCLR`=Zlc8(<)6nn{U=WqO6T5taU`U z%Ihe@!Fx|UUw%>(y$}*cd#g52Y4MJiV;%kHrlM_nwRBFDNF59;o%?r7J%U_L!n~hzz zBMQLZczeq?RZP>Bav*AlmYy@ll3 zd)Rk08^-mSwULFHmaGRcoa&RBz4}buBI&1yL1Mzn0+#j)S)}gnA`h|{=4h;+hGv*( zR*lOM7Vyp&h*EQ&7rleUy5e_7yxPm6%e~CBhVqUzn&?L__I<}MO;c4HbQQz|Q_GE{ z=pcLi@-7V@@^G}*(KC+e4Hpwhp0Vg`*f-h5mwC`a!jm$mv-fC2_pM!<{`^AF=I{Yq zd(L)L3~MP~4D*A#c)lyI9q~qf(4*Okv}SyqJ}7 zkfDXvHs)2e!6V9l#%7Q_wt9w%q-!x1>W}4jZsCFY3-*o|E7f~AvS{chLDW^)OE`yN zfB28E)u-~z<&v4MOPw96z1v=IJZlHHKPXF7nH$|Eu1*O#r~cp*so9m9X7t%4H^W3i zyX>SLp}>6`C$!2vq;XzDnD0&AO+>93!~rX6tvYQ^<5(Xb1XT%v2>Aw@&{FmF(3mOJ z2(v&lOxM(F>G`uaraD{*-{i0JnsU5Jo{VPc)xC(j7uA68l?BvZ?W>j0~{;D z1Uf8)@?M|5Y(>nsdmnM@aq9Asi8!EOgx1T@D?{UF6y3&GO~P0P$~7%ZNa-#Dnvfw# zdIe}vq%$sf)@1Zl&ld`iPF}6K_Rq_EtjBPnzeo(6Z9-hny%1}E)qEiFj8WQ7E)1S< zurSvs^p>yf1thD=ufQ#2ktBaP{Zafd%CH*`B2A|FL@9_B(hn>YGpHn zkn7Ef!;46Fv8V(?7&?b)7W#5HVGKD9COPYo_&q8_f@$d^kfcz0)qbK7@R@;){1VW` z3i6GSTa^OXHmp#*`b$hbIDJddSnho|xntu{7+NIE;YW14PbZmI$+48y_D9rGpLvGF zz%Ij7AG%~*H@m`~kqDj~J|Ab6OM~vjipZF5EQRm4#Hr}(bLf5m&(nh!u7v(m@^h<36&ViVU39i>R$M!BS3F^trBAen zQkE-SetzkT6q$+-eXUPR^e+XXNFRFKdd<@|TFA4dEjQGyTE84~lbpaM_zZ6blOESa z>2*8qyHASz)a2y3OuW&aMtw4fw#UxJ9>>oFBiu$pW{d{}b-G;At4gtqziOg-*x za|r&&i&V;R4AWD%A0K*VRtcX`3NYXWF}A(8g&}?skqRYV2C2w}XZj_~i0m_oJITu1*7a5%hSZ+NhX^}Hq+6}PUt5;T9XjYg{W#K5)5JO@N_;Bk5H~&K>3W8 zO9%ZzWNn(@bwEGjtRS{gKzE9N+QACzaPx42CcGD#;pJesCAOnQPkY7W(fCINVP$)rd5(ad;0*J=8JY>HO# zh@WG`Q^~zEeFvbqxC9NEUQ&h349i|zN{tN3$QFq`yOP2gs3n{#2t?MO8Ez*wZkNg0 z_apeZ>VQB$l5G3!*=^LFaC=A&%ysnludJ=#lMpFp;t^DkMV9Gb&w4S#cnD|vBar!4 z2|KmIF(Eh$mY;ZKuN_|>qA^01CD5&kS3;UhJ1o1mZ;24LYLG1aJ_9>4Gi_SP3HhyrZ5Kf zfwm3urH<@#x$r#ElKiF*EfUc53zkuJa89BJ%|Baja4Q8H@l9@EnKdjOXAs?q?c3Z3 z0{VDH>Gt?gj1FJCPLL0M^V&b`C8q%KIUKgCb@wV?QEi<8l72~JPJou`T!w8yGtYJJ z>M4>PyNAPy+m{mls`PAGmlNYrh=L&@%DU7qG>9IQ(NaQ&P55<_#Dk=WUfUU1uOTef zb;qrBbjdL6;&PI2IZV#NJ1B2>6)T7%Vb@_LgkBxL@7Wrbf~v}oIpt&s2rtQ5dQINO zAI^-8qG)gFfNPxcuHMhqf|&d}nyj!SvaD@XuuAvg{ucGbbEYm)+C+Gl-LS=>*kpdD z0vb*DA!4+%Q~rT7t{jiA5Kp!fkTYZt3y$`YU)0l3jFD0wzeBp4zz~vr$;A*}W@+2* z3fXvyQu}mO7ZHILVypRzEBRboPDZU_zl-ii#!~@6z-8V9!?WXIELUJBma}u!vU~mH z)m?D+dxM~q{lz{D7CC`6?=4-Pn6q3>A0(kwPQ8{Q0-NY)tPdd^JWC}?SckT;6Yglv z`@NNXGjsUxF>HIq9G*C1i!a17XT!Rv2yF6_1Q=4xe=v*2CiEx* zxe4trhjvgbN=Tl9kApX{{NZ=Pj} zctR|^p?$A_*_{=h>2c;vODCj>gWAiAK=D24DeQ^OyJvnLxi1g1)8D*a6uX1Ex0W}9 z<|X_#M(a{YVbUeZRk=eM5catWKb+8hgQ7h3%kz9n|E-g`HTJ3fo990t3hgfJV22hw zZp>QZW}$>XhARTS)!RToKt@bJ;BSc)2&;)M;as9^GH5>1@JNN#aDHVT!a=EG-Rcjkk0teS-VMhjI&PVQDRA9sc4#{27f_{h>YK z#=MpN0XJ`MY@D7=5Oln0LY|;%pU$U_qE9eGp>)2)J$(%8WRy=AuWNHmzt>?69v|HN za2!wUaCaBw?0r7^YJn(up&W7GrX5uw>fE%dJgpD=`*i~mV;^KtL~?7Dbe8AHSjjHVK!%;;4pVy zaoZ;Te2U@>TXY)JBnPL*EmmAnzKM#m083k>5?=&gM2rnQ1KtjYPVu6cr)?$)EaI26Q$b`5;=5qcJ5JV7Sf_xwB zXZGeLMe*$(9v)(e(-6c|70dlK-w5Uh%Dp|68x`VD#}a+RWQdW4(HPJ~`$pJn$e15- z8Y@gcK#45f%|F8DCkz?C5LG|PPj%ndwA=W(9xpyv55r)R6K^yPWz(}c?(3~HZuFfT zt@TC|4E8xWr^F4$#adx*oLo6Wf!Bh7+s%0L_%}S!zb!8WB>3{jLO{Vk{@|Y$h4=Bw zAL|bB_>_NI2pE*dAOEpL?vJ1UX(1qC9xwf|{~Yws=l_pG|Hq*JJ@kJJ`rkwUbI|`W z>wnG{g}0uP)^z7VGJ5geZQH5he&T_+vg)M5ZlZ$O%S2I^O|=Fu2zt07$hTdh{`X=QYyv6EVvr5-Fr?Z9zwx8R!+;1sI2aQDVOrd+12i7$4 z{P*{qWk%xc?r%IYN5_I@z%yV_HsGlI;0os%Dh5k~1F!XVI``yK_-JD)PbeaeZLg-i zGgbX&nk}oX*s$Qzoj6h=qEb?PPGf;%cxTxA-N;hFmO^2-=+S6x37Zq(_?dS zfQpO@>Bs8I-d5gL@}E1A6;S&E<-gUdFZRe^Vy({ACVdh&tT>!B_-LPl@UeGh)JGA? zDCW<&ClUL?ipTrs&bbDSuV%9l-fetqM2Jz77CgiU`s$;Snfh4%N|RxYq? zEXErhGmhEbNde_&63=2tum5@j0e%E!8!jp^#jeyba#K=+W2QI0&PyM}9^$y=Ui>?ELL(TG}Nu zxAB8HP2PX@)OCsi=9sF?}IfeVc(NUpU}ac>98&jW;ywf*fx2AF&MrO0)}g^h>~4-3+%R&xjAEfQn6?c-JucT@PHAXB9a{~81U3=1Fpgb^2bZP${pF!-%b@ag`^+%3_5kbkD zKP&rXXePsVFg#ji%+ACm1^NtoPQq;CVRWKxb5|j=A%%sZ#j@J+&$jTS75dFgr>Weh z$n*Vmd%D}uGpz+~l~3U9&(2)lO?)x8>b2#j&g=WBaZmuoV*=Lo;7>5!7iQ`OioY{D zps-9JB%lW_M490-LL*uWxakm+(YfLzrsPcMzHS*ufxxpWNVVnC6e@#gbuk!}d zBWz|*GO}yHrtYISN%LRas3=<_Z_=rtf0^!~*%r~gV-0+KhQniQji+m5@r_i#XPaoV zss055pN!#V&7;M)GqR0-&pusyD4;~r0=0N9-Sxv{XkZDqmkN{dMl3nA&?Oa<*fnLV zLkZr#>Yu<2eea0()7mHEXYN+*@4&P}R>r7!O}qQ}`d_atK7+{zf{>C_aic^Q$aqv)YDZw8|N0v)SPEI`HU$xaxSv8jh@#D}EfBWm?J!mgo!Gicw=90m(@?QfC=+O^ z90=88o2&SPeVtFvu4EaN!IasJQYhSZG+{|nlBx_Ct4_pSSjH}tjtQ!rIFQ$3O23#G z3t;yy_dKSI`#opo))e_swci-94o8#n68Ss*4{%G>*adyHgC@tuvJ2?A^X{5%h()}z zyP94-AI!t%`u)%K;Ar3mMNhD2uCf|cwr{ZJI7^Z?-p8MuT;D!Aq}f|=pS9VBhN$D= zf)R41MvmU;`0uQMUH)7ZX$6GozlRwd8jy!oAjhX4@_jBwMeVoq2sE`-C!-OjCJ#3g z=Wg zM8GL3k5bw6aIeze-ye|~i1~IqBa}Ox665$5HLTihKNa=M_)7&36g&2X2bO~9#Khf; zMR(lt^76|&7Ecxqj+=MRq9bVneoP!3;@T)NXJ=iLvnUKPo6pz!iVd z)itoTj)Em76Bie+wOIKHSG-tlAppKG`9o;AToQRV>j0zp_U&8glamv|dTt(`;`(|{ z9GDDtcJ}hp(xa9gTwL7k&COIkzRj(z;qFKZySpo3$45s6GEkznwhZPQU7RLU78_ku zMn^}P0{;2foa$;t>1597vkhQnEiCARJyGZ9=N<2FuZnow?r&>q>*@@~2#VAjzW4O> zxF6&f6pW4Me=@*2KiwQAoo{mES-RO@Y$_HBB^WLdz36DLMRm+=@kCHqw!XB-#LV=~ zDJYP**q=w(Zul~o{L#&gN775%(aDLKk@4l2(~q(;>-{;l`19nGjlrFb6gMP;_}W@_ zwIq9byY`nbaiz#x4b-R-6TIZcWA!Ot}VW@zFyVE#f1sg4D8)jD9=5-(ai3y z@y8Dzh6JJf&z8MVpdx{J5%22@TTxNr!))T>(x9xOVnAZd$i$R+baYgbP4oJ-jF{Nh z4JZ54O<6A2Th??3Gz<(wW8)z77u&nLV<~R;*u$K1^70?m)Fc_7{@W=;4_!KMGl)mUefGAwGYu$UZnSBDc1>YM<_AWMovy!)a|}^HEGpte}9Pq^8E; zri~joAvz(U^bhDK?^^xp))8C%x*bFkoG$jKE%!}qY;1w<;;1eS`D65f>tG~UGLu-+)&p2Mmm5le9i(hapehjdGH^w&)F!xqjD2|-F? zv-){d`(+?;`ROVB=GJIVt<^@zip{}XO-7D%$}!|_B{IJFr2DsLFsd)1B0ZXoCcXOx z2dNktM~CCBtgL#vx-`i}c)^l@XtyiZGchTA@$#kim(KP>LZAz7)Wsd>-*oh$qC`St zi12G#x}S`MhgDQCceeSF>FVhjnwSKm<2wP!u;z3Ym2+~cd&k0}ogG?bI)AKVGPROl z+z9>#6_sN9R|L6vMcm<*uL!F1inE5y%*>95OCBpWgq-%N@$onct5_!~54R$V&L_bu zHceOSxOW>o56jU0XAoM)VU&JGpN~(|9T$P~#>OK_9X-FXx3e>R(HolQe? z!odtz1|Q?0gudZ+S_54q4~rmB&<)_J%L|*Lne$k-5#gP%F0rD*Hm84)avJ_R%{)f zef;!^3WU?{){h@Q9&v&6@v*RvkA-dR^czU#${!ez>fM-~p3eL6WpG4Okc^5-985K4 zu&TWLgOZX`@}7}~M#}c~Hh*$aQIQk?v4U??Y3b?wS65fW(y=Ki#Q_1&a6}m+f8SzOG{&h}p) z9vc&E`W)zoOt@XO=o+X`&>2cNS*qQszs}+4=m@25Hab6FXx4NWqequ?Y2g`!p{&!?`w-lIymz%_%Qe1r~hZI8sFiDQzJ{NpT zN^)^J6^H;NCv$pQ3CF9Xpt$&;x>p%&=fvcs!E?y=!sBCOujxV0X%c+>C@(MI<;ld(zI{>i8u%OzK7JM-d^bo%aFcGLBf+>VfC>qE z0f_}=YHltuA~JIE-pfWu*d0kY*1Y+}!FQc{05c<>KP1TYwagkV@vv zOHL-h@ky1CmOgNVqZX@mxv9*~$uU5KB(pqF4BD3Gm5Q$HYnZVk$YB7HB$V=;-L57aNNHf3gVXzgdJ< zVPBs_|KQ+(Jq;ANMKbBJ`)hMJ!x7|u!IRh2)MHaqSpDAY92{qW z0SPmOj*Wc)pfM@|<^S&o1_R2+<#617FlY(RKfc5W0)%e}kfytPYI1TPj?WDnhs$k_ z=eu#_C-23@M~{ygB+p7JE8l@|(}?M#A|~bok;N1Zp(Xh@qZG`pu4Y|gYXDBii2x&P zZD(g>VPP?Nk(rZoLLFuThlDq@Gg(ZNv~hoTa|X~ieuwW)iy3v&Mx)D(_0|Y^ zJg#vQJmI+%g5V2olB9`zKoLD_Q_XdB1a)IaQT|t^z?+Gp`nv^fj^Ihe7T$=XJb>6mzOU{q&L;o6{4Y`shl)0 zFj%%dfI%_xf+mJ}1q@jUpm~6l_;9KL!UNDppU4=nr%XUZ!g3N46UT;z!g3s$Sy^)v z6Y)B?0rAl{HxG+u5~iW1-dyib2pO+)ILXGqz|gP%1nrOS;Q`nqFlBaTGcz--$&H1D z!S}82kO{fRE-&q;MngkGcb41yk~cq!i1-a8vaQ(9GkzJ!&B*YIX8y_Pa{a+>f6k(Q zA7qo$(=B;!_xbZ2?K2YVIxi^9>^2>lrPfpAHm{E&bb#vBBs`^N=H@n?Mh%LHc!7?F z)_xxHb(!b%=x*SGesS_Vu$z=f>bW}JQ;+VJkfyPR-tEzxR<2-#!_Ge+sN&~pZF(as zK8F@JUiU;+s5KQ67>V#EBEjBffu-@7d?*`PTr565wp3Ynx!70R5A*Tw>+74c#mu+b z7@!34sprJP%DQ=JpomPsIRvPYl7;qAYpdr<&S#gCeinAiRrvl3eh7M-51x;lvTcW0 z2lxZn>dFZ$0s;kDSyU<({>tj=-2Hu16{8Od3U2^b=sB@6Gp|kgYy$Ri{VVXTqJ{QU zPfvh0X1W_F0IpXfrS)quq2M=yS_7Bc@`+bLCIDD$l{o=m4FrTzH){FDU6Y;Fg#HF+rWbdW16Y ziRnc|=JvLs?w2oqTrRe@mEZI84agwdYczR5Zq{5HKm2^m^jfNgpC~vuIJsc2ys?pc ze0&_ljhb50_4T!)Y^i{dkk9eSi9j+f503_bNv1G}U#h&1w2t@iI6M?AASk)n)a^`7 zLl8cq$~ZgM8yXqm7pthK?9?D=fC8lmWRcXx#ksix060G)T8rg#qkraiU zU{&e|Jkfy9Ux~a^iqjv(^rshm5+ghN_ecuq>$~aVru*+NDWppHN%S8Jagd}IQ(P|Q z?XFKpr43$y1^GLb9v&<-H~}8{LwDis?vCB*oLW+NN>5L3Y`n77p8Do69gyTj2^`NQDtO!zlcN3ckN| z*9DLI0ODi%7L#glfe|ckT|h`X)h7Neg`E5S&6n(5O!W2Povs1aUL(!oaoAF) zAIiwcq-uAD42$vzi-_E*BS%I@XAKX_;&_Kq)6lq>-}#Zv|6BghfF}lS1pk?W)p@c= z4Poar50F{4I@@5_R)ytcZrw8j)l!hJzD#mxijDkJ^4L;4a3>e5GFzNDJ!J|eaaSb# zdy?ThySvY~1{6Vp0wsHCr422 zH-B*#9l5)?I0LE&4hFIG{1cKg=Yh4a**et;F0$Gb+Z;Fr*#F66P^kIquq*;Km5t$&s0l1BzRSS!Y2ZBPCTnwNi5JfU6 zUGcb`6$g{Ke(0!!MOIc<&(0&5n3%|Jy1(v?a3CimGe@w464m(^YkGJ$Sc%9I`qcR5 z9{>Mp7y3nTYBiQZW()PDK+gb(nV7zy6Xf%G*MVsNJ&dOa11mB z04xfss&lg$8U8K+b!&k@xCSJ=SWDtXG1^D|Kbqn4FAm`I3dZ(h=8p1706m&Rn^rO z;Ov?sGC^trX=#`UYkYY4s0F`-%s^XP03RPedA~P~@e5wD(|RAa^aYm!*Z<-Vg$?GY zwI3lp13!Ic;5D}})VD_=o(qCQoq0&=x8o|fMOv?$Ri<}?Y47m}A|<9{JG3Apvj;H& z2^0t*t>hLI1o^f+lD5YSG_rIGX3rcLKnRD8swRV~p`2RJ1!AOJ1tqx;{2 z+%cK8;#Z2a1Wb9%w1jHWM1;}LZ=An<-+BjoCRfg&q8VRY91IA4q;mS2n$5>oNY9_^>*@Wpx&!|9pZp>BKlsD< z#>Qk{M`m}3!ZR0E%MU=_+Qx<-r<$v4OcxLy!M2 z>C3Fi=-LP`U6 zvjI6}fUL|+`^LqLj7Q#gy>tc$S8zy389&MY>Y@CS05k}Y2x8~mqTF0j zz}YJMo0^(5K;6rP^I1$>{VVQ^)6?BYp@QAvI1>n2io~d(hw^_3 z#T4$}gu)|<%Si#yHlWx2_#qFP99b>?q6!KzfY#Hbm;u0BY`S#n2Y%(__Q(t{~H#h~}VmT3S~LhHM08wFA4rC1HuVW2z_j4%P6klkh*t)J9g@n3v% z`Qk@j9%Z@%6EpL$+te>moL=u$Et14{9<6lR>`u`SUX%fa(e7XY(P#$Lf{)?H|Emy? z|GT43!q1WkNlAqu7UM-BzH8!n75NIU!wJe}9($n2-QJ^j*6#@$f zr$nhe1`2r7h59YU2B5!yj)}+5m%RMQSbuXIP{8f6Hjn^q3^X^GV2fl_RO(i&gTWle zCng+gDF7>Bw^|Qcu^CKc%K+^Yg&wREAY{zfSalxN0&Qa$bQi85Y-b10XPAl7U=fO* zdWJcb-ho2>~-nFNpryo9<^bl3CYq8wsKLsr^m!l5Mf-rhxUEL1ALO`3@$>q44f@!`NvVB`V z7vy79Zg}G5?%v)}P;K7cQ-ihy$LE$@uvA=b_j3n>K(gR;qoDq};ohvw3Fi&*BKmJq z`~rtS5T0d)&pVCHe92wC$<<_w^WtE!7$~E|s>r}G0|2Ad2G{mxbHrn5hvzhJgf7h| z>-jF}*bWqB6%;V&K)KM>h@ZF3w3)M<$Yjg$Dg{V7mMs zwg2&P^kZuf~77@%xqE^pByd=1c!$Daot^BRugbLi;|Lq7s@LtqFvnr za5*+PDFXf98V23m-G*Wfd=f=Ys}b;2^(MN8e^R^^efa-W;f6klft4Hw8o!q#kTZX{ z-X4j>`>Js{p1#gCg3Btz{pNhN7Ry8f1&$B7>raf2>pr3l08DQi9L%NA@f>L%C!9~z z*8p9|=jZVcAMwN+8Rn6YYy$}c>9P?B6!C|jJ-*c<7UyLU;oZ6G?7#Mp4Rt-exDc`C zzElT2gkJnIl<>GX?Ad)S8_-qg9Sl^tRH1XW* z*BzV!A!~#T%Kz#w(zA9*4|H-s&s2;+2MsjP{n2rWRa8}yYs zQL(X|0l}%j`BedSgp7U9%aeWo{+VR#>G7I4KD)JO#ObdN1bj~Wuxuh@AOThZ7D6tD z`Br^QgwRD$9-e6ZufAezTj%Xm8c3I57}~A7#Cs^x#Ug#l)OZx$d;QvgpF|lNHLp<*!vdo87al zGqFDkMG8V*_D1&O?~2OA<0+PYf9)AB`^hR6lzOG=eT%-6J@~M;+?tkt%{$A`5c~*) z97F&6?fM-%o*SH+z(MVzYw`ky#O5RH_=i7vLcrf?r>UZ>2PGlJTA8}5^*=` zePaCncWj+zFcn@r1eC`X7>akO8^}Bcrltc&wE(zPA3WTDt*m?m!khj4%#3oV`U5IB zP3dLc{Odw?)VLqT>j|+pgqG9ae98IspI%YOc*3`DOGt|Fpm~vQXqZ^{Q5uA^$bmch z#t@S$Ud$N#a)BN>AbUm2MSGymiKBr5Dvet5|CfFHoS5MiXUJF05>hwH^}FMOIGLiu4G1?B?CdL0D!T@-%s%_0!K}N#t_qA4A21l zV#0{&0%a{Y6ry`VMM)_uC55!y*6pAkefmR)+-)J5khy=noRSWOpMN(tlxx2VnI!|A zZaKv-7L}XiKpSXnf+zYwYqN5v0mziQ=z2qw?tsVRs@fTXPwK2_8hUU~AZDvNFiNwf zV#uyO>XF)9L zhBsT8!Q^hy)}HYp<55;3l$YET?%wp(SDYV{uYGy$Sz&4NLDT8K+NJJ~I2;d5XN-{; zkSV>xl*FV|^%zOF7!7CC2*(^Gb4s_c!-qXF@j9N)5JHE{1CE1QcMIlhkD>%a<16w52&ZFaBx;$bIYXR2-$ zfFU49>|P}UF<2y-0;;~au1*CU`fzHb;NqGO-}nk@g(*g@C%Psi!0{!DLK?qOOS`lY zx`mx#my^WC#^z8+CtuzvnVJ@5NPmD^m>eEX1J&itoznaF0pQA)*l>u}89@kQ_iDqB zryU<0wsG$-1que6(QCj9|J}k9c%(gWKS3>N1SIoLBT!3zFD%rp8t&UC$u|ru7ol&(o_}{6%=u;$AgC_=W+MU1YB&T3a*Fn zj8|WZ#kr~vPThl(hFfcgRgyoFIMDe$6E{L5wyEDJRdMkc#^)Qz%Mdwxvz~YCQ!2tD zPuHe$o*}y3;{6JauxMTv4*C0txfUB|H6AB&*Q%WpR)1j_S~sJ3FZr45C?Lj^BQRmP z2!xQZbZx(v&Muc5I{D*@yJT=ZMILq6#kn}Ukv#5oRa2r8(kF0Q5t`u&pO8=fKTdgK zIDh#O{DK#6;bu;kh*9-%BVGM=7!ScRD$aG$ij9AkIB^5-Q1a&;8lLoX{_~t?&{AE3 zNhE}p{=rEDWWV`uUvd~i<#-h__9~CK&p?HIJaEl_c}-JZB+=9U;`3k|%7)-~OFdQ{A$>(K?Ay{fi$#6Q%DUt_>tyF=F zB9cFt1)ie;o(9eWU5fa_uUqiRMLr1}D(5N|wT)3inxqj)T!xExh2PRgYrn>p4;Ngy_un~nIz9*sq^9to$nD5 ze}X3xGSsQH3xOaO!mPbJ@=a63D?DZIIWEJs?ivcaA9#Fk!LCC=NW-FV*vUA{%KqNo zg#Tx+HXnYN^5e!+=krrc-hWV%`4~L;X~EN?4>k%6A4(WDCc7UM4+STv?K|wOrPK}` zua^I(zUeKW4x=h~8978LFxT!m{HXgB*c;Iy^JRG^KYaYQ@404W(UH#_iWwwLE^Vj#C=HRuj&lwpR7>_KP zD;|Gl%JO{=`|m$LW0G^+?0U?^DTxV!eHqV9`E=T~rvtMzP@T#lkh-a0b?N7;Z}!Qq zGhM#+_FgO1@PF^$nQZ@SJne8OC`N%+3q3p6t)B*t|M#1p*?yS0_M^S0E1!x7b49c8Z5tsg`#@#R3Pzzo!$`7czgiup!OhfJavF4|nIN)VuL-fH_{zJ!Zyg z_4`*q`;-KN6^}eh+~5t`0P<&k1RuYS`wGRD{7ld$ppKOSNtXpGkk?i(DFS5!!5of< z(o&#}KuKCHH*Eg&Ldv9@|L<^3J3RHYphnAww>+SIOopOPGE%?hWqe=0%>M*9;jN45 zyX(Yp{|wCThl%-dLCfY}@9TJ)UbvkZr5YA3h|Q{K*o5w`njxgN@xNA*1A{j literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/border/border-image-round.yaml b/third_party/webrender/wrench/reftests/border/border-image-round.yaml new file mode 100644 index 00000000000..e46a90c056b --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-image-round.yaml @@ -0,0 +1,42 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 500, 500] + items: + # The pattern fits exactly a whole number of times in the border. + - type: border + bounds: [ 50, 50, 192, 192 ] + width: 32 + border-type: image + image-source: "border-image-src.png" + image-width: 96 + image-height: 96 + slice: [ 32 ] + outset: 0 + repeat-vertical: round + repeat-horizontal: round + # The pattern has to be stretched to fit. + - type: border + bounds: [ 300, 50, 200, 200 ] + width: 32 + border-type: image + image-source: "border-image-src.png" + image-width: 96 + image-height: 96 + slice: [ 32 ] + outset: 0 + repeat-vertical: round + repeat-horizontal: round + # The pattern has to be shrunk to fit. + - type: border + bounds: [ 550, 50, 180, 180 ] + width: 32 + border-type: image + image-source: "border-image-src.png" + image-width: 96 + image-height: 96 + slice: [ 32 ] + outset: 0 + repeat-vertical: round + repeat-horizontal: round diff --git a/third_party/webrender/wrench/reftests/border/border-image-src.png b/third_party/webrender/wrench/reftests/border/border-image-src.png new file mode 100644 index 0000000000000000000000000000000000000000..128e560d90d87351b39f058c3063e29141881fd7 GIT binary patch literal 1247 zcmV<51R(o~P)PknVF!=f(6pqgtUN0` zBNfzV`bRvTJR)rU<8Ee_O38fO%*<5kW^Qg~kvRj>bY2exbY2e>bY2grfCeaRMn3(M zNGHn(6*_E2zWgPHPKptVba;%s^iz}0IwRESFc^6|piE~m00i?@MotYY52AS(IJWbQ zTozndswTmp-4yO(!zw!S6fmb1ckz%>(U~NIyfC?o=qyj_%|*kffGArVck!~VqH`(& z$>!Kyyzx`f`7Q-jdF3uH9aMA%LFkIlLsnLNs_4ARy6)M+$7w=-2B|vi=W_!{`X3JM z{S(QjUwQq&yA2VY!-j~?VM9dcWr)!keH9ED9TB5L5LcFnbm)v?YKcha+ayh4bS)Ax zIz&c;2-fX2=};I&(q5C!#WG)Jw3Zk$I!#8WiRf4@r-Y17nbB1WD259_Ll#_MGzkX5 zc7%Ay7;(sC!_YM!%}G&3XOYou09rRlP?l8{om&Jew;1Y!D2N*~6`cu)#R0fn%?~(& zzO`4;ImNL)g=Zdj98f}39IEIHP*Mxruhcoz@9)I!W@ek}3~$djGqXn`C2EpeaU^VDxR* zr>#vpBo`UY!_YL}UHCTbq<+ijHUOO)gtK+pwA+FSf|Xkg2#l+96`iI$qx}&`*X1fY zWqDHjBk=xfE;Vf0eovPvppJW&4jI#S3_DK*d*yHyoke-}qz=K}!==l%X}cDVBGBw! zUAplzZQsi;Qjpa@E-f8Q+d1`95W?#4kX6eoi`)Ou-bZ@^^$Siv?+4uj5_kAf%WK;o zecVCf4nJynW&0zT%0x{5;>*k0A3rBBX{*+wW->490bZbjX;tW7tg?+E>?{6~*%HNgX{C zlyy}s-`f_TVS>0b6U#R?mZ+Mb@9o9%)zSnd6Qb%+EMF>a{{rLy82hI<7ODUM002ov JPDHLkV1jh7L2dv5 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/border/border-image.yaml b/third_party/webrender/wrench/reftests/border/border-image.yaml new file mode 100644 index 00000000000..5dbc0acb1e5 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-image.yaml @@ -0,0 +1,17 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 500, 500] + items: + - type: border + bounds: [ 100, 100, 192, 192 ] + width: 32 + border-type: image + image-source: "border-image-src.png" + image-width: 96 + image-height: 96 + slice: [ 32 ] + outset: 0 + repeat-vertical: stretch + repeat-horizontal: stretch diff --git a/third_party/webrender/wrench/reftests/border/border-invisible-ref.yaml b/third_party/webrender/wrench/reftests/border/border-invisible-ref.yaml new file mode 100644 index 00000000000..949868c9b2e --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-invisible-ref.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 100, 100] + items: + - type: rect + bounds: [99, 10, 1, 90] + color: black diff --git a/third_party/webrender/wrench/reftests/border/border-invisible.yaml b/third_party/webrender/wrench/reftests/border/border-invisible.yaml new file mode 100644 index 00000000000..48387862998 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-invisible.yaml @@ -0,0 +1,17 @@ +--- # checks that invisible border corners don't render +root: + items: + - type: stacking-context + bounds: [0, 0, 100, 100] + items: + - type: border + bounds: [ 10, 10, 90, 90 ] + width: [0, 0, 0, 1] + border-type: normal + style: solid + radius: + top-left: 10 + bottom-right: 0 + top-right: 0 + bottom-left: 3 + color: [red, black, red, red] diff --git a/third_party/webrender/wrench/reftests/border/border-no-bogus-line-ref.png b/third_party/webrender/wrench/reftests/border/border-no-bogus-line-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..fefa2bcbced8d3890fe7781b091a26d51c830fd2 GIT binary patch literal 2253 zcma)8Yd8}O8y=dN=F}MRI!stz=9I{3h9T#%axCjGLYZTH&gSqkOD!9c!;GSlP>SSK zs7#ncyk06qGv^$l9Nzu?zF*&u=XtLCy6zwMb6xjy$Z~bI1%pn20002^oSn5BuSW9X zhL9jHw70rXY~;~AF=bQvifD= zPm=;}j-K1$3iIc|YLv_0Nd7A|DZs{K(j$7Ft}Gl%EA@W>*;;5~V#0p)OMB(~y#KGY zX@UKnb*qv^E|<~PX3kw$kVs;g2?!pe|M(Hktf{Fev-)eO3R8Z4rYj5U?rxQ<=5+7- zJ$p@Hx<$_HU~Ak@&hEzd&HdleVP{Nj?kG4t^Z&@`&W5@h7w17O+%jqOffBQFhkOL) z>eX_Hfv82_t5?R}?TKa|OU!(VvJ5FAplO6(n`tmU5vD!6 z5Zm>+H|Zkb=-ut*2S&ZRgwLkNaoaWIymGbEs4yB~>Q&>HgS3RTjue4}HiVP%0_}A`6fOj1Z)sDTkJkh9! zu9v9L^QiTeC_iQwreGd#H`{ajxb3t=CcSQ9zQ5Q(OG|48{&r!wa!w&gf86Tj&41Kv zWHZ2WHZCQmlv`IkWuTB~wHx{u9lu4?vF-WBY41y=#HF_Jp3zJUsy?8<=&XKAAM9&$ z(xLgo;Xn;l`FCaLZ0Qdj!noap^%*ogjz?2|qyU)ib<8g)0NG}eX!a=EuL0y9A1R z&>N0Zoj5*&JeLNvdKC!5k-CS#IV9e^<9EU)haz+TT|T|4u*SF9jjZ=NcB}Wq(f6lA zsgGPr5jL_RlT8wKw4Qvrifv9`UdPvuB}QSE1k}X#TM^W-_d}+H#*6Ut)>eo(^n9Zxd`p!T5TcvX`H~-VlLpVyA(eQI;Wiux+ z|BWr81xdH|YulIZ3Coz$f`^_vEk6MmipkpBXrYI24|h%#C0XdjPo+ymmB2JQV_b#;c%FVc9T!@ZNd~?+AS&xE-ubwT%j_%q{=-Yh`hxi^ zf3%Jg9>wLi)Cv_a8=8}b99mBh`J2x5X;KWc!sIHwV1(H&?>h+)B%h%-)J5~e6Fwy9 za)zfVh;MTfn23nK*kiIrpb>&$7cnu?5DpvBV)L0jrR>gr!hoZ8Q~Z*;R#N|3Yb7gql92s>W35vc8Y$ma6-y^yo{-5kna%NC&QimKA=NLCyQJhnJqS zsln<%-gGq40a0!Tuo7}X)*(y*xPm=kYC5!OIHZ`-+WQc`{8Cs-=@1~MeJ?6^UKs5P z*x-wUt68RKfYnq39~x3SLUox;;g0kyv(t9N9pExy5bii*%kMw!AjpT^KOcM)XVO8~ zz#WItRAot&6#U*S$)i}zSg7RgK$(ZZ5&)DU8he+5T9+iGj_z|!*a!~TBfX0e$vAFM@TVd1r1 zvGzkbJ2ZE35l@=g(tisJM@0?7NY|U{>+8Q(WPsDEtE+R_)sHW*l(J}aQ09VB&T}^I zfz+#rRQ_n#lEXM{A<~;v2)T0Vy15P-9%PMPdB>N?!wB15$cftj7%v zZfb{_&Jt;(BO`VXM0qC|b_759mgp@g5+tixg|DOMb^hO=ryU!Lf+oa6`FU>&;2hf7 Jy4Dg${1=jzFRcIo literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/border/border-no-bogus-line.yaml b/third_party/webrender/wrench/reftests/border/border-no-bogus-line.yaml new file mode 100644 index 00000000000..a030211da9a --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-no-bogus-line.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 100, 100] + items: + - type: border + bounds: [ 10, 10, 90, 80 ] + width: 3 + border-type: normal + style: solid + color: [ black, black, black, black ] + radius: 40.5 diff --git a/third_party/webrender/wrench/reftests/border/border-none-ref.yaml b/third_party/webrender/wrench/reftests/border/border-none-ref.yaml new file mode 100644 index 00000000000..e011c78eb51 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-none-ref.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 500, 500] + items: + - type: rect + bounds: [ 0, 0, 500, 12 ] + color: black diff --git a/third_party/webrender/wrench/reftests/border/border-none.yaml b/third_party/webrender/wrench/reftests/border/border-none.yaml new file mode 100644 index 00000000000..4b423c7da51 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-none.yaml @@ -0,0 +1,12 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 500, 500] + items: + - type: border + bounds: [ 0, 0, 500, 500 ] + width: [ 12, 12, 12, 12 ] + border-type: normal + style: [ solid, none, none, none ] + color: [ black ] diff --git a/third_party/webrender/wrench/reftests/border/border-overlapping-corner-ref.yaml b/third_party/webrender/wrench/reftests/border/border-overlapping-corner-ref.yaml new file mode 100644 index 00000000000..3ffbaacc005 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-overlapping-corner-ref.yaml @@ -0,0 +1,19 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 200, 200] + items: + - type: clip + bounds: [ 0, 0, 200, 200 ] + complex: + - rect: [ 10, 10, 180, 180 ] + radius: + top-left: [180, 180] + top-right: [0, 0] + bottom-left: [0, 0] + bottom-right: [180, 180] + items: + - type: rect + bounds: [ 0, 0, 200, 200 ] + color: [ 0, 0, 255, 0.5 ] diff --git a/third_party/webrender/wrench/reftests/border/border-overlapping-corner.yaml b/third_party/webrender/wrench/reftests/border/border-overlapping-corner.yaml new file mode 100644 index 00000000000..08e71fabef6 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-overlapping-corner.yaml @@ -0,0 +1,17 @@ +--- # Checks that corners are clipped correctly when they overlap with an adjacent corner +root: + items: + - type: stacking-context + bounds: [0, 0, 200, 200] + items: + - type: border + bounds: [ 10, 10, 180, 180 ] + width: 90 + border-type: normal + style: solid + radius: + top-left: 180 + bottom-right: 180 + top-right: 0 + bottom-left: 0 + color: [ [0, 0, 255, 0.5] ] diff --git a/third_party/webrender/wrench/reftests/border/border-overlapping-edge-ref.yaml b/third_party/webrender/wrench/reftests/border/border-overlapping-edge-ref.yaml new file mode 100644 index 00000000000..5cb34e5ae22 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-overlapping-edge-ref.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 120, 40] + items: + - type: rect + bounds: [ 10, 10, 100, 20 ] + color: [ 0, 0, 255, 0.5 ] diff --git a/third_party/webrender/wrench/reftests/border/border-overlapping-edge.yaml b/third_party/webrender/wrench/reftests/border/border-overlapping-edge.yaml new file mode 100644 index 00000000000..e39e06fddcd --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-overlapping-edge.yaml @@ -0,0 +1,12 @@ +--- # Checks that segments are clipped correctly when opposite edges of the border overlap +root: + items: + - type: stacking-context + bounds: [0, 0, 120, 40] + items: + - type: border + bounds: [ 10, 10, 100, 20 ] + width: 15 + border-type: normal + style: solid + color: [ [0, 0, 255, 0.5] ] diff --git a/third_party/webrender/wrench/reftests/border/border-radial-gradient-nine-patch.png b/third_party/webrender/wrench/reftests/border/border-radial-gradient-nine-patch.png new file mode 100644 index 0000000000000000000000000000000000000000..fd1ed84b8dba3a7d159efb70df23b23617901291 GIT binary patch literal 977 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST~P>fq90fi(^Q|oVQoK^Hw{ExL&OI z*Zl3VnT~7aN6vuOgc8M1-G

    j%YhzmNZpR$)riV`_9z5a3_| zQUxUnzJ(%<1>$H>N^f4AqugNG?*Ux$TDfJ$F2m) zOm~(`x_wFl=dNf@2%5;zvJ>nOBzX)Mz~#R1rR5_v z?ltOE+RV4TuL<;u;e=n+BzZ+f@WiJ-S9OJee(`Gzk#SWxQin zrw_>wCRfH??&fD_i<;ynyyRcw_G$=5MEdr*Zu? RG6QB+22WQ%mvv4FO#mOdJBR=P literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/border/border-radial-gradient-nine-patch.yaml b/third_party/webrender/wrench/reftests/border/border-radial-gradient-nine-patch.yaml new file mode 100644 index 00000000000..d3936a7424f --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-radial-gradient-nine-patch.yaml @@ -0,0 +1,12 @@ +--- +root: + items: + - type: border + bounds: [ 0, 0, 200, 200 ] + width: 20 + border-type: radial-gradient + center: [ 100, 100 ] + radius: [ 200, 200 ] + stops: [ 0.0, red, 0.5, red, 0.5, green ] + slice: [ 50 ] + outset: 0 diff --git a/third_party/webrender/wrench/reftests/border/border-radial-gradient-simple-ref.yaml b/third_party/webrender/wrench/reftests/border/border-radial-gradient-simple-ref.yaml new file mode 100644 index 00000000000..7c3e93fd76c --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-radial-gradient-simple-ref.yaml @@ -0,0 +1,54 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 50, 50] + items: + - type: radial-gradient # top left + bounds: [ 0, 0, 10, 10] + center: [ 25, 25 ] + radius: [50, 50] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: radial-gradient # top right + bounds: [ 40, 0, 10, 10] + center: [ -15, 25 ] + radius: [50, 50] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: radial-gradient # bottom left + bounds: [ 0, 40, 10, 10] + center: [ 25, -15 ] + radius: [50, 50] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: radial-gradient # bottom right + bounds: [ 40, 40, 10, 10] + center: [ -15, -15 ] + radius: [50, 50] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: radial-gradient # top + bounds: [ 10, 0, 30, 10] + center: [ 15, 25 ] + radius: [50, 50] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: radial-gradient # right + bounds: [ 40, 10, 10, 30] + center: [ -15, 15 ] + radius: [50, 50] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: radial-gradient # bottom + bounds: [ 10, 40, 30, 10] + center: [ 15, -15 ] + radius: [50, 50] + stops: [ 0.0, red, 1.0, green ] + repeat: false + - type: radial-gradient # left + bounds: [ 0, 10, 10, 30] + center: [ 25, 15 ] + radius: [50, 50] + stops: [ 0.0, red, 1.0, green ] + repeat: false diff --git a/third_party/webrender/wrench/reftests/border/border-radial-gradient-simple.yaml b/third_party/webrender/wrench/reftests/border/border-radial-gradient-simple.yaml new file mode 100644 index 00000000000..d5110ce4aeb --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-radial-gradient-simple.yaml @@ -0,0 +1,14 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 50, 50] + items: + - type: border + bounds: [ 0, 0, 50, 50 ] + width: [ 10, 10, 10, 10 ] + border-type: radial-gradient + center: [ 25, 25 ] + radius: [ 50, 50 ] + stops: [ 0.0, red, 1.0, green ] + outset: [ 0, 0, 0, 0 ] diff --git a/third_party/webrender/wrench/reftests/border/border-radii.png b/third_party/webrender/wrench/reftests/border/border-radii.png new file mode 100644 index 0000000000000000000000000000000000000000..87d5b674fd9a54d48dda50d8bf748a7542b9a3f9 GIT binary patch literal 1130 zcmeAS@N?(olHy`uVBq!ia0vp^g+N@+!3HEv)YqS7U|=!yba4!+nDh3owN7fF$gz+9 z=KWblTo)HAW$e-FbWw6zxI{%!sFmsB3ezShML{7iFIK@$mqnkl1hh&PDQNZQrLEs7 z*k5^P?|Iwor{})??J7Lill1BD+~V_B?=;Etz1V-OLone8hd~mHgdtOp1fyFIgR)zL zpz?tZK@ZzUcV##11t!m ztftoRG?k6};%)9TXHp)iO)YleG&5uK@!u`%wPYn{M7+P8(ejR>ucc*7$B(~jDN+i5 z@@J2=heF|zyRi%Q%I?@(sy?Ob@86X56FZeoiQc$zq9N0}%9PE0%Jl=ks_r?NPMN|W zF8)p-py0^EM7tvy6WM3CvzH5KXe_$prmb~{aplMLAC)wc{P{O*X-W7b{O!jJJ`L^v zQu=$8Pvs|dbsMkmv{SirfZ@Vj-YHX4&)IKkV*1|BzF#W8(mf`Uao4UpjqR6$5=uo^ ztSM3d;vMJmW$)BeqT9D0Z+QRI^~kHN{o6%9eX+89$@ijnPe(+=i|^0koIY#)iH&oa zayw;PyVOrPPTlyIYxi`l_&NUvhspP37v)*M4HpTX{VQ>?<5^u#hD))%o5`Y`^2$;d z@AM1yX1qJz;Wh%5G5%z@@czDW{Qswym%q9HS-mIY!4sAhtBTkoJzmU~E*Iz!;eo zYrp$r^ZDZ5&6{Ty%YO z`(*pY^m~H*d=0y-${kitXvofXaBj9fIkDX3;A%tJBJSA8lYhBt#6$w+a&Fyfc$*vG z^hvQ#Ny%u*Tf2H=XQF}Oo-pl zbo8i$FYkq0TwAuN{Pyixe?(BT9$T)&ky+uw_Kdwxx=wb!j_3rIAq<|belF{r5}E*i C@&u~@ literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/border/border-radii.yaml b/third_party/webrender/wrench/reftests/border/border-radii.yaml new file mode 100644 index 00000000000..c0b2fda968c --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-radii.yaml @@ -0,0 +1,17 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 100, 100] + items: + - type: border + bounds: [ 10, 10, 90, 90 ] + width: 10 + border-type: normal + style: solid + radius: + top-left: 16 + bottom-right: 16 + top-right: 8 + bottom-left: 8 + color: [ blue, blue, blue, blue ] diff --git a/third_party/webrender/wrench/reftests/border/border-ridge-simple-ref.yaml b/third_party/webrender/wrench/reftests/border/border-ridge-simple-ref.yaml new file mode 100644 index 00000000000..b940962d455 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-ridge-simple-ref.yaml @@ -0,0 +1,21 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 50, 50] + items: + - type: border + bounds: [ 0, 0, 50, 50 ] + width: [ 6, 6, 6, 6 ] + border-type: normal + style: [ solid, solid, solid, solid ] + color: [ 0 0 255 1.0, 0 0 170 1.0, 0 0 170 1.0, 0 0 255 1.0 ] + - type: stacking-context + bounds: [6, 6, 38, 38] + items: + - type: border + bounds: [ 0, 0, 38, 38 ] + width: [ 6, 6, 6, 6 ] + border-type: normal + style: [ solid, solid, solid, solid ] + color: [ 0 0 170 1.0, 0 0 255 1.0, 0 0 255 1.0, 0 0 170 1.0 ] diff --git a/third_party/webrender/wrench/reftests/border/border-ridge-simple.yaml b/third_party/webrender/wrench/reftests/border/border-ridge-simple.yaml new file mode 100644 index 00000000000..906b40fa426 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-ridge-simple.yaml @@ -0,0 +1,12 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 500, 500] + items: + - type: border + bounds: [ 0, 0, 50, 50 ] + width: [ 12, 12, 12, 12 ] + border-type: normal + style: [ ridge, ridge, ridge, ridge ] + color: [ blue, blue, blue, blue ] diff --git a/third_party/webrender/wrench/reftests/border/border-suite-2.png b/third_party/webrender/wrench/reftests/border/border-suite-2.png new file mode 100644 index 0000000000000000000000000000000000000000..519e43fc52e7830b58baf724f8a5014a64bfb256 GIT binary patch literal 48919 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

    i15TzB%HUZCd}Yr2gHkd@Ql@Ur?wCxU*oYh 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 Ocn{?sBMN1n`TZ|v*?!0X literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/border/border-suite-2.yaml b/third_party/webrender/wrench/reftests/border/border-suite-2.yaml new file mode 100644 index 00000000000..f86b20da308 --- /dev/null +++ b/third_party/webrender/wrench/reftests/border/border-suite-2.yaml @@ -0,0 +1,171 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + - type: border + bounds: [ 10, 10, 100, 100 ] + width: [ 20, 20, 20, 20 ] + border-type: normal + style: groove + color: [ red, green, blue, yellow ] + radius: 0 + - type: border + bounds: [ 120, 10, 100, 100 ] + width: [ 20, 20, 20, 20 ] + border-type: normal + style: groove + color: [ red, green, blue, yellow ] + radius: 10 + - type: border + bounds: [ 230, 10, 100, 100 ] + width: [ 20, 20, 20, 20 ] + border-type: normal + style: groove + color: [ red, green, blue, yellow ] + radius: 20 + - type: border + bounds: [ 340, 10, 100, 100 ] + width: [ 20, 20, 20, 20 ] + border-type: normal + style: groove + color: [ red, green, blue, yellow ] + radius: 30 + - type: border + bounds: [ 450, 10, 100, 100 ] + width: [ 20, 20, 20, 20 ] + border-type: normal + style: groove + color: [ red, green, blue, yellow ] + radius: 40 + - type: border + bounds: [ 560, 10, 100, 100 ] + width: [ 20, 20, 20, 20 ] + border-type: normal + style: groove + color: [ red, green, blue, yellow ] + radius: 50 + + - type: border + bounds: [ 10, 120, 100, 100 ] + width: [ 20, 20, 20, 20 ] + border-type: normal + style: ridge + color: [ red, green, blue, yellow ] + radius: 0 + - type: border + bounds: [ 120, 120, 100, 100 ] + width: [ 20, 20, 20, 20 ] + border-type: normal + style: ridge + color: [ red, green, blue, yellow ] + radius: 10 + - type: border + bounds: [ 230, 120, 100, 100 ] + width: [ 20, 20, 20, 20 ] + border-type: normal + style: ridge + color: [ red, green, blue, yellow ] + radius: 20 + - type: border + bounds: [ 340, 120, 100, 100 ] + width: [ 20, 20, 20, 20 ] + border-type: normal + style: ridge + color: [ red, green, blue, yellow ] + radius: 30 + - type: border + bounds: [ 450, 120, 100, 100 ] + width: [ 20, 20, 20, 20 ] + border-type: normal + style: ridge + color: [ red, green, blue, yellow ] + radius: 40 + - type: border + bounds: [ 560, 120, 100, 100 ] + width: [ 20, 20, 20, 20 ] + border-type: normal + style: ridge + color: [ red, green, blue, yellow ] + radius: 50 + + - type: border + bounds: [ 10, 230, 100, 100 ] + width: 1 + border-type: normal + style: dashed + color: [ red, green, blue, black ] + radius: 16 + - type: border + bounds: [ 120, 230, 100, 100 ] + width: 2 + border-type: normal + style: dashed + color: [ red, green, blue, black ] + radius: 32 + - type: border + bounds: [ 230, 230, 100, 100 ] + width: 3 + border-type: normal + style: dashed + color: [ red, green, blue, black ] + radius: 32 + - type: border + bounds: [ 340, 230, 100, 100 ] + width: 8 + border-type: normal + style: dashed + color: [ red, green, blue, black ] + radius: 32 + - type: border + bounds: [ 450, 230, 200, 100 ] + width: 4 + border-type: normal + style: dotted + color: [ red, green, blue, black ] + radius: { + top-left: [32, 48], + top-right: [64, 32], + bottom-left: [10, 40], + bottom-right: [48, 48], + } + + - type: border + bounds: [ 10, 340, 200, 200 ] + width: [4, 8, 16, 8] + border-type: normal + style: dashed + color: [ red, green, blue, black ] + radius: { + top-left: [32, 64], + top-right: [32, 32], + bottom-left: [64, 32], + bottom-right: [32, 32], + } + - type: border + bounds: [ 230, 340, 200, 200 ] + width: 4 + border-type: normal + style: dashed + color: [ red, green, blue, black ] + radius: { + top-left: [64, 128], + top-right: [16, 32], + bottom-left: [40, 18], + bottom-right: [100, 50], + } + - type: border + bounds: [ 450, 340, 200, 100 ] + width: 8 + border-type: normal + style: dotted + color: [ red, green, blue, black ] + radius: 32 + - type: border + bounds: [ 450, 450, 200, 100 ] + width: [8, 8, 12, 12] + border-type: normal + style: dotted + color: [ red, green, blue, black ] + radius: 40 diff --git a/third_party/webrender/wrench/reftests/border/border-suite-3.png b/third_party/webrender/wrench/reftests/border/border-suite-3.png new file mode 100644 index 0000000000000000000000000000000000000000..dcd6f5d76025d6a73a5e1b644c67736cf3b6b116 GIT binary patch literal 26021 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�Sb5*9t!>y7Ritc47ZmCT^vpIZKR@NsCT9{9Ly%P%8>Hxe0^j<22cktZJEW8Mo__Y zaa3tM|7E>bbN+3;zg^qeWo-SA@|>V!^b&;u7NO zKg^3nVCS$S_C!Ko0;iy-;s3*r0mk?g7&;!R0s33D?|tU==-WUaB+OfqeDBx(%lf@M&BE$#i;#IBuOae4Hd2AaI(O2?T`V)he3IzK9eE1|tFm;mOs1B&m(1;?)|+9UD* zyj3)O`X-IO2c6t$r6YJ_r+6Jd{_V0GFI;w=2~uBRovj#UCi{_C^?y1Q`nGLg`tidd znFB`PkZ;l(=GV~*nW4Zu1hTwuOLjKWiy?2`Pp}PixD4;bqq8X=tN!iV6jatORqX16 zdED!JLvmWD#Sk%hDfyFBLUXFbyB5GP?&@o&Rf4XlJ(G$l?*IX>F-fEoD89tIO5zl!-m?;aA$aV8pV; z@4$x|g~ZVbca}8n-;XXYScXGE3KD=76M{sI;>_ZchCRJle&pI;z)YENt}FO`&+KWUhaZ7)sWdsk_PuXzqF}r<6rxz#m*Kz7G`gi=e*y|l!cy4?T81QWJAmAs5 z@}%>~W#MqRW9>X%!xD|>Sn;P?C_QPNN#~P<0^|wB5m7G+E#h1j{wsyKJ?RO%m`Kdz zJ}eJ%9^G*b+9|s}ya>x+Z$`+dQ!^G5N!{F?AkXXfj2!0o3*q@HvC2fHxLX zdAvlqE;45}bDg#&Xy0fQd&;aT3|ZP1R5Tro(io%%@RO@)&r=RS{S%KaHzlD(vrMQu zfKC-4HBHMJH}>ThbwTtv=gzZIILRWvhlkwHf_A2qdF=3fy!?;(TAH|1CR5Ql>+IELGMntUhxYUJ6lI(MYwFl9FcHeE}kdQt6|@pj)V(E3Zx7=!yLl7#^fAHYtU zCu)E?3!Ay7!hbxTRZ*}|mQzvQq3rO`kAKmOy;69QZG4bo)ep2X27R&{Oc$3yOOVP0 zEm=Acz^{d*E&`LI_jRmQavRE&;aA~9wLU|fVYRC}YTt@{m3=3hMK;(Y>}m90P8JWp zqbYA(hWB#ne#7i3xYoA^DD}0yj;yMd8j1mEpp`4$ciP2^_}6RR|6OLg1qWEizC=c& z&8@gAF*;(t>tzv*KbO<));QzAx+N&?lh@T^5q-PHuEaiBrqEA!%x9SrBkQ2B2k~9_;8Dy_}x&Svbm3ZxrV6PQm z9|SWI6q0&S+ThpGlE_@Dn`8GJe{sdC4#{5|a@fXvMv-!j#847mtcFVmrYVbxiQN~~ z!Pf{4G2t^ZV*O)S)diK>b&kK(XXw;uiGRh5McGk*%J5e$Po*^cGYNpx5`N^$Ed=JT zLJXQ1v-aG+IGjcnm92!YNO?pPo-(tb*opK&A!Gn4fK;cg435E#un$T`)bQG&#!J>PI(0#iQp2`S+Ch zklnA;tQf@UKfB_5F1Jo~52__WWWB4M8(=5>1(&o(2mB568Ov9Ly_%c>gS&5wqT$Pf z84p31ad4FcqL^H1phFertn>pbZ0H8p4Y+>7AzdQjzz%{Nb|E%2__2T6z5~;e;N@*#bvg*4EkS}Wt}W??Qhhq zT1sYOG|Y};dlC!+g5@DD=cnOm{3Ihif~`|G&hePYAVMT&1xgg7Pa&MYoE5tKcR~xQ zbon2RS6^2KcSdneLe_%o_=?w>i#SutmdcAtd`uJ)))4njwn9|7Tfaqy5TYU?&wzR}nv|=%?kms`+3nY%?{{AW2gwG1*~Jj5 z1=>3>aYf{axL#|$(zKuO*&u^cdpBuJ!tvpE5;!h{8av};JyC?gP>!Myi)}a$(-k=p zeR=2HX^nWaLcK!m+?`|`GKm(kmv-3YrUrc@`D%B>#pFd=b;#(yV<`trhqOGfEJn$H zH4i?R23+0!`G^TZ9GA5F%{8O)^4oYzf{~WI@cW8R@_t9R`TtMQIn0vMfU5VCHY`qh zH}c2UEHk?}6{mWsX}8^k-mbkgYYj z*9`hC(VYg%^oiWY!M~vJ8sv+|94-*IS(}PE(0h#R&~Nj4kKrCe1j3P%KCN)dW5r_8 zM=r4ML)t>~WgbA^ZnhXM{z87GkCUE(RC$8!{VVe5Gkz;jufHOBcY=^g*iH}4AW+Y+ zE9iejD=oL%iHJOAx#Bj8`sM!q8+m3B){@#<7sK9|s?qwvPMQk>M!^Wa_?2j9nP|cA zySQ2UE-a?frL4oXwCCWJ~~+C|`?KF*0Hz>Clay7HsQ& ze!E#M()w_z)KZaoCg=gvf%0X-ism0)Yir}?T^)cy0%t51vcwZ2@Sw9nA=j~8FfkX( z6$x-dzYdxjb#;?g9QAKTU_NYkucV6dLdk-yQc@>_lB$W9S6`j>yXo#V42aq9MC?ti zP7nqg_*kulO3?U_6^a2 z`A{qK&QfBC2PMh)Gg{GJ`y==D_ z4@Lo0H~sj#0qO~Jjo@|nISYQdYKKpAVYH^^RGo{TeFxG>DG~D1%cIOO+>8-S;sCYE zX6d{kuOC<7C##^ zn^dGfah$I-i(rn;prB{{{2I56sa@seyK8U$afM&H;%b1jnI0g2KDcQzWrG#!>>d4N zXHFjblU6hJsT-9E=l40<_&%*dxK&25csCF9-;@5mz55o3fUw!@Qzx%MjEOiOwP zS9vm>RPQ^(OQP~^l4jg$dNvkExv(o?$aEqZLdy{%qHp)-LOP@Hcp@*_UH}$#1Sm@$ zs-~(~;So2Z=961!$eMU2!^>MEPp*h}#n5qCxrW-Q>)ntG3CS+Y#qgkjsCZ83(3`f- zb~QYl&&cjVPBNg1azo)wxbSdCDx44c?+XD++W?sqi4^O$G@W~%!joad2#%H`kQ73Im+$Yjj}1&HH^IbTEpB~v=Xs*vG2lSQnGxplCyVYqAAH5q54EPI-hcB;g1*N56tXFqpT$i zSlI2m3Ct(3i}a3_y~g;~bt#Hp=3;e*KBn>hc8&9{;CA^?f(9d2BLN^lhymHuht7L# z7Jl=8G6fzhrGB9iQF(l7Ht945VII0N#wQAdVi)o`c1JZ<*~~|nfQoggG{TCfrU?~D z)T1aC=_Z@-&>-^8tcsUVDN&`a*l;c;D}bD4T(Sx9K7l-O(j$Pa?+%01OZw#*@BZ}3 zH@#-nANdYZ_{`s9s5D$Cb5`>)b~)uNkmK$%5~7fVxo3*&JUagp=~0vLG1?1Dx2a&K z4Sg&~Ke2hDU)dea1YH5_^XTuJ16K(Wu1-YVFwYa+@_FKtc{Sbr2#|Pm2NEx24O(<* zf1J1ei`?<2j89dg$pu5~2E3Sb+Ml7h2`&oCr4!N*FmuC+Woo6vQg z8k4ltYNu-ZY(g^VQ8Iw2tS6rYS@9B#rMPm`X2(*5X-2w1^Iv`s#y76Qgf^H_lbIjA zJ^wbesYSW&u`O^;c_oeWC7*#A%yT1LNAN-ui1A$mjVM7g9=!?n%=yKjHf7*Q|DtxUJ8{&uz2m7wY14WsIsL-NIJnt_b?#SX6_$G1F)kx|+{EEEEkwnrBm}6`sPROf@C0l`Yd8o- zP!kwlb%eP`N(O=lD(xQfvqsmj>9r9rr@G)V9aRe;V8ItkgqCtekm-0%Bio#1b(s0o z$6DR~b~HtL70;$`^Y`2JIV{7es6b;G<+;T}rWO_LI<40^u zL5z`LVHan43v9FU$g=b{Ru;4FYrtYry+OCo`AKxFv~I5cu$X}qTUO%HyebFkX5mdJ zDZce5e9Y0El8CJkQJ3PFzKC^4@&USFkwMV#NYIY%`MgSk7ZfPEf>21i%jMk5$w<xm!{yxOkJbhp z8%RSroiktLsIJ(2g>XA&`tQpCC*m(e6yY#<=+0&lwseEG&pPWzO$WNp{`$Y^bJN<# z4CaZvZYxJxNwOytQv8AkGY9Kl)o))zRqaDu#>l?zaEpC7haUU4+|8!FptnFdVi`qlXjK$*NX%7R@b-a8=J-BCVl;7F2D{9Y=`l|^DkB|Uw??j8{5 zA5GZ*pfxbDAol6*_3Q8W>}xV6OB$Y+l@lF|dTe73n`$u3%YWE}3k?vs*;>%g$z-dV zV6ctx2dZ_IQ7y8A&4*!=WKt$vTH8M`o?Z4!sujP8mj^%pMZCPtOW+E zn$PSnLhbuwVtQLhv|^ttGctoGoK!n}Ay2=NUJ9rfe``bQ++8^^3@S*Xw%Kzluh160 zK%)!}>~xRYlir{8t8km&inP3wehT5ou95U@35==$+ z@uXfMU1mN*fdV3OG*Y;YKrzV{B@oQ1xMbGxIBv(w@8FN+knkuD>@bTx9;lU&!I6C4 zD5Xf8TXKMX#AcJ!K2MzJ!;{^(WaCy%TpMZ`f&08w5+-ZHO>*=n+@PWEgV+yEG9Gpm zsdGdx|FvU=REkfmQu}i&FTv}-x}+hNbfc9@NWvR@wm*IlbQS)Kf)q1g`jAXODc$6z zBC?M~q=4|A2)Drty7^2B;piniLQ|>LtLHl&YTH7>6_a{M(}FOPrzAx_Kt5S|Da|NTMNYNwfIwY%Er}NM>812AXtlKf!dMqDnvx;n1(+YB`i+X z6xUsK4kpn|bd|X>}RPRGZj@Om;>JV;)9b<~`Zh6~W~ERhC*XItPT$ znz&wm3z=e7Phnh0jwo`u?|Q1{QTR=rPfVuZB|OP5_#}q%fK2s41k+`!dV$&&An76A za55s2kn{N@G6;>jw?B-#hS{p?k|Q6xj=)@27gBe=xXGTWe*|tpc%PQduE!R)!)|^d zjy$z8;*I{TTqU)xtFOQQFxGlixzNE7TzvN5PvO zec*yq4@w0Owj09z=-po$2JbWP^W5p&pEK$!gH}WDw)LjWk2lI-0(Djtgbk!fFV+NT zlVpjKjYpmS=eL0l1)JD)@j&)!2;Ef)V z`z41(GF02gi)b1py6C^O@+YpiTXdqr{EpjgtT91^9l4H(RJVZ-N`J7QS3r~WT|N4- zCvJ@N{*YkdMb(Oy91ope=JO@Uw$`fXx>Cfq*G;mpl$;jE#|I;Li17V^INpgnB_nmQ zj7r_Gw0vw(;WsbL2O;Trklo;55#W^T&8bQnevC8vK zfA`~AVDZVSRzLwx17;B?4yL)@map_e2bW5k_}rIFOvn95&MYqL19PbTh5Yspf1?&_ zQ3J2=qJVEVHr}@t-{oC@{BE+cCmJ1^uc-}6;*5ywy&S(Cy(eq2Ix^MI<_VLPQ{KF8 zI*@s%typSFYZ&$Z(w+h(Vf&j0r)GWDk81sB5$)p3KU*@pq6G|*@4ax-GkNihEo~?! zchNg00&9MAq@SPpVu*0a$8Mu71%2-m6th|(>rS^L&g$wWb}Xg629I{tFKXdpb$YTT!jlNji`` z;FXda5fIVeY-B-8W5OmaU*|qI8N=~xcZ*x*fC8Cu2dApC^0YqD4!pg7&Tq1Eb*r1j zk(51}Nyk0K(tkaw*|6wMO{cLp&=LqoraNB^nMWb4XxF|KFQfupMtV!X9nc$yN}1^P z;+EZRvFA(H$&{p$A~b8c6qssZl@peReW9-IHK*d>YSVu5nlnXY2+udX8A!J!ZC!Xp zh||fKt?$~2@f8U2_H|{s+^A^3TdGL&tjHEnrPl|g{40{KIyTQlJ2dSL_~>2tqjwn) zUAKpLtz<36Ryl(6O)qx3{z`U7;SdI%F8t)Yj_Yut&{5FyhhHB0jMJ#DzYoa~pWjB#Xr zhW;qwOliSyBQR&_#d}z$&!tB+kz3GYY%uRMc)c7MN!MwIR?BfN{{(csi-zzhq-Jd;f=m8jYfGz@sxIN8iAA!J zJ)4*q!@QrmV5;oQdp1>SCe8aidVL7ZvxQIF(GEgfcXZj)6I(B|y!o7@_YiyubE=xt zg)&n8#R7U83(qRcBak^*#NePDr3F%IOVGvNdQ$Uarq$QM) z6ohvT`rr57_w(HE@w{K&=M%?q&9&CG@)zfM{)p7RuZ)L7jdSJ76+BfH1>GxGt}$J? za<%6MCU_D&DI9j?iZ+j`!aaT8jBlT@1IZ?TiS4jIy%qV?nUUvS1Ex03p&~9z#3WbU zjoe2iG`VEe5xUhCPjRLVIiheMk?H71MLn&1T_4Qy7wgu`(!2d^_iC%LQ*ZU}oe!_9 zXc2oP1h{_Bkg)&$<3LZszS?7ZP`Jn3Za5=SlKHNJg2M7Oawha2>i_@$F!uNLb{r;2 zOgH_!A%w{4u6%4zy{&Q+KchG-k3DbqR~pyxt_JHev(mZ4T4!&zo6dylLt3wEliXyl z?^M(9FtMaoCr0c1WS|Zq{3y;FJm)R+CG27dB`-A0G zX7%x?!k6H%PnEB`40`3QhIl+(18(;Ha?1JqbTuN<>oA|yM-X9?clHZoTHN(5!XrQ( zw{s#Uc|Iq;nsC?J-jSSK_8rNKWTT#q4s2bl0H+3L)%vjgRFuFw>+?imcE!3qG zC4^Y3CM06ed>FpYpVb!Me1AM|uEt65tc?RDP?@*it3T+XGgv{FI-8q1dxKxWtm!dE zA@8H;L5B3Fe2LnAx0Md(LvmmNDsdWZsN`6rCui2H@RZ;Qk$_lyY zuA^r*udj_x{C+R*po?27L|6dP7TJkX(q|OczTgaI;bf^QB9D@{%`-ZG9eA zpcQ-y&`7PG(|{JyfN3X85w)IX9{RlYRLBoO1ceoJ$leKB!K?6d0^-!Ao>02jcxGT= zvaq)N?oP&pdM5c=dQiEi1vK-QPdEuIfF8EF1%7ZItVjxtc*U+Ly__p}_?pT)m(zPe zC@=YBRm+0Gg?(r2ueW1_^S8+TTIb8c ztmEmAJOVUi+89q*{PrSS;aUw4nS~jxPZ}~_&D%$)_o${z-?_bv`M#Xg0w>_P`|syxLtk#F8PBv4-T%jSF3#>sbD*N%Zgl!-xvxU|lO zV_5s8G5EKdH4p=M7LXugR`|u^Z1I-7v6^b9LjfsLLS>gY4!l`!-7|%0+!ypP2&0^#?10Xk9ZG4tA%$%CSuJ>2AO)EmIjghC~$lJEalD z*ddM<*^B`XrTfWwAZ&UV%o8ZCZ`DL`3!F%Zl)3*hsh8|I=gbEh+qm#xq;%X;g^U@U zYYeXfd9-TJkX~@m{v*=DMI9-;gA9rFysW$+lfyH8iEC}?vXXSa*s+4jHAx+z5KC@0 z3n^%u)H0wDrDMTlh#lQa?<#sg zCFauGxR)y6p}j4lYIq-uED7$b z$jy417A>e9CmMe`VVz*T98t{fa*fOZ>B4O<(gXQ1Ei*@L6p^nn`#g&Pt3jCEiniM- zzOGk4VfjW(0y9~}$_P|$ulIdyiv#HEC#UY`r4}LvwK$C*IkZ%wYxjQDQAMcq@q&(K zB+(stM$AOvVHC_)8Ib%m>u6~;?+bL4`_kgkIZ;*Mz|car`9odyun>m@5#>*Mq$m}! zA^+Rb?)cptC(zQb-5*~0bwXDZhiPKB)XS5Z0_8u3%16R+gwd#*eTH5g}}t%zrqxEovSpXaDr zc+N!@$HOIoTgb_a=pWfha~;Atkc8Ey_-DQ~nOzZ<$UO%hf!4L9D{OZvRWAQYS2HmK z!&_qWFXqPuXFRnu%XAw>1LpnkUvjZ-?i;(cTY40BZ3^ceWyLS?4;S%j>^ zjY<&eX8g)c$9-@REi`04x|WcX2w4$p4na1Wnk*Y228f<0$9lRqfOT@Q5%6mEY9Ty% zv?z%ZZ_TS4KIJ@rvGNuBU{z;N=0Is8%cJvxTe-EGfW5o}Zz;;IgOqi}P{%2xIGd~Q zbV4h|aR|QXJOFHGuhpXOra$9&(VPiS-zH1Nn+XL@QO0s??+~W&rtlIrZ}Ked$vfV> z$fentV z4I^0FzQo_1^ykZQ`h>2@bt}0BV3CWByMQ+d}*@Y2pSEhpAg1M(Z?N!?) z3fGdCcr&freK$=QYlTH|hZ2?wFp39eUX5W+x@q{6uE(kpe^OzZuywUnRwGYm%^ZrF z-P1+9HuG?D$Shb5Emy{J(8=GtrOF{?&T`up*D7JyzDpB~gMp+Q@(~BV=6b(N9ydN8 zgGY&B{-$RxY$AdEF$+~c?3bRP)%i?$?k1}(9ij&~UN)r^=--&RO^3g2_1c+{%kedfd`sR+dOA(bmiC>Q#a1Jy zn6_Wx?CQ9{jV!U=Od=mMmS-j6R~Je&a*SHS(F-LlA;mCLb+B@fA>AdUa(6>eu|cdsGE%F05Tzm_#=VSm@4^>i$Ggkshi=9sZx zd<1#?O)q3XDa#5RrTlFA!3o{b@;3k7mT6%Ra`$G|i>PRXpH4&x4KL)tt$@&2g8BOA zaP~zX->W0m&%w}VZm9O+#wXUTQ+*ZV(P?OW)P3{pv2WN}^Qlm`6)~GthBFJ7V+M%=L|O*zR{dzLtR5%fTC5+O?!+@ZY(UQ^76 zU&xz7@8m1%H{D_3^s9#~EnTO}U(1#B+Sv}t40JU&ko`t{l2@5tg5bZ%Jv1rNK=D41 zn9~(NQM#IPDok(}Q)GRI*n~;IWjFc~pOBFw&r2Qk%Ogg#d=5U#8^paOI?1DtGk^(I zR^GBkmFU%Wp(jHLjYfRJg=a)d?K{J6Kt@gc6`ncpMjE#w$rp(aGHDfUY_7O``V#c$ zS+>Pn=UZH#FKlh!kXA(g{EndL-c6Rv322@)iztrNyP#^lK_XxVnp5#dBBB1~6(q$` zse;!jWl%@dF0EXd*LEvdx>AzXKbXF%?*F^xU^6!!-CW>}Z`C@W=?q7tanKr!Qt0j5 z{fBNJP2azAU1Z_vGiSHGuCsp5&+RjZJ($G{*1u!(Ub-9zmfCbyq;U^x0Y!A?$*Zhwz`M${zd ze9UE>3Fp4|L2_}BO|wm#fyT4ChwtT{_see!oCd`ze^i=W3P-Qh zl7Ul>W7|Ck39SIe_N$q4U2@OM3;a^0!$WU`Mupzc!S*!cYQ6$fU&%o~MA&hlSC5>@ z1}`(Ai0?g0w^-j>+&GCR57unUC^t2FB0htW3c*S8UyO>|j<@xUMer!}QcF@yBS9ZI zi@|m_jG0TkxJ0#K!>r-_==P1BcZ^lmdK!QTf9bolaqZv2@SapRBs7KhF=x$}c>$43{2!k4#Z zIHYsmJ12I2rFAuqcuCf6eXa7<9L47_R<#j~L0*h?$V0B6$G95V_%~_TAo~ztRlvbl z8P3`rXU2*CzMC9+!#GE@!Qy1qBtktpDGL`&bNpiTnrxf5#Sgn%IwMcb9^O}?_ivD3 zvDb21C&ExVLQg6>CARXbktI>B*180zS|>`EP%IB`k0_*!JO6D%Sg*tZ_p0T>daDO{ zpFJa22n~8|gy!gcasRdw#H-1{J6FuQL4NSAxP@HY?QTQk+E2+%q5!uS8HzqCamAkC zqJvd_WE`Pvz17xf#!2~BgsRdK%6S({8%wjAMp*DK#K9*59#byJXW{ivq|uOM9(x^Y ziB~AU=+vz*Q&irL-`0vXqX|U?I_(VdxS5@r(>;)!%8Sa zl|2|RtPVaq@vEMnUz>O#&1|m|ouN6jQvQh(5*fuMG`FhASO~3io(u`SLFII7t}CFC z?BfI~!Z!R#ePRP39NINN-BF#Ys!T$20J8JnmNJiep% zW>o5AOJYKQnCHOPT~y*jga##gMclu)ZTepKk^dblLsSQB!59_=r9x$ww8kX|Qc>jN zjCR~&q#3_7dH&=oc^;+U#gY^SUhWJUtFBqb!cb!xa0_E zxu-F&AN_OfWMscI!$E#IIO>?dr-mDS+!H%ytNQL z%&XiV7G^_)2ELKOijgG$rI9^A<&%`*N9qTnAuwVh?>y`$VI$Q9c=zp2`Vso{Gj34O~T=qLX z&M0J)=KL7$KM1Gk_DW2?nhN16yq(;Omqt@ykf?*?UCoQ!bXz*k&Hh&8vC5kwT-}fn zx%`;z(oh)8FX}#wi>txREbuXtTlqZgor@jahSFt3So6)bxr}r5H=>eX|0lTJian^bQ4BX zGnGp+amQprE9so_)vRu(QGfTG-nr}>lbtQ7gms@;1NyM2yu#XLDzt8_I}11%7D3~5 zCSjIP=6I9+%_q%F04V@1(N6fN#rUT^J)iy73y;EAeO{L!d?YFnGFfFKBRc%)a+j4g+aE-CC^#g!oV6ld9WNCR{+|$!K*nFct{bOQQPAWtz0H0t%RVudQhE#~*ve=by zZ`y7$Y}w>v@hVwI-lU>sjdvQuz8-N#-RO?UHww(PmUJ|_IMy-j&%b)^XQK>EFs-4| z4)oAAUyb*Me+5;cqEMKR+EP7HQmn_@LTbau$}59a+{{H?y%&%-2Q}VQovH-MlsD~uq_Uh`CKSo6N>~W0it@+@oT2>Lm6)_c; zQah%l)6sXc7F0y^@o+{Rr5iX5RppF3ZrMKgf!@ZDr;}i_e${(K`tU$U|_&q<{Jomk;gj3J*q%NA4v{?mx zP_)xzxUi2vCahTQ51yX4vRkP6_ZbzJ54hZz3-Bew zypKEL)2y!){mN#!QC{h#lMPEwt>Pkjj(l!^i3Y}nl$?IO=;jZM6AkiKgC6;_Pbc$6*%-#==-6!{weYJkV>goH}k^O4c|^|qb0 zR6Vors|jl|(nmdU$ab%DEs2-@q+i#4Ayl$9IQgZ5yjkb^;eC+~dXdAHVH94A^+zjr zD7%9(R}>PjXc1{3%StcDJ8!%0I!d^qiQsy(5o8>3^ec;|e8h$Fw;4vwU>(?WI`S~5 zsLAP3^-WxJ#Iv!{+f1HFgx{Bzd}u(dZyc|$L%g!zIlYKZeCa_tP|R^c6B;Ucb+XSw za`t{yJOJuDP@j5+tXL@T+YR&u`H0V?qHik4D?WncFk}pmS)Ysedb6f{PK_wqL<%-& zB;x!9qmgJ%oOOfwU*irPUPS>braZmV`#=D&9$)HFl5LU70VRRXBfuo%aMuUVm&2)) z__IiGR;OkGpi@+=ID)CAp4ej8O7@^6usI%m9mO_GF=4BQ0^U>$dlt|ZKnQrno8Uhg z{Z=$^0HwysOz{8*sJ(slG~pTexo;CW`Rs?%{tpuiNrtDHzt{f`N|0%Nemr^b%cVNZ{QWX)>yb^<A`a8c{wwOyiLWiI%NjWfZQGewpVM|;TamUiVG%FMGwGM_lQ;bXeL;hoK=-yNDy&o z2_4!24qd|-mHtEd8fhf@r6;WVb20E=M*!@hAAbR`erZ6{w0NH4Xh23O(_}@insUU$ z(D&)JS-Qx72qG2GQxEFX0&uSazq*7Cn-#RMRt>w2O5`6zymXbDcIoJ_^>;7WE31QL9Mu95K>(Dje=y%@4CVyj|1oxvx$aAc{t4);$;Ub zs8S)WpVHDpyGFJn=Q>1nCOJe-nNTnA5COKIs;*kETVO)Z|w{)PVP6xAnOA|@uLT-bvMLLwr#)Bo*1 z&pFtc31*E}EjBlpdr|(>Zx0RHTA2mK-Eyg8v!c6?>`TA&2q(RaqdY0fBw-F}SJYv5 zcb~x4uhM@iX%L%pFD6jL#qAn)1=nkX6zWG~y!h^sHG5im`5;YFsJ7j>oz;H&H}EKc~z{ohLCgl)q#YsPrKNlu?YbU zj!9o~@0g4~H>T;U7tfX;xt8JLGja_U&@>2*AY$;F1dl?eGRa5FpD*mIS*jBDi+#3C zZUzb9+>YMlkQO2X=Md(Ewc5b3D%Oq*Pl1|0bHJr${t?dh8uyB5io1Z~}&HrehX6*ZfwKilKd8z-_zdjMX9_BWNsB#0m~h(Je0|8+Abi><`y z8S3cd?R-+`*V|Fa1h7qfK+`aBB8YS@F?F3dQ(AYD+>Bf{6V1mP0W5LO+|nL6FdDPL z3VOhID#}5nO(SSOxl`u3o`JySt!iAmi(9*z#>o!OB_BY_H(2o|6=Dzg&OHyPG$ewD zG9h+B@6~Q=e#gYG!Sy`v+>7N(U{9=|2Nuu{z;||(ArEXA_htt^?DUgw&l?H%{i^t| zb5zwhG7y5Cj6?Fu0=~0qWlAr!7x?g=*Rxv8OQ(OW!=aHB_Ey6voW{LE^6|X8;PzQT zjxqsX6QnNkR|+6(!E##%Kr8qae1paxn!Syqx<6QfOof17VKW(!GF&)h z@x$)9nd=b!Vfv*9ebIw9Hi3iRLO};J=@2JexXi(n$?kZ5sW@MdL6YII#K|^3`+;sz z7jl(>n+~kf#pi`iR8|j4@)Z0)19P%vTwGPvAwWKgmVOkL`6$Y?@uqp+)x#XV!#p

    0euEXl<=hIMX7m~gn37g>O%|e`tVq(Wu^g9P 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/boxshadow/box-shadow-large-blur-radius-3.yaml b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-large-blur-radius-3.yaml new file mode 100644 index 00000000000..49d53e721f6 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-large-blur-radius-3.yaml @@ -0,0 +1,14 @@ +--- +root: + items: + - type: stacking-context + bounds: [50, 50, 1500, 200] + items: + - type: box-shadow + bounds: [ 10, 10, 1500, 100 ] + blur-radius: 200 + clip-mode: outset + offset: [10, 100] + spread-radius: 10 + color: [255, 0, 0] + border-radius: 10 diff --git a/third_party/webrender/wrench/reftests/boxshadow/box-shadow-large-blur-radius-ref.yaml b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-large-blur-radius-ref.yaml new file mode 100644 index 00000000000..29555c8f1dc --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-large-blur-radius-ref.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: stacking-context + bounds: [50, 50, 500, 500] + items: + - type: box-shadow + bounds: [ 10, 10, 400, 400 ] + blur-radius: 300 + clip-mode: outset + offset: [0, 0] + spread-radius: 0 + color: [255, 0, 0] diff --git a/third_party/webrender/wrench/reftests/boxshadow/box-shadow-large-blur-radius.yaml b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-large-blur-radius.yaml new file mode 100644 index 00000000000..13b2239658e --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-large-blur-radius.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: stacking-context + bounds: [50, 50, 500, 500] + items: + - type: box-shadow + bounds: [ 10, 10, 400, 400 ] + blur-radius: 2000 + clip-mode: outset + offset: [0, 0] + spread-radius: 0 + color: [255, 0, 0] diff --git a/third_party/webrender/wrench/reftests/boxshadow/box-shadow-spread-radii-ref.yaml b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-spread-radii-ref.yaml new file mode 100644 index 00000000000..e44008fcb50 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-spread-radii-ref.yaml @@ -0,0 +1,21 @@ +# This emulates the result of box-shadow-spread.yaml without with a spread +# amount of 0 compensated by manually inflating and offsetting the rectangle +# and border radii. +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + - type: box-shadow + bounds: [ 0, 0, 280, 280 ] + color: blue + clip-mode: outset + offset: 300 20 + blur-radius: 4 + spread-radius: 0 + border-radius: + top-left: [0, 0] + top-right: [140, 140] + bottom-left: [140, 140] + bottom-right: [0, 0] diff --git a/third_party/webrender/wrench/reftests/boxshadow/box-shadow-spread-radii.yaml b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-spread-radii.yaml new file mode 100644 index 00000000000..902c7d089b8 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-spread-radii.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + - type: box-shadow + bounds: [ 0, 0, 200, 200 ] + color: blue + clip-mode: outset + offset: 340 60 + blur-radius: 4 + spread-radius: 40 + border-radius: + top-left: [0, 0] + top-right: [100, 100] + bottom-left: [100, 100] + bottom-right: [0, 0] diff --git a/third_party/webrender/wrench/reftests/boxshadow/box-shadow-spread.png b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-spread.png new file mode 100644 index 0000000000000000000000000000000000000000..6fbd03aafff9c7152855c91e2698ee0c2859dedb GIT binary patch literal 7471 zcmeHMc{tST+aD%W3`tFPrA0LMWo$zoBt%(a$})zS#vU2_GL@x7WGnlU5M>4tgC;7G zB}r!NDIzmQ9D{7{w>q6Vy}$GR>i5Tcz3+9M=a0GO`<-X*<+<J!IjZK_9d(I?>b5K2w6CELc;KWOzvvy3N-#&3UiL&6h{%J1;t% zEbeMwh)r#o9?`b;=|GJ858Zn;2iATv)Uv+hf6J9Ie^;J|V-*GB{X7!3Dp_2P{xptY z1*P34_$2xbK98? z2Q%t9N>8zsqIiPW5riQ4OtV;>3yZp-Fz8y0QoI#_#-CoAw+OKH+3i0^DZiYjdEW; zI6v-p+9sO-C#ExHE*#tR_ND$j zt*_2rw9I_lKD_pbWY(L)i$%M&$_D`-5imua5;`qDjNY?_W z(_?vhJ&aR*CIci!WW>vJ580?D9w*##>ZWS}-C1D`wGU^nC@LL?I$anm%#PGMU8RG? zygG6g=WIz6d3NYR`S6%o#;OU5C=Tn}zjPd{fnrKNKBgTZoA%UH-znRf+Hu25dj&M$ z!$Yt?w3x>dE6Ap~BIng6|C&<)lC*RJf$S4f>W}C?c;hN0r7GRk^X2Uvk=D`MeZ)$| z{U%x`HInqGabs2}CydRb(}i_IzE;R1=&M?S>lVo6dUUD*V9CjdRnbe6(Yq^+PNvW; z*Jh27m8LIpXCsBuFn+TK-Fj!Kn5M=XDbSIs{_?3kLN{!~mfj|kDa0J2xm{V!KG>e> z9jMZ23nYmf&Xlb_jPblh`}m$h6Kt*MlOhSU^+Rt4m7{rB6 zz97UmMsl}{Vz!u9dyuQ# zQ>Rqn3?c@rGQ~Z%=GOcoD4GPSkEYdYWX=^s#KMgUxYNA%jl0gOq!m$ZDCO={Q?Ot0YK3vB?E@EVh5HB9g0R zEyKs8YRb8}c})(vz--2qX{~HQR>79dQxI3(X-Eqx22Z_&eVDwE2>-h0rMk7wg-%nO%y zEN5w7maEYgl5yXX1(yvrB^Q~JSC-JG2}!ckfsKFb&=)TygNjcVRc2<<`R86@N z$lJ@)$k0_^=1wNXtl|FDrhgUg?{c7kw)f+4HGesRgC6qbb1qWSo!MziZov&OlqfX8 z_{KFzX#hnzi5c8=_;e?@Jt(#yL{7oi1*x0zb0O}EYRy96jnfY0SxGf$N`#vQ}1;(lt){7Q)`o61-WDU0xLcSIgf zGIQrj$TDC%BB=a9KNpQlg~4y7K;AT`?*1HEk5IQ;uE-wcX%@Dsu^3%e)0Bs+@9q2B`h;22ZVM z5*L5<0Q}v~`7R6pvYiB__|WqTUONdcCxB2!`voK7>-^4($bpbG3NUepRgIZ{`OoiO znlbZo6?iRWZAq-dp+ZpQ;Mq;j3y&d{KH)6IPOcciIBii%mGrL((-+-D8xnykD>cmn z$z^9n%>~cQb)tEBr-t!A)}ikb^K)@ZE#Co2zC;Klrtg}3Vz7Q1uH!JMdTE|P{;`^T z=fvkVF`~vqw(5Rco*#ezu`Ye}{4XWy@Bah@?@ZCre^(>EsRxSP=ji-hG5k@}Kj#0< zsquNC{#pJ1F-`wTo_~_(Pwe%7wDLRyy7gsdgdQY_c{;3GfHufk=_v+Pu5?XZ9lVZQ z=dSy)K!2;He<=aa67*R~e!!Wmfdgf#N9gfUYWOAw)LJzQ5|%gmDRPEIqXys@#^wHsGZLoS~R(wtWD~ z6lCWDS>EM}iT)KYfmS+04Q<*Kzl;2$05B|)n=M6}Ge+>qRSxG)?w^$f;0?O7Y^{8o zHnrUj36I2LS)Rn|$h3ReIt@)pwgVIZg%~aM)8GhTTeMfV0VrWX1Yl?Ys~XltRj2P0 zie4Ax1GG$|cwT!tKG9@zjIe0%mg|~oFhNQsmK?GWp~sC1gJk$SAa#@e0~A@_y(Jx= zX{qjp1W-;s2jQ$EQWxk)8`f=wT<8qt_Z|gwv%UwNnqq;J;eNs{n7Gl>=v|2)8lL|S zG*TyY#zr2JFK-8yxyK4D+rSSs9!YZzPqo_�9K-C7qwB>#Y78`0rycmjq*Tq#x9!JdgP)1T0Ubcx}&R}&F{9r-|U@Z3C zzHE*%{%IiDTgvirfhR^d4PC9kkSRD>FF4UF3zPDqI*~^oqZ7ItBgoOb=q?acn)PrT zS4>7E4~JbC0wxvzvk@@#X_F^2>F9)}D*|L=c!VA}O^xsV(m3I1uF;N>D6z&oao1kf zcNu(8Hl@Zmr!Eo0xz|w#ha1qQ_@6`YK&w{wPIM$ezfrvKurMr!D`|Y1gpU&Aj@cY5 z3V|WFK-nnDDOVAeVL*?bJ_}H6Yp(mP0qUkLTMWA0;OByD7}+Xq9nNR6@l&@)ne3ig z7c?S8dcU~2Qdn2@aa<_pbE8YY$ee}%ptWH?d3v)eIE{WPP)x@CdgY*j(8aldv3Z92 zInb-JPF!EfwVzu)pwoTA9uHvoyq!WaW%pSpx+Q|f$8w`0J=LrHJeh7S0Km3FNoyTV z+r(FXXn(}#-Xclm^buyWNx?m#Ez}Q0ZLU}Kt3)9!s$|Kt6sNG(G>skn8qj3JpYyvl zvQ;c>gNa(fqK9b((UvUxU_+didT>HCgy=JVF=7R~vgj>#ep^pNh{{Hzv&`3zvk+)r z(;=~{S%$+p6QFm5M0il=G=sZ7%;+^YrSdoDm*>8q%yE|tEfxDoOH=3g%t?z5BlPN~ z-@Ll?$kv__daP9>*TN%4XK^ldbi?MGyB=slmH^_z{qFa~#~=JlYZiMyms_)354LQ1 zdERQ|no;isomfF6y5D=Jg*8%a~4qfF?f@AO_zayTH!! zEH#QP0g{r68miMssnS{MbDd;2bnG4It1v214Z3y5)<>lKgcVC!-Bzd>CE1DkGSdpAEfSA&%S4=3?dElAI{2(vbR2{5b1U%JyGaUPFg=)NQPQHE-o97 zg_rl{Z^LMJ%GN8v#LIifd^#f{w&7OiKZM0)2P+WJI)Iz?fh?$y?NDASM-Gvr#~Cda zPvMx9$3|n*Dcw;3^#qV%Er1$xbOlSCZ|hVTP|ZC^cNX6qejf|4P%RQQy&qkw%kN9! zB(15E&&7hbK(F=JytDLqztSuQ#$<~C9_fp*030`zcYDZq@!phdRIhd zsQS!gA(}TKa%EQO?J}RE;8Ar8K~g{`y&67ji>Awz9n*YRBYDh@DQpzPNBo0DOT=i`h#VhkVL&~}{{x}6Bes_dDJL@ZGtfo>bC;n)+;J)F*-pqUA3b3w>o;sJxs67C?NzfN-&#D$lTpdEzs{@Z~s};k$&-H0G(_*P;jnzR&Ug_EKI)O}p|%44n9 z*;@6r=636`^`_~n@@s{Kv5i{E*~$lkE~K2t!CXsRqP)XYkVD=vzyndFwJ`gxM~;$p zwX;}KU+011_E0-2$J@c$+^v2C37HZ5mZ;RrSG55?CuB`c&MOCSrQEhP`2fD#gcDWC z>sz!!Qg$uJu=NSm8mEV6YT$>%IGA+K^)4cg9@dX(n>}#HnuFHY6RhD}1%5@` z4G;><7SC3+cNo*4Fyy*Jp)&p3?My{?60J#Kd5@wp>B$TBll_+}6m8N%iio+7JN${6 zHp`$rOkf{94jtE?w_qa0tl`VVGfvHqLf0v8@;vI5tqbi78#~fd2c=K;&aWogmSOyK zL>2d~NtUfY!NJ^*eEfD5L){`86gA6mBh)R}jjS#x+q`mUdK>VLg$J<>lcVJE^^UDr m`~Tk!+*jNGgZsgCp5NNCZH&*??*}eIK?Zszx_5uGKmT6>Ao%nE literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/boxshadow/box-shadow-spread.yaml b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-spread.yaml new file mode 100644 index 00000000000..0add7519107 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-spread.yaml @@ -0,0 +1,68 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + - type: box-shadow + bounds: [ 20, 20, 80, 80 ] + color: blue + clip-mode: inset + spread-radius: 10 + border-radius: 20 + + - type: box-shadow + bounds: [ 120, 20, 80, 80 ] + color: blue + clip-mode: inset + spread-radius: 10 + border-radius: 25 + + - type: box-shadow + bounds: [ 220, 20, 80, 80 ] + color: blue + clip-mode: inset + spread-radius: 10 + border-radius: 10 + + - type: box-shadow + bounds: [ 320, 20, 80, 80 ] + color: blue + clip-mode: inset + spread-radius: 10 + border-radius: 9 + + - type: box-shadow + bounds: [ 420, 20, 80, 80 ] + color: blue + clip-mode: inset + spread-radius: 10 + border-radius: 8 + + - type: box-shadow + bounds: [ 520, 20, 80, 80 ] + color: blue + clip-mode: inset + spread-radius: 10 + border-radius: 7 + + - type: box-shadow + bounds: [ 620, 20, 80, 80 ] + color: blue + clip-mode: inset + spread-radius: 10 + border-radius: 6 + + - type: box-shadow + bounds: [ 720, 20, 80, 80 ] + color: blue + clip-mode: inset + spread-radius: 10 + border-radius: 5 + + - type: box-shadow + bounds: [ 820, 20, 80, 80 ] + color: blue + clip-mode: inset + spread-radius: 10 + border-radius: 4 diff --git a/third_party/webrender/wrench/reftests/boxshadow/box-shadow-stretch-mode-x.png b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-stretch-mode-x.png new file mode 100644 index 0000000000000000000000000000000000000000..662079e9f4f09052a5d8243d41327b44840cdd3d GIT binary patch literal 16139 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 a=Z(Gt#{zc(b^Dw-)DO~CdXu&lA literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/boxshadow/box-shadow-stretch-mode-x.yaml b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-stretch-mode-x.yaml new file mode 100644 index 00000000000..225433e4fe4 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-stretch-mode-x.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: box-shadow + bounds: [ 50, 50, 100, 800 ] + color: red + blur-radius: 16 + border-radius: 32 diff --git a/third_party/webrender/wrench/reftests/boxshadow/box-shadow-stretch-mode-y.png b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-stretch-mode-y.png new file mode 100644 index 0000000000000000000000000000000000000000..8ba78979873b9a3adf5451fdc7f1e1366f9ec885 GIT binary patch literal 6245 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/boxshadow/box-shadow-suite-blur.yaml b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-suite-blur.yaml new file mode 100644 index 00000000000..29ab5a2a439 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-suite-blur.yaml @@ -0,0 +1,154 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + # 1st row + - type: box-shadow + bounds: [ 50, 50, 100, 100 ] + color: red + clip-mode: outset + blur-radius: 5 + + - type: box-shadow + bounds: [ 200, 50, 100, 100 ] + color: red + clip-mode: outset + offset: 20 0 + blur-radius: 5 + + - type: box-shadow + bounds: [ 350, 50, 100, 100 ] + color: red + clip-mode: outset + offset: 0 -40 + blur-radius: 5 + + - type: box-shadow + bounds: [ 500, 50, 100, 100 ] + color: red + clip-mode: outset + spread-radius: 30 + blur-radius: 5 + + - type: box-shadow + bounds: [ 650, 50, 100, 100 ] + color: red + clip-mode: outset + spread-radius: 30 + offset: 50 -10 + blur-radius: 5 + + # 2nd row + - type: box-shadow + bounds: [ 50, 250, 100, 100 ] + color: green + border-radius: 32 + blur-radius: 5 + + - type: box-shadow + bounds: [ 200, 250, 100, 100 ] + color: green + offset: 20 0 + border-radius: 32 + blur-radius: 5 + + - type: box-shadow + bounds: [ 350, 250, 100, 100 ] + color: green + offset: 0 -40 + border-radius: 32 + blur-radius: 5 + + - type: box-shadow + bounds: [ 500, 250, 100, 100 ] + color: green + spread-radius: 30 + border-radius: 32 + blur-radius: 5 + + - type: box-shadow + bounds: [ 650, 250, 100, 100 ] + color: green + spread-radius: 30 + offset: 50 -10 + border-radius: 32 + blur-radius: 5 + + # 3rd row + - type: box-shadow + bounds: [ 50, 450, 100, 100 ] + color: red + clip-mode: inset + blur-radius: 5 + + - type: box-shadow + bounds: [ 200, 450, 100, 100 ] + color: red + clip-mode: inset + offset: 20 0 + blur-radius: 5 + + - type: box-shadow + bounds: [ 350, 450, 100, 100 ] + color: red + clip-mode: inset + offset: 0 -40 + blur-radius: 5 + + - type: box-shadow + bounds: [ 500, 450, 100, 100 ] + color: red + clip-mode: inset + spread-radius: 30 + blur-radius: 5 + + - type: box-shadow + bounds: [ 650, 450, 100, 100 ] + color: red + clip-mode: inset + spread-radius: 30 + offset: 50 -10 + blur-radius: 5 + + # 4th row + - type: box-shadow + bounds: [ 50, 650, 100, 100 ] + color: red + clip-mode: inset + border-radius: 32 + blur-radius: 5 + + - type: box-shadow + bounds: [ 200, 650, 100, 100 ] + color: red + clip-mode: inset + offset: 20 0 + border-radius: 32 + blur-radius: 5 + + - type: box-shadow + bounds: [ 350, 650, 100, 100 ] + color: red + clip-mode: inset + offset: 0 -40 + border-radius: 32 + blur-radius: 5 + + - type: box-shadow + bounds: [ 500, 650, 100, 100 ] + color: red + clip-mode: inset + spread-radius: 30 + border-radius: 32 + blur-radius: 5 + + - type: box-shadow + bounds: [ 650, 650, 100, 100 ] + color: red + clip-mode: inset + spread-radius: 30 + offset: 50 -10 + border-radius: 32 + blur-radius: 5 diff --git a/third_party/webrender/wrench/reftests/boxshadow/box-shadow-suite-no-blur.png b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-suite-no-blur.png new file mode 100644 index 0000000000000000000000000000000000000000..a8c3b1d0cab21d83b2b5e57db680d814925ecc1a GIT binary patch literal 21181 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 H|Ed20ni#E& literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/boxshadow/box-shadow-suite-no-blur.yaml b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-suite-no-blur.yaml new file mode 100644 index 00000000000..8544da8cccc --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/box-shadow-suite-no-blur.yaml @@ -0,0 +1,134 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + # 1st row + - type: box-shadow + bounds: [ 50, 50, 100, 100 ] + color: red + clip-mode: outset + + - type: box-shadow + bounds: [ 200, 50, 100, 100 ] + color: red + clip-mode: outset + offset: 20 0 + + - type: box-shadow + bounds: [ 350, 50, 100, 100 ] + color: red + clip-mode: outset + offset: 0 -40 + + - type: box-shadow + bounds: [ 500, 50, 100, 100 ] + color: red + clip-mode: outset + spread-radius: 30 + + - type: box-shadow + bounds: [ 650, 50, 100, 100 ] + color: red + clip-mode: outset + spread-radius: 30 + offset: 50 -10 + + # 2nd row + - type: box-shadow + bounds: [ 50, 250, 100, 100 ] + color: green + border-radius: 32 + + - type: box-shadow + bounds: [ 200, 250, 100, 100 ] + color: green + offset: 20 0 + border-radius: 32 + + - type: box-shadow + bounds: [ 350, 250, 100, 100 ] + color: green + offset: 0 -40 + border-radius: 32 + + - type: box-shadow + bounds: [ 500, 250, 100, 100 ] + color: green + spread-radius: 30 + border-radius: 32 + + - type: box-shadow + bounds: [ 650, 250, 100, 100 ] + color: green + spread-radius: 30 + offset: 50 -10 + border-radius: 32 + + # 3rd row + - type: box-shadow + bounds: [ 50, 450, 100, 100 ] + color: red + clip-mode: inset + + - type: box-shadow + bounds: [ 200, 450, 100, 100 ] + color: red + clip-mode: inset + offset: 20 0 + + - type: box-shadow + bounds: [ 350, 450, 100, 100 ] + color: red + clip-mode: inset + offset: 0 -40 + + - type: box-shadow + bounds: [ 500, 450, 100, 100 ] + color: red + clip-mode: inset + spread-radius: 30 + + - type: box-shadow + bounds: [ 650, 450, 100, 100 ] + color: red + clip-mode: inset + spread-radius: 30 + offset: 50 -10 + + # 4th row + - type: box-shadow + bounds: [ 50, 650, 100, 100 ] + color: red + clip-mode: inset + border-radius: 32 + + - type: box-shadow + bounds: [ 200, 650, 100, 100 ] + color: red + clip-mode: inset + offset: 20 0 + border-radius: 32 + + - type: box-shadow + bounds: [ 350, 650, 100, 100 ] + color: red + clip-mode: inset + offset: 0 -40 + border-radius: 32 + + - type: box-shadow + bounds: [ 500, 650, 100, 100 ] + color: red + clip-mode: inset + spread-radius: 30 + border-radius: 32 + + - type: box-shadow + bounds: [ 650, 650, 100, 100 ] + color: red + clip-mode: inset + spread-radius: 30 + offset: 50 -10 + border-radius: 32 diff --git a/third_party/webrender/wrench/reftests/boxshadow/boxshadow-spread-only-ref.png b/third_party/webrender/wrench/reftests/boxshadow/boxshadow-spread-only-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..a05bf7314eb6ceb5456fa51bdd8b101465ed00a9 GIT binary patch literal 19279 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/boxshadow/boxshadow-spread-only.yaml b/third_party/webrender/wrench/reftests/boxshadow/boxshadow-spread-only.yaml new file mode 100644 index 00000000000..d8dabde293c --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/boxshadow-spread-only.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 600, 600] + items: + - type: box-shadow + bounds: [ 40, 40, 400, 400 ] + blur-radius: 0 + spread-radius: 20 + clip-mode: outset + border-radius: 200 + color: black diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-alpha.png b/third_party/webrender/wrench/reftests/boxshadow/inset-alpha.png new file mode 100644 index 0000000000000000000000000000000000000000..4da7a8685770560b720f9d325283cca9254b9896 GIT binary patch literal 1670 zcmZ8ieO!`P8@3N097Cn0iOH0u6kr*HSz10~;Vt%I8}$xF_XnoEwi& z3I+;Vne*1qFxT0(tYx#rI5s=E@N(yWc^M9zXx_jGb0*$te1y)#v&j2|bnZx`fSaG2 z9A=bY${i|N=-lCk0+olON!p+8!XmSU?$W1X`liZqW9qDTPDH*zir1Bb(U)@zKDCIj zl^$Ai_0t>vo*{uc&BD~gq%5iC!$fbfcdggLIEisCKe$?sdGm*l$Y5pi?yF@0D073B z^5kO1iFYyMNAv7a(3{Xwsry5BYG6-zQ+F5&LPrdjCMG$l>YanVL{%^;ken3e-F&sS z9z)zM>Hv$%9hxDX;%@Ct3~{5z4JHZ5H!9{2y+iQ|m}Jj>imjw-Pjglh^R(|c+!jZ} zq-=At`sQdq^MsRrLe_+FsES7AvYj?Qk<5!d69cSGev)E0x&FW=yR%%$*aRjFC3hq2vCfuB>rnT-l z_l#21>MTBG1b<1985&h$sr|9cn!B7mn`;)Ogu-Ko=*f21h z_#v4RB0a4_u7an}QA(6i#F+uaWNIoke~r4cZSx5rMIy1M#mI*a!99e&zALuvKFB_= z+P4-fEcC}^okr~fi$@xBJNhHna_N}i^rW2*`naBYio$X1b^y`lbC zz;xdhPp_j3l1+7_EGcCX+5wUm?RCohTggizwtfpFo55P&gD*!Zu` z2Y@iDuBu=TP~EiI1^>8^@dt4I!Ri|p7hOI6dH*%voX8$?JEft{t}sA)ZKq2o@2Aq> zxNY2v>E1(_zYtsg9b^%>3YH>RVg5#2l!%R&Z-jcj>l&{|cE&2l2s2Fw|62S6?R&ER zSQxQsox0-{eCD({g48g>5wQ~lDiXtZYif{SM_Ip0cW3|m<&NQjI`?Mg6+noqDr<38 zTmI)JS#NUfGAo-w{EM@v9#^y=0$GYZR5pBZc=CvACr|N(q2|8D)lKu3DXHsir3_R& z3~+gt#+t6vN4c(@>rY9*UJ#x`WXCcIs1oCO*e4`2@7LF|geT*BPhnAxIlb;)vSw;-b7XA6*kQTXPFJX+UrqJY9cJA5ZEI9B1z zZuA5N0_tHs4-ghVz{Pi6dX6q`jH4dz5?;1LxLeihPyuad&&LzXsH5ZcVshzwIZZzc zpoyE?9*KA8l{E|aPRXtKy({HICT_y*j?&oCpGdbW3~hk$Q3uXp5L#0Aqhq1P$LTJe zP;TTA_H>q4iNu6X`nAE|1V>NZfA!zWi$tB2LGmh}0rQYajSLUj65sQ}(O$n|Y8wx;glkrVhkc zZr6V0^Y!QfPgQQg#0WxQB}uDnFi7PuOdQC98<8P19@?I$(_!+4Vv62x)p?b5bz6H) iYsFe_*<-({6;!}S38AXr=ChIi#VRN;ly$>DChPwoCl@>b literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-alpha.yaml b/third_party/webrender/wrench/reftests/boxshadow/inset-alpha.yaml new file mode 100644 index 00000000000..7764f0a2761 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/inset-alpha.yaml @@ -0,0 +1,14 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 100, 100] + items: + - type: rect + bounds: [ 0, 0, 100, 100 ] + color: [0, 255, 0] + - type: box-shadow + bounds: [ 0, 0, 100, 100 ] + clip-mode: inset + color: [255, 255, 255, 0.1] + blur-radius: 100 diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-border-radius-ref.yaml b/third_party/webrender/wrench/reftests/boxshadow/inset-border-radius-ref.yaml new file mode 100644 index 00000000000..62d5dd10c99 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/inset-border-radius-ref.yaml @@ -0,0 +1,16 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 100, 100] + items: + - type: clip + bounds: [0, 0, 100, 100] + complex: + - rect: [ 10, 10, 80, 80 ] + radius: 10 + items: + - type: box-shadow + bounds: [ 10, 10, 80, 80 ] + blur-radius: 25 + clip-mode: inset diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-border-radius.png b/third_party/webrender/wrench/reftests/boxshadow/inset-border-radius.png new file mode 100644 index 0000000000000000000000000000000000000000..b8e3d5034a648a7bb73e3a5dff20be0e0d8e612e GIT binary patch literal 3711 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 B;lTg^ literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-border-radius.yaml b/third_party/webrender/wrench/reftests/boxshadow/inset-border-radius.yaml new file mode 100644 index 00000000000..cb5c274f5aa --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/inset-border-radius.yaml @@ -0,0 +1,11 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 100, 100] + items: + - type: box-shadow + bounds: [ 10, 10, 80, 80 ] + blur-radius: 25 + clip-mode: inset + border-radius: 10 diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-downscale.png b/third_party/webrender/wrench/reftests/boxshadow/inset-downscale.png new file mode 100644 index 0000000000000000000000000000000000000000..40534805e1d8d1957e5bc99d43c86a0514483341 GIT binary patch literal 13809 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 DE+6O$ literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-downscale.yaml b/third_party/webrender/wrench/reftests/boxshadow/inset-downscale.yaml new file mode 100644 index 00000000000..dd0c3abc2d2 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/inset-downscale.yaml @@ -0,0 +1,10 @@ +--- +root: + items: + - type: stacking-context + bounds: [50, 50, 400, 200] + items: + - type: box-shadow + bounds: [ 10, 10, 400, 200 ] + blur-radius: 50 + clip-mode: inset diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-empty.yaml b/third_party/webrender/wrench/reftests/boxshadow/inset-empty.yaml new file mode 100644 index 00000000000..85d2840546c --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/inset-empty.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: stacking-context + bounds: [50, 50, 500, 500] + items: + - type: box-shadow + bounds: [ 10, 10, 400, 400 ] + blur-radius: 0 + clip-mode: inset + offset: [0, 0] + spread-radius: 0 + color: [255, 0, 0] diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-large-offset-ref.png b/third_party/webrender/wrench/reftests/boxshadow/inset-large-offset-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..92263830912f464af060a7573ad1c9087db7f760 GIT binary patch literal 2472 zcmeAS@N?(olHy`uVBq!ia0y~yU@~Q3VEoC!1{A5he*P4YVkvg=4B-HR8jh3>1_sV} zPZ!6KiaBrZ7-mTaO1ND#ckE>?dz^Yg`vcpa|A$T&JU!I=V5vZ6s-uv7$#WTrWq)_= z{Bty?_?#sJ!@E1{uQD?*{9_GdW>D#nVo-3r0yJsCix^_xN%0SFXGb`}Kd{zAdr*^3$Zp zQOpNSi~Bx)H9hwG_bb&q3H5=+R=pp8Ssi=4cm?BwU6#jx{D}b?^j1Hs@3{8`8{1AP zhIO+)f3lOi{_cSq&`Lu+!_Utd8?tM(-?jZ8iKX3WMi>kkVVKvk+bo~Q*fKFZ$lp2C cvsfLoOudS7snUxgVAG4i)78&qol`;+0K9`amjD0& literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-large-offset.yaml b/third_party/webrender/wrench/reftests/boxshadow/inset-large-offset.yaml new file mode 100644 index 00000000000..1cc2355d023 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/inset-large-offset.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: stacking-context + bounds: [50, 50, 500, 500] + items: + - type: box-shadow + bounds: [ 10, 10, 400, 400 ] + blur-radius: 1 + clip-mode: inset + offset: [0, 200] + spread-radius: 0 + color: [255, 0, 0] diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-mask-region.png b/third_party/webrender/wrench/reftests/boxshadow/inset-mask-region.png new file mode 100644 index 0000000000000000000000000000000000000000..5145148cd33d62af8d588d074ebc5ca608c81d50 GIT binary patch literal 23384 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 RfG1`%c)I$ztaD0e0s!$)aC-m% literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-mask-region.yaml b/third_party/webrender/wrench/reftests/boxshadow/inset-mask-region.yaml new file mode 100644 index 00000000000..08f52af181a --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/inset-mask-region.yaml @@ -0,0 +1,11 @@ +--- +root: + items: + - type: stacking-context + items: + - type: box-shadow + bounds: [ 50, 50, 400.1, 400.1 ] + color: red + blur-radius: 10 + clip-mode: inset + border-radius: 185 diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-neg-offset.png b/third_party/webrender/wrench/reftests/boxshadow/inset-neg-offset.png new file mode 100644 index 0000000000000000000000000000000000000000..462d46f8503e6340f3aecdde5bbebf6977a6a98a GIT binary patch literal 3607 zcmeHKe^}CY9xoQV#5uPM_0leuJ!hhVYu!l(=JH3@Oq^mRQ*)LVttknTpd@CcC9aWo zO>W7L)HAZIG?yS|xROcM#GRYz0AZGjKS1+G_zSoX*yFn0{c+E8_dNH{!NZs5`TqL6 zf4ttW*XvXCAwI$h<_=r2VujQ0U171{e;)i8t6v94)x~imIN!HBY)2e}XV73dsWXiQ z&tI4)8`lL=2hW@X-;-xnIe<@!v1jLMhoTh@MQ2W)Y$-voo?0@}Jx^<~pCL)TkF{+c=BgamZxW?1R%r#GN_#~h6ZKa`*vG~Dycp9OK1%pZn{<*ju%Trr|>!GvC8sSt)$V&%LK=f`D%==wx~CXUZI!0vq6gsJ{`Mju zCu$^>lJNBfQ86TuFz;To?laJe>$leTyKa*kdZ|wQ{(C5zXK^-{ntXU=qajWWm$;Z{ zh3GWjBRLk8_D%YWdSwHbSE32YGu#Q)Z7v}d;PWFWLid~i&@SBPGuM?<>S{^0AFYI- zuIPZ}_)w+6X4+C6o+Jma>JXa8Vsu1y*(tZJP=U)he6(<;@nTyYYbINOb+Lsk?nIJd zxU#F2L$ePzsUqz~fBsFS0wr%QL{lRv#k~77X&bwpMl+F|L*|hw9EPo8mkH$GwxY=} zHLR?n10QT*EUk#qo`5&H%*N?J8g%$pdD-t?d5&Ar5v$)^lE6OUqd!z)owvfqTxP?zADClq&Aj=2_ojz(G6k<;B}dA~ulf z(1S_s)sOvyO^Gq2N`>JwUNkI8O~~c_SrcLs#zQ!k5AnL^yUP<+Hl>Hz8f2TNq28S_ zs#SvASp6vCei3uNkEtrQ6?uXAR(=~H#7$gV52v`=B3XFPQIQVi|It$%?ID%j3EI?D zXqgfsJcr)UfL0j2(|l9K=M}d85#nWwI2AZS7N3W3z&jMfu3HQE=g05XH|qh!qLvpe8QLm&scZ> zT>wDutM?1Xo9a5{D5?~S%8-hj(X^5T3PF!^1r_|KV2+lxpg0BS3^vaJht2)4d=nMB zk&bXo`W_-G^Xyp0_zjTmR!tj-xpUZ+=BfNfyLi|R9-1|&FN_Svb}C*E5!K$fpb7Bw z=pKs2r8}g%`O(nz1?Z#sT_{~ztF#K|g#4Hcr|;9zZk0Qp z@FBCeVVl{V=2V$slN2nO={j0E>9-4-X|M06dRD~(5=~{>(2I!EZ=rfonF)G$7M$if z3WlE7zBrkwbU7Zt=7C5V2i@rK2J@rjH!+AkdYRR{!C2iA?Z7X>dX&iTMmG9RjVjYC zCG`*v-GSeK_@L}kCvw8u_z6V0Gj;Pg;Ht&>OqKUUO^XP^=|3E*h0{a*xveixO8$TH ze_oJ~aoM`ti4c1mTk88iH*-TR2=kL5ZX}5{*Eqz+dW3g0ok6FSDdsMRxS2jn(!m$f z0VhPhO~dSRGxfoIVmq=XnI%Yv--*(d+A!+KW8L18PZBwMOoDtz2tMDdwDNy}?HMcRk81TwKP5+hJ z`saLl0|+506ey-x@zh-^1edZ(f58y+7oaYbLOdl}n@eBP7U%6-SFMkBNa`$7!+O&< z-d@((0GNuc;^+R@sbwBpB6^cH*hch#a${PFH8yqinR=uQ+TYpBII4#ON{>Sf0e&}k zhuIWltyJ-)q8`%m>adQad5S|sin+gnN!HWv+V8HeXsHAS1P~X}=xy|VhU`WSe6nk0 zrl|jOPYy(+i*^bcRn~Sq1g2%CdjlYVYDuLInFhL)I`|DBD96~Z6H^kT@+h$cISsB} zM$ObG#<08@QLhgk>wE-We*UqnfbF+xYUYL z1Oi3F^fBO#Roa`y>i2-Iex!%rBze@4qhqrhZH}Gm50{ks zR&&-loEPvlwj9Fn&+)IW_cHv6Jvt&K8j3fGgfHFI$n~>V!|@bmL*hn}B--h>Q|N(v z0Lv=7b@8IH)v!x~HH@Fl&cGL;J;0kV5<{bPL|EXGwSS3YaFZbKe$sJsfm;#mQ#=kldQ{F`a#ugn5>?vf*@i zK=^jxo+wLJ5?zy=Cm7mEiJ(N=tzR@$U>+xxdEkzz#$R{!p00E55oVDO61V$1VFXtTuJ%q!GQp1l2!03N?>;b$T zJ;gjfRL|TCCW6Q&K8xvOohJ`k9w5ja?c{Rn$o{jJMW+BOz_a#!)%(M{;~8U5KFI!c z+r7oPPmOz1NODk-Vb|$&n3i*T?h_zwSHy7dldTiFkS;0Puzv z&3EMZQ=#RnB|q%XqMW-F;P1l(Bk j+5gI(XnA)vA)6e3t>Mm(gMR}Xz7@N}@nJVZK0ft#kqc?U literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-neg-offset.yaml b/third_party/webrender/wrench/reftests/boxshadow/inset-neg-offset.yaml new file mode 100644 index 00000000000..9fe63176c06 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/inset-neg-offset.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + - type: box-shadow + bounds: [ 22, 22, 200, 80 ] + color: blue + clip-mode: inset + offset: -2 -2 + blur-radius: 20 + spread-radius: 0 + border-radius: + top-left: [0, 0] + top-right: [0, 0] + bottom-left: [0, 0] + bottom-right: [0, 0] diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-no-blur-radius-ref.png b/third_party/webrender/wrench/reftests/boxshadow/inset-no-blur-radius-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..dfc23875e3ae7107e71020250aa37fa9b651e6d3 GIT binary patch literal 674 zcmeAS@N?(olHy`uVBq!ia0vp^SwLLC!3HG1frm9wqvNi#t&Pi?>w>d=kCaJ&{C3UmkK?y*N_>1u zef`Sk&MBQcf4lv2Z~G~S>(>SK_20_>Ev}F#EH<{i5wT+R>W~rse><%C@(n zDvP_++Oo5vCa1gbYk*w!V>OxjuK~Y*+vm@itJ?nP`t@xR;rv^pw%S{tFt$?vKIcL4 zr1SdgH$8PuT`=d$=a1Jnq&h#{Fx`26*|r!BW#6qnD{S5sm0d77-EwOFqJ5K>&5}R0 zJX7$q^HY|2lXrl4$@yU3I|J*xe*%uay>c8SB(MYyErh-XT=fp=y1mlhHsov6qDN(~ zg84)DzF56u?yoD(q3h3S=PkGU8nCkHPifBbUHby2E?<>wef4Xr*ZkNScXrOr`sKW| zKZ!SI`T5>fuS%YO?}PpyeEVhN$AJ3O4PUL2Hh130`+w)`!k1osfE*zVuACklKofH!&l+u&1CuGfL<8T3AHR2AHPoFvbG zR;0gtjIp_cDv>}EjqkG|ReRab9U@1tJu>e?_WlJatKsIji0 z9Q1Q#)g5I|$OeD^8+Q;fBr^KGZ^#VIdBdkL^hdUB<<4JoDhX?M8KZDt<{#~{mC2AT zZ*9zm`Rxw#8GZY6ZLM<6e{xfnjY%|=V^DvW6g+?DMow-jt7H1B0?|Ux&JBJks+%E| z(_3Rb9?U3T=xL$KCp6P!6K!ecx!)!UjziN^H-t3@kreLQsp!`6AB442`%hL;QNCz< z!6oV~JfCPgD%IBa_kehhW>Lm-k$nOWp@tFB=NDh2m3Oir- zU3(At4z(PnGaZN(!*z=;%VdL^)S(C=d?2DPjkvHa>Le|L#A0~18_pW;Oc!k&J3re} z{$sLNYKZ7CeUl}#RzTlOTU0#VVlFgv{sHXBhv<&^hs(M8I1RCwy4_I!rB(Oe(i^0; z33rc(&t}vs9LP}m55nXe?@@egw`4}? zu5+KN7r+cJCX>=#{7Y4cWw{h(=XTn8=8D4$V!qCC?%Tpt!KC+Sdrx3LoIyP_;L43x z_9dB4E=uyI92YXAOxN6UE0y&X9Nvw9#G~m!i2-wsk}nWB+#L_@SWyB)_crPHVe^+j~^1~Ym( zUJS!N`-Hud_Ap8vX|yOAMrkS#7h?TZn-$I)-;2kn5TYkogg^Ak%Li^=%{19`Y2s@!xlc1QEkB=X7$R#aiI&JFUrh4==Nc|*4^Y0(e{)W zEkuUqYB|_@3kh8{vBjXn^#Y1Fu?(XuQs%z+=X1_lxtH?Ehp>*&~hVQr9Alq4ZJ zUMN1u?}QoLbrT{z4jeKu%n5%$E3X%DXDW3N3CezxOXogNn90Ftuc9=OlDLLqN+kitV<kI|(eCM3)GFob-+yhChS)Yobv)ZBdd0 z2_0@K_8v1&=dTTiH1fTXia)VTS7oL~QPQ)`4|E*2lki#Zqwfj@bY^WKPm(fNtE`NR zzOVjq+p<}pTpDIQF>k+WFjeKN1_pdGeM+=^`R;ayP148$?2b)wv>HbIlps`no( zZzX1g~->*x#$ib*Fc@doABWj|Jzw<2|EGhr7uO9>MaYp#F%reB(owRX*mV0|> z);C9=k_M4t)VDaH)FauU z4rBzkvqr$R3VQQ*a*X+?cpfL6VeV*3MVVqRAYL%gL_g<+I)QU`%eIw;$T7XkDsP;eCr{bWj7&Y+*rcA*shpw+9D6=y+mN%k{5$Pyjd$S}p(isA5&ad*R$dm=^GTo0(hUP4qPSRI{BN zL@0vQDF;e(jIS=iJ;9qe41)D=JdAARe6QxjKzo&*WQ#w5R;Hq zhcqk??djHC#9v4DW!b1`DMkJac=k#(aS8w;-}$Du;8m_x0C_{Gu(TXliG+3 z@@2{OYbuD3?f!_sSbQXVBe{$tun4Z?X@pDKYM^)~#3t4<4U z>{pp(X&%$*n~dcS*A1jvNEv6hYreST2Lg+C^7WJu5wQJj#s-iq<|nwX!XZ@$J*xpy zt|YqYJi&kr#8IUvYI9{qCfUznE?NyyVn8lhs;Hw QQ`nmQ;ix^0h|kXb3t0#sApigX literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-offset.yaml b/third_party/webrender/wrench/reftests/boxshadow/inset-offset.yaml new file mode 100644 index 00000000000..17fa232f3a6 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/inset-offset.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + - type: box-shadow + bounds: [ 22, 22, 200, 80 ] + color: blue + clip-mode: inset + offset: 5 5 + blur-radius: 20 + spread-radius: 0 + border-radius: + top-left: [0, 0] + top-right: [0, 0] + bottom-left: [0, 0] + bottom-right: [0, 0] diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-simple-ref.yaml b/third_party/webrender/wrench/reftests/boxshadow/inset-simple-ref.yaml new file mode 100644 index 00000000000..526a1fa4e06 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/inset-simple-ref.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: stacking-context + bounds: [50, 50, 100, 100] + items: + - type: rect + bounds: [ 10, 10, 80, 80 ] + color: [0, 255, 0] diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-simple.yaml b/third_party/webrender/wrench/reftests/boxshadow/inset-simple.yaml new file mode 100644 index 00000000000..78f6ef18c16 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/inset-simple.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: stacking-context + bounds: [50, 50, 100, 100] + items: + - type: rect + bounds: [ 10, 10, 80, 80 ] + color: [0, 255, 0] + - type: box-shadow + bounds: [ 10, 10, 80, 80 ] + blur-radius: 25 + clip-mode: inset diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-spread-large-ref.yaml b/third_party/webrender/wrench/reftests/boxshadow/inset-spread-large-ref.yaml new file mode 100644 index 00000000000..bdeec79e35a --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/inset-spread-large-ref.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: stacking-context + bounds: [50, 50, 100, 100] + items: + - type: rect + bounds: [ 10, 10, 80, 80 ] + color: [255, 0, 0] diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-spread-large.yaml b/third_party/webrender/wrench/reftests/boxshadow/inset-spread-large.yaml new file mode 100644 index 00000000000..05cc5e40f2a --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/inset-spread-large.yaml @@ -0,0 +1,12 @@ +--- +root: + items: + - type: stacking-context + bounds: [50, 50, 100, 100] + items: + - type: box-shadow + bounds: [ 10, 10, 80, 80 ] + blur-radius: 5 + clip-mode: inset + spread-radius: 200 + color: [255, 0, 0] diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-spread-ref.yaml b/third_party/webrender/wrench/reftests/boxshadow/inset-spread-ref.yaml new file mode 100644 index 00000000000..76802f9db0a --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/inset-spread-ref.yaml @@ -0,0 +1,10 @@ +--- +root: + items: + - type: stacking-context + bounds: [50, 50, 100, 100] + items: + - type: rect + bounds: [ 10, 10, 80, 80 ] + color: [255, 255, 255] + diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-spread.yaml b/third_party/webrender/wrench/reftests/boxshadow/inset-spread.yaml new file mode 100644 index 00000000000..de09797dcf3 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/inset-spread.yaml @@ -0,0 +1,12 @@ +--- +root: + items: + - type: stacking-context + bounds: [50, 50, 100, 100] + items: + - type: box-shadow + bounds: [ 10, 10, 80, 80 ] + blur-radius: 5 + clip-mode: inset + spread-radius: 20 + color: [0, 0, 0] diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-subpx.png b/third_party/webrender/wrench/reftests/boxshadow/inset-subpx.png new file mode 100644 index 0000000000000000000000000000000000000000..b15cf63d80c0a27c4c663a358945c370ebdb2fe3 GIT binary patch literal 1810 zcmeAS@N?(olHy`uVBq!ia0vp^n}N8EgAGVdTfF-%0|VPlPZ!6KiaBp@pIj^IDbV`R zoBzyb9&4Ke9BU5n-Z{X#<^YGy0fuR7CPb86U%pzvdG%E8cxU@6PS5eLaA87IfXB|<4hvYM9UsUqjp1=X9jJjE-$h$X3&>WVZY3I0Bc2E6&+cKAwxFx}2S8Vcca7DK6312_Y@A|sGJFLp} zUumzXl-*Ok9j0VP_{V8qkEv=Vc2++=X8-ukUaPOazJ%|p_+19EXSt4n|DH%s&w%+t z_YPfOKi|(hf1cm={QB;_#ou3Dd3`y*ujJ#iv)^tAPOO~h7nvm{*gA#f>owIkYopT} z-`}&H`|hsw`fum%t`1)Bzkk8Ae_HF~^T9rUDa2~p-SSA++i$+0o%*{wmfSV}|GoV3 z;9%v}@^^7_t7?C-ntl(j{F()E#&ivX)sNnKc@r3(zQ9uDGye@xD);pCn>1iAs*(-)TH!&TYA0?Sl2Oly85}Sb4q9e0B5gW3jhEB literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/boxshadow/inset-subpx.yaml b/third_party/webrender/wrench/reftests/boxshadow/inset-subpx.yaml new file mode 100644 index 00000000000..22dc3f09594 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/inset-subpx.yaml @@ -0,0 +1,12 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + - type: box-shadow + bounds: [ 76, 67.5, 59.016666, 39 ] + offset: [30, 9] + blur-radius: 7 + color: blue + clip-mode: inset diff --git a/third_party/webrender/wrench/reftests/boxshadow/invalid-ref.yaml b/third_party/webrender/wrench/reftests/boxshadow/invalid-ref.yaml new file mode 100644 index 00000000000..4f2245ccaae --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/invalid-ref.yaml @@ -0,0 +1,5 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] diff --git a/third_party/webrender/wrench/reftests/boxshadow/invalid.yaml b/third_party/webrender/wrench/reftests/boxshadow/invalid.yaml new file mode 100644 index 00000000000..6c1fecb9955 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/invalid.yaml @@ -0,0 +1,12 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + - type: box-shadow + bounds: [ 100, 100, 200, 10 ] + blur-radius: 10.5 + offset: [15, 15] + spread-radius: -15 + color: red diff --git a/third_party/webrender/wrench/reftests/boxshadow/no-stretch.png b/third_party/webrender/wrench/reftests/boxshadow/no-stretch.png new file mode 100644 index 0000000000000000000000000000000000000000..c89ec417e6ab307d19565520806af1a5e5dbb976 GIT binary patch literal 3957 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/boxshadow/overlap1.yaml b/third_party/webrender/wrench/reftests/boxshadow/overlap1.yaml new file mode 100644 index 00000000000..1744983da6a --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/overlap1.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + - type: box-shadow + bounds: [ 50, 10, 200, 80 ] + color: black + clip-mode: outset + offset: 0 150 + blur-radius: 20 + border-radius: + top-left: [100, 40] + top-right: [0, 0] + bottom-left: [100, 40] + bottom-right: [0, 0] + diff --git a/third_party/webrender/wrench/reftests/boxshadow/overlap2.png b/third_party/webrender/wrench/reftests/boxshadow/overlap2.png new file mode 100644 index 0000000000000000000000000000000000000000..33401618904ff4d5a771d438e86ce4067df47a63 GIT binary patch literal 8490 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/boxshadow/overlap2.yaml b/third_party/webrender/wrench/reftests/boxshadow/overlap2.yaml new file mode 100644 index 00000000000..59ffb2c4382 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/overlap2.yaml @@ -0,0 +1,19 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + - type: box-shadow + bounds: [ 0, 0, 200, 200 ] + color: red + clip-mode: outset + offset: 200 0 + blur-radius: 1 + spread-radius: 0 + border-radius: + top-left: [100, 100] + top-right: [0, 0] + bottom-left: [0, 0] + bottom-right: [150, 150] + diff --git a/third_party/webrender/wrench/reftests/boxshadow/reftest.list b/third_party/webrender/wrench/reftests/boxshadow/reftest.list new file mode 100644 index 00000000000..92ff47e495e --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/reftest.list @@ -0,0 +1,37 @@ +!= inset-simple.yaml inset-simple-ref.yaml +!= inset-spread.yaml inset-spread-ref.yaml +== inset-no-blur-radius.yaml inset-no-blur-radius-ref.png +== inset-spread-large.yaml inset-spread-large-ref.yaml +platform(linux,mac) == inset-alpha.yaml inset-alpha.png +platform(linux,mac) == boxshadow-spread-only.yaml boxshadow-spread-only-ref.png +== box-shadow-clip.yaml box-shadow-clip-ref.yaml +fuzzy(1,396) == inset-large-offset.yaml inset-large-offset-ref.png +platform(linux,mac) == inset-border-radius.yaml inset-border-radius.png +platform(linux,mac) == inset-offset.yaml inset-offset.png +platform(linux,mac) == inset-neg-offset.yaml inset-neg-offset.png +== box-shadow-empty.yaml blank.yaml +platform(linux,mac) == box-shadow-suite-no-blur.yaml box-shadow-suite-no-blur.png +platform(linux,mac) == box-shadow-suite-blur.yaml box-shadow-suite-blur.png +== box-shadow-large-blur-radius.yaml box-shadow-large-blur-radius-ref.yaml +fuzzy(1,6388) == rounding.yaml rounding-ref.yaml +platform(linux,mac) == box-shadow-border-radii.yaml box-shadow-border-radii.png +skip_on(android) == box-shadow-spread.yaml box-shadow-spread.png # Too wide for Android +== box-shadow-spread-radii.yaml box-shadow-spread-radii-ref.yaml +== invalid.yaml invalid-ref.yaml +== inset-empty.yaml blank.yaml +platform(linux,mac) == inset-subpx.yaml inset-subpx.png +platform(linux,mac) fuzzy(1,4) == inset-downscale.yaml inset-downscale.png +platform(linux,mac) fuzzy(1,979) == box-shadow-cache.yaml box-shadow-cache.png +platform(linux,mac) fuzzy(1,685) == overlap1.yaml overlap1.png +fuzzy(2,691) == overlap2.yaml overlap2.png +platform(linux,mac) fuzzy(1,48) == no-stretch.yaml no-stretch.png +platform(linux,mac) fuzzy(1,9) == box-shadow-stretch-mode-x.yaml box-shadow-stretch-mode-x.png +platform(linux,mac) fuzzy(1,41) == box-shadow-stretch-mode-y.yaml box-shadow-stretch-mode-y.png +platform(linux,mac) fuzzy(1,14) == inset-mask-region.yaml inset-mask-region.png +== box-shadow-blurred-overlapping-radii.yaml box-shadow-blurred-overlapping-radii-ref.yaml + +fuzzy(1,5) platform(linux,mac) == box-shadow-huge-radius.yaml box-shadow-huge-radius.png + +platform(linux,mac) == box-shadow-large-blur-radius-2.yaml box-shadow-large-blur-radius-2.png +platform(linux,mac) == box-shadow-large-blur-radius-3.yaml box-shadow-large-blur-radius-3.png +platform(linux,mac) fuzzy(1,79) == scale.yaml scale.png diff --git a/third_party/webrender/wrench/reftests/boxshadow/rounding-ref.yaml b/third_party/webrender/wrench/reftests/boxshadow/rounding-ref.yaml new file mode 100644 index 00000000000..e6e2fb84643 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/rounding-ref.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + - type: rect + bounds: [100, 100, 500, 500] + color: green + - type: rect + bounds: [200, 200, 300, 300] + color: black + - type: box-shadow + bounds: [200, 200, 300, 300] + blur-radius: 20 + clip-mode: inset + spread-radius: 100 + color: green diff --git a/third_party/webrender/wrench/reftests/boxshadow/rounding.yaml b/third_party/webrender/wrench/reftests/boxshadow/rounding.yaml new file mode 100644 index 00000000000..8078b2e1d6f --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/rounding.yaml @@ -0,0 +1,15 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + - type: rect + bounds: [100, 100, 500, 500] + color: black + - type: box-shadow + bounds: [100, 100, 500, 500] + blur-radius: 20 + clip-mode: inset + spread-radius: 200 + color: green diff --git a/third_party/webrender/wrench/reftests/boxshadow/scale.png b/third_party/webrender/wrench/reftests/boxshadow/scale.png new file mode 100644 index 0000000000000000000000000000000000000000..49aa8e8e053c0cb268ddfc9462413da76678b7e2 GIT binary patch literal 13527 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 U4NcBZ1U?J$aND(`U>iE=ue+k|@Bjb+ literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/boxshadow/scale.yaml b/third_party/webrender/wrench/reftests/boxshadow/scale.yaml new file mode 100644 index 00000000000..12263b4eb91 --- /dev/null +++ b/third_party/webrender/wrench/reftests/boxshadow/scale.yaml @@ -0,0 +1,183 @@ +# Test that box shadows are drawn with correct symmetry in +# a variety of cases. This test checks for various scale +# factors, in combination with box shadows clipped by the +# screen rect. It also has come large box shadows that check +# the symmetry still works with segmentation enabled. +--- +root: + items: + - type: stacking-context + transform: scale(0.3) + items: + - type: box-shadow + bounds: [ -70, 50, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + - type: stacking-context + transform: scale(0.4) + items: + - type: box-shadow + bounds: [ -70, 150, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + - type: stacking-context + transform: scale(0.5) + items: + - type: box-shadow + bounds: [ -70, 250, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + - type: stacking-context + transform: scale(0.6) + items: + - type: box-shadow + bounds: [ -70, 350, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + - type: stacking-context + transform: scale(0.7) + items: + - type: box-shadow + bounds: [ -70, 450, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + - type: stacking-context + transform: scale(0.73) + items: + - type: box-shadow + bounds: [ -70, 550, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + - type: stacking-context + transform: scale(0.75) + items: + - type: box-shadow + bounds: [ -70, 650, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + - type: stacking-context + transform: scale(0.77) + items: + - type: box-shadow + bounds: [ -70, 750, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + - type: stacking-context + transform: scale(0.8) + items: + - type: box-shadow + bounds: [ -70, 850, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + - type: stacking-context + items: + - type: box-shadow + bounds: [ -70, 800, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + + - type: stacking-context + transform: scale(0.3) + items: + - type: box-shadow + bounds: [ 50, 50, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + - type: stacking-context + transform: scale(0.4) + items: + - type: box-shadow + bounds: [ 50, 150, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + - type: stacking-context + transform: scale(0.5) + items: + - type: box-shadow + bounds: [ 50, 250, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + - type: stacking-context + transform: scale(0.6) + items: + - type: box-shadow + bounds: [ 50, 350, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + - type: stacking-context + transform: scale(0.7) + items: + - type: box-shadow + bounds: [ 50, 450, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + - type: stacking-context + transform: scale(0.73) + items: + - type: box-shadow + bounds: [ 50, 550, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + - type: stacking-context + transform: scale(0.75) + items: + - type: box-shadow + bounds: [ 50, 650, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + - type: stacking-context + transform: scale(0.77) + items: + - type: box-shadow + bounds: [ 50, 750, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + - type: stacking-context + transform: scale(0.8) + items: + - type: box-shadow + bounds: [ 50, 850, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + - type: stacking-context + items: + - type: box-shadow + bounds: [ 50, 800, 100, 100 ] + color: black + blur-radius: 1 + clip-mode: inset + + - type: stacking-context + items: + - type: box-shadow + bounds: [ 200, -100, 600, 200 ] + color: black + blur-radius: 5 + clip-mode: inset + + - type: stacking-context + items: + - type: box-shadow + bounds: [ 200, 200, 600, 200 ] + color: black + blur-radius: 5 + clip-mode: outset diff --git a/third_party/webrender/wrench/reftests/clip/blank.yaml b/third_party/webrender/wrench/reftests/clip/blank.yaml new file mode 100644 index 00000000000..c4eb3ab6730 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/blank.yaml @@ -0,0 +1,2 @@ +--- +root: diff --git a/third_party/webrender/wrench/reftests/clip/border-with-rounded-clip.png b/third_party/webrender/wrench/reftests/clip/border-with-rounded-clip.png new file mode 100644 index 0000000000000000000000000000000000000000..f34000571496670e1c275a81e86978f731910c1f GIT binary patch literal 1900 zcmZ9NdsLEX9>-O@l$Zz-W(uYt>Ue!6F?>W!!oZorA&o7_F z+`r9qr>UNv-nQ@qA(8OThu7#9W0<9^i%LB`6H0gp{gANo?RB8O?C_2rzI#HwBW4t1 z+ixG~Wnwbc5n7^D+UMo44D09?0(21lO3oHci3BV7@n0c9>c{HG#!^EclC9eTw~(&S zmu6-T-f4Z;HpCxHALM^-pGtk8mA_k`TzNF)_pWCDv|q7-iXq{Wg8<8FD4(ScagKMu zQqTq8ZqdoqX_OE-dHK2KVDDy1=ai;uneYf@r_5bOZ`cefH>*MbeY8Rhez4AqZ_hO1Ohr z=)VL{e*Ie>*FbcANzxNHWQGT=sBP2wkoY7|WWIL#mBR&Gjx3#!EVhcH=H&Oq$naNM zcOJENAV(zyF`$1LoMcLRPm zehqk)&|UxcyuO?3i}qEka>fcER~E7a1it6ExvE`YqvfnxW94tW=X#@VX@`$9JPcDE znEnT&fPI%w1OvVkT_aBKH{UoYL_{ZICgXSulldBYD6cGP0$T1;X!gAGx6`&`pQ1g; zRiay$YdtGGC8@xan7FU|uEkf1Wcpc2eji(osy?#Eo_l^Z(a$z1zUP_+FqK6-I9ytj zt}dR7j(ED8>@@H^P!s%5XneidEPl$H1;zud9qMbs=pRYs)@QdR9pz*o-RA1KOQ+8K zqvuu8pO+Bm&xZ>8D28Q9M$8bA?K2k-{p^*OcSzPZd$cTwU6cs;Dkq0t<}*tn|FwlA z=l)2?x#$;*=y)32UlCCH!I{9v|MFJQ8|$)@6V;RklR`^#!GYh??$84kFlo*>P7HE^ z<2JX*Lfu%5Bq_S-qb!o5Y-x0w0H(|wHS%oHB08cs^!C?^MR*kWMmuiG}=Lp1I*a7Kyo!AU3Z4bC73mWtrb&BCU z%)~<^oC2zhoyWH2iCyNNuyS{HKgNQny0z5kE+tlLRF80;i7Qz1AGsje193gF9L&FD z>r3R|OK*p}F@Tl?3nYi(KCkM)a*%IzwTMl+DjET7`cN^jxlN!mYs++|g-&jv*~7(0 z_h4Z&>5WY4qM(j-cVNK;n@n3ZkQNxKqky4fJx9m!XqaoM6{v>L3^kBnrA8 zDIFC>ABx~J{=OaDw-6%f?KxS))PSzTvrRh6CPrs*V1n{{_eP+FIfi=mx{-&p#ypgFHEnqh_GcFYhu;FfGVqqvsoFWB6#69i zsrrwM8pj{rACfh=j=>D4b|P3M)rpKoQ+M%R|7NqpJPh8KQ?vl|ofqnxo~Y>ZM@0o` zB0%Vr>o`hkH^6;A{KC85@6#NHBug?Mb%#x4xAq+x&@7VT%(a|kv zFaa~kgrE>gFf~X;+I!@jMk4D2BfbtyM*TOeekM_L0B_H`N=zkUK}#xm7?=BI3m$wu z;R0F)kcZu$oD!hHvst;{38@oZq`$7b*br<7Ut4}U`%o7Co6TO|W$qSZ24Y`v0eZpAlVnx5QB zN$Rtsk~P-Iwz#rjN=}gSvaI#uEO3^ L%#g;QxUAm+0TFFX literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/clip/border-with-rounded-clip.yaml b/third_party/webrender/wrench/reftests/clip/border-with-rounded-clip.yaml new file mode 100644 index 00000000000..12785ef444d --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/border-with-rounded-clip.yaml @@ -0,0 +1,21 @@ +--- +root: + items: + - + bounds: [10, 10, 938, 200] + clip-rect: [10, 10, 938, 200] + type: clip + id: 2 + complex: + - + rect: [10, 10, 938, 200] + radius: [97, 97] + "clip-mode": clip + - + bounds: [10, 10, 100, 200] + clip-and-scroll: 2 + type: border + width: [100, 50, 100, 50] + border-type: normal + color: green + style: solid diff --git a/third_party/webrender/wrench/reftests/clip/clip-3d-transform-ref.yaml b/third_party/webrender/wrench/reftests/clip/clip-3d-transform-ref.yaml new file mode 100644 index 00000000000..58d66bec5ad --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/clip-3d-transform-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - + bounds: [0, 0, 200, 200] + "clip-rect": [0, 0, 200, 200] + type: rect + color: green diff --git a/third_party/webrender/wrench/reftests/clip/clip-3d-transform.yaml b/third_party/webrender/wrench/reftests/clip/clip-3d-transform.yaml new file mode 100644 index 00000000000..f844b382ad3 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/clip-3d-transform.yaml @@ -0,0 +1,30 @@ +# This tests that clipping works well inside of a transformation with a 3d component that +# is still axis-aligned and lacking a perspective component. These two spaces should exist +# within a compatible coordinate system for local clipping (no masking). +--- +root: + items: + - + bounds: [0, 0, 0, 0] + "clip-rect": [0, 0, 0, 0] + "backface-visible": true + type: "stacking-context" + "scroll-policy": scrollable + transform: [0.5, 0, -0.8660254, 0, 0, 1, 0, 0, 0.8660254, 0, 0.5, 0, 0, 0, 0, 1] + "transform-style": flat + filters: [] + items: + - + bounds: [0, 0, 800, 400] + "clip-rect": [0, 0, 800, 400] + "backface-visible": true + type: clip + id: 2 + "content-size": [800, 400] + - + bounds: [0, 0, 400, 200] + "clip-rect": [0, 0, 400, 200] + "backface-visible": true + type: rect + clip-and-scroll: 2 + color: green diff --git a/third_party/webrender/wrench/reftests/clip/clip-45-degree-rotation-ref.png b/third_party/webrender/wrench/reftests/clip/clip-45-degree-rotation-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..620eb49e43504e30b19f9c27a680603e2762e1c8 GIT binary patch literal 13358 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 B|IPpa literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/clip/clip-45-degree-rotation.yaml b/third_party/webrender/wrench/reftests/clip/clip-45-degree-rotation.yaml new file mode 100644 index 00000000000..316f249a3e0 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/clip-45-degree-rotation.yaml @@ -0,0 +1,32 @@ +# Test that transformed content is clipped properly by clips with a different transform. +--- +root: + items: + - + bounds: [0, 0, 0, 0] + "clip-rect": [0, 0, 0, 0] + type: "stacking-context" + transform: rotate(-45) translate(200, 0) + items: + - + bounds: [0, 0, 300, 300] + "clip-rect": [0, 0, 300, 300] + type: rect + color: red + - + bounds: [0, 0, 300, 300] + "clip-rect": [0, 0, 300, 300] + type: clip + id: 5 + - + bounds: [0, 0, 0, 0] + "clip-rect": [0, 0, 0, 0] + clip-and-scroll: 5 + type: "stacking-context" + transform: rotate(45) translate(-300, 0) + items: + - + bounds: [0, 0, 1598, 1200] + "clip-rect": [0, 0, 1598, 1200] + type: rect + color: green diff --git a/third_party/webrender/wrench/reftests/clip/clip-and-filter-with-rotation-ref.yaml b/third_party/webrender/wrench/reftests/clip/clip-and-filter-with-rotation-ref.yaml new file mode 100644 index 00000000000..c092030714e --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/clip-and-filter-with-rotation-ref.yaml @@ -0,0 +1,18 @@ +# Test that filtered content is clipped properly in a rotated context +--- +root: + items: + - + bounds: [16, 16, 0, 0] + type: "reference-frame" + transform: rotate(10) + id: 7 + items: + - + bounds: [0, 0, 0, 0] + type: "stacking-context" + items: + - + bounds: [0, 0, 324, 295] + type: rect + color: red diff --git a/third_party/webrender/wrench/reftests/clip/clip-and-filter-with-rotation.yaml b/third_party/webrender/wrench/reftests/clip/clip-and-filter-with-rotation.yaml new file mode 100644 index 00000000000..3c71e0148ff --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/clip-and-filter-with-rotation.yaml @@ -0,0 +1,35 @@ +# Test that filtered content is clipped properly in a rotated context +--- +root: + items: + - + bounds: [16, 16, 0, 0] + type: "reference-frame" + transform: rotate(10) + id: 7 + items: + - + bounds: [0, 0, 0, 0] + type: "stacking-context" + items: + - + bounds: [0, 0, 324, 295] + type: clip + id: 2 + # note it's important that we use clip-node here; clip-and-scroll in yaml + # defaults to push/pop, but we want to specifically attach this clip-chain + # to the stacking context and not the children (as gecko does in the bug) + - + bounds: [0, 0, 0, 0] + clip-node: 2 + type: "stacking-context" + filters: ["contrast(1.1)"] # any blend-style filter will do + items: + - + bounds: [-150, -150, 624, 624] + type: rect + color: blue + - + bounds: [0, 0, 324, 295] + type: rect + color: red diff --git a/third_party/webrender/wrench/reftests/clip/clip-corner-overlap-ref.yaml b/third_party/webrender/wrench/reftests/clip/clip-corner-overlap-ref.yaml new file mode 100644 index 00000000000..e9592b58317 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/clip-corner-overlap-ref.yaml @@ -0,0 +1,113 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + - type: clip + bounds: [ 50, 50, 200, 100 ] + complex: + - rect: [ 50, 50, 200, 100 ] + radius: + top-left: [200, 100] + top-right: [0, 0] + bottom-left: [0, 0] + bottom-right: [0, 0] + items: + - type: rect + bounds: [ 50, 50, 200, 100 ] + color: blue + - type: clip + bounds: [ 50, 150, 200, 100 ] + complex: + - rect: [ 50, 150, 200, 100 ] + radius: + top-left: [0, 0] + top-right: [0, 0] + bottom-left: [200, 100] + bottom-right: [0, 0] + items: + - type: rect + bounds: [ 50, 150, 200, 100 ] + color: blue + + - type: clip + bounds: [ 300, 50, 100, 200 ] + complex: + - rect: [ 300, 50, 100, 200 ] + radius: + top-left: [100, 200] + top-right: [0, 0] + bottom-left: [0, 0] + bottom-right: [0, 0] + items: + - type: rect + bounds: [ 300, 50, 100, 200 ] + color: green + - type: clip + bounds: [ 400, 50, 100, 200 ] + complex: + - rect: [ 400, 50, 100, 200 ] + radius: + top-left: [0, 0] + top-right: [100, 200] + bottom-left: [0, 0] + bottom-right: [0, 0] + items: + - type: rect + bounds: [ 400, 50, 100, 200 ] + color: green + + - type: clip + bounds: [ 50, 300, 200, 100 ] + complex: + - rect: [ 50, 300, 200, 100 ] + radius: + top-left: [0, 0] + top-right: [200, 100] + bottom-left: [0, 0] + bottom-right: [0, 0] + items: + - type: rect + bounds: [ 50, 300, 200, 100 ] + color: red + - type: clip + bounds: [ 50, 400, 200, 100 ] + complex: + - rect: [ 50, 400, 200, 100 ] + radius: + top-left: [0, 0] + top-right: [0, 0] + bottom-left: [0, 0] + bottom-right: [200, 100] + items: + - type: rect + bounds: [ 50, 400, 200, 100 ] + color: red + + - type: clip + bounds: [ 300, 300, 100, 200 ] + complex: + - rect: [ 300, 300, 100, 200 ] + radius: + top-left: [0, 0] + top-right: [0, 0] + bottom-left: [100, 200] + bottom-right: [0, 0] + items: + - type: rect + bounds: [ 300, 300, 100, 200 ] + color: yellow + - type: clip + bounds: [ 400, 300, 100, 200 ] + complex: + - rect: [ 400, 300, 100, 200 ] + radius: + top-left: [0, 0] + top-right: [0, 0] + bottom-left: [0, 0] + bottom-right: [100, 200] + items: + - type: rect + bounds: [ 400, 300, 100, 200 ] + color: yellow diff --git a/third_party/webrender/wrench/reftests/clip/clip-corner-overlap.yaml b/third_party/webrender/wrench/reftests/clip/clip-corner-overlap.yaml new file mode 100644 index 00000000000..2fa2225652f --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/clip-corner-overlap.yaml @@ -0,0 +1,61 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 1000, 1000] + items: + - type: clip + bounds: [ 50, 50, 200, 200 ] + complex: + - rect: [ 50, 50, 200, 200 ] + radius: + top-left: [200, 100] + top-right: [0, 0] + bottom-left: [200, 100] + bottom-right: [0, 0] + items: + - type: rect + bounds: [ 50, 50, 200, 200 ] + color: blue + + - type: clip + bounds: [ 300, 50, 200, 200 ] + complex: + - rect: [ 300, 50, 200, 200 ] + radius: + top-left: [100, 200] + top-right: [100, 200] + bottom-left: [0, 0] + bottom-right: [0, 0] + items: + - type: rect + bounds: [ 300, 50, 200, 200 ] + color: green + + - type: clip + bounds: [ 50, 300, 200, 200 ] + complex: + - rect: [ 50, 300, 200, 200 ] + radius: + top-left: [0, 0] + top-right: [200, 100] + bottom-left: [0, 0] + bottom-right: [200, 100] + items: + - type: rect + bounds: [ 50, 300, 200, 200 ] + color: red + + - type: clip + bounds: [ 300, 300, 200, 200 ] + complex: + - rect: [ 300, 300, 200, 200 ] + radius: + top-left: [0, 0] + top-right: [0, 0] + bottom-left: [100, 200] + bottom-right: [100, 200] + items: + - type: rect + bounds: [ 300, 300, 200, 200 ] + color: yellow diff --git a/third_party/webrender/wrench/reftests/clip/clip-ellipse.png b/third_party/webrender/wrench/reftests/clip/clip-ellipse.png new file mode 100644 index 0000000000000000000000000000000000000000..49624825573ef74f41cb50d55b0de4b9d400641c GIT binary patch literal 9486 zcmeHtXH-*LyEPpFfg=K;^D2rc5PFe%ju=2hkRm8u0*Q#!AT3lqh$aZ4l!U6G$x%US zfJhM#6apAgDS^;5L?sjnoxrz)oICFQ#`oR(j&aBRamRRnvG;z~+Ix@ewdS19oZP!; zVLLPt0Wua*Jov~s%mGZe8^?A z_Z)An!8Lj`5I*dcpN+0k55y$)dD`%2u$b zf$*GKLWdA%s;QiW7LWc<%f|@#&h5FnI$Azf0joh=B3?}q+Z$AgO~Y&uK}6kB)ekD0 zn=5U(K^$t$Tq-V|tyVu6`Hi3F9<{wqZ`+vVgy*p{zP|~A73JX8y!*ZS;pUnvoBeM7 ztLMB9EsHEG))bUGx(Xc zoxPM)ixrC-t!?k#)E#vo(PuWaVM+=C@T9_?;qJ}PomUuw(h2^R+#WIMYMtZUAht9k z4#wO&&?mognlaV_BOtM~b`+snljsL-!I9t8p}`!VpHB(Avz};aTOI&`DVLr^k?7N- z%hsOe)irk-?^dBBF1)$K7^z7_^iQ3+PW|MZ$alEmsr4Yv*2}}UIK2NVObfCkI$rU# zFq9u;k638wJwKD_j#U!}F17Yr((~IzD~a89j4pGp^-u0KAui< zo-W3f^+&xDIU#0z^Iuc{SN=>t)uPL6+EQiREQ4hYIateLRH#%zVaiuSU>CPLBi|-$ z8hqrnY>fKi#BQkFs4($$%AeV9RJPG3ejnqOF=Wm3w+8!r3u^is>hO^*Fl7#rcX6p zsXfp@JL=UZ)$)MIpbHr78mWgK^K`86BlTHQqe|5jMy_7*GpOBk5pmBze+*Pnw=LDKqN8~AO)JJ>xI473baA&E`2>8_iSm!s$ z-}?1hQ5I^ipkXHINAM*&cE$CQavIPc+Wz9f6S3Q#&)c2f@W(CpwRPTGtS-ZWF8Bn( z9M`{Je!T#NX_OS(F^&>6O`$xCUD7c}j^{@ysz+(6-FFU$G|PMyZGKxpw1xHi6xZru zB(0ZF(WgJ0iY9HYB&DsWH9Iy$mL+@h&pi<(G=%SLY;P0OW}Bmzaz~yP(;&?fTbJWa zprD0U@||}xjWfJKLmu99GaJ#R_I~2cy#n#Nx*Vpv#P|;B#22oK`G$$^gmvd{LLZMK;eq>XS#puaDa0lvm5|7pc*Kl9 ze)UK4#ZebV76ZzoX(q8%lXyCzs+FxtT^6%y-5e+KGJ@((w1Fu@lXkFvK2GYrl3NI! z8P!4vC|%d?pQ8DH#E=4z*T~42*Bq1Co)_`>syp8Jf%#Tb_2{sx1KXPTpbEzle4AXa zwYRy}mexwNw7*jL)|?uV1E#pl=Ws79Ffrf;R|P?6RJ`d-{r%+*Vdz@y&(t2(XwGHz zqJSsLP_n*fx3=b2%OcvU%;p=A8Tk+AclC(4`O}dm*&a3|`f0|jL;$b5EV+NfyEJe1 z!;D%}_?;Dpuc?))CohH35B@0I01odFX!YftPwm|9<|YXU=G zW!Emeg5dMLk_B(q$A-hmMr!V;`Z+d#7v&~87a@aGKhSa`clN6FdBe7xNOas(();iM z$trq*XJjJ!dz}*lBoVNdCrF;(I8%B&M0|YpcFldk`OpKkb%?GT0<@skuQ&r$F{Z1zt0BC%KEdQq7VQq?aJ$8wF6JqS=EvwC z`gF~MagXSb;E9tp$VNCX%(1q_IqT_PdT7~<-&2vWN1i_EQ z{=<32E{=SK%G^R6dHupADRjVXVc@yLZx#Ig}?J8eggPJ0)v@x&MIrl&Lf3_O7BPZ*Ifc4dHB8|~XMt|^t_Wvz&>_c`F+42=L95ED}#TG$_%fsCN6ya(9x`Ywj{IEowR8uanLO*y_i>ZI7_QniDTW6hI=@X>x6M1l=;qWNQ+?y|&bs#ugAg zQ_60jOkf9Jda)mSOe#v;ShfQzZ^pp16{b|Yy3|8NnW=^eg!$<+w;Gw@ll_K`N@-$D z8)~?Ddn3B>_6B+>;{pk!(l#G)ldqf7XOU*!_?%WGiFD2mo*R#0VFErtc?|xdY`Fbp z+2^^a+x__)I3p)Uf66$V&is;*(OuwDgB-(a0J}dgMssmz%gwScKTKqif^^FmKm}5u zHr~an6u1q{+6Yp3`f$TfD{wKjAd&;4eUAlVCikLlrH>LGV&7pRQ7v(?Q!|8*1+h04 zKA~Av3c4z7!5+C<6>AKgKG}5ohsxxAJwP{|UbdcG8ESlLT}7kb1bVsavw~EWQl$#q zK)yg8s{ztlXc0(}?q&gTtjsc4p$@^Xm3|_z4wM*_vcT;{1?Z~?$5$#;7+3p+Fj2)97k08jtvWI*CMdvYsUPvYpS zj$vAfQJLxWhRN8zxpUhPy>NxTHz3Yjz2zjw=dOB_jk6c~?rXdN*c__&!Kgb7S{#~- zMPp0#x@r~g<8r&qC!2mG;3>BpCmDl@$?`l>Qj_I*xLh9URce@d&zd+wcJOV6LOu=@ z`}5q?IXt%j|LmXNBv*Ig16ky#@FEN7+F`$LIkZZKD+31wI4wZC#ok8 zQ;d)nOsVA?uI3(Y5*u!jU1vsk&DwYkfhW^U`90%zuB^v7Y|U}b)q!5FQIaZG>4`$J z{1ZUy$+AF3#b}h~V8^Ant*dKstE;}qXJo|m>Tg!u-3mz;zp1|Tn3GUKT6Ps6@y1^R zf)KrNDY8>z`QLiNt{P};vBVyc5)W7FYMJ=BKrAH6$HM`Wq-r4uUa#qjxsW1zX8UJpd5@Ez|T6ZOh2YsY4oRZ2njccZNJGC{HF$|g_rKtfR)+oL z!}AGo%=P>ABMzi3jcoO7O{cK(0x_l}^$JX9Fh?J~GXig8u5nhrY6nfvk0BCs136Mn za@77Bv?ry(0=@fvJ13NhOd=fEh+^XvsS}|B>Tn@XOKMjd?|d^pSf};O1$3Zu_kwSi z*2Cu0MAa1cJ~dPsO$)~6P#SzhM=#a@XlOuRmBnK8@!zF^kT~P)yKHaid#z_0P}Bu%Rr*& zp|At{9W7pY`}v8R?f>#D{$=g_(>B%)MBYI3^8Rx28qow>x%MBty04t!1?*r#3Lrzi zl_j26`--dmcZ+Zg*aHDlAQ9rn^0wx+P`iR~Vl&avydB#$*1r8FvZs^=>pIBN+r`vU zTV6$5DlWz;Obd(cx1?XV3R71sz~wHqs=Xo%U^_q$8<%DT7N&3WEJ1ux5WU+H$O!oX z%T=oSDtO1(LDZRqHrYDZup2j-=?rjNJ`x-}c%hACu?#pA;?1;cR7I+r`F3;r*g#B| zHV95aJ5}-&#MTGDo+k%w2$qi?Nfrgyx`*<;uas{k>8gq~Kb1%DAx$*7`+{+d_stFPK9;{Kk_n#?gzpBT9JMg%z*ob5QJIjDp0} zZY)+988Vku<+QkfpTdrOvxWA0!~eMI(E7I%UgHKI>nP2MoWfjtrh@>a{@Dz#EEnV3?f0V^SH5+~bPn%L z_{U(I%v%QR`-lPcL}~h!*O$}j-`CTKE{e(<;LU;?|H-=Nds&|>G~5;0+w(7 zxme_Ya~M*QIZt>Z3sv?pBv8U#r>=xqe5G*s4-`*yMK0Lq7?4L+0=o(zY8F!Xw$WAl zQKRaBQ)Amz8KI~^@_E1IxAi9jcP?9PLxxJzuFFpl;lDRvBSPX@O1CW7tqvrs#N(5;5^ z;WrA`%&^pHTP62U+g%!cqp&E`Cb>2|0U;cOJ zdb@{Cw&3Iu)>A{K4~@50zLph++BH%kyBSrUyOyhSL)qQxK#CDYwjE#9$sVnWr3b`s z@aq6SNLk=k&UyN9%W8dVjGY@KepYs}7U0&LsI7p>j}Cy&AH)^*yy^q|(XLk=qCgNL z9mFLU^FW{+D;>xt7ny+W(N^8$BnqZ{?7vOCZw=hS^yl05KknHZhW|bT@(i}amO^%K zRoxaT37<>oQoyndnc3V=8&S}A)dT5Tvp&Cu3j;>IW>BARjPGSzIs4PQ;@c^jR2q(0Eorycy1H~KUc;}oPX^*ydq6@5CBO%pI^*@l65IoY$OFB>u{=BCQN>i8>Bcd2KM`Oq5!)u_9Be12 zyeUSs27tfQ=p7LJ7`jciYyqMsuthRc=HXQczJogub}Vm8!tj_vD$d_^cJb60&nT)Q zAiqO7kW)l06z(UFVDCs zR^#Z2p``p-lR8pZ4){TwV(v|VhaWJG3YAy+-hRXP7j=cvr#wtg4gVNl1r5o~uN+~y zHsf)+_(IZgqb*1uov^>?{|Lx$L~;uVj*p9Hq)KEq1u{)DSr;V)mAUqM#r_Wiy7-2m#i*35Wj;( zf0%oxgS>9_D_z4wAJQ9q_y$sK>!BIFUKXpPA@BABk(MnE*LvU^+@$kwuS$bT0= zBR4z#|1x$5GW4cl^9ee{u7?%;4OI?(PmXIAFJZZ2VdLr!rePh{8GCu*O@SNC_o^@V z$C;_Fmr2M9_2dPcySn`!MhV*eQlX3Y2OLi>}_tyjz}G=EJR2R3W=;`1*~tC~I`FAE({ zupaE*tvDnsn=7+-C&n6uv+Mp0U|Sl3^f!FKR=v}UvE?>3>$nqQ zHyRA%+h0YM9)gSoU`61sivf}bRI&nh()hO@e5^zOBr^8-MG}*}=Y7&h&;C;BD#&qR zIa{R>L5@E~uNng9-}@}}e@Gxh>1tgLx)5M%(BXiCv$sileSmX;WDSvi`>r_iv?AJJ z#f;b=B|e>jkOJdemZ=NZngLoBA+1Cpt#T+65B&Tfc&@YrXlDR(C{_xp=WnSWUPN>R z@PgbtA3i|%6S~#_905r&nfXZzm?{EYYXZ__2s**82!M-yBLdjo>BKYU?UHX)yaB6u zwC?wb79d6T|I8M<$%2|_d6=NW6vOwkCg2u@EeHX6De0pdkffDf*fWcCHTGgO>j)OU z7Nvoa(h4Cka#ZZ9B&+*kYEhNUuP-2|*I4Cel{|9sc>w%AtKg7BrtBHw5(qKC5Z6cO z{O9mH-(xU+0D#|??dsqNZx;L#s#|=DS5v*_=FbA~OMm=EQ~v31@KvvL*_W4DUVLzC z0N~ZbyM!)l1MofEGQB!~v<_N``)k;_^>P?=3q>ZNKc>c_t~X^P-5{+?=98~^8z1B} zPVr18^6tg-rfp+wmUN@HV{cjX6+vOQF}x4Z7k+}r#yE42zRHPYfWG{QhAjHR&pe__ z_?F8WDvqR{uedmfBd0e@OO)pqq{wEw0g}2;qniy40wjR62FE(X0eotxmPU1eaVdaM=3|A~c@*O3BXl(7UBsQK(H)dP}228{_03gHP zhmJe2ET}MIn6Z)N2k^W!AObw9q1$mb3<7`;u*F~)Jk&I%~qQgI7xnRWH|QxQc3pDq(VSLf_>+*`i`Vz1f7QBMNKMfvng+wjK{$7wXyE^x*v>(0&Q_f9y7hkmdyew4 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/clip/clip-ellipse.yaml b/third_party/webrender/wrench/reftests/clip/clip-ellipse.yaml new file mode 100644 index 00000000000..e0c93c79855 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/clip-ellipse.yaml @@ -0,0 +1,63 @@ +--- +root: + items: + - type: rect + bounds: [20, 20, 100, 100] + color: red + complex-clip: + rect: [20, 20, 100, 100] + radius: [32, 16] + + - type: rect + bounds: [130, 20, 100, 100] + color: green + complex-clip: + rect: [130, 20, 100, 100] + radius: [32, 16] + clip-mode: clip-out + + - type: rect + bounds: [20, 130, 100, 100] + color: red + complex-clip: + rect: [20, 130, 100, 100] + radius: [16, 32] + + - type: rect + bounds: [130, 130, 100, 100] + color: green + complex-clip: + rect: [130, 130, 100, 100] + radius: [16, 32] + clip-mode: clip-out + + - type: rect + bounds: [20, 240, 100, 100] + color: red + complex-clip: + rect: [20, 240, 100, 100] + radius: [128, 32] + + - type: rect + bounds: [130, 240, 100, 100] + color: green + complex-clip: + rect: [130, 240, 100, 100] + radius: [128, 32] + clip-mode: clip-out + + - type: rect + bounds: [20, 350, 100, 100] + color: red + complex-clip: + rect: [20, 350, 100, 100] + radius: [32, 128] + + - type: rect + bounds: [130, 350, 100, 100] + color: green + complex-clip: + rect: [130, 350, 100, 100] + radius: [32, 128] + clip-mode: clip-out + diff --git a/third_party/webrender/wrench/reftests/clip/clip-empty-inner-rect-ref.yaml b/third_party/webrender/wrench/reftests/clip/clip-empty-inner-rect-ref.yaml new file mode 100644 index 00000000000..0dea07f57dc --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/clip-empty-inner-rect-ref.yaml @@ -0,0 +1,20 @@ +# In this case, the inner rectangle of the clip is empty, because +# the size is 200 and each corner radius is 100. +# The blue rect should be fully invisible +--- +root: + items: + - + type: "stacking-context" + bounds: [0, 0, 0, 0] + items: + - + type: clip + bounds: [0, 0, 200, 200] + complex: + - rect: [ 0, 0, 200, 200 ] + radius: 100 + items: + - type: rect + bounds: [ 0, 0, 200, 200 ] + color: red diff --git a/third_party/webrender/wrench/reftests/clip/clip-empty-inner-rect.yaml b/third_party/webrender/wrench/reftests/clip/clip-empty-inner-rect.yaml new file mode 100644 index 00000000000..f8165f90d32 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/clip-empty-inner-rect.yaml @@ -0,0 +1,28 @@ +# In this case, the inner rectangle of the clip is empty, because +# the size is 200 and each corner radius is 100. +# The blue rect should be fully invisible +--- +root: + items: + - + type: "stacking-context" + bounds: [0, 0, 0, 0] + items: + - + type: clip + bounds: [0, 0, 200, 200] + complex: + - rect: [ 0, 0, 200, 200 ] + radius: 100 + items: + - type: rect + bounds: [ 0, 0, 200, 200 ] + color: red + - type: "stacking-context" + bounds: [0, 0, 0, 0] + transform: translate(0, 0, 1) + items: + - + type: rect + bounds: [0, 0, 25, 25] + color: blue diff --git a/third_party/webrender/wrench/reftests/clip/clip-mode.png b/third_party/webrender/wrench/reftests/clip/clip-mode.png new file mode 100644 index 0000000000000000000000000000000000000000..69f2b8b4ae8d94346d9e604217014e0ce410542c GIT binary patch literal 2740 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/clip/clip-mode.yaml b/third_party/webrender/wrench/reftests/clip/clip-mode.yaml new file mode 100644 index 00000000000..9c10cfdb3da --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/clip-mode.yaml @@ -0,0 +1,17 @@ +--- +root: + items: + - type: rect + bounds: [20, 20, 100, 100] + color: red + complex-clip: + rect: [20, 20, 100, 100] + radius: 32 + + - type: rect + bounds: [130, 20, 100, 100] + color: green + complex-clip: + rect: [130, 20, 100, 100] + radius: 32 + clip-mode: clip-out diff --git a/third_party/webrender/wrench/reftests/clip/clip-out-rotation.yaml b/third_party/webrender/wrench/reftests/clip/clip-out-rotation.yaml new file mode 100644 index 00000000000..43d4aa36978 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/clip-out-rotation.yaml @@ -0,0 +1,41 @@ +# Test that transformed content is clipped out properly by clips with a different transform. +# Also verifies that we aren't trying to render the clip mask at all for this clip-out case. +# +# The clip is a 500x500 rounded cornered rectangle rotated by 15 degrees. It fully contains the +# bounds of the red rect and has a clip-out mode (within the conservative logic of our +# `project_inner_rect` transformation), so nothing is visible, and no mask is rendered. +--- +root: + items: + - + bounds: [0, 0, 0, 0] + type: "reference-frame" + id: 2 + - + bounds: [0, 0, 0, 0] + type: "stacking-context" + transform: rotate(-15) translate(200, 0) + items: + - + bounds: [0, 0, 1000, 1000] + type: clip + id: 5 + complex: + - + rect: [0, 0, 500, 500] + radius: [5, 5] + clip-mode: clip-out + # uncomment this to see the clip area + #- + # bounds: [0, 0, 500, 500] + # type: rect + # color: green + - + bounds: [0, 0, 0, 0] + type: "stacking-context" + clip-and-scroll: [2, 5] + items: + - + bounds: [225, 150, 300, 300] + type: rect + color: red diff --git a/third_party/webrender/wrench/reftests/clip/clip-thin-rotated-ref.yaml b/third_party/webrender/wrench/reftests/clip/clip-thin-rotated-ref.yaml new file mode 100644 index 00000000000..e09079424d5 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/clip-thin-rotated-ref.yaml @@ -0,0 +1,13 @@ +# Test checks if a rotated clip with a long and thin rectangle would still +# correctly affect the primitive with regards to the inner bounds. +--- +root: + items: + - + bounds: [0, 0, 0, 0] + type: "stacking-context" + items: + - + bounds: [100, 100, 14, 14] + type: rect + color: blue diff --git a/third_party/webrender/wrench/reftests/clip/clip-thin-rotated.yaml b/third_party/webrender/wrench/reftests/clip/clip-thin-rotated.yaml new file mode 100644 index 00000000000..d0e626af91f --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/clip-thin-rotated.yaml @@ -0,0 +1,41 @@ +# Test checks if a rotated clip with a long and thin rectangle would still +# correctly affect the primitive with regards to the inner bounds. +--- +root: + items: + - + bounds: [0, 0, 0, 0] + type: "reference-frame" + id: 2 + - + bounds: [0, 0, 0, 0] + type: "stacking-context" + transform: rotate(45) translate(200, 0) + items: + - + bounds: [0, 0, 20, 1000] + type: clip + id: 5 + # uncomment this to see the clip area + #- + # bounds: [0, 0, 20, 1000] + # type: rect + # color: green + - # we aren't supposed to see this one + bounds: [0, 0, 0, 0] + type: "stacking-context" + clip-and-scroll: [2, 5] + items: + - + bounds: [120, 120, 10, 10] + type: rect + color: red + - + bounds: [0, 0, 0, 0] + type: "stacking-context" + clip-and-scroll: [2, 5] + items: + - + bounds: [100, 100, 14, 14] + type: rect + color: blue diff --git a/third_party/webrender/wrench/reftests/clip/clipped-occlusion-ref.yaml b/third_party/webrender/wrench/reftests/clip/clipped-occlusion-ref.yaml new file mode 100644 index 00000000000..69a5fb06246 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/clipped-occlusion-ref.yaml @@ -0,0 +1,7 @@ +--- +root: + items: + - + type: rect + bounds: [0, 0, 500, 500] + color: red diff --git a/third_party/webrender/wrench/reftests/clip/clipped-occlusion.yaml b/third_party/webrender/wrench/reftests/clip/clipped-occlusion.yaml new file mode 100644 index 00000000000..05a055a5a3a --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/clipped-occlusion.yaml @@ -0,0 +1,23 @@ +# This is a regression test for https://bugzilla.mozilla.org/show_bug.cgi?id=1594567 +# The single clip node from the primitive inside the scroll frame will be promoted +# to a 'shared clip' in the picture cache for the scroll frame. Ensure that this clip +# (zero sized in this test) is included in the tile occlusion culling. +--- +root: + items: + - + type: rect + bounds: [0, 0, 500, 500] + color: red + - + type: scroll-frame + content-size: [1000, 10000] + bounds: [0, -5000, 1000, 10000] + items: + - type: clip + bounds: [0, 0, 0, 0] + items: + - + bounds: [0, -5000, 1000, 10000] + type: rect + color: green diff --git a/third_party/webrender/wrench/reftests/clip/custom-clip-chain-node-ancestors-ref.yaml b/third_party/webrender/wrench/reftests/clip/custom-clip-chain-node-ancestors-ref.yaml new file mode 100644 index 00000000000..c84fe7a6125 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/custom-clip-chain-node-ancestors-ref.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - + bounds: [10, 10, 100, 100] + "clip-rect": [10, 10, 100, 100] + "backface-visible": true + type: rect + color: 0 255 0 1 diff --git a/third_party/webrender/wrench/reftests/clip/custom-clip-chain-node-ancestors.yaml b/third_party/webrender/wrench/reftests/clip/custom-clip-chain-node-ancestors.yaml new file mode 100644 index 00000000000..9824da4f9ca --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/custom-clip-chain-node-ancestors.yaml @@ -0,0 +1,32 @@ +# This test ensures that custom clip chains are not affected by the ancestors +# of their clipping nodes. In this case the node, 3, will probably be optimized +# away since its ancestor 2 has a tighter bounding rect. On the other hand, a +# clip chain which includes 3 should only get the rectangle specified by that +# node and not be affected by 2 at all. +--- +root: + items: + - + bounds: [25, 25, 50, 50] + "clip-rect": [25, 25, 50, 50] + type: clip + id: 2 + - + bounds: [10, 10, 100, 100] + clip-rect: [10, 10, 100, 100] + clip-and-scroll: 2 + type: clip + id: 3 + "content-size": [800, 1000] + - + bounds: [0, 0, 0, 0] + clip-rect: [0, 0, 0, 0] + type: "clip-chain" + id: 10 + clips: [3] + - + bounds: [0, 0, 200, 200] + clip-rect: [0, 0, 200, 200] + clip-and-scroll: [root-scroll-node, 10] + type: rect + color: 0 255 0 1 diff --git a/third_party/webrender/wrench/reftests/clip/custom-clip-chains-ref.yaml b/third_party/webrender/wrench/reftests/clip/custom-clip-chains-ref.yaml new file mode 100644 index 00000000000..e30bcfd2a4a --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/custom-clip-chains-ref.yaml @@ -0,0 +1,17 @@ +root: + items: + - type: clip + id: 2 + bounds: [0, 0, 200, 200] + complex: + - rect: [0, 0, 200, 200] + radius: { + top-left: 20, + top-right: 20, + bottom-left: 20, + bottom-right: 20, + } + items: + - type: rect + bounds: [0, 0, 200, 200] + color: green diff --git a/third_party/webrender/wrench/reftests/clip/custom-clip-chains.yaml b/third_party/webrender/wrench/reftests/clip/custom-clip-chains.yaml new file mode 100644 index 00000000000..62e2fde8c51 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/custom-clip-chains.yaml @@ -0,0 +1,62 @@ +# This tests a clip chain (clipid=6) that is composed of two clips (clipid=3 +# and clipid=4) with a parent clip chain (composed of two more clips (clipid=1 +# and clipid=2). This effectively tests a "complex" clip chain that includes +# and is applied to multiple non-hierarchical reference frames as well as +# having a similarly complicated parent. Each clip is a rounded corner which, +# when rotated, clips a box with all corners rounded. +root: + items: + - + bounds: [0, 0, 200, 200] + type: stacking-context + transform: rotate(180) + items: + - type: clip + id: 10 + bounds: [0, 0, 200, 200] + complex: + - rect: [0, 0, 200, 200] + radius: { + top-left: 20, + } + - type: clip + id: 2 + bounds: [0, 0, 200, 200] + complex: + - rect: [0, 0, 200, 200] + radius: { + top-right: 20, + } + - + bounds: [0, 0, 200, 200] + type: stacking-context + transform: rotate(-90) + items: + - type: clip + id: 3 + bounds: [0, 0, 200, 200] + complex: + - rect: [0, 0, 200, 200] + radius: { + bottom-left: 20, + } + - type: clip + id: 4 + bounds: [0, 0, 200, 200] + complex: + - rect: [0, 0, 200, 200] + radius: { + top-left: 20, + } + + - type: clip-chain + id: 5 + clips: [10, 2] + - type: clip-chain + id: 6 + parent: 5 + clips: [3, 4] + - type: rect + bounds: [0, 0, 200, 200] + color: green + clip-and-scroll: [root-scroll-node, 6] diff --git a/third_party/webrender/wrench/reftests/clip/fixed-position-clipping-ref.yaml b/third_party/webrender/wrench/reftests/clip/fixed-position-clipping-ref.yaml new file mode 100644 index 00000000000..abbc91f8978 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/fixed-position-clipping-ref.yaml @@ -0,0 +1,15 @@ +--- +root: + items: + - + bounds: [10, 10, 100, 100] + clip-rect: [10, 10, 100, 100] + type: rect + color: 0 255 0 1.0 + - + bounds: [110, 10, 100, 100] + clip-rect: [110, 10, 100, 100] + type: rect + color: 0 255 0 1.0 + id: [0, 1] +pipelines: [] diff --git a/third_party/webrender/wrench/reftests/clip/fixed-position-clipping.yaml b/third_party/webrender/wrench/reftests/clip/fixed-position-clipping.yaml new file mode 100644 index 00000000000..8eaa3887101 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/fixed-position-clipping.yaml @@ -0,0 +1,49 @@ +# This test ensures that children of fixed position stacking contexts are not +# clipped by parent clipping nodes. The contents of the fixed position stacking +# contexts below should not be clipped by their parent clipping nodes, but +# instead should be promoted to be children of the top-level reference frame. +--- +root: + items: + - + clip-rect: [15, 15, 30, 30] + type: scroll-frame + content-size: [60, 60] + bounds: [15, 15, 30, 30] + items: + - + bounds: [10, 10, 100, 100] + clip-rect: [10, 10, 100, 100] + clip-and-scroll: root-reference-frame + type: stacking-context + items: + - + bounds: [0, 0, 100, 100] + clip-rect: [0, 0, 100, 100] + clip-and-scroll: root-reference-frame + type: rect + color: 0 255 0 1.0 + # The same test as above, except this time the stacking context also starts its + # own reference frame. + - + clip-rect: [115, 15, 30, 30] + type: scroll-frame + content-size: [60, 60] + bounds: [115, 15, 30, 30] + items: + - + bounds: [110, 10, 100, 100] + clip-rect: [110, 10, 100, 100] + clip-and-scroll: root-reference-frame + id: 4 + type: stacking-context + transform: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] + items: + - + bounds: [0, 0, 100, 100] + clip-rect: [0, 0, 100, 100] + clip-and-scroll: 4 + type: rect + color: 0 255 0 1.0 + id: [0, 1] +pipelines: [] diff --git a/third_party/webrender/wrench/reftests/clip/iframe-nested-in-stacking-context-ref.yaml b/third_party/webrender/wrench/reftests/clip/iframe-nested-in-stacking-context-ref.yaml new file mode 100644 index 00000000000..c8f3148ea54 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/iframe-nested-in-stacking-context-ref.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - type: rect + bounds: [50, 50, 50, 100] + color: red diff --git a/third_party/webrender/wrench/reftests/clip/iframe-nested-in-stacking-context.yaml b/third_party/webrender/wrench/reftests/clip/iframe-nested-in-stacking-context.yaml new file mode 100644 index 00000000000..5976ccd5113 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/iframe-nested-in-stacking-context.yaml @@ -0,0 +1,23 @@ +# Ensure that a stacking context with a simple clip that encloses +# an iframe correctly propagates the stacking context clip to the +# clip on the iframe. +--- +root: + items: + - type: clip + id: 2 + bounds: [50, 50, 50, 100] + - type: stacking-context + bounds: [50, 50, 100, 100] + clip-node: 2 + items: + - type: iframe + id: [1, 3] + bounds: [0, 0, 100, 100] +pipelines: + - + id: [1, 3] + items: + - type: rect + color: red + bounds: [0, 0, 100, 100] diff --git a/third_party/webrender/wrench/reftests/clip/reftest.list b/third_party/webrender/wrench/reftests/clip/reftest.list new file mode 100644 index 00000000000..8518911a9f4 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/reftest.list @@ -0,0 +1,18 @@ + platform(linux,mac) == border-with-rounded-clip.yaml border-with-rounded-clip.png +== clip-mode.yaml clip-mode.png +== clip-ellipse.yaml clip-ellipse.png +platform(linux,mac) == clip-45-degree-rotation.yaml clip-45-degree-rotation-ref.png +== clip-3d-transform.yaml clip-3d-transform-ref.yaml +fuzzy(1,4) == clip-corner-overlap.yaml clip-corner-overlap-ref.yaml +fuzzy(8,60) == custom-clip-chains.yaml custom-clip-chains-ref.yaml +== custom-clip-chain-node-ancestors.yaml custom-clip-chain-node-ancestors-ref.yaml +== fixed-position-clipping.yaml fixed-position-clipping-ref.yaml +platform(linux,mac) == segmentation-with-other-coordinate-system-clip.yaml segmentation-with-other-coordinate-system-clip.png +== segmentation-across-rotation.yaml segmentation-across-rotation-ref.yaml +skip_on(android,device) == color_targets(3) alpha_targets(1) stacking-context-clip.yaml stacking-context-clip-ref.yaml +== snapping.yaml snapping-ref.yaml +fuzzy(70,2400) == clip-and-filter-with-rotation.yaml clip-and-filter-with-rotation-ref.yaml +color_targets(1) alpha_targets(0) == clip-out-rotation.yaml blank.yaml # Unexpected color targets, see bug 1580795 +== clipped-occlusion.yaml clipped-occlusion-ref.yaml +== clip-empty-inner-rect.yaml clip-empty-inner-rect-ref.yaml +== iframe-nested-in-stacking-context.yaml iframe-nested-in-stacking-context-ref.yaml diff --git a/third_party/webrender/wrench/reftests/clip/segmentation-across-rotation-ref.yaml b/third_party/webrender/wrench/reftests/clip/segmentation-across-rotation-ref.yaml new file mode 100644 index 00000000000..6d14bab91c6 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/segmentation-across-rotation-ref.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - type: rect + color: [0, 255, 0, 1] + bounds: [100, 100, 100, 100] diff --git a/third_party/webrender/wrench/reftests/clip/segmentation-across-rotation.yaml b/third_party/webrender/wrench/reftests/clip/segmentation-across-rotation.yaml new file mode 100644 index 00000000000..c90cf79e4e8 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/segmentation-across-rotation.yaml @@ -0,0 +1,30 @@ +# This test ensures that a clip that is segmented, with segments +# that have no intersection with the world-space outer bounds of +# the clip rectangle render correctly. In this case the first clip +# defines the outer bounds of the clip rectangle and the rotation +# ensures that the inner clip isn't optimized away completely. The +# segments of the rounded corner clip don't have any intersection at +# all with the clip in area from the outer clip, so they shouldn't +# affect the rendering of the green square. +--- +root: + items: + - type: clip + bounds: [100, 100, 100, 100] + id: 2 + - + type: stacking-context + clip-and-scroll: 2 + transform: rotate(0.25) + items: + - type: clip + bounds: [0, 0, 2400, 900] + id: 3 + complex: + - rect: [ 0, 0, 2400, 900 ] + radius: 50 + - type: rect + color: [0, 255, 0, 1] + bounds: [0, 0, 2400, 900] + clip-and-scroll: 3 + diff --git a/third_party/webrender/wrench/reftests/clip/segmentation-with-other-coordinate-system-clip-ref.yaml b/third_party/webrender/wrench/reftests/clip/segmentation-with-other-coordinate-system-clip-ref.yaml new file mode 100644 index 00000000000..8627d2ed746 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/segmentation-with-other-coordinate-system-clip-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - + type: rect + color: green + bounds: [0, 0, 100, 100] + "clip-rect": [0, 0, 100, 100] diff --git a/third_party/webrender/wrench/reftests/clip/segmentation-with-other-coordinate-system-clip.png b/third_party/webrender/wrench/reftests/clip/segmentation-with-other-coordinate-system-clip.png new file mode 100644 index 0000000000000000000000000000000000000000..67507a2573a03111175b53dedd7c1800bdf565fa GIT binary patch literal 763 zcmeAS@N?(olHy`uVBq!ia0vp^Yk+to2OE%#tPGpWz`%6K)5S5QV$R!38wC$D@Ekr+ z@qeML{{g98zrzAGQ#QiPwN o&T@`v!7QgWo1Tgc1L2>{2?mFFj!GTx114$)Pgg&ebxsLQ0Qw0&wg3PC literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/clip/segmentation-with-other-coordinate-system-clip.yaml b/third_party/webrender/wrench/reftests/clip/segmentation-with-other-coordinate-system-clip.yaml new file mode 100644 index 00000000000..db98da3a3e8 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/segmentation-with-other-coordinate-system-clip.yaml @@ -0,0 +1,47 @@ +# This is testing whether a clip properly clips a primitive in another +# coordinate system that is segmented. +# See https://github.com/servo/webrender/issues/2294 +--- +root: + items: + - + type: "stacking-context" + items: + - + bounds: [0, 0, 100, 100] + "clip-rect": [0, 0, 100, 100] + type: clip + items: + - + type: "stacking-context" + transform: [0.98883086, 0.14904226, 0, 0, -0.14904226, 0.98883086, 0, 0, 0, 0, 1, 0, 1533.3134, 109.21605, 0, 1] + items: + - + type: "stacking-context" + transform: [0.7818315, 0.6234898, 0, 0, -0.6234898, 0.7818315, 0, 0, 0, 0, 1, 0, 132.98201, -64.04077, 0, 1] + items: + - + type: "stacking-context" + transform: [0.93087375, 0.36534107, 0, 0, -0.36534107, 0.93087375, 0, 0, 0, 0, 1, 0, 68.64584, -46.80194, 0, 1] + items: + - + type: "stacking-context" + transform: [0.8262389, 0.56332004, 0, 0, -0.56332004, 0.8262389, 0, 0, 0, 0, 1, 0, 116.458824, -61.550323, 0, 1] + items: + - + type: "stacking-context" + transform: [0.90096885, 0.43388373, 0, 0, -0.43388373, 0.90096885, 0, 0, 0, 0, 1, 0, 84.200554, -52.906708, 0, 1] + items: + - + type: "stacking-context" + transform: [0.98883086, 0.14904226, 0, 0, -0.14904226, 0.98883086, 0, 0, 0, 0, 1, 0, 25.3134, -21.78395, 0, 1] + items: + - + type: "stacking-context" + transform: [0.73305184, 0.68017274, 0, 0, -0.68017274, 0.73305184, 0, 0, 0, 0, 1, 0, 149.64511, -65.28949, 0, 1] + items: + - + bounds: [1000, 0, 1000, 1000] + "clip-rect": [1000, 0, 1000, 1000] + type: rect + color: green diff --git a/third_party/webrender/wrench/reftests/clip/snapping-ref.yaml b/third_party/webrender/wrench/reftests/clip/snapping-ref.yaml new file mode 100644 index 00000000000..7be527df93e --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/snapping-ref.yaml @@ -0,0 +1,17 @@ +--- +root: + items: + - type: clip + bounds: [0, 0, 1000, 1000] + complex: + - rect: [50, 50, 100, 100] + radius: 16 + items: + - type: rect + bounds: 50 50 100 100 + color: red + + - type: rect + bounds: 200 50 100 100 + color: green + diff --git a/third_party/webrender/wrench/reftests/clip/snapping.yaml b/third_party/webrender/wrench/reftests/clip/snapping.yaml new file mode 100644 index 00000000000..d2ad449ea91 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/snapping.yaml @@ -0,0 +1,17 @@ +--- +root: + items: + - type: clip + bounds: [0, 0, 1000, 1000] + complex: + - rect: [50.3, 50.3, 100, 100] + radius: 16 + items: + - type: rect + bounds: 50.3 50.3 100 100 + color: red + + - type: rect + bounds: 200.3 50.3 100 100 + color: green + diff --git a/third_party/webrender/wrench/reftests/clip/stacking-context-clip-ref.yaml b/third_party/webrender/wrench/reftests/clip/stacking-context-clip-ref.yaml new file mode 100644 index 00000000000..50fb8facd46 --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/stacking-context-clip-ref.yaml @@ -0,0 +1,32 @@ +--- +root: + items: + - type: clip + id: 2 + bounds: [0, 0, 100, 100] + complex: + - rect: [0, 0, 100, 100] + radius: { + top-left: 50, + top-right: 50, + bottom-left: 50, + bottom-right: 50, + } + - type: stacking-context + bounds: [0, 0, 100, 100] + items: + - type: rect + bounds: [ 0, 0, 100, 100 ] + color: [0, 255, 0] + clip-and-scroll: 2 + - type: clip + id: 3 + bounds: [120, 0, 50, 50] + - type: stacking-context + bounds: [100, 0, 100, 100] + filters: hue-rotate(90) + items: + - type: rect + bounds: [ 0, 0, 100, 100 ] + color: [0, 255, 0] + clip-and-scroll: 3 diff --git a/third_party/webrender/wrench/reftests/clip/stacking-context-clip.yaml b/third_party/webrender/wrench/reftests/clip/stacking-context-clip.yaml new file mode 100644 index 00000000000..5516b65539c --- /dev/null +++ b/third_party/webrender/wrench/reftests/clip/stacking-context-clip.yaml @@ -0,0 +1,36 @@ +--- +root: + items: + - type: clip + id: 2 + bounds: [0, 0, 100, 100] + complex: + - rect: [0, 0, 100, 100] + radius: { + top-left: 50, + top-right: 50, + bottom-left: 50, + bottom-right: 50, + } + - type: stacking-context + bounds: [0, 0, 100, 100] + clip-node: 2 + items: + - type: rect + bounds: [ 0, 0, 100, 100 ] + color: [0, 255, 0] + # The same test, but this time with hue rotation, which means that the stacking + # context is rendered to an intermediate surface first. Unfortunately, we cannot + # use a rounded clip here because we want to avoid subpixel differences and avoid + # relying on a PNG reference image. + - type: clip + id: 3 + bounds: [120, 0, 50, 50] + - type: stacking-context + bounds: [100, 0, 100, 100] + filters: hue-rotate(90) + clip-node: 3 + items: + - type: rect + bounds: [ 0, 0, 100, 100 ] + color: [0, 255, 0] diff --git a/third_party/webrender/wrench/reftests/filters/backdrop-filter-basic-ref.yaml b/third_party/webrender/wrench/reftests/filters/backdrop-filter-basic-ref.yaml new file mode 100644 index 00000000000..aec471df212 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/backdrop-filter-basic-ref.yaml @@ -0,0 +1,11 @@ + +# Tests that a basic invert backdrop-filter works +--- +root: + items: + - type: stacking-context + bounds: 0 0 0 0 + items: + - type: rect + color: [0, 255, 255, 1] + bounds: 0 0 256 256 diff --git a/third_party/webrender/wrench/reftests/filters/backdrop-filter-basic.yaml b/third_party/webrender/wrench/reftests/filters/backdrop-filter-basic.yaml new file mode 100644 index 00000000000..e71decc659a --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/backdrop-filter-basic.yaml @@ -0,0 +1,21 @@ +# Tests that a basic invert backdrop-filter works +--- +root: + items: + - type: stacking-context + bounds: 0 0 0 0 + items: + - type: rect + color: [255, 0, 0, 1] + bounds: 0 0 256 256 + - type: clip + id: 2 + bounds: 0 0 256 256 + clip-rect: 0 0 256 256 + - type: stacking-context + bounds: 0 0 0 0 + items: + - type: backdrop-filter + bounds: 0 0 256 256 + clip-node: 2 + filters: invert(1) diff --git a/third_party/webrender/wrench/reftests/filters/backdrop-filter-perspective.png b/third_party/webrender/wrench/reftests/filters/backdrop-filter-perspective.png new file mode 100644 index 0000000000000000000000000000000000000000..83e181e970b2a43a263f3f9e0523400e005483ec GIT binary patch literal 60142 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/filters/backdrop-filter-perspective.yaml b/third_party/webrender/wrench/reftests/filters/backdrop-filter-perspective.yaml new file mode 100644 index 00000000000..43d29ddf1d6 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/backdrop-filter-perspective.yaml @@ -0,0 +1,28 @@ +# Tests that backdrop filter works with a perspective transform +--- +root: + items: + - type: stacking-context + bounds: 0 0 0 0 + perspective: 500 + items: + - type: rect + color: [255, 255, 255, 1] + bounds: 0 0 1024 1024 + - image: "firefox.png" + bounds: 0 0 256 256 + - type: stacking-context + bounds: 50 50 0 0 + transform: ["rotate-y(-50)", "rotate-z(-45)"] + items: + - type: clip + id: 2 + bounds: 0 0 100 100 + clip-rect: 0 0 100 100 + - type: stacking-context + bounds: 0 0 0 0 + items: + - type: backdrop-filter + bounds: 0 0 100 100 + clip-and-scroll: 2 + filters: invert(1) diff --git a/third_party/webrender/wrench/reftests/filters/blank.yaml b/third_party/webrender/wrench/reftests/filters/blank.yaml new file mode 100644 index 00000000000..c4eb3ab6730 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/blank.yaml @@ -0,0 +1,2 @@ +--- +root: diff --git a/third_party/webrender/wrench/reftests/filters/blend-clipped.png b/third_party/webrender/wrench/reftests/filters/blend-clipped.png new file mode 100644 index 0000000000000000000000000000000000000000..04b63a8909fb6231517fde44e4ee4d68cfbffb98 GIT binary patch literal 2102 zcmeAS@N?(olHy`uVBq!ia0y~yV7UTh@8n%H?e4-_`F{C(^RM6kaA&!7kQC4|1_LoiUJ%XH1m?1^ zn1Xm599bYrKmg<{1_gx`j6ll4A%Gc3H8d;$Ig^2LR3j)DMgwPfrJMz`f8AFEC+!CP zBk!;2Wt#JfU$v^_1ZRjEMZe#trr%l^y)1j~on0&cbAd(A>}NMypYvL*e)F}HYnyJ} zh6KpxtecU`w;o@&f629z>t|O*fHO}*-QLg7=U<)6bN9o;?U20o?Ci9s&$n2c>8v)D z-1X2IsyY43^xjf+ch;@utl%8^>}c*~@7r7LBOfD&SA0U~%VQ1mz2e~s@?)vjwPuiL(JL&D@<#`9;pCa-V0wHo51&71O0-h6vd_i)r?e@IYVbF*H* z>NY=A`qOIpxC?nZ-yDkidx7_TwJTW7gJS8wZ(r^Ds|YTDKJ2_(eRaX#FN^Z8zv}18 z(ude|WZ(7&(z*ZjmuG+VHvjY88)DwZi*KV}ed`B@iNl*u-RE>u-}GDxUCPK7FgtRQ$@T<#`qBAt71eXaDr{+Y9pMS6{6M+6E~Sd!8=L&Ay%L zeQw>=c!)X4k%i|Z-(Hk`9eqDEz#39iewgW2zkiwD-4Ak*@E5+l>y`D}Z>PhrE|R|U zc@M;V&a;1#qhtGCv6b7agZ)o(i{wMPRXW*;xTwsQHF=IwEj-TWoZyCDgt z@9lr{HQ9$$yVtEgtsWXs?FI?Wb z#NDNbPes*Cg1BM%nK!%k=^YPpe47bzDgWDjRlc{kBn9gBEtP%)Ni7xkf2Pm7#rNmm zsVEtL)~(AS&bVj1xw<;mZ;A99F>qqZc&qMxKKpz8@5NDbIv}Q3r+xb|*Q_sZ!)s5l z=?k9M8UOuT_W9S-tv__`W`8b(W2rYL8z8h!V;hm0+<8{0wGxU4Mimg z3R5Ae{2l^!1Db;f#CpPaT$~emP^6+eMw3l__3gb@7oQqyX{|ta#Y%(NNQxr z0iUES5;B31m6(jAk+MM)0Qk{-K#`P8O@Px#8JQFxnm=L@;RDKMH3|V=giwk8h{%wG z@K3YI$#8GvcBCf)V-1JH{m6tApYXuF@6ExMKjI52HQNV;%FD|`=6!<9BB!Ff&}cNu za~o>gHV+WtLCMdgCeS=GDNgU4{M%1pG9`(emQ78|%7mN!CM0I%Q2h}IGtuwQyKz#} zQvP7dq`VIc3=n1RL3trPQNQ~JP5sPTpHOmIGRSQ9kMZ(bME-~FJ&zyCO#Y|Ayo+>E z3x4r>v#v@fQ2O%fY{l#wq;|UIlgNxgIHQ_gh0)GkIn*+ zx2_p}&_&qnT{tavE&_e`Wmyq6{$hIVa&7Mt{ zc9Ifz>{v*E5<&o8v1H(5yYn}%X2|rE6&NUdG8HOA&WsPdZRwD*JZt=SmU4@rFG8@L zHonT4XPtSyvT-HG{X>pEUHhELpCXc`!{9)P#PXV5E?-l3eWHG6e@okuO#F23tA{9- zk+&JC(m*rWx`C1ro^&WT+U7XxI_Rxy{PrOXO2Xwf#f4(V(;G2B;vM1OkM0=PIL zRKlIfCGIh@M}0;8F@Q6qrn+8(Uo=vyw0|}t>wn>VMaddS>hmdKLxyZ3Ieb)=H!54_ z_VGs-Rzt1!$cmYAZ?P(duv&b)EHRwzDBlzy$1n24CqnZ370j+myH?}sCM$j%r8q3OrQ$LN%@Wfu3Bg->ihPx4@%uu2Onh(G^2t%k(`? zsW=gSv>`GGw?MCV+v*^}BaX?2UiK}(=HHM*(gn2>7baWVARbVI+Q!r&+VeCy?!nWuvvLR~iv z+U#T%%D*2LvM3{z7WpgRaYIxKS7TKeWbqBD(YW@P?xCZz0u4G-)Vl@OksIp{g=bf{ zyQFqsFwMZWRzP43YyiYED1aY$3V^~r zw_897BU3!IIRVUD!r}9Mw(CGl`%@_F@||arSLX9S<-}l2B?S}Am`_(>v8QmTsxLv~ zr=A@Bw#oJA#RbP!qr09LbfjyIuBF$1^Ls;Y zp$H8P^X6TGp7o+-aN3T~_CzT0Sd0EboYcVS5@9!R6o{OiUe zlt~37k0o^ej=lKx=AG}-!<>-uyd$3c$Yd48j|$Eo6WtuGQ$2gDeYg;PX*waF#$75t zbm8XR{A%KF4bzI*H&3)&*xDNm>4TXHwM;0D&QHm~<)oXAvD4+FuL%RXUjuspOPS87T8y zBhAJ0(r_gI+-b%c$P!2n#3u`hk)|8$75W4ESam-COkv!mX$iR=&rHfGO`!?9`NMx_ zvnEz@zG;fPt9Q$C%06rn_LVVo0#|vpo*kx@YaE9qD-+63oos&qEi7*?HekNx3x6UN(Tj9r_6GN0dv|9r$2B=LdhB_! zJMr7R0)Kw@4aGHy6?F~FG$7RpC+8ijCOqV|{Bxw& z8<*-nbII1u9jbR)?kfqFllpo3ZJ||m!eZ~afulDYk9BJ|I1MXM^4t7#tgvgO*(67x zglX46o7>Gk(wBcnDYd~PEZ*+ z)U*RK;R;ug<+jz@?yjg+rwZd1UgWDNO(2{O?Y^ZcB#z~;TW=ymHtxmB%7wFEMimIh zvo>%pgsz9cM)onZ%2G`XqL^mUFL1*=XsfMQ1{ONEmzO*%Zj-w@fJFz`jJJhY{@{Ez zr3%IXpwVCwj9%s909HpO`@y7n)wz-c))cTWz$}{Wz2g<(STbX@%TR>~Q z!-jT&n2HksWZV9)`d!V&GBC@^0?(TH{v!BarpRQ03u^bG=EeA!{~Y%3!3DB+#~=R} DM}K!M literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/filters/filter-blur-scaled-xonly.yaml b/third_party/webrender/wrench/reftests/filters/filter-blur-scaled-xonly.yaml new file mode 100644 index 00000000000..b244741cda3 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-blur-scaled-xonly.yaml @@ -0,0 +1,16 @@ +# Ensure scales from enclosing SCs get applied to blurs. This one +# applies a mixed-dimension scale by scaling the x-axis only. +--- +root: + items: + - type: reference-frame + bounds: [0, 0, 100, 100] + transform: [5, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] + items: + - type: stacking-context + bounds: [0, 0, 100, 100] + filters: blur(2) + items: + - type: rect + bounds: [10, 10, 50, 50] + color: 0 255 0 1.0 diff --git a/third_party/webrender/wrench/reftests/filters/filter-blur-scaled.yaml b/third_party/webrender/wrench/reftests/filters/filter-blur-scaled.yaml new file mode 100644 index 00000000000..b99b6c7f83e --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-blur-scaled.yaml @@ -0,0 +1,15 @@ +# Ensure scales from enclosing SCs get applied to blurs +--- +root: + items: + - type: reference-frame + bounds: [0, 0, 100, 100] + transform: [5, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] + items: + - type: stacking-context + bounds: [0, 0, 100, 100] + filters: blur(2) + items: + - type: rect + bounds: [10, 10, 50, 50] + color: 0 255 0 1.0 diff --git a/third_party/webrender/wrench/reftests/filters/filter-blur.png b/third_party/webrender/wrench/reftests/filters/filter-blur.png new file mode 100644 index 0000000000000000000000000000000000000000..067f8e4af726376671ede3123fe71172b747435f GIT binary patch literal 58574 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 AH~;_u literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/filters/filter-blur.yaml b/third_party/webrender/wrench/reftests/filters/filter-blur.yaml new file mode 100644 index 00000000000..42762f56d7b --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-blur.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: stacking-context + bounds: [100, 100, 300, 300] + filters: blur(10) + items: + - image: "firefox.png" + bounds: 20 20 256 256 diff --git a/third_party/webrender/wrench/reftests/filters/filter-brightness-2-ref.yaml b/third_party/webrender/wrench/reftests/filters/filter-brightness-2-ref.yaml new file mode 100644 index 00000000000..209dfb3cc3a --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-brightness-2-ref.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 120, 120] + items: + - type: rect + bounds: [10, 10, 100, 100] + color: [0, 0, 0, 1] diff --git a/third_party/webrender/wrench/reftests/filters/filter-brightness-2.yaml b/third_party/webrender/wrench/reftests/filters/filter-brightness-2.yaml new file mode 100644 index 00000000000..2bdb20aa1ab --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-brightness-2.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 120, 120] + items: + - type: stacking-context + bounds: [10, 10, 100, 100] + filters: brightness(0) + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [255, 0, 0, 1] diff --git a/third_party/webrender/wrench/reftests/filters/filter-brightness-3-ref.yaml b/third_party/webrender/wrench/reftests/filters/filter-brightness-3-ref.yaml new file mode 100644 index 00000000000..3201170182a --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-brightness-3-ref.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 120, 120] + items: + - type: rect + bounds: [10, 10, 100, 100] + color: [0, 128, 0, 1] diff --git a/third_party/webrender/wrench/reftests/filters/filter-brightness-3.yaml b/third_party/webrender/wrench/reftests/filters/filter-brightness-3.yaml new file mode 100644 index 00000000000..8ea1eef5a84 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-brightness-3.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 120, 120] + items: + - type: stacking-context + bounds: [10, 10, 100, 100] + filters: brightness(4) + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [0, 32, 0, 1] diff --git a/third_party/webrender/wrench/reftests/filters/filter-brightness-4-ref.yaml b/third_party/webrender/wrench/reftests/filters/filter-brightness-4-ref.yaml new file mode 100644 index 00000000000..b300d2a0268 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-brightness-4-ref.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 120, 120] + items: + - type: rect + bounds: [10, 10, 100, 100] + color: [0, 64, 0, 1] diff --git a/third_party/webrender/wrench/reftests/filters/filter-brightness-4.yaml b/third_party/webrender/wrench/reftests/filters/filter-brightness-4.yaml new file mode 100644 index 00000000000..9a15b15895a --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-brightness-4.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 120, 120] + items: + - type: stacking-context + bounds: [10, 10, 100, 100] + filters: brightness(0.25) + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [0, 255, 0, 1] diff --git a/third_party/webrender/wrench/reftests/filters/filter-brightness-ref.yaml b/third_party/webrender/wrench/reftests/filters/filter-brightness-ref.yaml new file mode 100644 index 00000000000..98e1bf419a6 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-brightness-ref.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 120, 120] + items: + - type: rect + bounds: [0, 0, 120, 120] + color: [0, 0, 0, 1] + + - type: rect + bounds: [10, 10, 100, 100] + color: [64, 64, 64, 1] diff --git a/third_party/webrender/wrench/reftests/filters/filter-brightness.yaml b/third_party/webrender/wrench/reftests/filters/filter-brightness.yaml new file mode 100644 index 00000000000..65da0e0b254 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-brightness.yaml @@ -0,0 +1,17 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 120, 120] + items: + - type: rect + bounds: [0, 0, 120, 120] + color: [0, 0, 0, 1] + + - type: stacking-context + bounds: [10, 10, 100, 100] + filters: brightness(2) + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [255, 255, 255, 0.25] diff --git a/third_party/webrender/wrench/reftests/filters/filter-color-matrix-ref.yaml b/third_party/webrender/wrench/reftests/filters/filter-color-matrix-ref.yaml new file mode 100644 index 00000000000..2d32e5307b8 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-color-matrix-ref.yaml @@ -0,0 +1,21 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 120, 170] + items: + - type: rect + bounds: [0, 0, 120, 170] + color: [0, 0, 0, 1] + - type: rect + bounds: [10, 10, 50, 50] + color: [100, 175, 136, 1] + - type: rect + bounds: [10, 60, 50, 50] + color: [255, 0, 255, 1] + - type: rect + bounds: [60, 10, 50, 50] + color: [255, 0, 0, 1] + - type: rect + bounds: [10, 110, 50, 50] + color: [0, 0, 128, 1] diff --git a/third_party/webrender/wrench/reftests/filters/filter-color-matrix.yaml b/third_party/webrender/wrench/reftests/filters/filter-color-matrix.yaml new file mode 100644 index 00000000000..4deb19b779d --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-color-matrix.yaml @@ -0,0 +1,53 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 120, 170] + items: + - type: rect + bounds: [0, 0, 120, 170] + color: [0, 0, 0, 1] + - type: stacking-context + bounds: [10, 10, 50, 50] + filters: color-matrix( 0.393, 0.686, 0.534, 0, + 0.189, 0.168, 0.131, 0, + 0.349, 0.272, 0, 0, + 0, 0, 0, 1, + 0, 0, 0, 0 ) + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [255, 0, 0, 1] + - type: stacking-context + bounds: [10, 60, 50, 50] + filters: color-matrix( -1, 0, 0, 0, + 0, -1, 0, 0, + 0, 0, -1, 0, + 0, 0, 0, 1, + 1, 1, 1, 0 ) + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [0, 255, 0, 1] + - type: stacking-context + bounds: [60, 10, 50, 50] + filters: color-matrix( 0, 0, 1, 0, + 0, 1, 0, 0, + 1, 0, 0, 0, + 0, 0, 0, 1, + 0, 0, 0, 0 ) + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [0, 0, 255, 1] + - type: stacking-context + bounds: [10, 110, 50, 50] + filters: color-matrix( 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0.5, + 0, 0, 0, 0, + 0, 0, 0, 0 ) + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [0, 0, 255, 1] diff --git a/third_party/webrender/wrench/reftests/filters/filter-component-transfer-ref.yaml b/third_party/webrender/wrench/reftests/filters/filter-component-transfer-ref.yaml new file mode 100644 index 00000000000..e578f95b391 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-component-transfer-ref.yaml @@ -0,0 +1,51 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 170, 250] + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [255, 0, 255, 1] + - type: rect + bounds: [0, 50, 50, 50] + color: [0, 255, 141, 1] + - type: rect + bounds: [0, 100, 50, 50] + color: [255, 255, 0, 1] + - type: rect + bounds: [0, 150, 50, 50] + color: [191, 128, 128, 1] + - type: rect + bounds: [0, 200, 50, 50] + color: [0, 255, 24, 1] + - type: rect + bounds: [60, 0, 50, 50] + color: [255, 0, 255, 1] + - type: rect + bounds: [60, 50, 50, 50] + color: [255, 255, 255, 1] + - type: rect + bounds: [60, 100, 50, 50] + color: [255, 255, 255, 1] + - type: rect + bounds: [60, 150, 50, 50] + color: [223, 191, 191, 1] + - type: rect + bounds: [60, 200, 50, 50] + color: [191, 255, 197, 1] + - type: rect + bounds: [120, 0, 50, 50] + color: [255, 127, 255, 1] + - type: rect + bounds: [120, 50, 50, 50] + color: [128, 255, 198, 1] + - type: rect + bounds: [120, 100, 50, 50] + color: [255, 255, 255, 1] + - type: rect + bounds: [120, 150, 50, 50] + color: [239, 223, 223, 1] + - type: rect + bounds: [120, 200, 50, 50] + color: [63, 255, 81, 1] diff --git a/third_party/webrender/wrench/reftests/filters/filter-component-transfer.yaml b/third_party/webrender/wrench/reftests/filters/filter-component-transfer.yaml new file mode 100644 index 00000000000..a573b5587ef --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-component-transfer.yaml @@ -0,0 +1,352 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 170, 250] + items: + - type: stacking-context + bounds: [0, 0, 50, 50] + filters: + - component-transfer + filter-datas: + - - - Identity + - Identity + - Identity + - Identity + - [] + - [] + - [] + - [] + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [255, 0, 255, 1] + - type: stacking-context + bounds: [0, 50, 50, 50] + filters: + - component-transfer + filter-datas: + - - - Table + - Table + - Table + - Identity + - - "1" + - "1" + - "0" + - "0" + - - "0" + - "0" + - "1" + - "1" + - - "0" + - "1" + - "1" + - "0" + - [] + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [173, 255, 47, 1] + - type: stacking-context + bounds: [0, 100, 50, 50] + filters: + - component-transfer + filter-datas: + - - - Discrete + - Discrete + - Discrete + - Identity + - - "1" + - "1" + - "0" + - "0" + - - "0" + - "0" + - "1" + - "1" + - - "0" + - "1" + - "1" + - "0" + - [] + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [0, 255, 255, 1] + - type: stacking-context + bounds: [0, 150, 50, 50] + filters: + - component-transfer + filter-datas: + - - - Linear + - Linear + - Linear + - Identity + - - "0.5" + - "0.25" + - - "0.5" + - "0" + - - "0.5" + - "0.5" + - [] + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [255, 255, 0, 1] + - type: stacking-context + bounds: [0, 200, 50, 50] + filters: + - component-transfer + filter-datas: + - - - Gamma + - Gamma + - Gamma + - Identity + - - "2" + - "5" + - "-1" + - - "2" + - "3" + - "0" + - - "2" + - "1" + - "-1.75" + - [] + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [135, 206, 235, 1] + - type: stacking-context + bounds: [60, 0, 50, 50] + filters: + - component-transfer + filter-datas: + - - - Identity + - Identity + - Identity + - Identity + - [] + - [] + - [] + - [] + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [255, 0, 255, 1] + - type: stacking-context + bounds: [60, 50, 50, 50] + filters: + - component-transfer + filter-datas: + - - - Table + - Table + - Table + - Table + - - "1" + - "1" + - "0" + - "0" + - - "0" + - "0" + - "1" + - "1" + - - "0" + - "1" + - "1" + - "0" + - - "1" + - "0" + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [173, 255, 47, 1] + - type: stacking-context + bounds: [60, 100, 50, 50] + filters: + - component-transfer + filter-datas: + - - - Discrete + - Discrete + - Discrete + - Discrete + - - "1" + - "1" + - "0" + - "0" + - - "0" + - "0" + - "1" + - "1" + - - "0" + - "1" + - "1" + - "0" + - - "1" + - "0" + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [0, 255, 255, 1] + - type: stacking-context + bounds: [60, 150, 50, 50] + filters: + - component-transfer + filter-datas: + - - - Linear + - Linear + - Linear + - Linear + - - "0.5" + - "0.25" + - - "0.5" + - "0" + - - "0.5" + - "0.5" + - - "0.5" + - "0" + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [255, 255, 0, 1] + - type: stacking-context + bounds: [60, 200, 50, 50] + filters: + - component-transfer + filter-datas: + - - - Gamma + - Gamma + - Gamma + - Gamma + - - "2" + - "5" + - "-1" + - - "2" + - "3" + - "0" + - - "2" + - "1" + - "-1.75" + - - "2" + - "1" + - "-1.75" + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [135, 206, 235, 1] + - type: stacking-context + bounds: [120, 0, 50, 50] + filters: + - component-transfer + filter-datas: + - - - Identity + - Identity + - Identity + - Identity + - [] + - [] + - [] + - [] + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [255, 0, 255, 0.5] + - type: stacking-context + bounds: [120, 50, 50, 50] + filters: + - component-transfer + filter-datas: + - - - Table + - Table + - Table + - Table + - - "1" + - "1" + - "0" + - "0" + - - "0" + - "0" + - "1" + - "1" + - - "0" + - "1" + - "1" + - "0" + - - "1" + - "0" + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [173, 255, 47, 0.5] + - type: stacking-context + bounds: [120, 100, 50, 50] + filters: + - component-transfer + filter-datas: + - - - Discrete + - Discrete + - Discrete + - Discrete + - - "1" + - "1" + - "0" + - "0" + - - "0" + - "0" + - "1" + - "1" + - - "0" + - "1" + - "1" + - "0" + - - "1" + - "0" + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [0, 255, 255, 0.5] + - type: stacking-context + bounds: [120, 150, 50, 50] + filters: + - component-transfer + filter-datas: + - - - Linear + - Linear + - Linear + - Linear + - - "0.5" + - "0.25" + - - "0.5" + - "0" + - - "0.5" + - "0.5" + - - "0.5" + - "0" + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [255, 255, 0, 0.5] + - type: stacking-context + bounds: [120, 200, 50, 50] + filters: + - component-transfer + filter-datas: + - - - Gamma + - Gamma + - Gamma + - Gamma + - - "2" + - "5" + - "-1" + - - "2" + - "3" + - "0" + - - "2" + - "1" + - "-1.75" + - - "2" + - "1" + - "-0.25" + items: + - type: rect + bounds: [0, 0, 50, 50] + color: [135, 206, 235, 0.5] diff --git a/third_party/webrender/wrench/reftests/filters/filter-contrast-gray-alpha-1-ref.yaml b/third_party/webrender/wrench/reftests/filters/filter-contrast-gray-alpha-1-ref.yaml new file mode 100644 index 00000000000..6b176d9570e --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-contrast-gray-alpha-1-ref.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 120, 120] + items: + - type: rect + bounds: [0, 0, 120, 120] + color: [255, 255, 255, 1] + + - type: rect + bounds: [10, 10, 100, 100] + color: [223, 223, 223, 1] diff --git a/third_party/webrender/wrench/reftests/filters/filter-contrast-gray-alpha-1.yaml b/third_party/webrender/wrench/reftests/filters/filter-contrast-gray-alpha-1.yaml new file mode 100644 index 00000000000..87758d3f4a2 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-contrast-gray-alpha-1.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 120, 120] + items: + - type: stacking-context + bounds: [10, 10, 100, 100] + filters: contrast(0) + items: + - type: rect + bounds: [0, 0, 100, 100] + color: [128, 128, 128, 0.25] diff --git a/third_party/webrender/wrench/reftests/filters/filter-drop-shadow-blur-clamping-ref.yaml b/third_party/webrender/wrench/reftests/filters/filter-drop-shadow-blur-clamping-ref.yaml new file mode 100644 index 00000000000..6ffcde5f5b9 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-drop-shadow-blur-clamping-ref.yaml @@ -0,0 +1,18 @@ +# Ensures that blur clamping happens after scale factors are applied +--- +root: + items: + - type: stacking-context + bounds: [100, 100, 300, 300] + filters: drop-shadow([0, 0], 100, blue) + items: + - type: rect + bounds: [0, 0, 100, 100] + color: 0 255 0 1.0 + - type: stacking-context + bounds: [400, 100, 300, 300] + filters: drop-shadow([0, 0], 50, green) + items: + - type: rect + bounds: [0, 0, 100, 100] + color: 255 0 0 1.0 diff --git a/third_party/webrender/wrench/reftests/filters/filter-drop-shadow-blur-clamping.yaml b/third_party/webrender/wrench/reftests/filters/filter-drop-shadow-blur-clamping.yaml new file mode 100644 index 00000000000..1d7157a8e63 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-drop-shadow-blur-clamping.yaml @@ -0,0 +1,30 @@ +# Ensures that blur clamping happens after scale factors are applied +--- +root: + items: + - type: stacking-context + bounds: [100, 100, 300, 300] + transform: scale(10) + items: + - type: stacking-context + bounds: [0, 0, 300, 300] + # Blur will be 20 * 10(scale) = 200 and it should then be clamped to 100 + filters: drop-shadow([0, 0], 20, blue) + items: + - type: rect + bounds: [0, 0, 10, 10] + color: 0 255 0 1.0 + - type: stacking-context + bounds: [400, 100, 300, 300] + transform: scale(0.1) + items: + - type: stacking-context + bounds: [0, 0, 300, 300] + # Blur should be 500 * 0.1(scale) = 50. This tests to make sure clamping + # does not occur before applying scale factors, otherwise 500 would be + # clamped to 100. + filters: drop-shadow([0, 0], 500, green) + items: + - type: rect + bounds: [0, 0, 1000, 1000] + color: 255 0 0 1.0 diff --git a/third_party/webrender/wrench/reftests/filters/filter-drop-shadow-clip-2.png b/third_party/webrender/wrench/reftests/filters/filter-drop-shadow-clip-2.png new file mode 100644 index 0000000000000000000000000000000000000000..f58e15c5fccb271c0e73667bffa856e338d1b43b GIT binary patch literal 3115 zcmeH~TToM16oxk?cmxdL9xIS=6GSVQ+7SW-NI`{>i>M4LcXaAFwh*9=AS5t{)(}+C zfq@_j0Ug0n(UCe@BABT~XrXXAK+&QCB5=%rT5=FcP3Yby?|tbj4`=4zf7bfeT6?dT z$o9nUVw!C*0{}3iA|v(!U__yB?HcIx{W)F^fcZjHgkXRE#rct-wv8ipC6hnrW|_HI z6|6ag|$U$0Ee zR+#|#1%+`~$^ZORO}Ks}u_b3=Vfq*d>&?JJGWGNKTBcuho5y2{X-h#df5rhScP{1M zUF4<)`>&Mh+vM31P9^x)M5DFlY^$-a@(9srL#y^=Voa<{a65Y+rmOKAFAD_QF|o8B^>v%Aw9qyj?y~w0 zyidO;Xmmj{P5dGHl&Z@_Cg<1*L8BjS#*6HzIy#b*;VfJDt8E~e~=QkLp20dZATQ*{c#yy_wHmqnJEc@eJ3X}JLGAx!$tu;Be?g>D7@t0 z(FCMCq3Y>|CC1=T4eDkHnTL?L91(;_AY_TR1wx9OEmX?jATm=Q0M%G9^z`{q)d%y) z%q}iexA;7gTO`~tcdFI+U7(;*4)4&4`W3QD6&P$E=eVdrj|b_}Y#~g>Ik1BscTxZk z!8m6lJh+k>@ZgAZU`IX9B<8FJVq9X`Y(aRCn5!D3;1XYWa3?W$HQ?itJ@DX4VjgPX zgiD0*;7pbU!sTwi!TngcJb9})sV2LNmje1D30780>ekTDxLf-c7L7{%Cnr)eK$9iw(57i(t)k(&m1KF08?kHp2cE%%+(+}3E4YNm_qyE0a$U1 zGKs+A*?Ayqs!hPHa9o7o`{$LII3RO4(GQzA#j&O)BjeLxs6*iaLh2SFhBe+yGzqxB z(QZ&dJ|Q*4R*0;qCR>`&{+uD+M2B+H69wF_Dzs>%-Zywi|9TXVuSi)VmV2qu{OL-j z);AngOUFb`RK_69z4Sy(NclTaHPMlB1*@ThAQZwyJk@oeSqNV3|K2k%k@1KKx+w_=gg=bq&^FUB;zk*d8kpkzcn_43d z@KnPAES!SKM|sWOaPDdaD^At{Qx4-@1&PD(IWE~yqn%zTjKR>2rdGlqeN6?$RaT2{ zAEwn*hT7+WtIlM$=IPPCg>E(svt>AePgcLxJ6PR;)g4&ff&bnCz4_bu=Sn4u{S5vI O08u+)Big=9$NvSBv1{-E literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/filters/filter-drop-shadow-clip-2.yaml b/third_party/webrender/wrench/reftests/filters/filter-drop-shadow-clip-2.yaml new file mode 100644 index 00000000000..6aafa731372 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-drop-shadow-clip-2.yaml @@ -0,0 +1,17 @@ +# Bug 1561447: If a clip task is created for a picture, it should take into account the rect of the drop shadow +--- +root: + items: + - type: clip + id: 2 + bounds: 10 0 300 300 + clip-rect: 10 0 300 300 + - type: stacking-context + bounds: 30 30 0 0 + transform: rotate-z(-45) + filters: drop-shadow([15, 0], 0, red) + clip-node: 2 + items: + - type: rect + bounds: 0 0 100 100 + color: blue diff --git a/third_party/webrender/wrench/reftests/filters/filter-drop-shadow-clip-3.png b/third_party/webrender/wrench/reftests/filters/filter-drop-shadow-clip-3.png new file mode 100644 index 0000000000000000000000000000000000000000..4583f5f730cf99b55c9189938d1f57d6b1a5a5b0 GIT binary patch literal 37433 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 bKlCdN&TWrl{^X~JpAWZv^nR&N8PxNA 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

    bZPM!h}P&``+e|4P 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 z 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%0A~ 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/image/occlusion.yaml b/third_party/webrender/wrench/reftests/image/occlusion.yaml new file mode 100644 index 00000000000..4e89a7765b6 --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/occlusion.yaml @@ -0,0 +1,15 @@ +# Ensure that the clip rect of a primitive that is promoted to a compositor +# surface is correctly applied when registering it as an occlusion plane. + +root: + items: + - type: rect + color: red + bounds: [50, 50, 200, 200] + + - type: yuv-image + format: interleaved + src: spacex-yuv.png + bounds: [50, 50, 200, 200] + prefer-compositor-surface: true + clip-rect: [75, 75, 150, 150] diff --git a/third_party/webrender/wrench/reftests/image/reftest.list b/third_party/webrender/wrench/reftests/image/reftest.list new file mode 100644 index 00000000000..5c3db88404c --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/reftest.list @@ -0,0 +1,14 @@ +== tile-size.yaml tile-size-ref.yaml +== very-big.yaml very-big-ref.yaml +== very-big-tile-size.yaml very-big-tile-size-ref.yaml +== tile-with-spacing.yaml tile-with-spacing-ref.yaml +skip_on(android,device) fuzzy(1,331264) == tile-repeat-prim-or-decompose.yaml tile-repeat-prim-or-decompose-ref.yaml +platform(linux,mac) options(allow-mipmaps) == downscale.yaml downscale.png +skip_on(android,device) == segments.yaml segments.png +platform(linux,mac) == yuv.yaml yuv.png +skip_on(android,device) == tiled-clip-chain.yaml tiled-clip-chain-ref.yaml +skip_on(android,device) == tiled-complex-clip.yaml tiled-complex-clip-ref.yaml +platform(linux,mac) == texture-rect.yaml texture-rect-ref.yaml +platform(linux) == occlusion.yaml occlusion.png +# allow slight lerp change where the squares meet, but catch lerping problems on the boundary (should clamp) +fuzzy-range(<=1,*450) == rgb_composite.yaml rgb_composite_ref.yaml diff --git a/third_party/webrender/wrench/reftests/image/rgb_composite.yaml b/third_party/webrender/wrench/reftests/image/rgb_composite.yaml new file mode 100644 index 00000000000..74db512eedc --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/rgb_composite.yaml @@ -0,0 +1,10 @@ +--- +root: + items: + - type: stacking-context + bounds: [50, 50, 100, 100] + transform: scale(1.5) + items: + - image: colorrect.png + bounds: [0, 0, 100, 100] + prefer-compositor-surface: true diff --git a/third_party/webrender/wrench/reftests/image/rgb_composite_ref.yaml b/third_party/webrender/wrench/reftests/image/rgb_composite_ref.yaml new file mode 100644 index 00000000000..0bc4a6e436a --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/rgb_composite_ref.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: stacking-context + bounds: [50, 50, 100, 100] + transform: scale(1.5) + items: + - image: colorrect.png + bounds: [0, 0, 100, 100] diff --git a/third_party/webrender/wrench/reftests/image/segments.png b/third_party/webrender/wrench/reftests/image/segments.png new file mode 100644 index 0000000000000000000000000000000000000000..168e9348ea07b8fb50b0950c928c5f4c6b9c02c1 GIT binary patch literal 7182 zcmeI1dsLEn8pkn9-ArLmJ7Z}^m~FG$DVwcX%?qSZYE9B*_Ar~8&el%1RlLo3L7>S- zD5t!1aau2wR@7n2nl7#vcr$Z~+$18Wn~Elcn1~>P@WRW!f+(|Rch2rfC;#wIe(&%4 zJkRfWKHndQH%EoL&Gwv)Kp@;ABM4g&2nQboVn(=&6Wp?z9DN6Y@M?`Dgnp7ys%Za^ znXsFitMXr0aPCpqtXc1QiS`^>w86zCZ0$i$Lg>tmu5|}aT~l5A@O)UHYx4n@BZMU` z4xug;5l8mS_1Y2|y8K2&!Ebc^R34bD99my?I{o!`HAWV?^fN_Q@YP}W`qI?T1%IbN z-M&R>ix?4)yB7FFGE$ko-YuA-w1EHEkSIWzARS*o=h3*Dt?Cl7FC9u%KUl`!U$vx) z=NW01^1vdBa#KHm%WRG=N(X{62O1=f=ud$T)iEPIr4MD&u=&-zlg3VGeifZ+ICO5m zs$A!b20vAw7Wg7@*@nA{FZ)oj4h2ryz40t3XAPM)STYFd030$z^GNQuRab^h1>0_7 zvW7H7j>nD+Og`4oBX4a@{u;+L{|a1vm6+oQ`qy{4Wvcm0iKJ>l?EDIrsc*Mll~2z$ zD9cG<$pyQgvA?2v&QX#d9Ca76APqYh?7Oa4km;1Ho0}KTw5;3+mZa>&YhLB~y=LHy zU2@kQRb@L&{tt3)W8L~h@fq}OiMyEPl>Qo#$K9y7UsJqKIKy*3V>b*y7h*sZ6?`df zsiF;WM6GWsgI)SW@UUE+zjQmJ=k)G!ezPBtU2BkifSh63ahGXR%`J=}oj&)4ebXt7 zfn;8)$+v5}#ON`$UQ<$)6gDUC?D)jzJu3#mLp;?{vXSK{G%vH;%_n%xL)eOR?w?gY zb~DweQDs0vw=uV=xng`C=>~inWz&4TZg+-Mg|L_#Mf!LdK$Fae6*6 zic}n)i5j!w$=r*fwx0??K1zSbgO{{ZuquVQ!se0uY{}wGNnv;t>8Suy>ua%xd!K-q zmHq1QQn*o>f={FA()7mYBRK($787TgpY0rSsxNPL6M6vOW-Ioy^Ej0uE*68yO>yS! z|49V9+G_HC8miPu2Z3M@g!3evWu|JK8(FwYr{C=pndy+q%xyke4F zh|Om5z;?+tRtnRyp=135yrxX)%Q8yjtwS}#D5`uTe;aWP!4eVSIvFO=98m##Rnu#T z$!0F32kRl7w~u8-*x?@}Mq0)GG!>@S3=IzA7L2c2 z2B?N+=OYXx_`tfhR6|lqgBTJ#T6oB^vId@ex76LZn9);omFNhr4yXl|+w9ozyG+|2hkN z`3C}a-ccAt;M@Me)XJfD5o(w37hnkrHyihez?bUy8(OD5$UDDG%KRVgD?Wu$H|#lQ zVXV;SQ!9s#N2p!C?;ODFBOg#N8ZAcRQ_hueN}!;A>5}xb_JwZEDMYSc3_}Q< z`W`OOiW1Jc^xU2V@_4Kmh@|v<+h*F5%(oqplIzmBf4p+X);IAg?iD$#WUS9-dbOF@gC1MqBc04o z$P!=8sOAM~1p&5&Khnzlmtmpd?sO?N%zVr?JO)pPI<6tf>&|!Q%27vc6qIO zF`*^Edl#IfVab>Sj!2vQVD^=NjL(!2?}E>0d)FICtE& z<%f0fY%7!puP1LQUg*O!N=5fLj#ko|!qhsRasQ18%qOc#vE?U%2J#nFY_wFc&YpLw z2*%SdEW0E*!U$`?eLtcHCPmRsE3V0SF_>0dlkp-mt+*!T#cNt|O~#Aj z&nPZCNj{aHU>V3Q6i1sL(m=O@=UwJHyvTWXkF4WJHU45-{W;TDJ_4~YA2-)^{w?N7 o6C!Ju(!r~A93kML7GXMYp literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/image/segments.yaml b/third_party/webrender/wrench/reftests/image/segments.yaml new file mode 100644 index 00000000000..5c4d42825c3 --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/segments.yaml @@ -0,0 +1,12 @@ +root: + items: + - type: clip + bounds: [10, 10, 260, 260] + complex: + - rect: [10, 10, 260, 260] + radius: 32 + items: + - image: checkerboard(2, 16, 16) + bounds: [10, 10, 260, 260] + - image: checkerboard(2, 16, 16) + bounds: [10, 290, 260, 260] diff --git a/third_party/webrender/wrench/reftests/image/spacex-u.png b/third_party/webrender/wrench/reftests/image/spacex-u.png new file mode 100644 index 0000000000000000000000000000000000000000..1de9b6ff664413b7a17c3787ad6143a5e3cd348a GIT binary patch literal 81302 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

    |6I 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

    BAl_psyagMvg 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

    wn(gGU`rfu-}i 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/image/spacex-v.png b/third_party/webrender/wrench/reftests/image/spacex-v.png new file mode 100644 index 0000000000000000000000000000000000000000..1d1a1c0118d9e37c59b29c5ed94208bbc7ee369c GIT binary patch literal 97851 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≪p^vj=fI2$i2azCpQRDDdielgHNWe^4y9=Wh|iUT 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⋙Fsmodfp{V7%I_4y4^1Ip@DWKPMH`#G$z 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 Ih(XZ*0GAd2kpKVy literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/image/spacex-y.png b/third_party/webrender/wrench/reftests/image/spacex-y.png new file mode 100644 index 0000000000000000000000000000000000000000..2256fabecd0a2bdfb86f565fc845ebf067def56a GIT binary patch literal 128761 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 z z|LyJ3cyYztsXZwz@O<8=VM9Le&{^5f0?_dEjvWlTG2gY%F4PQUpINv_OZT_`hw302(9oHKQuD%@A2GE>UtY8D5gx4uHlP)wIXzSwk5W)YwY zv*u*vZXQ!&q8_RwiM(Sh({yp|^@+Qdn;eBVw!bu&$RapRZXN@-qvninH* zifE2`9y>D6-&q*SoD$m{itv#&@@LnniHXQ5yq3d7*AGiI1I}a)@93Q`rb0+uN6v}e zkzrb3W|S6HjiFVm8B7RV&9u7r?a&pdPGbevK6f1vi8MinH}fZ=SzJVizLUwQjOiL4 zuL=)hSC+%xsP0oP)McN1q>d8+rWUP%hAYGg8?`t$qsC-0rF!-gXmwwkKOTaRF0DuC zz|Uuv?yPx^(jWm|vL9{s6gw!se_QQMvGJ{BnWZvZ;S(_iSt$^wT zu+8tUb<}Ne+Ke5Dye4p=!o+0GIZf)!&~Q*B@?3{VlkZk4offSMorduSiAQZ@I%y^9 zI$j(nBTBFsg9|%oip$m8WP%Kw)ZLdh?z>)w9dfE~ly{ERN4{DxWYbAbj-T$449<3N zDrm-u&1fPfQtZviXAUBWm6}JiGky8;qG|47jV#+@0u9kBMpHmeQyyPAx$%STbGQEZ zTfb6=)g1BJx@wyBl7~rt6b&4B6N5+uFoiMA(eHoJ7deH+yBK3uFT{@0P>lP$yvZwn z{o1{f)jeen_qH;q!;BNz!iRk++(Xz^DHT=H$!Zm8W{O1%Dzu~8oroD_F1RE^G|?W- zNnFhXlT78P<8c^I`0@Sk*R?JA0-2^{yUMpdef_&{o~M9n+3j%V$BKlTEm83VVnb=n zPHQyJC=_Rpkl8f`b72B*BiPv}CYkGO)67n+c=F1Hy#4NrtA~H=19ib5ojrGE@Rp@I zISgS$PxDC&Bvx|djPP%A`U}_T5W*bg3(ZM3)aJ}Sr~VPKlUm02Z#=&zb;*g)TlG5b z97&NDuyNC;^L?pLdj?6rBsZuUTQEilHmO1_pRZOOjmuG(%x33i_uvz^Y`qX~7?`;f z>z5fvM}7X_fgK;cri-(I*f)knKDl!I%99TgRE*e~ESTJ3l+C0-Lw3MUURovC+S&Mb zg9Xh*6=c~cN_uTZK;%knaAW7Bj@nPKJSCbsDni=to`3i2pZWe#db+g!w0p9lg-@1| zCXg>jc1?Mb;u82Gr_lNbf(s(Ua!MEryE`4Ay&v)wM}ZRu%Qr0D+(nJp9qMl6##xP> zp|z)B)u*c;_6wqvDyuW?kGU3JW(IZ_1nF?m?Z)nCe{paLDc0v&(D&G3(@&3W*e&*p$HzbC)~z?V({CQ#KlbbI zrf^`yJhg2%)+|>=B*@L`1douGRz1u2CG-?8B?qR%>+HA5D5?HPP)a z$mih<3$KLB?g$6(`?=N?>h`=ghiyZfo*tF$<-bhW2ySU5&l#HlVzmw&~4JEq`$G z$Dew($W)@OM2w#N`g4Ip>nR5F=aiomiw7NTTjwRWBeUMD4*RFgm{t+3iY zI9Vh0#b+TWv{hzd(~sti2~%}~A%VkLv105_)=6wtCs!uaqQxfZH<~1n6`y5_vpw9L z7(5r+;pYxM_?I6*3u7YD0P7}EQjDp2p8JIxIH8Ty+wcNWR?{3aMFdU9`S?VL*U9q+m6ui1V%Oj4 zZoREf``y!r`~m%d`Vit-i|;4F-u~*wus*LM{O&g z&fDDW@GC({Y-Seb8i~T9U;1*lPsHS|<^ne!5?iDuuzEGZH1+^-K#sq~^5#een#S!Y zi*>)TniySr|HI;|?8{qg7A7K?g1ihGzV&c~12hv{OK~Hp39Erz)khz+VhOk|JLy$u z61ZBeTY0r5(YLlc_znbRZ+-m%eg!ok#7e#PR+-CwBF@dZU#ny?%5F2R`0h-ngX9f~{m z9UD?V)mo;pE>YEq>}R*hR?whaloL{(*jm(of!bNA7$N+zCw z0vcHNlvRmspG>2dx7H4dHJs1F{<+SS*}3})F?TB_oW%tu7Q;oF1*%vl`FP_?Pec_=Ds zchknzGv&o11MamNnH48>n7E=8E`ui~5}qCO_rscD^_LCH;(GieP_GgEVqBMTCIn#zI5xh)S5n6 z-1+3E=#5EUUj4DAZ?8Ur#YRoh_L(QK3eFpE~J`}G7N62ZKT$E1st>v0*90E zLBheEc#WtetLp5vx(aj3$!V711ZTv_n>lqf9t*PobuI8-hKP}o$li3AaOHX3L)xC}@@#bjfv$rd^g5P0A5`-wX;-It_m(vA5 zemWjqNsx(!9VGEt&AaMgvkO*JfVi{~S|KJTD=IK2!GKIQNms3;(AdxSODsUVpH@*u zQb4#ZA`q#DUBq+Y2v-9*$mjFKXR{I{@Q(E=U%4w!@2o!dvp;xwzy6Q<-S+%f|M|cC z`CU0LYa7<~<^L`3-q*9^oB!&A`!D=T{Wt!}(`)x`?#F2f3X1e$vE~o8{p?av^dr8^ zXeR&q0l;);yM#Q#pT;SD^9xlR4oP|1^qEMM+?-6UNeGMy<{(l6i?VmM7`aaSEaSj+ z`qnVyo41cd8*`-*j%P+r)ZFG2Y2v}HB)B>hB3?aFtxh6DqE=mXFQ4_9Mb<7mYgm)1 z0q&&i#tnHVXPd`Wbg4aTs24H8GsCH+suB^^jxk^=({to^{?4y`{bO&x_V?xC?t=9T zzw`Dh<5{1VC;Rta^tyVX*U!KI^(Rk0{INIv`P;Ymf8aMCoO`SLe6H35z7-CLgji(L zfqpa|$bMGLX8%c|xoW5OfY0?X8>R9v-I;<8w)CSl8_SU3a8)JsNa~4kEIScQ!__&7 z>ojSfT&bg+&OR)g$i_9BAFo3i#=&0KL+j*bKrP}`T?k5Ei!&`uiJz+V(ewNB7Q`oaN^5c7ODW($9SO;QOyX*_?gi?1StLwqTHm z1mctuyPys8^Yy>VQUSUvrlQ_%It_*857K+KEyEiNf za4<6&!-%~YrKu)I{4V?`}gLB?b zzx|7UaPRzI|J?Jh-|EG^Kfm|~pL^%p>3TCw1*xpd!{f*AeCP4k&wuaY=#A80`O2L% z*?I;anOi}@4AVn$6Ic?HA3VE$#Mg{G$7#sXvmXdQ4ZnvClXOYgmAc%DdA6!%=5cJz zGd#?)4W?S;=p;?ak)`ZQ?Kh--cmBae8HfECpQ6{&tVyl4KGgICWPsPAFfGM&+?8>d zs+o1D%tDFsDtJIL#|$;*2+b8=+q6L&IS>Wi!9j|dV`VW1!_1OXB{CNnPs?Or(LMG5 z>FL8){)f;1%HwG{aEC4W_x{$qZ@kzoB!P7`onP18RsCQor+@M@-<;CJ-@2Ptb`d(0 znV8Lup3s7*+v|n@==*9GGLT#FE~COgZ5>r72UB^{7p(}FlC$6a+{o(j?+io=u{3PdAGR-5jfb&_J zr#^|BhI-;he;tNjbfB@#HO6HL3?dgE9Y^!w)X1TN#I8QpYr@n>o}hWSbsWj%lstb=)ek5QJF-TFQPLs)?28 zo!Z^|j6N!$;Dhx%+-45$Wk63$Nj)%Q%*>sbea3#GQ(dezW$yhbT|4@hU-{Ozo}&kw z?!zZsjOc_mkH2#M=7jC?g!X6Goy5NV}@nJHNh=`<=2hB$( z>rCpLauV)Da+bVOxR>pIB!E~%OaU`DxD%(u)VR3DDMg(#jYVs9PMx^wq&gKaRF&s= zU%Mo_K69j8LVqoeZtc0>ea7*^3DJH?_BKr>BH%E zx6ISiCvSiMwXffNvR{i&a(0`~7W8}Xy#4d5i}i2Ik5{f&L1PA8-VZS|L(GKQM#%DP zfAo>1`N%tg+T01ZjnxKWF=DwSp@HEtz4yKSQmS;b%#uhFamq=AXSq?7sLaGE%eAAV zHC=y+)cYPLNaW1CSTJ|K+wWNAn9q<-EOBC zCr!au7bwse&~0t-5gsP-JnD&@kV8bkOuXf0yqBG3<_r^vlI$s8`SA4m!`TPHWYU&_7!%3)aN)S0p=nVCBWWz0Yh zA)S_;*`%@z#Um-4#A@z}cC2C*UURZqxM#5$U(yPYxq*_fFuSXjk<}8J1cz|4%7)r8 zr7#a+G*@1qiwHmDlEr1l8V>g@;}q`9>OzM%A0#HH`33-3B6O)6&iH2e*WG`+{Ds%v zocN3PcHjEqvA(eX;vb#%^~R$!f1FJGY3?_U)7yWXfAu?l=6~%zJ=uNkdsXJE2CY^_ z{1n8@aGU?YUtzVV)lO}Ib$N0SyC*XPKw?ajB`zP($tD~23eW77Tvdck6vSp`EpJ09 zLYqNpu^0zmkyv3ea-_U13pMJvf(IEnb)=&xm=KyTJMgL~kt%U17h*bA;wCYIPYm0M ztB8D#gorI}w2<1h=th|-xIW8y9K>#I7Tt1&shN=RfovQ%S}K|>my|anAH8?}+z;O9 z&hGuf4<6;)Cr4M$?tHlV7vpFC(QosIpR6xF9`l{?@i)HJfAbSB>G9L=*U!9tb@|-y zJ^bV~QhZckkaHYIl3S+mMBt*7JQ#E zM$l?3?xs~s#cl@?_Q?(AJe~Kv-w7syr+JuS2R?f9ByR1!&Yc#`?>m$>cK8J%@SFkD zC{O@#resJbPy3Pie3M`M-1<*nIKKDY@80|Qlka}*A^+*cPmDK@zO*6P{dm5A^o{-& zT|9hx_3B?+(u0S`^v3aDdw=4u`IKfXsNJS?sBhXeHjN8*`*Y3k947ha7OibK412$(sXJ{KTO#~VG% zGm@L|l;eoo!j(tUqC8hVcz9I`r~GcQC&T^sZf<>j!>; z7=G^l=4toAho7!*S%=(8gzzYEgsz{sS*gRhmWBK;<7l$)}QnD)9CQ1tgT`pw%n=)pCz%|H3z?@BtF{9pcL|MmASZmzD~|3iP}27lr+ zL$B|j@_H+H`oeA5pP2mEH@W$8vC{#{BO-3Nq&Mp64=XPY6p?seOnx{u;AGTf-tLEo z5)^q4P7dnm(RBN^)487<6LJ-4ydY-Mc~`oLtCKKaNQQ&MCNZm}j8ck=I)J$p0o=;Y zdBNdD8?FWfcO*B5EIRFU9MtB{i>h-Ti*>}5{GdudP@)b}58`NOodPKEX5oP*n`;{~ z&Jb@kNQ?DO1SGC_a&)xvcNe!l^+b5_yLj>2a_?Jt>u(p%D?Ry=KfLjKr>n9*yZ3an z-n@3(>*8D8$NI%;^W?^`2iaj(-!@5W^JYmFxn$T62iiZnemo1jng(e8wsh%P7}amA zXLjn(uQ7_d3p;llqgX{G%lhR%;viTSGDGTXg0s}YJYg!ln3@IFtidq&G%`0EZL4j- z?EtJ+N7n=lk(W*a2X~`QWRm8M)QH{l!7;nL_}t6S@08n(^(ILw(y&$S`LKzX&y&BGrQ!2OUnE( z#7|7A=gswWBWcGOJR zYC|M!*p8m-q@1Y|0cy}>s??49B$C)>#xPnpb%^l`ss?5zia)EAK4{tqmoH>SMI9h@ zBZck0_&e`Beetv3e)oefJl>WUufOpAJBwSdfB6%)%aup_TMPZ*=(YW|PWdx0Jo?Tu zztCUHax7QzT77h7|LN(SCf=GyjE6K5Tq>@7-qb(r>+%>RZtcZ0dc6Y)pXPUad74Vn zLKn4e*5lSmxXvwus!_v;94#|6W$A>hj+GKY+&p)rl(0R&ChXo+)_|jOZ-Y}sE!C7n zSk;AsF1X(ohI3BjM(&eJnWSzj=PJU9=H2AcW!Zt+HzQ3mGK0%qMju&T06u3(z$k1R zoS2Qo@~i&6-TnKY_}G)*zxUGgQ%7IE{4HU9JK^pVw?b*yc@f+E9hMvH@U zwZ!&;yMW%ix9pNF7UE{YiQtY>S=>lfEuwY+%0~;8Iy`+^`lOIrkO+<>`$>hX2_KH> zREq#q#i|-OBhf@WCpwvF&ag=!ZdMDIVP)I8%5FeHgmF_L4qiY6xPe2y!%$~;Xabz1 z`7O6P0|>uZ0PL`!v>Vt-+)Wfl_wTOW?e84_kM3M-PL})e{Qhe{_0E4WjCcQ?pZ$Md z;8}F(ffP7_TIbGYCGI~XqlI(p_S20%084T;^W6UN+Zv@0`AN zy?*-c4}SgcrGL6OKHB~I-}|-SJL&s7eD&t$`0VNW`v2kPtMC5lqknk(+WO`fzxP%B z=1Y00cOFhRcbvT{p`;#%P=-sW=&&k361X_bVjGL7ks5Vw_UG^81d2>7b+NHpCEHgc zEQra~P1Ve1TA(wr6HQg^LXAdbs>PDTeU^jc8W?8qD>6gfiLy)LoLS9XoNcKls#T}G z=3IO^C7p(n^mySnA2Cl@r!*^n*{qF(%hiKmSipzO(zxm7(022Ik{G{fTsK{oXIU z`fs;yz40p-FLc|BNd+rX6sLT=6M zj?yl(>f$cKJ!dzW3~o-YUR43B9;Ja#ViuG$Ef@P$-lv6(L@;6^PUMWh-h$H{yjv$G z=bRHI&jxiTlbp%Ru;|LDgvlpWL+UzDH!qg`d96d5S$>xe5pD7@b5Wmx25~5&{J^R6 zmWE|bb03CeAdoXG`GYI#=f3ga`7eI#=_mfxJN54W@;`g$?#1zTvwHFye|!1*i>q&U z7n@%8yZw7l|1{tI+;{K4`X}H2(%IX5L3P^V?7Sftfano4(lL$!*g_Lf`y z)_fh|n0sL+K+x!_aX%gID$Jjie?&@02y7C>>}y!rfBuDUyeLQ8#rO~Y#eellw{w>5 z`Ztcg^v3Uh`Odo!ZwfEVm6!d=lV9$?MZbBjZ@%hpzVT2t|DW#->l=3-J^EMOy7xsK zD{Vg=I3^A}y9R&py7}efb-8x4xUso`#^=xuNzsCCy#I+D-As+-tyrrV=7;!~)Y->U z^z`N(Sq?YPwv$O_aP~`>A5fsA6{brO!Q5oJ}6z_iK{O3>p zkH2u`tBciQyQUX^clVOMIWbLnv%EU~zdnDOzy9mryLt81?blCt=VoWWgXfRw;lpdk zCabjF0I&n_7d@QMBgenO*I|L%-*xSJVg;b>iH7U^l5dl_`<+=axAIB{rxtg@%$bg@Ux zGGe_PA-Tv-mJ8d3hxfrIF1F)4zp44>hu2mg6#H< zR$uC*>wMI!PR5qz6RI7^4ZZwPi*;!j^z11(^Ok5n3NJwccY=CnWB=TuLfwmrTZNPO zOxK=ecf?Mf7q?PMwxoXSj<@;gU|@D5N<`t8sWURt>tG936`@xtuymm@b+$iH;G^jx)(N!*&cOp~_uma7tklcmT_gxBo>se$e{1Tsqsz@j`+Dc?S8p62@3xQr zjsJVk9i6>$XY=EqKRNo$`0cNCcit#v>!e8J#6evYCj=MuKygX^#%9}_AM$l5LdtgX zDR>7?h+tNC6BuF+XHA0T?ruivWG;^v+PkwBA}ae)>xe0_ipE@@kg6}7ZeA%1RKYY^ zDQa$(Kv~Q@N8Q)(7dKILl%mOrtpbye=c8n4m!H95>Q>xE048-(MJ;Y!A`byoWVOYd z*9{>9Aq~5yU|Nx(<)M0GR%1^8g*V0?;ZXO0Ab>rYgxA;h)6LD>UtbN-_Y>dPruAtG zKIKo^#bdqrcbqn;-$7#IW95Lc?YIROPgi#;- z6(;x=rV@U*zvg{+KrBeiF6+u!J-4q5zBX(pudX9^Jz2+{Oq_z94i4-8IcH_&(R7bl znAp`~VDGIdfLa8f8rWS`w}j;?1Ihi8gl3m_a52)UsHTiGM}ZPmg0pdiCpDMk;A}EM z%o*BbOHmO1*<>l3NNmuL#cOL%O zj&%I;ADka=%T~Tj&%M9+?32Yi!yBLZgLkH5AU-q?LQC3gdJ-MauHlwD?5JLhbwqBG*-<`6+AS#(sgrh1=o zpf*e*iO0u!bE+dRYhykWyw+M#l>-7YG7wQp#OCmD^*qcE90+~U?Dsuag~U9YE0DUW zs#DHcmtb;&#`&s`S5v=Fw(`vC>~#)m`q7@2%vyw4HK$iw=T;QJk3RatM#+;P4}NRufLOGkq4c%<3An3X9rF z&IgD_Wqi2rudEhXx>Y|soP;uK-zR2nb57|xDo%|G;-==Vy-1g=qLjG6=GP(2VIR|` zab%@T!&rhO>Q1ma?2E&LIm+ZDT~1{x>Cgvw`f(C=r-x}&Uc@me(96n{4VbFJoMd_tKWL}%fgR8dHT+CH~iy=e+r_Q8$cT3J(?(E zHxAkxC?+FX%H+&YS4olM=x}wUWYNmP*+5`xvzA0)X7+aRfc{M0QLB=Diq;`opQQKdb)M(;O5(meeySG2wVD5MIXe8&TkCM%bGWn}-c59w&u}!NaESVTaU@O? z=gv~U8goWUM%B5=pUB-+ca+@V)g3Hwm}>zfi3RO2eEQ;g5HOj$5eX%+ap$y#ld8GZ zX^gHw&D9H}Jx@t3HKCtLpnefF;EqI9GI8bhi8P7V;X#(#;u_nzm;qum7d>xOe-I;G ze0cAZvntJwfv?WDj?hs%GI+%YAm96lBF= zbt=`}%$1m-RGG|Fm9@&t3?qWT4bnvJoRYXV=CDzU!7R5?O!)W&Bob$ZsWXMcSt#2> zQ4w*QJbdGe7u){bSI)lCt%sxh@jvdEP5@ulbc{?X#q(>q7M|EX8L zaZ_;Ce`=6@=iO6YVxZ^Gd9Q@ZC*u-4J^z*`1~Ul@frzABS!&=HGq3iN*Ldh`LGZD- zIyrcb$v6zOiD-^=Z)%?d$5+64@>`F(#VSpY7gvs^flj(=#X%&AKq8P_hk^Qzv=$U2 zW-r-{Nj;2Scph<5a!_h_1*McY!zhXE>tJ5ZrtRacu&NhHpxmWSoo%dK94WDfU7s8Ep#IEw91fH9(%#{vwR9XU6XmNP4xfJa{HaZ+!z;ITe^6h3VJs&% z*LObgN4sBm^6tYwzIJc@@SER$MQqr7{E6Y6-Q#h6w6naNzO}e=zP#>ER+Bw>k~aD6 zOC+dW&tda7Z<$ui|9RYrleAMIATkD8ZYu{_&&i$0xK)Vk7J?h~`uM^AitNd{oc!*4 z<1V@75DO9jX0R)IArOnFu)v6R%PM2W=7O~+@f)NT*DQN-22h7>zgX!0$sX!Ov1YNU zT4&_yQilwd&xbLY&!I(hSrZ4h%vuZ66bA0IIcF4xm@Tu=4#%0E*0ia%#QQgohWFq2 z^4Z(t=H=&%UjB{W`sC5kXOCXi@ssB#Kllbcw@F|A!wY6TBft$mkR=F*RenIY>QJku9vHWr@YSj`)2H?;m~zTjp!trdTb z^3&-1Gp8gm*J=AfKH3-bIn}d~H%qjGnE^^EbzM%A0@b_jucamf!yxlOW7bbmbqJ_cNA9yn!rYkzXwvRq z0wA;g2W|{wiiiPU_!;he`VUXb*~W{*U?TKmMiLb$9ea_fv1Zb@SaPckcbsnvcdvyjN zR=7gcq)*JLJ&%k_K6+dR2%qXo^MMc-(;Su*PEC!^GfxqD-37Vj@TgrfHa~J-Ig6RI0wCfTr?(~&><9rU z`;0M4tUZz{!CZ5nN)_ssnl-C&3I!PE9Zsf9Or&lW4{AILrbrEja|cN1K%?TOu7(pN z!JWD5_f`^vFm^fg#HQ@ZX_AzJX6=_?mV?>MF3y-#irff-I!r9`ykBJBqQIq>0@V$@J^3VL-)X6~{ z9R*vPSviOea1Q8h~-+Yd5AMaj0`ut~qFl5qxojJp(XFae4T47)<_ofx* zGp_D`ZmDWfGYbLVvqaeT-b5r^KpUlVKI*|RHTTX(>N?3}=$1>~wQ`vWT5DBpj>9~& zxtT@c)N8~q0L(FYaRAZ|tE#Hn<^XKtu4upx8OE7Z)m>eDKTH!drQ{H&oFbS&k`Q;R zZkf|+*)P|NzDqg7orQ%I5{`95EM(3kUEeR4(uw5E?v%*cREUHlIYhcd`}JCuPhYt9 zzxubo@e_~z?{O~qpUba}(@CYezPHqq^VjcPJlcKwx8=p#=XdU%zJ7EgefBH29=-kh z-EcCjxpFdQn!pqhRZZ^;q>4~6Aw*-dwL;jn(V^fWWt2IS0swJJ%pw4>BoSsNcSo8p zlnAp$Du5V`xk{H^=vsZSh@=5=T1+F8m~H9@%H8~E;+Z``fTIkVKf)HI86(0W zDkILcFiW+=opuTSg_UZQw=EgFc}{zq?4{0<0OTcSfmf)Tkua!tGld#l6>2(I4`PBC z8&loZ)Ja5-hBLuD=Zb76vrdQ{5P>^Ly2Wal*x9<#4U7U7cNNr9Q=5r`mEfUUcC>ki z68X#(7tB~6F>{*;w;n5i4+FpgiasKwnFJzp_Hp6ocmD0}AO7Cm%{!l4e53#J>2#cb z^VRjQ^ZLQfzFW}tb8GxBKKB3p^1I*u@@n^mmC@Kui92H1+I1ajz9gnU%g_etwqH8^ zxytYGX&=r()Aq+=zsxDR4R=QvBRXaPejd0WqC`9E1R$N3Nwg{0i3Q><*UKDauIfbx z>ZAre5GD7^j55DyGie?mGuu+QNv2#n8cmOmI!X>=j74C@lBgNyei#zqfx|k;%_4vn zL~I-~=l0ek6~ovg56x|MpbgFiIY)yiXlvy}VSDNjv6PRG-}>86zqRoz_R8m=yVd>A z|IYXR?OLv7n;PskNewyvGiwko*PABTm^z)oV+0aTgXZ#E{CE&i8q_qM*buIT>-} zFuE})!kE>4*2cAVE}Di0mCTq$w4%grpxAL7Y!xOC@n|AX!|rdr{9i89*{$JQXCME} z&4l5(ul{GhI{jZ>zI~2KuD##=*tcIh`QVR#`Hj0j7@vL$H4!nL+DcG%|By zu3JB@%wEwSr_7hO5AcXZ^JmV&sO&ivS94-h5UO)$Hco&Glb50RFpR17Z198i0D*~k zeh;mEi(~^QQ1kuvCW?v+(oC4bnkhoF=2Fd#A=Gsx_si#hGn0}W*)Kk>i|t8$=g#)^ zAN%aR`vG& zXi)B4G_*v%1Jm}fMr|!G^`3#enmM4JIUqM!5{c{350n~nb9Kt1Lte9*BCXqulw^Lw z;$L7!5M<-Zpb$mC_2IBy!&pw1`FuBwg-fdN$;wod7!eVB3I;%kEQ81}GpD1S4^F!` z6ilLJj^~d_h=tvqNGzD_?cyU9Nyz#9?BVd;r;E~4y|{MotFNZNvHAmg@z#g`=x=P^ z>n}8oAAaS#)Ab9x^4_&iT0MSrM1_c;cV63LIvQC;r8IWDm}E_1k}PM1TAh*`AZ}&z=73 zeAegT=-aEG{G)z+e0!JRJ{&JpFHU>d0Yz)CarQk3I>Vol^vtFDvn2f_4TDB=U-X$L z6C?e@spl=)Y(_%d&A_z{3yol<5NC<&W2R}lnVG6jl52Zyl5B3JnC&AWU35q)V~dt7 zSskaMN?W;_Ny`qLHw&*aYU+ij!Cbx!Vv7(kV`OMIZ1xX@j<(*MkBOZmVo;e0U`eU(;g?0wv*n;O868UfVI+%_ z&&>UB#k4a~5BK1#b!2w~Q7Qtji$(1^wwgvAigQBZx*d>E8OEgMR@Gb!(um!Xx$j2p zP?aQii7>mFO2fE#rs^fhNl-^}twkL;yLxN|qnncsg?m~MrBN}}B%o+p!5{-=%>K1; zjRl_W&dxxHjetQ z>w5jWcR#*-?)A6pb0>_8M;A}t#qAF&bo8#u`26d`#dD&58Cx4>JWVR*;b8OUZ>>Xk z;QYlrbM70w+wQ40i4#BA>wCjJ$;qh*v#U*sK%DCyb&!P)<36EaoU-zYhMtZ;8KELs$N8S5pyT#BgU8l}=tA0C4Gz(~lyEifnwR`3xz3^pW zL5yb|qURp^qq35D`PiGoDh_&NA^<{UAVF76UGgBVsoxi8clSx$bI-IJh-xV4s*?3c zTB(DY6AqY4>+3GBQv!onsMcx#aUy0ty?MOfG241u``yBPy1@RZM-p~U!ofuJ zn8XtSVge(cCBgutY2u^LHEzUBVj>2T&RHUH8fl}KfNp-Hzx()4PPprHx4Zvj|I7ck zkN@2p#M<82t%DmEN$DCYsuYCON+}*4);>COK%l!)((ybTx@mKQ~pSsEEt>N0w zo&SKI-nL$w(UWnMZPLOCD-)CbSuc{=7WLT;#D{15^3nEZA&N`9cMgMa_Zr!zrx$%n z)0ntca2-_&v3RZIJuI<+h~1TlsM2T?>(LVA(~zB*g9C<{H#w6$!e2d<3DgCeW&jcdK+qH+ zgCcF1l;{YBX_J)ybA=bSK$ zC%bA66dh*$c7IoQv;3ZC(Y?hz0MI`$NrQtoc=W}0d2w(&AFpo>-wn?_yZ+V(?i;*! zbuqa5-1~d0R6gD;r`6CVyR9_x$-jCV?+%8e7J_V49~3Z|H{S9xWk19m1Kv5ofPwvY zE-XVddD^37X={T3s6Ah#W6EZ3MPIHj0Si?l#YAfD2NxMh14~d);+BvMG^#5|Q{lak z=FuIpX*6t_0+2xpq|TdXS*tI{#QnSCj6@Ycq4HmZS7e6e%V7)_Lf{jP%Dxe1afpCy zhSE9qD5?=5Is{;XOzNKU9_KK9FViCC0s^%2Rd{mto)ge}PxKUT9ci#G3+O6f8 z=jRV!{r&ixzy92#t?M#D-rZk(|H7vl`NNZIqw_SVE(E*~t(wXyw3ErGoejt16G@X= zv74}U9Z;`&a0GwCW4+%lfELAf6~^+S%lZ&l(snI-j;cMqyVOhpJKdrKR8hpe?N$nl z2`@Mrec7d*tZ zd59yjw#Jx(m8lGXj>(Wrfsjc6tpG7kK!$>T3FVr36hdWH$7Dv7I@cA~U(lGNM*HTm z8xGTg{gc7k?p-NQ?hPjK&aOr-m#`H6Gw%IZ9w7KwUuq27JDW6pcdCG3Ca&O(*+RCdJ@n8DxQQoTExMju zInT_DNRE-pL0c#|G%pF%1PM&RsTne(86zTs;Ba&>x!F9pImU;Dd*_4y=F96Zu73Fo z-}&ji{ZqI9!{gW1KRGJ!-2Hu8zsleL$&JlM_6b*Z<91yZyVK)IDG%JqMiV_GBA`4C zK0}v+r_$*XYwD3&L?w&F(p#*u%u|X_?|Ecy+lWYJ!jbFLR&g*xJ>J3+gJ8vQgo9til~~!%OoD)g)?N8mUO)qR^C< z+?#wT0+TmY{C&Jcyz1d)u0jUk$A1C82qz*1UHR;PadCD!I;_Hw!rV;?Zqj0S0!)cL zo*iTg*(4?DpP()+O9|B!aRj}sUiR{Osy6T5_kPi=w?6NuSk45QnOa@o^Pf1z69>iv z#2A?%vwi`MvUQRoW}jvNGZ96yWM*$12)e`8??x1vj88+*>ikE%m@L3U=rTXeGF3|vtm@wDevJ4Op9vvw#U1OlLt%Qag}l>L zE$99sN@qjWnLi=`sS){FX}nQwl{=>`sj^@ZYgaH+&aGqw1ym6R$g%?!O#;XcOhm~n zGu?8H+&kHKyem^NF#|>fMFlhARN8EFx=@8eT}dZ~Or`?J{O72PQ~@L=LO>=WW*}xJ z;=I+UV;?RpvJBy8v{uAm%X5*Gya||s^A5dGp{?<3{bIGdvfh+CcVPX0^Y47~Kl^Jp zZhO9R{DaSa=`?)va4%lnplZ1DAjVW5{@VQYPps&urdu1+36Iv^{^D=^=l-QFoBztcGxYzUePuYRCZ+oIQP9DgCv@lM z*1oGBwat&tjShyd$#^9~>sN-a{a!WP)%7w26Iq^t8Gls`ic7xB*_CC$NTg<|H;qQ3 zTqbuLO0pSo<{DxL*;-c8s+pmRu?F|dbI+`3jA8(>#lh-|)=)U?VpXzXqo_g?k|A;D zCl3ZHN}OY_sGA?<1GrZM=WEq8CDF`?QUgdP^ZBFqc4vo2r*qMnrfBRvi=pGbY3a(R z9!2WyAiAN|pDu}U{^G$J?Y@ZqUEsutOi(fD`dXp$fT?Nx>B3!J|B0}Lx_#tkFr;^X z`MqoR{;Mwu!ExL={ri9O)wkX`C*)B|(&sM!^c%nO?alk5u=n8AkJ~@y4c2ndq`Mc_ z?wyVfKe@JlA`EAZP&Wh&C7+Q(p86mIi^^wHh#Wi$XBQSC5Sa96LLP=Fo_QyjX?6XR zDJ_)9*wlPEC>M3>xS&pVg=pCdA2joyl|c~^*^w%COq_1JTTeO7@4R)A(j0c40aoXk z6WhK_2J9BKSZ#pltJFk9=TxC5a{>}2gN~R9Jx@6QP)p8TwgGY*gM*v*_OA?EDU(G{ z$%NJIl&ht{s7QMF#^b22K~1Tm_byKC^6THYeCw6jZ0q0u?e-rPjc?xlDha$f2nz6@blkNn*a3A9ZkNmHk%EG0~-f_?Ge!AWHdPnBTn4U1Uqmqh?9LdT zJ-K+BA+$w6AH$r8(S3G*C%t^@;DaI9e5eKe@SC5#1t;r^#j35n`|z8a|8zWm|C_&4 zmLH#P&%(_7tN;Lj07*naRB6>W!8nx?+vy=a-gt20H5r>C5~y-^{6SVnU3#PeEIqso z5nVkTV_#@UQ9aS&e91dCB#_K^$Z!KvhLn0-P|SdKvMrVwG`=j*P-e;%N;6*K%+^MH~*8~MHBV8&j3gT2z(P zwxsHM09`<$ztvS|SJs{O)DyN8yQy3P0#!BF6y_VFgK7BhKRS5vwO{iz6{)2>I=`Pn zd-vShE(bZbwdh;g9fSC!^}3(LFnV^DbUb@*{rm^9DHDbEnq7YN?{5!BvkGPx{0rZ| zzvACr`^*2eJNoV~J{+`MU>fbIGaLd?TP0YO%|H>cBid$Nr!V|joeJGl2oNF7!{n)v z2)H5vLF!a%)|;ej_POsb6zNb~QeZXb08v4~h#3J@TOwcwQJ5_$V#?5CQa}VISE><3 z=~)>4CR%cVAyW}^njabyiCHpo*0((F#(O1W3IgJPTO?ixL$JwNBU6dDv%`fp%94n2{Nq0+ItV zmE77pQFGRMfMkf0OC3nTNRuLfIee<;FtFu;jtNl_QpeRdHFIh%2`#wby*SnK{-;kr zcyad=x3SYcws*>^Sk%|U{(wp5xb@FHtTcCDdiUnnAIj~m?c1yMSjEkY7{^}u!3LOb%Y4S0 zWh!e{)O4bhWyOHd`*1&nvtx)VF)fKP8CzF-YV_A5xZ2?!s%9Y;WB}|Wb2^v_K_Zij zNtwV%yTGDOTc?uaDglY}d5b-CuK<84Q=^vP3nHQzVj^+|Kns)>4I?@RFvA2U0)%Ab zI#vkA{tgrcFjZ1SLy%r@N+t**g4BAscQOt~%{ktAQ?Age?0({{>)TiF;>|0&Kib<~ zyX(i3OQVhTFRnda9KCVV{CyY0Y*^3svHs{s54JDyYz1mU|0Is9Q?!VV*FJyi7VMsT z_L=I{kDuT8{~Wz|?QJVsjcdEZRF6idC`adxs(HSk70C>=3xQDu0$pBAec6`-otEgW z%w{q52i#dTsh|QKbu*XrmK}hUP>eMxkRiFEHFRW1g=ontpx{yjV(Ds~&VDFmS1V`A zh^otQF6>8kxaKvmF` z-#;(_hzZ0z)&=7`FZ>zU8NKs}hUwhB_Tl5(kDJ#U_`_S64mUqDT04L3yem%*-f6=| zyD=Gz$5nYe8cr`022C?S(5p z7(f3p6%D6q-y4nRCj_%=hZqx3mkvjr3Y4pgfuNh^*^)MB*3N|nzE|SM!$pkU@v#Kk z)v|A+sHMzTI^&`dD%1Lk^UjhwYSDQ{awLg8A|^8gi$RtH-pofP$rrpLYE%RQIWrdm zTRIt_88R3;>}YfZDiR~5CS-N0NfJ{53kfZX8h>3>R8lfk6%h%N{bB^oP-Qvq2?*vi zRaa(U;iKosZ>_(}x)WYJs;x%79#_}>)$#BJK5B2x?;I9m9H(<_adJE^-ulCLo;|mE z>-YCQe{}M-S8rXp7;lZvKX|ivfU9rp9(aEI|o0w{4A;*ZVwix%@1FGc5i+Coo^ic{;$88 zCPQ60rowatMtU-^+1er~qM1Ve`BhRvRO>lhXZI5!5%klvTx>9!N>8oPj{DeSgOs_I zg3#G{BvK7?Lyc-EQdQijcj`tqj{-`>kP;LmV&J+z7?Bh3Ab~KG5)|V?kDIoi-1khW z%xlWLs?>RlCa{!5l7=W?fFLG-CS|hDXiNF)q=J$#XiB}94^g8JTBPJ_g+$0wuKr|y zdvkrd$#q z`<`{&>0TR0-AWM{W0-t$Y-)DmDENU8y2OSB$2JCH6p>`Us#ujAqNx$68x#OVLB;^p z*sEx>P}jkYs$gtNn4$n7k_D6`Mra@*G;P#H6V*Ud)Xe6N$v_k^n7Wp8Q-NTo|>95>5n*8&>z8D>>tPb|W_vQ4-_SfUv5A}!BE7PEE z@Z|@S;&c!TFFS+h%lB93<@qH0$7|_CSFwuV;Ply(-I+ItRJz#lf>ql^kwtl?Xc|-2 zg&F{tz^*S4kp_@V4W-u_AR0WS2Qknk!yOPnA%M=GY!J;6>#alq3XM{#ArGjjMO0Lc zKoK2)fB~^b0AwRDatzMMFqom43U*T$MYNuV@Sqm6c=kgjf8z{buk+1FP!}Bc>S-PLB2`k2kNapZosD#I9)FtUWQhxUu)(haY^|&7gz` zy=4f*=5QOWBXy#sISpLaSFsP+;elx2hOq@oU6pP<1+3%5WN0q0RenSYi74zoFKGeZ z%3-L;&_P5%H}FS^>6KNK6>=gt*=~g zn^k!Hv%%ovUpTFQv^$)9umQij_Md+3FaOs3Tictj)|B6Y z_A{^4NQa;OC!gOV*PO=>o1F%?8q<1THv*g7@q3Gnq}zwpV(^k&4G+A`hm%JW4%OnE zp1@$<-g@!-KT{(`)`*2>pCQmN+s4jx6OrtM9T@?Fp`+ee0RXUoNIvz%z~#_UyMODb zFrX@3jUfPVaxiW%xpJkfu%MZ6bKR$k%`jY_jW_|z1I(~f8*<4 zY!3GB{U2WW^;h<)Pi}_bZ#@7kd^l*Pc4U?|$zLj)KfH6Txb@-p%jx+kG{+5O^ zS*IH5bc`yQ>z)TuBBBW_KuSfyYz4ZeZE&FGjR=515|J;^upnka@?%dX;LA)g&zcNW z1Cfw=;M>%UO$@s;5IrMgUml1mDI_(pBnFyHGJ>e4Q2W~aDs}vQ{Ap1Hn_{GDc z>E7oro&LtP-M1cWUf#H}{&?_-3DL0G-(J^&9`OF*lMvFq$;WFO&6nR_Jg4E=#omv; zcV+cg9^E=Uo}~GsHXZ2Pm2lhQKU*72Mz>x%x;30_4>!(jKEM9{Z|Q6>UR&9EaA&J% z_t!rh6ve}>!Or6Q?s}!;&6Qg%O?0tJ!L7!IbUK?p^TjuBShNyS7NsB}DlD8|vc%a7 zcbW_=>8X$|al@jGMrWmSUzX)gOS)fYHj)||Xf*j~@FGwhM?4OpY1twpfnhl%g`!Mo zn1v9IoiCygqAGDtq0eioz?n9cnI2jEU{+W%AW{)kF(gDLLLil-mQ+~`J4Z?t1rq>A zB!+-wlzn=NXkdi#{)N@w35);Hi~sr2_g61JH$VN(tH;~F_b6`6(pu?PqOWu^JpX)r zIOVtgZ_mTe{?d(dfuqV@v3B_6ovUBnz31WbM;~xKo6OGBZNBnydxtKrHEXLY?}YK= zgZ0{6F`fG21>IV`b=rb%Ke{maub)5vz`MJz{SEPxwj4G9$A_*I&jLZi!MVTIJ{lB> z{X7hMR;O{%RKD}yF!ZiB0zsondpbUVK~c_ngM{UW=3cnF-_;bPQ;ry@IdRQlD%zwe z7G!NAYX^#Ez!f)<9XLZI24eHVf4KIykAk?x6~U%>vidni zR*Q@4)8cr*ZFz9q%$}E3o5vIdbAp;lSpp)6&#<+JKJpm_9Gv;dAOJIK&noNdh;FT{ z-*C`@HV`RM8L4=9_x#~tsu&lMT!MV}0AU#$OrqwH7y!{p0s}--K0Z{p=W83|={+Dzx zfA3>^XAk%ge{kEr2B$xjt)fVGoPV^=Y1|Cj z(fG~D9E-FiRb1TKy|y#@)m!yT$N%nIzd0JV_n!~4I{P!L|G$&rdpeA~dwg}W8eU3Y z_}m+A<;~|GZ2XTu2E_p|!iw6cHW!9Z+9^~cJ~>P__aFcc3~|0uU{jhA2r>*(Tau89 zcV;=w0{{)xw5d|Q?eqG8spPXyC1Y=SXLW8xN)Wi!;`ImT{Ve*0H6wQlBVkj6sEVZo z_EHimuwz3uH5G77ZOApEnQ7S&gXcw+UoBITh%hcgbOoE6h?>lUcts5JzyzdN4Z=)~ zO*EAVF|`3)Ky)#`gwPYZyX2Q5EDi^=cRsi?J9knxU;U`K`(Bzqm_I6_E*N?1bo6N0 zp3J6rgy-9H_3e!xrGNVeZ(TjE_NV9m&z~w*j`7+v?%*GP@)y5P?)y{UqU`_dv*jD> z^1EL+fBeU>f)(2$IbFH-)qgR%dFRv}|Kas-?_B-zAGd>n-C4Q)Xa#SNYbzucsG&rU zXnyUZ_}nQmIG-am%oL4H+Eh@l8KA-w00BDd++e#ikRgipLW2%ex1L^<&9eGwNY`QI zl0=$4h4WP?X`brQeCC-TTcZP7w4OC&o!-py&{os~IRvKxlz^O1MA?m2M73=yrJ_J2 zAfmQ50f{QnL>vmYRIJb2m^`DHnFxR&5r4VsGgM8_bRLM*7SoUa)#-(|-#OVhf3Ixq zI4V|8BuOB@xmHcbgL@B(mHPV5`t4$C`|8dA==j<@|Lrj!9bEY6qhI;e?;phnKhfcZ z^S^Uo-~P;KR&$j)`iu0x;3N=>x*%D>u3M|d;fCx zos-KaS5AKG^4fQI*q`hb9t{VCu?}YA$K!3)80*tIbynj*uAotg9h?Nj;$5forQTmB zKlR0_rXbQ~sCCdFc0aaKC*SUi`Cdkj*0$yLD3v~yPSoeoE&!>aCU9kT`ZUTVie>|R zfMiGnA`%5mRZ|}Z41m&b8K^3dmv^GlPMLPx z)cB>zx3FbBOwUL?7?m=Xv~l0B2Wc@#7=-Ya^=rTJzdhM_dD`Co zo9pw--}~^T9p`Soxqb8T>yx({ITxx04BtDV_x|e9P}}WeXb{^q_cFP5in6&8UwCiv z$-i>>x367$i-$j!Fa7S`3*{K7qJ03bR{X3l57ijEc9 zK^_W#5incTW!qmDyA93)OtvUBJ6IWs!e|u3Vs9o;K+RaKrIQ+%oJBN3Kvpwg0~h;| zS4YT_OpI6h!G~+UI=ltt$FtTSGsFTH_XG&+3)?)G*5)dKE zq<1hvt+k}H8Kk1)MOv1C&==A6RG$SMqw~z6L=1KR{l#S1HbLrj2aZxyK=3R^%>9>3 z`D8Lc%S4Kl9qtVoEPK-CFRSK|YOGumB9cf9;D%OXadN7K3&ZK!d|^e*{V$p-aa06Q zm-z>TDPv@yXuzQA9$cm$e}2uao!*;3i;qs==v+G}@AyzRaX7^B(9ud+uibwmym|FU z(;Is~{u}>Ec;~3lAD7j9Mc*pc>f*w?Czr<0xX7>o9v~l9mbJLXve|nypc@0WiTT$ z2-4C2LjVv`%q!HMQUSg7nbUxzDodxBu6oZ&2c5@{oh?|W&N5WO_HmlNSRS`<`JB@{ zkU_PSjK~sXv&Gn%%>>Hvq-p>bTalEz3Cc+CeaUn@@&25 zQc9Y~cR&V8phY%HQM1^PQkm%^rfoamGh$;(7@Gyl(#WDa=gxdO`U4Vr#UB_LCqqmp z&E^0Lo)nEw*^V`aHW7g(LNlOV8)VTrL`gaGOj6VYgv{uY5vn1h08gT(6wMJ@DL{lt zP;s!W@v*h6Yb&iAxOx^KDIy_xR5Ej!hGCNsAFQr=Hy?Yas>r5sUUP*20vQO*^K$@ZHf`H9PRUHDsMV0!oPiJ; zAd)H;WolBCNCa7>fL;{+#vtK6x9Ucku&hSog(#V0Af}ik@n^c*%1BX_fpQ9vfH{1! zrn8TqOwz;olY8TM`1WnN7w!+98QdUrOq?I#bMFL(Do^S$f)w(<7nKNkJ|VD%sVo6~#i(|YFRVDiidz!60o9q)M`un!3_Fknr**ziNN!h+5Y z(6?BXY>DPoO|Q5t88yhnIO!-CZ0D8t0^Svzs67ZbjU^T{myvT#E}g zT6pnB(Wd7XjfeePU;XlLy|}->X|ookwC3ZqnSA;A>AZLn-g|kx@j3Uc(?>V#%YVSn zPwuQ-|BD~I@-Mfh3!GG&197i>{Kl@X%pTVO^^J(qrB^J6!X7KtEx zZ<~SHXf$wUjB?rz(yXZrk(~e3k1ziDE3V^K11T7h^vPvNp;>Hdd^9-!cmk&8OTH1C zE2rx#AOGynK7@1or$tpnQZm2yzUK&bbnxyk{7$<1;d7}fhcbb>X&)b*Pd~f+neFEu z*2Cj^ZTgG8dY#v!z%uOe@7{bbZk}$& z;fxAzvj{aK+Nf&js(yT6YUhWc?jV^Y1VVraegGypS54SkOp&>F4Ww8nwng@YkfhM- zt@4c~PcimwNf6JJF$1u`HJ~zQE}(W^uAL}clH|LCC<15_wP>K7sTDD>kR@}Znh+V) zxbPJ%CQ~p&WLvffxH@!ZAZdSZffhA6P!2DBV+cD7{(L7h$ns95UX@GU3nbF!(t%Y8 zY*|H|IZ-EjQq;ggdm3%>u#_WNOV zI=r_ynSMC@;e{t3uQn$i2c~A5uWtP4FK&JO+#esmwV=iEIv)M#j`P?5<_qH;f4_O= z(d0@E!>yW5niL!LBacxdg~i||D{NkR=G03-B<}`}ct{37rU;0F4ggUI9i|{f=P>2k zy%QmK<~W)40iY^s8YLxBm1HW0 zAUTxS7&U3`4UkX)iLKSq7(B6ej0PfL1~%Yigj(O7B~9c_$jja*E?qc>l_)v(?l0 zzxbp&=5xhj`x`TTuB|TKo>JqBIvS0pZLu)nV&w?#h0zU}A4g86LAYRVZ5VKOzhs*= zLvUz-ps9nZ4O0V{xKuz;wQNlGwAf&oCbonZ4A6kZi~v-lJakvEEhR~(C0D_YN~8uR zUl(N#?gs; zFk1TyuidRBIC=I7UAw(DhQ#H=Nj!+>V>OWY?5w2_tNH82XdKzZ!qEd1!DJrt505~xu zOWryss4ULxU2FlY357eBs6;L$vA$C8+pS)I+6O}Q7a4UZp+MD!g$^VMvXj`6ZI_NS z-9M!_&vPoE%8VjNAdw8%Ys&7yrse?)pF$--ieQ%ItHjpX8n4dhh`>dQN-v|d#Z>uo zJ>!my2+6u{mtvr!qcAMY=9iwJ6Qf8F3kS{gBJKOC9xaBzV5m5J>8)m;oR6~--y!_ zL~xWBLQR^)6?GYZr$+_OxEN^AEHKTpVL6irc8y!^ zDKN}7xt;RYrEOU)3`ib7^X_^m6p$T=+M_U$`2cZnc=330h*+;(4X5HHjZ(Y)qpw%- zzDq`H5bIt3TVI6biP>T-9EN7w_x>Ofb zmJ$L^6tK(O0ac`wi*`^WY0!$%+=E#1>GZO-1q4=f&WmygC3uYG%y~s{kj$IN;Nm=$ z1~$0A^U&EKa@q3u=(Y0|iwM7bFxlO~6C6L8izZ~kxTx=b>BqOzMrGIEFn+i- zTzN7pK$bRwW=^^q*Y(=!uv9YFZwDNS%rxyI3MX4qfg{pHKdtt@KS$AxJ( zx?I-#-4WUqlzDr0U@;ki01>0W0tv|oodpCiVeP_Xvh+!+gcT%6w-jBoCCwjNKpGa>eT%kDqsn6@r$4vd3$ z^l(?`@Y%6N&FQlp!BG49q%Wxv&@jh|_hRN=+o*^HU?yNhP9VFecP^R+IlY@3T%~rd zW=b<~4hANUrZ;!W9e?NG;O%x;H3Nc{*igNBr%oxUi@bNG1Ot^pbuv7x7DwS+Oyl9Z zzxsG6>Vb<3tNX12nnH2$yMO)b-})v=x$?~ZJHXJ~Xg>PJ<{jPo{0~1r`g>_UJo@Ir z?*@MJC!;IFZ~m3rN28m&SPbdYlkX1q%Eu>{e{yc+6A#n$_4)YRWOwWQ?|*XT07mYh zY6XcQ+VJ6~KwIF+P+g2eis(3-sZd!j+^_%*4PqREXNI20gn+x{N^qrwLBh~3p2phq z6GrRB^3Xf%cQ|4G1ob9ZX+16Cd|I}h^&V*Vnb_&q@;%twe`W$F8<=1hIFg0Qgd*9F zmYhWDRb-CM0^BYO6qP*lo3c}E97`)hyPtn95H?77on@g!c4 z4}FMudJ3-Lueu-Zd{?gRt>5_M;U6{MI={tx_V`jka&+TG-F4slv+w^OFaP(x{Mzl` z*gqn=y;{%b&9qA8#^H@SibXA){QZsjYLIwuQiyM(x2)|(03?Ji9iE63u(wsr62Q1w zT4BnNKqNZowE(8Q2Eooyi|jOm)}3HrKuJN8gAotL^9KXRo$-k+Rqs9c3rn??bzFG> z15T!r4dFnIKn(~WsWS(Pnj?r2v({1xM#qab0;+3vZ#XUd022RCiL^~-DY&f*hX*kN zfhZVIs_(D(K%@nTjb<~EB#p+wR(<~J!|mFV+KaPCSJKJwYq!^9B0yc=T;LDr*(<-j zd9Xjc6Jhmp={t0C^2vJ}_tsXXCHzyl_l3#c!SCOHa`N?WA8r3ERL`9Ltavu07ytFQ zzH`vV7e5!i@zQ|CC(jN(n7ZZ}|J~W8>0yI-e13Ojtcyq#Zf%S<(#fN?n(PkEmApFj zgdLbZ7_hNxTV~KUkz0lwASA!V458rKTMdsM`HsW{-DtQs{XXMQ=ij(YSxNw$3`!l1 zMXIW6$@Hv0>-Hb^@?glmF5QWjQb;*h7<(BH0y&pdj2OG9c(tSeW<+4xo&+_u$u^Xg zITKeQ8CAR(inKd__3-r{Cj}uyAMjeiQfS#IC)&ICC|Y7p^k6T1d_mqCt*N{BWc&8x zH66`LJV8?wLoFV?{hPo3$@=`EeR}WydDXqGlcOpGzvb-qGgso3$Jg{nm#R_o)1>d8)Z0$k1g=~m&Acz3Cp z3q|fc~fhw0|Wi(uUeQOb!*;c2IFC1TabncZOPh;D{#*I%M{n3brul(W3wVh|}{uiEk^6Kkb zU;VcpKDYT9JHO#h2G#kalX6(e&4VGw^51*-fBo|}nv2c+0^^F^uU5zH`}E9y<5HDe z+N1^>Pm{gzx_6WD>CdLZLl~}%xDOQq1@x9$7)l)@3@TWrW}+aHMK3;?({e>lw6D}G z$24_I*7>qqe*bgE7=W1-DgZnj68X}x$FoWTmIv*!bzZuGbS;BsT`_{WZss3>$PCdt zwv=EA1qU!yL;wtWtcm^p#%6JR&_*!g7F@JKNGX_^{-t#!*4i;JFJN95iLpBIqbGi{ z#yk?QT>9-5;Gtmo9_~-}XmCmvkU_yTHA90pPPb?LeDLI%%WwXbPrdZ>=dX@hH{fE!Ni09} zrNb2tXg1tOKZ>PB3@4!^3X|1PSz7m|Okx1skq>-18AGaPu^i?WKWh~o8K5^7W}r0v z(la%q5SXZm^cLlvAF+W!&fEYovkXiLkof`0XwWg9`}s!Z#Q+eR8n}Ei5)qh6ShP(D zO)x`E2BMU*vLITF3MvL7ZZxn(8%0tS%_D#mVyv*7MAFqK@7!ydt)8DbR~jNI;?`gM zZ|+ZLqqZuy^2MP) ze0cSj|G%SWW^sQq(&Ohp_tTZ@*RPhpIrwUN<%NGRzWT}U{KA1-d-3GP>JJWH{rrm` z?pJqTw(2ph{pydapW_5>03OxGmt*11mA+0@@F3-M;i!y>V|{;j3jyY=ZE1+1EfBP~ zbVN|a0v4$r7Ys^$uKf}=B2W`awqm|Xh-aRv*k3nieNq=-2?$BO_>osC$T{Tf4sQTp zWJ@>Oo}1k*V8#(s7uwxwxAtA_iP>7iW!nIznA3ulvg!yZCE9R{sMA#roC?zxVa=-p^heZx)lmy<62{ z0rl$7y}7Z#Vt(PRpASFz+Pq|?5(okPMocuUAiCiRVS94@-Qu&elLCqhuy0;0L8;|X zEW7*~p4rJd!ojEIFC*hS9J6+#BE1l@yQ1zj(bFU;pgxn|I~hQIW#P3w|cTej+~r zAVlMWE7-RUe&^+j2h!3mtw5^v==v-AozIsaOuzNyCGvN+KKd-m2!{_paW|$rmp6{& zctO+Wp7|FyCLU1{*#Mm}C}`u23ITSflTQ>UI*zA1pKOntk%KW>K~wRg5elK?_Y=GM z{V0QkjH%BEJ@dptU=#dlI<44%bXjaKD~zYga4;cKKv(i~gj_Z==7Mtq1N0!r(~EV6 z`sS++IeP{wBeO{N$-NmF5+=^ku?kF@LS!=p6G2o_QY8=(1x-2ss!J-%uMri^&F?m4T(P>GSu5A9lPOqPA$lk@pYrlS{Sdp>_FkXB4q}+PvjW7KD zo9DFr@R#cG3y}e`DU7i=1@MI4InY5lIlq8mjqUo$;e&I>LmjN|JO(wlHtxnay;qR| z2)Hq6R{bmig{SHOFtacC-PeLiS8Mml@K1eoV@T?PLd#*5uyn@cy@6-=Qy;yRb@#n2 zUA{RJvy}wAiO|vw0}KEpi8C@VO#r4qK5EyO$JPq)K$D5q15BV<0k{ONh^)2g!>hmi z<>!tUbiOXCRERpA`mHZMH@m!3x;7Pybbhh%YgfMVg=a5ab%)zOe{MFKj5+7MS;H&k zIi<%RjbAJq@OO)6uN+O=ou9sY{@wlm!PW7qT&39)d@B^5tLwXuolSqR*xb75Pi|gW z`K957z^bYUs47OKV^l86ftthFpw-C{%&CYM_I1d^7q-|h2HRfqG1V1wWC{ePY3Z-u zw@u3>Fhf_|y!ryz=Cn3=dYIq)5MsYn#wn@ta4_-Y*=XX49Xm(ZpK~1tzJrl+3k*xI z7*G`vRc%8;!rXjo=1G}KY}$}SO@%DfO>%?6SqvdbQqfc^VXPa;o00!+m%Eeg8BnWq z`sleq*t>G`{5p&Ubf9&$dj1z*e(v_I8wFOt5CIB$;i^K^W>WKUed(4P%>hw1#<%1B zwQ7HKpNePFeYMy%)2B;62ctNF&hT}DOfKZpYwuaJskLRD>(xpD-!_#mo9HP7aNyk z{a(!|l8jsvs@PoG-V1psDND88pW0xCqk)yHqXCg)1xMr^W)??Rlb^Zu^;cAH7nR{Z zGX;~Fwc!2l1Il80v7D+2Ne$6bi>hiS5>cf-Y7teiaA=Yu|J}|Y8W;^7N@5-Ew6pE4 ztLKtwD6DZV#r0SZX5W4MN*O@aN{EhXw33F6bE^-I9-liZHGyt!Jc+NpFghZ)AD&-* z@A$ppO z5g9qw@szx_V;;cau{WFO@l_8%=}xtFK1Zj4fTm=C`GV0d2@x0pvZ}NvM1!Uf#2^_= z-duat-_9Vbp0%oH>}v#oG`P5~JT3sxF(MMM#~izpofSKR3Ose%LY9(b2qPC1s*s{| z68kQ%Dd}!f$`>%kTeFdG1$^|1o~% z2M<0PzEmG>%VO_ezFWK5v(-oS`LY>;tU4cK%O#-zA|r4v#IS=t{hoUyS~Vl3vYENU z&drXu*Tvv)b^CnTT}7lRiK;;k76m}UDA4)pu|@013J@cpilnC0dn-lt@DRZz%=s5! zN*!LrkvW=E9d3^ObczKcp#g)VY#`lb17gQd!7jq2bHzmI=Ek|hOJX8`*psKTZ?JW| zKm^s4WeZkAY7?ROP&ci)0yoD7s3>*Ae`m?f=bXcsnHgC{oJqO)%BJG-4XDdxIa^%1 zP$dwg>U877`-=k&d-FFo%9H2*=J(x|rkef2_s(4&6*sqR*9JrB_a%ww>-f86-TLp%)nNL?s#=+{;3v~<^ zl}=ujtt#_>1q%)Fk@0YzYaVecvK}CedXeh-mIfPJWSrAV=)9}rVQ$+QFbHER3QT} zbz>VX5P7x=>b#21z|8=JnK7xeZnBD)8D0H6SCJqhux4R&Ze5VF;$ETv*DJdFAVgFFOTt4Jh6AOL&pODq+snyU*Gy4Qsp;I{&&V}BLmv7`X5RLiEBefK z>wJ+-B}N58R8dWag+Q7*MNF#BI{!cb%W=jjsU{HvMHNzIK+OisZ6!^js>yEl*!eLN zCZA|BpRJeUzpM{-#*W>uKiIue9>4n5etqlaN7GN88-4rcoiP3DH&YX=aua>|ndcsr zQ-2Y~apN<`3N<2;31Xst=+sYGp<0Y~!ODQ3u23NK{P^*8OlAf}-bd2cVcnHZNC69t zW?)E^q?1+?h=hc!=B$B@**ja8r`}z>J4)2`pV-5=n1D=u3IQ=SlCoSweYOPc;q-o( zxvWI`PS?(K{!igi9cMscXT$(<3_BlR0dgd@b|M{<%;bfiu;o|wJ zzI5Y|ocFVFqGJ4Im}zap)VeY_1uzv&h-l`$E?dn0B^O6Sl4cB=l!`hzDSfjL?#?O% z6*0?4UG}>)Ai@R=B}z1|oJpVLVJa!H^CZ6S{Jc}%}j>J$KSD3X( zuD8j^zf~+rQX&&lADs3&Ey`IW6 zjTaAPJe3>U#{+aJOloc=7Cf+ML(oLs>7)XNXvjYHEsl1K2Ug}m(GD~<6JqDV8L=KW z!-Pzl2u)K`OsZL{zSAgFSTF&QI}vzV(c1OqM#)<#BQK+Mc0bu zq8=bujRs)k6u`u#gac{>4NR$WniBudI!udH5!5KD@?hw(F(5~MQ*S0-O@i}%!P;+Q z1mAi@O!XjCEDnJ^kO8Meqe0yGz3=|8`0AB8mm~u~CIV0+W7d>|a4mZvgCe7v6+NrG zBkGwG7iE7RJX_e~@j}p*qpiz+nn7ul7-Nblh^m1|o88Y;HMYrAw6n5MW;7MCHUJZ` z4T+ILQ5wLvX5&D3RzK$Z1);ifeWRKbg_ndz$YnuG_l#~9jg|!pOM{wzZ=rUB<-R6` zK4q&(o4MBhs5fWNbAOej7NnW!XidfFD6F0~ACsxVM`4wsm3|GhZ`H}##Fr*No#ul0oYb1|6p6j<+Ok2{PIkjpv1gxOaWg9dAra-w8tigA zM39+?oh8ZS6jjjRKyvX+nc$5;z8uwtFbBkPF5EtTe0t$@kPwi>R8-U&@NFn{u@fV5 zLDB$d#0&rgA4P!}RMA3W2(664fB-Di`|q~AYB7-&QCa)F0c+=`Y-7M6pkl_J0gwfj zA(2lFhR@Q0&Pu{1bbq$rw098cK&Va&nX458)2aSOM))HnMLRsE$1?q{Qe!m{2uOaa055;}8tI z%Z6@dYS>~)kgy1ZIrs(;z?Z9sRVpHhQ)4HC zsTJy5uH}?v30a{b^vx7x#*A18&n!6rf8x#qFmd@46|+;T86ZkFhcs`A;ym<}4s=;9 zkrPc2jWM_WIYmPx9|;8QDKSuF$_0|d>``1CkqM|FEnpwi`@Bs2t zBGMSG>da0v!^Eu|dRJN@ZalAzi{0^b_i~>Fozn*usz%II;j&q}d?#Ifc8GyLV_C$+|ri(PfgaW-ToMG&K563SB1)VF_U1>dq~U!HwEAQ^Va7BRkdPcCMv0kg;Ccj$I%8!a4a}}dT|crshx+@8G#QCdrhk}b zUvk`ioOsRWTpF`3XUz;EDiCX+t^>gCfNZPrz{Pp(MSvYDnjt&yn~;jZz=1M(6i|l% zN<1w>rB)AS|Ev(Fx+h4J+4BW%H!0+Rd8G>6oUT}4XQoj2aLfofxc=$mE>Ery`cS*4uPp2Dtfw|k z$BD|yaWtf!jCdvn>UyZo6e3fsyW7HbdCr|Nu^JkoQ6b5mroSm>o~fBh#@zaImxu`w zz*j0R>zS8f51I1DBmsQN=0S>xngnp$mxZI%#IbHQ>)_ct)K)bRCN<{>88TD?G#t+o z!D1ke49a4=eKOf_#-y9=L!;IL2}?<#@`B@q>K&YUuOPk>;O z%oh1(*ZJrfKY#teN%vR-89FmDLS*!XnSqM*1${r8gnsdGiN-GZ(#zGdJ{q}?#|7$e z4)?WNzO0R^X=Ov8MO#vB9gaP=O3Qc4S(vR!_sZJpYl`gT40il zzAt?=dRr*!fE>>GT0m;Zph5urcaR7mn^v1nIih&Dy529j7g$&^!S`- zBZlqWfv@5=U{Q;6`s9^^t>I7zk{XB>RRXpqqJ|dhW-yt|5G<+bynvQUXcD$qGN6@6 zrK5p@N)lD*q8cL-oqSwd_B%5mD66V8Aq5jhF*;Ba<+H@S<-q?b!iIqns3PQx`>6vF zyS_=02+<7G5G|)V=90q@%+Sbrk0NEBZbQmnp9I-u8WS!BC2D910Fa)(AdwR0T?oWv zVn!XZk}!+@7hp(e+Ch^!46K_$Fnh|h5D_B;1;j{sVG}tT>?o#zAYh7x8mOGb&}JbT zzzR2ndc?+RhJIKTcQ2JuN>PQXWJQT737~4iaO{e;lST?iH2`92y_aYqcpoa@L>_XI zh#43W^z*)XP1F}=q%#Yv<8lt{yqNb4e?n*40~k+TT~F_)?xCI?gZ;vFBqZY;Gu@FA z`=gH#$&n%ZryDO-gDkFnYG|E{#teE8;nGn69SQ&%B(R!h-NUZ!oa9rS3eX3 zD*xbmGq2f4KqQBzC^2=D5w*y);R17|5kjOv1>T@i^Xv{ckUr~)i#V3}Q~2nNO4W9LlDzy*cqcrdAvoqrt}8@3XHdj^`y+Mjkr`zOQHjVc zFTiei2@1$0qNZJ9ZAPOqJ4nIINDYyRx?C``oc>^ngrq$q82}kX6RH3}>P|BzE0w$n z3jm-d5F=tTCL)?gi0DWJy&?k>_1>i5H}57(sWoL6fd=;Y;YU~2jZ^L%Qk&!KiJY}< zFlspMZsxCMAo<+`QWJ0ll(Tw}!H5iOK1ppDdNv|M)t>YM zWZLhSt|NPDiGggNRM*c8A_CHv~-TL#S}e>0lIVgN^mpi!tQ9l9h4jGZ61k_lJZ zatlaAuTs*h|IY5=LF(=y^S~ z+a-WXUDc2klVH7|m5>NS|7E(3k|lmwXo9BT)BseZk95duh1p=LV`6tFC^}n;(6hA` z<{=oGB6N{6#7hD;Le5mqKyJtX5|d<_A{c`(k#ae-n3AGm&dSYP`_w8!tK#H09b|!E=%;hSY60_ZYeXRh-m4j1--JB02L`G z1R{~>RVLTRsrXl#{&uL;w`f5h~?5 zjLY$aMt}e&Nj#gHny3+&U@FH>pe#cjk}JTHvFF;#x~%3*|LwSFOA0yyQGUZcf@nX2 z$pbqAB1@PXtE7quh>0Thl9(i10b>lDiK_^NCZ`iAg!{_5n4eDr$C^_nSsJg>Lg_+7~y&0 zM>H|S5eJVxQKS`33XUX1mKX&zp`$Ph){tXOb;EPj7$vo$vjUZer2#WyVIim?s+AJa zaSA?EM|VJ}mvTcE%ZVSL7A1IfWi&AoE&PQH9h`<^*#d63!p!Vw}P z5rU>3A5s8h?q--v{*x#gC^*OHKwS?;<>!vnap+C0n|)?~U2W^nTn7BMFbE4X)uvAG9MU_^O)<){IV#hp_Qxa zp^dZxBpOq2XjUvFX9Op;1eyc@IAStQ5|AKilGxqG1X^&3MZXP*FhxKWB60+#I>g1I zKz*v912yBSn8c=XHINc9QiheVSCl+Gxat)<-3jA)UMYscc%{acX@v%i>U$uCPXCBq+q}RTIBH+Dj=eYxeQa0v4#jt zojS>wxa0cd7F$w|+yD?!0E-zY(4;N-!uMI2Mmbarq~w@W#{F6fZ8T;;Zvbdo9ycgv z7DG}Y&K%aa-P2++l)%fWYj4Q!BYLp(PQ(dOnMG@ONf@c4pbVD33Bsg*gyV zJBEsh38uU=74s_>kr7O>%!3KeLnzB8zyux2iCq?QKPy2{1qO@4C`xK%Vq&1GDM?t= z>WCbHYV@VE1WXzmLoh_|Yw%?>F(7D$iXi22!H{3aU})Cg5MrW?hHSwUEi{SMbMnHy zh{+fYTv76h3mJkDbkMW|eY(K~gr;%meQittnmX~PEhkj@sOoJ9^U2pe%S`!m07f7N zM9j{ST1O>BAjsrMgY2F4G-3q^npH|?Bh9Du!u>0eQ4r0;#QcmwZArQ|0Y^kl;j9+& zA_ZxSR2q;2Q%uQFH7y02XhaIsMfaj4G|CQaS$1S%U?NEZ0)dPG&4Gybg$Glzwr+_5 zNx>n25Gt6F7$T%<-V~IX;KB%w$RMZc387loYo`=N1w?=xci0LNsJUwBof8E$C>8@g zvT48`bM5}-LpNHfq)5d7%VQb34p@@9aMn2{m^uz>Ti zR1=2{GN`FUP(mh)h$%$_pct7{6tYXOsHhMY`G*3cq{he=Fjv1QW4FJ!+5Ci;W)%6u zwU5er96S*-Q|EzjHu8OndC@`OJ%pM^OdYy`Y{Ud+Mmcgk8-w`-`CM;osBxptg04hlY%~V1fSZG3;4#d#=!i*8wk%EA;)|@3INaQ#U zOhJ7ZBC@)K2}w~?%fK-bnFT4F7@7(*f{>{qQTKR})R+;Qu;#!_hym)f>W|kC!Fo0= z02st!svkLWtK^GP$uMt8=xtWvY>UC!VlDq4$vMab$;=eY_2C5s1|}&ja>>ohwNe#T zQOd6JONz4s@PE)rS%|sYULx5SKFgOa8Y*%kH9(VOt)du2iAsb3go~r%%66gSvFEbl zlsgOQ+k|95VCWdcAPFWUL?UaVs1cemih{HXgF=(}C&^MGY&k1RiI~6`6QUt&F(}Hy z72bR(5CyFgAV(Ghb>lUKwkakQ1G05l^(uxW&aD(h2?PM_m_mSoZx;%_)R^;#+fgw*3m}+P%#tw8ehdlBNTmT2adAuB(}*faC^-PL~8}8 z$Cuf(BY*;em{Lx~MKJ23+z?bj4M0TH43nuaOFKDD4ifuVK+3zja0!Twfhs>^216vo zh-7M1R)9!^?ld7|PT@gqW9kY28NUX#X6hUyhuu>~j%{OwAGmBQheDp9oy{BbPV>0{ z2*CN13r|L10!}5JO?(mX>=3l<-l_n=rl?tHrCB2941gJpGDL-mKt&7{h;n7$4S)?1 zNVB|^l4d;_Q1ag8+K({DcdIV7CTFBXVlr_O1q-psxIH{1Vl_qQ7(}h&wiPr{P|Wjb zIOB15(0gJ;Qx#*)X?x4S6_JjDtqGh{5kVx?#0thL(G{cuEEJP-ZYThVM8ski(s1Zh z1(6&mD2fOvSkkoQItyZ!6e|(2^>I~z1W=V8N)iz!BtXU(*4CdteytuR=vtS~6O|mB zF@ol~aaWpSZ&Z<+X9U7-4h4`KG40!DEyT!q>RQs`Z?uxsoi9{?()Il@%TbuK01be|D=L(gyltF@ME)Q~yvBR%>4;C;gB!+@A z^3WMy>kq$nV9&u}tjGh_#<(6eECg?{F4dr%P|LpDT2Ms==RUX^?V z<|?Mw<5?#)R~$XJm?b}`m`-i#ju$nN_aH*#nW8f?$?W;gr^N`Dp?63S6Z-6+BRz=J z=_mv`j0qVffheOIfkv|u5fvDKASo!Sk%fGsAbKrVs=AGW1_}!7l*r}ytG@k!ejJC0 zeo&4`k-JyQ47^!H#yr2#NCp_`^59YRa(aBionKiV;i?EBLKfW{Aa#aZOZ&}!T5@La z(;=$)paLXhbleLzO)Zn6EahnyLf@kUATlCymTrNu0x*+2ciDA#V@6%OTD224m@0rV zq3CF5Wwd6fF>c7B8lrQtPUI0t)d%McVwYXqg`M}Kj82%2B%ngr6~tZpke@n;0w`EU zrpN>oy@y0DsTNR>B7(4Ba-4f$Kt`y_d2J3F=Lw9#fnp|SaTg7dW${@g2u8Cf#h@w5 z&=<#&B!yU#Br#2phy}oGzns(Oj(;ch6&B@{$(Lmd#aXHdB1SGi63&9~&PZ{o1QbCqh{*^6 zRe>Qz@@l4v&NL=hIBbv=9A)M%IaiE|2PcE`qmR)ef{~D#l7cfv6Oin3lxr#k&{}H0 zgd4v|Dq9u(~s*W_!Jh-@i{x0K-0K zt4r51aY%@Sz0ADZdP@1WQ-=hm-DA{Ts5le@0s&V02*Sxah;be(-AZz6EO>k zof;~knTaSeBAEJM1wWkH_wdrVDKdetquIe z1$2%yMiI3z7{`2Gn};MS37vxkto6KJERriR7|!cRO+0Q`W40VY@|WqXA(AHPS={BuxoS$aJzkEr3%5G%h`?4ri`R&W5Amhf)G*h=kG|YPkhQ z1I`{v!nscAkxfj=Oc49nP(m^jv8B~7W=*{%l$m$d6ljJ5ykIN+!p+Yic><>9=#*g* z;c~^H_Jvo-GWuv-IcenV49m1`AU!!;;~613k`z_JX{-~51ZmM``m<{yP!VUNRSyaz znz`Xwb%qf}MI3t~2R&fF{8&!%yUWrLjR8!F$PAF!I|4(deDIUF!lmRY20}?dzzm=) zp<`52sfh}X)DBx2^D9BbWh6-~f+cYbrU67uJeV4Rurz~dK|-Pq+QbX*Kw=a8*lges zjUd!Qnmx$@G>4w$U#cma7-CR&Ki}k<&gfo0E)_0O7hM+hrORYV7A|y*x1}cd- zhN=loa3$gCaT%?Byjb6<1VcguO-VpSK@GVx?dfnEfZ$y`sfUrkDZx^i3Mq<)I+z+z z;hhCgYbnhkA_7s$&MRQv5^{iH!A#f-+IrBoQ_kgzv*RV@0!k6V1c4nhm@-4p^i*&q zum^UOND#9_d8Q1hp%h+#8Pvz|sBM4x2{qT3m6ptqoGTR4Xy}?fQG~FE!PwjK$2)Mm-=D? z?3)=>MSI#%%Z{BQC1$3fwAs_c4=K!=(ZzY|5~D;0?ywaUC6}2vrnx%+HFSC2nyEDe zk`ki}D;pYJJ*T5=65U+BS*hKv7@%`UD#r&gVO` zBVV{=2}#A!AnjZr80x4o4cZ@kW@k(G6zL)#G1v**MMMCah}}F0lPj!TxUoE{ zhXyE%y6_$6N{MpjQ+~TL6eGk`<_VDu!)%?M8ekIe z9_GBt0UvmRW=~Am$Hjc;4SQBWp6_Uf(Su4-Q0ab|#L$$z+lf(AGA_cRF}CC~mrkh* ze20Jqwu{0HL=&0EY7T?bqp4`H#e=p2*DkypMwJ1z(E|W7iv~16R_Q0Sot_0*6!m0A z6KoSnE0~N@5C+NO8j@rX7XCm$XTzOwas9#{KX5KL9w4&%{8}8rLdghNfwNMwpWz$y z*?r3^viqA5**OHwzWSJBWQfK}1c;=>Ku8fVqC?)fak=%Uo(0T+|L3CU z903hj%t0!WsK(hG2t`G~NmXqQJusSrD|k?p6h&ibYD+1_Dgq^*c*%H#bgx?0=y%g2 zOQE$8)ijwIn(}buB!+^@5IAWfT0w2uCGe0D889avNnBNPkcHQ}8i^EsG^WMz!Td;8 z=X;?UvW*6th>DWbXtGveyRsMY=aJb1BVoMp+-j*&PrWV=zPSiKrmx+#NIEK?LLw zh>4hy7^5ffKm~xc&%8L14SYO+oiFh)c>+QpcABdWKqf^q3`W*8bwXu}q85kSen=5L zA{m0sHECuA1oi9wM2W-=ydaYT(XyJ&@$g**Zoq1qaZNJy(<-tnvqYr9l5J%z)w@kZ zYY~}1kSk~xML{t+YpJvahu{f;7lY}2KbyoS>`zY@7j`rrnvad5`XSBdanJ^iJX}yp zj=)GVMFl7uI>)_1X#-P zYN`b2pi{~bBuN6v#F7G_MGWlA$hjNWB%o2PRaJvx9MQnBn#+}WjKoaP(Kbvhp=lB$ zg`6@dEKp8xRyn3*3Vz{}XjOPxNE;pJ_QIDVf<$%Ou3}K21llx(94oU+<5|+<%4>s> z;*Rb6Z8%ND5Fw>-j7C()n!r)$U2FjWogt#eIY^YsGb36IQ8WpmWZcC>Lm4AFY^@!- zj|l)Qp(U`XXJ^tIOW4_RJbmWD)6ih=Rsuvw@JhSs`1Wh|AWoRh?m5`)I+nEdrHH8%7aFx+g z2vI@ugsoE|Ae5ORVwK&y?UdCJ2*O}h*f>jh6f)rgdj00pM5eP+-a6Ez*T>CNeW)1%_0tRs^aNf*Fi?tpX6|DsMbm zER=}JT0rB;h2xo`I|h!$02Ls?%Ecebb6c}LntbBJqXo2MD(eu-)IepkqcBe{?gEzH z5V1+%p~n#zkpQFt330?pViE&TMiH}Qngoe)=7*MqeD(Gl4QfVQT)VOs$ssY2MFGl2 zMxFS>uxIft+pT_(NJwM|#Avh>0s-{y?45geCTuwK5C)SAC^U*?O)j=JgwrAirZONp z0|a7bH8o~bQIilgPqb7F$gBY>EE{E66M~o-M3sOZ+L~Mdh(;ATNl-dKGj^^uO`6n9 zMFCZS3$mz&<_ObpFem4y!-0}8H6fT)k|H@05gDNORdlQ4l}85$gQ~!!8pD%z zb$}nY^Jaa^Y0D#MH5o>;_K<7^%jOc_2Nw=v@*cT}-Nm7b1eRxh2F8u%_pvJux*Q#( z$g03ZwV)LNV@8ICoC*ZYSxQL#d=G7@YsNplxpEBwCdgSQ-<^n0K~n}uh8>*-9fk7J z1x(ZeCl>>ZO16qKT&fPqp@Q=cRl7{^m_0f$>Jl+y7D)_%h#FJYn@T`J6=Wqu(4Z%+ zL=x1P`G}e3CYIxM(ftvTm#W9<{Ei-;QmM8v%(&i?jVvl!zy((0@UlBH<6Pir-syD)E$)84d*Sgwnz z%R~qxGSJs{IDUM8^&froGvP_Ei}?P;m;bPb^3s>9D34JN-rQBa-F|v+`eB}bSaE9a zfBtROyxMo7Nf`%9vM|%fyqZJ|X-g(V8Bw`rRp}pI|KUICiY#H$%lWI%69P*>l<_=7 z;$Plby6y7w`BK^n^cbtdu}yn^ro=NqdV2}C!S&tuUR|(D_O;j{*Es64RR|Q?zY8f- zVHh$-g8X2S#`YRlZ?}^$1(B-0u9-okn>&z!J)N?8d0du8Wmhrbh=b3}Oxw6JIaNaj zqwH!@;;F})YqnP(zsO2SyFT<>olbg6QL@LwW7~g5_Y3x~9@?q5MJtxWr|p~d^5p!` z^rMgR@cNVGktLn`yB$3Cr^oBpAN=l<l3gtf~l-$lzEkfJ47D-k0yz_RkMcRh3YCPR@M(0sJM6j2kIL zX@-{{uNCW+Uf>DX&Za@KwPI>TMGO(qk>OrNRfPelMe7u}{OE^QPwb*MjYOo2v?iBC zhD{>Hx*v+V#$&(VYnx1%s%aUX!J->`QQ2iTwI`t++OobjotEip7GBz}TP%C!>+@qN zk-2(}dAfF))^pf#7wi1;kL`f_m9z7k&zFB32(7zUhyCtl$u5Vtb=`mR;%NtK`F#?KEsvx99vViJAmEy|WFLGS?Sziw&L`r5* zIP^U7onU&q{hxcvVPX^&22?X3)W3X&Nr;9qba;&tDp>;d`m-IWsGVVIs6tRBl2!%u z^32_A6||eumP{0gM-SAX`1ik`lb+*Iyi?Pu3~JfXk|xN6fSBRFUbPmasxc8EHNgTA zY9=HiP!9J=VT=rcIaB(b^x|z&)p976CmEsTjMY;OZ*^}`WZwPo&-TD=m(L%shi|FO zzPr6s?4#VgEbGmm{@{}m2b|8b*I#@5C%F6M{Q1MBto1u3zbHUtoy;$E0Zathzw>yA zLZF#xs#0;BoeI$fyp(rU2dYNA-S4!T1#T=$C#Jq($iu~tt^>&n3b z7R`WUibiHXW4;AbgR@*Eg!;AZ@ z{U1Du=OWLT&q_4`RJJqtyJs~l0#b*!yGTlgz1)}gWwzZc&o#*q11KR$Wrhl2|1%Xq zCUd@-E)TdhN}Vke5u%wYa$kC8Pi)d0tYz9qY9yd47CFogKC*IVvy6;z4=JKk-Nq_G z(QOfw6l585f6Ci^=_t7?g|R-J3et8}K&6<7>}%CXEq&j9KJi)}Z|j$DG}Dz=@3Cv4 zesR?MV8PRuyH9?0yMA%HU*De4K3m@m9U_w^Dom(?5#(^tikJ+d)rRgFdKAou`o}-} z`Ki{N);VYm<+tu`CQ*8N1QXIRmiM-m-p^N&ZZI-#8rkO^|92bzpeIoyh-9O-=y)geuv!@C?nFfq^k&~{bw&_ePwZTBd`=PC8_B>Y@SmkiEI;4`22o8 zo%OhvPKuh$MGsFO{7~7yKQAx{J()pWd)eJ;sy&m=AWTM@lS=qwM4_tLG!>Dw+_d(> z0D){mm!d(D^wxtc$52V1v#;02R+X#k95B;xw%$vrMR&W;{`P;g?myhVdG+fb-WBdI za(uOa`mpmRt;=G^_U&W0xABAZ+t=9t0wvbRr`!ZaLr52tjJaf&QUYW^;IP7sj7W$u zLN$9lJ?=79xzkv5cRBp}=j#~LZrLQHW~RuqOm=H<$Kd%{#y5Z;-)U#YpfnBLZC7}j zU*u8`zx3lLuW+T^u#QJB#_ZwrwPBuWRYJfOQU)bb;L}`k(S3smQK}X_>nnZSm*uL9 z*-(W;&h^Dyuj^@TE<|v1`$Q5E0$nqXvnlpfTp;M%JrQIiRJx^?S~5f!o_PKC!)w1P zsL*0&ASQYC&AhY>`(CCavwrV~-#&ePGI2#>DgDp&D20)^ z>e$EeS$FyD$$p;5j^-LvZ1>bwr9bz|6kxi0v8$)uO~2Um?qa*za<}e;1Y_%#voTtu zulrUF*_{ykcc5TqMzKi4IuV*Zke>64qwrR)ip#M(RGDIW_gczjIjw7eWI!bo>DX>j zIOqK&P-Oz%eET2kG8`G5(NQD;p*u0*4K*WrSv%Z)lB5Yap}ShTsVUUT&M&^l*<6ha z_CI)_Esu*FZpd#=X0JY4fBwbkvWvIxzgqK6#+SS6RR5rSfuyRaMy9GkrXl*`Zhx2* z(Pu*vYLZT2rgX@9x~}Js^noR@yR-Andj9;V5-q^=@Xdn;9g#234+Z1nJOqiRYF0!{ zpHc45A}_C>{P1;saLr54D>&WF?t-FPBmxjsMzoBG1mP(hvm*Ocd$?+I6=)ytL)x z{O4bMxHQdtNOYI}q;qYD?;U6?+=X1ILc-Jv)x>AxkeqlrqU@2NN*Y!qXZPBA&`_%J= zYWCdi;GIk-Z$qn ztV*=jeC^2QYN}&yV3R2XmMPWPfq<#0n_-LDAVkEni>gI@^8FiR#%Z2Qf4cZ%PIIyF z!JDZA2a>%rdtcYysRs9iMcBV18;$|#-j<%sb#c)(kiCVUgV1zlz!XwmV6FS>V;{2? zlqv#uqDO`*B|rUiPpMm|E(t~gjEIr{GixHGH^#akJr{;H`*>PY>Wp>iiija1W6}z> zh-Byu+BYuOFRQ-(Y}ehUn{xB??YU^@`ByLg%KXK;_VC_32^EFzil*$%M3w9|tqNxbmBv&V}@0-%^Uv%?)6UAQe{0|d|1CDl_a4GD_OfETJzkm3}YSS{uW`fv>%!gg68A(K2 zkN=1N@F#!#f1cm{pMLp!q2kX>EkQA+7MoHQfBCC?yq+H)PM6DJk5fPFlBPB=SSf0% zMKZ|fm2iRIeE!J`g=r`N6|ye3KRG<|{>3iVR6y(bp~tdX(0we3p;ZR!gPGpe$V_&D zhnQGo$rOxINkHV7N%jtyge#gxph&Ql-oqH@V&PeGs6B)XHSwhxy1e)apKis?V(>Ra zh@p`K8J-G}ro*07B+{E#U6|9M+dh3cUtPpg%HaA5&^bqkR^j9R2RHja{d!(^rA6`c z9W_4vxcz)hP=EQ>O-F#aF_~HFefqoWZ{DB(^wsbEWB6``ZFVn;RQ6S&jJCF$AO5_4 zyZEIRv>?j5)pC^~bt)2=`ugJa7jvE{Ce+*YL%G*wB;N&0v><5fyi3Qvv`e-7`RV1W z8up4$Z-pB}t5joj%x4%Dn>=H^7({(dOr}62inx#&bRV1rNh+lhs#PNr zNLgS4(NcOu)9AhTwcZs!&9!=n4KF1_DB4$hY1(^L**!x~z%gCZ5m`Cx;!G4Iqt5k+ z=$#yzgFYWTC>@Ba8vm5x0D*X`M6Yiq85GqB2IaC_lvI6{mt%n%AP7^Zrj>w{n2N3-XEX7 z@p4l-+d}3#DI78e^1k*GB@0B9mYpK1N(0qQ&`pO$FA*SGM{rs*v!`&CNn(F{>0s0J zKy$^se*i0`u;y^zYDn=(zG_kaHJ zCqIAFtn?{`sl}jG#FI*4IIaBhD?h*bqks5fU%nyM;c!ubM41YxREvB3@SwU(6V4JE2q#Kq_Z0MH zk)_I9jRd5%77SuEyUFu3mCe>j$-Z_p`?6DG4LvT6NxOe?vMV}8HI{jwCRglinKra8 zWbHRE4omfEr_HCDEa)tK5L~(@RT({kA{nvdDoHI57Ay9CUhWoo_3-88$3{y z*$3oG*|nf~ovtrGsDJ+XA0PTvCE9*b&zo|ajM_^%6_U2AYnLQ5(f03-@AGfIcvn7| z|NfMk212aLzNCv$m9e%Em4C>;{qeQD{o>_)f1%etZLoyP}rkr_5Lq@1f$_%Hj z8L34%&Bg9N-8tApj6QvIcu}r%N~Bstd6rDicJ0RXf}Ov^%Uz?o2}-%f3-w&JWnjDTom- zj#L{&6+0IduKK3c-P>POwx?FjoOYf2*HWfyvsI=JE!vpLluT9c$5z&Vba#8|k2_Zl ziWx%6S?g2;yaaQ~6}P)Taee#ptH-@g6iruCvJ``=A`?c{+JzZYK!t1F`7f6#Jf>Zd z^10RhOxb*HWl_r|2SZZ{Ua-6NlB@M@%yU*pMj2H;LNwLT7;g=sC)BWx=-@doqX(R8F7$7tq zEG}K%#!Wnaote=>Ap561A9we&kA;wi2z!b+RS=PgEbH6LMX!&UA+|A~vx(Ir5cUX_ zu+RSB&OZ5-FQsTv=ma$Z%D_g8D1@uc?IeP3J!B88arZ{+G#5>^sqSXFl=OtzGpreK zK+9~jipKrjF1iPrbN}_Zo;ghl>8^@Fv4*Ww&{wV(5_!!`?Q5|N9n~A!Q>kD^>!B2Q zWU`oKYtdcJ3C1j~Lz9|J(_eiy$6@L(?ax1mL>C&Qyb$x}DY9;K%JGNyqSeYf7yam4gTo0i zW0OM3phI%CVv_BA|M(;ouCg`5E3~PIh^Lz*{(pDh|Mcbi%g58qrWBZ{0#y>*+^d=GrBSkZF6BVjYEngbm$jV9Sh917w8DSHo;N7XND=)%TcbblSY4h_yjsL z?JYAiBW*k@xEM7vjQ+l4Hz7ldnN@h3O@HSf?SAXOxuRf z@W@E&?aee@d5U&joS7QkYCdSagx%~i9&g{;ZFPJ%zYgJG5Dsr0F1nn)rZdb`DN9GITBM$pS|mn@HOr~{s;DA!ax2eFXvAbR{U9Q zKGu#jE!N(Dbzbime6)L*|GLzl{0u26>+#iHohhnHrpb30wA>DHMh}uPNzdg_ZSgua zdD2fGKg8LrSDU>_DQc7{)I=?!Ou5>mv6yzR6>iZFN6k}OX#?eBGB{_$h?D@-Q_u}P zgh>fHAj0>q$##zWsgi-hlsT#w5L-TAj>UcsPDVtMPJKD|yvEbY(<+FhteUmIyL<6r zZC&RZ#M|TNa=FOH=k;yzuEQS9^rpc*U4e9De?gnH{`!Nmx1L=y25EC_jzqnwOmS^uAYm?MQ&deA4brOr0%C|dYFVa zhh^Evm9Ia?H|s4ghu3;pu!*!mhQ~98ADI-jq(m!81qHHFOk7mn9c#GD!>$*BA>ug0 z{24POB!|!^6O88JM1b|Q03rJ^E<-@ga~w# zC>s8(XXx2>$0z*te{c;_Rb7%$NQsT1r={dI^u~o2Xb>Gjbdl$5SI;Dt<$J&Jm9)bT zjIGq-gO(^dU@c_g{rT~2PS?X{Kepb2HrsE083IOQdH`1!iEA3(tz zko4@?rZQI636wb*-6!7>}Uh~QEPQpsjGeSF^* zzck@`xQVjI+qNs$R6!1rVh#)=kcjC{zUie^*8pmf^0fB;xU=cJN_QRH3{ioqrZCH1 z696xcDpO3{y47X&44T{`|D4|$Lk&IxQjLtHj?(*>{C)KIoX}#N}*IoCv z>uri_+XGq*C?D`*wx{}fZOiKGywl5CqUYGKu(b$6YS--ArpfzEJAizv^?J& z(0d3n7**?WR@bPx0VaDuBH`$=N>28a>IJ)--pcARUm6u2(u>w_^mu*KLd5zJBD=~p zJXM&Ht0t6bn`}tpG9RCOYIV5r+CI+7Njx7*xyrO2O@ryQxw@y?jn zi^`aGai~ld?(V<67kpn<+t?@2=q%1%n0Ujt4f1;#{}R!v3*~b2<^M-|2|0gqG))Vs zMF|j9BQYrxV=a@UnW~K~Mk}S)v*Rvzd6{m`;$llLx38|g=Cr!ZyvmOIC1&%$z8p;I zeXQ-`s@HQx6pESB6HsFYBdmq=s3%BoiI??qE*27-_$`1;RrBwXh`JX5!K7sOq)mf| zm7#)bH7%)kck6-Ysa{VN9u8ws-RaXcmKf-i9g0NKoBLQ{=g8F@L@-02CtcU=wQvAw zL@_)x)me%NhhEqA>by%K#}6By4_|pXk+2p*v{6l96h;ng()K z``){^4}bW>$sIEIrV}52@zL#Cmw)*ux2qUOz(tM>;D}JfGd7lkje@j-M_+AcY)I4@ zW@PU@`IKT}$oOEvT2`g(ce;k$2z)ZZdRnL3`t)$uuO&rI+1l)8tn2bo7OF))Jxoy>mep~mjQL@F67q(CE_2wGB^^mOd{wd?6BR`!{egVh>n3aZFT9AbGfe~9q8 zkh?B!6p10==8nlkou|337j=g+g{}dHO18?ZeVtxBzB$KlJf3zr0B%uQzWDWTUE1!a z_t$ERLR1AhY9S^E6_FbgJO*dU%CTQ%zsGgWu_Bfx>}g%JWTePq3y#4<>GSUDb<%9C zl@W*Ayq>Q4c=K_oYLcNk5{3ZylGz&{syfWdLoCb?3qW*`2OS}?JuB9XFcJ~1i6JEY z8UaC0^DJ>Jr?uWp6>~OP14FY*Mh;*P zEjK&429PD7j>G**)6Tk;sy+KC+OMnL?(jmg~pA;g1ZUhsW?gMHh_3 zmDJ3r@agP+^myNI_8IMpDHTjw7fDW+_JjY%pa1-uk4slZKt+}vmKQ&Lc=L523Oob8 zW)i#6Lo=T}Ae+T42Oce4N3v9b0;17sM=4Y3SM7kibV0klHM!FNp=A-HwOp z$g(P;_e>e!Sw_id?e1ntwsbH1wORJ`B0>?SB!IBtyf7eQJ(Xz$=|v2Hi%KD2Rh3h@ z-|u(4f}Pn@JS3t-E7nuFcJpwmrKpP{G}7IQWW$DT8+k#3sH3~iXF4|8>5@f+(Ku*s z8@)aeA~IFT$A87oKYuU6zU7O9Ifht}R7Ns;)AyJ1g@3qmUj-7Ulv+(y7eTx1KfC#@ zfAtsV=sPBpsiG0`_Iuy|=F;w@n_WR^9+bldq#wQeU$PXEpeSTDMYvfD?@c7;o4BAB zN|RNkC0%GQ=gT^*djZ|=jbc&q@|ed%LlgtV&<8LxgNR%j-(C?RFgb@eC^`8vZp~Fl zv+0DDt`C>1{94loc>W9TZS?DiM#KW@O18fFTrRR>4%R-_6#cw7!r zUfjGq9&78%f@&cGOlL;V&wu*yaysm;4`rY;(#i$L>s&tn{r}>t-^~TZ!DPTT)+3+i zHyinJ^b$}|vXlEE^o&?bF-Ufu(icS+MW!q#Uoa~(q}D?XE&YzyzO#ZWSrjzY+>oQ0 zyDaP7O|tyzNu$2JypD@t3(EmS?7zy8+0Y5tGK?a;C!vPk*t=Ml!=l~_ zdR;{ezJ1uAF&|7=blO={2@8*D0?Tr?e@k!`(e4>tI5GAwHFxj2gm;2|&3auqw@M1U zHA@U^oWfHSW|=~dg={U;#oCp{Oo)(Q3mIh--BltgTH^g+S&RMldW6thC4Fq7^P zi0fLyb_LFp3xWYjj(s>nlA-}&3^g}n0MbMh8w(ZijVS_Rm=HEkW*Wd_t@*Qi!7_51F_<#QXAN~MVGc%}RBPv8UX8SXkctpWb z3n)lhaTSq_(y7HG4MTY8+ya$qXUy}Q29VL$GFvpazV@;rWueRg@w8bmmAm-^&TuHa zcXUfIw=7649R@JcQb9KjM1%??BtitCySYpT-wuYzlsN+08(pUIz$ral7-SpMKLvoU zhJ6M6Rw@z!%=dyC|f=$$oZ$!?|pP=Ebaj_ z08~J$zgHXC6W_6;`CVp#P%EiZ(K@ADB%>jQ%O;pZRgn-?rRr6AQ;U-XBcPM?r6GDR znLa^eUff+o)#g|JF7)W090{HwU}^`TTC9%^%Ah-rh%yNksH++{wzyW3Nva66y4Jpw z=rp+K1<_ju8Kj$%jv_(JVf#TMB1fL&b5#W(p_wKiatpx84Mg2^NU1l{5uvpzG7&6V zb~g_nl@D$<6SkwLiI|u`>SW#x{BQlQ=DW4tfB4(~*Z<(Z)1Nxi4*P<`i$B0=DbiCR zlk_I;BcigaxENb)AHV*=^e}aZRI&05ZGB#pf*2f(3@AvmX*MXd)ZwHiL??;lW}+8J znn;zaPP4kIf=(PjAXo1z%G9J9dxm!yJaE}v9y)3;laz5VF~peI&ZOL;f3~EFvFR=y zbyrnN6dm;V6sSrOfjD&4`y-wjrS>^%{`vb)0{`?E-}~y1*Q|1h z0-dTIUN_MrI;*7HwH@ksoFhw-uekccAYGxPF zC>(s!%nXjtJ75S42m)120%Gbe0s3;u5~Zp)4|iCJbu7F9tu404c&XF#>mFzv{OOJCTv0c5IVs##G@#D@2<5#1@NgTnmm zYu|#?o~er2wo!-D~V`OUpDPG(iVddrJ?Q|M73>dG{Aj zKa&6U7o}We-k)0B><$O>*6U14G}XHxhpNHH5Jqy%ZKiTYU{d6ItBFX>+9p;t-<3v z`u6}=`^djSMn*wK8KToP3Mi08N`aS2Dv1ai4u(W#NG-e2kVc*CcDXXxMG&D9bfZg- zd_$1zLC~cRVEjglAjF@kr4$G@@;?Oj-H|hr#s~Z@ToTIG9n)l8WZ56DC2G$_pd#}Q z*Q+P3y!q;ff1N-2wSS?E{bkuLzw;(ue6-X3Xp|zZ)LJ9^*cCZyY7o1-zOF}mj7ih0 zS#GIDqT^3Ia|@}5N}E6gBPkLXGiKq~9)VHGj zp1Z@%^|Q4WZK2UHk`Xc3cX+nWjdP&x=axA*k8G)l=#lB^Av(ru9MJ*^6-G&jQ77pc zl9G&NiFkTIMwyt0NEe8mS?8{^t$eIyd^^dcuyCZoWvT>3l91pq&vY4XW@WZgO?%bA zF0WOKq(g%;PtnXYu=23Z$M<22x%M2iNp_LUvZhQk)&)#WlXPqV8bIg?V+fveTr>hh zDPRFm%0}Q!4+ymuM$GwWWoDk=wbw6EPh`&4S5uTjaX($V(j<(P{a9AbQ=ir!+&)gr z(ltuU@U@LfXPTB)#naL?DKXv3hZnC?u{`w|RhLPfRFO;wVYzYBBI`8g5~}QjNfNFi zv95aiZgmA5Eh(DrmZ;TZUTv+a9fpzWty1{#*tw_`hzPw0JTaAQHxq;C>vOrheLiH{ zx@JJfNX;ZO6dMwN<-FE;XHPE$y%U~A>1)cYTG(XI&}>oXhn#vaBV)|<8B__9qVPd( z501|VWD_Nkl)+|HK?+6Y>69TOV{ce>M~+EKR~b3d%Xyt|X0s~mL+on}AH#vlZkN@j$x9QQXQeFRguL{D@nlxE}!0$aneyP~)?(p8Z_ zia15ANTw9a?ofFKC=SoUmLR(^bR@FYGJKV>FcXopjottUcVkn03EC zQDrw9o3A6$lRzl7YB~Lj7l&!Rd0{x5F~zCAkc{+odCKFA>qX1}?>)<^uV2oGgY; zuJk5{qOZE`UbK^Hi55(c7+3*J)EPGG((Amwx;^FOPDu5lL~+&n$z42VpWuk=S|!V? z*K6uZAf@V~O%+|O*f5V`e3T;;u&U$j9iO8VHFZiRCQJ6nOy}mORx(_tsZ`{2s3UDE zl9s+M;f<6cWp>YCXyIkK#QXa>VmvDl<2M&1lnEWTC_t!r57@}hpd_Ki%ArbZ-VkRg zma*<8wh+0_dwDwNY(2|uJwJ9RQXrI)E0m-Yo{FXgw|$rwq= z5CSgx%&%e!F>M3IW?@z9^r+>IQ%GA*UDN_j|u^IUh06b%%!{q9VG1=fp8`On1mZgXQK#9g`KU%#eAK8rSF^&JD5@ zg3*a_mHWMmWLsMTip%Ai-Ut+{^zQZWBt zdN~@{u4xbxX4hPOSyPm2Sh3ZtHcED4u9IGWAsgs^(>=;C!ZNw_S6j!B+e{}FDgtCu zMS&|fmMOQ&e&b+TLQ;U)T?^77(_D)#+0}0J+zoyqsh*OlrokPAQSGujTJWW;SYF*# znxv$6&%Uk~bk96o?fq^XrRL=k)8p z`(l0CO}4kM=D7jky|2v;rW3BQ@qzOgBH{Y-+EWKZPq17=t#W+6_N|5sb=c#IQ93hkGNts z>)|HGRfNgd^vW7tP0(#U9tt%Y!h0i@F>(%N z+P}%>j=CaTGDC?@jd0o0q_>-EY|N6-9J!7)0wB{oty-O9o~uA}PujsI81XE$?pk-&~8JSTq5`Q8dT877YR+O_2%qbugsl8I7O5anY|}!jvQ2i{cx` zZ%DI|a2D}Mn`*s(`5*rM-76ydQ7`xF+j47`A;quvfAyp9?-?)t=|{i+vE#$;cDeeg zUu2f0p~||ZHY@9;!=?92|IJUIE|UwQA-a16>zQ-UyD6)90xHFNs3B?6uPt42?ZQUY zTwC{;i0Bn+E5W*)+tXRuu1gCtT8mPf6gDlZXfnH3as&` z|HvM$GGPzoM}O_}nBFh`?SIk#=I8JI_3oR%Np9Pd-_R*>0mTFvZsn?@Ukc_Jd`&Fkxve00Red=pt;FsIwQT}VB#PMG(l#aPT_(u%ZZlBbIn zi6y5h)2^$c_ZIF`+U&*5Hld$LQiSOsl-J$G_0Z3r0UHf%2GcX8=q@i&!xxLS+bAHo zg?Ek_+t__<`;UzyFMzLU%BV$>A_hRW3^_3i_hg%a1hS_{WK?Ux<*=^H$MYrJHoW=R ztz;RUHg#xKnMougZ5X}3!_ZqaRV~^g3lo|-F0&0jM|x(00|`dat~gwm7xL#Hz5BoX zt>5XgyEeZ)_J^7-7GHcZ|6rQ)?#1tX_KUmI7cV|~dd&U~du?WyC*Rd}opHkLi`QS@ zym-9cB;Lx7ym^gMCUY-0yH!&R(W)?$1}1}T)$plZbdPi_ew>7vy?HLOBa{j;9VVLR z6>^e^V344c6W8X;6_bi}=U9G>{VX6N&jBGrqxfC!I(+RWPB0x> zi&Zm~RItvb0M-N*EiC)$kVL2xv8E^e(hncTreVtx%}q>)0SO&BbBGZM0ev2-11Md@ zN-g3U`ka3@4r&=5*q%W8q{>84&2CmdFL#FzcR%^~a`S+WyF7d5wECBCgYS;#a_^@n zeeoEl{6?~O?Cx&AIjkX8j{7_P%J-AJ(sC#_h`9M(uE$%WFA2EcifH&12@;V-kU>VU z>ApWifrol4*8{y>t`4>ZH9Rp&3v7YT153`#ssDOpt83lydZkYDGE#;;Sst!yVDbBp zFOQ}nbvz>-w71&boa&@s~Jx(yGE{urC zjs4L#Inc6GwnMi4-cPOyW2(XGV@8SI-DG-E`_mEu+aw+1VQ2EYMa=g1_-^lsOeziC z3yQ5M)x=DzP$4S58kDxKiwB{i5$X>2&L{PS0Y@fX^t-K;h-i)=>H${lA<2zR_Iz_f zArcy6>li~}DDtfK?`j&U>QrHR+m|DDx9{iIr;qgUxM(b3zb+HBp^ACE^TX2Z>HW7m zxv1Hy*@er)-g{L)udY&e;pNG-hMCrIMo3@C=vGQo35~wEyH(IqkOcG0_VuawlR*1n zhtqW~(N>47<#s1EoZge^$QaC75i%*#{@?$4^o#cE^~oWWq-ChpYm}z7d&DW;pQOTL z%nCMj%3u>^j(Js%QfmZK7etD|*LhZ<6SP{)YCWeGLj&3 z9o22sNPV2k8E*@AcE0F7dbC{wC88X1E^dw%N3DBZ&@P#L zsvsj>%5i6xQ*%jn56r_V(fjS`$@5(oQNNxKC8zyf+&esNo)*&AwNp)dL}a)Z)$~jh z{o|k9um1Sz@o9lbT?xs~nGE~yx2r(QhpRJvr1Xt(^~M$SZEW9#kr1Xk36YGl(ObOxTdg5>WCy#$c z@)Chqrv?|TM@d<1+8-?XddVGUiTiq9A9|iIewL@O{nT@J59ugNi>1xyr;A?aU~6Z2 ze_bvuI(_MkHA^&9xt^D`_wU8^uw0j=XQs7`^d7P^B}F8$`?KjM=i|9npf9nSXx61) zDJ9F{{V|_zuI_^)k_5-6u;vf%p_7YM369~iB82Fu?2TzN>OkPPl%+u zj{-D36C6<&qG>|Vk+voF1{9;o3euo$9KbM)wY`WC>0uy{_8k-0l@z6lN!l)aH!lX;#=IV%s?XOfrgCYKF_z;iA?wwG&Cq}KyGa}Mrlq+2?Y_9919QTG!oS$1w!V? zqLfWn1W=WtlTJBlN3DTcGIR&&vO^Uz)3jZ+cJSK|A;ibD7|w@ET=F#Pqs_PWEGN%B7(KG8xMz^QvB@acD^uUTeCG0Jx|(O zF@F6i9mrhAmTFK~EM))99x!YMcT z-NvWjGX(~Ui1B`HI+lch>5@r53K=Sy?o<^dDH!r)imD6)Fl2N;;gM7YpbSCks3@R1 zc0d^-Yz{z0GMM7qGVj^+?IS#D4E4rJHeqEhYM^TgLb{q*XCmFHCR@dZp^lQG z4I;x%9OAu)jW!MjxP!Xsk!dZu!yF1wvotY?=ZZSsBz&WD#=SNDd{2MG$3`0D=L}p=sJ*tL&Mg+=Wf8upMA*N;9=nDLj)Z;s5~;>x7arM z+ljnUWbrI_hn9lq9ZNY@@hkNjI`!Z_S2dj~cL%(Wbm^1v-O8FCFf)%5DUy!|HA70< z-CBmPYh6RFos1V%nfDo&d4K9_;q7gG>ht0K6OVgdys}Vxzn+_w{(jxPnofE-#0Shx zGy_Bz!GI_*BOB#c_;8Mn64xfODW9CAJJIeDAI`t^las1waxQ&WK5D!cqWs@~lK;u~ z{+n{$cHflUzMUH^#;2Vz>>3itAsrZ`f7#-YWkf0f9a+UGN@^9PaLAiGC_+y~nqvHN z(Nf)vB-w^(ia8)ggh(P3xhW!s`%Q*83nH--61gD zd#kf9PSf4t)2Gv_nL}5(b^vP2{oVesJS=&Kb>CXNcyV7(sB%$R_qTKK!~5ecOI+uv zt!-KQIef|8Q=75#I^DeSzd3gjndY0Zhr;z)J#zZ$(Y0kor(}{6h_=aE zj<|%oBf zSoPUSk+OrPCN~QrOd`A_NKlYcuef;@lXnfj~x$LcW7oIL{tuN-Q zygu>s`~CIJJ6*2yQV&P>UGoo5K<}^Yec$&>H1V_f#WR*tZ*+h7NZvg>7F-^rMTDjY zyiX%-Srk=TsAhucPs_BbTv8>131svxpbC>-d&zHmryC;D8`nRNZzm@0Pxt?WPak@n z^Z+et+I23im1B4yQ!OG06?jGr>!NHk*DVngk!BfTlHO?}p$--~w7=XYNuA2*y(iK` zxfaPD1Z#AJBTFkGxm}6r-ivuiu9hQvaA+RKraA#CRdeu$btsvfF=r1`&>u6WRhqt* zY8?}Dms#R`4P7QB_n-an#k+SPEW_*S1{F78S8vgIuCa?BQ#Qvi14y!&|+s8Bg~S(M^5LK_v(ZvS&C+iZeQsSIrbf zW)F{W4;52(Cp;N_{qfG^@$Kn+x_kSxUwk-KI9dMi;oIHy{tK09KgYZGs~x7xI#qfM z%az9%Fe;ddOdT_g98_2`^jU5-V#lSZne_F55h`AoV>>AnWDG?1&{~fMEHPLkmLU;R zo{!c}D#b#8glMZa^0@Jwzx*sQ5Fk^{)$QEY`DtI4*j@H6`m1LQ7UvFA4b)F0^h5W$2`?Q_Pc@6I_oo=PB?;frTg@h~`F2-e<Il zgo7|LBQ}*y4l7P3Iv|QjgQh6dXCL+5#!L!lGJ_lvbzs1hW$>uT4YlxHhhbqMmvG4A zF%Ws>akuCDzLHnJxs7#>wTCi0jifB~URX*Ls8`XvJ)$-6>D?UVqyC{h__8vg>fGPi z>CKZnOd0jslhr65bcQr7=I2ZNIQylWudk-%x@s)yOWjyA*DTw(?*dd z^6VX8WS98o|B5Uh-o5LLHHT4$DDVE(umAbdZx8(-PrVCqLFPJ5%osuTxtSM}qTI40 zWpgnzpHsEQ1`rSxlVsT~5$VrgL;65K3{Qll!$MSrkyIg&)&?YPobIBCpwRnRU2&wC zF?I6M0_u3B0$X^m8bl+~yx8Nksxf2X#5Q%4_9$8Dy}N2MD**lFBWIQ9YhN#)l#*@L zo`&<&PJiR@)v}+CL-dqHuIGn$IaERcD3yg2S=asH=Dc*(1b3HKE*|$keD}pQGGy^> zmo&U^GUd=RwocW6K1*d4uyLmdGgFu@KH0Zl-Ji}Uk?SR_Y?pF5{o>o{v$wqh{n9&2 zd-tB_H8e7YMT!iF$hxJ|b;<2%OWbeZiFBJ=I#0(S2mEk0ByF)Dr0e z(MWRE7#bb|GO$~qX?jAkb!#ZgfBDhV z^F^Ur$(G2-jp4>2G#ayQ89Ry1cB$Hj zgGHyX-LzR$y9>i(1cYpP*lt3|Mt9%ljlwKH{m*nsx~(c0;_orzh|RBLQE;QUOsq36l(3 zA_bg&^7m_IF6>s2G}V-hq;N#uf^=o(S``#9l@RE6DTtyb=f&nUneey%cc1ouujYM*Z#}3WN*8rB2=V>rvib7e!eFZ@YE$-e{5f#H8f5Q-m;a*+54*Wh!}x< zB3krp%F8$B%TrJlqHo~mPi;M2?Xt^K+ZK76l0=FKRoF)!n#XozMMPqphhXFu=q4!= zc6noMzEr(*ovYD>EaiXkt51IT^uN?@Zs=l!go_R%2pJ-#0>KnH$T1r(Q8(RDT5>gr zPnMZB$0R*eaqwW!rsgRbIRacMYU8W-;zK0Q0EMYH%PZr0I_Crl(i|$00Mrx^X_+p8 zfC&MnK5~2ONJO81r*8-OM`u!i5!}osi<*R$|Kl*=sdB57_ZrVZ3ic5A; zx~Ywr9a`1-x<=KnAKdqHJugU+0?G}%P#LC%Zqn! z?n_TPAZn6ra1aH8=pn{&T*W+2K)a$824P@sNT8Ow*6VI}E|=XytbU#LRRmJLUtUGt z3F=b22jIHz`!D{z!`Mvpf{PKEtUHfnRr^{nOxgDa!%-n3iDp*VsSB zueYap@(5K)KsBRFf%70BxlZ9^kBXM0&}$Kr$(dAck?(m`*RPMCU+tt^6Ki|1_kEQA zT9xJMV%obD%P@>vd&dE(6Cu<%#&IJ}cXO`{C7zL@V5))*Vh#eT61`MUp=i@mW{+qU zwnU@rI^DeXfBCpy*7C>~w?8}J?%T^xU!MNOH=q3G@I7+y(+!_#__X%@X5zmf9G;I@?am|{~!CC|N7q33Z(2_xz_TaYIfM`?GjOG(EAjuWdH2b;)UFRA%WN&S9l^;kY~^7qsD`Qh9@{%!VjEtUamwIE>Cr8n=cD~dZ^(uWU!4)^p3&EDO; z(=jP->yx){=KU=$$M)Fqd;g<ag7OAG& zdt=-lmV%H7<4BP#k0(ga7Kwn=1eLT|T|`FD~4DI3Lfu+W+bw{qnQF{m(y?8P|tb|Go3K z|Iz*(E_X-urKp}MqE$D??||4sz+E6T!L_WsqU&nTUW1oMZN)2`qT<(8J8MPwZe6F^ zdzzuS?kiXB?V;_A8QB_D1nGUGHfw}PtT~s~_U2M<=i39zv7k4qtV=U9rfiAfx@`(U z!fbFS6yR0Wh6%@j7-}K~a?eOrK^qdyXBSY27*nN$X(%L%ih!_OX=a}Ly%Cd2GI~;N zH!`JVqzpl3i8Rw%K~)h>DOO}_=tT8Bf@PA_f?N>GPUes2Mss)XetF{YFaGH-Wj**M zxXuxAzEsV(U&|$y>qr0UF=xB?FMj{`{x@G8uTj=t|84pIetqBeclgi`k{4QqOFPCb{) z<+#)63AziUcbT-s^;{3-qJC{9v_|?B*Y^HY1k%@>27?(XL)OAhWr~$c&g+}J|FoY< z#R@ILP-CQM*~BJo2w4G;PNZ%q6k()-SxG=u+qlI9Mok+s%A~-fr~|!(Wzr3SG*_7< z)|}_!MduYM$Gm&G^TSDlr2w+?9;Jv*>k?`Tv8tARAZXG!1tBm|h(RDLA?U<8YqfBO zA-pY^zB=4oyFL`(Y255yNv^)W;S0aHy#HRzQToMs+fPL=him`5&Q+S4;nnHIzt)xy zFJIpC`e)ZZU(Cxa9am$xA!)g~Ff60PsM2S_IyV7@Jr&D=InmACGd)5;Y8QEb()?hy zNE%v;ORTFiV^OHlF{WiAxgqkAQH7A^9b(sSf3q#}cc+p)Mc}Se)sY4Q(ZU22NN})T zb!1RyQi~x&h81)iX{stw++0-0B)sRYu8>8ri_n~}ZmhjXN*C@kQKDw$wAw}wf}nfd zxtK&~!N~I!EovnlqF%BUP84ARwz*R0NF=pnXK@}R1gfdo(B!DtpVW=z@pjFZk8?Wt zbf=KeOI+L_rt{3}Z+^&s|7YL3#O*i#8ozk^?xgdpPwU}FiVt#^tL<}@DCjPd*Q(y4 zw+~MqQ0`^9Ep|GT4lM#0A78yaAExP$k?HOtqD9;C2p0poF->Xv@bE^VN}$N5J<(^E zI+HTZCg@$4oFRw#Ncp>_n==F9k=SKQDf*04L)o_GgP@afz@V&^42Y0MQKncoMa0PY z8UhGzbAM7GXw|~08dfAdl4?po!c^6CKt&?btSF%(nI5stEp(exBeyYznQy*R786zP zDyjg%;)TTRn&AN}*V~#c3VQ0SPE$`ah}9B{W4)Zz(;oi$pZwM@UI^Oyul=q67ykH* z-~Q?}eeZ9-|7X9EU;k8Wx!$EpMzV-U39^&^bbh>g*U8I4R!sY|?o!m@89mc?2bd_| zDv2U}i6Q|-FjWF3K{8Uz$t_T^YC?wQ5+t?OYT&hn$h0pmV*e_jbu!U(lv*@I**&o( z7;h>hV0gO9)Ds-0>d}3PJ594=n-)F~o%#lF90Br~+tM8s(F0nFrUyk-WOJO0ik1nv zUR#QEXsM+bMg&WSl&P3%A*B@L@M@_Fymdk_=2@XVk8AN|_*v5*JZvuhWMbL6*0s0p z1TNiK=6QcNU%z>0-~WTZ{`SejaCr5w|H&@!e)Y{fu5;oeo5qszRo!W}@YA zxO?^DIL+y;dunYHJ9?{Pye?XDjkHoUIx-@n71sgO`JGaEDDKB8i^Dzp?6CqYpQM)$$D%NYIN_5s+M1rVahq6$*gW_*1& z9|wO=RPT3wx%#x*l@TO0O_OcY&7$FPV~0;Zo^5{d_RI6xU|)ap#nW|>$)}fJ|ID@T zC+qL$(xADf35`?}okDfqA6~wCeYZnvNxzD%^KbrsTQ9P+ScR;b(NdpB)MGZL+=wcp z9N6;Nx1!F9=D4<`sFk7_JxEwFirC*^LRC_2tRp5wV$c#ZGolRifEJ{CkgG>`cc%|) zxCy636*F@1Op~ zQ_dQ)7pVB!!c2!^%S_c&IilXVT}%VH1vOAq&5&xP6g7yLA>0!{nQN!m-!wB-!9;4Y z=#HYyVXy%+B_opp9a}5TCRS_+@bx3YWS)?#Bll1Z2Y**w{t83c$@8h67r0$BZk0;KP zlEFGxJC>;ysO}|8#qkGr`D*`1zj=T8>eG)ue|-1j<@^n9zgQoCar`y;!LO|sE=0-q zPc~6WxQRm4YMt0~x_kK={=Q8ge)eZB6W6306)j*YfJ(ZlY%5#U%x7-7+E~)+aF>Ws z%!-+6Pz2$t>S~2ac4g3>K!SFlowwTCbKJYNI@p z?>3RCCYgY#2?qEtQUnGjp;|VX->7MacSQlA+3+J`q=j*NH+y&rNTZ0Uh;Kd~<@4{S zLQ(6xn3&fx0iqx;nkJd+EV1TvvyW=+>S7L4foM!{6cxU|w7Z*6A5MK;AJ$jx{3jpo zzZUyges_|xyT5zj`{hp0zGvxTeL|w8x>|$>p^4?qyFS<6p_#~tl3B+?5VWOk2d|{5 zQ@=YD`lfPJr=V2Wb}FtSsu>j&0PcMF65?K#PhE=?Hck zc{I#GDJ7l*A|X2R%tnTK-0<=uS=zW-Mb$H+psON#^Ziu)a$%VyR+b`5mwj2Zm6^{aii%1C=`L1B`649MB%DP_QIxUmO^gE!Log`FjG&dWA8B>zq6+Wv)&AyC z2L*;CvqFWrDK~T!FJoJ#rtHB|4emjXOJ{r{WDH5ZLjaN0W#3AssVSI1p)!oE7+Y8C z3lDcS{Cb!YCDxrZuf_X(mGxoWy|jYMJyyFc%SGP3`}*zqcJ`OQeO`X@BKy1c%FF(O z-8!M&U9msS=ppDWQ;RI$zHLL4x;$x4G2NaXia<(1EHa|NyF)3$d2LXM+=ZvAAat@M zqG&ZkZjv8}4FF6~ME*^yMh{Wb>}ds(5in~5911B$PthtOkjxI1K-m-*pd`hFxfO{* zAw`kf_yghzhA8T+y`w0>WGU_O3AeWs=piG>7h7ww{Y3^Dl>$*SYvCwU)gC<{KuI&G z4jZA!Gd4zM7lRnW0EHM$qO+734-=KXG}-Urmp<)k=}=KdpUJ+g(EEK^7YJvhw{=~TrY2y1|{@eI_J8cX9$W+A0%^1 zV-2^Yk#!A-vX^WcHK93KSV=-ui*4b0+d)pzq}boszSnK^No%24Es_TWOA%@~N|D@B z?JPH6h3!w&;NYNxzRHWtb0?9zXhasF0?YU73$sEQn2oUX8<@RXq z)Ku?H;*zS!ww^D&%sL1_)8Xds<4<4RPs{moI-h3$aC!CF>+jE(Ctb_Ni*6n%O75#> z?>c|{>h;I>mHFa;RS&vaN~y$RB2`Rl4vfrIs8;<428So=|N02jy1HZ86#lx&L)F* zkJSC_3oV(1nx$AR3K&#ih8EqEum)8`Zt21i&@y_S^yzM19wMf@D9dS9P_&dL5}q4t zL)1!lLe}@Jcl&y4_b+~U`|!_x{txQ?&G%pA@;E+w61^I{ht0dGNCu$=U2a~z{wNj{ zS6vkv$^o(jP$CGxl_4~OQZ&TI9WRi421;yv7qx1CH2R%7X?ka!Gd;lYPRZ^tm*(d&;6>w5aL%ddL*kgq=a@uzo?-lddr4+x^8R1>C& zKmOuR{_LwyKY6*FWp~aZh9Z(K0)?svA&i1lT8hAQ%XHY9ksMRM-cpsJYW91uDwznF zfMiioRWoL1DQv|GB8b4okLX)tE2R|8*s-USQdCV<)yDXcpcLC;omBvnY0{*YfcKsv zMyRQk8L9Jrw{IA@PXL1S@C^exzT`-U2%|T6+B6q44@x!I06<2C05GGNs41+dDpGc1 z$t=1RJ;M(z@MyuLPIU+A%fm5RTe?hFSkWZwP|X9RPTCfdNk7()bg>tQ*MC&M{4aj? zPk4X#$;;bGynBh%YQ74`Oc`EZe{`s`^Yu?&uPtn@bJ>^D!vqS&#xr;ZkJFxudEfDBG@B&=Vqr z>NIOOR1+#GIx1?0DczxBRwT!KL_}5TYp2yR{9R;5ia^vyQizzCNxCoFv^#qOW>gN3 z1VvRcAuypzS0&A?GSf`l>*z4B0;-6Zkzz!Fmv;F7SM@G2w{6*d*Z7Yy=3Hy<{k_iX zzHhzmt*W@H?D$pX*l|okM4TXGf)q$1uw)@2(V<75E)9g}(4Z56gb)%f1X2VENraNv zB*t;MQm!id)~#Fjeb4hd-(&B!<{S?ibDwL`KRV~r+51~-&N2S~->>nqlh8k@# zW=^hE0FDwNKs}V`$cxK5`OV>x>1+S`5Ayn3U*Rua+$qb=Vh?=)2iQk~d!zwD)tu-4 z?1xV(N>1*}Do9EeOG-wl9nwm2<|Ia9$12630A>;ihoh8fp71*YGpGc4unILtsce0; z)RI&L1ZE2hW(vtXKC8wzbvzsy!JJ{9h0}r&E?O1g6@Hvv&_u(H|_^X0YX zvN#o;BG?Di7c8#PA(``-fADN;*GW_PIH?p$3LAH45mB6WKNiZmb+^n|||TefIo%^No{to|O4pZ=4_S zlpEXruy-QjH1WA)>*maelv*FX|JmvOmD^9*l2SM{8)ZmxIr$_No>b-qFIdZz$1W4% zNbNk|%=m5Ocs8o0hm1y)sEobS@b6JlZkT!>AGQaI+VSc$h69W|vY>3#0KncoK~%<@ z0bP`dlA<>kRorAHuwgeYvniHZXjvl!rSu>gugP^=JPG3C_Yb{DI*EAALu{sO6p(D= z@)jNnBIZ$GNlGfmYSFO-iOxV0a2WL`@G*nn70V%22(fmaHLDk_Rmydt^GCh;)+~h(E zYLuagKl`jU2LbumCylT3Xl!I6<8+>MmmY-hNN1twGRW&-VotXalmt;nk{}|0!W6yc z5wlH4iC+v*qxKFWEr;>v!f{N;j#_@IB0(M}mPTN?9mm292pQ@W%~x{>CndvTDr`0q zSwIGgyHKU$T(0$y|jj z3X7InO4SG%3e$*i>p|I;5D?dr5e{USV{m1F2uAkA*bgLmZ@u@3Bk%KQC&(;3It^v~ zlxPHI=5nFS%__T7JEWFbI%m2=n1sQ~6q9ln2&c>G$$avPzCNXW_sRdG^Cxj}G4E*) zrZ2p5_k2?o1K>kas$`?-VzYg=(u5YO-k^X(=9_W%;$lbc^XEV5i`yeIH?`omk2BBe zMAo^4AUumYkuZz2vB^adLBu$I2dUclGGrfTat!&*3kd4)5sW$gm>1hv6k`%055kff z$sxx9E$Nv25@i9?8v7x!N^p{9AB+GHHC7InDkRDtM3srm$3JXlNEReyY;G({#F!`^ z(L~cR11iTo!sp;Um2^_M0i}858P>fBpGP7L2nQug`qceQWu-PDC{W}1a=rIQefj!J zmzUe^+vgUC+b0j|#EgDe<9Tn*B1z^9J<0Xv-S=B5h}suHzwBIBs= zIk+|{V|%j0e;h`l2oBM*)N5NqxrJU^WpSlIBB+OZ026HH^jX4xp!N~A%&r*cR!OGD(vnssX z=~1$vFDeNvpfKBod62LlpT;AX7TF?5N|B^O(Qo=43MWy??p)cra836pCCr?di8C^W zet4YQ$P86GQ|iP?eWAHB%b-4zTPH0Un&V&094bBsz+f6TdJuz0TWxFukC7foT99yf z-ca{D=X^yBQYG52x-H&!VGCEyb=FLhLLD8t`m=qkKgQwb&Tr`C?7?ZQS9_j{s6!}v z>+SO92G}IitXCg@^yH4M38ZWV7X)t+{r=^ZY-L?e8>6-&=i_^yDl2Vz zi}Q!mlY{qH&>_Lc9H^ONoJ(Pw$0Iu*|FeeC-FQM-i-)oFe5-tBFfBUYX-J~2kJ z@z!)SK=HYODAQFUh+(zhKj{u(A}3Zh^U9poEy_`hBgjCV#FxdG!sk-hJxCNiSb5BV zM3w{unGT|47UE3i^dXU?EXTm1P-H@Q1q-Kpj=p}3xer6esYiW~xzfy`Mc6HbDY6g| zg-0+22_txfPlAGKS#K3WtB_YuYdZ%FQ0!!>?6iiSCUONxK5hO z^V_S&QxW;>#~;>^((uwJ#>B3Ik;;_I^QYH_IF*N=z26G-{#|`#P#n?LEf5@n z2M;m~?gI%PY;b}T+y{5J;O;Ji26uON88pG2;4VP}1o*h`)%~j8tLm!mAA9X{{&ZKL zz4l&9Du$~VORweQ?PDRz-l!#K2T_>wA4uP3B7kJjc`&NThmQ8BUP(RjS;UC0PS)!l z7Tridcdr(qAS4?i5O<%!6&#U>Nrj7b@@ZgKRClByfXUP>;}7pnV4S-;d|1fGSmqxE*QtEHBxuCWSgeD*+Pfax%6Za( zwQ;V${8jLGIPS$?kA)vMhOMzWB@?;_+Xv$}!<6=fV#Vfv za!6Tl%OIG;o2San_%wp)p1{Mk2)e80c8iY%d39j3v4I4XwyI-RtM>ss<|OVNm?p@Y zLzcV7kDN;n+c{}cb6%9@4#_m7y0MFqWVjHq8vKE7*v4jw= zEJS*{mi?45{K~k@p^_gv_VD)~XS_!bRD5h=78s|=48wYoDZd*361~^OJ^SX%Sc$OD z<$lLSbw`JN*QZZUryJL3*K;htub+oR2l0HJ`!&woCP(aZ{tCoB_p$G6zjTw7)AzgiwMSEZIU}*!XxIH`+U@HH(QIILnKE9A4{q&076xr+{^J89TUU;u zrYL`a_QitE$qp<0Uj8WDrW?JI!6E9#Uu^4oIvY9KJf-%|m9LxN@^6cz18l%qfqT8xF?zVB`1=$!$)Xo0UH6?%7e}jo#M> zJ^OK~lQIW%34-(Y%@853{ZA3F5{>GT{vt$Fzc9W9!cGz*MQL79|NXKg58+x=q`U}p z)!k?p>{Rh6e(P2bX?4m^5!A@&alvMIwBz|>1PQd?1&s}KNi=bA30OS(THz(KBwM7% z>g*^Xcw-Z+Nv=^@aZa6%*aSzo?;^<&0j2n!a}i+LpsQ(%C)}VuSD*_f5x*wN19f_= zKviA7gbJoo`scda^yv?lhsf+IK&Byo}NB5MIho#r2or83B zxorm9iMdPbEq1;a$Fz^v*i#BCro!Q}m0^USG|P;qjFY?FV}s{$%Gd85Yr#V|O~Sk} zInqzpirCZmpPxfsO_H8(n*DcmPMA7ao9qfIlV}{}o#!78dK8J zh*029!`znPO9JCaHIQn+D-(u#JA8b=+W#rDhTz<+5E)>0v`;eH6yoTC6OaZ0-=i{$ z*(s2mQz%>91bBhXT9rL&V-+xn3*T5=Btf{3^J$-=%1sjqcfP~ z$A!UE$EBT}(>-1nieJ1=UJAkt{(b0Z*X{G$i`(@4cR4#!KRNlZwVY?#)pp;eA(zJT9vtY%t{VP>cdD}@MDvdzx$&u@p(7NVM( zrQhE$5F3?6`%B*pY0<_R7@^DRWSS0E1tc^R62faXvMVz;S*y6oDWemLIsrAe)+JjE zx3@kkAje6U6MQKAqZXeNSa(C7tCgsv5_-E4hWUds4qK2|->0_)AVvBsTBjkH-EQBk zi)M|*xR8`DpS%Y7c}KwSwrJDuppOUpqSLXi%k%2rxve}G`-Q&##tdxztHFGQ?Lqg# zL6QC64^5sO74(yk4$sS!^{aJv-yiM@B&YLb!Nb`a(>ITbH;feDpNMXg-uhP@?K_fR z&cd)yB#Q^wpOqAbtg|cr6*1m<=en)kf8Fn24@4wkg^RzZQO7NkSH+e55LpPaysK=O zfzu9-6D3Q>UC18)T~kj73M`C1W3kW>fte}{FQL{sA*bn`+OGWQY2AJAM%qShLQcae zFMT0on&GXDZLxRi6W`aQw)$_gl8H9AbcO8By zuIuQVx;UMq($JyY?DY+6+V>R}XLodzbks?pg+#y#>(J?A*Vkn(eLvXi@+9*#&)w-6wxgVL+e?4R zQ2pr8wb08uMp|qh@72>copXs#+6N$veiiQ6Y!czr3&L=8QxTLuqTx2=bs(9@-fY^A z2aaAPA^l!8Bq;RxufZ3w4E9QOe-92CKrmOL76NL&a;m?)nNYPD2+#m0-D0Rtk-z$f znr#H5mm8pb3P{GC$`8c6GWlq)NpJ9-0BXF`@|k+4ck~BpU^J<5G*mql>@50oDD%X3 zH5azE@i+*V)`$?RBfe-|`ID61$Z9VVQwfwt9M^1fr(rUJPrG24H$)Mv( zdMMi74=*gN6sP5rR!C8p+Ie;(;z+47ZJqqqp#0pharnoPY&*P2tadt05BV+{X+9wd zlx~$h$w;Txs0L7ej3jGUDTnb`G~zYCl!n#aK008hKThj0H1y3bCuimT=JOvX zb3Gfo#x=*bo7VE##vyy`o6m~llhiKN$qL%AOp-;dUn058H6ll!7~65DCvZz|+oq@` zeNF=sOOpfaKykt0__%e_*orAM071)IFsQRR6kx;>GJy9gl5tPwgUur8N65%WJ;R zMH|y!{+;5zCQwa8d{|DKcWPi<2#**R)TN6=#Y2O~1w@C~Hi4m%8Yvzr4C1HK6MX3y z4ha2%a;Oumza_i-Dzk_bY~Qmo}U6_0>f; zOM|c1^^Eo3RYQ_FIj6V@>ukDKQ^pK41;MX2k`pQSOIcc1vp7~tJRYF$L%BPY7=;JmUn3DRu$elku2hxhS zL&VJ^Fgq6V*te0B-h$*o^eTX8T4KMIfNKfx;CiKKD_P0p9re~|etz0u@>7t4s5Jc+ zA~vD^{&M2!V)g$JRq(4-r8nP>3j4%3)0)!K(Q~!9iiWpvA~#uT6N$j;mOCasN7sCxG9UgvwChArp=bC*tM+!q0Jv;8R6UX&5PRGVSgFAc=__W zV&7=)M`Qo_dfwcg<#n4!A@yX@_urPmQ_Vro(tOXq^^who0(ZZ&sl`)+e`C3we=g}a z|IWKV!radUG2^06xS<*KCT+S`8PW#Y!Z%ejuR z=gGdu>#dWz+t2dMo$V%CGD2Woi#WWOJC|F)V}c91nGCYl!A>`eQDEdB{x0N&fO*D~ z7h-A45{;UcpGp_#rr|MCC{5An4m?t;Y2j^Uk4ntqoWd)(MEp3?kF`)_s{{}a(i{f!dLr4y(k<6@F?NPv93+{ z+F887SZY44iHyin;G0+;{v|E{4dRsYS0B`C0BFh4mS=IFZ0X1u~ zOka*atwXVVLUvl{s$fnsFd>x?C=i+meQUm8pA|xWk0z6>Us1C^PKS(^PYcDBM$L-2 zl*dLOV|*9jD~OG)0Jj%4W@@$IrQUs+bl-h5N&i}}`!aFweYn2PXQxQ$bG?6s?RySu z`Fl$F^KEJ-=IZp+==tDvIq2Eji}24)=5(vd1JKWCJ}rDl@nyJ}(uu#a#qSojs+aok z`tOVf;`wvYQp?x(IkGGHF}jF=#7z=IIXu3j)~T+!(aj1AFZ-d^g_OU8AiA2gNMiA0 zC)At~lA|Biws%-DCQ|R;t7it*c}m4$E|Xu*hA(nZxq03#pycqpP)WdLCc2x~8YOL| zpgxVEcEF&Gf+(S#{8S;?fe->HWH=}Wxr5;i1L#BGQA6Pntx_2U8X#5uf;f_eQol<& z!szo7@dG%qbL%mG-2D<$oS-=LDfqMU_{aPHpuv9gb$jpM@1v`xlDRJt$YnKWONB>6 zX_T|;^=*&CX!9DGH;WtmM*ulu{Rc?9*%DBb`D=My$3h63|JT3gZm(SMleB0*ZNYd! ztx&Zo3!p@LplZov`=jrQ@@kU&D!#$DQiAk6Lb(fI`8v^pOuK6>!22gBtZWj&bR=es=*3c6oANJ-(IK*}Qq^xxS(An&4Eus&Ug?6y zAZLz{&^G)i4GHmSwtF8xYrjt2tE7hgKb%xmmz?l=8}6mPpz&0kNntezx%FFOo$Q)k;RR#!W( zyU8g^z3zr3Tm;N`GR+jI6zH&*f3|O)G@=QIAG1^?lqPFp%M%P*6ZQ&N-X5;gDGih+ zgqcS6o1%V#lRyTTMRD(+R*$JW*E9hD`=@%~C~kaO#_1xpipMWN|A!V9V<75a;IYnw|Mjbt2_>u?-8e&-s4UCGh(D96PP-nMd)> z!fg9-ccL zFPXFF+u7IZHQfz84Np&T`hJc7Y=wLtg}ZNT?%y=c6t{s5`b%E5mH9_N88~yW!p5RR zNq+N>QMGz@-Kf^_cnM%3B-pZSJ%|${Bq*IA0rBIkDAl91EZ~J0kIacl_W>@e zNK*TN&Y3r%=w}3G5!Mt~phEtA*Ksjt8Z1`mgCQ{@$&4lgAci4GlUIPF_Rb7vEBrF} zGVhxvea5}+XdM-pI@x_C5BD^ZYiPe&&%bu=XiIS1^}Xsg-cfhM9O>Jk_k-Qj^U9|C z_GI+Y01A&mpIjQz{dm&FEsx*d85CW+_i3}L?a*|8Z-{DD1V|l*L#NzIVQI-YHj4V; zBd()=f09%*q7ubhwTLb0FaLu4F3qE#;Ja4W2D5blqX^W9b(l`c;PR~tKP2KE6(j0l z;TGsC4QS#s9;XwkaxB#(YIr#z{7gXzeTbBoP6obIB6Z5*#=HF9-c!?G{2zuARGC&1 zY`f0c_!;W}Z3;!ZFJd>9Hp14}<>mdWEG=##BQp!l{3A}z-nXZ}21Z)K#CKF0X}^Bw zNb|LgIl2MzTx?7!PVoq2hv=Len@ty?7t46)O&>!<|G3~ma>oa*J-0W`#>0e#>K1bK zUdUdzlYD>9-sg7vJm0190METs!Xf^A3;I@7LabELsfe|gvXzjonxe~aue6)XUE(5! zIDb@&D@j1)Mq(7?s+E$Ds{tpEmJ>Is@TamLW+ad@0czqUhO-_lv>XGD=tM$aZ=GYkj?sw$NiH;>w7RU`bNqM;!LwgW@wo3mm3Ll?%K=9}dFi6j zGiFK7bbs=!IOXf@_+sMcs?po76a2kyUkAVC>G-SI^T+P2!R;)1l5U^Y?(gPW)>`6J@E1M?zx+Bh3pCA-G7hJm)=K%PazcI>IRtAf+DR zIk2)TCfqZSZ{TA=wmn)tM3hMYhj3(RLiz9@5E5T3mEnksZ4AKVz^y1!i#VrGAb?-4 z0QK68L{h{#7m)`&0b@)1%8>5}%-jy7wE%4k03)H!8rx;T&W9QBURMS4re3l-gWcWr z0*_M_d`~M=NS?Fpk-?7#_Sb=n*GdB?rqrpFqa{-j$<0dKZy;W-lk&_=jF#iD$uSPW zO=5YtVyxErDIe`VqrBFpB-dGr7*G!3o;B!Q6y7vr9b946vp*d)y`}5VU+HtB8GQCg8$DVo4keUCk*fWw2{1)!7&H*3mKk7IB_G zw!L56FDA-z9Ftt4sTGxh%? ze15!v5%zOgHu84mZ!uLPt4@24R@?dK(Dyo>D~thCMR|5}F`paf+FYum1KVY61AD9q z|{N17_5d60r4bHAxisi9mDb zugQ&6)HGAFG>4=kS4}d31EVs`t|QVHbuRVuFda$6i*vnyKfjrrGkf~;WgQKPa^z7# zLBq3JQ|?X!N$gr%V3iC-BiGt{wUhS1hNt?186=<(AK<$ja$Q!XTqXTeJ*ce&#qQju zdhC4Q=++KJ*n1yH-v28&A|bCr%*i0hb`k5#PBVPcdXAbMS)RC1&q|Z)$>C~K?xB&` z=FiD4)1D?o8$2G3Myp88khT*Qt_G&>cVGCb0{u3d8dK6uKl8`ir_W?$g%_IV$D=q) z*7r3!zuA4St91)G%#-k>-@8UK>gM+klHk3U?d))gxrHP)+f6={XgvRWQ2_R1$}0;X z9oTBr7vUG7oc&s!%~J2zxQlmT$|KGHED&5jLEG*)>C2N<2>QTB(0tY`1<3Y5JrEny zpIDk#MW>hfF(W36feWBo3xp|a%d_dPCziT1P_xoo!VlJ~*lr)uuVZ2Y_m~4Z;DN+u?h(f24AwkQlC2I5J!Im`!$|P1ZQHg=%JL4Rz}aNQ+eeIuN(p%<%P_A(!a#Dc!hBF5>@>za`-LzJt+iOb*+r9e$j&|&B>XJM0dZKk{@}$b+Qmn zLADbG#YFoVB~$wlV-1BR!&t@He>~1oIFEIi z)@OMnKr^)`Y}~^%VpFliJFPYYh$0vd?w7aseP9(b%pJY6yBz=`7gftpf$#Dx2Sj1` zzwjNzr*WCb)~xTO@k%}giG1Gng77`YNNx>M32gSbJ^x7)(5lxO9g`u=IZh=ZL@5Qk zyVe)oq6Cbl~DBBn&5YtBr^zB`3Q6WirT-3gfj z(RZZTM~NS6v5c-L=*6HpfDLg1nnk1MQ4s{@B6>B?)_o3H3Bo-OQVhT|{qP*7Zk1Hv z(7gDE!ewPzqlo+>rz%EH*Hy+a@j@jLhF-~ONBeI45&tTW&^nb>WrEC>lUe!$d6%UG zlq_SCWc+Z-5)2w+5-m-%1V}QX;e4a~cFw+kZBXd`wM98AaX|5#9r;G?f-jB4Y}Sez z@R8I~)xBLbM6MZb9t}xN|C|*(ns%y1+a3-E)jN?gCIGd<@si|4wciiPTAW5fsGL6% zYHLYxqPez$9Q2MyNkh#aHT~YxsB5g87@U4DmBa_>oRDV}4bWjUPUYDC`P&7H z5#YtlUV8KcP8kZRk{gN)qE#A)oFoRlq5nu zs!2>>*`@}BGSl*sTVWa_L3zX(>peG)>NlScY2$8&`LE7qJFA()f7Kul1Zc3G4tAR; z5DsytSxl*n?!hfrQPGCt$f7AC`R50J%o0nKMNZ}leO+G@Y;;=UI0k6g)7%rk>-WVI zWkEM7e88nV4KPd<`|&>Ir@|LUVg?&heMDh2Z2Az+Oy9|Bsn%hXmZQaJ)6m~_S zGdy1%RT4Ww6-vrnqsX}s`Bz)rtgc&2nyxBpvZhpOXZOJ4#-l7&^_h^5v%sb^;T0jD z``hOf+im0$HY199ioE9kO}6!^O~>{<^MhP?GqEKHTtSEul6X)5t^= z@q)XAHd|5I9mGfkC&haBenlbvPae^yt@I%^G)h3scqUmfZ#=|>g#9A~W(G0QL^H(x zDyTpNd1J3`vAFYoZ8GqT#SBWj0FT=E311UCQ7U3Tqr|3Dgv1&hHOo*bv(s8~J?!~S zcPV>#Cq-RnXYJ#fD6a1|-F}}aN}w$L4H8xID(WZpU1$!L`h_4=6iGGwcj{DaG4a=o zS<-7pmeNo)Z7d#lEcwu5rIY^Y104f>@4bH~n|_yvFW(H_(KCx-^hMy}WK^Tu(T@#V zO{$C-seIc7Cy8h6{Em{JIu@15sLE=z-T|4?xP(41z1!;Tom87ivcO$|)HtRJ;Rr2e z*?G$tq8clK{L>ZMO+)i}u-<28!M^1nlIQd-(4m`vOUY6KAy{cQ(ov1E#^h@sihyMu zC~0m=P6{#^Yk+hmR9t9b$heqlfRrX(9*Qck1}Qjt$h5v&prkutp>fRjNFH@+hJ@N^ zAPegQ8dK@H-_pGhnW5w>ZqtO;ZyxOuJ$Rl_;jW;Sy1S@}F!J3Mx z558Ry4w3n${TN^q`tz9tE(y$CZcvwo!Pn$edi_fAc-+Qg{P>5`EaTp)+-T z8&6)TB*_*|%qRY6rr?TQqw#_~OdL;f^aLrKpe#VRKmf@9xelWbO+*fXMopSEM38X; zPxKQ|XZl1=ctZ2MM_*?)wca5K0k5x(iY4NQES`66pb|7uYc8k+78Igg;eh3D6>Jwg zAV>nwRGq+pQ{1|^prDvC0j3Q5Gy>f6Gr?Wj<&5Dj^DD5Oc zAu~hP<(3C}oWY8F`w|CYvxw+q6-;p5Q2D;T&u6rXts~`YbWthZ=qPdccD9(aCz%hM zo;L7yYN}Lxg5)8}CW2Hw0-a)FZWGU=Py{B&4Y!>jpho`)!06Lrlqnd-{TBT#L;F?qLWN*x!QGsgg>onyV@S zxB=3_V%Bw>ajI|!`_&R7M%(Zp;pI4m>TU_=BBS3An_OggXX^=vosxhQ-?HG}qthw! zb8;}lI;#e=KQaXCCPoNJFvIJj)vOy5T&jnDjq zISJ#>bb+`uWJ*6ECz_8%IPa5f<4|*99M`o6Ma4_Ri~kVoL#4k|CgTRuqJ1G#=Z-~< z8=Xsr!$*?>^K$XssyUx!7r(#15&7Y=<;ko-ZM`++!okncM@LOQNz zg(!dBpn-Ct6<$@GynSN(Qd)Xtg0vhhvF}60E~`*3CyNG_goKp_FX7T!vwOg39Y-t` z#G|hAS5eW}x=e|XpXPBKfyR>tZ#+tJMukoJZ=|SD763Y8Y=#_(4cn$t(RQLWKpQnO zKu9zI6XQ$ZU(pC2MTH&X-9iMuR+R60R4nA&mIskdvjp#zsi9?ARcT)b<$b-9o_D!enX(?Vlo7$R zhTNR2LV;?5(bjC%PP7LTV&0p@ElV8yU%|8j_V#U6Vq_<(`0`2K-d3y6~p<=nmg-`kkWh}a`*wjQ4 z2i(S2hWzVG=+aRUR}(_eq#;l=nRgz5d=U4?<*%lX-_+6lNBegBV6ZVbFEg)Js74f2 zD_*U?9#P#+evOE2!*bt3Io`sQDI_hXBEc(Q91P`JiC518TUUKT-;-xDRZy{wlA@uX z{TiP<*gbjv_k`KCU431ynU220wqkZGwPYa+!(4${`P_Oj4|LUDG5l zBQJ$zKA(4?TnP0DQQ+LOIgl#*#*E98CMA=AIkuSpy9h(_E=(2~`1G%zUe^^USl?$t z7XIOLLhXGMA3PosnX_ZUf<$COytqz=XhMGh#^|x{Ks{8|x>5_B^0-|K6}A;Q(CCH= zxN$=;Sw81UNnd@Tpk31K=c!;*#WqF1M8%2@?KcDnM5xe_7^AW0#uM9IeldkCw?)=IkS_f+6(r$`+%~OMyr- zr-#Q`J_IAq^;Ds6AN>4d#$;xyC#J#fzo{KAl+F8%dk#{wr33(M@JY^URpG>}adD#IW-89CJk}_o7nKKL>?zcriplxT?z1Hts+4dmN2+-W zNMeoPP1BrborCmmpBh6Zp)8)O<2YtJE6F+mAr>n-hIwRgd7+KDFI9vjOanXt%^4gF zTFMojd-GfZ%bD)PWRTMqguN?X5sY9Egt}MkVagaKBA_BQvdpM1MQGg_r}j^C!RdQV&$r~ z)TEe^AMfsU-n~^-<;*+;9Y*Yx+tJrPNG1v?j50Oly}F8p5Ty}LX{F*(rLhDSS-T-5 zM+3=Hr~u!#R#i~J)Y`N*(ld;LPqjH-BnocX^-(9J<#_9T)mvb~FHuTXD{2jU+*Oie z3teX5(n_qBltRN+SXps;IwF1Ny9DIPlyWsJ6%VEoiK}MLu`zOa@v83*b2&y}Olm22 zF=QF({23P<z$RQ7}|+6pYr z+h9EqQpi7K%yP?|%BOgyc&~~g>6>Ep_)|8Kna1sN7uw5=CGc;k+8v|`B8g4es?YzhjZ!%%*{Q4ix}3c zcjKUDe5%ULszd^;M3cOKwbgBaVB8UiL8D@yjgTV_>e*N@j}x{J zIqxOanWJX+SRpKR1#pcz&Q#?+{Ie1pblgM73GQ{sLJ=v z!}ZfSJ{K8J0M;gVFNwn`BK$(jVEm!+|xYY|(|XgVSH-x$G7DXibt zCqk=&r6#x}c)n5JH1Ww)@!K=lWNVvfVO1oT%S5|wIt8gLwewhhcW<{9Wg%c{+UsuY zwY9;sjqmHL3$0b61mZA8L`h$nUCPsaGb|SplVz4p6Lc{1!a3;W5Y>UeY!=6svx&Fp zB2}_N_tf6f=u>Cu{h>Oxg!bO zvFPl;mZLu#5$``ii}~uq6CfCt2?W+RWh4#ZZUudfMme1b<8bZ?ttg!RysTkKqH@lSsAB2kV0AKk-X<%{<+myIGFY~@nw}3Tw3eJd;#ez=Ex7$kp2F44>|c}2 zzCz5BOmGv0Y{N{dm?#*QrA%&Z^wW;<4A^*`QB9bC{8bTKh6yLcvpvl-;*0A|!Ab`^ zJ>JI@Api)^sjZA)P_rRa+h|CWoK;6Valp%!Ywi?h>kt`HhMKv{L!NOZUy;)|M)nM$ViZ|L<2vq8?9$0!hNe_^-7xj__Zh)wUI^=p7Z5n#yvG3Z>v8 zv18Wz$4)nW>MUKWli?=7qOA3l4JPXx*2nPou6b9|E{|8`e53w}!{Qm(Uey-2|7vSh#e)|UC{m%-P zCMNtQ_70v-mgW{NaN_@cqGB*%q~he`4Ru+d3H8c|K@( z^1S7s{%;P5sk4cbrGty5y&ashij)|YjD+}SPQK5dSwU=^8dWP&Z^3l`4YoD1b2T$E zadCArb%J{fq_S{vao}fXcXxMZvotkfGqE7|gOh(l@cb{r9%5?qe?tE^DHYh%%*fTo ph3Y@6nb_OdyE;)hI~bXmQn6C~X9r+a>9V&JZy<%YK`g_-{{e7+ps4@= literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/image/spacex-yuv.png b/third_party/webrender/wrench/reftests/image/spacex-yuv.png new file mode 100644 index 0000000000000000000000000000000000000000..6073e71735b73d72986acbc5fbe495038ec5f78f GIT binary patch literal 310131 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�A@I(~;!v6q`CrpVWnpA=_;Hn=$Ctjn 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/image/texture-rect-ref.yaml b/third_party/webrender/wrench/reftests/image/texture-rect-ref.yaml new file mode 100644 index 00000000000..42d8907434f --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/texture-rect-ref.yaml @@ -0,0 +1,15 @@ +# Reference for test to ensure external images using the textureRect sampler mode work correctly. +root: + items: + - type: rect + bounds: [0, 0, 50, 50] + color: red + - type: rect + bounds: [50, 0, 50, 50] + color: blue + - type: rect + bounds: [0, 50, 50, 50] + color: green + - type: rect + bounds: [50, 50, 50, 50] + color: white \ No newline at end of file diff --git a/third_party/webrender/wrench/reftests/image/texture-rect.yaml b/third_party/webrender/wrench/reftests/image/texture-rect.yaml new file mode 100644 index 00000000000..78e7ce6e869 --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/texture-rect.yaml @@ -0,0 +1,7 @@ +# Test to ensure external images using the textureRect sampler mode work correctly. +root: + items: + - image: colorrect.png + bounds: [0, 0, 100, 100] + external: true + external-target: rect \ No newline at end of file diff --git a/third_party/webrender/wrench/reftests/image/tile-repeat-prim-or-decompose-ref.yaml b/third_party/webrender/wrench/reftests/image/tile-repeat-prim-or-decompose-ref.yaml new file mode 100644 index 00000000000..8b7e801d569 --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/tile-repeat-prim-or-decompose-ref.yaml @@ -0,0 +1,8 @@ +root: + items: + - image: xy-gradient(500, 50) + bounds: 0 0 800 800 + stretch-size: 50 50 + - image: xy-gradient(50, 500) + bounds: 800 0 800 800 + stretch-size: 50 50 diff --git a/third_party/webrender/wrench/reftests/image/tile-repeat-prim-or-decompose.yaml b/third_party/webrender/wrench/reftests/image/tile-repeat-prim-or-decompose.yaml new file mode 100644 index 00000000000..43b12bbd94a --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/tile-repeat-prim-or-decompose.yaml @@ -0,0 +1,17 @@ +# This test aims at exercising the different ways we handle repetition of tiled images. +root: + items: + # This should cause the primitive repetition to be decomposed on the cpu along the x axis + # but perform the repetition along the y axis on the image shader because the image width + # fits within the tile size. + - image: xy-gradient(500, 50) + bounds: 0 0 800 800 + stretch-size: 50 50 + tile-size: 50 + # This should cause the primitive repetition to be decomposed on the cpu along the y axis + # but perform the repetition along the x axis in the image shader because the image height + # fits within the tile size. + - image: xy-gradient(50, 500) + bounds: 800 0 800 800 + stretch-size: 50 50 + tile-size: 50 diff --git a/third_party/webrender/wrench/reftests/image/tile-size-ref.yaml b/third_party/webrender/wrench/reftests/image/tile-size-ref.yaml new file mode 100644 index 00000000000..c90ea8a3419 --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/tile-size-ref.yaml @@ -0,0 +1,14 @@ +root: + items: + - image: xy-gradient(512, 512) + bounds: 0 0 512 512 + stretch-size: 512 512 + - image: xy-gradient(512, 512) + bounds: 512 0 512 512 + stretch-size: 512 512 + - image: xy-gradient(512, 512) + bounds: 0 512 512 512 + stretch-size: 512 512 + - image: xy-gradient(512, 512) + bounds: 512 512 512 512 + stretch-size: 512 512 diff --git a/third_party/webrender/wrench/reftests/image/tile-size.yaml b/third_party/webrender/wrench/reftests/image/tile-size.yaml new file mode 100644 index 00000000000..e17a7b53a4c --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/tile-size.yaml @@ -0,0 +1,19 @@ +root: + items: + - image: xy-gradient(512, 512) + bounds: 0 0 512 512 + stretch-size: 512 512 + tile-size: 64 + - image: xy-gradient(512, 512) + bounds: 512 0 512 512 + stretch-size: 512 512 + tile-size: 128 + - image: xy-gradient(512, 512) + bounds: 0 512 512 512 + stretch-size: 512 512 + tile-size: 256 + # tile size bigger than the image itself + - image: xy-gradient(512, 512) + bounds: 512 512 512 512 + stretch-size: 512 512 + tile-size: 4096 diff --git a/third_party/webrender/wrench/reftests/image/tile-with-spacing-ref.yaml b/third_party/webrender/wrench/reftests/image/tile-with-spacing-ref.yaml new file mode 100644 index 00000000000..63e1315f0b3 --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/tile-with-spacing-ref.yaml @@ -0,0 +1,6 @@ +root: + items: + - image: solid-color(255, 0, 0, 255, 300, 300) + bounds: 0 0 800 800 + stretch-size: 200 200 + tile-spacing: 10 10 diff --git a/third_party/webrender/wrench/reftests/image/tile-with-spacing.yaml b/third_party/webrender/wrench/reftests/image/tile-with-spacing.yaml new file mode 100644 index 00000000000..40bc5802d4c --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/tile-with-spacing.yaml @@ -0,0 +1,12 @@ +root: + items: + # TODO: This test would be more useful if we used an image with variations instead + # of a solid color. To do this we first need to change the way pixel snapping is + # applied so that when an image is split into several primitives, so that the latter + # all get snapped the same way rather than independently. + #- image: xy-gradient(300, 300) + - image: solid-color(255, 0, 0, 255, 300, 300) + bounds: 0 0 800 800 + stretch-size: 200 200 + tile-spacing: 10 10 + tile-size: 64 diff --git a/third_party/webrender/wrench/reftests/image/tiled-clip-chain-ref.yaml b/third_party/webrender/wrench/reftests/image/tiled-clip-chain-ref.yaml new file mode 100644 index 00000000000..83b44942694 --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/tiled-clip-chain-ref.yaml @@ -0,0 +1,10 @@ +root: + items: + - image: checkerboard(2, 16, 16) + bounds: [10, 10, 260, 260] + - type: rect + bounds: [10, 110, 200, 200] + color: white + - type: rect + bounds: [110, 10, 200, 270] + color: white \ No newline at end of file diff --git a/third_party/webrender/wrench/reftests/image/tiled-clip-chain.yaml b/third_party/webrender/wrench/reftests/image/tiled-clip-chain.yaml new file mode 100644 index 00000000000..fdc71577535 --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/tiled-clip-chain.yaml @@ -0,0 +1,10 @@ +# Test that local clip rects from clip-chains are correctly +# propagated into the local clip rect for tiled images. +root: + items: + - type: clip + bounds: [10, 10, 100, 100] + items: + - image: checkerboard(2, 16, 16) + bounds: [10, 10, 260, 260] + tile-size: 64 \ No newline at end of file diff --git a/third_party/webrender/wrench/reftests/image/tiled-complex-clip-ref.yaml b/third_party/webrender/wrench/reftests/image/tiled-complex-clip-ref.yaml new file mode 100644 index 00000000000..9350da23724 --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/tiled-complex-clip-ref.yaml @@ -0,0 +1,10 @@ +root: + items: + - type: clip + bounds: [10, 10, 100, 100] + complex: + - rect: [10, 10, 100, 100] + radius: 32 + items: + - image: checkerboard(2, 16, 16) + bounds: [10, 10, 260, 260] diff --git a/third_party/webrender/wrench/reftests/image/tiled-complex-clip.yaml b/third_party/webrender/wrench/reftests/image/tiled-complex-clip.yaml new file mode 100644 index 00000000000..83ad3f1a8fe --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/tiled-complex-clip.yaml @@ -0,0 +1,13 @@ +# Test that complex clips from clip-chains are correctly +# taken into account for tiled images. +root: + items: + - type: clip + bounds: [10, 10, 100, 100] + complex: + - rect: [10, 10, 100, 100] + radius: 32 + items: + - image: checkerboard(2, 16, 16) + bounds: [10, 10, 260, 260] + tile-size: 64 diff --git a/third_party/webrender/wrench/reftests/image/very-big-ref.yaml b/third_party/webrender/wrench/reftests/image/very-big-ref.yaml new file mode 100644 index 00000000000..6d7ea1b4b1c --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/very-big-ref.yaml @@ -0,0 +1,5 @@ +root: + items: + - type: rect + bounds: 0 0 500 500 + color: red diff --git a/third_party/webrender/wrench/reftests/image/very-big-tile-size-ref.yaml b/third_party/webrender/wrench/reftests/image/very-big-tile-size-ref.yaml new file mode 100644 index 00000000000..5001c2812c4 --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/very-big-tile-size-ref.yaml @@ -0,0 +1,5 @@ +root: + items: + - type: rect + bounds: 0 0 500 500 + color: green diff --git a/third_party/webrender/wrench/reftests/image/very-big-tile-size.yaml b/third_party/webrender/wrench/reftests/image/very-big-tile-size.yaml new file mode 100644 index 00000000000..d3bb3b8ec7a --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/very-big-tile-size.yaml @@ -0,0 +1,9 @@ +root: + items: + - type: rect + bounds: 0 0 500 500 + color: green + - image: solid-color(255, 0, 0, 255, 100000, 1000) + bounds: 0 0 500 500 + stretch-size: 1000000 1000 + tile-size: 60000 diff --git a/third_party/webrender/wrench/reftests/image/very-big.yaml b/third_party/webrender/wrench/reftests/image/very-big.yaml new file mode 100644 index 00000000000..dc0981d3a44 --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/very-big.yaml @@ -0,0 +1,5 @@ +root: + items: + - image: solid-color(255, 0, 0, 255, 100000, 1000) + bounds: 0 0 500 500 + stretch-size: 1000000 1000 diff --git a/third_party/webrender/wrench/reftests/image/yuv.png b/third_party/webrender/wrench/reftests/image/yuv.png new file mode 100644 index 0000000000000000000000000000000000000000..56320e4a6ba8fdf7f4df0615367c2f61a0bdbe85 GIT binary patch literal 621771 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

    X8avG`gwTi={|?%+Fk2onV=C9WVo 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

    xh{$8zo3chp-#xK5LHSEh(WtR++)iOG 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

    @@ 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 =BZxoNCy2#v1ZYRcKQ1vyCtbouOj2 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 z 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

  • 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≠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 Q2Rw+Hl9poi3+s^o0UFA*t^fc4 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/image/yuv.yaml b/third_party/webrender/wrench/reftests/image/yuv.yaml new file mode 100644 index 00000000000..8120d93f8cc --- /dev/null +++ b/third_party/webrender/wrench/reftests/image/yuv.yaml @@ -0,0 +1,19 @@ +root: + items: + - type: yuv-image + format: planar + src-y: spacex-y.png + src-u: spacex-u.png + src-v: spacex-v.png + bounds: [10, 10, 427, 640] + + - type: yuv-image + format: interleaved + src: spacex-yuv.png + bounds: [447, 10, 427, 640] + + - type: yuv-image + format: nv12 + src-y: spacex-y.png + src-uv: spacex-uv.png + bounds: [887, 10, 427, 640] diff --git a/third_party/webrender/wrench/reftests/invalidation/one-rounded-rect-green.yaml b/third_party/webrender/wrench/reftests/invalidation/one-rounded-rect-green.yaml new file mode 100644 index 00000000000..2366913cbdc --- /dev/null +++ b/third_party/webrender/wrench/reftests/invalidation/one-rounded-rect-green.yaml @@ -0,0 +1,17 @@ +--- +root: + items: + - + bounds: 0 0 1000 1000 + type: stacking-context + cache: true + items: + - type: clip + bounds: [50, 50, 200, 200] + complex: + - rect: [50, 50, 200, 200] + radius: 8 + items: + - type: rect + bounds: 50 50 200 200 + color: green diff --git a/third_party/webrender/wrench/reftests/invalidation/one-rounded-rect.yaml b/third_party/webrender/wrench/reftests/invalidation/one-rounded-rect.yaml new file mode 100644 index 00000000000..988233e9ed2 --- /dev/null +++ b/third_party/webrender/wrench/reftests/invalidation/one-rounded-rect.yaml @@ -0,0 +1,17 @@ +--- +root: + items: + - + bounds: 0 0 1000 1000 + type: stacking-context + cache: true + items: + - type: clip + bounds: [50, 50, 200, 200] + complex: + - rect: [50, 50, 200, 200] + radius: 8 + items: + - type: rect + bounds: 50 50 200 200 + color: red diff --git a/third_party/webrender/wrench/reftests/invalidation/reftest.list b/third_party/webrender/wrench/reftests/invalidation/reftest.list new file mode 100644 index 00000000000..5925e61da96 --- /dev/null +++ b/third_party/webrender/wrench/reftests/invalidation/reftest.list @@ -0,0 +1 @@ +#dirty([(50,50):256x256]) one-rounded-rect.yaml dirty([(50,50):256x256]) one-rounded-rect-green.yaml dirty([(50,50):256x256]) one-rounded-rect.yaml == one-rounded-rect.yaml diff --git a/third_party/webrender/wrench/reftests/invalidation/rounded-rects.yaml b/third_party/webrender/wrench/reftests/invalidation/rounded-rects.yaml new file mode 100644 index 00000000000..8c9b0db2317 --- /dev/null +++ b/third_party/webrender/wrench/reftests/invalidation/rounded-rects.yaml @@ -0,0 +1,42 @@ +--- +root: + items: + - + bounds: 0 0 1000 1000 + type: stacking-context + cache: true + items: + - type: clip + bounds: [50, 50, 200, 200] + complex: + - rect: [50, 50, 200, 200] + radius: 8 + items: + - type: rect + bounds: 50 50 200 200 + color: red + + - type: clip + bounds: [270, 50, 200, 200] + complex: + - rect: [270, 50, 200, 200] + radius: [16, 32, 48, 64] + items: + - type: rect + bounds: 270 50 200 200 + color: green + + - type: clip + bounds: [490, 50, 500, 500] + complex: + - rect: [490, 50, 500, 500] + radius: { + top-left: [32, 16], + top-right: [40, 24], + bottom-left: [48, 64], + bottom-right: [52, 80], + } + items: + - type: rect + bounds: 490 50 500 500 + color: blue diff --git a/third_party/webrender/wrench/reftests/invalidation/two-rounded-rects.yaml b/third_party/webrender/wrench/reftests/invalidation/two-rounded-rects.yaml new file mode 100644 index 00000000000..315d6434cdc --- /dev/null +++ b/third_party/webrender/wrench/reftests/invalidation/two-rounded-rects.yaml @@ -0,0 +1,27 @@ +--- +root: + items: + - + bounds: 0 0 1000 1000 + type: stacking-context + cache: true + items: + - type: clip + bounds: [50, 50, 200, 200] + complex: + - rect: [50, 50, 200, 200] + radius: 8 + items: + - type: rect + bounds: 50 50 200 200 + color: red + + - type: clip + bounds: [270, 50, 200, 200] + complex: + - rect: [270, 50, 200, 200] + radius: [16, 32, 48, 64] + items: + - type: rect + bounds: 270 50 200 200 + color: green diff --git a/third_party/webrender/wrench/reftests/mask/aligned-layer-rect-ref.yaml b/third_party/webrender/wrench/reftests/mask/aligned-layer-rect-ref.yaml new file mode 100644 index 00000000000..d4ef946d7c0 --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/aligned-layer-rect-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: stacking-context + items: + - type: rect + bounds: [9, 9, 10, 10] + color: blue diff --git a/third_party/webrender/wrench/reftests/mask/aligned-layer-rect.yaml b/third_party/webrender/wrench/reftests/mask/aligned-layer-rect.yaml new file mode 100644 index 00000000000..a1c6e2f17e9 --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/aligned-layer-rect.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: scroll-frame + bounds: [9, 9, 10, 10] + content-size: [95, 88] + items: + - type: scroll-frame + bounds: [0, 0, 100, 100] + items: + - type: rect + bounds: [0, 0, 100, 100] + color: blue diff --git a/third_party/webrender/wrench/reftests/mask/checkerboard-tiling.yaml b/third_party/webrender/wrench/reftests/mask/checkerboard-tiling.yaml new file mode 100644 index 00000000000..782babbef6c --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/checkerboard-tiling.yaml @@ -0,0 +1,16 @@ +# Tests a tiled image mask with leftover tile offsets. +--- +root: + items: + - type: clip + bounds: [0, 0, 200, 200] + image-mask: + image: transparent-checkerboard(2, 16, 16) + rect: [0, 0, 200, 200] + repeat: false + # This is the point of the test, can't be a divisor of 200. + tile-size: 37 + items: + - type: rect + bounds: [0, 0, 200, 200] + color: blue diff --git a/third_party/webrender/wrench/reftests/mask/checkerboard.png b/third_party/webrender/wrench/reftests/mask/checkerboard.png new file mode 100644 index 0000000000000000000000000000000000000000..e8a05df95be5c0bbe94e87e8f1fb08cf78a0c85e GIT binary patch literal 11433 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/mask/checkerboard.yaml b/third_party/webrender/wrench/reftests/mask/checkerboard.yaml new file mode 100644 index 00000000000..980bdb13fea --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/checkerboard.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: clip + bounds: [0, 0, 200, 200] + image-mask: + image: transparent-checkerboard(2, 16, 16) + rect: [0, 0, 200, 200] + repeat: false + items: + - type: rect + bounds: [0, 0, 200, 200] + color: blue diff --git a/third_party/webrender/wrench/reftests/mask/green.yaml b/third_party/webrender/wrench/reftests/mask/green.yaml new file mode 100644 index 00000000000..0d3df605821 --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/green.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - type: rect + bounds: [0, 0, 95, 88] + color: green diff --git a/third_party/webrender/wrench/reftests/mask/mask-atomicity-ref.yaml b/third_party/webrender/wrench/reftests/mask/mask-atomicity-ref.yaml new file mode 100644 index 00000000000..81480856c02 --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/mask-atomicity-ref.yaml @@ -0,0 +1,17 @@ +--- +root: + items: + - type: rect + bounds: [25, 25, 100, 100] + color: red + - type: stacking-context + bounds: [0, 0, 200, 200] + filters: + - "opacity(0.5)" + items: + - type: rect + bounds: [0, 0, 100, 100] + color: blue + - type: rect + bounds: [50, 50, 100, 100] + color: green diff --git a/third_party/webrender/wrench/reftests/mask/mask-atomicity-tiling.yaml b/third_party/webrender/wrench/reftests/mask/mask-atomicity-tiling.yaml new file mode 100644 index 00000000000..f44b4ab3cec --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/mask-atomicity-tiling.yaml @@ -0,0 +1,25 @@ +--- +root: + items: + - type: rect + bounds: [25, 25, 100, 100] + color: red + - type: clip + bounds: [0, 0, 200, 200] + id: 2 + image-mask: + # premultiplied 0.5 alpha white(??) + image: solid-color(127,127,127,127,200,200) + rect: [0, 0, 200, 200] + tile-size: 10 + repeat: false + - type: stacking-context + bounds: [0, 0, 200, 200] + clip-node: 2 + items: + - type: rect + bounds: [0, 0, 100, 100] + color: blue + - type: rect + bounds: [50, 50, 100, 100] + color: green diff --git a/third_party/webrender/wrench/reftests/mask/mask-atomicity.yaml b/third_party/webrender/wrench/reftests/mask/mask-atomicity.yaml new file mode 100644 index 00000000000..269030e3615 --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/mask-atomicity.yaml @@ -0,0 +1,24 @@ +--- +root: + items: + - type: rect + bounds: [25, 25, 100, 100] + color: red + - type: clip + bounds: [0, 0, 200, 200] + id: 2 + image-mask: + # premultiplied 0.5 alpha white(??) + image: solid-color(127,127,127,127,200,200) + rect: [0, 0, 200, 200] + repeat: false + - type: stacking-context + bounds: [0, 0, 200, 200] + clip-node: 2 + items: + - type: rect + bounds: [0, 0, 100, 100] + color: blue + - type: rect + bounds: [50, 50, 100, 100] + color: green diff --git a/third_party/webrender/wrench/reftests/mask/mask-perspective-tiling.yaml b/third_party/webrender/wrench/reftests/mask/mask-perspective-tiling.yaml new file mode 100644 index 00000000000..bd0fad7af96 --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/mask-perspective-tiling.yaml @@ -0,0 +1,23 @@ +--- +root: + items: + - + type: stacking-context + perspective: 1 + perspective-origin: 0 0 + items: + - + type: stacking-context + transform: 10 0 0 0 0 10 0 0 0 0 10 0 0 0 -9 1 + items: + - type: clip + bounds: [0, 0, 95, 88] + image-mask: + image: "mask.png" + rect: [0, 0, 35, 35] + repeat: false + tile-size: 5 + items: + - type: rect + bounds: [0, 0, 95, 88] + color: blue diff --git a/third_party/webrender/wrench/reftests/mask/mask-perspective.png b/third_party/webrender/wrench/reftests/mask/mask-perspective.png new file mode 100644 index 0000000000000000000000000000000000000000..02c0cc93ba39dcf25e8dea83313b699c4bf95d13 GIT binary patch literal 311 zcmeAS@N?(olHy`uVBq!ia0vp^r9d3W!3HGXSiYPCr2cujIEGZrd3$l8U~>QiOW>me zNiX+nWgU1t$)EMXj5mAK^Ul40Ztb(ALrNuU!V1SlY@HBB|Dt)%XRtr3gTe~DWM4fkOy|+ literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/mask/mask-perspective.yaml b/third_party/webrender/wrench/reftests/mask/mask-perspective.yaml new file mode 100644 index 00000000000..e8d8e4d5d44 --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/mask-perspective.yaml @@ -0,0 +1,22 @@ +--- +root: + items: + - + type: stacking-context + perspective: 1 + perspective-origin: 0 0 + items: + - + type: stacking-context + transform: 10 0 0 0 0 10 0 0 0 0 10 0 0 0 -9 1 + items: + - type: clip + bounds: [0, 0, 95, 88] + image-mask: + image: "mask.png" + rect: [0, 0, 35, 35] + repeat: false + items: + - type: rect + bounds: [0, 0, 95, 88] + color: blue diff --git a/third_party/webrender/wrench/reftests/mask/mask-ref.yaml b/third_party/webrender/wrench/reftests/mask/mask-ref.yaml new file mode 100644 index 00000000000..01c4e60946b --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/mask-ref.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 95, 88] + items: + - type: rect + bounds: [9, 9, 10, 10] + color: blue diff --git a/third_party/webrender/wrench/reftests/mask/mask-tiling.yaml b/third_party/webrender/wrench/reftests/mask/mask-tiling.yaml new file mode 100644 index 00000000000..021ec9fa288 --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/mask-tiling.yaml @@ -0,0 +1,14 @@ +--- +root: + items: + - type: clip + bounds: [0, 0, 95, 88] + image-mask: + image: "mask.png" + rect: [0, 0, 35, 35] + repeat: false + tile-size: 5 + items: + - type: rect + bounds: [0, 0, 95, 88] + color: blue diff --git a/third_party/webrender/wrench/reftests/mask/mask-transformed-to-empty-rect-ref.yaml b/third_party/webrender/wrench/reftests/mask/mask-transformed-to-empty-rect-ref.yaml new file mode 100644 index 00000000000..1df66453d6b --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/mask-transformed-to-empty-rect-ref.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - type: rect + bounds: [0, 0, 100, 100] + color: 0 255 0 1.0 diff --git a/third_party/webrender/wrench/reftests/mask/mask-transformed-to-empty-rect.yaml b/third_party/webrender/wrench/reftests/mask/mask-transformed-to-empty-rect.yaml new file mode 100644 index 00000000000..a291c1fb96a --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/mask-transformed-to-empty-rect.yaml @@ -0,0 +1,26 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 300, 300] + "scroll-policy": scrollable + z-index: 4 + transform: [1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 150, -150, 1] + items: + - type: clip + bounds: [0, 0, 300, 300] + # This image mask here assures that we will be forced to try to + # mask instead of skipping it due to the mask rect becoming a + # zero rect. + image-mask: + image: "tiny-check-mask.png" + rect: [0, 0, 300, 300] + repeat: false + id: 2 + items: + - type: rect + bounds: [0, 0, 300, 300] + color: 0 128 0 1.0000 + - type: rect + bounds: [0, 0, 100, 100] + color: 0 255 0 1.0 diff --git a/third_party/webrender/wrench/reftests/mask/mask.png b/third_party/webrender/wrench/reftests/mask/mask.png new file mode 100644 index 0000000000000000000000000000000000000000..ab1bdb9b50d81079625804e576a4a0dba47c5c71 GIT binary patch literal 187 zcmeAS@N?(olHy`uVBq!ia0vp^${@_a1SBW!R^bCuoCO|{#S9GG!XV7ZFl&wkP>{XE z)7O>#AtSdCA4f^J+!UaYY-UJAiF1B#Zfaf$kjuc}T$GwvlA5AWo>`Ki;O^-gkfN8$ z4iq=^ba4#vIR5thK~4q*9+rbg{#DyqbNmpTGV{IQv=GMkFB83uV$0;GUfVi<%9JS# a5Avng_%Ms~+Gi{Qnd<54=d#Wzp$P!-?K3F= literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/mask/mask.yaml b/third_party/webrender/wrench/reftests/mask/mask.yaml new file mode 100644 index 00000000000..b7432f58274 --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/mask.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: clip + bounds: [0, 0, 95, 88] + image-mask: + image: "mask.png" + rect: [0, 0, 35, 35] + repeat: false + items: + - type: rect + bounds: [0, 0, 95, 88] + color: blue diff --git a/third_party/webrender/wrench/reftests/mask/missing-mask-ref.yaml b/third_party/webrender/wrench/reftests/mask/missing-mask-ref.yaml new file mode 100644 index 00000000000..112da9c9d10 --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/missing-mask-ref.yaml @@ -0,0 +1,7 @@ +# Don't crash when supplied an invalid image key for the mask! +--- +root: + items: + - type: rect + bounds: [0, 0, 35, 35] + color: blue diff --git a/third_party/webrender/wrench/reftests/mask/missing-mask.yaml b/third_party/webrender/wrench/reftests/mask/missing-mask.yaml new file mode 100644 index 00000000000..124c2cd8930 --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/missing-mask.yaml @@ -0,0 +1,14 @@ +# Don't crash when supplied an invalid image key for the mask! +--- +root: + items: + - type: clip + bounds: [0, 0, 95, 88] + image-mask: + image: invalid + rect: [0, 0, 35, 35] + repeat: false + items: + - type: rect + bounds: [0, 0, 95, 88] + color: blue diff --git a/third_party/webrender/wrench/reftests/mask/nested-mask-ref.yaml b/third_party/webrender/wrench/reftests/mask/nested-mask-ref.yaml new file mode 100644 index 00000000000..c16df79e4cf --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/nested-mask-ref.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 95, 88] + items: + - type: rect + bounds: [13, 13, 6, 6] + color: blue diff --git a/third_party/webrender/wrench/reftests/mask/nested-mask-tiling.yaml b/third_party/webrender/wrench/reftests/mask/nested-mask-tiling.yaml new file mode 100644 index 00000000000..0e70758f582 --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/nested-mask-tiling.yaml @@ -0,0 +1,22 @@ +--- +root: + items: + - type: clip + bounds: [0, 0, 95, 88] + image-mask: + image: "mask.png" + rect: [0, 0, 35, 35] + repeat: false + tile-size: 5 + items: + - type: clip + bounds: [0, 0, 95, 88] + image-mask: + image: "mask.png" + rect: [4, 4, 35, 35] + repeat: false + tile-size: 5 + items: + - type: rect + bounds: [0, 0, 95, 88] + color: blue diff --git a/third_party/webrender/wrench/reftests/mask/nested-mask.yaml b/third_party/webrender/wrench/reftests/mask/nested-mask.yaml new file mode 100644 index 00000000000..cacbb634346 --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/nested-mask.yaml @@ -0,0 +1,20 @@ +--- +root: + items: + - type: clip + bounds: [0, 0, 95, 88] + image-mask: + image: "mask.png" + rect: [0, 0, 35, 35] + repeat: false + items: + - type: clip + bounds: [0, 0, 95, 88] + image-mask: + image: "mask.png" + rect: [4, 4, 35, 35] + repeat: false + items: + - type: rect + bounds: [0, 0, 95, 88] + color: blue diff --git a/third_party/webrender/wrench/reftests/mask/out-of-bounds.yaml b/third_party/webrender/wrench/reftests/mask/out-of-bounds.yaml new file mode 100644 index 00000000000..8e0eed09e00 --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/out-of-bounds.yaml @@ -0,0 +1,16 @@ +--- +root: + items: + - type: clip + bounds: [0, 0, 10000, 10000] + complex: + - rect: [0, 0, 10000, 10000] + radius: + top-left: [30, 30] + top-right: [30, 30] + bottom-right: [30, 30] + bottom-left: [30, 30] + items: + - type: rect + bounds: [0, 0, 10000, 10000] + color: green diff --git a/third_party/webrender/wrench/reftests/mask/reftest.list b/third_party/webrender/wrench/reftests/mask/reftest.list new file mode 100644 index 00000000000..7a18b4a0108 --- /dev/null +++ b/third_party/webrender/wrench/reftests/mask/reftest.list @@ -0,0 +1,16 @@ +== mask.yaml mask-ref.yaml +== mask-tiling.yaml mask-ref.yaml +== nested-mask.yaml nested-mask-ref.yaml +== nested-mask-tiling.yaml nested-mask-ref.yaml +!= mask.yaml green.yaml +== aligned-layer-rect.yaml aligned-layer-rect-ref.yaml +== mask-transformed-to-empty-rect.yaml mask-transformed-to-empty-rect-ref.yaml +platform(linux,mac) == rounded-corners.yaml rounded-corners.png +!= mask.yaml out-of-bounds.yaml +platform(linux,mac) fuzzy(1,17500) color_targets(3) alpha_targets(1) == mask-atomicity.yaml mask-atomicity-ref.yaml +platform(linux,mac) fuzzy(1,17500) == mask-atomicity-tiling.yaml mask-atomicity-ref.yaml +platform(linux,mac) == mask-perspective.yaml mask-perspective.png +== fuzzy(1,11) mask-perspective-tiling.yaml mask-perspective.yaml +platform(linux,mac) == checkerboard.yaml checkerboard.png +skip_on(android,device) == checkerboard.yaml checkerboard-tiling.yaml # Fails on a Pixel2 +== missing-mask.yaml missing-mask-ref.yaml diff --git a/third_party/webrender/wrench/reftests/mask/rounded-corners.png b/third_party/webrender/wrench/reftests/mask/rounded-corners.png new file mode 100644 index 0000000000000000000000000000000000000000..19adfe02ec87279d9df7f04fefe84eb9357309e4 GIT binary patch literal 1720 zcmcgtSyU1T6vYKC2`Ry}+)GNyG^1S65yXTv6U8M_Y_c-bK}oa76)Y9g)YP1eT+Qu+~N`0002S zG5ZMm+#f*+Z?TBu!MifgS)%*4)ag9c zc#>J1xz^9}kY^tAtOs0Yckxwy%qrU~u%F$=syb8jp%8t&d%Byf5H4*^{QBYeY2K;n zB;^1OGf6eOSB`hL!& z=n81=3vg~ibB&Q!t`P{Pr?zeB!YzGxv0SQ92V!C)A`bFVV|g=xo&Y zIzU^BT3aem*sfWQQlY$8r3e!$&`B^Dz@GBpwW&3x%Q@9B_)#$oTw7$9j@BVlI4zkw)WQ+A5^)_5sGLtPIV?xw7$)?Qdd#TyNUPU5 z^VKpeOT9x6Tz1A5D74QTq!WeYw`?RMp!3v>BuJw0^yEQ@TndiLQK*|XI~>5*E-FKo!(bdUZaJ|cD^@JF)Y zb+&Az%B@RZ%w6!0LYV{~z@u>?5-J3_HEZvBJ)6YV&?6&p)(NsuEvvG!4kd~tdEY+W z_*UK#vDWy!#pNvS8`Pz59ns01xmBj>+Z7BaL}=bar)`(B!uKzhDRgSZA9vXsK#Rvy zm)WzgxF>g^OdOIOfa_5i&@E9@{d|K6<<6F_+=|LNWK0w9X2b5I-LM=~amjckio(#C zYH8s}x?tC`{R58VUe)FigBR6ndpqiSSbcsGlU;%2V>L+4nGU2jdq)pw|GRQWcXW<} zhexLz5or@Us;Z{pTluW>@hJ|64m&Lx;6qIO3O4zm>9*hIAU3CXp-0(qjYXyCGasU= zAtn~MbzANE`U~zRln!sEK~gZsgfc&q!GKknDC-sBgpZs{+_SWAbK>RUrJ!O5UmAn( z{{l}cr&RN(0tF_vx~MdAO>C{iLwZD$WE{Jplay7vS0s!;*f_7Or(FVrkM)YVFq$uI z@X4j1;IVRK1;PiaZQ`Qe?)KYhJSSa z(Bx3kPFAKy#H7*l0CHlpbcW_LKM%Wx>#5RbS0scPl`K~(?qmd!slPp*PS6jLObNla z!}r(zbb~!9D!Cu6*Gw@2njI_T8Zc%FHLHo_M0vM(Gj0m}bhyUDIOX}|Ag5#-Y^_We zPSH^ueW_|4UT13Fp!j3QD7J~$TK#MvhAbc-Wdzc^tHlasj?Pvqo4uZJv;mDdr2Kn4 z-VibLBH|C?(hkdwaHnzC=n9mKmO1s+;w?^jU_oMe;$LlIn&)el>guMq#^I?S&S3`z z#=#tavx{tX7D)Qsi`5%AVve-glv7r}#w$dj(~Mo-@71fBxcjv01yXek-m738ZS@03 zDU$#C>cnD8Vf5hQyln6j)jq%L#SN6r%DK+aY{Zg*i{E$4=aKRy=QsW8f|V?diXbTx z3BctHAZ0-{L-X;~Tl2I*k#k-kv7D(<>&pI+Nra14%JOUFF`$sRr;B3<$92|&jEq2DlfrNNSqdxG aH1UNV<@lLpdSDJvkipZ{&t;ucLK6VA7$JWE literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/performance/compositor-surface-opaque-slice-ref.yaml b/third_party/webrender/wrench/reftests/performance/compositor-surface-opaque-slice-ref.yaml new file mode 100644 index 00000000000..7c538488bcb --- /dev/null +++ b/third_party/webrender/wrench/reftests/performance/compositor-surface-opaque-slice-ref.yaml @@ -0,0 +1,9 @@ +root: + items: + - + bounds: [0, 0, 200, 200] + image: checkerboard(2, 16, 12) + stretch-size: 200 200 + - type: rect + bounds: [0, 0, 200, 200] + color: [255, 0, 0, 0.5] diff --git a/third_party/webrender/wrench/reftests/performance/compositor-surface-opaque-slice.yaml b/third_party/webrender/wrench/reftests/performance/compositor-surface-opaque-slice.yaml new file mode 100644 index 00000000000..a5821973e9a --- /dev/null +++ b/third_party/webrender/wrench/reftests/performance/compositor-surface-opaque-slice.yaml @@ -0,0 +1,10 @@ +root: + items: + - + bounds: [0, 0, 200, 200] + image: checkerboard(2, 16, 12) + stretch-size: 200 200 + prefer-compositor-surface: true + - type: rect + bounds: [0, 0, 200, 200] + color: [255, 0, 0, 0.5] diff --git a/third_party/webrender/wrench/reftests/performance/no-clip-mask.png b/third_party/webrender/wrench/reftests/performance/no-clip-mask.png new file mode 100644 index 0000000000000000000000000000000000000000..1fc4ef0483af62ad5115d388d58322c310498e6c GIT binary patch literal 2776 zcmeAS@N?(olHy`uVBq!ia0y~yU} zAXk^Ci(^Q|oVT|P`;;vOTn|27^iKDl&GBiMFErdx+oFF+JK)%M`Jn8yXYxQ37$*JO zYYUUe`uX?!Ge2&P;bQ~}v~)Uy^d&wL0Fn+fELeb)fEpI58?$Ea zv<0P;2g%uc>%FDjN!4Fm4b;qVpmXo{i`8>ala?E!jg7avgS6M=to@#!`M85b{ndH= zAl()lzuQ%mdKI;Vst0NVI2 A=l}o! literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/performance/no-clip-mask.yaml b/third_party/webrender/wrench/reftests/performance/no-clip-mask.yaml new file mode 100644 index 00000000000..51d981e2a07 --- /dev/null +++ b/third_party/webrender/wrench/reftests/performance/no-clip-mask.yaml @@ -0,0 +1,24 @@ +# In this case, there is no incompatible transform. +# Therefore, the clip condition should be handled +# by the vertex shader via the local_clip_rect, and +# there should be no clip mask generated. +--- +root: + items: + - + type: "stacking-context" + items: + - + bounds: [0, 111, 1087, 565] + "clip-rect": [0, 111, 1087, 565] + type: iframe + id: [1, 2] +pipelines: + - + id: [1, 2] + items: + - + bounds: [1075, -1, 12, 199] + "clip-rect": [1075, -1, 12, 199] + image: checkerboard(4, 8, 8) + stretch-size: 72 72 diff --git a/third_party/webrender/wrench/reftests/performance/reftest.list b/third_party/webrender/wrench/reftests/performance/reftest.list new file mode 100644 index 00000000000..5ca69146dc2 --- /dev/null +++ b/third_party/webrender/wrench/reftests/performance/reftest.list @@ -0,0 +1,2 @@ +skip_on(android) == color_targets(2) alpha_targets(0) no-clip-mask.yaml no-clip-mask.png # Too wide for Android +platform(linux,mac) == compositor-surface-opaque-slice.yaml compositor-surface-opaque-slice-ref.yaml diff --git a/third_party/webrender/wrench/reftests/reftest.list b/third_party/webrender/wrench/reftests/reftest.list new file mode 100644 index 00000000000..a3194332021 --- /dev/null +++ b/third_party/webrender/wrench/reftests/reftest.list @@ -0,0 +1,18 @@ +include aa/reftest.list +include backface/reftest.list +include blend/reftest.list +include border/reftest.list +include boxshadow/reftest.list +include clip/reftest.list +include filters/reftest.list +include gradient/reftest.list +include image/reftest.list +include invalidation/reftest.list +include mask/reftest.list +include performance/reftest.list +include scrolling/reftest.list +include snap/reftest.list +include split/reftest.list +include text/reftest.list +include transforms/reftest.list +include tiles/reftest.list diff --git a/third_party/webrender/wrench/reftests/scrolling/clip-and-scroll-property-ref.yaml b/third_party/webrender/wrench/reftests/scrolling/clip-and-scroll-property-ref.yaml new file mode 100644 index 00000000000..a465f412dfe --- /dev/null +++ b/third_party/webrender/wrench/reftests/scrolling/clip-and-scroll-property-ref.yaml @@ -0,0 +1,5 @@ +root: + items: + - type: rect + bounds: [0, 0, 200, 200] + color: green diff --git a/third_party/webrender/wrench/reftests/scrolling/clip-and-scroll-property.yaml b/third_party/webrender/wrench/reftests/scrolling/clip-and-scroll-property.yaml new file mode 100644 index 00000000000..ed72c23fcea --- /dev/null +++ b/third_party/webrender/wrench/reftests/scrolling/clip-and-scroll-property.yaml @@ -0,0 +1,30 @@ +--- +root: + items: + - + bounds: [0, 0, 200, 200] + type: "stacking-context" + "scroll-policy": scrollable + items: + - + bounds: [0, 0, 200, 200] + type: clip + id: 2 + # Here we are testing that the clip-and-scroll property applies to + # both stacking contexts and items. + - + bounds: [0, 0, 0, 0] + content-size: [200, 200] + clip-and-scroll: 2 + type: "stacking-context" + "scroll-policy": scrollable + transform: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -100, 0, 0, 1] + "transform-style": flat + items: + - + bounds: [100, 0, 200, 200] + clip: [-8947849, -8947849, 17895698, 17895698] + type: rect + color: green + id: [0, 0] +pipelines: [] diff --git a/third_party/webrender/wrench/reftests/scrolling/empty-mask-ref.yaml b/third_party/webrender/wrench/reftests/scrolling/empty-mask-ref.yaml new file mode 100644 index 00000000000..aca8ccd66cf --- /dev/null +++ b/third_party/webrender/wrench/reftests/scrolling/empty-mask-ref.yaml @@ -0,0 +1,5 @@ +root: + items: + - type: rect + bounds: [0, 0, 100, 100] + color: green diff --git a/third_party/webrender/wrench/reftests/scrolling/empty-mask.yaml b/third_party/webrender/wrench/reftests/scrolling/empty-mask.yaml new file mode 100644 index 00000000000..69378dd5ad8 --- /dev/null +++ b/third_party/webrender/wrench/reftests/scrolling/empty-mask.yaml @@ -0,0 +1,18 @@ +root: + items: + - type: rect + bounds: [0, 0, 100, 100] + color: green + - type: scroll-frame + bounds: [0, 0, 100, 100] + items: + - type: clip + bounds: [0, 0, 0, 0] + content-size: [100, 100] + complex: + - rect: [0, 0, 100, 100] + radius: 20 + items: + - type: rect + bounds: [0, 0, 500, 500] + color: red diff --git a/third_party/webrender/wrench/reftests/scrolling/ext-scroll-offset-1-ref.yaml b/third_party/webrender/wrench/reftests/scrolling/ext-scroll-offset-1-ref.yaml new file mode 100644 index 00000000000..3b8d47e94c9 --- /dev/null +++ b/third_party/webrender/wrench/reftests/scrolling/ext-scroll-offset-1-ref.yaml @@ -0,0 +1,10 @@ +root: + items: + - type: scroll-frame + bounds: [0, 0, 100, 100] + content-size: [100, 1000] + scroll-offset: [0, -50] + items: + - type: rect + color: green + bounds: [0, 0, 100, 100] diff --git a/third_party/webrender/wrench/reftests/scrolling/ext-scroll-offset-1.yaml b/third_party/webrender/wrench/reftests/scrolling/ext-scroll-offset-1.yaml new file mode 100644 index 00000000000..7803574bede --- /dev/null +++ b/third_party/webrender/wrench/reftests/scrolling/ext-scroll-offset-1.yaml @@ -0,0 +1,14 @@ +# Test that items with an external scroll offset in both the +# scroll node and primitive match up with items that have +# been scrolled without an external scroll offset. +root: + items: + - type: scroll-frame + bounds: [0, 0, 100, 100] + content-size: [100, 1000] + scroll-offset: [0, 0] + external-scroll-offset: [0, 50] + items: + - type: rect + color: green + bounds: [0, 50, 100, 100] diff --git a/third_party/webrender/wrench/reftests/scrolling/fixed-position-ref.yaml b/third_party/webrender/wrench/reftests/scrolling/fixed-position-ref.yaml new file mode 100644 index 00000000000..aa263878281 --- /dev/null +++ b/third_party/webrender/wrench/reftests/scrolling/fixed-position-ref.yaml @@ -0,0 +1,14 @@ +root: + items: + - type: rect + bounds: [0, 0, 50, 50] + color: green + - type: rect + bounds: [60, 0, 50, 50] + color: green + - type: rect + bounds: [120, 0, 50, 50] + color: green + - type: rect + bounds: [180, 0, 50, 50] + color: green diff --git a/third_party/webrender/wrench/reftests/scrolling/fixed-position-scrolling-clip-ref.yaml b/third_party/webrender/wrench/reftests/scrolling/fixed-position-scrolling-clip-ref.yaml new file mode 100644 index 00000000000..eb503533142 --- /dev/null +++ b/third_party/webrender/wrench/reftests/scrolling/fixed-position-scrolling-clip-ref.yaml @@ -0,0 +1,5 @@ +root: + items: + - type: rect + bounds: [10, 10, 50, 50] + color: green diff --git a/third_party/webrender/wrench/reftests/scrolling/fixed-position-scrolling-clip.yaml b/third_party/webrender/wrench/reftests/scrolling/fixed-position-scrolling-clip.yaml new file mode 100644 index 00000000000..63573e5742f --- /dev/null +++ b/third_party/webrender/wrench/reftests/scrolling/fixed-position-scrolling-clip.yaml @@ -0,0 +1,28 @@ +root: + items: + - type: scroll-frame + bounds: [10, 10, 100, 300] + content-size: [100, 700] + id: 41 + scroll-offset: [0, 50] + items: + # The rectangles below should stay in place even when the parent scroll area scrolls, + # because they use the root reference frame as their scroll node (fixed position). + # On the other hand, the clip item here will scroll with its parent scroll area. Normally + # fixed position items would only be clipped by their reference frame (in this case the + # root), but since these items specify an auxiliary clip, they will be clipped by their + # sibling clip (42). + - type: clip + bounds: [10, 60, 50, 50] + id: 42 + - type: stacking-context + bounds: [10, 10, 100, 100] + items: + - type: rect + bounds: [0, 0, 100, 50] + color: green + clip-and-scroll: [root-reference-frame, 42] + - type: rect + bounds: [0, 50, 100, 50] + color: red + clip-and-scroll: [root-reference-frame, 42] diff --git a/third_party/webrender/wrench/reftests/scrolling/fixed-position.yaml b/third_party/webrender/wrench/reftests/scrolling/fixed-position.yaml new file mode 100644 index 00000000000..6fa099b5e37 --- /dev/null +++ b/third_party/webrender/wrench/reftests/scrolling/fixed-position.yaml @@ -0,0 +1,55 @@ +root: + bounds: [0, 0, 1024, 10000] + scroll-offset: [0, 100] + items: + - type: stacking-context + bounds: [0, 0, 50, 50] + items: + # This item should not scroll out of view because it is fixed position. + - type: rect + bounds: [0, 0, 50, 50] + color: green + clip-and-scroll: root-reference-frame + - type: stacking-context + bounds: [0, 0, 50, 50] + transform: translate(60, 100) + id: 100 + items: + - type: stacking-context + bounds: [0, 0, 50, 50] + items: + # Even though there is a custom clip-scroll ID, it should scroll, + # because it is fixed relative to its reference frame. The reference frame + # of this stacking context is the stacking context parent because it has + # a transformation. + - type: rect + bounds: [0, 0, 50, 50] + color: green + clip-and-scroll: 100 + - type: stacking-context + bounds: [120, 0, 50, 200] + transform: translate(0, 0) + id: 101 + items: + # This is similar to the previous case, but ensures that this still works + # even with an identity transform. + - type: stacking-context + bounds: [0, 0, 50, 200] + items: + - type: rect + bounds: [0, 100, 50, 50] + color: green + clip-and-scroll: 101 + - type: stacking-context + bounds: [180, 0, 50, 200] + perspective: 1 + id: 102 + items: + # This is similar to the previous case, but for perspective. + - type: stacking-context + bounds: [0, 0, 50, 200] + items: + - type: rect + bounds: [0, 100, 50, 50] + color: green + clip-and-scroll: 102 diff --git a/third_party/webrender/wrench/reftests/scrolling/mask.png b/third_party/webrender/wrench/reftests/scrolling/mask.png new file mode 100644 index 0000000000000000000000000000000000000000..d3cbfe6a63fbeb6a5fc9a1f770f9c2629070f9f8 GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^DImFdh=keQK7L-+Sl4r`#0Y-UJAiF1B#Zfaf$kjuc}T$GwvlA5AWo>`Ki;O^-gkfN8$ z4iul@>EaktaqI1+jl2vBJj@4M{+63byKE8szIONI)heEoRNk(9Zh10Z#I!TTf{PXr2p~d22*mYL zYF!dqXGI}EOV>~=f|SZdkXSE?ESJcp2uMPpQD`6+4wqoaZJ)y~%=jnlY<|eho8+AH zzUO(r&*%F+=hBB!`yJLhtw#tsL>ve^gph3seCq9g3BPFmarh=ePKgm=d;Y}Wsb!Jh z#GDwtkHyPp)ptsFpDH~R{xtQi&Fd%^b_8wM^QGSxPxpMd_s1`JTf^*J-WWVkWB+DQ zY25i8guxx3g?~ax|I4O7K8@Jt(3PnUVSc3*Pt6aHkIWhmga6klB`aVByMS^+y)H=LaO@Dm5g5rk&Nn@Vo)-Sv248~2%RlBOwH#K|7mw~azTC@U|_qkfY(WJ$^}>!(DS0+WA6#^Ur~^89395B;6SuH|Xv z8wgDjoR0nAX}YA5bOT#S%c8PPi*4$bQDLLN z9F&nlQ*soMUiW<6*hXyanCM{a(QtE$*td_?SMH~nop`ADY-htYe}t}8p1iP*KHo>= zdI^lD%O{%`!UxN!Mfv3jCFRv@-YUJgEiqvJ>pDw@gilexZCC&9zbCaWe5zDRYHL$N zBfTC3=Bb8#ZP0yHn8&WR8$Xk>e|qHGm!d2vAzO^|Yk%x)>^$ufc=|X#&E#NosQ&oc zH0K>d1ICIB>eO9H7CYT3yPZ;!(r{bsZ!!@0U>S^2%m z(hs%ACMz;Ic5aNM`j76M3bOf6F@Ib1YNEI)ZK&a8meI5%ue~B5P4RMsM`9EFbgB^_ zO`C#)Z%sc3F8bmXrcgACL!Iq5y2KSNEc`t1b&ovjsG!Kyh6$ZTg-bG`rgOeOd*nnJ zCDs<*AK3!yMeh#SBlL$~TZh&Fx>cooL;9I)Tn+kJ%~`m3#-5Ec-+(LJ=Ylgd=yP>dC5T}gSWxGLD%>UW%M5IRt^ z8KJZ9{_hWCN);k5R~%|ccL~T!!mSM7VP0qboBPw@?BV zh1&C}NgS}&JedJdOF9cWg48f)3R(vam8@hZFBFM`!YM0S|=I^nIff=AicU+C& z&B+FxTJdAuVd#er&D}H>*l03D8VPLTium0?DVT0FGg`vJTMimv_o^ysO6*@A`#A?C zELw-U1?whM9|W27MuFzyJMz>4D^X&q=JM~IUds}GY0q}CFJ~u44zw@qb_HD(<3R@G z#DPGR3kf4t!pFeFqgOB10Zk!-AP`8I&sQ~xgb)*gSJ$AqM5x+cpi^Dvk9dYWXTF-9 z$bS5G?27jBbr_as8q4HogJwK#T9$_d!pmM=0k7q&NOleMiz|t);kEeCmt=G+y!qYO`*RDb|ux~^6jOD*)P zG(xY>2q6TDfB;toVu%kZk-iVsH^0TjW9eMwqYsKVdTN1BE=#cGa}|e(>BHU$>kzWM qY4upE$G+~d*6eA`;P?Z`GWGm7S?ccTf$dNa6tOQVtYL4`xqkz_lf}OP literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/snap/preserve-3d.yaml b/third_party/webrender/wrench/reftests/snap/preserve-3d.yaml new file mode 100644 index 00000000000..346e881d835 --- /dev/null +++ b/third_party/webrender/wrench/reftests/snap/preserve-3d.yaml @@ -0,0 +1,55 @@ +--- +root: + items: + - + bounds: [293, 139, 500, 500] + type: reference-frame + transform: scale(0.7) + items: + - + bounds: [0, 0, 500, 500] + type: stacking-context + perspective: [1, 0, 0, 0, 0, 1, 0, 0, -0.25416666, -0.23866667, 1, -0.00083333335, 0, 0, 0, 1] + "transform-style": preserve-3d + items: + - + bounds: [0, 0, 0, 0] + type: stacking-context + transform: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -110, 0, 0, 1] + items: + - + bounds: [24, 539, 24, 24] + type: clip + complex: + - + rect: [24, 539, 24, 24] + radius: [12, 12] + items: + - + bounds: [24, 539, 24, 24] + type: rect + color: black + - + bounds: [24, 770, 24, 24] + type: clip + complex: + - + rect: [24, 770, 24, 24] + radius: [12, 12] + items: + - + bounds: [24, 770, 24, 24] + type: rect + color: black + - + bounds: [24, 847, 24, 24] + type: clip + complex: + - + rect: [24, 847, 24, 24] + radius: [12, 12] + items: + - + bounds: [24, 847, 24, 24] + type: rect + color: black diff --git a/third_party/webrender/wrench/reftests/snap/reftest.list b/third_party/webrender/wrench/reftests/snap/reftest.list new file mode 100644 index 00000000000..84fe6c4979d --- /dev/null +++ b/third_party/webrender/wrench/reftests/snap/reftest.list @@ -0,0 +1,3 @@ +platform(linux,mac) == snap.yaml snap.png +== transform.yaml transform.png +platform(linux,mac) == preserve-3d.yaml preserve-3d.png diff --git a/third_party/webrender/wrench/reftests/snap/snap.png b/third_party/webrender/wrench/reftests/snap/snap.png new file mode 100644 index 0000000000000000000000000000000000000000..1f736082d84078f3fff8acfe270cd61bf144ad16 GIT binary patch literal 2885 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

    YoY{Tps(}N873!pmd$-^|tLKtcQ|SKo^_N|G zm3yjs*(F!G$(>A>&B&%mYt?G>HYdjbXjU8+1cwx%(7nYzeep%j$=uPh6W0om&)`U-k}WMU@DkNSv3qyEx)!xp{^NN&=FR+Kv&RaFlE zjmmQJt)uHpF2R*1e6#Lm%T=h_nI9C9F0`GvV9#)D|5V$_4p7@f_rjE&Xq)FrB0prf zAunZ_{^wZ6!Nu-7Prki^fEZkIIf7A0f-_)~kK|s;=UeAL)H$Q*#&GjpK9_Yb`H^aw4_$v@xG}e@N zImk!rdxVw4DtWyGu{8t$*E=c*BCCWO1n&i>FYI@Ey(Gb7SZT0byeLEZSAlIvML=A} zzU$oG-R|qeyZXxHwG}RB<=V+GKz;-*YcpQ{70Mf=Ud_yy>z%oU#);EP_B7fk!i`O; z2VemtvWd1VqR2NRn}KMJ)zmIuwXWp6uoMeKCfims^#ogKk4f3 zZylsasVjuq ztxX#U+5i!sK>aEqz4M5^kqc&k8alm_LdxB0RA1GKE}^(&@rJ!x$(9|K<)g5jS2dBs zavtBJwOYPz#kw%n0Und?p-a3P(#C`+RnGvG0^6eh5~+lgIs^BPxVXRm0S zw`bS9tUdScQTZo}`MWZ8-+~4E7HE!!TDJS%J&o1XjeG9h&1xIw?b&l3Rj}{_{)I5^ zmn~r;-oF|-xY~joom?y57*10Rr}h3kUgS*(>Vtgb_z$w!pC{S_Y1^TBB|oAX$e{4} z5Ly*4d=e38O>z-z71khw4pSt_rf{O+#NCW6LX`@S5~!nf>gK=Mm!C0fLUElwH@JdbfuA97ZRz|-6DS5RcBY*Ez&W>*ObNxbV_jDaU-sO3uq45!K z=kep6-bWf69x3haa9*`HUoq(y&0z|51q5h| zMOT>7u7m7DuA(}6Gx1R&Nlbi+9rm+3KX3bu`fS_h z!oghe_@7tG{VyvwNLuz?{4NX9EAbq`9~OxGethLqn#LlxGvDq8(kkEm^!~PQtCzLi z|7l`c$&4B`J-w->s4>?b70&!$JZ!(>^M~y{-RvVRnNzr3+Qsanv#X}7&CO~-Rd-cZ zWW%}Vo{OfqZ1fD;M*N}`E^}^B8F-T!++u<;I^chVfk{~FAvo>}KNzkeBTtqsCw#0( zoq&ZYsvdDXyCT^u*I$2B$!sd}M2!m95$2`o`9)FQmRB*Hy8J}?YWS5oWQ+vQZcdQ6 zE$sJ2=haVHzi@Cm(X`;Tfbm#(d=v-MZ{yz0V%$!k+{n#zaL{v3DV^aXqOE*QFy zrO?+FKRnYh+2yG#Y-)3qHVw>MT-07!m!~FAJA3Z~dpDt7u0v6lqaTz5mNLLskq)H@ zs9^;w>2aF$chU@51w$#=S7ekTQUv;scciK!F=>msgIHR;{z~QjlBLB3c@eCgOixv1 zng9DNnKQXC0~4=H_b8Mbg3PY&RRFeE#V2CTm`F$9;}(`tl6pX4Ko)wQ=1cH!&=!#X zKvx_f!#b%H=+_}S1g=WRnUs()sS$6zIb4^Zs}%lB`US}OLDhox3TYT>T&@~=k#>QV zae-b-VNS^RZ{EXYI>85g$m1q?v=jN7aQ6X15fE_r6hOM zJQw|*cqGs%x!1@&#x-`GPZ{=!21ETJ&U60?ZHteULTy1tWdb-yX!QYaxw8Q)E0Psc zsmukuvck{M2c4E2QVvkxt-^7gJPOfiFZ84|6u)AZtLkyM)BPPUwiIt4sOkr-$RJ11 z?@!bBh%xKb-9m*mS9`&P~awWei9o))&D-{U%RCU3!;uj?8=$ zg^xuV5aIa5fMs9tVbb2>MeH{Lozwqe<*&$Xz@zhdT0S!Mlh{t$-vpD{!m~sMpJ7nV z(h{<-rgh_v_M!8qmzL%C&fa$6<|*?c<0rf4PdnQyA2zLe=IlT48QfV`-IKp+YJG29 zsj05S(^=BrG%bRC7~5IaT9jpYzG+bnXKdtp{6zU0r@kQX!B|n66cyOh=Pv7v`E5*R znY%A|$r@Mpc}q&XeWH+hpfb!8?J;3E*^`3zanK3->yJO1?p0M&-?11%jZ#v3f3%iV*;1q=oZ=vr)3J! zGMlYI~v!F|x>N|Pls*rj?5D@qU<_L^>?(z$Bw6TWhk^LXKZDQK?JeF%eX=91lN z*WXY%rNt?TCDTgQcgmGZ%d zBgg4L;k4>=Z)SqwspDm`L&>R)H+dLW>CQrbqp77po(-=0ncjyvV$} zm=%=gpS!f@c4-LeXVzVOzk|)%i~1;kIa#b}n*}df=Pl${q=H4(DB5*tJ0{W-;K%e3 z0;w?edf1z~LE2oIonD!BXx1p}^?lW<>aJE5MXYq;k+DD6zyo|Y6YKwz)3wD!jCwZlj8acCETjSk3XX+2vO(OT0m@n#&!KNo+AK4ck>blYZoNi|qyTk!i9S5R+ zBj8lxNuXWG;Ia*gC!`%_q750_k7Z=!1=#ljv8(MIe)47pwaKN zB9|<+*2gX*S@JTSM~mY!ZlVsK-nx`tDf10>1qWQ2xm1heD1y>&L=Z$>%4K(PNb8ov zj_~SI2(`%)X6-Fjpb7{%6d4>&|ND@k9^XU2X*$TOZoFm#fd?*9@J_2R53m>%acRm+ zuJIUs%;@jbS67+E| zUO$S`kLjnRn|4Q0`>|Y=1iuzok3mlc@4P2RTH{Q_>sQupvjUj$T9-^F7`!X-Wwe&0K`xZamu8X^MchrF0Fhc zf&_ZX9{-wyA_&<#|6)ux_Ju|Uv^}l8Vt`A}lSF(Zqy+TbOWgVyfRb{b?1i#arr#L{ zx=Ld|V7j6|oM(6%Acbq3=!;x8?_nItV@Zc|{DA{ZNC-3zJZS#D(>9MqN3^cPl%ZS? zDx4>>N$I&w5DyAxATt7Ocor|alV}Tfk+P>Y1*5N#hd&sK4`G>ZFaGgYKX#U=7xWH1 zJkYyCEh=OcQ|qehE-yxSb?vu*`Rzqf#l=%+t($deLKY%%t!eBW%HKJ)%i-vm8{_fB z&h5_4>+JXKC@d){+%XiJzoKEqx4*q&TW@UN#qQa&-TOD2V@%;@gNz0~!f~eQv?zt+ zjMKP?gmDryjphdO;5Wmg3E|BdQ3Wsv9U#S3^E{?jFt?!c0;jp;3yRP28d<;akzf5x z{VDze1>*5=_qqB2{!pl&f3EAIO)dJ`6}%C2hHGn$%aZ0CCN zhYi=7({S@^`cBhrTuH7hH{2LMko;&Qhbh$ZX^fr&w@nNqITL~T79KV-S(S)hyI)4q zQDmJaDX|u1jH}b`HH6ba@l8Q?IuV{MxJTV)h}?RT-wLsM%DqXBh;^bcvcDTQtqV9% z<3b#=gMEtwcV_5?uH}8@HP94j53S_($aTd$&Q#~>L7SyAk?(UZ5^%_|CI6CGw{NHV zh5FMW7fDI3p(3>?Gr`Od?NZ<5O}AyFc<^6|s;^^&FeZI~BrHv&J^ZHdNF9iz$rFQb79VLQ!m=j-tk!DSW2DZL;BG=DvS}a& z@KE9V8RtjnxD%lpAT8FYvD8Q0M`wQ6NLj%xuY7csDxW(xG2Po#U+Ym7b3&%Gu_!Rm zjZO@-q7QVUmlHWZEs05rx^|jaie}j{Y=>p&72PlXW?)O`xf~Jjl_r0qfSe=gDEv5t)XT{@rSXswFB3{toi}?? z_(I>&B9n?S5$*OUM^q(3_kcd2UPBJidV(<_$7FCH5|Xtmg3`7gXGA&1gkHo(ZA&Q( z)6RKDW4^I_At9=L%XCLb0haD=Ei;cDxJEmA4F^D5#^L&$zw3KvHRcfd3DQYeL3c_|Wer}N1>l`wSY8m=On#s$)lcdh1{u{rZ=pA% z-`dFYb1bbHoM1=P_}VDHryqp25uylFwi+6w^`R;^qrDNj8@VsJMzzM01jEVS-&fg@ zKxfvgC%9sgHSuDvp&h~hP`@CpBTp1D9^Caw7bf^7%zmP(+_g&k0rmktNV8_WKMQN@ z-|Ta$Owv-Mm&3~DCkZpMdWU4U*KxCfw_iyW@<~=LSA5;g@4v@ z9Ve`a%o%4I{c8$5<0ixreMp66(_llDxNRD#$dOG& z=O%;P6tbU!pR^UoJy&8>k9tzQL*mRGxG`##uo|9}Xmje+DYl(sk~Yi>%p2j6YYcL_U3$^bVg;io>pPZ58&N~mw;x4cdxw5Lw-{YJuP9G4L*cI_c|UHe?uyU$T_59ME;@yrE%7+Q8W;Jqk}KJ?|ft?eZzZ zGGfd5v{%dcoDumi`FhgfA}kD8xK0nl+k8Mrx2nX9+me*u$ag4B`VLW)3pvt^bRbGe zR*S2QK13!Q-Mih^TN?BvlDzDVoQ{zd6Phl*3z5ze=FAAtf_<;4zxfS|rem$O|M`t_ z1BC~`>Bg$vqr9QgsW6xt!nfhi6{k+e#>k#VRE1bGWFz3GIffX`PpNB;u2C=j?WMY! zCWjCHjqDd%>za!$T0`Z*8%Nkr`?y_1m@ooij^J8(bf<3GnCNo^#Cz4&TD7(n<@}l3 z)6%wgm!J8`%=y1j;wJZ~vu3G1lh0b+(^EJrnC4Pd@_pcGDw$#c0C;D=6y{1nc|veSNR`iuNKoo^d|P z6w3t~7@}%PA#m)4%x>oe!jVNa*GT--#dwKtxtKBr&n{ z?Ah}O%XtLvh+9yW@mwXOWTeC;Vy%F|BAa{~C|Li9u^?@gz+$J@9@^>P2O**(*0 z+dcj53YpsRCOROivHx&hbPSmii5i8rhR*N|NRNgqgUqRfNNDWEu|%?h;KLB15_!eJ zY_?SEf|y?D4<($pWBgm;{Y~@pGQuyY%AQ0J(1a?l{y-4v#UO3In-_|CjF#9t2Cm@J z(XGlgaSqzW`4J(j^mMonxmBph14|~jhReHYD+*b!C$*I)%xaWY$wu1*|AW!T6>Y5N0YN5W>HI!2I%7rfXm*hUHPX~E~Z z;v7>Ka%?F$2^pu1Aj}e?n$>4FxqJ@0+2pfZ-(au*2P<>5Cvz7s&SlLxtJp%4RfbAvurC28{ELYzrHEE~u`jjD*f#!} z%I;Ne&^~I`J|f$v&|iG6kX-GcC}k!ym!HP-$ZYUBI)2%h?mR!zosQTIQ!gFUgXjAJ zrz5m4WCDVj)*aNwa=l6E6X)dw`w=D<1b;te>{EzY{~<&Fz{`Q(mU;t<{~1l;zCoWw z$crAJ%f33p2qYM&;1AC{8eDg;`^u1Xq-$ucFv1b>8DVHZe3CGG9Lz&HL3jT;J&oYn z$$f4et66yg{Q9@f2;>ih!2{%$WDv^vM84(Od&H*7`lxxi^g=saz8xFp|w9uEiACbw}%Ha9lU zU8s7x7P~4$3Wb5T&OVVNytH(2Pc5CJ!0zx>RrxM&Yu8?no!5BvPy)}OP*A`Vg(uEF z+&wMkw_Ir>bgB{_ar(|ThDkaMNZe)Gq@)5Y(J0X)}$a2Pz=$CO(!mayPkL;Va6p2`iLp3;)oSXb#JFWb61t=N_xZ+ciS0iRgw zhXG#{ECEHNB}FtoZ`q}nrVzMnNh76z(WKa~z zI5BG99F_!~paW52$R{JI$4FSD1`oO?VN((r5BT^IOw;)wu{r}}31Zwn9s!^jD7Jkh zn1AQwqxn(C-cuknfcTJ3dV!^S7Oo>b=Sz1Y>18dYj4F1BSSMzM~6Ny%Zy(RNw$W(D1Z2$V3M=HX+?L%QTn%tZ-wkZgePwJ+5MjLcf#K!V0)HUQHjYT zWl!c@xO?Hk-A8vmxO3-&uoh9PF3^N$|=*Qh!CyiOM3cMd%UV9 zq^fNLmx_n$*H5dgOiDsP)<(zHJF*A6W#rbJHCgGtq<9M6vZijm$+=&~Z!OuFAYtTs z%#GMDJT1)UK*$r{^l^krxX*@MJox}9l3dvl9mRbVf-$jX2RVS@<3O(X)BT#gHECOs zc6+kBP&H>*v+@&SEJ-oRaTc>R+>{xYWpTQ5(pIO&C)+l6H+OnUo7Ghl^Bgu3R`oYuVYJ5T7Q0CFUEx)0^VHZQ+!; z-PN(#YHVWHWZcY|RGs8Fj*D}qv(4Hag&mKoJsV2e&-FIpz3Gplz?5=1KP>!G_BkR} zz+Q2Rn=OuXM=li;KN<%Y;PuFfzaWGZkhV>-^*vX;a8+7PRc5ndPRvS4&y5dn4v)`G zcc#<&pPZRFbxLODWT=q#&s;Nm*HCBFP(Q6!PO8&`qs`h6Sg9k&uVtDT-MOyIp%5Ao+gJQ3ejLq zt$5JF)}L6PqkW#U{-j15B<#Z{#l6l)Ahi*nH#!w#0otGwU*tq#&b$!ADZZD_C{RCm z6FR+eI)l>~F?wR3a;-@bQ<~oE%H&r?MQc;VMPR;z6U@#D%r)Dd?249i9G9iW$9D{f z*-v)H$V($4REEsCKJ@_%;IR!R!6PJs4;;j)1IxqC#)yy9+x`DI9ww0h;%ku(g8l9P zFgikF$>Ais@&D72($|VEOM;II$dU+S%7@eb1SCr0UyzGOm)BTb_onXC%ax8chZ+>q z=F>R@yH6)v?k=dPC?NkJh0AD^f5C9_6jzIX695ql4lgreRb7$FGv2DK^u$J2{TW~# z)Gpwn@=q$KSn^BG2yAQXXS&QBT;I`%&yL2(#xtIISBV~#Y(Gi!TB_R{PYg!1qiOvs!c431dtw!iEt)uJ= z2G=^n(WP3GFedy?6w`FWhhDUX*bRsu+soS~Zb(13T#mo&F|s>M3?47%$sXbFzY7{? zP7B+O2G$HGrE;Uqm|$`me*$O1%GeMd!7XW=xx2jAVv z;2*{1D20Ac^N|cq`xrVf1MNH?YYB-e$!55n|K;J?F2&xF}ZteF2!pkM4&zO>)s@62wzd3)kLmh0hi%tZzK z*Bc;R!3WsKZ7TXK2-}aC0I=zHeJ+Icg(j-VU~e(nUM}{KoyDuFUSShbRak{!g+VDF zu!RQGkkJV99@$)0vhbB;CZg!t`L$IGs3e07p5Lq6ibCu$eEyU_=xe(fnutDFpAGXT z>28GVAEZa!wuLJ=ouqq;5VVHo->&o(80Px6wYS35i;;pYi?dY*PWo5wEF z?qGAZJFo_^{|0Ol>99(`{xOzS!fL2(wEf{SS$(Lz;f4`y37T7|&kMz)8fdPZ6JtwE zWUvW(k%~@qaY`#z4ZJ)*BWOfKSsEKFlGP?cP+2?A7Gw7maU92v$1&!m*N@TCS zCZct+?u*qFqy3iEMm6hZ@yV?bIz6OwHi+(kbCzQw%ZbBc5*9oE?R?W>DdR)-s9`{r zhBH7Z^<)xG;-&g9Y$N2Lk@x>w*p)m!(G%>6;xu$c6RJyrDBq}GB95_-&QR0i14mCc zgnaVJ9*rE5o0GFPawL>$%AOjE8x#f_iaakEyB#_RK;CzZh{L+DE*>?amIw$C9S<1= zU4KSrGZSu)k@I$Rj$I^ZwvC%-w-yv-TJz~s?UFr8tV@|c*dEp%#BRw|rs^0}G;yfj z;^-}Q%k%iP_Jh#Gydf!hYgy{+96y*C8j&5;dIr$Nx)lASDh^3uA+yF5wQ-*_N)6P z>bVbegYVaL_#IYGef7$(`K+bf!iVr$m0)bGaedm zxP+6kKyey9^)#a=bm;^mtRFHC8YoJLe44QWJ6<5>>wDijEeO{mjfA>QWy;v?Y8vQ4 zNZXWH2OQY=4Rg*6$6=qo-S4StC-Nn*&8KPiN+f#&${2JGf>#305|>1IFyxE+tz=`uk>| zf90ArSDrt+uYb;NuNR#0%9ZNS(p@{34XGQ>QLnjF>#r^j>IrB#NC5OFK~(A<*~DLu{Lc#O(UDXa^-RQudW<3m-ue&5KkuV zUja+gF)}ylV}SD=J_B4%?lUq*_ENYlnYn`}Cy#V2xSirO$#FYBlJz%)<}RI<63^1l zZ=zFo5ExLi%)YRm4&Cu>W>>BVrY*f35u1H;=>aeaE^lIKP`+sO@Lv+ROTP z9%f4^HVxK2oQI1;sDb@B8W)G-_8jc=bmIR(y}U}@nzI%E^@6uC{8e}{>mWO5kqGkD z(??5bmRLz3KD{s+JR%?v;m?;KNxfvp;a8MC8m~meHOi*=P>`5gE*Amb z`@(u5nH9{{8Z>wf+0~iECmFY{z_O0Y7z5PSVWR z)!NONXy{@1VNA6?w!K!Qfm(Y_ZHa?i-k_FP?eO{Z~Zsjki z8~Y0K!2`P!6c{rJn}I519>;jriWebNi-K!>proq(RuQ9_(P7M~Hl#nz>tW2rlhfxu z8pxmsj za^#|(J$_fWa>Vc20~Hs?0aKguAwR_zjxwKg^u@g)593U2*~AO%t7WUpD?_QUV9GHZtbWZ+GbpD>{gaW zfo%wCJNQXpq3y)l7X0>gsNV!^gAK~T9L&mdt+PSx3uUJE#r^jq9SY}_ZlE(rv?!tp z;&;dKyC_f`$OVii_HR*&9J=I0whv1D2yUInC4eX>D|OE(?Fx2M`jENOx!;-aB2#YQ zURE1)H?$;eC)uop=at^(-_RzlLl~Pzu0a#AN+ZY-ws@Ozj$!&v8VBa(m-ib5NbRW1 z^^DBVi`1`VKM(L+07>M>7)CS*lVBB057@yJ{d@!7Hk?uzFpV4@bknGwsDR~jtJ0^s z?eAzg2;q^#p45`2$4B>a#(5?*$VQ@l0pc)Y>)gcNgwy)T=qKSBPu*hI(XVFse-x8G+W&?12G7JJ z_=QaHsPaDW4I5!H_rZ%yDWY8HoZVj3c5>fI)nNgAh;u+;$rl5*#3pBd@l4N-1_zajXEH*HlO!(Gmr#SJ+ z;~trR9pR3t7(LCkXShl0m+}lP$&j`7_rp1EELueEV5Cgodmw+Mq!xJv%SYNPcw2D+ z1qcex77{Bhc`ky~u{#AVx~#qJa?m4IuG5|Z;kTbKbam^pIemv`Cn^^RVq{-97v$)D z1K}y{>uehk!2lJm#nZbwhZckQ=rD%Y7|sy@;#Fc2Loz^;>{U>S6p%zS0V@s?fIudb zF3OaU$_t@+AlJN|^G@hl0`KI!y#`qqMNY^OxgdAFbdh^+=urZD-50&IE0`IQ;04|d zYwT~JQ9P2Kdw;-F1u#k17M*@ehc8x6{wZ)^(W%Q_9QiKeekdj6tMZ#55n*8*;n2+R zaF~7g0ah0_kYrX?hnf6m@n?u*4SP(9ONtE3{(!zy8@7=ZvSmpH|1sI=8{X9_VuuJcxdbwQo*K?~%(o-t>`^u_HZ5d$xAq=+7;52gZjxAK3m}E{eJIFy~-^g1}Pc+!JiVZJeA}O`|PW*q=K-|7$TUH z@|E+y{bf>taz3ja{*vy}zDzPH=ZD=f{3Yfi41L{c+Qst*hgrm$1L_7(a4z%|6sBhs z<|HS?Tdf&ZQ(Jmrfv2!ABOw`gsGpC?G`1LLGMhjL0c$NQXM-}fD~yGQHL=&}Gtz!F z;reCzO<36LtXv7ldZ*{aHWV~{@=22tety%A5uX>K>t5JPSvh^&7W@SfDtj7=bo8et@D3QteodM4xe{2C53{auiNUpV~WRuyD%u z84azD-bf`vF)0qOFK5=zItt6{bDVjJgM+4q_`? zj4o@I#uPxKpSoOk&vz1lX*X{p(;mI^xQ%6*IQ*5$Y)zaSKWUDYum?&)k zJmUt_t=!It9nxy1KJp+w(QRXcxsYS*X1lw&FQYKMCU?rDDQ@i#!8WTmoc%&(vT0RJ zMM-6oPjv4Z-~Ja1?m^?7=I}S!YUN-H=E;fCtx{^xCeEJ%v(<>4isXfxYS$?o{Bf0e z8Fe|4(NU4%ktVY#+7zFe**UP}=<=xxv)@P^=wcYzC{vs%Ho5WgHS3XZm24?iQfZME1Sj>7qiT*~qcpX&|A?kY`BnFrQ!Os7O--_uC)7@vSra#HNo$oY zEt%S@np9v}tmJ!fz^K)cQJ9;S8=D_j*4$Ke_4JM%@piObKfNby-jAtWn}YMFOj8Ia zO;7|pm&da56tDABC&u{T@IS(qD3uuLJmNx3*lZNLiwqQI7ePb&I#8^Vl`$6%If8D> zc4YRX%)IVw)6zW)vrQ&@NpEGXYeBooF(s?y#yeT7t=nWSDfrWjuEC^YR*+s;s{MS@ zR9|&xa{3S4&_rU4XAXbJu4OyJa4sZHX@|awH^4C%GByn~^}aT>&EDDUlvEjO8B;;h-pjW(+}O(}NTa>`Sp zvP&I&4gPuf)v()vA11C%4nvl#0y|l>fe|irnD7BT#W3?xZKjOOi7eHw+uL;j7FJu>ONm;B^x<6)F+TUz7rMcls&9?cIGv*XGr?zw_FR3am%`YsS zWa>iG*c1LSTs&L=|LIi7-x5!-Y&a1xb5=x~UD;dcb~i6cDzaoIILlhp+L|TW$AjNV z%S=g%FI8%a3p%MTMQb1K7%mFi0Qw6JZ4q4LQ|-WK9rLtLV@i_JWBar3={w8gPMKa- z9chcU#%AWJ1GU;K?mW(WFs&-AX!tWWK=PumKnWGX(| z1mvauUpm|{Tpf0Ve6u5n$q@~3ia~hB)xDMi&7atr7cpp_H^o|+&@eN)F0QuT?Y=6x zIBrf|MR8mO;*R1<|D^qZ*Qu=?{&@H)wv75fC@K~o5hI#EVucC%Tyg9tv2)KS=cU-) zZfjC{WNAcFX}TjTBR?rVwz8qSsQO!Z_TOvw6ldh+CB-CK3QLPE>6XZfa9??88yZuF z{~7is`w+N8=gNm69QQq{!@5weYq)?rP~YtA8YRc=g3(ejPE<@Z8RE!K&J1t5A=_*UL!52^OA4z`Ot*#@~K-lnyRfLr%`^V;qmr_V9L38!blkz@CPy zVRnRS=nF!6o^qHRb~oevuH9#)y|T}v0~Ly;QM)vVz;go6Ul_Owo-@XHL!1I{V!{_; z4uge+(g|6~Xu@xag(v8ZBVQLB49BD$-{zpEOKeP>9g>xbq4MDPPi(qzw6tFbAE>- z@GG|mjq17-3z_Wo`djd4K0nYx$5uX*ted1XyaK)QnL7T=TLYDAu9^14r6Z{NgnC!k z{=h?t+dAyN-Um1>j85sg(%#7Mi&#OarT-i_h_Jn_;7$;Xz}sXC=p~NfaGF)Lkjv^& z`@vAFc!30Blq#>qidHRvB^bzwCdL&y>kj%tTrly_4t;PvWiz|XUSys~_!=YVQ@t-Z zX*EKJSAQM(5M9c0QiW&b;L-RE^v zRKbFfnX;Pf)HoAl-3Vfg=LX(|o&>ldlZ8vwhJ5k5)fUsBm=C1ETn9NTnz5oAaAL7L zsd@hlz4oiCzq(6kYgg|*!v=Wf`fhB?5i0NDMj(&y54@jB z!tP(kxI7EzT!>iX1+y9~E`m1qBRjC6G8F~hr9jO&BfP;OGH69}1PQR;4Q#_6-BvgF zhyGd4%j&vVm(d+ut=rk+$>vA(< zfO2N1rDSYfF7W2C%sWh`tEy>RuH2$5Zkyfy?>2TO?T$w#QzzP;xZkU9=N5!K)uI?4 z=W0to82BSJPd$t4`~E5qXJtVzh*V?fu(vRHXho8t>OI4Gv;LxXNEcn1Kt3^iUoG)7-RX>(9NDB02?&fIO2pLC zF9se)Wzzse9Pq}DAKVYb9JnHbjR<@peil;0Sr7ScKaqJ3xXGQIzU_$2vmR+sYtFEd zXoU6MdP7ai%3-CQt>^*jqs2{nHQUZI)#2=xf-gu5k(S1}^ll?p<&tNX7Mh*m>ZZ6A z2Dvaj{%zp>=)EA*IQPK$x?w3e;wFeRn{?T1$TE$-7fO5)ec^8+Ely4BpZHK#H#tM< zV64#P{0%gy`X18tTYZfvKbNXqf?C1q=1yTo6C)~=-SKyQA8^*;yP&_;1SAa$0w?Ck z$FCv(HDYxNZT?rrkzN;Yvb@5jlq(*t#}Nd|y_Y@W8&Y9+S|}?Mc(%ull1Qe8NWBib z)&L12fTc!^4Kg`GJsKa8oYu>PL~8=p0Rz5b!5to=h6>rhrs#=ceF2p${*j_L-@xV_ zKGOw$U@hE=cCz}!bTjIq#LPPNu>r@yVGSa z*Wqi2;eAZGYAMK(SXv2mJ#kf#AVopo)1F5+8@xdfIcojLrO`!#4ZZ_BI@WhIk(-e# zmh1_1@GmB|cf%%=6*+V#dcY!bS+oE1y8b}h=ayMtnif*IQU+ujKKok(pFkD(Z0Ong z4aynG2@cKh6KwHbr@@7IH9ZxncY5DO`*q~LLF8yY63J9^3-8c-cM-bGp&~U$wILUW z;L$$9I`@}wjRA(oNCY1WQ%vbPoM zqHn0{4{X&xFzA&YQW(XXcGdlIz?lkrN&xdu2i}f82QcHP&WPQJ3F{|{AOtYlLHrXh zz=|fYppM-XdNM498QN;+w2aQ5%mzBC(7G0SruUgrsHo(tkwkL1jE?%N_KG!>jP(Ak zQWz1OPMz{EJYhk#EPT=h3>7?zlQ_ zHJ8;*&cqzLX;PIzPaf??k06B_oRFz zg;re`zNmD)9PnsPo?^X0m!EOFEl!lIZ5%Ajm9v6J4<8#&SAxk-rgks-D4?$dDtdUGfl4&v{4qTT}osl=smSO+obvW z`*gkB&eZ8lh7E${DTg9)a~ebmzW+x+-+4k`xXNi-4HWSUw>bn)597SF&ZENxT{@Og zhx4ZG*5OBrq4?zP8f169M;oOfTc)L0JCSU7gd6)$keQ3uAkfmc2VO@11m6ca2{Odq z5Zr!Jf-@hIU1EC$ylEgw3HIJdQ5$tB5(=a;IjAxu5+sev7DV)w#~rnV zb8)K_0dmqY5N2+x%c z`yUKg1;4*?sP-D~_Onoc<(qPKF%z^-sw3_JKB;uhv0I}R0wb~_0LqZ_i`&SETUhB zWBEoX9cAv+{Z`eljb`0;T~ExmJbiik>A)Vem+%)DfH;zT<7x?f{&N1)=P$5Arjj7# zD9W-Kr~meJ_~yr%Qa)DYo%OIFbII7ON^8X^G^Tc-d!efvFj@CBw0rfYmkkH(f(%;7 z%0fC;aR%}`Nxcc%iz>PmzCTU$dyOZD_qqXP&?nfiFRoE<8cNwlv=jTQVC=o+A=ZlEy#)Ha0N>|=N&~ueZZpNgaFrK46lln5L9m2ia+X|%<;KN-;-;tc}d;uOw9j| z*mjg%jo2s7GU`M817Orq@4Jwl{UvoD&cC(lnivK0T@X7Qyjp>%%RxF8Lk&y9HN(5~ zlP7h3abM0&*Y?8N2jL@W@4|n?e3<0*SW4WgP6IaM4U_3b;tNyiG?e3;;Ebt~w!=Qk*{Wj`ZnJ zDr36j^68p&{<2O`^qCwXACG>m>t77Ty_Vh;V>_hAqO8{9Bk^H0Vw!2`dG` z$3e?>!gmw@*o16k4RuY1cl>ilX>4msyz_zYqSHrfU(Mep$?w@280=&lM^iaqss#-? zH#AT(ETt9pi;PZdKo{|Cz*pw05 zjW15<`*I#u^C_9H$!VQciqNhFEySQ+$?x`CgYb9}{`~Jl7IOwZA4dj#vx#Fw2-jg} z5#P?(P<;wThQ_3c-E>Y5SR{1Q9F)suMk61Urlz80x0F$%u8$EqejD~5KM$WZ(tmdh z*Tfs(BS1WTaSZ560UwJLrUfKQM(o>n$~X>#p(t$8sD*4BrrRi+9LkjR;jPn~A;FX~ z{^oFMQ@B}3Mo-6`CeCD*a$63G+nQ(ol=>mMenwMLX)EvJ`Eu{YcFyY8;5m-1Qb!+3 zQ*hB*y}YQx-g{Zg9v;3nVr6E3Mk+)pwJux)~&W>_*H z%)|u{d~|GC5^=Z)Jr*N?Mpv`mm>B33gKAZ9ip4-C9=OXH5QFTKBUWqpmdAQ`Q15>! z(4H=r;+eD}GJbYv-H}DcH*JEgjx*KnQT&(_y>sO@z44|$gqnI1Su9NzCKl4X7 zG80kDChPoee~F%&ws*j|dOy_)G_DrIwM?SIr(FTBm>1KX9f|oPwXIugRVbk@jH)$v zk?CYSxv}j1){1#F9}d+sR7OdLz?|FJ#;|XFvOaB-4x;aD*U!LJZ!}GNHK5lh{}$M_Cisgn9w7B%^fZjf{BVJ%ME4RS=4z?GtP*a z7+a4e6)M5Xye$|_Mk@R3vtM1%&wL8g9coZZ??T@L`N_e1oQC{>MK-eSwc^vS9D-Pp zxD>|WA!@>9MqeDn3|-}?<00mx2z8obARF`F>)`!z_^vr-gVmjyWsjgAcl0Myit+<|?Gn&@lmwxa3QT}(^L6!LEMrYGmZ@lTj=^ZyY0zNNq+gp{(o2l8}Fx-#f4+UINEqu6J zol&|%%oo<57pHyUio!kY*E|uB6s_pdgj(61+jXe6x5A0E&14$gSLDC1>w#d3QgJxigft$aLr|!(uyS2&a<=Nq#?;e_mXPB+ccy}oL`cs1 zlUea#E!^%_6g`@4s@~XWBwf*}-nk!8H}}3M%(dB0NM#BI8&^BLgKrGhJSQ6acJi5u zXIy0qF?#~$E;e(`P4{ix{>IUiy`d$7sY=OrsG{pl&8%KJuZ`UU{Dj04XoXtub|Ry%EXn9a(^lBwhQgPnXftZe zYu!a3C&<5`W(lDZOkEPJac!40i=*PWi)b{hH=<#~Yg|ebmHSXtNy@TUc!O?Lp@E2X{{j zK}R$c-7u^u`rg^r`UvNbG5L)R{mSC_m>TQdi-I?eZr#q0Y#5`j#yW_W{$*(w6$QB* zSu2-D=+`&Lgk_q(oc`dXZ@I=!Dxgik4tG7#9p1^PZP5Zwr08vLan?NbMpi#{FAUKg z-ZQjWeqk`UKrJi?ksTJle?Y!hrjsvC->-DO6nH*ztZ(D z^IIa-lfmgo@B0MSmzGlKR{-naDy--Dj2Tkq8U9)b zcPAD7X}JFK4^FOlwaf@|UWyjO`Uf>O=m`&|i0q{Fpj-On0)%W(?C)Kz_$-z?_NJJa z+KJXjj9Syu50?Im5&=eX7Xs=7sz5Rdu=^JhcvyJ=i%bTz1Q>e?Q}r`{{mi=?U?KHl z*83*!$UKvGY?p6}Xeq`o-DtM39xkYiB%zY~HhMU?%U@ zgbw)IlN=i=Yty;STDCc~vosjt_O6>gq=KmqX)%|qiMM`sr1uK7JQrbJdI+7N?#0pO za@=sXZ4scDN76KK6$TPu9|M3K{IUKZ*=nl;-2?%Tb>4|(m7bchb?i0B7{(9guhK@F z8KoP71j-GBw+#2(DJ)Yodwxs)TFPE058M7X*K5`c@LM%n?a)F)X^66Fjp-8$($xn#p`4U5um%pPH? zlx~G&(dpqBt8H>UM1&O6S=R)^ik5z{v>)Isk|+gy$A$X&kgVbD7h^(&RJ)Au90c?h z5Kh}hD{1o+(QMSsT&yld3!EdS)@XSob6D@afwC>?r$7)1Xju-6;g5Z#K~2=6uDqh9 zCfVvx`m(=)XzB=t>HAC15!(o9t1$xy(S{k41Mq7=Aw1OrRX1?MU>f_D-zY&EGa%MA zp;i?lr+aJ;uamYgl(#)3F}%$qXEG@|Gh6k#eTm))YUJ{u9MH`^tJ}f&f+ry6%1YoD zz6Ku4Y;u`>)N|HCEZ}q4tSVcTlFoeIDl;PiT`#19b-MRh%?hnavlRuQNugnwrK z?-P! zo<5ptw69yZ>sUVK4~Lx2$XvTNO+3;{POxrlpF6n_Z$NK5(@s_Lsl#Z)x}##%RdK}i z;WOU_8~Y!iU*89K^LXD4dgm8NA3?v*&ND%F@U1_f?A?$o@}Z#nsTOpDZ+_>VAt8YL5wkm&h0rFYqgyQN z0``L{!1X*ZUj?{YnD2fgpThNeUmVoPhMo91u5&qzF>C55cHJ8Xq2OuMddj8TeY7F3<#;@aCL_Ud&T2q4T8}+`iGJ=qP-a=f} z6YQ8`Tfl-^0ORgxIrWy3Y#*K&j4|!xBU|%}vHE6xVT|K(H*^QUwayTZ1g66zBG(p09-0S z^^4+yofC=Ljhq}}{^jBxEUJI9E1&mgYj@FyGzIO4g;FWeXfcqqe&HcB0Ctc4iGWmm<`yfDAfc?OlGQrTqW@oEC5tzZ^OC6^Cv=E{DT2b=Mn;zwYDf`R&OaE|!zMTq zwl6{*6sjG#fAM3WHgD&MlbO3DOlj6%>Qm-fw2gUQYDF@x^WTOR*mx|?en zIRLfvlcl}rHvnS;Ujn{}4#GMR_P&_Xq_2;Q)60Sj$*R#PLXUvS?WX`xM}iQs+I9{K zTv~!mdn|8e?KExY3Y2U701Q?@U-^5Zbv7E)s#o-t4l-;~i%}vQYTer9L0io2w@vxy znj9A8L)24Zt0ql-4CMHqmVQHeHPiToF=BV|3>P#iuyKIO-Ez7@$}dL#RWI6NB(PYu ze59QOIoqx5y-u6cZnroguac{Z5BfPm%eJPUvjZHhnK64zZmY-RaId)M><6LG6TCZw zkcD!Xz2+BCNL$nGvAcCW9P5O8@2puk3#5L?wBnyrnvQOSRuGc}d|p{vM6Uo3245`` zyI5Zc;|Jjg)IW&*yP26>jn!~)+o;|Dd;EdHR9o_FkA%k~(efpw=81QHa91i?>zzi= z-a=v`NW^fs=FVf zVdo@qJeW&B03JFbjk*A>S0I9AwLWhY%Se~X8gIgXCiP5QF@$jol%LvJtc7AH!i(9r zu!V1gCmog`r`969Kim1Iy+5Z;{(RtbBD^uD?)8A&@c<(M1$+8-`0p=YQb z><_@U5xg4@fwUEd`k}^Uv=oRj?dJ|0*$E*jI1x%RH*UzR;Tf>8C7zF*IyW{W8b`E< zLqpT2>(#wq^5)CxTr?_JgAT7_S8FyYCl>>5Z#(UjdYE$lx&+Y;lOvf)&+wx@IHSgkf0)BLGJ zJg$_YtaQ5E{$HzYGjf?+2YHbs0paBe8ud z+BJbC5&%w&jNzt8phNOZl32jM`gLIqcJ_=q2bl=?TC}FME38;aYqH`OT^!Z9^uBhz zN;m0PV=$E;mc+)s?%=E=or@Q}Bcg|61JOv(FPwRW`Z}6T1^A&vBkX3w#q3CaBG$37 zy^l5cjLTg~hn3okO^E~A_`uZ>%w0WtpGNNY6(WEsD8wdHB*q8-aS_{4wOJytygYn*fP#?j(#eRN( z@YV)b+7E$tlL3|f_7FH3!c4&4bTL*#3-W@j#vL8lcvxlhm|ubV;atRt84kr^c1uo; z<%*js(s^80OWQ8Qb*Foo>Hh2eF2>3*QWU)nGe0mS^j@q+^X0hen%+JWJXZ+vp-`Dc z=SxA@x*`HCthY#} z8apkvI%9IKwIeD(dYDke#DfQ5@oolxX4z=(vz|on`cQrUaMEg6zd|P|Ve2P^qm^}b zb9(#%V*NVA`gs#}7tsSxO7{crpBwH7z8k)Lc3JD;)X1JKQKAHmbfiWsp)BP>;esCq zAsS~JLV@T&Y9z(=K0qDkB(gt5Eq!O{W@;c*GJ;6w}tCn6EnvKQXTM$!}B%>8`x z?aVG!Yc+&G|FWQVL8>*mlC|g$ZDOiNdp9L8#A+f~RWqy$!e; z-r4Vxw!o#>qxs^9dJ{X%%Wks3i%q4T9keEWL zWo{U`&l!^YG@-i+)jxk6_fVH}8riZE7T^^S!_}OV1L2u%SYQEOY5fG}NW<(5u_S@v zq)IIAU(R6e)6dCs^h{Uvc9rQ#WTZ0a3fS@CO(b1GCOb@mgpGt{s`u76oSWBdPqdmM zq%WrVVUoJwJnklf%Pd^S-VK%lG@{?BAdwpurxoB0ZGi+1Ml3IkF0jl<-~c(uN>|gj znWK%2Z*D5CZ!E0hvtDBbs$mX`7r>j(rzQOt}C{s8417f(ug#Rpf*akd^tHED2T*iY=trRN-J9>D* zfku3ECggQ(-10tFh)+Z#E|1OYxok7v`6aUX%qOV7U#Evp!Rw8FLz+I%WVeb{6S0G z&pUWq$(-TKyez_`XCVxuYH_oj4n$M|HbG2~_kE7K2WX-W4B`1KylJHxg;XpCq>5&o zw*~u>(kx*2fi^3FJr9$=c#go=zzgR{Q18CC_j&4xEjrT{#CkFh=e(kDkI?3&oiy6; zZnQ-jalLPRMv24Fl{H|pJGK`O9NO1D9zmCE)?lRn%ok7cq|OBIaN$w& z<>IPQZ|Jkuf@ige8#nmYgfP$W^l0}@!`tX_VGA{$@BI-S9m$>^(Km0_w~QR5C!Gy|WiZcmSEYTC9H`=+xwn2NwK4oWB)E#=mrSd%MWa{r?auxNOZ|%~1=;-3fW3i;< z6eTeVbNUH;Mu>7&E|&_oXIyrV993Z6i~)uSzzg653z8`SA4sd!D8b|44LYzS8VO*m zuy~pQ2P)tx?z*Tp@N}fk;Bw#Kiy{fu~4#Q8Z&Czl;K~0ms)cT96WOSV5aD zduI-@5g_3_bIS&ei}nVeHeTf7d*tyvS7`PlXD_6PDu zg+f-bN6-fwi&uPpLH~1l+oo-Y>4{6GGOXLx`^?y>(bFtx!bruTaEpm!LcCSTEgF&0qEhQlUIptiKk9!^@=ExZ~>S71Bh$ z<2HM3P%92ApZ!Q7*9KY4Eq$HLK7tqbW3DXkUBYfJ_7&JR7=9XO&&2BWeZAo@NJSL{ z6z@jEMvI|VK$`z$j5Jo`qOIU6ijib6!CIYGs?eF!Dj6r-^jw{XoXTcfIa7ZYlByV^ zjV}osD8=hy3$>`obEYz)P@|9^DMv!T?9W81Q)#7<;f#4m;3LN6QGyFhL7s(+0V))u z`v`z-dtK0zI|Z~%$4Vo4xLfcV5WlDM_K@iFyR%g|Voqx)lWH zlPN{LgGza|NQQ&C(aYG4=%0rPeZ3M1vVp$gyVo-tOjeUMHH#j`bE6&`$HjRvHd-O` zq*pIu5_9e>Rn;(!6Jxt!+7uz27J-lZHUKLDoFh=9-3$VOT{C!efe&CeZI~Ytv^n6+ z4QvBqaW&#+kl6kXT%5Pz{#LyS4zCH->}D+ott9kKO&Qv8+;9KT6A4&^94zV102^dP zSJ);Z?zXt(J(1K!8w_jKT-htA*=>$bMUTu4UgGwcD1_)Ejwn+>`$^|J^ONqA4B^sqe~bNB*TN8thLvTgRXJh<~XXZ+@_ULz!(kbuCrAPRlmg$5>L~kRvG2g5rnKR-c?a z?h`OA#fH6XPejVSdtlC>?wfu16F!p}NaahyRQJnEXH5Sa=ydtOwF7qzyb1d)a^C@Pr|FW)LY58nAm!_}gS+;lg(j zpr{Ui3sZ4Tu>4@-n`k)1Qg`$;Nf*LSn;u_oW;o0N3(g1pPK%Wmtah+{cDsGH8KDwB zD&R>rFK}BSQ^&Uz3X1fR)HmuW~v`=2Lhx)Vp;mp z($j>_b5Lu2XyB=V51b{FWJH*%8&m=(0TmU}5Cy@4FdJgFS7QdbLK4Tqcp>sbD7q@? zB$+vvYZv4K^&uF>pqK`Wx;s1}kzA{&AFlJPi3y3>YNl5IS2P{GIG1|2vFS`EA)N-! za2~jNk-%3k^7e@?EkLM7+E?eXv353#Vtx(ezE!c16BwPh9J9!2o24KQw{we|^o#Ir zkO~<$GPVM*EKYO0tdKhz6H+b;e97&sPhXI=yDq>suaXC(c+p{g~PRUITTf1 zUAT!fkNN3Pt?OIAek5!*b~k#K&4qhl9&sWoim>6Kvgb=~R$7rWf!1BW-uZh)*HVfO*b4k0?U_=shNh{#rhKR04;;w3~#nPjUU6AXm<%+3N}suYphcVf(9vBLx(~FEJ9=Z{}{nfhk3Jg11B$Lm4;w)4@QYF z3qh=bxvO>wS3u$CV1Uj>&33l@6FbzvDbZ@kLp=ba=avi{ZJF9(x6#WdaXe;E5m^~H zjFYKjXJF8$_k>}`SCP-I(rMN<*@f|-T4R-pRljC6Q{4t4)&q=mAmug%d@3eJ4$ka= z{eWSnjA69blEuG5!EItb>dBS+l!ds$yz~ml!+(Q3*x{;U%(#W% zDQp$Y@Fd&DWwPN*LYX@Hl41VZd_(6Jp1q4t=!~McRf!lv9uSB1!jH;#I{2cx(U%`j zE|Z7&FcLexuyh5r2slTw6$*j&MGzaNJiSpp=jFz9qX4x|N&{So&5xDd3)3c@A6 zDBcoNUQCsa$(wv7pv&0qXvFtxyot-iv-v3d?kuLt3Wq#zK0lp0qTUN@#Vb-~0rdzI{5tY_R&FDVjm zi+N>i*U4kA2X%u|YN__6^GIN^`s{8R3I!udwmebpygC&o`}9gA{saBy0u;eKZxVWJ z8rVB<2}I;8y~^`uGX7sv29I1j=4~sp81_M!4}I%{ub)0Xy7tO>^f_-5WAXCWMC;44x8uBy*c!05Hhk-V=qkuJg{N%r1OphFzQtONUhtSaAHe^{ z0}N{}TtT`A<}8W-f(>+ps2Kir9fvN*!tGo{2J}-AFRvq+9~p;tAXo5t)RNQN@FsruL1=c;lg#Y8H-;N58hQ zV78v#^xdFx(^R7wio|ke-2~@GsHGqDK7~F3G$Qxv5MIAn+tPpyXl%rm4I9bupdM)= z)oUOnY{`dm!?8x1nH$d4=1RFMa+CDXf<**tQ)uEsOktVUeIQGQ1e-4fc&IC7khG#Bg9hxS|2j}*@X%flZyihI3ur0&j#qU)s( z{Fc-#M%=o8Q(m3h`&*MVE-<06^8MFK*!=n?^U&AetZ8|!C`-Q$b#dQkhPnl0k_SJ# z4QN|y;uC)`n+Sf?*yx3Wc7h*X@(RH7Pq=aqG$j5XB0Y0m?+<_3aI8aWM~2v98`8u@ zq9{V2YA6ql79&-Y(1xi@m*l%Y2ThY|KrW--sl+?&Eos4Av3)!-6p05y5hYK@tItvI z=>4H-%TU+%ClgQ|Za?~_ZKkVono`cYd|NUC{|={wuF1uUZL6*K+4<(vZj1X;y+!Bj z6AMS%a_>9IY$n9Jt@*q++sx{^3S*9+iz`kZ4{Vy2o*CE*@66yBZ`z1rL5r4+p@9e1 zBv_Wh*3XFgaps5nVJf^`AT&rkkDEj`jl>%yHUtL@ItqV-3bz{>?TyhzpJz*67~jN; z{_=bwnMpTY!=+2>!QL;ZAN6iERf7v1>;7_Mlx8WD#h$Ea-1y@qZn8mF&==>T4KT1L zM#V%`+Rz-i-FfV7iZ}}NDczRZ=7Z8-W|2Ag_{6UIAf7LP!TQeFy83t*(i-Sjn86Sp zr}n{i&jRkoB#DUt-a(-W50}Q_0ue4JLp+DG8Pb2jHREPu*)Js5Y;UjGQt(o62>AGP z;>dJyIF?^XKNnk^k2NaD>zVmPOIBpbFDZUI>xJtk ztXjYOPxtJ9AvgTa-jCO}B|#c}`^LItcjr-6Qp&tDmw)6>n^SP?N1910WB%4?A?K4M z#sjQX`Dm$mlf0AQ$Suvnt@ugk!5M3q=QTFcdHVmf{U3~yVRz*r^U zZ8VM)l<24$=P!DZDtBFzALmJ^>Xw4 z9BQz_se98BZF8hihab7$y zLN`s}klP#ZP;Ho!)#zenryyvEfrYSiE}7(tQq<=t?Qe9`JLF*0n#twR2PcYhz-@CB z5Zob)w?KQ;r3$=jSb$%-;40yjAYE8OnsA>1|KGAn075}VZ%7wd58l5_Ei6*LI7~@^ zrGz92cM4_Rx(<+1K@VbSA*g=Q%{lB;`r-*eBF-aoz9 z-p-dIR%E5F+>)9Iw4w_`bijqiZOn~D3Rx^pO>7LlW#qD`knn&$qNQgCg6K;o4YHYm zBuEYJ1}<9;BTpt5FmTT#PTFk783;^=0K5qUOL~3zfi4UYJ1bg@)6s7BEqjaeSF2sI zQIs;Q{Z4cmnb4kh*Wq%4!+Yp)eG-*=---`ymEnedHX6$W|07ePQ-$bvxX-7RG@a#C zh5awx=CaqFor;{190^CsHKjZCmz!6&3v5Ciukw(PA-It;gnk6@!!!t)l01iB%l#6} zTO5x8+y7^1VLt;ACpt=xNE8j-lFid>*3(>Pb|>7HqMjanaBpeun(!n>IPJGRiXH@n z_X5H(K=|N$YU2nlMF7H4cSKC9u}lay==HagCdu|jCI^e^dKn7G*?TYv;z_(^7gKyI zB#7h;63kT2BEdYLE!xaId?pEK@m1(QDnN^MK#P73fxD0W#+?9fjGoIa>@|%v922I` zVC)TnzvS*LVEo}tF&8Jg8Mmy!Nh!VerRWg8cOj?LSYhx;Ew5dI_E`2jj*nsl4?Wfx z`}DZJM;i6n;pUWGg^6eAHo~!sw@fC;v5UqHTe=$D!*F}6lyMP55sI$qhLzl1sIl{mK`AteiIk|h@2ud1XtRr2`>X( z?ArKvZR^3&SufS<)|t7C%(YWpX$E9 z(oKySIMLG^L`BkK&8o^Y^s94hFp7%fwG9d8YtM{bbH&)+YCIbm{auHvnENunfcXPE z8Uy2|qY%HycmfT4IN%0GWQ2lv&1BeiNE0B4BWc0PnOhKA;svlBBL4;En3@p2;W{^U zDkAj0;9ymszbS?7dRfRMQ_YYo9^L$_;QnysS<`L5a(TDVg~p+&*#i}3N2$87t`jQN z|3oPVU1%WT{n~v&Fwb6pTWHoV9SR5GLKK}GOgG;cZ|LvU^^XL$hJyQQ<2TRlFRzQN zfADg5?;C14CKJvZhCwRE}Hel3uYM0AYEVP3n786o(wd*xpS_1kXvUk zS2RrB4tjxSA%KcF8N=b`8;?aFU-Tfrrf|3j5=b&O>__1TSVNv< z0&XQKdLo0Vcvo`%0vot@wWic8Pt`(id!*F+2i87%7#8k%C8o{2X`+-q7TGiIM0txl zRFC6JzQsI$gqXOY3S5R!Tu#J^jvJkN?=f%SsdX;SpNw6x@e{-8e+*rFU;z;wAMn5q zGsXq#2&?vPZLOu>AjZRSJnbJ>=x7!_9L~9Ape3)UvqW{Jn zjU&kP;X~PYX7~Rx?K#3!6H{AbDC-WllAVRCf=5r6=fbi#RTYlMbSaq4cgy?UxOaxJ zIc=p&G6(e3j-8wC65+`6iESCs9?+$q4@{vyq^dBTYE(^$w1HJHEhjTJ%uyrOAhsHB zOk;p?1LN$JpegX^4?U6OhVB=R*X;4eT#65eO0Iy-2Eje!4TdsVsVJF(3C>TuS(rhH zmBQ(mJ335j={hcJ$NweQ+W{Y(+ z-+SH{sELAW&JitcYiz!2UiVYFYh-47D_CBa@PR>2hT^p{Ime8iN;ZeCfb%_u4PXi* z)x^vtw;xCydU&Da&UUIkY=?E<&>rJhGokg;F+%Ih`Z`3Ti$@LrV-}F|t`Rfb0892} z7w3YW(fl5AInGR{-PT$oss9f+1e~Ra_L$)I|NL0GK+eQ~F6C|)e>}N_aK;ckoJgO?3HU)4^Yq^Wf4omShXB!r6&+V#?j=+ecaP(^gp{ygkfjx6|qmIrOvF!T=3;VaJ7~PC>`jpzP{UNvcFGQ`QT|`88b62Rjf& zenZS_Mu+zfUAL<`J6nF}vQh_j3a_eIoV=~zF`Hd19TB4Ok;e5I$>NiJu9VM* z?l10Sv%Anz7Zz74k2%b{&6CAk&gl%w?m7)s?<4~S&vH9;Y{BK+I1to#zenMB za!95Cq$AKkBx5vyplR=09`o<37c>wc)dR1JkNIMhV1mVnUy(-$+jDd|<;hfHqdt0E z=|!o%v(;Op_MpjTbwD4-7K}5tbjj=vD0l8FdR(GkQSu?Rm`9((F#;TdQ!DOpGkKZp zm!&Lv5GMWqZ$sVYcbIKvGew~cSZG@$=W$w*87v&!n`ga~NvYMRe?LzU$^O=gIUwmK zptOPt4C9eASH^7o1=(^=s&M9M>X`|>_m!0caF=d9mm%jQ3jq0b=;hVJZ~OMX;cSk8 z&G;VF*6}U7_4wWgo@BcqD1DyvCBz2r&0Jsy3jXGD&_?8E4-<;T&joP>`mrh<2(8(H zqRo`K&>1i5nj6+3ZwSqM&W3f(0lR%CM}FMSQBk*dbq&e{qt%J&iiFQqW10Hq0A;d4 zZVk4EECBZ1M?ayC;>H2618mRA)HlS!%d&@e7y8Xg7zl5m?+z{$^zm_h>p^uIx$Le; z+#M@SXRD;y|=BCxw~1={=PdvDrJRhrW{Z?>a@kT>J|B@Ry}MsjD34|=@R zh7&tx9P>vm*~NScLRwva;TEojpU{H3|2M&q{~zFo5ALR8TSJDC`sZ!&oCNLNR8a0nuxzk?gMcbH^vGzd3tzZZg8<^Nl~u4<(XC`~R{@xe!N@u0FlAP0<5 zWjJx`75XP`VOIHt$tjz+x3~t()gdPP!ZkafiUxH47Tjel0K7%`4SY2p0J?TbgICZn zlsMibzp#;C5vr;f36#-Hyx*(FtFerz2SZSL%~XBNh|Lb&!L8KpUS2wpM(^67+OzIx z&==rB@|C+=2ie6V`B`5)Hl?KXj;h-{mJMk{>H0I}1t{`S08f*^)5dRE;j9tQDPTBk zHUh>n)d&nYM;fsieM?*j)yNh$dKmkYsomC)J3zAz&K9x&VAI_dsFa`^1~S4P#g(>T z;Ngs1(#ykBevd|T<(X2clApEGu7Fc^=j;T`%JCH#)xucU8}`Ejz143b7=8ZlTwW_@ z_rcSDK+m55J$C~=;bIU}alv2^;7tbbH0+ai`pXpFGD39r7kv_P4rzS7}Ms z5YMRb5Wwz?xC5-nR*z9v5V9(oDO1>qL61+ze$TfZyasf{{^8N-S`$(^f|aih5I0cM1v4u?SZu*<)PP#O2-JWWLRO5~ zwf1Q8UMpkwkiitVCBR1l7lWQBcru(T=!qrW7}*CjXg_XVagOXEX1m*7x3QcH2_1(-9kt#nY%e+nPD} z;lq*cLKVMa8IYS&9iAp4&sbW#<#;A;vRl0om=5?nynkedMi$S@F;hgP&3+5M8U5Ew zUnjUgsS;xO`U>!Hq?5EA!8Z`CyBPdYEYv`7FN%R>F;K2Hz~^a+YD%lcBaF=!fHH#E zv70Z7M^+EFe2&l`c&qf_P%ttV_>fYlq{2ya+~s9`ExQ!CI36ymxA)0<2vP46hvj2iheUX5 z5MA4wt@Fb^%1OafIiYfZn?bNm3cmHKfES#G25t=cqlKkiaQmkYxg2Ec007|Q-@tAn90P-d?;$S@3$PqM9y@$690~~DcyXk#b$aH+LgcFbnS55# zTu+_eR#;z2Q52ky4R~!S!NEeCHbX3IlPX;dl`YY^Bl6Uj~chMFb64mw<^T14@4!zeysXmo}>=SS2P| zabzusVjm3@ozR~)zd}}S3Ymm8)8Z=-=2YPJ;#KiBPO;h`! zt~?6AF?v7ecZ;x=tf>hR!s2aXTs>XeJVKwTk3)Me+Bve^C%S7xUhBV~)=uO1)2Nsk zE%%J5k54tN_#pank7(P6MpFm-kE?Cg=ii~j5Zjt=5zraOSjPywhh9ew?{xv)?|7Y$ zHP-4c!PCCW6+GiiN?)%VL$89^W9O2gpdW#bAFkVx^Ufu&QT2Yj_Nw^0&LPj#Z13Yh z1AKS2hF&tAgscj8HZT+LJ$NW!^(`=*iZ-4pgNzq`M*Iz_&VW}0wIx3!@jjlBhd>XW zW8h!NMtU#(MnyqsTB$Wv&o@54HBlQWG9}p7V%y#{R|xvd3>}pYDV@Ua%N}cSu$3yJ zg^8q^gbrr1(wOXIlC|t&mC>)gp}wUkJ7;Qi6Cv7f5XW1wF^~m*#j-et@_YLVXUP@ERxJd{7#eI=5`0P9QsTB1U%&vxqWv3 z>~-T;-@49SUp(B+@0|{-hhoJ+)*fNOaSYn4?MD5zaZ%SIAJOlD{t7%7OFAscvL_Y9$1b1F zy(*F`#3`?L6a}NnwVZ$MyT1L`nK=!;`rspn(b;Q#>s#Nd?+X)S zenG;6DxCP6dMm>1+rO?ZT4rU@)%Dln^^LFV%x0k)^BbK0^l1KW$$0z^yYDy^K`l}g zLHCuY2s+}YA}9yWtN?qzDaIoEhL@yYfJrWKSb?EIVTqyDs%xdIgcHSls|cNqgP~&I zD91e>ZM>2neFccgvl&BlYrS+}yyBG$t`miLw7SsboiD^xDNhD4UwO zCq)QsyI?S0684rVf%4+MIgjr6t39^y67kYotU*9dS2gm0*|s z1G6Y}sbGj+6i^^swu$-J1Vo(bSx>am=BJ` zqjlWDXuy>6K(M4YW!V4;ehWW(GgJ~t>qmu9%7X*K<)L&brTJ@)fubRy&1XX0EbU{Zw0PE+r=|)QS-Rb|X!cVkc zB*IQdY?(f#TDbgZL=)R_sple&T|DcfOgFQX@nUd|PPf2S#rr8XPO!UtzH`SQ^`}&> z*m&~Qe-OU$Uu(7CksU`t>KeSCH_8kOgG#T_%Qx6CJMyI4>3Cin%tV|MZ;V`ZhfI(U zr{)joUdsVZSE@pBhu{3Xy(4NHa1#($H29lwwo%F-DeP|{c2N4>@wB|Ez}Jv* z$Ke7|ndJU4amplY`1M=1`bsuMnO7S1Mm2`Xr4}xyJia}!)4Ihp%DwZ|X;055z4w}c zSKqkZq{U8+S|h{WI%mmjkIZPS{~;Z;KF;>j$?hBc>!2HS)`hNTkH#(WS1ZA8W(MoO zU(Ay*mPt9HJkrp`3n6(~TJgz)mj&d(tZKa)f6hPL_z-2{wgR@7%=T?|3lUMcoHCQG zzeGe7=g=)EDP&F=^!sD)-v2cEmqwx=X<*T)b!C^O<%BIGd>A1DbZB?2Ze=~F}Bvc1Y zv3SML1E4ikt)_1ggg4n`flG(_uT<{2ZgeQ_4}Hvz8qAN^me}{z&;vs6zg3;P;7^2s zB5PpZ=|~uOdQrH5eI*kbTNLKb^T`^>^&0x>vOM83%J6X96-gAw(lzIKzP=729o$=( z4BuBHMU&#WAYV_MWg>>bJk`#r0<+#FLcyR(6M%D@RGn zlh1z4UW@xlHg4^iwQIR=!_|SXt zjdg~tOl@gYKQWHj<^J1o8B1PR3e0vC*^kiev{1MQqo!W1gR3db36ITgj1J8NTESU8 z<4Nds#%SYz@o%3JQvN`g_$dSFZ@GtdN0C2yhHf3xlL(HX97x+M2`oWYusg9h`b#*kjUK zgwNv^wbAfka5$57)%@WljVmcr%DwSod}qlbwJhvC!bca2=F&%eD|)*=lI8B0*)g*V zH0?K8v;c?az}%U*CZ$iul>nv(Bd8}ee55ZyuzDzWgaA$` zp(A7z+(1A~A-Fo8aP)7T<$G&{g1YR z1+vB5j{RNYUFALe&EOWdfUALZHZY@3pIZ}RtVsat0VDwnnVREiYz(n8^+6&6c)j_* zdTT1dT``fF2`QAE(H1Yri^3lJcS9btDHU9n$Bi~iYTJ$b_f%I_OD4~Ib7n_ean(So zGUWQor0*5wdG3K&Ff9WFTJY_OiqUw7&!qD>^{%(S=4(r^xqBSmkJ`9*<`YK-=BTbJ z0oTtp;5NMhyT>Bi6MbpR`9S}ek>C6Pkfp$?a98N(5j0@>GtkC4O0MVl`QBuuQSNa3 zaNGOKmv?ZEB2Lb-k>%$V{6drEr*V>yj)<4&A;wEor2ey|c^SwWI%P~k#1cz#uQVAR0v&;1uw1KDnKD+nEoA?^a!$Vs!)v)Bxld3x2cuNoYdEi#>u02jPh}ASlJ_7 zD2n#7>tB0CY0)h`Z&xcu_y8tg+rXS0{#D~X*CVTB?O$;h?qcui5WN)Q39a|0c+Aj@ z#+^^p(W!?06u6CN6ei4mO7*J><^KHT`{7=fcl{gx{>7rcs#Pb;CU?vewIwY^OVBPi zc;1$cxy(kJFOtwG!&Yx<_xlb6Tw1r+$?Z+f+?vv7;)hgT6zD-7;8 zn5}VOb5*T&cgSkb95Kz*4-uCyjnj`W;O zYqZ(D{`A##SIK^5+BO__E%bALDVezXQfJN_lWR=;VM zk-hPpM3Ns`Ojcv+w9(~zcR%CU+R9(w5Jr==fmbcb)W-eS_uDNV&Z~-)#u}>r93R9~ z1OuG2@kKJh`d$K@b4^^LZZdAA`@y)CILdA1j&4iD=-vMTIH&K2O-kv#!5SqNSCMtG ziXG}N)b(gZe{8ENFR$7h*!U&rC~yO!m(;3BQK=Fhg-iT*HduDwSK9%+^Y=AAt-U0c zv6cpWg|OS8Fh@*5r(16a2aGE>{^8gS(Vz*H%fU=OS9b&wc6-d?j&n~LgU0+~F0BR| zL{?omeN*P7?uKOL&LE;=16uMn?lFl_+VST7+h*>+@}4~A5X07hq3p64AxotWLGyuY$6x;o{+*3CUHXn}z+lU}IjKfh z*;Dl?1`@C4PAiA)PsFU{Aq*{1R$Do`x^+RddYMrZ-tSS=7Y3a1{YB=_@cVzpet$pt zV8kc*g=lfZYRIeM#cuyvvil961^&5#_h8?JcCLM}Drcp@z0`F*9On~Slu-xp54`JIiwl=A{fr+=+!^nIS6-t4>aDf$)X>iOnAhdg$kvf#j)u5=DA@?X+?8bKvQ3IFu&nFTF(A^jx9Z<@NW#(_VPfWD15#yPW z{qr&I-SVi-W&qXA-u!(n3Y^OrTAx{;=1a-wWSNHS__bfIeVTER*4Z?uc#Ajbh9gAA z*n0Puv7iCUtWmg(dd7#U*X*SxxFS&LZh{XeuT9D&ob%wR#y|0~&t?IIaxy7WAAa~TG$+FP`kW5v{#XvIKzU!wgwGT5Vl{$DV1V% zQpQQ_nOKN>tdcb)!v=GFX9&DJSSIz_b_b>pmKOIec+A1whbP8Mp-~s`g#)3uK5tS* zfGEic9GCGs5;AsIegwzT4}RGdAU&?1oXtzg0k&(zcxlRDmc^Z6EiX3b%_PE;+7b!z z0l9IUx?M(!Vk;y3od;u=FA$q-*guq=if6A*Oft4vi9||k>pFvLIx<%`1f4(%1S}b} z??_`p?j-kQsy?>b_?O6^>(!D(+HW1?KG45Sp4vxDvklooELe59!HX`nn(@OV^-k)#cJWFg_j8ZhJtU?4T2Q<5Q8)br2;!DdcKosn1 zR&cfYHvM}3r_Iu9$gT>sWdaBnVGyr$)_ppF<zCF3SOUJ0X)* zJTSa;HLZ^Lvv#N#`u*XWj{8S^FJnlfEoGLyupou`gznk|6$QxcHmjfXtm5at0;tv_ zk;(Edhcq?)dc)|}N$!UOEhyJWekM0k2Mc7VT>4qy&M6{(Je0lP8xJw!M{^6nRY+!r zxNqC~%OVLR-cQ*2G31F_Fd^)RCMjQ~F7 zQG*$jxtrM==$>32MR=gcv~VOWc6fxzez@<7z8l5bF3O?QGba0Q&Pa(9NEab$)5O;4 z>MV-ozlGf+9&?#Cl~iQ;pv66xu2Q=YMXb|Z<7;P8GJ(Cqt8iP!oAhi1_sXTttt0Xh zLQAE@&AUPAq7I*u9D)6u1}4H?Y&MO(dlzmQO?5Hsh_+=RQHDXW+d+m!m!T_D#zpQ; zmOF;Z2g@mK(%{y&5`LJGc1r zV^l6E#pLSH*ujqJzMfKRrK4$QiQp!7#&qcR#mOqqJnv9RV;(*LCXAdAa1jv(OLnrc z88m-#&GJIEWzuA$#1GcTKMud4W}60ZT39(0$}zYy5lT3OhgL74y%ZDPA~r1or`<_k zn{(FedRmd3k|RX+{TDKO0c?IljlWNOCGr#e7aO0GsVxSVWdD7`yi%q*e0?NV2q&#( z&z626@s4WfO2?H(yAtb&7Nsf8eH(mP`mj->@kFF1g&ax-Xh2C{>t& z?Sxe94ipEBxq{3sF^O^h!7d}G*y9-{^Ul5_%!6#0Zl#QVjDSAK7S0-sh=V7nSkn91 zJLhWYNN)`#BK2^p+?se*I8AEbtl7HBI|By+=^f63;2IRiUUkK3r+0ft1N&$Kwj6WE zX#!SeFt{d^J?~6Yc5}bk*cpDUm?~unH`%2E79Z?qIuKtXdVPCdp;j`{owDhhKO(kD z*P8+)ZVuygSpqn9C@@EZsiTmC^PI z((CBxx9QG#ziOHt7|MZ3X`)+iEPmnN zg8L$%Mo%;HNUl(wKbxgAl>!q%-bl|1g;>P9BL?O5W-|)`7dKIjTdls$!jIrd{>dp} zUqNl7HkPL&>8C&7k(I6TY3@t@WZV|%u>=x`8d-;oDyK4;`bA5M_Z0_p=KLt%PpJp9 zgKPL}Ay0+o4Pt~R)104eesFVZlOoxc1nc6*kQgp<4-3WgB+uwzX+3NN(K_Mz+I7@>6R+WcIp9Xs$Goepa2gLvChx;%7tebd|+- zofW_-I!9WJ)uq;9aKK8HY0r8sgM!tdF(}eX-x(%1*Ik8FENAu0t)Wwc*%^QNa80N*i z(bd6|lCi1)PH(%acP12DOOr8w@@mYaU7gbsBT(7oZ|$-)FAk zA6l;}+}W?M{e(XNT@BHm8PT1yIX}dVI8dBwvp(v_OaklNz$V~*s33?!B{K_FwUW!w z=%KJ`A!M=dSW5VYwAPTr=)~COeP63D`qdRzKsQ**CH9VJLQ{{+!$yxP;~)3NI7j%j ztv~1Ya+f&*S?6+gBxDUrRnDV=@T4j!gpIXO${5%BQkuqBQcHuyl_6``>or^wIHDS| zDXo6QM;vyt|CU>W3^Y;QsS2{$pte_sdD1I~HA&PkD48W&Z%q=h#G843(SJ5$V&_O1 zxK+WBr*Rh_zCqYO8Jd^}{3)j~m=duGmCS04`&24Z;@Hf@eAsA~m%QVVki)As>S9^$ zXlbq*_bg-jwk5P^EicW;emd4=4sGw2oBz_2US?_OO<_D8I-u&;45e=AsjF#gZR z2T{8QKYK3LXW8_KygVsstmdq`L%hRV&Ep}(vNAC{Xw4gD z=45nP`-N)lBJ{9{9tGi5*0|O-U1`#&;F_l2RlXS2$TsSBd1vFphy^(C>eSdgA6_0y z*O*Z2q%jSLA_F2i>d5s3hA$fzf!}i3sLN*djYYAlLf^f%kA3&CbNXX;*l5Hdoo$27^n7*GxY;=I2ANqf z!M%V}WrQN|!Fj{L4#opg9>`?RCDmD#Aqdx7;{a+|p1^UYQJg1kkK5g65!KVBo7n!2 zZnn+#H@?gC@Y34D)y;Y*BIlzCmrlN*^TdWUq*e8AT@!RyC7?o+F55Sa^{ z{3y+eJH15r7RwCrhs;Tj)obufhnxkYQfaZ7-0^{2Y$|9k1!wU%|BrLYHGPmqj+^J( z;Hj30rS>4)~V|fK_0hcn-%#FE(gKm%DH`hr*EC3Fg3#sqSNs3fDhHNjb{gm0!B=uUl zYR4C9Hb{s&^D>x7!Q2?ZkkZ5WFTx4W`AtW#Ip6K`INe55M72NSPFPf&Bcj#Hg~Zfv z$HPyb+j`FY@~F%5rl7@VNT{Q!nL`$@Lv00^nhb+u9OmOhPR3RI4H7p+Tc+N6U-N;( zPzmdZwTDUUYx&(7}E#*~vOF=BJtq9wBpmV?jf+L3nB^o{pf+0gE_osEsl!I zPJN{&N;ZB0`YARFG`Dfn7O75UX8OVChD)pTEe{G*{1R6an$I7Vm8+uI!i6iX9B3)+ z3@xN^!1uU@)dw-zaTeSnh88C}PofB;IXoJrMJ$J9Gq}!HWY~^sxli|3{0~hEs}Y?^ z#L7&JTdZa-kq@iverquvCB^{E40wKO z-WPW$r4nUa;1uSRH#NW&SMS(f%($J}*AbEj$?%P}|I1N7O_bpR-Zez8@pV6jnzzW} z*UXK%wAFETx5=*voX+M}q!op*DeH(C^+1C!^iL)8g845Ue|k12dgja?g*+9|=?uO} zuMYq{c?ma=iO$w((2b|R_=h3KSRdeAV*udA>>b;d#WW2=;1ba=ar+#E90~GHF`pn0 zYy-y$;j>XOlEKPj^)bO6)W&Rny;*O?1ckzCi^z>mwLIoAJJnJE68Gn{{zM_@jfCpl zm5nbt3*jlJM(fn>yERjNxRl%B;bYvKP(kCZsM7{yo=imUb6T^`lH3r~$Aw~`u&{JM z7>PucDuqOD4A_cy9}+&6cIdQjR$u)EI8f#w&w)Om%W)N&B-A5ZV!#g+M2b?e5)zXM zB2oSW|3fc=Unz=(pQC`A9%{xBLP=5`e>loBIjJ3Nv+_rX0>v)Bq_{N|k68>>-f9Y( zgEDu{VOHyOsllYys&(v%OUqihTB(w&Ku%?l@~(z8zSX02yYpkmMo$5HbKpx?VYA8N zoZ43Vfp^;I6#`t^=GDuj+{C^cm*yNXpT=R!1f2GgrKFR@6TvJ$VNP=#fw&kHR@qj; zHmsIx=`S6&CJRf^i5tr@y?Rm{>+osF5%Hh((^pqp4O=3(M?<$;2i#sy z!oT^)o_VCzz{QL{&Gc}twz#6dd4c)Nd#KJ6TmMn)tj;6c=J;?-PVPr7vLCmd@!QML#UjeK`_Fx~6Y&*z0PK&y>(N zG`cQA($!TA;rhE;hO7cPG(~MeuCIMOu*S1Ud*`)-@&LSIOF5qXpPzSd6I6A z%I@R<{SXxdc-)9;+u*7piD3tfRy006x3yl3+6)1sq8O1VQj{Y|@cqvMXMr%fc9K`= z?3LD0n+FP7lZ6Fo9DE45R;t+0OgEed!5ZO!=m*6%ydN8-aJO(m3DN?l(K>oia3$BA zB8n!c+8=d8tVbGOx2XIHPa+s9M#AQjuPhXYH9HjgL@wY-8kI zZK;IYv*4HOB94I5t+r?VqO1;^Y#`;*_4{(3h`|<_{AD?<)%%9GO}fMVDo~8;e9si> z)|f^v+uK$XKH-$UlyEbF~lL3Y+#hnNY04Hpb)Poa;a(>&{^INA)N!{?cqO8mpL)*7XgUOJ#Ui~tE`ny)g))NPPa$BbE)#}y4Ljkj;$i#M9 ztc8;+TXEDQjM#2hKhR3k)`6@xT^gG#Mm2S6!paVAYhGXpHxb?O<5X%8ed)ep(j@iMU z5=Tu8IAHz1n5mw6jTwN1<^Jp|Bd+0xF=u>O?F-t69_DrrI=2S)EE?oqE|iRu+M2E^doh?X zT;my538W!iqu+FJ)M#yivTuZ_pvNVS^fG}4lOvHD z@hi*$i^a~*D=t(9Q&d60g8XnJV4e*#7$Ln9T9gw23lm>6l|DfdQLg4tkf2_riH`a z$>Ha@KWZHkrDx!02D1oDM2=r+xfNL`kWrXZJ^+CH=gFVY;ulkm$P#`04CDbeOx!~(^d#hiw6BJ`l#8u zCr+9BbK@l11L9y0feO#e>WYR+Eu9gMHFQB%kPHb=K3RU%QNoHqs>Rv?CEajJm$}o& zPC9>KhLJgzXnc!*i{&!dvlN^X=rBokV%EHbJ30sN48NO*NU#A2ff3o%sdwBm6TdtF zxb;Xg)Qi(%2aG+y)!1{B040UO%4rpbd+aK;M=>4C+J_Eu@@=(vMk-eW>rTt1zWsNj z#p81`dmhodh6Yl@aZ`PEI6f%YxZ9l0lE3kOvt6N;ro3_UR@0bpXl{7cJCp^ljYVTZ za9$OTT(`3L;O-ao_VTF3>Gnr{a?|+E-L-@LA^CMoo*&_o{vCX66uXuaw51Cq3?d2X zSz@q_0&bHY8T^%Cjs>j&ma)*{3YF(*tqXeu63p81$^0p|FmZe??LU=!O_=|7<1eHU zZNTF5=(EF_d?{hi*(BRczNp0(oGuqH4d)v_@XZeuZ{78cop&8tiT>mG!Fhf#wmmW- z2zN_kS|1h#M|0I!NjP=dB`ES*r9SBl>Sw2y=I&08X6pLuxZ{x<9|eEm)3~@k%v0(5 zY~UeSpe^dqHS&Lx7-@Y#70dhul$BvWi#rX1!cPJ{5AJCK7?1bBGTP zqIcKA|6*Oqmg&Jmon`!vg|zKBqFeT!K|9$xWhtP>h@F4TFze@yb-;ahfdzr*sx>sG za9SieiFI#U3@Ny0tp+wECN!QFC+uhzEHKviO^@~-*BTP?)<96G0@!bnSDj1s4vDEG zsd_WQFv!?sSl4jfOpij4WaX>s&T%N0TEYoH)(@S>rmaT1ie^Hdm1^ zos5Qb4y#1vGTHAHM&Fur9(DC|hwWQ^E{EevyU(l~HRazi60cVUA>xQ9E0NLgC^ssZ z8AL@$KV-F;w>7Sw*yF2NM*IX)Kyp073VcJ)eZHwMYk(muVc#LY z+!LqRx|Kprt&9c2!;XH1T%iTOr$udya@;fg`?)dOj53_%u8hw28={;iT=i2^A1r8i$w27&7OaT|IBl? zl*5zqv((CCM{ZRbQtnp7>TV5kt4vw3U0ce=d{exro)?i&*@K~Zhi{A|3dwB&A+I+e zi-hRjLOZcCT3BWk5z>a#1X3&K5G7U%FNk^~d;8|vtJryNM=o`&<2Tq7(45p}aHAe-^pc=V=q7Q&r24tkW6a#tbmHKuf<-~qUkmxbn)<;P|QR{0Za8L@u54 zXbe2B(5jbAl2XyE8xDI)F~24ne1scwms6wMBZ;7gbE{oCqv8YEsAGyB*(03!&v?OS z8B3&$o`s?%ZtyD1a*3Q@RA_`kZE!G@x>}eyVZ$wjOyBJbFuVkMA5i@l3j$PsZ9Zdh zHW}W+d4Tb@7AJ5M0Cn3v`Ouw_*(FGHu*eEr?6ISSf{+gQ3$>%QV8&&u=&iX>*;yLT z#ypn1R5N)=nSX-gBu%y;jyw8@Fg?}yxc1>hsqybeqJi!2SYiMO0U#$hLXYIeDr1$) zohwteC6odLG?3-0ib#t_!w<58W~#0f8dp2Gk(rOVuc0Ame(m4;7WgUTW=c#hp$?5A zGx>kO1yHI?)=B*g(eF^M%rZ!HC1oTooh03N#H=iE-z;b2o{0H(4@5?$D8-WNh531* zKl3W&%v(=q5Q@2MDO*927&5R6ZcrP8Dhx?H!+nVowB1XFVlzSebAK{A&)Jb<$z$fo zWCa_stpqwU7A__YzJN1iipUfrX^|gUNrkso4I&>KTy2uP#FH0g1c8qIq{Bkv zzneAz09bUtbpD}a0?l__8PHCA=6=`3m`BU1idSbEU*q?7*$VP_vXv{^x?86?=J~|? zec={DS8B7e43q}SPSGB@`wcSz#TEraJIv{4=2ZP^p?`chEnKh#k^NcQ<~IU9>R=Rx zIZD|*u}h)iB+sTWVgxknd#Mn+N+TVGG63b7qGY|% zT78c#;Etkp41B_9-J=6SVKprx6uwrBZn&9waKr+rFtTFz%u|5V8?t%AzLM5kWhjNA zLP}R2)DL;%f{0W27p>Ta<~vUFG{brfH@sDsT6e=czL6dIxlOnkvbK#_T#R5LkFD#} zUum4i>RF33j&8S1o778%et)WTV65U*2&RLhqLnjj%5t*mYFa>7PzyE1f|2m3aDdrB zhc>QxvB?h}Qv?hRX8K%AoKduNy_h=HzX{2riNkuj{~|BHVJN>ZokPJeUl zIzA2^)d}4W$c20tav_b6F|Xfc_S7-xiiKelUFZG@Q)IDp+xx<=`cAbHiWWyhN9F=0 z|1E{?aF?D#9>V40W1kR@`xnNH5Se^1a@Fm)9Wqlkyl_Z+o5J6YY-b``KtFiGvAQhs znbzsjw|^b~VZgVm2}cOnDD+)^NsPtoosvK6*@#1XEXSiAQ9Skt(qw4qVD4m_&lP;D z&g4%t{zYlEm^2mrKk&nsnX9GPUcZntt_~*(`|TrDzl@h!RaUdt$opd^?xu(>77s=< z)~Ld#JC0H7s;iW0{PE=CWZi5o*ye(x6aFU@xhi1avzDMvTM+kEf3kKra8d}j0DB08 zN5S40G(JUi{2s^xF((zjmq6C2lgfaAZy2wdX~z3=h^fHCQ*AXr0qLF1&Brh*ZB6>h zevFPw%}TvqY0YuJare_?_a_lb0ovuveJgKqxlI=IBT|v6a_O_)tt{43AY)XZ&?jB{ zCYTFUZgEvLg!0z1H@&@kv-OH7~7ER(vLaJ6w zhJ5QPX0&vellv-xO3*A9Q>dOqu-@5VMR-N2pv~Hw^+Ls-7|00 zsjjuxSkI|0CsS|t9&1PSOpi}V9)TW@LyNce-2orNWUqiJt1~=@Lh(f?Y||yyu4R|r z9oBUk<^PP@(N?uuBa!}9Ebj0EQ$))9N@q1Gzx(d_u02*|i73ffm$ig+ujkUc%u9Pc}?9)8jIhNy?PmSsxqz8h0PQHxslKwQ|>)Jd83 zgnM>d^B1C+^cWO#WQ(Uv3e^PSZqS+8MKz(014R)hG5KrE-EcTsR0Anu0h>iTGK)2l zCgZ3|wW^+@TywLJ-`quWpRk4V7DdpZ;TnH0ns;L#|Bb22!Hn9TFa=Y|m|j)TD$jC* zC{EA&io+6*RVC#p1c&jmojv=0qspa7W&)nDsu1EgyBOm6|D-v$sAT&!tvA!M`4S%+ zts%CFo)@!K&9FsUZ>LZa3V+l+Pvfy}@^13g)BO(k%{GL!79W6J!n5@LrX_Ir4b8MYoEB^U5PXZVA)D)$52twF*>VFQG6+`3uKP?Oz4l;&+BjpCBplBkCa zaf?eIF0febmbcn38H^8lbz7pINl&17qENf??N98B$9+QMUEKF>bB6mdIAgV_(`6Q^ z?L1x_2ziqcZFD#iH9OM&fy~nWfosYKt_`K#-{uKKi22N~uYHMMz&8_;3u0rItT(IG za+J^U_2M`8X1LHk!Z}6&`Y*-7yw5)rH<%}Ew%h(_fBcE>*bii<_AbRs8EHndI#xWY zt~L_~<@tC$+xRjN5&dDi z`}t!5F6%c*r6Imhod4oBp`ihVaX=kkS;AL6aL1Z+XiNf zg>Whv^m@&*gJp@}dzT?~zkOM4TJl~RN*AZIm0euzNIDNhN8XxTE=49|376BMtS_H= znlDQi&5FH+Jt>vJ8^p2oW!ApT{fxg=#2;t%x#q+$B2{(L1UTp_Jg&p+^3;84>VWp(i=kSmf?$~2i7;oo|6nv19B@c{H1WZ+zNfn z%#QGNBO>EweBQjmJu@P_J01+($;S-|Z_4_tTll;dOoB!*3Eqy+n#mO>Rmtrf*Pr#f zkh|~TXM)k&IUV9}%$*!{7K4os%1uhNNj*>(zVZ3BUEIg;jY_ZqVi~B5-w=fycviMR zAaziPXo@7*$s%axOvWhiC_r6l-Ifh0t=i+UB%V~t%yyHrGS_(Od;2bPSsg(SzT+iS9ce#Df!QH!SjhmdX$Q@- zo=4f3(8R=9BF4&@!8j5Xa41Kidnn#H8(VZBFH4SJCRTXD37efeD2vzv_*5a*U$l&E zFXY2ct3YMe$wPO%T0Xb?CR!1&$uBASixocI7}2T1rLt400`!o7YvcRADWkU()CVPI ztsPvO++PQWlP-lrz&57a^iu(B0JDW%l>=p~M=zI1#t-G~&j|vu9*dFyikj2i0wG45a8<2`^6lINPdQgtyv@oJ}TUg-PVm-NYSd8i&5e+2Iy;g0}(C! zD;di4KA`9or;QI%WZ?F1q{Vh7df2C=tV&eF^(85fn|#F-g3 zy!qf3GRTbMT!=R6dsN7R}Rm16QeC`X?)mk z3wcatxsglzR0Bb71rwXS+-SgbTAOB^5(DVGS&=as zF95g02WV*PqHo%o{53z@_<`A4aXZyZg;E6v5=>X@sj*^4DM*wiTR1Tg8rtr*ZZD%g zue8972J-eV3&Q8@IhVs+zA|#e(|A$9N#w#ZbA9!pOWfHlBQyEH0U;6hjlr*t!LJd| zJtB*y?X==YI~vIf@foG+I>yXf4;OcW3}efQHdVY#xsKsp^km03=Z1$HlVA@l&v>2{ zn2-Tg^}5~%%(TE?2q*XvbT^D9k?k}mJY3?q%^AU~MWJy!|0*xEq&A6;BOea_S|-_? z4t(M=cjFiQS6Lv-gKPZ&_hVp23iPFn$G*D-Pxnonf@#OG&|FzWj-(L0Y-bq8T&0By z3EvhR-a2q%iwFz}#L6ZxWH7n=HLK-C-?FC*_wmdT0SY;RoX6anVyzQ1yizEgmC;eVY*2@I+uU&;4EF6U*CLq&2!0X-yuaHU3@p zC6Tm-L;d^lYtKqmBO4&98Y%XS`uP6>2crs6KSQU7=fTAV^d1+ZbPHyLhn>N{!{4(T zfNM)?La5eeio{D=jc{)*-s)A_;@(4zpSLI%KYU8C-8&_8(=j#~BZX5pKFbYiRw_T| zRYq<3Z#tBV4cXqpt*XW^cXu-~Hi;v@vKY)jdBh!gn()4$B8SXFqQIT%VS=Be5^4r32dosAa(6S=ySx4d|+TJs3?_~TeQL;T8AAJXn zIf&l@|A7&W_PIw0<=XCe|Kg=e??ik9?Fsl;;9%a&p4TPs%+{iBY-$s_lXv4@T*qEd zFV_DU*T9z_AF$i*H-@ zW#ET@1U!cce3ugM6?NZOh$v~^MphufQ`^Kb?z~s)V2=|xS?-=zy!c!yV6EswdaZRS zCWOrTnAr%>V#k(jD3Kn#WHN0Th`nuRc3Zf*U0C@me(zi>Zsh(XQuG%+7W-g%POFK8 z27-VpZoEXVSIVbde&rR@_J^KW9TNik>z`%0)sNO*WH?n)>`@JOZbcK%Cq}+cY>(21 zi?;(|9gW_!9ySE*Pt8SVZt8$aHGa;3;au*{B}rTvJx*A?Qkh)t%*2DA!2O7Swgn{h zJ_0kd3>h1L-MJ=_DdikP$x9RNF!(Daz=%B#hTS~s4U8>@tWyEVyLJOxoYd?7O1AIZ z+?v66c1HgY)l)3Q(6l%f$Z>~FL*Cu7$(kjjAD>p#75Pd#wi(>VzFsO}48-Z_yKRG~tO>1sx|%3x3&z2$HdAWHI)%C@4WABpBT^B)ZPL@o zb34PuM^m4#WOYkY-2jvEG{e)91Lq!IfU0l!P{L-SY_46FVp7Az*$@Vvs02xK6GOu? z@5qD}Pl*^>Bb7D~?&%e0f%}aJes$)(!Ax6Z&_B;2b$V#>VQME*#!_KU{< zaNmQzr9iP>gk96`kcb~7!3@ROzlK$9*`)d=^Ggtg)8k|QhJEeFiYsOM)}4;Y`~$j) z)aF^>=PwSdBsvxEOc8mVXeo(MuJes zw!Z-15cv_SSAV+W52BOCL)7C9SrRJ+HUeZ0<4ykw-c0Jl$5Dx7T7!9Yx7a%k15rU_ zzZ51_|9#=~CiP|3C!kI|bn}*GcOE(__tcV`Z2_hh?)zdxp5hDC6#4(gx2TBUyYn_t z8CLA!p%B_G*lt>0#2fW?@s08yYRpRvmsUJ)o(~`1w2Ua6Qe1sq`>E2GK0(cxy|vqG z!OiQ2tJUQIi_`F(305Ce;1qEO*5?fuS_4Oh#nOiDnb6GsSKhO}%-K`_wtbd@tj2^#jCg5a4R-$SrHn6W)>LRYZ&lu0d(qUf4q^Dyep~H2x2KYdmh4H5 z)#%Q;lf!$W%f~A(zNm48{54%#rLri;iV+~44OABFE=S|xYVRt(Cf#Bc{bzbynd!!hL7A@PK|cw>M!@n4?4fB%WeX{#zkGYSb|VI8~OR!@PeN z>48UYMGC>M6!gkV&M&Ub&=L0^|FJk9kjvF$uf5aOxca;j8ZAn5uY`|j)!xXF7y(PH z{i?6X{X3tgdW(e;t2+4+3d32+rH+mgO<|)1p@+b%xl3Lz0X3jvK}SYsia>OX9lAKy zTzfiFwAo@e6wE6_mgX|=fh%5wj?%A_y7q_YQtuv?2oIa-WFVYaooNTl(R8V%{n z_ta8ZHiJ)0y4>(tOR4u8=CXSe&W^p3?6*SpJ+N0wS^y-~>Hz)D^0(}E#9VN2hhK)! zU;Em4Js`htYEpg46Cs=0*0}fLqir6I{%}+=l^@wO|K=qe1&WW`%R-;fWcR4QLZuWyGw18*h%B$DY5AQQusK<3#w zri+jar!2QwNQ`3=qj(mF&npT&6Juwl`MEu@E1dLG^7#q-R$c3w=_f??4 z_M-K$;Xyd{vZU{N4q(T>S^EUq5!C_PZQ++s1*ftv@6DtkP5d?(6 zgrOz&6q_B$QJ$N%`J*aX)D*J`<2ifKsZ&^NI&HGJWnefNE|i^>K|$&B4dt=}=Ah?Z zKGb;Jk@4Ft#dz_c#qV=-%hQ!TGk%v%C%3sxroiOHz(BFzJ+`}L|;5C-v2C}P8x7!Cy=F-&RZ7RQ9=9K9J<7u};X0{dvLpEXX@*T!MEpw<0 z5hSWYub<7d-yX>LGhhci1{nz%qaC-t90^-SxWim<@(0c-lIJcG=_8F8M3U$-cr7S2 zJ@&qk#%k>q!((rYacXy$7;+=hi zrx>OK>XCPGy5R-y&_7Krrn0Oqr*m3nW|!@$1CE(OvLE9uYMs?6S4=MoJ?TY`B`7Sd zhPX|6Z-u;btYc4UR8M+UJFmFi5kNChaj`9U?&iXfY0}gq=JfGB8^z>VMuA735)x8P zc~ILU*UWTa6S-RcLDxw|uHz)4UYKla&#nE4^{MoP#XtpG^Q0 z+hUcjwt8HlOg1a`B>O>Q4;6?7{qkU&kkjEMy-0V zw7%=s6e#I2M!Zh1Gv*5i?@3%^38!cNIw?p7PDwn`_Al(x6_rn#B@_u;)5JfMN2*Q8 zNfz0~Ki^**ORW2v<`;*i!RV>%Ok3xMUP_ zIkrH#Hp$KDU;pq_N~BK>;_oQr>M3>UybHZzFg+x`4_rz{d=;DQtyezHw22C{#k|h=>7Z+K%<~?A4LxBQrLwoU%}R?x<`e2v1+?%(i>xyL%JDsH~0r9 zDrpqN5c~CjI%92*-Kabb-|V(#xx%mo1w{!Y@lT&6ljV56_R$yR_$`l<}o6P2~dehNVXV z?TVSDam~dj;avt9%UHR{I{BYsc0qe&9lJn@sI^hh-jl%WO}InwD>iM`;dZ@D3jr>v z&n?eNjR~hK>114nBJ%lEp&FyLvfss0p0%qLBVEkEO%_91{vtlai!&Iqm#2W+-@Hei z=oCrt??Gj-97-+LSDUDCO4DC?Bl-n0}_gE6u zJ${Y5MBHYKy{XP-h$+O)aPks0%h{vNsv8^f4puD_9TFeW!{j=B>NW@YYi)O{?JlSG zy?`kS-}27JgZ#rU>eao@Vxp2t87me&kX{E1E-iRKJb7!1)39NZ@}0)8Q|Fz}KET(G zNPXPYq$@wcc7QQT&_{jK0=%4T+ayO`BN`xZNX4G zn?x&QFnaPQhJGLO`hUEh(OgnrM|yt0ehy8$b`M09F9Z4JXi~c8zP21(Gj8l&nG!R9 zdhB|~m?+YrZk?nx#soqa+|5J1qP6$Hy5&>KMFFRu4(|-eKiisay03K=ZH?Eh7Y>UC z>&1JwQYSibWt+yG&JYLrWyrXlcH!9DUf^tzyaaD>1ndHxy98os$V6{H%RyS9JR6Y4 z#oVlrMa^=m3JmxaIhop-3rT~R8wv~zMU5r>R4IK`-FI}lI3##tMmx&WmVv@xe@TX@ zt*3sKIaBBVjh^m;{#U?1H}bqbyjTsC4JBt~QWgH;uF+x$)FM8MPU!Eq_bbFmt=W4W zd`O&Gg{}UI?PAUlLR$A(UaY7sKTEP*O(%Hx!eyJN!J(eHh5vll(LHm1nPyr}^HLIq zU;FR15yly@O=QyQj>p!wCXqV>Ng5Fqy$ueci$E=>J!`aCoggL-1@W(2P9@VOiaGQ_ zEZwq>@z#)7dfQWjPZZ(6{;&To zeYHi~9Saj}chBW}yEqcmlJ!>D=PcC*y3dUq$c9ejdBkmAK^o_Px8hm<@xTR%c+WqK zRS{0@WOB8`C~L!dA+oiq1l*-9xfnL2g?`|U%EN;_go%s|(4g_h zXl?&~jk+DT`;!AJGE)k$XYnK#thELesXil~!$091t^^Xo>n_m3x{)Q=)lyvKS-=kQ z5+GFDT=@@?TiGKp;8sA^;Ogb{)AOo9rO@xuNtH7Guc9%poztMUwkc;kk$o#<-X&uD z{?eCbGJaXZ+)}v6?7hFE@U|DshIrc>~r2`7T?D6*!_l&;bH zn9?atc!L+QU0d6sE{1RHY2iYz;4Ty6>v&hjz+TGk6}L?jL=bS$%p$$7G%x~J#@ECo z(e%hD8XM6&9%=kFf4ZFnwgw^^#er8iZ=Chp^>M%5;cF&=jeUiCxys>M9ImI6;$c`P zr#hH;G2Hk!yWi!p6AvTpm;H)Q;JYK3RK|YUIxp0_o!4|RdMZC0jGvmy6Tcot{dF_**u3 z!wN_}lQ)<=N9u)61%=M7`l6#XvDl`lV)HvR*h7kE5dfBw>2YC^xyjDGvXBC9$i@Cl}<^u z__xg*UNBl+)3fKM?`r&b8fLlnQr}VTKlnw;>RVhXOp92Es|h4D24*2W?JY5&sisz- zZM-WTePgQ$e}l!VJW8E#y4Xd>y@&d~tW26MF11OewI0#RK!~#?ri|IVIZm&Y%2P8@ z!3$T!0cU0GYL zZ8ijZ7P_35)<+`kk`Fu^p~}-dl-R4ta9h?5%WlS;(-W4o^m9M-gBob3?$@3R`#q_M zy$}2%`rHm~x9~e_<%?gO%AzNQ7Wm=!W7eSQ=wFUH$ix4ELe#@k%ua6VvXibdMmsU; z!T=KIrYIQ2ZGZu>%g~w$JO`c9IV@&#R+{Dn)$hS>PypJTwdO*3Z#J=?r}#I+ey;9) zNB)1QuK67C+v)K+^!rlR(@#$z8g4&Sw#FxaS3R{tWW;15UjAnglb!6lzVDX4yZi2I z+E{01`1}zvMaBwdTr7p9)-^e-W(8VA1@0NTilL|4j)XVI(w98Bl=h>;EkQ$psTAsapp7UL)fQi*x`0H9-?Zk65;U0K<&RyS4srh1&!^=eii`xH$&YQbxzo7 z`%vS*y6;Asd0Q9fY|OSc<|XIWdeaztz}3#8>0=ZEAANa>{^x%l9z|58JfCvOep!>= z-njnd==VK(WwR=NF4fYm#o8L%wNN38C00{5JV-4-3q=mp=0oQfC6s34SFEp~^NZoa z))EGgSTJBB#uno0Q;7aw5ZQ$jUvpWEez`3f`QiVg?K{9MtEzNspHnyIoO3ug=Uh47 zTe-WctE;MWpc`mHcaxI}3Zo#P4j{^i2nafgIm{!Nm@$AN3Mhk)0mBH0S;x__PrbGF zIk)Oobzyq*2&MCD9m}npJN~hli^9NV>|o>T*5HEXRYr_KJKH z>L>qe0c+?HjlrmKlb|c;&8w<%U)(L}iFu;7)(n>phSQuf7|>|7(qpIL@`=z z<*q9%5uYLCa#%csgnYCr?DRB>Az>@eL`!TzlHn|Lz63DX+enpwOS=qjE^66sgBdHs zvI=Q-;3Pc@?@uHCY6NFb;DIWU^3(Yqf+wiqmLB)J<3@QwzBgaZTDRI*TW}Ca%#&4XQ_ix< zSEvrlB15lT(cXCbXk4YvGvL&_bT*@mD`{t4O|`fG^u3Ea#@U}e%>M!YuOQ<6#v0>k zo~NjHP}!ivB?54Eq_wt;A2B4OCV;t13>!zh#OP95RNcai2pfcJdGa2PbJPuw)74rl|unJ@kg8^5#LAn47bYkQ{Hp4~lQe=hEN zZP$%mZ=o`+lf%ppIUW~G~deDNN|Hjdw4cV{`7hn@cWp}Wh5!+CYMw$6L zpg&KW+Bo%ZuY|bTkq{G$){vd~`v5^VbYZRe10B$y0>)~$70%%p_yE^*{lgYf&i{wA zpz+QIw_p?Z*=K1iv>Rig{|)|sMM&rBUWw^zv67iTv0fI#$Jp~XVLAM zO7wWtFsxw!2*dPg;H$B}2h8=?zt+MSlijZ%>rLmU9$l+EZ-#NjOObW(|6x;fM_jZ( zzX_Wa%kzH|&g=F1@e~)*wrBS0b8DRro!G9Zr#{`ShO@d1?#7$DZWF!a_SgX}<%1&n z|9E0;WxkPue8qD?i_jivF!7)K-#eanu%gXa??CB1 z7qpolrTzoOm0$B8Alb0#DE=C3%fHo=_&cyKh1ul4>`*-Y*J5IdbIh;cR0#ZeEBF+< zXyyt#qdR2w|CLK^nOt))mPPs7_jj}AeNPTmK-AnFu0~t1Ec^%lMy|B83#9FF08aht zTOobg#^QvmHiTESe;?bbAtb?(ZVo(GAzM`xJ8x~^byFnHcV608Oa>B5YPlqM)`PD$ zHVgHIs6N>WCqsegkbQTv(uxij3VfOTi8M#g_IfPQd9}PdeC(>=crcKQYHN`LMy`+! z=L)7B3h^U8H4*|yp3DyG96yNFtB)!WI(_(?JJq}`hcmZ3MM>D6{*Tytj{f}QshpcRjB7+|3?bmMH7&q z(Cwnh&`toCt@3vfh@mVTfS!WLKs%<;>vu=J-RW>zD+m8OPkrYb!dgY-4c4^^&KjP} zhAdlE;vMWiVo@k0%Lox6)^G4Vg#bi%fXB2cZ@Yn1L6QMFbd5Ul6Y6>_7dv2P258;p zQ|lz5zaVmVQfXT>t;%Y9Vi+w(($_PULrLf{LuGDvt!3~~u&F%b9$y1{#P?sf>WcWQH!A9F zwnN;RA=;G$O%Z>^4*&71l|g7o>bM8%ElF?vi`%cMHe3+IK}}H{R2Jkc4+&we zYJ*an*aPSV5G@GrHo%{X1>~xLg39Rb_uhZ%7q5hSYWSx?fJOa-0Dt<-m9ZweGu{AU4b;*8>s z9Yy1Z8fJa5k#M<6u?Rv@SahzakLyEnnECR!%zY4HBzVbu>h;n*(K^#5C!kFve1JA# zFv^YDQ3HVZAWcc2=^~iZW)VY+($s;o`WJ@OnI$A@yNI^>3Ipeqr0-C?RbCEgw_AX* z3OY8$;A{ZfWrkA|DS;u6Hr6U9Kx(eNe;}oZ#7q&6ia)p!X!WQ7zv{YnvW>?|X&Bf9 zu$o8URbDbQv`bBaSdkwr^w_4D0;7Sy^+L8)paXl)G0BR#A}xX8ZOEezz!j&0M62Wg zPk~i(Eo79SKPn2sWuqn*9Dy=pMjFpiOq4t`z6_^LxdfNzWd{3+Y%)zLvCI)oaL;W$&kC?Kr*AZT%gzxZ<#gp#1MWOH3c3YNuy)x^z^yJ~0t6n#(9K8IT zp|MOZ6)fHA2soE~{eb@I*Fcj2^7O~6De@BKsTy=H-Akm649ikOyN((Qq?4~8p>Gi| zVP#HZCsypzm06_ac@YSfzfo)3fD)T~Ej+V{YyJxOhxo88WOAVxjuOSLp~Gg_zZDbq zDj-R8Y(e{0cqjZsG6exdVc34zB9 z59%RfvBwFZx1ePLhe&CwfUL9hONV0dW>&=uDm+zg@xs_C?OBwSVOXxh+zsS+=gv&* zu@#5+8AGjC*|X+K-Kp{DJi$iFs8CBH=c^P}bubiAd3<*z%3Q&d@TU5TE_eB0W~pW_ zMjTqBiK91we7BT4bJ+K#=^~NcF;-WO^wtwlM<5F-8@bG-(i_5lMcC7S)9}H()6$cvW0k7I9Y}Q-*$C_(ad=Y{hnJ;j)3|#?z~2go7Zpl$D+FGG7vuiQ&_)Q}#^F+m z>qOqgLt*!fSEQZDyOm282DsC~b^+rJ=z1T#b5ReoI7l7NK z>O$qBct!vsjJglc*G$RcU(6Q1@S(!s(bo7*gQH;gnN*KI0B_vKNTW2-s|z-Bl&m+V zW~;?*7wl`jdDp1buJYM^vwabl@T7g&A~`j4$?%Sywt<-;%}L0|O;;M31G;{KXq^OcjAc#3jwekMo3eyQxt7UQXNQs`SGC(&L0w? zeSwB=0DnF}X%Mw3Cms$WC_}d-i}TvhlunHWp}hEGio1;bJO8xGY%G=|UX9b6=+D&8 zzy&`9hX64ff6{A`F#gI)9^Xmr^VY70wjl;^gj393z!Ao?XrBbQlrkw?O?nI)5QK$3 z8fFmfu_fhJ$_?I<&nX;$9yGZsWjrm5*!r6?E9|ki>mH=EB^!N8*-9@@b zZcR^MwBHUCQ4H8Z>(L+~_NH|5-c$ffdiT5rTu> zp43QmOL$;P&_ZpSG`@m(1-xV;;x9WK(k-xo@$S9|bfqbO9KyM9cnvmAw?i0BeaIg= za9DkgJkI|gn5oNHaH;oAAErZs?E&=duU>?wDiJR?1#g^};cr3c5)@7xl^q?)h8swu z;?99>7<kXjWnlmOKIiEiOxP(b4R9P=E9*AnEMCnlg>3h&&5B zMPKVm$c5sbbK+{K%GZFgH#N%2svDA)%w7<5mZcn+%WrGH z8n8)JcWPJ<(hA z+*$J+iiMBPG8gd0{#d1c$&^H?-*rLFYI0HLd`S_?5AeIjhxRKYGoi|0v6!eu^C|8i znI#9eaS;{LgK=|;@l$%tT^@l??8f1c4s=_Fg+uwT_4t_l%g`F0EnQTymS2Ede`jZW zjTH24f$qV(OfPvZ_AMK}5qtBxvY(xV%I>1XlAcCLnO;{!&n(4$pmrWOx|v|i}?D1fk^}JPkpbxqO=*NI1CX=(5UJsy)qU zi5|g|^%12}Tfn8KN9L78&zQ6}hjzSb3I@vrpu_;*ciWnO|U!l}(o!SJ>?tbYyNds`CzEDWk(fYw1% znZtNDzx;|wsGV@Bl$@`}>m;}R>iIwWoDJxwp8;JrL;oAbXTph~j|2hNpJ@PWzs$4V zHOQQN57?@_y5tUdou*f41_e~q&K5u_vE44t8#is$=?Rx<-&#W`-rH_!D@G{~Oc?WLy!|3q zaf?0%O2m6MJUhxuez&1JLC{weQg`W0M z(ih10>Dsr8*q~dyg`^j9%D@BR&KBv*UYPso@Dy>T1r&r3xuX&TPm!kTwGv@6fV#Q# z9`ldFLq1G~|4)_PJa3PcE?wz6Sm$)4$YjQ5L-9VZHl&Z~tU7ZJU-$7k9X_OxyuA|p zaIyG+4S+P3WA+olmE#j@|sQFU~KwL$Kc_s zwA-n)d*w*m1KBFM4dmx6t;8Z{_MYJjX@b76BRC`<+3A-YVy=Xapc3PzSLNFd(7g_#+*R_$bZc3 z)J2?bZImA7)ZPMWz!TZp@LLn*X2>Ep*mNF6kdUMNvWCN-1BwJ)7^3vZx^#f|PU-1P zI$AbcLq-C0{9!$>xX~Qa#V0zFhRYo$af9wvb-CLXCnBbzp}5zXsordP`H!~q&>Pom zXU=i$r^MW|reoPoOALM(zqI<@t~+7R6h)pBJaD+kR*O)tF+-Rk#JHAF8BGvX=PGuAQRC>uz}1KH(>)=xEv>E*5gIM6=`>eA+(6t z9p={COj7I)3*>F^aw^hsu*<&6lN9E=C>_WcY<3eOLgW-SLx4|PxiB}hrNK%i$j~V6-jEQJ~WdUm>#gpY@@T4QX-JCne_&z$LAUr*IE71>Vsq+bCnVN z8XgU}hp@or-eRhL@hD(n{`bbc31_jy6%<~(S{a|QWOS*JF0bWM9(z@Chs7NSv&{cI z9#Tr#ky5y#EZTfYbMOzL#!^#-{S-ojz{pRK-n_3K?<0jzp$R~NOepdb)4AAb4Jcx*W3 z8tt{toKY;^>W7Esr?a{2Y>xjZo-4yY(-nXhFK>X~HN)?MQI^UeO$NURsOH?u6T$K& zxhvOSI(i|Ry2I^{PY3wh*WNM`;;f>SIwYsyjNGyHhS#t1D5x}qjB1N)D`ItsdPp| zEoJ0CT6PfTc1N!^<>-sEz(=zB;Oe`f2Oc9;Od1T3HuNZM`STIH4*Z)2<_KVe<~WNp z+Z%`lO{qfQ!sha@QUZvcisGQ$WV!90L^7s#(szX)2VW>_9`dt{RgIHToFTiIKEtI`R=Xj}jaJ&YO z#%un0hchgLXE0=}lyT@)TEO{p8bh*bIAV=xjR|+MsO;5i%u>0`U~7ilQKOzbUS28q zTv~r1G}u@=y6lyo{8~tFlf%3}QKcm@gTCK5;hoN?><)X7$+%_1tV*I}7>hA5X|UK~ zd(;PiJM%R*4xJzuzz%T=l_n^_6k=HNzcT8DeG|BETT#$2UKUc2WVp0=kUR|H+|aHRD=3m1o- zlJ4$Mz~-XzU|M~m>w{p+VHlJvRQe#(i*-M^>ol_qaimaM0-x$`XQY@5YW*X{>xp?8 zW{rI-Lz<{Gp0o#piGo5l-XiCL19wYfJEz;GcEH7k^PvB_6uxhv)1`&7A+QD#iDn7> zZnj-LL-!UGAuLYC3u@ZvIFOmU_=l}2ug#d{a;DP29r{1@oX1?ceCWi=Fe#`F(Lql^ zR-5vhBb=>xGilhjMJOudXzBfZGBU7G0h+$B_l#{MEE}1$?i?6Yxq3iOo(AHuCq9f` z0pOMNQH4Y8FTH+ZEGz6DXCh68HO73#786mqYH>j2`Ucap?5mW$E5nM2M0#B=XtNI& z_r%;s*0HwUjzmz#$erCzau@$@SThh4AbtbVf+F_UKJy`v2;)6o z(m^AcaYi}=*{!49Pc6}N99Bn(RC#4CsP~N&;6UH|=U)L4&sgasdIfokKPm3zJmLO5 z!Q2HgFnxh>eJ|_bEiFX;g_w9v<~ldD%^B$od4qpGK>iL&C>#k$D4dA6uV1eP!Hrjr zmDdD#ADq7#p0EAM)NQd^;mj&d^jZl&0wPhj>|FG663TsLgitTwE6(BGai- z;a*=LFb06cZYH+uuL7Y%YEEjh#T@mV$#|go-r9*`Hl+8C6yL;J^f`x}WKy-5-VS2) z$sW94O1FdiyuTO*kn26fZ8`HQ{?=qcj(3pO&V~2c{~2h_clAIeOOPtKUw@{5ynWU2 ztpe-%07wxWn>EQNyPOetdZ6StZuR!}Qy@)v4dgb3O4=0WLh42~7c{v?3VV|FFR8YA zbA9bq3i&scDh*}Z$(?{t1IU_@^W6!ALyq3tCu8O5 zJ=4SdZ^+Jueb%=$smtlFa4s!1uen2YhtZJq=JE@9F1(|E#4=qRmlpH- zVRc1QmVl3acvZIgMX-hr^u#0u8vvOiEi!DOY@?T=?Z+FAV{ZJvnaV_6(8wUs5_Gl! zj=N<+eUzB|K`o5JyLG;>-0iSf(m@JjY_~SHdz@N`AVBxu*7Zo&gK!3Z%;K8R1Y#>B zc4@4^4z>HV0MZ1K9PbjLBPH6P5?;nNrrRx8<7s&GEsWjc2n4A#mXz8Km=FOhh*|v@ zyl8@0WO|Gt0JBwm{Q~SRI{7R_^o|wrEis4@m*@jZ3)Gn7?WpRS2Fsr{NYI2aSi~Itp8@lxRx{z|9Y5`OO zcIUyW2YexeP2&t*F9}a@g|%61h(8`#y_ z6~P)G)Of@WGNf()!h$*8Q#Oo^)GO1MEu1qpv3K%j{%*!}?ssirqryJ-aC{_eES}Hh z6KBuw8=u%SapYAK6JM~00Q(l!>}IcJuf_f%c*9eM6O1Stg2!9sWWZku7m0FEOQxL- zwDg8F(BhZ{Vh8Y{uyv=(l>}AB8j~|jEWo)Xwc6ascsXXfxyAn~Nys5@@E?0DK#z``9n;gC<$%Mu0%rR6jgwj+YG+Q^>); ze`{KeSD^{IyJ2^xP&KBuYV|Tb6O9kM`+B4P?+YjUb(#O&y+IozHI z%gR|5=}qXoppwIp{>@AnX}%&8Sm43g(Kz3fjM0CiJ{#MWT6x#|9bG?&C9c;gljYqo=@24ROt)-&PVyD$=7zhBx`DvXX}Ew z8}(<{(%fh;^>#L8R9P%~o6^x8p9P?e&+YoyIWv`lsz)30kMHL%vA(%vbsm_Qc%M|a z&E4aFQ_4P12JE$9dbiJuc>gMAcP;6&cCq|b%v9Gd_9D6;E8Iui4_J#LYE5|R+UzB= z5LcCtjz$7pcC?ruODV&qsh{<+(xD5&-on5@LFw|`@bez@Cl=xT+$MVsd`k~`)Xs0w zPg!jcFha;!U1s73PQO*`xCka$$nDM zd3Urhhr5mFRQI8oak5~Kb}^GFJ1BHL)wJ9r1yss zG3MDpdbWdkINMzpPWVzrnYy83qW;j11nO1(Ir5)WuO263tDgbAx);t!0(V2nTub$; z3~X?h%|q5GsX3zt+Jy{vO=o$tPve4i9`dxQ6I+` zFYtQ-&KyW7f=7oH8%qF+_tb_%j6oS{YY-7jNYK-)sHdUH7JrZSItXGR%XF7Z$HyDQ z!oPu0PVt}B4diS8#+F*q5XlE17`E9Ip<+*8EvB`bRR%U1ANBUuBgEXeExRXJgLg(s z=Ay+WBi-Zu)qSnjeR5sRcuB}BH@H-0!gQFva-VZACHn%o ztVShwF5zz=m;7M1$^W9me@VSzhVkGRpZ~@?+Rn*MKL=lx?;^~{LFNk(gHygz zn`(p3aZn=YSAl^MS~W1$g%=b6`$19=J2t}z4`+dcd&W??)*Ronka%sV;olc~1F?)Z zg9oyeA>%*Cf4a>WT#TkY*IF!w57aVYZJwOU)oQuX(j+%f@2RDh%VZ4L`=j%Uwc$!M zxHxoKRcqu-b>*nll~5;>x|j^M%z%f`#asro(hgh`_2y7Y0W3|FV&DbP){V49*GRiG zQE*k8W3TU>{g3e$|5WU`*Qj)E+ps_FxW4a$^|aprM(RI2)cr$ zHwXAdF7Xc;X8vP*LSj=G)dsynm#K3zee0A7M4!PK)1gEvpP+Q^3L43qkWnM7R-){d zFd9#PVpk@)PHI)$$e4WGm=8t@>)`gCJ@IDzuf4}W_X%gf|G}rdI{YsTx1x@(!Q{Q7g)NI}yQhOS)#@W3g!i+w6 zckQ8{u6#V;)rFlAU5KbpBE95rtm4RI-OCp&D`~v&Jo6QxLx$5O6mtYl&;;GfaE|na z8@RdHVj$%3fprkp1Dj=BM0%npRjqyV*whgcXTHLJZfr0%;;;3O1r~}fnOd*1YJJM) z?@}btLneaR?0@mME)G81YJGC3JmwFc7|s~Z$#79tAv3Bxa{l*MzpMIi5bXWuSJ&x* z*8f+g=yZZ$)C9P=6;+o2QF+e>qJpWHCMHfNZ1FWnS1i(|>%Xo6y3E?vn4tHbC(lq` zfdE!fVWYRv<`uesz?QP~*`Crjd$PzD@=UXdXBGeN>d&7EW}B1&r+x_Ed`J?dG7Obw z!b;$qQTpyXYJa%Xz-Ad5ynoQX-&ps1n!Wt@SR;Sa$hlSiM;^ul$1cCS{EsuLaF&_8I4UVSYLVr@8=PD! zmbsyIf1&hmM`TD3+TGtV$AKQ`OeU}nwJ~t`f-fTwg_cO#K@)SF|5+&ByQh||F6aHJ zq-r3o)Zb9bTO%oF%{gNd=y@4F>p5v#sFjwp`Kdl%l(VLo&m_y%SS2tUKH)-AZbPLF zG=LubHqg)qf7zN17I>gSpbf-80x^bgrO`x!RwTow9+y^`-JOze#|T176uS-_}Hw-WbR|2!RLGE|JVi6&z~m8=St-&k0iXr(Q18QcDZ-&Raw)SIe+RRp1*Tu zK&sFvhBR@R&oPr6I>e}E6^c@r>jm5Lo7Fc_ISXQBCst6S5QmGR)ix{=E0PI>LS~Jo zN`*MMET#4a-E63|a^P`*Kte5<>~oyZ&%a}jwMV1Mq<_R3XPW%?6e=SW;O3kWuR>vv zd&^6u$eqdRNY8YzkkC54R%2x<=JO93PY!4kvT)#!y~%Sy5PPYQ8wwTkqqS(wsn(9k;KdJr(DONn4M0{OWCnouYUs^^>^kfUoW6)fh8yT` z%JE(T{WNrM`{0HEb#kH8&PJTJ6U0EY_eR)HIC~wV%)ofFZ>o`N`n7lYY9tk3xQu_< zV&g*Ysd%$r7nff%H!~oC#O3>>L*=L)jnpIlkUtmEHq@NQ8G@MA;eX zUCe%ZQg;lSkCB{{UDG3YzU?E!{K2=o(iJFABxuDZUaxc63j`U!-oE&SS#NwS{5XQ z*5avzLULQMM{tD%kz`J86sdv*M9O{pKy}|>HDsUyX>vZx`1sYy3KhcWJCa1+AxA}b zDCqf?yEjzYKAG!LfehNLAcSu#z-x1T+#sY21zq$V;sIK9@PDA<0XiA|t|D4T;+gga zN@cEoJ^X}+|81#U@jANiXzQ@c$tU6WFxyGp4fKAimbM(WT~SMDO%7l3*OFXivgW*e z24%03N;pQz8D9`3j|_7jM#h@(oy%MQ<0q(srp-ls6dqDGVfCx~OI#E@Q~owlp3KffX5vZ@Jm(Q31i6S8YQ7OU8^S@*XF!Q-TRyoia?J^F+_0u(Z+BGF?Kssd;^qh<08pMpPc z>U5Jdw}i|iC0Gx(`HoK4s1>UO`-(cdv;8&_TqN|kz6Ta*K*9uH5I{M#9R-_->~;TDN9NUn<+ufY*TbTFS(L4wQ8o z4T@ULKm`JI8S1n$wb5W+fK$V;fp`@1!e}EdDHYB(w}b~ z1x@xMVN-*QTB8`y#^w8+KG)#BIeBT{g@QcBp-y%ktUHL_KO_OxLJwKhf(q5vp-!QY z34Eb#h7e0tEz}$!yD+pSaZ|F^=Q{G6X0uqhH^dU+0)q6HyFJB2Lot#_-&KtdH}vT~ zw`#H8+VyDq^D1X8_B5*6??66trcc3k+kiwxLzWq7I#rh?%IPdSVvWKuWZ!|&I~_wn zFv``{sY_Ujf{JG{M`PihNSxzQEZa_YiyF?oH6IhAFlUHDn?4#5m&5U02(;3nIjOlHbr|$ zZ4EVz)F%NFJS0@24?#ep1)?Lckb=2iXRox)Utk{b*iJBpcO)~ZL64@KDo(}IV=dCH z&}*#LkV^;G%J*nJGjid9pN%)8(TR5})ZiR6`}A@7q~4c_Xdt1vBc5+gL_K<)Ss}HX zxl~xL8=behj{g?;`=cj9*3jGxdIc=ZZnk3teel(v(HSidglio~sjL&Nd5 zd?}@G*_~_ddo=y2n@+M$wOyl=Qfaz8Q9^0DAU||y^e!q&^v)&8J+KETI)PUKHmOsb zAO~Xu6Hc8qx+_s5L}!BIXWY#HnA|h)DX*1@8L|e3_>`fLEjh5l{U z>Gb5G*r%jE@1AND>jQIO9TG_;%Z2rpAM~&kJ)H5mx|5Iduagv4E^Xo_RFT# z(JcSmFN1G`X!~hlj~A0=D(9Xq$ekmXOgU}0V9P||kU>9w6_x-q#z~2k|09vkyX9_^ zUq5!{$_V&&^?={;0q7`WUVy4f^llk)a?}j5lVn$O?67LqsC$(}B4vE35;0x~J*sdv z@vghblXP8hqEJR8&=M%*0C6-JIJ0I>OW=3H!|pfa^Zff&WU^okg>4T#pO<_s6A7~y_AGc`&w-++XAiV>367(ZM6A1d0Mllv~1um*}sbYJG@2anU z)#}UUNJQ&@yw!TVIp?cFiwOPw_mbP-_hFg^D(Rqe+t-A{KEMK4exXgr%J)^&z1;jn z|Mq>ahHzbC*2T-!tI5c@9Qj;!i4DXT&!}E<08+qADp(1(TF@Xt&v*4XF<;t+XZ)^v6y`vy!vbN>L=@kojBbXqB!VS5f-vxgje^V)JnVhX3oLnA^^d{qH zYKFS~`9!jXa`%qLHIlyA&>4lXz2gxd(>zuRc#;`e66+jY93=jBFFhQ)h`J z_tiI%j54;aLj;~e+;72nT41J-!k|H-pgoG*yc({qs68NGfsr8|-cr`I=(Y(;L!l6& zL6qNB@=iE6H(F1jx7a3w>LT=XRz=GqJQj}K^U%-?=fsko$xCNHoa29*%5by&OrdH{ z#$8(3e)7)qZNr`@=Tlg$IwRw$jPCe{w-tU>;ylBtxWY`HwN{NQLiDtbN6fOe(!iM|1$aQ+oT@=DiREn zvw0Vy>hj|29uzWjt(%NS@YHaf)pr4=j1YSP7a+-mK^+uTWElk}x1pJBLzV2(q@28m zzrsF}8|$@{yF-SEH3+YTDtu;xhucni`AJ49=dY2I6WYm%?pz=<9x~>MN~+aZto~ry z;VtXc-X-S{dXxg6yrNFWa{@~u5pw=M0{Is3wBb>1Q1JrX>XZBgK@7ke9*Ci7qMrIozn3E&+yJgj6@a6I5wNb`t zxqva;X10tS-ZiXf3KmYy@*g1I|JH>%OQK2^PrbNzY(NX?lel6^a=-aG5LH-EzsX!{h0%L}n=EZ6})7{`ljMQ`r>G1y^G%{cvfx=u+t2-ek2uVnKpmLCr&xJWq$3Vjj8PtS^;Imy86r zr32-$W`1V;%vyDMR9V$olJL?~mE4pAB>VDMbwtm92!1O)YjV511)>eRpKP^`H@B5* zBk-=!c}e5$ls9_TgJW%)yP-8cvV8->CFqiGFadyAztXDOE5_aE|W+aM~Gnk|2 zQD-g(`C|cLQ}F+w)dquaAYZth>%j_01H~AT(#-$^q#>9qfVy`F17MffODSv~9UPp3 zlQNgh&7FGwz|HyNK2iG!FDh=hmu{2Y^ z@lawi5gakaWrnELZY#Y`mKiEK{b)b%z8waPCERZ>VgRAX8|jGe1DJh*J%HqiDOQNL z>B9#wL;!Z8lQ$;V9q@<)LBRh^wK|RDXo?p5>zKFA&hnMC?NJ_ponFQBdd{VuvqkOp zX6)KX`RH(L$nEuoOpVO$b9Wr}X!h&UD^E;Iw)OWV_G)&dLT;}|uXe{=MzvLGCw5Yu zk~z5r*mT%K>@HV?Ke8>59a>p(gsnyQvR+pxjfebB%U=F1(KqR|hbt0q!z$VG^G)WQvw7ZA(d|$-Lan>!=1$sGAF7<|bFzz>p30RO zUC5Ho*R9E2t=8QW#Bw2j2YGagp3ixdHpD0bg>l;XtdT%jk6MGKX9cq9d=u;hvbZwv z5{2V|TL^g(lt8|(lQPYsrYG3Cs^2|UB4K^Rnk~U5&I-U5(g%*)dfB;G_YMq94{OH; zeWYhvYR=5ITDMIw*0cE6j*J8y`IuW-v?dJBYW2!=sN1rif4|Y_n!3v2)0YvAEV;GrDw-+`kvsVj7^;w1x@8r)u*2ka;?P=f@iK&au6@omKCFvo(1eZ%{Y zWF-f3#mbSiGq>pOJ#6$k{J~Q{W$tY@-({byUvYIRoZA-dmqv=3x!qFpJLyQ2Mn7E3*WmC`AJ>`J0gZXeZB$w7*)6yWAxbamW1t;%8+Z-y z?=_u(m~@Crh6)Wg0lk(%7>yu9-q;t(h%JnyyqmeO z$@hJE#A}(0d)vItnsTn-j+N>oDtj&fqlK+R zX3%YOgd7&THQJ*zK{895oE}UaIqr`R^@n}~O8hGGj3%bCIK(`lXQAJim{TW)!gi;m=?Iu?H z*I1n|?uy=%A(qAk+F*~tp$n#lh()25`Lmq1<{p2KcKM1P%Hcj{YN*X&%;*MpEN|aG z+pSe6Lr#_XpvP^tdpKfJS>YQ+hdO`p!ANwlKP+;W-bKjGUkUv6(WwoZEfvmR5EzsM z(Hks>ID|#9z>q4WLdiCj(X`)e@m=80S`|a7-M?b4Y&P%wzDxb!ou9ZT+HcbzwdgHX zcfzj@^ut+#68MS6^cr#$qJ{Rln47oKY9n%K!vRu52>m)j(iO9nSZ)*(&3_!<<(i(E z-pRkfT-R*Q9Qo+&U$LvxCdU}0fyd9t#+!RWWJ(S?FcKb=fRdJ>#8X#d*WgY(x)NV3SPk$ZV9UkS&s-z4EIyCIvhJ zT>^<}>dt`5uz~Bd{&zSQ>T+0kk&x)04C)z3)B5X1%` zt*{oV4{=!tmLOhTCkTlXxKS-(D~gwed$95K0Zi$4-4)qX8PAALTPytX{y`DSjiHy-ciye- zbk7;4f3KrkW*O45Wuuk@$LR|`(hWaYO4++LN7H6gZ3Vn@!56{3(f5NkRNWwJfn$I$ zoKK5@f|%QI&(wy)l@Rxje>;75M3{iHm>n=7>E?s>YwMj1C)zL?03iF}&e=y5N%TDL0X45O1Mx`_rh)!?olDhw!{ z;Y6mF%MPEMuZ(iCV|)Az3%hiL?A_W~-ptrd@lZfG!!I&_f_*|C3}k^}qK0O{Dd}LJ zAT@#4#R}OXG*F}(UF)dEu?6U=zBHyH%!3cAjix=_W@G5>j^X~5a&jnWkwV63R7e>8 zX{L3ykTU8c$6HmC?p&KeE{S$$gJZ3NB$BR2^q4jpel69?jO{+k|3{eTfUX#JbHOYU z_;rF^L~{VQlHO)9ECO%|2oeDbOad7KBntg2!J&cy$P-I@{3*lae4i!M+xQ9pN#?m` z)48vFa>ORvo?SjfDm&*Mjqj&^qn!G*PJ?d*wqg?1TpMJIfuL(@Rlm*^;<$V zrneH3Cq3ysOL#-_C>-Ll}Ks-iL#B5pVIF5D;6^lMCxc+3{{xzjek+G-i_h01Pi zMG`6>D>o*4tnqyA`ISI3wxX{ zt4s!X+*`-MHqD`af4Jh*5NKxC1;c^Rww5ViGpp6za$+3_XhS`A8|?ek?f$H-5@RZjzY^_725*Ltoq}8tj-$bt8lH>TX}MM2mNf{O85=8w#83G?{ito1cVyD!*{xDa z?X>SQsB?}6v_djh=(;rB#Q)Ia7(LgHsjH7%AHQC~*e5j-W|oyf^X29QFMk(t9Fgq= z&5L${{~ePATH&ThAk8fdGSE&za0TF>O%A1CqdWQr1QkZ!ufy>LhD(ekw)c%z6$-gl z){Q=U+angb2*6u5Hg}9x9>swL7#VX9BpYLlPOAd@xyINGcPas?6>5%pIAa(K*}PaS6NJoTrM`A{YsSRB`-y-fajtI>5}@Jjc5!ZkMdX=s&$)~4Wg!A~X38-Wf9?ZU=X ztt>)wn3xu%qCSA(m5jFHZpi8r{vIhQk2i;*QfI!%@W8v=p;0I8!(H41seP1&mzg(` zb3Xz({EwV^@$3FUR%OLB=0XHA{Sy~+lJz`496!VxC71*IHoFUFN7)dl6$~W2j1Nnu zsHMwAtVm+w4{q9?7N@jk{qgy}gpAC7KG>?if`!RVzvU+ zsMv`peU(ST;yT-8X4sQPWp)$+u_a{>7mg{rw^hTgoT8Fu9vjkF#SJQr(ae}co^dy# zy57)a)H3cj8Y)$FkA55M`B;~n7=RYYPn+7EEp7g;FHDEW)(Bm))>e)rt7E|llfPW{ z?0&Mpw7cQA;k2m^IdG;mHx%zrWo&_Pa=@=Ww{NUmt|@vnVc+;3m=$5{ShfK}%kz7-)P8r)YzwTaanEELE^B5~PHt__jQ1%BL5HhWgopC-{GCap|y5 zxB2unYLkQImTRo+@-b!4cy!62d0l8IKd)dSkqs{0qd()av0yQ# zjjmkN79GD2rKV@5wI($vH-CV5wa)JA!hQu%&s03uga=6g@H4O6Mqa+!l%)v+Pv6v-o7UaX2bz}<>hf?h_bJnb>AQSEzuJsA_EQpQ4 zGC=Pm)iG-lfSBOIxv|t{8I)>x5#}eI^~( zn1srZuow1L_9*Sf4AF!IT-?*Y-_727;G*x|IomgC6n0bZA6>YGj4($huJH#aIEV0Y zUp&8TbG!-kFIM1w!z@EK6FcPT?3e`w3cijhD53ihYYEt?PiMwpD>VED-a+^#Z4ZqR zJ4}u3n3#i!u|tP7g!sV5TBEMQkTI>UcMmQe86K@G-JH+n$o7=$RdSs?Hs^QrC+CPx z67-tvUe27-W>i^c_u|+DOp0X&yLI-Y+f<#6=Dk5b)vv4n%4n#b&jYRC?MY~_)rL8- zjwvykwG=#S2q6Fv@?jkM;!i}~P#Rv7Y2?0 z9f!9mx0P8%DS0fgoV;knVfSl^KJE_b&}H~G)x!WRln^#c)0>5(?{ljB=}wk-bS8g4uY=?yuwaO8kpE{{tHvj14J;0aF#gt zg;gy~o#Jp6O+;QlnXZk~xvv1zh>zBT`?IA{)B9pC%~{44GnVSk-VfGc97mrgf2if^ z*%27|8mw1((~~2|VA!h^jqDt{yrK=p>rz*`kxHe&J4TQ!2Bu=C$g{9digD3Fmjy?> zL^3+&&yEb0tF7_Z_wrB2p1a!6TFh80i^8~>A4<&PxY;rPcAV_egkd>%N|-GZCc7BG zh;Wea!Fov=A%ky$&SwS?>DO07uSj5z@AP@Da|cS9fKL_7MqM+#hSp9XV5iSBT&}dn z&WM^)p+u&TtY*B%!miS!TPL%4D?ymt5=rsr1KD~lH&(>Ctz1Z)+lnilct)5zc^!b1 z3AJCiRU76~eek|R0cIzJ!Bm>s5wdImsV&6xC|h_XH;$o(U2AiwJ)w#-RBSp6Mc-`} z=dN(=#J<7Oz)5(HtUYt84%x+_Q;p3#?La30M@;+Ey_ed19Cyvq`BK97t$uX%+OAJw z^@9Vcyo&$NG-;mqaP4V0PaiY2%F~%@s7tb7k+L+s2{Y^%(4g00W43R%jN!a=Bbjl= z6PBc#dDld*hE7TM!0Rsws|sT#m#*%44V#M%9xnD&O*8&p z%)bDfr$rkWPA|d#?+&@s-oH?%e!e$B>l@TpBB;PCr= zU^TP)WAKxtfaeFjxS?v%weQLs4t{y$satRTcd+R{1e@t+w?iL-3b2sDW(wXJG>aj= zK(zy3;H8=7>DxOzvb#4K!?*y4?{~gElNi~tb10Bf!I}I4c=>I12F^QS-)gRwk8$pKaYc8Rp>`+((veX$-&1!T5SA$ABx zo2>7T^bXm_V939o$~sdSXVey;-~R4!-*5!Hpw^zAsS-Z)Bj@iC79gv@QNT3368$g= zvC5A)L5**NpH+)^qQ zQ^Rg1^!*WsOt6*~W&foY}^Rl+47L4^75(F_^`QbCeqOaKxw;sByD{(`{-P#HCpMBgLfv zM+EO5NLf{S;J`RwPcTpJ{vIMu0L>J?A_hN4v3J_&3-pR2-&pvi7{V;nQ|VL{kU=g& z=!ytHn@p_qAkK+V&A$wv5^yjQbUibbl(ic#oGX!ZdlKpS z*i=#-_BHo3hWiR`hnjx@sbc^eV!F!+*brgBx)qC=q?5qeWjMsS1c+SW-tKKnDOtOqh;a%BE@hxi1CXNQuBB;k!2j z9oWT5dAWv7Q7Kk!7K%^`5l9Fo77iV{PW5I{o-ZLEQ|? z*oJDvEkEcq6ktXcus@_-zhd72=Kymxc+aTh2#_di*9?I_Uc2JYh2a5sVB!uRh^YYl zan^+`f6^oa028I2tmuqbYofn!i?A!3wVg6V7Z=TUV zuHeFru(zy35lGnDdgbQowO8{mxK8x`PRBeyun ze{}k!Yb!%naHYr=xoz{?3=<{w25=FnEgUg8248RFmW8 zu>9v?fEUglY($j>ObtXUY}Y-!aQCqC0TKxM6_b)xjIy_wc!gpFYKDFyBn#R9yzI$% zDLGIx?aK}~4}EOkm2W)oC0D60d)k1R?*pr0+=%HM(& zl21-@>9LSuA$~~v^ewl1Lt=;)w&g7rjLL=a&z|^7=(X1M4!pkd^m#3igHhn$uR#vj zF2BG%!EUa5^e6*s_w(1=wsphx+!lc&E56Y8ducKafJHGc^Vmd66ZW@u2|RnT!GEVd zQHtbEp!k7TfLE{|`s=9&NWDlOROl?=T$ZVNgdwX}?zW2wZFcz?Y`O#3fyZ8{-Hu#E zo9!CE5s(d@Kl>GW?yx@VXKsMA!#Q<`Y7jj{(4Jyl3Ts8^v}0U`X+xY+XUnt}07pz) zexOE5zT|s&iHt2Q@Ws68b9_H@!`1&|Q3j%lq@6R^lc9QFpmgs1aJWeNmd@c$`qV9R zl8_(4elpr3qn1i1{G}PrSee;gm1G#kFt;#xB4`EpNcbP-`a_6`C7ch% zIq9f$)2xXob{Kq<6aI&GzW>b1?fe^=+7-{l%c;Std4F+q=at81W*>p2nVIuWOiVm9 z9m`FG^g9z5H6fzMHGCqz7EE5yHOx4V2#J|0Y#*aAA*InyZ9`5@3z(#^)=qb<5Y@gx z#MpA=bA|B^l&#Ogiqc3=$RCKHA6%@{_J^$!h`aI1hw!lf zp`|MY{vTfXA6L@J&X)z|7th;10?Z#+Iu;d~kM!IW3?S=!%9c%h*Kh0KJIaWgaZe-#Liv7o|kmKq2$~6I~nJB{~j-_%s5DY zDO7fx5QHl=|jWKJ@0t&<&zdh(v`BSugEusdD{R~U8PFKMqI`o-WAzVci+ z#8867Y%y?q+B`V_>-2Tyf#g<0x56yA@vK}R3A4BF{&;;^NJ2YAiS|K$C6XMS@e{44S-=&_)r$%eBxO?}94?M}^V~?Ono6Zg)o@;)M{+y|gwJo|=T2 z@Gs+_E)I<&(!+&whHP&fhjr;7csS}xPXxqK-BT|fepl_`5f~keLiZNE&n4QM!QvDE zYE>EN&oz@sOOAM{+e#Ed-v3M7dxuL}Rq3O>zsfo19KXuvRMn}fQ|aWKbLegJ1tW;cs9*wdMlt*Z2M|fxRrlSy>YNV1%oJL{f z@rz!y^A4dv7PE$Q))>i;f3LMaY!E(m%PqftmhYOcDyDcg$EI2R&-g2dND-qhONMJ=-`CTJZjqfp=LP10$STGsa z#{0k}(JU8|CQWpKWC2NPB56N4e=$#-5|c!#K@(N&b+fvWiTb;IIcb8;SThTc* zbWovoMO+uH?>7AGmRp{Xtc;B`&vS#6g79of;C=)$^}8+vsi_Yh>4TG!REKCrTli@v5n?B>unf*U={EnKr_nI0vr z{=f~$X29-eGtWiG$SG~i+(QF{HzmKW-Q;DB%clk^)oQsPaaQ~Q`v zv;Ohok5oR=Xkd1yrf3KA<+9da-z(ORs)2eMYyKg32i7d0S!gCFeEWN{DT^B!LQ!L-1Yy}p``CaSYxGhBnw^9koMIC)I*;|$euciN*lo3Y zIo1CEh-Bu+rN|Tq)05k#c27-{ye=Y0C+YIm)pfb9BwErxQ%zKN8;%1)iuMNgB=IOzYeaUSEoPU)TJUP(OCUToXmXqnH))>`HVH$0N8 z#n+nV|9tU1?H4&xeFJB7%5UvOr@YcBZQQ*pS+6_~&wQ%iqvv`Owb7UAaQb|RR(73E zZ=*?Gn9-QGcwrf&l>L5df4p`<@2q^dcpunqcK5jY)0_*j7e{%OP;T`Z57atPJ)-vL zZF-xLx`G!v*-pZf8x=cRS_a1jA{nX?6(rf{i4PObY%LmNIlt#&(i0pwy!rWop_h`6 z2^F1&iLAn)(a4oTIq}R#G6U#_cLg|`$v>q)&$HdaTH*hE_J>ei-MFW9W|st16PT^U z;%J1}oEFyT>LKrEHt|OEK=;+Qaf!jfr-|6A+jGx{+aElj55I?6J~FsqRh-@U&2Kh) zt6=R;@2z6M2j~g{V3p2q?WuYRbITSI|5H2F(%tP3rgv4*z5jEjMPF3V9nyt%%H?#mD2_)kN*oh|28o6a-6un;I(XaD0sWBRiH!9R6>+( zx2^U}9r#fDmxh~7QVvTh(b(@_S>9SenQ$6mOPH3Og7)xtP9jDj3_eW`JbK$|TNK^w zqlDuGBC%A2{;1i<9!oWwqOQ8T_@$OfA_8A76bLwSX!X1v#d||Lk&mX6Q<|N|&??MG zk(}OX+yJx;m80ia(Gk_StX8dFrXUaUPp&l{bDn|WSCYh6nnF7=Z9w+K#WJ;0D^rr+ z#?Lv~hRguxSH0PDkrV$TUXuC*)&RSN_io@rX(bk39H7N8r*h?SZm#{K+FbkiHSO;fJlRZ;&^^+;g%EEn(zt@vpEZlTkfuBx8|DBx z=r9nPqdwAX^<5cV>gw&QsknCy4&Iu4xMj1VTVk>kR)dRn{^Be#H&M^sjBiUBQe+_|h;@S)`=dd@Ns4NXG(rvq+T1AXqND9l$3)et z(Ta@D8=_;g5fAF5I_Frb3JxjRAhFnJ#Z6v!t`KOm$fF^>JReO+U7BTEJ7?9jML0aD z_HtuNV(sFrNaUKD?ZmW>HI~cL+FoqwYa5iiqPCDrUiSLs!hi`q2U^>VLXNJoau~LU z`4W{N$nAwM4$%tgMrSwQZ0ydStkGq0K(m|xL%RYG6?^1V2rd znf_A0W)f&%7pcF^oVk|JTDdSqD6~SQ&`gA&at8xIDN4XhKJ?DAAihX6R)`H;q=P2q)OXr>56`b0hteXF|?A&wFlGDJyRR{B3&+WT=2t%3$Y}I z0}7DQ9-O;|)mE(TUc^lD+X$-x-jID*4GefOT6?J|YKoIfWD<+CCA!)Sn=;+L*{Bkc z6~NWhxw;7F6aF+=IrRIJ%Y@*DBk3WpdN!IYD1Ls+EkBp->+?3(*URKavm!Ki#&8NP zVsFmJSSr3ddBgu8DuyEEpI( zaRNh?MZfacf_Pq5WzibdYAMOP3*A$JSr^ZL0T+Gf#QR8U_;qrB6g<=`>w@G_tiiC( zOKTly#vQj6TE!YZDD92MWm$7)bJpAHmA}0*9?OpQU)6P~rFTB%YWdq*Q|ka7+Sy7j zE*08(b3J|8y@4G%PqZYp)_z7yvM|!EizqGRn`}g!xc6HsU^w}65K4|TO#tQ;u3SfV*hwVp2q>{iZ5iCWi!`PxH73;1(RB~~ zer-Sv9apm{Bv8@MDwiZn;iz`6_rP6!XN`mi| zs;i`S78$%L*4h@m4)44qr_xR?tsR)_bY6X>A2OhMvpsmPbLYCA#_M(d(nga(e=91plT)p(hKQ-25uALA9Dsbi_=5?Q z3j#|al@?Tc^@58`ya}}{2OjgH`(7b7iX)zqU|_uLO_6>%| z+v3~l*?TcE%-X4r$c|x$^XD-QvY)AEul01QRkDUo-Nfz0o}uY)sg3VI2JmEY{M#(= z)WUeY+$WO;Q5%0Cp34mR)F;gj`7u5JP5@!ewnR92ugE;P=sUIsMIkDZ*s z!BBa7xCRF!p*^^C9YD0%##{v!fdPduc_SNghP*9nsaz^=DP?0addh}1*cg$$lghhT z^*6b&j>UbeTv&5;YmAIkEjpbF(@xBGacf+d5bIp!WT?DQ`%YIXVk>=Xn&cgsnOm_= z#XFEQ!MsfY;_ei3CNuf$JJK_es#m*4&#-f5+zdPCPp(6Szy6Pn^FQ3=*Jo%SW zkx*zx7N@N19Z)IDyD~)2FlhtRpR&0B0ogM=+Q9ZF*86_=;AwF5E%2U#(Z-!e11QBP z0^n!RKU8}|%zZE*)W~<7bRef=73ORjP=&mPM=NSyqQ;t!vB@`YK`c#QL-ES%-XH#) zS?X@Thk=08Knu+I!nr3A3vkrh8ga+y3i$?ZJJaL6?UI{5kFL-1c7E?I%cYJF3(UT) z@9Pe@PcC@X`!Q^ZHBw9^pqw9FBkQ;Se+8@>GJUh(>3t`3HDP}{bcMx}iT<)R_;&cp zFsgnBe08dvn{SvjG~x6DOomyTD633UgBV408q8u^af&`P`r202p^3GW3bR5XT8iWC z916CYy0NAgw7abdW!KP_*498_z7wjZ6v3lCO+_5EKb{9)dpk1Rf8^-#c&4LqLpSx^zun7PJgQqaL4V7bHnzV@QWchD-yRd#sI zWS%^}g-Z;LMK3=%uiVml9ye^Py{P--5;@-yL>G5w(boJlM8 ziKRbKDSl_?lD>VKo2_1Jkgi0Sgek@w76D*Kl9Mo1!d&Ctc<_{jTg9(mEWTc2JU?v9Gv z!^HvBtSZ+vf#qUG!JFhK8=%gb%NuOblIqeB|_^h+MqcSs2F)MPlo5`Q3 z_YE1v=+i{v4DHv1JyydoPY%tmnEc850$HyCM8_uAAnj)+|D8*7`-LK_LD;!$clxEg zSL(Hj)MB|v3S2GHrP{;ueUa%>izRy?qPIGnX^kp5HITa^(q-eMOUne+sG3kprzczR z%$bs_^!ZIgqwWPhTuvFfZgnjh$dyLJ{-w_G6(+}jkmt#> zwBj$s&W!Py?3_}#p-kau<13a)E^)Q3&$aB=MTBGCWS=AARrW?Jm4(Yy2MPI#w%pU2 zHD92~m-_u_v)px#`#`TJjT9`+<9wTZf&3TZ3uG`1bbn>`em9a|Y~#=g0ZKm%K+uo^ zI)2pTgegpd5#jipHZX|7_po_l{OEhh5i(kdF6x|yo#YPll#c8ka24A9=5f7XWm7GA z^%jz?{h)K+qFk2hA@_CS`ZMIconvM@;tRKu$$C-(#L~vQnVU*Eg^&yZvY`I zP7W$>62G$d+GKD0=BOu|+>%-BTt788tPA-EGO_GY*{o*kv{xro4F+4XcDFm0?%r7K zpB52faJ3Pkw8OZ9lmn@ssJ~9W&4nQn#c)uu6X~T)zvg#9w7>23DGB z8Zrx(yc+ZH$gnma)WpdklZ?f@g3M^Dk1x180$=0&;k{@;P5F?}{gl`9a}T9W5W3owM|ACIagZ z3R5wIYFI?hQC&@c`%Ul}X7Vb%Js9dk=dJM)wScOcgz5vUylD=z#Z!FGFGKkhK((`sBMTaqD|}JGPd?>baiMygeRAxdWA{ zYTXbSCcmb2P2jG&68$H922n^71+VMSIDfay*w@AssQFdO?14=X}5wDTTJM z-{ICt$b;k;6bH~3k*J+R)|O@i;0CByq={4%+R{&0+_0c+=c%~)rK5LvEt`B1u6vQZ zuf2~uUm8)`1p=8r;@X%o?Px!~EYLW4M|ozhdI=F(EGD-Sp#-%vg8FaCXW%(MGhQi@c%pg5rA&`p2uAtQ7UvIt4 znQC?Dy&htU*FUV>UBb`>pft4zOuKY%%I zKDd(GrMX0!&l=lPYFUA&~r8FcI|ADWN1WP8cB_o$|gU7R~OI90nb);EA* zFeZ;{v`Rp*i&NI zI;jNv`ZC^gA0Xthn?}31v5h)`I3jn(2UB~~v$JDuip(fk5a722^%Latz$FY+z>lIhWbya%!qtZ?sIFO>_)rHKM@!&y5~4^d2Q5 zc{~@M7kZO%7-uTGf7b6MFCtz*lx#3!|7H@6gBF3D@4DO*_HV@cxcUp2qwObu2j>LW z((G{*V~{CoY^d+@hZz@Urv2+(+b5?EaT|U_Y7b73w(tC~=Zm`O&%1*i@?mm8Zg8wJ z;+vVgec{O1@@&iSO5CT`bFAXo+O8WmAI5k7UVnn?fUGU_ zBuNAEHi8&hM*)5oc1%V15aEkGra@V{y>ClW|I8&lv*CeCftYI;R{vq*X&q9C7Zkyc zO5kgUvJ>t9cHYc(=m@F57oY!c7SmEJM&W~q&u1A(VwMXm@X5&L8^f2!$EU#h#6Bq7 zJdVI7)Y0MFk>p~DtA{Qkzw9hZIc49@ug;hBy%`$Ygn~JTQa!8AcOtr>dQ-1|0rAbV zjIV%*;_P8?rk7AlYE-D{KQ+2B8ICxw7S%o;Jtw{-Ltx_y$(oVP0*!1|+7z?5&PL^Z z9kVWnqBJ{(Z#<8AzW3l8z6^%Xz$d25=@~q-h17EQ7$^6*ai4IT8^DWPar(#B|)^=D0zlfipAU??-pf#Lpp|589&a?=t@d>$`lrMMwu9M^+sOC07P@z20#_j| zb#!bgfDX;_^i}N3UC?f5VB1{c4mGJ=*>?67EAa|LzLC}lIolqjsCLM<>udeF=vJ;f zY8r}+cqAT`%&pTnLRwUyz|z#}p9kK37yOT&;x~qPY397aH&X2c?q=Sc`bBQT5GmiK zS8X2Iu|$4TdsmS>_yh_0mUs;Rc&Te@++<7UdX_G}ZsgL#3I7lmuoHor=?{GGAIPo1 zRR-*jnr92s9ULkwGt_2}xw!k>+UIhQ-a-T#1=_TKooKmsGBbbohN0aTh($6b^*3+S z?*#UE9(aJ-28sfjSd$f_HW30w=Fv-gpr!r5A#zZ3aMQCWr2)>2PmoX7mfFdGlE{U* z=N+fv`m={P3;8F`fNBCoo50Jbf=yg0HXLFV3?hdYdVT?O z%{>XDG-aN2^roj~;>LU@HUM-8yxl?Ke<_pDkG7QEJX~0+l+<@zs7&Z@=^Q&!w9%SJ zfX`^6W#@VQN9Y<*hjl+0L2PeleRNb(r$SHW#^C32d4TKCuKqm5pvhcfMQv6EENP;A zwuo7>e`;^v1uV)J!<&myA0CBRkkyhq2Y{-m?|c%&P@bk4XJB=TM03=G!g-^}7o0XO z7(H(ZRe(4FGG(|SC2rvYxeo@@WZ}kt&kV`3aFZQ9&y*id4Lc1PZpXC2iARZ)ibG$M zs^wCI;2a^5+=~(K%uI_*ogA7Jcf5boUP+~_kW0%Wn}lkiaE23$q+*#+D7>qICBDV! zxod#G;hE4q<07kNe|bD8lhZL+$=oRo#VF~_kS1UT9RW)ntqJ8q61$a?>y0*_o#Tg! z!L1RM_4wu&x&EFB>pf(#y)rjq$dZ2U8oyd%(&@ccEq~(LxQ~dcEprxit!MUA)t10q zesH={o#%NsW+>PHQGWzAuD7APl6uUHnSPyq+CmqExYHzPtOuo3s|ymFaDP$zhw68l z0?OTL_P8wCSmeo6c!fkmw)P83KwxbgT}S z&Q=m*k?@!>oeb(Wt{gpf1R!`F9{(Bnj3bmVI0F2A#SIysA~iM|!Ej4mqK|a;T|#)r z61UzJ4!O78lkUOm2quX^rW9SpeIR7_d7>6);Dp-)n4uxJd9+9&N@WGon; z<2`oo^hgeq@kDB^#b+Fx$GZPi{{r_6_XK3x#n!zlQyLGmW#KQV{z1?iVzTeCsa{jm>Tj4-Mc=Fjp~D z7$O2yc8M$ua!(|}iJ(KDtnKc^%8CQ#?E?!trbzF;%f7Z!8zc{w`p2nhu6;%B3Rn|i zZ(@eto8QzQ7j(jx13}P}X1*1Ig;BIfs34&gjhVi8Wx*I-Hlj@#jUts zMhnM{-a6AM3ZE4wm8VsgXch%uPaiV3lVF!C*vf6IJ-cDo-s)N2@$IqAvoERd?3X#Z zS{=KzVnvUBMUW--MAuf!INk{G*mQ%!o#FR}4KqTgD;^k6Df_E4#d54_>{(3bt;2$B z)*9eV8tD`YKN2(*f&ILWym1Qqfu%!y$}AmUfYW0!9fF?KG_obc_DAbJX|X?1r#mvn zneJ`1H>KY+udfWpeFyCFSpR`;l8`)X+x9}=Rh~h~xt3%wFeErDu&HHAJM81rT8(tn z)a5e;M-8*-_b07m+C0|qC*Y%J$e$^v#QZ(FMGBaDs*||U3(HEK;E9g-yA7b zW1>e+yhc89H)oI-6h@s{`EJf+i&^xc^%Cjwawh5u%k?tZ77IsqP#Ha6Z{_}t{B7L; zFg>+Fh!~M{qLEEA;R=Oh(I_GuCO7II*dACm5qB_aG?U5Q*W9D@ahy;r*BYFbhAG{y<=nVjji(?Q6ru&bMgj> zW>^^1kCa_1dm>>?R8$_9I5Q9}S{}0ab$FLfDb}>PLVxrny1Yg? z&I7vlPw3t&(7h?Ddo+HduQ5^Dj)px5vHSu25x$Uxbbv+@I>M9gnU4Eo1JK<<2HEic zZM@XV+7(}kjddwanRMEEI5R%a31yW|wd7+`&hmM@x;+@vN(Ond?FLu3(qGtADd;8A zY)7iv8MIt=NYZ(kDV|GL+qRGBvl4YOU@#apcpW(_lp!xACuEy@UNfmDGP%{H!%H9~ zy+7p8i{us)Xd9u*7_jwBleB3h;B?s&*jRIqFXJCkVMJdCp_TxxlgQRsOiV{Hre+Se*?WNU5uoyhY%t~W%2`=X`cVZD7$f5u&)F*NX4@W4s6Vu4y*=zv zNQ{%y!$V`$qFu+04!*kg$|rXSq=UO|YPB9}+t5cyE4Rg|u)7RIE*z6;VoFzMgw9G4 z1oR0vaVLBc3~iLpbYC#ld)#ZnVAh@?bDHIQ5Q~hFr-9RHUokZgBH$}(&$4U-xL_!mV@-Jk}?6;H}6W_QAcmrP`ff_XtU>hK-&uf&QMF>L<|?CuzOfpRs> zpR&9x!^hN}7j*Egxqy*n(9q#k zw@4pNi4%IB4Y1x!v$jD%IDhgHk7{Clg%8R$Ul55Hs)pEX$#YOgTHBRZBs_QC?2Qqj zz6ZCG3HHyks?{GEX?-OzYi9jKk<)YBJKh}r1 zU*Vi!FBllhQ|5(^il>-|ofZaXHP%J17fufk8>1zI2UiJ#iP`>q0jiq9L#&jYYgkUd z__1(2wpXM`wei0yI8G8nzosgv6b(HOc~WoGSkq?nk<_~RYs8x83l@gaeJVsN6MuOK~R zZyy_$RY=a-maKsJ#oqo_@8UKBKj9ccY{*=rq8%b-8CO_%n!ux5#b&5tQoF(QlA>yE zqKA)88`B$a*m})GXJ)M9Ja24hFm~ncJZCiSjC5Z%%?V6L^zv|K{sW1&OXi}g|9JNW z%ZGCLwp?{&mS}ywsiP5t!eTM%BJPsOY3Or;+$dmOui+h*9C?Y&SBLtUvkeSMrAhgy z=G}!y#XkZWH^4C!K8zd59SF%ti{^W_ZNdw;*~K5Ydhby0oO(mXm@aZcMRe2bzE@SB zn;2b6tr#Uva!@pl=rU51!E5wyCkXSXh9x9J^Su^x?dM8ov7{%6F# z-$Cq4J6mCY)aZ(EgTzDW67u2*JXknf@L?TT49oLi!4#`-`;WLIzLwpiR*6#mSnW?q zMP&Z(EoHLrR|q)^%XnVI3(q&&;R@*8o&S2JD<1c78;s(a);ner!LgZAr_YpL>6w^U zOwu~o`mgIx3U*>0RroO0A)Re*o~Qm7@0L;D%v6s3MI#9JQTF`gD*tK7lp|bM@SKse zTBb(39BLp;nbu}h`dm>p23!axvU?}20#2%sKUVv*%`S<|z0y)4`?2DuiN@U-m1rl9 zUElJAit{)vdZV)63+!pQiTsTPV#SNq!u^3(RA(u5Q)-Fg?+)$^_n_=saB3E@K`3%ketmf%xVw_H` z!PSHp!3xymvSNSORaDeIoA{{7mG#>8F0_Iu^QhahFSn&dL~K{A|HN!nBy$9NW7C@i zNEA}4h+?Y$gIIq9Cq_;ce`Ls&#dY{`s;1P+)4+&Y2n&OKMzer+EWwi*$<2}N9p~>G zoU0_3o=V++uyZ`Qc+LUSOjk1b1c}^$X?|0I7IU#neSD>SWJFoqwZ5FH#&Wu$O0YL= zR*l9dO)ioYY2C__A7+%sXWO^{unyugf;$9_U~8eZ;dHlvJ=A4V!$s3Jbp8S^J8^#H zqn++l=G@z^Xxn_Kx~QKs_hpMcuk86^;W@FwTOp25J*FO$eCWcvI&86gj+iFETzPC7 zk?QKd{Oh)j(;A&ui*LVJKbyM>Irwf!pYLG3_a~I{nxPV z_=8E3ZFm|9Ubya%^3G7X>h1QEVDIIh5*U0vDSJElw9K3?NJzA6L&(wZ;T7cS z!-uaHN`tF%c;j|=P>%J! zT;I@t} zvhR?Qe7h2}k+`5_&bmNIqCG1`qj6U`b?_i(+LRvO=E`cuwnztNc7tgY5Rt!iX+v); zUe2oZF7j_EqY#f6IgvwQl~@Kvv9MYJp469#s7U0*8+)z6O&DsEh;hqeO;yFvkxf4$H=EMAu})L>%#rVY0vyT?eP}$Jp9*QW z2Sc_@%I)LHQ%;9MBoHT!`aB7yM+0qBdEu}x(c{(3cWjA|=a=VF#V+*LIeo1l0tNNI z;2vdhkCfmAk!K``|6tc}7|5*i4B??Xz!bvjm?9AacEd~_4GyVG2$926PNSEU)H+Lg zdHqqbLaA39<$Cgs+6zBV-EUg#9^W-Qy8SrW+m{IS%nNStlRok;_y>W@ri%+?!CQta zGM!qfluCsX_2wSjHk6#^bhRto;pDZ4#w?mjVQTIp^q$oaQ~Z*=Mr)}=DD=Ssc$~hl zfD|JwQ{u%m-lJL#q>T(?mU>P+H$(qKuS1^fKI_iWmj#|^SE?d;U07XQtnQogb5nfl z`jPVde3=X+hEvB#wK6wfaVDJ_LAWoFxPr4KFgZ=bT^=bq3}%f_AkhrSwJm{QM5T^| zcuywM<247uYHcEHaVs&3n%Y0;Ip{9j%My)!n7ah;nWGsK_zfz;x8Xi%0LW4(_-Z@11&!@bV&UsgY52{uEATe(UlFN>P0=4P?^~9S* z!$-ZR(4Z|BbJs@l9-~WVbvXhunb#T=)0)87kkd(`4gp*Ocu-i!0(j8I8sTtHlA&!w zd-K*~pC{zeg9mTa`+1jv+c>5Z2wQ`d?$Dmh?D`Y0lMN!BqtzoX_mX*3IndgJpK&JS z74X~eo1jJM@8&2qiZ~95dh{Y8WdC$_BB&cUc(C^Cv7h(4%zD4xPO6>87VPR~F2x=5;)N|8Mnsxq0NoptEnsjk2#v)J$2yo1Vds&MVsH4kote|iqw=(}C@_`vJo^`D~LA%@=zaQ%d3|UcaHqmQF$zFV?@z9RU}IIuAOZ zj>c$EFvhnurOw*Hn`CKQtaQYiCZ*ne`yluW%H)wfy5MMfVk3F)Y?C2<*NN8;ffjw2 zNf}Cd$yoUuoFevgk|RZ8@*Ox*=H5cc+`lbPYB}^8tT;LN7N)fJ?BwjsFgeQZ&g=E} z!k_+kqmHe?mo}w^!~m^RV)PnIC^VcIvin~ixja5PjSW%ObQ8~mHVs!hecKZxmbhkw z(xxQ|r|cz#6K@;m5S4zpj$PqB7LXEo+oDF;0XUDtZ9Uk>cMoF4Am#@{| z$MN7ZQRlLXlbU<|-#E>E;55S*Di0r~q~=#ivJXXk?zZGgda=u$&wJ-a^nt+VoH-bf zvY9&I8$oP3ulMLt&etF|J*elS+4F%(F+rqH?ZIt$OQD70m@{}yL-X>|vZyn;&BM87 z-Ciz!n}!@ZQoA~P^ztq7^2J`kl9;iZ#u#Nc2W%vC;*Cqz7|wy%uvh9>4UzkB-=aRu+jfO{mnms?J?2X(BL^&MGwCM zqI0vgeb@Pr@BR75ZbV*$Je^2gH<2dXi8nrX_xj;o7ka=pKyK^w;5t79t`n&*))~^E zI!|6b>Zq76GGR|wv{YJ(`wwBMOGWaZhjib3hO8e+fb$$DiAqm_zkT@X6K`z%(A2fJ zeyYKFHrDzg_|D_tJ24GmjqT(pn2@X%5HPm0VfPy2ACTw%{aiNej%JmurYXvI&c^k* z&J_60g9ji2 zrbQhRr%nv_A$xSQ*U4D)68JnMlNOGp%4i={jrm&yYVsS5Jv=R+T4( z(!rA<&UvL?-dR+@GTop2m)X(1^J%-m`aQ7BWz>tkg z1-O9J-u@QY>>I#l3d*_|Xv|CF7aTfvN|Wvg&S_x2hRq-%7Vsbq36DnsHHPSApB88a z5zth~i-#=b9f`hqL~%W~rKegxXI^BX&mP-MK@m^1=7#Fv91YqrIO(f7`j19&*ZTUqS*JIM8R$Td#UL#P30 zbE^=SDjEPJ;zo9ts9IB$ODAxz0b?>eJhJ_w+Vf8(KWkdrHMexORB6;$l?L*Sg9ksb z4HCY=M|!)a9TXO8wEFyeJfY;ZRtjzV!`bQxo_5q|WD1lGK62vqtw*bQ;M@BsB6eGW zh{bR{q#h&xfoX^@;{^=l2LWTL>7YlCGDz;J>s?N7FzmwI-$+ElU3k8-r1VGhJHu`L zP)E!Su6Lm(+Kie^bo|j-u~{u0`TAJjH!<4)=k;v;dmtr$kLjf5b)9RuHlB@u$me)W zFQOqBuJdYrqlqTCp=zr|O3idORFD_n98R=Pik~IH*{bkIkdhzMk0Mn8?W&4=JZPUGrqzTu4GF8z@PKo<+xRi=Tk&F*HxQypAi^l{-6#C z-I1~S7P2Zaxa~x@wN5^ZMt=8UsJTJ3y+TlyE?-szMl*wLlWQPWM87) zVM~YS`oqhfiul{N-14ZRk~5+NRiK-Bj;`P8HMv(bDp%=#e^eWCew4<6&(@95iN7P? z*2QKHvkXFmv}mrpnXp`2_$fNU`XN9bHdQA?kA-p}+|H%)J&pJ~IupY@#y!oM=pZAhnuV*S7n^r!Z#jJKSUXn#UJ-lsFm{fhaKgf`CM0MFeQcVF)%v%5!sNrIKoh4s&aS4yQ zL6}?B5GJ22*sZO+vosi5?<&Y1#Y%;i*kCGF_1fVvai{-Q-@wfwW&sZ&3K&?4C=E;@ z5VyI z*8BGRtFOjB96aPDv)Ulxq(P@cpf`AnR~y!6uOx}gaGRZo6n4GCtCbnNU01r*bIg|+ z$m__-AZJKfEZAmxkPPdcdE?h~A$ma+?j}yZ!B(+(=KswDT z2e7tC+p!E9#renp<2qrcWy7d7Qi$cxdwABLouuHySLo(9g#EG8n<@O ziapd0Gu`<(>*jKaTNvEu1X}JSKU%8gi zbGKKQ7ONdC5~0kR2_ENm^}m4c37z3g%AV@-+I`lHXSZ4~TjW|x7GpG+DiTXHZeK}O zcAnQc(1l&vbTVn}2`{2EgGgLi4>ik{mSKCNq?t60k;y=}t_ zQAaT!v57z674E&Zdj1o-sDnp&l2Fc%LA!-^CC@eH!;@O2_Ei02^#@peG&Y7?nLWz7JXEtYuD?bjOEs3yRceBGa2Mq zOV=J=XRLpjt)1m&q4yFbJ80IN)?hV*bgq`}olaH{4#cEm@h$DGt`@E=>j@|~v>4*8 zkEc6~ss5t5O*=9HXK8N{$vTvrK%i}KTeZ2`li-k^q}BPbqm1vTMxSOj==_j?zxJ6- zN8qSDp*=TWT}~SBayt9WAHT~HBOc9!%41I?ZRd#42?jq?J4)WaF2H>Zv51y>V0JA5 z0B}Vu0g_$j%}zh0<}=#^na#CV-^cBA2BKz@-WvKG5z+mw;J?>$pQf<|01ot+{%08r zCDHIdlCa0uN^*wj+OOV6HjpbUF=FGk@~!?pIT0QNA+}F8rDp5cjNkFG-$7mS+y0V? z;uU|JkNx0O?Uy%m%Z(qjn%2MNH{0FH*c{#80=a8lmAfC<5*(?Z0URlS&*P*Gc$PpE z@bC_2G=0a>owseM{bGW9f2+|Q#%X)*y)#hz^Jn|9#|`98bo@NXeu07$h`2EepYjT( zsLv^_(Es!^XYFuFTKt3D!L&)IKJwZAJMYAcE#Wb?za{*J55FO!Q`VTA8F$Po$4~#g zs$ZQ-sr@>;Us73L`#FAgr&F2?3~^4YIn`?3A93qdr~Wus`#AZ2{lBqCIo0IG9@!Qd zUnAhN^v*80osJGyY%$Pn3|Csy>dnbxVLx|&Qm>20^nTYbv|@X5x$+6HQlvIYZiVRn zjbeTQ^M=r@Y!g1xQ>7S90x2AC(|U8ROq+YNV53cUhb?FfbaKHExi#jq=#!iKG{%e7 z+NVXRSwXZ{Vp z`$=%iYu_W^s#VD&jqjni6c5fb`wq&)a*DnQnAL2Quu+bW5|CI8?SE-HBP@p%yIqPx zu>c=T*aF3}CveCvD^_-K0!hX|yt*f^dS!NDY_oJ*GC#UtuDzsr+_Iw@^_bFWlP5}k zOuM6X(kXk31?^x{ruO9<J@K{vaRB6Hm6IcmMv~yZ37R!>z)x-9)S{k#Nj|(9OvE$)oYi zJssu8c7OE5?}^8tj;8$!9q+YDXfi^8+{b=yg!}{F*RYzVQzRq`=rEgxo(b!6wYbG5 zi5l^5N(^gXK&SNaGcVcn$9>geve)+ zRuJ^m^zg-sB&cO$?hDID zub;P84=wL(A+eV0#x5ZjkMdDZ$e%UxzQ^redD%j`PCUQgoZK+d9g~zW?OeT}Y44hb z++MBwxB*T8J8h+io2{t%^*lNZ;pPx-k# zp2k4ZocS~<`9X0?9Q8Q-vNWiXJ6ij^DzdBBNQx((KWa+rOfg$lWu6asEf$$%ZUReW zI@tza@=wUNLhFD|lP#*c>1yd*99Gwj_2PeNL05)WV9@M7=sesU$`c{w#LNlU4Hy~m z6->^2Ft=+dFi1z=zlV%e0@h(J?1+j!cul8Sdp9@NJswTXZhX(w1=Hk+FW_@&EP9C} zQjImX5b-@a*wUBvrxh1ty6Bx-%ZlyZ$O1*`fB{j$!mCL6|8}KMgD_okza?pVSoL4i4^3Ii-xO(q4 zrlHf2HCeJA3-PjEkvE_B7(y}`vDo~nHat@%E0-5zT`6}sj|s6vlux;nq&sw@E8p$x z@$*b$V7-269Sn4O^81E%wHonQ_p)`dzkMKVDQp=T_bi4Z9Z7xHz^r@8B)A%6DBa5d zw-vghpxI=$7YLDFL<5osO(Lv;r&rdeRh=Wp3%n5VZ!IZ!!78MGYI!0K{o?IU31C*8a^8K|dphx&j!(96O)Lg!g=r`J{HV+LEskJNw0SoC_uB&Sajl&dlI>o7L zDtPIEogR-NOr+SZ8IN%vb47f?s6Tgp?M)VqWqMgOmgpJpNp!Rbq!vD8JWhtTZ;QDN z*|drG6(4TIV_7ea$J)7Q!D7-zJgFiVDcBV&fp%bVWW)bf*K<2i_d$Jj z1Wc^xGuLS806e65DVnZ2b%@0Fjxf8UT=EDxT)VG-3(*FL(t&WqG%iR4#d2wgg!KDv zA!n2EYGldivYxXNbF+KtIm8*of|V(6vFNi*8U@>917q=$}$@!?PLcZb>I>s(;d! z$(nM>t=Z-7O;Zaa`B0xK#(RvwOYqOz)P?!5I-T z*+9@!{o?8Y@}sxYdHTi)tLn)g0#q;BV1mPs^pj#;& z8|m3_+3ss6^p&$WR+p>st7E=sX*6!H0RQWP5 zw_Y4gM@xoF$tB}tu$a0{M-JO!UQ;@0i}%({%!4jPf<#dRA6(5%kiQ~!Gtrk9C|^UZ9yqRbcL?hLD)iYe}k_VU1b zjwHR!Gu^&rkn|ooLO!TdZyxt`RKs_BeAA=;;5DlU`Q!fCqbFVmb>G`?S} z-w6)p1*Ti<`>f-OsS?#K0Tdg2Wuws*&K<@JNenohMK>RHD&5{Vx&JKg$lv4rVVl$% zCL7F^P(T&wl@j%EINHI>{0?tq94a(v3nqNZFBs;MtzWYFWCFc%S!uyJy;g7IdYMgH zMV_lZ?v$8@ChZ$Vl`a&#!9pT?g#-#MW<*&Q{t>ZFc!wuUfkn)Qce9<@Z3c<2qu3Ef zJ(KQn%WmOCyX@i*UAup@cTu~|uhx1~9!{v+QhQDHrRk0}nDJ9j0u!L*BN|UcVdK|p z=TqXLN$!$&8*|+*KN0Kjx=zkk+-X4YO6|dagX_JK;UW0O!YSv4Q(UVY&;>A)3Z)th z=rt7{qd_imhf#X%k6qYOI(+uwBWH0kXMDW1@?{^gyor>qI4lg{U84vMz0T=r`Pkg8 zun94nD%cuv5`q1R;%wBQ8j~E;T!(eMSpNuj4JXGL!aA_th^7n$sOm&RC2cY?G$SsW z9c5ER*u2_R>Qg|n^!5SbL`>~niGAeoQL;y>i#Q}Z1oM1I+a8IgLNng+Kz^_{O)p@^ z6!DQ?YP{XC(gRqT%r3Kg&BFZ~t9HMfw6%}6Tw_R>Ztd>eUg22|2A2L7@@3Q=G26*x zMGumJ83rV z#aa9QH z%$=CZiuE?$U7nloxQr87O%{&|`7Mg?7Ez1!H=Ns<_ztTB4wW4Y|BjUyr&U8fTo5=b*tTZH)}GV&;a9&=M9_ zkx98lYA0}zXRT%v&YIYLHlUtQF16V*(+iitD8~&}znI9PI=dwkS+X>&Bk$*A3qyFW zYrt zCM1GH`9Q;7|Gj=5Hwsul&kLRndcAsD^y?yuI}kmdI?jbMBM=Zc&y%BH+QKu23}!Xw zbDV~*XHT3@8w?tAL{dg|RBYFA@3Fh(E7nZ<4Uc~h=CcvmCL!@ zXoU8~WTEFY;dDT7@bHoA6th)}NNBn3k0}{Hzvo3b&WMB)1mA$xp)w z+D*Bw8s%L2p814F=!?;u6z!)Da1u@iUG6>|hcE<0i<{9fLkmz4n&3jQ9w!(eI!urb zAZRfBjk^5|N}|sWd;LKi+vC^$%k9>W$tL4O(&bL3CNBt77Z-Hx5kqe_WoOkrb$ zo?Yz^oD%k0fI3|C*NEuY4u4}pTqD)P1CL{_u;2_F&M9kO>LAzD{$vjNS|?`dNH>2t zWSN-Vb0Zt+7FpIfEl1V0R~FwO=US)ryN8_Z?E%ZUVaMiL;)?Bbs$18bRk17w;-)(E zYVA4HlGP9$L|FuS#^(el*F+n`8f!{%u#u>8GGzaDsQnjg=c{zy9DBof~FWq-xJ zY~NH3dqNSld>7pxIF8y)_4DfgN#|gQ+=A7DY;cDytihg*yv3}aCPuEkM8q0b#LFon zj;KYY5~A@;pbc3qD*Xo&599)x&%P-)qsLC}*E$_OiAW~nBnG_EtB|y<@G1wxoCF-t zBlXMce`eei3}yqYtXeTT{tva}wJR6?AJX0fzOCwRAJ@6EBoEn^H9a&f>0aqt!+Y;I zPU1N6jO{p^EHZ#V2_qyl0|FuJkw9DahW4d{RiNxb%Pehap^Q=rZK2Szn!Zh&>)-SJ zN^%lN-?#t&+kSX_a&+(6&v%@4g#A2^K9?*w}FJ--(#`43RJ6*f<^eW`wQAyY1IW?!t>2B zDKFcdXXqA1`QdrD{=Td0(Bpy(EG%PbmrQM)dIxQcsR*(}6}sLiV<@1Q3ihy*JKZM! z8Ay5@dD(tntJ`)@Rrj}q(VXv%%+f9?bXKcTduff$cgF7%op%}WAw9?JS}wcRsa-a8 z#uV>A;#&={KSPf_whr_ptRo0by5*&z`uyI6d{L3)s6BapTr%k+lU*(0#Ub?8e2yRWur?D)vA+3#}dfx*7cnsbs=(Yf280SzV2mp)k+M1KYCe-2%gkb3Yg=_?TkCprSwpz;6dd;p7pw}# zio1hV$#8dZEV!y59QQ6knm21dow{V|H>q_uq|rbNG*oP;TD1BrZ3DSpeS3K+_Dvgd z>a?GpxDcPkaSac{XDg}4!80n?53~=bemM0y^pq~O6afiWKa?`DVu$tE8(s_ zb(ILJ+7z!y2x2$%gHM^#pUhi@Y#FGYv3^)K>0Pcu?ZU964^>Aa4@A0aqdRx^cdbkI6yoHNA)o3i zX=}ArQ?DYgJe{BN!pxIM<%WBik7z-jPB$4V)-4Rp3cJ?~O)j~+W-t%L5BUOlxj98H zZLd<0Z*Gi6oa1XMqMnj@gOy3IUBPvYl6JMWW$L}D-?OEL-iBELd85>LvrK54ldVom z$K}DY;-+}Q@|ozrQy+JEuAY#a$}G}b^)RILkXd!y&h$-Xj7x#H4ERrv1JPIy`HBS7 zo)V4P)1vXTTw%48I9!(8yn>T$hg)8|v8rlgWuSCGsYZ!ePnw{Kmly)NCVJ-KYF8ykF#`dDO3_q>g&+7|FPm6z*p_SBTk*27?ER?Nux0UtV;1SJ&l5mkm_Tidu6d(dnqI>sxbj zM{OOnAZ(q_bq7Tqx05r|Qs`pbix(_{Wra)TDnzW(?x+mR_HXFSTE2YG!i6*Swot$) z+bb&%{ZsUI!A8POuK$?o)F0Ac!*e2ToEn^^TLkThK1?$TvXiF0(XjQH2gfcS+{uk^ zVPg#<;0R#r2zs2ib|$gW8eg?Yz&ywH61r>gaf=QR>{#zi7$Iv5BRPld*z= z4J*37SKq5`&)%|PeP6j|GRtindPh%dbc$~6F?avWQ;U@T+KX>1G>QRfmSO2>4Z}%q zj`Vi-0)u-_JXGV%@nnr%9f@>Bl!Fb6aKYeT>csOu7lHAGUE!GnO>*7jcy-t0iRC>T z-1T+-pM8IK$lkU7L{CLqopr7Ms(C$vRfeqSEMjH(2R%0+u zw?f@?Ncq3D2Tt|C-AuJd?Yf17i&rj~Sh}%);S%~A`#-jeOAgzk^UyBZ&lLp?Gxq<# zc}RzK^m6l%-wLu>_4i?KryrIz_Ca7RIn%xA+VDO#Oda!tyRc%d1DC8QbPV>WVIpC9 z(1owj(3GG4tW;-K%=KiAYuyXu`rML=$u`ev%P=lFEn>!RPhRY8o2&~Ln)Kt^O{MdC zH)BlH-FHRLIU+AF7z|y!rBZq!)tQ|$-FY*{4!zpA2p{beYtO5LJzZN>BuQR%cB9%g zaZ<2ngQvdMkNe^SU0p4W%~q=z+JSG8VD93_(hsE_7R6YuyIr@!Vl)cO-A{oR&q@D6 zyljN=Wic7c#a+5Ji2wW4v(h)y&wod^(rPqfdI9kVv2){*bo{lty+{YUSvmbnq#M%l z*Xt?}Z%U{CqO>y||2^G0#N+rbr*lZUBpv^>?smjO5+eRHoWH*!9eChw+PR} zDd6}|@a>sY``o6x5b;GE|KC%8mTpbQ-=#Yb@z|Gx=gCGrKOO&&?k2?BIR1;NN2Oiq z_-l2Oh_`e6-|_r6(($+JZbUrf8`Amv)Z5awbo?EFI4{Ucnjze{IK=X(X>p?@fysYBEK@@?Inh{x`= zbozJUd7}~bG5&pE>JfaqI@K=E>Lw5$$ULV%J{2#Lx*PD#8`Kc;nm81M?VhK@Tu?nJ z%JfVIj3eJRf8H(uinZe&XKdUl6f9!t-%zXUI@8v4I6n0ccx6+my{r^_ zC!M}hS5|#ROXqnHU)Q;^Ykso9THy1$lu$*;?(6PrDRlVc5`T%WMEK>##o0EG%O*M6 zmuxC^XFFVBd_wDBUU|);CdKCZWtaAl=!I*r&R^U&vt4!+dz#f5p}f6bd3=s*NrT7b zOom#@Z8@zIT{+(5M5Co=1#A$U&AdT>s{U5gv6H?+5jvul*gZuJ>W5~OrH2F zkK>Bd3pRId+qPk4SzWb%Yy7M`b2D|4KI?B@c}~~odW*YJ39Ru1J!a*&P5q;b)}DCi zajCPbdDmLII#_qpw_HCU|99#M{oP!C3w@jH$X>!aIf_GDh@s@zpD+beljfL6#ddJ~ z9~DdcF`0Tbrc1vKMbhR`F(;*Gs{3ZB*`1d18Hp7q+;dOowad0%-JGe;MDwO!R!o;Y ziBf4>sjOW)*0j$Kb+m@o?rMh1CUt(^IF_o7x@;4M7mwRc>G%3;Ajf3sr;u2(u`?yN zbdTz`(C^ya>E9LU{$u=(x=Xj~Z^Zi%=>q;8YXl_sboc3&aQX-J$Ln9k?@|3c`klr# z%f*;(AOHTg^jrOXEJu+e#{E>fT<@jdtI$Id9QEGzG%rTjj+ua7q0T2`py5s)?G4umz>qqOEX2z!Rf<2?7eVzH^$^V!h(>BvR|2KA^a#Np6ow}`%0odP$ z>$@EJdoTjK;A^bV=1sjY^>^4Fr->o$ZZY)p?Dn!!36}|kaq(JKj?WSmWxJxvqwww&BT?LOz;RC+74dR-;A z7u}qSR~2e090o=^>Cr)uRY&zuWzA)E-!*nP{Sxj*b`*G3SAMQOrz#jLr}ing+@tk7 zH#>HW=NOE+W)r@r=QWrM%6$Hc^1fh!3p0Q4fBL6nX}9z`&2@!wK#RN|w!~@Tg=kPC zB_`(0$}cR!Cb>@Sw29T{N~8aJsIE~zBwDJZ^@qNPcPSlcWATRcCd~!o21}Ap^#8Pc z<&|Js0V2Mo{~_KOAK5+9+uPObDz@b|?@s^4)xBDy_zC^jv#_zlFjLeGXZ}(?ur|aw z$Oqb%ig!2{I^#w{--Crian8tvob(T7>gN_TUx*uL4t;Sr(KXqQI^5IAW!{Ulim3mN zP7TGZbI6ohjo3SK5hpgU*w)#6k@Wf@<#194X%-5Ys=w5Ow6NNPg4Cs6%@pw8HHEWA z>KSpW%`c8@89DQCTb$X|oI4<-*VmOJ?M%VH+#(<~#x~UfUmJWAXAaSy({=dw89Gu$ zlR8E=e<@1?X6~%bhpYFM3}4($Ri!+qJ`kTEPY2zVPQ6WjSNcv5?(-Ul{!5+j5TA_M z%1R0qPgU)#StEZvTv_VyYuvW1UQ9}(uo(-CCVg={=9@QKA80Pb{P+2l;qzdsd@-`5w-%p3 z?{74enTiaCS@k0Ky?8}wwd#Aj2zO3&E$ftQ23wA`z-G%U3t5akE{jrc=eqn&{|nTm z7Zt;9KPp>Ipa^B)vZKtX4l0%~pk`EHJr>1M^;3wRXlVGvVsk}J1(iz}Atmogwp}nj z(7Ykk6fVs-iTmn3s_L1p*^tOoP2B6Lmpf%Dj`q*)S9@mNy+Zpz-vUdq5~y-J#hb3e zuw2pP)dmh%>Kg5$RISqe(5Qa}W$!$y?4WddcKPawg784ghuXdwZI@rZ{VN0H z&?Ai^cc$cRnUQ$#!LzB@PY$$rV)5d~tDWxSQ)9a^>-M9^1I8-#G`{SZ8Rl46i;8kbeN%h1yH|g=C_nUh-&k3=ry>5%p>6+5^WuAp z&AKaemq`;AIqMm+&9c5TRVSPn<2va|QLT@YthQA*)Ue}$sZ6o2FK?DMN;S<}mQ@SR__=Asc5tsIkVe|K6SDf`dv8jB^tcxzny6%kcX_rJ#?%KBx z#X*{oc={5g39p)@EshO1q-VwtJs^EfIz}@0W4eN5B&=C|EuN#jZ#ud}3J+u2I8avt zic8IBZNzmzU6Wcq)p^AU+HP^`P;-9&p*zIs+8)ZMX=+N+@IGuGxr!f!3w)ju{PRaUf}zD)X92kjOJV#T^Wt4(05 z5y=`%IpR{Q1LMw+Yeq}G|ImtY>H7A0;})N7WOO;BG^(QUS&XFc|Ez)*N6A!c63 zEskml@t3nsNVP)yQH{XwkY&0V@4##4`jcf+2S&ghVsWGQE*jLX-F(KrR&nU03&+K& zGe@+?)~@Q`B6|C`tHM0f zveWV3(;pR&^3n6i4|_^ZNWH<_(@FK5&ZAtud;gXyH)apS_c;EsRQ!FZc#K=g$MKJ+ z;z3iYC&sDN%<)g8;<36<@#tmUPdNU`8m_?G|eq#xn*#HU+x_@om%T{5X|P zT`C=u@alfe&p*?fdS20W;(2+h5qpoR9iB}+|Bux3O-Lt;<9~8gJdL4%^f8|QX)6BE zY|a-m^Xc+GpNglkkMwI$&Qv?UhYjWh0FjHl~T zBM|Wz|I%6f{IBsmjiAd?>EpGlq&LtG-{*9mOs4}`hw(u14^-0l!KmQpQ|(FdsrGyT z!jSS!)r;a&^?IO@UJ$gi9^>s}mRfZGWsqD#TRXNa)JW|^EmyA5jvs$N+6*Dtvs3xG z)N|)m+QaV2CP+s;?7}DXC+q$ALI``7JV$GE@HJC3|D^Yj2hgt92k{}F97#H5$^5Z} zJ@MvbqZbCtY*;PcOJ>gwXoI*!;~w$xtdV7lTe_;FWmSq&>%}S9ct~m09v|6EKWjrM zYrgIz{T<@Fq#GLGsnC=O0Jf9~cZ1f*p@PlthVoApCumoEU4Hi@(UraJ-0W@1i!MSH z@oj=N67)4}SU-F>e#69R6k#P10@74e6D(4U_(N$*+qFNh^Y?G?da~ArhoquSkN;s) zcUl0j&D}bvpRgS?GO6dwBByR#jp@q1X%ZV)XKsPmRAWI3K&$9CicA9;*K?g$OF7am z(oyvF9ux<<5?x3u#ry}jH*288=dpBo#M#=;^8SM5^fx$Xqc}U5wXjU@Eu7(wl#k{g zCo6L{h0(Y8U)EXDR(Sg=nl)HpNL4jmS?kq^Z@o1@=0+TL#mD@DgMtV8caL`0`vs(R5ufHaH{c zCvvaPm$yRuM9A~CFA8zRy*$(Bvn&$E&G-W4aU!W8+O$slBSkyLM2S3Kn9ZyIu)*uK zP5dEU{(B`Cv7@Fg$o(RLUnn{Tv4pXhE=17reA@=UACtpnZSDz?uPbu-ww|9`3e_{(SP+8jAx7EwW}9de0qQG3JU%$I3fzn zD~qfPa^>Yt$(p?unwV0b`Z%itd$6+M!Mo9?+;12g*zJrf`EW$O97lz*q$LzlE3Oe{ z(>g`Vc0TGd2uqpvNm6>>`KYsHx%R0?6p7cpUUc<#v3-^6QD^Hi?UOQ5_?YuCr$PI~ zEefHx(Vu8>y$^E8fq#&6OA{S3EB5_udzr*?;o1y^ZDnB?I_%bW^a{+(%9hB z4v2jJFtr176=}6|JLqsmKP>3`XSV6Of7{cfX+ny|_dkNl)vwn6S|%(XcHZ@w&`n$z z676V)EbUWNnDjW;)2#2)eM&MkOyZ4Y9EtE0?K$M2AEeq_{4HfSSE^;jhKZX?vnFmS z)=%DeqratelfP$Gb@ltw(&b{=a%pKGfHOZxJ4Zhtor`i}JP4Ppa5+r@tS#_RFhJ|L z0@8yECho*1WfS|~(&|jL_>LQnra*GMpw;f!DV`S%AI!f+H#K-g_(oH$GisBz+E0<< z=kBKQpwsJpVmoYAS`)@S+$km-5?Nmg>|2`@_q$MbY?LF_us;QX-GHrx5v^g{I?H2 z(@mjOsU1-wtT%0?`6U~VbOWk~75FSvH-s8Juhs3?C7#pjO<{+(I9E=~zTJuJup{81 zec`Dz9FhrpS}^8V@?{1aOsA8=C!GwbCz*WukIZoTFE(57 zzp-nG{$dg(_3!&OdeL6n#l!3Uik!RS^`^myoPYMai<0>B^>^0#YE2T>h*Ckk$W!6ZzTDq-!J```_m2UFF|q+CBSl*(>2|z{8eQW zF7-EH(G7o`F1kDdyvGq3Z&pgW>(^b>?s?OlW!9qE)s|qXMU3a1tMw#ym3qG@#ZH9F zL~iA?M(3%+|GE;lp?+Rw-4he{XU7Z6JvQx5%h&VsvD9X872W@{?5aY)!yJ(dwngxTR>Q$ZRbHt>07K(iF zp|i)RohmN$%z%i49wD8i52AhX&tq$))R=%sF&?d-0)Btdq;%P#lRW=kQ{{{Nq7@>7 zk&B#2o~_i!AWveH*HO|a93-(;7sPBY3x<@cI6_7(f5qUj6gzSQXXXTQ?WTfkW58=Z zj?Kfb%CCzJZcABd;pQBFu06+MhJ}~C4)!69C)9U2m!VkaM=2oV+3HRyVN98)ObVk& z3uf^o?a%+?)@+m$V)*M(AdMFSEN?-w~6k6$!L?*h(q5`s}+YRurF0I%H?l9U+GZV+TZnC=4!`y>tF*o0 zb8vU{#wfm`K;_QSnf0%sA7r}ALZqw&jDVEun07mmA!o@3L#9LYul;=38Q5Z!^Gk3I z#5hT)jh0CkR5N{&r(pROZ{gyf|LZO8ASJn9x^g(U*%;0*b(jm6$(svSDx{|*I|f9~ z@!Inrey*Dux`H|hZ3&;{9Bv1)TwF>pRr3b2hR7H=VxWlE7yNmDMrZ#y8ryfqD29o2 zA`O$zKlpP>iN8CsH+%tFK==%6GEIWvM?BD9ogRbbUtz*w5u7sSsKpQS!0-2`#BxA9 z@SyzF;a?*1A+i0~@>fu_A}Yd=3&g#p@FgI%g-an}b}Wa2t15o7;PXc1s~*MY zsHPAfyeA#}s#Ea+s>*GMTQ0I@pl(jm`U0Z*=pJP|CS5PiEa*xKV^+7evn7%4Ol3x@ zGjZ;n)WtvFp=u8PRgLZ2H}>(cEM3{$L>;O0p(FKv;|6UW$(TPc+7tTlw*p!!i+VEa zZ{S)XRw&C!)=;ikXE3=M=&Qaa%%_kqlcNg~$4g8|6c@T_Bc1KJuQRz&zI zFY0H9J^>jEIrAkMdw66WV?33Abr_ZLFv5sIx>-|e_0PceF5rGWT0)TVMpgitm_N6b z3IO%bh?C@#NKj~R%y;`FZ^5#I2PX?8J^jVx!NIRei$kx?kLtaq6o9RUZ%sJ{_MIXc-cnYS>N`x{}LwaB2gZkQRcizeyZrJE; z#ptpuJ$O(I7yG;$4j%g5v^;i`RyZe)3USgc=2$@cFL|NXFJ z=5Z|9xi5*gBqw>aCSe59`P{k&>};f2nqWhau2!Sw$E*eu4la>o$AKW+e1Ecth~aX9 zFW15Z8*}(?!=>qY$9?fd_^+P)6%-eUnV})i@JZ`G{&?fYk3Sx9`CgIwOIjT}ZV-l9 zCGOex8+yxJ<>qYCo{(Pp`=#xW-$&M14s*6C|AZ77{DuPCGDSXQKFU$)57=e#33S;g zwQ%^?_EX~eb9N`^2O)5gHgwrXXwL##En@i_$Iz-_IdQlx7mWR>DcxI5e+pOaKjdPi z`RNBAeDJxp;Nst-75|aZmKeHePTT`|U}8>W6gznx8JQrV#3h_4bhJTnFLy0K&nKa zqy2Kv{Oxb^0%oVN(2$#Ll*L2oUVBLUoqdUdnF*;Mu6I(Lo3jy||fpi{o9}+!`ijw9qDMc>yBMp(%w1Q$Z)G!lFIFg-~2GTjc zE5{D>1?S)fi$Dt^dbAhD!W?c2WNEkJC>304ijTByj2R2Jz8? zi@%n?IwGQLwuOho(jv$K)SuczC+UA6nOPF0O2T~46ab}4H^hA357Tx2A?GM-XQyhu z?X*-CPoeZ>5j{9TtAkWQsA2PApP5@xOC@{SS6s*A<&Y-yZ&HmbP)Y$RKWI04~s)T zogXDln3C7}f1;i;eHkF5{oum!TtDGq>%H!<{Qe8q@fBk@+m%~jge9MuaJMer;>};%W_4Bd!z4?x-sS{ue5$j$ypjMX*p}iD6saFp-N(w z2F&FwYNGMW#jmElvTn;}DUp(|G-whebtW#9`41WFBx|&+hox<}^E3!cxt45YFHTI< zI8Y}tC`>`zehSZ=yV8Ww%KC-JSEe9ego?U~i(h|z=dRa(v(hb#zVvh?v_SuhyZZXY z-?+zxEo1#i+X`lsmBq!m=OtE0E=o;S(%j{}yG33wqq;K|5k@smqh!g_&r!ZA%*J%; z=3+nn65YzonA1ZSpqva1Jb}Qng2gd|3=M1x8o)Jr1_nQ^rs#PkB?v<)6R%Kwfu4TA z&+wt_ONDa#sMFU!W)FzpV7>^KmzeQMxv=fTw>;J*bMW6X=fD1Y*KhE}`|1J8L zb{T%eyg0Ry!HqUj&`Nc>7VK@CryIe!u4A?ms)+%`twb{rxWEMP)mCcYR)VE@xRtn# zjNC*8QaC*l(`^;GE!9^3-;~d@rdo-N>^TI-Xs6o*Hj7EZzTy-pg3obiyJZJ^l) zg;Wr$A^p++#KA>KA{ZH8H0}>K-f@SgEWF=SwsKYDJ{kXV1m%6D-jyvPe<&dD-L-4i z`Ipwu-BCZ+E*kKUJ(nm8w^t52B0k@I4;2ccCzZec@^t>Num0!!kC+Idh1CD46quZ% zRR*Q4@!yuD$A9GNLu(~D$>^b3`cI*gq5W;xB$l@Ej?&3#%0n_;(ucX~%Ri}58=;-| z?;rb9;{s^sK3|__08@4Xw=u!(yc_V{rX7EqQ1CDH>}emt_CgADb5ZNxQ|}QFgOjTUMMidUL5^{FdS+yteh} zho5Vsm%KNzms}NiEgy(2WqqsVjz5|vS7a?&u_6%Yp>hc-n|MdO&F5mVB7r%6T9b4h zUV#)jSdYs)F2SpfsdqAa9Z))_r^O@V#*;b)H^~~+D-n6fk1&-V;eF2Sd8i$9!G7gn>+O(?h;&&cE-?4-J*gsR|RypFV3aV zK4zSJq%|QdwA?zBtemrCWX`*dO=k_aOjegXT@vVNar8wVYwqf8ICJ}q)BZC1vL%z7 zqg`{t`>*!|YTFBw-N>V9>LY26G@)y!-90wG%aIOy32e>8&FnSRI1N^#hiY=j$T)M& z1r~Uay@LOypXDP}v<12T%0_Fk*_dO~FO!P0v)hVnX5Z3izUWXxCCg82AK1CWSZc}* zhpNie*7`nv44-2bh4wYGRd-Xp_^&~0ajCbovL`pE(N|>~GuwZoeKwqBoNtge49@Iq zpDTLHW3iDzZ?dhx*x|0#{um5foDI}ob5NIuP?ucd3x7>0b!wWAU(g+uxcCW&7!7p_}=sl8)hqSAl-Wax}sb3uLYtopXjYLQp?fc6~R1&l!yO!qO$gmF!mDqX&4 zVkve&-jVtZ<^4W>!zzR!RWLG$Z8bQNhAu_3^Id{2NqpM5%_j{_OZLac^KJ23`P69xZ%XnP;72jbF0hJTkq&apA1a>Ng9wg zVXrF~jeB8vD?43PF@3%Y1rjTJzc$*2|FnDVK6%A1`cHD+w0w9w{il6)#jCe%!GGLl zw5QL3I?>)wl%Iwmopr+(R!|X~G>{xGEcv!IR6SuY>CQ{o2L08i+3TFtll|I5e?n ze)Xw8wA&{|4mL>{4B8g2E0`3msA~ttx4`(0(vC~!eOgSQT+Jxg<>&*I{R(Uu#-1*MOb28H z9R>m&cnvntO|m$iKU; z*{keMY2)k$cfvP&$kCN2dspquz5JHZImL0iucEAa{L!DB(B3dVKI<`eNnTY+5f-0l zU-w7SD7RONuZKle15P1+RVs`AqeYc9w5li5Fm3iyCkd+?jucYriP6!4OpDmYN@Dk= z#1y1c+tf$;0NR9ZT^K-brAL1ugd_0)O1@A!+(3r(=)o0re8WbElpd|N)Lm>iQjhHm zEM)fgwyx^#vzzvmyBAlth#51If!qpzxT<_^UQwC9qOrVp&~7>3>z}`Kv|E)8zG7!x zceL7qFQVp((}F>}(<|1NcxqlaFG2(pcLj2|1eR5)%JzAxf+v?`eikH@2<{xTq>g`I*DGq%7 z&>cHw44#_Grw)AkuC$gV%Mqh0rG3y)2?${pzPP-?DPF4xg`(93!=`+1xJ&I?8p$dQ zl(v?3EbBFuRPBdKjp~=hCOzKD_{^|%)xz$RubXI}+c@Sa5mgUZ z;D>wvx(^{PVtT$O2}BEK(dv!lDSLCA~ByZTXPcxPXOqugya>&1L|Xx_5T4KRCOk{#uu#G#Tr*H#Zd& z_S!7?4q){A$+mMgx-Y$M(_o}0;Hw^Mo_(P!<|rvmB>etHSMjq}OL4BPydr^isX)8D zgmHq&8{8s*%?7yIjzuL-snO8GLJ(aWhto>c-p+f z{k?kQ?MF*STk^}IFm#3}NC`hl*Esd5cn)PZ(-(H=d zeT*3!2~%kM)Sr|<{n9`-l4wYaQ&VNd2?IlVT!i}NL0l9x$5E#^u2IMn2TwVEvZUC0 ztU>ItO<_~;WF%bY3)1+7CKgYkEdIk~(L?6JgbJoaVkYMcC}*@=Nocg(wk#zM2h{u{ zatxgT)2wRnR2GJjd580Wn<(x@9<(n^&a@>Nv_Wb^@>;n1;`qY3#}r25p|mv8UOqw^ z2_5=eA{hsZjqTaAeoXrz-0x`>Ud#UwHy$|(7DREgC)J2r%@;I(c}%pnk`>RR08>d8i**!fDnzaU0&Wx=-DzgkDRF6sJR6X|AZPsHv^#9GKbGG;SDj*Dr4w-Ckc_ zefmJh3F6H%Pq)5xzRk3n;=h|hRD@xqb zQ~1{#F_#1F#vT}VS88;!(3-YpAmR0y+xr}LVPCifm)@0j#d}72yK7lU)wQM@Yc+s6;MyS;3F11;>9U9RWKSVpg zCYiR7ab&rHjB$`<+UmQaPOkRA7|yf#m5z~S+oFN|+=|wwMHzcf+>5R7ztldPyJ#X2 z&L3R9v^O|5FJtPVy%QJ%dGNy^FVixJQfH~0V!Y|(;Z@{%wXODz#QMk|!*}V5C&=^a zZL69jPplQ`kUtkor4vq-kGRYqsY#8O2JyjH6YBg*IwZg8+#kx5#Wom8?F^GBEZVNi zkk#(B=ViA}b~)`wGSuBx9T$_@+2^1g;aA~{Vc(V3VYL8WPU?=;)WitpF50tUqF{+K_x}d;3Ivk$0-jdMRA6hT5)fH^@6%Q6w zh93Lj9aoLETURRvN*{9xQ};~l0 z*vzwqNQP&|isOx9ljkYz)j8&>k&?L+HGP995zTGnNGBjIY?9!K1WpO-YuNaUq=1xS zL;J=4>c6P9j@jlmi|X)O!Y%t+&KwIX-;I^^Z5C@suhK@(wsc)mFvnWy@E1lJez4)Z zfoSld-m%qB8fn3$tr-XJkFzS|Rd- zwMB~0m%T~0b=;;*{H5mFWp`qk>y zj%Bx=5cTKXO`Mo&lU7J4=`6HXA4V@@b2jW=WC}Vs^*GXw(b)h^MFoVUu_B&kA;kT; zak;TNDOVPil_y-q!Dl*mEov^Q^hf%aZ)=}5`qCcX)~9R#QlFi5s`I!8QC;T=#@STBuzxUa-G$uC$tL51t9az++;#Ac43X z=|O{H%A#`U6oF}oHJeuy=GRz8Obyu;iJ2!?pO`0=Y@DB)Ysk-EbHVW;yCqbVa7FHD zH?=KlD?8*oK)V7OrW(bMq&w*}27NB1mIn$?e^JeuI1R>cSI_7T*L4)dX0;UP3u@-P zp;q}GYU~rgOhnr|BT2(8O3ZZ`m(npGZlgY&L7+Awi&U>z zd2WMb74GZBFn~AlhgjnW>YH(cZ0NqQD%9!AHpt@qhWPW^Z_9=C!Gxi~BSNJ!FPLYb zi|HuM_w+f^D!Q+VPJu%Ja_iuJS`2v{7^Jk@vl{SyXcCB+T~ALk;5nnXyudbMOk8$l zr+fKT{-$7^J6zuAtM#8IRT@w+~#+dI&wm_Qgc`2tagjNMxU!X%bg`w$?htM zK3o#I`VuACa!c8=@nxkE&v9dmOU?}yg-dIlv-3@|Jz%rvsKJZ!!gE4@IRFUDk{ )r+_%USi1dhK+UkOZ?7=*O+WA-{?*j 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/text/VeraBd.ttf b/third_party/webrender/wrench/reftests/text/VeraBd.ttf new file mode 100644 index 0000000000000000000000000000000000000000..51d6111d722981a86766cc99c53f1956e5c0a229 GIT binary patch literal 58716 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 XXN=0@K!kf}4sE>m|9AV{oAYZwIeYIl?03Cu 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 N_otz|YIek({x5+|?PLG| literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/text/border-radius-subpx.png b/third_party/webrender/wrench/reftests/text/border-radius-subpx.png new file mode 100644 index 0000000000000000000000000000000000000000..267b2030a75b2e277b50ea9d95917ac4aaf5d904 GIT binary patch literal 14411 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 XSoEa>c=V3tVB>!z=rGkc=KQ|_Xr8Uq literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/text/border-radius.yaml b/third_party/webrender/wrench/reftests/text/border-radius.yaml new file mode 100644 index 00000000000..cda868891d0 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/border-radius.yaml @@ -0,0 +1,17 @@ +--- +root: + items: + - type: clip + bounds: [20, 20, 100, 100] + complex: + - rect: [20, 20, 100, 100] + radius: 32 + items: + - type: rect + bounds: [20, 20, 100, 100] + color: blue + - text: "A" + origin: 30 120 + size: 200 + color: red + font: "FreeSans.ttf" diff --git a/third_party/webrender/wrench/reftests/text/clipped-transform.png b/third_party/webrender/wrench/reftests/text/clipped-transform.png new file mode 100644 index 0000000000000000000000000000000000000000..2afb04b8bbdf8464ec8a499f7a9359b82d1e30c5 GIT binary patch literal 1792 zcmai#XE+;*0)}lujdHCbl*CAh*;*|^s8FuhBdUiw8r2dpVw6&)Xr0=`rqr&)I63yF zG^yHqRBEf*BgOUS{ysmx?|Yv2`ToDL7UuUk*>AAZ(a~`tjNz773%iOD8_QKE@1360 z(XsR);4o`k_I75Fwe>20d${)_y+zqE9=wz*265XS)3wL|f2WM;p}w4Un5 zxW~`H)#IN{3pB1chef9ZY1j~m4bKEimUzO;WEPo^;86oa{ws`P)z@EsXF|zo>rb6A z@`L8!Ci?`%6$7)W-rF7Opn^2)XFr0*71hW3`dI z6X0{-DP&!3lorI-%D(l4IRIEzxD;kqJ%_v6?@<)y1#gE$j831bo09FPx+;BlMDURsAxY^axuo-?4=HH{l+ z@?Ck*If)8=<={_x8AX?RL=?EOzw!;cB~5X{o2_?NZgF^JtwAE5l|nkK&zvGzpv6PJ z@gHoo%|qXPCA#6dTVXzeW*;95o%JOyNYF#yPWj=F!_YL81u`;oV^pb;JW$e`gxq~J zoTl0=d|tC=kzNv)E_7ZRac&C;j;_+C#O(ex9a?>LPb>m30vxd@9l%+^-Ajy4{MoeQ z^n{zdBPG9&@3gz|J;%+@?#duoDOn(ext~Wt-lA-wn#m)vBhY6T&Mt7ls z{H8}pUzu!&F|tzv=&~Ohn$#^;l4M(-CGMZ1XZR)ka-gnN^)t!U=A{)kV~|V|0F&5m zWSt`MN_pEMw1`*(Jjp(Hu(Z%0=COI0==dZbcjyycb=RcDoilq4-Cv}p$rN(K+z_Br zKj^->A*kq-4ld5Ui2#eD_A*k+xq6AZN>F18$ArRZF>ay&Bu4fiw7s}h3Zb{s+%yQ+ z`+fg#SkN~wSY*;J=|Sm@E<(xs4}>B_e;b;tFtr;yPbMfNuY0WmE3290c7_vteizLz zmj6h!nH5e;`AUmt-14-0>oLWD58r>Fs@7m#v7b9IE|m#C-2f4}Q^9PU;7S0Hve#8# z2p8}XX^;GS&t83?7aRUUQ`_Ue(g8GprviYv=W`4i&5FqTgXZ)-r&JMEdapTY2w9=& z`oce@>BvHMQ01aG4mtk}GL3R>ZxsGY*lIC(OD_B18pzV!(!=^RuYE+iB5;`p;M=`a zYnKc5qX3}}Kn~dYP}ZA6ZA7M+ae4@zWaeNbiCvYc-3i?fB@~i|w0&7=4N=Qa7v#NO zzXGwXWQhic@h)%}q?OIzgJ znJ!X6Hds9OVSV!G>zlZ_+W6@NH?X}jMtO%hOdzW}SC!ng*NXJR9c6WtlSMMrhn?;i z_6>3I;*}R5Qct@eyy|~i90V<2rWdU3Zs;Zob?FPhxt5zCe_vh>Kj&@DzmPQCd7N0g zW55-TGVkf}^vv=j1qF|ced4B%=n_6WU@~IG%^W}f#xm{F$@u19b?|?2bcWOIc`2dn TWjDgE%tnVWG>2E~KZ^VxZP92t literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/text/clipped-transform.yaml b/third_party/webrender/wrench/reftests/text/clipped-transform.yaml new file mode 100644 index 00000000000..7f9c9b86617 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/clipped-transform.yaml @@ -0,0 +1,12 @@ +--- # checks that local clip rects don't inappropriately shear transformed glyphs +root: + items: + - type: stacking-context + bounds: [0, 0, 65, 70] + transform: [0.7086478, 0.7055624, 0, 0, -0.7055624, 0.7086478, 0, 0, 0, 0, 1, 0, 40, 10, 0, 1] + items: + - text: "O" + clip-rect: [0, 0, 44, 44] + origin: 0 38 + size: 30 + font: "FreeSans.ttf" diff --git a/third_party/webrender/wrench/reftests/text/color-bitmap-shadow-ref.yaml b/third_party/webrender/wrench/reftests/text/color-bitmap-shadow-ref.yaml new file mode 100644 index 00000000000..039f3c6edfe --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/color-bitmap-shadow-ref.yaml @@ -0,0 +1,30 @@ +--- +root: + items: + - + text: "\u263A" + origin: [8, 56] + size: 36 + color: white + family: "Apple Color Emoji" + embedded-bitmaps: true + - + type: rect + bounds: [56, 56, 48, 48] + color: yellow + - + type: rect + bounds: [56, 56, 48, 12] + color: black + - + type: rect + bounds: [56, 92, 48, 12] + color: black + - + type: rect + bounds: [56, 56, 12, 48] + color: black + - + type: rect + bounds: [92, 56, 12, 48] + color: black diff --git a/third_party/webrender/wrench/reftests/text/color-bitmap-shadow.yaml b/third_party/webrender/wrench/reftests/text/color-bitmap-shadow.yaml new file mode 100644 index 00000000000..4ead9270cd4 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/color-bitmap-shadow.yaml @@ -0,0 +1,34 @@ +--- # checks that color emoji fast shadows use the shadow color +root: + items: + - + type: "shadow" + bounds: [0, 0, 115, 115] + offset: [48, 48] + blur-radius: 0 + color: yellow + - + text: "\u263A" + origin: [8, 56] + size: 36 + color: blue + family: "Apple Color Emoji" + embedded-bitmaps: true + - + type: "pop-all-shadows" + - + type: rect + bounds: [56, 56, 48, 12] + color: black + - + type: rect + bounds: [56, 92, 48, 12] + color: black + - + type: rect + bounds: [56, 56, 12, 48] + color: black + - + type: rect + bounds: [92, 56, 12, 48] + color: black diff --git a/third_party/webrender/wrench/reftests/text/colors-alpha.png b/third_party/webrender/wrench/reftests/text/colors-alpha.png new file mode 100644 index 0000000000000000000000000000000000000000..e82f79ddce2d40069269cec9edce78a4cb786355 GIT binary patch literal 93670 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ⅅDAW^aaX1p4Vw6|}pH-^$qrm=r$;9Ko z9@JLphAV#e%(a5P}HYzDelAimych_xovPfU64j=#_E?gWHG z62C|Nu2|$_tV63+a#W*GrmF)e7nbeyZXBV2Q#2afS&C-{iV*q7oh);~4_7Lmo-IzQFg#iedU@!`&o_*D>_oY8hzl5~sCcFQl$Jn6~C@1T4NNIfX> zZ0b$CN8az(JbIt_A)NrzNiJzk=1W2UuX zQ-pUaJ|LwXId=(JET8(IkjYl*%DdcNvqUzf3y~D^%<~=ghT+sgetx5Mke$bhj zF>NoXfoSOSqZ2H(L>dn@Wx2n?Miu3@d`CSU^riPLuS_SeEYh$m>_n}tnK}YZl?z?n ztgis!sM6s0F(6wG@{GB!U%@)N5iMrZwK=zI8#Lr&8?X~*9DuyLm3>&U{6Rr@QI zf50d5U_KLC#=GR>Nnfiq=dhV5{KysswF?=nTOvj1@ZP(3B9AA96!?{P$B$Z@!zIvb1-7VJN|A|#aL_~zi-ejiOJy$H8 zLZF2es;sm$>gvidv4Ftn4KD6>LPIz>#sxY7aE?+P!^*>h4-^Mu(4uHKrSBufQ14{w z)YR16H}d<|fMYGpI^Ns6R6`u?8ms+X!}z+oy6@h)5@F*$Vs z@h6F^j5VM#nO)Y^x4&BrE#5))_ZnqRosr3mDgwaE{oA+N3-b-gb|$7)l=QPTbdCUa zS=KFp7U9p2Ldufga0A#L@@ihu*bB>R3$a_Loeokm(bOW>a1C`$@jInUyYTpLv7|xr zh`c}Km_xMdN_%sZzi>#3({?GjKI_Jh8&&mb_ob|mXo%+vLEBDssryl=&o~~D9;68x zwDnlQ-wkr-ibozzV^h6<|B~!e@1+1<+8)Vt`t#d@W4$Mm`s2`J3JXXXfGdwmO*P|j z1c@J5LD*sitpE`_md6b&9*0vlSVuZ#Dt}5!^8ft{d3bPd!N)>E%8;=H2iMqa1c3KM zixBJExB2|w47eEtIJgTJ%JvM4Th(ew&en&q8OMEOR!T#dN8xt!wn`a`j|bddo!KeX zbBpYrIH41Zau_ddHz?d^x<_d0t~1Yk<>@|e#!@7+YA#TaVnRnhVJ{FXy12$Mo{=dqy^8axSV+fkaA6t7| z#tM~vnOTB5aB|E+Yf@14J4Qv8lZ4WGC6+sMbOAG7G+-C1F(6-=obf>;eHP_SFLv6C znO4N;!-via0&F9U!0oR4?!9@jvu5SJ`DWUvDcl!V#BWU{B@xkp?dAQ(M&3+9tcN^n;Mfbnw zHNSrU)nX{VhKBQP+(SwG^Wj~|9nAKR0h)gWS(VxI|C#?S)&ZD5ZbvA^(W79A590A7 zrK*o3@ah<{+p4*W;J3z=rCol;k!8Wk(h=h#nmD+A-^+z=W<6|}H!zL#4xGO>W zOW@3qh?$vLNf;%bfG00FnO|N1=J4=vV|$xWmYfIRiSUM5K!P_o;MxTNc1JdC6uvqF zbcu1)tH2gU#N!y|>kFgaKT(YM`t^kiQ2^&Mz6U8Kd;ot`3n#+NeT?tC!mtvqSwx-s zsMN?)&iZ5x$CyCn^(!hyLV?&K7X)qv2K|UQRHwaLG0C*mfR%dTc+Sy0OVnjdIg2f@Lc`PO*|#o>avGrPYZ z`*jhjRkmG5lY2~1wxPoZcsjHrPJda*sC{=Edq0Mgvk+T}5L=q0Dmee{9FS={~TnO$}_$27oI~ z5w_pl+zdnwxw&zc$>wTeY_8F0bp9IqkDZNe5(~E|Cx-$+Ibz?w!=E7`z=f6I1B)=v zf@cA#pE{{(Xn*b31uV^;qTPd&?H)*vd;V|c=?~q%Mjb{=#R#Y}ZK_gk}-csh%Wn#$IA4R z)~|=m+qk+}_hw#GH;|v{ZA-3p^lrgjYY%)tD>Kgo3$GpDTu|DBnySn(JI%$eoK$+o zCcd5hp+#?Hw%Es6*m}pni3(nQMiv?f|G5YT3zQ2v3-LY|(Zs8U;e88|r9y@s&9tz> z_xs9Idp_jw-TSFK6|CVz+~brpv493*&^nM_a0i{ImYMv{M5b2r!DCD<_Tvu`Nl7GN zfD2T9d%<7~i$E}PuJ!$UH~6Ox>;lu!(9o|*`O`Kw4A$1yfz8S$De0c_eJST(lgk2R zG2hio507|)8cs?Kw7KN1u(Y-4{l4fkURmt}jn!G#D;;3b2G>!*FjL1h;qePqTf%X|B;{UnZhba**XCyL+5oF(nAQhHx za)f53FgCC3fPnw!@Z(qA`Q_FDi-^Cy_b~h%G*=Xln?zgW?N3zRVl{AYf`E#xz z1FcZ!%D=?BU+V(I9^hd6z7j}RJjiGc$&NF`S7|fw!uehJjBNKU;?<^MKPPLkw9+1l z#d@wDG>RePZGh~4eR$lwRIy}7cly})JS-Yys&Cp)z`!Dnn>&VkQnnn>eASum{AAz( zt)}gWQtoPPB|<7;G>yAPuK9l~*{n8=R78`m0aBPx!T&6cPDBhj+vQ(Q_f~6Yy`>P^RX%a*hTDY zZC`(!Ka~od?mee4gd}d@haMi4Y37h4XUh=n->(*SOj0nlIm7G^_Z3U=qA*udybE^44;b(2x_o6;kp! zI5qpiUCl;BTP1!bd!J)ZaYMhiW?GHBANN#!!fTMt#`k(PP#2{Kky$RWHEymfW*6sv zz~D^vB7cS%#eL@I1oy@X|C{MYh&Rr+3af=1qk&iUO~K;8nK_4k0h#~s{&(;HZKYvH zdW2UB3KK}jQ@?*Zu0>oFJ^}N`54l@jUdAFb8=@*JEPPEU2pYOp+{4&>QJxu_8c%-{ z1yyMQ6gAN6LYI;+6cIa!E4lsEbq9;Sw~pdhFn(lkbtYjZk>pT=V^pxacLJ_f z$9#9V_9E7dwF_Cy#cX6tp1n1lJ;Gnc=Lr52RQfjKGw5ycZoNhwzXJX@(i`%6@+R2p zKS**E3nvi|D1PrB7TH%)jU7&K7ut${BeBIw@_(HIfRwGz*bRX5xw$`Wrc=|@T!+Pt zPk=u=Jtp6IZZLXf(Qt9C$Di$YMgjb0*^VIS+;u$pHudwdV>FDZ@EYq?ree#TC2gjJ z|03OLDjR50u@47hMa0DXE_MX}UDs^()p(n)Qf*v)k4vN{@6$*ZmD3#xG$X@i(WV3< zrmFc$)$mZhtfg#=S#*?YiZb0)dwB*8I4$!Mwxt_0qO%mU`!Dap=A(QdqV?J?d!@%! zSdI1q)pk{+p8t8o#Zx(9{e7d`sX62KW0Oie=jmTa$%+74UQZQv9@S55nCwZ<#vpr~ zBbAed#L;{6de?w5%Y8Sv**l8SLRHjvebBRy6rl==QZnjGagupI8O#$0=aJ)CJ0$$>oF zy2LhWK%qipWIcrZTM{%klGmk=Bt9}?;bnNz<)BrElTQWyG0f++M@B&rsmt)}taJMF zj2e8i|6HiCpy1VN=BI=Nm=?puVwfL+D@czRDeIL{eLgs<)~DH9P_M48t=QPY(Q@!t zaJg;x=Jc&6O*{L%C8nM{>jq>%N3B~rd-x6-uQE$GmuE+prs@>BL*$%{dpN?@C;TD# zTn(G!RR^F%bu46T_AkrT%HP;0X=y3fPWSxFL2)iIC27~!-(+EB{+o4apU=xKN-Lhc zbM3zWYHpF@zVrPUy>sEm+5wvnQIaou@9VE0n3~%A_EgUOlk5|eKMZfYx54bMU*Y8~ zKUa`EeE36SYmxf;ne$iC*b#Imwj;xD$L9!+gd=aeL|_keY+iH9CWrxoV-4z_3J8^W zU)-dtNVd(^e&Cr>94Wvym5g@ZeS8~I_TfN>21Yu=6(B)BvH2(8?H1jn=j3l~pagC7 zZD8@JS#r0o<9n=6$!=9>9*U0dTaY3k_~H=oQ^&rU1(RMcn%npgZA% z9+~9J>VvMc-gp`&CZ=9f1n*94!BjOuIsnrr6ayZGte;=YpmYepet;&@#Eku|huuM} zSD;$;$TS5i{6g4!5`f_6=E6R@TegumroqeQ-i;c}3WZgfT zTao(Tf3GM>6K{C^Rq#MbD+mEGjn53jU!uO(-ND<4_`&aN-za`Nife@G`5ebg>E`JQa4@`0iC27C&jFFqV>v>N1uB!Sp+VHRn)D=CUC(@Z05pIcY-Ef#xG(kjzxNm&M0AluTNh^@EcLQU7p#! z+?v5T`H^S8p0;C_xp>WF^!b!K_)Cm`!5%w+oEQ#Q-0*FPeA!g?u~50iPW$tFzy{_O zZ0?6yBISf3EX?1?yRpI6So?yOX?%m-+OEHj_uR0r!&M8?TFHbkO}8+3P0uHtfpCv5 z!l=)5m>FFHyH{%bMTP-&c%$;7>?d$J#^{Xc#TxL(8qPdqAW9yehE)KZ23FVJ41$r^CR zoll1a6VuWP>stW@8OZC6l^IJs(#1qX(1K+Ma8Us92Y9_)$-5p2DrDpM%YwYUyhMzB z&yq`;&3kc9PEJV0z4%pCRY3~nv6T}Pg}~a1DcLfFLhE{%ML`oN2=xE%PLEb2IKF=U zItlb(EiNj7g1h~6sbyCL{$qKEi0B2#&O8!fy(BR=H&5g3{*t`e)+Tu`;cZP#{WAIN z;k`$`DCYJaIvr+0vLqTFA`%+mbGnc>8h9^Ion(-mjJln!gim*bR)@B*;&bB$16EF0 znV&K<(@LQg&{m?NA$=r8OL$VrWYOFz!I^QOzwo-D@rP)n3*_!MqNbtS?zaXP=TY%F zkW~X%s88pkWp#CRhM#~VP}k7#*o_R@ke=UAhlPcm*C`!OC0q?U@&6=`?NTX|>_Wv( z64>}%@T@(%PDL}QYMtZ>?t_pDlpGa9dCs>vJsOAWO$*J3mVp$k_!G=_W5%U;>sI~X z4XxFs8<~{Rm9h1zBbSSggf{r2i=&|MDQtn*v&ReVRU%aDjiD+eTA4bBII9xZ_Qdw? zqQ;so`_J4_w+xGhR2QgjZEoY1$OfNWvsxstncrLmGZ(H}&iT%ZA?G^CxHF=l;sj(h z8sVFx!l-i-89Z%lY&(8^bxD1=!c66NL|Q?(?$pffO#bX_VI*Wa<;pW4h;+fgO8BQ# zvk`WMegO;Qc#xck9X-@>;w1LDM_|NJ>>A5P+cFea`K1z^#g)~5-!Tzn%E!u&qux-j z-V}_5Ve6JJ`h=A4iO@w;vO^$?(nsdgy-gASyKkS*pM~8dP%p8825Jul0D}fpSGR;sZ6s?&GEKV;CF;76K zrlzj`M4_kV1xTClfCyu6wyKvs{u@Yx{5YbEl??{DSJ0@Sq&f?NE>#s3S`E1d&|sHe zPYrMbsbZeIz>c1te|an^gI?{{8(gt$MO=U_mjIA*(~E-zlbD?PdNPpqI^?$)nwz8G zvl>$Q=3NKx@9(de{u~?N$@eqAgT1u2vGF++U4A`FOxG{B;=n{Mlk|Z9jn%C%Tnt*A zR6uy`*xd45lgt9SGN+}Aq|m+K8TCsq5!bHEdvqPXo7cC8nLq28`|0baRNiV{CTOh& zC7U0%&DR}A4Lq0dd&-Ncgf@KF(wY!@DB6EU`h*wFKhsA7yg4R~g}!=W-H-8}6j0Xz zWnAWoBhEiuM2{=VrEy7y!d^zkg}x?3BI4~Pt~>ON{~>J?WR+)DTtYGO47XR}yLeDO z@1m`;*?X{l&gX+?GUqDTRsHBLch?RMM>hf}qn<`gI>B9`m%x#@wXDrp;}KeCL}K^| z2vhr*wNTBQuV9h55GM?rLFt#J(&L-9rhU=hkeP2v!&a-mFwGYms zbsim`C3ro8DP(K-O{+iI3ii}6V##8|sE;hJGu53Z^Z;ThbVn$F7bR3ak5@lqcyv^7 zS8`9$z|e4ccQ?AOP6$*4sX*J0TLF(LEiElv?SebS$Hc_9JH0t-g?RY*fdJph`c4tf z2N?qR=zb7X9~>M2;wvd|sLjvM51n9AO1*f21eyeFHF2at0UFTVmFR36tAif1$CXpm zBypE)Z^rQB9Ya&5LxE6#~*0*W8 znw$!k_1?>cNuf{;W#~? zXO%x|`c;O2>w(k^)MG@w@`oZzXq4_1NgavS_ZzO9Ct~y4`i?SmG?EN;IqR5^=*#lVRQ~|OYw1ji_ zz?PTcjg5@~ZU7Gv1q-hu4Zx5cSGv%_X8KxMiX)PNaey|x ziCtK8EEXb}8+`~V{^*l5t(}wnldi3kLbZi1mD^E>5r-7sqvV&#J6rp1Ii{BG+WK%6 zHEnq14AHN5(#K`n@vNM4XP+4NScJl2n&|6vG=qxHG##fK=nCR-KVEm6>HTKL!cgJN zX`v@2#W4~IZboKutmLQBrud<{5mC14d~S+hF6z!b^~WxwcycnP`Ch~v9)2X{r)W(qMXbb4p27l`^81-$Bpj%C zANy{w_l!7BeOBIdqhcn6*&=jLVo!qoQ@6+)t4@1_^fvc$s^VnbeB2+p0^S+TBM3SH z6PEjN)3R2-QB!8}-lgOVHZ=rEXQBmdkJ>*yrfn>_&(OwDB30cg=?Z6Am(}|i8ifsi`)<$zb z6A|0@f^j?n+JaxM*`Z2~0t>4OvHyv;vbS-s<1sq@*Nm3tcQf z21jEa?NtR7v6VaLtsa1`FPRh+EN=g@2J*gPWKQFa-Qkv&wy~UBWA&h*0IpV~cdb-2 z4UG^pu6=#kk*1FoN(x)c#WY!VpNrqOF%Pw2MPl|4Lz*B?Mw&1=K?dWCff* zL=Vz6_AJyb_N^(J6<6u3~Qv zAi<4F;5z*IeSD+G0b51HqQ)BfjI5Z&c~2QHF~YQ+mNqcBv5UDkZ~IPhZx*rXn)PVE zR#H9j$}KXo?PR^p_kzrF(k>92A^@L0;0xutB?B6TPRL6YFo9!|!N@uRf-oQ7BeBe0 zM%QB8>ah+~2x&}vYN&2%+;+$4u@H5ymcvU%-X|%;BrP+M8CE@A)O+nk&?ieP`uyz% zG|!&m4^~iKEVsDp{v0zyd~SjyON$C7o2^1rD1kQO!6+$ia%6y@*Z)x5>7fmNvnxiy zg_#=KR{Edn_1E2q&E1BTVxZdeJ|CG$5O0#Fx_#{QWTf>2k@Jx!{-U7E0 zwS?N7H+{%1wE*|LvKhy>W#Q^8CB~ewp_DKYl9VeKI=T;^icVi=2suS;Z%+*T)2Cm! z(n~0X;O0H^p$_BEXoYlUZ-%l)#h;?+>DQ-N50mfq&NQdTo6Fm=a(hp@|-@QAJx5n1vxcJl6gYe-4U{o+awB{%+ohKwF z?y=nC{B4)XPD3&HFlp1EOw9B(XkbW%ThUP>xB%Bn{b&9v-kB6_(gf-$H|{SmDK0|c z+9s_K9b{FSp$_)C_KL^54Z@Gn>3T}=0Znpr47JqpCDK#8Fm9V)zSol20$`(o@6e>i zvaNRzAI;1bL`^+*xCah4!(-G#1dHu0H5P0kp~1zX*f6-->*hDWgvI_&TDiaJzH7Z3 zc0T#}lTw27`I?K{I-Qj~pAFv8$>6P@n_CmUi4smiN-qVXmge!=Sugb}!`qCPCbUQ4 z5{_kRGKl`fp`o{YV$Mj=1L4yFojA$-Rfcen{gtJgj|BHV^75Be{e7^-rUQOgN<~(+ zx%620HXh1OTSwn=WPL{}Szo+(D3;9O80zGk%E$OtqR5E;>8gS%Q%%tbfpP1FD zDc+537XSIziKP0KU3J~vEEkFb)I2l4K_^gY)pXV#_Ja;WvwcuGOeo^>sbQ0|2_^RH}3qi1TZ!_)|a$XlWCstyY zDKhQ^zxY%T;nU8Z`jaOYQegRWDA99(+Jhjy>r-meiq03aQH@Uiy(i}w_NG(2!Dd=R z<2s+scms5m29(Tgi&Nc7aOHJb(nmMgSmU?ZLi5V`hD3ZFqcY57W>v!b&8~Mab_Gx# z{UioPHW4cG=~A*@2is*rs|gokPNGA7l{Pj?VLODp5Gj1(9v$^Wrx=A4QuU(b56ahK z&)+(abg7NhxR>}YReWdc>4{ZW!YP#Ec}s3SJUlfxG1W73cft1HGd@mgZ&SWMtDzy( zTTo8$IM8?V=~Pvk>T+V@p`cI~V%}o_&BI8IDx`cKsMe8Np{xG;2v3+riXv^ zZPc7_Zmrxp=eIpal`;)~1>^I$Sarq6v0~qt4-a|yj`jSRx!d#W&Qc~!kMnr*I?GcuPcssfv0=hjc z1EoL)>y_ps-7T|3iAXG})M4 z;IxrTjKSh!(_z?Ptd^p%a8u<>S7h7PuQn>2N#~NyD24@~TWm_kd)SQPnn1{5Jhth=`^`WO# z6*U(!CLKKuBlU%0zjtUushhVM{uE4E+$FEr^&+OnW1g-qqR)gATjjYIGXkP#fr?Tb^zIYKfF~1NPtALhUS(yETh^an-uB|CE~Xgm z+}>{4^h8B<*>HPOFXd0-EHT`ctUj` z5{zLL9n#0Q;WX}ho7N2S{~3LcGkVi^nk)$jOJP6@UUheNd4B06{Z zKG{CACnUl#eGF5ROIj zOUGU%=5+@Vs`oY4w4!q&t2_Spa<^nmiYl_y3Mkzh!-nBw42++{pRs&Uro%R#Yi+fc zVPJUKWVHn8^$XK_>hX1DxG9u7o?NT*x*S40nlbvqS7ksx4b#P`4@urmg{b1GdrhJ? z=b+{KP$dK!-Q4Y}m|x(NpH_eyR<(@di6h_Ss@O}Orb=T`!gq`4gAm}ty zyxO2RZf=vLMp?lM3ifpS8dp=m+_toI+aS0I=XW9MIhn$~yv}f-t;r(V@-ZarWZ)%A z%k6l>SQ(#nm#>_r&yO-xYTqOfGOo_>P}f)u1mh00K6K(n->_-H4G(7+pRUHYwfOej zB@y+eT)H8gax0^q&V;;?u8U?_f>A6tOCp)Ee#ylJMpP=J=_?+xf9h_q;1P;n_)`Cm z77KK)i0f<&ac!EAr$i#8XYDDM<3GUAz-!25g-qsB$FHbzyO!d{Et49B72r~b0Pm3d zg>N|m+gQxVS$q5?{*B3RRF+px(8#3`+FjwM`pG_j-9LM?GPK z`bX5eBvVay%FLG)s=E6y)H@?bMDn~Ff9)wJ#KvXPi2^&Z+#T`V7pNd|wEH0@jEE3` zP8f58imR7}TV)=8{9q>_Xy+~LA@v(|e8cYh?_6qQU^2yy`0<|iMO21Gk_J>e@afGEr>5{{H+Hg+DdEUn2Hmx+d9V4f=7mNl_?@16L#`HFlbxIxWION)*FvV!s+Up zse~F50tbA?kEn2ns0itTr{7(aB}!t%)Z)X#Q60~I@S3x|F{fANu6dFdTOL=QR|S2E zT<*fe?frbo!);w$b?;4-vJ&w{twY8ApA=&hBj3q7u6@t(L9~|z+-VI#Y?nA0yTRBq z0m#5Ds!zrAsLO?qn?&se&cWDE$e2_(UI;Bby2_GHEmcI|Ow2G8xeR8@oQwO78W$G| zWA*wDv3Wu7Y}yD>`GdBLrY{n$osA$`L0Hv(7RYA6gVnjG6r~K_Xib}!?-8$2PZ9SH zqU|W5^}o!)$ohzAY~njOo!Zh}mDIO$&}0f_<(9+kem2C{jkavV>F*H^TkP+y0t4Il zM&UrlD&Ov*4b+`l2KRXJB|&vTV6T{#+?b%``tlkKm+X{bm8tT zP6y0@k%GsZz+(*9l=#IHIPw4Jr9LovFUi5UaW&GfcWVUtl9zZ%a1iS=GQC5U|0 zl~G6N0;BYGBn7c^*0D>Y6g(VHM8#Zo9r{;Dg1gpX%=2n=Y??;GN7vDj6A+XgjDGG` zh;R?(pSl|nrFyS2Bd<%wCGk0>Hx?h!{|Xa#mz6D1@IIMM(zV)2g89`VbmT?e3iX!{ z^jED;PLdAnT*$@0gtR~P$et2(8#>)L)p6x}yPIU!Jq&W4$4?H{Q-A{{RF^cm^ehkz zp)|kk7~G*Xpc0m>6DhW~{03}6<0BHEn0Qqb&ubV)DJ?nh6(2)e4HY_gbkfKVuOtyx z@WOSv%-~QY5jS^&W%)lRGap2qf*t$iU^WmKL9VTg>i{f_Alfgz33N?|0W3~V=dsC| zBfH1iS_3Hxwu@fTo1UcCvQX$zLW%!_{$kzLm6+d>q}kJQ@7g9Fc79?ZJ8GC+XCZAp87|D1-L(ciJrkfh9^R}g&xv4B2;C>mI|*v z@?EVFR^TtpRGeT(b%qBrD8$_4%Jx%?wngBZz0%Tra#ZHuPvfznc<^#(qW9z`2A+fq zVRlwhi@40>n_3#vGFz-THGW~ZqSAfb-+Orh$e!h^WTV~+l*`Y=9-_ZmdDXmq-o1s$!l#xz4+iauc=H2HJ}kJ$Or;KlRG525UPosfcT zUWE>Aa&NJxYPe}%ZmG8KlI?u99Qv|p(K>_+G&UyFS}r?k#uAf=AXftAHmain%-`0P zZ_W}WHZsLm?yGH^917-BuX)V7?h={SV^NQkM$-kOClmDaJrPdM_nRnp)A%;lgWk93 z-ZwXPP#*^IcNKo^xW7?ow(}BK(Xe=cHsd|fumZ!hkvtF0{6>pR4-V_-@`7G+qVbdb zJ8cNYn0G;+e;IQVE1#C)ZG z8huVZ4m^BQ{evKWe&slL+L9pw6F4ruTjMTpe{iH{QUX7m1P^L#d3`Y7wWokX5r_$mO zNBdNIo{K}%YW`x0_b6Y_ES$4oF{uCo!Iq=wmwccgODo(;H$+Fp_)RyB4InjFA? z)biblL3?46(r*uGP^S5p244MTH_QYW0%svg=DAxraL}LmnSC6!5jVQJ12K*6B{O61 zwp5HhPZ?$UYQ=1GaiN|c{PY)t+nP(cK<7Cc7W6T&chp*EooHb+#3x2k%ygI^?f*b> z@^?acrG=wekWqP~=d-Cx2-8rzO^!hNWpVKSbvAx%P&fi?GSlm6E%4KIfxlkdt(TLt zpn}z{UJhPEv~_6ey>pT|*V=Y)%NC{wIQs-IQP#_qXoQNKlWmO?OVMOV89NJbofGKU z*0z}B>QY7+_sVf@A9fjy({yPpb|GKt~j@B6)IQYALRhD>iAs4qM4(~<4XN^$1 zz(_sUVwhGjI1u1}uP%0ed%A%VBM?J{$!^F>?S1;@DpBM8d#9yO{k2tMN>x_J8?IAF z`4VXtN*`oQbGDX&SML=cSzR7ja>wrl2aYxxFzuw>?%GV(L|-A`FpiKK9Y^KkA*=;n z$y`nhgjt5{9$l42EPitsi`1Spxqmvhrf}K1ZfWUW-dc1pQ9{w!5;P zhiS^JT`m7?W20lZD0T1Z42Z@oXc2p}zd&?CHKm7|iSC~4r9KE%rQy#dvj^L-2^-8!u*V+?LH8FT z<2?9;a~HVFc${A;&fx#0si|MSTi96d){zN@`Z`y>HGfs>v54O*>aDd14clMv=ys}o zYrc2rf9U0X=hoA59aT}udupv|IIQE-#CwwV4jnyaax!g+i$1sloe+fV9LAgb!!s_U zJX-4XM$;fRZ}J@I2RIy|`ygbhtfEO?kW4c`VZkI}r628yC2k#X7^Uu!mL3|_E+q9{ z!|x%d=sq>}dQIl(D$-L~*;N79j@j9VPOb_IV+tu1=#|zxwtmE$w8L! zqi*FJ^H;`)+^RHREUhhLt)b0>z;}*)YwlmWawg)P;oIF}W;tX)qfuo?Ji7Aa!|31S zHUb?gzG+l4_TV=rgsGnkIgB=kn`T}1gtazAzbS~2Pkfi>~Yq0K)8dkngd^R#j*eE{N*%i6LpdlBR zl){X8L{NRP#PuFaeP{OyqgHKMNzH0_#Rx@P`}9IyJ|N3~W7$$bpy}!FY$*``2SkS= z`1{||A8wTpv1xw}w|YC<(HV+?0Yz-|y9;nyg`VXfIr=Gi3gh9KG+90P9eA>^;Je5< zSgy*#&gZTk_;@dUvF~{7>^(mO(91#nBO}mYQ~JYfq8TFH-u`_Y6ZW(`_(G17x3@bx zb?g#%T{7JuEnlb}NXFN-)M(gv=_F^i{#OziEK&k?e1gc+1SVJSn()X}8!c(KXR-tG zNyq^n$D9Y@&ez%0XQ|Oez?$UnAoy6Z!I zcx88|S?gtAeoV#~wy?)F+9lsQ=_v3XyuZlR>$p@c<7(mujQK>H*0nf7LGwiWqbJY{f&8X%VN!D zQgU}{S=nlT|fG`@3|IlF1pYf1idhQ2n<_c^Qx?fAMFOOt06}j>UGz5R) z-h}{g!fl9FfCuqCC3y4zWa*V+K2wtdNo1u}el2r>@egs&p0SM6ma@gc(85efwaoI~ z+zDZgIzE#%6G)myePh$XKEzuzU>Qk}WJ30d4%-&klEuK)vv!h81VWx)KPW@uk9pJ* z%+v{G?Pnu1UP(pit}`iA=U~&U$i#H(=xTmX`d33Mr_u+dw9TAfM@kd6w(}|xF-Sf> zbvc@ZEu88xnP;83+^o>i z>1n-P%>J%`B{m!tH&m|c zRgE-0!UUP~_#=F0n3!+dSUu!l)?$th;_(D=C#QGs$@4le)Ye~*ZQJ(%kRoqGJl{E?}SwO~H9KNSm^spV3#dWJB?E)z}~V_mqfSm}+i7 z0rOmGy&n}b8)8&cz^zYjpCx8aj-(zrw-pv1753!~q6n3WCbEVGT)q-KDh?bOndF-{ z9tyi+YH9&Tj`t7OUO=$&AUS-CQczG3$Y`WejSdV*o15pVTaXGl(jp)tzHRuPmxm0f zBOYNQy1Hq>&#I^a2n7&`6Mgvb0d#YPV$wR>enyZ@b2DiOJsNwy+1OlIrU;psI0xxM_z&=yVu<}I8W;h=Tt zewWJ-q2?{3RtdsLhPo1`eJVFR^jz0uM?-QUvds3{_5S@EY`|mM7R`mQzOIB{q3?O? z^v}L>88Zfd&F2Gpw1BaBX7BRmur(fN>4?4zLnmZ30nmJRV`Ej-KtPCS1^~g#EG()j zm8FrfvE88iklUgUuRvt`g$SSpgKGnGRN?>N2T;aOevat1Z_9pH%7{vT*MJVEqO1EF zRO0hZ2h5YTq~U zn1pY!`QWmiAfrQC?#kkL&3`oADzce*f8W*8(f+8gd+2gY}r zcnWn!2y3pxgP`A0u`?Xof148{T3(j4`>}rUT@fBQG2?2R1J6)NNDeq0-H^3PUFUtx z%%EH2a4yQ+KZ*k94O+-YNnyK~SX2WK0u0P(mnRA>Uaf9Ls}>As_Q$8EGKw+lksW~3 zF=u*Lbs~D@?6;kO6)5DCXhmaFJQv!70F@5tN+c8P@bLY)5ujNa7#L7dRfUH;Iy#aB z9G_xhVj-shBn1SN;%b-t(zW&@sRKtw35|H@h?h>uDN#xmsfGoxl|BvlY1j!!Tg@XS zYXwJBP;S0;!#t6lC#+iDDHHTA`fjQXqGT26*eA0!gkX zDao`BbSAD?W9_$+d0OrM8vIcnS+1ojaGY<2L+s^5mA+`${~m+Zb%@W9Ksr&|m|9wo zT>dtC3b85^Ws7V^tyI=S>-6{;e~cQ*9+$FfWVWD9ja#`JX5GVYiP|zFsH4;l{unR7 zzZ>KK;^Tx^N+K$#5xv#ynl43$?V;EBNg0aGuJ>CUr8}8TEV7>Md=Gxn3zJmzQ||^U z($OmGlcUy$q9&;vHFz3dBrHlBv8mrkV%N;&R&O?^sYOx5j^N7QQ5{Wc2QZDw1f^4q znx>ghf0xHodP#opw-Z+atsV=o9WR)vXlQAz0DPZerUy`8wXm{!%F2oXq=lKq-rs&K z)LRKGWpX+7M+ z9EaFAiS*vbk$ZO1<;)tlT@Gns>n|stJ~qBoufeYgvs9PNKM*_&E3PI_~Oj8wBLfOl2|=gNZ1N^Hk{- zKOXtkM~@Z96^OavuS-O(v)(9?vnoJVT2b>Ui2i@zDzz0!zWGDxy*CP^LLNBK^(QyI z`ak98vjn2UC4>n|K6$@2GY{;0Wgem9vAQ!L4p<)ydJd#ZHu_I0DsGKqzu#CmF|sEA z2dV-@x1LcdE4eEr6EQHe6ER76HG6*i)(mJO=$QM20G|*`d+qonDaA~^x;oVVp)B;Zxx;o-pn=fS4oI7KB-O{#1<_x)1K*zTE%zWzX2Z7#ws zgXR}3!ug_b=g?L7{^tBY>b^NT`3o=0=4lLCzcJS#lZj_fE3i7P&zZZ&YgenY55f2x z;C}1%-g6f4+u1aW!=^w^J>!b1Bv^q~_%oomk_)TGa``Uae9l4|Jp8#Rr~ z=4ZwsWd1kl>uW$G-0zFvTJ#!2>hjKj%uv$E4Jm)bW;cL2%7mpV&lDA1ZMNDH9^K!J z*f)@n_T9wK`wC{YQ8?R>@eQ0#={K=t)BoCrjwXv9z*25t*OPa(e7?!;MGNK`aXh;85od{kB`yN(RtF#1^M~2%ggcP;;FlV zwAfblcW+1#RDrfyp?({<&}vU?Cm@=_r=VaH%K|(k zfGfZp^b`mIu`)eWUk6;=g4w|3Z*2 zUJ?Q7;pM#}?y*36dF^xloYHd2CvvB%=c~H;0t*v63;0PQNbu%n%Tv?6sT!jJ4IR81 zly}Gs-by)nixtCKnw)k>anZpKeau_tG9jB2Q!_E2ZwY|&)jlaxZ&GsZzbL=r8Z7-e z=Fb1Y`quv~N4c-4$3+BC6SWW!f;yb*gUKo)6Ze{M!ub0i-_rBhI?bU1e zMPHIFW`kyB8rVkq)%g%#6Z$uSZUiriaYw*_mkwt3i-;Qr=zrhu1>k+}P$|0M3zZ-J z8Gx@PVRdX~CIU1$_fcQu#zP3O8h`v?oi!&{DVnSU@`2oLJHM5*|12ylfR{A4nEihL z&rd7U_ws}^H1RhFjkrce8^@Ob!Vi2~xyNo0piA~>h=3}adjuumhpGemuz2%QCmBUW zY(OA31~5U=i;GhgX_wXf(2;yE^Eu`%x!=T}nV28yB+KbGilu**BSpg@vszDQ{)Z6KSRb7LyI~~3GN}-0!z{bt^ z(>r>0EKP3OM^4BhsYvoqhM>k7z#5EJOTwJyG z*ObXtfB0d$WgHZQY6yt+){;sjXK}9-Ry_r$Hd=am5tsZ&y`yeFJ32W@si~>`GhKf7 zSJumvwlfnzQoQ)x{FLG4^)>JYe)zUlR*thH4HyAa7Mdjiv|#WLdx3oe!NUFdoBr_% zARHvCr}z1dsVHEJ)2T2EJ)%rzgg~g{#m`e;noCC+mGzG2DOkFx9xBE$WkSEom`lrp zV<6LOhb1SWy$n7%n%wVo&uIEj9)r89CdWAby?%cU*YI^qZwwGXZRN&fy;ZlQ+QCTX z32HKv3CU_@y&=$}ST9@C2DY&B&U#MJ7AJxdL?$m0}P`;1MsMo%YgXFy(X9)gPnR+tv*Xav{FO@XS_I>+(e=rl%T+A!}>Ci0Z}4 z5oJ<3TJz3`^DmwFe(ZAdcX`wHXPbQ)OZwQmwz+hQB2K}UV}Pu(IQT$+d-L3p_9AA% zDf}0nTH&Rz^$6U}lbx4DG^6PlpEruvHV_V*5O4ny;#Ogx1MX2E!>zM2-5bsXP~z9S z=>A~-@IDt?T3e@=!~mVKoWeqM&`<#fLV@^#0_L@iD8lH>Ag!WqAYjNAQUsV5sbY9u z_Qb~#6RL6<)MfY8#XY5?gJ&UN0McsVh^RP^=PBSu0m~t`2~TV!gJml@>mqW zF3_bg9z}!!J2;2~4syXl9}8&pXz{&q1dZ{4xRVZa>wq0kD(q}=BMf%v<8lDxktZif zx@lPG=S}#K(bt9|?%Z0VzRvZLMG!4pkHgQL7~=aB*?PPWtGf_g9<#0?Wz} zbi@!wd(GKUE4I}qx+G&$zY%`Mu{mq|i~m(^FX-P-YgYfQhyC>+<5@@%(OZe0Rr@EB zGz-czWsNG|!Gm=$U<+L+7~iGJ%-__;oqg)Rz2>#oEYRUEOQT}$@J3oVC321#%4&jva%G|bWiZ= zp$MfUn%RuXX@^YVg~p( zugEJiABy!mv%c7=yz0#OHW>y-Jzn)TeXAdYOq*mxfr0aqUgx^h;qOg$wP3@7$&K+A z_i&;Z^+L(L)Pxrg*V?f9&7sl3ftAsnpVOB+jiyfT)?Q`%`@>dVTCVdBUl}xi9<*&J z_uj(8eeZSW23Zz_^8B`gHD5qEV~F=Jf#*Ryr*{o{c0jd1|IW-5k0cN@AhBHfbkt(0pOkp89B_ zA(t>j{ zMjak0YIRMPC_1dznLaNo8WHe=0Ob@_noaPzemkcbTX7;DWw_- z-t=I?ZxmbD>Q*VltFRzYYN7RiTBcTn=XUt4J+;n@ndFLQPf9;gRZzg7P;xR&fqB83 zJUHtMVg^^%eBIgEV-AIRVRwf6*NEe1spb7)CK!sH{Z-d60mbtOl!<(U`^@pZN2U;hOHAA4xpqe(^wnCEBZ(GB25^^SFvE26wil@wL%B%v=f7 z*U#sk?iBTyCN+9zmjowHs;IUf1dQ|b;xSCBZQY+y z5%$jvzr8yOKA!pdvK1$|V_|^`opIO(Fl0Cx=&|mj0Lx2^6$KQM04pQ)TlxF1ZfzX3 z^Fctl$nCO4HaD=bv9X)=B_Qy<1iIHmr%G^mCzNoOE>uZ9z#jwDj&@62tgIa=R4jKC zfTh_Ch}#3vcd45SpuW(Jje!V$?*z)_^UKyrSy}Nw#vOZ9>{RilOF~G96r47drz}9% zBgDA_G>tt<35xJ*$SWvRc%9ja-Tj^T=Y9AC>)vCCnuTPpW^8ZHS4zcI4n3Ic^bC?( zP9X}#-1SFt34)+CTJm4Nq4;%-PA*_D??zlilx&gN6FTarG9(Hw+2;ZWvVS7oJzC_z zNErpkn0jHqe8PNtvZ+)GD>~J7r|b+smgUOV4@g--2@Q-gZ-u4AUm#HVya1`dKF@u7 zEYFSS=1u`7G?$FBeIxlt3Q^KDiOwBAJsJAE4;yi?lXvqp`wTMz>69rF;}AcZg9M?Y z9!__Y=gPp|_c!wtrjhSk@Ru;LkiILq75kw4Z~Wn44-n9E%#~9*V^Db2>Fs@V3!jrS zU%-8|o-#$IZ9NikgP}D;R8Y2=(cL({*&F9X-3(8cLSGN@WD!#+sym*UDlK8Udg^9Fgh^>BPwHBmq2vrCVr zm9J}gr@}2Dk;1~T@5sRVJ$<^StN!0%kTT;c6S{et`^y7QXc>FzUNfyzEf=?jQ#?Nk z@wA8T3-COX*PEKuLO_5Dup$1#K$?rBs20_$&dq%|Pbw!mu*e2BTlb^d`$q)iT7Tm4 zxB?gYlOIWv)^-&#v(DA>>bkm-z^5(sP5cLgyraRPfy4~R?|yvavHypGG<>|Wv}Oe) zHgytpW{v%>x_6EQCI+-wlYcbNzke_LM_u}l!EjQ|cVZ2T$kBn;@QZI~{~v2_8B|xW zbphh;?ry=|T@xIV;O+r}ySuw4!7T(079hA=aDoMQclT-D`{t{unwcL{HUFr~J*T_R z?bE&XTC4ZY&J0*%W5Htl@Fq})o`V7B?SLE3NUAIU*<#c6(Gc2!mpOmgOZRm5Z>!R} z(eC#xObQ5*ll|Apw8hcI#41(o5=U&b8sR@be$!}EFIgqpT{;7x=?$eyUf#Od{t)hJ z^NC=)MK@eW&q3^OXGnMV+`LW>P9lCkk}BDs2QI>%yp(||=MdR*&7NHLf|l#4X`z2N zz#&n+qr8vjM8uv5$rTKrLtH~xvU-^qJ!C58Ywz01n8pgK_}&-@zAV-B-rE#W8jE-E8?(VdxqWntXy+?aNDVp zCi(`klw05+rtk`+UQdNT$a{BY#*P-Fx4;q7zgJ3i{UJDYH#GLE+q0!>3e^XjI(Mci zw_?6}EdMnu)ND4Ge*fDl_u?2jp1u8=Gq&>CtA{lsE+6=^&Z6V+Rc!-*MUb&b)p)5C zRutjRDttz-K7Buja1dc5SAN30>{Q^~d~zBH;qenKp54FMSxcC@cQP55G~_{hedyYh zVU#@$_p{Aj8x^YZ-Sv`ZvE!=)bHqC<3qS%O8N39tS|@&B2G^oXz1x1 zRQ{31R`hkSwLek~;NjqyHAqYtCXrYwXdCx^=|=rzxt>Ep{a3c#>(nPbk3E&1E%#UT zyw+CVN8}Yu=#ilzMy0{*_Y6;M7rp530aJac0AhoT@P>5vBR&yKrD@bInsEai;tb>c zg7L_hGg(1HhLp93J#^JLM{}GoA)0$chRB+rO)7L%QX2T#L%zWzkE54TkjLd#H(>d zoIpehGF-VeN*FT%@e7pwf(Cn?WBR+yoyFhIXQ1*^o~*^NiMe@^>fXDPA0gE6x!}k= z5m6M#6GFw!jnmoLxx3K7_W1Pld0#CLhJ}TNm%dJ+mBkn3s`5zu{&#Bn0(;?>h?D{y~Qf>*rI6yk93X`I-g@Jy{A2rZ#J8udfb{b zoh$-hj)bxM#7KK532mNv2916wgcmEfO6@WThHV(`)PPYuCtS8$usbww)s2mt|@# zj4MQqjY9&lApcj}PH#w>S$2@W6Uya;-fW30>nSmAXwuTT{D1-5&#+~h>o%1ahz~ib zD-!xg`JhqO5#frr{INE<_(Ama9rk88E~5IxTjfPcZ-JS&0=1qV*m)-!=fxr&9r0r^ zy*Kv|`4@8&yy!QLcI!U>KO1c2d3=dEBvA%SOQD{x*zPt!WyRLFq%6xEeF|L`mKO#% z57&+=Vcdv@pAOF=-inwxJ#QBK{qHB4AOUofDhKd+*K|4 z@X8k)db@{-EX@>=aS9mAsmI486@_|ZtDl6Bd|s}lfhW}De>0Sj&p(3RYwFpjubC`HuNl9Jw?QnQ3`@i8rxqsFf ziv8J{52~Y|`5!N@*E*tksSp_9(C%*8zp=h-b@E7Tssy0tp zdv*)!I^h@Qc{-s$e9Fs(g!cHl_Cg1q*_!=iPZ3D$bJF%usg%41it`xVoo^jIfPez{ z+2g0T#Iho(iB4W4MNsVJ$k>F8j11&i+phygBKCL=|V?-UcHGWyt~ zl^88sAOViOD*Z-&qG6JDYIFXTWKI11{0!9cYVVfi2+<~Ty{FjUG!>{y1K#fGO3B76 zn3xgzqOHCwHmV3NqQ2#pA78H_e&Vlu7JNxQ@G#PY+~jO*AUrnAl{XUy2?t!RN16EW{9o$ z2{d7sxUU0r#QU>6N6=MXYw2Uj2T6hXY-#ZTt>031lJ$Y){*P*NIUr?6c{ai;1T%m$ z(j|Pa@MGG#b4u0FFkaKp_m><2He8{>*GhI&L(`Z+&ZfdK37xAFE>MN+v*?2sD4GVh=*);29@K{OkiKR!l+g!`7RM)~ zLqg#8Z+*m6EBSazWUKhYw#2??UbEpE;lIP$x}#gOF{;;$EZ|{4q5KYNAZYCN*#f%TKoWij6l$*hcsE+<3Rh2%-`3U^!R4q8 zO1canc@B-?+bl0kdQ-Eqq6NdF+pUnSflTj=vHxt>ov&8!&_<8H)Te1IJI=$rOo-GD zX1PqaKPPdb77h6cN6#Geg%aMViiIdPw5KZ!#AJkgc~1LxAjxOpJ?`ZxUcm#`^T9%2 zT%p3X><_EK=$qIhv7!6O#?e{qWk*@)lDtB7je+-{+jg_+iXihf^%z>aV z@u8N{p8DBbhW$`Zp?r7u=dV6Oe>S*n@a&Z>QT^|eJzp)>cu{0=9H!+rU)~}U&;3&= zpl1iWciynY$B(H?(~*TmCZN0oLyGzlA_~}T_}(q57UMY0PXGOD1UxTY)qt$dAY`Ax zz{L&yFvaP#+RC@7tX?F5RIk{UwdF_z6u+*n^60T_kQ(UK(5=_(rgZ8cEi0>Y#6d`{ zMgr2+)|l;~`Gj5L583i+PA}+Zz13l?AL|g$OR-kzu$+UqU83=80~48%)6H24I?64I ztap5#dQGV7I?N$k%@=4q()=1VJ1K__ET7_nSd#56Ju`j6P($Rt_|MWqA!KmLPYTH_ zLn%)E(&_A8Bb=YLj(^udFfpa6o2zOSL&`s#o1h%TYg^?%*<_P`_dlL)_Z_7pUVFTF zOS4lFs3NUj@-e?>t?#&6cgPO=CfBq%mtx znm3#Q@~H4jk63+u{dzdLUxM!o0Aj{3poI2u6;WDXo>VxJ{x&k;36FHf5Q(et_GotZ z*MyttD?ZMr4{J0R4K6Q*>Kp{EmULq_D_&A)m>Wti$!vMW;~WW7_AHihTlWZaqe5=L zxjLl|yjY&J6fBgEy}ZoS*}h9N^BkbAHx*D`pF>n0prS+kS{RcC#gZ~PKBfN6URQo$ zSL2E@{=9VhXxnj0EbNRo<=R&j)YzQBzZ^YI)C_i))o~Gp7X%tYT%bRE>i~ ze{~di(Shxcv=qy3hY)C_M9rinl8`<(&4H221^87c(2rv;RZ}JFGR4R!LBx?H(=%}D zF@40+3|Nq2cZd!zPl;nn^#jTwv+H+$YkSot_xxUA{tWM4D(0YY_Vh_xx zPbctUkC1_P%+&QUsi?JHuDQ5pR$bH|xdJaQFC%13sDxVl+Q~s-04=Uk3@Z?gdu+t= zt+(xX0P`Au>2A0dbC^y=m_`{meg%K%B6tSXk1e3jil6Os{%#SJ}%>>kIzceM6CoS9T>u>-#F@eri0s&?6% zbcDx5f`NK1veu@w3!=5tgW*)GMH1nuNu zO>Uc&h)3m?Y#G7Xe2wT^rFiL-EK$4ghRo96bkKflKS4p^GspZbkeSW0JiA5yPuNIt^(lWFN)`(iw$hc9xRd8az87&*X9`;TQ zeG;ayAAw5o`OV9JMFMeu?K}JqJ0Pz>Y25aQtgJDGd=iI3>~D8Jw2HCAaBqHfngJ%H z+}5lms#<%)^>sGPBr1ls+g@>-Da?QW0$`|nZu`aNf=rGVJJ#6~CFiT>YG_X*@S8=d z5=6b-&TUMK@+cin(hZPMz7vBIQXQs75d40^2?iF`FoKdJS;)+~;A9W|{__L$&3aUE zwh5cZ%UQfQ&EDNkATmA_^x^phG}WPvA+&6hKN@Pa%=}Fo{@VjWf>qf=h`@*m^K{s4 z({g>{ACVqgsU0W%YX~@^hVJ@imc?9_0pVopeQ*sz7;5*s1>-jjUOeM-1XWwR6XK;> zcC1y7)Th74szXP-Phj)axne=l5e?B+mv%vj9dNEDcAdrrJb`qpi`ld z#-Q8oCn6{$6#Rn5%I|(m0+ORmPENABAM1holF&f4+dVuCSvZn)bK?aia7j2^s*8)^ z?VY%}`wy^E%!cCI7Ii11I?NW-e;Dw~6oRalRJ0hrD~9_mxBP&`P7q(1*sdRN z&bxljoagg6Wsb5(kx~8A{tI(|%Q;tU25L3|Hf9VykqVl#0#1NwM=}%l6w;t9nRe)W zMzedS1o>I0vBRa3*y%Ib?I$X;QI?J_Qa;nhHqv@!(nmY;$(k)y7S?EjrrlkR+#Mym zXE?&2HF&SJ~H=_0yD{b)uH*IG)Z-8=B-`9^xl<|_uzcybv3o?>qd(uCJ*5dJGE-V2Q*Dlas2u zaOrZFr(X{jd!#Je7<(1MbUcbf3D(X$UX6`5=jY8QZPEU8KN{=pmZCt$y^v!L$fBxLK5hv7#qiM>)&K|JT?CvbeX3`l1dpqjh){V84JTWyDtl#WL z>nr~V?m{~4+HdFYbRI2gzKs6Ucm!{EUN`)BHm={67?}*6r`gV2$>7-g2HWN7;;m!@ z%R&>Lz^}z&nQGU)@#kXb%wd-=2%p}e4cV_iyfg5@?td~M7rY_qTZP}@{}oNYY5E*5 zI!1KNN4R~l1k%QfF#DW-k9doYv2eng3FaaEfzJK%9n4I1L8{%uPxE<)i>$jNs${U+|p$YQ+?c9cUmAun(5Ghq5(jik@+04E|+ zl6Xt9va)XXRQ#j-pD%Jaiq_dtf?w_4KtaolMx>zl;0KVGw96I|a_xK!P!!PG@27u) zvONy$6ZOt&uW=jrCFMQ_I{;$haUH+??a=9Ouv~$^Ze^r*imQzX;N;>+I!CjFl3D4J z0^cTf?#DsXq#fgg7*n}TO)LDtUPzQ5$QEC#E|?AIL?6xDi)`h;7*}|Qk1d|-`>9*y zC9=|=ERqE`+}MbJK8#DqU|mlUB9Qd<`^uwGKGUq0$1s|n$1ybb^S+V?Ga3qsqU4Yq zLD;=p=E{@T2Ob0|xeUi^{GW8@x0!o~GMNv(WtdqxeIdr|E`^Ae?rq;(I51J)MXF+y zNKqh22lLvaA%xa&ikF}uRIIIai2ril2|yP`aLJNqpQ5qW_=#4?qwl6oQA&dW1J}*N z4Fl6(_7ke$fLA78Jhq>u0Y*J9rcZofNqTbZ6-;6LZruAWonKOc%ovoZvL9`zEKoU!mhGf8)MKGEk})4#K` zwR9H9Fi^JhHL#$ZhMa~59+&}5&CO>ZmUghq&|>aOqE)DJ*;C)#+LFjq?bvcM_V6G8 zC6W0-{^nxXtBuZ$GvMBj1e+SzRlwfzmIiC?$PEP*_0=*$?t6)OpIrTi1MNRure=T1 zLl}asrC=G6(NU%g*)s_k3!dE$n%xd5a3@IH79|*S>E)hd!Q8}!9njW>{HlQ+!Fc9* z`sJ|7{ea`q5kBldXqA|UBLjdABL&o&bWQP0+ThvapjO@Z@ATJBcEkYs%p`ICNFNQP zQGu+cf)-83KQ?9>jujavw-2p|Uii?&u?876q&-mjCRj1!brXP!L=bHz9;M`BR%#vh z{R?C1zYn*ngqKGF4*E!AV1aZ3G2%f%6cL!}+2_*#lMu7VAD!lZ^HoL=zkcbz|LH+R z1X&gSdjw`Z3UKH$UbKwLriij~m6uN78oc}%GLuL2>c8mXy zkKpU10FLNS^gapG9#8X1ET}vRTHd)BUjC!eF9zK^7nF+Fl9_a}Ie+K zFl7@+#UqG6`;q&`#3^vZ8L+7Iv(diF!^zZBndcn1ba2`RF08>poWlD2IREh87LxR=+4(Yf)?Z;24xi!#j*~texM76**BZ&U->YwEaPxu9o^wJA}(9 zIN6abFDkh4mYZhU%VeXyVgPnoM9dV->>%M37^A9aux+iu9V5iNy+ti z9#OH>MAy7G=@y@baKU$VY&T%Wl=@mG?WZkDqmCG!rWB`bFKe&MrYJ?H$Zg;A137{> zP&FVkp9E+0O^E8_e9ou2m1T6Pc1ZD6Zz>6n)`_3X5pgF=*p3Ddf5H=5{q5y#3FgjW zcX{=RZw`8;f>pDHgB~?=!V8O|rT)wOGfX8RmkVnNtJEOu+FR|5q>;V_qYv21{I zd`BI!JrJ8UM)Z`^P8jx469S^gixe^H(`esvY-2IyB)M?iF6AMMe5}~59kBM0$n*|MKVjVb9t~N2PJmg#G=JB{z8Sk@x0sj!S$N3w0 zzJ`?;rhDjTar%kG+|__LN}iK9`~AXVe}z&^6xREXTuiHMFpAN$`+C1ezwuQ0xe%S! zgqJDiMMk&OV`9P$X5AD1z2p7r9tX7)3`gqj&qk-=3n?4EqPe77N6Q_P(;>Fi%aiNX z^_kDFm+J+ldAqGYvSo*SI*UT=?XmLNBiwD@md$)#A|qgHWggVPlD5^G7pfPV`zz%9 zC{f9UZ@7C8bxpDu{8_2w3?8WgO6b6CeXu~qB-E}q1oQw*mqm{?*+YRw_;l1HF$xpQ zA(+sGwJ>davI~KW1VXX~`d&O*xNi2{&qp$|cbmtO{GXLG1$^ilFGPs~RUt|wkv?W4 zl?vl-YC9pwfKzGl3&GpTBLrjw%B2-Bm@LPK!e8+9IJcd*Wfa!bM$31xs4;SH`YLws zw{Mc~MMs0Q`7e45gN05CFy}re)bmKVKC*x4N0acoHbtmNi#QOL$NhG)5V~gm>)o$Z zihSC!#Bha6(}&<9DTO?zF=l@XBbd2=O}Hy!IePx=e|VAoMIL8YM<1+gC5gY`DDh{y z6NI0mt!eIXDxdl8zViLbkyWv#B2{IdJZy-b;3KUj53POqh_o^JPh4=~hris)j(yPi9HklWUuU}vO***^srE3Y^dovM zm05|4e`E{?@qs(Bi(jfgF|j*LqmF-Wp|fJ5#>u}r4p;NtsYq~fcd}!W=;`{tth8!u z>)&B{J2&18$(HtgnOs=^jC*;uMeO(jN*3a`T%5Yi8*6y$YRQJ6tCPl(CpwZLkq|GQ zn`iOk#Esd0Kh)UZBlP{hu5(=8=y6*l!Qte4=#0a-jcZ+~joa%y61t%=80fQNF}j*7 zQOa-bps57EE2{2#&+RD-)H-P)8~Bd>MLLY7a|%@kTYDEz9P!on=&X3YCdg|~a>3KHVcPwbycLxGby z?M^+GH1qCk%@ncIvM+VjDygU6$o3md9I9Zny{ym42I-znXrA7NVXg)|-;P&M?Z0!L z!o-JXXS8>==B}QNUw6Z=4*fyTI`>k&-z*qdy15TwOReWAIGjbNepnT3)vmG#5Z6%1sr z9C+Mv7Ez0YRzX6mHDZ>PRXx?yj&5jju>c>r0dkL9GU29GwH5>6YB8%(+rDmO@;;%^ z6Qi5KrMEY5kAZV+eM`kBef6oE=n=bLl2YiKd(oO{|0;=RvdRuVVx8VmI+x2b>AB5n zvE^tCeBex(O{pFIdS&6ExA@(5@8Pn%37#xI{QTSbJFqTrPJd-E9Il@>sfx+)lEoo8wa;UnsBH0#!|a!>?rg$!~9?It?hB>d^YUAx6mr6ysS zs!`7KbFRd6z!8tFf0cAQj5U74vyuLa;9s<`$H1JO{wJOr6-GK)XLVn!8)l1#U?M40 z*6Cz1ihNUfm|j(Mp`|y?6vR-85K$zs2ag^y3sa~v^-z19ZXdd>>zv&D!;Ht5{*DN0 zYN_!XThDkfIHwG-nz@e?HL@op_`)P2NE!Fe&BI@VEmK_0KgHFc-NFLLlu=YP@Up6o z@*wjrb14TCY2u0Jy3%;<0=$CV&>SlY+U_{>@XHoi#J7uAi1Tt7kr-rIbDCA?c!{ub z&U4R3-*GH+21Emu2^YwR`J8spLt@zTS0DDGiXFwVo5lFQIpf&l*F1Gz;SZ2wOmNT! zQRy+nFgr}c+D?lmH;I^=SyaWz6^qkG<&8lUvv4y>QKjbQGCwbUeXQ}BsdgMkXkQ_} zdCCtoCh*WB0}E{9A*-@jj`Zonlf%5egV2)E7ls9MT4*{|=94;sHAnC7Gie{XHe?V& z6Jaaw$GyuhxJnk@%X)Ji%$2QHM*OX^Ce6H)=`?+(f{$cy@4a~Q$N$O`w4=@PXyUkd z6AdTJ5F%F;R$E2Ds$|R{V;glAE$y&{+mV%pEXyBS@ypZ4BZZM4IeJa4wBz(e$YT7w zBN_vV8hP;s>GfG4AW~|2YjcM`2952&<2Rs1L%Y?Am{g0BBC>^TBR4*D!Ly; tM_c6D{rNs=R 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/text/colors.yaml b/third_party/webrender/wrench/reftests/text/colors.yaml new file mode 100644 index 00000000000..aceb0328981 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/colors.yaml @@ -0,0 +1,211 @@ +root: + items: + - type: rect + bounds: [10, 10, 250, 50] + color: white + - text: "A B C a b c" + origin: 20 50 + size: 32 + font: "FreeSans.ttf" + + - type: rect + bounds: [270, 10, 250, 50] + color: red + - text: "A B C a b c" + origin: 280 50 + size: 32 + font: "FreeSans.ttf" + + - type: rect + bounds: [530, 10, 250, 50] + color: green + - text: "A B C a b c" + origin: 540 50 + size: 32 + font: "FreeSans.ttf" + + - type: rect + bounds: [10, 70, 250, 50] + color: blue + - text: "A B C a b c" + origin: 20 110 + size: 32 + font: "FreeSans.ttf" + + - type: rect + bounds: [270, 70, 250, 50] + color: [100, 100, 100] + - text: "A B C a b c" + origin: 280 110 + size: 32 + font: "FreeSans.ttf" + + - type: rect + bounds: [530, 70, 250, 50] + color: [200, 100, 150] + - text: "A B C a b c" + origin: 540 110 + size: 32 + font: "FreeSans.ttf" + + - type: rect + bounds: [10, 130, 250, 50] + color: white + - text: "A B C a b c" + origin: 20 170 + size: 32 + font: "FreeSans.ttf" + color: white + + - type: rect + bounds: [270, 130, 250, 50] + color: red + - text: "A B C a b c" + origin: 280 170 + size: 32 + font: "FreeSans.ttf" + color: white + + - type: rect + bounds: [530, 130, 250, 50] + color: green + - text: "A B C a b c" + origin: 540 170 + size: 32 + font: "FreeSans.ttf" + color: white + + - type: rect + bounds: [10, 190, 250, 50] + color: blue + - text: "A B C a b c" + origin: 20 230 + size: 32 + font: "FreeSans.ttf" + color: white + + - type: rect + bounds: [270, 190, 250, 50] + color: [100, 100, 100] + - text: "A B C a b c" + origin: 280 230 + size: 32 + font: "FreeSans.ttf" + color: white + + - type: rect + bounds: [530, 190, 250, 50] + color: [200, 100, 150] + - text: "A B C a b c" + origin: 540 230 + size: 32 + font: "FreeSans.ttf" + color: white + + - type: rect + bounds: [10, 250, 250, 50] + color: white + - text: "A B C a b c" + origin: 20 290 + size: 32 + font: "FreeSans.ttf" + color: [200, 180, 200] + + - type: rect + bounds: [270, 250, 250, 50] + color: red + - text: "A B C a b c" + origin: 280 290 + size: 32 + font: "FreeSans.ttf" + color: [200, 180, 200] + + - type: rect + bounds: [530, 250, 250, 50] + color: green + - text: "A B C a b c" + origin: 540 290 + size: 32 + font: "FreeSans.ttf" + color: [200, 180, 200] + + - type: rect + bounds: [10, 310, 250, 50] + color: blue + - text: "A B C a b c" + origin: 20 350 + size: 32 + font: "FreeSans.ttf" + color: [200, 180, 200] + + - type: rect + bounds: [270, 310, 250, 50] + color: [100, 100, 100] + - text: "A B C a b c" + origin: 280 350 + size: 32 + font: "FreeSans.ttf" + color: [200, 180, 200] + + - type: rect + bounds: [530, 310, 250, 50] + color: [200, 100, 150] + - text: "A B C a b c" + origin: 540 350 + size: 32 + font: "FreeSans.ttf" + color: [200, 180, 200] + + - type: rect + bounds: [10, 370, 250, 50] + color: white + - text: "A B C a b c" + origin: 20 410 + size: 32 + font: "FreeSans.ttf" + color: [50, 50, 50, 0.5] + + - type: rect + bounds: [270, 370, 250, 50] + color: red + - text: "A B C a b c" + origin: 280 410 + size: 32 + font: "FreeSans.ttf" + color: [50, 50, 50, 0.5] + + - type: rect + bounds: [530, 370, 250, 50] + color: green + - text: "A B C a b c" + origin: 540 410 + size: 32 + font: "FreeSans.ttf" + color: [50, 50, 50, 0.5] + + - type: rect + bounds: [10, 430, 250, 50] + color: blue + - text: "A B C a b c" + origin: 20 470 + size: 32 + font: "FreeSans.ttf" + color: [50, 50, 50, 0.5] + + - type: rect + bounds: [270, 430, 250, 50] + color: [100, 100, 100] + - text: "A B C a b c" + origin: 280 470 + size: 32 + font: "FreeSans.ttf" + color: [50, 50, 50, 0.5] + + - type: rect + bounds: [530, 430, 250, 50] + color: [200, 100, 150] + - text: "A B C a b c" + origin: 540 470 + size: 32 + font: "FreeSans.ttf" + color: [50, 50, 50, 0.5] diff --git a/third_party/webrender/wrench/reftests/text/decorations-ref.yaml b/third_party/webrender/wrench/reftests/text/decorations-ref.yaml new file mode 100644 index 00000000000..5ad75109fab --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/decorations-ref.yaml @@ -0,0 +1,19 @@ +--- +root: + items: + - type: rect # short, horizontal + bounds: [ 4, 2, 5, 1 ] + color: green + - type: rect # short, vertical + bounds: [ 12, 14, 1, 5 ] + color: red + style: solid + - type: rect # long, horizontal + bounds: [ 34, 32, 200, 3 ] + color: blue + style: solid + - type: rect # long, vertical + bounds: [ 52, 54, 3, 200 ] + color: black + style: solid + diff --git a/third_party/webrender/wrench/reftests/text/decorations-suite.png b/third_party/webrender/wrench/reftests/text/decorations-suite.png new file mode 100644 index 0000000000000000000000000000000000000000..dc69e0f6d9b3b5660088a733107acd7cf4e056af GIT binary patch literal 24087 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/text/decorations-suite.yaml b/third_party/webrender/wrench/reftests/text/decorations-suite.yaml new file mode 100644 index 00000000000..bbe02dcc95a --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/decorations-suite.yaml @@ -0,0 +1,348 @@ +--- +root: + items: + - type: line + baseline: 10 + start: 10 + end: 210 + width: 1 + orientation: horizontal + color: black + style: solid + - type: line + baseline: 20 + start: 10 + end: 210 + width: 1 + orientation: horizontal + color: blue + style: dashed + - type: line + baseline: 30 + start: 10 + end: 210 + width: 1 + orientation: horizontal + color: green + style: dotted + - type: line + baseline: 40 + start: 10 + end: 210 + width: 3 + thickness: 1 + orientation: horizontal + color: red + style: wavy + + - type: line + baseline: 50 + start: 10 + end: 210 + width: 2 + orientation: horizontal + color: black + style: solid + - type: line + baseline: 65 + start: 10 + end: 210 + width: 2 + orientation: horizontal + color: blue + style: dashed + - type: line + baseline: 80 + start: 10 + end: 207 # pruposefully cut off + width: 2 + orientation: horizontal + color: green + style: dotted + - type: line + baseline: 95 + start: 10 + end: 210 + width: 6 + thickness: 2 + orientation: horizontal + color: red + style: wavy + + - + type: "shadow" + bounds: [8, 100, 225, 50] + blur-radius: 0 + offset: [2, 2] + color: red + - type: line + baseline: 110 + start: 10 + end: 210 + width: 1 + orientation: horizontal + color: [0,0,0,0] + style: solid + - type: line + baseline: 120 + start: 10 + end: 210 + width: 1 + orientation: horizontal + color: [0,0,0,0] + style: dashed + - type: line + baseline: 130 + start: 10 + end: 209 + width: 1 + orientation: horizontal + color: [0,0,0,0] + style: dotted + - type: line + baseline: 140 + start: 10 + end: 210 + width: 3 + thickness: 1 + orientation: horizontal + color: [0,0,0,0] + style: wavy + - + type: pop-all-shadows + + - + type: "shadow" + bounds: [8, 145, 225, 65] + blur-radius: 1 + offset: [2, 3] + color: red + - type: line + baseline: 150 + start: 10 + end: 210 + width: 2 + orientation: horizontal + color: black + style: solid + - type: line + baseline: 165 + start: 10 + end: 210 + width: 2 + orientation: horizontal + color: blue + style: dashed + - type: line + baseline: 180 + start: 10 + end: 207 # purposefully cut off + width: 2 + orientation: horizontal + color: green + style: dotted + - type: line + baseline: 195 + start: 10 + end: 210 + width: 6 + thickness: 2 + orientation: horizontal + color: red + style: wavy + - + type: pop-all-shadows + + - + type: "shadow" + bounds: [8, 220, 225, 40] + blur-radius: 0 + offset: [5, 7] + color: red + - type: line + baseline: 230 + start: 10 + end: 210 + width: 8 + orientation: horizontal + color: black + style: solid + - type: line + baseline: 250 + start: 10 + end: 210 + width: 8 + orientation: horizontal + color: blue + style: dashed + - type: line + baseline: 270 + start: 10 + end: 205 # purposefully cut off + width: 8 + orientation: horizontal + color: green + style: dotted + - type: line + baseline: 290 + start: 10 + end: 210 + width: 12 + thickness: 3 + orientation: horizontal + color: black + style: wavy + - + type: "pop-all-shadows" + + - + type: "shadow" + bounds: [0, 320, 240, 140] + blur-radius: 3 + offset: [5, 7] + color: red + - type: line + baseline: 330 + start: 10 + end: 210 + width: 8 + orientation: horizontal + color: black + style: solid + - type: line + baseline: 350 + start: 10 + end: 210 + width: 8 + orientation: horizontal + color: blue + style: dashed + - type: line + baseline: 370 + start: 10 + end: 205 # purposefully cut off + width: 8 + orientation: horizontal + color: green + style: dotted + - type: line + baseline: 390 + start: 10 + end: 210 + width: 16 + thickness: 4 + orientation: horizontal + color: black + style: wavy + - + type: "pop-all-shadows" + + - type: line + baseline: 220 + start: 10 + end: 210 + width: 1 + orientation: vertical + color: black + style: solid + - type: line + baseline: 230 + start: 10 + end: 210 + width: 1 + orientation: vertical + color: blue + style: dashed + - type: line + baseline: 240 + start: 10 + end: 210 + width: 1 + orientation: vertical + color: green + style: dotted + - type: line + baseline: 250 + start: 10 + end: 210 + thickness: 1 + width: 3 + orientation: vertical + color: red + style: wavy + + - type: line + baseline: 270 + start: 10 + end: 210 + width: 2 + orientation: vertical + color: black + style: solid + - type: line + baseline: 290 + start: 10 + end: 210 + width: 2 + orientation: vertical + color: blue + style: dashed + - type: line + baseline: 310 + start: 10 + end: 207 # purposefully cut off + width: 2 + orientation: vertical + color: green + style: dotted + - type: line + baseline: 330 + start: 10 + end: 210 + thickness: 2 + width: 6 + orientation: vertical + color: red + style: wavy + + - + type: "shadow" + bounds: [350, 0, 120, 240] + blur-radius: 3 + offset: [5, 2] + color: black + - type: line + baseline: 380 + start: 10 + end: 210 + width: 8 + orientation: vertical + color: yellow + style: solid + - type: line + baseline: 400 + start: 10 + end: 210 + width: 8 + orientation: vertical + color: blue + style: dashed + - type: line + baseline: 420 + start: 10 + end: 205 # purposefully cut off + width: 8 + orientation: vertical + color: green + style: dotted + - type: line + baseline: 440 + start: 10 + end: 210 + thickness: 4 + width: 16 + orientation: vertical + color: red + style: wavy + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/decorations.yaml b/third_party/webrender/wrench/reftests/text/decorations.yaml new file mode 100644 index 00000000000..db15551a74e --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/decorations.yaml @@ -0,0 +1,36 @@ +--- +root: + items: + - type: line # short, horizontal + baseline: 2 + start: 4 + end: 9 + width: 1 + orientation: horizontal + color: green + style: solid + - type: line # short, vertical + baseline: 12 + start: 14 + end: 19 + width: 1 + orientation: vertical + color: red + style: solid + - type: line # long, horizontal + baseline: 32 + start: 34 + end: 234 + width: 3 + orientation: horizontal + color: blue + style: solid + - type: line # long, vertical + baseline: 52 + start: 54 + end: 254 + width: 3 + orientation: vertical + color: black + style: solid + diff --git a/third_party/webrender/wrench/reftests/text/diacritics-ref.yaml b/third_party/webrender/wrench/reftests/text/diacritics-ref.yaml new file mode 100644 index 00000000000..334e1899eec --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/diacritics-ref.yaml @@ -0,0 +1,6 @@ +root: + items: + - text: "x" + origin: 20 30 + size: 20 + font: "FreeSans.ttf" diff --git a/third_party/webrender/wrench/reftests/text/diacritics.yaml b/third_party/webrender/wrench/reftests/text/diacritics.yaml new file mode 100644 index 00000000000..7c613c3aa6b --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/diacritics.yaml @@ -0,0 +1,6 @@ +root: + items: + - text: "x̂" + origin: 20 30 + size: 20 + font: "FreeSans.ttf" diff --git a/third_party/webrender/wrench/reftests/text/embedded-bitmaps.png b/third_party/webrender/wrench/reftests/text/embedded-bitmaps.png new file mode 100644 index 0000000000000000000000000000000000000000..a8039015a5b27afff4b46b5d6b8d571ca98a919f GIT binary patch literal 2288 zcmc&$eQXow9rlVeD%5tB7y_+D{LzwN2#K(P)Nx{Wh?SWn_8>Ho33jRy=ai8I>Wh6i zj+1uN)UigLMx3NheD+qr1`=$^juOsiCrA}c{>WXyNvsfmlq~k)kN66XF^)0W8PGQE z|7p@s_ndTk@Ap2>?|I(43%}tXd2`b*HqmIbH+jE4e4Ivm?GAO%c>Sl;wP|&GKaKYK zB=7JqPn_4T%r8pcIr8@JA3V+Ps1Tm|>!v@%G*y4>tBJ067~c8j$}aY2EBfA_tyAl0 z)9;oQ-g4`Ut*0sY;nr9CC^+}S?QIl%@Z+a%Q1I^geT5cp*|%+=V57M2Cp6mU8~!KD z>{vlg++kP8&Ky@toz8W4B>Z# zrq-S{r!|=B`H8ctee*kX%YQ!Cvf#Yl6ut-F49Nx|KRwl^X*~!`#MDG7)_pL1QPjd3 zu>>Wq5_SqsXoYe{8s3?y=x0PvVHIIc0cYtx*0GG5liGtX!Z-r@bd_Ml1&EQ!De*ZA;Z@uQdiC_=Jl)Gil#uexhxJ5W+8qJIMeL8_Ue+J8%|#D8XbLx^6(NLC7i;uiZZ}i1w`|n4(JG1woWu8~P5xw}+xkR;m?( z+?B+*t6{B&WJs3$Kx0@RX)n8z-8eqHGoxLcWu;gOiBCx0$oav+;7oR|t}<7Hb)QVK zMwt3RCAP;4iy~foPHpzwQ{I==LVNYjQtz9f4sVdr-D$A zQNd-`qRmCFfvh%RV`kehYRfN6I?+fVM2W+#zLErSmQ0u1pBtwHdsPRjlio^U8YXns z^BFo|6|fh*rs^plFl-r*4OBxeIIY#XP1QCZ%y9Fw9b79q9jjLiCu@Ww$zAos7$BC?x8ipSP;c|5F)S&+*Jk7y(r>BmnL}? zy=a!}rINwm6>G44K+rvj@0U!(OUUIChcO`?42F*$T%n^|xS$-e8A~$GPQZ0Cc(){o zud2u;PrWD->G6sFMi_ILD)<2CK8h_B$nIp~oc75x2LXf0YVP_w(DNmgDnaiDSa%V? zDW;r4EO!u|7s*m3S=HoVc7N{Yxa!qI#WN< zFkYI`7LQJ)3TBtMj#E0NF0&WHlummyS}G7P3oc<;yN-q)V7N`W&aTXVEzsw7i~GK4N^DvG@gXxYs!#LRbPdxX7}Y;CuJ`Ca?A^FD z{bg`O8D*nbrxB%p2V@u5-_rL?8*#^LWp{3sUFtoTD9@ATP%cI)iKOv+vYERAZTP(5 h!T;mK|7_!yk@t4IU2$oR`oEy@e#Jj*I`ql!{t1mQ?jryI literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/text/embedded-bitmaps.yaml b/third_party/webrender/wrench/reftests/text/embedded-bitmaps.yaml new file mode 100644 index 00000000000..f22b11d95f7 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/embedded-bitmaps.yaml @@ -0,0 +1,9 @@ +--- # checks that embedded bitmaps are rasterized and use a proper layout +root: + items: + - text: "ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 {}[]()<>$*-+=/#_%^@\\&|~?'\"!,.;:" + origin: 20 30 + bounds: [0, 0, 710, 50] + size: 8.25 + font: "Proggy.ttf" + embedded-bitmaps: true diff --git a/third_party/webrender/wrench/reftests/text/intermediate-transform.yaml b/third_party/webrender/wrench/reftests/text/intermediate-transform.yaml new file mode 100644 index 00000000000..ab2e51804c5 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/intermediate-transform.yaml @@ -0,0 +1,54 @@ +# This test case makes the text flipped relative to the surface it renders to, but not to the world. +--- +root: + items: + - + type: "stacking-context" + items: + - + type: "reference-frame" + transform: [0.7753850221633911, 0, 0, 0, 0, 0.7753850221633911, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] + items: + - + type: "stacking-context" + items: + - + type: "stacking-context" + transform-style: "preserve-3d" + origin: [237, 246] + items: + - + type: "reference-frame" + transform-style: "preserve-3d" + transform: [1, 0, 0, 0, 0, 1, 0, 0, -0.09215625375509262, -0.05100416764616966, 1, -0.0001250000059371814, 0, -3, 0, 1] + items: + - + type: "stacking-context" + transform-style: "preserve-3d" + items: + - + type: "reference-frame" + transform: [-1, 0, 0, 0, 0, -0.9659258127212524, 0.258819043636322, 0, 0, 0.258819043636322, 0.9659258127212524, 0, 1474.5, 802.0977172851563, -105.5981674194336, 1] + items: + - + type: "stacking-context" + items: + - + rect: [0, 0, 1475, 408] + color: red + - + type: "reference-frame" + transform: [-1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1474.5, 408, 0, 1] + items: + - + type: "stacking-context" + items: + - + glyphs: [55,43,40,3,44,54,36,37,40,47,47,36] + offsets: [511, 290, 551.2000122070313, 290, 597.066650390625, 290, 637.2666625976563, 290, 656.1333618164063, 290, 682.0999755859375, 290, 716.6333618164063, 290, 759.6666870117188, 290, 799.8666381835938, 290, 840.066650390625, 290, 880.2666625976563, 290, 920.4666748046875, 290] + size: 22 + color: black + font: "../text/VeraBd.ttf" + bounds: [509, 250, 457, 54] + clip-rect: [508, 249, 459, 56] + diff --git a/third_party/webrender/wrench/reftests/text/isolated-text.png b/third_party/webrender/wrench/reftests/text/isolated-text.png new file mode 100644 index 0000000000000000000000000000000000000000..7b310e78dcb2ec25cfa7797615b58bbdfe19c72f GIT binary patch literal 6737 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 NigK#5<!N= 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 yxm04J8M4J-lYyfQ{1Am4=10.14) fuzzy(3,67) == white-opacity.yaml white-opacity.png +fuzzy(1,4) platform(linux) options(disable-subpixel) == colors.yaml colors-alpha.png +# Run without dual-source blending path, batches are broken when text colors change. +fuzzy(1,6) platform(linux) options(disable-dual-source-blending) draw_calls(6) == colors.yaml colors-subpx.png +# Run with both dual-source blending, ensuring batching is improved. +fuzzy(1,6) platform(linux) draw_calls(3) == colors.yaml colors-subpx.png +platform(linux) options(disable-subpixel) == border-radius.yaml border-radius-alpha.png +platform(linux) == border-radius.yaml border-radius-subpx.png +options(disable-aa) == transparent-no-aa.yaml transparent-no-aa-ref.yaml +!= diacritics.yaml diacritics-ref.yaml +fuzzy(1,1) platform(linux) options(disable-subpixel) == text-masking.yaml text-masking-alpha.png +fuzzy(1,44) platform(linux) == text-masking.yaml text-masking-subpx.png +platform(linux) options(disable-subpixel) == alpha-transform.yaml alpha-transform.png +platform(linux) == subpixel-rotate.yaml subpixel-rotate.png +platform(linux) == subpixel-scale.yaml subpixel-scale.png +platform(linux) == subpixel-skew.yaml subpixel-skew.png +fuzzy(1,381) platform(linux) == subpixel-translate.yaml subpixel-translate-ref.yaml +!= shadow-rotate.yaml blank.yaml +platform(linux) == embedded-bitmaps.yaml embedded-bitmaps.png +platform(linux) == clipped-transform.yaml clipped-transform.png +platform(mac) fuzzy(195,30) == color-bitmap-shadow.yaml color-bitmap-shadow-ref.yaml +platform(linux) == writing-modes.yaml writing-modes-ref.yaml +platform(linux) == blurred-shadow-local-clip-rect.yaml blurred-shadow-local-clip-rect-ref.png +fuzzy(1,1) platform(linux) == two-shadows.yaml two-shadows.png +== shadow-clip.yaml shadow-clip-ref.yaml +== shadow-fast-clip.yaml shadow-fast-clip-ref.yaml +skip_on(android,device) == shadow-partial-glyph.yaml shadow-partial-glyph-ref.yaml # Fails on Pixel2 +fuzzy(1,107) platform(linux) == shadow-transforms.yaml shadow-transforms.png +fuzzy(1,113) platform(linux) == raster-space.yaml raster-space.png +skip_on(android) skip_on(mac,>=10.14) != allow-subpixel.yaml allow-subpixel-ref.yaml # Android: we don't enable sub-px aa on this platform. +skip_on(android,device) == bg-color.yaml bg-color-ref.yaml # Fails on Pixel2 +!= large-glyphs.yaml blank.yaml +!= large-line-decoration.yaml blank.yaml +skip_on(android,device) == snap-text-offset.yaml snap-text-offset-ref.yaml +fuzzy(5,4435) == shadow-border.yaml shadow-solid-ref.yaml +fuzzy(5,4435) == shadow-image.yaml shadow-solid-ref.yaml +options(disable-aa) == snap-clip.yaml snap-clip-ref.yaml +platform(linux) == perspective-clip.yaml perspective-clip.png +fuzzy(1,39) options(disable-subpixel) == raster-space-snap.yaml raster-space-snap-ref.yaml +# == intermediate-transform.yaml intermediate-transform-ref.yaml # fails because of AA inavailable with an intermediate surface +platform(linux) force_subpixel_aa_where_possible(true) == text-fixed-slice.yaml text-fixed-slice-slow.png +platform(linux) force_subpixel_aa_where_possible(false) == text-fixed-slice.yaml text-fixed-slice-fast.png + +# a 8544x8544 raster root vs. 2136x2136 +# most pixels are off by a small amount, but a few pixels on the edge vary by a lot, pushing up the fuzzy max-diff; +# the main goal of the test is that everything is in the same place, at the same scale, clipped the same way, +# despite 4x on-the-fly scale change. +skip_on(android) fuzzy-range(<=3,*20569,<=20,*2525,<=115,*543) == raster_root_C_8192.yaml raster_root_C_ref.yaml diff --git a/third_party/webrender/wrench/reftests/text/shadow-atomic-ref.yaml b/third_party/webrender/wrench/reftests/text/shadow-atomic-ref.yaml new file mode 100644 index 00000000000..ccf159b5a78 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-atomic-ref.yaml @@ -0,0 +1,81 @@ +--- +root: + items: +# equivalent of shadow with [20, 16] offset + - + type: line + baseline: 61 + start: 34 + end: 230 + width: 3 + orientation: horizontal + color: green + style: solid + - + bounds: [34, 34, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [36, 59, 55.533333, 59, 71.533333, 59, 80.4, 59, 92.833336, 59, 100.833336, 59, 109.7, 59, 122.13333, 59, 130.13333, 59, 139, 59, 155, 59, 169.2, 59, 177.2, 59, 193.2, 59, 207.4, 59, 216.26666, 59] + size: 18 + color: green + font: "VeraBd.ttf" + - + type: line + baseline: 48 + start: 34 + end: 230 + width: 3 + orientation: horizontal + color: green + style: solid +# equivalent of shadow with [10, 8] offset + - + type: line + baseline: 53 + start: 24 + end: 220 + width: 3 + orientation: horizontal + color: black + style: solid + - + bounds: [24, 26, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [26, 51, 45.533333, 51, 61.533333, 51, 70.4, 51, 82.833336, 51, 90.833336, 51, 99.7, 51, 112.13333, 51, 120.13333, 51, 129, 51, 145, 51, 159.2, 51, 167.2, 51, 183.2, 51, 197.4, 51, 206.26666, 51] + size: 18 + color: black + font: "VeraBd.ttf" + - + type: line + baseline: 40 + start: 24 + end: 220 + width: 3 + orientation: horizontal + color: black + style: solid +# same as shadow-atomic.yaml + - + type: line + baseline: 45 + start: 14 + end: 210 + width: 3 + orientation: horizontal + color: red + style: solid + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 18 + color: red + font: "VeraBd.ttf" + - + type: line + baseline: 32 + start: 14 + end: 210 + width: 3 + orientation: horizontal + color: red + style: solid diff --git a/third_party/webrender/wrench/reftests/text/shadow-atomic.yaml b/third_party/webrender/wrench/reftests/text/shadow-atomic.yaml new file mode 100644 index 00000000000..21b9eac876a --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-atomic.yaml @@ -0,0 +1,42 @@ +--- # checks that decorations on "real" content and "shadow" content are on seperate, atomic, layers +root: + items: + - + type: "shadow" + bounds: [14, 18, 205, 35] + blur-radius: 0 + offset: [20, 16] + color: green + - + type: "shadow" + bounds: [14, 18, 205, 35] + blur-radius: 0 + offset: [10, 8] + color: black + - + type: line + baseline: 45 + start: 14 + end: 210 + width: 3 + orientation: horizontal + color: red + style: solid + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 18 + color: red + font: "VeraBd.ttf" + - + type: line + baseline: 32 + start: 14 + end: 210 + width: 3 + orientation: horizontal + color: red + style: solid + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/shadow-border.yaml b/third_party/webrender/wrench/reftests/text/shadow-border.yaml new file mode 100644 index 00000000000..b05fb88cb50 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-border.yaml @@ -0,0 +1,19 @@ +--- +root: + items: + - + type: "shadow" + blur-radius: 25 + bounds: [0, 0, 90, 90] + offset: [0, 0] + color: [0, 0, 0, 0.8] + - + type: border + bounds: [ 30, 30, 30, 30 ] + width: [ 15, 15, 15, 15 ] + border-type: normal + style: solid + color: blue + radius: 0 + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/shadow-clip-rect.yaml b/third_party/webrender/wrench/reftests/text/shadow-clip-rect.yaml new file mode 100644 index 00000000000..6edf6ccb5aa --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-clip-rect.yaml @@ -0,0 +1,45 @@ +--- # checks that decorations on "real" content and "shadow" content are on seperate, atomic, layers +root: + items: + - + type: "shadow" + bounds: [14, 18, 205, 35] + blur-radius: 0 + offset: [20, 16] + color: green + - + type: "shadow" + bounds: [14, 18, 205, 35] + blur-radius: 0 + offset: [10, 8] + color: black + - + type: line + clip-rect: [14, 18, 205, 35] + baseline: 45 + start: 14 + end: 210 + width: 3 + orientation: horizontal + color: red + style: solid + - + bounds: [14, 18, 205, 35] + clip-rect: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 18 + color: red + font: "VeraBd.ttf" + - + type: line + clip-rect: [14, 18, 205, 35] + baseline: 32 + start: 14 + end: 210 + width: 3 + orientation: horizontal + color: red + style: solid + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/shadow-clip-ref.yaml b/third_party/webrender/wrench/reftests/text/shadow-clip-ref.yaml new file mode 100644 index 00000000000..ff57bfde63b --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-clip-ref.yaml @@ -0,0 +1,11 @@ +# Test that the clip-chain local rect is not applied +# to the text run being drawn in the shadow. If it was +# applied, the edges of the quad will be a slightly +# different color, since the blur will include some +# clipped out pixels. +--- +root: + items: + - type: rect + bounds: [28, 28, 80, 80] + color: [255, 127, 127, 1] diff --git a/third_party/webrender/wrench/reftests/text/shadow-clip.yaml b/third_party/webrender/wrench/reftests/text/shadow-clip.yaml new file mode 100644 index 00000000000..ecfade06dc1 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-clip.yaml @@ -0,0 +1,25 @@ +# Test that the clip-chain local rect is not applied +# to the text run being drawn in the shadow. If it was +# applied, the edges of the quad will be a slightly +# different color, since the blur will include some +# clipped out pixels. +--- +root: + items: + - type: clip + bounds: [28, 28, 80, 80] + items: + - + type: "shadow" + bounds: [0, 0, 200, 200] + blur-radius: 2 + color: [255, 0, 0, 0.5] + - + bounds: [6, 6, 132, 133] + glyphs: [67] + offsets: [8, 111] + size: 128 + color: [0, 0, 0, 0] + font: "Ahem.ttf" + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/shadow-clipped-text.yaml b/third_party/webrender/wrench/reftests/text/shadow-clipped-text.yaml new file mode 100644 index 00000000000..9ab01d0a3fa --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-clipped-text.yaml @@ -0,0 +1,24 @@ +# Tests that the unclipped parts of a shadow still render even if the associated +# text is clipped out. +--- +root: + items: + - type: scroll-frame + bounds: [14, 18, 10, 5] + content-size: [10, 5] + items: + - + type: "shadow" + bounds: [11, 20, 100, 100] + blur-radius: 3 + offset: [0, 0] + color: black + - + bounds: [14, 23, 100, 100] + glyphs: [55] + offsets: [16, 43] + size: 18 + color: black + font: "VeraBd.ttf" + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/shadow-complex.yaml b/third_party/webrender/wrench/reftests/text/shadow-complex.yaml new file mode 100644 index 00000000000..e1bfbc1b3ac --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-complex.yaml @@ -0,0 +1,46 @@ +--- # The same as shadow-many.yaml, except the shadows only apply to parts of the text +root: + items: + - + type: "shadow" + bounds: [14, 18, 205, 35] + blur-radius: 5 + offset: [0, 0] + color: black + - + type: "shadow" + bounds: [14, 18, 205, 35] + blur-radius: 0 + offset: [2, 3] + color: red + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43] + size: 18 + color: [0, 0, 0, 0] # actual text is transparent + font: "VeraBd.ttf" + - + type: "pop-all-shadows" + - + type: "shadow" + bounds: [14, 18, 205, 35] + blur-radius: 3 + offset: [-2, 3.5] + color: blue + - + bounds: [14, 18, 205, 35] + glyphs: [3, 76, 86, 3, 87, 75] + offsets: [72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43] + size: 18 + color: [0, 0, 0, 0] # actual text is transparent + font: "VeraBd.ttf" + - + bounds: [14, 18, 205, 35] + glyphs: [72, 3, 69, 72, 86, 87] + offsets: [135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 18 + color: [0, 0, 0, 0] # actual text is transparent + font: "VeraBd.ttf" + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/shadow-cover-1.yaml b/third_party/webrender/wrench/reftests/text/shadow-cover-1.yaml new file mode 100644 index 00000000000..088c26f1c5c --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-cover-1.yaml @@ -0,0 +1,18 @@ +--- # text covering a shadow +root: + items: + - + type: "shadow" + bounds: [14, 18, 205, 35] + blur-radius: 3 + offset: [0, 0] + color: red + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 18 + color: green + font: "VeraBd.ttf" + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/shadow-cover-2.yaml b/third_party/webrender/wrench/reftests/text/shadow-cover-2.yaml new file mode 100644 index 00000000000..c18e277340b --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-cover-2.yaml @@ -0,0 +1,26 @@ +--- # shadow covering text +root: + items: + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 18 + color: green + font: "VeraBd.ttf" + - + type: "shadow" + bounds: [14, 18, 205, 35] + blur-radius: 3 + offset: [0, 0] + color: red + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 18 + color: [0, 0, 0, 0] # text transparent + font: "VeraBd.ttf" + - + type: "pop-all-shadows" + diff --git a/third_party/webrender/wrench/reftests/text/shadow-fast-clip-ref.yaml b/third_party/webrender/wrench/reftests/text/shadow-fast-clip-ref.yaml new file mode 100644 index 00000000000..3e1b08de5fa --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-fast-clip-ref.yaml @@ -0,0 +1,21 @@ +# Test that fast shadows actually apply clips +--- +root: + items: + - type: clip + bounds: [28, 28, 80, 80] + items: + - + bounds: [6, 6, 132, 133] + text: "overflow text" + origin: [9, 101] + size: 12 + color: [0, 0, 0, 1] + font: "VeraBd.ttf" + - + bounds: [6, 6, 132, 133] + text: "overflow text" + origin: [8, 100] + size: 12 + color: [255, 0, 0, 1] + font: "VeraBd.ttf" diff --git a/third_party/webrender/wrench/reftests/text/shadow-fast-clip.yaml b/third_party/webrender/wrench/reftests/text/shadow-fast-clip.yaml new file mode 100644 index 00000000000..f28213573cc --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-fast-clip.yaml @@ -0,0 +1,22 @@ +# Test that fast shadows actually apply clips +--- +root: + items: + - type: clip + bounds: [28, 28, 80, 80] + items: + - + type: "shadow" + bounds: [0, 0, 200, 200] + blur-radius: 0 + offset: [1, 1] + color: [0, 0, 0, 1] + - + bounds: [6, 6, 132, 133] + text: "overflow text" + origin: [8, 100] + size: 12 + color: [255, 0, 0, 1] + font: "VeraBd.ttf" + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/shadow-grey-ref.yaml b/third_party/webrender/wrench/reftests/text/shadow-grey-ref.yaml new file mode 100644 index 00000000000..d40d997161b --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-grey-ref.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - rect: [14, 10, 205, 38] + color: [100, 100, 100, 1.0] + diff --git a/third_party/webrender/wrench/reftests/text/shadow-grey-transparent.yaml b/third_party/webrender/wrench/reftests/text/shadow-grey-transparent.yaml new file mode 100644 index 00000000000..ba54ede1e25 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-grey-transparent.yaml @@ -0,0 +1,19 @@ +--- +root: + items: + - rect: [14, 10, 205, 38] + color: [100, 100, 100, 1.0] + - + type: "shadow" + blur-radius: 2 + color: [100, 100, 100, 0.2] + bounds: [14, 10, 205, 38] + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 24 + color: [0,0,0,0] + - + type: "pop-all-shadows" + diff --git a/third_party/webrender/wrench/reftests/text/shadow-grey.yaml b/third_party/webrender/wrench/reftests/text/shadow-grey.yaml new file mode 100644 index 00000000000..a728518ce33 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-grey.yaml @@ -0,0 +1,19 @@ +--- +root: + items: + - rect: [14, 10, 205, 38] + color: [100, 100, 100, 1.0] + - + type: "shadow" + blur-radius: 2 + color: [100, 100, 100, 1.0] + bounds: [14, 10, 205, 38] + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 24 + color: [0,0,0,0] + - + type: "pop-all-shadows" + diff --git a/third_party/webrender/wrench/reftests/text/shadow-huge-ref.yaml b/third_party/webrender/wrench/reftests/text/shadow-huge-ref.yaml new file mode 100644 index 00000000000..c0f8332a9e3 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-huge-ref.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - + type: "shadow" + bounds: [14, 18, 205, 35] + blur-radius: 600 + offset: [10, 10] + color: black + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 18 + color: [0, 0, 0, 1] + font: "VeraBd.ttf" + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/shadow-huge.yaml b/third_party/webrender/wrench/reftests/text/shadow-huge.yaml new file mode 100644 index 00000000000..1814dcf1dfc --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-huge.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - + type: "shadow" + bounds: [14, 18, 205, 35] + blur-radius: 100000000 + offset: [10, 10] + color: black + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 18 + color: [0, 0, 0, 1] + font: "VeraBd.ttf" + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/shadow-image.yaml b/third_party/webrender/wrench/reftests/text/shadow-image.yaml new file mode 100644 index 00000000000..4d472aadc49 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-image.yaml @@ -0,0 +1,14 @@ +--- +root: + items: + - + type: "shadow" + blur-radius: 25 + bounds: [0, 0, 90, 90] + offset: [0, 0] + color: [0, 0, 0, 0.8] + - + bounds: [ 30, 30, 30, 30 ] + image: solid-color(0, 0, 255, 255, 30, 30) + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/shadow-many.yaml b/third_party/webrender/wrench/reftests/text/shadow-many.yaml new file mode 100644 index 00000000000..b5daabe669b --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-many.yaml @@ -0,0 +1,30 @@ +--- # the same as shadow.yaml, except there are many shadows with different offsets and colors +root: + items: + - + type: "shadow" + bounds: [14, 18, 205, 35] + blur-radius: 5 + offset: [0, 0] + color: black + - + type: "shadow" + bounds: [14, 18, 205, 35] + blur-radius: 0 + offset: [2, 3] + color: red + - + type: "shadow" + bounds: [14, 18, 205, 35] + blur-radius: 3 + offset: [-2, 3.5] + color: blue + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 18 + color: [0, 0, 0, 0] # actual text is transparent + font: "VeraBd.ttf" + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/shadow-ordering-ref.yaml b/third_party/webrender/wrench/reftests/text/shadow-ordering-ref.yaml new file mode 100644 index 00000000000..cc0b378cb0d --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-ordering-ref.yaml @@ -0,0 +1,131 @@ +--- # checks that decorations on "real" content and "shadow" content are on seperate, atomic, layers +root: + items: + - + type: "shadow" + bounds: [44, 42, 205, 35] + blur-radius: 1.0 + offset: [30, 24] + color: blue + - + type: line + baseline: 45 + start: 14 + end: 210 + width: 3 + orientation: horizontal + color: [0,0,0,0] # transparent + style: solid + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 18 + color: [0,0,0,0] # transparent + font: "VeraBd.ttf" + - + type: line + baseline: 32 + start: 14 + end: 210 + width: 3 + orientation: horizontal + color: [0,0,0,0] # transparent + style: solid + - + type: "pop-all-shadows" + + - + type: "shadow" + bounds: [34, 34, 205, 35] + blur-radius: 0.0 # no blur to tigger fast shadows + offset: [20, 16] + color: green + - + type: line + baseline: 45 + start: 14 + end: 210 + width: 3 + orientation: horizontal + color: [0,0,0,0] # transparent + style: solid + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 18 + color: [0,0,0,0] # transparent + font: "VeraBd.ttf" + - + type: line + baseline: 32 + start: 14 + end: 210 + width: 3 + orientation: horizontal + color: [0,0,0,0] # transparent + style: solid + - + type: "pop-all-shadows" + + - + type: "shadow" + bounds: [24, 26, 205, 35] + blur-radius: 1.0 + offset: [10, 8] + color: black + - + type: line + baseline: 45 + start: 14 + end: 210 + width: 3 + orientation: horizontal + color: [0,0,0,0] # transparent + style: solid + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 18 + color: [0,0,0,0] # transparent + font: "VeraBd.ttf" + - + type: line + baseline: 32 + start: 14 + end: 210 + width: 3 + orientation: horizontal + color: [0,0,0,0] # transparent + style: solid + - + type: "pop-all-shadows" + + # real text + - + type: line + baseline: 45 + start: 14 + end: 210 + width: 3 + orientation: horizontal + color: red + style: solid + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 18 + color: red + font: "VeraBd.ttf" + - + type: line + baseline: 32 + start: 14 + end: 210 + width: 3 + orientation: horizontal + color: red + style: solid diff --git a/third_party/webrender/wrench/reftests/text/shadow-ordering.yaml b/third_party/webrender/wrench/reftests/text/shadow-ordering.yaml new file mode 100644 index 00000000000..d1969ef09bc --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-ordering.yaml @@ -0,0 +1,48 @@ +--- # checks that decorations on "real" content and "shadow" content are on seperate, atomic, layers +root: + items: + - + type: "shadow" + bounds: [44, 42, 205, 35] + blur-radius: 1.0 + offset: [30, 24] + color: blue + - + type: "shadow" + bounds: [34, 34, 205, 35] + blur-radius: 0.0 # no blur to tigger fast shadows + offset: [20, 16] + color: green + - + type: "shadow" + bounds: [24, 26, 205, 35] + blur-radius: 1.0 + offset: [10, 8] + color: black + - + type: line + baseline: 45 + start: 14 + end: 210 + width: 3 + orientation: horizontal + color: red + style: solid + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 18 + color: red + font: "VeraBd.ttf" + - + type: line + baseline: 32 + start: 14 + end: 210 + width: 3 + orientation: horizontal + color: red + style: solid + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/shadow-partial-glyph-ref.yaml b/third_party/webrender/wrench/reftests/text/shadow-partial-glyph-ref.yaml new file mode 100644 index 00000000000..df32d3fd3c9 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-partial-glyph-ref.yaml @@ -0,0 +1,50 @@ +--- # taking the shadow of multiple copies of a glyph with different clips should look the same as the unclipped glyph +root: + items: + - + type: "shadow" + blur-radius: 2 + bounds: [14, 18, 205, 35] + offset: [40, 0] + color: black + - + bounds: [14, 18, 205, 35] + glyphs: [58] + offsets: [16, 43] + size: 18 + color: [0, 0, 0, 0] + font: "VeraBd.ttf" + - + type: "pop-all-shadows" + - + bounds: [14, 18, 205, 35] + clip-rect: [16, 18, 14, 14] + glyphs: [58] + offsets: [16, 43] + size: 18 + color: [255, 0, 0, 1] + font: "VeraBd.ttf" + - + bounds: [14, 18, 205, 35] + clip-rect: [30, 18, 14, 14] + glyphs: [58] + offsets: [16, 43] + size: 18 + color: [0, 255, 0, 1] + font: "VeraBd.ttf" + - + bounds: [14, 18, 205, 35] + clip-rect: [16, 32, 14, 14] + glyphs: [58] + offsets: [16, 43] + size: 18 + color: [0, 0, 255, 1] + font: "VeraBd.ttf" + - + bounds: [14, 18, 205, 35] + clip-rect: [30, 32, 14, 14] + glyphs: [58] + offsets: [16, 43] + size: 18 + color: [255, 0, 255, 1] + font: "VeraBd.ttf" \ No newline at end of file diff --git a/third_party/webrender/wrench/reftests/text/shadow-partial-glyph.yaml b/third_party/webrender/wrench/reftests/text/shadow-partial-glyph.yaml new file mode 100644 index 00000000000..4508db9d8b3 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-partial-glyph.yaml @@ -0,0 +1,43 @@ +--- # taking the shadow of multiple copies of a glyph with different clips should look the same as the unclipped glyph +root: + items: + - + type: "shadow" + blur-radius: 2 + bounds: [14, 18, 205, 35] + offset: [40, 0] + color: black + - + bounds: [14, 18, 205, 35] + clip-rect: [16, 18, 14, 14] + glyphs: [58] + offsets: [16, 43] + size: 18 + color: [255, 0, 0, 1] + font: "VeraBd.ttf" + - + bounds: [14, 18, 205, 35] + clip-rect: [30, 18, 14, 14] + glyphs: [58] + offsets: [16, 43] + size: 18 + color: [0, 255, 0, 1] + font: "VeraBd.ttf" + - + bounds: [14, 18, 205, 35] + clip-rect: [16, 32, 14, 14] + glyphs: [58] + offsets: [16, 43] + size: 18 + color: [0, 0, 255, 1] + font: "VeraBd.ttf" + - + bounds: [14, 18, 205, 35] + clip-rect: [30, 32, 14, 14] + glyphs: [58] + offsets: [16, 43] + size: 18 + color: [255, 0, 255, 1] + font: "VeraBd.ttf" + - + type: "pop-all-shadows" \ No newline at end of file diff --git a/third_party/webrender/wrench/reftests/text/shadow-red-ref.yaml b/third_party/webrender/wrench/reftests/text/shadow-red-ref.yaml new file mode 100644 index 00000000000..6c2f50fba01 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-red-ref.yaml @@ -0,0 +1,5 @@ +--- +root: + items: + - rect: [14, 10, 205, 38] + color: red diff --git a/third_party/webrender/wrench/reftests/text/shadow-red.yaml b/third_party/webrender/wrench/reftests/text/shadow-red.yaml new file mode 100644 index 00000000000..62df9fc39c4 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-red.yaml @@ -0,0 +1,19 @@ +--- +root: + items: + - rect: [14, 10, 205, 38] + color: red + - + type: "shadow" + blur-radius: 1 + color: red + bounds: [14, 10, 205, 38] + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 24 + color: [0,0,0,0] + - + type: "pop-all-shadows" + diff --git a/third_party/webrender/wrench/reftests/text/shadow-ref.yaml b/third_party/webrender/wrench/reftests/text/shadow-ref.yaml new file mode 100644 index 00000000000..efc8d2d9f0d --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-ref.yaml @@ -0,0 +1,25 @@ +--- +root: + items: + - # Compare non-blurred offset shadow to text with all glyphs offset that much + bounds: [16, 21, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [18, 46, 37.533333, 46, 53.533333, 46, 62.4, 46, 74.833336, 46, 82.833336, 46, 91.7, 46, 104.13333, 46, 112.13333, 46, 121, 46, 137, 46, 151.2, 46, 159.2, 46, 175.2, 46, 189.4, 46, 198.26666, 46] + size: 18 + color: black + font: "VeraBd.ttf" + - # Compare blurred offset shadow to shadow + type: "shadow" + bounds: [12, 314, 205, 35] + blur-radius: 5 + offset: [0, 0] + color: red + - + bounds: [14, 318, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [14, 339, 33.533333, 339, 49.533333, 339, 58.4, 339, 70.833336, 339, 78.833336, 339, 87.7, 339, 100.13333, 339, 108.13333, 339, 117, 339, 133, 339, 147.2, 339, 155.2, 339, 171.2, 339, 185.4, 339, 194.26666, 339] + size: 18 + color: [0, 0, 0, 0] # actual text is transparent + font: "VeraBd.ttf" + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/shadow-rotate.yaml b/third_party/webrender/wrench/reftests/text/shadow-rotate.yaml new file mode 100644 index 00000000000..e0ffc9ac03d --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-rotate.yaml @@ -0,0 +1,18 @@ +--- #checks that transformed text shadows can locate glyphs in the glyph cache +root: + items: + - type: stacking-context + bounds: [0, 0, 430, 330] + transform: rotate(30) + items: + - type: "shadow" + bounds: [0, 0, 430, 330] + blur-radius: 1 + offset: [0, 1] + color: blue + - text: "a Bcd Efgh Ijklm Nopqrs Tuvwxyz" + origin: 50 200 + size: 20 + font: "FreeSans.ttf" + - type: "pop-all-shadows" + diff --git a/third_party/webrender/wrench/reftests/text/shadow-single.yaml b/third_party/webrender/wrench/reftests/text/shadow-single.yaml new file mode 100644 index 00000000000..1804a073ff0 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-single.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - + type: "shadow" + blur-radius: 5 + bounds: [14, 18, 205, 35] + offset: [0, 0] + color: black + - + bounds: [14, 18, 205, 35] + glyphs: [55] + offsets: [16, 43] + size: 18 + color: [0, 0, 0, 0] # actual text is transparent + font: "VeraBd.ttf" + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/shadow-solid-ref.yaml b/third_party/webrender/wrench/reftests/text/shadow-solid-ref.yaml new file mode 100644 index 00000000000..264b1e10962 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-solid-ref.yaml @@ -0,0 +1,16 @@ +--- +root: + items: + - + type: "shadow" + blur-radius: 25 + bounds: [0, 0, 90, 90] + offset: [0, 0] + color: [0, 0, 0, 0.8] + - + bounds: [30, 30, 30, 30] + type: rect + style: solid + color: blue + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/shadow-transforms.png b/third_party/webrender/wrench/reftests/text/shadow-transforms.png new file mode 100644 index 0000000000000000000000000000000000000000..71d97bff4c8708e302f7e853e41be461c1cf4b1c GIT binary patch literal 96541 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/text/shadow-transforms.yaml b/third_party/webrender/wrench/reftests/text/shadow-transforms.yaml new file mode 100644 index 00000000000..7a75133dab4 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow-transforms.yaml @@ -0,0 +1,58 @@ +# Various tests for rotated text shadows, such as +# blur radius, offset, shadow color. +root: + items: + - type: stacking-context + transform: rotate(-30) + transform-origin: 80 80 + items: + - + type: "shadow" + bounds: [0, 0, 350, 100] + blur-radius: 5 + color: [255, 0, 0, 1] + - text: "A red shadow" + origin: 50 40 + size: 20 + font: "FreeSans.ttf" + - + type: "pop-all-shadows" + + - + type: "shadow" + bounds: [100, 100, 350, 100] + blur-radius: 2 + offset: 10 10 + color: [0, 255, 0, 0.5] + - text: "Red text, green shadow" + origin: 150 140 + size: 20 + font: "FreeSans.ttf" + color: red + - + type: "pop-all-shadows" + + - type: stacking-context + perspective: 100 + perspective-origin: 650 100 + items: + - type: "stacking-context" + transform-origin: 235 235 + transform: rotate-x(-15) + items: + - + type: "shadow" + blur-radius: 5 + color: [255, 0, 0, 1] + offset: [0, 20] + - + type: "shadow" + blur-radius: 0 + color: [0, 0, 255, 1] + offset: [0, -20] + - text: "PERSPECTIVE!!!" + origin: 500 100 + size: 100 + font: "FreeSans.ttf" + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/shadow.yaml b/third_party/webrender/wrench/reftests/text/shadow.yaml new file mode 100644 index 00000000000..56ffdde93b1 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/shadow.yaml @@ -0,0 +1,33 @@ +--- +root: + items: + - + type: "shadow" + bounds: [14, 18, 205, 35] + blur-radius: 0 + offset: [2, 3] + color: black + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 18 + color: [0, 0, 0, 0] # actual text is transparent + font: "VeraBd.ttf" + - + type: "pop-all-shadows" + - + type: "shadow" + bounds: [12, 314, 205, 35] + blur-radius: 5 + offset: [-2, -4] + color: red + - + bounds: [14, 318, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 343, 35.533333, 343, 51.533333, 343, 60.4, 343, 72.833336, 343, 80.833336, 343, 89.7, 343, 102.13333, 343, 110.13333, 343, 119, 343, 135, 343, 149.2, 343, 157.2, 343, 173.2, 343, 187.4, 343, 196.26666, 343] + size: 18 + color: [0, 0, 0, 0] # actual text is transparent + font: "VeraBd.ttf" + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/snap-clip-ref.yaml b/third_party/webrender/wrench/reftests/text/snap-clip-ref.yaml new file mode 100644 index 00000000000..676b7c80c57 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/snap-clip-ref.yaml @@ -0,0 +1,7 @@ +root: + items: + - bounds: [0, 0, 35, 35] + glyphs: [50] + offsets: [10, 30] + size: 20 + font: "FreeSans.ttf" diff --git a/third_party/webrender/wrench/reftests/text/snap-clip.yaml b/third_party/webrender/wrench/reftests/text/snap-clip.yaml new file mode 100644 index 00000000000..6ee30aa09b2 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/snap-clip.yaml @@ -0,0 +1,8 @@ +root: + items: + - bounds: [0, 0, 35, 35] + glyphs: [50] + offsets: [10.3, 30] + clip-rect: [0, 0, 29.7, 35] + size: 20 + font: "FreeSans.ttf" diff --git a/third_party/webrender/wrench/reftests/text/snap-text-offset-ref.yaml b/third_party/webrender/wrench/reftests/text/snap-text-offset-ref.yaml new file mode 100644 index 00000000000..51b6a8c7c9b --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/snap-text-offset-ref.yaml @@ -0,0 +1,11 @@ +root: + items: + - type: stacking-context + bounds: [0, 0, 200, 100] + transform: translate(0.5, 0.5) + items: + - bounds: [0, 0, 200, 100] + glyphs: [68, 3, 37, 70, 71, 3, 40, 73, 74, 75] + offsets: [20.3, 50, 35, 50, 43, 50.49, 61.2, 50, 75.4, 50, 90.6, 50, 98.8, 50, 116.7, 50, 124.1, 50, 139.5, 50] + size: 20 + font: "FreeSans.ttf" diff --git a/third_party/webrender/wrench/reftests/text/snap-text-offset.yaml b/third_party/webrender/wrench/reftests/text/snap-text-offset.yaml new file mode 100644 index 00000000000..df8c3680d8f --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/snap-text-offset.yaml @@ -0,0 +1,10 @@ +root: + items: + - type: stacking-context + bounds: [0.5, 0.5, 200, 100] + items: + - bounds: [0, 0, 200, 100] + glyphs: [68, 3, 37, 70, 71, 3, 40, 73, 74, 75] + offsets: [20.3, 50, 35, 50, 43, 50.49, 61.2, 50, 75.4, 50, 90.6, 50, 98.8, 50, 116.7, 50, 124.1, 50, 139.5, 50] + size: 20 + font: "FreeSans.ttf" diff --git a/third_party/webrender/wrench/reftests/text/split-batch-ref.yaml b/third_party/webrender/wrench/reftests/text/split-batch-ref.yaml new file mode 100644 index 00000000000..687b051607e --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/split-batch-ref.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - + type: "shadow" + bounds: [0, 0, 650, 670] + blur-radius: 2 + offset: [10, 10] + color: black + - + bounds: [0, 0, 650, 670] + glyphs: [55, 45] + offsets: [20, 500, 400, 500] + size: 500 + color: [255, 0, 0, 1] + font: "VeraBd.ttf" + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/split-batch.yaml b/third_party/webrender/wrench/reftests/text/split-batch.yaml new file mode 100644 index 00000000000..687b051607e --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/split-batch.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - + type: "shadow" + bounds: [0, 0, 650, 670] + blur-radius: 2 + offset: [10, 10] + color: black + - + bounds: [0, 0, 650, 670] + glyphs: [55, 45] + offsets: [20, 500, 400, 500] + size: 500 + color: [255, 0, 0, 1] + font: "VeraBd.ttf" + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/subpixel-rotate.png b/third_party/webrender/wrench/reftests/text/subpixel-rotate.png new file mode 100644 index 0000000000000000000000000000000000000000..2bff307a8ed55c3c9a82113121b475248102bcd7 GIT binary patch literal 19021 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∠T?CR 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/text/subpixel-rotate.yaml b/third_party/webrender/wrench/reftests/text/subpixel-rotate.yaml new file mode 100644 index 00000000000..296afb0d28d --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/subpixel-rotate.yaml @@ -0,0 +1,10 @@ +root: + items: + - type: stacking-context + bounds: [0, 0, 430, 330] + transform: rotate(-30) + items: + - text: "a Bcd Efgh Ijklm Nopqrs Tuvwxyz" + origin: 50 200 + size: 20 + font: "FreeSans.ttf" diff --git a/third_party/webrender/wrench/reftests/text/subpixel-scale.png b/third_party/webrender/wrench/reftests/text/subpixel-scale.png new file mode 100644 index 0000000000000000000000000000000000000000..f3c245c25ac112636ff41c28f6ca11013d2bd9ee GIT binary patch literal 14063 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 E4-tD4Q~&?~ literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/text/subpixel-scale.yaml b/third_party/webrender/wrench/reftests/text/subpixel-scale.yaml new file mode 100644 index 00000000000..84e7b72bfdb --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/subpixel-scale.yaml @@ -0,0 +1,10 @@ +root: + items: + - type: stacking-context + bounds: [0, 0, 680, 80] + transform: scale-x(1.5) + items: + - text: "a Bcd Efgh Ijklm Nopqrs Tuvwxyz" + origin: 20 50 + size: 20 + font: "FreeSans.ttf" diff --git a/third_party/webrender/wrench/reftests/text/subpixel-skew.png b/third_party/webrender/wrench/reftests/text/subpixel-skew.png new file mode 100644 index 0000000000000000000000000000000000000000..3e713f16711ae7ed48b014c7a8d23d01cccf2ee2 GIT binary patch literal 15337 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 P{UR-{AXX*v`|tk&uC@>w literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/text/subpixel-skew.yaml b/third_party/webrender/wrench/reftests/text/subpixel-skew.yaml new file mode 100644 index 00000000000..7d06f722b27 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/subpixel-skew.yaml @@ -0,0 +1,10 @@ +root: + items: + - type: stacking-context + bounds: [0, 0, 480, 80] + transform: skew-x(30) + items: + - text: "a Bcd Efgh Ijklm Nopqrs Tuvwxyz" + origin: 20 50 + size: 20 + font: "FreeSans.ttf" diff --git a/third_party/webrender/wrench/reftests/text/subpixel-translate-ref.yaml b/third_party/webrender/wrench/reftests/text/subpixel-translate-ref.yaml new file mode 100644 index 00000000000..a4377ee76d3 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/subpixel-translate-ref.yaml @@ -0,0 +1,12 @@ +root: + items: + - type: stacking-context + bounds: [0, 0, 500, 500] + items: + - + bounds: [0, 0, 500, 500] + glyphs: [80, 80, 80, 80, 80] + offsets: [50, 100, 71.111, 100, 92.222, 100, 113.333, 100, 134.444, 100] + origin: 0 0 + size: 20 + font: "FreeSans.ttf" diff --git a/third_party/webrender/wrench/reftests/text/subpixel-translate.yaml b/third_party/webrender/wrench/reftests/text/subpixel-translate.yaml new file mode 100644 index 00000000000..940dc6fef7f --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/subpixel-translate.yaml @@ -0,0 +1,13 @@ +root: + items: + - type: stacking-context + bounds: [0, 0, 500, 500] + transform: translate(0.444, 0) + items: + - + bounds: [0, 0, 500, 500] + glyphs: [80, 80, 80, 80, 80] + offsets: [50, 100, 71.111, 100, 92.222, 100, 113.333, 100, 134.444, 100] + origin: 0 0 + size: 20 + font: "FreeSans.ttf" diff --git a/third_party/webrender/wrench/reftests/text/subtle-shadow-ref.yaml b/third_party/webrender/wrench/reftests/text/subtle-shadow-ref.yaml new file mode 100644 index 00000000000..54374e11911 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/subtle-shadow-ref.yaml @@ -0,0 +1,27 @@ +--- # incredibly faint shadows showing up as opaque black?? +root: + items: + - type: rect + bounds: [0, 0, 255, 85] + color: [245, 100, 100, 1.0] + - + type: "shadow" + bounds: [14, 18, 205, 35] + blur-radius: 0 + offset: [0, 1] + color: [0.0,0.0,0.0, 0.101961] + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 12 + color: [168, 168, 168, 0.0] + font: "VeraBd.ttf" + - + type: "pop-all-shadows" + - bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 12 + color: [168, 168, 168, 1.0] + font: "VeraBd.ttf" diff --git a/third_party/webrender/wrench/reftests/text/subtle-shadow.yaml b/third_party/webrender/wrench/reftests/text/subtle-shadow.yaml new file mode 100644 index 00000000000..67d19e81fd2 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/subtle-shadow.yaml @@ -0,0 +1,21 @@ +--- +root: + items: + - type: rect + bounds: [0, 0, 255, 85] + color: [245, 100, 100, 1.0] + - + type: "shadow" + bounds: [14, 18, 205, 35] + blur-radius: 0 + offset: [0, 1] + color: [0.0,0.0,0.0, 0.101961] + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 12 + color: [168, 168, 168, 1.0] + font: "VeraBd.ttf" + - + type: "pop-all-shadows" diff --git a/third_party/webrender/wrench/reftests/text/synthetic-bold-not-ref.yaml b/third_party/webrender/wrench/reftests/text/synthetic-bold-not-ref.yaml new file mode 100644 index 00000000000..b947bf45979 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/synthetic-bold-not-ref.yaml @@ -0,0 +1,5 @@ +root: + items: + - text: "Fake bold is great" + origin: 20 40 + size: 20 diff --git a/third_party/webrender/wrench/reftests/text/synthetic-bold-transparent-ref.yaml b/third_party/webrender/wrench/reftests/text/synthetic-bold-transparent-ref.yaml new file mode 100644 index 00000000000..c7db0a34263 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/synthetic-bold-transparent-ref.yaml @@ -0,0 +1,10 @@ +root: + items: + - type: stacking-context + bounds: [0, 0, 660, 210] + filters: opacity(0.5) + items: + - text: "Fake bold is great" + origin: 20 40 + size: 20 + synthetic-bold: true diff --git a/third_party/webrender/wrench/reftests/text/synthetic-bold-transparent.yaml b/third_party/webrender/wrench/reftests/text/synthetic-bold-transparent.yaml new file mode 100644 index 00000000000..043a6da781d --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/synthetic-bold-transparent.yaml @@ -0,0 +1,7 @@ +root: + items: + - text: "Fake bold is great" + origin: 20 40 + size: 20 + color: [0, 0, 0, 0.5] + synthetic-bold: true diff --git a/third_party/webrender/wrench/reftests/text/synthetic-bold.yaml b/third_party/webrender/wrench/reftests/text/synthetic-bold.yaml new file mode 100644 index 00000000000..8181dbb84ec --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/synthetic-bold.yaml @@ -0,0 +1,6 @@ +root: + items: + - text: "Fake bold is great" + origin: 20 40 + size: 20 + synthetic-bold: true diff --git a/third_party/webrender/wrench/reftests/text/synthetic-italics-custom.yaml b/third_party/webrender/wrench/reftests/text/synthetic-italics-custom.yaml new file mode 100644 index 00000000000..00f30a776f7 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/synthetic-italics-custom.yaml @@ -0,0 +1,7 @@ +root: + items: + - text: "Fake italics are great" + origin: 20 40 + size: 20 + synthetic-italics: 45 + diff --git a/third_party/webrender/wrench/reftests/text/synthetic-italics-ref.yaml b/third_party/webrender/wrench/reftests/text/synthetic-italics-ref.yaml new file mode 100644 index 00000000000..5c5be707f7b --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/synthetic-italics-ref.yaml @@ -0,0 +1,5 @@ +root: + items: + - text: "Fake italics are great" + origin: 20 40 + size: 20 diff --git a/third_party/webrender/wrench/reftests/text/synthetic-italics.yaml b/third_party/webrender/wrench/reftests/text/synthetic-italics.yaml new file mode 100644 index 00000000000..fa9b78a3f01 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/synthetic-italics.yaml @@ -0,0 +1,7 @@ +root: + items: + - text: "Fake italics are great" + origin: 20 40 + size: 20 + synthetic-italics: true + diff --git a/third_party/webrender/wrench/reftests/text/text-fixed-slice-fast.png b/third_party/webrender/wrench/reftests/text/text-fixed-slice-fast.png new file mode 100644 index 0000000000000000000000000000000000000000..7f3ee0ec9d32ee54f212408d767153d754c36dd5 GIT binary patch literal 5025 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/text/text-fixed-slice-slow.png b/third_party/webrender/wrench/reftests/text/text-fixed-slice-slow.png new file mode 100644 index 0000000000000000000000000000000000000000..4eaa7fc3ad5f3cf382b4e6bb4c70a9239cf49c20 GIT binary patch literal 6024 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/text/text-fixed-slice.yaml b/third_party/webrender/wrench/reftests/text/text-fixed-slice.yaml new file mode 100644 index 00000000000..902b7c83a75 --- /dev/null +++ b/third_party/webrender/wrench/reftests/text/text-fixed-slice.yaml @@ -0,0 +1,20 @@ +# Verify that the option to configure performance / quality settings for +# subpixel AA with picture caching is respected. +root: + items: + - type: scroll-frame + bounds: [0, 0, 500, 200] + content-size: [500, 500] + id: 2 + items: + - type: rect + bounds: [0, 0, 500, 200] + color: white + - type: rect + bounds: [0, 0, 500, 200] + color: white + - text: "The sun has frightened off the night!" + origin: 20 40 + size: 20 + font: "FreeSans.ttf" + clip-and-scroll: [2, 2] diff --git a/third_party/webrender/wrench/reftests/text/text-masking-alpha.png b/third_party/webrender/wrench/reftests/text/text-masking-alpha.png new file mode 100644 index 0000000000000000000000000000000000000000..1f02f8bac5df21ca29749e539b85bfd2b9d1e630 GIT binary patch literal 18885 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/text/text-masking-mask.png b/third_party/webrender/wrench/reftests/text/text-masking-mask.png new file mode 100644 index 0000000000000000000000000000000000000000..a27a494849b3998f72c7f711c963077873446ffb GIT binary patch literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^UO+6v0VEh6=ajhvDGyH<$B>FSZ_ga$WKa-cx^Sz0 z&N1yp0zopr0G5I+9smFU literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/text/text-masking-subpx.png b/third_party/webrender/wrench/reftests/text/text-masking-subpx.png new file mode 100644 index 0000000000000000000000000000000000000000..08db66a537615a6d7ff8b8bc849349fd6a928a36 GIT binary patch literal 28989 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

  • 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 G@P7eLV5{)} literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/filters/filter-small-blur-radius.yaml b/third_party/webrender/wrench/reftests/filters/filter-small-blur-radius.yaml new file mode 100644 index 00000000000..7ebf95545de --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/filter-small-blur-radius.yaml @@ -0,0 +1,10 @@ +--- +root: + items: + - type: stacking-context + bounds: 100 100 512 512 + filters: blur(2) + items: + - type: rect + bounds: 0 0 512 512 + color: red diff --git a/third_party/webrender/wrench/reftests/filters/firefox.png b/third_party/webrender/wrench/reftests/filters/firefox.png new file mode 100644 index 0000000000000000000000000000000000000000..696ba9c9b54c49de87f23c4219a7a26c5e1b750c GIT binary patch literal 45376 zcmce7Wmg-_^LB6u?i7kcu>!@4ySuvtX`x63^%1W(XB_wU93 zD?EGloSAoXW@mQx8rdi<4MkiaB@h4r;3_N0=>Pyo|FK8_O!WVVnQNu(e+1P{Mp+N@ zzvYMdDG~sn11QT$>v`p#^?G(NDSE&VRVNo&oi1*R4xX(t#!A*{Qb?bq0{crdVo@w( zdEjVHZyQ0YViY$+LR5!=am{fqfRGPlqv)Xo$pCo+tJW2Bd$%4d+pe0E=hw>Wi~LSE zD3D-|E5BJ2>JD><s! z27y-&?#MxRvGa8u_uh`vBGBJ4>9s~YA|fBzRUA23cT9xUMXQFG(&3i5E%YYOezy?Wr6eK7d+1)w4I#Yg0T)kF{OmtblkQBnK zAI-1*g}FHIH$HT;88(G@&PdpJ(FDInJkhQ_-Z`Jw;q0%{|EEFluM@s$$kxshVp+4m z^Uf`wPq_1o_C@McZHeRCw(@fw8SnaDuCrJbF6kN$;-a35?iG0z#%;kC;2~QWkUY8w zz(6lo*i$YDq%4!#9YPo1QdS}1PeCoq?dTEj-aYl)co>m<>G!35ey(^m&-Kl{9M>XC zE<*A`{=9$CJl%O4CwbLunt!*>J#JD`(kbf^ThfpQYuh0p(Lf~^gBZGmVFTI9V(_Ec zUliz~fn!KIp2s?U6~P0M?4Z2k?EqAqMJ#20BA~m`65Jy%@WQvJX*dGcx@$cT-|r=# z3XJH5G`zSif&ISV-5y*-c5w=-bYNcAW@F-loqsMqRBH*#Zx({I$FeQ6mg_ zgSO6leN^`7tBCpcZw84nGIM3)pA%O;n+yh zUr0Q(S2op#+BjC4VVjQ+)8P9T$=Agp(YAF&=EhmkTf=blEth270@snW&ATbs z%V-w4lefyZw0wc|Z*yF zi!b0W;hL;j64Fj{e)KAXxcf&#pK$$Ed~2Wfiko;a4r}deIxja%8*o8g8H$HDi8z z8`MIK#(u`JV9exY?NHZwQ9o3T2gDZOO?03YkR?<`CY?dKnglW`UEb<6ixc9qGP9Qb znQtLV4kRGj?*$ioc?5sT@^E!W4)%o{zdgw-C(~ zilSVsoW?@VhdZ_R5Yis7eY1pk&an{n{SM7{J~w1S)~hyS?P^<{Zo5Q%WH)TSWq?XyHeb;e}STq>zd^IQZbW)?HD8Vw(8@KzSWt6#Be z*ewbeJw9o@zH|v+(uWZZgb8ilg|SLZ8c8yqwM|h0t}5xYfwkgMnP23C8D`<RC z6)Qei%+?N}qHjjRUc+=KkYbivl^&%dZ%*UiU`|*l*U1;F>+Tql%uB>cfHn{{^ z5Fu_~sI?G5qWg-*bWix4iQVE%yysuw_hiN`foPstaTrCkc&wQ`-sYOU|vi%81(Brt_>m2Q? z@d_oi_*~lr$tZ{a<$W93DHT$Q-Uon01~ipDZA12}q0OGy-^HYkgwI{lNq%@18KF7s zh?;0mIn=V&LtH&lUadZS9!mnY7cqvqQH;NR4WZw|`ZZHMut=Th)eOcBDZ^*l4| z?>~nJZoRZBA}h190>9ABM$0)i4LjSJSaqLIExn#|BX%I*JC;peCTxhne+G8eNp}|6 z{b~4mAnE6~e!%ioVy*4%Ipxnric``JCH038r0;k@H1BLL6u@WtgwrQB6$NUQu!TF4 z9GxSbB0b}2TAP5f8d|}oSCw7a2snnroT<2xH=;wQ{4EO6cO^R3DvOZq?*b*(z}+Fd zBq@NwCMP>YfhQZ7!OeGeUV4_P?vGiB!d*xzqOR1+fE`|x_N7ZVBEkOzMe-ruSMtG= z*5`e|JR9dp80oT}NGFp>C9Z6bmx)5~IZC4+xbn|(A{eHa!fM5@-?Q&D zl1>_J3S(aL0$hkPNQfyhQYwuH7wb7vn(S@e*TP=^B=qfcl_ZF@oUgt-fh&H`0Q^u~ zS9Bq#!kbT~V3>BcP03TaTbH-ARc{3>e=|)D;%1pU5@lGZ^U#c1BA^g<()a!RXh2^J z$6OFl7Ho*)6U`oDPeiwn_J>;>EL0zhKU}d}?IT&uZ^4A`uqLJFHM~P2R55N6Cc_lU zlg5*{QiF=JGbq5U#}6zVF8aAS{xcD?6zMmUNr$_9NWlv4_;ph3%hzo;{6oKkq+tKA ziplSoq6Cqn3_h9omKeETuS#53fZ?5+&spvMrnJG(5C6H0B7NNA(Gf?$Q-OrXg>HSH zH8fUxOax8ds$uHCka=}1e)wC0Y8&DOhYmMCuPZ3jrFn*`Sn0ptyP7f2xbkJK!0n>4 z*-_aJ;puNaNy49DXF~OuIFBfPyN|4XWD~B0oG#giJp4e4_1qVFy5NAPwb9z$BxY>& zeQzp3?o22GH?m(_|HCLj5zvE)jC0P3TtYk&-T`_z>z^|i)O0c))OXA&n~#HbG^?%W8)Liwg#Iym7fbd>aaEYXTWU65 zXLF!N?k5M6q6t41+*w$--+NoJsTutoT85vn{w!+s3v-$A>5l6A*8XHdoQSXV|}US#L}&Wb)#?<%#vQzvTD zn^hA~Yo-ThpuRo%IVtLWHU#|~TxQQ*;$LaLc}}*kwK@1bj4wDpWz?@j zj&~}|8+X4eQac{HyAk*F6@Ae0c^wo1GEz7q(*`P{n9Qpa;S8*fEp0vY2;N3-xoy!D zD;lgv2@%p2vh2t8f%p6T9!7M9CvDN7YL)(M4)~rkIas-ldJ|S3(Po5v6T` zmA?2*)T?AdQ?K{`bTr^;JUOyR@oC3~r{=rF*I^5wdgV)zFXD4*@GnxX@R>(z#Pj9* zi7mGJoeXf07XO;J%>(hSI4d}Lub_0ws z_oSf?M@88e9RxgYF{^G~`EGXK!XN=(_VPu4ynQ)ocdd=fuy8wHeci?eUD(>BbmL9V zP2sMuX=Mhl83Bpu`Wh)%G6$_y?l&{;4oq}Qu@u;Fh*Bj=kx~9~V|xs?F%B;@n>$tX zJH)Y)(zP3YgizafJL6O??1Wsi1?Kx~M=ukDgtq>;4cSGKifvH9DPK2Kh6%Jlr zJ>BigIrDqwiq^Cf?X}dKn0D7btJfJzj;n1i|LE8y%)soLb;`Z@nbFh4)91cFmE#PT zd(H#7Yu}KU&CYu#g!=gW1? z=F=0n!M9rJp8D%NCyu;DB9*MR(Dk7A2)EM*WIUU(W0j4w{LMb={pqzefYbH2s3gFIL+cfoY>V-fv3swC)OGc0o{;)b_bs_QZ@i<&-e2e zAhF*abe1{)3N~91u)bNxBPQj(!-RUsRWdo`eqr@~x{`68`g%}{WMFrRbYn5&LkEk& zvvT0?Q7J3>;a+9!L<+@ZDlJ(c@)cFQwv$+n4X2xV%78VO8=yz zU%aFN(kM`>)`K&T!o=+hX+2r2+^1K+w0v&dnWEf`>jjQx_96*njZUcxHYc@*u*6aJ z^I5z`p-LbXgGi*je3p)yEt_$=rCWjVRjwRn+}v;k@<8$O;P=g;G58?YShEJL71OZ~ z(-vN!Ly!Z{Kedj>RPDvscWbQ>+3rjGj0KO-Ch)pHC11B8q(z@=9V^8P$J!Gf-Bw>uyccL(?@Gn{V5*r)$vI*`x@53cG z4|wS9PT&!kFUlde;@R_-ZvU|7ZRQ7T_{CYy`e76e%1uE&7^H>u1)1#&r;($-0UZT4 z4<0at4{x1E$j;wiDjQXnNW9jN5`RG7*pk@o1P|n=%VrXIGHb3lO;e1_NiTA#2aa0h zz=R@K9eShf2HUaVm=Q$`@Lg!=h4~B~w#o?a%o6b;5yd0r-q(^ z^f+ePA0F*H8bF>kC2m@MN2l`8k1m|5mi8)M;pDGO-TN(fenZpKzqwchkl`7EKM`xg zk+G3?w_QIzKAlq~^lggpBpf{dPIJ_Jm<2<22i&is^;hpT=6}0E&#_gtg@866cqZ-q zs=0l@9Ch!bt441l6liofVyjxOnv$M@7BipNf(zYbH=Hczndt6?@+JPFDNP(23ZQIs zpEf`YENY~pE~v>1_5S5^=%Bf!^G<1P6h@*BF%D=%!fr7&j z!k2qldr|d56pA}O-LdAp+>5um^){^~Yu{7h5uXrOeI+u6Lj;_@_AwHojF8a;WKi?BiX zoUSXz(RhDBkjQ@%I}?-`a|X&<`S&=h>G@GrKeKl@=cSHtH!}Uui1YK?a=hwM*6oTz z$D7eOivQ@N3a{LOs${&habf!vzgC~Ttf-o^SJaQ6=fCJxiK)u1HTwPIfJlEoa-~Zl zh{+D#DQU3gfg`i9Shh-PntgsNC6h8I<@~rE7AZuuJ0sM@WYv?kojWs$UBIc_%Kd3v zJxd|?+(hDsXT%>tjRuFFkVtuDj0uey8~V?fj-!0}0pk>+@jtx1*dtn!&VemHFF6!585}t7US$-+o16pJdBOID9%f}qIwl4V#WQSOFd>OHM zh%wL#!W26X^7s>*+k15rd~qPIE8q6C#qX`HwP*1VlX${6$`L@Aw&@M0!8R){&L}mk zd`3}$f)=MDwp%nfqTiRzJ0uF~=F(EB&Z>!(8isO-SQII;Hk3L|{BYC1*ROL5h*LH3 zZ01>)l{Lm&VeX5>yc{jkIFXk(>{Zb~gPoqnZv<15)A_C0IV5h3PAGivdN&Fa%0+e6 zs%qk$BaBt#93n_bC6XcBuTI||7a@@J^D0f6Q6d&eqE+H&!jhXKIHDYbtj``?tH&7g zNf7tNNk`|sLYX)KU&^vMG~SeyZ9Dqf@u=KfKE~L0qn~>Ehglo&HMf?5?riUZ+pG@C zS7WFkzZGvwlTw-pj-VE;uk%Z}8-BLg#b)PhZOHP1NpX+>XWP7G>BD^20p0r7ZAr*0 zpD12y%8R0{N|_*Y4anP<>g1_6bOfLJH&4W%!FQ^fiT167~uadfXU0 zs1oPdFO>xlaZ@H|Sc#s_pE)^N!sUseRcN5lmi zZD5gqVE1>4-UBy><@~4{Pohrm&*a znHkLiYDbqgY{?*OXbp=}txJzN7(~r?J%m-YN5g4pfsUQ8`N59UCWAYRjn_F*;-O6# zbfRfrPz_?s%^JU@cEj3*XI$Qj?fb=7X)j88apW;(Ur0~JSfuzbZODGx{Azk*+V}79 zPA(&53h+FIz4&^U#nI$8>=WkMdO|#1$1!ETwtO9C-i+w|K5=iT0Y9LrPez`x~zigxvOFx=d-q^1$tNFV;yHglGYOI1ZdBD6z^ zqMxzInO5bB>Bp5QBkh)54tTVDkI)^3_bXDED`l=i1y*%Z;aq4=_u+N-n1zC2Q})i% zI6$v;9UD<|r}!CAIT+aF(|GVgO}a@4dozF^Q?BC&j#mT-NegdkO}Gh85eJ$u+GG0v zluZu(+gY726sm>HKNp&0JL<#&gLq~a{|vl)NfYVZf2!(KQrk%qb z?u%l~I;g(eJrkMRF0w(3SS)7z^$uRR@?eBNnVbfIJ7w51;P%m`b`qbtQr$EV7 z=S^T#xWz5UQte|139kzfYjhKhrvFXYAJXdY#~f2e&nN|(>kkR*^CNX-R8vB*TJTe6 zPCK;VzCn^TKwB=En*^S!26oyav@sem!8+^1uGh!)yM0c6*YW=5bAyzz6fMmLQ$tXS z1EutpZXEtiHzQ;)I1-f0vFD9mSWNTSDWMN@94J0+pN&@2wvZ5d@Q+K}SlwegqQLAIxe z9FyZ1&Bk5v4r(P+7EpO>lz?j69Zq#I-*X!rYs(v>go&|cmAa1d?_ds-$>i#<$8O6_ zurHpwP|K@XbdTl2l&J7?pzTPf0m$=V`3CWT&4+-mQ@sPNoBf4CR`JuROZoe@VF&YLdZ->rLo=?Z>H zI^+1M2pBbR0pDJ@eAnbhE!#1?Eck*tM`Hg;X{Y3CP<1ByFA=m7-R2&vGn=0JJH?)l zFR0u~eUU^F--5qwfpKf3iQLPz)%SC}StoH7xp(eN!J)@*!ty40%y+d13WYl>fAt4I zCF_wA148IplYDhV>$pjdu&{h^g9tYT1N$*p$xSk27$&ik2a`SAQr$Y_BJj08i{~y{ zY}ax^RDZha0pFRtS(o~&lI-Jd<1FcoPi%hqAJ zz{;pR@YUDtKJW3<-&>yaixripLhJL+YVY62wG{!|s_Wd(k1>XbsFjWuw?BCi-vZ9^ z&sa1x*ikNn4r?D#c$lWbH3T;F>CvZADD+J+aE|#PPb1E~-d7{PFG9vxuaQ$QmT5Dz zRvd@ZfzTp$GC6#NFszb)zdZZ;(y1cwSn8q>fZSV`dJzXRh( zTq*g-JE|}Mv}z0d(kf<4BfB-+j+}ChuXOoLmh!Gt9%(9xge>Eo{$mR8nuB_jYxo)B zRoEp_$wyo;$LBz&*bSHd2 zy;rzf(CpXp5Psr1>;rL4XxwSD$5Z^WIQc}Z*zUL^3%AWb-kY-ev!|W*dpXG~*Exzn z7EZ_fn{dvU4O=(p1L1RnX+Kl82_Fcy}{t+O=XC_xq3O2PWh_ z5Dr}*o_pA3vyLB@3nbFHnYosbNxfRiY$Vte8j&&ic1KoqbDEUV6aL)mr2}f0(g~6) ziiK~ysGr}gk8>cz^Kg4?PZQ?T%b`d!zqC>q{~rDWE_Ch}WYEZdpt~mrtF5n2%L4=| zL8ed^)RM7!9EH!AGVF9-{*ou$Ip>Ag81NwVs;dI_$v6a!Nm1^YmWtRdY`CxI-yW!>1;Rlwk0DGz|w zZy+0Dyfm)irtom<{2pHrNagVP>xC?m3*91d=v4oMsPkSMsj1LU1@X0M+_n!z4T?R4 z-)9{=i7sNWk2$Z{qzDgTBN2T4zWNj&@UD@|Qu1B9ud>)A#2)^Lb#~&W0j3Vy{zgg> zHTj0Yxgh{Hq&0bpRM>>h+8&I3-9yhPXLal$B-jqGrM7UL)p<9ElFBM2%t{mZNw}25!E>_7 zD#>~waytftUv%1wl0y<AJq`w!1usxK^R7DhWf5 zOG0vo%RJ}%?ZJ>UGxgTs>zYVR04Hz`b>Zjo18oXq-}pDDnB(oo`g)C`7TMNI^-G<8 zuXNM6E!>dn8Iay!4%8RJvsI9R)v5ZJi)QGvGAsNI&Yd)_9C8{VMd`EunPe||e*}PZ zqJWzuvM89c@L9z#NHhIE{O9q-ah9IDDp!`|QzyN^C?h)K=OC@@)h3tU`Nz3>90A+e z>OB|53uZmv-m(n7#+R*gvPL7%MkyliIEwb&{}STvt&DMUNw2<&DSOB^vDR-Y= zx;Cx4eyC%3HJRIX|KUO=+NmV|Oa4)sYl3EP?Up{hhVA>{Pe;p^?XNm4t%KX3-~ixbi5A+wh~%;X#rP;QqnORSwT1hlD_LAC{A6m#-8E*uuy*Os zdc^AcJh3orgY);YNDvYltXQXknh-3{!6t8>7QLwqQPpL7#}0l7;aAcJ`Y^ZXV_!RC zw{@laL8!@7u_V049D+Fax`3>^>gXZocQ@UoYWDniscnuxB4=r+q=PgMsIDebwjc#P z^1EGbSy`$y57e7q+Fnquhk6WpH|5n#oafg3XowF{p!4gp9xJ}9Z1q` zdnwM~PRxeEAiwFLpjyES(E-d*Z{%F4P3R4A#Je3)RAP1t-9s5-U3nP+h^0UKHJ?G; z^%&yt?K@NZ2XnkBGQX|xC-|S7A5uy4zq;uX?MHAW(;EYBe-NC`hZrFe`3quAZXFWw zl;P_P68oT{YAlCcO0V~8@Ty7> zup?!MoaM!@#RqU+c}#g;+OD88;c0cxE#4oB?m|lJM@m%)EToVtg9-u`O1H4700{6p zKyTfj-A0RD;+EAX0anp=5vm}{a!R>L)-#5wH42M@_sd88izV_Kw}>f_l@iucQF83X zoiY_2pzS*|8>Q?GyuVl<^Ey<7ZpCBVr)0mC>+rj=e!YLXK=+N*c?hA`N~sTAM03Ll z?BIezeqWD#MT4$AaJ~DsaUrV~)X1l~f94st+lAny;&LqLa`b#EttJ#=lL-X*9E@*j z*xr`Lgh_^J;%3QsH%+6%xh=6}`~wdG!``1PIa?I07SOHE6~@>-Ld*FAhRwft)a0c~ z=8e{!zTV=c>qqd% z@Ro*8<{@~3T4OeJdKke(cYY>1dRCnUontBcs*C{EqXIFOduP{yA3yfYi_vKD-xz0cemDj54b|nH2bvRkqenpbqX_I zTpqc)-!|K)x~1o%v}f5cw|`zI)ye`H4v2G96dnt7;$Q?L!H=K7nTjHR8sk#$ca+q$ zb)U0Y@M*^~7OHiEPOBhW5x%LuiAb5<&UHwUr#I{Y>$@4~{j=HU$p>WWdI5$XnP&$u zzmPX`1yMy3FsZ#%m@SV!AZcJl@^q~2bfJmjO?+=iahrUTG>Z>YV9R$sA*$=Oq;3~$ zNO&0&S?ig+^4AQ}i%;L!%wYD^J|Lv%jO;V0V;cW{HI65E1{&^d3cmRf@ULCOEm2La z0p3IUKC8oicEjKCe$#dAHX{L{S3u#aLlH>Z>-W&Ts;FfxHeeSQc&88k4_z*L4M|Nt zu~aoU^fuh{7a1E&S0_^|hDEfZSJeo2`rX7YmuymnHE5flv#x%{>d??(Z;9WqATEdC zo+7j3q8EPykgW0glR>&~$_h#ORNsyBpuEiV{tS;=)~1On8T{m2CUA`AwZ_n6|S^U0z@>Or#SsG_qyfCA%N&hb{z?wpt(~j%g zjpo?L`IYvVG)0>G8*vR!DZCdDyxYNRj^oOYF-->gweMQX?;jxtxNONjyn8QV}{RR54MS(49n98{N~qxZg6Jv`c-bmo}I2saqqD zlU}z-b?I6HXje0w2fMIp%OEG~W&GDs=f^buuSht9Xvo<+gd&h)HICm?CeYSCp3VK) z;y5<1DF1mSq%Vj!=Q~BmHhmm)^P;ke@v`fSB7^Ke(??4RA1p^rDa(96@{!MpapWVV z661)#3rF098)rGD|A?P>pewza)(%w!S^Bd6(G6&GET%1YP>GQmF5d2BF;Jcs13$U9 zpE7;0)C!2uK@US(LJ~xqY(ADo+P;e|qi$>w?qX923)HGQUTQzPl5BvS%wgq&?QR7-(i$E>cdT-8|>)rU-oyS7E ztV`o~VTa$CKrDJ1Z z_M6u)s7{0yC|Dh@IAf1|sAc-fAM~fS%KmYQF@1ifBo*sWIes|R(MT-{Ui951*qH|$ zs}5-sh4_Q40|#M$a51=Lh{C>>`A1Lpj89ALgQT>{b!PHy%*rlbmPGOiGn>_Mv2HI4 zpl&lT35LXXA4GO^lNU1_sT>IoS?owL*eCs14?+<=yMz)yPUF!EYGXduGZ^2KfzT)l z1BBg;a9rVxx2Ot@4RWDZ;oEjxzM<1%=TBKW>Ue9f4KTp(V}RL{IYzy4Ypy)R35qUV541 z9UFKcK^N3e+LIr7O!qe6nR9_^T|B#ZA^L3fdHf-c)o=PK0U1q6=-bq$YpY||$qnb; z3kC(%fq8{9qVTJh$@YxVE{01KX{?Ko=~gZ+x|(c;(pHAVqX3?o2U$S>Tw23k#0&Jy z*w&9QO5Ot+a-Vmrm%4y6*!joK`}at8Lhg%eIZS<4;sfpq6`(BcaUP}~;I%FJI{XLA z8L${H@G)~x)<5Nm5}@lPH$xfL%-tNTM&NKy%6@QG1)!43#Vh$4u3I>mvd;#PG*~CJ z_J1BCE9etC%Se1vdHgMkq3|(89KF)a5M}8`Hd=54)}E14G1KW2PA!R|UDxvo zWZh-WPEA0thzuf<=#Mu{aA=-vQ}8o(FmNAZ3XJ;Cl?RrPKloP`L%N>3{;9{&F2DB^ zp_?udOJeRN)7GCUv)gd}>Z{UYl)kMo^w5tY+i8$lOckhg4jVWhd_`lS*k+-8XHjd{ zmwIM^b1)Pu7E~Bx9Qf-Z200D0c!)_{Bos3VKN1JLMTnjh;BgOr96J#q~jiJNGdo{)&#m2P~*}Jc*@DzwTVY@{i|T{4qhd8VIOrIa;wLi z9t*=)5fi0kk}^`|66r;!q>sGGOjtIoe&KY=#KUB)vult;){Ji!11n`EH)2-EB#E_X zo;Y2w^l3Dek}r%tZ0zPb^r0)s4nyVp!jQW=4>8Yc`t&@1U?vq(j81X%kNKA+_4{x% z+G`dq4KLI!(@CFS&Th0%3EYHJorB4?ufMqvXbC%Uf$X#=O&v@UugrhzPmmZC66Y{) zuQ8Z|40VnJ6g5B5``5deAAraUdqwG$x6fgIk<25rG}Eh4WAbpsw^x{NA%vX+vye-7B7kx^$6z) z)`CdaK`MY_?}^E-@-A}?^`V88vMiUyxQ>OS%%2cX#N}U7BRzS%U|J|`FZ&Gfu4-q= z;9aA68}LbGXqNox4;|wSWSt!74r3+4t^JlE*##s2(qRn<4IqgY#CSCvuSrk@+*3p% zJ<-ThXw~w;pCx{QE@BQ-i{ubIZb?#1n4M zfAgec(|STQ(g_MBl586ZwHe?d^~xX6H-z!%`I(gb-Yh#p2K`geG6?qe@Ud?DKoB_r z9d{xE^T7B3kK;oBwS#lf4mr26BM>#$JVu7Nlv@jjm6BqGxL~#n1DGpVN=FilWccuJ`-p4!Qe&DH@W3Kl5niZJn+z$zt9og}JP^ zkw!SH&axemB>y!ej_>4}E_h&=-utMtzs#xnTfYS7^T{VmWdapiHj2#*i|&PH$bH|1 zx2BKM@B>!=W5BU+dKa%OZBN2dCG-?_@xJo;?mL6V%+7r8>(A%^%z(|=ekug&_7(l4R<^s@-KP5_@CxH1ls^D?7cP@|?b(Mdo*nw3{CUSbfRI_y~JM#m+Pv5ouRB z{3^TkE`XUf7wr=0Y$qGJUFyV3-v|v1{0JnH-+8|~XFKPvWl}OSq+7;sQo*YIrEDO~ zWh_R@yfQP(V9kB?za+gYBzn#oJ5X#*NnJ0-j95LCi+qG>4b}f17T*J*CD4FH49ReOBo;3 zQs-7Oh~9{sYfdW*Pqf^g1fiy$%oI*k?*TEx83&b*4WomA>+Kt!PW)5z+PPJfge&`TdrXos2L%rm~&{WZ5?eTnS^WFA>ygJXUB8v3)1I#`&Bh|-lN87 z3Q{Xy>pJdAK7;ybEG+x}%jEp}U37!LadB(KJUw*=E6QRn4h24>g3K~om|MJH^#MG_ zD1_Tn5qK_tUVM9t=j-7?Z&G^(ETOc*K^Dro%5{CQHHSS{sDH3x z03SIWa<9t&ux#qA&yRDc1g`R|)qL=Z+t=MPsg9M)p15bdTf>qJ-?0=@ue0wX` zBA93pls3*rI+&stF?}>bQDNlgPQH@m5I>j?ooAWNS&G*d-&7ud(mt>M)ww}Xh<9}? zi;8m3x%G0W``R&FCm&UP=l+Y=tbIr_;2Q>c!H-ePeOiLmn%S5{n*#K=<8P6(!reKS zKX%*$NIrBt!#fSDP9+{d*T20lZiyLK-Xp`?4Rc*>l2h*(=oX&8r;!%ox65LkQ2+Oz zVSl`i54j_fU`JNlZ7dB|--KQ*!tmB`m2mNM{>yfgfDJ){;R4T!udnHF(d14yH3lj{ zOu_TbF;#3ASI3(VV{dtaJo~gQlg>B1kp@9E@eFM z0us=X-g2jC)f2PA_`c%|3zj_hN&VW19icp3${Vu9CDBBhF{-sfILQX)A;*KFPKizF2j_{g(+2v$mLiTz}FM~Av$g+617;!pP{Xkkf z?cpu3XocJ2MMch%brv(QTZTyjW91n4tvY%vbeC$U5x`PV&*VBiLF3 zRmgGM5rHGbh5P{}~LizGUNLza&0)Avm9W6IiSl*_yF@+^_pII!Pds2o5t zU?ka7J}v`ZveXOq<9-XJ4eg*2K|)a(v+5SukkVDasyy+zDqwl6Nw&(IzQk#eY`s%y z*YoK4+qRpmB0N4z68xLA922F$b@Qg*ogNxZi)BAB6?`tjL~dr-Kpk-ktlMp8CO7-W zV@_W{C^8@5k`lwypU+H19^dTv%krXO3oSb65hQA!XLa{+7=AXbaP4PqyZU`LtwBM*z=Jj zRf{$=DfgCUjQ|W2T&}irYlFykBYQM{rNpOu5$g|R&JUT%L`_k>!x)4`DX2$OO}T&P z5<$viZvmjOnq_5^_=)n{`T4p#GU4Qrz03b0d=fZ#1}QMFnZ8%?5UcV*tv9qb9gG`hp(J4wSQVV_J zs}f~eC6TjQ_Gwk1$E5Ox+U`Y1?AP(2Y0>Cn6nVuYPt;?~Ma`cvY$WogM{PxXwO=%) zeSsqLGkAY~eb(IlTj0glO)D(r>(0|{nCEFWE%|}*114@N4MYar2-|!!LQ+GLDWFbZ zVwNqs%HFA4mWS_uTY^vB&H=c3JSB}9V%eM1yg6C(%%I~9Cg(-AN6{7f*6~8nD<(By zW>NU;u#^pIEIwl z?;X|Vv+MPbjNYe&?=ZA_1!OECh+aXh;A?_K53Iw-JP-(x&85@BETyGGHPXcVR~#{F zXP3-$to`g2&^ungIS-=?4zdc8T%?p{8Q5cCixQIXSS7Uh;q4d*qYXr55KQC>w-#Ks z2T}giQPK=@SC>K>%S=E;6@t2Z1SDYi2JeUR0=OlepIgTt`kYChLtc%!Rr=qy{Ee#a z8a*-b!Y}(Fie*AeVrc)}V8+D{49D5PoS=pn4+c~YhU9-QRC$=hMLF=D2B(i&2PyiU zfizr`!(K6@ujVJf|BZlLuYsn*2EK4j1bvaQ(OakdydP6AP>elg{ZF4f5YzK@j6751 zocF$7=ny^*ddlvdcF^+*(!etYnmj>w{lEdag{JDX=|)Qx@YHd&Oh&rbQ4@}2g7FR$ zs>?8X#Fb3QxYqE$eEnoPA*bZ4&hB#7Xy#AKme507G>0`nAkAGbN_5@^AW#wMA%qTt zyraG0n7>`xvBeD~8Z93nY=zT*%58Lgf5o#9scu}{q?DOfa{DYN!~HfxXqb6BZzJ=a z0xpwI9<9iO$RVjXWr|L_{$&5{sd64hC4G`Gt0jQFk_2-7>azK(sd{5ZSo75wyDN}M zKemp-W?%nsR^&78zhvgj<2QI4ei({3cU>aooiWz*J^*mZL4yiK#{ z)<6*uBq8D4^!NEG_f4eo-m>xUbq-%aGrgJ%~jHekF*%GPfH{a z8HJ4@Mwx8>?^CmRUv)EsXB)3B?Pz-mE8{)fHrEpLz9qeIc zsh}-3nPYB=_YE37$f`~oduf8@P=;{>6>r91FJxASZ%WPjlaI1+f&x+;<0HmH5mAK+>sw&4jO=G#)#@Kpe8JjB#Dv%f3@s51r=Ls=C0>$yB3dywJs$7F_ z6zw(%4IpPC1{Q9FT}cm;1&8j?4(!Hs$cDre2)eqZn$yaj_JjUd^JOFr{*s(zyFY*( zl39(q_#@_;GK2)OQ99zr70Be?Jp!@ zJp5_{>Y##yok^S9Tl7k*KpGnYoFF{i{_c;zNRCKpqT`%`jv=B`vM8rZO#xp34ov~t zyDgt%{xTp!yhl741}JVB04~z@Hbz_4D^rr@E`bR~J-h z8zp?+%#xYyW-b!+(1;G0*~pGMUYXS=2CLou8vAx#1q05qPCr7Min{B@8nzkZ(Y@n} z!fZ~bZ_4CxMT*(~&3eS)q=gkUvmW`K<2kkencnP8PJkLKBp{h7p?f1JS){b-Slv&w zGl&wG1bvHyo)~h5Ij#{8#WtuJ10fQ6yKQhsqjh!Q-n~20PC%B*qcjB;u2rJ zv!1^BK@{QGp4I#C@j{ZJ=k9_}SgTaRcB%T{g*;t)7_|2BypA@v^kqU+1$FftRg)%- z|E-xqXI;;J5+F93+iFhpzdXmq!Z_}9PCro~JAq-N!1-7nqopFLIAnpcWGlVaJonEK z!M4P9q}1U5%>r;0n$_O`(vS$UWd4QB2%#_`iDSjp6g?fIL7_8%+uxmzREIYIp$&2d z_j{LsxGM6np$B(b0W`S>V5g7--4&(`2W`CyMGd#c_V{BUCYRnD_FE-L{zL@MW2$$+ zuT5VkXNp3hed~yo4f%h;3bsk#{vQBsK$5=~X&IC$5R(NXP_2`uJ&KRDOn>!WW_R!A zJr8W;(+B+QDBku7Hcdl3E`z^I?fVY&{vlk-Mw*DRC68dcIZ}4osnV!koG0G!f7cgi z)u4R2?s~$6_~VNR&KM-iYD!!)G>=m0%j5GdblO-a=u{{)Om{2~?d`B>mDH5J9tzYw zf6W-gSzw9-Fj8v@(fn@c{Aak4gN^OV<*c0NX?on{2{!;2tsc2-v?Uof#Pc>X{}q$3 zTqOKm=~Wc5@FxuYRk0PggqB}kH{!#rlpv@`B$VDbrVPuSQRY$j5*hR>Iekh_pD_sM z%x4UiJlSyIevR&Ski3I*!=g1RZQ$IDhxqNb-h z?GGzlquGrrZdF^4U3Y?k>3ode+iH-ZM#%rTFc#J3QG1QFq$9bGo9WB~-wZwx}Xqq0Eyc@u|H z(7YB=lVu~3IX7Dgizit;|1a?P$`ft?e*M{NKCDA;KmU8yie;ekAOTWKy%#{K{DKbg zzZdC=^1h1Dy(iG-R6`@^A;&?Luc3DZ1$K zNjP>G-5SMCA0b_`073WYoAyFFRiki}glbi%wEn#!85uoP%<9lTlSU?cn=_#_2QIt-4m6~tB z5IE1Q%tRhhki*V15g6pXQ52;u}GBA^?7P*5x<73F&fG@gaD|0Udz>aP3e zM6qZ>7S(?VtD0_KMZGGMlfVoOu?jN*-58j8`w0+o|W(PzbVj& zD8gBCDKL3Knxts0eYI?l()(=*V=|(`IH}eVDdYZ1@uQLG+vkLQW{RZQLZ>kt;8CK1 zfGnKtH7;WPjM5tJc_)DUY0#8)tdPDru6wuC|m{QFWJE7noWOk>(J zp%mE7SzK6p_X``BH&N}GFr_qszYxS&taUuN+ffY)st*pz!!k(u1<;5T%<&G*D}W3{ z6I<4Yfe0X)ELB-N^cic%u6yD6K$o97d=GAfKC_&q4ql`th{KOygLH;@# zc>k-{(lO`q{l9Vpy4w!--ua1QUBbf$g8`sEqA$L>%JcM~6#x4Fv-jTdmR(n!=XdRM z?!7NplvJful4Z%VWjSFBV{pK@O|r2~vZu+-K+~jd()=+@2bwU`g!zyf=EJ~z2p?z| znhy;ogQ0CqFml4SjDxIHB~=bD-E_{`YyQ~roO|D^l0fxJGqa^z@7|NcUcdEQVXy6; zbEgcLSwYJe!u0(F@Xt%um&WwMKCi!NbzZKA=g=QVwJ5B{MBXOW|(4m?~QddBZ1_`=0aGDHe7YRBrD>Hk;22D%hKv4ai^ zm;(lLS%8IuuXnL_%|d^+Y53jk+xfziZ{)+{173a_EK1Ys)AR?P**L8iwXp4Y#O*Gh zj?HXyZJsZi^Bpmiz0#QlMZsXOK(9YY1A)S|V7-zP2(?qPZGJ%sgkI>t^$ z{{H4)e?DmF+{TEvz4~SR=r_J9(;|-fG`41a)3Q0twSUn1HVJ(`L1+#FTBPM!XOY2Y zW}076YurbLvftYW&^I!8_0i?eU%X%re(mS)`^GB}nSb)OrvR|J$U?9MwIyiI9Iy>e zu)R5up068F;?%QG9P--0;JL9<%GPG(3CX)?DyaVUAk}T zo+Aafvmauz4ocZpB z(Q5izmLTiC62>}V6+UPp%;Xf>U^FSR^+ zW(dHyz3CNvP+UeQq&v?c7`1rr@o$Iv=0^d@A`(6#pu*s& zGTx_5HifUMzk)NYArk0uLDO5n7G)YaCn?KMy<^ioqU^%s?e25~ooA7MYpu?IXWzs5 z!(Wu5=+PU%bTo94gTUt^ohH$KoF<1L_~BfE)~7rHmxAmE6sxclZhUI%6sFi>AiFKb5oE2@QmFW%-1pWbs& z9RO3(T-B~MuE;&Fzj@^?VcE?GdbiGbDNlRbGY0TK+_Ux#_?W+Y=HAgHVK}JY(60|P z*_+F0nGeBO@8om)i4PGbQ$T%4Kz+DI@}i@qV2eNC(mj%-xGK+A}+91O%zb+INQ67Fg73e67l3)A2x3SRk8KG*i zjk3N){EaH~?}Xw|=Y@2IKTAjL&J-ji(tD0iWD`_P{8YNKWCZi-82yM z_Y44ztQ9w4eO|s72K7|m*|Li9!XCzxJDs9Y!vH>o*AF4~ra}BqvPfShTsz8qSAaXK zX11BPzuiH|uK*yTFTjGT@OZP2dcg~~7^Z_I>Z0drY}jBoYZCmR(W20&Wjj*+9ZiEI z^w!Q7e4Y0_1A_=q)}mtQ4Hn%y^aAa)cSrCuYM!)y@*`k7%QP?ux3AE|hz07BevUuk z3xN7|+mS>3+yC)%{d%RLVK~kO+`4k$#q}8|wEY3$oX7$m*_Z)={yoe_0)(k9FTL>4 z@@v+L-jS+uwHwy7ci;tg^LX)RQ>V9pgQ|Lo-ppTD!zJUwks0N^`5 z{krcH6-L{rLwvV90IUsQkdM|=U+&9q6EXvhgjjb*_XFA*zs&m=;y9QVc+6=#FoC(C z(LKQBSb)8P_5Sj)#9#IX4j5pR9uU}-cwM9Lhw1hu_fblRsNAO$Pzpe2DmYO`ls;P*qUXFg`2t+*`|gc9WZ@qwNU*e8F>8zF1Vb|B3qkexD+xhNh2tt=%Y@ zqbo3hz^2>}ZAo{C`+IPR%QUe#w^#e3Is*NVW&w82vhI4Dwreqh3?yz_Tc6RULHO-l zAV|OSWlt^&5c~nNKu}Faj5jVZ-dtz2xxsXM+X29Il#WwPsfOE3w>Rd4K(y&I8^0s? z0T`mcKs)>RJ`oa}z-bP#Nec%vK!MR{wovEG?zxRGc;$1!TDHg8%PDV2LfyXw)g}pi zc6?Qfm<7^oLV*SN!XdpQK%Z_T;QaP7!@=PrRpqpQRk^CqHyti-neAOqZ`+e7zzwU# zk?|I~RoP2|&jM5;<=5c57SdE}nczd1Ay*S)@Jzt%=e_+=;|pR0iWC;i^I}#N};t*y1OsoRbI|%eqO$kJr`wGf^ zq;Ue>NZbL1_Yg-$i zg`4Em`sbl&U17%~w{M=DaixOqM;e+uJI{ve@wo-1L^B~Teli?x;xu)*W(!(#$ISPuBfYuDdVNwzQUCYDh^kl2ko!wmIeBmlR z7pkZVRjnLcW#2BZ=V@tI-2}L0|KJWWD&O=c$KSkfk$ofu5w*ESXITmpiiBmE;>qm& z%t7mF=0~mSnvEgCyd`ZFDfqli@=v~5XZh@YnXT_>eRLLg*ZbdHQ^3^8<_{kA{JXZO zx+XIrciP2I+ZovUbRqznn;H4~>|D_!pV9c3;A1V?)Yy8Oq5_(Z+u9E|4#8!B+L#EW z0R#fwwwi=LQa|gdf7|X)-z^r))^Hm)r`iN`0_ta5)d~ORpTCfvIGV3!3Fpw8#E7l45w%MJt<^jGX@ueeGn_fs1hddh}1LBs}Qg@Xf}1vSfdqynCyF&7PD(&&u%PW zm;L6mfQ;%#q1UA4{mmq1(!RN~lSQnxK8eYJo`}x<9<+WeJ{pdkT~u!Rbemr0hhrEi z3@iR71I^a3ZHDM}xQ#3>yLZ~uEe#s-J(9-vS{{=2CzKV>!*Jo^tT&%*6nN??`Uc#! zScS?FUoNAbQ~1Yp6HY@QiJ zlt4g={jpzz&{)IS^#!IqVcaMK@7%5}EDRXTe=|>CyXpXN->r+U{15Lw_pjgj+=Jgl z=s3X(4pAd2g7K!J7)3>*QcwLkc+Y*}P#PN(TgWgBj^`*}En6co3xQn$Va6A*6Ch~1 zmz}77)fnD)@d)=FI!O_;{9vo34MwQ+E-H%=P)!%x{?SCUR zBu(YtaTIVL_7ktU)!8c{YAAS$EY{I0gRAkM33+fZxk|yEVjw&XEFc4s(FSatgVBb# zx%?`h+*Ar`;E#_!j~k9m`QYaDeD6nJ$s2C`6mPrrAGm3Gi;I&*e*Lj~_`Dk*;v*Li zVN2`o*K7Ok_ZAS4ZY-HR8D#D<<M2y9{ok727DpjmS7vVS9vAtWGi&ckW?m;tHc41Ds;LDVXR5!UOH)v^MCa>Y`V zQtbG0o~Cxy0pNX4jQ$$nj>E-W;r(a*#yR*xR7d?d0a2t5kvc(MMObq#3>+|8Q zf98rSh8azOTqwxo;vc~RvgN}|aPAS|;t6P`X*M5Ii2v)r=!HLtFc6)EsG@BGVD3nj$uh=-b#FdjJU6 z6&OGe)(P}(@Evf=3BSDrb{%vb^b~NCV&OFP){8)U$lyrQ`dZ_hhLr~ecU+Ou+5W!I zeB#sr7tS<>&8DC>4wP4Jv4~#%sQgt2fS>%k^WP7!+%qeYy8C|O1hnlKb7R4UCX3Vg zzK=yI17H{@9Y@T)3Rg}={yT3&Nrd0nI z{(SXWysN$i5sRuY^-~#%S{f_-$rHEoXHVS1*FF0IzU-C<_~}Pqj`bZ}TLXT?Ydv=n zi4pjVlAvwdwx>K_2<#Z?A4IE*PUG8)pcg?efjtBI1n2|-%E5=24(~$*6Vc3+?6(|*9^=A9z!1Y<==6fPNvRSoh*WM9AhN z`2r{u;$OVRfk3g$nuXe{YZdDlK8K#|ao)OKP*l>Rxx8#ycd)c&e3a`oI=i0`XK> zfV3}V`#d~!KTJnn>-V56z1Gh^2E;RgclU0lnYaT^$^{B-81??h(fctL^0Y* zUV4IXLu3%}Bn2PnI0wbr+2dyU0MqFhnv3aJt=fN6T-<*fDbxXgu6yA>4)%1)+H3@E z3S{scw_gkaW)8rd1u%`dAXbHk&aQf&O+zCE)b1Se1Y9g{G$y(G zH19v*9cFC6jS_S#g6;$_`!>b+jSvcg#e^WEif!f;{rpF;fUOJg;NJ*M?d|-Y)Ba`Y zwY~=fQ+QTT0EO^}qhlz_aAB3I9Dv|vi8KYnqQ|Ib8PpT{&6Gl6bFl31-NuWFA-FCO z_=^rA=Kk0AeYS3UCwjBP_L25W6s-1knr-9$*o8%xnj3{PX9f`Ld4Fdu!AEU!4+p_- zbr3K3nNu*87;8rd1ei`Lr^UlDqX{qp#yHRaHd1VY-2^=i`nYEUMD1^z0^1ar!9BQK zf_(NM@BNPvfe|nwOilOb*(IixV0sp;cxhK|e3ANV%!VvprM4#k@S0mz-WZ}@z>M~f zbNOjd@anz)NszrSH|}GkqbV(@Q5tKIo^|HHQ(*y}x#n7;%l4MVcc<(M%Uf~TJ|y1amv*XJ6LV+;}GB7Sr+5R z&eqJm|SZ`91BojT3y^lxT4iQY<6@$ma}wWW_HBV)7aGv=hz-@FEll|0=HVq(RQ z_H~IPA;C@%H+7hC#|Qj$fGKM(wvK!xaLHgI+y2ByVX_TnA9_9S;477~=R$p@2ZCVp zm!%?wUHHrm#vxl% zJD-4nH6;{%&;~nLjHCIankEL&5qc-ENvlh~?y`cPZ>#O&M5kh45sG2#9D9IM>9yDV zLnv=|bFo(+C#Gq8;a4k3@S6f#7Zm$m zO0vo9;hY8R00#N;5dIr&!ub=DG=EQ=tzWp=qRUb}{r9ZEIDjyvVhjulSH#n4qad7t z$_E=>5;JHmCkna7i|T+vyobPE@|(zR^0HpQS&lGcHYnJ^va|1}LkC-RjZ~Z1r4;&OFZaWiM$UMP^_^hMqWwPvJAnSH zVf?-p0BBQS8-q55|6XG2g8s@~wCTUk`fC9|G6S@+T-q+VwB5s$Vg3cn%An@v7it{w z`o!tx@m=1|)6(_?09FUZYAE{H_!pW606Aa{-9oeeo%hd}0HdJB#rVbILV?EXRV!{} zkSL+5aI61!ggyd8M2F-MJoAuHmYMLE$<8l(-qtUmECmUE0pm;oQwYnZ=8ze2-Yx>c zB#|~4_`lhR1%GKt^T+MGpb#Eb7$LaZOpmtXc5d^EQuAnx~>0(UUO1W~!-*f~U zXVq(eFZ{jC+aDZ&rTVNtjkdoj{OyVI*40n&GZ$arwRg?}AWl}I;aPN8KwRhVzXb~6 z2C@e+Zy>;qOJ*NIdxtrD7=tt8oKWh*qMNZegb0oCV<3Vi&V*j`7uHSe;fe*neeBuP z;P72kIj}rw&)<<_kSUBLSm-Qjd1n-N;KLGpuh$os0*+?%6-?nFd>=uKZO1w0EBS_n`pg-*310P`>zL_@Gq4W zKlR4HLOK%)eehHN9|3>@3Ta&E4)C@;4xt4s$uq9xx%`rxecqPGQ-Ad~fXwBAAOHX$ z07*naRQiINJ}CedMGG+2387eG6@(TG*u^0@S~t$o=N*1A^jDCUd*OGn|0KX-G)^9S zOMjcMKkxv*aN&8G5YKS}W(XC4@Uo6TAcgwoQTOZyXB;52KX>K{+4=g5(Etgv_1t3({Yt&<0s}1x@wa z-~k{AztjGP-oaZDGbTF!=%fILP$GUT0b6K2=KSVMc$ zvi{T#5B+ItdjbF=VuD7<*&dKQ_c2nW80&|A_`Z-$s_RNB6&fq6xb;G)GZ>E7h?_j5 zOa134(3Y&&8YWvl(Wl5X{tm)lmc;UJV*Ig8I_9?=_z3SF9^n(?1Md9(0=l6#aZ zQ*4pCcOF&CH=wO;1)%I9W2c3sW%qYbza{j3fw$+Qum{r984MJjgQ#r-O9m=pEeBR= znyQzb!o!scCHn|df=T~5G{XlR189UWV|BTy;)q^`e|EIC-2>>W6VHiKO-z8)kS~IekF~Rs-tQUAU;?JPe zN)#hJ15|N-MYmnvK_DvLISfG659h$yv>|AS`z%-L}^bV5;QznO3zoZWBySOQ$hup)p#-l69>1QBRz zH@Yv+?Kg!V;a9jAe^Ca(*C^~NUZ@3Mq)-yr7`V|4`Sm;B!M8m8ncTnWXP}V`xOM`B z%t6rt255mpejBMIRcq-Lv>h14OjIBj>NXRg*Mku$F5eaU%sDup=7(ct0`3JnX9w|E zPHP~USpcwDSej|p-8BI~-|QpA?2F}_;;i6df|lNzp7KOx8E^FX zgYS5N!f4Cp@3ues!0`8XUi<}e-r0`_I|pxx)MY)PX?|*3jCHzpxOR%BS@Ptsf|V4F7qdGem74h2NVtVL6@w z1e0J@Dt__qck+v;?&K#=+`~l6WD@KI2>u@OhZA*hCn0FA9siWtEGw9a2(*6Zz@bRd zhh~_q>j-@m>ac&8=I7h&BKVq_5*bLXE!IdSSUSUP!{aKJy#^mrEUqE8@5+k#Q!E_4 zgroZb0J7WNMfg!zRH0JcQS*o0-uubP->?U5Z-xUas1-hVdiZxY9a#P%ZHRrsy;C>5 z*W!iTgzUf=3<~~lVmI?lq4D&=n6_OkHfUu{<+HPhz)q%sYA*aP#8(#LG=H4DFhWui zKJhmiXClsSKYTY(Smo=Feu5XTp67cXd?}AiRPId5=K{QcR(oEt98d6Dr^wSMQIp|pn(r(wIf6VqlL zAdmm^F3(8{aVs0js{OmJ0`FQ9^HGkS-w8{@{eXF<^{1>Tf{2(@zZT9i!9F ziKq!yxwsQK)4*+QG7tcG@ydC=>)40+spHS%6T>y~+<*uOJ9+*))TIRw*)tDqsjPY3 zO(*!=TaNRZn;&OoVe0OsRL4+CntV07ywg>1O}!zdXjz{nmLN`1m}2->s2kca+~K!>;M*V}B{$Qm@AZ_at2_pBwp z@7r$ROTYDzJ~>A^^yR-c1SarUPrn zroG>s_S=8A^_#nI@$|L5@c~$APLK7}{bxsi9)L3mLn`r21C1($ubnP=c+d-Ulxc4^ zxOKh^SYAU+vwHSHi4=}eI!B#9ZpRcb#trZ>Mx6a`yp{}hKH0wU!d!Omj}0%>hI zV{#aj*By9**ByLPxXp;awQgfF03{ru1Zyx4mr4o9_8be(aq$@pJEeE=}%K&FMQH ztXKA7CL?6B9obzJdPsi-R0^moS^#kcf*+pH%{Q*_{OfL~-}_6Z(}vp*Oq}W7Vt4Lp z&A!7fwy#e3(Tfy|HASyt;b^A)^Oxq|foA=!1pxl9j!LMr&M`;(TSfomL!;lDU2}EX zo&Z4A(EP*ZO!J3;DZoB;lmMP1cFaDhC_^bCk%!1n4*t_4QSXu#l-T{CI0ke39<=Zd1$CRa| zr~P&ke!rbM22gMIceMY5MfT|F`jJb+SgPo%x4i*?n(Cb=H{T9WHPnUDgRHLvCJ^j> z>zr>vJ-51nN|13sPC&sL2i!&pBZ@+HAetPC?t2Q)h{@0Inxp>C`(GFr_6y~>@aG7F zl7h(|ch7AJI>+7(1hV~Kymp?K>^sj$3*K|_F!!$?=99x!PK=k?sCyKm92`uzVPVX% zrETszbdHxEI?KU@sTW{So1BvB0YH=(xlqyM2MNniKX}Tq07~~D?YO2HHkkf^VtEOS z@QP!b{N|UwgMa>CU&ER0h4ur??RW+04Ul5wG<?Pj%`70c*FS=;m9PNuju~>kf1-s!v;BkcGnAHlK7wOdWF4IIdlD^Mj0nJuF+5bCq zgWrGu@VDn&*;Q+M0sxzpU4PGsjlTdGO?13sq^AV?9lY+2A6pB?+t`8CtJnHr78$i5 zwHu0yUn+{P4CIGk;*xh%)p?f135PBGs6m@)cYltugvJYq5qx%`&-6pxNW&sz@j)OR z(KN}JLfh0~XAqF~Hma~F4X-(Ln%5jUosJn;g^xx=QzqB;UZ~yiC2pJhBajXoFz$k^ z6<;~a-7miOfmtXK1NHiO#Plduuj9@moBZ;d-^rW)%WIi7KIu9~^h`)~d7m36P)(5W z7Bqg)4j|JZ+F$f(m9vy@C&3Rd>wg8}*VF1#(l%N00^`D-9aaX792>?8Be)@sa+tm{QXEy88YX^I) z)Lz?=^nn(58_YfEXcolxA4ZV*ZehLO53nk3K!~JK_GlU+j7SVIwoHKz9hZ`p3E&qK zdZRYUb`2MAgU#RVcmD7*QHLu8fthaXvmE23ApYiqDH4S`93z-0(KMlBngpMUw#}Su zxov<+4aQ;?7N9JtEwq4o?Q6gX6 zm*{^h0c<; zdqP$j$jzP_XZy(%iul}n6gQ2wI(wB$qj0E#n$S@kjZmCGOK>paY6`~Woxn)F&2Pjz z0R26*Xuc8)M{4258CU~F=sMBmpRWB$g8*~(^Md4-`xTO%E(kAaL}EBc%u>wFf?VJ| zg=+aNa;9J{c6$gzkX{d4ZDPmp=I2!WCO0 z+CJ0G@z1??Oj*$ADnOBhB+&TyYBP`mVh55i zHozAI=?eQ5g8;MpMH@cs6NXb~+C6T!0~?4yq2kSk9e^OPg}KZ9x@i@`Wq&!Q?c9t1 z#2aX{0(CFG?~;u2>OLBWfHG(Q{21>0LeFyOx-GDic$394?K%KiEB9Xldm$>78~|Jq znxd_;gRp1XU&9O2HVe>=-L*f!FZ|Ws(tp^)i9tg9t8?3bprI2NAV#p2#!MM8g7s)0 zJo`#Yqj7tumv(GbD2g}@N3x*3SQDPUqe8W86P2z@;m`09J2lwhY} zzz)ty5t5G@!CE(xFwFWBj(J%SAk*Skx;~f$yRiY)Lu%2wo|tNqV!+4CPdarFy;blaW-K&{l%N~^-q z3yC_;Ypv0%9%#Icqq|alfG}NF$O=VV{=SI1`hYmwSS>EDEv_c7@v7VT0l_>jMrWDy_1;mHuiQ49*S*{ICE0 z&UTHycx^ify#L)=ZX0xagVy9At;4mC8=k?NKkEsw?Ye)(N)N4X>O3eAqx24rI{+ZD z8W7f30ct&8$!?`B_=*0zW%iP3_)P**}- zK{XZXDO44H{tp2ksXmX;>??R;WrfF|3mOVq?G`eey7u>b?&dcBy{*CAH%F z*>-|LQ=_&s=w=6@&yW9U_nx1j-KN)|wajSX@`kZX=4x>L%V+P!a$tc0#21lcS|&XeSxjIgiJ7QZ-=c1GU^8jU zpxP9r5aT8*!Q0$o!?h7iNOv8fh^XS6p21w02NLs4g3#2l9@_ww(2PMxN<9G?z+@Aq zLnrX@Gli;h!e3XUIY9lnE*)6lSEk4KXtf3xaG&F}gxHuGTlCr&#=M#VNpPhtT`n?E zlxUsJl$C=b0@gt8{AHFo&yX2p|T@wKCb05F>LvOox?JH_80hDMaa<@dW1AGUVsr>1a>00>5;_weIFc$q`S z)dCbRH}2mC@9DSVC^(M#M{l*SPGrLHU>h>wJZs1t0>Muq!L|<8gZD2K3osq}F$4zi z3`29~;6s-oLr+6el7w^^b!E#)VugBVTLygi5N3>U_>WizT7dyHb@(^|Z|Y3&<0q{P z&Gg~^{fGI5^;^(*HFIp07&#HtPku5h?6mjo3Va3%0?_Lt({Z*w?il~%;PBa>eip4u zf^#-+rc|1Y0rak)AU&Lh*ChOr0r=n6n+N`~2zS(a44LK+ww}Et?Qu@ot{W5G{Ns;p z|IX|~+?#ey006+*jpqFIOU3oJal%me@gUlGd)Wxmv36pY}Y?fxa=8G^eOG0MMnvRzX{(?|jVuk`~PsB3Y{I+TBOsLziL@8I!^1M(g( z2(~Dku;Z8qMp0wXvIIgXwJq%a4w)5r_m5=Tg>6LA{e{KJtCEiWs=GH?Jvem&E!pw1 zXOxcy?B6oMHYJJvOB)D+51`JeSaumL;cwG_EZ9xi(fX^s-$q@B4R8?bd}~}aVW)+?uowP& zNF_vLTA|<>0%VexU3_OijuByDO&M>)WRwbugmDF-AZZ++h=7_r6a8y6#-lHY05G~E{)~a;-+Dm zuCPF99j7Jm-SxRvS4G7{$!(be9pL}wH=T5qd&R`y;Sjb08=-)KWw7;akLGVK~ zpz$xMa|4*VX>v()_2nD6uwZ zSNs3oM~DBdYOa3i@7>xp0|4t)bADr6hf4)ZwsD=`fdEh&OwVFOd~84U!%lq|n%Hy=I2@wM~i;wl8JBzdNjS*~_rm?Ku7opw{U);hvnm? zy!IyveRKpS5Wsp(K9BZy|7rjeuoqMOKiB?^75(l%I`ws2W9^y&0D!-HeDv3^y?ya@ zc8n`WRf1HYjdS7{BWUft-5JLV3e#rNj$a*K;feU@d9MF!v~*%y4A~vp}(SRpNGjzUj;W!sqot1xjy{{d z2cfqS1SB#5+q5rJ1cuiS`0>qS9KSGVy$dZ$Y*D%k$(#di;mQ#>zu2(t4zxWN__%$# zQ30?u&13>usjykLp?uM+E@4B_jqqN(09t|8P+Z?2YoJw;#qHbLAH@TJ+pl|T!3PtU zWoCem#LYO)EQr7NaXG$S>HI+nd)Tfi0Qlh#pZ$){dG@gn2ea2vasVfUQsV4FZ0a+< z55_2f^_71lm;ga*dV%w`1A{+65Eoh$H^E3KA@0ltoYT+XviW=%5kDruIrEdY^a;W- z%9rdAw@GgR`)`7JqHLUl$)@WiaR3st9VZ;7EBtL1&}IO(ad{HK1Zc(c{rM(`di?t2 zDDOGzLVHPA1zYw|V@OkTwXUS|wWD!&pz5u2bOb)G%Z_R58l9U7n2!JDFJEVI*)nkcE{Wqw!Ac{4Dv5FK*p#Ih>1y0k4A;KmD0V$Nm zI|3V@1e6^2$xaVhf;kJw!KvuOfunHXMwffPy$%;I7BE(tDrvnO1Y)SE1p?`N-pU22 zmkS;)m-(~p1AK6M1-9orbmwt%8F+IAbKWlV*+8q!4gclGou477%?=2bwKu(Zlj{yw zXytr6LGo3=^bEFg``ZbiLjQR+n%VS;1>ibMcxRy>Rg$Ivm`8##&5weP#mx$qKLvq{ z(-Ij0eYp4F!b|^u0Ju#1U;h5|SHIz|l?wo%Bp3TK<0@__aF!^P%mM$RL%OhL;YN2s?l@%O!wv`pU=m=ktPGZ6 zbHCv`-+PS1X3UEhwmDW#*hj;HD*dKG>UQW&04asKC>R-GgB}kz3w&U_%EyL_*l}I_m^l2(}mq6oZ0t zP%;@!+|e%y<~oZxtZsWCrSSUeie>!H&#|Q`KlQRl`1beQ z#4n#Y>^3Rpy(Zc9r{>?ZyQzz@t8s!K6D)RUGmgLZ3EDq0%08>z^Xo|kLaeQ^O+Ay? zwNv|=&sb;wzB-2X;=>hX;rd0Y?J5d;vtj5z-_8hsysSX|Gc!Of=v3O1iL`D((*EK7 zpYPcJ0)KpZjU&Z0rjm~uJG`PByo$|Sev_-;e(r$_KV0;2)oGD9DlgX>Av_zpnn1c6 z1=~37uC5A*|4X9h{s8J7K%ro$ZQS<|X|SrXrd)Eod2^v@Q*f)Mqgm25Yj-*AZDL4M z;l{;^pMCiWZd;zP;{nk*2S^{8`2-~?sFZ}MlV`VcWAXD3`Nw8A9JlfN*!J(}oXC%9 zHEt8`%Yo#T3IAdbzV-FzTx`FR^r?mZLXTe0pcCS9vtN+`&Q}ojsoVR--~KPPpi|Pi zTHN@G_?t9MU;}o;Zy)9qv~Y6R=lef$Ghcl0yi2=oU}K1H<^TX707*naRJ+=#IN9E{ zYZ3r{`LT;XQdQEJ(uJ=~8NEFIV=}utXnOC_6&L>(j2kp$#Sb1zsI6rExCH`45U^bk zux${qR+HZ$9wQCNNm>P$8XJC-zR#2AAnVr+YX0-fPjJ`jgpkXS_URsAmrta~`0OJP z?2M4NKOHcz*{8KWk3$CF$+odMh~z-lZej|ITv=EA(YKsp&sZx*B z3%g6YXn{p8{N3eVu(cL9_htn zqDJvPOeKCMv!JzbA`wT&u9-3tgVD@>>Vn!6>$j|z%vr?gXVEo~xyT8>@yY(1twD^< zf1eJN>@`RvPQRR0pU;~F!R7LSLAlYEkhl^tS)=7gcXgEP15Yf(#-Q`|-=BjZ*p>^8d=WVV-26K+oIJ$E`kW3=c>6eTz~R9l*oK)CG4&ycY^ZBzfV4xe%w5 zTOsp;9Gx?uH$wpPZ!O9h)_MhiM^yUyATcO zWo5X@?}bfQkZrsxS0**`BnJ-;OaOzpReY+d1(v!R-UAA`TFxwgD?+v{$ERM>X5IhB zX$wL2o9ecMRupF zNif4-EM(bgg1?`IY_alVRUBaJZw!?{TDL|LU{#pM*dM7l zKpzx`#+7blulZv`ss3K|Kn&G`;(ds>y} zHEZ5fI!C6*>0J3mb}l;8m6K+3hj&Wf{;A63R8Cgg0RVS%kD8tLaOtx&cxL(07j4mp zw`&Z0a*ug9K1S=|3(fcq$g8BLy#3xe87jP3NTdb}ab)4TmFy`w=XIP3?`ecf<4}_o-?Yp!l_GU9au*YA43sik?%0&jU{{GNaY zOS!S^>-YZz0@fUkHwaij znl4Sv4m!n31Z+(guPoAOqyNW+)nbhI^E^w&XMev<_1j@I?d0p( zA&CsQBMbEZ*K`K?-S9o;gWZ4G0NtbQJ1jYF=|>*X)&BG&H|jN=$MYGyhbT+T4l#T- zvgbNaQdTLFS@cyg`ydknU4KnX#ATD%)!cw^;3n zSK#FxW%tV+gj*ep`~AB#Kb%MZwAZBhvGrV&&lwX2-8w{t`#1@u153NnNglcayh|sO zFz?PgZ0EMEq;)@s*&@4JK1&v?<}Q4){;jvU=`>2Lhjfx z8lYfd1-vi|Tv<6y_jr4Oa9>K@*9V?{%yl{XlQP)+5a8wxduZG~Zrx6-lAcW#MC&6L zG0R_-Qb5y6+4*kZYgKMj=?1%Ugy(IJoOTF$OPu7f7B3I5KRPvDqb|)YEZVva^U{d# z(dWjG8!Elk;1e7lCX@4*Xa*2kj`)1_Szce2-Fnmob~M4sW3(+}&sQu_q-rvr<28-% zg78eqpKhEcoNJZ@dO-kk@`meYk(IhdbsDOcLo7qT_8Ms}GIj|1UKp>1${$&ix^v@= zJ|B1b%E$C<5bGCu*K5516!TarjU0pPC`F|POZGxi`osaGsT6QWH=hWO__Z+b!>yJV zFA-8?PUh|1>fr5FM0|~_U(WmbT6y>TyR@QX*uo*ViRt5 z_nLH~y&{rl)MnoB`}@xEJ({r$Fu3mLf2#{xDKs+jdxpk-jm-J)3TE9V_3(sWKP_!= z(Z|GM9X@s4gbdzo{P@v&yt7MP!=o|96Gpb8$9w)``!IZWC-8i@$+dWXMHJu@&I^Dl zHMO_xuYa4k!Gg~b-MFLNY0=1;Qc4EogiMy9#;B2y!X|K+#w{^YnZ=*fo@AyHm3gVA z_hp8UPerwu_a6m6N?lcyx%ZtVCf@Bl(;aZ(%?mNTuJJoN-{BqxGsvT&DP&dn(Ju{a zCV1>?+5G8y>g9sD_6d^6Zi0|uR4y8$%*$p8?sGa*uV{a~?{`6Px}EneHCH$jQY~A2 zUW^o2_J5Y91{94~SeorR#BKEA!%p07^hY8z$4URo?)#A%Kn~vIZ>c2OnWq!$Th=^{ zA4&V^8$)gD2;dnFUH{}WpLrq^@OTktw)FJVe?E^7o4)bUEO75jp?Tq@ZdLg(|3?1QaGTVjpEf0~6n$ z1AYEbj6qb*@w@gm#d|!}lQAru2vtZ4@VBVL?ryA(+|3Ehw0~lOd^34^UpszJ?@)C_)S!?fdN1|LiG^OfCndG>3*BWfT}I}c$G3cx8%drk zk>WQjgT_I4F#J8t$plGaz;CwhP)w{E5WcVyyi!%ANP& zo`L_wnMtc%;Em{EAPT9P)P>Gyx%PFcXy32tv|I1zqFdm)w@u=@xGxm?8p3=mXy$QO zq^aYK)&1%29*qaF(Zr27$96;7t6;S+%^mvcyFh`VQ8GZ&;LgnszMc5&QqrZmr-uj(}%T^MdV z#g9{6aMF4p`D>HTl{))9;uAM#3>sHw*hWfXYqi8dssAq0;^gUYdm*jKbGySyW1x!g zArA96k`U3uk>pVYY1i}fa=Bis^NX;IFPy2v+)*V%qxub^OnGcL@M>*v-DfeT9d$`y z8iam=XLj*lvIrOa`@$bqA{gP|R#}kLVgVgLS|J+1fK;+!1bIYIH8MCvD^m!A@d=OE ztiy@lWUs&$n76)r)EgB`=UlMp6+M6mO?dyMLxAtgv!nyXpzIDD%rCGz^Lz`h-m0B# zhq|D!2HklRc06^m{Aej5{xLr3e=|?xg{?NroIspXO3*xEY#Omh$y!|!j7DPZI!0sk zCYRvO>9I+ZW#*3$&x#8UVI-s*V0Euff<1+ix<2fn)?M4UBO>U33mw0L?qY3C5qr@ zv)JG7kx976Z3pA;jL?a+?94zi4HIxAqvtUXq()yA zHSySI65V~ar^}47?>xtMbmbYpb2Xh4(MiB%o~D13L9F1#rV=Ld&YURy_` zOG{v{dVVsIob}Q^=pR2JrU{xZd@hIkh~Yf(RgncYf)7%O&nCu6$B+wXhUR^!^}3bw zCa7!VxA-mwr;q)%Ai{UT|D2v%4-i>lQosM{Mu6+XO3oJRC4cc}aW1Xd+Gxd*HEMMf z^S*N?@Ou-@5jDYQ9P&l93%_3I1AeOLnJl?J&26R=*6h!%^ZhJUq1cur3W9J~JkkJW z3$np{PF>4=p)|{wnLDoB()6@ByufvDr|asoz>RB>msI`cpK;hxkAw{9#O)l_ghFs9013Mv-JM6#R!LdCl%KLZ2B2!No`^Bi$g}; zd^m^h{+Vbkipr*Zub6-qMv9mMNw3VanmPMnP1}vg*DGedsn8)KSXJ7YY{E2#|I|!h z9={S%0E=sq-itaRE83NkWSuH@Rdv{b_2=jJD9L=~oJ0!eW?}&G6Rp5N$2htp9$??6 zPmr!TgRtb~n<$a4qe4qzKi)iwlT2Iy3wL4V3t7JBUS^maDK*^N68S&Qr3@}?m19tk zFOg~ezc0DPo5M4-p;C)46ue+t({B0|3UZ%AS|y_P6TzkP@#?|U^JrhtGf0eGWfu*d zwo9!0iro+w5G5B8Gv8We^QcgtJmdOOYug*R=#FmjUWZdLk)R@DVxySuf>L}Cs{jl@ z(VUlJSK)eO)y*E5wOuHL>l0;skNR1%1|lvn#z&QM{DBC+jQAKQAk`!dnbcQ17wC7h zu;S_1G{IZtZ=N?@JR51Ut-vsM>0L+m!`$)b#$LZsRK5*NZJ_%$v5yP+P-BSjWF+KIKU3%K}6lck&^{_6vN0=ne-7D75uIYw1E z8`8e~5ttE5K^r#MJGN&ne75AAC5>+3=8Al?LnCdvjJ7*R`Vf0LK@ebX;a{|D zb~zs}8Q9>&J=*=#{+ARHumFYu0RalUoj&C^=|_{D8Tqvl99=ypH~tjb6hda*y^FYD zldTOd@bRL}PFiqLLk&4sXiB?1`iI16*n{VtlCpQ^{r7zJC3aK3MKWP~<8SnQG?Xry z8pMI=qsEDh%!t9BbV#N$KpcRivuT4<%%0M#$zfhZ6u*yUw^~c)GpleUKYX21g!7T; zpy{LP&Mbccda&^;f5PDLK5*$*J^B0ULqx98jq|F2RE%whMTbQM{^#RbCI&g;5@vd0 z1{QYUCG;|ZK9QcbNFkay>NuJ(F@k)DfgKAEL=gm10R=A*F)k~hVlRN=%WMaJ`$*;; zJeeKkNv`;xO6i%rj%)dHQqH7jFk996^3Zja>zq1^`q7XZiJAo+$5E~;D}nmYrRH3h{_Eyy90Eq%oD+ zIpK!xTx(YN9jOhY;-)D-v=>cAPVy~ZwMK{P5Pfh_t>zF0d1w9^hpR91IRb~o9mOdoP$cdzrMAkKcO-H?96vu-a}#}~DKQQO0ORyW z?fW{AR`kU3R4Q(YN(l_NHH8WLlSRUl5x;?hCxxu+-78Lpg4timQhWn09yq7UTH0`> z7nU|p{b{A2)HFRKG+{XJggG-=@rr5zURvk{n1~-%ld7YC_Dt`Sc!Y>%9LT94qXBVX zD=#}1^6=a6q;W0ect)FxtBvm?1S^qJ&y5B62dXZ}Vf?Uga|Ad@R~FPh0Eos$61(%q zG)cGq#dGDx3@{)H+g6fmHjO1NkT*<3!6#rTV=9=E+aJU~HA_hgOk43Rp=xUUe!UUD z+HsYJ6SVdnkw?wVwTZ(3k!()H7a!3t&vi}eR>z?iO?+e2@pObIeGzk1d`$Q!D6l|f zgB~B&xG|9k^NhO=M?3W&E~CUqN~uTpq3WqNLi=;R)`NWaRrY|8oc|V>a@xx8vR8qU z3QuVjaJYJOBaaLP*A_NI8{zOZN!_cS&h6^2TF{fujwxsgmF}8JN4449Y~5@{})p#`5UR1?FXMw#NWhRoW+KI3`Ue@L< z@95)6hH(&nxKceNzk>=}K! zWfD&Xn4QW?gw!XPPclh*xb6k6KMox_TT7A^s0ogKUSK4GRWCs14i-1RJOwzPk5cLD zuYp}w-&Nh7*yA+SG)n4n z|D~hHkRGIR`=C943Hq!^;A75&8%J8PyKTBnAfI_$O#6pU{x~I4bpuUY5!ZP7d99pt zo{A_l37*Gnj)8G-i#fgp_ky01_V<{T^qY@|JmSmk_=u}j&lM5gp=j1fbwRs-p*-&& z{s?@mq%*s-A9cKhTH)~iL?;mt~DoLN0V%z5p86HQ7;giTNUtw!lgCYyG zsBwPg??(F0%MtFlQ(S%Sqjq%_fU2k(u%oaA ziqtCDTn8~^YiHz$G$?~{*3(vxB&r09gF$tW@5al4-8DXaVBUVT4{K&2bl~HH?Vf&V z()eLLj){@$Nzp;Nwd<`^8)W!c#2PLyHGVx8CqY`*qjnYBOM}*qs*|8OpbEEmN{hZl zI;XDQ6b0g4BViOe$OAVB+)8AF!2g+-$m|Kd1-e*d-Q4Ysk|qMM2SOm9w}uanTfc!rH3?3#GCF@lxA{O9U0Iw3r*(tiuA|qXgcA%tKLC>T#P! z+dE>h5g3Nm6{~eVzDFAPQF6nMEKgy)g6FFTqG19l0HqU6$6d@lMT{z9wVZT`5_6UgW=!itQM>O`xw& zPdw48jYv~-3?CNPRtGu1weZh%@pAOI@01a}8N{nP3owwf-X!Bj@i1&F8;k;7bSxDS zK>MEc@*or2=RPg)zimAmne%U}RtC*WqW1kxP$F2x$BP_ft z_McKWCLf2}K#iX;Laho|LErcAtlQ7z+2c+5D^S5!GLQ3r3BnF>D85AhmAWU-Vy0!& z{1kTH6C&-ii(M-(m)6hEu7yTYl;nKR-K({llzI(!PexLUFjnE`>oVB0X1(@8w@*)8 z4Lto<_I!DLAAidQ-{}_G(M7qecZ)$8LE)kVs<7p&g7Z+WfBn&a46*H+e}xI{?iAOI z^J@usZnY;iCf@Lrkzj*6BenS2wUIFxRLoXcp@mA6q_D?%hQ`<#>7e3KHgFitl>FCo zw6MfV-gI2e4~p9>@#)jie|qH=@kd^Q{#}xT+nxfdqR*15evO0&W}ud8Byra64Tln& z?*Kp37A)E{E2$ZHo6L z2s+BuB(wg;ndjKEZ);>bYnANEF;|c9_H#ECaf*H?O0X{}O9@96EvI?EF#d{Udxeh^ z4?B?=+=G<)CK$*pzR9$Wepdg9ijq8{%gHh@J@cU)rIq@97GR78{-kL3-_TF61}7db zHNeO|w5jLI-ZvTPZ!bGj`}|%aYZT*#!Y8g+GooJzNWgp;AJd4r-emjc-+Y*Rxn4>| zTlZT{G+2TxIg~<~pV%MD5F@I`l-WaSTq+hy17Ic+4oWkt6JIJf9C`!fS18RL#g6dO zzj11c6_WA};8CV7O0@F)a&fzhseqr~NhbX{Wle^#ZinPKDQA9i)|XXEQ>b99c|I+lK1kK+rW~Hc&VQR9}zq0X;9Is+mcN zP)mkLejF)fEGWw?m9k-Qm_j$_to?VG0QI%<-z%rX@zd4{dTAC0U-syjZ$fzcTxq_@hX?AEVazBCpWiO=xqzHrm;hypRhI}m+ z9El4l-cAkGXDqb%a6j-z)vC)_#OgW*PV_ejf{PiNAdDV|n;^XU@>wvH4*PGvshz}n zj`xE1+A~5U`mx1%z$@PM%p8ZKZ9NhFZs7KI3-3R(XI3v{{_NVW{6{WqAeS4V%WlaU zgl3*zP{wWW?*L1v8K%u_RlDqGAT53yPN2JdMQJzQQb(+zQY`wb}>38Ciyl4N=@aIg2n zSY?{iLK$v0txh^03rs>yXk0N}oNU4$Riai2CWKpPVlcED!IaiF5;qwTYH+0-Fx?q{ zrD%`5AovS^La+H)Y%?VAX0*H7b`kY9tuK2|w9}Lg_$F5CqzCR^3!k>LMeh7bV%Zq;Ux@rUD zZYTSyPXeK;7C^FMS&nbpX@2<~+3B6%N&x z*+y|&bdU?j?kt(>8)V(9=10y;>kN$`%m<~qjzILRF)HoQ8Ybz0!eH0@N&(S zk6xET=3>PXWK6MNFaulZzA2NxjeQHqsKFr(gIV}dp9maAP|cX}<`0{r8cM#<=%eW{S=dxF;s{p_STdR34up_ANfKY=;9qNZ@q7Vo5ej4#@h#)B|5S@z^NUR4lg-PS>vuSjD zA@-%tB>n!;^6qOL5*l1yM3J8j=%zcR?$J(GA}N9ro_aO>HVDq@HkS`WFu~R}#40W6 zvMN30=km!R|01TD8MHJ2n-ut+0kbj8^E&b6{2X<&R{yO=hI&xa1Oz-W)9#@M07Qq6 ze1`W0^^+J*kC|I5S79GCWTgZ7|B&xR6zAee`|fUK8#YHBKxKFsM&D6lS;wdVV5e&V z{=iE}u*`}<$RuLM$mu>zBI!0N94H>uF?8zLVu;v?OL(g%+N^*^j9;yZf7&uSSJBZfpke?Jwpp z$hk}~wjJL>!+u3MM)g3^IRvuidVQ>SPCFgAkO!b|A=7l8FURFq`BEa}#tJtss}ncO zR<*o(flH*B01tK-y^cHT$JDsjyh4@0_L;WLc&FBjoAUQrZ;`oF=hl)mJKB`p?!4*f z6D#_Rvw=Q4^uH~2*0#b&16(SJ<&cq~gl@%`+G|)N<~hAcz*vCR&t~;VizvA_Lo1QJ zUuEnuytZL<<3DQ3oMy-v@k#p`Kd&{JeKG%`!KqihkY*KWgK}z`cZ}* z?OD*uMF_)k)H^cY%*g6>3(W=_P$2|>U7yA7@0s4Tbi)@s;jC$tC!v!U_dq1MGdWBJ zK;J3!G^k00n+5p?wjld&QIIS*uX=A6W+HvQFyTGjCDyNPh;$12Km}jQ1CqNaS2yLK zl|p`P7iqEt9iD!x5-w5=&d{j|=onW6-l;?W^Uw@gx8$qKNtxilwf+O6%2D1It%fGpYnE8ua%0qR6QxD(r5mw})gJaw z^(C-Hk?*<$0spxGlCX#JJJb2Rl+r|wf4s{HU@9E*_Et63q zU_A@~dc6NCSvKL=cNI6Usy%s@+wt7gBJ^o+f!{M$c4QA2iViqtM835iN(^`4Y1ge{ z{k&HAC2UDq_qbFjV5iiCH44Y`i|Iz6ruV@~5b;o4CGQaVQ*7Pu#q%i$93f-YalL*d zysQK=(y!|jzur8Ka0_(2A zO-J8LP&Ium@N(P!%%M@k2K#HW>>z&Drwo5N@>&$&7km;q6qf9m0+v)DJH25bk|aEz z=IT^~>>Y70AoV`oG1K}wldqBV+lZvqc;3#`u1HIZ#y3~TrwDnQM0j$k9fk!V-?5Ox z{0_X5r+{_Ls=bUiZCj_jgjCW=fNFWE8|Hbi~epRT>6f zu}{l)j%JZkG!n^Sp<NoF7&kl?7VZhQrfi=1#V~IlBe&M)6{| zUl)uGQ0M#a&KDq)+{a^BjRZ*q^?DdpdLm4eaU&Je^FWs;1&4lt@)Vd>E~MMyQ=vR zGM4DiSI!Fr2t`pke>Cm7A<6w=Wcv3D^#QDrh#uv)N&^si-r$A5{$|3tTIL~ctV-wk zj(>x!jFT}V9Kf2`!?h_p+gx#OPAex`>()JL8VRD;R|F*k%vSVS&jx)TRM#M%Cd`xo zei|f4KR+rDyVVM3+AM2b`6PK|g(o$HJnuM;NU19W^b~lpD2U}lDvW!W88Z}V)G-N6 zYhh&m3`EcjU8>WvzV&$W?vF~Aua%g2g1i1+S&9JFAN}2j0|*I@l|^~qQXI2ks53&z zn9uvGQIPPHse2%*h^Lgt*Kj{o7@aZ_Yz-Z}+fV^j1@SrtyUbZ_nyS!eU%q&3oP1&g zIBr=zL0I9t@2)NQ&j$&B@6v7pD7Shq|HJ6vB;l>3{HcvJQxU{Zn2{pUT6+A61;FZco>yzGPuOa6@`(-LD6Wvf+9txY59fRw zi3C{A-CU968yF>mZV6GePE4hx8e4SAgEw|4+FW{=2Z|tV|Tu% z|MZDq$E83?`y^)>>ld!Pz_ctVKM5}fHhUTg3GBa3m?>ds)0nvs@Hl=fY5xtfzh1p_ zUf+7hnwdj)FVwI%sFOW->GrkXn5Hu6{#Ay)gn^HnPyMXr&`xGh*$1E=M(^iH?Pc03 zR_NZGP5EK=++(p-n}ew_t!+a%u>Z!67{-kB^F-`*s-(YdqTqX}FAa#fM;cV!pzh|q z#ji-BqQV>?Scb2wFJ-m}HoH?zRrAu<%KxY4SM%`;5JiK}LGq)q=Ic8KKvhvgp<2#7 F_f?v|Mt@+z>+%Xi>|w+yzB4S0ZpL3UW-V(bCY&%)}H-l*H83+(s~- zjG-F0R@6ewHq)G$%!#s6t0~P)Tl^pDo#p+1e80UPp6fc-eeU&l-{(H(e)2+s{1?HO z!RO7Jw_0=Io(H|^A3lHhfTuF7ga=XlF=)jx%mA3<`gk$UeJjlN?`ey$G znICj#V9o-C$FKLI=MmSTar&d0Y!q^+T7s`PGc3v7e^h2rv?qj$yuS#(5#Q$h|BuQ2 zMEjou1NRO$@MH#~f|Q4qM=lta5cj^i&=V9D0pD0}Hy9nT3EPl5Jo2ZoSu(40)dn@A2gVjN9kK$c7wAO!`Pk$g8;@DE^86jen&sKmDlhI_(vj8t zqrxo-u-bYBR;VSBr_LQFq<>Cq&0%}#^&Pkx$*-+7NK+R)dG{~h{qq4z%NSku2>;D< zpJN-%e;Mnk`xZG) zY~xSQgO;SMTG?PDJn%S=H8Yai{Fw9c*@-$QURJeZE{F6|pRDN+8o>F!nhDW5q$yGt zl?8$1jAun_)2!{5Psz0P&t0ahlsf6oAQ}Qytf_(j64DA)2?Z`pyRXWVg(Na@dc8F5 z>G9my)5lq(KP71|5FR5gw7wV+8mi^a>qXdNzO9f71QJto=X)WDbE4t2#J&CEbAE0! z4A(a0_Jaxx^8(tHM|wGP>TewDb$n|1ZFc#p(t-?9>=<2fRu9ErCr;+ZR@o23lG1WR z^z;HuSCDSksZvUJC4Wn9cCmM0pfJ{Q$L!)zDpH&ktwlTZk>1H&<>&U5)fpZv*sI>^ zU`VS^cYNIv^gp7hn$`aCY*JzuN@S zwJmM3_bHS8qlkE{syyyya+0Os?)eHGWUWCJ*FoUGvwI&nPNjgRwpguQS9nl9*7^cz znc<@R`KF?`=26#e{Y6q@9D}g_{eyp*H54%J>8%QP3V zWFeHi!?;vZ`q^Ow_R+Mug-HNr8kQD)fpO~0M1Ez>Y=KP^~$fG zxKPXSBXh%?86w6h`+`V52AxOVc3-Eg*r#}})eQ#)9kcB~04=dcc4(U5_C6#Xi+!^z z)+5WukcjD6gULc3)3w@Kr0u@8uVDPyCCGeJB02eSs{Zh&n9fn*$rlxc5V!+g8K)RA zx-7=+u}zVQcQ#wntT9#7u(XwVMLebP6HS(^BDW~8|AMOo=LY0Z=q5tQl2 z2Lmae)sLNigh1EhQNP;obU@BIi261pB5hZL#gd>onA9iiWRyYTyDlI+5*VV&O^{|g zafNvJOH&l%9FvWJW%-cEe0RQL^>Gdr=5q$?Q!Cs#=1`0L)^(0r;iJ;xT2s1)C)1Y@ zfZyuv{tAwj!#>8;G0$Qvr^$oYi;eiz25H|MXjD0nqjJDDZC4J^5k-C>8apFKHnr4N z7&xAOQMMXjZTf22MaV^ZhS|j{0|%KF0_wGv|Eh?b6Gv7j=mkZGhdt1dDv4v#9%NIi z?e>Eklv&yew-V1pfIV_>g>g4$SgQfr7C-(Gu?SP+|F`?qu&gs!WUX+b*J^lPOh#(w z)IGUqaS@;QH=G6 zJ(qynYEgTKBkkSr4IJMnWE_D47BPPTR!vlJ6mf`$qF>5yDJJD z9<6rS9)_SEJr-fd^m$oiAYkz79Rur-nEQ&c7oNP2zur+aXFa-dZFukQt_r|^ZI1-x z(`t*on4;&}6kk~k|5t1hm!YR?3c2G~KQpz2*>cX!pKq1bNbrLiDK_GIBSOJ=#6o+} zsiZaEfi$OCV^>1M>HE4rq_j?RW+EdU{0&QPnK{p0mSf`BMNiuXyh&sq-hR>YgA788 zyh_?OHIN&*C$D98Ea^`p9@Er`K<`ec_YLzPKE)ikV*xV!UVe=9QcJ2V?@xYcL>o$p zbVWUTjD0@Y?Bqx9cB7y7%mvH{74tK|Ow1*<$P1vBoAA(~jDz|n;5c%xmbt<=K3{n1 zGln6!fb33;9!J;#KqKUwJ}(Aa^ybp}_krP*IbVABrEUAR{sh#aRqF5rl$>KDw)%or z7Hr@^Ee>Q_Wu}ZjbFJaZVjAi!H7V-hdLs9C6PFR+kle9#SMA-^!YOvu5{9Lu>|8h{ z09$JQaP3jZg`<>@HMJuP% zBsKWPhDE2Q3#FupC(lY1_m*rY(T4T~cWzoIh*RtWa0Z$3Vx0t95G1mMM6kMxrG$*wBQWM0>rAjp{qPDNRR8($kkJ_AV$g@~0?v-9h8Z zj1z9};2R;tvnn<-z;XP82AZY^UuWy?Sr8w%rH-6APRUqZh52Z>y2NQ|~I{7mG84L2}NP z@rODZY9Yo^K(a`%1th?AqQe%D)B(xVS(k~j@uqht4X!B`E}Zzj*f;U6|hs; zQoeqzs`GWLL1<5v3R=z&{ z5?Ex(${o`$fJL^f9Fq=#MUJc-nO+YTxw7(Q=@nqXk(C>y7lTETUbts96lExYi+?BL}y(=M9U)dQ71^LO3zDPq%qve^y*lX3H z?MzhCHp787`V1UTwguoYVc)UP@ z0XlyRp^CHmMG$^~Ba6_0e1N?a!vTPgJ+{a@gu3v^W+8;v^rgQxp`4N$m>RM#T2Yxm z=Z}U1G4y1>bi7*fK}}4eh!K?7-0X^Dh|wHFJk)`wDF7a8%kt^+YJ1O2I&7BQy2ej1UF zJw9l1T63bMJmnHeB~X?lVQJfA3!p;K6y=<~3nFREO~1XvfI`3u57XaG#M8cfoz~ZN z3t@B>8BMZuj{IW#`Xd4Waq%t)d=xf%RF?z2Y5$W2`t4Njx{*ZFAL8#9OdkdLH2VVQ zo;+UYB29ZLu`k4K);IW#W)s7z z3G}59;jYZ3lLxXLE-t>XHtr@D#B%f=Pr@pbbL!Aw#I#IZuIw`Dxv=UAI!S>L>g@Ph zwYp(IdcKiBh%z4FfCl}aBFU(yg~uSU3PDE1CAy@5OV&7*;eBn~);dQXjvS83NE#OogC zvXprfr{7+lAh|Arn1Z#c;m@>0K)*GiwPTmX4_JWdamOI~JHB4G@U-8hDN1Kw@X1SG z(_E#>HLFqm1FE4kmbx>?;m)@(qFoocO+P|{=@0p))gJ_Z%Z1GjEWE$r&*I-rT824w zxB{s3HX%iwP)Lsa+q+ALWTg=cMXL9t>Oj%cWj^M_(aY0{=FCU3w;z7L$zT`j8Q0hm z&z!Z#fWhspx#wYwtB0!C{VL`z^H$5iN(@c6oTpw z!Y8dx`7{f2TIHdXqW;PQ#Na7k4eyuf!4az{{*BK3+BG zI>|9d^TQ|y9_c4e4}s8@Cs3$vYbQRwI*+t3IMdQ{yEL9Awbu^cl1hkR)a)y+ccV;&XaPkvCW)8~}GZ~eby6mT%Lsb+zaNJTPskKV|d zLNmYSUwrU4u1jRfMws-E2dfBduM&;xZVx>OP#e7t2sk-PN^MmZ2^)70Knp$WQY zsAGkMRZbzjUoMPg#+c#5d+ZL5Ngqs(YV{~n3;UTT37?fFBjm#IEss=JlwZ$knbO{+ z*4xltN47?t5G)l=v^c|rCBYvaFdJX=o&Vf%%x2}~HAI(V1vB;;VA*c5E4ORLSj``{mlbe(gmR!MOyRx;BHfuxa&1 z1m?Sdw@Sd-&C7W1QbXPgkUxd0k%Jo}Fl!11 zSe?JKRs+`hZ>+NPD>J@fpKS}neV%~^V%ssBt-&>}>>E>|-~~`f@*!te#i=`3+h=!8 zVKR1gtkEyi88{TP!-kfD?aI2927-SF# zfkaLnCTPCv3PyQuU;!PNsEP0~Z!F=DThq-$Pf*%*N{!5qW zm*G0#+Q#nD-%Q)GH9&I_<>m&KKT+BGhJ#?9AlwtY1&U}AQ~LEEY%b1INHc{j*643FR0&vDz|b6q#|XZJoxT9G4_R zsi;jf#0+hi$)MO!(u|yQXd;ZW@AIC~X-~g@e}C+?ZPzt3&+~rn_kG{beR$okVjRE2 zu3h!TDk&+cwR`QXous6s6QTd*KLg)HRPAbzlKRqWueFu)>8#OTMYm1}PPMZ3&S=$A6-!Le_IK^N-P6Msop=HVGRm zUjO>j>wwo4?iRNUcenhH_VoaJn?Y5>W>dXk-`#h1=Xk%cMcyB<2bL4l~^NsQ4 zDqOg$X#%k?2|Pop>Nz=bU(9N)%uN9XiE3qm>VkSCRzXcVjM)dvP*SnmWz>II)6{>| zjGcm|Yns63{dlrFwGXZS`E19kH}(vthk+4H7()7EVZ7vSUgR*xxr$x#Z(Jgnxe9Nx zZ7WdKRG8VSol$xcNj&ft3$J)8$!96oJI)vI<=DJ>FQB13~tF;Rd z#fXQHO1z?WG*N?lmAaI=aEzVN6?zV=XJ{D0st+9Dr#iZ?zBp|BLYe_euGGnFmZgWS zA9s0%0P-TapFE*_K>QN*@Tz`to*ovz``~ZwHX)qOogEqayxExjuIwDdlYL`PxAT(7 zeK;0=Yqg>^Rx?TuTO6F=$^Lq4Px_ve&`kp@8l-FigWud<2t5bQY=OVomuT#z+7aYX zYQ)61C4|op9Xy^unNl})dR_j6En1};r5DF9GXAk(u);awqN^md7Ue2whV!$rA z*J-jzCS_0R%7d`cvngrPXh{&b<1V8<30qHo!XxL= z80ok<^_98E#677t#Lury`D{;22s`;x^e;tU>c_5lQO`RR(s%dEx-8`I(t<@0+>BmK z>#j&6CHJ|IoB_R zQ{RkhCvzMj+QqpTaU#VP0Pk+b92s$+yNAQ{6K6GneH&PitQ|2VR( zmY7V{FBQ5(I6O8%@d(Anv_K%@y71Ey5l`MQd((Gg@u@__@Fil@*)TU!A_*weOWE+4 zAi^O6d08(i-G;c;*7VK>Zq)7)zc5w%6(?#*FEdju7F*YWciT1`zF-iA45P7o9pL<9 zc({J8k*at3hAt)O{p^;v%2||^T0d~vm{zjk;dpEKvO+DEuFAP04$y;L*06$!TVIKC zzj}Z(SixWAua!(lnDFr^F(gEWbgeiQdHjdP-uC0GJsXUR7dcD*n682y=R5^S(rwk>=~2$yq>&Ck)pQpx)fBW<{bl$=wJ zsn~bR1#|i;+$r}%xQ#`r77L}%TQ&yJ3zwz@2zpuGmLqC9zHBVwbp!TH{brvdf-RYu zt5YXg!WUm46Tu;i87Gd44_UYc4$=OSoYxtk9Izj(7s>C8O1~T$cid0^`!-Rf?@u&K zy~MfKrqfsY9xawH@0u|HyP_;ihP;iP==!C^nwNBX@>o*N07^-K#uk^C{|_%%cwK~s z=_Wd=_x3#se{uIK$eApbe4~1bJQEmtjFEVT9ujUu@5qWVPgL}R<_m4I)BrtXbl#OLSy_}xJk3d#yi?6GX-1{sRsMTXyv;t%nHkUZh6()QTbAsx z%)9yMeguB0Uw}azONsc3vYm(RW|FMcV;K5aG*(*Qs$U_1cO33j~7WQuo$ z_q4C$xW(o_<`ps02{;&L3$rC_58osf4IkfvaMheWojsT)y9 zx`!f4W4H(J2pD zCQd86?8;B&Az0f+QPRqHYG}ys)Qw@TFvLpl@*@HyeJ1dm)XBBPr(*A3p;01Hr9;(e zOqa<7#zb3`r>3n5!cn)sFT6~qOsja!sHvwg=t6Pft?&ilGliDw6I~K_ai5+s#qPd$ zvLUn6KEU3RPRU@HlQls=NjzI@){tDl{LL~ z_@?H%B=Q7m+29~PU$|3vEA&K_r6Ve-@AA6msdecAUJKVQnIZh)3+nOgRyY>zkQ|Aq z)gx7s@@NhbSh17cu`9ilDeZv?4~^rSz@5|00zh0p8Q z-&D*VIykqg7ni`7XeUx=f$*nS=6w@=%y+D)P>=-n;6B4R)e%V5q!^Li`?vJPyU)~v ze6*p?hLl4H4xaFoT#qWDP1Fj5Yin|$q5}09Lg#>tm$6fwHr+`7RlG0E7>WW($1IE& zV9!>8>{q{HM?&z3B+kXLMgG;sZ*D7i*p3x}oU}k!7I;}Jb`>;6KZtjr^iZ`x2w1jG z?e?3W+o9k`gGYLn&mA}i&c(@Kpy-;4td~k?p>5G*9PU4IA%A-7p)+mJZMM0-&^xi( zYdh`vy@Olz6+D~LCeH~%F|TpDGR97RYPEVe`LY^9u7dwwB7GM=mPpnsy|OJwg6G#u zC|A2(Oy(T$<6dTdGzQP6UxawjFvD9(BAp{k1ANK=ZilW^4-8$GT=*yygj?hWX?k+skz{PHmhLqzptFz!&9|Sd z3Z8mdjf|DL`0Lm{WovEvE^ozrRW>#;SRyXIEv?eaJ<{yeLvhG+RhH)vWFtV9n|7(& zK4#&o_2&(w$c>)%ZN3)6{+)_85mR2uf?oS%>#g>=8?Xp$ulbnx2jU($p)4^XFn|PpjXl0kvoGnY$ z<8VywV2ql!Vy!esn@^R=z>3`&p%RV0(i<458EY9UOsBS}Ys3_9jLA*Jbd}Xm|D@9W z;T;!yG|aCX?HSDy*(KN5Da&@B?!h%o$?sft9pKjc9@<3ucOH}~d&17=XQ{@PZ_q}c zkA)FyKN#rFEz8|=&*b#>&)gM93_lh5?rDe7O_M%Krq@Cpz=IJd>T|aRz85mxhM#PsveWzS%yAXR46z-wvU znYmo+y|fxD0g?-H40P4XENN)%iq(0cOpgQ)Tgm~Y6jba!#Ke2o_^!(G*Kte9&A%oH z8{qahP5RZU*Xp4enz}gU49wvB7ZzJ3kD!TmVBwXiQ1(1iV2$(oSbk84go@mh>$Fdt zD-L1ALxt1q;^QYn|cThmmR8&mTrxG+3FZL4;>nkj-m(N%1gQ5Ei%bhgQSnq%s z))Th2y@yupmh~U0Q+(aZES289TiPmXQ0YfKbr>Z8W#*!Gk{*NFbJ_HuZtjb$u%sEg zL*{(r+i@phsPQinNUp~XOIyJ#Q2NAkgx7#T6T!k$Bb|+Rp3cB&FeX? zhgZIPBe655z+z@Ljs-~;fNQpgjwEY;VsW6Au-NJ65;@ODgS)FFw0{?7#8CrM$lxX2 z#oZDyw6x$D_1Z$Ecb+Jg_JypH$bGRdDrguN5{1PnA#qAda)<;Emruk2-Tvi{{bD6{ z!}b;|N5qdzq@VaRh~SXq|1JGb1OI8@KMnk+f&ZrlK+aYQ2W{X#Mcs#^u;%-m{fgn6 zme=4AB&cNmBvItUtk4?BMg8R%{{x9jvcCCgm1Q59GsVKre=A8@dnanpX@@?k8(VfL z=wym&n9JEFDdy~lf>oSJ^jBGP)InbTx4idbq#fg8&AXKL*Jrd{4Vwe?Gd*nozb*^h zApiF%{5vxKHor~*||wt<=*KKp4ATJEfg>MuC1e><&%3F@W4Nz?$T z{zlyNj>Q?MfSN|l9$7Ez(SQ%gw{!&67N~#z?%|-(eRIk3?(bzNdQP8?;I6o3wTT?; z>OuKjGl~NhSZX>>;ZGHqIB^0cJF^J4LHpf+iiX;Zk3_!eu1WZ@M7tZ5Mj`l1Q%@cw zVG)#Tl;wi%Zy#4&Vd+TRdV3F=F?~gACvc&Uiz0Ce0km}a->S=TnAK%#anY(Gn~MZU z8}nFDC8I;tCMgC~J|DS&s*3!*gImBeLF@QxzbPW9=011}Rz{v00Q7)n|5V`)!K?@| zpeQwVzbAX9!w6K(flyHxn}q2|w%I@Op=zI7<@LvS?^Q8eK(4SnIY9y%XBitR2W$rg z+6GyE?988xic(Np7zAE=Y@iY}V4xIK;A~3pxck0kS#Flh7C^J4tyvq?V16_qL2Q0I zsM}QLV7`LvPkiQCMB#UOPY3@2@kg?4n)vSQ8yWFgslI~_ykep+jLg|->4 z#?0TrI~2+&?Uwx&z0m;b2Gh+m3yEQ>^tLNHrwuKMNa>9g6AFl}320)-rb< zj#)BBl1g;qP>pZi_X7a`n8*kCN6h{X2>$@ZJLWZbyQ@BC>t%5>6iKU?MT+bU65Kc1kr4j-a?4)z z*1t9o@aWKM7ISK@3+o2bJo)fX-A^A4TiwKobCR%>y=#hxiK1BXf;b(s zrJ5neuw?%c!{W_eKjgLppzh8Hm5%Z80;~iUTC5gnz%g=%hc@gnjU#$5&;eQ(y`?&7 zCqH%t-Jufu0R-dC4jmG1PsHwCp5{b_z45Ox9>?jPb77VH%&Gz4Oy|s?ohlaLhbRr! zdN+511cnYB?^8_Zl;Czr8oL2*7qVYBD!b#N7+;J)dYi@e#3Q{!KI{W{XmBGN7CeoV zfKlNiSO`X~!k##4{VIr}kNXTvxdKWa7NkXXuIFy%<%(gY@*gyZrz#JAp#tFgDD!#g zy7aeR3o#lN2qJvpRK6gFo~$DcVQ$b|f+j_jC3A}hm*J(-TVQmOvm|rIFGcPL_1WJf z=;Wo@hYzF&xWeW^!l=LU9cxuCt^s%~w!Tc}wDS97@onuazZdgxXc}_lPsp$52jmx^ zZ-0aQ29#P(-}()Bgp=mFFs{UVA1#c$j6l*uor%9t0vg{GU>Nd_sftmiCR>;Q6gP$@ zX62xt14{Z=bUXH+(CvRm(q;c1NhgN%h>>*mWaKjNKhBHVLq9n}661FK3xUZ}v3QTV z`&$zb()!Z$pNm~fgo>OH)6KluD@G^c6(27tx`jFLjKX=6x4gA51m04Onio>Y?4It4 zrg_l2k1**TpNVmQ;~Fq~y{N;R?Kc9BN1W}KSVS;A`M@g8LveyUjOuqmgd>s&N1GIh z01*Xajo}pFK(I66%fRjh)!u6paPgc@lmv0O~~ z1{&e`g|DtvHNLPIRGmMiCR?VJ+^2A7u(9Dp>f{MxMaX938k25JZ23XL8XUuIzH!%~ z?gB7P15AKZTr%&7Z^L0*tj=qSvB{Z1C$sA(Zq_Jb@qYOpgGWo^s-E>XSEZ=Ms=iu_ zfn4RoD9fE4i}0vHgZcM9M)8pi;SR>Lb_fy}Kx;?StqpioT3{W5H~8hzZBe_B(R_Qk zgDsmh_F~OUV#`|!Fo|IfU;x-$b>S~FE{>_byDeNh3PDPzhN;yG9Q`hauP9IH2cwQ( z2IYT%2IS@F_RXf>k@&o3Fft#-wVG14_nUN?W6_s%7i@>4Y17QEqI8GRiVa=R`Z#_= zrw?8*)5L2hy~sw8;N#rw-h!jM$u(6g(z-MCN{P@&Fm9R-;CV>Na=J|(pRq_X&4f~H zs!=RRpzs+k0CHSr{d!T0RISE0E+jYx#3nD}^No$NyxE&UZtO%v!)a~`m4E#rlbELp zGM~~C+_eP~%+eMN>%q$S@=adg(Nw?-=tjr4nB9BAKGo1?ru}S^TdOoIpXYA6IUA`Xcyks)=q%My=gCF%dJbc;vC1dKhVKsnw;|#D^G?G6S z&MtOgqKB{~St8Y?OEEn{i{ zTFc{NR)~&q7GlT+5rohYA#@Td4`vMuz8p`=A>&x_Qbv%_u=K)D?!N8ElnHMw*|ehxUU(-nCHdE5YNp=4a|{CjG8W)zJ#z zP8E;%NC8reljnhP854ET04Tgd zT^0kzLA)fu#bBY4*Uu)8_Ee@Zkkt&S_RXLep%sVFipP(H8?YY&=o%1fB=iAvM@Et5 z?)jHDRFd|@V^V=q+LOYt^kh(|gY*F^t+=Tj;8Kd@p4~q-Kw-kYR=(LcN@@dHZG2dF zkq&$cVsxw!hj0WKDrIaOZE(>9M3BC?htjRhzy*x|7~C{!tKOOku|B}OyxhCw>@i|P z^pIx!6saBDbHE5Hm_i{NZU2Gnapi!f!n(WQcHAlPy%KaXPfxhl6!2!SxoLf>t^D-N zJd^J`c$XXH{bic5QFv1c3&FWDnE?@7=nxO-=VC}SFd4iAMo`TOhgs<+PYM8o#amgx zqA~PDHR@cjyJ(sJQs){qWH~tnf;}XgZIo(lr~!ccQEaLz2%Q^(6x5*HL44SsD}LVX z1UFV9;u!p@vY;O0l*%}qiY#_!QO}428sO@9KQaQrGP~!=+}0*>7)I9aiKh?&Jq02d zVPaKFg-P!cLDDheUD(Vt=hk7)RJ5-!3E7wJFiO3ZBd$FtBMVLG<|F3cv4q`)Y1pvB z4#v%(M@i${{Gp5BVgM2qxITcvuu!uN3bWo)gBE;o zfz&{%XUD%iTnB<|gb*hz_{jy7I$(@{RPOXvz;#+PT9Mz82}lClEP`ir$y(cM83I5R z(j%rcN)v&f0m4<%alZzZWne>hWg3+Z%A?cU)h1ki<$?CWd&c?xVHFTKK=!$(H+*&X zshKlmM^Qo0bkE-~vala9vKSgc_*BXrPZbrufGLpM>vDociVasiM81nI-q4kjjiA`!`bxfH;c3A|lcHAy&Rpo?{ZnXT_?SL{-nfPN)N!td)-6wb|yuU?p- z#BztJNoPZ(JQ+}3#F&FzmDt#*pE}75pBw+Wl~k(@?yv=*KSenB7Xscvaga{~%U<|6 z-iPeC8-zCP>VyqlfIb8jDPEfRzG5!}%MsFq1-Y%_GfU1rg!EIy)543NPm9 zY#gP{e1kUed#2w+#l!2#n$XW;Y8<2t=)P;p^|b*@EBeb)2&6r!?pnR@aLxB1xPodY`7PBP3 z1h445upp=k`TCT#n93-YTFmBK(^}zOqA?0W^hUMPW_ZWKpqk+zivwN|*a_Tjkcu|5 zJ%541W5JXUw-qL>=41@)nG0$!O%rfMd@z#V0PoNltN)z&){|dv4rr8CC!y?aRelPj z6~JIhYP2ae+LJoDZn0~8Ln|)jEcp?5rTe$_JB&*1Os$2&7O<6*S_PjCRv+=DXaJv) zC}V2vXdz%{fq<_Ax|y66+vWNWAfVtBm0p{Thr$8yQb#p*pn!e(R~?=eC;|@v2gs%D z0XfRmnR+KKEYi~KZhg_&@oRvU!aW6X)%xR7niWSz=@%bb%{T+|ZRU9h*NETIh&TJ} z(P7q2lQJ^sT~WeQPcI;Ye#$0|i0V3JIO=61bHfccD%*i`u=I$p8mWNF1{8c@P({AR z^9Qhj30#Cr?)7UVOojP2sGMg-8O*&ld3!kZEE$p+a(w?0gso}7&frK(>i>Ew>Ou1j z1?A74fo}^WFY};z_w&hq5S+np53<)lVdmSBhSKlb9)-RhI(TB;C@@nEvlxnDrsN?w z$c64S=O|K%g^!AW1r!6>YzWwF<0#fN-~{OBJ04IQsQZ5zdO?1{LK_}XYw#2?MA>UN zAT}g83jmFBN1ZHe)XGorxT)Ke=g6W$UwZmnF-dP#?;zR=L74d^unklQ!?@5xqm8Bb zwz=086w-5KC;P`odXR?z8#+vWTWaLvbDe=7y)63HG)@R;bq|1v)N!GN(MGq+?wXHH zYpyn3H=6JrXNdq1&dcYHW?bWZZ4WKw++7lT;RbGqwXhFhV56}H9bz!u%rWkIurxx2>Gf9!v*=Jot4~+i!$Ow6JPE^-Wb86jicGWHR zwW;3*G8NkwfHsFEM;7#Z?u8R?f?ht-lRm{K zioy<&m|>jsj52~;pinrn0H@E~<(yjLcW#(ckM+M-t8H06NZEHoO$PVaK=by{8?)FylttG2(bW*^y=(OCsJKS!gzGhrD636&&9=b zVx#$EeV+><3^Je{b4Ux z6Eyw*s3OvgFJ|t3W3({WZ+mUEOkfCkhu^Fy9?c2(gR^iYT+oS`TO|`17Pu3vs7qhy zoFG-b5*UeA&#jiBPfuux*M4mj+DV)hH7?YVWQ)@_P4D$=FsH}894X6yMQ)nKgHO@C z9=-B%@M-=i_;iNXbG-Z+__P@8*`T73#=%1~^(^}8-a-f>{f;9`Zi8q8S9@T_KS2oJ zAEd~vuZ`R_>o$5`sR1MobiWq(8m#>NzYqWa=wQ*pYUaeRh#wz2fPYgUwb$l5>zm*F H@67)JeK&M`8OubR_LRMeG-VlEW(=V+QW#q7&XJO` zWZy}Yk!2ccWTz&BF{3OqF%6$<^m@H--#_5{L*=ID<9c3??S6ke9@q6u{M*@H`d5`- z|MQ>!NMjt($HBkrqCb*5z~AJBbC&=4k6{%CjXHTPhchl!zlw7o+9amX5W9YniBUu! z6yHa;!XERAbuIWaRZQ1e{L+-7>Gj*XPcjOByP-R)bIjUw`a0zg!;25P|9%j<{f4W| z^?`pR#4V|zdC}#599m>6jl>}Es(AH04?b&&!xCh3iJY3q#TwcJc71U05mjYn#E^19 z%AFF_tyvfhm#Vc+rK(`bNW;n<|G)owdG9(xSjjk~syeu-g1FRQG|T$xsFG=jwF>Ib zpJgrdD9?eP|L$*{JwseO({uCH=omMUN__FPw(R6Eb-so@SQe^20|0dE|tos zIw1t+aiDEcUeY3ge}8fz`qr!l{b_eFq0b1diCA^}5wBYI!#A6fQPF^4Po1s4fN`8z zQSrNI8}nhWK`kw6>idpT8;>;wlj^qcP5#lr-;N&+CHL-slpnwHSC~gt$&=YZ$Msu* zW0z>x-W*KH5m-z=pZKuxEj=J(W6(5FQmfqb5WBRrhvSxlvmD^7_+GT#EF=b0o3!|6 z+4qw+dfg{SC%#v0hGacSg+NTy8;s|7bp-{QIZWan+*c%B2(fI_VrM0l3uFglREicO zp}3p9-bBG$nN=ii#evXQNhnxs$??Hqp-Vc7MN$>%2cco;gKbXbz8g zAye}Bb>9KTg`{`yUWu}hvy7um~4fA{BfX)Zf*ykn;8d=KxalsA>j{5bvUrfb63 zBX8-m_Pz4$gSgcDzX>5JW0y8RZT~3}C|onCWmzqJF;Yf3%ROj@8c9Zvhn=Zi)}o#5 zDycXi^xNY=kgilIVCgKZsxMX#Pi(Y3@y1?VqSr-62wJ+VIF+g5(uKB#jQi~Jwy3<> zpqS{tFX7IqemXnAvUsky-uZ^Na#z{4$t`MZs244cLY@7jZK%-1I$f9L9+Eccm3$s9e&zzIZzx^$FSJAB9O#(O>hgbvbZ2EH_-^K zb5_e=&S{+;_Hg;}CMJ->jqzQ70>m~1oAyAD%j9lvkH* zj0#FsO+$7eEa~jp%p#X%KjDcw@9ii5L9 zd-qTBYO8QTo_VpxUgOpQ4wJ0?&tdBtvTHy3cxm}Rg(W689*)u`K4<&pH$(M~kqqNs z7RoSn8*ha`&FPk6?3Y<^SxD1jNXDR23$JsvZa5%jX=3ggmV+OEBN!oa*I2%X<8mOm z^-#lSQ{Pi%Gjl_vqyKnW#fY7hmkhXiJX_>*wTm_A{OGXBkGZW{FkE(0gz!ctxe%?G zTMysSfciRuwMTyNmM-WR5tlvwrED4Tc8=^(*ISY>i_1^a3}IzX-GJ+okKpApsD-*F z^Eo&!uxFt9S(8+i0Y0K*MVia0uX9f+rwZzgce1B5HX zMP^jY@wT{hMmYz*qXM<`w=Lgh+o@1o9250yDh#QIaHkKZGiTa<%#Cs;+tz=ybi;=7 z+Ny;kyq-E?&aj@oRySsV?Cd75pu)7fG1TAGCA*#WQiTy$BUOe`PczF;FS$4SIiF_F z{B%{%74titq#e&J)H&cF9e2hJW=>ze!z$g5i(e>Y_&iuayzNf%s545y@b2y$bQ!1A z&|aT+DUgRC6HzB$X=}%SY+2}kq%>~!LSIs&@K+C3Na}rv&?TU`*gd9cE_;6^FOs&Z zIZ`%C*mSbXf60XJ@jhm&hmh1zW95;#XJXhd1Ak@cUQ68*kxN6Jwpp1Q{Gnr2N7q=| zK4{{FXf?7lbz;AC!-k@x272lKH*K3YgZP;J>3i*hLk{% z=z86&LzdoEq|K@)CL|zVbKdIRM)ys=r&eM?WpQxXzRonCH_nLp^fvzj<9?)g^JtGrwd@(3aZSq{Drax zy@CsQv(PYu#`kM?SfRGE=?kA_gJH%><=I$WEqWs6-5vs((a8;#PC_TFN@*0nb!Qpn zr7N}sT$Hg{q3A0aI2{))c`_~jCHj_2u$&rlI5$C4ykS!IcF9HekH8}`m|WU5XU`SP zQe8_;jrnBQ`i?ty7QU1=<-bGv3OGg(n4SZBP#Lak)Qwu2`e@9rSIVoowfZip?Df7m z;0HP3n!(3QDLr}U$NDW*?kRzpP|m*_$#5K*CRif3g<2V-We zgO1}4=!GFW5b9e&mSE-cT=+GG17ZwzqSc4pHL^3j?Xs?!a$>`1B6lbl1b}$?cde-` z!I(=Z?fg#0_V)Ob4eY2}Rq_RsbNZtk{zNo#;vMBtx4Oy1L^S_ZewKlmccP=0wn7#N zorYT$pCj34X^k%=y0=1eIwxl~Of@6}s~JO9&3u@>xg_Hw|Gs>JvYV3m@l>c0-ja5S zeKv*pA{speqEneBN5WnVS@xVjImOA37a97ad<@gg9%g zH-Mx4N&zH6ZusZBgKJ>Jb(c+Qty;MqzVdN7u`eJd+UaWI3Cd;dEzm~I-SC7O(ldpe z_vd;++;};kS(!UQ`k@uTyxg$zFya%0at|)ijdGJuSMN+_&bdVxSoqy7Ib*v8eagLa ziAYTJw}34SE}K+Bt$qENQ44VmYUBQFRh``~b0s;jNuehO+qj-Hz!7+XZp z&hI^|nVp@knErF4XPr;4tiT)A7D-*c#J7B>KrWRU7d6T_F&e?y5N6&$hG8(F`P3Ua z`mu=%>DeFuRT>ZD6O(5d6&jTrGc(xN@ao+m$5}=XJ}b58=SRA%w5?p{@b;D?ngS=N z-&li(JO?*~TtBznlzZYjX8x#9`S_1o1s%qzYUsLKtEvCX%m?X8mQIx^R-Jd<9QB2$ zWHMBTj=~SjNBQ7<0KCMpygrt{}~BR46-D%{6}t^=vE*X@l^fvj;Nuy8BWDBYiwEtAi~SFqnW@ao*~E zUX1_{AsNtTH58#;n>-sN&0SS1@%}4yY#oXy)*|IsJ(ZGlrc#}P%svkA) zWPOrO+M}QZ-_ecY0$oq)AuIy_l2eD*rDQw?z;Bc`2qCSX6y>d}Zr%4Db8V34&R*K~ z`0*qzC?~~eED4EjZ`nA7b4iHeW#w);64QyfJ0?;Bm3Ra{AfM3-JyiUBHrYMIGw)lo z=I`QlPiiH8f{7h|slX*|bG^KdmzZzSQVfF)Wtc2Ra27MtltlPfT34t}5#OaA_6GOc8+CZODt!B&D zrjBNS@o0VojiG7`dV#?=i#2ub z^=v2WRD4958d_%`UCIet5I=U`EhOJ>3SgoFB%>4!1aF2 zAN`-v!h#mE1WUW8Bj#YRav>5cOq0hW7@Tsz`-n85%{ki^Fik+(br@Ub*-t7 zLG&G?U48CaEOp+UY^t(r>Q9J6g!@2q90>m+T+ZRms zpyzmzNg$SaeQl<+sIG_`EpW1smzPKD?eRz90UuK=I2L2}c-pnRS%!_`IPfoiDAE@A zR|(14Hp=<%*>lkXPaX?gHs+iOx4NLKAt@Cn4?+W=bbwrQI9H31w<^{_fHqiK2OSm_ z10Hlh%7aDg-)+XI$C$e}Pn|tU4}&ub?ZjPJW)|swr3vJ+?kg_j&xQSav(h1?G&fng zeX_O)1O80p4rY{m{qv}oAt)5=XWWLw`8lcU6@U*l0g~0OvwoG{5qOxw#Pn#gI!(RF}-_53AQi^TlB7%Ysz5Bv$}*9_?yv zv!EE(6jt2jeM!lfsHRYCX6$PNt}F-Cqx#o$z>QBoS$r5 z{QK1E_mQvhh6OHdts;(5yI2N{1J>^K2>nGtxf3vc1J{FgeU?#GW?Vz^a>1NwU^n|9 z%;;>x%vE@UMnQ_&#ej_-^J%h!({cISr#EyAC@5ciJm@Xl6Sm-DC`*O+%fB@lU{hzE z(JMo3tu@3+KSGC{UGg74>zq=PUaX#$7V28%j$h*6F=gOG{T-Ik`%}k4meF-enHZVi z9MLA50c>RFuf}I{aIMjVbdfx5EINuf)?ekf6q^bq00m)&6*lMu95*;HPp5G;4g^Z7 zO4UMC-bqPJ{fmd)>U%-ScGy*#m>j&?|PU!g^QJuSHTaIr-h2D8s-Yh7F7 z4UE&Wr6lF$)9)eg!hRa<91OH9+iC4puCk0e+5d%p+Gj=5`l>Kovto2&wQTMO@E6Nw zq-jLcgHJIUrrLLsF`)(WyT$F`Llm`S_XNQ-;3d%l3sT2z29C27snfRQDSp6$gdeX- zOw$lRG!VM6DBmP}_~=kh^fe6ztf4j`lbISSLiyASio%%9MFw)de=ys~5bw0+*`zs& z#IyMZJ00xMp{b1WMYwKACqr?I$MqU(D#LkNCX%I~osZlO%mqSo^GlM+h|-U~xuakT z_~b`#zfv7-GP)ZODTE3C)3w`KWU%-#MV4DcIcv%lM;UD#K|)P|#c=DNPxR80Jib2( zoVen$IWZ?KS|RE)gI`<3LHg^eu2Fw?P@y{d$YlW@-=5Y{g|5>e&{3BF`bND)oTgiG zHUNH>yXP8&Gk<)E&Mv>&Rw+26pJe~$Z!5ZSEGjg$=~E6nC?>Bec~y>HBZSTY)OSH7 zD73`956*}tiE1N6B0!46ClOp&Y# z`C6@X_9n|R=8@$*eNffV5Z@cg=iR zoCJ}gww1S0w-NQT!VAeAD{O(~VvEtnYClX5xOtU^hG|N{%&5PUP64HBr#ViWQ@Ru;J2ZTF8qBVcsd$L2@F1 z%0Hg$0a`LkFfi>N+h;btl&InG>pEIINz)$~+Rlo>W^JhQ+^qWPK7Htzq|8=HTm-f0+ z%-m@~cfP*oS1R=YD=?t-sj!p#(DOkuIW`9rjFKAEU2!lYy8FBEb`<{ zHK&zz2j9DML;Musja(k1E|05psM4ch`OW#!^ZH{X-SbQsEIh(5Ev}lWbAVjht&W%B zuHNzU{tNd?gBM5o8SuX%&WWZeBGUKAPHQr$b3l)%_V6lBc`Le7$-*3Bie|%s^4n$q`D>J z(iCs#9Oy>P$aRNt>3QGw?M@8v{23BK50qNk#)GG;G^;;&Y}}qpQWq`2nAtnp6RwGO zHIq*h;flkS=2gke5k!5LQQl2W6-G63L1HUmjfLN&z62HcF+hs9b*#`>i$6N%#zb21 zII^SWWrC-f*iJF6W402Jumi<5@S%n>R$?-_jI+~_-5CrN=%s`1yR6G|%--2ZR5eLr zLgVC5+fN-7O(`3=h%_ASyaFS^5``t=L|7DuP@(hTo1Do2&gVrW=^G4IbZ>H`#a>Wf zTGwAFCMBgz@Ba0bolmM68eczyWFkZp7DhsgHU6i{UNQ!RX}xItBg=)JA_D@Jd2i`e zZuuq8W?@0*l}^|NsGck8?2;W8pKaVeScWLK{Oo$RCkvj?;^irKE~(*(6*6|89wFxW z=U1QGf~!cAd>79JD)rAV?7F<+)(BSO!^fgEFk&Kg_d>?Q>Wgl3PmF^Fgw>WP-&|AY z`KZGn=`)R=cup?zujlfzwrZ%?V-d?WoDUnTGLn6evfj=nfZc5n^26ut>_xr+62&QggoL*7xUKf`YzxyKVB}BygDh=Fd+D+Sn zQKcKV3Lp3D-Rnr|zp~ufNdK+#ZYQ*+EUg2&k2#LM z8L!<2H8n$$o&Hx$trytaIV1kBnCcW;lT0L+J)g~be_wL(9QT`3UTFdDc(yUMWDAd> zL;8f6?9Z>N@~h~r>lK4a2t)kawSPHXHC+o95xTAhW{5-$s%j$UDubfd-)=T|@_)~l+g4PFEEcXS>3B^&(YTmJVMDLI zxK-m#BED*cRynJIiQ$Zz^NMBnWXR{&;m=21!0?57oB3vE#HFFY)(Bm7ZEY%Mz$C4ugd@?)UiuU6v9 z=3A$O1GeZ?1vIEV@r4XP?n4mEt0H501#amJ{DfX9vLsbmemh@ea0QFCT}r8Kt04{c z^ZU>+bqLC?tx>@Oun+{{LldBfxO_D-uvaN3^Hw3TLGby)+a-{|#3*`4?Fe}Lgp#3Q zt;4fx>xBWUitZ=eC~l z@R9QqcteA0SmwE<3e~`?Z6VWKqju|;xt7Wi5e7+RiK@VYeP;Rd$_y2Xr%{Y6`0;s-}#U#fC8|R8IA`@M|B%CCsVmpCe#=Hfq9pz!sKez<3>VW+%oo-{U{4h_^B~j%zpfs!71NzIDkyh4#hCyYe)BbAV?4xZ0`e!fYxU%QT%qJZb=El9dYRg<%r7k z@oW$(7>xzLMe9orW^*Yira~tiK(d4jHQcMJ)#@gCe9R zSDjti2u%ibH6@{6%Q=74^F4?WmY7n&cH~bk%o)lPHw#7mG{G8Ztx+rp58GVaRzxz z#kW9$?<>u|c=c+VSIkcf6u4#2X0B>jeFY6|u%}hacaeNmZCk5lxM29<#iScq8I+Uh zhnD2c&_U9CaX#S??yM@{+A5QuFrKnsVz-du+3+E6v*XL_aco(!c^uhl4Vvk@Z7Wt{nu14q`z<5Z{Xe^$DyO7;pnY`oYe%??1wA`1Xe9 zY4D>V6ARU(O8VFxKkBB9^GSE@SgT$R%4aP#ua@9sQ!V6%aqm(?2Q)QNGf5NqZ@gl6<=lrCVNhio_=nEBqJ!5mPTtgn#iE470%Rex<-Zh-Mx@S` zU;APwD52$gfZkBKyC*{9y+*w-$*4h6A}+z<8Adfy$~}=eVhK9iK9!bPUc3-B0|j6X zxfnw?Vq3PdinBTA+eidD4CV))Y!Pxeivi#a{0dX78`bad3|6jd+z@}EemCfCX$d8P zE>)yH^dX!m$7M6W*aD*oceY(?680t)DVZ7K?UuHmHwZ8o5e{T}Va3+SlHfd1+`D_{ zFZSH(<&g?y?y_56U0#^Crl5TT7#uf4^p2?GE-xDgBdj){pPgV4J_#-RUch(Gz~t!iR|r z8NF_+%1LU)ZW(glhUY=cP{C`XcB&mR6+$_Ye&`q6;q>tm^^S^gO>%Nc`l|7QJEdB5 z3`gDf*ZL%Bz?V2b_{78?%e?dDd~NbwiEt`q@!E@fJ$Lr z0s(lSaaVO@(KCjX z=>=6^)AnJCyTT%~-Y*CJRX?=cKJo&&ASFmQ-Lk^As*a z;O384lm00qLQ3W(=79Jea;NyTl%t#c8S;2cW%l?^nybxu2}Xj@YtecJI#+0gw0cM<7vO4wCYU>F5h>a-vuDk6fl?ON{8#f&6olYYY<*pa z0S@gQ4Xfg*4VP|XpVP!9wAgr@_*DPIN&_)je0(K@jNP9uiSYk&vOj{Z zv%HUf6ysG#*baOO(+xv<@sx7WGgH6c5Zl%*>9gaNyop>^ubJsBc6&&k#B?c6_M-t2 zckQUd?mifs;&EGzZT$z@bZPtHG$XmX!?WbvTf?i?q4`Y4I}kJREz`#BIilX+CE(mh zw4wAbaj9^fq8-6Dom|y237Reb?9lpf&xB(6pCW{??~i!)n@~;ilr8KU6m~!gj0wQo zKT=Dk314$q@ilvSrPtg=dDx7dNy>9QLsrz*=q-d_g6<1O&5(2LW(ehe-j-+c7kJh@ zM&wbm;;NM{7|Xmx4#9{$8~YXNnW0C>dUq zdTL#5jZRJbXMg&kR0NuPL~*=|aeY{$Wp+Pt>&+IG8o-i94vN&>RENU_@&7>esgAR9 z?R&ZLM9b{3P~N*2R=pl)fO#aSPAhXUWe%2y6!so{aj@MOf{}_Vcz zKo(RN*v2=pbc;%)Xi@4ZEfw$>1YztCGsxU~DqO@}yBV>Thj|~`aW<8gkk-ICCzHK( z&t^qw;h!Y#TuO_CG}semTss=?FFc?A|E#5bO7U(~>}=|YzK4}EH1NPn0UTMrF{if(vur9s&BY4{5|*s+LU&@iQx*b}to z4^Y%PcD=Gis?){VQ65L{vdEBF)u$nGm{&D}Bi)rGgcWthKW z#kN5Xh1LQ0$7}NT&@Xg{ zd5g-{xyM0cHv3*Zmik@0NY^x2timR_T(>doFptiY_HeNk<@jCKeT`hL-?i&?u{WOr z-$BEkcCBhu`ISNzwh8`e-4P2%?TA`o$2?+>ls>-W z)eiYH+Vyr^`q!w}Bg0)2FJizIoCh1h7VcI=H>&2FqGV z;2xLe*BUh6N(Hk!(Z1{nkok&+1h3dAO6tA#q67Y76rw}fv<5B1(zAR0g&#?JCiT7R z`ez*Vet{$No!sH}3SAHJ_i$Ob&I{ca$}^II&K)i71b;9X@QrcDh-^zGVDltba}cRJ zGhzSgUesB-oLaBPsv-8}m7X;xzHA-#Op+nZ_or=G|nw z<1C%3oY|~U#lv<^N5Roy>N!3W$#KQ1<#PlwcL*WPGbApmz#J-IE z*zez^$eLa&dBnLu%9u7Tw6z(K=)FkSdBoMauj_s@L+jPNWQh`m=v`~4!l)~+t#dsC zw=}Hu)V?D_ag2|Oy+{71P#WU;=r%X%brnMrMAwZWIXq}X|I@H<-NXHMLjqP&bXao< z8UPKY1~*RNq)q+CZwr@HLTv}YJ%F40QK5y0I|rdU+I#$`O%LN%LV^Nc?3mZ^{TQQ> zhV%mPcCw)t>Ip5RO*KT$Kxq| zmXN>g-?d_PbuEurhH<;sL3(Ya_FuLS)rNL_JJ@^Q;n`k*mu37+VrA>COCmG@H>PM7 zsCk4D+P?LmYoAt^_@RUi(|Qy4itrTCtz~T^ditH^#su=ezh=7{Xb%mBwe&V>g9e03 zMV)wkz~-Q~56Al4`$&z3vSWTY{llOe(=qZ4?Jm($fgFo3Y5b&S$d1Z_u~KU;^BpE( zu!N)c^{Tju_Zsy+%>#xw)6u9ncOJxY*>CRnv#JF)H{LHl)%IiTAM!I|+y0+`2f7pf zB3vyeUO6)?#sKdu5$ufL7lyPh`>o}U39UveD?_Vmj_Ov-!fUQKXpvO8T}jfIIoAK)phP5_L$~=L6ZIqwDGpTU2eHu3$0lbkPw#nPw_7o%Sgr!x6Kyq zxgV(e%tXNfK&ODHeTDN*6UJKkkXpSj3oM(pht}-sd6)cbudqRFhzq%krGwqOhtki$ zmipOKrYl`?TNO2xdaUr_A6+U1ds|*0cwJLVf+ha5>;}Ec1`Sq?0LqsU!FI;B)Dk z60Yx$`^}}4A4pJeU(qHMv$T@Vfl3f&pX*UT$Y}_-UD5uRmHV(7>+G2O*sApn$dmbR z%?-R72WdKLAPpgkJOp>e@QdkRGq-0irnc&YfV%G?AOpSClQY#TA#yF61(e?GLDKGA zaB}OgTk0f@mTipYlReOt3!ngQn#I>B7sQM1$rpElI)d`q+2E9Ao(V9FIvO?{=&Y%| zZ2K_BS#Q-e_gE>%@0xjUJ#~MRx*E0UhdfgJ})>^HMvTmq`!@R)z{=fbMLV*5V(74+wUa|fg_>^RdPr8J=CV&mINxe*2(;%s-(VWBIc!rTV@KE1V;2{#zyZd3f!bgt z5HQq(uN3T}gUP%$xRXZ-$vg4DP4rPnSFY%zkSylCi<;m=ksP$M>&m7?K~L4w!06<+ R;47B@U~HYyWrw}u{vYF~K@I=_ literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow-perspective.yaml b/third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow-perspective.yaml new file mode 100644 index 00000000000..744e2f655e5 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow-perspective.yaml @@ -0,0 +1,22 @@ +# Tests SVG drop shadows with perspective transforms +--- +root: + items: + - type: stacking-context + perspective: 100 + perspective-origin: 100 50 + items: + - type: "stacking-context" + transform-origin: 0 250 + transform: rotate-x(-15) + filter-primitives: + - type: drop-shadow + color: red + offset: [20, 20] + radius: 10 + in: previous + color-space: srgb + items: + - type: rect + color: blue + bounds: 0 0 200 200 diff --git a/third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow-rotate-ref.yaml b/third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow-rotate-ref.yaml new file mode 100644 index 00000000000..69501250a05 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow-rotate-ref.yaml @@ -0,0 +1,11 @@ +# Tests SVG drop shadows with transforms +--- +root: + items: + - type: stacking-context + bounds: [100, 100, 400, 400] + filters: drop-shadow([73, 73], 20, [255, 0, 0, 1]) + transform: rotate-z(45) + items: + - image: "firefox.png" + bounds: 0 0 256 256 diff --git a/third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow-rotate.yaml b/third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow-rotate.yaml new file mode 100644 index 00000000000..71acca0f526 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow-rotate.yaml @@ -0,0 +1,17 @@ +# Tests SVG drop shadows with transforms +--- +root: + items: + - type: stacking-context + bounds: [100, 100, 400, 400] + transform: rotate-z(45) + filter-primitives: + - type: drop-shadow + in: previous + offset: [73, 73] + radius: 20 + color: [255, 0, 0, 1] + color-space: srgb + items: + - image: "firefox.png" + bounds: 0 0 256 256 diff --git a/third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow.png b/third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow.png new file mode 100644 index 0000000000000000000000000000000000000000..e6c2c0e49e409ffafcc35a0757652d717b8c52a1 GIT binary patch literal 79573 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 z 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 py(T|>vtVie{|oFj literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow.yaml b/third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow.yaml new file mode 100644 index 00000000000..25cdfaf61ed --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/svg-filter-drop-shadow.yaml @@ -0,0 +1,16 @@ +# Tests that SVG drop shadows are working properly +--- +root: + items: + - type: stacking-context + bounds: [100, 100, 400, 400] + filter-primitives: + - type: drop-shadow + in: previous + offset: [73, 73] + radius: 20 + color: [255, 0, 0, 1] + color-space: srgb + items: + - image: "firefox.png" + bounds: 0 0 256 256 diff --git a/third_party/webrender/wrench/reftests/filters/svg-filter-flood-ref.yaml b/third_party/webrender/wrench/reftests/filters/svg-filter-flood-ref.yaml new file mode 100644 index 00000000000..ae4eb6a0d15 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/svg-filter-flood-ref.yaml @@ -0,0 +1,10 @@ +# Test that flood filter is equivalent to drawing a rect with the same size and color +--- +root: + items: + - type: stacking-context + bounds: [100, 100, 400, 400] + items: + - type: rect + bounds: [20, 20, 256, 256] + color: [0, 255.0, 0, 0.4] diff --git a/third_party/webrender/wrench/reftests/filters/svg-filter-flood.yaml b/third_party/webrender/wrench/reftests/filters/svg-filter-flood.yaml new file mode 100644 index 00000000000..bf896e266f3 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/svg-filter-flood.yaml @@ -0,0 +1,10 @@ +# Test that flood filter is equivalent to drawing a rect with the same size and color +--- +root: + items: + - type: stacking-context + bounds: [100, 100, 400, 400] + filters: flood([0, 255.0, 0, 0.4]) + items: + - image: "firefox.png" + bounds: 20 20 256 256 diff --git a/third_party/webrender/wrench/reftests/filters/svg-filter-offset-ref.yaml b/third_party/webrender/wrench/reftests/filters/svg-filter-offset-ref.yaml new file mode 100644 index 00000000000..f6326b51347 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/svg-filter-offset-ref.yaml @@ -0,0 +1,11 @@ +# Tests the SVG offset filter primitive +# An offset filter should have the same effect as changing the origin of the rectangle. +--- +root: + items: + - type: stacking-context + bounds: 0 0 0 0 + items: + - type: rect + bounds: 20 20 100 100 + color: red diff --git a/third_party/webrender/wrench/reftests/filters/svg-filter-offset.yaml b/third_party/webrender/wrench/reftests/filters/svg-filter-offset.yaml new file mode 100644 index 00000000000..f48fb5104e4 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/svg-filter-offset.yaml @@ -0,0 +1,15 @@ +# Tests the SVG offset filter primitive +# An offset filter should have the same effect as changing the origin of the rectangle. +--- +root: + items: + - type: stacking-context + bounds: 0 0 0 0 + filter-primitives: + - type: offset + offset: 10 10 + in: original + items: + - type: rect + bounds: 10 10 100 100 + color: red diff --git a/third_party/webrender/wrench/reftests/filters/svg-srgb-to-linear.yaml b/third_party/webrender/wrench/reftests/filters/svg-srgb-to-linear.yaml new file mode 100644 index 00000000000..f7f33165f54 --- /dev/null +++ b/third_party/webrender/wrench/reftests/filters/svg-srgb-to-linear.yaml @@ -0,0 +1,20 @@ +# this test ensures that a sRGB -> linear-RGB -> sRGB results in no change (with exception to rounding error) +--- +root: + items: + - type: stacking-context + bounds: [0, 0, 300, 100] + filter-primitives: + - type: identity + in: previous + color-space: linear-rgb + items: + - type: rect + bounds: [100, 0, 100, 100] + color: [200, 200, 200, 1.0] + - type: rect + bounds: [100, 0, 100, 100] + color: [100, 100, 100, 1.0] + - type: rect + bounds: [200, 0, 100, 100] + color: [50, 50, 50, 1.0] diff --git a/third_party/webrender/wrench/reftests/gradient/conic-angle-wraparound-negative.yaml b/third_party/webrender/wrench/reftests/gradient/conic-angle-wraparound-negative.yaml new file mode 100644 index 00000000000..f2053c42b6f --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/conic-angle-wraparound-negative.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: conic-gradient + bounds: 50 50 300 300 + center: 150 150 + angle: -5.497787143782138 + stops: [0.0, red, 1.0, yellow] \ No newline at end of file diff --git a/third_party/webrender/wrench/reftests/gradient/conic-angle-wraparound.yaml b/third_party/webrender/wrench/reftests/gradient/conic-angle-wraparound.yaml new file mode 100644 index 00000000000..67a1370eacc --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/conic-angle-wraparound.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: conic-gradient + bounds: 50 50 300 300 + center: 150 150 + angle: 7.0685834705770345 + stops: [0.0, red, 1.0, yellow] \ No newline at end of file diff --git a/third_party/webrender/wrench/reftests/gradient/conic-angle.png b/third_party/webrender/wrench/reftests/gradient/conic-angle.png new file mode 100644 index 0000000000000000000000000000000000000000..81ec931cecdf97789efffd154f19cfb6885096c6 GIT binary patch literal 24700 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 as`=?+_N#69e-+UBWRb(dl#e%j^M3$n9LG8U literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/gradient/conic-angle.yaml b/third_party/webrender/wrench/reftests/gradient/conic-angle.yaml new file mode 100644 index 00000000000..11a068c9e6b --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/conic-angle.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: conic-gradient + bounds: 50 50 300 300 + center: 150 150 + angle: 0.7853981633974483 + stops: [0.0, red, 1.0, yellow] \ No newline at end of file diff --git a/third_party/webrender/wrench/reftests/gradient/conic-backdrop-ref.yaml b/third_party/webrender/wrench/reftests/gradient/conic-backdrop-ref.yaml new file mode 100644 index 00000000000..e4d55171d31 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/conic-backdrop-ref.yaml @@ -0,0 +1,11 @@ +--- +root: + items: + - type: rect + bounds: 0 0 800 450 + color: red + - type: conic-gradient + bounds: 0 0 800 450 + center: 400 225 + angle: 0.0 + stops: [ 0.0, [255, 255, 255, 1], 1.0, [0,0,0,1] ] diff --git a/third_party/webrender/wrench/reftests/gradient/conic-backdrop-with-spacing-ref.yaml b/third_party/webrender/wrench/reftests/gradient/conic-backdrop-with-spacing-ref.yaml new file mode 100644 index 00000000000..2f9bd2225b2 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/conic-backdrop-with-spacing-ref.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: rect + bounds: 0 0 800 450 + color: red + - type: conic-gradient + bounds: 0 0 800 450 + center: 100 100 + angle: 0.0 + stops: [ 0.0, [255, 255, 255, 1], 1.0, [0,0,0,1] ] + tile-size: 100 100 + tile-spacing: 20 20 diff --git a/third_party/webrender/wrench/reftests/gradient/conic-backdrop-with-spacing.yaml b/third_party/webrender/wrench/reftests/gradient/conic-backdrop-with-spacing.yaml new file mode 100644 index 00000000000..063492ec95b --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/conic-backdrop-with-spacing.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - type: rect + bounds: 0 0 800 450 + color: red + - type: "scroll-frame" + bounds: 0 0 800 450 + clip-rect: 0 0 800 450 + id: 2 + - type: conic-gradient + bounds: 0 0 800 450 + "clip-and-scroll": 2 + center: 100 100 + angle: 0.0 + stops: [ 0.0, [255, 255, 255, 1], 1.0, [0,0,0,1] ] + tile-size: 100 100 + tile-spacing: 20 20 diff --git a/third_party/webrender/wrench/reftests/gradient/conic-backdrop.yaml b/third_party/webrender/wrench/reftests/gradient/conic-backdrop.yaml new file mode 100644 index 00000000000..fafaacd4fa3 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/conic-backdrop.yaml @@ -0,0 +1,16 @@ +--- +root: + items: + - type: rect + bounds: 0 0 800 450 + color: red + - type: "scroll-frame" + bounds: 0 0 800 450 + clip-rect: 0 0 800 450 + id: 2 + - type: conic-gradient + bounds: 0 0 800 450 + "clip-and-scroll": 2 + center: 400 225 + angle: 0.0 + stops: [ 0.0, [255, 255, 255, 1], 1.0, [0,0,0,1] ] diff --git a/third_party/webrender/wrench/reftests/gradient/conic-center.png b/third_party/webrender/wrench/reftests/gradient/conic-center.png new file mode 100644 index 0000000000000000000000000000000000000000..aeb0c293981f58e2b8938d855ca7a65b0d7aae52 GIT binary patch literal 3117 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4mO}jWo=(6kYX$ja(7}_cTVOdkR6#B5>XQ2 z>tmIipR1RclAn~SSCLx)RK>txQ(*;U=BAb;Dpcg=|?L9&C18K8MsJ85p>lJY5_^Dj45hbL@*T7jQY4nf$K)>9&Vk zcX(+W)M}fS8{q0zYyaZ&-}mSH?O8YkBoscJZ?N8_#K^?*Ph3Dpo{5nunS~|$%&M3v z42%aq@d_vyxH)V%w&s*8r+|RYeTGqKDhJDtyG|c|)`ZzS-Tg>Zu5j*w^5fH=#z$PQ zjns2#IN<59!Oy5p-Qj^1<|H+hJbC%@Su7>)=y_+)Mt88#^*dV5JQJ7FKET9yFlvK~zru&EhJ;aL=oK~(;u&`EhuP~& R{{uDbJzf1=);T3K0RUY{L|^~_ literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/gradient/conic-center.yaml b/third_party/webrender/wrench/reftests/gradient/conic-center.yaml new file mode 100644 index 00000000000..d01ebc9c8ee --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/conic-center.yaml @@ -0,0 +1,11 @@ +--- +root: + items: + - type: conic-gradient + bounds: 50 50 200 200 + center: 50 50 + angle: 0.0 + stops: [0.0, red, 0.25, red, + 0.25, green, 0.5, green, + 0.5, blue, 0.75, blue, + 0.75, black, 1.0, black] \ No newline at end of file diff --git a/third_party/webrender/wrench/reftests/gradient/conic-color-wheel.png b/third_party/webrender/wrench/reftests/gradient/conic-color-wheel.png new file mode 100644 index 0000000000000000000000000000000000000000..368007c74f6ed33e2124242b7124e83aa496209d GIT binary patch literal 36018 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 H+Fke`0E)c9 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/gradient/conic-color-wheel.yaml b/third_party/webrender/wrench/reftests/gradient/conic-color-wheel.yaml new file mode 100644 index 00000000000..54145295729 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/conic-color-wheel.yaml @@ -0,0 +1,14 @@ +--- +root: + items: + - type: clip + bounds: [50, 50, 300, 300] + complex: + - rect: [50, 50, 300, 300] + radius: 300 + items: + - type: conic-gradient + bounds: 50 50 300 300 + center: 150 150 + angle: 0.0 + stops: [0.0, red, 0.16666, yellow, 0.33333, green, 0.5, [0,255,255,1], 0.66666, blue, 0.83333, [255,0,255,1], 1.0, red] diff --git a/third_party/webrender/wrench/reftests/gradient/conic-ref.yaml b/third_party/webrender/wrench/reftests/gradient/conic-ref.yaml new file mode 100644 index 00000000000..c29484ae9cd --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/conic-ref.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - type: stacking-context + bounds: 50 50 100 100 + items: + - type: rect + bounds: 0 0 100 100 + color: black + - type: rect + bounds: 100 0 100 100 + color: red + - type: rect + bounds: 100 100 100 100 + color: green + - type: rect + bounds: 0 100 100 100 + color: blue diff --git a/third_party/webrender/wrench/reftests/gradient/conic-simple.png b/third_party/webrender/wrench/reftests/gradient/conic-simple.png new file mode 100644 index 0000000000000000000000000000000000000000..79d0885c402c3bdda2a28172e2aa2ac2c6101014 GIT binary patch literal 24158 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 Lvl3=}v*mvQB3~1I literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/gradient/conic-simple.yaml b/third_party/webrender/wrench/reftests/gradient/conic-simple.yaml new file mode 100644 index 00000000000..c40c878403e --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/conic-simple.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: conic-gradient + bounds: 50 50 300 300 + center: 150 150 + angle: 0.0 + stops: [0.0, red, 1.0, yellow] \ No newline at end of file diff --git a/third_party/webrender/wrench/reftests/gradient/conic.yaml b/third_party/webrender/wrench/reftests/gradient/conic.yaml new file mode 100644 index 00000000000..ad034d6a9b6 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/conic.yaml @@ -0,0 +1,11 @@ +--- +root: + items: + - type: conic-gradient + bounds: 50 50 200 200 + center: 100 100 + angle: 0.0 + stops: [0.0, red, 0.25, red, + 0.25, green, 0.5, green, + 0.5, blue, 0.75, blue, + 0.75, black, 1.0, black] \ No newline at end of file diff --git a/third_party/webrender/wrench/reftests/gradient/gradient_cache_5stops.yaml b/third_party/webrender/wrench/reftests/gradient/gradient_cache_5stops.yaml new file mode 100644 index 00000000000..9acc2620842 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/gradient_cache_5stops.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 960 540 + start: 0 0 + end: 960 0 + stops: [0.0, red, + 0.25, green, + 0.5, blue, + 0.75, [40,40,40,1], + 1.0, [100,200,50,1]] + diff --git a/third_party/webrender/wrench/reftests/gradient/gradient_cache_5stops_ref.yaml b/third_party/webrender/wrench/reftests/gradient/gradient_cache_5stops_ref.yaml new file mode 100644 index 00000000000..af37ae47078 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/gradient_cache_5stops_ref.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 480 540 + start: 0 0 + end: 480 0 + stops: [0.0, red, + 0.5, green, + 1.0, blue] + - type: gradient + bounds: 480 0 480 540 + start: 0 0 + end: 480 0 + stops: [ 0.0, blue, + 0.5, [40,40,40,1], + 1.0, [100,200,50,1]] + diff --git a/third_party/webrender/wrench/reftests/gradient/gradient_cache_5stops_vertical.yaml b/third_party/webrender/wrench/reftests/gradient/gradient_cache_5stops_vertical.yaml new file mode 100644 index 00000000000..dcfc7f1c20d --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/gradient_cache_5stops_vertical.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 960 540 + start: 0 0 + end: 0 540 + stops: [0.0, red, + 0.25, green, + 0.5, blue, + 0.75, [40,40,40,1], + 1.0, [100,200,50,1]] + diff --git a/third_party/webrender/wrench/reftests/gradient/gradient_cache_5stops_vertical_ref.yaml b/third_party/webrender/wrench/reftests/gradient/gradient_cache_5stops_vertical_ref.yaml new file mode 100644 index 00000000000..17597328d9a --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/gradient_cache_5stops_vertical_ref.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 960 270 + start: 0 0 + end: 0 270 + stops: [0.0, red, + 0.5, green, + 1.0, blue] + - type: gradient + bounds: 0 270 960 270 + start: 0 0 + end: 0 270 + stops: [ 0.0, blue, + 0.5, [40,40,40,1], + 1.0, [100,200,50,1]] + diff --git a/third_party/webrender/wrench/reftests/gradient/gradient_cache_clamp.yaml b/third_party/webrender/wrench/reftests/gradient/gradient_cache_clamp.yaml new file mode 100644 index 00000000000..ab8484a1ca9 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/gradient_cache_clamp.yaml @@ -0,0 +1,20 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 400 200 + start: 0 100 + end: 100 100 + stops: [0.0, blue, 1.0, blue, 1.0, red] + - type: gradient + bounds: 0 300 400 200 + start: 100 100 + end: 200 100 + stops: [0.0, blue, 1.0, blue, 1.0, red] + - type: gradient + bounds: 0 600 200 400 + start: 0 100 + end: 0 300 + stops: [ + 0.0, blue, + 1.0, red] diff --git a/third_party/webrender/wrench/reftests/gradient/gradient_cache_clamp_ref.yaml b/third_party/webrender/wrench/reftests/gradient/gradient_cache_clamp_ref.yaml new file mode 100644 index 00000000000..4b0dfaeb63b --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/gradient_cache_clamp_ref.yaml @@ -0,0 +1,30 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 400 200 + start: 0 100 + end: 400 100 + stops: [ + 0.0, blue, + 0.25, blue, + 0.25, red, + 1.0, red] + - type: gradient + bounds: 0 300 400 200 + start: 0 100 + end: 400 100 + stops: [ + 0.0, blue, + 0.5, blue, + 0.5, red, + 1.0, red] + - type: gradient + bounds: 0 600 200 400 + start: 0 0 + end: 0 400 + stops: [ + 0.0, blue, + 0.25, blue, + 0.75, red, + 1.0, red] diff --git a/third_party/webrender/wrench/reftests/gradient/gradient_cache_hardstop.yaml b/third_party/webrender/wrench/reftests/gradient/gradient_cache_hardstop.yaml new file mode 100644 index 00000000000..15e917eff47 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/gradient_cache_hardstop.yaml @@ -0,0 +1,19 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 960 540 + start: 0 0 + end: 960 0 + stops: [0.0, red, + 0.125, yellow, + 0.25, red, + 0.25, green, + 0.375, yellow, + 0.5, green, + 0.5, blue, + 0.625, yellow, + 0.75, blue, + 0.75, white, + 1.0, [100,200,50,1]] + diff --git a/third_party/webrender/wrench/reftests/gradient/gradient_cache_hardstop_clip.yaml b/third_party/webrender/wrench/reftests/gradient/gradient_cache_hardstop_clip.yaml new file mode 100644 index 00000000000..833853e2d75 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/gradient_cache_hardstop_clip.yaml @@ -0,0 +1,21 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 960 540 + start: 0 0 + end: 960 0 + stops: [0.0, red, + 0.125, yellow, + 0.25, red, + 0.25, green, + 0.375, yellow, + 0.5, green, + 0.5, blue, + 0.625, yellow, + 0.75, blue, + 0.75, white, + 1.0, [100,200,50,1]] + complex-clip: + rect: [100, 100, 760, 340] + radius: [32, 32] diff --git a/third_party/webrender/wrench/reftests/gradient/gradient_cache_hardstop_clip_ref.yaml b/third_party/webrender/wrench/reftests/gradient/gradient_cache_hardstop_clip_ref.yaml new file mode 100644 index 00000000000..e10cc3a41c3 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/gradient_cache_hardstop_clip_ref.yaml @@ -0,0 +1,28 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 480 540 + start: 0 0 + end: 480 0 + stops: [0.0, red, + 0.25, yellow, + 0.5, red, + 0.5, green, + 0.75, yellow, + 1.0, green] + complex-clip: + rect: [100, 100, 760, 340] + radius: [32, 32] + - type: gradient + bounds: 480 0 480 540 + start: 0 0 + end: 480 0 + stops: [0.0, blue, + 0.25, yellow, + 0.5, blue, + 0.5, white, + 1.0, [100,200,50,1]] + complex-clip: + rect: [100, 100, 760, 340] + radius: [32, 32] diff --git a/third_party/webrender/wrench/reftests/gradient/gradient_cache_hardstop_ref.yaml b/third_party/webrender/wrench/reftests/gradient/gradient_cache_hardstop_ref.yaml new file mode 100644 index 00000000000..899e62d8e1e --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/gradient_cache_hardstop_ref.yaml @@ -0,0 +1,24 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 480 540 + start: 0 0 + end: 480 0 + stops: [0.0, red, + 0.25, yellow, + 0.5, red, + 0.5, green, + 0.75, yellow, + 1.0, green] + - type: gradient + bounds: 480 0 480 540 + start: 0 0 + end: 480 0 + stops: [0.0, blue, + 0.25, yellow, + 0.5, blue, + 0.5, white, + 1.0, [100,200,50,1]] + + diff --git a/third_party/webrender/wrench/reftests/gradient/gradient_cache_repeat.yaml b/third_party/webrender/wrench/reftests/gradient/gradient_cache_repeat.yaml new file mode 100644 index 00000000000..96dc7a40f9b --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/gradient_cache_repeat.yaml @@ -0,0 +1,119 @@ +--- +root: + items: + # non-repeating + - type: gradient + bounds: 100 50 500 10 + start: 100 0 + end: 200 0 + repeat: false + stops: [0.0, green, + 0.5, green, + 0.5, blue, + 1.0, blue ] + + # repeat 4 times + - type: gradient + bounds: 100 100 500 10 + start: 100 0 + end: 200 0 + repeat: true + stops: [0.0, green, + 0.5, green, + 0.5, blue, + 1.0, blue ] + + # same but start doesn't line up with 0 + - type: gradient + bounds: 100 150 500 10 + start: 125 0 + end: 225 0 + repeat: true + stops: [0.0, green, + 0.5, green, + 0.5, blue, + 1.0, blue ] + + # more hard stops, non-uniform distribution + - type: gradient + bounds: 100 250 500 10 + start: 200 0 + end: 300 0 + repeat: false + stops: [0.0, green, + 0.25, green, + 0.25, red, + 0.75, red, + 0.75, blue, + 1.0, blue ] + + # repeat the hard stops + - type: gradient + bounds: 100 300 500 10 + start: 200 0 + end: 300 0 + repeat: true + stops: [0.0, green, + 0.25, green, + 0.25, red, + 0.75, red, + 0.75, blue, + 1.0, blue ] + + # same but start doesn't line up with 0 + - type: gradient + bounds: 100 350 500 10 + start: 175 0 + end: 275 0 + repeat: true + stops: [0.0, green, + 0.25, green, + 0.25, red, + 0.75, red, + 0.75, blue, + 1.0, blue ] + + # the entire gradient from 0 to 1 is + # "offscreen", we're only seeing its + # repeats. the gradient is 100 wide + # and ends at -75, so the first + # three-quarters of it would be hidden, + # that is, it should start with blue. + - type: gradient + bounds: 100 400 500 10 + start: -175 0 + end: -75 0 + repeat: true + stops: [0.0, green, + 0.25, green, + 0.25, red, + 0.75, red, + 0.75, blue, + 1.0, blue ] + + # same but over on the right + - type: gradient + bounds: 100 450 500 10 + start: 575 0 + end: 675 0 + repeat: true + stops: [0.0, green, + 0.25, green, + 0.25, red, + 0.75, red, + 0.75, blue, + 1.0, blue ] + + # a repeat, but not really because only part + # of the gradient is visible + - type: gradient + bounds: 100 500 500 10 + start: -50 0 + end: 550 0 + repeat: true + stops: [0.0, green, + 0.25, green, + 0.25, red, + 0.75, red, + 0.75, blue, + 1.0, blue ] diff --git a/third_party/webrender/wrench/reftests/gradient/gradient_cache_repeat_ref.yaml b/third_party/webrender/wrench/reftests/gradient/gradient_cache_repeat_ref.yaml new file mode 100644 index 00000000000..3fe749e5456 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/gradient_cache_repeat_ref.yaml @@ -0,0 +1,119 @@ +--- +root: + items: + # non-repeating + - type: gradient + bounds: 100 50 500 10 + start: 100 0 + end: 200 0.001 + repeat: false + stops: [0.0, green, + 0.5, green, + 0.5, blue, + 1.0, blue ] + + # repeat 4 times + - type: gradient + bounds: 100 100 500 10 + start: 100 0 + end: 200 0.001 + repeat: true + stops: [0.0, green, + 0.5, green, + 0.5, blue, + 1.0, blue ] + + # same but start doesn't line up with 0 + - type: gradient + bounds: 100 150 500 10 + start: 125 0 + end: 225 0.001 + repeat: true + stops: [0.0, green, + 0.5, green, + 0.5, blue, + 1.0, blue ] + + # more hard stops, non-uniform distribution + - type: gradient + bounds: 100 250 500 10 + start: 200 0 + end: 300 0.001 + repeat: false + stops: [0.0, green, + 0.25, green, + 0.25, red, + 0.75, red, + 0.75, blue, + 1.0, blue ] + + # repeat the hard stops + - type: gradient + bounds: 100 300 500 10 + start: 200 0 + end: 300 0.001 + repeat: true + stops: [0.0, green, + 0.25, green, + 0.25, red, + 0.75, red, + 0.75, blue, + 1.0, blue ] + + # same but start doesn't line up with 0 + - type: gradient + bounds: 100 350 500 10 + start: 175 0 + end: 275 0.001 + repeat: true + stops: [0.0, green, + 0.25, green, + 0.25, red, + 0.75, red, + 0.75, blue, + 1.0, blue ] + + # the entire gradient from 0 to 1 is + # "offscreen", we're only seeing its + # repeats. the gradient is 100 wide + # and ends at -75, so the first + # three-quarters of it would be hidden, + # that is, it should start with blue. + - type: gradient + bounds: 100 400 500 10 + start: -175 0 + end: -75 0.001 + repeat: true + stops: [0.0, green, + 0.25, green, + 0.25, red, + 0.75, red, + 0.75, blue, + 1.0, blue ] + + # same but over on the right + - type: gradient + bounds: 100 450 500 10 + start: 575 0 + end: 675 0.001 + repeat: true + stops: [0.0, green, + 0.25, green, + 0.25, red, + 0.75, red, + 0.75, blue, + 1.0, blue ] + + # a repeat, but not really because only part + # of the gradient is visible + - type: gradient + bounds: 100 500 500 10 + start: -50 0 + end: 550 0.001 + repeat: true + stops: [0.0, green, + 0.25, green, + 0.25, red, + 0.75, red, + 0.75, blue, + 1.0, blue ] diff --git a/third_party/webrender/wrench/reftests/gradient/linear-adjust-tile-size-ref.yaml b/third_party/webrender/wrench/reftests/gradient/linear-adjust-tile-size-ref.yaml new file mode 100644 index 00000000000..c9145fc5e69 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/linear-adjust-tile-size-ref.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: gradient + bounds: 15.47998046875 18 684.39990234375 643.199951171875 + start: 10.286011695861816 653.47998046875 + end: 143.13165283203125 520.7279663085938 + stops: [0.0, red, 1.0, blue] + repeat: true diff --git a/third_party/webrender/wrench/reftests/gradient/linear-adjust-tile-size.yaml b/third_party/webrender/wrench/reftests/gradient/linear-adjust-tile-size.yaml new file mode 100644 index 00000000000..4d78b9b99e5 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/linear-adjust-tile-size.yaml @@ -0,0 +1,10 @@ +--- +root: + items: + - type: gradient + bounds: 15.47998046875 18 684.39990234375 643.199951171875 + tile-size: 684.4000244140625 643.2000122070313 + start: 10.286011695861816 653.47998046875 + end: 143.13165283203125 520.7279663085938 + stops: [0.0, red, 1.0, blue] + repeat: true diff --git a/third_party/webrender/wrench/reftests/gradient/linear-aligned-border-radius.png b/third_party/webrender/wrench/reftests/gradient/linear-aligned-border-radius.png new file mode 100644 index 0000000000000000000000000000000000000000..2cdeeb679a8d2189b01142f70a3a57b9243f557b GIT binary patch literal 6832 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 literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/gradient/linear-aligned-border-radius.yaml b/third_party/webrender/wrench/reftests/gradient/linear-aligned-border-radius.yaml new file mode 100644 index 00000000000..a79b4423c3e --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/linear-aligned-border-radius.yaml @@ -0,0 +1,46 @@ +--- +root: + items: + - type: clip + bounds: [20, 20, 100, 100] + complex: + - rect: [20, 20, 100, 100] + radius: 32 + items: + - type: gradient + bounds: 20 20 100 100 + start: 50 0 + end: 50 100 + stops: [0.0, red, 1.0, yellow] + + - type: rect + bounds: [130, 10, 120, 120] + color: blue + + - type: clip + bounds: [140, 20, 100, 100] + complex: + - rect: [140, 20, 100, 100] + radius: 32 + items: + - type: gradient + bounds: 140 20 100 100 + start: 50 0 + end: 50 100 + stops: [0.0, red, 1.0, yellow] + + - type: rect + bounds: [260, 10, 120, 120] + color: black + + - type: clip + bounds: [270, 20, 100, 100] + complex: + - rect: [270, 20, 100, 100] + radius: 32 + items: + - type: gradient + bounds: 270 20 100 100 + start: 50 0 + end: 50 100 + stops: [0.0, red, 1.0, yellow] diff --git a/third_party/webrender/wrench/reftests/gradient/linear-aligned-clip-ref.yaml b/third_party/webrender/wrench/reftests/gradient/linear-aligned-clip-ref.yaml new file mode 100644 index 00000000000..08a395dc71d --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/linear-aligned-clip-ref.yaml @@ -0,0 +1,16 @@ +--- +root: + items: + # an aligned gradient from [0, 400] + - type: gradient + bounds: 0 0 200 400 + start: 100 0 + end: 100 400 + stops: [0.0, green, 1.0, blue] + # manual clipping + - type: rect + bounds: 0 0 200 100 + color: white + - type: rect + bounds: 0 300 200 100 + color: white diff --git a/third_party/webrender/wrench/reftests/gradient/linear-aligned-clip.yaml b/third_party/webrender/wrench/reftests/gradient/linear-aligned-clip.yaml new file mode 100644 index 00000000000..fad030724f2 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/linear-aligned-clip.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + # an aligned gradient from [0, 400] and clipped to [100, 300] + - type: gradient + bounds: 0 100 200 200 + start: 100 -100 + end: 100 300 + stops: [0.0, green, 1.0, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/linear-backdrop-ref.yaml b/third_party/webrender/wrench/reftests/gradient/linear-backdrop-ref.yaml new file mode 100644 index 00000000000..7cc02b573c0 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/linear-backdrop-ref.yaml @@ -0,0 +1,11 @@ +--- +root: + items: + - type: rect + bounds: 0 0 800 450 + color: red + - type: gradient + bounds: 0 0 800 450 + start: 100 100 + end: 700 350 + stops: [ 0.0, [255, 255, 255, 1], 1.0, [0,0,0,1] ] diff --git a/third_party/webrender/wrench/reftests/gradient/linear-backdrop-with-spacing-ref.yaml b/third_party/webrender/wrench/reftests/gradient/linear-backdrop-with-spacing-ref.yaml new file mode 100644 index 00000000000..e72d1564338 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/linear-backdrop-with-spacing-ref.yaml @@ -0,0 +1,14 @@ +--- +root: + items: + - type: rect + bounds: 0 0 800 450 + color: red + - type: gradient + bounds: 0 0 800 450 + start: 20 20 + end: 80 50 + stops: [ 0.0, [255, 255, 255, 1], 1.0, [0,0,0,1] ] + tile-size: 100 100 + tile-spacing: 20 20 + diff --git a/third_party/webrender/wrench/reftests/gradient/linear-backdrop-with-spacing.yaml b/third_party/webrender/wrench/reftests/gradient/linear-backdrop-with-spacing.yaml new file mode 100644 index 00000000000..083222b6ac7 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/linear-backdrop-with-spacing.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - type: rect + bounds: 0 0 800 450 + color: red + - type: "scroll-frame" + bounds: 0 0 800 450 + clip-rect: 0 0 800 450 + id: 2 + - type: gradient + bounds: 0 0 800 450 + "clip-and-scroll": 2 + start: 20 20 + end: 80 50 + stops: [ 0.0, [255, 255, 255, 1], 1.0, [0,0,0,1] ] + tile-size: 100 100 + tile-spacing: 20 20 diff --git a/third_party/webrender/wrench/reftests/gradient/linear-backdrop.yaml b/third_party/webrender/wrench/reftests/gradient/linear-backdrop.yaml new file mode 100644 index 00000000000..4a4eea5b66a --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/linear-backdrop.yaml @@ -0,0 +1,16 @@ +--- +root: + items: + - type: rect + bounds: 0 0 800 450 + color: red + - type: "scroll-frame" + bounds: 0 0 800 450 + clip-rect: 0 0 800 450 + id: 2 + - type: gradient + bounds: 0 0 800 450 + "clip-and-scroll": 2 + start: 100 100 + end: 700 350 + stops: [ 0.0, [255, 255, 255, 1], 1.0, [0,0,0,1] ] diff --git a/third_party/webrender/wrench/reftests/gradient/linear-clamp-1-ref.yaml b/third_party/webrender/wrench/reftests/gradient/linear-clamp-1-ref.yaml new file mode 100644 index 00000000000..81c366d8589 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/linear-clamp-1-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 200 100 + stops: [0.0, blue, 0.5, blue, 0.5, red, 1.0, red] diff --git a/third_party/webrender/wrench/reftests/gradient/linear-clamp-1a.yaml b/third_party/webrender/wrench/reftests/gradient/linear-clamp-1a.yaml new file mode 100644 index 00000000000..b83963a37a1 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/linear-clamp-1a.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 100 100 + stops: [0.0, blue, 1.0, blue, 1.0, red] diff --git a/third_party/webrender/wrench/reftests/gradient/linear-clamp-1b.yaml b/third_party/webrender/wrench/reftests/gradient/linear-clamp-1b.yaml new file mode 100644 index 00000000000..ffe33919995 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/linear-clamp-1b.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 100 100 + end: 200 100 + stops: [0.0, blue, 0.0, red, 1.0, red] diff --git a/third_party/webrender/wrench/reftests/gradient/linear-clamp-2-ref.yaml b/third_party/webrender/wrench/reftests/gradient/linear-clamp-2-ref.yaml new file mode 100644 index 00000000000..8eb475d0a50 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/linear-clamp-2-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 200 100 + stops: [0.0, blue, 0.25, blue, 0.25, green, 0.75, green, 0.75, red, 1.0, red] diff --git a/third_party/webrender/wrench/reftests/gradient/linear-clamp-2.yaml b/third_party/webrender/wrench/reftests/gradient/linear-clamp-2.yaml new file mode 100644 index 00000000000..48428b974ac --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/linear-clamp-2.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 50 100 + end: 150 100 + stops: [0.0, blue, 0.0, green, 1.0, green, 1.0, red] diff --git a/third_party/webrender/wrench/reftests/gradient/linear-double.yaml b/third_party/webrender/wrench/reftests/gradient/linear-double.yaml new file mode 100644 index 00000000000..c9e4a44d846 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/linear-double.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: gradient + bounds: 300 50 200 200 + start: 0 100 + end: 200 100 + stops: [0.0, blue, 1.0, red] + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 200 100 + stops: [0.0, green, 1.0, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/linear-hard-stop-ref.png b/third_party/webrender/wrench/reftests/gradient/linear-hard-stop-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..a863648a3fdcb14eb9f31a3d09647bc2016f0fe2 GIT binary patch literal 2026 zcmeIz>q}E%90%}YYV(pDO>$=Q%n~YcCPVPjre#@Xk&2WJG?@kEZ7U6Fn^SQh;-xK! z5Suwe$`{UDZ%p&D7V4>(_9j&F7A>a~DeEA*oU`A4zUw~_d~tX<59d6e@9+Gc!&8@$ zmg46V;zJOGU+Tr=OoDLZxh}80Fe9%}mk|W(b!u|bRawjLFNfK|nZcvB*>hq3ULJd@ z&$Mk1VgAQmcdkjF?Lk%3Rh;LWJ@r1Q$50bloF|yva9hrPwl-L4Tx?kkchLJ-M2J7H z&Cj&w>rNORN>pW*UW(M`Xk&-h-dr(Asq2>NClSocb!U(FM3<#hXece1+dn@aV|rVW z)5yv?X`cSRF@03@K+V|Red?2^D66JDel3^8P@?$gmHL&tTJfwPjdqXduqN0xC4u@4 z`lC#|Fgy@7WR$2@zr_K@(UOQ!R0?3>%yY!rE%yM+Pee?iMRcb%ulC50hsC$Ibyoe#A)H(okR$olgL~9^2$-F?W z)3O1%`t6snfK{7BX!tXxjKq}I^;49{UN{uss#{U61Ww7HM{ZD%2wb9e1-Ut{4mh28 z4LPn_1DwXnLQX^10>@(7k?XK*0yk(+MUJsafg5-5kXw)mD3QFV61l&M0pJGsM&v34 zxxnRW=aC!Yb_3UKUP11(x(T=@D~X(rZUD}g=|HaAVh66zei^wnTQP8E$98p#GN?{o z=-lSYg}@2MO`a>;^X$uvl{uVgc?Gw~JXS&nqK2=OB$q;_k)*6IKzq5^J&Si I&Wct403M84Z2$lO literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/gradient/linear-hard-stop.yaml b/third_party/webrender/wrench/reftests/gradient/linear-hard-stop.yaml new file mode 100644 index 00000000000..b9249e7f2af --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/linear-hard-stop.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 0 0 + end: 0 100 + stops: [0.0, blue, 0.5 , red, 0.5, green] diff --git a/third_party/webrender/wrench/reftests/gradient/linear-ref.png b/third_party/webrender/wrench/reftests/gradient/linear-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..999c831000d806e105efcbe5639c5ae58f47031b GIT binary patch literal 3576 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4mO}jWo=(6kZP+6i6{w5ELSKf%1_J8NmVGR zEJ#&It;kGcV5qoN+INuC*^tNi{mezm7MqlhKc4czzNEP?zWvGM;QNkZhd8Fb{=M^b zb`a~)S>%1*XS zQL?w=vZ=5F8jzb>lBiITo0C^;Rbi_HHrEQs1_|pcDS(xfWZNo5_y#CA=NF|anCO}4 z8R)uJWR@8z*>Ne@6s4qD1-ZCEjR5j&l`=|73as??%gf94%8m8%i_-NCEiEne4UF`S zjC6r2bc-wVN)jt{^NN*0MnKGPNi9w;$}A|!%+FH*nVFcBUs__Tqy&^#fEo@8eYoj) z#n3Eqn3~q#-qVC8eAlW07;5RQ`cy4krVBhlnw?4W^PXx$B>F!Z?7HXZE)aVa5VgO|5H?>zr1^z6~iRn z;8I4J2iuq&HZZVo3+ON$WD?K;vw;MN4J25&iIgN-A=HWor#Gifnkk#S<*8gYTcn}u z+a2xv5I2x&5}IQVZW<8(pt%AXo)r3Jn3?jxJvf!OfghBlNKIa-2@{+qNi)Ub7Prz% V{y?*HhfF}-BTrX9mvv4FO#u0Yr0DNwvmj)fY0%5`Q);d3|=z^W>iIe`4xq|9@xs z`1?J%&vN`bPfAvwElj`ulAGZ<2SW?zC>jj`>V?4Bz2EI>IUn%YGjIqGdQ{CQ{O-bc UP3gIoz%0h#>FVdQ&MBb@0Aa_eF8}}l literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/gradient/linear-stops.yaml b/third_party/webrender/wrench/reftests/gradient/linear-stops.yaml new file mode 100644 index 00000000000..f5b8bfbc990 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/linear-stops.yaml @@ -0,0 +1,7 @@ +root: + items: + - type: gradient + bounds: [0, 0, 200, 200] + start: 0 100 + end: 200 100 + stops: [0.0, red, 0.5, green, 1.0, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/linear.yaml b/third_party/webrender/wrench/reftests/gradient/linear.yaml new file mode 100644 index 00000000000..53d8d512cd2 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/linear.yaml @@ -0,0 +1,11 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 200 100 + stops: [0.0, red, 0.25, red, + 0.25, green, 0.5, green, + 0.5, blue, 0.75, blue, + 0.75, black, 1.0, black] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-conic-1-ref.yaml b/third_party/webrender/wrench/reftests/gradient/norm-conic-1-ref.yaml new file mode 100644 index 00000000000..a3bb7614638 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-conic-1-ref.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: conic-gradient + bounds: 50 50 200 200 + angle: 0.0 + center: 100 100 + stops: [0.0, green, 0.5, green, + 0.5, blue, 1.0, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-conic-1.yaml b/third_party/webrender/wrench/reftests/gradient/norm-conic-1.yaml new file mode 100644 index 00000000000..8bf6b734c21 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-conic-1.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: conic-gradient + bounds: 50 50 200 200 + angle: 0.0 + center: 100 100 + stops: [0.25, green, 0.5, green, + 0.5, blue, 0.75, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-conic-2-ref.yaml b/third_party/webrender/wrench/reftests/gradient/norm-conic-2-ref.yaml new file mode 100644 index 00000000000..a3bb7614638 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-conic-2-ref.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: conic-gradient + bounds: 50 50 200 200 + angle: 0.0 + center: 100 100 + stops: [0.0, green, 0.5, green, + 0.5, blue, 1.0, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-conic-2.yaml b/third_party/webrender/wrench/reftests/gradient/norm-conic-2.yaml new file mode 100644 index 00000000000..ba9b174b51b --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-conic-2.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: conic-gradient + bounds: 50 50 200 200 + angle: 0.0 + center: 100 100 + stops: [0.5, green, + 0.5, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-conic-3-ref.yaml b/third_party/webrender/wrench/reftests/gradient/norm-conic-3-ref.yaml new file mode 100644 index 00000000000..d5403c498f6 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-conic-3-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: conic-gradient + bounds: 50 50 200 200 + angle: 0.0 + center: 100 100 + stops: [0.0, blue, 1.0, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-conic-3.yaml b/third_party/webrender/wrench/reftests/gradient/norm-conic-3.yaml new file mode 100644 index 00000000000..962ff4e7aa6 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-conic-3.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: conic-gradient + bounds: 50 50 200 200 + angle: 0.0 + center: 100 100 + stops: [-0.5, green, + -0.5, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-conic-4-ref.yaml b/third_party/webrender/wrench/reftests/gradient/norm-conic-4-ref.yaml new file mode 100644 index 00000000000..6c0b6e508f8 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-conic-4-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: conic-gradient + bounds: 50 50 200 200 + angle: 0.0 + center: 100 100 + stops: [0.0, green, 1.0, green] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-conic-4.yaml b/third_party/webrender/wrench/reftests/gradient/norm-conic-4.yaml new file mode 100644 index 00000000000..6564d847216 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-conic-4.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: conic-gradient + bounds: 50 50 200 200 + angle: 0.0 + center: 100 100 + stops: [1.5, green, + 1.5, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-conic-degenerate-ref.yaml b/third_party/webrender/wrench/reftests/gradient/norm-conic-degenerate-ref.yaml new file mode 100644 index 00000000000..1ba6bd2f9e7 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-conic-degenerate-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: conic-gradient + bounds: 50 50 300 300 + angle: 0.0 + center: 150 150 + stops: [0.0, red, 1.0, red] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-conic-degenerate.yaml b/third_party/webrender/wrench/reftests/gradient/norm-conic-degenerate.yaml new file mode 100644 index 00000000000..be96200722b --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-conic-degenerate.yaml @@ -0,0 +1,14 @@ +# see: https://www.w3.org/TR/2012/CR-css3-images-20120417/#repeating-gradients +# the spec says that repeating gradients with color stops in the same offset +# must render as a solid rect with color equal to the average color of the +# gradient. Gecko and Blink seem to draw it with color equal to the last stop +# so that is the behavior tested here +--- +root: + items: + - type: conic-gradient + bounds: 50 50 300 300 + angle: 0.0 + center: 150 150 + stops: [0.5, blue, 0.5, red] + repeat: true diff --git a/third_party/webrender/wrench/reftests/gradient/norm-linear-1-ref.yaml b/third_party/webrender/wrench/reftests/gradient/norm-linear-1-ref.yaml new file mode 100644 index 00000000000..5b8a0b317ac --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-linear-1-ref.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 200 100 + stops: [0.0, green, 0.5, green, + 0.5, blue, 1.0, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-linear-1.yaml b/third_party/webrender/wrench/reftests/gradient/norm-linear-1.yaml new file mode 100644 index 00000000000..d79b8608b17 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-linear-1.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 200 100 + stops: [0.25, green, 0.5, green, + 0.5, blue, 0.75, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-linear-2-ref.yaml b/third_party/webrender/wrench/reftests/gradient/norm-linear-2-ref.yaml new file mode 100644 index 00000000000..5b8a0b317ac --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-linear-2-ref.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 200 100 + stops: [0.0, green, 0.5, green, + 0.5, blue, 1.0, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-linear-2.yaml b/third_party/webrender/wrench/reftests/gradient/norm-linear-2.yaml new file mode 100644 index 00000000000..aa3aa2afe13 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-linear-2.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 200 100 + stops: [0.5, green, + 0.5, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-linear-3-ref.yaml b/third_party/webrender/wrench/reftests/gradient/norm-linear-3-ref.yaml new file mode 100644 index 00000000000..62bfda97e6b --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-linear-3-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 200 100 + stops: [0.0, blue, 1.0, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-linear-3.yaml b/third_party/webrender/wrench/reftests/gradient/norm-linear-3.yaml new file mode 100644 index 00000000000..cc183715925 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-linear-3.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 200 100 + stops: [-0.5, green, + -0.5, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-linear-4-ref.yaml b/third_party/webrender/wrench/reftests/gradient/norm-linear-4-ref.yaml new file mode 100644 index 00000000000..bcd84d82943 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-linear-4-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 200 100 + stops: [0.0, green, 1.0, green] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-linear-4.yaml b/third_party/webrender/wrench/reftests/gradient/norm-linear-4.yaml new file mode 100644 index 00000000000..df622bf299f --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-linear-4.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 200 100 + stops: [1.5, green, + 1.5, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-linear-degenerate-ref.yaml b/third_party/webrender/wrench/reftests/gradient/norm-linear-degenerate-ref.yaml new file mode 100644 index 00000000000..ae61c486cbc --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-linear-degenerate-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 300 300 + start: 0 150 + end: 300 150 + stops: [0.0, red, 1.0, red] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-linear-degenerate.yaml b/third_party/webrender/wrench/reftests/gradient/norm-linear-degenerate.yaml new file mode 100644 index 00000000000..89b431df326 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-linear-degenerate.yaml @@ -0,0 +1,14 @@ +# see: https://www.w3.org/TR/2012/CR-css3-images-20120417/#repeating-gradients +# the spec says that repeating gradients with color stops in the same offset +# must render as a solid rect with color equal to the average color of the +# gradient. Gecko and Blink seem to draw it with color equal to the last stop +# so that is the behavior tested here +--- +root: + items: + - type: gradient + bounds: 50 50 300 300 + start: 0 150 + end: 300 150 + stops: [0.5, blue, 0.5, red] + repeat: true diff --git a/third_party/webrender/wrench/reftests/gradient/norm-radial-1-ref.yaml b/third_party/webrender/wrench/reftests/gradient/norm-radial-1-ref.yaml new file mode 100644 index 00000000000..a67b51ecf8a --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-radial-1-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: radial-gradient + bounds: 50 50 300 300 + center: 150 150 + radius: 200 200 + stops: [0.0, red, 0.5, red, 0.5, blue, 1.0, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-radial-1.yaml b/third_party/webrender/wrench/reftests/gradient/norm-radial-1.yaml new file mode 100644 index 00000000000..7ee6caeafaa --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-radial-1.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: radial-gradient + bounds: 50 50 300 300 + center: 150 150 + radius: 200 200 + stops: [0.5, red, 0.5, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-radial-2-ref.yaml b/third_party/webrender/wrench/reftests/gradient/norm-radial-2-ref.yaml new file mode 100644 index 00000000000..adfc8d70d49 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-radial-2-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: radial-gradient + bounds: 50 50 300 300 + center: 150 150 + radius: 200 200 + stops: [0.0, blue, 1.0, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-radial-2.yaml b/third_party/webrender/wrench/reftests/gradient/norm-radial-2.yaml new file mode 100644 index 00000000000..dac1b783616 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-radial-2.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: radial-gradient + bounds: 50 50 300 300 + center: 150 150 + radius: 200 200 + stops: [-0.5, red, -0.5, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-radial-3-ref.yaml b/third_party/webrender/wrench/reftests/gradient/norm-radial-3-ref.yaml new file mode 100644 index 00000000000..d90d1ee4f8a --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-radial-3-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: radial-gradient + bounds: 50 50 300 300 + center: 150 150 + radius: 200 200 + stops: [0.0, red, 1.0, red] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-radial-3.yaml b/third_party/webrender/wrench/reftests/gradient/norm-radial-3.yaml new file mode 100644 index 00000000000..fbd5dc929fe --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-radial-3.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: radial-gradient + bounds: 50 50 300 300 + center: 150 150 + radius: 200 200 + stops: [1.5, red, 1.5, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-radial-degenerate-ref.yaml b/third_party/webrender/wrench/reftests/gradient/norm-radial-degenerate-ref.yaml new file mode 100644 index 00000000000..afe59a770b9 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-radial-degenerate-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: radial-gradient + bounds: 50 50 300 300 + center: 150 150 + radius: 150 150 + stops: [0.0, red, 1.0, red] diff --git a/third_party/webrender/wrench/reftests/gradient/norm-radial-degenerate.yaml b/third_party/webrender/wrench/reftests/gradient/norm-radial-degenerate.yaml new file mode 100644 index 00000000000..26d99354756 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/norm-radial-degenerate.yaml @@ -0,0 +1,14 @@ +# see: https://www.w3.org/TR/2012/CR-css3-images-20120417/#repeating-gradients +# the spec says that repeating gradients with color stops in the same offset +# must render as a solid rect with color equal to the average color of the +# gradient. Gecko and Blink seem to draw it with color equal to the last stop +# so that is the behavior tested here +--- +root: + items: + - type: radial-gradient + bounds: 50 50 300 300 + center: 150 150 + radius: 150 150 + stops: [0.5, blue, 0.5, red] + repeat: true diff --git a/third_party/webrender/wrench/reftests/gradient/premultiplied-aligned-2.png b/third_party/webrender/wrench/reftests/gradient/premultiplied-aligned-2.png new file mode 100644 index 0000000000000000000000000000000000000000..b27b9df586035770229313f74b28222318a890d6 GIT binary patch literal 10340 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|7#K9PJzX3_D(1YsdXS4bT9ozT za{G1vcAQ_=azaYhK!SafdB}aO7e9ZVo`3$|jcvKNxBV}$R=5phgx_Yo38ePj;yVGP zzFjSI0LiZ3#=Hqg)ozuY0Hofoe%p`(R2-kno&uzPXN_tlC76Eiw^XSA_xOGNpP$q1 z|DCn}|EtcKKbk+ffA!Z5K)53ci6dVBg|Ys^;cJJlJw8)XG-P;G=-AOEId&wYNFetux|>u+OjYKPiW6|=S-2%(mC%TD>Q-iqI5JO~RJ z)L=TwcjEWo1eiZJFmL*sWUWvEvj^f%sHOXFFjnrDhk05a7_IR+qk%gbxH+RK7gQ>Z z77L)#ceGdl)l;KoF0eixEfz+Lh0)f+XjAOQuhDicutzc4i2(I{MmrIpZslkX4b)z4*}Q$iB}vEH|? literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/gradient/premultiplied-aligned-2.yaml b/third_party/webrender/wrench/reftests/gradient/premultiplied-aligned-2.yaml new file mode 100644 index 00000000000..ea8acd5f26e --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/premultiplied-aligned-2.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 200 100 + stops: [0.0, [255.0, 0.0, 0.0, 0.5], 0.5, [0.0, 255.0, 0.0, 0.5], 1.0, [0.0, 0.0, 255.0, 0.5]] diff --git a/third_party/webrender/wrench/reftests/gradient/premultiplied-aligned.png b/third_party/webrender/wrench/reftests/gradient/premultiplied-aligned.png new file mode 100644 index 0000000000000000000000000000000000000000..6fc18cd8c0eb2931605546f016a87a0b0363dd2a GIT binary patch literal 12560 zcmeAS@N?(olHy`uVBq!ia0y~yV2S`?4mP03zO)&47#Iw$d%8G=RLprhjgd>2d*ac3 zyZ)=5w~%csSopw3V3wPSCf`Y+SNZ>IerPKJLEK_?5dA2%Y6F5CN(YIxO(Qz(`WXjeKXp;e&$}dQ2+m*+xP$bb^rOl=ay%0o}N<+BKDrS zc^yO`b9;Aobo}U()4^?0^@cix?os7PVLTc#xKbZ1WWWiB_%t)B96cM42IOcG>mN-7 zqlsWN5#Vg@lRX$A4LQopW)|-lT$vY#5Pq#?|FW`IiL4-?M-%{J6AlrkkhF ztUU{C^y?dgh3f7!e|bL=P1YxwlHrii<3@wfJ@v)2x zjb@M0>@k`>Fovc_`)i~9wISVK8%@BY33xOCkB-OP95RXEI;ew;Hbac36dP3zZn|S> zzmJB@Xvo|gO#~Dsg8%o}yAfmBz@Bq9a7-JxkOdsDkLA|H#}|JyN(U3kdP++B7PG(h zM=!sii(cfvC;a_2Yx8u=-ylM|SWxhztZhWS5!zTebddP(4))jA!E0c^#EkBaj`uGP z{+bM0IfLv5Xi<6GHe&xi(6~I9I2PmLQsd5lZ9aUJ4b%(^UW&)f_q48;Wn?_X^GstykaB8uxZ_VC3qtR zJqa5pMf2|94)Wvm=bA+Yff1YU#2@D3;&SBlmjqWJy~k&iI~p3JiD5J^j1~o>CE;jE nI9d{pmV~1vA@-8+kNgeatj*g0n6CnNIxu*;`njxgN@xNA_?PT_ literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/gradient/premultiplied-aligned.yaml b/third_party/webrender/wrench/reftests/gradient/premultiplied-aligned.yaml new file mode 100644 index 00000000000..a709f845ea1 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/premultiplied-aligned.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 200 100 + stops: [0.0, red, 0.5, [0.0, 0.0, 0.0, 0.0], 1.0, green] diff --git a/third_party/webrender/wrench/reftests/gradient/premultiplied-angle-2.png b/third_party/webrender/wrench/reftests/gradient/premultiplied-angle-2.png new file mode 100644 index 0000000000000000000000000000000000000000..ea7a5cf7efdba8fd23348182f26e7595b114b778 GIT binary patch literal 19390 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 N)gD{(;$4TL{s*)bdv5>$ literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/gradient/premultiplied-radial-2.yaml b/third_party/webrender/wrench/reftests/gradient/premultiplied-radial-2.yaml new file mode 100644 index 00000000000..3e682328d1b --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/premultiplied-radial-2.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: radial-gradient + bounds: 50 50 200 200 + center: 100 100 + radius: 100 100 + stops: [0.0, [255.0, 0.0, 0.0, 0.5], 0.5, [0.0, 255.0, 0.0, 0.5], 1.0, [0.0, 0.0, 255.0, 0.5]] diff --git a/third_party/webrender/wrench/reftests/gradient/premultiplied-radial.png b/third_party/webrender/wrench/reftests/gradient/premultiplied-radial.png new file mode 100644 index 0000000000000000000000000000000000000000..0b2b2c2f21f0550118e2889cba3a02ba63da4092 GIT binary patch literal 18857 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 EAKyz&jsO4v literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/gradient/radial-ellipse.yaml b/third_party/webrender/wrench/reftests/gradient/radial-ellipse.yaml new file mode 100644 index 00000000000..7c733f72234 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/radial-ellipse.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: radial-gradient + bounds: 50 50 300 300 + center: 150 150 + radius: 100 200 + stops: [0, red, 1, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/radial-zero-size-1.yaml b/third_party/webrender/wrench/reftests/gradient/radial-zero-size-1.yaml new file mode 100644 index 00000000000..43e4ef323da --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/radial-zero-size-1.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: radial-gradient + bounds: 50 50 200 200 + center: 100 100 + radius: 100 0 + stops: [0.0, blue, 1.0, red] diff --git a/third_party/webrender/wrench/reftests/gradient/radial-zero-size-2.yaml b/third_party/webrender/wrench/reftests/gradient/radial-zero-size-2.yaml new file mode 100644 index 00000000000..94bf6eae730 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/radial-zero-size-2.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: radial-gradient + bounds: 50 50 200 200 + center: 100 100 + radius: 0 100 + stops: [0.0, blue, 1.0, red] diff --git a/third_party/webrender/wrench/reftests/gradient/radial-zero-size-3.yaml b/third_party/webrender/wrench/reftests/gradient/radial-zero-size-3.yaml new file mode 100644 index 00000000000..3efa1ec307d --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/radial-zero-size-3.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - type: rect + bounds: 50 50 200 200 + color: red diff --git a/third_party/webrender/wrench/reftests/gradient/radial-zero-size-ref.yaml b/third_party/webrender/wrench/reftests/gradient/radial-zero-size-ref.yaml new file mode 100644 index 00000000000..b3770b752e1 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/radial-zero-size-ref.yaml @@ -0,0 +1,8 @@ +--- +root: + items: + - type: radial-gradient + bounds: 50 50 200 200 + center: 100 100 + radius: 100 100 + stops: [0.0, red, 1.0, red] diff --git a/third_party/webrender/wrench/reftests/gradient/reftest.list b/third_party/webrender/wrench/reftests/gradient/reftest.list new file mode 100644 index 00000000000..847c06ea8ee --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/reftest.list @@ -0,0 +1,106 @@ +platform(linux,mac) == premultiplied-aligned.yaml premultiplied-aligned.png +platform(linux,mac) == premultiplied-angle.yaml premultiplied-angle.png +platform(linux,mac) == premultiplied-radial.yaml premultiplied-radial.png +platform(linux,mac) == premultiplied-conic.yaml premultiplied-conic.png + +platform(linux,mac) == premultiplied-aligned-2.yaml premultiplied-aligned-2.png +platform(linux,mac) == premultiplied-angle-2.yaml premultiplied-angle-2.png +platform(linux,mac) == premultiplied-radial-2.yaml premultiplied-radial-2.png +platform(linux,mac) == premultiplied-conic-2.yaml premultiplied-conic-2.png + +== linear.yaml linear-ref.png +== linear-reverse.yaml linear-ref.png +platform(linux,mac) fuzzy(1,35000) == linear-stops.yaml linear-stops-ref.png + +== linear-clamp-1a.yaml linear-clamp-1-ref.yaml +== linear-clamp-1b.yaml linear-clamp-1-ref.yaml +== linear-clamp-2.yaml linear-clamp-2-ref.yaml + +fuzzy-range(<=1,*4800) == linear-hard-stop.yaml linear-hard-stop-ref.png + +# dithering requires us to fuzz here +fuzzy(1,20000) == linear.yaml linear-ref.yaml +fuzzy(1,20000) == linear-reverse.yaml linear-ref.yaml + +fuzzy(1,15200) == linear-aligned-clip.yaml linear-aligned-clip-ref.yaml + +platform(linux,mac) fuzzy(1,80000) == radial-circle.yaml radial-circle-ref.png +platform(linux,mac) fuzzy(1,80000) == radial-ellipse.yaml radial-ellipse-ref.png + +!= radial-circle.yaml radial-ellipse.yaml + +== norm-linear-1.yaml norm-linear-1-ref.yaml +== norm-linear-2.yaml norm-linear-2-ref.yaml +== norm-linear-3.yaml norm-linear-3-ref.yaml +== norm-linear-4.yaml norm-linear-4-ref.yaml +== norm-linear-degenerate.yaml norm-radial-degenerate-ref.yaml + +== norm-radial-1.yaml norm-radial-1-ref.yaml +== norm-radial-2.yaml norm-radial-2-ref.yaml +== norm-radial-3.yaml norm-radial-3-ref.yaml +== norm-radial-degenerate.yaml norm-radial-degenerate-ref.yaml + +== norm-conic-1.yaml norm-conic-1-ref.yaml +== norm-conic-2.yaml norm-conic-2-ref.yaml +== norm-conic-3.yaml norm-conic-3-ref.yaml +== norm-conic-4.yaml norm-conic-4-ref.yaml +== norm-conic-degenerate.yaml norm-conic-degenerate-ref.yaml + +# fuzzy because of differences from normalization +# this might be able to be improved +fuzzy(255,1200) == repeat-linear.yaml repeat-linear-ref.yaml +fuzzy(255,1200) == repeat-linear-reverse.yaml repeat-linear-ref.yaml +fuzzy(255,2664) == repeat-radial.yaml repeat-radial-ref.yaml +fuzzy(255,2664) == repeat-radial-negative.yaml repeat-radial-ref.yaml +fuzzy(255,1652) == repeat-conic.yaml repeat-conic-ref.yaml +fuzzy(255,1652) == repeat-conic-negative.yaml repeat-conic-ref.yaml + +# fuzzy because of thin spaced out column of pixels that are 1 off +fuzzy(1,83164) == tiling-linear-1.yaml tiling-linear-1-ref.yaml +fuzzy(1,46293) == tiling-linear-2.yaml tiling-linear-2-ref.yaml +fuzzy(1,62154) == tiling-linear-3.yaml tiling-linear-3-ref.yaml + +fuzzy(1,17) == tiling-radial-1.yaml tiling-radial-1-ref.yaml +fuzzy(1,1) == tiling-radial-2.yaml tiling-radial-2-ref.yaml +fuzzy(1,3) == tiling-radial-3.yaml tiling-radial-3-ref.yaml +fuzzy(1,17) == tiling-radial-4.yaml tiling-radial-4-ref.yaml + +fuzzy(1,17) == tiling-conic-1.yaml tiling-conic-1-ref.yaml +fuzzy(1,1) == tiling-conic-2.yaml tiling-conic-2-ref.yaml +fuzzy(1,3) == tiling-conic-3.yaml tiling-conic-3-ref.yaml + +== radial-zero-size-1.yaml radial-zero-size-ref.yaml +== radial-zero-size-2.yaml radial-zero-size-ref.yaml +== radial-zero-size-3.yaml radial-zero-size-ref.yaml + +== linear-adjust-tile-size.yaml linear-adjust-tile-size-ref.yaml + +platform(linux,mac) == linear-aligned-border-radius.yaml linear-aligned-border-radius.png +# interpolation fuzz from sampling texture-baked gradient ramps +platform(linux,mac) fuzzy-range(<=1,*1404) == repeat-border-radius.yaml repeat-border-radius.png + +== conic.yaml conic-ref.yaml +fuzzy(1,57) == conic-simple.yaml conic-simple.png +fuzzy(255,166) == conic-angle.yaml conic-angle.png +== conic-center.yaml conic-center.png +fuzzy(1,2) == conic-angle-wraparound.yaml conic-angle.yaml +fuzzy(1,1) == conic-angle-wraparound-negative.yaml conic-angle.yaml +fuzzy(1,115) == conic-color-wheel.yaml conic-color-wheel.png + +# gradient caching tests +# replaces a computed gradient by a sampled texture, so a lot of off-by-one +# variation from interpolation, which is fine: +fuzzy-range(<=1,*195000) == gradient_cache_5stops.yaml gradient_cache_5stops_ref.yaml +fuzzy-range(<=1,*169000) == gradient_cache_5stops_vertical.yaml gradient_cache_5stops_vertical_ref.yaml +== gradient_cache_hardstop.yaml gradient_cache_hardstop_ref.yaml +== gradient_cache_hardstop_clip.yaml gradient_cache_hardstop_clip_ref.yaml +== gradient_cache_clamp.yaml gradient_cache_clamp_ref.yaml +== gradient_cache_repeat.yaml gradient_cache_repeat_ref.yaml + +# Recognize opaque tiles with gradient backgrounds +== conic-backdrop-with-spacing.yaml conic-backdrop-with-spacing-ref.yaml +== conic-backdrop.yaml conic-backdrop-ref.yaml +== linear-backdrop-with-spacing.yaml linear-backdrop-with-spacing-ref.yaml +== linear-backdrop.yaml linear-backdrop-ref.yaml +== radial-backdrop-with-spacing.yaml radial-backdrop-with-spacing-ref.yaml +== radial-backdrop.yaml radial-backdrop-ref.yaml diff --git a/third_party/webrender/wrench/reftests/gradient/repeat-border-radius.png b/third_party/webrender/wrench/reftests/gradient/repeat-border-radius.png new file mode 100644 index 0000000000000000000000000000000000000000..7896fa7e2aed42d9a011a7ecf1f54d45da7307d7 GIT binary patch literal 31964 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 MiCZ0W*LofQAO3kA6#xJL literal 0 HcmV?d00001 diff --git a/third_party/webrender/wrench/reftests/gradient/repeat-border-radius.yaml b/third_party/webrender/wrench/reftests/gradient/repeat-border-radius.yaml new file mode 100644 index 00000000000..b578f80b5d3 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/repeat-border-radius.yaml @@ -0,0 +1,137 @@ +--- +root: + items: + - type: clip + bounds: [20, 20, 100, 100] + complex: + - rect: [20, 20, 100, 100] + radius: 32 + items: + - type: gradient + bounds: 20 20 100 100 + start: 50 0 + end: 50 100 + stops: [0.0, red, 1.0, yellow] + repeat: true + + - type: rect + bounds: [130, 10, 120, 120] + color: blue + + - type: clip + bounds: [140, 20, 100, 100] + complex: + - rect: [140, 20, 100, 100] + radius: 32 + items: + - type: gradient + bounds: 140 20 100 100 + start: 50 0 + end: 50 100 + stops: [0.2, red, 1.0, yellow] + + - type: rect + bounds: [260, 10, 120, 120] + color: black + + - type: clip + bounds: [270, 20, 100, 100] + complex: + - rect: [270, 20, 100, 100] + radius: 32 + items: + - type: gradient + bounds: 270 20 100 100 + start: 50 0 + end: 51 100 + stops: [0.0, red, 1.0, yellow] + + - type: clip + bounds: [20, 160, 100, 100] + complex: + - rect: [20, 160, 100, 100] + radius: 32 + items: + - type: radial-gradient + bounds: 20 160 100 100 + center: 50 50 + radius: 25 25 + stops: [0.0, red, 1.0, yellow] + + - type: rect + bounds: [130, 150, 120, 120] + color: blue + + - type: clip + bounds: [140, 160, 100, 100] + complex: + - rect: [140, 160, 100, 100] + radius: 32 + items: + - type: radial-gradient + bounds: 140 160 100 100 + center: 50 50 + radius: 25 25 + stops: [0.0, red, 1.0, yellow] + + - type: rect + bounds: [260, 150, 120, 120] + color: black + + - type: clip + bounds: [270, 160, 100, 100] + complex: + - rect: [270, 160, 100, 100] + radius: 32 + items: + - type: radial-gradient + bounds: 270 160 100 100 + center: 50 50 + radius: 25 25 + stops: [0.0, red, 1.0, yellow] + + - type: clip + bounds: [20, 300, 100, 100] + complex: + - rect: [20, 300, 100, 100] + radius: 32 + items: + - type: conic-gradient + bounds: 20 300 100 100 + angle: 0.0 + center: 50 50 + stops: [0.0, red, 1.0, yellow] + repeat: true + + - type: rect + bounds: [130, 290, 120, 120] + color: blue + + - type: clip + bounds: [140, 300, 100, 100] + complex: + - rect: [140, 300, 100, 100] + radius: 32 + items: + - type: conic-gradient + bounds: 140 300 100 100 + angle: 0.0 + center: 50 50 + stops: [0.0, red, 1.0, yellow] + + - type: rect + bounds: [260, 290, 120, 120] + color: black + + - type: clip + bounds: [270, 300, 100, 100] + complex: + - rect: [270, 300, 100, 100] + radius: 32 + items: + - type: conic-gradient + bounds: 270 300 100 100 + angle: 0.0 + center: 50 50 + stops: [0.0, red, 1.0, yellow] + diff --git a/third_party/webrender/wrench/reftests/gradient/repeat-conic-negative.yaml b/third_party/webrender/wrench/reftests/gradient/repeat-conic-negative.yaml new file mode 100644 index 00000000000..e1043562da5 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/repeat-conic-negative.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: conic-gradient + bounds: 50 50 300 300 + angle: -0.62831853 + center: 150 150 + stops: [0.1, red, 0.2, red, 0.2, blue, 0.3, blue] + repeat: true diff --git a/third_party/webrender/wrench/reftests/gradient/repeat-conic-ref.yaml b/third_party/webrender/wrench/reftests/gradient/repeat-conic-ref.yaml new file mode 100644 index 00000000000..b98cc378a7a --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/repeat-conic-ref.yaml @@ -0,0 +1,27 @@ +--- +root: + items: + - type: conic-gradient + bounds: 50 50 300 300 + angle: 0.0 + center: 150 150 + stops: [0.0, red, + 0.1, red, + 0.1, blue, + 0.2, blue, + 0.2, red, + 0.3, red, + 0.3, blue, + 0.4, blue, + 0.4, red, + 0.5, red, + 0.5, blue, + 0.6, blue, + 0.6, red, + 0.7, red, + 0.7, blue, + 0.8, blue, + 0.8, red, + 0.9, red, + 0.9, blue, + 1.0, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/repeat-conic.yaml b/third_party/webrender/wrench/reftests/gradient/repeat-conic.yaml new file mode 100644 index 00000000000..bbc4202aec2 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/repeat-conic.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: conic-gradient + bounds: 50 50 300 300 + angle: 0.0 + center: 150 150 + stops: [0.1, blue, 0.2, blue, 0.2, red, 0.3, red] + repeat: true diff --git a/third_party/webrender/wrench/reftests/gradient/repeat-linear-ref.yaml b/third_party/webrender/wrench/reftests/gradient/repeat-linear-ref.yaml new file mode 100644 index 00000000000..420a08db01f --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/repeat-linear-ref.yaml @@ -0,0 +1,27 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 300 300 + start: 0 150 + end: 300 150 + stops: [0.0, red, + 0.1, red, + 0.1, blue, + 0.2, blue, + 0.2, red, + 0.3, red, + 0.3, blue, + 0.4, blue, + 0.4, red, + 0.5, red, + 0.5, blue, + 0.6, blue, + 0.6, red, + 0.7, red, + 0.7, blue, + 0.8, blue, + 0.8, red, + 0.9, red, + 0.9, blue, + 1.0, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/repeat-linear-reverse.yaml b/third_party/webrender/wrench/reftests/gradient/repeat-linear-reverse.yaml new file mode 100644 index 00000000000..a81bafe1bdc --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/repeat-linear-reverse.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 300 300 + start: 300 150 + end: 0 150 + stops: [0.1, red, 0.2, red, 0.2, blue, 0.3, blue] + repeat: true diff --git a/third_party/webrender/wrench/reftests/gradient/repeat-linear.yaml b/third_party/webrender/wrench/reftests/gradient/repeat-linear.yaml new file mode 100644 index 00000000000..63e136bc8ea --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/repeat-linear.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: gradient + bounds: 50 50 300 300 + start: 0 150 + end: 300 150 + stops: [0.1, blue, 0.2, blue, 0.2, red, 0.3, red] + repeat: true diff --git a/third_party/webrender/wrench/reftests/gradient/repeat-radial-negative.yaml b/third_party/webrender/wrench/reftests/gradient/repeat-radial-negative.yaml new file mode 100644 index 00000000000..949455f8937 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/repeat-radial-negative.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: radial-gradient + bounds: 50 50 300 300 + center: 150 150 + radius: 150 150 + stops: [-0.3, blue, -0.2, blue, -0.2, red, -0.1, red] + repeat: true diff --git a/third_party/webrender/wrench/reftests/gradient/repeat-radial-ref.yaml b/third_party/webrender/wrench/reftests/gradient/repeat-radial-ref.yaml new file mode 100644 index 00000000000..d2c0292e6e9 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/repeat-radial-ref.yaml @@ -0,0 +1,38 @@ +--- +root: + items: + - type: radial-gradient + bounds: 50 50 300 300 + center: 150 150 + radius: 150 150 + # note: we need stops up to 1.4 because a repeating radial gradient + # will fill the whole rect beyond 1.0. So the furthest radius we have + # to fill in is the diagonal of the unit square + stops: [0.0, red, + 0.1, red, + 0.1, blue, + 0.2, blue, + 0.2, red, + 0.3, red, + 0.3, blue, + 0.4, blue, + 0.4, red, + 0.5, red, + 0.5, blue, + 0.6, blue, + 0.6, red, + 0.7, red, + 0.7, blue, + 0.8, blue, + 0.8, red, + 0.9, red, + 0.9, blue, + 1.0, blue, + 1.0, red, + 1.1, red, + 1.1, blue, + 1.2, blue, + 1.2, red, + 1.3, red, + 1.3, blue, + 1.4, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/repeat-radial.yaml b/third_party/webrender/wrench/reftests/gradient/repeat-radial.yaml new file mode 100644 index 00000000000..04c676c25bf --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/repeat-radial.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: radial-gradient + bounds: 50 50 300 300 + center: 150 150 + radius: 150 150 + stops: [0.1, blue, 0.2, blue, 0.2, red, 0.3, red] + repeat: true diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-conic-1-ref.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-conic-1-ref.yaml new file mode 100644 index 00000000000..6b93e28fa10 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-conic-1-ref.yaml @@ -0,0 +1,27 @@ +--- +root: + items: + # top left + - type: conic-gradient + bounds: 50 50 200 200 + angle: 0.0 + center: 100 100 + stops: [0, red, 1, blue] + # top right + - type: conic-gradient + bounds: 350 50 200 200 + angle: 0.0 + center: 100 100 + stops: [0, red, 1, blue] + # bottom left + - type: conic-gradient + bounds: 50 350 200 200 + angle: 0.0 + center: 100 100 + stops: [0, red, 1, blue] + # bottom right + - type: conic-gradient + bounds: 350 350 200 200 + angle: 0.0 + center: 100 100 + stops: [0, red, 1, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-conic-1.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-conic-1.yaml new file mode 100644 index 00000000000..71ffe80b227 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-conic-1.yaml @@ -0,0 +1,11 @@ +--- +root: + items: + # basic - 4 tiles spaced out with no clipping + - type: conic-gradient + bounds: 50 50 500 500 + angle: 0.0 + center: 100 100 + stops: [0, red, 1, blue] + tile-size: 200 200 + tile-spacing: 100 100 diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-conic-2-ref.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-conic-2-ref.yaml new file mode 100644 index 00000000000..4360aa204cb --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-conic-2-ref.yaml @@ -0,0 +1,27 @@ +--- +root: + items: + # top left + - type: conic-gradient + bounds: 50 50 200 200 + angle: 0.0 + center: 100 100 + stops: [0, red, 1, blue] + # top right + - type: conic-gradient + bounds: 350 50 100 200 + angle: 0.0 + center: 100 100 + stops: [0, red, 1, blue] + # bottom left + - type: conic-gradient + bounds: 50 350 200 100 + angle: 0.0 + center: 100 100 + stops: [0, red, 1, blue] + # bottom right + - type: conic-gradient + bounds: 350 350 100 100 + angle: 0.0 + center: 100 100 + stops: [0, red, 1, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-conic-2.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-conic-2.yaml new file mode 100644 index 00000000000..ba9522a68cb --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-conic-2.yaml @@ -0,0 +1,11 @@ +--- +root: + items: + # slightly clip the last tile + - type: conic-gradient + bounds: 50 50 400 400 + angle: 0.0 + center: 100 100 + stops: [0, red, 1, blue] + tile-size: 200 200 + tile-spacing: 100 100 diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-conic-3-ref.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-conic-3-ref.yaml new file mode 100644 index 00000000000..d7b9541ef4d --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-conic-3-ref.yaml @@ -0,0 +1,27 @@ +--- +root: + items: + # top left + - type: conic-gradient + bounds: 50 50 200 200 + angle: 0.0 + center: 100 100 + stops: [0, red, 1, blue] + # top right + - type: conic-gradient + bounds: 250 50 200 200 + angle: 0.0 + center: 100 100 + stops: [0, red, 1, blue] + # bottom left + - type: conic-gradient + bounds: 50 250 200 100 + angle: 0.0 + center: 100 100 + stops: [0, red, 1, blue] + # bottom right + - type: conic-gradient + bounds: 250 250 200 100 + angle: 0.0 + center: 100 100 + stops: [0, red, 1, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-conic-3.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-conic-3.yaml new file mode 100644 index 00000000000..77b28a9e22c --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-conic-3.yaml @@ -0,0 +1,11 @@ +--- +root: + items: + # no spacing with a clip + - type: conic-gradient + bounds: 50 50 400 300 + angle: 0.0 + center: 100 100 + stops: [0, red, 1, blue] + tile-size: 200 200 + tile-spacing: 0 0 diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-linear-1-ref.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-linear-1-ref.yaml new file mode 100644 index 00000000000..c06e05fb539 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-linear-1-ref.yaml @@ -0,0 +1,27 @@ +--- +root: + items: + # top left + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 200 100 + stops: [0, red, 1, blue] + # top right + - type: gradient + bounds: 350 50 200 200 + start: 0 100 + end: 200 100 + stops: [0, red, 1, blue] + # bottom left + - type: gradient + bounds: 50 350 200 200 + start: 0 100 + end: 200 100 + stops: [0, red, 1, blue] + # bottom right + - type: gradient + bounds: 350 350 200 200 + start: 0 100 + end: 200 100 + stops: [0, red, 1, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-linear-1.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-linear-1.yaml new file mode 100644 index 00000000000..f388e7c5936 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-linear-1.yaml @@ -0,0 +1,11 @@ +--- +root: + items: + # basic - 4 tiles spaced out with no clipping + - type: gradient + bounds: 50 50 500 500 + start: 0 100 + end: 200 100 + stops: [0, red, 1, blue] + tile-size: 200 200 + tile-spacing: 100 100 diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-linear-2-ref.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-linear-2-ref.yaml new file mode 100644 index 00000000000..be7dc774636 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-linear-2-ref.yaml @@ -0,0 +1,27 @@ +--- +root: + items: + # top left + - type: gradient + bounds: 50 50 200 200 + start: 0 100 + end: 200 100 + stops: [0, red, 1, blue] + # top right + - type: gradient + bounds: 350 50 100 200 + start: 0 100 + end: 200 100 + stops: [0, red, 1, blue] + # bottom left + - type: gradient + bounds: 50 350 200 100 + start: 0 100 + end: 200 100 + stops: [0, red, 1, blue] + # bottom right + - type: gradient + bounds: 350 350 100 100 + start: 0 100 + end: 200 100 + stops: [0, red, 1, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-linear-2.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-linear-2.yaml new file mode 100644 index 00000000000..7a01c062d16 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-linear-2.yaml @@ -0,0 +1,11 @@ +--- +root: + items: + # slightly clip the last tile + - type: gradient + bounds: 50 50 400 400 + start: 0 100 + end: 200 100 + stops: [0, red, 1, blue] + tile-size: 200 200 + tile-spacing: 100 100 diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-linear-3-ref.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-linear-3-ref.yaml new file mode 100644 index 00000000000..90b110c8d0c --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-linear-3-ref.yaml @@ -0,0 +1,15 @@ +--- +root: + items: + # top left and bottom left + - type: gradient + bounds: 50 50 200 300 + start: 0 100 + end: 200 100 + stops: [0, red, 1, blue] + # top right and bottom right + - type: gradient + bounds: 250 50 200 300 + start: 0 100 + end: 200 100 + stops: [0, red, 1, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-linear-3.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-linear-3.yaml new file mode 100644 index 00000000000..a595f3572f7 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-linear-3.yaml @@ -0,0 +1,11 @@ +--- +root: + items: + # no spacing with a clip + - type: gradient + bounds: 50 50 400 300 + start: 0 100 + end: 200 100 + stops: [0, red, 1, blue] + tile-size: 200 200 + tile-spacing: 0 0 diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-radial-1-ref.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-radial-1-ref.yaml new file mode 100644 index 00000000000..66f8c035927 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-radial-1-ref.yaml @@ -0,0 +1,27 @@ +--- +root: + items: + # top left + - type: radial-gradient + bounds: 50 50 200 200 + center: 100 100 + radius: 100 100 + stops: [0, red, 1, blue] + # top right + - type: radial-gradient + bounds: 350 50 200 200 + center: 100 100 + radius: 100 100 + stops: [0, red, 1, blue] + # bottom left + - type: radial-gradient + bounds: 50 350 200 200 + center: 100 100 + radius: 100 100 + stops: [0, red, 1, blue] + # bottom right + - type: radial-gradient + bounds: 350 350 200 200 + center: 100 100 + radius: 100 100 + stops: [0, red, 1, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-radial-1.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-radial-1.yaml new file mode 100644 index 00000000000..b832a72f96a --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-radial-1.yaml @@ -0,0 +1,11 @@ +--- +root: + items: + # basic - 4 tiles spaced out with no clipping + - type: radial-gradient + bounds: 50 50 500 500 + center: 100 100 + radius: 100 100 + stops: [0, red, 1, blue] + tile-size: 200 200 + tile-spacing: 100 100 diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-radial-2-ref.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-radial-2-ref.yaml new file mode 100644 index 00000000000..da9bcd1bffc --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-radial-2-ref.yaml @@ -0,0 +1,27 @@ +--- +root: + items: + # top left + - type: radial-gradient + bounds: 50 50 200 200 + center: 100 100 + radius: 100 100 + stops: [0, red, 1, blue] + # top right + - type: radial-gradient + bounds: 350 50 100 200 + center: 100 100 + radius: 100 100 + stops: [0, red, 1, blue] + # bottom left + - type: radial-gradient + bounds: 50 350 200 100 + center: 100 100 + radius: 100 100 + stops: [0, red, 1, blue] + # bottom right + - type: radial-gradient + bounds: 350 350 100 100 + center: 100 100 + radius: 100 100 + stops: [0, red, 1, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-radial-2.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-radial-2.yaml new file mode 100644 index 00000000000..8b7feb205e7 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-radial-2.yaml @@ -0,0 +1,11 @@ +--- +root: + items: + # slightly clip the last tile + - type: radial-gradient + bounds: 50 50 400 400 + center: 100 100 + radius: 100 100 + stops: [0, red, 1, blue] + tile-size: 200 200 + tile-spacing: 100 100 diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-radial-3-ref.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-radial-3-ref.yaml new file mode 100644 index 00000000000..d8564eed645 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-radial-3-ref.yaml @@ -0,0 +1,27 @@ +--- +root: + items: + # top left + - type: radial-gradient + bounds: 50 50 200 200 + center: 100 100 + radius: 200 100 + stops: [0, red, 1, blue] + # top right + - type: radial-gradient + bounds: 250 50 200 200 + center: 100 100 + radius: 200 100 + stops: [0, red, 1, blue] + # bottom left + - type: radial-gradient + bounds: 50 250 200 100 + center: 100 100 + radius: 200 100 + stops: [0, red, 1, blue] + # bottom right + - type: radial-gradient + bounds: 250 250 200 100 + center: 100 100 + radius: 200 100 + stops: [0, red, 1, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-radial-3.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-radial-3.yaml new file mode 100644 index 00000000000..4ca0886a0df --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-radial-3.yaml @@ -0,0 +1,11 @@ +--- +root: + items: + # no spacing with a clip + - type: radial-gradient + bounds: 50 50 400 300 + center: 100 100 + radius: 200 100 + stops: [0, red, 1, blue] + tile-size: 200 200 + tile-spacing: 0 0 diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-radial-4-ref.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-radial-4-ref.yaml new file mode 100644 index 00000000000..2ba2a96322c --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-radial-4-ref.yaml @@ -0,0 +1,27 @@ +--- +root: + items: + # top left + - type: radial-gradient + bounds: 50 50 200 200 + center: 100 100 + radius: 200 100 + stops: [0, red, 1, blue] + # top right + - type: radial-gradient + bounds: 350 50 200 200 + center: 100 100 + radius: 200 100 + stops: [0, red, 1, blue] + # bottom left + - type: radial-gradient + bounds: 50 350 200 200 + center: 100 100 + radius: 200 100 + stops: [0, red, 1, blue] + # bottom right + - type: radial-gradient + bounds: 350 350 200 200 + center: 100 100 + radius: 200 100 + stops: [0, red, 1, blue] diff --git a/third_party/webrender/wrench/reftests/gradient/tiling-radial-4.yaml b/third_party/webrender/wrench/reftests/gradient/tiling-radial-4.yaml new file mode 100644 index 00000000000..724019441f8 --- /dev/null +++ b/third_party/webrender/wrench/reftests/gradient/tiling-radial-4.yaml @@ -0,0 +1,11 @@ +--- +root: + items: + # make sure the ellipse transformation retains square tiles + - type: radial-gradient + bounds: 50 50 500 500 + center: 100 100 + radius: 200 100 + stops: [0, red, 1, blue] + tile-size: 200 200 + tile-spacing: 100 100 diff --git a/third_party/webrender/wrench/reftests/image/colorrect.png b/third_party/webrender/wrench/reftests/image/colorrect.png new file mode 100644 index 0000000000000000000000000000000000000000..75283ee1f13cac6235ba879f537cc8d098009eec GIT binary patch literal 256 zcmeAS@N?(olHy`uVBq!ia0vp^DIm2xNn<$k-o@FZ;D3JEM$gBw z>P@WNVmc8USiocgCy1SJPzX#mse;%|tlnUfdm4xhR0bgEVDCzU{_GI+ZBxvXNuome~=~!TCq;u(5VCfD438kc@8>LZtDd}23rMtTumXKb$q(oZc?(g3F z{qa5DKi_j^=FGe^^PV|hv^ACSaj0=#ym)~RQBl-=@dCvG`E7xPfqZlFim1MLA<_d; zl+*XkK3T)eW>GW1IM2*^KWUO=;xUN=4ph?~$LzB%`!N;3y!(Bb7A&qoY< zID4k=bt3OA4OGQ?{==~OT+>0<8`I^B)r6q$?Kq?BJw1Kc!`S}x#1@WtIw4x$50bx4 zmcicio6152O26Gem-j{7;BT&UNriWG5H&XLiE?pra{61kL!Z(=VQ(r(1HI##*%Qn4+BVB#>g(69SJv*(;Ny1+x8D=KF4(xER0CT?JGUj| zeW$~itlFA>0Oabzn*!R1a{t8MuAm=%$({hd-C_}G%YF6g)$bt~aH8mOyrzBJ>*BVx++z3(Ok!REG%bokU=HqqOIh&@rH zSvWB)1)#RV%;i48`&kugF?Kf{u2_i2HOBVwv2j-?vD|;F2Ya{eQ!xmSk6sVA3CfOd zI{q_fB=WbXs%M9suPVnUY>W&dgqAeHl@?=Y3)dPx#l|Y{_Z} z!bF^i2I*ZYn;Nfp2L5hZr^2j!_d=%a_n!p9%?^ZwH%XT$U7@iV#^$GgO~$;8kr<}V z9QC!LkD(~~WF7^9>sJ{C4iM$u_8(j{8(1!! z@32Lk|LbB{ll+nEdXB;0I&ky#>T@Ty+dTb3Q1HIbmeS9zr{6?RR~BbNlrN~dcGM!z zFL)Vx%Q`<`RXr{-D?QvNk zZm1?OpDs!8CN)k?`sRA?S%)9H{Kl+eiT?fXGc#)_)VI)Iu}w?`P&V`)3>xNS!PP5*g3Psf^6*R7hm4iI9^OW zaYN-ub7y*p>l?d26qAz?iQ-Q#@~;pWp#!bxYtE6zzGCAPy)Py5Yl z|8P42-siLr78VZ!jO{}K;niI-T{WAxcX^mGFuKQs;@Z=D+Aqyjrj z@naw|4}JVcN_G(zkMqXq7I&%k;Pm`T9`_p7klV*fWBPI%!HTNeuZvX=-f<`a?-5JSrO+{dBnX)aLLKR zXkoN{Iw1U4eQUZ#AxGVH?YQZ$!t`szVLNA?gTC|wv35(yd?f5{HMNPCcIiG2LHTZL z<;Y@&kQ^uP=~7IvhIJZqIU%*ZEui$_@Owy_T+*_xKEX{<2BN}JZ0##$4(!vm=vGqp zLwX7dJGSm#8JQk;Z(oN7`j-rTfc5OXzScCy`a#^*b4Q=VxT~iu$nFcQVd__qxu+5A z`mTWCE6-1v$64d`P9mCk7;&&_nYH*degFaeO*0LCo(RDHR8fa!fik?6X}{~fj41fV zXPY{@`9qMdMRA#}WNw*Q0A(DkIJDgO?WDd)kf?juU@3cG*29d_twyB3u6ND4%zcs1 z0x5U4)CS57sOIAWx`2cla02Y+NhaaTWazfe?QmtV-uXT4QY_!pmVf7vbjdp;$+F}_ z#ZK-`td)16OMQ;?NR@Nrzd9KjbK}(n3Z8xucY|!HSS5JM7ZNOddF^O!D?2ulg{D9? z{b-N}qWqhY)>X8J^)ql+T^$LD)gjrDZBJ}! z#UxAO3=y<(q`zej1bY~K>E6(Cz$_!cBed(lRRogW|MS5wR)B)*q}O!?{!QoRo8wdw7Q#PpUnd9qgB z<{@jhMf0y}S}~6XQs3F-u99d|{^iOS^$nDQJhm_B+f*1x;% zmN_9}*u|`42D%D{qw~~Zp&AZ+09fD#U5=cZ*JX-KWAAWZrnddqTIgX1PQcyOw@ll? z59%C851_IMF3m5q8Bu`Z?O7J7^TQ8eU0!m31*M{P;5VI#7y`S@J026SX~L4gG*vjv zm^meAhxm*RX(Z{W8KY-HmIZT~x_MiD6{57X;pfy<#P3`p+znz;A4jb>hk~83WUk+> z0X5i;75JjR!hSY;@!>$2x%QQ0ZhL07R2P@O>Uo-K=8%;t+=P(mXwQB~E91y(b{xtD z{ZoLc&|v#72#x~hhFM?aam%PqE;pRq(INPQ8D830FMWfrOuo)T+aQ}KU>#kuWU@JMp?$nThZVO!5I*Zd8i-8ND! z5E|SP4B{VO!VYHpo;4sW?T1KhK-*msf>s(cBnovX!?*r$K@h?<0+4{^rAGNlZrJYE za2VpsyzgpuqaV+QIL4Fc)rF9aLgesr(C88AAMt8hhq};5jAypfSxwHem9BH#q;FFD z7tb5XHqXQi#0eC;VRSEH55*SLy%a$@+*$Rc*Vly&VISuuQY&yr^&qW<_?HUYb#WZF z_O`sv`J|~ZSB+SCxTdB;p!`|lyU;`4=Ze=ADOi3Da-iI+CKdB=EZy;tTlI=vWyWGx z?Tty=m8b(*4H~bz9xVXY}k*L?V@`f#>W!e^X&x%d#>~ z2=X?T)&|gp2VI6xUSZTIa8}ZhmAd=a^BO-t5MP8 zpfBDwjG*1am!E3mP#%^QqBTwWmAiIL2RZBs-cK>O^6TXMlQ-dl7}bXaPvg45hnt|6 z=A>LnLh|1B9ZdaOFtl`BQOsxY+A~wu4M&RK6Efc?#ho_3O%!_ZtaSp)G=L7ftd1u+Jh(q6e~Dv1)}hf>xg^yD3eSq zazSAw)L&u3gf=z+V}Lhgg+C9&zK%2fAw8ttd?R_C1!|ZpGE52)H0&?CiEwYdm(LiE zT)jfA)EtZv0r)(#N!3pCj&qN&RX{k{)=tW3f@BF(xEivw?!U(!6kKqlI;gJ9Q(WkZ zQuvA#Z@;~NW;Ff+6Hd`PV<)~C?rHVZFRhqW2Lcpq)9h1x}UwR1!WN zkg!MHnq!VqqD6+1d^MUkX|{G>#F->?Nz#`NlDo)~hm&bwYTM?_SjnjTNqAy+IpuHr zXklu)ctu?yVv`aXzcX8Wx*#~*>*Y=|yPYXO*>#u|Nt4gb17x(#DN|Dc_+$k{Ypi3~ zgo9-!CR+hgHh)HJ_R2qk*w+*%F#w>mv*AylWC#Gm!!#P}I~wxMHyuT8OzV_Ob(^Ia96*Zkc*`@OT#7fT>u5e-SXe_m@Y~m%H$SrHji{~0|b!f z&&~4224K*w>v3i1%+vU(vQs-AobEmY20486G4G(p)Kq5X8cPh6Z9N(=p1TfC&&NN$ z7h)gLYB?Aify{BBVici7-92|AO_-D_NXKLTbwP>1Fm_r(;#^4I;-H!!=0;QMP_)1! zTZZ9_$Rqv$dq{8luK;j5niYP;y$P*Nhp0r%xqRDeeD%>^6u^WOkr3FOB7XF6N^g=} zumh^9LutVhe(mydrcCn-P@Sd?3MH|-2~~DO@pI84oaD?gX~8ltC3E~CF2ZJK*Dr@d zYWvxIgDO3qGfu=eUT#BdVTl{sDU_)*KTDkNy?9@tmj=-I zwL9jP_*cq2nplTjnI=U+8q3YcC%EXvE_jaswawQpxCs4%4OQuD2JC{7u9i{oS+QzB-JMee%33*bt$lBB*S5I++G%c_M31@%7Rg1t;@w^za}D^}X?CIUoB z+YvV$z%YX7;9>8w=XWj*&M(J&aD{xZx<^ zE(a;4WgIdkhApA`Nsx4RAJEsDk?$0_GTE_RiLXs*zPW%KJ$|CW+nEd(myQp^=z#oU z@;f;J7s(;$w%iS5K+M@`R)x-H&e|@hp@NR%@8<>(f`)Pm#H;RD%ZTd%RiHDO zV4S3Me322sN(sZCXtCXClY4m(h+hpB*9@)x3vDaiBqU(U$auqu$DFS8K8Whj(@>rc zC#I=^6uuI9{ zk@bvmw8*UWmA4cwD0(S*_OTh5dESn%`;>Q8zWw_c?)e(`s)UB;e((DWK^epfK*p~s z1~xKCJ?i9L=cH@Wj898GSSXYaO@H>N2nFV?t*PY3vK6#G{4)2Su-7B6A`;Bfi}!pT zDw$Xsk~>MszH9z+J9(3X0o4~9J|KGe6o>E)en%k5_^sQCw<6=mSp+VBY0xfcAD6`e|q0C(0; zIU1NYGpU~gX_YN~ICMv?rdb1Kt%99*W#!+6#RC|WhBmF5Pk$CwX(V!)n^HjPPLClU z-z)IY?DCn`EWBqekGt&Sc&{LH2_f&G;<-EDhbBBbz8o@!DdryPck_rt`Xa;{>byt- z=PF`{Z zsQSKsztrM-{6gKWeP?<>K3@2HaISkwH?#xsSg6fw7LB9q`%X^kb&79MY*>UlWij?D z+4#$G?L?}==Bc35H^7O}>fCOdnfbxo(#w+N==fe!*fDNcgNUQIx>vJ1%MnkP*kcYg zNFbe93M7UlDwjdj4^r`W|VUwVk9(9GydJ zkvxRdoZtcP?XWQ%c~^Eb8z|Q>9MlLmk!_<~0q&?Xk+ftixgd`_A)Y6m#OlTXmC8yX z2ywYP_Q$+1sD=!}th*4a$y_Ii9=~QgN`Yvq#z6h}t?La!(c2wn%(1Pl6SPI8Egx zFxs(D#vanb?G;AVmOGOZ97;3~MGrh5HZB+1#O}n}HpKgxU#H7awX|wc+%7cG7X}V=q;1e|0*!nSD~a%=lECGBX)J9O2GP$50O8}rl2|;AVHMBt z6bmoK4K`Csm-~^ZQI*nGvUe*oIcN9WN=U{A1riUxi7x30U-`VEkZZoUPOup!&HiS1 z^w5W3Y8Jw?yu?jyciIrLI!7gE3b4)Q{1n;>C*~ogh0q^GR-Z7RJ6Ujp;hg!5EtIhz zkQBqWuV(FwJUcx0pGhF3k>B*0Ib^1uo*woT0q^zn4JjhVtlZ{=l!s>R733~_z)+0C zZ~nF6hH|4}4?(}gha<}rFx|hU{kxdWxMQtoHMPrkbUq)S)by*!A|>pd=R1W@&1~Lz3ReEAv1L& z#uniuFV#?tqHA0oA``WL7x!AYtP!`#$j#XIR(YjIp2L#VXYN zY&Ob^*aFHx>a5R6Bicj+1$ysC;o*{4!}6#I;t0Jz9I^r+(yqTF5Hb<=zNg@0qClUc z!*y+yPgs!O;p8oziHvdWShkXZUO{70B9JhuXv)Ka>Gk)3$#kt$uQ5&^#0N=7D!-33 zeAKdR@1-(dQTeuEX@QpY6~CY?U#3j9q=!}3o;yoJPD$z7(RRk?WuuC-!r~zm!*Be} zfbwX&^B5PFRLqKD5E|XA??t%7FE?jAW_W^|dj5JQxTX$F*?<#<2cKEk+gbs(VetbN zJY;q0`dwgcR)-l;f8eEVu!f4o@`6Zwy!G>;Dh1vPZWG*+-Y4BwcICdg2hmCS{ zsWX5m)Wd4e(tcf_bA72LIX(NQ&|ZsXB4w>!gMtFU0S}bbxXu4IJv`!03SA|_%j*71 zY>-wWTW|eLs6dW{Xjco`4u3dIBPm>C#Fc7kZ-W%+s)x<0+EX=5HwOxg_9s$O^tGtz;^KGrbLmoRj- z0fO2dZn+5wdNmSRZx!JL#!RJVm|2TUU+UDZNQw1&7Zx7!#CNO=dlt{RNxZ7?cShh+ zps$Y!!mnTdVgj0&Y7zMl9_WbvV!2W!z8dcGv2qcI_%~{|l)8|J{*<}u-0 zEMEF|nkc(`5#auE(k{~2Z28`1kTLI=2#Fs7;~|oPeVt`NaPt00IXDPDYeEzu;Dcgy z&Ek+oVtmq5 zQ;gq9k_xCT&Yk-{#4iAMO(4lo%R@0O0Q}joj;4ln|*Ay{ws!Pk=+JzLW zwB)!_GQt?i^-9H9N^~z(W$C#oDhX201E>IfJka%(m|&$@8U9qVV4UxzleVMBg05nU zR;*SycB36m3Q7A!Pt zt=Va(gd3W?PXy>zOScY==0DRzqCdHWISBx81_m&vo$ZdzJj%Z{KitASQGKRLw*1&o zpd@n^D!?~-pv$r6ua3v$fTlWPFg+^Hq^9l0yiS@&Nx_5lj%v6_5#E@c#YsJ%Hc^Vk z-1UR&x7z+#@%z;019l!-_Nm%LTb4ujOu%bg=|TtN*2@ZBW8>bR&4-=`BGW8~$Zg9j zl8lmFwzdY-VKp3u)>F}V|8hq-M6nKo8NNYb#L3>;`>O{Zm_{Pq2JwM1H<6zMjc3TM zuH3p-WTh{*f3s;h62@FGGpB=9cQYrLlpEw);ruVmI%$Ju<3S=HKmMzi`?o>kNH*GM znK%jJi-uv(FTJuxy}Kp)(7XN?Zvk1l{Ny%8arn^91Klfc`aCGw;v~%i*efbBW(d=P zLX|`&-1b5uSmK(vVJ48)>=J>SIVu1E4j*nvJ+ zaS6^oKDp=6R531~GhZzv!Um>}oid-Im0gkKwdDVD1d$5KWSF+r<LDfz#Aqfa{Tw$N6aV6G9WWH^T%|ZC5#JQh9thBp+Bz0 zHCXr2jU>s=fGh zp<5cQ3vqa*(ggJs*J#}p5&(s8-^;gNLST{1hH88VaOaq-cs^=woka!YeZtwM$U@86##uR>ACKd$hB9aql0_`amdn%6%oUXFTX z0xNP;hPU`~|PF;+#FMa~eHIrp@V=KT}x|!k!Br!OUf7~GrFsWw7 z$erZA7iZ{19H^n@qdHoIN4%P4Hl;76!~Gg3U*B|E zVW~U43EHFi_dFF~5(~@s^d(+}(ZMR+dzJ%+bw^H|krH9|x1#>ZLbyGg-v?;qC-dO; zSQB$`Ue!44!n@)c&^@F2EbU_$l|m>+-OmZpujNM0e144R;R{i)d8xRaN&`UbXnpp> zJhQ#sv&<66DyRHD{wg7i+crd{j-Bn8d%bboIaC zthepWx=20Iat1U-6@Q$Tecp*mtk;kvck%Wys%mO&zF)8+g;3vBml%I$5qJ?3VXG~j z9??2YgFAL9_A8IIzCeZ1h3&MC6;@2BE(gDy$WkU^fpeo+Wiav$rv$vR#|Y61xW<*5Im{x zGu<4*Jij;@MchvpnSCOc3l`b^mO*u!bzUybhYs04+*2~5F|M0bH1R6Iy9jvLE{ga` z%kr^+TD|#AgBGNUWapiT#~by9OR$9_S8q{DB(iPU|S&Arw!CMRcb-_8#3y33$|o;-BoO`;uk z;0Cwr`?fBIv02PzGbxik@+ae8@85)Gq)0L3ZtoprA@tdy_0KH&3$?GSsOO~eDY-+I zxJgzhQyCYLl3mq*v-~e|c;SS|3ditbX!lbX!Q)=R`zN zwlP|?VEjMGbe4;Y86fBmv~9;)PRe`RHTP^7;XnOODZxGEGt8txr02F$GN}8Y*GPnI zsahcoDUC^3qJ(>CKxrHkgjs`40lQ<TiWOdB;D2&}Mg@Q;;{?Bfpx-8FckFriEDHgdNKkoUk|^cw*ryHk{=m>T%3pSG zMv*dWIW#XGdCIJ*T)6)HR716+P41z zd&%}&d%&9?gdrJ8+1ZMB*M3FLacs}>v*RO z8W>9MuPhdxNlr*+5;XnuzO7rLeo4L@DIy2Npv zS;Akh}5q7!>`Po=fkyVIr-*z+T{+V)}x=+>{%RAnPvYifF*BpJ_Cs&XGYsD2KyZn z>m$8HK64oStrxQMG^Q)j?mZtw8~J}AN##DS~l z6cfx#jJ9609xC+vVh7j?I$j=m9=${r$2F+S=VR3jgK0bx;XeE8Ib@4E@{z0+)C{6! z{J&v-!#wq4o$Jggky+GzFWXZBpQ_*AAI;V9*$$kx4~yL`-)ykV~mpZpr6n89*ml!gK!#`)NUQM=ib3=H$++AQi@e_L6_# ze7S%&e9sAY(tA9@Co6R9@C)~ff#v6o4}T0PcMn3JD$bS)7KMFP zXYc66I8MmD8{~o%ohbv2hhyP9x@;CMe09#e9rMo%#mp5z=Wx=Qe7MF-Ms8`QsL#w3 z{5396!+;pdltI;|2RT%GO1Ig@6J7P2!SME^m-3FIzQml6sZV&HJ&A?kC!}nSR?#bC ztc1>=mbHWbiz`+Nw<#uWr!3wO3u6XiXyl?{h)Eo}dDJuW5YMz1uhIs+98@DQX>5zH zPe%yTN0sIPre4^d9aQ5P}E$%(JMa{h7FIlvSMuFd9~o+q$JWQ zfzCjx;%*MC*t^(1tNrO}=}wrKf6gA3VqlYF=`>u1tVt&fRFfa88GCH=&LBG99A)L@fCO zJR9w$8pbNGqy#{h^V=8$`B zjt-}sl`)NqMtvAgLYx=0-v2^5YtW=6&FAIj3dA9kWi_!k7QeO{m%8vMo!+^I83!T0l$lWB~%&9}lY zy_DQXN4ak!Qg=xG%s@c6{g&8kfmIpgdR!zA85+lIQBTYo2 zRfNGfigo7akd+UVZ>=Lx+$4k+6HYB4>NXbZ#TQe#e1gWrx1!K=Li$lCJvD` ze}NgKxhf5oLUvS?g z7NM)`AX21s7cl*ZZ0im*4w6fxN9?^QlM#9wtWZO%wtB26U3N)!vOF@B0I>9yyQ|ZI z_HTg0otWs0A3_wg>{t|7G6o*~bfg#M|7UHC9D>8ISK!QINiQ+G58pIbzI+>NFMA80 zw=;{5CRo7}&KmH2KUF(j;Wdp&*<(@}`#T(Qd^IG(ekK5{$R4$s&141;N1?UJbqy4P%DCWF0LVIP@&~51 zi20rp(LlHGMz;IS725`LT{J$F5zm& zFrGy8PCg=<`udZ_<`9^0ywcW@Y>Lm5mYE{|Pd6X-IembqpOyw3ZYyfY2g zfDVqq1s&-F5dT4;SGfV?4gKW`u6BjoBDhQ;?K5)9*ZHFytqKjtA>pzdU2&M?K?d7qzjg4FL;R0$NAD~K_c(0tTbmDkgf1!I#%h1+jV(_7X zS@X-eJ;^z6*G#C73N_8o+b5I*Cw!yL%<}W#pDHs-rG-m&KZNaHlG+7h6&GiXA`h2Z zketIHC8(61MJ3rMWPO(Ou63{%AE<$Kz0W{5a0nsz>Hxg#RmTCnLPiPoR&Rv;?oLkh z#|ZjN)e$vjE%g7xB|NBRwWZCnT6&TS1I+*SeJUey9i+0_W8eCg+8q3FO+ksLn*+Ba zIBf2}e&cI>dAk;FS1ch6LDgMgOH4kffUBdXEDbCRFW%0v6&J5+PSlZicNFkck_?Bw za;*DuCSLhAPTB^RP^LPqd^?>GzvUlZdmVx@HmP_2gMdZl7hX(NE{*-MS2>MJCLE-f z(J%wVrwd1b*%aUE$)VQCo_bY%f6zESR*c|F6*4B87Wf#*QZU-rohaKJ`maT9r@`qy zNcQ5t(ls6e$9Rt_h!6_8mT-M0IZ$rZeM~{H(A}Gc?wd`*EL))Sh1V&pwWgR5RO{eD zK_<*<{rLfd(Au_40Y*ZjJRwQPY0}&w78@OCZrJ|3xzNyzPRbWgHQ8)i8MAbZg-*U< z0o}U}yE=9X8)Q&0(DtJY9`|nWk-GpK{{4!xq>k2hC(4!2+OkjlkuRhq@Fy#LHoV>B zs{C+m%?}+pTaPJEU!p#2%%*`4c-_zYGpzTY6iZ6ce>DI<1QZhSUe#F;vrL`JOfO8G z2tKJ<4+FD*th!qZzW~6HIr`xe{kKl_kfw7m+3`~K%i_-7MX-N$H^`ducIq%jbLddH#X_)sf>t<$`P#HJXbj&fz|P;5LA4scc?~j7yijHDDoMGJzmf> z@v>0d&#y8l!U1fUQzRzHkZQ;g6r))aUXE#nbCQnC)j$r+h{v1h!bJ ziit(zYhkk^Q}Hn_xdFEK+nHCjtIL^)gBJp#mT9t>InJ7YNJ_lK~C%X3^2uts3fn0fCmqE zcX3Ji>z>KEAn|ye5I*M@eKb#931;5jTfG1(taqO6;Z5xuWY7Dq?kZ~Zg%;T@OuRR$ zveEE`p`3s!yfWFf{&rJ!7St}aM7F2uDS7=bB0jDrW^4{$EkAVEI=o2_=luB4Ll`f= z?rGey2j^oy{3f2rlAfT``9nw#1>2ac;Ftx#RY#3~`i9Lo)39AlTWO)5!vdQisEJ7b zWki$VG~P%ij;-+e!B#o``?wUR;b{|R{gKyiPP%d4)6LRI8=$^ovX*Ky6&eo0(RP_C$u&GIv}> zW+*OL0!}_Z41Ml|U~-Fa88LjP>1s}=IX)XgjBSA+deU6basTj~z6|)wo*d!)Iw#OA z!vv+e12zMe_L?!{yVyX5{F&4~J8ZJ)hRWJqZKZIX&>jt{pfrHgVH)5yos0R^0{Li! zCsFuPdQXbQf`8n<(Mrk7$W8z->us~GdJ}Q+^KUNX;9Q43J#vDVTUN7d=NI}N7v}aL zcS>=L$_SS0jyogijv@g$#z?=ct*n~BA0Ow$bhY3k1O*Br3X->C&~HGj2u?1^p{_B-RQ&oTQRBURL-8un~n56L)M!ulmGQE z0!S(JTVkUs_NC62g`-%PLLEfYh*7bYKGf5xn7zPSY52j1v+kyh$5l%NO~H=6ZEXBm zdT0#>g9l7{7CiC-PBLFv1;0tN4Rs(LYCBKIl>Yb>MWa65?Y{S~p<>RCKu-X(`g|md zUmy8ozj89~@IRs6ayJCm7KUhjNoEM~=ywGndEvF-fLI;jcf(e0m&ik=_}ZWU-P@VO zY0P~*cKp>0YF}Vwb07$IRXh*oFB{AvaNB4^kzmqQ(IF_ChzDN|#^&VQ#r=0tM=-Qv zNcwtN7-iS6(nF>q^#lLcrmtRi+57!D8b+0?2Me14H|upnlB|{EuJ^G8g@s!Sy$cEq zNTEbOFHR}KqgDAj<6dhXkvvw9RNLRymkt=&xOu^-P(wx3d>8w+sQSz#k=L1C*S7P)=hf{EZbT=Zez!;4~O2i-p z1Q8}GVEyhL`u+dMAFtcJ&;2~-d`>*)oVyow%~+3~hKuH(fBvCIBH*U~{6km$&p(t` z&rt$bj2883fPdt!8Jg?-{{R22pWoC29zP#uYHaq;d46flYfeG8vmZ3}PruzWfBTs5 z&p&Q0NcdHA4$htbP%bYm2fUuwg=P+(uYCIYx9CkX_J8r*Jp%<50y1alQ=VAjwMDzM z66b2yXdoCEvb*j!42gkB8fy}*gn;k1n0`S~G;k@5odWoZX;nV;#4IsPHxc+8AhD;Z zkCW2}FJC+bJcYC^z5TjU>+(Y_;ByCCHXH@q$`{&G2A9B=QGcO7nF`#yE`Y?~N@&&6 znYYrJ!5s}XL3}om2j3oNPd?6uA&DQUaLHCsVA&&TwN!8oGD46a{3nfNTGbrkX^z0a zyiX^by$-#19b7g<4O~DHS56=JLWrE8HeA}8(LrQ2u>GzV$!h5RRpY1WA2oEDBF_mC zImyc&{1x%(UlD^(^&g(`Y}O5b zjcxbG*!)|vWIH)%UNHw`fq82r%!>Wd^x0oIkI{c+xYTI*SBCEY$sk0QA>nrX1CS45 zDTTPjUx%RmYYWFewkXzexp(5SS+F!swxb(L|HNhC9_a(?>WrZt0_Q27zMwx)mjo^f zAra9}X^_O`N9SGgJ2lq=f0hJj5}9EKe1ODTyMr92=j;Ve-=KGR)3JOT7bRp_Mdq&c zvH(7}Gf!fFcDl`BUeH^S8sJft%~U^OY0UIa*~gb`$QHzA3@A^ulg-*CU?+EiDV9QT zlyu=`z5iL5kz|uDs3h@`={eNk)7I9wH&Ab5fhiUwfIaYC z3fClFa6z9H&SlnKlBdK7=VTw;fJ9$xLZI)b25Vb&@L(z+sZi-IiK(p*6?#N@=40bT z7*m?s9&fPTEuUzZkR1-VT;=u}Ooh*pz$EovhLJk^(NIAobE6chS^e=8Om~11-!!$4B8+n#K4S zFSPufMoDXIa!h)zT`T0V(z><8vAzcEOr z*K+f+#%gr%%r`{$qsAq@eDSrCT5aM-AAM(0YXcDHl2Pw-kIt^ z8WTX?`Iuiaa^?4Pfh|SFrS$c&)N+lli0&%FhrifjtuBjZ|IxdblxY*`aV9$WxRes< z14MV(;LBXlR zh#7!FdWn`b3vzc357hX#R-5A{e~P9aF9>c4Lw#G;X&|;Ol2*#)ZDL>3p?)tpPqlk)zpRRX;UW zCwb6ePj181YdbyN_E|7eIdM{*5pfx~ZK{JTFc|BuN4kW0XWDAhz5N-=0Yl+x6T6teoM87!1|R5`N<-7wDXo8Y*`Bs(d@c(h6yL;wP%kf zRW}#1Ds3X_0fV`JYT7Nr`v(+fN_s3SKCf=G_ZX1S(jqeMKx=^2^i;rVdV3h`ebZIqEeKT&h+TI32AJ=? zwk}IGI}1l8;XVziZv%@ZspiVT0N9=jMc?USMI0M{MSXc316wxL#O&Hn36avli<26S zTmwuO;@EPbg?E4&*#WUefhMlA z8wOC00F*fmS9N0`z{L5H-|9A)QJh0`$loo<+TkEAVg}V|GIB50U~ODfk9eh7Qapq! zJIpoVCRxeRoA&&t-{)o@WX0j5=|o`5wt##~{Zz~?`iNt@Q5*EkZJZr#3h026H^zjZ zEi{~%U(KF6Ni3KEDSl)GxL=eG?58)kHM-ywQi zHh)>_e31>DSmKXQy#OSe+hpg9rDQ)hF2TQLAz_W~(hUiB-B4p0~9UOq?`6o>5dfGLbI?QECUKqP`k>^iDCP* zEVZ8obY`!QXQY^l?6jEe=GJcGf&sf@+YsGKqD@f1`+`sNI)De}ZvSmuOpCKoCV~t3 z_7`}ndVK+8M$13VZUNV!fyXkx*h5{znYP~f+vEsh@XV1xAidp`>Jh@)(P1|KrM609 z@R)3z>Kt@yl2ttCF8!E zN~ir=ptHw$-vExW_=;V$eEFa@nN9W0E+uuMt9aqnTeU~Gch`lcXeU*n-iFl3ZbzPi zEvVc!n%6enFhCi5__!i!LKlT~Vg(I`(56(FeTAooI#D|0Q+ z8;w`%M7yD=Z%$DvDP1r*Qv3%9H&@zMZJ)uF^q5D#x%~*K~ z>1u!=E^~F7_c$9%5(USYbGEQ&@uu@S!CJ&k=ByepeSIriHiu5`_TC;$m#OHsjYWHcqMs+ZBD-+nYSk#XX>A03&)%Eq9Zw1 zeY0D|626;*AwE1Ee9v_XUd)1luKJ=|#ZDo+4brh2pF+B&H(VuFpXR+r=1q4h zubaI>`ob9)6Q_d`zqbViu{(G3YMA}Dxm<#FG7i~_ur26!5JMkqo~i#zl?mMDoeH5`$zI}*fZ2Av|>ZTXW(!0JIv8txV?f;hxXjLt>yDfY^L@1N~1U_-v4O|RD` zzJ%=>qhJ|jKbx1`?A?{I+45w(rN@K-#Y`eF8(lH1JDleTgo4q`Q%E0 za}>5^$^5MXbcSx*Zx@uP3bwa_Byn_rur0X@+p=4JXYD5|MVwQ>c+3iV`!h2>0z>@R z?v~nnoynM+H)jOFHeH}j2Nfn~l(QZ+dibc_I~v&PqtLD)3TEW|*q3NkIv?(@3iO=1 zXrQxr-hN&?-Dc&@%d+hr2B5dY4hmL*{dj!sHdh}_!qc08Ox|Ir@1}rjK{FnI0(w%j zzb!8>0+b%;2-$@A>$JgM))E$EMQ;Ap@BHc4-Ufr^57;5vFn`0XUC)J#T_atA7?68x z1*Dg^exI25doz-@P$1aX;hqce>JIT-EV39;&5gBa5(C>s+rqo~99IMOmT5WDt@^L)YB{t=Cd?(s z(+x}EU?u(C8zVd5pNmK{Yq>G=xRerjYrk7I-iJVi2D`ulb0AKHjW} zl{|*He>>o@3vv`3W^5T#@oElfi*cF_T?ULIJPkz_(7Z~-s9tYkbszbcJaM3*YV{N1 z1388*x7-e>cA*}{pe<^($>^eu@sO+vvcjPWG1o}_`cmfk>Gdu$WIAt1;BM z2-|MIx4gLAm4XChB8PSg=Np4w{a&tat5-dPHTlDH!mS)AD@o!rq5L|yXP#lb%T;}x z4z0k@`7FJBsrRD$v+oJ!TB$Po3^w}Gt^P~Fm$dj#H=i3!=i|KmAhEBKJ6o5XLPqd3 z0eVF?OjAiZjE;wR2Nu?#50f;fk-?{{o;TCyy!Ep5jPQ9=UdjRjskE<3l}*xJCJh!Z zX`s1cqOE738ir6RXfD)Yewr)9zh4lr`MqpA26rT6S?e;PAC^et%_@NBfJO*Z0O>A4zsv098g2K3WSairSSL5Nbt^8-Q|-B`1i ze6(D(YjcOgEd7;jJ~N%eQaB|sQ7ct6#Mee&^P5E)|4_r>%rDLi$+8%8+2qvO$JqhU zG?}Eqp9IrU$_>BFbU`*9yph||$KTwAHu^H|>B0uo-R=HoEg$KvqC_91?+>19by#9h zt7m<>!d>RD^)}N@^bujoO=sdhEijp-Q8O@|7tuap^3s4UM1rtw52%;#e0}Vc$uSStF3F8 z9jdgt&DqRS&^;_>*vO|wWAwmGrtYS?+_@;GB7;{S0f?@cIL!_g5>&Q?Jbd!3Td_hp zE~!7fF2q&~zj6(F5iOjfl`gaTYcSUouS1#+6?e7^ic!cJBdK}b2=3Va5bYG@g70xp z7ne!P=e*mSGA_9r?y!7NYxF~G-?-|r&4WUjwz5^IOw#$t8G8RWp}JZT6>h>1;s7-e z5?y)%CZek$L+B+!IF+kcp*5CW;_9o9#of-{;i#cV7lw~=W@98HH_nxS`B|eW+rb8A z_C*ECw47TmA(wbQ<%5T%9>sCH_aXZ3t^umvQsAd47$iNcz$`CIH1m~K`=VQV*>;iX z#ngV6wWv3x(XAI1SjN&yg-|^Xc1o1rq~h~B?b}tFSEYD3S_$2UyJ&)T*>!WoM>8AMXif<`C5#e(?6(&b7`jv2=7|vo2J@}26kG$ z62cRu^J*7Pvt69}Zl?Yq(d$e_j-tv~E=1>6_1RtH6q%v)nw|SL`R3AHuERZp4l5$0 z-gwT#g1fs&6z=-qc~`nu4HA!7yAoF1--<1IjCplqz3&>Ko31%`)S0N;-c71^f@3ni+x?}e#t+(7d@oM{ZgZdV=*GK%s=70)Cm(7(9 z)st_^+m+Y*p39q$!(;;Xtlep(d7?t&t5Vwuy{|n(^bWU z5uV7b&a58~{g(pX8l{Np{K9-BI={WeoMut}DRcdOB>#Bwjk%km8@EC`+dmd|~E z7sh2>e%>{eQ*dEUJ*~+zT%wqUJrw^X(E2;;rF+a>Z@pzll;id4de)}>|0;XYxSY4gud#+fgLS?!J`WqlI zzvO#g{J7>2sKnXOT)C0?6gF4o5^phLl!qXeWLM_O$P!8n{>szV4qgbr!uUzYD=AQ;5>0)e; z;L-L1k61a(#?NRtwYd5@OcgdyS� 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