Files
electron/shell/common/api/electron_api_shared_texture.cc

823 lines
29 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 "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<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";
default:
NOTREACHED();
}
}
struct ImportedSharedTexture
: base::RefCountedThreadSafe<ImportedSharedTexture> {
// 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<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();
// 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::RefCountedThreadSafe<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();
}
// This function will be called when the VideoFrame is destructed.
void OnVideoFrameMailboxReleased(
const scoped_refptr<ImportedSharedTexture>& ist,
const gpu::SyncToken& sync_token) {
ist->UpdateReleaseSyncToken(sync_token);
}
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 = base::BindOnce(OnVideoFrameMailboxReleased, ist);
scoped_refptr<media::VideoFrame> 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<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 (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<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.Data().As<v8::External>()->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<v8::Value>& info) {
auto* isolate = info.GetIsolate();
auto* wrapper = static_cast<ImportedSharedTextureWrapper*>(
info.Data().As<v8::External>()->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<v8::Value>& info) {
auto* wrapper = static_cast<ImportedSharedTextureWrapper*>(
info.Data().As<v8::External>()->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<v8::Value>& info) {
auto* isolate = info.GetIsolate();
auto* wrapper = static_cast<ImportedSharedTextureWrapper*>(
info.Data().As<v8::External>()->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<v8::Value>& info) {
auto* isolate = info.GetIsolate();
auto* wrapper = static_cast<ImportedSharedTextureWrapper*>(
info.Data().As<v8::External>()->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::Object>());
}
v8::Local<v8::Value> 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<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
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 =
v8_planes->Get(isolate->GetCurrentContext(), i).ToLocalChecked();
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 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<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();
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<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("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)