source: josm/trunk/test/performance/org/openstreetmap/josm/gui/mappaint/MapRendererPerformanceTest.java@ 14120

Last change on this file since 14120 was 14120, checked in by Don-vip, 6 years ago

see #15229 - deprecate all Main methods related to projections. New ProjectionRegistry class

  • Property svn:eol-style set to native
File size: 13.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint;
3
4import java.awt.Color;
5import java.awt.Graphics2D;
6import java.awt.image.BufferedImage;
7import java.io.File;
8import java.io.IOException;
9import java.io.InputStream;
10import java.util.ArrayList;
11import java.util.Collections;
12import java.util.EnumMap;
13import java.util.HashMap;
14import java.util.List;
15import java.util.Locale;
16import java.util.Map;
17import java.util.stream.Collectors;
18
19import javax.imageio.ImageIO;
20
21import org.junit.AfterClass;
22import org.junit.Assert;
23import org.junit.BeforeClass;
24import org.junit.Rule;
25import org.junit.Test;
26import org.junit.rules.Timeout;
27import org.openstreetmap.josm.JOSMFixture;
28import org.openstreetmap.josm.PerformanceTestUtils;
29import org.openstreetmap.josm.TestUtils;
30import org.openstreetmap.josm.data.Bounds;
31import org.openstreetmap.josm.data.coor.LatLon;
32import org.openstreetmap.josm.data.osm.DataSet;
33import org.openstreetmap.josm.data.osm.visitor.paint.RenderBenchmarkCollector.CapturingBenchmark;
34import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
35import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer.StyleRecord;
36import org.openstreetmap.josm.data.preferences.sources.SourceEntry;
37import org.openstreetmap.josm.data.projection.ProjectionRegistry;
38import org.openstreetmap.josm.gui.MainApplication;
39import org.openstreetmap.josm.gui.NavigatableComponent;
40import org.openstreetmap.josm.gui.mappaint.StyleSetting.BooleanStyleSetting;
41import org.openstreetmap.josm.gui.mappaint.loader.MapPaintStyleLoader;
42import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
43import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
44import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement;
45import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
46import org.openstreetmap.josm.io.Compression;
47import org.openstreetmap.josm.io.OsmReader;
48import org.openstreetmap.josm.tools.Logging;
49
50import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
51
52/**
53 * Performance test of map renderer.
54 */
55public class MapRendererPerformanceTest {
56
57 private static final boolean DUMP_IMAGE = false; // dump images to file for debugging purpose
58
59 private static final int IMG_WIDTH = 2048;
60 private static final int IMG_HEIGHT = 1536;
61
62 private static Graphics2D g;
63 private static BufferedImage img;
64 private static NavigatableComponent nc;
65 private static DataSet dsCity;
66 private static final Bounds BOUNDS_CITY_ALL = new Bounds(53.4382, 13.1094, 53.6153, 13.4074, false);
67 private static final LatLon LL_CITY = new LatLon(53.5574458, 13.2602781);
68 private static final double SCALE_Z17 = 1.5;
69
70 private static int defaultStyleIdx;
71 private static BooleanStyleSetting hideIconsSetting;
72
73 private static int filterStyleIdx;
74 private static StyleSource filterStyle;
75
76 private enum Feature {
77 ICON, SYMBOL, NODE_TEXT, LINE, LINE_TEXT, AREA;
78 public String label() {
79 return name().toLowerCase(Locale.ENGLISH);
80 }
81 }
82
83 private static final EnumMap<Feature, BooleanStyleSetting> filters = new EnumMap<>(Feature.class);
84
85 /**
86 * Global timeout applied to all test methods.
87 */
88 @Rule
89 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
90 public Timeout globalTimeout = Timeout.seconds(15*60);
91
92 /**
93 * Initializes test environment.
94 * @throws Exception if any error occurs
95 */
96 @BeforeClass
97 public static void load() throws Exception {
98 JOSMFixture.createPerformanceTestFixture().init(true);
99
100 img = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, BufferedImage.TYPE_INT_ARGB);
101 g = (Graphics2D) img.getGraphics();
102 g.setClip(0, 0, IMG_WIDTH, IMG_WIDTH);
103 g.setColor(Color.BLACK);
104 g.fillRect(0, 0, IMG_WIDTH, IMG_WIDTH);
105 nc = MainApplication.getMap().mapView;
106 nc.setBounds(0, 0, IMG_WIDTH, IMG_HEIGHT);
107
108 MapPaintStyles.readFromPreferences();
109
110 SourceEntry se = new MapCSSStyleSource(TestUtils.getTestDataRoot() + "styles/filter.mapcss", "filter", "");
111 filterStyle = MapPaintStyles.addStyle(se);
112 List<StyleSource> sources = MapPaintStyles.getStyles().getStyleSources();
113 filterStyleIdx = sources.indexOf(filterStyle);
114 Assert.assertEquals(2, filterStyleIdx);
115
116 Assert.assertEquals(Feature.values().length, filterStyle.settings.size());
117 for (StyleSetting set : filterStyle.settings) {
118 BooleanStyleSetting bset = (BooleanStyleSetting) set;
119 String prefKey = bset.prefKey;
120 boolean found = false;
121 for (Feature f : Feature.values()) {
122 if (prefKey.endsWith(":" + f.label() + "_off")) {
123 filters.put(f, bset);
124 found = true;
125 break;
126 }
127 }
128 Assert.assertTrue(prefKey, found);
129 }
130
131 MapCSSStyleSource defaultStyle = null;
132 for (int i = 0; i < sources.size(); i++) {
133 StyleSource s = sources.get(i);
134 if ("resource://styles/standard/elemstyles.mapcss".equals(s.url)) {
135 defaultStyle = (MapCSSStyleSource) s;
136 defaultStyleIdx = i;
137 break;
138 }
139 }
140 Assert.assertNotNull(defaultStyle);
141
142 for (StyleSetting set : defaultStyle.settings) {
143 if (set instanceof BooleanStyleSetting) {
144 BooleanStyleSetting bset = (BooleanStyleSetting) set;
145 if (bset.prefKey.endsWith(":hide_icons")) {
146 hideIconsSetting = bset;
147 }
148 }
149 }
150 Assert.assertNotNull(hideIconsSetting);
151 hideIconsSetting.setValue(false);
152 MapPaintStyleLoader.reloadStyles(defaultStyleIdx);
153
154 try (
155 InputStream fisC = Compression.getUncompressedFileInputStream(new File("data_nodist/neubrandenburg.osm.bz2"));
156 ) {
157 dsCity = OsmReader.parseDataSet(fisC, NullProgressMonitor.INSTANCE);
158 }
159 }
160
161 /**
162 * Cleanup test environment.
163 */
164 @AfterClass
165 public static void cleanUp() {
166 setFilterStyleActive(false);
167 if (hideIconsSetting != null) {
168 hideIconsSetting.setValue(true);
169 }
170 MapPaintStyleLoader.reloadStyles(defaultStyleIdx);
171 }
172
173 private static class PerformanceTester {
174 public double scale = 0;
175 public LatLon center = LL_CITY;
176 public Bounds bounds;
177 public int noWarmup = 3;
178 public int noIterations = 7;
179 public boolean dumpImage = DUMP_IMAGE;
180 public boolean clearStyleCache = true;
181 public String label = "";
182 public boolean mpGenerate = false;
183 public boolean mpSort = false;
184 public boolean mpDraw = false;
185 public boolean mpTotal = false;
186
187 private final List<Long> generateTimes = new ArrayList<>();
188 private final List<Long> sortTimes = new ArrayList<>();
189 private final List<Long> drawTimes = new ArrayList<>();
190 private final List<Long> totalTimes = new ArrayList<>();
191
192 @SuppressFBWarnings(value = "DM_GC")
193 public void run() throws IOException {
194 boolean checkScale = false;
195 if (scale == 0) {
196 checkScale = true;
197 scale = SCALE_Z17;
198 }
199 nc.zoomTo(ProjectionRegistry.getProjection().latlon2eastNorth(center), scale);
200 if (checkScale) {
201 int lvl = Selector.OptimizedGeneralSelector.scale2level(nc.getDist100Pixel());
202 Assert.assertEquals(17, lvl);
203 }
204
205 if (bounds == null) {
206 bounds = nc.getLatLonBounds(g.getClipBounds());
207 }
208
209 StyledMapRenderer renderer = new StyledMapRenderer(g, nc, false);
210
211 int noTotal = noWarmup + noIterations;
212 for (int i = 1; i <= noTotal; i++) {
213 g.setColor(Color.BLACK);
214 g.fillRect(0, 0, IMG_WIDTH, IMG_WIDTH);
215 if (clearStyleCache) {
216 MapPaintStyles.getStyles().clearCached();
217 }
218 System.gc();
219 System.runFinalization();
220 try {
221 Thread.sleep(300);
222 } catch (InterruptedException ex) {
223 Logging.warn(ex);
224 }
225 BenchmarkData data = new BenchmarkData();
226 renderer.setBenchmarkFactory(() -> data);
227 renderer.render(dsCity, false, bounds);
228
229 if (i > noWarmup) {
230 generateTimes.add(data.getGenerateTime());
231 sortTimes.add(data.getSortTime());
232 drawTimes.add(data.getDrawTime());
233 totalTimes.add(data.getGenerateTime() + data.getSortTime() + data.getDrawTime());
234 }
235 if (i == 1) {
236 dumpElementCount(data);
237 }
238 dumpTimes(data);
239 if (dumpImage && i == noTotal) {
240 dumpRenderedImage(label);
241 }
242 }
243
244 if (mpGenerate) {
245 processTimes(generateTimes, "generate");
246 }
247 if (mpSort) {
248 processTimes(sortTimes, "sort");
249 }
250 if (mpDraw) {
251 processTimes(drawTimes, "draw");
252 }
253 if (mpTotal) {
254 processTimes(totalTimes, "total");
255 }
256 }
257
258 private void processTimes(List<Long> times, String sublabel) {
259 Collections.sort(times);
260 // Take median instead of average. This should give a more stable
261 // result and avoids distortions by outliers.
262 long medianTime = times.get(times.size() / 2);
263 PerformanceTestUtils.measurementPlotsPluginOutput(label + " " + sublabel + " (ms)", medianTime);
264 }
265 }
266
267 /**
268 * Test phase 1, the calculation of {@link StyleElement}s.
269 * @throws IOException in case of an I/O error
270 */
271 @Test
272 public void testPerformanceGenerate() throws IOException {
273 setFilterStyleActive(false);
274 PerformanceTester test = new PerformanceTester();
275 test.bounds = BOUNDS_CITY_ALL;
276 test.label = "big";
277 test.dumpImage = false;
278 test.noWarmup = 3;
279 test.noIterations = 10;
280 test.mpGenerate = true;
281 test.clearStyleCache = true;
282 test.run();
283 }
284
285 private static void testDrawFeature(Feature feature) throws IOException {
286 PerformanceTester test = new PerformanceTester();
287 test.noWarmup = 3;
288 test.noIterations = 10;
289 test.mpDraw = true;
290 test.clearStyleCache = false;
291 if (feature != null) {
292 BooleanStyleSetting filterSetting = filters.get(feature);
293 test.label = filterSetting.label;
294 setFilterStyleActive(true);
295 for (Feature f : Feature.values()) {
296 filters.get(f).setValue(true);
297 }
298 filterSetting.setValue(false);
299 } else {
300 test.label = "all";
301 setFilterStyleActive(false);
302 }
303 MapPaintStyleLoader.reloadStyles(filterStyleIdx);
304 test.run();
305 }
306
307 /**
308 * Test phase 2, the actual drawing.
309 * Several runs: Icons, lines, etc. are tested separately (+ one run with
310 * all features activated)
311 * @throws IOException in case of an I/O error
312 */
313 @Test
314 public void testPerformanceDrawFeatures() throws IOException {
315 testDrawFeature(null);
316 for (Feature f : Feature.values()) {
317 testDrawFeature(f);
318 }
319 }
320
321 /**
322 * Resets MapPaintStyles to a single source.
323 * @param source new map paint style source
324 */
325 public static void resetStylesToSingle(StyleSource source) {
326 MapPaintStyles.getStyles().clear();
327 MapPaintStyles.getStyles().add(source);
328 }
329
330 private static void setFilterStyleActive(boolean active) {
331 if (filterStyle != null) {
332 if (filterStyle.active != active) {
333 MapPaintStyles.toggleStyleActive(filterStyleIdx);
334 }
335 Assert.assertEquals(active, filterStyle.active);
336 }
337 }
338
339 private static void dumpRenderedImage(String id) throws IOException {
340 File outputfile = new File("test-neubrandenburg-"+id+".png");
341 ImageIO.write(img, "png", outputfile);
342 }
343
344 public static void dumpTimes(BenchmarkData bd) {
345 System.out.print(String.format("gen. %3d, sort %3d, draw %3d%n", bd.getGenerateTime(), bd.getSortTime(), bd.getDrawTime()));
346 }
347
348 public static void dumpElementCount(BenchmarkData bd) {
349 System.out.println(bd.recordElementStats().entrySet().stream()
350 .map(e -> e.getKey().getSimpleName().replace("Element", "") + ":" + e.getValue()).collect(Collectors.joining(" ")));
351 }
352
353 public static class BenchmarkData extends CapturingBenchmark {
354
355 private List<StyleRecord> allStyleElems;
356
357 @Override
358 public boolean renderDraw(List<StyleRecord> allStyleElems) {
359 this.allStyleElems = allStyleElems;
360 return super.renderDraw(allStyleElems);
361 }
362
363 private Map<Class<? extends StyleElement>, Integer> recordElementStats() {
364 Map<Class<? extends StyleElement>, Integer> styleElementCount = new HashMap<>();
365 for (StyleRecord r : allStyleElems) {
366 Class<? extends StyleElement> klass = r.getStyle().getClass();
367 Integer count = styleElementCount.get(klass);
368 if (count == null) {
369 count = 0;
370 }
371 styleElementCount.put(klass, count + 1);
372 }
373 return styleElementCount;
374 }
375 }
376}
Note: See TracBrowser for help on using the repository browser.