From 55933e2f620f825f2be72ae005f3c09497dfecd7 Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Fri, 30 Aug 2019 14:59:46 +0800 Subject: [PATCH 01/19] Flutter External Texture Based On Share Texture --- ci/licenses_golden/licenses_flutter | 4 ++ shell/platform/android/BUILD.gn | 2 + shell/platform/android/android_context_gl.cc | 4 ++ shell/platform/android/android_context_gl.h | 2 + ...droid_external_texture_gl_share_context.cc | 56 ++++++++++++++++++ ...ndroid_external_texture_gl_share_context.h | 43 ++++++++++++++ shell/platform/android/android_surface.h | 3 + shell/platform/android/android_surface_gl.cc | 3 + shell/platform/android/android_surface_gl.h | 3 + .../android/android_surface_software.cc | 4 ++ .../android/android_surface_software.h | 3 + .../flutter/embedding/engine/FlutterJNI.java | 18 ++++++ .../engine/renderer/FlutterRenderer.java | 51 +++++++++++++++- .../android/io/flutter/view/FlutterView.java | 42 +++++++++++++ .../io/flutter/view/TextureRegistry.java | 39 ++++++++++++ .../platform/android/platform_view_android.cc | 12 ++++ .../platform/android/platform_view_android.h | 6 ++ .../android/platform_view_android_jni.cc | 59 ++++++++++++++++++- shell/platform/darwin/ios/BUILD.gn | 2 + .../ios/framework/Headers/FlutterTexture.h | 12 ++++ .../ios/framework/Source/FlutterEngine.mm | 10 ++++ .../framework/Source/FlutterViewController.mm | 8 +++ .../ios_external_texture_gl_share_context.h | 40 +++++++++++++ .../ios_external_texture_gl_share_context.mm | 55 +++++++++++++++++ shell/platform/darwin/ios/ios_gl_context.h | 2 + shell/platform/darwin/ios/ios_gl_context.mm | 5 ++ shell/platform/darwin/ios/ios_surface.h | 2 + shell/platform/darwin/ios/ios_surface_gl.h | 2 + shell/platform/darwin/ios/ios_surface_gl.mm | 4 ++ .../darwin/ios/ios_surface_software.h | 3 + .../darwin/ios/ios_surface_software.mm | 4 ++ shell/platform/darwin/ios/platform_view_ios.h | 4 ++ .../platform/darwin/ios/platform_view_ios.mm | 13 ++++ 33 files changed, 518 insertions(+), 2 deletions(-) create mode 100644 shell/platform/android/android_external_texture_gl_share_context.cc create mode 100644 shell/platform/android/android_external_texture_gl_share_context.h create mode 100644 shell/platform/darwin/ios/ios_external_texture_gl_share_context.h create mode 100644 shell/platform/darwin/ios/ios_external_texture_gl_share_context.mm diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index ddfd07d2a2c9a..d23245f977b8f 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -539,6 +539,8 @@ FILE: ../../../flutter/shell/platform/android/android_environment_gl.h FILE: ../../../flutter/shell/platform/android/android_exports.lst FILE: ../../../flutter/shell/platform/android/android_external_texture_gl.cc FILE: ../../../flutter/shell/platform/android/android_external_texture_gl.h +FILE: ../../../flutter/shell/platform/android/android_external_texture_gl_share_context.cc +FILE: ../../../flutter/shell/platform/android/android_external_texture_gl_share_context.h FILE: ../../../flutter/shell/platform/android/android_native_window.cc FILE: ../../../flutter/shell/platform/android/android_native_window.h FILE: ../../../flutter/shell/platform/android/android_shell_holder.cc @@ -775,6 +777,8 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_i FILE: ../../../flutter/shell/platform/darwin/ios/framework/module.modulemap FILE: ../../../flutter/shell/platform/darwin/ios/ios_external_texture_gl.h FILE: ../../../flutter/shell/platform/darwin/ios/ios_external_texture_gl.mm +FILE: ../../../flutter/shell/platform/darwin/ios/ios_external_texture_gl_share_context.h +FILE: ../../../flutter/shell/platform/darwin/ios/ios_external_texture_gl_share_context.mm FILE: ../../../flutter/shell/platform/darwin/ios/ios_gl_context.h FILE: ../../../flutter/shell/platform/darwin/ios/ios_gl_context.mm FILE: ../../../flutter/shell/platform/darwin/ios/ios_gl_render_target.h diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 934120e0a563a..f58d5feadd686 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -28,6 +28,8 @@ shared_library("flutter_shell_native") { "android_environment_gl.h", "android_external_texture_gl.cc", "android_external_texture_gl.h", + "android_external_texture_gl_share_context.cc", + "android_external_texture_gl_share_context.h", "android_native_window.cc", "android_native_window.h", "android_shell_holder.cc", diff --git a/shell/platform/android/android_context_gl.cc b/shell/platform/android/android_context_gl.cc index d3fd715a38abc..ece127988371e 100644 --- a/shell/platform/android/android_context_gl.cc +++ b/shell/platform/android/android_context_gl.cc @@ -272,4 +272,8 @@ bool AndroidContextGL::Resize(const SkISize& size) { return true; } +EGLContext AndroidContextGL::GetShareContext() { + return context_; +} + } // namespace flutter diff --git a/shell/platform/android/android_context_gl.h b/shell/platform/android/android_context_gl.h index 50e15aba2ed4f..3c389b370ee0c 100644 --- a/shell/platform/android/android_context_gl.h +++ b/shell/platform/android/android_context_gl.h @@ -35,6 +35,8 @@ class AndroidContextGL : public fml::RefCountedThreadSafe { bool Resize(const SkISize& size); + EGLContext GetShareContext(); + private: fml::RefPtr environment_; fml::RefPtr window_; diff --git a/shell/platform/android/android_external_texture_gl_share_context.cc b/shell/platform/android/android_external_texture_gl_share_context.cc new file mode 100644 index 0000000000000..c620b5c2e6429 --- /dev/null +++ b/shell/platform/android/android_external_texture_gl_share_context.cc @@ -0,0 +1,56 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/android/android_external_texture_gl_share_context.h" + +#include + +#include "flutter/shell/platform/android/platform_view_android_jni.h" +#include "third_party/skia/include/gpu/GrBackendSurface.h" + +namespace flutter { + +AndroidExternalTextureShareContext::AndroidExternalTextureShareContext( + int64_t id, + int64_t shareTextureID) + : Texture(id), texture_id_(shareTextureID) {} + +AndroidExternalTextureShareContext::~AndroidExternalTextureShareContext() {} + +void AndroidExternalTextureShareContext::OnGrContextCreated() {} + +void AndroidExternalTextureShareContext::MarkNewFrameAvailable() {} + +void AndroidExternalTextureShareContext::Paint(SkCanvas& canvas, + const SkRect& bounds, + bool freeze, + GrContext* context) { + GrGLTextureInfo textureInfo = {GL_TEXTURE_EXTERNAL_OES, texture_id_, + GL_RGBA8_OES}; + + textureInfo.fTarget = GL_TEXTURE_2D; + transform.setIdentity(); + + GrBackendTexture backendTexture(1, 1, GrMipMapped::kNo, textureInfo); + sk_sp image = SkImage::MakeFromTexture( + canvas.getGrContext(), backendTexture, kTopLeft_GrSurfaceOrigin, + kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr); + if (image) { + SkAutoCanvasRestore autoRestore(&canvas, true); + canvas.translate(bounds.x(), bounds.y()); + canvas.scale(bounds.width(), bounds.height()); + if (!transform.isIdentity()) { + SkMatrix transformAroundCenter(transform); + + transformAroundCenter.preTranslate(-0.5, -0.5); + transformAroundCenter.postScale(1, -1); + transformAroundCenter.postTranslate(0.5, 0.5); + canvas.concat(transformAroundCenter); + } + canvas.drawImage(image, 0, 0); + } +} + +void AndroidExternalTextureShareContext::OnGrContextDestroyed() {} +} // namespace flutter diff --git a/shell/platform/android/android_external_texture_gl_share_context.h b/shell/platform/android/android_external_texture_gl_share_context.h new file mode 100644 index 0000000000000..f54fc087bd6ee --- /dev/null +++ b/shell/platform/android/android_external_texture_gl_share_context.h @@ -0,0 +1,43 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_ANDROID_EXTERNAL_TEXTURE_SHARE_CONTEXT_H_ +#define FLUTTER_SHELL_PLATFORM_ANDROID_EXTERNAL_TEXTURE_SHARE_CONTEXT_H_ + +#include +#include "flutter/flow/texture.h" +#include "flutter/fml/platform/android/jni_weak_ref.h" + +namespace flutter { + +class AndroidExternalTextureShareContext : public flutter::Texture { + public: + AndroidExternalTextureShareContext(int64_t id, int64_t shareTextureID); + + ~AndroidExternalTextureShareContext() override; + + void Paint(SkCanvas& canvas, + const SkRect& bounds, + bool freeze, + GrContext* context) override; + + void OnGrContextCreated() override; + + void OnGrContextDestroyed() override; + + void MarkNewFrameAvailable() override; + + private: + fml::jni::JavaObjectWeakGlobalRef surface_texture_; + + GLuint texture_id_ = 0; + + SkMatrix transform; + + FML_DISALLOW_COPY_AND_ASSIGN(AndroidExternalTextureShareContext); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_ANDROID_EXTERNAL_TEXTURE_GL_H_ diff --git a/shell/platform/android/android_surface.h b/shell/platform/android/android_surface.h index 43f4cf33d0d50..bdef5043cd665 100644 --- a/shell/platform/android/android_surface.h +++ b/shell/platform/android/android_surface.h @@ -7,6 +7,7 @@ #include +#include #include "flutter/fml/macros.h" #include "flutter/fml/platform/android/jni_util.h" #include "flutter/fml/platform/android/jni_weak_ref.h" @@ -36,6 +37,8 @@ class AndroidSurface { virtual bool ResourceContextClearCurrent() = 0; virtual bool SetNativeWindow(fml::RefPtr window) = 0; + + virtual EGLContext GetShareContext() = 0; }; } // namespace flutter diff --git a/shell/platform/android/android_surface_gl.cc b/shell/platform/android/android_surface_gl.cc index 737d9f293a518..4232ee7c65b01 100644 --- a/shell/platform/android/android_surface_gl.cc +++ b/shell/platform/android/android_surface_gl.cc @@ -130,4 +130,7 @@ ExternalViewEmbedder* AndroidSurfaceGL::GetExternalViewEmbedder() { return nullptr; } +EGLContext AndroidSurfaceGL::GetShareContext() { + return offscreen_context_->GetShareContext(); +} } // namespace flutter diff --git a/shell/platform/android/android_surface_gl.h b/shell/platform/android/android_surface_gl.h index d59302ad66509..d41e721f3c1b4 100644 --- a/shell/platform/android/android_surface_gl.h +++ b/shell/platform/android/android_surface_gl.h @@ -46,6 +46,9 @@ class AndroidSurfaceGL final : public GPUSurfaceGLDelegate, // |AndroidSurface| bool SetNativeWindow(fml::RefPtr window) override; + // |AndroidSurface| + EGLContext GetShareContext() override; + // |GPUSurfaceGLDelegate| bool GLContextMakeCurrent() override; diff --git a/shell/platform/android/android_surface_software.cc b/shell/platform/android/android_surface_software.cc index 620e15301a581..f656212a849d9 100644 --- a/shell/platform/android/android_surface_software.cc +++ b/shell/platform/android/android_surface_software.cc @@ -159,4 +159,8 @@ bool AndroidSurfaceSoftware::SetNativeWindow( return true; } +EGLContext AndroidSurfaceSoftware::GetShareContext() { + return 0; +} + } // namespace flutter diff --git a/shell/platform/android/android_surface_software.h b/shell/platform/android/android_surface_software.h index 30888091fe0d5..4af224d6be6eb 100644 --- a/shell/platform/android/android_surface_software.h +++ b/shell/platform/android/android_surface_software.h @@ -41,6 +41,9 @@ class AndroidSurfaceSoftware final : public AndroidSurface, // |AndroidSurface| bool SetNativeWindow(fml::RefPtr window) override; + // |AndroidSurface| + EGLContext GetShareContext() override; + // |GPUSurfaceSoftwareDelegate| sk_sp AcquireBackingStore(const SkISize& size) override; diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index fbd63888c19d1..ce0953ef6c1d4 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -14,6 +14,7 @@ import android.support.annotation.UiThread; import android.view.Surface; import android.view.SurfaceHolder; +import android.opengl.EGLContext; import java.nio.ByteBuffer; import java.util.HashSet; @@ -555,6 +556,23 @@ public void registerTexture(long textureId, @NonNull SurfaceTexture surfaceTextu private native void nativeRegisterTexture(long nativePlatformViewId, long textureId, @NonNull SurfaceTexture surfaceTexture); + + @UiThread + public void registerShareTexture(long texIndex, long shareTextureId) { + ensureAttachedToNative(); + nativeRegisterShareTexture(nativePlatformViewId, texIndex, shareTextureId); + } + + private native void nativeRegisterShareTexture(long nativePlatformViewId, long texIndex, long shareTextureId); + + @UiThread + public EGLContext getShareContext(long sdkInt) { + ensureAttachedToNative(); + return nativeGetShareContext(nativePlatformViewId,sdkInt); + } + + private native EGLContext nativeGetShareContext(long nativePlatformViewId,long sdkInt); + /** * Call this method to inform Flutter that a texture previously registered with * {@link #registerTexture(long, SurfaceTexture)} has a new frame available. diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index bac9132df255f..842432413f1a0 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -12,6 +12,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.Surface; +import android.opengl.EGLContext; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicLong; @@ -120,6 +121,24 @@ public SurfaceTextureEntry createSurfaceTexture() { return entry; } + @Override + public TextureRegistry.ShareTextureEntry createShareTexture(long shareTextureID) { + final ShareTextureRegistryEntry entry = new ShareTextureRegistryEntry(nextTextureId.getAndIncrement(),shareTextureID); + Log.v(TAG, "New ShareTexture ID: " + entry.id()); + registerShareTexture(entry.id(),shareTextureID); + return entry; + } + + @Override + public void onShareFrameAvaliable(int textureIndex) { + markTextureFrameAvailable(textureIndex); + } + + @Override + public EGLContext getShareContext(long sdkInt) { + return flutterJNI.getShareContext(sdkInt); + } + final class SurfaceTextureRegistryEntry implements TextureRegistry.SurfaceTextureEntry { private final long id; @NonNull @@ -178,7 +197,33 @@ public void release() { released = true; } } - //------ END TextureRegistry IMPLEMENTATION ---- + + final class ShareTextureRegistryEntry implements TextureRegistry.ShareTextureEntry { + private final long id; + private final long textureID; + private boolean released; + ShareTextureRegistryEntry(long id, long shareTextureID) { + this.id = id; + this.textureID = shareTextureID; + } + + @Override + public long id() { + return id; + } + + @Override + public void release() { + if (released) { + return; + } + released = true; + unregisterTexture(id); + } + } + +//------ END TextureRegistry IMPLEMENTATION ---- + // TODO(mattcarroll): describe the native behavior that this invokes public void surfaceCreated(@NonNull Surface surface) { @@ -241,6 +286,10 @@ private void registerTexture(long textureId, @NonNull SurfaceTexture surfaceText flutterJNI.registerTexture(textureId, surfaceTexture); } + private void registerShareTexture(long textureId, long shareTextureID) { + flutterJNI.registerShareTexture(textureId, shareTextureID); + } + // TODO(mattcarroll): describe the native behavior that this invokes private void markTextureFrameAvailable(long textureId) { flutterJNI.markTextureFrameAvailable(textureId); diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index f10a72414fafa..237561d8ba5ad 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -36,6 +36,7 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.opengl.EGLContext; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -802,6 +803,23 @@ public TextureRegistry.SurfaceTextureEntry createSurfaceTexture() { return entry; } + @Override + public void onShareFrameAvaliable(int textureIndex) { + mNativeView.getFlutterJNI().markTextureFrameAvailable(textureIndex); + } + + @Override + public TextureRegistry.ShareTextureEntry createShareTexture(long shareTextureID) { + final ShareTextureRegistryEntry entry = new ShareTextureRegistryEntry(nextTextureId.getAndIncrement(),shareTextureID); + mNativeView.getFlutterJNI().registerShareTexture(entry.id(),shareTextureID); + return entry; + } + + @Override + public EGLContext getShareContext(long sdkInt) { + return mNativeView.getFlutterJNI().getShareContext(sdkInt); + } + final class SurfaceTextureRegistryEntry implements TextureRegistry.SurfaceTextureEntry { private final long id; private final SurfaceTexture surfaceTexture; @@ -865,4 +883,28 @@ public void release() { mNativeView.getFlutterJNI().unregisterTexture(id); } } + + final class ShareTextureRegistryEntry implements TextureRegistry.ShareTextureEntry { + private final long id; + private final long textureID; + private boolean released; + ShareTextureRegistryEntry(long id, long shareTextureID) { + this.id = id; + this.textureID = shareTextureID; + } + + @Override + public long id() { + return id; + } + + @Override + public void release() { + if (released) { + return; + } + released = true; + mNativeView.getFlutterJNI().unregisterTexture(id); + } + } } diff --git a/shell/platform/android/io/flutter/view/TextureRegistry.java b/shell/platform/android/io/flutter/view/TextureRegistry.java index ed3134bb035dd..46887b6093554 100644 --- a/shell/platform/android/io/flutter/view/TextureRegistry.java +++ b/shell/platform/android/io/flutter/view/TextureRegistry.java @@ -5,6 +5,7 @@ package io.flutter.view; import android.graphics.SurfaceTexture; +import android.opengl.EGLContext; /** * Registry of backend textures used with a single {@link FlutterView} instance. @@ -39,4 +40,42 @@ interface SurfaceTextureEntry { */ void release(); } + + /** + * Informs the the Flutter Engine that the external texture has been updated, and to start a new rendering pipeline. + */ + void onShareFrameAvaliable(int textureIndex); + + /** + * The OpenGL context created in the Flutter Engine, which is safe to user for sharing + * Param: Android SDK version code + */ + EGLContext getShareContext(long sdkInt); + + /** + * Create a ShareTextureEntry, and registers a External Texture in Flutter Engine + * The paramater is a OpenGL Texture ID + * Generally, this is used for users who process image frame with OpenGL(such as filter with GPUImage), and display the image frame using Flutter external texture + * Unlike SurfaceTexture, this can directly send the processed textures from OpenGL to flutter rendering pipeline. + * Avoid the performance consumption of data writing to SurfaceTexture + * Requirement: The OpenGL Texture should created in the EGLContext which is created using getShareContext(sdkInt) as a ShareContext + * @return A ShareTextureEntry. + */ + ShareTextureEntry createShareTexture(long shareTextureID); + + /** + * A registry entry for a managed ShareTexture. + */ + interface ShareTextureEntry { + + /** + * @return The identity of this ShareTexture. + */ + long id(); + + /** + * Deregisters and releases this ShareTexture. + */ + void release(); + } } diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index 449a7e4571fba..d17387ebe32ce 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -11,6 +11,7 @@ #include "flutter/shell/common/shell_io_manager.h" #include "flutter/shell/gpu/gpu_surface_gl_delegate.h" #include "flutter/shell/platform/android/android_external_texture_gl.h" +#include "flutter/shell/platform/android/android_external_texture_gl_share_context.h" #include "flutter/shell/platform/android/android_surface_gl.h" #include "flutter/shell/platform/android/platform_message_response_android.h" #include "flutter/shell/platform/android/platform_view_android_jni.h" @@ -376,6 +377,17 @@ void PlatformViewAndroid::RegisterExternalTexture( std::make_shared(texture_id, surface_texture)); } +void PlatformViewAndroid::RegisterExternalShareTexture( + int64_t texture_id, + int64_t share_texture_id) { + RegisterTexture(std::make_shared( + texture_id, share_texture_id)); +} + +EGLContext PlatformViewAndroid::GetShareContext() { + return android_surface_->GetShareContext(); +} + // |PlatformView| std::unique_ptr PlatformViewAndroid::CreateVSyncWaiter() { return std::make_unique(task_runners_); diff --git a/shell/platform/android/platform_view_android.h b/shell/platform/android/platform_view_android.h index 74c0bbe6c26e7..7cf1a709b527b 100644 --- a/shell/platform/android/platform_view_android.h +++ b/shell/platform/android/platform_view_android.h @@ -10,6 +10,7 @@ #include #include +#include #include "flutter/fml/memory/weak_ptr.h" #include "flutter/fml/platform/android/jni_weak_ref.h" #include "flutter/fml/platform/android/scoped_java_ref.h" @@ -73,6 +74,11 @@ class PlatformViewAndroid final : public PlatformView { int64_t texture_id, const fml::jni::JavaObjectWeakGlobalRef& surface_texture); + void RegisterExternalShareTexture(int64_t texture_id, + int64_t share_texture_id); + + EGLContext GetShareContext(); + private: const fml::jni::JavaObjectWeakGlobalRef java_object_; const std::unique_ptr android_surface_; diff --git a/shell/platform/android/platform_view_android_jni.cc b/shell/platform/android/platform_view_android_jni.cc index f2d17dd13fbe1..6abe8a7da8995 100644 --- a/shell/platform/android/platform_view_android_jni.cc +++ b/shell/platform/android/platform_view_android_jni.cc @@ -8,6 +8,8 @@ #include +#include +#include #include "flutter/assets/directory_asset_bundle.h" #include "flutter/common/settings.h" #include "flutter/fml/file.h" @@ -444,6 +446,52 @@ static void RegisterTexture(JNIEnv* env, ); } +static void RegisterShareTexture(JNIEnv* env, + jobject jcaller, + jlong shell_holder, + jlong texture_id, + jlong share_texture_id) { + ANDROID_SHELL_HOLDER->GetPlatformView()->RegisterExternalShareTexture( + static_cast(texture_id), // + static_cast(share_texture_id) // + ); +} + +static jobject GetShareContext(JNIEnv* env, + jobject jcaller, + jlong shell_holder, + jlong sdk_int) { + EGLContext cxt = ANDROID_SHELL_HOLDER->GetPlatformView()->GetShareContext(); + + jclass eglcontextClassLocal = env->FindClass("android/opengl/EGLContext"); + jmethodID eglcontextConstructor; + jobject eglContext; + if (sdk_int >= 21) { + // 5.0and above + eglcontextConstructor = + env->GetMethodID(eglcontextClassLocal, "", "(J)V"); + if ((EGLContext)cxt == EGL_NO_CONTEXT) { + return env->NewObject(eglcontextClassLocal, eglcontextConstructor, + reinterpret_cast(EGL_NO_CONTEXT)); + } + eglContext = env->NewObject(eglcontextClassLocal, eglcontextConstructor, + reinterpret_cast(jlong(cxt))); + } else { + eglcontextConstructor = + env->GetMethodID(eglcontextClassLocal, "", "(I)V"); + int contextAddress = (int)(size_t)cxt; + if ((EGLContext)cxt == EGL_NO_CONTEXT) { + contextAddress = (int)(size_t)EGL_NO_CONTEXT; + return env->NewObject(eglcontextClassLocal, eglcontextConstructor, + reinterpret_cast(jint(contextAddress))); + } + eglContext = env->NewObject(eglcontextClassLocal, eglcontextConstructor, + reinterpret_cast(jint(contextAddress))); + } + + return eglContext; +} + static void MarkTextureFrameAvailable(JNIEnv* env, jobject jcaller, jlong shell_holder, @@ -592,7 +640,16 @@ bool RegisterApi(JNIEnv* env) { .signature = "(JJ)V", .fnPtr = reinterpret_cast(&UnregisterTexture), }, - + { + .name = "nativeRegisterShareTexture", + .signature = "(JJJ)V", + .fnPtr = reinterpret_cast(&RegisterShareTexture), + }, + { + .name = "nativeGetShareContext", + .signature = "(JJ)Landroid/opengl/EGLContext;", + .fnPtr = reinterpret_cast(&GetShareContext), + }, // Methods for Dart callback functionality. { .name = "nativeLookupCallbackInformation", diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 6e00a098ee4ed..fe2fd44512e53 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -81,6 +81,8 @@ shared_library("create_flutter_framework_dylib") { "framework/Source/vsync_waiter_ios.mm", "ios_external_texture_gl.h", "ios_external_texture_gl.mm", + "ios_external_texture_gl_share_context.h", + "ios_external_texture_gl_share_context.mm", "ios_gl_context.h", "ios_gl_context.mm", "ios_gl_render_target.h", diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterTexture.h b/shell/platform/darwin/ios/framework/Headers/FlutterTexture.h index e7cd01337deb9..5aec0fe2a80ee 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterTexture.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterTexture.h @@ -8,11 +8,21 @@ #import #import +#import #include "FlutterMacros.h" NS_ASSUME_NONNULL_BEGIN FLUTTER_EXPORT +@protocol FlutterShareTexture +/* + *This is for users who implements this protocol to return the texture they create for image + *processing(such as GPUImage) to flutter rendering pipe line for display. The returned texture + *format should be GL_RGBA The returned texture target should be GL_TEXTURE_2D + */ +- (GLuint)copyShareTexture; +@end + @protocol FlutterTexture - (CVPixelBufferRef _Nullable)copyPixelBuffer; @end @@ -20,8 +30,10 @@ FLUTTER_EXPORT FLUTTER_EXPORT @protocol FlutterTextureRegistry - (int64_t)registerTexture:(NSObject*)texture; +- (int64_t)registerShareTexture:(NSObject*)texture; - (void)textureFrameAvailable:(int64_t)textureId; - (void)unregisterTexture:(int64_t)textureId; +- (EAGLSharegroup*)getShareGroup; @end NS_ASSUME_NONNULL_END diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index ad7cfc263ee5d..884eaefec3822 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -554,6 +554,12 @@ - (int64_t)registerTexture:(NSObject*)texture { return textureId; } +- (int64_t)registerShareTexture:(NSObject*)texture { + int64_t textureId = _nextTextureId++; + self.iosPlatformView->RegisterExternalShareTexture(textureId, texture); + return textureId; +} + - (void)unregisterTexture:(int64_t)textureId { _shell->GetPlatformView()->UnregisterTexture(textureId); } @@ -562,6 +568,10 @@ - (void)textureFrameAvailable:(int64_t)textureId { _shell->GetPlatformView()->MarkTextureFrameAvailable(textureId); } +- (EAGLSharegroup*)getShareGroup { + return self.iosPlatformView->GetGLShareGroup(); +} + - (NSString*)lookupKeyForAsset:(NSString*)asset { return [FlutterDartProject lookupKeyForAsset:asset]; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 3cea602f86bad..e2b1401937c70 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1118,6 +1118,10 @@ - (int64_t)registerTexture:(NSObject*)texture { return [_engine.get() registerTexture:texture]; } +- (int64_t)registerShareTexture:(NSObject*)texture { + return [_engine.get() registerShareTexture:texture]; +} + - (void)unregisterTexture:(int64_t)textureId { [_engine.get() unregisterTexture:textureId]; } @@ -1126,6 +1130,10 @@ - (void)textureFrameAvailable:(int64_t)textureId { [_engine.get() textureFrameAvailable:textureId]; } +- (id)getShareGroup { + return [_engine.get() getShareGroup]; +} + - (NSString*)lookupKeyForAsset:(NSString*)asset { return [FlutterDartProject lookupKeyForAsset:asset]; } diff --git a/shell/platform/darwin/ios/ios_external_texture_gl_share_context.h b/shell/platform/darwin/ios/ios_external_texture_gl_share_context.h new file mode 100644 index 0000000000000..33ea6b12b8627 --- /dev/null +++ b/shell/platform/darwin/ios/ios_external_texture_gl_share_context.h @@ -0,0 +1,40 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_IOS_EXTERNAL_TEXTURE_SHARE_CONTEXT_H_ +#define FLUTTER_SHELL_PLATFORM_IOS_EXTERNAL_TEXTURE_SHARE_CONTEXT_H_ + +#include "flutter/flow/texture.h" +#include "flutter/fml/platform/darwin/cf_utils.h" +#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterTexture.h" + +namespace flutter { +// This is a new way of external texture. +// Unlike the original external texture which copy pixel buffer from an object that impelement +// FlutterTexture procotol, and create a texture reference using the pixel buffer. This solution will +// copy the OpenGL texture directly from the object impelementing FlutterShareTexture protocol. +class IOSExternalTextureShareContext : public flutter::Texture { + public: + IOSExternalTextureShareContext(int64_t textureId, NSObject* externalTexture); + + ~IOSExternalTextureShareContext() override; + + // Called from GPU thread. + void Paint(SkCanvas& canvas, const SkRect& bounds, bool freeze, GrContext* context) override; + + void OnGrContextCreated() override; + + void OnGrContextDestroyed() override; + + void MarkNewFrameAvailable() override; + + private: + NSObject* external_texture_; + + FML_DISALLOW_COPY_AND_ASSIGN(IOSExternalTextureShareContext); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_IOS_EXTERNAL_TEXTURE_SHARE_CONTEXT_H_ diff --git a/shell/platform/darwin/ios/ios_external_texture_gl_share_context.mm b/shell/platform/darwin/ios/ios_external_texture_gl_share_context.mm new file mode 100644 index 0000000000000..0f8a9eec9068e --- /dev/null +++ b/shell/platform/darwin/ios/ios_external_texture_gl_share_context.mm @@ -0,0 +1,55 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/darwin/ios/ios_external_texture_gl_share_context.h" + +#import +#import +#import + +#include "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h" +#include "third_party/skia/include/core/SkSurface.h" +#include "third_party/skia/include/gpu/GrBackendSurface.h" + +namespace flutter { + +IOSExternalTextureShareContext::IOSExternalTextureShareContext( + int64_t textureId, + NSObject* externalTexture) + : Texture(textureId), external_texture_(externalTexture) { + FML_DCHECK(external_texture_); +} + +IOSExternalTextureShareContext::~IOSExternalTextureShareContext() = default; + +void IOSExternalTextureShareContext::Paint(SkCanvas& canvas, + const SkRect& bounds, + bool freeze, + GrContext* context) { + GLuint texture_id = [external_texture_ copyShareTexture]; + if (texture_id == 0) { + return; + } + GrGLTextureInfo textureInfo; + textureInfo.fFormat = GL_RGBA8_OES; + textureInfo.fID = texture_id; + textureInfo.fTarget = GL_TEXTURE_2D; + + GrBackendTexture backendTexture(bounds.width(), bounds.height(), GrMipMapped::kNo, textureInfo); + sk_sp image = + SkImage::MakeFromTexture(context, backendTexture, kTopLeft_GrSurfaceOrigin, + kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr); + FML_DCHECK(image) << "Failed to create SkImage from Texture."; + if (image) { + canvas.drawImage(image, bounds.x(), bounds.y()); + } +} + +void IOSExternalTextureShareContext::OnGrContextCreated() {} + +void IOSExternalTextureShareContext::OnGrContextDestroyed() {} + +void IOSExternalTextureShareContext::MarkNewFrameAvailable() {} + +} // namespace flutter diff --git a/shell/platform/darwin/ios/ios_gl_context.h b/shell/platform/darwin/ios/ios_gl_context.h index 232645d9c8592..4697950b66872 100644 --- a/shell/platform/darwin/ios/ios_gl_context.h +++ b/shell/platform/darwin/ios/ios_gl_context.h @@ -32,6 +32,8 @@ class IOSGLContext { sk_sp ColorSpace() const { return color_space_; } + EAGLSharegroup* GetGLShareGroup(); + private: fml::scoped_nsobject context_; fml::scoped_nsobject resource_context_; diff --git a/shell/platform/darwin/ios/ios_gl_context.mm b/shell/platform/darwin/ios/ios_gl_context.mm index 52fb85f8f19a9..c965300db3b01 100644 --- a/shell/platform/darwin/ios/ios_gl_context.mm +++ b/shell/platform/darwin/ios/ios_gl_context.mm @@ -60,4 +60,9 @@ return [EAGLContext setCurrentContext:resource_context_.get()]; } +EAGLSharegroup* IOSGLContext::GetGLShareGroup() { + EAGLSharegroup* shareGroup = context_.get().sharegroup; + return shareGroup; +} + } // namespace flutter diff --git a/shell/platform/darwin/ios/ios_surface.h b/shell/platform/darwin/ios/ios_surface.h index 49f40f9eec76a..34f1a13fa5bc5 100644 --- a/shell/platform/darwin/ios/ios_surface.h +++ b/shell/platform/darwin/ios/ios_surface.h @@ -39,6 +39,8 @@ class IOSSurface { virtual std::unique_ptr CreateGPUSurface( GrContext* gr_context = nullptr) = 0; + virtual EAGLSharegroup* GetGLShareGroup() = 0; + protected: FlutterPlatformViewsController* GetPlatformViewsController(); diff --git a/shell/platform/darwin/ios/ios_surface_gl.h b/shell/platform/darwin/ios/ios_surface_gl.h index 964cc7adb692e..50d1b09213c99 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.h +++ b/shell/platform/darwin/ios/ios_surface_gl.h @@ -50,6 +50,8 @@ class IOSSurfaceGL final : public IOSSurface, bool UseOffscreenSurface() const override; + EAGLSharegroup* GetGLShareGroup() override; + // |GPUSurfaceGLDelegate| ExternalViewEmbedder* GetExternalViewEmbedder() override; diff --git a/shell/platform/darwin/ios/ios_surface_gl.mm b/shell/platform/darwin/ios/ios_surface_gl.mm index 9e838e200df34..543423aad0741 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.mm +++ b/shell/platform/darwin/ios/ios_surface_gl.mm @@ -73,6 +73,10 @@ return IsValid() && render_target_->PresentRenderBuffer(); } +EAGLSharegroup* IOSSurfaceGL::GetGLShareGroup() { + return context_.get()->GetGLShareGroup(); +} + // |ExternalViewEmbedder| sk_sp IOSSurfaceGL::GetRootSurface() { // On iOS, the root surface is created from the on-screen render target. Only the surfaces for the diff --git a/shell/platform/darwin/ios/ios_surface_software.h b/shell/platform/darwin/ios/ios_surface_software.h index d2ff61efdc4bf..4bbf755ae1233 100644 --- a/shell/platform/darwin/ios/ios_surface_software.h +++ b/shell/platform/darwin/ios/ios_surface_software.h @@ -36,6 +36,9 @@ class IOSSurfaceSoftware final : public IOSSurface, // |IOSSurface| std::unique_ptr CreateGPUSurface(GrContext* gr_context = nullptr) override; + // |IOSSurface| + EAGLSharegroup* GetGLShareGroup() override; + // |GPUSurfaceSoftwareDelegate| sk_sp AcquireBackingStore(const SkISize& size) override; diff --git a/shell/platform/darwin/ios/ios_surface_software.mm b/shell/platform/darwin/ios/ios_surface_software.mm index 4566bbf3f3624..e312425e19026 100644 --- a/shell/platform/darwin/ios/ios_surface_software.mm +++ b/shell/platform/darwin/ios/ios_surface_software.mm @@ -38,6 +38,10 @@ // Android oddities. } +EAGLSharegroup* IOSSurfaceSoftware::GetGLShareGroup() { + return nullptr; +} + std::unique_ptr IOSSurfaceSoftware::CreateGPUSurface(GrContext* gr_context) { if (!IsValid()) { return nullptr; diff --git a/shell/platform/darwin/ios/platform_view_ios.h b/shell/platform/darwin/ios/platform_view_ios.h index 38fc2a9f30b28..1462ef5c82132 100644 --- a/shell/platform/darwin/ios/platform_view_ios.h +++ b/shell/platform/darwin/ios/platform_view_ios.h @@ -37,6 +37,8 @@ class PlatformViewIOS final : public PlatformView { void RegisterExternalTexture(int64_t id, NSObject* texture); + void RegisterExternalShareTexture(int64_t id, NSObject* texture); + fml::scoped_nsprotocol GetTextInputPlugin() const; void SetTextInputPlugin(fml::scoped_nsprotocol plugin); @@ -44,6 +46,8 @@ class PlatformViewIOS final : public PlatformView { // |PlatformView| void SetSemanticsEnabled(bool enabled) override; + EAGLSharegroup* GetGLShareGroup(); + private: fml::WeakPtr owner_controller_; std::unique_ptr ios_surface_; diff --git a/shell/platform/darwin/ios/platform_view_ios.mm b/shell/platform/darwin/ios/platform_view_ios.mm index c80ae377c03a1..1b6fd730253cd 100644 --- a/shell/platform/darwin/ios/platform_view_ios.mm +++ b/shell/platform/darwin/ios/platform_view_ios.mm @@ -16,6 +16,7 @@ #include "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h" #include "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h" #include "flutter/shell/platform/darwin/ios/ios_external_texture_gl.h" +#include "flutter/shell/platform/darwin/ios/ios_external_texture_gl_share_context.h" namespace flutter { @@ -71,6 +72,18 @@ new AccessibilityBridge(static_cast(owner_controller_.get().view), RegisterTexture(std::make_shared(texture_id, texture)); } +void PlatformViewIOS::RegisterExternalShareTexture(int64_t texture_id, + NSObject* texture) { + RegisterTexture(std::make_shared(texture_id, texture)); +} + +EAGLSharegroup* PlatformViewIOS::GetGLShareGroup() { + if (ios_surface_.get() == nullptr) { + return nullptr; + } + return ios_surface_->GetGLShareGroup(); +} + // |PlatformView| std::unique_ptr PlatformViewIOS::CreateRenderingSurface() { if (!ios_surface_) { From b5b19c779bda62f1ff9a5c6c8ac28c64a2c13201 Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Mon, 2 Sep 2019 10:04:36 +0800 Subject: [PATCH 02/19] code format --- .../darwin/ios/ios_external_texture_gl_share_context.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/ios/ios_external_texture_gl_share_context.h b/shell/platform/darwin/ios/ios_external_texture_gl_share_context.h index 33ea6b12b8627..b9dd306d59cab 100644 --- a/shell/platform/darwin/ios/ios_external_texture_gl_share_context.h +++ b/shell/platform/darwin/ios/ios_external_texture_gl_share_context.h @@ -12,8 +12,8 @@ namespace flutter { // This is a new way of external texture. // Unlike the original external texture which copy pixel buffer from an object that impelement -// FlutterTexture procotol, and create a texture reference using the pixel buffer. This solution will -// copy the OpenGL texture directly from the object impelementing FlutterShareTexture protocol. +// FlutterTexture procotol, and create a texture reference using the pixel buffer. This solution +// will copy the OpenGL texture directly from the object impelementing FlutterShareTexture protocol. class IOSExternalTextureShareContext : public flutter::Texture { public: IOSExternalTextureShareContext(int64_t textureId, NSObject* externalTexture); From 8c2beb26377cc6c6b98b58abe8a6e55429cab6b6 Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Wed, 11 Sep 2019 20:15:13 +0800 Subject: [PATCH 03/19] Add testing code for new external texture --- .../engine/renderer/FlutterRenderer.java | 2 +- .../android/io/flutter/view/FlutterView.java | 2 +- .../io/flutter/view/TextureRegistry.java | 2 +- .../scenarios/ExampleInstrumentedTest.java | 11 + .../dev/flutter/scenarios/MainActivity.java | 79 ++-- .../scenarios/TestExternalTexture.java | 175 ++++++++ .../flutter/scenarios/TextureComplete.java | 5 + .../plugins/GeneratedPluginRegistrant.java | 23 ++ .../src/main/res/mipmap-xxhdpi/flutter.png | Bin 0 -> 10568 bytes .../ios/Flutter/Generated.xcconfig | 7 + .../ios/Flutter/flutter_export_environment.sh | 8 + .../ios/Runner/GeneratedPluginRegistrant.h | 14 + .../ios/Runner/GeneratedPluginRegistrant.m | 12 + .../Scenarios.xcodeproj/project.pbxproj | 62 ++- .../ios/Scenarios/Scenarios/AppDelegate.m | 38 +- .../Scenarios/Scenarios/TestExternalTexture.h | 19 + .../Scenarios/Scenarios/TestExternalTexture.m | 384 ++++++++++++++++++ .../ios/Scenarios/Scenarios/flutter.png | Bin 0 -> 10568 bytes .../ScenariosUITests/ExternalTextureUITests.m | 174 ++++++++ .../golden_external_texture_D10AP.png | Bin 0 -> 65312 bytes .../golden_external_texture_N69AP.png | Bin 0 -> 48154 bytes testing/scenario_app/lib/main.dart | 15 +- .../lib/src/external_texture_view.dart | 53 +++ testing/scenario_app/lib/src/scenario.dart | 6 + 24 files changed, 1042 insertions(+), 49 deletions(-) create mode 100644 testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestExternalTexture.java create mode 100644 testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextureComplete.java create mode 100644 testing/scenario_app/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java create mode 100644 testing/scenario_app/android/app/src/main/res/mipmap-xxhdpi/flutter.png create mode 100644 testing/scenario_app/ios/Flutter/Generated.xcconfig create mode 100755 testing/scenario_app/ios/Flutter/flutter_export_environment.sh create mode 100644 testing/scenario_app/ios/Runner/GeneratedPluginRegistrant.h create mode 100644 testing/scenario_app/ios/Runner/GeneratedPluginRegistrant.m create mode 100644 testing/scenario_app/ios/Scenarios/Scenarios/TestExternalTexture.h create mode 100644 testing/scenario_app/ios/Scenarios/Scenarios/TestExternalTexture.m create mode 100644 testing/scenario_app/ios/Scenarios/Scenarios/flutter.png create mode 100644 testing/scenario_app/ios/Scenarios/ScenariosUITests/ExternalTextureUITests.m create mode 100644 testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_external_texture_D10AP.png create mode 100644 testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_external_texture_N69AP.png create mode 100644 testing/scenario_app/lib/src/external_texture_view.dart diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index 68cffff471ba3..8e0436f35af14 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -120,7 +120,7 @@ public TextureRegistry.ShareTextureEntry createShareTexture(long shareTextureID) } @Override - public void onShareFrameAvaliable(int textureIndex) { + public void onShareFrameAvaliable(long textureIndex) { markTextureFrameAvailable(textureIndex); } diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index df9fe518b8af4..1ddd95bcd7879 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -781,7 +781,7 @@ public TextureRegistry.SurfaceTextureEntry createSurfaceTexture() { } @Override - public void onShareFrameAvaliable(int textureIndex) { + public void onShareFrameAvaliable(long textureIndex) { mNativeView.getFlutterJNI().markTextureFrameAvailable(textureIndex); } diff --git a/shell/platform/android/io/flutter/view/TextureRegistry.java b/shell/platform/android/io/flutter/view/TextureRegistry.java index 84eaa2e439712..d723f07d81030 100644 --- a/shell/platform/android/io/flutter/view/TextureRegistry.java +++ b/shell/platform/android/io/flutter/view/TextureRegistry.java @@ -45,7 +45,7 @@ interface SurfaceTextureEntry { /** * Informs the the Flutter Engine that the external texture has been updated, and to start a new rendering pipeline. */ - void onShareFrameAvaliable(int textureIndex); + void onShareFrameAvaliable(long textureIndex); /** * The OpenGL context created in the Flutter Engine, which is safe to user for sharing diff --git a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenarios/ExampleInstrumentedTest.java b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenarios/ExampleInstrumentedTest.java index edcfc22fee06b..eee9a1dc7687a 100644 --- a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenarios/ExampleInstrumentedTest.java +++ b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenarios/ExampleInstrumentedTest.java @@ -1,6 +1,7 @@ package dev.flutter.scenarios; import android.content.Context; +import android.content.Intent; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; @@ -23,4 +24,14 @@ public void useAppContext() { assertEquals("dev.flutter.scenarios", appContext.getPackageName()); } + + @Test + public void testExternalTexture(){ + + Context appContext = InstrumentationRegistry.getTargetContext(); + Intent intent = new Intent(appContext,MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setAction("com.google.intent.action.TEST_EXTERNAL_TEXTURE"); + appContext.startActivity(intent); + } } diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/MainActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/MainActivity.java index 2d237bdbb10cc..0fa11e1249339 100644 --- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/MainActivity.java +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/MainActivity.java @@ -7,20 +7,28 @@ import android.os.Bundle; import android.os.Handler; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; -import io.flutter.Log; -import io.flutter.embedding.android.FlutterActivity; +import io.flutter.app.FlutterPluginRegistry; import io.flutter.embedding.android.FlutterFragment; import io.flutter.embedding.android.FlutterView; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.plugin.common.BasicMessageChannel; import io.flutter.plugin.common.BinaryCodec; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.PluginRegistry; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.plugin.common.StringCodec; +import io.flutter.plugins.GeneratedPluginRegistrant; +import io.flutter.view.TextureRegistry; public class MainActivity extends FlutterActivity { final static String TAG = "Scenarios"; @@ -37,39 +45,20 @@ protected void onCreate(Bundle savedInstanceState) { final Uri logFileUri = launchIntent.getData(); new Handler().postDelayed(() -> writeTimelineData(logFileUri), 20000); } + else if("com.google.intent.action.TEST_EXTERNAL_TEXTURE".equals(launchIntent.getAction())){ + startExternalTexture(); + } + else if("com.google.intent.action.TEST_PLATFORM_VIEW".equals(launchIntent.getAction())){ + new Handler().postDelayed(() -> startPlatformView(), 20000); + } } - @Override - @NonNull - public FlutterShellArgs getFlutterShellArgs() { - FlutterShellArgs args = FlutterShellArgs.fromIntent(getIntent()); - args.add(FlutterShellArgs.ARG_TRACE_STARTUP); - args.add(FlutterShellArgs.ARG_ENABLE_DART_PROFILING); - args.add(FlutterShellArgs.ARG_VERBOSE_LOGGING); - - return args; - } - - @Override - public void configureFlutterEngine(FlutterEngine flutterEngine) { - flutterEngine.getPlatformViewsController() - .getRegistry() - .registerViewFactory( - "scenarios/textPlatformView", - new TextPlatformViewFactory()); - } - - private void writeTimelineData(Uri logFile) { if (logFile == null) { throw new IllegalArgumentException(); } - if (getFlutterEngine() == null) { - Log.e(TAG, "Could not write timeline data - no engine."); - return; - } - final BasicMessageChannel channel = new BasicMessageChannel<>( - getFlutterEngine().getDartExecutor(), "write_timeline", BinaryCodec.INSTANCE); + final BasicMessageChannel channel = new BasicMessageChannel( + getFlutterEngine().getDartExecutor(), "write_timeline", BinaryCodec.INSTANCE); channel.send(null, (ByteBuffer reply) -> { try { final FileDescriptor fd = getContentResolver() @@ -84,4 +73,36 @@ private void writeTimelineData(Uri logFile) { finish(); }); } + + + private void startPlatformView(){ + getFlutterEngine().getPlatformViewsController().getRegistry() + .registerViewFactory( + "scenarios/textPlatformView", + new TextPlatformViewFactory()); + } + + private void startExternalTexture(){ + + getFlutterEngine().getDartExecutor().setMessageHandler("scenario_status", (byteBuffer, binaryReply) -> { + final BasicMessageChannel channel = new BasicMessageChannel<>( + getFlutterEngine().getDartExecutor(), "set_scenario", StringCodec.INSTANCE); + channel.send("external_texture", null); + }); + + getFlutterEngine().getDartExecutor().setMessageHandler("create_external_texture", (byteBuffer, binaryReply) -> { + + TestExternalTexture testExternalTexture = new TestExternalTexture(getFlutterEngine()); + + testExternalTexture.createTexture((int textureId)->{ + TextureRegistry.ShareTextureEntry entry = getFlutterEngine().getRenderer().createShareTexture(textureId); + long textureIndex = entry.id(); + final BasicMessageChannel channel = new BasicMessageChannel<>( + getFlutterEngine().getDartExecutor(), "update_data", StringCodec.INSTANCE); + channel.send(""+textureIndex, null); + + testExternalTexture.startWithId(textureIndex,getApplicationContext()); + }); + }); + } } diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestExternalTexture.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestExternalTexture.java new file mode 100644 index 0000000000000..53c8849b9cab5 --- /dev/null +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestExternalTexture.java @@ -0,0 +1,175 @@ +package dev.flutter.scenarios; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; +import android.opengl.GLES11Ext; +import android.opengl.GLES20; +import android.opengl.GLUtils; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import java.util.HashMap; + +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.PluginRegistry; + +public class TestExternalTexture { + private volatile Handler mGLHandler; + private volatile Handler mMainHandler; + private final FlutterEngine flutterEngine; + private int imageTexture; + /** + * Plugin registration. + */ + @SuppressLint("UseSparseArrays") + public TestExternalTexture(FlutterEngine engine) { + this.flutterEngine = engine; + startThread(); + } + + public void createTexture(TextureComplete complete){ + if(mGLHandler != null){ + mGLHandler.post(()->{ + final int[] textures = new int[1]; + GLES20.glGenTextures(1, textures, 0); + imageTexture = textures[0]; + if(mMainHandler == null){ + mMainHandler = new Handler(); + } + mMainHandler.post(()->{ + complete.onTextureCreated(imageTexture); + }); + }); + } + else{ + if(mMainHandler == null){ + mMainHandler = new Handler(); + } + mMainHandler.postDelayed(()->{ + this.createTexture(complete); + },200); + } + } + + void startWithId(long textureIndex, Context context){ + mGLHandler.post(()->{ + + int identifier = context.getResources().getIdentifier("flutter","mipmap",context.getPackageName()); + + final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(),identifier); + + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, imageTexture); + + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_NEAREST); + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, + GLES20.GL_LINEAR); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, + GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, + GLES20.GL_CLAMP_TO_EDGE); + try { + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D,0,bitmap,0); + } + catch (Exception e){ + } + + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); + + mMainHandler.post(() -> flutterEngine.getRenderer().onShareFrameAvaliable(textureIndex)); + }); + } + + + private void startThread(){ + Thread glThread = new Thread(new Runnable() { + @Override + public void run() { + Looper.prepare(); + mGLHandler = new Handler(); + initOpenGL(); + Looper.loop(); + } + }); + glThread.setName("external.testqueue"); + glThread.start(); + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + private void initOpenGL(){ + EGLContext sharedContext = flutterEngine.getRenderer().getShareContext(Build.VERSION.SDK_INT); + EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + if (eglDisplay == EGL14.EGL_NO_DISPLAY) { + throw new RuntimeException("unable to get EGL14 display"); + } + int[] version = new int[2]; + if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) { + throw new RuntimeException("unable to initialize EGL14"); + } + EGLConfig config = null; + config = TestExternalTexture.getConfig2(eglDisplay); + if (config == null) { + throw new RuntimeException("Unable to find a suitable EGLConfig"); + } + int[] attrib2_list = { + EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, + EGL14.EGL_NONE + }; + EGLContext myEGLContext = EGL14.eglCreateContext(eglDisplay, config, sharedContext, + attrib2_list, 0); + + // Confirm with query. + int[] values = new int[1]; + EGL14.eglQueryContext(eglDisplay, myEGLContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, + values, 0); + + int[] surfaceAttribs = { + EGL14.EGL_WIDTH, 16, + EGL14.EGL_HEIGHT, 16, + EGL14.EGL_NONE + }; + EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, config, + surfaceAttribs, 0); + EGL14.eglMakeCurrent(eglDisplay,eglSurface,eglSurface,myEGLContext); + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + static EGLConfig getConfig2(EGLDisplay display) { + + // The actual surface is generally RGBA or RGBX, so situationally omitting alpha + // doesn't really help. It can also lead to a huge performance hit on glReadPixels() + // when reading into a GL_RGBA buffer. + int[] attribList = { + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_ALPHA_SIZE, 8, + //EGL14.EGL_DEPTH_SIZE, 16, + //EGL14.EGL_STENCIL_SIZE, 8, + EGL14.EGL_RENDERABLE_TYPE,EGL14.EGL_OPENGL_ES2_BIT, + EGL14.EGL_NONE + }; + + EGLConfig[] configs = new EGLConfig[1]; + int[] numConfigs = new int[1]; + if (!EGL14.eglChooseConfig(display, attribList, 0, configs, 0, configs.length, + numConfigs, 0)) { + return null; + } + return configs[0]; + } + +} diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextureComplete.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextureComplete.java new file mode 100644 index 0000000000000..e0ffc217eb3bb --- /dev/null +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextureComplete.java @@ -0,0 +1,5 @@ +package dev.flutter.scenarios; + +public interface TextureComplete { + void onTextureCreated(int textureID); +} diff --git a/testing/scenario_app/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/testing/scenario_app/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java new file mode 100644 index 0000000000000..d007606a44d83 --- /dev/null +++ b/testing/scenario_app/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -0,0 +1,23 @@ +package io.flutter.plugins; + +import io.flutter.plugin.common.PluginRegistry; + +/** + * Generated file. Do not edit. + */ +public final class GeneratedPluginRegistrant { + public static void registerWith(PluginRegistry registry) { + if (alreadyRegisteredWith(registry)) { + return; + } + } + + private static boolean alreadyRegisteredWith(PluginRegistry registry) { + final String key = GeneratedPluginRegistrant.class.getCanonicalName(); + if (registry.hasPlugin(key)) { + return true; + } + registry.registrarFor(key); + return false; + } +} diff --git a/testing/scenario_app/android/app/src/main/res/mipmap-xxhdpi/flutter.png b/testing/scenario_app/android/app/src/main/res/mipmap-xxhdpi/flutter.png new file mode 100644 index 0000000000000000000000000000000000000000..8dcbcebf0223799eccea9607aa437bda9f2d9c7d GIT binary patch literal 10568 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 literal 0 HcmV?d00001 diff --git a/testing/scenario_app/ios/Flutter/Generated.xcconfig b/testing/scenario_app/ios/Flutter/Generated.xcconfig new file mode 100644 index 0000000000000..0ba7bfe608990 --- /dev/null +++ b/testing/scenario_app/ios/Flutter/Generated.xcconfig @@ -0,0 +1,7 @@ +// This is a generated file; do not edit or check into version control. +FLUTTER_ROOT=/Users/ljc/Documents/third_party/engine/flutter +FLUTTER_APPLICATION_PATH=/Users/ljc/Documents/third_party/engine/src/flutter/testing/scenario_app +FLUTTER_TARGET=lib/main.dart +FLUTTER_BUILD_DIR=build +SYMROOT=${SOURCE_ROOT}/../build/ios +FLUTTER_FRAMEWORK_DIR=/Users/ljc/Documents/third_party/engine/flutter/bin/cache/artifacts/engine/ios diff --git a/testing/scenario_app/ios/Flutter/flutter_export_environment.sh b/testing/scenario_app/ios/Flutter/flutter_export_environment.sh new file mode 100755 index 0000000000000..0f266c8ff412a --- /dev/null +++ b/testing/scenario_app/ios/Flutter/flutter_export_environment.sh @@ -0,0 +1,8 @@ +#!/bin/sh +# This is a generated file; do not edit or check into version control. +export "FLUTTER_ROOT=/Users/ljc/Documents/third_party/engine/flutter" +export "FLUTTER_APPLICATION_PATH=/Users/ljc/Documents/third_party/engine/src/flutter/testing/scenario_app" +export "FLUTTER_TARGET=lib/main.dart" +export "FLUTTER_BUILD_DIR=build" +export "SYMROOT=${SOURCE_ROOT}/../build/ios" +export "FLUTTER_FRAMEWORK_DIR=/Users/ljc/Documents/third_party/engine/flutter/bin/cache/artifacts/engine/ios" diff --git a/testing/scenario_app/ios/Runner/GeneratedPluginRegistrant.h b/testing/scenario_app/ios/Runner/GeneratedPluginRegistrant.h new file mode 100644 index 0000000000000..3b700eb481958 --- /dev/null +++ b/testing/scenario_app/ios/Runner/GeneratedPluginRegistrant.h @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +#ifndef GeneratedPluginRegistrant_h +#define GeneratedPluginRegistrant_h + +#import + +@interface GeneratedPluginRegistrant : NSObject ++ (void)registerWithRegistry:(NSObject*)registry; +@end + +#endif /* GeneratedPluginRegistrant_h */ diff --git a/testing/scenario_app/ios/Runner/GeneratedPluginRegistrant.m b/testing/scenario_app/ios/Runner/GeneratedPluginRegistrant.m new file mode 100644 index 0000000000000..60dfa42b328db --- /dev/null +++ b/testing/scenario_app/ios/Runner/GeneratedPluginRegistrant.m @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +#import "GeneratedPluginRegistrant.h" + +@implementation GeneratedPluginRegistrant + ++ (void)registerWithRegistry:(NSObject*)registry { +} + +@end diff --git a/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj b/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj index c7fe99847ea68..f795a6beaced0 100644 --- a/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj +++ b/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj @@ -14,6 +14,11 @@ 0DB7820022EA2C9D00E9B371 /* App.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 246B4E4122E3B5F700073EBF /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 0DB7820122EA2CA500E9B371 /* App.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 246B4E4122E3B5F700073EBF /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 0DB7820222EA493B00E9B371 /* FlutterViewControllerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DB781FC22EA2C0300E9B371 /* FlutterViewControllerTest.m */; }; + 221C7253231FBE3800F2FCD1 /* golden_external_texture_D10AP.png in Resources */ = {isa = PBXBuildFile; fileRef = 221C7252231FBE3800F2FCD1 /* golden_external_texture_D10AP.png */; }; + 223563E823191864009EC45A /* TestExternalTexture.m in Sources */ = {isa = PBXBuildFile; fileRef = 223563E723191864009EC45A /* TestExternalTexture.m */; }; + 228708AC231D100800F7CA59 /* flutter.png in Resources */ = {isa = PBXBuildFile; fileRef = 228708AB231D100800F7CA59 /* flutter.png */; }; + 228708B3231F93B800F7CA59 /* ExternalTextureUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 228708B2231F93B800F7CA59 /* ExternalTextureUITests.m */; }; + 22E1E7202320AB3800577639 /* golden_external_texture_N69AP.png in Resources */ = {isa = PBXBuildFile; fileRef = 22E1E71F2320AB3800577639 /* golden_external_texture_N69AP.png */; }; 242F37A222E636DE001E83D4 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 246B4E4522E3B61000073EBF /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 242F37A322E636DE001E83D4 /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 246B4E4122E3B5F700073EBF /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 244EA6D0230DBE8900B2D26E /* golden_platform_view_D21AP.png in Resources */ = {isa = PBXBuildFile; fileRef = 244EA6CF230DBE8900B2D26E /* golden_platform_view_D21AP.png */; }; @@ -85,6 +90,12 @@ /* Begin PBXFileReference section */ 0DB781FC22EA2C0300E9B371 /* FlutterViewControllerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FlutterViewControllerTest.m; sourceTree = ""; }; + 221C7252231FBE3800F2FCD1 /* golden_external_texture_D10AP.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = golden_external_texture_D10AP.png; sourceTree = ""; }; + 223563E623191864009EC45A /* TestExternalTexture.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestExternalTexture.h; sourceTree = ""; }; + 223563E723191864009EC45A /* TestExternalTexture.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestExternalTexture.m; sourceTree = ""; }; + 228708AB231D100800F7CA59 /* flutter.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = flutter.png; sourceTree = ""; }; + 228708B2231F93B800F7CA59 /* ExternalTextureUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExternalTextureUITests.m; sourceTree = ""; }; + 22E1E71F2320AB3800577639 /* golden_external_texture_N69AP.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = golden_external_texture_N69AP.png; sourceTree = ""; }; 244EA6CF230DBE8900B2D26E /* golden_platform_view_D21AP.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = golden_platform_view_D21AP.png; sourceTree = ""; }; 246B4E4122E3B5F700073EBF /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = App.framework; sourceTree = ""; }; 246B4E4522E3B61000073EBF /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Flutter.framework; sourceTree = ""; }; @@ -160,8 +171,11 @@ 248D76C922E388370012F0C1 /* Scenarios */ = { isa = PBXGroup; children = ( + 228708AB231D100800F7CA59 /* flutter.png */, 24F1FB88230B4579005ACE7C /* TextPlatformView.h */, 24F1FB87230B4579005ACE7C /* TextPlatformView.m */, + 223563E623191864009EC45A /* TestExternalTexture.h */, + 223563E723191864009EC45A /* TestExternalTexture.m */, 248D76CA22E388370012F0C1 /* AppDelegate.h */, 248D76CB22E388370012F0C1 /* AppDelegate.m */, 248D76D322E388380012F0C1 /* Assets.xcassets */, @@ -184,10 +198,13 @@ 248D76ED22E388380012F0C1 /* ScenariosUITests */ = { isa = PBXGroup; children = ( + 22E1E71F2320AB3800577639 /* golden_external_texture_N69AP.png */, + 221C7252231FBE3800F2FCD1 /* golden_external_texture_D10AP.png */, 244EA6CF230DBE8900B2D26E /* golden_platform_view_D21AP.png */, 24D47D1C230CA2700069DD5E /* golden_platform_view_iPhone SE_simulator.png */, 24D47D1A230C79840069DD5E /* golden_platform_view_D211AP.png */, 248D76EE22E388380012F0C1 /* PlatformViewUITests.m */, + 228708B2231F93B800F7CA59 /* ExternalTextureUITests.m */, 248D76F022E388380012F0C1 /* Info.plist */, 24D47D1E230CA4480069DD5E /* README.md */, ); @@ -309,6 +326,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 228708AC231D100800F7CA59 /* flutter.png in Resources */, 248D76D422E388380012F0C1 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -324,6 +342,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 221C7253231FBE3800F2FCD1 /* golden_external_texture_D10AP.png in Resources */, + 22E1E7202320AB3800577639 /* golden_external_texture_N69AP.png in Resources */, 24D47D1B230C79840069DD5E /* golden_platform_view_D211AP.png in Resources */, 24D47D1D230CA2700069DD5E /* golden_platform_view_iPhone SE_simulator.png in Resources */, 244EA6D0230DBE8900B2D26E /* golden_platform_view_D21AP.png in Resources */, @@ -337,6 +357,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 223563E823191864009EC45A /* TestExternalTexture.m in Sources */, 248D76DA22E388380012F0C1 /* main.m in Sources */, 24F1FB89230B4579005ACE7C /* TextPlatformView.m in Sources */, 248D76CC22E388370012F0C1 /* AppDelegate.m in Sources */, @@ -356,6 +377,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 228708B3231F93B800F7CA59 /* ExternalTextureUITests.m in Sources */, 248D76EF22E388380012F0C1 /* PlatformViewUITests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -496,18 +518,24 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = S8QB4VV633; + DEVELOPMENT_TEAM = 7MSDM3RKPG; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + GLES_SILENCE_DEPRECATION, + "$(inherited)", + ); INFOPLIST_FILE = Scenarios/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.Scenarios; + PRODUCT_BUNDLE_IDENTIFIER = com.taobao.ScenariosAA; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; TARGETED_DEVICE_FAMILY = "1,2"; @@ -520,18 +548,20 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = S8QB4VV633; + DEVELOPMENT_TEAM = 7MSDM3RKPG; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", ); + GCC_PREPROCESSOR_DEFINITIONS = "GLES_SILENCE_DEPRECATION=1"; INFOPLIST_FILE = Scenarios/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.Scenarios; + PRODUCT_BUNDLE_IDENTIFIER = com.taobao.ScenariosAA; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; TARGETED_DEVICE_FAMILY = "1,2"; @@ -542,13 +572,15 @@ isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = S8QB4VV633; + DEVELOPMENT_TEAM = 7MSDM3RKPG; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", ); INFOPLIST_FILE = ScenariosTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -560,6 +592,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.ScenariosTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Scenarios.app/Scenarios"; }; @@ -569,13 +602,15 @@ isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = S8QB4VV633; + DEVELOPMENT_TEAM = 7MSDM3RKPG; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", ); INFOPLIST_FILE = ScenariosTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -587,6 +622,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.ScenariosTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Scenarios.app/Scenarios"; }; @@ -595,13 +631,15 @@ 248D76FA22E388380012F0C1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = S8QB4VV633; + DEVELOPMENT_TEAM = 7MSDM3RKPG; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", ); INFOPLIST_FILE = ScenariosUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -611,8 +649,9 @@ "-framework", Flutter, ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.ScenariosUITests; + PRODUCT_BUNDLE_IDENTIFIER = com.taobao.ScenariosUITests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = Scenarios; }; @@ -621,13 +660,15 @@ 248D76FB22E388380012F0C1 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = S8QB4VV633; + DEVELOPMENT_TEAM = 7MSDM3RKPG; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", ); INFOPLIST_FILE = ScenariosUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -637,8 +678,9 @@ "-framework", Flutter, ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.ScenariosUITests; + PRODUCT_BUNDLE_IDENTIFIER = com.taobao.ScenariosUITests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = Scenarios; }; diff --git a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m index 38e14bda4f359..bfec7bd97ad30 100644 --- a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m +++ b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m @@ -1,8 +1,8 @@ #include "AppDelegate.h" #import "TextPlatformView.h" +#import "TestExternalTexture.h" @interface NoStatusBarFlutterViewController : FlutterViewController - @end @implementation NoStatusBarFlutterViewController @@ -11,12 +11,15 @@ - (BOOL)prefersStatusBarHidden { } @end +@interface AppDelegate() +@property(nonatomic,strong) TestExternalTexture * externalTexture; +@end + @implementation AppDelegate - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - // This argument is used by the XCUITest for Platform Views so that the app // under test will create platform views. if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--platform-view"]) { @@ -38,7 +41,36 @@ - (BOOL)application:(UIApplication*)application [flutterViewController.engine registrarForPlugin:@"scenarios/TextPlatformViewPlugin"]; [registrar registerViewFactory:textPlatformViewFactory withId:@"scenarios/textPlatformView"]; self.window.rootViewController = flutterViewController; - } else { + } + else if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--external-texture"]){ + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"ExternalTextureTest" project:nil]; + [engine runWithEntrypoint:nil]; + + FlutterViewController* flutterViewController = [[NoStatusBarFlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil]; + + [engine.binaryMessenger + setMessageHandlerOnChannel:@"scenario_status" + binaryMessageHandler:^(NSData* _Nullable message, FlutterBinaryReply _Nonnull reply) { + [engine.binaryMessenger + sendOnChannel:@"set_scenario" + message:[@"external_texture" dataUsingEncoding:NSUTF8StringEncoding]]; + }]; + + [engine.binaryMessenger + setMessageHandlerOnChannel:@"create_external_texture" + binaryMessageHandler:^(NSData* _Nullable message, FlutterBinaryReply _Nonnull reply) { + NSObject* registrar = + [flutterViewController.engine registrarForPlugin:@"scenarios/ExternalTexturePlugin"]; + self.externalTexture = [[TestExternalTexture alloc] initWithWithRegistrar:registrar]; + int64_t textureID = [registrar.textures registerShareTexture:self.externalTexture]; + [self.externalTexture startWithID:textureID]; + NSData * data = [NSData dataWithBytes:&textureID length:sizeof(textureID)]; + reply(data); + }]; + + self.window.rootViewController = flutterViewController; + } + else { self.window.rootViewController = [[UIViewController alloc] init]; } [self.window makeKeyAndVisible]; diff --git a/testing/scenario_app/ios/Scenarios/Scenarios/TestExternalTexture.h b/testing/scenario_app/ios/Scenarios/Scenarios/TestExternalTexture.h new file mode 100644 index 0000000000000..1ae5730ee09c8 --- /dev/null +++ b/testing/scenario_app/ios/Scenarios/Scenarios/TestExternalTexture.h @@ -0,0 +1,19 @@ +// +// TestExternalTexture.h +// Scenarios +// +// Created by lujunchen on 2019/8/30. +// Copyright © 2019 flutter. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface TestExternalTexture : NSObject +- (instancetype)initWithWithRegistrar:(NSObject*)registrar; +- (void)startWithID:(int64_t)textureID; +@end + +NS_ASSUME_NONNULL_END diff --git a/testing/scenario_app/ios/Scenarios/Scenarios/TestExternalTexture.m b/testing/scenario_app/ios/Scenarios/Scenarios/TestExternalTexture.m new file mode 100644 index 0000000000000..907ba83b7f0ff --- /dev/null +++ b/testing/scenario_app/ios/Scenarios/Scenarios/TestExternalTexture.m @@ -0,0 +1,384 @@ +// +// TestExternalTexture.m +// Scenarios +// +// Created by lujunchen on 2019/8/30. +// Copyright © 2019 flutter. All rights reserved. +// + +#import "TestExternalTexture.h" +#import +#import + +#define SHADER_STRING(text) @#text + +NSString *const kIFGLGeneralVertexShaderString = SHADER_STRING +( + attribute vec4 position; + attribute vec4 inputTextureCoordinate; + + varying vec2 textureCoordinate; + + void main() + { + gl_Position = position; + textureCoordinate = inputTextureCoordinate.xy; + } + ); + +NSString *const kIFGLGeneralFragmentShaderString = SHADER_STRING +( + varying highp vec2 textureCoordinate; + + uniform sampler2D inputImageTexture; + + void main() + { + gl_FragColor = texture2D(inputImageTexture,textureCoordinate); + } + ); + +const GLfloat vetex1[] = { + -1.0f, -1.0f, + -1.0f, 0.0f, + 0.0f, -1.0f, + 0.0f, 0.0f, +}; + +const GLfloat vetex2[] = { + -1.0f, 0.0f, + -1.0f, 1.0f, + 0.0f, 0.0f, + 0.0f, 1.0f, +}; + +const GLfloat vetex3[] = { + 0.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, +}; + +const GLfloat vetex4[] = { + 0.0f, -1.0f, + 0.0f, 0.0f, + 1.0f, -1.0f, + 1.0f, 0.0f, +}; + +const GLfloat vetex[] = { + -1.0f, -1.0f, + -1.0f, 1.0f, + 1.0f, -1.0f, + 1.0f, 1.0f, +}; + +const GLfloat textureGeneralTexCoord[] = { + 0.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, +}; + +@interface TestExternalTexture() +@property(nonatomic,assign) GLuint program; +@property(nonatomic,assign) GLuint vertShader; +@property(nonatomic,assign) GLuint fragShader; +@property(nonatomic,strong) NSMutableArray *attributes; +@property(nonatomic,strong) NSMutableDictionary *uniforms; +@property(nonatomic,assign) GLuint positionAttributeLocation; +@property(nonatomic,assign) GLuint texCoordAttributeLocation; + +@property(nonatomic,strong) dispatch_queue_t displayQueue; +@property(nonatomic,strong) EAGLContext *glContext; + +@property(nonatomic,assign) GLuint frameBuffer; +@property(nonatomic,assign) GLuint frameTexture; +@property(nonatomic,assign) GLuint materialTexture; + +@property(strong,nonatomic) NSObject *registry; +@property(strong,nonatomic) NSObject *messenger; +@end + +@implementation TestExternalTexture +- (instancetype)initWithWithRegistrar:(NSObject*)registrar{ + if ((self = [super init])) + { + self.registry = [registrar textures]; + self.messenger = [registrar messenger]; + + self.attributes = [[NSMutableArray alloc] init]; + self.uniforms = [[NSMutableDictionary alloc] init]; + + self.displayQueue = dispatch_queue_create("external.testqueue", nil); + } + + return self; +} + +-(GLuint)copyShareTexture{ + if(self.frameTexture != 0){ + return self.frameTexture; + } + return 0; +} + +-(void)initProgramm{ + self.program = glCreateProgram(); + + if (![self compileShader:&_vertShader + type:GL_VERTEX_SHADER + string:kIFGLGeneralVertexShaderString]) + { + NSLog(@"FMAVEffect FMAVEffectGLProgram Failed to compile vertex shader"); + } + + if (![self compileShader:&_fragShader + type:GL_FRAGMENT_SHADER + string:kIFGLGeneralFragmentShaderString]) + { + NSLog(@"FMAVEffect FMAVEffectGLProgram Failed to compile fragment shader"); + } + + glAttachShader(self.program, _vertShader); + glAttachShader(self.program, _fragShader); + + //called before program link + [self addAttribute:@"position"]; + [self addAttribute:@"inputTextureCoordinate"]; + + if (![self link]) { + NSLog(@"FMAVEffect FMAVEffectGLProgram link failed"); + } + // 4 + self.positionAttributeLocation = [self attributeIndex:@"position"]; + self.texCoordAttributeLocation = [self attributeIndex:@"inputTextureCoordinate"]; +} + +- (void)startWithID:(int64_t)textureID{ + + int width = [UIScreen mainScreen].bounds.size.width; + int height = [UIScreen mainScreen].bounds.size.height; + + dispatch_async(self.displayQueue, ^{ + if(self.glContext == NULL){ + EAGLSharegroup * flutterShareGroup = [self.registry getShareGroup]; + if(flutterShareGroup != NULL){ + self.glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3 sharegroup:flutterShareGroup]; + } + else{ + self.glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; + } + } + [EAGLContext setCurrentContext:self.glContext]; + if (self.program == 0){ + [self initProgramm]; + } + if (self.frameTexture == 0) { + self.frameTexture = [self createTextureWithWidth:width andHeight:height]; + } + if (self.frameBuffer == 0){ + glGenFramebuffers(1, &self->_frameBuffer); + glBindFramebuffer(GL_FRAMEBUFFER, self.frameBuffer); + } + if(self.materialTexture == 0){ + glGenTextures(1, &self->_materialTexture); + UIImage * materialImage = [UIImage imageNamed:@"flutter.png"]; + if(materialImage != NULL){ + [self convertCGImage:materialImage.CGImage toTexture:self.materialTexture inSize:materialImage.size]; + } + } + + + glViewport(0, 0, (int)width, (int)height); + + glBindFramebuffer(GL_FRAMEBUFFER, self.frameBuffer); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self.frameTexture, 0); + + [self useProgramm]; + + [self renderTexture:self.materialTexture withVertex:(GLvoid *)vetex]; + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + [self.registry textureFrameAvailable:textureID]; + }); +} + +- (void)convertCGImage:(CGImageRef)image toTexture:(GLuint)textureID inSize:(CGSize)size +{ + CGImageRef cgImageRef = image; + GLuint width = size.width; + GLuint height = size.height; + CGRect rect = CGRectMake(0, 0, width, height); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + void *imageData = malloc(width * height * 4); + CGContextRef context = CGBitmapContextCreate(imageData, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); + if (context == NULL) { + return; + } + CGContextTranslateCTM(context, 0, 0); + CGContextScaleCTM(context, 1.0f, 1.0f); + CGColorSpaceRelease(colorSpace); + CGContextClearRect(context, rect); + CGContextDrawImage(context, rect, cgImageRef); + glEnable(GL_TEXTURE_2D); + + glBindTexture(GL_TEXTURE_2D, textureID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData); + glBindTexture(GL_TEXTURE_2D, 0); + CGContextRelease(context); + free(imageData); +} + +-(void)renderTexture:(GLuint)srcTexture withVertex:(GLvoid *)vertex{ + glVertexAttribPointer(self.positionAttributeLocation, 2, GL_FLOAT, 0, 0, vertex); + glEnableVertexAttribArray(self.positionAttributeLocation); + + glVertexAttribPointer(self.texCoordAttributeLocation, 2, GL_FLOAT, 0, 0, textureGeneralTexCoord); + glEnableVertexAttribArray(self.texCoordAttributeLocation); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, srcTexture); + glUniform1i([self uniformIndex:@"inputImageTexture"], 0); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glFlush(); + glBindTexture(GL_TEXTURE_2D, 0); +} + +- (GLuint)createTextureWithWidth:(size_t)width andHeight:(size_t)height +{ + GLuint textureID = -1; + glActiveTexture(GL_TEXTURE1); + glGenTextures(1, &textureID); + glBindTexture(GL_TEXTURE_2D, textureID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)width, (int)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glBindTexture(GL_TEXTURE_2D, 0); + return textureID; +} + +#pragma mark OPENGL +- (BOOL)compileShader:(GLuint *)shader + type:(GLenum)type + string:(NSString *)shaderString +{ + GLint status; + const GLchar *source; + + source = + (GLchar *)[shaderString UTF8String]; + if (!source) + { + NSLog(@"FMAVEffect FMAVEffectGLProgram Failed to load shader source"); + return NO; + } + + *shader = glCreateShader(type); + glShaderSource(*shader, 1, &source, NULL); + glCompileShader(*shader); + + glGetShaderiv(*shader, GL_COMPILE_STATUS, &status); + + if (status != GL_TRUE) + { + GLint logLength; + glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength); + if (logLength > 0) + { + GLchar *log = (GLchar *)malloc(logLength); + glGetShaderInfoLog(*shader, logLength, &logLength, log); + if (shader == &_vertShader) + { + NSLog(@"FMAVEffect FMAVEffectGLProgram compile vertext shader error: %s", log); + } + else + { + NSLog(@"FMAVEffect FMAVEffectGLProgram compile fragment shader error: %s", log); + } + + free(log); + } + } + + return status == GL_TRUE; +} + +- (void)addAttribute:(NSString *)attributeName +{ + if (![self.attributes containsObject:attributeName]) + { + [self.attributes addObject:attributeName]; + glBindAttribLocation(self.program, + (GLuint)[self.attributes indexOfObject:attributeName], + [attributeName UTF8String]); + } +} + +- (GLuint)attributeIndex:(NSString *)attributeName +{ + return (GLuint)[self.attributes indexOfObject:attributeName]; +} + +- (GLuint)uniformIndex:(NSString *)uniformName +{ + if ([self.uniforms.allKeys containsObject:uniformName]) { + return (GLuint)[self.uniforms[uniformName] unsignedIntValue]; + } + int loc = glGetUniformLocation(self.program, [uniformName UTF8String]); + _uniforms[uniformName] = [NSNumber numberWithInt:loc]; + return loc; +} + +- (BOOL)link +{ + GLint status; + + glLinkProgram(self.program); + + glGetProgramiv(self.program, GL_LINK_STATUS, &status); + if (status == GL_FALSE) + return NO; + + if (self.vertShader) + { + glDeleteShader(self.vertShader); + self.vertShader = 0; + } + if (self.fragShader) + { + glDeleteShader(self.fragShader); + self.fragShader = 0; + } + return YES; +} + +- (void)useProgramm +{ + glUseProgram(self.program); +} + +- (void)dealloc +{ + if (_vertShader!=0) + glDeleteShader(_vertShader); + + if (_fragShader!=0) + glDeleteShader(_fragShader); + + if (_program!=0) + glDeleteProgram(_program); + +} + +@end diff --git a/testing/scenario_app/ios/Scenarios/Scenarios/flutter.png b/testing/scenario_app/ios/Scenarios/Scenarios/flutter.png new file mode 100644 index 0000000000000000000000000000000000000000..8dcbcebf0223799eccea9607aa437bda9f2d9c7d GIT binary patch literal 10568 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 literal 0 HcmV?d00001 diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/ExternalTextureUITests.m b/testing/scenario_app/ios/Scenarios/ScenariosUITests/ExternalTextureUITests.m new file mode 100644 index 0000000000000..4a10e0ae6b6f7 --- /dev/null +++ b/testing/scenario_app/ios/Scenarios/ScenariosUITests/ExternalTextureUITests.m @@ -0,0 +1,174 @@ +// +// ExternalTextureUITests.m +// ScenariosUITests +// +// Created by lujunchen on 2019/9/4. +// Copyright © 2019 flutter. All rights reserved. +// + +#import +#import +#include + +@interface ExternalTextureUITests : XCTestCase +@property(nonatomic, strong) XCUIApplication* application; +@end + +@implementation ExternalTextureUITests + +- (void)setUp { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + self.continueAfterFailure = NO; + + // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. + self.application = [[XCUIApplication alloc] init]; + self.application.launchArguments = @[ @"--external-texture" ]; + [self.application launch]; + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. +} + +- (void)testExample { + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. + NSBundle* bundle = [NSBundle bundleForClass:[self class]]; + NSString* goldenName = + [NSString stringWithFormat:@"golden_external_texture_%@", [self platformName]]; + NSString* path = [bundle pathForResource:goldenName ofType:@"png"]; + UIImage* golden = [[UIImage alloc] initWithContentsOfFile:path]; + + XCUIScreenshot* screenshot = [[XCUIScreen mainScreen] screenshot]; + XCTAttachment* attachment = [XCTAttachment attachmentWithScreenshot:screenshot]; + attachment.lifetime = XCTAttachmentLifetimeKeepAlways; + [self addAttachment:attachment]; + + if (golden) { + XCTAttachment* goldenAttachment = [XCTAttachment attachmentWithImage:golden]; + goldenAttachment.lifetime = XCTAttachmentLifetimeKeepAlways; + [self addAttachment:goldenAttachment]; + } else { + + NSString *folder = [NSString stringWithFormat:@"%@/",NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]]; + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:folder]) { + NSError *error = nil; + [fileManager createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:nil error:&error]; + if (error) { + } + } + NSString *imgPath = [[folder stringByAppendingPathComponent:goldenName] stringByAppendingPathExtension:@"png"]; + [self writeImageToFile:screenshot.image atPath:imgPath]; + + XCTFail(@"This test will fail - no golden named %@ found. Follow the steps in the " + @"README to add a new golden.", + goldenName); + } + + XCTAssertTrue([self compareImage:golden toOther:screenshot.image]); +} + +- (NSString*)platformName { + NSString* simulatorName = + [[NSProcessInfo processInfo].environment objectForKey:@"SIMULATOR_DEVICE_NAME"]; + if (simulatorName) { + return [NSString stringWithFormat:@"%@_simulator", simulatorName]; + } + + size_t size; + sysctlbyname("hw.model", NULL, &size, NULL, 0); + char* answer = malloc(size); + sysctlbyname("hw.model", answer, &size, NULL, 0); + + NSString* results = [NSString stringWithCString:answer encoding:NSUTF8StringEncoding]; + free(answer); + return results; +} + +- (BOOL)writeImageToFile:(UIImage *)image atPath:(NSString *)aPath { + if ((image == nil) || (aPath == nil) || ([aPath isEqualToString:@""])) { + return NO; + } + + NSFileManager * fileManager = [NSFileManager defaultManager]; + BOOL isDirectory; + NSString * directory = [aPath stringByDeletingLastPathComponent]; + BOOL exist = [fileManager fileExistsAtPath:directory isDirectory:&isDirectory]; + if (!exist) { + [fileManager createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:nil]; + } + + @try { + NSData *imageData = nil; + NSString *ext = [aPath pathExtension]; + if ([ext isEqualToString:@"png"]) { + imageData = UIImagePNGRepresentation(image); + } else { + imageData = UIImageJPEGRepresentation(image, 1.0); + } + + if ((imageData == nil) || ([imageData length] <= 0)) { + return NO; + } + + BOOL success = [imageData writeToFile:aPath atomically:YES]; + return success; + } @catch (NSException *e) { + //NSLog(@"create thumbnail exception."); + } + return NO; +} + +- (BOOL)compareImage:(UIImage*)a toOther:(UIImage*)b { + CGImageRef imageRefA = [a CGImage]; + CGImageRef imageRefB = [b CGImage]; + + NSUInteger widthA = CGImageGetWidth(imageRefA); + NSUInteger heightA = CGImageGetHeight(imageRefA); + NSUInteger widthB = CGImageGetWidth(imageRefB); + NSUInteger heightB = CGImageGetHeight(imageRefB); + + if (widthA != widthB || heightA != heightB) { + return NO; + } + NSUInteger bytesPerPixel = 4; + NSUInteger size = widthA * heightA * bytesPerPixel; + NSMutableData* rawA = [NSMutableData dataWithLength:size]; + NSMutableData* rawB = [NSMutableData dataWithLength:size]; + + if (!rawA || !rawB) { + return NO; + } + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + NSUInteger bytesPerRow = bytesPerPixel * widthA; + NSUInteger bitsPerComponent = 8; + CGContextRef contextA = + CGBitmapContextCreate(rawA.mutableBytes, widthA, heightA, bitsPerComponent, bytesPerRow, + colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); + + CGContextDrawImage(contextA, CGRectMake(0, 0, widthA, heightA), imageRefA); + CGContextRelease(contextA); + + CGContextRef contextB = + CGBitmapContextCreate(rawB.mutableBytes, widthA, heightA, bitsPerComponent, bytesPerRow, + colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); + CGColorSpaceRelease(colorSpace); + + CGContextDrawImage(contextB, CGRectMake(0, 0, widthA, heightA), imageRefB); + CGContextRelease(contextB); + + if (memcmp(rawA.bytes, rawB.bytes, rawA.length)) { + return NO; + } + + return YES; +} + +@end diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_external_texture_D10AP.png b/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_external_texture_D10AP.png new file mode 100644 index 0000000000000000000000000000000000000000..018ec7177bf0256e5b3616ce9f50e58219bee286 GIT binary patch literal 65312 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 literal 0 HcmV?d00001 diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_external_texture_N69AP.png b/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_external_texture_N69AP.png new file mode 100644 index 0000000000000000000000000000000000000000..7a6448e22ae4a0c7c971a294754a47d549148660 GIT binary patch literal 48154 zcmeFZc|4T=_dY(;WQdV=gDh=?%9ec@Eh@5v87gHgp|MM{ju{CRB9bjeCCd;MBFi9K zp~&7?hU{wWW-McTpVRBTe18A_{(e7tNR7GYzVCCN=XI{@I_K%p72}Kiyu!Q?2!vn% z(s?roWE&9z+3v{$2d@OgjDx=+P;aw~XCVdO4^D&s?C`vF%^LzaaE$W>)i*mj4_-Wo zJ8yyp-?xD;&Nnsi*YSV8bN-z|U&}jc3xViD^v|C``$7Mt`M$V;`Po!dxd3R~t`1qu8Pnal)BEEl z@##*pp8}nf^gp(?ey^HjHq9wrzLXr;#!O$TXj8BD*6^9>dL%IblZU`-AV>(z27XJBKim-dSg^k|9P?I9^3}9&;$EF2elw|L#=v-KUOac zuP^lc=i?^0;Yb7#`tKuq6l?~$>Q+X-3H;~9cWF?hZsp2<-(ypw2yY*Lu3Sd<&?*bA zTnYWx(Zsb7D4cWA|GbsxgShHj>$&N-1pCj6u1*m6E)VVhya$4O7X>|C=D_d1AtF#9 z?y>prpV$Q35NrPx1MqP+qOhKu@;6XJtbYfF2<;alLhG#r{)yAyQ=YJg@Vj3N?PoWJ z{%*2D{re|L;{OZR|HAdZ?E2qgguwoXUH^mn|5e8SKb2AUh0rDam&UQ8Oi0$}d*&g( znQcnDCZEtys@vLMy{*x$L=xAE>R9@kO_7IKRb{0%5QNnMkqVJ;w!dWfE-qla2Y^pibyYOcLZthtZTJcWy#=^l3ruv2c zpY8YLi8*Xg;&+L_%_=7@!gozpn|CN4KXy`-cR>5nsEWh;waeeW@X9LXedYOlzR%{c zXEb|UOGOj~zcO%9-bMk|IJJrYuPMv+N|5Zb)TZl!JB$l-p!tweBX_h54tZ? zVJU>jAn-$5U)*$aTF7v)JU3csTTAxt#Wn5Ndk5EB-d$i#?4BXac9Y$eNnWzme$w@;yzsb`kWBWS1)+B~+dZ+h zx_^+j9U(;<=-8aO#OjNCXR-O@^vwfC$xF<4j#c{Rjv@<%EnA^1ZvjWIz}zx^r%mPj zWRH&6mN_$@Jav_Ot|RIVRoh_;w>V7dIN(dgAyAdz4~=bT_VQ%c=^6h&(&QhdT4z>d zb*_Y2&?Sx|nHj<3JNJb3{K*I}NyWsI0>TU^_X-SJt2eZE97&6!)*#_qS%HrNLjrdP z!soA)r(ynhoFX=8^B;%Vj!I3KJZt9L(({L1Xdf1*Ws=jGlAbBl@?T_0+aPO*I$QJn&;|b6Ux=rVY6vHNd!R7< zc3pp5?)7C~%$gADWpj09Chyy!3;e&CVI{NX&P0~KK#zC7I`7+`E$}Ob!@&iAf(=J! zZ|2C5QBM>j19DH(-zM0i+43K2ut(Klw=<4_q!2mtysFT7U|BlMV$NJSf9-*A&%39@ATNQlyXi;Wnl4xu-;*=hbhk7LaOGdo zoa9YNs@|8*77SbCk*baPve%m0UL6|plESPS$O^L z&@X@I+NJHIFk_8$DDm-kUIa1ZfP8x^#F2UGi0M~pr_M~No?Kzo*{sGDQcm#9#zQWn2$Mi4>ZXyuVZVy2sqznxB3T|Tg1 z`5^5X!zU}~=91ITBB(uVZWjdJ{t$ZmJW>gv^WH(Cx!5DJgCb90s}N?(Q3^{3Cv1&^ z?`ZR@%?V>H+V&Xen^;bNfE3LNv|(LIr~#9Kwt9I8j0eU8uOUW2ovKK=Z2ho{U9XDe zIB1ZOgmS77Ozt49voy)JmaLHe;5eC+QZd(fJea>M*kcZd6him2MF*VZcUi%;Lrd8^dT|C<`7QEGC8BJiEhcDG z^EDO@3+Je;s{2s+8pQczMnZ@fjz$#G^`QD5$;MwZcic>9uExKYDYfHylj+rMQm=(H zo^Q>dZHG~uYM6pHFHYzzSis=aR!9fMBHZ$HvHeH-Pa~hpLm2+*i8C{1LS}CqZ+*%V zfbSYTW{Ax|@^m%SK#G4th_F;mm7<|Gv~Mhf->v$*S(#CZgc569muw3c|<843yj9+c*Ita3v@w#J9 zETg0{hWL~3@z2P6qC^o%9>mqJx<&6I(0=DHZNLX00iBe>KiO14&IQg`12BGbmMVyU zm4JVRcNENk-IcdF_D={8AW?5w{v8w--}Y71DfEi=+KD}*f+~ceBE0&lEv@FE%Z1eb z8VH8suMx8ZbI*`RKlFnb#UMCxl zwe zUva*WWsnv4O~GSI_Qb5L&66ng4`d~OLUla+wEj1gTSb4D|f5ehz zlGcRI>$tO|{hRICd@*AKiqsTA+;l129f}eQkl%G1o+6~1iHEsEjJn$bKhBk>oo1V~ zxr>GD?YkbjZrTJFI$UmCdD({ zu5Rs6cK=l@m*-fE%Kb$k6o9(gJUFIaIRs*_tFt8^ct+LZeVGn|?YCO1JCD?*_O2Jt zm20C*|KPiGM8vb*m7Bv zIgnKQ(1&15W&X6h-5%p%=8(8EqpS7A{tn7i;+`S75eL*YJX{Q}g|Ly>g*eR@2u>BU z;F8Y3C$}+fE>fIV3&k_PkGf!R{$ZCkK0i2`x1*Lhqo$&;%5674r$(Hgmu#u>aD;FI z0*%U9r;ls1&sW_3OtQX`5JcyG_MCPttXQs5J<8igf)`dMZR*ZzX!IdTI4I^w5z>Jw z2+}jPdICp_-dKGWgGU!liO%>kx#cHJf3ceX<=AGbif?8ZLOrqAO>2&uHYHN%;OU=2`LbJ2S73g@x%Z1R-u@l})iASgd<&YAg|_m56`A-~xJ+ z!2vZKjXRNSQ0=ATaCeq$F!CM93I-`M>9>{n?m4^0q2&(Y^l1e=0n?d9VV`D^#qzb<)R$LlS59~c zVA|NS{8g+|822sP7t7yF57ARIoOa8NAeZhpLBzz8gydtNaWXKeVKeNAEbdDi;34=~ zH|MKGfKGf+n9sJ=%ay#Q9yZgoA>mdQ9x9fP3;O7>`QaPcR8_}#kQ`(BM({39V;rCX zGJaTSZ!yk}x-XPqwv+t`7o>ITYY-`wv*tss*X&12jS-bde-+~4p-GQlB+Hy;ijRWB z-IEs67ILnvu{RJLHp&)RSEDN;R1@to8Xf9y;J7q4Id|Z1#%83VC_3K7{p%*HH)!vi zv3@b|7rxPCoAbxsI6CImLk@%0$Y(Ezye$PQ3!ztr$lniEvC?M_K4p~LAz#nJJMdzU z;b<`!dv}siLp0Xxt7KbRYs(q3EAw-I<-@oqwo%fz_sS3Ng|}bP!5zQ5d;oFxZ|EaG zoKmR&y(z33Oy+(2ym;;cW^QXuW&9c9%2#WGv@7epd@!<9-aRA)DNjsAhNkj(JBFx7 z%1UK@I3lvDfjYv$*SqI5Z){psTVk{)jfB@DD=>h~UU9xxzzgme<3$f`kLXu>GWzoK zIWD-yGpI_C*_u)}K&F%O!Dd|OCBdD6hW#Kn$E`50J%Z!Pdk^Lr$o+X-EEn}Cx^a8& z3*@ZAs_)b=Dd|FE-LqEar)21Ps_GQHJ)Y6+jXy@Oh>+R(V1Nm3eDmyQRfNo8y6hv` ztqCNJpALhw8X*V?4UByIAx_cP1BXqR$Phk!i!@vd=%8HI$qW&+&Xzf-oviBj-2z9o zj3T)+@7G+6(8`uq4pvfLrJQs+5Q@^+FCXg%PXRHW>2;lZs%oRg&t8Zo&-iJ>sk0KsIWBvrPO|T_#8U8(Zo-9{ut)=ib}fhv*|O}q$E4T<%5H(xll*zHqssd=eQp&2+KITE@fEdTpX29TkJ_8=X4KN;A zUH1m>;h$388*H5Lxvve|U9jJcAHJuJ!wWvxNF4eX)C@wzUNGFo zs@O8(H($%3mK2*$%)BwscUqE4Kq(Mj)eCH2RrPACCE^h_^B0}?;J=#xL7Gr^_Uihp zO#JWXZdqppw=BB-U46`{s%dQu|wTgBH4GN&ts`A^KlZ{Ok*7D{vG_96e1 z_Ho~z7kh*r#b0|_QjzQ2C_<)Wzc}}4GaN9hhQ84E+=#pKu-n9)?vSLSor+FgZp?op zcNGrn{c*06&_16heq~%h#02+gjRRMV`Vs+NyXwa-k4vyM=N?AvLezs?C;ZLzqOxkw zTgUjB3Q`JWXLQ`nY?{}k6>_S9s} zrn{;*A!eyUzuW%?{~O22oWH=smXzG&Z^gbcG>hF5)r#^%*6MBu-A-X7foP_4B z8zMueW4;i9DuMg%2zu!G(c@iohAO@P-8})Lv*W$aAMSSB7qmvi zfJ+g3kYjwDgTve$|6wDwDtO_mUI2sEMpuGgrEqP%+Fk8`9;0?=&zs5puVU zn7ZM%*6W`APys11WBGPTcuJTyAkj6e>YbF`I2uMXH(i5RO7`UXcM4H$%8yD8aY ze%&}7pzN~r+Zl5kS35V?d1gtJv{sJam0H~vq1y%<2E%o(GpSYz58yY0s>lJeSRB=5 z1H)gbAg|)TQ@7i^m-Xn8K&pSy*QM2s1LlRJ#+a`hf;V;f`cUGq^@z2P8cdYUHQZ%2 z(d9*o8`e=a*~k*Scg*x>>8V$b1gdj@u@Fwi)?^WnbW#Fy0~jduo9y(9{;=>ov!Xk( z45Rx_`?9v#Yankcz)rHGL$O;nb#=y( zQaRM7K;){EYR@1Iydqy)_T^ppNjdCWCEmkY>xX!6=#QIpm3g5=)(6S&eZOUkk=7n_ zs*|`p%BH3Xk+Bdo;P*)R>_=gfLZH@TyYtR>Pmax*jW1)!jr@Q^6;N~GI+dK_0sI}k z?yZQei|WTVG3D@Q@j$299jsLYwCM{(_*+CYd^C|qX$HNXV#$6Sqx0zeYxfl66qk#OdJ-ppd$6X6qTP9Td z1&0Mo!s9r9<&;MEGj)&lVoP;;i}Nt(V!b{~q^eFpXY{0?5$1+8Mat7bVityLe`UN++8WHllcEQ_Yp}0$x3&nq; zC)=4{T_9_7XQ`^gz?0)DS#L@}4gD_k+RR3;w7QU?li1K=k3f<%Ywa9UwG8|bZ37$t zkui=?>!wWnW&<5w20h(L0R>gOfc=)LcaGp+O8))q^-~6Y70N3Kb%VfvH4(={2`u*$ z87mD{fHin-Fq4~yMs%Xr;@f<*HY?&(7aWaPO9nIe&qCyUF(BMl8kC|G5PAst_6u;& zQL5jl&HOfbH4tj>6z0h!dtPOFMS3Bo9{$deU5y@hx?!+(u90k3I6~y6aT_=n;KA4%A53anN;^W+hyv=UdZP!R)&DE?n7hVuLf+$CcV2g?iU6*gH z{Gp*kPnCx(t_zrR2>XKhk`%nXg+I7)lhYO33CPutHSVa8fBQNEI4tm<Kpp*Sblny1LvO9d^AA7!>5&V+ z8NfHrFZ_l#8{fGcgw2Kgy2e~95+j}2bz_)9ZH8!UNAT)!%*el91j?kZ?ghQSCZT4e zS1U(`7a!8Wr;!)SSeY*3KWCSBsMbg7IsWc0d1CM_put=^DdiI}dBML^J1FsD{kq4n zn-rM}kFbjO-UioGHk8u6Q96^W--iz%>YqXf2GoXbRDP?Uc7ub5ErUO)zp=RBZhmd^ z`=|T77c9seGYCi9rxofI0RQ1(_vc@JA#vwW)=r(jm#30+2DqM16^Yaa@s2*tNpd+x zyiT8Xm=nuRTSG8!aeFuHzr&(+gI?! z0hJL?%ilae5$rRx=Fs&lpyw*#vEs)$Vu~=~iY=P;sDqNzmp(dwaVRxq$10G!T3wCS zXDsAEm8vlsf(p8Ye$O|qaQ!qu36V`OY!d6RJnGeZ-;-B-3OBVJ?#WvC80fnx z68&cFmIg+20HuPaBEi^-h+dpR4X0Xv%Om`MNWt5mxz1OaJCw^h!&H(oiPodQU?*6g z!B^k;C{tnbY0?$zR3%O(sPJDcvp}Af-=ztUbDfVF41?~e9=Z&ufr~CxH6y%mt*bD} zPm11O7LpS%i^7}>4$vGI5Y0CD7FS$|X4kX8!mO;cV}agWhA3iqqGWWU_EuxZtqXsb z*tR7na_Qoy^mxi)72;oRqcd|pZkk|vo?5yWWJQI1a0pbseJ9*=iR!gv1F?660FT;S zQPKZhTjoIUIoz;bZ*Eqre`r=!Ikx+qqj(lR)v`Tkjpn)@1s$6czg}gcyq4UX`=^)> z^T{DJmJ$8aJDe@HsSO%~r(m-J@=8;;G^0W$es3B!WdE@e{81l~NIsiay14I9AydhP z9fb2ckuz2D>gL%y#i?tXH`elQez>MQe6iPb^1U*-SPN7V8`~HE^c{z@8b`A3B|Tjn zEcq+2d$+~kCI<(6^zfrvw$jQ1)6GFg!6bPwkVC`8=@r5J4cnta*fx+fK8?hkb~Pdl z&nSuSj1th;95=}$1bmcRC`Y|&zSwi+Yi}FIfl0rZ`%|6lIUL%%vEvAP3caBH!x*IC zYDmaVVHPw(eE8v<@3OG&1e62CBtvF-ceP3G(`f8UB_S{TMzG-is2nu#F@uF1#XkZ!aJl24$g2Tpb!s`*yh7;SiKK^+%~)!OYg0mi6|}< z?W7!1AUv^k9q=U1SP$e2`~C39T`J2Z7gv7HdOi4VNC1C){R(If{rX0%Mr^9vI4ccQ3}Dzd_$)E6~6?~YL1U(lx4Y{Htgn?e-A&Jhx(8jAqM-ee0K#NRg`KDOA}==&JdrF3^E5mUt1KVuno?I*bWN3tFFk$bKD02 zBIYcs=UNxmY9FUpM0HZ~&m<0R zZAKdfe1*(-WkS3d*2IDK>3o8@Mr?-~T^1a9q0Opa6OTwL^92u{2R0SuDPUI*!l-EZ z7wTUW7rH=zl{JL|U9iE+X-&Q4TX}*C!M00H7b{=VYX1S=yQg6a!mJ;$qJv;cVD6uK zy=_U*fm-Ud_s5hK$#g|)7Tr8`J<~wH4MF-HS%lY|Ro4@b4g}MPx<_H4$$z1k(|)Ns z#FH9)FE988*WETo+KMW9b;tx*b{7Hl`uWptzLvvSB$AQ0+lFIME*1XgIedb)yMw}i z=i}UmljPdO%_rq(m7ET~atV55?WttAW{}`K5B#tY@lnxWeaZCtzxysxNsRH|H>*jV zlvJQhE~B-dXor0WSjK=ANhvYHUmk;mK}DD_M12hOuGv)P^{>SLGT0#;tuZ=oRa-Y6 z&`TBn@6saYaq9knBU!E8Xef+tf;kE1$J2PQ(2Yli- zOevWE#?kvwt^BEXR$uSD+U^b}fk}H}UcQEW>h4f;% zxdo@^T&H~$r#!vn{*G*ZZPIOl6OxWr%$YwH>MHrr`RM3~w9MuN*Xf0}%jlQgR;DYO z4<0S%?KrSB8SI-5v?oPRd2CTL*QNV3Cg5cz{+ndm2w8reuVRC*7)2FFtlo!WgY+xa z?{=kz+OsO*SE8_qJABBdVDzEnG7Y0lX?3Z$@D6)8o)S(JI_y#)3p&Gtm#uF9IwwaC zSdeUUq-Ud9H(-6HFMiSV#CO0sMCT;PK|gc#?J&i6n!JV6yjv93OM6^mW<-W5u? zgV)#X+)W=*CbH&IHwV{Wtcs?rez^=}dqs{H4D@y{ZL9~rC0PwF3)idwovO^NNY%;) z1$tRu^oP_4ZM(0H#B+|Jw5=}+40>q1l_I4_y!E4Q#R5GmV7~cS0N@h5ep&xbPcmaA z>thv;HiXqkQ><9Iq7S*G5?}#T(83Epk`QYk-(z7}iX%B~GU|k;=H-armA-1|%>jMz z`%p*m99MN#NWOHD^=n)qaAUh`a*_t=sjhsQR_eZ12oyg}Ql~!= z5R}A09IcXFm(l^~xLl=9m;8N`WfG?>LTQS_{c`Rk(@0CbTA=l5YM?vx)^HPEzx`*B zs5R_MC&e7=XQ}*4Rbb)SoC3M{g_ic_v7l{;EWARy?f;F~Tcw5}95&{YmK{~_H#^9k z=rAaG8IfU66v67~0(bs(oLjJB1=B37jp5Tl`5YOsmlZ|X`!_x=@Sx3c1&I=SU4}77 ziJR(mqS;;BXC01|C6Xtr^fn28Z^$-%eoQT_`p7v|XvEq%Z08dSToO8Z0(r>*9=CY; zWl^-ZQ)F?W9*D-bx0q^At}7jsmkCywx`MG{4`8HyP~@$+0AinDcXxP7_U|}$h@He0 zLD;C@(b1_ihEMPXjv$*#=3L!MOR3rTYgu$vHE5L#fybkli8oz4N4k@#!?h(IMTAx> zLSVPQiSP?dfP3~H1W7vx(fGajVg|@ti*J4j>!iR0nZhkkkXH}ae0J3_;T1L+ZXol8 zkL+6ow%4tzz#1tBjrvLSmfts$;YQCXcA@haf6SHAT#~scmNO&<|__%+@ZdGPf^-?b<3%022>&g_Ic3t*`*y3ahb{D~21A zbnmDTzERFdoEK=tD(_S+8RgysR^*ZrusxE`sK0g4Hh~r$rEB8W4KSqm@0ove_JbPp zgUR(_j<20hi^|=0`-M;q|C1$t<4U1hb(3ifinLGC?*?pw;9JWVi&V~7%HhcRC*+g|4X z(jj*(BUD#kUaa(2+tKPxbm46te()R8Vk1G}JQs)IrN4>$Xd0Mo0J&b!t%|1Hg#AW5 z&_Hh8=#1c;G-KJfZ09=!occ|-Vr!e)@~X!2fkDvuJ*gPvSl;Wx?%VE6_3c*SeGdlj z4#|)wfiZX?akc>3MJAa`UzmJE`MZt5Mj2*-4T@~Vn4u&Nu%6eo{3gpx>ZyF(cDWO)JEwgA=Dg^4-3S5{amh_I)?XfAQA)%Pz~&Jd zU`hvo1#n@qS_7ittf}i5c7~}tRS<((_R8|8k-7@7v8E2<9zN-~+7cDOFOhnQBZGrA zHd`>LCJ3hFTROEy8@<7*zTJLIHAf@ntu1KTi_7uL|K1#PfWl;F zp&%gv)E#UErzXKKyzb?12TmyJ4?S>=4Q3Rq z_0LO(^#FWUo++*T>OHzZjyk1xX+vzF_GM?2Hk9*$=fVDuc0qVM$6${F$kS+n$N;^h zaf=kz*mca;+(|RzX$VeFV4j(Tp6KonkvVEpAaAX&gO55(>P6bM26Mc z#`(6D?jrnTWvw}-8Z#~+6bW4T$n*RMR()%Pkvs>$@@>3wjGhgcY!HXZvMM=Aqp}EM z9|Dc-FyUmm0!ec;Hr|9Lf z)p0aj90PCjp1dqFX$D5h5*`o_gC?#$%C~WKc32R|rt<*llJYrs$w61icEgRwYgQGO z$}3@n&Wy_*Xdf}vlH5@F{iwC`O^*U0@V^AxvCs4;-G#md+<3nri{ z>7^_;aV9SlOc+AtKtS~EA^+s@T-aZr$Yq7~oPLAPo7{{JXDuJV)V}lL7^IKoiBqkW zX+ptp0Z!Emc@BC(x1%f~2q2SH!E77ozdGWux$N$f8@kF9jO^T|_gK?MYmxH7#y(|r z$!`7^Lyd+0o>V^e23Ya;RwZT-6W}4(t%8RT1zpa>OR@c>A2l0q?RHp^;gIfNa@|Jm zm~ffR(RMMLj7?Ap=~GX-{~gBdI$AgywPEXCHon3z-(_alu)##9f2)0Tu10%1C%cGH zWX+RjhSL_v0wCQwdBwq*2}$7mQoL^1ziD;usH&PxI>lbx(dp| z1nnZVcN=U9&8`H6Va?-5dy9wVeuQ`o$iu@T+=p){m2tx!anc_7?g5??IyFw{P}ee$aOG+P6f03ZrhFT16&1!D!{dpQq7p%isaTgUr@Op%p9V!W|*F006Oxd1YcQr~~{V(j=LHw&ompUls>4TXvsL`fpRto3$PqZoEKl31L;tw^R zJ~qq6K_3ZgkTZ?nAd5D$pr&o@d)?{jGObmV2Fd3$9vJx)nMcCh`065ZI)}^ z1+uTUxfkpw8PUZu14CwfD&a1yD*5~G9XYWtN+R6Jo)y-GbATg?AswY0iNa|A|8vcyfnf@4=- zywd)WZUdHi53ImyW$)gk^p|m+qHybZm+oiR3ZEijrgc>O32Q0I#c=NVqOIA8{wE*s zZDN?g4qx2Vel2>-%yl31tq7MWvsf9)&y}Xm!eHb9SiFm0F!xTqIm52vRv!p{V)jWi z8oajFyCkhFTY+;Hfw_(;#cYIZJmsvy#tz5@Lm#hmoN12r=q22*bCH;r7^?vrT|cba zWf}W3DkaWr@MwKB`yjI!@2g3A+Ni%xuePl3Cat7gh@@pi`?6^Ne3hA*dcz-NKU#$_ z9d;A*6EAbTqVUMzQY>MXe{E6hl9jN3|GQfdha%AGGdH+iF5~HPy?@@Y{!UOWjjMW( zJ>2u0{hf1ZXWCrA7+uMiK4&gV88ETu+tYG746{&0G6MrsWO=723w$Zi)2t%sGv3(a z6`A6|SA7A-*!=0QU#Bu&Pldd~k+s#NI`XP%+ToxojoYa2Cr{i;zecab~BU6I(V z-k6jWr65nD2dL|boX78c%|7gY*F{WacC58H(E{}X1uz=0rDR5T@m$ts*BA1&RRJ)C zGD!aXG+vEvmDAz;(EiyrjL>d&r(b#EF_zO^S&Nm`)!3?%W zuzUGM{UA9DuZ>3*b0ckO|%nVoMncl89YAFzA`4Ey!R1Myc^@=DFS^Nq-%z2zFCyfVb6c&MT1?8Q3@ z5Ln*<*c5-@7ZL}JM>!qrcMJB@KPkUKFB**91kJ3i73St&+Gq|yWp7JLrzLB|B)<~& z+I)?!nx~Q5*YU2anFdGCcDMC1iP(U%`2izC3M<`wYqvz4B42-gnvh;npp{i7r;Wr? z6)D4Q#MJEyM3F(V2>o9wXwQ-pBBSW5_d{6;bH{4(jPG|jX^$G7gvDEM-8DqVJvr$Q z-}r!{f}Y-})}JZ*v&@GvpJ0W48yb42^3Zm;#5JF1X*6kv!oFDSY>44V(W>F^e=37Z zR_esZU$;`n$O9R1;f#k|p!S?pA!;QBX5+t+I9UYy;;5(cn~~1(jR+m{d2{CbpA@`W zW}Qv2+VuQ})lF9zp&4ytmD^?yNd-knYL(r;vxr0%+>B(Q- znLbbuUFnbK$hdJQYv-n~D)Peb<%L%@m&Z?@T5XQL}XHg2fnUTdp);zxbmaJ7lyX>zgaA(u^)_f~?n04QpOd z&2EG^2Zq;am0VV*Z?tG)Zc3TB*jcY*>-1xL$|db?)K>5Qx_kEG;kku^qwK|VE}uTM2zyT&now0&rTVwes2krR zg%DcgKudP`so0%4P8082>(k!jT(eHiO_lok1f zoeh3W*oKi)bRK0{4P`$c^GDDV<%x<*1YD+$Gr8%e81$_GLl)1!Ff^BMyJ_4FrrA7~}uqr{}fxsMG;rgoCH>&x{Ed`LgJ~PCJ{!0c}_8&wv!2!Dc>R&|6d2g6^+dlg(n*j#rHF?dq)o4F~AR&J|m>OPda z=Mf{%c302d*J+XixY#R&YJx@EMuHOIDWk#9zieU8R-13o>MWnC z@4JPyx#O?^HF}^nfB9+v`P#gsiSI%=^0_dV0>W8XOY-m+xb$%EfnRSg3uD=6<&`zU zkB2m^oVd-|TQtq5XjEkjY+L8rS0D9Mtrq18Fe91}w0VOZ6!e-LyyC9H@4D~2^(aJX z9J}`feQLX(u>afN_0=aV6VkkPI51CHzRAq?Cj3rgG_3AUn$+QuD<})^SwEyzV=8W# zeNyq?zYD+yn8BTt%E5;E2&|d1d*2)LPz~+l=3!9P^Nbs$qS>~1jtbs3voT(Tc4iq$ zjVco-dV@A`a7I=UUWvED^hfcQDStI%M6$E=jPVMXjYpygBB)*#aC^;xy~SMY$J~@u zzOYj!%cH*7V+_BrQY0Y0tDAd!wb74xZq-V}gYWo}?*)F;9ptB~T4z^UP-x|Op=`&; z@(1_nj<+!-O=Zeln7L!Fw67*_EmyYKa?;t)@@!6cP)q$~1{@mSjh31eeN`$1d*+%g z_D3aM3MJf&aBdzbl|I_omV`221G)NXExsg`wI>e@boKkv?+N2K5HkzOa(O#-`g{FN z=S66%6r)k$S*}^VB#@|CA^mq<#b94TZNK&g=cJs*D~ql)LNR^EZK%D>EECyM<8G5q zirpXPygv8bk9ilrcqa=cwe8~F1GQ<4@n%v|^`9Hfr=|;K*470+{i?dYU#B`Tp7LSB z>f%1hic6c57CnRv)C9cy)1Q2^}wH z)p-lzADvXN7EgYf!h?q31I07fYad_Q7-Ak=S^uSq0fx`ulXw0ym5)RTsEgn%PyF`k zmw(E63f9Evtp5||#Nhlrqmx;9pY_Bok%=}EvnF>)Op)G_fZY-F;GFn?oE^274!ghD zI;z1MbZW}k^xV6%{AtHebq|8Hmc#*Ze4KMvbj;$8UE;aItCi-yyhzFRYRRkAC$bW+ z+Gj*xEaT594-e!WC$t|=_%IBll@f8HybqG%d3kc7aG^^~E_QX}GQ21qsl4*ZV7ZnFvOcCXV@aUvMyNcMNDnm=e;5hTi(p!u796l~5Y za{jy-nKLHql=nw}!|~3$WATdR2h7{r@J7lr6WKYoN5V?d&xsE`eoAZIN%4p`Ri@`y zRe?Ph>pnrBVOLur!dsrvb7_m6(HT-b=7K#_{pnfdf?Ijc1N8|{yKhL_zT7hfQ~CmX zsYlp zu#6(MC17217T!J;;c~YS`iz!n-G{+2-bVV#tm<(lnN@-n`A^txdvG?cce^GA<(3Dq zE_jU_IQn`oqpzBHMSQ$EPoa;?f>@CZdx ztN597qMHSP3($y{uY3}65zOV?kXU~&vE1<0hu=^B$*RqHAuR_8-RqAf`n3l>E}Nj5 zcUa}&#CKKyJX@V4^7v!()>+a=y)pyYCT4dli1aX5uNmmv$c)#xxyJToxaN&)( zW+ok)*Y~Qx{7KfK>N0aoX;7q7ywlIgxlYd&0St;@xp@%wun3Q5Bcu{>JdKwXW^;>= zh(#Ts!Ne{NI!n+>{|q^h;|kM_;z2b7*WM%HCeKpRUpAkZ zHNqQfol|BMsCUgP&Mh?Y&Mi!$H@nofA7$CReyWT?4Ss0K#M8heT5$05g+VukTaPHl z$eIHpy8b+A(C&)oGOsW1_9@i4pbRzuqPO;t1Y6H)l!Y1Q2v%j1aX!M*2t4a{fsH&onX_gdXsxTh_*P(10227Bhp z;qo~+taTFZ=mE2FL1ry$uU`z|58Kv~i( zi&DJUv z9#MX8o*=LOpxyrREE*OpH}DHA|R z??4azy8WdsID5wI=dh4OC8ipJu25JFq#m&Mt7!5J^xvkX5Ym(!>@6Fz2QfVq|9GZ4 zLwGA&Q0?LTd*10`wIq~!NSJaoQ!`aD5*T$Mf~=G~E!=s<+w7>vFYVt|bwH@SF0|Zm z<q@IULDIi?cfhhq8z2jtFFns!MH|iU%YXx!} zCCL@zuYh^0C`CiyB6@|E%K78=Ut)K<6ZW^GH-UebAnw({ z+>Kc{?Lk$otR{)9&*Qo8&DWf2EVNnO-aSkjI@`U(&T~k3x{2@t0A2Um|oZ{id0a2ZhwkhaBrcj0CjMm;K7RD!%upwxC~pqA2Y3eXaEcb zadb&rb?9Bs6IsOZ%>B>PD}QC;WyNpm;f*!S_a`&l6erko@-Lu+Z)YBmMnJ_WAB=Ea zQ&>ypKiW@x*mWEXh$StqltAxN7h-YPV5mIC#y3?ksk(1vPM%bjSPP6z`H65{jT=oo z_u?K)$STW5luFpzf98l`>%xymPFR&+*%5JqI z2hEeC{R5@S&v_PnMf=>!4aoui?u0bW3H`F9lKYs)_CLm8C-*v`>NW1%nwBfI^xC+I zpZ@9Bt4vR@fkvDYb*ss#B;B_>S#L}^Ue2PD9A_)8rLy}i*zrktq~|@)$Y`6}D>~XZ zy&m(Ps#UBozQ2=vD&Y{=T}3wA`@OMkFso@!)J$pAsf>QzXWyf6_M&l0`Iso?cG^Mc zL_TwG9nr^|UCm8>GNPgfYT$E!_QH+>)LMwUwWj;fh$saNVF>Ae`}OVBHN~qjm9ui> zazUDXXHnDcRNdLJ_a?)(XAjSAjwrk3`N!z>fGIOGuxr5V5twPl-`R?WE?2C@&&??i zCF!YyrQ(<%rXps1IpM`J*yir#v+rUB*R66Z&gQ1;@eDnhSSdxDXLCov$oy!jD3d$w z{0+?=PVL6}Mn`spJqSI7SAWwqc?XpD%KI~xZeTaT*pqZwt5-=L@p~ebeML8YAFe3@ zHKt)cQTY;#RQWvX_sEescBE6;;QE?UAZd+dd#_IR^Iq)bfZ&&8CivvOz=@oh=Y)Fm zP_PBA@pO(C&%@mx-anqkfj+r{MT1ZNqQCf~z|O46*G2@M@1GI`B9S%5UV-EK6ZC{#eP+KzP|Y&3}h##Sc=c;#a$6pm@cZxaygBy&(nPx;T* ztdFX{`*3mL1K9b#F?ES7OU@p5q{nsjp&CSOZ*eSiAm_kP4#VfHqUcxK*!h5(y9O=e zo7Ai2*Op5JIy^1t9Zc}c5H>K^r|(3!*=C*BESE`qNWk`7`0yD=8w~n5=T3g{9Y>3= z&TF2*{Q4MOG1Qj(>{zDTXtz3_(v!REy=%UU@aRl7ik0Bp{KE26K;_Lau$MMTC-?5- z1$)pySoqqDzgoF|fmJn|)N8CWPb^u2f2=(8g4d>xXYrZ5C0A!ziuq!RI9XEbT~eLI z4X%BMME@UE-x&>8|3*6)hG4WP(Mu2{YV;mmbOuTE9ud7W43j94goxgSC_@mE=)Fa6 z5#1nq8@-KkkN1DqUF&|B<(rvv&hvZ9e)isnF^hnfYC9j!@OL}Ec<4$=^o+Z@CTL!z z^ut&XqmDIqsrkS0djX`ZHw>wraHGbiGi`c5V9Dtui{W`W+Ej4Bz1rHfF`8pmWee=z z*>`3Ru0<_|*sd*pD7pgH{VC8`2vCg!dMDn*R;!i zF6U(qjB@VJ;Lv^^7P0gKRR(FPe1jr!Z{%u%y^r?syLH`3hP_B}K z6;)9fzEC)De>EQD1Mp@s0M&$P59uTpASJKpRyryx~yZKABuO>c!7mo}H4QD}1M&7v%5FUX-<_$XjO@ z9@%ra-EzzFm-2Lt0M^WV7?ZGZ>o!5{oez;H4i;3Dw_rAnjaUqG;JLYxEVdlACl|)} zChn)ZumX4--1~FmxaViYsVd3$sZWY}e-0-N*?9|hnL}*~uI4pTX>o_>Rj0-(fbn9y zD4qEqX;wf0sASQe2(oQx{moLLU%(5ajqTMBVV^<^R|i*U#`=oP(Bp>%G|N|=U0-AT zMj`|eOwPK#xcjmDZnLq2g;{@-1rVb8P`o&0-!CVACdQru|* zr^)=6ra}@~-tK{H5Rhr7TBbU{z9~mpHgTHa|DjTv1Zl2ll{m_gM%JJ$pYS*AWx~$a zJ#{Xwl{r_3q(MNlDmkAuy-&l(#ya%FEszTmCd`xPB0-()^V9jPouSBgz%>z!_+gM= zzy@0FUcFQBBllNcu-I2P!k5*omQ#aQm;|%%GhbYZK)zdc-Vcw%P`)Ebjkay zQq9WQp+NH&Xv6E^)CeYGv8HHStvMEMLvmket6LMG0-W#uCkh`3Z!WR>>?jTZ6?(Q8 z%T+Jg8is0p{$lTWr9U73p#0YIO6a)hb+q%s`P;*FoY zTl4{0q84#oq1|u95-rsOG_1K?%et?wOSWNdfQH^{Mg*0WjSW=h{6nX}L~=Dh@2XPx zQZ|pSdE4Uyin%#UkyE#^9FR%=uvHwNbTmcCTo7kE`$A6;!OK{|bmYehvdp z)fItai{Y#Jn*}WQ0A2Qdvvv@dhLUo44^wkGbnv(PL@Tvu@F(rjD%1t_%=sIVdGZua z23)Lzlmy^Pu(_da6u3zSlWbu5QXK3wK131~f@wIC0z}gTq7KmFeAl(P0$%tfi;ugA=f=}#znWnw|N`KN< zUwoLt!%Ngg=XKW(FXeCEdu9CY8-e3m$4pf4h%3tUs2Jt4%WWj(Snj+a!ZKR(_JxsQ zvRCKbanYMuyza}5I(cAN%!g+yPFJts!CpWlE+uDe1>K!)sSS1(?*S;b!+(>z7Yn zas`|A(niP5{K3Mc&M(Fdrd4Ou+vY#cz2`$n^fH zt{2D}IzKYLI$QfQsDf%U6v&%zwk+dKum3Q>%pgC~pgV^GoQ6_|gtMSDq!~XmyZW!x zVfAK|iyuegoO8~n#HtcqCSFY|PRbiiS2K(@HLV9(8<^k zVOg+If4uE*0ilKtphnZ*P#tlQ%^^`>5@mXL^GmZJzkT)b4+p+%Z$ZQHtr|B^n26F> zn0)Fc%^|m8L5SN#)Jrr#{Oh+o>Bxi0hKQtVIyRJ}{C5q-e6dDd+qKy?gwLpnPYkCT zDta9;1#CkhrD;cPVkq-k6Y4AnWl=$%ziPs#w|*k~aQW5K5IXdYx-Ad0fu(G|u*KcFfHgD z*L4X9t`2EoD^piaDD6qhzwFXpTfisd>Ih?}cW zG%#v)5rVHu0X|$~d-FdqI{;qc2Y*PSuSZH&#uxvZP66$s;}EE?l~X0F_1sm?=bMzK z=n<`-zSB5A>>mM9Sk%3zr9|)5vHUktUFdpsn2}!wAo$HCo48a7-)M%6|A=v%%XWGB z+3!y=ig`jtX~pZ;8_`fQldJp#ZmFZXW)6BK3n~f!6XWrkx9P>Mo&2!&KrMXkS#S_Z zPR5S(V(wR707w(+^0R%9#@YnmBz?^Gmw;p4d%Y{TnKH)n$f5(Y_n732Lca3;aCXec zN$AZmURz3l`rd?0CA&vDA5gt2x&csT6pA@h=^c4z)RF21d zKKT}KjedMqnGWnk{)qZ??LK`1`dfQ(o!U9MHuIfqTlAP`r=HfIF!+kxf2?(?Hleqd zML42&sKy9ZwUAj*Y+8J0zfQ)S_BYb2zn#tImfBDpbFK0vHyNz~^im~uEK0T)v%T3Q z?{<(i|85!zH@Ua2M(a+DeB^dKH@!|ZVdt|uk2JB&-#2N6MEhIGv+mFwXIFi$jyhL| zHF~F~gULLkOn=3+CJA{Iy#sKX?N=8e(&*Ej$D6C&#b-rSMnHdT`yPW8Z$2G5yN;yi z*moybTzsz%0aWV6Qc=X*nd+Jb{ zQuJi`cYz2*chdiUg{){@m!j544~w^_$Y7D$STPZwN1|cWmutQL+BrpLzoJrWRYXHt zyB{Ba%|wxx}?5hTjl%% z=5~^B91^#@wp{WuxvvL*Y>kCcbc>jb3fg%%O zA3srpy%GzO!eD0ePk@ox>#K2HB|d!?Q0LM!4I>-@Vv_T|!- z0p4e{PNS`Y;X=aUE|-tDTes5>Yf9FxKM>r!0gA_Ydzb!mi!k0ckgu6qtRl@RGq5LF z1yC%4Hqf0(pi^_^GMP0ExQZOfi3ur56FzivzU~0ky=ZsB&tn>EbzRX#h1M7QZiE^E zq+2vVUh*1RnJ+xOm%}TwA+vc{XkxSyBe6Y#`r$UV@Aj=2SCg~C3^?-?%RXVAHiT|8 zk5JDW5(n;Nx3I_iIR7dgh?(%YeYj7jPdfq5rd!avz~I$BULt!HI(b8!4$z-qP+KUH zNO0KW_9u3M(iBz0sdc0zWSiq|?MXy0QS#sSKf4p~ss3lc0=U^-Uw5Ja^9-Jyxk(wpukFcM=N| z6>v3tYsXZi-#}t8d}9%dlTq*z&6#mx-Zs8VeCC2SNc4;05DmX=x8=p)kX?>l+gqXp z9UY!iiw$x~8{&9tG>9(G(EeOdZqnm0QpctD7r&QAsO-5ZX~JftQzo6J4t*7ud~MdP z(+{9sAxal4ma@+rPSbtG=(%g1CN)e`9B-KI~^{Uq$#(q%07A-s$|YGdD*(IVVQ zHGn8M`w9|SOOA#sy zUCJG?E+(W7sScK!1^Qlj_8lx8H~dEJwq{VGPu)T=+51;7q`Q`KNe`5^fE(t`^92~- zin`_{lXX|m^uBTpXE2o}wkV}2+`BZ=eu*ZSKd_mdJ2ZmpNFYaR=(+A3{Sbh>?l%^Y zH2lb+U!Ip#+h{au&htvk|1jTX*pm`zu| z<*Mj;TzM~}-TLldgri)(2T-xo>M{y&0|MAGpQSo$h3 zDByGo2{^>iO>wCL1GeYXYt&|~+u>9z#OV17AdCZcZI)g`Cv*Rw4dL6gugg?KDtf360t}tEQGyL-*kIP@YB!M_PzyeFQDluZ1*i`s8gZzP@C!WWhxYFk`~R5nWLlPHY@Xi5A0e-xcmpbN>Ck zb-iR9{)62Pm(BU2ZLD=*tchxOZJDE_X#Z)((ayKgh3HF$uFLZ)w$oYh31u&8(Hk$c z{&7PHlp9|Cc3kl#Iw}2$q+3eo#zM%DtNw?SLpGSe9O};Z)=yMQMz2XVz&k?${{dm# zxbiw@jm=%BZZX9j$kzkNfrtVgri>?&U-4jH(G3V)0e@}k=T#G=VQ&hN_L`=DFFmIH zGBfxZKBKbc74Qa|jT6VK{>*uH0ZIc= zSOPA5X(6K+IBiqti}!KgLJ0_lQ6C42oqN11B#UznC!Oi{^0thAJ{3Fko(QY3V>KI? ziKr-Otjxyaz@Q>5eFOkzkpaHg&MQ=kbJ`eBV~z4S;Av4b#4=GF&$To2{BS)V$>)H{ zR~!n-Pdl2W0ZzUN=Z@P>nu6{185MBeNsT{{3VswzQ;=B}lUud{t(aNpJDg_SXAqC+ znmd$tLl{X9tSI*sC-X;{{|ibtJ3 zim46!^$hksYGQm~s!%xb*YeFW@7K@Jq=7Gej(ZW~rTSiSUw8y|fwq02)dA1xq`TB| zLv{w>fHGh;%tS5~Tka{~K^nsHn#a}c`-I#8EpNhHb@nZ@+i=D5ZxOvXQ_f2PujANT zYbun)AvdZ4r#*l-RXl(u;Jsa;j?C*#N4y8Cx@BtR(HLFTB;Fu1Jn9?Zh)s=Q4jyB6 zE0*BD++OhoeY?J@$&wId<0Jar7& zjoF{05=~lSt3F{9Pgkj;&{}av7P*?6y4D0?+>0F?}&Wx7-+r{n&uCC9hsillAdYDRoruy zQiT+e@+-^&=tuO|Taa7PBuw_BT=cDIGPM`L!Y}32B~)v8j#|g2fnF?h)(<8dT94oh znQ7O&_~G`sSY)+~GaG2VE6}A(s6{Vv!||nWyrj6_{)y(6#_{(0@^Hy4PUr$2tP9BT zvtAszzKZ}r79j6^IsgDbbllra+`}h0u0fAo5e(J#5r&1x_H>n}oDbcGAHcGyhVpM4 z`QBY~o#K0`yu}I8=Z1LeL5JO3rnoHZ7MSGt%<^7(vD7-t12tF=)y=;4bTy}gz$RwV zC(OP<|A$jc>FdW1*WIY%KEFe`us4CF_UVz?bTm3!M`s_Zg`z{<4l+$6gM~ zG==tEsLXQD7B1ZK;u~@9{+7iv1E6wBV4(23-EEujISeA!Pa(<^*thM11}hqvXJwlg zjcC1WtQe|yZAa^475qhGSNYFz1q-U*LHNcMM2r%zN}Su;VvBt{U191v?}UQzoyYsT z$8V2Mm34av%JQeS=u(}$q5e8CucXun)~rSzn;8%sO2+`uwcCK!qYV^D=hT!mM5k1x zI+!Bg?8mxV*8w^Rl-GZ*Z`u1zT7MktC*V2i7t@>l5-pY@51f_PHvZWPBYRUUW6PW~ zFrSiBU~r~dw*^R)#ef!j!l2rQ58#9TL@k%Bt;fVRT)rW-JX=u+J&`^>DttrIyEd zEtstCHwt9|9DH{{Alny#n8R@}Ers$`9tX$90)wZ?yahstm-iC%g`SL`|LH4QUTiMG zO+0zs_vgDI>BH2jWC9OPqsOjn7_wTM$K1p~T*W?^^09+q@-g5bl>sV_u9NLS8LrLes7QuJO+LIy6c#I*|y zDDVGzC!lH#b1{baPyha#`w}5JZF2%WbnIOQ=2SS@Go>|`+}^h=#hdn0lUjwEKvDfW zYx%V8rOo+AgGRfT)2B^qPBJCSuh4FqHHT8efDGGjb^q!L?UqYA60vXFvW5)2bS268 z9JEERknP1ju~+~Pz}uR|h5h|a&wJ5xbz9Bz-mTqQ&{k&eq!S(%-Vm7JK3xm>vMC-U z{UPHj3>CQHd$Ucwo@OW(`f94C^6^g4h|l9Vzz{uwYw+!-9KqNo3iz$SJ<_pd4G+60 z0R0e)rE&nML;&G`6nkXiBfF-$i7iZqtsMG#jK{{R8EUZqB7L zpex$vThs;e1Yjk6_eIO}(?>jgSEk<6CclySH8de3q zsqw6cWqsL%ci-VnZ-m*aO;;xt_%gc@x5E@ZX+hE(aU=WB5L|NADzJJ_GTjm`0Oqg( zy@)>%9k*WO{KfR}T5k4MvJ;;e1|qvg@IT9a+J8Fw(h^`-Oa%a9B<2+@sib%1{${X~ zeyrKX3{_Iu`M+M4bY`O)sQulQ`3k$3lxHl(T(Z@*FG+K@sZvO`K#hs=`EaVkhTP^( zI00UW?XBVZrJ}6@O5!0Ad~bs}uni)xJG3qTe0=3cU-9xtD5_K!`^&d?S_6_ic$GjP zQ~yjfa5vKEa8w7@81XRPihfe)*6(y@s_WRQ0T)lm4kRuTAwK4|E1!ZE*PIql)D`2A zS&T$kR6kthP0HA1doo@9dVCPiNG_ePuvAe*eC(_iQ?h9clxthiS9&L2kJSmveV!Yx z(WchmU;5Th>H)_#Kzl@pN5@LwJv}kC`BwK-eSpu%KDM}UDO)z57k+^U2-IJkzdFJC zXtU}jCil>{rb;_az6XHir@!>)HsQR*O|=e!`nN>De7LQo`T{0=1BBOV$Ett~6=WbJ zvv@T4}}V5#=HUqdnWQ^KL^Vbn+JhbNu;`1bucitprJ;I z6IMe|hWl>G6|DXBlRV%wl{R!@f2(lD__spvtQO)(7!N{Q{C{-$FP!>56qpAD$*X;u zm}fcxYT2kw_yqlYzkmH5VE&GF~pAQ1NWP*QWeB1gKgyEy{RUstzPK^x^L%DAb0`t^1;GHe#O;@Zx;l2iTnPBCc&&JAwG=&3%8ch}vC4 zE5ccV&0%tY1w+9mZo8*k8#w z-cUN$;5Gzw+vW1dMRJ>AlR2#RC%0bgtF@wDhrJ)=8N0dhEs*k4rQ`B;QunFt#>aLi zG~;hnUKF{mxF)96CcRN=NR$VNIOp`FH4F5&veH}oy>~^I)(9sIC=7UIMvH>`y}h0V zmRW+9cAt)4uQjm+w_!2AEct5E4mMF$_7$iFCW!(zfR5HJl0mNeo%3iba2IdeE($L( zpzV5%3e19vDSm7(BlRW$+LhBTwc?^{;#;-47M6Km@`+_^G91XL>~v9QWGNKhUwXO` zfTQh8*=|sQ&z8zt%X*`6>>$kL;>_&%NO0C};%f`<654#YIY$!7*qku+5WQd!@IND#01y@KGK8*{~SH>OSEq(Iwnt})RgrV zZa@s3v7{T7h|{V=q83Egyy*pX88~o|%oi1Mu?3jUeKsHC7J!uDD5I^^&iG)&s_YgBOVX#$e7G#ty&^OHKu%X2$8(zDyuq zd#rkImY$*HP8++%o6J!MfVucZYf758d#w>}0D#<~`(Hl}obSFFnosQRhM7AyieuID z=Hg53RmCRhklHx5E?V!C5kg({_EVD);L5w)VHNhdVHJgM=-$6>s7!gZ0CvKKcDU%M zr}4iWvkd7ef04$Y`$$8{N$I)Mbbj?(M!@L+(`#l8k4?^T|W}8CD5qK6HOvAEor6L4%0ybzjx8(z0R4ibd^&8N!LJ(KWQtrHG zx_k8tWDFTZ3^=Zhr!Rx=e_#(Sk2A^go=C;=CqOC9XCa+?wq)jV2Pg_aL zJwP!KaE^&YPHa%_1YxZd(Wg}%SoI2lA~p>3X)Jm0%(i8RfpO$1lal8fjb*6Z{#$W; zuja#FQua3#iro=?=G6iWVei1KxbG%#@039@z>v#!oN{oencDi>qV$pPiJ85G23|=e zNzzc_${&^w{SwT|-jp&Phbv`WWt}f;v0{i4;9+VZ-?*4Uo(ldexhbkEZ!px8w)JJp zY1w%}M|=amXC|_GhjEV!!rJgeprJuQizXBXEjnU&xl8`o+)KfS`Qp2RaOmJO&-LBK zfqxbekJP_DeQE(Zz&JFY{L1mdRvXQIAq_V*&ts5rpC$hLs&wM^afv|_cF1aD9&z4EnS;r5eX0C=X+ zvCaHN%_sK~C2YU7^!^wP87>0hlE3P!n4l4mSy-aaA#3QtdHBFoPo@p08mt8m5&5&0 ztXJh(vZTW@y7LdFbd(F;cEjn%wNRF^zgdrSQteKj*Bhv2qRtnPE23O87l_i4PVQIu zv-u$>b1~o_#PPjso4FZe6_*hNx$MSym_x|U{0~H=7!!CUJc%>wy>k^l$Zg4;n{(hl zHFd+ysjNiD-nS-Ttf z4RVwrlC5sA8DuD5(ao}T;^tg-cNZ4sJcbNk?v;kQ{B;OReCg5GTIp!y_~EC%qomvUIxb#F@`2zOg3lq1vBt>+a&s{I1&4tBX3EaKC7dOb5>N?Z={T> z-jS_dhbXezdJ#mUO{gH?$%-LdHFo!{Gj3T~h#;HeT)*gjp+nF!rNkA4>%sbi?_DF; zg>t^vU$NNj!#`cI!tUnhKny-Ud$tV%UF?ZC<(BJ6x&shvVo!kpYIfc`_ z?3NsSqSvgpcjxI|+ZuSHHEhJAB?j;`si6)N6EeSi6 z7w#wwf^Bqj#Jh7?ucukC+?_?uyCu8>Q*uu#9a-!zp?8!oJ?^D=P$Y(n1I~(NwJ%g0 z%kEAdXLgXTMz&KBDuCNZ@Ri=PbP2dHA|}Hp4+J3(s@l~;2`PD~UvJUa8N-#eXAjMB zh+AACk~PRiPK0)KIY+-Vv@ti$6%LD=f3oC?7cE2Jc$onYbvP05`E=-}qpDYrB05n(n}7X)5?jI{J_fai#2A5crYjt&-4M1i3V|En6f_-de64*^Aw^AVlfona{Icd zbS*ZLiM#BaYIeA?pNZ_MF;&YaoIiFim%qLG56li`%E}S1=*Bok;gkcEX_K>>=uTYR;Vs5tY=b)3K){qBn<6EvT2pW_ zg8Q&7ma(;r@Qw5&L#o2g$=7Ym8N5)A2fo+?Ir&~(VF|2w94GIgy0uigwSo-p-w}`3 z@0cP>nXb^~9Bfv!a&+Y3n`3RU9x)a9?hp=ql?LO3jYv({=QBx}0HY*49S619)?#{l zD#|g3l8dKq)ZTK3BAyV#jcO-d*4b8DF5It27U44{ju#RA7o_)beQ1WytFy31@5tqY zN-Adxh*9i9OF4t|?M&|#qUaz{1Rl~I1js*HjEsLPJoxAwtnF7}K2aK9l|=Y_SLO*E z8Ol%2b|xGnPw?MA|Bz>)>4|j~GTkhhr&727cf{pMN)=!7t9V_5_D*S?@018??q3;Q zhpad%2)cFOhEsxE0t(*6v;IV^lq1sHYAo}W{gHWinmvor>-uZ;mM`V$7Z8yJXjuhY z!9!Pcm~hC~P=Wnwo#%pOZs2-J29Q0AQ2A@5kT zDA+FO!L32U1KpI0{#oNQ(h)*hD>ygXPXZ)e&jY+HU?T{4L|A}sahm~AD@x1~_u z4@EeJEF&y|*U>PvjV~hfp{sDqk-r(NKt50NUl0J-a$xSxwmv89ef9Qfp7n;NOg zqgo(0@Ug39<_h0&;%w{2zz>$VxJ^#Ky?|!!3Ta(KW0VfaPw=l+Y3gk?>InNHa9L#K zG2nmTDSj0rEupb%^(1(61GT%vfS+j@(oT(-6}{igO}@K8k^BbYfz_#r48&UKG{T9B zEu<<{h-<9t;cD?jk3%A%F*+1WhjOLcNmSt7dtnzD3OjeZu8^X4fxF(;gF~nwLjjJ& zQ`ue6%G1oMQy=!kS_AmBE`F_vzd+7EsH&v@-4(06YE0grnPfe+M!4ahiR6*;>T()n zV&HfXZYR?vc`cH~peHfgWMtzhzHz}%cZJ94N~|D?qxO48LYNf4fv^Q|D4}FOXT9t* z@SUR9X8E;BdhmHp)NAak@Y$!$#&c>tOj^Gi)t?OWSbmgXph*%gKbN#0V)?*gZ&+LH z#&LXNpYV5P;`bkzCdp>=(=USG*Pkoe9mseRs2w7tO(CF$CnN~G#TAe;-W%H%3Bfq& znl147UcDu`x6DN~D!mdx!bm34CL0HzYf3hBvwWh_2>&Tj@~31FJX%tNoNzJGc(UDp z%2O}I9cOFvPT8tuH*AmKan!wjSqVKUNX4Eah~NWWgmg;X6H~Bsrhf6WHKMe#Acd8t z>oU;pg7qDR?wxeb{aN1|OX9As9qHLp>rg*;o2hTSVqQO6G{@_)sw2`8PN(v6WH$5? zdh8HEJx=J|EMCn})LxhAy%j58#HOeF06vQz)oHLv9QGoV8Us<5^k0_^8$9!tx$^S3 zaISXqJa+9=-)d^27KbN1ApGhlD2eQ3YB1phP?Hwd?BY^8Rn|!5Eti~a;*PW^Q3~Mk zK4}fp3s!d{q--WCVl^x0hp0JOS_wajV)1*2nmOZ9T+5x%*c*Np2Ie7ZR>$014XGAv z7S|R|xaw=#E6cIRW;3{=uOeoum934758RrYU!6b;PGzO>ze^_b)}Q-_F_PtxvONSJ zV_ElDvTCqS*H+G^Z}>!X!)r2~ek^Y)Oc! z_81nb9J51{Gn#H5I@MfA!nA|7>3V#EUY0Y}x)SYv7r1&eDRD=skvpy`K7;7~{Umhm z7w!zc3TLhsCK-Q(!d}ND0t^)LubKTfQ$Y2w)W<4> zH9v%GZ%P_^mG0cMBC7a$2oL2jAg7o%g!TFuvY!uPRDbU}TTV$_PN`hC77xmYfC(IL z^q{IOWUejUvo@~UPz6Q>)`&NFIs|$jqoO;D{oKt|W?~JGSuQhjt`;muzjGyA+3rb( zNioo^eq=K`*$F;*t++MFGAhADX_vHXkeV2TFWR{L%f9=O<1_)~3ybrtLGZy(Pt3x& zAMui-i@q{$spRR#`XEh_0@E5<;SM5Z1Y4;~WI80ed3qcRPW_369be#u z|LO90c${?qv9_O}J%7*rcr)LacLSzu6_od$pM8KM(nJWOtcjw|G8O_;B*^0z$P6N* z|0snT-`Q;7);qzkl&<-%tylF18Z&>)qb_W*F2IBy~?AWT1k4SvE`-ayLeSBAnBHxr@)%DXC=R zs*Wjqa{Fn*++z*aARHetz1~(Sktk8BS6o2cg2VB`&dA9nt*JfpE@y#kxcNL$VqdHE z4Fg&6pg=SK@5Z(Wav&6+wzu*_s`g%m9{DK#gGIu2rttzE*6WUclV%m{%s!ug2q~1! z#$R^fR2<~L4-q+r>O8F1Rw?ACG#%IeQ6}+)444YGA572_aygNbd4fJKwQ985NE^pI zN=Eu%7EGV0&_4%noWFu?nX7?=O@GWtpM6EtA|($exUmx+>yD^%2Tv^JD>iSc&MU2{*warCahY|m9)vH1_)Y-f z1%?tkas=BNDESn;4D}6!yy$UGOp&x4_V3h5l)YpL-_67Ef{<;VaE?@mw**}uSv1RG zuV*n^p_s$8-0boK*)Wo!^e6mFE?+-?Uq118(=nQENjsR?QNV43;0CQi}kQ7M-~LUP`a z&x-eJed`f@t-iK8ah3x8o)w48spOd1?iAvFd*|V)5&uptP96+N&GO;sbvQBy-xn=C^XXW)s%P)Da=;F`&SZv4k@7KcJE()yfyP_3UEVB(Nt|hA( z8?sKO&vO}F>fVTN8v=t5m2zd(J6hkyNTWBbyG*6ow<9A1^lbG>46V70xs?p>08SO- zcFK>doLCTuK379o(MXA1dk5uuNE#juX;KT{Y-T#^WYTJcH){gj%eT|`nF-0|fJxvx zm9uDqt}L*1$SZf#$JrF?tc`DHTT6B+J9K7pO0`OI-idTd;XRmAC@mzs{(oP~=Fj`c zfA19Qu7x?Y@pFGnwn-PCB4;vO;gh}@c%rSi`yesg(xauj!+*D8J$Op|>BP7%A`naY zE)e^%ZD&Szn@NfBVuGn58@w2jC~>1oo$8yFqp8IfNXpq%Go^4P`(LlIfN+#tm?GYy zdhd~06WtY!clXb+^$6p_j#eHkTqoA~ysa-BNYXlAVo$+RSX_vNRe+32?$!x>lh(WC zuo~=>UQ>HF}?9)vgVCVKPN<&+F7lxF%*f zqB5KGr%0bEW+_R%>MyS;7bLj7xi6vq7fTgU>{0&j@Cu|;$YMTfXYn{)bSR>%x6IJnAhZ*&N?zJG{s zjB09ug;=#T#t7XG0B&9YWI`+|p(hNq z{Un_tw7R>{I@=`vc_vwq#ubh8mfey5Lu1&(OFGf5b$>cFkmpQI_@waLl3?SmlUEjZxNhrnZuVV3pN!!g*=|?KAYYpbBdaIR?QUiSRMkf`RsM z-LjD$uz{E$T&NalGS3s0IX%|Rv^{I`n}C2#Z%IU95C0Qz4BPKq*ARvKQ;{5sY``t> zaekXCEg9<4{;~9}-weF4#Gg~s0DUZ$_GhieOdP=BYEv`S_eUHZF8&VV4BtOd-t#CJ z+8{R5?jkce`i|2f-Zs==Yz{5Jyk_+iWc$7>!W-BYgnypo(KY?fWkC=lWV>ClYO~!E zrs)9^rw6l22(m-fB~zYvX-trN-YI;AUtHS<>)RP3s*FVEljO+p#hu_APcq5W%=kfS z;eN|O7Y1Bq1_iP4!tjFi8M}A9EKRh2d+of9=hlhOt3N~ET}QQK33j?e#;)*jOp8Gg z&BPvH==%)dLOJeSbFjF_iK^Qc;z`FSY|^zw{qiZ<^r|LYu2|=2HjsRBs_zM5Ve=Er zb=h4|oNEf5?*>w|C2mZ!XM#^;?~&vqboi$Y>7)muCyy$n{g3CuCtq&$+?(Zm@Z2&* zKygW4j(C-daZdzDqu*JPczLUc5$Tk^Rv{_&h!Ou4IO^Z`X3AxYz+_>>tk4_%!THvL znN#jR|NGQ7j8)sXq8p->`WaWVA@c2>BACf--peU?|6Mx)r| zS5dY4g>icTOSdJt$|=K$0KEZ$A-(Nk;nFp{PjKT}9Pzt0Dx6fWIJ04yme=_eU@X=I8mMe54{qGZ!dgJdX230`?SF@-Pj+gGIlTQ zeMi7V$?t@>;V4s-L=8KGsSNWXQN!76$}0hPP;0YHE6cwq4{r(9m4#q}xVdcLVSP%2 z%Q4-(`oW5w9eRN64#K_H?gt^;Jz;w0hQ3aflPih+=$cFEc6M^a#5ZQr);yN`cbmlF z{=`m>!QKikFr09*>k%sgFdtaa46nn~R1&m^yNC--$i$PXsFJ)-Nu~U}&M45j+-fOH zUVp0m#y~G#+F!jI!{&L(>?s`D4P-Efiw8<6i&YndqIS!4XBkaTo{eR%9VfE><;@(h z^V>rS5au(Qp>rQTp;${1AZ(+<12Qz-3n=TE?IJ#ew+m;Ci7}7EhD(R?u}h5?`5FGbMBBA#qVwI>tq$C$zN6E zZ1K4j?N0n=(pq1G$^jv-2&<1j!3y24?_Rs4xUj#-n6LkR1XEZi<>;#kseU(fg=oo= z$UD0per8+B(D^TXzr`Pcm$(FAvOv1f(}$8QKpiMjo_W;fyvCONAd7>>I5fzlNc#&` z3)o-%0NwX+bhuuAFXy{=APL`E|4oA{3~wiY#nF2~dUQ%`e{__jW(4pY!`I&@E|X-j zBBbeF>8VVx5*BF^J!rl$pY^ZVZwc%5Y;lKpX-rPh()DSZysrb^pPi|DZ5rnXG2}U; z=7)TiT&Cv5QdSDwsAe?bS7Wk*LoIiAbNd=R&0?y`V^vs==k4!ijye9a=1Xt6r+%~$ zgojDJ?c+5`fcJH5v&HI4(8FVp~E# zQjM%SQ|+34BE^#_@dw0bACWh(XSeC({7*XSe02isv;1%%Ll@{&e%?Pi({l*3yQnC1 zvszrCP;{JrZj=h>L@fSfy zwoayC+FB&WVB{WO0&If+!4mr+z9OA12*CRi7eV3jG^XxW{gPI7$c>8i2TT<0PG`fJ zsH=rje4syl`coe0vWQ4oLX%Don8^P;M1OM@lVL4VRr|Ov>hoT`g?`s+?UX8}-0;(` zBre~x7lgj#h_~O1v_Ku`?wW{HARXB?z*(Wvx7gT_B6z_@7jz;ir)#V-|9)D!-5L+1sbQvfmBW8d+6m8U=^0mkRy;u;kI| zZza!s;(f_29a^^zU&BqXyy z+J8Xy@fwD9^22A~5dN2lT1LRqm;4=+5_yW&ZHV0L_ytkvOJz|_pY+;^McZ8F+~csRDn)n$)+ygd@i)OOClCx z-`-{1BZ165WOc;V02gfHe@`tb4(j+8ZnJ1IskXXH{N}+?@dNM5x`yu0!r|}F+Dc&e zrC(TaioES)!mSXbA-td?cDKq0uJTlC4-&1WxdggmCYC4VC01L)t!D$m*@9i&AUf{F zJE0$XaVCWF*>~hKe{Ng;)h(O(YEvaZG5KxmVlSf#K~lQ-zYItLQ9XKU>RquOjz|3u zK{d(Q_phOPqh-D45FD*CvE0{eMi8-1yg*0y%-j>goiu+xN~c-j*@miOq6(9}@}(k} zeUBpoiN~?p5*12;n`46u@(O22lT>J(8z6Yzi+V|+T=*#W%DP!VeLqBj|K4UL8`xR_ zoI6M`I$KuQnEj@s?Um7ezk(dz-d(Fl*Zn4?Os3K;IPN)i(1c4Qc`$h}a1uRR$Pw%W zJX=bsEJ1B|sk4JpS!-RniI^YJ)Fe1HO?*{pxDYVh?9bbPS~O~R4o$LGFnDPyTgkIU z9W%DF08oYIszQ%^)Pg+l>d3?J^c2YjJHLJ{)_UX&0A#8+oPKtVAlzMGy<<)jo`7#x zA?<&@1|FeG=H*k`wF>W}7@#gSjDp?byz~&($E=TAaE82EX2~9?b^I9-67t~{vrU{) z#NA!Uxud|%P_w}{ar9YYO_O><%G`=`lF210>^Nw4BCEOlDe)+|2wfr2*u{Wph}>If z83ij5uupWgNZZ~rjAvhX&$!=bku!aumMBspulg>`E@CskKeMXV5)DVvgkHYG(*Kt_ zlsv6|hjy@stNa&B84DH(r0|+ zgIvIYM6}|N{1qf1w)rh$^Ps2URCThHa%d=EW% z<<_pk|I{(v5BXubQ2OayPJLyh0#4(|Tx&?kf}^ZnR`;~i;WgzXsrlRaR)+$Av-5X! zc-FX3{{*}gx>y|&Wt#B6vIQ{1np+o%XkE&(=mX;hzVVlW3Ruo>&iB|!uTvpr-(1DjzS_>DBoU$qTZx>TVP_6xl9$Y7nv0;TG zQQ|aE(RpiZWY zn`zgy{M(G+9z863|9OY*jj)YGl~c$N`({AC$qv-wJb+sIr-{{spB~1FGVD3wrj`({ z4H^+u-Wo0F0aEnyR;2wGxqnCrxkyG%kyur0({|9BD>Wkhh=-*oF<;yeTWkNUy)g^( zjS0t%D&(>uIQGbN{LWn~0T0@V@Sk@X>1?6EYd`&4+K;@$hNLWX$5hg?`?0z@$&Z#mj}WM*UbewAE4`;G|kwCZ#v>4ZQ+AVxwSh~Pe3rhin` zKBc4W@l7S+pNc%xJFD@Z^^pYJCT>a?t7hV+u(E8G93Jq7WBjY#Tm$!5L^ZhPmBk1W z*CtGw-_|E~>4wLh!d8gcHY}Z4Tvmcy3wk5~sbYP<>_EIkT;?sJJb6mH=K`b*G*&~= zyY~sO6efB%*J=t0uZ3D!2$dr}t%gJC$VA`WQ0XczC`?IO+2OuC3~gm<_&vu#;O7p) z&q-wh+R(a8T)BtSuGwtv+H9^|Qy1{7`C5NV%5|$ub)+zt!={vNJD&{-o*jS>}00( z<3qCV<_0R*2sT2DP8=1*zvQtw=Pzc&5xpmcW*CN11gU0+v=}+a3q(0eoBli%5{Xkt z3U7SC%$f_C18vaKy_EYsn@lB%qswbeUOBtd;s`9nz%~>aXMyElwo_Z3n{d(J7eDo1 zrFiKgmq;9mcY(U*qu3r_M(!=B;6Ct;BwxrxLrGU5)XfQ30(FWfMq``Q$Q7-YBR0izJ6?!|IjQW}sJgiBVP&`tQHVd+@02C9QxXE}hngqc94S_A}n*cK)<_1y6+`*P{ z0Dq3i5@ZAjxQo1&o637GYmU;fA;{KP)pQ(}F*0^}>@Ea;z+Hk?U>`VPfA9$Q)0VDD z$jHe(*Wjm&M**x&eYgXai0;T%?xQhE#SJnZpGzaihBecfAnNpcQuRCMVeW045btvH z(Lz%x@l^W46bZ?Ik2Tpnq4~gx$-0N^-;cm~CwI=$EmpA~*&LC_Iw+Uegj2>f>k?Ka zi!lN|Ww~So)Ksi)+@iTtSTbiTw=N+FXan}mi4-QW)x>~Ji(`xWP3&@VdsXWvxVy0> z^@#-u`aMOBQ{m!wr&o4J(J3dj-uuaAyv_=+8ZYNB_23mLFHBTsmhG>yf)I6}U-!le zXOK;n)znVfsKs-U_0arRewDA>)S87!pxns4Qa7V(b_5HbKZ05-_q4A`#_+M0Wj5Zc%Cr9tIyGp={k;ABCbL4lW&S;j2_c%v_B;@>K zopO{=yNbwG@XV^9C`N2Of`(YO5if_m#od-;AF{-o znKF-qm+yxJTY@+X|(MwXpT2bHZR`J+^X=jQ-0sX_8ZO@30t=S7ezo=fP71Y({TVq>Z`$ByG zATZQK!h*a8O=z}_cBN4nxCqi65Wm2GliL;92c$xv{mUEbwl02Ax^*v(vOyiu5-s+t zvQm-g7G$k4R`_EX(PW>wiM)@?|MxSKV0NO8P!+*F=9w{HA(^K)b8J^Nx*F)rZ|Irp zY8wjIs5&swZvqAcGa+=yH)-h?-Bw~6Ld^vu?Spgc5(w0`&iu9X+yjz@P&eSkEpvim=qpkwy4=md%xL>480j_L+tN91RK;K8kSC{^l5)K=Q%c3JJ0{f`s9yKf(N>)B z%HJd4@9J@WxMXt;rQg$PoSPM@v8ZoyAS(m($>r@o+8a;)ANJbWegO zR!-Z>Bhv^QrhP?x_Cg~v?kjAwO#;W0gIQkg;1qr6ZLQ^2>R4l~!zSF+Ui@+U@xpW` z{9<({w|#1P`HGljJFOkQA9Nz2Xj^rjKA~Ye&L-2GCoLSn?pbYQ+7+*Zp6(uJ6Uq-v zP3K=|F5iKf%#I0OFo!8>o74_X`6J|c-tq-;F4jG)Wyivy(+kyG)Wcmtmss4}qR#BAW{;qy z#cPxtbD$H8?A%4AN7P1uAqTY~{Siq{-!f0^Gd=@OpnPh(F6m-YYCS9SqEZaa8I~;; zWZHHAt>L1_5PeZKPn>C|^KsWO0Lm`{hJ^?{JS0ln5E7;)_{8jI=V>OcY4TqgZ0S&F z2Y+A(gA5^nZ8!I%K@+&gg|yWf5w{Az`uj};;ip10QA8B|_c{dOz+tpZTd*#JSz@cb zrgby=9FHJO{m)C?&pn*hS03DRt3mwv8{tp=f6YJvdd3w29y2o=QO&go1m(f3S@q^I z9jRpDn=cQBdLWaG4GJ0!`+q-s&zCDg8d}XBA`ByL=YFTUhd|?LN^q#oLf-lN^$>oD z5dHCl-Ir4jjd84Ba=ye_Nv*Y-I_xD^TV4Fe&!JK~I25D~P7{1fz&qzNy1*y_gDH%N zaFImgVMK%x5s)L8BhImGm?Od*5k^E95n)7x#RXVMf%PU>G=g _scenarios = { 'animated_color_square': AnimatedColorSquareScenario(window), + 'external_texture': ExternalTextureScenario(window), 'text_platform_view': PlatformViewScenario(window, 'Hello from Scenarios (Platform View)'), }; @@ -27,19 +29,21 @@ void main() { ..onDrawFrame = _onDrawFrame ..onMetricsChanged = _onMetricsChanged ..scheduleFrame(); - final ByteData data = ByteData(1); - data.setUint8(0, 1); + final ByteData data = ByteData(2); + data.setUint8(0, 128); + data.setUint8(1, 2); window.sendPlatformMessage('scenario_status', data, null); } Future _handlePlatformMessage( String name, ByteData data, PlatformMessageResponseCallback callback) async { - print(name); - print(utf8.decode(data.buffer.asUint8List())); + print('_handlePlatformMessage $name data ${utf8.decode(data.buffer.asUint8List())}'); if (name == 'set_scenario' && data != null) { final String scenarioName = utf8.decode(data.buffer.asUint8List()); + print('_handlePlatformMessage2 $name data $scenarioName'); final Scenario candidateScenario = _scenarios[scenarioName]; if (candidateScenario != null) { + print('_handlePlatformMessage3 $name data $scenarioName'); _currentScenario = candidateScenario; window.scheduleFrame(); } @@ -52,6 +56,9 @@ Future _handlePlatformMessage( final String timelineData = await _getTimelineData(); callback(Uint8List.fromList(utf8.encode(timelineData)).buffer.asByteData()); } + else if(name == 'update_data'){ + _currentScenario.onUpdateData(data); + } } Future _getTimelineData() async { diff --git a/testing/scenario_app/lib/src/external_texture_view.dart b/testing/scenario_app/lib/src/external_texture_view.dart new file mode 100644 index 0000000000000..3f9b3bc5f7ba3 --- /dev/null +++ b/testing/scenario_app/lib/src/external_texture_view.dart @@ -0,0 +1,53 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; +import 'dart:ui'; +import 'scenario.dart'; + + +class ExternalTextureScenario extends Scenario { + /// Creates the PlatformView scenario. + /// + /// The [window] parameter must not be null. + ExternalTextureScenario(Window window) + : assert(window != null), + super(window) { + final ByteData data = ByteData(1); + data.setUint8(0, 1); + window.sendPlatformMessage( + 'create_external_texture', + data, + null, + ); + } + + int _textureId; + + @override + void onBeginFrame(Duration duration) { + + print('begin frame'); + + final SceneBuilder builder = SceneBuilder(); + + builder.pushOffset(0, 0); + + if (_textureId != null) { + builder.addTexture(_textureId, offset: const Offset(0, 0), width: 480, height: 480); + } + + + final Scene scene = builder.build(); + window.render(scene); + scene.dispose(); + } + + @override + void onUpdateData(ByteData data) { + super.onUpdateData(data); + if (data != null) { + String string = utf8.decode(data.buffer.asUint8List()); + _textureId = int.parse(string); + } + } +} diff --git a/testing/scenario_app/lib/src/scenario.dart b/testing/scenario_app/lib/src/scenario.dart index be474016a3162..572f2e933caa0 100644 --- a/testing/scenario_app/lib/src/scenario.dart +++ b/testing/scenario_app/lib/src/scenario.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:typed_data'; import 'dart:ui'; /// A scenario to run for testing. @@ -27,4 +28,9 @@ abstract class Scenario { /// /// See [Window.onMetricsChanged]. void onMetricsChanged() {} + + /// Called by the program when Scenario's data updated + /// + /// + void onUpdateData(ByteData data){} } From 618094ae0f6c44fcf11d41eb995163f7c55506ea Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Wed, 11 Sep 2019 20:18:04 +0800 Subject: [PATCH 04/19] Code format --- .../ios/Scenarios/Scenarios/AppDelegate.m | 52 +-- .../Scenarios/Scenarios/TestExternalTexture.h | 4 +- .../Scenarios/Scenarios/TestExternalTexture.m | 300 ++++++++---------- .../ScenariosUITests/ExternalTextureUITests.m | 104 +++--- 4 files changed, 211 insertions(+), 249 deletions(-) diff --git a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m index bfec7bd97ad30..e72ada80b1d7c 100644 --- a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m +++ b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m @@ -1,6 +1,6 @@ #include "AppDelegate.h" -#import "TextPlatformView.h" #import "TestExternalTexture.h" +#import "TextPlatformView.h" @interface NoStatusBarFlutterViewController : FlutterViewController @end @@ -11,8 +11,8 @@ - (BOOL)prefersStatusBarHidden { } @end -@interface AppDelegate() -@property(nonatomic,strong) TestExternalTexture * externalTexture; +@interface AppDelegate () +@property(nonatomic, strong) TestExternalTexture* externalTexture; @end @implementation AppDelegate @@ -41,36 +41,36 @@ - (BOOL)application:(UIApplication*)application [flutterViewController.engine registrarForPlugin:@"scenarios/TextPlatformViewPlugin"]; [registrar registerViewFactory:textPlatformViewFactory withId:@"scenarios/textPlatformView"]; self.window.rootViewController = flutterViewController; - } - else if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--external-texture"]){ + } else if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--external-texture"]) { FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"ExternalTextureTest" project:nil]; [engine runWithEntrypoint:nil]; - - FlutterViewController* flutterViewController = [[NoStatusBarFlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil]; - + + FlutterViewController* flutterViewController = + [[NoStatusBarFlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil]; + [engine.binaryMessenger - setMessageHandlerOnChannel:@"scenario_status" - binaryMessageHandler:^(NSData* _Nullable message, FlutterBinaryReply _Nonnull reply) { - [engine.binaryMessenger - sendOnChannel:@"set_scenario" - message:[@"external_texture" dataUsingEncoding:NSUTF8StringEncoding]]; - }]; + setMessageHandlerOnChannel:@"scenario_status" + binaryMessageHandler:^(NSData* _Nullable message, FlutterBinaryReply _Nonnull reply) { + [engine.binaryMessenger + sendOnChannel:@"set_scenario" + message:[@"external_texture" dataUsingEncoding:NSUTF8StringEncoding]]; + }]; [engine.binaryMessenger - setMessageHandlerOnChannel:@"create_external_texture" - binaryMessageHandler:^(NSData* _Nullable message, FlutterBinaryReply _Nonnull reply) { - NSObject* registrar = - [flutterViewController.engine registrarForPlugin:@"scenarios/ExternalTexturePlugin"]; - self.externalTexture = [[TestExternalTexture alloc] initWithWithRegistrar:registrar]; - int64_t textureID = [registrar.textures registerShareTexture:self.externalTexture]; - [self.externalTexture startWithID:textureID]; - NSData * data = [NSData dataWithBytes:&textureID length:sizeof(textureID)]; - reply(data); - }]; + setMessageHandlerOnChannel:@"create_external_texture" + binaryMessageHandler:^(NSData* _Nullable message, FlutterBinaryReply _Nonnull reply) { + NSObject* registrar = [flutterViewController.engine + registrarForPlugin:@"scenarios/ExternalTexturePlugin"]; + self.externalTexture = + [[TestExternalTexture alloc] initWithWithRegistrar:registrar]; + int64_t textureID = [registrar.textures registerShareTexture:self.externalTexture]; + [self.externalTexture startWithID:textureID]; + NSData* data = [NSData dataWithBytes:&textureID length:sizeof(textureID)]; + reply(data); + }]; self.window.rootViewController = flutterViewController; - } - else { + } else { self.window.rootViewController = [[UIViewController alloc] init]; } [self.window makeKeyAndVisible]; diff --git a/testing/scenario_app/ios/Scenarios/Scenarios/TestExternalTexture.h b/testing/scenario_app/ios/Scenarios/Scenarios/TestExternalTexture.h index 1ae5730ee09c8..bb27bff77cc9b 100644 --- a/testing/scenario_app/ios/Scenarios/Scenarios/TestExternalTexture.h +++ b/testing/scenario_app/ios/Scenarios/Scenarios/TestExternalTexture.h @@ -6,12 +6,12 @@ // Copyright © 2019 flutter. All rights reserved. // -#import #import +#import NS_ASSUME_NONNULL_BEGIN -@interface TestExternalTexture : NSObject +@interface TestExternalTexture : NSObject - (instancetype)initWithWithRegistrar:(NSObject*)registrar; - (void)startWithID:(int64_t)textureID; @end diff --git a/testing/scenario_app/ios/Scenarios/Scenarios/TestExternalTexture.m b/testing/scenario_app/ios/Scenarios/Scenarios/TestExternalTexture.m index 907ba83b7f0ff..5e61ffed23135 100644 --- a/testing/scenario_app/ios/Scenarios/Scenarios/TestExternalTexture.m +++ b/testing/scenario_app/ios/Scenarios/Scenarios/TestExternalTexture.m @@ -10,143 +10,113 @@ #import #import -#define SHADER_STRING(text) @#text - -NSString *const kIFGLGeneralVertexShaderString = SHADER_STRING -( - attribute vec4 position; - attribute vec4 inputTextureCoordinate; - - varying vec2 textureCoordinate; - - void main() - { - gl_Position = position; - textureCoordinate = inputTextureCoordinate.xy; - } - ); - -NSString *const kIFGLGeneralFragmentShaderString = SHADER_STRING -( - varying highp vec2 textureCoordinate; - - uniform sampler2D inputImageTexture; - - void main() - { - gl_FragColor = texture2D(inputImageTexture,textureCoordinate); - } - ); +#define SHADER_STRING(text) @ #text + +NSString* const kIFGLGeneralVertexShaderString = + SHADER_STRING(attribute vec4 position; attribute vec4 inputTextureCoordinate; + + varying vec2 textureCoordinate; + + void main() { + gl_Position = position; + textureCoordinate = inputTextureCoordinate.xy; + }); + +NSString* const kIFGLGeneralFragmentShaderString = + SHADER_STRING(varying highp vec2 textureCoordinate; + + uniform sampler2D inputImageTexture; + + void main() { gl_FragColor = texture2D(inputImageTexture, textureCoordinate); }); const GLfloat vetex1[] = { - -1.0f, -1.0f, - -1.0f, 0.0f, - 0.0f, -1.0f, - 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, }; const GLfloat vetex2[] = { - -1.0f, 0.0f, - -1.0f, 1.0f, - 0.0f, 0.0f, - 0.0f, 1.0f, + -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; const GLfloat vetex3[] = { - 0.0f, 0.0f, - 0.0f, 1.0f, - 1.0f, 0.0f, - 1.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, }; const GLfloat vetex4[] = { - 0.0f, -1.0f, - 0.0f, 0.0f, - 1.0f, -1.0f, - 1.0f, 0.0f, + 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, }; const GLfloat vetex[] = { - -1.0f, -1.0f, - -1.0f, 1.0f, - 1.0f, -1.0f, - 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, }; const GLfloat textureGeneralTexCoord[] = { - 0.0f, 0.0f, - 0.0f, 1.0f, - 1.0f, 0.0f, - 1.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, }; -@interface TestExternalTexture() -@property(nonatomic,assign) GLuint program; -@property(nonatomic,assign) GLuint vertShader; -@property(nonatomic,assign) GLuint fragShader; -@property(nonatomic,strong) NSMutableArray *attributes; -@property(nonatomic,strong) NSMutableDictionary *uniforms; -@property(nonatomic,assign) GLuint positionAttributeLocation; -@property(nonatomic,assign) GLuint texCoordAttributeLocation; +@interface TestExternalTexture () +@property(nonatomic, assign) GLuint program; +@property(nonatomic, assign) GLuint vertShader; +@property(nonatomic, assign) GLuint fragShader; +@property(nonatomic, strong) NSMutableArray* attributes; +@property(nonatomic, strong) NSMutableDictionary* uniforms; +@property(nonatomic, assign) GLuint positionAttributeLocation; +@property(nonatomic, assign) GLuint texCoordAttributeLocation; -@property(nonatomic,strong) dispatch_queue_t displayQueue; -@property(nonatomic,strong) EAGLContext *glContext; +@property(nonatomic, strong) dispatch_queue_t displayQueue; +@property(nonatomic, strong) EAGLContext* glContext; -@property(nonatomic,assign) GLuint frameBuffer; -@property(nonatomic,assign) GLuint frameTexture; -@property(nonatomic,assign) GLuint materialTexture; +@property(nonatomic, assign) GLuint frameBuffer; +@property(nonatomic, assign) GLuint frameTexture; +@property(nonatomic, assign) GLuint materialTexture; -@property(strong,nonatomic) NSObject *registry; -@property(strong,nonatomic) NSObject *messenger; +@property(strong, nonatomic) NSObject* registry; +@property(strong, nonatomic) NSObject* messenger; @end @implementation TestExternalTexture -- (instancetype)initWithWithRegistrar:(NSObject*)registrar{ - if ((self = [super init])) - { +- (instancetype)initWithWithRegistrar:(NSObject*)registrar { + if ((self = [super init])) { self.registry = [registrar textures]; self.messenger = [registrar messenger]; - + self.attributes = [[NSMutableArray alloc] init]; self.uniforms = [[NSMutableDictionary alloc] init]; - + self.displayQueue = dispatch_queue_create("external.testqueue", nil); } - + return self; } --(GLuint)copyShareTexture{ - if(self.frameTexture != 0){ +- (GLuint)copyShareTexture { + if (self.frameTexture != 0) { return self.frameTexture; } return 0; } --(void)initProgramm{ +- (void)initProgramm { self.program = glCreateProgram(); - + if (![self compileShader:&_vertShader type:GL_VERTEX_SHADER - string:kIFGLGeneralVertexShaderString]) - { + string:kIFGLGeneralVertexShaderString]) { NSLog(@"FMAVEffect FMAVEffectGLProgram Failed to compile vertex shader"); } - + if (![self compileShader:&_fragShader type:GL_FRAGMENT_SHADER - string:kIFGLGeneralFragmentShaderString]) - { + string:kIFGLGeneralFragmentShaderString]) { NSLog(@"FMAVEffect FMAVEffectGLProgram Failed to compile fragment shader"); } - + glAttachShader(self.program, _vertShader); glAttachShader(self.program, _fragShader); - - //called before program link + + // called before program link [self addAttribute:@"position"]; [self addAttribute:@"inputTextureCoordinate"]; - + if (![self link]) { NSLog(@"FMAVEffect FMAVEffectGLProgram link failed"); } @@ -155,65 +125,67 @@ -(void)initProgramm{ self.texCoordAttributeLocation = [self attributeIndex:@"inputTextureCoordinate"]; } -- (void)startWithID:(int64_t)textureID{ - +- (void)startWithID:(int64_t)textureID { int width = [UIScreen mainScreen].bounds.size.width; int height = [UIScreen mainScreen].bounds.size.height; - + dispatch_async(self.displayQueue, ^{ - if(self.glContext == NULL){ - EAGLSharegroup * flutterShareGroup = [self.registry getShareGroup]; - if(flutterShareGroup != NULL){ - self.glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3 sharegroup:flutterShareGroup]; - } - else{ + if (self.glContext == NULL) { + EAGLSharegroup* flutterShareGroup = [self.registry getShareGroup]; + if (flutterShareGroup != NULL) { + self.glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3 + sharegroup:flutterShareGroup]; + } else { self.glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; } } [EAGLContext setCurrentContext:self.glContext]; - if (self.program == 0){ + if (self.program == 0) { [self initProgramm]; } if (self.frameTexture == 0) { self.frameTexture = [self createTextureWithWidth:width andHeight:height]; } - if (self.frameBuffer == 0){ + if (self.frameBuffer == 0) { glGenFramebuffers(1, &self->_frameBuffer); glBindFramebuffer(GL_FRAMEBUFFER, self.frameBuffer); } - if(self.materialTexture == 0){ + if (self.materialTexture == 0) { glGenTextures(1, &self->_materialTexture); - UIImage * materialImage = [UIImage imageNamed:@"flutter.png"]; - if(materialImage != NULL){ - [self convertCGImage:materialImage.CGImage toTexture:self.materialTexture inSize:materialImage.size]; + UIImage* materialImage = [UIImage imageNamed:@"flutter.png"]; + if (materialImage != NULL) { + [self convertCGImage:materialImage.CGImage + toTexture:self.materialTexture + inSize:materialImage.size]; } } - - + glViewport(0, 0, (int)width, (int)height); - + glBindFramebuffer(GL_FRAMEBUFFER, self.frameBuffer); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self.frameTexture, 0); - + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self.frameTexture, + 0); + [self useProgramm]; - - [self renderTexture:self.materialTexture withVertex:(GLvoid *)vetex]; - + + [self renderTexture:self.materialTexture withVertex:(GLvoid*)vetex]; + glBindFramebuffer(GL_FRAMEBUFFER, 0); [self.registry textureFrameAvailable:textureID]; }); } -- (void)convertCGImage:(CGImageRef)image toTexture:(GLuint)textureID inSize:(CGSize)size -{ +- (void)convertCGImage:(CGImageRef)image toTexture:(GLuint)textureID inSize:(CGSize)size { CGImageRef cgImageRef = image; GLuint width = size.width; GLuint height = size.height; CGRect rect = CGRectMake(0, 0, width, height); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - void *imageData = malloc(width * height * 4); - CGContextRef context = CGBitmapContextCreate(imageData, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); + void* imageData = malloc(width * height * 4); + CGContextRef context = + CGBitmapContextCreate(imageData, width, height, 8, width * 4, colorSpace, + kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); if (context == NULL) { return; } @@ -223,38 +195,37 @@ - (void)convertCGImage:(CGImageRef)image toTexture:(GLuint)textureID inSize:(CGS CGContextClearRect(context, rect); CGContextDrawImage(context, rect, cgImageRef); glEnable(GL_TEXTURE_2D); - + glBindTexture(GL_TEXTURE_2D, textureID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData); glBindTexture(GL_TEXTURE_2D, 0); CGContextRelease(context); free(imageData); } --(void)renderTexture:(GLuint)srcTexture withVertex:(GLvoid *)vertex{ +- (void)renderTexture:(GLuint)srcTexture withVertex:(GLvoid*)vertex { glVertexAttribPointer(self.positionAttributeLocation, 2, GL_FLOAT, 0, 0, vertex); glEnableVertexAttribArray(self.positionAttributeLocation); - + glVertexAttribPointer(self.texCoordAttributeLocation, 2, GL_FLOAT, 0, 0, textureGeneralTexCoord); glEnableVertexAttribArray(self.texCoordAttributeLocation); - + glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, srcTexture); glUniform1i([self uniformIndex:@"inputImageTexture"], 0); - + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - + glFlush(); glBindTexture(GL_TEXTURE_2D, 0); } -- (GLuint)createTextureWithWidth:(size_t)width andHeight:(size_t)height -{ +- (GLuint)createTextureWithWidth:(size_t)width andHeight:(size_t)height { GLuint textureID = -1; glActiveTexture(GL_TEXTURE1); glGenTextures(1, &textureID); @@ -269,69 +240,54 @@ - (GLuint)createTextureWithWidth:(size_t)width andHeight:(size_t)height } #pragma mark OPENGL -- (BOOL)compileShader:(GLuint *)shader - type:(GLenum)type - string:(NSString *)shaderString -{ +- (BOOL)compileShader:(GLuint*)shader type:(GLenum)type string:(NSString*)shaderString { GLint status; - const GLchar *source; - - source = - (GLchar *)[shaderString UTF8String]; - if (!source) - { + const GLchar* source; + + source = (GLchar*)[shaderString UTF8String]; + if (!source) { NSLog(@"FMAVEffect FMAVEffectGLProgram Failed to load shader source"); return NO; } - + *shader = glCreateShader(type); glShaderSource(*shader, 1, &source, NULL); glCompileShader(*shader); - + glGetShaderiv(*shader, GL_COMPILE_STATUS, &status); - - if (status != GL_TRUE) - { + + if (status != GL_TRUE) { GLint logLength; glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength); - if (logLength > 0) - { - GLchar *log = (GLchar *)malloc(logLength); + if (logLength > 0) { + GLchar* log = (GLchar*)malloc(logLength); glGetShaderInfoLog(*shader, logLength, &logLength, log); - if (shader == &_vertShader) - { + if (shader == &_vertShader) { NSLog(@"FMAVEffect FMAVEffectGLProgram compile vertext shader error: %s", log); - } - else - { + } else { NSLog(@"FMAVEffect FMAVEffectGLProgram compile fragment shader error: %s", log); } - + free(log); } } - + return status == GL_TRUE; } -- (void)addAttribute:(NSString *)attributeName -{ - if (![self.attributes containsObject:attributeName]) - { +- (void)addAttribute:(NSString*)attributeName { + if (![self.attributes containsObject:attributeName]) { [self.attributes addObject:attributeName]; - glBindAttribLocation(self.program, - (GLuint)[self.attributes indexOfObject:attributeName], + glBindAttribLocation(self.program, (GLuint)[self.attributes indexOfObject:attributeName], [attributeName UTF8String]); } } -- (GLuint)attributeIndex:(NSString *)attributeName -{ +- (GLuint)attributeIndex:(NSString*)attributeName { return (GLuint)[self.attributes indexOfObject:attributeName]; } -- (GLuint)uniformIndex:(NSString *)uniformName -{ +- (GLuint)uniformIndex:(NSString*)uniformName { if ([self.uniforms.allKeys containsObject:uniformName]) { return (GLuint)[self.uniforms[uniformName] unsignedIntValue]; } @@ -340,45 +296,39 @@ - (GLuint)uniformIndex:(NSString *)uniformName return loc; } -- (BOOL)link -{ +- (BOOL)link { GLint status; - + glLinkProgram(self.program); - + glGetProgramiv(self.program, GL_LINK_STATUS, &status); if (status == GL_FALSE) return NO; - - if (self.vertShader) - { + + if (self.vertShader) { glDeleteShader(self.vertShader); self.vertShader = 0; } - if (self.fragShader) - { + if (self.fragShader) { glDeleteShader(self.fragShader); self.fragShader = 0; } return YES; } -- (void)useProgramm -{ +- (void)useProgramm { glUseProgram(self.program); } -- (void)dealloc -{ - if (_vertShader!=0) +- (void)dealloc { + if (_vertShader != 0) glDeleteShader(_vertShader); - - if (_fragShader!=0) + + if (_fragShader != 0) glDeleteShader(_fragShader); - - if (_program!=0) + + if (_program != 0) glDeleteProgram(_program); - } @end diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/ExternalTextureUITests.m b/testing/scenario_app/ios/Scenarios/ScenariosUITests/ExternalTextureUITests.m index 4a10e0ae6b6f7..6f79e4bd397bb 100644 --- a/testing/scenario_app/ios/Scenarios/ScenariosUITests/ExternalTextureUITests.m +++ b/testing/scenario_app/ios/Scenarios/ScenariosUITests/ExternalTextureUITests.m @@ -6,8 +6,8 @@ // Copyright © 2019 flutter. All rights reserved. // -#import #import +#import #include @interface ExternalTextureUITests : XCTestCase @@ -17,21 +17,25 @@ @interface ExternalTextureUITests : XCTestCase @implementation ExternalTextureUITests - (void)setUp { - // Put setup code here. This method is called before the invocation of each test method in the class. - + // Put setup code here. This method is called before the invocation of each test method in the + // class. + // In UI tests it is usually best to stop immediately when a failure occurs. self.continueAfterFailure = NO; - // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. + // UI tests must launch the application that they test. Doing this in setup will make sure it + // happens for each test method. self.application = [[XCUIApplication alloc] init]; self.application.launchArguments = @[ @"--external-texture" ]; [self.application launch]; - // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + // In UI tests it’s important to set the initial state - such as interface orientation - required + // for your tests before they run. The setUp method is a good place to do this. } - (void)tearDown { - // Put teardown code here. This method is called after the invocation of each test method in the class. + // Put teardown code here. This method is called after the invocation of each test method in the + // class. } - (void)testExample { @@ -39,87 +43,95 @@ - (void)testExample { // Use XCTAssert and related functions to verify your tests produce the correct results. NSBundle* bundle = [NSBundle bundleForClass:[self class]]; NSString* goldenName = - [NSString stringWithFormat:@"golden_external_texture_%@", [self platformName]]; + [NSString stringWithFormat:@"golden_external_texture_%@", [self platformName]]; NSString* path = [bundle pathForResource:goldenName ofType:@"png"]; UIImage* golden = [[UIImage alloc] initWithContentsOfFile:path]; - + XCUIScreenshot* screenshot = [[XCUIScreen mainScreen] screenshot]; XCTAttachment* attachment = [XCTAttachment attachmentWithScreenshot:screenshot]; attachment.lifetime = XCTAttachmentLifetimeKeepAlways; [self addAttachment:attachment]; - + if (golden) { XCTAttachment* goldenAttachment = [XCTAttachment attachmentWithImage:golden]; goldenAttachment.lifetime = XCTAttachmentLifetimeKeepAlways; [self addAttachment:goldenAttachment]; } else { - - NSString *folder = [NSString stringWithFormat:@"%@/",NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]]; - NSFileManager *fileManager = [NSFileManager defaultManager]; + NSString* folder = + [NSString stringWithFormat:@"%@/", NSSearchPathForDirectoriesInDomains( + NSCachesDirectory, NSUserDomainMask, YES)[0]]; + NSFileManager* fileManager = [NSFileManager defaultManager]; if (![fileManager fileExistsAtPath:folder]) { - NSError *error = nil; - [fileManager createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:nil error:&error]; + NSError* error = nil; + [fileManager createDirectoryAtPath:folder + withIntermediateDirectories:YES + attributes:nil + error:&error]; if (error) { } } - NSString *imgPath = [[folder stringByAppendingPathComponent:goldenName] stringByAppendingPathExtension:@"png"]; + NSString* imgPath = + [[folder stringByAppendingPathComponent:goldenName] stringByAppendingPathExtension:@"png"]; [self writeImageToFile:screenshot.image atPath:imgPath]; - + XCTFail(@"This test will fail - no golden named %@ found. Follow the steps in the " @"README to add a new golden.", goldenName); } - + XCTAssertTrue([self compareImage:golden toOther:screenshot.image]); } - (NSString*)platformName { NSString* simulatorName = - [[NSProcessInfo processInfo].environment objectForKey:@"SIMULATOR_DEVICE_NAME"]; + [[NSProcessInfo processInfo].environment objectForKey:@"SIMULATOR_DEVICE_NAME"]; if (simulatorName) { return [NSString stringWithFormat:@"%@_simulator", simulatorName]; } - + size_t size; sysctlbyname("hw.model", NULL, &size, NULL, 0); char* answer = malloc(size); sysctlbyname("hw.model", answer, &size, NULL, 0); - + NSString* results = [NSString stringWithCString:answer encoding:NSUTF8StringEncoding]; free(answer); return results; } -- (BOOL)writeImageToFile:(UIImage *)image atPath:(NSString *)aPath { +- (BOOL)writeImageToFile:(UIImage*)image atPath:(NSString*)aPath { if ((image == nil) || (aPath == nil) || ([aPath isEqualToString:@""])) { return NO; } - - NSFileManager * fileManager = [NSFileManager defaultManager]; + + NSFileManager* fileManager = [NSFileManager defaultManager]; BOOL isDirectory; - NSString * directory = [aPath stringByDeletingLastPathComponent]; + NSString* directory = [aPath stringByDeletingLastPathComponent]; BOOL exist = [fileManager fileExistsAtPath:directory isDirectory:&isDirectory]; if (!exist) { - [fileManager createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:nil]; + [fileManager createDirectoryAtPath:directory + withIntermediateDirectories:YES + attributes:nil + error:nil]; } - + @try { - NSData *imageData = nil; - NSString *ext = [aPath pathExtension]; + NSData* imageData = nil; + NSString* ext = [aPath pathExtension]; if ([ext isEqualToString:@"png"]) { imageData = UIImagePNGRepresentation(image); } else { imageData = UIImageJPEGRepresentation(image, 1.0); } - + if ((imageData == nil) || ([imageData length] <= 0)) { return NO; } - + BOOL success = [imageData writeToFile:aPath atomically:YES]; return success; - } @catch (NSException *e) { - //NSLog(@"create thumbnail exception."); + } @catch (NSException* e) { + // NSLog(@"create thumbnail exception."); } return NO; } @@ -127,12 +139,12 @@ - (BOOL)writeImageToFile:(UIImage *)image atPath:(NSString *)aPath { - (BOOL)compareImage:(UIImage*)a toOther:(UIImage*)b { CGImageRef imageRefA = [a CGImage]; CGImageRef imageRefB = [b CGImage]; - + NSUInteger widthA = CGImageGetWidth(imageRefA); NSUInteger heightA = CGImageGetHeight(imageRefA); NSUInteger widthB = CGImageGetWidth(imageRefB); NSUInteger heightB = CGImageGetHeight(imageRefB); - + if (widthA != widthB || heightA != heightB) { return NO; } @@ -140,34 +152,34 @@ - (BOOL)compareImage:(UIImage*)a toOther:(UIImage*)b { NSUInteger size = widthA * heightA * bytesPerPixel; NSMutableData* rawA = [NSMutableData dataWithLength:size]; NSMutableData* rawB = [NSMutableData dataWithLength:size]; - + if (!rawA || !rawB) { return NO; } - + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - + NSUInteger bytesPerRow = bytesPerPixel * widthA; NSUInteger bitsPerComponent = 8; CGContextRef contextA = - CGBitmapContextCreate(rawA.mutableBytes, widthA, heightA, bitsPerComponent, bytesPerRow, - colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); - + CGBitmapContextCreate(rawA.mutableBytes, widthA, heightA, bitsPerComponent, bytesPerRow, + colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); + CGContextDrawImage(contextA, CGRectMake(0, 0, widthA, heightA), imageRefA); CGContextRelease(contextA); - + CGContextRef contextB = - CGBitmapContextCreate(rawB.mutableBytes, widthA, heightA, bitsPerComponent, bytesPerRow, - colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); + CGBitmapContextCreate(rawB.mutableBytes, widthA, heightA, bitsPerComponent, bytesPerRow, + colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); CGColorSpaceRelease(colorSpace); - + CGContextDrawImage(contextB, CGRectMake(0, 0, widthA, heightA), imageRefB); CGContextRelease(contextB); - + if (memcmp(rawA.bytes, rawB.bytes, rawA.length)) { return NO; } - + return YES; } From 0052f4722469f49310226b1b35074fd7c77fe567 Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Thu, 12 Sep 2019 10:12:12 +0800 Subject: [PATCH 05/19] iOS update texture id --- testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m index e72ada80b1d7c..3e0b5c1731d7e 100644 --- a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m +++ b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m @@ -65,8 +65,9 @@ - (BOOL)application:(UIApplication*)application [[TestExternalTexture alloc] initWithWithRegistrar:registrar]; int64_t textureID = [registrar.textures registerShareTexture:self.externalTexture]; [self.externalTexture startWithID:textureID]; - NSData* data = [NSData dataWithBytes:&textureID length:sizeof(textureID)]; - reply(data); + [engine.binaryMessenger + sendOnChannel:@"update_data" + message:[[NSString stringWithFormat:@"%lld",textureID] dataUsingEncoding:NSUTF8StringEncoding]]; }]; self.window.rootViewController = flutterViewController; From 6dfa3035d369bfc049210e12f507c3333ad04e07 Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Thu, 12 Sep 2019 10:13:37 +0800 Subject: [PATCH 06/19] Code format --- testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m index 3e0b5c1731d7e..17be85e44e20a 100644 --- a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m +++ b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m @@ -66,8 +66,9 @@ - (BOOL)application:(UIApplication*)application int64_t textureID = [registrar.textures registerShareTexture:self.externalTexture]; [self.externalTexture startWithID:textureID]; [engine.binaryMessenger - sendOnChannel:@"update_data" - message:[[NSString stringWithFormat:@"%lld",textureID] dataUsingEncoding:NSUTF8StringEncoding]]; + sendOnChannel:@"update_data" + message:[[NSString stringWithFormat:@"%lld", textureID] + dataUsingEncoding:NSUTF8StringEncoding]]; }]; self.window.rootViewController = flutterViewController; From 7e297d8ce5432ce84c438a93bf6f23c6a2345d08 Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Thu, 12 Sep 2019 12:38:04 +0800 Subject: [PATCH 07/19] Code format --- shell/platform/darwin/ios/platform_view_ios.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/platform_view_ios.h b/shell/platform/darwin/ios/platform_view_ios.h index 7e14d51ec0738..2a232eb051bf0 100644 --- a/shell/platform/darwin/ios/platform_view_ios.h +++ b/shell/platform/darwin/ios/platform_view_ios.h @@ -38,7 +38,7 @@ class PlatformViewIOS final : public PlatformView { void RegisterExternalTexture(int64_t id, NSObject* texture); void RegisterExternalShareTexture(int64_t id, NSObject* texture); - + // |PlatformView| PointerDataDispatcherMaker GetDispatcherMaker() override; From f964c8a266984b570379c61dd0848ae0c05553e1 Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Mon, 16 Sep 2019 14:46:31 +0800 Subject: [PATCH 08/19] Code format --- testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m index c56ac04aa309f..9ef836387b867 100644 --- a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m +++ b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m @@ -3,9 +3,9 @@ // found in the LICENSE file. #include "AppDelegate.h" -#import "TestExternalTexture.h" #import "FlutterEngine+ScenariosTest.h" #import "ScreenBeforeFlutter.h" +#import "TestExternalTexture.h" #import "TextPlatformView.h" @interface NoStatusBarFlutterViewController : FlutterViewController From 1a2be7a9897e18baa2d079c1abb7989a49b029ac Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Mon, 16 Sep 2019 21:13:21 +0800 Subject: [PATCH 09/19] Codereview --- ...droid_external_texture_gl_share_context.cc | 24 ++++++++----------- .../flutter/embedding/engine/FlutterJNI.java | 4 ++-- .../engine/renderer/FlutterRenderer.java | 11 +++++---- .../android/io/flutter/view/FlutterView.java | 8 +++---- .../io/flutter/view/TextureRegistry.java | 3 ++- .../android/platform_view_android_jni.cc | 3 +++ .../ios_external_texture_gl_share_context.h | 6 ++--- .../dev/flutter/scenarios/MainActivity.java | 2 +- .../Scenarios.xcodeproj/project.pbxproj | 4 ++-- .../ios/Scenarios/Scenarios/AppDelegate.m | 11 ++------- testing/scenario_app/lib/main.dart | 8 +++---- .../lib/src/external_texture_view.dart | 20 +++++++++++++++- 12 files changed, 59 insertions(+), 45 deletions(-) diff --git a/shell/platform/android/android_external_texture_gl_share_context.cc b/shell/platform/android/android_external_texture_gl_share_context.cc index c620b5c2e6429..c1196a68430f4 100644 --- a/shell/platform/android/android_external_texture_gl_share_context.cc +++ b/shell/platform/android/android_external_texture_gl_share_context.cc @@ -9,8 +9,12 @@ #include "flutter/shell/platform/android/platform_view_android_jni.h" #include "third_party/skia/include/gpu/GrBackendSurface.h" -namespace flutter { +#include "flutter/fml/trace_event.h" +namespace flutter { +// This is another solution for Flutter's ExternalTexture. +// The original ExternalTexture uses SurfaceTexture to update the frame data that native video object produces to an OpenGL texture. +// In this scheme, we directly pass an OpenGL texture ID to the ExternalTexture object, and avoid the performance consumption of data writing to SurfaceTexture AndroidExternalTextureShareContext::AndroidExternalTextureShareContext( int64_t id, int64_t shareTextureID) @@ -32,23 +36,15 @@ void AndroidExternalTextureShareContext::Paint(SkCanvas& canvas, textureInfo.fTarget = GL_TEXTURE_2D; transform.setIdentity(); - GrBackendTexture backendTexture(1, 1, GrMipMapped::kNo, textureInfo); + GrBackendTexture backendTexture(bounds.width(), bounds.height(), GrMipMapped::kNo, textureInfo); sk_sp image = SkImage::MakeFromTexture( canvas.getGrContext(), backendTexture, kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr); if (image) { - SkAutoCanvasRestore autoRestore(&canvas, true); - canvas.translate(bounds.x(), bounds.y()); - canvas.scale(bounds.width(), bounds.height()); - if (!transform.isIdentity()) { - SkMatrix transformAroundCenter(transform); - - transformAroundCenter.preTranslate(-0.5, -0.5); - transformAroundCenter.postScale(1, -1); - transformAroundCenter.postTranslate(0.5, 0.5); - canvas.concat(transformAroundCenter); - } - canvas.drawImage(image, 0, 0); + canvas.drawImage(image, bounds.x(), bounds.y()); + } + else{ + FML_LOG(ERROR) << "Create SKIImage Fail !!"; } } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 1387b62b31b5d..f2e5f656bfb11 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -557,10 +557,10 @@ public void registerShareTexture(long texIndex, long shareTextureId) { @UiThread public EGLContext getShareContext(long sdkInt) { ensureAttachedToNative(); - return nativeGetShareContext(nativePlatformViewId,sdkInt); + return nativeGetShareContext(nativePlatformViewId, sdkInt); } - private native EGLContext nativeGetShareContext(long nativePlatformViewId,long sdkInt); + private native EGLContext nativeGetShareContext(long nativePlatformViewId, long sdkInt); /** * Call this method to inform Flutter that a texture previously registered with diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index 8e0436f35af14..56c9711ee99ff 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -113,15 +113,18 @@ public SurfaceTextureEntry createSurfaceTexture() { @Override public TextureRegistry.ShareTextureEntry createShareTexture(long shareTextureID) { - final ShareTextureRegistryEntry entry = new ShareTextureRegistryEntry(nextTextureId.getAndIncrement(),shareTextureID); + final ShareTextureRegistryEntry entry = new ShareTextureRegistryEntry( + nextTextureId.getAndIncrement(), + shareTextureID + ); Log.v(TAG, "New ShareTexture ID: " + entry.id()); - registerShareTexture(entry.id(),shareTextureID); + registerShareTexture(entry.id(), shareTextureID); return entry; } @Override - public void onShareFrameAvaliable(long textureIndex) { - markTextureFrameAvailable(textureIndex); + public void onShareFrameAvaliable(long textureId) { + markTextureFrameAvailable(textureId); } @Override diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 1ddd95bcd7879..ae0831508181a 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -781,14 +781,14 @@ public TextureRegistry.SurfaceTextureEntry createSurfaceTexture() { } @Override - public void onShareFrameAvaliable(long textureIndex) { - mNativeView.getFlutterJNI().markTextureFrameAvailable(textureIndex); + public void onShareFrameAvaliable(long textureId) { + mNativeView.getFlutterJNI().markTextureFrameAvailable(textureId); } @Override public TextureRegistry.ShareTextureEntry createShareTexture(long shareTextureID) { - final ShareTextureRegistryEntry entry = new ShareTextureRegistryEntry(nextTextureId.getAndIncrement(),shareTextureID); - mNativeView.getFlutterJNI().registerShareTexture(entry.id(),shareTextureID); + final ShareTextureRegistryEntry entry = new ShareTextureRegistryEntry(nextTextureId.getAndIncrement(), shareTextureID); + mNativeView.getFlutterJNI().registerShareTexture(entry.id(), shareTextureID); return entry; } diff --git a/shell/platform/android/io/flutter/view/TextureRegistry.java b/shell/platform/android/io/flutter/view/TextureRegistry.java index d723f07d81030..f3d6f11a0bd44 100644 --- a/shell/platform/android/io/flutter/view/TextureRegistry.java +++ b/shell/platform/android/io/flutter/view/TextureRegistry.java @@ -44,8 +44,9 @@ interface SurfaceTextureEntry { /** * Informs the the Flutter Engine that the external texture has been updated, and to start a new rendering pipeline. + * The paramater is the id of the ShareTextureEntry (Not OpenGL Texture ID) */ - void onShareFrameAvaliable(long textureIndex); + void onShareFrameAvaliable(long textureId); /** * The OpenGL context created in the Flutter Engine, which is safe to user for sharing diff --git a/shell/platform/android/platform_view_android_jni.cc b/shell/platform/android/platform_view_android_jni.cc index 9c11dc4048860..d6eeefc798021 100644 --- a/shell/platform/android/platform_view_android_jni.cc +++ b/shell/platform/android/platform_view_android_jni.cc @@ -464,6 +464,9 @@ static jobject GetShareContext(JNIEnv* env, EGLContext cxt = ANDROID_SHELL_HOLDER->GetPlatformView()->GetShareContext(); jclass eglcontextClassLocal = env->FindClass("android/opengl/EGLContext"); + if (eglcontextClassLocal == nullptr) { + return nullptr; + } jmethodID eglcontextConstructor; jobject eglContext; if (sdk_int >= 21) { diff --git a/shell/platform/darwin/ios/ios_external_texture_gl_share_context.h b/shell/platform/darwin/ios/ios_external_texture_gl_share_context.h index b9dd306d59cab..a53ec4096ffff 100644 --- a/shell/platform/darwin/ios/ios_external_texture_gl_share_context.h +++ b/shell/platform/darwin/ios/ios_external_texture_gl_share_context.h @@ -10,9 +10,9 @@ #include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterTexture.h" namespace flutter { -// This is a new way of external texture. -// Unlike the original external texture which copy pixel buffer from an object that impelement -// FlutterTexture procotol, and create a texture reference using the pixel buffer. This solution +// This is another solution for Flutter's external texture. +// Unlike the original external texture which copies pixel buffer from an object that impelements +// FlutterTexture procotol, and creates a texture reference using the pixel buffer, this solution // will copy the OpenGL texture directly from the object impelementing FlutterShareTexture protocol. class IOSExternalTextureShareContext : public flutter::Texture { public: diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/MainActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/MainActivity.java index 0fa11e1249339..c40b634da84d3 100644 --- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/MainActivity.java +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/MainActivity.java @@ -45,7 +45,7 @@ protected void onCreate(Bundle savedInstanceState) { final Uri logFileUri = launchIntent.getData(); new Handler().postDelayed(() -> writeTimelineData(logFileUri), 20000); } - else if("com.google.intent.action.TEST_EXTERNAL_TEXTURE".equals(launchIntent.getAction())){ + else if(true){ startExternalTexture(); } else if("com.google.intent.action.TEST_PLATFORM_VIEW".equals(launchIntent.getAction())){ diff --git a/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj b/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj index 3522b5f20acb5..53c157b25657b 100644 --- a/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj +++ b/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj @@ -536,7 +536,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = S8QB4VV633; + DEVELOPMENT_TEAM = 7MSDM3RKPG; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", @@ -565,7 +565,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = S8QB4VV633; + DEVELOPMENT_TEAM = 7MSDM3RKPG; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", diff --git a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m index 9ef836387b867..174d3cacf9814 100644 --- a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m +++ b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m @@ -42,20 +42,13 @@ - (BOOL)application:(UIApplication*)application [registrar registerViewFactory:textPlatformViewFactory withId:@"scenarios/textPlatformView"]; self.window.rootViewController = flutterViewController; } else if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--external-texture"]) { - FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"ExternalTextureTest" project:nil]; + FlutterEngine* engine = [[FlutterEngine alloc] initWithScenario:@"external_texture" + withCompletion:nil]; [engine runWithEntrypoint:nil]; FlutterViewController* flutterViewController = [[NoStatusBarFlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil]; - [engine.binaryMessenger - setMessageHandlerOnChannel:@"scenario_status" - binaryMessageHandler:^(NSData* _Nullable message, FlutterBinaryReply _Nonnull reply) { - [engine.binaryMessenger - sendOnChannel:@"set_scenario" - message:[@"external_texture" dataUsingEncoding:NSUTF8StringEncoding]]; - }]; - [engine.binaryMessenger setMessageHandlerOnChannel:@"create_external_texture" binaryMessageHandler:^(NSData* _Nullable message, FlutterBinaryReply _Nonnull reply) { diff --git a/testing/scenario_app/lib/main.dart b/testing/scenario_app/lib/main.dart index 3f9da6ce59c2f..a26070f98d4b3 100644 --- a/testing/scenario_app/lib/main.dart +++ b/testing/scenario_app/lib/main.dart @@ -57,12 +57,12 @@ Future _handlePlatformMessage( } else if (name == 'write_timeline') { final String timelineData = await _getTimelineData(); callback(Uint8List.fromList(utf8.encode(timelineData)).buffer.asByteData()); - } else { - _currentScenario?.onPlatformMessage(name, data, callback); - } - else if(name == 'update_data'){ + } else if(name == 'update_data'){ _currentScenario.onUpdateData(data); } + else { + _currentScenario?.onPlatformMessage(name, data, callback); + } } Future _getTimelineData() async { diff --git a/testing/scenario_app/lib/src/external_texture_view.dart b/testing/scenario_app/lib/src/external_texture_view.dart index 3f9b3bc5f7ba3..f669505b54795 100644 --- a/testing/scenario_app/lib/src/external_texture_view.dart +++ b/testing/scenario_app/lib/src/external_texture_view.dart @@ -26,13 +26,15 @@ class ExternalTextureScenario extends Scenario { @override void onBeginFrame(Duration duration) { - print('begin frame'); + print('begin frame 0000'); final SceneBuilder builder = SceneBuilder(); builder.pushOffset(0, 0); if (_textureId != null) { + print('begin frame 1111'); + builder.addTexture(_textureId, offset: const Offset(0, 0), width: 480, height: 480); } @@ -48,6 +50,22 @@ class ExternalTextureScenario extends Scenario { if (data != null) { String string = utf8.decode(data.buffer.asUint8List()); _textureId = int.parse(string); + print('update textureid $_textureId'); + + final SceneBuilder builder = SceneBuilder(); + + builder.pushOffset(0, 0); + + if (_textureId != null) { + print('begin frame 1111'); + + builder.addTexture(_textureId, offset: const Offset(0, 0), width: 480, height: 480); + } + + + final Scene scene = builder.build(); + window.render(scene); + scene.dispose(); } } } From 6da0037445a774042c9c5a3b01b00fb4180c026f Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Mon, 16 Sep 2019 21:14:22 +0800 Subject: [PATCH 10/19] Code format --- .../android_external_texture_gl_share_context.cc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/shell/platform/android/android_external_texture_gl_share_context.cc b/shell/platform/android/android_external_texture_gl_share_context.cc index c1196a68430f4..0b3fc3b3b746a 100644 --- a/shell/platform/android/android_external_texture_gl_share_context.cc +++ b/shell/platform/android/android_external_texture_gl_share_context.cc @@ -13,8 +13,10 @@ namespace flutter { // This is another solution for Flutter's ExternalTexture. -// The original ExternalTexture uses SurfaceTexture to update the frame data that native video object produces to an OpenGL texture. -// In this scheme, we directly pass an OpenGL texture ID to the ExternalTexture object, and avoid the performance consumption of data writing to SurfaceTexture +// The original ExternalTexture uses SurfaceTexture to update the frame data +// that native video object produces to an OpenGL texture. In this scheme, we +// directly pass an OpenGL texture ID to the ExternalTexture object, and avoid +// the performance consumption of data writing to SurfaceTexture AndroidExternalTextureShareContext::AndroidExternalTextureShareContext( int64_t id, int64_t shareTextureID) @@ -36,14 +38,14 @@ void AndroidExternalTextureShareContext::Paint(SkCanvas& canvas, textureInfo.fTarget = GL_TEXTURE_2D; transform.setIdentity(); - GrBackendTexture backendTexture(bounds.width(), bounds.height(), GrMipMapped::kNo, textureInfo); + GrBackendTexture backendTexture(bounds.width(), bounds.height(), + GrMipMapped::kNo, textureInfo); sk_sp image = SkImage::MakeFromTexture( canvas.getGrContext(), backendTexture, kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr); if (image) { canvas.drawImage(image, bounds.x(), bounds.y()); - } - else{ + } else { FML_LOG(ERROR) << "Create SKIImage Fail !!"; } } From 464de18c736c7671777a06f861e728058f02bd27 Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Mon, 16 Sep 2019 21:16:42 +0800 Subject: [PATCH 11/19] Hard code remove --- .../app/src/main/java/dev/flutter/scenarios/MainActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/MainActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/MainActivity.java index c40b634da84d3..0fa11e1249339 100644 --- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/MainActivity.java +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/MainActivity.java @@ -45,7 +45,7 @@ protected void onCreate(Bundle savedInstanceState) { final Uri logFileUri = launchIntent.getData(); new Handler().postDelayed(() -> writeTimelineData(logFileUri), 20000); } - else if(true){ + else if("com.google.intent.action.TEST_EXTERNAL_TEXTURE".equals(launchIntent.getAction())){ startExternalTexture(); } else if("com.google.intent.action.TEST_PLATFORM_VIEW".equals(launchIntent.getAction())){ From 87bd692a463756ff2ea68f7d46066bd1c8e31ce8 Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Tue, 17 Sep 2019 09:46:45 +0800 Subject: [PATCH 12/19] code format --- shell/platform/darwin/ios/platform_view_ios.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/platform_view_ios.h b/shell/platform/darwin/ios/platform_view_ios.h index b9f2cb6717aed..a4d328a0bf428 100644 --- a/shell/platform/darwin/ios/platform_view_ios.h +++ b/shell/platform/darwin/ios/platform_view_ios.h @@ -38,7 +38,7 @@ class PlatformViewIOS final : public PlatformView { void RegisterExternalTexture(int64_t id, NSObject* texture); void RegisterExternalShareTexture(int64_t id, NSObject* texture); - + // |PlatformView| PointerDataDispatcherMaker GetDispatcherMaker() override; From a1ef963cc56f3163be30d82d7a7fbe571272db9e Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Tue, 17 Sep 2019 09:59:40 +0800 Subject: [PATCH 13/19] Merge compile error modify --- shell/platform/darwin/ios/ios_surface_gl.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/ios_surface_gl.mm b/shell/platform/darwin/ios/ios_surface_gl.mm index 197fecdb9034f..d7bea95929e1e 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.mm +++ b/shell/platform/darwin/ios/ios_surface_gl.mm @@ -82,7 +82,7 @@ } EAGLSharegroup* IOSSurfaceGL::GetGLShareGroup() { - return context_.get()->GetGLShareGroup(); + return onscreen_gl_context_.get()->GetGLShareGroup(); } // |ExternalViewEmbedder| From 7675cdca15f79015156621b701f7a11b792abba4 Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Tue, 17 Sep 2019 10:13:49 +0800 Subject: [PATCH 14/19] Delete unused code --- .../android/android_external_texture_gl_share_context.cc | 1 - .../android/android_external_texture_gl_share_context.h | 2 -- 2 files changed, 3 deletions(-) diff --git a/shell/platform/android/android_external_texture_gl_share_context.cc b/shell/platform/android/android_external_texture_gl_share_context.cc index 0b3fc3b3b746a..cac68fab0ce59 100644 --- a/shell/platform/android/android_external_texture_gl_share_context.cc +++ b/shell/platform/android/android_external_texture_gl_share_context.cc @@ -36,7 +36,6 @@ void AndroidExternalTextureShareContext::Paint(SkCanvas& canvas, GL_RGBA8_OES}; textureInfo.fTarget = GL_TEXTURE_2D; - transform.setIdentity(); GrBackendTexture backendTexture(bounds.width(), bounds.height(), GrMipMapped::kNo, textureInfo); diff --git a/shell/platform/android/android_external_texture_gl_share_context.h b/shell/platform/android/android_external_texture_gl_share_context.h index f54fc087bd6ee..6f6c47421c897 100644 --- a/shell/platform/android/android_external_texture_gl_share_context.h +++ b/shell/platform/android/android_external_texture_gl_share_context.h @@ -33,8 +33,6 @@ class AndroidExternalTextureShareContext : public flutter::Texture { GLuint texture_id_ = 0; - SkMatrix transform; - FML_DISALLOW_COPY_AND_ASSIGN(AndroidExternalTextureShareContext); }; From 0c9a675c7bcf9476ab43bce29ebe925f4dee358c Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Wed, 18 Sep 2019 15:29:13 +0800 Subject: [PATCH 15/19] Check SKImage null --- .../android/android_external_texture_gl_share_context.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shell/platform/android/android_external_texture_gl_share_context.cc b/shell/platform/android/android_external_texture_gl_share_context.cc index cac68fab0ce59..8fa9c743b1daa 100644 --- a/shell/platform/android/android_external_texture_gl_share_context.cc +++ b/shell/platform/android/android_external_texture_gl_share_context.cc @@ -42,10 +42,9 @@ void AndroidExternalTextureShareContext::Paint(SkCanvas& canvas, sk_sp image = SkImage::MakeFromTexture( canvas.getGrContext(), backendTexture, kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr); + FML_DCHECK(image) << "Failed to create SkImage from Texture."; if (image) { canvas.drawImage(image, bounds.x(), bounds.y()); - } else { - FML_LOG(ERROR) << "Create SKIImage Fail !!"; } } From 78089db1d15c3dede97098656455d14620da9a73 Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Wed, 18 Sep 2019 16:26:23 +0800 Subject: [PATCH 16/19] Add texture_id == 0 debug log --- .../platform/darwin/ios/ios_external_texture_gl_share_context.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/shell/platform/darwin/ios/ios_external_texture_gl_share_context.mm b/shell/platform/darwin/ios/ios_external_texture_gl_share_context.mm index 0f8a9eec9068e..79b2249cb2a07 100644 --- a/shell/platform/darwin/ios/ios_external_texture_gl_share_context.mm +++ b/shell/platform/darwin/ios/ios_external_texture_gl_share_context.mm @@ -29,6 +29,7 @@ GrContext* context) { GLuint texture_id = [external_texture_ copyShareTexture]; if (texture_id == 0) { + FML_LOG(WARNING) << "Paint nothing when texture_id is 0"; return; } GrGLTextureInfo textureInfo; From 17185dcb72213dfb1f3ef81d0ebe7a74c1e0b633 Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Wed, 18 Sep 2019 16:52:51 +0800 Subject: [PATCH 17/19] Move docstring to header file --- .../android/android_external_texture_gl_share_context.cc | 5 ----- .../android/android_external_texture_gl_share_context.h | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/shell/platform/android/android_external_texture_gl_share_context.cc b/shell/platform/android/android_external_texture_gl_share_context.cc index 8fa9c743b1daa..909a1534bad0f 100644 --- a/shell/platform/android/android_external_texture_gl_share_context.cc +++ b/shell/platform/android/android_external_texture_gl_share_context.cc @@ -12,11 +12,6 @@ #include "flutter/fml/trace_event.h" namespace flutter { -// This is another solution for Flutter's ExternalTexture. -// The original ExternalTexture uses SurfaceTexture to update the frame data -// that native video object produces to an OpenGL texture. In this scheme, we -// directly pass an OpenGL texture ID to the ExternalTexture object, and avoid -// the performance consumption of data writing to SurfaceTexture AndroidExternalTextureShareContext::AndroidExternalTextureShareContext( int64_t id, int64_t shareTextureID) diff --git a/shell/platform/android/android_external_texture_gl_share_context.h b/shell/platform/android/android_external_texture_gl_share_context.h index 6f6c47421c897..26f3ef8b0c1e4 100644 --- a/shell/platform/android/android_external_texture_gl_share_context.h +++ b/shell/platform/android/android_external_texture_gl_share_context.h @@ -11,6 +11,11 @@ namespace flutter { +// This is another solution for Flutter's ExternalTexture. +// The original ExternalTexture uses SurfaceTexture to update the frame data +// that native video object produces to an OpenGL texture. In this scheme, we +// directly pass an OpenGL texture ID to the ExternalTexture object, and avoid +// the performance consumption of data writing to SurfaceTexture class AndroidExternalTextureShareContext : public flutter::Texture { public: AndroidExternalTextureShareContext(int64_t id, int64_t shareTextureID); From 3073e061ea2543db48bb6360fc6aa5c9468284ff Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Fri, 20 Sep 2019 11:41:09 +0800 Subject: [PATCH 18/19] code format --- shell/platform/fuchsia/runtime/dart/utils/BUILD.gn | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/fuchsia/runtime/dart/utils/BUILD.gn b/shell/platform/fuchsia/runtime/dart/utils/BUILD.gn index b2324ec25536a..9e523ea264bcc 100644 --- a/shell/platform/fuchsia/runtime/dart/utils/BUILD.gn +++ b/shell/platform/fuchsia/runtime/dart/utils/BUILD.gn @@ -33,10 +33,10 @@ source_set("utils") { "$fuchsia_sdk_root/pkg:async-loop-cpp", "$fuchsia_sdk_root/pkg:fdio", "$fuchsia_sdk_root/pkg:memfs", - "$fuchsia_sdk_root/pkg:syslog", - "$fuchsia_sdk_root/pkg:zx", "$fuchsia_sdk_root/pkg:sys_cpp", + "$fuchsia_sdk_root/pkg:syslog", "$fuchsia_sdk_root/pkg:vfs_cpp", + "$fuchsia_sdk_root/pkg:zx", "//third_party/tonic", ] From ada65653a91c82e0a7a6e45d9b5750597b8253fa Mon Sep 17 00:00:00 2001 From: kaisa695275735 Date: Wed, 25 Sep 2019 10:22:13 +0800 Subject: [PATCH 19/19] doc string --- .../darwin/ios/ios_external_texture_gl_share_context.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shell/platform/darwin/ios/ios_external_texture_gl_share_context.h b/shell/platform/darwin/ios/ios_external_texture_gl_share_context.h index a53ec4096ffff..eb3925df05052 100644 --- a/shell/platform/darwin/ios/ios_external_texture_gl_share_context.h +++ b/shell/platform/darwin/ios/ios_external_texture_gl_share_context.h @@ -11,9 +11,9 @@ namespace flutter { // This is another solution for Flutter's external texture. -// Unlike the original external texture which copies pixel buffer from an object that impelements -// FlutterTexture procotol, and creates a texture reference using the pixel buffer, this solution -// will copy the OpenGL texture directly from the object impelementing FlutterShareTexture protocol. +// Unlike the original external texture which copies pixel buffer from an object that implements +// FlutterTexture protocol, and creates a texture reference using the pixel buffer, this solution +// will copy the OpenGL texture directly from the object implementing FlutterShareTexture protocol. class IOSExternalTextureShareContext : public flutter::Texture { public: IOSExternalTextureShareContext(int64_t textureId, NSObject* externalTexture);