/* ============================================================================== This file is part of the JUCE 6 technical preview. Copyright (c) 2020 - Raw Material Software Limited You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For this technical preview, this file is not subject to commercial licensing. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { //============================================================================== // This byte-code is generated from native/java/com/rmsl/juce/JuceOpenGLView.java with min sdk version 16 // See juce_core/native/java/README.txt on how to generate this byte-code. static const uint8 javaJuceOpenGLView[] = {31,139,8,8,95,114,161,94,0,3,74,117,99,101,79,112,101,110,71,76,86,105,101,119,83,117,114,102,97,99,101,46,100,101,120,0,109, 84,75,107,19,81,20,62,119,230,38,169,105,76,99,218,138,86,144,32,5,55,173,83,53,98,33,173,86,90,170,196,193,34,173,81,106,93, 12,147,169,153,210,204,196,100,154,22,65,40,110,234,170,43,23,34,46,93,22,87,62,138,27,65,168,235,174,213,133,11,23,254,129, 162,136,130,223,125,196,164,208,129,239,126,231,125,206,188,78,217,91,75,142,156,191,64,151,223,190,187,182,183,251,251,212,240, 247,216,223,197,205,174,170,255,233,209,230,135,123,183,222,252,225,68,53,34,90,43,229,179,164,175,5,216,142,145,178,199,129, 93,205,63,0,6,140,224,72,129,71,153,210,159,225,152,50,137,182,193,239,13,162,143,192,14,240,25,216,3,50,240,13,1,54,48,3,204, 2,119,128,5,192,1,124,224,1,240,16,120,2,108,2,207,129,87,192,14,240,5,248,9,196,184,234,7,145,32,82,76,207,149,208,136,233, 249,187,180,252,20,189,15,105,249,5,228,164,150,95,66,238,214,242,86,135,253,181,161,234,102,100,15,83,214,50,225,233,209,61,179, 154,251,100,127,46,253,226,76,73,86,113,92,199,113,76,218,171,245,62,173,247,75,54,232,168,182,183,238,69,92,134,230,188,42,139, 251,226,210,214,205,213,124,181,28,209,57,153,57,15,105,126,144,40,141,92,38,99,251,185,154,191,229,77,203,124,67,246,56,129, 227,8,56,204,49,154,163,217,9,68,161,236,89,52,28,197,51,19,122,109,162,155,248,205,180,124,6,76,206,51,216,202,201,136,26,7, 230,140,116,17,103,157,57,67,58,231,224,232,36,162,67,124,6,92,206,198,246,221,179,33,117,166,245,182,28,31,243,3,63,186,68,172, 72,189,197,21,215,155,169,121,193,85,187,228,123,171,103,150,156,166,67,199,109,39,40,215,67,191,108,185,97,16,121,65,100,77, 10,94,139,10,29,174,251,117,167,86,241,221,134,53,233,4,77,167,81,160,129,255,174,38,42,89,179,43,245,69,199,245,68,213,2,157, 180,221,176,106,213,171,141,101,107,9,13,173,253,93,11,196,74,100,148,138,100,150,138,54,4,27,130,93,164,184,235,4,174,183,44, 25,29,40,225,170,41,40,85,246,27,53,39,114,43,83,117,103,149,120,37,108,68,148,12,156,200,111,122,115,21,191,65,217,48,184,18, 69,142,91,241,202,115,225,109,63,40,135,171,212,47,109,194,164,12,55,100,56,245,133,193,148,167,66,167,235,97,85,7,15,28,100, 213,25,41,248,208,86,107,60,18,13,79,27,61,217,68,250,226,204,48,13,83,34,125,157,166,89,58,145,30,223,152,167,60,41,30,7,111, 220,29,195,11,224,248,248,248,250,58,223,54,249,99,131,12,128,1,49,246,213,100,252,23,176,197,13,254,141,31,214,239,145,117, 112,107,111,24,29,187,195,236,216,31,173,239,94,236,144,24,181,247,72,156,218,187,132,229,148,79,236,19,150,105,255,203,70,78, 213,23,59,198,212,49,226,255,160,156,202,205,235,159,87,200,98,135,253,3,40,26,5,36,252,4,0,0,0,0}; //============================================================================== struct AndroidGLCallbacks { static void attachedToWindow (JNIEnv*, jobject, jlong); static void detachedFromWindow (JNIEnv*, jobject, jlong); static void dispatchDraw (JNIEnv*, jobject, jlong, jobject); }; //============================================================================== #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(Landroid/content/Context;J)V") \ METHOD (getParent, "getParent", "()Landroid/view/ViewParent;") \ METHOD (getHolder, "getHolder", "()Landroid/view/SurfaceHolder;") \ METHOD (layout, "layout", "(IIII)V" ) \ CALLBACK (AndroidGLCallbacks::attachedToWindow, "onAttchedWindowNative", "(J)V") \ CALLBACK (AndroidGLCallbacks::detachedFromWindow, "onDetachedFromWindowNative", "(J)V") \ CALLBACK (AndroidGLCallbacks::dispatchDraw, "onDrawNative", "(JLandroid/graphics/Canvas;)V") DECLARE_JNI_CLASS_WITH_BYTECODE (JuceOpenGLViewSurface, "com/rmsl/juce/JuceOpenGLView", 16, javaJuceOpenGLView, sizeof(javaJuceOpenGLView)) #undef JNI_CLASS_MEMBERS //============================================================================== class OpenGLContext::NativeContext : private SurfaceHolderCallback { public: NativeContext (Component& comp, const OpenGLPixelFormat& /*pixelFormat*/, void* /*contextToShareWith*/, bool /*useMultisampling*/, OpenGLVersion) : component (comp), surface (EGL_NO_SURFACE), context (EGL_NO_CONTEXT) { auto env = getEnv(); // Do we have a native peer that we can attach to? if (component.getPeer()->getNativeHandle() == nullptr) return; // Initialise the EGL display if (! initEGLDisplay()) return; // create a native surface view surfaceView = GlobalRef (LocalRef(env->NewObject (JuceOpenGLViewSurface, JuceOpenGLViewSurface.constructor, getAppContext().get(), reinterpret_cast (this)))); if (surfaceView.get() == nullptr) return; // add the view to the view hierarchy // after this the nativecontext can receive callbacks env->CallVoidMethod ((jobject) component.getPeer()->getNativeHandle(), AndroidViewGroup.addView, surfaceView.get()); // initialise the geometry of the view auto bounds = component.getTopLevelComponent()->getLocalArea (&component, component.getLocalBounds()); bounds *= component.getDesktopScaleFactor(); updateWindowPosition (bounds); hasInitialised = true; } ~NativeContext() override { auto env = getEnv(); if (jobject viewParent = env->CallObjectMethod (surfaceView.get(), JuceOpenGLViewSurface.getParent)) env->CallVoidMethod (viewParent, AndroidViewGroup.removeView, surfaceView.get()); } //============================================================================== bool initialiseOnRenderThread (OpenGLContext& aContext) { jassert (hasInitialised); // has the context already attached? jassert (surface == EGL_NO_SURFACE && context == EGL_NO_CONTEXT); auto env = getEnv(); ANativeWindow* window = nullptr; LocalRef holder (env->CallObjectMethod (surfaceView.get(), JuceOpenGLViewSurface.getHolder)); if (holder != nullptr) { LocalRef jSurface (env->CallObjectMethod (holder.get(), AndroidSurfaceHolder.getSurface)); if (jSurface != nullptr) { window = ANativeWindow_fromSurface(env, jSurface.get()); // if we didn't succeed the first time, wait 25ms and try again if (window == nullptr) { Thread::sleep (200); window = ANativeWindow_fromSurface (env, jSurface.get()); } } } if (window == nullptr) { // failed to get a pointer to the native window after second try so bail out jassertfalse; return false; } // create the surface surface = eglCreateWindowSurface (display, config, window, nullptr); jassert (surface != EGL_NO_SURFACE); ANativeWindow_release (window); // create the OpenGL context EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; context = eglCreateContext (display, config, EGL_NO_CONTEXT, contextAttribs); jassert (context != EGL_NO_CONTEXT); juceContext = &aContext; return true; } void shutdownOnRenderThread() { jassert (hasInitialised); // is there a context available to detach? jassert (surface != EGL_NO_SURFACE && context != EGL_NO_CONTEXT); eglDestroyContext (display, context); context = EGL_NO_CONTEXT; eglDestroySurface (display, surface); surface = EGL_NO_SURFACE; } //============================================================================== bool makeActive() const noexcept { if (! hasInitialised) return false; if (surface == EGL_NO_SURFACE || context == EGL_NO_CONTEXT) return false; if (! eglMakeCurrent (display, surface, surface, context)) return false; return true; } bool isActive() const noexcept { return eglGetCurrentContext() == context; } static void deactivateCurrentContext() { eglMakeCurrent (display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } //============================================================================== void swapBuffers() const noexcept { eglSwapBuffers (display, surface); } bool setSwapInterval (const int) { return false; } int getSwapInterval() const { return 0; } //============================================================================== bool createdOk() const noexcept { return hasInitialised; } void* getRawContext() const noexcept { return surfaceView.get(); } GLuint getFrameBufferID() const noexcept { return 0; } //============================================================================== void updateWindowPosition (Rectangle bounds) { if (lastBounds != bounds) { auto env = getEnv(); lastBounds = bounds; auto r = bounds * Desktop::getInstance().getDisplays().getMainDisplay().scale; env->CallVoidMethod (surfaceView.get(), JuceOpenGLViewSurface.layout, (jint) r.getX(), (jint) r.getY(), (jint) r.getRight(), (jint) r.getBottom()); } } //============================================================================== // Android Surface Callbacks: void surfaceChanged (LocalRef holder, int format, int width, int height) override { ignoreUnused (holder, format, width, height); } void surfaceCreated (LocalRef holder) override; void surfaceDestroyed (LocalRef holder) override; //============================================================================== struct Locker { Locker (NativeContext&) {} }; Component& component; private: //============================================================================== friend struct AndroidGLCallbacks; void attachedToWindow() { auto* env = getEnv(); LocalRef holder (env->CallObjectMethod (surfaceView.get(), JuceOpenGLViewSurface.getHolder)); if (surfaceHolderCallback == nullptr) surfaceHolderCallback = GlobalRef (CreateJavaInterface (this, "android/view/SurfaceHolder$Callback")); env->CallVoidMethod (holder, AndroidSurfaceHolder.addCallback, surfaceHolderCallback.get()); } void detachedFromWindow() { if (surfaceHolderCallback != nullptr) { auto* env = getEnv(); LocalRef holder (env->CallObjectMethod (surfaceView.get(), JuceOpenGLViewSurface.getHolder)); env->CallVoidMethod (holder.get(), AndroidSurfaceHolder.removeCallback, surfaceHolderCallback.get()); surfaceHolderCallback.clear(); } } void dispatchDraw (jobject /*canvas*/) { if (juceContext != nullptr) juceContext->triggerRepaint(); } //============================================================================== bool initEGLDisplay() { // already initialised? if (display != EGL_NO_DISPLAY) return true; const EGLint attribs[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_ALPHA_SIZE, 0, EGL_DEPTH_SIZE, 16, EGL_NONE }; EGLint numConfigs; if ((display = eglGetDisplay (EGL_DEFAULT_DISPLAY)) == EGL_NO_DISPLAY) { jassertfalse; return false; } if (! eglInitialize (display, nullptr, nullptr)) { jassertfalse; return false; } if (! eglChooseConfig (display, attribs, &config, 1, &numConfigs)) { eglTerminate (display); jassertfalse; return false; } return true; } //============================================================================== bool hasInitialised = false; GlobalRef surfaceView; Rectangle lastBounds; OpenGLContext* juceContext = nullptr; EGLSurface surface; EGLContext context; GlobalRef surfaceHolderCallback; static EGLDisplay display; static EGLConfig config; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeContext) }; //============================================================================== void AndroidGLCallbacks::attachedToWindow (JNIEnv*, jobject /*this*/, jlong host) { if (auto* nativeContext = reinterpret_cast (host)) nativeContext->attachedToWindow(); } void AndroidGLCallbacks::detachedFromWindow (JNIEnv*, jobject /*this*/, jlong host) { if (auto* nativeContext = reinterpret_cast (host)) nativeContext->detachedFromWindow(); } void AndroidGLCallbacks::dispatchDraw (JNIEnv*, jobject /*this*/, jlong host, jobject canvas) { if (auto* nativeContext = reinterpret_cast (host)) nativeContext->dispatchDraw (canvas); } //============================================================================== bool OpenGLHelpers::isContextActive() { return eglGetCurrentContext() != EGL_NO_CONTEXT; } } // namespace juce