Add multiBackend Android flavor for runtime OpenGL/Vulkan switching (#4288)
This commit is contained in:
committed by
GitHub
parent
ccb9af119e
commit
b0388d186d
@@ -0,0 +1,42 @@
|
||||
package org.maplibre.android.maps.renderer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.Surface;
|
||||
import android.view.TextureView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.maplibre.android.maps.renderer.surfaceview.GLSurfaceViewMapRenderer;
|
||||
import org.maplibre.android.maps.renderer.surfaceview.MapLibreGLSurfaceView;
|
||||
import org.maplibre.android.maps.renderer.surfaceview.SurfaceViewMapRenderer;
|
||||
import org.maplibre.android.maps.renderer.textureview.GLTextureViewRenderThread;
|
||||
import org.maplibre.android.maps.renderer.textureview.TextureViewMapRenderer;
|
||||
|
||||
/**
|
||||
* OpenGL concrete impl behind {@link MapRendererFactory}. Lives alongside the
|
||||
* other GL renderer helpers in src/sharedRenderer/opengl/.
|
||||
*/
|
||||
final class OpenGLRendererStrategy {
|
||||
|
||||
private OpenGLRendererStrategy() {}
|
||||
|
||||
static void attachTextureRenderThread(@NonNull TextureView textureView,
|
||||
@NonNull TextureViewMapRenderer renderer) {
|
||||
renderer.setRenderThread(new GLTextureViewRenderThread(textureView, renderer));
|
||||
}
|
||||
|
||||
static SurfaceViewMapRenderer createSurfaceViewRenderer(@NonNull Context context,
|
||||
String localFontFamily,
|
||||
boolean renderSurfaceOnTop,
|
||||
Runnable initCallback) {
|
||||
MapLibreGLSurfaceView surfaceView = new MapLibreGLSurfaceView(context);
|
||||
surfaceView.setZOrderMediaOverlay(renderSurfaceOnTop);
|
||||
return new GLSurfaceViewMapRenderer(context, surfaceView, localFontFamily) {
|
||||
@Override
|
||||
public void onSurfaceCreated(Surface surface) {
|
||||
initCallback.run();
|
||||
super.onSurfaceCreated(surface);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
package org.maplibre.android.maps.renderer.egl;
|
||||
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.maplibre.android.constants.MapLibreConstants;
|
||||
import org.maplibre.android.log.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.microedition.khronos.egl.EGL10;
|
||||
import javax.microedition.khronos.egl.EGLConfig;
|
||||
import javax.microedition.khronos.egl.EGLDisplay;
|
||||
|
||||
import static org.maplibre.android.utils.Compare.compare;
|
||||
import static javax.microedition.khronos.egl.EGL10.EGL_ALPHA_MASK_SIZE;
|
||||
import static javax.microedition.khronos.egl.EGL10.EGL_ALPHA_SIZE;
|
||||
import static javax.microedition.khronos.egl.EGL10.EGL_BLUE_SIZE;
|
||||
import static javax.microedition.khronos.egl.EGL10.EGL_BUFFER_SIZE;
|
||||
import static javax.microedition.khronos.egl.EGL10.EGL_COLOR_BUFFER_TYPE;
|
||||
import static javax.microedition.khronos.egl.EGL10.EGL_CONFIG_CAVEAT;
|
||||
import static javax.microedition.khronos.egl.EGL10.EGL_DEPTH_SIZE;
|
||||
import static javax.microedition.khronos.egl.EGL10.EGL_GREEN_SIZE;
|
||||
import static javax.microedition.khronos.egl.EGL10.EGL_NONE;
|
||||
import static javax.microedition.khronos.egl.EGL10.EGL_RED_SIZE;
|
||||
import static javax.microedition.khronos.egl.EGL10.EGL_RENDERABLE_TYPE;
|
||||
import static javax.microedition.khronos.egl.EGL10.EGL_RGB_BUFFER;
|
||||
import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLES;
|
||||
import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLE_BUFFERS;
|
||||
import static javax.microedition.khronos.egl.EGL10.EGL_STENCIL_SIZE;
|
||||
import static javax.microedition.khronos.egl.EGL10.EGL_SURFACE_TYPE;
|
||||
import static javax.microedition.khronos.egl.EGL10.EGL_WINDOW_BIT;
|
||||
|
||||
/**
|
||||
* Selects the right EGLConfig needed for `mapbox-gl-native`
|
||||
*/
|
||||
public class EGLConfigChooser implements GLSurfaceView.EGLConfigChooser {
|
||||
|
||||
private static final String TAG = "Mbgl-EGLConfigChooser";
|
||||
|
||||
/**
|
||||
* Requires API level 18
|
||||
*
|
||||
* @see android.opengl.EGL15.EGL_OPENGL_ES3_BIT;
|
||||
*/
|
||||
@SuppressWarnings("JavadocReference")
|
||||
private static final int EGL_OPENGL_ES3_BIT = 0x0040;
|
||||
|
||||
private boolean translucentSurface;
|
||||
|
||||
public EGLConfigChooser() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public EGLConfigChooser(boolean translucentSurface) {
|
||||
super();
|
||||
this.translucentSurface = translucentSurface;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EGLConfig chooseConfig(@NonNull EGL10 egl, EGLDisplay display) {
|
||||
int[] configAttribs = getConfigAttributes();
|
||||
|
||||
// Determine number of possible configurations
|
||||
int[] numConfigs = getNumberOfConfigurations(egl, display, configAttribs);
|
||||
if (numConfigs[0] < 1) {
|
||||
Logger.e(TAG, "eglChooseConfig() returned no configs.");
|
||||
}
|
||||
|
||||
// Get all possible configurations
|
||||
EGLConfig[] possibleConfigurations = getPossibleConfigurations(egl, display, configAttribs, numConfigs);
|
||||
|
||||
// Choose best match
|
||||
EGLConfig config = chooseBestMatchConfig(egl, display, possibleConfigurations);
|
||||
if (config == null) {
|
||||
Logger.e(TAG, "No config chosen");
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private int[] getNumberOfConfigurations(EGL10 egl, EGLDisplay display, int[] configAttributes) {
|
||||
int[] numConfigs = new int[1];
|
||||
if (!egl.eglChooseConfig(display, configAttributes, null, 0, numConfigs)) {
|
||||
Logger.e(TAG, String.format(
|
||||
MapLibreConstants.MAPLIBRE_LOCALE, "eglChooseConfig(NULL) returned error %d", egl.eglGetError())
|
||||
);
|
||||
}
|
||||
return numConfigs;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private EGLConfig[] getPossibleConfigurations(EGL10 egl, EGLDisplay display,
|
||||
int[] configAttributes, int[] numConfigs) {
|
||||
EGLConfig[] configs = new EGLConfig[numConfigs[0]];
|
||||
if (!egl.eglChooseConfig(display, configAttributes, configs, numConfigs[0], numConfigs)) {
|
||||
Logger.e(TAG, String.format(
|
||||
MapLibreConstants.MAPLIBRE_LOCALE, "eglChooseConfig() returned error %d", egl.eglGetError())
|
||||
);
|
||||
}
|
||||
return configs;
|
||||
}
|
||||
|
||||
// Quality
|
||||
enum BufferFormat {
|
||||
Format16Bit(3),
|
||||
Format32BitNoAlpha(1),
|
||||
Format32BitAlpha(2),
|
||||
Format24Bit(0),
|
||||
Unknown(4);
|
||||
|
||||
int value;
|
||||
|
||||
BufferFormat(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
enum DepthStencilFormat {
|
||||
Format16Depth8Stencil(1),
|
||||
Format24Depth8Stencil(0);
|
||||
|
||||
int value;
|
||||
|
||||
DepthStencilFormat(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
private EGLConfig chooseBestMatchConfig(@NonNull EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
|
||||
class Config implements Comparable<Config> {
|
||||
private final BufferFormat bufferFormat;
|
||||
private final DepthStencilFormat depthStencilFormat;
|
||||
private final boolean isCaveat;
|
||||
private final int index;
|
||||
private final EGLConfig config;
|
||||
|
||||
public Config(BufferFormat bufferFormat, DepthStencilFormat depthStencilFormat,
|
||||
boolean isCaveat, int index, EGLConfig config) {
|
||||
this.bufferFormat = bufferFormat;
|
||||
this.depthStencilFormat = depthStencilFormat;
|
||||
this.isCaveat = isCaveat;
|
||||
this.index = index;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull Config other) {
|
||||
int i = compare(bufferFormat.value, other.bufferFormat.value);
|
||||
if (i != 0) {
|
||||
return i;
|
||||
}
|
||||
|
||||
i = compare(depthStencilFormat.value, other.depthStencilFormat.value);
|
||||
if (i != 0) {
|
||||
return i;
|
||||
}
|
||||
|
||||
i = compare(isCaveat, other.isCaveat);
|
||||
if (i != 0) {
|
||||
return i;
|
||||
}
|
||||
|
||||
i = compare(index, other.index);
|
||||
if (i != 0) {
|
||||
return i;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
List<Config> matches = new ArrayList<>();
|
||||
|
||||
int i = 0;
|
||||
for (EGLConfig config : configs) {
|
||||
if (config == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
int caveat = getConfigAttr(egl, display, config, EGL_CONFIG_CAVEAT);
|
||||
int bits = getConfigAttr(egl, display, config, EGL_BUFFER_SIZE);
|
||||
int red = getConfigAttr(egl, display, config, EGL_RED_SIZE);
|
||||
int green = getConfigAttr(egl, display, config, EGL_GREEN_SIZE);
|
||||
int blue = getConfigAttr(egl, display, config, EGL_BLUE_SIZE);
|
||||
int alpha = getConfigAttr(egl, display, config, EGL_ALPHA_SIZE);
|
||||
int alphaMask = getConfigAttr(egl, display, config, EGL_ALPHA_MASK_SIZE);
|
||||
int depth = getConfigAttr(egl, display, config, EGL_DEPTH_SIZE);
|
||||
int stencil = getConfigAttr(egl, display, config, EGL_STENCIL_SIZE);
|
||||
int sampleBuffers = getConfigAttr(egl, display, config, EGL_SAMPLE_BUFFERS);
|
||||
int samples = getConfigAttr(egl, display, config, EGL_SAMPLES);
|
||||
|
||||
boolean configOk = (depth == 24) || (depth == 16);
|
||||
configOk &= stencil == 8;
|
||||
configOk &= sampleBuffers == 0;
|
||||
configOk &= samples == 0;
|
||||
|
||||
// Filter our configs first for depth, stencil and anti-aliasing
|
||||
if (configOk) {
|
||||
// Work out the config's buffer format
|
||||
BufferFormat bufferFormat;
|
||||
if ((bits == 16) && (red == 5) && (green == 6) && (blue == 5) && (alpha == 0)) {
|
||||
bufferFormat = BufferFormat.Format16Bit;
|
||||
} else if ((bits == 32) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 0)) {
|
||||
bufferFormat = BufferFormat.Format32BitNoAlpha;
|
||||
} else if ((bits == 32) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 8)) {
|
||||
bufferFormat = BufferFormat.Format32BitAlpha;
|
||||
} else if ((bits == 24) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 0)) {
|
||||
bufferFormat = BufferFormat.Format24Bit;
|
||||
} else {
|
||||
bufferFormat = BufferFormat.Unknown;
|
||||
}
|
||||
|
||||
// Work out the config's depth stencil format
|
||||
DepthStencilFormat depthStencilFormat;
|
||||
if ((depth == 16) && (stencil == 8)) {
|
||||
depthStencilFormat = DepthStencilFormat.Format16Depth8Stencil;
|
||||
} else {
|
||||
depthStencilFormat = DepthStencilFormat.Format24Depth8Stencil;
|
||||
}
|
||||
|
||||
boolean isCaveat = caveat != EGL_NONE;
|
||||
|
||||
// Ignore formats we don't recognise
|
||||
if (bufferFormat != BufferFormat.Unknown) {
|
||||
matches.add(new Config(bufferFormat, depthStencilFormat, isCaveat, i, config));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Sort
|
||||
Collections.sort(matches);
|
||||
|
||||
if (matches.size() == 0) {
|
||||
Logger.e(TAG, "No matching configurations after filtering");
|
||||
return null;
|
||||
}
|
||||
|
||||
Config bestMatch = matches.get(0);
|
||||
|
||||
if (bestMatch.isCaveat) {
|
||||
Logger.w(TAG, "Chosen config has a caveat.");
|
||||
}
|
||||
|
||||
return bestMatch.config;
|
||||
}
|
||||
|
||||
private int getConfigAttr(EGL10 egl, EGLDisplay display, EGLConfig config, int attributeName) {
|
||||
int[] attributevalue = new int[1];
|
||||
if (!egl.eglGetConfigAttrib(display, config, attributeName, attributevalue)) {
|
||||
Logger.e(TAG, String.format(
|
||||
MapLibreConstants.MAPLIBRE_LOCALE, "eglGetConfigAttrib(%d) returned error %d", attributeName, egl.eglGetError())
|
||||
);
|
||||
}
|
||||
return attributevalue[0];
|
||||
}
|
||||
|
||||
private int[] getConfigAttributes() {
|
||||
boolean emulator = inEmulator() || inGenymotion();
|
||||
Logger.i(TAG, String.format("In emulator: %s", emulator));
|
||||
|
||||
// Get all configs at least RGB 565 with 16 depth and 8 stencil
|
||||
return new int[] {
|
||||
EGL_CONFIG_CAVEAT, EGL_NONE,
|
||||
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
||||
EGL_BUFFER_SIZE, 16,
|
||||
EGL_RED_SIZE, 5,
|
||||
EGL_GREEN_SIZE, 6,
|
||||
EGL_BLUE_SIZE, 5,
|
||||
EGL_ALPHA_SIZE, translucentSurface ? 8 : 0,
|
||||
EGL_DEPTH_SIZE, 16,
|
||||
EGL_STENCIL_SIZE, 8,
|
||||
(emulator ? EGL_NONE : EGL_COLOR_BUFFER_TYPE), EGL_RGB_BUFFER,
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
|
||||
EGL_NONE
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if we are in emulator.
|
||||
*/
|
||||
private boolean inEmulator() {
|
||||
return Build.FINGERPRINT.startsWith("generic")
|
||||
|| Build.FINGERPRINT.startsWith("unknown")
|
||||
|| Build.MODEL.contains("google_sdk")
|
||||
|| Build.MODEL.contains("Emulator")
|
||||
|| Build.MODEL.contains("Android SDK built for x86")
|
||||
|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
|
||||
|| "google_sdk".equals(Build.PRODUCT)
|
||||
|| System.getProperty("ro.kernel.qemu") != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if we are in genymotion
|
||||
*/
|
||||
private boolean inGenymotion() {
|
||||
return Build.MANUFACTURER.contains("Genymotion");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.maplibre.android.maps.renderer.egl;
|
||||
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import javax.microedition.khronos.egl.EGL10;
|
||||
import javax.microedition.khronos.egl.EGLConfig;
|
||||
import javax.microedition.khronos.egl.EGLContext;
|
||||
import javax.microedition.khronos.egl.EGLDisplay;
|
||||
|
||||
public class EGLContextFactory implements GLSurfaceView.EGLContextFactory {
|
||||
|
||||
public EGLContext createContext(EGL10 egl, @Nullable EGLDisplay display, @Nullable EGLConfig config) {
|
||||
if (display == null || config == null) {
|
||||
return EGL10.EGL_NO_CONTEXT;
|
||||
}
|
||||
int[] attrib_list = {0x3098, 2, EGL10.EGL_NONE};
|
||||
return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, attrib_list);
|
||||
}
|
||||
|
||||
public void destroyContext(EGL10 egl, EGLDisplay display,
|
||||
EGLContext context) {
|
||||
if (!egl.eglDestroyContext(display, context)) {
|
||||
Log.e("DefaultContextFactory", "display:" + display + " context: " + context);
|
||||
Log.i("DefaultContextFactory", "tid=" + Thread.currentThread().getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,545 @@
|
||||
package org.maplibre.android.maps.renderer.egl;
|
||||
|
||||
import android.opengl.GLDebugHelper;
|
||||
import android.opengl.GLException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
import javax.microedition.khronos.egl.EGL;
|
||||
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;
|
||||
|
||||
public class EGLLogWrapper implements EGL11 {
|
||||
private EGL10 egl10;
|
||||
private Writer log;
|
||||
private boolean logArgumentNames;
|
||||
private boolean checkError;
|
||||
private int argCount;
|
||||
|
||||
public EGLLogWrapper(EGL egl, int configFlags, Writer log) {
|
||||
egl10 = (EGL10) egl;
|
||||
this.log = log;
|
||||
logArgumentNames = (GLDebugHelper.CONFIG_LOG_ARGUMENT_NAMES & configFlags) != 0;
|
||||
checkError = (GLDebugHelper.CONFIG_CHECK_GL_ERROR & configFlags) != 0;
|
||||
}
|
||||
|
||||
public boolean eglChooseConfig(EGLDisplay display, int[] attrib_list,
|
||||
EGLConfig[] configs, int config_size, int[] num_config) {
|
||||
begin("eglChooseConfig");
|
||||
arg("display", display);
|
||||
arg("attrib_list", attrib_list);
|
||||
arg("config_size", config_size);
|
||||
end();
|
||||
|
||||
boolean result = egl10.eglChooseConfig(display, attrib_list, configs,
|
||||
config_size, num_config);
|
||||
arg("configs", configs);
|
||||
arg("num_config", num_config);
|
||||
returns(result);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean eglCopyBuffers(EGLDisplay display, EGLSurface surface,
|
||||
Object native_pixmap) {
|
||||
begin("eglCopyBuffers");
|
||||
arg("display", display);
|
||||
arg("surface", surface);
|
||||
arg("native_pixmap", native_pixmap);
|
||||
end();
|
||||
|
||||
boolean result = egl10.eglCopyBuffers(display, surface, native_pixmap);
|
||||
returns(result);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public EGLContext eglCreateContext(EGLDisplay display, EGLConfig config,
|
||||
EGLContext share_context, int[] attrib_list) {
|
||||
begin("eglCreateContext");
|
||||
arg("display", display);
|
||||
arg("config", config);
|
||||
arg("share_context", share_context);
|
||||
arg("attrib_list", attrib_list);
|
||||
end();
|
||||
|
||||
EGLContext result = egl10.eglCreateContext(display, config,
|
||||
share_context, attrib_list);
|
||||
returns(result);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public EGLSurface eglCreatePbufferSurface(EGLDisplay display,
|
||||
EGLConfig config, int[] attrib_list) {
|
||||
begin("eglCreatePbufferSurface");
|
||||
arg("display", display);
|
||||
arg("config", config);
|
||||
arg("attrib_list", attrib_list);
|
||||
end();
|
||||
|
||||
EGLSurface result = egl10.eglCreatePbufferSurface(display, config,
|
||||
attrib_list);
|
||||
returns(result);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public EGLSurface eglCreatePixmapSurface(EGLDisplay display,
|
||||
EGLConfig config, Object native_pixmap, int[] attrib_list) {
|
||||
begin("eglCreatePixmapSurface");
|
||||
arg("display", display);
|
||||
arg("config", config);
|
||||
arg("native_pixmap", native_pixmap);
|
||||
arg("attrib_list", attrib_list);
|
||||
end();
|
||||
|
||||
EGLSurface result = egl10.eglCreatePixmapSurface(display, config,
|
||||
native_pixmap, attrib_list);
|
||||
returns(result);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public EGLSurface eglCreateWindowSurface(EGLDisplay display,
|
||||
EGLConfig config, Object native_window, int[] attrib_list) {
|
||||
begin("eglCreateWindowSurface");
|
||||
arg("display", display);
|
||||
arg("config", config);
|
||||
arg("native_window", native_window);
|
||||
arg("attrib_list", attrib_list);
|
||||
end();
|
||||
|
||||
EGLSurface result = egl10.eglCreateWindowSurface(display, config,
|
||||
native_window, attrib_list);
|
||||
returns(result);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean eglDestroyContext(EGLDisplay display, EGLContext context) {
|
||||
begin("eglDestroyContext");
|
||||
arg("display", display);
|
||||
arg("context", context);
|
||||
end();
|
||||
|
||||
boolean result = egl10.eglDestroyContext(display, context);
|
||||
returns(result);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean eglDestroySurface(EGLDisplay display, EGLSurface surface) {
|
||||
begin("eglDestroySurface");
|
||||
arg("display", display);
|
||||
arg("surface", surface);
|
||||
end();
|
||||
|
||||
boolean result = egl10.eglDestroySurface(display, surface);
|
||||
returns(result);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean eglGetConfigAttrib(EGLDisplay display, EGLConfig config,
|
||||
int attribute, int[] value) {
|
||||
begin("eglGetConfigAttrib");
|
||||
arg("display", display);
|
||||
arg("config", config);
|
||||
arg("attribute", attribute);
|
||||
end();
|
||||
boolean result = egl10.eglGetConfigAttrib(display, config, attribute,
|
||||
value);
|
||||
arg("value", value);
|
||||
returns(result);
|
||||
checkError();
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean eglGetConfigs(EGLDisplay display, EGLConfig[] configs,
|
||||
int config_size, int[] num_config) {
|
||||
begin("eglGetConfigs");
|
||||
arg("display", display);
|
||||
arg("config_size", config_size);
|
||||
end();
|
||||
|
||||
boolean result = egl10.eglGetConfigs(display, configs, config_size,
|
||||
num_config);
|
||||
arg("configs", configs);
|
||||
arg("num_config", num_config);
|
||||
returns(result);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public EGLContext eglGetCurrentContext() {
|
||||
begin("eglGetCurrentContext");
|
||||
end();
|
||||
|
||||
EGLContext result = egl10.eglGetCurrentContext();
|
||||
returns(result);
|
||||
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public EGLDisplay eglGetCurrentDisplay() {
|
||||
begin("eglGetCurrentDisplay");
|
||||
end();
|
||||
|
||||
EGLDisplay result = egl10.eglGetCurrentDisplay();
|
||||
returns(result);
|
||||
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public EGLSurface eglGetCurrentSurface(int readdraw) {
|
||||
begin("eglGetCurrentSurface");
|
||||
arg("readdraw", readdraw);
|
||||
end();
|
||||
|
||||
EGLSurface result = egl10.eglGetCurrentSurface(readdraw);
|
||||
returns(result);
|
||||
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public EGLDisplay eglGetDisplay(Object native_display) {
|
||||
begin("eglGetDisplay");
|
||||
arg("native_display", native_display);
|
||||
end();
|
||||
|
||||
EGLDisplay result = egl10.eglGetDisplay(native_display);
|
||||
returns(result);
|
||||
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public int eglGetError() {
|
||||
begin("eglGetError");
|
||||
end();
|
||||
|
||||
int result = egl10.eglGetError();
|
||||
returns(getErrorString(result));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean eglInitialize(EGLDisplay display, int[] major_minor) {
|
||||
begin("eglInitialize");
|
||||
arg("display", display);
|
||||
end();
|
||||
boolean result = egl10.eglInitialize(display, major_minor);
|
||||
returns(result);
|
||||
arg("major_minor", major_minor);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean eglMakeCurrent(EGLDisplay display, EGLSurface draw,
|
||||
EGLSurface read, EGLContext context) {
|
||||
begin("eglMakeCurrent");
|
||||
arg("display", display);
|
||||
arg("draw", draw);
|
||||
arg("read", read);
|
||||
arg("context", context);
|
||||
end();
|
||||
boolean result = egl10.eglMakeCurrent(display, draw, read, context);
|
||||
returns(result);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean eglQueryContext(EGLDisplay display, EGLContext context,
|
||||
int attribute, int[] value) {
|
||||
begin("eglQueryContext");
|
||||
arg("display", display);
|
||||
arg("context", context);
|
||||
arg("attribute", attribute);
|
||||
end();
|
||||
boolean result = egl10.eglQueryContext(display, context, attribute,
|
||||
value);
|
||||
returns(value[0]);
|
||||
returns(result);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public String eglQueryString(EGLDisplay display, int name) {
|
||||
begin("eglQueryString");
|
||||
arg("display", display);
|
||||
arg("name", name);
|
||||
end();
|
||||
String result = egl10.eglQueryString(display, name);
|
||||
returns(result);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean eglQuerySurface(EGLDisplay display, EGLSurface surface,
|
||||
int attribute, int[] value) {
|
||||
begin("eglQuerySurface");
|
||||
arg("display", display);
|
||||
arg("surface", surface);
|
||||
arg("attribute", attribute);
|
||||
end();
|
||||
boolean result = egl10.eglQuerySurface(display, surface, attribute,
|
||||
value);
|
||||
returns(value[0]);
|
||||
returns(result);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean eglSwapBuffers(EGLDisplay display, EGLSurface surface) {
|
||||
begin("eglSwapBuffers");
|
||||
arg("display", display);
|
||||
arg("surface", surface);
|
||||
end();
|
||||
boolean result = egl10.eglSwapBuffers(display, surface);
|
||||
returns(result);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean eglTerminate(EGLDisplay display) {
|
||||
begin("eglTerminate");
|
||||
arg("display", display);
|
||||
end();
|
||||
boolean result = egl10.eglTerminate(display);
|
||||
returns(result);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean eglWaitGL() {
|
||||
begin("eglWaitGL");
|
||||
end();
|
||||
boolean result = egl10.eglWaitGL();
|
||||
returns(result);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean eglWaitNative(int engine, Object bindTarget) {
|
||||
begin("eglWaitNative");
|
||||
arg("engine", engine);
|
||||
arg("bindTarget", bindTarget);
|
||||
end();
|
||||
boolean result = egl10.eglWaitNative(engine, bindTarget);
|
||||
returns(result);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
private void checkError() {
|
||||
int eglError;
|
||||
if ((eglError = egl10.eglGetError()) != EGL_SUCCESS) {
|
||||
String errorMessage = "eglError: " + getErrorString(eglError);
|
||||
logLine(errorMessage);
|
||||
if (checkError) {
|
||||
throw new GLException(eglError, errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void logLine(String message) {
|
||||
log(message + '\n');
|
||||
}
|
||||
|
||||
private void log(String message) {
|
||||
try {
|
||||
log.write(message);
|
||||
} catch (IOException exception) {
|
||||
// Ignore exception, keep on trying
|
||||
}
|
||||
}
|
||||
|
||||
private void begin(String name) {
|
||||
log(name + '(');
|
||||
argCount = 0;
|
||||
}
|
||||
|
||||
private void arg(String name, String value) {
|
||||
if (argCount++ > 0) {
|
||||
log(", ");
|
||||
}
|
||||
if (logArgumentNames) {
|
||||
log(name + "=");
|
||||
}
|
||||
log(value);
|
||||
}
|
||||
|
||||
private void end() {
|
||||
log(");\n");
|
||||
flush();
|
||||
}
|
||||
|
||||
private void flush() {
|
||||
try {
|
||||
log.flush();
|
||||
} catch (IOException exception) {
|
||||
log = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void arg(String name, int value) {
|
||||
arg(name, Integer.toString(value));
|
||||
}
|
||||
|
||||
private void arg(String name, Object object) {
|
||||
arg(name, toString(object));
|
||||
}
|
||||
|
||||
private void arg(String name, EGLDisplay object) {
|
||||
if (object == EGL10.EGL_DEFAULT_DISPLAY) {
|
||||
arg(name, "EGL10.EGL_DEFAULT_DISPLAY");
|
||||
} else if (object == EGL_NO_DISPLAY) {
|
||||
arg(name, "EGL10.EGL_NO_DISPLAY");
|
||||
} else {
|
||||
arg(name, toString(object));
|
||||
}
|
||||
}
|
||||
|
||||
private void arg(String name, EGLContext object) {
|
||||
if (object == EGL10.EGL_NO_CONTEXT) {
|
||||
arg(name, "EGL10.EGL_NO_CONTEXT");
|
||||
} else {
|
||||
arg(name, toString(object));
|
||||
}
|
||||
}
|
||||
|
||||
private void arg(String name, EGLSurface object) {
|
||||
if (object == EGL10.EGL_NO_SURFACE) {
|
||||
arg(name, "EGL10.EGL_NO_SURFACE");
|
||||
} else {
|
||||
arg(name, toString(object));
|
||||
}
|
||||
}
|
||||
|
||||
private void returns(String result) {
|
||||
log(" returns " + result + ";\n");
|
||||
flush();
|
||||
}
|
||||
|
||||
private void returns(int result) {
|
||||
returns(Integer.toString(result));
|
||||
}
|
||||
|
||||
private void returns(boolean result) {
|
||||
returns(Boolean.toString(result));
|
||||
}
|
||||
|
||||
private void returns(Object result) {
|
||||
returns(toString(result));
|
||||
}
|
||||
|
||||
private String toString(Object obj) {
|
||||
if (obj == null) {
|
||||
return "null";
|
||||
} else {
|
||||
return obj.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private void arg(String name, int[] arr) {
|
||||
if (arr == null) {
|
||||
arg(name, "null");
|
||||
} else {
|
||||
arg(name, toString(arr.length, arr, 0));
|
||||
}
|
||||
}
|
||||
|
||||
private void arg(String name, Object[] arr) {
|
||||
if (arr == null) {
|
||||
arg(name, "null");
|
||||
} else {
|
||||
arg(name, toString(arr.length, arr, 0));
|
||||
}
|
||||
}
|
||||
|
||||
private String toString(int n, int[] arr, int offset) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("{\n");
|
||||
int arrLen = arr.length;
|
||||
for (int i = 0; i < n; i++) {
|
||||
int index = offset + i;
|
||||
buf.append(" [" + index + "] = ");
|
||||
if (index < 0 || index >= arrLen) {
|
||||
buf.append("out of bounds");
|
||||
} else {
|
||||
buf.append(arr[index]);
|
||||
}
|
||||
buf.append('\n');
|
||||
}
|
||||
buf.append("}");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private String toString(int n, Object[] arr, int offset) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("{\n");
|
||||
int arrLen = arr.length;
|
||||
for (int i = 0; i < n; i++) {
|
||||
int index = offset + i;
|
||||
buf.append(" [" + index + "] = ");
|
||||
if (index < 0 || index >= arrLen) {
|
||||
buf.append("out of bounds");
|
||||
} else {
|
||||
buf.append(arr[index]);
|
||||
}
|
||||
buf.append('\n');
|
||||
}
|
||||
buf.append("}");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static String getHex(int value) {
|
||||
return "0x" + Integer.toHexString(value);
|
||||
}
|
||||
|
||||
public static String getErrorString(int error) {
|
||||
switch (error) {
|
||||
case EGL_SUCCESS:
|
||||
return "EGL_SUCCESS";
|
||||
case EGL_NOT_INITIALIZED:
|
||||
return "EGL_NOT_INITIALIZED";
|
||||
case EGL_BAD_ACCESS:
|
||||
return "EGL_BAD_ACCESS";
|
||||
case EGL_BAD_ALLOC:
|
||||
return "EGL_BAD_ALLOC";
|
||||
case EGL_BAD_ATTRIBUTE:
|
||||
return "EGL_BAD_ATTRIBUTE";
|
||||
case EGL_BAD_CONFIG:
|
||||
return "EGL_BAD_CONFIG";
|
||||
case EGL_BAD_CONTEXT:
|
||||
return "EGL_BAD_CONTEXT";
|
||||
case EGL_BAD_CURRENT_SURFACE:
|
||||
return "EGL_BAD_CURRENT_SURFACE";
|
||||
case EGL_BAD_DISPLAY:
|
||||
return "EGL_BAD_DISPLAY";
|
||||
case EGL_BAD_MATCH:
|
||||
return "EGL_BAD_MATCH";
|
||||
case EGL_BAD_NATIVE_PIXMAP:
|
||||
return "EGL_BAD_NATIVE_PIXMAP";
|
||||
case EGL_BAD_NATIVE_WINDOW:
|
||||
return "EGL_BAD_NATIVE_WINDOW";
|
||||
case EGL_BAD_PARAMETER:
|
||||
return "EGL_BAD_PARAMETER";
|
||||
case EGL_BAD_SURFACE:
|
||||
return "EGL_BAD_SURFACE";
|
||||
case EGL11.EGL_CONTEXT_LOST:
|
||||
return "EGL_CONTEXT_LOST";
|
||||
default:
|
||||
return getHex(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.maplibre.android.maps.renderer.egl;
|
||||
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import javax.microedition.khronos.egl.EGL10;
|
||||
import javax.microedition.khronos.egl.EGLConfig;
|
||||
import javax.microedition.khronos.egl.EGLDisplay;
|
||||
import javax.microedition.khronos.egl.EGLSurface;
|
||||
|
||||
public class EGLWindowSurfaceFactory implements GLSurfaceView.EGLWindowSurfaceFactory {
|
||||
public EGLSurface createWindowSurface(@NonNull EGL10 egl, @Nullable EGLDisplay display, @Nullable EGLConfig config,
|
||||
@Nullable Object nativeWindow) {
|
||||
EGLSurface result = null;
|
||||
if (display != null && config != null && nativeWindow != null) {
|
||||
try {
|
||||
result = egl.eglCreateWindowSurface(display, config, nativeWindow, null);
|
||||
} catch (Exception exception) {
|
||||
// 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("EGLWindowSurfaceFactory", "eglCreateWindowSurface", exception);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void destroySurface(EGL10 egl, EGLDisplay display,
|
||||
EGLSurface surface) {
|
||||
egl.eglDestroySurface(display, surface);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Contains the MapLibre Maps Android EGL API classes.
|
||||
*/
|
||||
package org.maplibre.android.maps.renderer.egl;
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.maplibre.android.maps.renderer.surfaceview;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.maplibre.android.maps.renderer.egl.EGLConfigChooser;
|
||||
import org.maplibre.android.maps.renderer.egl.EGLContextFactory;
|
||||
import org.maplibre.android.maps.renderer.egl.EGLWindowSurfaceFactory;
|
||||
import org.maplibre.android.maps.renderer.MapRenderer;
|
||||
|
||||
public class GLSurfaceViewMapRenderer extends SurfaceViewMapRenderer {
|
||||
|
||||
public GLSurfaceViewMapRenderer(Context context,
|
||||
@NonNull MapLibreGLSurfaceView surfaceView,
|
||||
String localIdeographFontFamily) {
|
||||
super(context, surfaceView, localIdeographFontFamily);
|
||||
|
||||
surfaceView.setEGLContextFactory(new EGLContextFactory());
|
||||
surfaceView.setEGLWindowSurfaceFactory(new EGLWindowSurfaceFactory());
|
||||
surfaceView.setEGLConfigChooser(new EGLConfigChooser());
|
||||
surfaceView.setRenderer(this);
|
||||
surfaceView.setRenderingRefreshMode(MapRenderer.RenderingRefreshMode.WHEN_DIRTY);
|
||||
surfaceView.setPreserveEGLContextOnPause(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,660 @@
|
||||
package org.maplibre.android.maps.renderer.surfaceview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
|
||||
import org.maplibre.android.maps.renderer.egl.EGLLogWrapper;
|
||||
|
||||
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;
|
||||
|
||||
public class MapLibreGLSurfaceView extends MapLibreSurfaceView {
|
||||
|
||||
protected final WeakReference<MapLibreGLSurfaceView> viewWeakReference = new WeakReference<>(this);
|
||||
|
||||
private GLSurfaceView.EGLConfigChooser eglConfigChooser;
|
||||
private GLSurfaceView.EGLContextFactory eglContextFactory;
|
||||
private GLSurfaceView.EGLWindowSurfaceFactory eglWindowSurfaceFactory;
|
||||
|
||||
private boolean preserveEGLContextOnPause;
|
||||
|
||||
public MapLibreGLSurfaceView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public MapLibreGLSurfaceView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Control whether the EGL context is preserved when the GLSurfaceView is paused and
|
||||
* resumed.
|
||||
* <p>
|
||||
* If set to true, then the EGL context may be preserved when the GLSurfaceView is paused.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* If set to false, the EGL context will be released when the GLSurfaceView is paused,
|
||||
* and recreated when the GLSurfaceView is resumed.
|
||||
* <p>
|
||||
* <p>
|
||||
* The default is false.
|
||||
*
|
||||
* @param preserveOnPause preserve the EGL context when paused
|
||||
*/
|
||||
public void setPreserveEGLContextOnPause(boolean preserveOnPause) {
|
||||
preserveEGLContextOnPause = preserveOnPause;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the EGL context will be preserved when paused
|
||||
*/
|
||||
public boolean getPreserveEGLContextOnPause() {
|
||||
return preserveEGLContextOnPause;
|
||||
}
|
||||
|
||||
public void setRenderer(SurfaceViewMapRenderer renderer) {
|
||||
if (eglConfigChooser == null) {
|
||||
throw new IllegalStateException("No eglConfigChooser provided");
|
||||
}
|
||||
if (eglContextFactory == null) {
|
||||
throw new IllegalStateException("No eglContextFactory provided");
|
||||
}
|
||||
if (eglWindowSurfaceFactory == null) {
|
||||
throw new IllegalStateException("No eglWindowSurfaceFactory provided");
|
||||
}
|
||||
|
||||
super.setRenderer(renderer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a custom EGLContextFactory.
|
||||
* <p>If this method is
|
||||
* called, it must be called before {@link #setRenderer(GLSurfaceView.Renderer)}
|
||||
* is called.
|
||||
*/
|
||||
public void setEGLContextFactory(GLSurfaceView.EGLContextFactory factory) {
|
||||
checkRenderThreadState();
|
||||
eglContextFactory = factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a custom EGLWindowSurfaceFactory.
|
||||
* <p>If this method is
|
||||
* called, it must be called before {@link #setRenderer(GLSurfaceView.Renderer)}
|
||||
* is called.
|
||||
*/
|
||||
public void setEGLWindowSurfaceFactory(GLSurfaceView.EGLWindowSurfaceFactory factory) {
|
||||
checkRenderThreadState();
|
||||
eglWindowSurfaceFactory = factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a custom EGLConfigChooser.
|
||||
* <p>If this method is
|
||||
* called, it must be called before {@link #setRenderer(GLSurfaceView.Renderer)}
|
||||
* is called.
|
||||
*/
|
||||
public void setEGLConfigChooser(GLSurfaceView.EGLConfigChooser configChooser) {
|
||||
checkRenderThreadState();
|
||||
eglConfigChooser = configChooser;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createRenderThread() {
|
||||
renderThread = new GLThread(viewWeakReference);
|
||||
}
|
||||
|
||||
/**
|
||||
* An EGL helper class.
|
||||
*/
|
||||
private static class EglHelper {
|
||||
private EglHelper(WeakReference<MapLibreGLSurfaceView> glSurfaceViewWeakRef) {
|
||||
mGLSurfaceViewWeakRef = glSurfaceViewWeakRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize EGL for a given configuration spec.
|
||||
*/
|
||||
public void start() {
|
||||
try {
|
||||
/*
|
||||
* 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) {
|
||||
Log.e(TAG, "eglGetDisplay failed");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* We can now initialize EGL for that display
|
||||
*/
|
||||
int[] version = new int[2];
|
||||
if (!mEgl.eglInitialize(mEglDisplay, version)) {
|
||||
Log.e(TAG, "eglInitialize failed");
|
||||
return;
|
||||
}
|
||||
MapLibreGLSurfaceView view = mGLSurfaceViewWeakRef.get();
|
||||
if (view == null) {
|
||||
mEglConfig = null;
|
||||
mEglContext = null;
|
||||
} else {
|
||||
mEglConfig = view.eglConfigChooser.chooseConfig(mEgl, mEglDisplay);
|
||||
if (mEglConfig == null) {
|
||||
Log.e(TAG, "failed to select an EGL configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.eglContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
|
||||
}
|
||||
if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
|
||||
mEglContext = null;
|
||||
Log.e(TAG, "createContext failed");
|
||||
return;
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
Log.e(TAG, "createContext failed: ", exception);
|
||||
}
|
||||
mEglSurface = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
boolean createSurface() {
|
||||
/*
|
||||
* Check preconditions.
|
||||
*/
|
||||
if (mEgl == null) {
|
||||
Log.e(TAG, "egl not initialized");
|
||||
return false;
|
||||
}
|
||||
if (mEglDisplay == null) {
|
||||
Log.e(TAG, "eglDisplay not initialized");
|
||||
return false;
|
||||
}
|
||||
if (mEglConfig == null) {
|
||||
Log.e(TAG, "mEglConfig not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* The window size has changed, so we need to create a new
|
||||
* surface.
|
||||
*/
|
||||
destroySurfaceImp();
|
||||
|
||||
/*
|
||||
* Create an EGL surface we can render into.
|
||||
*/
|
||||
MapLibreGLSurfaceView view = mGLSurfaceViewWeakRef.get();
|
||||
if (view != null) {
|
||||
mEglSurface = view.eglWindowSurfaceFactory.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(TAG, "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(TAG, "eglMakeCurrent", mEgl.eglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a GL object for the current EGL context.
|
||||
*/
|
||||
GL createGL() {
|
||||
return mEglContext.getGL();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
void destroySurface() {
|
||||
destroySurfaceImp();
|
||||
}
|
||||
|
||||
private void destroySurfaceImp() {
|
||||
if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
|
||||
mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, mEglContext);
|
||||
MapLibreGLSurfaceView view = mGLSurfaceViewWeakRef.get();
|
||||
if (view != null) {
|
||||
view.eglWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface);
|
||||
}
|
||||
mEglSurface = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
if (mEglDisplay != null) {
|
||||
mEgl.eglMakeCurrent(mEglDisplay,
|
||||
EGL10.EGL_NO_SURFACE,
|
||||
EGL10.EGL_NO_SURFACE,
|
||||
EGL10.EGL_NO_CONTEXT);
|
||||
}
|
||||
|
||||
if (mEglContext != null) {
|
||||
MapLibreGLSurfaceView view = mGLSurfaceViewWeakRef.get();
|
||||
if (view != null) {
|
||||
view.eglContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext);
|
||||
}
|
||||
mEglContext = null;
|
||||
}
|
||||
if (mEglDisplay != null) {
|
||||
mEgl.eglTerminate(mEglDisplay);
|
||||
mEglDisplay = null;
|
||||
}
|
||||
}
|
||||
|
||||
static void logEglErrorAsWarning(String tag, String function, int error) {
|
||||
Log.w(tag, formatEglError(function, error));
|
||||
}
|
||||
|
||||
static String formatEglError(String function, int error) {
|
||||
return function + " failed: " + EGLLogWrapper.getErrorString(error);
|
||||
}
|
||||
|
||||
private final WeakReference<MapLibreGLSurfaceView> mGLSurfaceViewWeakRef;
|
||||
EGL10 mEgl;
|
||||
EGLDisplay mEglDisplay;
|
||||
EGLSurface mEglSurface;
|
||||
EGLConfig mEglConfig;
|
||||
EGLContext mEglContext;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic GL Thread. Takes care of initializing EGL and GL. Delegates
|
||||
* to a Renderer instance to do the actual drawing. Can be configured to
|
||||
* render continuously or on request.
|
||||
* <p>
|
||||
* All potentially blocking synchronization is done through the
|
||||
* sGLThreadManager object. This avoids multiple-lock ordering issues.
|
||||
*/
|
||||
static class GLThread extends MapLibreSurfaceView.RenderThread {
|
||||
GLThread(WeakReference<MapLibreGLSurfaceView> surfaceViewWeakRef) {
|
||||
super(surfaceViewWeakRef.get().renderThreadManager);
|
||||
|
||||
mSurfaceViewWeakRef = surfaceViewWeakRef;
|
||||
}
|
||||
|
||||
/*
|
||||
* This private method should only be called inside a
|
||||
* synchronized(sGLThreadManager) block.
|
||||
*/
|
||||
private void stopEglSurfaceLocked() {
|
||||
if (haveEglSurface) {
|
||||
haveEglSurface = false;
|
||||
eglHelper.destroySurface();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This private method should only be called inside a
|
||||
* synchronized(sGLThreadManager) block.
|
||||
*/
|
||||
private void stopEglContextLocked() {
|
||||
if (haveEglContext) {
|
||||
eglHelper.finish();
|
||||
haveEglContext = false;
|
||||
renderThreadManager.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void guardedRun() throws InterruptedException {
|
||||
eglHelper = new EglHelper(mSurfaceViewWeakRef);
|
||||
haveEglContext = false;
|
||||
haveEglSurface = false;
|
||||
wantRenderNotification = false;
|
||||
|
||||
try {
|
||||
GL10 gl = null;
|
||||
boolean createEglContext = false;
|
||||
boolean createEglSurface = false;
|
||||
boolean createGlInterface = false;
|
||||
boolean lostEglContext = false;
|
||||
boolean sizeChanged = false;
|
||||
boolean wantRenderNotification = false;
|
||||
boolean doRenderNotification = false;
|
||||
boolean askedToReleaseEglContext = false;
|
||||
int w = 0;
|
||||
int h = 0;
|
||||
Runnable event = null;
|
||||
Runnable finishDrawingRunnable = null;
|
||||
|
||||
while (true) {
|
||||
synchronized (renderThreadManager) {
|
||||
while (true) {
|
||||
if (shouldExit) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eventQueue.isEmpty()) {
|
||||
event = eventQueue.remove(0);
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the pause state.
|
||||
boolean pausing = false;
|
||||
if (paused != requestPaused) {
|
||||
pausing = requestPaused;
|
||||
paused = requestPaused;
|
||||
renderThreadManager.notifyAll();
|
||||
}
|
||||
|
||||
// Do we need to give up the EGL context?
|
||||
if (shouldReleaseEglContext) {
|
||||
stopEglSurfaceLocked();
|
||||
stopEglContextLocked();
|
||||
shouldReleaseEglContext = false;
|
||||
askedToReleaseEglContext = true;
|
||||
}
|
||||
|
||||
// Have we lost the EGL context?
|
||||
if (lostEglContext) {
|
||||
stopEglSurfaceLocked();
|
||||
stopEglContextLocked();
|
||||
lostEglContext = false;
|
||||
}
|
||||
|
||||
// When pausing, release the EGL surface:
|
||||
if (pausing && haveEglSurface) {
|
||||
stopEglSurfaceLocked();
|
||||
}
|
||||
|
||||
// When pausing, optionally release the EGL Context:
|
||||
if (pausing && haveEglContext) {
|
||||
MapLibreGLSurfaceView view = mSurfaceViewWeakRef.get();
|
||||
boolean preserveEglContextOnPause = view != null && view.preserveEGLContextOnPause;
|
||||
if (!preserveEglContextOnPause) {
|
||||
stopEglContextLocked();
|
||||
}
|
||||
}
|
||||
|
||||
// Have we lost the SurfaceView surface?
|
||||
if ((!hasSurface) && (!waitingForSurface)) {
|
||||
if (haveEglSurface) {
|
||||
stopEglSurfaceLocked();
|
||||
}
|
||||
waitingForSurface = true;
|
||||
surfaceIsBad = false;
|
||||
renderThreadManager.notifyAll();
|
||||
}
|
||||
|
||||
// Have we acquired the surface view surface?
|
||||
if (hasSurface && waitingForSurface) {
|
||||
waitingForSurface = false;
|
||||
renderThreadManager.notifyAll();
|
||||
}
|
||||
|
||||
if (doRenderNotification) {
|
||||
this.wantRenderNotification = false;
|
||||
doRenderNotification = false;
|
||||
renderComplete = true;
|
||||
renderThreadManager.notifyAll();
|
||||
}
|
||||
|
||||
if (this.finishDrawingRunnable != null) {
|
||||
finishDrawingRunnable = this.finishDrawingRunnable;
|
||||
this.finishDrawingRunnable = null;
|
||||
}
|
||||
|
||||
// Ready to draw?
|
||||
if (readyToDraw()) {
|
||||
|
||||
// If we don't have an EGL context, try to acquire one.
|
||||
if (!haveEglContext) {
|
||||
if (askedToReleaseEglContext) {
|
||||
askedToReleaseEglContext = false;
|
||||
} else {
|
||||
try {
|
||||
eglHelper.start();
|
||||
} catch (RuntimeException exception) {
|
||||
renderThreadManager.notifyAll();
|
||||
return;
|
||||
}
|
||||
haveEglContext = true;
|
||||
createEglContext = true;
|
||||
|
||||
renderThreadManager.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
if (haveEglContext && !haveEglSurface) {
|
||||
haveEglSurface = true;
|
||||
createEglSurface = true;
|
||||
createGlInterface = true;
|
||||
sizeChanged = true;
|
||||
}
|
||||
|
||||
if (haveEglSurface) {
|
||||
if (this.sizeChanged) {
|
||||
sizeChanged = true;
|
||||
w = width;
|
||||
h = height;
|
||||
this.wantRenderNotification = true;
|
||||
|
||||
// Destroy and recreate the EGL surface.
|
||||
createEglSurface = true;
|
||||
|
||||
this.sizeChanged = false;
|
||||
}
|
||||
requestRender = false;
|
||||
renderThreadManager.notifyAll();
|
||||
if (this.wantRenderNotification) {
|
||||
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 GLThread thread where we wait().
|
||||
renderThreadManager.wait();
|
||||
}
|
||||
} // end of synchronized(sGLThreadManager)
|
||||
|
||||
if (event != null) {
|
||||
event.run();
|
||||
event = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (createEglSurface) {
|
||||
if (eglHelper.createSurface()) {
|
||||
synchronized (renderThreadManager) {
|
||||
finishedCreatingEglSurface = true;
|
||||
renderThreadManager.notifyAll();
|
||||
}
|
||||
} else {
|
||||
synchronized (renderThreadManager) {
|
||||
finishedCreatingEglSurface = true;
|
||||
surfaceIsBad = true;
|
||||
renderThreadManager.notifyAll();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
createEglSurface = false;
|
||||
}
|
||||
|
||||
if (createGlInterface) {
|
||||
gl = (GL10) eglHelper.createGL();
|
||||
|
||||
createGlInterface = false;
|
||||
}
|
||||
|
||||
if (createEglContext) {
|
||||
MapLibreSurfaceView view = mSurfaceViewWeakRef.get();
|
||||
if (view != null) {
|
||||
view.renderer.onSurfaceCreated(null);
|
||||
}
|
||||
createEglContext = false;
|
||||
}
|
||||
|
||||
if (sizeChanged) {
|
||||
MapLibreSurfaceView view = mSurfaceViewWeakRef.get();
|
||||
if (view != null) {
|
||||
view.renderer.onSurfaceChanged(w, h);
|
||||
}
|
||||
sizeChanged = false;
|
||||
}
|
||||
|
||||
{
|
||||
MapLibreSurfaceView view = mSurfaceViewWeakRef.get();
|
||||
if (view != null) {
|
||||
view.renderer.onDrawFrame();
|
||||
if (finishDrawingRunnable != null) {
|
||||
finishDrawingRunnable.run();
|
||||
finishDrawingRunnable = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int swapError = eglHelper.swap();
|
||||
switch (swapError) {
|
||||
case EGL10.EGL_SUCCESS:
|
||||
break;
|
||||
case EGL11.EGL_CONTEXT_LOST:
|
||||
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(TAG, "eglSwapBuffers", swapError);
|
||||
|
||||
synchronized (renderThreadManager) {
|
||||
surfaceIsBad = true;
|
||||
renderThreadManager.notifyAll();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (wantRenderNotification) {
|
||||
doRenderNotification = true;
|
||||
wantRenderNotification = false;
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
/*
|
||||
* clean-up everything...
|
||||
*/
|
||||
synchronized (renderThreadManager) {
|
||||
stopEglSurfaceLocked();
|
||||
stopEglContextLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean readyToDraw() {
|
||||
return super.readyToDraw() && !surfaceIsBad;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ableToDraw() {
|
||||
return haveEglContext && haveEglSurface && readyToDraw();
|
||||
}
|
||||
|
||||
public void surfaceCreated() {
|
||||
synchronized (renderThreadManager) {
|
||||
hasSurface = true;
|
||||
finishedCreatingEglSurface = false;
|
||||
renderThreadManager.notifyAll();
|
||||
while (waitingForSurface
|
||||
&& !finishedCreatingEglSurface
|
||||
&& !exited) {
|
||||
try {
|
||||
renderThreadManager.wait();
|
||||
} catch (InterruptedException exception) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void requestReleaseEglContextLocked() {
|
||||
shouldReleaseEglContext = true;
|
||||
renderThreadManager.notifyAll();
|
||||
}
|
||||
|
||||
private boolean surfaceIsBad;
|
||||
private boolean haveEglContext;
|
||||
private boolean haveEglSurface;
|
||||
private boolean finishedCreatingEglSurface;
|
||||
private boolean shouldReleaseEglContext;
|
||||
|
||||
private EglHelper eglHelper;
|
||||
|
||||
/**
|
||||
* Set once at thread construction time, nulled out when the parent view is garbage
|
||||
* called. This weak reference allows the SurfaceView to be garbage collected while
|
||||
* the RenderThread is still alive.
|
||||
*/
|
||||
protected WeakReference<MapLibreGLSurfaceView> mSurfaceViewWeakRef;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
package org.maplibre.android.maps.renderer.textureview;
|
||||
|
||||
import android.view.TextureView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import org.maplibre.android.log.Logger;
|
||||
import org.maplibre.android.maps.renderer.egl.EGLConfigChooser;
|
||||
|
||||
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.GL10;
|
||||
|
||||
/**
|
||||
* The render thread is responsible for managing the communication between the
|
||||
* ui thread and the render thread it creates. Also, the EGL and GL contexts
|
||||
* are managed from here.
|
||||
*/
|
||||
public class GLTextureViewRenderThread extends TextureViewRenderThread {
|
||||
|
||||
@NonNull
|
||||
private final EGLHolder eglHolder;
|
||||
|
||||
private boolean destroyContext;
|
||||
|
||||
/**
|
||||
* Create a render thread for the given TextureView / Maprenderer combination.
|
||||
*
|
||||
* @param textureView the TextureView
|
||||
* @param mapRenderer the MapRenderer
|
||||
*/
|
||||
@UiThread
|
||||
public GLTextureViewRenderThread(@NonNull TextureView textureView, @NonNull TextureViewMapRenderer mapRenderer) {
|
||||
super(textureView, mapRenderer);
|
||||
this.eglHolder = new EGLHolder(new WeakReference<>(textureView), mapRenderer.isTranslucentSurface());
|
||||
}
|
||||
|
||||
// Thread implementation
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
|
||||
while (true) {
|
||||
Runnable event = null;
|
||||
boolean initializeEGL = false;
|
||||
boolean recreateSurface = false;
|
||||
int w = -1;
|
||||
int h = -1;
|
||||
|
||||
// Guarded block
|
||||
synchronized (lock) {
|
||||
while (true) {
|
||||
|
||||
if (shouldExit) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If any events are scheduled, pop one for processing
|
||||
if (!eventQueue.isEmpty()) {
|
||||
event = eventQueue.remove(0);
|
||||
break;
|
||||
}
|
||||
|
||||
if (destroySurface) {
|
||||
eglHolder.destroySurface();
|
||||
destroySurface = false;
|
||||
this.hasNativeSurface = false;
|
||||
lock.notifyAll();
|
||||
break;
|
||||
}
|
||||
|
||||
if (destroyContext) {
|
||||
eglHolder.destroyContext();
|
||||
destroyContext = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (surfaceTexture != null && !paused && requestRender) {
|
||||
|
||||
w = width;
|
||||
h = height;
|
||||
|
||||
// Initialize EGL if needed
|
||||
if (eglHolder.eglContext == EGL10.EGL_NO_CONTEXT) {
|
||||
this.hasNativeSurface = true;
|
||||
initializeEGL = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// (re-)Initialize EGL Surface if needed
|
||||
if (eglHolder.eglSurface == EGL10.EGL_NO_SURFACE) {
|
||||
this.hasNativeSurface = true;
|
||||
recreateSurface = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset the request render flag now, so we can catch new requests
|
||||
// while rendering
|
||||
requestRender = false;
|
||||
|
||||
// Break the guarded loop and continue to process
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Wait until needed
|
||||
lock.wait();
|
||||
|
||||
} // end guarded while loop
|
||||
|
||||
} // end guarded block
|
||||
|
||||
// Run event, if any
|
||||
if (event != null) {
|
||||
event.run();
|
||||
continue;
|
||||
}
|
||||
|
||||
GL10 gl = eglHolder.createGL();
|
||||
|
||||
// Initialize EGL
|
||||
if (initializeEGL) {
|
||||
eglHolder.prepare();
|
||||
synchronized (lock) {
|
||||
if (!eglHolder.createSurface()) {
|
||||
// Cleanup the surface if one could not be created
|
||||
// and wait for another to be ready.
|
||||
destroySurface = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
mapRenderer.onSurfaceCreated(null);
|
||||
mapRenderer.onSurfaceChanged(w, h);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the surface size has changed inform the map renderer.
|
||||
if (recreateSurface) {
|
||||
synchronized (lock) {
|
||||
eglHolder.createSurface();
|
||||
}
|
||||
mapRenderer.onSurfaceChanged(w, h);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sizeChanged) {
|
||||
mapRenderer.onSurfaceChanged(w, h);
|
||||
sizeChanged = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't continue without a surface
|
||||
if (eglHolder.eglSurface == EGL10.EGL_NO_SURFACE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Time to render a frame
|
||||
mapRenderer.onDrawFrame();
|
||||
|
||||
// Swap and check the result
|
||||
int swapError = eglHolder.swap();
|
||||
switch (swapError) {
|
||||
case EGL10.EGL_SUCCESS:
|
||||
break;
|
||||
case EGL11.EGL_CONTEXT_LOST:
|
||||
Logger.w(TAG, "Context lost. Waiting for re-aquire");
|
||||
synchronized (lock) {
|
||||
surfaceTexture = null;
|
||||
destroySurface = true;
|
||||
destroyContext = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Logger.w(TAG, String.format("eglSwapBuffer error: %s. Waiting or new surface", swapError));
|
||||
// Probably lost the surface. Clear the current one and
|
||||
// wait for a new one to be set
|
||||
synchronized (lock) {
|
||||
surfaceTexture = null;
|
||||
destroySurface = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (InterruptedException err) {
|
||||
// To be expected
|
||||
} finally {
|
||||
// Cleanup
|
||||
eglHolder.cleanup();
|
||||
|
||||
// Signal we're done
|
||||
synchronized (lock) {
|
||||
this.hasNativeSurface = false;
|
||||
this.exited = true;
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the EGL state and offers methods to mutate it.
|
||||
*/
|
||||
private static class EGLHolder {
|
||||
private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
|
||||
private final WeakReference<TextureView> textureViewWeakRef;
|
||||
private boolean translucentSurface;
|
||||
|
||||
private EGL10 egl;
|
||||
@Nullable
|
||||
private EGLConfig eglConfig;
|
||||
private EGLDisplay eglDisplay = EGL10.EGL_NO_DISPLAY;
|
||||
private EGLContext eglContext = EGL10.EGL_NO_CONTEXT;
|
||||
private EGLSurface eglSurface = EGL10.EGL_NO_SURFACE;
|
||||
|
||||
EGLHolder(WeakReference<TextureView> textureViewWeakRef, boolean translucentSurface) {
|
||||
this.textureViewWeakRef = textureViewWeakRef;
|
||||
this.translucentSurface = translucentSurface;
|
||||
}
|
||||
|
||||
void prepare() {
|
||||
this.egl = (EGL10) EGLContext.getEGL();
|
||||
|
||||
// Only re-initialize display when needed
|
||||
if (eglDisplay == EGL10.EGL_NO_DISPLAY) {
|
||||
this.eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
|
||||
|
||||
if (eglDisplay == EGL10.EGL_NO_DISPLAY) {
|
||||
throw new RuntimeException("eglGetDisplay failed");
|
||||
}
|
||||
|
||||
int[] version = new int[2];
|
||||
if (!egl.eglInitialize(eglDisplay, version)) {
|
||||
throw new RuntimeException("eglInitialize failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (textureViewWeakRef == null) {
|
||||
// No texture view present
|
||||
eglConfig = null;
|
||||
eglContext = EGL10.EGL_NO_CONTEXT;
|
||||
} else if (eglContext == EGL10.EGL_NO_CONTEXT) {
|
||||
eglConfig = new EGLConfigChooser(translucentSurface).chooseConfig(egl, eglDisplay);
|
||||
int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE};
|
||||
eglContext = egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
|
||||
}
|
||||
|
||||
if (eglContext == EGL10.EGL_NO_CONTEXT) {
|
||||
throw new RuntimeException("createContext");
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
GL10 createGL() {
|
||||
return (GL10) eglContext.getGL();
|
||||
}
|
||||
|
||||
boolean createSurface() {
|
||||
// The window size has changed, so we need to create a new surface.
|
||||
destroySurface();
|
||||
|
||||
// Create an EGL surface we can render into.
|
||||
TextureView view = textureViewWeakRef.get();
|
||||
if (view != null && view.getSurfaceTexture() != null) {
|
||||
int[] surfaceAttribs = {EGL10.EGL_NONE};
|
||||
eglSurface = egl.eglCreateWindowSurface(eglDisplay, eglConfig, view.getSurfaceTexture(), surfaceAttribs);
|
||||
} else {
|
||||
eglSurface = EGL10.EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
if (eglSurface == null || eglSurface == EGL10.EGL_NO_SURFACE) {
|
||||
int error = egl.eglGetError();
|
||||
if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
|
||||
Logger.e(TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return makeCurrent();
|
||||
}
|
||||
|
||||
boolean makeCurrent() {
|
||||
if (!egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
|
||||
// Could not make the context current, probably because the underlying
|
||||
// SurfaceView surface has been destroyed.
|
||||
Logger.w(TAG, String.format("eglMakeCurrent: %s", egl.eglGetError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int swap() {
|
||||
if (!egl.eglSwapBuffers(eglDisplay, eglSurface)) {
|
||||
return egl.eglGetError();
|
||||
}
|
||||
return EGL10.EGL_SUCCESS;
|
||||
}
|
||||
|
||||
private void destroySurface() {
|
||||
if (eglSurface == EGL10.EGL_NO_SURFACE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!egl.eglDestroySurface(eglDisplay, eglSurface)) {
|
||||
Logger.w(TAG, String.format("Could not destroy egl surface. Display %s, Surface %s", eglDisplay, eglSurface));
|
||||
}
|
||||
|
||||
eglSurface = EGL10.EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
private void destroyContext() {
|
||||
if (eglContext == EGL10.EGL_NO_CONTEXT) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!egl.eglDestroyContext(eglDisplay, eglContext)) {
|
||||
Logger.w(TAG, String.format("Could not destroy egl context. Display %s, Context %s", eglDisplay, eglContext));
|
||||
}
|
||||
|
||||
eglContext = EGL10.EGL_NO_CONTEXT;
|
||||
}
|
||||
|
||||
private void terminate() {
|
||||
if (eglDisplay == EGL10.EGL_NO_DISPLAY) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!egl.eglTerminate(eglDisplay)) {
|
||||
Logger.w(TAG, String.format("Could not terminate egl. Display %s", eglDisplay));
|
||||
}
|
||||
eglDisplay = EGL10.EGL_NO_DISPLAY;
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
destroySurface();
|
||||
destroyContext();
|
||||
terminate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.maplibre.android.maps.renderer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.Surface;
|
||||
import android.view.TextureView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.maplibre.android.maps.renderer.surfaceview.MapLibreVulkanSurfaceView;
|
||||
import org.maplibre.android.maps.renderer.surfaceview.SurfaceViewMapRenderer;
|
||||
import org.maplibre.android.maps.renderer.surfaceview.VulkanSurfaceViewMapRenderer;
|
||||
import org.maplibre.android.maps.renderer.textureview.TextureViewMapRenderer;
|
||||
import org.maplibre.android.maps.renderer.textureview.VulkanTextureViewRenderThread;
|
||||
|
||||
/**
|
||||
* Vulkan concrete impl behind {@link MapRendererFactory}. Lives alongside the
|
||||
* other Vulkan renderer helpers in src/sharedRenderer/vulkan/.
|
||||
*/
|
||||
final class VulkanRendererStrategy {
|
||||
|
||||
private VulkanRendererStrategy() {}
|
||||
|
||||
static void attachTextureRenderThread(@NonNull TextureView textureView,
|
||||
@NonNull TextureViewMapRenderer renderer) {
|
||||
renderer.setRenderThread(new VulkanTextureViewRenderThread(textureView, renderer));
|
||||
}
|
||||
|
||||
static SurfaceViewMapRenderer createSurfaceViewRenderer(@NonNull Context context,
|
||||
String localFontFamily,
|
||||
boolean renderSurfaceOnTop,
|
||||
Runnable initCallback) {
|
||||
MapLibreVulkanSurfaceView surfaceView = new MapLibreVulkanSurfaceView(context);
|
||||
surfaceView.setZOrderMediaOverlay(renderSurfaceOnTop);
|
||||
return new VulkanSurfaceViewMapRenderer(context, surfaceView, localFontFamily) {
|
||||
@Override
|
||||
public void onSurfaceCreated(Surface surface) {
|
||||
initCallback.run();
|
||||
super.onSurfaceCreated(surface);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package org.maplibre.android.maps.renderer.surfaceview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class MapLibreVulkanSurfaceView extends MapLibreSurfaceView {
|
||||
|
||||
protected final WeakReference<MapLibreVulkanSurfaceView> viewWeakReference = new WeakReference<>(this);
|
||||
|
||||
public MapLibreVulkanSurfaceView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public MapLibreVulkanSurfaceView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createRenderThread() {
|
||||
renderThread = new VulkanThread(viewWeakReference);
|
||||
}
|
||||
|
||||
static class VulkanThread extends MapLibreSurfaceView.RenderThread {
|
||||
VulkanThread(WeakReference<MapLibreVulkanSurfaceView> surfaceViewWeakRef) {
|
||||
super(surfaceViewWeakRef.get().renderThreadManager);
|
||||
mSurfaceViewWeakRef = surfaceViewWeakRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void guardedRun() throws InterruptedException {
|
||||
wantRenderNotification = false;
|
||||
|
||||
boolean sizeChanged = false;
|
||||
boolean initSurface = false;
|
||||
boolean wantRenderNotification = false;
|
||||
boolean doRenderNotification = false;
|
||||
int w = 0;
|
||||
int h = 0;
|
||||
Runnable event = null;
|
||||
Runnable finishDrawingRunnable = null;
|
||||
|
||||
while (true) {
|
||||
synchronized (renderThreadManager) {
|
||||
while (true) {
|
||||
if (shouldExit) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eventQueue.isEmpty()) {
|
||||
event = eventQueue.remove(0);
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the pause state.
|
||||
if (paused != requestPaused) {
|
||||
paused = requestPaused;
|
||||
renderThreadManager.notifyAll();
|
||||
}
|
||||
|
||||
// lost surface
|
||||
if (!hasSurface && !waitingForSurface) {
|
||||
MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get();
|
||||
if (view != null && graphicsSurfaceCreated) {
|
||||
view.renderer.onSurfaceDestroyed();
|
||||
}
|
||||
graphicsSurfaceCreated = false;
|
||||
waitingForSurface = true;
|
||||
renderThreadManager.notifyAll();
|
||||
}
|
||||
|
||||
// acquired surface
|
||||
if (hasSurface && waitingForSurface) {
|
||||
MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get();
|
||||
if (view != null) {
|
||||
initSurface = true;
|
||||
graphicsSurfaceCreated = true;
|
||||
}
|
||||
waitingForSurface = false;
|
||||
renderThreadManager.notifyAll();
|
||||
}
|
||||
|
||||
if (doRenderNotification) {
|
||||
this.wantRenderNotification = false;
|
||||
doRenderNotification = false;
|
||||
renderComplete = true;
|
||||
renderThreadManager.notifyAll();
|
||||
}
|
||||
|
||||
if (this.finishDrawingRunnable != null) {
|
||||
finishDrawingRunnable = this.finishDrawingRunnable;
|
||||
this.finishDrawingRunnable = null;
|
||||
}
|
||||
|
||||
// Ready to draw?
|
||||
if (readyToDraw() && graphicsSurfaceCreated) {
|
||||
if (this.sizeChanged) {
|
||||
sizeChanged = true;
|
||||
w = width;
|
||||
h = height;
|
||||
this.wantRenderNotification = true;
|
||||
this.sizeChanged = false;
|
||||
}
|
||||
requestRender = false;
|
||||
renderThreadManager.notifyAll();
|
||||
if (this.wantRenderNotification) {
|
||||
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 RenderThread thread where we wait().
|
||||
renderThreadManager.wait();
|
||||
}
|
||||
} // end of synchronized(sRenderThreadManager)
|
||||
|
||||
if (event != null) {
|
||||
event.run();
|
||||
event = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (initSurface) {
|
||||
MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get();
|
||||
if (view != null) {
|
||||
view.renderer.onSurfaceCreated(view.getHolder().getSurface());
|
||||
initSurface = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sizeChanged) {
|
||||
MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get();
|
||||
if (view != null) {
|
||||
view.renderer.onSurfaceChanged(w, h);
|
||||
sizeChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get();
|
||||
if (view != null) {
|
||||
view.renderer.onDrawFrame();
|
||||
if (finishDrawingRunnable != null) {
|
||||
finishDrawingRunnable.run();
|
||||
finishDrawingRunnable = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wantRenderNotification) {
|
||||
doRenderNotification = true;
|
||||
wantRenderNotification = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ableToDraw() {
|
||||
return graphicsSurfaceCreated && readyToDraw();
|
||||
}
|
||||
|
||||
private boolean graphicsSurfaceCreated;
|
||||
|
||||
private final WeakReference<MapLibreVulkanSurfaceView> mSurfaceViewWeakRef;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.maplibre.android.maps.renderer.surfaceview;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class VulkanSurfaceViewMapRenderer extends SurfaceViewMapRenderer {
|
||||
|
||||
public VulkanSurfaceViewMapRenderer(Context context,
|
||||
@NonNull MapLibreVulkanSurfaceView surfaceView,
|
||||
String localIdeographFontFamily) {
|
||||
super(context, surfaceView, localIdeographFontFamily);
|
||||
|
||||
this.surfaceView.setRenderer(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package org.maplibre.android.maps.renderer.textureview;
|
||||
|
||||
import android.view.Surface;
|
||||
import android.view.TextureView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
public class VulkanTextureViewRenderThread extends TextureViewRenderThread {
|
||||
|
||||
private Surface surface;
|
||||
|
||||
@UiThread
|
||||
public VulkanTextureViewRenderThread(@NonNull TextureView textureView, @NonNull TextureViewMapRenderer mapRenderer) {
|
||||
super(textureView, mapRenderer);
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
if (surface != null) {
|
||||
mapRenderer.onSurfaceDestroyed();
|
||||
surface.release();
|
||||
surface = null;
|
||||
}
|
||||
|
||||
hasNativeSurface = false;
|
||||
}
|
||||
|
||||
// Thread implementation
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
|
||||
while (true) {
|
||||
Runnable event = null;
|
||||
boolean createSurface = false;
|
||||
boolean sizeChanged = false;
|
||||
int w = -1;
|
||||
int h = -1;
|
||||
|
||||
// Guarded block
|
||||
synchronized (lock) {
|
||||
while (true) {
|
||||
|
||||
if (shouldExit) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If any events are scheduled, pop one for processing
|
||||
if (!eventQueue.isEmpty()) {
|
||||
event = eventQueue.remove(0);
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.destroySurface) {
|
||||
if (surface != null) {
|
||||
mapRenderer.onSurfaceDestroyed();
|
||||
surface.release();
|
||||
surface = null;
|
||||
}
|
||||
|
||||
this.hasNativeSurface = false;
|
||||
this.destroySurface = false;
|
||||
lock.notifyAll();
|
||||
break;
|
||||
}
|
||||
|
||||
if (surfaceTexture != null && !paused && requestRender) {
|
||||
|
||||
w = width;
|
||||
h = height;
|
||||
|
||||
if (surface == null) {
|
||||
surface = new Surface(surfaceTexture);
|
||||
this.hasNativeSurface = true;
|
||||
createSurface = true;
|
||||
}
|
||||
|
||||
if (this.sizeChanged) {
|
||||
sizeChanged = true;
|
||||
this.sizeChanged = false;
|
||||
}
|
||||
|
||||
// Reset the request render flag now, so we can catch new requests
|
||||
// while rendering
|
||||
requestRender = false;
|
||||
|
||||
// Break the guarded loop and continue to process
|
||||
break;
|
||||
}
|
||||
|
||||
// Wait until needed
|
||||
lock.wait();
|
||||
|
||||
} // end guarded while loop
|
||||
|
||||
} // end guarded block
|
||||
|
||||
// Run event, if any
|
||||
if (event != null) {
|
||||
event.run();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (createSurface) {
|
||||
mapRenderer.onSurfaceCreated(surface);
|
||||
mapRenderer.onSurfaceChanged(w, h);
|
||||
createSurface = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the surface size has changed inform the map renderer.
|
||||
if (sizeChanged) {
|
||||
mapRenderer.onSurfaceChanged(w, h);
|
||||
sizeChanged = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (surface == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Time to render a frame
|
||||
mapRenderer.onDrawFrame();
|
||||
}
|
||||
|
||||
} catch (InterruptedException err) {
|
||||
// To be expected
|
||||
} finally {
|
||||
|
||||
// Signal we're done
|
||||
synchronized (lock) {
|
||||
cleanup();
|
||||
this.exited = true;
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user