Bug 1449562 - Update webrender to commit 941bf5ac998061689a1bcd18d417f1f315e41ae6. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Wed, 04 Apr 2018 15:21:50 -0400
changeset 777448 98c66b47c1a376b76ac043059e89217488917b9d
parent 777437 c23c7481957f7b982cffc0ce1d25979c69ca2c2f
child 777449 26a9f58fd46e1fc7ae61ecc2c7e5fbf5e4847011
push id105209
push userkgupta@mozilla.com
push dateWed, 04 Apr 2018 19:24:07 +0000
reviewersjrmuizel
bugs1449562
milestone61.0a1
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 0000000000000000000000000000000000000000..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,