aboutsummaryrefslogtreecommitdiff
path: root/src/windows/classes/sun/java2d/d3d/D3DScreenUpdateManager.java
blob: 53ae895ddfe94b870f995b7a38095532aa4cb8da (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
/*
 * Copyright 2007-2009 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package sun.java2d.d3d;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Window;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.HashMap;
import sun.awt.SunToolkit;
import sun.awt.AWTAccessor;
import sun.awt.Win32GraphicsConfig;
import sun.awt.windows.WComponentPeer;
import sun.java2d.InvalidPipeException;
import sun.java2d.ScreenUpdateManager;
import sun.java2d.SunGraphics2D;
import sun.java2d.SurfaceData;
import sun.java2d.windows.GDIWindowSurfaceData;
import sun.java2d.d3d.D3DSurfaceData.D3DWindowSurfaceData;
import sun.java2d.windows.WindowsFlags;

/**
 * This class handles rendering to the screen with the D3D pipeline.
 *
 * Since it is not possible to render directly to the front buffer
 * with D3D9, we create a swap chain surface (with COPY effect) in place of the
 * GDIWindowSurfaceData. A background thread handles the swap chain flips.
 *
 * There are some restrictions to which windows we would use this for.
 * @see #createScreenSurface()
 */
public class D3DScreenUpdateManager extends ScreenUpdateManager
    implements Runnable
{
    /**
     * A window must be at least MIN_WIN_SIZE in one or both dimensions
     * to be considered for the update manager.
     */
    private static final int MIN_WIN_SIZE = 150;

    private volatile boolean done;
    private volatile Thread screenUpdater;
    private boolean needsUpdateNow;

    /**
     * Object used by the screen updater thread for waiting
     */
    private Object runLock = new Object();
    /**
     * List of D3DWindowSurfaceData surfaces. Surfaces are added to the
     * list when a graphics object is created, and removed when the surface
     * is invalidated.
     */
    private ArrayList<D3DWindowSurfaceData> d3dwSurfaces;
    /**
     * Cache of GDIWindowSurfaceData surfaces corresponding to the
     * D3DWindowSurfaceData surfaces. Surfaces are added to the list when
     * a d3dw surface is lost and could not be restored (due to lack of vram,
     * for example), and removed then the d3dw surface is invalidated.
     */
    private HashMap<D3DWindowSurfaceData, GDIWindowSurfaceData> gdiSurfaces;

    public D3DScreenUpdateManager() {
        done = false;
        AccessController.doPrivileged(
            new PrivilegedAction() {
                public Object run() {
                    ThreadGroup currentTG =
                        Thread.currentThread().getThreadGroup();
                    ThreadGroup parentTG = currentTG.getParent();
                    while (parentTG != null) {
                        currentTG = parentTG;
                        parentTG = currentTG.getParent();
                    }
                    try {
                        Runtime.getRuntime().addShutdownHook(
                            new Thread(currentTG,
                                new Runnable() {
                                    public void run() {
                                        done = true;
                                        wakeUpUpdateThread();
                                    }
                                }
                            )
                        );
                    } catch (Exception e) {
                        done = true;
                    }
                    return null;
                }
            }
        );
    }

    /**
     * If possible, creates a D3DWindowSurfaceData (which is actually
     * a back-buffer surface). If the creation fails, returns GDI
     * onscreen surface instead.
     *
     * Note that the created D3D surface does not initialize the native
     * resources (and is marked lost) to avoid wasting video memory. It is
     * restored when a graphics object is requested from the peer.
     *
     * Note that this method is called from a synchronized block in
     * WComponentPeer, so we don't need to synchronize
     *
     * Note that we only create a substibute d3dw surface if certain conditions
     * are met
     * <ul>
     *  <li>the fake d3d rendering on screen is not disabled via flag
     *  <li>d3d on the device is enabled
     *  <li>surface is larger than MIN_WIN_SIZE (don't bother for smaller ones)
     *  <li>it doesn't have a backBuffer for a BufferStrategy already
     *  <li>the peer is either Canvas, Panel, Window, Frame,
     *  Dialog or EmbeddedFrame
     * </ul>
     *
     * @param gc GraphicsConfiguration on associated with the surface
     * @param peer peer for which the surface is to be created
     * @param bbNum number of back-buffers requested. if this number is >0,
     * method returns GDI surface (we don't want to have two swap chains)
     * @param isResize whether this surface is being created in response to
     * a component resize event. This determines whether a repaint event will
     * be issued after a surface is created: it will be if <code>isResize</code>
     * is <code>true</code>.
     * @return surface data to be use for onscreen rendering
     */
    @Override
    public SurfaceData createScreenSurface(Win32GraphicsConfig gc,
                                           WComponentPeer peer,
                                           int bbNum, boolean isResize)
    {
        if (done || !(gc instanceof D3DGraphicsConfig)) {
            return super.createScreenSurface(gc, peer, bbNum, isResize);
        }

        SurfaceData sd = null;

        if (canUseD3DOnScreen(peer, gc, bbNum)) {
            try {
                // note that the created surface will be in the "lost"
                // state, it will be restored prior to rendering to it
                // for the first time. This is done so that vram is not
                // wasted for surfaces never rendered to
                sd = D3DSurfaceData.createData(peer);
            }  catch (InvalidPipeException ipe) {
                sd = null;
            }
        }
        if (sd == null) {
            sd = GDIWindowSurfaceData.createData(peer);
            // note that we do not add this surface to the list of cached gdi
            // surfaces as there's no d3dw surface to associate it with;
            // this peer will have a gdi surface until next time a surface
            // will need to be replaced
        }

        if (isResize) {
            // since we'd potentially replaced the back-buffer surface
            // (either with another bb, or a gdi one), the
            // component will need to be completely repainted;
            // this only need to be done when the surface is created in
            // response to a resize event since when a component is created it
            // will be repainted anyway
            repaintPeerTarget(peer);
        }

        return sd;
    }

    /**
     * Determines if we can use a d3d surface for onscreen rendering for this
     * peer.
     * We only create onscreen d3d surfaces if the following conditions are met:
     *  - d3d is enabled on this device and onscreen emulation is enabled
     *  - window is big enough to bother (either dimension > MIN_WIN_SIZE)
     *  - this heavyweight doesn't have a BufferStrategy
     *  - if we are in full-screen mode then it must be the peer of the
     *    full-screen window (since there could be only one SwapChain in fs)
     *    and it must not have any heavyweight children
     *    (as Present() doesn't respect component clipping in fullscreen mode)
     *  - it's one of the classes likely to have custom rendering worth
     *    accelerating
     *
     * @returns true if we can use a d3d surface for this peer's onscreen
     *          rendering
     */
    public static boolean canUseD3DOnScreen(final WComponentPeer peer,
                                            final Win32GraphicsConfig gc,
                                            final int bbNum)
    {
        if (!(gc instanceof D3DGraphicsConfig)) {
            return false;
        }
        D3DGraphicsConfig d3dgc = (D3DGraphicsConfig)gc;
        D3DGraphicsDevice d3dgd = d3dgc.getD3DDevice();
        String peerName = peer.getClass().getName();
        Rectangle r = peer.getBounds();
        Component target = (Component)peer.getTarget();
        Window fsw = d3dgd.getFullScreenWindow();

        return
            WindowsFlags.isD3DOnScreenEnabled() &&
            d3dgd.isD3DEnabledOnDevice() &&
            peer.isAccelCapable() &&
            (r.width > MIN_WIN_SIZE || r.height > MIN_WIN_SIZE) &&
            bbNum == 0 &&
            (fsw == null || (fsw == target && !hasHWChildren(target))) &&
            (peerName.equals("sun.awt.windows.WCanvasPeer") ||
             peerName.equals("sun.awt.windows.WDialogPeer") ||
             peerName.equals("sun.awt.windows.WPanelPeer")  ||
             peerName.equals("sun.awt.windows.WWindowPeer") ||
             peerName.equals("sun.awt.windows.WFramePeer")  ||
             peerName.equals("sun.awt.windows.WEmbeddedFramePeer"));
    }

    /**
     * Creates a graphics object for the passed in surface data. If
     * the surface is lost, it is restored.
     * If the surface wasn't lost or the restoration was successful
     * the surface is added to the list of maintained surfaces
     * (if it hasn't been already).
     *
     * If the updater thread hasn't been created yet , it will be created and
     * started.
     *
     * @param sd surface data for which to create SunGraphics2D
     * @param peer peer associated with the surface data
     * @param fgColor fg color to be used in graphics
     * @param bgColor bg color to be used in graphics
     * @param font font to be used in graphics
     * @return a SunGraphics2D object for the surface (or for temp GDI
     * surface data)
     */
    @Override
    public Graphics2D createGraphics(SurfaceData sd,
            WComponentPeer peer, Color fgColor, Color bgColor, Font font)
    {
        if (!done && sd instanceof D3DWindowSurfaceData) {
            D3DWindowSurfaceData d3dw = (D3DWindowSurfaceData)sd;
            if (!d3dw.isSurfaceLost() || validate(d3dw)) {
                trackScreenSurface(d3dw);
                return new SunGraphics2D(sd, fgColor, bgColor, font);
            }
            // could not restore the d3dw surface, use the cached gdi surface
            // instead for this graphics object; note that we do not track
            // this new gdi surface, it is only used for this graphics
            // object
            sd = getGdiSurface(d3dw);
        }
        return super.createGraphics(sd, peer, fgColor, bgColor, font);
    }

    /**
     * Posts a repaint event for the peer's target to the EDT
     * @param peer for which target's the repaint should be issued
     */
    private void repaintPeerTarget(WComponentPeer peer) {
        Component target = (Component)peer.getTarget();
        Rectangle bounds = AWTAccessor.getComponentAccessor().getBounds(target);
        // the system-level painting operations should call the handlePaint()
        // method of the WComponentPeer class to repaint the component;
        // calling repaint() forces AWT to make call to update()
        peer.handlePaint(0, 0, bounds.width, bounds.height);
    }

    /**
     * Adds a surface to the list of tracked surfaces.
     *
     * @param d3dw the surface to be added
     */
    private void trackScreenSurface(SurfaceData sd) {
        if (!done && sd instanceof D3DWindowSurfaceData) {
            synchronized (this) {
                if (d3dwSurfaces == null) {
                    d3dwSurfaces = new ArrayList<D3DWindowSurfaceData>();
                }
                D3DWindowSurfaceData d3dw = (D3DWindowSurfaceData)sd;
                if (!d3dwSurfaces.contains(d3dw)) {
                    d3dwSurfaces.add(d3dw);
                }
            }
            startUpdateThread();
        }
    }

    @Override
    public synchronized void dropScreenSurface(SurfaceData sd) {
        if (d3dwSurfaces != null && sd instanceof D3DWindowSurfaceData) {
            D3DWindowSurfaceData d3dw = (D3DWindowSurfaceData)sd;
            removeGdiSurface(d3dw);
            d3dwSurfaces.remove(d3dw);
        }
    }

    @Override
    public SurfaceData getReplacementScreenSurface(WComponentPeer peer,
                                                   SurfaceData sd)
    {
        SurfaceData newSurface = super.getReplacementScreenSurface(peer, sd);
        // if some outstanding graphics context wants to get a replacement we
        // need to make sure that the new surface (if it is accelerated) is
        // being tracked
        trackScreenSurface(newSurface);
        return newSurface;
    }

    /**
     * Remove the gdi surface corresponding to the passed d3dw surface
     * from list of the cached gdi surfaces.
     *
     * @param d3dw surface for which associated gdi surface is to be removed
     */
    private void removeGdiSurface(final D3DWindowSurfaceData d3dw) {
        if (gdiSurfaces != null) {
            GDIWindowSurfaceData gdisd = gdiSurfaces.get(d3dw);
            if (gdisd != null) {
                gdisd.invalidate();
                gdiSurfaces.remove(d3dw);
            }
        }
    }

    /**
     * If the update thread hasn't yet been created, it will be;
     * otherwise it is awaken
     */
    private synchronized void startUpdateThread() {
        if (screenUpdater == null) {
            screenUpdater = (Thread)java.security.AccessController.doPrivileged(
                new java.security.PrivilegedAction() {
                    public Object run() {
                        ThreadGroup tg =
                            Thread.currentThread().getThreadGroup();
                        for (ThreadGroup tgn = tg;
                             tgn != null; tg = tgn, tgn = tg.getParent());
                        Thread t = new Thread(tg, D3DScreenUpdateManager.this,
                                              "D3D Screen Updater");
                        // REMIND: should it be higher?
                        t.setPriority(Thread.NORM_PRIORITY + 2);
                        t.setDaemon(true);
                        return t;
                    }
            });
            screenUpdater.start();
        } else {
            wakeUpUpdateThread();
        }
    }

    /**
     * Wakes up the screen updater thread.
     *
     * This method is not synchronous, it doesn't wait
     * for the updater thread to complete the updates.
     *
     * It should be used when it is not necessary to wait for the
     * completion, for example, when a new surface had been added
     * to the list of tracked surfaces (which means that it's about
     * to be rendered to).
     */
    public void wakeUpUpdateThread() {
        synchronized (runLock) {
            runLock.notifyAll();
        }
    }

    /**
     * Wakes up the screen updater thread and waits for the completion
     * of the update.
     *
     * This method is called from Toolkit.sync() or
     * when there was a copy from a VI to the screen
     * so that swing applications would not appear to be
     * sluggish.
     */
    public void runUpdateNow() {
        synchronized (this) {
            // nothing to do if the updater thread hadn't been started or if
            // there are no tracked surfaces
            if (done || screenUpdater == null ||
                d3dwSurfaces  == null || d3dwSurfaces.size() == 0)
            {
                return;
            }
        }
        synchronized (runLock) {
            needsUpdateNow = true;
            runLock.notifyAll();
            while (needsUpdateNow) {
                try {
                    runLock.wait();
                } catch (InterruptedException e) {}
            }
        }
    }

    public void run() {
        while (!done) {
            synchronized (runLock) {
                // If the list is empty, suspend the thread until a
                // new surface is added. Note that we have to check before
                // wait() (and inside the runLock), otherwise we could miss a
                // notify() when a new surface is added and sleep forever.
                long timeout = d3dwSurfaces.size() > 0 ? 100 : 0;

                // don't go to sleep if there's a thread waiting for an update
                if (!needsUpdateNow) {
                    try { runLock.wait(timeout); }
                        catch (InterruptedException e) {}
                }
                // if we were woken up, there are probably surfaces in the list,
                // no need to check if the list is empty
            }

            // make a copy to avoid synchronization during the loop
            D3DWindowSurfaceData surfaces[] = new D3DWindowSurfaceData[] {};
            synchronized (this) {
                surfaces = d3dwSurfaces.toArray(surfaces);
            }
            for (D3DWindowSurfaceData sd : surfaces) {
                // skip invalid surfaces (they could have become invalid
                // after we made a copy of the list) - just a precaution
                if (sd.isValid() && (sd.isDirty() || sd.isSurfaceLost())) {
                    if (!sd.isSurfaceLost()) {
                        // the flip and the clearing of the dirty state
                        // must be done under the lock, otherwise it's
                        // possible to miss an update to the surface
                        D3DRenderQueue rq = D3DRenderQueue.getInstance();
                        rq.lock();
                        try {
                            Rectangle r = sd.getBounds();
                            D3DSurfaceData.swapBuffers(sd, 0, 0,
                                                       r.width, r.height);
                            sd.markClean();
                        } finally {
                            rq.unlock();
                        }
                    } else if (!validate(sd)) {
                        // it is possible that the validation may never
                        // succeed, we need to detect this and replace
                        // the d3dw surface with gdi; the replacement of
                        // the surface will also trigger a repaint
                        sd.getPeer().replaceSurfaceDataLater();
                    }
                }
            }
            synchronized (runLock) {
                needsUpdateNow = false;
                runLock.notifyAll();
            }
        }
    }

    /**
     * Restores the passed surface if it was lost, resets the lost status.
     * @param sd surface to be validated
     * @return true if surface wasn't lost or if restoration was successful,
     * false otherwise
     */
    private boolean validate(D3DWindowSurfaceData sd) {
        if (sd.isSurfaceLost()) {
            try {
                sd.restoreSurface();
                // if succeeded, first fill the surface with bg color
                // note: use the non-synch method to avoid incorrect lock order
                Color bg = sd.getPeer().getBackgroundNoSync();
                SunGraphics2D sg2d = new SunGraphics2D(sd, bg, bg, null);
                sg2d.fillRect(0, 0, sd.getBounds().width, sd.getBounds().height);
                sg2d.dispose();
                // now clean the dirty status so that we don't flip it
                // next time before it gets repainted; it is safe
                // to do without the lock because we will issue a
                // repaint anyway so we will not lose any rendering
                sd.markClean();
                // since the surface was successfully restored we need to
                // repaint whole window to repopulate the back-buffer
                repaintPeerTarget(sd.getPeer());
            } catch (InvalidPipeException ipe) {
                return false;
            }
        }
        return true;
    }

    /**
     * Creates (or returns a cached one) gdi surface for the same peer as
     * the passed d3dw surface has.
     *
     * @param d3dw surface used as key into the cache
     * @return gdi window surface associated with the d3d window surfaces' peer
     */
    private synchronized SurfaceData getGdiSurface(D3DWindowSurfaceData d3dw) {
        if (gdiSurfaces == null) {
            gdiSurfaces =
                new HashMap<D3DWindowSurfaceData, GDIWindowSurfaceData>();
        }
        GDIWindowSurfaceData gdisd = gdiSurfaces.get(d3dw);
        if (gdisd == null) {
            gdisd = GDIWindowSurfaceData.createData(d3dw.getPeer());
            gdiSurfaces.put(d3dw, gdisd);
        }
        return gdisd;
    }

    /**
     * Returns true if the component has heavyweight children.
     *
     * @param comp component to check for hw children
     * @return true if Component has heavyweight children
     */
    private static boolean hasHWChildren(Component comp) {
        if (comp instanceof Container) {
            for (Component c : ((Container)comp).getComponents()) {
                if (c.getPeer() instanceof WComponentPeer || hasHWChildren(c)) {
                    return true;
                }
            }
        }
        return false;
    }
}