Bug 1449562 - Update webrender to commit 941bf5ac998061689a1bcd18d417f1f315e41ae6. r=jrmuizel
authorKartikaya Gupta <kgupta@mozilla.com>
Wed, 04 Apr 2018 15:21:50 -0400
changeset 411836 ca8e5e236ab31fbf421efda13fe4e1e571a79d4c
parent 411778 9be8c32a4c59187abee7374ee54609498cf8503b
child 411837 dfc311c89e45ead7e975d45b1643afe7ff160ac8
push id101767
push usernerli@mozilla.com
push dateThu, 05 Apr 2018 10:44:37 +0000
treeherdermozilla-inbound@41ab21c6f36e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1449562
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1449562 - Update webrender to commit 941bf5ac998061689a1bcd18d417f1f315e41ae6. r=jrmuizel MozReview-Commit-ID: 88ia1A1Dyhq
gfx/webrender/Cargo.toml
gfx/webrender/doc/text-rendering.md
gfx/webrender/examples/blob.rs
gfx/webrender/res/area-lut.tga
gfx/webrender/res/brush.glsl
gfx/webrender/res/brush_blend.glsl
gfx/webrender/res/brush_image.glsl
gfx/webrender/res/brush_linear_gradient.glsl
gfx/webrender/res/brush_mask_rounded_rect.glsl
gfx/webrender/res/brush_mix_blend.glsl
gfx/webrender/res/brush_radial_gradient.glsl
gfx/webrender/res/brush_solid.glsl
gfx/webrender/res/brush_yuv_image.glsl
gfx/webrender/res/ellipse.glsl
gfx/webrender/res/pf_vector_cover.glsl
gfx/webrender/res/pf_vector_stencil.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_hardware_composite.glsl
gfx/webrender/res/ps_text_run.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/border.rs
gfx/webrender/src/capture.rs
gfx/webrender/src/device.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/glyph_cache.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/gpu_glyph_renderer.rs
gfx/webrender/src/gpu_types.rs
gfx/webrender/src/hit_test.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/platform/macos/font.rs
gfx/webrender/src/platform/unix/font.rs
gfx/webrender/src/platform/windows/font.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/profiler.rs
gfx/webrender/src/query.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/scene_builder.rs
gfx/webrender/src/shade.rs
gfx/webrender/src/spring.rs
gfx/webrender/src/texture_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender/tests/angle_shader_validation.rs
gfx/webrender_api/Cargo.toml
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/channel_mpsc.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_api/src/font.rs
gfx/webrender_api/src/image.rs
gfx/webrender_bindings/Cargo.toml
gfx/webrender_bindings/revision.txt
gfx/wrench/Cargo.toml
gfx/wrench/src/args.yaml
gfx/wrench/src/blob.rs
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -1,57 +1,78 @@
 [package]
 name = "webrender"
-version = "0.57.0"
+version = "0.57.2"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 build = "build.rs"
 
 [features]
 default = ["freetype-lib"]
 freetype-lib = ["freetype/servo-freetype-sys"]
-profiler = ["thread_profiler/thread_profiler"]
-debugger = ["ws", "serde_json", "serde", "image", "base64"]
-capture = ["webrender_api/serialize", "ron", "serde"]
+profiler = ["thread_profiler/thread_profiler", "debug_renderer"]
+debugger = ["ws", "serde_json", "serde", "image", "base64", "debug_renderer"]
+capture = ["webrender_api/serialize", "ron", "serde", "debug_renderer"]
 replay = ["webrender_api/deserialize", "ron", "serde"]
+debug_renderer = []
+pathfinder = ["pathfinder_font_renderer", "pathfinder_gfx_utils", "pathfinder_partitioner", "pathfinder_path_utils"]
 
 [dependencies]
 app_units = "0.6"
 byteorder = "1.0"
 bincode = "1.0"
 euclid = "0.17"
 fxhash = "0.2.1"
 gleam = "0.4.20"
 lazy_static = "1"
 log = "0.4"
-num-traits = "0.1.32"
+num-traits = "0.1.43"
 time = "0.1"
 rayon = "1"
 webrender_api = {path = "../webrender_api"}
 bitflags = "1.0"
 thread_profiler = "0.1.1"
 plane-split = "0.8"
 png = { optional = true, version = "0.11" }
 smallvec = "0.6"
 ws = { optional = true, version = "0.7.3" }
 serde_json = { optional = true, version = "1.0" }
 serde = { optional = true, version = "1.0", features = ["serde_derive"] }
 image = { optional = true, version = "0.18" }
 base64 = { optional = true, version = "0.6" }
 ron = { optional = true, version = "0.1.7" }
+cfg-if = "0.1.2"
+
+[dependencies.pathfinder_font_renderer]
+git = "https://github.com/pcwalton/pathfinder"
+optional = true
+# Uncomment to test FreeType on macOS:
+# features = ["freetype"]
+
+[dependencies.pathfinder_gfx_utils]
+git = "https://github.com/pcwalton/pathfinder"
+optional = true
+
+[dependencies.pathfinder_partitioner]
+git = "https://github.com/pcwalton/pathfinder"
+optional = true
+
+[dependencies.pathfinder_path_utils]
+git = "https://github.com/pcwalton/pathfinder"
+optional = true
 
 [dev-dependencies]
 mozangle = "0.1"
 env_logger = "0.5"
 rand = "0.3"                # for the benchmarks
-glutin = "0.12"             # for the example apps
+glutin = "0.13"             # for the example apps
 
 [target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies]
-freetype = { version = "0.3", default-features = false }
+freetype = { version = "0.4", default-features = false }
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.5"
 core-graphics = "0.13"
 core-text = { version = "9.2.0", default-features = false }
--- a/gfx/webrender/doc/text-rendering.md
+++ b/gfx/webrender/doc/text-rendering.md
@@ -133,17 +133,17 @@ for subpixel text, the text mask contain
 
 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 opererator "over":
+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;
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -19,18 +19,18 @@ use std::sync::mpsc::{Receiver, Sender, 
 use webrender::api::{self, DisplayListBuilder, DocumentId, PipelineId, RenderApi, ResourceUpdates};
 
 // This example shows how to implement a very basic BlobImageRenderer 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/deserialze the blob.
-// Ror real usecases you should probably use serde rather than doing it by hand.
+// Serialize/deserialize the blob.
+// For real usecases you should probably use serde rather than doing it by hand.
 
 fn serialize_blob(color: api::ColorU) -> Vec<u8> {
     vec![color.r, color.g, color.b, color.a]
 }
 
 fn deserialize_blob(blob: &[u8]) -> Result<ImageRenderingCommands, ()> {
     let mut iter = blob.iter();
     return match (iter.next(), iter.next(), iter.next(), iter.next()) {
@@ -68,32 +68,32 @@ fn render_blob(
             let y2 = y + descriptor.offset.y as u32;
 
             // Render a simple checkerboard pattern
             let checker = if (x2 % 20 >= 10) != (y2 % 20 >= 10) {
                 1
             } else {
                 0
             };
-            // ..nested in the per-tile cherkerboard pattern
+            // ..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!("Usupported image format"),
+                        format!("Unsupported image format"),
                     ));
                 }
             }
         }
     }
 
     Ok(api::RasterizedBlobImage {
         data: texels,
@@ -173,17 +173,17 @@ impl api::BlobImageRenderer for Checkerb
         self.workers.spawn(move || {
             let result = render_blob(cmds, &descriptor, request.tile);
             tx.send((request, result)).unwrap();
         });
 
         // Add None in the map of rendered images. This makes it possible to differentiate
         // between commands that aren't finished yet (entry in the map is equal to None) and
         // keys that have never been requested (entry not in the map), which would cause deadlocks
-        // if we were to block upon receing their result in resolve!
+        // if we were to block upon receiving their result in resolve!
         self.rendered_images.insert(request, None);
     }
 
     fn resolve(&mut self, request: api::BlobImageRequest) -> api::BlobImageResult {
         // In this method we wait until the work is complete on the worker threads and
         // gather the results.
 
         // First look at whether we have already received the rendered image
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5edcddc3d16058b22265792febf739529044b5a8
GIT binary patch
literal 65580
zc%0>%iQiXq-p2DV7`rTC%otm!q)F03ibCSnUfQNbN@QuX&iw9Y##mA$MSDc4L?YQs
zii9+lYzZ^Qnvkqh@qE_ra?bhf=bZa_&GZ+zexK`lU)T5CGu5hXTh092rrI_;R{N!@
z{(bfS>Lv51-&}b~@3zhB{eF*XRrIf_<Zlh=|DXRd{hI^&xBKtX|MO2@diwX=ew+U)
z{ab(7;OVcvV-*FUiu7;(ZoQ{}@13eB097OZ=YMbd{5?ni!Tap;tEve=)#%^y<Hk?d
zIr<N&vD*&Y=KqiVPtpJL=I_7yc<swZ{~r$9e~({pS0x6h68&3$`gX&=-dSbz*FXF>
z)py>$N(fjL@_*j)<2UR7`PNH|MSqK9kNEB0yY8@URT!Wu^l$z5_g{bZ!5c3c{SA&f
zXy4s`{i~`lKo!XUdCO1Ve)-9JYgRrb`cH0H`{4cd+<E(|FhEu4-~7X-FFsoL+VfA`
zPx{-nJig8$2kf=Wj#WXxDv%HM-?;w6wXZz)_<f|meXGWG4?A%0-5>zz{{U5>f6I^G
ze)ZWu-d?@@v3b)#f1eJgHmz6lcl++Kb5#&9ef?X({u@4d|IJm)9=Ufa=<nO{^b_hI
z`TPB<LjckzVCwRLeqjHP)~$JQ>7uz)ZmPVrU#Hg18q_-E00=-85HNN9TQ{TqKV19T
z$|VbDPX_(xc4^bRVV%Pc!~mpkfVAcRyyf3Ne6!(`f4u$5^G`i=&!o|rA^p3adGhgf
zYyNJ(>bvgvtJDpUw*IY~fBJ6Y`j6j#bM<piE|@uSRA%UT-6~FLT<^%=LjZQ#K6L}6
zEFa46hi||7{9o(VtXlr~{JSTNsvJ6?d)rn`>(@HuH~Z|d^Ny)kVA}eDe&1~P^q*^A
zd-0h^@1Jq|jg`X&_H1|Bi4E!;_S+iOcTJywDa!}?efRYjAAj)nD=VK~H1E#aK>zu@
zI<!8iVcnVs?Z4OVI{^XH{Q*+e5A@pv_J8lq)hiY+oI8E|$jae^dUtBmqEWpg4~78j
zk~#s?m5=m8`@g$p)w54NID6VS(0@UnE@z$6q<*bKF#t%wRDXbU^~3qU{d)b!AH4J0
zi_0EgaL?3RZ>St`Vc)J5txjlA=kSB}-+T9+(>6e=^5Oj7Z~7AK|JExjmp(dw=9ICZ
ze{jEUZBJ{~u<j8MfIWBlb;<@vRsWWM|M=a;4WE9v?v2$emMpsO?#W|s$cz|#Zuj=B
zn>VU=)FB7fsJ`1yJEUuXH05vI^3xBS!2bVO`}(S9pIUhDU6XFPzVfn*`uFJA=Hw>z
zYajMI2*55oZoh4s21r#ul;1aBe)g~T-+uMQWluabcg950e_pRnXSF<`!O=B;|C@dH
z+%@G2OjAD6Z{rtW|2O}>a_M6W=G-x14Cudjz#ltToZ776F-JlG_Sy{znBotRrhcT~
zS7`q=FRxhg$ozYz-+uFTm6u;Kuy@zCr#El(hgukbJyIrMit>qmWdGM*diJSB_s^Op
z`um*IuJy@Hj;%uhAOfcM1Ei>b^G{IzWdB!QSoY+?`({qNjr5=2w_ArcEl)VEE(HK4
zK$-?fO+J<1H(z}Y_J3pb^GhFp=-#`hj=u@?UplC7_l{?^YIc0RqaXnL?6E6VU|K&w
zYWnH?8#jRc-+6u2b4wmwF!!!0pg#lp`}OElaa!}n^=lsv0jR!Pss>0+|CY@^0{y=D
z<imAuz53$v#g9BNXU61lqpz*JV#o#O_UwH2=`ETzIQob~4%}}qAm9$$rM7|6l8@yF
z<q!6M_l>`=T=wL{_s_m_66nu>{{FqXv^(RJ6B-_K)S(anm;fnOU|RaOK>2<9^@dN;
z{x7ds`uM{8?zv;qt#tnj`~R_P`!=nbHTnYvphorGcZLc~%K$0K-^%n`|H(hW{x3bZ
z<gth5&6+;()={8;=!NI?KBvQ(r#5eVECk@U`|ZsHOvwOg>1X<V{t?>$1&IHGduL9c
zFqZTW9?-j6#|i+zaTtID_DPk1DanWPf4>RJpX~qqGfyshVD8=1P=5yX_vzm0?AE88
zcsvJSuib%wX&E3T{cwJu-};Zy{;O6jed6KybMBgY`z<&At@6rY7Y*#&qjTFcS~hD`
z9|N%e-blc-43Lg|KL3W#KKkI@H(q)1*(HxHynptLDMo+4o?Y6tIkkC{26c}*^q>Rw
zt)8L*($T+}>G$b}VE@%CmoI+w!Tau+G3B-~r2pdc`}OSF{;bnlG;LT90&rjrCSW=S
zNJah@q92q$*#EUv5dTLO%$s%RWTXGwUgvbEI2`~`zYYdqpFN3ysTd#~{cwJuAJ~5_
z*#EhuPb_-i-kEny9zSLz=pTOZpmYD&t>f9PTQ+M90XPf-00c~{0#lIxGo2sk_sNIv
zy|ZTZ3(qcj{Ned?XHK6q{$|pD$)NtdyLD=NMyuvcj)MRkeBgdSKr%oIub>q48~T0x
z53v8s&o5gH_?vV0^hx7H{{{Vfckc`UY;nTze}Dk|ZvPYsn7n*CKhO`#AMO9rif5jB
z^r8D^-!*Nb(SKf_9$f%{C!g5pSPTG6fIX-JQ!qgC`sw^gKd}GXYyJ-LfAWzBt^TV<
zTyo)nzCF6OuK)lvX;80rO$Y!IFdYLVE}yp#(U0uEayi6*!MuBBOr3D+O*dSVxoX6v
zpucC=4ix}^rVS|oOhDQ|N&f(e>xc7G`K^cTi}t7ZKXC7?JExHTO48r2*Et=}CIDan
zU<2<>6_|nnl9o@~2k8gp&-Tao1OD%va{E}(KX_ojUQhsM0stB}IHp$3gJA+N0h2dC
z()za=`oYr=?Ek{EPe1v{L-)^t`oDebXwpAq@IV*<o!gyt`Y9(hISv8<k3d7fqz#a;
zd^!I{EPu5Bs^^z4d14XZ@9yc7Z!`MOKeu;xD1g>2n>RhaJ_Z0Mz;3A+AYuJ-ek}ig
zt^@nOv|`y}#NX_@rcb)<meJRP{>z43bbkNdJ-Q$OTb$77SO@@YV3+`@7$8~sru=|@
zAH4hKYpY*)Zs}8qzkBYQHfg-kKd6779$gWDCpT++T)jF+9(oWHkSj3JA0S!%e14)I
zJpJByb=69UKj81)Su>_h9DmEG>!JTH8+y^8^ZN8Wry~Fm0MM}h(MQ1}kO-K90TPu@
z=ZES46_r2QAL9@Bn>%a9RMua~`up}er&GH#PjA^A8t|CfHE9Cu0gr&h4UnjQJipKn
zwlCQK#TCyiejM;OXXc$#CypB<`Y*l!^mpqF0BqIb#6}Gu0I-3DfJqx5N%_2eu>632
z?}7cH{UH7iF97{_Oqt;5AJ7j9uzkfD003w}2mnlgeYpY?Hb9d4;rxbvYJZ6T!UyKf
zzB{D9dzTJpw*de&l>it5CToC%^$Y#5{K@{yF#hua|I;Vme(TLQUia6^t1lmR$%O;Y
z?F|LcmH@y3fC-Qa0h5%!mCuj#6Z=1h@z1WG0MJ+hfCOX%{7-^EK$809{6xQZ-+KKO
zXg`Smq6ZQG(*pXT06Voit2F|kVg0(bk2sVFn7jd!lh4!lTc#h`pW;vW1O1b3bM&9z
zAM_Id2>=uTCg9EqkKp9=+wy~_&pNa}#vk#A^#}ULqW%o%zjW~V{iy&EfB*mt08W4!
zRDsDFATjw`enda8{~EAA#2@g7_=ozxZR|~Qf6xyD01E&B2mpWp)PevI0h6x4#Psv|
zm40A<Xg`QQ;ScH$=HJ+xMw0&Fmkx#j2n9$01OPw-LI5lQlQck5^5y(cen`J{Z@=*>
zwBPclVf{RKKj8n4DKLMHei*?0{s;xo?kre9CpCoz!~h@xxdIb3KvMc;`w;y;e4p(9
z!gCn^hZg|;@0vbkf~S8#zurBd04mNv05m?10sxO7L%_rgkdS;mKhy8MchLSTpT+ni
z{$|aXK6%2pTSkw(7W7{+e8@!?3^=zp7QoqU5CBaYVgL?5SO}P?0TR-0&QJ7X`#-;Y
z>Eg!`e{*I=>hIPW0obZVv!=&O0FZzQ8z33^x_vf&iS&DaEsUR+U!eFedT{={*)wte
zc=}-g_3GB8Lt9)xCt(A^1cC_w1Y`qjmy`jL(NE`>@`I-j+8^2v;!pU4`kOZC_Hkp3
z{);ad*uM`HAOR2o(6BxR03Jb7fyo&l5&3TUeGKJ~_J{U^_&>UE0pK6%f70!@jv0N!
zwXlD#7%}AH3kSjg>J9}^(dIM+08Bs#08D`W-3m<N2S`M}oFAwEr&#_#e;9uh|Ah<g
zn+x?f73MGLuVno&0HFXowXbMH0BF>J0ss@B20sE4Ge8pZ_54IXD1Wm53oDjAjq3;T
z5Bev9{!w!Op`afIP!BACGhqQC05|{#{YD9xlmU{^j}KodKYaSI{bBq<{1Jb1u>L{+
z%|<^wfX>4K(3t>uN^_XN8~`MsR$wyE-~{BG^E3U#{v7{@8ULt%Ea|T#{ew||Pbz>j
z7yuA}S`YvsU~&dXK>t?VK1@HgKg1u_58!V$)&F=;{~#IwT@Zk<fB*m*04AVppacvM
zzkIWOn106o82?8E|I>{AjM3jm3gC1EfD-^DpxHpN{{Zpp=krtf`S!>71ODcE`YS#C
zoe+Qs051Skfe9HPcKKfUvHf4qp+9r=l_Q2-I+z9^0`TlJ0RYXLHEGnKe%;!%f%o?+
zFzz293H@|_E<ZZIi9g|w>JR4MEjQhG{onqQ`O}q`4Z9QuaDOU50iXcD5D+UcnO9H(
z^7;HY{rUWB{P^e8?*pKg5|An|5d$QlU&>GEXYFtBKgH;;H2V9Q0x$rmtprT20uzvL
z%FnmI#$W0`pkD@n0?-eDslfPufCTjaq~+(@pW`q1qxm<+^*;?jE&v7q2cR|uz*JxY
z28dt2oFD0D?N9M1{1N`Ge!f3G0Qyk@5&#JRHULDxgbWbBemFmtAJWg*pW=`BL;O?y
z;q%YjA0L1;0I&cMfCK;&03sk%U=jw1UA`$lq@UPd<FEL~`Um|Z<^ID#KMo)*00n>q
z010R+Fzz29e*Id0NI$VZv>(PF@Q3(^`osC->Bj*G1&9Rz02Ba70GNOYRbcG$>HJuJ
zNI$kev>(JD*AL?#>OVq13;-?w1Rwwa8ju6P1f&X#-vF`e|B=g&=|}d5_JjDt`T_g_
z{-OS0{=oblJ@Pur|K-DnzykmV5ELL3000mc5C8xgkOII2<O+=40I|x)=_}<&^ke%|
z`|<iA{L}nV{gqZf3;-$sUO>&7LIY|5fPh+o^a_ga2Z&X_IX}@)>`(0n@hAMvn?v;v
z^Dm&E1|Sq*=MDrw0su6i1b_*cgaP7|Z_iKkL;Jsu_J{Vv_!Iu9{-#Vs{iBTjp{Sn*
z02TlN@Z^)I0VM!Tz$6S1r+(f(Sbjvm_tv8QVf?LFj`64Uckk?3chUSAcZ;W=1~9&W
zyL4=iPhbQ<(?&1>F#v}hOs`<9z}PoXeEPTQ_JQ&v`mKA1?a%QS{9*k~n{4%GtbQB-
zSO5e-1^@>DCjb%<E08x(eBVK0lkb(^XCJfu*Sw1Dhw(@JvHls3{%hd=SCM`ifS_Li
zhydUK)S>_&0h6e}*!0`;WBGk#?T_up@hALa{p0+-<)%?Li2e~nFD3mr07$<A04JaZ
z-~g$>m<<q@eCPaFe%Ag@{2BjP|5ks->c;`V1>gX{20$w?UIWCXf2&u1T>ff*jX&d`
z^p6keAH@2p00aQofc1}o0PqCxDljes#3bK6Ka?Mpzt|tgFUOzoPxX(_A5T9%fM@_v
z0i1;ks0B`74FFC6t-x3f5R-oQ{7`;GKd?WJKaM}FAHpBiAIv{|{+s;c2LK#^RDf6j
zxPS-%H~}#LI02{v<2FD%^7;I*eYpIPerkVcKZw8L59*KR4?KT-{V;%O06+mM0098d
zfDnM%7yu?9R3L4j*bES({vUDsaQOlK*1m=Ihw%sP2k~e80sf)>UHutPKNlbakO2S<
z2myc%%ml;=jCBOZC*PDG($Cl*+K=N8>kskI^$+uR%;+0$xR&mJ#fV|>0EPh!0{{vT
z3IGAf0DuNG0YCz31;+LR#He4(59tTxkM@W02krMXJboB|SbtFeFn_}OVF1DafC3Z%
zwrp_{HXuwu3;;|3Bw*|n7?b=hdVZmw+8^7G<B#}5{6qbb{+m7hm#}^;KqvqNpa1|G
zPy=8INUxxHet?+t+ww#DLHVowq5UZSh(E+X)E~~jDE%-1sQ{q>5P$>#Y(NPBOaLNa
zyatFzK2BdLKcpX)KiMDK591H(hwz8>NAnNn|L7a97yXxkes}=k0HOjU0Kx(y0N@0q
z0Kfzw0&)ey2#)0ki2eMZ!ufIgzJX7FYCnoU;Scdo^9SZ{P(Kb}7(iSA1V91+HekJ@
zApo#}!2mVlHb5Nmx61jc{D^+|^m~oQAC6yMKe&Fm{%HQ*JbEPR&!GO{s2>L)6(B8O
zT0r;)rU_UFAAv$Zs6a759N$4=JinA5mA}{@+ixj8ei(mTe>DFh^y2`e0u%tk6Q~(C
zpacLX09Rmq28ieUvVCa!bNP$?ar`b;{L$-&=g+P9{I~jtl71XO^a4@<wx9{D0l*2M
zj{q}*%l!djI=_}5(NFEq?I-I;@Xzyyo_`}v{)f{8kOz<!fB^tCU>$k{Is}Ys1I2rO
z-hS5p8h^o`)_;Wli%CBZ00bcER{)p*@CJ<205PARpFU*&zmxqj{<MBbKjNSC!~FOB
z&kq0`0Q3SP0OA5d0Pq_K13-_UcnuKK`L+DW{&;?}Kabxh34gSHML*8p;QeI)_M!r4
zPx=V}O$h)LfWs&NrUGLzK<wvd`@gRCe@55Oe4`)dPo#bxKwJR2fKChsAZ7!^aemr9
zT7Jg<rv09H%<8B5%dX!RV1)v}G$02+E6{A9GJk+r&QIINqo3H{=zmc07u25#=??&q
z0U!Zz2uK^Kd;`RE{($}0c=k8=i_jmwzv#CBkO0&Q1Rx#*#B+X^e%Ai+>h}U56<EFj
zk~x3S{x1Hw{_**H-QWKDzm<Q!`pU~k4C4Xp6o3Z+KLD{Bpv?30%O`mLWb{WGpxkG0
z`RBLgm&5*9^=Go{&mMq41(x#zlzV=iz7h71h<`!%j{rbK0PF}Z%K-75Kd1cz_{*t3
z0KfnM`~bvafb!2z+sB;WwZ9vG7ynuHy9M9^zzu*yKpny57@&0dcKRCnS^GQjxA?R5
z7ny%sfEIub0BisZ0i6+C>JL!H`Ss-!q@T6F#^2!I)PGpN82|==1^^KaU@Nf9S5Ud<
z*YZ>Psr@zn27iKooj)P}i+)o83P1&b20#cH+CU}#0Og(EY#*VY*k9UD<1hG=`jhz=
zxW5cQDL?_B0zev20w4s8X9LA@exaXde~CZi&#OO~zux_20DA?<0Q4GA0^nC*Sq3Qg
z{J4FXeq8=+e>eV&KgK`TzpFpv>gNH#1;7Aw0l)#^3BUxT4OE^1VmQA;Kga%V{0V<t
ze>8vi`Qzy42M`S)E<gu>3;-tp4gt$IKw0Ov+lT1q*q_?Zt{=v~oj;NK?EvBeunUM9
z&<TJgVA&g}yz}e!A^K7Iqy4G<DE=0Iw*F}T(eu~nrw1?%KpFs402Y7*0BS%C08IcQ
zpxr=a7@*wqoARUbNBh(GqxLiF2k}SsXXc;LU+L+$1Hb?f0YD8%6A%MH695V5Z=li)
zP@?`Vn>YWY+sDw4wlCV>j9-kuS-)EUG=D?-%>c#%Gz-`O02|N*zz|S3(DvJv`W>YF
z^BelP_Sg6u{AvB+{K=|c2M`v30iXiF4S*qF*#;=_{C4^({apKF`)T|Ye_DS!|MBye
z=*JIW`T)=ZQ~)XfbOLGslz?^wmu7%6&Tq<3pFU!LZ9j=W;!j^c1?krpkN{8tAa5Yu
zz)C=;0!#S;VmQCh&#}LZUtK?ff7U-yo<E{LWA*C*lmZk0;sTNh><2&ySf&BWKfllq
z%b)Ep?I-aU{7L=E{KMy;r+*mhmjNUNAOI8q$OMD{$OMo_KzRlz_53`2q5N?AbNS2o
z;r5gG3;yKw!}G_}Um4Ud1ArH>EFhV{@)3Xo00hKGfNUTd!KE0Wtn+jE3H{jqGX6OJ
z7JpoSGXMDb8@#^^U>*Qm00KY;fJ{IRfF&SppwbOc&iT3ggnq95P5dqXxc+$lx%w*u
z`gs6w0aySs0Nemr0&)eGYJif@Pt%v_=h~m!kK@ny<MnIwPYB)rsw*zz4*(ZB`egwJ
z06=dbc?41bxB~46F8T*3@%%qJ=MUJQ+t0*b@z3?o^N&A&Tz~B~bbnVr51^i^pZ*A7
z0KgN_4S-{S9ZEAm>F3w-3)$a~zrnxOe;)m&0I2{%0Pq9gRbWX5DEa)R{IvW%`#bSB
z_}BSE&z}hWJb+2RQvePC`~c7duoYOM0m?W(m!FouwLiC?jlbfb>tFPb3g{my`gH(k
z0q_Dkxj9c@8vvaET7jh+pfvq*ekwnuAK9PUPvcMPNAXYf$MY|%{tHAu6(ARY08jxy
z6OaPn5YRP1lKO+^xAfx=e`0@XKZ?J_AK{<sKd*io091e$fCK<)KraB6fF&ECH2u1L
zsQg^})A*zIqxcK{2>(=nH2>)Nd*cn)8U6GDlpa7dfNTNMUx5Vx)POVrZ2*LTWo)3L
z<kNQ#eECTE3H@CAQ~OE$E&inbX#V*6GeP|{0H^?6015!80o?!y0c8WhE2!8HVWpqn
z(2vSr=uhL%#9#17_?P;Z`5&o&FzcrQAO#=*6adHslmO5K5CWELfa1?D)0fIm=;zp9
z+E3Px;Lp^b%wPKa<N1F$>mS7Wr2q{8X#trAGyyOKqzaS~T%-X?JO3s=zoDOFe`-IB
zzpNjFe=~pN`5(|fg!P*NAO*kxXvqMO1~dUM1mq1=iUEo{{}$)`X8U>e_u_BxXX>vY
z{iXmN03raS0qO4`8~}O*F#-KoP{BVy3Fr69FJyl&{)j(=f7O30K7S(gn*q!PC<TB3
z^Z*bDK$#U-^!e@fQThe!ukn}lqxd)T$Jd|n^_u~t1>h_o20$nP)&TAZF7yW|{rpV7
zNc(gAt$x8j&!3$4=K&Nb0A4^001ZHG8vrI?i3TX?{FZ)M>~G_*_%BJn1)u^TYXGDI
zi#9+h=f~|6w7-tOu>N`U`r-PI(*GyWPalBk0mK8)7N7!v^>YK_8z?dWB^aRC^ZyWi
z{;2p{{AvB^{KMxj{`u#czmR_X0}wxebpUAr`T+C*ki`Hy7HNQ@&+nF>bN*cM&!s<;
zOMgxPL>geHVhvEr`6K8T5&sDM7kdA20Ym_xRs;cyH$chf&t?Ar{`~sSsox&}K>+#<
z=m(%g0~CFJQ-0a(@5SH2zcc@``QI6UUI91&$QA%ofkhgii1W+C*P&mO{Z0H0{+#;L
z`5SqEGk~1}GypUJhzfv1Kz;@nZh+#>FWbk^FKB-ge}g~4zo~zH{)O(Z4?sHrqyP*6
z4FG}xFa&frP=N+0`276xk@7S2^X>1)KZt*sKe_eG016g>4*)*^hJd<(<QZI$0g6Ar
zlpoR0x4*QX#Gmjd_?P;V`NN+-JpaoF5E+0{fKmViKmmX>pdSDtpj2S71}NhEI(=RG
zx%PMCPx$lkPxUYJ&+5;F_0s_G3xELV0>BM`OF%n<3p7AM=Xa+soS*0?<!|jz?dQgy
z@JIM}>p!61y+1tw+yP)0unPccKpOy=07Ssh2+sQh6nlOuKcXL%zqLQLpN&7^&*DF;
z{!DiLwgBt`A^=bW+5pf5AOh+JD#8H8oS(K2l^@ZM%3tkI?Pudp>xb|M__y^>^N*fC
z`1#-Hr#}GD1CR!w9RN@O1VCCq768<M`Uap0Km?=;3~Zo$KS1H<*X^V93)`RCPvdX#
zr}ankC#b(Nq@M<m7N7?}0)RGPAOK20X9E>vfa1=t<!8&^u|KsR#9!AB;ZO0e^M~et
zHvJcgei{H;017}_K=KB517IhBR$ws(DCGRk^wsIF<&R%J#Qr*dHU5e}tv{WAtbb%c
zzkC2N15gXl0iXgv8_)@W5>PA9jNrTmDE9nXeq8=)f2aK*{<?k?e}I3TKOy~@uznpt
zTmT9{T0jf{Z9ooyP5`F@i!eaZ=V$t9`Lq3X{Bits{qXu#{PX<L=TFrA^#Q~K$SD8@
zAT1yUfKEUY0Ga?yz#<J$`1y7F82b72=l0|H>-sVHcjiw%{muX~1>h_o20&B*n1E&j
z6=;A$&(GV(ryrERYkw1eAAgL0uK$Ada{+PzH~=&N2n4_<ptFJU8laf-bNMm-0`}+j
zGx2Bq6{0^=kbVO|0|0J7Hvoo!vVjUSKtBD^=Qs32`kVGM@i+K0_~-iP`Nz-y8?L+d
zZ-1rxUwtKh0Ot<?JOFtBxCOuf3;+N>1496K0vG}Y4N$UvJ-<GD@%)B<5%zcE@8S>f
zUy^=P00w{-0FeO@0;&PF|5bhi6n_37{UYoih<~vDaQ+ma-wXh&KL~(`00anFr~wK&
zKfZiy`FZwV<JvzMe~W*se_Ss8W&n$RTL2yaat6RwAijd~8lbTAo9*M!&)VO%pN+r8
zpRIp0{~i6AX#KVTEdVV5Yy;W=SONw%P;LX{((gQc@%)y4QvTNd(tbAnf<KFYnLna`
zRD}MaM!yUoTL1z;3jk?A8vvOAmVj!2e6OH_&oAXC^b6Tv+E3yy_!Im~{mJ~zqhAJ~
z6rdD<08juR4JZMqWda}slnN}&00o>Mr>~Ts&`-)A?Jwg`+E3!|;7{sL>R;wRfBtmy
z&mTbb0U!fF3eW+d4*;2f7yy|7LO`j&LY~0|o!==xp`UDDw7=7S5`V;>tY4@8Wc~#7
zXF~dA067IH3m5_D06-cL10WmNA)vQ`av7lT^E>pz@^{7$wx5h&jK70F#J|)Z*1zf>
z9eMvDRzD5^DL^a$2Y?6wnZVe9P5^KMI0P)v0EM0(%g>>oYk#NxF#ZAjY5mFk!TFm*
zKMvr5R=*a213(3UG@u&*hk#gt1sNd!`E~mQ=;zuW+fU;kz#rlt>ksQcN`EF=KMnva
zKrDa&02Kh(fNlT+1avk~E(2sgzxVPn^mFZx^w;*&_{;jy^-K6y{S$KBe?)|Sw*ayL
zzyzSC4S*7mDlp$GsOa-Y+F!@7iNA|~t$&@r`254q-`8Fv`mYlG_yE)a+}F`h1rP*)
zHee_K#TlT0^KWv`A8G%n_#6Cd{n7jh>CZ&z*8zY9s09!SfXDz8Wq|DG4{RSQKP`XU
zzG3^j@%Qj=^^ePY{|j9GZUOiJFabEm34l%jQ-RI~%3^?==ck9SSAM4at^KwAaQvF}
zWAX3Szj^*-(SM2RcL#te0JDJ11f~Ww0niEHS0Frta~YtZ^K1E8`WgG{_~ZDS^<(gF
z>QCn%>mM1?KV0?e0JH_D1z-Tk0MH3&17HYfDlo4Ba-3gYK6HMfpDBOe{?vXZ{yzSw
z{>=Q*`JZ3E830s(J^%>-rUCr`=mcN_vH@~FgY%u=Y#&R%2>aXk`}ni<XXal{{qz7b
z1CRy)6@U*w3xJ3KSONw&P^1BJo}Z>Km7k?wg#D@gZ2T?$2>(=nG=HM>XR_$G1Aq#^
z0?-108ZaUNmVi`&G=j4lppf%Z`C0l=`P24y+Rw({;*ao8^+)wj^N*fC`1u3>{H^-&
z58(U(gdPAifM@_v0a5{200sa+4afnY31A6G6`0Qe`Oi<~XX!`fk6%9I?L+NH<Co)a
z@n`W*^;fKZTYyvm7Jv=_%mkFLAT$9i0jUCW8z9g5Y5P$5G5x6gY5Ve*FB*T;eop)u
ze-{6u-^^cEe`P>F4d4M*zbya;paTGEK$?K~6-*w1Gy#}^RDsS0iZno$^V{jm=XdBA
zvcD65T0e|G2me(6JpWk#s7U=ot$rFnGyvQJAOO+=asj{rsBHt_5ReVvj^N-AP{{eY
z{Fr_r``h-D_%r?-{B!-=`I}q69l%n6P603g82~haxdB4~U;=Ul<}pCt^K<zz{kZ&H
z`*Zt+;?MYF{B!;B{3}>L4<If;E&v8#2msuGZUFcZ#02CGl*<6Q&(GV3%a7^D<<H|s
z+Ru$YuOG%A<Dct~=MT?+dj8U%|BU`CNIyM*WdQR4-~!|VU;y$0asj{%C=I|9fC<PI
zC?hzV0dkz5r!SWuzkH?qJ^OR}x$zhLG5)#!xc-aM&jWx9kPAQn=mLNn&<g-h03je(
zppM`u1LQtGZyzo{DSzMo+<p>&UOz7Wr2bU@c=P--`ZHnugIxVwfKmVqKwdxs0B%4(
z06YPNfV_dS8X(8{dHYED3H`+Wue<i=@yp*nWc>*KwElSh@chfApC7<70C@mO0V)7_
z0ns-QoxmD^+HL@ZfLwuQ14S4h_xYv#gnnH9YJYzFNc(C01%JGLwf<!O6sunb02iPv
zU<IH6KpK$VKqde@0hEBUfwCDO@A;K}QvUq(3)o-dFY8C}C-|58m-#2pfBN%}qyKW&
z&ksNyfHDBI00e-%fD{0M0PrJFCV&z!mjQzM>HPBWHRUJUPs-oWpT}P?{)#`rzto@1
zzkK@n0VD%Z2Y?np1?vw0KqjDR0GR+vK&e1{21k7g&whT{K8Ai${z3ao`)T|Ye~N#p
zKb?Q_{L8Cf29Oq@7JvZI06-cr7yy|7@(6G@P{;rg=eI8(Ek8p)-oEDPZ`#kqU-75-
zm-^HBBhMfC{O$A~{{g@}fXo2W0@MYp05ky52BePw&IUFFlnOMjpl!41|2ca5So%r%
zTl>rSlee#3KL&rM{$&0XqhAJ)7N7;7EFgUYn+a$GAQQk4FuZ}b$)aD+FVojde?ve1
z^5MLFbo@H&NAYLyZ|09a{~Z09p#H(CUk89G00Tf-KxP7)H;{e>aR?}n03E?017tt{
zMm@iwUxfX&{cQXV{`B=@>fg+N`TXnOe}wA4Q1$BokQb1%0385m14abE5HMl`ZIea6
zod3H`f%3EE@7Uk8pN+pf{v7<<`q%lJPrnXeGXQJ>m?xkE0Mmd@0CWO46(}P(_#Gts
z`EB{>)5o*Fjz2g4x_<QIhx7Wi^VfX-3+Nwi^bb<~wgAlnwhPDsfSG`90OS$mR$zny
zqR(%(k9_#>o_?<V&G>cJk6piR{hRq$fd2E1{$57ES-^GyITP3mfNo&t5nwk^zyLYU
zZ_3X;eSG`d_A}o;oX5Xce|G+v&tD<^!>s;+M!zjU^9jKD2I4iK7XW<(c@-F8fIQpB
zqn|$g{PE|-pZ|A%_xrCOZ~hgge}L6*UO?^w^Z{Tdp!*2)37BmI<v71BKX3awPk(p(
z2Jz?M-<^N<^G{g+P)GlHj()cQ900l#*nR}?HgKQ<BMcCI{%@S~yY#b9-@y3w)=vQc
z?)<TzKce&xarD~(6es|90l5>{eFTRH=xw0wg8H+Z-%kGs`@8MuK7PFQ8=OBy>30Tz
zw}3+ca3-Ml2n;*|A`K9Ie)I4Tm%sP+3CG`Czk&Mqp8vu9=Y0SO4*+igM*zUSfx;6Y
z^ci3~U%zvH`|^pPU)cV^_`B;bRR7-dH~Rk012Cch{RJ2XKsW$C0r?f=8z9U1z48le
z->CNUAAeEx=Rbe4-QRrxMHHa_1dM9H;0BJa!0lZ9_Wa(<XMO1D^H%unvpn?pSrC5x
zObtJOvftl%0QC*OfGR>ypr+wB&=H|W(B9rF$TdK=^Lx`jdi-RIe;EIv`Je0l?gJoO
z0Yn2JG=aj8fRF)#=Xa;?r;*$DmFV&NSmgSh5jlVJ>30Wk&&UOQYV-uI8@YjN1V^xA
zfb8dwqF?0rTasn{&B`|a3efKlz;m)Jz~<Q|U@ijs2C(Ok-aZA`KWhEV%|3tf=0AD>
zb;`bg3J5@O1DQ|Zxz8V2{)NOpd;J%oKRf^m0YLT%5Mh97zl6>oS$+}vqwJrr{)*Ng
zU4Y>LMC;E{fntDazvMrEVfN3D|C04*FMy&05HtYjk2wDZ?{B_&?4NV}#i&2q0?ZYF
z90X(ou>L~NUrPMr*Ixtx@*1GpFWJvuhW%61Uq%4P0II+A^Z!58U#J0Cf8po<57Uo7
zfapj0-+znS=j$)O{NjtxbLfBB)Bp6-i|O(6*rSUUJ^au^4?Y0v_r7^^>EqwbyJyU}
zgXRy--`mEG8#{JPNPp(fSL6QyMn3@KU%>GJfM3AyPe8qU_3YWbTeq(CNAM2q+P1Bz
zI1~R0iVFz8f#~1C8#UxV1L$YqBWoT`k3jtns-Ho01d9R8-+?-|@6@Vu>kh5Yt~k2u
Msjbg!Rkzyz0eL$NuK)l5
--- a/gfx/webrender/res/brush.glsl
+++ b/gfx/webrender/res/brush.glsl
@@ -4,16 +4,17 @@
 
 #ifdef WR_VERTEX_SHADER
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     ivec3 user_data,
+    mat4 transform,
     PictureTask pic_task
 );
 
 #define VECS_PER_BRUSH_PRIM                 2
 #define VECS_PER_SEGMENT                    2
 
 #define BRUSH_FLAG_PERSPECTIVE_INTERPOLATION    1
 
@@ -141,16 +142,17 @@ void main(void) {
 #endif
 
     // Run the specific brush VS code to write interpolators.
     brush_vs(
         vi,
         brush.prim_address + VECS_PER_BRUSH_PRIM,
         brush_prim.local_rect,
         brush.user_data,
+        scroll_node.transform,
         pic_task
     );
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 
 vec4 brush_fs();
--- a/gfx/webrender/res/brush_blend.glsl
+++ b/gfx/webrender/res/brush_blend.glsl
@@ -1,13 +1,13 @@
 /* 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_SPECIFIC_BRUSH 2
+#define VECS_PER_SPECIFIC_BRUSH 0
 #define FORCE_NO_PERSPECTIVE
 
 #include shared,prim_shared,brush
 
 varying vec3 vUv;
 
 flat varying float vAmount;
 flat varying int vOp;
@@ -17,16 +17,17 @@ flat varying vec4 vUvClipBounds;
 
 #ifdef WR_VERTEX_SHADER
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     ivec3 user_data,
+    mat4 transform,
     PictureTask pic_task
 ) {
     PictureTask src_task = fetch_picture_task(user_data.x);
     vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
     vec2 uv = vi.snapped_device_pos +
               src_task.common_data.task_rect.p0 -
               src_task.content_origin;
     vUv = vec3(uv / texture_size, src_task.common_data.texture_layer_index);
--- a/gfx/webrender/res/brush_image.glsl
+++ b/gfx/webrender/res/brush_image.glsl
@@ -1,13 +1,13 @@
 /* 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_SPECIFIC_BRUSH 2
+#define VECS_PER_SPECIFIC_BRUSH 0
 
 #include shared,prim_shared,brush
 
 #ifdef WR_FEATURE_ALPHA_PASS
 varying vec2 vLocalPos;
 #endif
 
 varying vec3 vUv;
@@ -24,31 +24,47 @@ flat varying vec4 vColor;
 #ifdef WR_FEATURE_ALPHA_PASS
     #define IMAGE_SOURCE_COLOR              0
     #define IMAGE_SOURCE_ALPHA              1
     #define IMAGE_SOURCE_MASK_FROM_COLOR    2
 #endif
 
 struct ImageBrush {
     RectWithSize rendered_task_rect;
+    vec2 offset;
     vec4 color;
 };
 
 ImageBrush fetch_image_primitive(int address) {
-    vec4[2] data = fetch_from_resource_cache_2(address);
+    vec4[3] data = fetch_from_resource_cache_3(address);
     RectWithSize rendered_task_rect = RectWithSize(data[0].xy, data[0].zw);
-    ImageBrush brush = ImageBrush(rendered_task_rect, data[1]);
+    ImageBrush brush = ImageBrush(rendered_task_rect, data[1].xy, data[2]);
     return brush;
 }
 
+#ifdef WR_FEATURE_ALPHA_PASS
+vec2 transform_point_snapped(
+    vec2 local_pos,
+    RectWithSize local_rect,
+    mat4 transform
+) {
+    vec2 snap_offset = compute_snap_offset(local_pos, transform, local_rect);
+    vec4 world_pos = transform * vec4(local_pos, 0.0, 1.0);
+    vec2 device_pos = world_pos.xy / world_pos.w * uDevicePixelRatio;
+
+    return device_pos + snap_offset;
+}
+#endif
+
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     ivec3 user_data,
+    mat4 transform,
     PictureTask pic_task
 ) {
     // 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));
@@ -68,33 +84,61 @@ void brush_vs(
     vUvBounds = vec4(
         min_uv + vec2(0.5),
         max_uv - vec2(0.5)
     ) / texture_size.xyxy;
 
     vec2 f;
 
 #ifdef WR_FEATURE_ALPHA_PASS
-    ImageBrush image = fetch_image_primitive(prim_address);
-    vColor = image.color;
+    int image_source = user_data.y >> 16;
+    int raster_space = user_data.y & 0xffff;
 
     // Derive the texture coordinates for this image, based on
     // whether the source image is a local-space or screen-space
     // image.
-    switch (user_data.z) {
-        case RASTER_SCREEN:
-            f = (vi.snapped_device_pos - image.rendered_task_rect.p0) / image.rendered_task_rect.size;
+    switch (raster_space) {
+        case RASTER_SCREEN: {
+            ImageBrush image = fetch_image_primitive(user_data.z);
+            vColor = image.color;
+
+            vec2 snapped_device_pos;
+
+            // For drop-shadows, we need to apply a local offset
+            // in order to generate the correct screen-space UV.
+            // For other effects, we can use the 1:1 mapping of
+            // the vertex device position for the UV generation.
+            switch (image_source) {
+                case IMAGE_SOURCE_MASK_FROM_COLOR: {
+                    vec2 local_pos = vi.local_pos - image.offset;
+                    snapped_device_pos = transform_point_snapped(
+                        local_pos,
+                        local_rect,
+                        transform
+                    );
+                    break;
+                }
+                case IMAGE_SOURCE_COLOR:
+                case IMAGE_SOURCE_ALPHA:
+                default:
+                    snapped_device_pos = vi.snapped_device_pos;
+                    break;
+            }
+
+            f = (snapped_device_pos - image.rendered_task_rect.p0) / image.rendered_task_rect.size;
 
             vUvClipBounds = vec4(
                 min_uv,
                 max_uv
             ) / texture_size.xyxy;
             break;
+        }
         case RASTER_LOCAL:
         default: {
+            vColor = vec4(1.0);
             f = (vi.local_pos - local_rect.p0) / local_rect.size;
 
             // Set the clip bounds to a value that won't have any
             // effect for local space images.
 #ifdef WR_FEATURE_TEXTURE_RECT
             vUvClipBounds = vec4(0.0, 0.0, vec2(textureSize(sColor0)));
 #else
             vUvClipBounds = vec4(0.0, 0.0, 1.0, 1.0);
@@ -105,26 +149,27 @@ void brush_vs(
 #else
     f = (vi.local_pos - local_rect.p0) / local_rect.size;
 #endif
 
     vUv.xy = mix(uv0, uv1, f);
     vUv.xy /= texture_size;
 
 #ifdef WR_FEATURE_ALPHA_PASS
-    switch (user_data.y) {
-        case IMAGE_SOURCE_COLOR:
-            vSelect = vec2(0.0, 0.0);
-            break;
+    switch (image_source) {
         case IMAGE_SOURCE_ALPHA:
             vSelect = vec2(0.0, 1.0);
             break;
         case IMAGE_SOURCE_MASK_FROM_COLOR:
             vSelect = vec2(1.0, 1.0);
             break;
+        case IMAGE_SOURCE_COLOR:
+        default:
+            vSelect = vec2(0.0, 0.0);
+            break;
     }
 
     vLocalPos = vi.local_pos;
 #endif
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
--- a/gfx/webrender/res/brush_linear_gradient.glsl
+++ b/gfx/webrender/res/brush_linear_gradient.glsl
@@ -30,16 +30,17 @@ Gradient fetch_gradient(int address) {
     return Gradient(data[0], data[1]);
 }
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     ivec3 user_data,
+    mat4 transform,
     PictureTask pic_task
 ) {
     Gradient gradient = fetch_gradient(prim_address);
 
     vPos = vi.local_pos - local_rect.p0;
 
     vec2 start_point = gradient.start_end_point.xy;
     vec2 end_point = gradient.start_end_point.zw;
deleted file mode 100644
--- a/gfx/webrender/res/brush_mask_rounded_rect.glsl
+++ /dev/null
@@ -1,98 +0,0 @@
-/* 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_SPECIFIC_BRUSH 4
-
-#include shared,prim_shared,ellipse,brush
-
-flat varying float vClipMode;
-flat varying vec4 vClipCenter_Radius_TL;
-flat varying vec4 vClipCenter_Radius_TR;
-flat varying vec4 vClipCenter_Radius_BR;
-flat varying vec4 vClipCenter_Radius_BL;
-flat varying vec4 vLocalRect;
-varying vec2 vLocalPos;
-
-#ifdef WR_VERTEX_SHADER
-
-struct RoundedRectPrimitive {
-    float clip_mode;
-    vec4 rect;
-    vec2 radius_tl;
-    vec2 radius_tr;
-    vec2 radius_br;
-    vec2 radius_bl;
-};
-
-RoundedRectPrimitive fetch_rounded_rect_primitive(int address) {
-    vec4 data[4] = fetch_from_resource_cache_4(address);
-    return RoundedRectPrimitive(
-        data[0].x,
-        data[1],
-        data[2].xy,
-        data[2].zw,
-        data[3].xy,
-        data[3].zw
-    );
-}
-
-void brush_vs(
-    VertexInfo vi,
-    int prim_address,
-    RectWithSize local_rect,
-    ivec3 user_data,
-    PictureTask pic_task
-) {
-    // Load the specific primitive.
-    RoundedRectPrimitive prim = fetch_rounded_rect_primitive(prim_address);
-
-    // Write clip parameters
-    vClipMode = prim.clip_mode;
-
-    // TODO(gw): In the future, when brush primitives may be segment rects
-    //           we need to account for that here, and differentiate between
-    //           the segment rect (geometry) amd the primitive rect (which
-    //           defines where the clip radii are relative to).
-    vec4 clip_rect = vec4(prim.rect.xy, prim.rect.xy + prim.rect.zw);
-
-    vClipCenter_Radius_TL = vec4(clip_rect.xy + prim.radius_tl, prim.radius_tl);
-    vClipCenter_Radius_TR = vec4(clip_rect.zy + vec2(-prim.radius_tr.x, prim.radius_tr.y), prim.radius_tr);
-    vClipCenter_Radius_BR = vec4(clip_rect.zw - prim.radius_br, prim.radius_br);
-    vClipCenter_Radius_BL = vec4(clip_rect.xw + vec2(prim.radius_bl.x, -prim.radius_bl.y), prim.radius_bl);
-
-    vLocalRect = clip_rect;
-    vLocalPos = vi.local_pos;
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-vec4 brush_fs() {
-    // TODO(gw): The mask code below is super-inefficient. Once we
-    // start using primitive segments in brush shaders, this can
-    // be made much faster.
-
-    // NOTE: The AA range must be computed outside the if statement,
-    //       since otherwise the results can be undefined if the
-    //       input function is not continuous. I have observed this
-    //       as flickering behaviour on Intel GPUs.
-    float aa_range = compute_aa_range(vLocalPos);
-
-    // Apply ellipse clip on each corner.
-    float d = 0.0;
-
-    if (vLocalPos.x > vLocalRect.x &&
-        vLocalPos.y > vLocalRect.y &&
-        vLocalPos.x <= vLocalRect.z &&
-        vLocalPos.y <= vLocalRect.w) {
-        d = rounded_rect(vLocalPos,
-                         vClipCenter_Radius_TL,
-                         vClipCenter_Radius_TR,
-                         vClipCenter_Radius_BR,
-                         vClipCenter_Radius_BL,
-                         aa_range);
-    }
-
-    return vec4(mix(d, 1.0 - d, vClipMode));
-}
-#endif
--- a/gfx/webrender/res/brush_mix_blend.glsl
+++ b/gfx/webrender/res/brush_mix_blend.glsl
@@ -1,27 +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/. */
 
-#define VECS_PER_SPECIFIC_BRUSH 2
+#define VECS_PER_SPECIFIC_BRUSH 0
 
 #include shared,prim_shared,brush
 
 varying vec3 vSrcUv;
 varying vec3 vBackdropUv;
 flat varying int vOp;
 
 #ifdef WR_VERTEX_SHADER
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     ivec3 user_data,
+    mat4 transform,
     PictureTask pic_task
 ) {
     vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
     vOp = user_data.x;
 
     PictureTask src_task = fetch_picture_task(user_data.z);
     vec2 src_uv = vi.snapped_device_pos +
                   src_task.common_data.task_rect.p0 -
--- a/gfx/webrender/res/brush_radial_gradient.glsl
+++ b/gfx/webrender/res/brush_radial_gradient.glsl
@@ -31,16 +31,17 @@ RadialGradient fetch_radial_gradient(int
     return RadialGradient(data[0], data[1]);
 }
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     ivec3 user_data,
+    mat4 transform,
     PictureTask pic_task
 ) {
     RadialGradient gradient = fetch_radial_gradient(prim_address);
 
     vPos = vi.local_pos - local_rect.p0;
 
     vCenter = gradient.center_start_end_radius.xy;
     vStartRadius = gradient.center_start_end_radius.z;
--- a/gfx/webrender/res/brush_solid.glsl
+++ b/gfx/webrender/res/brush_solid.glsl
@@ -23,16 +23,17 @@ SolidBrush fetch_solid_primitive(int add
     return SolidBrush(data);
 }
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     ivec3 user_data,
+    mat4 transform,
     PictureTask pic_task
 ) {
     SolidBrush prim = fetch_solid_primitive(prim_address);
     vColor = prim.color;
 
 #ifdef WR_FEATURE_ALPHA_PASS
     vLocalPos = vi.local_pos;
 #endif
--- a/gfx/webrender/res/brush_yuv_image.glsl
+++ b/gfx/webrender/res/brush_yuv_image.glsl
@@ -70,16 +70,17 @@ void write_uv_rect(
     #endif
 }
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     ivec3 user_data,
+    mat4 transform,
     PictureTask pic_task
 ) {
     vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
 
 #ifdef WR_FEATURE_ALPHA_PASS
     vLocalPos = vi.local_pos;
 #endif
 
--- a/gfx/webrender/res/ellipse.glsl
+++ b/gfx/webrender/res/ellipse.glsl
@@ -53,17 +53,17 @@ 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 = -1.0;
+    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);
 
new file mode 100644
--- /dev/null
+++ b/gfx/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
+
+in ivec4 aTargetRect;
+in ivec2 aStencilOrigin;
+in int aSubpixel;
+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
new file mode 100644
--- /dev/null
+++ b/gfx/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
+
+in vec2 aFromPosition;
+in vec2 aCtrlPosition;
+in vec2 aToPosition;
+in vec2 aFromNormal;
+in vec2 aCtrlNormal;
+in vec2 aToNormal;
+in int aPathID;
+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
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -484,29 +484,29 @@ vec4 get_node_pos(vec2 pos, ClipScrollNo
     // get the normal to the scroll node plane
     vec3 n = transpose(mat3(node.inv_transform)) * vec3(0.0, 0.0, 1.0);
     return untransform(pos, n, a, node.inv_transform);
 }
 
 // Compute a snapping offset in world space (adjusted to pixel ratio),
 // given local position on the scroll_node and a snap rectangle.
 vec2 compute_snap_offset(vec2 local_pos,
-                         ClipScrollNode scroll_node,
+                         mat4 transform,
                          RectWithSize snap_rect) {
     // Ensure that the snap rect is at *least* one device pixel in size.
     // TODO(gw): It's not clear to me that this is "correct". Specifically,
     //           how should it interact with sub-pixel snap rects when there
     //           is a scroll_node transform with scale present? But it does fix
     //           the test cases we have in Servo that are failing without it
     //           and seem better than not having this at all.
     snap_rect.size = max(snap_rect.size, vec2(1.0 / uDevicePixelRatio));
 
     // Transform the snap corners to the world space.
-    vec4 world_snap_p0 = scroll_node.transform * vec4(snap_rect.p0, 0.0, 1.0);
-    vec4 world_snap_p1 = scroll_node.transform * vec4(snap_rect.p0 + snap_rect.size, 0.0, 1.0);
+    vec4 world_snap_p0 = transform * vec4(snap_rect.p0, 0.0, 1.0);
+    vec4 world_snap_p1 = transform * vec4(snap_rect.p0 + snap_rect.size, 0.0, 1.0);
     // Snap bounds in world coordinates, adjusted for pixel ratio. XY = top left, ZW = bottom right
     vec4 world_snap = uDevicePixelRatio * vec4(world_snap_p0.xy, world_snap_p1.xy) /
                                           vec4(world_snap_p0.ww, world_snap_p1.ww);
     /// World offsets applied to the corners of the snap rectangle.
     vec4 snap_offsets = floor(world_snap + 0.5) - world_snap;
 
     /// Compute the position of this vertex inside the snap rectangle.
     vec2 normalized_snap_pos = (local_pos - snap_rect.p0) / snap_rect.size;
@@ -530,17 +530,21 @@ VertexInfo write_vertex(RectWithSize ins
 
     // Select the corner of the local rect that we are processing.
     vec2 local_pos = instance_rect.p0 + instance_rect.size * aPosition.xy;
 
     // Clamp to the two local clip rects.
     vec2 clamped_local_pos = clamp_rect(local_pos, local_clip_rect);
 
     /// Compute the snapping offset.
-    vec2 snap_offset = compute_snap_offset(clamped_local_pos, scroll_node, snap_rect);
+    vec2 snap_offset = compute_snap_offset(
+        clamped_local_pos,
+        scroll_node.transform,
+        snap_rect
+    );
 
     // Transform the current vertex to world space.
     vec4 world_pos = scroll_node.transform * vec4(clamped_local_pos, 0.0, 1.0);
 
     // Convert the world positions to device pixel space.
     vec2 device_pos = world_pos.xy / world_pos.w * uDevicePixelRatio;
 
     // Apply offsets for the render task to get correct screen location.
deleted file mode 100644
--- a/gfx/webrender/res/ps_hardware_composite.glsl
+++ /dev/null
@@ -1,39 +0,0 @@
-/* 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 vUvBounds;
-
-#ifdef WR_VERTEX_SHADER
-void main(void) {
-    CompositeInstance ci = fetch_composite_instance();
-    PictureTask dest_task = fetch_picture_task(ci.render_task_index);
-    PictureTask src_task = fetch_picture_task(ci.src_task_index);
-
-    vec2 dest_origin = dest_task.common_data.task_rect.p0 -
-                       dest_task.content_origin +
-                       vec2(ci.user_data0, ci.user_data1);
-
-    vec2 local_pos = mix(dest_origin,
-                         dest_origin + vec2(ci.user_data2, ci.user_data3),
-                         aPosition.xy);
-
-    vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
-    vec2 st0 = src_task.common_data.task_rect.p0;
-    vec2 st1 = src_task.common_data.task_rect.p0 + src_task.common_data.task_rect.size;
-    vUv = vec3(mix(st0, st1, aPosition.xy) / texture_size, src_task.common_data.texture_layer_index);
-    vUvBounds = vec4(st0 + 0.5, st1 - 0.5) / texture_size.xyxy;
-
-    gl_Position = uTransform * vec4(local_pos, ci.z, 1.0);
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-void main(void) {
-    vec2 uv = clamp(vUv.xy, vUvBounds.xy, vUvBounds.zw);
-    oFragColor = texture(sColor0, vec3(uv, vUv.z));
-}
-#endif
--- a/gfx/webrender/res/ps_text_run.glsl
+++ b/gfx/webrender/res/ps_text_run.glsl
@@ -45,17 +45,21 @@ VertexInfo write_text_vertex(vec2 clampe
 
 #ifdef WR_FEATURE_GLYPH_TRANSFORM
     // For transformed subpixels, we just need to align the glyph origin to a device pixel.
     // Only check the scroll node transform's translation since the scales and axes match.
     vec2 world_snap_p0 = snap_rect.p0 + scroll_node.transform[3].xy * uDevicePixelRatio;
     final_pos += floor(world_snap_p0 + 0.5) - world_snap_p0;
 #elif !defined(WR_FEATURE_TRANSFORM)
     // Compute the snapping offset only if the scroll node transform is axis-aligned.
-    final_pos += compute_snap_offset(clamped_local_pos, scroll_node, snap_rect);
+    final_pos += compute_snap_offset(
+        clamped_local_pos,
+        scroll_node.transform,
+        snap_rect
+    );
 #endif
 
     gl_Position = uTransform * vec4(final_pos, z, 1.0);
 
     VertexInfo vi = VertexInfo(
         clamped_local_pos,
         device_pos,
         world_pos.w,
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -1,13 +1,13 @@
 /* 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, DeviceIntRect, DeviceIntSize, LayerToWorldScale};
+use api::{AlphaType, DeviceIntRect, DeviceIntSize};
 use api::{DeviceUintRect, DeviceUintPoint, DeviceUintSize, ExternalImageType, FilterOp, ImageRendering, LayerRect};
 use api::{DeviceIntPoint, SubpixelDirection, YuvColorSpace, YuvFormat};
 use api::{LayerToWorldTransform, WorldPixel};
 use border::{BorderCornerInstance, BorderCornerSide, BorderEdgeKind};
 use clip::{ClipSource, ClipStore, ClipWorkItem};
 use clip_scroll_tree::{CoordinateSystemId};
 use euclid::{TypedTransform3D, vec3};
 use glyph_rasterizer::GlyphFormat;
@@ -67,17 +67,16 @@ pub enum BrushBatchKind {
     RadialGradient,
     LinearGradient,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BatchKind {
-    HardwareComposite,
     SplitComposite,
     Transformable(TransformedRectKind, TransformBatchKind),
     Brush(BrushBatchKind),
 }
 
 /// 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)]
@@ -634,62 +633,56 @@ impl AlphaBatchBuilder {
             BlendMode::None
         };
 
         match prim_metadata.prim_kind {
             PrimitiveKind::Brush => {
                 let brush = &ctx.prim_store.cpu_brushes[prim_metadata.cpu_prim_index.0];
 
                 match brush.kind {
-                    BrushKind::Picture { pic_index } => {
+                    BrushKind::Picture { pic_index, source_kind, .. } => {
                         let picture =
                             &ctx.prim_store.pictures[pic_index.0];
 
-                        match picture.surface {
-                            Some(cache_task_id) => {
-                                let cache_task_address = render_tasks.get_task_address(cache_task_id);
-                                let textures = BatchTextures::render_target_cache();
-
-                                // If this picture is participating in a 3D rendering context,
-                                // then don't add it to any batches here. Instead, create a polygon
-                                // for it and add it to the current plane splitter.
-                                if picture.is_in_3d_context {
-                                    // Push into parent plane splitter.
+                        // If this picture is participating in a 3D rendering context,
+                        // then don't add it to any batches here. Instead, create a polygon
+                        // for it and add it to the current plane splitter.
+                        if picture.is_in_3d_context {
+                            // Push into parent plane splitter.
+                            debug_assert!(picture.surface.is_some());
 
-                                    let real_xf = &ctx.clip_scroll_tree
-                                        .nodes[picture.reference_frame_index.0]
-                                        .world_content_transform
-                                        .into();
-                                    let polygon = make_polygon(
-                                        picture.real_local_rect,
-                                        &real_xf,
-                                        prim_index.0,
-                                    );
-
-                                    splitter.add(polygon);
+                            let real_xf = &ctx.clip_scroll_tree
+                                .nodes[picture.reference_frame_index.0]
+                                .world_content_transform
+                                .into();
+                            let polygon = make_polygon(
+                                picture.real_local_rect,
+                                &real_xf,
+                                prim_index.0,
+                            );
 
-                                    return;
-                                }
+                            splitter.add(polygon);
+
+                            return;
+                        }
 
-                                // Depending on the composite mode of the picture, we generate the
-                                // old style Composite primitive instances. In the future, we'll
-                                // remove these and pass them through the brush batching pipeline.
-                                // This will allow us to unify some of the shaders, apply clip masks
-                                // when compositing pictures, and also correctly apply pixel snapping
-                                // to picture compositing operations.
-                                let source_id = cache_task_id;
-
-                                match picture.composite_mode.expect("bug: only composites here") {
-                                    PictureCompositeMode::Filter(filter) => {
-                                        match filter {
-                                            FilterOp::Blur(..) => {
+                        let add_to_parent_pic = match picture.composite_mode {
+                            Some(PictureCompositeMode::Filter(filter)) => {
+                                match filter {
+                                    FilterOp::Blur(..) => {
+                                        match picture.surface {
+                                            Some(cache_task_id) => {
                                                 let kind = BatchKind::Brush(
                                                     BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
                                                 );
-                                                let key = BatchKey::new(kind, non_segmented_blend_mode, textures);
+                                                let key = BatchKey::new(
+                                                    kind,
+                                                    non_segmented_blend_mode,
+                                                    BatchTextures::render_target_cache(),
+                                                );
                                                 let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
 
                                                 let uv_rect_address = render_tasks[cache_task_id]
                                                     .get_texture_handle()
                                                     .as_int(gpu_cache);
 
                                                 let instance = BrushInstance {
                                                     picture_address: task_address,
@@ -698,94 +691,91 @@ impl AlphaBatchBuilder {
                                                     scroll_id,
                                                     clip_task_address,
                                                     z,
                                                     segment_index: 0,
                                                     edge_flags: EdgeAaSegmentMask::empty(),
                                                     brush_flags: BrushFlags::empty(),
                                                     user_data: [
                                                         uv_rect_address,
-                                                        BrushImageSourceKind::Color as i32,
+                                                        (BrushImageSourceKind::Color as i32) << 16 |
                                                         RasterizationSpace::Screen as i32,
+                                                        picture.extra_gpu_data_handle.as_int(gpu_cache),
                                                     ],
                                                 };
                                                 batch.push(PrimitiveInstance::from(instance));
+                                                false
                                             }
-                                            FilterOp::DropShadow(offset, _, _) => {
-                                                let kind = BatchKind::Brush(
-                                                    BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
-                                                );
-                                                let key = BatchKey::new(kind, non_segmented_blend_mode, textures);
-
-                                                let uv_rect_address = render_tasks[cache_task_id]
-                                                    .get_texture_handle()
-                                                    .as_int(gpu_cache);
+                                            None => {
+                                                true
+                                            }
+                                        }
+                                    }
+                                    FilterOp::DropShadow(..) => {
+                                        if let Some(cache_task_id) = picture.surface {
+                                            let kind = BatchKind::Brush(
+                                                BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
+                                            );
 
-                                                let instance = BrushInstance {
-                                                    picture_address: task_address,
-                                                    prim_address: prim_cache_address,
-                                                    clip_chain_rect_index,
-                                                    scroll_id,
-                                                    clip_task_address,
-                                                    z,
-                                                    segment_index: 0,
-                                                    edge_flags: EdgeAaSegmentMask::empty(),
-                                                    brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
-                                                    user_data: [
-                                                        uv_rect_address,
-                                                        BrushImageSourceKind::ColorAlphaMask as i32,
-                                                        // TODO(gw): This is totally wrong, but the drop-shadow code itself
-                                                        //           is completely wrong, and doesn't work correctly with
-                                                        //           transformed Picture sources. I'm leaving this as is for
-                                                        //           now, and will fix drop-shadows properly, as a follow up.
-                                                        RasterizationSpace::Local as i32,
-                                                    ],
-                                                };
-
-                                                {
-                                                    let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
-                                                    batch.push(PrimitiveInstance::from(instance));
+                                            let (textures, task_id) = match source_kind {
+                                                BrushImageSourceKind::Color => {
+                                                    let secondary_id = picture.secondary_render_task_id.expect("no secondary!?");
+                                                    let saved_index = render_tasks[secondary_id].saved_index.expect("no saved index!?");
+                                                    debug_assert_ne!(saved_index, SavedTargetIndex::PENDING);
+                                                    let textures = BatchTextures {
+                                                        colors: [
+                                                            SourceTexture::RenderTaskCache(saved_index),
+                                                            SourceTexture::Invalid,
+                                                            SourceTexture::Invalid,
+                                                        ],
+                                                    };
+                                                    (textures, secondary_id)
                                                 }
+                                                BrushImageSourceKind::ColorAlphaMask => {
+                                                    (BatchTextures::render_target_cache(), cache_task_id)
+                                                }
+                                            };
 
-                                                let secondary_id = picture.secondary_render_task_id.expect("no secondary!?");
-                                                let saved_index = render_tasks[secondary_id].saved_index.expect("no saved index!?");
-                                                debug_assert_ne!(saved_index, SavedTargetIndex::PENDING);
-                                                let secondary_task_address = render_tasks.get_task_address(secondary_id);
-                                                let secondary_textures = BatchTextures {
-                                                    colors: [
-                                                        SourceTexture::RenderTaskCache(saved_index),
-                                                        SourceTexture::Invalid,
-                                                        SourceTexture::Invalid,
-                                                    ],
-                                                };
-                                                let key = BatchKey::new(
-                                                    BatchKind::HardwareComposite,
-                                                    BlendMode::PremultipliedAlpha,
-                                                    secondary_textures,
-                                                );
-                                                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
-                                                let content_rect = prim_metadata.local_rect.translate(&-offset);
-                                                let rect =
-                                                    (content_rect * LayerToWorldScale::new(1.0) * ctx.device_pixel_scale).round()
-                                                                                                                         .to_i32();
+                                            let key = BatchKey::new(
+                                                kind,
+                                                non_segmented_blend_mode,
+                                                textures,
+                                            );
+
+                                            let uv_rect_address = render_tasks[task_id]
+                                                .get_texture_handle()
+                                                .as_int(gpu_cache);
 
-                                                let instance = CompositePrimitiveInstance::new(
-                                                    task_address,
-                                                    secondary_task_address,
-                                                    RenderTaskAddress(0),
-                                                    rect.origin.x,
-                                                    rect.origin.y,
-                                                    z,
-                                                    rect.size.width,
-                                                    rect.size.height,
-                                                );
+                                            let instance = BrushInstance {
+                                                picture_address: task_address,
+                                                prim_address: prim_cache_address,
+                                                clip_chain_rect_index,
+                                                scroll_id,
+                                                clip_task_address,
+                                                z,
+                                                segment_index: 0,
+                                                edge_flags: EdgeAaSegmentMask::empty(),
+                                                brush_flags: BrushFlags::empty(),
+                                                user_data: [
+                                                    uv_rect_address,
+                                                    (source_kind as i32) << 16 |
+                                                    RasterizationSpace::Screen as i32,
+                                                    picture.extra_gpu_data_handle.as_int(gpu_cache),
+                                                ],
+                                            };
 
-                                                batch.push(PrimitiveInstance::from(instance));
-                                            }
-                                            _ => {
+                                            let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
+                                            batch.push(PrimitiveInstance::from(instance));
+                                        }
+
+                                        false
+                                    }
+                                    _ => {
+                                        match picture.surface {
+                                            Some(cache_task_id) => {
                                                 let key = BatchKey::new(
                                                     BatchKind::Brush(BrushBatchKind::Blend),
                                                     BlendMode::PremultipliedAlpha,
                                                     BatchTextures::render_target_cache(),
                                                 );
 
                                                 let filter_mode = match filter {
                                                     FilterOp::Blur(..) => 0,
@@ -819,16 +809,18 @@ impl AlphaBatchBuilder {
                                                     FilterOp::DropShadow(..) => {
                                                         unreachable!();
                                                     }
                                                     FilterOp::ColorMatrix(_) => {
                                                         picture.extra_gpu_data_handle.as_int(gpu_cache)
                                                     }
                                                 };
 
+                                                let cache_task_address = render_tasks.get_task_address(cache_task_id);
+
                                                 let instance = BrushInstance {
                                                     picture_address: task_address,
                                                     prim_address: prim_cache_address,
                                                     clip_chain_rect_index,
                                                     scroll_id,
                                                     clip_task_address,
                                                     z,
                                                     segment_index: 0,
@@ -838,100 +830,117 @@ impl AlphaBatchBuilder {
                                                         cache_task_address.0 as i32,
                                                         filter_mode,
                                                         user_data,
                                                     ],
                                                 };
 
                                                 let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
                                                 batch.push(PrimitiveInstance::from(instance));
+                                                false
+                                            }
+                                            None => {
+                                                true
                                             }
                                         }
                                     }
-                                    PictureCompositeMode::MixBlend(mode) => {
-                                        let backdrop_id = picture.secondary_render_task_id.expect("no backdrop!?");
-
-                                        let key = BatchKey::new(
-                                            BatchKind::Brush(
-                                                BrushBatchKind::MixBlend {
-                                                    task_id,
-                                                    source_id,
-                                                    backdrop_id,
-                                                },
-                                            ),
-                                            BlendMode::PremultipliedAlpha,
-                                            BatchTextures::no_texture(),
-                                        );
-                                        let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
-                                        let backdrop_task_address = render_tasks.get_task_address(backdrop_id);
-                                        let source_task_address = render_tasks.get_task_address(source_id);
-
-                                        let instance = BrushInstance {
-                                            picture_address: task_address,
-                                            prim_address: prim_cache_address,
-                                            clip_chain_rect_index,
-                                            scroll_id,
-                                            clip_task_address,
-                                            z,
-                                            segment_index: 0,
-                                            edge_flags: EdgeAaSegmentMask::empty(),
-                                            brush_flags: BrushFlags::empty(),
-                                            user_data: [
-                                                mode as u32 as i32,
-                                                backdrop_task_address.0 as i32,
-                                                source_task_address.0 as i32,
-                                            ],
-                                        };
-
-                                        batch.push(PrimitiveInstance::from(instance));
-                                    }
-                                    PictureCompositeMode::Blit => {
-                                        let kind = BatchKind::Brush(
-                                            BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
-                                        );
-                                        let key = BatchKey::new(kind, non_segmented_blend_mode, textures);
-                                        let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
-
-                                        let uv_rect_address = render_tasks[cache_task_id]
-                                            .get_texture_handle()
-                                            .as_int(gpu_cache);
-
-                                        let instance = BrushInstance {
-                                            picture_address: task_address,
-                                            prim_address: prim_cache_address,
-                                            clip_chain_rect_index,
-                                            scroll_id,
-                                            clip_task_address,
-                                            z,
-                                            segment_index: 0,
-                                            edge_flags: EdgeAaSegmentMask::empty(),
-                                            brush_flags: BrushFlags::empty(),
-                                            user_data: [
-                                                uv_rect_address,
-                                                BrushImageSourceKind::Color as i32,
-                                                RasterizationSpace::Screen as i32,
-                                            ],
-                                        };
-                                        batch.push(PrimitiveInstance::from(instance));
-                                    }
                                 }
                             }
+                            Some(PictureCompositeMode::MixBlend(mode)) => {
+                                let cache_task_id = picture.surface.expect("bug: no surface allocated");
+                                let backdrop_id = picture.secondary_render_task_id.expect("no backdrop!?");
+
+                                let key = BatchKey::new(
+                                    BatchKind::Brush(
+                                        BrushBatchKind::MixBlend {
+                                            task_id,
+                                            source_id: cache_task_id,
+                                            backdrop_id,
+                                        },
+                                    ),
+                                    BlendMode::PremultipliedAlpha,
+                                    BatchTextures::no_texture(),
+                                );
+                                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
+                                let backdrop_task_address = render_tasks.get_task_address(backdrop_id);
+                                let source_task_address = render_tasks.get_task_address(cache_task_id);
+
+                                let instance = BrushInstance {
+                                    picture_address: task_address,
+                                    prim_address: prim_cache_address,
+                                    clip_chain_rect_index,
+                                    scroll_id,
+                                    clip_task_address,
+                                    z,
+                                    segment_index: 0,
+                                    edge_flags: EdgeAaSegmentMask::empty(),
+                                    brush_flags: BrushFlags::empty(),
+                                    user_data: [
+                                        mode as u32 as i32,
+                                        backdrop_task_address.0 as i32,
+                                        source_task_address.0 as i32,
+                                    ],
+                                };
+
+                                batch.push(PrimitiveInstance::from(instance));
+                                false
+                            }
+                            Some(PictureCompositeMode::Blit) => {
+                                let cache_task_id = picture.surface.expect("bug: no surface allocated");
+                                let kind = BatchKind::Brush(
+                                    BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
+                                );
+                                let key = BatchKey::new(
+                                    kind,
+                                    non_segmented_blend_mode,
+                                    BatchTextures::render_target_cache(),
+                                );
+                                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
+
+                                let uv_rect_address = render_tasks[cache_task_id]
+                                    .get_texture_handle()
+                                    .as_int(gpu_cache);
+
+                                let instance = BrushInstance {
+                                    picture_address: task_address,
+                                    prim_address: prim_cache_address,
+                                    clip_chain_rect_index,
+                                    scroll_id,
+                                    clip_task_address,
+                                    z,
+                                    segment_index: 0,
+                                    edge_flags: EdgeAaSegmentMask::empty(),
+                                    brush_flags: BrushFlags::empty(),
+                                    user_data: [
+                                        uv_rect_address,
+                                        (BrushImageSourceKind::Color as i32) << 16 |
+                                        RasterizationSpace::Screen as i32,
+                                        picture.extra_gpu_data_handle.as_int(gpu_cache),
+                                    ],
+                                };
+                                batch.push(PrimitiveInstance::from(instance));
+                                false
+                            }
                             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,
-                                    task_id,
-                                    ctx,
-                                    gpu_cache,
-                                    render_tasks,
-                                    deferred_resolves,
-                                    z_generator,
-                                );
+                                true
                             }
+                        };
+
+                        // If this picture is being drawn into an existing target (i.e. with
+                        // no composition operation), recurse and add to the current batch list.
+                        if add_to_parent_pic {
+                            self.add_pic_to_batch(
+                                picture,
+                                task_id,
+                                ctx,
+                                gpu_cache,
+                                render_tasks,
+                                deferred_resolves,
+                                z_generator,
+                            );
                         }
                     }
                     _ => {
                         if let Some((batch_kind, textures, user_data)) = brush.get_batch_params(
                                 ctx.resource_cache,
                                 gpu_cache,
                                 deferred_resolves,
                                 &ctx.cached_gradients,
@@ -1223,17 +1232,17 @@ impl AlphaBatchBuilder {
             }
         }
     }
 }
 
 impl BrushPrimitive {
     pub fn get_picture_index(&self) -> PictureIndex {
         match self.kind {
-            BrushKind::Picture { pic_index } => {
+            BrushKind::Picture { pic_index, .. } => {
                 pic_index
             }
             _ => {
                 panic!("bug: not a picture brush!!");
             }
         }
     }
 
@@ -1258,18 +1267,19 @@ impl BrushPrimitive {
                 } else {
                     let textures = BatchTextures::color(cache_item.texture_id);
 
                     Some((
                         BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
                         textures,
                         [
                             cache_item.uv_rect_handle.as_int(gpu_cache),
-                            BrushImageSourceKind::Color as i32,
-                            RasterizationSpace::Local as i32,
+                            (BrushImageSourceKind::Color as i32) << 16|
+                             RasterizationSpace::Local as i32,
+                            0,
                         ],
                     ))
                 }
             }
             BrushKind::Picture { .. } => {
                 panic!("bug: get_batch_key is handled at higher level for pictures");
             }
             BrushKind::Solid { .. } => {
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -161,17 +161,17 @@ impl NormalBorderHelpers for NormalBorde
             (BorderStyle::Solid, BorderStyle::Solid) => {
                 if edge0.color == edge1.color && radius.width == 0.0 && radius.height == 0.0 {
                     BorderCornerKind::Solid
                 } else {
                     BorderCornerKind::Clip(BorderCornerInstance::Single)
                 }
             }
 
-            // Inset / outset borders just modtify the color of edges, so can be
+            // Inset / outset borders just modify the color of edges, so can be
             // drawn with the normal border corner shader.
             (BorderStyle::Outset, BorderStyle::Outset) |
             (BorderStyle::Inset, BorderStyle::Inset) |
             (BorderStyle::Double, BorderStyle::Double) |
             (BorderStyle::Groove, BorderStyle::Groove) |
             (BorderStyle::Ridge, BorderStyle::Ridge) => {
                 BorderCornerKind::Clip(BorderCornerInstance::Single)
             }
@@ -440,17 +440,17 @@ impl<'a> DisplayListFlattener<'a> {
                 info.rect.origin.x + info.rect.size.width - right_len,
                 info.rect.origin.y + info.rect.size.height - bottom_len,
             );
             let p3 = info.rect.bottom_right();
 
             let segment = |x0, y0, x1, y1| BrushSegment::new(
                 LayerPoint::new(x0, y0),
                 LayerSize::new(x1-x0, y1-y0),
-                false,
+                true,
                 EdgeAaSegmentMask::all() // Note: this doesn't seem right, needs revision
             );
 
             // Add a solid rectangle for each visible edge/corner combination.
             if top_edge == BorderEdgeKind::Solid {
                 let descriptor = BrushSegmentDescriptor {
                     segments: vec![
                         segment(p0.x, p0.y, p1.x, p1.y),
--- a/gfx/webrender/src/capture.rs
+++ b/gfx/webrender/src/capture.rs
@@ -1,16 +1,16 @@
 /* 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, ExternalImageId, ImageDescriptor, TexelRect};
+use api::{CaptureBits, ExternalImageData, ImageDescriptor, TexelRect};
 #[cfg(feature = "png")]
 use device::ReadPixelsFormat;
 use ron;
 use serde;
 
 
 pub struct CaptureConfig {
     pub root: PathBuf,
@@ -118,15 +118,13 @@ pub struct ExternalCaptureImage {
 
 /// 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,
-    /// Public ID of the external image.
-    pub id: ExternalImageId,
-    /// Channel index of an external image.
-    pub channel_index: u8,
+    /// External image data source.
+    pub external: ExternalImageData,
     /// UV sub-rectangle of the image.
     pub uv: TexelRect,
 }
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -1,32 +1,34 @@
 /* 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::shader_source;
-use api::{ColorF, ImageDescriptor, ImageFormat};
+use api::{ColorF, ImageFormat};
 use api::{DeviceIntPoint, DeviceIntRect, DeviceUintRect, DeviceUintSize};
 use api::TextureTarget;
+#[cfg(any(feature = "debug_renderer", feature="capture"))]
+use api::ImageDescriptor;
 use euclid::Transform3D;
 use gleam::gl;
 use internal_types::{FastHashMap, RenderTargetInfo};
 use smallvec::SmallVec;
 use std::cell::RefCell;
 use std::fs::File;
 use std::io::Read;
 use std::marker::PhantomData;
 use std::mem;
 use std::ops::Add;
 use std::path::PathBuf;
 use std::ptr;
 use std::rc::Rc;
+use std::slice;
 use std::thread;
 
-
 #[derive(Debug, Copy, Clone, PartialEq, Ord, Eq, PartialOrd)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameId(usize);
 
 impl FrameId {
     pub fn new(value: usize) -> Self {
         FrameId(value)
@@ -55,32 +57,34 @@ const SHADER_LINE_MARKER: &str = "#line 
 
 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 {
+    #[cfg(feature = "debug_renderer")]
     Less = gl::LESS,
     LessEqual = gl::LEQUAL,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum TextureFilter {
     Nearest,
     Linear,
     Trilinear,
 }
 
 #[derive(Debug)]
 pub enum VertexAttributeKind {
     F32,
+    #[cfg(feature = "debug_renderer")]
     U8Norm,
     U16Norm,
     I32,
     U16,
 }
 
 #[derive(Debug)]
 pub struct VertexAttribute {
@@ -104,16 +108,21 @@ enum FBOTarget {
 #[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 {}
+
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub enum ReadPixelsFormat {
     Standard(ImageFormat),
     Rgba8,
 }
 
 pub fn get_gl_target(target: TextureTarget) -> gl::GLuint {
     match target {
@@ -225,16 +234,17 @@ pub fn build_shader_strings(
 pub trait FileWatcherHandler: Send {
     fn file_changed(&self, path: PathBuf);
 }
 
 impl VertexAttributeKind {
     fn size_in_bytes(&self) -> u32 {
         match *self {
             VertexAttributeKind::F32 => 4,
+            #[cfg(feature = "debug_renderer")]
             VertexAttributeKind::U8Norm => 1,
             VertexAttributeKind::U16Norm => 2,
             VertexAttributeKind::I32 => 4,
             VertexAttributeKind::U16 => 2,
         }
     }
 }
 
@@ -260,16 +270,17 @@ impl VertexAttribute {
                     attr_index,
                     self.count as gl::GLint,
                     gl::FLOAT,
                     false,
                     stride,
                     offset,
                 );
             }
+            #[cfg(feature = "debug_renderer")]
             VertexAttributeKind::U8Norm => {
                 gl.vertex_attrib_pointer(
                     attr_index,
                     self.count as gl::GLint,
                     gl::UNSIGNED_BYTE,
                     true,
                     stride,
                     offset,
@@ -453,20 +464,22 @@ impl Texture {
     pub fn get_layer_count(&self) -> i32 {
         self.layer_count
     }
 
     pub fn get_format(&self) -> ImageFormat {
         self.format
     }
 
+    #[cfg(any(feature = "debug_renderer", feature = "capture"))]
     pub fn get_filter(&self) -> TextureFilter {
         self.filter
     }
 
+    #[cfg(any(feature = "debug_renderer", feature = "capture"))]
     pub fn get_render_target(&self) -> Option<RenderTargetInfo> {
         self.render_target.clone()
     }
 
     pub fn has_depth(&self) -> bool {
         self.depth_rb.is_some()
     }
 
@@ -631,23 +644,24 @@ impl VertexUsageHint {
 
 #[derive(Copy, Clone, Debug)]
 pub struct UniformLocation(gl::GLint);
 
 impl UniformLocation {
     pub const INVALID: Self = UniformLocation(-1);
 }
 
+#[cfg(feature = "debug_renderer")]
 pub struct Capabilities {
     pub supports_multisampling: bool,
 }
 
 #[derive(Clone, Debug)]
 pub enum ShaderError {
-    Compilation(String, String), // name, error mssage
+    Compilation(String, String), // name, error message
     Link(String, String),        // name, error message
 }
 
 pub struct Device {
     gl: Rc<gl::Gl>,
     // device state
     bound_textures: [gl::GLuint; 16],
     bound_program: gl::GLuint,
@@ -656,17 +670,18 @@ pub struct Device {
     bound_draw_fbo: FBOId,
     program_mode_id: UniformLocation,
     default_read_fbo: gl::GLuint,
     default_draw_fbo: gl::GLuint,
 
     device_pixel_ratio: f32,
     upload_method: UploadMethod,
 
-    // HW or API capabilties
+    // HW or API capabilities
+    #[cfg(feature = "debug_renderer")]
     capabilities: Capabilities,
 
     // debug
     inside_frame: bool,
 
     // resources
     resource_override_path: Option<PathBuf>,
 
@@ -703,16 +718,17 @@ impl Device {
             gl,
             resource_override_path,
             // This is initialized to 1 by default, but it is reset
             // at the beginning of each frame in `Renderer::bind_frame_data`.
             device_pixel_ratio: 1.0,
             upload_method,
             inside_frame: false,
 
+            #[cfg(feature = "debug_renderer")]
             capabilities: Capabilities {
                 supports_multisampling: false, //TODO
             },
 
             bound_textures: [0; 16],
             bound_program: 0,
             bound_vao: 0,
             bound_read_fbo: FBOId(0),
@@ -744,16 +760,17 @@ impl Device {
     pub fn update_program_cache(&mut self, cached_programs: Rc<ProgramCache>) {
         self.cached_programs = Some(cached_programs);
     }
 
     pub fn max_texture_size(&self) -> u32 {
         self.max_texture_size
     }
 
+    #[cfg(feature = "debug_renderer")]
     pub fn get_capabilities(&self) -> &Capabilities {
         &self.capabilities
     }
 
     pub fn reset_state(&mut self) {
         self.bound_textures = [0; 16];
         self.bound_vao = 0;
         self.bound_read_fbo = FBOId(0);
@@ -781,17 +798,17 @@ impl Device {
             Ok(id)
         }
     }
 
     pub fn begin_frame(&mut self) -> FrameId {
         debug_assert!(!self.inside_frame);
         self.inside_frame = true;
 
-        // Retrive the currently set FBO.
+        // Retrieve the currently set FBO.
         let default_read_fbo = self.gl.get_integer_v(gl::READ_FRAMEBUFFER_BINDING);
         self.default_read_fbo = default_read_fbo as gl::GLuint;
         let default_draw_fbo = self.gl.get_integer_v(gl::DRAW_FRAMEBUFFER_BINDING);
         self.default_draw_fbo = default_draw_fbo as gl::GLuint;
 
         // Texture state
         for i in 0 .. self.bound_textures.len() {
             self.bound_textures[i] = 0;
@@ -990,38 +1007,38 @@ impl Device {
         texture.width = new_size.width;
         texture.height = new_size.height;
         let rt_info = texture.render_target
             .clone()
             .expect("Only renderable textures are expected for resize here");
 
         self.bind_texture(DEFAULT_TEXTURE, texture);
         self.set_texture_parameters(texture.target, texture.filter);
-        self.update_target_storage(texture, &rt_info, true, None);
+        self.update_target_storage::<u8>(texture, &rt_info, true, None);
 
         let rect = DeviceIntRect::new(DeviceIntPoint::zero(), old_size.to_i32());
         for (read_fbo, &draw_fbo) in old_fbos.into_iter().zip(&texture.fbo_ids) {
             self.bind_read_target_impl(read_fbo);
             self.bind_draw_target_impl(draw_fbo);
             self.blit_render_target(rect, rect);
             self.delete_fbo(read_fbo);
         }
         self.gl.delete_textures(&[old_texture_id]);
         self.bind_read_target(None);
     }
 
-    pub fn init_texture(
+    pub fn init_texture<T: Texel>(
         &mut self,
         texture: &mut Texture,
         mut width: u32,
         mut height: u32,
         filter: TextureFilter,
         render_target: Option<RenderTargetInfo>,
         layer_count: i32,
-        pixels: Option<&[u8]>,
+        pixels: Option<&[T]>,
     ) {
         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);
         }
@@ -1044,22 +1061,22 @@ impl Device {
             }
             None => {
                 self.update_texture_storage(texture, pixels);
             }
         }
     }
 
     /// Updates the render target storage for the texture, creating FBOs as required.
-    fn update_target_storage(
+    fn update_target_storage<T: Texel>(
         &mut self,
         texture: &mut Texture,
         rt_info: &RenderTargetInfo,
         is_resized: bool,
-        pixels: Option<&[u8]>,
+        pixels: Option<&[T]>,
     ) {
         assert!(texture.layer_count > 0 || texture.width + texture.height == 0);
 
         let needed_layer_count = texture.layer_count - texture.fbo_ids.len() as i32;
         let allocate_color = needed_layer_count != 0 || is_resized || pixels.is_some();
 
         if allocate_color {
             let desc = gl_describe_format(self.gl(), texture.format);
@@ -1070,31 +1087,31 @@ impl Device {
                         0,
                         desc.internal,
                         texture.width as _,
                         texture.height as _,
                         texture.layer_count,
                         0,
                         desc.external,
                         desc.pixel_type,
-                        pixels,
+                        pixels.map(texels_to_u8_slice),
                     )
                 }
                 _ => {
                     assert_eq!(texture.layer_count, 1);
                     self.gl.tex_image_2d(
                         texture.target,
                         0,
                         desc.internal,
                         texture.width as _,
                         texture.height as _,
                         0,
                         desc.external,
                         desc.pixel_type,
-                        pixels,
+                        pixels.map(texels_to_u8_slice),
                     )
                 }
             }
         }
 
         if needed_layer_count > 0 {
             // Create more framebuffers to fill the gap
             let new_fbos = self.gl.gen_framebuffers(needed_layer_count);
@@ -1167,44 +1184,44 @@ impl Device {
                     gl::RENDERBUFFER,
                     depth_rb,
                 );
             }
             self.bind_external_draw_target(original_bound_fbo);
         }
     }
 
-    fn update_texture_storage(&mut self, texture: &Texture, pixels: Option<&[u8]>) {
+    fn update_texture_storage<T: Texel>(&mut self, texture: &Texture, pixels: Option<&[T]>) {
         let desc = gl_describe_format(self.gl(), texture.format);
         match texture.target {
             gl::TEXTURE_2D_ARRAY => {
                 self.gl.tex_image_3d(
                     gl::TEXTURE_2D_ARRAY,
                     0,
                     desc.internal,
                     texture.width as _,
                     texture.height as _,
                     texture.layer_count,
                     0,
                     desc.external,
                     desc.pixel_type,
-                    pixels,
+                    pixels.map(texels_to_u8_slice),
                 );
             }
             gl::TEXTURE_2D | gl::TEXTURE_RECTANGLE | gl::TEXTURE_EXTERNAL_OES => {
                 self.gl.tex_image_2d(
                     texture.target,
                     0,
                     desc.internal,
                     texture.width as _,
                     texture.height as _,
                     0,
                     desc.external,
                     desc.pixel_type,
-                    pixels,
+                    pixels.map(texels_to_u8_slice),
                 );
             }
             _ => panic!("BUG: Unexpected texture target!"),
         }
     }
 
     pub fn blit_render_target(&mut self, src_rect: DeviceIntRect, dest_rect: DeviceIntRect) {
         debug_assert!(self.inside_frame);
@@ -1283,16 +1300,23 @@ impl Device {
         texture.width = 0;
         texture.height = 0;
         texture.layer_count = 0;
     }
 
     pub fn delete_texture(&mut self, mut texture: Texture) {
         self.free_texture_storage(&mut texture);
         self.gl.delete_textures(&[texture.id]);
+
+        for bound_texture in &mut self.bound_textures {
+            if *bound_texture == texture.id {
+                *bound_texture = 0
+            }
+        }
+
         texture.id = 0;
     }
 
     #[cfg(feature = "replay")]
     pub fn delete_external_texture(&mut self, mut external: ExternalTexture) {
         self.bind_external_texture(DEFAULT_TEXTURE, &external);
         //Note: the format descriptor here doesn't really matter
         self.free_texture_storage_impl(external.target, FormatDesc {
@@ -1446,16 +1470,17 @@ impl Device {
             if u_location != -1 {
                 self.bind_program(program);
                 self.gl
                     .uniform_1i(u_location, binding.1.into().0 as gl::GLint);
             }
         }
     }
 
+    #[cfg(feature = "debug_renderer")]
     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<f32>,
@@ -1513,16 +1538,17 @@ impl Device {
                 gl: &*self.gl,
                 texture,
             },
             buffer,
             marker: PhantomData,
         }
     }
 
+    #[cfg(any(feature = "debug_renderer", feature = "capture"))]
     pub fn read_pixels(&mut self, img_desc: &ImageDescriptor) -> Vec<u8> {
         let desc = gl_describe_format(self.gl(), img_desc.format);
         self.gl.read_pixels(
             0, 0,
             img_desc.width as i32,
             img_desc.height as i32,
             desc.external,
             desc.pixel_type,
@@ -1559,16 +1585,17 @@ impl Device {
             rect.size.height as _,
             desc.external,
             desc.pixel_type,
             output,
         );
     }
 
     /// Get texels of a texture into the specified output slice.
+    #[cfg(feature = "debug_renderer")]
     pub fn get_tex_image_into(
         &mut self,
         texture: &Texture,
         format: ImageFormat,
         output: &mut [u8],
     ) {
         self.bind_texture(DEFAULT_TEXTURE, texture);
         let desc = gl_describe_format(self.gl(), format);
@@ -1577,16 +1604,17 @@ impl Device {
             0,
             desc.external,
             desc.pixel_type,
             output,
         );
     }
 
     /// Attaches the provided texture to the current Read FBO binding.
+    #[cfg(any(feature = "debug_renderer", feature="capture"))]
     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,
@@ -1603,22 +1631,24 @@ impl Device {
                     target,
                     texture_id,
                     0,
                 )
             }
         }
     }
 
+    #[cfg(any(feature = "debug_renderer", feature="capture"))]
     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)
     }
 
+    #[cfg(any(feature = "debug_renderer", feature="capture"))]
     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 {
@@ -1844,31 +1874,33 @@ impl Device {
         self.gl.draw_elements(
             gl::TRIANGLES,
             index_count,
             gl::UNSIGNED_SHORT,
             first_vertex as u32 * 2,
         );
     }
 
+    #[cfg(feature = "debug_renderer")]
     pub fn draw_triangles_u32(&mut self, first_vertex: i32, index_count: i32) {
         debug_assert!(self.inside_frame);
         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);
         self.gl.draw_arrays(gl::POINTS, first_vertex, vertex_count);
     }
 
+    #[cfg(feature = "debug_renderer")]
     pub fn draw_nonindexed_lines(&mut self, first_vertex: i32, vertex_count: i32) {
         debug_assert!(self.inside_frame);
         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);
         self.gl.draw_elements_instanced(
@@ -2009,26 +2041,29 @@ impl Device {
             .blend_func_separate(gl::ZERO, gl::SRC_COLOR, gl::ZERO, gl::SRC_ALPHA);
         self.gl.blend_equation(gl::FUNC_ADD);
     }
     pub fn set_blend_mode_max(&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(feature = "debug_renderer")]
     pub fn set_blend_mode_min(&self) {
         self.gl
             .blend_func_separate(gl::ONE, gl::ONE, gl::ONE, gl::ONE);
         self.gl.blend_equation_separate(gl::MIN, gl::FUNC_ADD);
     }
     pub fn set_blend_mode_subpixel_pass0(&self) {
         self.gl.blend_func(gl::ZERO, gl::ONE_MINUS_SRC_COLOR);
+        self.gl.blend_equation(gl::FUNC_ADD);
     }
     pub fn set_blend_mode_subpixel_pass1(&self) {
         self.gl.blend_func(gl::ONE, gl::ONE);
+        self.gl.blend_equation(gl::FUNC_ADD);
     }
     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);
         self.gl.blend_equation(gl::FUNC_ADD);
     }
     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);
         self.gl.blend_equation(gl::FUNC_ADD);
@@ -2041,16 +2076,17 @@ impl Device {
         // color is an unpremultiplied color.
         self.gl.blend_color(color.r, color.g, color.b, 1.0);
         self.gl
             .blend_func(gl::CONSTANT_COLOR, gl::ONE_MINUS_SRC_COLOR);
         self.gl.blend_equation(gl::FUNC_ADD);
     }
     pub fn set_blend_mode_subpixel_dual_source(&self) {
         self.gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC1_COLOR);
+        self.gl.blend_equation(gl::FUNC_ADD);
     }
 
     pub fn supports_extension(&self, extension: &str) -> bool {
         self.extensions.iter().any(|s| s == extension)
     }
 }
 
 struct FormatDesc {
@@ -2255,8 +2291,14 @@ impl<'a> UploadTarget<'a> {
         }
 
         // Reset row length to 0, otherwise the stride would apply to all texture uploads.
         if chunk.stride.is_some() {
             self.gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, 0 as _);
         }
     }
 }
+
+fn texels_to_u8_slice<T: Texel>(texels: &[T]) -> &[u8] {
+    unsafe {
+        slice::from_raw_parts(texels.as_ptr() as *const u8, texels.len() * mem::size_of::<T>())
+    }
+}
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -9,16 +9,17 @@ use api::{DevicePixelScale, DeviceUintRe
 use api::{FilterOp, FontInstanceKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayerPoint, LayerPrimitiveInfo};
 use api::{LayerRect, LayerSize, LayerVector2D, LayoutRect, LayoutSize, LayoutTransform};
 use api::{LayoutVector2D, LineOrientation, LineStyle, LocalClip, PipelineId, PropertyBinding};
 use api::{RepeatMode, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity, Shadow};
 use api::{SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect, TileOffset};
 use api::{TransformStyle, YuvColorSpace, YuvData};
 use app_units::Au;
+use batch::BrushImageSourceKind;
 use border::ImageBorderSegment;
 use clip::{ClipRegion, ClipSource, ClipSources, ClipStore};
 use clip_scroll_node::{ClipScrollNode, NodeType, StickyFrameInfo};
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
 use euclid::{SideOffsets2D, vec2};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use hit_test::{HitTestingItem, HitTestingRun};
@@ -1050,17 +1051,21 @@ impl<'a> DisplayListFlattener<'a> {
                 None,
                 false,
                 pipeline_id,
                 current_reference_frame_index,
                 None,
                 true,
             );
 
-            let prim = BrushPrimitive::new_picture(container_index);
+            let prim = BrushPrimitive::new_picture(
+                container_index,
+                BrushImageSourceKind::Color,
+                LayerVector2D::zero(),
+            );
 
             let prim_index = self.prim_store.add_primitive(
                 &LayerRect::zero(),
                 &max_clip,
                 is_backface_visible,
                 None,
                 None,
                 PrimitiveContainer::Brush(prim),
@@ -1103,29 +1108,63 @@ impl<'a> DisplayListFlattener<'a> {
                 Some(PictureCompositeMode::Filter(*filter)),
                 false,
                 pipeline_id,
                 current_reference_frame_index,
                 None,
                 true,
             );
 
-            let src_prim = BrushPrimitive::new_picture(src_pic_index);
+            // For drop shadows, add an extra brush::picture primitive
+            // that will draw the picture as an alpha mask.
+            let shadow_prim_index = match *filter {
+                FilterOp::DropShadow(offset, ..) => {
+                    let shadow_prim = BrushPrimitive::new_picture(
+                        src_pic_index,
+                        BrushImageSourceKind::ColorAlphaMask,
+                        offset,
+                    );
+                    Some(self.prim_store.add_primitive(
+                        &LayerRect::zero(),
+                        &max_clip,
+                        is_backface_visible,
+                        None,
+                        None,
+                        PrimitiveContainer::Brush(shadow_prim),
+                    ))
+                }
+                _ => {
+                    None
+                }
+            };
 
+            let src_prim = BrushPrimitive::new_picture(
+                src_pic_index,
+                BrushImageSourceKind::Color,
+                LayoutVector2D::zero(),
+            );
             let src_prim_index = self.prim_store.add_primitive(
                 &LayerRect::zero(),
                 &max_clip,
                 is_backface_visible,
                 None,
                 None,
                 PrimitiveContainer::Brush(src_prim),
             );
 
             let parent_pic = &mut self.prim_store.pictures[parent_pic_index.0];
             parent_pic_index = src_pic_index;
+
+            if let Some(shadow_prim_index) = shadow_prim_index {
+                parent_pic.add_primitive(
+                    shadow_prim_index,
+                    clip_and_scroll,
+                );
+            }
+
             parent_pic.add_primitive(
                 src_prim_index,
                 clip_and_scroll,
             );
 
             self.picture_stack.push(src_pic_index);
         }
 
@@ -1135,17 +1174,21 @@ impl<'a> DisplayListFlattener<'a> {
                 Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
                 false,
                 pipeline_id,
                 current_reference_frame_index,
                 None,
                 true,
             );
 
-            let src_prim = BrushPrimitive::new_picture(src_pic_index);
+            let src_prim = BrushPrimitive::new_picture(
+                src_pic_index,
+                BrushImageSourceKind::Color,
+                LayoutVector2D::zero(),
+            );
 
             let src_prim_index = self.prim_store.add_primitive(
                 &LayerRect::zero(),
                 &max_clip,
                 is_backface_visible,
                 None,
                 None,
                 PrimitiveContainer::Brush(src_prim),
@@ -1190,17 +1233,21 @@ impl<'a> DisplayListFlattener<'a> {
             participating_in_3d_context,
             pipeline_id,
             current_reference_frame_index,
             frame_output_pipeline_id,
                 true,
         );
 
         // Create a brush primitive that draws this picture.
-        let sc_prim = BrushPrimitive::new_picture(pic_index);
+        let sc_prim = BrushPrimitive::new_picture(
+            pic_index,
+            BrushImageSourceKind::Color,
+            LayoutVector2D::zero(),
+        );
 
         // Add the brush to the parent picture.
         let sc_prim_index = self.prim_store.add_primitive(
             &LayerRect::zero(),
             &max_clip,
             is_backface_visible,
             None,
             None,
@@ -1405,31 +1452,41 @@ impl<'a> DisplayListFlattener<'a> {
         let current_reference_frame_index = self.current_reference_frame_index();
         let max_clip = LayerRect::max_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 std_deviation = 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 apply_local_clip_rect = shadow.blur_radius == 0.0;
+
         // 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 shadow_pic_index = self.prim_store.add_image_picture(
             Some(PictureCompositeMode::Filter(FilterOp::Blur(std_deviation))),
             false,
             pipeline_id,
             current_reference_frame_index,
             None,
-            false,
+            apply_local_clip_rect,
         );
 
         // Create the primitive to draw the shadow picture into the scene.
-        let shadow_prim = BrushPrimitive::new_picture(shadow_pic_index);
+        let shadow_prim = BrushPrimitive::new_picture(
+            shadow_pic_index,
+            BrushImageSourceKind::Color,
+            LayoutVector2D::zero(),
+        );
         let shadow_prim_index = self.prim_store.add_primitive(
             &LayerRect::zero(),
             &max_clip,
             info.is_backface_visible,
             None,
             None,
             PrimitiveContainer::Brush(shadow_prim),
         );
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,33 +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/. */
 
 use api::{BuiltDisplayList, ColorF, DeviceIntPoint, DeviceIntRect, DevicePixelScale};
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FontRenderMode};
-use api::{LayerRect, LayerSize, PipelineId, PremultipliedColorF, WorldPoint};
+use api::{LayerRect, LayerSize, PipelineId, WorldPoint};
 use clip::{ClipChain, ClipStore};
 use clip_scroll_node::{ClipScrollNode};
 use clip_scroll_tree::{ClipScrollNodeIndex, ClipScrollTree};
 use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::GpuCache;
 use gpu_types::{ClipChainRectIndex, ClipScrollNodeData};
 use hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap};
 use prim_store::{CachedGradient, PrimitiveIndex, PrimitiveRun, PrimitiveStore};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_backend::FrameId;
-use render_task::{ClearMode, RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
+use render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
 use resource_cache::{ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
 use std::{mem, f32};
 use std::sync::Arc;
-use tiling::{Frame, RenderPass, RenderPassKind, RenderTargetContext, RenderTargetKind};
-use tiling::ScrollbarPrimitive;
+use tiling::{Frame, RenderPass, RenderPassKind, RenderTargetContext};
+use tiling::{ScrollbarPrimitive, SpecialRenderPasses};
 use util::{self, MaxRect, WorldToLayerFastTransform};
 
 #[derive(Clone, Copy)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameBuilderConfig {
     pub enable_scrollbars: bool,
     pub default_font_render_mode: FontRenderMode,
@@ -60,16 +60,17 @@ pub struct FrameBuildingContext<'a> {
 pub struct FrameBuildingState<'a> {
     pub render_tasks: &'a mut RenderTaskTree,
     pub profile_counters: &'a mut FrameProfileCounters,
     pub clip_store: &'a mut ClipStore,
     pub local_clip_rects: &'a mut Vec<LayerRect>,
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub cached_gradients: &'a mut [CachedGradient],
+    pub special_render_passes: &'a mut SpecialRenderPasses,
 }
 
 pub struct PictureContext<'a> {
     pub pipeline_id: PipelineId,
     pub prim_runs: Vec<PrimitiveRun>,
     pub original_reference_frame_index: Option<ClipScrollNodeIndex>,
     pub display_list: &'a BuiltDisplayList,
     pub inv_world_transform: Option<WorldToLayerFastTransform>,
@@ -152,16 +153,17 @@ impl FrameBuilder {
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(
         &mut self,
         clip_scroll_tree: &ClipScrollTree,
         pipelines: &FastHashMap<PipelineId, Arc<ScenePipeline>>,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
+        special_render_passes: &mut SpecialRenderPasses,
         profile_counters: &mut FrameProfileCounters,
         device_pixel_scale: DevicePixelScale,
         scene_properties: &SceneProperties,
         local_clip_rects: &mut Vec<LayerRect>,
         node_data: &[ClipScrollNodeData],
     ) -> Option<RenderTaskId> {
         profile_scope!("cull");
 
@@ -189,16 +191,17 @@ impl FrameBuilder {
 
         let mut frame_state = FrameBuildingState {
             render_tasks,
             profile_counters,
             clip_store: &mut self.clip_store,
             local_clip_rects,
             resource_cache,
             gpu_cache,
+            special_render_passes,
             cached_gradients: &mut self.cached_gradients,
         };
 
         let pic_context = PictureContext {
             pipeline_id: root_clip_scroll_node.pipeline_id,
             prim_runs: mem::replace(&mut self.prim_store.pictures[0].runs, Vec::new()),
             original_reference_frame_index: None,
             display_list,
@@ -218,20 +221,17 @@ impl FrameBuilder {
         );
 
         let pic = &mut self.prim_store.pictures[0];
         pic.runs = pic_context.prim_runs;
 
         let root_render_task = RenderTask::new_picture(
             RenderTaskLocation::Fixed(frame_context.screen_rect),
             PrimitiveIndex(0),
-            RenderTargetKind::Color,
             DeviceIntPoint::zero(),
-            PremultipliedColorF::TRANSPARENT,
-            ClearMode::Transparent,
             pic_state.tasks,
         );
 
         let render_task_id = frame_state.render_tasks.add(root_render_task);
         pic.surface = Some(render_task_id);
         Some(render_task_id)
     }
 
@@ -308,69 +308,79 @@ impl FrameBuilder {
             &mut node_data,
             scene_properties,
         );
 
         self.update_scroll_bars(clip_scroll_tree, gpu_cache);
 
         let mut render_tasks = RenderTaskTree::new(frame_id);
 
+        let screen_size = self.screen_rect.size.to_i32();
+        let mut special_render_passes = SpecialRenderPasses::new(&screen_size);
+
         let main_render_task_id = self.build_layer_screen_rects_and_cull_layers(
             clip_scroll_tree,
             pipelines,
             resource_cache,
             gpu_cache,
             &mut render_tasks,
+            &mut special_render_passes,
             &mut profile_counters,
             device_pixel_scale,
             scene_properties,
             &mut clip_chain_local_clip_rects,
             &node_data,
         );
 
-        let mut passes = Vec::new();
-        resource_cache.block_until_all_resources_added(gpu_cache, texture_cache_profile);
+        resource_cache.block_until_all_resources_added(gpu_cache,
+                                                       &mut render_tasks,
+                                                       texture_cache_profile);
+
+        let mut passes = vec![
+            special_render_passes.alpha_glyph_pass,
+            special_render_passes.color_glyph_pass,
+        ];
 
         if let Some(main_render_task_id) = main_render_task_id {
             let mut required_pass_count = 0;
             render_tasks.max_depth(main_render_task_id, 0, &mut required_pass_count);
             assert_ne!(required_pass_count, 0);
 
             // Do the allocations now, assigning each tile's tasks to a render
             // pass and target as required.
             for _ in 0 .. required_pass_count - 1 {
-                passes.push(RenderPass::new_off_screen(self.screen_rect.size.to_i32()));
+                passes.push(RenderPass::new_off_screen(screen_size));
             }
-            passes.push(RenderPass::new_main_framebuffer(self.screen_rect.size.to_i32()));
+            passes.push(RenderPass::new_main_framebuffer(screen_size));
 
             render_tasks.assign_to_passes(
                 main_render_task_id,
                 required_pass_count - 1,
-                &mut passes,
+                &mut passes[2..],
             );
         }
 
         let mut deferred_resolves = vec![];
         let mut has_texture_cache_tasks = false;
         let use_dual_source_blending = self.config.dual_source_blending_is_enabled &&
                                        self.config.dual_source_blending_is_supported;
 
         for pass in &mut passes {
-            let ctx = RenderTargetContext {
+            let mut ctx = RenderTargetContext {
                 device_pixel_scale,
                 prim_store: &self.prim_store,
                 resource_cache,
                 clip_scroll_tree,
                 use_dual_source_blending,
                 node_data: &node_data,
                 cached_gradients: &self.cached_gradients,
             };
 
             pass.build(
-                &ctx,
+                &mut ctx,
                 gpu_cache,
                 &mut render_tasks,
                 &mut deferred_resolves,
                 &self.clip_store,
             );
 
             if let RenderPassKind::OffScreen { ref texture_cache, .. } = pass.kind {
                 has_texture_cache_tasks |= !texture_cache.is_empty();
--- a/gfx/webrender/src/glyph_cache.rs
+++ b/gfx/webrender/src/glyph_cache.rs
@@ -1,37 +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/. */
 
+#[cfg(feature = "pathfinder")]
+use api::DeviceIntPoint;
 use api::GlyphKey;
 use glyph_rasterizer::{FontInstance, GlyphFormat};
 use internal_types::FastHashMap;
+use render_task::RenderTaskCache;
+#[cfg(feature = "pathfinder")]
+use render_task::RenderTaskCacheKey;
 use resource_cache::ResourceClassCache;
-use texture_cache::{TextureCache, TextureCacheHandle, EvictionNotice};
+use std::sync::Arc;
+use texture_cache::{EvictionNotice, TextureCache};
+#[cfg(not(feature = "pathfinder"))]
+use texture_cache::TextureCacheHandle;
 
+#[cfg(feature = "pathfinder")]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Clone, Debug)]
+pub struct CachedGlyphInfo {
+    pub render_task_cache_key: RenderTaskCacheKey,
+    pub format: GlyphFormat,
+    pub origin: DeviceIntPoint,
+}
+
+#[cfg(not(feature = "pathfinder"))]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CachedGlyphInfo {
     pub texture_cache_handle: TextureCacheHandle,
     pub format: GlyphFormat,
 }
 
 #[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 {
+    #[cfg(feature = "pathfinder")]
+    fn is_allocated(&self, texture_cache: &TextureCache, render_task_cache: &RenderTaskCache)
+                    -> bool {
+        match *self {
+            GlyphCacheEntry::Cached(ref glyph) => {
+                let render_task_cache_key = &glyph.render_task_cache_key;
+                render_task_cache.cache_item_is_allocated_for_render_task(texture_cache,
+                                                                          &render_task_cache_key)
+            }
+            GlyphCacheEntry::Pending => true,
+            // If the cache only has blank glyphs left, just get rid of it.
+            GlyphCacheEntry::Blank => false,
+        }
+    }
+
+    #[cfg(not(feature = "pathfinder"))]
+    fn is_allocated(&self, texture_cache: &TextureCache, _: &RenderTaskCache) -> bool {
+        match *self {
+            GlyphCacheEntry::Cached(ref glyph) => {
+                texture_cache.is_allocated(&glyph.texture_cache_handle)
+            }
+            GlyphCacheEntry::Pending => true,
+            // If the cache only has blank glyphs left, just get rid of it.
+            GlyphCacheEntry::Blank => false,
+        }
+    }
+}
+
+#[allow(dead_code)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Clone)]
+pub enum CachedGlyphData {
+    Memory(Arc<Vec<u8>>),
+    Gpu,
+}
+
 pub type GlyphKeyCache = ResourceClassCache<GlyphKey, GlyphCacheEntry, EvictionNotice>;
 
 impl GlyphKeyCache {
     pub fn eviction_notice(&self) -> &EvictionNotice {
         &self.user_data
     }
 }
 
@@ -81,38 +139,36 @@ impl GlyphCache {
         for key in caches_to_destroy {
             let mut cache = self.glyph_key_caches.remove(&key).unwrap();
             cache.clear();
         }
     }
 
     // Clear out evicted entries from glyph key caches and, if possible,
     // also remove entirely any subsequently empty glyph key caches.
-    fn clear_evicted(&mut self, texture_cache: &TextureCache) {
+    fn clear_evicted(&mut self,
+                     texture_cache: &TextureCache,
+                     render_task_cache: &RenderTaskCache) {
         self.glyph_key_caches.retain(|_, cache| {
             // Scan for any glyph key caches that have evictions.
             if cache.eviction_notice().check() {
                 // If there are evictions, filter out any glyphs evicted from the
                 // texture cache from the glyph key cache.
                 let mut keep_cache = false;
                 cache.retain(|_, entry| {
-                    let keep_glyph = match *entry {
-                        GlyphCacheEntry::Cached(ref glyph) =>
-                            texture_cache.is_allocated(&glyph.texture_cache_handle),
-                        GlyphCacheEntry::Pending => true,
-                        // If the cache only has blank glyphs left, just get rid of it.
-                        GlyphCacheEntry::Blank => false,
-                    };
+                    let keep_glyph = entry.is_allocated(texture_cache, render_task_cache);
                     keep_cache |= keep_glyph;
                     keep_glyph
                 });
                 // Only keep the glyph key cache if it still has valid glyphs.
                 keep_cache
             } else {
                 true
             }
         });
     }
 
-    pub fn begin_frame(&mut self, texture_cache: &TextureCache) {
-        self.clear_evicted(texture_cache);
+    pub fn begin_frame(&mut self,
+                       texture_cache: &TextureCache,
+                       render_task_cache: &RenderTaskCache) {
+        self.clear_evicted(texture_cache, render_task_cache);
     }
 }
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -2,36 +2,85 @@
  * 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(test)]
 use api::{IdNamespace, LayoutPoint};
 use api::{ColorF, ColorU};
 use api::{FontInstanceFlags, FontInstancePlatformOptions};
 use api::{FontKey, FontRenderMode, FontTemplate, FontVariation};
-use api::{GlyphDimensions, GlyphKey, SubpixelDirection};
-use api::{ImageData, ImageDescriptor, ImageFormat, LayerToWorldTransform};
+use api::{GlyphDimensions, GlyphKey, LayerToWorldTransform, SubpixelDirection};
+#[cfg(any(test, feature = "pathfinder"))]
+use api::DeviceIntSize;
+#[cfg(not(feature = "pathfinder"))]
+use api::{ImageData, ImageDescriptor, ImageFormat};
 use app_units::Au;
+#[cfg(not(feature = "pathfinder"))]
 use device::TextureFilter;
-use glyph_cache::{GlyphCache, GlyphCacheEntry, CachedGlyphInfo};
+#[cfg(feature = "pathfinder")]
+use euclid::{TypedPoint2D, TypedSize2D, TypedVector2D};
+use glyph_cache::{CachedGlyphInfo, GlyphCache, GlyphCacheEntry};
 use gpu_cache::GpuCache;
 use internal_types::ResourceCacheError;
+#[cfg(feature = "pathfinder")]
+use pathfinder_font_renderer;
+#[cfg(feature = "pathfinder")]
+use pathfinder_partitioner::mesh::Mesh as PathfinderMesh;
+#[cfg(feature = "pathfinder")]
+use pathfinder_path_utils::cubic_to_quadratic::CubicToQuadraticTransformer;
 use platform::font::FontContext;
 use profiler::TextureCacheProfileCounters;
 use rayon::ThreadPool;
+#[cfg(not(feature = "pathfinder"))]
 use rayon::prelude::*;
+#[cfg(test)]
+use render_backend::FrameId;
+use render_task::{RenderTaskCache, RenderTaskTree};
+#[cfg(feature = "pathfinder")]
+use render_task::{RenderTask, RenderTaskCacheKey, RenderTaskCacheKeyKind};
+#[cfg(feature = "pathfinder")]
+use render_task::{RenderTaskId, RenderTaskLocation};
+#[cfg(feature = "pathfinder")]
+use resource_cache::CacheItem;
 use std::cmp;
 use std::collections::hash_map::Entry;
+use std::f32;
 use std::hash::{Hash, Hasher};
 use std::mem;
 use std::sync::{Arc, Mutex, MutexGuard};
 use std::sync::mpsc::{channel, Receiver, Sender};
-use texture_cache::{TextureCache, TextureCacheHandle};
+use texture_cache::TextureCache;
+#[cfg(not(feature = "pathfinder"))]
+use texture_cache::TextureCacheHandle;
 #[cfg(test)]
 use thread_profiler::register_thread_with_profiler;
+#[cfg(feature = "pathfinder")]
+use tiling::RenderTargetKind;
+use tiling::SpecialRenderPasses;
+#[cfg(feature = "pathfinder")]
+use webrender_api::{DeviceIntPoint, DevicePixel};
+
+/// Should match macOS 10.13 High Sierra.
+///
+/// We multiply by sqrt(2) to compensate for the fact that dilation amounts are relative to the
+/// pixel square on macOS and relative to the vertex normal in Pathfinder.
+#[cfg(feature = "pathfinder")]
+const STEM_DARKENING_FACTOR_X: f32 = 0.0121 * f32::consts::SQRT_2;
+#[cfg(feature = "pathfinder")]
+const STEM_DARKENING_FACTOR_Y: f32 = 0.0121 * 1.25 * f32::consts::SQRT_2;
+
+/// Likewise, should match macOS 10.13 High Sierra.
+#[cfg(feature = "pathfinder")]
+const MAX_STEM_DARKENING_AMOUNT: f32 = 0.3 * f32::consts::SQRT_2;
+
+#[cfg(feature = "pathfinder")]
+const CUBIC_TO_QUADRATIC_APPROX_TOLERANCE: f32 = 0.01;
+
+#[cfg(feature = "pathfinder")]
+type PathfinderFontContext = pathfinder_font_renderer::FontContext<FontKey>;
 
 #[derive(Clone, Copy, Debug, 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,
@@ -74,40 +123,44 @@ impl FontTransform {
         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,
         )
     }
 
+    #[cfg(not(feature = "pathfinder"))]
     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
     }
 
+    #[cfg(not(feature = "pathfinder"))]
     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
         }
     }
 
+    #[cfg(not(feature = "pathfinder"))]
     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,
         )
     }
 
+    #[cfg(not(feature = "pathfinder"))]
     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, skew_factor: f32) -> Self {
         FontTransform::new(
             self.scale_x,
             self.skew_x - self.scale_x * skew_factor,
@@ -249,29 +302,37 @@ pub struct RasterizedGlyph {
     pub height: u32,
     pub scale: f32,
     pub format: GlyphFormat,
     pub bytes: Vec<u8>,
 }
 
 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 muteces.
+    // The goal is that there should be no noticeable contention on the mutexes.
     worker_contexts: Vec<Mutex<FontContext>>,
 
-    // This worker should be accessed by threads that don't belong to thre thread pool
+    // This worker should be accessed by threads that don't belong to the thread pool
     // (in theory that's only the render backend thread so no contention expected either).
     shared_context: Mutex<FontContext>,
 
+    #[cfg(feature = "pathfinder")]
+    pathfinder_context: Box<Mutex<PathfinderFontContext>>,
+    #[cfg(not(feature = "pathfinder"))]
+    #[allow(dead_code)]
+    pathfinder_context: (),
+
     // Stored here as a convenience to get the current thread index.
+    #[allow(dead_code)]
     workers: Arc<ThreadPool>,
 }
 
 impl FontContexts {
     /// Get access to the font context associated to the current thread.
+    #[cfg(not(feature = "pathfinder"))]
     pub fn lock_current_context(&self) -> MutexGuard<FontContext> {
         let id = self.current_worker_id();
         self.lock_context(id)
     }
 
     /// Get access to any particular font context.
     ///
     /// The id is ```Some(i)``` where i is an index between 0 and num_worker_contexts
@@ -284,73 +345,90 @@ impl FontContexts {
         }
     }
 
     /// Get access to the font context usable outside of the thread pool.
     pub fn lock_shared_context(&self) -> MutexGuard<FontContext> {
         self.shared_context.lock().unwrap()
     }
 
+    #[cfg(feature = "pathfinder")]
+    pub fn lock_pathfinder_context(&self) -> MutexGuard<PathfinderFontContext> {
+        self.pathfinder_context.lock().unwrap()
+    }
+
     // number of contexts associated to workers
     pub fn num_worker_contexts(&self) -> usize {
         self.worker_contexts.len()
     }
 
+    #[cfg(not(feature = "pathfinder"))]
     fn current_worker_id(&self) -> Option<usize> {
         self.workers.current_thread_index()
     }
 }
 
 pub struct GlyphRasterizer {
+    #[allow(dead_code)]
     workers: Arc<ThreadPool>,
     font_contexts: Arc<FontContexts>,
 
     // 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<GlyphRasterJobs>,
+    #[allow(dead_code)]
     glyph_tx: Sender<GlyphRasterJobs>,
 
     // 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<FontKey>,
+
+    #[allow(dead_code)]
+    next_gpu_glyph_cache_key: GpuGlyphCacheKey,
 }
 
 impl GlyphRasterizer {
     pub fn new(workers: Arc<ThreadPool>) -> Result<Self, ResourceCacheError> {
         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 pathfinder_context = create_pathfinder_font_context()?;
+
         Ok(GlyphRasterizer {
             font_contexts: Arc::new(FontContexts {
                 worker_contexts: contexts,
                 shared_context: Mutex::new(shared_context),
+                pathfinder_context: pathfinder_context,
                 workers: Arc::clone(&workers),
             }),
             pending_glyphs: 0,
             glyph_rx,
             glyph_tx,
             workers,
             fonts_to_remove: Vec::new(),
+            next_gpu_glyph_cache_key: GpuGlyphCacheKey(0),
         })
     }
 
     pub fn add_font(&mut self, font_key: FontKey, template: FontTemplate) {
         let font_contexts = Arc::clone(&self.font_contexts);
         // It's important to synchronously add the font for the shared context because
         // we use it to check that fonts have been properly added when requesting glyphs.
         font_contexts
@@ -363,33 +441,192 @@ impl GlyphRasterizer {
         // before rendering a glyph.
         // We can also move this into a worker to free up some cycles in the calling (render backend)
         // thread.
         for i in 0 .. font_contexts.num_worker_contexts() {
             font_contexts
                 .lock_context(Some(i))
                 .add_font(&font_key, &template);
         }
+
+        self.add_font_to_pathfinder(&font_key, &template);
     }
 
+    #[cfg(feature = "pathfinder")]
+    fn add_font_to_pathfinder(&mut self, font_key: &FontKey, template: &FontTemplate) {
+        let font_contexts = Arc::clone(&self.font_contexts);
+        font_contexts.lock_pathfinder_context().add_font(&font_key, &template);
+    }
+
+    #[cfg(not(feature = "pathfinder"))]
+    fn add_font_to_pathfinder(&mut self, _: &FontKey, _: &FontTemplate) {}
+
     pub fn delete_font(&mut self, font_key: FontKey) {
         self.fonts_to_remove.push(font_key);
     }
 
     pub fn prepare_font(&self, font: &mut FontInstance) {
         FontContext::prepare_font(font);
     }
 
+    #[cfg(feature = "pathfinder")]
+    pub fn get_cache_item_for_glyph(&self,
+                                    glyph_key: &GlyphKey,
+                                    font: &FontInstance,
+                                    glyph_cache: &GlyphCache,
+                                    texture_cache: &TextureCache,
+                                    render_task_cache: &RenderTaskCache)
+                                    -> Option<(CacheItem, GlyphFormat)> {
+        let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font(font);
+        let render_task_cache_key = match *glyph_key_cache.get(glyph_key) {
+            GlyphCacheEntry::Cached(ref cached_glyph) => {
+                (*cached_glyph).render_task_cache_key.clone()
+            }
+            GlyphCacheEntry::Blank => return None,
+            GlyphCacheEntry::Pending => {
+                panic!("GlyphRasterizer::get_cache_item_for_glyph(): Glyph should have been \
+                        cached by now!")
+            }
+        };
+        let cache_item = render_task_cache.get_cache_item_for_render_task(texture_cache,
+                                                                          &render_task_cache_key);
+        Some((cache_item, font.get_glyph_format()))
+    }
+
+    #[cfg(feature = "pathfinder")]
+    fn request_glyph_from_pathfinder_if_necessary(&mut self,
+                                                  glyph_key: &GlyphKey,
+                                                  font: &FontInstance,
+                                                  cached_glyph_info: CachedGlyphInfo,
+                                                  texture_cache: &mut TextureCache,
+                                                  gpu_cache: &mut GpuCache,
+                                                  render_task_cache: &mut RenderTaskCache,
+                                                  render_task_tree: &mut RenderTaskTree,
+                                                  render_passes: &mut SpecialRenderPasses)
+                                                  -> Result<(CacheItem, GlyphFormat), ()> {
+        let mut pathfinder_font_context = self.font_contexts.lock_pathfinder_context();
+        let render_task_cache_key = cached_glyph_info.render_task_cache_key;
+        let (glyph_origin, glyph_size) = (cached_glyph_info.origin, render_task_cache_key.size);
+        let user_data = [glyph_origin.x as f32, (glyph_origin.y - glyph_size.height) as f32, 1.0];
+        let cache_item = try!(render_task_cache.request_render_task(render_task_cache_key,
+                                                                    texture_cache,
+                                                                    gpu_cache,
+                                                                    render_task_tree,
+                                                                    Some(user_data),
+                                                                    |render_tasks| {
+            // TODO(pcwalton): Non-subpixel font render mode.
+            request_render_task_from_pathfinder(glyph_key,
+                                                font,
+                                                &glyph_origin,
+                                                &glyph_size,
+                                                &mut *pathfinder_font_context,
+                                                font.render_mode,
+                                                render_tasks,
+                                                render_passes)
+        }));
+        Ok((cache_item, font.get_glyph_format()))
+    }
+
+    #[cfg(feature = "pathfinder")]
     pub fn request_glyphs(
         &mut self,
         glyph_cache: &mut GlyphCache,
         font: FontInstance,
         glyph_keys: &[GlyphKey],
         texture_cache: &mut TextureCache,
         gpu_cache: &mut GpuCache,
+        render_task_cache: &mut RenderTaskCache,
+        render_task_tree: &mut RenderTaskTree,
+        render_passes: &mut SpecialRenderPasses,
+    ) {
+        debug_assert!(self.font_contexts.lock_shared_context().has_font(&font.font_key));
+
+        let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(font.clone());
+
+        // select glyphs that have not been requested yet.
+        for glyph_key in glyph_keys {
+            let mut cached_glyph_info = None;
+            match glyph_key_cache.entry(glyph_key.clone()) {
+                Entry::Occupied(mut entry) => {
+                    let value = entry.into_mut();
+                    match *value {
+                        GlyphCacheEntry::Cached(ref glyph_info) => {
+                            cached_glyph_info = Some(glyph_info.clone())
+                        }
+                        GlyphCacheEntry::Blank | GlyphCacheEntry::Pending => {}
+                    }
+                }
+                Entry::Vacant(_) => {}
+            }
+
+            let cached_glyph_info = match cached_glyph_info {
+                Some(cached_glyph_info) => cached_glyph_info,
+                None => {
+                    let mut pathfinder_font_context = self.font_contexts.lock_pathfinder_context();
+
+                    let pathfinder_font_instance = pathfinder_font_renderer::FontInstance {
+                        font_key: font.font_key.clone(),
+                        size: font.size,
+                    };
+
+                    let pathfinder_subpixel_offset =
+                        pathfinder_font_renderer::SubpixelOffset(glyph_key.subpixel_offset as u8);
+                    let pathfinder_glyph_key =
+                        pathfinder_font_renderer::GlyphKey::new(glyph_key.index,
+                                                                pathfinder_subpixel_offset);
+                    let glyph_dimensions =
+                        match pathfinder_font_context.glyph_dimensions(&pathfinder_font_instance,
+                                                                       &pathfinder_glyph_key,
+                                                                       false) {
+                            Ok(glyph_dimensions) => glyph_dimensions,
+                            Err(_) => continue,
+                        };
+
+                    let cached_glyph_info = CachedGlyphInfo {
+                        render_task_cache_key: RenderTaskCacheKey {
+                            size: TypedSize2D::from_untyped(&glyph_dimensions.size.to_i32()),
+                            kind: RenderTaskCacheKeyKind::Glyph(self.next_gpu_glyph_cache_key),
+                        },
+                        format: font.get_glyph_format(),
+                        origin: DeviceIntPoint::new(glyph_dimensions.origin.x as i32,
+                                                    -glyph_dimensions.origin.y as i32),
+                    };
+                    self.next_gpu_glyph_cache_key.0 += 1;
+                    cached_glyph_info
+                }
+            };
+
+            let cache_entry =
+                match self.request_glyph_from_pathfinder_if_necessary(glyph_key,
+                                                                      &font,
+                                                                      cached_glyph_info.clone(),
+                                                                      texture_cache,
+                                                                      gpu_cache,
+                                                                      render_task_cache,
+                                                                      render_task_tree,
+                                                                      render_passes) {
+                    Ok(_) => GlyphCacheEntry::Cached(cached_glyph_info),
+                    Err(_) => GlyphCacheEntry::Blank,
+                };
+
+            glyph_key_cache.insert(glyph_key.clone(), cache_entry);
+        }
+    }
+
+    #[cfg(not(feature = "pathfinder"))]
+    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 RenderTaskTree,
+        _: &mut SpecialRenderPasses,
     ) {
         assert!(
             self.font_contexts
                 .lock_shared_context()
                 .has_font(&font.font_key)
         );
         let mut new_glyphs = Vec::new();
 
@@ -403,56 +640,63 @@ impl GlyphRasterizer {
                     match *value {
                         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;
                             }
                         }
                         // Otherwise, skip the entry if it is blank or pending.
-                        GlyphCacheEntry::Blank |
-                        GlyphCacheEntry::Pending => continue,
+                        GlyphCacheEntry::Blank | GlyphCacheEntry::Pending => 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.
                     *value = GlyphCacheEntry::Pending;
+                    new_glyphs.push((*key).clone());
                 }
                 Entry::Vacant(entry) => {
                     // This is the first time we've seen the glyph, so mark it as pending.
                     entry.insert(GlyphCacheEntry::Pending);
+                    new_glyphs.push((*key).clone());
                 }
             }
-
-            new_glyphs.push(key.clone());
         }
 
         if new_glyphs.is_empty() {
             return;
         }
 
         self.pending_glyphs += 1;
+
+        self.request_glyphs_from_backend(font, new_glyphs);
+    }
+
+    #[cfg(not(feature = "pathfinder"))]
+    fn request_glyphs_from_backend(&mut self, font: FontInstance, glyphs: Vec<GlyphKey>) {
         let font_contexts = Arc::clone(&self.font_contexts);
         let glyph_tx = self.glyph_tx.clone();
+
         // 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.
         self.workers.spawn(move || {
-            let jobs = new_glyphs
+            let jobs = glyphs
                 .par_iter()
                 .map(|key: &GlyphKey| {
                     profile_scope!("glyph-raster");
                     let mut context = font_contexts.lock_current_context();
                     let job = GlyphRasterJob {
                         key: key.clone(),
                         result: context.rasterize_glyph(&font, key),
                     };
 
                     // Sanity check.
-                    if let Some(ref glyph) = job.result {
+                    if let GlyphRasterResult::Bitmap(ref glyph) = job.result {
                         let bpp = 4; // We always render glyphs in 32 bits RGBA format.
                         assert_eq!(
                             glyph.bytes.len(),
                             bpp * (glyph.width * glyph.height) as usize
                         );
                     }
 
                     job
@@ -474,47 +718,68 @@ impl GlyphRasterizer {
     }
 
     pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
         self.font_contexts
             .lock_shared_context()
             .get_glyph_index(font_key, ch)
     }
 
+    #[cfg(feature = "pathfinder")]
+    pub fn resolve_glyphs(
+        &mut self,
+        _: &mut GlyphCache,
+        _: &mut TextureCache,
+        _: &mut GpuCache,
+        _: &mut RenderTaskCache,
+        _: &mut RenderTaskTree,
+        _: &mut TextureCacheProfileCounters,
+    ) {
+        self.remove_dead_fonts();
+    }
+
+    #[cfg(not(feature = "pathfinder"))]
     pub fn resolve_glyphs(
         &mut self,
         glyph_cache: &mut GlyphCache,
         texture_cache: &mut TextureCache,
         gpu_cache: &mut GpuCache,
-        _texture_cache_profile: &mut TextureCacheProfileCounters,
+        _: &mut RenderTaskCache,
+        _: &mut RenderTaskTree,
+        _: &mut TextureCacheProfileCounters,
     ) {
-        // Pull rasterized glyphs from the queue and Update the caches.
+        // 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 } = 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 inaccuacry
+            // 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 = result.map_or(GlyphCacheEntry::Blank, |glyph| {
-                    if glyph.width > 0 && glyph.height > 0 {
+                let glyph_info = match result {
+                    GlyphRasterResult::LoadFailed => GlyphCacheEntry::Blank,
+                    GlyphRasterResult::Bitmap(ref glyph) if glyph.width == 0 ||
+                                                            glyph.height == 0 => {
+                        GlyphCacheEntry::Blank
+                    }
+                    GlyphRasterResult::Bitmap(glyph) => {
                         assert_eq!((glyph.left.fract(), glyph.top.fract()), (0.0, 0.0));
                         let mut texture_cache_handle = TextureCacheHandle::new();
                         texture_cache.request(&mut texture_cache_handle, gpu_cache);
                         texture_cache.update(
                             &mut texture_cache_handle,
                             ImageDescriptor {
                                 width: glyph.width,
                                 height: glyph.height,
@@ -530,64 +795,86 @@ impl GlyphRasterizer {
                             None,
                             gpu_cache,
                             Some(glyph_key_cache.eviction_notice()),
                         );
                         GlyphCacheEntry::Cached(CachedGlyphInfo {
                             texture_cache_handle,
                             format: glyph.format,
                         })
-                    } else {
-                        GlyphCacheEntry::Blank
                     }
-                });
+                };
                 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.
-        if !self.fonts_to_remove.is_empty() {
-            let font_contexts = Arc::clone(&self.font_contexts);
-            let fonts_to_remove = mem::replace(&mut self.fonts_to_remove, Vec::new());
-            self.workers.spawn(move || {
+        self.remove_dead_fonts();
+    }
+
+    fn remove_dead_fonts(&mut self) {
+        if self.fonts_to_remove.is_empty() {
+            return
+        }
+
+        let font_contexts = Arc::clone(&self.font_contexts);
+        let fonts_to_remove = mem::replace(&mut self.fonts_to_remove, Vec::new());
+        self.workers.spawn(move || {
+            for font_key in &fonts_to_remove {
+                font_contexts.lock_shared_context().delete_font(font_key);
+            }
+            for i in 0 .. font_contexts.num_worker_contexts() {
+                let mut context = font_contexts.lock_context(Some(i));
                 for font_key in &fonts_to_remove {
-                    font_contexts.lock_shared_context().delete_font(font_key);
+                    context.delete_font(font_key);
                 }
-                for i in 0 .. font_contexts.num_worker_contexts() {
-                    let mut context = font_contexts.lock_context(Some(i));
-                    for font_key in &fonts_to_remove {
-                        context.delete_font(font_key);
-                    }
-                }
-            });
-        }
+            }
+        });
     }
 
     #[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();
     }
 }
 
-impl FontContext {
+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());
             }
         }
     }
 }
 
+#[cfg(feature = "pathfinder")]
+impl AddFont for PathfinderFontContext {
+    fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate) {
+        match template {
+            &FontTemplate::Raw(ref bytes, index) => {
+                drop(self.add_font_from_memory(&font_key, bytes.clone(), index));
+            }
+            &FontTemplate::Native(ref native_font_handle) => {
+                drop(self.add_native_font(&font_key, (*native_font_handle).clone().0));
+            }
+        }
+    }
+}
+
 #[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct GlyphRequest {
     pub key: GlyphKey,
     pub font: FontInstance,
 }
 
@@ -595,21 +882,51 @@ impl GlyphRequest {
     pub fn new(font: &FontInstance, key: &GlyphKey) -> Self {
         GlyphRequest {
             key: key.clone(),
             font: font.clone(),
         }
     }
 }
 
+#[allow(dead_code)]
 struct GlyphRasterJob {
     key: GlyphKey,
-    result: Option<RasterizedGlyph>,
+    result: GlyphRasterResult,
+}
+
+#[allow(dead_code)]
+pub enum GlyphRasterResult {
+    LoadFailed,
+    Bitmap(RasterizedGlyph),
 }
 
+#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct GpuGlyphCacheKey(pub u32);
+
+#[cfg(feature = "pathfinder")]
+fn create_pathfinder_font_context() -> Result<Box<Mutex<PathfinderFontContext>>,
+                                              ResourceCacheError> {
+    match PathfinderFontContext::new() {
+        Ok(context) => Ok(Box::new(Mutex::new(context))),
+        Err(_) => {
+            let msg = "Failed to create the Pathfinder font context!".to_owned();
+            Err(ResourceCacheError::new(msg))
+        }
+    }
+}
+
+#[cfg(not(feature = "pathfinder"))]
+fn create_pathfinder_font_context() -> Result<(), ResourceCacheError> {
+    Ok(())
+}
+
+#[allow(dead_code)]
 struct GlyphRasterJobs {
     font: FontInstance,
     jobs: Vec<GlyphRasterJob>,
 }
 
 #[test]
 fn rasterize_200_glyphs() {
     // This test loads a font from disc, the renders 4 requests containing
@@ -625,16 +942,19 @@ fn rasterize_200_glyphs() {
             register_thread_with_profiler(format!("WRWorker#{}", idx));
         })
         .build();
     let workers = Arc::new(worker.unwrap());
     let mut glyph_rasterizer = GlyphRasterizer::new(workers).unwrap();
     let mut glyph_cache = GlyphCache::new();
     let mut gpu_cache = GpuCache::new();
     let mut texture_cache = TextureCache::new(2048);
+    let mut render_task_cache = RenderTaskCache::new();
+    let mut render_task_tree = RenderTaskTree::new(FrameId(0));
+    let mut special_render_passes = SpecialRenderPasses::new(&DeviceIntSize::new(1366, 768));
 
     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");
 
@@ -665,20 +985,85 @@ fn rasterize_200_glyphs() {
 
     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,
+            &mut special_render_passes,
         );
     }
 
     glyph_rasterizer.delete_font(font_key);
 
     glyph_rasterizer.resolve_glyphs(
         &mut glyph_cache,
         &mut TextureCache::new(4096),
         &mut gpu_cache,
+        &mut render_task_cache,
+        &mut render_task_tree,
         &mut TextureCacheProfileCounters::new(),
     );
 }
+
+#[cfg(feature = "pathfinder")]
+fn compute_embolden_amount(ppem: f32) -> TypedVector2D<f32, DevicePixel> {
+    TypedVector2D::new(f32::min(ppem * STEM_DARKENING_FACTOR_X, MAX_STEM_DARKENING_AMOUNT),
+                       f32::min(ppem * STEM_DARKENING_FACTOR_Y, MAX_STEM_DARKENING_AMOUNT))
+}
+
+#[cfg(feature = "pathfinder")]
+fn request_render_task_from_pathfinder(glyph_key: &GlyphKey,
+                                       font: &FontInstance,
+                                       glyph_origin: &DeviceIntPoint,
+                                       glyph_size: &DeviceIntSize,
+                                       font_context: &mut PathfinderFontContext,
+                                       render_mode: FontRenderMode,
+                                       render_tasks: &mut RenderTaskTree,
+                                       render_passes: &mut SpecialRenderPasses)
+                                       -> Result<(RenderTaskId, bool), ()> {
+    let pathfinder_font_instance = pathfinder_font_renderer::FontInstance {
+        font_key: font.font_key.clone(),
+        size: font.size,
+    };
+
+    let pathfinder_subpixel_offset =
+        pathfinder_font_renderer::SubpixelOffset(glyph_key.subpixel_offset as u8);
+    let glyph_subpixel_offset: f64 = glyph_key.subpixel_offset.into();
+    let pathfinder_glyph_key = pathfinder_font_renderer::GlyphKey::new(glyph_key.index,
+                                                                       pathfinder_subpixel_offset);
+
+    // TODO(pcwalton): Fall back to CPU rendering if Pathfinder fails to collect the outline.
+    let mut mesh = PathfinderMesh::new();
+    let outline = try!(font_context.glyph_outline(&pathfinder_font_instance,
+                                                  &pathfinder_glyph_key));
+    let tolerance = CUBIC_TO_QUADRATIC_APPROX_TOLERANCE;
+    mesh.push_stencil_segments(CubicToQuadraticTransformer::new(outline.iter(), tolerance));
+    mesh.push_stencil_normals(CubicToQuadraticTransformer::new(outline.iter(), tolerance));
+
+    // FIXME(pcwalton): Support vertical subpixel offsets.
+    // FIXME(pcwalton): Embolden amount should be 0 on macOS if "Use LCD font
+    // smoothing" is unchecked in System Preferences.
+
+    let subpixel_offset = TypedPoint2D::new(glyph_subpixel_offset as f32, 0.0);
+    let embolden_amount = compute_embolden_amount(font.size.to_f32_px());
+
+    let location = RenderTaskLocation::Dynamic(None, *glyph_size);
+    let glyph_render_task = RenderTask::new_glyph(location,
+                                                  mesh,
+                                                  &glyph_origin,
+                                                  &subpixel_offset,
+                                                  font.render_mode,
+                                                  &embolden_amount);
+
+    let root_task_id = render_tasks.add(glyph_render_task);
+    let render_pass = match render_mode {
+        FontRenderMode::Mono | FontRenderMode::Alpha => &mut render_passes.alpha_glyph_pass,
+        FontRenderMode::Subpixel => &mut render_passes.color_glyph_pass,
+    };
+    render_pass.add_render_task(root_task_id, *glyph_size, RenderTargetKind::Color);
+
+    Ok((root_task_id, false))
+}
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/gpu_glyph_renderer.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/. */
+
+//! GPU glyph rasterization using Pathfinder.
+
+use api::{DeviceIntPoint, DeviceIntRect, DeviceUintSize, FontRenderMode};
+use api::{ImageFormat, TextureTarget};
+use debug_colors;
+use device::{Device, Texture, TextureFilter, VAO};
+use euclid::{Point2D, Size2D, Transform3D, TypedVector2D, Vector2D};
+use internal_types::RenderTargetInfo;
+use pathfinder_gfx_utils::ShelfBinPacker;
+use profiler::GpuProfileTag;
+use renderer::{self, ImageBufferKind, Renderer, RendererError, RendererStats};
+use renderer::{TextureSampler, VertexArrayKind};
+use shade::{LazilyCompiledShader, ShaderKind};
+use tiling::GlyphJob;
+
+// The area lookup table in uncompressed grayscale TGA format (TGA image format 3).
+static AREA_LUT_TGA_BYTES: &'static [u8] = include_bytes!("../res/area-lut.tga");
+
+const HORIZONTAL_BIN_PADDING: i32 = 3;
+
+const GPU_TAG_GLYPH_STENCIL: GpuProfileTag = GpuProfileTag {
+    label: "Glyph Stencil",
+    color: debug_colors::STEELBLUE,
+};
+const GPU_TAG_GLYPH_COVER: GpuProfileTag = GpuProfileTag {
+    label: "Glyph Cover",
+    color: debug_colors::LIGHTSTEELBLUE,
+};
+
+pub struct GpuGlyphRenderer {
+    pub area_lut_texture: Texture,
+    pub vector_stencil_vao: VAO,
+    pub vector_cover_vao: VAO,
+
+    // These are Pathfinder shaders, used for rendering vector graphics.
+    vector_stencil: LazilyCompiledShader,
+    vector_cover: LazilyCompiledShader,
+}
+
+impl GpuGlyphRenderer {
+    pub fn new(device: &mut Device, prim_vao: &VAO, precache_shaders: bool)
+               -> Result<GpuGlyphRenderer, RendererError> {
+        // Make sure the area LUT is uncompressed grayscale TGA, 8bpp.
+        debug_assert!(AREA_LUT_TGA_BYTES[2] == 3);
+        debug_assert!(AREA_LUT_TGA_BYTES[16] == 8);
+        let area_lut_width = (AREA_LUT_TGA_BYTES[12] as u32) |
+            ((AREA_LUT_TGA_BYTES[13] as u32) << 8);
+        let area_lut_height = (AREA_LUT_TGA_BYTES[14] as u32) |
+            ((AREA_LUT_TGA_BYTES[15] as u32) << 8);
+        let area_lut_pixels =
+            &AREA_LUT_TGA_BYTES[18..(18 + area_lut_width * area_lut_height) as usize];
+
+        let mut area_lut_texture = device.create_texture(TextureTarget::Default, ImageFormat::R8);
+        device.init_texture(&mut area_lut_texture,
+                            area_lut_width,
+                            area_lut_height,
+                            TextureFilter::Linear,
+                            None,
+                            1,
+                            Some(area_lut_pixels));
+
+        let vector_stencil_vao =
+            device.create_vao_with_new_instances(&renderer::desc::VECTOR_STENCIL, prim_vao);
+        let vector_cover_vao = device.create_vao_with_new_instances(&renderer::desc::VECTOR_COVER,
+                                                                    prim_vao);
+
+        // Load Pathfinder vector graphics shaders.
+        let vector_stencil = try!{
+            LazilyCompiledShader::new(ShaderKind::VectorStencil,
+                                      "pf_vector_stencil",
+                                      &[ImageBufferKind::Texture2D.get_feature_string()],
+                                      device,
+                                      precache_shaders)
+        };
+        let vector_cover = try!{
+            LazilyCompiledShader::new(ShaderKind::VectorCover,
+                                      "pf_vector_cover",
+                                      &[ImageBufferKind::Texture2D.get_feature_string()],
+                                      device,
+                                      precache_shaders)
+        };
+
+        Ok(GpuGlyphRenderer {
+            area_lut_texture,
+            vector_stencil_vao,
+            vector_cover_vao,
+            vector_stencil,
+            vector_cover,
+        })
+    }
+}
+
+impl Renderer {
+    /// Renders glyphs using the vector graphics shaders (Pathfinder).
+    pub fn stencil_glyphs(&mut self,
+                          glyphs: &[GlyphJob],
+                          projection: &Transform3D<f32>,
+                          target_size: &DeviceUintSize,
+                          stats: &mut RendererStats)
+                          -> Option<StenciledGlyphPage> {
+        if glyphs.is_empty() {
+            return None
+        }
+
+        let _timer = self.gpu_profile.start_timer(GPU_TAG_GLYPH_STENCIL);
+
+        // Initialize temporary framebuffer.
+        // FIXME(pcwalton): Cache this!
+        // FIXME(pcwalton): Use RF32, not RGBAF32!
+        let mut current_page = StenciledGlyphPage {
+            texture: self.device.create_texture(TextureTarget::Default, ImageFormat::RGBAF32),
+            glyphs: vec![],
+        };
+        self.device.init_texture::<f32>(&mut current_page.texture,
+                                        target_size.width,
+                                        target_size.height,
+                                        TextureFilter::Nearest,
+                                        Some(RenderTargetInfo {
+                                            has_depth: false,
+                                        }),
+                                        1,
+                                        None);
+
+        // Allocate all target rects.
+        let mut packer = ShelfBinPacker::new(&target_size.to_i32().to_untyped(),
+                                             &Vector2D::new(HORIZONTAL_BIN_PADDING, 0));
+        let mut glyph_indices: Vec<_> = (0..(glyphs.len())).collect();
+        glyph_indices.sort_by(|&a, &b| {
+            glyphs[b].target_rect.size.height.cmp(&glyphs[a].target_rect.size.height)
+        });
+        for &glyph_index in &glyph_indices {
+            let glyph = &glyphs[glyph_index];
+            let x_scale = x_scale_for_render_mode(glyph.render_mode);
+            let stencil_size = Size2D::new(glyph.target_rect.size.width * x_scale,
+                                           glyph.target_rect.size.height);
+            match packer.add(&stencil_size) {
+                Err(_) => return None,
+                Ok(origin) => {
+                    current_page.glyphs.push(VectorCoverInstanceAttrs {
+                        target_rect: glyph.target_rect,
+                        stencil_origin: DeviceIntPoint::from_untyped(&origin),
+                        subpixel: (glyph.render_mode == FontRenderMode::Subpixel) as u16,
+                    })
+                }
+            }
+        }
+
+        // Initialize path info.
+        // TODO(pcwalton): Cache this texture!
+        let mut path_info_texture = self.device.create_texture(TextureTarget::Default,
+                                                               ImageFormat::RGBAF32);
+
+        let mut path_info_texels = Vec::with_capacity(glyphs.len() * 12);
+        for (stenciled_glyph_index, &glyph_index) in glyph_indices.iter().enumerate() {
+            let glyph = &glyphs[glyph_index];
+            let stenciled_glyph = &current_page.glyphs[stenciled_glyph_index];
+            let x_scale = x_scale_for_render_mode(glyph.render_mode) as f32;
+            let glyph_origin = TypedVector2D::new(-glyph.origin.x as f32 * x_scale,
+                                                  -glyph.origin.y as f32);
+            let subpixel_offset = TypedVector2D::new(glyph.subpixel_offset.x * x_scale,
+                                                     glyph.subpixel_offset.y);
+            let rect = stenciled_glyph.stencil_rect()
+                                      .to_f32()
+                                      .translate(&glyph_origin)
+                                      .translate(&subpixel_offset);
+            path_info_texels.extend_from_slice(&[
+                x_scale, 0.0, 0.0, -1.0,
+                rect.origin.x, rect.max_y(), 0.0, 0.0,
+                rect.size.width, rect.size.height,
+                glyph.embolden_amount.x,
+                glyph.embolden_amount.y,
+            ]);
+        }
+
+        self.device.init_texture(&mut path_info_texture,
+                                 3,
+                                 glyphs.len() as u32,
+                                 TextureFilter::Nearest,
+                                 None,
+                                 1,
+                                 Some(&path_info_texels));
+
+        self.gpu_glyph_renderer.vector_stencil.bind(&mut self.device,
+                                                    projection,
+                                                    &mut self.renderer_errors);
+
+        self.device.bind_draw_target(Some((&current_page.texture, 0)), Some(*target_size));
+        self.device.clear_target(Some([0.0, 0.0, 0.0, 0.0]), None, None);
+
+        self.device.set_blend(true);
+        self.device.set_blend_mode_subpixel_pass1();
+
+        let mut instance_data = vec![];
+        for (path_id, &glyph_id) in glyph_indices.iter().enumerate() {
+            let glyph = &glyphs[glyph_id];
+            instance_data.extend(glyph.mesh
+                                      .stencil_segments
+                                      .iter()
+                                      .zip(glyph.mesh.stencil_normals.iter())
+                                      .map(|(segment, normals)| {
+                VectorStencilInstanceAttrs {
+                    from_position: segment.from,
+                    ctrl_position: segment.ctrl,
+                    to_position: segment.to,
+                    from_normal: normals.from,
+                    ctrl_normal: normals.ctrl,
+                    to_normal: normals.to,
+                    path_id: path_id as u16,
+                }
+            }));
+        }
+
+        self.device.bind_texture(TextureSampler::color(0),
+                                 &self.gpu_glyph_renderer.area_lut_texture);
+        self.device.bind_texture(TextureSampler::color(1), &path_info_texture);
+        self.draw_instanced_batch_with_previously_bound_textures(&instance_data,
+                                                                 VertexArrayKind::VectorStencil,
+                                                                 stats);
+
+        self.device.delete_texture(path_info_texture);
+
+        Some(current_page)
+    }
+
+    /// Blits glyphs from the stencil texture to the texture cache.
+    ///
+    /// Deletes the stencil texture at the end.
+    /// FIXME(pcwalton): This is bad. Cache it somehow.
+    pub fn cover_glyphs(&mut self,
+                        stencil_page: StenciledGlyphPage,
+                        projection: &Transform3D<f32>,
+                        stats: &mut RendererStats) {
+        debug_assert!(!stencil_page.glyphs.is_empty());
+
+        let _timer = self.gpu_profile.start_timer(GPU_TAG_GLYPH_COVER);
+
+        self.gpu_glyph_renderer.vector_cover.bind(&mut self.device,
+                                                  projection,
+                                                  &mut self.renderer_errors);
+
+        self.device.bind_texture(TextureSampler::color(0), &stencil_page.texture);
+        self.draw_instanced_batch_with_previously_bound_textures(&stencil_page.glyphs,
+                                                                 VertexArrayKind::VectorCover,
+                                                                 stats);
+
+        self.device.delete_texture(stencil_page.texture);
+    }
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C)]
+struct VectorStencilInstanceAttrs {
+    from_position: Point2D<f32>,
+    ctrl_position: Point2D<f32>,
+    to_position: Point2D<f32>,
+    from_normal: Vector2D<f32>,
+    ctrl_normal: Vector2D<f32>,
+    to_normal: Vector2D<f32>,
+    path_id: u16,
+}
+
+pub struct StenciledGlyphPage {
+    texture: Texture,
+    glyphs: Vec<VectorCoverInstanceAttrs>,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[repr(C)]
+struct VectorCoverInstanceAttrs {
+    target_rect: DeviceIntRect,
+    stencil_origin: DeviceIntPoint,
+    subpixel: u16,
+}
+
+impl VectorCoverInstanceAttrs {
+    fn stencil_rect(&self) -> DeviceIntRect {
+        DeviceIntRect::new(self.stencil_origin, self.target_rect.size)
+    }
+}
+
+fn x_scale_for_render_mode(render_mode: FontRenderMode) -> i32 {
+    match render_mode {
+        FontRenderMode::Subpixel => 3,
+        FontRenderMode::Mono | FontRenderMode::Alpha => 1,
+    }
+}
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -190,17 +190,17 @@ impl From<CompositePrimitiveInstance> fo
 bitflags! {
     /// Flags that define how the common brush shader
     /// code should process this instance.
     pub struct BrushFlags: u8 {
         const PERSPECTIVE_INTERPOLATION = 0x1;
     }
 }
 
-// TODO(gw): While we are comverting things over, we
+// TODO(gw): While we are converting things over, we
 //           need to have the instance be the same
 //           size as an old PrimitiveInstance. In the
 //           future, we can compress this vertex
 //           format a lot - e.g. z, render task
 //           addresses etc can reasonably become
 //           a u16 type.
 #[repr(C)]
 pub struct BrushInstance {
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -52,24 +52,26 @@ impl HitTestClipChainDescriptor {
     }
 }
 
 #[derive(Clone)]
 pub struct HitTestingItem {
     rect: LayerRect,
     clip_rect: LayerRect,
     tag: ItemTag,
+    is_backface_visible: bool,
 }
 
 impl HitTestingItem {
     pub fn new(tag: ItemTag, info: &LayerPrimitiveInfo) -> HitTestingItem {
         HitTestingItem {
             rect: info.rect,
             clip_rect: info.clip_rect,
             tag: tag,
+            is_backface_visible: info.is_backface_visible,
         }
     }
 }
 
 #[derive(Clone)]
 pub struct HitTestingRun(pub Vec<HitTestingItem>, pub ScrollNodeAndClipChain);
 
 enum HitTestRegion {
@@ -227,16 +229,17 @@ impl HitTester {
             let scroll_node = &self.nodes[scroll_node_id.0];
             let pipeline_id = scroll_node.pipeline_id;
             match (test.pipeline_id, pipeline_id) {
                 (Some(id), node_id) if node_id != id => continue,
                 _ => {},
             }
 
             let transform = scroll_node.world_content_transform;
+            let mut facing_backwards: Option<bool> = None;  // will be computed on first use
             let point_in_layer = match transform.inverse() {
                 Some(inverted) => inverted.transform_point2d(&point),
                 None => continue,
             };
 
             let mut clipped_in = false;
             for item in items.iter().rev() {
                 if !item.rect.contains(&point_in_layer) ||
@@ -260,16 +263,23 @@ impl HitTester {
                 if !self.is_point_clipped_in_for_node(point, root_node_index, &mut test) {
                     continue;
                 }
                 let point_in_viewport = match test.node_cache[&root_node_index] {
                     Some(point) => point,
                     None => continue,
                 };
 
+                // Don't hit items with backface-visibility:hidden if they are facing the back.
+                if !item.is_backface_visible {
+                    if *facing_backwards.get_or_insert_with(|| transform.is_backface_visible()) {
+                        continue;
+                    }
+                }
+
                 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;
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -43,44 +43,50 @@ they're nestable.
 #[macro_use]
 extern crate bitflags;
 #[macro_use]
 extern crate lazy_static;
 #[macro_use]
 extern crate log;
 #[macro_use]
 extern crate thread_profiler;
+#[macro_use]
+extern crate cfg_if;
 #[cfg(any(feature = "debugger", feature = "capture", feature = "replay"))]
 #[macro_use]
 extern crate serde;
 
 mod batch;
 mod border;
 mod box_shadow;
 #[cfg(any(feature = "capture", feature = "replay"))]
 mod capture;
 mod clip;
 mod clip_scroll_node;
 mod clip_scroll_tree;
 mod debug_colors;
+#[cfg(feature = "debug_renderer")]
 mod debug_font_data;
+#[cfg(feature = "debug_renderer")]
 mod debug_render;
 #[cfg(feature = "debugger")]
 mod debug_server;
 mod device;
 mod display_list_flattener;
 mod ellipse;
 mod frame_builder;
 mod freelist;
 #[cfg(any(target_os = "macos", target_os = "windows"))]
 mod gamma_lut;
 mod geometry;
 mod glyph_cache;
 mod glyph_rasterizer;
 mod gpu_cache;
+#[cfg(feature = "pathfinder")]
+mod gpu_glyph_renderer;
 mod gpu_types;
 mod hit_test;
 mod image;
 mod internal_types;
 mod picture;
 mod prim_store;
 mod print_tree;
 mod profiler;
@@ -143,16 +149,24 @@ extern crate dwrote;
 
 extern crate app_units;
 extern crate bincode;
 extern crate byteorder;
 extern crate euclid;
 extern crate fxhash;
 extern crate gleam;
 extern crate num_traits;
+#[cfg(feature = "pathfinder")]
+extern crate pathfinder_font_renderer;
+#[cfg(feature = "pathfinder")]
+extern crate pathfinder_gfx_utils;
+#[cfg(feature = "pathfinder")]
+extern crate pathfinder_partitioner;
+#[cfg(feature = "pathfinder")]
+extern crate pathfinder_path_utils;
 extern crate plane_split;
 extern crate rayon;
 #[cfg(feature = "ron")]
 extern crate ron;
 #[cfg(feature = "debugger")]
 extern crate serde_json;
 extern crate smallvec;
 extern crate time;
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -1,18 +1,18 @@
 /* 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::{FilterOp, MixBlendMode, PipelineId, PremultipliedColorF};
-use api::{DeviceIntRect, LayerRect, LayerToWorldScale};
+use api::{FilterOp, LayerVector2D, MixBlendMode, PipelineId, PremultipliedColorF};
+use api::{DeviceIntRect, LayerRect};
 use box_shadow::{BLUR_SAMPLE_SCALE};
 use clip_scroll_tree::ClipScrollNodeIndex;
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState};
-use gpu_cache::{GpuCacheHandle, GpuDataRequest};
+use gpu_cache::{GpuCacheHandle};
 use prim_store::{PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
 use prim_store::{PrimitiveMetadata, ScrollNodeAndClipChain};
 use render_task::{ClearMode, RenderTask};
 use render_task::{RenderTaskId, RenderTaskLocation};
 use scene::{FilterOpHelpers, SceneProperties};
 use tiling::RenderTargetKind;
 
 /*
@@ -154,50 +154,55 @@ impl PicturePrimitive {
 
         self.real_local_rect = prim_run_rect.local_rect_in_original_parent_space;
 
         match self.composite_mode {
             Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
                 let inflate_size = (blur_radius * BLUR_SAMPLE_SCALE).ceil();
                 local_content_rect.inflate(inflate_size, inflate_size)
             }
-            Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, _))) => {
-                let inflate_size = blur_radius * BLUR_SAMPLE_SCALE;
+            Some(PictureCompositeMode::Filter(FilterOp::DropShadow(_, blur_radius, _))) => {
+                let inflate_size = (blur_radius * BLUR_SAMPLE_SCALE).ceil();
                 local_content_rect.inflate(inflate_size, inflate_size)
-                                  .translate(&offset)
             }
             _ => {
                 local_content_rect
             }
         }
     }
 
     pub fn prepare_for_render(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_metadata: &mut PrimitiveMetadata,
         pic_state_for_children: PictureState,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
     ) {
-        let content_scale = LayerToWorldScale::new(1.0) * frame_context.device_pixel_scale;
         let prim_screen_rect = prim_metadata
                                 .screen_rect
                                 .as_ref()
                                 .expect("bug: trying to draw an off-screen picture!?");
+
+        // 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.
         let device_rect = match self.composite_mode {
             Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
-                // If blur radius is 0, we can skip drawing this an an
+                // If blur radius is 0, we can skip drawing this on an
                 // intermediate surface.
                 if blur_radius == 0.0 {
                     pic_state.tasks.extend(pic_state_for_children.tasks);
                     self.surface = None;
 
-                    DeviceIntRect::zero()
+                    None
                 } else {
                     let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
                     let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
 
                     // 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
@@ -209,20 +214,17 @@ impl PicturePrimitive {
                         .clipped
                         .inflate(blur_range, blur_range)
                         .intersection(&prim_screen_rect.unclipped)
                         .unwrap();
 
                     let picture_task = RenderTask::new_picture(
                         RenderTaskLocation::Dynamic(None, device_rect.size),
                         prim_index,
-                        RenderTargetKind::Color,
                         device_rect.origin,
-                        PremultipliedColorF::TRANSPARENT,
-                        ClearMode::Transparent,
                         pic_state_for_children.tasks,
                     );
 
                     let picture_task_id = frame_state.render_tasks.add(picture_task);
 
                     let blur_render_task = RenderTask::new_blur(
                         blur_std_deviation,
                         picture_task_id,
@@ -230,155 +232,172 @@ impl PicturePrimitive {
                         RenderTargetKind::Color,
                         ClearMode::Transparent,
                     );
 
                     let render_task_id = frame_state.render_tasks.add(blur_render_task);
                     pic_state.tasks.push(render_task_id);
                     self.surface = Some(render_task_id);
 
-                    device_rect
+                    Some(device_rect)
                 }
             }
-            Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, _))) => {
-                // TODO(gw): This is totally wrong and can never work with
-                //           transformed drop-shadow elements. Fix me!
-                let rect = (prim_metadata.local_rect.translate(&-offset) * content_scale).round().to_i32();
+            Some(PictureCompositeMode::Filter(FilterOp::DropShadow(_, blur_radius, _))) => {
+                let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
+                let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
+
+                // 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.
+                let device_rect = prim_screen_rect
+                    .clipped
+                    .inflate(blur_range, blur_range)
+                    .intersection(&prim_screen_rect.unclipped)
+                    .unwrap();
+
                 let mut picture_task = RenderTask::new_picture(
-                    RenderTaskLocation::Dynamic(None, rect.size),
+                    RenderTaskLocation::Dynamic(None, device_rect.size),
                     prim_index,
-                    RenderTargetKind::Color,
-                    rect.origin,
-                    PremultipliedColorF::TRANSPARENT,
-                    ClearMode::Transparent,
+                    device_rect.origin,
                     pic_state_for_children.tasks,
                 );
                 picture_task.mark_for_saving();
 
-                let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
                 let picture_task_id = frame_state.render_tasks.add(picture_task);
 
                 let blur_render_task = RenderTask::new_blur(
                     blur_std_deviation.round(),
                     picture_task_id,
                     frame_state.render_tasks,
                     RenderTargetKind::Color,
                     ClearMode::Transparent,
                 );
 
                 self.secondary_render_task_id = Some(picture_task_id);
 
                 let render_task_id = frame_state.render_tasks.add(blur_render_task);
                 pic_state.tasks.push(render_task_id);
                 self.surface = Some(render_task_id);
 
-                rect
+                Some(device_rect)
             }
             Some(PictureCompositeMode::MixBlend(..)) => {
                 let picture_task = RenderTask::new_picture(
                     RenderTaskLocation::Dynamic(None, prim_screen_rect.clipped.size),
                     prim_index,
-                    RenderTargetKind::Color,
                     prim_screen_rect.clipped.origin,
-                    PremultipliedColorF::TRANSPARENT,
-                    ClearMode::Transparent,
                     pic_state_for_children.tasks,
                 );
 
                 let readback_task_id = frame_state.render_tasks.add(
                     RenderTask::new_readback(prim_screen_rect.clipped)
                 );
 
                 self.secondary_render_task_id = Some(readback_task_id);
                 pic_state.tasks.push(readback_task_id);
 
                 let render_task_id = frame_state.render_tasks.add(picture_task);
                 pic_state.tasks.push(render_task_id);
                 self.surface = Some(render_task_id);
 
-                prim_screen_rect.clipped
+                Some(prim_screen_rect.clipped)
             }
             Some(PictureCompositeMode::Filter(filter)) => {
                 // If this filter is not currently going to affect
                 // the picture, just collapse this picture into the
                 // current render task. This most commonly occurs
                 // when opacity == 1.0, but can also occur on other
                 // filters and be a significant performance win.
                 if filter.is_noop() {
                     pic_state.tasks.extend(pic_state_for_children.tasks);
                     self.surface = None;
-                } else {
 
-                    if let FilterOp::ColorMatrix(m) = filter {
-                        if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
-                            for i in 0..5 {
-                                request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
+                    None
+                } else {
+                    let device_rect = match filter {
+                        FilterOp::ColorMatrix(m) => {
+                            if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
+                                for i in 0..5 {
+                                    request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
+                                }
                             }
+
+                            None
                         }
-                    }
+                        _ => {
+                            Some(prim_screen_rect.clipped)
+                        }
+                    };
 
                     let picture_task = RenderTask::new_picture(
                         RenderTaskLocation::Dynamic(None, prim_screen_rect.clipped.size),
                         prim_index,
-                        RenderTargetKind::Color,
                         prim_screen_rect.clipped.origin,
-                        PremultipliedColorF::TRANSPARENT,
-                        ClearMode::Transparent,
                         pic_state_for_children.tasks,
                     );
 
                     let render_task_id = frame_state.render_tasks.add(picture_task);
                     pic_state.tasks.push(render_task_id);
                     self.surface = Some(render_task_id);
+
+                    device_rect
                 }
-
-                prim_screen_rect.clipped
             }
             Some(PictureCompositeMode::Blit) => {
                 let picture_task = RenderTask::new_picture(
                     RenderTaskLocation::Dynamic(None, prim_screen_rect.clipped.size),
                     prim_index,
-                    RenderTargetKind::Color,
                     prim_screen_rect.clipped.origin,
-                    PremultipliedColorF::TRANSPARENT,
-                    ClearMode::Transparent,
                     pic_state_for_children.tasks,
                 );
 
                 let render_task_id = frame_state.render_tasks.add(picture_task);
                 pic_state.tasks.push(render_task_id);
                 self.surface = Some(render_task_id);
 
-                prim_screen_rect.clipped
+                Some(prim_screen_rect.clipped)
             }
             None => {
                 pic_state.tasks.extend(pic_state_for_children.tasks);
                 self.surface = None;
 
-                DeviceIntRect::zero()
+                None
             }
         };
 
-        // If scrolling or property animation has resulted in the task
-        // rect being different than last time, invalidate the GPU
-        // cache entry for this picture to ensure that the correct
-        // task rect is provided to the image shader.
-        if self.task_rect != device_rect {
-            frame_state.gpu_cache.invalidate(&prim_metadata.gpu_location);
-            self.task_rect = device_rect;
+        // If this picture type uses the common / general GPU data
+        // format, then write it now.
+        if let Some(device_rect) = device_rect {
+            // If scrolling or property animation has resulted in the task
+            // rect being different than last time, invalidate the GPU
+            // cache entry for this picture to ensure that the correct
+            // task rect is provided to the image shader.
+            if self.task_rect != device_rect {
+                frame_state.gpu_cache.invalidate(&self.extra_gpu_data_handle);
+                self.task_rect = device_rect;
+            }
+
+            if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
+                request.push(self.task_rect.to_f32());
+
+                // TODO(gw): It would make the shaders a bit simpler if the offset
+                //           was provided as part of the brush::picture instance,
+                //           rather than in the Picture data itself.
+                let (offset, color) = match self.composite_mode {
+                    Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, _, color))) => {
+                        (offset, color.premultiplied())
+                    }
+                    _ => {
+                        (LayerVector2D::zero(), PremultipliedColorF::WHITE)
+                    }
+                };
+
+                request.push([offset.x, offset.y, 0.0, 0.0]);
+                request.push(color);
+            }
         }
     }
-
-    pub fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
-        request.push(self.task_rect.to_f32());
-
-        let color = match self.composite_mode {
-            Some(PictureCompositeMode::Filter(FilterOp::DropShadow(_, _, color))) => {
-                color.premultiplied()
-            }
-            _ => {
-                PremultipliedColorF::WHITE
-            }
-        };
-
-        request.push(color);
-    }
 }
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -6,44 +6,53 @@ use api::{ColorU, FontKey, FontRenderMod
 use api::{FontInstanceFlags, FontVariation, NativeFontHandle};
 use api::{GlyphKey, SubpixelDirection};
 use app_units::Au;
 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::base::{kCGImageAlphaNoneSkipFirst, kCGBitmapByteOrder32Little};
+#[cfg(not(feature = "pathfinder"))]
+use core_graphics::base::kCGImageAlphaPremultipliedFirst;
 use core_graphics::color_space::CGColorSpace;
-use core_graphics::context::{CGContext, CGTextDrawingMode};
+use core_graphics::context::CGContext;
+#[cfg(not(feature = "pathfinder"))]
+use core_graphics::context::CGTextDrawingMode;
 use core_graphics::data_provider::CGDataProvider;
 use core_graphics::font::{CGFont, CGGlyph};
-use core_graphics::geometry::{CGAffineTransform, CGPoint, CGRect, CGSize};
+use core_graphics::geometry::{CGAffineTransform, CGPoint, CGSize};
+#[cfg(not(feature = "pathfinder"))]
+use core_graphics::geometry::CGRect;
 use core_text;
 use core_text::font::{CTFont, CTFontRef};
 use core_text::font_descriptor::{kCTFontDefaultOrientation, kCTFontColorGlyphsTrait};
 use gamma_lut::{ColorLut, GammaLut};
-use glyph_rasterizer::{FontInstance, FontTransform, GlyphFormat, RasterizedGlyph};
+use glyph_rasterizer::{FontInstance, FontTransform};
+#[cfg(not(feature = "pathfinder"))]
+use glyph_rasterizer::{GlyphFormat, GlyphRasterResult, RasterizedGlyph};
 use internal_types::{FastHashMap, ResourceCacheError};
 use std::collections::hash_map::Entry;
 use std::sync::Arc;
 
 pub struct FontContext {
     cg_fonts: FastHashMap<FontKey, CGFont>,
     ct_fonts: FastHashMap<(FontKey, Au, Vec<FontVariation>), CTFont>,
+    #[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: u32,
     rasterized_height: u32,
     advance: f32,
 }
 
 // According to the Skia source code, there's no public API to
@@ -406,16 +415,17 @@ impl FontContext {
                         height: metrics.rasterized_height as u32,
                         advance: metrics.advance,
                     })
                 }
             })
     }
 
     // Assumes the pixels here are linear values from CG
+    #[cfg(not(feature = "pathfinder"))]
     fn gamma_correct_pixels(
         &self,
         pixels: &mut Vec<u8>,
         render_mode: FontRenderMode,
         color: ColorU,
     ) {
         // Then convert back to gamma corrected values.
         match render_mode {
@@ -473,26 +483,23 @@ impl FontContext {
                     font.color.quantized_ceil()
                 } else {
                     font.color.quantized_floor()
                 };
             }
         }
     }
 
-    pub fn rasterize_glyph(
-        &mut self,
-        font: &FontInstance,
-        key: &GlyphKey,
-    ) -> Option<RasterizedGlyph> {
+    #[cfg(not(feature = "pathfinder"))]
+    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.scale_by(y_scale as f32);
         let ct_font = match self.get_ct_font(font.font_key, size, &font.variations) {
             Some(font) => font,
-            None => return None,
+            None => return GlyphRasterResult::LoadFailed,
         };
 
         let bitmap = is_bitmap_font(&ct_font);
         let (mut shape, (x_offset, y_offset)) = if bitmap {
             (FontTransform::identity(), (0.0, 0.0))
         } else {
             (font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key))
         };
@@ -528,17 +535,17 @@ impl FontContext {
             &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 None;
+            return GlyphRasterResult::LoadFailed
         }
 
         // The result of this function, in all render modes, is going to be a
         // BGRA surface with white text on transparency using premultiplied
         // alpha. For subpixel text, the RGB values will be the mask value for
         // the individual components. For bitmap glyphs, the RGB values will be
         // the (premultiplied) color of the pixel. For Alpha and Mono, each
         // pixel will have R==G==B==A at the end of this function.
@@ -708,17 +715,17 @@ impl FontContext {
                 self.gamma_correct_pixels(
                     &mut rasterized_pixels,
                     font.render_mode,
                     font.color,
                 );
             }
         }
 
-        Some(RasterizedGlyph {
+        GlyphRasterResult::Bitmap(RasterizedGlyph {
             left: metrics.rasterized_left as f32,
             top: metrics.rasterized_ascent as f32,
             width: metrics.rasterized_width,
             height: metrics.rasterized_height,
             scale: if bitmap { y_scale.recip() as f32 } else { 1.0 },
             format: if bitmap { GlyphFormat::ColorBitmap } else { font.get_glyph_format() },
             bytes: rasterized_pixels,
         })
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -12,17 +12,18 @@ use freetype::freetype::{FT_F26Dot6, FT_
 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};
 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, FT_LOAD_VERTICAL_LAYOUT};
 use freetype::freetype::{FT_FACE_FLAG_SCALABLE, FT_FACE_FLAG_FIXED_SIZES};
-use glyph_rasterizer::{FontInstance, GlyphFormat, RasterizedGlyph};
+use freetype::succeeded;
+use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphRasterResult, RasterizedGlyph};
 use internal_types::{FastHashMap, ResourceCacheError};
 use std::{cmp, mem, ptr, slice};
 use std::cmp::max;
 use std::ffi::CString;
 use std::sync::Arc;
 
 // These constants are not present in the freetype
 // bindings due to bindgen not handling the way
@@ -147,26 +148,26 @@ impl FontContext {
         // 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 result.succeeded() {
+        if succeeded(result) {
             Ok(FontContext {
                 lib,
                 faces: FastHashMap::default(),
                 lcd_extra_pixels,
             })
         } else {
             // TODO(gw): Provide detailed error values.
             Err(ResourceCacheError::new(
-                format!("Failed to initialize FreeType - {}", result.0)
+                format!("Failed to initialize FreeType - {}", result)
             ))
         }
     }
 
     pub fn has_font(&self, font_key: &FontKey) -> bool {
         self.faces.contains_key(font_key)
     }
 
@@ -177,17 +178,17 @@ impl FontContext {
                 FT_New_Memory_Face(
                     self.lib,
                     bytes.as_ptr(),
                     bytes.len() as FT_Long,
                     index as FT_Long,
                     &mut face,
                 )
             };
-            if result.succeeded() && !face.is_null() {
+            if succeeded(result) && !face.is_null() {
                 self.faces.insert(
                     *font_key,
                     Face {
                         face,
                         _bytes: Some(bytes),
                     },
                 );
             } else {
@@ -204,17 +205,17 @@ impl FontContext {
             let result = unsafe {
                 FT_New_Face(
                     self.lib,
                     pathname.as_ptr(),
                     native_font_handle.index as FT_Long,
                     &mut face,
                 )
             };
-            if result.succeeded() && !face.is_null() {
+            if succeeded(result) && !face.is_null() {
                 self.faces.insert(
                     *font_key,
                     Face {
                         face,
                         _bytes: None,
                     },
                 );
             } else {
@@ -222,17 +223,17 @@ impl FontContext {
                 debug!("font={:?}, path={:?}", font_key, pathname);
             }
         }
     }
 
     pub fn delete_font(&mut self, font_key: &FontKey) {
         if let Some(face) = self.faces.remove(font_key) {
             let result = unsafe { FT_Done_Face(face.face) };
-            assert!(result.succeeded());
+            assert!(succeeded(result));
         }
     }
 
     fn load_glyph(&self, font: &FontInstance, glyph: &GlyphKey) -> Option<FT_GlyphSlot> {
         debug_assert!(self.faces.contains_key(&font.font_key));
         let face = self.faces.get(&font.font_key).unwrap();
 
         let mut load_flags = FT_LOAD_DEFAULT;
@@ -311,21 +312,21 @@ impl FontContext {
                     (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 result.succeeded() {
+        if succeeded(result) {
             result = unsafe { FT_Load_Glyph(face.face, glyph.index as FT_UInt, load_flags as FT_Int32) };
         };
 
-        if result.succeeded() {
+        if succeeded(result) {
             let slot = unsafe { (*face.face).glyph };
             assert!(slot != ptr::null_mut());
 
             if font.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) {
                 unsafe { FT_GlyphSlot_Embolden(slot) };
             }
 
             let format = unsafe { (*slot).format };
@@ -562,68 +563,65 @@ impl FontContext {
         }
         let render_mode = match (font.render_mode, font.subpx_dir) {
             (FontRenderMode::Mono, _) => FT_Render_Mode::FT_RENDER_MODE_MONO,
             (FontRenderMode::Alpha, _) => FT_Render_Mode::FT_RENDER_MODE_NORMAL,
             (FontRenderMode::Subpixel, SubpixelDirection::Vertical) => FT_Render_Mode::FT_RENDER_MODE_LCD_V,
             (FontRenderMode::Subpixel, _) => FT_Render_Mode::FT_RENDER_MODE_LCD,
         };
         let result = unsafe { FT_Render_Glyph(slot, render_mode) };
-        if !result.succeeded() {
+        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,
-    ) -> Option<RasterizedGlyph> {
+    #[cfg(not(feature = "pathfinder"))]
+    pub fn rasterize_glyph(&mut self, font: &FontInstance, key: &GlyphKey) -> GlyphRasterResult {
         let slot = match self.load_glyph(font, key) {
             Some(slot) => slot,
-            None => return None,
+            None => return GlyphRasterResult::LoadFailed,
         };
 
         // Get dimensions of the glyph, to see if we need to rasterize it.
         let dimensions = match self.get_glyph_dimensions_impl(slot, font, key, false) {
             Some(val) => val,
-            None => return None,
+            None => return GlyphRasterResult::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 None;
+            return GlyphRasterResult::LoadFailed;
         }
 
         let format = unsafe { (*slot).format };
         let mut scale = 1.0;
         match format {
             FT_Glyph_Format::FT_GLYPH_FORMAT_BITMAP => {
                 let y_size = unsafe { (*(*(*slot).face).size).metrics.y_ppem };
                 scale = font.size.to_f32_px() / y_size as f32;
             }
             FT_Glyph_Format::FT_GLYPH_FORMAT_OUTLINE => {
                 if !self.rasterize_glyph_outline(slot, font, key) {
-                    return None;
+                    return GlyphRasterResult::LoadFailed;
                 }
             }
             _ => {
                 error!("Unsupported format");
                 debug!("format={:?}", format);
-                return None;
+                return GlyphRasterResult::LoadFailed;
             }
         };
 
         debug!(
             "Rasterizing {:?} as {:?} with dimensions {:?}",
             key,
             font.render_mode,
             dimensions
@@ -766,17 +764,17 @@ impl FontContext {
         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(),
         };
 
-        Some(RasterizedGlyph {
+        GlyphRasterResult::Bitmap(RasterizedGlyph {
             left: left as f32,
             top: top as f32,
             width: actual_width as u32,
             height: actual_height as u32,
             scale,
             format: glyph_format,
             bytes: final_buffer,
         })
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -1,17 +1,18 @@
 /* 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};
 use api::{ColorU, GlyphDimensions, GlyphKey, SubpixelDirection};
 use dwrote;
 use gamma_lut::{ColorLut, GammaLut};
-use glyph_rasterizer::{FontInstance, FontTransform, GlyphFormat, RasterizedGlyph};
+use glyph_rasterizer::{FontInstance, FontTransform, GlyphFormat};
+use glyph_rasterizer::{GlyphRasterResult, RasterizedGlyph};
 use internal_types::{FastHashMap, ResourceCacheError};
 use std::collections::hash_map::Entry;
 use std::sync::Arc;
 
 lazy_static! {
     static ref DEFAULT_FONT_DESCRIPTOR: dwrote::FontDescriptor = dwrote::FontDescriptor {
         family_name: "Arial".to_owned(),
         weight: dwrote::FontWeight::Regular,
@@ -356,21 +357,18 @@ impl FontContext {
                 font.color = font.color.luminance_color().quantize();
             }
             FontRenderMode::Subpixel => {
                 font.color = font.color.quantize();
             }
         }
     }
 
-    pub fn rasterize_glyph(
-        &mut self,
-        font: &FontInstance,
-        key: &GlyphKey,
-    ) -> Option<RasterizedGlyph> {
+    #[cfg(not(feature = "pathfinder"))]
+    pub fn rasterize_glyph(&mut self, font: &FontInstance, key: &GlyphKey) -> GlyphRasterResult {
         let (.., y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
         let size = (font.size.to_f64_px() * y_scale) as f32;
         let bitmaps = is_bitmap_font(font);
         let (mut shape, (x_offset, y_offset)) = if bitmaps {
             (FontTransform::identity(), (0.0, 0.0))
         } else {
             (font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key))
         };
@@ -404,17 +402,17 @@ impl FontContext {
 
         let bounds = analysis.get_alpha_texture_bounds(texture_type);
         let width = (bounds.right - bounds.left) as u32;
         let height = (bounds.bottom - bounds.top) as u32;
 
         // Alpha texture bounds can sometimes return an empty rect
         // Such as for spaces
         if width == 0 || height == 0 {
-            return None;
+            return GlyphRasterResult::LoadFailed;
         }
 
         let pixels = analysis.create_alpha_texture(texture_type, bounds);
         let mut bgra_pixels = self.convert_to_bgra(&pixels, font.render_mode, bitmaps);
 
         let lut_correction = match font.render_mode {
             FontRenderMode::Mono => &self.gdi_gamma_lut,
             FontRenderMode::Alpha | FontRenderMode::Subpixel => {
@@ -422,17 +420,17 @@ impl FontContext {
                     &self.gdi_gamma_lut
                 } else {
                     &self.gamma_lut
                 }
             }
         };
         lut_correction.preblend(&mut bgra_pixels, font.color);
 
-        Some(RasterizedGlyph {
+        GlyphRasterResult::Bitmap(RasterizedGlyph {
             left: bounds.left as f32,
             top: -bounds.top as f32,
             width,
             height,
             scale: if bitmaps { y_scale.recip() as f32 } else { 1.0 },
             format: if bitmaps { GlyphFormat::Bitmap } else { font.get_glyph_format() },
             bytes: bgra_pixels,
         })
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -2,33 +2,34 @@
  * 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, BorderRadius, BoxShadowClipMode, BuiltDisplayList, ClipMode, ColorF, ComplexClipRegion};
 use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch, ExtendMode, FontRenderMode};
 use api::{FilterOp, GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag};
 use api::{LayerPoint, LayerRect, LayerSize, LayerToWorldTransform, LayerVector2D};
 use api::{PipelineId, PremultipliedColorF, Shadow, YuvColorSpace, YuvFormat};
+use batch::BrushImageSourceKind;
 use border::{BorderCornerInstance, BorderEdgeKind};
 use box_shadow::BLUR_SAMPLE_SCALE;
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
 use clip_scroll_node::ClipScrollNode;
 use clip::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipSource};
 use clip::{ClipSourcesHandle, ClipWorkItem};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveRunContext;
 use glyph_rasterizer::{FontInstance, FontTransform};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
 use gpu_types::{ClipChainRectIndex};
 use picture::{PictureCompositeMode, PicturePrimitive};
-use render_task::{BlitSource, RenderTask, RenderTaskCacheKey, RenderTaskCacheKeyKind};
-use render_task::RenderTaskId;
+use render_task::{BlitSource, RenderTask, RenderTaskCacheKey};
+use render_task::{RenderTaskCacheKeyKind, RenderTaskId};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
-use resource_cache::{CacheItem, ImageProperties, ImageRequest, ResourceCache};
+use resource_cache::{CacheItem, ImageProperties, ImageRequest};
 use segment::SegmentBuilder;
 use std::{mem, usize};
 use std::sync::Arc;
 use util::{MatrixHelpers, WorldToLayerFastTransform, calculate_screen_bounding_rect};
 use util::{pack_as_float, recycle_vec};
 
 
 const MIN_BRUSH_SPLIT_AREA: f32 = 256.0 * 256.0;
@@ -196,16 +197,22 @@ pub struct PrimitiveMetadata {
 #[derive(Debug)]
 pub enum BrushKind {
     Solid {
         color: ColorF,
     },
     Clear,
     Picture {
         pic_index: PictureIndex,
+        // What kind of texels to sample from the
+        // picture (e.g color or alpha mask).
+        source_kind: BrushImageSourceKind,
+        // A local space offset to apply when drawing
+        // this picture.
+        local_offset: LayerVector2D,
     },
     Image {
         request: ImageRequest,
         current_epoch: Epoch,
         alpha_type: AlphaType,
     },
     YuvImage {
         yuv_key: [ImageKey; 3],
@@ -232,22 +239,25 @@ pub enum BrushKind {
         end_point: LayerPoint,
     }
 }
 
 impl BrushKind {
     fn supports_segments(&self) -> bool {
         match *self {
             BrushKind::Solid { .. } |
-            BrushKind::Picture { .. } |
             BrushKind::Image { .. } |
             BrushKind::YuvImage { .. } |
             BrushKind::RadialGradient { .. } |
             BrushKind::LinearGradient { .. } => true,
 
+            // TODO(gw): Allow batch.rs to add segment instances
+            //           for Picture primitives.
+            BrushKind::Picture { .. } => false,
+
             BrushKind::Clear => false,
         }
     }
 }
 
 bitflags! {
     /// Each bit of the edge AA mask is:
     /// 0, when the edge of the primitive needs to be considered for AA
@@ -312,41 +322,40 @@ impl BrushPrimitive {
         segment_desc: Option<BrushSegmentDescriptor>,
     ) -> BrushPrimitive {
         BrushPrimitive {
             kind,
             segment_desc,
         }
     }
 
-    pub fn new_picture(pic_index: PictureIndex) -> BrushPrimitive {
+    pub fn new_picture(
+        pic_index: PictureIndex,
+        source_kind: BrushImageSourceKind,
+        local_offset: LayerVector2D,
+    ) -> BrushPrimitive {
         BrushPrimitive {
             kind: BrushKind::Picture {
                 pic_index,
+                source_kind,
+                local_offset,
             },
             segment_desc: None,
         }
     }
 
     fn write_gpu_blocks(
         &self,
         request: &mut GpuDataRequest,
-        pictures: &[PicturePrimitive],
     ) {
         // has to match VECS_PER_SPECIFIC_BRUSH
         match self.kind {
-            BrushKind::Picture { pic_index } => {
-                pictures[pic_index.0].write_gpu_blocks(request);
-            }
-            BrushKind::YuvImage { .. } => {
-            }
-            BrushKind::Image { .. } => {
-                request.push([0.0; 4]);
-                request.push(PremultipliedColorF::WHITE);
-            }
+            BrushKind::Picture { .. } |
+            BrushKind::YuvImage { .. } |
+            BrushKind::Image { .. } => {}
             BrushKind::Solid { color } => {
                 request.push(color.premultiplied());
             }
             BrushKind::Clear => {
                 // Opaque black with operator dest out
                 request.push(PremultipliedColorF::BLACK);
             }
             BrushKind::LinearGradient { start_point, end_point, extend_mode, .. } => {
@@ -645,21 +654,20 @@ impl TextRunPrimitiveCpu {
                 font.transform = FontTransform::from(&transform).quantize();
             }
         }
         font
     }
 
     fn prepare_for_render(
         &mut self,
-        resource_cache: &mut ResourceCache,
         device_pixel_scale: DevicePixelScale,
         transform: Option<LayerToWorldTransform>,
         display_list: &BuiltDisplayList,
-        gpu_cache: &mut GpuCache,
+        frame_building_state: &mut FrameBuildingState,
     ) {
         let font = self.get_font(device_pixel_scale, transform);
 
         // Cache the glyph positions, if not in the cache already.
         // TODO(gw): In the future, remove `glyph_instances`
         //           completely, and just reference the glyphs
         //           directly from the display list.
         if self.glyph_keys.is_empty() {
@@ -688,17 +696,22 @@ impl TextRunPrimitiveCpu {
 
             // Ensure the last block is added in the case
             // of an odd number of glyphs.
             if (self.glyph_keys.len() & 1) != 0 {
                 self.glyph_gpu_blocks.push(gpu_block.into());
             }
         }
 
-        resource_cache.request_glyphs(font, &self.glyph_keys, gpu_cache);
+        frame_building_state.resource_cache
+                            .request_glyphs(font,
+                                            &self.glyph_keys,
+                                            frame_building_state.gpu_cache,
+                                            frame_building_state.render_tasks,
+                                            frame_building_state.special_render_passes);
     }
 
     fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
         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]);
         request.push([
@@ -1163,21 +1176,20 @@ impl PrimitiveStore {
         let metadata = &mut self.cpu_metadata[prim_index.0];
         match metadata.prim_kind {
             PrimitiveKind::Border => {}
             PrimitiveKind::TextRun => {
                 let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0];
                 // The transform only makes sense for screen space rasterization
                 let transform = Some(prim_run_context.scroll_node.world_content_transform.into());
                 text.prepare_for_render(
-                    frame_state.resource_cache,
                     frame_context.device_pixel_scale,
                     transform,
                     pic_context.display_list,
-                    frame_state.gpu_cache,
+                    frame_state,
                 );
             }
             PrimitiveKind::Image => {
                 let image_cpu = &mut self.cpu_images[metadata.cpu_prim_index.0];
                 let image_properties = frame_state
                     .resource_cache
                     .get_image_properties(image_cpu.key.request.key);
 
@@ -1228,16 +1240,17 @@ impl PrimitiveStore {
                             // Request a pre-rendered image task.
                             *item = frame_state.resource_cache.request_render_task(
                                 RenderTaskCacheKey {
                                     size,
                                     kind: RenderTaskCacheKeyKind::Image(key),
                                 },
                                 frame_state.gpu_cache,
                                 frame_state.render_tasks,
+                                None,
                                 |render_tasks| {
                                     // We need to render the image cache this frame,
                                     // so will need access to the source texture.
                                     request_source_image = true;
 
                                     // 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.
@@ -1342,26 +1355,52 @@ impl PrimitiveStore {
                                 pic_context.display_list,
                             );
                             gradient_builder.build(
                                 reverse_stops,
                                 &mut request,
                             );
                         }
                     }
-                    BrushKind::Picture { pic_index } => {
-                        self.pictures[pic_index.0]
-                            .prepare_for_render(
-                                prim_index,
-                                metadata,
-                                pic_state_for_children,
-                                pic_state,
-                                frame_context,
-                                frame_state,
-                            );
+                    BrushKind::Picture { pic_index, source_kind, .. } => {
+                        let pic = &mut self.pictures[pic_index.0];
+                        // If this picture is referenced by multiple brushes,
+                        // we only want to prepare it once per frame. It
+                        // should be prepared for the main color pass.
+                        // TODO(gw): Make this a bit more explicit - perhaps
+                        //           we could mark which brush::picture is
+                        //           the owner of the picture, vs the shadow
+                        //           which is just referencing it.
+                        match source_kind {
+                            BrushImageSourceKind::Color => {
+                                pic.prepare_for_render(
+                                    prim_index,
+                                    metadata,
+                                    pic_state_for_children,
+                                    pic_state,
+                                    frame_context,
+                                    frame_state,
+                                );
+                            }
+                            BrushImageSourceKind::ColorAlphaMask => {
+                                // Since we will always visit the shadow
+                                // brush first, use this to clear out the
+                                // render tasks from the previous frame.
+                                // This ensures that if the primary brush
+                                // is found to be non-visible, then we
+                                // won't try to draw the drop-shadow either.
+                                // This isn't quite correct - it can result
+                                // in clipping artifacts if the image is
+                                // off-screen, but the drop-shadow is
+                                // partially visible - we can fix this edge
+                                // case as a follow up.
+                                pic.surface = None;
+                                pic.secondary_render_task_id = None;
+                            }
+                        }
                     }
                     BrushKind::Solid { .. } |
                     BrushKind::Clear => {}
                 }
             }
         }
 
         // Mark this GPU resource as required for this frame.
@@ -1380,17 +1419,17 @@ impl PrimitiveStore {
                     image.write_gpu_blocks(request);
                 }
                 PrimitiveKind::TextRun => {
                     let text = &self.cpu_text_runs[metadata.cpu_prim_index.0];
                     text.write_gpu_blocks(&mut request);
                 }
                 PrimitiveKind::Brush => {
                     let brush = &self.cpu_brushes[metadata.cpu_prim_index.0];
-                    brush.write_gpu_blocks(&mut request, &self.pictures);
+                    brush.write_gpu_blocks(&mut request);
                     match brush.segment_desc {
                         Some(ref segment_desc) => {
                             for segment in &segment_desc.segments {
                                 // has to match VECS_PER_SEGMENT
                                 request.write_segment(segment.local_rect);
                             }
                         }
                         None => {
@@ -1792,17 +1831,17 @@ impl PrimitiveStore {
         };
 
         // 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.
         if let PrimitiveKind::Brush = prim_kind {
-            if let BrushKind::Picture { pic_index } = self.cpu_brushes[cpu_prim_index.0].kind {
+            if let BrushKind::Picture { pic_index, local_offset, .. } = self.cpu_brushes[cpu_prim_index.0].kind {
                 let pic_context_for_children = {
                     let pic = &mut self.pictures[pic_index.0];
 
                     if !pic.resolve_scene_properties(frame_context.scene_properties) {
                         return None;
                     }
 
                     may_need_clip_mask = pic.composite_mode.is_some();
@@ -1847,17 +1886,21 @@ impl PrimitiveStore {
                     frame_state,
                 );
 
                 // Restore the dependencies (borrow check dance)
                 let pic = &mut self.pictures[pic_index.0];
                 pic.runs = pic_context_for_children.prim_runs;
 
                 let metadata = &mut self.cpu_metadata[prim_index.0];
-                metadata.local_rect = pic.update_local_rect(result);
+                // Store local rect of the picture for this brush,
+                // also applying any local offset for the instance.
+                metadata.local_rect = pic
+                    .update_local_rect(result)
+                    .translate(&local_offset);
             }
         }
 
         let (local_rect, unclipped_device_rect) = {
             let metadata = &mut self.cpu_metadata[prim_index.0];
             if metadata.local_rect.size.width <= 0.0 ||
                metadata.local_rect.size.height <= 0.0 {
                 //warn!("invalid primitive rect {:?}", metadata.local_rect);
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -1,36 +1,48 @@
 /* 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 debug_render::DebugRenderer;
-use euclid::{Point2D, Rect, Size2D, vec2};
-use query::{GpuSampler, GpuTimer, NamedTag};
+use api::ColorF;
+use query::{GpuTimer, NamedTag};
 use std::collections::vec_deque::VecDeque;
-use internal_types::FastHashMap;
-use renderer::MAX_VERTEX_TEXTURE_WIDTH;
-use std::{f32, mem};
+use std::f32;
 use time::precise_time_ns;
 
-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 = 10.0;
+cfg_if! {
+    if #[cfg(feature = "debug_renderer")] {
+        use api::ColorU;
+        use debug_render::DebugRenderer;
+        use euclid::{Point2D, Rect, Size2D, vec2};
+        use query::GpuSampler;
+        use internal_types::FastHashMap;
+        use renderer::MAX_VERTEX_TEXTURE_WIDTH;
+        use std::mem;
+    }
+}
+
+cfg_if! {
+    if #[cfg(feature = "debug_renderer")] {
+        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 = 10.0;
+    }
+}
 
 const ONE_SECOND_NS: u64 = 1000000000;
 
 #[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;
@@ -80,21 +92,23 @@ impl ProfileCounter for IntProfileCounte
         self.description
     }
 
     fn value(&self) -> String {
         format!("{}", self.value)
     }
 }
 
+#[cfg(feature = "debug_renderer")]
 pub struct FloatProfileCounter {
     description: &'static str,
     value: f32,
 }
 
+#[cfg(feature = "debug_renderer")]
 impl ProfileCounter for FloatProfileCounter {
     fn description(&self) -> &'static str {
         self.description
     }
 
     fn value(&self) -> String {
         format!("{:.2}", self.value)
     }
@@ -484,33 +498,36 @@ impl RendererProfileTimers {
 
 struct GraphStats {
     min_value: f32,
     mean_value: f32,
     max_value: f32,
 }
 
 struct ProfileGraph {
+    #[cfg(feature = "debug_renderer")]
     max_samples: usize,
     values: VecDeque<f32>,
     short_description: &'static str,
 }
 
 impl ProfileGraph {
+    #[cfg(feature = "debug_renderer")]
     fn new(
         max_samples: usize,
         short_description: &'static str,
     ) -> Self {
         ProfileGraph {
             max_samples,
             values: VecDeque::new(),
             short_description,
         }
     }
 
+    #[cfg(feature = "debug_renderer")]
     fn push(&mut self, ns: u64) {
         let ms = ns as f64 / 1000000.0;
         if self.values.len() == self.max_samples {
             self.values.pop_back();
         }
         self.values.push_front(ms as f32);
     }
 
@@ -529,16 +546,17 @@ impl ProfileGraph {
 
         if !self.values.is_empty() {
             stats.mean_value = stats.mean_value / self.values.len() as f32;
         }
 
         stats
     }
 
+    #[cfg(feature = "debug_renderer")]
     fn draw_graph(
         &self,
         x: f32,
         y: f32,
         description: &'static str,
         debug_renderer: &mut DebugRenderer,
     ) -> Rect<f32> {
         let size = Size2D::new(600.0, 120.0);
@@ -628,25 +646,28 @@ impl ProfileCounter for ProfileGraph {
         self.short_description
     }
 
     fn value(&self) -> String {
         format!("{:.2}ms", self.stats().mean_value)
     }
 }
 
+#[cfg(feature = "debug_renderer")]
 struct GpuFrame {
     total_time: u64,
     samples: Vec<GpuTimer<GpuProfileTag>>,
 }
 
+#[cfg(feature = "debug_renderer")]
 struct GpuFrameCollection {
     frames: VecDeque<GpuFrame>,
 }
 
+#[cfg(feature = "debug_renderer")]
 impl GpuFrameCollection {
     fn new() -> Self {
         GpuFrameCollection {
             frames: VecDeque::new(),
         }
     }
 
     fn push(&mut self, total_time: u64, samples: Vec<GpuTimer<GpuProfileTag>>) {
@@ -655,16 +676,17 @@ impl GpuFrameCollection {
         }
         self.frames.push_front(GpuFrame {
             total_time,
             samples,
         });
     }
 }
 
+#[cfg(feature = "debug_renderer")]
 impl GpuFrameCollection {
     fn draw(&self, x: f32, y: f32, debug_renderer: &mut DebugRenderer) -> Rect<f32> {
         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);
 
@@ -745,33 +767,37 @@ impl GpuFrameCollection {
                 ColorU::new(255, 255, 0, 255),
             );
         }
 
         bounding_rect
     }
 }
 
+#[cfg(feature = "debug_renderer")]
 struct DrawState {
     x_left: f32,
     y_left: f32,
     x_right: f32,
     y_right: f32,
 }
 
+#[cfg(feature = "debug_renderer")]
 pub struct Profiler {
     draw_state: DrawState,
     backend_time: ProfileGraph,
     compositor_time: ProfileGraph,
     gpu_time: ProfileGraph,
     gpu_frames: GpuFrameCollection,
     ipc_time: ProfileGraph,
 }
 
+#[cfg(feature = "debug_renderer")]
 impl Profiler {
+
     pub fn new() -> Self {
         Profiler {
             draw_state: DrawState {
                 x_left: 0.0,
                 y_left: 0.0,
                 x_right: 0.0,
                 y_right: 0.0,
             },
--- a/gfx/webrender/src/query.rs
+++ b/gfx/webrender/src/query.rs
@@ -1,13 +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/. */
 
 use gleam::gl;
+#[cfg(feature = "debug_renderer")]
 use std::mem;
 use std::rc::Rc;
 
 use device::FrameId;
 
 
 pub trait NamedTag {
     fn get_label(&self) -> &str;
@@ -49,16 +50,17 @@ impl<T> QuerySet<T> {
         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
         })
     }
 
+    #[cfg(feature = "debug_renderer")]
     fn take<F: Fn(&mut T, gl::GLuint)>(&mut self, fun: F) -> Vec<T> {
         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
     }
 }
@@ -152,16 +154,17 @@ impl<T: NamedTag> GpuFrameProfile<T> {
 
         if let Some(query) = self.samplers.add(GpuSampler { tag, count: 0 }) {
             self.gl.begin_query(gl::SAMPLES_PASSED, query);
         }
 
         GpuSampleQuery
     }
 
+    #[cfg(feature = "debug_renderer")]
     fn build_samples(&mut self) -> (FrameId, Vec<GpuTimer<T>>, Vec<GpuSampler<T>>) {
         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)
@@ -228,16 +231,17 @@ impl<T> GpuProfiler<T> {
     pub fn disable_samplers(&mut self) {
         for frame in &mut self.frames {
             frame.disable_samplers();
         }
     }
 }
 
 impl<T: NamedTag> GpuProfiler<T> {
+    #[cfg(feature = "debug_renderer")]
     pub fn build_samples(&mut self) -> (FrameId, Vec<GpuTimer<T>>, Vec<GpuSampler<T>>) {
         self.frames[self.next_frame].build_samples()
     }
 
     pub fn begin_frame(&mut self, frame_id: FrameId) {
         self.frames[self.next_frame].begin_frame(frame_id);
     }
 
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -1,30 +1,38 @@
 /* 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::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, ImageDescriptor, ImageFormat};
-use api::{DeviceSize, PremultipliedColorF};
+use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceSize, ImageDescriptor, ImageFormat};
+#[cfg(feature = "pathfinder")]
+use api::FontRenderMode;
 use box_shadow::{BoxShadowCacheKey};
 use clip::{ClipSource, ClipStore, ClipWorkItem};
 use clip_scroll_tree::CoordinateSystemId;
 use device::TextureFilter;
+#[cfg(feature = "pathfinder")]
+use euclid::{TypedPoint2D, TypedVector2D};
+use glyph_rasterizer::GpuGlyphCacheKey;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use gpu_types::{ImageSource, RasterizationSpace};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
+#[cfg(feature = "pathfinder")]
+use pathfinder_partitioner::mesh::Mesh;
 use prim_store::{PrimitiveIndex, ImageCacheKey};
 #[cfg(feature = "debugger")]
 use print_tree::{PrintTreePrinter};
 use render_backend::FrameId;
 use resource_cache::{CacheItem, ResourceCache};
 use std::{cmp, ops, usize, f32, i32};
 use texture_cache::{TextureCache, TextureCacheHandle};
 use tiling::{RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind};
+#[cfg(feature = "pathfinder")]
+use webrender_api::DevicePixel;
 
 const FLOATS_PER_RENDER_TASK_INFO: usize = 8;
 pub const MAX_BLUR_STD_DEVIATION: f32 = 4.0;
 pub const MIN_DOWNSCALING_RT_SIZE: i32 = 128;
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -71,17 +79,17 @@ impl RenderTaskTree {
             self.max_depth(*child, depth, max_depth);
         }
     }
 
     pub fn assign_to_passes(
         &self,
         id: RenderTaskId,
         pass_index: usize,
-        passes: &mut Vec<RenderPass>,
+        passes: &mut [RenderPass],
     ) {
         debug_assert_eq!(self.frame_id, id.1);
         let task = &self.tasks[id.0 as usize];
 
         for child in &task.children {
             self.assign_to_passes(*child, pass_index - 1, passes);
         }
 
@@ -168,19 +176,17 @@ pub struct ClipRegionTask {
     pub clip_data_address: GpuCacheAddress,
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PictureTask {
     pub prim_index: PrimitiveIndex,
-    pub target_kind: RenderTargetKind,
     pub content_origin: DeviceIntPoint,
-    pub color: PremultipliedColorF,
     pub uv_rect_handle: GpuCacheHandle,
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BlurTask {
     pub blur_std_deviation: f32,
@@ -191,16 +197,35 @@ pub struct BlurTask {
 impl BlurTask {
     #[cfg(feature = "debugger")]
     fn print_with<T: PrintTreePrinter>(&self, pt: &mut T) {
         pt.add_item(format!("std deviation: {}", self.blur_std_deviation));
         pt.add_item(format!("target: {:?}", self.target_kind));
     }
 }
 
+#[cfg(feature = "pathfinder")]
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct GlyphTask {
+    /// After job building, this becomes `None`.
+    pub mesh: Option<Mesh>,
+    pub origin: DeviceIntPoint,
+    pub subpixel_offset: TypedPoint2D<f32, DevicePixel>,
+    pub render_mode: FontRenderMode,
+    pub embolden_amount: TypedVector2D<f32, DevicePixel>,
+}
+
+#[cfg(not(feature = "pathfinder"))]
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct GlyphTask;
+
 // 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,
     },
@@ -227,16 +252,18 @@ pub struct RenderTaskData {
 #[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),
+    #[allow(dead_code)]
+    Glyph(GlyphTask),
     Readback(DeviceIntRect),
     Scaling(RenderTargetKind),
     Blit(BlitTask),
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -259,33 +286,28 @@ pub struct RenderTask {
     pub clear_mode: ClearMode,
     pub saved_index: Option<SavedTargetIndex>,
 }
 
 impl RenderTask {
     pub fn new_picture(
         location: RenderTaskLocation,
         prim_index: PrimitiveIndex,
-        target_kind: RenderTargetKind,
         content_origin: DeviceIntPoint,
-        color: PremultipliedColorF,
-        clear_mode: ClearMode,
         children: Vec<RenderTaskId>,
     ) -> Self {
         RenderTask {
             children,
             location,
             kind: RenderTaskKind::Picture(PictureTask {
                 prim_index,
-                target_kind,
                 content_origin,
-                color,
                 uv_rect_handle: GpuCacheHandle::new(),
             }),
-            clear_mode,
+            clear_mode: ClearMode::Transparent,
             saved_index: None,
         }
     }
 
     pub fn new_readback(screen_rect: DeviceIntRect) -> Self {
         RenderTask {
             children: Vec::new(),
             location: RenderTaskLocation::Dynamic(None, screen_rect.size),
@@ -357,16 +379,17 @@ impl RenderTask {
                         // sized box-shadow rect.
                         info.cache_item = resource_cache.request_render_task(
                             RenderTaskCacheKey {
                                 size: cache_size,
                                 kind: RenderTaskCacheKeyKind::BoxShadow(cache_key),
                             },
                             gpu_cache,
                             render_tasks,
+                            None,
                             |render_tasks| {
                                 // Draw the rounded rect.
                                 let mask_task = RenderTask::new_rounded_rect_mask(
                                     cache_size,
                                     clip_data_address,
                                 );
 
                                 let mask_task_id = render_tasks.add(mask_task);
@@ -512,16 +535,39 @@ impl RenderTask {
             clear_mode: match target_kind {
                 RenderTargetKind::Color => ClearMode::Transparent,
                 RenderTargetKind::Alpha => ClearMode::One,
             },
             saved_index: None,
         }
     }
 
+    #[cfg(feature = "pathfinder")]
+    pub fn new_glyph(location: RenderTaskLocation,
+                     mesh: Mesh,
+                     origin: &DeviceIntPoint,
+                     subpixel_offset: &TypedPoint2D<f32, DevicePixel>,
+                     render_mode: FontRenderMode,
+                     embolden_amount: &TypedVector2D<f32, DevicePixel>)
+                     -> Self {
+        RenderTask {
+            children: vec![],
+            location: location,
+            kind: RenderTaskKind::Glyph(GlyphTask {
+                mesh: Some(mesh),
+                origin: *origin,
+                subpixel_offset: *subpixel_offset,
+                render_mode: render_mode,
+                embolden_amount: *embolden_amount,
+            }),
+            clear_mode: ClearMode::Transparent,
+            saved_index: None,
+        }
+    }
+
     // 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.
@@ -555,16 +601,19 @@ impl RenderTask {
             RenderTaskKind::VerticalBlur(ref task) |
             RenderTaskKind::HorizontalBlur(ref task) => {
                 [
                     task.blur_std_deviation,
                     0.0,
                     0.0,
                 ]
             }
+            RenderTaskKind::Glyph(_) => {
+                [1.0, 0.0, 0.0]
+            }
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) => {
                 [0.0; 3]
             }
         };
 
         let (target_rect, target_index) = self.get_target_rect();
@@ -591,17 +640,18 @@ impl RenderTask {
             RenderTaskKind::VerticalBlur(ref info) |
             RenderTaskKind::HorizontalBlur(ref info) => {
                 &info.uv_rect_handle
             }
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) |
-            RenderTaskKind::CacheMask(..) => {
+            RenderTaskKind::CacheMask(..) |
+            RenderTaskKind::Glyph(..) => {
                 panic!("texture handle not supported for this task kind");
             }
         }
     }
 
     pub fn get_dynamic_size(&self) -> DeviceIntSize {
         match self.location {
             RenderTaskLocation::Fixed(..) => DeviceIntSize::zero(),
@@ -650,22 +700,26 @@ impl RenderTask {
                 RenderTargetKind::Alpha
             }
 
             RenderTaskKind::VerticalBlur(ref task_info) |
             RenderTaskKind::HorizontalBlur(ref task_info) => {
                 task_info.target_kind
             }
 
+            RenderTaskKind::Glyph(..) => {
+                RenderTargetKind::Color
+            }
+
             RenderTaskKind::Scaling(target_kind) => {
                 target_kind
             }
 
-            RenderTaskKind::Picture(ref task_info) => {
-                task_info.target_kind
+            RenderTaskKind::Picture(..) => {
+                RenderTargetKind::Color
             }
 
             RenderTaskKind::Blit(..) => {
                 RenderTargetKind::Color
             }
         }
     }
 
@@ -678,17 +732,18 @@ impl RenderTask {
     pub fn is_shared(&self) -> bool {
         match self.kind {
             RenderTaskKind::Picture(..) |
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::HorizontalBlur(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::ClipRegion(..) |
-            RenderTaskKind::Blit(..) => false,
+            RenderTaskKind::Blit(..) |
+            RenderTaskKind::Glyph(..) => false,
 
             // TODO(gw): For now, we've disabled the shared clip mask
             //           optimization. It's of dubious value in the
             //           future once we start to cache clip tasks anyway.
             //           I have left shared texture support here though,
             //           just in case we want it in the future.
             RenderTaskKind::CacheMask(..) => false,
         }
@@ -707,17 +762,18 @@ impl RenderTask {
             }
             RenderTaskKind::Picture(ref mut info) => {
                 &mut info.uv_rect_handle
             }
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) |
             RenderTaskKind::Blit(..) |
             RenderTaskKind::ClipRegion(..) |
-            RenderTaskKind::CacheMask(..) => {
+            RenderTaskKind::CacheMask(..) |
+            RenderTaskKind::Glyph(..) => {
                 return;
             }
         };
 
         if let Some(mut request) = gpu_cache.request(cache_handle) {
             let image_source = ImageSource {
                 p0: target_rect.origin.to_f32(),
                 p1: target_rect.bottom_right().to_f32(),
@@ -728,17 +784,16 @@ impl RenderTask {
         }
     }
 
     #[cfg(feature = "debugger")]
     pub fn print_with<T: PrintTreePrinter>(&self, pt: &mut T, tree: &RenderTaskTree) -> bool {
         match self.kind {
             RenderTaskKind::Picture(ref task) => {
                 pt.new_level(format!("Picture of {:?}", task.prim_index));
-                pt.add_item(format!("kind: {:?}", task.target_kind));
             }
             RenderTaskKind::CacheMask(ref task) => {
                 pt.new_level(format!("CacheMask with {} clips", task.clips.len()));
                 pt.add_item(format!("rect: {:?}", task.actual_rect));
             }
             RenderTaskKind::ClipRegion(..) => {
                 pt.new_level("ClipRegion".to_owned());
             }
@@ -757,16 +812,19 @@ impl RenderTask {
             RenderTaskKind::Scaling(ref kind) => {
                 pt.new_level("Scaling".to_owned());
                 pt.add_item(format!("kind: {:?}", kind));
             }
             RenderTaskKind::Blit(ref task) => {
                 pt.new_level("Blit".to_owned());
                 pt.add_item(format!("source: {:?}", task.source));
             }
+            RenderTaskKind::Glyph(..) => {
+                pt.new_level("Glyph".to_owned());
+            }
         }
 
         pt.add_item(format!("clear to: {:?}", self.clear_mode));
 
         for &child_id in &self.children {
             if tree[child_id].print_with(pt, tree) {
                 pt.add_item(format!("self: {:?}", child_id))
             }
@@ -785,25 +843,27 @@ impl RenderTask {
             }
             RenderTaskLocation::TextureCache(..) => {
                 panic!("Unable to mark a permanently cached task for saving!");
             }
         }
     }
 }
 
-#[derive(Debug, Hash, PartialEq, Eq)]
+#[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),
+    #[allow(dead_code)]
+    Glyph(GpuGlyphCacheKey),
 }
 
-#[derive(Debug, Hash, PartialEq, Eq)]
+#[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,
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -854,31 +914,33 @@ impl RenderTaskCache {
     }
 
     pub fn request_render_task<F>(
         &mut self,
         key: RenderTaskCacheKey,
         texture_cache: &mut TextureCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
+        user_data: Option<[f32; 3]>,
         mut f: F,
-    ) -> CacheItem where F: FnMut(&mut RenderTaskTree) -> (RenderTaskId, bool) {
+    ) -> Result<CacheItem, ()>
+         where F: FnMut(&mut RenderTaskTree) -> Result<(RenderTaskId, bool), ()> {
         // Get the texture cache handle for this cache key,
         // or create one.
         let cache_entry = self.entries
                               .entry(key)
                               .or_insert(RenderTaskCacheEntry {
                                   handle: TextureCacheHandle::new(),
                               });
 
-        // Check if this texture cache handle is valie.
+        // Check if this texture cache handle is valid.
         if texture_cache.request(&mut cache_entry.handle, gpu_cache) {
             // Invoke user closure to get render task chain
             // to draw this into the texture cache.
-            let (render_task_id, is_opaque) = f(render_tasks);
+            let (render_task_id, is_opaque) = try!(f(render_tasks));
             let render_task = &mut render_tasks[render_task_id];
 
             // Select the right texture page to allocate from.
             let image_format = match render_task.target_kind() {
                 RenderTargetKind::Color => ImageFormat::BGRA8,
                 RenderTargetKind::Alpha => ImageFormat::R8,
             };
 
@@ -904,17 +966,17 @@ impl RenderTaskCache {
 
             // Allocate space in the texture cache, but don't supply
             // and CPU-side data to be uploaded.
             texture_cache.update(
                 &mut cache_entry.handle,
                 descriptor,
                 TextureFilter::Linear,
                 None,
-                [0.0; 3],
+                user_data.unwrap_or([0.0; 3]),
                 None,
                 gpu_cache,
                 None,
             );
 
             // 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
@@ -926,18 +988,37 @@ impl RenderTaskCache {
                 texture_id,
                 texture_layer,
                 uv_rect.to_i32()
             );
         }
 
         // Finally, return the texture cache handle that we know
         // is now up to date.
+        Ok(texture_cache.get(&cache_entry.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 cache_entry = self.entries.get(key).unwrap();
         texture_cache.get(&cache_entry.handle)
     }
+
+    #[allow(dead_code)]
+    pub fn cache_item_is_allocated_for_render_task(&self,
+                                                   texture_cache: &TextureCache,
+                                                   key: &RenderTaskCacheKey)
+                                                   -> bool {
+        let cache_entry = self.entries.get(key).unwrap();
+        texture_cache.is_allocated(&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 tree, so
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -4,81 +4,94 @@
 
 //! The webrender API.
 //!
 //! The `webrender::renderer` module provides the interface to webrender, which
 //! is accessible through [`Renderer`][renderer]
 //!
 //! [renderer]: struct.Renderer.html
 
-use api::{BlobImageRenderer, ColorF, ColorU, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
+use api::{BlobImageRenderer, ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentId, Epoch, ExternalImageId};
 use api::{ExternalImageType, FontRenderMode, ImageFormat, PipelineId};
 use api::{RenderApiSender, RenderNotifier, TexelRect, TextureTarget};
 use api::{channel};
-#[cfg(not(feature = "debugger"))]
-use api::ApiMsg;
 use api::DebugCommand;
-#[cfg(not(feature = "debugger"))]
-use api::channel::MsgSender;
 use api::channel::PayloadReceiverHelperMethods;
 use batch::{BatchKey, BatchKind, BatchTextures, BrushBatchKind, TransformBatchKind};
 #[cfg(any(feature = "capture", feature = "replay"))]
 use capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
 use debug_colors;
-use debug_render::DebugRenderer;
-#[cfg(feature = "debugger")]
-use debug_server::{self, DebugServer};
 use device::{DepthFunction, Device, FrameId, Program, UploadMethod, Texture, PBO};
 use device::{ExternalTexture, FBOId, TextureSlot};
 use device::{FileWatcherHandler, ShaderError, TextureFilter,
              VertexUsageHint, VAO, VBO, CustomVAO};
 use device::{ProgramCache, ReadPixelsFormat};
 use euclid::{rect, Transform3D};
 use frame_builder::FrameBuilderConfig;
 use gleam::gl;
 use glyph_rasterizer::{GlyphFormat, GlyphRasterizer};
 use gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
+#[cfg(feature = "pathfinder")]
+use gpu_glyph_renderer::GpuGlyphRenderer;
 use gpu_types::PrimitiveInstance;
 use internal_types::{SourceTexture, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, ResourceCacheError};
 use internal_types::{CacheTextureId, DebugOutput, FastHashMap, RenderedDocument, ResultMsg};
 use internal_types::{TextureUpdateList, TextureUpdateOp, TextureUpdateSource};
 use internal_types::{RenderTargetInfo, SavedTargetIndex};
 use prim_store::DeferredResolve;
-use profiler::{BackendProfileCounters, FrameProfileCounters, Profiler};
-use profiler::{GpuProfileTag, RendererProfileCounters, RendererProfileTimers};
-use query::{GpuProfiler, GpuTimer};
+use profiler::{BackendProfileCounters, FrameProfileCounters,
+               GpuProfileTag, RendererProfileCounters, RendererProfileTimers};
+use query::GpuProfiler;
 use rayon::{ThreadPool, ThreadPoolBuilder};
 use record::ApiRecordingReceiver;
 use render_backend::RenderBackend;
 use scene_builder::SceneBuilder;
 use shade::Shaders;
 use render_task::{RenderTask, RenderTaskKind, RenderTaskTree};
 use resource_cache::ResourceCache;
 
-#[cfg(feature = "debugger")]
-use serde_json;
 use std;
 use std::cmp;
 use std::collections::VecDeque;
 use std::collections::hash_map::Entry;
 use std::f32;
 use std::mem;
 use std::path::PathBuf;
 use std::rc::Rc;
 use std::sync::Arc;
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::thread;
 use texture_cache::TextureCache;
 use thread_profiler::{register_thread_with_profiler, write_profile};
 use tiling::{AlphaRenderTarget, ColorRenderTarget};
 use tiling::{BlitJob, BlitJobSource, RenderPass, RenderPassKind, RenderTargetList};
 use tiling::{Frame, RenderTarget, ScalingInfo, TextureCacheRenderTarget};
+#[cfg(not(feature = "pathfinder"))]
+use tiling::GlyphJob;
 use time::precise_time_ns;
 
+cfg_if! {
+    if #[cfg(feature = "debugger")] {
+        use serde_json;
+        use debug_server::{self, DebugServer};
+    } else {
+        use api::ApiMsg;
+        use api::channel::MsgSender;
+    }
+}
+
+cfg_if! {
+    if #[cfg(feature = "debug_renderer")] {
+        use api::ColorU;
+        use debug_render::DebugRenderer;
+        use profiler::Profiler;
+        use query::GpuTimer;
+    }
+}
 
 pub const MAX_VERTEX_TEXTURE_WIDTH: usize = 1024;
 /// 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.
@@ -123,20 +136,16 @@ const GPU_TAG_SETUP_TARGET: GpuProfileTa
 const GPU_TAG_SETUP_DATA: GpuProfileTag = GpuProfileTag {
     label: "data init",
     color: debug_colors::LIGHTGREY,
 };
 const GPU_TAG_PRIM_IMAGE: GpuProfileTag = GpuProfileTag {
     label: "Image",
     color: debug_colors::GREEN,
 };
-const GPU_TAG_PRIM_HW_COMPOSITE: GpuProfileTag = GpuProfileTag {
-    label: "HwComposite",
-    color: debug_colors::DODGERBLUE,
-};
 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,
 };
@@ -195,17 +204,16 @@ impl TransformBatchKind {
         }
     }
 }
 
 impl BatchKind {
     #[cfg(feature = "debugger")]
     fn debug_name(&self) -> &'static str {
         match *self {
-            BatchKind::HardwareComposite => "HardwareComposite",
             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)",
@@ -214,17 +222,16 @@ impl BatchKind {
                 }
             }
             BatchKind::Transformable(_, batch_kind) => batch_kind.debug_name(),
         }
     }
 
     fn sampler_tag(&self) -> GpuProfileTag {
         match *self {
-            BatchKind::HardwareComposite => GPU_TAG_PRIM_HW_COMPOSITE,
             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,
@@ -300,17 +307,17 @@ pub(crate) enum TextureSampler {
     // A special sampler that is bound to the A8 output of
     // the *first* pass. Items rendered in this target are
     // available as inputs to tasks in any subsequent pass.
     SharedCacheA8,
     LocalClipRects
 }
 
 impl TextureSampler {
-    fn color(n: usize) -> 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.");
             }
         }
@@ -435,23 +442,109 @@ pub(crate) mod desc {
             VertexAttribute {
                 name: "aValue",
                 count: 4,
                 kind: VertexAttributeKind::F32,
             },
         ],
         instance_attributes: &[],
     };
+
+    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,
+            },
+        ],
+    };
 }
 
 #[derive(Debug, Copy, Clone)]
 pub(crate) enum VertexArrayKind {
     Primitive,
     Blur,
     Clip,
+    VectorStencil,
+    VectorCover,
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub enum GraphicsApi {
     OpenGL,
 }
 
 #[derive(Clone, Debug)]
@@ -491,16 +584,17 @@ pub enum RendererKind {
 
 #[derive(Debug)]
 pub struct GpuProfile {
     pub frame_id: FrameId,
     pub paint_time_ns: u64,
 }
 
 impl GpuProfile {
+    #[cfg(feature = "debug_renderer")]
     fn new<T>(frame_id: FrameId, timers: &[GpuTimer<T>]) -> GpuProfile {
         let mut paint_time_ns = 0;
         for timer in timers {
             paint_time_ns += timer.time_ns;
         }
         GpuProfile {
             frame_id,
             paint_time_ns,
@@ -527,16 +621,29 @@ impl CpuProfile {
             frame_id,
             backend_time_ns,
             composite_time_ns,
             draw_calls,
         }
     }
 }
 
+#[cfg(not(feature = "pathfinder"))]
+pub struct GpuGlyphRenderer;
+
+#[cfg(not(feature = "pathfinder"))]
+impl GpuGlyphRenderer {
+    fn new(_: &mut Device, _: &VAO, _: bool) -> Result<GpuGlyphRenderer, RendererError> {
+        Ok(GpuGlyphRenderer)
+    }
+}
+
+#[cfg(not(feature = "pathfinder"))]
+struct StenciledGlyphPage;
+
 struct ActiveTexture {
     texture: Texture,
     saved_index: Option<SavedTargetIndex>,
     is_shared: bool,
 }
 
 struct SourceTextureResolver {
     /// A vector for fast resolves of texture cache IDs to
@@ -571,17 +678,17 @@ struct SourceTextureResolver {
     /// General pool of render targets.
     render_target_pool: Vec<Texture>,
 }
 
 impl SourceTextureResolver {
     fn new(device: &mut Device) -> SourceTextureResolver {
         let mut dummy_cache_texture = device
             .create_texture(TextureTarget::Array, ImageFormat::BGRA8);
-        device.init_texture(
+        device.init_texture::<u8>(
             &mut dummy_cache_texture,
             1,
             1,
             TextureFilter::Linear,
             None,
             1,
             None,
         );
@@ -855,17 +962,17 @@ impl CacheTexture {
         let old_size = self.texture.get_dimensions();
         let new_size = DeviceUintSize::new(MAX_VERTEX_TEXTURE_WIDTH as _, max_height);
 
         match self.bus {
             CacheBus::PixelBuffer { ref mut rows, .. } => {
                 if max_height > old_size.height {
                     // Create a f32 texture that can be used for the vertex shader
                     // to fetch data from.
-                    device.init_texture(
+                    device.init_texture::<u8>(
                         &mut self.texture,
                         new_size.width,
                         new_size.height,
                         TextureFilter::Nearest,
                         None,
                         1,
                         None,
                     );
@@ -889,17 +996,17 @@ impl CacheTexture {
                     device.allocate_vbo(buf_position, total_block_count, VertexUsageHint::Stream);
                     device.allocate_vbo(buf_value,    total_block_count, VertexUsageHint::Stream);
                 }
 
                 if new_size.height > old_size.height || GPU_CACHE_RESIZE_TEST {
                     if old_size.height > 0 {
                         device.resize_renderable_texture(&mut self.texture, new_size);
                     } else {
-                        device.init_texture(
+                        device.init_texture::<u8>(
                             &mut self.texture,
                             new_size.width,
                             new_size.height,
                             TextureFilter::Nearest,
                             Some(RenderTargetInfo {
                                 has_depth: false,
                             }),
                             1,
@@ -1071,17 +1178,17 @@ impl VertexDataTexture {
         let needed_height = (data.len() / items_per_row) as u32;
 
         // Determine if the texture needs to be resized.
         let texture_size = self.texture.get_dimensions();
 
         if needed_height > texture_size.height {
             let new_height = (needed_height + 127) & !127;
 
-            device.init_texture(
+            device.init_texture::<u8>(
                 &mut self.texture,
                 width,
                 new_height,
                 TextureFilter::Nearest,
                 None,
                 1,
                 None,
             );
@@ -1121,43 +1228,70 @@ struct FrameOutput {
 
 #[derive(PartialEq)]
 struct TargetSelector {
     size: DeviceUintSize,
     num_layers: usize,
     format: ImageFormat,
 }
 
+#[cfg(feature = "debug_renderer")]
+struct LazyInitializedDebugRenderer {
+    debug_renderer: Option<DebugRenderer>,
+}
+
+#[cfg(feature = "debug_renderer")]
+impl LazyInitializedDebugRenderer {
+    pub fn new() -> Self {
+        Self {
+            debug_renderer: None,
+        }
+    }
+
+    pub fn get_mut<'a>(&'a mut self, device: &mut Device) -> &'a mut DebugRenderer {
+        self.debug_renderer.get_or_insert_with(|| DebugRenderer::new(device))
+    }
+
+    pub fn deinit(self, device: &mut Device) {
+        if let Some(debug_renderer) = self.debug_renderer {
+            debug_renderer.deinit(device);
+        }
+    }
+}
 
 /// The renderer is responsible for submitting to the GPU the work prepared by the
 /// RenderBackend.
 pub struct Renderer {
     result_rx: Receiver<ResultMsg>,
     debug_server: DebugServer,
-    device: Device,
+    pub device: Device,
     pending_texture_updates: Vec<TextureUpdateList>,
     pending_gpu_cache_updates: Vec<GpuCacheUpdateList>,
     pending_shader_updates: Vec<PathBuf>,
     active_documents: Vec<(DocumentId, RenderedDocument)>,
 
     shaders: Shaders,
 
+    pub gpu_glyph_renderer: GpuGlyphRenderer,
+
     max_texture_size: u32,
     max_recorded_profiles: usize,
 
     clear_color: Option<ColorF>,
     enable_clear_scissor: bool,
-    debug: DebugRenderer,
+    #[cfg(feature = "debug_renderer")]
+    debug: LazyInitializedDebugRenderer,
     debug_flags: DebugFlags,
     backend_profile_counters: BackendProfileCounters,
     profile_counters: RendererProfileCounters,
+    #[cfg(feature = "debug_renderer")]
     profiler: Profiler,
     last_time: u64,
 
-    gpu_profile: GpuProfiler<GpuProfileTag>,
+    pub gpu_profile: GpuProfiler<GpuProfileTag>,
     prim_vao: VAO,
     blur_vao: VAO,
     clip_vao: VAO,
 
     node_data_texture: VertexDataTexture,
     local_clip_rects_texture: VertexDataTexture,
     render_task_texture: VertexDataTexture,
     gpu_cache_texture: CacheTexture,
@@ -1182,17 +1316,17 @@ pub struct Renderer {
     /// Optional trait object that allows the client
     /// application to provide a texture handle to
     /// copy the WR output to.
     output_image_handler: Option<Box<OutputImageHandler>>,
 
     // Currently allocated FBOs for output frames.
     output_targets: FastHashMap<u32, FrameOutput>,
 
-    renderer_errors: Vec<RendererError>,
+    pub renderer_errors: Vec<RendererError>,
 
     /// List of profile results from previous frames. Can be retrieved
     /// via get_frame_profiles().
     cpu_profiles: VecDeque<CpuProfile>,
     gpu_profiles: VecDeque<GpuProfile>,
 
     #[cfg(feature = "capture")]
     read_fbo: FBOId,
@@ -1380,18 +1514,16 @@ impl Renderer {
                 Some(&dither_matrix),
             );
 
             Some(texture)
         } else {
             None
         };
 
-        let debug_renderer = DebugRenderer::new(&mut device);
-
         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] },
@@ -1400,19 +1532,22 @@ impl Renderer {
             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 gpu_glyph_renderer = try!(GpuGlyphRenderer::new(&mut device,
+                                                            &prim_vao,
+                                                            options.precache_shaders));
+
         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 texture_cache_upload_pbo = device.create_pbo();
 
         let texture_resolver = SourceTextureResolver::new(&mut device);
 
         let node_data_texture = VertexDataTexture::new(&mut device);
         let local_clip_rects_texture = VertexDataTexture::new(&mut device);
         let render_task_texture = VertexDataTexture::new(&mut device);
 
@@ -1533,27 +1668,30 @@ impl Renderer {
             result_rx,
             debug_server,
             device,
             active_documents: Vec::new(),
             pending_texture_updates: Vec::new(),
             pending_gpu_cache_updates: Vec::new(),
             pending_shader_updates: Vec::new(),
             shaders,
-            debug: debug_renderer,
+            #[cfg(feature = "debug_renderer")]
+            debug: LazyInitializedDebugRenderer::new(),
             debug_flags,
             backend_profile_counters: BackendProfileCounters::new(),
             profile_counters: RendererProfileCounters::new(),
+            #[cfg(feature = "debug_renderer")]
             profiler: Profiler::new(),
             max_texture_size: max_device_size,
             max_recorded_profiles: options.max_recorded_profiles,
             clear_color: options.clear_color,
             enable_clear_scissor: options.enable_clear_scissor,
             last_time: 0,
             gpu_profile,
+            gpu_glyph_renderer,
             prim_vao,
             blur_vao,
             clip_vao,
             node_data_texture,
             local_clip_rects_texture,
             render_task_texture,
             pipeline_info: PipelineInfo::default(),
             dither_matrix_texture,
@@ -2018,16 +2156,17 @@ impl Renderer {
             self.last_time = precise_time_ns();
             return Ok(RendererStats::empty());
         }
 
         let mut stats = RendererStats::empty();
         let mut frame_profiles = Vec::new();
         let mut profile_timers = RendererProfileTimers::new();
 
+        #[cfg(feature = "debug_renderer")]
         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 {
@@ -2132,41 +2271,48 @@ impl Renderer {
                 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(framebuffer_size) = framebuffer_size {
-                //TODO: take device/pixel ratio into equation?
-                let screen_fraction = 1.0 / framebuffer_size.to_f32().area();
-                self.profiler.draw_profile(
-                    &frame_profiles,
-                    &self.backend_profile_counters,
-                    &self.profile_counters,
-                    &mut profile_timers,
-                    &profile_samplers,
-                    screen_fraction,
-                    &mut self.debug,
-                    self.debug_flags.contains(DebugFlags::COMPACT_PROFILER),
-                );
+        #[cfg(feature = "debug_renderer")]
+        {
+            if self.debug_flags.contains(DebugFlags::PROFILER_DBG) {
+                if let Some(framebuffer_size) = framebuffer_size {
+                    //TODO: take device/pixel ratio into equation?
+                    let screen_fraction = 1.0 / framebuffer_size.to_f32().area();
+                    self.profiler.draw_profile(
+                        &frame_profiles,
+                        &self.backend_profile_counters,
+                        &self.profile_counters,
+                        &mut profile_timers,
+                        &profile_samplers,
+                        screen_fraction,
+                        self.debug.get_mut(&mut self.device),
+                        self.debug_flags.contains(DebugFlags::COMPACT_PROFILER),
+                    );
+                }
             }
         }
 
         self.backend_profile_counters.reset();
         self.profile_counters.reset();
         self.profile_counters.frame_counter.inc();
 
         profile_timers.cpu_time.profile(|| {
             let _gm = self.gpu_profile.start_marker("end frame");
             self.gpu_profile.end_frame();
-            self.debug.render(&mut self.device, framebuffer_size);
+            #[cfg(feature = "debug_renderer")]
+            {
+                self.debug.get_mut(&mut self.device)
+                          .render(&mut self.device, framebuffer_size);
+            }
             self.device.end_frame();
         });
         self.last_time = current_time;
 
         if self.renderer_errors.is_empty() {
             Ok(stats)
         } else {
             Err(mem::replace(&mut self.renderer_errors, Vec::new()))
@@ -2180,17 +2326,17 @@ impl Renderer {
     }
 
     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 {
+        if gpu_cache_height != 0 && GPU_CACHE_RESIZE_TEST {
             self.pending_gpu_cache_updates.push(GpuCacheUpdateList {
                 frame_id: FrameId::new(0),
                 height: gpu_cache_height,
                 blocks: vec![[1f32; 4].into()],
                 updates: Vec::new(),
             });
         }
 
@@ -2201,17 +2347,17 @@ impl Renderer {
                 (count + list.blocks.len(), cmp::max(height, list.height))
             });
 
         if max_requested_height > self.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
+        // 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,
         );
 
@@ -2267,17 +2413,17 @@ impl Renderer {
                             self.texture_resolver.cache_texture_map.push(texture);
                         }
                         let texture =
                             &mut self.texture_resolver.cache_texture_map[cache_texture_index];
                         assert_eq!(texture.get_format(), format);
 
                         // Ensure no PBO is bound when creating the texture storage,
                         // or GL will attempt to read data from there.
-                        self.device.init_texture(
+                        self.device.init_texture::<u8>(
                             texture,
                             width,
                             height,
                             filter,
                             render_target,
                             layer_count,
                             None,
                         );
@@ -2319,32 +2465,34 @@ impl Renderer {
                                         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.
                                         let dummy_data: Vec<u8> = vec![255; total_size as usize];
                                         uploader.upload(rect, layer_index, stride, &dummy_data);
                                     }
-                                    _ => panic!("No external buffer found"),
+                                    ExternalImageSource::NativeTexture(eid) => {
+                                        panic!("Unexpected external texture {:?} for the texture cache update of {:?}", eid, id);
+                                    }
                                 };
                                 handler.unlock(id, channel_index);
                             }
                         }
                     }
                     TextureUpdateOp::Free => {
                         let texture = &mut self.texture_resolver.cache_texture_map[update.id.0];
                         self.device.free_texture_storage(texture);
                     }
                 }
             }
         }
     }
 
-    fn draw_instanced_batch<T>(
+    pub(crate) fn draw_instanced_batch<T>(
         &mut self,
         data: &[T],
         vertex_array_kind: VertexArrayKind,
         textures: &BatchTextures,
         stats: &mut RendererStats,
     ) {
         for i in 0 .. textures.colors.len() {
             self.texture_resolver.bind(
@@ -2354,21 +2502,30 @@ impl Renderer {
             );
         }
 
         // 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);
         }
 
-        let vao = match vertex_array_kind {
-            VertexArrayKind::Primitive => &self.prim_vao,
-            VertexArrayKind::Clip => &self.clip_vao,
-            VertexArrayKind::Blur => &self.blur_vao,
-        };
+        self.draw_instanced_batch_with_previously_bound_textures(data, vertex_array_kind, stats)
+    }
+
+    pub(crate) fn draw_instanced_batch_with_previously_bound_textures<T>(
+        &mut self,
+        data: &[T],
+        vertex_array_kind: VertexArrayKind,
+        stats: &mut RendererStats,
+    ) {
+        let vao = get_vao(vertex_array_kind,
+                          &self.prim_vao,
+                          &self.clip_vao,
+                          &self.blur_vao,
+                          &self.gpu_glyph_renderer);
 
         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);
@@ -3127,38 +3284,52 @@ impl Renderer {
     fn draw_texture_cache_target(
         &mut self,
         texture: &SourceTexture,
         layer: i32,
         target: &TextureCacheRenderTarget,
         render_tasks: &RenderTaskTree,
         stats: &mut RendererStats,
     ) {
-        let projection = {
+        let (target_size, projection) = {
             let texture = self.texture_resolver
                 .resolve(texture)
                 .expect("BUG: invalid target texture");
             let target_size = texture.get_dimensions();
-
-            self.device
-                .bind_draw_target(Some((texture, layer)), Some(target_size));
-            self.device.disable_depth();
-            self.device.disable_depth_write();
-            self.device.set_blend(false);
-
-            Transform3D::ortho(
+            let projection = Transform3D::ortho(
                 0.0,
                 target_size.width as f32,
                 0.0,
                 target_size.height as f32,
                 ORTHO_NEAR_PLANE,
                 ORTHO_FAR_PLANE,
-            )
+            );
+            (target_size, projection)
         };
 
+        self.device.disable_depth();
+        self.device.disable_depth_write();
+
+        self.device.set_blend(false);
+
+        // Handle any Pathfinder glyphs.
+        let stencil_page = self.stencil_glyphs(&target.glyphs, &projection, &target_size, stats);
+
+        {
+            let texture = self.texture_resolver
+                .resolve(texture)
+                .expect("BUG: invalid target texture");
+            self.device
+                .bind_draw_target(Some((texture, layer)), Some(target_size));
+        }
+
+        self.device.disable_depth();
+        self.device.disable_depth_write();
+        self.device.set_blend(false);
+
         // Handle any blits to this texture from child tasks.
         self.handle_blits(&target.blits, render_tasks);
 
         // Draw any blurs for this target.
         if !target.horizontal_blurs.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
 
             self.shaders.cs_blur_a8
@@ -3166,18 +3337,39 @@ impl Renderer {
 
             self.draw_instanced_batch(
                 &target.horizontal_blurs,
                 VertexArrayKind::Blur,
                 &BatchTextures::no_texture(),
                 stats,
             );
         }
+
+        // Blit any Pathfinder glyphs to the cache texture.
+        if let Some(stencil_page) = stencil_page {
+            self.cover_glyphs(stencil_page, &projection, stats);
+        }
     }
 
+    #[cfg(not(feature = "pathfinder"))]
+    fn stencil_glyphs(&mut self,
+                      _: &[GlyphJob],
+                      _: &Transform3D<f32>,
+                      _: &DeviceUintSize,
+                      _: &mut RendererStats)
+                      -> Option<StenciledGlyphPage> {
+        None
+    }
+
+    #[cfg(not(feature = "pathfinder"))]
+    fn cover_glyphs(&mut self,
+                    _: StenciledGlyphPage,
+                    _: &Transform3D<f32>,
+                    _: &mut RendererStats) {}
+
     fn update_deferred_resolves(&mut self, deferred_resolves: &[DeferredResolve]) -> Option<GpuCacheUpdateList> {
         // 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;
         }
@@ -3302,17 +3494,17 @@ impl Renderer {
             }
             None => {
                 counters.targets_created.inc();
                 // finally, give up and create a new one
                 self.device.create_texture(TextureTarget::Array, list.format)
             }
         };
 
-        self.device.init_texture(
+        self.device.init_texture::<u8>(
             &mut texture,
             list.max_size.width,
             list.max_size.height,
             TextureFilter::Linear,
             Some(RenderTargetInfo {
                 has_depth: list.needs_depth(),
             }),
             list.targets.len() as _,
@@ -3504,33 +3696,36 @@ impl Renderer {
             );
         }
 
         self.texture_resolver.end_frame();
         if let Some(framebuffer_size) = framebuffer_size {
             self.draw_render_target_debug(framebuffer_size);
             self.draw_texture_cache_debug(framebuffer_size);
         }
+
+        #[cfg(feature = "debug_renderer")]
         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;
     }
 
+    #[cfg(feature = "debug_renderer")]
     pub fn debug_renderer<'b>(&'b mut self) -> &'b mut DebugRenderer {
-        &mut self.debug
+        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) {
@@ -3651,38 +3846,41 @@ impl Renderer {
 
                 let dest_rect = rect(x, y, size, size);
                 self.device.blit_render_target(src_rect, dest_rect);
                 i += 1;
             }
         }
     }
 
+    #[cfg(feature = "debug_renderer")]
     fn draw_epoch_debug(&mut self) {
         if !self.debug_flags.contains(DebugFlags::EPOCHS) {
             return;
         }
 
-        let dy = self.debug.line_height();
+        let debug_renderer = self.debug.get_mut(&mut self.device);
+
+        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, epoch) in  &self.pipeline_info.epochs {
             y += dy;
-            let w = self.debug.add_text(
+            let w = debug_renderer.add_text(
                 x0, y,
                 &format!("{:?}: {:?}", pipeline, epoch),
                 ColorU::new(255, 255, 0, 255),
             ).size.width;
             text_width = f32::max(text_width, w);
         }
 
         let margin = 10.0;
-        self.debug.add_quad(
+        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),
         );
     }
@@ -3724,17 +3922,22 @@ impl Renderer {
         self.node_data_texture.deinit(&mut self.device);
         self.local_clip_rects_texture.deinit(&mut self.device);
         self.render_task_texture.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.prim_vao);
         self.device.delete_vao(self.clip_vao);
         self.device.delete_vao(self.blur_vao);
-        self.debug.deinit(&mut self.device);
+
+        #[cfg(feature = "debug_renderer")]
+        {
+            self.debug.deinit(&mut self.device);
+        }
+
         for (_, target) in self.output_targets {
             self.device.delete_fbo(target.fbo_id);
         }
         self.shaders.deinit(&mut self.device);
         #[cfg(feature = "capture")]
         self.device.delete_fbo(self.read_fbo);
         #[cfg(feature = "replay")]
         for (_, ext) in self.owned_external_images {
@@ -4098,18 +4301,17 @@ impl Renderer {
                 if let Some(bytes) = data {
                     fs::File::create(config.root.join(&short_path))
                         .expect(&format!("Unable to create {}", short_path))
                         .write_all(&bytes)
                         .unwrap();
                 }
                 let plain = PlainExternalImage {
                     data: short_path,
-                    id: def.external.id,
-                    channel_index: def.external.channel_index,
+                    external: def.external,
                     uv: ext_image.uv,
                 };
                 config.serialize(&plain, &def.short_path);
             }
             for def in &deferred_images {
                 handler.unlock(def.external.id, def.external.channel_index);
             }
         }
@@ -4173,19 +4375,19 @@ impl Renderer {
                     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 key = (plain_ext.id, plain_ext.channel_index);
+            let ext = plain_ext.external;
             let value = (CapturedExternalImageData::Buffer(data), plain_ext.uv);
-            image_handler.data.insert(key, value);
+            image_handler.data.insert((ext.id, ext.channel_index), value);
         }
 
         if let Some(renderer) = CaptureConfig::deserialize::<PlainRenderer, _>(&root, "renderer") {
             info!("loading cached textures");
             self.device.begin_frame();
 
             for texture in self.texture_resolver.cache_texture_map.drain(..) {
                 self.device.delete_texture(texture);
@@ -4261,8 +4463,41 @@ impl Renderer {
             self.device.end_frame();
         }
 
         self.output_image_handler = Some(Box::new(()) as Box<_>);
         self.external_image_handler = Some(Box::new(image_handler) as Box<_>);
         info!("done.");
     }
 }
+
+// FIXME(pcwalton): We should really gather up all the VAOs into a separate structure so that they
+// don't have to be passed in as parameters here.
+#[cfg(feature = "pathfinder")]
+fn get_vao<'a>(vertex_array_kind: VertexArrayKind,
+               prim_vao: &'a VAO,
+               clip_vao: &'a VAO,
+               blur_vao: &'a VAO,
+               gpu_glyph_renderer: &'a GpuGlyphRenderer)
+               -> &'a VAO {
+    match vertex_array_kind {
+        VertexArrayKind::Primitive => prim_vao,
+        VertexArrayKind::Clip => clip_vao,
+        VertexArrayKind::Blur => blur_vao,
+        VertexArrayKind::VectorStencil => &gpu_glyph_renderer.vector_stencil_vao,
+        VertexArrayKind::VectorCover => &gpu_glyph_renderer.vector_cover_vao,
+    }
+}
+
+#[cfg(not(feature = "pathfinder"))]
+fn get_vao<'a>(vertex_array_kind: VertexArrayKind,
+               prim_vao: &'a VAO,
+               clip_vao: &'a VAO,
+               blur_vao: &'a VAO,
+               _: &'a GpuGlyphRenderer)
+               -> &'a VAO {
+    match vertex_array_kind {
+        VertexArrayKind::Primitive => prim_vao,
+        VertexArrayKind::Clip => clip_vao,
+        VertexArrayKind::Blur => blur_vao,
+        VertexArrayKind::VectorStencil | VertexArrayKind::VectorCover => unreachable!(),
+    }
+}
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -14,33 +14,35 @@ use api::{TileOffset, TileSize};
 use app_units::Au;
 #[cfg(feature = "capture")]
 use capture::ExternalCaptureImage;
 #[cfg(feature = "replay")]
 use capture::PlainExternalImage;
 #[cfg(any(feature = "replay", feature = "png"))]
 use capture::CaptureConfig;
 use device::TextureFilter;
-use glyph_cache::{GlyphCache, GlyphCacheEntry};
+use glyph_cache::GlyphCache;
+#[cfg(not(feature = "pathfinder"))]
+use glyph_cache::GlyphCacheEntry;
 use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphRasterizer, GlyphRequest};
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use internal_types::{FastHashMap, FastHashSet, SourceTexture, TextureUpdateList};
 use profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
 use render_backend::FrameId;
 use render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId, RenderTaskTree};
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
 use std::cmp;
 use std::fmt::Debug;
 use std::hash::Hash;
 use std::mem;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
 use texture_cache::{TextureCache, TextureCacheHandle};
-
+use tiling::SpecialRenderPasses;
 
 const DEFAULT_TILE_SIZE: TileSize = 512;
 
 #[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,
@@ -315,25 +317,27 @@ impl ResourceCache {
     // 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<F>(
         &mut self,
         key: RenderTaskCacheKey,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
-        f: F,
+        user_data: Option<[f32; 3]>,
+        mut f: F,
     ) -> CacheItem where F: FnMut(&mut RenderTaskTree) -> (RenderTaskId, bool) {
         self.cached_render_tasks.request_render_task(
             key,
             &mut self.texture_cache,
             gpu_cache,
             render_tasks,
-            f
-        )
+            user_data,
+            |render_task_tree| Ok(f(render_task_tree))
+        ).expect("Failed to request a render task from the resource cache!")
     }
 
     pub fn update_resources(
         &mut self,
         updates: ResourceUpdates,
         profile_counters: &mut ResourceProfileCounters,
     ) {
         // TODO, there is potential for optimization here, by processing updates in
@@ -641,69 +645,128 @@ impl ResourceCache {
         }
     }
 
     pub fn request_glyphs(
         &mut self,
         mut font: FontInstance,
         glyph_keys: &[GlyphKey],
         gpu_cache: &mut GpuCache,
+        render_task_tree: &mut RenderTaskTree,
+        render_passes: &mut SpecialRenderPasses,
     ) {
         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,
+            render_passes,
         );
     }
 
     pub fn pending_updates(&mut self) -> TextureUpdateList {
         self.texture_cache.pending_updates()
     }
 
+    #[cfg(feature = "pathfinder")]
     pub fn fetch_glyphs<F>(
         &self,
         mut font: FontInstance,
         glyph_keys: &[GlyphKey],
         fetch_buffer: &mut Vec<GlyphFetchResult>,
-        gpu_cache: &GpuCache,
+        gpu_cache: &mut GpuCache,
+        mut f: F,
+    ) where
+        F: FnMut(SourceTexture, GlyphFormat, &[GlyphFetchResult]),
+    {
+        debug_assert_eq!(self.state, State::QueryResources);
+
+        self.glyph_rasterizer.prepare_font(&mut font);
+
+        let mut current_texture_id = SourceTexture::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 self.glyph_rasterizer.get_cache_item_for_glyph(key,
+                                                                     &font,
+                                                                     &self.cached_glyphs,
+                                                                     &self.texture_cache,
+                                                                     &self.cached_render_tasks) {
+                    None => continue,
+                    Some(result) => result,
+                };
+            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();
+        }
+    }
+
+    #[cfg(not(feature = "pathfinder"))]
+    pub fn fetch_glyphs<F>(
+        &self,
+        mut font: FontInstance,
+        glyph_keys: &[GlyphKey],
+        fetch_buffer: &mut Vec<GlyphFetchResult>,
+        gpu_cache: &mut GpuCache,
         mut f: F,
     ) where
         F: FnMut(SourceTexture, 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 = SourceTexture::Invalid;
         let mut current_glyph_format = GlyphFormat::Subpixel;
         debug_assert!(fetch_buffer.is_empty());
 
         for (loop_index, key) in glyph_keys.iter().enumerate() {
-            if let GlyphCacheEntry::Cached(ref glyph) = *glyph_key_cache.get(key) {
-                let cache_item = self.texture_cache.get(&glyph.texture_cache_handle);
-                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;
+            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)
                 }
-                fetch_buffer.push(GlyphFetchResult {
-                    index_in_text_run: loop_index as i32,
-                    uv_rect_address: gpu_cache.get_address(&cache_item.uv_rect_handle),
-                });
+                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();
         }
     }
 
@@ -790,35 +853,38 @@ impl ResourceCache {
             })
             .collect()
     }
 
     pub fn begin_frame(&mut self, frame_id: FrameId) {
         debug_assert_eq!(self.state, State::Idle);
         self.state = State::AddResources;
         self.texture_cache.begin_frame(frame_id);
-        self.cached_glyphs.begin_frame(&mut self.texture_cache);
+        self.cached_glyphs.begin_frame(&mut self.texture_cache, &self.cached_render_tasks);
         self.cached_render_tasks.begin_frame(&mut self.texture_cache);
         self.current_frame_id = frame_id;
     }
 
     pub fn block_until_all_resources_added(
         &mut self,
         gpu_cache: &mut GpuCache,
+        render_tasks: &mut RenderTaskTree,
         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);
         self.texture_cache.end_frame(texture_cache_profile);
     }
 
@@ -1317,21 +1383,17 @@ impl ResourceCache {
             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 CaptureConfig::deserialize::<PlainExternalImage, _>(root, &template.data) {
                 Some(plain) => {
-                    let ext_data = ExternalImageData {
-                        id: plain.id,
-                        channel_index: plain.channel_index,
-                        image_type: ExternalImageType::Buffer,
-                    };
+                    let ext_data = plain.external;
                     external_images.push(plain);
                     ImageData::External(ext_data)
                 }
                 None => {
                     let arc = match raw_map.entry(template.data) {
                         Entry::Occupied(e) => {
                             e.get().clone()
                         }
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -31,17 +31,17 @@ pub enum SceneBuilderResult {
         document_id: DocumentId,
         built_scene: Option<BuiltScene>,
         resource_updates: ResourceUpdates,
         frame_ops: Vec<FrameMsg>,
         render: bool,
     },
 }
 
-/// Contains the the render backend data needed to build a scene.
+/// Contains the render backend data needed to build a scene.
 pub struct SceneRequest {
     pub scene: Scene,
     pub view: DocumentView,
     pub font_instances: FontInstanceMap,
     pub tiled_image_map: TiledImageMap,
     pub output_pipelines: FastHashSet<PipelineId>,
     pub removed_pipelines: Vec<PipelineId>,
 }
--- a/gfx/webrender/src/shade.rs
+++ b/gfx/webrender/src/shade.rs
@@ -18,17 +18,17 @@ use renderer::{
 };
 use util::TransformedRectKind;
 
 use gleam::gl::GlType;
 use time::precise_time_ns;
 
 
 impl ImageBufferKind {
-    fn get_feature_string(&self) -> &'static str {
+    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",
         }
     }
 
@@ -49,33 +49,37 @@ pub const IMAGE_BUFFER_KINDS: [ImageBuff
     ImageBufferKind::TextureExternal,
     ImageBufferKind::Texture2DArray,
 ];
 
 const TRANSFORM_FEATURE: &str = "TRANSFORM";
 const ALPHA_FEATURE: &str = "ALPHA_PASS";
 const DITHERING_FEATURE: &str = "DITHERING";
 
-enum ShaderKind {
+pub(crate) enum ShaderKind {
     Primitive,
     Cache(VertexArrayKind),
     ClipCache,
     Brush,
     Text,
+    #[allow(dead_code)]
+    VectorStencil,
+    #[allow(dead_code)]
+    VectorCover,
 }
 
 pub struct LazilyCompiledShader {
     program: Option<Program>,
     name: &'static str,
     kind: ShaderKind,
     features: Vec<&'static str>,
 }
 
 impl LazilyCompiledShader {
-    fn new(
+    pub(crate) fn new(
         kind: ShaderKind,
         name: &'static str,
         features: &[&'static str],
         device: &mut Device,
         precache: bool,
     ) -> Result<Self, ShaderError> {
         let mut shader = LazilyCompiledShader {
             program: None,
@@ -129,16 +133,28 @@ impl LazilyCompiledShader {
                                        VertexArrayKind::Primitive)
                 }
                 ShaderKind::Cache(format) => {
                     create_prim_shader(self.name,
                                        device,
                                        &self.features,
                                        format)
                 }
+                ShaderKind::VectorStencil => {
+                    create_prim_shader(self.name,
+                                       device,
+                                       &self.features,
+                                       VertexArrayKind::VectorStencil)
+                }
+                ShaderKind::VectorCover => {
+                    create_prim_shader(self.name,
+                                       device,
+                                       &self.features,
+                                       VertexArrayKind::VectorCover)
+                }
                 ShaderKind::ClipCache => {
                     create_clip_shader(self.name, device)
                 }
             };
             self.program = Some(program?);
         }
 
         Ok(self.program.as_ref().unwrap())
@@ -349,16 +365,18 @@ fn create_prim_shader(
     }
 
     debug!("PrimShader {}", name);
 
     let vertex_descriptor = match vertex_format {
         VertexArrayKind::Primitive => desc::PRIM_INSTANCES,
         VertexArrayKind::Blur => desc::BLUR,
         VertexArrayKind::Clip => desc::CLIP,
+        VertexArrayKind::VectorStencil => desc::VECTOR_STENCIL,
+        VertexArrayKind::VectorCover => desc::VECTOR_COVER,
     };
 
     let program = device.create_program(name, &prefix, &vertex_descriptor);
 
     if let Ok(ref program) = program {
         device.bind_shader_samplers(
             program,
             &[
@@ -442,17 +460,16 @@ pub struct Shaders {
     // 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: TextShader,
     ps_image: Vec<Option<PrimitiveShader>>,
     ps_border_corner: PrimitiveShader,
     ps_border_edge: PrimitiveShader,
 
-    ps_hw_composite: LazilyCompiledShader,
     ps_split_composite: LazilyCompiledShader,
 }
 
 impl Shaders {
     pub fn new(
         device: &mut Device,
         gl_type: GlType,
         options: &RendererOptions,
@@ -652,24 +669,16 @@ impl Shaders {
 
         let ps_border_edge = PrimitiveShader::new(
             "ps_border_edge",
              device,
              &[],
              options.precache_shaders,
         )?;
 
-        let ps_hw_composite = LazilyCompiledShader::new(
-            ShaderKind::Primitive,
-            "ps_hardware_composite",
-            &[],
-            device,
-            options.precache_shaders,
-        )?;
-
         let ps_split_composite = LazilyCompiledShader::new(
             ShaderKind::Primitive,
             "ps_split_composite",
             &[],
             device,
             options.precache_shaders,
         )?;
 
@@ -688,35 +697,31 @@ impl Shaders {
             cs_clip_border,
             cs_clip_image,
             cs_clip_line,
             ps_text_run,
             ps_text_run_dual_source,
             ps_image,
             ps_border_corner,
             ps_border_edge,
-            ps_hw_composite,
             ps_split_composite,
         })
     }
 
     fn get_yuv_shader_index(
         buffer_kind: ImageBufferKind,
         format: YuvFormat,
         color_space: YuvColorSpace,
     ) -> usize {
         ((buffer_kind as usize) * YUV_FORMATS.len() + (format as usize)) * YUV_COLOR_SPACES.len() +
             (color_space as usize)
     }
 
     pub fn get(&mut self, key: &BatchKey) -> &mut LazilyCompiledShader {
         match key.kind {
-            BatchKind::HardwareComposite => {
-                &mut self.ps_hw_composite
-            }
             BatchKind::SplitComposite => {
                 &mut self.ps_split_composite
             }
             BatchKind::Brush(brush_kind) => {
                 let brush_shader = match brush_kind {
                     BrushBatchKind::Solid => {
                         &mut self.brush_solid
                     }
@@ -796,12 +801,11 @@ impl Shaders {
         }
         for shader in self.brush_yuv_image {
             if let Some(shader) = shader {
                 shader.deinit(device);
             }
         }
         self.ps_border_corner.deinit(device);
         self.ps_border_edge.deinit(device);
-        self.ps_hw_composite.deinit(device);
         self.ps_split_composite.deinit(device);
     }
 }
--- a/gfx/webrender/src/spring.rs
+++ b/gfx/webrender/src/spring.rs
@@ -99,12 +99,12 @@ fn next(cur: f32, prev: f32, dest: f32, 
     // Calculate new velocity after adding acceleration. Scale to framerate.
     let nextv = vel + acc;
 
     // Calculate next position by integrating velocity. Scale to framerate.
     let next = cur + nextv;
     next
 }
 
-/// Given numbers, calcluate if a spring is at rest.
+/// Given numbers, calculate if a spring is at rest.
 fn is_resting(cur: f32, prev: f32, dest: f32) -> bool {
     (cur - prev).abs() < EPSILON && (cur - dest).abs() < EPSILON
 }
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -167,17 +167,17 @@ type WeakCacheEntryHandle = WeakFreeList
 
 // A texture cache handle is a weak reference to a cache entry.
 // If the handle has not been inserted into the cache yet, the
 // value will be None. Even when the value is Some(), the location
 // may not actually be valid if it has been evicted by the cache.
 // In this case, the cache handle needs to re-upload this item
 // to the texture cache (see request() below).
 #[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Clone, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct TextureCacheHandle {
     entry: Option<WeakCacheEntryHandle>,
 }
 
 impl TextureCacheHandle {
     pub fn new() -> Self {
         TextureCacheHandle { entry: None }
@@ -492,17 +492,17 @@ impl TextureCache {
         handle.entry.as_ref().map_or(false, |handle| {
             self.entries.get_opt(handle).is_some()
         })
     }
 
     // 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 asssert in debug modes if the caller
+    // 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 {
         match handle.entry {
             Some(ref handle) => {
                 let entry = self.entries
                     .get_opt(handle)
                     .expect("BUG: was dropped from cache or not updated!");
                 debug_assert_eq!(entry.last_access, self.frame_id);
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -4,47 +4,53 @@
 
 use api::{ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale, DeviceUintPoint};
 use api::{DeviceUintRect, DeviceUintSize, DocumentLayer, FilterOp, ImageFormat, LayerRect};
 use api::{MixBlendMode, PipelineId};
 use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
 use clip::{ClipStore};
 use clip_scroll_tree::{ClipScrollTree, ClipScrollNodeIndex};
 use device::{FrameId, Texture};
+#[cfg(feature = "pathfinder")]
+use euclid::{TypedPoint2D, TypedVector2D};
 use gpu_cache::{GpuCache};
 use gpu_types::{BlurDirection, BlurInstance};
 use gpu_types::{ClipScrollNodeData, ZBufferIdGenerator};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
+#[cfg(feature = "pathfinder")]
+use pathfinder_partitioner::mesh::Mesh;
 use prim_store::{CachedGradient, PrimitiveIndex, PrimitiveKind, PrimitiveStore};
 use prim_store::{BrushKind, DeferredResolve};
 use profiler::FrameProfileCounters;
-use render_task::{BlitSource, RenderTaskId, RenderTaskKind};
-use render_task::{BlurTask, ClearMode, RenderTaskLocation, RenderTaskTree};
+use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
+use render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskTree};
 use resource_cache::ResourceCache;
 use std::{cmp, usize, f32, i32};
 use texture_allocator::GuillotineAllocator;
+#[cfg(feature = "pathfinder")]
+use webrender_api::{DevicePixel, FontRenderMode};
 
 const MIN_TARGET_SIZE: u32 = 2048;
 
 #[derive(Debug)]
 pub struct ScrollbarPrimitive {
     pub scroll_frame_index: ClipScrollNodeIndex,
     pub prim_index: PrimitiveIndex,
     pub frame_rect: LayerRect,
 }
 
 #[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> {
+pub struct RenderTargetContext<'a, 'rc> {
     pub device_pixel_scale: DevicePixelScale,
     pub prim_store: &'a PrimitiveStore,
-    pub resource_cache: &'a ResourceCache,
+    pub resource_cache: &'rc mut ResourceCache,
     pub clip_scroll_tree: &'a ClipScrollTree,
     pub use_dual_source_blending: bool,
     pub node_data: &'a [ClipScrollNodeData],
     pub cached_gradients: &'a [CachedGradient],
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -89,17 +95,17 @@ impl TextureAllocator {
 pub trait RenderTarget {
     fn new(
         size: Option<DeviceUintSize>,
         screen_size: DeviceIntSize,
     ) -> Self;
     fn allocate(&mut self, size: DeviceUintSize) -> Option<DeviceUintPoint>;
     fn build(
         &mut self,
-        _ctx: &RenderTargetContext,
+        _ctx: &mut RenderTargetContext,
         _gpu_cache: &mut GpuCache,
         _render_tasks: &mut RenderTaskTree,
         _deferred_resolves: &mut Vec<DeferredResolve>,
     ) {
     }
     // 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
@@ -151,17 +157,17 @@ impl<T: RenderTarget> RenderTargetList<T
             targets: Vec::new(),
             saved_index: None,
             is_shared: false,
         }
     }
 
     fn build(
         &mut self,
-        ctx: &RenderTargetContext,
+        ctx: &mut RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         deferred_resolves: &mut Vec<DeferredResolve>,
         saved_index: Option<SavedTargetIndex>,
     ) {
         debug_assert_eq!(None, self.saved_index);
         self.saved_index = saved_index;
 
@@ -256,16 +262,33 @@ pub enum BlitJobSource {
 // 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(feature = "pathfinder")]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct GlyphJob {
+    pub mesh: Mesh,
+    pub target_rect: DeviceIntRect,
+    pub origin: DeviceIntPoint,
+    pub subpixel_offset: TypedPoint2D<f32, DevicePixel>,
+    pub render_mode: FontRenderMode,
+    pub embolden_amount: TypedVector2D<f32, DevicePixel>,
+}
+
+#[cfg(not(feature = "pathfinder"))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct GlyphJob;
+
 /// A render target represents a number of rendering operations on a surface.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ColorRenderTarget {
     pub alpha_batch_containers: Vec<AlphaBatchContainer>,
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurInstance>,
     pub horizontal_blurs: Vec<BlurInstance>,
@@ -302,33 +325,33 @@ impl RenderTarget for ColorRenderTarget 
             outputs: Vec::new(),
             alpha_tasks: Vec::new(),
             screen_size,
         }
     }
 
     fn build(
         &mut self,
-        ctx: &RenderTargetContext,
+        ctx: &mut RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         deferred_resolves: &mut Vec<DeferredResolve>,
     ) {
         let mut merged_batches = AlphaBatchContainer::new(None);
         let mut z_generator = ZBufferIdGenerator::new();
 
         for task_id in &self.alpha_tasks {
             let task = &render_tasks[*task_id];
 
             match task.kind {
                 RenderTaskKind::Picture(ref pic_task) => {
                     let brush_index = ctx.prim_store.cpu_metadata[pic_task.prim_index.0].cpu_prim_index;
                     let brush = &ctx.prim_store.cpu_brushes[brush_index.0];
                     match brush.kind {
-                        BrushKind::Picture { pic_index } => {
+                        BrushKind::Picture { pic_index, .. } => {
                             let pic = &ctx.prim_store.pictures[pic_index.0];
                             let (target_rect, _) = task.get_target_rect();
 
                             let mut batch_builder = AlphaBatchBuilder::new(self.screen_size, target_rect);
 
                             batch_builder.add_pic_to_batch(
                                 pic,
                                 *task_id,
@@ -367,29 +390,27 @@ impl RenderTarget for ColorRenderTarget 
         deferred_resolves: &mut Vec<DeferredResolve>,
     ) {
         let task = &render_tasks[task_id];
 
         match task.kind {
             RenderTaskKind::VerticalBlur(ref info) => {
                 info.add_instances(
                     &mut self.vertical_blurs,
-                    task_id,
-                    task.children[0],
                     BlurDirection::Vertical,
-                    render_tasks,
+                    render_tasks.get_task_address(task_id),
+                    render_tasks.get_task_address(task.children[0]),
                 );
             }
             RenderTaskKind::HorizontalBlur(ref info) => {
                 info.add_instances(
                     &mut self.horizontal_blurs,
-                    task_id,
-                    task.children[0],
                     BlurDirection::Horizontal,
-                    render_tasks,
+                    render_tasks.get_task_address(task_id),
+                    render_tasks.get_task_address(task.children[0]),
                 );
             }
             RenderTaskKind::Picture(ref task_info) => {
                 let prim_metadata = ctx.prim_store.get_metadata(task_info.prim_index);
                 match prim_metadata.prim_kind {
                     PrimitiveKind::Brush => {
                         let brush = &ctx.prim_store.cpu_brushes[prim_metadata.cpu_prim_index.0];
                         let pic = &ctx.prim_store.pictures[brush.get_picture_index().0];
@@ -410,16 +431,20 @@ impl RenderTarget for ColorRenderTarget 
                         unreachable!()
                     }
                 }
             }
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) => {
                 panic!("Should not be added to color target!");
             }
+            RenderTaskKind::Glyph(..) => {
+                // FIXME(pcwalton): Support color glyphs.
+                panic!("Glyphs should not be added to color target!");
+            }
             RenderTaskKind::Readback(device_rect) => {
                 self.readbacks.push(device_rect);
             }
             RenderTaskKind::Scaling(..) => {
                 self.scalings.push(ScalingInfo {
                     src_task_id: task.children[0],
                     dest_task_id: task_id,
                 });
@@ -534,35 +559,34 @@ impl RenderTarget for AlphaRenderTarget 
             ClearMode::Transparent => {
                 panic!("bug: invalid clear mode for alpha task");
             }
         }
 
         match task.kind {
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Picture(..) |
-            RenderTaskKind::Blit(..) => {
+            RenderTaskKind::Blit(..) |
+            RenderTaskKind::Glyph(..) => {
                 panic!("BUG: should not be added to alpha target!");
             }
             RenderTaskKind::VerticalBlur(ref info) => {
                 info.add_instances(
                     &mut self.vertical_blurs,
-                    task_id,
-                    task.children[0],
                     BlurDirection::Vertical,
-                    render_tasks,
+                    render_tasks.get_task_address(task_id),
+                    render_tasks.get_task_address(task.children[0]),
                 );
             }
             RenderTaskKind::HorizontalBlur(ref info) => {
                 info.add_instances(
                     &mut self.horizontal_blurs,
-                    task_id,
-                    task.children[0],
                     BlurDirection::Horizontal,
-                    render_tasks,
+                    render_tasks.get_task_address(task_id),
+                    render_tasks.get_task_address(task.children[0]),
                 );
             }
             RenderTaskKind::CacheMask(ref task_info) => {
                 let task_address = render_tasks.get_task_address(task_id);
                 self.clip_batcher.add(
                     task_address,
                     &task_info.clips,
                     task_info.coordinate_system_id,
@@ -596,74 +620,98 @@ impl RenderTarget for AlphaRenderTarget 
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct TextureCacheRenderTarget {
     pub horizontal_blurs: Vec<BlurInstance>,
     pub blits: Vec<BlitJob>,
+    pub glyphs: Vec<GlyphJob>,
 }
 
 impl TextureCacheRenderTarget {
     fn new(
         _size: Option<DeviceUintSize>,
         _screen_size: DeviceIntSize,
     ) -> Self {
         TextureCacheRenderTarget {
-            horizontal_blurs: Vec::new(),
-            blits: Vec::new(),
+            horizontal_blurs: vec![],
+            blits: vec![],
+            glyphs: vec![],
         }
     }
 
     fn add_task(
         &mut self,
         task_id: RenderTaskId,
-        render_tasks: &RenderTaskTree,
+        render_tasks: &mut RenderTaskTree,
     ) {
-        let task = &render_tasks[task_id];
+        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::HorizontalBlur(ref info) => {
                 info.add_instances(
                     &mut self.horizontal_blurs,
-                    task_id,
-                    task.children[0],
                     BlurDirection::Horizontal,
-                    render_tasks,
+                    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.
-                        let (target_rect, _) = task.get_target_rect();
                         self.blits.push(BlitJob {
                             source: BlitJobSource::RenderTask(task_id),
-                            target_rect,
+                            target_rect: target_rect.0,
                         });
                     }
                 }
             }
+            RenderTaskKind::Glyph(ref mut task_info) => {
+                self.add_glyph_task(task_info, target_rect.0)
+            }
             RenderTaskKind::VerticalBlur(..) |
             RenderTaskKind::Picture(..) |
             RenderTaskKind::ClipRegion(..) |
             RenderTaskKind::CacheMask(..) |
             RenderTaskKind::Readback(..) |
             RenderTaskKind::Scaling(..) => {
                 panic!("BUG: unexpected task kind for texture cache target");
             }
         }
     }
+
+    #[cfg(feature = "pathfinder")]
+    fn add_glyph_task(&mut self, task_info: &mut GlyphTask, target_rect: DeviceIntRect) {
+        self.glyphs.push(GlyphJob {
+            mesh: task_info.mesh.take().unwrap(),
+            target_rect: target_rect,
+            origin: task_info.origin,
+            subpixel_offset: task_info.subpixel_offset,
+            render_mode: task_info.render_mode,
+            embolden_amount: task_info.embolden_amount,
+        })
+    }
+
+    #[cfg(not(feature = "pathfinder"))]
+    fn add_glyph_task(&mut self, _: &mut GlyphTask, _: DeviceIntRect) {}
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderPassKind {
     MainFramebuffer(ColorRenderTarget),
     OffScreen {
         alpha: RenderTargetList<AlphaRenderTarget>,
@@ -719,17 +767,17 @@ impl RenderPass {
             max_size.height = cmp::max(max_size.height, size.height as u32);
         }
 
         self.tasks.push(task_id);
     }
 
     pub fn build(
         &mut self,
-        ctx: &RenderTargetContext,
+        ctx: &mut RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         deferred_resolves: &mut Vec<DeferredResolve>,
         clip_store: &ClipStore,
     ) {
         profile_scope!("RenderPass::build");
 
         match self.kind {
@@ -922,22 +970,35 @@ impl Frame {
         self.has_texture_cache_tasks && !self.has_been_rendered
     }
 }
 
 impl BlurTask {
     fn add_instances(
         &self,
         instances: &mut Vec<BlurInstance>,
-        task_id: RenderTaskId,
-        source_task_id: RenderTaskId,
         blur_direction: BlurDirection,
-        render_tasks: &RenderTaskTree,
+        task_address: RenderTaskAddress,
+        src_task_address: RenderTaskAddress,
     ) {
         let instance = BlurInstance {
-            task_address: render_tasks.get_task_address(task_id),
-            src_task_address: render_tasks.get_task_address(source_task_id),
+            task_address,
+            src_task_address,
             blur_direction,
         };
 
         instances.push(instance);
     }
 }
+
+pub struct SpecialRenderPasses {
+    pub alpha_glyph_pass: RenderPass,
+    pub color_glyph_pass: RenderPass,
+}
+
+impl SpecialRenderPasses {
+    pub fn new(screen_size: &DeviceIntSize) -> SpecialRenderPasses {
+        SpecialRenderPasses {
+            alpha_glyph_pass: RenderPass::new_off_screen(*screen_size),
+            color_glyph_pass: RenderPass::new_off_screen(*screen_size),
+        }
+    }
+}
--- a/gfx/webrender/tests/angle_shader_validation.rs
+++ b/gfx/webrender/tests/angle_shader_validation.rs
@@ -54,20 +54,16 @@ const SHADERS: &[Shader] = &[
         name: "ps_border_corner",
         features: PRIM_FEATURES,
     },
     Shader {
         name: "ps_border_edge",
         features: PRIM_FEATURES,
     },
     Shader {
-        name: "ps_hardware_composite",
-        features: PRIM_FEATURES,
-    },
-    Shader {
         name: "ps_split_composite",
         features: PRIM_FEATURES,
     },
     Shader {
         name: "ps_image",
         features: PRIM_FEATURES,
     },
     Shader {
@@ -75,29 +71,29 @@ const SHADERS: &[Shader] = &[
         features: PRIM_FEATURES,
     },
     // Brush shaders
     Shader {
         name: "brush_yuv_image",
         features: &["", "YUV_NV12", "YUV_PLANAR", "YUV_INTERLEAVED", "YUV_NV12,TEXTURE_RECT"],
     },
     Shader {
-        name: "brush_mask",
+        name: "brush_solid",
         features: &[],
     },
     Shader {
-        name: "brush_solid",
-        features: &[],
+        name: "brush_image",
+        features: &["", "ALPHA_PASS"],
     },
     Shader {
         name: "brush_blend",
         features: &[],
     },
     Shader {
-        name: "brush_composite",
+        name: "brush_mix_blend",
         features: &[],
     },
     Shader {
         name: "brush_radial_gradient",
         features: &[ "DITHERING" ],
     },
     Shader {
         name: "brush_linear_gradient",
--- a/gfx/webrender_api/Cargo.toml
+++ b/gfx/webrender_api/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "webrender_api"
-version = "0.57.0"
+version = "0.57.2"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 
 [features]
 nightly = ["euclid/unstable", "serde/unstable"]
 ipc = ["ipc-channel"]
 serialize = []
@@ -15,16 +15,17 @@ deserialize = []
 app_units = "0.6"
 bincode = "1.0"
 bitflags = "1.0"
 byteorder = "1.2.1"
 ipc-channel = {version = "0.10.0", optional = true}
 euclid = { version = "0.17", features = ["serde"] }
 serde = { version = "=1.0.35", features = ["rc"] }
 serde_derive = { version = "=1.0.35", features = ["deserialize_in_place"] }
+serde_bytes = "0.10"
 time = "0.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.5"
 core-graphics = "0.13"
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -1,12 +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/. */
 
+extern crate serde_bytes;
+
 use app_units::Au;
 use channel::{self, MsgSender, Payload, PayloadSender, PayloadSenderHelperMethods};
 use std::cell::Cell;
 use std::fmt;
 use std::marker::PhantomData;
 use std::path::PathBuf;
 use std::u32;
 use {BuiltDisplayList, BuiltDisplayListDescriptor, ColorF, DeviceIntPoint, DeviceUintRect};
@@ -400,17 +402,21 @@ pub struct UpdateImage {
     pub key: ImageKey,
     pub descriptor: ImageDescriptor,
     pub data: ImageData,
     pub dirty_rect: Option<DeviceUintRect>,
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum AddFont {
-    Raw(FontKey, Vec<u8>, u32),
+    Raw(
+        FontKey,
+        #[serde(with = "serde_bytes")] Vec<u8>,
+        u32
+    ),
     Native(FontKey, NativeFontHandle),
 }
 
 #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
 pub struct HitTestItem {
     /// The pipeline that the display item that was hit belongs to.
     pub pipeline: PipelineId,
 
@@ -686,17 +692,17 @@ pub struct ResourceId(pub u32);
 pub struct ExternalEvent {
     raw: usize,
 }
 
 unsafe impl Send for ExternalEvent {}
 
 impl ExternalEvent {
     pub fn from_raw(raw: usize) -> Self {
-        ExternalEvent { raw: raw }
+        ExternalEvent { raw }
     }
     /// Consumes self to make it obvious that the event should be forwarded only once.
     pub fn unwrap(self) -> usize {
         self.raw
     }
 }
 
 #[derive(Clone, Deserialize, Serialize)]
@@ -735,17 +741,17 @@ impl RenderApiSender {
                 } else {
                     panic!("CloneApi message response was dropped while Webrender was still alive: {}", e);
                 }
             }
         };
         RenderApi {
             api_sender: self.api_sender.clone(),
             payload_sender: self.payload_sender.clone(),
-            namespace_id: namespace_id,
+            namespace_id,
             next_id: Cell::new(ResourceId(0)),
         }
     }
 }
 
 pub struct RenderApi {
     api_sender: MsgSender<ApiMsg>,
     payload_sender: PayloadSender,
--- a/gfx/webrender_api/src/channel_mpsc.rs
+++ b/gfx/webrender_api/src/channel_mpsc.rs
@@ -50,22 +50,22 @@ pub struct MsgSender<T> {
 impl<T> MsgSender<T> {
     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: tx }, PayloadReceiver { rx: rx }))
+    Ok((PayloadSender { tx }, PayloadReceiver { rx }))
 }
 
 pub fn msg_channel<T>() -> Result<(MsgSender<T>, MsgReceiver<T>), Error> {
     let (tx, rx) = mpsc::channel();
-    Ok((MsgSender { tx: tx }, MsgReceiver { rx: rx }))
+    Ok((MsgSender { tx }, MsgReceiver { rx }))
 }
 
 ///
 /// These serialize methods are needed to satisfy the compiler
 /// which uses these implementations for IPC, and also 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.
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -622,17 +622,17 @@ impl From<LayoutRect> for LocalClip {
         LocalClip::Rect(rect)
     }
 }
 
 impl LocalClip {
     pub fn clip_rect(&self) -> &LayoutRect {
         match *self {
             LocalClip::Rect(ref rect) => rect,
-            LocalClip::RoundedRect(ref rect, _) => &rect,
+            LocalClip::RoundedRect(ref rect, _) => rect,
         }
     }
 
     pub fn create_with_offset(&self, offset: &LayoutVector2D) -> LocalClip {
         match *self {
             LocalClip::Rect(rect) => LocalClip::from(rect.translate(offset)),
             LocalClip::RoundedRect(rect, complex) => LocalClip::RoundedRect(
                 rect.translate(offset),
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -190,17 +190,17 @@ fn skip_slice<T: for<'de> Deserialize<'d
 impl<'a> BuiltDisplayListIter<'a> {
     pub fn new(list: &'a BuiltDisplayList) -> Self {
         Self::new_with_list_and_data(list, list.item_slice())
     }
 
     pub fn new_with_list_and_data(list: &'a BuiltDisplayList, data: &'a [u8]) -> Self {
         BuiltDisplayListIter {
             list,
-            data: &data,
+            data,
             cur_item: DisplayItem {
                 // Dummy data, will be overwritten by `next`
                 item: SpecificDisplayItem::PopStackingContext,
                 clip_and_scroll:
                     ClipAndScrollInfo::simple(ClipId::root_scroll_node(PipelineId::dummy())),
                 info: LayoutPrimitiveInfo::new(LayoutRect::zero()),
             },
             cur_stops: ItemRange::default(),
@@ -231,17 +231,17 @@ impl<'a> BuiltDisplayListIter<'a> {
         }
 
         // Don't let these bleed into another item
         self.cur_stops = ItemRange::default();
         self.cur_complex_clip = (ItemRange::default(), 0);
         self.cur_clip_chain_items = ItemRange::default();
 
         loop {
-            if self.data.len() == 0 {
+            if self.data.is_empty() {
                 return None;
             }
 
             {
                 let reader = bincode::IoReader::new(UnsafeReader::new(&mut self.data));
                 bincode::deserialize_in_place(reader, &mut self.cur_item)
                     .expect("MEH: malicious process?");
             }
@@ -329,18 +329,18 @@ impl<'a, 'b> DisplayItemRef<'a, 'b> {
 
     pub fn rect(&self) -> LayoutRect {
         self.iter.cur_item.info.rect
     }
 
     pub fn get_layer_primitive_info(&self, offset: &LayoutVector2D) -> LayerPrimitiveInfo {
         let info = self.iter.cur_item.info;
         LayerPrimitiveInfo {
-            rect: info.rect.translate(&offset),
-            clip_rect: info.clip_rect.translate(&offset),
+            rect: info.rect.translate(offset),
+            clip_rect: info.clip_rect.translate(offset),
             is_backface_visible: info.is_backface_visible,
             tag: info.tag,
         }
     }
 
     pub fn clip_rect(&self) -> &LayoutRect {
         &self.iter.cur_item.info.clip_rect
     }
@@ -384,17 +384,17 @@ impl<'a, 'b> DisplayItemRef<'a, 'b> {
     // Creates a new iterator where this element's iterator is, to hack around borrowck.
     pub fn sub_iter(&self) -> BuiltDisplayListIter<'a> {
         BuiltDisplayListIter::new_with_list_and_data(self.iter.list, self.iter.data)
     }
 }
 
 impl<'de, 'a, T: Deserialize<'de>> AuxIter<'a, T> {
     pub fn new(mut data: &'a [u8]) -> Self {
-        let size: usize = if data.len() == 0 {
+        let size: usize = if data.is_empty() {
             0 // Accept empty ItemRanges pointing anywhere
         } else {
             bincode::deserialize_from(&mut UnsafeReader::new(&mut data)).expect("MEH: malicious input?")
         };
 
         AuxIter {
             data,
             size,
@@ -737,17 +737,17 @@ struct UnsafeReader<'a: 'b, 'b> {
 }
 
 impl<'a, 'b> UnsafeReader<'a, 'b> {
     #[inline(always)]
     fn new(buf: &'b mut &'a [u8]) -> UnsafeReader<'a, 'b> {
         unsafe {
             let end = buf.as_ptr().offset(buf.len() as isize);
             let start = buf.as_ptr();
-            UnsafeReader { start: start, end, slice: buf }
+            UnsafeReader { start, end, slice: buf }
         }
     }
 
     // This read implementation is significantly faster than the standard &[u8] one.
     //
     // First, it only supports reading exactly buf.len() bytes. This ensures that
     // the argument to memcpy is always buf.len() and will allow a constant buf.len()
     // to be propagated through to memcpy which LLVM will turn into explicit loads and
@@ -870,17 +870,17 @@ impl DisplayListBuilder {
         let state = self.save_state.take().expect("No save to restore DisplayListBuilder from");
 
         self.clip_stack.truncate(state.clip_stack_len);
         self.data.truncate(state.dl_len);
         self.next_clip_id = state.next_clip_id;
         self.next_clip_chain_id = state.next_clip_chain_id;
     }
 
-    /// Discards the builder's save (indicating the attempted operation was sucessful).
+    /// 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");
     }
 
     pub fn print_display_list(&mut self) {
         let mut temp = BuiltDisplayList::default();
         mem::swap(&mut temp.data, &mut self.data);
 
@@ -1171,18 +1171,18 @@ impl DisplayListBuilder {
         let (start_offset, end_offset) =
             DisplayListBuilder::normalize_stops(&mut stops, extend_mode);
 
         self.push_stops(&stops);
 
         RadialGradient {
             center,
             radius,
-            start_offset: start_offset,
-            end_offset: end_offset,
+            start_offset,
+            end_offset,
             extend_mode,
         }
     }
 
     pub fn push_border(
         &mut self,
         info: &LayoutPrimitiveInfo,
         widths: BorderWidths,
@@ -1218,17 +1218,17 @@ impl DisplayListBuilder {
     }
 
     /// 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 infinitly. If `tile_spacing`
+    /// 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 `info.rect.origin`.
     /// If the gradient should only be displayed once limit
     /// the `info.rect.size` to a single tile.
     /// The gradient is only visible within the local clip.
     pub fn push_gradient(
@@ -1420,17 +1420,17 @@ impl DisplayListBuilder {
     ) -> ClipId
     where
         I: IntoIterator<Item = ComplexClipRegion>,
         I::IntoIter: ExactSizeIterator + Clone,
     {
         let id = self.generate_clip_id();
         let item = SpecificDisplayItem::Clip(ClipDisplayItem {
             id,
-            image_mask: image_mask,
+            image_mask,
         });
 
         let info = LayoutPrimitiveInfo::new(clip_rect);
 
         let scrollinfo = ClipAndScrollInfo::simple(parent);
         self.push_item_with_clip_scroll_info(item, &info, scrollinfo);
         self.push_iter(complex_clips);
         id
@@ -1468,17 +1468,17 @@ impl DisplayListBuilder {
     }
 
     pub fn pop_clip_id(&mut self) {
         self.clip_stack.pop();
         if let Some(save_state) = self.save_state.as_ref() {
             assert!(self.clip_stack.len() >= save_state.clip_stack_len,
                     "Cannot pop clips that were pushed before the DisplayListBuilder save.");
         }
-        assert!(self.clip_stack.len() > 0);
+        assert!(!self.clip_stack.is_empty());
     }
 
     pub fn push_iframe(&mut self, info: &LayoutPrimitiveInfo, pipeline_id: PipelineId) {
         let item = SpecificDisplayItem::Iframe(IframeDisplayItem {
             clip_id: self.generate_clip_id(),
             pipeline_id,
         });
         self.push_item(item, info);
--- a/gfx/webrender_api/src/font.rs
+++ b/gfx/webrender_api/src/font.rs
@@ -98,17 +98,17 @@ pub enum FontRenderMode {
 #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Deserialize, Serialize, Ord, PartialOrd)]
 pub enum SubpixelDirection {
     None = 0,
     Horizontal,
     Vertical,
 }
 
 impl FontRenderMode {
-    // Skia quantizes subpixel offets into 1/4 increments.
+    // Skia quantizes subpixel offsets into 1/4 increments.
     // Given the absolute position, return the quantized increment
     fn subpixel_quantize_offset(&self, pos: f32) -> SubpixelOffset {
         // 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,
--- a/gfx/webrender_api/src/image.rs
+++ b/gfx/webrender_api/src/image.rs
@@ -1,12 +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/. */
 
+extern crate serde_bytes;
+
 use font::{FontInstanceKey, FontKey, FontTemplate};
 use std::sync::Arc;
 use {DevicePoint, DeviceUintRect, IdNamespace, TileOffset, TileSize};
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ImageKey(pub IdNamespace, pub u32);
 
@@ -105,38 +107,53 @@ impl ImageDescriptor {
 
     pub fn compute_total_size(&self) -> u32 {
         self.compute_stride() * self.height
     }
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize)]
 pub enum ImageData {
-    Raw(Arc<Vec<u8>>),
-    Blob(BlobImageData),
+    Raw(#[serde(with = "serde_image_data_raw")] Arc<Vec<u8>>),
+    Blob(#[serde(with = "serde_bytes")] BlobImageData),
     External(ExternalImageData),
 }
 
+mod serde_image_data_raw {
+    extern crate serde_bytes;
+
+    use std::sync::Arc;
+    use serde::{Deserializer, Serializer};
+
+    pub fn serialize<'a, S: Serializer>(bytes: &'a Arc<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error> {
+        serde_bytes::serialize(bytes.as_slice(), serializer)
+    }
+
+    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Arc<Vec<u8>>, D::Error> {
+        serde_bytes::deserialize(deserializer).map(Arc::new)
+    }
+}
+
 impl ImageData {
     pub fn new(bytes: Vec<u8>) -> Self {
         ImageData::Raw(Arc::new(bytes))
     }
 
     pub fn new_shared(bytes: Arc<Vec<u8>>) -> Self {
         ImageData::Raw(bytes)
     }
 
     pub fn new_blob_image(commands: Vec<u8>) -> Self {
         ImageData::Blob(commands)
     }
 
     #[inline]
     pub fn is_blob(&self) -> bool {
-        match self {
-            &ImageData::Blob(_) => true,
+        match *self {
+            ImageData::Blob(_) => true,
             _ => false,
         }
     }
 
     #[inline]
     pub fn uses_texture_cache(&self) -> bool {
         match *self {
             ImageData::External(ref ext_data) => match ext_data.image_type {
--- a/gfx/webrender_bindings/Cargo.toml
+++ b/gfx/webrender_bindings/Cargo.toml
@@ -9,17 +9,17 @@ rayon = "1"
 thread_profiler = "0.1.1"
 euclid = { version = "0.17", features = ["serde"] }
 app_units = "0.6"
 gleam = "0.4.20"
 log = "0.4"
 
 [dependencies.webrender]
 path = "../webrender"
-version = "0.57.0"
+version = "0.57.2"
 default-features = false
 features = ["capture"]
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.5"
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-22493328352ba432a7cd89491d81bfaa19bc1bce
+941bf5ac998061689a1bcd18d417f1f315e41ae6
--- a/gfx/wrench/Cargo.toml
+++ b/gfx/wrench/Cargo.toml
@@ -7,17 +7,17 @@ license = "MPL-2.0"
 
 [dependencies]
 base64 = "0.6"
 bincode = "1.0"
 byteorder = "1.0"
 env_logger = { version = "0.5", optional = true }
 euclid = "0.17"
 gleam = "0.4"
-glutin = "0.12"
+glutin = "0.13"
 app_units = "0.6"
 image = "0.18"
 clap = { version = "2", features = ["yaml"] }
 lazy_static = "1"
 log = "0.4"
 yaml-rust = { git = "https://github.com/vvuk/yaml-rust", features = ["preserve_order"] }
 serde_json = "1.0"
 ron = "0.1.5"
--- a/gfx/wrench/src/args.yaml
+++ b/gfx/wrench/src/args.yaml
@@ -105,17 +105,17 @@ subcommands:
               help: The input YAML file
               required: true
               index: 1
     - replay:
         about: replay binary recording
         args:
           - api:
               long: api
-              help: Reissue Api messsages for each frame
+              help: Reissue Api messages for each frame
           - skip-uploads:
               long: skip-uploads
               help: Skip re-uploads while reissuing Api messages (BROKEN)
           - play:
               long: play
               help: Play entire recording through, then quit (useful with --save)
           - INPUT:
               help: The input binary file or directory
--- a/gfx/wrench/src/blob.rs
+++ b/gfx/wrench/src/blob.rs
@@ -4,17 +4,17 @@
 
 // A very basic BlobImageRenderer that can only render a checkerboard pattern.
 
 use std::collections::HashMap;
 use std::sync::Arc;
 use std::sync::Mutex;
 use webrender::api::*;
 
-// Serialize/deserialze the blob.
+// Serialize/deserialize the blob.
 
 pub fn serialize_blob(color: ColorU) -> Vec<u8> {
     vec![color.r, color.g, color.b, color.a]
 }
 
 fn deserialize_blob(blob: &[u8]) -> Result<ColorU, ()> {
     let mut iter = blob.iter();
     return match (iter.next(), iter.next(), iter.next(), iter.next()) {
@@ -50,32 +50,32 @@ fn render_blob(
             let y2 = y + descriptor.offset.y as u32;
 
             // Render a simple checkerboard pattern
             let checker = if (x2 % 20 >= 10) != (y2 % 20 >= 10) {
                 1
             } else {
                 0
             };
-            // ..nested in the per-tile cherkerboard pattern
+            // ..nested in the per-tile checkerboard pattern
             let tc = if tile_checker { 0 } else { (1 - checker) * 40 };
 
             match descriptor.format {
                 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);
                 }
                 ImageFormat::R8 => {
                     texels.push(color.a * checker + tc);
                 }
                 _ => {
                     return Err(BlobImageError::Other(
-                        format!("Usupported image format {:?}", descriptor.format),
+                        format!("Unsupported image format {:?}", descriptor.format),
                     ));
                 }
             }
         }
     }
 
     Ok(RasterizedBlobImage {
         data: texels,