1+ package com.mapbox.maps.renderer
2+
3+ import android.annotation.SuppressLint
4+ import android.opengl.EGL14
5+ import android.opengl.EGLSurface
6+ import android.opengl.GLES20
7+ import android.view.Surface
8+ import androidx.annotation.VisibleForTesting
9+ import com.mapbox.maps.ContextMode
10+ import com.mapbox.maps.logI
11+ import com.mapbox.maps.logW
12+ import com.mapbox.maps.renderer.egl.EGLCore
13+ import com.mapbox.maps.renderer.gl.TextureRenderer
14+ import java.util.concurrent.locks.Condition
15+ import java.util.concurrent.locks.ReentrantLock
16+
17+ /* *
18+ * OpenGL ES -based implementation of MapboxRenderThread.
19+ */
20+ internal class GLMapboxRenderThread : MapboxRenderThread {
21+
22+ private val translucentSurface: Boolean
23+ private val eglCore: EGLCore
24+ @VisibleForTesting(otherwise = VisibleForTesting .PRIVATE )
25+ internal var eglSurface: EGLSurface
26+
27+ private var widgetRenderCreated = false
28+ private val widgetTextureRenderer: TextureRenderer
29+
30+ private val contextMode: ContextMode
31+
32+ private var eglContextCreated = false
33+
34+ constructor (
35+ mapboxRenderer: MapboxRenderer ,
36+ mapboxWidgetRenderer: MapboxWidgetRenderer ,
37+ translucentSurface: Boolean ,
38+ antialiasingSampleCount: Int ,
39+ contextMode: ContextMode ,
40+ mapName: String ,
41+ ) : super (mapboxRenderer, mapboxWidgetRenderer, mapName, " GL" ) {
42+ this .eglCore = EGLCore (translucentSurface, antialiasingSampleCount, mapName = mapName)
43+ this .eglSurface = eglCore.eglNoSurface
44+ this .widgetTextureRenderer = TextureRenderer ()
45+ this .contextMode = contextMode
46+ this .translucentSurface = translucentSurface
47+ }
48+
49+ @SuppressLint(" VisibleForTests" )
50+ @VisibleForTesting(otherwise = VisibleForTesting .PRIVATE )
51+ constructor (
52+ mapboxRenderer: MapboxRenderer ,
53+ mapboxWidgetRenderer: MapboxWidgetRenderer ,
54+ handlerThread: RenderHandlerThread ,
55+ eglCore: EGLCore ,
56+ fpsManager: FpsManager ,
57+ widgetTextureRenderer: TextureRenderer ,
58+ surfaceProcessingLock: ReentrantLock ,
59+ createCondition: Condition ,
60+ destroyCondition: Condition ,
61+ ) : super (
62+ mapboxRenderer,
63+ mapboxWidgetRenderer,
64+ handlerThread,
65+ fpsManager,
66+ surfaceProcessingLock,
67+ createCondition,
68+ destroyCondition
69+ ) {
70+ this .translucentSurface = false
71+ this .eglCore = eglCore
72+ this .widgetTextureRenderer = widgetTextureRenderer
73+ this .eglSurface = eglCore.eglNoSurface
74+ this .contextMode = ContextMode .UNIQUE
75+ }
76+
77+ override fun detachSurfaceFromRenderer (creatingSurface : Boolean ) {
78+ // on Android SDK <= 23 at least on x86 emulators we need to force set EGL14.EGL_NO_CONTEXT
79+ // when resuming activity
80+ if (creatingSurface) {
81+ eglCore.makeNothingCurrent()
82+ }
83+ }
84+
85+ override fun prepareRenderer (): Boolean {
86+ logI(
87+ TAG ,
88+ " prepareRenderer:" +
89+ " eglContextCreated=$eglContextCreated "
90+ )
91+ if (! eglContextCreated) {
92+ val eglOk = eglCore.prepareEgl()
93+ if (eglOk) {
94+ eglContextCreated = true
95+ } else {
96+ logW(TAG , " EGL was not configured, please check logs above." )
97+ return false
98+ }
99+ }
100+ return true
101+ }
102+
103+ /* *
104+ * Create the EGL surface [eglSurface] from the given surface if not yet created.
105+ */
106+ override fun attachSurfaceToRenderer (surface : Surface ): Boolean {
107+ if (eglSurface == eglCore.eglNoSurface) {
108+ eglSurface = eglCore.createWindowSurface(surface)
109+ if (eglSurface == eglCore.eglNoSurface) {
110+ // try to recreate it in next iteration.
111+ logW(
112+ TAG ,
113+ " Could not create EGL surface although Android surface was valid," +
114+ " retrying in $RETRY_DELAY_MS ms..."
115+ )
116+ postPrepareRenderFrame(delayMillis = RETRY_DELAY_MS )
117+ return false
118+ }
119+ }
120+ // Once we have a valid eglSurface let's make it current.
121+ return checkEglContextCurrent()
122+ }
123+
124+ override fun presentFrame () {
125+ swapBuffers()
126+ }
127+
128+ override fun preRenderWithSharedContext () {
129+ if (contextMode == ContextMode .SHARED ) {
130+ GLES20 .glClear(GLES20 .GL_COLOR_BUFFER_BIT or GLES20 .GL_DEPTH_BUFFER_BIT or GLES20 .GL_STENCIL_BUFFER_BIT )
131+ }
132+ }
133+
134+ override fun renderWithWidgets () {
135+ if (widgetRenderer?.needRender == true ) {
136+ widgetRenderer.renderToFrameBuffer()
137+ eglCore.makeCurrent(eglSurface)
138+ }
139+ render()
140+ resetGlState()
141+ if (widgetRenderer?.hasTexture() == true ) {
142+ widgetTextureRenderer.render(widgetRenderer.getTexture())
143+ }
144+ }
145+
146+ override fun releaseResources () {
147+ releaseEglSurface()
148+ if (eglContextCreated) {
149+ eglCore.release()
150+ }
151+ eglContextCreated = false
152+ }
153+
154+ override fun prepareWidgetRender () {
155+ if (eglContextCreated && ! widgetRenderCreated && widgetRenderer?.hasWidgets() == true ) {
156+ widgetRenderer.setSharedContext(eglCore.eglContext)
157+ widgetRenderCreated = true
158+ }
159+ }
160+
161+ override fun releaseRenderSurface () {
162+ releaseEglSurface()
163+ }
164+
165+ override fun clearRendererStateListeners () {
166+ eglCore.clearRendererStateListeners()
167+ }
168+
169+ override fun addRendererStateListener (listener : RendererSetupErrorListener ) {
170+ eglCore.addRendererStateListener(listener)
171+ }
172+
173+ override fun removeRendererStateListener (listener : RendererSetupErrorListener ) {
174+ eglCore.removeRendererStateListener(listener)
175+ }
176+
177+ override fun resize (width : Int , height : Int ) {
178+ GLES20 .glViewport(0 , 0 , width, height)
179+ }
180+
181+ override fun renderWithoutWidgets () {
182+ render()
183+ }
184+
185+ private fun render () {
186+ mapboxRenderer.render()
187+ }
188+
189+ override fun flushCommands () {
190+ // explicit flush as we will not be doing any drawing until buffer swap for the next frame -
191+ // we send commands to GPU this frame as we should have some free time and perform present frame asap on the next frame
192+ // note that this doesn't block the calling thread, it merely signals the driver that we might not be sending any additional commands.
193+ // ref https://stackoverflow.com/a/38297697
194+ GLES20 .glFlush()
195+ }
196+
197+ private fun checkEglContextCurrent (): Boolean {
198+ val eglContextAttached = eglCore.makeCurrent(eglSurface)
199+ if (! eglContextAttached) {
200+ logW(TAG , " EGL was configured but context could not be made current. Trying again in a moment..." )
201+ postPrepareRenderFrame(delayMillis = RETRY_DELAY_MS )
202+ return false
203+ }
204+ return true
205+ }
206+
207+ /* *
208+ * Resetting OpenGL state to make sure widget textures are rendered on top of the map.
209+ */
210+ // TODO remove when rendering engine will handle this for us;
211+ // for now we have to clean context a bit so that our texture(s) is (are) drawn on top of the map
212+ private fun resetGlState () {
213+ // using at least MapDebugOptions.TILE_BORDERS and perhaps in other situations
214+ // rending engine may change the blend function; so we explicitly reset it to needed values
215+ GLES20 .glEnable(GLES20 .GL_BLEND )
216+ GLES20 .glBlendFunc(GLES20 .GL_ONE , GLES20 .GL_ONE_MINUS_SRC_ALPHA )
217+ GLES20 .glBlendEquation(GLES20 .GL_FUNC_ADD )
218+
219+ // explicitly disable stencil and depth because otherwise widgets may be rendered
220+ // behind the map tiles in some scenarios
221+ GLES20 .glDisable(GLES20 .GL_STENCIL_TEST )
222+ GLES20 .glDisable(GLES20 .GL_DEPTH_TEST )
223+ GLES20 .glUseProgram(0 )
224+ GLES20 .glBindBuffer(GLES20 .GL_ARRAY_BUFFER , 0 )
225+ GLES20 .glBindBuffer(GLES20 .GL_ELEMENT_ARRAY_BUFFER , 0 )
226+ }
227+
228+ private fun swapBuffers () {
229+ when (val swapStatus = eglCore.swapBuffers(eglSurface)) {
230+ EGL14 .EGL_SUCCESS -> { }
231+ EGL14 .EGL_CONTEXT_LOST -> {
232+ logW(TAG , " Context lost. Waiting for re-acquire" )
233+ // release all resources but not release Android surface
234+ // as it still potentially may be valid - then it could be re-used to recreate EGL;
235+ // if it's not valid - system should shortly send us brand new surface and
236+ // we will recreate EGL and native renderer anyway
237+ releaseAll(tryRecreate = true )
238+ }
239+ else -> {
240+ logW(TAG , " eglSwapBuffer error: $swapStatus . Waiting for new surface" )
241+ releaseEglSurface()
242+ }
243+ }
244+ }
245+
246+ private fun releaseEglSurface () {
247+ trace(" release-egl-surface" ) {
248+ widgetTextureRenderer.release()
249+ eglCore.releaseSurface(eglSurface)
250+ isRendererReady = false
251+ eglSurface = eglCore.eglNoSurface
252+ widgetRenderCreated = false
253+ widgetRenderer?.release()
254+ }
255+ }
256+ }
0 commit comments