mirror of
https://github.com/electron/electron.git
synced 2026-01-09 15:38:08 -05:00
* chore: bump chromium in DEPS to 145.0.7562.0 * fix(patch-conflict): update code cache patch for PersistentCache refactor Upstream refactored code cache to use PersistentCache with new class-based implementation (NoopCodeCacheHost, LocalCodeCacheHost, CodeCacheWithPersistentCacheHost). Updated patch to integrate custom scheme support into the new structure while preserving ProcessLockURLIsCodeCacheScheme checks for embedder-registered schemes. Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7044986 Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * fix(patch-conflict): update dialog patch for RequestXdgDesktopPortal API Upstream changed from SetSystemdScopeUnitNameForXdgPortal to RequestXdgDesktopPortal API pattern. Updated OnServiceStarted signature and kept OnSystemdUnitStarted callback that calls Electron's file_dialog::StartPortalAvailabilityTestInBackground(). Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7204285 Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * fix(patch-conflict): remove reference to deleted AbortByPlaceholderLayout flag Upstream removed the AbortByPlaceholderLayout runtime flag from runtime_enabled_features.json5. Updated patch to only add ElectronCSSCornerSmoothing without the removed flag reference. Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7226494 Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * chore: update patch hunk headers Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * fix(build): guard media_file_system_registry for ChromeOS only Upstream CL https://chromium-review.googlesource.com/c/chromium/src/+/7100719 moved media_file_system_registry to be ChromeOS-only since Media Galleries is a Chrome Apps API and Chrome Apps are only available on Chrome OS now. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * fix(build): update VideoPixelFormat API for SharedImageFormat Upstream CL https://chromium-review.googlesource.com/c/chromium/src/+/7207153 removed VideoPixelFormatToGfxBufferFormat as part of migration to SharedImageFormat. Update to use VideoPixelFormatToSharedImageFormat which directly returns the SharedImageFormat. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * fix(build): extend profile methods patch for ShouldEnableXfaForms The ShouldEnableXfaForms function uses Profile::FromBrowserContext() which is not available in Electron. Wrap the profile-dependent code in #if 0 to fall through to the feature flag default. Co-Authored-By: Claude <noreply@anthropic.com> * chore: bump chromium in DEPS to 145.0.7563.0 * chore: bump chromium in DEPS to 145.0.7565.0 * chore: bump chromium in DEPS to 145.0.7567.0 * chore: bump chromium in DEPS to 145.0.7568.0 * fix(patch-conflict): update content_main_delegate.h context for IsInitFeatureListEarly Upstream added a new IsInitFeatureListEarly() virtual method to ContentMainDelegate just before where our GetBrowserV8SnapshotFilename() method is added. Updated patch context to account for this new method. Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7092856 Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * chore: update patch hunk headers Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * fix(patch-update): include v8-cppgc.h for CppHeap complete type The std::unique_ptr<v8::CppHeap> default argument in node.h requires the complete CppHeap type definition for the destructor. Added the v8-cppgc.h include to provide the full type definition. Ref: Unable to locate CL - libc++ unique_ptr requires complete type for destructor Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * chore: update patch hunk headers Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * fix(build): move NativeAppWindowFrameViewMacClient before constructor The std::unique_ptr<NativeAppWindowFrameViewMacClient> member requires the complete type definition to be visible at the point of the constructor because the unique_ptr destructor may be instantiated during exception handling. Moved the class definition before the NativeWindowMac constructor. Ref: Unable to locate CL - libc++ unique_ptr requires complete type for destructor Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * fix(patch-conflict): update create_browser_v8_snapshot_file_name_fuse context for IsInitFeatureListEarly The upstream added IsInitFeatureListEarly() virtual method declaration to ContentMainDelegate class. Updated the patch context to account for this new function being present before the GetBrowserV8SnapshotFilename() declaration we add. Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7092856 Co-Authored-By: Claude <noreply@anthropic.com> * chore: update patch hunk headers Co-Authored-By: Claude <noreply@anthropic.com> * fix(patch-update): remove reverted IsInitFeatureListEarly from v8 snapshot patch The upstream added IsInitFeatureListEarly() was reverted, so the patch should not include this declaration. Only GetBrowserV8SnapshotFilename() should be added by the create_browser_v8_snapshot_file_name_fuse patch. Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7230430 Co-Authored-By: Claude <noreply@anthropic.com> * 6171655: include single_thread_task_runner.h for complete type Added include for base/task/single_thread_task_runner.h in osr_converter.cc to resolve incomplete type error when using base::SingleThreadTaskRunner::GetCurrentDefault(). Ref: https://chromium-review.googlesource.com/c/chromium/src/+/6171655 Co-Authored-By: Claude <noreply@anthropic.com> * 7224136: use CHROMIUM_GIT_REVISION directly instead of removed function Upstream removed GetChromiumGitRevision() function from embedder_support. Updated to use CHROMIUM_GIT_REVISION macro directly via build/util/chromium_git_revision.h as recommended in the Chromium CL. Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7224136 Co-Authored-By: Claude <noreply@anthropic.com> * fixup! 7224136: use CHROMIUM_GIT_REVISION directly instead of removed function * fix(build): add missing include `components/dbus/xdg/systemd.h` for `void OnSystemdUnitStarted(dbus_xdg::SystemdUnitStatus)` in the same patch. * fix(build): adapt to string-view-ification change in windows jump_list.cc 7186922: Fix unsafe buffer usage in base/win/win_util.cc https://chromium-review.googlesource.com/c/chromium/src/+/7186922 * chore: update libc++ filenames * fixup! fix(build): add missing include * fixup! fix(build): extend profile methods patch for ShouldEnableXfaForms * fixup! fix(build): guard media_file_system_registry for ChromeOS only * fixup! fixup! fix(build): extend profile methods patch for ShouldEnableXfaForms --------- Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com> Co-authored-by: Keeley Hammond <khammond@slack-corp.com> Co-authored-by: Claude <svc-devxp-claude@slack-corp.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: clavin <clavin@electronjs.org>
820 lines
29 KiB
C++
820 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 "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 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();
|
|
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);
|
|
}
|
|
|
|
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)
|