1 /*
   2  * Copyright (c) 2009, 2015, 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 com.sun.prism.es2;
  27 
  28 import com.sun.glass.ui.Screen;
  29 import com.sun.javafx.geom.Rectangle;
  30 import com.sun.javafx.geom.Vec3d;
  31 import com.sun.javafx.geom.transform.Affine2D;
  32 import com.sun.javafx.geom.transform.Affine3D;
  33 import com.sun.javafx.geom.transform.BaseTransform;
  34 import com.sun.javafx.geom.transform.GeneralTransform3D;
  35 import com.sun.javafx.sg.prism.NGCamera;
  36 import com.sun.javafx.sg.prism.NGDefaultCamera;
  37 import com.sun.prism.CompositeMode;
  38 import com.sun.prism.Graphics;
  39 import com.sun.prism.Material;
  40 import com.sun.prism.RTTexture;
  41 import com.sun.prism.RenderTarget;
  42 import com.sun.prism.Texture;
  43 import com.sun.prism.impl.PrismSettings;
  44 import com.sun.prism.impl.ps.BaseShaderContext;
  45 import com.sun.prism.ps.Shader;
  46 import com.sun.prism.ps.ShaderFactory;
  47 
  48 class ES2Context extends BaseShaderContext {
  49 
  50     // Temporary variables
  51     private static GeneralTransform3D scratchTx = new GeneralTransform3D();
  52     private static final GeneralTransform3D flipTx = new GeneralTransform3D();
  53     private static final Affine3D scratchAffine3DTx = new Affine3D();
  54     // contains the combined projection/modelview matrix (elements 0-15)
  55     private static float rawMatrix[] = new float[GLContext.NUM_MATRIX_ELEMENTS];
  56 
  57     private GeneralTransform3D projViewTx = new GeneralTransform3D();
  58     private GeneralTransform3D worldTx = new GeneralTransform3D();
  59     private Vec3d cameraPos = new Vec3d();
  60 
  61     private RenderTarget currentTarget;
  62     private final GLContext glContext;
  63     private final GLDrawable dummyGLDrawable;
  64     private final GLPixelFormat pixelFormat;
  65     private State state;
  66     private int quadIndices;
  67     // The drawable that is current to the glContext
  68     private GLDrawable currentDrawable = null;
  69     private int indexBuffer = 0;
  70     private int shaderProgram;
  71 
  72     public static final int NUM_QUADS = PrismSettings.superShader ? 4096 : 256;
  73 
  74     ES2Context(Screen screen, ShaderFactory factory) {
  75         super(screen, factory, NUM_QUADS);
  76         GLFactory glF = ES2Pipeline.glFactory;
  77 
  78         // NOTE: There is issue with the returned value of getNativeScreen.
  79         // HMonitor (Windows), GTKMonitor index (Linux) ...
  80         // We would prefer HDC (Windows) and screen number(index) (Linux)
  81         pixelFormat =
  82                 glF.createGLPixelFormat(screen.getNativeScreen(),
  83                 ES2Pipeline.pixelFormatAttributes);
  84 
  85         dummyGLDrawable = glF.createDummyGLDrawable(pixelFormat);
  86         glContext = glF.createGLContext(dummyGLDrawable, pixelFormat,
  87                 glF.getShareContext(), PrismSettings.isVsyncEnabled);
  88         makeCurrent(dummyGLDrawable);
  89 
  90         glContext.enableVertexAttributes();
  91         quadIndices = genQuadsIndexBuffer(NUM_QUADS);
  92         setIndexBuffer(quadIndices);
  93         state = new State();
  94     }
  95 
  96     static short [] getQuadIndices16bit(int numQuads) {
  97         short data[] = new short[numQuads * 6];
  98 
  99         for (int i = 0; i != numQuads; ++i) {
 100             int vtx = i * 4;
 101             int idx = i * 6;
 102             data[idx+0] = (short) (vtx+0);
 103             data[idx+1] = (short) (vtx+1);
 104             data[idx+2] = (short) (vtx+2);
 105 
 106             data[idx+3] = (short) (vtx+2);
 107             data[idx+4] = (short) (vtx+1);
 108             data[idx+5] = (short) (vtx+3);
 109         }
 110 
 111         return data;
 112     }
 113 
 114     int genQuadsIndexBuffer(int numQuads) {
 115         if (numQuads * 6 > 0x10000)
 116             throw new IllegalArgumentException("vertex indices overflow");
 117 
 118         return glContext.createIndexBuffer16(getQuadIndices16bit(numQuads));
 119     }
 120 
 121     final void clearContext() {
 122         if (currentDrawable != null) {
 123             currentDrawable.swapBuffers(glContext);
 124         }
 125     }
 126 
 127     final void setIndexBuffer(int ib) {
 128         if (indexBuffer != ib) {
 129             glContext.setIndexBuffer(indexBuffer = ib);
 130         }
 131     }
 132 
 133     GLContext getGLContext() {
 134         return glContext;
 135     }
 136 
 137     GLPixelFormat getPixelFormat() {
 138         return pixelFormat;
 139     }
 140 
 141     ES2Shader getPhongShader(ES2MeshView meshView) {
 142         return ES2PhongShader.getShader(meshView, this);
 143     }
 144 
 145     void makeCurrent(GLDrawable drawable) {
 146         if (drawable == null) {
 147             drawable = dummyGLDrawable;
 148         }
 149         if (drawable != currentDrawable) {
 150             glContext.makeCurrent(drawable);
 151             // Need to restore FBO to on screen framebuffer
 152             glContext.bindFBO(0);
 153             currentDrawable = drawable;
 154         }
 155     }
 156 
 157     /**
 158      * Called from ES2Graphics.updateRenderTarget() in response to a window
 159      * resize event.  This method ensures that the context is made current
 160      * after the resize event, which is required on Mac OS X in order to
 161      * force a call to [NSOpenGLContext update].
 162      */
 163     void forceRenderTarget(ES2Graphics g) {
 164         updateRenderTarget(g.getRenderTarget(), g.getCameraNoClone(),
 165                 g.isDepthTest() && g.isDepthBuffer());
 166     }
 167 
 168     int getShaderProgram() {
 169         return shaderProgram;
 170     }
 171 
 172     // Forcibly sets the current shader program to the given object.
 173     void setShaderProgram(int progid) {
 174         shaderProgram = progid;
 175         glContext.setShaderProgram(progid);
 176     }
 177 
 178     // Sets the current shader program to the given object only if it was
 179     // not already the current program.
 180     void updateShaderProgram(int progid) {
 181         if (progid != shaderProgram) {
 182             setShaderProgram(progid);
 183         }
 184     }
 185 
 186     @Override
 187     protected void init() {
 188         super.init();
 189     }
 190 
 191     @Override
 192     protected void releaseRenderTarget() {
 193         currentTarget = null;
 194         super.releaseRenderTarget();
 195     }
 196 
 197     @Override
 198     protected State updateRenderTarget(RenderTarget target, NGCamera camera,
 199             boolean depthTest) {
 200         int fboID = ((ES2RenderTarget)target).getFboID();
 201         glContext.bindFBO(fboID);
 202 
 203         boolean msaa = false;
 204         if (target instanceof ES2RTTexture) {
 205             // Attach a depth buffer to the currently bound FBO
 206             ES2RTTexture rtTarget = (ES2RTTexture)target;
 207             msaa = rtTarget.isMSAA();
 208             if (depthTest) {
 209                 rtTarget.attachDepthBuffer(this);
 210             }
 211         }
 212 
 213         // update viewport
 214         int x = target.getContentX();
 215         int y = target.getContentY();
 216         int w = target.getContentWidth();
 217         int h = target.getContentHeight();
 218         glContext.updateViewportAndDepthTest(x, y, w, h, depthTest);
 219         glContext.updateMSAAState(msaa);
 220 
 221         if (camera instanceof NGDefaultCamera) {
 222             // update projection matrix; this will be uploaded to the shader
 223             // along with the modelview matrix in updateShaderTransform()
 224             ((NGDefaultCamera) camera).validate(w, h);
 225             scratchTx = camera.getProjViewTx(scratchTx);
 226         } else {
 227             scratchTx = camera.getProjViewTx(scratchTx);
 228             // TODO: verify that this is the right solution. There may be
 229             // other use-cases where rendering needs different viewport size.
 230             double vw = camera.getViewWidth();
 231             double vh = camera.getViewHeight();
 232             if (w != vw || h != vh) {
 233                 scratchTx.scale(vw / w, vh / h, 1.0);
 234             }
 235         }
 236 
 237         if (target instanceof ES2RTTexture) {
 238             // Compute a flipped version of projViewTx
 239             projViewTx.set(flipTx);
 240             projViewTx.mul(scratchTx);
 241         } else {
 242             projViewTx.set(scratchTx);
 243         }
 244 
 245         // update camera position; this will be uploaded to the shader
 246         // when we switch to 3D state
 247         cameraPos = camera.getPositionInWorld(cameraPos);
 248 
 249         currentTarget = target;
 250         return state;
 251     }
 252 
 253     @Override
 254     protected void updateTexture(int texUnit, Texture tex) {
 255         glContext.updateActiveTextureUnit(texUnit);
 256 
 257         if (tex == null) {
 258             glContext.updateBoundTexture(0);
 259         } else {
 260             ES2Texture es2Tex = (ES2Texture)tex;
 261             glContext.updateBoundTexture(es2Tex.getNativeSourceHandle());
 262             es2Tex.updateWrapState();
 263             es2Tex.updateFilterState();
 264         }
 265     }
 266 
 267     @Override
 268     protected void updateShaderTransform(Shader shader, BaseTransform xform) {
 269         if (xform == null) {
 270             xform = BaseTransform.IDENTITY_TRANSFORM;
 271         }
 272 
 273         scratchTx.set(projViewTx);
 274         updateRawMatrix(scratchTx.mul(xform));
 275 
 276         ES2Shader es2shader = (ES2Shader) shader;
 277         es2shader.setMatrix("mvpMatrix", rawMatrix);
 278 //        printRawMatrix("mvpMatrix");
 279 
 280         if (es2shader.isPixcoordUsed()) {
 281             // the gl_FragCoord variable is in window coordinates and
 282             // does not take the viewport origin into account (or the fact
 283             // that we do a y-flip of the projection matrix in the case
 284             // of onscreen windows for that matter); we need to update
 285             // the special jsl_pixCoordOffset param here so that the shader
 286             // can continue to treat pixcoord as if it were in the range
 287             // [0,0] to [contentWidth,contentHeight] of the destination surface
 288             float xoff = currentTarget.getContentX();
 289             float yoff = currentTarget.getContentY();
 290             float yinv, yflip;
 291             if (currentTarget instanceof ES2SwapChain) {
 292                 // there is a y-flip in this case
 293                 yinv = currentTarget.getPhysicalHeight();
 294                 yflip = 1f;
 295             } else {
 296                 // no y-flip for RTTextures
 297                 yinv = 0f;
 298                 yflip = -1f;
 299             }
 300             shader.setConstant("jsl_pixCoordOffset", xoff, yoff, yinv, yflip);
 301         }
 302     }
 303 
 304     @Override
 305     protected void updateWorldTransform(BaseTransform xform) {
 306         worldTx.setIdentity();
 307         if ((xform != null) && (!xform.isIdentity())) {
 308             worldTx.mul(xform);
 309         }
 310     }
 311 
 312     @Override
 313     protected void updateClipRect(Rectangle clipRect) {
 314         if (clipRect == null || clipRect.isEmpty()) {
 315             glContext.scissorTest(false, 0, 0, 0, 0);
 316         } else {
 317             // the scissor rectangle is specified using the lower-left
 318             // origin of the clip region (in the framebuffer's coordinate
 319             // space), so we must account for the x/y offsets of the
 320             // destination surface, and use a flipped y origin when rendering
 321             // to an ES2SwapChain
 322             int w = clipRect.width;
 323             int h = clipRect.height;
 324             int x = currentTarget.getContentX();
 325             int y = currentTarget.getContentY();
 326             if (currentTarget instanceof ES2RTTexture) {
 327                 x += clipRect.x;
 328                 y += clipRect.y;
 329             } else {
 330                 int dsth = currentTarget.getPhysicalHeight();
 331                 x += clipRect.x;
 332                 y += dsth - (clipRect.y + h);
 333             }
 334             glContext.scissorTest(true, x, y, w, h);
 335         }
 336     }
 337 
 338     @Override
 339     protected void updateCompositeMode(CompositeMode mode) {
 340         switch (mode) {
 341             case CLEAR:
 342                 glContext.blendFunc(GLContext.GL_ZERO, GLContext.GL_ZERO);
 343                 break;
 344             case SRC:
 345                 glContext.blendFunc(GLContext.GL_ONE, GLContext.GL_ZERO);
 346                 break;
 347             case SRC_OVER:
 348                 glContext.blendFunc(GLContext.GL_ONE, GLContext.GL_ONE_MINUS_SRC_ALPHA);
 349                 break;
 350             case DST_OUT:
 351                 glContext.blendFunc(GLContext.GL_ZERO, GLContext.GL_ONE_MINUS_SRC_ALPHA);
 352                 break;
 353             case ADD:
 354                 glContext.blendFunc(GLContext.GL_ONE, GLContext.GL_ONE);
 355                 break;
 356             default:
 357                 throw new InternalError("Unrecognized composite mode: " + mode);
 358         }
 359     }
 360 
 361     @Override
 362     public void setDeviceParametersFor2D() {
 363         // invalidate cache data
 364         indexBuffer = 0;
 365         shaderProgram = 0;
 366         glContext.setDeviceParametersFor2D();
 367 
 368         // Bind vertex attributes and index buffer
 369         glContext.enableVertexAttributes();
 370         setIndexBuffer(quadIndices);
 371     }
 372 
 373     @Override
 374     public void setDeviceParametersFor3D() {
 375         // unbind vertex attributes and index buffer
 376         glContext.disableVertexAttributes();
 377         glContext.setDeviceParametersFor3D();
 378     }
 379 
 380     long createES2Mesh() {
 381         return glContext.createES2Mesh();
 382     }
 383 
 384     // TODO: 3D - Should this be called dispose?
 385     void releaseES2Mesh(long nativeHandle) {
 386         glContext.releaseES2Mesh(nativeHandle);
 387     }
 388 
 389     boolean buildNativeGeometry(long nativeHandle, float[] vertexBuffer,
 390             int vertexBufferLength, short[] indexBuffer, int indexBufferLength) {
 391         return glContext.buildNativeGeometry(nativeHandle, vertexBuffer,
 392                 vertexBufferLength, indexBuffer, indexBufferLength);
 393     }
 394 
 395     boolean buildNativeGeometry(long nativeHandle, float[] vertexBuffer,
 396             int vertexBufferLength, int[] indexBuffer, int indexBufferLength) {
 397         return glContext.buildNativeGeometry(nativeHandle, vertexBuffer,
 398                 vertexBufferLength, indexBuffer, indexBufferLength);
 399     }
 400 
 401     long createES2PhongMaterial() {
 402         return glContext.createES2PhongMaterial();
 403     }
 404 
 405     // TODO: 3D - Should this be called dispose?
 406     void releaseES2PhongMaterial(long nativeHandle) {
 407         glContext.releaseES2PhongMaterial(nativeHandle);
 408     }
 409 
 410     void setSolidColor(long nativeHandle, float r, float g, float b, float a) {
 411         glContext.setSolidColor(nativeHandle, r, g, b, a);
 412     }
 413 
 414     void setMap(long nativeHandle, int mapType, int texID) {
 415         glContext.setMap(nativeHandle, mapType, texID);
 416     }
 417 
 418     long createES2MeshView(ES2Mesh mesh) {
 419         return glContext.createES2MeshView(mesh.getNativeHandle());
 420     }
 421 
 422     // TODO: 3D - Should this be called dispose?
 423     void releaseES2MeshView(long nativeHandle) {
 424         glContext.releaseES2MeshView(nativeHandle);
 425     }
 426 
 427     void setCullingMode(long nativeHandle, int cullingMode) {
 428         // NOTE: Native code has set clockwise order as front-facing
 429         glContext.setCullingMode(nativeHandle, cullingMode);
 430     }
 431 
 432     void setMaterial(long nativeHandle, Material material) {
 433         ES2PhongMaterial es2Material = (ES2PhongMaterial)material;
 434 
 435         glContext.setMaterial(nativeHandle,
 436                 (es2Material).getNativeHandle());
 437     }
 438 
 439     void setWireframe(long nativeHandle, boolean wireframe) {
 440        glContext.setWireframe(nativeHandle, wireframe);
 441     }
 442 
 443     void setAmbientLight(long nativeHandle, float r, float g, float b) {
 444         glContext.setAmbientLight(nativeHandle, r, g, b);
 445     }
 446 
 447     void setPointLight(long nativeHandle, int index, float x, float y, float z, float r, float g, float b, float w) {
 448         glContext.setPointLight(nativeHandle, index, x, y, z, r, g, b, w);
 449     }
 450 
 451     @Override
 452     public void blit(RTTexture srcRTT, RTTexture dstRTT,
 453                      int srcX0, int srcY0, int srcX1, int srcY1,
 454                      int dstX0, int dstY0, int dstX1, int dstY1)
 455     {
 456         // If dstRTT is null then will blit to currently bound fbo
 457         int dstFboID = dstRTT == null ? 0 : ((ES2RTTexture)dstRTT).getFboID();
 458         int srcFboID = ((ES2RTTexture)srcRTT).getFboID();
 459         glContext.blitFBO(srcFboID, dstFboID,
 460                           srcX0, srcY0, srcX1, srcY1,
 461                           dstX0, dstY0, dstX1, dstY1);
 462     }
 463 
 464     void renderMeshView(long nativeHandle, Graphics g, ES2MeshView meshView) {
 465 
 466         ES2Shader shader = (ES2Shader) getPhongShader(meshView);
 467         setShaderProgram(shader.getProgramObject());
 468 
 469         // Support retina display by scaling the projViewTx and pass it to the shader.
 470         float pixelScaleFactorX = g.getPixelScaleFactorX();
 471         float pixelScaleFactorY = g.getPixelScaleFactorY();
 472         if (pixelScaleFactorX != 1.0 || pixelScaleFactorY != 1.0) {
 473             scratchTx = scratchTx.set(projViewTx);
 474             scratchTx.scale(pixelScaleFactorX, pixelScaleFactorY, 1.0);
 475             updateRawMatrix(scratchTx);
 476         } else {
 477             updateRawMatrix(projViewTx);
 478         }
 479         shader.setMatrix("viewProjectionMatrix", rawMatrix);
 480         shader.setConstant("camPos", (float) cameraPos.x,
 481                 (float) cameraPos.y, (float)cameraPos.z);
 482 
 483         // Undo the SwapChain scaling done in createGraphics() because 3D needs
 484         // this information in the shader (via projViewTx)
 485         BaseTransform xform = g.getTransformNoClone();
 486         if (pixelScaleFactorX != 1.0 || pixelScaleFactorY != 1.0) {
 487             scratchAffine3DTx.setToIdentity();
 488             scratchAffine3DTx.scale(1.0 / pixelScaleFactorX, 1.0 / pixelScaleFactorY);
 489             scratchAffine3DTx.concatenate(xform);
 490             updateWorldTransform(scratchAffine3DTx);
 491         } else {
 492             updateWorldTransform(xform);
 493         }
 494         updateRawMatrix(worldTx);
 495 
 496         shader.setMatrix("worldMatrix", rawMatrix);
 497 //        printRawMatrix("worldMatrix");
 498 
 499         ES2PhongShader.setShaderParamaters(shader, meshView, this);
 500 
 501         glContext.renderMeshView(nativeHandle);
 502     }
 503 
 504     @Override
 505     protected void renderQuads(float coordArray[], byte colorArray[], int numVertices) {
 506         glContext.drawIndexedQuads(coordArray, colorArray, numVertices);
 507     }
 508 
 509     void printRawMatrix(String mesg) {
 510         System.err.println(mesg + " = ");
 511         for (int i = 0; i < 4; i++) {
 512             System.err.println(rawMatrix[i] + ", " + rawMatrix[i+4]
 513                     + ", " + rawMatrix[i+8] + ", " + rawMatrix[i+12]);
 514         }
 515     }
 516 
 517     // Need to transpose the matrix because OpenGL stores its matrix in
 518     // column major (though matrix computation is done in row major)
 519     private void updateRawMatrix(GeneralTransform3D src) {
 520         rawMatrix[0]  = (float)src.get(0); // Scale X
 521         rawMatrix[1]  = (float)src.get(4); // Shear Y
 522         rawMatrix[2]  = (float)src.get(8);
 523         rawMatrix[3]  = (float)src.get(12);
 524         rawMatrix[4]  = (float)src.get(1); // Shear X
 525         rawMatrix[5]  = (float)src.get(5); // Scale Y
 526         rawMatrix[6]  = (float)src.get(9);
 527         rawMatrix[7]  = (float)src.get(13);
 528         rawMatrix[8]  = (float)src.get(2);
 529         rawMatrix[9]  = (float)src.get(6);
 530         rawMatrix[10] = (float)src.get(10);
 531         rawMatrix[11] = (float)src.get(14);
 532         rawMatrix[12] = (float)src.get(3);  // Translate X
 533         rawMatrix[13] = (float)src.get(7);  // Translate Y
 534         rawMatrix[14] = (float)src.get(11);
 535         rawMatrix[15] = (float)src.get(15);
 536     }
 537 
 538     static {
 539         BaseTransform tx = Affine2D.getScaleInstance(1.0, -1.0);
 540         flipTx.setIdentity();
 541         flipTx.mul(tx);
 542     }
 543 }