Build CEF directly from sources was less complicated than I anticipated: I meanly followed the instructions from the Master Build Quick Start guide. Also taking some notes from the official Branches and Building page.
Initial steps:
d:\Projects\CEFBuild\automate d:\Projects\CEFBuild\chromium_git
d:\Projects\CEFBuild\depot_tools
update_depot_tools.bat
In the end, I simply created a few helper batch files, placed in the chromium_git folder (note that my build root for CEF is: D:\Projects\CEFBuild):
set CEF_USE_GN=1 REM set GN_DEFINES=is_win_fastlink=true set GN_ARGUMENTS=--ide=vs2015 --sln=cef --filters=//cef/* python ..\automate\automate-git.py --download-dir=D:\Projects\CEFBuild\chromium_git --depot-tools-dir=D:\Projects\CEFBuild\depot_tools --no-distrib --no-build --branch=3163
cd D:\Projects\CEFBuild\chromium_git\chromium\src\cef set CEF_USE_GN=1 REM set GN_DEFINES=is_win_fastlink=true set GN_ARGUMENTS=--ide=vs2015 --sln=cef --filters=//cef/* call cef_create_projects.bat
cd D:\Projects\CEFBuild\chromium_git\chromium\src ninja -C out\Release_GN_x64 cef
REM cf. https://bitbucket.org/chromiumembedded/cef/wiki/BranchesAndBuilding.md set CEF_VCVARS="D:\Apps\VisualStudio2015_CE\VC\bin\amd64\vcvars64.bat" cd D:\Projects\CEFBuild\chromium_git\chromium\src\cef\tools ./make_distrib.bat --ninja-build --x64-build --allow-partial
One thing to note though is: by default on Windows the build system will attempt to build both x86 and x64 binaries. Yet, some dependencies where not available on my computer to build the x86 version of the library, so I had to manually disable that (before running create_cef_project.bat AFAIR) by tweaking the line of D:\Projects\CEFBuild\chromium_git\chromium\src\cef\tools\gn_args.py to only request the build of the x64 version:
if platform == 'linux': use_sysroot = GetArgValue(args, 'use_sysroot') if use_sysroot: # Only generate configurations for sysroots that have been installed. for cpu in ('x86', 'x64', 'arm'): if LinuxSysrootExists(cpu): supported_cpus.append(cpu) else: msg('Not generating %s configuration due to missing sysroot directory' % cpu) else: supported_cpus = ['x64'] elif platform == 'windows': # supported_cpus = ['x86', 'x64'] supported_cpus = ['x64'] elif platform == 'macosx': supported_cpus = ['x64'] else: raise Exception('Unsupported platform')
But now I realize there might be a more appropriate way to handle this by specifying the target_cpu command line argument somewhere maybe ? (to be investigated).
Note: Currently experimenting with CEF branch 3163
The logic used the copy the headers during the make_distrib step is found in make_distrib.py around line 525:
# transfer common include files transfer_gypi_files(cef_dir, cef_paths2['includes_common'], \ 'include/', include_dir, options.quiet)
We need to be able to retrieve some debug infos from the cef library in an integrated way: so we need to be able to assign a log handler inside the library, and then use this handle to output log messages if valid.
Can we simply add a new class/method inside the source base ? Let's try that: building the LogHandler class and trying to use it in CefRenderWidgetHostViewOSR:
[3/7] LINK(DLL) libcef.dll libcef.dll.lib libcef.dll.pdb FAILED: libcef.dll libcef.dll.lib libcef.dll.pdb D:/Projects/CEFBuild/depot_tools/win_tools-2_7_6_bin/python/bin/python.exe ../../build/toolchain/win/tool_wrapper.py link-wrapper environment.x64 False link.exe /nologo /IMPLIB:./libcef.dll.lib /DLL /OUT:./libcef.dll /PDB:./libcef.dll.pdb @./libcef.dll.rsp libcef_static.lib(render_widget_host_view_osr.obj) : error LNK2019: unresolved external symbol "void __cdecl cef::handleLogMessage(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const &)" (?handleLogMessage@cef@@YAXAEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) referenced in function "private: void __cdecl CefCopyFrameGenerator::CopyFromCompositingSurfaceHasResult(class gfx::Rect const &,class std::unique_ptr<class cc::CopyOutputResult,struct std::default_delete<class cc::CopyOutputResult> >)" (?CopyFromCompositingSurfaceHasResult@CefCopyFrameGenerator@@AEAAXAEBVRect@gfx@@V?$unique_ptr@VCopyOutputResult@cc@@U?$default_delete@VCopyOutputResult@cc@@@std@@@std@@@Z) ./libcef.dll : fatal error LNK1120: 1 unresolved externals
Now that we have a LogHandler support we should assign it on start of our software. How would we do that ?
CEFManager.cpp.obj : error LNK2019: unresolved external symbol "void __cdecl cef::setLogHandler(class cef::CefLogHandler *)" (?setLogHandler@cef@@YAXPEAVCefLogHandler@1@@Z) referenced in function "public: void __cdecl cef::CEFManager::initialize(class cef::CEFManager::Traits &)" (?initialize@CEFManager@cef@@QEAAXAEAVTraits@12@@Z) scCEF.dll : fatal error LNK1120: 1 unresolved externals
So we need to figure out what we are missing here and how to make our function available from the libcef library to the outside world.
/// // This function should be called on the main application thread to assign // a log handler to the CEF module (used for investigations) /// CEF_EXPORT void cef_set_log_handler(struct cef::CefLogHandler* handler);
CEF_EXPORT void cef_set_log_handler(cef::CefLogHandler* handler) { cef::setLogHandler(handler); }
void CopyFromCompositingSurfaceHasResult( const gfx::Rect& damage_rect, std::unique_ptr<cc::CopyOutputResult> result) { if (result->IsEmpty() || result->size().IsEmpty() || !view_->render_widget_host()) { OnCopyFrameCaptureFailure(damage_rect); return; } if (result->HasTexture()) { DEBUG_MSG("NervCEF: CompositingSurface has texture!"); PrepareTextureCopyOutputResult(damage_rect, std::move(result)); return; } DCHECK(result->HasBitmap()); DEBUG_MSG("NervCEF: CompositingSurface has bitmap!"); PrepareBitmapCopyOutputResult(damage_rect, std::move(result)); }
⇒ We expect to get one or the other of those 2 debug outputs (or both ?)… well, instead we got a crash but it doesn't seem to be related to what we are changing here:
2017-09-15T10:19:33.735898 [Debug] Calling CefInitialize... [0915/111933.735:FATAL:libcef_dll_wrapper.cc(209)] Check failed: false.
CEF_GLOBAL bool CefInitialize(const CefMainArgs& args, const CefSettings& settings, CefRefPtr<CefApp> application, void* windows_sandbox_info) { const char* api_hash = cef_api_hash(0); if (strcmp(api_hash, CEF_API_HASH_PLATFORM)) { // The libcef API hash does not match the current header API hash. NOTREACHED(); return false; } // More stuff here }
chmod +x *.dll chmod +x *.exe
//The API hash is created by analyzing CEF header files for C API type // definitions. The hash value will change when header files are modified // in a way that may cause binary incompatibility with other builds. The // universal hash value will change if any platform is affected whereas the // platform hash values will change only if that particular platform is // affected.
Okay, so we are probably not too far from success, BUT we should avoid making changes to the include/capi folder, thus, not include our new exported function into cef_app_capi.h ⇒ This doesn't matter much: let's just defined it in a separated file!
⇒ Great! We now have the expected outputs:
2017-09-17T13:37:32.881686 [Debug] NervCEF: CompositingSurface has texture! 2017-09-17T13:37:33.056642 [Debug] NervCEF: CompositingSurface has texture! 2017-09-17T13:37:33.377407 [Debug] NervCEF: CompositingSurface has texture!
Now that we can output some debug outputs if need, let's try to clarify further the offscreen image copy pipeline.
First, let's check what kind of messages we got in the previous run test:
⇒ It all comes down to the method: viz::GLHelper::CropScaleReadbackAndCleanMailbox(…)
Found the following description for this method in components/viz/common/gl_helper.h:
// Copies the block of pixels specified with |src_subrect| from |src_mailbox|, // scales it to |dst_size|, and writes it into |out|. // |src_size| is the size of |src_mailbox|. The result is in |out_color_type| // format and is potentially flipped vertically to make it a correct image // representation. |callback| is invoked with the copy result when the copy // operation has completed. // Note that the texture bound to src_mailbox will have the min/mag filter set // to GL_LINEAR and wrap_s/t set to CLAMP_TO_EDGE in this call. src_mailbox is // assumed to be GL_TEXTURE_2D.
So our parameter “src_mailbox” is assumed to be a GL_TEXTURE_2D ⇒ sounds good for us! Now checking the method implementation in components/viz/common/gl_helper.cc:
void GLHelper::CropScaleReadbackAndCleanMailbox( const gpu::Mailbox& src_mailbox, const gpu::SyncToken& sync_token, const gfx::Size& src_size, const gfx::Rect& src_subrect, const gfx::Size& dst_size, unsigned char* out, const SkColorType out_color_type, const base::Callback<void(bool)>& callback, GLHelper::ScalerQuality quality) { GLuint mailbox_texture = ConsumeMailboxToTexture(src_mailbox, sync_token); CropScaleReadbackAndCleanTexture(mailbox_texture, src_size, src_subrect, dst_size, out, out_color_type, callback, quality); gl_->DeleteTextures(1, &mailbox_texture);
Checking the implementation oc ComsumeMailboxToTexture:
GLuint GLHelper::ConsumeMailboxToTexture(const gpu::Mailbox& mailbox, const gpu::SyncToken& sync_token) { if (mailbox.IsZero()) return 0; if (sync_token.HasData()) WaitSyncToken(sync_token); GLuint texture = gl_->CreateAndConsumeTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name); return texture; }
And finally we find for CreateAndConsumeTextureCHROMIUM (inside gpu/command_buffer/client/gles2_implementation.cc):
GLuint GLES2Implementation::CreateAndConsumeTextureCHROMIUM( GLenum target, const GLbyte* data) { GPU_CLIENT_SINGLE_THREAD_CHECK(); GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glCreateAndConsumeTextureCHROMIUM(" << static_cast<const void*>(data) << ")"); const Mailbox& mailbox = *reinterpret_cast<const Mailbox*>(data); DCHECK(mailbox.Verify()) << "CreateAndConsumeTextureCHROMIUM was passed a " "mailbox that was not generated by " "GenMailboxCHROMIUM."; GLuint client_id; GetIdHandler(SharedIdNamespaces::kTextures)->MakeIds(this, 0, 1, &client_id); helper_->CreateAndConsumeTextureINTERNALImmediate(target, client_id, data); if (share_group_->bind_generates_resource()) helper_->CommandBufferHelper::Flush(); CheckGLError(); return client_id; }
So it sounds like everytime we request a copy we start with creating a GL texture, then we crop/scale/readback on CPU memory, and finally we destroy the texture.
We continue following the CreateAndConsumeTextureINTERNALImmediate method (found in gles2_cmd_helper_autogen.h):
void CreateAndConsumeTextureINTERNALImmediate(GLenum target, GLuint texture, const GLbyte* mailbox) { const uint32_t size = gles2::cmds::CreateAndConsumeTextureINTERNALImmediate::ComputeSize(); gles2::cmds::CreateAndConsumeTextureINTERNALImmediate* c = GetImmediateCmdSpaceTotalSize< gles2::cmds::CreateAndConsumeTextureINTERNALImmediate>(size); if (c) { c->Init(target, texture, mailbox); } }
And from there, we call this init method (found in gles2_cmd_format_autogen.h):
void Init(GLenum _target, GLuint _texture, const GLbyte* _mailbox) { SetHeader(); target = _target; texture = _texture; memcpy(ImmediateDataAddress(this), _mailbox, ComputeDataSize()); }
So: we create a GL texture Id (with the call to MakeIds(…)), then we send a command to copy the mailbox data into our texture. So, I think when CreateAndConsumeTextureCHROMIUM() returns we should have a valid GL texture ID, which contains the imagery data we want to retrieve ?
Then the next question is: How can we retrieve this data into our own texture object on the GPU from a different GL or DirectX context ?
Reading a few related pages:
⇒ Those links do not seem very optimistic, but then I found the GL_NV_copy_image extension page, which states that:
This extension enables efficient image data transfer between image objects (i.e. textures and renderbuffers) without the need to bind the objects or otherwise configure the rendering pipeline. The WGL and GLX versions allow copying between images in different contexts, even if those contexts are in different sharelists or even on different physical devices.
This sounds more interesting already. But then we have the context specification issue in wglCopyImageSubDataNV and glXCopyImageSubDataNV: we need to be able to provide the source/dest contexts as HGLRC or GLXContext objects.
So let's assume for a moment that this function is available (ie. we are using a recent nvidia GPU), and that we are on windows (ie. we are considering the HGLRC contexts): how could we retrieve the source context ?
eglCreateContext creates an EGL rendering context for the current rendering API (as set with eglBindAPI) and returns a handle to the context. The context can then be used to render into an EGL drawing surface. If eglCreateContext fails to create a rendering context, EGL_NO_CONTEXT is returned. If share_context is not EGL_NO_CONTEXT, then all shareable data in the context (as defined by the client API specification for the current rendering API) are shared by context share_context, all other contexts share_context already shares with, and the newly created context. An arbitrary number of rendering contexts can share data. However, all rendering contexts that share data must themselves exist in the same address space. Two rendering contexts share an address space if both are owned by a single process.
Found this article which sounds promizing: https://github.com/Microsoft/angle/wiki/Interop-with-other-DirectX-code
⇒ Are we using “ANGLE” in CEF ? According to the previous forum post YES. So the rational here should be applicable: we should be able to:
This might all just sound like a plan …
So what about the first accelerated paint implementation mentioned in this forum post ?
⇒ Not understanding much from this patch. But it seems to rely on the OpenGL API. I don't think we can really apply the logic available here in the new Chromium source code base. So let's instead focus on the DirectX/Angle interactions mentioned above.
Now trying to figure out where we create a GLSurfaceEGL class instance… Searching in all .cc files with:
$ grep -rnw --include \*.cc . -e 'GLSurfaceEGL' ./components/exo/wayland/clients/client_base.cc:279: if (gl::GLSurfaceEGL::HasEGLExtension("EGL_EXT_image_flush_external") || ./components/exo/wayland/clients/client_base.cc:280: gl::GLSurfaceEGL::HasEGLExtension("EGL_ARM_implicit_external_sync")) { ./components/exo/wayland/clients/client_base.cc:283: if (gl::GLSurfaceEGL::HasEGLExtension("EGL_ANDROID_native_fence_sync")) { ./gpu/command_buffer/service/texture_definition.cc:150: EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay(); ./gpu/ipc/service/direct_composition_child_surface_win.cc:45: : gl::GLSurfaceEGL(), ./gpu/ipc/service/direct_composition_surface_win.cc:97: if (!gl::GLSurfaceEGL::IsDirectCompositionSupported()) ./gpu/ipc/service/direct_composition_surface_win.cc:1005: : gl::GLSurfaceEGL(), ./gpu/ipc/service/gpu_init.cc:110: gl::GLSurfaceEGL::IsDirectCompositionSupported() && ./gpu/ipc/service/image_transport_surface_win.cc:54: if (gl::GLSurfaceEGL::IsDirectCompositionSupported()) { # (...) => more lines here.
scoped_refptr<NativeImageBufferEGL> NativeImageBufferEGL::Create( GLuint texture_id) { EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay(); EGLContext egl_context = eglGetCurrentContext(); DCHECK_NE(EGL_NO_CONTEXT, egl_context); DCHECK_NE(EGL_NO_DISPLAY, egl_display); DCHECK(glIsTexture(texture_id)); DCHECK(gl::g_driver_egl.ext.b_EGL_KHR_image_base && gl::g_driver_egl.ext.b_EGL_KHR_gl_texture_2D_image && gl::g_current_gl_driver->ext.b_GL_OES_EGL_image); const EGLint egl_attrib_list[] = { EGL_GL_TEXTURE_LEVEL_KHR, 0, EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE}; EGLClientBuffer egl_buffer = reinterpret_cast<EGLClientBuffer>(texture_id); EGLenum egl_target = EGL_GL_TEXTURE_2D_KHR; EGLImageKHR egl_image = eglCreateImageKHR( egl_display, egl_context, egl_target, egl_buffer, egl_attrib_list); if (egl_image == EGL_NO_IMAGE_KHR) { LOG(ERROR) << "eglCreateImageKHR for cross-thread sharing failed: 0x" << std::hex << eglGetError(); return NULL; } return new NativeImageBufferEGL(egl_display, egl_image); }
On the client side, it now seems relatively clear how the various GL commands are “posted”, but now we need to figure out how those commands are handled on the server side.
bool DirectCompositionChildSurfaceWin::Initialize(gl::GLSurfaceFormat format) { d3d11_device_ = gl::QueryD3D11DeviceObjectFromANGLE(); dcomp_device_ = gl::QueryDirectCompositionDevice(d3d11_device_); if (!dcomp_device_) return false; // More code here }
⇒ Focusing on the initial question: trying to find where we use the command ID kGenTexturesImmediate ?
So it seems the Command ID is not how we retrieve the GenTexturesImmediate of command on the server/host side ?
error::Error GLES2DecoderImpl::HandleGenTexturesImmediate( uint32_t immediate_data_size, const volatile void* cmd_data) { const volatile gles2::cmds::GenTexturesImmediate& c = *static_cast<const volatile gles2::cmds::GenTexturesImmediate*>(cmd_data); GLsizei n = static_cast<GLsizei>(c.n); uint32_t data_size; if (!SafeMultiplyUint32(n, sizeof(GLuint), &data_size)) { return error::kOutOfBounds; } volatile GLuint* textures = GetImmediateDataAs<volatile GLuint*>(c, data_size, immediate_data_size); if (textures == NULL) { return error::kOutOfBounds; } auto textures_copy = base::MakeUnique<GLuint[]>(n); GLuint* textures_safe = textures_copy.get(); std::copy(textures, textures + n, textures_safe); if (!CheckUniqueAndNonNullIds(n, textures_safe) || !GenTexturesHelper(n, textures_safe)) { return error::kInvalidArguments; } return error::kNoError; }
⇒ From there, the GenTexturesHelper(…) method will call glGenTextures():
bool GLES2DecoderImpl::GenTexturesHelper(GLsizei n, const GLuint* client_ids) { for (GLsizei ii = 0; ii < n; ++ii) { if (GetTexture(client_ids[ii])) { return false; } } std::unique_ptr<GLuint[]> service_ids(new GLuint[n]); glGenTextures(n, service_ids.get()); for (GLsizei ii = 0; ii < n; ++ii) { CreateTexture(client_ids[ii], service_ids[ii]); } return true; }
So this is where we call the gl functions: in the GLES2Decoder. Yet, how do we reach the HandleGenTexturesImmediate method ?
GLES2Decoder class cannot be instantiated because is is abstract. Yet we need to figure out what are all the possible derived classes and where they are instantiated now:
GLES2Decoder* GLES2Decoder::Create( GLES2DecoderClient* client, CommandBufferServiceBase* command_buffer_service, ContextGroup* group) { if (group->gpu_preferences().use_passthrough_cmd_decoder) { return new GLES2DecoderPassthroughImpl(client, command_buffer_service, group); } return new GLES2DecoderImpl(client, command_buffer_service, group); }
This all still doesn't tell us where HandleGenTexturesImmediate is effectively called… The method is not available to any public interface so it should be used only “internally” ?
The GLES2Decoder instance is create with a CommandBufferService object.
⇒ Found information on how to add a command from this page
Found the CommandInfo struct in gles2_cmd_decoder.cc:
// A struct to hold info about each command. struct CommandInfo { CmdHandler cmd_handler; uint8_t arg_flags; // How to handle the arguments for this command uint8_t cmd_flags; // How to handle this command uint16_t arg_count; // How many arguments are expected for this command. }; // A table of CommandInfo for all the commands. static const CommandInfo command_info[kNumCommands - kFirstGLES2Command];
OK, so now we need to figure out where this array is filled, and where we call the cmd_handler function!
const GLES2DecoderImpl::CommandInfo GLES2DecoderImpl::command_info[] = { #define GLES2_CMD_OP(name) \ { \ &GLES2DecoderImpl::Handle##name, cmds::name::kArgFlags, \ cmds::name::cmd_flags, \ sizeof(cmds::name) / sizeof(CommandBufferEntry) - 1, \ } \ , /* NOLINT */ GLES2_COMMAND_LIST(GLES2_CMD_OP) #undef GLES2_CMD_OP };
As mentioned above, it seems to be possible to have ANGLE write to ashared DirectX texture. So in our case we would need to follow those steps:
We should already have the required support functions to do this ?
struct SharedRenderSurfaceManager { virtual void* getShareHandle(CefRenderHandler* handler) = 0; }; CEF_EXPORT void cef_set_shared_render_surface_manager(SharedRenderSurfaceManager* manager);
Then we can provide our own implementation of the SharedRenderSurfaceManager class on CEF start, just like with the LogHandler class above.
Where do we use the getShareHandle() then ?
CefRefPtr<CefRenderHandler> handler = browser_impl_->GetClient()->GetRenderHandler();
Then, we need to create an EGL surface based on this handle:
EGLSurface surface = EGL_NO_SURFACE; EGLint pBufferAttributes[] = { EGL_WIDTH, width, EGL_HEIGHT, height, EGL_TEXTURE_TARGET, EGL_TEXTURE_2D, EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA, EGL_NONE }; surface = eglCreatePbufferFromClientBuffer(mEglDisplay, EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE, sharedHandle, mEglConfig, pBufferAttributes); if (surface == EGL_NO_SURFACE) { // error handling code }
⇒ Any chance we could execute the previous code in the CEF context ?
viz::GLHelper* GpuProcessTransportFactory::GetGLHelper() { if (!gl_helper_ && !per_compositor_data_.empty()) { scoped_refptr<ContextProvider> provider = SharedMainThreadContextProvider(); if (provider.get()) gl_helper_.reset( new viz::GLHelper(provider->ContextGL(), provider->ContextSupport())); } return gl_helper_.get(); }
gpu::gles2::GLES2Interface* ContextProviderCommandBuffer::ContextGL() { DCHECK(bind_succeeded_); DCHECK(context_thread_checker_.CalledOnValidThread()); if (trace_impl_) return trace_impl_.get(); return gles2_impl_.get(); }
Once we have a valid share handle for our DirectX texture, then each time we have to refresh the offscreen rendered image, we could send a command to the GPUProcess, to request rendering from a given GL texture on our shared DX texture. Something like: copyOnDXSharedSurface(uint glTextureID, void* shareHandle).
Before we can copy to our shared DirectX surface, we need to allocate the required resources for it: this can be done on the GPU process itself directly: if when we handle the copy, if the required resources for a given share handle are not found, then we create them.
But then we have the question of the resources deallocation: when we are done rendering with a given browser, we should release those resources allocated on the GPU Process. For instance with a command such as: releaseDXSharedSurfaceResources(void* shareHandle).
During development, we will have to get some outputs from the GPU Process itself to ensure everything is happening as expected. How can we get those outputs ?
Creating the nvLogger helper class, and trying to use it to log messages to file when creating the GLES command decoder:
[6/13] LINK ui.service.exe ui.service.exe.pdb FAILED: ui.service.exe ui.service.exe.pdb D:/Projects/CEFBuild/depot_tools/win_tools-2_7_6_bin/python/bin/python.exe ../../build/toolchain/win/tool_wrapper.py link-wrapper environment.x64 False link.exe /nologo /OUT:./ui.service.exe /PDB:./ui.service.exe.pdb @./ui.service.exe.rsp service_sources.lib(gles2_cmd_decoder.obj) : error LNK2019: unresolved external symbol "void __cdecl cef::nvLOG(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const &)" (?nvLOG@cef@@YAXAEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) referenced in function "public: static class gpu::gles2::GLES2Decoder * __cdecl gpu::gles2::GLES2Decoder::Create(class gpu::gles2::GLES2DecoderClient *,class gpu::CommandBufferServiceBase *,class gpu::gles2::ContextGroup *)" (?Create@GLES2Decoder@gles2@gpu@@SAPEAV123@PEAVGLES2DecoderClient@23@PEAVCommandBufferServiceBase@3@PEAVContextGroup@23@@Z) ./ui.service.exe : fatal error LNK1120: 1 unresolved externals
Running the target project with this new version of CEF we have a cef_nv.log file generated! But the content is not what we expected:
nvLogger initialized.Destroying nvLogger.
nvLogger initialized.Creating GLES2DecoderImpl instance.Creating GLES2DecoderImpl instance.Creating GLES2DecoderImpl instance.Creating GLES2DecoderImpl instance.
nvLogger initialized. Creating GLES2DecoderImpl instance. Creating GLES2DecoderImpl instance. Creating GLES2DecoderImpl instance. Creating GLES2DecoderImpl instance. Creating GLES2DecoderImpl instance. Creating GLES2DecoderImpl instance. Creating GLES2DecoderImpl instance. Creating GLES2DecoderImpl instance. Creating GLES2DecoderImpl instance. Creating GLES2DecoderImpl instance. Creating GLES2DecoderImpl instance.
What would be interesting to add from that point now si the process ID / thread id / and log time for each message in that file.
⇒ OK, updated the code, and now we get those results:
2017-09-26 13:53:13.473 UTC [20408]{16984}: nvLogger initialized. 2017-09-26 13:53:13.473 UTC [20408]{16984}: Creating GLES2DecoderImpl instance. 2017-09-26 13:53:13.479 UTC [20408]{16984}: Creating GLES2DecoderImpl instance. 2017-09-26 13:53:13.482 UTC [20408]{16984}: Creating GLES2DecoderImpl instance. 2017-09-26 13:53:13.484 UTC [20408]{16984}: Creating GLES2DecoderImpl instance. 2017-09-26 13:53:32.438 UTC [20408]{16984}: Creating GLES2DecoderImpl instance. 2017-09-26 13:53:34.164 UTC [20408]{16984}: Creating GLES2DecoderImpl instance. 2017-09-26 13:53:34.170 UTC [20408]{16984}: Creating GLES2DecoderImpl instance. 2017-09-26 13:53:36.405 UTC [20408]{16984}: Creating GLES2DecoderImpl instance. 2017-09-26 13:53:36.443 UTC [20408]{16984}: Creating GLES2DecoderImpl instance. 2017-09-26 13:53:36.448 UTC [20408]{16984}: Creating GLES2DecoderImpl instance. 2017-09-26 13:53:37.010 UTC [20408]{16984}: Creating GLES2DecoderImpl instance.
So it seems all GLES2Decoder instances are created from the same process id (expected) and from the same thread: so this all good for us.
/** * Check if shared handle should be used. */ virtual bool UseSharedHandle() const { return false; } /** * Retrieve the shared handle if applicable. */ virtual void* GetSharedHandle() const { return nullptr; }
../../cef/tests/cefclient/browser/browser_window_osr_win.cc(19): error C2259: 'client::ClientHandlerOsr': cannot instantiate abstract class ../../cef/tests/cefclient/browser/browser_window_osr_win.cc(19): note: due to following members: ../../cef/tests/cefclient/browser/browser_window_osr_win.cc(19): note: 'bool CefRenderHandler::UseSharedHandle(void) const': is abstract D:\Projects\CEFBuild\chromium_git\chromium\src\cef\include/cef_render_handler.h(64): note: see declaration of 'CefRenderHandler::UseSharedHandle' ../../cef/tests/cefclient/browser/browser_window_osr_win.cc(19): note: 'void *CefRenderHandler::GetSharedHandle(void) const': is abstract D:\Projects\CEFBuild\chromium_git\chromium\src\cef\include/cef_render_handler.h(69): note: see declaration of 'CefRenderHandler::GetSharedHandle'
virtual bool UseSharedHandle() const { return false; } virtual void* GetSharedHandle() const { return nullptr; }
D:\Projects\CEFBuild\chromium_git\chromium\src\cef\libcef_dll/ctocpp/ctocpp_ref_counted.h(110): error C2259: 'CefRenderHandlerCToCpp': cannot instantiate abstract class
translator.bat
/*--cef()--*/
/// // Check if shared handle should be used with this render handler. /// /*--cef()--*/ virtual bool UseSharedHandle() { return false; } /// // Return the shared handle for this renderhandler. If no shared handle // is available then null is returned. /// /*--cef()--*/ virtual void* GetSharedHandle() { return nullptr; }
bool CefRenderHandlerCToCpp::UseSharedHandle() const { cef_render_handler_t* _struct = GetStruct(); if (CEF_MEMBER_MISSING(_struct, use_shared_handle)) return false; // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING // Execute int _retval = _struct->use_shared_handle(_struct); // Return type: bool return _retval ? true : false; }
void* CefRenderHandlerCToCpp::GetSharedHandle() const { cef_render_handler_t* _struct = GetStruct(); if (CEF_MEMBER_MISSING(_struct, get_shared_handle)) return NULL; void* handle = _struct->get_shared_handle(_struct); return handle; }
void* CEF_CALLBACK render_handler_get_shared_handle(const struct _cef_render_handler_t* self) { DCHECK(self); if (!self) return 0; // Execute bool handle = CefRenderHandlerCppToC::Get(self)->GetSharedHandle(); // Return type: void* return handle; }
void GLHelper::CopyToSharedDXHandle( const gpu::Mailbox& src_mailbox, const gpu::SyncToken& sync_token, void* sharedHandle) { GLuint mailbox_texture = ConsumeMailboxToTexture(src_mailbox, sync_token); gl_->NvCopyTextureToSharedHandle(mailbox_texture, sharedHandle); gl_->DeleteTextures(1, &mailbox_texture); }
GL_APICALL void GL_APIENTRY glNervCopyTextureToSharedHandle (GLuint texture_id, const GLvoid* handle);
'NervCopyTextureToSharedHandle': { 'decoder_func': 'DoNervCopyTextureToSharedHandle', 'unit_test': False, },
python gpu\command_buffer\build_gles2_cmd_buffer.py
File "gpu\command_buffer\build_gles2_cmd_buffer.py", line 9848, in WriteCmdInit self.type_handler.WriteImmediateCmdInit(self, f) File "gpu\command_buffer\build_gles2_cmd_buffer.py", line 5447, in WriteImmediateCmdInit raise NotImplementedError(func.name) NotImplementedError: NervCopyTextureToSharedHandleImmediate
'NervCopyTextureToSharedHandle': { 'type': 'Custom', 'decoder_func': 'DoNervCopyTextureToSharedHandle', 'unit_test': False, 'impl_func': False, 'client_test': False, },
[679/961] LINK ui.service.exe ui.service.exe.pdb FAILED: ui.service.exe ui.service.exe.pdb D:/Projects/CEFBuild/depot_tools/win_tools-2_7_6_bin/python/bin/python.exe ../../build/toolchain/win/tool_wrapper.py link-wrapper environment.x64 False link.exe /nologo /OUT:./ui.service.exe /PDB:./ui.service.exe.pdb @./ui.service.exe.rsp service_sources.lib(gles2_cmd_decoder.obj) : error LNK2019: unresolved external symbol "private: enum gpu::error::Error __cdecl gpu::gles2::GLES2DecoderImpl::HandleNervCopyTextureToSharedHandle(unsigned int,void const volatile *)" (?HandleNervCopyTextureToSharedHandle@GLES2DecoderImpl@gles2@gpu@@AEAA?AW4Error@error@3@IPEDX@Z) referenced in function "void __cdecl `dynamic initializer for 'private: static struct gpu::gles2::GLES2DecoderImpl::CommandInfo const * const gpu::gles2::GLES2DecoderImpl::command_info''(void)" (??__E?command_info@GLES2DecoderImpl@gles2@gpu@@0QBUCommandInfo@123@B@@YAXXZ) service_sources.lib(gles2_cmd_decoder_passthrough.obj) : error LNK2019: unresolved external symbol "private: enum gpu::error::Error __cdecl gpu::gles2::GLES2DecoderPassthroughImpl::HandleNervCopyTextureToSharedHandle(unsigned int,void const volatile *)" (?HandleNervCopyTextureToSharedHandle@GLES2DecoderPassthroughImpl@gles2@gpu@@AEAA?AW4Error@error@3@IPEDX@Z) referenced in function "void __cdecl `dynamic initializer for 'private: static struct gpu::gles2::GLES2DecoderPassthroughImpl::CommandInfo const * const gpu::gles2::GLES2DecoderPassthroughImpl::command_info''(void)" (??__E?command_info@GLES2DecoderPassthroughImpl@gles2@gpu@@0QBUCommandInfo@123@B@@YAXXZ) gles2_implementation.lib(gles2_implementation.obj) : error LNK2001: unresolved external symbol "public: virtual void __cdecl gpu::gles2::GLES2Implementation::NervCopyTextureToSharedHandle(unsigned int,void const *)" (?NervCopyTextureToSharedHandle@GLES2Implementation@gles2@gpu@@UEAAXIPEBX@Z) ./ui.service.exe : fatal error LNK1120: 3 unresolved externals [688/961] CXX obj/third_party/WebKit/Source/bindings/modules/v8/bindings_modules_impl_0/V8VRLayer.obj ninja: build stopped: subcommand failed.
error::Error GLES2DecoderImpl::HandleNervCopyTextureToSharedHandle( uint32_t immediate_data_size, const volatile void* cmd_data) { const volatile gles2::cmds::NervCopyTextureToSharedHandle& c = *static_cast<const volatile gles2::cmds::NervCopyTextureToSharedHandle*>( cmd_data); GLuint texture_id = c.texture_id; void* handle = (void*)(c.shared_handle()); NV_LOG("GLES2DecoderImpl: received shared handle "<<(const void*)handle<<" for source texture_id: "<<texture_id); return error::kNoError; }
error::Error GLES2DecoderPassthroughImpl::HandleNervCopyTextureToSharedHandle( uint32_t immediate_data_size, const volatile void* cmd_data) { const volatile gles2::cmds::NervCopyTextureToSharedHandle& c = *static_cast<const volatile gles2::cmds::NervCopyTextureToSharedHandle*>( cmd_data); GLuint texture_id = c.texture_id; void* handle = (void*)(c.shared_handle()); NV_LOG("GLES2DecoderPassthroughImpl: received shared handle "<<(const void*)handle<<" for source texture_id: "<<texture_id); return error::kNoError; }
<sxh cpp>void GLES2Implementation::NervCopyTextureToSharedHandle( GLuint texture_id, GLuint64 shared_handle) { helper_->NervCopyTextureToSharedHandle(texture_id, shared_handle);
}</sxh>
DCHECK(result->HasTexture()); base::ScopedClosureRunner scoped_callback_runner( base::Bind(&CefCopyFrameGenerator::OnCopyFrameCaptureFailure, weak_ptr_factory_.GetWeakPtr(), damage_rect)); const gfx::Size& result_size = result->size(); DEBUG_MSG("Texture size is: "<<result_size.width()<<"x"<<result_size.height()); // We don't allocate any SkBitmap below: #if 0 SkIRect bitmap_size; if (bitmap_) bitmap_->getBounds(&bitmap_size); if (!bitmap_ || bitmap_size.width() != result_size.width() || bitmap_size.height() != result_size.height()) { // Create a new bitmap if the size has changed. bitmap_.reset(new SkBitmap); bitmap_->allocN32Pixels(result_size.width(), result_size.height(), true); if (bitmap_->drawsNothing()) return; } #endif content::ImageTransportFactory* factory = content::ImageTransportFactory::GetInstance(); viz::GLHelper* gl_helper = factory->GetGLHelper(); if (!gl_helper) return; // We do not need the pixels below: #if 0 uint8_t* pixels = static_cast<uint8_t*>(bitmap_->getPixels()); #endif viz::TextureMailbox texture_mailbox; std::unique_ptr<cc::SingleReleaseCallback> release_callback; result->TakeTexture(&texture_mailbox, &release_callback); DCHECK(texture_mailbox.IsTexture()); if (!texture_mailbox.IsTexture()) return; ignore_result(scoped_callback_runner.Release()); // We don't call CropScaleReadback method here: #if 0 gl_helper->CropScaleReadbackAndCleanMailbox( texture_mailbox.mailbox(), texture_mailbox.sync_token(), result_size, gfx::Rect(result_size), result_size, pixels, kN32_SkColorType, base::Bind( &CefCopyFrameGenerator::CopyFromCompositingSurfaceFinishedProxy, weak_ptr_factory_.GetWeakPtr(), base::Passed(&release_callback), damage_rect, base::Passed(&bitmap_)), viz::GLHelper::SCALER_QUALITY_FAST); #endif CefRefPtr<CefRenderHandler> handler = view_->browser_impl()->GetClient()->GetRenderHandler(); if(!handler.get()) { DEBUG_MSG("NervCEF: Invalid render handler: not performing texture copy."); OnCopyFrameCaptureCompletion(false); return; } if(!handler->UseSharedHandle()) { DEBUG_MSG("NervCEF: Not using shared handle."); OnCopyFrameCaptureCompletion(false); return; } void* handle = handler->GetSharedHandle(); if(handle == nullptr) { DEBUG_MSG("NervCEF: ignoring null shared handle."); OnCopyFrameCaptureCompletion(false); return; } // Here we call another function to copy the texture to the shared handle: DEBUG_MSG("NervCEF: using shared handle: "<<(const void*)handle); gl_helper->NervCopyMailboxToSharedHandle(texture_mailbox.mailbox(), texture_mailbox.sync_token(), handle); OnCopyFrameCaptureCompletion(false);
bool RenderToTexture::DrawTextureBox() { glDisable(GL_LIGHTING); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(-1.0f, -1.0f, 0.5f); glScalef(2.0f, 2.0f, 1.0f); static VERTTYPE Vertices[] = { 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f }; static VERTTYPE TexCoords[] = { 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f }; glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glVertexPointer(3, VERTTYPEENUM, 0, (VERTTYPE *)&Vertices); glTexCoordPointer(2, VERTTYPEENUM, 0, (VERTTYPE *)&TexCoords); glBindTexture(GL_TEXTURE_2D, m_hTexture); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); return true; }
// We can retrieve the context with: gl::GLContext* GetGLContext(); // And the context group with: ContextGroup* GetContextGroup(); // From the GLContext, we can retrieve the share group: // share_group = GLContext::share_group(); // Then with this share group we can try to create a shared context: nvContext = eglCreateContext(egl_display, nvConfig, sgroup->GetHandle(), contextAttribs);
2017-11-16 19:11:32.913 UTC [808]{12132}: nvLogger initialized. 2017-11-16 19:11:32.913 UTC [808]{12132}: Reached: CreateOffscreenGLSurfaceWithFormat() function. 2017-11-16 19:11:32.914 UTC [808]{12132}: CreateOffscreenGLSurfaceWithFormat: Using EGLGLES2 SurfacelessEGL implementation 2017-11-16 19:11:33.042 UTC [808]{12132}: Reached: CreateOffscreenGLSurfaceWithFormat() function. 2017-11-16 19:11:33.043 UTC [808]{12132}: CreateOffscreenGLSurfaceWithFormat: Using EGLGLES2 SurfacelessEGL implementation 2017-11-16 19:11:33.043 UTC [808]{12132}: Creating GLES2DecoderImpl instance. 2017-11-16 19:11:33.043 UTC [808]{12132}: GpuCommandBufferStub: using offscreen rendering.
Stage7 (error code: 1282)
glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), vVertices);
EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE, EGL_NONE}; nvContext = eglCreateContext(egl_display, nvConfig, nullptr, contextAttribs);
pointer Specifies a pointer to the first generic vertex attribute in the array. If a non-zero buffer is currently bound to the GL_ARRAY_BUFFER target, pointer specifies an offset of into the array in the data store of that buffer. The initial value is 0.
GLint curBuffer = 0; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &curBuffer); CHECK(glIsBuffer(curBuffer)==GL_TRUE); // We bind the buffer to 0; glBindBuffer(GL_ARRAY_BUFFER, 0); // Here we do our rendering. // Then we restore the bound buffer: glBindBuffer(GL_ARRAY_BUFFER, curBuffer);
// during init: glGenBuffersARB(1, &nvVertBuffer); glBindBuffer(GL_ARRAY_BUFFER, nvVertBuffer); glBufferData(GL_ARRAY_BUFFER, size_of_buffer, vVertices, GL_STATIC_DRAW);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), reinterpret_cast<const void*>(3)); // Should rather be: glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), reinterpret_cast<const void*>(3*sizeof(GLfloat)));
Currently it seems we are generating a new texture each time the compositor has a new output to provide: we need to investigate what is actually the source of this generation and thus see if we can use the root texture directly (if any ?)
std::unique_ptr<cc::CopyOutputRequest> request = cc::CopyOutputRequest::CreateRequest(base::Bind( &CefCopyFrameGenerator::CopyFromCompositingSurfaceHasResult, weak_ptr_factory_.GetWeakPtr(), damage_rect)); request->set_area(gfx::Rect(view_->GetPhysicalBackingSize())); view_->GetRootLayer()->RequestCopyOfOutput(std::move(request));
root_layer_.reset(new ui::Layer(ui::LAYER_SOLID_COLOR));
void Texture::RemoveTextureRef(TextureRef* ref, bool have_context) { NV_LOG2("In Texture::RemoveTextureRef for "<<owned_service_id_<<", have_context="<<(have_context ? "true" : "false")); if (memory_tracking_ref_ == ref) { GetMemTracker()->TrackMemFree(estimated_size()); memory_tracking_ref_ = NULL; } size_t result = refs_.erase(ref); DCHECK_EQ(result, 1u); NV_LOG2("Left texture refs size: "<<refs_.size()); for(auto& lref: refs_) { NV_LOG2("Left refs: "<<(const void*)(*refs_.begin())); } if (refs_.empty()) { if (have_context) { NV_LOG2("Deleting service texture: "<<owned_service_id_); glDeleteTextures(1, &owned_service_id_); } delete this; } else if (memory_tracking_ref_ == NULL) { // TODO(piman): tune ownership semantics for cross-context group shared // textures. memory_tracking_ref_ = *refs_.begin(); GetMemTracker()->TrackMemAlloc(estimated_size()); } }
#include "include/cef_nv_exports.h" // ... (more lines here) ... CEF_EXPORT void cef_set_log_handler(cef::CefLogHandler* handler) { cef::setLogHandler(handler); }
void Texture::AddTextureRef(TextureRef* ref) { DCHECK(refs_.find(ref) == refs_.end()); refs_.insert(ref); // Trace where we are: NV_LOG2("Adding textureref " << (const void*)(ref) << " on texture with service id "<< owned_service_id_ << ": "<< base::debug::StackTrace().ToString()); if (!memory_tracking_ref_) { memory_tracking_ref_ = ref; GetMemTracker()->TrackMemAlloc(estimated_size()); } }
2018-01-09 08:14:27.557 UTC [2300]{11140}: GenTexturesHelper: Creating service texture: 1009 2018-01-09 08:14:27.557 UTC [2300]{11140}: Adding textureref 0000010E093B00A0 on texture with service id 1009 2018-01-09 08:14:27.557 UTC [2300]{11140}: TextureManager: num textures: 775 2018-01-09 08:14:27.569 UTC [2300]{11140}: Adding textureref 0000010E093AF8C0 on texture with service id 1009 2018-01-09 08:14:27.569 UTC [2300]{11140}: TextureManager: num textures: 3 2018-01-09 08:14:27.569 UTC [2300]{11140}: GLES2DecoderImpl: received shared handle 00000000C0000202 for source texture_id: 679 2018-01-09 08:14:27.569 UTC [2300]{11140}: GLES2DecoderImpl: Drawing from service texture_id: 1009 2018-01-09 08:14:27.569 UTC [2300]{11140}: GLES2DecoderImpl: Releasing texture unit infos. 2018-01-09 08:14:27.569 UTC [2300]{11140}: GLES2DecoderImpl: Deleting texture with client id=679 (service_id=1009) 2018-01-09 08:14:27.569 UTC [2300]{11140}: In TextureRef destructor with force_context_lost=false, manager have_context:true 2018-01-09 08:14:27.569 UTC [2300]{11140}: In Texture::RemoveTextureRef for 1009, have_context=true 2018-01-09 08:14:27.569 UTC [2300]{11140}: Left texture refs size: 1 2018-01-09 08:14:27.569 UTC [2300]{11140}: Left refs: 0000010E093B00A0 2018-01-09 08:14:27.569 UTC [2300]{11140}: GLES2DecoderImpl: Done copying to shared handle surface.
std::unique_ptr<GLuint[]> service_ids(new GLuint[n]); glGenTextures(n, service_ids.get()); for (GLsizei ii = 0; ii < n; ++ii) { NV_LOG2("GenTexturesHelper: Creating service texture: "<<service_ids[ii]); CreateTexture(client_ids[ii], service_ids[ii]); } return true;
// Creates a Texture for the given texture. TextureRef* CreateTexture( GLuint client_id, GLuint service_id) { return texture_manager()->CreateTexture(client_id, service_id); }
TextureRef* TextureManager::CreateTexture( GLuint client_id, GLuint service_id) { DCHECK_NE(0u, service_id); scoped_refptr<TextureRef> ref(TextureRef::Create( this, client_id, service_id)); std::pair<TextureMap::iterator, bool> result = textures_.insert(std::make_pair(client_id, ref)); DCHECK(result.second); return ref.get(); }
scoped_refptr<TextureRef> TextureRef::Create(TextureManager* manager, GLuint client_id, GLuint service_id) { TextureRef* ref = new TextureRef(manager, client_id, new Texture(service_id)); NV_LOG2("Created textureref " << (const void*)(ref) << " on texture with service id "<< service_id << " for manager "<<<(const void*)manager); return ref; }
texture_ref = texture_manager()->Consume(client_id, texture);
TextureRef* TextureManager::Consume( GLuint client_id, Texture* texture) { DCHECK(client_id); scoped_refptr<TextureRef> ref(new TextureRef(this, client_id, texture)); bool result = textures_.insert(std::make_pair(client_id, ref)).second; DCHECK(result); return ref.get(); }
Texture* texture = static_cast<Texture*>(group_->mailbox_manager()->ConsumeTexture(mailbox));
2018-01-09 12:28:27.570 UTC [5012]{220}: Deleting service texture: 958 2018-01-09 12:28:27.570 UTC [5012]{220}: Adding textureref 0000023DE5FB2950 on texture 0000023DE2DB7E30 with service id 265 2018-01-09 12:28:27.570 UTC [5012]{220}: TextureManager 0000023DD68C8D60: num textures: 777 2018-01-09 12:28:27.570 UTC [5012]{220}: GenTexturesHelper: Creating service texture: 958 2018-01-09 12:28:27.570 UTC [5012]{220}: Adding textureref 0000023DE5FB2470 on texture 0000023DE2F97720 with service id 958 2018-01-09 12:28:27.570 UTC [5012]{220}: TextureManager 0000023DD68C8D60: num textures: 778 2018-01-09 12:28:27.570 UTC [5012]{220}: Created textureref 0000023DE5FB2470 on texture with service id 958 for manager 0000023DD68C8D60 2018-01-09 12:28:27.585 UTC [5012]{220}: Adding textureref 0000023DE5FB29B0 on texture 0000023DE2F97720 with service id 958 2018-01-09 12:28:27.585 UTC [5012]{220}: TextureManager 0000023DE2DF9100: num textures: 3 2018-01-09 12:28:27.585 UTC [5012]{220}: GLES2DecoderImpl: received shared handle 0000000040001342 for source texture_id: 649 2018-01-09 12:28:27.585 UTC [5012]{220}: GLES2DecoderImpl: Drawing from service texture_id: 958 2018-01-09 12:28:27.585 UTC [5012]{220}: GLES2DecoderImpl: Releasing texture unit infos. 2018-01-09 12:28:27.585 UTC [5012]{220}: GLES2DecoderImpl: Deleting texture with client id=649 (service_id=958) 2018-01-09 12:28:27.585 UTC [5012]{220}: Deleting TextureRef with force_context_lost=false, manager have_context:true for manager 0000023DE2DF9100 2018-01-09 12:28:27.585 UTC [5012]{220}: Removing TextureRef 0000023DE5FB29B0 from texture 0000023DE2F97720 with service id 958, have_context=true 2018-01-09 12:28:27.585 UTC [5012]{220}: Left texture refs size: 1 2018-01-09 12:28:27.586 UTC [5012]{220}: Left refs: 0000023DE5FB2470 2018-01-09 12:28:27.586 UTC [5012]{220}: GLES2DecoderImpl: Done copying to shared handle surface.