External Texture Support In MEC

Bug: angleproject:4964
Change-Id: I5cfbadf515a30fb20d75b2d745fdecdafa12268f
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3812378
Reviewed-by: Yuxin Hu <yuxinhu@google.com>
Auto-Submit: Faye Zhang <ffz@google.com>
Commit-Queue: Faye Zhang <ffz@google.com>
Reviewed-by: Cody Northrop <cnorthrop@google.com>
diff --git a/src/libANGLE/capture/FrameCapture.cpp b/src/libANGLE/capture/FrameCapture.cpp
index 12dd107..1bd9526 100644
--- a/src/libANGLE/capture/FrameCapture.cpp
+++ b/src/libANGLE/capture/FrameCapture.cpp
@@ -32,6 +32,7 @@
 #include "libANGLE/Shader.h"
 #include "libANGLE/Surface.h"
 #include "libANGLE/VertexArray.h"
+#include "libANGLE/capture/capture_egl.h"
 #include "libANGLE/capture/capture_gles_1_0_autogen.h"
 #include "libANGLE/capture/capture_gles_2_0_autogen.h"
 #include "libANGLE/capture/capture_gles_3_0_autogen.h"
@@ -43,6 +44,7 @@
 #include "libANGLE/entry_points_utils.h"
 #include "libANGLE/queryconversions.h"
 #include "libANGLE/queryutils.h"
+#include "libANGLE/validationEGL.h"
 #include "third_party/ceval/ceval.h"
 
 #define USE_SYSTEM_ZLIB
@@ -2887,6 +2889,18 @@
         return;
     }
 
+    if (index.getType() == gl::TextureType::External)
+    {
+        // The generated glTexImage2D call is for creating the staging texture
+        Capture(setupCalls,
+                CaptureTexImage2D(*replayState, true, gl::TextureTarget::_2D, index.getLevelIndex(),
+                                  format.internalFormat, desc.size.width, desc.size.height, 0,
+                                  format.format, format.type, data));
+
+        // For external textures, we're done
+        return;
+    }
+
     bool is3D =
         (index.getType() == gl::TextureType::_3D || index.getType() == gl::TextureType::_2DArray ||
          index.getType() == gl::TextureType::CubeMapArray);
@@ -3267,12 +3281,13 @@
 
 // Capture the setup of the state that's shared by all of the contexts in the share group
 // See IsSharedObjectResource for the list of objects covered here.
-void CaptureShareGroupMidExecutionSetup(const gl::Context *context,
-                                        std::vector<CallCapture> *setupCalls,
-                                        ResourceTracker *resourceTracker,
-                                        gl::State &replayState)
+void CaptureShareGroupMidExecutionSetup(
+    const gl::Context *context,
+    std::vector<CallCapture> *setupCalls,
+    ResourceTracker *resourceTracker,
+    gl::State &replayState,
+    const PackedEnumMap<ResourceIDType, uint32_t> &maxAccessedResourceIDs)
 {
-
     FrameCaptureShared *frameCaptureShared = context->getShareGroup()->getFrameCaptureShared();
     const gl::State &apiState              = context->getState();
 
@@ -3574,7 +3589,8 @@
                                   index.getType() == gl::TextureType::_2DArray ||
                                   index.getType() == gl::TextureType::Buffer ||
                                   index.getType() == gl::TextureType::CubeMap ||
-                                  index.getType() == gl::TextureType::CubeMapArray);
+                                  index.getType() == gl::TextureType::CubeMapArray ||
+                                  index.getType() == gl::TextureType::External);
 
             // Check for supported textures
             if (!supportedType)
@@ -3594,6 +3610,20 @@
                 continue;
             }
 
+            // create a staging GL_TEXTURE_2D texture to create the eglImage with
+            gl::TextureID stagingTexId = {maxAccessedResourceIDs[ResourceIDType::Texture] + 1};
+            if (index.getType() == gl::TextureType::External)
+            {
+                Capture(setupCalls, CaptureGenTextures(replayState, true, 1, &stagingTexId));
+                MaybeCaptureUpdateResourceIDs(context, resourceTracker, setupCalls);
+                Capture(setupCalls,
+                        CaptureBindTexture(replayState, true, gl::TextureType::_2D, stagingTexId));
+                Capture(setupCalls, CaptureTexParameteri(replayState, true, gl::TextureType::_2D,
+                                                         GL_TEXTURE_MIN_FILTER, GL_NEAREST));
+                Capture(setupCalls, CaptureTexParameteri(replayState, true, gl::TextureType::_2D,
+                                                         GL_TEXTURE_MAG_FILTER, GL_NEAREST));
+            }
+
             if (context->getExtensions().getImageANGLE)
             {
                 // Use ANGLE_get_image to read back pixel data.
@@ -3643,6 +3673,37 @@
                     CaptureTextureContents(calls, &replayState, texture, index, desc,
                                            static_cast<GLuint>(data.size()), data.data());
                 }
+
+                if (index.getType() == gl::TextureType::External)
+                {
+                    constexpr EGLint attribs[] = {
+                        EGL_IMAGE_PRESERVED,
+                        EGL_TRUE,
+                        EGL_NONE,
+                    };
+                    const egl::AttributeMap &attrib_listPacked =
+                        egl::PackParam<const egl::AttributeMap &>(attribs);
+                    attrib_listPacked.initializeWithoutValidation();
+
+                    // Create the image on demand
+                    egl::Image *image = nullptr;
+                    Capture(setupCalls,
+                            CaptureEGLCreateImage(context, EGL_GL_TEXTURE_2D_KHR,
+                                                  reinterpret_cast<EGLClientBuffer>(
+                                                      static_cast<GLuint64>(stagingTexId.value)),
+                                                  attrib_listPacked, image));
+
+                    // Pass the eglImage to the texture that is bound to GL_TEXTURE_EXTERNAL_OES
+                    // target
+                    for (std::vector<CallCapture> *calls : texSetupCalls)
+                    {
+                        Capture(calls, CaptureEGLImageTargetTexture2DOES(
+                                           replayState, true, gl::TextureType::External, image));
+                    }
+
+                    // Delete the staging texture
+                    Capture(setupCalls, CaptureDeleteTextures(replayState, true, 1, &stagingTexId));
+                }
             }
             else
             {
@@ -6399,11 +6460,13 @@
 
             break;
         }
+
         case EntryPoint::GLCopyBufferSubData:
         {
             maybeCaptureCoherentBuffers(context);
             break;
         }
+
         case EntryPoint::GLDeleteFramebuffers:
         case EntryPoint::GLDeleteFramebuffersOES:
         {
@@ -6423,6 +6486,7 @@
             }
             break;
         }
+
         case EntryPoint::GLUseProgram:
         {
             if (isCaptureActive())
@@ -6432,6 +6496,7 @@
             }
             break;
         }
+
         case EntryPoint::GLGenVertexArrays:
         case EntryPoint::GLGenVertexArraysOES:
         {
@@ -6445,6 +6510,7 @@
             }
             break;
         }
+
         case EntryPoint::GLDeleteVertexArrays:
         case EntryPoint::GLDeleteVertexArraysOES:
         {
@@ -6459,6 +6525,7 @@
             }
             break;
         }
+
         case EntryPoint::GLBindVertexArray:
         case EntryPoint::GLBindVertexArrayOES:
         {
@@ -6469,6 +6536,38 @@
             }
             break;
         }
+
+        case EntryPoint::EGLCreateImage:
+        case EntryPoint::EGLCreateImageKHR:
+        {
+            ParamData attribs =
+                call.params.getParam("attrib_list", ParamType::TGLint64Pointer, 4).data;
+
+            int bytesPerAttrib = 8;
+            int numAttribs     = static_cast<int>(attribs[0].size() / bytesPerAttrib);
+            std::vector<GLint64> reconstructedAttribs(numAttribs);
+            for (int i = 0; i < numAttribs; i++)
+            {
+                GLint64 compiledAttrib = 0;
+                for (int b = 0; b < bytesPerAttrib; b++)
+                {
+                    compiledAttrib |= attribs[0][i * bytesPerAttrib + b] << (b * sizeof(uint64_t));
+                }
+                reconstructedAttribs[i] = compiledAttrib;
+            }
+
+            // Check if the passed-in attribs are as expected, otherwise throw an UNREACHABLE
+            if (!isCaptureActive() &&
+                (reconstructedAttribs[0] != EGL_IMAGE_PRESERVED ||
+                 reconstructedAttribs[1] != EGL_TRUE || reconstructedAttribs[2] != EGL_NONE))
+            {
+                ERR() << "EGLImage created with " << numAttribs << " unsupported attribs types.";
+                UNREACHABLE();
+            }
+
+            break;
+        }
+
         default:
             break;
     }
@@ -6976,7 +7075,7 @@
     mainContextReplayState.initializeForCapture(mainContext);
 
     CaptureShareGroupMidExecutionSetup(mainContext, &mShareGroupSetupCalls, &mResourceTracker,
-                                       mainContextReplayState);
+                                       mainContextReplayState, mMaxAccessedResourceIDs);
 
     scanSetupCalls(mainContext, mShareGroupSetupCalls);
 
diff --git a/src/libANGLE/capture/capture_egl.cpp b/src/libANGLE/capture/capture_egl.cpp
index 6ad2a43..5f8945f 100644
--- a/src/libANGLE/capture/capture_egl.cpp
+++ b/src/libANGLE/capture/capture_egl.cpp
@@ -128,7 +128,7 @@
                               std::move(paramBuffer));
 }
 
-angle::CallCapture CaptureEGLCreateImage(gl::Context *context,
+angle::CallCapture CaptureEGLCreateImage(const gl::Context *context,
                                          EGLenum target,
                                          EGLClientBuffer buffer,
                                          const egl::AttributeMap &attributes,
diff --git a/src/libANGLE/capture/capture_egl.h b/src/libANGLE/capture/capture_egl.h
index e8e2fc6..f23bc1a 100644
--- a/src/libANGLE/capture/capture_egl.h
+++ b/src/libANGLE/capture/capture_egl.h
@@ -45,7 +45,7 @@
 
 angle::CallCapture CaptureCreateNativeClientBufferANDROID(const egl::AttributeMap &attribMap,
                                                           EGLClientBuffer eglClientBuffer);
-angle::CallCapture CaptureEGLCreateImage(gl::Context *context,
+angle::CallCapture CaptureEGLCreateImage(const gl::Context *context,
                                          EGLenum target,
                                          EGLClientBuffer buffer,
                                          const egl::AttributeMap &attributes,
diff --git a/src/tests/capture_replay_tests/capture_replay_expectations.txt b/src/tests/capture_replay_tests/capture_replay_expectations.txt
index 4ef3a5a..03459df 100644
--- a/src/tests/capture_replay_tests/capture_replay_expectations.txt
+++ b/src/tests/capture_replay_tests/capture_replay_expectations.txt
@@ -162,3 +162,19 @@
 7483 : MultisampleTestES3.ResolveToFBO/* = SKIP_FOR_CAPTURE
 
 6723 : EGLMultiContextTest.ReuseUnterminatedDisplay/* = SKIP_FOR_CAPTURE
+
+# No support yet for Capture/Replay of External Textures using other attribs
+7570 : ImageTest.MipLevels/* = SKIP_FOR_CAPTURE
+7570 : ImageTest.Source2DTarget2D_Colorspace/* = SKIP_FOR_CAPTURE
+7570 : ImageTest.Source2DTarget2DArray_Colorspace/* = SKIP_FOR_CAPTURE
+7570 : ImageTest.Source2DTargetExternal_Colorspace/* = SKIP_FOR_CAPTURE
+7570 : ImageTest.SourceCubeTarget2D_Colorspace/* = SKIP_FOR_CAPTURE
+7570 : ImageTest.SourceCubeTargetExternal_Colorspace/* = SKIP_FOR_CAPTURE
+7570 : ImageTest.SourceRenderbufferTargetTexture_Colorspace/* = SKIP_FOR_CAPTURE
+7570 : ImageTest.SourceRenderbufferTargetTextureExternal_Colorspace/* = SKIP_FOR_CAPTURE
+7570 : ImageTest.ValidationGLEGLImage_Colorspace/* = SKIP_FOR_CAPTURE
+7570 : ImageTest.ValidationImageBase/* = SKIP_FOR_CAPTURE
+7570 : ImageTestES3.Source2DTargetExternalESSL3_Colorspace/* = SKIP_FOR_CAPTURE
+7570 : ImageTestES3.SourceCubeTargetExternalESSL3/* = SKIP_FOR_CAPTURE
+7570 : ImageTestES3.SourceCubeTargetExternalESSL3_Colorspace/* = SKIP_FOR_CAPTURE
+7570 : ImageTestES3.SourceRenderbufferTargetTextureExternalESSL3_Colorspace/* = SKIP_FOR_CAPTURE
\ No newline at end of file
diff --git a/src/tests/gl_tests/ImageTest.cpp b/src/tests/gl_tests/ImageTest.cpp
index 57dcda1..053617c 100644
--- a/src/tests/gl_tests/ImageTest.cpp
+++ b/src/tests/gl_tests/ImageTest.cpp
@@ -5526,6 +5526,199 @@
     }
 }
 
+// Case for testing External Texture support in MEC.
+// To run this test with the right capture setting, make sure to set these environment variables:
+//
+// For Linux:
+//      export ANGLE_CAPTURE_FRAME_START=2
+//      export ANGLE_CAPTURE_FRAME_END=2
+//      export ANGLE_CAPTURE_LABEL=external_textures
+//      export ANGLE_CAPTURE_OUT_DIR=[PATH_TO_ANGLE]/src/tests/restricted_traces/external_textures/
+//
+// For Android:
+//      adb shell setprop debug.angle.capture.frame_start 2
+//      adb shell setprop debug.angle.capture.frame_end 2
+//      adb shell setprop debug.angle.capture.label external_textures
+//      adb shell setprop debug.angle.capture.out_dir /data/data/externaltextures/angle_capture/
+TEST_P(ImageTest, AppTraceExternalTextureUseCase)
+{
+    EGLWindow *window = getEGLWindow();
+    ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt() || !has2DTextureExt() ||
+                       !hasExternalESSL3Ext());
+
+    constexpr EGLint attribs[] = {
+        EGL_IMAGE_PRESERVED,
+        EGL_TRUE,
+        EGL_NONE,
+    };
+
+    // Create the Image
+    GLTexture sourceTexture1;
+    EGLImageKHR image1;
+
+    GLubyte data[] = {132, 55, 219, 255};
+    // Create a source 2D texture
+    glBindTexture(GL_TEXTURE_2D, sourceTexture1);
+
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, static_cast<GLsizei>(1), static_cast<GLsizei>(1), 0,
+                 GL_RGBA, GL_UNSIGNED_BYTE, data);
+
+    // Disable mipmapping
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+    ASSERT_GL_NO_ERROR();
+
+    image1 = eglCreateImageKHR(window->getDisplay(), window->getContext(), EGL_GL_TEXTURE_2D_KHR,
+                               reinterpretHelper<EGLClientBuffer>(sourceTexture1.get()), attribs);
+
+    ASSERT_EGL_SUCCESS();
+
+    // Create the target
+    GLTexture targetTexture1;
+    // Create a target texture from the image
+    glBindTexture(GL_TEXTURE_EXTERNAL_OES, targetTexture1);
+    glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image1);
+
+    // Disable mipmapping
+    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+    ASSERT_GL_NO_ERROR();
+
+    // Calls On EndFrame(), with MidExecutionSetup to restore external texture targetTexture1 above
+    EGLDisplay display = getEGLWindow()->getDisplay();
+    EGLSurface surface = getEGLWindow()->getSurface();
+    eglSwapBuffers(display, surface);
+
+    // Create another eglImage with another associated texture
+    // Draw using the eglImage texture targetTexture1 created in frame 1
+    GLTexture sourceTexture2;
+    EGLImageKHR image2;
+
+    // Create a source 2D texture
+    glBindTexture(GL_TEXTURE_2D, sourceTexture2);
+
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, static_cast<GLsizei>(1), static_cast<GLsizei>(1), 0,
+                 GL_RGBA, GL_UNSIGNED_BYTE, data);
+
+    // Disable mipmapping
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+    ASSERT_GL_NO_ERROR();
+
+    image2 = eglCreateImageKHR(window->getDisplay(), window->getContext(), EGL_GL_TEXTURE_2D_KHR,
+                               reinterpretHelper<EGLClientBuffer>(sourceTexture2.get()), attribs);
+
+    ASSERT_EGL_SUCCESS();
+
+    // Create the target
+    GLTexture targetTexture2;
+    // Create a target texture from the image
+    glBindTexture(GL_TEXTURE_EXTERNAL_OES, targetTexture2);
+    glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image2);
+
+    // Disable mipmapping
+    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+    ASSERT_GL_NO_ERROR();
+    glUseProgram(mTextureExternalProgram);
+    glBindTexture(GL_TEXTURE_EXTERNAL_OES, targetTexture1);
+    glUniform1i(mTextureExternalUniformLocation, 0);
+
+    drawQuad(mTextureExternalProgram, "position", 0.5f);
+
+    // Calls On EndFrame() to save the gl calls creating external texture targetTexture2;
+    // We use this as a reference to check the gl calls we restore for targetTexture1
+    // in MidExecutionSetup
+    eglSwapBuffers(display, surface);
+
+    // Draw a quad with the targetTexture2
+    glUseProgram(mTextureExternalProgram);
+    glBindTexture(GL_TEXTURE_EXTERNAL_OES, targetTexture2);
+    glUniform1i(mTextureExternalUniformLocation, 0);
+
+    drawQuad(mTextureExternalProgram, "position", 0.5f);
+
+    eglSwapBuffers(display, surface);
+
+    // Clean up
+    eglDestroyImageKHR(window->getDisplay(), image1);
+    eglDestroyImageKHR(window->getDisplay(), image2);
+}
+
+// Alternate case for testing External Texture (created with AHB) support in MEC.
+// Make sure to use the following environment variables for the right capture setting on Android:
+//
+// adb shell setprop debug.angle.capture.frame_start 2
+// adb shell setprop debug.angle.capture.frame_end 2
+// adb shell setprop debug.angle.capture.label AHB_textures
+// adb shell setprop debug.angle.capture.out_dir /data/data/AHBtextures/angle_capture/
+TEST_P(ImageTest, AppTraceExternalTextureWithAHBUseCase)
+{
+    EGLWindow *window = getEGLWindow();
+
+    ANGLE_SKIP_TEST_IF(!IsAndroid());
+    ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt() || !has2DTextureExt());
+    ANGLE_SKIP_TEST_IF(!hasAndroidImageNativeBufferExt() || !hasAndroidHardwareBufferSupport());
+
+    GLubyte data[4] = {7, 51, 197, 231};
+
+    // Create the Image
+    AHardwareBuffer *source;
+    EGLImageKHR image;
+    createEGLImageAndroidHardwareBufferSource(1, 1, 1, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+                                              kDefaultAHBUsage, kDefaultAttribs, {{data, 4}},
+                                              &source, &image);
+
+    // Create a texture target to bind the egl image & disable mipmapping
+    GLTexture target;
+    createEGLImageTargetTextureExternal(image, target);
+
+    // Calls On EndFrame(), with MidExecutionSetup to restore external target texture above
+    EGLDisplay display = getEGLWindow()->getDisplay();
+    EGLSurface surface = getEGLWindow()->getSurface();
+    eglSwapBuffers(display, surface);
+
+    // Create another eglImage with another associated texture
+    // Draw using the eglImage target texture created in frame 1
+    AHardwareBuffer *source2;
+    EGLImageKHR image2;
+    createEGLImageAndroidHardwareBufferSource(1, 1, 1, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+                                              kDefaultAHBUsage, kDefaultAttribs, {{data, 4}},
+                                              &source2, &image2);
+
+    // Create another texture target to bind the egl image & disable mipmapping
+    GLTexture target2;
+    createEGLImageTargetTextureExternal(image, target2);
+
+    glUseProgram(mTextureExternalProgram);
+    glBindTexture(GL_TEXTURE_EXTERNAL_OES, target);
+    glUniform1i(mTextureExternalUniformLocation, 0);
+
+    drawQuad(mTextureExternalProgram, "position", 0.5f);
+
+    // Calls On EndFrame() to save the gl calls creating external texture target2;
+    // We use this as a reference to check the gl calls we restore for GLTexture target
+    // in MidExecutionSetup
+    eglSwapBuffers(display, surface);
+
+    // Draw a quad with the GLTexture target2
+    glUseProgram(mTextureExternalProgram);
+    glBindTexture(GL_TEXTURE_EXTERNAL_OES, target2);
+    glUniform1i(mTextureExternalUniformLocation, 0);
+
+    drawQuad(mTextureExternalProgram, "position", 0.5f);
+
+    eglSwapBuffers(display, surface);
+
+    // Clean up
+    eglDestroyImageKHR(window->getDisplay(), image);
+    eglDestroyImageKHR(window->getDisplay(), image2);
+}
+
 ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(ImageTest);
 
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ImageTestES3);