diff --git a/LNCONFIG.h.in b/LNCONFIG.h.in index 86854b82..50ddef8d 100644 --- a/LNCONFIG.h.in +++ b/LNCONFIG.h.in @@ -117,10 +117,19 @@ int scm_screenheight(); // hook prototype (performs scheme init) void ffi_event(int,int,int); +void ln_gambit_unlock(); +typedef struct { + int t; + int x; + int y; + int fd[2]; +} ffi_event_params_t; +extern ffi_event_params_t ffi_event_params; // C prototypes // these are used by the desktop platforms void microgl_hook(int,int,int); +void microgl_draw_before(); void microgl_swapbuffers(); void microgl_close(); void microgl_init(); @@ -130,6 +139,7 @@ void microgl_pollevents(); void microgl_refresh(); int microgl_screenwidth(); int microgl_screenheight(); +unsigned int microgl_redraw_period(unsigned int); #endif // STANDALONE diff --git a/languages/scm.c b/languages/scm.c index 3bf39d6b..347005bf 100644 --- a/languages/scm.c +++ b/languages/scm.c @@ -42,6 +42,9 @@ void lambdanative_payload_setup() setup_params.debug_settings = debug_settings; lambdanative_exit_call_count = 0; ___setup(&setup_params); + /* Runing from a single thread should fix the race conditions, which + lead to the following mitigation. Should be ready to be removed. + #if defined(ANDROID) #if (___VERSION < 409002) ___disable_heartbeat_interrupts(); @@ -49,6 +52,7 @@ void lambdanative_payload_setup() ___cleanup_heartbeat_interrupt_handling(); #endif #endif + */ } void lambdanative_payload_cleanup() diff --git a/languages/scm.sh b/languages/scm.sh index d991ddbb..d084d88d 100644 --- a/languages/scm.sh +++ b/languages/scm.sh @@ -133,7 +133,7 @@ compile_payload_scm() ( # exported list of "well known" build parameters export SYS_PREFIX SYS_ROOT SYS_PATH SYS_ANDROIDAPI SYS_ANDROID_ABI - veval "$SYS_GSC -:~~tgt=${SYS_PREFIX} -prelude \"$scm_opts\" -c -o $scm_ctgt $gsc_processing $scm_hdr $scm_src" + veval "$SYS_GSC -:~~tgt=${SYS_PREFIX},~~bld=${SYS_HOSTPREFIX} -prelude \"$scm_opts\" -c -o $scm_ctgt $gsc_processing $scm_hdr $scm_src" ) if [ $veval_result != "0" ]; then rmifexists "$scm_ctgt"; fi assertfile "$scm_ctgt" diff --git a/libraries/libgambit/0001-Fix-incorrect-space-allocation-report-by-time-specia.patch b/libraries/libgambit/0001-Fix-incorrect-space-allocation-report-by-time-specia.patch new file mode 100644 index 00000000..60207b0c --- /dev/null +++ b/libraries/libgambit/0001-Fix-incorrect-space-allocation-report-by-time-specia.patch @@ -0,0 +1,50 @@ +From e529b1d6d5b7bae2fcc3e9cd8e7f3e11a1318193 Mon Sep 17 00:00:00 2001 +From: Marc Feeley +Date: Sun, 28 Mar 2021 19:29:54 -0400 +Subject: [PATCH] Fix incorrect space allocation report by time special form + +--- + lib/_kernel.scm | 6 ++++-- + lib/mem.c | 3 ++- + 2 files changed, 6 insertions(+), 3 deletions(-) + +diff --git a/lib/_kernel.scm b/lib/_kernel.scm +index 59b9e41..2d827dd 100644 +--- lib/_kernel.scm ++++ lib/_kernel.scm +@@ -4346,7 +4346,9 @@ end-of-code + + if (!___FIXNUMP(result)) + { +- n = ___bytes_allocated (___PSPNC) - n; ++ ___F64 ba = ___bytes_allocated (___PSPNC); ++ ++ n = ba - n; + + ___process_times (&user, &sys, &real); + ___vm_stats (&minflt, &majflt); +@@ -4358,7 +4360,7 @@ end-of-code + ___F64VECTORSET(result,___FIX(4),___vms->mem.gc_sys_time_) + ___F64VECTORSET(result,___FIX(5),___vms->mem.gc_real_time_) + ___F64VECTORSET(result,___FIX(6),___vms->mem.nb_gcs_) +- ___F64VECTORSET(result,___FIX(7),___bytes_allocated (___PSPNC)) ++ ___F64VECTORSET(result,___FIX(7),ba) + ___F64VECTORSET(result,___FIX(8),(2*(1+2)<<___LWS)) + ___F64VECTORSET(result,___FIX(9),n) + ___F64VECTORSET(result,___FIX(10),minflt) +diff --git a/lib/mem.c b/lib/mem.c +index 2c6cafd..9223da1 100755 +--- lib/mem.c ++++ lib/mem.c +@@ -7080,7 +7080,8 @@ ___PSDKR) + alloc_stack_ptr = ___ps->fp; + alloc_heap_ptr = ___ps->hp; + +- return bytes_allocated_minus_occupied + bytes_occupied(___ps); ++ return bytes_allocated_minus_occupied + bytes_occupied(___ps) + ++ ___CAST(___F64,occupied_words_still) * ___WS; + } + + +-- +2.20.1 diff --git a/loaders/android/GLState.java.in b/loaders/android/GLState.java.in new file mode 100644 index 00000000..313882cf --- /dev/null +++ b/loaders/android/GLState.java.in @@ -0,0 +1,985 @@ +/* NativeStaticGLState -*- mode: java; c-basic-offset: 2; -*- */ +/* + * Copyright (C) 2021 JFW + * + * # History + * + * - API derived from from GLSurfaceView. Must not create its own + * rendering thread; it is not safe to call into Gambit from multiple + * threads unless no threads or asynchronous i/o is done in Gambit. + * + * - Stripped down to API, keeping comments mostly for documentation. + * + * # License + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 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. + * + * Neither the name of the University of British Columbia nor + * the names of its contributors may be used to endorse or + * promote products derived from this software without specific + * prior written permission. + + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "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 THE COPYRIGHT HOLDER 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. + * + * In case of conflict the follwoing licens shall apply: + * + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package @SYS_PACKAGE_DOT@; + +// import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; +import android.os.Trace; +import android.util.AttributeSet; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import java.io.Writer; +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGL11; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; +import javax.microedition.khronos.opengles.GL; +import javax.microedition.khronos.opengles.GL10; + +import android.opengl.GLDebugHelper; +import android.opengl.EGLExt; +import android.opengl.EGL14; + +/** + *

Using NativeGLSurfaceView

+ *

+ * Typically you use NativeGLSurfaceView by subclassing it and overriding one or more of the + * View system input event methods. If your application does not need to override event + * methods then NativeGLSurfaceView can be used as-is. For the most part + * NativeGLSurfaceView behavior is customized by calling "set" methods rather than by subclassing. + * For example, unlike a regular View, drawing is delegated to a separate Renderer object which + * is registered with the NativeGLSurfaceView + * using the {@link #setRenderer(Renderer)} call. + * + * Note: Those set methods are likely to be removed. + * + *

+ *

Initializing NativeGLSurfaceView

+ * All you have to do to initialize a NativeGLSurfaceView is call {@link #setRenderer(Renderer)}. + * However, if desired, you can modify the default behavior of NativeGLSurfaceView by calling one or + * more of these methods before calling setRenderer: + * + *

+ *

Specifying the android.view.Surface

+ * By default NativeGLSurfaceView will create a PixelFormat.RGB_888 format surface. If a translucent + * surface is required, call getHolder().setFormat(PixelFormat.TRANSLUCENT). + * The exact format of a TRANSLUCENT surface is device dependent, but it will be + * a 32-bit-per-pixel surface with 8 bits per component. + *

+ *

Choosing an EGL Configuration

+ * A given Android device may support multiple EGLConfig rendering configurations. + * The available configurations may differ in how many channels of data are present, as + * well as how many bits are allocated to each channel. Therefore, the first thing + * NativeGLSurfaceView has to do when starting to render is choose what EGLConfig to use. + *

+ * By default NativeGLSurfaceView chooses a EGLConfig that has an RGB_888 pixel format, + * with at least a 16-bit depth buffer and no stencil. + *

+ * If you would prefer a different EGLConfig + * you can override the default behavior by calling one of the + * setEGLConfigChooser methods. + *

+ *

Debug Behavior

+ * You can optionally modify the behavior of NativeGLSurfaceView by calling + * one or more of the debugging methods {@link #setDebugFlags(int)}, + * and {@link #setGLWrapper}. These methods may be called before and/or after setRenderer, but + * typically they are called before setRenderer so that they take effect immediately. + *

+ *

Setting a Renderer

+ * Finally, you must call {@link #setRenderer} to register a {@link Renderer}. + * The renderer is + * responsible for doing the actual OpenGL rendering. + */ + +/* ****************************************** */ + +/** + * A generic GL State. + * + * - Bookkeeping of initializing EGL and GL. + * + * - IMPORTANT: MUST ONLY be called from (native) thread doing the + * OpenGL drawing! + * + * - FIXME (do we need this?): Delegates to a Renderer instance to + * do the actual drawing. Can be configured to render continuously + * or on request. + * + * All potentially blocking synchronization is done through the + * sGLStateManager object. + */ +/* + * FIXME TBD: replace GLStateManager with synchronisation + * primitive guarding native thread state. + */ +class GLState { + private static final GLStateManager sGLStateManager = new GLStateManager(); + private final static String TAG = "NativeGLSurfaceView->GLState"; + private final static boolean LOG_THREADS = false; + private final static boolean LOG_PAUSE_RESUME = false; + private final static boolean LOG_SURFACE = false; + /* + private final static boolean LOG_ATTACH_DETACH = false; + */ + private final static boolean LOG_RENDERER = false; + private final static boolean LOG_RENDERER_DRAW_FRAME = false; + private final static boolean LOG_EGL = false; + // mOwnerThread thread allowed to mutate this state. When null, + // any thread may assume ownership. + Thread mOwnerThread = null; + + /* + * cache IDs when a class is loaded, and automatically re-cache them + * if the class is ever unloaded and reloaded: use a class + * initializer to allow the native code to cache some field + * offsets. This native function looks up and caches interesting + * class/field/method IDs. Throws on failure. + */ + private static native void nativeInit(); + static { + nativeInit(); + } + + private native void nativeOnNewObject(); + GLState(WeakReference glSurfaceViewWeakRef) { + super(); + nativeOnNewObject(); + mWidth = 0; + mHeight = 0; + mRequestRender = true; + mRenderMode = NativeGLSurfaceView.RENDERMODE_CONTINUOUSLY; + mWantRenderNotification = false; + mNativeGLSurfaceViewWeakRef = glSurfaceViewWeakRef; + } + private GL10 gl = null; + private boolean createEglContext = false; + private boolean createEglSurface = false; + private boolean createGlInterface = false; + private boolean lostEglContext = false; + private boolean sizeChanged = false; + private boolean wantRenderNotification = false; + private boolean doRenderNotification = false; + private boolean askedToReleaseEglContext = false; + private int w = 0; + private int h = 0; + private Runnable event = null; + private Runnable finishDrawingRunnable = null; + public void fromNativeStart() { + if(LOG_PAUSE_RESUME) { + Log.i("GLState", "fromNativeStart()"); + } + // Must be called from OwnerThread once. + if(mOwnerThread!=null) return; + Thread cur = Thread.currentThread(); + cur.setName("GLState Owner " + cur.getId()); + if (LOG_EGL) { + Log.i("GLState", "fromNativeStart() tid=" + cur.getName()); + } + mOwnerThread = cur; + mEglHelper = new EglHelper(mNativeGLSurfaceViewWeakRef); + mHaveEglContext = false; + mHaveEglSurface = false; + mWantRenderNotification = false; + + // TBD: what are these good for? + gl = null; + createEglContext = false; + createEglSurface = false; + createGlInterface = false; + lostEglContext = false; + sizeChanged = false; + wantRenderNotification = false; + doRenderNotification = false; + askedToReleaseEglContext = false; + w = 0; + h = 0; + event = null; + finishDrawingRunnable = null; + } + public void finalize() { + /* + * clean-up everything... + */ + synchronized (sGLStateManager) { + stopEglSurfaceLocked(); + stopEglContextLocked(); + } + } + /* + * This private method should only be called inside a + * synchronized(sGLStateManager) block. + */ + private void stopEglSurfaceLocked() { + if (mHaveEglSurface) { + mHaveEglSurface = false; + mEglHelper.destroySurface(); + } + } + /* + * This private method should only be called inside a + * synchronized(sGLStateManager) block. + */ + private void stopEglContextLocked() { + if (mHaveEglContext) { + mEglHelper.finish(); + mHaveEglContext = false; + sGLStateManager.releaseEglContextLocked(this); + } + } + /* + * Usage from native thread via JNI: + * - fromNativeInitDraw(); + * - draw native + * - fromNativeOnDrawFrame(); + * - fromNativeSwapBuffers(); + */ + private void fromNativeInitDraw() { + while (true) { + if(LOG_RENDERER) { + Log.i("GLState", "fromNativeInitDraw tid=" + Thread.currentThread().getName()); + } + synchronized (sGLStateManager) { + while (true) { + if(LOG_RENDERER) { + Log.i("GLState", "fromNativeInitDraw in monitor tid=" + Thread.currentThread().getName() + " then " + mFinishDrawingRunnable + " and " + finishDrawingRunnable); + } + if (mShouldExit) { + return; + } + if (! mEventQueue.isEmpty()) { + event = mEventQueue.remove(0); + break; + } + // Update the pause state. + boolean pausing = false; + if (mPaused != mRequestPaused) { + pausing = mRequestPaused; + mPaused = mRequestPaused; + sGLStateManager.notifyAll(); + if (LOG_PAUSE_RESUME) { + Log.i("GLState", "mPaused is now " + mPaused + " tid=" + mOwnerThread.getId()); + } + } + // Do we need to give up the EGL context? + if (mShouldReleaseEglContext) { + if (LOG_SURFACE) { + Log.i("GLState", "releasing EGL context because asked to tid=" + mOwnerThread.getId()); + } + stopEglSurfaceLocked(); + stopEglContextLocked(); + mShouldReleaseEglContext = false; + askedToReleaseEglContext = true; + } + // Have we lost the EGL context? + if (lostEglContext) { + stopEglSurfaceLocked(); + stopEglContextLocked(); + lostEglContext = false; + } + // When pausing, release the EGL surface: + if (pausing && mHaveEglSurface) { + if (LOG_SURFACE) { + Log.i("GLState", "releasing EGL surface because paused tid=" + mOwnerThread.getId()); + } + stopEglSurfaceLocked(); + } + // When pausing, optionally release the EGL Context: + if (pausing && mHaveEglContext) { + NativeGLSurfaceView view = mNativeGLSurfaceViewWeakRef.get(); + boolean preserveEglContextOnPause = view == null ? + false : view.getPreserveEGLContextOnPause(); + if (!preserveEglContextOnPause) { + stopEglContextLocked(); + if (LOG_SURFACE) { + Log.i("GLState", "releasing EGL context because paused tid=" + mOwnerThread.getId()); + } + } + } + // Have we lost the SurfaceView surface? + if ((! mHasSurface) && (! mWaitingForSurface)) { + if (LOG_SURFACE) { + Log.i("GLState", "noticed surfaceView surface lost tid=" + mOwnerThread.getId()); + } + if (mHaveEglSurface) { + stopEglSurfaceLocked(); + } + mWaitingForSurface = true; + mSurfaceIsBad = false; + sGLStateManager.notifyAll(); + } + // Have we acquired the surface view surface? + if (mHasSurface && mWaitingForSurface) { + if (LOG_SURFACE) { + Log.i("GLState", "noticed surfaceView surface acquired tid=" + mOwnerThread.getId()); + } + mWaitingForSurface = false; + sGLStateManager.notifyAll(); + } + if (mFinishDrawingRunnable != null) { + if(LOG_THREADS) { + Log.i("GLState", "finishDrawingRunnable = mFinishDrawingRunnable"); + } + finishDrawingRunnable = mFinishDrawingRunnable; + mFinishDrawingRunnable = null; + } + // Ready to draw? + if (readyToDraw()) { + // If we don't have an EGL context, try to acquire one. + if (! mHaveEglContext) { + if (askedToReleaseEglContext) { + askedToReleaseEglContext = false; + } else { + try { + mEglHelper.start(); + } catch (RuntimeException t) { + sGLStateManager.releaseEglContextLocked(this); + throw t; + } + mHaveEglContext = true; + createEglContext = true; + sGLStateManager.notifyAll(); + } + } + if (mHaveEglContext && !mHaveEglSurface) { + mHaveEglSurface = true; + createEglSurface = true; + createGlInterface = true; + sizeChanged = true; + } + if (mHaveEglSurface) { + if (mSizeChanged) { + sizeChanged = true; + w = mWidth; + h = mHeight; + mWantRenderNotification = true; + if (LOG_SURFACE) { + Log.i("GLState", + "noticing that we want render notification tid=" + + mOwnerThread.getId()); + } + // Destroy and recreate the EGL surface. + createEglSurface = true; + mSizeChanged = false; + } + mRequestRender = false; + sGLStateManager.notifyAll(); + if (mWantRenderNotification) { + wantRenderNotification = true; + } + break; + } + } else { + if (finishDrawingRunnable != null) { + Log.w(TAG, "Warning, !readyToDraw() but waiting for " + + "draw finished! Early reporting draw finished."); + finishDrawingRunnable.run(); + finishDrawingRunnable = null; + } + } + // By design, this is the only place in a GLState thread where we wait(). + if (LOG_THREADS) { + Log.i("GLState", "waiting tid=" + mOwnerThread.getId() + + " mHaveEglContext: " + mHaveEglContext + + " mHaveEglSurface: " + mHaveEglSurface + + " mFinishedCreatingEglSurface: " + mFinishedCreatingEglSurface + + " mPaused: " + mPaused + + " mHasSurface: " + mHasSurface + + " mSurfaceIsBad: " + mSurfaceIsBad + + " mWaitingForSurface: " + mWaitingForSurface + + " mWidth: " + mWidth + + " mHeight: " + mHeight + + " mRequestRender: " + mRequestRender + + " mRenderMode: " + mRenderMode); + } + return; // sGLStateManager.wait(); + } + } + // end of synchronized(sGLStateManager) + if (event != null) { + // early exited monitor; why? Run event and retry step + event.run(); // BEWARE: MUST NOT block on native code. + event = null; + continue; + } + if (createEglSurface) { + if (LOG_SURFACE) { + Log.w("GLState", "egl createSurface"); + } + if (mEglHelper.createSurface()) { + synchronized(sGLStateManager) { + mFinishedCreatingEglSurface = true; + sGLStateManager.notifyAll(); + } + } else { + synchronized(sGLStateManager) { + mFinishedCreatingEglSurface = true; + mSurfaceIsBad = true; + sGLStateManager.notifyAll(); + } + continue; + } + createEglSurface = false; + } + if (createGlInterface) { + gl = (GL10) mEglHelper.createGL(); + createGlInterface = false; + } + if (createEglContext) { + if (LOG_RENDERER) { + Log.w("GLState", "onSurfaceCreated"); + } + NativeGLSurfaceView view = mNativeGLSurfaceViewWeakRef.get(); + if (view != null) { + // try { + // Trace.traceBegin(Trace.TRACE_TAG_VIEW, "onSurfaceCreated"); + view.getRenderer().onSurfaceCreated(gl, mEglHelper.mEglConfig); + // } finally { + // Trace.traceEnd(Trace.TRACE_TAG_VIEW); + // } + } + createEglContext = false; + } + if (sizeChanged) { + if (LOG_RENDERER) { + Log.w("GLState", "onSurfaceChanged(" + w + ", " + h + ")"); + } + NativeGLSurfaceView view = mNativeGLSurfaceViewWeakRef.get(); + if (view != null) { + // try { + // Trace.traceBegin(Trace.TRACE_TAG_VIEW, "onSurfaceChanged"); + view.getRenderer().onSurfaceChanged(gl, w, h); + // } finally { + // Trace.traceEnd(Trace.TRACE_TAG_VIEW); + // } + } + sizeChanged = false; + } + break; + } + } + private void fromNativeOnDrawFrame() throws InterruptedException { + if (LOG_RENDERER_DRAW_FRAME) { + Log.w("GLState", "onDrawFrame tid=" + mOwnerThread.getId()); + } + { + NativeGLSurfaceView view = mNativeGLSurfaceViewWeakRef.get(); + if (view != null) { + // try { + // Trace.traceBegin(Trace.TRACE_TAG_VIEW, "onDrawFrame"); + view.getRenderer().onDrawFrame(gl); + if (finishDrawingRunnable != null) { + finishDrawingRunnable.run(); + finishDrawingRunnable = null; + } + // } finally { + // Trace.traceEnd(Trace.TRACE_TAG_VIEW); + // } + } + } + } + private void fromNativeSwapBuffers() throws InterruptedException { + if(mEglHelper.mEglDisplay==null) { + Log.e("GLState", "fromNativeSwapBuffers no display tid=" + mOwnerThread.getId()); + return; + } + if(mEglHelper.mEglSurface==null) { + Log.e("GLState", "fromNativeSwapBuffers no surface tid=" + mOwnerThread.getId()); + return; + } + if (finishDrawingRunnable != null) { // fromNativeOnDrawFrame shall be optional + if (LOG_RENDERER) { + Log.i("GLState", "fromNativeSwapBuffers running finishDrawingRunnable " + finishDrawingRunnable); + } + finishDrawingRunnable.run(); + // Log.i("GLState", "fromNativeSwapBuffers done with finishDrawingRunnable"); + finishDrawingRunnable = null; + } else { + if (LOG_RENDERER) { + Log.i("GLState", "fromNativeSwapBuffers no finishDrawingRunnable given"); + } + } + int swapError = mEglHelper.swap(); + switch (swapError) { + case EGL10.EGL_SUCCESS: + break; + case EGL11.EGL_CONTEXT_LOST: + if (LOG_SURFACE) { + Log.i("GLState", "egl context lost tid=" + mOwnerThread.getId()); + } + lostEglContext = true; + break; + default: + // Other errors typically mean that the current surface is bad, + // probably because the SurfaceView surface has been destroyed, + // but we haven't been notified yet. + // Log the error to help developers understand why rendering stopped. + EglHelper.logEglErrorAsWarning("GLState", "eglSwapBuffers", swapError); + synchronized(sGLStateManager) { + mSurfaceIsBad = true; + sGLStateManager.notifyAll(); + } + break; + } + if (wantRenderNotification) { + doRenderNotification = true; + wantRenderNotification = false; + } + } + public boolean ableToDraw() { + return mHaveEglContext && mHaveEglSurface && readyToDraw(); + } + private boolean readyToDraw() { + return (!mPaused) && mHasSurface && (!mSurfaceIsBad) + && (mWidth > 0) && (mHeight > 0) + && (mRequestRender || (mRenderMode == NativeGLSurfaceView.RENDERMODE_CONTINUOUSLY)); + } + public void setRenderMode(int renderMode) { + if ( !((NativeGLSurfaceView.RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= NativeGLSurfaceView.RENDERMODE_CONTINUOUSLY)) ) { + throw new IllegalArgumentException("renderMode"); + } + synchronized(sGLStateManager) { + mRenderMode = renderMode; + sGLStateManager.notifyAll(); + } + } + public int getRenderMode() { + synchronized(sGLStateManager) { + return mRenderMode; + } + } + public void requestRender() { + // TBD: does not make much sense at all. + synchronized(sGLStateManager) { + mRequestRender = true; + sGLStateManager.notifyAll(); + } + } + public void requestRenderAndNotify(Runnable finishDrawing) { + if(LOG_THREADS) { + Log.i("GLState", "requestRenderAndNotify tid=" + Thread.currentThread().getName() + " then " + finishDrawing); + } + synchronized(sGLStateManager) { + // If we are already on the GL thread, this means a client callback + // has caused reentrancy, for example via updating the SurfaceView parameters. + // We will return to the client rendering code, so here we don't need to + // do anything. + if (Thread.currentThread() == mOwnerThread) { + return; + } + mWantRenderNotification = true; + mRequestRender = true; + mRenderComplete = false; + mFinishDrawingRunnable = finishDrawing; + sGLStateManager.notifyAll(); + } + } + public void surfaceCreated() { + synchronized(sGLStateManager) { + if (LOG_THREADS) { + Log.i("GLState", "surfaceCreated tid=" + Thread.currentThread().getName()); + } + mHasSurface = true; + mFinishedCreatingEglSurface = false; + sGLStateManager.notifyAll(); + // while (mWaitingForSurface + // && !mFinishedCreatingEglSurface + // && !mExited) { + // try { + // sGLStateManager.wait(); + // } catch (InterruptedException e) { + // Thread.currentThread().interrupt(); + // } + // } + } + NativeGLSurfaceView view = mNativeGLSurfaceViewWeakRef.get(); + view.getRenderer().onSurfaceCreated(null, null); + } + public void surfaceDestroyed() { + synchronized(sGLStateManager) { + if (LOG_THREADS) { + Log.i("GLState", "surfaceDestroyed tid=" + Thread.currentThread().getName()); + } + mHasSurface = false; + sGLStateManager.notifyAll(); + // while((!mWaitingForSurface) && (!mExited)) { + // try { + // sGLStateManager.wait(); + // } catch (InterruptedException e) { + // Thread.currentThread().interrupt(); + // } + // } + } + NativeGLSurfaceView view = mNativeGLSurfaceViewWeakRef.get(); + view.getRenderer().onPause(); + } + public void onWindowResize(int w, int h) { + NativeGLSurfaceView view; + synchronized (sGLStateManager) { + mWidth = w; + mHeight = h; + mSizeChanged = true; + mRequestRender = true; + mRenderComplete = false; + // If we are already on the GL thread, this means a client callback + // has caused reentrancy, for example via updating the SurfaceView parameters. + // We need to process the size change eventually though and update our EGLSurface. + // So we set the parameters and return so they can be processed on our + // next iteration. + if (Thread.currentThread() == mOwnerThread) { + return; + } + if (LOG_SURFACE) { + Log.i("GLState", "onWindowResize notifying renderer from tid=" + Thread.currentThread().getName()); + } + view = mNativeGLSurfaceViewWeakRef.get(); + sGLStateManager.notifyAll(); + } + // Wait for thread to react to resize and render a frame + view.getRenderer().onWindowResize(w, h); + } + public void requestExitAndWait() { + // don't call this from GLState thread or it is a guaranteed + // deadlock! + synchronized(sGLStateManager) { + mShouldExit = true; + sGLStateManager.notifyAll(); + while (! mExited) { + try { + sGLStateManager.wait(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + } + } + public void requestReleaseEglContextLocked() { + mShouldReleaseEglContext = true; + sGLStateManager.notifyAll(); + } + /** + * Queue an "event" to be run on the GL rendering thread. + * @param r the runnable to be run on the GL rendering thread. + */ + public void queueEvent(Runnable r) { + if (r == null) { + throw new IllegalArgumentException("r must not be null"); + } + synchronized(sGLStateManager) { + mEventQueue.add(r); + sGLStateManager.notifyAll(); + } + } + // Once the thread is started, all accesses to the following member + // variables are protected by the sGLStateManager monitor + private boolean mShouldExit; + private boolean mExited; + private boolean mRequestPaused; + private boolean mPaused; + private boolean mHasSurface; + private boolean mSurfaceIsBad; + private boolean mWaitingForSurface; + private boolean mHaveEglContext; + private boolean mHaveEglSurface; + private boolean mFinishedCreatingEglSurface; + private boolean mShouldReleaseEglContext; + private int mWidth; + private int mHeight; + private int mRenderMode; + private boolean mRequestRender; + private boolean mWantRenderNotification; + private boolean mRenderComplete; + private ArrayList mEventQueue = new ArrayList(); + private boolean mSizeChanged = true; + private Runnable mFinishDrawingRunnable = null; + // End of member variables protected by the sGLStateManager monitor. + // @UnsupportedAppUsage + private EglHelper mEglHelper; + /** + * Set once at thread construction time, nulled out when the parent view is garbage + * called. This weak reference allows the NativeGLSurfaceView to be garbage collected while + * the GLState is still alive. + */ + private WeakReference mNativeGLSurfaceViewWeakRef; + /** + * An EGL helper class. + */ + private static class EglHelper { + public EglHelper(WeakReference glSurfaceViewWeakRef) { + mNativeGLSurfaceViewWeakRef = glSurfaceViewWeakRef; + mEglSurface = null; + } + /** + * Initialize EGL for a given configuration spec. + * @param configSpec + */ + public void start() { + if (LOG_EGL) { + Log.w("EglHelper", "start() tid=" + Thread.currentThread().getName()); + } + /* + * Get an EGL instance + */ + mEgl = (EGL10) EGLContext.getEGL(); + /* + * Get to the default display. + */ + mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { + throw new RuntimeException("eglGetDisplay failed"); + } + /* + * We can now initialize EGL for that display + */ + int[] version = new int[2]; + if(!mEgl.eglInitialize(mEglDisplay, version)) { + throw new RuntimeException("eglInitialize failed"); + } + NativeGLSurfaceView view = mNativeGLSurfaceViewWeakRef.get(); + if (view == null) { + mEglConfig = null; + mEglContext = null; + } else { + mEglConfig = view.getEGLConfigChooser().chooseConfig(mEgl, mEglDisplay); + /* + * Create an EGL context. We want to do this as rarely as we can, because an + * EGL context is a somewhat heavy object. + */ + mEglContext = view.getEGLContextFactory().createContext(mEgl, mEglDisplay, mEglConfig); + } + if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) { + mEglContext = null; + throwEglException("createContext"); + } + if (LOG_EGL) { + Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getName()); + } + } + /** + * Create an egl surface for the current SurfaceHolder surface. If a surface + * already exists, destroy it before creating the new surface. + * + * @return true if the surface was created successfully. + */ + public boolean createSurface() { + if (LOG_EGL) { + Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getName()); + } + /* + * Check preconditions. + */ + if (mEgl == null) { + throw new RuntimeException("egl not initialized"); + } + if (mEglDisplay == null) { + throw new RuntimeException("eglDisplay not initialized"); + } + if (mEglConfig == null) { + throw new RuntimeException("mEglConfig not initialized"); + } + /* + * The window size has changed, so we need to create a new + * surface. + */ + destroySurfaceImp(); + /* + * Create an EGL surface we can render into. + */ + NativeGLSurfaceView view = mNativeGLSurfaceViewWeakRef.get(); + if (view != null) { + mEglSurface = view.getEGLWindowSurfaceFactory().createWindowSurface + (mEgl, mEglDisplay, mEglConfig, view.getHolder()); + } else { + mEglSurface = null; + } + if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { + int error = mEgl.eglGetError(); + if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { + Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); + } + return false; + } + /* + * Before we can issue GL commands, we need to make sure + * the context is current and bound to a surface. + */ + if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { + /* + * Could not make the context current, probably because the underlying + * SurfaceView surface has been destroyed. + */ + logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError()); + return false; + } + return true; + } + /** + * Create a GL object for the current EGL context. + * @return + */ + GL createGL() { + GL gl = mEglContext.getGL(); + NativeGLSurfaceView view = mNativeGLSurfaceViewWeakRef.get(); + if (view != null) { + NativeGLSurfaceView.GLWrapper wrapper = view.getGLWrapper(); + if (wrapper != null) { + gl = wrapper.wrap(gl); + } + int view_mDebugFlags = view.getDebugFlags(); + if ((view_mDebugFlags & (NativeGLSurfaceView.DEBUG_CHECK_GL_ERROR | NativeGLSurfaceView.DEBUG_LOG_GL_CALLS)) != 0) { + int configFlags = 0; + Writer log = null; + if ((view_mDebugFlags & NativeGLSurfaceView.DEBUG_CHECK_GL_ERROR) != 0) { + configFlags |= GLDebugHelper.CONFIG_CHECK_GL_ERROR; + } + if ((view_mDebugFlags & NativeGLSurfaceView.DEBUG_LOG_GL_CALLS) != 0) { + log = new NativeGLSurfaceView.LogWriter(); + } + gl = GLDebugHelper.wrap(gl, configFlags, log); + } + } + return gl; + } + /** + * Display the current render surface. + * @return the EGL error code from eglSwapBuffers. + */ + public int swap() { + if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { + return mEgl.eglGetError(); + } + return EGL10.EGL_SUCCESS; + } + public void destroySurface() { + if (LOG_EGL) { + Log.w("EglHelper", "destroySurface() tid=" + Thread.currentThread().getName()); + } + destroySurfaceImp(); + } + private void destroySurfaceImp() { + if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) { + mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_CONTEXT); + NativeGLSurfaceView view = mNativeGLSurfaceViewWeakRef.get(); + if (view != null) { + view.getEGLWindowSurfaceFactory().destroySurface(mEgl, mEglDisplay, mEglSurface); + } + mEglSurface = null; + } + } + public void finish() { + if (LOG_EGL) { + Log.w("EglHelper", "finish() tid=" + Thread.currentThread().getName()); + } + if (mEglContext != null) { + NativeGLSurfaceView view = mNativeGLSurfaceViewWeakRef.get(); + if (view != null) { + view.getEGLContextFactory().destroyContext(mEgl, mEglDisplay, mEglContext); + } + mEglContext = null; + } + if (mEglDisplay != null) { + mEgl.eglTerminate(mEglDisplay); + mEglDisplay = null; + } + } + private void throwEglException(String function) { + throwEglException(function, mEgl.eglGetError()); + } + public static void throwEglException(String function, int error) { + String message = formatEglError(function, error); + if (LOG_THREADS) { + Log.e("EglHelper", "throwEglException tid=" + Thread.currentThread().getName() + " " + + message); + } + throw new RuntimeException(message); + } + public static void logEglErrorAsWarning(String tag, String function, int error) { + Log.w(tag, formatEglError(function, error)); + } + public static String formatEglError(String function, int error) { + return function + " failed: " + error; // EGLLogWrapper.getErrorString(error); + } + private WeakReference mNativeGLSurfaceViewWeakRef; + EGL10 mEgl; + EGLDisplay mEglDisplay; + EGLSurface mEglSurface; + EGLConfig mEglConfig; + // @UnsupportedAppUsage + EGLContext mEglContext; + } + public void throwEglException(String function) { + if(mEglHelper!=null) { + mEglHelper.throwEglException(function); + } + } + private static class GLStateManager { + private static String TAG = "GLStateManager"; + public synchronized void threadExiting(GLState thread) { + if (LOG_THREADS) { + Log.i("GLState", "exiting tid=" + Thread.currentThread().getName()); + } + thread.mExited = true; + notifyAll(); + } + /* + * Releases the EGL context. Requires that we are already in the + * sGLStateManager monitor when this is called. + */ + public void releaseEglContextLocked(GLState thread) { + notifyAll(); + } + } +} diff --git a/loaders/android/NativeGLSurfaceView.java.in b/loaders/android/NativeGLSurfaceView.java.in new file mode 100644 index 00000000..a7d8595a --- /dev/null +++ b/loaders/android/NativeGLSurfaceView.java.in @@ -0,0 +1,1080 @@ +/* NativeGLSurfaceView -*- mode: java; c-basic-offset: 2; -*- */ +/* + * Copyright (C) 2021 JFW + * + * # History + * + * - API derived from from GLSurfaceView. Must not create its own + * rendering thread; it is not safe to call into Gambit from multiple + * threads unless no threads or asynchronous i/o is done in Gambit. + * + * - Stripped down to API, keeping comments mostly for documentation. + * + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package @SYS_PACKAGE_DOT@; + +// import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; +import android.os.Trace; +import android.util.AttributeSet; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import java.io.Writer; +import java.lang.ref.WeakReference; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGL11; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; +import javax.microedition.khronos.opengles.GL; +import javax.microedition.khronos.opengles.GL10; + +import android.opengl.EGLExt; +import android.opengl.EGL14; + + +/** + * An implementation of SurfaceView that uses the dedicated surface for + * displaying OpenGL rendering. + *

+ * A NativeGLSurfaceView provides the following features: + *

+ *

    + *
  • Manages a surface, which is a special piece of memory that can be + * composited into the Android view system. + *
  • Manages an EGL display, which enables OpenGL to render into a surface. + *
  • Accepts a user-provided Renderer object that does the actual rendering. + *
  • Renders on a native thread decoupled from the UI thread. + *
  • Supports both on-demand and continuous rendering. + *
  • Optionally wraps, traces, and/or error-checks the renderer's OpenGL calls. + *
+ * + *
+ *

Developer Guides

+ *

For more information about how to use OpenGL, read the + * OpenGL developer guide.

+ *
+ * + *

Using NativeGLSurfaceView

+ *

+ * Typically you use NativeGLSurfaceView by subclassing it and overriding one or more of the + * View system input event methods. If your application does not need to override event + * methods then NativeGLSurfaceView can be used as-is. For the most part + * NativeGLSurfaceView behavior is customized by calling "set" methods rather than by subclassing. + * For example, unlike a regular View, drawing is delegated to a separate Renderer object which + * is registered with the NativeGLSurfaceView + * using the {@link #setRenderer(Renderer)} call. + * + * NOTE: The above comment derives from GLSurfaceView. Those set + * methods are likely to be removed when the code matures. + * + *

+ *

Initializing NativeGLSurfaceView

+ * All you have to do to initialize a NativeGLSurfaceView is call {@link #setRenderer(Renderer)}. + * However, if desired, you can modify the default behavior of NativeGLSurfaceView by calling one or + * more of these methods before calling setRenderer: + *
    + *
  • {@link #setDebugFlags(int)} + *
  • {@link #setEGLConfigChooser(boolean)} + *
  • {@link #setEGLConfigChooser(EGLConfigChooser)} + *
  • {@link #setEGLConfigChooser(int, int, int, int, int, int)} + *
  • {@link #setGLWrapper(GLWrapper)} + *
+ *

+ *

Specifying the android.view.Surface

+ * By default NativeGLSurfaceView will create a PixelFormat.RGB_888 format surface. If a translucent + * surface is required, call getHolder().setFormat(PixelFormat.TRANSLUCENT). + * The exact format of a TRANSLUCENT surface is device dependent, but it will be + * a 32-bit-per-pixel surface with 8 bits per component. + *

+ *

Choosing an EGL Configuration

+ * A given Android device may support multiple EGLConfig rendering configurations. + * The available configurations may differ in how many channels of data are present, as + * well as how many bits are allocated to each channel. Therefore, the first thing + * NativeGLSurfaceView has to do when starting to render is choose what EGLConfig to use. + *

+ * By default NativeGLSurfaceView chooses a EGLConfig that has an RGB_888 pixel format, + * with at least a 16-bit depth buffer and no stencil. + *

+ * If you would prefer a different EGLConfig + * you can override the default behavior by calling one of the + * setEGLConfigChooser methods. + *

+ *

Debug Behavior

+ * You can optionally modify the behavior of NativeGLSurfaceView by calling + * one or more of the debugging methods {@link #setDebugFlags(int)}, + * and {@link #setGLWrapper}. These methods may be called before and/or after setRenderer, but + * typically they are called before setRenderer so that they take effect immediately. + *

+ *

Setting a Renderer

+ * Finally, you must call {@link #setRenderer} to register a {@link Renderer}. + * The renderer is + * responsible for doing the actual OpenGL rendering. + *

+ *

Rendering Mode

+ * Once the renderer is set, you can control whether the renderer draws + * continuously or on-demand by calling + * {@link #setRenderMode}. The default is continuous rendering. + *

+ *

Activity Life-cycle

+ * A NativeGLSurfaceView should be notified when to pause and resume + * rendering. NativeGLSurfaceView clients should call {@link + * #onPause()} when the activity stops and {@link #onResume()} when + * the activity starts. These calls are forwarded to the object set + * via setRenderer to pause and resume the rendering thread, and + * possibly release and recreate the OpenGL display. + *

+ *

Handling events

+ *

+ * To handle an event you will typically subclass NativeGLSurfaceView and override the + * appropriate method, just as you would with any other View. However, when handling + * the event, you may need to communicate with the Renderer object + * that's running in the rendering thread. You can do this using any + * standard Java cross-thread communication mechanism. In addition, + * one relatively easy way to communicate with your renderer is + * to call + * {@link #queueEvent(Runnable)}. For example: + *

+ * class MyNativeGLSurfaceView extends NativeGLSurfaceView {
+ *
+ *     private MyRenderer mMyRenderer;
+ *
+ *     public void start() {
+ *         mMyRenderer = ...;
+ *         setRenderer(mMyRenderer);
+ *     }
+ *
+ *     public boolean onKeyDown(int keyCode, KeyEvent event) {
+ *         if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+ *             queueEvent(new Runnable() {
+ *                 // This method will be called on the rendering
+ *                 // thread:
+ *                 public void run() {
+ *                     mMyRenderer.handleDpadCenter();
+ *                 }});
+ *             return true;
+ *         }
+ *         return super.onKeyDown(keyCode, event);
+ *     }
+ * }
+ * 
+ * + */ +public class NativeGLSurfaceView extends SurfaceView implements SurfaceHolder.Callback2 { + private final static String TAG = "NativeGLSurfaceView"; + private final static boolean LOG_ATTACH_DETACH = false; + private final static boolean LOG_THREADS = false; + + /* Summary 20210415: + * + * private final WeakReference mThisWeakRef = + * new WeakReference(this); + * // @UnsupportedAppUsage + * private GLState mGLState; + * // @UnsupportedAppUsage + * private Renderer mRenderer; + * private boolean mDetached; + * private EGLConfigChooser mEGLConfigChooser; + * private EGLContextFactory mEGLContextFactory; + * private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory; + * private GLWrapper mGLWrapper; + * private int mDebugFlags; + * private int mEGLContextClientVersion; + * private boolean mPreserveEGLContextOnPause; + */ + + /** + * The renderer only renders + * when the surface is created, or when {@link #requestRender} is called. + * + * @see #getRenderMode() + * @see #setRenderMode(int) + * @see #requestRender() + */ + public final static int RENDERMODE_WHEN_DIRTY = 0; + /** + * The renderer is called + * continuously to re-render the scene. + * + * @see #getRenderMode() + * @see #setRenderMode(int) + */ + public final static int RENDERMODE_CONTINUOUSLY = 1; + /** + * Check glError() after every GL call and throw an exception if glError indicates + * that an error has occurred. This can be used to help track down which OpenGL ES call + * is causing an error. + * + * @see #getDebugFlags + * @see #setDebugFlags + */ + public final static int DEBUG_CHECK_GL_ERROR = 1; + /** + * Log GL calls to the system log at "verbose" level with tag "NativeGLSurfaceView". + * + * @see #getDebugFlags + * @see #setDebugFlags + */ + public final static int DEBUG_LOG_GL_CALLS = 2; + /** + * Standard View constructor. In order to render something, you + * must call {@link #setRenderer} to register a renderer. + */ + public NativeGLSurfaceView(Context context) { + super(context); + init(); + } + /** + * Standard View constructor. In order to render something, you + * must call {@link #setRenderer} to register a renderer. + */ + public NativeGLSurfaceView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + private void init() { + // Install a SurfaceHolder.Callback so we get notified when the + // underlying surface is created and destroyed + SurfaceHolder holder = getHolder(); + holder.addCallback(this); + // setFormat is done by SurfaceView in SDK 2.3 and newer. Uncomment + // this statement if back-porting to 2.2 or older: + // holder.setFormat(PixelFormat.RGB_565); + // + // setType is not needed for SDK 2.0 or newer. Uncomment this + // statement if back-porting this code to older SDKs. + // holder.setType(SurfaceHolder.SURFACE_TYPE_GPU); + } + /** + * Set the glWrapper. If the glWrapper is not null, its + * {@link GLWrapper#wrap(GL)} method is called + * whenever a surface is created. A GLWrapper can be used to wrap + * the GL object that's passed to the renderer. Wrapping a GL + * object enables examining and modifying the behavior of the + * GL calls made by the renderer. + *

+ * Wrapping is typically used for debugging purposes. + *

+ * The default value is null. + * @param glWrapper the new GLWrapper + */ + private GLWrapper mGLWrapper; + public GLWrapper getGLWrapper() { return mGLWrapper; } + public void setGLWrapper(GLWrapper glWrapper) { + mGLWrapper = glWrapper; + } + /** + * Set the debug flags to a new value. The value is + * constructed by OR-together zero or more + * of the DEBUG_CHECK_* constants. The debug flags take effect + * whenever a surface is created. The default value is zero. + * @param debugFlags the new debug flags + * @see #DEBUG_CHECK_GL_ERROR + * @see #DEBUG_LOG_GL_CALLS + */ + private int mDebugFlags; + public void setDebugFlags(int debugFlags) { + mDebugFlags = debugFlags; + } + /** + * Get the current value of the debug flags. + * @return the current value of the debug flags. + */ + public int getDebugFlags() { + return mDebugFlags; + } + /** + * Control whether the EGL context is preserved when the NativeGLSurfaceView is paused and + * resumed. + *

+ * If set to true, then the EGL context may be preserved when the NativeGLSurfaceView is paused. + *

+ * Prior to API level 11, whether the EGL context is actually preserved or not + * depends upon whether the Android device can support an arbitrary number of + * EGL contexts or not. Devices that can only support a limited number of EGL + * contexts must release the EGL context in order to allow multiple applications + * to share the GPU. + *

+ * If set to false, the EGL context will be released when the NativeGLSurfaceView is paused, + * and recreated when the NativeGLSurfaceView is resumed. + *

+ * + * The default is false. + * + * @param preserveOnPause preserve the EGL context when paused + */ + private boolean mPreserveEGLContextOnPause; + public void setPreserveEGLContextOnPause(boolean preserveOnPause) { + mPreserveEGLContextOnPause = preserveOnPause; + } + /** + * @return true if the EGL context will be preserved when paused + */ + public boolean getPreserveEGLContextOnPause() { + return mPreserveEGLContextOnPause; + } + /** + * Set the renderer associated with this view. Also starts the thread that + * will call the renderer, which in turn causes the rendering to start. + *

This method should be called once and only once in the life-cycle of + * a NativeGLSurfaceView. + *

The following NativeGLSurfaceView methods can only be called before + * setRenderer is called: + *

    + *
  • {@link #setEGLConfigChooser(boolean)} + *
  • {@link #setEGLConfigChooser(EGLConfigChooser)} + *
  • {@link #setEGLConfigChooser(int, int, int, int, int, int)} + *
+ *

+ * The following NativeGLSurfaceView methods can only be called after + * setRenderer is called: + *

    + *
  • {@link #getRenderMode()} + *
  • {@link #onPause()} + *
  • {@link #onResume()} + *
  • {@link #queueEvent(Runnable)} + *
  • {@link #requestRender()} + *
  • {@link #setRenderMode(int)} + *
+ * + * @param renderer the renderer to use to perform OpenGL drawing. + */ + private EGLConfigChooser mEGLConfigChooser; + public EGLConfigChooser getEGLConfigChooser() { return mEGLConfigChooser; } + private EGLContextFactory mEGLContextFactory; + public EGLContextFactory getEGLContextFactory() { return mEGLContextFactory; } + private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory; + public EGLWindowSurfaceFactory getEGLWindowSurfaceFactory() { return mEGLWindowSurfaceFactory; } + // @UnsupportedAppUsage + private Renderer mRenderer; + // @UnsupportedAppUsage + private final WeakReference mThisWeakRef = + new WeakReference(this); + public void setRenderer(Renderer renderer) { + checkRenderThreadState(); + if (mEGLConfigChooser == null) { + mEGLConfigChooser = new SimpleEGLConfigChooser(true); + } + if (mEGLContextFactory == null) { + mEGLContextFactory = new DefaultContextFactory(); // FIXME: depends on mEGLContextClientVersion + } + if (mEGLWindowSurfaceFactory == null) { + mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory(); + } + mRenderer = renderer; + mGLState = new GLState(mThisWeakRef); + mRenderer.start(); + } + public Renderer getRenderer() { return mRenderer; } + /** + * Install a custom EGLContextFactory. + *

If this method is + * called, it must be called before {@link #setRenderer(Renderer)} + * is called. + *

+ * If this method is not called, then by default + * a context will be created with no shared context and + * with a null attribute list. + */ + public void setEGLContextFactory(EGLContextFactory factory) { + checkRenderThreadState(); + mEGLContextFactory = factory; + } + /** + * Install a custom EGLWindowSurfaceFactory. + *

If this method is + * called, it must be called before {@link #setRenderer(Renderer)} + * is called. + *

+ * If this method is not called, then by default + * a window surface will be created with a null attribute list. + */ + public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory) { + checkRenderThreadState(); + mEGLWindowSurfaceFactory = factory; + } + /** + * Install a custom EGLConfigChooser. + *

If this method is + * called, it must be called before {@link #setRenderer(Renderer)} + * is called. + *

+ * If no setEGLConfigChooser method is called, then by default the + * view will choose an EGLConfig that is compatible with the current + * android.view.Surface, with a depth buffer depth of + * at least 16 bits. + * @param configChooser + */ + public void setEGLConfigChooser(EGLConfigChooser configChooser) { + checkRenderThreadState(); + mEGLConfigChooser = configChooser; + } + /** + * Install a config chooser which will choose a config + * as close to 16-bit RGB as possible, with or without an optional depth + * buffer as close to 16-bits as possible. + *

If this method is + * called, it must be called before {@link #setRenderer(Renderer)} + * is called. + *

+ * If no setEGLConfigChooser method is called, then by default the + * view will choose an RGB_888 surface with a depth buffer depth of + * at least 16 bits. + * + * @param needDepth + */ + public void setEGLConfigChooser(boolean needDepth) { + setEGLConfigChooser(new SimpleEGLConfigChooser(needDepth)); + } + /** + * Install a config chooser which will choose a config + * with at least the specified depthSize and stencilSize, + * and exactly the specified redSize, greenSize, blueSize and alphaSize. + *

If this method is + * called, it must be called before {@link #setRenderer(Renderer)} + * is called. + *

+ * If no setEGLConfigChooser method is called, then by default the + * view will choose an RGB_888 surface with a depth buffer depth of + * at least 16 bits. + * + */ + public void setEGLConfigChooser(int redSize, int greenSize, int blueSize, + int alphaSize, int depthSize, int stencilSize) { + setEGLConfigChooser(new ComponentSizeChooser(redSize, greenSize, + blueSize, alphaSize, depthSize, stencilSize)); + } + /** + * Inform the default EGLContextFactory and default EGLConfigChooser + * which EGLContext client version to pick. + *

Use this method to create an OpenGL ES 2.0-compatible context. + * Example: + *

+   *     public MyView(Context context) {
+   *         super(context);
+   *         setEGLContextClientVersion(2); // Pick an OpenGL ES 2.0 context.
+   *         setRenderer(new MyRenderer());
+   *     }
+   * 
+ *

Note: Activities which require OpenGL ES 2.0 should indicate this by + * setting @lt;uses-feature android:glEsVersion="0x00020000" /> in the activity's + * AndroidManifest.xml file. + *

If this method is called, it must be called before {@link #setRenderer(Renderer)} + * is called. + *

This method only affects the behavior of the default EGLContexFactory and the + * default EGLConfigChooser. If + * {@link #setEGLContextFactory(EGLContextFactory)} has been called, then the supplied + * EGLContextFactory is responsible for creating an OpenGL ES 2.0-compatible context. + * If + * {@link #setEGLConfigChooser(EGLConfigChooser)} has been called, then the supplied + * EGLConfigChooser is responsible for choosing an OpenGL ES 2.0-compatible config. + * @param version The EGLContext client version to choose. Use 2 for OpenGL ES 2.0 + */ + private int mEGLContextClientVersion; + public void setEGLContextClientVersion(int version) { + checkRenderThreadState(); + mEGLContextClientVersion = version; + } + /** + * Set the rendering mode. When renderMode is + * RENDERMODE_CONTINUOUSLY, the renderer is called + * repeatedly to re-render the scene. When renderMode + * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface + * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY. + *

+ * Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance + * by allowing the GPU and CPU to idle when the view does not need to be updated. + *

+ * This method can only be called after {@link #setRenderer(Renderer)} + * + * @param renderMode one of the RENDERMODE_X constants + * @see #RENDERMODE_CONTINUOUSLY + * @see #RENDERMODE_WHEN_DIRTY + */ + public void setRenderMode(int renderMode) { + mGLState.setRenderMode(renderMode); + } + /** + * Get the current rendering mode. May be called + * from any thread. Must not be called before a renderer has been set. + * @return the current rendering mode. + * @see #RENDERMODE_CONTINUOUSLY + * @see #RENDERMODE_WHEN_DIRTY + */ + public int getRenderMode() { + return mGLState.getRenderMode(); + } + /** + * Request that the renderer render a frame. + * This method is typically used when the render mode has been set to + * {@link #RENDERMODE_WHEN_DIRTY}, so that frames are only rendered on demand. + * May be called + * from any thread. Must not be called before a renderer has been set. + */ + public void requestRender() { + mGLState.requestRender(); + } + /** + * This method is part of the SurfaceHolder.Callback interface, and is + * not normally called or subclassed by clients of NativeGLSurfaceView. + */ + public void surfaceCreated(SurfaceHolder holder) { + mGLState.surfaceCreated(); + } + /** + * This method is part of the SurfaceHolder.Callback interface, and is + * not normally called or subclassed by clients of NativeGLSurfaceView. + */ + public void surfaceDestroyed(SurfaceHolder holder) { + // Surface will be destroyed when we return + mGLState.surfaceDestroyed(); + } + /** + * This method is part of the SurfaceHolder.Callback interface, and is + * not normally called or subclassed by clients of NativeGLSurfaceView. + */ + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { + mGLState.onWindowResize(w, h); + } + /** + * This method is part of the SurfaceHolder.Callback2 interface, and is + * not normally called or subclassed by clients of NativeGLSurfaceView. + */ + @Override + public void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable finishDrawing) { + if (mGLState != null) { + mGLState.requestRenderAndNotify(finishDrawing); + surfaceRedrawNeeded(holder); + } + } + /** + * This method is part of the SurfaceHolder.Callback2 interface, and is + * not normally called or subclassed by clients of NativeGLSurfaceView. + */ + @Deprecated + @Override + public void surfaceRedrawNeeded(SurfaceHolder holder) { + // Since we are not part of the framework we pretent not to know + // only surfaceRedrawNeededAsync will be called. + mRenderer.onRedrawNeeded(); + } + /** + * Pause the rendering thread. + * + * This method should be called when it is no longer desirable for the + * NativeGLSurfaceView to continue rendering, such as in response to + * {@link android.app.Activity#onStop Activity.onStop}. + * + * Must not be called before a renderer has been set. + */ + public void onPause() { + mRenderer.onPause(); + } + /** + * Resumes the rendering thread, re-creating the OpenGL context if necessary. It + * is the counterpart to {@link #onPause()}. + * + * This method should typically be called in + * {@link android.app.Activity#onStart Activity.onStart}. + * + * Must not be called before a renderer has been set. + */ + public void onResume() { + mRenderer.onResume(); + } + /** + * Queue a runnable to be run on the GL rendering thread. This can be used + * to communicate with the Renderer on the rendering thread. + * Must not be called before a renderer has been set. + * @param r the runnable to be run on the GL rendering thread. + */ + public void queueEvent(Runnable r) { + mGLState.queueEvent(r); + } + // ---------------------------------------------------------------------- + /** + * An interface used to wrap a GL interface. + *

Typically + * used for implementing debugging and tracing on top of the default + * GL interface. You would typically use this by creating your own class + * that implemented all the GL methods by delegating to another GL instance. + * Then you could add your own behavior before or after calling the + * delegate. All the GLWrapper would do was instantiate and return the + * wrapper GL instance: + *

+   * class MyGLWrapper implements GLWrapper {
+   *     GL wrap(GL gl) {
+   *         return new MyGLImplementation(gl);
+   *     }
+   *     static class MyGLImplementation implements GL,GL10,GL11,... {
+   *         ...
+   *     }
+   * }
+   * 
+ * @see #setGLWrapper(GLWrapper) + */ + public interface GLWrapper { + /** + * Wraps a gl interface in another gl interface. + * @param gl a GL interface that is to be wrapped. + * @return either the input argument or another GL object that wraps the input argument. + */ + GL wrap(GL gl); + } + /** + * A generic renderer interface. + *

+ * The renderer is responsible for making OpenGL calls to render a frame. + *

+ * NativeGLSurfaceView clients typically create their own classes that implement + * this interface, and then call {@link NativeGLSurfaceView#setRenderer} to + * register the renderer with the NativeGLSurfaceView. + *

+ * + *

+ *

Developer Guides

+ *

For more information about how to use OpenGL, read the + * OpenGL developer guide.

+ *
+ * + *

Threading

+ * + * The renderer will be called on the native thread. Communication + * to the renderer thread, e.g., from the UI thread, should be done + * {@link NativeGLSurfaceView#queueEvent(Runnable)} + * + *

EGL Context Lost

+ * There are situations where the EGL rendering context will be lost. This + * typically happens when device wakes up after going to sleep. When + * the EGL context is lost, all OpenGL resources (such as textures) that are + * associated with that context will be automatically deleted. In order to + * keep rendering correctly, a renderer must recreate any lost resources + * that it still needs. The {@link #onSurfaceCreated(GL10, EGLConfig)} method + * is a convenient place to do this. + * + * @see #setRenderer(Renderer) + */ + public interface Renderer { + /* void start() + * + * Called when the GLState onject is created. The native thread + * must attach by calling GLState.fromNativeStart(); before this + * call may return. + */ + void start(); + /* void onPause + * + * Called when the NativeGLSurfaceView received an onPause() call. + */ + void onPause(); + /* void onResume + * + * Called when the NativeGLSurfaceView received an onResume() call. + */ + void onResume(); + /* void onWindowResize(int w, int h) + * + * Called when native thread should learn about new dimension. + */ + void onWindowResize(int w, int h); + /** + * Called when the surface is created or recreated. + *

+ * Called when the rendering thread + * starts and whenever the EGL context is lost. The EGL context will typically + * be lost when the Android device awakes after going to sleep. + *

+ * Since this method is called at the beginning of rendering, as well as + * every time the EGL context is lost, this method is a convenient place to put + * code to create resources that need to be created when the rendering + * starts, and that need to be recreated when the EGL context is lost. + * Textures are an example of a resource that you might want to create + * here. + *

+ * Note that when the EGL context is lost, all OpenGL resources associated + * with that context will be automatically deleted. You do not need to call + * the corresponding "glDelete" methods such as glDeleteTextures to + * manually delete these lost resources. + *

+ * @param gl the GL interface. Use instanceof to + * test if the interface supports GL11 or higher interfaces. + * @param config the EGLConfig of the created surface. Can be used + * to create matching pbuffers. + */ + void onSurfaceCreated(GL10 gl, EGLConfig config); + /** + * Called when the surface changed size. + *

+ * Called after the surface is created and whenever + * the OpenGL ES surface size changes. + *

+ * Typically you will set your viewport here. If your camera + * is fixed then you could also set your projection matrix here: + *

+     * void onSurfaceChanged(GL10 gl, int width, int height) {
+     *     gl.glViewport(0, 0, width, height);
+     *     // for a fixed camera, set the projection too
+     *     float ratio = (float) width / height;
+     *     gl.glMatrixMode(GL10.GL_PROJECTION);
+     *     gl.glLoadIdentity();
+     *     gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
+     * }
+     * 
+ * @param gl the GL interface. Use instanceof to + * test if the interface supports GL11 or higher interfaces. + * @param width + * @param height + */ + void onSurfaceChanged(GL10 gl, int width, int height); + /** void onRedrawNeeded() + * + * Called to request a redraw of the current frame. + */ + void onRedrawNeeded(); + /** + * Optional: Called when the current frame was drawn from native + * thread just before swapping buffers. + *

+ * This method is responsible for drawing the current frame. + *

+ * The implementation of this method typically looks like this: + *

+     * void onDrawFrame(GL10 gl) {
+     *     gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
+     *     //... other gl calls to render the scene ...
+     * }
+     * 
+ * @param gl the GL interface. Use instanceof to + * test if the interface supports GL11 or higher interfaces. + */ + void onDrawFrame(GL10 gl); + } + /* NOTE, FIXME: Are these interfaces useful enough to be kept at all? */ + /** + * An interface for customizing the eglCreateContext and eglDestroyContext calls. + *

+ * This interface must be implemented by clients wishing to call + * {@link NativeGLSurfaceView#setEGLContextFactory(EGLContextFactory)} + */ + public interface EGLContextFactory { + EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig); + void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context); + } + /** + * An interface for customizing the eglCreateWindowSurface and eglDestroySurface calls. + *

+ * This interface must be implemented by clients wishing to call + * {@link NativeGLSurfaceView#setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory)} + */ + public interface EGLWindowSurfaceFactory { + /** + * @return null if the surface cannot be constructed. + */ + EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config, + Object nativeWindow); + void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface); + } + /** + * An interface for choosing an EGLConfig configuration from a list of + * potential configurations. + *

+ * This interface must be implemented by clients wishing to call + * {@link NativeGLSurfaceView#setEGLConfigChooser(EGLConfigChooser)} + */ + public interface EGLConfigChooser { + /** + * Choose a configuration from the list. Implementors typically + * implement this method by calling + * {@link EGL10#eglChooseConfig} and iterating through the results. Please consult the + * EGL specification available from The Khronos Group to learn how to call eglChooseConfig. + * @param egl the EGL10 for the current display. + * @param display the current display. + * @return the chosen configuration. + */ + EGLConfig chooseConfig(EGL10 egl, EGLDisplay display); + } + /* + * + * ----------------------- END OF PUBLIC API ------------------------------------------ + * + */ + /* + * + * ----------------------- BEGIN PROTECTED ------------------------------------------ + * + */ + // @UnsupportedAppUsage + private static GLState mGLState; // PRIVATE data referenced from protected code + @Override + protected void finalize() throws Throwable { + try { + if (mGLState != null) { + // GLState may still be running if this view was never + // attached to a window. + mGLState.requestExitAndWait(); + } + } finally { + super.finalize(); + } + } + /** + * This method is used as part of the View class and is not normally + * called or subclassed by clients of NativeGLSurfaceView. + */ + private boolean mDetached; + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (LOG_ATTACH_DETACH) { + Log.d(TAG, "onAttachedToWindow reattach =" + mDetached); + } + if (mDetached && (mRenderer != null)) { + int renderMode = RENDERMODE_CONTINUOUSLY; + if (mGLState != null) { + renderMode = mGLState.getRenderMode(); + } + mGLState = new GLState(mThisWeakRef); + if (renderMode != RENDERMODE_CONTINUOUSLY) { + mGLState.setRenderMode(renderMode); + } + mRenderer.start(); + } + mDetached = false; + } + @Override + protected void onDetachedFromWindow() { + if (LOG_ATTACH_DETACH) { + Log.d(TAG, "onDetachedFromWindow"); + } + if (mGLState != null) { + mGLState.requestExitAndWait(); + } + mDetached = true; + super.onDetachedFromWindow(); + } + private class DefaultContextFactory implements EGLContextFactory { + private int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) { + int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion, + EGL10.EGL_NONE }; + return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, + mEGLContextClientVersion != 0 ? attrib_list : null); + } + public void destroyContext(EGL10 egl, EGLDisplay display, + EGLContext context) { + if (!egl.eglDestroyContext(display, context)) { + Log.e("DefaultContextFactory", "display:" + display + " context: " + context); + if (LOG_THREADS) { + Log.i("DefaultContextFactory", "tid=" + Thread.currentThread().getId()); + } + mGLState.throwEglException("eglDestroyContex"); + } + } + } + /* + * + * ----------------------- END PROTECTED ------------------------------------------ + * + */ + /* + * + * ----------------------- BEGIN INTERNAL ------------------------------------------ + * + */ + private static class DefaultWindowSurfaceFactory implements EGLWindowSurfaceFactory { + public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, + EGLConfig config, Object nativeWindow) { + EGLSurface result = null; + try { + result = egl.eglCreateWindowSurface(display, config, nativeWindow, null); + } catch (IllegalArgumentException e) { + // This exception indicates that the surface flinger surface + // is not valid. This can happen if the surface flinger surface has + // been torn down, but the application has not yet been + // notified via SurfaceHolder.Callback.surfaceDestroyed. + // In theory the application should be notified first, + // but in practice sometimes it is not. See b/4588890 + Log.e(TAG, "eglCreateWindowSurface", e); + } + return result; + } + public void destroySurface(EGL10 egl, EGLDisplay display, + EGLSurface surface) { + egl.eglDestroySurface(display, surface); + } + } + private abstract class BaseConfigChooser + implements EGLConfigChooser { + public BaseConfigChooser(int[] configSpec) { + mConfigSpec = filterConfigSpec(configSpec); + } + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + int[] num_config = new int[1]; + if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, + num_config)) { + throw new IllegalArgumentException("eglChooseConfig failed"); + } + int numConfigs = num_config[0]; + if (numConfigs <= 0) { + throw new IllegalArgumentException( + "No configs match configSpec"); + } + EGLConfig[] configs = new EGLConfig[numConfigs]; + if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, + num_config)) { + throw new IllegalArgumentException("eglChooseConfig#2 failed"); + } + EGLConfig config = chooseConfig(egl, display, configs); + if (config == null) { + throw new IllegalArgumentException("No config chosen"); + } + return config; + } + abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, + EGLConfig[] configs); + protected int[] mConfigSpec; + private int[] filterConfigSpec(int[] configSpec) { + if (mEGLContextClientVersion != 2 && mEGLContextClientVersion != 3) { + return configSpec; + } + /* We know none of the subclasses define EGL_RENDERABLE_TYPE. + * And we know the configSpec is well formed. + */ + int len = configSpec.length; + int[] newConfigSpec = new int[len + 2]; + System.arraycopy(configSpec, 0, newConfigSpec, 0, len-1); + newConfigSpec[len-1] = EGL10.EGL_RENDERABLE_TYPE; + if (mEGLContextClientVersion == 2) { + newConfigSpec[len] = EGL14.EGL_OPENGL_ES2_BIT; /* EGL_OPENGL_ES2_BIT */ + } else { + newConfigSpec[len] = EGLExt.EGL_OPENGL_ES3_BIT_KHR; /* EGL_OPENGL_ES3_BIT_KHR */ + } + newConfigSpec[len+1] = EGL10.EGL_NONE; + return newConfigSpec; + } + } + /** + * Choose a configuration with exactly the specified r,g,b,a sizes, + * and at least the specified depth and stencil sizes. + */ + private class ComponentSizeChooser extends BaseConfigChooser { + public ComponentSizeChooser(int redSize, int greenSize, int blueSize, + int alphaSize, int depthSize, int stencilSize) { + super(new int[] { + EGL10.EGL_RED_SIZE, redSize, + EGL10.EGL_GREEN_SIZE, greenSize, + EGL10.EGL_BLUE_SIZE, blueSize, + EGL10.EGL_ALPHA_SIZE, alphaSize, + EGL10.EGL_DEPTH_SIZE, depthSize, + EGL10.EGL_STENCIL_SIZE, stencilSize, + EGL10.EGL_NONE}); + mValue = new int[1]; + mRedSize = redSize; + mGreenSize = greenSize; + mBlueSize = blueSize; + mAlphaSize = alphaSize; + mDepthSize = depthSize; + mStencilSize = stencilSize; + } + @Override + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, + EGLConfig[] configs) { + for (EGLConfig config : configs) { + int d = findConfigAttrib(egl, display, config, + EGL10.EGL_DEPTH_SIZE, 0); + int s = findConfigAttrib(egl, display, config, + EGL10.EGL_STENCIL_SIZE, 0); + if ((d >= mDepthSize) && (s >= mStencilSize)) { + int r = findConfigAttrib(egl, display, config, + EGL10.EGL_RED_SIZE, 0); + int g = findConfigAttrib(egl, display, config, + EGL10.EGL_GREEN_SIZE, 0); + int b = findConfigAttrib(egl, display, config, + EGL10.EGL_BLUE_SIZE, 0); + int a = findConfigAttrib(egl, display, config, + EGL10.EGL_ALPHA_SIZE, 0); + if ((r == mRedSize) && (g == mGreenSize) + && (b == mBlueSize) && (a == mAlphaSize)) { + return config; + } + } + } + return null; + } + private int findConfigAttrib(EGL10 egl, EGLDisplay display, + EGLConfig config, int attribute, int defaultValue) { + if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { + return mValue[0]; + } + return defaultValue; + } + private int[] mValue; + // Subclasses can adjust these values: + protected int mRedSize; + protected int mGreenSize; + protected int mBlueSize; + protected int mAlphaSize; + protected int mDepthSize; + protected int mStencilSize; + } + /** + * This class will choose a RGB_888 surface with + * or without a depth buffer. + * + */ + private class SimpleEGLConfigChooser extends ComponentSizeChooser { + public SimpleEGLConfigChooser(boolean withDepthBuffer) { + super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0); + } + } + static class LogWriter extends Writer { + @Override public void close() { + flushBuilder(); + } + @Override public void flush() { + flushBuilder(); + } + @Override public void write(char[] buf, int offset, int count) { + for(int i = 0; i < count; i++) { + char c = buf[offset + i]; + if ( c == '\n') { + flushBuilder(); + } + else { + mBuilder.append(c); + } + } + } + private void flushBuilder() { + if (mBuilder.length() > 0) { + Log.v("NativeGLSurfaceView", mBuilder.toString()); + mBuilder.delete(0, mBuilder.length()); + } + } + private StringBuilder mBuilder = new StringBuilder(); + } + private void checkRenderThreadState() { + if (mGLState != null) { + throw new IllegalStateException( + "setRenderer has already been called for this instance."); + } + } +} diff --git a/loaders/android/bootstrap.c.in b/loaders/android/bootstrap.c.in index 95507393..8526f496 100644 --- a/loaders/android/bootstrap.c.in +++ b/loaders/android/bootstrap.c.in @@ -46,34 +46,89 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +#define LAMBDANATIVE_JNI_VERSION JNI_VERSION_1_4 + +#define DEBUG 0 // FIXME: The DEBUG flag is currently not passed when compiling this file! + +#ifdef DEBUG +// naming convention LOGX=>log level, LOGXN=> as LOGX N hints at level +// order, LOGXXX*=>level is internal/restricted/deprecated +// see also +#define LOGUNKNOWN(...) ((void)__android_log_print(ANDROID_LOG_UNKNOWN, "@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@", __VA_ARGS__)) +/** The default priority, for internal use only. */ +#define LOGDFL(...) ((void)__android_log_print(ANDROID_LOG_DEFAULT, "@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@", __VA_ARGS__)) +/** Verbose logging. Should typically be disabled for a release apk. */ +#define LOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, "@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@", __VA_ARGS__)) +/** Debug logging. Should typically be disabled for a release apk. */ +#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@", __VA_ARGS__)) +/** Informational logging. Should typically be disabled for a release apk. */ +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@", __VA_ARGS__)) +/** Warning logging. For use with recoverable failures. */ +#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@", __VA_ARGS__)) +/** Error logging. For use with unrecoverable failures. */ +#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@", __VA_ARGS__)) +/** Fatal logging. For use when aborting. */ +#define LOGF(...) ((void)__android_log_print(ANDROID_LOG_FATAL, "@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@", __VA_ARGS__)) +#define LOGSILENT(...) ((void)__android_log_print(ANDROID_LOG_SILENT, "@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@", __VA_ARGS__)) +#else // log levels which should be active in release apk +// uncomment the next line to ensure DEBUG mode is actually active. +#error "NOT IN DEBUG MODE" +#define LOGUNKNOWN(...) +#define LOGDFL(...) +#define LOGV(...) +#define LOGD(...) +#define LOGI(...) +#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@", __VA_ARGS__)) +#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@", __VA_ARGS__)) +#define LOGF(...) ((void)__android_log_print(ANDROID_LOG_FATAL, "@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@", __VA_ARGS__)) +#define LOGSILENT(...) +#endif + @ANDROID_C_DEFINES@ // event hook void Java_@SYS_PACKAGE_UNDERSCORE@_myRenderer_nativeEvent(JNIEnv* e, jobject o, jint t, jint x, jint y){ if ((*e)->ExceptionCheck(e)) return; - ffi_event((int)t,(int)x,(int)y); + ffi_event((int)t,(int)x,(int)y); } void Java_@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@_nativeEvent(JNIEnv* e, jobject o, jint t, jint x, jint y){ if ((*e)->ExceptionCheck(e)) return; - ffi_event((int)t,(int)x,(int)y); + ffi_event((int)t,(int)x,(int)y); } // JNI Hooks and Global Objects -static jobject globalObj=NULL; -static JavaVM* s_vm = NULL; +static JavaVM* s_vm = NULL; // The JVM instance this code runs in. +static jclass main_class = NULL; // required to look up methods by components +static jobject globalObj = NULL; // The (only one) `Activity` object of `main_class`. static const char* app_directory_files = NULL; static const char* app_code_path = NULL; -void Java_@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@_nativeInstanceInit(JNIEnv* env, jobject thiz, jstring codePath, jstring directoryFiles){ +void Java_@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@_nativeClassInit(JNIEnv* env, jclass class_object) { + main_class = (*env)->NewGlobalRef(env, class_object); + LOGD("Java_@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@_nativeClassInit main_class %p", main_class); +} + +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + // FIXME: This was better done from _nativeClassInit above! + JNIEnv *env; + s_vm=vm; + // if ((*s_vm)->GetEnv(s_vm,(void**) &env, JNI_VERSION_1_4) != JNI_OK) return -1; + return LAMBDANATIVE_JNI_VERSION; +} + +void Java_@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@_nativeInstanceInit(JNIEnv* env, jobject activity, jstring codePath, jstring directoryFiles){ const char* tmp; - globalObj = (*env)->NewGlobalRef(env,thiz); tmp = (*env)->GetStringUTFChars(env, directoryFiles, 0); app_directory_files = strdup(tmp); (*env)->ReleaseStringUTFChars(env, directoryFiles, tmp); tmp = (*env)->GetStringUTFChars(env, codePath, 0); app_code_path = strdup(tmp); (*env)->ReleaseStringUTFChars(env, codePath, tmp); + globalObj = (*env)->NewGlobalRef(env, activity); + // FIXME: next line should be replaced by nativeClassInit, which magically is not found! + main_class = (*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, globalObj)); + LOGD("Java_@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@_nativeInstanceInit globalObj %p main_class %p", globalObj, main_class); } char* android_getFilesDir() { @@ -83,55 +138,193 @@ char* android_getPackageCodePath() { return (char*) app_code_path; } -jint JNI_OnLoad(JavaVM* vm, void* reserved){ - JNIEnv *env; - s_vm=vm; -// if ((*s_vm)->GetEnv(s_vm,(void**) &env, JNI_VERSION_1_4) != JNI_OK) return -1; - return JNI_VERSION_1_4; +// # JNI Untilities + +int JNI_forward_exception_to_gambit(JNIEnv*env) { + // TBD: actually forward, not only clear! + // + // TBD: Add location hint to API + if((*env)->ExceptionCheck(env)) { + LOGI("JNI_forward_exception_to_gambit"); + jthrowable ex = (*env)->ExceptionOccurred(env); + jclass cls = (*env)->GetObjectClass(env, ex); + jmethodID getMessage = (*env)->GetMethodID(env, cls, "getMessage","()Ljava/lang/String;"); + jstring message = (jstring)(*env)->CallObjectMethod(env, ex, getMessage); + const char *str = (*env)->GetStringUTFChars(env, message, NULL); + + // (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); // clears the exception in JVM + + // USE str WHILE VALID + + LOGE("JNI_forward_exception_to_gambit: %s", str); + + //* Release JNI resources + (*env)->ReleaseStringUTFChars(env, message, str); + (*env)->DeleteLocalRef(env, message); + (*env)->DeleteLocalRef(env, cls); + (*env)->DeleteLocalRef(env, ex); + //*/ + + return 1; + } + return 0; } -JNIEnv* GetJNIEnv(){ - int error=0; +JNIEnv* GetJNIEnv() { + // TBD: Add location hint to API + jint error=0; JNIEnv* env = NULL; - /* static `env` does NOT work! Once in a while we should ponder if - it still does not work or why. + /* TBD: static `env` does NOT work! Once in a while we should + ponder if it still does not work or why. As to the 'why': Gambit + was run from multiple threads before, which is at least tricky, + if not bound to fail. if(env) { if((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env); return env; } */ - if(s_vm) error=(*s_vm)->AttachCurrentThread(s_vm, &env, NULL); - if(!error) error = JNI_forward_exception_to_gambit(env); - return (error?NULL:env); + if(s_vm) { + // LOGI("GetJniEnv: check attached."); + // some say that despite AttachCurrentThread being a no-op, one + // may save overhead when checking first via GetEnv, so we do. + error = (*s_vm)->GetEnv(s_vm, (void**)&env, LAMBDANATIVE_JNI_VERSION); + if(error==JNI_EDETACHED) { + LOGI("Attaching thread to JVM\n"); + // error=(*s_vm)->AttachCurrentThreadAsDaemon(s_vm, &env, NULL); + error=(*s_vm)->AttachCurrentThread(s_vm, &env, NULL); + } + } + if(error!=JNI_OK) { + LOGE("GetJniEnv: failed to attach error %d.\n", error); + JNI_forward_exception_to_gambit(env); + return NULL; + } else { + return env; + } } -int JNI_forward_exception_to_gambit(JNIEnv*env) { - // TBD: actually forward, not only clear! - if((*env)->ExceptionCheck(env)) { - (*env)->ExceptionClear(env); - return 1; +// # OpenGL drawn from/within native thread. + +static jobject current_gl_state_object = NULL; // current, i.e., volatile +static int gl_state_start_required = 1; +static jmethodID gl_state_start = NULL; +static jmethodID gl_state_draw_init = NULL; +static jmethodID gl_state_on_draw_frame = NULL; +static jmethodID gl_state_swap_buffers = NULL; + +void Java_@SYS_PACKAGE_UNDERSCORE@_GLState_nativeInit(JNIEnv* env, jobject gl_state_class) { + // free old references + if(gl_state_start!=NULL) { + (*env)->DeleteGlobalRef(env, gl_state_start); + gl_state_start = NULL; + } + if(gl_state_draw_init!=NULL) { + (*env)->DeleteGlobalRef(env, gl_state_draw_init); + gl_state_draw_init = NULL; + } + if(gl_state_on_draw_frame!=NULL) { + (*env)->DeleteGlobalRef(env, gl_state_on_draw_frame); + gl_state_on_draw_frame = NULL; + } + if(gl_state_swap_buffers!=NULL) { + (*env)->DeleteGlobalRef(env, gl_state_swap_buffers); + gl_state_swap_buffers = NULL; + } + // rebuild + gl_state_start = (*env)->GetMethodID(env, gl_state_class, "fromNativeStart", "()V"); + LOGI("Java_@SYS_PACKAGE_UNDERSCORE@_GLState_nativeInit"); + if(gl_state_start==NULL) { + LOGE("Java_@SYS_PACKAGE_UNDERSCORE@_GLState_nativeInit failed to find fromNativeStart"); + // FatalError does not return + (*env)->FatalError(env, "@SYS_PACKAGE_UNDERSCORE@_GLState_nativeInit failed to find fromNativeStart"); + } + gl_state_draw_init = (*env)->GetMethodID(env, gl_state_class, "fromNativeInitDraw", "()V"); + if(gl_state_draw_init==NULL) { + (*env)->FatalError(env, "@SYS_PACKAGE_UNDERSCORE@_GLState_nativeInit failed to find fromNativeInitDraw"); + } + gl_state_on_draw_frame = (*env)->GetMethodID(env, gl_state_class, "fromNativeOnDrawFrame", "()V"); + if(gl_state_on_draw_frame==NULL) { + (*env)->FatalError(env, "@SYS_PACKAGE_UNDERSCORE@_GLState_nativeInit failed to find fromNativeOnDrawFrame"); + } + gl_state_swap_buffers = (*env)->GetMethodID(env, gl_state_class, "fromNativeSwapBuffers", "()V"); + if(gl_state_swap_buffers==NULL) { + (*env)->FatalError(env, "@SYS_PACKAGE_UNDERSCORE@_GLState_nativeInit failed to find fromNativeSwapBuffers"); } - return 0; +} + +void Java_@SYS_PACKAGE_UNDERSCORE@_GLState_nativeOnNewObject(JNIEnv* env, jobject obj) { + if(current_gl_state_object!=NULL) { + (*env)->DeleteGlobalRef(env, current_gl_state_object); + current_gl_state_object=NULL; + } + gl_state_start_required = 1; + current_gl_state_object = (*env)->NewGlobalRef(env, obj); +} + +void android_GLState_start() { + // LOGI("android_GLState_start"); + if(current_gl_state_object && gl_state_start_required) { + JNIEnv *env = GetJNIEnv(); + gl_state_start_required = 0; + (*env)->CallVoidMethod(env, current_gl_state_object, gl_state_start); + if(JNI_forward_exception_to_gambit(env)) { + LOGE("android_GLState_start gl_state_start failed"); + } + } else { + // LOGI("android_GLState_start already started"); + } +} + +void microgl_draw_before() { + // LOGI("microgl_draw_before.\n"); + JNIEnv *env = GetJNIEnv(); + if(env && current_gl_state_object && gl_state_draw_init) { + if(gl_state_start_required) { + gl_state_start_required = 0; + (*env)->CallVoidMethod(env, current_gl_state_object, gl_state_start); + if(JNI_forward_exception_to_gambit(env)) { + LOGE("microgl_draw_before gl_state_start failed"); + return; + } + } + (*env)->CallVoidMethod(env, current_gl_state_object, gl_state_draw_init); + // LOGI("microgl_draw_before returned"); + JNI_forward_exception_to_gambit(env); + } +} + +void microgl_swapbuffers() { + JNIEnv *env = GetJNIEnv(); + if(env && current_gl_state_object && gl_state_swap_buffers) { + // LOGI("microgl_swapbuffers do.\n"); + (*env)->CallVoidMethod(env, current_gl_state_object, gl_state_swap_buffers); + JNI_forward_exception_to_gambit(env); + } +} + +int microgl_fullscreen(int x, int y) { + JNIEnv *env = GetJNIEnv(); + return 1; // SUCCESS +} + +int microgl_window(int x, int y) { + return microgl_fullscreen(x, y); } // url launcher ffi void android_launch_url(char* urlstring){ JNIEnv *env = GetJNIEnv(); if (env&&globalObj) { - jstring jurlstring = (*env)->NewStringUTF(env,urlstring); - jclass cls = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); + jstring jurlstring = (*env)->NewStringUTF(env, urlstring); + jclass cls = main_class; jmethodID method = cls ? (*env)->GetMethodID(env, cls, "openURL", "(Ljava/lang/String;)V") : NULL; - if(method) { - (*env)->CallVoidMethod(env, globalObj, method, jurlstring); - (*env)->DeleteLocalRef(env, method); - } - if(jurlstring) (*env)->DeleteLocalRef(env, jurlstring); - if(cls) (*env)->DeleteLocalRef(env, cls); + if(method) (*env)->CallVoidMethod(env, globalObj, method, jurlstring); JNI_forward_exception_to_gambit(env); } } -// Add code here if needed for modules, such as GPS. +// # Add code here if needed for modules, such as GPS. @ANDROID_C_ADDITIONS@ diff --git a/loaders/android/bootstrap.java.in b/loaders/android/bootstrap.java.in index a34f209f..93655d68 100644 --- a/loaders/android/bootstrap.java.in +++ b/loaders/android/bootstrap.java.in @@ -48,7 +48,6 @@ import android.content.BroadcastReceiver; import android.content.res.Configuration; import android.content.pm.PackageManager; import android.content.pm.PackageInfo; -import android.opengl.GLSurfaceView; import android.os.Bundle; import android.os.PowerManager; import android.os.Handler; @@ -74,7 +73,19 @@ import android.hardware.SensorManager; @ANDROID_JAVA_ADDITIONS@ public class @SYS_APPNAME@ extends Activity implements @ANDROID_JAVA_IMPLEMENTS@ SensorEventListener{ - private static android.view.View current_ContentView = null; + private final static boolean LOG_LAYOUT = false; + /* + * To cache IDs when the activity class is loaded we use a class + * initializer to allow the native code to cache some field + * offsets. This native function looks up and caches interesting + * class/field/method IDs. Throws on failure, preventing start. + */ + private static native void nativeClassInit(); + static { + // NOT FOUND: WHY???: nativeClassInit(); + } + /* + */ private SensorManager mSensorManager; //Variable declarations needed for modules, e.g. gps @ANDROID_JAVA_VARIABLES@ @@ -153,25 +164,76 @@ public class @SYS_APPNAME@ extends Activity implements @ANDROID_JAVA_IMPLEMENTS@ } } + static android.view.ViewGroup topLayout = null; // used for setContentView + static NativeGLSurfaceView mGLView = null; // native opengl + // current_ContentView is what's exposed to the user, not (no + // longer) what's being fed to setcontentVeiw + private static android.view.View current_ContentView = null; + private void buildTopLayout() { + // Compose this layout; once only call; factored out + if(topLayout==null) { + if(LOG_LAYOUT) Log.i(TAG, "buildTopLayout building new"); + topLayout = new android.widget.LinearLayout(this); + topLayout.setLayoutParams + (new android.view.ViewGroup.LayoutParams + (android.view.ViewGroup.LayoutParams.FILL_PARENT, + android.view.ViewGroup.LayoutParams.FILL_PARENT)); + super.setContentView(topLayout); + } + if(mGLView==null) { + if(LOG_LAYOUT) Log.i(TAG, "Init GL"); + mGLView = new xGLSurfaceView(this); + if(LOG_LAYOUT) Log.i(TAG, "Adding mGLView"); + topLayout.addView(mGLView); + } + if(current_ContentView==null) { + current_ContentView=mGLView; + } + if(LOG_LAYOUT) Log.i(TAG, "buildTopLayout completed"); + } + @Override public void setContentView(android.view.View view) { - if(current_ContentView instanceof android.opengl.GLSurfaceView) { - ((android.opengl.GLSurfaceView)current_ContentView).onPause(); + // This setContentView manipulates private topLayout. + // + // Reason: deparenting the NativeGLSurfaceView causes lolcat "E + // SurfaceFlinger: Failed to find layer (SurfaceView - + // class.path.to.app) in layer parent (no-parent)." + // + // For mGLView we pause and set it invisible, others are added and + // removed instead. + if(LOG_LAYOUT) Log.i(TAG, "setContentView changing to " + view); + if(current_ContentView==mGLView) { + if(LOG_LAYOUT) Log.i(TAG, "setContentView pausing " + current_ContentView); + ((NativeGLSurfaceView)current_ContentView).onPause(); + current_ContentView.setVisibility(android.view.View.GONE); + } else { + topLayout.removeView(current_ContentView); } - android.view.ViewParent parent0 = view.getParent(); - if(parent0 instanceof android.view.ViewGroup) { - android.view.ViewGroup parent = (android.view.ViewGroup) parent0; - if(parent!=null) { - parent.removeView(current_ContentView); + if(view!=null && view!=mGLView) { + if(LOG_LAYOUT) Log.i(TAG, "setContentView clearing " + view); + android.view.ViewParent parent0 = view.getParent(); + if(parent0 instanceof android.view.ViewGroup) { + android.view.ViewGroup parent = (android.view.ViewGroup) parent0; + if(parent!=null) { + parent.removeView(current_ContentView); + } + } else if(parent0 != null) { + Log.e(TAG, "setContentView view " + view + " has unhandled parent " + parent0); } + topLayout.addView(view); } current_ContentView = view; - super.setContentView(current_ContentView); - if(current_ContentView instanceof android.opengl.GLSurfaceView) { - ((android.opengl.GLSurfaceView)current_ContentView).onResume(); + // TBD: chances are that other classes might need a resume call too. + if(current_ContentView==mGLView) { + if(LOG_LAYOUT) Log.i(TAG, "setContentView resuming " + current_ContentView); + current_ContentView.setVisibility(android.view.View.VISIBLE); + ((NativeGLSurfaceView)current_ContentView).onResume(); } + if(LOG_LAYOUT) Log.i(TAG, "setContentView now " + current_ContentView); } + private static boolean native_instance_is_up = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -192,19 +254,23 @@ public class @SYS_APPNAME@ extends Activity implements @ANDROID_JAVA_IMPLEMENTS@ WindowManager.LayoutParams.FLAG_FULLSCREEN); // prevent sleep getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - if(mGLView==null) { // once only! - mGLView = new xGLSurfaceView(this); - // This may better before other pieces + // get the permissions the `nativeInstanceInit` may require any + requestAllPermissions(); + if(!native_instance_is_up) { + native_instance_is_up=true; + // Log.i(TAG, "Init native, files: " + getFilesDir().toString()); nativeInstanceInit(getApplicationContext().getPackageCodePath().toString(), getFilesDir().toString()); + // Log.i(TAG, "Init native done"); } mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE); - requestAllPermissions(); - // MUST NOT run before nativeInstanceInit completed // and MUST NOT run before permission checks - setContentView(current_ContentView==null ? mGLView : current_ContentView); + if(LOG_LAYOUT) Log.i(TAG, "Setting content view"); + buildTopLayout(); + setContentView(current_ContentView); + if(LOG_LAYOUT) Log.i(TAG, "Set content view"); // Additions needed by modules, e.g. gps @ANDROID_JAVA_ONCREATE@ @@ -217,34 +283,30 @@ public class @SYS_APPNAME@ extends Activity implements @ANDROID_JAVA_IMPLEMENTS@ protected void onDestroy() { setContentView(mGLView); @ANDROID_JAVA_ONDESTROY@ - nativeEvent(14,0,0); // EVENT_CLOSE - nativeEvent(127,0,0); // EVENT_TERMINATE + nativeEvent(14, 0, 0); // EVENT_CLOSE + nativeEvent(128, 0, 0); // EVENT_TERMINATE super.onDestroy(); } - private void onPauseOrStop() { - // Additions needed by modules, e.g. gps - @ANDROID_JAVA_ONPAUSE@ - if (!isFinishing() && current_ContentView==mGLView && mGLView!=null) { - mGLView.onPause(); - } - } @Override protected void onStop() { Log.e("@SYS_PACKAGE_DOT@", "onStop"); - onPauseOrStop(); super.onStop(); } @Override protected void onPause() { - onPauseOrStop(); + // Additions needed by modules, e.g. gps + @ANDROID_JAVA_ONPAUSE@ + if (!isFinishing() && mGLView!=null) { + mGLView.onPause(); + } super.onPause(); } @Override protected void onResume() { - super.onResume(); - if(current_ContentView==mGLView && mGLView!=null) { + if(current_ContentView==mGLView) { mGLView.onResume(); } + super.onResume(); // Additions needed by modules, e.g. gps @ANDROID_JAVA_ONRESUME@ } @@ -266,8 +328,7 @@ public class @SYS_APPNAME@ extends Activity implements @ANDROID_JAVA_IMPLEMENTS@ @ANDROID_JAVA_ACTIVITYADDITIONS@ // Native event bindings - static GLSurfaceView mGLView = null; - native void nativeEvent(int t, int x, int y); + public native void nativeEvent(int t, int x, int y); static { System.loadLibrary("payloadshared"); } // OpenURL code void openURL(String url) { @@ -284,16 +345,24 @@ public class @SYS_APPNAME@ extends Activity implements @ANDROID_JAVA_IMPLEMENTS@ native void nativeInstanceInit(String packageCodePath, String filesDir); } -class xGLSurfaceView extends GLSurfaceView { - public xGLSurfaceView(Context context) { +class xGLSurfaceView extends NativeGLSurfaceView { + public xGLSurfaceView(Activity context) { super(context); setFocusable(true); setFocusableInTouchMode(true); - renderer = new myRenderer(); + renderer = new myRenderer(context); setRenderer(renderer); } + public void queueNativeEvent(Runnable r) { + r.run(); + } + public void requestRender() { + // super.requestRender(); + renderer.nativeEvent(15, 0, 0); // EVENT_REDRAW + } public boolean onTouchEvent(final MotionEvent event) { super.onTouchEvent(event); + // Log.i("xGLSurfaceView", "onTouchEvent " + event); t=0; x=(int)event.getX(); y=(int)event.getY(); switch (event.getAction()&MotionEvent.ACTION_MASK) { @@ -310,15 +379,15 @@ class xGLSurfaceView extends GLSurfaceView { final int x0=(int)event.getX(0); final int y0=(int)event.getY(0); if (n>1) { // MultiTouch - queueEvent(new Runnable(){ public void run() { renderer.pointerEvent(18,id0,0); }}); + renderer.pointerEvent(18,id0,0); } - queueEvent(new Runnable(){ public void run() { renderer.pointerEvent(t0,x0,y0); }}); + renderer.pointerEvent(t0,x0,y0); if (n>1) { // MultiTouch final int id1=event.getPointerId(1); final int x1=(int)event.getX(1); final int y1=(int)event.getY(1); - queueEvent(new Runnable(){ public void run() { renderer.pointerEvent(18,id1,0); }}); - queueEvent(new Runnable(){ public void run() { renderer.pointerEvent(t0,x1,y1); }}); + renderer.pointerEvent(18, id1, 0); + renderer.pointerEvent(t0, x1, y1); } } return true; @@ -336,8 +405,7 @@ class xGLSurfaceView extends GLSurfaceView { if (asciiKey > 0) { t=type; x=asciiKey; - queueEvent(new Runnable(){ public void run() { - renderer.nativeEvent(t,x,y); }}); + renderer.nativeEvent(t, x, y); return true; } // Get everything else @@ -356,34 +424,60 @@ class xGLSurfaceView extends GLSurfaceView { case KeyEvent.KEYCODE_SOFT_LEFT: return true; } if (t>0) { - queueEvent(new Runnable(){ public void run() { - renderer.nativeEvent(t,x,y); }}); + renderer.nativeEvent(t, x, y); } return true; } + int t, x, y; + myRenderer renderer; +} +class myRenderer implements NativeGLSurfaceView.Renderer { + final String TAG = "@SYS_PACKAGE_DOT@/NativeGLSurfaceView.Renderer"; + private Activity mContext = null; + public myRenderer(Activity context) { + mContext = context; + } + public void queueNativeEvent(Runnable r) { mContext.runOnUiThread(r); } + public void start() { + // Note the exception: MUST wait for native thread to attach. + nativeEvent(127, 0, 0); // EVENT_INIT + } public void onPause() { - super.onPause(); - renderer.nativeEvent(16,0,0); // EVENT_SUSPEND + nativeEvent(16, 0, 0); // EVENT_SUSPEND } public void onResume() { - super.onResume(); - renderer.nativeEvent(17,0,0); // EVENT_RESUME + nativeEvent(17, 0, 0); // EVENT_RESUME } - int t,x,y; - myRenderer renderer; -} -class myRenderer implements GLSurfaceView.Renderer { - public void onSurfaceCreated(GL10 gl, EGLConfig config) { - } - public void onSurfaceChanged(GL10 gl, int w, int h) { - gl.glViewport(0, 0, w, h); - width=(float)w; height=(float)h; - nativeEvent(127,w,h); // EVENT_INIT + public void onWindowResize(int w, int h) { + // Log.i(TAG, "onWindowResize sending EVENT_INIT"); + nativeEvent(127, w, h); // EVENT_INIT + // Log.i(TAG, "onWindowResize sending EVENT_REDRAW"); + nativeEvent(15, 0, 0); // EVENT_REDRAW + // Log.i(TAG, "onWindowResize completed"); + } + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + // Log.i(TAG, "surfaceCreated"); + if(gl==null && config==null) { + // Log.i(TAG, "surfaceCreated sending EVENT_RESUME"); + nativeEvent(17, 0, 0); // EVENT_RESUME } - public void onDrawFrame(GL10 gl) { - nativeEvent(15,0,0); // EVENT_REDRAW - } - public void pointerEvent(int t, int x, int y) { nativeEvent(t,x,(int)height-y); } - public float width,height; - native void nativeEvent(int t,int x,int y); + } + public void onSurfaceChanged(GL10 gl, final int w, final int h) { + gl.glViewport(0, 0, w, h); + width=(float)w; height=(float)h; + queueNativeEvent(new Runnable(){ public void run() { + nativeEvent(127, w, h); // EVENT_INIT + }}); + } + public void onDrawFrame(GL10 gl) { + // nativeEvent(15, 0, 0); // EVENT_REDRAW + } + public void onRedrawNeeded() { + nativeEvent(15, 0, 0); // EVENT_REDRAW + } + public void pointerEvent(final int t, final int x, final int y) { + nativeEvent(t, x, (int)height-y); + } + public float width,height; + native void nativeEvent(int t,int x,int y); } diff --git a/loaders/common/main.c b/loaders/common/main.c.in similarity index 79% rename from loaders/common/main.c rename to loaders/common/main.c.in index 6697fe46..e7ce31b3 100644 --- a/loaders/common/main.c +++ b/loaders/common/main.c.in @@ -62,7 +62,7 @@ static int run_flag=1; static int int_flag=0; // signal handler -void signal_hook() +void signal_hook(int sig) { run_flag=0; int_flag=1; @@ -82,17 +82,6 @@ void signal_hook() void microgl_hook(int t, int x, int y) { switch (t) { - case EVENT_REDRAW: - glClearColor(0.0, 0.0, 0.0, 0.0); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0.,scm_width(),0.,scm_height(),-1.,1.); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glClear(GL_COLOR_BUFFER_BIT); - ffi_event(EVENT_REDRAW,0,0); - microgl_swapbuffers(); - break; case EVENT_CLOSE: ffi_event(EVENT_CLOSE,0,0); run_flag=0; @@ -108,26 +97,53 @@ void microgl_hook(int t, int x, int y) int cmd_argc=0; char **cmd_argv; +#ifdef __cplusplus +extern "C" { +#endif +@MAIN_c_additions@ +#ifdef __cplusplus +} +#endif +typedef struct subcommand_def { + const char* name; + void (*main)(int, const char*argv[]); +} subcommand_def_t; int main(int argc, char *argv[]) { int w=0,h=0; + subcommand_def_t subcmd[] = + { + @MAIN_subcommand_defines@ + {NULL, NULL} + }; + + if(argc>2 && strcmp(argv[1], "-s")==0) { + int i=0; + for(;subcmd[i].name!=NULL; ++i) { + if(strcmp(argv[2], subcmd[i].name)==0) { + subcmd[i].main(argc-2, argv+2); + exit(23); // subcmd SHOULD NOT return + } + } + } cmd_argc=argc; cmd_argv=argv; // fork to release terminal (for starting processes on embedded systems) #if defined(USECONSOLE) && defined(OPENBSD) - signal(SIGHUP,SIG_IGN); - signal(SIGTERM,SIG_IGN); - signal(SIGCHLD,SIG_IGN); + signal(SIGHUP, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGCHLD, SIG_IGN); if(fork() != 0) return 0; chdir("/"); setsid(); umask(0); if(fork() != 0) return 0; - { int fd = open("/tmp/stdin",O_RDONLY|O_CREAT); - dup2(fd,STDIN_FILENO); - fd = open("/tmp/stdout",O_WRONLY|O_CREAT); - dup2(fd,STDOUT_FILENO); - fd = open("/tmp/stderr",O_WRONLY|O_CREAT); - dup2(fd,STDERR_FILENO); + { int fd = open("/tmp/stdin", O_RDONLY|O_CREAT); + dup2(fd, STDIN_FILENO); + fd = open("/tmp/stdout", O_WRONLY|O_CREAT); + dup2(fd, STDOUT_FILENO); + fd = open("/tmp/stderr", O_WRONLY|O_CREAT); + dup2(fd, STDERR_FILENO); + close(fd); } #endif @@ -151,17 +167,6 @@ int main(int argc, char *argv[]) signal(SIGTERM,signal_hook); signal(SIGINT,signal_hook); -#ifndef USECONSOLE - - // open a window - if ((w==scm_width()&&h==scm_height())||scm_forcefullscreen()) { - microgl_fullscreen(w,h); - } else { - microgl_window(scm_width(),scm_height()); - } - -#endif // USECONSOLE - while (run_flag) { // check for application exit if (run_flag) run_flag=scm_runflag(); @@ -170,8 +175,6 @@ int main(int argc, char *argv[]) // check for events microgl_pollevents(); - // ask for a redraw - microgl_refresh(); #else diff --git a/loaders/hook/hook.c b/loaders/hook/hook.c deleted file mode 100644 index 27e1787b..00000000 --- a/loaders/hook/hook.c +++ /dev/null @@ -1,74 +0,0 @@ -// lambdanative hook - -#include -#include - -#include - -// --------------- -// lambdanative payload bootstrap - -#ifndef PAYLOADONLY - -#include - -void lambdanative_payload_setup(); -void lambdanative_payload_cleanup(); -void lambdanative_payload_event(int,int,int); - -void lambdanative_exit(int code) -{ - lambdanative_payload_cleanup(); - exit(code); -} -#ifdef STANDALONE -// standalone setup -char **cmd_argv; -int cmd_argc=0; -int main(int argc, char *argv[]) -{ - cmd_argc=argc; cmd_argv=argv; - lambdanative_payload_setup(); - lambdanative_payload_cleanup(); - return 0; -} -#else -// event loop setup -#if defined(ANDROID) || defined(MACOSX) || defined(IOS) || defined(LINUX) || defined(OPENBSD) || defined(BB10) || defined(PLAYBOOK) || defined(NETBSD) - #include - pthread_mutex_t ffi_event_lock; - #define FFI_EVENT_INIT pthread_mutex_init(&ffi_event_lock, 0); - #define FFI_EVENT_LOCK pthread_mutex_lock( &ffi_event_lock); - #define FFI_EVENT_UNLOCK pthread_mutex_unlock( &ffi_event_lock); -#else - #ifdef WIN32 - #include - CRITICAL_SECTION ffi_event_cs; - #define FFI_EVENT_INIT InitializeCriticalSection(&ffi_event_cs); - #define FFI_EVENT_LOCK EnterCriticalSection(&ffi_event_cs); - #define FFI_EVENT_UNLOCK LeaveCriticalSection( &ffi_event_cs); - #else - static int ffi_event_lock; - #define FFI_EVENT_INIT ffi_event_lock=0; - #define FFI_EVENT_LOCK { while (ffi_event_lock) { }; ffi_event_lock=1; } - #define FFI_EVENT_UNLOCK ffi_event_lock=0; - #endif -#endif -void ffi_event(int t, int x, int y) -{ - static int lambdanative_needsinit=1; - if (lambdanative_needsinit) { - lambdanative_payload_setup(); - FFI_EVENT_INIT - lambdanative_needsinit=0; - } - FFI_EVENT_LOCK - if (!lambdanative_needsinit&&t) lambdanative_payload_event(t,x,y); - if (t==EVENT_TERMINATE) { lambdanative_exit(0); } - FFI_EVENT_UNLOCK -} -#endif // STANDALONE - -#endif // PAYLOADONLY - -// eof diff --git a/loaders/hook/hook.c.in b/loaders/hook/hook.c.in new file mode 100644 index 00000000..68204aff --- /dev/null +++ b/loaders/hook/hook.c.in @@ -0,0 +1,173 @@ +// lambdanative hook +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include /* Obtain O_* constant definitions */ +#include + +#include +#include + +#include + +// --------------- +// lambdanative payload bootstrap + +#ifndef PAYLOADONLY + +#include + +void lambdanative_payload_setup(); +void lambdanative_payload_cleanup(); +void lambdanative_payload_event(int,int,int); + +void lambdanative_exit(int code) +{ + lambdanative_payload_cleanup(); + exit(code); +} + +#ifdef __cplusplus +extern "C" { +#endif +@MAIN_c_additions@ +#ifdef __cplusplus +} +#endif +typedef struct subcommand_def { + const char* name; + void (*main)(int, const char*argv[]); +} subcommand_def_t; + +#ifdef STANDALONE +// standalone setup +char **cmd_argv; +int cmd_argc=0; +int main(int argc, char *argv[]) +{ + subcommand_def_t subcmd[] = + { + @MAIN_subcommand_defines@ + {NULL, NULL} + }; + + if(argc>2 && strcmp(argv[1], "-s")==0) { + int i=0; + for(;subcmd[i].name!=NULL; ++i) { + if(strcmp(argv[2], subcmd[i].name)==0) { + subcmd[i].main(argc-2, argv+2); + exit(23); // subcmd SHOULD NOT return + } + } + } + + cmd_argc=argc; cmd_argv=argv; + lambdanative_payload_setup(); + lambdanative_payload_cleanup(); + return 0; +} +#else +// event loop setup +#if defined(ANDROID) || defined(MACOSX) || defined(IOS) || defined(LINUX) || defined(OPENBSD) || defined(BB10) || defined(PLAYBOOK) || defined(NETBSD) + #define _USE_PTHREAD_FOR_EVENT_LOOP_ 1 + #include + pthread_mutex_t ffi_event_lock; + #define FFI_EVENT_INIT pthread_mutex_init(&ffi_event_lock, 0); + #define FFI_EVENT_LOCK pthread_mutex_lock( &ffi_event_lock); + #define FFI_EVENT_UNLOCK pthread_mutex_unlock( &ffi_event_lock); +#else + #ifdef WIN32 + #define _USE_PTHREAD_FOR_EVENT_LOOP_ 0 + #include + CRITICAL_SECTION ffi_event_cs; + #define FFI_EVENT_INIT InitializeCriticalSection(&ffi_event_cs); + #define FFI_EVENT_LOCK EnterCriticalSection(&ffi_event_cs); + #define FFI_EVENT_UNLOCK LeaveCriticalSection( &ffi_event_cs); + #include + #else + static int ffi_event_lock; + #define FFI_EVENT_INIT ffi_event_lock=0; + #define FFI_EVENT_LOCK { while (ffi_event_lock) { }; ffi_event_lock=1; } + #define FFI_EVENT_UNLOCK ffi_event_lock=0; + #endif +#endif + +#if _USE_PTHREAD_FOR_EVENT_LOOP_ +/* Note: This is a nice place for extensions. Alternative + * implementations could run any other code instead of a gambit + * program. + */ +static void* gambit_pthread_job(void *arg) { + ffi_event_params_t* args = (ffi_event_params_t*) arg; + lambdanative_payload_setup(); + // fprintf(stderr, "delivering inital event %d %d %d\n", args->t, args->x, args->y); + scm_event(args->t, args->x, args->y); + return NULL; +} +#endif + +void ln_gambit_lock() +{ + FFI_EVENT_LOCK +} +void ln_gambit_unlock() +{ + // fprintf(stderr, "unblocking main\n"); + FFI_EVENT_UNLOCK +} +ffi_event_params_t ffi_event_params; +void ffi_event(int t, int x, int y) +{ + static int lambdanative_needsinit=1; + ffi_event_params.t=t; ffi_event_params.x=x; ffi_event_params.y=y; + //fprintf(stderr, "F"); + #if _USE_PTHREAD_FOR_EVENT_LOOP_ + #ifdef WIN32 + // not a good place, but where should it go? + #define pipe(x) _pipe(x, 256, _O_BINARY) + #endif + if(t == EVENT_INIT && lambdanative_needsinit) { + pthread_t gambit_kernel; + pthread_attr_t attr; + FFI_EVENT_INIT + lambdanative_needsinit=0; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + FFI_EVENT_LOCK + if(pipe(ffi_event_params.fd)) { + fprintf(stderr, "pipe failed"); exit(1); + } + int res = pthread_create(&gambit_kernel, &attr, gambit_pthread_job, (void*)&ffi_event_params); + if(res!=0) exit(res); + FFI_EVENT_LOCK + FFI_EVENT_UNLOCK + // fprintf(stderr, "initial EVENT_INIT processed\n"); + return; + } else { + //fprintf(stderr, "f"); + FFI_EVENT_LOCK + write(ffi_event_params.fd[1], &ffi_event_params.t, 1); + // fsync(ffi_event_params.fd[1]); + // fprintf(stderr, "delivered event %d %d %d\n", ffi_event_params.t, ffi_event_params.x, ffi_event_params.y); + FFI_EVENT_LOCK + if (t==EVENT_TERMINATE) { lambdanative_exit(0); } + FFI_EVENT_UNLOCK + //fprintf(stderr, "ffi_event return\n"); + return; + } + #else + if (lambdanative_needsinit) { + FFI_EVENT_INIT + lambdanative_needsinit=0; + lambdanative_payload_setup(); + } + FFI_EVENT_LOCK + if (!lambdanative_needsinit&&t) lambdanative_payload_event(t,x,y); + if (t==EVENT_TERMINATE) { lambdanative_exit(0); } + FFI_EVENT_UNLOCK + fprintf(stderr, "ffi_event return\n"); + #endif +} +#endif // STANDALONE + +#endif // PAYLOADONLY + +// eof diff --git a/loaders/win32/win32_microgl.c b/loaders/win32/win32_microgl.c index 6d40dbc0..ae7e4615 100644 --- a/loaders/win32/win32_microgl.c +++ b/loaders/win32/win32_microgl.c @@ -37,6 +37,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // simple win32 GL window interface +// FIXME 2021-04-25: As Windows are bound to the thread, which created +// it, the microgl_pollevents would needs to be called from the +// eventloop and the microgl_hook under Windows simply needs to report +// the events. Eventually that's going to be a bit tricky, as it puts +// us back into the situation, where we are already in a +// Scheme-C-Scheme calling situation, which is better avoided under +// Gambit. + #include #include #include @@ -95,6 +103,8 @@ static int _microgl_key(WPARAM wParam, LPARAM lParam, int modifier, int action) case VK_DOWN: return EVENT_KEYDOWN; case VK_HOME: return EVENT_KEYHOME; case VK_END: return EVENT_KEYEND; + case VK_PRIOR: return 0xff55; + case VK_NEXT: return 0xff56; default: // if CTRL is down, ToAscii puts ^A to ^Z (0x01 to 0x1a) in char_buf // if ALT is down, ToAscii puts a to z (lowercase) in char_buf @@ -123,7 +133,7 @@ static int _microgl_key(WPARAM wParam, LPARAM lParam, int modifier, int action) // timer callback static VOID CALLBACK event_timer_callback(HWND hWnd, UINT uMsg, UINT idTimer, DWORD dwTime){ - ffi_event(EVENT_IDLE,0,0); + //ffi_event(EVENT_IDLE,0,0); } // window event callback @@ -237,7 +247,7 @@ void microgl_init(void) // 20100729: we need to fix this icon business... // 20100729: note that the app dies mysteriously without the icon??? wc.hIcon = LoadIcon(microglInstance, MAKEINTRESOURCE(AppIcon)); - wc.hIconSm = LoadImage(microglInstance,MAKEINTRESOURCE(AppIcon),IMAGE_ICON,16,16, LR_DEFAULTCOLOR); + wc.hIconSm = (HICON)LoadImage(microglInstance, MAKEINTRESOURCE(AppIcon), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR); // wc.hIcon = LoadIcon( NULL, IDI_APPLICATION); // wc.hIconSm = LoadIcon( NULL, IDI_APPLICATION); @@ -353,10 +363,21 @@ void microgl_close() void microgl_pollevents(void) { MSG Msg; - while( PeekMessage( &Msg, NULL, 0, 0, PM_REMOVE ) ) { DispatchMessage( &Msg ); } -// if (GetMessage(&Msg, NULL, 0, 0) > 0) { -// TranslateMessage(&Msg); DispatchMessage(&Msg); -// } + #if 1 + // PeekMessage does not block for message + while( PeekMessage( &Msg, NULL, 0, 0, PM_REMOVE ) ) { + DispatchMessage( &Msg ); + } + microgl_refresh(); + ffi_event(EVENT_IDLE, 0, 0); + // SleepEx(25, 1); // milliseconds + #else + // GetMessage waits until a message is received + if( GetMessage(&Msg, NULL, 0, 0) > 0 ) { + TranslateMessage(&Msg); + DispatchMessage(&Msg); + } + #endif } int microgl_screenwidth() diff --git a/loaders/x11/x11_microgl.c b/loaders/x11/x11_microgl.c index cd70d8b5..e39b401b 100644 --- a/loaders/x11/x11_microgl.c +++ b/loaders/x11/x11_microgl.c @@ -46,6 +46,8 @@ extern "C" { #include #include #include +#include +#include #include #include @@ -89,7 +91,7 @@ typedef struct microglWindow { Atom WMDeleteWindow; int Scrn; int w,h; - int mouse_x, mouse_y; + int mouse_x, mouse_y; int KeyboardGrabbed, PointerGrabbed; } microglWindow; @@ -111,8 +113,38 @@ void microgl_refresh() event.type = Expose; event.xany.window = win.Win; XSendEvent(Dpy, win.Win, False, Expose, &event); + XSync(Dpy, 0); } +static +Bool _microgl_wait_redraw_required(Bool enforced) { + // Arrange pending check and process suspension. + // + // TBD: instead of a fixed wait, the Gambit site could get a handle + // to interrupt this sleep here. + static struct timeval next_draw = {0, 0}; + struct timeval now; + time_t to = 0; + gettimeofday(&now, NULL); + to = (now.tv_sec > next_draw.tv_sec) || + (now.tv_sec == next_draw.tv_sec) && (now.tv_usec > next_draw.tv_usec); + if(enforced || to ) { // process expose events + to = now.tv_usec + microgl_redraw_period(0); + if(to>=1000000) { + next_draw.tv_usec = to % 1000000; + next_draw.tv_sec = now.tv_sec + (to / 1000000); + } else { + next_draw.tv_usec = to; + next_draw.tv_sec = now.tv_sec; + } + return True; // redraw required + } else { + usleep(25000); // TBD do not hard code + return False; // no redraw, just re-check X events + } +} + + // https://tronche.com/gui/x/xlib/events/keyboard-pointer/keyboard-pointer.html int _microgl_modifier(XKeyEvent *event) { unsigned int state = event->state; @@ -139,6 +171,9 @@ int _microgl_key( XKeyEvent *event ) case XK_Right: return EVENT_KEYRIGHT; case XK_Down: return EVENT_KEYDOWN; case XK_Up: return EVENT_KEYUP; + case XK_Page_Up: /* 0xff55 */ + case XK_Page_Down: /* 0xff56 */ + return keysym; } // Printable chars (Latin 1) if( (keysym >= 0x0020 && keysym <= 0x007e) || // Basic Latin 1 charset @@ -180,7 +215,7 @@ void _microgl_sendCopyStringEvent(XSelectionRequestEvent* selReqEv) { .time = CurrentTime }; if (copiedString && selReqEv->target == format && selReqEv->property != None) { - XChangeProperty(Dpy, selReqEv->requestor, selReqEv->property, format, 8, PropModeReplace, copiedString, copiedStringLen + 1); + XChangeProperty(Dpy, selReqEv->requestor, selReqEv->property, format, 8, PropModeReplace, copiedString, copiedStringLen); } else { selEv.property = None; } @@ -190,8 +225,9 @@ void _microgl_sendCopyStringEvent(XSelectionRequestEvent* selReqEv) { void microgl_pollevents(void) { XEvent event; - int expose=0; - int motion=0; + // Some events are summarised in order to reduce load. + int expose=0; // summarised + int motion=0; // summarised while( XPending( Dpy ) ) { XNextEvent( Dpy, &event ); @@ -205,38 +241,38 @@ void microgl_pollevents(void) case ButtonPress: switch (event.xbutton.button) { case Button1: - win.mouse_x = event.xbutton.x; + win.mouse_x = event.xbutton.x; win.mouse_y = win.h - event.xbutton.y - 1; - microgl_hook(EVENT_BUTTON1DOWN,win.mouse_x, win.mouse_y); + microgl_hook(EVENT_BUTTON1DOWN,win.mouse_x, win.mouse_y); break; case Button2: - win.mouse_x = event.xbutton.x; + win.mouse_x = event.xbutton.x; win.mouse_y = win.h - event.xbutton.y - 1; - microgl_hook(EVENT_BUTTON2DOWN, win.mouse_x, win.mouse_y); + microgl_hook(EVENT_BUTTON2DOWN, win.mouse_x, win.mouse_y); break; case Button3: - win.mouse_x = event.xbutton.x; + win.mouse_x = event.xbutton.x; win.mouse_y = win.h - event.xbutton.y - 1; - microgl_hook(EVENT_BUTTON3DOWN, win.mouse_x, win.mouse_y); + microgl_hook(EVENT_BUTTON3DOWN, win.mouse_x, win.mouse_y); break; } break; case ButtonRelease: switch (event.xbutton.button) { case Button1: - win.mouse_x = event.xbutton.x; + win.mouse_x = event.xbutton.x; win.mouse_y = win.h - event.xbutton.y - 1; - microgl_hook(EVENT_BUTTON1UP, win.mouse_x, win.mouse_y); + microgl_hook(EVENT_BUTTON1UP, win.mouse_x, win.mouse_y); break; case Button2: - win.mouse_x = event.xbutton.x; + win.mouse_x = event.xbutton.x; win.mouse_y = win.h - event.xbutton.y - 1; - microgl_hook(EVENT_BUTTON2UP, win.mouse_x, win.mouse_y); + microgl_hook(EVENT_BUTTON2UP, win.mouse_x, win.mouse_y); break; case Button3: - win.mouse_x = event.xbutton.x; + win.mouse_x = event.xbutton.x; win.mouse_y = win.h - event.xbutton.y - 1; - microgl_hook(EVENT_BUTTON3UP, win.mouse_x, win.mouse_y); + microgl_hook(EVENT_BUTTON3UP, win.mouse_x, win.mouse_y); break; } break; @@ -246,18 +282,23 @@ void microgl_pollevents(void) motion=1; break; case Expose: - expose=1; + expose = expose || (event.xexpose.width && event.xexpose.height) ? 1 : 0; break; - case ClientMessage: + case ClientMessage: if( (Atom) event.xclient.data.l[ 0 ] == win.WMDeleteWindow ) - microgl_hook(EVENT_CLOSE,0,0); - return; + microgl_hook(EVENT_CLOSE, 0, 0); + return; case DestroyNotify: - microgl_hook(EVENT_CLOSE,0,0); + microgl_hook(EVENT_CLOSE, 0, 0); return; case ConfigureNotify: - if( event.xconfigure.width != win.w || event.xconfigure.height != win.h ) - XResizeWindow( Dpy, win.Win, win.w, win.h); + if( event.xconfigure.width != win.w || event.xconfigure.height != win.h ) { + // That's the wrong thing to do: XResizeWindow( Dpy, win.Win, win.w, win.h); + // Just this does not help either! + win.w = event.xconfigure.width; + win.h = event.xconfigure.height; + microgl_hook(EVENT_INIT, win.w, win.h); + } break; case SelectionClear: if (copiedString) free(copiedString); @@ -265,17 +306,23 @@ void microgl_pollevents(void) case SelectionRequest: _microgl_sendCopyStringEvent((XSelectionRequestEvent*) &event.xselectionrequest); break; - } + } } // Xpending - if (expose) { // process expose events - microgl_hook(EVENT_REDRAW,0,0); - } + // Immediate events done. Handle summarized events. if (motion) { // process motion events microgl_hook(EVENT_MOTION, win.mouse_x, win.mouse_y); } + // Eventually update user interface. + if( _microgl_wait_redraw_required(expose) ) { + // _microgl_redraw_required arranges pending check and process + // suspension + microgl_hook(EVENT_REDRAW, 0, 0); + } + // All done for this round of event polling, return expected to call + // as soon as it assumes reasonable for another turn. } Bool _microglWaitForMapNotify( Display *d, XEvent *e, char *arg ) @@ -293,13 +340,13 @@ int microgl_open(int w, int h, int fs) // step 1: create the window wa.event_mask=ExposureMask|KeyPressMask|KeyReleaseMask|ButtonPressMask|ButtonReleaseMask; - if (!fs) wa.event_mask|= PointerMotionMask | StructureNotifyMask| ExposureMask | FocusChangeMask | VisibilityChangeMask; + if (!fs) wa.event_mask|= PointerMotionMask | StructureNotifyMask| ExposureMask | FocusChangeMask | VisibilityChangeMask; win.Win=XCreateWindow(Dpy,DefaultRootWindow(Dpy), 0,0,w,h,0,CopyFromParent,InputOutput, CopyFromParent,CWEventMask,&wa); // step 2: fullscreen tweaks - if (fs) { + if (fs) { int success=0; Atom atom; @@ -313,7 +360,7 @@ int microgl_open(int w, int h, int fs) long input_mode; unsigned long status; } MWMHints = { MWM_HINTS_DECORATIONS, 0, 0, 0, 0 }; - XChangeProperty( Dpy, win.Win, atom, atom, 32, + XChangeProperty( Dpy, win.Win, atom, atom, 32, PropModeReplace, (unsigned char *)&MWMHints, sizeof(MWMHints)/4 ); sucess=1; } @@ -321,7 +368,7 @@ int microgl_open(int w, int h, int fs) atom = XInternAtom( Dpy, "KWM_WIN_DECORATION", True ); if ( atom!= None ) { long KWMHints = KDE_tinyDecoration; - XChangeProperty( Dpy, win.Win, atom, atom, 32, + XChangeProperty( Dpy, win.Win, atom, atom, 32, PropModeReplace, (unsigned char *)&KWMHints, sizeof(KWMHints)/4 ); success= 1; } @@ -330,53 +377,54 @@ int microgl_open(int w, int h, int fs) atom= XInternAtom(Dpy,"_WIN_HINTS",True); if ( atom != None ) { long GNOMEHints = 0; - XChangeProperty( Dpy, win.Win, atom, atom, 32, + XChangeProperty( Dpy, win.Win, atom, atom, 32, PropModeReplace, (unsigned char *)&GNOMEHints, sizeof(GNOMEHints)/4 ); success = 1; } - + atom = XInternAtom( Dpy, "_NET_WM_WINDOW_TYPE", True ); if ( atom != None ) { Atom NET_WMHints[2]; NET_WMHints[0] = XInternAtom( Dpy, "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE", True ); NET_WMHints[1] = XInternAtom( Dpy, "_NET_WM_WINDOW_TYPE_NORMAL", True ); - XChangeProperty( Dpy, win.Win, atom, XA_ATOM, 32, + XChangeProperty( Dpy, win.Win, atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&NET_WMHints, 2 ); success = 1; } - atom=XInternAtom(Dpy,"_NET_WM_STATE",True); + atom=XInternAtom(Dpy,"_NET_WM_STATE",True); if (atom!=None) { - Atom NET_WMHints[1]; + Atom NET_WMHints[2]; NET_WMHints[0] = XInternAtom( Dpy, "_NET_WM_STATE_FULLSCREEN",True); - XChangeProperty(Dpy,win.Win,atom, XA_ATOM, 32, - PropModeReplace, (unsigned char*)&NET_WMHints, 1); + NET_WMHints[1] = XInternAtom( Dpy, "_NET_WM_STATE_ABOVE",True); // does not help + XChangeProperty(Dpy,win.Win,atom, XA_ATOM, 32, + PropModeReplace, (unsigned char*)&NET_WMHints, 2); success=1; } atom=XInternAtom(Dpy,"_HILDON_NON_COMPOSITED_WINDOW",True); if (atom!=None) { int one=1; - XChangeProperty(Dpy,win.Win,atom, XA_INTEGER, 32, - PropModeReplace, (unsigned char*)&one, 1); + XChangeProperty(Dpy,win.Win,atom, XA_INTEGER, 32, + PropModeReplace, (unsigned char*)&one, 1); success=1; } // Hildon run in landscape mode by default // this is opposite of other mobiles ! -/* +/* atom=XInternAtom(Dpy,"_HILDON_PORTRAIT_MODE_SUPPORT",True); if (atom!=None) { long one=1; XChangeProperty(Dpy,win.Win,atom, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&one, 1); - } + } atom=XInternAtom(Dpy,"_HILDON_PORTRAIT_MODE_REQUEST",True); - if (atom!=None) { + if (atom!=None) { long one=1; XChangeProperty(Dpy,win.Win,atom, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&one, 1); - } + } */ if (success) { @@ -408,7 +456,7 @@ int microgl_open(int w, int h, int fs) #endif #ifdef USE_EGL - { + { EGLConfig ecfg; EGLint num_config; // EGLint attr[]={EGL_BUFFER_SIZE,16,EGL_RENDERABLE_TYPE,EGL_OPENGL_ES2_BIT, EGL_NONE}; @@ -424,7 +472,7 @@ int microgl_open(int w, int h, int fs) if (win.egl_surface==EGL_NO_SURFACE) { printf("ERROR 5\n"); return 0; } win.egl_context=eglCreateContext(win.egl_display, ecfg, EGL_NO_CONTEXT, ctxattr); if (win.egl_context==EGL_NO_CONTEXT) { printf("ERROR 6\n"); return 0; } - eglMakeCurrent(win.egl_display,win.egl_surface,win.egl_surface,win.egl_context); + eglMakeCurrent(win.egl_display,win.egl_surface,win.egl_surface,win.egl_context); } #endif @@ -466,7 +514,7 @@ int microgl_open(int w, int h, int fs) glClearColor(0,0,0,0); glClear( GL_COLOR_BUFFER_BIT ); microgl_swapbuffers(); - + return 1; } @@ -486,7 +534,7 @@ void microgl_close() #endif if (win.KeyboardGrabbed) { XUngrabKeyboard(Dpy,CurrentTime); - } + } if (win.PointerGrabbed) { XUngrabPointer(Dpy, CurrentTime); } diff --git a/make.sh b/make.sh index 3b48f374..64710109 100755 --- a/make.sh +++ b/make.sh @@ -340,6 +340,13 @@ filter_entries() ########################### # general compiler functions +startup_subst() +{ + d=$1 + ac_subst MAIN_c_additions "@$d/MAIN_c_additions" + ac_subst MAIN_subcommand_defines "@$d/MAIN_subcommand_defines" +} + compile_payload() { dmsg_make "entering compile_payload [$@]" @@ -378,7 +385,13 @@ compile_payload() hookhash=`stringhash "apps/$SYS_APPNAME/hook.c"` hctgt="$SYS_PREFIX/build/$hookhash.c" hotgt=`echo "$hctgt" | sed 's/c$/o/'` - cp loaders/hook/hook.c "$hctgt" + rmifexists "$hotgt" + for m in $modules; do + modpath=`locatedir modules/$m silent` + startup_subst $modpath + done + startup_subst `locatedir apps/$SYS_APPNAME` + ac_output loaders/hook/hook.c "$hctgt" veval "$SYS_ENV $SYS_CC $payload_cdefs $languages_def -c -o $hotgt $hctgt -I$SYS_PREFIX/include" assertfile $hotgt payload_objs="$payload_objs $hotgt" diff --git a/modules/audio/ANDROID_c_additions b/modules/audio/ANDROID_c_additions index fa0ee8be..29ab6574 100644 --- a/modules/audio/ANDROID_c_additions +++ b/modules/audio/ANDROID_c_additions @@ -1,47 +1,72 @@ +/* modules/audio/ANDROID_c_additions -*-C-*- */ + +/* + * # Heritage + * + * `(*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/AudioHelper")` worked + * before, but minor changes elsewhere broke it. + * + * This change uses "textbook solution". + */ + // AudioHelper ffi -static jobject audiohelper_object=NULL; -static jclass audiohelper_class=NULL; -static jmethodID s_load=NULL; -static jmethodID s_play=NULL; -static jmethodID s_stop=NULL; -static jmethodID s_stopfile=NULL; -static jmethodID s_unload=NULL; -static jmethodID s_setvolume=NULL; +// TBD: Try harder to avoid conflicts over generic global names! +static jobject audiohelper_object = NULL; +static jmethodID s_load = NULL; +static jmethodID s_play = NULL; +static jmethodID s_stop = NULL; +static jmethodID s_stopfile = NULL; +static jmethodID s_unload = NULL; +static jmethodID s_setvolume = NULL; + +void Java_@SYS_PACKAGE_UNDERSCORE@_AudioHelper_nativeInit(JNIEnv* env, jobject audiohelper_class) { + // note btw: the advantage of using static class initializers over + // manual maintained re-initialisation is that the JVM will call + // class initialisers exactly whenever the class is (re)loaded. + s_load = (*env)->GetMethodID(env, audiohelper_class, "fromNativeLoadSound", "(ILjava/lang/String;I)V"); + if(s_load == NULL) { + LOGE("AudioHelper_nativeInit LoadSound not found"); + JNI_forward_exception_to_gambit(env); + return; + } + // TBD: fatal error when any did not load - unlikely + s_play = (*env)->GetMethodID(env, audiohelper_class, "PlaySound", "(IFFIIF)I"); + s_stop = (*env)->GetMethodID(env, audiohelper_class, "StopAll", "()I"); + s_stopfile = (*env)->GetMethodID(env, audiohelper_class, "StopSound", "(I)I"); + s_unload = (*env)->GetMethodID(env, audiohelper_class, "UnloadSound", "(I)Z"); + s_setvolume = (*env)->GetMethodID(env, audiohelper_class, "SetVolume", "(F)Z"); + if(s_play == NULL) { + LOGE("AudioHelper_nativeInit PlaySound not found"); + JNI_forward_exception_to_gambit(env); + return; + } +} -void android_audio_init(){ - static int needsinit=1; - if (needsinit) { - JNIEnv *env = GetJNIEnv(); - if (env) { - audiohelper_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/AudioHelper"); - if (audiohelper_class) { - jmethodID getInstance = (*env)->GetStaticMethodID(env,audiohelper_class, "getInstance", - "()L@SYS_PACKAGE_SLASH@/AudioHelper;"); - s_load = (*env)->GetMethodID(env,audiohelper_class, "LoadSound", "(Ljava/lang/String;I)I"); - s_play = (*env)->GetMethodID(env,audiohelper_class, "PlaySound", "(IFFIIF)I"); - s_stop = (*env)->GetMethodID(env,audiohelper_class, "StopAll", "()I"); - s_stopfile = (*env)->GetMethodID(env,audiohelper_class, "StopSound", "(I)I"); - s_unload = (*env)->GetMethodID(env,audiohelper_class, "UnloadSound", "(I)Z"); - s_setvolume = (*env)->GetMethodID(env,audiohelper_class, "SetVolume", "(F)Z"); - if (getInstance) { - jobject thiz = (*env)->CallStaticObjectMethod(env,audiohelper_class, getInstance); - audiohelper_object = (*env)->NewGlobalRef(env,thiz); - } - } - } else { - audiohelper_object=NULL; - } - needsinit=0; +void Java_@SYS_PACKAGE_UNDERSCORE@_AudioHelper_nativeSetInstance(JNIEnv* env, jobject obj) { + if(audiohelper_object != NULL) { + LOGW("AudioHelper_nativeSetInstance replacing global object"); + (*env)->DeleteGlobalRef(env, audiohelper_object); + audiohelper_object = NULL; + } + audiohelper_object = (*env)->NewGlobalRef(env, obj); + if(audiohelper_object == NULL) { + LOGE("AudioHelper_nativeSetInstance cache object failed"); + JNI_forward_exception_to_gambit(env); + (*env)->FatalError(env, "AudioHelper_nativeSetInstance cache object failed"); + } else { + LOGI("AudioHelper_nativeSetInstance %p\n", audiohelper_object); } } -int android_audio_loadfile(const char *FileName, int Priority){ +int android_audio_loadfile(int ReplyID, const char *FileName, int Priority){ int SoundID=-1; JNIEnv *env = GetJNIEnv(); if (env&&audiohelper_object) { jstring s = (*env)->NewStringUTF(env,FileName); - SoundID = (*env)->CallIntMethod(env,audiohelper_object, s_load, s, Priority); - (*env)->DeleteLocalRef(env,(jobject)s); + jstring sg = (*env)->NewGlobalRef(env, s); // AudioHelper runs on different thread + SoundID = (*env)->CallIntMethod(env, audiohelper_object, s_load, ReplyID, sg, Priority); + JNI_forward_exception_to_gambit(env); + (*env)->DeleteGlobalRef(env, (jobject)sg); // better feed it global ref's } return SoundID; } @@ -49,7 +74,7 @@ int android_audio_loadfile(const char *FileName, int Priority){ int android_audio_playfile(int SoundID, float LeftVolume, float RightVolume, int Priority, int Loop, float Rate){ JNIEnv *env = GetJNIEnv(); if (env&&audiohelper_object) { - return (*env)->CallIntMethod(env,audiohelper_object, s_play, SoundID, LeftVolume, RightVolume, Priority, Loop, Rate); + return (*env)->CallIntMethod(env, audiohelper_object, s_play, SoundID, LeftVolume, RightVolume, Priority, Loop, Rate); } else { return -1; } diff --git a/modules/audio/ANDROID_java_additions b/modules/audio/ANDROID_java_additions index a851a11b..7b283d2a 100644 --- a/modules/audio/ANDROID_java_additions +++ b/modules/audio/ANDROID_java_additions @@ -1,3 +1,5 @@ +/* modules/audio/ANDROID_java_additions -*- mode: java; c-basic-offset: 2; -*- */ + class AudioHelper { final static String TAG = "@SYS_PACKAGE_DOT@"; private static class MediaPlayerHelper { @@ -68,12 +70,25 @@ class AudioHelper { private MediaPlayer mediaPlayer = null; private List mediaPlayers = null; private AudioManager audioManager = null; - private AudioHelper(){ + /* + * To cache IDs when a class is loaded, and automatically re-cache + * them if the class is ever unloaded and reloaded: use a class + * initializer to allow the native code to cache some field + * offsets. This native function looks up and caches interesting + * class/field/method IDs. Throws on failure. + */ + private static native void nativeInit(); + static { + nativeInit(); } - void Initialise(){ + private native void nativeSetInstance(); + private @SYS_APPNAME@ mActivity = null; + private AudioHelper(@SYS_APPNAME@ forActivity) { + mActivity = forActivity; mediaPlayers = new ArrayList(); - } - public void finalize(){ + nativeSetInstance(); + } + public void finalize() { for(MediaPlayerHelper mp : mediaPlayers){ if(mp!=null){ mp.release(); @@ -83,16 +98,21 @@ class AudioHelper { public void setContext(Context context, MediaPlayer mediaPlayer){ this.context = context; this.audioManager = (AudioManager) context.getSystemService(Activity.AUDIO_SERVICE); - this.mediaPlayer = mediaPlayer; + this.mediaPlayer = mediaPlayer; + } + public static AudioHelper createInstance(@SYS_APPNAME@ forActivity){ + if (instance == null){ + instance = new AudioHelper(forActivity); + } + return instance; } - public static AudioHelper getInstance(){ + public static AudioHelper getInstance() { if (instance == null){ - instance = new AudioHelper(); - instance.Initialise(); + Log.e(TAG, "AudioHelper_getInstance get before create"); } return instance; } - public int LoadSound(String filename, int Priority) { + private int doLoadSound(String filename, int Priority) { String s = ResourceLocation + filename; int resID = context.getResources().getIdentifier(s, null, null); Log.d(TAG,"Entered load sound method."); @@ -102,39 +122,95 @@ class AudioHelper { } MediaPlayerHelper localMediaPlayer = new MediaPlayerHelper(context, resID); mediaPlayers.add(localMediaPlayer); + Log.e(TAG, "Sound " + filename + " is # " + mediaPlayers.size()); + System.err.println(TAG + ": Sound " + filename + " is # " + mediaPlayers.size()); return mediaPlayers.size(); - } - public int PlaySound(int SoundID, float lv, float rv, int priority, int loop, float rate) { + } + public int LoadSound(final String filename, final int Priority) { + java.util.concurrent.FutureTask job = new java.util.concurrent.FutureTask + (new java.util.concurrent.Callable() { + @Override + public Integer call() throws Exception { + return doLoadSound(filename, Priority); } + }); + mActivity.runOnUiThread(job); + try { + return job.get().intValue(); + } catch (Exception e) { + return 0; // FIXME, should we retry? + } + } + private void fromNativeLoadSound(final int replyID, final String filename, final int Priority) { + // We can NOT call LoadSound from a native thread in a situation + // which MUST NOT block. The job done within the Java environment + // by FutureTask above needs to be replicated between Java Threads and + // corresponding Gambit threads. + mActivity.runOnUiThread(new Runnable() { + public void run() { + mActivity.nativeEvent(126, replyID, doLoadSound(filename, Priority)); + }}); + } + private int doPlaySound(int SoundID, float lv, float rv, int priority, int loop, float rate) { MediaPlayerHelper localMediaPlayer = mediaPlayers.get(SoundID-1); if(localMediaPlayer!=null){ localMediaPlayer.start(lv, rv, loop); } return SoundID; } - public int StopSound(int SoundID) { - MediaPlayerHelper localMediaPlayer = mediaPlayers.get(SoundID-1); - if(localMediaPlayer!=null){ - localMediaPlayer.stop(); - } + public int PlaySound(final int SoundID, final float lv, final float rv, + final int priority, final int loop, final float rate) { + mActivity.runOnUiThread(new Runnable() { + public void run() { + doPlaySound(SoundID, lv, rv, priority, loop, rate); + } + }); + return SoundID; + } + public int StopSound(final int SoundID) { + mActivity.runOnUiThread(new Runnable() { + public void run() { + MediaPlayerHelper localMediaPlayer = mediaPlayers.get(SoundID-1); + if(localMediaPlayer!=null) { + localMediaPlayer.stop(); + } + } + }); return SoundID; } public int StopAll() { - int ct=0; - for(MediaPlayerHelper player : this.mediaPlayers){ - if(player!=null){ - player.stop(); - ct++; - } + final AudioHelper here = this; + // FIXME: bad example + java.util.concurrent.FutureTask job = new java.util.concurrent.FutureTask + (new java.util.concurrent.Callable() { + @Override + public Integer call() throws Exception { + int ct=0; + for(MediaPlayerHelper player : here.mediaPlayers){ + if(player!=null){ + player.stop(); + ct++; + } + } + return ct; + } + }); + mActivity.runOnUiThread(job); + try { + return job.get(); + } catch (Exception e) { + return 0; // FIXME, should we retry? } - return ct; } - public boolean UnloadSound(int SoundID) { + public boolean UnloadSound(final int SoundID) { // We null out removed sounds - we have to null check sounds // later but this simplifies the representation of sounds by // their offset index. - MediaPlayerHelper toRemove = mediaPlayers.get(SoundID-1); - toRemove.release(); - mediaPlayers.set(SoundID-1,null); + mActivity.runOnUiThread(new Runnable() { + public void run() { + MediaPlayerHelper toRemove = mediaPlayers.get(SoundID-1); + toRemove.release(); + mediaPlayers.set(SoundID-1, null); + }}); return true; } public void onPause(){ @@ -151,7 +227,7 @@ class AudioHelper { } } } - public boolean SetVolume(float volume){ + public boolean SetVolume(final float volume){ float systemMaxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); float systemVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)/systemMaxVolume; if (Math.abs(systemVolume-volume)*100 >= 1) { @@ -161,4 +237,4 @@ class AudioHelper { return true; } } -// eof AudioHelper +// eof AudioHelper diff --git a/modules/audio/ANDROID_java_oncreate b/modules/audio/ANDROID_java_oncreate index 934f6561..75ca84d3 100644 --- a/modules/audio/ANDROID_java_oncreate +++ b/modules/audio/ANDROID_java_oncreate @@ -1 +1,9 @@ -AudioHelper.getInstance().setContext(this, new MediaPlayer()); +/* AudioHelper onCreate -*-C-*- */ +AudioHelper.createInstance(this).setContext(this, new MediaPlayer()); +/* DEBUG check it works +Log.i(TAG, "AudioHelper playing test sound"); +int thatsit = AudioHelper.getInstance().LoadSound("thatsit", 0); +Log.i(TAG, "AudioHelper that sit " + thatsit); +AudioHelper.getInstance().PlaySound(thatsit, (float)1.0, (float)1.0, 0, 0, (float)1.0); +Log.i(TAG, "AudioHelper test completed"); +// DEBUG check end: AudioHelper */ diff --git a/modules/audio/audiofile.scm b/modules/audio/audiofile.scm index 448c189b..c67cebc5 100644 --- a/modules/audio/audiofile.scm +++ b/modules/audio/audiofile.scm @@ -159,7 +159,7 @@ static int soundfile_load(char *fname) static int soundfile_play(int id) { char *fname=(char*)id; - PlaySound(fname, NULL, SND_FILENAME | SND_ASYNC); + PlaySound(fname, NULL, SND_FILENAME | SND_ASYNC); // This | just fixes highlighting } #endif @@ -256,8 +256,7 @@ static void portaudio_stop(void){ // %%%%%%%%%%%%%%%%%%%%%% #ifdef USE_ANDROID_NATIVE -void android_audio_init(void); -int android_audio_loadfile(const char*, int); +int android_audio_loadfile(int, const char*, int); int android_audio_playfile(int, float, float, int, int, float); int android_audio_stopfile(int); int android_audio_stop(); @@ -275,9 +274,6 @@ int android_audio_setvolume(float vol){ return 0;} // %%%%%%%%%%%%%%%%%%%%%% void audiofile_init(void) { -#ifdef USE_ANDROID_NATIVE - android_audio_init(); -#endif #ifdef USE_PORTAUDIO portaudio_init(); #endif @@ -286,10 +282,10 @@ void audiofile_init(void) { #endif } -int audiofile_load(char *name) +int audiofile_load(int id, char *name) { #ifdef USE_ANDROID_NATIVE - return android_audio_loadfile(name, 0); + return android_audio_loadfile(id, name, 0); #endif #ifdef USE_APPLE_NATIVE SystemSoundID sid; @@ -396,7 +392,17 @@ end-of-c-declare (define audiofile-init (c-lambda () void "audiofile_init")) -(define audiofile:load (c-lambda (char-string) int "audiofile_load")) +(define audiofile:load + (cond-expand + (android + (lambda (fn) + (let* ((result (eventloop-open-channel)) + (reply-id (mutex-specific result))) + ((c-lambda (int char-string) int "audiofile_load") reply-id fn) + (eventloop-await-channel result)))) + (else + (lambda (fn) + ((c-lambda (int char-string) int "audiofile_load") 0 fn))))) (define (audiofile-load name) (define (autoext name) diff --git a/modules/audioaux/ANDROID_c_additions b/modules/audioaux/ANDROID_c_additions index 62217f6c..96cefe5e 100644 --- a/modules/audioaux/ANDROID_c_additions +++ b/modules/audioaux/ANDROID_c_additions @@ -1,7 +1,6 @@ -// android_audioaux_ FFI +/* android_audioaux_ FFI -*-C-*- */ int android_audioaux_setvolume(float vol){ JNIEnv *env = GetJNIEnv(); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); if (env&&globalObj){ jmethodID method = (*env)->GetMethodID(env, main_class, "SetVolume", "(F)Z"); return (*env)->CallBooleanMethod(env, globalObj, method, vol); @@ -12,7 +11,6 @@ int android_audioaux_setvolume(float vol){ float android_audioaux_getvolume(){ JNIEnv *env = GetJNIEnv(); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); if (env&&globalObj){ jmethodID method = (*env)->GetMethodID(env, main_class, "GetVolume", "()F"); return (*env)->CallFloatMethod(env, globalObj, method); @@ -23,7 +21,6 @@ float android_audioaux_getvolume(){ int android_audioaux_headphonepresent(void){ JNIEnv *env = GetJNIEnv(); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); if (env&&globalObj){ jmethodID method = (*env)->GetMethodID(env, main_class, "HeadphonePresent", "()Z"); return (*env)->CallBooleanMethod(env, globalObj, method); diff --git a/modules/camera/ANDROID_c_additions b/modules/camera/ANDROID_c_additions index a2f1f438..5bc5a3aa 100644 --- a/modules/camera/ANDROID_c_additions +++ b/modules/camera/ANDROID_c_additions @@ -1,3 +1,4 @@ +// TBD: untested, maybe we need to pass global references void android_camera_start(char* fnl_name, char *tmp_name) { @@ -5,8 +6,7 @@ void android_camera_start(char* fnl_name, char *tmp_name) if (env&&globalObj){ jstring jfnl = (*env)->NewStringUTF(env,fnl_name); jstring jtmp = (*env)->NewStringUTF(env,tmp_name); - jclass cls = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); - jmethodID method = cls ? (*env)->GetMethodID(env, cls, "startCamera", "(Ljava/lang/String;Ljava/lang/String;)V") : NULL; + jmethodID method = (*env)->GetMethodID(env, main_class, "startCamera", "(Ljava/lang/String;Ljava/lang/String;)V"); if(method) (*env)->CallVoidMethod(env, globalObj, method, jfnl, jtmp); } } @@ -17,8 +17,7 @@ void android_videocamera_start(char* fnl_name, char *tmp_name, int *maxlength) if (env&&globalObj){ jstring jfnl = (*env)->NewStringUTF(env,fnl_name); jstring jtmp = (*env)->NewStringUTF(env,tmp_name); - jclass cls = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); - jmethodID method = cls ? (*env)->GetMethodID(env, cls, "startVidCamera", "(Ljava/lang/String;Ljava/lang/String;I)V"): NULL; + jmethodID method = (*env)->GetMethodID(env, main_class, "startVidCamera", "(Ljava/lang/String;Ljava/lang/String;I)V"); if(method) (*env)->CallVoidMethod(env, globalObj, method, jfnl, jtmp, maxlength); } } \ No newline at end of file diff --git a/modules/clipboard/ANDROID_c_additions b/modules/clipboard/ANDROID_c_additions index cbe03487..fd8faf8a 100644 --- a/modules/clipboard/ANDROID_c_additions +++ b/modules/clipboard/ANDROID_c_additions @@ -1,12 +1,20 @@ +/* clipboard -*-C-*- */ + jstring jstr; const char *str = NULL; -int android_clipboard_copy(char *str){ +int android_clipboard_copy(char *str) { JNIEnv *env = GetJNIEnv(); jstring jstr = (*env)->NewStringUTF(env,str); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); - if (env&&globalObj){ - jmethodID method = (*env)->GetMethodID(env, main_class, "setClipboardContent", "(Ljava/lang/String;)I"); + static jmethodID method = 0; + if(env) { + if(!method) { + method = (*env)->GetMethodID(env, main_class, "setClipboardContent", "(Ljava/lang/String;)I"); + if(!method) { + JNI_forward_exception_to_gambit(env); + return 0; + } + } return (*env)->CallIntMethod(env, globalObj, method, jstr); } return 0; @@ -14,9 +22,16 @@ int android_clipboard_copy(char *str){ const char *android_clipboard_paste(){ JNIEnv *env = GetJNIEnv(); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); - if (env&&globalObj){ - jmethodID method = (*env)->GetMethodID(env, main_class, "getClipboardContent", "()Ljava/lang/String;"); + static jmethodID method = 0; + if(!main_class) (*env)->FatalError(env, "android_clipboard_paste: no main_class"); + if(env) { + if(!method) { + method = (*env)->GetMethodID(env, main_class, "getClipboardContent", "()Ljava/lang/String;"); + if(!method) { + JNI_forward_exception_to_gambit(env); + return ""; + } + } jstr = (jstring) (*env)->CallObjectMethod(env, globalObj, method, jstr); str = (*env)->GetStringUTFChars(env, jstr, 0); // (*env)->ReleaseStringUTFChars(env, jstr, str); @@ -25,7 +40,7 @@ const char *android_clipboard_paste(){ return ""; } -void android_clipboard_release(){ +void android_clipboard_release() { if (str) { JNIEnv *env = GetJNIEnv(); (*env)->ReleaseStringUTFChars(env, jstr, str); @@ -38,12 +53,18 @@ int android_clipboard_clear(){ return 0; } -int android_clipboard_hascontent(){ +int android_clipboard_hascontent() { JNIEnv *env = GetJNIEnv(); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); - if (env&&globalObj){ + static jmethodID method = 0; + if(env) { + if(!method) { jmethodID method = (*env)->GetMethodID(env, main_class, "checkClipboardContent", "()I"); + if(!method) { + JNI_forward_exception_to_gambit(env); + return 0; + } return (*env)->CallIntMethod(env, globalObj, method); + } } return 0; } diff --git a/modules/clipboard/ANDROID_java_activityadditions b/modules/clipboard/ANDROID_java_activityadditions index c327aa5d..58fa870e 100644 --- a/modules/clipboard/ANDROID_java_activityadditions +++ b/modules/clipboard/ANDROID_java_activityadditions @@ -1,11 +1,10 @@ private String getClipboardContent(){ if (!(mClipboardManager.hasPrimaryClip())) { return ""; - } else if (mClipboardManager.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { + } else /* if (mClipboardManager.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) */ { ClipData.Item item = mClipboardManager.getPrimaryClip().getItemAt(0); - return item.getText().toString(); + return item.coerceToText(this).toString(); } - return ""; } private int setClipboardContent(String str){ diff --git a/modules/clipboard/ANDROID_java_oncreate b/modules/clipboard/ANDROID_java_oncreate index f1c2ac24..3c083693 100644 --- a/modules/clipboard/ANDROID_java_oncreate +++ b/modules/clipboard/ANDROID_java_oncreate @@ -1 +1,2 @@ +// Log.i(TAG, "mClipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE)"); mClipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); diff --git a/modules/clipboard/clipboard.scm b/modules/clipboard/clipboard.scm index 8d3ed529..73c2185d 100644 --- a/modules/clipboard/clipboard.scm +++ b/modules/clipboard/clipboard.scm @@ -36,6 +36,8 @@ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |# +(c-declare "#include ") ;; debug + (c-declare #< 0 ) x = arg; + return x; +} +end-of-decl +) + +(define (microgl-redraw-period . arg) + (cond + ((null? arg) ((c-lambda (unsigned-int) unsigned-int "microgl_redraw_period") 0)) + (else ((c-lambda (unsigned-int) unsigned-int "microgl_redraw_period") (car arg))))) + ;; Cleanup and exit with given exit code. (Unlike force-terminate, ;; which always exists with zero.) ;; @@ -100,13 +142,6 @@ end-of-c-declare (with-exception-catcher (lambda (e) #f) (lambda () (create-directory (system-directory))))) -;; Disable the android heartbeat as it causes problems. Note that for 4.7.9 this -;; has to be below the definition of system-platform to avoid an (#!unbound2) -;; *** ERROR IN test# -- Operator is not a PROCEDURE -(cond-expand - (gambit-c (if (string=? (system-platform) "android") (##heartbeat-interval-set! -1.))) - (else (if (string=? (system-platform) "android") (##set-heartbeat-interval! -1.)))) - ;; Gain access to Android app_directory_files and app_code_path (define android-get-filesdir (c-lambda () char-string "android_getFilesDir")) (define android-get-codepath (c-lambda () char-string "android_getPackageCodePath")) diff --git a/modules/eventloop/ANDROID_c_additions b/modules/eventloop/ANDROID_c_additions index c214fb09..13371537 100644 --- a/modules/eventloop/ANDROID_c_additions +++ b/modules/eventloop/ANDROID_c_additions @@ -2,7 +2,6 @@ extern void scm_mediascanner_callback(); void ln_android_finish(){ JNIEnv *env = GetJNIEnv(); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); if (env&&globalObj){ jmethodID method = (*env)->GetMethodID(env, main_class, "ln_finish", "()V"); (*env)->CallVoidMethod(env, globalObj, method); @@ -13,8 +12,7 @@ void ln_android_finish(){ void ln_android_run_mediascanner(){ JNIEnv *env = GetJNIEnv(); if (env&&globalObj) { - jclass cls = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); - jmethodID method = (*env)->GetMethodID(env, cls, "runMediaScanner", "()V"); + jmethodID method = (*env)->GetMethodID(env, main_class, "runMediaScanner", "()V"); (*env)->CallVoidMethod(env, globalObj, method); } } diff --git a/modules/eventloop/eventloop.scm b/modules/eventloop/eventloop.scm index 07ca3ca0..8874309f 100644 --- a/modules/eventloop/eventloop.scm +++ b/modules/eventloop/eventloop.scm @@ -160,123 +160,257 @@ end-of-c-declare (mutex-specific-set! mux #f) (lambda args (cond - ((null? args) - ;; return receiver procedure - (lambda (t x y) - (let ((proc (mutex-specific mux))) - (when proc - (mutex-specific-set! mux #f) - (proc t x y) - (mutex-unlock! mux))))) - ;; ( => ) is a clause, where if evaluates to #t, - ;; is evaluated as ( ) - ((let ((proc (car args))) (and (procedure? proc) proc)) - => - ;;set 'proc' as inner receiver - (lambda (proc) - (mutex-lock! mux) - (mutex-specific-set! mux proc) - #t)) - (else (log-error "illegal arguments" on-jscm-result args)))))) - -(define eventloop:mutex (make-mutex 'eventloop)) -(define (eventloop:grab!) (mutex-lock! eventloop:mutex)) -(define (eventloop:release!) (mutex-unlock! eventloop:mutex)) - -(c-define (c-event t x y) (int int int) void "scm_event" "" - (eventloop:grab!) + ((null? args) ;; return receiver procedure + (lambda (t x y) + (let ((proc (mutex-specific mux))) + (when proc + (mutex-specific-set! mux #f) + (proc t x y) + (mutex-unlock! mux))))) + ((let ((proc (car args))) (and (procedure? proc) proc)) => + ;; set `proc` as inner receiver + (lambda (proc) + (mutex-lock! mux) + (mutex-specific-set! mux proc) + #t)) + (else (log-error "illegal arguments" on-jscm-result args)))))) + +(define eventloop-open-channel) +(define eventloop-close-channel) +(define eventloop-await-channel) +(define eventloop-notify-channel! + (let ((channels (make-table test: eqv?)) + (mux (make-mutex 'eventloop-open-channel))) + (define (close channel) + (mutex-lock! mux) + (let ((x (cond + ((mutex? channel) (mutex-specific channel)) + (else channel)))) + (table-set! channels x)) + (mutex-unlock! mux)) + (define (open #!optional (receiver #f) #!key + (kind (cond + ((not receiver) 'mutex) + (else 'once))) + (fail raise)) + (mutex-lock! mux #f #f) + (let* ((id (mutex-specific mux)) + (result #f) + (handler + (case kind + ((once) + (lambda (t x y) + (close x) + (receiver t x y))) + ((multiple) receiver) + ((mutex block) ;; special case of 'once likely useful + (set! result (make-mutex receiver)) + (mutex-specific-set! result id) + (mutex-lock! result #f #f) ;; be careful NOT take onwership + (lambda (t x y) + (close x) + (cond + ((eqv? (mutex-state result) 'not-owned) + (receive results + (cond + ((procedure? receiver) (receiver t x y)) + (else y)) + (mutex-specific-set! result results) + (mutex-unlock! result)))))) + (else (error "unhandled argument kind" eventloop-open-channel kind))))) + (mutex-specific-set! mux (+ id 1)) + (table-set! channels id handler) + (mutex-unlock! mux) + (or result id))) + (define (await result) + (mutex-lock! result) + (let ((results (mutex-specific result))) + (mutex-unlock! mux) + (apply values results))) + (mutex-specific-set! mux 1) + (set! eventloop-open-channel open) + (set! eventloop-close-channel close) + (set! eventloop-await-channel await) + (lambda (t x y) + (let ((receiver (table-ref channels x #f))) + (when receiver (receiver t x y)))))) + +(cond-expand + (android + (c-declare + ;; calls GLState.fromNativeStart() + "extern void android_GLState_start();") + (define-macro (android-glstate-start) + '((c-lambda () void "android_GLState_start"))) + (c-declare + ;; calls GLState.fromNativeInitDraw() + "extern void microgl_draw_before();") + (define-macro (microgl-draw-before) + '((c-lambda () void "microgl_draw_before")))) + (else + (define-macro (android-glstate-start) + #!void) + (define-macro (microgl-draw-before) + #!void))) + +(define (eventloop-handle-event t x y) (set! ##now (current-time-seconds)) (let ((xtra (if (not app:mustinit) (event-pop) #f))) (if xtra (apply hook:event xtra)) (cond - ((fx= t EVENT_REDRAW) - (hook:event t 0 0) - (if app:android? (##thread-heartbeat!)) + ((eqv? t EVENT_REDRAW) + (hook:event t 0 0)) + ((eqv? t EVENT_IDLE) + (hook:event t 0 0) + (cond-expand + (win32 + ;; better wait here than in C. + (thread-sleep! 0.05)) + (else #!void))) + ((or (eqv? t EVENT_BUTTON1DOWN) (eqv? t EVENT_BUTTON1UP) + (eqv? t EVENT_BUTTON2DOWN) (eqv? t EVENT_BUTTON2UP) + (eqv? t EVENT_BUTTON3DOWN) (eqv? t EVENT_BUTTON3UP) + (eqv? t EVENT_MOTION)) + ;; handle potential scaling (running stretched on a device) + (hook:event t (if app:scale? (fix (* app:xscale x)) x) + (if app:scale? (fix (* app:yscale y)) y)) ) - ((fx= t EVENT_IDLE) - (if app:suspended (begin - (hook:event t 0 0) - (if app:android? (##thread-heartbeat!)) - )) - ) - ((or (fx= t EVENT_BUTTON1DOWN) (fx= t EVENT_BUTTON1UP) - (fx= t EVENT_BUTTON2DOWN) (fx= t EVENT_BUTTON2UP) - (fx= t EVENT_BUTTON3DOWN) (fx= t EVENT_BUTTON3UP) - (fx= t EVENT_MOTION)) - ;; handle potential scaling (running stretched on a device) - (hook:event t (if app:scale? (fix (* app:xscale x)) x) - (if app:scale? (fix (* app:yscale y)) y)) - ) - ((fx= t EVENT_JSCM_RESULT) - ((on-jscm-result) t x y)) - ((fx= t EVENT_INIT) - ;; prevent multiple inits - (if app:mustinit (begin - (set! app:width x) - (set! app:height y) - (set! app:screenwidth x) - (set! app:screenheight y) - (if (procedure? hook:init) (hook:init x y)) - (set! app:mustinit #f) - )) - ) - ((fx= t EVENT_TERMINATE) - (log-system "System shutdown") - (terminate)) - ((fx= t EVENT_SUSPEND) - (if (and (not app:mustinit) (not app:suspended)) (begin - (set! app:suspended #t) - (if (procedure? hook:suspend) (hook:suspend)) - ))) - ((fx= t EVENT_RESUME) - (if (and (not app:mustinit) app:suspended) (begin - (set! app:suspended #f) - (if (procedure? hook:resume) (hook:resume)) - ))) + ((eqv? t EVENT_JSCM_RESULT) + (case x + ((0) ((on-jscm-result) t x y)) + (else (eventloop-notify-channel! t x y)))) + ((eqv? t EVENT_INIT) + (android-glstate-start) + ;; prevent multiple inits + (when (and app:mustinit (> x 0) (> y 0)) + (set! app:mustinit #f) + (eventloop-window-dimensions! x y) + (eventloop-screen-dimensions! x y) + (if (procedure? hook:init) (hook:init x y)) + ;;(debug 'init2 (list 'done w: app:width h: app:height sw: app:screenwidth sh: app:screenheight)) + (cond + (app:forcefullscreen (microgl-fullscreen app:screenwidth app:screenheight)) + (else (microgl-window app:width app:height))))) + ((eqv? t EVENT_TERMINATE) + (log-system "System shutdown") + (terminate)) + ((eqv? t EVENT_SUSPEND) + (when (and (not app:mustinit) (not app:suspended)) + (set! app:suspended #t) + (if (procedure? hook:suspend) (hook:suspend)))) + ((eqv? t EVENT_RESUME) + (when (and (not app:mustinit) app:suspended) + (set! app:suspended #f) + (if (procedure? hook:resume) (hook:resume)))) + (else + (if (and (not app:mustinit) (procedure? hook:event)) (hook:event t x y)))))) + +(define eventloop-thread #f) +(define (eventloop t x y) + (continuation-capture + (lambda (context) + (with-exception-catcher + (lambda (exn) + (handle-debug-exception exn eventloop context: context) + (exit 23)) + (lambda () + (define sigport (##open-predefined 1 'sigport ((c-lambda () int "___return(ffi_event_params.fd[0]);")))) + (android-glstate-start) + (eventloop-handle-event t x y) + (do () + (#f) + ;; unlock signals "done" into "hook.c" + ((c-lambda () void "ln_gambit_unlock")) + (read-char sigport) + (let ((t ((c-lambda () int "___return(ffi_event_params.t);"))) + (x ((c-lambda () int "___return(ffi_event_params.x);"))) + (y ((c-lambda () int "___return(ffi_event_params.y);")))) + ;;(debug 'handling (list t x y)) + (eventloop-handle-event t x y)))))))) + +(cond-expand + ((or android linux macosx ios openbsd bb10 playbook netbsd) + (c-define + (c-event t x y) (int int int) void "scm_event" "" + (let () + (declare (not interrupts-enabled)) + (cond + ((and (eqv? EVENT_INIT t) (not eventloop-thread)) + (set! eventloop-thread (current-thread)) + (eventloop t x y)) (else - (if (and (not app:mustinit) (procedure? hook:event)) (hook:event t x y))) - )) - (eventloop:release!)) - -(c-define (c-width) () int "scm_width" "" - (if (number? app:width) app:width 0)) - -(c-define (c-height) () int "scm_height" "" - (if (number? app:height) app:height 0)) + #f #;(thread-send eventloop-thread t)))))) + (else ;; backward compatible + ;; + ;; Note: we MUST BE reasonable sure that this is only ever called + ;; from a single native thread. + (c-define + (c-event t x y) (int int int) void "scm_event" "" + (eventloop-handle-event t x y)))) + + +(c-declare + #< app:screenwidth app:screenheight))) - (xscale (/ (flo (if flip? h w)) (flo app:screenwidth))) - (yscale (/ (flo (if flip? w h)) (flo app:screenheight)))) - (set! app:width (if flip? h w)) - (set! app:height (if flip? w h)) - (if (or app:forcefullscreen - (string=? (system-platform) "ios") - (string=? (system-platform) "bb10") - (string=? (system-platform) "playbook") - (string=? (system-platform) "android")) (begin - (set! app:xscale xscale) - (set! app:yscale yscale) - (set! app:scale? #t) - )) -)) + (xscale (/ (flo (if flip? h w)) (flo app:screenwidth))) + (yscale (/ (flo (if flip? w h)) (flo app:screenheight)))) + (if flip? + (eventloop-window-dimensions! h w) + (eventloop-window-dimensions! w h)) + (if (or app:forcefullscreen + (string=? (system-platform) "ios") + (string=? (system-platform) "bb10") + (string=? (system-platform) "playbook") + (string=? (system-platform) "android")) + (begin + (set! app:xscale xscale) + (set! app:yscale yscale) + (set! app:scale? #t))))) ;; assign scheme entry points (define (ln-main p1 p2 p3 . px) diff --git a/modules/gps/ANDROID_c_additions b/modules/gps/ANDROID_c_additions index d798b4b3..aa26a0f1 100644 --- a/modules/gps/ANDROID_c_additions +++ b/modules/gps/ANDROID_c_additions @@ -16,7 +16,6 @@ void Java_@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@_satelliteEvent(JNIEnv* e, jobje void android_location_toggleGPS(int status){ JNIEnv *env = GetJNIEnv(); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); if (env&&globalObj){ jmethodID method = (*env)->GetMethodID(env, main_class, "toggleGPS", "(I)V"); (*env)->CallVoidMethod(env, globalObj, method, status); @@ -25,7 +24,6 @@ void android_location_toggleGPS(int status){ int android_location_service(){ JNIEnv *env = GetJNIEnv(); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); if (env&&globalObj){ jmethodID method = (*env)->GetMethodID(env, main_class, "isLocationServiceEnabled", "()I"); return (int)(*env)->CallIntMethod(env,globalObj,method); diff --git a/modules/irregex/irregex.scm b/modules/irregex/irregex.scm index b957ae95..43d79b98 100644 --- a/modules/irregex/irregex.scm +++ b/modules/irregex/irregex.scm @@ -34,7 +34,18 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; History -;; +;; 0.9.10: 2021/07/06 - fixes for submatches under kleene star, empty seqs +;; in alternations, and bol in folds for backtracking +;; matcher (thanks John Clements and snan for reporting +;; and Peter Bex for fixing) +;; 0.9.9: 2021/05/14 - more comprehensive fix for repeated empty matches +;; 0.9.8: 2020/07/13 - fix irregex-replace/all with look-behind patterns +;; 0.9.7: 2019/12/31 - more intuitive handling of empty matches in -fold, +;; -replace and -split +;; 0.9.6: 2016/12/05 - fixed exponential memory use of + in compilation +;; of backtracking matcher (CVE-2016-9954). +;; 0.9.5: 2016/09/10 - fixed a bug in irregex-fold handling of bow +;; 0.9.4: 2015/12/14 - performance improvement for {n,m} matches ;; 0.9.3: 2014/07/01 - R7RS library ;; 0.9.2: 2012/11/29 - fixed a bug in -fold on conditional bos patterns ;; 0.9.1: 2012/11/27 - various accumulated bugfixes @@ -2131,12 +2142,18 @@ (chunk&position (cons src (+ i 1)))) (vector-set! slot (car s) chunk&position))) (cdr cmds)) - (for-each (lambda (c) - (let* ((tag (vector-ref c 0)) - (ss (vector-ref memory (vector-ref c 1))) - (ds (vector-ref memory (vector-ref c 2)))) - (vector-set! ds tag (vector-ref ss tag)))) - (car cmds))))) + ;; Reassigning commands may be in an order which + ;; causes memory cells to be clobbered before + ;; they're read out. Make 2 passes to maintain + ;; old values by copying them into a closure. + (for-each (lambda (execute!) (execute!)) + (map (lambda (c) + (let* ((tag (vector-ref c 0)) + (ss (vector-ref memory (vector-ref c 1))) + (ds (vector-ref memory (vector-ref c 2))) + (value-from (vector-ref ss tag))) + (lambda () (vector-set! ds tag value-from)))) + (car cmds)))))) (if new-finalizer (lp2 (+ i 1) next src (+ i 1) new-finalizer) (lp2 (+ i 1) next res-src res-index #f)))) @@ -2453,7 +2470,7 @@ flags next)))) (and a - (let ((c (add-state! (new-state-number a) + (let ((c (add-state! (new-state-number (max a b)) '()))) (nfa-add-epsilon! buf c a #f) (nfa-add-epsilon! buf c b #f) @@ -3357,9 +3374,10 @@ (fail)))) ((bol) (lambda (cnk init src str i end matches fail) - (if (or (and (eq? src (car init)) (eqv? i (cdr init))) - (and (> i ((chunker-get-start cnk) src)) - (eqv? #\newline (string-ref str (- i 1))))) + (if (let ((ch (if (> i ((chunker-get-start cnk) src)) + (string-ref str (- i 1)) + (chunker-prev-char cnk init src)))) + (or (not ch) (eqv? #\newline ch))) (next cnk init src str i end matches fail) (fail)))) ((bow) @@ -3753,19 +3771,19 @@ i matches))) (if (not m) - (finish i acc) - (let ((j (%irregex-match-end-index m 0))) - (if (= j i) - ;; skip one char forward if we match the empty string - (lp (list str (+ j 1) end) (+ j 1) acc) - (let ((acc (kons i m acc))) - (irregex-reset-matches! matches) - ;; no need to continue looping if this is a - ;; searcher - it's already consumed the only - ;; available match - (if (flag-set? (irregex-flags irx) ~searcher?) - (finish j acc) - (lp (list str j end) j acc))))))))))) + (finish from acc) + (let ((j-start (%irregex-match-start-index m 0)) + (j (%irregex-match-end-index m 0)) + (acc (kons from m acc))) + (irregex-reset-matches! matches) + (cond + ((flag-set? (irregex-flags irx) ~consumer?) + (finish j acc)) + ((= j j-start) + ;; skip one char forward if we match the empty string + (lp (list str j end) j (+ j 1) acc)) + (else + (lp (list str j end) j j acc)))))))))) (define (irregex-fold irx kons . args) (if (not (procedure? kons)) (error "irregex-fold: not a procedure" kons)) diff --git a/modules/json/json#.scm b/modules/json/json#.scm index 6e78ab20..908138a7 100644 --- a/modules/json/json#.scm +++ b/modules/json/json#.scm @@ -42,6 +42,7 @@ json-read json-write json-error json-error? +json-set-options! )) diff --git a/modules/json/json.scm b/modules/json/json.scm index b06c74a5..c4d8a254 100644 --- a/modules/json/json.scm +++ b/modules/json/json.scm @@ -52,6 +52,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (define use-symbols? #f) ;; how to encode JS true, false and null (define use-tables? #f) ;; how to encode JS objects +(define use-symbols-for-keys? #f) ;; how to encode JS object slot names +(define use-newlines? #f) ;; vertical layout (define debug? #f) @@ -64,6 +66,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (cons 'TABLE (table->list obj)) obj))))) +(define (json-set-options! #!key (symbols #f) (tables #f) (keys #f) (newlines #f)) + (set! use-symbols? symbols) + (set! use-tables? tables) + (set! use-symbols-for-keys? keys) + (set! use-newlines? newlines)) + +(define-macro (->string obj) + `(cond + ((symbol? ,obj) (symbol->string ,obj)) + (else ,obj))) + (define (json-decode str) (call-with-input-string str json-read)) @@ -175,7 +188,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (if (json-error? val) val (let ((new-rev-elements - (cons (cons str val) rev-elements))) + (cons (cons (if use-symbols-for-keys? + (string->symbol str) + str) val) + rev-elements))) (space) (let ((c (pk))) (cond ((eqv? c #\}) @@ -350,7 +366,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (display #\" port))))) (define (wr-prop prop) - (wr-string (car prop)) + (wr-string (->string (car prop))) (display ":" port) (wr (cdr prop))) @@ -365,7 +381,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (let loop ((lst (cdr lst))) (if (pair? lst) (begin - (display "," port) + (display (if use-newlines? ",\n" ",") port) (wr-prop (car lst)) (loop (cdr lst))))))) (display "}" port)) diff --git a/modules/ln_core/time.scm b/modules/ln_core/time.scm index 12048920..7391261b 100644 --- a/modules/ln_core/time.scm +++ b/modules/ln_core/time.scm @@ -258,9 +258,10 @@ end-of-c-declare ;; thanks, Martin Gasbichler ... (define (copy-time time) - (make-srfi19:time (srfi19:time-type time) - (srfi19:time-second time) - (srfi19:time-nanosecond time))) + (make-srfi19:time + (srfi19:time-type time) + (srfi19:time-nanosecond time) + (srfi19:time-second time))) ;;; current-time @@ -811,9 +812,12 @@ end-of-c-declare (offset (date-zone-offset date)) ) (+ (tm:encode-julian-day-number day month year) (- 1/2) - (+ (/ (/ (+ (* hour 60 60) - (* minute 60) second (/ nanosecond tm:nano)) tm:sid) - (- offset)))))) + (+ (/ (+ (* hour 60 60) + (* minute 60) + second + (- offset) + (/ nanosecond tm:nano)) + tm:sid))))) (define (date->modified-julian-day date) (- (date->julian-day date) @@ -1471,8 +1475,7 @@ end-of-c-declare (date-month date) (date-year date) (date-zone-offset date))) - (let* ((today (current-date)) - (newdate (make-date 0 0 0 0 (date-day today) (date-month today) (date-year today) 0))) + (let ((newdate (make-date 0 0 0 0 #f #f #f 0))) (tm:string->date newdate 0 template-string diff --git a/modules/ln_glcore/glcore-ffi.scm b/modules/ln_glcore/glcore-ffi.scm index e963d3c8..9fffa124 100644 --- a/modules/ln_glcore/glcore-ffi.scm +++ b/modules/ln_glcore/glcore-ffi.scm @@ -169,6 +169,16 @@ ___result = GL_CLAMP_TO_EDGE; ((c-lambda (float float float) void "glTranslatef") (flo a) (flo b) (flo c))) +(define glTranslatef//checks (c-lambda (float float float) void "glTranslatef")) + + +(define glTranslatef/f32vector//checks + ;; call site argument checks are supposed to ensure type and length + (c-lambda + (scheme-object) void " +___F32* args = ___CAST(___F32*, ___BODY_AS(___arg1, ___tSUBTYPED)); +glTranslatef(args[0], args[1], args[2]);")) + (define (glScalef a b c) ((c-lambda (float float float) void "glScalef") (flo a) (flo b) (flo c))) diff --git a/modules/ln_glcore/glcore.scm b/modules/ln_glcore/glcore.scm index 4b030d89..e9f15bcc 100644 --- a/modules/ln_glcore/glcore.scm +++ b/modules/ln_glcore/glcore.scm @@ -37,9 +37,26 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |# ;; Absolutely minimal OpenGL (ES) interface -(define glcore:debuglevel 0) -(define (glcore:log level . x) - (if (>= glcore:debuglevel level) (apply log-system (append (list "glcore: " x))))) +;;* Compiletime + +#| ;; enable manually in source +(define-cond-expand-feature profile) +;;|# + +(cond-expand + (debug + (define glcore:debuglevel 0) + (define (glcore:log level . x) + (if (>= glcore:debuglevel level) (apply log-system (append (list "glcore: " x)))))) + (else)) + +(cond-expand + (profile ;; ignore even when otherwise in `debug` mode + (define-macro (glcore:log . ignored) #!void)) + (debug) ;; defined by previous `debug` expansion + (else (define-macro (glcore:log . ignored) #!void))) + +;;* Runtime ;; ---------------------------------- ;; Initialization @@ -47,17 +64,39 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (define glCore:customhook #f) (define (glCore-registerhook h) (set! glCore:customhook h)) +(cond-expand + (android + (c-declare + ;; calls GLState.fromNativeInitDraw() + "extern void microgl_draw_before();") + (define-macro (microgl-draw-before) + '((c-lambda () void "microgl_draw_before")))) + (else + (define-macro (microgl-draw-before) + #!void))) + (define glCore:needsinit #t) (define (glCoreInit) - (if (and glCore:customhook app:width app:height) (begin + (microgl-draw-before) + (begin + ;; This block faithful rebuilds the legacy sequence, which was + ;; done in main.c/microgl_hook before on any EVENT_REDRAW + (glClearColor 0. 0. 0. 0.) + (glMatrixMode GL_PROJECTION) + (glLoadIdentity) ;; ?? Isn't only the last of these actually effective? + (glOrtho 0. (exact->inexact app:width) 0. (exact->inexact app:height) -1. 1.) + (glMatrixMode GL_MODELVIEW) + (glLoadIdentity) + (glClear GL_COLOR_BUFFER_BIT)) + (if (and glCore:customhook app:width app:height) (begin (glDisable GL_BLEND) - (glCore:customhook) + (glCore:customhook) (glDisable GL_CULL_FACE) (glDisable GL_DEPTH_TEST) (set! glCore:needsinit #t))) (if glCore:needsinit (begin (if (and app:width app:height) (begin - (glcore:log 5 "glCoreInit") + (glcore:log 5 "glCoreInit") ;; suspend/resume might invalidate the textures (glCoreTextureReset) (glClearColor 0. 0. 0. 0.) @@ -107,9 +146,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (set! glCore:alpha (color-alpha c))) (define (glCoreBegin type) - (set! glCore:cindex 0) - (set! glCore:vindex 0) - (set! glCore:tindex 0) + (set! glCore:cindex 0) + (set! glCore:vindex 0) + (set! glCore:tindex 0) (set! glCore:type type) ) @@ -118,10 +157,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (glVertexPointer (if glCore:use3D 3 2) GL_FLOAT 0 (if glCore:use3D glCore:varray3D glCore:varray)) (glColorPointer 4 GL_UNSIGNED_BYTE 0 glCore:carray) (if (or (fx= glCore:type GL_LINES) (fx= glCore:type GL_LINE_LOOP) (fx= glCore:type GL_LINE_STRIP)) - (begin + (begin (glDisable GL_TEXTURE_2D) (glDisableClientState GL_TEXTURE_COORD_ARRAY) - ) + ) (begin (glEnable GL_TEXTURE_2D) (glEnableClientState GL_TEXTURE_COORD_ARRAY) @@ -136,7 +175,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (ty (cond ((not txx) 0.5) ((let ((r (cdr xtra))) - (and (pair? r) (car r)))) + (and (pair? r) (car r)))) (else 0.5)))) (let ((x (flo x0)) (y (flo y0))) (f32vector-set! glCore:varray (fx+ glCore:vindex 0) x) @@ -151,7 +190,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (u8vector-set! glCore:carray (fx+ glCore:cindex 3) glCore:alpha) (set! glCore:cindex (fx+ glCore:cindex 4)) (set! glCore:use3D #f) - ))) + ))) ;; ------------------------------------------ ;; 3D rendering @@ -181,50 +220,246 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ))) ;; ---------------------------------- -;; textures - -;; each entry is a vector of initflag,texure,w,h,u8data,pixeltype -(define glCore:textures (##still-copy (make-table))) -(define glCore:tidx 0) -(define glCore:curtexture -1) - -(define (glCoreTextureCreate w h data . aux) - (glcore:log 5 "glCoreTextureCreate") - (let* ((o1x (pair? aux)) - (o2 (and o1x (cdr aux)))) - (let ((idx glCore:tidx) - (pixeltype - (cond +;; textures + +(cond-expand ;; CONSTRUCTION-CASE + ((or gambit debug) ;; tentative changes + ;;; intentions: + ;;; 1. hide globals glCore:textures and glCore:tidx (at least) + ;;; 2. (short term) replace vector with distinct type + + ;; (: (%%glCore:textures-ref t d) <<== (table-ref [abstract:glCore:textures] t d)) + (define %%glCore:textures-ref) + ;; glCoreTextureCreate EXPORTED - ubiquitious + ;;; + ;;; (: (glCoreTextureCreate w h data #!optional (interpolation GL_LINEAR) (wrap GL_CLAMP)) + ;;; -> fixnum) + (define glCoreTextureCreate) + ;; glCoreTextureReset -- TBD: unknown usage status + ;;; + ;;; (: glCoreTextureReset -> undefined) + ;;; + ;;; purpose: clear resources + (define glCoreTextureReset) + + ;; Implementation (volatile) + + (define-type glCore:texture + macros: prefix: %MATURITY+3%texture%macro- + %%valid ;; FIXME: factor out from immutable components + glidx ;; index (for opengl and internal table) + %%-???-u32vector ;; what is this? mutable? + width + height + (%%-???-u8vector:data unprintable:) + pixeltype + interpolation + wrap + ) + + (define (glCore:texture? x) (%MATURITY+3%texture%macro-glCore:texture? x)) ;; avoid eventually! + + (define (glCore:texture-valid? texture) + (%MATURITY+3%texture%macro-glCore:texture-%%valid texture)) + + (define (glCore:texture-invalidate! texture) + (%MATURITY+3%texture%macro-glCore:texture-%%valid-set! texture #f)) + + (define (glCore:texture-valid! texture) + (%MATURITY+3%texture%macro-glCore:texture-%%valid-set! texture #t)) + + (define (glCore:texture-%%-???-u32vector texture) ;; was vector-ref t 1 + (%MATURITY+3%texture%macro-glCore:texture-%%-???-u32vector texture)) + + (define (glCore:texture-width texture) + (%MATURITY+3%texture%macro-glCore:texture-width texture)) + + (define (glCore:texture-height texture) + (%MATURITY+3%texture%macro-glCore:texture-height texture)) + + (define (glCore:texture-data texture) + (%MATURITY+3%texture%macro-glCore:texture-%%-???-u8vector:data texture)) + + (define (glCore:texture-pixeltype texture) + (%MATURITY+3%texture%macro-glCore:texture-pixeltype texture)) + + (define (glCore:texture-pixeltype-set! texture) + (%MATURITY+3%texture%macro-glCore:texture-pixeltype texture)) + + (define (glCore:texture-interpolation texture) + (%MATURITY+3%texture%macro-glCore:texture-interpolation texture)) + + (define (glCore:texture-interpolation-set! texture) + (%MATURITY+3%texture%macro-glCore:texture-interpolation texture)) + + (define (glCore:texture-wrap texture) + (%MATURITY+3%texture%macro-glCore:texture-wrap texture)) + + (define (glCore:texture-wrap-set! texture) + (%MATURITY+3%texture%macro-glCore:texture-wrap texture)) + + (let (;; TBD: not thread safe, assert exclusive access at least in debug + (glCore:textures (make-table)) + (glCore:tidx 0) + ;; TBD: now never using ##still-copy + (maturity:use-still-copy/-1 (if #f ##still-copy identity))) + + ;; ?? should we use `(##still-copy (make-table))` for glCore:textures? + (define (glCore:textures-ref texture default) + (if (%MATURITY+3%texture%macro-glCore:texture? texture) + texture + (table-ref glCore:textures texture default))) + + (define (%%glCoreTextureCreate w h data #!optional (interpolation GL_LINEAR) (wrap GL_CLAMP)) + ;; (glcore:log 5 "glCoreTextureCreate") + #;(MATURITY -1 "legacy; TBD: ensure resources are actually released" 'glCoreTextureCreate) + (let ((idx glCore:tidx) + (pixeltype + (cond + ((fx= (u8vector-length data) (* w h)) GL_ALPHA) + ((fx= (u8vector-length data) (* 3 w h)) GL_RGB) + ((fx= (u8vector-length data) (* 4 w h)) GL_RGBA) + (else (log-error "glCoreTextureCreate: Invalid data range") #f)))) + (table-set! + glCore:textures idx + (%MATURITY+3%texture%macro-make-glCore:texture + #f ;; volatile + idx + (u32vector 0) ;; unknown + w h ;; 2d interval + (maturity:use-still-copy/-1 data) + pixeltype interpolation wrap)) + (set! glCore:tidx (fx+ glCore:tidx 1)) + idx)) + + ;; clear all textures + (define (%%glCoreTextureReset!) + (table-for-each + (lambda (k entry) + (when (glCore:texture-valid? entry) + (glDeleteTextures 1 (%MATURITY+3%texture%macro-glCore:texture-%%-???-u32vector entry)) + (glCore:texture-invalidate! entry))) + glCore:textures) + (when #f ;; should we clean references too? + (set! glCore:textures (make-table)) + (set! glCore:tidx 0))) + + (unless glCore:textures (%%reset!)) + + (set! glCoreTextureReset %%glCoreTextureReset!) + (set! %%glCore:textures-ref glCore:textures-ref) + (set! glCoreTextureCreate %%glCoreTextureCreate)) + + (define glCore:curtexture -1) ;; deprecated but required + + ) ;; end of tentative changes + (else ;; old version + + ;; each entry is a vector of initflag,texure,w,h,u8data,pixeltype + (define glCore:textures (##still-copy (make-table))) + (define glCore:tidx 0) + (define glCore:curtexture -1) + ;; forward compatible replacements + (define (%%glCore:textures-ref texture default) + (table-ref glCore:textures texture default)) + + (define (glCore:texture-valid? texture) + (vector-ref texture 0)) + + (define (glCore:texture-invalidate! texture) + (vector-set! texture 0 #f)) + + (define (glCore:texture-valid! texture) + (vector-set! texture 0 #t)) + + (define (glCore:texture-%%-???-u32vector texture) ;; was vector-ref t 1 + (vector-ref texture 1)) + + (define (glCore:texture-width texture) + (vector-ref texture 2)) + + (define (glCore:texture-height texture) + (vector-ref texture 3)) + + (define (glCore:texture-data texture) + (vector-ref texture 4)) + + (define (glCore:texture-pixeltype texture) + (vector-ref texture 5)) + + (define (glCore:texture-interpolation texture) + (vector-ref texture 6)) + + (define (glCore:texture-wrap texture) + (vector-ref texture 7)) + + (define (glCoreTextureCreate w h data . aux) + (glcore:log 5 "glCoreTextureCreate") + (let* ((o1x (pair? aux)) + (o2 (and o1x (cdr aux)))) + (let ((idx glCore:tidx) + (pixeltype + (cond ((fx= (u8vector-length data) (* w h)) GL_ALPHA) ((fx= (u8vector-length data) (* 3 w h)) GL_RGB) ((fx= (u8vector-length data) (* 4 w h)) GL_RGBA) (else (log-error "glCoreTextureCreate: Invalid data range") #f))) - (interpolation (if o1x (car aux) GL_LINEAR)) - (wrap (if (pair? o2) (car o2) GL_CLAMP))) - (table-set! glCore:textures idx - (##still-copy (vector #f (u32vector 0) w h (##still-copy data) pixeltype interpolation wrap))) - (set! glCore:tidx (fx+ glCore:tidx 1)) - idx))) + (interpolation (if o1x (car aux) GL_LINEAR)) + (wrap (if (pair? o2) (car o2) GL_CLAMP))) + (table-set! + glCore:textures idx + (##still-copy + (vector #f (u32vector 0) w h (##still-copy data) pixeltype interpolation wrap))) + (set! glCore:tidx (fx+ glCore:tidx 1)) + idx))) + ;; reset a texture entry + (define (_glCoreTextureReset t) + (glcore:log 5 "_glCoreTextureReset") + (let ((entry (%%glCore:textures-ref t #f))) + (if (and entry (glCore:texture-valid? entry)) + (begin + (glDeleteTextures 1 (glCore:texture-%%-???-u32vector entry)) + (glCore:texture-invalidate! entry))))) + + ;; clear all textures + (define (glCoreTextureReset) + (glcore:log 5 "glCoreTextureReset") + (let ((tlist '())) ;; collect list of entries + ;;; + ;;; Jikes: by ... no way! + (table-for-each (lambda (k v) (set! tlist (append tlist (list k)))) glCore:textures) + (for-each (lambda (t) (_glCoreTextureReset t)) tlist))) + + ) ;; end of old version + ) ;; end of CONSTRUCTION-CASE + + +(define (glCore:textures-ref + num #!optional + (failure (lambda (num) (error "glCore:textures-ref: unbound index" num)))) + (cond + ((fixnum? num) (or (%%glCore:textures-ref num #f) (failure num))) + (else (error "not a fixnum" num glCore:textures-ref)))) ;; return texture width (define (glCoreTextureWidth t) (glcore:log 5 "glCoreTextureWidth") - (let ((entry (table-ref glCore:textures t #f))) - (if entry (vector-ref entry 2) (begin + (let ((entry (%%glCore:textures-ref t #f))) + (if entry (glCore:texture-width entry) (begin (log-error "glCoreTextureWidth: unbound index " t) #f)))) ;; return texture height (define (glCoreTextureHeight t) (glcore:log 5 "glCoreTextureWidth") - (let ((entry (table-ref glCore:textures t #f))) - (if entry (vector-ref entry 3) (begin + (let ((entry (%%glCore:textures-ref t #f))) + (if entry (glCore:texture-height entry) (begin (log-error "glCoreTextureHeight: unbound index " t) #f)))) ;; return texture data (define (glCoreTextureData t) (glcore:log 5 "glCoreTextureData") - (let ((entry (table-ref glCore:textures t #f))) - (if entry (vector-ref entry 4) (begin + (let ((entry (%%glCore:textures-ref t #f))) + (if entry (glCore:texture-data entry) (begin (log-error "glCoreTextureData: unbound index " t) #f)))) ;; %%%%%%%%%%%%%%%%%%%% @@ -240,22 +475,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (define (glCoreClipPush . coords) (let* ((oldlist glcore:cliplist) (newcoords - (if (fx= (length coords) 4) - (map flo - (list (min (car coords) (caddr coords)) - (min (cadr coords) (cadddr coords)) - (max (car coords) (caddr coords)) - (max (cadr coords) (cadddr coords)))) - #f)) + (if (fx= (length coords) 4) + (map lambdanative#flo + (list (min (car coords) (caddr coords)) + (min (cadr coords) (cadddr coords)) + (max (car coords) (caddr coords)) + (max (cadr coords) (cadddr coords)))) + #f)) (newlist (if newcoords - (append (list newcoords) oldlist) - (if (null? oldlist) oldlist (cdr oldlist))))) + (append (list newcoords) oldlist) + (if (null? oldlist) oldlist (cdr oldlist))))) (if (not (null? newlist)) - (begin - (set! glcore:clipx1 (car (car newlist))) - (set! glcore:clipy1 (cadr (car newlist))) - (set! glcore:clipx2 (caddr (car newlist))) - (set! glcore:clipy2 (cadddr (car newlist))))) + (begin + (set! glcore:clipx1 (car (car newlist))) + (set! glcore:clipy1 (cadr (car newlist))) + (set! glcore:clipx2 (caddr (car newlist))) + (set! glcore:clipy2 (cadddr (car newlist))))) (set! glcore:cliplist newlist))) (define glCoreClipPop glCoreClipPush) @@ -266,39 +501,41 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ;; polygons are not clipped at all (define (glCoreTextureDraw x y w0 h0 t x1 y1 x2 y2 r . colors) - (let ((entry (table-ref glCore:textures t #f))) - (if entry - (let ((w (flo (if (fx= (fix w0) 0) (vector-ref entry 2) w0))) - (h (flo (if (fx= (fix h0) 0) (vector-ref entry 3) h0)))) - (if (null? glcore:cliplist) - (if (pair? colors) - (glCore:TextureDrawUnClipped - (flo x) (flo y) w h t (flo x1) (flo y1) (flo x2) (flo y2) (flo r) - (car colors)) - (glCore:TextureDrawUnClipped - (flo x) (flo y) w h t (flo x1) (flo y1) (flo x2) (flo y2) (flo r))) - (if (pair? colors) - (glCore:TextureDrawClipped - (flo x) (flo y) w h t (flo x1) (flo y1) (flo x2) (flo y2) (flo r) - (car colors)) - (glCore:TextureDrawClipped - (flo x) (flo y) w h t (flo x1) (flo y1) (flo x2) (flo y2) (flo r))))) - (log-error "glCoreTextureDraw: unbound index " t)))) + (let ((entry (%%glCore:textures-ref t #f))) + (if entry + (let ((w (flo (if (fx= (fix w0) 0) (glCore:texture-width entry) w0))) + (h (flo (if (fx= (fix h0) 0) (glCore:texture-height entry) h0)))) + (if (null? glcore:cliplist) + (if (pair? colors) + (glCore:TextureDrawUnClipped + (flo x) (flo y) w h t (flo x1) (flo y1) (flo x2) (flo y2) (flo r) + (car colors)) + (glCore:TextureDrawUnClipped + (flo x) (flo y) w h t (flo x1) (flo y1) (flo x2) (flo y2) (flo r))) + (if (pair? colors) + (glCore:TextureDrawClipped + (flo x) (flo y) w h t (flo x1) (flo y1) (flo x2) (flo y2) (flo r) + (car colors)) + (glCore:TextureDrawClipped + (flo x) (flo y) w h t (flo x1) (flo y1) (flo x2) (flo y2) (flo r))))) + (log-error "glCoreTextureDraw: unbound index " t)))) (define (glCore:TextureDrawUnClipped x y w h t @x1 @y1 @x2 @y2 r . colors) (glcore:log 5 "glCoreTextureDrawUnclipped enter") - (let ((w2 (fl/ w 2.)) (h2 (fl/ h 2.))) - (glPushMatrix) - (glTranslatef (fl+ x w2) (fl+ y h2) 0.) - (glRotatef r 0. 0. 1.) - (_glCoreTextureBind t) - (glCoreBegin GL_TRIANGLE_STRIP) - (if (null? colors) (begin - (glCoreVertex2f (fl- w2) h2 @x1 @y2) - (glCoreVertex2f w2 h2 @x2 @y2) - (glCoreVertex2f (fl- w2) (fl- h2) @x1 @y1) - (glCoreVertex2f w2 (fl- h2) @x2 @y1) - )(let ((colors (list->vector (car colors)))) + (let ((w2 (fl/ w 2.)) (h2 (fl/ h 2.))) + (glPushMatrix) + (glTranslatef (fl+ x w2) (fl+ y h2) 0.) + (glRotatef r 0. 0. 1.) + (_glCoreTextureBind t) + (glCoreBegin GL_TRIANGLE_STRIP) + (if (null? colors) + (begin + (glCoreVertex2f (fl- w2) h2 @x1 @y2) + (glCoreVertex2f w2 h2 @x2 @y2) + (glCoreVertex2f (fl- w2) (fl- h2) @x1 @y1) + (glCoreVertex2f w2 (fl- h2) @x2 @y1) + ) + (let ((colors (list->vector (car colors)))) (glCoreColor (vector-ref colors 0)) (glCoreVertex2f (fl- w2) h2 @x1 @y2) (glCoreColor (vector-ref colors 1)) @@ -307,54 +544,54 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (glCoreVertex2f (fl- w2) (fl- h2) @x1 @y1) (glCoreColor (vector-ref colors 3)) (glCoreVertex2f w2 (fl- h2) @x2 @y1) - )) - (glCoreEnd) - (glPopMatrix) - ) + )) + (glCoreEnd) + (glPopMatrix) + ) (glcore:log 5 "glCoreTextureDrawUnclipped leave") ) (define (glCore:TextureDrawClipped x y w h t @x1 @y1 @x2 @y2 r . colors) (if (and (fl< x glcore:clipx2) (fl> (fl+ x w) glcore:clipx1) (fl< y glcore:clipy2) (fl> (fl+ y h) glcore:clipy1)) - (let* ((cx1 (flmax x glcore:clipx1)) - (cx2 (flmin (fl+ x w) glcore:clipx2)) - (cy1 (flmax y glcore:clipy1)) - (cy2 (flmin (fl+ y h) glcore:clipy2)) - (cw (fl- cx2 cx1)) - (ch (fl- cy2 cy1)) - (cw2 (fl/ cw 2.)) - (ch2 (fl/ ch 2.)) - (c@x1 (fl+ (fl* (fl/ (fl- cx1 x) w) (fl- @x2 @x1)) @x1)) - (c@x2 (fl+ (fl* (fl/ (fl- cx2 x) w) (fl- @x2 @x1)) @x1)) - (c@y1 (fl+ (fl* (fl/ (fl- cy1 y) h) (fl- @y2 @y1)) @y1)) - (c@y2 (fl+ (fl* (fl/ (fl- cy2 y) h) (fl- @y2 @y1)) @y1))) + (let* ((cx1 (flmax x glcore:clipx1)) + (cx2 (flmin (fl+ x w) glcore:clipx2)) + (cy1 (flmax y glcore:clipy1)) + (cy2 (flmin (fl+ y h) glcore:clipy2)) + (cw (fl- cx2 cx1)) + (ch (fl- cy2 cy1)) + (cw2 (fl/ cw 2.)) + (ch2 (fl/ ch 2.)) + (c@x1 (fl+ (fl* (fl/ (fl- cx1 x) w) (fl- @x2 @x1)) @x1)) + (c@x2 (fl+ (fl* (fl/ (fl- cx2 x) w) (fl- @x2 @x1)) @x1)) + (c@y1 (fl+ (fl* (fl/ (fl- cy1 y) h) (fl- @y2 @y1)) @y1)) + (c@y2 (fl+ (fl* (fl/ (fl- cy2 y) h) (fl- @y2 @y1)) @y1))) (glPushMatrix) (glTranslatef (fl+ cx1 cw2) (fl+ cy1 ch2) 0.) (glRotatef r 0. 0. 1.) (_glCoreTextureBind t) (glCoreBegin GL_TRIANGLE_STRIP) (if (null? colors) - (begin - (glCoreVertex2f (fl- cw2) ch2 c@x1 c@y2) - (glCoreVertex2f cw2 ch2 c@x2 c@y2) - (glCoreVertex2f (fl- cw2) (fl- ch2) c@x1 c@y1) - (glCoreVertex2f cw2 (fl- ch2) c@x2 c@y1) - ) - (let ((colors (list->vector (car colors)))) - ;; TODO: color interpolation here! - (glCoreColor (vector-ref colors 0)) - (glCoreVertex2f (fl- cw2) ch2 c@x1 c@y2) - (glCoreColor (vector-ref colors 1)) - (glCoreVertex2f cw2 ch2 c@x2 c@y2) - (glCoreColor (vector-ref colors 2)) - (glCoreVertex2f (fl- cw2) (fl- ch2) c@x1 c@y1) - (glCoreColor (vector-ref colors 3)) - (glCoreVertex2f cw2 (fl- ch2) c@x2 c@y1) - )) + (begin + (glCoreVertex2f (fl- cw2) ch2 c@x1 c@y2) + (glCoreVertex2f cw2 ch2 c@x2 c@y2) + (glCoreVertex2f (fl- cw2) (fl- ch2) c@x1 c@y1) + (glCoreVertex2f cw2 (fl- ch2) c@x2 c@y1) + ) + (let ((colors (list->vector (car colors)))) + ;; TODO: color interpolation here! + (glCoreColor (vector-ref colors 0)) + (glCoreVertex2f (fl- cw2) ch2 c@x1 c@y2) + (glCoreColor (vector-ref colors 1)) + (glCoreVertex2f cw2 ch2 c@x2 c@y2) + (glCoreColor (vector-ref colors 2)) + (glCoreVertex2f (fl- cw2) (fl- ch2) c@x1 c@y1) + (glCoreColor (vector-ref colors 3)) + (glCoreVertex2f cw2 (fl- ch2) c@x2 c@y1) + )) (glCoreEnd) (glPopMatrix) - ))) + ))) (define glCoreTextureGradientDraw glCoreTextureDraw) @@ -363,97 +600,84 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ;; draw a texture (define (glCoreTexturePolygonDraw _cx _cy points t _r) (glcore:log 5 "glCoreTexturePolygonDraw") - (let ((entry (table-ref glCore:textures t #f))) + (let ((entry (%%glCore:textures-ref t #f))) (if entry - (let* ((cx (flo _cx)) (cy (flo _cy)) (r (flo _r))) - (glPushMatrix) - (glTranslatef cx cy 0.) - (glRotatef r 0. 0. 1.) - (_glCoreTextureBind t) - (glCoreBegin GL_TRIANGLE_STRIP) - (for-each - (lambda (p) - ;; TBD: should accept vectoralikes as point - (let* ((p (list->vector p)) - (x (fl- (vector-ref p 0) cx)) - (y (fl- (vector-ref p 1) cy)) - (tx (vector-ref p 2)) - (ty (vector-ref p 3))) - (glCoreVertex2f x y tx ty))) - points) - (glCoreEnd) - (glPopMatrix)) - (log-error "glCoreTexturePolygonDraw: unbound index " t)))) + (let* ((cx (flo _cx)) (cy (flo _cy)) (r (flo _r))) + (glPushMatrix) + (glTranslatef cx cy 0.) + (glRotatef r 0. 0. 1.) + (_glCoreTextureBind t) + (glCoreBegin GL_TRIANGLE_STRIP) + (for-each + (lambda (p) + ;; TBD: should accept vectoralikes as point + (let* ((p (list->vector p)) + (x (fl- (vector-ref p 0) cx)) + (y (fl- (vector-ref p 1) cy)) + (tx (vector-ref p 2)) + (ty (vector-ref p 3))) + (glCoreVertex2f x y tx ty))) + points) + (glCoreEnd) + (glPopMatrix)) + (log-error "glCoreTexturePolygonDraw: unbound index " t)))) ;; update texture data (for dynamic textures) ;; to use this, first modify data returned with glCoreTextureData.. (define (glCoreTextureUpdate t) (glcore:log 5 "glCoreTextureUpdate") + (if (fixnum? t) (set! t (%%glCore:textures-ref t #f))) (_glCoreTextureBind t) ;; select the texture as current - (let* ((entry (table-ref glCore:textures t #f)) - (w (vector-ref entry 2)) - (h (vector-ref entry 3)) - (data (vector-ref entry 4)) - (pixeltype (vector-ref entry 5))) - (glTexSubImage2D GL_TEXTURE_2D 0 0 0 w h pixeltype GL_UNSIGNED_BYTE data) - )) + (let ((entry t)) + (let ((w (glCore:texture-width entry)) + (h (glCore:texture-height entry)) + (data (glCore:texture-data entry)) + (pixeltype (glCore:texture-pixeltype entry))) + (glTexSubImage2D GL_TEXTURE_2D 0 0 0 w h pixeltype GL_UNSIGNED_BYTE data)))) + +(define (%%glCoreTextureInit! texture) ;; texture structure + (let ((u32t (glCore:texture-%%-???-u32vector texture)) + (w (glCore:texture-width texture)) + (h (glCore:texture-height texture)) + (data (glCore:texture-data texture)) + (pixeltype (glCore:texture-pixeltype texture)) + (interp (glCore:texture-interpolation texture)) + (wrap (glCore:texture-wrap texture))) + (glGenTextures 1 u32t) + (if (or (= (u32vector-ref u32t 0) GL_INVALID_VALUE) + ;; this is a general check that gl is working in this thread + (= (glIsEnabled GL_TEXTURE_2D) 0)) + (glcore:log 5 "_glCoreTextureInit: failed to generate texture") + (begin + (glCore:texture-valid! texture) ;; mark as initialized + (glBindTexture GL_TEXTURE_2D (u32vector-ref u32t 0)) + (glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER interp) + (glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER interp) + (glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_S wrap) + (glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_T wrap) + (glTexImage2D GL_TEXTURE_2D 0 pixeltype w h 0 pixeltype GL_UNSIGNED_BYTE data))))) (define (_glCoreTextureBind t) (glcore:log 5 "_glCoreTextureBind") - (let ((entry (table-ref glCore:textures t #f))) - (if entry (begin - (if (not (vector-ref entry 0)) (_glCoreTextureInit t)) - (let ((tx (u32vector-ref (vector-ref entry 1) 0))) - (if (not (= glCore:curtexture tx)) (begin - (glBindTexture GL_TEXTURE_2D tx) - (set! glCore:curtexture tx)))) - ) (log-error "glCoreTextureBind: unbound index " t) - ))) + (let ((entry (if (fixnum? t) (%%glCore:textures-ref t #f) t))) + (if entry + (begin + (unless (glCore:texture-valid? entry) + ;; TBD: maybe move into the reference operation? + (%%glCoreTextureInit! entry)) + (let ((tx (u32vector-ref (glCore:texture-%%-???-u32vector entry) 0))) + (if (not (= glCore:curtexture tx)) + (begin + (glBindTexture GL_TEXTURE_2D tx) + (set! glCore:curtexture tx))))) + (log-error "glCoreTextureBind: unbound index " t)))) (define (_glCoreTextureInit t) (glcore:log 5 "_glCoreTextureInit") - (let* ((entry (table-ref glCore:textures t #f)) - (u32t (vector-ref entry 1)) - (w (vector-ref entry 2)) - (h (vector-ref entry 3)) - (data (vector-ref entry 4)) - (pixeltype (vector-ref entry 5)) - (interp (vector-ref entry 6)) - (wrap (vector-ref entry 7))) - (glGenTextures 1 u32t) - (if (or (= (u32vector-ref u32t 0) GL_INVALID_VALUE) - ;this is a general check that gl is working in this thread - (= (glIsEnabled GL_TEXTURE_2D) 0)) - (glcore:log 5 "_glCoreTextureInit: failed to generate texture") - (begin - (vector-set! entry 0 #t) ;; mark as initialized - (glBindTexture GL_TEXTURE_2D (u32vector-ref u32t 0)) - (glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER interp) - (glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER interp) - (glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_S wrap) - (glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_T wrap) - (glTexImage2D GL_TEXTURE_2D 0 pixeltype w h 0 pixeltype GL_UNSIGNED_BYTE data) - )) - )) - -;; reset a texture entry -(define (_glCoreTextureReset t) - (glcore:log 5 "_glCoreTextureReset") - (let* ((entry (table-ref glCore:textures t #f)) - (u32t (vector-ref entry 1))) - (if (vector-ref entry 0) (begin - (glDeleteTextures 1 u32t) - (vector-set! entry 0 #f) ;; mark as uninitialized - )) - )) - -;; clear all textures -(define (glCoreTextureReset) - (glcore:log 5 "glCoreTextureReset") - (let ((tlist '())) - (table-for-each (lambda (k v) (set! tlist (append tlist (list k)))) glCore:textures) - (for-each (lambda (t) (_glCoreTextureReset t)) tlist) - )) + (unless (fixnum? t) (error "_glCoreTextureInit: wrong argument type")) + (let ((entry (%%glCore:textures-ref t #f))) + (if entry (%%glCoreTextureInit! entry) + (log-error "_glCoreTextureInit: unknown index " t)))) ;; take screen shot (define (glCoreReadPixels x y w h) diff --git a/modules/ln_glgui/glgui.scm b/modules/ln_glgui/glgui.scm index 37931c22..fa82c0fc 100644 --- a/modules/ln_glgui/glgui.scm +++ b/modules/ln_glgui/glgui.scm @@ -267,73 +267,30 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ;; (set! last-frame-time next) ;; (seconds->time next))))) -(define glgui-timings-set!) +(define (glgui-timings-set! . _) + (log-error "glgui-timings-set! is outdated and ignored")) ;; glgui-wakeup! - a thunk, which when called will immediately unblock ;; the thread waiting in glgui-event. Should be called if other ;; threads notice the event loop should proceed. Immediately resets ;; frame-period to frame-period-min. -(define glgui-wakeup!) +(define (glgui-wakeup!) (log-error "glgui-wakeup! was a mitigation, gone for the better.")) ;; process an input event ;; 20100519: allow multiple guis ;; 20100804: support gui offset -(define glgui-event - (let ((frame-period-max-value 0.5) ;; How long to sleep at most in redraw. - (step 0.05) ;; delay increase - (consecutive-redraw-count 1) - (customized-moment #f) ;; may be a procedure returning the wait time/moment - (wait-mutex (make-mutex 'glgui-event)) - (wait-cv (make-condition-variable 'glgui-event))) - (define (timings-set! #!key (frame-period-max #f) (frame-period-min #f) (frame-period-custom #f)) - (define (legal? x) (and (number? x) (positive? x))) - (if (legal? frame-period-max) (set! frame-period-max-value frame-period-max)) - (if (legal? frame-period-min) (set! step frame-period-min)) - (if (or (not frame-period-custom) (procedure? frame-period-custom)) - (set! customized-moment frame-period-custom))) - (define (wakeup!) - (condition-variable-signal! wait-cv)) - (define (reset-wait!) - (set! consecutive-redraw-count 1)) - (define (wait-for-time-or-signal!) - ;; wait for delay or signal from other thread - (if (let ((moment (if customized-moment - (customized-moment consecutive-redraw-count) - (min frame-period-max-value (* consecutive-redraw-count step))))) - (mutex-unlock! wait-mutex wait-cv moment)) - (reset-wait!) - (set! consecutive-redraw-count (fx+ consecutive-redraw-count 1)))) - (define (glgui-event guis t x0 y0) - (if (and glgui:active app:width app:height) - (let ((gs (if (list? guis) guis (list guis)))) - (if (fx= t EVENT_REDRAW) - (when (mutex-lock! wait-mutex 0) - (apply glgui:render gs) - (wait-for-time-or-signal!)) - (begin - (reset-wait!) - (apply glgui:inputloop (append (list t x0 y0) gs))))) - (if (fx= t EVENT_REDRAW) - (wait-for-time-or-signal!) - (if customized-moment - (thread-sleep! (customized-moment 1)) - (begin - (thread-sleep! step) - (reset-wait!)))))) - (set! glgui-wakeup! wakeup!) - (set! glgui-timings-set! timings-set!) - glgui-event)) +(define (glgui-event guis t x0 y0) + (if (and glgui:active app:width app:height) + (let ((gs (if (list? guis) guis (list guis)))) + (if (eqv? t EVENT_REDRAW) + (when (not app:suspended) + (apply glgui:render gs) + (microgl-swapbuffers)) + (apply glgui:inputloop (append (list t x0 y0) gs)))))) (define (glgui-timings-at-sec! sec) - (define (wait-for-sec _) (seconds->time (+ ##now sec))) - (define (no-wait _) 0) - (cond-expand - ((or android ios) - ;; TBD: convey the time value to signaling code. - ;; switch delays to zero - (glgui-timings-set! frame-period-custom: no-wait)) - (else (glgui-timings-set! frame-period-custom: wait-for-sec)))) + (log-error "glgui-timings-at-sec! is outdated and ignored")) (define (glgui-timings-at-10msec!) (glgui-timings-at-sec! 0.01)) diff --git a/modules/ln_glgui/primitives.scm b/modules/ln_glgui/primitives.scm index db5e743c..89d15fe4 100644 --- a/modules/ln_glgui/primitives.scm +++ b/modules/ln_glgui/primitives.scm @@ -216,6 +216,31 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ;;; Reducing the computational complexity of font operations improves ;;; rendering time. +(define-type ln-ttf:glyph + macros: prefix: macro- + desc ;; for now legacy: (key (texcoord1..4) X Y Z) + width + height + texture + texcoords ;; generic 4 element vector of flownums + rect-texcoords ;; 4x2 element f32vector + ;; order is sorta important here + offsetx + advancex + offsety + ) + +(define (ttf:glyph? obj) (macro-ln-ttf:glyph? obj)) +(define (ttf:glyph-desc obj) (macro-ln-ttf:glyph-desc obj)) +(define (ttf:glyph-width obj) (macro-ln-ttf:glyph-width obj)) +(define (ttf:glyph-height obj) (macro-ln-ttf:glyph-height obj)) +(define (ttf:glyph-image obj) (macro-ln-ttf:glyph-texture obj)) +(define (ttf:glyph-texcoords obj) (macro-ln-ttf:glyph-texcoords obj)) +(define (ttf:glyph-rect-texcoords obj) (macro-ln-ttf:glyph-rect-texcoords obj)) +(define (ttf:glyph-offsetx obj) (macro-ln-ttf:glyph-offsetx obj)) +(define (ttf:glyph-advancex obj) (macro-ln-ttf:glyph-advancex obj)) +(define (ttf:glyph-offsety obj) (macro-ln-ttf:glyph-offsety obj)) + (define-type ln-ttf:font macros: prefix: macro- desc ;; for now the legacy description of a font as a assoc-list @@ -298,10 +323,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (loop (fl+ x0 gax) (cdr cs)))))) (define (glgui:fontheight fnt) - (let* ((g (assoc 0 fnt)) - (i (if g (glgui:glyph-image g) #f)) - (h (if i (glgui:image-h i) - (cadr (cadr (car fnt)))))) h)) + (cond + ((macro-ln-ttf:font? fnt) (ttf:glyph-height (MATURITY+1:ln-ttf:font-ref fnt 0))) + ((find-font fnt) => glgui:fontheight) + (else ;; MATURITY -1 backward compatible, the old code + (let* ((g (assoc 0 fnt)) + (i (if g (glgui:glyph-image g) #f)) + (h (if i (glgui:image-h i) + (cadr (cadr (car fnt)))))) h)))) (define (glgui:stringheight txt fnt) (define font (find-font fnt)) diff --git a/modules/ln_jscheme/ANDROID_c_additions b/modules/ln_jscheme/ANDROID_c_additions index da2da14d..c84d40d1 100644 --- a/modules/ln_jscheme/ANDROID_c_additions +++ b/modules/ln_jscheme/ANDROID_c_additions @@ -1,5 +1,16 @@ /* ln_jscheme -*-C-*- */ +#if DEBUG +#define LOGJSC(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "@SYS_PACKAGE_UNDERSCORE@_@SYS_APPNAME@_jScheme", __VA_ARGS__)) +#else +#define LOGJSC(...) +#endif + +// No idea whether ot not global references are required when the +// called site switiches threads. For flexibility both ways are +// encoded here. (TBD: complete) +#define LNJSCHEME_PASS_INPUT_AS_GLOBAL_REF 1 + const char* android_app_class() { return "@SYS_PACKAGE_DOT@.@SYS_APPNAME@"; } // for jscheme /* lnjscheme_eval @@ -9,23 +20,31 @@ const char* android_app_class() { return "@SYS_PACKAGE_DOT@.@SYS_APPNAME@"; } // * may only be changed by the Java thread which created them. Use the * asynchronous version in those cases. */ -const char* lnjscheme_eval(const char* input){ +const char* lnjscheme_eval(const char* input) +{ static const char *str = NULL; static jstring jstr = NULL; + static jmethodID method = 0; JNIEnv *env = GetJNIEnv(); + LOGJSC("eval enter env: %p app: %p", env, globalObj); if (env&&globalObj){ jstring jin = (*env)->NewStringUTF(env,input); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); - jmethodID method = main_class ? (*env)->GetMethodID(env, main_class, "LNjSchemeCall", "(Ljava/lang/String;)Ljava/lang/String;") : NULL; - if(main_class) (*env)->DeleteLocalRef(env, main_class); - if(!method) { - JNI_forward_exception_to_gambit(env); - return "E \"JNI: method LNjSchemeCall not found\""; + // NOW cached global jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); + if(method==0) { + method = (*env)->GetMethodID(env, main_class, "LNjSchemeCall", "(Ljava/lang/String;)Ljava/lang/String;"); + if(!method) { + LOGJSC("ERROR no LNjSchemeCall"); + JNI_forward_exception_to_gambit(env); + return "E \"JNI: method LNjSchemeCall not found\""; + } } + /* if(main_class) (*env)->DeleteLocalRef(env, main_class); */ if(jstr) { (*env)->ReleaseStringUTFChars(env, jstr, str); jstr = NULL; } jstr = (jstring) (*env)->CallObjectMethod(env, globalObj, method, jin); + /* (*env)->DeleteLocalRef(env, method); (*env)->DeleteLocalRef(env, jin); + */ str = jstr ? (*env)->GetStringUTFChars(env, jstr, 0) : NULL; // (*env)->ReleaseStringUTFChars(env, jstr, str); // we do it upon next call JNI_forward_exception_to_gambit(env); @@ -33,22 +52,35 @@ const char* lnjscheme_eval(const char* input){ return str; } -void lnjscheme_eval_send(const char* input){ +void lnjscheme_eval_send(const char* input) +{ JNIEnv *env = GetJNIEnv(); - if (env&&globalObj){ - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); - jmethodID method = main_class ? (*env)->GetMethodID(env, main_class, "LNjSchemeSend", "(Ljava/lang/String;)V") : NULL; - if(main_class) (*env)->DeleteLocalRef(env, main_class); + static jmethodID method = 0; + if (env) { +#ifdef DEBUG + if(!globalObj) (*env)->FatalError(env, "@SYS_PACKAGE_UNDERSCORE@ lnjscheme_eval_send no global object!"); +#endif if(!method) { - JNI_forward_exception_to_gambit(env); - return; // "E \"JNI: method LNjSchemeSend not found\""; - } else { - jstring jin = (*env)->NewStringUTF(env,input); - (*env)->CallVoidMethod(env, globalObj, method, jin); + method = (*env)->GetMethodID(env, main_class, "LNjSchemeSend", "(Ljava/lang/String;)V"); + if(!method) { + LOGE("lnjscheme_eval_send LNjSchemeSend not found; main_class: %p", main_class); + JNI_forward_exception_to_gambit(env); + return; // "E \"JNI: method LNjSchemeSend not found\""; + } + } + jstring jin = (*env)->NewStringUTF(env, input); +#if LNJSCHEME_PASS_INPUT_AS_GLOBAL_REF + jin = (*env)->NewGlobalRef(env, jin); +#endif + (*env)->CallVoidMethod(env, globalObj, method, jin); +#if LNJSCHEME_PASS_INPUT_AS_GLOBAL_REF + (*env)->DeleteGlobalRef(env, jin); +#endif + /* (*env)->DeleteLocalRef(env, method); (*env)->DeleteLocalRef(env, jin); - JNI_forward_exception_to_gambit(env); - } + */ + JNI_forward_exception_to_gambit(env); } } @@ -59,22 +91,23 @@ const char* lnjscheme_eval_receive_result() { static const char *str = NULL; static jstring jstr = NULL; + static jmethodID method = 0; JNIEnv *env = GetJNIEnv(); - if (env&&globalObj){ + if (env) { if(jstr) { (*env)->ReleaseStringUTFChars(env, jstr, str); jstr = NULL; } - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); - jmethodID method = main_class ? (*env)->GetMethodID(env, main_class, "LNjSchemeResult", "()Ljava/lang/String;") : NULL; - if(main_class) (*env)->DeleteLocalRef(env, main_class); if(!method) { - JNI_forward_exception_to_gambit(env); - return "E \"JNI: method LNjSchemeResult not found\""; - } else { - jstr = (jstring) (*env)->CallObjectMethod(env, globalObj, method); - str = jstr ? (*env)->GetStringUTFChars(env, jstr, 0) : NULL; - // (*env)->ReleaseStringUTFChars(env, jstr, str); // we do it upon next call - (*env)->DeleteLocalRef(env, method); - JNI_forward_exception_to_gambit(env); + method = (*env)->GetMethodID(env, main_class, "LNjSchemeResult", "()Ljava/lang/String;"); + if(!method) { + LOGE("lnjscheme_eval_receive_result LNjSchemeResult not found; main_class: %p", main_class); + JNI_forward_exception_to_gambit(env); + return "E \"JNI: method LNjSchemeResult not found\""; + } } + jstr = (jstring) (*env)->CallObjectMethod(env, globalObj, method); + str = jstr ? (*env)->GetStringUTFChars(env, jstr, 0) : NULL; + // (*env)->ReleaseStringUTFChars(env, jstr, str); // we do it upon next call + /* (*env)->DeleteLocalRef(env, method); */ + JNI_forward_exception_to_gambit(env); } return str; } diff --git a/modules/ln_jscheme/ANDROID_java_activityadditions b/modules/ln_jscheme/ANDROID_java_activityadditions index ff091612..4fc37256 100644 --- a/modules/ln_jscheme/ANDROID_java_activityadditions +++ b/modules/ln_jscheme/ANDROID_java_activityadditions @@ -1,10 +1,13 @@ /* LNjScheme -*- mode: java; c-basic-offset: 2; -*- */ /* # Helper methods */ + java.text.SimpleDateFormat ln_log_date_formatter = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss "); String TAG = "@SYS_APPNAME@"; +private final static boolean ANDROID_LOG_LN_JSCHEME = true; // default: false; for debug + public void ln_log(String msg) { String m = ln_log_date_formatter.format(new java.util.Date()) + msg; System.err.println(TAG + ": " + m); @@ -109,23 +112,25 @@ public void LNjSchemeSend(String msg) { (new java.util.concurrent.Callable() { @Override public Object call() throws Exception { - // ln_log("invocation of " + this + " evaluating."); + if(ANDROID_LOG_LN_JSCHEME) ln_log("invocation of " + this + " evaluating"); if(in.isEOF(expr)) throw new Exception("invalid input"); return LNjSchemeEvaluate(expr); } }); - // ln_log("Sending to UI: " + job + " for: " + expr); + if(ANDROID_LOG_LN_JSCHEME) ln_log("Sending to UI: " + job + " for: " + expr); LNjSchemeJob = job; + // TBD: There should be no need for this being a threadof it's own, is it? new Thread() { @Override public void run() { - // ln_log("LNjScheme waiting for completion"); + if(ANDROID_LOG_LN_JSCHEME) ln_log("LNjScheme waiting for completion"); try { LNjSchemeJob.get(); } catch (Exception e) { // InterruptedException java.util.concurrent.ExecutionException // FIXME: Do something sensible here! } - // ln_log("LNjScheme notifying result"); + if(ANDROID_LOG_LN_JSCHEME) ln_log("LNjScheme notifying result"); nativeEvent(126,0,0); + if(ANDROID_LOG_LN_JSCHEME) ln_log("LNjScheme native event 126 done."); } }.start(); runOnUiThread(job); @@ -135,21 +140,21 @@ public String LNjSchemeResult() { try { Object result = LNjSchemeJob != null ? LNjSchemeJob.get() : null; LNjSchemeJob = null; - // ln_log("got result from UI"); + if(ANDROID_LOG_LN_JSCHEME) ln_log("got result from UI"); java.io.StringWriter buf = new java.io.StringWriter(); java.io.PrintWriter port = new java.io.PrintWriter(buf); port.println("D"); LNjScheme.SchemeUtils.write(result, port, true); return buf.toString(); } catch (java.util.concurrent.ExecutionException e) { - // ln_log("got error from call"); + if(ANDROID_LOG_LN_JSCHEME) ln_log("got error from call"); java.io.StringWriter buf = new java.io.StringWriter(); java.io.PrintWriter port = new java.io.PrintWriter(buf); port.println("E"); LNjScheme.SchemeUtils.write(("" + e.getCause()).toCharArray(), port, true); return buf.toString(); } catch (Exception e) { - // ln_log("got exception from call"); + if(ANDROID_LOG_LN_JSCHEME) ln_log("got exception from call"); java.io.StringWriter buf = new java.io.StringWriter(); java.io.PrintWriter port = new java.io.PrintWriter(buf); port.println("E"); diff --git a/modules/ln_jscheme/ANDROID_java_additions b/modules/ln_jscheme/ANDROID_java_additions index 150d224a..aa995da9 100644 --- a/modules/ln_jscheme/ANDROID_java_additions +++ b/modules/ln_jscheme/ANDROID_java_additions @@ -1,4 +1,5 @@ -/*-*-java -*-*/ +/* LNjScheme -*- mode: java; c-basic-offset: 2; -*- */ + class LNMethod extends LNjScheme.Procedure { String name = null; diff --git a/modules/ln_jscheme/ANDROID_java_oncreate b/modules/ln_jscheme/ANDROID_java_oncreate index dcaeebbf..80a71247 100644 --- a/modules/ln_jscheme/ANDROID_java_oncreate +++ b/modules/ln_jscheme/ANDROID_java_oncreate @@ -1,6 +1,4 @@ /* LNjScheme -*- mode: java; c-basic-offset: 2; -*- */ -LNjSchemeSession = new LNjScheme.Scheme(new String[0]){ - String TAG = "calculator"; if(LNjSchemeSession==null) { @@ -125,7 +123,11 @@ LNjSchemeEvaluateNoSync (LNjScheme.Scheme.cons (LNjScheme.Scheme.sym("define"), LNjScheme.Scheme.list - (LNjScheme.Scheme.sym("ln-mglview"), - mGLView + (LNjScheme.Scheme.sym("lambdanative-glview"), + new LNMethod("lambdanative-glview") { + public Object apply(LNjScheme.Scheme interpreter, Object args) { + return mGLView; + }} ))); + // eof: LNjScheme diff --git a/modules/ln_jscheme/ln_jscheme.scm b/modules/ln_jscheme/ln_jscheme.scm index d3804486..0f1c8fdf 100644 --- a/modules/ln_jscheme/ln_jscheme.scm +++ b/modules/ln_jscheme/ln_jscheme.scm @@ -1,10 +1,10 @@ (cond-expand - (android - (c-declare "extern const char* android_app_class();") - (define android-app-class (c-lambda () char-string "android_app_class"))) - (else (define (android-app-class) - (log-error "android-app-class: called in non-Android context") - "android-app-class"))) + (android + (c-declare "extern const char* android_app_class();") + (define android-app-class (c-lambda () char-string "android_app_class"))) + (else (define (android-app-class) + (log-error "android-app-class: called in non-Android context") + "android-app-class"))) (define call-with-lnjscheme-result ;; SIGNATURE (NAME S-EXPR #!optional (RECEIVER force)) @@ -76,9 +76,8 @@ NULL; ;; etc. by default force it expecting the application to ;; abort on any exception. (receiver promise)))) - (jscheme-send (object->string obj)) - (thread-yield!)) - jscheme-call)) + (jscheme-send (object->string obj))) + jscheme-call)) (define (lnjscheme-future obj) ;; a promise waiting for the evaluation of OBJ diff --git a/modules/localnotification/ANDROID_c_additions b/modules/localnotification/ANDROID_c_additions index c13eda63..67b7faa2 100644 --- a/modules/localnotification/ANDROID_c_additions +++ b/modules/localnotification/ANDROID_c_additions @@ -7,7 +7,6 @@ int android_localnotification_schedule(char* msg, double ts, int repeat, char* s JNIEnv *env = GetJNIEnv(); jstring jmsg = (*env)->NewStringUTF(env,msg); jstring jsound = (*env)->NewStringUTF(env,sound); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); if (env&&globalObj){ jmethodID method = (*env)->GetMethodID(env, main_class, "scheduleNotification", "(Ljava/lang/String;DILjava/lang/String;)I"); return (*env)->CallIntMethod(env, globalObj, method, jmsg, ts, repeat, jsound); @@ -24,7 +23,6 @@ int android_localnotification_schedule_batch(char* text[], double* time, int* re int android_localnotification_cancel(int id){ JNIEnv *env = GetJNIEnv(); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); if (env&&globalObj){ jmethodID method = (*env)->GetMethodID(env, main_class, "notification_cancel", "(I)I"); return (*env)->CallIntMethod(env, globalObj, method, id); @@ -33,7 +31,6 @@ int android_localnotification_cancel(int id){ int android_localnotification_cancelall(){ JNIEnv *env = GetJNIEnv(); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); if (env&&globalObj){ jmethodID method = (*env)->GetMethodID(env, main_class, "notification_cancelall", "()I"); return (*env)->CallIntMethod(env, globalObj, method); diff --git a/modules/native-keypad/ANDROID_c_additions b/modules/native-keypad/ANDROID_c_additions index ec71a69e..9f932988 100644 --- a/modules/native-keypad/ANDROID_c_additions +++ b/modules/native-keypad/ANDROID_c_additions @@ -3,7 +3,14 @@ void android_show_keypad(){ jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); if (env&&globalObj){ jmethodID method = (*env)->GetMethodID(env, main_class, "showKeyboard", "()V"); - (*env)->CallVoidMethod(env, globalObj, method); + /* (*env)->DeleteLocalRef(env, main_class); */ + if(!method) { + JNI_forward_exception_to_gambit(env); + } else { + (*env)->CallVoidMethod(env, globalObj, method); + /* (*env)->DeleteLocalRef(env, method); */ + JNI_forward_exception_to_gambit(env); + } } } void android_hide_keypad(int orientation){ @@ -11,6 +18,13 @@ void android_hide_keypad(int orientation){ jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); if (env&&globalObj){ jmethodID method = (*env)->GetMethodID(env, main_class, "hideKeyboard", "()V"); - (*env)->CallVoidMethod(env, globalObj, method); + /* (*env)->DeleteLocalRef(env, main_class); */ + if(!method) { + JNI_forward_exception_to_gambit(env); + } else { + (*env)->CallVoidMethod(env, globalObj, method); + /* (*env)->DeleteLocalRef(env, method); */ + JNI_forward_exception_to_gambit(env); + } } } diff --git a/modules/serial/ANDROID_c_additions b/modules/serial/ANDROID_c_additions index 327dcd76..321d077d 100644 --- a/modules/serial/ANDROID_c_additions +++ b/modules/serial/ANDROID_c_additions @@ -1,3 +1,6 @@ +// TBD: untested, maybe we need to pass global references +// TBD FIXME: unlikely to work at all. Test case required. + static int needs_init=1; static jobject serial_object=NULL; static jclass serial_class=NULL; @@ -15,7 +18,6 @@ int serial_init(){ JNIEnv *env = GetJNIEnv(); if (env) { serial_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/RS232Manager"); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); if (serial_class && main_class) { jmethodID getRS232Instance = (*env)->GetStaticMethodID(env,main_class, "getRS232Instance","()L@SYS_PACKAGE_SLASH@/RS232Manager;"); s_serial_open = (*env)->GetMethodID(env,serial_class, "open", "(Ljava/lang/String;IIII)I"); diff --git a/modules/vibrate/ANDROID_c_additions b/modules/vibrate/ANDROID_c_additions index 0ca472f1..052c1380 100644 --- a/modules/vibrate/ANDROID_c_additions +++ b/modules/vibrate/ANDROID_c_additions @@ -1,6 +1,7 @@ +// TBD: untested, maybe we need to pass global references + void android_vibrate(){ JNIEnv *env = GetJNIEnv(); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); if (env&&globalObj){ jmethodID method = (*env)->GetMethodID(env, main_class, "vibrate", "()V"); (*env)->CallVoidMethod(env, globalObj, method); @@ -9,7 +10,6 @@ void android_vibrate(){ void android_timed_vibrate(int milli){ JNIEnv *env = GetJNIEnv(); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); if (env&&globalObj){ jmethodID method = (*env)->GetMethodID(env, main_class, "timedVibrate", "(I)V"); (*env)->CallVoidMethod(env, globalObj, method, milli); diff --git a/modules/videoplayer/ANDROID_c_additions b/modules/videoplayer/ANDROID_c_additions index 703dc4f3..2d0da3f9 100644 --- a/modules/videoplayer/ANDROID_c_additions +++ b/modules/videoplayer/ANDROID_c_additions @@ -1,8 +1,8 @@ +// TBD: untested, maybe we need to pass global references void android_videoplayer(char* mov_name, int orientation) { JNIEnv *env = GetJNIEnv(); - jclass main_class = (*env)->FindClass(env, "@SYS_PACKAGE_SLASH@/@SYS_APPNAME@"); if (env&&globalObj){ jstring jmov = (*env)->NewStringUTF(env,mov_name); jmethodID method = (*env)->GetMethodID(env, main_class, "startVideoPlayer", "(Ljava/lang/String;I)V"); diff --git a/modules/webview/webview.scm b/modules/webview/webview.scm index e2302b34..cf15382f 100644 --- a/modules/webview/webview.scm +++ b/modules/webview/webview.scm @@ -6,7 +6,7 @@ (lambda (expr) (cond-expand (android - ;; (log-debug "jScheme EVAL:" 1 expr) + (log-debug "jScheme EVAL:" 1 expr) (call-with-lnjscheme-result expr (lambda (promise) @@ -19,7 +19,9 @@ (pretty-print expr port) (display "EXN: " port) (display-exception exn port))))) - (lambda () (success (force promise))))))) + (cond + ((procedure? promise) (lambda () (success (promise)))) + (else (lambda () (success (force promise))))))))) (else #f))) body)) 'jscheme-worker)) @@ -45,6 +47,7 @@ (onclick-set! (method "LNjScheme_Set_OnClickListener" app "android.view.View" "java.lang.Object")) (checkOrRequestPermission (method "checkOrRequestPermission" app "java.lang.String")) (loadUrl (method "loadUrl" "android.webkit.WebView" "java.lang.String")) + (getUrl (method "getUrl" "android.webkit.WebView")) (wv-can-go-back? (method "canGoBack" "android.webkit.WebView")) (wv-goBack! (method "goBack" "android.webkit.WebView")) (wv-setClient! (method "setWebViewClient" "android.webkit.WebView" "android.webkit.WebViewClient")) @@ -67,11 +70,12 @@ (back-pressed-h #f) (reload (new "android.widget.Button" this)) (Button3 (new "android.widget.Button" this)) + (Bcopy (new "android.widget.Button" this)) ) (define (switch-back-to-glgui! v) (on-back-pressed back-pressed-h) (set! back-pressed-h #f) - (setContentView this ln-mglview)) + (setContentView this (lambdanative-glview))) (define (back-pressed) (if (wv-can-go-back? wv) (wv-goBack! wv) (switch-back-to-glgui! frame))) ;; (webview! wv 'onpagecomplete (lambda (view url) (log-message "webview post visual state"))) @@ -86,20 +90,23 @@ (begin (setText Button3 (String "JS+-")) (onclick-set! this Button3 js+-)) + (begin + (setText Bcopy (String "COPY")) + (onclick-set! this Bcopy (lambda _ (setClipboardContent (getUrl wv))))) (wvs-zoom-support-set! wvs #t) (wvs-zoom-builtin-set! wvs #t) (wvs-zoom-builtin-controls-set! wvs #f)) - (arrange-in-order! navigation (list back reload Button3)) + (arrange-in-order! navigation (list back reload Button3 Bcopy)) (setText back (String "Back")) (setText reload (String "Reload")) (onclick-set! this back switch-back-to-glgui!) (onclick-set! this reload (lambda (v) ((method "reload" "android.webkit.WebView") wv))) (set-layout-vertical! frame) - (set-layout-vertical! frame) (arrange-in-order! frame (list navigation wv)) (lambda (cmd arg) (case cmd ((load) (webview! wv cmd arg)) + ((getURL) (getUrl wv)) (else (if (not back-pressed-h) (begin @@ -120,6 +127,7 @@ (cond ((eq? a1 #t) '(webview #t #t)) ((string? a1) `(webview 'load ,a1)) + ((eq? a1 'getURL) `(webview 'getURL #t)) (else (otherwise)))))))) (webview-running #f)) (lambda args diff --git a/targets/android/build-binary b/targets/android/build-binary index 098c4fb9..939b6160 100755 --- a/targets/android/build-binary +++ b/targets/android/build-binary @@ -256,6 +256,8 @@ fi echo " => transferring java hook.." appdir=$tmpdir/src/$SYS_PACKAGE_SLASH ac_output loaders/android/bootstrap.java.in $appdir/$SYS_APPNAME.java +ac_output loaders/android/NativeGLSurfaceView.java.in $appdir/NativeGLSurfaceView.java +ac_output loaders/android/GLState.java.in $appdir/GLState.java echo " => transferring java public classes and jars from modules.." for m in $modules; do @@ -365,7 +367,7 @@ if [ `has_module hybridapp-xwalk` = yes ]; then cd $tmpdir if [ $use_android_tool = yes ]; then $ANDROIDSDK/tools/android --silent update project --target $ANDROIDSDKTARGET --path $tmpdir --library ../libxwalk - else + else assert "## gradle is not supported" fi cd "$xwalk_here" @@ -377,7 +379,7 @@ cat > $tmpdir/res/layout/main.xml << __EOF __EOF else - assert "crosswalk library not found?" + assert "crosswalk library not found?" fi fi @@ -444,4 +446,10 @@ if [ -d $ANDROIDSDK/tools.gradle ]; then ln -s $ANDROIDSDK/tools.gradle $ANDROIDSDK/tools fi +# Local Variables: +# mode: shell-script +# sh-basic-offset: 2 +# comment-column: 0 +# End: + #eof diff --git a/targets/bb10/build-binary b/targets/bb10/build-binary index 23346d2d..0e92aeb9 100755 --- a/targets/bb10/build-binary +++ b/targets/bb10/build-binary @@ -57,7 +57,7 @@ if [ `is_gui_app` = yes ]; then ac_output loaders/qnx/bootstrap.c.in $tmpdir/bootstrap.c else if [ `is_standalone_app` = "no" ]; then - cp loaders/common/main.c $tmpdir + ac_output loaders/common/main.c.in $tmpdir/main.c fi fi cd $tmpdir diff --git a/targets/carlson-minot/build-binary b/targets/carlson-minot/build-binary index 65ca4406..d77305e4 100755 --- a/targets/carlson-minot/build-binary +++ b/targets/carlson-minot/build-binary @@ -49,7 +49,7 @@ rmifexists "$apptgtdir" mkdir "$apptgtdir" tmpdir=`mktemp -d $SYS_TMPDIR/tmp.XXXXXX` if [ `is_standalone_app` = "no" ]; then - cp loaders/common/main.c $tmpdir + ac_output loaders/common/main.c.in $tmpdir/main.c fi cd $tmpdir diff --git a/targets/freebsd/build-binary b/targets/freebsd/build-binary index efddf859..61556cd3 100755 --- a/targets/freebsd/build-binary +++ b/targets/freebsd/build-binary @@ -42,7 +42,7 @@ if [ `is_gui_app` = yes ]; then cp loaders/x11/x11_microgl.c $tmpdir fi if [ `is_standalone_app` = "no" ]; then - cp loaders/common/main.c $tmpdir + ac_output loaders/common/main.c.in $tmpdir/main.c fi cd $tmpdir diff --git a/targets/linux/build-binary b/targets/linux/build-binary index f5f0eaf6..38ebb359 100755 --- a/targets/linux/build-binary +++ b/targets/linux/build-binary @@ -39,10 +39,10 @@ rmifexists "$apptgtdir" mkdir "$apptgtdir" tmpdir=`mktemp -d $SYS_TMPDIR/tmp.XXXXXX` if [ `is_gui_app` = yes ]; then - cp loaders/x11/x11_microgl.c $tmpdir + cp loaders/x11/x11_microgl.c $tmpdir fi if [ `is_standalone_app` = "no" ]; then - cp loaders/common/main.c $tmpdir + ac_output loaders/common/main.c $tmpdir/main.c fi cd $tmpdir @@ -61,6 +61,9 @@ if [ "$SYS_MODE" = "debug" ]; then cflag_additions="$cflag_additions -pg" fi +## Links REALLY static +#cflag_additions="$cflag_additions -static" + if [ `is_standalone_app` = "yes" ]; then veval "$SYS_CC -I$SYS_PREFIX/include \ $cflag_additions -DUSECONSOLE -o $tgt \ diff --git a/targets/linux486/build-binary b/targets/linux486/build-binary index a069210f..7d3ea2e0 100755 --- a/targets/linux486/build-binary +++ b/targets/linux486/build-binary @@ -42,7 +42,7 @@ if [ `is_gui_app` = yes ]; then cp loaders/x11/x11_microgl.c $tmpdir fi if [ `is_standalone_app` = "no" ]; then - cp loaders/common/main.c $tmpdir + ac_output loaders/common/main.c.in $tmpdir/main.c fi cd $tmpdir diff --git a/targets/macosx/build-binary b/targets/macosx/build-binary index 07a3128c..58f2b9cc 100755 --- a/targets/macosx/build-binary +++ b/targets/macosx/build-binary @@ -109,7 +109,7 @@ else cd "$tmpdir" veval "$SYS_HOST_CC $cflag_additions $ldflag_additions -framework OpenGL -framework Cocoa -framework ApplicationServices -framework CoreAudio -framework AudioUnit -framework AudioToolbox -framework CoreFoundation -framework CoreServices -framework Foundation -framework Accelerate -x objective-c -I$SYS_PREFIX/include -L$SYS_PREFIX/lib -o $apptgtdir/$SYS_APPNAME -lpayload *.m" else - cp loaders/common/main.c $tmpdir + ac_output loaders/common/main.c.in $tmpdir/main.c cd "$tmpdir" veval "$SYS_HOST_CC $cflag_additions $ldflag_additions -framework ApplicationServices -framework CoreAudio -framework AudioUnit -framework AudioToolbox -framework CoreFoundation -framework CoreServices -framework Foundation -I$SYS_PREFIX/include -L$SYS_PREFIX/lib -DUSECONSOLE -o $apptgtdir/$SYS_APPNAME -lpayload main.c" fi diff --git a/targets/netbsd/build-binary b/targets/netbsd/build-binary index a21f1ce0..2688f999 100755 --- a/targets/netbsd/build-binary +++ b/targets/netbsd/build-binary @@ -42,7 +42,7 @@ if [ `is_gui_app` = yes ]; then cp loaders/x11/x11_microgl.c $tmpdir fi if [ `is_standalone_app` = "no" ]; then - cp loaders/common/main.c $tmpdir + ac_output loaders/common/main.c.in $tmpdir/main.c fi cd $tmpdir diff --git a/targets/openbsd/build-binary b/targets/openbsd/build-binary index 079ee485..8ae5c13e 100755 --- a/targets/openbsd/build-binary +++ b/targets/openbsd/build-binary @@ -42,7 +42,7 @@ if [ `is_gui_app` = yes ]; then cp loaders/x11/x11_microgl.c $tmpdir fi if [ `is_standalone_app` = "no" ]; then - cp loaders/common/main.c $tmpdir + ac_output loaders/common/main.c.in $tmpdir/main.c fi cd $tmpdir diff --git a/targets/playbook/build-binary b/targets/playbook/build-binary index 6a3e772f..72f6240d 100755 --- a/targets/playbook/build-binary +++ b/targets/playbook/build-binary @@ -57,7 +57,7 @@ if [ `is_gui_app` = yes ]; then ac_output loaders/qnx/bootstrap.c.in $tmpdir/bootstrap.c else if [ `is_standalone_app` = "no" ]; then - cp loaders/common/main.c $tmpdir + ac_output loaders/common/main.c.in $tmpdir/main.c fi fi cd $tmpdir diff --git a/targets/sitara/build-binary b/targets/sitara/build-binary index 65ca4406..d77305e4 100755 --- a/targets/sitara/build-binary +++ b/targets/sitara/build-binary @@ -49,7 +49,7 @@ rmifexists "$apptgtdir" mkdir "$apptgtdir" tmpdir=`mktemp -d $SYS_TMPDIR/tmp.XXXXXX` if [ `is_standalone_app` = "no" ]; then - cp loaders/common/main.c $tmpdir + ac_output loaders/common/main.c.in $tmpdir/main.c fi cd $tmpdir diff --git a/targets/win32/build-binary b/targets/win32/build-binary index 3895c0e3..35259d4e 100755 --- a/targets/win32/build-binary +++ b/targets/win32/build-binary @@ -42,7 +42,7 @@ if [ `is_gui_app` = yes ]; then cp loaders/win32/win32_microgl.c $tmpdir fi if [ `is_standalone_app` = "no" ]; then - cp loaders/common/main.c $tmpdir + ac_output loaders/common/main.c.in $tmpdir/main.c fi cd $tmpdir