From 5b2fe7b3fc707ef02eaeeffb160a4b6346456015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Sun, 14 Mar 2021 13:19:38 +0100 Subject: [PATCH 01/23] CORE: fix extension of srfi-23 copy-time should not copy seconds to nanoseconds and vice versa --- modules/ln_core/time.scm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/ln_core/time.scm b/modules/ln_core/time.scm index 12048920..19fecc01 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 From 0603b04929d4866ccaba7d28dd74bba966650760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Mon, 11 Jan 2021 18:07:47 +0100 Subject: [PATCH 02/23] GLGUI: fix typo and backward compatible dispatch prefering new code --- modules/ln_glgui/primitives.scm | 37 +++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) 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)) From 145d9b525156e9914d8285f487e435b7a3cd9daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Wed, 28 Apr 2021 19:54:24 +0200 Subject: [PATCH 03/23] GLCORE: fix missing globalbinding and unbound variable Also: - add another wrapper for glTranslatef This one allows to feed it an f32vector, no checks. Useful for upcomming, though unrelated, improvements. - add wrapper and macros around texture handling --- modules/ln_glcore/glcore-ffi.scm | 10 + modules/ln_glcore/glcore.scm | 614 +++++++++++++++++++++---------- 2 files changed, 429 insertions(+), 195 deletions(-) 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) From 3c8055d5072fbf1bded4024212884ba714659fcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Sun, 21 Feb 2021 13:19:26 +0100 Subject: [PATCH 04/23] X11: fix hanging display --- loaders/x11/x11_microgl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/loaders/x11/x11_microgl.c b/loaders/x11/x11_microgl.c index cd70d8b5..035cb5f0 100644 --- a/loaders/x11/x11_microgl.c +++ b/loaders/x11/x11_microgl.c @@ -111,6 +111,7 @@ void microgl_refresh() event.type = Expose; event.xany.window = win.Win; XSendEvent(Dpy, win.Win, False, Expose, &event); + XSync(Dpy, 0); } // https://tronche.com/gui/x/xlib/events/keyboard-pointer/keyboard-pointer.html From 9f5e1c5afb31be66a49e10fa53e57a739c926f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Thu, 29 Apr 2021 12:09:53 +0200 Subject: [PATCH 05/23] CLIPBOARD: make clipboard-copy work under Linux TBD: rename to something like `copy-to-clipboard!` for consistency with general Scheme naming conventions. --- modules/clipboard/clipboard.scm | 91 ++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 36 deletions(-) diff --git a/modules/clipboard/clipboard.scm b/modules/clipboard/clipboard.scm index 8d3ed529..c2a59bb3 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 #< Date: Thu, 29 Apr 2021 12:11:15 +0200 Subject: [PATCH 06/23] X11 CLIPBOARD: fix off-by-one --- loaders/x11/x11_microgl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loaders/x11/x11_microgl.c b/loaders/x11/x11_microgl.c index 035cb5f0..808a0741 100644 --- a/loaders/x11/x11_microgl.c +++ b/loaders/x11/x11_microgl.c @@ -181,7 +181,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; } From 4e9612e2e32b7d456854942fd632ae38a6c189b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Tue, 30 Mar 2021 19:59:59 +0200 Subject: [PATCH 07/23] GAMBIT: fix incorrect numbers - follwing fix in upstream --- ...ace-allocation-report-by-time-specia.patch | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 libraries/libgambit/0001-Fix-incorrect-space-allocation-report-by-time-specia.patch 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 From cb12f0dbb56199a3e9b5cc7c7b2a21ecf57760fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Sun, 11 Apr 2021 12:23:38 +0200 Subject: [PATCH 08/23] JSON: add encoding options choice --- modules/json/json#.scm | 1 + modules/json/json.scm | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) 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..18e50628 100644 --- a/modules/json/json.scm +++ b/modules/json/json.scm @@ -52,6 +52,7 @@ 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 debug? #f) @@ -64,6 +65,16 @@ 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)) + (set! use-symbols? symbols) + (set! use-tables? tables) + (set! use-symbols-for-keys? keys)) + +(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 +186,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 +364,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))) From 4571ef9dc132383f35f4c2f31ca06974aa524822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Sun, 11 Apr 2021 13:15:46 +0200 Subject: [PATCH 09/23] ANDROID CLIPBOARD: support coerce mimetypes to text/plain --- modules/clipboard/ANDROID_java_activityadditions | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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){ From f8c84e1c276f1cad0eb904dca0133393f71e8044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Sat, 24 Apr 2021 19:48:23 +0200 Subject: [PATCH 10/23] BUILD: ensure failures to compile hook.c break build --- make.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/make.sh b/make.sh index 3b48f374..c61d0e66 100755 --- a/make.sh +++ b/make.sh @@ -378,6 +378,7 @@ compile_payload() hookhash=`stringhash "apps/$SYS_APPNAME/hook.c"` hctgt="$SYS_PREFIX/build/$hookhash.c" hotgt=`echo "$hctgt" | sed 's/c$/o/'` + rmifexists "$hotgt" cp loaders/hook/hook.c "$hctgt" veval "$SYS_ENV $SYS_CC $payload_cdefs $languages_def -c -o $hotgt $hctgt -I$SYS_PREFIX/include" assertfile $hotgt From b39b45b9692fd4509e43942949a0a48458c60942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Sat, 13 Feb 2021 13:28:11 +0100 Subject: [PATCH 11/23] MICROGL: add support for pageup/down --- loaders/win32/win32_microgl.c | 2 ++ loaders/x11/x11_microgl.c | 3 +++ 2 files changed, 5 insertions(+) diff --git a/loaders/win32/win32_microgl.c b/loaders/win32/win32_microgl.c index 6d40dbc0..dbad8230 100644 --- a/loaders/win32/win32_microgl.c +++ b/loaders/win32/win32_microgl.c @@ -95,6 +95,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 diff --git a/loaders/x11/x11_microgl.c b/loaders/x11/x11_microgl.c index 808a0741..cc25dbed 100644 --- a/loaders/x11/x11_microgl.c +++ b/loaders/x11/x11_microgl.c @@ -140,6 +140,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 From 9c435d23bba506565d966a73588bf13d97f3f90d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Sat, 13 Feb 2021 16:06:20 +0100 Subject: [PATCH 12/23] MICROGL X11: avoid some of the needless redraw events --- loaders/common/main.c | 7 +++++++ loaders/x11/x11_microgl.c | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/loaders/common/main.c b/loaders/common/main.c index 6697fe46..0d61bcbe 100644 --- a/loaders/common/main.c +++ b/loaders/common/main.c @@ -37,6 +37,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // microgl based main loop +#if defined(LINUX) +// new code path +#define LEGACY_2021 0 +#else +#define LEGACY_2021 1 +#endif + #include "LNCONFIG.h" #include diff --git a/loaders/x11/x11_microgl.c b/loaders/x11/x11_microgl.c index cc25dbed..a87b2599 100644 --- a/loaders/x11/x11_microgl.c +++ b/loaders/x11/x11_microgl.c @@ -250,7 +250,7 @@ void microgl_pollevents(void) motion=1; break; case Expose: - expose=1; + expose = expose || (event.xexpose.width && event.xexpose.height) ? 1 : 0; break; case ClientMessage: if( (Atom) event.xclient.data.l[ 0 ] == win.WMDeleteWindow ) From fc63e27495432c2bb12a21feb41981901c208981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Thu, 29 Apr 2021 14:09:13 +0200 Subject: [PATCH 13/23] EVENTLOOP: ensure exclusive Gambit thread exists. At 2020-08-07 Marc Feeley wrote that the situation how lambdanative uses Gambit (at least on Android) is not supported by Gambit, he wrote: Any number of Scheme threads can call C functions, but only one Scheme thread at a time can do a Scheme to C call that calls back to Scheme. This is because the C stack is shared by all the Scheme to C calls... if the Scheme threads T1 and T2 call C (in the order T1 then T2) and if T1 returns to Scheme while T2's call is still in progress, then T2's C stack frame will be removed when T1's C stack frame is removed (because T2's frame was added after T1's frame). Under Android both the UI thread and the OpenGL thread call into Gambit, all bets are off. Race conditions are not avoidable. This seems to explain random crashes I observed eventually to the point that I could reproduce them by tapping the screen fast enough. The only reasonable escape I could conceive was to use Gambit as Marc intented: one single thread running contineously. The upshot is: for Android and X11 based systems running Gambit from a dedicated thread avoids jitter and event loop related delays for Gambit threads. In practice this work much smoother. --- ## Status This change will certainly break some applications: 1. At least those, where race conditions are exacerbated to sure conflicts. 2. A batch of untested modifications required for compatible with this patch is half prepared already. Notably the 'serial' module is unlikely to work out of the box. 3. I assume that those other X11 based system should mostly work. Non tested, non available here. This change avoids many C-to-Scheme calls, which where not properly protected and runs the event loop in a pthread of its own for X11 and Android. Under win32 it still calls the previos way, but used EVENT_IDLE to better wait with the Gambit thread system than block threads in Windows. ======================================================================= For Android the change includes a new NativeGLSurfaceView and supporting GLState class. These work so far more reliable than the version before. But the are premature. Frequently the android app will come up first time after installation but not again. Forced stop of the app and restart helps. Further restarts magically work. ======================================================================= Tested are so far Android, Linux, Windows. --- LNCONFIG.h.in | 10 + languages/scm.c | 4 + loaders/android/GLState.java.in | 985 +++++++++++++++ loaders/android/NativeGLSurfaceView.java.in | 1080 +++++++++++++++++ loaders/android/bootstrap.c.in | 259 +++- loaders/android/bootstrap.java.in | 222 +++- loaders/common/main.c | 52 +- loaders/hook/hook.c | 74 +- loaders/win32/win32_microgl.c | 31 +- loaders/x11/x11_microgl.c | 136 ++- modules/audio/ANDROID_c_additions | 97 +- modules/audio/ANDROID_java_additions | 134 +- modules/audio/ANDROID_java_oncreate | 10 +- modules/audio/audiofile.scm | 24 +- modules/audioaux/ANDROID_c_additions | 5 +- modules/clipboard/ANDROID_c_additions | 47 +- modules/clipboard/ANDROID_java_oncreate | 1 + modules/config/config.scm | 49 +- modules/eventloop/ANDROID_c_additions | 4 +- modules/eventloop/eventloop.scm | 334 +++-- modules/ln_glgui/glgui.scm | 67 +- modules/ln_jscheme/ANDROID_c_additions | 95 +- .../ln_jscheme/ANDROID_java_activityadditions | 19 +- modules/ln_jscheme/ANDROID_java_additions | 3 +- modules/ln_jscheme/ANDROID_java_oncreate | 10 +- modules/ln_jscheme/ln_jscheme.scm | 17 +- modules/webview/webview.scm | 18 +- targets/android/build-binary | 12 +- targets/linux/build-binary | 3 + 29 files changed, 3294 insertions(+), 508 deletions(-) create mode 100644 loaders/android/GLState.java.in create mode 100644 loaders/android/NativeGLSurfaceView.java.in 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/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: + *
    + *
  • {@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. + */ + +/* ****************************************** */ + +/** + * 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..b5dc2457 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 1 // 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 index 0d61bcbe..b29c0fd2 100644 --- a/loaders/common/main.c +++ b/loaders/common/main.c @@ -37,13 +37,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // microgl based main loop -#if defined(LINUX) -// new code path -#define LEGACY_2021 0 -#else -#define LEGACY_2021 1 -#endif - #include "LNCONFIG.h" #include @@ -69,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; @@ -89,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; @@ -123,18 +105,19 @@ int main(int argc, char *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 @@ -158,17 +141,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(); @@ -177,8 +149,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 index 27e1787b..7b27be86 100644 --- a/loaders/hook/hook.c +++ b/loaders/hook/hook.c @@ -1,4 +1,7 @@ // lambdanative hook +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include /* Obtain O_* constant definitions */ +#include #include #include @@ -35,6 +38,7 @@ int main(int argc, char *argv[]) #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); @@ -42,11 +46,13 @@ int main(int argc, char *argv[]) #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; @@ -54,18 +60,82 @@ int main(int argc, char *argv[]) #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) { - lambdanative_payload_setup(); 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 diff --git a/loaders/win32/win32_microgl.c b/loaders/win32/win32_microgl.c index dbad8230..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 @@ -125,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 @@ -239,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); @@ -355,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 a87b2599..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; @@ -114,6 +116,35 @@ void microgl_refresh() 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; @@ -194,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 ); @@ -209,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; @@ -252,16 +284,21 @@ void microgl_pollevents(void) case Expose: 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); @@ -269,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 ) @@ -297,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; @@ -317,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; } @@ -325,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; } @@ -334,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) { @@ -412,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}; @@ -428,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 @@ -470,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; } @@ -490,7 +534,7 @@ void microgl_close() #endif if (win.KeyboardGrabbed) { XUngrabKeyboard(Dpy,CurrentTime); - } + } if (win.PointerGrabbed) { XUngrabPointer(Dpy, CurrentTime); } 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/clipboard/ANDROID_c_additions b/modules/clipboard/ANDROID_c_additions index cbe03487..dd245adf 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,18 +22,25 @@ 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;"); - jstr = (jstring) (*env)->CallObjectMethod(env, globalObj, method, jstr); - str = (*env)->GetStringUTFChars(env, jstr, 0); + 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); return str; } 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_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/config/config.scm b/modules/config/config.scm index 955d4ade..73326271 100644 --- a/modules/config/config.scm +++ b/modules/config/config.scm @@ -87,6 +87,48 @@ end-of-c-declare (define force-terminate (c-lambda () void "force_terminate")) +(define microgl-swapbuffers + (c-lambda + () void " +#if !defined(STANDALONE) + microgl_swapbuffers(); +#endif +")) + +(define microgl-window + (c-lambda + (int int) void " +#if !defined(STANDALONE) + microgl_window(___arg1, ___arg2); +#endif +")) + +(define microgl-fullscreen + (c-lambda + (int int) void " +#if !defined(STANDALONE) + microgl_fullscreen(___arg1, ___arg2); +#endif +")) + +(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/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_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/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/linux/build-binary b/targets/linux/build-binary index f5f0eaf6..f8354952 100755 --- a/targets/linux/build-binary +++ b/targets/linux/build-binary @@ -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 \ From 8b3c78416f557ad503585ce016a12a33a281505e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Wed, 28 Apr 2021 18:29:00 +0200 Subject: [PATCH 14/23] EVENTLOOP: untested changes required by #fc63e27 --- modules/camera/ANDROID_c_additions | 7 +++---- modules/gps/ANDROID_c_additions | 2 -- modules/localnotification/ANDROID_c_additions | 3 --- modules/native-keypad/ANDROID_c_additions | 18 ++++++++++++++++-- modules/serial/ANDROID_c_additions | 4 +++- modules/vibrate/ANDROID_c_additions | 4 ++-- modules/videoplayer/ANDROID_c_additions | 2 +- 7 files changed, 25 insertions(+), 15 deletions(-) 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/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/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"); From 44cbdc0cb4bbabb637d6ff4cb1b37d899744f044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Thu, 29 Apr 2021 19:05:58 +0200 Subject: [PATCH 15/23] ANDROID: switch debug off by default --- loaders/android/bootstrap.c.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loaders/android/bootstrap.c.in b/loaders/android/bootstrap.c.in index b5dc2457..8526f496 100644 --- a/loaders/android/bootstrap.c.in +++ b/loaders/android/bootstrap.c.in @@ -48,7 +48,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define LAMBDANATIVE_JNI_VERSION JNI_VERSION_1_4 -#define DEBUG 1 // FIXME: The DEBUG flag is currently not passed when compiling this file! +#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 From 30aec58b32b226f1d919c5c7fed96fa5da86e925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Sun, 25 Jul 2021 14:39:08 +0200 Subject: [PATCH 16/23] CLIPBOARD on ANDROID: fix paste working one only --- modules/clipboard/ANDROID_c_additions | 4 ++-- modules/clipboard/clipboard.scm | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/clipboard/ANDROID_c_additions b/modules/clipboard/ANDROID_c_additions index dd245adf..fd8faf8a 100644 --- a/modules/clipboard/ANDROID_c_additions +++ b/modules/clipboard/ANDROID_c_additions @@ -31,9 +31,9 @@ const char *android_clipboard_paste(){ JNI_forward_exception_to_gambit(env); return ""; } - jstr = (jstring) (*env)->CallObjectMethod(env, globalObj, method, jstr); - str = (*env)->GetStringUTFChars(env, jstr, 0); } + jstr = (jstring) (*env)->CallObjectMethod(env, globalObj, method, jstr); + str = (*env)->GetStringUTFChars(env, jstr, 0); // (*env)->ReleaseStringUTFChars(env, jstr, str); return str; } diff --git a/modules/clipboard/clipboard.scm b/modules/clipboard/clipboard.scm index c2a59bb3..73c2185d 100644 --- a/modules/clipboard/clipboard.scm +++ b/modules/clipboard/clipboard.scm @@ -257,7 +257,7 @@ end-of-c-declare ) ;; Function prototypes/bindings -(define clipboard-clear (c-lambda (char-string) bool "clipboard_clear")) +(define clipboard-clear (c-lambda () bool "clipboard_clear")) (define (clipboard-copy str) ((c-lambda (char-string int) bool "clipboard_copy") str (string-length str))) From 5dbd4fe8e62848239da65666e2deef8148b323f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Tue, 20 Jul 2021 15:53:05 +0200 Subject: [PATCH 17/23] IRREGEX: follow upstream to 0.9.10 --- modules/irregex/irregex.scm | 66 +++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 24 deletions(-) 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)) From d5387ed15dc41de728506c07f01265880d2c8045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Sun, 11 Jul 2021 12:17:07 +0200 Subject: [PATCH 18/23] SRFI19: fix risky+damaging defaults Bad local modifications. --- modules/ln_core/time.scm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/ln_core/time.scm b/modules/ln_core/time.scm index 19fecc01..9a3a9098 100644 --- a/modules/ln_core/time.scm +++ b/modules/ln_core/time.scm @@ -1472,8 +1472,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 From 58d7394404423cebb66499f2a881a2e793bb9244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Wed, 30 Jun 2021 12:55:45 +0200 Subject: [PATCH 19/23] SRFI19: fix julian day calculation --- modules/ln_core/time.scm | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/ln_core/time.scm b/modules/ln_core/time.scm index 9a3a9098..7391261b 100644 --- a/modules/ln_core/time.scm +++ b/modules/ln_core/time.scm @@ -812,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) From c583615e3c0d491df7b744b4de9909c96f214ee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Fri, 2 Jul 2021 12:18:58 +0200 Subject: [PATCH 20/23] JSON: add option to generate newlines --- modules/json/json.scm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/json/json.scm b/modules/json/json.scm index 18e50628..c4d8a254 100644 --- a/modules/json/json.scm +++ b/modules/json/json.scm @@ -53,6 +53,7 @@ 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) @@ -65,10 +66,11 @@ 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)) +(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-symbols-for-keys? keys) + (set! use-newlines? newlines)) (define-macro (->string obj) `(cond @@ -379,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)) From 304d5b70d46c6957cdbbf1143d0139d142d155a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Sun, 6 Jun 2021 19:55:02 +0200 Subject: [PATCH 21/23] SCM.sh: export ~~bld for SYS_HOSTPREFIX to gambit --- languages/scm.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From f3f632e7dd0377d3513cf68688f41fa0d9f662c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Sun, 6 Jun 2021 19:47:59 +0200 Subject: [PATCH 22/23] MAKE.sh: enable substitutions for C startup new procedure `startup_subst` reads subsitutions from modules and app expanded into the C `main` procedure. - MAIN_c_additions :: expanded in toplevel file - MAIN_subcommand_defines expanded in initializer of subcommand structure: typedef struct subcommand_def { const char* name; void (*main)(int, const char*argv[]); } subcommand_def_t; example: for the `fossil` module `MAIN_subcommand_defines` contains: { "fossil", fossil_main }, --- loaders/common/{main.c => main.c.in} | 26 +++++++++++++++++++++++++ loaders/hook/{hook.c => hook.c.in} | 29 ++++++++++++++++++++++++++++ make.sh | 14 +++++++++++++- targets/bb10/build-binary | 2 +- targets/carlson-minot/build-binary | 2 +- targets/freebsd/build-binary | 2 +- targets/linux/build-binary | 4 ++-- targets/linux486/build-binary | 2 +- targets/macosx/build-binary | 2 +- targets/netbsd/build-binary | 2 +- targets/openbsd/build-binary | 2 +- targets/playbook/build-binary | 2 +- targets/sitara/build-binary | 2 +- targets/win32/build-binary | 2 +- 14 files changed, 80 insertions(+), 13 deletions(-) rename loaders/common/{main.c => main.c.in} (88%) rename loaders/hook/{hook.c => hook.c.in} (88%) diff --git a/loaders/common/main.c b/loaders/common/main.c.in similarity index 88% rename from loaders/common/main.c rename to loaders/common/main.c.in index b29c0fd2..e7ce31b3 100644 --- a/loaders/common/main.c +++ b/loaders/common/main.c.in @@ -97,9 +97,35 @@ 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; diff --git a/loaders/hook/hook.c b/loaders/hook/hook.c.in similarity index 88% rename from loaders/hook/hook.c rename to loaders/hook/hook.c.in index 7b27be86..68204aff 100644 --- a/loaders/hook/hook.c +++ b/loaders/hook/hook.c.in @@ -24,12 +24,41 @@ 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(); diff --git a/make.sh b/make.sh index c61d0e66..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 [$@]" @@ -379,7 +386,12 @@ compile_payload() hctgt="$SYS_PREFIX/build/$hookhash.c" hotgt=`echo "$hctgt" | sed 's/c$/o/'` rmifexists "$hotgt" - cp loaders/hook/hook.c "$hctgt" + 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/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 f8354952..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 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 From c60a74eb194217212178ddd1544d20b5a5c16989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20F=2E=20Wittenberger?= Date: Mon, 31 May 2021 14:38:43 +0200 Subject: [PATCH 23/23] CONFIG: do not require microgl for console apps --- modules/config/config.scm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/config/config.scm b/modules/config/config.scm index 73326271..4c70a486 100644 --- a/modules/config/config.scm +++ b/modules/config/config.scm @@ -98,7 +98,7 @@ end-of-c-declare (define microgl-window (c-lambda (int int) void " -#if !defined(STANDALONE) +#if !defined(STANDALONE) && !defined(USECONSOLE) microgl_window(___arg1, ___arg2); #endif ")) @@ -106,7 +106,7 @@ end-of-c-declare (define microgl-fullscreen (c-lambda (int int) void " -#if !defined(STANDALONE) +#if !defined(STANDALONE) && !defined(USECONSOLE) microgl_fullscreen(___arg1, ___arg2); #endif "))