// Copyright (C) 2022 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "EmulationGl.h" #include #include #include #include #include "DisplaySurfaceGl.h" #include "GLESVersionDetector.h" #include "OpenGLESDispatch/DispatchTables.h" #include "OpenGLESDispatch/EGLDispatch.h" #include "OpenGLESDispatch/GLESv2Dispatch.h" #include "OpenGLESDispatch/OpenGLDispatchLoader.h" #include "RenderThreadInfoGl.h" #include "aemu/base/misc/StringUtils.h" #include "host-common/GfxstreamFatalError.h" #include "host-common/feature_control.h" #include "host-common/logging.h" #include "host-common/opengl/misc.h" namespace gfxstream { namespace gl { namespace { static void EGLAPIENTRY EglDebugCallback(EGLenum error, const char *command, EGLint messageType, EGLLabelKHR threadLabel, EGLLabelKHR objectLabel, const char *message) { GL_LOG("command:%s message:%s", command, message); } static void GL_APIENTRY GlDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) { GL_LOG("message:%s", message); } static const GLint kGles2ContextAttribsESOrGLCompat[] = { EGL_CONTEXT_CLIENT_VERSION, 2, // EGL_NONE, // }; static const GLint kGles2ContextAttribsCoreGL[] = { EGL_CONTEXT_CLIENT_VERSION, 2, // EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, // EGL_NONE, // }; static const GLint kGles3ContextAttribsESOrGLCompat[] = { EGL_CONTEXT_CLIENT_VERSION, 3, // EGL_NONE, // }; static const GLint kGles3ContextAttribsCoreGL[] = { EGL_CONTEXT_CLIENT_VERSION, 3, // EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, // EGL_NONE, // }; static bool validateGles2Context(EGLDisplay display) { const GLint configAttribs[] = { EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE, }; EGLint numConfigs = 0; EGLConfig config; if (!s_egl.eglChooseConfig(display, configAttribs, &config, 1, &numConfigs)) { ERR("Failed to find GLES 2.x config."); return false; } if (numConfigs != 1) { ERR("Failed to find exactly 1 GLES 2.x config: found %d.", numConfigs); return false; } const EGLint surfaceAttribs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE, }; EGLSurface surface = s_egl.eglCreatePbufferSurface(display, config, surfaceAttribs); if (surface == EGL_NO_SURFACE) { ERR("Failed to create GLES 2.x pbuffer surface."); return false; } const GLint* contextAttribs = EmulationGl::getGlesMaxContextAttribs(); EGLContext context = s_egl.eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs); if (context == EGL_NO_CONTEXT) { ERR("Failed to create GLES 2.x context."); s_egl.eglDestroySurface(display, surface); return false; } if (!s_egl.eglMakeCurrent(display, surface, surface, context)) { ERR("Failed to make GLES 2.x context current."); s_egl.eglDestroySurface(display, surface); s_egl.eglDestroyContext(display, context); return false; } const char* extensions = (const char*)s_gles2.glGetString(GL_EXTENSIONS); if (extensions == nullptr) { ERR("Failed to query GLES 2.x context extensions."); s_egl.eglDestroySurface(display, surface); s_egl.eglDestroyContext(display, context); return false; } // It is rare but some drivers actually fail this... if (!s_egl.eglMakeCurrent(display, EGL_NO_CONTEXT, EGL_NO_SURFACE, EGL_NO_SURFACE)) { ERR("Failed to unbind GLES 2.x context."); s_egl.eglDestroySurface(display, surface); s_egl.eglDestroyContext(display, context); return false; } s_egl.eglDestroyContext(display, context); s_egl.eglDestroySurface(display, surface); return true; } static std::optional getEmulationEglConfig(EGLDisplay display, bool allowWindowSurface) { GLint surfaceType = EGL_PBUFFER_BIT; if (allowWindowSurface) { surfaceType |= EGL_WINDOW_BIT; } // On Linux, we need RGB888 exactly, or eglMakeCurrent will fail, // as glXMakeContextCurrent needs to match the format of the // native pixmap. constexpr const EGLint kWantedRedSize = 8; constexpr const EGLint kWantedGreenSize = 8; constexpr const EGLint kWantedBlueSize = 8; const GLint configAttribs[] = { EGL_RED_SIZE, kWantedRedSize, // EGL_GREEN_SIZE, kWantedGreenSize, // EGL_BLUE_SIZE, kWantedBlueSize, // EGL_SURFACE_TYPE, surfaceType, // EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, // EGL_NONE, // }; EGLint numConfigs = 0; s_egl.eglGetConfigs(display, nullptr, 0, &numConfigs); std::vector configs(numConfigs); EGLint numMatchedConfigs = 0; s_egl.eglChooseConfig(display, configAttribs, configs.data(), numConfigs, &numMatchedConfigs); configs.resize(numMatchedConfigs); for (EGLConfig config : configs) { EGLint foundRedSize = 0; s_egl.eglGetConfigAttrib(display, config, EGL_RED_SIZE, &foundRedSize); if (foundRedSize != kWantedRedSize) { continue; } EGLint foundGreenSize = 0; s_egl.eglGetConfigAttrib(display, config, EGL_GREEN_SIZE, &foundGreenSize); if (foundGreenSize != kWantedGreenSize) { continue; } EGLint foundBlueSize = 0; s_egl.eglGetConfigAttrib(display, config, EGL_BLUE_SIZE, &foundBlueSize); if (foundBlueSize != kWantedBlueSize) { continue; } return config; } return std::nullopt; } } // namespace std::unique_ptr EmulationGl::create(uint32_t width, uint32_t height, gfxstream::host::FeatureSet features, bool allowWindowSurface, bool egl2egl) { // Loads the glestranslator function pointers. if (!LazyLoadedEGLDispatch::get()) { ERR("Failed to load EGL dispatch."); return nullptr; } if (!LazyLoadedGLESv1Dispatch::get()) { ERR("Failed to load GLESv1 dispatch."); return nullptr; } if (!LazyLoadedGLESv2Dispatch::get()) { ERR("Failed to load GLESv2 dispatch."); return nullptr; } if (s_egl.eglUseOsEglApi) { s_egl.eglUseOsEglApi(egl2egl, EGL_FALSE); } std::unique_ptr emulationGl(new EmulationGl()); emulationGl->mFeatures = features; emulationGl->mWidth = width; emulationGl->mHeight = height; emulationGl->mEglDisplay = s_egl.eglGetDisplay(EGL_DEFAULT_DISPLAY); if (emulationGl->mEglDisplay == EGL_NO_DISPLAY) { ERR("Failed to get EGL display."); return nullptr; } GL_LOG("call eglInitialize"); if (!s_egl.eglInitialize(emulationGl->mEglDisplay, &emulationGl->mEglVersionMajor, &emulationGl->mEglVersionMinor)) { ERR("Failed to eglInitialize."); return nullptr; } if (s_egl.eglSetNativeTextureDecompressionEnabledANDROID) { s_egl.eglSetNativeTextureDecompressionEnabledANDROID( emulationGl->mEglDisplay, emulationGl->mFeatures.NativeTextureDecompression.enabled); } s_egl.eglBindAPI(EGL_OPENGL_ES_API); #ifdef ENABLE_GL_LOG if (s_egl.eglDebugMessageControlKHR) { const EGLAttrib controls[] = { EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE, EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE, EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE, EGL_DEBUG_MSG_INFO_KHR, EGL_FALSE, EGL_NONE, EGL_NONE, }; if (s_egl.eglDebugMessageControlKHR(&EglDebugCallback, controls) == EGL_SUCCESS) { GL_LOG("Successfully set eglDebugMessageControlKHR"); } else { GL_LOG("Failed to eglDebugMessageControlKHR"); } } else { GL_LOG("eglDebugMessageControlKHR not available"); } #endif emulationGl->mEglVendor = s_egl.eglQueryString(emulationGl->mEglDisplay, EGL_VENDOR); const std::string eglExtensions = s_egl.eglQueryString(emulationGl->mEglDisplay, EGL_EXTENSIONS); android::base::split(eglExtensions, " ", [&](const std::string& found) { emulationGl->mEglExtensions.insert(found); }); if (!emulationGl->hasEglExtension("EGL_KHR_gl_texture_2D_image")) { ERR("Failed to find required EGL_KHR_gl_texture_2D_image extension."); return nullptr; } emulationGl->mGlesDispatchMaxVersion = calcMaxVersionFromDispatch(emulationGl->mFeatures, emulationGl->mEglDisplay); if (s_egl.eglSetMaxGLESVersion) { // eglSetMaxGLESVersion must be called before any context binding // because it changes how we initialize the dispatcher table. s_egl.eglSetMaxGLESVersion(emulationGl->mGlesDispatchMaxVersion); } int glesVersionMajor; int glesVersionMinor; emugl::getGlesVersion(&glesVersionMajor, &glesVersionMinor); emulationGl->mGlesVersionMajor = glesVersionMajor; emulationGl->mGlesVersionMinor = glesVersionMinor; if (!validateGles2Context(emulationGl->mEglDisplay)) { ERR("Failed to validate creating GLES 2.x context."); return nullptr; } // TODO (b/207426737): Remove the Imagination-specific workaround. const bool disableFastBlit = emulationGl->mEglVendor.find("Imagination Technologies") != std::string::npos; emulationGl->mFastBlitSupported = (emulationGl->mGlesDispatchMaxVersion > GLES_DISPATCH_MAX_VERSION_2) && !disableFastBlit && (emugl::getRenderer() == SELECTED_RENDERER_HOST || emugl::getRenderer() == SELECTED_RENDERER_SWIFTSHADER_INDIRECT || emugl::getRenderer() == SELECTED_RENDERER_ANGLE_INDIRECT); auto eglConfigOpt = getEmulationEglConfig(emulationGl->mEglDisplay, allowWindowSurface); if (!eglConfigOpt) { ERR("Failed to find config for emulation GL."); return nullptr; } emulationGl->mEglConfig = *eglConfigOpt; const GLint* maxContextAttribs = getGlesMaxContextAttribs(); emulationGl->mEglContext = s_egl.eglCreateContext(emulationGl->mEglDisplay, emulationGl->mEglConfig, EGL_NO_CONTEXT, maxContextAttribs); if (emulationGl->mEglContext == EGL_NO_CONTEXT) { ERR("Failed to create context, error 0x%x.", s_egl.eglGetError()); return nullptr; } // Create another context which shares with the default context to be // used when we bind the pbuffer. This prevents switching the drawable // binding back and forth on the framebuffer context. // The main purpose of it is to solve a "blanking" behaviour we see on // on Mac platform when switching binded drawable for a context however // it is more efficient on other platforms as well. auto pbufferSurfaceGl = DisplaySurfaceGl::createPbufferSurface(emulationGl->mEglDisplay, emulationGl->mEglConfig, emulationGl->mEglContext, maxContextAttribs, /*width=*/1, /*height=*/1); if (!pbufferSurfaceGl) { ERR("Failed to create pbuffer display surface."); return nullptr; } auto* pbufferSurfaceGlPtr = pbufferSurfaceGl.get(); emulationGl->mPbufferSurface = std::make_unique( /*width=*/1, /*height=*/1, std::move(pbufferSurfaceGl)); emulationGl->mEmulatedEglConfigs = std::make_unique(emulationGl->mEglDisplay, emulationGl->mGlesDispatchMaxVersion, emulationGl->mFeatures); if (emulationGl->mEmulatedEglConfigs->empty()) { ERR("Failed to initialize emulated configs."); return nullptr; } const bool hasEsOrEs2Context = std::any_of(emulationGl->mEmulatedEglConfigs->begin(), emulationGl->mEmulatedEglConfigs->end(), [](const EmulatedEglConfig& config) { const GLint renderableType = config.getRenderableType(); return renderableType & (EGL_OPENGL_ES_BIT | EGL_OPENGL_ES2_BIT); }); if (!hasEsOrEs2Context) { ERR("Failed to find any usable guest EGL configs."); return nullptr; } RecursiveScopedContextBind contextBind(pbufferSurfaceGlPtr->getContextHelper()); if (!contextBind.isOk()) { ERR("Failed to make pbuffer context and surface current"); return nullptr; } #ifdef ENABLE_GL_LOG bool debugSetup = false; if (s_gles2.glDebugMessageCallback) { s_gles2.glEnable(GL_DEBUG_OUTPUT); s_gles2.glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); s_gles2.glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_HIGH, 0, nullptr, GL_TRUE); s_gles2.glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr, GL_TRUE); s_gles2.glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_LOW, 0, nullptr, GL_TRUE); s_gles2.glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION, 0, nullptr, GL_TRUE); s_gles2.glDebugMessageCallback(&GlDebugCallback, nullptr); debugSetup = s_gles2.glGetError() == GL_NO_ERROR; if (!debugSetup) { ERR("Failed to set up glDebugMessageCallback"); } else { GL_LOG("Successfully set up glDebugMessageCallback"); } } if (s_gles2.glDebugMessageCallbackKHR && !debugSetup) { s_gles2.glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_HIGH_KHR, 0, nullptr, GL_TRUE); s_gles2.glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_MEDIUM_KHR, 0, nullptr, GL_TRUE); s_gles2.glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_LOW_KHR, 0, nullptr, GL_TRUE); s_gles2.glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION_KHR, 0, nullptr, GL_TRUE); s_gles2.glDebugMessageCallbackKHR(&GlDebugCallback, nullptr); debugSetup = s_gles2.glGetError() == GL_NO_ERROR; if (!debugSetup) { ERR("Failed to set up glDebugMessageCallbackKHR"); } else { GL_LOG("Successfully set up glDebugMessageCallbackKHR"); } } if (!debugSetup) { GL_LOG("glDebugMessageCallback and glDebugMessageCallbackKHR not available"); } #endif emulationGl->mGlesVendor = (const char*)s_gles2.glGetString(GL_VENDOR); emulationGl->mGlesRenderer = (const char*)s_gles2.glGetString(GL_RENDERER); emulationGl->mGlesVersion = (const char*)s_gles2.glGetString(GL_VERSION); emulationGl->mGlesExtensions = (const char*)s_gles2.glGetString(GL_EXTENSIONS); s_gles2.glGetError(); GLint numDeviceUuids = 0; s_gles2.glGetIntegerv(GL_NUM_DEVICE_UUIDS_EXT, &numDeviceUuids); if (numDeviceUuids == 1) { GlesUuid uuid{}; s_gles2.glGetUnsignedBytei_vEXT(GL_DEVICE_UUID_EXT, 0, uuid.data()); emulationGl->mGlesDeviceUuid = uuid; } emulationGl->mGlesVulkanInteropSupported = false; if (s_egl.eglQueryVulkanInteropSupportANDROID) { emulationGl->mGlesVulkanInteropSupported = s_egl.eglQueryVulkanInteropSupportANDROID(); } emulationGl->mTextureDraw = std::make_unique(); if (!emulationGl->mTextureDraw) { ERR("Failed to initialize TextureDraw."); return nullptr; } emulationGl->mCompositorGl = std::make_unique(emulationGl->mTextureDraw.get()); emulationGl->mDisplayGl = std::make_unique(emulationGl->mTextureDraw.get()); { auto surface1 = DisplaySurfaceGl::createPbufferSurface(emulationGl->mEglDisplay, emulationGl->mEglConfig, emulationGl->mEglContext, getGlesMaxContextAttribs(), /*width=*/1, /*height=*/1); if (!surface1) { ERR("Failed to create pbuffer surface for ReadbackWorkerGl."); return nullptr; } auto surface2 = DisplaySurfaceGl::createPbufferSurface(emulationGl->mEglDisplay, emulationGl->mEglConfig, emulationGl->mEglContext, getGlesMaxContextAttribs(), /*width=*/1, /*height=*/1); if (!surface2) { ERR("Failed to create pbuffer surface for ReadbackWorkerGl."); return nullptr; } emulationGl->mReadbackWorkerGl = std::make_unique(std::move(surface1), std::move(surface2)); } return emulationGl; } EmulationGl::~EmulationGl() { if (mPbufferSurface) { // TODO(b/267349580): remove after Mac issue fixed. mTextureDraw.release(); // const auto* displaySurfaceGl = // reinterpret_cast(mPbufferSurface->getImpl()); // RecursiveScopedContextBind contextBind(displaySurfaceGl->getContextHelper()); // if (contextBind.isOk()) { // mTextureDraw.reset(); // } else { // ERR("Failed to bind context for destroying TextureDraw."); // } } if (mEglDisplay != EGL_NO_DISPLAY) { s_egl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (mEglContext != EGL_NO_CONTEXT) { s_egl.eglDestroyContext(mEglDisplay, mEglContext); mEglContext = EGL_NO_CONTEXT; } mEglDisplay = EGL_NO_DISPLAY; } } std::unique_ptr EmulationGl::createFakeWindowSurface() { return std::make_unique( mWidth, mHeight, std::move(DisplaySurfaceGl::createPbufferSurface( mEglDisplay, mEglConfig, mEglContext, getGlesMaxContextAttribs(), mWidth, mHeight))); } /*static*/ const GLint* EmulationGl::getGlesMaxContextAttribs() { int glesMaj, glesMin; emugl::getGlesVersion(&glesMaj, &glesMin); if (shouldEnableCoreProfile()) { if (glesMaj == 2) { return kGles2ContextAttribsCoreGL; } else { return kGles3ContextAttribsCoreGL; } } if (glesMaj == 2) { return kGles2ContextAttribsESOrGLCompat; } else { return kGles3ContextAttribsESOrGLCompat; } } const EGLDispatch* EmulationGl::getEglDispatch() { return &s_egl; } const GLESv2Dispatch* EmulationGl::getGles2Dispatch() { return &s_gles2; } GLESDispatchMaxVersion EmulationGl::getGlesMaxDispatchVersion() const { return mGlesDispatchMaxVersion; } bool EmulationGl::hasEglExtension(const std::string& ext) const { return mEglExtensions.find(ext) != mEglExtensions.end(); } void EmulationGl::getEglVersion(EGLint* major, EGLint* minor) const { if (major) { *major = mEglVersionMajor; } if (minor) { *minor = mEglVersionMinor; } } void EmulationGl::getGlesVersion(GLint* major, GLint* minor) const { if (major) { *major = mGlesVersionMajor; } if (minor) { *minor = mGlesVersionMinor; } } bool EmulationGl::isMesa() const { return mGlesVersion.find("Mesa") != std::string::npos; } bool EmulationGl::isFastBlitSupported() const { return mFastBlitSupported; } void EmulationGl::disableFastBlitForTesting() { mFastBlitSupported = false; } bool EmulationGl::isAsyncReadbackSupported() const { return mGlesVersionMajor > 2; } std::unique_ptr EmulationGl::createWindowSurface( uint32_t width, uint32_t height, EGLNativeWindowType window) { auto surfaceGl = DisplaySurfaceGl::createWindowSurface(mEglDisplay, mEglConfig, mEglContext, getGlesMaxContextAttribs(), window); if (!surfaceGl) { ERR("Failed to create DisplaySurfaceGl."); return nullptr; } return std::make_unique(width, height, std::move(surfaceGl)); } ContextHelper* EmulationGl::getColorBufferContextHelper() { if (!mPbufferSurface) { return nullptr; } const auto* surfaceGl = static_cast(mPbufferSurface->getImpl()); return surfaceGl->getContextHelper(); } std::unique_ptr EmulationGl::createBuffer(uint64_t size, HandleType handle) { return BufferGl::create(size, handle, getColorBufferContextHelper()); } std::unique_ptr EmulationGl::loadBuffer(android::base::Stream* stream) { return BufferGl::onLoad(stream, getColorBufferContextHelper()); } std::unique_ptr EmulationGl::createColorBuffer(uint32_t width, uint32_t height, GLenum internalFormat, FrameworkFormat frameworkFormat, HandleType handle) { return ColorBufferGl::create(mEglDisplay, width, height, internalFormat, frameworkFormat, handle, getColorBufferContextHelper(), mTextureDraw.get(), isFastBlitSupported(), mFeatures); } std::unique_ptr EmulationGl::loadColorBuffer(android::base::Stream* stream) { return ColorBufferGl::onLoad(stream, mEglDisplay, getColorBufferContextHelper(), mTextureDraw.get(), isFastBlitSupported(), mFeatures); } std::unique_ptr EmulationGl::createEmulatedEglContext( uint32_t emulatedEglConfigIndex, const EmulatedEglContext* sharedContext, GLESApi api, HandleType handle) { if (!mEmulatedEglConfigs) { ERR("EmulatedEglConfigs unavailable."); return nullptr; } const EmulatedEglConfig* emulatedEglConfig = mEmulatedEglConfigs->get(emulatedEglConfigIndex); if (!emulatedEglConfig) { ERR("Failed to find emulated EGL config %d", emulatedEglConfigIndex); return nullptr; } EGLConfig config = emulatedEglConfig->getHostEglConfig(); EGLContext share = sharedContext ? sharedContext->getEGLContext() : EGL_NO_CONTEXT; return EmulatedEglContext::create(mEglDisplay, config, share, handle, api); } std::unique_ptr EmulationGl::loadEmulatedEglContext( android::base::Stream* stream) { return EmulatedEglContext::onLoad(stream, mEglDisplay); } std::unique_ptr EmulationGl::createEmulatedEglFenceSync( EGLenum type, int destroyWhenSignaled) { const bool hasNativeFence = type == EGL_SYNC_NATIVE_FENCE_ANDROID; return EmulatedEglFenceSync::create(mEglDisplay, hasNativeFence, destroyWhenSignaled); } std::unique_ptr EmulationGl::createEmulatedEglImage( EmulatedEglContext* context, EGLenum target, EGLClientBuffer buffer) { EGLContext eglContext = context ? context->getEGLContext() : EGL_NO_CONTEXT; return EmulatedEglImage::create(mEglDisplay, eglContext, target, buffer); } std::unique_ptr EmulationGl::createEmulatedEglWindowSurface( uint32_t emulatedConfigIndex, uint32_t width, uint32_t height, HandleType handle) { if (!mEmulatedEglConfigs) { ERR("EmulatedEglConfigs unavailable."); return nullptr; } const EmulatedEglConfig* emulatedEglConfig = mEmulatedEglConfigs->get(emulatedConfigIndex); if (!emulatedEglConfig) { ERR("Failed to find emulated EGL config %d", emulatedConfigIndex); return nullptr; } EGLConfig config = emulatedEglConfig->getHostEglConfig(); return EmulatedEglWindowSurface::create(mEglDisplay, config, width, height, handle); } std::unique_ptr EmulationGl::loadEmulatedEglWindowSurface( android::base::Stream* stream, const ColorBufferMap& colorBuffers, const EmulatedEglContextMap& contexts) { return EmulatedEglWindowSurface::onLoad(stream, mEglDisplay, colorBuffers, contexts); } } // namespace gl } // namespace gfxstream