Files
electron/shell/common/api/electron_api_shared_texture.cc
70c69e36eb chore: bump chromium to 158.0.8107.0 (main) (#55013)
* chore: bump chromium in DEPS to 158.0.8106.0

* chore: update patches

Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>

* 8526232: media: Group capture-related fields into CaptureMetadata

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8526232

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* 8525546: [api] Advance deprecation of callback data APIs

Upstream advanced FunctionCallbackInfo::Data() and
PropertyCallbackInfo::Data() from V8_DEPRECATE_SOON to V8_DEPRECATED,
which Electron builds with -Werror. Switched all call sites to DataV2(),
casting through v8::Value where the target type's Cast() only accepts
v8::Value.

Ref: https://chromium-review.googlesource.com/c/v8/v8/+/8525546

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* 8577897: Enable DeprecateUnload and DeprecateUnloadByBucket by default.

Unload handlers no longer run on http(s) pages unless the page opts in
via Permissions-Policy. The detached-frame specs observe IPC sent from
an unload handler during cross-origin navigation, so serve their test
pages with `Permissions-Policy: unload=*`.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8577897

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01We3DwyzcxvA6LqGJdoeHAX

* fixup! 8577897: Enable DeprecateUnload and DeprecateUnloadByBucket by default.

docs: add breaking change for unload deprecation

Document that unload handlers no longer run in http(s) documents, how to
migrate to pagehide/visibilitychange, and how to opt back in. Mention
pagehide alongside unload in the webFrameMain `frame.detached` docs.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8577897

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01We3DwyzcxvA6LqGJdoeHAX

* fixup! 8577897: Enable DeprecateUnload and DeprecateUnloadByBucket by default.

docs: drop DeprecateUnloadByBucket note from unload breaking change

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01We3DwyzcxvA6LqGJdoeHAX

* chore: bump chromium in DEPS to 158.0.8107.0

* 8527334: [Linux] Fix GlobalShortcuts portal binding issues

Upstream restructured GlobalAcceleratorListenerLinux's bind state
machine around the set of IDs to bind and only binds when that set
changes. Adapted the registration-resolved patch to it:

- ListShortcuts failures no longer abort binding upstream, so they no
  longer resolve registrations as failed.
- Groups in a bind request are collected from the IDs being bound.
- Resolve as bound when a version 1 portal already lists every
  shortcut, and when a group's shortcuts are already bound in the
  current session (upstream no longer re-binds then).
- Keep closing the session on a failed bind so the failed IDs are not
  recorded as bound in the session.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8527334

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01We3DwyzcxvA6LqGJdoeHAX

* chore: update patches

* 8574624: [Linux] Prevent QT from loading GTK3 when GSettings schemas are invalid

The GSettings schema check moved from ui/gtk to ui/base/glib/gsettings.cc,
so the warning for the missing font-antialiasing key now reads
"Invalid GSettings schema org.gnome.desktop.interface for key
font-antialiasing: missing" instead of "Schema org.gnome.desktop.interface
does not have key font-antialiasing". Accept either wording in the
"creates and refreshes a menu when GTK cannot initialize" spec, since the
CL was reverted and relanded.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8574624

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01We3DwyzcxvA6LqGJdoeHAX

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: electron-claude[bot] <279139701+electron-claude[bot]@users.noreply.github.com>
Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-19 10:37:22 -04:00

925 lines
33 KiB
C++

// Copyright (c) 2025 Reito <reito@chromium.org>
// 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 <string>
#include <vector>
#include "base/base64.h"
#include "base/command_line.h"
#include "base/memory/ref_counted_delete_on_sequence.h"
#include "base/memory/scoped_refptr.h"
#include "base/numerics/byte_conversions.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.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();
if (!factory)
return nullptr;
scoped_refptr<viz::RasterContextProvider> provider =
factory->GetContextFactory()->SharedMainThreadRasterContextProvider();
return provider ? provider->ContextSupport() : nullptr;
} else {
base::WeakPtr<blink::WebGraphicsContext3DProviderWrapper> wrapper =
blink::SharedGpuContext::ContextProviderWrapper();
if (!wrapper)
return nullptr;
auto& context_provider = wrapper->ContextProvider();
if (context_provider.IsContextLost())
return nullptr;
return context_provider.ContextSupport();
}
}
gpu::SharedImageInterface* GetSharedImageInterface() {
if (IsBrowserProcess()) {
auto* factory = content::ImageTransportFactory::GetInstance();
if (!factory)
return nullptr;
scoped_refptr<viz::RasterContextProvider> provider =
factory->GetContextFactory()->SharedMainThreadRasterContextProvider();
return provider ? provider->SharedImageInterface() : nullptr;
} else {
auto* provider = blink::SharedGpuContext::SharedImageInterfaceProvider();
return provider ? provider->SharedImageInterface() : nullptr;
}
}
std::string GetBase64StringFromSyncToken(gpu::SyncToken& sync_token) {
if (!sync_token.verified_flush()) {
if (auto* sii = GetSharedImageInterface())
sii->VerifySyncToken(sync_token);
}
auto sync_token_data = base::Base64Encode(UNSAFE_BUFFERS(
base::span(reinterpret_cast<uint8_t*>(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<const uint8_t> sync_token_span = UNSAFE_BUFFERS(
base::span(sync_token_bytes->data(), sync_token_bytes->size()));
auto* sync_token_source =
reinterpret_cast<const gpu::SyncToken*>(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";
case media::PIXEL_FORMAT_NV16:
return "nv16";
case media::PIXEL_FORMAT_P010LE:
return "p010le";
default:
NOTREACHED();
}
}
// VideoFrames holding a reference may die on any thread, but the GPU helpers
// and cppgc-backed |release_callback| are sequence-affine; delete on sequence.
struct ImportedSharedTexture
: base::RefCountedDeleteOnSequence<ImportedSharedTexture> {
ImportedSharedTexture()
: base::RefCountedDeleteOnSequence<ImportedSharedTexture>(
base::SequencedTaskRunner::GetCurrentDefault()) {}
// Metadata
gfx::Size coded_size;
gfx::Rect visible_rect;
int64_t timestamp = 0;
media::VideoPixelFormat pixel_format = media::PIXEL_FORMAT_UNKNOWN;
// Holds a reference to prevent it from being destroyed.
scoped_refptr<gpu::ClientSharedImage> 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();
// Safe to run or destroy on any thread; forwards the release sync token to
// UpdateReleaseSyncToken() on the owning sequence.
media::VideoFrame::ReleaseMailboxCB CreateFrameReleaseCallback();
// Transfer to other Chromium processes.
v8::Local<v8::Value> 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<v8::Value> 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<v8::Value> options);
// The cleanup happens at destructor.
private:
friend class base::RefCountedDeleteOnSequence<ImportedSharedTexture>;
friend class base::DeleteHelper<ImportedSharedTexture>;
~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<ImportedSharedTexture> ist;
// Monitor garbage collection.
std::unique_ptr<v8::Persistent<v8::Value>> persistent_;
void ResetPersistent() const { persistent_->Reset(); }
v8::Persistent<v8::Value>* CreatePersistent(v8::Isolate* isolate,
v8::Local<v8::Value> value) {
persistent_ = std::make_unique<v8::Persistent<v8::Value>>(isolate, value);
return persistent_.get();
}
// Create VideoFrame from the shared image.
v8::Local<v8::Value> 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();
}
media::VideoFrame::ReleaseMailboxCB
ImportedSharedTexture::CreateFrameReleaseCallback() {
return base::BindPostTask(
base::WrapRefCounted(owning_task_runner()),
base::BindOnce(&ImportedSharedTexture::UpdateReleaseSyncToken,
base::WrapRefCounted(this)));
}
v8::Local<v8::Value> 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 = ist->CreateFrameReleaseCallback();
scoped_refptr<media::VideoFrame> raw_frame =
media::VideoFrame::WrapSharedImage(
ist->pixel_format, si, ist->frame_creation_sync_token, std::move(cb),
ist->visible_rect, ist->coded_size,
base::Microseconds(ist->timestamp));
raw_frame->set_color_space(si->color_space());
blink::VideoFrame* frame = blink::MakeGarbageCollected<blink::VideoFrame>(
raw_frame, current_execution_context);
return blink::ToV8Traits<blink::VideoFrame>::ToV8(current_script_state,
frame);
}
v8::Local<v8::Value> 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<gpu::mojom::ExportedSharedImageDataView,
gpu::ExportedSharedImage>::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<v8::Value> 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<v8::Value> options) {
std::string sync_token_data;
gin::Dictionary dict(isolate, options.As<v8::Object>());
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 (sii && 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_);
if (!release_sync_token.HasData()) {
if (auto* sii = GetSharedImageInterface())
release_sync_token = sii->GenUnverifiedSyncToken();
}
if (release_sync_token.HasData())
client_shared_image->UpdateDestructionSyncToken(release_sync_token);
if (release_callback) {
if (auto* context_support = GetContextSupport()) {
context_support->SignalSyncToken(release_sync_token,
std::move(release_callback));
} else {
std::move(release_callback).Run();
}
}
}
void PersistentCallbackPass1(
const v8::WeakCallbackInfo<ImportedSharedTextureWrapper>& 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<v8::Value>& info) {
auto* isolate = info.GetIsolate();
auto* wrapper = static_cast<ImportedSharedTextureWrapper*>(
info.DataV2().As<v8::External>()->Value(
v8::kExternalPointerTypeTagDefault));
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<v8::Value>& info) {
auto* isolate = info.GetIsolate();
auto* wrapper = static_cast<ImportedSharedTextureWrapper*>(
info.DataV2().As<v8::External>()->Value(
v8::kExternalPointerTypeTagDefault));
if (wrapper->IsReferenceReleased()) {
gin_helper::ErrorThrower(isolate).ThrowTypeError(
"The shared texture has been released.");
return;
}
if (!GetSharedImageInterface()) {
gin_helper::ErrorThrower(isolate).ThrowError(
"Failed to start shared texture transfer: GPU is not available");
return;
}
auto ret = wrapper->ist->StartTransferSharedTexture(isolate);
info.GetReturnValue().Set(ret);
}
void ImportedTextureRelease(const v8::FunctionCallbackInfo<v8::Value>& info) {
auto* wrapper = static_cast<ImportedSharedTextureWrapper*>(
info.DataV2().As<v8::External>()->Value(
v8::kExternalPointerTypeTagDefault));
if (wrapper->IsReferenceReleased()) {
LOG(WARNING) << "This imported shared texture is already released.";
return;
}
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<v8::Value>& info) {
auto* isolate = info.GetIsolate();
auto* wrapper = static_cast<ImportedSharedTextureWrapper*>(
info.DataV2().As<v8::External>()->Value(
v8::kExternalPointerTypeTagDefault));
if (wrapper->IsReferenceReleased()) {
gin_helper::ErrorThrower(isolate).ThrowTypeError(
"The shared texture has been released.");
return;
}
if (!GetSharedImageInterface()) {
gin_helper::ErrorThrower(isolate).ThrowError(
"Failed to get frame creation sync token: GPU is not available");
return;
}
auto ret = wrapper->ist->GetFrameCreationSyncToken(isolate);
info.GetReturnValue().Set(ret);
}
void ImportedTextureSetReleaseSyncToken(
const v8::FunctionCallbackInfo<v8::Value>& info) {
auto* isolate = info.GetIsolate();
auto* wrapper = static_cast<ImportedSharedTextureWrapper*>(
info.DataV2().As<v8::External>()->Value(
v8::kExternalPointerTypeTagDefault));
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;
}
if (!GetSharedImageInterface()) {
gin_helper::ErrorThrower(isolate).ThrowError(
"Failed to set release sync token: GPU is not available");
return;
}
wrapper->ist->SetReleaseSyncToken(isolate, info[0].As<v8::Object>());
}
v8::Local<v8::Value> CreateImportedSharedTextureFromSharedImage(
v8::Isolate* isolate,
scoped_refptr<ImportedSharedTexture> imported) {
auto* wrapper = new ImportedSharedTextureWrapper();
wrapper->ist = std::move(imported);
// Every method below carries |imported_wrapped| as its [[Data]], so the
// external stays alive for as long as any of them is reachable. Tie the
// lifetime of |wrapper| to the external rather than to the returned
// dictionary so that a retained method can never observe a freed wrapper.
auto imported_wrapped =
v8::External::New(isolate, wrapper, v8::kExternalPointerTypeTagDefault);
auto* persistent = wrapper->CreatePersistent(isolate, imported_wrapped);
persistent->SetWeak(wrapper, PersistentCallbackPass1,
v8::WeakCallbackType::kParameter);
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);
return gin::ConvertToV8(isolate, root);
}
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<ImportSharedTextureInfoPlane> planes;
uint64_t modifier = gfx::NativePixmapHandle::kNoModifier;
bool supports_zero_copy_webgpu_import = false;
#endif
};
} // namespace
namespace gin {
template <>
struct Converter<ImportSharedTextureInfo> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
ImportSharedTextureInfo* out) {
if (!val->IsObject())
return false;
gin::Dictionary dict(isolate, val.As<v8::Object>());
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 if (pixel_format_str == "nv16")
out->pixel_format = media::PIXEL_FORMAT_NV16;
else if (pixel_format_str == "p010le")
out->pixel_format = media::PIXEL_FORMAT_P010LE;
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<v8::Object>());
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<v8::Value> 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<uintptr_t*>(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<v8::Object> native_pixmap;
if (shared_texture.Get("nativePixmap", &native_pixmap)) {
gin::Dictionary v8_native_pixmap(isolate, native_pixmap);
v8::Local<v8::Array> 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::Value> v8_item;
if (!v8_planes->Get(isolate->GetCurrentContext(), i)
.ToLocal(&v8_item) ||
!v8_item->IsObject()) {
return false;
}
gin::Dictionary v8_plane(isolate, v8_item.As<v8::Object>());
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<v8::Value> ImportSharedTexture(v8::Isolate* isolate,
v8::Local<v8::Value> 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<HANDLE>(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<IOSurfaceRef>(shared_texture.io_surface);
auto io_surface_scoped = base::apple::ScopedCFTypeRef<IOSurfaceRef>(
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 si_format = media::VideoPixelFormatToSharedImageFormat(pixel_format);
if (!si_format.has_value()) {
gin_helper::ErrorThrower(isolate).ThrowTypeError(
"Invalid shared texture buffer format");
return v8::Null(isolate);
}
auto* sii = GetSharedImageInterface();
if (!sii) {
gin_helper::ErrorThrower(isolate).ThrowError(
"Failed to import shared texture: GPU is not available");
return v8::Null(isolate);
}
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 =
sii->CreateSharedImage({si_format.value(), 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);
}
auto imported = base::MakeRefCounted<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,
std::move(imported));
}
void SetSharedTextureReceiver(v8::Isolate* isolate,
v8::Local<v8::Function> receiver) {
gin_helper::Dictionary global(isolate,
isolate->GetCurrentContext()->Global());
global.SetHidden("sharedTextureReceiver", receiver.As<v8::Value>());
}
v8::Local<v8::Value> FinishTransferSharedTexture(v8::Isolate* isolate,
v8::Local<v8::Value> 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<v8::Object>());
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<gpu::mojom::ExportedSharedImageDataView,
gpu::ExportedSharedImage>::Deserialize(data.data(),
&exported,
&message);
auto* sii = GetSharedImageInterface();
if (!sii) {
gin_helper::ErrorThrower(isolate).ThrowError(
"Failed to finish shared texture transfer: GPU is not available");
return v8::Null(isolate);
}
auto si = sii->ImportSharedImage(std::move(exported));
auto source_st = GetSyncTokenFromBase64String(sync_token_data);
sii->WaitSyncToken(source_st);
auto imported = base::MakeRefCounted<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,
std::move(imported));
}
} // namespace electron::api::shared_texture
namespace {
void Initialize(v8::Local<v8::Object> exports,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context,
void* priv) {
v8::Isolate* const isolate = v8::Isolate::GetCurrent();
gin_helper::Dictionary dict(isolate, exports);
dict.SetMethod<&electron::api::shared_texture::ImportSharedTexture>(
"importSharedTexture");
dict.SetMethod<&electron::api::shared_texture::FinishTransferSharedTexture>(
"finishTransferSharedTexture");
// Renderer: the callback ElectronApiServiceImpl::ReceiveSharedTexture()
// hands textures sent with sharedTexture.sendSharedTexture() to.
dict.SetMethod<&electron::api::shared_texture::SetSharedTextureReceiver>(
"setSharedTextureReceiver");
}
} // namespace
NODE_LINKED_BINDING_CONTEXT_AWARE(electron_common_shared_texture, Initialize)