1 /* 2 * Copyright (c) 2007, 2014, 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 sun.java2d.d3d; 27 28 import java.awt.Dialog; 29 import java.awt.DisplayMode; 30 import java.awt.Frame; 31 import java.awt.GraphicsConfiguration; 32 import java.awt.Rectangle; 33 import java.awt.Toolkit; 34 import java.awt.Window; 35 import java.awt.event.WindowAdapter; 36 import java.awt.event.WindowEvent; 37 import java.awt.event.WindowListener; 38 import java.awt.peer.WindowPeer; 39 import java.util.ArrayList; 40 import sun.awt.Win32GraphicsDevice; 41 import sun.awt.windows.WWindowPeer; 42 import sun.java2d.pipe.hw.ContextCapabilities; 43 import sun.java2d.windows.WindowsFlags; 44 import static sun.java2d.pipe.BufferedOpCodes.*; 45 import static sun.java2d.d3d.D3DContext.D3DContextCaps.*; 46 import sun.java2d.d3d.D3DContext.D3DContextCaps; 47 48 /** 49 * This class implements D3D-specific functionality, such as fullscreen 50 * exclusive mode and display changes. It is kept separate from 51 * Win32GraphicsDevice to help avoid overburdening the parent class. 52 */ 53 public class D3DGraphicsDevice extends Win32GraphicsDevice { 54 private D3DContext context; 55 56 private static boolean d3dAvailable; 57 58 private ContextCapabilities d3dCaps; 59 60 private static native boolean initD3D(); 61 62 static { 63 // loading the library doesn't help because we need the 64 // toolkit thread running, so we have to call getDefaultToolkit() 65 Toolkit.getDefaultToolkit(); 66 d3dAvailable = initD3D(); 67 if (d3dAvailable) { 68 // we don't use pixel formats for the d3d pipeline 69 pfDisabled = true; 70 sun.misc.PerfCounter.getD3DAvailable().set(1); 71 } else { 72 sun.misc.PerfCounter.getD3DAvailable().set(0); 73 } 74 } 75 76 /** 77 * Used to construct a Direct3D-enabled GraphicsDevice. 78 * 79 * @return a D3DGraphicsDevice if it could be created 80 * successfully, null otherwise. 81 */ 82 public static D3DGraphicsDevice createDevice(int screen) { 83 if (!d3dAvailable) { 84 return null; 85 } 86 87 ContextCapabilities d3dCaps = getDeviceCaps(screen); 88 // could not initialize the device successfully 89 if ((d3dCaps.getCaps() & CAPS_DEVICE_OK) == 0) { 90 if (WindowsFlags.isD3DVerbose()) { 91 System.out.println("Could not enable Direct3D pipeline on " + 92 "screen " + screen); 93 } 94 return null; 95 } 96 if (WindowsFlags.isD3DVerbose()) { 97 System.out.println("Direct3D pipeline enabled on screen " + screen); 98 } 99 100 D3DGraphicsDevice gd = new D3DGraphicsDevice(screen, d3dCaps); 101 return gd; 102 } 103 104 private static native int getDeviceCapsNative(int screen); 105 private static native String getDeviceIdNative(int screen); 106 private static ContextCapabilities getDeviceCaps(final int screen) { 107 ContextCapabilities d3dCaps = null; 108 D3DRenderQueue rq = D3DRenderQueue.getInstance(); 109 rq.lock(); 110 try { 111 class Result { 112 int caps; 113 String id; 114 }; 115 final Result res = new Result(); 116 rq.flushAndInvokeNow(new Runnable() { 117 public void run() { 118 res.caps = getDeviceCapsNative(screen); 119 res.id = getDeviceIdNative(screen); 120 } 121 }); 122 d3dCaps = new D3DContextCaps(res.caps, res.id); 123 } finally { 124 rq.unlock(); 125 } 126 127 return d3dCaps != null ? d3dCaps : new D3DContextCaps(CAPS_EMPTY, null); 128 } 129 130 public final boolean isCapPresent(int cap) { 131 return ((d3dCaps.getCaps() & cap) != 0); 132 } 133 134 private D3DGraphicsDevice(int screennum, ContextCapabilities d3dCaps) { 135 super(screennum); 136 descString = "D3DGraphicsDevice[screen="+screennum; 137 this.d3dCaps = d3dCaps; 138 context = new D3DContext(D3DRenderQueue.getInstance(), this); 139 } 140 141 public boolean isD3DEnabledOnDevice() { 142 return isValid() && isCapPresent(CAPS_DEVICE_OK); 143 } 144 145 /** 146 * Returns true if d3d pipeline has been successfully initialized. 147 * @return true if d3d pipeline is initialized, false otherwise 148 */ 149 public static boolean isD3DAvailable() { 150 return d3dAvailable; 151 } 152 153 /** 154 * Return the owning Frame for a given Window. Used in setFSWindow below 155 * to set the properties of the owning Frame when a Window goes 156 * into fullscreen mode. 157 */ 158 private Frame getToplevelOwner(Window w) { 159 Window owner = w; 160 while (owner != null) { 161 owner = owner.getOwner(); 162 if (owner instanceof Frame) { 163 return (Frame) owner; 164 } 165 } 166 // could get here if passed Window is an owner-less Dialog 167 return null; 168 } 169 170 private boolean fsStatus; 171 private Rectangle ownerOrigBounds = null; 172 private boolean ownerWasVisible; 173 private Window realFSWindow; 174 private WindowListener fsWindowListener; 175 private boolean fsWindowWasAlwaysOnTop; 176 private static native boolean enterFullScreenExclusiveNative(int screen, 177 long hwnd); 178 179 @Override 180 protected void enterFullScreenExclusive(final int screen, WindowPeer wp) 181 { 182 final WWindowPeer wpeer = (WWindowPeer)realFSWindow.getPeer(); 183 184 D3DRenderQueue rq = D3DRenderQueue.getInstance(); 185 rq.lock(); 186 try { 187 rq.flushAndInvokeNow(new Runnable() { 188 public void run() { 189 long hwnd = wpeer.getHWnd(); 190 if (hwnd == 0l) { 191 // window is disposed 192 fsStatus = false; 193 return; 194 } 195 fsStatus = enterFullScreenExclusiveNative(screen, hwnd); 196 } 197 }); 198 } finally { 199 rq.unlock(); 200 } 201 if (!fsStatus) { 202 super.enterFullScreenExclusive(screen, wp); 203 } 204 } 205 206 private static native boolean exitFullScreenExclusiveNative(int screen); 207 @Override 208 protected void exitFullScreenExclusive(final int screen, WindowPeer w) { 209 if (fsStatus) { 210 D3DRenderQueue rq = D3DRenderQueue.getInstance(); 211 rq.lock(); 212 try { 213 rq.flushAndInvokeNow(new Runnable() { 214 public void run() { 215 exitFullScreenExclusiveNative(screen); 216 } 217 }); 218 } finally { 219 rq.unlock(); 220 } 221 } else { 222 super.exitFullScreenExclusive(screen, w); 223 } 224 } 225 226 /** 227 * WindowAdapter class for the full-screen frame, responsible for 228 * restoring the devices. This is important to do because unless the device 229 * is restored it will not go back into the FS mode once alt+tabbed out. 230 * This is a problem for windows for which we do not do any d3d-related 231 * operations (like when we disabled on-screen rendering). 232 * 233 * REMIND: we create an instance per each full-screen device while a single 234 * instance would suffice (but requires more management). 235 */ 236 private static class D3DFSWindowAdapter extends WindowAdapter { 237 @Override 238 @SuppressWarnings("static") 239 public void windowDeactivated(WindowEvent e) { 240 D3DRenderQueue.getInstance().restoreDevices(); 241 } 242 @Override 243 @SuppressWarnings("static") 244 public void windowActivated(WindowEvent e) { 245 D3DRenderQueue.getInstance().restoreDevices(); 246 } 247 } 248 249 @Override 250 protected void addFSWindowListener(Window w) { 251 // if the window is not a toplevel (has an owner) we have to use the 252 // real toplevel to enter the full-screen mode with (4933099). 253 if (!(w instanceof Frame) && !(w instanceof Dialog) && 254 (realFSWindow = getToplevelOwner(w)) != null) 255 { 256 ownerOrigBounds = realFSWindow.getBounds(); 257 WWindowPeer fp = (WWindowPeer)realFSWindow.getPeer(); 258 259 ownerWasVisible = realFSWindow.isVisible(); 260 Rectangle r = w.getBounds(); 261 // we use operations on peer instead of component because calling 262 // them on component will take the tree lock 263 fp.reshape(r.x, r.y, r.width, r.height); 264 fp.setVisible(true); 265 } else { 266 realFSWindow = w; 267 } 268 269 fsWindowWasAlwaysOnTop = realFSWindow.isAlwaysOnTop(); 270 ((WWindowPeer)realFSWindow.getPeer()).setAlwaysOnTop(true); 271 272 fsWindowListener = new D3DFSWindowAdapter(); 273 realFSWindow.addWindowListener(fsWindowListener); 274 } 275 276 @Override 277 protected void removeFSWindowListener(Window w) { 278 realFSWindow.removeWindowListener(fsWindowListener); 279 fsWindowListener = null; 280 281 /** 282 * Bug 4933099: There is some funny-business to deal with when this 283 * method is called with a Window instead of a Frame. See 4836744 284 * for more information on this. One side-effect of our workaround 285 * for the problem is that the owning Frame of a Window may end 286 * up getting resized during the fullscreen process. When we 287 * return from fullscreen mode, we should resize the Frame to 288 * its original size (just like the Window is being resized 289 * to its original size in GraphicsDevice). 290 */ 291 WWindowPeer wpeer = (WWindowPeer)realFSWindow.getPeer(); 292 if (wpeer != null) { 293 if (ownerOrigBounds != null) { 294 // if the window went into fs mode before it was realized it 295 // could have (0,0) dimensions 296 if (ownerOrigBounds.width == 0) ownerOrigBounds.width = 1; 297 if (ownerOrigBounds.height == 0) ownerOrigBounds.height = 1; 298 wpeer.reshape(ownerOrigBounds.x, ownerOrigBounds.y, 299 ownerOrigBounds.width, ownerOrigBounds.height); 300 if (!ownerWasVisible) { 301 wpeer.setVisible(false); 302 } 303 ownerOrigBounds = null; 304 } 305 if (!fsWindowWasAlwaysOnTop) { 306 wpeer.setAlwaysOnTop(false); 307 } 308 } 309 310 realFSWindow = null; 311 } 312 313 private static native DisplayMode getCurrentDisplayModeNative(int screen); 314 @Override 315 protected DisplayMode getCurrentDisplayMode(final int screen) { 316 D3DRenderQueue rq = D3DRenderQueue.getInstance(); 317 rq.lock(); 318 try { 319 class Result { 320 DisplayMode dm = null; 321 }; 322 final Result res = new Result(); 323 rq.flushAndInvokeNow(new Runnable() { 324 public void run() { 325 res.dm = getCurrentDisplayModeNative(screen); 326 } 327 }); 328 if (res.dm == null) { 329 return super.getCurrentDisplayMode(screen); 330 } 331 return res.dm; 332 } finally { 333 rq.unlock(); 334 } 335 } 336 private static native void configDisplayModeNative(int screen, long hwnd, 337 int width, int height, 338 int bitDepth, 339 int refreshRate); 340 @Override 341 protected void configDisplayMode(final int screen, final WindowPeer w, 342 final int width, final int height, 343 final int bitDepth, final int refreshRate) 344 { 345 // we entered fs mode via gdi 346 if (!fsStatus) { 347 super.configDisplayMode(screen, w, width, height, bitDepth, 348 refreshRate); 349 return; 350 } 351 352 final WWindowPeer wpeer = (WWindowPeer)realFSWindow.getPeer(); 353 354 // REMIND: we do this before we switch the display mode, so 355 // the dimensions may be exceeding the dimensions of the screen, 356 // is this a problem? 357 358 // update the bounds of the owner frame 359 if (getFullScreenWindow() != realFSWindow) { 360 Rectangle screenBounds = getDefaultConfiguration().getBounds(); 361 wpeer.reshape(screenBounds.x, screenBounds.y, width, height); 362 } 363 364 D3DRenderQueue rq = D3DRenderQueue.getInstance(); 365 rq.lock(); 366 try { 367 rq.flushAndInvokeNow(new Runnable() { 368 public void run() { 369 long hwnd = wpeer.getHWnd(); 370 if (hwnd == 0l) { 371 // window is disposed 372 return; 373 } 374 // REMIND: do we really need a window here? 375 // we should probably just use the current one 376 configDisplayModeNative(screen, hwnd, width, height, 377 bitDepth, refreshRate); 378 } 379 }); 380 } finally { 381 rq.unlock(); 382 } 383 } 384 385 private static native void enumDisplayModesNative(int screen, 386 ArrayList modes); 387 @Override 388 protected void enumDisplayModes(final int screen, final ArrayList modes) { 389 D3DRenderQueue rq = D3DRenderQueue.getInstance(); 390 rq.lock(); 391 try { 392 rq.flushAndInvokeNow(new Runnable() { 393 public void run() { 394 enumDisplayModesNative(screen, modes); 395 } 396 }); 397 if (modes.size() == 0) { 398 modes.add(getCurrentDisplayModeNative(screen)); 399 } 400 } finally { 401 rq.unlock(); 402 } 403 } 404 405 private static native long getAvailableAcceleratedMemoryNative(int screen); 406 @Override 407 public int getAvailableAcceleratedMemory() { 408 D3DRenderQueue rq = D3DRenderQueue.getInstance(); 409 rq.lock(); 410 try { 411 class Result { 412 long mem = 0L; 413 }; 414 final Result res = new Result(); 415 rq.flushAndInvokeNow(new Runnable() { 416 public void run() { 417 res.mem = getAvailableAcceleratedMemoryNative(getScreen()); 418 } 419 }); 420 return (int)res.mem; 421 } finally { 422 rq.unlock(); 423 } 424 } 425 426 @Override 427 public GraphicsConfiguration[] getConfigurations() { 428 if (configs == null) { 429 if (isD3DEnabledOnDevice()) { 430 defaultConfig = getDefaultConfiguration(); 431 if (defaultConfig != null) { 432 configs = new GraphicsConfiguration[1]; 433 configs[0] = defaultConfig; 434 return configs.clone(); 435 } 436 } 437 } 438 return super.getConfigurations(); 439 } 440 441 @Override 442 public GraphicsConfiguration getDefaultConfiguration() { 443 if (defaultConfig == null) { 444 if (isD3DEnabledOnDevice()) { 445 defaultConfig = new D3DGraphicsConfig(this); 446 } else { 447 defaultConfig = super.getDefaultConfiguration(); 448 } 449 } 450 return defaultConfig; 451 } 452 453 private static native boolean isD3DAvailableOnDeviceNative(int screen); 454 // REMIND: this method is not used now, we use caps instead 455 public static boolean isD3DAvailableOnDevice(final int screen) { 456 if (!d3dAvailable) { 457 return false; 458 } 459 460 // REMIND: should we cache the result per device somehow, 461 // and then reset and retry it on display change? 462 D3DRenderQueue rq = D3DRenderQueue.getInstance(); 463 rq.lock(); 464 try { 465 class Result { 466 boolean avail = false; 467 }; 468 final Result res = new Result(); 469 rq.flushAndInvokeNow(new Runnable() { 470 public void run() { 471 res.avail = isD3DAvailableOnDeviceNative(screen); 472 } 473 }); 474 return res.avail; 475 } finally { 476 rq.unlock(); 477 } 478 } 479 480 D3DContext getContext() { 481 return context; 482 } 483 484 ContextCapabilities getContextCapabilities() { 485 return d3dCaps; 486 } 487 488 @Override 489 public void displayChanged() { 490 super.displayChanged(); 491 // REMIND: make sure this works when the device is lost and we don't 492 // disable d3d too eagerly 493 if (d3dAvailable) { 494 d3dCaps = getDeviceCaps(getScreen()); 495 } 496 } 497 498 @Override 499 protected void invalidate(int defaultScreen) { 500 super.invalidate(defaultScreen); 501 // REMIND: this is a bit excessive, isD3DEnabledOnDevice will return 502 // false anyway because the device is invalid 503 d3dCaps = new D3DContextCaps(CAPS_EMPTY, null); 504 } 505 }