diff --git a/android/src/main/java/com/google/android/filament/SwapChain.java b/android/src/main/java/com/google/android/filament/SwapChain.java
new file mode 100644
index 00000000..391f1f6e
--- /dev/null
+++ b/android/src/main/java/com/google/android/filament/SwapChain.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.filament;
+
+import androidx.annotation.NonNull;
+
+/**
+ * A SwapChain represents an Operating System's native renderable surface.
+ *
+ *
Typically it's a native window or a view. Because a SwapChain is initialized
+ * from a native object, it is given to filament as an Object, which must be of the
+ * proper type for each platform filament is running on.
+ * SwapChain swapChain = engine.createSwapChain(nativeWindow);
+ *
+ *
+ * The nativeWindow parameter above must be of type:
| Platform | nativeWindow type |
|---|---|
| Android | {@link android.view.Surface Surface} |
+ * + *
A {@link android.view.Surface Surface} can be retrieved from a + * {@link android.view.SurfaceView SurfaceView} or {@link android.view.SurfaceHolder SurfaceHolder} + * easily using {@link android.view.SurfaceHolder#getSurface SurfaceHolder.getSurface()} and/or + * {@link android.view.SurfaceView#getHolder SurfaceView.getHolder()}.
+ * + *To use a {@link android.view.TextureView Textureview} as a SwapChain, it is
+ * necessary to first get its {@link android.graphics.SurfaceTexture SurfaceTexture},
+ * for instance using {@link android.view.TextureView.SurfaceTextureListener SurfaceTextureListener}
+ * and then create a {@link android.view.Surface Surface}:
+ * // using a TextureView.SurfaceTextureListener:
+ * public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
+ * mSurface = new Surface(surfaceTexture);
+ * // mSurface can now be used with Engine.createSwapChain()
+ * }
+ *
+ *
+ * @see Engine
+ */
+public class SwapChain {
+ private final Object mSurface;
+ private long mNativeObject;
+
+ public static final long CONFIG_DEFAULT = 0x0;
+
+ /**
+ * This flag indicates that the SwapChain must be allocated with an
+ * alpha-channel.
+ */
+ public static final long CONFIG_TRANSPARENT = 0x1;
+
+ /**
+ * This flag indicates that the SwapChain may be used as a source surface
+ * for reading back render results. This config must be set when creating
+ * any SwapChain that will be used as the source for a blit operation.
+ *
+ * @see Renderer#copyFrame
+ */
+ public static final long CONFIG_READABLE = 0x2;
+
+ /**
+ * Indicates that the native X11 window is an XCB window rather than an XLIB window.
+ * This is ignored on non-Linux platforms and in builds that support only one X11 API.
+ */
+ public static final long CONFIG_ENABLE_XCB = 0x4;
+
+ SwapChain(long nativeSwapChain, Object surface) {
+ mNativeObject = nativeSwapChain;
+ mSurface = surface;
+ }
+
+ /**
+ * @return the native Object this SwapChain was created from or null
+ * for a headless SwapChain.
+ */
+ public Object getNativeWindow() {
+ return mSurface;
+ }
+
+ /**
+ * FrameCompletedCallback is a callback function that notifies an application when a frame's
+ * contents have completed rendering on the GPU.
+ *
+ * + * Use setFrameCompletedCallback to set a callback on an individual SwapChain. Each time a frame + * completes GPU rendering, the callback will be called. + *
+ * + *+ * The FrameCompletedCallback is guaranteed to be called on the main Filament thread. + *
+ * + *+ * Warning: Only Filament's Metal backend supports frame callbacks. Other backends ignore the + * callback (which will never be called) and proceed normally. + *
+ * + * @param handler A {@link java.util.concurrent.Executor Executor}. + * @param callback The Runnable callback to invoke. + */ + public void setFrameCompletedCallback(@NonNull Object handler, @NonNull Runnable callback) { + nSetFrameCompletedCallback(getNativeObject(), handler, callback); + } + + public long getNativeObject() { + if (mNativeObject == 0) { + throw new IllegalStateException("Calling method on destroyed SwapChain"); + } + return mNativeObject; + } + + void clearNativeObject() { + mNativeObject = 0; + } + + private static native void nSetFrameCompletedCallback(long nativeSwapChain, Object handler, Runnable callback); +} diff --git a/android/src/main/java/com/google/android/filament/android/DisplayHelper.java b/android/src/main/java/com/google/android/filament/android/DisplayHelper.java new file mode 100644 index 00000000..3fc1f5e3 --- /dev/null +++ b/android/src/main/java/com/google/android/filament/android/DisplayHelper.java @@ -0,0 +1,202 @@ +// /* +// * Copyright (C) 2020 The Android Open Source Project +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ + +// package com.google.android.filament.android; + +// import android.content.Context; +// import android.hardware.display.DisplayManager; +// import android.os.Build; +// import android.os.Handler; +// import android.view.Display; + +// import com.google.android.filament.Renderer; + +// import androidx.annotation.NonNull; +// import androidx.annotation.Nullable; + +// /** +// * DisplayHelper is here to help managing a Display, for instance being notified when its +// * resolution or refresh rate changes. +// */ +// public class DisplayHelper { + +// private Handler mHandler = null; +// private DisplayManager mDisplayManager; +// private Display mDisplay; +// private Renderer mRenderer; +// private DisplayManager.DisplayListener mListener; + +// /** +// * Creates a DisplayHelper which helps managing a {@link Display}. +// * +// * The {@link Display} to manage is specified with {@link #attach} +// * +// * @param context a {@link Context} to used to retrieve the {@link DisplayManager} +// */ +// public DisplayHelper(@NonNull Context context) { +// mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); +// } + +// /** +// * Creates a DisplayHelper which helps manage a {@link Display} and provides a Handler +// * where callbacks can execute filament code. Use this method if filament is executing +// * on another thread. +// * +// * @param context a {@link Context} to used to retrieve teh {@link DisplayManager} +// * @param handler a {@link Handler} used to run callbacks accessing filament +// */ +// public DisplayHelper(@NonNull Context context, @NonNull Handler handler) { +// this(context); +// mHandler = handler; +// } + +// @Override +// protected void finalize() throws Throwable { +// try { +// // just for safety +// detach(); +// } finally { +// super.finalize(); +// } +// } + +// /** +// * Sets the filament {@link Renderer} associated to the {@link Display}, from this point +// * on, {@link Renderer.DisplayInfo} will be automatically updated when the {@link Display} +// * properties change. +// * +// * This is typically called from {@link UiHelper.RendererCallback#onNativeWindowChanged}. +// * +// * @param renderer a filament {@link Renderer} instance +// * @param display a {@link Display} to be associated with the {@link Renderer} +// */ +// public void attach(@NonNull Renderer renderer, @NonNull Display display) { +// if (renderer == mRenderer && display == mDisplay) { +// return; +// } +// mRenderer = renderer; +// mDisplay = display; +// mListener = new DisplayManager.DisplayListener() { +// @Override +// public void onDisplayAdded(int displayId) { +// } +// @Override +// public void onDisplayRemoved(int displayId) { +// } +// @Override +// public void onDisplayChanged(int displayId) { +// if (displayId == display.getDisplayId()) { +// updateDisplayInfo(); +// } +// } +// }; +// mDisplayManager.registerDisplayListener(mListener, mHandler); + +// // always invoke the callback when it's registered +// if (mHandler != null) { +// mHandler.post(new Runnable() { +// @Override +// public void run() { +// updateDisplayInfo(); +// } +// }); +// } else { +// updateDisplayInfo(); +// } +// } + +// /** +// * Disconnect the previously set {@link Renderer} from {@link Display} +// * This is typically called from {@link UiHelper.RendererCallback#onDetachedFromSurface}. +// */ +// public void detach() { +// if (mListener != null) { +// mDisplayManager.unregisterDisplayListener(mListener); +// mListener = null; +// mDisplay = null; +// mRenderer = null; +// } +// } + +// private void updateDisplayInfo() { +// mRenderer.setDisplayInfo( +// DisplayHelper.getDisplayInfo(mDisplay, mRenderer.getDisplayInfo())); +// } + +// /** +// * Returns the {@link Display} currently monitored +// * @return the {@link Display} set in {@link #attach} or null +// */ +// public Display getDisplay() { +// return mDisplay; +// } + +// /** +// * Populate a {@link Renderer.DisplayInfo} with properties from the given {@link Display} +// * +// * @param display {@link Display} to get {@link Renderer.DisplayInfo} from +// * @param info an instance of {@link Renderer.DisplayInfo} or null +// * @return an populated instance of {@link Renderer.DisplayInfo} +// */ +// @NonNull +// public static Renderer.DisplayInfo getDisplayInfo(@NonNull Display display, @Nullable Renderer.DisplayInfo info) { +// if (info == null) { +// info = new Renderer.DisplayInfo(); +// } +// info.refreshRate = DisplayHelper.getRefreshRate(display); +// return info; +// } + +// /** +// * @return the {@link Display} application vsync offset 0 if not supported +// * @see Display#getAppVsyncOffsetNanos +// */ +// public static long getAppVsyncOffsetNanos(@NonNull Display display) { +// if (Build.VERSION.SDK_INT >= 29) { +// return display.getAppVsyncOffsetNanos(); +// } +// return 0; +// } + +// /** +// * @return the {@link Display} presentation deadline before the h/w vsync event in nanoseconds +// * @see Display#getPresentationDeadlineNanos +// */ +// public static long getPresentationDeadlineNanos(@NonNull Display display) { +// if (Build.VERSION.SDK_INT >= 29) { +// return display.getPresentationDeadlineNanos(); +// } +// // not supported, pick something reasonable +// return 11_600_000; +// } + +// /** +// * @return the {@link Display} refresh rate in Hz +// * @see Display#getRefreshRate +// */ +// public static float getRefreshRate(@NonNull Display display) { +// return display.getRefreshRate(); +// } + +// /** +// * Returns a {@link Display}'s refresh period in nanoseconds +// * @param display the {@link Display} to get the refresh period from +// * @return the {@link Display} refresh period in nanoseconds +// */ +// public static long getRefreshPeriodNanos(@NonNull Display display) { +// return (long) (1_000_000_000.0 / display.getRefreshRate()); +// } +// } diff --git a/android/src/main/java/com/google/android/filament/android/UiHelper.java b/android/src/main/java/com/google/android/filament/android/UiHelper.java new file mode 100644 index 00000000..ae1bdcd7 --- /dev/null +++ b/android/src/main/java/com/google/android/filament/android/UiHelper.java @@ -0,0 +1,581 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.filament.android; + +import android.graphics.PixelFormat; +import android.graphics.SurfaceTexture; +import android.os.Build; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.TextureView; + +import com.google.android.filament.SwapChain; + +/** + * UiHelper is a simple class that can manage either a SurfaceView, TextureView, or a SurfaceHolder + * so it can be used to render into with Filament. + * + * Here is a simple example with a SurfaceView. The code would be exactly the same with a + * TextureView: + * + *
+ * public class FilamentActivity extends Activity {
+ * private UiHelper mUiHelper;
+ * private SurfaceView mSurfaceView;
+ *
+ * // Filament specific APIs
+ * private Engine mEngine;
+ * private Renderer mRenderer;
+ * private View mView; // com.google.android.filament.View, not android.view.View
+ * private SwapChain mSwapChain;
+ *
+ * public void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ *
+ * // Create a SurfaceView and add it to the activity
+ * mSurfaceView = new SurfaceView(this);
+ * setContentView(mSurfaceView);
+ *
+ * // Create the Filament UI helper
+ * mUiHelper = new UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK);
+ *
+ * // Attach the SurfaceView to the helper, you could do the same with a TextureView
+ * mUiHelper.attachTo(mSurfaceView);
+ *
+ * // Set a rendering callback that we will use to invoke Filament
+ * mUiHelper.setRenderCallback(new UiHelper.RendererCallback() {
+ * public void onNativeWindowChanged(Surface surface) {
+ * if (mSwapChain != null) mEngine.destroySwapChain(mSwapChain);
+ * mSwapChain = mEngine.createSwapChain(surface, mUiHelper.getSwapChainFlags());
+ * }
+ *
+ * // The native surface went away, we must stop rendering.
+ * public void onDetachedFromSurface() {
+ * if (mSwapChain != null) {
+ * mEngine.destroySwapChain(mSwapChain);
+ *
+ * // Required to ensure we don't return before Filament is done executing the
+ * // destroySwapChain command, otherwise Android might destroy the Surface
+ * // too early
+ * mEngine.flushAndWait();
+ *
+ * mSwapChain = null;
+ * }
+ * }
+ *
+ * // The native surface has changed size. This is always called at least once
+ * // after the surface is created (after onNativeWindowChanged() is invoked).
+ * public void onResized(int width, int height) {
+ * // Compute camera projection and set the viewport on the view
+ * }
+ * });
+ *
+ * mEngine = Engine.create();
+ * mRenderer = mEngine.createRenderer();
+ * mView = mEngine.createView();
+ * // Create scene, camera, etc.
+ * }
+ *
+ * public void onDestroy() {
+ * super.onDestroy();
+ * // Always detach the surface before destroying the engine
+ * mUiHelper.detach();
+ *
+ * mEngine.destroy();
+ * }
+ *
+ * // This is an example of a render function. You will most likely invoke this from
+ * // a Choreographer callback to trigger rendering at vsync.
+ * public void render() {
+ * if (mUiHelper.isReadyToRender) {
+ * // If beginFrame() returns false you should skip the frame
+ * // This means you are sending frames too quickly to the GPU
+ * if (mRenderer.beginFrame(swapChain)) {
+ * mRenderer.render(mView);
+ * mRenderer.endFrame();
+ * }
+ * }
+ * }
+ * }
+ *
+ */
+public class UiHelper {
+ private static final String LOG_TAG = "UiHelper";
+ private static final boolean LOGGING = false;
+
+ private int mDesiredWidth;
+ private int mDesiredHeight;
+ private Object mNativeWindow;
+
+ private RendererCallback mRenderCallback;
+ private boolean mHasSwapChain;
+
+ private RenderSurface mRenderSurface;
+
+ private boolean mOpaque = true;
+ private boolean mOverlay = false;
+
+ /**
+ * Enum used to decide whether UiHelper should perform extra error checking.
+ *
+ * @see UiHelper#UiHelper(ContextErrorPolicy)
+ */
+ public enum ContextErrorPolicy {
+ /** Check for extra errors. */
+ CHECK,
+ /** Do not check for extra errors. */
+ DONT_CHECK
+ }
+
+ /**
+ * Interface used to know when the native surface is created, destroyed or resized.
+ *
+ * @see #setRenderCallback(RendererCallback)
+ */
+ public interface RendererCallback {
+ /**
+ * Called when the underlying native window has changed.
+ */
+ void onNativeWindowChanged(Surface surface);
+
+ /**
+ * Called when the surface is going away. After this call isReadyToRender()
+ * returns false. You MUST have stopped drawing when returning.
+ * This is called from detach() or if the surface disappears on its own.
+ */
+ void onDetachedFromSurface();
+
+ /**
+ * Called when the underlying native window has been resized.
+ */
+ void onResized(int width, int height);
+ }
+
+ private interface RenderSurface {
+ void resize(int width, int height);
+ void detach();
+ }
+
+ private static class SurfaceViewHandler implements RenderSurface {
+ private SurfaceView mSurfaceView;
+
+ SurfaceViewHandler(SurfaceView surface) {
+ mSurfaceView = surface;
+ }
+
+ @Override
+ public void resize(int width, int height) {
+ mSurfaceView.getHolder().setFixedSize(width, height);
+ }
+
+ @Override
+ public void detach() {
+ }
+ }
+
+ private static class SurfaceHolderHandler implements RenderSurface {
+ private SurfaceHolder mSurfaceHolder;
+
+ SurfaceHolderHandler(SurfaceHolder surface) {
+ mSurfaceHolder = surface;
+ }
+
+ @Override
+ public void resize(int width, int height) {
+ mSurfaceHolder.setFixedSize(width, height);
+ }
+
+ @Override
+ public void detach() {
+ }
+ }
+
+ private class TextureViewHandler implements RenderSurface {
+ private TextureView mTextureView;
+ private Surface mSurface;
+
+ TextureViewHandler(TextureView surface) { mTextureView = surface; }
+
+ @Override
+ public void resize(int width, int height) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+ mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height);
+ }
+ // the call above won't cause TextureView.onSurfaceTextureSizeChanged()
+ mRenderCallback.onResized(width, height);
+ }
+
+ @Override
+ public void detach() {
+ setSurface(null);
+ }
+
+ void setSurface(Surface surface) {
+ if (surface == null) {
+ if (mSurface != null) {
+ mSurface.release();
+ }
+ }
+ mSurface = surface;
+ }
+ }
+
+ /**
+ * Creates a UiHelper which will help manage the native surface provided by a
+ * SurfaceView or a TextureView.
+ */
+ public UiHelper() {
+ this(ContextErrorPolicy.CHECK);
+ }
+
+ /**
+ * Creates a UiHelper which will help manage the native surface provided by a
+ * SurfaceView or a TextureView.
+ *
+ * @param policy The error checking policy to use.
+ */
+ public UiHelper(ContextErrorPolicy policy) {
+ // TODO: do something with policy
+ }
+
+ /**
+ * Sets the renderer callback that will be notified when the native surface is
+ * created, destroyed or resized.
+ *
+ * @param renderCallback The callback to register.
+ */
+ public void setRenderCallback(@Nullable RendererCallback renderCallback) {
+ mRenderCallback = renderCallback;
+ }
+
+ /**
+ * Returns the current render callback associated with this UiHelper.
+ */
+ @Nullable
+ public RendererCallback getRenderCallback() {
+ return mRenderCallback;
+ }
+
+ /**
+ * Free resources associated to the native window specified in {@link #attachTo(SurfaceView)},
+ * {@link #attachTo(TextureView)}, or {@link #attachTo(SurfaceHolder)}.
+ */
+ public void detach() {
+ destroySwapChain();
+ mNativeWindow = null;
+ mRenderSurface = null;
+ }
+
+ /**
+ * Checks whether we are ready to render into the attached surface.
+ *
+ * Using OpenGL ES when this returns true, will result in drawing commands being lost,
+ * HOWEVER, GLES state will be preserved. This is useful to initialize the engine.
+ *
+ * @return true: rendering is possible, false: rendering is not possible.
+ */
+ public boolean isReadyToRender() {
+ return mHasSwapChain;
+ }
+
+ /**
+ * Set the size of the render target buffers of the native surface.
+ */
+ public void setDesiredSize(int width, int height) {
+ mDesiredWidth = width;
+ mDesiredHeight = height;
+ if (mRenderSurface != null) {
+ mRenderSurface.resize(width, height);
+ }
+ }
+
+ /**
+ * Returns the requested width for the native surface.
+ */
+ public int getDesiredWidth() {
+ return mDesiredWidth;
+ }
+
+ /**
+ * Returns the requested height for the native surface.
+ */
+ public int getDesiredHeight() {
+ return mDesiredHeight;
+ }
+
+ /**
+ * Returns true if the render target is opaque.
+ */
+ public boolean isOpaque() {
+ return mOpaque;
+ }
+
+ /**
+ * Controls whether the render target (SurfaceView or TextureView) is opaque or not.
+ * The render target is considered opaque by default.
+ *
+ * Must be called before calling {@link #attachTo(SurfaceView)}, {@link #attachTo(TextureView)},
+ * or {@link #attachTo(SurfaceHolder)}.
+ *
+ * @param opaque Indicates whether the render target should be opaque. True by default.
+ */
+ public void setOpaque(boolean opaque) {
+ mOpaque = opaque;
+ }
+
+ /**
+ * Returns true if the SurfaceView used as a render target should be positioned above
+ * other surfaces but below the activity's surface. False by default.
+ */
+ public boolean isMediaOverlay() {
+ return mOverlay;
+ }
+
+ /**
+ * Controls whether the surface of the SurfaceView used as a render target should be
+ * positioned above other surfaces but below the activity's surface. This property
+ * only has an effect when used in combination with {@link #setOpaque(boolean) setOpaque(false)}
+ * and does not affect TextureView targets.
+ *
+ * Must be called before calling {@link #attachTo(SurfaceView)}
+ * or {@link #attachTo(TextureView)}.
+ *
+ * Has no effect when using {@link #attachTo(SurfaceHolder)}.
+ *
+ * @param overlay Indicates whether the render target should be rendered below the activity's
+ * surface when transparent.
+ */
+ public void setMediaOverlay(boolean overlay) {
+ mOverlay = overlay;
+ }
+
+ /**
+ * Returns the flags to pass to
+ * {@link com.google.android.filament.Engine#createSwapChain(Object, long)} to honor all
+ * the options set on this UiHelper.
+ */
+ public long getSwapChainFlags() {
+ return isOpaque() ? SwapChain.CONFIG_DEFAULT : SwapChain.CONFIG_TRANSPARENT;
+ }
+
+ /**
+ * Associate UiHelper with a SurfaceView.
+ *
+ * As soon as SurfaceView is ready (i.e. has a Surface), we'll create the
+ * EGL resources needed, and call user callbacks if needed.
+ */
+ public void attachTo(@NonNull SurfaceView view) {
+ if (attach(view)) {
+ boolean translucent = !isOpaque();
+ // setZOrderOnTop() and setZOrderMediaOverlay() override each other,
+ // we must only call one of them
+ if (isMediaOverlay()) {
+ view.setZOrderMediaOverlay(translucent);
+ } else {
+ view.setZOrderOnTop(translucent);
+ }
+
+ int format = isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
+ view.getHolder().setFormat(format);
+
+ mRenderSurface = new SurfaceViewHandler(view);
+
+ final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
+ createSwapChain(holder.getSurface());
+ }
+
+ @Override
+ public void surfaceChanged(
+ SurfaceHolder holder, int format, int width, int height) {
+ // Note: this is always called at least once after surfaceCreated()
+ if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")");
+ mRenderCallback.onResized(width, height);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
+ destroySwapChain();
+ }
+ };
+
+ SurfaceHolder holder = view.getHolder();
+ holder.addCallback(callback);
+ if (mDesiredWidth > 0 && mDesiredHeight > 0) {
+ holder.setFixedSize(mDesiredWidth, mDesiredHeight);
+ }
+
+ // in case the SurfaceView's surface already existed
+ final Surface surface = holder.getSurface();
+ if (surface != null && surface.isValid()) {
+ callback.surfaceCreated(holder);
+ callback.surfaceChanged(holder, format,
+ holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height());
+ }
+ }
+ }
+
+ /**
+ * Associate UiHelper with a TextureView.
+ *
+ * As soon as TextureView is ready (i.e. has a buffer), we'll create the
+ * EGL resources needed, and call user callbacks if needed.
+ */
+ public void attachTo(@NonNull TextureView view) {
+ if (attach(view)) {
+ view.setOpaque(isOpaque());
+
+ mRenderSurface = new TextureViewHandler(view);
+
+ TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() {
+ @Override
+ public void onSurfaceTextureAvailable(
+ SurfaceTexture surfaceTexture, int width, int height) {
+ if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureAvailable()");
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+ if (mDesiredWidth > 0 && mDesiredHeight > 0) {
+ surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight);
+ }
+ }
+
+ Surface surface = new Surface(surfaceTexture);
+ TextureViewHandler textureViewHandler = (TextureViewHandler) mRenderSurface;
+ textureViewHandler.setSurface(surface);
+
+ createSwapChain(surface);
+
+ // Call this the first time because onSurfaceTextureSizeChanged()
+ // isn't called at initialization time
+ mRenderCallback.onResized(width, height);
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(
+ SurfaceTexture surfaceTexture, int width, int height) {
+ if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureSizeChanged()");
+ if (mDesiredWidth > 0 && mDesiredHeight > 0) {
+ surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight);
+ mRenderCallback.onResized(mDesiredWidth, mDesiredHeight);
+ } else {
+ mRenderCallback.onResized(width, height);
+ }
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
+ if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureDestroyed()");
+ destroySwapChain();
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) { }
+ };
+
+ view.setSurfaceTextureListener(listener);
+
+ // in case the View's SurfaceTexture already existed
+ if (view.isAvailable()) {
+ SurfaceTexture surfaceTexture = view.getSurfaceTexture();
+ listener.onSurfaceTextureAvailable(surfaceTexture, mDesiredWidth, mDesiredHeight);
+ }
+ }
+ }
+
+ /**
+ * Associate UiHelper with a SurfaceHolder.
+ *
+ * As soon as a Surface is created, we'll create the
+ * EGL resources needed, and call user callbacks if needed.
+ */
+ public void attachTo(@NonNull SurfaceHolder holder) {
+ if (attach(holder)) {
+ int format = isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
+ holder.setFormat(format);
+
+ mRenderSurface = new SurfaceHolderHandler(holder);
+
+ final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder surfaceHolder) {
+ if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
+ createSwapChain(holder.getSurface());
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ // Note: this is always called at least once after surfaceCreated()
+ if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")");
+ mRenderCallback.onResized(width, height);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
+ if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
+ destroySwapChain();
+ }
+ };
+
+ holder.addCallback(callback);
+ if (mDesiredWidth > 0 && mDesiredHeight > 0) {
+ holder.setFixedSize(mDesiredWidth, mDesiredHeight);
+ }
+
+ // in case the SurfaceHolder's surface already existed
+ final Surface surface = holder.getSurface();
+ if (surface != null && surface.isValid()) {
+ callback.surfaceCreated(holder);
+ callback.surfaceChanged(holder, format,
+ holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height());
+ }
+ }
+ }
+
+ private boolean attach(@NonNull Object nativeWindow) {
+ if (mNativeWindow != null) {
+ // we are already attached to a native window
+ if (mNativeWindow == nativeWindow) {
+ // nothing to do
+ return false;
+ }
+ destroySwapChain();
+ }
+ mNativeWindow = nativeWindow;
+ return true;
+ }
+
+ private void createSwapChain(@NonNull Surface surface) {
+ mRenderCallback.onNativeWindowChanged(surface);
+ mHasSwapChain = true;
+ }
+
+ private void destroySwapChain() {
+ if (mRenderSurface != null) {
+ mRenderSurface.detach();
+ }
+ mRenderCallback.onDetachedFromSurface();
+ mHasSwapChain = false;
+ }
+}