/** * Copyright 2019 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of JogAmp Community. */ package jogamp.opengl.ios.eagl; import java.nio.Buffer; import java.nio.ShortBuffer; import java.util.HashMap; import java.util.HashSet; import java.util.List; import com.jogamp.nativewindow.AbstractGraphicsDevice; import com.jogamp.nativewindow.AbstractGraphicsScreen; import com.jogamp.nativewindow.DefaultGraphicsScreen; import com.jogamp.nativewindow.NativeSurface; import com.jogamp.nativewindow.ProxySurface; import com.jogamp.nativewindow.UpstreamSurfaceHook; import com.jogamp.nativewindow.ios.IOSGraphicsDevice; import com.jogamp.opengl.FBObject; import com.jogamp.opengl.GL; import com.jogamp.opengl.GLCapabilities; import com.jogamp.opengl.GLCapabilitiesChooser; import com.jogamp.opengl.GLCapabilitiesImmutable; import com.jogamp.opengl.GLContext; import com.jogamp.opengl.GLDrawable; import com.jogamp.opengl.GLException; import com.jogamp.opengl.GLProfile; import jogamp.nativewindow.WrappedSurface; import jogamp.nativewindow.ios.IOSDummyUpstreamSurfaceHook; import jogamp.nativewindow.ios.IOSUtil; import jogamp.opengl.GLContextImpl; import jogamp.opengl.GLDrawableFactoryImpl; import jogamp.opengl.GLDrawableImpl; import jogamp.opengl.GLDynamicLookupHelper; import jogamp.opengl.GLGraphicsConfigurationUtil; import jogamp.opengl.SharedResourceRunner; import com.jogamp.nativewindow.GenericUpstreamSurfacelessHook; import com.jogamp.opengl.GLExtensions; import com.jogamp.opengl.GLRendererQuirks; import com.jogamp.opengl.FBObject.Attachment; import com.jogamp.opengl.FBObject.Attachment.StorageDefinition; public class IOSEAGLDrawableFactory extends GLDrawableFactoryImpl { private static final boolean DEBUG_SHAREDCTX = DEBUG || GLContext.DEBUG; private static GLDynamicLookupHelper iosEAGLDynamicLookupHelper = null; public IOSEAGLDrawableFactory() { super(); synchronized(IOSEAGLDrawableFactory.class) { if(null==iosEAGLDynamicLookupHelper) { GLDynamicLookupHelper tmp = null; try { tmp = new GLDynamicLookupHelper(new IOSEAGLDynamicLibraryBundleInfo()); } catch (final GLException gle) { if(DEBUG) { gle.printStackTrace(); } } if(null!=tmp && tmp.isLibComplete()) { iosEAGLDynamicLookupHelper = tmp; } } } defaultDevice = new IOSGraphicsDevice(AbstractGraphicsDevice.DEFAULT_UNIT); if(null!=iosEAGLDynamicLookupHelper) { // Register our GraphicsConfigurationFactory implementations // The act of constructing them causes them to be registered IOSEAGLGraphicsConfigurationFactory.registerFactory(); sharedMap = new HashMap(); } } @Override protected final boolean isComplete() { return null != iosEAGLDynamicLookupHelper; } @Override protected final void shutdownImpl() { if( DEBUG ) { System.err.println("IOSEAGLDrawableFactory.shutdown"); } if(null != sharedMap) { sharedMap.clear(); sharedMap = null; } defaultDevice = null; /** * Pulling away the native library may cause havoc .. * macOSXCGLDynamicLookupHelper.destroy(); */ iosEAGLDynamicLookupHelper = null; } @Override public final GLDynamicLookupHelper getGLDynamicLookupHelper(final int majorVersion, final int contextOptions) { return iosEAGLDynamicLookupHelper; } private HashMap sharedMap = new HashMap(); private IOSGraphicsDevice defaultDevice; static class SharedResource implements SharedResourceRunner.Resource { // private IOSEAGLDrawable drawable; // private IOSEAGLContext context; private final GLRendererQuirks glRendererQuirks; IOSGraphicsDevice device; boolean valid; boolean hasNPOTTextures; boolean hasRECTTextures; boolean hasAppleFloatPixels; SharedResource(final IOSGraphicsDevice device, final boolean valid, final boolean hasNPOTTextures, final boolean hasRECTTextures, final boolean hasAppletFloatPixels /* IOSEAGLDrawable draw, IOSEAGLContext ctx */, final GLRendererQuirks glRendererQuirks) { // drawable = draw; // this.context = ctx; this.glRendererQuirks = glRendererQuirks; this.device = device; this.valid = valid; this.hasNPOTTextures = hasNPOTTextures; this.hasRECTTextures = hasRECTTextures; this.hasAppleFloatPixels = hasAppletFloatPixels; } @Override public final boolean isAvailable() { return valid; } @Override public final IOSGraphicsDevice getDevice() { return device; } // final IOSEAGLContext getContext() { return context; } final boolean isNPOTTextureAvailable() { return hasNPOTTextures; } final boolean isRECTTextureAvailable() { return hasRECTTextures; } final boolean isAppleFloatPixelsAvailable() { return hasAppleFloatPixels; } @Override public final AbstractGraphicsScreen getScreen() { return null; } @Override public final GLDrawableImpl getDrawable() { return null; } @Override public GLContextImpl getContext() { return null; } @Override public GLRendererQuirks getRendererQuirks(final GLProfile glp) { return glRendererQuirks; } } @Override public final AbstractGraphicsDevice getDefaultDevice() { return defaultDevice; } @Override public final boolean getIsDeviceCompatible(final AbstractGraphicsDevice device) { if(null!=iosEAGLDynamicLookupHelper && device instanceof IOSGraphicsDevice) { return true; } return false; } private final HashSet devicesTried = new HashSet(); private boolean getDeviceTried(final String connection) { synchronized (devicesTried) { return devicesTried.contains(connection); } } private void addDeviceTried(final String connection) { synchronized (devicesTried) { devicesTried.add(connection); } } private void removeDeviceTried(final String connection) { synchronized (devicesTried) { devicesTried.remove(connection); } } @Override protected final SharedResource getOrCreateSharedResourceImpl(final AbstractGraphicsDevice adevice) { final String connection = adevice.getConnection(); SharedResource sr; synchronized(sharedMap) { sr = sharedMap.get(connection); } if(null==sr && !getDeviceTried(connection)) { addDeviceTried(connection); final IOSGraphicsDevice device = new IOSGraphicsDevice(adevice.getUnitID()); GLDrawable drawable = null; GLDrawable zeroDrawable = null; GLContextImpl context = null; boolean contextIsCurrent = false; device.lock(); try { final GLProfile glp = GLProfile.get(device, GLProfile.GL_PROFILE_LIST_MAX_MOBILE, false); if (null == glp) { throw new GLException("Couldn't get default GLProfile for device: "+device); } final GLCapabilitiesImmutable caps = new GLCapabilities(glp); // drawable = createSurfacelessFBODrawable(device, caps, 64, 64); drawable = createSurfacelessDrawable(device, caps, 64, 64); drawable.setRealized(true); context = (IOSEAGLContext) drawable.createContext(null); if (null == context) { throw new GLException("Couldn't create shared context for drawable: "+drawable); } contextIsCurrent = GLContext.CONTEXT_NOT_CURRENT != context.makeCurrent(); final boolean allowsSurfacelessCtx; final boolean hasNPOTTextures; final boolean hasRECTTextures; final boolean hasAppleFloatPixels; final GLRendererQuirks glRendererQuirks; if( contextIsCurrent ) { // We allow probing surfaceless for even the compatible 2.1 context, // which we probably have right here - since OSX may support this. // Otherwise, we cannot map the quirk to the device. if( probeSurfacelessCtx(context, false /* restoreDrawable */) ) { allowsSurfacelessCtx = true; zeroDrawable = context.getGLDrawable(); } else { allowsSurfacelessCtx = false; } final GL gl = context.getGL(); hasNPOTTextures = gl.isNPOTTextureAvailable(); hasRECTTextures = gl.isExtensionAvailable(GLExtensions.EXT_texture_rectangle); hasAppleFloatPixels = gl.isExtensionAvailable(GLExtensions.APPLE_float_pixels); glRendererQuirks = context.getRendererQuirks(); } else { allowsSurfacelessCtx = false; hasNPOTTextures = false; hasRECTTextures = false; hasAppleFloatPixels = false; glRendererQuirks = null; } sr = new SharedResource(device, contextIsCurrent, hasNPOTTextures, hasRECTTextures, hasAppleFloatPixels, glRendererQuirks); if ( DEBUG_SHAREDCTX ) { System.err.println("SharedDevice: " + device); System.err.println("SharedContext: " + context + ", madeCurrent " + contextIsCurrent); System.err.println(" NPOT "+hasNPOTTextures+", RECT "+hasRECTTextures+", FloatPixels "+hasAppleFloatPixels); System.err.println(" allowsSurfacelessCtx "+allowsSurfacelessCtx); System.err.println(" glRendererQuirks "+glRendererQuirks); } synchronized(sharedMap) { sharedMap.put(connection, sr); } } catch (final Throwable t) { throw new GLException("IOSEAGLDrawableFactory - Could not initialize shared resources for "+adevice, t); } finally { if( null != context ) { try { context.destroy(); } catch (final GLException gle) { if ( DEBUG_SHAREDCTX ) { System.err.println("IOSEAGLDrawableFactory.createShared: INFO: destroy caught exception:"); gle.printStackTrace(); } } } if( null != zeroDrawable ) { zeroDrawable.setRealized(false); } if( null != drawable ) { drawable.setRealized(false); } device.unlock(); removeDeviceTried(connection); } } return sr; } @Override protected final Thread getSharedResourceThread() { return null; } /** * {@inheritDoc} *

* This factory never supports native desktop OpenGL profiles. *

*/ @Override public final boolean hasOpenGLDesktopSupport() { return false; } /** * {@inheritDoc} *

* This factory always supports native GLES profiles. *

*/ @Override public final boolean hasOpenGLESSupport() { return true; } /** * {@inheritDoc} *

* Always returns false. *

*/ @Override public final boolean hasMajorMinorCreateContextARB() { return false; } @Override protected List getAvailableCapabilitiesImpl(final AbstractGraphicsDevice device) { return IOSEAGLGraphicsConfiguration.getAvailableCapabilities(this, device); } @Override protected final OnscreenFBOColorbufferStorageDefinition getOnscreenFBOColorbufStorageDef() { return onscreenGLDrawableFBOColorRenderbufferStorageDef; } private final OnscreenFBOColorbufferStorageDefinition onscreenGLDrawableFBOColorRenderbufferStorageDef = new OnscreenFBOColorbufferStorageDefinition() { @Override public final void setStorage(final GL gl, final Attachment a) { final int samples = ((FBObject.RenderAttachment) a).getSamples(); if( samples > 0 ) { gl.glRenderbufferStorageMultisample(GL.GL_RENDERBUFFER, samples, a.format, a.getWidth(), a.getHeight()); } else { // gl.glRenderbufferStorage(GL.GL_RENDERBUFFER, a.format, a.getWidth(), a.getHeight()); final long eaglLayer = IOSUtil.GetCAEAGLLayer(gl.getContext().getGLDrawable().getNativeSurface().getSurfaceHandle()); EAGL.eaglBindDrawableStorageToRenderbuffer(gl.getContext().getHandle(), GL.GL_RENDERBUFFER, eaglLayer); if( DEBUG ) { System.err.println("EAGL.eaglBindDrawableStorageToRenderbuffer: ctx 0x"+Long.toHexString(gl.getContext().getHandle()) + ", eaglLayer 0x"+Long.toHexString(eaglLayer)); } } } @Override public final boolean isDoubleBufferSupported() { return false; } @Override public final int getTextureUnit() { return -1; // force using a color renderbuffer } }; /** * {@inheritDoc} *

* This IOS onscreen drawable implementation actually is only a dummy implementation *

*/ @Override protected GLDrawableImpl createOnscreenDrawableImpl(final NativeSurface target) { if (target == null) { throw new IllegalArgumentException("Null target"); } return new IOSOnscreenEAGLDrawable(this, target); } @Override protected GLDrawableImpl createOffscreenDrawableImpl(final NativeSurface target) { throw new GLException("Only FBO is supported for offscreen"); } @Override public boolean canCreateGLPbuffer(final AbstractGraphicsDevice device, final GLProfile glp) { return false; } @Override protected ProxySurface createMutableSurfaceImpl(final AbstractGraphicsDevice deviceOrig, final AbstractGraphicsDevice deviceReq, final boolean createNewDevice, final GLCapabilitiesImmutable capsChosen, final GLCapabilitiesImmutable capsRequested, final GLCapabilitiesChooser chooser, final UpstreamSurfaceHook upstreamHook) { final IOSGraphicsDevice device; final boolean owning; if( createNewDevice || !(deviceReq instanceof IOSGraphicsDevice) ) { device = new IOSGraphicsDevice(deviceReq.getUnitID()); owning = true; } else { device = (IOSGraphicsDevice)deviceReq; owning = false; } final AbstractGraphicsScreen screen = new DefaultGraphicsScreen(device, 0); final IOSEAGLGraphicsConfiguration config = IOSEAGLGraphicsConfigurationFactory.chooseGraphicsConfigurationStatic(capsChosen, capsRequested, chooser, screen, true); if(null == config) { throw new GLException("Choosing GraphicsConfiguration failed w/ "+capsChosen+" on "+screen); } return new WrappedSurface(config, 0, upstreamHook, owning); } @Override public final ProxySurface createDummySurfaceImpl(final AbstractGraphicsDevice deviceOrig, final AbstractGraphicsDevice device, final boolean createNewDevice, GLCapabilitiesImmutable chosenCaps, final GLCapabilitiesImmutable requestedCaps, final GLCapabilitiesChooser chooser, final int width, final int height) { chosenCaps = GLGraphicsConfigurationUtil.fixOnscreenGLCapabilities(chosenCaps); return createMutableSurfaceImpl(deviceOrig, device, createNewDevice, chosenCaps, requestedCaps, chooser, new IOSDummyUpstreamSurfaceHook(width, height)); } @Override public final ProxySurface createSurfacelessImpl(final AbstractGraphicsDevice deviceOrig, final AbstractGraphicsDevice device, final boolean createNewDevice, GLCapabilitiesImmutable chosenCaps, final GLCapabilitiesImmutable requestedCaps, final GLCapabilitiesChooser chooser, final int width, final int height) { chosenCaps = GLGraphicsConfigurationUtil.fixOnscreenGLCapabilities(chosenCaps); return createMutableSurfaceImpl(deviceOrig, device, createNewDevice, chosenCaps, requestedCaps, chooser, new GenericUpstreamSurfacelessHook(width, height)); } @Override protected ProxySurface createProxySurfaceImpl(final AbstractGraphicsDevice deviceReq, final int screenIdx, final long windowHandle, final GLCapabilitiesImmutable capsRequested, final GLCapabilitiesChooser chooser, final UpstreamSurfaceHook upstream) { final IOSGraphicsDevice device = new IOSGraphicsDevice(deviceReq.getUnitID()); final AbstractGraphicsScreen screen = new DefaultGraphicsScreen(device, screenIdx); final IOSEAGLGraphicsConfiguration config = IOSEAGLGraphicsConfigurationFactory.chooseGraphicsConfigurationStatic(capsRequested, capsRequested, chooser, screen, true); return new WrappedSurface(config, windowHandle, upstream, true); } @Override protected GLContext createExternalGLContextImpl() { throw new GLException("Not implemented"); } @Override public boolean canCreateExternalGLDrawable(final AbstractGraphicsDevice device) { return false; } @Override protected GLDrawable createExternalGLDrawableImpl() { throw new GLException("Not implemented"); } //------------------------------------------------------ // Gamma-related functionality // private static final int GAMMA_RAMP_LENGTH = 256; /** Returns the length of the computed gamma ramp for this OS and hardware. Returns 0 if gamma changes are not supported. */ @Override protected int getGammaRampLength(final NativeSurface surface) { return GAMMA_RAMP_LENGTH; } @Override protected boolean setGammaRamp(final NativeSurface surface, final float[] ramp) { // final FloatBuffer rampNIO = Buffers.newDirectFloatBuffer(ramp); return false; // TODO CGL.setGammaRamp(ramp.length, rampNIO, rampNIO, rampNIO); } @Override protected Buffer getGammaRamp(final NativeSurface surface) { return ShortBuffer.allocate(0); // return a dummy gamma ramp default for reset } @Override protected void resetGammaRamp(final NativeSurface surface, final Buffer originalGammaRamp) { // TODO CGL.resetGammaRamp(); } @Override protected final void resetGammaRamp(final DeviceScreenID deviceScreenID, final Buffer originalGammaRamp) { // TODO CGL.resetGammaRamp(); } }