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