// Copyright (c) 2025 Reito // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. #include "shell/common/api/electron_api_shared_texture.h" #include "base/base64.h" #include "base/command_line.h" #include "base/numerics/byte_conversions.h" #include "components/viz/common/resources/shared_image_format_utils.h" #include "content/browser/compositor/image_transport_factory.h" // nogncheck #include "gpu/command_buffer/client/context_support.h" #include "gpu/ipc/client/client_shared_image_interface.h" #include "gpu/ipc/client/gpu_channel_host.h" #include "gpu/ipc/common/exported_shared_image.mojom-shared.h" #include "gpu/ipc/common/exported_shared_image_mojom_traits.h" #include "media/base/format_utils.h" #include "media/base/video_frame.h" #include "media/mojo/mojom/video_frame_mojom_traits.h" #include "shell/common/gin_converters/blink_converter.h" #include "shell/common/gin_converters/callback_converter.h" #include "shell/common/gin_converters/gfx_converter.h" #include "shell/common/gin_helper/dictionary.h" #include "shell/common/gin_helper/error_thrower.h" #include "shell/common/node_includes.h" #include "shell/common/node_util.h" #include "third_party/blink/renderer/modules/webcodecs/video_frame.h" // nogncheck #include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h" // nogncheck #include "ui/compositor/compositor.h" #if BUILDFLAG(IS_LINUX) #include "base/posix/eintr_wrapper.h" #include "base/strings/string_number_conversions.h" #endif namespace { bool IsBrowserProcess() { static int is_browser_process = -1; if (is_browser_process == -1) { // Browser process does not specify a type. is_browser_process = base::CommandLine::ForCurrentProcess() ->GetSwitchValueASCII("type") .empty(); } return is_browser_process == 1; } gpu::ContextSupport* GetContextSupport() { if (IsBrowserProcess()) { auto* factory = content::ImageTransportFactory::GetInstance(); return factory->GetContextFactory() ->SharedMainThreadRasterContextProvider() ->ContextSupport(); } else { return blink::SharedGpuContext::ContextProviderWrapper() ->ContextProvider() .ContextSupport(); } } gpu::SharedImageInterface* GetSharedImageInterface() { if (IsBrowserProcess()) { auto* factory = content::ImageTransportFactory::GetInstance(); return factory->GetContextFactory() ->SharedMainThreadRasterContextProvider() ->SharedImageInterface(); } else { return blink::SharedGpuContext::SharedImageInterfaceProvider() ->SharedImageInterface(); } } std::string GetBase64StringFromSyncToken(gpu::SyncToken& sync_token) { if (!sync_token.verified_flush()) { auto* sii = GetSharedImageInterface(); sii->VerifySyncToken(sync_token); } auto sync_token_data = base::Base64Encode(UNSAFE_BUFFERS( base::span(reinterpret_cast(sync_token.GetData()), sizeof(gpu::SyncToken)))); return sync_token_data; } gpu::SyncToken GetSyncTokenFromBase64String( const std::string& sync_token_data) { if (sync_token_data.empty()) { LOG(ERROR) << "Sync token data is empty."; return {}; } auto sync_token_bytes = base::Base64Decode(sync_token_data); if (!sync_token_bytes.has_value()) { LOG(ERROR) << "Failed to decode sync token from base64 string."; return {}; } if (sync_token_bytes->size() != sizeof(gpu::SyncToken)) { LOG(ERROR) << "Invalid sync token size: " << sync_token_bytes->size(); return {}; } base::span sync_token_span = UNSAFE_BUFFERS( base::span(sync_token_bytes->data(), sync_token_bytes->size())); auto* sync_token_source = reinterpret_cast(sync_token_span.data()); gpu::SyncToken sync_token(sync_token_source->namespace_id(), sync_token_source->command_buffer_id(), sync_token_source->release_count()); if (sync_token_source->verified_flush()) { sync_token.SetVerifyFlush(); } return sync_token; } std::string TransferVideoPixelFormatToString(media::VideoPixelFormat format) { switch (format) { case media::PIXEL_FORMAT_ARGB: return "bgra"; case media::PIXEL_FORMAT_ABGR: return "rgba"; case media::PIXEL_FORMAT_RGBAF16: return "rgbaf16"; case media::PIXEL_FORMAT_NV12: return "nv12"; default: NOTREACHED(); } } struct ImportedSharedTexture : base::RefCountedThreadSafe { // Metadata gfx::Size coded_size; gfx::Rect visible_rect; int64_t timestamp; media::VideoPixelFormat pixel_format; // Holds a reference to prevent it from being destroyed. scoped_refptr client_shared_image; gpu::SyncToken frame_creation_sync_token; base::Lock release_sync_token_lock_; gpu::SyncToken release_sync_token GUARDED_BY(release_sync_token_lock_); base::OnceClosure release_callback; // Texture id for printing warnings like GC check. std::string id; void UpdateReleaseSyncToken(const gpu::SyncToken& token); void SetupReleaseSyncTokenCallback(); // Transfer to other Chromium processes. v8::Local StartTransferSharedTexture(v8::Isolate* isolate); // Get the creation sync token for the shared image. This is called // after |finishTransferSharedTexture| and users need to pass the // sync token back to the source object, and call |setReleaseSyncToken| // to prevent the resource being released before actual acquisition // happens for the target object. v8::Local GetFrameCreationSyncToken(v8::Isolate* isolate); // Set a release sync token for this shared texture. This is set // when |finishTransferSharedTexture| is called and prevent the source // object release the underlying resource before the target object // actually done acquiring the resource at gpu process. // Note that this is optional to set, if not set, users can still use // `release()` and use the callback to wait for the signal that source // object is safe to do the further release. If set, users can call // `release()` on the source object without worrying the target object // didn't finish acquiring the resource at gpu process. void SetReleaseSyncToken(v8::Isolate* isolate, v8::Local options); // The cleanup happens at destructor. private: friend class base::RefCountedThreadSafe; ~ImportedSharedTexture(); }; // Wraps the structure so that it can be ref counted. struct ImportedSharedTextureWrapper { // Make the import shared texture wrapper ref counted, so that it can be // held by multiple VideoFrames. scoped_refptr ist; // Monitor garbage collection. std::unique_ptr> persistent_; void ResetPersistent() const { persistent_->Reset(); } v8::Persistent* CreatePersistent(v8::Isolate* isolate, v8::Local value) { persistent_ = std::make_unique>(isolate, value); return persistent_.get(); } // Create VideoFrame from the shared image. v8::Local CreateVideoFrame(v8::Isolate* isolate); // Release the shared image. bool IsReferenceReleased() const { return ist.get() == nullptr; } void ReleaseReference(); }; void ImportedSharedTextureWrapper::ReleaseReference() { if (IsReferenceReleased()) { LOG(ERROR) << "This imported shared texture is already released."; return; } // Drop the reference, if at lease one VideoFrame is still holding it, // the final clean up will wait for them. ist.reset(); } // This function will be called when the VideoFrame is destructed. void OnVideoFrameMailboxReleased( const scoped_refptr& ist, const gpu::SyncToken& sync_token) { ist->UpdateReleaseSyncToken(sync_token); } v8::Local ImportedSharedTextureWrapper::CreateVideoFrame( v8::Isolate* isolate) { auto* current_script_state = blink::ScriptState::ForCurrentRealm(isolate); auto* current_execution_context = blink::ToExecutionContext(current_script_state); auto si = ist->client_shared_image; auto cb = base::BindOnce(OnVideoFrameMailboxReleased, ist); scoped_refptr raw_frame = media::VideoFrame::WrapSharedImage( ist->pixel_format, si, ist->frame_creation_sync_token, std::move(cb), ist->coded_size, ist->visible_rect, ist->coded_size, base::Microseconds(ist->timestamp)); raw_frame->set_color_space(si->color_space()); blink::VideoFrame* frame = blink::MakeGarbageCollected( raw_frame, current_execution_context); return blink::ToV8Traits::ToV8(current_script_state, frame); } v8::Local ImportedSharedTexture::StartTransferSharedTexture( v8::Isolate* isolate) { auto exported = client_shared_image->Export(); // Use mojo to serialize the exported shared image. mojo::Message message(0, 0, MOJO_CREATE_MESSAGE_FLAG_UNLIMITED_SIZE, 0); mojo::internal::MessageFragment< gpu::mojom::internal::ExportedSharedImage_Data> data(message); data.Allocate(); mojo::internal::Serializer::Serialize(exported, data); auto encoded = base::Base64Encode(UNSAFE_BUFFERS( base::span(message.payload(), message.payload_num_bytes()))); gin_helper::Dictionary root(isolate, v8::Object::New(isolate)); root.SetReadOnly("transfer", encoded); auto sync_token = GetBase64StringFromSyncToken(frame_creation_sync_token); root.SetReadOnly("syncToken", sync_token); root.SetReadOnly("pixelFormat", TransferVideoPixelFormatToString(pixel_format)); root.SetReadOnly("codedSize", coded_size); root.SetReadOnly("visibleRect", visible_rect); root.SetReadOnly("timestamp", timestamp); return gin::ConvertToV8(isolate, root); } v8::Local ImportedSharedTexture::GetFrameCreationSyncToken( v8::Isolate* isolate) { gin::Dictionary root(isolate, v8::Object::New(isolate)); auto sync_token = GetBase64StringFromSyncToken(frame_creation_sync_token); root.Set("syncToken", sync_token); return gin::ConvertToV8(isolate, root); } void ImportedSharedTexture::SetReleaseSyncToken(v8::Isolate* isolate, v8::Local options) { std::string sync_token_data; gin::Dictionary dict(isolate, options.As()); dict.Get("syncToken", &sync_token_data); auto sync_token = GetSyncTokenFromBase64String(sync_token_data); UpdateReleaseSyncToken(sync_token); } ImportedSharedTexture::~ImportedSharedTexture() { // When nothing holds this, 1) all VideoFrames are destructed and the // release_sync_token have been updated; 2) the user called `release()` // explicitly. This is destructed and the final clean up is started. SetupReleaseSyncTokenCallback(); client_shared_image.reset(); } void ImportedSharedTexture::UpdateReleaseSyncToken( const gpu::SyncToken& token) { base::AutoLock locker(release_sync_token_lock_); auto* sii = GetSharedImageInterface(); if (release_sync_token.HasData()) { // If we already have a release sync token, we need to wait for it // to be signaled before we can set the new one. sii->WaitSyncToken(release_sync_token); } // Set the new release sync token to use at last. release_sync_token = token; } void ImportedSharedTexture::SetupReleaseSyncTokenCallback() { base::AutoLock locker(release_sync_token_lock_); auto* sii = GetSharedImageInterface(); if (!release_sync_token.HasData()) { release_sync_token = sii->GenUnverifiedSyncToken(); } client_shared_image->UpdateDestructionSyncToken(release_sync_token); if (release_callback) { GetContextSupport()->SignalSyncToken(release_sync_token, std::move(release_callback)); } } void PersistentCallbackPass1( const v8::WeakCallbackInfo& data) { auto* wrapper = data.GetParameter(); // The |wrapper->ist| must be valid here, as we are a holder of it. if (!wrapper->IsReferenceReleased()) { // Emit a warning when the user didn't properly manually release the // texture. LOG(ERROR) << "The imported shared texture " << wrapper->ist->id << " was garbage collected before calling `release()`. You have " "to manually release the resource once you're done with it."; // Release it for user here. wrapper->ReleaseReference(); } // We are responsible for resetting the persistent handle. wrapper->ResetPersistent(); // Finally, release the import monitor; delete wrapper; } void ImportedTextureGetVideoFrame( const v8::FunctionCallbackInfo& info) { auto* isolate = info.GetIsolate(); auto* wrapper = static_cast( info.Data().As()->Value()); if (wrapper->IsReferenceReleased()) { gin_helper::ErrorThrower(isolate).ThrowTypeError( "The shared texture has been released."); return; } if (IsBrowserProcess()) { gin_helper::ErrorThrower(isolate).ThrowTypeError( "The VideoFrame cannot be created at current process."); return; } auto ret = wrapper->CreateVideoFrame(isolate); info.GetReturnValue().Set(ret); } void ImportedTextureStartTransferSharedTexture( const v8::FunctionCallbackInfo& info) { auto* isolate = info.GetIsolate(); auto* wrapper = static_cast( info.Data().As()->Value()); if (wrapper->IsReferenceReleased()) { gin_helper::ErrorThrower(isolate).ThrowTypeError( "The shared texture has been released."); return; } auto ret = wrapper->ist->StartTransferSharedTexture(isolate); info.GetReturnValue().Set(ret); } void ImportedTextureRelease(const v8::FunctionCallbackInfo& info) { auto* wrapper = static_cast( info.Data().As()->Value()); auto cb = info[0]; if (cb->IsFunction()) { auto* isolate = info.GetIsolate(); gin::ConvertFromV8(isolate, cb, &wrapper->ist->release_callback); } // Release the shared texture, so that future frames can be generated. wrapper->ReleaseReference(); // Release of the wrapper happens at GC persistent callback. // Release of the |ist| happens when nothing holds a reference to it. } void ImportedTextureGetFrameCreationSyncToken( const v8::FunctionCallbackInfo& info) { auto* isolate = info.GetIsolate(); auto* wrapper = static_cast( info.Data().As()->Value()); if (wrapper->IsReferenceReleased()) { gin_helper::ErrorThrower(isolate).ThrowTypeError( "The shared texture has been released."); return; } auto ret = wrapper->ist->GetFrameCreationSyncToken(isolate); info.GetReturnValue().Set(ret); } void ImportedTextureSetReleaseSyncToken( const v8::FunctionCallbackInfo& info) { auto* isolate = info.GetIsolate(); auto* wrapper = static_cast( info.Data().As()->Value()); if (wrapper->IsReferenceReleased()) { gin_helper::ErrorThrower(isolate).ThrowTypeError( "The shared texture has been released."); return; } if (info.Length() < 1 || !info[0]->IsObject()) { gin_helper::ErrorThrower(isolate).ThrowTypeError( "Expected an options object with a syncToken property."); return; } wrapper->ist->SetReleaseSyncToken(isolate, info[0].As()); } v8::Local CreateImportedSharedTextureFromSharedImage( v8::Isolate* isolate, ImportedSharedTexture* imported) { auto* wrapper = new ImportedSharedTextureWrapper(); wrapper->ist = base::WrapRefCounted(imported); auto imported_wrapped = v8::External::New(isolate, wrapper); gin::Dictionary root(isolate, v8::Object::New(isolate)); auto releaser = v8::Function::New(isolate->GetCurrentContext(), ImportedTextureRelease, imported_wrapped) .ToLocalChecked(); auto get_video_frame = v8::Function::New(isolate->GetCurrentContext(), ImportedTextureGetVideoFrame, imported_wrapped) .ToLocalChecked(); auto start_transfer = v8::Function::New(isolate->GetCurrentContext(), ImportedTextureStartTransferSharedTexture, imported_wrapped) .ToLocalChecked(); auto get_frame_creation_sync_token = v8::Function::New(isolate->GetCurrentContext(), ImportedTextureGetFrameCreationSyncToken, imported_wrapped) .ToLocalChecked(); auto set_release_sync_token = v8::Function::New(isolate->GetCurrentContext(), ImportedTextureSetReleaseSyncToken, imported_wrapped) .ToLocalChecked(); root.Set("release", releaser); root.Set("getVideoFrame", get_video_frame); root.Set("startTransferSharedTexture", start_transfer); root.Set("getFrameCreationSyncToken", get_frame_creation_sync_token); root.Set("setReleaseSyncToken", set_release_sync_token); auto root_local = gin::ConvertToV8(isolate, root); auto* persistent = wrapper->CreatePersistent(isolate, root_local); persistent->SetWeak(wrapper, PersistentCallbackPass1, v8::WeakCallbackType::kParameter); return root_local; } struct ImportSharedTextureInfoPlane { // The strides and offsets in bytes to be used when accessing the buffers // via a memory mapping. One per plane per entry. Size in bytes of the // plane is necessary to map the buffers. uint32_t stride; uint64_t offset; uint64_t size; // File descriptor for the underlying memory object (usually dmabuf). int fd = 0; }; struct ImportSharedTextureInfo { // Texture id for printing warnings like GC check. std::string id; // The pixel format of the shared texture, RGBA or BGRA depends on platform. media::VideoPixelFormat pixel_format; // The full dimensions of the video frame data. gfx::Size coded_size; // A subsection of [0, 0, coded_size().width(), coded_size.height()]. // In OSR case, it is expected to have the full area of the section. gfx::Rect visible_rect; // The color space of the video frame. gfx::ColorSpace color_space = gfx::ColorSpace::CreateSRGB(); // The capture timestamp, microseconds since capture start int64_t timestamp = 0; #if BUILDFLAG(IS_WIN) // On Windows, it must be a NT HANDLE (CreateSharedHandle) to the shared // texture, it can't be a deprecated non-NT HANDLE (GetSharedHandle). This // must be a handle already duplicated for the current process and can be // owned by this. uintptr_t nt_handle = 0; #elif BUILDFLAG(IS_APPLE) // On macOS, it is an IOSurfaceRef, this must be a valid IOSurface at the // current process. uintptr_t io_surface = 0; #elif BUILDFLAG(IS_LINUX) // On Linux, to be implemented. std::vector planes; uint64_t modifier = gfx::NativePixmapHandle::kNoModifier; bool supports_zero_copy_webgpu_import = false; #endif }; } // namespace namespace gin { template <> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, ImportSharedTextureInfo* out) { if (!val->IsObject()) return false; gin::Dictionary dict(isolate, val.As()); std::string pixel_format_str; if (dict.Get("pixelFormat", &pixel_format_str)) { if (pixel_format_str == "bgra") out->pixel_format = media::PIXEL_FORMAT_ARGB; else if (pixel_format_str == "rgba") out->pixel_format = media::PIXEL_FORMAT_ABGR; else if (pixel_format_str == "rgbaf16") out->pixel_format = media::PIXEL_FORMAT_RGBAF16; else if (pixel_format_str == "nv12") out->pixel_format = media::PIXEL_FORMAT_NV12; else return false; } dict.Get("codedSize", &out->coded_size); if (!dict.Get("visibleRect", &out->visible_rect)) { out->visible_rect = gfx::Rect(out->coded_size); } dict.Get("colorSpace", &out->color_space); dict.Get("timestamp", &out->timestamp); dict.Get("id", &out->id); gin::Dictionary shared_texture(isolate, val.As()); if (!dict.Get("handle", &shared_texture)) { return false; } #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE) auto GetNativeHandle = [&](const std::string& property_key, uintptr_t* output) { v8::Local handle_buf; if (shared_texture.Get(property_key, &handle_buf) && node::Buffer::HasInstance(handle_buf)) { char* data = node::Buffer::Data(handle_buf); if (node::Buffer::Length(handle_buf) == sizeof(uintptr_t)) { *output = *reinterpret_cast(data); } } }; #endif #if BUILDFLAG(IS_WIN) GetNativeHandle("ntHandle", &out->nt_handle); #elif BUILDFLAG(IS_APPLE) GetNativeHandle("ioSurface", &out->io_surface); #elif BUILDFLAG(IS_LINUX) v8::Local native_pixmap; if (shared_texture.Get("nativePixmap", &native_pixmap)) { gin::Dictionary v8_native_pixmap(isolate, native_pixmap); v8::Local v8_planes; if (v8_native_pixmap.Get("planes", &v8_planes)) { out->planes.clear(); for (uint32_t i = 0; i < v8_planes->Length(); ++i) { v8::Local v8_item = v8_planes->Get(isolate->GetCurrentContext(), i).ToLocalChecked(); gin::Dictionary v8_plane(isolate, v8_item.As()); ImportSharedTextureInfoPlane plane; v8_plane.Get("stride", &plane.stride); v8_plane.Get("offset", &plane.offset); v8_plane.Get("size", &plane.size); v8_plane.Get("fd", &plane.fd); out->planes.push_back(plane); } } std::string modifier_str; if (v8_native_pixmap.Get("modifier", &modifier_str)) { base::StringToUint64(modifier_str, &out->modifier); } v8_native_pixmap.Get("supportsZeroCopyWebGpuImport", &out->supports_zero_copy_webgpu_import); } #endif return true; } }; } // namespace gin namespace electron::api::shared_texture { v8::Local ImportSharedTexture(v8::Isolate* isolate, v8::Local options) { ImportSharedTextureInfo shared_texture{}; if (!gin::ConvertFromV8(isolate, options, &shared_texture)) { gin_helper::ErrorThrower(isolate).ThrowTypeError( "Invalid shared texture info object"); return v8::Null(isolate); } gfx::GpuMemoryBufferHandle gmb_handle; #if BUILDFLAG(IS_WIN) if (shared_texture.nt_handle == 0) { gin_helper::ErrorThrower(isolate).ThrowTypeError("Invalid ntHandle value"); return v8::Null(isolate); } auto handle = reinterpret_cast(shared_texture.nt_handle); HANDLE dup_handle; // Duplicate the handle to allow scoped handle close it. if (!DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), &dup_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) { gin_helper::ErrorThrower(isolate).ThrowTypeError( "Unable to duplicate handle."); return v8::Null(isolate); } auto dxgi_handle = gfx::DXGIHandle(base::win::ScopedHandle(dup_handle)); gmb_handle = gfx::GpuMemoryBufferHandle(std::move(dxgi_handle)); #elif BUILDFLAG(IS_APPLE) if (shared_texture.io_surface == 0) { gin_helper::ErrorThrower(isolate).ThrowTypeError("Invalid ioSurface value"); return v8::Null(isolate); } // Retain the io_surface reference to increase the reference count. auto io_surface = reinterpret_cast(shared_texture.io_surface); auto io_surface_scoped = base::apple::ScopedCFTypeRef( io_surface, base::scoped_policy::RETAIN); gmb_handle = gfx::GpuMemoryBufferHandle(std::move(io_surface_scoped)); #elif BUILDFLAG(IS_LINUX) gfx::NativePixmapHandle pixmap; pixmap.modifier = shared_texture.modifier; pixmap.supports_zero_copy_webgpu_import = shared_texture.supports_zero_copy_webgpu_import; for (const auto& plane : shared_texture.planes) { gfx::NativePixmapPlane plane_info; plane_info.stride = plane.stride; plane_info.offset = plane.offset; plane_info.size = plane.size; // Duplicate fd, otherwise the process may already have ownership. int checked_dup = HANDLE_EINTR(dup(plane.fd)); plane_info.fd = base::ScopedFD(checked_dup); pixmap.planes.push_back(std::move(plane_info)); } gmb_handle = gfx::GpuMemoryBufferHandle(std::move(pixmap)); #endif gfx::Size coded_size = shared_texture.coded_size; media::VideoPixelFormat pixel_format = shared_texture.pixel_format; gfx::ColorSpace color_space = shared_texture.color_space; auto buffer_format = media::VideoPixelFormatToGfxBufferFormat(pixel_format); if (!buffer_format.has_value()) { gin_helper::ErrorThrower(isolate).ThrowTypeError( "Invalid shared texture buffer format"); return v8::Null(isolate); } auto* sii = GetSharedImageInterface(); gpu::SharedImageUsageSet shared_image_usage = #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE) gpu::SHARED_IMAGE_USAGE_GLES2_READ | gpu::SHARED_IMAGE_USAGE_GLES2_WRITE | gpu::SHARED_IMAGE_USAGE_RASTER_READ | gpu::SHARED_IMAGE_USAGE_DISPLAY_READ | gpu::SHARED_IMAGE_USAGE_WEBGPU_READ | gpu::SHARED_IMAGE_USAGE_WEBGPU_WRITE; #else gpu::SHARED_IMAGE_USAGE_GLES2_READ | gpu::SHARED_IMAGE_USAGE_GLES2_WRITE | gpu::SHARED_IMAGE_USAGE_RASTER_READ | gpu::SHARED_IMAGE_USAGE_DISPLAY_READ; #endif auto si_format = viz::GetSharedImageFormat(buffer_format.value()); auto si = sii->CreateSharedImage({si_format, coded_size, color_space, shared_image_usage, "SharedTextureVideoFrame"}, std::move(gmb_handle)); if (!si) { gin_helper::ErrorThrower(isolate).ThrowTypeError( "Failed to create shared image from shared texture handle. Texture " "format or dimension might not be supported on current device or " "platform."); return v8::Null(isolate); } ImportedSharedTexture* imported = new ImportedSharedTexture(); imported->pixel_format = shared_texture.pixel_format; imported->coded_size = shared_texture.coded_size; imported->visible_rect = shared_texture.visible_rect; imported->timestamp = shared_texture.timestamp; imported->frame_creation_sync_token = si->creation_sync_token(); imported->client_shared_image = std::move(si); imported->id = shared_texture.id; return CreateImportedSharedTextureFromSharedImage(isolate, imported); } v8::Local FinishTransferSharedTexture(v8::Isolate* isolate, v8::Local options) { ImportSharedTextureInfo partial{}; gin::ConvertFromV8(isolate, options, &partial); std::string id; std::string transfer; std::string sync_token_data; gin::Dictionary dict(isolate, options.As()); dict.Get("id", &id); dict.Get("transfer", &transfer); dict.Get("syncToken", &sync_token_data); auto transfer_data = base::Base64Decode(transfer); // Use mojo to deserialize the exported shared image. mojo::Message message(transfer_data.value(), {}); mojo::internal::MessageFragment< gpu::mojom::internal::ExportedSharedImage_Data> data(message); data.Claim(message.mutable_payload()); gpu::ExportedSharedImage exported; mojo::internal::Serializer::Deserialize(data.data(), &exported, &message); auto* sii = GetSharedImageInterface(); auto si = sii->ImportSharedImage(std::move(exported)); auto source_st = GetSyncTokenFromBase64String(sync_token_data); sii->WaitSyncToken(source_st); ImportedSharedTexture* imported = new ImportedSharedTexture(); imported->pixel_format = partial.pixel_format; imported->coded_size = partial.coded_size; imported->visible_rect = partial.visible_rect; imported->timestamp = partial.timestamp; imported->frame_creation_sync_token = sii->GenUnverifiedSyncToken(); imported->client_shared_image = std::move(si); imported->id = id; return CreateImportedSharedTextureFromSharedImage(isolate, imported); } } // namespace electron::api::shared_texture namespace { void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { v8::Isolate* const isolate = v8::Isolate::GetCurrent(); gin_helper::Dictionary dict(isolate, exports); dict.SetMethod("importSharedTexture", &electron::api::shared_texture::ImportSharedTexture); dict.SetMethod("finishTransferSharedTexture", &electron::api::shared_texture::FinishTransferSharedTexture); } } // namespace NODE_LINKED_BINDING_CONTEXT_AWARE(electron_common_shared_texture, Initialize)