diff --git a/vp8/dixie/dixie.c b/vp8/dixie/dixie.c
index cd61ff50e22dbbe1bd5d7af4bfb0ef3b17b5862a..6328163340d4770b09cdc3e36ab446e967e12113 100644
--- a/vp8/dixie/dixie.c
+++ b/vp8/dixie/dixie.c
@@ -15,6 +15,7 @@
 #include "modemv.h"
 #include "tokens.h"
 #include "predict.h"
+#include "dixie_loopfilter.h"
 #include <string.h>
 #include <assert.h>
 
@@ -384,10 +385,16 @@ decode_frame(struct vp8_decoder_ctx *ctx,
         vp8_dixie_tokens_process_row(ctx, partition, row, 0, ctx->mb_cols);
         vp8_dixie_predict_process_row(ctx, row, 0, ctx->mb_cols);
 
+        if (ctx->loopfilter_hdr.level && row)
+            vp8_dixie_loopfilter_process_row(ctx, row - 1, 0, ctx->mb_cols);
+
         if (++partition == ctx->token_hdr.partitions)
             partition = 0;
     }
 
+    if (ctx->loopfilter_hdr.level)
+        vp8_dixie_loopfilter_process_row(ctx, row - 1, 0, ctx->mb_cols);
+
     ctx->frame_cnt++;
 
     if (!ctx->reference_hdr.refresh_entropy)
diff --git a/vp8/dixie/dixie_loopfilter.c b/vp8/dixie/dixie_loopfilter.c
new file mode 100644
index 0000000000000000000000000000000000000000..5aec36c571aafe84e89558b691eda83d84662e60
--- /dev/null
+++ b/vp8/dixie/dixie_loopfilter.c
@@ -0,0 +1,392 @@
+/*
+ *  Copyright (c) 2010 The VP8 project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+#include "dixie.h"
+#include "dixie_loopfilter.h"
+
+#define ABS(x) ((x) >= 0 ? (x) : -(x))
+
+#define p3 pixels[-4*stride]
+#define p2 pixels[-3*stride]
+#define p1 pixels[-2*stride]
+#define p0 pixels[-1*stride]
+#define q0 pixels[ 0*stride]
+#define q1 pixels[ 1*stride]
+#define q2 pixels[ 2*stride]
+#define q3 pixels[ 3*stride]
+
+#define static
+static int
+saturate_int8(int x)
+{
+    if (x < -128)
+        return -128;
+
+    if (x > 127)
+        return 127;
+
+    return x;
+}
+
+
+static int
+saturate_uint8(int x)
+{
+    if (x < 0)
+        return 0;
+
+    if (x > 255)
+        return 255;
+
+    return x;
+}
+
+
+static int
+high_edge_variance(unsigned char *pixels,
+                   int            stride,
+                   int            hev_threshold)
+{
+    return ABS(p1 - p0) > hev_threshold || ABS(q1 - q0) > hev_threshold;
+}
+
+static int
+normal_threshold(unsigned char *pixels,
+                 int            stride,
+                 int            edge_limit,
+                 int            interior_limit)
+{
+    int E = edge_limit;
+    int I = interior_limit;
+
+    /* Note: Deviates from spec */
+    return (ABS(p0 - q0) * 2 + (ABS(p1 - q1) >> 1)) <= 2 * E + I
+           && ABS(p3 - p2) <= I && ABS(p2 - p1) <= I && ABS(p1 - p0) <= I
+           && ABS(q3 - q2) <= I && ABS(q2 - q1) <= I && ABS(q1 - q0) <= I;
+}
+
+
+static void
+filter_common(unsigned char *pixels,
+              int            stride,
+              int            use_outer_taps)
+{
+    int a, f1, f2;
+
+    /* This logic cribbed from ffvp8, which incorporates a number of
+     * deviations from the bitstream guide to match what the libvpx
+     * reference code does.
+     */
+
+    a = 3 * (q0 - p0);
+
+    if (use_outer_taps)
+        a += saturate_int8(p1 - q1);
+
+    a = saturate_int8(a);
+
+    f1 = ((a + 4 > 127) ? 127 : a + 4) >> 3;
+    f2 = ((a + 3 > 127) ? 127 : a + 3) >> 3;
+
+    p0 = saturate_uint8(p0 + f2);
+    q0 = saturate_uint8(q0 - f1);
+
+    if (!use_outer_taps)
+    {
+        /* This handles the case of subblock_filter() (from the bitstream
+         * guide.
+         */
+        a = (f1 + 1) >> 1;
+        p1 = saturate_uint8(p1 + a);
+        q1 = saturate_uint8(q1 - a);
+    }
+}
+
+
+static void
+filter_mb_edge(unsigned char *pixels,
+               int            stride)
+{
+    int w, a;
+
+    w = saturate_int8(saturate_int8(p1 - q1) + 3 * (q0 - p0));
+
+    a = (27 * w + 63) >> 7;
+    p0 = saturate_uint8(p0 + a);
+    q0 = saturate_uint8(q0 - a);
+
+    a = (18 * w + 63) >> 7;
+    p1 = saturate_uint8(p1 + a);
+    q1 = saturate_uint8(q1 - a);
+
+    a = (9 * w + 63) >> 7;
+    p2 = saturate_uint8(p2 + a);
+    q2 = saturate_uint8(q2 - a);
+
+}
+
+
+static void
+filter_mb_v_edge(unsigned char *src,
+                 int            stride,
+                 int            edge_limit,
+                 int            interior_limit,
+                 int            hev_threshold,
+                 int            size)
+{
+    int i;
+
+    for (i = 0; i < 8 * size; i++)
+    {
+        if (normal_threshold(src, 1, edge_limit, interior_limit))
+        {
+            if (high_edge_variance(src, 1, hev_threshold))
+                filter_common(src, 1, 1);
+            else
+                filter_mb_edge(src, 1);
+        }
+
+        src += stride;
+    }
+}
+
+
+static void
+filter_subblock_v_edge(unsigned char *src,
+                       int            stride,
+                       int            edge_limit,
+                       int            interior_limit,
+                       int            hev_threshold,
+                       int            size)
+{
+    int i;
+
+    for (i = 0; i < 8 * size; i++)
+    {
+        if (normal_threshold(src, 1, edge_limit, interior_limit))
+            filter_common(src, 1, high_edge_variance(src, 1, hev_threshold));
+
+        src += stride;
+    }
+}
+
+
+static void
+filter_mb_h_edge(unsigned char *src,
+                 int            stride,
+                 int            edge_limit,
+                 int            interior_limit,
+                 int            hev_threshold,
+                 int            size)
+{
+    int i;
+
+    for (i = 0; i < 8 * size; i++)
+    {
+        if (normal_threshold(src, stride, edge_limit, interior_limit))
+        {
+            if (high_edge_variance(src, stride, hev_threshold))
+                filter_common(src, stride, 1);
+            else
+                filter_mb_edge(src, stride);
+        }
+
+        src += 1;
+    }
+}
+
+
+static void
+filter_subblock_h_edge(unsigned char *src,
+                       int            stride,
+                       int            edge_limit,
+                       int            interior_limit,
+                       int            hev_threshold,
+                       int            size)
+{
+    int i;
+
+    for (i = 0; i < 8 * size; i++)
+    {
+        if (normal_threshold(src, stride, edge_limit, interior_limit))
+            filter_common(src, stride,
+                          high_edge_variance(src, stride, hev_threshold));
+
+        src += 1;
+    }
+}
+
+
+static void
+calculate_filter_parameters(struct vp8_decoder_ctx *ctx,
+                            struct mb_info         *mbi,
+                            int                    *edge_limit_,
+                            int                    *interior_limit_,
+                            int                    *hev_threshold_)
+{
+    int filter_level, interior_limit, hev_threshold;
+
+    /* Reference code/spec seems to conflate filter_level and edge_limit */
+
+    filter_level = ctx->loopfilter_hdr.level;
+
+    if (ctx->segment_hdr.enabled)
+    {
+        if (!ctx->segment_hdr.abs)
+            filter_level += ctx->segment_hdr.lf_level[mbi->base.segment_id];
+        else
+            filter_level = ctx->segment_hdr.lf_level[mbi->base.segment_id];
+    }
+
+    if (ctx->loopfilter_hdr.delta_enabled)
+    {
+        filter_level += ctx->loopfilter_hdr.ref_delta[mbi->base.ref_frame];
+
+        if (mbi->base.ref_frame == CURRENT_FRAME)
+        {
+            if (mbi->base.y_mode == B_PRED)
+                filter_level += ctx->loopfilter_hdr.mode_delta[0];
+        }
+        else if (mbi->base.y_mode == ZEROMV)
+            filter_level += ctx->loopfilter_hdr.mode_delta[1];
+        else if (mbi->base.y_mode == SPLITMV)
+            filter_level += ctx->loopfilter_hdr.mode_delta[3];
+        else
+            filter_level += ctx->loopfilter_hdr.mode_delta[2];
+    }
+
+    if (filter_level > 63)
+        filter_level = 63;
+    else if (filter_level < 0)
+        filter_level = 0;
+
+    interior_limit = filter_level;
+
+    if (ctx->loopfilter_hdr.sharpness)
+    {
+        interior_limit >>= ctx->loopfilter_hdr.sharpness > 4 ? 2 : 1;
+
+        if (interior_limit > 9 - ctx->loopfilter_hdr.sharpness)
+            interior_limit = 9 - ctx->loopfilter_hdr.sharpness;
+    }
+
+    if (interior_limit < 1)
+        interior_limit = 1;
+
+    hev_threshold = (filter_level >= 15);
+
+    if (filter_level >= 40)
+        hev_threshold++;
+
+    if (filter_level >= 20 && !ctx->frame_hdr.is_keyframe)
+        hev_threshold++;
+
+    *edge_limit_ = filter_level;
+    *interior_limit_ = interior_limit;
+    *hev_threshold_ = hev_threshold;
+}
+
+#include <assert.h>
+void
+vp8_dixie_loopfilter_process_row(struct vp8_decoder_ctx *ctx,
+                                 unsigned int            row,
+                                 unsigned int            start_col,
+                                 unsigned int            num_cols)
+{
+    unsigned char  *y, *u, *v;
+    int             stride, uv_stride;
+    struct mb_info *mbi;
+    unsigned int    col;
+    int             i;
+
+    /* Adjust pointers based on row, start_col */
+    stride    = ctx->ref_frames[CURRENT_FRAME]->img.stride[PLANE_Y];
+    uv_stride = ctx->ref_frames[CURRENT_FRAME]->img.stride[PLANE_U];
+    y = ctx->ref_frames[CURRENT_FRAME]->img.planes[PLANE_Y];
+    u = ctx->ref_frames[CURRENT_FRAME]->img.planes[PLANE_U];
+    v = ctx->ref_frames[CURRENT_FRAME]->img.planes[PLANE_V];
+    y += (stride * row + start_col) * 16;
+    u += (uv_stride * row + start_col) * 8;
+    v += (uv_stride * row + start_col) * 8;
+    mbi = ctx->mb_info_rows[row] + start_col;
+
+    for (col = start_col; col < start_col + num_cols; col++)
+    {
+        int edge_limit, interior_limit, hev_threshold;
+
+        /* TODO: only need to recalculate every MB if segmentation is
+         * enabled.
+         */
+        calculate_filter_parameters(ctx, mbi, &edge_limit, &interior_limit,
+                                    &hev_threshold);
+
+        if (edge_limit)
+        {
+            if (col)
+            {
+                filter_mb_v_edge(y, stride, edge_limit + 2, interior_limit,
+                                 hev_threshold, 2);
+                filter_mb_v_edge(u, uv_stride, edge_limit + 2, interior_limit,
+                                 hev_threshold, 1);
+                filter_mb_v_edge(v, uv_stride, edge_limit + 2, interior_limit,
+                                 hev_threshold, 1);
+            }
+
+            /* NOTE: This conditional is actually dependent on the number
+             * of coefficients decoded, not the skip flag as coded in the
+             * bitstream. The tokens task is expected to set 31 if there
+             * is *any* non-zero data.
+             */
+            if (mbi->base.eob_mask
+                || mbi->base.y_mode == SPLITMV || mbi->base.y_mode == B_PRED)
+            {
+                filter_subblock_v_edge(y + 4, stride, edge_limit,
+                                       interior_limit, hev_threshold, 2);
+                filter_subblock_v_edge(y + 8, stride, edge_limit,
+                                       interior_limit, hev_threshold, 2);
+                filter_subblock_v_edge(y + 12, stride, edge_limit,
+                                       interior_limit, hev_threshold, 2);
+                filter_subblock_v_edge(u + 4, uv_stride, edge_limit,
+                                       interior_limit, hev_threshold, 1);
+                filter_subblock_v_edge(v + 4, uv_stride, edge_limit,
+                                       interior_limit, hev_threshold, 1);
+            }
+
+            if (row)
+            {
+                filter_mb_h_edge(y, stride, edge_limit + 2, interior_limit,
+                                 hev_threshold, 2);
+                filter_mb_h_edge(u, uv_stride, edge_limit + 2, interior_limit,
+                                 hev_threshold, 1);
+                filter_mb_h_edge(v, uv_stride, edge_limit + 2, interior_limit,
+                                 hev_threshold, 1);
+            }
+
+            if (mbi->base.eob_mask
+                || mbi->base.y_mode == SPLITMV || mbi->base.y_mode == B_PRED)
+            {
+                filter_subblock_h_edge(y + 4 * stride, stride, edge_limit,
+                                       interior_limit, hev_threshold, 2);
+                filter_subblock_h_edge(y + 8 * stride, stride, edge_limit,
+                                       interior_limit, hev_threshold, 2);
+                filter_subblock_h_edge(y + 12 * stride, stride, edge_limit,
+                                       interior_limit, hev_threshold, 2);
+                filter_subblock_h_edge(u + 4 * uv_stride, uv_stride, edge_limit,
+                                       interior_limit, hev_threshold, 1);
+                filter_subblock_h_edge(v + 4 * uv_stride, uv_stride, edge_limit,
+                                       interior_limit, hev_threshold, 1);
+            }
+        }
+
+        y += 16;
+        u += 8;
+        v += 8;
+        mbi++;
+    }
+}
diff --git a/vp8/dixie/dixie_loopfilter.h b/vp8/dixie/dixie_loopfilter.h
new file mode 100644
index 0000000000000000000000000000000000000000..fa8324b306a3d6f6b25ec4b398327dcab45107d1
--- /dev/null
+++ b/vp8/dixie/dixie_loopfilter.h
@@ -0,0 +1,19 @@
+/*
+ *  Copyright (c) 2010 The VP8 project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+#ifndef DIXIE_LOOPFILTER_H
+#define DIXIE_LOOPFILTER_H
+
+void
+vp8_dixie_loopfilter_process_row(struct vp8_decoder_ctx *ctx,
+                                 unsigned int            row,
+                                 unsigned int            start_col,
+                                 unsigned int            num_cols);
+
+#endif
diff --git a/vp8/dixie/tokens.c b/vp8/dixie/tokens.c
index c55195152bea26ee34b86af8d69454df762d6e89..ecbacf0f076d1acba44f776f88ba396d161cb73d 100644
--- a/vp8/dixie/tokens.c
+++ b/vp8/dixie/tokens.c
@@ -330,6 +330,7 @@ ONE_CONTEXT_NODE_0_:
 BLOCK_FINISHED:
     eob_mask |= (c > 1) << i;
     t = (c != !type);   // any nonzero data?
+    eob_mask |= t << 31;
 
     left[left_context_index[i]] = above[above_context_index[i]] = t;
     b_tokens += 16;
diff --git a/vp8/vp8dx.mk b/vp8/vp8dx.mk
index 1ac10956ea140c4512ae423710346957057c3cc8..7ae3875aed286e69481d8047c605cd5bfe864b11 100644
--- a/vp8/vp8dx.mk
+++ b/vp8/vp8dx.mk
@@ -38,6 +38,8 @@ VP8_DX_SRCS-$(CONFIG_DIXIE) += dixie/predict.c
 VP8_DX_SRCS-$(CONFIG_DIXIE) += dixie/predict.h
 VP8_DX_SRCS-$(CONFIG_DIXIE) += dixie/idct_add.c
 VP8_DX_SRCS-$(CONFIG_DIXIE) += dixie/idct_add.h
+VP8_DX_SRCS-$(CONFIG_DIXIE) += dixie/dixie_loopfilter.c
+VP8_DX_SRCS-$(CONFIG_DIXIE) += dixie/dixie_loopfilter.h
 
 CFLAGS+=-I$(SRC_PATH_BARE)/$(VP8_PREFIX)decoder