diff --git a/platform/android/SCsub b/platform/android/SCsub index f97ad7a8a701..b5fe4774b971 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -23,7 +23,6 @@ android_files = [ "java_godot_lib_jni.cpp", "java_class_wrapper.cpp", "java_godot_wrapper.cpp", - "java_godot_view_wrapper.cpp", "java_godot_io_wrapper.cpp", "jni_utils.cpp", "android_keys_utils.cpp", diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 7738b25c5e5d..2d45d56a5a09 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -36,6 +36,7 @@ #include "tts_android.h" #include "core/config/project_settings.h" +#include "main/main.h" #if defined(RD_ENABLED) #include "servers/rendering/renderer_rd/renderer_compositor_rd.h" @@ -484,24 +485,16 @@ int64_t DisplayServerAndroid::window_get_native_handle(HandleType p_handle_type, } #ifdef GLES3_ENABLED case DISPLAY_HANDLE: { - if (rendering_driver == "opengl3") { - return reinterpret_cast(eglGetCurrentDisplay()); - } - return 0; + return reinterpret_cast(egl_display); } case OPENGL_CONTEXT: { - if (rendering_driver == "opengl3") { - return reinterpret_cast(eglGetCurrentContext()); - } - return 0; + return reinterpret_cast(egl_context); } case EGL_DISPLAY: { - // @todo Find a way to get this from the Java side. - return 0; + return reinterpret_cast(egl_display); } case EGL_CONFIG: { - // @todo Find a way to get this from the Java side. - return 0; + return reinterpret_cast(egl_config); } #endif default: { @@ -703,6 +696,9 @@ void DisplayServerAndroid::reset_window() { } } #endif + + // We force redraw to ensure we render at least once when the window is reset. + Main::force_redraw(); } void DisplayServerAndroid::notify_surface_changed(int p_width, int p_height) { @@ -711,6 +707,15 @@ void DisplayServerAndroid::notify_surface_changed(int p_width, int p_height) { } } +void DisplayServerAndroid::update_egl_resources(void *p_display, void *p_surface, void *p_context, void *p_config) { +#ifdef GLES3_ENABLED + egl_display = p_display; + egl_surface = p_surface; + egl_context = p_context; + egl_config = p_config; +#endif +} + DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { rendering_driver = p_rendering_driver; @@ -792,6 +797,11 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis #if defined(GLES3_ENABLED) if (rendering_driver == "opengl3") { RasterizerGLES3::make_current(false); + + if (OS::get_singleton()->is_separate_thread_rendering_enabled()) { + GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java(); + godot_java->set_separate_render_thread_enabled(true); + } } #endif @@ -837,7 +847,7 @@ void DisplayServerAndroid::_mouse_update_mode() { ? mouse_mode_override : mouse_mode_base; - if (!OS_Android::get_singleton()->get_godot_java()->get_godot_view()->can_update_pointer_icon() || !OS_Android::get_singleton()->get_godot_java()->get_godot_view()->can_capture_pointer()) { + if (!OS_Android::get_singleton()->get_godot_java()->can_capture_pointer()) { return; } if (mouse_mode == wanted_mouse_mode) { @@ -845,15 +855,15 @@ void DisplayServerAndroid::_mouse_update_mode() { } if (wanted_mouse_mode == MouseMode::MOUSE_MODE_HIDDEN) { - OS_Android::get_singleton()->get_godot_java()->get_godot_view()->set_pointer_icon(CURSOR_TYPE_NULL); + OS_Android::get_singleton()->get_godot_java()->set_pointer_icon(CURSOR_TYPE_NULL); } else { cursor_set_shape(cursor_shape); } if (wanted_mouse_mode == MouseMode::MOUSE_MODE_CAPTURED) { - OS_Android::get_singleton()->get_godot_java()->get_godot_view()->request_pointer_capture(); + OS_Android::get_singleton()->get_godot_java()->request_pointer_capture(); } else { - OS_Android::get_singleton()->get_godot_java()->get_godot_view()->release_pointer_capture(); + OS_Android::get_singleton()->get_godot_java()->release_pointer_capture(); } mouse_mode = wanted_mouse_mode; @@ -903,9 +913,6 @@ BitField DisplayServerAndroid::mouse_get_button_state() const { } void DisplayServerAndroid::_cursor_set_shape_helper(CursorShape p_shape, bool force) { - if (!OS_Android::get_singleton()->get_godot_java()->get_godot_view()->can_update_pointer_icon()) { - return; - } if (cursor_shape == p_shape && !force) { return; } @@ -913,7 +920,7 @@ void DisplayServerAndroid::_cursor_set_shape_helper(CursorShape p_shape, bool fo cursor_shape = p_shape; if (mouse_mode == MouseMode::MOUSE_MODE_VISIBLE || mouse_mode == MouseMode::MOUSE_MODE_CONFINED) { - OS_Android::get_singleton()->get_godot_java()->get_godot_view()->set_pointer_icon(android_cursors[cursor_shape]); + OS_Android::get_singleton()->get_godot_java()->set_pointer_icon(android_cursors[cursor_shape]); } } @@ -932,7 +939,7 @@ void DisplayServerAndroid::cursor_set_custom_image(const Ref &p_cursor if (!cursor_path.is_empty()) { cursor_path = ProjectSettings::get_singleton()->globalize_path(cursor_path); } - OS_Android::get_singleton()->get_godot_java()->get_godot_view()->configure_pointer_icon(android_cursors[cursor_shape], cursor_path, p_hotspot); + OS_Android::get_singleton()->get_godot_java()->configure_pointer_icon(android_cursors[cursor_shape], cursor_path, p_hotspot); _cursor_set_shape_helper(p_shape, true); } @@ -953,16 +960,25 @@ DisplayServer::VSyncMode DisplayServerAndroid::window_get_vsync_mode(WindowID p_ return DisplayServer::VSYNC_ENABLED; } -void DisplayServerAndroid::reset_swap_buffers_flag() { - swap_buffers_flag = false; -} +void DisplayServerAndroid::release_rendering_thread() { + if (egl_current_window_id == INVALID_WINDOW_ID) { + return; + } -bool DisplayServerAndroid::should_swap_buffers() const { - return swap_buffers_flag; + GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java(); + ERR_FAIL_NULL(godot_java); + godot_java->release_current_gl_window(egl_current_window_id); + egl_current_window_id = INVALID_WINDOW_ID; } void DisplayServerAndroid::swap_buffers() { - swap_buffers_flag = true; + if (egl_current_window_id == INVALID_WINDOW_ID) { + return; + } + + GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java(); + ERR_FAIL_NULL(godot_java); + godot_java->egl_swap_buffers(egl_current_window_id); } void DisplayServerAndroid::set_native_icon(const String &p_filename) { @@ -976,3 +992,18 @@ void DisplayServerAndroid::set_icon(const Ref &p_icon) { bool DisplayServerAndroid::is_window_transparency_available() const { return GLOBAL_GET_CACHED(bool, "display/window/per_pixel_transparency/allowed"); } + +void DisplayServerAndroid::gl_window_make_current(DisplayServer::WindowID p_window_id) { + if (p_window_id == INVALID_WINDOW_ID) { + return; + } + + if (egl_current_window_id == p_window_id) { + return; + } + + GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java(); + ERR_FAIL_NULL(godot_java); + godot_java->make_gl_window_current(p_window_id); + egl_current_window_id = p_window_id; +} diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h index 5b09128ee3c4..abf202ae3c8d 100644 --- a/platform/android/display_server_android.h +++ b/platform/android/display_server_android.h @@ -71,7 +71,7 @@ class DisplayServerAndroid : public DisplayServer { void _mouse_update_mode(); bool keep_screen_on; - bool swap_buffers_flag; + WindowID egl_current_window_id = MAIN_WINDOW_ID; CursorShape cursor_shape = CursorShape::CURSOR_ARROW; @@ -79,6 +79,14 @@ class DisplayServerAndroid : public DisplayServer { RenderingContextDriver *rendering_context = nullptr; RenderingDevice *rendering_device = nullptr; #endif + +#ifdef GLES3_ENABLED + void *egl_display = nullptr; + void *egl_surface = nullptr; + void *egl_context = nullptr; + void *egl_config = nullptr; +#endif + NativeMenu *native_menu = nullptr; ObjectID window_attached_instance_id; @@ -245,12 +253,12 @@ class DisplayServerAndroid : public DisplayServer { void reset_window(); void notify_surface_changed(int p_width, int p_height); + void update_egl_resources(void *p_display, void *p_surface, void *p_context, void *p_config); virtual Point2i mouse_get_position() const override; virtual BitField mouse_get_button_state() const override; - void reset_swap_buffers_flag(); - bool should_swap_buffers() const; + virtual void release_rendering_thread() override; virtual void swap_buffers() override; virtual void set_native_icon(const String &p_filename) override; @@ -258,6 +266,8 @@ class DisplayServerAndroid : public DisplayServer { virtual bool is_window_transparency_available() const override; + virtual void gl_window_make_current(WindowID p_window_id) override; + DisplayServerAndroid(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); ~DisplayServerAndroid(); }; diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt index 934c4827ae66..be3beff05728 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt @@ -316,7 +316,7 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe runOnUiThread { // Enable long press, panning and scaling gestures - godotFragment?.godot?.renderView?.inputHandler?.apply { + godotFragment?.godot?.godotInputHandler?.apply { enableLongPress(longPressEnabled) enablePanningAndScalingGestures(panScaleEnabled) setOverrideVolumeButtons(overrideVolumeButtonsEnabled) diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index 3311e7733de3..377f0423c50c 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -37,12 +37,16 @@ import android.content.* import android.content.pm.PackageManager import android.content.res.Configuration import android.content.res.Resources +import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.hardware.Sensor import android.hardware.SensorManager import android.os.* +import android.text.TextUtils import android.util.Log +import android.util.SparseArray import android.util.TypedValue import android.view.* import android.widget.FrameLayout @@ -65,6 +69,10 @@ import org.godotengine.godot.io.file.FileAccessHandler import org.godotengine.godot.plugin.AndroidRuntimePlugin import org.godotengine.godot.plugin.GodotPlugin import org.godotengine.godot.plugin.GodotPluginRegistry +import org.godotengine.godot.render.GodotGLRenderView +import org.godotengine.godot.render.GodotRenderer +import org.godotengine.godot.render.GodotVulkanRenderView +import org.godotengine.godot.render.Renderer import org.godotengine.godot.tts.GodotTTS import org.godotengine.godot.utils.DialogUtils import org.godotengine.godot.utils.GodotNetUtils @@ -74,6 +82,7 @@ import org.godotengine.godot.utils.beginBenchmarkMeasure import org.godotengine.godot.utils.benchmarkFile import org.godotengine.godot.utils.dumpBenchmark import org.godotengine.godot.utils.endBenchmarkMeasure +import org.godotengine.godot.utils.isNativeXRDevice import org.godotengine.godot.utils.useBenchmark import org.godotengine.godot.xr.XRMode import java.io.File @@ -117,6 +126,8 @@ class Godot private constructor(val context: Context) { internal fun isEditorBuild() = BuildConfig.FLAVOR == EDITOR_FLAVOR } + private lateinit var renderer: GodotRenderer + private val mSensorManager: SensorManager? by lazy { context.getSystemService(Context.SENSOR_SERVICE) as? SensorManager } private val mClipboard: ClipboardManager? by lazy { context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager } private val vibratorService: Vibrator? by lazy { context.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator } @@ -138,7 +149,7 @@ class Godot private constructor(val context: Context) { val directoryAccessHandler = DirectoryAccessHandler(context) val fileAccessHandler = FileAccessHandler(context) val netUtils = GodotNetUtils(context) - private val godotInputHandler = GodotInputHandler(context, this) + val godotInputHandler = GodotInputHandler(context, this) private val hasClipboardCallable = Callable { mClipboard?.hasPrimaryClip() == true @@ -194,11 +205,12 @@ class Godot private constructor(val context: Context) { internal var containerLayout: FrameLayout? = null var renderView: GodotRenderView? = null + private val customPointerIcons = SparseArray() /** - * Returns true if the native engine has been initialized through [onInitNativeLayer], false otherwise. + * Returns true if the native engine has been initialized through [initEngine], false otherwise. */ - private fun isNativeInitialized() = nativeLayerInitializeCompleted && nativeLayerSetupCompleted + private fun isNativeInitialized() = nativeLayerInitializeCompleted && nativeLayerSetupCompleted && ::renderer.isInitialized /** * Returns true if the engine has been initialized, false otherwise. @@ -206,10 +218,15 @@ class Godot private constructor(val context: Context) { fun isInitialized() = primaryHost != null && isNativeInitialized() && renderViewInitialized /** - * Provides access to the primary host [Activity] + * Provides access to the primary host [Activity]. */ fun getActivity() = primaryHost?.activity + /** + * Provides access to the Godot [Renderer]. + */ + fun getRenderer(): Renderer = renderer + /** * Start initialization of the Godot engine. * @@ -358,6 +375,21 @@ class Godot private constructor(val context: Context) { Log.v(TAG, "Godot native layer setup completed") } } + + val useVulkan = if (usesVulkan()) { + if (meetsVulkanRequirements(context.packageManager)) { + true + } else if (canFallbackToOpenGL()) { + // Fallback to OpenGl. + false + } else { + throw IllegalStateException(context.getString(R.string.error_missing_vulkan_requirements_message)) + } + } else { + false + } + renderer = GodotRenderer(useVulkan) + renderer.startRenderer() } finally { endBenchmarkMeasure("Startup", "Godot::initEngine") } @@ -549,23 +581,13 @@ class Godot private constructor(val context: Context) { !isEditorHint() && java.lang.Boolean.parseBoolean(GodotLib.getGlobal("display/window/per_pixel_transparency/allowed")) Log.d(TAG, "Render view should be transparent: $shouldBeTransparent") - renderView = if (usesVulkan()) { - if (meetsVulkanRequirements(context.packageManager)) { - GodotVulkanRenderView(this, godotInputHandler, shouldBeTransparent) - } else if (canFallbackToOpenGL()) { - // Fallback to OpenGl. - GodotGLRenderView(this, godotInputHandler, xrMode, useDebugOpengl, shouldBeTransparent) - } else { - throw IllegalStateException(context.getString(R.string.error_missing_vulkan_requirements_message)) - } - + renderView = if (renderer.useVulkan) { + GodotVulkanRenderView(this, renderer, godotInputHandler, shouldBeTransparent) } else { - // Fallback to OpenGl. - GodotGLRenderView(this, godotInputHandler, xrMode, useDebugOpengl, shouldBeTransparent) + GodotGLRenderView(this, renderer, godotInputHandler, xrMode, useDebugOpengl, shouldBeTransparent) } renderView?.let { - it.startRenderer() containerLayout?.addView( it.view, ViewGroup.LayoutParams( @@ -631,7 +653,7 @@ class Godot private constructor(val context: Context) { } }) - renderView?.queueOnRenderThread { + runOnRenderThread { for (plugin in pluginRegistry.allPlugins) { plugin.onRegisterPluginWithGodotNative() } @@ -667,7 +689,10 @@ class Godot private constructor(val context: Context) { return } - renderView?.onActivityStarted() + renderer.onActivityStarted() + for (plugin in pluginRegistry.allPlugins) { + plugin.onMainStart() + } } fun onResume(host: GodotHost) { @@ -677,7 +702,7 @@ class Godot private constructor(val context: Context) { return } - renderView?.onActivityResumed() + renderer.onActivityResumed() registerSensorsIfNeeded() for (plugin in pluginRegistry.allPlugins) { plugin.onMainResume() @@ -710,11 +735,11 @@ class Godot private constructor(val context: Context) { return } - renderView?.onActivityPaused() - mSensorManager?.unregisterListener(godotInputHandler) for (plugin in pluginRegistry.allPlugins) { plugin.onMainPause() } + renderer.onActivityPaused() + mSensorManager?.unregisterListener(godotInputHandler) } fun onStop(host: GodotHost) { @@ -723,7 +748,10 @@ class Godot private constructor(val context: Context) { return } - renderView?.onActivityStopped() + for (plugin in pluginRegistry.allPlugins) { + plugin.onMainStop() + } + renderer.onActivityStopped() } fun onDestroy(primaryHost: GodotHost) { @@ -732,19 +760,29 @@ class Godot private constructor(val context: Context) { } Log.v(TAG, "OnDestroy: $primaryHost") + // If the host activity is being destroyed because it's changing configurations, it'll be recreated, so we keep + // the engine around to continue where we left off. + val isHostChangingConfigurations = primaryHost.activity?.isChangingConfigurations == true + if (!isHostChangingConfigurations) { + destroyEngine() + } + this.primaryHost = null + } + + private fun destroyEngine() { + Log.d(TAG, "Destroying Godot Engine") for (plugin in pluginRegistry.allPlugins) { plugin.onMainDestroy() } - renderView?.onActivityDestroyed() - this.primaryHost = null + renderer.onActivityDestroyed() } /** * Configuration change callback */ fun onConfigurationChanged(newConfig: Configuration) { - renderView?.inputHandler?.onConfigurationChanged(newConfig) + godotInputHandler.onConfigurationChanged(newConfig) val newDarkMode = newConfig.uiMode.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES if (darkMode != newDarkMode) { @@ -798,7 +836,7 @@ class Godot private constructor(val context: Context) { val scrollDeadzoneDisabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/disable_scroll_deadzone")) runOnHostThread { - renderView?.inputHandler?.apply { + godotInputHandler.apply { enableLongPress(longPressEnabled) enablePanningAndScalingGestures(panScaleEnabled) setOverrideVolumeButtons(overrideVolumeButtons) @@ -885,7 +923,7 @@ class Godot private constructor(val context: Context) { * This must be called after the render thread has started. */ fun runOnRenderThread(action: Runnable) { - renderView?.queueOnRenderThread(action) + renderer.queueOnRenderThread(action) } /** @@ -1069,7 +1107,7 @@ class Godot private constructor(val context: Context) { runOnTerminate.set(destroyRunnable) runOnHostThread { - onDestroy(host) + destroyEngine() } } @@ -1089,7 +1127,7 @@ class Godot private constructor(val context: Context) { for (plugin in pluginRegistry.allPlugins) { plugin.onMainBackPressed() } - renderView?.queueOnRenderThread { GodotLib.back() } + runOnRenderThread { GodotLib.back() } } /** @@ -1273,4 +1311,94 @@ class Godot private constructor(val context: Context) { private fun nativeOnEditorWorkspaceSelected(workspace: String) { primaryHost?.onEditorWorkspaceSelected(workspace) } + + /** + * @return true if pointer capture is supported. + */ + fun canCapturePointer(): Boolean { + // Pointer capture is not supported on native XR devices. + return !isNativeXRDevice(context) && godotInputHandler.canCapturePointer() + } + + @Keep + private fun requestPointerCapture() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + renderView?.view?.requestPointerCapture() + } + } + + @Keep + private fun releasePointerCapture() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + renderView?.view?.releasePointerCapture() + } + } + + /** + * Used to configure the PointerIcon for the given type. + * + * Called from JNI + */ + @Keep + private fun configurePointerIcon( + pointerType: Int, + imagePath: String, + hotSpotX: Float, + hotSpotY: Float + ) { + try { + var bitmap: Bitmap? = null + if (!TextUtils.isEmpty(imagePath)) { + if (directoryAccessHandler.filesystemFileExists(imagePath)) { + // Try to load the bitmap from the file system + bitmap = BitmapFactory.decodeFile(imagePath) + } else if (directoryAccessHandler.assetsFileExists(imagePath)) { + // Try to load the bitmap from the assets directory + val am = context.assets + val imageInputStream = am.open(imagePath) + bitmap = BitmapFactory.decodeStream(imageInputStream) + } + } + + if (bitmap != null) { + val customPointerIcon = PointerIcon.create(bitmap, hotSpotX, hotSpotY) + customPointerIcons.put(pointerType, customPointerIcon) + } + } catch (e: Exception) { + // Reset the custom pointer icon + customPointerIcons.delete(pointerType) + } + } + + /** + * Called from JNI to change pointer icon + */ + @Keep + private fun setPointerIcon(pointerType: Int) { + var pointerIcon = customPointerIcons[pointerType] + if (pointerIcon == null) { + pointerIcon = PointerIcon.getSystemIcon(context, pointerType) + } + renderView?.view?.pointerIcon = pointerIcon + } + + @Keep + private fun setSeparateRenderThreadEnabled(enabled: Boolean) { + renderer.renderThread.setSeparateRenderThreadEnabled(enabled) + } + + @Keep + private fun makeGLWindowCurrent(windowId: Int): Boolean { + return renderer.renderThread.makeEglCurrent(windowId) + } + + @Keep + private fun eglSwapBuffers(windowId: Int) { + renderer.renderThread.eglSwapBuffers(windowId) + } + + @Keep + private fun releaseCurrentGLWindow(windowId: Int) { + renderer.renderThread.releaseCurrentGLWindow(windowId) + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java deleted file mode 100644 index fdd0d544485f..000000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java +++ /dev/null @@ -1,286 +0,0 @@ -/**************************************************************************/ -/* GodotGLRenderView.java */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -package org.godotengine.godot; - -import org.godotengine.godot.gl.GLSurfaceView; -import org.godotengine.godot.gl.GodotRenderer; -import org.godotengine.godot.input.GodotInputHandler; -import org.godotengine.godot.xr.XRMode; -import org.godotengine.godot.xr.ovr.OvrConfigChooser; -import org.godotengine.godot.xr.ovr.OvrContextFactory; -import org.godotengine.godot.xr.ovr.OvrWindowSurfaceFactory; -import org.godotengine.godot.xr.regular.RegularConfigChooser; -import org.godotengine.godot.xr.regular.RegularContextFactory; -import org.godotengine.godot.xr.regular.RegularFallbackConfigChooser; - -import android.annotation.SuppressLint; -import android.content.res.AssetManager; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.PixelFormat; -import android.text.TextUtils; -import android.util.SparseArray; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.PointerIcon; -import android.view.SurfaceView; - -import androidx.annotation.Keep; - -import java.io.InputStream; - -/** - * A simple GLSurfaceView sub-class that demonstrate how to perform - * OpenGL ES 2.0 rendering into a GL Surface. Note the following important - * details: - * - * - The class must use a custom context factory to enable 2.0 rendering. - * See ContextFactory class definition below. - * - * - The class must use a custom EGLConfigChooser to be able to select - * an EGLConfig that supports 3.0. This is done by providing a config - * specification to eglChooseConfig() that has the attribute - * EGL10.ELG_RENDERABLE_TYPE containing the EGL_OPENGL_ES2_BIT flag - * set. See ConfigChooser class definition below. - * - * - The class must select the surface's format, then choose an EGLConfig - * that matches it exactly (with regards to red/green/blue/alpha channels - * bit depths). Failure to do so would result in an EGL_BAD_MATCH error. - */ -class GodotGLRenderView extends GLSurfaceView implements GodotRenderView { - private final Godot godot; - private final GodotInputHandler inputHandler; - private final GodotRenderer godotRenderer; - private final SparseArray customPointerIcons = new SparseArray<>(); - - public GodotGLRenderView(Godot godot, GodotInputHandler inputHandler, XRMode xrMode, boolean useDebugOpengl, boolean shouldBeTranslucent) { - super(godot.getContext()); - - this.godot = godot; - this.inputHandler = inputHandler; - this.godotRenderer = new GodotRenderer(); - setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT)); - init(xrMode, shouldBeTranslucent, useDebugOpengl); - } - - @Override - public SurfaceView getView() { - return this; - } - - @Override - public void queueOnRenderThread(Runnable event) { - queueEvent(event); - } - - @Override - public void onActivityPaused() { - queueEvent(() -> { - GodotLib.focusout(); - // Pause the renderer - godotRenderer.onActivityPaused(); - }); - } - - @Override - public void onActivityStopped() { - pauseGLThread(); - } - - @Override - public void onActivityResumed() { - queueEvent(() -> { - // Resume the renderer - godotRenderer.onActivityResumed(); - GodotLib.focusin(); - }); - } - - @Override - public void onActivityStarted() { - resumeGLThread(); - } - - @Override - public void onActivityDestroyed() { - requestRenderThreadExitAndWait(); - } - - @Override - public GodotInputHandler getInputHandler() { - return inputHandler; - } - - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouchEvent(MotionEvent event) { - super.onTouchEvent(event); - return inputHandler.onTouchEvent(event); - } - - @Override - public boolean onKeyUp(final int keyCode, KeyEvent event) { - return inputHandler.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); - } - - @Override - public boolean onKeyDown(final int keyCode, KeyEvent event) { - return inputHandler.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); - } - - @Override - public boolean onGenericMotionEvent(MotionEvent event) { - return inputHandler.onGenericMotionEvent(event) || super.onGenericMotionEvent(event); - } - - @Override - public boolean onCapturedPointerEvent(MotionEvent event) { - return inputHandler.onGenericMotionEvent(event); - } - - @Override - public void onPointerCaptureChange(boolean hasCapture) { - super.onPointerCaptureChange(hasCapture); - inputHandler.onPointerCaptureChange(hasCapture); - } - - @Override - public void requestPointerCapture() { - if (canCapturePointer()) { - super.requestPointerCapture(); - inputHandler.onPointerCaptureChange(true); - } - } - - @Override - public void releasePointerCapture() { - super.releasePointerCapture(); - inputHandler.onPointerCaptureChange(false); - } - - /** - * Used to configure the PointerIcon for the given type. - * - * Called from JNI - */ - @Keep - @Override - public void configurePointerIcon(int pointerType, String imagePath, float hotSpotX, float hotSpotY) { - try { - Bitmap bitmap = null; - if (!TextUtils.isEmpty(imagePath)) { - if (godot.getDirectoryAccessHandler().filesystemFileExists(imagePath)) { - // Try to load the bitmap from the file system - bitmap = BitmapFactory.decodeFile(imagePath); - } else if (godot.getDirectoryAccessHandler().assetsFileExists(imagePath)) { - // Try to load the bitmap from the assets directory - AssetManager am = getContext().getAssets(); - InputStream imageInputStream = am.open(imagePath); - bitmap = BitmapFactory.decodeStream(imageInputStream); - } - } - - PointerIcon customPointerIcon = PointerIcon.create(bitmap, hotSpotX, hotSpotY); - customPointerIcons.put(pointerType, customPointerIcon); - } catch (Exception e) { - // Reset the custom pointer icon - customPointerIcons.delete(pointerType); - } - } - - /** - * called from JNI to change pointer icon - */ - @Keep - @Override - public void setPointerIcon(int pointerType) { - PointerIcon pointerIcon = customPointerIcons.get(pointerType); - if (pointerIcon == null) { - pointerIcon = PointerIcon.getSystemIcon(getContext(), pointerType); - } - setPointerIcon(pointerIcon); - } - - @Override - public PointerIcon onResolvePointerIcon(MotionEvent me, int pointerIndex) { - return getPointerIcon(); - } - - private void init(XRMode xrMode, boolean translucent, boolean useDebugOpengl) { - setPreserveEGLContextOnPause(true); - setFocusableInTouchMode(true); - switch (xrMode) { - case OPENXR: - // Replace the default egl config chooser. - setEGLConfigChooser(new OvrConfigChooser()); - - // Replace the default context factory. - setEGLContextFactory(new OvrContextFactory()); - - // Replace the default window surface factory. - setEGLWindowSurfaceFactory(new OvrWindowSurfaceFactory()); - break; - - case REGULAR: - default: - /* By default, GLSurfaceView() creates a RGB_565 opaque surface. - * If we want a translucent one, we should change the surface's - * format here, using PixelFormat.TRANSLUCENT for GL Surfaces - * is interpreted as any 32-bit surface with alpha by SurfaceFlinger. - */ - if (translucent) { - this.getHolder().setFormat(PixelFormat.TRANSLUCENT); - } - - /* Setup the context factory for 2.0 rendering. - * See ContextFactory class definition below - */ - setEGLContextFactory(new RegularContextFactory(useDebugOpengl)); - - /* We need to choose an EGLConfig that matches the format of - * our surface exactly. This is going to be done in our - * custom config chooser. See ConfigChooser class definition - * below. - */ - - setEGLConfigChooser( - new RegularFallbackConfigChooser(8, 8, 8, 8, 24, 0, - new RegularConfigChooser(8, 8, 8, 8, 16, 0))); - break; - } - } - - @Override - public void startRenderer() { - /* Set the renderer responsible for frame rendering */ - setRenderer(godotRenderer); - } -} diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java index bbc7c1d56142..089ba80a5aa7 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -30,9 +30,9 @@ package org.godotengine.godot; -import org.godotengine.godot.gl.GodotRenderer; import org.godotengine.godot.io.directory.DirectoryAccessHandler; import org.godotengine.godot.io.file.FileAccessHandler; +import org.godotengine.godot.render.GodotRenderer; import org.godotengine.godot.tts.GodotTTS; import org.godotengine.godot.utils.GodotNetUtils; import org.godotengine.godot.variant.Callable; @@ -42,8 +42,6 @@ import android.hardware.SensorEvent; import android.view.Surface; -import javax.microedition.khronos.opengles.GL10; - /** * Wrapper for native library */ @@ -81,7 +79,7 @@ public static native boolean initialize( * @param p_surface * @param p_width * @param p_height - * @see org.godotengine.godot.gl.GLSurfaceView.Renderer#onSurfaceChanged(GL10, int, int) + * @see GodotRenderer#onRenderSurfaceChanged(Surface, int, int) */ public static native void resize(Surface p_surface, int p_width, int p_height); @@ -91,6 +89,11 @@ public static native boolean initialize( */ public static native void newcontext(Surface p_surface); + /** + * Invoked on the render thread when any of the EGL resources may have changed. + */ + public static native void updateEglResources(long display, long surface, long context, long config); + /** * Forward {@link Activity#onBackPressed()} event. */ @@ -98,9 +101,9 @@ public static native boolean initialize( /** * Invoked on the GL thread to draw the current frame. - * @see org.godotengine.godot.gl.GLSurfaceView.Renderer#onDrawFrame(GL10) + * @see GodotRenderer#onRenderDrawFrame() */ - public static native boolean step(); + public static native void step(); /** * TTS callback. diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java index 9fe4fb20aa7a..b533490b558f 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java @@ -31,44 +31,11 @@ package org.godotengine.godot; import org.godotengine.godot.input.GodotInputHandler; -import org.godotengine.godot.utils.DeviceUtils; import android.view.SurfaceView; public interface GodotRenderView { SurfaceView getView(); - /** - * Starts the thread that will drive Godot's rendering. - */ - void startRenderer(); - - /** - * Queues a runnable to be run on the rendering thread. - */ - void queueOnRenderThread(Runnable event); - - void onActivityPaused(); - - void onActivityStopped(); - - void onActivityResumed(); - - void onActivityStarted(); - - void onActivityDestroyed(); - GodotInputHandler getInputHandler(); - - void configurePointerIcon(int pointerType, String imagePath, float hotSpotX, float hotSpotY); - - void setPointerIcon(int pointerType); - - /** - * @return true if pointer capture is supported. - */ - default boolean canCapturePointer() { - // Pointer capture is not supported on native XR devices. - return !DeviceUtils.isNativeXRDevice(getView().getContext()) && getInputHandler().canCapturePointer(); - } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java deleted file mode 100644 index 6287065f114b..000000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java +++ /dev/null @@ -1,221 +0,0 @@ -/**************************************************************************/ -/* GodotVulkanRenderView.java */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -package org.godotengine.godot; - -import org.godotengine.godot.input.GodotInputHandler; -import org.godotengine.godot.vulkan.VkRenderer; -import org.godotengine.godot.vulkan.VkSurfaceView; - -import android.annotation.SuppressLint; -import android.content.res.AssetManager; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.PixelFormat; -import android.text.TextUtils; -import android.util.SparseArray; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.PointerIcon; -import android.view.SurfaceView; - -import androidx.annotation.Keep; - -import java.io.InputStream; - -class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView { - private final Godot godot; - private final GodotInputHandler mInputHandler; - private final VkRenderer mRenderer; - private final SparseArray customPointerIcons = new SparseArray<>(); - - public GodotVulkanRenderView(Godot godot, GodotInputHandler inputHandler, boolean shouldBeTranslucent) { - super(godot.getContext()); - - this.godot = godot; - mInputHandler = inputHandler; - mRenderer = new VkRenderer(); - setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT)); - setFocusableInTouchMode(true); - setClickable(false); - - if (shouldBeTranslucent) { - this.getHolder().setFormat(PixelFormat.TRANSLUCENT); - } - } - - @Override - public void startRenderer() { - startRenderer(mRenderer); - } - - @Override - public SurfaceView getView() { - return this; - } - - @Override - public void queueOnRenderThread(Runnable event) { - queueOnVkThread(event); - } - - @Override - public void onActivityPaused() { - queueOnVkThread(() -> { - GodotLib.focusout(); - // Pause the renderer - mRenderer.onVkPause(); - }); - } - - @Override - public void onActivityStopped() { - pauseRenderThread(); - } - - @Override - public void onActivityStarted() { - resumeRenderThread(); - } - - @Override - public void onActivityResumed() { - queueOnVkThread(() -> { - // Resume the renderer - mRenderer.onVkResume(); - GodotLib.focusin(); - }); - } - - @Override - public void onActivityDestroyed() { - requestRenderThreadExitAndWait(); - } - - @Override - public GodotInputHandler getInputHandler() { - return mInputHandler; - } - - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouchEvent(MotionEvent event) { - super.onTouchEvent(event); - return mInputHandler.onTouchEvent(event); - } - - @Override - public boolean onKeyUp(final int keyCode, KeyEvent event) { - return mInputHandler.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); - } - - @Override - public boolean onKeyDown(final int keyCode, KeyEvent event) { - return mInputHandler.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); - } - - @Override - public boolean onGenericMotionEvent(MotionEvent event) { - return mInputHandler.onGenericMotionEvent(event) || super.onGenericMotionEvent(event); - } - - @Override - public boolean onCapturedPointerEvent(MotionEvent event) { - return mInputHandler.onGenericMotionEvent(event); - } - - @Override - public void requestPointerCapture() { - if (canCapturePointer()) { - super.requestPointerCapture(); - mInputHandler.onPointerCaptureChange(true); - } - } - - @Override - public void releasePointerCapture() { - super.releasePointerCapture(); - mInputHandler.onPointerCaptureChange(false); - } - - @Override - public void onPointerCaptureChange(boolean hasCapture) { - super.onPointerCaptureChange(hasCapture); - mInputHandler.onPointerCaptureChange(hasCapture); - } - - /** - * Used to configure the PointerIcon for the given type. - * - * Called from JNI - */ - @Keep - @Override - public void configurePointerIcon(int pointerType, String imagePath, float hotSpotX, float hotSpotY) { - try { - Bitmap bitmap = null; - if (!TextUtils.isEmpty(imagePath)) { - if (godot.getDirectoryAccessHandler().filesystemFileExists(imagePath)) { - // Try to load the bitmap from the file system - bitmap = BitmapFactory.decodeFile(imagePath); - } else if (godot.getDirectoryAccessHandler().assetsFileExists(imagePath)) { - // Try to load the bitmap from the assets directory - AssetManager am = getContext().getAssets(); - InputStream imageInputStream = am.open(imagePath); - bitmap = BitmapFactory.decodeStream(imageInputStream); - } - } - - PointerIcon customPointerIcon = PointerIcon.create(bitmap, hotSpotX, hotSpotY); - customPointerIcons.put(pointerType, customPointerIcon); - } catch (Exception e) { - // Reset the custom pointer icon - customPointerIcons.delete(pointerType); - } - } - - /** - * called from JNI to change pointer icon - */ - @Keep - @Override - public void setPointerIcon(int pointerType) { - PointerIcon pointerIcon = customPointerIcons.get(pointerType); - if (pointerIcon == null) { - pointerIcon = PointerIcon.getSystemIcon(getContext(), pointerType); - } - setPointerIcon(pointerIcon); - } - - @Override - public PointerIcon onResolvePointerIcon(MotionEvent me, int pointerIndex) { - return getPointerIcon(); - } -} diff --git a/platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper.java deleted file mode 100644 index af16cfce7448..000000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper.java +++ /dev/null @@ -1,566 +0,0 @@ -// clang-format off - -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.godotengine.godot.gl; - -import android.opengl.GLDebugHelper; -import android.opengl.GLException; - -import java.io.IOException; -import java.io.Writer; - -import javax.microedition.khronos.egl.EGL; -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGL11; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; -import javax.microedition.khronos.egl.EGLSurface; - -class EGLLogWrapper implements EGL11 { - private EGL10 mEgl10; - Writer mLog; - boolean mLogArgumentNames; - boolean mCheckError; - private int mArgCount; - - - public EGLLogWrapper(EGL egl, int configFlags, Writer log) { - mEgl10 = (EGL10) egl; - mLog = log; - mLogArgumentNames = - (GLDebugHelper.CONFIG_LOG_ARGUMENT_NAMES & configFlags) != 0; - mCheckError = - (GLDebugHelper.CONFIG_CHECK_GL_ERROR & configFlags) != 0; - } - - public boolean eglChooseConfig(EGLDisplay display, int[] attrib_list, - EGLConfig[] configs, int config_size, int[] num_config) { - begin("eglChooseConfig"); - arg("display", display); - arg("attrib_list", attrib_list); - arg("config_size", config_size); - end(); - - boolean result = mEgl10.eglChooseConfig(display, attrib_list, configs, - config_size, num_config); - arg("configs", configs); - arg("num_config", num_config); - returns(result); - checkError(); - return result; - } - - public boolean eglCopyBuffers(EGLDisplay display, EGLSurface surface, - Object native_pixmap) { - begin("eglCopyBuffers"); - arg("display", display); - arg("surface", surface); - arg("native_pixmap", native_pixmap); - end(); - - boolean result = mEgl10.eglCopyBuffers(display, surface, native_pixmap); - returns(result); - checkError(); - return result; - } - - public EGLContext eglCreateContext(EGLDisplay display, EGLConfig config, - EGLContext share_context, int[] attrib_list) { - begin("eglCreateContext"); - arg("display", display); - arg("config", config); - arg("share_context", share_context); - arg("attrib_list", attrib_list); - end(); - - EGLContext result = mEgl10.eglCreateContext(display, config, - share_context, attrib_list); - returns(result); - checkError(); - return result; - } - - public EGLSurface eglCreatePbufferSurface(EGLDisplay display, - EGLConfig config, int[] attrib_list) { - begin("eglCreatePbufferSurface"); - arg("display", display); - arg("config", config); - arg("attrib_list", attrib_list); - end(); - - EGLSurface result = mEgl10.eglCreatePbufferSurface(display, config, - attrib_list); - returns(result); - checkError(); - return result; - } - - public EGLSurface eglCreatePixmapSurface(EGLDisplay display, - EGLConfig config, Object native_pixmap, int[] attrib_list) { - begin("eglCreatePixmapSurface"); - arg("display", display); - arg("config", config); - arg("native_pixmap", native_pixmap); - arg("attrib_list", attrib_list); - end(); - - EGLSurface result = mEgl10.eglCreatePixmapSurface(display, config, - native_pixmap, attrib_list); - returns(result); - checkError(); - return result; - } - - public EGLSurface eglCreateWindowSurface(EGLDisplay display, - EGLConfig config, Object native_window, int[] attrib_list) { - begin("eglCreateWindowSurface"); - arg("display", display); - arg("config", config); - arg("native_window", native_window); - arg("attrib_list", attrib_list); - end(); - - EGLSurface result = mEgl10.eglCreateWindowSurface(display, config, - native_window, attrib_list); - returns(result); - checkError(); - return result; - } - - public boolean eglDestroyContext(EGLDisplay display, EGLContext context) { - begin("eglDestroyContext"); - arg("display", display); - arg("context", context); - end(); - - boolean result = mEgl10.eglDestroyContext(display, context); - returns(result); - checkError(); - return result; - } - - public boolean eglDestroySurface(EGLDisplay display, EGLSurface surface) { - begin("eglDestroySurface"); - arg("display", display); - arg("surface", surface); - end(); - - boolean result = mEgl10.eglDestroySurface(display, surface); - returns(result); - checkError(); - return result; - } - - public boolean eglGetConfigAttrib(EGLDisplay display, EGLConfig config, - int attribute, int[] value) { - begin("eglGetConfigAttrib"); - arg("display", display); - arg("config", config); - arg("attribute", attribute); - end(); - boolean result = mEgl10.eglGetConfigAttrib(display, config, attribute, - value); - arg("value", value); - returns(result); - checkError(); - return false; - } - - public boolean eglGetConfigs(EGLDisplay display, EGLConfig[] configs, - int config_size, int[] num_config) { - begin("eglGetConfigs"); - arg("display", display); - arg("config_size", config_size); - end(); - - boolean result = mEgl10.eglGetConfigs(display, configs, config_size, - num_config); - arg("configs", configs); - arg("num_config", num_config); - returns(result); - checkError(); - return result; - } - - public EGLContext eglGetCurrentContext() { - begin("eglGetCurrentContext"); - end(); - - EGLContext result = mEgl10.eglGetCurrentContext(); - returns(result); - - checkError(); - return result; - } - - public EGLDisplay eglGetCurrentDisplay() { - begin("eglGetCurrentDisplay"); - end(); - - EGLDisplay result = mEgl10.eglGetCurrentDisplay(); - returns(result); - - checkError(); - return result; - } - - public EGLSurface eglGetCurrentSurface(int readdraw) { - begin("eglGetCurrentSurface"); - arg("readdraw", readdraw); - end(); - - EGLSurface result = mEgl10.eglGetCurrentSurface(readdraw); - returns(result); - - checkError(); - return result; - } - - public EGLDisplay eglGetDisplay(Object native_display) { - begin("eglGetDisplay"); - arg("native_display", native_display); - end(); - - EGLDisplay result = mEgl10.eglGetDisplay(native_display); - returns(result); - - checkError(); - return result; - } - - public int eglGetError() { - begin("eglGetError"); - end(); - - int result = mEgl10.eglGetError(); - returns(getErrorString(result)); - - return result; - } - - public boolean eglInitialize(EGLDisplay display, int[] major_minor) { - begin("eglInitialize"); - arg("display", display); - end(); - boolean result = mEgl10.eglInitialize(display, major_minor); - returns(result); - arg("major_minor", major_minor); - checkError(); - return result; - } - - public boolean eglMakeCurrent(EGLDisplay display, EGLSurface draw, - EGLSurface read, EGLContext context) { - begin("eglMakeCurrent"); - arg("display", display); - arg("draw", draw); - arg("read", read); - arg("context", context); - end(); - boolean result = mEgl10.eglMakeCurrent(display, draw, read, context); - returns(result); - checkError(); - return result; - } - - public boolean eglQueryContext(EGLDisplay display, EGLContext context, - int attribute, int[] value) { - begin("eglQueryContext"); - arg("display", display); - arg("context", context); - arg("attribute", attribute); - end(); - boolean result = mEgl10.eglQueryContext(display, context, attribute, - value); - returns(value[0]); - returns(result); - checkError(); - return result; - } - - public String eglQueryString(EGLDisplay display, int name) { - begin("eglQueryString"); - arg("display", display); - arg("name", name); - end(); - String result = mEgl10.eglQueryString(display, name); - returns(result); - checkError(); - return result; - } - - public boolean eglQuerySurface(EGLDisplay display, EGLSurface surface, - int attribute, int[] value) { - begin("eglQuerySurface"); - arg("display", display); - arg("surface", surface); - arg("attribute", attribute); - end(); - boolean result = mEgl10.eglQuerySurface(display, surface, attribute, - value); - returns(value[0]); - returns(result); - checkError(); - return result; - } - - public boolean eglSwapBuffers(EGLDisplay display, EGLSurface surface) { - begin("eglSwapBuffers"); - arg("display", display); - arg("surface", surface); - end(); - boolean result = mEgl10.eglSwapBuffers(display, surface); - returns(result); - checkError(); - return result; - } - - public boolean eglTerminate(EGLDisplay display) { - begin("eglTerminate"); - arg("display", display); - end(); - boolean result = mEgl10.eglTerminate(display); - returns(result); - checkError(); - return result; - } - - public boolean eglWaitGL() { - begin("eglWaitGL"); - end(); - boolean result = mEgl10.eglWaitGL(); - returns(result); - checkError(); - return result; - } - - public boolean eglWaitNative(int engine, Object bindTarget) { - begin("eglWaitNative"); - arg("engine", engine); - arg("bindTarget", bindTarget); - end(); - boolean result = mEgl10.eglWaitNative(engine, bindTarget); - returns(result); - checkError(); - return result; - } - - private void checkError() { - int eglError; - if ((eglError = mEgl10.eglGetError()) != EGL_SUCCESS) { - String errorMessage = "eglError: " + getErrorString(eglError); - logLine(errorMessage); - if (mCheckError) { - throw new GLException(eglError, errorMessage); - } - } - } - - private void logLine(String message) { - log(message + '\n'); - } - - private void log(String message) { - try { - mLog.write(message); - } catch (IOException e) { - // Ignore exception, keep on trying - } - } - - private void begin(String name) { - log(name + '('); - mArgCount = 0; - } - - private void arg(String name, String value) { - if (mArgCount++ > 0) { - log(", "); - } - if (mLogArgumentNames) { - log(name + "="); - } - log(value); - } - - private void end() { - log(");\n"); - flush(); - } - - private void flush() { - try { - mLog.flush(); - } catch (IOException e) { - mLog = null; - } - } - - private void arg(String name, int value) { - arg(name, Integer.toString(value)); - } - - private void arg(String name, Object object) { - arg(name, toString(object)); - } - - private void arg(String name, EGLDisplay object) { - if (object == EGL10.EGL_DEFAULT_DISPLAY) { - arg(name, "EGL10.EGL_DEFAULT_DISPLAY"); - } else if (object == EGL_NO_DISPLAY) { - arg(name, "EGL10.EGL_NO_DISPLAY"); - } else { - arg(name, toString(object)); - } - } - - private void arg(String name, EGLContext object) { - if (object == EGL10.EGL_NO_CONTEXT) { - arg(name, "EGL10.EGL_NO_CONTEXT"); - } else { - arg(name, toString(object)); - } - } - - private void arg(String name, EGLSurface object) { - if (object == EGL10.EGL_NO_SURFACE) { - arg(name, "EGL10.EGL_NO_SURFACE"); - } else { - arg(name, toString(object)); - } - } - - private void returns(String result) { - log(" returns " + result + ";\n"); - flush(); - } - - private void returns(int result) { - returns(Integer.toString(result)); - } - - private void returns(boolean result) { - returns(Boolean.toString(result)); - } - - private void returns(Object result) { - returns(toString(result)); - } - - private String toString(Object obj) { - if (obj == null) { - return "null"; - } else { - return obj.toString(); - } - } - - private void arg(String name, int[] arr) { - if (arr == null) { - arg(name, "null"); - } else { - arg(name, toString(arr.length, arr, 0)); - } - } - - private void arg(String name, Object[] arr) { - if (arr == null) { - arg(name, "null"); - } else { - arg(name, toString(arr.length, arr, 0)); - } - } - - private String toString(int n, int[] arr, int offset) { - StringBuilder buf = new StringBuilder(); - buf.append("{\n"); - int arrLen = arr.length; - for (int i = 0; i < n; i++) { - int index = offset + i; - buf.append(" [" + index + "] = "); - if (index < 0 || index >= arrLen) { - buf.append("out of bounds"); - } else { - buf.append(arr[index]); - } - buf.append('\n'); - } - buf.append("}"); - return buf.toString(); - } - - private String toString(int n, Object[] arr, int offset) { - StringBuilder buf = new StringBuilder(); - buf.append("{\n"); - int arrLen = arr.length; - for (int i = 0; i < n; i++) { - int index = offset + i; - buf.append(" [" + index + "] = "); - if (index < 0 || index >= arrLen) { - buf.append("out of bounds"); - } else { - buf.append(arr[index]); - } - buf.append('\n'); - } - buf.append("}"); - return buf.toString(); - } - - private static String getHex(int value) { - return "0x" + Integer.toHexString(value); - } - - public static String getErrorString(int error) { - switch (error) { - case EGL_SUCCESS: - return "EGL_SUCCESS"; - case EGL_NOT_INITIALIZED: - return "EGL_NOT_INITIALIZED"; - case EGL_BAD_ACCESS: - return "EGL_BAD_ACCESS"; - case EGL_BAD_ALLOC: - return "EGL_BAD_ALLOC"; - case EGL_BAD_ATTRIBUTE: - return "EGL_BAD_ATTRIBUTE"; - case EGL_BAD_CONFIG: - return "EGL_BAD_CONFIG"; - case EGL_BAD_CONTEXT: - return "EGL_BAD_CONTEXT"; - case EGL_BAD_CURRENT_SURFACE: - return "EGL_BAD_CURRENT_SURFACE"; - case EGL_BAD_DISPLAY: - return "EGL_BAD_DISPLAY"; - case EGL_BAD_MATCH: - return "EGL_BAD_MATCH"; - case EGL_BAD_NATIVE_PIXMAP: - return "EGL_BAD_NATIVE_PIXMAP"; - case EGL_BAD_NATIVE_WINDOW: - return "EGL_BAD_NATIVE_WINDOW"; - case EGL_BAD_PARAMETER: - return "EGL_BAD_PARAMETER"; - case EGL_BAD_SURFACE: - return "EGL_BAD_SURFACE"; - case EGL11.EGL_CONTEXT_LOST: - return "EGL_CONTEXT_LOST"; - default: - return getHex(error); - } - } -} diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java index 8b0f785458b6..ee5f21d46158 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java @@ -206,6 +206,16 @@ public void onMainRequestPermissionsResult(int requestCode, String[] permissions */ public void onMainPause() {} + /** + * @see Activity#onStop() + */ + public void onMainStop() {} + + /** + * @see Activity#onStart() + */ + public void onMainStart() {} + /** * @see Activity#onResume() */ @@ -238,37 +248,71 @@ public void onGodotMainLoopStarted() {} /** * When using the OpenGL renderer, this is invoked once per frame on the GL thread after the * frame is drawn. + * + * @deprecated Use {@link #onRenderDrawFrame()} instead. */ + @Deprecated public void onGLDrawFrame(GL10 gl) {} /** * When using the OpenGL renderer, this is called on the GL thread after the surface is created * and whenever the OpenGL ES surface size changes. + * + * @deprecated Use {@link #onRenderSurfaceChanged(Surface, int, int)} instead. */ + @Deprecated public void onGLSurfaceChanged(GL10 gl, int width, int height) {} /** * When using the OpenGL renderer, this is called on the GL thread when the surface is created * or recreated. + * + * @deprecated Use {@link #onRenderSurfaceCreated(Surface)} instead. */ + @Deprecated public void onGLSurfaceCreated(GL10 gl, EGLConfig config) {} + /** + * This is called on the render thread when the surface is created or recreated. + */ + public void onRenderSurfaceCreated(Surface surface) {} + + /** + * This is called on the render thread after the surface is created and whenever the surface + * size changes. + */ + public void onRenderSurfaceChanged(Surface surface, int width, int height) {} + + /** + * Invoked once per frame on the render thread after the frame is drawn. + */ + public void onRenderDrawFrame() {} + /** * When using the Vulkan renderer, this is invoked once per frame on the Vulkan thread after * the frame is drawn. + * + * @deprecated Use {@link #onRenderDrawFrame()} instead. */ + @Deprecated public void onVkDrawFrame() {} /** * When using the Vulkan renderer, this is called on the Vulkan thread after the surface is * created and whenever the surface size changes. + * + * @deprecated Use {@link #onRenderSurfaceChanged(Surface, int, int)} instead. */ + @Deprecated public void onVkSurfaceChanged(Surface surface, int width, int height) {} /** * When using the Vulkan renderer, this is called on the Vulkan thread when the surface is * created or recreated. + * + * @deprecated Use {@link #onRenderSurfaceCreated(Surface)} instead. */ + @Deprecated public void onVkSurfaceCreated(Surface surface) {} /** diff --git a/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java b/platform/android/java/lib/src/org/godotengine/godot/render/GLSurfaceView.java similarity index 58% rename from platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java rename to platform/android/java/lib/src/org/godotengine/godot/render/GLSurfaceView.java index 6a4e9da699e3..b3ba077f345a 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/render/GLSurfaceView.java @@ -1,3 +1,33 @@ +/**************************************************************************/ +/* GLSurfaceView.java */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + // clang-format off /* @@ -16,29 +46,28 @@ * limitations under the License. */ -package org.godotengine.godot.gl; +package org.godotengine.godot.render; import android.content.Context; import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; import android.opengl.EGLExt; -import android.opengl.GLDebugHelper; +import android.opengl.EGLSurface; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; +import androidx.annotation.NonNull; + +import org.godotengine.godot.utils.GLUtils; + import java.io.Writer; import java.lang.ref.WeakReference; import java.util.ArrayList; - -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGL11; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; -import javax.microedition.khronos.egl.EGLSurface; -import javax.microedition.khronos.opengles.GL; -import javax.microedition.khronos.opengles.GL10; +import java.util.Arrays; /** * An implementation of SurfaceView that uses the dedicated surface for @@ -71,10 +100,10 @@ * GLSurfaceView behavior is customized by calling "set" methods rather than by subclassing. * For example, unlike a regular View, drawing is delegated to a separate Renderer object which * is registered with the GLSurfaceView - * using the {@link #setRenderer(Renderer)} call. + * using the {@link #setRenderer(GodotRenderer)} call. *

*

Initializing GLSurfaceView

- * All you have to do to initialize a GLSurfaceView is call {@link #setRenderer(Renderer)}. + * All you have to do to initialize a GLSurfaceView is call {@link #setRenderer(GodotRenderer)}. * However, if desired, you can modify the default behavior of GLSurfaceView by calling one or * more of these methods before calling setRenderer: *
    @@ -82,7 +111,6 @@ *
  • {@link #setEGLConfigChooser(boolean)} *
  • {@link #setEGLConfigChooser(EGLConfigChooser)} *
  • {@link #setEGLConfigChooser(int, int, int, int, int, int)} - *
  • {@link #setGLWrapper(GLWrapper)} *
*

*

Specifying the android.view.Surface

@@ -104,29 +132,11 @@ * you can override the default behavior by calling one of the * setEGLConfigChooser methods. *

- *

Debug Behavior

- * You can optionally modify the behavior of GLSurfaceView by calling - * one or more of the debugging methods {@link #setDebugFlags(int)}, - * and {@link #setGLWrapper}. These methods may be called before and/or after setRenderer, but - * typically they are called before setRenderer so that they take effect immediately. - *

*

Setting a Renderer

* Finally, you must call {@link #setRenderer} to register a {@link Renderer}. * The renderer is * responsible for doing the actual OpenGL rendering. *

- *

Rendering Mode

- * Once the renderer is set, you can control whether the renderer draws - * continuously or on-demand by calling - * {@link #setRenderMode}. The default is continuous rendering. - *

- *

Activity Life-cycle

- * A GLSurfaceView must be notified when to pause and resume rendering. GLSurfaceView clients - * are required to call {@link #pauseGLThread()} when the activity stops and - * {@link #resumeGLThread()} when the activity starts. These calls allow GLSurfaceView to - * pause and resume the rendering thread, and also allow GLSurfaceView to release and recreate - * the OpenGL display. - *

*

Handling events

*

* To handle an event you will typically subclass GLSurfaceView and override the @@ -163,7 +173,7 @@ * * */ -public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback2 { +class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback2 { private final static String TAG = "GLSurfaceView"; private final static boolean LOG_ATTACH_DETACH = false; private final static boolean LOG_THREADS = false; @@ -172,23 +182,6 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback private final static boolean LOG_RENDERER = false; private final static boolean LOG_RENDERER_DRAW_FRAME = false; private final static boolean LOG_EGL = false; - /** - * The renderer only renders - * when the surface is created, or when {@link #requestRender} is called. - * - * @see #getRenderMode() - * @see #setRenderMode(int) - * @see #requestRender() - */ - public final static int RENDERMODE_WHEN_DIRTY = 0; - /** - * The renderer is called - * continuously to re-render the scene. - * - * @see #getRenderMode() - * @see #setRenderMode(int) - */ - public final static int RENDERMODE_CONTINUOUSLY = 1; /** * Check glError() after every GL call and throw an exception if glError indicates @@ -226,19 +219,6 @@ public GLSurfaceView(Context context, AttributeSet attrs) { init(); } - @Override - protected void finalize() throws Throwable { - try { - if (mGLThread != null) { - // GLThread may still be running if this view was never - // attached to a window. - mGLThread.requestExitAndWait(); - } - } finally { - super.finalize(); - } - } - private void init() { // Install a SurfaceHolder.Callback so we get notified when the // underlying surface is created and destroyed @@ -253,23 +233,6 @@ private void init() { // holder.setType(SurfaceHolder.SURFACE_TYPE_GPU); } - /** - * Set the glWrapper. If the glWrapper is not null, its - * {@link GLWrapper#wrap(GL)} method is called - * whenever a surface is created. A GLWrapper can be used to wrap - * the GL object that's passed to the renderer. Wrapping a GL - * object enables examining and modifying the behavior of the - * GL calls made by the renderer. - *

- * Wrapping is typically used for debugging purposes. - *

- * The default value is null. - * @param glWrapper the new GLWrapper - */ - public void setGLWrapper(GLWrapper glWrapper) { - mGLWrapper = glWrapper; - } - /** * Set the debug flags to a new value. The value is * constructed by OR-together zero or more @@ -323,8 +286,8 @@ public boolean getPreserveEGLContextOnPause() { } /** - * Set the renderer associated with this view. Also starts the thread that - * will call the renderer, which in turn causes the rendering to start. + * Set and start the renderer associated with this view which in turn causes the rendering + * to start. *

This method should be called once and only once in the life-cycle of * a GLSurfaceView. *

The following GLSurfaceView methods can only be called before @@ -338,17 +301,12 @@ public boolean getPreserveEGLContextOnPause() { * The following GLSurfaceView methods can only be called after * setRenderer is called: *

    - *
  • {@link #getRenderMode()} - *
  • {@link #pauseGLThread()} - *
  • {@link #resumeGLThread()} *
  • {@link #queueEvent(Runnable)} - *
  • {@link #requestRender()} - *
  • {@link #setRenderMode(int)} *
* * @param renderer the renderer to use to perform OpenGL drawing. */ - public void setRenderer(Renderer renderer) { + public void setRenderer(GodotRenderer renderer) { checkRenderThreadState(); if (mEGLConfigChooser == null) { mEGLConfigChooser = new SimpleEGLConfigChooser(true); @@ -360,14 +318,18 @@ public void setRenderer(Renderer renderer) { mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory(); } mRenderer = renderer; - mGLThread = new GLThread(mThisWeakRef); - mGLThread.start(); + mRenderer.startRenderer(); + + RenderThread renderThread = mRenderer.getRenderThread(); + if (renderThread instanceof GLThread) { + ((GLThread) renderThread).registerGLSurfaceInfo(getHolder(), mThisWeakRef); + } } /** * Install a custom EGLContextFactory. *

If this method is - * called, it must be called before {@link #setRenderer(Renderer)} + * called, it must be called before {@link #setRenderer(GodotRenderer)} * is called. *

* If this method is not called, then by default @@ -382,7 +344,7 @@ public void setEGLContextFactory(EGLContextFactory factory) { /** * Install a custom EGLWindowSurfaceFactory. *

If this method is - * called, it must be called before {@link #setRenderer(Renderer)} + * called, it must be called before {@link #setRenderer(GodotRenderer)} * is called. *

* If this method is not called, then by default @@ -396,7 +358,7 @@ public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory) { /** * Install a custom EGLConfigChooser. *

If this method is - * called, it must be called before {@link #setRenderer(Renderer)} + * called, it must be called before {@link #setRenderer(GodotRenderer)} * is called. *

* If no setEGLConfigChooser method is called, then by default the @@ -415,7 +377,7 @@ public void setEGLConfigChooser(EGLConfigChooser configChooser) { * as close to 16-bit RGB as possible, with or without an optional depth * buffer as close to 16-bits as possible. *

If this method is - * called, it must be called before {@link #setRenderer(Renderer)} + * called, it must be called before {@link #setRenderer(GodotRenderer)} * is called. *

* If no setEGLConfigChooser method is called, then by default the @@ -433,7 +395,7 @@ public void setEGLConfigChooser(boolean needDepth) { * with at least the specified depthSize and stencilSize, * and exactly the specified redSize, greenSize, blueSize and alphaSize. *

If this method is - * called, it must be called before {@link #setRenderer(Renderer)} + * called, it must be called before {@link #setRenderer(GodotRenderer)} * is called. *

* If no setEGLConfigChooser method is called, then by default the @@ -462,7 +424,7 @@ public void setEGLConfigChooser(int redSize, int greenSize, int blueSize, *

Note: Activities which require OpenGL ES 2.0 should indicate this by * setting @lt;uses-feature android:glEsVersion="0x00020000" /> in the activity's * AndroidManifest.xml file. - *

If this method is called, it must be called before {@link #setRenderer(Renderer)} + *

If this method is called, it must be called before {@link #setRenderer(GodotRenderer)} * is called. *

This method only affects the behavior of the default EGLContexFactory and the * default EGLConfigChooser. If @@ -478,54 +440,12 @@ public void setEGLContextClientVersion(int version) { mEGLContextClientVersion = version; } - /** - * Set the rendering mode. When renderMode is - * RENDERMODE_CONTINUOUSLY, the renderer is called - * repeatedly to re-render the scene. When renderMode - * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface - * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY. - *

- * Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance - * by allowing the GPU and CPU to idle when the view does not need to be updated. - *

- * This method can only be called after {@link #setRenderer(Renderer)} - * - * @param renderMode one of the RENDERMODE_X constants - * @see #RENDERMODE_CONTINUOUSLY - * @see #RENDERMODE_WHEN_DIRTY - */ - public void setRenderMode(int renderMode) { - mGLThread.setRenderMode(renderMode); - } - - /** - * Get the current rendering mode. May be called - * from any thread. Must not be called before a renderer has been set. - * @return the current rendering mode. - * @see #RENDERMODE_CONTINUOUSLY - * @see #RENDERMODE_WHEN_DIRTY - */ - public int getRenderMode() { - return mGLThread.getRenderMode(); - } - - /** - * Request that the renderer render a frame. - * This method is typically used when the render mode has been set to - * {@link #RENDERMODE_WHEN_DIRTY}, so that frames are only rendered on demand. - * May be called - * from any thread. Must not be called before a renderer has been set. - */ - public void requestRender() { - mGLThread.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) { - mGLThread.surfaceCreated(); + mRenderer.getRenderThread().surfaceCreated(holder, mThisWeakRef); } /** @@ -534,7 +454,7 @@ public void surfaceCreated(SurfaceHolder holder) { */ public void surfaceDestroyed(SurfaceHolder holder) { // Surface will be destroyed when we return - mGLThread.surfaceDestroyed(); + mRenderer.getRenderThread().surfaceDestroyed(holder); } /** @@ -542,7 +462,7 @@ public void surfaceDestroyed(SurfaceHolder holder) { * not normally called or subclassed by clients of GLSurfaceView. */ public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { - mGLThread.onWindowResize(w, h); + mRenderer.getRenderThread().surfaceChanged(holder, w, h); } /** @@ -551,8 +471,9 @@ public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { */ @Override public void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable finishDrawing) { - if (mGLThread != null) { - mGLThread.requestRenderAndNotify(finishDrawing); + RenderThread renderThread = mRenderer.getRenderThread(); + if (renderThread instanceof GLThread) { + ((GLThread) renderThread).requestRenderAndNotify(finishDrawing); } } @@ -567,45 +488,6 @@ public void surfaceRedrawNeeded(SurfaceHolder holder) { // will be called. } - - // -- GODOT start -- - /** - * Pause the rendering thread, optionally tearing down the EGL context - * depending upon the value of {@link #setPreserveEGLContextOnPause(boolean)}. - * - * This method should be called when it is no longer desirable for the - * GLSurfaceView to continue rendering, such as in response to - * {@link android.app.Activity#onStop Activity.onStop}. - * - * Must not be called before a renderer has been set. - */ - protected final void pauseGLThread() { - mGLThread.onPause(); - } - - /** - * Resumes the rendering thread, re-creating the OpenGL context if necessary. It - * is the counterpart to {@link #pauseGLThread()}. - * - * This method should typically be called in - * {@link android.app.Activity#onStart Activity.onStart}. - * - * Must not be called before a renderer has been set. - */ - protected final void resumeGLThread() { - mGLThread.onResume(); - } - - /** - * Requests the render thread to exit and block until it does. - */ - protected final void requestRenderThreadExitAndWait() { - if (mGLThread != null) { - mGLThread.requestExitAndWait(); - } - } - // -- GODOT end -- - /** * Queue a runnable to be run on the GL rendering thread. This can be used * to communicate with the Renderer on the rendering thread. @@ -613,7 +495,7 @@ protected final void requestRenderThreadExitAndWait() { * @param r the runnable to be run on the GL rendering thread. */ public void queueEvent(Runnable r) { - mGLThread.queueEvent(r); + mRenderer.getRenderThread().queueEvent(r); } /** @@ -626,17 +508,6 @@ protected void onAttachedToWindow() { if (LOG_ATTACH_DETACH) { Log.d(TAG, "onAttachedToWindow reattach =" + mDetached); } - if (mDetached && (mRenderer != null)) { - int renderMode = RENDERMODE_CONTINUOUSLY; - if (mGLThread != null) { - renderMode = mGLThread.getRenderMode(); - } - mGLThread = new GLThread(mThisWeakRef); - if (renderMode != RENDERMODE_CONTINUOUSLY) { - mGLThread.setRenderMode(renderMode); - } - mGLThread.start(); - } mDetached = false; } @@ -645,161 +516,12 @@ protected void onDetachedFromWindow() { if (LOG_ATTACH_DETACH) { Log.d(TAG, "onDetachedFromWindow"); } - if (mGLThread != null) { - mGLThread.requestExitAndWait(); - } mDetached = true; super.onDetachedFromWindow(); } // ---------------------------------------------------------------------- - /** - * An interface used to wrap a GL interface. - *

Typically - * used for implementing debugging and tracing on top of the default - * GL interface. You would typically use this by creating your own class - * that implemented all the GL methods by delegating to another GL instance. - * Then you could add your own behavior before or after calling the - * delegate. All the GLWrapper would do was instantiate and return the - * wrapper GL instance: - *

-	 * class MyGLWrapper implements GLWrapper {
-	 *     GL wrap(GL gl) {
-	 *         return new MyGLImplementation(gl);
-	 *     }
-	 *     static class MyGLImplementation implements GL,GL10,GL11,... {
-	 *         ...
-	 *     }
-	 * }
-	 * 
- * @see #setGLWrapper(GLWrapper) - */ - public interface GLWrapper { - /** - * Wraps a gl interface in another gl interface. - * @param gl a GL interface that is to be wrapped. - * @return either the input argument or another GL object that wraps the input argument. - */ - GL wrap(GL gl); - } - - /** - * A generic renderer interface. - *

- * The renderer is responsible for making OpenGL calls to render a frame. - *

- * GLSurfaceView clients typically create their own classes that implement - * this interface, and then call {@link GLSurfaceView#setRenderer} to - * register the renderer with the GLSurfaceView. - *

- * - *

- *

Developer Guides

- *

For more information about how to use OpenGL, read the - * OpenGL developer guide.

- *
- * - *

Threading

- * The renderer will be called on a separate thread, so that rendering - * performance is decoupled from the UI thread. Clients typically need to - * communicate with the renderer from the UI thread, because that's where - * input events are received. Clients can communicate using any of the - * standard Java techniques for cross-thread communication, or they can - * use the {@link GLSurfaceView#queueEvent(Runnable)} convenience method. - *

- *

EGL Context Lost

- * There are situations where the EGL rendering context will be lost. This - * typically happens when device wakes up after going to sleep. When - * the EGL context is lost, all OpenGL resources (such as textures) that are - * associated with that context will be automatically deleted. In order to - * keep rendering correctly, a renderer must recreate any lost resources - * that it still needs. The {@link #onSurfaceCreated(GL10, EGLConfig)} method - * is a convenient place to do this. - * - * - * @see #setRenderer(Renderer) - */ - public interface Renderer { - /** - * Called when the surface is created or recreated. - *

- * Called when the rendering thread - * starts and whenever the EGL context is lost. The EGL context will typically - * be lost when the Android device awakes after going to sleep. - *

- * Since this method is called at the beginning of rendering, as well as - * every time the EGL context is lost, this method is a convenient place to put - * code to create resources that need to be created when the rendering - * starts, and that need to be recreated when the EGL context is lost. - * Textures are an example of a resource that you might want to create - * here. - *

- * Note that when the EGL context is lost, all OpenGL resources associated - * with that context will be automatically deleted. You do not need to call - * the corresponding "glDelete" methods such as glDeleteTextures to - * manually delete these lost resources. - *

- * @param gl the GL interface. Use instanceof to - * test if the interface supports GL11 or higher interfaces. - * @param config the EGLConfig of the created surface. Can be used - * to create matching pbuffers. - */ - void onSurfaceCreated(GL10 gl, EGLConfig config); - - /** - * Called when the surface changed size. - *

- * Called after the surface is created and whenever - * the OpenGL ES surface size changes. - *

- * Typically you will set your viewport here. If your camera - * is fixed then you could also set your projection matrix here: - *

-		 * void onSurfaceChanged(GL10 gl, int width, int height) {
-		 *     gl.glViewport(0, 0, width, height);
-		 *     // for a fixed camera, set the projection too
-		 *     float ratio = (float) width / height;
-		 *     gl.glMatrixMode(GL10.GL_PROJECTION);
-		 *     gl.glLoadIdentity();
-		 *     gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
-		 * }
-		 * 
- * @param gl the GL interface. Use instanceof to - * test if the interface supports GL11 or higher interfaces. - * @param width - * @param height - */ - void onSurfaceChanged(GL10 gl, int width, int height); - - // -- GODOT start -- - /** - * Called to draw the current frame. - *

- * This method is responsible for drawing the current frame. - *

- * The implementation of this method typically looks like this: - *

-		 * boolean onDrawFrame(GL10 gl) {
-		 *     gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
-		 *     //... other gl calls to render the scene ...
-		 *     return true;
-		 * }
-		 * 
- * @param gl the GL interface. Use instanceof to - * test if the interface supports GL11 or higher interfaces. - * - * @return true if the buffers should be swapped, false otherwise. - */ - boolean onDrawFrame(GL10 gl); - - /** - * Invoked when the render thread is in the process of shutting down. - */ - void onRenderThreadExiting(); - // -- GODOT end -- - } - /** * An interface for customizing the eglCreateContext and eglDestroyContext calls. *

@@ -807,29 +529,28 @@ public interface Renderer { * {@link GLSurfaceView#setEGLContextFactory(EGLContextFactory)} */ public interface EGLContextFactory { - EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig); - void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context); + EGLContext createContext(EGLDisplay display, EGLConfig eglConfig); + void destroyContext(EGLDisplay display, EGLContext context); } private class DefaultContextFactory implements EGLContextFactory { - private int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; - public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) { + public EGLContext createContext(EGLDisplay display, EGLConfig config) { int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion, - EGL10.EGL_NONE }; + EGL14.EGL_NONE }; - return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, - mEGLContextClientVersion != 0 ? attrib_list : null); + return EGL14.eglCreateContext(display, config, EGL14.EGL_NO_CONTEXT, + mEGLContextClientVersion != 0 ? attrib_list : null, 0); } - public void destroyContext(EGL10 egl, EGLDisplay display, - EGLContext context) { - if (!egl.eglDestroyContext(display, context)) { + public void destroyContext(EGLDisplay display, EGLContext context) { + if (!EGL14.eglDestroyContext(display, context)) { Log.e("DefaultContextFactory", "display:" + display + " context: " + context); if (LOG_THREADS) { Log.i("DefaultContextFactory", "tid=" + Thread.currentThread().getId()); } - EglHelper.throwEglException("eglDestroyContex", egl.eglGetError()); + EglHelper.throwEglException("eglDestroyContex", EGL14.eglGetError()); } } } @@ -844,36 +565,36 @@ public interface EGLWindowSurfaceFactory { /** * @return null if the surface cannot be constructed. */ - EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config, - Object nativeWindow); - void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface); - } - - private static class DefaultWindowSurfaceFactory implements EGLWindowSurfaceFactory { - - public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, - EGLConfig config, Object nativeWindow) { + default EGLSurface createWindowSurface(EGLDisplay display, EGLConfig config, SurfaceHolder surfaceHolder) { EGLSurface result = null; - try { - result = egl.eglCreateWindowSurface(display, config, nativeWindow, null); - } catch (IllegalArgumentException e) { - // This exception indicates that the surface flinger surface - // is not valid. This can happen if the surface flinger surface has - // been torn down, but the application has not yet been - // notified via SurfaceHolder.Callback.surfaceDestroyed. - // In theory the application should be notified first, - // but in practice sometimes it is not. See b/4588890 - Log.e(TAG, "eglCreateWindowSurface", e); + if (surfaceHolder != null && surfaceHolder.getSurface() != null && surfaceHolder.getSurface().isValid()) { + try { + Log.d(TAG, "Creating egl window surface"); + result = EGL14.eglCreateWindowSurface(display, config, surfaceHolder, null, 0); + } catch (IllegalArgumentException e) { + // This exception indicates that the surface flinger surface + // is not valid. This can happen if the surface flinger surface has + // been torn down, but the application has not yet been + // notified via SurfaceHolder.Callback.surfaceDestroyed. + // In theory the application should be notified first, + // but in practice sometimes it is not. See b/4588890 + Log.e(TAG, "eglCreateWindowSurface", e); + } + } else { + // Create an offscreen buffer + Log.d(TAG, "Creating egl offscreen buffer"); + result = EGL14.eglCreatePbufferSurface(display, config, null, 0); } return result; } - public void destroySurface(EGL10 egl, EGLDisplay display, - EGLSurface surface) { - egl.eglDestroySurface(display, surface); + default void destroySurface(EGLDisplay display, EGLSurface surface) { + EGL14.eglDestroySurface(display, surface); } } + private static class DefaultWindowSurfaceFactory implements EGLWindowSurfaceFactory {} + /** * An interface for choosing an EGLConfig configuration from a list of * potential configurations. @@ -885,13 +606,12 @@ public interface EGLConfigChooser { /** * Choose a configuration from the list. Implementers typically * implement this method by calling - * {@link EGL10#eglChooseConfig} and iterating through the results. Please consult the + * {@link EGL14#eglChooseConfig} and iterating through the results. Please consult the * EGL specification available from The Khronos Group to learn how to call eglChooseConfig. - * @param egl the EGL10 for the current display. * @param display the current display. * @return the chosen configuration. */ - EGLConfig chooseConfig(EGL10 egl, EGLDisplay display); + EGLConfig chooseConfig(EGLDisplay display); } private abstract class BaseConfigChooser @@ -900,10 +620,10 @@ public BaseConfigChooser(int[] configSpec) { mConfigSpec = filterConfigSpec(configSpec); } - public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + public EGLConfig chooseConfig(EGLDisplay display) { int[] num_config = new int[1]; - if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, - num_config)) { + if (!EGL14.eglChooseConfig(display, mConfigSpec, 0, null, 0, 0, + num_config, 0)) { throw new IllegalArgumentException("eglChooseConfig failed"); } @@ -915,19 +635,18 @@ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { } EGLConfig[] configs = new EGLConfig[numConfigs]; - if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, - num_config)) { + if (!EGL14.eglChooseConfig(display, mConfigSpec, 0, configs, 0, numConfigs, + num_config, 0)) { throw new IllegalArgumentException("eglChooseConfig#2 failed"); } - EGLConfig config = chooseConfig(egl, display, configs); + EGLConfig config = chooseConfig(display, configs); if (config == null) { throw new IllegalArgumentException("No config chosen"); } return config; } - abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, - EGLConfig[] configs); + abstract EGLConfig chooseConfig(EGLDisplay display, EGLConfig[] configs); protected int[] mConfigSpec; @@ -941,13 +660,13 @@ private int[] filterConfigSpec(int[] configSpec) { int len = configSpec.length; int[] newConfigSpec = new int[len + 2]; System.arraycopy(configSpec, 0, newConfigSpec, 0, len-1); - newConfigSpec[len-1] = EGL10.EGL_RENDERABLE_TYPE; + newConfigSpec[len-1] = EGL14.EGL_RENDERABLE_TYPE; if (mEGLContextClientVersion == 2) { newConfigSpec[len] = EGL14.EGL_OPENGL_ES2_BIT; /* EGL_OPENGL_ES2_BIT */ } else { newConfigSpec[len] = EGLExt.EGL_OPENGL_ES3_BIT_KHR; /* EGL_OPENGL_ES3_BIT_KHR */ } - newConfigSpec[len+1] = EGL10.EGL_NONE; + newConfigSpec[len+1] = EGL14.EGL_NONE; return newConfigSpec; } } @@ -960,13 +679,13 @@ private class ComponentSizeChooser extends BaseConfigChooser { public ComponentSizeChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize) { super(new int[] { - EGL10.EGL_RED_SIZE, redSize, - EGL10.EGL_GREEN_SIZE, greenSize, - EGL10.EGL_BLUE_SIZE, blueSize, - EGL10.EGL_ALPHA_SIZE, alphaSize, - EGL10.EGL_DEPTH_SIZE, depthSize, - EGL10.EGL_STENCIL_SIZE, stencilSize, - EGL10.EGL_NONE}); + EGL14.EGL_RED_SIZE, redSize, + EGL14.EGL_GREEN_SIZE, greenSize, + EGL14.EGL_BLUE_SIZE, blueSize, + EGL14.EGL_ALPHA_SIZE, alphaSize, + EGL14.EGL_DEPTH_SIZE, depthSize, + EGL14.EGL_STENCIL_SIZE, stencilSize, + EGL14.EGL_NONE}); mValue = new int[1]; mRedSize = redSize; mGreenSize = greenSize; @@ -977,22 +696,21 @@ public ComponentSizeChooser(int redSize, int greenSize, int blueSize, } @Override - public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, - EGLConfig[] configs) { + public EGLConfig chooseConfig(EGLDisplay display, EGLConfig[] configs) { for (EGLConfig config : configs) { - int d = findConfigAttrib(egl, display, config, - EGL10.EGL_DEPTH_SIZE, 0); - int s = findConfigAttrib(egl, display, config, - EGL10.EGL_STENCIL_SIZE, 0); + int d = findConfigAttrib(display, config, + EGL14.EGL_DEPTH_SIZE, 0); + int s = findConfigAttrib(display, config, + EGL14.EGL_STENCIL_SIZE, 0); if ((d >= mDepthSize) && (s >= mStencilSize)) { - int r = findConfigAttrib(egl, display, config, - EGL10.EGL_RED_SIZE, 0); - int g = findConfigAttrib(egl, display, config, - EGL10.EGL_GREEN_SIZE, 0); - int b = findConfigAttrib(egl, display, config, - EGL10.EGL_BLUE_SIZE, 0); - int a = findConfigAttrib(egl, display, config, - EGL10.EGL_ALPHA_SIZE, 0); + int r = findConfigAttrib(display, config, + EGL14.EGL_RED_SIZE, 0); + int g = findConfigAttrib(display, config, + EGL14.EGL_GREEN_SIZE, 0); + int b = findConfigAttrib(display, config, + EGL14.EGL_BLUE_SIZE, 0); + int a = findConfigAttrib(display, config, + EGL14.EGL_ALPHA_SIZE, 0); if ((r == mRedSize) && (g == mGreenSize) && (b == mBlueSize) && (a == mAlphaSize)) { return config; @@ -1002,16 +720,16 @@ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, return null; } - private int findConfigAttrib(EGL10 egl, EGLDisplay display, + private int findConfigAttrib(EGLDisplay display, EGLConfig config, int attribute, int defaultValue) { - if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { + if (EGL14.eglGetConfigAttrib(display, config, attribute, mValue, 0)) { return mValue[0]; } return defaultValue; } - private int[] mValue; + private final int[] mValue; // Subclasses can adjust these values: protected int mRedSize; protected int mGreenSize; @@ -1037,7 +755,8 @@ public SimpleEGLConfigChooser(boolean withDepthBuffer) { */ private static class EglHelper { - public EglHelper(WeakReference glSurfaceViewWeakRef) { + public EglHelper(GLThread.GLSurfaceInfo surfaceInfo, WeakReference glSurfaceViewWeakRef) { + mSurfaceInfo = surfaceInfo; mGLSurfaceViewWeakRef = glSurfaceViewWeakRef; } @@ -1048,17 +767,13 @@ public void start() { if (LOG_EGL) { Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId()); } - /* - * Get an EGL instance - */ - mEgl = (EGL10) EGLContext.getEGL(); /* * Get to the default display. */ - mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); - if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { + if (mEglDisplay == EGL14.EGL_NO_DISPLAY) { throw new RuntimeException("eglGetDisplay failed"); } @@ -1066,23 +781,24 @@ public void start() { * We can now initialize EGL for that display */ int[] version = new int[2]; - if(!mEgl.eglInitialize(mEglDisplay, version)) { + if(!EGL14.eglInitialize(mEglDisplay, version, 0, version, 1)) { throw new RuntimeException("eglInitialize failed"); } + Log.d(TAG, "Initialized egl with version: " + Arrays.toString(version)); GLSurfaceView view = mGLSurfaceViewWeakRef.get(); if (view == null) { mEglConfig = null; mEglContext = null; } else { - mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay); + mEglConfig = view.mEGLConfigChooser.chooseConfig(mEglDisplay); /* * Create an EGL context. We want to do this as rarely as we can, because an * EGL context is a somewhat heavy object. */ - mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig); + mEglContext = view.mEGLContextFactory.createContext(mEglDisplay, mEglConfig); } - if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) { + if (mEglContext == null || mEglContext == EGL14.EGL_NO_CONTEXT) { mEglContext = null; throwEglException("createContext"); } @@ -1099,16 +815,13 @@ public void start() { * * @return true if the surface was created successfully. */ - public boolean createSurface() { + public boolean createSurface(boolean requireMakeCurrent) { if (LOG_EGL) { Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getId()); } /* * Check preconditions. */ - if (mEgl == null) { - throw new RuntimeException("egl not initialized"); - } if (mEglDisplay == null) { throw new RuntimeException("eglDisplay not initialized"); } @@ -1127,73 +840,103 @@ public boolean createSurface() { */ GLSurfaceView view = mGLSurfaceViewWeakRef.get(); if (view != null) { - mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl, + mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface( mEglDisplay, mEglConfig, view.getHolder()); } else { mEglSurface = null; } - if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { - int error = mEgl.eglGetError(); - if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { + if (mEglSurface == null || mEglSurface == EGL14.EGL_NO_SURFACE) { + int error = EGL14.eglGetError(); + if (error == EGL14.EGL_BAD_NATIVE_WINDOW) { Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); } return false; } + if (!makeEglCurrent() && requireMakeCurrent) { + return false; + } + + return true; + } + + public void releaseEglCurrent() { + if (LOG_EGL) { + Log.w("EglHelper", "releaseEglCurrent() tid=" + Thread.currentThread().getId()); + } + if (mEglDisplay == null) { + return; + } + EGL14.eglMakeCurrent(mEglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); + } + + public boolean makeEglCurrent() { + if (LOG_EGL) { + Log.w("EglHelper", "makeEglCurrent() tid=" + Thread.currentThread().getId()); + } + /* + * Check preconditions. + */ + if (mEglDisplay == null) { + return false; + } + if (mEglSurface == null) { + return false; + } + if (mEglContext == null) { + return false; + } /* * Before we can issue GL commands, we need to make sure * the context is current and bound to a surface. */ - if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { + if (EGL14.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { + return true; + } else { /* * Could not make the context current, probably because the underlying * SurfaceView surface has been destroyed. */ - logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError()); + logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", EGL14.eglGetError()); return false; } - - return true; - } - - /** - * Create a GL object for the current EGL context. - * @return - */ - GL createGL() { - - GL gl = mEglContext.getGL(); - GLSurfaceView view = mGLSurfaceViewWeakRef.get(); - if (view != null) { - if (view.mGLWrapper != null) { - gl = view.mGLWrapper.wrap(gl); - } - - if ((view.mDebugFlags & (DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS)) != 0) { - int configFlags = 0; - Writer log = null; - if ((view.mDebugFlags & DEBUG_CHECK_GL_ERROR) != 0) { - configFlags |= GLDebugHelper.CONFIG_CHECK_GL_ERROR; - } - if ((view.mDebugFlags & DEBUG_LOG_GL_CALLS) != 0) { - log = new LogWriter(); - } - gl = GLDebugHelper.wrap(gl, configFlags, log); - } - } - return gl; } /** * Display the current render surface. * @return the EGL error code from eglSwapBuffers. */ - public int swap() { - if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { - return mEgl.eglGetError(); + public void swap() { + if (mEglDisplay == null || mEglSurface == null) { + return; + } + + if (! EGL14.eglSwapBuffers(mEglDisplay, mEglSurface)) { + int swapError = EGL14.eglGetError(); + switch (swapError) { + case EGL14.EGL_SUCCESS: + break; + case EGL14.EGL_CONTEXT_LOST: + if (LOG_SURFACE) { + Log.i("GLThread", "egl context lost"); + } + mSurfaceInfo.mFrameParams.lostEglContext = true; + break; + default: + // Other errors typically mean that the current surface is bad, + // probably because the SurfaceView surface has been destroyed, + // but we haven't been notified yet. + // Log the error to help developers understand why rendering stopped. + EglHelper.logEglErrorAsWarning("GLThread", "eglSwapBuffers", swapError); + + synchronized (sGLThreadManager) { + mSurfaceInfo.mSurfaceIsBad = true; + sGLThreadManager.notifyAll(); + } + break; + } } - return EGL10.EGL_SUCCESS; } public void destroySurface() { @@ -1204,13 +947,11 @@ public void destroySurface() { } private void destroySurfaceImp() { - if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) { - mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_CONTEXT); + if (mEglSurface != null && mEglSurface != EGL14.EGL_NO_SURFACE) { + releaseEglCurrent(); GLSurfaceView view = mGLSurfaceViewWeakRef.get(); if (view != null) { - view.mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface); + view.mEGLWindowSurfaceFactory.destroySurface(mEglDisplay, mEglSurface); } mEglSurface = null; } @@ -1223,18 +964,18 @@ public void finish() { if (mEglContext != null) { GLSurfaceView view = mGLSurfaceViewWeakRef.get(); if (view != null) { - view.mEGLContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext); + view.mEGLContextFactory.destroyContext(mEglDisplay, mEglContext); } mEglContext = null; } if (mEglDisplay != null) { - mEgl.eglTerminate(mEglDisplay); + EGL14.eglTerminate(mEglDisplay); mEglDisplay = null; } } private void throwEglException(String function) { - throwEglException(function, mEgl.eglGetError()); + throwEglException(function, EGL14.eglGetError()); } public static void throwEglException(String function, int error) { @@ -1251,11 +992,11 @@ public static void logEglErrorAsWarning(String tag, String function, int error) } public static String formatEglError(String function, int error) { - return function + " failed: " + EGLLogWrapper.getErrorString(error); + return function + " failed: " + GLUtils.getErrorString(error); } - private WeakReference mGLSurfaceViewWeakRef; - EGL10 mEgl; + private final GLThread.GLSurfaceInfo mSurfaceInfo; + private final WeakReference mGLSurfaceViewWeakRef; EGLDisplay mEglDisplay; EGLSurface mEglSurface; EGLConfig mEglConfig; @@ -1263,6 +1004,7 @@ public static String formatEglError(String function, int error) { } + // -- GODOT start -- /** * A generic GL Thread. Takes care of initializing EGL and GL. Delegates * to a Renderer instance to do the actual drawing. Can be configured to @@ -1272,15 +1014,13 @@ public static String formatEglError(String function, int error) { * sGLThreadManager object. This avoids multiple-lock ordering issues. * */ - static class GLThread extends Thread { - GLThread(WeakReference glSurfaceViewWeakRef) { - super(); - mWidth = 0; - mHeight = 0; + static class GLThread extends RenderThread { + GLThread(GodotRenderer renderer) { + super("GLThread"); + mRenderer = renderer; mRequestRender = true; - mRenderMode = RENDERMODE_CONTINUOUSLY; + mRenderMode = Renderer.RenderMode.CONTINUOUSLY; mWantRenderNotification = false; - mGLSurfaceViewWeakRef = glSurfaceViewWeakRef; } @Override @@ -1304,9 +1044,8 @@ public void run() { * synchronized(sGLThreadManager) block. */ private void stopEglSurfaceLocked() { - if (mHaveEglSurface) { - mHaveEglSurface = false; - mEglHelper.destroySurface(); + if (mRegisteredGLSurface != null) { + mRegisteredGLSurface.stopEglSurfaceLocked(); } } @@ -1315,30 +1054,30 @@ private void stopEglSurfaceLocked() { * synchronized(sGLThreadManager) block. */ private void stopEglContextLocked() { - if (mHaveEglContext) { - mEglHelper.finish(); - mHaveEglContext = false; + boolean hadEglContext = false; + if (mRegisteredGLSurface != null) { + hadEglContext = mRegisteredGLSurface.stopEglContextLocked(); + } + if (hadEglContext) { sGLThreadManager.releaseEglContextLocked(this); } } private void guardedRun() throws InterruptedException { - mEglHelper = new EglHelper(mGLSurfaceViewWeakRef); - mHaveEglContext = false; - mHaveEglSurface = false; mWantRenderNotification = false; try { - GL10 gl = null; - boolean createEglContext = false; - boolean createEglSurface = false; - boolean createGlInterface = false; - boolean lostEglContext = false; - boolean sizeChanged = false; + synchronized (sGLThreadManager) { + Log.d("GLThread", "Starting render thread"); + mRenderer.onRenderThreadStarting(); + } + + if (mRegisteredGLSurface != null) { + mRegisteredGLSurface.resetFrameParams(); + } + boolean wantRenderNotification = false; boolean doRenderNotification = false; - boolean askedToReleaseEglContext = false; - int w = 0; - int h = 0; + Runnable event = null; Runnable finishDrawingRunnable = null; @@ -1373,57 +1112,45 @@ private void guardedRun() throws InterruptedException { stopEglSurfaceLocked(); stopEglContextLocked(); mShouldReleaseEglContext = false; - askedToReleaseEglContext = true; + if (mRegisteredGLSurface != null) { + mRegisteredGLSurface.mFrameParams.askedToReleaseEglContext = true; + } } + boolean notifyAll = false; // Have we lost the EGL context? - if (lostEglContext) { - stopEglSurfaceLocked(); - stopEglContextLocked(); - lostEglContext = false; - } - - // When pausing, release the EGL surface: - if (pausing && mHaveEglSurface) { - if (LOG_SURFACE) { - Log.i("GLThread", "releasing EGL surface because paused tid=" + getId()); + if (mRegisteredGLSurface != null) { + if (mRegisteredGLSurface.mFrameParams.lostEglContext) { + mRegisteredGLSurface.stopEglSurfaceLocked(); + mRegisteredGLSurface.stopEglContextLocked(); + notifyAll = true; + mRegisteredGLSurface.mFrameParams.lostEglContext = false; } - stopEglSurfaceLocked(); - } - // When pausing, optionally release the EGL Context: - if (pausing && mHaveEglContext) { - GLSurfaceView view = mGLSurfaceViewWeakRef.get(); - boolean preserveEglContextOnPause = view == null ? - false : view.mPreserveEGLContextOnPause; - if (!preserveEglContextOnPause) { - stopEglContextLocked(); + // When pausing, release the EGL surface: + if (pausing && mRegisteredGLSurface.mHaveEglSurface) { if (LOG_SURFACE) { - Log.i("GLThread", "releasing EGL context because paused tid=" + getId()); + Log.i("GLThread", "releasing EGL surface because paused tid=" + getId()); } + mRegisteredGLSurface.stopEglSurfaceLocked(); } - } - // Have we lost the SurfaceView surface? - if ((! mHasSurface) && (! mWaitingForSurface)) { - if (LOG_SURFACE) { - Log.i("GLThread", "noticed surfaceView surface lost tid=" + getId()); - } - if (mHaveEglSurface) { - stopEglSurfaceLocked(); + // When pausing, optionally release the EGL Context: + if (pausing && mRegisteredGLSurface.mHaveEglContext) { + GLSurfaceView view = mRegisteredGLSurface.mGLSurfaceViewWeakRef.get(); + boolean preserveEglContextOnPause = view != null && view.mPreserveEGLContextOnPause; + if (!preserveEglContextOnPause) { + mRegisteredGLSurface.stopEglContextLocked(); + notifyAll = true; + if (LOG_SURFACE) { + Log.i("GLThread", "releasing EGL context because paused tid=" + getId()); + } + } } - mWaitingForSurface = true; - mSurfaceIsBad = false; - sGLThreadManager.notifyAll(); } - - // Have we acquired the surface view surface? - if (mHasSurface && mWaitingForSurface) { - if (LOG_SURFACE) { - Log.i("GLThread", "noticed surfaceView surface acquired tid=" + getId()); - } - mWaitingForSurface = false; + if (notifyAll) { sGLThreadManager.notifyAll(); + notifyAll = false; } if (doRenderNotification) { @@ -1445,35 +1172,34 @@ private void guardedRun() throws InterruptedException { if (readyToDraw()) { // If we don't have an EGL context, try to acquire one. - if (! mHaveEglContext) { - if (askedToReleaseEglContext) { - askedToReleaseEglContext = false; + if (! mRegisteredGLSurface.mHaveEglContext) { + if (mRegisteredGLSurface.mFrameParams.askedToReleaseEglContext) { + mRegisteredGLSurface.mFrameParams.askedToReleaseEglContext = false; } else { try { - mEglHelper.start(); + mRegisteredGLSurface.mEglHelper.start(); } catch (RuntimeException t) { sGLThreadManager.releaseEglContextLocked(this); throw t; } - mHaveEglContext = true; - createEglContext = true; + mRegisteredGLSurface.mHaveEglContext = true; + mRegisteredGLSurface.mFrameParams.createEglContext = true; sGLThreadManager.notifyAll(); } } - if (mHaveEglContext && !mHaveEglSurface) { - mHaveEglSurface = true; - createEglSurface = true; - createGlInterface = true; - sizeChanged = true; + if (mRegisteredGLSurface.mHaveEglContext && !mRegisteredGLSurface.mHaveEglSurface) { + mRegisteredGLSurface.mHaveEglSurface = true; + mRegisteredGLSurface.mFrameParams.createEglSurface = true; + mRegisteredGLSurface.mFrameParams.sizeChanged = true; } - if (mHaveEglSurface) { - if (mSizeChanged) { - sizeChanged = true; - w = mWidth; - h = mHeight; + if (mRegisteredGLSurface.mHaveEglSurface) { + if (mRegisteredGLSurface.mSizeChanged) { + mRegisteredGLSurface.mFrameParams.sizeChanged = true; + mRegisteredGLSurface.mFrameParams.w = mRegisteredGLSurface.mWidth; + mRegisteredGLSurface.mFrameParams.h = mRegisteredGLSurface.mHeight; mWantRenderNotification = true; if (LOG_SURFACE) { Log.i("GLThread", @@ -1482,10 +1208,17 @@ private void guardedRun() throws InterruptedException { } // Destroy and recreate the EGL surface. - createEglSurface = true; + mRegisteredGLSurface.mFrameParams.createEglSurface = true; + + mRegisteredGLSurface.mSizeChanged = false; + } - mSizeChanged = false; + if (mRegisteredGLSurface.mSurfaceIsBad) { + // Destroy and recreate the EGL surface. + mRegisteredGLSurface.mFrameParams.createEglSurface = true; + mRegisteredGLSurface.mSurfaceIsBad = false; } + mRequestRender = false; sGLThreadManager.notifyAll(); if (mWantRenderNotification) { @@ -1503,18 +1236,19 @@ private void guardedRun() throws InterruptedException { } // By design, this is the only place in a GLThread thread where we wait(). if (LOG_THREADS) { - Log.i("GLThread", "waiting tid=" + getId() - + " mHaveEglContext: " + mHaveEglContext - + " mHaveEglSurface: " + mHaveEglSurface - + " mFinishedCreatingEglSurface: " + mFinishedCreatingEglSurface - + " mPaused: " + mPaused - + " mHasSurface: " + mHasSurface - + " mSurfaceIsBad: " + mSurfaceIsBad - + " mWaitingForSurface: " + mWaitingForSurface - + " mWidth: " + mWidth - + " mHeight: " + mHeight - + " mRequestRender: " + mRequestRender - + " mRenderMode: " + mRenderMode); + StringBuilder logMessage = new StringBuilder("waiting tid=").append(getId()) + .append(" mPaused: ").append(mPaused) + .append(" mRequestRender: ").append(mRequestRender) + .append(" mRenderMode: ").append(mRenderMode); + if (mRegisteredGLSurface != null) { + logMessage.append(" mHaveEglContext: ").append(mRegisteredGLSurface.mHaveEglContext) + .append(" mHaveEglSurface: ").append(mRegisteredGLSurface.mHaveEglSurface) + .append(" mFinishedCreatingEglSurface: ").append(mRegisteredGLSurface.mFinishedCreatingEglSurface) + .append(" mSurfaceIsBad: ").append(mRegisteredGLSurface.mSurfaceIsBad) + .append(" mWidth: ").append(mRegisteredGLSurface.mWidth) + .append(" mHeight: ").append(mRegisteredGLSurface.mHeight); + } + Log.i("GLThread", logMessage.toString()); } sGLThreadManager.wait(); } @@ -1526,103 +1260,69 @@ private void guardedRun() throws InterruptedException { continue; } - if (createEglSurface) { - if (LOG_SURFACE) { - Log.w("GLThread", "egl createSurface"); - } - if (mEglHelper.createSurface()) { - synchronized(sGLThreadManager) { - mFinishedCreatingEglSurface = true; - sGLThreadManager.notifyAll(); + if (mRegisteredGLSurface != null) { + boolean eglResourcesChanged = false; + + if (mRegisteredGLSurface.mFrameParams.createEglSurface) { + if (LOG_SURFACE) { + Log.w("GLThread", "egl createSurface"); } - } else { - synchronized(sGLThreadManager) { - mFinishedCreatingEglSurface = true; - mSurfaceIsBad = true; - sGLThreadManager.notifyAll(); + if (mRegisteredGLSurface.mEglHelper.createSurface(!mSeparateRenderThreadEnabled)) { + synchronized (sGLThreadManager) { + mRegisteredGLSurface.mFinishedCreatingEglSurface = true; + sGLThreadManager.notifyAll(); + } + } else { + synchronized (sGLThreadManager) { + mRegisteredGLSurface.mFinishedCreatingEglSurface = true; + mRegisteredGLSurface.mSurfaceIsBad = true; + sGLThreadManager.notifyAll(); + } + continue; } - continue; + eglResourcesChanged = true; + mRegisteredGLSurface.mFrameParams.createEglSurface = false; } - createEglSurface = false; - } - - if (createGlInterface) { - gl = (GL10) mEglHelper.createGL(); - createGlInterface = false; - } - - // -- GODOT start -- - if (createEglContext) { - if (LOG_RENDERER) { - Log.w("GLThread", "onSurfaceCreated"); - } - GLSurfaceView view = mGLSurfaceViewWeakRef.get(); - if (view != null) { + if (mRegisteredGLSurface.mFrameParams.createEglContext) { + if (LOG_RENDERER) { + Log.w("GLThread", "onSurfaceCreated"); + } try { - view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig); + mRenderer.onRenderSurfaceCreated(null); } finally { } + mRegisteredGLSurface.mFrameParams.createEglContext = false; } - createEglContext = false; - } - if (sizeChanged) { - if (LOG_RENDERER) { - Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")"); - } - GLSurfaceView view = mGLSurfaceViewWeakRef.get(); - if (view != null) { + if (mRegisteredGLSurface.mFrameParams.sizeChanged) { + if (LOG_RENDERER) { + Log.w("GLThread", "onSurfaceChanged(" + mRegisteredGLSurface.mFrameParams.w + ", " + mRegisteredGLSurface.mFrameParams.h + ")"); + } try { - view.mRenderer.onSurfaceChanged(gl, w, h); + mRenderer.onRenderSurfaceChanged(null, mRegisteredGLSurface.mFrameParams.w, mRegisteredGLSurface.mFrameParams.h); } finally { } + mRegisteredGLSurface.mFrameParams.sizeChanged = false; + } + + if (eglResourcesChanged) { + mRenderer.onRenderEglResourcesChanged(mRegisteredGLSurface.mEglHelper.mEglDisplay, mRegisteredGLSurface.mEglHelper.mEglSurface, mRegisteredGLSurface.mEglHelper.mEglContext, mRegisteredGLSurface.mEglHelper.mEglConfig); } - sizeChanged = false; } - boolean swapBuffers = false; if (LOG_RENDERER_DRAW_FRAME) { Log.w("GLThread", "onDrawFrame tid=" + getId()); } { - GLSurfaceView view = mGLSurfaceViewWeakRef.get(); - if (view != null) { - try { - swapBuffers = view.mRenderer.onDrawFrame(gl); - if (finishDrawingRunnable != null) { - finishDrawingRunnable.run(); - finishDrawingRunnable = null; - } - } finally {} - } - } - if (swapBuffers) { - int swapError = mEglHelper.swap(); - switch (swapError) { - case EGL10.EGL_SUCCESS: - break; - case EGL11.EGL_CONTEXT_LOST: - if (LOG_SURFACE) { - Log.i("GLThread", "egl context lost tid=" + getId()); - } - lostEglContext = true; - break; - default: - // Other errors typically mean that the current surface is bad, - // probably because the SurfaceView surface has been destroyed, - // but we haven't been notified yet. - // Log the error to help developers understand why rendering stopped. - EglHelper.logEglErrorAsWarning("GLThread", "eglSwapBuffers", swapError); - - synchronized (sGLThreadManager) { - mSurfaceIsBad = true; - sGLThreadManager.notifyAll(); - } - break; - } + try { + mRenderer.onRenderDrawFrame(); + if (finishDrawingRunnable != null) { + finishDrawingRunnable.run(); + finishDrawingRunnable = null; + } + } finally {} } - // -- GODOT end -- if (wantRenderNotification) { doRenderNotification = true; @@ -1636,10 +1336,7 @@ private void guardedRun() throws InterruptedException { */ synchronized (sGLThreadManager) { Log.d("GLThread", "Exiting render thread"); - GLSurfaceView view = mGLSurfaceViewWeakRef.get(); - if (view != null) { - view.mRenderer.onRenderThreadExiting(); - } + mRenderer.onRenderThreadExiting(); stopEglSurfaceLocked(); stopEglContextLocked(); @@ -1647,32 +1344,32 @@ private void guardedRun() throws InterruptedException { } } - public boolean ableToDraw() { - return mHaveEglContext && mHaveEglSurface && readyToDraw(); - } - private boolean readyToDraw() { - return (!mPaused) && mHasSurface && (!mSurfaceIsBad) - && (mWidth > 0) && (mHeight > 0) - && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY)); + boolean threadReadyToDraw = (!mPaused) && (mRequestRender || (mRenderMode == Renderer.RenderMode.CONTINUOUSLY)); + if (!threadReadyToDraw) { + return false; + } + + return mRegisteredGLSurface != null; } - public void setRenderMode(int renderMode) { - if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY)) ) { - throw new IllegalArgumentException("renderMode"); - } + @Override + public void setRenderMode(@NonNull Renderer.RenderMode renderMode) { synchronized(sGLThreadManager) { mRenderMode = renderMode; sGLThreadManager.notifyAll(); } } - public int getRenderMode() { + @NonNull + @Override + public Renderer.RenderMode getRenderMode() { synchronized(sGLThreadManager) { return mRenderMode; } } + @Override public void requestRender() { synchronized(sGLThreadManager) { mRequestRender = true; @@ -1716,27 +1413,103 @@ public void requestRenderAndNotify(Runnable finishDrawing) { } } - public void surfaceCreated() { + void registerGLSurfaceInfo(SurfaceHolder holder, WeakReference surfaceViewWeakRef) { + synchronized(sGLThreadManager) { + if (LOG_THREADS) { + Log.i("GLThread", "registering GLSurfaceInfo with tid=" + getId()); + } + + GLSurfaceInfo surfaceInfo = new GLSurfaceInfo(surfaceViewWeakRef); + surfaceInfo.mFinishedCreatingEglSurface = false; + mRegisteredGLSurface = surfaceInfo; + + sGLThreadManager.notifyAll(); + } + } + + void unregisterGLSurfaceInfo(int id) { + synchronized(sGLThreadManager) { + if (LOG_THREADS) { + Log.i("GLThread", "Unregistering GLSurfaceInfo with tid=" + getId()); + } + + GLSurfaceInfo surfaceInfo = mRegisteredGLSurface; + if (surfaceInfo == null) { + return; + } + + mRegisteredGLSurface = null; + + surfaceInfo.stopEglSurfaceLocked(); + surfaceInfo.stopEglContextLocked(); + + sGLThreadManager.notifyAll(); + } + } + + @Override + public void surfaceCreated(SurfaceHolder holder, WeakReference surfaceViewWeakRef) { synchronized(sGLThreadManager) { if (LOG_THREADS) { Log.i("GLThread", "surfaceCreated tid=" + getId()); } - mHasSurface = true; - mFinishedCreatingEglSurface = false; + + GLSurfaceInfo surfaceInfo = mRegisteredGLSurface; + if (surfaceInfo == null) { + return; + } + + // This will cause the egl surface to be recreated. + surfaceInfo.mSizeChanged = true; + + mRequestRender = true; + mRenderComplete = false; + + // If we are already on the GL thread, this means a client callback + // has caused reentrancy, for example via updating the SurfaceView parameters. + // We need to process the size change eventually though and update our EGLSurface. + // So we set the parameters and return so they can be processed on our + // next iteration. + if (Thread.currentThread() == this) { + return; + } + sGLThreadManager.notifyAll(); } } - public void surfaceDestroyed() { + @Override + public void surfaceDestroyed(SurfaceHolder holder) { synchronized(sGLThreadManager) { if (LOG_THREADS) { Log.i("GLThread", "surfaceDestroyed tid=" + getId()); } - mHasSurface = false; + GLSurfaceInfo surfaceInfo = mRegisteredGLSurface; + if (surfaceInfo == null) { + return; + } + + // This will cause the egl surface to be recreated. Without a valid surface object, we'll use an + // offscreen buffer instead to create the egl surface. + surfaceInfo.mSizeChanged = true; + + mRequestRender = true; + mRenderComplete = false; + + // If we are already on the GL thread, this means a client callback + // has caused reentrancy, for example via updating the SurfaceView parameters. + // We need to process the size change eventually though and update our EGLSurface. + // So we set the parameters and return so they can be processed on our + // next iteration. + if (Thread.currentThread() == this) { + return; + } + sGLThreadManager.notifyAll(); } } + @Override public void onPause() { synchronized (sGLThreadManager) { if (LOG_PAUSE_RESUME) { @@ -1747,6 +1520,7 @@ public void onPause() { } } + @Override public void onResume() { synchronized (sGLThreadManager) { if (LOG_PAUSE_RESUME) { @@ -1759,11 +1533,16 @@ public void onResume() { } } - public void onWindowResize(int w, int h) { + @Override + public void surfaceChanged(SurfaceHolder holder, int w, int h) { synchronized (sGLThreadManager) { - mWidth = w; - mHeight = h; - mSizeChanged = true; + if (mRegisteredGLSurface == null) { + return; + } + mRegisteredGLSurface.mWidth = w; + mRegisteredGLSurface.mHeight = h; + mRegisteredGLSurface.mSizeChanged = true; + mRequestRender = true; mRenderComplete = false; @@ -1805,6 +1584,7 @@ public void requestReleaseEglContextLocked() { * Queue an "event" to be run on the GL rendering thread. * @param r the runnable to be run on the GL rendering thread. */ + @Override public void queueEvent(Runnable r) { if (r == null) { throw new IllegalArgumentException("r must not be null"); @@ -1815,41 +1595,150 @@ public void queueEvent(Runnable r) { } } + @Override + public void setSeparateRenderThreadEnabled(boolean enabled) { + synchronized(sGLThreadManager) { + mSeparateRenderThreadEnabled = enabled; + } + } + + @Override + public boolean makeEglCurrent(int id) { + synchronized (sGLThreadManager) { + GLSurfaceInfo surfaceInfo = mRegisteredGLSurface; + if (surfaceInfo == null) { + return false; + } + + boolean result = surfaceInfo.mEglHelper.makeEglCurrent(); + sGLThreadManager.notifyAll(); + + return result; + } + } + + @Override + public void eglSwapBuffers(int id) { + synchronized (sGLThreadManager) { + GLSurfaceInfo surfaceInfo = mRegisteredGLSurface; + if (surfaceInfo == null) { + return; + } + + surfaceInfo.mEglHelper.swap(); + sGLThreadManager.notifyAll(); + } + } + + @Override + public void releaseCurrentGLWindow(int id) { + synchronized (sGLThreadManager) { + GLSurfaceInfo surfaceInfo = mRegisteredGLSurface; + if (surfaceInfo == null) { + return; + } + + surfaceInfo.mEglHelper.releaseEglCurrent(); + sGLThreadManager.notifyAll(); + } + } + // Once the thread is started, all accesses to the following member // variables are protected by the sGLThreadManager monitor private boolean mShouldExit; private boolean mExited; private boolean mRequestPaused; private boolean mPaused; - private boolean mHasSurface; - private boolean mSurfaceIsBad; - private boolean mWaitingForSurface; - private boolean mHaveEglContext; - private boolean mHaveEglSurface; - private boolean mFinishedCreatingEglSurface; private boolean mShouldReleaseEglContext; - private int mWidth; - private int mHeight; - private int mRenderMode; + private Renderer.RenderMode mRenderMode; private boolean mRequestRender; private boolean mWantRenderNotification; private boolean mRenderComplete; - private ArrayList mEventQueue = new ArrayList(); - private boolean mSizeChanged = true; + private boolean mSeparateRenderThreadEnabled; + private final ArrayList mEventQueue = new ArrayList(); private Runnable mFinishDrawingRunnable = null; + private final GodotRenderer mRenderer; - // End of member variables protected by the sGLThreadManager monitor. + private GLSurfaceInfo mRegisteredGLSurface = null; - private EglHelper mEglHelper; + // End of member variables protected by the sGLThreadManager monitor. /** - * 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. + * Stores set of info for each registered GLSurface. */ - private WeakReference mGLSurfaceViewWeakRef; + static class GLSurfaceInfo { + private boolean mSurfaceIsBad; + private boolean mHaveEglContext; + private boolean mHaveEglSurface; + private boolean mFinishedCreatingEglSurface; + private int mWidth = 0; + private int mHeight = 0; + private boolean mSizeChanged = true; + private final EglHelper mEglHelper; + + /** + * 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. + */ + private final WeakReference mGLSurfaceViewWeakRef; + + private final FrameParams mFrameParams = new FrameParams(); + + GLSurfaceInfo(WeakReference glSurfaceViewWeakRef) { + mWidth = 0; + mHeight = 0; + mGLSurfaceViewWeakRef = glSurfaceViewWeakRef; + mEglHelper = new EglHelper(this, glSurfaceViewWeakRef); + mHaveEglContext = false; + mHaveEglSurface = false; + } + + boolean ableToDraw() { + return mHaveEglContext && mHaveEglSurface && !mSurfaceIsBad; + } + + void stopEglSurfaceLocked() { + if (mHaveEglSurface) { + mHaveEglSurface = false; + mEglHelper.destroySurface(); + } + } + + boolean stopEglContextLocked() { + if (mHaveEglContext) { + mEglHelper.finish(); + mHaveEglContext = false; + return true; + } + return false; + } + + void resetFrameParams() { + mFrameParams.createEglContext = false; + mFrameParams.createEglSurface = false; + mFrameParams.lostEglContext = false; + mFrameParams.sizeChanged = false; + mFrameParams.w = 0; + mFrameParams.h = 0; + mFrameParams.askedToReleaseEglContext = false; + } + /** + * Stores set of parameters used during render thread's frame run. + */ + static class FrameParams { + boolean createEglContext = false; + boolean createEglSurface = false; + boolean lostEglContext = false; + boolean sizeChanged = false; + int w = 0; + int h = 0; + boolean askedToReleaseEglContext = false; + } + } } + // -- GODOT end -- static class LogWriter extends Writer { @@ -1874,18 +1763,18 @@ static class LogWriter extends Writer { } private void flushBuilder() { - if (mBuilder.length() > 0) { + if (!mBuilder.isEmpty()) { Log.v("GLSurfaceView", mBuilder.toString()); mBuilder.delete(0, mBuilder.length()); } } - private StringBuilder mBuilder = new StringBuilder(); + private final StringBuilder mBuilder = new StringBuilder(); } private void checkRenderThreadState() { - if (mGLThread != null) { + if (mRenderer != null) { throw new IllegalStateException( "setRenderer has already been called for this instance."); } @@ -1915,13 +1804,11 @@ public void releaseEglContextLocked(GLThread thread) { private final WeakReference mThisWeakRef = new WeakReference(this); - private GLThread mGLThread; - private Renderer mRenderer; + private GodotRenderer mRenderer; private boolean mDetached; private EGLConfigChooser mEGLConfigChooser; private EGLContextFactory mEGLContextFactory; private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory; - private GLWrapper mGLWrapper; private int mDebugFlags; private int mEGLContextClientVersion; private boolean mPreserveEGLContextOnPause; diff --git a/platform/android/java/lib/src/org/godotengine/godot/render/GodotGLRenderView.kt b/platform/android/java/lib/src/org/godotengine/godot/render/GodotGLRenderView.kt new file mode 100644 index 000000000000..958443b06a45 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/render/GodotGLRenderView.kt @@ -0,0 +1,174 @@ +/**************************************************************************/ +/* GodotGLRenderView.kt */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +package org.godotengine.godot.render + +import android.annotation.SuppressLint +import android.graphics.PixelFormat +import android.view.KeyEvent +import android.view.MotionEvent +import android.view.PointerIcon +import org.godotengine.godot.Godot +import org.godotengine.godot.GodotRenderView +import org.godotengine.godot.input.GodotInputHandler +import org.godotengine.godot.xr.XRMode + +/** + * A simple GLSurfaceView sub-class that demonstrate how to perform + * OpenGL ES 2.0 rendering into a GL Surface. Note the following important + * details: + * + * - The class must use a custom context factory to enable 2.0 rendering. + * See ContextFactory class definition below. + * + * - The class must use a custom EGLConfigChooser to be able to select + * an EGLConfig that supports 3.0. This is done by providing a config + * specification to eglChooseConfig() that has the attribute + * EGL10.ELG_RENDERABLE_TYPE containing the EGL_OPENGL_ES2_BIT flag + * set. See ConfigChooser class definition below. + * + * - The class must select the surface's format, then choose an EGLConfig + * that matches it exactly (with regards to red/green/blue/alpha channels + * bit depths). Failure to do so would result in an EGL_BAD_MATCH error. + */ +internal class GodotGLRenderView( + private val godot: Godot, + godotRenderer: GodotRenderer, + private val inputHandler: GodotInputHandler, + xrMode: XRMode, + useDebugOpengl: Boolean, + shouldBeTranslucent: Boolean +) : GLSurfaceView( + godot.context +), GodotRenderView { + init { + pointerIcon = PointerIcon.getSystemIcon(context, PointerIcon.TYPE_DEFAULT) + init(xrMode, godotRenderer, shouldBeTranslucent, useDebugOpengl) + } + + override fun getView() = this + + override fun getInputHandler() = inputHandler + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent): Boolean { + super.onTouchEvent(event) + return inputHandler.onTouchEvent(event) + } + + override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { + return inputHandler.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event) + } + + override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { + return inputHandler.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event) + } + + override fun onGenericMotionEvent(event: MotionEvent): Boolean { + return inputHandler.onGenericMotionEvent(event) || super.onGenericMotionEvent(event) + } + + override fun onCapturedPointerEvent(event: MotionEvent): Boolean { + return inputHandler.onGenericMotionEvent(event) + } + + override fun onPointerCaptureChange(hasCapture: Boolean) { + super.onPointerCaptureChange(hasCapture) + inputHandler.onPointerCaptureChange(hasCapture) + } + + override fun requestPointerCapture() { + if (godot.canCapturePointer()) { + super.requestPointerCapture() + inputHandler.onPointerCaptureChange(true) + } + } + + override fun releasePointerCapture() { + super.releasePointerCapture() + inputHandler.onPointerCaptureChange(false) + } + + override fun onResolvePointerIcon(me: MotionEvent, pointerIndex: Int): PointerIcon { + return pointerIcon + } + + private fun init(xrMode: XRMode, renderer: GodotRenderer, translucent: Boolean, useDebugOpengl: Boolean) { + preserveEGLContextOnPause = true + isFocusableInTouchMode = true + when (xrMode) { + XRMode.OPENXR -> { + // Replace the default egl config chooser. + setEGLConfigChooser(OvrConfigChooser()) + + // Replace the default context factory. + setEGLContextFactory(OvrContextFactory()) + + // Replace the default window surface factory. + setEGLWindowSurfaceFactory(OvrWindowSurfaceFactory()) + } + + XRMode.REGULAR -> { + /* By default, GLSurfaceView() creates a RGB_565 opaque surface. + * If we want a translucent one, we should change the surface's + * format here, using PixelFormat.TRANSLUCENT for GL Surfaces + * is interpreted as any 32-bit surface with alpha by SurfaceFlinger. + */ + if (translucent) { + this.holder.setFormat(PixelFormat.TRANSLUCENT) + } + + /* Setup the context factory for 2.0 rendering. + * See ContextFactory class definition below + */ + setEGLContextFactory( + RegularContextFactory( + useDebugOpengl + ) + ) + + /* We need to choose an EGLConfig that matches the format of + * our surface exactly. This is going to be done in our + * custom config chooser. See ConfigChooser class definition + * below. + */ + setEGLConfigChooser( + RegularFallbackConfigChooser( + 8, 8, 8, 8, 24, 0, + RegularConfigChooser(8, 8, 8, 8, 16, 0) + ) + ) + } + } + + // Set the renderer responsible for frame rendering + setRenderer(renderer) + } +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/render/GodotRenderer.kt b/platform/android/java/lib/src/org/godotengine/godot/render/GodotRenderer.kt new file mode 100644 index 000000000000..bb460cc5504b --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/render/GodotRenderer.kt @@ -0,0 +1,272 @@ +/**************************************************************************/ +/* GodotRenderer.kt */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +@file:JvmName("GodotRenderer") + +package org.godotengine.godot.render + +import android.util.Log +import android.view.Surface +import org.godotengine.godot.GodotLib +import org.godotengine.godot.plugin.GodotPluginRegistry +import org.godotengine.godot.render.GLSurfaceView.GLThread +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; + +/** + * Responsible for setting up and driving Godot rendering logic. + * + *

Threading

+ * The renderer will create a separate render thread, so that rendering + * performance is decoupled from the UI thread. Clients typically need to + * communicate with the renderer from the UI thread, because that's where + * input events are received. Clients can communicate using any of the + * standard Java techniques for cross-thread communication, or they can + * use the [GodotRenderer.queueOnRenderThread] convenience method. + */ +internal class GodotRenderer(val useVulkan: Boolean) : Renderer { + companion object { + private val TAG = GodotRenderer::class.java.simpleName + } + + private val pluginRegistry: GodotPluginRegistry by lazy { + GodotPluginRegistry.getPluginRegistry() + } + + private var isStarted = false + private var glRendererJustResumed = false + + private var rendererResumed = false + + /** + * Thread used to drive the render logic. + */ + val renderThread: RenderThread by lazy { + if (useVulkan) { + VkThread(this) + } else { + GLThread(this) + } + } + + /** + * Start the renderer which starts the render thread which in turn causes the + * rendering to start. + * + *

+ * The following methods can only be called after + * startRenderer is called: + *

    + *
  • {@link #getRenderMode()} + *
  • {@link #requestRender()} + *
  • {@link #setRenderMode(int)} + *
+ */ + fun startRenderer() { + if (isStarted) { + return + } + isStarted = true + renderThread.start() + } + + fun queueOnRenderThread(runnable: Runnable) { + renderThread.queueEvent(runnable) + } + + override fun setRenderMode(renderMode: Renderer.RenderMode) { + renderThread.setRenderMode(renderMode) + } + + override fun getRenderMode(): Renderer.RenderMode { + return renderThread.getRenderMode() + } + + override fun requestRender() { + renderThread.requestRender() + } + + /** + * Called to resume the renderer. + *

+ * The renderer can be resumed either because the activity is resumed or because [Surface] to + * render onto just became available. + *

+ */ + private fun resumeRenderer() { + Log.d(TAG, "Resuming renderer") + + rendererResumed = true + if (useVulkan) { + GodotLib.onRendererResumed() + } else { + // For OpenGL, we defer invoking GodotLib.onRendererResumed() until the first draw frame call. + // This ensures we have a valid GL context and surface when we do so. + glRendererJustResumed = true + } + } + + /** + * Called to pause the renderer. + * + *

+ * The renderer can be paused either because the activity is paused or because there are + * no [Surface] to render on. + *

+ */ + private fun pauseRenderer() { + Log.d(TAG, "Pausing renderer") + + GodotLib.onRendererPaused() + rendererResumed = false + } + + /** + * Called when the rendering thread is starting and used as signal to complete engine setup. + */ + override fun onRenderThreadStarting() { + Log.d(TAG, "Starting Godot Engine render thread") + } + + /** + * Called when the rendering thread is exiting and used as signal to tear down the native logic. + */ + override fun onRenderThreadExiting() { + Log.d(TAG, "Destroying Godot Engine") + GodotLib.ondestroy() + } + + /** + * The Activity was resumed, let's resume the render thread. + */ + fun onActivityStarted() { + renderThread.onResume() + } + + /** + * The Activity was resumed, let's resume the renderer. + */ + fun onActivityResumed() { + queueOnRenderThread { + resumeRenderer() + GodotLib.focusin() + } + } + + /** + * Pauses the renderer. + */ + fun onActivityPaused() { + queueOnRenderThread { + GodotLib.focusout() + pauseRenderer() + } + } + + /** + * Pauses the render thread. + */ + fun onActivityStopped() { + renderThread.onPause() + } + + /** + * Destroy the render thread. + */ + fun onActivityDestroyed() { + renderThread.requestExitAndWait() + } + + /** + * Called to draw the current frame. + *

+ * This method is responsible for drawing the current frame. + *

+ * @param gl the GL interface. Use instanceof to + * test if the interface supports GL11 or higher interfaces. + * + * @return true if the buffers should be swapped, false otherwise. + */ + override fun onRenderDrawFrame() { + if (!useVulkan) { + // For OpenGL, we defer invoking GodotLib.onRendererResumed() until the first draw frame call. + // This ensures we have a valid GL context and surface when we do so. + if (glRendererJustResumed) { + GodotLib.onRendererResumed() + glRendererJustResumed = false + } + } + + GodotLib.step() + for (plugin in pluginRegistry.allPlugins) { + if (useVulkan) { + plugin.onVkDrawFrame() + } else { + plugin.onGLDrawFrame(null) + } + plugin.onRenderDrawFrame() + } + } + + override fun onRenderSurfaceChanged(surface: Surface?, width: Int, height: Int) { + Log.d(TAG, "Rendering surface changed: $surface, w-$width, h-$height") + GodotLib.resize(surface, width, height) + + for (plugin in pluginRegistry.allPlugins) { + if (useVulkan) { + plugin.onVkSurfaceChanged(surface, width, height) + } else { + plugin.onGLSurfaceChanged(null, width, height) + } + plugin.onRenderSurfaceChanged(surface, width, height) + } + } + + override fun onRenderSurfaceCreated(surface: Surface?) { + Log.d(TAG, "Rendering surface created: $surface") + GodotLib.newcontext(surface) + + for (plugin in pluginRegistry.allPlugins) { + if (useVulkan) { + plugin.onVkSurfaceCreated(surface) + } else { + plugin.onGLSurfaceCreated(null, null) + } + plugin.onRenderSurfaceCreated(surface) + } + } + + override fun onRenderEglResourcesChanged(display: EGLDisplay?, surface: EGLSurface?, context: EGLContext?, config: EGLConfig?) { + GodotLib.updateEglResources(display?.getNativeHandle() ?: 0L, surface?.getNativeHandle() ?: 0L, context?.getNativeHandle() ?: 0L, config?.getNativeHandle() ?: 0L); + } + +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/render/GodotVulkanRenderView.kt b/platform/android/java/lib/src/org/godotengine/godot/render/GodotVulkanRenderView.kt new file mode 100644 index 000000000000..e6eb81d9fe07 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/render/GodotVulkanRenderView.kt @@ -0,0 +1,108 @@ +/**************************************************************************/ +/* GodotVulkanRenderView.kt */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +package org.godotengine.godot.render + +import android.annotation.SuppressLint +import android.graphics.PixelFormat +import android.view.KeyEvent +import android.view.MotionEvent +import android.view.PointerIcon +import org.godotengine.godot.Godot +import org.godotengine.godot.GodotRenderView +import org.godotengine.godot.input.GodotInputHandler + +internal class GodotVulkanRenderView( + private val godot: Godot, + renderer: GodotRenderer, + private val mInputHandler: GodotInputHandler, + shouldBeTranslucent: Boolean +) : VkSurfaceView( + godot.context, + renderer +), GodotRenderView { + + init { + pointerIcon = PointerIcon.getSystemIcon(context, PointerIcon.TYPE_DEFAULT) + isFocusableInTouchMode = true + isClickable = false + + if (shouldBeTranslucent) { + this.holder.setFormat(PixelFormat.TRANSLUCENT) + } + } + + override fun getView() = this + + override fun getInputHandler() = mInputHandler + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent): Boolean { + super.onTouchEvent(event) + return mInputHandler.onTouchEvent(event) + } + + override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { + return mInputHandler.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event) + } + + override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { + return mInputHandler.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event) + } + + override fun onGenericMotionEvent(event: MotionEvent): Boolean { + return mInputHandler.onGenericMotionEvent(event) || super.onGenericMotionEvent(event) + } + + override fun onCapturedPointerEvent(event: MotionEvent): Boolean { + return mInputHandler.onGenericMotionEvent(event) + } + + override fun requestPointerCapture() { + if (godot.canCapturePointer()) { + super.requestPointerCapture() + mInputHandler.onPointerCaptureChange(true) + } + } + + override fun releasePointerCapture() { + super.releasePointerCapture() + mInputHandler.onPointerCaptureChange(false) + } + + override fun onPointerCaptureChange(hasCapture: Boolean) { + super.onPointerCaptureChange(hasCapture) + mInputHandler.onPointerCaptureChange(hasCapture) + } + + override fun onResolvePointerIcon(me: MotionEvent, pointerIndex: Int): PointerIcon { + return pointerIcon + } +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/render/OvrConfigChooser.java similarity index 76% rename from platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java rename to platform/android/java/lib/src/org/godotengine/godot/render/OvrConfigChooser.java index 942e57308cf2..a4e6b2998ba4 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java +++ b/platform/android/java/lib/src/org/godotengine/godot/render/OvrConfigChooser.java @@ -28,39 +28,36 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -package org.godotengine.godot.xr.ovr; - -import org.godotengine.godot.gl.GLSurfaceView; +package org.godotengine.godot.render; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLDisplay; import android.opengl.EGLExt; -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLDisplay; - /** * EGL config chooser for the Oculus Mobile VR SDK. */ -public class OvrConfigChooser implements GLSurfaceView.EGLConfigChooser { +class OvrConfigChooser implements GLSurfaceView.EGLConfigChooser { private static final int[] CONFIG_ATTRIBS = { - EGL10.EGL_RED_SIZE, 8, - EGL10.EGL_GREEN_SIZE, 8, - EGL10.EGL_BLUE_SIZE, 8, - EGL10.EGL_ALPHA_SIZE, 8, // Need alpha for the multi-pass timewarp compositor - EGL10.EGL_DEPTH_SIZE, 0, - EGL10.EGL_STENCIL_SIZE, 0, - EGL10.EGL_SAMPLES, 0, - EGL10.EGL_NONE + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_ALPHA_SIZE, 8, // Need alpha for the multi-pass timewarp compositor + EGL14.EGL_DEPTH_SIZE, 0, + EGL14.EGL_STENCIL_SIZE, 0, + EGL14.EGL_SAMPLES, 0, + EGL14.EGL_NONE }; @Override - public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + public EGLConfig chooseConfig(EGLDisplay display) { // Do NOT use eglChooseConfig, because the Android EGL code pushes in // multisample flags in eglChooseConfig if the user has selected the "force 4x // MSAA" option in settings, and that is completely wasted for our warp // target. int[] numConfig = new int[1]; - if (!egl.eglGetConfigs(display, null, 0, numConfig)) { + if (!EGL14.eglGetConfigs(display, null, 0, 0, numConfig, 0)) { throw new IllegalArgumentException("eglGetConfigs failed."); } @@ -70,29 +67,29 @@ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { } EGLConfig[] configs = new EGLConfig[configsCount]; - if (!egl.eglGetConfigs(display, configs, configsCount, numConfig)) { + if (!EGL14.eglGetConfigs(display, configs, 0, configsCount, numConfig, 0)) { throw new IllegalArgumentException("eglGetConfigs #2 failed."); } int[] value = new int[1]; for (EGLConfig config : configs) { - egl.eglGetConfigAttrib(display, config, EGL10.EGL_RENDERABLE_TYPE, value); + EGL14.eglGetConfigAttrib(display, config, EGL14.EGL_RENDERABLE_TYPE, value, 0); if ((value[0] & EGLExt.EGL_OPENGL_ES3_BIT_KHR) != EGLExt.EGL_OPENGL_ES3_BIT_KHR) { continue; } // The pbuffer config also needs to be compatible with normal window rendering // so it can share textures with the window context. - egl.eglGetConfigAttrib(display, config, EGL10.EGL_SURFACE_TYPE, value); - if ((value[0] & (EGL10.EGL_WINDOW_BIT | EGL10.EGL_PBUFFER_BIT)) != (EGL10.EGL_WINDOW_BIT | EGL10.EGL_PBUFFER_BIT)) { + EGL14.eglGetConfigAttrib(display, config, EGL14.EGL_SURFACE_TYPE, value, 0); + if ((value[0] & (EGL14.EGL_WINDOW_BIT | EGL14.EGL_PBUFFER_BIT)) != (EGL14.EGL_WINDOW_BIT | EGL14.EGL_PBUFFER_BIT)) { continue; } // Check each attribute in CONFIG_ATTRIBS (which are the attributes we care about) // and ensure the value in config matches. int attribIndex = 0; - while (CONFIG_ATTRIBS[attribIndex] != EGL10.EGL_NONE) { - egl.eglGetConfigAttrib(display, config, CONFIG_ATTRIBS[attribIndex], value); + while (CONFIG_ATTRIBS[attribIndex] != EGL14.EGL_NONE) { + EGL14.eglGetConfigAttrib(display, config, CONFIG_ATTRIBS[attribIndex], value, 0); if (value[0] != CONFIG_ATTRIBS[attribIndex + 1]) { // Attribute key's value does not match the configs value. // Start checking next config. @@ -103,7 +100,7 @@ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { attribIndex += 2; } - if (CONFIG_ATTRIBS[attribIndex] == EGL10.EGL_NONE) { + if (CONFIG_ATTRIBS[attribIndex] == EGL14.EGL_NONE) { // All relevant attributes match, set the config and stop checking the rest. return config; } diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/render/OvrContextFactory.java similarity index 77% rename from platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java rename to platform/android/java/lib/src/org/godotengine/godot/render/OvrContextFactory.java index 1f1034dacffc..b6b7ca5e4bce 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java +++ b/platform/android/java/lib/src/org/godotengine/godot/render/OvrContextFactory.java @@ -28,32 +28,28 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -package org.godotengine.godot.xr.ovr; - -import org.godotengine.godot.gl.GLSurfaceView; +package org.godotengine.godot.render; import android.opengl.EGL14; - -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; /** * EGL Context factory for the Oculus mobile VR SDK. */ -public class OvrContextFactory implements GLSurfaceView.EGLContextFactory { +class OvrContextFactory implements GLSurfaceView.EGLContextFactory { private static final int[] CONTEXT_ATTRIBS = { - EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE + EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE }; @Override - public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) { - return egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, CONTEXT_ATTRIBS); + public EGLContext createContext(EGLDisplay display, EGLConfig eglConfig) { + return EGL14.eglCreateContext(display, eglConfig, EGL14.EGL_NO_CONTEXT, CONTEXT_ATTRIBS, 0); } @Override - public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) { - egl.eglDestroyContext(display, context); + public void destroyContext(EGLDisplay display, EGLContext context) { + EGL14.eglDestroyContext(display, context); } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java b/platform/android/java/lib/src/org/godotengine/godot/render/OvrWindowSurfaceFactory.java similarity index 75% rename from platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java rename to platform/android/java/lib/src/org/godotengine/godot/render/OvrWindowSurfaceFactory.java index 60774eb24172..c96db2f45d14 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java +++ b/platform/android/java/lib/src/org/godotengine/godot/render/OvrWindowSurfaceFactory.java @@ -28,33 +28,26 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -package org.godotengine.godot.xr.ovr; +package org.godotengine.godot.render; -import org.godotengine.godot.gl.GLSurfaceView; - -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLDisplay; -import javax.microedition.khronos.egl.EGLSurface; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; +import android.view.SurfaceHolder; /** * EGL window surface factory for the Oculus mobile VR SDK. */ -public class OvrWindowSurfaceFactory implements GLSurfaceView.EGLWindowSurfaceFactory { +class OvrWindowSurfaceFactory implements GLSurfaceView.EGLWindowSurfaceFactory { private final static int[] SURFACE_ATTRIBS = { - EGL10.EGL_WIDTH, 16, - EGL10.EGL_HEIGHT, 16, - EGL10.EGL_NONE + EGL14.EGL_WIDTH, 16, + EGL14.EGL_HEIGHT, 16, + EGL14.EGL_NONE }; @Override - public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config, - Object nativeWindow) { - return egl.eglCreatePbufferSurface(display, config, SURFACE_ATTRIBS); - } - - @Override - public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) { - egl.eglDestroySurface(display, surface); + public EGLSurface createWindowSurface(EGLDisplay display, EGLConfig config, SurfaceHolder surfaceHolder) { + return EGL14.eglCreatePbufferSurface(display, config, SURFACE_ATTRIBS, 0); } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/render/RegularConfigChooser.java similarity index 69% rename from platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java rename to platform/android/java/lib/src/org/godotengine/godot/render/RegularConfigChooser.java index 147b4ea67669..d2bc73019c39 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java +++ b/platform/android/java/lib/src/org/godotengine/godot/render/RegularConfigChooser.java @@ -28,36 +28,34 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -package org.godotengine.godot.xr.regular; +package org.godotengine.godot.render; -import org.godotengine.godot.gl.GLSurfaceView; import org.godotengine.godot.utils.GLUtils; -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLDisplay; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLDisplay; /** * Used to select the egl config for pancake games. */ -public class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser { +class RegularConfigChooser implements GLSurfaceView.EGLConfigChooser { private static final String TAG = RegularConfigChooser.class.getSimpleName(); - private int[] mValue = new int[1]; + private final int[] mValue = new int[1]; /* This EGL config specification is used to specify 3.0 rendering. * We use a minimum size of 4 bits for red/green/blue, but will * perform actual matching in chooseConfig() below. */ - private static int EGL_OPENGL_ES2_BIT = 4; - private static int[] s_configAttribs = { - EGL10.EGL_RED_SIZE, 4, - EGL10.EGL_GREEN_SIZE, 4, - EGL10.EGL_BLUE_SIZE, 4, - // EGL10.EGL_DEPTH_SIZE, 16, - // EGL10.EGL_STENCIL_SIZE, EGL10.EGL_DONT_CARE, - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //apparently there is no EGL_OPENGL_ES3_BIT - EGL10.EGL_NONE + private static final int[] s_configAttribs = { + EGL14.EGL_RED_SIZE, 4, + EGL14.EGL_GREEN_SIZE, 4, + EGL14.EGL_BLUE_SIZE, 4, + // EGL14.EGL_DEPTH_SIZE, 16, + // EGL14.EGL_STENCIL_SIZE, EGL14.EGL_DONT_CARE, + EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, //apparently there is no EGL_OPENGL_ES3_BIT + EGL14.EGL_NONE }; public RegularConfigChooser(int r, int g, int b, int a, int depth, int stencil) { @@ -69,11 +67,11 @@ public RegularConfigChooser(int r, int g, int b, int a, int depth, int stencil) mStencilSize = stencil; } - public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + public EGLConfig chooseConfig(EGLDisplay display) { /* Get the number of minimally matching EGL configurations */ int[] num_config = new int[1]; - egl.eglChooseConfig(display, s_configAttribs, null, 0, num_config); + EGL14.eglChooseConfig(display, s_configAttribs, 0, null, 0, 0, num_config, 0); int numConfigs = num_config[0]; @@ -84,37 +82,36 @@ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { /* Allocate then read the array of minimally matching EGL configs */ EGLConfig[] configs = new EGLConfig[numConfigs]; - egl.eglChooseConfig(display, s_configAttribs, configs, numConfigs, num_config); + EGL14.eglChooseConfig(display, s_configAttribs, 0, configs, 0, numConfigs, num_config, 0); if (GLUtils.DEBUG) { - GLUtils.printConfigs(egl, display, configs); + GLUtils.printConfigs(display, configs); } /* Now return the "best" one */ - return chooseConfig(egl, display, configs); + return chooseConfig(display, configs); } - public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, - EGLConfig[] configs) { + public EGLConfig chooseConfig(EGLDisplay display, EGLConfig[] configs) { for (EGLConfig config : configs) { - int d = findConfigAttrib(egl, display, config, - EGL10.EGL_DEPTH_SIZE, 0); - int s = findConfigAttrib(egl, display, config, - EGL10.EGL_STENCIL_SIZE, 0); + int d = findConfigAttrib(display, config, + EGL14.EGL_DEPTH_SIZE, 0); + int s = findConfigAttrib(display, config, + EGL14.EGL_STENCIL_SIZE, 0); // We need at least mDepthSize and mStencilSize bits if (d < mDepthSize || s < mStencilSize) continue; // We want an *exact* match for red/green/blue/alpha - int r = findConfigAttrib(egl, display, config, - EGL10.EGL_RED_SIZE, 0); - int g = findConfigAttrib(egl, display, config, - EGL10.EGL_GREEN_SIZE, 0); - int b = findConfigAttrib(egl, display, config, - EGL10.EGL_BLUE_SIZE, 0); - int a = findConfigAttrib(egl, display, config, - EGL10.EGL_ALPHA_SIZE, 0); + int r = findConfigAttrib(display, config, + EGL14.EGL_RED_SIZE, 0); + int g = findConfigAttrib(display, config, + EGL14.EGL_GREEN_SIZE, 0); + int b = findConfigAttrib(display, config, + EGL14.EGL_BLUE_SIZE, 0); + int a = findConfigAttrib(display, config, + EGL14.EGL_ALPHA_SIZE, 0); if (r == mRedSize && g == mGreenSize && b == mBlueSize && a == mAlphaSize) return config; @@ -122,9 +119,9 @@ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, return null; } - private int findConfigAttrib(EGL10 egl, EGLDisplay display, + private int findConfigAttrib(EGLDisplay display, EGLConfig config, int attribute, int defaultValue) { - if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { + if (EGL14.eglGetConfigAttrib(display, config, attribute, mValue, 0)) { return mValue[0]; } return defaultValue; diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/render/RegularContextFactory.java similarity index 71% rename from platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java rename to platform/android/java/lib/src/org/godotengine/godot/render/RegularContextFactory.java index 1f0d8592b318..2efb1a8a7df9 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java +++ b/platform/android/java/lib/src/org/godotengine/godot/render/RegularContextFactory.java @@ -28,28 +28,26 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -package org.godotengine.godot.xr.regular; +package org.godotengine.godot.render; -import org.godotengine.godot.gl.GLSurfaceView; import org.godotengine.godot.utils.GLUtils; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; import android.util.Log; -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; - /** * Factory used to setup the opengl context for pancake games. */ -public class RegularContextFactory implements GLSurfaceView.EGLContextFactory { +class RegularContextFactory implements GLSurfaceView.EGLContextFactory { private static final String TAG = RegularContextFactory.class.getSimpleName(); private static final int _EGL_CONTEXT_FLAGS_KHR = 0x30FC; private static final int _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR = 0x00000001; - private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; private final boolean mUseDebugOpengl; @@ -61,27 +59,27 @@ public RegularContextFactory(boolean useDebugOpengl) { this.mUseDebugOpengl = useDebugOpengl; } - public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) { + public EGLContext createContext(EGLDisplay display, EGLConfig eglConfig) { Log.w(TAG, "creating OpenGL ES 3.0 context :"); - GLUtils.checkEglError(TAG, "Before eglCreateContext", egl); + GLUtils.checkEglError(TAG, "Before eglCreateContext"); EGLContext context; - int[] debug_attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 3, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE }; - int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE }; + int[] debug_attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 3, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL14.EGL_NONE }; + int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE }; if (mUseDebugOpengl) { - context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, debug_attrib_list); - if (context == null || context == EGL10.EGL_NO_CONTEXT) { + context = EGL14.eglCreateContext(display, eglConfig, EGL14.EGL_NO_CONTEXT, debug_attrib_list, 0); + if (context == null || context == EGL14.EGL_NO_CONTEXT) { Log.w(TAG, "creating 'OpenGL Debug' context failed"); - context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list); + context = EGL14.eglCreateContext(display, eglConfig, EGL14.EGL_NO_CONTEXT, attrib_list, 0); } } else { - context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list); + context = EGL14.eglCreateContext(display, eglConfig, EGL14.EGL_NO_CONTEXT, attrib_list, 0); } - GLUtils.checkEglError(TAG, "After eglCreateContext", egl); + GLUtils.checkEglError(TAG, "After eglCreateContext"); return context; } - public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) { - egl.eglDestroyContext(display, context); + public void destroyContext(EGLDisplay display, EGLContext context) { + EGL14.eglDestroyContext(display, context); } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/render/RegularFallbackConfigChooser.java similarity index 84% rename from platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java rename to platform/android/java/lib/src/org/godotengine/godot/render/RegularFallbackConfigChooser.java index ed5e5ec56177..a92c4898e3e7 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularFallbackConfigChooser.java +++ b/platform/android/java/lib/src/org/godotengine/godot/render/RegularFallbackConfigChooser.java @@ -28,19 +28,17 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -package org.godotengine.godot.xr.regular; +package org.godotengine.godot.render; +import android.opengl.EGLConfig; +import android.opengl.EGLDisplay; import android.util.Log; -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLDisplay; - /* Fallback if the requested configuration is not supported */ -public class RegularFallbackConfigChooser extends RegularConfigChooser { +class RegularFallbackConfigChooser extends RegularConfigChooser { private static final String TAG = RegularFallbackConfigChooser.class.getSimpleName(); - private RegularConfigChooser fallback; + private final RegularConfigChooser fallback; public RegularFallbackConfigChooser(int r, int g, int b, int a, int depth, int stencil, RegularConfigChooser fallback) { super(r, g, b, a, depth, stencil); @@ -48,11 +46,11 @@ public RegularFallbackConfigChooser(int r, int g, int b, int a, int depth, int s } @Override - public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) { - EGLConfig ec = super.chooseConfig(egl, display, configs); + public EGLConfig chooseConfig(EGLDisplay display, EGLConfig[] configs) { + EGLConfig ec = super.chooseConfig(display, configs); if (ec == null) { Log.w(TAG, "Trying ConfigChooser fallback"); - ec = fallback.chooseConfig(egl, display, configs); + ec = fallback.chooseConfig(display, configs); } return ec; } diff --git a/platform/android/java/lib/src/org/godotengine/godot/gl/GodotRenderer.java b/platform/android/java/lib/src/org/godotengine/godot/render/RenderThread.kt similarity index 54% rename from platform/android/java/lib/src/org/godotengine/godot/gl/GodotRenderer.java rename to platform/android/java/lib/src/org/godotengine/godot/render/RenderThread.kt index 7e5e262b2dee..d50768625ccc 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/gl/GodotRenderer.java +++ b/platform/android/java/lib/src/org/godotengine/godot/render/RenderThread.kt @@ -1,5 +1,5 @@ /**************************************************************************/ -/* GodotRenderer.java */ +/* RenderThread.kt */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,71 +28,63 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -package org.godotengine.godot.gl; +@file:JvmName("RenderThread") +package org.godotengine.godot.render -import org.godotengine.godot.GodotLib; -import org.godotengine.godot.plugin.GodotPlugin; -import org.godotengine.godot.plugin.GodotPluginRegistry; - -import android.util.Log; - -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.opengles.GL10; +import android.view.SurfaceHolder +import java.lang.ref.WeakReference /** - * Godot's GL renderer implementation. + * Base class for the OpenGL / Vulkan render thread implementations. */ -public class GodotRenderer implements GLSurfaceView.Renderer { - private final String TAG = GodotRenderer.class.getSimpleName(); - - private final GodotPluginRegistry pluginRegistry; - private boolean activityJustResumed = false; - - public GodotRenderer() { - this.pluginRegistry = GodotPluginRegistry.getPluginRegistry(); - } - - public boolean onDrawFrame(GL10 gl) { - if (activityJustResumed) { - GodotLib.onRendererResumed(); - activityJustResumed = false; - } - - boolean swapBuffers = GodotLib.step(); - for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { - plugin.onGLDrawFrame(gl); - } - - return swapBuffers; - } - - @Override - public void onRenderThreadExiting() { - Log.d(TAG, "Destroying Godot Engine"); - GodotLib.ondestroy(); - } - - public void onSurfaceChanged(GL10 gl, int width, int height) { - GodotLib.resize(null, width, height); - for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { - plugin.onGLSurfaceChanged(gl, width, height); - } - } - - public void onSurfaceCreated(GL10 gl, EGLConfig config) { - GodotLib.newcontext(null); - for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { - plugin.onGLSurfaceCreated(gl, config); - } - } - - public void onActivityResumed() { - // We defer invoking GodotLib.onRendererResumed() until the first draw frame call. - // This ensures we have a valid GL context and surface when we do so. - activityJustResumed = true; - } - - public void onActivityPaused() { - GodotLib.onRendererPaused(); - } +internal abstract class RenderThread(tag: String) : Thread(tag) { + + /** + * Queues an event on the render thread + */ + abstract fun queueEvent(event: Runnable) + + /** + * Request the thread to exit and block until it's done. + */ + abstract fun requestExitAndWait() + + /** + * Invoked when the app resumes. + */ + abstract fun onResume() + + /** + * Invoked when the app pauses. + */ + abstract fun onPause() + + abstract fun setRenderMode(renderMode: Renderer.RenderMode) + + abstract fun getRenderMode(): Renderer.RenderMode + + abstract fun requestRender() + + /** + * Invoked when the [android.view.Surface] has been created. + */ + open fun surfaceCreated(holder: SurfaceHolder, surfaceViewWeakRef: WeakReference? = null) { } + + /** + * Invoked following structural updates to [android.view.Surface]. + */ + open fun surfaceChanged(holder: SurfaceHolder, width: Int, height: Int) { } + + /** + * Invoked when the [android.view.Surface] is no longer available. + */ + open fun surfaceDestroyed(holder: SurfaceHolder) { } + + open fun setSeparateRenderThreadEnabled(enabled: Boolean) {} + + open fun makeEglCurrent(windowId: Int): Boolean = false + + open fun eglSwapBuffers(windowId: Int) {} + + open fun releaseCurrentGLWindow(windowId: Int) {} } diff --git a/platform/android/java/lib/src/org/godotengine/godot/render/Renderer.java b/platform/android/java/lib/src/org/godotengine/godot/render/Renderer.java new file mode 100644 index 000000000000..1ce4a09acf81 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/render/Renderer.java @@ -0,0 +1,136 @@ +/**************************************************************************/ +/* Renderer.java */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +package org.godotengine.godot.render; + +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; +import android.view.Surface; + +import androidx.annotation.NonNull; + +/** + * Godot renderer interface. + *

+ * The Godot renderer is responsible for driving the render thread and making calls to render a frame. + */ +public interface Renderer { + enum RenderMode { + /** + * The renderer only renders + * when the surface is created, or when {@link #requestRender} is called. + * + * @see #getRenderMode() + * @see #setRenderMode(RenderMode) + * @see #requestRender() + */ + WHEN_DIRTY, + + /** + * The renderer is called + * continuously to re-render the scene. + * + * @see #getRenderMode() + * @see #setRenderMode(RenderMode) + */ + CONTINUOUSLY + } + + /** + * Get the current rendering mode. + * + * @return the current rendering mode. + * @see RenderMode#CONTINUOUSLY + * @see RenderMode#WHEN_DIRTY + */ + RenderMode getRenderMode(); + + /** + * Set the rendering mode. When renderMode is + * RenderMode#CONTINUOUSLY, the renderer is called + * repeatedly to re-render the scene. When renderMode + * is RenderMode#WHEN_DIRTY, the renderer only rendered when the surface + * is created, or when {@link #requestRender} is called. Defaults to RenderMode#CONTINUOUSLY. + *

+ * Using RenderMode#WHEN_DIRTY can improve battery life and overall system performance + * by allowing the GPU and CPU to idle when the view does not need to be updated. + * + * @param renderMode one of the RENDERMODE_X constants + * @see RenderMode#CONTINUOUSLY + * @see RenderMode#WHEN_DIRTY + */ + void setRenderMode(@NonNull RenderMode renderMode); + + /** + * Request that the renderer render a frame. + * This method is typically used when the render mode has been set to + * {@link RenderMode#WHEN_DIRTY}, so that frames are only rendered on demand. + */ + void requestRender(); + + /** + * Called when the surface is created or recreated. + */ + void onRenderSurfaceCreated(Surface surface); + + /** + * Called when the surface changed size. + *

+ * Called after the surface is created and whenever the surface size changes. + */ + void onRenderSurfaceChanged(Surface surface, int width, int height); + + // -- GODOT start -- + + /** + * Called to draw the current frame. + *

+ * This method is responsible for drawing the current frame. + *

+ */ + void onRenderDrawFrame(); + + /** + * Invoked when the render thread is in the process of starting. + */ + void onRenderThreadStarting(); + + /** + * Invoked when the render thread is in the process of shutting down. + */ + void onRenderThreadExiting(); + + /** + * Invoked when any of the EGL resources may have changed. + */ + void onRenderEglResourcesChanged(EGLDisplay display, EGLSurface surface, EGLContext context, EGLConfig config); +} diff --git a/platform/android/java_godot_view_wrapper.h b/platform/android/java/lib/src/org/godotengine/godot/render/VkSurfaceView.kt similarity index 62% rename from platform/android/java_godot_view_wrapper.h rename to platform/android/java/lib/src/org/godotengine/godot/render/VkSurfaceView.kt index 991e64c46e74..ef487f575eec 100644 --- a/platform/android/java_godot_view_wrapper.h +++ b/platform/android/java/lib/src/org/godotengine/godot/render/VkSurfaceView.kt @@ -1,5 +1,5 @@ /**************************************************************************/ -/* java_godot_view_wrapper.h */ +/* VkSurfaceView.kt */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,40 +28,42 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#pragma once +@file:JvmName("VkSurfaceView") +package org.godotengine.godot.render -#include "jni_utils.h" +import android.content.Context +import android.view.SurfaceHolder +import android.view.SurfaceView -#include "core/math/vector2.h" +/** + * An implementation of SurfaceView that uses the dedicated surface for + * displaying Vulkan rendering. + *

+ * A [VkSurfaceView] provides the following features: + *

+ *

    + *
  • Manages a surface, which is a special piece of memory that can be + * composited into the Android view system. + *
  • Accepts a [GodotRenderer] object that does the actual rendering. + *
  • Renders on the [RenderThread] thread provided by the [GodotRenderer] to decouple rendering + * * performance from the UI thread. + *
+ */ +internal open class VkSurfaceView(context: Context, private val renderer: GodotRenderer) : SurfaceView(context), SurfaceHolder.Callback { + init { + isClickable = true + holder.addCallback(this) + } -#include -#include + override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { + renderer.renderThread.surfaceChanged(holder, width, height) + } -// Class that makes functions in java/src/org/godotengine/godot/GodotRenderView.java callable from C++ -class GodotJavaViewWrapper { -private: - jclass _cls; + override fun surfaceDestroyed(holder: SurfaceHolder) { + renderer.renderThread.surfaceDestroyed(holder) + } - jobject _godot_view; - - jmethodID _can_capture_pointer = 0; - jmethodID _request_pointer_capture = 0; - jmethodID _release_pointer_capture = 0; - - jmethodID _configure_pointer_icon = 0; - jmethodID _set_pointer_icon = 0; - -public: - GodotJavaViewWrapper(jobject godot_view); - - bool can_update_pointer_icon() const; - bool can_capture_pointer() const; - - void request_pointer_capture(); - void release_pointer_capture(); - - void configure_pointer_icon(int pointer_type, const String &image_path, const Vector2 &p_hotspot); - void set_pointer_icon(int pointer_type); - - ~GodotJavaViewWrapper(); -}; + override fun surfaceCreated(holder: SurfaceHolder) { + renderer.renderThread.surfaceCreated(holder) + } +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt b/platform/android/java/lib/src/org/godotengine/godot/render/VkThread.kt similarity index 66% rename from platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt rename to platform/android/java/lib/src/org/godotengine/godot/render/VkThread.kt index c7cb97d911ed..bb1761aaf657 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/render/VkThread.kt @@ -29,27 +29,37 @@ /**************************************************************************/ @file:JvmName("VkThread") -package org.godotengine.godot.vulkan +package org.godotengine.godot.render import android.util.Log +import android.view.SurfaceHolder +import java.lang.ref.WeakReference import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock /** - * Thread implementation for the [VkSurfaceView] onto which the vulkan logic is ran. + * Implementation of the thread used by the [GodotRenderer] to drive render logic. * - * The implementation is modeled after [android.opengl.GLSurfaceView]'s GLThread. + * The implementation is modeled after [android.opengl.GLSurfaceView]'s GLThread */ -internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vkRenderer: VkRenderer) : Thread(TAG) { +internal class VkThread(private val renderer: GodotRenderer) : RenderThread(TAG) { companion object { private val TAG = VkThread::class.java.simpleName } + /** + * Store [Surface] related data. + */ + data class SurfaceInfo(var holder: SurfaceHolder, var width: Int, var height: Int, var surfaceChanged: Boolean = true) + /** * Used to run events scheduled on the thread. */ private val eventQueue = ArrayList() + private var renderVkSurfaceInfo: SurfaceInfo? = null + private fun hasSurface() = renderVkSurfaceInfo != null + /** * Used to synchronize interaction with other threads (e.g: main thread). */ @@ -59,44 +69,65 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk private var shouldExit = false private var exited = false private var rendererInitialized = false - private var rendererResumed = false - private var resumed = false - private var surfaceChanged = false - private var hasSurface = false - private var width = 0 - private var height = 0 + private var threadResumed = false + + private var renderMode = Renderer.RenderMode.CONTINUOUSLY + private var requestRender = true /** * Determine when drawing can occur on the thread. This usually occurs after the * [android.view.Surface] is available, the app is in a resumed state. */ - private val readyToDraw - get() = hasSurface && resumed + private fun readyToDraw(): Boolean { + return threadResumed && (requestRender || (renderMode == Renderer.RenderMode.CONTINUOUSLY)) && hasSurface() + } + + private fun threadStarting() { + lock.withLock { + Log.d(TAG, "Starting render thread") + renderer.onRenderThreadStarting() + lockCondition.signalAll() + } + } private fun threadExiting() { lock.withLock { Log.d(TAG, "Exiting render thread") - vkRenderer.onRenderThreadExiting() + renderer.onRenderThreadExiting() exited = true lockCondition.signalAll() } } - /** - * Queue an event on the [VkThread]. - */ - fun queueEvent(event: Runnable) { + override fun setRenderMode(renderMode: Renderer.RenderMode) { + lock.withLock { + this.renderMode = renderMode + lockCondition.signalAll() + } + } + + override fun getRenderMode(): Renderer.RenderMode { + return lock.withLock { + renderMode + } + } + + override fun requestRender() { + lock.withLock { + requestRender = true + lockCondition.signalAll() + } + } + + override fun queueEvent(event: Runnable) { lock.withLock { eventQueue.add(event) lockCondition.signalAll() } } - /** - * Request the thread to exit and block until it's done. - */ - fun requestExitAndWait() { + override fun requestExitAndWait() { lock.withLock { shouldExit = true lockCondition.signalAll() @@ -111,54 +142,49 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk } } - /** - * Invoked when the app resumes. - */ - fun onResume() { + override fun onResume() { lock.withLock { - resumed = true + Log.d(TAG, "Resuming render thread") + + threadResumed = true + requestRender = true lockCondition.signalAll() } } - /** - * Invoked when the app pauses. - */ - fun onPause() { + override fun onPause() { lock.withLock { - resumed = false + Log.d(TAG, "Pausing render thread") + + threadResumed = false lockCondition.signalAll() } } - /** - * Invoked when the [android.view.Surface] has been created. - */ - fun onSurfaceCreated() { + override fun surfaceCreated(holder: SurfaceHolder, surfaceViewWeakRef: WeakReference?) { // This is a no op because surface creation will always be followed by surfaceChanged() // which provide all the needed information. } - /** - * Invoked following structural updates to [android.view.Surface]. - */ - fun onSurfaceChanged(width: Int, height: Int) { + override fun surfaceChanged(holder: SurfaceHolder, width: Int, height: Int) { lock.withLock { - hasSurface = true - surfaceChanged = true - this.width = width - this.height = height + val surfaceInfo = renderVkSurfaceInfo ?: SurfaceInfo(holder, width, height) + surfaceInfo.apply { + surfaceChanged = true + this.width = width + this.height = height + } + + requestRender = true + renderVkSurfaceInfo = surfaceInfo lockCondition.signalAll() } } - /** - * Invoked when the [android.view.Surface] is no longer available. - */ - fun onSurfaceDestroyed() { + override fun surfaceDestroyed(holder: SurfaceHolder) { lock.withLock { - hasSurface = false + renderVkSurfaceInfo = null lockCondition.signalAll() } } @@ -168,6 +194,8 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk */ override fun run() { try { + threadStarting() + while (true) { var event: Runnable? = null lock.withLock { @@ -184,29 +212,24 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk break } - if (readyToDraw) { - if (!rendererResumed) { - rendererResumed = true - vkRenderer.onVkResume() - - if (!rendererInitialized) { - rendererInitialized = true - vkRenderer.onVkSurfaceCreated(vkSurfaceView.holder.surface) + if (readyToDraw()) { + if (!rendererInitialized) { + rendererInitialized = true + renderVkSurfaceInfo?.apply { + renderer.onRenderSurfaceCreated(holder.surface) } } - if (surfaceChanged) { - vkRenderer.onVkSurfaceChanged(vkSurfaceView.holder.surface, width, height) - surfaceChanged = false + renderVkSurfaceInfo?.let { + if (it.surfaceChanged) { + renderer.onRenderSurfaceChanged(it.holder.surface, it.width, it.height) + it.surfaceChanged = false + } } // Break out of the loop so drawing can occur without holding onto the lock. + requestRender = false break - } else if (rendererResumed) { - // If we aren't ready to draw but are resumed, that means we either lost a surface - // or the app was paused. - rendererResumed = false - vkRenderer.onVkPause() } // We only reach this state if we are not ready to draw and have no queued events, so // we wait. @@ -219,11 +242,12 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk // Run queued event. if (event != null) { event?.run() + event = null continue } // Draw only when there no more queued events. - vkRenderer.onVkDrawFrame() + renderer.onRenderDrawFrame() } } catch (ex: InterruptedException) { Log.i(TAG, "InterruptedException", ex) diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java b/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java index 2c7b73ae4d79..94dec80e3704 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java @@ -30,12 +30,11 @@ package org.godotengine.godot.utils; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLDisplay; import android.util.Log; -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLDisplay; - /** * Contains GL utilities methods. */ @@ -81,74 +80,115 @@ public class GLUtils { }; private static final int[] ATTRIBUTES = new int[] { - EGL10.EGL_BUFFER_SIZE, - EGL10.EGL_ALPHA_SIZE, - EGL10.EGL_BLUE_SIZE, - EGL10.EGL_GREEN_SIZE, - EGL10.EGL_RED_SIZE, - EGL10.EGL_DEPTH_SIZE, - EGL10.EGL_STENCIL_SIZE, - EGL10.EGL_CONFIG_CAVEAT, - EGL10.EGL_CONFIG_ID, - EGL10.EGL_LEVEL, - EGL10.EGL_MAX_PBUFFER_HEIGHT, - EGL10.EGL_MAX_PBUFFER_PIXELS, - EGL10.EGL_MAX_PBUFFER_WIDTH, - EGL10.EGL_NATIVE_RENDERABLE, - EGL10.EGL_NATIVE_VISUAL_ID, - EGL10.EGL_NATIVE_VISUAL_TYPE, - 0x3030, // EGL10.EGL_PRESERVED_RESOURCES, - EGL10.EGL_SAMPLES, - EGL10.EGL_SAMPLE_BUFFERS, - EGL10.EGL_SURFACE_TYPE, - EGL10.EGL_TRANSPARENT_TYPE, - EGL10.EGL_TRANSPARENT_RED_VALUE, - EGL10.EGL_TRANSPARENT_GREEN_VALUE, - EGL10.EGL_TRANSPARENT_BLUE_VALUE, - 0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB, - 0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA, - 0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL, - 0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL, - EGL10.EGL_LUMINANCE_SIZE, - EGL10.EGL_ALPHA_MASK_SIZE, - EGL10.EGL_COLOR_BUFFER_TYPE, - EGL10.EGL_RENDERABLE_TYPE, - 0x3042 // EGL10.EGL_CONFORMANT + EGL14.EGL_BUFFER_SIZE, + EGL14.EGL_ALPHA_SIZE, + EGL14.EGL_BLUE_SIZE, + EGL14.EGL_GREEN_SIZE, + EGL14.EGL_RED_SIZE, + EGL14.EGL_DEPTH_SIZE, + EGL14.EGL_STENCIL_SIZE, + EGL14.EGL_CONFIG_CAVEAT, + EGL14.EGL_CONFIG_ID, + EGL14.EGL_LEVEL, + EGL14.EGL_MAX_PBUFFER_HEIGHT, + EGL14.EGL_MAX_PBUFFER_PIXELS, + EGL14.EGL_MAX_PBUFFER_WIDTH, + EGL14.EGL_NATIVE_RENDERABLE, + EGL14.EGL_NATIVE_VISUAL_ID, + EGL14.EGL_NATIVE_VISUAL_TYPE, + 0x3030, // EGL14.EGL_PRESERVED_RESOURCES, + EGL14.EGL_SAMPLES, + EGL14.EGL_SAMPLE_BUFFERS, + EGL14.EGL_SURFACE_TYPE, + EGL14.EGL_TRANSPARENT_TYPE, + EGL14.EGL_TRANSPARENT_RED_VALUE, + EGL14.EGL_TRANSPARENT_GREEN_VALUE, + EGL14.EGL_TRANSPARENT_BLUE_VALUE, + EGL14.EGL_BIND_TO_TEXTURE_RGB, + EGL14.EGL_BIND_TO_TEXTURE_RGBA, + EGL14.EGL_MIN_SWAP_INTERVAL, + EGL14.EGL_MAX_SWAP_INTERVAL, + EGL14.EGL_LUMINANCE_SIZE, + EGL14.EGL_ALPHA_MASK_SIZE, + EGL14.EGL_COLOR_BUFFER_TYPE, + EGL14.EGL_RENDERABLE_TYPE, + EGL14.EGL_CONFORMANT }; private GLUtils() {} - public static void checkEglError(String tag, String prompt, EGL10 egl) { + public static void checkEglError(String tag, String prompt) { int error; - while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) { + while ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { Log.e(tag, String.format("%s: EGL error: 0x%x", prompt, error)); } } - public static void printConfigs(EGL10 egl, EGLDisplay display, + public static void printConfigs(EGLDisplay display, EGLConfig[] configs) { int numConfigs = configs.length; Log.v(TAG, String.format("%d configurations", numConfigs)); for (int i = 0; i < numConfigs; i++) { Log.v(TAG, String.format("Configuration %d:\n", i)); - printConfig(egl, display, configs[i]); + printConfig(display, configs[i]); } } - private static void printConfig(EGL10 egl, EGLDisplay display, + private static void printConfig(EGLDisplay display, EGLConfig config) { int[] value = new int[1]; for (int i = 0; i < ATTRIBUTES.length; i++) { int attribute = ATTRIBUTES[i]; String name = ATTRIBUTES_NAMES[i]; - if (egl.eglGetConfigAttrib(display, config, attribute, value)) { + if (EGL14.eglGetConfigAttrib(display, config, attribute, value, 0)) { Log.i(TAG, String.format(" %s: %d\n", name, value[0])); } else { // Log.w(TAG, String.format(" %s: failed\n", name)); - while (egl.eglGetError() != EGL10.EGL_SUCCESS) { + while (EGL14.eglGetError() != EGL14.EGL_SUCCESS) { // Continue. } } } } + + public static String getErrorString(int error) { + switch (error) { + case EGL14.EGL_SUCCESS: + return "EGL_SUCCESS"; + case EGL14.EGL_NOT_INITIALIZED: + return "EGL_NOT_INITIALIZED"; + case EGL14.EGL_BAD_ACCESS: + return "EGL_BAD_ACCESS"; + case EGL14.EGL_BAD_ALLOC: + return "EGL_BAD_ALLOC"; + case EGL14.EGL_BAD_ATTRIBUTE: + return "EGL_BAD_ATTRIBUTE"; + case EGL14.EGL_BAD_CONFIG: + return "EGL_BAD_CONFIG"; + case EGL14.EGL_BAD_CONTEXT: + return "EGL_BAD_CONTEXT"; + case EGL14.EGL_BAD_CURRENT_SURFACE: + return "EGL_BAD_CURRENT_SURFACE"; + case EGL14.EGL_BAD_DISPLAY: + return "EGL_BAD_DISPLAY"; + case EGL14.EGL_BAD_MATCH: + return "EGL_BAD_MATCH"; + case EGL14.EGL_BAD_NATIVE_PIXMAP: + return "EGL_BAD_NATIVE_PIXMAP"; + case EGL14.EGL_BAD_NATIVE_WINDOW: + return "EGL_BAD_NATIVE_WINDOW"; + case EGL14.EGL_BAD_PARAMETER: + return "EGL_BAD_PARAMETER"; + case EGL14.EGL_BAD_SURFACE: + return "EGL_BAD_SURFACE"; + case EGL14.EGL_CONTEXT_LOST: + return "EGL_CONTEXT_LOST"; + default: + return getHex(error); + } + } + + private static String getHex(int value) { + return "0x" + Integer.toHexString(value); + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt deleted file mode 100644 index a93a7dbe0971..000000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt +++ /dev/null @@ -1,113 +0,0 @@ -/**************************************************************************/ -/* VkRenderer.kt */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -@file:JvmName("VkRenderer") -package org.godotengine.godot.vulkan - -import android.util.Log -import android.view.Surface -import org.godotengine.godot.GodotLib -import org.godotengine.godot.plugin.GodotPluginRegistry - -/** - * Responsible to setting up and driving the Vulkan rendering logic. - * - *

Threading

- * The renderer will be called on a separate thread, so that rendering - * performance is decoupled from the UI thread. Clients typically need to - * communicate with the renderer from the UI thread, because that's where - * input events are received. Clients can communicate using any of the - * standard Java techniques for cross-thread communication, or they can - * use the [VkSurfaceView.queueOnVkThread] convenience method. - * - * @see [VkSurfaceView.startRenderer] - */ -internal class VkRenderer { - - companion object { - private val TAG = VkRenderer::class.java.simpleName - } - - private val pluginRegistry: GodotPluginRegistry = GodotPluginRegistry.getPluginRegistry() - - /** - * Called when the surface is created and signals the beginning of rendering. - */ - fun onVkSurfaceCreated(surface: Surface) { - GodotLib.newcontext(surface) - - for (plugin in pluginRegistry.getAllPlugins()) { - plugin.onVkSurfaceCreated(surface) - } - } - - /** - * Called after the surface is created and whenever its size changes. - */ - fun onVkSurfaceChanged(surface: Surface, width: Int, height: Int) { - GodotLib.resize(surface, width, height) - - for (plugin in pluginRegistry.getAllPlugins()) { - plugin.onVkSurfaceChanged(surface, width, height) - } - } - - /** - * Called to draw the current frame. - */ - fun onVkDrawFrame() { - GodotLib.step() - for (plugin in pluginRegistry.getAllPlugins()) { - plugin.onVkDrawFrame() - } - } - - /** - * Called when the rendering thread is resumed. - */ - fun onVkResume() { - GodotLib.onRendererResumed() - } - - /** - * Called when the rendering thread is paused. - */ - fun onVkPause() { - GodotLib.onRendererPaused() - } - - /** - * Invoked when the render thread is in the process of shutting down. - */ - fun onRenderThreadExiting() { - Log.d(TAG, "Destroying Godot Engine") - GodotLib.ondestroy() - } -} diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt deleted file mode 100644 index 9e30de6a153f..000000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt +++ /dev/null @@ -1,133 +0,0 @@ -/**************************************************************************/ -/* VkSurfaceView.kt */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -@file:JvmName("VkSurfaceView") -package org.godotengine.godot.vulkan - -import android.content.Context -import android.view.SurfaceHolder -import android.view.SurfaceView - -/** - * An implementation of SurfaceView that uses the dedicated surface for - * displaying Vulkan rendering. - *

- * A [VkSurfaceView] provides the following features: - *

- *

    - *
  • Manages a surface, which is a special piece of memory that can be - * composited into the Android view system. - *
  • Accepts a user-provided [VkRenderer] object that does the actual rendering. - *
  • Renders on a dedicated [VkThread] thread to decouple rendering performance from the - * UI thread. - *
- */ -open internal class VkSurfaceView(context: Context) : SurfaceView(context), SurfaceHolder.Callback { - companion object { - fun checkState(expression: Boolean, errorMessage: Any) { - check(expression) { errorMessage.toString() } - } - } - - /** - * Thread used to drive the vulkan logic. - */ - private val vkThread: VkThread by lazy { - VkThread(this, renderer) - } - - /** - * Performs the actual rendering. - */ - private lateinit var renderer: VkRenderer - - init { - isClickable = true - holder.addCallback(this) - } - - /** - * Set the [VkRenderer] associated with the view, and starts the thread that will drive the vulkan - * rendering. - * - * This method should be called once and only once in the life-cycle of [VkSurfaceView]. - */ - fun startRenderer(renderer: VkRenderer) { - checkState(!this::renderer.isInitialized, "startRenderer must only be invoked once") - this.renderer = renderer - vkThread.start() - } - - /** - * Queues a runnable to be run on the Vulkan rendering thread. - * - * Must not be called before a [VkRenderer] has been set. - */ - fun queueOnVkThread(runnable: Runnable) { - vkThread.queueEvent(runnable) - } - - /** - * Resumes the rendering thread. - * - * Must not be called before a [VkRenderer] has been set. - */ - protected fun resumeRenderThread() { - vkThread.onResume() - } - - /** - * Pauses the rendering thread. - * - * Must not be called before a [VkRenderer] has been set. - */ - protected fun pauseRenderThread() { - vkThread.onPause() - } - - /** - * Requests the render thread to exit and block until it does. - */ - fun requestRenderThreadExitAndWait() { - vkThread.requestExitAndWait() - } - - override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { - vkThread.onSurfaceChanged(width, height) - } - - override fun surfaceDestroyed(holder: SurfaceHolder) { - vkThread.onSurfaceDestroyed() - } - - override fun surfaceCreated(holder: SurfaceHolder) { - vkThread.onSurfaceCreated() - } -} diff --git a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt index 1584c6fd5ce0..c4ca23f3fb22 100644 --- a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt +++ b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt @@ -9,6 +9,7 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(GODOT_ROOT_DIR ../../../..) set(ANDROID_ROOT_DIR "${GODOT_ROOT_DIR}/platform/android" CACHE STRING "") set(OPENXR_INCLUDE_DIR "${GODOT_ROOT_DIR}/thirdparty/openxr/include" CACHE STRING "") +set(VULKAN_INCLUDE_DIR "${GODOT_ROOT_DIR}/thirdparty/vulkan/include" CACHE STRING "") # Get sources file(GLOB_RECURSE SOURCES ${GODOT_ROOT_DIR}/*.c**) @@ -19,6 +20,17 @@ target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${GODOT_ROOT_DIR} ${ANDROID_ROOT_DIR} - ${OPENXR_INCLUDE_DIR}) + ${OPENXR_INCLUDE_DIR} + ${VULKAN_INCLUDE_DIR} +) -add_definitions(-DUNIX_ENABLED -DVULKAN_ENABLED -DANDROID_ENABLED -DGLES3_ENABLED -DTOOLS_ENABLED -DDEBUG_ENABLED) +add_definitions( + -DUNIX_ENABLED + -DVULKAN_ENABLED + -DVK_USE_PLATFORM_ANDROID_KHR + -DANDROID_ENABLED + -DGLES3_ENABLED + -DTOOLS_ENABLED + -DDEBUG_ENABLED + -DRD_ENABLED +) diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index c7a549f1a851..5eede47e2108 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -245,6 +245,12 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *en } } +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_updateEglResources(JNIEnv *env, jlong p_display, jlong p_surface, jlong p_context, jlong p_config) { + if (DisplayServerAndroid *dsa = DisplayServerAndroid::get_singleton()) { + dsa->update_egl_resources(reinterpret_cast(p_display), reinterpret_cast(p_surface), reinterpret_cast(p_context), reinterpret_cast(p_config)); + } +} + JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jclass clazz) { if (step.get() <= STEP_SETUP) { return; @@ -259,9 +265,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ttsCallback(JNIEnv *e TTS_Android::_java_utterance_callback(event, id, pos); } -JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz) { if (step.get() == STEP_TERMINATED) { - return true; + return; } if (step.get() == STEP_SETUP) { @@ -270,7 +276,7 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, Main::setup2(false); // The logo is shown in the next frame otherwise we run into rendering issues input_handler = new AndroidInputHandler(); step.increment(); - return true; + return; } if (step.get() == STEP_SHOW_LOGO) { @@ -289,12 +295,12 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, } step.increment(); - return true; + return; } if (step.get() == STEP_STARTED) { if (Main::start() != EXIT_SUCCESS) { - return true; // should exit instead and print the error + return; // should exit instead and print the error } godot_java->on_godot_setup_completed(env); @@ -308,12 +314,9 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, DisplayServerAndroid::get_singleton()->process_magnetometer(magnetometer); DisplayServerAndroid::get_singleton()->process_gyroscope(gyroscope); - bool should_swap_buffers = false; - if (os_android->main_loop_iterate(&should_swap_buffers)) { + if (os_android->main_loop_iterate()) { _terminate(env, false); } - - return should_swap_buffers; } // Called on the UI thread diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index fe8c5c7593c2..c25ed0919d72 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -41,7 +41,8 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline, jobject p_godot_tts); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jobject p_surface, jint p_width, jint p_height); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface); -JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_updateEglResources(JNIEnv *env, jlong p_display, jlong p_surface, jlong p_context, jlong p_config); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ttsCallback(JNIEnv *env, jclass clazz, jint event, jint id, jint pos); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jclass clazz); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchMouseEvent(JNIEnv *env, jclass clazz, jint p_event_type, jint p_button_mask, jfloat p_x, jfloat p_y, jfloat p_delta_x, jfloat p_delta_y, jboolean p_double_click, jboolean p_source_mouse_relative, jfloat p_pressure, jfloat p_tilt_x, jfloat p_tilt_y); diff --git a/platform/android/java_godot_view_wrapper.cpp b/platform/android/java_godot_view_wrapper.cpp deleted file mode 100644 index 010b374f2ab2..000000000000 --- a/platform/android/java_godot_view_wrapper.cpp +++ /dev/null @@ -1,116 +0,0 @@ -/**************************************************************************/ -/* java_godot_view_wrapper.cpp */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#include "java_godot_view_wrapper.h" - -#include "thread_jandroid.h" - -GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) { - JNIEnv *env = get_jni_env(); - ERR_FAIL_NULL(env); - - _godot_view = env->NewGlobalRef(godot_view); - - _cls = (jclass)env->NewGlobalRef(env->GetObjectClass(godot_view)); - - _configure_pointer_icon = env->GetMethodID(_cls, "configurePointerIcon", "(ILjava/lang/String;FF)V"); - _set_pointer_icon = env->GetMethodID(_cls, "setPointerIcon", "(I)V"); - - int android_device_api_level = android_get_device_api_level(); - if (android_device_api_level >= __ANDROID_API_O__) { - _request_pointer_capture = env->GetMethodID(_cls, "requestPointerCapture", "()V"); - _release_pointer_capture = env->GetMethodID(_cls, "releasePointerCapture", "()V"); - } - - _can_capture_pointer = env->GetMethodID(_cls, "canCapturePointer", "()Z"); -} - -bool GodotJavaViewWrapper::can_update_pointer_icon() const { - return _configure_pointer_icon != nullptr && _set_pointer_icon != nullptr; -} - -bool GodotJavaViewWrapper::can_capture_pointer() const { - // We can capture the pointer if the other jni capture method ids are initialized, - // and GodotView#canCapturePointer() returns true. - if (_request_pointer_capture != nullptr && _release_pointer_capture != nullptr && _can_capture_pointer != nullptr) { - JNIEnv *env = get_jni_env(); - ERR_FAIL_NULL_V(env, false); - - return env->CallBooleanMethod(_godot_view, _can_capture_pointer); - } - - return false; -} - -void GodotJavaViewWrapper::request_pointer_capture() { - if (_request_pointer_capture != nullptr) { - JNIEnv *env = get_jni_env(); - ERR_FAIL_NULL(env); - - env->CallVoidMethod(_godot_view, _request_pointer_capture); - } -} - -void GodotJavaViewWrapper::release_pointer_capture() { - if (_release_pointer_capture != nullptr) { - JNIEnv *env = get_jni_env(); - ERR_FAIL_NULL(env); - - env->CallVoidMethod(_godot_view, _release_pointer_capture); - } -} - -void GodotJavaViewWrapper::configure_pointer_icon(int pointer_type, const String &image_path, const Vector2 &p_hotspot) { - if (_configure_pointer_icon != nullptr) { - JNIEnv *env = get_jni_env(); - ERR_FAIL_NULL(env); - - jstring jImagePath = env->NewStringUTF(image_path.utf8().get_data()); - env->CallVoidMethod(_godot_view, _configure_pointer_icon, pointer_type, jImagePath, p_hotspot.x, p_hotspot.y); - env->DeleteLocalRef(jImagePath); - } -} - -void GodotJavaViewWrapper::set_pointer_icon(int pointer_type) { - if (_set_pointer_icon != nullptr) { - JNIEnv *env = get_jni_env(); - ERR_FAIL_NULL(env); - - env->CallVoidMethod(_godot_view, _set_pointer_icon, pointer_type); - } -} - -GodotJavaViewWrapper::~GodotJavaViewWrapper() { - JNIEnv *env = get_jni_env(); - ERR_FAIL_NULL(env); - - env->DeleteGlobalRef(_godot_view); - env->DeleteGlobalRef(_cls); -} diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 14fa75f5f1a3..948314e13f15 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -75,7 +75,6 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) { _on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V"); _on_godot_terminating = p_env->GetMethodID(godot_class, "onGodotTerminating", "()V"); _create_new_godot_instance = p_env->GetMethodID(godot_class, "createNewGodotInstance", "([Ljava/lang/String;)I"); - _get_render_view = p_env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotRenderView;"); _begin_benchmark_measure = p_env->GetMethodID(godot_class, "nativeBeginBenchmarkMeasure", "(Ljava/lang/String;Ljava/lang/String;)V"); _end_benchmark_measure = p_env->GetMethodID(godot_class, "nativeEndBenchmarkMeasure", "(Ljava/lang/String;Ljava/lang/String;)V"); _dump_benchmark = p_env->GetMethodID(godot_class, "nativeDumpBenchmark", "(Ljava/lang/String;)V"); @@ -87,13 +86,23 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) { _is_in_immersive_mode = p_env->GetMethodID(godot_class, "isInImmersiveMode", "()Z"); _on_editor_workspace_selected = p_env->GetMethodID(godot_class, "nativeOnEditorWorkspaceSelected", "(Ljava/lang/String;)V"); _get_activity = p_env->GetMethodID(godot_class, "getActivity", "()Landroid/app/Activity;"); -} + _can_capture_pointer = p_env->GetMethodID(godot_class, "canCapturePointer", "()Z"); + _configure_pointer_icon = p_env->GetMethodID(godot_class, "configurePointerIcon", "(ILjava/lang/String;FF)V"); + _set_pointer_icon = p_env->GetMethodID(godot_class, "setPointerIcon", "(I)V"); -GodotJavaWrapper::~GodotJavaWrapper() { - if (godot_view) { - delete godot_view; + int android_device_api_level = android_get_device_api_level(); + if (android_device_api_level >= __ANDROID_API_O__) { + _request_pointer_capture = p_env->GetMethodID(godot_class, "requestPointerCapture", "()V"); + _release_pointer_capture = p_env->GetMethodID(godot_class, "releasePointerCapture", "()V"); } + _set_separate_render_thread_enabled = p_env->GetMethodID(godot_class, "setSeparateRenderThreadEnabled", "(Z)V"); + _make_gl_window_current = p_env->GetMethodID(godot_class, "makeGLWindowCurrent", "(I)Z"); + _egl_swap_buffers = p_env->GetMethodID(godot_class, "eglSwapBuffers", "(I)V"); + _release_current_gl_window = p_env->GetMethodID(godot_class, "releaseCurrentGLWindow", "(I)V"); +} + +GodotJavaWrapper::~GodotJavaWrapper() { JNIEnv *env = get_jni_env(); ERR_FAIL_NULL(env); env->DeleteGlobalRef(godot_instance); @@ -110,21 +119,6 @@ jobject GodotJavaWrapper::get_activity() { return nullptr; } -GodotJavaViewWrapper *GodotJavaWrapper::get_godot_view() { - if (godot_view != nullptr) { - return godot_view; - } - if (_get_render_view) { - JNIEnv *env = get_jni_env(); - ERR_FAIL_NULL_V(env, nullptr); - jobject godot_render_view = env->CallObjectMethod(godot_instance, _get_render_view); - if (!env->IsSameObject(godot_render_view, nullptr)) { - godot_view = new GodotJavaViewWrapper(godot_render_view); - } - } - return godot_view; -} - void GodotJavaWrapper::on_godot_setup_completed(JNIEnv *p_env) { if (_on_godot_setup_completed) { if (p_env == nullptr) { @@ -596,3 +590,91 @@ void GodotJavaWrapper::on_editor_workspace_selected(const String &p_workspace) { env->CallVoidMethod(godot_instance, _on_editor_workspace_selected, j_workspace); } } + +bool GodotJavaWrapper::can_capture_pointer() const { + // We can capture the pointer if the other jni capture method ids are initialized, + // and GodotView#canCapturePointer() returns true. + if (_request_pointer_capture != nullptr && _release_pointer_capture != nullptr && _can_capture_pointer != nullptr) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL_V(env, false); + + return env->CallBooleanMethod(godot_instance, _can_capture_pointer); + } + + return false; +} + +void GodotJavaWrapper::request_pointer_capture() { + if (_request_pointer_capture != nullptr) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->CallVoidMethod(godot_instance, _request_pointer_capture); + } +} + +void GodotJavaWrapper::release_pointer_capture() { + if (_release_pointer_capture != nullptr) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->CallVoidMethod(godot_instance, _release_pointer_capture); + } +} + +void GodotJavaWrapper::configure_pointer_icon(int pointer_type, const String &image_path, const Vector2 &p_hotspot) { + if (_configure_pointer_icon != nullptr) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + jstring jImagePath = env->NewStringUTF(image_path.utf8().get_data()); + env->CallVoidMethod(godot_instance, _configure_pointer_icon, pointer_type, jImagePath, p_hotspot.x, p_hotspot.y); + env->DeleteLocalRef(jImagePath); + } +} + +void GodotJavaWrapper::set_pointer_icon(int pointer_type) { + if (_set_pointer_icon != nullptr) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->CallVoidMethod(godot_instance, _set_pointer_icon, pointer_type); + } +} + +void GodotJavaWrapper::set_separate_render_thread_enabled(bool p_enabled) { + if (_set_separate_render_thread_enabled != nullptr) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->CallVoidMethod(godot_instance, _set_separate_render_thread_enabled, p_enabled); + } +} + +bool GodotJavaWrapper::make_gl_window_current(DisplayServer::WindowID p_window_id) { + if (_make_gl_window_current != nullptr) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL_V(env, false); + + return env->CallBooleanMethod(godot_instance, _make_gl_window_current, p_window_id); + } + return false; +} + +void GodotJavaWrapper::egl_swap_buffers(DisplayServer::WindowID p_window_id) { + if (_egl_swap_buffers != nullptr) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->CallVoidMethod(godot_instance, _egl_swap_buffers, p_window_id); + } +} + +void GodotJavaWrapper::release_current_gl_window(DisplayServer::WindowID p_window_id) { + if (_release_current_gl_window != nullptr) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->CallVoidMethod(godot_instance, _release_current_gl_window, p_window_id); + } +} diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index a7957b180738..3d49ece9370e 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -30,10 +30,11 @@ #pragma once -#include "java_godot_view_wrapper.h" +#include "jni_utils.h" #include "core/math/color.h" #include "core/templates/list.h" +#include "servers/display_server.h" #include #include @@ -44,8 +45,6 @@ class GodotJavaWrapper { jobject godot_instance; jclass godot_class; - GodotJavaViewWrapper *godot_view = nullptr; - jmethodID _restart = nullptr; jmethodID _finish = nullptr; jmethodID _set_keep_screen_on = nullptr; @@ -72,7 +71,6 @@ class GodotJavaWrapper { jmethodID _on_godot_main_loop_started = nullptr; jmethodID _on_godot_terminating = nullptr; jmethodID _create_new_godot_instance = nullptr; - jmethodID _get_render_view = nullptr; jmethodID _begin_benchmark_measure = nullptr; jmethodID _end_benchmark_measure = nullptr; jmethodID _dump_benchmark = nullptr; @@ -83,6 +81,15 @@ class GodotJavaWrapper { jmethodID _is_in_immersive_mode = nullptr; jmethodID _on_editor_workspace_selected = nullptr; jmethodID _get_activity = nullptr; + jmethodID _can_capture_pointer = nullptr; + jmethodID _request_pointer_capture = nullptr; + jmethodID _release_pointer_capture = nullptr; + jmethodID _configure_pointer_icon = nullptr; + jmethodID _set_pointer_icon = nullptr; + jmethodID _set_separate_render_thread_enabled = nullptr; + jmethodID _make_gl_window_current = nullptr; + jmethodID _egl_swap_buffers = nullptr; + jmethodID _release_current_gl_window = nullptr; public: GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance); @@ -90,8 +97,6 @@ class GodotJavaWrapper { jobject get_activity(); - GodotJavaViewWrapper *get_godot_view(); - void on_godot_setup_completed(JNIEnv *p_env = nullptr); void on_godot_main_loop_started(JNIEnv *p_env = nullptr); void on_godot_terminating(JNIEnv *p_env = nullptr); @@ -138,4 +143,15 @@ class GodotJavaWrapper { bool is_in_immersive_mode(); void on_editor_workspace_selected(const String &p_workspace); + + bool can_capture_pointer() const; + void request_pointer_capture(); + void release_pointer_capture(); + void configure_pointer_icon(int pointer_type, const String &image_path, const Vector2 &p_hotspot); + void set_pointer_icon(int pointer_type); + + void set_separate_render_thread_enabled(bool p_enabled); + bool make_gl_window_current(DisplayServer::WindowID p_window_id); + void egl_swap_buffers(DisplayServer::WindowID p_window_id); + void release_current_gl_window(DisplayServer::WindowID p_window_id); }; diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 2f495b1539c0..a3994a8aa942 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -354,22 +354,13 @@ void OS_Android::main_loop_begin() { #endif } -bool OS_Android::main_loop_iterate(bool *r_should_swap_buffers) { +bool OS_Android::main_loop_iterate() { if (!main_loop) { return false; } - DisplayServerAndroid::get_singleton()->reset_swap_buffers_flag(); DisplayServerAndroid::get_singleton()->process_events(); - uint64_t current_frames_drawn = Engine::get_singleton()->get_frames_drawn(); bool exit = Main::iteration(); - if (r_should_swap_buffers) { - *r_should_swap_buffers = !is_in_low_processor_usage_mode() || - DisplayServerAndroid::get_singleton()->should_swap_buffers() || - RenderingServer::get_singleton()->has_changed() || - current_frames_drawn != Engine::get_singleton()->get_frames_drawn(); - } - return exit; } diff --git a/platform/android/os_android.h b/platform/android/os_android.h index c4a8c8f02b23..236c72075716 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -128,7 +128,7 @@ class OS_Android : public OS_Unix { virtual MainLoop *get_main_loop() const override; void main_loop_begin(); - bool main_loop_iterate(bool *r_should_swap_buffers = nullptr); + bool main_loop_iterate(); void main_loop_end(); void main_loop_focusout(); void main_loop_focusin();