Add Android Vulkan backend (#2711)

Co-authored-by: Bart Louwers <bart.louwers@gmail.com>
This commit is contained in:
Adrian Cojocaru
2024-09-06 17:40:57 +03:00
committed by GitHub
parent 4b5d06d908
commit ea09bdf2b6
79 changed files with 2580 additions and 1400 deletions

View File

@@ -78,6 +78,24 @@ android {
}
}
}
create("vulkan") {
dimension = "renderer"
externalNativeBuild {
cmake {
arguments "-DMLN_LEGACY_RENDERER=OFF", "-DMLN_DRAWABLE_RENDERER=ON"
arguments "-DMLN_WITH_OPENGL=OFF", "-DMLN_WITH_VULKAN=ON"
}
}
}
}
sourceSets {
legacy {
java.srcDirs += ['src/opengl/java/']
}
drawable {
java.srcDirs += ['src/opengl/java/']
}
}
// build native libraries
@@ -156,6 +174,10 @@ configurations {
all*.exclude group: 'commons-collections', module: 'commons-collections'
}
// Download validation layers from
// https://github.com/KhronosGroup/Vulkan-ValidationLayers
apply from: "${rootDir}/gradle/download-vulkan-validation.gradle"
apply from: "${rootDir}/gradle/gradle-checkstyle.gradle"
apply from: "${rootDir}/gradle/gradle-dependencies-graph.gradle"
apply from: "${rootDir}/gradle/android-nitpick.gradle"

View File

@@ -195,6 +195,30 @@ add_library(maplibre SHARED
${PROJECT_SOURCE_DIR}/util/default_style.hpp
${PROJECT_SOURCE_DIR}/util/tile_server_options.hpp)
if(MLN_WITH_OPENGL)
target_sources(
maplibre PRIVATE
${PROJECT_SOURCE_DIR}/android_gl_renderer_backend.cpp
${PROJECT_SOURCE_DIR}/android_gl_renderer_backend.hpp
)
target_compile_definitions(
maplibre
PRIVATE MLN_RENDER_BACKEND_OPENGL=1
)
endif()
if(MLN_WITH_VULKAN)
target_sources(
maplibre PRIVATE
${PROJECT_SOURCE_DIR}/android_vulkan_renderer_backend.cpp
${PROJECT_SOURCE_DIR}/android_vulkan_renderer_backend.hpp
)
target_compile_definitions(
maplibre
PRIVATE MLN_RENDER_BACKEND_VULKAN=1
)
endif()
target_include_directories(
maplibre
PRIVATE ${PROJECT_SOURCE_DIR}

View File

@@ -0,0 +1,84 @@
#include "android_gl_renderer_backend.hpp"
#include <mbgl/gfx/backend_scope.hpp>
#include <mbgl/gl/context.hpp>
#include <mbgl/gl/renderable_resource.hpp>
#include <EGL/egl.h>
#include <cassert>
namespace mbgl {
namespace android {
class AndroidGLRenderableResource final : public mbgl::gl::RenderableResource {
public:
AndroidGLRenderableResource(AndroidGLRendererBackend& backend_)
: backend(backend_) {}
void bind() override {
assert(gfx::BackendScope::exists());
backend.setFramebufferBinding(0);
backend.setViewport(0, 0, backend.getSize());
}
void swap() override {
const auto& swapBehaviour = static_cast<AndroidGLRendererBackend&>(backend).getSwapBehavior();
if (swapBehaviour == gfx::Renderable::SwapBehaviour::Flush) {
static_cast<gl::Context&>(backend.getContext()).finish();
}
}
private:
AndroidGLRendererBackend& backend;
};
AndroidGLRendererBackend::AndroidGLRendererBackend()
: gl::RendererBackend(gfx::ContextMode::Unique),
mbgl::gfx::Renderable({64, 64}, std::make_unique<AndroidGLRenderableResource>(*this)) {}
AndroidGLRendererBackend::~AndroidGLRendererBackend() = default;
gl::ProcAddress AndroidGLRendererBackend::getExtensionFunctionPointer(const char* name) {
assert(gfx::BackendScope::exists());
return eglGetProcAddress(name);
}
void AndroidGLRendererBackend::updateViewPort() {
assert(gfx::BackendScope::exists());
setViewport(0, 0, size);
}
void AndroidGLRendererBackend::resizeFramebuffer(int width, int height) {
size = {static_cast<uint32_t>(width), static_cast<uint32_t>(height)};
}
PremultipliedImage AndroidGLRendererBackend::readFramebuffer() {
assert(gfx::BackendScope::exists());
return gl::RendererBackend::readFramebuffer(size);
}
void AndroidGLRendererBackend::updateAssumedState() {
assumeFramebufferBinding(0);
assumeViewport(0, 0, size);
}
void AndroidGLRendererBackend::markContextLost() {
if (context) {
getContext<gl::Context>().setCleanupOnDestruction(false);
}
}
} // namespace android
} // namespace mbgl
namespace mbgl {
namespace gfx {
template <>
std::unique_ptr<android::AndroidRendererBackend> Backend::Create<mbgl::gfx::Backend::Type::OpenGL>(ANativeWindow*) {
return std::make_unique<android::AndroidGLRendererBackend>();
}
} // namespace gfx
} // namespace mbgl

View File

@@ -0,0 +1,46 @@
#pragma once
#include <mbgl/gfx/renderable.hpp>
#include <mbgl/gl/renderer_backend.hpp>
#include "android_renderer_backend.hpp"
namespace mbgl {
namespace android {
class AndroidGLRendererBackend : public AndroidRendererBackend,
public gl::RendererBackend,
public mbgl::gfx::Renderable {
public:
AndroidGLRendererBackend();
~AndroidGLRendererBackend() override;
mbgl::gfx::RendererBackend& getImpl() override { return *this; }
void updateViewPort() override;
// Ensures the current context is not cleaned up when destroyed
void markContextLost() override;
void resizeFramebuffer(int width, int height) override;
PremultipliedImage readFramebuffer() override;
// mbgl::gfx::RendererBackend implementation
public:
mbgl::gfx::Renderable& getDefaultRenderable() override { return *this; }
protected:
void activate() override {
// no-op
}
void deactivate() override {
// no-op
}
// mbgl::gl::RendererBackend implementation
protected:
mbgl::gl::ProcAddress getExtensionFunctionPointer(const char*) override;
void updateAssumedState() override;
};
} // namespace android
} // namespace mbgl

View File

@@ -1,78 +1,22 @@
#include "android_renderer_backend.hpp"
#include <mbgl/gfx/backend_scope.hpp>
#include <mbgl/gl/context.hpp>
#include <mbgl/gl/renderable_resource.hpp>
#include <EGL/egl.h>
#include <cassert>
namespace mbgl {
namespace android {
class AndroidGLRenderableResource final : public mbgl::gl::RenderableResource {
public:
AndroidGLRenderableResource(AndroidRendererBackend& backend_)
: backend(backend_) {}
void AndroidRendererBackend::updateViewPort() {}
void bind() override {
assert(gfx::BackendScope::exists());
backend.setFramebufferBinding(0);
backend.setViewport(0, 0, backend.getSize());
}
void swap() override { backend.swap(); }
private:
AndroidRendererBackend& backend;
};
AndroidRendererBackend::AndroidRendererBackend(const TaggedScheduler& threadPool)
: gl::RendererBackend(gfx::ContextMode::Unique),
mbgl::gfx::Renderable({64, 64}, std::make_unique<AndroidGLRenderableResource>(*this)) {}
AndroidRendererBackend::~AndroidRendererBackend() = default;
gl::ProcAddress AndroidRendererBackend::getExtensionFunctionPointer(const char* name) {
assert(gfx::BackendScope::exists());
return eglGetProcAddress(name);
}
void AndroidRendererBackend::updateViewPort() {
assert(gfx::BackendScope::exists());
setViewport(0, 0, size);
}
void AndroidRendererBackend::resizeFramebuffer(int width, int height) {
size = {static_cast<uint32_t>(width), static_cast<uint32_t>(height)};
}
void AndroidRendererBackend::resizeFramebuffer(int width, int height) {}
PremultipliedImage AndroidRendererBackend::readFramebuffer() {
assert(gfx::BackendScope::exists());
return gl::RendererBackend::readFramebuffer(size);
return PremultipliedImage();
}
void AndroidRendererBackend::updateAssumedState() {
assumeFramebufferBinding(0);
assumeViewport(0, 0, size);
}
void AndroidRendererBackend::markContextLost() {}
void AndroidRendererBackend::markContextLost() {
if (context) {
getContext<gl::Context>().setCleanupOnDestruction(false);
}
}
void AndroidRendererBackend::setSwapBehavior(SwapBehaviour swapBehaviour_) {
void AndroidRendererBackend::setSwapBehavior(gfx::Renderable::SwapBehaviour swapBehaviour_) {
swapBehaviour = swapBehaviour_;
}
void AndroidRendererBackend::swap() {
if (swapBehaviour == SwapBehaviour::Flush) {
static_cast<gl::Context&>(getContext()).finish();
}
}
} // namespace android
} // namespace mbgl

View File

@@ -1,46 +1,40 @@
#pragma once
#include <mbgl/gfx/backend.hpp>
#include <mbgl/gfx/renderer_backend.hpp>
#include <mbgl/gfx/renderable.hpp>
#include <mbgl/gl/renderer_backend.hpp>
#include <mbgl/util/image.hpp>
#include <android/native_window.h>
namespace mbgl {
namespace android {
class AndroidRendererBackend : public gl::RendererBackend, public mbgl::gfx::Renderable {
class AndroidRendererBackend {
public:
AndroidRendererBackend(const TaggedScheduler& threadPool);
~AndroidRendererBackend() override;
AndroidRendererBackend() = default;
AndroidRendererBackend(const AndroidRendererBackend&) = delete;
AndroidRendererBackend& operator=(const AndroidRendererBackend&) = delete;
virtual ~AndroidRendererBackend() = default;
void updateViewPort();
static std::unique_ptr<AndroidRendererBackend> Create(ANativeWindow* window) {
return mbgl::gfx::Backend::Create<AndroidRendererBackend, ANativeWindow*>(window);
}
virtual mbgl::gfx::RendererBackend& getImpl() = 0;
virtual void updateViewPort();
// Ensures the current context is not cleaned up when destroyed
void markContextLost();
virtual void markContextLost();
void resizeFramebuffer(int width, int height);
PremultipliedImage readFramebuffer();
virtual void resizeFramebuffer(int width, int height);
virtual PremultipliedImage readFramebuffer();
void setSwapBehavior(SwapBehaviour swapBehaviour);
void swap();
private:
SwapBehaviour swapBehaviour = SwapBehaviour::NoFlush;
// mbgl::gfx::RendererBackend implementation
public:
mbgl::gfx::Renderable& getDefaultRenderable() override { return *this; }
gfx::Renderable::SwapBehaviour getSwapBehavior() const { return swapBehaviour; }
virtual void setSwapBehavior(gfx::Renderable::SwapBehaviour swapBehaviour);
protected:
void activate() override {
// no-op
}
void deactivate() override {
// no-op
}
// mbgl::gl::RendererBackend implementation
protected:
mbgl::gl::ProcAddress getExtensionFunctionPointer(const char*) override;
void updateAssumedState() override;
gfx::Renderable::SwapBehaviour swapBehaviour = gfx::Renderable::SwapBehaviour::NoFlush;
};
} // namespace android

View File

@@ -0,0 +1,79 @@
#include "android_vulkan_renderer_backend.hpp"
#include <mbgl/gfx/backend_scope.hpp>
#include <mbgl/vulkan/context.hpp>
#include <mbgl/vulkan/renderable_resource.hpp>
#include <cassert>
#include <vulkan/vulkan_android.h>
namespace mbgl {
namespace android {
class AndroidVulkanRenderableResource final : public mbgl::vulkan::SurfaceRenderableResource {
public:
AndroidVulkanRenderableResource(AndroidVulkanRendererBackend& backend_)
: SurfaceRenderableResource(backend_) {}
std::vector<const char*> getDeviceExtensions() override {
return {
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
};
}
void createPlatformSurface() override {
auto& backendImpl = static_cast<AndroidVulkanRendererBackend&>(backend);
const vk::AndroidSurfaceCreateInfoKHR createInfo({}, backendImpl.getWindow());
surface = backendImpl.getInstance()->createAndroidSurfaceKHRUnique(createInfo);
}
void bind() override {}
void swap() override {
vulkan::SurfaceRenderableResource::swap();
const auto& swapBehaviour = static_cast<AndroidVulkanRendererBackend&>(backend).getSwapBehavior();
if (swapBehaviour == gfx::Renderable::SwapBehaviour::Flush) {
static_cast<vulkan::Context&>(backend.getContext()).waitFrame();
}
}
private:
};
AndroidVulkanRendererBackend::AndroidVulkanRendererBackend(ANativeWindow* window_)
: vulkan::RendererBackend(gfx::ContextMode::Unique),
mbgl::gfx::Renderable({64, 64}, std::make_unique<AndroidVulkanRenderableResource>(*this)),
window(window_) {
init();
}
AndroidVulkanRendererBackend::~AndroidVulkanRendererBackend() = default;
std::vector<const char*> AndroidVulkanRendererBackend::getInstanceExtensions() {
auto extensions = mbgl::vulkan::RendererBackend::getInstanceExtensions();
extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
return extensions;
}
void AndroidVulkanRendererBackend::resizeFramebuffer(int width, int height) {
size = {static_cast<uint32_t>(width), static_cast<uint32_t>(height)};
if (context) {
static_cast<vulkan::Context&>(*context).requestSurfaceUpdate();
}
}
} // namespace android
} // namespace mbgl
namespace mbgl {
namespace gfx {
template <>
std::unique_ptr<android::AndroidRendererBackend> Backend::Create<mbgl::gfx::Backend::Type::Vulkan>(
ANativeWindow* window) {
return std::make_unique<android::AndroidVulkanRendererBackend>(window);
}
} // namespace gfx
} // namespace mbgl

View File

@@ -0,0 +1,42 @@
#pragma once
#include <mbgl/gfx/renderable.hpp>
#include <mbgl/vulkan/renderer_backend.hpp>
#include "android_renderer_backend.hpp"
#include <android/native_window.h>
namespace mbgl {
namespace android {
class AndroidVulkanRendererBackend : public AndroidRendererBackend,
public vulkan::RendererBackend,
public mbgl::gfx::Renderable {
public:
AndroidVulkanRendererBackend(ANativeWindow*);
~AndroidVulkanRendererBackend() override;
ANativeWindow* getWindow() { return window; }
mbgl::gfx::RendererBackend& getImpl() override { return *this; }
std::vector<const char*> getInstanceExtensions() override;
void resizeFramebuffer(int width, int height) override;
// mbgl::gfx::RendererBackend implementation
public:
mbgl::gfx::Renderable& getDefaultRenderable() override { return *this; }
protected:
void activate() override {
// no-op
}
void deactivate() override {
// no-op
}
protected:
ANativeWindow* window;
};
} // namespace android
} // namespace mbgl

View File

@@ -7,6 +7,7 @@
#include <mbgl/util/run_loop.hpp>
#include <string>
#include <android/native_window_jni.h>
#include "attach_env.hpp"
#include "android_renderer_backend.hpp"
@@ -24,8 +25,7 @@ MapRenderer::MapRenderer(jni::JNIEnv& _env,
localIdeographFontFamily(localIdeographFontFamily_ ? jni::Make<std::string>(_env, localIdeographFontFamily_)
: std::optional<std::string>{}),
threadPool(Scheduler::GetBackground(), {}),
mailboxData(this),
backend(std::make_unique<AndroidRendererBackend>(threadPool)) {}
mailboxData(this) {}
MapRenderer::MailboxData::MailboxData(Scheduler* scheduler_)
: scheduler(scheduler_) {
@@ -163,7 +163,7 @@ void MapRenderer::requestSnapshot(SnapshotCallback callback) {
void MapRenderer::resetRenderer() {
renderer.reset();
backend = std::make_unique<AndroidRendererBackend>(threadPool);
swapBehaviorFlush = false;
}
void MapRenderer::scheduleSnapshot(std::unique_ptr<SnapshotCallback> callback) {
@@ -186,7 +186,7 @@ void MapRenderer::render(JNIEnv&) {
// Activate the backend
assert(backend);
gfx::BackendScope backendGuard{*backend};
gfx::BackendScope backendGuard{backend->getImpl()};
// Ensure that the "current" scheduler on the render thread is
// this scheduler.
@@ -206,14 +206,10 @@ void MapRenderer::render(JNIEnv&) {
}
}
void MapRenderer::onSurfaceCreated(JNIEnv&) {
void MapRenderer::onSurfaceCreated(JNIEnv& env, const jni::Object<AndroidSurface>& surface) {
// Lock as the initialization can come from the main thread or the GL thread first
std::lock_guard<std::mutex> lock(initialisationMutex);
// The GL context is already active if get a new surface.
assert(backend);
gfx::BackendScope backendGuard{*backend, gfx::BackendScope::ScopeType::Implicit};
// The android system will have already destroyed the underlying
// GL resources if this is not the first initialization and an
// attempt to clean them up will fail
@@ -223,12 +219,22 @@ void MapRenderer::onSurfaceCreated(JNIEnv&) {
// Reset in opposite order
renderer.reset();
backend.reset();
window.reset();
if (surface) {
window = std::unique_ptr<ANativeWindow, std::function<void(ANativeWindow*)>>(
ANativeWindow_fromSurface(&env, reinterpret_cast<jobject>(surface.get())),
[](ANativeWindow* window_) { ANativeWindow_release(window_); });
}
// Create the new backend and renderer
backend = std::make_unique<AndroidRendererBackend>(threadPool);
renderer = std::make_unique<Renderer>(*backend, pixelRatio, localIdeographFontFamily);
backend = AndroidRendererBackend::Create(window.get());
renderer = std::make_unique<Renderer>(backend->getImpl(), pixelRatio, localIdeographFontFamily);
rendererRef = std::make_unique<ActorRef<Renderer>>(*renderer, mailboxData.getMailbox());
backend->setSwapBehavior(swapBehaviorFlush ? gfx::Renderable::SwapBehaviour::Flush
: gfx::Renderable::SwapBehaviour::NoFlush);
// Set the observer on the new Renderer implementation
if (rendererObserver) {
renderer->setObserver(rendererObserver.get());
@@ -238,7 +244,8 @@ void MapRenderer::onSurfaceCreated(JNIEnv&) {
void MapRenderer::onSurfaceChanged(JNIEnv& env, jint width, jint height) {
if (!renderer) {
// In case the surface has been destroyed (due to app back-grounding)
onSurfaceCreated(env);
jni::jobject* nullObj = nullptr;
onSurfaceCreated(env, jni::Object<AndroidSurface>(nullObj));
}
backend->resizeFramebuffer(width, height);
@@ -255,10 +262,16 @@ void MapRenderer::onRendererReset(JNIEnv&) {
// needs to be called on GL thread
void MapRenderer::onSurfaceDestroyed(JNIEnv&) {
resetRenderer();
window.reset();
}
void MapRenderer::setSwapBehaviorFlush(JNIEnv&, jboolean flush) {
backend->setSwapBehavior(flush ? gfx::Renderable::SwapBehaviour::Flush : gfx::Renderable::SwapBehaviour::NoFlush);
swapBehaviorFlush = flush;
if (backend) {
backend->setSwapBehavior(flush ? gfx::Renderable::SwapBehaviour::Flush
: gfx::Renderable::SwapBehaviour::NoFlush);
}
}
// Static methods //

View File

@@ -10,6 +10,7 @@
#include <optional>
#include <jni/jni.hpp>
#include <android/native_window.h>
namespace mbgl {
@@ -26,6 +27,11 @@ namespace android {
class AndroidRendererBackend;
class AndroidSurface {
public:
static constexpr auto Name() { return "android/view/Surface"; };
};
/**
* The MapRenderer is a peer class that encapsulates the actions
* performed on the GL Thread.
@@ -105,7 +111,7 @@ private:
// Renders a frame.
void render(JNIEnv&);
void onSurfaceCreated(JNIEnv&);
void onSurfaceCreated(JNIEnv&, const jni::Object<AndroidSurface>& surface);
void onSurfaceChanged(JNIEnv&, jint width, jint height);
@@ -132,12 +138,14 @@ private:
std::unique_ptr<AndroidRendererBackend> backend;
std::unique_ptr<Renderer> renderer;
std::unique_ptr<ActorRef<Renderer>> rendererRef;
std::unique_ptr<ANativeWindow, std::function<void(ANativeWindow*)>> window;
std::shared_ptr<UpdateParameters> updateParameters;
std::mutex updateMutex;
bool framebufferSizeChanged = false;
std::atomic<bool> destroyed{false};
bool swapBehaviorFlush{true};
std::unique_ptr<SnapshotCallback> snapshotCallback;
mapbox::base::WeakPtrFactory<Scheduler> weakFactory{this};

View File

@@ -8,7 +8,6 @@ import android.os.Bundle;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.TextureView;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -30,9 +29,6 @@ import org.maplibre.android.constants.MapLibreConstants;
import org.maplibre.android.exceptions.MapLibreConfigurationException;
import org.maplibre.android.location.LocationComponent;
import org.maplibre.android.maps.renderer.MapRenderer;
import org.maplibre.android.maps.renderer.glsurfaceview.GLSurfaceViewMapRenderer;
import org.maplibre.android.maps.renderer.glsurfaceview.MapLibreGLSurfaceView;
import org.maplibre.android.maps.renderer.textureview.TextureViewMapRenderer;
import org.maplibre.android.maps.widgets.CompassView;
import org.maplibre.android.net.ConnectivityReceiver;
import org.maplibre.android.storage.FileSource;
@@ -43,9 +39,6 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import static org.maplibre.android.maps.widgets.CompassView.TIME_MAP_NORTH_ANIMATION;
import static org.maplibre.android.maps.widgets.CompassView.TIME_WAIT_IDLE;
@@ -313,37 +306,12 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback {
}
private void initialiseDrawingSurface(MapLibreMapOptions options) {
String localFontFamily = options.getLocalIdeographFontFamily();
if (options.getTextureMode()) {
TextureView textureView = new TextureView(getContext());
boolean translucentSurface = options.getTranslucentTextureSurface();
mapRenderer = new TextureViewMapRenderer(getContext(),
textureView, localFontFamily, translucentSurface) {
@Override
protected void onSurfaceCreated(GL10 gl, EGLConfig config) {
MapView.this.onSurfaceCreated();
super.onSurfaceCreated(gl, config);
}
};
mapRenderer = MapRenderer.create(options, getContext(), () -> MapView.this.onSurfaceCreated());
renderView = mapRenderer.getView();
addView(textureView, 0);
renderView = textureView;
} else {
MapLibreGLSurfaceView glSurfaceView = new MapLibreGLSurfaceView(getContext());
glSurfaceView.setZOrderMediaOverlay(maplibreMapOptions.getRenderSurfaceOnTop());
mapRenderer = new GLSurfaceViewMapRenderer(getContext(), glSurfaceView, localFontFamily) {
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
MapView.this.onSurfaceCreated();
super.onSurfaceCreated(gl, config);
}
};
addView(renderView, 0);
addView(glSurfaceView, 0);
renderView = glSurfaceView;
}
boolean crossSourceCollisions = maplibreMapOptions.getCrossSourceCollisions();
boolean crossSourceCollisions = options.getCrossSourceCollisions();
nativeMapView = new NativeMapView(
getContext(), getPixelRatio(), crossSourceCollisions, this, mapChangeReceiver, mapRenderer
);

View File

@@ -1,6 +1,9 @@
package org.maplibre.android.maps.renderer;
import android.content.Context;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import androidx.annotation.CallSuper;
import androidx.annotation.Keep;
@@ -9,16 +12,14 @@ import androidx.annotation.NonNull;
import org.maplibre.android.LibraryLoader;
import org.maplibre.android.log.Logger;
import org.maplibre.android.maps.MapLibreMap;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import org.maplibre.android.maps.MapLibreMapOptions;
/**
* The {@link MapRenderer} encapsulates the GL thread.
* The {@link MapRenderer} encapsulates the render thread.
* <p>
* Performs actions on the GL thread to manage the GL resources and
* Performs actions on the render thread to manage the resources and
* render on the one end and acts as a scheduler to request work to
* be performed on the GL thread on the other.
* be performed on the render thread on the other.
*/
@Keep
public abstract class MapRenderer implements MapRendererScheduler {
@@ -34,6 +35,25 @@ public abstract class MapRenderer implements MapRendererScheduler {
private double expectedRenderTime = 0;
private MapLibreMap.OnFpsChangedListener onFpsChangedListener;
public static MapRenderer create(MapLibreMapOptions options, @NonNull Context context, Runnable initCallback) {
MapRenderer renderer = null;
String localFontFamily = options.getLocalIdeographFontFamily();
if (options.getTextureMode()) {
TextureView textureView = new TextureView(context);
boolean translucentSurface = options.getTranslucentTextureSurface();
renderer = MapRendererFactory.newTextureViewMapRenderer(context, textureView, localFontFamily,
translucentSurface, initCallback);
} else {
boolean renderSurfaceOnTop = options.getRenderSurfaceOnTop();
renderer = MapRendererFactory.newSurfaceViewMapRenderer(context, localFontFamily,
renderSurfaceOnTop, initCallback);
}
return renderer;
}
public MapRenderer(@NonNull Context context, String localIdeographFontFamily) {
float pixelRatio = context.getResources().getDisplayMetrics().density;
@@ -41,6 +61,8 @@ public abstract class MapRenderer implements MapRendererScheduler {
nativeInitialize(this, pixelRatio, localIdeographFontFamily);
}
public abstract View getView();
public void onStart() {
// Implement if needed
}
@@ -66,13 +88,12 @@ public abstract class MapRenderer implements MapRendererScheduler {
}
@CallSuper
protected void onSurfaceCreated(GL10 gl, EGLConfig config) {
nativeOnSurfaceCreated();
protected void onSurfaceCreated(Surface surface) {
nativeOnSurfaceCreated(surface);
}
@CallSuper
protected void onSurfaceChanged(@NonNull GL10 gl, int width, int height) {
gl.glViewport(0, 0, width, height);
protected void onSurfaceChanged(int width, int height) {
nativeOnSurfaceChanged(width, height);
}
@@ -82,7 +103,7 @@ public abstract class MapRenderer implements MapRendererScheduler {
}
@CallSuper
protected void onDrawFrame(GL10 gl) {
protected void onDrawFrame() {
long startTime = System.nanoTime();
try {
nativeRender();
@@ -128,7 +149,7 @@ public abstract class MapRenderer implements MapRendererScheduler {
@Override
protected native void finalize() throws Throwable;
private native void nativeOnSurfaceCreated();
private native void nativeOnSurfaceCreated(Surface surface);
private native void nativeOnSurfaceChanged(int width, int height);

View File

@@ -1,126 +0,0 @@
package org.maplibre.android.maps.renderer.glsurfaceview;
import android.content.Context;
import android.opengl.GLSurfaceView;
import androidx.annotation.NonNull;
import org.maplibre.android.maps.renderer.MapRenderer;
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 javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import static android.opengl.GLSurfaceView.RENDERMODE_WHEN_DIRTY;
/**
* The {@link GLSurfaceViewMapRenderer} encapsulates the GL thread and
* {@link GLSurfaceView} specifics to render the map.
*
* @see MapRenderer
*/
public class GLSurfaceViewMapRenderer extends MapRenderer implements GLSurfaceView.Renderer {
@NonNull
private final MapLibreGLSurfaceView glSurfaceView;
public GLSurfaceViewMapRenderer(Context context,
MapLibreGLSurfaceView glSurfaceView,
String localIdeographFontFamily) {
super(context, localIdeographFontFamily);
this.glSurfaceView = glSurfaceView;
glSurfaceView.setEGLContextFactory(new EGLContextFactory());
glSurfaceView.setEGLWindowSurfaceFactory(new EGLWindowSurfaceFactory());
glSurfaceView.setEGLConfigChooser(new EGLConfigChooser());
glSurfaceView.setRenderer(this);
glSurfaceView.setRenderMode(RENDERMODE_WHEN_DIRTY);
glSurfaceView.setPreserveEGLContextOnPause(true);
glSurfaceView.setDetachedListener(new MapLibreGLSurfaceView.OnGLSurfaceViewDetachedListener() {
@Override
public void onGLSurfaceViewDetached() {
// because the GL thread is destroyed when the view is detached from window,
// we need to ensure releasing the native renderer as well.
// This avoids releasing it only when the view is being recreated, which is already on a new GL thread,
// and leads to JNI crashes like https://github.com/mapbox/mapbox-gl-native/issues/14618
nativeReset();
}
});
}
@Override
public void onStop() {
glSurfaceView.onPause();
}
@Override
public void onPause() {
super.onPause();
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public void onStart() {
glSurfaceView.onResume();
}
@Override
public void onResume() {
super.onResume();
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
super.onSurfaceCreated(gl, config);
}
@Override
protected void onSurfaceDestroyed() {
super.onSurfaceDestroyed();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
super.onSurfaceChanged(gl, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
super.onDrawFrame(gl);
}
/**
* May be called from any thread.
* <p>
* Called from the renderer frontend to schedule a render.
*/
@Override
public void requestRender() {
glSurfaceView.requestRender();
}
/**
* May be called from any thread.
* <p>
* Schedules work to be performed on the MapRenderer thread.
*
* @param runnable the runnable to execute
*/
@Override
public void queueEvent(Runnable runnable) {
glSurfaceView.queueEvent(runnable);
}
/**
* {@inheritDoc}
*/
@Override
public void waitForEmpty() {
glSurfaceView.waitForEmpty();
}
}

View File

@@ -0,0 +1,543 @@
package org.maplibre.android.maps.renderer.surfaceview;
import android.content.Context;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import androidx.annotation.NonNull;
import java.util.ArrayList;
public abstract class MapLibreSurfaceView extends SurfaceView implements SurfaceHolder.Callback2 {
protected static final String TAG = "MapLibreSurfaceView";
protected static final RenderThreadManager renderThreadManager = new RenderThreadManager();
protected SurfaceViewMapRenderer renderer;
protected RenderThread renderThread;
protected OnSurfaceViewDetachedListener detachedListener;
protected boolean detached;
/**
* The renderer only renders
* when the surface is created, or when {@link #requestRender} is called.
*
* @see #getRenderMode()
* @see #setRenderMode(int)
* @see #requestRender()
*/
public static final int RENDERMODE_WHEN_DIRTY = 0;
/**
* The renderer is called
* continuously to re-render the scene.
*
* @see #getRenderMode()
* @see #setRenderMode(int)
*/
public static final int RENDERMODE_CONTINUOUSLY = 1;
/**
* Standard View constructor. In order to render something, you
* must call {@link #setRenderer} to register a renderer.
*/
public MapLibreSurfaceView(Context context) {
super(context);
init();
}
/**
* Standard View constructor. In order to render something, you
* must call {@link #setRenderer} to register a renderer.
*/
public MapLibreSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed
SurfaceHolder holder = getHolder();
holder.setFormat(PixelFormat.TRANSPARENT);
holder.addCallback(this);
}
@Override
protected void finalize() throws Throwable {
try {
if (renderThread != null) {
// thread may still be running if this view was never
// attached to a window.
renderThread.requestExitAndWait();
}
} finally {
super.finalize();
}
}
/**
* Set a listener that gets notified when the view is detached from window.
*
* @param detachedListener listener
*/
public void setDetachedListener(@NonNull OnSurfaceViewDetachedListener detachedListener) {
if (this.detachedListener != null) {
throw new IllegalArgumentException("Detached from window listener has been already set.");
}
this.detachedListener = detachedListener;
}
/**
/**
* Set the renderer associated with this view. Also starts the thread that
* will call the renderer, which in turn causes the rendering to start.
* <p>This method should be called once and only once in the life-cycle of
* a SurfaceView.
* The following SurfaceView methods can only be called <em>after</em>
* setRenderer is called:
* <ul>
* <li>{@link #getRenderMode()}
* <li>{@link #onPause()}
* <li>{@link #onResume()}
* <li>{@link #queueEvent(Runnable)}
* <li>{@link #requestRender()}
* <li>{@link #setRenderMode(int)}
* </ul>
*
* @param renderer the renderer to use to perform drawing.
*/
public void setRenderer(SurfaceViewMapRenderer renderer) {
checkRenderThreadState();
this.renderer = renderer;
createRenderThread();
renderThread.start();
}
/**
* Set the rendering mode. When renderMode is
* RENDERMODE_CONTINUOUSLY, the renderer is called
* repeatedly to re-render the scene. When renderMode
* is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface
* is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY.
* <p>
* Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance
* by allowing the GPU and CPU to idle when the view does not need to be updated.
* <p>
* This method can only be called after {@link #setRenderer(SurfaceViewMapRenderer)}
*
* @param renderMode one of the RENDERMODE_X constants
*/
public void setRenderMode(int renderMode) {
renderThread.setRenderMode(renderMode);
}
/**
* Get the current rendering mode. May be called
* from any thread. Must not be called before a renderer has been set.
*
* @return the current rendering mode.
*/
public int getRenderMode() {
return renderThread.getRenderMode();
}
/**
* Request that the renderer render a frame.
* This method is typically used when the render mode has been set to
* RENDERMODE_WHEN_DIRTY, so that frames are only rendered on demand.
* May be called
* from any thread. Must not be called before a renderer has been set.
*/
public void requestRender() {
renderThread.requestRender();
}
/**
* This method is part of the SurfaceHolder.Callback interface, and is
* not normally called or subclassed by clients of MapLibreSurfaceView.
*/
public void surfaceCreated(SurfaceHolder holder) {
renderThread.surfaceCreated();
}
/**
* This method is part of the SurfaceHolder.Callback interface, and is
* not normally called or subclassed by clients of MapLibreSurfaceView.
*/
public void surfaceDestroyed(SurfaceHolder holder) {
// Surface will be destroyed when we return
renderThread.surfaceDestroyed();
}
/**
* This method is part of the SurfaceHolder.Callback interface, and is
* not normally called or subclassed by clients of MapLibreSurfaceView.
*/
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
renderThread.onWindowResize(w, h);
}
/**
* This method is part of the SurfaceHolder.Callback2 interface, and is
* not normally called or subclassed by clients of MapLibreSurfaceView.
*/
@Override
public void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable finishDrawing) {
if (renderThread != null) {
renderThread.requestRenderAndNotify(finishDrawing);
}
}
/**
* This method is part of the SurfaceHolder.Callback2 interface, and is
* not normally called or subclassed by clients of MapLibreSurfaceView.
*/
@Deprecated
@Override
public void surfaceRedrawNeeded(SurfaceHolder holder) {
// Since we are part of the framework we know only surfaceRedrawNeededAsync
// will be called.
}
/**
* Pause the rendering thread.
* <p>
* Must not be called before a renderer has been set.
*/
public void onPause() {
renderThread.onPause();
}
/**
* Resumes the rendering thread. It
* is the counterpart to {@link #onPause()}.
* <p>
* Must not be called before a renderer has been set.
*/
public void onResume() {
renderThread.onResume();
}
/**
* Queue a runnable to be run on the rendering thread. This can be used
* to communicate with the Renderer on the rendering thread.
* Must not be called before a renderer has been set.
*
* @param r the runnable to be run on the rendering thread.
*/
public void queueEvent(Runnable r) {
renderThread.queueEvent(r);
}
/**
* Wait for the queue to become empty
*/
public void waitForEmpty() {
renderThread.waitForEmpty();
}
/**
* This method is used as part of the View class and is not normally
* called or subclassed by clients of MapLibreSurfaceView.
*/
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (detached && (renderer != null)) {
int renderMode = RENDERMODE_CONTINUOUSLY;
if (renderThread != null) {
renderMode = renderThread.getRenderMode();
}
createRenderThread();
if (renderMode != RENDERMODE_CONTINUOUSLY) {
renderThread.setRenderMode(renderMode);
}
renderThread.start();
}
detached = false;
}
@Override
protected void onDetachedFromWindow() {
if (detachedListener != null) {
detachedListener.onSurfaceViewDetached();
}
if (renderThread != null) {
renderThread.requestExitAndWait();
}
detached = true;
super.onDetachedFromWindow();
}
/**
/**
* A generic render Thread. 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
* sRenderThreadManager object. This avoids multiple-lock ordering issues.
*/
abstract static class RenderThread extends Thread {
RenderThread() {
super();
width = 0;
height = 0;
requestRender = true;
renderMode = RENDERMODE_CONTINUOUSLY;
wantRenderNotification = false;
}
@Override
public void run() {
setName("RenderThread " + getId());
try {
guardedRun();
} catch (InterruptedException exception) {
// fall thru and exit normally
} finally {
renderThreadManager.threadExiting(this);
}
}
protected abstract void guardedRun() throws InterruptedException;
protected boolean readyToDraw() {
return (!paused) && hasSurface
&& (width > 0) && (height > 0)
&& (requestRender || (renderMode == RENDERMODE_CONTINUOUSLY));
}
public boolean ableToDraw() {
return readyToDraw();
}
public void setRenderMode(int renderMode) {
if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY)) ) {
throw new IllegalArgumentException("renderMode");
}
synchronized (renderThreadManager) {
this.renderMode = renderMode;
renderThreadManager.notifyAll();
}
}
public int getRenderMode() {
synchronized (renderThreadManager) {
return renderMode;
}
}
public void requestRender() {
synchronized (renderThreadManager) {
requestRender = true;
renderThreadManager.notifyAll();
}
}
public void requestRenderAndNotify(Runnable finishDrawing) {
synchronized (renderThreadManager) {
// If we are already on the render thread, this means a client callback
// has caused reentrancy, for example via updating the SurfaceView parameters.
// We will return to the client rendering code, so here we don't need to
// do anything.
if (Thread.currentThread() == this) {
return;
}
wantRenderNotification = true;
requestRender = true;
renderComplete = false;
finishDrawingRunnable = finishDrawing;
renderThreadManager.notifyAll();
}
}
public void surfaceCreated() {
synchronized (renderThreadManager) {
hasSurface = true;
renderThreadManager.notifyAll();
while (!exited && waitingForSurface) {
try {
renderThreadManager.wait();
} catch (InterruptedException exception) {
Thread.currentThread().interrupt();
}
}
}
}
public void surfaceDestroyed() {
synchronized (renderThreadManager) {
hasSurface = false;
renderThreadManager.notifyAll();
while (!exited && !waitingForSurface) {
try {
renderThreadManager.wait();
} catch (InterruptedException exception) {
Thread.currentThread().interrupt();
}
}
}
}
public void onPause() {
synchronized (renderThreadManager) {
requestPaused = true;
renderThreadManager.notifyAll();
while ((!exited) && (!paused)) {
try {
renderThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
public void onResume() {
synchronized (renderThreadManager) {
requestPaused = false;
requestRender = true;
renderComplete = false;
renderThreadManager.notifyAll();
while ((!exited) && paused && (!renderComplete)) {
try {
renderThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
public void onWindowResize(int w, int h) {
synchronized (renderThreadManager) {
width = w;
height = h;
sizeChanged = true;
requestRender = true;
renderComplete = false;
// If we are already on the render thread, this means a client callback
// has caused reentrancy, for example via updating the SurfaceView parameters.
// We need to process the size change eventually though and update our surface.
// So we set the parameters and return so they can be processed on our
// next iteration.
if (Thread.currentThread() == this) {
return;
}
renderThreadManager.notifyAll();
// Wait for thread to react to resize and render a frame
while (!exited && !paused && !renderComplete
&& ableToDraw()) {
try {
renderThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
public void requestExitAndWait() {
// don't call this from renderThread thread or it is a guaranteed
// deadlock!
synchronized (renderThreadManager) {
shouldExit = true;
renderThreadManager.notifyAll();
while (!exited) {
try {
renderThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
/**
* Queue an "event" to be run on the rendering thread.
*
* @param r the runnable to be run on the rendering thread.
*/
public void queueEvent(@NonNull Runnable r) {
synchronized (renderThreadManager) {
eventQueue.add(r);
renderThreadManager.notifyAll();
}
}
/**
* Wait for the queue to become empty
*/
public void waitForEmpty() {
synchronized (renderThreadManager) {
// Wait for the queue to be empty
while (!this.eventQueue.isEmpty()) {
try {
renderThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
// Once the thread is started, all accesses to the following member
// variables are protected by the sRenderThreadManager monitor
protected boolean shouldExit;
protected boolean exited;
protected boolean requestPaused;
protected boolean paused;
protected boolean hasSurface;
protected boolean waitingForSurface;
protected int width;
protected int height;
protected int renderMode;
protected boolean requestRender;
protected boolean wantRenderNotification;
protected boolean renderComplete;
protected ArrayList<Runnable> eventQueue = new ArrayList<>();
protected boolean sizeChanged = true;
protected Runnable finishDrawingRunnable = null;
// End of member variables protected by the sRenderThreadManager monitor.
}
protected abstract void createRenderThread();
protected void checkRenderThreadState() {
if (renderThread != null) {
throw new IllegalStateException("setRenderer has already been called for this instance.");
}
}
protected static class RenderThreadManager {
synchronized void threadExiting(RenderThread thread) {
thread.exited = true;
notifyAll();
}
}
/**
* Listener interface that notifies when a {@link MapLibreSurfaceView} is detached from window.
*/
public interface OnSurfaceViewDetachedListener {
/**
* Called when a {@link MapLibreSurfaceView} is detached from window.
*/
void onSurfaceViewDetached();
}
}

View File

@@ -0,0 +1,115 @@
package org.maplibre.android.maps.renderer.surfaceview;
import android.content.Context;
import android.view.Surface;
import android.view.View;
import androidx.annotation.NonNull;
import org.maplibre.android.maps.renderer.MapRenderer;
/**
* The {@link SurfaceViewMapRenderer} encapsulates the render thread and
* {@link MapLibreSurfaceView} specifics to render the map.
*
* @see MapRenderer
*/
public class SurfaceViewMapRenderer extends MapRenderer {
@NonNull
protected final MapLibreSurfaceView surfaceView;
public SurfaceViewMapRenderer(Context context,
MapLibreSurfaceView surfaceView,
String localIdeographFontFamily) {
super(context, localIdeographFontFamily);
this.surfaceView = surfaceView;
surfaceView.setDetachedListener(new MapLibreSurfaceView.OnSurfaceViewDetachedListener() {
@Override
public void onSurfaceViewDetached() {
// because the GL thread is destroyed when the view is detached from window,
// we need to ensure releasing the native renderer as well.
// This avoids releasing it only when the view is being recreated, which is already on a new GL thread,
// and leads to JNI crashes like https://github.com/mapbox/mapbox-gl-native/issues/14618
nativeReset();
}
});
}
@Override
public View getView() {
return this.surfaceView;
}
@Override
public void onStop() {
surfaceView.onPause();
}
@Override
public void onPause() {
super.onPause();
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public void onStart() {
surfaceView.onResume();
}
@Override
public void onResume() {
super.onResume();
}
public void onSurfaceCreated(Surface surface) {
super.onSurfaceCreated(surface);
}
public void onSurfaceDestroyed() {
super.onSurfaceDestroyed();
}
public void onSurfaceChanged(int width, int height) {
super.onSurfaceChanged(width, height);
}
public void onDrawFrame() {
super.onDrawFrame();
}
/**
* May be called from any thread.
* <p>
* Called from the renderer frontend to schedule a render.
*/
@Override
public void requestRender() {
surfaceView.requestRender();
}
/**
* May be called from any thread.
* <p>
* Schedules work to be performed on the MapRenderer thread.
*
* @param runnable the runnable to execute
*/
@Override
public void queueEvent(Runnable runnable) {
surfaceView.queueEvent(runnable);
}
/**
* {@inheritDoc}
*/
@Override
public void waitForEmpty() {
surfaceView.waitForEmpty();
}
}

View File

@@ -1,15 +1,14 @@
package org.maplibre.android.maps.renderer.textureview;
import android.content.Context;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import androidx.annotation.NonNull;
import org.maplibre.android.maps.renderer.MapRenderer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
/**
* The {@link TextureViewMapRenderer} encapsulates the GL thread and
* {@link TextureView} specifics to render the map.
@@ -19,6 +18,7 @@ import javax.microedition.khronos.opengles.GL10;
public class TextureViewMapRenderer extends MapRenderer {
private TextureViewRenderThread renderThread;
private boolean translucentSurface;
private TextureView textureView;
/**
* Create a {@link MapRenderer} for the given {@link TextureView}
@@ -33,26 +33,36 @@ public class TextureViewMapRenderer extends MapRenderer {
String localIdeographFontFamily,
boolean translucentSurface) {
super(context, localIdeographFontFamily);
this.textureView = textureView;
this.translucentSurface = translucentSurface;
renderThread = new TextureViewRenderThread(textureView, this);
}
public void setRenderThread(TextureViewRenderThread thread) {
renderThread = thread;
renderThread.setName("TextureViewRenderer");
renderThread.start();
}
/**
* Overridden to provide package access
*/
@Override
protected void onSurfaceCreated(GL10 gl, EGLConfig config) {
super.onSurfaceCreated(gl, config);
public View getView() {
return this.textureView;
}
/**
* Overridden to provide package access
*/
@Override
protected void onSurfaceChanged(GL10 gl, int width, int height) {
super.onSurfaceChanged(gl, width, height);
protected void onSurfaceCreated(Surface surface) {
super.onSurfaceCreated(surface);
}
/**
* Overridden to provide package access
*/
@Override
protected void onSurfaceChanged(int width, int height) {
super.onSurfaceChanged(width, height);
}
/**
@@ -67,8 +77,8 @@ public class TextureViewMapRenderer extends MapRenderer {
* Overridden to provide package access
*/
@Override
protected void onDrawFrame(GL10 gl) {
super.onDrawFrame(gl);
protected void onDrawFrame() {
super.onDrawFrame();
}
/**

View File

@@ -7,50 +7,35 @@ 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 java.util.ArrayList;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGL11;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
import javax.microedition.khronos.opengles.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.
* ui thread and the render thread it creates
*/
class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextureListener {
abstract class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextureListener {
private static final String TAG = "Mbgl-TextureViewRenderThread";
protected static final String TAG = "Mbgl-TextureViewRenderThread";
@NonNull
private final TextureViewMapRenderer mapRenderer;
@NonNull
private final EGLHolder eglHolder;
protected final TextureViewMapRenderer mapRenderer;
// Lock used for synchronization
private final Object lock = new Object();
protected final Object lock = new Object();
// Guarded by lock
private final ArrayList<Runnable> eventQueue = new ArrayList<>();
protected final ArrayList<Runnable> eventQueue = new ArrayList<>();
@Nullable
private SurfaceTexture surface;
private int width;
private int height;
private boolean requestRender;
private boolean sizeChanged;
private boolean paused;
private boolean destroyContext;
private boolean destroySurface;
private boolean shouldExit;
private boolean exited;
protected SurfaceTexture surfaceTexture;
protected boolean hasNativeSurface;
protected int width;
protected int height;
protected boolean requestRender;
protected boolean sizeChanged;
protected boolean paused;
protected boolean destroySurface;
protected boolean shouldExit;
protected boolean exited;
/**
* Create a render thread for the given TextureView / Maprenderer combination.
@@ -63,16 +48,15 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu
textureView.setOpaque(!mapRenderer.isTranslucentSurface());
textureView.setSurfaceTextureListener(this);
this.mapRenderer = mapRenderer;
this.eglHolder = new EGLHolder(new WeakReference<>(textureView), mapRenderer.isTranslucentSurface());
}
// SurfaceTextureListener methods
@UiThread
@Override
public void onSurfaceTextureAvailable(final SurfaceTexture surface, final int width, final int height) {
public void onSurfaceTextureAvailable(final SurfaceTexture surfaceTexture, final int width, final int height) {
synchronized (lock) {
this.surface = surface;
this.surfaceTexture = surfaceTexture;
this.width = width;
this.height = height;
this.requestRender = true;
@@ -82,7 +66,7 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu
@Override
@UiThread
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, final int width, final int height) {
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, final int width, final int height) {
synchronized (lock) {
this.width = width;
this.height = height;
@@ -94,9 +78,9 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu
@Override
@UiThread
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
synchronized (lock) {
this.surface = null;
this.surfaceTexture = null;
this.destroySurface = true;
this.requestRender = false;
lock.notifyAll();
@@ -106,7 +90,7 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu
@Override
@UiThread
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
// Ignored
}
@@ -137,7 +121,6 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu
/**
* Wait for the queue to be empty.
* @param timeoutMillis Maximum time to wait, in milliseconds
* @return The number of items remaining in the queue
*/
@UiThread
@@ -170,7 +153,6 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu
}
}
@UiThread
void onDestroy() {
synchronized (lock) {
@@ -187,303 +169,4 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu
}
}
}
// 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;
break;
}
if (destroyContext) {
eglHolder.destroyContext();
destroyContext = false;
break;
}
if (surface != null && !paused && requestRender) {
w = width;
h = height;
// Initialize EGL if needed
if (eglHolder.eglContext == EGL10.EGL_NO_CONTEXT) {
initializeEGL = true;
break;
}
// (re-)Initialize EGL Surface if needed
if (eglHolder.eglSurface == EGL10.EGL_NO_SURFACE) {
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(gl, eglHolder.eglConfig);
mapRenderer.onSurfaceChanged(gl, w, h);
continue;
}
// If the surface size has changed inform the map renderer.
if (recreateSurface) {
synchronized (lock) {
eglHolder.createSurface();
}
mapRenderer.onSurfaceChanged(gl, w, h);
continue;
}
if (sizeChanged) {
mapRenderer.onSurfaceChanged(gl, 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(gl);
// 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) {
surface = 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) {
surface = null;
destroySurface = true;
}
}
}
} catch (InterruptedException err) {
// To be expected
} finally {
// Cleanup
eglHolder.cleanup();
// Signal we're done
synchronized (lock) {
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();
}
}
}

View File

@@ -0,0 +1,49 @@
package org.maplibre.android.maps.renderer;
import android.content.Context;
import android.view.Surface;
import android.view.TextureView;
import androidx.annotation.Keep;
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;
@Keep
public class MapRendererFactory {
public static TextureViewMapRenderer newTextureViewMapRenderer(@NonNull Context context, TextureView textureView,
String localFontFamily, boolean translucentSurface,
Runnable initCallback) {
TextureViewMapRenderer mapRenderer = new TextureViewMapRenderer(context, textureView,
localFontFamily, translucentSurface) {
@Override
protected void onSurfaceCreated(Surface surface) {
initCallback.run();
super.onSurfaceCreated(surface);
}
};
mapRenderer.setRenderThread(new GLTextureViewRenderThread(textureView, mapRenderer));
return mapRenderer;
}
public static SurfaceViewMapRenderer newSurfaceViewMapRenderer(@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);
}
};
}
}

View File

@@ -0,0 +1,26 @@
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 static android.opengl.GLSurfaceView.RENDERMODE_WHEN_DIRTY;
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.setRenderMode(RENDERMODE_WHEN_DIRTY);
surfaceView.setPreserveEGLContextOnPause(true);
}
}

View File

@@ -1,19 +1,13 @@
package org.maplibre.android.maps.renderer.glsurfaceview;
package org.maplibre.android.maps.renderer.surfaceview;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import androidx.annotation.NonNull;
import org.maplibre.android.maps.renderer.egl.EGLLogWrapper;
import java.io.Writer;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGL11;
@@ -24,76 +18,22 @@ import javax.microedition.khronos.egl.EGLSurface;
import javax.microedition.khronos.opengles.GL;
import javax.microedition.khronos.opengles.GL10;
import static android.opengl.GLSurfaceView.RENDERMODE_CONTINUOUSLY;
public class MapLibreGLSurfaceView extends MapLibreSurfaceView {
/**
* {@link GLSurfaceView} extension that notifies a listener when the view is detached from window,
* which is the point of destruction of the GL thread.
*/
public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.Callback2 {
protected final WeakReference<MapLibreGLSurfaceView> viewWeakReference = new WeakReference<>(this);
private static final String TAG = "GLSurfaceView";
private static final GLThreadManager glThreadManager = new GLThreadManager();
private final WeakReference<MapLibreGLSurfaceView> viewWeakReference = new WeakReference<>(this);
private GLThread glThread;
private GLSurfaceView.Renderer renderer;
private GLSurfaceView.EGLConfigChooser eglConfigChooser;
private GLSurfaceView.EGLContextFactory eglContextFactory;
private GLSurfaceView.EGLWindowSurfaceFactory eglWindowSurfaceFactory;
private OnGLSurfaceViewDetachedListener detachedListener;
private boolean preserveEGLContextOnPause;
private boolean detached;
/**
* Standard View constructor. In order to render something, you
* must call {@link #setRenderer} to register a renderer.
*/
public MapLibreGLSurfaceView(Context context) {
super(context);
init();
}
/**
* Standard View constructor. In order to render something, you
* must call {@link #setRenderer} to register a renderer.
*/
public MapLibreGLSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed
SurfaceHolder holder = getHolder();
holder.addCallback(this);
}
@Override
protected void finalize() throws Throwable {
try {
if (glThread != null) {
// GLThread may still be running if this view was never
// attached to a window.
glThread.requestExitAndWait();
}
} finally {
super.finalize();
}
}
/**
* Set a listener that gets notified when the view is detached from window.
*
* @param detachedListener listener
*/
public void setDetachedListener(@NonNull OnGLSurfaceViewDetachedListener detachedListener) {
if (this.detachedListener != null) {
throw new IllegalArgumentException("Detached from window listener has been already set.");
}
this.detachedListener = detachedListener;
}
/**
@@ -127,32 +67,7 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
return preserveEGLContextOnPause;
}
/**
* Set the renderer associated with this view. Also starts the thread that
* will call the renderer, which in turn causes the rendering to start.
* <p>This method should be called once and only once in the life-cycle of
* a GLSurfaceView.
* <p>The following GLSurfaceView methods can only be called <em>before</em>
* setRenderer is called:
* <ul>
* <li>{@link #setEGLConfigChooser(GLSurfaceView.EGLConfigChooser)}
* </ul>
* <p>
* The following GLSurfaceView methods can only be called <em>after</em>
* setRenderer is called:
* <ul>
* <li>{@link #getRenderMode()}
* <li>{@link #onPause()}
* <li>{@link #onResume()}
* <li>{@link #queueEvent(Runnable)}
* <li>{@link #requestRender()}
* <li>{@link #setRenderMode(int)}
* </ul>
*
* @param renderer the renderer to use to perform OpenGL drawing.
*/
public void setRenderer(GLSurfaceView.Renderer renderer) {
checkRenderThreadState();
public void setRenderer(SurfaceViewMapRenderer renderer) {
if (eglConfigChooser == null) {
throw new IllegalStateException("No eglConfigChooser provided");
}
@@ -162,9 +77,8 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
if (eglWindowSurfaceFactory == null) {
throw new IllegalStateException("No eglWindowSurfaceFactory provided");
}
this.renderer = renderer;
glThread = new GLThread(viewWeakReference);
glThread.start();
super.setRenderer(renderer);
}
/**
@@ -200,164 +114,9 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
eglConfigChooser = configChooser;
}
/**
* Set the rendering mode. When renderMode is
* RENDERMODE_CONTINUOUSLY, the renderer is called
* repeatedly to re-render the scene. When renderMode
* is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface
* is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY.
* <p>
* Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance
* by allowing the GPU and CPU to idle when the view does not need to be updated.
* <p>
* This method can only be called after {@link #setRenderer(GLSurfaceView.Renderer)}
*
* @param renderMode one of the RENDERMODE_X constants
*/
public void setRenderMode(int renderMode) {
glThread.setRenderMode(renderMode);
}
/**
* Get the current rendering mode. May be called
* from any thread. Must not be called before a renderer has been set.
*
* @return the current rendering mode.
*/
public int getRenderMode() {
return glThread.getRenderMode();
}
/**
* Request that the renderer render a frame.
* This method is typically used when the render mode has been set to
* RENDERMODE_WHEN_DIRTY, so that frames are only rendered on demand.
* May be called
* from any thread. Must not be called before a renderer has been set.
*/
public void requestRender() {
glThread.requestRender();
}
/**
* This method is part of the SurfaceHolder.Callback interface, and is
* not normally called or subclassed by clients of GLSurfaceView.
*/
public void surfaceCreated(SurfaceHolder holder) {
glThread.surfaceCreated();
}
/**
* This method is part of the SurfaceHolder.Callback interface, and is
* not normally called or subclassed by clients of GLSurfaceView.
*/
public void surfaceDestroyed(SurfaceHolder holder) {
// Surface will be destroyed when we return
glThread.surfaceDestroyed();
}
/**
* This method is part of the SurfaceHolder.Callback interface, and is
* not normally called or subclassed by clients of GLSurfaceView.
*/
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
glThread.onWindowResize(w, h);
}
/**
* This method is part of the SurfaceHolder.Callback2 interface, and is
* not normally called or subclassed by clients of GLSurfaceView.
*/
@Override
public void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable finishDrawing) {
if (glThread != null) {
glThread.requestRenderAndNotify(finishDrawing);
}
}
/**
* This method is part of the SurfaceHolder.Callback2 interface, and is
* not normally called or subclassed by clients of GLSurfaceView.
*/
@Deprecated
@Override
public void surfaceRedrawNeeded(SurfaceHolder holder) {
// Since we are part of the framework we know only surfaceRedrawNeededAsync
// will be called.
}
/**
* Pause the rendering thread, optionally tearing down the EGL context
* depending upon the value of {@link #setPreserveEGLContextOnPause(boolean)}.
* <p>
* Must not be called before a renderer has been set.
*/
public void onPause() {
glThread.onPause();
}
/**
* Resumes the rendering thread, re-creating the OpenGL context if necessary. It
* is the counterpart to {@link #onPause()}.
* <p>
* Must not be called before a renderer has been set.
*/
public void onResume() {
glThread.onResume();
}
/**
* Queue a runnable to be run on the GL rendering thread. This can be used
* to communicate with the Renderer on the rendering thread.
* Must not be called before a renderer has been set.
*
* @param r the runnable to be run on the GL rendering thread.
*/
public void queueEvent(Runnable r) {
glThread.queueEvent(r);
}
/**
* Wait for the queue to become empty
* @param timeoutMillis Timeout in milliseconds
* @return Number of queue items remaining
*/
public void waitForEmpty() {
glThread.waitForEmpty();
}
/**
* This method is used as part of the View class and is not normally
* called or subclassed by clients of GLSurfaceView.
*/
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (detached && (renderer != null)) {
int renderMode = RENDERMODE_CONTINUOUSLY;
if (glThread != null) {
renderMode = glThread.getRenderMode();
}
glThread = new GLThread(viewWeakReference);
if (renderMode != RENDERMODE_CONTINUOUSLY) {
glThread.setRenderMode(renderMode);
}
glThread.start();
}
detached = false;
}
@Override
protected void onDetachedFromWindow() {
if (detachedListener != null) {
detachedListener.onGLSurfaceViewDetached();
}
if (glThread != null) {
glThread.requestExitAndWait();
}
detached = true;
super.onDetachedFromWindow();
protected void createRenderThread() {
renderThread = new GLThread(viewWeakReference);
}
/**
@@ -563,28 +322,11 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
* All potentially blocking synchronization is done through the
* sGLThreadManager object. This avoids multiple-lock ordering issues.
*/
static class GLThread extends Thread {
GLThread(WeakReference<MapLibreGLSurfaceView> glSurfaceViewWeakRef) {
static class GLThread extends MapLibreSurfaceView.RenderThread {
GLThread(WeakReference<MapLibreGLSurfaceView> surfaceViewWeakRef) {
super();
width = 0;
height = 0;
requestRender = true;
renderMode = RENDERMODE_CONTINUOUSLY;
wantRenderNotification = false;
mGLSurfaceViewWeakRef = glSurfaceViewWeakRef;
}
@Override
public void run() {
setName("GLThread " + getId());
try {
guardedRun();
} catch (InterruptedException exception) {
// fall thru and exit normally
} finally {
glThreadManager.threadExiting(this);
}
mSurfaceViewWeakRef = surfaceViewWeakRef;
}
/*
@@ -606,12 +348,13 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
if (haveEglContext) {
eglHelper.finish();
haveEglContext = false;
glThreadManager.releaseEglContextLocked(this);
renderThreadManager.notifyAll();
}
}
private void guardedRun() throws InterruptedException {
eglHelper = new EglHelper(mGLSurfaceViewWeakRef);
@Override
protected void guardedRun() throws InterruptedException {
eglHelper = new EglHelper(mSurfaceViewWeakRef);
haveEglContext = false;
haveEglSurface = false;
wantRenderNotification = false;
@@ -632,7 +375,7 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
Runnable finishDrawingRunnable = null;
while (true) {
synchronized (glThreadManager) {
synchronized (renderThreadManager) {
while (true) {
if (shouldExit) {
return;
@@ -648,7 +391,7 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
if (paused != requestPaused) {
pausing = requestPaused;
paused = requestPaused;
glThreadManager.notifyAll();
renderThreadManager.notifyAll();
}
// Do we need to give up the EGL context?
@@ -673,7 +416,7 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
// When pausing, optionally release the EGL Context:
if (pausing && haveEglContext) {
MapLibreGLSurfaceView view = mGLSurfaceViewWeakRef.get();
MapLibreGLSurfaceView view = mSurfaceViewWeakRef.get();
boolean preserveEglContextOnPause = view != null && view.preserveEGLContextOnPause;
if (!preserveEglContextOnPause) {
stopEglContextLocked();
@@ -687,20 +430,20 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
}
waitingForSurface = true;
surfaceIsBad = false;
glThreadManager.notifyAll();
renderThreadManager.notifyAll();
}
// Have we acquired the surface view surface?
if (hasSurface && waitingForSurface) {
waitingForSurface = false;
glThreadManager.notifyAll();
renderThreadManager.notifyAll();
}
if (doRenderNotification) {
this.wantRenderNotification = false;
doRenderNotification = false;
renderComplete = true;
glThreadManager.notifyAll();
renderThreadManager.notifyAll();
}
if (this.finishDrawingRunnable != null) {
@@ -719,13 +462,13 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
try {
eglHelper.start();
} catch (RuntimeException exception) {
glThreadManager.releaseEglContextLocked(this);
renderThreadManager.notifyAll();
return;
}
haveEglContext = true;
createEglContext = true;
glThreadManager.notifyAll();
renderThreadManager.notifyAll();
}
}
@@ -749,7 +492,7 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
this.sizeChanged = false;
}
requestRender = false;
glThreadManager.notifyAll();
renderThreadManager.notifyAll();
if (this.wantRenderNotification) {
wantRenderNotification = true;
}
@@ -764,7 +507,7 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
}
}
// By design, this is the only place in a GLThread thread where we wait().
glThreadManager.wait();
renderThreadManager.wait();
}
} // end of synchronized(sGLThreadManager)
@@ -776,15 +519,15 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
if (createEglSurface) {
if (eglHelper.createSurface()) {
synchronized (glThreadManager) {
synchronized (renderThreadManager) {
finishedCreatingEglSurface = true;
glThreadManager.notifyAll();
renderThreadManager.notifyAll();
}
} else {
synchronized (glThreadManager) {
synchronized (renderThreadManager) {
finishedCreatingEglSurface = true;
surfaceIsBad = true;
glThreadManager.notifyAll();
renderThreadManager.notifyAll();
}
continue;
}
@@ -798,24 +541,24 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
}
if (createEglContext) {
MapLibreGLSurfaceView view = mGLSurfaceViewWeakRef.get();
MapLibreSurfaceView view = mSurfaceViewWeakRef.get();
if (view != null) {
view.renderer.onSurfaceCreated(gl, eglHelper.mEglConfig);
view.renderer.onSurfaceCreated(null);
}
createEglContext = false;
}
if (sizeChanged) {
MapLibreGLSurfaceView view = mGLSurfaceViewWeakRef.get();
MapLibreSurfaceView view = mSurfaceViewWeakRef.get();
if (view != null) {
view.renderer.onSurfaceChanged(gl, w, h);
view.renderer.onSurfaceChanged(w, h);
}
sizeChanged = false;
}
MapLibreGLSurfaceView view = mGLSurfaceViewWeakRef.get();
MapLibreSurfaceView view = mSurfaceViewWeakRef.get();
if (view != null) {
view.renderer.onDrawFrame(gl);
view.renderer.onDrawFrame();
if (finishDrawingRunnable != null) {
finishDrawingRunnable.run();
finishDrawingRunnable = null;
@@ -835,9 +578,9 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
// Log the error to help developers understand why rendering stopped.
EglHelper.logEglErrorAsWarning(TAG, "eglSwapBuffers", swapError);
synchronized (glThreadManager) {
synchronized (renderThreadManager) {
surfaceIsBad = true;
glThreadManager.notifyAll();
renderThreadManager.notifyAll();
}
break;
}
@@ -852,72 +595,33 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
/*
* clean-up everything...
*/
synchronized (glThreadManager) {
synchronized (renderThreadManager) {
stopEglSurfaceLocked();
stopEglContextLocked();
}
}
}
@Override
protected boolean readyToDraw() {
return super.readyToDraw() && !surfaceIsBad;
}
@Override
public boolean ableToDraw() {
return haveEglContext && haveEglSurface && readyToDraw();
}
private boolean readyToDraw() {
return (!paused) && hasSurface && (!surfaceIsBad)
&& (width > 0) && (height > 0)
&& (requestRender || (renderMode == RENDERMODE_CONTINUOUSLY));
}
public void setRenderMode(int renderMode) {
synchronized (glThreadManager) {
this.renderMode = renderMode;
glThreadManager.notifyAll();
}
}
public int getRenderMode() {
synchronized (glThreadManager) {
return renderMode;
}
}
public void requestRender() {
synchronized (glThreadManager) {
requestRender = true;
glThreadManager.notifyAll();
}
}
public void requestRenderAndNotify(Runnable finishDrawing) {
synchronized (glThreadManager) {
// If we are already on the GL thread, this means a client callback
// has caused reentrancy, for example via updating the SurfaceView parameters.
// We will return to the client rendering code, so here we don't need to
// do anything.
if (Thread.currentThread() == this) {
return;
}
wantRenderNotification = true;
requestRender = true;
renderComplete = false;
finishDrawingRunnable = finishDrawing;
glThreadManager.notifyAll();
}
}
public void surfaceCreated() {
synchronized (glThreadManager) {
synchronized (renderThreadManager) {
hasSurface = true;
finishedCreatingEglSurface = false;
glThreadManager.notifyAll();
renderThreadManager.notifyAll();
while (waitingForSurface
&& !finishedCreatingEglSurface
&& !exited) {
try {
glThreadManager.wait();
renderThreadManager.wait();
} catch (InterruptedException exception) {
Thread.currentThread().interrupt();
}
@@ -925,231 +629,24 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
}
}
public void surfaceDestroyed() {
synchronized (glThreadManager) {
hasSurface = false;
glThreadManager.notifyAll();
while ((!waitingForSurface) && (!exited)) {
try {
glThreadManager.wait();
} catch (InterruptedException exception) {
Thread.currentThread().interrupt();
}
}
}
}
public void onPause() {
synchronized (glThreadManager) {
requestPaused = true;
glThreadManager.notifyAll();
while ((!exited) && (!paused)) {
try {
glThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
public void onResume() {
synchronized (glThreadManager) {
requestPaused = false;
requestRender = true;
renderComplete = false;
glThreadManager.notifyAll();
while ((!exited) && paused && (!renderComplete)) {
try {
glThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
public void onWindowResize(int w, int h) {
synchronized (glThreadManager) {
width = w;
height = h;
sizeChanged = true;
requestRender = true;
renderComplete = false;
// If we are already on the GL thread, this means a client callback
// has caused reentrancy, for example via updating the SurfaceView parameters.
// We need to process the size change eventually though and update our EGLSurface.
// So we set the parameters and return so they can be processed on our
// next iteration.
if (Thread.currentThread() == this) {
return;
}
glThreadManager.notifyAll();
// Wait for thread to react to resize and render a frame
while (!exited && !paused && !renderComplete
&& ableToDraw()) {
try {
glThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
public void requestExitAndWait() {
// don't call this from GLThread thread or it is a guaranteed
// deadlock!
synchronized (glThreadManager) {
shouldExit = true;
glThreadManager.notifyAll();
while (!exited) {
try {
glThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
public void requestReleaseEglContextLocked() {
shouldReleaseEglContext = true;
glThreadManager.notifyAll();
renderThreadManager.notifyAll();
}
/**
* Queue an "event" to be run on the GL rendering thread.
*
* @param r the runnable to be run on the GL rendering thread.
*/
public void queueEvent(@NonNull Runnable r) {
synchronized (glThreadManager) {
eventQueue.add(r);
glThreadManager.notifyAll();
}
}
/**
* Wait for the queue to become empty
* @param timeoutMillis Timeout in milliseconds, zero for indefinite wait
* @return Number of queue items remaining
*/
public void waitForEmpty() {
synchronized (glThreadManager) {
// Wait for the queue to be empty
while (!this.eventQueue.isEmpty()) {
try {
glThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
// Once the thread is started, all accesses to the following member
// variables are protected by the sGLThreadManager monitor
private boolean shouldExit;
private boolean exited;
private boolean requestPaused;
private boolean paused;
private boolean hasSurface;
private boolean surfaceIsBad;
private boolean waitingForSurface;
private boolean haveEglContext;
private boolean haveEglSurface;
private boolean finishedCreatingEglSurface;
private boolean shouldReleaseEglContext;
private int width;
private int height;
private int renderMode;
private boolean requestRender;
private boolean wantRenderNotification;
private boolean renderComplete;
private ArrayList<Runnable> eventQueue = new ArrayList<>();
private boolean sizeChanged = true;
private Runnable finishDrawingRunnable = null;
// End of member variables protected by the sGLThreadManager monitor.
private EglHelper eglHelper;
/**
* Set once at thread construction time, nulled out when the parent view is garbage
* called. This weak reference allows the GLSurfaceView to be garbage collected while
* the GLThread is still alive.
* called. This weak reference allows the SurfaceView to be garbage collected while
* the RenderThread is still alive.
*/
private WeakReference<MapLibreGLSurfaceView> mGLSurfaceViewWeakRef;
}
static class LogWriter extends Writer {
@Override
public void close() {
flushBuilder();
}
@Override
public void flush() {
flushBuilder();
}
@Override
public void write(char[] buf, int offset, int count) {
for (int i = 0; i < count; i++) {
char c = buf[offset + i];
if (c == '\n') {
flushBuilder();
} else {
mBuilder.append(c);
}
}
}
private void flushBuilder() {
if (mBuilder.length() > 0) {
Log.v("GLSurfaceView", mBuilder.toString());
mBuilder.delete(0, mBuilder.length());
}
}
private StringBuilder mBuilder = new StringBuilder();
}
private void checkRenderThreadState() {
if (glThread != null) {
throw new IllegalStateException("setRenderer has already been called for this instance.");
}
}
private static class GLThreadManager {
synchronized void threadExiting(GLThread thread) {
thread.exited = true;
notifyAll();
}
/*
* Releases the EGL context. Requires that we are already in the
* sGLThreadManager monitor when this is called.
*/
void releaseEglContextLocked(GLThread thread) {
notifyAll();
}
}
/**
* Listener interface that notifies when a {@link MapLibreGLSurfaceView} is detached from window.
*/
public interface OnGLSurfaceViewDetachedListener {
/**
* Called when a {@link MapLibreGLSurfaceView} is detached from window.
*/
void onGLSurfaceViewDetached();
protected WeakReference<MapLibreGLSurfaceView> mSurfaceViewWeakRef;
}
}

View File

@@ -0,0 +1,344 @@
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;
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) {
initializeEGL = true;
break;
}
// (re-)Initialize EGL Surface if needed
if (eglHolder.eglSurface == EGL10.EGL_NO_SURFACE) {
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.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();
}
}
}

View File

@@ -0,0 +1,49 @@
package org.maplibre.android.maps.renderer;
import android.content.Context;
import android.view.Surface;
import android.view.TextureView;
import androidx.annotation.Keep;
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;
@Keep
public class MapRendererFactory {
public static TextureViewMapRenderer newTextureViewMapRenderer(@NonNull Context context, TextureView textureView,
String localFontFamily, boolean translucentSurface,
Runnable initCallback) {
TextureViewMapRenderer mapRenderer = new TextureViewMapRenderer(context, textureView,
localFontFamily, translucentSurface) {
@Override
protected void onSurfaceCreated(Surface surface) {
initCallback.run();
super.onSurfaceCreated(surface);
}
};
mapRenderer.setRenderThread(new VulkanTextureViewRenderThread(textureView, mapRenderer));
return mapRenderer;
}
public static SurfaceViewMapRenderer newSurfaceViewMapRenderer(@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);
}
};
}
}

View File

@@ -0,0 +1,183 @@
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();
mSurfaceViewWeakRef = surfaceViewWeakRef;
}
@Override
protected void guardedRun() throws InterruptedException {
wantRenderNotification = false;
boolean sizeChanged = false;
boolean initSurface = false;
boolean destroySurface = 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();
}
if (paused && graphicsSurfaceCreated) {
MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get();
if (view != null) {
destroySurface = true;
graphicsSurfaceCreated = false;
}
renderThreadManager.notifyAll();
}
// lost surface
if (!hasSurface && !waitingForSurface) {
MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get();
if (view != null) {
destroySurface = true;
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()) {
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;
}
MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get();
if (destroySurface) {
if (view != null) {
view.renderer.onSurfaceDestroyed();
destroySurface = false;
}
}
if (initSurface) {
if (view != null) {
view.renderer.onSurfaceCreated(view.getHolder().getSurface());
initSurface = false;
}
}
if (sizeChanged) {
if (view != null) {
view.renderer.onSurfaceChanged(w, h);
sizeChanged = false;
}
}
if (view != null) {
view.renderer.onDrawFrame();
if (finishDrawingRunnable != null) {
finishDrawingRunnable.run();
finishDrawingRunnable = null;
}
}
if (wantRenderNotification) {
doRenderNotification = true;
wantRenderNotification = false;
}
}
}
private boolean graphicsSurfaceCreated;
private WeakReference<MapLibreVulkanSurfaceView> mSurfaceViewWeakRef;
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,114 @@
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);
}
// Thread implementation
@Override
public void run() {
try {
while (true) {
Runnable event = null;
boolean initialize = 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) {
destroySurface = false;
mapRenderer.onSurfaceDestroyed();
surface = null;
break;
}
if (surfaceTexture != null && !paused && requestRender && surface == null) {
w = width;
h = height;
surface = new Surface(surfaceTexture);
initialize = true;
// 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;
}
if (requestRender && !paused) {
break;
}
// Wait until needed
lock.wait();
} // end guarded while loop
} // end guarded block
// Run event, if any
if (event != null) {
event.run();
continue;
}
if (initialize) {
mapRenderer.onSurfaceCreated(surface);
mapRenderer.onSurfaceChanged( w, h);
initialize = false;
continue;
}
// If the surface size has changed inform the map renderer.
if (sizeChanged) {
mapRenderer.onSurfaceChanged(w, h);
sizeChanged = false;
continue;
}
// Time to render a frame
mapRenderer.onDrawFrame();
}
} catch (InterruptedException err) {
// To be expected
} finally {
// Signal we're done
synchronized (lock) {
this.exited = true;
lock.notifyAll();
}
}
}
}