Path: blob/master/src/java.desktop/windows/classes/sun/java2d/d3d/D3DScreenUpdateManager.java
41159 views
/*1* Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.java2d.d3d;2627import java.awt.Color;28import java.awt.Component;29import java.awt.Container;30import java.awt.Font;31import java.awt.Graphics2D;32import java.awt.Rectangle;33import java.awt.Window;34import java.security.AccessController;35import java.security.PrivilegedAction;36import java.util.ArrayList;37import java.util.HashMap;3839import sun.awt.AWTAccessor;40import sun.awt.AWTAccessor.ComponentAccessor;41import sun.awt.util.ThreadGroupUtils;42import sun.awt.Win32GraphicsConfig;43import sun.awt.windows.WComponentPeer;44import sun.java2d.InvalidPipeException;45import sun.java2d.ScreenUpdateManager;46import sun.java2d.SunGraphics2D;47import sun.java2d.SurfaceData;48import sun.java2d.windows.GDIWindowSurfaceData;49import sun.java2d.d3d.D3DSurfaceData.D3DWindowSurfaceData;50import sun.java2d.windows.WindowsFlags;5152/**53* This class handles rendering to the screen with the D3D pipeline.54*55* Since it is not possible to render directly to the front buffer56* with D3D9, we create a swap chain surface (with COPY effect) in place of the57* GDIWindowSurfaceData. A background thread handles the swap chain flips.58*59* There are some restrictions to which windows we would use this for.60* @see #createScreenSurface61*/62public class D3DScreenUpdateManager extends ScreenUpdateManager63implements Runnable64{65/**66* A window must be at least MIN_WIN_SIZE in one or both dimensions67* to be considered for the update manager.68*/69private static final int MIN_WIN_SIZE = 150;7071private volatile boolean done;72private volatile Thread screenUpdater;73private boolean needsUpdateNow;7475/**76* Object used by the screen updater thread for waiting77*/78private Object runLock = new Object();79/**80* List of D3DWindowSurfaceData surfaces. Surfaces are added to the81* list when a graphics object is created, and removed when the surface82* is invalidated.83*/84private ArrayList<D3DWindowSurfaceData> d3dwSurfaces;85/**86* Cache of GDIWindowSurfaceData surfaces corresponding to the87* D3DWindowSurfaceData surfaces. Surfaces are added to the list when88* a d3dw surface is lost and could not be restored (due to lack of vram,89* for example), and removed then the d3dw surface is invalidated.90*/91private HashMap<D3DWindowSurfaceData, GDIWindowSurfaceData> gdiSurfaces;9293@SuppressWarnings("removal")94public D3DScreenUpdateManager() {95done = false;96AccessController.doPrivileged((PrivilegedAction<Void>) () -> {97Runnable shutdownRunnable = () -> {98done = true;99wakeUpUpdateThread();100};101Thread shutdown = new Thread(102ThreadGroupUtils.getRootThreadGroup(), shutdownRunnable,103"ScreenUpdater", 0, false);104shutdown.setContextClassLoader(null);105try {106Runtime.getRuntime().addShutdownHook(shutdown);107} catch (Exception e) {108done = true;109}110return null;111});112}113114/**115* If possible, creates a D3DWindowSurfaceData (which is actually116* a back-buffer surface). If the creation fails, returns GDI117* onscreen surface instead.118*119* Note that the created D3D surface does not initialize the native120* resources (and is marked lost) to avoid wasting video memory. It is121* restored when a graphics object is requested from the peer.122*123* Note that this method is called from a synchronized block in124* WComponentPeer, so we don't need to synchronize125*126* Note that we only create a substibute d3dw surface if certain conditions127* are met128* <ul>129* <li>the fake d3d rendering on screen is not disabled via flag130* <li>d3d on the device is enabled131* <li>surface is larger than MIN_WIN_SIZE (don't bother for smaller ones)132* <li>it doesn't have a backBuffer for a BufferStrategy already133* <li>the peer is either Canvas, Panel, Window, Frame,134* Dialog or EmbeddedFrame135* </ul>136*137* @param gc GraphicsConfiguration on associated with the surface138* @param peer peer for which the surface is to be created139* @param bbNum number of back-buffers requested. if this number is >0,140* method returns GDI surface (we don't want to have two swap chains)141* @param isResize whether this surface is being created in response to142* a component resize event. This determines whether a repaint event will143* be issued after a surface is created: it will be if {@code isResize}144* is {@code true}.145* @return surface data to be use for onscreen rendering146*/147@Override148public SurfaceData createScreenSurface(Win32GraphicsConfig gc,149WComponentPeer peer,150int bbNum, boolean isResize)151{152if (done || !(gc instanceof D3DGraphicsConfig)) {153return super.createScreenSurface(gc, peer, bbNum, isResize);154}155156SurfaceData sd = null;157158if (canUseD3DOnScreen(peer, gc, bbNum)) {159try {160// note that the created surface will be in the "lost"161// state, it will be restored prior to rendering to it162// for the first time. This is done so that vram is not163// wasted for surfaces never rendered to164sd = D3DSurfaceData.createData(peer);165} catch (InvalidPipeException ipe) {166sd = null;167}168}169if (sd == null) {170sd = GDIWindowSurfaceData.createData(peer);171// note that we do not add this surface to the list of cached gdi172// surfaces as there's no d3dw surface to associate it with;173// this peer will have a gdi surface until next time a surface174// will need to be replaced175}176177if (isResize) {178// since we'd potentially replaced the back-buffer surface179// (either with another bb, or a gdi one), the180// component will need to be completely repainted;181// this only need to be done when the surface is created in182// response to a resize event since when a component is created it183// will be repainted anyway184repaintPeerTarget(peer);185}186187return sd;188}189190/**191* Determines if we can use a d3d surface for onscreen rendering for this192* peer.193* We only create onscreen d3d surfaces if the following conditions are met:194* - d3d is enabled on this device and onscreen emulation is enabled195* - window is big enough to bother (either dimension > MIN_WIN_SIZE)196* - this heavyweight doesn't have a BufferStrategy197* - if we are in full-screen mode then it must be the peer of the198* full-screen window (since there could be only one SwapChain in fs)199* and it must not have any heavyweight children200* (as Present() doesn't respect component clipping in fullscreen mode)201* - it's one of the classes likely to have custom rendering worth202* accelerating203*204* @return true if we can use a d3d surface for this peer's onscreen205* rendering206*/207public static boolean canUseD3DOnScreen(final WComponentPeer peer,208final Win32GraphicsConfig gc,209final int bbNum)210{211if (!(gc instanceof D3DGraphicsConfig)) {212return false;213}214D3DGraphicsConfig d3dgc = (D3DGraphicsConfig)gc;215D3DGraphicsDevice d3dgd = d3dgc.getD3DDevice();216String peerName = peer.getClass().getName();217Rectangle r = peer.getBounds();218Component target = (Component)peer.getTarget();219Window fsw = d3dgd.getFullScreenWindow();220221return222WindowsFlags.isD3DOnScreenEnabled() &&223d3dgd.isD3DEnabledOnDevice() &&224peer.isAccelCapable() &&225(r.width > MIN_WIN_SIZE || r.height > MIN_WIN_SIZE) &&226bbNum == 0 &&227(fsw == null || (fsw == target && !hasHWChildren(target))) &&228(peerName.equals("sun.awt.windows.WCanvasPeer") ||229peerName.equals("sun.awt.windows.WDialogPeer") ||230peerName.equals("sun.awt.windows.WPanelPeer") ||231peerName.equals("sun.awt.windows.WWindowPeer") ||232peerName.equals("sun.awt.windows.WFramePeer") ||233peerName.equals("sun.awt.windows.WEmbeddedFramePeer"));234}235236/**237* Creates a graphics object for the passed in surface data. If238* the surface is lost, it is restored.239* If the surface wasn't lost or the restoration was successful240* the surface is added to the list of maintained surfaces241* (if it hasn't been already).242*243* If the updater thread hasn't been created yet , it will be created and244* started.245*246* @param sd surface data for which to create SunGraphics2D247* @param peer peer associated with the surface data248* @param fgColor fg color to be used in graphics249* @param bgColor bg color to be used in graphics250* @param font font to be used in graphics251* @return a SunGraphics2D object for the surface (or for temp GDI252* surface data)253*/254@Override255public Graphics2D createGraphics(SurfaceData sd,256WComponentPeer peer, Color fgColor, Color bgColor, Font font)257{258if (!done && sd instanceof D3DWindowSurfaceData) {259D3DWindowSurfaceData d3dw = (D3DWindowSurfaceData)sd;260if (!d3dw.isSurfaceLost() || validate(d3dw)) {261trackScreenSurface(d3dw);262return new SunGraphics2D(sd, fgColor, bgColor, font);263}264// could not restore the d3dw surface, use the cached gdi surface265// instead for this graphics object; note that we do not track266// this new gdi surface, it is only used for this graphics267// object268sd = getGdiSurface(d3dw);269}270return super.createGraphics(sd, peer, fgColor, bgColor, font);271}272273/**274* Posts a repaint event for the peer's target to the EDT275* @param peer for which target's the repaint should be issued276*/277private void repaintPeerTarget(WComponentPeer peer) {278Component target = (Component)peer.getTarget();279Rectangle bounds = AWTAccessor.getComponentAccessor().getBounds(target);280// the system-level painting operations should call the handlePaint()281// method of the WComponentPeer class to repaint the component;282// calling repaint() forces AWT to make call to update()283peer.handlePaint(0, 0, bounds.width, bounds.height);284}285286/**287* Adds a surface to the list of tracked surfaces.288*289* @param sd the surface to be added290*/291private void trackScreenSurface(SurfaceData sd) {292if (!done && sd instanceof D3DWindowSurfaceData) {293synchronized (this) {294if (d3dwSurfaces == null) {295d3dwSurfaces = new ArrayList<D3DWindowSurfaceData>();296}297D3DWindowSurfaceData d3dw = (D3DWindowSurfaceData)sd;298if (!d3dwSurfaces.contains(d3dw)) {299d3dwSurfaces.add(d3dw);300}301}302startUpdateThread();303}304}305306@Override307public synchronized void dropScreenSurface(SurfaceData sd) {308if (d3dwSurfaces != null && sd instanceof D3DWindowSurfaceData) {309D3DWindowSurfaceData d3dw = (D3DWindowSurfaceData)sd;310removeGdiSurface(d3dw);311d3dwSurfaces.remove(d3dw);312}313}314315@Override316public SurfaceData getReplacementScreenSurface(WComponentPeer peer,317SurfaceData sd)318{319SurfaceData newSurface = super.getReplacementScreenSurface(peer, sd);320// if some outstanding graphics context wants to get a replacement we321// need to make sure that the new surface (if it is accelerated) is322// being tracked323trackScreenSurface(newSurface);324return newSurface;325}326327/**328* Remove the gdi surface corresponding to the passed d3dw surface329* from list of the cached gdi surfaces.330*331* @param d3dw surface for which associated gdi surface is to be removed332*/333private void removeGdiSurface(final D3DWindowSurfaceData d3dw) {334if (gdiSurfaces != null) {335GDIWindowSurfaceData gdisd = gdiSurfaces.get(d3dw);336if (gdisd != null) {337gdisd.invalidate();338gdiSurfaces.remove(d3dw);339}340}341}342343/**344* If the update thread hasn't yet been created, it will be;345* otherwise it is awaken346*/347@SuppressWarnings("removal")348private synchronized void startUpdateThread() {349if (screenUpdater == null) {350screenUpdater = AccessController.doPrivileged((PrivilegedAction<Thread>) () -> {351String name = "D3D Screen Updater";352Thread t = new Thread(353ThreadGroupUtils.getRootThreadGroup(), this, name,3540, false);355// REMIND: should it be higher?356t.setPriority(Thread.NORM_PRIORITY + 2);357t.setDaemon(true);358return t;359});360screenUpdater.start();361} else {362wakeUpUpdateThread();363}364}365366/**367* Wakes up the screen updater thread.368*369* This method is not synchronous, it doesn't wait370* for the updater thread to complete the updates.371*372* It should be used when it is not necessary to wait for the373* completion, for example, when a new surface had been added374* to the list of tracked surfaces (which means that it's about375* to be rendered to).376*/377public void wakeUpUpdateThread() {378synchronized (runLock) {379runLock.notifyAll();380}381}382383/**384* Wakes up the screen updater thread and waits for the completion385* of the update.386*387* This method is called from Toolkit.sync() or388* when there was a copy from a VI to the screen389* so that swing applications would not appear to be390* sluggish.391*/392public void runUpdateNow() {393synchronized (this) {394// nothing to do if the updater thread hadn't been started or if395// there are no tracked surfaces396if (done || screenUpdater == null ||397d3dwSurfaces == null || d3dwSurfaces.size() == 0)398{399return;400}401}402synchronized (runLock) {403needsUpdateNow = true;404runLock.notifyAll();405while (needsUpdateNow) {406try {407runLock.wait();408} catch (InterruptedException e) {}409}410}411}412413public void run() {414while (!done) {415synchronized (runLock) {416// If the list is empty, suspend the thread until a417// new surface is added. Note that we have to check before418// wait() (and inside the runLock), otherwise we could miss a419// notify() when a new surface is added and sleep forever.420long timeout = d3dwSurfaces.size() > 0 ? 100 : 0;421422// don't go to sleep if there's a thread waiting for an update423if (!needsUpdateNow) {424try { runLock.wait(timeout); }425catch (InterruptedException e) {}426}427// if we were woken up, there are probably surfaces in the list,428// no need to check if the list is empty429}430431// make a copy to avoid synchronization during the loop432D3DWindowSurfaceData[] surfaces = new D3DWindowSurfaceData[] {};433synchronized (this) {434surfaces = d3dwSurfaces.toArray(surfaces);435}436for (D3DWindowSurfaceData sd : surfaces) {437// skip invalid surfaces (they could have become invalid438// after we made a copy of the list) - just a precaution439if (sd.isValid() && (sd.isDirty() || sd.isSurfaceLost())) {440if (!sd.isSurfaceLost()) {441// the flip and the clearing of the dirty state442// must be done under the lock, otherwise it's443// possible to miss an update to the surface444D3DRenderQueue rq = D3DRenderQueue.getInstance();445rq.lock();446try {447Rectangle r = sd.getBounds();448D3DSurfaceData.swapBuffers(sd, 0, 0,449r.width, r.height);450sd.markClean();451} finally {452rq.unlock();453}454} else if (!validate(sd)) {455// it is possible that the validation may never456// succeed, we need to detect this and replace457// the d3dw surface with gdi; the replacement of458// the surface will also trigger a repaint459sd.getPeer().replaceSurfaceDataLater();460}461}462}463synchronized (runLock) {464needsUpdateNow = false;465runLock.notifyAll();466}467}468}469470/**471* Restores the passed surface if it was lost, resets the lost status.472* @param sd surface to be validated473* @return true if surface wasn't lost or if restoration was successful,474* false otherwise475*/476private boolean validate(D3DWindowSurfaceData sd) {477if (sd.isSurfaceLost()) {478try {479sd.restoreSurface();480// if succeeded, first fill the surface with bg color481// note: use the non-synch method to avoid incorrect lock order482Color bg = sd.getPeer().getBackgroundNoSync();483SunGraphics2D sg2d = new SunGraphics2D(sd, bg, bg, null);484sg2d.fillRect(0, 0, sd.getBounds().width, sd.getBounds().height);485sg2d.dispose();486// now clean the dirty status so that we don't flip it487// next time before it gets repainted; it is safe488// to do without the lock because we will issue a489// repaint anyway so we will not lose any rendering490sd.markClean();491// since the surface was successfully restored we need to492// repaint whole window to repopulate the back-buffer493repaintPeerTarget(sd.getPeer());494} catch (InvalidPipeException ipe) {495return false;496}497}498return true;499}500501/**502* Creates (or returns a cached one) gdi surface for the same peer as503* the passed d3dw surface has.504*505* @param d3dw surface used as key into the cache506* @return gdi window surface associated with the d3d window surfaces' peer507*/508private synchronized SurfaceData getGdiSurface(D3DWindowSurfaceData d3dw) {509if (gdiSurfaces == null) {510gdiSurfaces =511new HashMap<D3DWindowSurfaceData, GDIWindowSurfaceData>();512}513GDIWindowSurfaceData gdisd = gdiSurfaces.get(d3dw);514if (gdisd == null) {515gdisd = GDIWindowSurfaceData.createData(d3dw.getPeer());516gdiSurfaces.put(d3dw, gdisd);517}518return gdisd;519}520521/**522* Returns true if the component has heavyweight children.523*524* @param comp component to check for hw children525* @return true if Component has heavyweight children526*/527private static boolean hasHWChildren(Component comp) {528final ComponentAccessor acc = AWTAccessor.getComponentAccessor();529if (comp instanceof Container) {530for (Component c : ((Container)comp).getComponents()) {531if (acc.getPeer(c) instanceof WComponentPeer || hasHWChildren(c)) {532return true;533}534}535}536return false;537}538}539540541