/*
 * Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.prism.es2;

import com.sun.glass.ui.Screen;
import com.sun.javafx.geom.Rectangle;
import com.sun.prism.GraphicsResource;
import com.sun.prism.Presentable;
import com.sun.prism.PresentableState;
import com.sun.prism.RTTexture;
import com.sun.prism.CompositeMode;
import com.sun.prism.impl.PrismSettings;
import com.sun.javafx.PlatformUtil;
import com.sun.prism.ResourceFactory;
import com.sun.prism.Texture.WrapMode;

class ES2SwapChain implements ES2RenderTarget, Presentable, GraphicsResource {

    private final ES2Context context;
    private final PresentableState pState;
    // On screen
    private GLDrawable drawable;
    private boolean needsResize;
    private boolean opaque = false;
    private int w, h;
    private float pixelScaleFactorX;
    private float pixelScaleFactorY;
    // a value of zero corresponds to the windowing system-provided
    // framebuffer object
    int nativeDestHandle = 0;
    private final boolean msaa;
    
An offscreen surface that acts as a persistent backbuffer, currently only used when dirty region optimizations are enabled in the scenegraph. In OpenGL, the contents of a window's (hardware) backbuffer are undefined after a swapBuffers() operation. The dirty region optimizations used in the Prism scenegraph require the window's backbuffer to be persistent, so when those optimizations are enabled, we insert this special stableBackbuffer into the swap chain. In createGraphics() we return a Graphics object that points to this stableBackbuffer so that the scenegraph gets rendered into it, and then at present() time we first copy stableBackbuffer into the window's hardware backbuffer prior to calling swapBuffers().
/** * An offscreen surface that acts as a persistent backbuffer, currently * only used when dirty region optimizations are enabled in the scenegraph. * * In OpenGL, the contents of a window's (hardware) backbuffer are * undefined after a swapBuffers() operation. The dirty region * optimizations used in the Prism scenegraph require the window's * backbuffer to be persistent, so when those optimizations are enabled, * we insert this special stableBackbuffer into the swap chain. * In createGraphics() we return a Graphics object that points to this * stableBackbuffer so that the scenegraph gets rendered into it, * and then at present() time we first copy stableBackbuffer into the * window's hardware backbuffer prior to calling swapBuffers(). */
private RTTexture stableBackbuffer; private boolean copyFullBuffer; @Override public boolean isOpaque() { if (stableBackbuffer != null) { return stableBackbuffer.isOpaque(); } else { return opaque; } } @Override public void setOpaque(boolean isOpaque) { if (stableBackbuffer != null) { stableBackbuffer.setOpaque(isOpaque); } else { this.opaque = isOpaque; } } ES2SwapChain(ES2Context context, PresentableState pState) { this.context = context; this.pState = pState; this.pixelScaleFactorX = pState.getRenderScaleX(); this.pixelScaleFactorY = pState.getRenderScaleY(); this.msaa = pState.isMSAA(); long nativeWindow = pState.getNativeWindow(); drawable = ES2Pipeline.glFactory.createGLDrawable( nativeWindow, context.getPixelFormat()); } @Override public boolean lockResources(PresentableState pState) { if (this.pState != pState || pixelScaleFactorX != pState.getRenderScaleX() || pixelScaleFactorY != pState.getRenderScaleY()) { return true; } needsResize = (w != pState.getRenderWidth() || h != pState.getRenderHeight()); // the stableBackbuffer will be used as the render target if (stableBackbuffer != null && !needsResize) { stableBackbuffer.lock(); if (stableBackbuffer.isSurfaceLost()) { stableBackbuffer = null; // For resizes we can keep the back buffer, but if we lose // the back buffer then we need the caller to know that a // new buffer is coming so that the entire scene can be // redrawn. To force this, we return true and the Presentable // is recreated and repainted in its entirety. return true; } } return false; } @Override public boolean prepare(Rectangle clip) { try { ES2Graphics g = ES2Graphics.create(context, this); if (stableBackbuffer != null) { if (needsResize) { g.forceRenderTarget(); needsResize = false; } // Copy (not blend) the stableBackbuffer into place. //TODO: Determine why w/h is needed here w = pState.getRenderWidth(); h = pState.getRenderHeight(); int sw = w; int sh = h; int dw = pState.getOutputWidth(); int dh = pState.getOutputHeight(); copyFullBuffer = false; if (isMSAA()) { context.flushVertexBuffer(); // Note must flip the image vertically during blit g.blit(stableBackbuffer, null, 0, 0, sw, sh, 0, dh, dw, 0); } else { drawTexture(g, stableBackbuffer, 0, 0, dw, dh, 0, 0, sw, sh); } stableBackbuffer.unlock(); } return drawable != null; } catch (Throwable th) { if (PrismSettings.verbose) { th.printStackTrace(); } return false; } } private void drawTexture(ES2Graphics g, RTTexture src, float dx1, float dy1, float dx2, float dy2, float sx1, float sy1, float sx2, float sy2) { CompositeMode savedMode = g.getCompositeMode(); if (!pState.hasWindowManager()) { // no window manager - we need to do the blending ourselves // pass any window-level alpha setting on to the prism graphics object g.setExtraAlpha(pState.getAlpha()); g.setCompositeMode(CompositeMode.SRC_OVER); } else { // we have a window manager - copy (not blend) stable backbuffer into place g.setCompositeMode(CompositeMode.SRC); } g.drawTexture(src, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2); context.flushVertexBuffer(); // restore the blend g.setCompositeMode(savedMode); } @Override public boolean present() { boolean presented = drawable.swapBuffers(context.getGLContext()); context.makeCurrent(null); return presented; } @Override public ES2Graphics createGraphics() { if (drawable.getNativeWindow() != pState.getNativeWindow()) { drawable = ES2Pipeline.glFactory.createGLDrawable( pState.getNativeWindow(), context.getPixelFormat()); } context.makeCurrent(drawable); nativeDestHandle = pState.getNativeFrameBuffer(); if (nativeDestHandle == 0) { GLContext glContext = context.getGLContext(); nativeDestHandle = glContext.getBoundFBO(); } needsResize = (w != pState.getRenderWidth() || h != pState.getRenderHeight()); // the stableBackbuffer will be used as the render target if (stableBackbuffer == null || needsResize) { // note that we will take care of calling // forceRenderTarget() for the hardware backbuffer and // reset the needsResize flag at present() time... if (stableBackbuffer != null) { stableBackbuffer.dispose(); stableBackbuffer = null; } else { // RT-27554 // TODO: this implementation was done to make sure there is a // context current for the hardware backbuffer before we start // attempting to use the FBO associated with the // RTTexture "backbuffer"... ES2Graphics.create(context, this); } w = pState.getRenderWidth(); h = pState.getRenderHeight(); ResourceFactory factory = context.getResourceFactory(); stableBackbuffer = factory.createRTTexture(w, h, WrapMode.CLAMP_NOT_NEEDED, msaa); if (PrismSettings.dirtyOptsEnabled) { stableBackbuffer.contentsUseful(); } copyFullBuffer = true; } ES2Graphics g = ES2Graphics.create(context, stableBackbuffer); g.scale(pixelScaleFactorX, pixelScaleFactorY); return g; } @Override public int getFboID() { return nativeDestHandle; } @Override public Screen getAssociatedScreen() { return context.getAssociatedScreen(); } @Override public int getPhysicalWidth() { return pState.getOutputWidth(); } @Override public int getPhysicalHeight() { return pState.getOutputHeight(); } @Override public int getContentX() { // EGL doesn't have a window manager, so we need to ask the window for // the x/y offset to use if (PlatformUtil.useEGL()) { return pState.getWindowX(); } else { return 0; } } @Override public int getContentY() { // EGL doesn't have a window manager, so we need to ask the window // for the x/y offset to use if (PlatformUtil.useEGL()) { return pState.getScreenHeight() - pState.getOutputHeight() - pState.getWindowY(); } else { return 0; } } @Override public int getContentWidth() { return pState.getOutputWidth(); } @Override public int getContentHeight() { return pState.getOutputHeight(); } @Override public float getPixelScaleFactorX() { return pixelScaleFactorX; } @Override public float getPixelScaleFactorY() { return pixelScaleFactorY; } @Override public void dispose() { if (stableBackbuffer != null) { stableBackbuffer.dispose(); stableBackbuffer = null; } } @Override public boolean isMSAA() { return stableBackbuffer != null ? stableBackbuffer.isMSAA() : msaa; } }