Index: trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java	(revision 9785)
+++ trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java	(revision 9786)
@@ -33,4 +33,5 @@
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.ForkJoinTask;
@@ -237,4 +238,30 @@
     }
 
+    /**
+     * Saves benchmark data for tests.
+     */
+    public static class BenchmarkData {
+        public long generateTime;
+        public long sortTime;
+        public long drawTime;
+        public Map<Class<? extends StyleElement>, Integer> styleElementCount;
+        public boolean skipDraw;
+
+        private void recordElementStats(List<StyleRecord> srs) {
+            styleElementCount = new HashMap<>();
+            for (StyleRecord r : srs) {
+                Class<? extends StyleElement> klass = r.style.getClass();
+                Integer count = styleElementCount.get(klass);
+                if (count == null) {
+                    count = 0;
+                }
+                styleElementCount.put(klass, count + 1);
+            }
+
+        }
+    }
+    /* can be set by tests, if detailed benchmark data is requested */
+    public BenchmarkData benchmarkData = null;
+
     private static Map<Font, Boolean> IS_GLYPH_VECTOR_DOUBLE_TRANSLATION_BUG = new HashMap<>();
 
@@ -1868,5 +1895,6 @@
         BBox bbox = bounds.toBBox();
         getSettings(renderVirtualNodes);
-        boolean benchmark = Main.isTraceEnabled() || Main.pref.getBoolean("mappaint.render.benchmark", false);
+        boolean benchmarkOutput = Main.isTraceEnabled() || Main.pref.getBoolean("mappaint.render.benchmark", false);
+        boolean benchmark = benchmarkOutput || benchmarkData != null;
 
         data.getReadLock().lock();
@@ -1874,5 +1902,5 @@
             highlightWaySegments = data.getHighlightedWaySegments();
 
-            long timeStart = 0, timePhase1 = 0, timeFinished;
+            long timeStart = 0, timeGenerateDone = 0, timeSortingDone = 0, timeFinished;
             if (benchmark) {
                 timeStart = System.currentTimeMillis();
@@ -1897,9 +1925,23 @@
 
             if (benchmark) {
-                timePhase1 = System.currentTimeMillis();
-                System.err.print("phase 1 (calculate styles): " + Utils.getDurationString(timePhase1 - timeStart));
+                timeGenerateDone = System.currentTimeMillis();
+                if (benchmarkOutput) {
+                    System.err.print("phase 1 (calculate styles): " + Utils.getDurationString(timeGenerateDone - timeStart));
+                }
+                if (benchmarkData != null) {
+                    benchmarkData.generateTime = timeGenerateDone - timeStart;
+                }
             }
 
             Collections.sort(allStyleElems); // TODO: try parallel sort when switching to Java 8
+
+            if (benchmarkData != null) {
+                timeSortingDone = System.currentTimeMillis();
+                benchmarkData.sortTime = timeSortingDone - timeGenerateDone;
+                if (benchmarkData.skipDraw) {
+                    benchmarkData.recordElementStats(allStyleElems);
+                    return;
+                }
+            }
 
             for (StyleRecord r : allStyleElems) {
@@ -1916,7 +1958,13 @@
             if (benchmark) {
                 timeFinished = System.currentTimeMillis();
-                System.err.println("; phase 2 (draw): " + Utils.getDurationString(timeFinished - timePhase1) +
-                    "; total: " + Utils.getDurationString(timeFinished - timeStart) +
-                    " (scale: " + circum + " zoom level: " + Selector.GeneralSelector.scale2level(circum) + ')');
+                if (benchmarkData != null) {
+                    benchmarkData.drawTime = timeFinished - timeGenerateDone;
+                    benchmarkData.recordElementStats(allStyleElems);
+                }
+                if (benchmarkOutput) {
+                    System.err.println("; phase 2 (draw): " + Utils.getDurationString(timeFinished - timeGenerateDone) +
+                        "; total: " + Utils.getDurationString(timeFinished - timeStart) +
+                        " (scale: " + circum + " zoom level: " + Selector.GeneralSelector.scale2level(circum) + ')');
+                }
             }
 
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/StyleSetting.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/StyleSetting.java	(revision 9785)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/StyleSetting.java	(revision 9786)
@@ -60,10 +60,5 @@
                 @Override
                 public void actionPerformed(ActionEvent e) {
-                    boolean b = item.isSelected();
-                    if (b == def) {
-                        Main.pref.put(prefKey, null);
-                    } else {
-                        Main.pref.put(prefKey, b);
-                    }
+                    setValue(item.isSelected());
                     Main.worker.submit(new MapPaintStyles.MapPaintStyleLoader(Arrays.asList(parentStyle)));
                 }
@@ -95,4 +90,16 @@
             return Boolean.valueOf(val);
         }
+
+        public void setValue(Object o) {
+            if (o == null || !(o instanceof Boolean)) {
+                throw new IllegalArgumentException();
+            }
+            boolean b = (Boolean) o;
+            if (b == def) {
+                Main.pref.put(prefKey, null);
+            } else {
+                Main.pref.put(prefKey, b);
+            }
+        }
     }
 }
Index: trunk/test/data/styles/filter.mapcss
===================================================================
--- trunk/test/data/styles/filter.mapcss	(revision 9786)
+++ trunk/test/data/styles/filter.mapcss	(revision 9786)
@@ -0,0 +1,70 @@
+meta {
+    title: "filter style elments";
+}
+
+setting::icon_off {
+    type: boolean;
+    label: "icon";
+    default: false;
+}
+
+setting::symbol_off {
+    type: boolean;
+    label: "symbol";
+    default: false;
+}
+
+setting::node_text_off {
+    type: boolean;
+    label: "node_text";
+    default: false;
+}
+
+setting::line_off {
+    type: boolean;
+    label: "line";
+    default: false;
+}
+
+setting::line_text_off {
+    type: boolean;
+    label: "line_text";
+    default: false;
+}
+
+setting::area_off {
+    type: boolean;
+    label: "area";
+    default: false;
+}
+
+canvas[setting("line_off")] {
+    default-lines: false;
+}
+
+node[setting("icon_off")]::*, relation[setting("icon_off")]::* {
+    icon-image: none;
+}
+
+node[setting("symbol_off")]::* {
+    symbol-shape: none;
+}
+
+node[setting("node_text_off")]::* {
+    text: none;
+}
+
+way[setting("line_off")]::*, relation[setting("line_off")]::* {
+    width: none;
+    casing-width: none;
+    repeat-image: none;
+}
+
+way[prop("text-position", "default")="line"][setting("line_text_off")]::* {
+    text: none;
+}
+
+area[setting("area_off")]::* {
+    fill-color : none;
+}
+
Index: trunk/test/performance/org/openstreetmap/josm/PerformanceTestUtils.java
===================================================================
--- trunk/test/performance/org/openstreetmap/josm/PerformanceTestUtils.java	(revision 9785)
+++ trunk/test/performance/org/openstreetmap/josm/PerformanceTestUtils.java	(revision 9786)
@@ -3,4 +3,6 @@
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+import org.openstreetmap.josm.io.XmlWriter;
 
 /**
@@ -14,6 +16,6 @@
      */
     public static class PerformanceTestTimer {
-        private String name;
-        private long time;
+        private final String name;
+        private final long time;
         private boolean measurementPlotsPlugin = false;
 
@@ -37,5 +39,5 @@
             long dTime = (System.nanoTime() - time) / 1000000;
             if (measurementPlotsPlugin) {
-                System.out.println(String.format("<measurement><name>%s (ms)</name><value>%.1f</value></measurement>", name, (double) dTime));
+                measurementPlotsPluginOutput(name + "(ms)", dTime);
             } else {
                 System.out.println("TIMER " + name + ": " + dTime + "ms");
@@ -58,3 +60,16 @@
         return new PerformanceTestTimer(name);
     }
+
+    /**
+     * Emit one data value for the Jenkins Measurement Plots Plugin.
+     * 
+     * The plugin collects the values over multiple builds and plots them in a diagram.
+     * 
+     * @see https://wiki.jenkins-ci.org/display/JENKINS/Measurement+Plots+Plugin
+     * @param name the name / title of the measurement
+     * @param value the value
+     */
+    public static void measurementPlotsPluginOutput(String name, double value) {
+        System.out.println("<measurement><name>"+XmlWriter.encode(name)+"</name><value>"+value+"</value></measurement>");
+    }
 }
Index: trunk/test/performance/org/openstreetmap/josm/gui/mappaint/MapRendererPerformanceTest.java
===================================================================
--- trunk/test/performance/org/openstreetmap/josm/gui/mappaint/MapRendererPerformanceTest.java	(revision 9786)
+++ trunk/test/performance/org/openstreetmap/josm/gui/mappaint/MapRendererPerformanceTest.java	(revision 9786)
@@ -0,0 +1,319 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.mappaint;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.imageio.ImageIO;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.Timeout;
+import org.openstreetmap.josm.JOSMFixture;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.PerformanceTestUtils;
+import org.openstreetmap.josm.TestUtils;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
+import org.openstreetmap.josm.data.projection.Projections;
+import org.openstreetmap.josm.gui.NavigatableComponent;
+import org.openstreetmap.josm.gui.mappaint.StyleSetting.BooleanStyleSetting;
+import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
+import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
+import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement;
+import org.openstreetmap.josm.gui.preferences.SourceEntry;
+import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
+import org.openstreetmap.josm.io.Compression;
+import org.openstreetmap.josm.io.OsmReader;
+
+public class MapRendererPerformanceTest {
+
+    private static final boolean DUMP_IMAGE = false; // dump images to file for debugging purpose
+
+    private static final int IMG_WIDTH = 2048;
+    private static final int IMG_HEIGHT = 1536;
+
+    private static Graphics2D g;
+    private static BufferedImage img;
+    private static NavigatableComponent nc;
+    private static DataSet dsCity;
+    private static final Bounds BOUNDS_CITY_ALL = new Bounds(53.4382, 13.1094, 53.6153, 13.4074, false);
+    private static final LatLon LL_CITY = new LatLon(53.5574458, 13.2602781);
+    private static final double SCALE_Z17 = 1.5;
+
+    private static int defaultStyleIdx;
+    private static BooleanStyleSetting hideIconsSetting;
+
+    private static int filterStyleIdx;
+    private static StyleSource filterStyle;
+
+    private enum Feature {
+        ICON, SYMBOL, NODE_TEXT, LINE, LINE_TEXT, AREA;
+        public String label() {
+            return name().toLowerCase();
+        }
+    }
+    private static final EnumMap<Feature, BooleanStyleSetting> filters = new EnumMap<>(Feature.class);
+
+    /**
+     * Global timeout applied to all test methods.
+     */
+    @Rule
+    public Timeout globalTimeout = Timeout.seconds(15*60);
+
+    @BeforeClass
+    public static void load() throws Exception {
+        JOSMFixture.createPerformanceTestFixture().init(true);
+
+        img = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, BufferedImage.TYPE_INT_ARGB);
+        g = (Graphics2D) img.getGraphics();
+        g.setClip(0, 0, IMG_WIDTH, IMG_WIDTH);
+        g.setColor(Color.BLACK);
+        g.fillRect(0, 0, IMG_WIDTH, IMG_WIDTH);
+        nc = Main.map.mapView;
+        nc.setBounds(0, 0, IMG_WIDTH, IMG_HEIGHT);
+
+        MapPaintStyles.readFromPreferences();
+
+        SourceEntry se = new MapCSSStyleSource(TestUtils.getTestDataRoot() + "styles/filter.mapcss", "filter", "");
+        filterStyle = MapPaintStyles.addStyle(se);
+        List<StyleSource> sources = MapPaintStyles.getStyles().getStyleSources();
+        filterStyleIdx = sources.indexOf(filterStyle);
+        Assert.assertEquals(2, filterStyleIdx);
+
+        Assert.assertEquals(Feature.values().length, filterStyle.settings.size());
+        for (StyleSetting set : filterStyle.settings) {
+            BooleanStyleSetting bset = (BooleanStyleSetting) set;
+            String prefKey = bset.prefKey;
+            boolean found = false;
+            for (Feature f : Feature.values()) {
+                if (prefKey.endsWith(":" + f.label() + "_off")) {
+                    filters.put(f, bset);
+                    found = true;
+                    break;
+                }
+            }
+            Assert.assertTrue(prefKey, found);
+        }
+
+        MapCSSStyleSource defaultStyle = null;
+        for (int i = 0; i< sources.size(); i++) {
+            StyleSource s = sources.get(i);
+            if ("resource://styles/standard/elemstyles.mapcss".equals(s.url)) {
+                defaultStyle = (MapCSSStyleSource) s;
+                defaultStyleIdx = i;
+                break;
+            }
+        }
+        Assert.assertNotNull(defaultStyle);
+
+        for (StyleSetting set : defaultStyle.settings) {
+            if (set instanceof BooleanStyleSetting) {
+                BooleanStyleSetting bset = (BooleanStyleSetting) set;
+                if (bset.prefKey.endsWith(":hide_icons")) {
+                    hideIconsSetting = bset;
+                }
+            }
+        }
+        Assert.assertNotNull(hideIconsSetting);
+        hideIconsSetting.setValue(false);
+        MapPaintStyles.reloadStyles(defaultStyleIdx);
+
+        try (
+            InputStream fisC = Compression.getUncompressedFileInputStream(new File("data_nodist/neubrandenburg.osm.bz2"));
+        ) {
+            dsCity = OsmReader.parseDataSet(fisC, NullProgressMonitor.INSTANCE);
+        }
+    }
+
+    @AfterClass
+    public static void cleanUp() {
+        setFilterStyleActive(false);
+        if (hideIconsSetting != null) {
+            hideIconsSetting.setValue(true);
+        }
+        MapPaintStyles.reloadStyles(defaultStyleIdx);
+    }
+
+    private static class PerformanceTester {
+        public double scale = 0;
+        public LatLon center = LL_CITY;
+        public Bounds bounds;
+        public int noWarmup = 3;
+        public int noIterations = 7;
+        public boolean dumpImage = DUMP_IMAGE;
+        public boolean skipDraw = false;
+        public boolean clearStyleCache = true;
+        public String label = "";
+        public boolean mpGenerate = false;
+        public boolean mpSort = false;
+        public boolean mpDraw = false;
+        public boolean mpTotal = false;
+
+        private final List<Long> generateTimes = new ArrayList<>();
+        private final List<Long> sortTimes = new ArrayList<>();
+        private final List<Long> drawTimes = new ArrayList<>();
+        private final List<Long> totalTimes = new ArrayList<>();
+
+        public void run() throws IOException {
+            boolean checkScale = false;
+            if (scale == 0) {
+                checkScale = true;
+                scale = SCALE_Z17;
+            }
+            nc.zoomTo(Projections.project(center), scale);
+            if (checkScale) {
+                int lvl = Selector.OptimizedGeneralSelector.scale2level(nc.getDist100Pixel());
+                Assert.assertEquals(17, lvl);
+            }
+
+            if (bounds == null) {
+                bounds = nc.getLatLonBounds(g.getClipBounds());
+            }
+
+            StyledMapRenderer renderer = new StyledMapRenderer(g, nc, false);
+
+            int noTotal = noWarmup + noIterations;
+            for (int i = 1; i <= noTotal; i++) {
+                g.setColor(Color.BLACK);
+                g.fillRect(0, 0, IMG_WIDTH, IMG_WIDTH);
+                if (clearStyleCache) {
+                    MapPaintStyles.getStyles().clearCached();
+                }
+                System.gc();
+                System.runFinalization();
+                try {
+                    Thread.sleep(300);
+                } catch (InterruptedException ex) {}
+                StyledMapRenderer.BenchmarkData data = new StyledMapRenderer.BenchmarkData();
+                data.skipDraw = skipDraw;
+                renderer.benchmarkData = data;
+                renderer.render(dsCity, false, bounds);
+
+                if (i > noWarmup) {
+                    generateTimes.add(data.generateTime);
+                    sortTimes.add(data.sortTime);
+                    drawTimes.add(data.drawTime);
+                    totalTimes.add(data.generateTime + data.sortTime + data.drawTime);
+                }
+                dump(data);
+                if (dumpImage && i == noTotal) {
+                    dumpRenderedImage(label);
+                }
+            }
+
+            if (mpGenerate) {
+                processTimes(generateTimes, "generate");
+            }
+            if (mpSort) {
+                processTimes(sortTimes, "sort");
+            }
+            if (mpDraw) {
+                processTimes(drawTimes, "draw");
+            }
+            if (mpTotal) {
+                processTimes(totalTimes, "total");
+            }
+        }
+
+        private void processTimes(List<Long> times, String sublabel) {
+            Collections.sort(times);
+            // Take median instead of average. This should give a more stable
+            // result and avoids distortions by outliers.
+            long medianTime = times.get(times.size() / 2);
+            PerformanceTestUtils.measurementPlotsPluginOutput(label + " " + sublabel + " (ms)", medianTime);
+        }
+    }
+
+    /**
+     * Test phase 1, the calculation of {@link StyleElement}s.
+     * @throws IOException in case of an I/O error
+     */
+    @Test
+    public void testPerformanceGenerate() throws IOException {
+        setFilterStyleActive(false);
+        PerformanceTester test = new PerformanceTester();
+        test.bounds = BOUNDS_CITY_ALL;
+        test.label = "big";
+        test.skipDraw = true;
+        test.dumpImage = false;
+        test.noWarmup = 3;
+        test.noIterations = 10;
+        test.mpGenerate = true;
+        test.clearStyleCache = true;
+        test.run();
+    }
+
+    private static void testDrawFeature(Feature feature) throws IOException {
+        PerformanceTester test = new PerformanceTester();
+        test.noWarmup = 3;
+        test.noIterations = 10;
+        test.mpDraw = true;
+        test.clearStyleCache = false;
+        if (feature != null) {
+            BooleanStyleSetting filterSetting = filters.get(feature);
+            test.label = filterSetting.label;
+            setFilterStyleActive(true);
+            for (Feature f : Feature.values()) {
+                filters.get(f).setValue(true);
+            }
+            filterSetting.setValue(false);
+        } else {
+            test.label = "all";
+            setFilterStyleActive(false);
+        }
+        MapPaintStyles.reloadStyles(filterStyleIdx);
+        test.run();
+    }
+
+    /**
+     * Test phase 2, the actual drawing.
+     * Several runs: Icons, lines, etc. are tested separately (+ one run with
+     * all features activated)
+     * @throws IOException in case of an I/O error
+     */
+    @Test
+    public void testPerformanceDrawFeatures() throws IOException {
+        testDrawFeature(null);
+        for (Feature f : Feature.values()) {
+            testDrawFeature(f);
+        }
+    }
+
+    private static void setFilterStyleActive(boolean active) {
+        if (filterStyle.active != active) {
+            MapPaintStyles.toggleStyleActive(filterStyleIdx);
+        }
+        Assert.assertEquals(active, filterStyle.active);
+    }
+
+    private static void dumpRenderedImage(String id) throws IOException {
+        File outputfile = new File("test-neubrandenburg-"+id+".png");
+        ImageIO.write(img, "png", outputfile);
+    }
+
+    public static void dump(StyledMapRenderer.BenchmarkData bd) {
+        System.out.println("generate style elements: " + bd.generateTime);
+        System.out.println("sort style elements:     " + bd.sortTime);
+        System.out.println("draw style elements:     " + bd.drawTime);
+        System.out.print("rendered elements:");
+        for (Map.Entry<Class<? extends StyleElement>, Integer> e : bd.styleElementCount.entrySet()) {
+            System.out.print(" "+e.getKey().getSimpleName()+"="+e.getValue());
+        }
+        System.out.println();
+    }
+}
