1 /* 2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.javafx.tk.quantum; 27 28 import java.util.ArrayList; 29 import java.util.Collections; 30 import java.util.Comparator; 31 import java.util.List; 32 import java.util.concurrent.CountDownLatch; 33 import com.sun.javafx.PlatformUtil; 34 35 import com.sun.glass.ui.Application; 36 import com.sun.glass.ui.Window; 37 import com.sun.javafx.tk.CompletionListener; 38 import com.sun.javafx.tk.RenderJob; 39 40 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED; 41 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGER; 42 43 /** 44 * Manages the collection and rendering of dirty scenes. This class has 45 * methods which may be called from one of several threads, depending 46 * on the method. 47 * 48 * <ul> 49 * <li>createInstance: Called by QuantumToolkit once during initialization</li> 50 * <li>getInstance: May be called from any thread</li> 51 * <li>hasDirty: May be called from any thread</li> 52 * <li>addDirtyScene: Called only on the FX Thread</li> 53 * <li>removeDirtyScene: Called only on the FX Thread</li> 54 * <li>getRendered: May be called from any thread</li> 55 * <li>liveRepaintRenderJob: Called only on the FX Thread</li> 56 * <li>renderAll: Called only on the FX Thread</li> 57 * </ul> 58 * 59 * Assertions have been added to each method to verify whether the calling 60 * thread is the expected thread. 61 */ 62 final class PaintCollector implements CompletionListener { 63 /* 64 Generally we would prefer to remove this static state and pass the 65 collector where it needs to go rather than having code reach into this 66 static method to get the instance. IoC (inversion of control) makes 67 the code more readable and testable, in general. 68 */ 69 private static volatile PaintCollector collector; 70 71 static PaintCollector createInstance(QuantumToolkit toolkit) { 72 return collector = new PaintCollector(toolkit); 73 } 74 75 static PaintCollector getInstance() { 76 return collector; 77 } 78 79 /** 80 * Sorts the dirty scenes such that asynchronous scenes come first 81 */ 82 private static final Comparator<GlassScene> DIRTY_SCENE_SORTER = new Comparator<GlassScene>() { 83 @Override public int compare(GlassScene o1, GlassScene o2) { 84 int i1 = o1.isSynchronous() ? 1 : 0; 85 int i2 = o2.isSynchronous() ? 1 : 0; 86 return i1 - i2; 87 } 88 }; 89 90 /** 91 * Contains a list of all of the dirty scenes. This list is populated 92 * only from the FX Thread in consequence of a call to addDirtyScene, 93 * or cleared from the FX Thread in consequence of a call to renderAll 94 * or removeDirtyScene. It is only ever accessed (both read and write!) 95 * from the FX thread. 96 */ 97 private final List<GlassScene> dirtyScenes = new ArrayList<>(); 98 99 /** 100 * Keeps track of the number of scenes which still need to be processed. 101 * In the renderAll method, we will await on this latch until all currently 102 * pending scenes are completed. Once they are all completed, we will 103 * create a new CountDownLatch initialized to the size of the number of 104 * scenes to be processed, and then process each scene in turn (or rather, 105 * cause them to render on the render thread). As each scene completes, 106 * the CompletionListener will be invoked which will decrement the 107 * allWorkCompletedLatch. 108 */ 109 private volatile CountDownLatch allWorkCompletedLatch = new CountDownLatch(0); 110 111 /** 112 * Indicates whether this PaintCollector has any dirty scenes that 113 * need to be processed. This is used by the QuantumToolkit to detect 114 * in the postPulse() method whether there are dirty scenes. If there 115 * are, then the postPulse will potentially post a new pulse event. 116 * Updated from the FX Thread, but may be read from any thread. 117 */ 118 private volatile boolean hasDirty; 119 120 /** 121 * A reference to the toolkit. This is supplied in the constructor. 122 * Although a Toolkit.getToolkit() call and cast to QuantumToolkit 123 * could be used, it is somewhat cleaner to simply supply these 124 * parameters in the constructor and not reach out to static state. 125 */ 126 private final QuantumToolkit toolkit; 127 128 /** 129 * Indicates whether we should attempt to wait for vsync at 130 * the conclusion of rendering all scenes. This is set in the 131 * renderAll method if there are any synchronous scenes. If true, 132 * then after the last scene is processed we will indicate to the 133 * Toolkit that it should exercise the vsync block, and let it 134 * decide whether to actually do so or not (based on flags, or 135 * what OS we're on, etc). 136 * 137 * <p>This field will be set from the FX thread and read from 138 * the Render thread, hence it is volatile.</p> 139 */ 140 private volatile boolean needsHint; 141 142 /** 143 * Singleton constructor. 144 * 145 * @param qt The QuantumToolkit instance. 146 */ 147 private PaintCollector(QuantumToolkit qt) { 148 toolkit = qt; 149 } 150 151 /** 152 * Called by renderAll to wait for rendering to complete before 153 * continuing. 154 */ 155 void waitForRenderingToComplete() { 156 while (true) { 157 try { 158 // We need to keep waiting until things are done! 159 allWorkCompletedLatch.await(); 160 return; 161 } catch (InterruptedException ex) { 162 // An interrupted exception at this point is a 163 // bad thing. It might have happened during shutdown, 164 // perhaps? Or somebody is poking the FX thread and 165 // asking it to interrupt. Either way, it means 166 // that we have not yet completed rendering some 167 // scenes and we're about to make a mess of things. 168 // Best thing to do is to retry. 169 } 170 } 171 } 172 173 /** 174 * Gets whether there are any dirty scenes that need to be rendered. If 175 * true, then a subsequent pulse event and renderAll call is required. 176 * 177 * @return Whether there are any dirty scenes that need to be rendered. 178 */ 179 final boolean hasDirty() { 180 return hasDirty; 181 } 182 183 /** 184 * Adds a dirty scene to the PaintCollector for subsequent processing. 185 * This method simply makes the PaintCollector aware of this new 186 * scene and ensure it gets processed on the next call to renderAll. 187 * 188 * The next QuantumToolkit Glass timer generated pulse or PaintCollector 189 * rendering vsync hinted pulse will process these dirty scenes. 190 * 191 * <p>This method must only be called on the FX Thread</p> 192 * 193 * @param scene The scene which is dirty. This must not be null. 194 */ 195 final void addDirtyScene(GlassScene scene) { 196 // Check that we are on the expected thread. 197 assert Thread.currentThread() == QuantumToolkit.getFxUserThread(); 198 // Scene must not be null (using assert for performance) 199 assert scene != null; 200 201 if (QuantumToolkit.verbose) { 202 System.err.println("PC.addDirtyScene: " + System.nanoTime() + scene); 203 } 204 205 // Because dirtyScenes is ever only accessed from the FX Thread, 206 // we don't need any form of concurrent access here. Note also 207 // that doing a contains() call here is probably faster than using 208 // a HashSet because we are dealing with such a small number of 209 // scenes that simple iteration is likely to be much faster 210 if (!dirtyScenes.contains(scene)) { 211 dirtyScenes.add(scene); 212 // Now that we know we have added a scene to dirtyScenes, 213 // we should ensure hasDirty is true. 214 hasDirty = true; 215 } 216 } 217 218 /** 219 * Removes a scene from the dirtyScene list. If the given scene 220 * was previously added with a call to addDirtyScene, it will 221 * be removed. Potentially this means that after this call the 222 * PaintCollector will no longer have any dirty scenes and will 223 * no longer require a repaint. 224 * 225 * <p>This method is typically called when a scene is removed 226 * from a stage, or when visible becomes false. 227 * </p> 228 * 229 * <p>This method must only be called on the FX Thread</p> 230 * 231 * @param scene The scene which is no longer dirty. Must not be null. 232 */ 233 final void removeDirtyScene(GlassScene scene) { 234 // Ensure we're called only from the FX thread 235 assert Thread.currentThread() == QuantumToolkit.getFxUserThread(); 236 assert scene != null; 237 238 // Need to convert to use JavaFX Logging instead. 239 if (QuantumToolkit.verbose) { 240 System.err.println("PC.removeDirtyScene: " + scene); 241 } 242 243 // Remove the scene 244 dirtyScenes.remove(scene); 245 // Update hasDirty 246 hasDirty = !dirtyScenes.isEmpty(); 247 } 248 249 /** 250 * Gets the CompletionListener which must be notified when a 251 * GlassScene has completed rendering. 252 * 253 * @return The CompletionListener. Will never be null. 254 */ 255 final CompletionListener getRendered() { 256 return this; 257 } 258 259 /** 260 * This object is a CompletionListener is registered with every GlassScene, 261 * such that when the repaint has completed, this method is called. 262 * This method will decrement the count on the allWorkCompletedLatch. 263 */ 264 @Override public void done(RenderJob job) { 265 // It would be better to have an assertive check that 266 // this call is being made on the render thread, rather 267 // than on the FXT, but this is easier for now. 268 assert Thread.currentThread() != QuantumToolkit.getFxUserThread(); 269 270 if (!(job instanceof PaintRenderJob)) { 271 throw new IllegalArgumentException("PaintCollector: invalid RenderJob"); 272 } 273 274 final PaintRenderJob paintjob = (PaintRenderJob)job; 275 final GlassScene scene = paintjob.getScene(); 276 277 if (scene == null) { 278 throw new IllegalArgumentException("PaintCollector: null scene"); 279 } 280 281 // This callback on Scene only exists to allow the performance 282 // counter to be notified when a scene has been rendered. We 283 // could reduce the class count and indirection if we had a more 284 // direct method for notifying some performance tracker rather 285 // than going through this round-about way. 286 scene.frameRendered(); 287 288 // Work to be done after all rendering is completed. Note that 289 // I check against "1" to indicate all rendering is done, and 290 // only decrement the allWorkCompletedLatch after wards. This is 291 // because as soon as I decrement the allWorkCompletedLatch to 0, 292 // then whatever code remains in this method will run concurrently 293 // with the FX app thread, and I'd prefer to minimize the number 294 // of things here that could be happening in parallel. 295 if (allWorkCompletedLatch.getCount() == 1) { 296 // In some cases we need to tell the toolkit that 297 // now would be a great time to vsync! 298 if (needsHint && !toolkit.hasNativeSystemVsync()) { 299 toolkit.vsyncHint(); 300 } 301 302 Runnable renderEndNotifier = Application.GetApplication().getRenderEndNotifier(); 303 if (renderEndNotifier != null) { 304 Application.invokeLater(renderEndNotifier); 305 } 306 307 // If pulse logging is enabled, then we must call renderEnd now 308 // that we know that all of the scene's being rendered are finished 309 if (PULSE_LOGGING_ENABLED) { 310 PULSE_LOGGER.renderEnd(); 311 } 312 } 313 314 // Count down the latch, indicating that drawing has 315 // completed for some scene. 316 allWorkCompletedLatch.countDown(); 317 } 318 319 /** 320 * Run a full pulse and repaint before returning. 321 */ 322 final void liveRepaintRenderJob(final ViewScene scene) { 323 ViewPainter viewPainter = scene.getPainter(); 324 QuantumToolkit quantum = (QuantumToolkit)QuantumToolkit.getToolkit(); 325 quantum.pulse(false); 326 final CountDownLatch latch = new CountDownLatch(1); 327 boolean locked = ViewPainter.renderLock.isHeldByCurrentThread(); 328 if (locked) { 329 ViewPainter.renderLock.unlock(); 330 } 331 try { 332 quantum.addRenderJob(new RenderJob(viewPainter, new CompletionListener() { 333 @Override public void done(final RenderJob rj) { 334 latch.countDown(); 335 } 336 })); 337 try { 338 latch.await(); 339 } catch (InterruptedException e) { 340 //Fail silently. If interrupted, then proceed with the UI ... 341 } 342 } finally { 343 if (locked) { 344 ViewPainter.renderLock.lock(); 345 } 346 } 347 } 348 349 /** 350 * Called by QuantumToolkit during a pulse to render whatever dirty scenes 351 * we have. This method is only called on the FX thread. 352 */ 353 final void renderAll() { 354 // Ensure we're called only from the FX thread 355 assert Thread.currentThread() == QuantumToolkit.getFxUserThread(); 356 357 // TODO switch to using a logger 358 if (QuantumToolkit.pulseDebug) { 359 System.err.println("PC.renderAll(" + dirtyScenes.size() + "): " + System.nanoTime()); 360 } 361 362 // Since hasDirty can only be set to true from the FX Thread, 363 // we can do just a simple boolean check here. If we don't 364 // have any dirty scenes to process, then we are done. 365 if (!hasDirty) { 366 return; 367 } 368 369 // Because hasDirty is tied to dirtyScenes, it should 370 // not be possible that we reach this point if dirtyScenes 371 // is empty (since hasDirty was true) 372 assert !dirtyScenes.isEmpty(); 373 374 // Sort the dirty scenes based on whether they are 375 // synchronous or not. If they are not synchronous, 376 // then we want to process them first. 377 Collections.sort(dirtyScenes, DIRTY_SCENE_SORTER); 378 379 // Reset the fields 380 hasDirty = false; 381 needsHint = false; 382 383 // If pulse logging is enabled, then we must call renderStart 384 // BEFORE we actually call repaint on any of the dirty scenes. 385 if (PULSE_LOGGING_ENABLED) { 386 PULSE_LOGGER.renderStart(); 387 } 388 389 // This part needs to be handled a bit differently depending on whether our platform has a native 390 // window manager or not. 391 // So, check to see if we do (Note: how we determine this need to be improved, this should 392 // eventually call down into platform-specific glass code and not rely on 393 // a system property, but we will use this for now) 394 if (!Application.GetApplication().hasWindowManager()) { 395 // No native window manager. We call repaint on every scene (to make sure it gets recopied 396 // to the screen) but we may be able to skip some steps in the repaint. 397 398 // Obtain a z-ordered window list from glass. For platforms without a native window manager, 399 // we need to recopy the all of the window contents to the screen on every frame. 400 final List<com.sun.glass.ui.Window> glassWindowList = com.sun.glass.ui.Window.getWindows(); 401 allWorkCompletedLatch = new CountDownLatch(glassWindowList.size()); 402 for (int i = 0, n = glassWindowList.size(); i < n; i++) { 403 final Window w = glassWindowList.get(i); 404 final WindowStage ws = WindowStage.findWindowStage(w); 405 if (ws != null) { 406 final ViewScene vs = ws.getViewScene(); 407 408 // Check to see if this scene is in our dirty list. If so, we will need to render 409 // the scene before we recopy it to the screen. If not, we can skip this step. 410 if (dirtyScenes.indexOf(vs) != -1) { 411 if (!needsHint) { 412 needsHint = vs.isSynchronous(); 413 } 414 } 415 if (!PlatformUtil.useEGL() || i == (n - 1)) { 416 // for platforms without a native window manager, we only want to do the 417 // swap to the screen after the last window has been rendered 418 vs.setDoPresent(true); 419 } else { 420 vs.setDoPresent(false); 421 } 422 try { 423 vs.repaint(); 424 } catch (Throwable t) { 425 t.printStackTrace(); 426 } 427 } 428 } 429 } else { 430 // We have a native window manager. Only call repaint on the dirty scenes, 431 // and swap to the screen on a per-window basis. 432 // 433 // Now we are ready to repaint each scene. We will first process 434 // the uploadScenes, followed by the syncScenes. The reason we 435 // want to do this is that when the last syncScene is processed, 436 // if needsHint is true, then we will wait for vsync. We clearly 437 // don't want to do this until all the dirty scenes have been 438 // processed. 439 allWorkCompletedLatch = new CountDownLatch(dirtyScenes.size()); 440 441 for (final GlassScene gs : dirtyScenes) { 442 // Only post the vsync hint if there are synchronous scenes 443 if (!needsHint) { 444 needsHint = gs.isSynchronous(); 445 } 446 // On platforms with a window manager, we always set doPresent = true, because 447 // we always need to rerender the scene if it's in the dirty list and we do a 448 // swap on a per-window basis 449 gs.setDoPresent(true); 450 try { 451 gs.repaint(); 452 } catch (Throwable t) { 453 t.printStackTrace(); 454 } 455 } 456 } 457 458 dirtyScenes.clear(); 459 // This should be removed when RT-15195 and all associated issues is finished. 460 if (!QuantumToolkit.multithreaded) { 461 waitForRenderingToComplete(); 462 } 463 } 464 }