diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt index e70ddc7f2063..b8934c7851d4 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt @@ -123,7 +123,7 @@ open class GodotEditor : GodotActivity() { runOnUiThread { // Enable long press, panning and scaling gestures - godotFragment?.godot?.renderView?.inputHandler?.apply { + godotFragment?.godot?.inputHandler?.apply { enableLongPress(longPressEnabled) enablePanningAndScalingGestures(panScaleEnabled) } 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 321e0660319e..9a49acbc4994 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -44,6 +44,7 @@ import android.hardware.SensorEventListener import android.hardware.SensorManager import android.os.* import android.util.Log +import android.util.SparseArray import android.view.* import android.widget.FrameLayout import androidx.annotation.Keep @@ -53,9 +54,11 @@ import androidx.core.view.WindowInsetsAnimationCompat import androidx.core.view.WindowInsetsCompat import com.google.android.vending.expansion.downloader.* import org.godotengine.godot.input.GodotEditText +import org.godotengine.godot.input.GodotInputHandler import org.godotengine.godot.io.directory.DirectoryAccessHandler import org.godotengine.godot.io.file.FileAccessHandler import org.godotengine.godot.plugin.GodotPluginRegistry +import org.godotengine.godot.render.GodotRenderer import org.godotengine.godot.tts.GodotTTS import org.godotengine.godot.utils.CommandLineFileParser import org.godotengine.godot.utils.GodotNetUtils @@ -82,14 +85,23 @@ import java.util.* */ class Godot(private val context: Context) : SensorEventListener { - private companion object { + companion object { private val TAG = Godot::class.java.simpleName // Supported build flavors const val EDITOR_FLAVOR = "editor" const val TEMPLATE_FLAVOR = "template" + + /** + * Tag for the primary render view. + */ + internal const val PRIMARY_RENDER_VIEW_ID = 0 } + val inputHandler = GodotInputHandler(context, this) + + private lateinit var renderer: GodotRenderer + private val windowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager private val mSensorManager: SensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager private val mClipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager @@ -158,8 +170,16 @@ class Godot(private val context: Context) : SensorEventListener { private var useDebugOpengl = false private var darkMode = false - private var containerLayout: FrameLayout? = null - var renderView: GodotRenderView? = null + /** + * Monotonically increasing render view id used to track the registered render views. + */ + private var renderViewId = PRIMARY_RENDER_VIEW_ID + val renderViews = SparseArray() + + @JvmOverloads + fun getRenderView(id: Int = PRIMARY_RENDER_VIEW_ID): GodotRenderView? { + return renderViews[id] + } /** * Returns true if the native engine has been initialized through [onInitNativeLayer], false otherwise. @@ -329,13 +349,13 @@ class Godot(private val context: Context) : SensorEventListener { Log.v(TAG, "OnInitNativeLayer: $host") + val activity = requireActivity() beginBenchmarkMeasure("Startup", "Godot::onInitNativeLayer") try { if (expansionPackPath.isNotEmpty()) { commandLine.add("--main-pack") commandLine.add(expansionPackPath) } - val activity = requireActivity() if (!nativeLayerInitializeCompleted) { nativeLayerInitializeCompleted = GodotLib.initialize( activity, @@ -361,6 +381,20 @@ class Godot(private val context: Context) : SensorEventListener { } finally { endBenchmarkMeasure("Startup", "Godot::onInitNativeLayer") } + + val useVulkan = usesVulkan() + renderer = GodotRenderer(useVulkan) + if (useVulkan) { + if (!meetsVulkanRequirements(activity.packageManager)) { + throw IllegalStateException(activity.getString(R.string.error_missing_vulkan_requirements_message)) + } + } + + for (plugin in pluginRegistry.allPlugins) { + plugin.onRegisterPluginWithGodotNative() + } + setKeepScreenOn(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on"))) + return isNativeInitialized() } @@ -373,12 +407,12 @@ class Godot(private val context: Context) : SensorEventListener { * @param host The [GodotHost] that's initializing the render views * @param providedContainerLayout Optional argument; if provided, this is reused to host the Godot's render views * - * @return A [FrameLayout] instance containing Godot's render views if initialization is successful, null otherwise. + * @return A [FrameLayout] instance. It contains Godot's render views if initialization is successful, otherwise it's empty. * * @throws IllegalStateException if [onInitNativeLayer] has not been called */ @JvmOverloads - fun onInitRenderView(host: GodotHost, providedContainerLayout: FrameLayout = FrameLayout(host.activity)): FrameLayout? { + fun onInitRenderView(host: GodotHost, providedContainerLayout: FrameLayout = FrameLayout(host.activity)): FrameLayout { if (!isNativeInitialized()) { throw IllegalStateException("onInitNativeLayer() must be invoked successfully prior to initializing the render view") } @@ -388,9 +422,8 @@ class Godot(private val context: Context) : SensorEventListener { beginBenchmarkMeasure("Startup", "Godot::onInitRenderView") try { val activity: Activity = host.activity - containerLayout = providedContainerLayout - containerLayout?.removeAllViews() - containerLayout?.layoutParams = ViewGroup.LayoutParams( + providedContainerLayout.removeAllViews() + providedContainerLayout.layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) @@ -405,34 +438,30 @@ class Godot(private val context: Context) : SensorEventListener { // Prevent GodotEditText from showing on splash screen on devices with Android 14 or newer. editText.setBackgroundColor(Color.TRANSPARENT) // ...add to FrameLayout - containerLayout?.addView(editText) - renderView = if (usesVulkan()) { - if (!meetsVulkanRequirements(activity.packageManager)) { - throw IllegalStateException(activity.getString(R.string.error_missing_vulkan_requirements_message)) - } - GodotVulkanRenderView(host, this) + providedContainerLayout.addView(editText) + + val renderView: GodotRenderView = if (usesVulkan()) { + GodotVulkanRenderView(host, this, renderer, inputHandler) } else { // Fallback to openGl - GodotGLRenderView(host, this, xrMode, useDebugOpengl) - } - - if (host == primaryHost) { - renderView?.startRenderer() + GodotGLRenderView(host, this, renderer, xrMode, inputHandler, useDebugOpengl) } + renderView.id = renderViewId - renderView?.let { - containerLayout?.addView( - it.view, + renderView.startRenderer() + val view: View = renderView.view + providedContainerLayout.addView( + view, ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) ) - } editText.setView(renderView) io?.setEdit(editText) + if (renderViewId == PRIMARY_RENDER_VIEW_ID) { // Listeners for keyboard height. val decorView = activity.window.decorView // Report the height of virtual keyboard as it changes during the animation. @@ -471,36 +500,30 @@ class Godot(private val context: Context) : SensorEventListener { override fun onEnd(animation: WindowInsetsAnimationCompat) {} }) - if (host == primaryHost) { - renderView?.queueOnRenderThread { - for (plugin in pluginRegistry.allPlugins) { - plugin.onRegisterPluginWithGodotNative() - } - setKeepScreenOn(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on"))) - } - // Include the returned non-null views in the Godot view hierarchy. for (plugin in pluginRegistry.allPlugins) { val pluginView = plugin.onMainCreate(activity) if (pluginView != null) { if (plugin.shouldBeOnTop()) { - containerLayout?.addView(pluginView) + providedContainerLayout.addView(pluginView) } else { - containerLayout?.addView(pluginView, 0) + providedContainerLayout.addView(pluginView, 0) } } } } + renderViewInitialized = true + renderViews[renderView.id] = renderView + renderViewId++ } finally { if (!renderViewInitialized) { - containerLayout?.removeAllViews() - containerLayout = null + providedContainerLayout.removeAllViews() } endBenchmarkMeasure("Startup", "Godot::onInitRenderView") } - return containerLayout + return providedContainerLayout } fun onStart(host: GodotHost) { @@ -509,7 +532,10 @@ class Godot(private val context: Context) : SensorEventListener { return } - renderView?.onActivityStarted() + renderer.onActivityStarted() + for (plugin in pluginRegistry.allPlugins) { + plugin.onMainStart() + } } fun onResume(host: GodotHost) { @@ -518,7 +544,7 @@ class Godot(private val context: Context) : SensorEventListener { return } - renderView?.onActivityResumed() + renderer.onActivityResumed() if (mAccelerometer != null) { mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME) } @@ -551,11 +577,11 @@ class Godot(private val context: Context) : SensorEventListener { return } - renderView?.onActivityPaused() - mSensorManager.unregisterListener(this) for (plugin in pluginRegistry.allPlugins) { plugin.onMainPause() } + renderer.onActivityPaused() + mSensorManager.unregisterListener(this) } fun onStop(host: GodotHost) { @@ -564,7 +590,10 @@ class Godot(private val context: Context) : SensorEventListener { return } - renderView?.onActivityStopped() + for (plugin in pluginRegistry.allPlugins) { + plugin.onMainStop() + } + renderer.onActivityStopped() } fun onDestroy(primaryHost: GodotHost) { @@ -576,11 +605,7 @@ class Godot(private val context: Context) : SensorEventListener { for (plugin in pluginRegistry.allPlugins) { plugin.onMainDestroy() } - - runOnRenderThread { - GodotLib.ondestroy() - forceQuit() - } + renderer.onActivityDestroyed() } /** @@ -634,7 +659,7 @@ class Godot(private val context: Context) : SensorEventListener { val rotaryInputAxisValue = GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis") runOnUiThread { - renderView?.inputHandler?.apply { + inputHandler.apply { enableLongPress(longPressEnabled) enablePanningAndScalingGestures(panScaleEnabled) try { @@ -708,7 +733,7 @@ class Godot(private val context: Context) : SensorEventListener { * This must be called after the render thread has started. */ fun runOnRenderThread(action: Runnable) { - renderView?.queueOnRenderThread(action) + renderer.queueOnRenderThread(action) } /** @@ -731,9 +756,9 @@ class Godot(private val context: Context) : SensorEventListener { * Returns true if `Vulkan` is used for rendering. */ private fun usesVulkan(): Boolean { - val renderer = GodotLib.getGlobal("rendering/renderer/rendering_method") + val renderingMethod = GodotLib.getGlobal("rendering/renderer/rendering_method") val renderingDevice = GodotLib.getGlobal("rendering/rendering_device/driver") - return ("forward_plus" == renderer || "mobile" == renderer) && "vulkan" == renderingDevice + return ("forward_plus" == renderingMethod || "mobile" == renderingMethod) && "vulkan" == renderingDevice } /** @@ -817,11 +842,7 @@ class Godot(private val context: Context) : SensorEventListener { } ?: return false } - fun onBackPressed(host: GodotHost) { - if (host != primaryHost) { - return - } - + fun onBackPressed() { var shouldQuit = true for (plugin in pluginRegistry.allPlugins) { if (plugin.onMainBackPressed()) { @@ -829,7 +850,7 @@ class Godot(private val context: Context) : SensorEventListener { } } if (shouldQuit) { - renderView?.queueOnRenderThread { GodotLib.back() } + renderer.queueOnRenderThread { GodotLib.back() } } } @@ -864,37 +885,33 @@ class Godot(private val context: Context) : SensorEventListener { } override fun onSensorChanged(event: SensorEvent) { - if (renderView == null) { - return - } - val rotatedValues = getRotatedValues(event.values) when (event.sensor.type) { Sensor.TYPE_ACCELEROMETER -> { rotatedValues?.let { - renderView?.queueOnRenderThread { + renderer.queueOnRenderThread { GodotLib.accelerometer(-it[0], -it[1], -it[2]) } } } Sensor.TYPE_GRAVITY -> { rotatedValues?.let { - renderView?.queueOnRenderThread { + renderer.queueOnRenderThread { GodotLib.gravity(-it[0], -it[1], -it[2]) } } } Sensor.TYPE_MAGNETIC_FIELD -> { rotatedValues?.let { - renderView?.queueOnRenderThread { + renderer.queueOnRenderThread { GodotLib.magnetometer(-it[0], -it[1], -it[2]) } } } Sensor.TYPE_GYROSCOPE -> { rotatedValues?.let { - renderView?.queueOnRenderThread { + renderer.queueOnRenderThread { GodotLib.gyroscope(it[0], it[1], it[2]) } } @@ -1038,7 +1055,7 @@ class Godot(private val context: Context) : SensorEventListener { @Keep private fun initInputDevices() { - renderView?.initInputDevices() + inputHandler.initInputDevices() } @Keep diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt index 4c5e857b7ac2..b3122b9fd731 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt @@ -85,12 +85,8 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { protected open fun getGodotAppLayout() = R.layout.godot_app_layout override fun onDestroy() { - Log.v(TAG, "Destroying Godot app...") + Log.v(TAG, "Destroying GodotActivity ${this}...") super.onDestroy() - - godotFragment?.let { - terminateGodotInstance(it.godot) - } } override fun onGodotForceQuit(instance: Godot) { diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java index 1612ddd0b3b1..2b32493ce870 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java @@ -187,7 +187,12 @@ public void onCreate(Bundle icicle) { final Activity activity = getActivity(); mCurrentIntent = activity.getIntent(); - godot = new Godot(requireContext()); + if (parentHost != null) { + godot = parentHost.getGodot(); + } + if (godot == null) { + godot = new Godot(requireContext()); + } performEngineInitialization(); BenchmarkUtils.endBenchmarkMeasure("Startup", "GodotFragment::onCreate"); } @@ -201,7 +206,7 @@ private void performEngineInitialization() { } godotContainerLayout = godot.onInitRenderView(this); - if (godotContainerLayout == null) { + if (godotContainerLayout.getChildCount() == 0) { throw new IllegalStateException("Unable to initialize engine render view"); } } catch (IllegalStateException e) { @@ -325,7 +330,7 @@ public void onResume() { } public void onBackPressed() { - godot.onBackPressed(this); + godot.onBackPressed(); } /** diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java index 81043ce782b2..09d82e77c7a5 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java @@ -30,9 +30,9 @@ 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.render.GLSurfaceView; +import org.godotengine.godot.render.GodotRenderer; import org.godotengine.godot.xr.XRMode; import org.godotengine.godot.xr.ovr.OvrConfigChooser; import org.godotengine.godot.xr.ovr.OvrContextFactory; @@ -42,7 +42,6 @@ import org.godotengine.godot.xr.regular.RegularFallbackConfigChooser; import android.annotation.SuppressLint; -import android.content.Context; import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -77,20 +76,20 @@ * 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. */ -public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView { +class GodotGLRenderView extends GLSurfaceView implements GodotRenderView { private final GodotHost host; private final Godot godot; private final GodotInputHandler inputHandler; private final GodotRenderer godotRenderer; private final SparseArray customPointerIcons = new SparseArray<>(); - public GodotGLRenderView(GodotHost host, Godot godot, XRMode xrMode, boolean useDebugOpengl) { + public GodotGLRenderView(GodotHost host, Godot godot, GodotRenderer renderer, XRMode xrMode, GodotInputHandler inputHandler, boolean useDebugOpengl) { super(host.getActivity()); this.host = host; this.godot = godot; - this.inputHandler = new GodotInputHandler(this); - this.godotRenderer = new GodotRenderer(); + this.inputHandler = inputHandler; + this.godotRenderer = renderer; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT)); } @@ -102,47 +101,14 @@ public SurfaceView getView() { return this; } - @Override - public void initInputDevices() { - this.inputHandler.initInputDevices(); - } - @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 onBackPressed() { - godot.onBackPressed(host); + godot.onBackPressed(); } @Override 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 909daf05c978..0c253deb06d4 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.GLSurfaceView; import org.godotengine.godot.tts.GodotTTS; import org.godotengine.godot.utils.GodotNetUtils; @@ -65,7 +65,6 @@ public static native boolean initialize(Activity activity, /** * Invoked on the main thread to clean up Godot native layer. - * @see androidx.fragment.app.Fragment#onDestroy() */ public static native void ondestroy(); @@ -80,15 +79,15 @@ public static native boolean initialize(Activity activity, * @param p_surface * @param p_width * @param p_height - * @see org.godotengine.godot.gl.GLSurfaceView.Renderer#onSurfaceChanged(GL10, int, int) + * @see GLSurfaceView.Renderer#onRenderSurfaceChanged(int, Surface, int, int) */ - public static native void resize(Surface p_surface, int p_width, int p_height); + public static native void resize(int surfaceId, Surface p_surface, int p_width, int p_height); /** * Invoked on the render thread when the underlying Android surface is created or recreated. * @param p_surface */ - public static native void newcontext(Surface p_surface); + public static native void newcontext(int surfaceId, Surface p_surface); /** * Forward {@link Activity#onBackPressed()} event. @@ -97,7 +96,7 @@ public static native boolean initialize(Activity activity, /** * Invoked on the GL thread to draw the current frame. - * @see org.godotengine.godot.gl.GLSurfaceView.Renderer#onDrawFrame(GL10) + * @see GLSurfaceView.Renderer#onRenderDrawFrame() */ public static native boolean step(); @@ -171,13 +170,11 @@ public static native boolean initialize(Activity activity, /** * Invoked when the Android app resumes. - * @see androidx.fragment.app.Fragment#onResume() */ public static native void focusin(); /** * Invoked when the Android app pauses. - * @see androidx.fragment.app.Fragment#onPause() */ public static native void focusout(); @@ -230,14 +227,14 @@ public static native boolean initialize(Activity activity, public static native void setVirtualKeyboardHeight(int p_height); /** - * Invoked on the GL thread when the {@link GodotRenderer} has been resumed. - * @see GodotRenderer#onActivityResumed() + * Invoked on the GL thread when the {@link org.godotengine.godot.render.GodotRenderer} has been resumed. + * @see org.godotengine.godot.render.GodotRenderer#onActivityResumed() */ public static native void onRendererResumed(); /** - * Invoked on the GL thread when the {@link GodotRenderer} has been paused. - * @see GodotRenderer#onActivityPaused() + * Invoked on the GL thread when the {@link org.godotengine.godot.render.GodotRenderer} has been paused. + * @see org.godotengine.godot.render.GodotRenderer#onActivityPaused() */ public static native void onRendererPaused(); 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 5b2f9f57c7ab..98bab9deb5ee 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java @@ -37,23 +37,20 @@ public interface GodotRenderView { SurfaceView getView(); - void initInputDevices(); + void setId(int id); + + int getId(); /** * 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 onBackPressed(); GodotInputHandler getInputHandler(); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java index a1ee9bd6b4d6..737df81d8ac9 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java @@ -31,8 +31,8 @@ package org.godotengine.godot; import org.godotengine.godot.input.GodotInputHandler; -import org.godotengine.godot.vulkan.VkRenderer; -import org.godotengine.godot.vulkan.VkSurfaceView; +import org.godotengine.godot.render.GodotRenderer; +import org.godotengine.godot.render.VkSurfaceView; import android.annotation.SuppressLint; import android.content.res.AssetManager; @@ -50,20 +50,20 @@ import java.io.InputStream; -public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView { +class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView { private final GodotHost host; private final Godot godot; private final GodotInputHandler mInputHandler; - private final VkRenderer mRenderer; + private final GodotRenderer mRenderer; private final SparseArray customPointerIcons = new SparseArray<>(); - public GodotVulkanRenderView(GodotHost host, Godot godot) { + public GodotVulkanRenderView(GodotHost host, Godot godot, GodotRenderer renderer, GodotInputHandler inputHandler) { super(host.getActivity()); this.host = host; this.godot = godot; - mInputHandler = new GodotInputHandler(this); - mRenderer = new VkRenderer(); + mInputHandler = inputHandler; + mRenderer = renderer; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT)); } @@ -80,47 +80,14 @@ public SurfaceView getView() { return this; } - @Override - public void initInputDevices() { - mInputHandler.initInputDevices(); - } - @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(); - }); + mRenderer.queueOnRenderThread(event); } @Override public void onBackPressed() { - godot.onBackPressed(host); + godot.onBackPressed(); } @Override diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java index 273774a33d1e..6a24d14a1ae5 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java @@ -30,10 +30,10 @@ package org.godotengine.godot.input; -import static org.godotengine.godot.utils.GLUtils.DEBUG; - +import org.godotengine.godot.Godot; import org.godotengine.godot.GodotLib; import org.godotengine.godot.GodotRenderView; +import org.godotengine.godot.utils.GLUtils; import android.content.Context; import android.hardware.input.InputManager; @@ -64,7 +64,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { private final SparseArray mJoysticksDevices = new SparseArray<>(4); private final HashSet mHardwareKeyboardIds = new HashSet<>(); - private final GodotRenderView mRenderView; + private final Godot godot; private final InputManager mInputManager; private final GestureDetector gestureDetector; private final ScaleGestureDetector scaleGestureDetector; @@ -77,9 +77,8 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { private int rotaryInputAxis = ROTARY_INPUT_VERTICAL_AXIS; - public GodotInputHandler(GodotRenderView godotView) { - final Context context = godotView.getView().getContext(); - mRenderView = godotView; + public GodotInputHandler(Context context, Godot godot) { + this.godot = godot; mInputManager = (InputManager)context.getSystemService(Context.INPUT_SERVICE); mInputManager.registerInputDeviceListener(this, null); @@ -174,7 +173,7 @@ public boolean onKeyUp(final int keyCode, KeyEvent event) { public boolean onKeyDown(final int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { - mRenderView.onBackPressed(); + godot.onBackPressed(); // press 'back' button should not terminate program //normal handle 'back' event in game logic return true; @@ -293,7 +292,7 @@ public void initInputDevices() { for (int deviceId : deviceIds) { InputDevice device = mInputManager.getInputDevice(deviceId); if (device != null) { - if (DEBUG) { + if (GLUtils.DEBUG) { Log.v(TAG, String.format("init() deviceId:%d, Name:%s\n", deviceId, device.getName())); } onInputDeviceAdded(deviceId); @@ -608,7 +607,7 @@ boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y, flo case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_SCROLL: { if (shouldDispatchInputToRenderThread()) { - mRenderView.queueOnRenderThread(() -> GodotLib.dispatchMouseEvent(eventAction, updatedButtonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, pressure, tiltX, tiltY)); + godot.runOnRenderThread(() -> GodotLib.dispatchMouseEvent(eventAction, updatedButtonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, pressure, tiltX, tiltY)); } else { GodotLib.dispatchMouseEvent(eventAction, updatedButtonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, pressure, tiltX, tiltY); } @@ -652,7 +651,7 @@ boolean handleTouchEvent(final MotionEvent event, int eventActionOverride, boole case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_POINTER_DOWN: { if (shouldDispatchInputToRenderThread()) { - mRenderView.queueOnRenderThread(() -> GodotLib.dispatchTouchEvent(eventActionOverride, actionPointerId, pointerCount, positions, doubleTap)); + godot.runOnRenderThread(() -> GodotLib.dispatchTouchEvent(eventActionOverride, actionPointerId, pointerCount, positions, doubleTap)); } else { GodotLib.dispatchTouchEvent(eventActionOverride, actionPointerId, pointerCount, positions, doubleTap); } @@ -664,7 +663,7 @@ boolean handleTouchEvent(final MotionEvent event, int eventActionOverride, boole void handleMagnifyEvent(float x, float y, float factor) { if (shouldDispatchInputToRenderThread()) { - mRenderView.queueOnRenderThread(() -> GodotLib.magnify(x, y, factor)); + godot.runOnRenderThread(() -> GodotLib.magnify(x, y, factor)); } else { GodotLib.magnify(x, y, factor); } @@ -672,7 +671,7 @@ void handleMagnifyEvent(float x, float y, float factor) { void handlePanEvent(float x, float y, float deltaX, float deltaY) { if (shouldDispatchInputToRenderThread()) { - mRenderView.queueOnRenderThread(() -> GodotLib.pan(x, y, deltaX, deltaY)); + godot.runOnRenderThread(() -> GodotLib.pan(x, y, deltaX, deltaY)); } else { GodotLib.pan(x, y, deltaX, deltaY); } @@ -680,7 +679,7 @@ void handlePanEvent(float x, float y, float deltaX, float deltaY) { private void handleJoystickButtonEvent(int device, int button, boolean pressed) { if (shouldDispatchInputToRenderThread()) { - mRenderView.queueOnRenderThread(() -> GodotLib.joybutton(device, button, pressed)); + godot.runOnRenderThread(() -> GodotLib.joybutton(device, button, pressed)); } else { GodotLib.joybutton(device, button, pressed); } @@ -688,7 +687,7 @@ private void handleJoystickButtonEvent(int device, int button, boolean pressed) private void handleJoystickAxisEvent(int device, int axis, float value) { if (shouldDispatchInputToRenderThread()) { - mRenderView.queueOnRenderThread(() -> GodotLib.joyaxis(device, axis, value)); + godot.runOnRenderThread(() -> GodotLib.joyaxis(device, axis, value)); } else { GodotLib.joyaxis(device, axis, value); } @@ -696,7 +695,7 @@ private void handleJoystickAxisEvent(int device, int axis, float value) { private void handleJoystickHatEvent(int device, int hatX, int hatY) { if (shouldDispatchInputToRenderThread()) { - mRenderView.queueOnRenderThread(() -> GodotLib.joyhat(device, hatX, hatY)); + godot.runOnRenderThread(() -> GodotLib.joyhat(device, hatX, hatY)); } else { GodotLib.joyhat(device, hatX, hatY); } @@ -704,7 +703,7 @@ private void handleJoystickHatEvent(int device, int hatX, int hatY) { private void handleJoystickConnectionChangedEvent(int device, boolean connected, String name) { if (shouldDispatchInputToRenderThread()) { - mRenderView.queueOnRenderThread(() -> GodotLib.joyconnectionchanged(device, connected, name)); + godot.runOnRenderThread(() -> GodotLib.joyconnectionchanged(device, connected, name)); } else { GodotLib.joyconnectionchanged(device, connected, name); } @@ -712,7 +711,7 @@ private void handleJoystickConnectionChangedEvent(int device, boolean connected, void handleKeyEvent(int physicalKeycode, int unicode, int keyLabel, boolean pressed, boolean echo) { if (shouldDispatchInputToRenderThread()) { - mRenderView.queueOnRenderThread(() -> GodotLib.key(physicalKeycode, unicode, keyLabel, pressed, echo)); + godot.runOnRenderThread(() -> GodotLib.key(physicalKeycode, unicode, keyLabel, pressed, echo)); } else { GodotLib.key(physicalKeycode, unicode, keyLabel, pressed, echo); } 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 c975c29e9693..7667c0f6a224 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 @@ -200,6 +200,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() */ @@ -232,37 +242,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(int, 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(int, 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(int surfaceId, Surface surface) {} + + /** + * This is called on the render thread after the surface is created and whenever the surface + * size changes. + */ + public void onRenderSurfaceChanged(int surfaceId, 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(int, 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(int, Surface)} instead. */ + @Deprecated public void onVkSurfaceCreated(Surface surface) {} /** diff --git a/platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/render/EGLLogWrapper.java similarity index 99% rename from platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper.java rename to platform/android/java/lib/src/org/godotengine/godot/render/EGLLogWrapper.java index af16cfce7448..cfc073d5e3b6 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper.java +++ b/platform/android/java/lib/src/org/godotengine/godot/render/EGLLogWrapper.java @@ -16,7 +16,7 @@ * limitations under the License. */ -package org.godotengine.godot.gl; +package org.godotengine.godot.render; import android.opengl.GLDebugHelper; import android.opengl.GLException; 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 73% 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 c9421a325737..913d9a0e28de 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 @@ -16,7 +16,7 @@ * limitations under the License. */ -package org.godotengine.godot.gl; +package org.godotengine.godot.render; import android.content.Context; import android.opengl.EGL14; @@ -24,6 +24,8 @@ import android.opengl.GLDebugHelper; import android.util.AttributeSet; import android.util.Log; +import android.util.SparseArray; +import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; @@ -118,14 +120,7 @@ *

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. + * {@link Renderer#setRenderMode}. The default is continuous rendering. *

*

Handling events

*

@@ -172,23 +167,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 +204,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 @@ -323,8 +288,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,12 +303,7 @@ 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. @@ -360,8 +320,7 @@ public void setRenderer(Renderer renderer) { mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory(); } mRenderer = renderer; - mGLThread = new GLThread(mThisWeakRef); - mGLThread.start(); + mRenderer.startRenderer(); } /** @@ -478,54 +437,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().glSurfaceCreated(getId(), mThisWeakRef); } /** @@ -534,7 +451,7 @@ public void surfaceCreated(SurfaceHolder holder) { */ public void surfaceDestroyed(SurfaceHolder holder) { // Surface will be destroyed when we return - mGLThread.surfaceDestroyed(); + mRenderer.getRenderThread().glSurfaceDestroyed(getId()); } /** @@ -542,7 +459,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().glSurfaceChanged(getId(), w, h); } /** @@ -551,8 +468,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,36 +485,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(); - } - // -- 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. @@ -604,7 +492,7 @@ protected final void resumeGLThread() { * @param r the runnable to be run on the GL rendering thread. */ public void queueEvent(Runnable r) { - mGLThread.queueEvent(r); + mRenderer.getRenderThread().queueEvent(r); } /** @@ -615,18 +503,7 @@ public void queueEvent(Runnable r) { protected void onAttachedToWindow() { super.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(); + Log.d(TAG, "onAttachedToWindow for id " + getId() + " reattach =" + mDetached); } mDetached = false; } @@ -634,10 +511,7 @@ protected void onAttachedToWindow() { @Override protected void onDetachedFromWindow() { if (LOG_ATTACH_DETACH) { - Log.d(TAG, "onDetachedFromWindow"); - } - if (mGLThread != null) { - mGLThread.requestExitAndWait(); + Log.d(TAG, "onDetachedFromWindow for id " + getId()); } mDetached = true; super.onDetachedFromWindow(); @@ -675,6 +549,7 @@ public interface GLWrapper { GL wrap(GL gl); } + // -- GODOT start -- /** * A generic renderer interface. *

@@ -705,86 +580,112 @@ public interface GLWrapper { * 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 + * that it still needs. The {@link #onRenderSurfaceCreated(int, Surface)} 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. + * 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; + + /** + * Start the renderer which starts the render thread which in turn causes the + * rendering to start. + * *

- * 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. + * The following methods can only be called after + * startRenderer is called: + *

    + *
  • {@link #getRenderMode()} + *
  • {@link #queueEvent(Runnable)} + *
  • {@link #requestRender()} + *
  • {@link #setRenderMode(int)} + *
+ */ + void startRenderer(); + + /** + * 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 + */ + int 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. *

- * 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. + * 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 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. + * 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 + */ + void setRenderMode(int 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. + * May be called + * from any thread. Must not be called before a renderer has been set. + */ + void requestRender(); + + /** + * Returns the render thread used by this renderer. + */ + RenderThread getRenderThread(); + + /** + * Called when the surface is created or recreated. */ - void onSurfaceCreated(GL10 gl, EGLConfig config); + void onRenderSurfaceCreated(int surfaceId, Surface surface); /** * 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 + * Called after the surface is created and whenever the surface size changes. */ - void onSurfaceChanged(GL10 gl, int width, int height); + void onRenderSurfaceChanged(int surfaceId, Surface surface, 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); - // -- GODOT end -- + boolean onRenderDrawFrame(); } + // -- GODOT end -- /** * An interface for customizing the eglCreateContext and eglDestroyContext calls. @@ -1249,6 +1150,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 @@ -1258,15 +1160,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 @@ -1290,9 +1190,11 @@ public void run() { * synchronized(sGLThreadManager) block. */ private void stopEglSurfaceLocked() { - if (mHaveEglSurface) { - mHaveEglSurface = false; - mEglHelper.destroySurface(); + for (int i = 0; i < mRegisteredGLSurfaces.size(); i++) { + GLSurfaceInfo surfaceInfo = mRegisteredGLSurfaces.valueAt(i); + if (surfaceInfo != null) { + surfaceInfo.stopEglSurfaceLocked(); + } } } @@ -1301,30 +1203,32 @@ private void stopEglSurfaceLocked() { * synchronized(sGLThreadManager) block. */ private void stopEglContextLocked() { - if (mHaveEglContext) { - mEglHelper.finish(); - mHaveEglContext = false; + boolean hadEglContext = false; + for (int i = 0; i < mRegisteredGLSurfaces.size(); i++) { + GLSurfaceInfo surfaceInfo = mRegisteredGLSurfaces.valueAt(i); + if (surfaceInfo != null) { + hadEglContext |= surfaceInfo.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; + for (int i = 0; i < mRegisteredGLSurfaces.size(); i++) { + GLSurfaceInfo surfaceInfo = mRegisteredGLSurfaces.valueAt(i); + if (surfaceInfo != null) { + surfaceInfo.resetFrameParams(); + } + } + boolean wantRenderNotification = false; boolean doRenderNotification = false; - boolean askedToReleaseEglContext = false; - int w = 0; - int h = 0; + Runnable event = null; Runnable finishDrawingRunnable = null; @@ -1359,57 +1263,77 @@ private void guardedRun() throws InterruptedException { stopEglSurfaceLocked(); stopEglContextLocked(); mShouldReleaseEglContext = false; - askedToReleaseEglContext = true; - } - // Have we lost the EGL context? - if (lostEglContext) { - stopEglSurfaceLocked(); - stopEglContextLocked(); - lostEglContext = false; + for(int i = 0; i < mRegisteredGLSurfaces.size(); i++) { + GLSurfaceInfo surfaceInfo = mRegisteredGLSurfaces.valueAt(i); + if (surfaceInfo != null) { + surfaceInfo.mFrameParams.askedToReleaseEglContext = true; + } + } } - // When pausing, release the EGL surface: - if (pausing && mHaveEglSurface) { - if (LOG_SURFACE) { - Log.i("GLThread", "releasing EGL surface because paused tid=" + getId()); + boolean notifyAll = false; + for (int i = 0; i < mRegisteredGLSurfaces.size(); i++) { + GLSurfaceInfo surfaceInfo = mRegisteredGLSurfaces.valueAt(i); + if (surfaceInfo == null) { + continue; + } + + // Have we lost the EGL context? + if (surfaceInfo.mFrameParams.lostEglContext) { + surfaceInfo.stopEglSurfaceLocked(); + surfaceInfo.stopEglContextLocked(); + notifyAll = true; + surfaceInfo.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 && surfaceInfo.mHaveEglSurface) { if (LOG_SURFACE) { - Log.i("GLThread", "releasing EGL context because paused tid=" + getId()); + Log.i("GLThread", "releasing EGL surface for id " + surfaceInfo.mId + " because paused tid=" + getId()); } + surfaceInfo.stopEglSurfaceLocked(); } - } - // Have we lost the SurfaceView surface? - if ((! mHasSurface) && (! mWaitingForSurface)) { - if (LOG_SURFACE) { - Log.i("GLThread", "noticed surfaceView surface lost tid=" + getId()); + // When pausing, optionally release the EGL Context: + if (pausing && surfaceInfo.mHaveEglContext) { + GLSurfaceView view = surfaceInfo.mGLSurfaceViewWeakRef.get(); + boolean preserveEglContextOnPause = view == null ? + false : view.mPreserveEGLContextOnPause; + if (!preserveEglContextOnPause) { + surfaceInfo.stopEglContextLocked(); + notifyAll = true; + if (LOG_SURFACE) { + Log.i("GLThread", "releasing EGL context for id " + surfaceInfo.mId + " because paused tid=" + getId()); + } + } } - if (mHaveEglSurface) { - stopEglSurfaceLocked(); + + // Have we lost the SurfaceView surface? + if ((!surfaceInfo.mHasSurface) && (!surfaceInfo.mWaitingForSurface)) { + if (LOG_SURFACE) { + Log.i("GLThread", "noticed surfaceView surface with id " + surfaceInfo.mId + " lost tid=" + getId()); + } + if (surfaceInfo.mHaveEglSurface) { + surfaceInfo.stopEglSurfaceLocked(); + } + surfaceInfo.mWaitingForSurface = true; + surfaceInfo.mSurfaceIsBad = false; + notifyAll = true; } - 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()); + // Have we acquired the surface view surface? + if (surfaceInfo.mHasSurface && surfaceInfo.mWaitingForSurface) { + if (LOG_SURFACE) { + Log.i("GLThread", "noticed surfaceView surface with id " + surfaceInfo.mId + " acquired tid=" + getId()); + } + surfaceInfo.mWaitingForSurface = false; + notifyAll = true; } - mWaitingForSurface = false; + } + if (notifyAll) { sGLThreadManager.notifyAll(); + notifyAll = false; } if (doRenderNotification) { @@ -1430,55 +1354,62 @@ private void guardedRun() throws InterruptedException { // Ready to draw? if (readyToDraw()) { - // If we don't have an EGL context, try to acquire one. - if (! mHaveEglContext) { - if (askedToReleaseEglContext) { - askedToReleaseEglContext = false; - } else { - try { - mEglHelper.start(); - } catch (RuntimeException t) { - sGLThreadManager.releaseEglContextLocked(this); - throw t; - } - mHaveEglContext = true; - createEglContext = true; - - sGLThreadManager.notifyAll(); + for (int i = 0; i < mRegisteredGLSurfaces.size(); i++) { + GLSurfaceInfo surfaceInfo = mRegisteredGLSurfaces.valueAt(i); + if (surfaceInfo == null || !surfaceInfo.readyToDraw()) { + continue; } - } - if (mHaveEglContext && !mHaveEglSurface) { - mHaveEglSurface = true; - createEglSurface = true; - createGlInterface = true; - sizeChanged = true; - } - - if (mHaveEglSurface) { - if (mSizeChanged) { - sizeChanged = true; - w = mWidth; - h = mHeight; - mWantRenderNotification = true; - if (LOG_SURFACE) { - Log.i("GLThread", - "noticing that we want render notification tid=" - + getId()); + // If we don't have an EGL context, try to acquire one. + if (!surfaceInfo.mHaveEglContext) { + if (surfaceInfo.mFrameParams.askedToReleaseEglContext) { + surfaceInfo.mFrameParams.askedToReleaseEglContext = false; + } else { + try { + surfaceInfo.mEglHelper.start(); + } catch (RuntimeException t) { + sGLThreadManager.releaseEglContextLocked(this); + throw t; + } + surfaceInfo.mHaveEglContext = true; + surfaceInfo.mFrameParams.createEglContext = true; + + sGLThreadManager.notifyAll(); } + } - // Destroy and recreate the EGL surface. - createEglSurface = true; - - mSizeChanged = false; + if (surfaceInfo.mHaveEglContext && !surfaceInfo.mHaveEglSurface) { + surfaceInfo.mHaveEglSurface = true; + surfaceInfo.mFrameParams.createEglSurface = true; + surfaceInfo.mFrameParams.createGlInterface = true; + surfaceInfo.mFrameParams.sizeChanged = true; } - mRequestRender = false; - sGLThreadManager.notifyAll(); - if (mWantRenderNotification) { - wantRenderNotification = true; + + if (surfaceInfo.mHaveEglSurface) { + if (surfaceInfo.mSizeChanged) { + surfaceInfo.mFrameParams.sizeChanged = true; + surfaceInfo.mFrameParams.w = surfaceInfo.mWidth; + surfaceInfo.mFrameParams.h = surfaceInfo.mHeight; + mWantRenderNotification = true; + if (LOG_SURFACE) { + Log.i("GLThread", + "noticing that we want render notification for surface id " + surfaceInfo.mId + " tid=" + + getId()); + } + + // Destroy and recreate the EGL surface. + surfaceInfo.mFrameParams.createEglSurface = true; + + surfaceInfo.mSizeChanged = false; + } + mRequestRender = false; + sGLThreadManager.notifyAll(); + if (mWantRenderNotification) { + wantRenderNotification = true; + } } - break; } + break; } else { if (finishDrawingRunnable != null) { Log.w(TAG, "Warning, !readyToDraw() but waiting for " + @@ -1489,18 +1420,29 @@ private void guardedRun() throws InterruptedException { } // By design, this is the only place in a GLThread thread where we wait(). if (LOG_THREADS) { + int surfaceCount = mRegisteredGLSurfaces.size(); + StringBuilder registeredSurfacesLog = new StringBuilder("Registered surfaces (" + surfaceCount + ")"); + for (int i = 0; i < surfaceCount; i++) { + GLSurfaceInfo surfaceInfo = mRegisteredGLSurfaces.valueAt(i); + if (surfaceInfo == null) { + continue; + } + registeredSurfacesLog + .append("\n Surface id ").append(surfaceInfo.mId).append(" - ") + .append(" mHaveEglContext: ").append(surfaceInfo.mHaveEglContext) + .append(" mHaveEglSurface: ").append(surfaceInfo.mHaveEglSurface) + .append(" mFinishedCreatingEglSurface: ").append(surfaceInfo.mFinishedCreatingEglSurface) + .append(" mHasSurface: ").append(surfaceInfo.mHasSurface) + .append(" mSurfaceIsBad: ").append(surfaceInfo.mSurfaceIsBad) + .append(" mWaitingForSurface: ").append(surfaceInfo.mWaitingForSurface) + .append(" mWidth: ").append(surfaceInfo.mWidth) + .append(" mHeight: ").append(surfaceInfo.mHeight); + } 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); + + " mRenderMode: " + mRenderMode + + "\n" + registeredSurfacesLog); } sGLThreadManager.wait(); } @@ -1512,59 +1454,59 @@ private void guardedRun() throws InterruptedException { continue; } - if (createEglSurface) { - if (LOG_SURFACE) { - Log.w("GLThread", "egl createSurface"); + for (int i = 0; i < mRegisteredGLSurfaces.size(); i++) { + GLSurfaceInfo surfaceInfo = mRegisteredGLSurfaces.valueAt(i); + if (surfaceInfo == null) { + continue; } - if (mEglHelper.createSurface()) { - synchronized(sGLThreadManager) { - mFinishedCreatingEglSurface = true; - sGLThreadManager.notifyAll(); + + if (surfaceInfo.mFrameParams.createEglSurface) { + if (LOG_SURFACE) { + Log.w("GLThread", "egl createSurface for surface id " + surfaceInfo.mId); } - } else { - synchronized(sGLThreadManager) { - mFinishedCreatingEglSurface = true; - mSurfaceIsBad = true; - sGLThreadManager.notifyAll(); + if (surfaceInfo.mEglHelper.createSurface()) { + synchronized (sGLThreadManager) { + surfaceInfo.mFinishedCreatingEglSurface = true; + sGLThreadManager.notifyAll(); + } + } else { + synchronized (sGLThreadManager) { + surfaceInfo.mFinishedCreatingEglSurface = true; + surfaceInfo.mSurfaceIsBad = true; + sGLThreadManager.notifyAll(); + } + continue; } - continue; + surfaceInfo.mFrameParams.createEglSurface = false; } - createEglSurface = false; - } - if (createGlInterface) { - gl = (GL10) mEglHelper.createGL(); + if (surfaceInfo.mFrameParams.createGlInterface) { + surfaceInfo.mFrameParams.gl = (GL10) surfaceInfo.mEglHelper.createGL(); - createGlInterface = false; - } - - // -- GODOT start -- - if (createEglContext) { - if (LOG_RENDERER) { - Log.w("GLThread", "onSurfaceCreated"); + surfaceInfo.mFrameParams.createGlInterface = false; } - GLSurfaceView view = mGLSurfaceViewWeakRef.get(); - if (view != null) { + + if (surfaceInfo.mFrameParams.createEglContext) { + if (LOG_RENDERER) { + Log.w("GLThread", "onSurfaceCreated for surface id " + surfaceInfo.mId); + } try { - view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig); + mRenderer.onRenderSurfaceCreated(surfaceInfo.mId, null); } finally { } + surfaceInfo.mFrameParams.createEglContext = false; } - createEglContext = false; - } - if (sizeChanged) { - if (LOG_RENDERER) { - Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")"); - } - GLSurfaceView view = mGLSurfaceViewWeakRef.get(); - if (view != null) { + if (surfaceInfo.mFrameParams.sizeChanged) { + if (LOG_RENDERER) { + Log.w("GLThread", "onSurfaceChanged(" + surfaceInfo.mFrameParams.w + ", " + surfaceInfo.mFrameParams.h + ") for surface id " + surfaceInfo.mId); + } try { - view.mRenderer.onSurfaceChanged(gl, w, h); + mRenderer.onRenderSurfaceChanged(surfaceInfo.mId, null, surfaceInfo.mFrameParams.w, surfaceInfo.mFrameParams.h); } finally { } + surfaceInfo.mFrameParams.sizeChanged = false; } - sizeChanged = false; } boolean swapBuffers = false; @@ -1572,43 +1514,46 @@ private void guardedRun() throws InterruptedException { 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 {} - } + try { + swapBuffers = mRenderer.onRenderDrawFrame(); + 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); + for (int i = 0; i < mRegisteredGLSurfaces.size(); i++) { + GLSurfaceInfo surfaceInfo = mRegisteredGLSurfaces.valueAt(i); + if (surfaceInfo == null) { + continue; + } - synchronized (sGLThreadManager) { - mSurfaceIsBad = true; - sGLThreadManager.notifyAll(); - } - break; + int swapError = surfaceInfo.mEglHelper.swap(); + switch (swapError) { + case EGL10.EGL_SUCCESS: + break; + case EGL11.EGL_CONTEXT_LOST: + if (LOG_SURFACE) { + Log.i("GLThread", "egl context lost for surface id " + surfaceInfo.mId + " tid=" + getId()); + } + surfaceInfo.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 for surface id " + surfaceInfo.mId, swapError); + + synchronized (sGLThreadManager) { + surfaceInfo.mSurfaceIsBad = true; + sGLThreadManager.notifyAll(); + } + break; + } } } - // -- GODOT end -- if (wantRenderNotification) { doRenderNotification = true; @@ -1621,24 +1566,34 @@ private void guardedRun() throws InterruptedException { * clean-up everything... */ synchronized (sGLThreadManager) { + Log.d("GLThread", "Exiting render thread"); + mRenderer.destroyRenderer(); + stopEglSurfaceLocked(); stopEglContextLocked(); } } } - 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; + } + + for (int i = 0; i < mRegisteredGLSurfaces.size(); i++) { + GLSurfaceInfo surfaceInfo = mRegisteredGLSurfaces.valueAt(i); + if (surfaceInfo != null && surfaceInfo.readyToDraw()) { + return true; + } + } + + return false; } + @Override public void setRenderMode(int renderMode) { - if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY)) ) { + if ( !((Renderer.RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= Renderer.RENDERMODE_CONTINUOUSLY)) ) { throw new IllegalArgumentException("renderMode"); } synchronized(sGLThreadManager) { @@ -1647,12 +1602,14 @@ public void setRenderMode(int renderMode) { } } + @Override public int getRenderMode() { synchronized(sGLThreadManager) { return mRenderMode; } } + @Override public void requestRender() { synchronized(sGLThreadManager) { mRequestRender = true; @@ -1696,27 +1653,41 @@ public void requestRenderAndNotify(Runnable finishDrawing) { } } - public void surfaceCreated() { + @Override + public void glSurfaceCreated(int id, WeakReference glSurfaceViewWeakRef) { synchronized(sGLThreadManager) { if (LOG_THREADS) { - Log.i("GLThread", "surfaceCreated tid=" + getId()); + Log.i("GLThread", "surfaceCreated for id " + id + "with tid=" + getId()); } - mHasSurface = true; - mFinishedCreatingEglSurface = false; + + GLSurfaceInfo surfaceInfo = new GLSurfaceInfo(id, glSurfaceViewWeakRef); + surfaceInfo.mHasSurface = true; + surfaceInfo.mFinishedCreatingEglSurface = false; + mRegisteredGLSurfaces.put(id, surfaceInfo); + sGLThreadManager.notifyAll(); } } - public void surfaceDestroyed() { + @Override + public void glSurfaceDestroyed(int id) { synchronized(sGLThreadManager) { if (LOG_THREADS) { - Log.i("GLThread", "surfaceDestroyed tid=" + getId()); + Log.i("GLThread", "surfaceDestroyed for id " + id + " with tid=" + getId()); } - mHasSurface = false; + + GLSurfaceInfo surfaceInfo = mRegisteredGLSurfaces.get(id); + if (surfaceInfo == null) { + return; + } + + surfaceInfo.mHasSurface = false; sGLThreadManager.notifyAll(); + mRegisteredGLSurfaces.delete(id); } } + @Override public void onPause() { synchronized (sGLThreadManager) { if (LOG_PAUSE_RESUME) { @@ -1727,6 +1698,7 @@ public void onPause() { } } + @Override public void onResume() { synchronized (sGLThreadManager) { if (LOG_PAUSE_RESUME) { @@ -1739,11 +1711,18 @@ public void onResume() { } } - public void onWindowResize(int w, int h) { + @Override + public void glSurfaceChanged(int id, int w, int h) { synchronized (sGLThreadManager) { - mWidth = w; - mHeight = h; - mSizeChanged = true; + GLSurfaceInfo surfaceInfo = mRegisteredGLSurfaces.get(id); + if (surfaceInfo == null) { + return; + } + + surfaceInfo.mWidth = w; + surfaceInfo.mHeight = h; + surfaceInfo.mSizeChanged = true; + mRequestRender = true; mRenderComplete = false; @@ -1801,35 +1780,108 @@ public void queueEvent(Runnable r) { 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 boolean mRequestRender; private boolean mWantRenderNotification; private boolean mRenderComplete; - private ArrayList mEventQueue = new ArrayList(); - private boolean mSizeChanged = true; + private final ArrayList mEventQueue = new ArrayList(); private Runnable mFinishDrawingRunnable = null; + private final GodotRenderer mRenderer; - // End of member variables protected by the sGLThreadManager monitor. + private final SparseArray mRegisteredGLSurfaces = new SparseArray<>(); - 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 mHasSurface; + private boolean mSurfaceIsBad; + private boolean mWaitingForSurface; + 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 int mId; + private final FrameParams mFrameParams = new FrameParams(); + + GLSurfaceInfo(int id, WeakReference glSurfaceViewWeakRef) { + mId = id; + mWidth = 0; + mHeight = 0; + mGLSurfaceViewWeakRef = glSurfaceViewWeakRef; + mEglHelper = new EglHelper(glSurfaceViewWeakRef); + mHaveEglContext = false; + mHaveEglSurface = false; + } + + boolean ableToDraw() { + return mHaveEglContext && mHaveEglSurface && readyToDraw(); + } + + boolean readyToDraw() { + return mHasSurface && (!mSurfaceIsBad) && (mWidth > 0) && (mHeight > 0); + } + + void stopEglSurfaceLocked() { + if (mHaveEglSurface) { + mHaveEglSurface = false; + mEglHelper.destroySurface(); + } + } + + boolean stopEglContextLocked() { + if (mHaveEglContext) { + mEglHelper.finish(); + mHaveEglContext = false; + return true; + } + return false; + } + + void resetFrameParams() { + mFrameParams.gl = null; + mFrameParams.createEglContext = false; + mFrameParams.createEglSurface = false; + mFrameParams.createGlInterface = 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 { + GL10 gl = null; + boolean createEglContext = false; + boolean createEglSurface = false; + boolean createGlInterface = false; + boolean lostEglContext = false; + boolean sizeChanged = false; + int w = 0; + int h = 0; + boolean askedToReleaseEglContext = false; + } + } } + // -- GODOT end -- static class LogWriter extends Writer { @@ -1865,7 +1917,7 @@ private void flushBuilder() { private void checkRenderThreadState() { - if (mGLThread != null) { + if (mRenderer != null) { throw new IllegalStateException( "setRenderer has already been called for this instance."); } @@ -1895,7 +1947,6 @@ public void releaseEglContextLocked(GLThread thread) { private final WeakReference mThisWeakRef = new WeakReference(this); - private GLThread mGLThread; private Renderer mRenderer; private boolean mDetached; private EGLConfigChooser mEGLConfigChooser; 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..b4a45b4187fd --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/render/GodotRenderer.kt @@ -0,0 +1,249 @@ +/**************************************************************************/ +/* 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.Godot +import org.godotengine.godot.GodotLib +import org.godotengine.godot.plugin.GodotPluginRegistry +import org.godotengine.godot.render.GLSurfaceView.GLThread + +/** + * 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(private val useVulkan: Boolean) : GLSurfaceView.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 + + internal var rendererResumed = false + + /** + * Thread used to drive the render logic. + */ + private val renderingThread: RenderThread by lazy { + if (useVulkan) { + VkThread(this) + } else { + GLThread(this) + } + } + + override fun getRenderThread() = renderingThread + + override fun startRenderer() { + if (isStarted) { + return + } + isStarted = true + renderingThread.start() + } + + fun queueOnRenderThread(runnable: Runnable) { + renderingThread.queueEvent(runnable) + } + + override fun setRenderMode(renderMode: Int) { + renderingThread.setRenderMode(renderMode) + } + + override fun getRenderMode(): Int { + return renderingThread.getRenderMode() + } + + override fun requestRender() { + renderingThread.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. + *

+ */ + internal 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. + *

+ */ + internal fun pauseRenderer() { + Log.d(TAG, "Pausing renderer") + + GodotLib.onRendererPaused() + rendererResumed = false + } + + /** + * Called when the rendering thread is destroyed and used as signal to tear down the Vulkan logic. + */ + fun destroyRenderer() { + Log.d(TAG, "Destroying renderer") + GodotLib.ondestroy() + } + + /** + * The Activity was resumed, let's resume the render thread. + */ + fun onActivityStarted() { + renderingThread.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() { + renderingThread.onPause() + } + + /** + * Destroy the render thread. + */ + fun onActivityDestroyed() { + renderingThread.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(): Boolean { + 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 + } + } + + val swapBuffers = GodotLib.step() + for (plugin in pluginRegistry.allPlugins) { + if (useVulkan) { + plugin.onVkDrawFrame() + } else { + plugin.onGLDrawFrame(null) + } + plugin.onRenderDrawFrame() + } + return swapBuffers + } + + override fun onRenderSurfaceChanged(surfaceId: Int, surface: Surface?, width: Int, height: Int) { + GodotLib.resize(surfaceId, surface, width, height) + + for (plugin in pluginRegistry.allPlugins) { + if (surfaceId == Godot.PRIMARY_RENDER_VIEW_ID) { + if (useVulkan) { + plugin.onVkSurfaceChanged(surface, width, height) + } else { + plugin.onGLSurfaceChanged(null, width, height) + } + } + plugin.onRenderSurfaceChanged(surfaceId, surface, width, height) + } + } + + override fun onRenderSurfaceCreated(surfaceId: Int, surface: Surface?) { + GodotLib.newcontext(surfaceId, surface) + + for (plugin in pluginRegistry.allPlugins) { + if (surfaceId == Godot.PRIMARY_RENDER_VIEW_ID) { + if (useVulkan) { + plugin.onVkSurfaceCreated(surface) + } else { + plugin.onGLSurfaceCreated(null, null) + } + } + plugin.onRenderSurfaceCreated(surfaceId, surface) + } + } +} 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 56% 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 9d44d8826cb8..f251ed45b59f 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,61 +28,58 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -package org.godotengine.godot.gl; - -import org.godotengine.godot.GodotLib; -import org.godotengine.godot.plugin.GodotPlugin; -import org.godotengine.godot.plugin.GodotPluginRegistry; - -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.opengles.GL10; - -/** - * Godot's GL renderer implementation. - */ -public class GodotRenderer implements GLSurfaceView.Renderer { - 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; - } - - 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(); - } +@file:JvmName("RenderThread") +package org.godotengine.godot.render + +import android.view.SurfaceHolder +import java.lang.ref.WeakReference + +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: Int) + + abstract fun getRenderMode(): Int + + abstract fun requestRender() + + /** + * Invoked when the [android.view.Surface] has been created. + */ + open fun vkSurfaceCreated(id: Int, holder: SurfaceHolder) { } + + /** + * Invoked following structural updates to [android.view.Surface]. + */ + open fun vkSurfaceChanged(id: Int, holder: SurfaceHolder, width: Int, height: Int) { } + + /** + * Invoked when the [android.view.Surface] is no longer available. + */ + open fun vkSurfaceDestroyed(id: Int, holder: SurfaceHolder) { } + + open fun glSurfaceCreated(id: Int, glSurfaeViewWeakRef: WeakReference) {} + + open fun glSurfaceChanged(id: Int, width: Int, height: Int) {} + + open fun glSurfaceDestroyed(id: Int) {} } diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt b/platform/android/java/lib/src/org/godotengine/godot/render/VkSurfaceView.kt similarity index 70% rename from platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt rename to platform/android/java/lib/src/org/godotengine/godot/render/VkSurfaceView.kt index 791b42544432..8814be948154 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/render/VkSurfaceView.kt @@ -29,7 +29,7 @@ /**************************************************************************/ @file:JvmName("VkSurfaceView") -package org.godotengine.godot.vulkan +package org.godotengine.godot.render import android.content.Context import android.view.SurfaceHolder @@ -44,29 +44,22 @@ import android.view.SurfaceView *

    *
  • 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. + *
  • Accepts [GodotRenderer] object that does the actual rendering. + *
  • Renders on the [VkThread] thread provided by the [GodotRenderer] to decouple rendering + * performance from the UI thread. *
*/ -open internal class VkSurfaceView(context: Context) : SurfaceView(context), SurfaceHolder.Callback { +internal open 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 + private lateinit var renderer: GodotRenderer init { isClickable = true @@ -74,62 +67,26 @@ open internal class VkSurfaceView(context: Context) : SurfaceView(context), Surf } /** - * Set the [VkRenderer] associated with the view, and starts the thread that will drive the vulkan + * Set the [GodotRenderer] 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) { + fun startRenderer(renderer: GodotRenderer) { 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() - } - - /** - * Tear down the rendering thread. - * - * Must not be called before a [VkRenderer] has been set. - */ - fun onDestroy() { - vkThread.blockingExit() + this.renderer.startRenderer() } override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { - vkThread.onSurfaceChanged(width, height) + renderer.renderThread.vkSurfaceChanged(id, holder, width, height) } override fun surfaceDestroyed(holder: SurfaceHolder) { - vkThread.onSurfaceDestroyed() + renderer.renderThread.vkSurfaceDestroyed(id, holder) } override fun surfaceCreated(holder: SurfaceHolder) { - vkThread.onSurfaceCreated() + renderer.renderThread.vkSurfaceCreated(id, 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 61% 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 8c0065b31ea9..b1e09d062053 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,40 @@ /**************************************************************************/ @file:JvmName("VkThread") -package org.godotengine.godot.vulkan +package org.godotengine.godot.render import android.util.Log +import android.util.SparseArray +import android.view.Surface +import android.view.SurfaceHolder 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() + /** + * Holds the [Surface] onto which rendering is done. + */ + private val renderVkSurfaces = SparseArray() + /** * Used to synchronize interaction with other threads (e.g: main thread). */ @@ -59,41 +72,63 @@ 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 = GLSurfaceView.Renderer.RENDERMODE_CONTINUOUSLY + private var requestRender = true + + private val hasSurface + get() = renderVkSurfaces.size() > 0 /** * 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 + get() = threadResumed && (requestRender || (renderMode == GLSurfaceView.Renderer.RENDERMODE_CONTINUOUSLY)) && hasSurface private fun threadExiting() { lock.withLock { + Log.d(TAG, "Exiting render thread") + renderer.destroyRenderer() + exited = true lockCondition.signalAll() } } + override fun setRenderMode(renderMode: Int) { + require(GLSurfaceView.Renderer.RENDERMODE_WHEN_DIRTY <= renderMode && renderMode <= GLSurfaceView.Renderer.RENDERMODE_CONTINUOUSLY) { "renderMode" } + lock.withLock { + this.renderMode = renderMode + lockCondition.signalAll() + } + } + + override fun getRenderMode(): Int { + return lock.withLock { + renderMode + } + } + + override fun requestRender() { + lock.withLock { + requestRender = true + lockCondition.signalAll() + } + } + /** * Queue an event on the [VkThread]. */ - fun queueEvent(event: Runnable) { + override fun queueEvent(event: Runnable) { lock.withLock { eventQueue.add(event) lockCondition.signalAll() } } - /** - * Request the thread to exit and block until it's done. - */ - fun blockingExit() { + override fun requestExitAndWait() { lock.withLock { shouldExit = true lockCondition.signalAll() @@ -108,54 +143,48 @@ 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 vkSurfaceCreated(id: Int, holder: SurfaceHolder) { // 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 vkSurfaceChanged(id: Int, holder: SurfaceHolder, width: Int, height: Int) { lock.withLock { - hasSurface = true - surfaceChanged = true - this.width = width - this.height = height + val surfaceInfo = renderVkSurfaces.get(id, SurfaceInfo(holder, width, height)) + surfaceInfo.apply { + surfaceChanged = true + this.width = width + this.height = height + } + requestRender = true + renderVkSurfaces.put(id, surfaceInfo) lockCondition.signalAll() } } - /** - * Invoked when the [android.view.Surface] is no longer available. - */ - fun onSurfaceDestroyed() { + override fun vkSurfaceDestroyed(id: Int, holder: SurfaceHolder) { lock.withLock { - hasSurface = false + renderVkSurfaces.delete(id) lockCondition.signalAll() } } @@ -171,7 +200,6 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk while (true) { // Code path for exiting the thread loop. if (shouldExit) { - vkRenderer.onVkDestroy() return } @@ -183,28 +211,35 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk } if (readyToDraw) { - if (!rendererResumed) { - rendererResumed = true - vkRenderer.onVkResume() + if (!renderer.rendererResumed) { + renderer.resumeRenderer() + } - if (!rendererInitialized) { - rendererInitialized = true - vkRenderer.onVkSurfaceCreated(vkSurfaceView.holder.surface) + if (!rendererInitialized) { + rendererInitialized = true + for (index in 0 until renderVkSurfaces.size()) { + val surfaceId = renderVkSurfaces.keyAt(index) + val surfaceInfo = renderVkSurfaces.valueAt(index) + renderer.onRenderSurfaceCreated(surfaceId, surfaceInfo.holder.surface) } } - if (surfaceChanged) { - vkRenderer.onVkSurfaceChanged(vkSurfaceView.holder.surface, width, height) - surfaceChanged = false + for (index in 0 until renderVkSurfaces.size()) { + val surfaceId = renderVkSurfaces.keyAt(index) + val surfaceInfo = renderVkSurfaces.valueAt(index) + if (surfaceInfo.surfaceChanged) { + renderer.onRenderSurfaceChanged(surfaceId, surfaceInfo.holder.surface, surfaceInfo.width, surfaceInfo.height) + surfaceInfo.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() + } else if (renderer.rendererResumed) { + // If we aren't ready to draw but the renderer is resumed, that means + // we either lost a surface or the app was paused. + renderer.pauseRenderer() } // We only reach this state if we are not ready to draw and have no queued events, so // we wait. @@ -217,11 +252,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/vulkan/VkRenderer.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt deleted file mode 100644 index 6f09f51d4c26..000000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt +++ /dev/null @@ -1,108 +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.view.Surface - -import org.godotengine.godot.Godot -import org.godotengine.godot.GodotLib -import org.godotengine.godot.plugin.GodotPlugin -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 { - 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() - } - - /** - * Called when the rendering thread is destroyed and used as signal to tear down the Vulkan logic. - */ - fun onVkDestroy() { - } -} diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java index 942e57308cf2..9089526df9ed 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java @@ -30,7 +30,7 @@ package org.godotengine.godot.xr.ovr; -import org.godotengine.godot.gl.GLSurfaceView; +import org.godotengine.godot.render.GLSurfaceView; import android.opengl.EGLExt; diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java index 1f1034dacffc..f28d14efc3c4 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java @@ -30,7 +30,7 @@ package org.godotengine.godot.xr.ovr; -import org.godotengine.godot.gl.GLSurfaceView; +import org.godotengine.godot.render.GLSurfaceView; import android.opengl.EGL14; diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java index 60774eb24172..5574c676ec3e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java @@ -30,7 +30,7 @@ package org.godotengine.godot.xr.ovr; -import org.godotengine.godot.gl.GLSurfaceView; +import org.godotengine.godot.render.GLSurfaceView; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java index 147b4ea67669..04957f4186b5 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularConfigChooser.java @@ -30,7 +30,7 @@ package org.godotengine.godot.xr.regular; -import org.godotengine.godot.gl.GLSurfaceView; +import org.godotengine.godot.render.GLSurfaceView; import org.godotengine.godot.utils.GLUtils; import javax.microedition.khronos.egl.EGL10; diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java index 1f0d8592b318..9985dbf1db7f 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java +++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java @@ -30,7 +30,7 @@ package org.godotengine.godot.xr.regular; -import org.godotengine.godot.gl.GLSurfaceView; +import org.godotengine.godot.render.GLSurfaceView; import org.godotengine.godot.utils.GLUtils; import android.util.Log; diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 11e897facf17..80d6a397ff0f 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -205,7 +205,7 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env return true; } -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_resize(JNIEnv *env, jclass clazz, jint p_surface_id, jobject p_surface, jint p_width, jint p_height) { if (os_android) { os_android->set_display_size(Size2i(p_width, p_height)); @@ -221,7 +221,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, j } } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jint p_surface_id, jobject p_surface) { if (os_android) { if (step.get() == STEP_SETUP) { // During startup diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index d027da31fa43..a5b4bc8b95d4 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -40,8 +40,8 @@ extern "C" { JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject p_activity, jobject p_godot_instance, jobject p_asset_manager, jobject p_godot_io, jobject p_net_utils, jobject p_directory_access_handler, jobject p_file_access_handler, jboolean p_use_apk_expansion); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz); 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 void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jint p_surface_id, jobject p_surface, jint p_width, jint p_height); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jint p_surface_id, jobject p_surface); JNIEXPORT jboolean 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); diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 6e7f5ef5a178..a1bf86abf547 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -77,7 +77,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ _on_godot_setup_completed = p_env->GetMethodID(godot_class, "onGodotSetupCompleted", "()V"); _on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()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;"); + _get_render_view = p_env->GetMethodID(godot_class, "getRenderView", "(I)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"); @@ -86,9 +86,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ } GodotJavaWrapper::~GodotJavaWrapper() { - if (godot_view) { - delete godot_view; - } + godot_views.clear(); JNIEnv *env = get_jni_env(); ERR_FAIL_NULL(env); @@ -102,16 +100,18 @@ jobject GodotJavaWrapper::get_activity() { return activity; } -GodotJavaViewWrapper *GodotJavaWrapper::get_godot_view() { +GodotJavaViewWrapper *GodotJavaWrapper::get_godot_view(int id) { + GodotJavaViewWrapper *godot_view = godot_views[id]; 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); + jobject godot_render_view = env->CallObjectMethod(godot_instance, _get_render_view, id); if (!env->IsSameObject(godot_render_view, nullptr)) { godot_view = new GodotJavaViewWrapper(godot_render_view); + godot_views[id] = godot_view; } } return godot_view; diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index e86391d4e3e7..5a5cb8971b81 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -34,6 +34,7 @@ #include "java_godot_view_wrapper.h" #include "string_android.h" +#include "core/templates/hash_map.h" #include "core/templates/list.h" #include @@ -47,7 +48,7 @@ class GodotJavaWrapper { jclass godot_class; jclass activity_class; - GodotJavaViewWrapper *godot_view = nullptr; + HashMap godot_views; jmethodID _restart = nullptr; jmethodID _finish = nullptr; @@ -81,7 +82,7 @@ class GodotJavaWrapper { jobject get_activity(); - GodotJavaViewWrapper *get_godot_view(); + GodotJavaViewWrapper *get_godot_view(int id = 0); void on_godot_setup_completed(JNIEnv *p_env = nullptr); void on_godot_main_loop_started(JNIEnv *p_env = nullptr);