/* * Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package com.sun.prism.es2; import com.sun.glass.ui.Screen; import com.sun.prism.Graphics; import com.sun.prism.Image; import com.sun.prism.PixelFormat; import com.sun.prism.RTTexture; import com.sun.prism.ReadbackRenderTarget; import com.sun.prism.Texture; import com.sun.prism.impl.PrismSettings; import com.sun.prism.impl.PrismTrace; import java.nio.Buffer; class ES2RTTexture extends ES2Texture implements ES2RenderTarget, RTTexture, ReadbackRenderTarget { private boolean opaque; private ES2RTTexture(ES2Context context, ES2TextureResource resource, WrapMode wrapMode, int physicalWidth, int physicalHeight, int contentX, int contentY, int contentWidth, int contentHeight, int maxContentWidth, int maxContentHeight) { super(context, resource, PixelFormat.BYTE_BGRA_PRE, wrapMode, physicalWidth, physicalHeight, contentX, contentY, contentWidth, contentHeight, maxContentWidth, maxContentHeight, false); PrismTrace.rttCreated(resource.getResource().getFboID(), physicalWidth, physicalHeight, PixelFormat.BYTE_BGRA_PRE.getBytesPerPixelUnit()); this.opaque = false; } /** * Attach a depth buffer to the currently bound FBO * @param context current context */ void attachDepthBuffer(ES2Context context) { // Return immediately if this RTT already has a depth buffer ES2RTTextureData texData = resource.getResource(); int dbID = texData.getDepthBufferID(); if (dbID != 0) { return; } int msaaSamples = isMSAA() ? context.getGLContext().getSampleSize() : 0; dbID = context.getGLContext().createDepthBuffer(getPhysicalWidth(), getPhysicalHeight(), msaaSamples); // Add to disposer record so that we can cleanup the depth buffer when // this RTT is destroyed. texData.setDepthBufferID(dbID); } /** * Create and attach a color multisample render buffer to current FBO * @param context current context */ private void createAndAttachMSAABuffer(ES2Context context) { // Assert isMSAA() must be true ES2RTTextureData texData = resource.getResource(); int rbID = texData.getMSAARenderBufferID(); if (rbID != 0) { return; } GLContext glContext = context.getGLContext(); rbID = glContext.createRenderBuffer(getPhysicalWidth(), getPhysicalHeight(), glContext.getSampleSize()); // Add to disposer record so that we can cleanup the MSAA // render buffer when this RTT is destroyed. texData.setMSAARenderBufferID(rbID); } static int getCompatibleDimension(ES2Context context, int dim, WrapMode wrapMode) { GLContext glContext = context.getGLContext(); boolean pad; switch (wrapMode) { case CLAMP_NOT_NEEDED: pad = false; break; case CLAMP_TO_ZERO: pad = !glContext.canClampToZero(); break; default: case CLAMP_TO_EDGE: case REPEAT: throw new IllegalArgumentException("wrap mode not supported for RT textures: "+wrapMode); case CLAMP_TO_EDGE_SIMULATED: case CLAMP_TO_ZERO_SIMULATED: case REPEAT_SIMULATED: throw new IllegalArgumentException("Cannot request simulated wrap mode: "+wrapMode); } int paddedDim = pad ? dim + 2 : dim; int maxSize = glContext.getMaxTextureSize(); int texDim; if (glContext.canCreateNonPowTwoTextures()) { texDim = (paddedDim <= maxSize) ? paddedDim : 0; } else { texDim = nextPowerOfTwo(paddedDim, maxSize); } if (texDim == 0) { throw new RuntimeException( "Requested texture dimension (" + dim + ") " + "requires dimension (" + texDim + ") " + "that exceeds maximum texture size (" + maxSize + ")"); } // make sure the texture is not smaller than minimum size texDim = Math.max(texDim, PrismSettings.minRTTSize); // Note that ES2Context will set the viewport to the content // region of a RenderTarget (to ensure that we don't render into // the padded area of transparent pixels, if present). Since // RTTextures are frequently recycled (i.e., reused by Decora) it // is imperative that we initialize the content region of the // RTTexture to be the physical size of the FBO modulo the padded // region. (Suppose the caller asks for a 110x220 RTTexture, but // nonpow2 textures aren't supported; in this case, we will actually // create a 128x256 FBO. If later that RTTexture gets reused for // a caller expecting to use 126x240 pixels, the viewport will be // setup correctly because it will set to the content region, or // 128x256 in this case, assuming no padding.) return pad ? texDim - 2 : texDim; } static ES2RTTexture create(ES2Context context, int w, int h, WrapMode wrapMode, boolean msaa) { // Normally we would use GL_CLAMP_TO_BORDER with a transparent border // color to implement our CLAMP_TO_ZERO edge mode, but unfortunately // that mode is not available in OpenGL ES. The workaround is to pad // the fbo with 1 row/column of transparent pixels on each side if the // caller requires and requests it, and we have to be careful to use // the getContentX/Y/Width/Height() methods so that we access only the // content area of the texture. // The downside of this approach is that when npot textures are not // supported, the padding may cause us to cross the power-of-two // threshold more easily and therefore waste more VRAM. // Note that only CLAMP_NOT_NEEDED and CLAMP_TO_ZERO are supported // for RT textures. The other modes could be supported on desktop // platforms, but emulating them on non-GL2 platforms would be // prohibitively difficult. GLContext glContext = context.getGLContext(); boolean pad; switch (wrapMode) { case CLAMP_NOT_NEEDED: pad = false; break; case CLAMP_TO_ZERO: pad = !glContext.canClampToZero(); break; default: case CLAMP_TO_EDGE: case REPEAT: throw new IllegalArgumentException("wrap mode not supported for RT textures: "+wrapMode); case CLAMP_TO_EDGE_SIMULATED: case CLAMP_TO_ZERO_SIMULATED: case REPEAT_SIMULATED: throw new IllegalArgumentException("Cannot request simulated wrap mode: "+wrapMode); } int contentX, contentY; int paddedW, paddedH; if (pad) { contentX = 1; contentY = 1; paddedW = w + 2; paddedH = h + 2; wrapMode = wrapMode.simulatedVersion(); } else { contentX = 0; contentY = 0; paddedW = w; paddedH = h; } int maxSize = glContext.getMaxTextureSize(); int texWidth, texHeight; if (glContext.canCreateNonPowTwoTextures()) { texWidth = (paddedW <= maxSize) ? paddedW : 0; texHeight = (paddedH <= maxSize) ? paddedH : 0; } else { texWidth = nextPowerOfTwo(paddedW, maxSize); texHeight = nextPowerOfTwo(paddedH, maxSize); } if (texWidth == 0 || texHeight == 0) { throw new RuntimeException( "Requested texture dimensions (" + w + "x" + h + ") " + "require dimensions (" + texWidth + "x" + texHeight + ") " + "that exceed maximum texture size (" + maxSize + ")"); } // make sure the texture is not smaller than minimum size int minSize = PrismSettings.minRTTSize; texWidth = Math.max(texWidth, minSize); texHeight = Math.max(texHeight, minSize); ES2VramPool pool = ES2VramPool.instance; long size = pool.estimateRTTextureSize(texWidth, texHeight, false); if (!pool.prepareForAllocation(size)) { return null; } // Note that ES2Context will set the viewport to the content // region of a RenderTarget (to ensure that we don't render into // the padded area of transparent pixels, if present). Since // RTTextures are frequently recycled (i.e., reused by Decora) it // is imperative that we initialize the content region of the // RTTexture to be the physical size of the FBO modulo the padded // region. (Suppose the caller asks for a 110x220 RTTexture, but // nonpow2 textures aren't supported; in this case, we will actually // create a 128x256 FBO. If later that RTTexture gets reused for // a caller expecting to use 126x240 pixels, the viewport will be // setup correctly because it will set to the content region, or // 128x256 in this case, assuming no padding.) int contentW, contentH; int maxContentW, maxContentH; if (pad) { maxContentW = texWidth - 2; maxContentH = texHeight - 2; contentW = w; contentH = h; } else { maxContentW = texWidth; maxContentH = texHeight; contentW = w; contentH = h; } // save current texture glContext.setActiveTextureUnit(0); int savedFBO = glContext.getBoundFBO(); int savedTex = glContext.getBoundTexture(); int nativeTexID = 0; if (!msaa) { // TODO Mac and some other platforms do not support multisample texture // thus forced to skip texture creation below, and rather create a // msaa render buffer nativeTexID = glContext.createTexture(texWidth, texHeight); } int nativeFBOID = 0; if (nativeTexID != 0 || msaa) { // Create FBO (this method will generate and bind a new FBO, // and if texture is valid, attach texture as color attribute) nativeFBOID = glContext.createFBO(nativeTexID); if (nativeFBOID == 0) { glContext.deleteTexture(nativeTexID); nativeTexID = 0; } } ES2RTTextureData texData = new ES2RTTextureData(context, nativeTexID, nativeFBOID, texWidth, texHeight, size); ES2TextureResource texRes = new ES2TextureResource(texData); ES2RTTexture es2RTT = new ES2RTTexture(context, texRes, wrapMode, texWidth, texHeight, contentX, contentY, contentW, contentH, maxContentW, maxContentH); if (msaa) { es2RTT.createAndAttachMSAABuffer(context); } // Restore previous FBO glContext.bindFBO(savedFBO); // restore previous texture glContext.setBoundTexture(savedTex); return es2RTT; } public Texture getBackBuffer() { return this; } public Graphics createGraphics() { return ES2Graphics.create(context, this); } public int[] getPixels() { return null; } public boolean readPixels(Buffer pixels, int x, int y, int width, int height) { context.flushVertexBuffer(); GLContext glContext = context.getGLContext(); int id = glContext.getBoundFBO(); int fboID = getFboID(); boolean changeBoundFBO = id != fboID; if (changeBoundFBO) { glContext.bindFBO(fboID); } boolean result = glContext.readPixels(pixels, x, y, width, height); if (changeBoundFBO) { glContext.bindFBO(id); } return result; } public boolean readPixels(Buffer pixels) { return readPixels(pixels, getContentX(), getContentY(), getContentWidth(), getContentHeight()); } public int getFboID() { return resource.getResource().getFboID(); } public Screen getAssociatedScreen() { return context.getAssociatedScreen(); } @Override public void update(Image img) { throw new UnsupportedOperationException("update() not supported for RTTextures"); } @Override public void update(Image img, int dstx, int dsty) { throw new UnsupportedOperationException("update() not supported for RTTextures"); } @Override public void update(Image img, int dstx, int dsty, int w, int h) { throw new UnsupportedOperationException("update() not supported for RTTextures"); } @Override public void update(Image img, int dstx, int dsty, int w, int h, boolean skipFlush) { throw new UnsupportedOperationException("update() not supported for RTTextures"); } @Override public void update(Buffer pixels, PixelFormat format, int dstx, int dsty, int srcx, int srcy, int srcw, int srch, int srcscan, boolean skipFlush) { throw new UnsupportedOperationException("update() not supported for RTTextures"); } public boolean isOpaque() { return opaque; } public void setOpaque(boolean opaque) { this.opaque = opaque; } public boolean isVolatile() { return false; } public boolean isMSAA() { return resource.getResource().getMSAARenderBufferID() != 0; } }