1 /* 2 * Copyright (c) 2007, 2008, 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 public void windowDeactivated(WindowEvent e) { 239 D3DRenderQueue.getInstance().restoreDevices(); 240 } 241 @Override 242 public void windowActivated(WindowEvent e) { 243 D3DRenderQueue.getInstance().restoreDevices(); 244 } 245 } 246 247 @Override 248 protected void addFSWindowListener(Window w) { 249 // if the window is not a toplevel (has an owner) we have to use the 250 // real toplevel to enter the full-screen mode with (4933099). 251 if (!(w instanceof Frame) && !(w instanceof Dialog) && 252 (realFSWindow = getToplevelOwner(w)) != null) 253 { 254 ownerOrigBounds = realFSWindow.getBounds(); 255 WWindowPeer fp = (WWindowPeer)realFSWindow.getPeer(); 256 257 ownerWasVisible = realFSWindow.isVisible(); 258 Rectangle r = w.getBounds(); 259 // we use operations on peer instead of component because calling 260 // them on component will take the tree lock 261 fp.reshape(r.x, r.y, r.width, r.height); 262 fp.setVisible(true); 263 } else { 264 realFSWindow = w; 265 } 266 267 fsWindowWasAlwaysOnTop = realFSWindow.isAlwaysOnTop(); 268 ((WWindowPeer)realFSWindow.getPeer()).setAlwaysOnTop(true); 269 270 fsWindowListener = new D3DFSWindowAdapter(); 271 realFSWindow.addWindowListener(fsWindowListener); 272 } 273 274 @Override 275 protected void removeFSWindowListener(Window w) { 276 realFSWindow.removeWindowListener(fsWindowListener); 277 fsWindowListener = null; 278 279 /** 280 * Bug 4933099: There is some funny-business to deal with when this 281 * method is called with a Window instead of a Frame. See 4836744 282 * for more information on this. One side-effect of our workaround 283 * for the problem is that the owning Frame of a Window may end 284 * up getting resized during the fullscreen process. When we 285 * return from fullscreen mode, we should resize the Frame to 286 * its original size (just like the Window is being resized 287 * to its original size in GraphicsDevice). 288 */ 289 WWindowPeer wpeer = (WWindowPeer)realFSWindow.getPeer(); 290 if (wpeer != null) { 291 if (ownerOrigBounds != null) { 292 // if the window went into fs mode before it was realized it 293 // could have (0,0) dimensions 294 if (ownerOrigBounds.width == 0) ownerOrigBounds.width = 1; 295 if (ownerOrigBounds.height == 0) ownerOrigBounds.height = 1; 296 wpeer.reshape(ownerOrigBounds.x, ownerOrigBounds.y, 297 ownerOrigBounds.width, ownerOrigBounds.height); 298 if (!ownerWasVisible) { 299 wpeer.setVisible(false); 300 } 301 ownerOrigBounds = null; 302 } 303 if (!fsWindowWasAlwaysOnTop) { 304 wpeer.setAlwaysOnTop(false); 305 } 306 } 307 308 realFSWindow = null; 309 } 310 311 private static native DisplayMode getCurrentDisplayModeNative(int screen); 312 @Override 313 protected DisplayMode getCurrentDisplayMode(final int screen) { 314 D3DRenderQueue rq = D3DRenderQueue.getInstance(); 315 rq.lock(); 316 try { 317 class Result { 318 DisplayMode dm = null; 319 }; 320 final Result res = new Result(); 321 rq.flushAndInvokeNow(new Runnable() { 322 public void run() { 323 res.dm = getCurrentDisplayModeNative(screen); 324 } 325 }); 326 if (res.dm == null) { 327 return super.getCurrentDisplayMode(screen); 328 } 329 return res.dm; 330 } finally { 331 rq.unlock(); 332 } 333 } 334 private static native void configDisplayModeNative(int screen, long hwnd, 335 int width, int height, 336 int bitDepth, 337 int refreshRate); 338 @Override 339 protected void configDisplayMode(final int screen, final WindowPeer w, 340 final int width, final int height, 341 final int bitDepth, final int refreshRate) 342 { 343 // we entered fs mode via gdi 344 if (!fsStatus) { 345 super.configDisplayMode(screen, w, width, height, bitDepth, 346 refreshRate); 347 return; 348 } 349 350 final WWindowPeer wpeer = (WWindowPeer)realFSWindow.getPeer(); 351 352 // REMIND: we do this before we switch the display mode, so 353 // the dimensions may be exceeding the dimensions of the screen, 354 // is this a problem? 355 356 // update the bounds of the owner frame 357 if (getFullScreenWindow() != realFSWindow) { 358 Rectangle screenBounds = getDefaultConfiguration().getBounds(); 359 wpeer.reshape(screenBounds.x, screenBounds.y, width, height); 360 } 361 362 D3DRenderQueue rq = D3DRenderQueue.getInstance(); 363 rq.lock(); 364 try { 365 rq.flushAndInvokeNow(new Runnable() { 366 public void run() { 367 long hwnd = wpeer.getHWnd(); 368 if (hwnd == 0l) { 369 // window is disposed 370 return; 371 } 372 // REMIND: do we really need a window here? 373 // we should probably just use the current one 374 configDisplayModeNative(screen, hwnd, width, height, 375 bitDepth, refreshRate); 376 } 377 }); 378 } finally { 379 rq.unlock(); 380 } 381 } 382 383 private static native void enumDisplayModesNative(int screen, 384 ArrayList modes); 385 @Override 386 protected void enumDisplayModes(final int screen, final ArrayList modes) { 387 D3DRenderQueue rq = D3DRenderQueue.getInstance(); 388 rq.lock(); 389 try { 390 rq.flushAndInvokeNow(new Runnable() { 391 public void run() { 392 enumDisplayModesNative(screen, modes); 393 } 394 }); 395 if (modes.size() == 0) { 396 modes.add(getCurrentDisplayModeNative(screen)); 397 } 398 } finally { 399 rq.unlock(); 400 } 401 } 402 403 private static native long getAvailableAcceleratedMemoryNative(int screen); 404 @Override 405 public int getAvailableAcceleratedMemory() { 406 D3DRenderQueue rq = D3DRenderQueue.getInstance(); 407 rq.lock(); 408 try { 409 class Result { 410 long mem = 0L; 411 }; 412 final Result res = new Result(); 413 rq.flushAndInvokeNow(new Runnable() { 414 public void run() { 415 res.mem = getAvailableAcceleratedMemoryNative(getScreen()); 416 } 417 }); 418 return (int)res.mem; 419 } finally { 420 rq.unlock(); 421 } 422 } 423 424 @Override 425 public GraphicsConfiguration[] getConfigurations() { 426 if (configs == null) { 427 if (isD3DEnabledOnDevice()) { 428 defaultConfig = getDefaultConfiguration(); 429 if (defaultConfig != null) { 430 configs = new GraphicsConfiguration[1]; 431 configs[0] = defaultConfig; 432 return configs.clone(); 433 } 434 } 435 } 436 return super.getConfigurations(); 437 } 438 439 @Override 440 public GraphicsConfiguration getDefaultConfiguration() { 441 if (defaultConfig == null) { 442 if (isD3DEnabledOnDevice()) { 443 defaultConfig = new D3DGraphicsConfig(this); 444 } else { 445 defaultConfig = super.getDefaultConfiguration(); 446 } 447 } 448 return defaultConfig; 449 } 450 451 private static native boolean isD3DAvailableOnDeviceNative(int screen); 452 // REMIND: this method is not used now, we use caps instead 453 public static boolean isD3DAvailableOnDevice(final int screen) { 454 if (!d3dAvailable) { 455 return false; 456 } 457 458 // REMIND: should we cache the result per device somehow, 459 // and then reset and retry it on display change? 460 D3DRenderQueue rq = D3DRenderQueue.getInstance(); 461 rq.lock(); 462 try { 463 class Result { 464 boolean avail = false; 465 }; 466 final Result res = new Result(); 467 rq.flushAndInvokeNow(new Runnable() { 468 public void run() { 469 res.avail = isD3DAvailableOnDeviceNative(screen); 470 } 471 }); 472 return res.avail; 473 } finally { 474 rq.unlock(); 475 } 476 } 477 478 D3DContext getContext() { 479 return context; 480 } 481 482 ContextCapabilities getContextCapabilities() { 483 return d3dCaps; 484 } 485 486 @Override 487 public void displayChanged() { 488 super.displayChanged(); 489 // REMIND: make sure this works when the device is lost and we don't 490 // disable d3d too eagerly 491 if (d3dAvailable) { 492 d3dCaps = getDeviceCaps(getScreen()); 493 } 494 } 495 496 @Override 497 protected void invalidate(int defaultScreen) { 498 super.invalidate(defaultScreen); 499 // REMIND: this is a bit excessive, isD3DEnabledOnDevice will return 500 // false anyway because the device is invalid 501 d3dCaps = new D3DContextCaps(CAPS_EMPTY, null); 502 } 503 }