Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions indra/llplugin/llpluginclassmedia.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@
#include "llpluginmessageclasses.h"
#include "llcontrol.h"

#if LL_WINDOWS
#include <process.h> // _getpid (host pid for accelerated-paint handle dup)
#else
#include <unistd.h> // getpid
#endif

extern LLControlGroup gSavedSettings;
#if LL_DARWIN || LL_LINUX
extern bool gHiDPISupport;
Expand All @@ -56,6 +62,11 @@ LLPluginClassMedia::LLPluginClassMedia(LLPluginClassMediaOwner *owner)
mOwner = owner;
reset();

// Unique per-media id for the macOS accelerated-paint mach-port demux. Media
// sources are created on the main thread, so a plain counter is fine.
static int sNextAccelId = 1;
mAccelId = sNextAccelId++;

//debug use
mDeleteOK = true ;
}
Expand All @@ -75,11 +86,25 @@ bool LLPluginClassMedia::init(const std::string &launcher_filename, const std::s

mPlugin = LLPluginProcessParent::create(this);
mPlugin->setSleepTime(mSleepTime);
mPlugin->setUseDaemon(mUseDaemon, mDaemonRendezvous);

// Queue up the media init message -- it will be sent after all the currently queued messages.
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "init");
message.setValue("target", mTarget);
message.setValueReal("factor", mZoomFactor);
// Zero-copy paint: ask for GPU shared-texture handles, and hand the plugin
// this (viewer) process id so it can DuplicateHandle the shared texture into
// us across the process boundary.
message.setValueBoolean("accelerated_paint", mUseAcceleratedPaint);
// macOS shares the accelerated-paint IOSurface over a mach channel; the plugin
// rendezvous via the bootstrap name derived from host_pid, and tags each frame
// with accel_id so the viewer demuxes it back to this media.
message.setValueS32("accel_id", mAccelId);
#if LL_WINDOWS
message.setValueS32("host_pid", (S32)_getpid());
#else
message.setValueS32("host_pid", (S32)getpid());
#endif
sendMessage(message);

mPlugin->init(launcher_filename, plugin_dir, plugin_filename, debug);
Expand Down Expand Up @@ -1049,6 +1074,30 @@ void LLPluginClassMedia::receivePluginMessage(const LLPluginMessage &message)

mTextureParamsReceived = true;
}
else if(message_name == "accelerated_paint")
{
// Zero-copy frame ready. The plugin holds one persistent keyed-mutex
// shared texture and sends its viewer-side handle ONLY when that
// texture is (re)created (handle != 0, once per size); a "0" handle
// means "same texture, new frame". Keep the last real handle so a
// per-frame ping doesn't clear it before the consumer takes it. The
// value is a decimal string so a 64-bit handle survives intact.
unsigned long long h = strtoull(message.getValue("handle").c_str(), nullptr, 10);
if (h != 0)
{
mAcceleratedPaintHandle = h;
}
mAcceleratedPaintFormat = message.getValueS32("format");
mAcceleratedPaintWidth = message.getValueS32("width");
mAcceleratedPaintHeight = message.getValueS32("height");
// Linux dma-buf layout (absent -> 0 on Windows/macOS).
mAcceleratedPaintStride = message.getValueS32("stride");
mAcceleratedPaintSrcPid = message.getValueS32("src_pid");
mAcceleratedPaintOffset = strtoull(message.getValue("offset").c_str(), nullptr, 10);
mAcceleratedPaintModifier = strtoull(message.getValue("modifier").c_str(), nullptr, 10);
mAcceleratedPaintDirty = true;
mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CONTENT_UPDATED);
}
else if(message_name == "updated")
{
if(message.hasValue("left"))
Expand Down
53 changes: 53 additions & 0 deletions indra/llplugin/llpluginclassmedia.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,42 @@ class LLPluginClassMedia : public LLPluginProcessParentOwner
bool getDisableTimeout() { return mPlugin?mPlugin->getDisableTimeout():false; };
void setDisableTimeout(bool disable) { if(mPlugin) mPlugin->setDisableTimeout(disable); };

// Route this media instance through the shared CEF daemon host, using a
// user-writable rendezvous path (see LLPluginProcessParent::setUseDaemon).
// Set before init().
void setUseDaemon(bool use_daemon, const std::string& rendezvous_path = std::string())
{ mUseDaemon = use_daemon; mDaemonRendezvous = rendezvous_path; if(mPlugin) mPlugin->setUseDaemon(use_daemon, rendezvous_path); };
bool getUseDaemon() const { return mUseDaemon; };

// Zero-copy paint: ask the plugin to deliver a GPU shared-texture handle
// (duplicated into this process) instead of CPU pixels. Set before init().
void setUseAcceleratedPaint(bool b) { mUseAcceleratedPaint = b; };
bool getUseAcceleratedPaint() const { return mUseAcceleratedPaint; };

// Per-media id sent to the plugin in init() and tagged onto each macOS
// IOSurface mach-port message, so the process-global viewer-side receiver can
// demux surfaces from many tabs/plugin processes back to the right media.
int getAccelId() const { return mAccelId; }

// The plugin's stable shared texture: a native handle already duplicated into
// THIS process (Windows: a D3D11 shared-texture HANDLE), plus its
// cef_color_type format and coded size. The handle is PERSISTENT - it is only
// (re)sent when the plugin recreates the texture (per size), so it is kept,
// not consumed: the consumer compares it against what it last bound and only
// re-opens when it changes. mAcceleratedPaintDirty marks a fresh frame.
bool getAcceleratedPaintDirty() const { return mAcceleratedPaintDirty; };
int getAcceleratedPaintFormat() const { return mAcceleratedPaintFormat; };
int getAcceleratedPaintWidth() const { return mAcceleratedPaintWidth; };
int getAcceleratedPaintHeight() const { return mAcceleratedPaintHeight; };
unsigned long long getAcceleratedPaintHandle() const { return mAcceleratedPaintHandle; };
void clearAcceleratedPaintDirty() { mAcceleratedPaintDirty = false; };
// Linux dma-buf layout (0 on Windows/macOS): the handle is an fd in process
// getAcceleratedPaintSrcPid(), with this plane stride/offset and DRM modifier.
int getAcceleratedPaintStride() const { return mAcceleratedPaintStride; };
unsigned long long getAcceleratedPaintOffset() const { return mAcceleratedPaintOffset; };
unsigned long long getAcceleratedPaintModifier() const { return mAcceleratedPaintModifier; };
int getAcceleratedPaintSrcPid() const { return mAcceleratedPaintSrcPid; };

// Inherited from LLPluginProcessParentOwner
/* virtual */ void receivePluginMessage(const LLPluginMessage &message);
/* virtual */ void pluginLaunchFailed();
Expand Down Expand Up @@ -430,6 +466,23 @@ class LLPluginClassMedia : public LLPluginProcessParentOwner


LLPluginProcessParent::ptr_t mPlugin;
bool mUseDaemon = false;
std::string mDaemonRendezvous;

// accelerated (zero-copy) paint - see setUseAcceleratedPaint / the getters above
bool mUseAcceleratedPaint = false;
// Unique per-media id (macOS mach-port demux); assigned at construction.
int mAccelId = 0;
unsigned long long mAcceleratedPaintHandle = 0; // native handle, dup'd into this process
int mAcceleratedPaintFormat = 0;
int mAcceleratedPaintWidth = 0;
int mAcceleratedPaintHeight = 0;
bool mAcceleratedPaintDirty = false;
// Linux dma-buf only:
int mAcceleratedPaintStride = 0;
unsigned long long mAcceleratedPaintOffset = 0;
unsigned long long mAcceleratedPaintModifier = 0;
int mAcceleratedPaintSrcPid = 0;

LLRect mDirtyRect;

Expand Down
73 changes: 45 additions & 28 deletions indra/llplugin/llplugininstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ LLPluginInstanceMessageListener::~LLPluginInstanceMessageListener()
*/
const char *LLPluginInstance::PLUGIN_INIT_FUNCTION_NAME = "LLPluginInitEntryPoint";

LLPluginInstance::pluginInitFunction LLPluginInstance::sStaticInitFunction = NULL;

// static
void LLPluginInstance::setStaticInitFunction(pluginInitFunction func)
{
sStaticInitFunction = func;
}

/**
* Constructor.
*
Expand Down Expand Up @@ -76,48 +84,57 @@ LLPluginInstance::~LLPluginInstance()
int LLPluginInstance::load(const std::string& plugin_dir, std::string &plugin_file)
{
pluginInitFunction init_function = NULL;

if ( plugin_dir.length() )
{
#if LL_WINDOWS
// VWR-21275:
// *SOME* Windows systems fail to load the Qt plugins if the current working
// directory is not the same as the directory with the Qt DLLs in.
// This should not cause any run time issues since we are changing the cwd for the
// plugin shell process and not the viewer.
// Changing back to the previous directory is not necessary since the plugin shell
// quits once the plugin exits.
_chdir( plugin_dir.c_str() );
#endif
};

int result = 0;

try
if (sStaticInitFunction)
{
mDSOHandle.load(boost::dll::fs::path(plugin_file),
boost::dll::load_mode::rtld_now);
// Dedicated single-plugin host: the plugin is statically linked into
// this executable, so call its entry point directly and skip dlopen
// (and the cwd hack below) entirely. plugin_dir/plugin_file are ignored.
init_function = sStaticInitFunction;
}
catch (const std::exception& e)
else
{
LL_WARNS("Plugin") << "boost::dll load of " << plugin_file
<< " failed: " << e.what() << LL_ENDL;
result = -1;
}
if ( plugin_dir.length() )
{
#if LL_WINDOWS
// VWR-21275:
// *SOME* Windows systems fail to load the Qt plugins if the current working
// directory is not the same as the directory with the Qt DLLs in.
// This should not cause any run time issues since we are changing the cwd for the
// plugin shell process and not the viewer.
// Changing back to the previous directory is not necessary since the plugin shell
// quits once the plugin exits.
_chdir( plugin_dir.c_str() );
#endif
};

if(result == 0)
{
try
{
init_function = &mDSOHandle.get<std::remove_pointer_t<pluginInitFunction>>(
PLUGIN_INIT_FUNCTION_NAME);
mDSOHandle.load(boost::dll::fs::path(plugin_file),
boost::dll::load_mode::rtld_now);
}
catch (const std::exception& e)
{
LL_WARNS("Plugin") << "symbol lookup for " << PLUGIN_INIT_FUNCTION_NAME
LL_WARNS("Plugin") << "boost::dll load of " << plugin_file
<< " failed: " << e.what() << LL_ENDL;
result = -1;
}

if(result == 0)
{
try
{
init_function = &mDSOHandle.get<std::remove_pointer_t<pluginInitFunction>>(
PLUGIN_INIT_FUNCTION_NAME);
}
catch (const std::exception& e)
{
LL_WARNS("Plugin") << "symbol lookup for " << PLUGIN_INIT_FUNCTION_NAME
<< " failed: " << e.what() << LL_ENDL;
result = -1;
}
}
}

if(result == 0)
Expand Down
11 changes: 11 additions & 0 deletions indra/llplugin/llplugininstance.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ class LLPluginInstance
/** Name of plugin init function */
static const char *PLUGIN_INIT_FUNCTION_NAME;

// Register a statically-linked plugin init entry point. When set, load()
// calls it directly instead of dlopen()ing a plugin library. Used by
// dedicated single-plugin host executables (see slplugin) to avoid dlopen
// of large TLS-using libraries (CEF on Linux) and to satisfy the Windows
// sandbox requirement that the host and its sub-processes be one image.
// Process-global: a host links exactly one plugin.
static void setStaticInitFunction(pluginInitFunction func);

private:
static void staticReceiveMessage(const char *message_string, void **user_data);
void receiveMessage(const char *message_string);
Expand All @@ -93,6 +101,9 @@ class LLPluginInstance
sendMessageFunction mPluginSendMessageFunction;

LLPluginInstanceMessageListener *mOwner;

// non-null in dedicated single-plugin host executables; see setStaticInitFunction
static pluginInitFunction sStaticInitFunction;
};

#endif // LL_LLPLUGININSTANCE_H
52 changes: 39 additions & 13 deletions indra/llplugin/llpluginprocesschild.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,28 @@ LLPluginProcessChild::~LLPluginProcessChild()
{
sendMessageToPlugin(LLPluginMessage("base", "cleanup"));

// IMPORTANT: under some (unknown) circumstances the library unload triggered when mInstance is deleted
// appears to fail and lock up which means that a given instance of the slplugin process never exits.
// This is bad, especially when users try to update their version of SL - it fails because the slplugin
// process as well as a bunch of plugin specific files are locked and cannot be overwritten.
exit(0);
//delete mInstance;
//mInstance = NULL;
if (mDaemonMode)
{
// Daemon tab: this process hosts many tabs, so exit() here would kill
// every other tab (the "close one media, all of them die" crash). The
// library-unload lockup the exit(0) below guards against cannot happen
// in the daemon either - daemon plugins are statically linked, so
// there is no DSO to unload and ~LLPluginInstance is a no-op. Delete
// the instance directly. (Normally the graceful unload path has
// already nulled mInstance and we never get here.)
delete mInstance;
mInstance = NULL;
}
else
{
// IMPORTANT: under some (unknown) circumstances the library unload triggered when mInstance is deleted
// appears to fail and lock up which means that a given instance of the slplugin process never exits.
// This is bad, especially when users try to update their version of SL - it fails because the slplugin
// process as well as a bunch of plugin specific files are locked and cannot be overwritten.
exit(0);
//delete mInstance;
//mInstance = NULL;
}
}
}

Expand All @@ -86,23 +101,34 @@ void LLPluginProcessChild::idle(void)
{ // Once we have hit the shutdown request state checking for errors might put us in a spurious
// error state... don't do that.

// A lost parent socket means this tab is going away. In the daemon a
// hard STATE_ERROR would reap the tab with its browser still live in
// the shared CEF runtime (and, pre-unload, leave mInstance set), which
// crashes every tab. Instead run the same graceful unload as a normal
// shutdown so the browser closes cleanly first, then the daemon reaps
// us. The single-process host keeps the old hard-error behaviour.
const EState socket_dead_state =
(mDaemonMode && mInstance != NULL) ? STATE_SHUTDOWNREQ : STATE_ERROR;

if (APR_STATUS_IS_EOF(mSocketError))
{
// Plugin socket was closed. This covers both normal plugin termination and host crashes.
setState(STATE_ERROR);
setState(socket_dead_state);
}
else if (mSocketError != APR_SUCCESS)
{
LL_INFOS("Plugin") << "message pipe is in error state (" << mSocketError << "), moving to STATE_ERROR" << LL_ENDL;
setState(STATE_ERROR);
LL_INFOS("Plugin") << "message pipe is in error state (" << mSocketError << "), moving to "
<< (socket_dead_state == STATE_SHUTDOWNREQ ? "STATE_SHUTDOWNREQ" : "STATE_ERROR") << LL_ENDL;
setState(socket_dead_state);
}

if ((mState > STATE_INITIALIZED) && (mMessagePipe == NULL))
if ((mState > STATE_INITIALIZED) && (mState < STATE_SHUTDOWNREQ) && (mMessagePipe == NULL))
{
// The pipe has been closed -- we're done.
// TODO: This could be slightly more subtle, but I'm not sure it needs to be.
LL_INFOS("Plugin") << "message pipe went away, moving to STATE_ERROR" << LL_ENDL;
setState(STATE_ERROR);
LL_INFOS("Plugin") << "message pipe went away, moving to "
<< (socket_dead_state == STATE_SHUTDOWNREQ ? "STATE_SHUTDOWNREQ" : "STATE_ERROR") << LL_ENDL;
setState(socket_dead_state);
}
}

Expand Down
8 changes: 8 additions & 0 deletions indra/llplugin/llpluginprocesschild.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ class LLPluginProcessChild: public LLPluginMessagePipeOwner, public LLPluginInst
void sleep(F64 seconds);
void pump();

// Mark this child as a tab inside the shared daemon host (one process serving
// many tabs). In daemon mode the child must never exit() the process on its
// own teardown (that would kill every other tab) and a lost parent socket is
// handled with a graceful plugin unload rather than a hard error. Set before
// the first idle().
void setDaemonMode(bool daemon) { mDaemonMode = daemon; }

// returns true if the plugin is in the steady state (processing messages)
bool isRunning(void);

Expand Down Expand Up @@ -104,6 +111,7 @@ class LLPluginProcessChild: public LLPluginMessagePipeOwner, public LLPluginInst
LLTimer mHeartbeat;
F64 mSleepTime;
F64 mCPUElapsed;
bool mDaemonMode = false;
bool mBlockingRequest;
bool mBlockingResponseReceived;
std::queue<std::string> mMessageQueue;
Expand Down
Loading
Loading