Skip to content

Commit d8a4d88

Browse files
jushgithub-actions[bot]
authored andcommitted
Extract OpenGL ES logic from MapboxRenderThread (#9437)
GitOrigin-RevId: 04d5dd7c91145abaae3a0d2d23ce04d79660a41a
1 parent de61b5d commit d8a4d88

6 files changed

Lines changed: 449 additions & 268 deletions

File tree

maps-sdk/src/main/java/com/mapbox/maps/MapController.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,13 +307,13 @@ internal class MapController : MapPluginProviderDelegate, MapControllable {
307307

308308
override fun addRendererSetupErrorListener(rendererSetupErrorListener: RendererSetupErrorListener) {
309309
renderer.renderThread.renderHandlerThread.post {
310-
renderer.renderThread.eglCore.addRendererStateListener(rendererSetupErrorListener)
310+
renderer.renderThread.addRendererStateListener(rendererSetupErrorListener)
311311
}
312312
}
313313

314314
override fun removeRendererSetupErrorListener(rendererSetupErrorListener: RendererSetupErrorListener) {
315315
renderer.renderThread.renderHandlerThread.post {
316-
renderer.renderThread.eglCore.removeRendererStateListener(rendererSetupErrorListener)
316+
renderer.renderThread.removeRendererStateListener(rendererSetupErrorListener)
317317
}
318318
}
319319

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
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

Comments
 (0)