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 }