1 /*
   2  * Copyright (c) 2011, 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.opengl;
  27 
  28 import java.awt.AWTException;
  29 import java.awt.BufferCapabilities;
  30 import java.awt.Color;
  31 import java.awt.Component;
  32 import java.awt.Graphics;
  33 import java.awt.Graphics2D;
  34 import java.awt.GraphicsConfiguration;
  35 import java.awt.GraphicsDevice;
  36 import java.awt.GraphicsEnvironment;
  37 import java.awt.Image;
  38 import java.awt.ImageCapabilities;
  39 import java.awt.Rectangle;
  40 import java.awt.Transparency;
  41 import java.awt.color.ColorSpace;
  42 import java.awt.image.BufferedImage;
  43 import java.awt.image.ColorModel;
  44 import java.awt.image.DataBuffer;
  45 import java.awt.image.DirectColorModel;
  46 import java.awt.image.VolatileImage;
  47 import java.awt.image.WritableRaster;
  48 
  49 import sun.awt.CGraphicsConfig;
  50 import sun.awt.CGraphicsDevice;
  51 import sun.awt.TextureSizeConstraining;
  52 import sun.awt.image.OffScreenImage;
  53 import sun.awt.image.SunVolatileImage;
  54 import sun.awt.image.SurfaceManager;
  55 import sun.java2d.Disposer;
  56 import sun.java2d.DisposerRecord;
  57 import sun.java2d.SunGraphics2D;
  58 import sun.java2d.Surface;
  59 import sun.java2d.SurfaceData;
  60 import sun.java2d.opengl.OGLContext.OGLContextCaps;
  61 import sun.java2d.pipe.hw.AccelSurface;
  62 import sun.java2d.pipe.hw.AccelTypedVolatileImage;
  63 import sun.java2d.pipe.hw.ContextCapabilities;
  64 import static sun.java2d.opengl.OGLSurfaceData.*;
  65 import static sun.java2d.opengl.OGLContext.OGLContextCaps.*;
  66 import sun.java2d.opengl.CGLSurfaceData.CGLVSyncOffScreenSurfaceData;
  67 import sun.java2d.pipe.hw.AccelDeviceEventListener;
  68 import sun.java2d.pipe.hw.AccelDeviceEventNotifier;
  69 
  70 import sun.lwawt.macosx.CPlatformView;
  71 
  72 public class CGLGraphicsConfig extends CGraphicsConfig
  73     implements OGLGraphicsConfig, TextureSizeConstraining
  74 {
  75     //private static final int kOpenGLSwapInterval = RuntimeOptions.getCurrentOptions().OpenGLSwapInterval;
  76     private static final int kOpenGLSwapInterval = 0; // TODO
  77     protected static boolean cglAvailable;
  78     private static ImageCapabilities imageCaps = new CGLImageCaps();
  79 
  80     private int pixfmt;
  81     private BufferCapabilities bufferCaps;
  82     private long pConfigInfo;
  83     private ContextCapabilities oglCaps;
  84     private OGLContext context;
  85     private Object disposerReferent = new Object();
  86 
  87     private final int cachedMaxTextureSize;
  88 
  89     public static native int getDefaultPixFmt(int screennum);
  90     private static native boolean initCGL();
  91     private static native long getCGLConfigInfo(int screennum, int visualnum,
  92                                                 int swapInterval);
  93     private static native int getOGLCapabilities(long configInfo);
  94     private static native int _getMaxTextureSize();
  95 
  96     static {
  97         cglAvailable = initCGL();
  98     }
  99 
 100     protected CGLGraphicsConfig(CGraphicsDevice device, int pixfmt,
 101                                 long configInfo, ContextCapabilities oglCaps)
 102     {
 103         super(device);
 104 
 105         this.pixfmt = pixfmt;
 106         this.pConfigInfo = configInfo;
 107         this.oglCaps = oglCaps;
 108         context = new OGLContext(OGLRenderQueue.getInstance(), this);
 109 
 110         // add a record to the Disposer so that we destroy the native
 111         // CGLGraphicsConfigInfo data when this object goes away
 112         Disposer.addRecord(disposerReferent,
 113                            new CGLGCDisposerRecord(pConfigInfo));
 114 
 115         // 7200762: Workaround a deadlock by caching the value
 116         //          A fix for JDK 8 will remove the workaround
 117         this.cachedMaxTextureSize = _getMaxTextureSize();
 118     }
 119 
 120     @Override
 121     public Object getProxyKey() {
 122         return this;
 123     }
 124 
 125     @Override
 126     public SurfaceData createManagedSurface(int w, int h, int transparency) {
 127         return CGLSurfaceData.createData(this, w, h,
 128                                          getColorModel(transparency),
 129                                          null,
 130                                          OGLSurfaceData.TEXTURE);
 131     }
 132 
 133     public static CGLGraphicsConfig getConfig(CGraphicsDevice device,
 134                                               int pixfmt)
 135     {
 136         if (!cglAvailable) {
 137             return null;
 138         }
 139 
 140         long cfginfo = 0;
 141         final String ids[] = new String[1];
 142         OGLRenderQueue rq = OGLRenderQueue.getInstance();
 143         rq.lock();
 144         try {
 145             // getCGLConfigInfo() creates and destroys temporary
 146             // surfaces/contexts, so we should first invalidate the current
 147             // Java-level context and flush the queue...
 148             OGLContext.invalidateCurrentContext();
 149 
 150             cfginfo = getCGLConfigInfo(device.getCoreGraphicsScreen(), pixfmt,
 151                                        kOpenGLSwapInterval);
 152 
 153             OGLContext.setScratchSurface(cfginfo);
 154             rq.flushAndInvokeNow(new Runnable() {
 155                 public void run() {
 156                     ids[0] = OGLContext.getOGLIdString();
 157                 }
 158             });
 159         } finally {
 160             rq.unlock();
 161         }
 162         if (cfginfo == 0) {
 163             return null;
 164         }
 165 
 166         int oglCaps = getOGLCapabilities(cfginfo);
 167         ContextCapabilities caps = new OGLContextCaps(oglCaps, ids[0]);
 168 
 169         return new CGLGraphicsConfig(device, pixfmt, cfginfo, caps);
 170     }
 171 
 172     public static boolean isCGLAvailable() {
 173         return cglAvailable;
 174     }
 175 
 176     /**
 177      * Returns true if the provided capability bit is present for this config.
 178      * See OGLContext.java for a list of supported capabilities.
 179      */
 180     public final boolean isCapPresent(int cap) {
 181         return ((oglCaps.getCaps() & cap) != 0);
 182     }
 183 
 184     public final long getNativeConfigInfo() {
 185         return pConfigInfo;
 186     }
 187 
 188     /**
 189      * {@inheritDoc}
 190      *
 191      * @see sun.java2d.pipe.hw.BufferedContextProvider#getContext
 192      */
 193     public final OGLContext getContext() {
 194         return context;
 195     }
 196 
 197     @Override
 198     public BufferedImage createCompatibleImage(int width, int height) {
 199         ColorModel model = new DirectColorModel(24, 0xff0000, 0xff00, 0xff);
 200         WritableRaster
 201             raster = model.createCompatibleWritableRaster(width, height);
 202         return new BufferedImage(model, raster, model.isAlphaPremultiplied(),
 203                                  null);
 204     }
 205 
 206     @Override
 207     public ColorModel getColorModel(int transparency) {
 208         switch (transparency) {
 209         case Transparency.OPAQUE:
 210             // REMIND: once the ColorModel spec is changed, this should be
 211             //         an opaque premultiplied DCM...
 212             return new DirectColorModel(24, 0xff0000, 0xff00, 0xff);
 213         case Transparency.BITMASK:
 214             return new DirectColorModel(25, 0xff0000, 0xff00, 0xff, 0x1000000);
 215         case Transparency.TRANSLUCENT:
 216             ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
 217             return new DirectColorModel(cs, 32,
 218                                         0xff0000, 0xff00, 0xff, 0xff000000,
 219                                         true, DataBuffer.TYPE_INT);
 220         default:
 221             return null;
 222         }
 223     }
 224 
 225     public boolean isDoubleBuffered() {
 226         return isCapPresent(CAPS_DOUBLEBUFFERED);
 227     }
 228 
 229     private static class CGLGCDisposerRecord implements DisposerRecord {
 230         private long pCfgInfo;
 231         public CGLGCDisposerRecord(long pCfgInfo) {
 232             this.pCfgInfo = pCfgInfo;
 233         }
 234         public void dispose() {
 235             if (pCfgInfo != 0) {
 236                 OGLRenderQueue.disposeGraphicsConfig(pCfgInfo);
 237                 pCfgInfo = 0;
 238             }
 239         }
 240     }
 241 
 242     // TODO: CGraphicsConfig doesn't implement displayChanged() yet
 243     //@Override
 244     public synchronized void displayChanged() {
 245         //super.displayChanged();
 246 
 247         // the context could hold a reference to a CGLSurfaceData, which in
 248         // turn has a reference back to this CGLGraphicsConfig, so in order
 249         // for this instance to be disposed we need to break the connection
 250         OGLRenderQueue rq = OGLRenderQueue.getInstance();
 251         rq.lock();
 252         try {
 253             OGLContext.invalidateCurrentContext();
 254         } finally {
 255             rq.unlock();
 256         }
 257 
 258         updateTotalDisplayBounds();
 259     }
 260 
 261     @Override
 262     public String toString() {
 263         int screen = getDevice().getCoreGraphicsScreen();
 264         return ("CGLGraphicsConfig[dev="+screen+",pixfmt="+pixfmt+"]");
 265     }
 266 
 267 
 268     /**
 269      * The following methods are invoked from ComponentModel.java rather
 270      * than having the Mac OS X-dependent implementations hardcoded in that
 271      * class.  This way the appropriate actions are taken based on the peer's
 272      * GraphicsConfig, whether it is a CGraphicsConfig or a
 273      * CGLGraphicsConfig.
 274      */
 275 
 276     /**
 277      * Creates a new SurfaceData that will be associated with the given
 278      * LWWindowPeer.
 279      */
 280     @Override
 281     public SurfaceData createSurfaceData(CPlatformView pView) {
 282         return CGLSurfaceData.createData(pView);
 283     }
 284 
 285     /**
 286      * Creates a new SurfaceData that will be associated with the given
 287      * CGLLayer.
 288      */
 289     @Override
 290     public SurfaceData createSurfaceData(CGLLayer layer) {
 291         return CGLSurfaceData.createData(layer);
 292     }
 293 
 294     /**
 295      * Creates a new hidden-acceleration image of the given width and height
 296      * that is associated with the target Component.
 297      */
 298     @Override
 299     public Image createAcceleratedImage(Component target,
 300                                         int width, int height)
 301     {
 302         ColorModel model = getColorModel(Transparency.OPAQUE);
 303         WritableRaster wr =
 304             model.createCompatibleWritableRaster(width, height);
 305         return new OffScreenImage(target, model, wr,
 306                                   model.isAlphaPremultiplied());
 307     }
 308 
 309     /**
 310      * The following methods correspond to the multibuffering methods in
 311      * CWindowPeer.java...
 312      */
 313 
 314     /**
 315      * Attempts to create a OGL-based backbuffer for the given peer.  If
 316      * the requested configuration is not natively supported, an AWTException
 317      * is thrown.  Otherwise, if the backbuffer creation is successful, a
 318      * value of 1 is returned.
 319      */
 320     @Override
 321     public long createBackBuffer(CPlatformView pView,
 322                                  int numBuffers, BufferCapabilities caps)
 323         throws AWTException
 324     {
 325         if (numBuffers > 2) {
 326             throw new AWTException(
 327                 "Only double or single buffering is supported");
 328         }
 329         BufferCapabilities configCaps = getBufferCapabilities();
 330         if (!configCaps.isPageFlipping()) {
 331             throw new AWTException("Page flipping is not supported");
 332         }
 333         if (caps.getFlipContents() == BufferCapabilities.FlipContents.PRIOR) {
 334             throw new AWTException("FlipContents.PRIOR is not supported");
 335         }
 336 
 337         // non-zero return value means backbuffer creation was successful
 338         // (checked in CPlatformWindow.flip(), etc.)
 339         return 1;
 340     }
 341 
 342     /**
 343      * Destroys the backbuffer object represented by the given handle value.
 344      */
 345     @Override
 346     public void destroyBackBuffer(long backBuffer) {
 347     }
 348 
 349     /**
 350      * Creates a VolatileImage that essentially wraps the target Component's
 351      * backbuffer (the provided backbuffer handle is essentially ignored).
 352      */
 353     @Override
 354     public VolatileImage createBackBufferImage(Component target,
 355                                                long backBuffer)
 356     {
 357         return new SunVolatileImage(target,
 358                                     target.getWidth(), target.getHeight(),
 359                                     Boolean.TRUE);
 360     }
 361 
 362     /**
 363      * Performs the native OGL flip operation for the given target Component.
 364      */
 365     @Override
 366     public void flip(CPlatformView pView,
 367                      Component target, VolatileImage xBackBuffer,
 368                      int x1, int y1, int x2, int y2,
 369                      BufferCapabilities.FlipContents flipAction)
 370     {
 371         if (flipAction == BufferCapabilities.FlipContents.COPIED) {
 372             SurfaceManager vsm = SurfaceManager.getManager(xBackBuffer);
 373             SurfaceData sd = vsm.getPrimarySurfaceData();
 374 
 375             if (sd instanceof CGLVSyncOffScreenSurfaceData) {
 376                 CGLVSyncOffScreenSurfaceData vsd =
 377                     (CGLVSyncOffScreenSurfaceData)sd;
 378                 SurfaceData bbsd = vsd.getFlipSurface();
 379                 Graphics2D bbg =
 380                     new SunGraphics2D(bbsd, Color.black, Color.white, null);
 381                 try {
 382                     bbg.drawImage(xBackBuffer, 0, 0, null);
 383                 } finally {
 384                     bbg.dispose();
 385                 }
 386             } else {
 387                 pView.drawImageOnPeer(xBackBuffer, x1, y1, x2, y2);
 388                 return;
 389             }
 390         } else if (flipAction == BufferCapabilities.FlipContents.PRIOR) {
 391             // not supported by CGL...
 392             return;
 393         }
 394 
 395         OGLSurfaceData.swapBuffers(pView.getAWTView());
 396 
 397         if (flipAction == BufferCapabilities.FlipContents.BACKGROUND) {
 398             Graphics g = xBackBuffer.getGraphics();
 399             try {
 400                 g.setColor(target.getBackground());
 401                 g.fillRect(0, 0,
 402                            xBackBuffer.getWidth(),
 403                            xBackBuffer.getHeight());
 404             } finally {
 405                 g.dispose();
 406             }
 407         }
 408     }
 409 
 410     private static class CGLBufferCaps extends BufferCapabilities {
 411         public CGLBufferCaps(boolean dblBuf) {
 412             super(imageCaps, imageCaps,
 413                   dblBuf ? FlipContents.UNDEFINED : null);
 414         }
 415     }
 416 
 417     @Override
 418     public BufferCapabilities getBufferCapabilities() {
 419         if (bufferCaps == null) {
 420             bufferCaps = new CGLBufferCaps(isDoubleBuffered());
 421         }
 422         return bufferCaps;
 423     }
 424 
 425     private static class CGLImageCaps extends ImageCapabilities {
 426         private CGLImageCaps() {
 427             super(true);
 428         }
 429         public boolean isTrueVolatile() {
 430             return true;
 431         }
 432     }
 433 
 434     @Override
 435     public ImageCapabilities getImageCapabilities() {
 436         return imageCaps;
 437     }
 438 
 439     /**
 440      * {@inheritDoc}
 441      *
 442      * @see sun.java2d.pipe.hw.AccelGraphicsConfig#createCompatibleVolatileImage
 443      */
 444     public VolatileImage
 445         createCompatibleVolatileImage(int width, int height,
 446                                       int transparency, int type)
 447     {
 448         if (type == FLIP_BACKBUFFER || type == WINDOW || type == UNDEFINED ||
 449             transparency == Transparency.BITMASK)
 450         {
 451             return null;
 452         }
 453 
 454         if (type == FBOBJECT) {
 455             if (!isCapPresent(CAPS_EXT_FBOBJECT)) {
 456                 return null;
 457             }
 458         } else if (type == PBUFFER) {
 459             boolean isOpaque = transparency == Transparency.OPAQUE;
 460             if (!isOpaque && !isCapPresent(CAPS_STORED_ALPHA)) {
 461                 return null;
 462             }
 463         }
 464 
 465         SunVolatileImage vi = new AccelTypedVolatileImage(this, width, height,
 466                                                           transparency, type);
 467         Surface sd = vi.getDestSurface();
 468         if (!(sd instanceof AccelSurface) ||
 469             ((AccelSurface)sd).getType() != type)
 470         {
 471             vi.flush();
 472             vi = null;
 473         }
 474 
 475         return vi;
 476     }
 477 
 478     /**
 479      * {@inheritDoc}
 480      *
 481      * @see sun.java2d.pipe.hw.AccelGraphicsConfig#getContextCapabilities
 482      */
 483     public ContextCapabilities getContextCapabilities() {
 484         return oglCaps;
 485     }
 486 
 487     public void addDeviceEventListener(AccelDeviceEventListener l) {
 488         int screen = getDevice().getCoreGraphicsScreen();
 489         AccelDeviceEventNotifier.addListener(l, screen);
 490     }
 491 
 492     public void removeDeviceEventListener(AccelDeviceEventListener l) {
 493         AccelDeviceEventNotifier.removeListener(l);
 494     }
 495 
 496     private static final Rectangle totalDisplayBounds = new Rectangle();
 497 
 498     private static void updateTotalDisplayBounds() {
 499         synchronized (totalDisplayBounds) {
 500             Rectangle virtualBounds = new Rectangle();
 501             for (GraphicsDevice gd : GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()) {
 502                 for (GraphicsConfiguration gc : gd.getConfigurations()) {
 503                     virtualBounds = virtualBounds.union(gc.getBounds());
 504                 }
 505             }
 506             totalDisplayBounds.setBounds(virtualBounds);
 507         }
 508     }
 509 
 510 
 511     // 7160609: GL still fails to create a square texture of this size,
 512     //          so we use this value to cap the total display bounds.
 513     private int getMaxTextureSize() {
 514         return cachedMaxTextureSize;
 515     }
 516 
 517     @Override
 518     public int getMaxTextureWidth() {
 519         int width;
 520 
 521         synchronized (totalDisplayBounds) {
 522             if (totalDisplayBounds.width == 0) {
 523                 updateTotalDisplayBounds();
 524             }
 525             width = totalDisplayBounds.width;
 526         }
 527 
 528         return Math.min(width, getMaxTextureSize());
 529     }
 530 
 531     @Override
 532     public int getMaxTextureHeight() {
 533         int height;
 534 
 535         synchronized (totalDisplayBounds) {
 536             if (totalDisplayBounds.height == 0) {
 537                 updateTotalDisplayBounds();
 538             }
 539             height = totalDisplayBounds.height;
 540         }
 541 
 542         return Math.min(height, getMaxTextureSize());
 543     }
 544 }