From 88d907c9f272eba71c24cfdfc92f1e2267456c9f Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Tue, 31 Mar 2026 23:25:18 -0700 Subject: [PATCH 1/8] Use handles to GPU Textures for CEF instead of copying the buffer to CPU space First part - windows --- indra/llplugin/llpluginclassmedia.cpp | 30 +- indra/llplugin/llpluginclassmedia.h | 11 +- indra/llplugin/llpluginclassmediaowner.h | 4 +- indra/llrender/CMakeLists.txt | 4 + indra/llrender/llgl.cpp | 61 ++++ indra/llrender/llgl.h | 3 + indra/llrender/llglheaders.h | 8 + indra/llrender/llgltexture.cpp | 342 ++++++++++++++++++ indra/llrender/llgltexture.h | 17 +- indra/llrender/llimagegl.cpp | 5 +- indra/media_plugins/cef/CMakeLists.txt | 4 + indra/media_plugins/cef/media_plugin_cef.cpp | 116 ++++++ indra/media_plugins/example/CMakeLists.txt | 7 +- .../example/media_plugin_example.cpp | 54 +++ indra/newview/llviewermedia.cpp | 59 ++- 15 files changed, 716 insertions(+), 9 deletions(-) diff --git a/indra/llplugin/llpluginclassmedia.cpp b/indra/llplugin/llpluginclassmedia.cpp index 4e80f4c0caf..be011db6019 100644 --- a/indra/llplugin/llpluginclassmedia.cpp +++ b/indra/llplugin/llpluginclassmedia.cpp @@ -67,7 +67,7 @@ LLPluginClassMedia::~LLPluginClassMedia() reset(); } -bool LLPluginClassMedia::init(const std::string &launcher_filename, const std::string &plugin_dir, const std::string &plugin_filename, bool debug) +bool LLPluginClassMedia::init(const std::string &launcher_filename, const std::string &plugin_dir, const std::string &plugin_filename, bool debug, S32 adapter_luid_high, U32 adapter_luid_low) { LL_DEBUGS("Plugin") << "launcher: " << launcher_filename << LL_ENDL; LL_DEBUGS("Plugin") << "dir: " << plugin_dir << LL_ENDL; @@ -80,6 +80,11 @@ bool LLPluginClassMedia::init(const std::string &launcher_filename, const std::s LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "init"); message.setValue("target", mTarget); message.setValueReal("factor", mZoomFactor); +#if LL_WINDOWS + message.setValueU32("viewer_pid", (U32)GetCurrentProcessId()); + message.setValueS32("adapter_luid_high", adapter_luid_high); + message.setValueU32("adapter_luid_low", adapter_luid_low); +#endif sendMessage(message); mPlugin->init(launcher_filename, plugin_dir, plugin_filename, debug); @@ -142,6 +147,8 @@ void LLPluginClassMedia::reset() mMediaName.clear(); mMediaDescription.clear(); mBackgroundColor = LLColor4(1.0f, 1.0f, 1.0f, 1.0f); + mAcceleratedTextureHandle = nullptr; + mAcceleratedTextureId = 0; // media_browser class mNavigateURI.clear(); @@ -1275,6 +1282,19 @@ void LLPluginClassMedia::receivePluginMessage(const LLPluginMessage &message) { mHoverText = message.getValue("tooltip"); } + else if (message_name == "accelerated_update") + { + mAcceleratedTextureHandle = message.getValuePointer("handle"); + mAcceleratedTextureId = message.getValueU32("texture_id"); + + // For the accelerated path, the texture dimensions are the full + // media size (what was given to CEF via setSize), not the dirty + // rect bounds which may be smaller + mTextureWidth = mMediaWidth; + mTextureHeight = mMediaHeight; + + mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_ACCELERATED_UPDATE); + } else { LL_WARNS("Plugin") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL; @@ -1401,6 +1421,14 @@ void LLPluginClassMedia::mediaEvent(LLPluginClassMediaOwner::EMediaEvent event) } } +void LLPluginClassMedia::releaseAcceleratedTextureHandle() +{ + if (mAcceleratedTextureHandle) + { + mAcceleratedTextureHandle = nullptr; + } +} + void LLPluginClassMedia::sendMessage(const LLPluginMessage &message) { if(mPlugin && mPlugin->isRunning()) diff --git a/indra/llplugin/llpluginclassmedia.h b/indra/llplugin/llpluginclassmedia.h index 40f6295e556..f1891271ac2 100644 --- a/indra/llplugin/llpluginclassmedia.h +++ b/indra/llplugin/llpluginclassmedia.h @@ -46,7 +46,9 @@ class LLPluginClassMedia : public LLPluginProcessParentOwner bool init(const std::string &launcher_filename, const std::string &plugin_dir, const std::string &plugin_filename, - bool debug); + bool debug, + S32 adapter_luid_high = 0, + U32 adapter_luid_low = 0); // undoes everything init() didm called by the media manager when destroying a source void reset(); @@ -315,6 +317,10 @@ class LLPluginClassMedia : public LLPluginProcessParentOwner std::string getHoverText() const { return mHoverText; }; std::string getHoverLink() const { return mHoverLink; }; + // Valid after MEDIA_EVENT_ACCELERATED_UPDATE + void* getAcceleratedTextureHandle() const { return mAcceleratedTextureHandle; }; + void releaseAcceleratedTextureHandle(); + // these are valid during MEDIA_EVENT_LINK_HOVERED std::string getFileDownloadFilename() const { return mFileDownloadFilename; } @@ -479,6 +485,9 @@ class LLPluginClassMedia : public LLPluginProcessParentOwner std::string mFileDownloadFilename; bool mIsMultipleFilePick; + void* mAcceleratedTextureHandle; + U32 mAcceleratedTextureId; + ///////////////////////////////////////// // media_time class F64 mCurrentTime; diff --git a/indra/llplugin/llpluginclassmediaowner.h b/indra/llplugin/llpluginclassmediaowner.h index a75d0b9960e..1b9d18454c8 100644 --- a/indra/llplugin/llpluginclassmediaowner.h +++ b/indra/llplugin/llpluginclassmediaowner.h @@ -67,7 +67,9 @@ class LLPluginClassMediaOwner MEDIA_EVENT_DEBUG_MESSAGE, // plugin sending back debug information for host to process - MEDIA_EVENT_LINK_HOVERED // Got a "link hovered" event from the plugin + MEDIA_EVENT_LINK_HOVERED, // Got a "link hovered" event from the plugin + + MEDIA_EVENT_ACCELERATED_UPDATE // Accelerated (GPU) texture handle update from the plugin } EMediaEvent; diff --git a/indra/llrender/CMakeLists.txt b/indra/llrender/CMakeLists.txt index fcd287bbb39..a861bd671d4 100644 --- a/indra/llrender/CMakeLists.txt +++ b/indra/llrender/CMakeLists.txt @@ -106,3 +106,7 @@ target_link_libraries(llrender OpenGL::GLU ) +if (WINDOWS) + target_link_libraries(llrender d3d11 dxgi) +endif (WINDOWS) + diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index 4584ed1d865..fd43cc0d112 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -56,6 +56,8 @@ #if LL_WINDOWS #include "lldxhardware.h" +#include +#include #endif #ifdef _DEBUG @@ -236,6 +238,14 @@ PFNWGLBLITCONTEXTFRAMEBUFFERAMDPROC wglBlitContextFramebufferAMD = n PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = nullptr; PFNWGLGETSWAPINTERVALEXTPROC wglGetSwapIntervalEXT = nullptr; +// WGL_NV_DX_interop +PFNWGLDXOPENDEVICENVPROC wglDXOpenDeviceNV = nullptr; +PFNWGLDXCLOSEDEVICENVPROC wglDXCloseDeviceNV = nullptr; +PFNWGLDXREGISTEROBJECTNVPROC wglDXRegisterObjectNV = nullptr; +PFNWGLDXUNREGISTEROBJECTNVPROC wglDXUnregisterObjectNV = nullptr; +PFNWGLDXLOCKOBJECTSNVPROC wglDXLockObjectsNV = nullptr; +PFNWGLDXUNLOCKOBJECTSNVPROC wglDXUnlockObjectsNV = nullptr; + #endif // GL_VERSION_1_2 @@ -1058,6 +1068,57 @@ void LLGLManager::initWGL() { LL_WARNS("RenderInit") << "No ARB WGL render texture extensions" << LL_ENDL; } + + mHasNVDXInterop = ExtensionExists("WGL_NV_DX_interop", gGLHExts.mSysExts); + if (mHasNVDXInterop) + { + wglDXOpenDeviceNV = (PFNWGLDXOPENDEVICENVPROC)GLH_EXT_GET_PROC_ADDRESS("wglDXOpenDeviceNV"); + wglDXCloseDeviceNV = (PFNWGLDXCLOSEDEVICENVPROC)GLH_EXT_GET_PROC_ADDRESS("wglDXCloseDeviceNV"); + wglDXRegisterObjectNV = (PFNWGLDXREGISTEROBJECTNVPROC)GLH_EXT_GET_PROC_ADDRESS("wglDXRegisterObjectNV"); + wglDXUnregisterObjectNV = (PFNWGLDXUNREGISTEROBJECTNVPROC)GLH_EXT_GET_PROC_ADDRESS("wglDXUnregisterObjectNV"); + wglDXLockObjectsNV = (PFNWGLDXLOCKOBJECTSNVPROC)GLH_EXT_GET_PROC_ADDRESS("wglDXLockObjectsNV"); + wglDXUnlockObjectsNV = (PFNWGLDXUNLOCKOBJECTSNVPROC)GLH_EXT_GET_PROC_ADDRESS("wglDXUnlockObjectsNV"); + + // Discover which adapter the GL context lives on by probing wglDXOpenDeviceNV + IDXGIFactory1* dxgi_factory = nullptr; + if (SUCCEEDED(CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&dxgi_factory))) + { + IDXGIAdapter* adapter = nullptr; + for (UINT i = 0; dxgi_factory->EnumAdapters(i, &adapter) != DXGI_ERROR_NOT_FOUND; ++i) + { + ID3D11Device* probe_device = nullptr; + HRESULT hr = D3D11CreateDevice( + adapter, D3D_DRIVER_TYPE_UNKNOWN, + nullptr, 0, nullptr, 0, + D3D11_SDK_VERSION, + &probe_device, nullptr, nullptr); + + if (SUCCEEDED(hr) && probe_device) + { + HANDLE probe_handle = wglDXOpenDeviceNV(probe_device); + if (probe_handle) + { + // This adapter matches the GL context + DXGI_ADAPTER_DESC adesc; + if (SUCCEEDED(adapter->GetDesc(&adesc))) + { + mGLAdapterLuidHigh = adesc.AdapterLuid.HighPart; + mGLAdapterLuidLow = adesc.AdapterLuid.LowPart; + LL_INFOS("RenderInit") << "GL adapter LUID: " + << std::hex << mGLAdapterLuidHigh << ":" << mGLAdapterLuidLow << LL_ENDL; + } + wglDXCloseDeviceNV(probe_handle); + probe_device->Release(); + adapter->Release(); + break; + } + probe_device->Release(); + } + adapter->Release(); + } + dxgi_factory->Release(); + } + } #endif } diff --git a/indra/llrender/llgl.h b/indra/llrender/llgl.h index e1ab2a49e6d..a3f57753697 100644 --- a/indra/llrender/llgl.h +++ b/indra/llrender/llgl.h @@ -100,6 +100,9 @@ class LLGLManager // Vendor-specific extensions bool mHasAMDAssociations = false; bool mHasNVXGpuMemoryInfo = false; + bool mHasNVDXInterop = false; + S32 mGLAdapterLuidHigh = 0; + U32 mGLAdapterLuidLow = 0; bool mIsAMD; bool mIsNVIDIA; diff --git a/indra/llrender/llglheaders.h b/indra/llrender/llglheaders.h index 3d4dc5e6982..d6a929cf449 100644 --- a/indra/llrender/llglheaders.h +++ b/indra/llrender/llglheaders.h @@ -74,6 +74,14 @@ extern PFNWGLGETSWAPINTERVALEXTPROC wglGetSwapIntervalEXT; // WGL_ARB_create_context extern PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB; +// WGL_NV_DX_interop +extern PFNWGLDXOPENDEVICENVPROC wglDXOpenDeviceNV; +extern PFNWGLDXCLOSEDEVICENVPROC wglDXCloseDeviceNV; +extern PFNWGLDXREGISTEROBJECTNVPROC wglDXRegisterObjectNV; +extern PFNWGLDXUNREGISTEROBJECTNVPROC wglDXUnregisterObjectNV; +extern PFNWGLDXLOCKOBJECTSNVPROC wglDXLockObjectsNV; +extern PFNWGLDXUNLOCKOBJECTSNVPROC wglDXUnlockObjectsNV; + // GL_VERSION_1_3 extern PFNGLACTIVETEXTUREPROC glActiveTexture; extern PFNGLSAMPLECOVERAGEPROC glSampleCoverage; diff --git a/indra/llrender/llgltexture.cpp b/indra/llrender/llgltexture.cpp index 4dcca5a726a..55d39beb7c8 100644 --- a/indra/llrender/llgltexture.cpp +++ b/indra/llrender/llgltexture.cpp @@ -25,7 +25,12 @@ */ #include "linden_common.h" #include "llgltexture.h" +#include "llimagegl.h" +#if LL_WINDOWS +#include +#include +#endif LLGLTexture::LLGLTexture(bool usemipmaps) { @@ -77,6 +82,9 @@ void LLGLTexture::init() void LLGLTexture::cleanup() { +#if LL_WINDOWS + releaseInteropResources(); +#endif if(mGLTexturep) { mGLTexturep->cleanup(); @@ -367,6 +375,340 @@ void LLGLTexture::destroyGLTexture() } } +bool LLGLTexture::createGLTextureFromHandle(void* handle, S32 width, S32 height, LLGLuint* tex_name) +{ +#if LL_WINDOWS + if (!gGLManager.mHasNVDXInterop) + { + LL_WARNS("Texture") << "WGL_NV_DX_interop not available" << LL_ENDL; + return false; + } + + ID3D11Device1* d3d_device = (ID3D11Device1*)mInteropDevice; + ID3D11DeviceContext* d3d_context = (ID3D11DeviceContext*)mInteropContext; + HANDLE gl_device = mInteropGLDevice; + + // Lazily create the D3D device and GL interop device — reuse across frames + if (!d3d_device) + { + IDXGIAdapter* gl_adapter = nullptr; + IDXGIFactory1* factory = nullptr; + HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&factory); + if (SUCCEEDED(hr) && factory) + { + if (gGLManager.mGLAdapterLuidHigh || gGLManager.mGLAdapterLuidLow) + { + IDXGIAdapter* adapter = nullptr; + for (UINT i = 0; factory->EnumAdapters(i, &adapter) != DXGI_ERROR_NOT_FOUND; ++i) + { + DXGI_ADAPTER_DESC adesc; + if (SUCCEEDED(adapter->GetDesc(&adesc)) && + adesc.AdapterLuid.HighPart == (LONG)gGLManager.mGLAdapterLuidHigh && + adesc.AdapterLuid.LowPart == (DWORD)gGLManager.mGLAdapterLuidLow) + { + gl_adapter = adapter; + break; + } + adapter->Release(); + } + } + factory->Release(); + } + + ID3D11Device* base_device = nullptr; + hr = D3D11CreateDevice( + gl_adapter, + gl_adapter ? D3D_DRIVER_TYPE_UNKNOWN : D3D_DRIVER_TYPE_HARDWARE, + nullptr, 0, nullptr, 0, + D3D11_SDK_VERSION, + &base_device, nullptr, &d3d_context); + + if (gl_adapter) + { + gl_adapter->Release(); + } + + if (FAILED(hr) || !base_device) + { + LL_WARNS("Texture") << "Failed to create D3D11 device, hr=0x" << std::hex << hr << LL_ENDL; + return false; + } + + hr = base_device->QueryInterface(__uuidof(ID3D11Device1), (void**)&d3d_device); + base_device->Release(); + if (FAILED(hr) || !d3d_device) + { + LL_WARNS("Texture") << "Failed to get ID3D11Device1, hr=0x" << std::hex << hr << LL_ENDL; + d3d_context->Release(); + return false; + } + + gl_device = wglDXOpenDeviceNV(d3d_device); + if (!gl_device) + { + LL_WARNS("Texture") << "wglDXOpenDeviceNV failed" << LL_ENDL; + d3d_context->Release(); + d3d_device->Release(); + return false; + } + + mInteropDevice = d3d_device; + mInteropContext = d3d_context; + mInteropGLDevice = gl_device; + } + + // Open the shared texture — NT handle from CEF via DuplicateHandle + ID3D11Texture2D* src_texture = nullptr; + HRESULT hr = d3d_device->OpenSharedResource1((HANDLE)handle, __uuidof(ID3D11Texture2D), (void**)&src_texture); + if (FAILED(hr) || !src_texture) + { + LL_WARNS("Texture") << "OpenSharedResource1 failed, handle=0x" << std::hex << (uintptr_t)handle + << " hr=0x" << hr << LL_ENDL; + return false; + } + + D3D11_TEXTURE2D_DESC desc; + src_texture->GetDesc(&desc); + + // (Re)create the copy texture if dimensions changed or first call + ID3D11Texture2D* d3d_texture = (ID3D11Texture2D*)mInteropTexture; + bool need_new_texture = !d3d_texture; + if (d3d_texture) + { + D3D11_TEXTURE2D_DESC existing_desc; + d3d_texture->GetDesc(&existing_desc); + if (existing_desc.Width != (UINT)width || existing_desc.Height != (UINT)height || existing_desc.Format != desc.Format) + { + need_new_texture = true; + } + } + + if (need_new_texture) + { + // Build the new texture and interop BEFORE tearing down the old one, + // so a failure leaves the previous frame intact + D3D11_TEXTURE2D_DESC copy_desc = {}; + copy_desc.Width = width; + copy_desc.Height = height; + copy_desc.MipLevels = 1; + copy_desc.ArraySize = 1; + copy_desc.Format = desc.Format; + copy_desc.SampleDesc.Count = 1; + copy_desc.SampleDesc.Quality = 0; + copy_desc.Usage = D3D11_USAGE_DEFAULT; + copy_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + ID3D11Texture2D* new_texture = nullptr; + hr = d3d_device->CreateTexture2D(©_desc, nullptr, &new_texture); + if (FAILED(hr) || !new_texture) + { + LL_WARNS("Texture") << "Failed to create copy texture, hr=0x" << std::hex << hr << LL_ENDL; + src_texture->Release(); + return (mInteropGLHandle != nullptr); // keep old frame if available + } + + LLGLuint new_gl_name = 0; + LLImageGL::generateTextures(1, &new_gl_name); + if (!new_gl_name) + { + LL_WARNS("Texture") << "Failed to generate GL texture name" << LL_ENDL; + new_texture->Release(); + src_texture->Release(); + return (mInteropGLHandle != nullptr); + } + + HANDLE new_gl_handle = wglDXRegisterObjectNV(gl_device, new_texture, new_gl_name, GL_TEXTURE_2D, WGL_ACCESS_READ_ONLY_NV); + if (!new_gl_handle) + { + LL_WARNS("Texture") << "wglDXRegisterObjectNV failed" << LL_ENDL; + LLImageGL::deleteTextures(1, &new_gl_name); + new_texture->Release(); + src_texture->Release(); + return (mInteropGLHandle != nullptr); + } + + if (!wglDXLockObjectsNV(gl_device, 1, &new_gl_handle)) + { + LL_WARNS("Texture") << "wglDXLockObjectsNV failed after registration" << LL_ENDL; + wglDXUnregisterObjectNV(gl_device, new_gl_handle); + LLImageGL::deleteTextures(1, &new_gl_name); + new_texture->Release(); + src_texture->Release(); + return (mInteropGLHandle != nullptr); + } + + // New resources are ready — now tear down the old ones + if (mInteropGLHandle) + { + wglDXUnlockObjectsNV(gl_device, 1, &mInteropGLHandle); + wglDXUnregisterObjectNV(gl_device, mInteropGLHandle); + } + if (d3d_texture) + { + d3d_texture->Release(); + } + + d3d_texture = new_texture; + mInteropTexture = new_texture; + mInteropGLHandle = new_gl_handle; + mInteropSrcTex = new_gl_name; + + // Create persistent output texture for the flipped result + if (mInteropOutputTex) + { + LLImageGL::deleteTextures(1, &mInteropOutputTex); + } + LLImageGL::generateTextures(1, &mInteropOutputTex); + glBindTexture(GL_TEXTURE_2D, mInteropOutputTex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D, 0); + + // Create FBO for the blit + if (mInteropBlitFBO) + { + glDeleteFramebuffers(1, &mInteropBlitFBO); + } + glGenFramebuffers(1, &mInteropBlitFBO); + + if (mGLTexturep.isNull()) + { + generateGLTexture(); + } + mGLTexturep->setTexName(mInteropOutputTex); + mGLTexturep->setGLTextureCreated(true); + + mFullWidth = width; + mFullHeight = height; + mComponents = 4; + setTexelsPerImage(); + } + + // Unlock GL, copy D3D, re-lock GL — the lock/unlock handles D3D↔GL sync + HANDLE gl_handle = mInteropGLHandle; + wglDXUnlockObjectsNV(gl_device, 1, &gl_handle); + + // Acquire keyed mutex on the source if present — use 0 timeout to avoid stalling + IDXGIKeyedMutex* keyed_mutex = nullptr; + src_texture->QueryInterface(__uuidof(IDXGIKeyedMutex), (void**)&keyed_mutex); + if (keyed_mutex) + { + hr = keyed_mutex->AcquireSync(0, 0); + if (FAILED(hr)) + { + // Texture not ready yet — skip this frame, keep previous content + keyed_mutex->Release(); + src_texture->Release(); + wglDXLockObjectsNV(gl_device, 1, &gl_handle); + return true; + } + } + + D3D11_BOX src_box = {}; + src_box.left = 0; + src_box.right = desc.Width; + src_box.top = 0; + src_box.bottom = desc.Height; + src_box.front = 0; + src_box.back = 1; + d3d_context->CopySubresourceRegion(d3d_texture, 0, 0, 0, 0, src_texture, 0, &src_box); + + if (keyed_mutex) + { + keyed_mutex->ReleaseSync(0); + keyed_mutex->Release(); + } + src_texture->Release(); + + // Re-lock interop for GL access + wglDXLockObjectsNV(gl_device, 1, &gl_handle); + + // Blit from interop texture to output texture with vertical flip + // Blit from interop texture to output texture with vertical flip + GLint prev_read_fbo = 0, prev_draw_fbo = 0; + glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &prev_read_fbo); + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &prev_draw_fbo); + + glBindFramebuffer(GL_FRAMEBUFFER, mInteropBlitFBO); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mInteropSrcTex, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, mInteropOutputTex, 0); + glReadBuffer(GL_COLOR_ATTACHMENT0); + glDrawBuffer(GL_COLOR_ATTACHMENT1); + + // Blit only the content region (desc.Width x desc.Height), not the power-of-two padding. + // Flip Y and place at bottom-left of the destination (GL row 0 = bottom). + glBlitFramebuffer( + 0, 0, desc.Width, desc.Height, // src: content region + 0, desc.Height, desc.Width, 0, // dst: flipped into bottom-left + GL_COLOR_BUFFER_BIT, GL_NEAREST); + + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, 0, 0); + glBindFramebuffer(GL_READ_FRAMEBUFFER, prev_read_fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, prev_draw_fbo); + + if (tex_name) + { + *tex_name = mInteropOutputTex; + } + + return true; +#else + LL_WARNS("Texture") << "createGLTextureFromHandle is only supported on Windows" << LL_ENDL; + return false; +#endif +} + +#if LL_WINDOWS +void LLGLTexture::releaseInteropResources() +{ + if (mInteropBlitFBO) + { + glDeleteFramebuffers(1, &mInteropBlitFBO); + mInteropBlitFBO = 0; + } + if (mInteropOutputTex) + { + LLImageGL::deleteTextures(1, &mInteropOutputTex); + mInteropOutputTex = 0; + } + if (mInteropSrcTex) + { + LLImageGL::deleteTextures(1, &mInteropSrcTex); + mInteropSrcTex = 0; + } + if (mInteropGLHandle) + { + wglDXUnlockObjectsNV(mInteropGLDevice, 1, &mInteropGLHandle); + wglDXUnregisterObjectNV(mInteropGLDevice, mInteropGLHandle); + mInteropGLHandle = nullptr; + } + if (mInteropGLDevice) + { + wglDXCloseDeviceNV(mInteropGLDevice); + mInteropGLDevice = nullptr; + } + if (mInteropTexture) + { + ((ID3D11Texture2D*)mInteropTexture)->Release(); + mInteropTexture = nullptr; + } + if (mInteropContext) + { + ((ID3D11DeviceContext*)mInteropContext)->Release(); + mInteropContext = nullptr; + } + if (mInteropDevice) + { + ((ID3D11Device1*)mInteropDevice)->Release(); + mInteropDevice = nullptr; + } +} +#endif + void LLGLTexture::setTexelsPerImage() { U32 fullwidth = llmin(mFullWidth,U32(MAX_IMAGE_SIZE_DEFAULT)); diff --git a/indra/llrender/llgltexture.h b/indra/llrender/llgltexture.h index 122d2a7f9ca..adaaf49acb7 100644 --- a/indra/llrender/llgltexture.h +++ b/indra/llrender/llgltexture.h @@ -138,6 +138,10 @@ class LLGLTexture : public LLTexture void setTexName(LLGLuint); // for forcing w/ externally created textures only void setTarget(const LLGLenum target, const LLTexUnit::eTextureType bind_target); + bool createGLTextureFromHandle(void* handle, S32 width, S32 height, LLGLuint* tex_name = nullptr); + bool hasInteropTexture() const { return mInteropGLHandle != nullptr; } + void releaseInteropResources(); + LLTexUnit::eTextureAddressMode getAddressMode(void) const ; S32 getMaxDiscardLevel() const; S32 getDiscardLevel() const; @@ -194,8 +198,19 @@ class LLGLTexture : public LLTexture protected: LLGLTextureState mTextureState ; - +#if LL_WINDOWS + // NV_DX_interop resources kept alive while the GL texture is in use + void* mInteropDevice = nullptr; // ID3D11Device1* + void* mInteropContext = nullptr; // ID3D11DeviceContext* + void* mInteropTexture = nullptr; // ID3D11Texture2D* + void* mInteropGLDevice = nullptr; // HANDLE from wglDXOpenDeviceNV + void* mInteropGLHandle = nullptr; // HANDLE from wglDXRegisterObjectNV + LLGLuint mInteropSrcTex = 0; // GL name from NV_DX_interop registration + LLGLuint mInteropBlitFBO = 0; // FBO for flip blit + LLGLuint mInteropOutputTex = 0; // Persistent flipped GL texture +#endif }; #endif // LL_GL_TEXTURE_H + diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index 4a3d32c7ffc..0cfe4db54d8 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -1165,7 +1165,7 @@ bool LLImageGL::setSubImage(const U8* datap, S32 data_width, S32 data_height, S3 << " getHeight() " << getHeight() << LL_ENDL; } - + if ((x_pos + width) > data_width || (y_pos + height) > data_height) { @@ -1180,7 +1180,6 @@ bool LLImageGL::setSubImage(const U8* datap, S32 data_width, S32 data_height, S3 << LL_ENDL; } - glPixelStorei(GL_UNPACK_ROW_LENGTH, data_width); stop_glerror(); @@ -1489,7 +1488,7 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt } //create an empty GL texture: just create a texture name -//the texture is assiciate with some image by calling glTexImage outside LLImageGL +//the texture is associated with some image by calling glTexImage outside LLImageGL bool LLImageGL::createGLTexture() { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; diff --git a/indra/media_plugins/cef/CMakeLists.txt b/indra/media_plugins/cef/CMakeLists.txt index dc2d82017d0..0fb0e4a0b9a 100644 --- a/indra/media_plugins/cef/CMakeLists.txt +++ b/indra/media_plugins/cef/CMakeLists.txt @@ -54,6 +54,10 @@ target_link_libraries(media_plugin_cef ll::cef ) +if (WINDOWS) + target_link_libraries(media_plugin_cef d3d11 dxgi) +endif (WINDOWS) + if (WINDOWS) set_target_properties( media_plugin_cef diff --git a/indra/media_plugins/cef/media_plugin_cef.cpp b/indra/media_plugins/cef/media_plugin_cef.cpp index caf804f915d..e64b4f531e3 100644 --- a/indra/media_plugins/cef/media_plugin_cef.cpp +++ b/indra/media_plugins/cef/media_plugin_cef.cpp @@ -41,10 +41,13 @@ // _getpid()/getpid() #if LL_WINDOWS #include +#include +#include #else #include #endif +#include #include "dullahan.h" //////////////////////////////////////////////////////////////////////////////// @@ -63,6 +66,7 @@ class MediaPluginCEF : bool init(); void onPageChangedCallback(const unsigned char* pixels, int x, int y, const int width, const int height); + void onAcceleratedPageChangedCallback(void* handle, const std::vector& dirty_rects); void onCustomSchemeURLCallback(std::string url, bool user_gesture, bool is_redirect); void onConsoleMessageCallback(std::string message, std::string source, int line); void onStatusMessageCallback(std::string value); @@ -120,6 +124,15 @@ class MediaPluginCEF : VolumeCatcher mVolumeCatcher; F32 mCurVolume; dullahan* mCEFLib; +#if LL_WINDOWS + DWORD mViewerProcessID; + LUID mViewerAdapterLuid; + ID3D11Device* mD3DDevice; + ID3D11Device1* mD3DDevice1; + ID3D11DeviceContext* mD3DContext; + U32 mNextTextureId; + std::unordered_map mCopyTextures; +#endif }; //////////////////////////////////////////////////////////////////////////////// @@ -162,6 +175,15 @@ MediaPluginBase(host_send_func, host_user_data) mCEFLib = new dullahan(); +#if LL_WINDOWS + mViewerProcessID = 0; + mViewerAdapterLuid = {}; + mD3DDevice = nullptr; + mD3DDevice1 = nullptr; + mD3DContext = nullptr; + mNextTextureId = 1; +#endif + setVolume(); } @@ -170,6 +192,32 @@ MediaPluginBase(host_send_func, host_user_data) MediaPluginCEF::~MediaPluginCEF() { mCEFLib->shutdown(); + +#if LL_WINDOWS + for (auto& pair : mCopyTextures) + { + if (pair.second) + { + pair.second->Release(); + } + } + mCopyTextures.clear(); + if (mD3DDevice1) + { + mD3DDevice1->Release(); + mD3DDevice1 = nullptr; + } + if (mD3DContext) + { + mD3DContext->Release(); + mD3DContext = nullptr; + } + if (mD3DDevice) + { + mD3DDevice->Release(); + mD3DDevice = nullptr; + } +#endif } //////////////////////////////////////////////////////////////////////////////// @@ -206,6 +254,49 @@ void MediaPluginCEF::onPageChangedCallback(const unsigned char* pixels, int x, i } } +//////////////////////////////////////////////////////////////////////////////// +// +void MediaPluginCEF::onAcceleratedPageChangedCallback(void* handle, const std::vector& dirty_rects) +{ +#if LL_WINDOWS + // DuplicateHandle CEF's NT handle into the viewer process + HANDLE viewer_handle = nullptr; + HANDLE viewer_process = OpenProcess(PROCESS_DUP_HANDLE, FALSE, mViewerProcessID); + if (viewer_process) + { + DuplicateHandle( + GetCurrentProcess(), + handle, + viewer_process, + &viewer_handle, + 0, + FALSE, + DUPLICATE_SAME_ACCESS); + CloseHandle(viewer_process); + } + + if (!viewer_handle) + { + return; + } + + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "accelerated_update"); + message.setValuePointer("handle", viewer_handle); + message.setValueS32("dirty_count", (S32)dirty_rects.size()); + + for (size_t i = 0; i < dirty_rects.size(); ++i) + { + std::string prefix = "dirty_" + std::to_string(i) + "_"; + message.setValueS32(prefix + "x", dirty_rects[i].x); + message.setValueS32(prefix + "y", dirty_rects[i].y); + message.setValueS32(prefix + "width", dirty_rects[i].width); + message.setValueS32(prefix + "height", dirty_rects[i].height); + } + + sendMessage(message); +#endif +} + //////////////////////////////////////////////////////////////////////////////// // void MediaPluginCEF::onConsoleMessageCallback(std::string message, std::string source, int line) @@ -612,8 +703,14 @@ void MediaPluginCEF::receiveMessage(const char* message_string) { if (message_name == "init") { +#if LL_WINDOWS + mViewerProcessID = message_in.getValueU32("viewer_pid"); + mViewerAdapterLuid.HighPart = (LONG)message_in.getValueS32("adapter_luid_high"); + mViewerAdapterLuid.LowPart = (DWORD)message_in.getValueU32("adapter_luid_low"); +#endif // event callbacks from Dullahan mCEFLib->setOnPageChangedCallback(std::bind(&MediaPluginCEF::onPageChangedCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); + mCEFLib->setOnAcceleratedPageChangedCallback(std::bind(&MediaPluginCEF::onAcceleratedPageChangedCallback, this, std::placeholders::_1, std::placeholders::_2)); mCEFLib->setOnCustomSchemeURLCallback(std::bind(&MediaPluginCEF::onCustomSchemeURLCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); mCEFLib->setOnConsoleMessageCallback(std::bind(&MediaPluginCEF::onConsoleMessageCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); mCEFLib->setOnStatusMessageCallback(std::bind(&MediaPluginCEF::onStatusMessageCallback, this, std::placeholders::_1)); @@ -711,6 +808,10 @@ void MediaPluginCEF::receiveMessage(const char* message_string) settings.log_verbose = mCefLogVerbose; settings.autoplay_without_gesture = true; + settings.use_adapter_luid = true; + settings.adapter_luid.high_part = mViewerAdapterLuid.HighPart; + settings.adapter_luid.low_part = mViewerAdapterLuid.LowPart; + std::vector custom_schemes(1, "secondlife"); mCEFLib->setCustomSchemes(custom_schemes); @@ -933,6 +1034,21 @@ void MediaPluginCEF::receiveMessage(const char* message_string) { mEnableMediaPluginDebugging = message_in.getValueBoolean("enable"); } +#if LL_WINDOWS0 + else if (message_name == "accelerated_release") + { + U32 texture_id = message_in.getValueU32("texture_id"); + auto it = mCopyTextures.find(texture_id); + if (it != mCopyTextures.end()) + { + if (it->second) + { + it->second->Release(); + } + mCopyTextures.erase(it); + } + } +#endif if (message_name == "pick_file_response") { LLSD file_list_llsd = message_in.getValueLLSD("file_list"); diff --git a/indra/media_plugins/example/CMakeLists.txt b/indra/media_plugins/example/CMakeLists.txt index be8ffe5a40a..67a0287d686 100644 --- a/indra/media_plugins/example/CMakeLists.txt +++ b/indra/media_plugins/example/CMakeLists.txt @@ -10,6 +10,7 @@ include(Linking) include(PluginAPI) include(ExamplePlugin) +include(CEFPlugin) ### media_plugin_example @@ -22,7 +23,11 @@ add_library(media_plugin_example ${media_plugin_example_SOURCE_FILES} ) -target_link_libraries(media_plugin_example media_plugin_base ) +target_link_libraries(media_plugin_example media_plugin_base ll::cef) + +if (WINDOWS) + target_link_libraries(media_plugin_example d3d11 dxgi) +endif (WINDOWS) if (WINDOWS) set_target_properties( diff --git a/indra/media_plugins/example/media_plugin_example.cpp b/indra/media_plugins/example/media_plugin_example.cpp index 0b22b7833ff..6231b3f7b78 100644 --- a/indra/media_plugins/example/media_plugin_example.cpp +++ b/indra/media_plugins/example/media_plugin_example.cpp @@ -34,6 +34,13 @@ #include "llpluginmessageclasses.h" #include "media_plugin_base.h" +#if LL_WINDOWS +#include +#include +#endif + +#include "dullahan.h" + #include //////////////////////////////////////////////////////////////////////////////// @@ -50,8 +57,15 @@ class mediaPluginExample : private: bool init(); void update(F64 milliseconds); + void determineAdapterLuid(); bool mFirstTime; + dullahan* mCEFLib; +#if LL_WINDOWS + S32 mAdapterLuidHigh; + U32 mAdapterLuidLow; +#endif + time_t mLastUpdateTime; enum Constants { ENumObjects = 64 }; unsigned char* mBackgroundPixels; @@ -79,6 +93,12 @@ MediaPluginBase(host_send_func, host_user_data) mPixels = 0; mLastUpdateTime = 0; mBackgroundPixels = 0; + + mCEFLib = new dullahan(); +#if LL_WINDOWS + mAdapterLuidHigh = 0; + mAdapterLuidLow = 0; +#endif } //////////////////////////////////////////////////////////////////////////////// @@ -369,6 +389,30 @@ void mediaPluginExample::update(F64 milliseconds) setDirty(0, 0, mWidth, mHeight); }; +//////////////////////////////////////////////////////////////////////////////// +// +void mediaPluginExample::determineAdapterLuid() +{ +#if LL_WINDOWS + IDXGIFactory1* dxgi_factory = nullptr; + if (SUCCEEDED(CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&dxgi_factory))) + { + IDXGIAdapter* adapter = nullptr; + if (SUCCEEDED(dxgi_factory->EnumAdapters(0, &adapter))) + { + DXGI_ADAPTER_DESC adesc; + if (SUCCEEDED(adapter->GetDesc(&adesc))) + { + mAdapterLuidHigh = adesc.AdapterLuid.HighPart; + mAdapterLuidLow = adesc.AdapterLuid.LowPart; + } + adapter->Release(); + } + dxgi_factory->Release(); + } +#endif +} + //////////////////////////////////////////////////////////////////////////////// // bool mediaPluginExample::init() @@ -377,6 +421,16 @@ bool mediaPluginExample::init() message.setValue("name", "Example Plugin"); sendMessage(message); + determineAdapterLuid(); + + dullahan::dullahan_settings settings; +#if LL_WINDOWS + settings.use_adapter_luid = true; + settings.adapter_luid.high_part = mAdapterLuidHigh; + settings.adapter_luid.low_part = mAdapterLuidLow; +#endif + mCEFLib->init(settings); + return true; }; diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index be4961e3c44..6521845c7b7 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -39,7 +39,11 @@ #include "llfilepicker.h" #include "llfloaterwebcontent.h" // for handling window close requests and geometry change requests in media browser windows. #include "llfocusmgr.h" +#include "llgl.h" #include "llimagegl.h" +#if LL_WINDOWS +#include "llwin32headers.h" +#endif #include "llkeyboard.h" #include "lllogininstance.h" #include "llmarketplacefunctions.h" @@ -1879,7 +1883,8 @@ LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_ media_source->setTarget(target); const std::string plugin_dir = gDirUtilp->getLLPluginDir(); - if (media_source->init(launcher_name, plugin_dir, plugin_name, gSavedSettings.getBOOL("PluginAttachDebuggerToPlugins"))) + if (media_source->init(launcher_name, plugin_dir, plugin_name, gSavedSettings.getBOOL("PluginAttachDebuggerToPlugins"), + gGLManager.mGLAdapterLuidHigh, gGLManager.mGLAdapterLuidLow)) { return media_source; } @@ -3101,6 +3106,13 @@ void LLViewerMediaImpl::doMediaTexUpdate(LLViewerMediaTexture* media_tex, U8* da LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; LLCoros::LockType lock(mLock); // don't allow media source tear-down during update + // If the accelerated (D3D interop) texture is active, skip the software path — + // the GL texture is updated on the main thread via MEDIA_EVENT_ACCELERATED_UPDATE + if (media_tex->hasInteropTexture()) + { + return; + } + // wrap "data" in an LLImageRaw but do NOT make a copy LLPointer raw = new LLImageRaw(data, media_tex->getWidth(), media_tex->getHeight(), media_tex->getComponents(), true); @@ -3152,6 +3164,12 @@ LLViewerMediaTexture* LLViewerMediaImpl::updateMediaImage() // *TODO: Consider enabling mipmaps (they have been disabled for a long time). Likely has a significant performance impact for tiled/high texture repeat media. Mip generation in a shader may also be an option if necessary. LLViewerMediaTexture* media_tex = LLViewerTextureManager::getMediaTexture( mTextureId, USE_MIPMAPS ); + // Don't destroy/recreate the texture when it's backed by D3D interop + if (media_tex->hasInteropTexture()) + { + return media_tex; + } + if ( mNeedsNewTexture || (media_tex->getWidth() != mMediaSource->getTextureWidth()) || (media_tex->getHeight() != mMediaSource->getTextureHeight()) @@ -3164,6 +3182,7 @@ LLViewerMediaTexture* LLViewerMediaImpl::updateMediaImage() int texture_width = mMediaSource->getTextureWidth(); int texture_height = mMediaSource->getTextureHeight(); + int texture_depth = mMediaSource->getTextureDepth(); // MEDIAOPT: check to see if size actually changed before doing work @@ -3613,6 +3632,44 @@ void LLViewerMediaImpl::handleMediaEvent(LLPluginClassMedia* plugin, LLPluginCla } break; + case LLViewerMediaObserver::MEDIA_EVENT_ACCELERATED_UPDATE: + { + void* accel_handle = plugin->getAcceleratedTextureHandle(); + if (accel_handle) + { + LLViewerMediaTexture* media_tex = LLViewerTextureManager::getMediaTexture(mTextureId, USE_MIPMAPS); + if (media_tex) + { + // Pass power-of-two dimensions so the copy texture matches + // what the renderer expects (content in top-left sub-region) + S32 tex_width = plugin->getTextureWidth(); + S32 tex_height = plugin->getTextureHeight(); + + LLGLuint tex_name = 0; + if (media_tex->createGLTextureFromHandle(accel_handle, tex_width, tex_height, &tex_name)) + { + media_tex->getGLTexture()->syncTexName(tex_name); + + mTextureUsedWidth = plugin->getWidth(); + mTextureUsedHeight = plugin->getHeight(); + mNeedsNewTexture = false; + } + else + { + // Handle was stale (e.g. resize in progress) — release interop + // so the system can recover on the next successful frame + media_tex->releaseInteropResources(); + mNeedsNewTexture = true; + } + } +#if LL_WINDOWS + CloseHandle(accel_handle); +#endif + plugin->releaseAcceleratedTextureHandle(); + } + } + break; + default: break; } From 11e65f4790416ea6042036a32efb87e066a7e6c0 Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Tue, 31 Mar 2026 23:32:23 -0700 Subject: [PATCH 2/8] Guard CEF adapter LUID settings behind LL_WINDOWS The adapter_luid functionality is Windows-only, matching the existing guards on the member declaration, initialization, and message passing. Co-Authored-By: Claude Opus 4.6 (1M context) --- indra/media_plugins/cef/media_plugin_cef.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indra/media_plugins/cef/media_plugin_cef.cpp b/indra/media_plugins/cef/media_plugin_cef.cpp index e64b4f531e3..9a006e0e92a 100644 --- a/indra/media_plugins/cef/media_plugin_cef.cpp +++ b/indra/media_plugins/cef/media_plugin_cef.cpp @@ -808,9 +808,11 @@ void MediaPluginCEF::receiveMessage(const char* message_string) settings.log_verbose = mCefLogVerbose; settings.autoplay_without_gesture = true; +#if LL_WINDOWS settings.use_adapter_luid = true; settings.adapter_luid.high_part = mViewerAdapterLuid.HighPart; settings.adapter_luid.low_part = mViewerAdapterLuid.LowPart; +#endif std::vector custom_schemes(1, "secondlife"); mCEFLib->setCustomSchemes(custom_schemes); From 69fb3ccdc0584bc23d3bff9ba7905ec40782417e Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Wed, 1 Apr 2026 00:53:05 -0700 Subject: [PATCH 3/8] Enable shared texture for cef media plugins --- indra/media_plugins/cef/media_plugin_cef.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/media_plugins/cef/media_plugin_cef.cpp b/indra/media_plugins/cef/media_plugin_cef.cpp index 9a006e0e92a..7dedf9f7b19 100644 --- a/indra/media_plugins/cef/media_plugin_cef.cpp +++ b/indra/media_plugins/cef/media_plugin_cef.cpp @@ -812,6 +812,7 @@ void MediaPluginCEF::receiveMessage(const char* message_string) settings.use_adapter_luid = true; settings.adapter_luid.high_part = mViewerAdapterLuid.HighPart; settings.adapter_luid.low_part = mViewerAdapterLuid.LowPart; + settings.shared_texture_enable = true; #endif std::vector custom_schemes(1, "secondlife"); From 789fa9787c5ad2f955054d10b1d2c5185203f1be Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Wed, 1 Apr 2026 01:11:55 -0700 Subject: [PATCH 4/8] Update to dullahan with accelerated painting. --- autobuild.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 17d35d82e95..fd70e16c7f9 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -450,11 +450,11 @@ archive hash - 126e0fa4c16dfd433c9fb7d1d242da98f213d933 + 2a4147260900b4a8ebc781cd8d0dc385b9983c89 hash_algorithm sha1 url - https://github.com/secondlife/dullahan/releases/download/v1.24.0-CEF_139.0.40/dullahan-1.24.0.202510081737_139.0.40_g465474a_chromium-139.0.7258.139-darwin64-18353103947.tar.zst + https://github.com/secondlife/dullahan/releases/download/v1.26.1-CEF_139.0.40.accel/dullahan-1.26.0.202604010801_139.0.40_g465474a_chromium-139.0.7258.139-darwin64-23838332395.tar.zst name darwin64 @@ -464,11 +464,11 @@ archive hash - 209d031ae67bc66d8e8f8509c71d259014c65ceb + 6f8d73431199fb0f669ca510ae1bdb5d797cd31e hash_algorithm sha1 url - https://github.com/secondlife/dullahan/releases/download/v1.14.0-r3/dullahan-1.14.0.202408091637_118.4.1_g3dd6078_chromium-118.0.5993.54-linux64-10322607516.tar.zst + https://github.com/secondlife/dullahan/releases/download/v1.26.1-CEF_139.0.40.accel/dullahan-1.26.0.202604010801_139.0.40_g465474a_chromium-139.0.7258.139-linux64-23838332395.tar.zst name linux64 @@ -478,11 +478,11 @@ archive hash - 20de62c9e57d9e6539c1e2437ec4b46c3ca237bc + 7a477ea33efaf1e6cea662ea602de914a718771e hash_algorithm sha1 url - https://github.com/secondlife/dullahan/releases/download/v1.24.0-CEF_139.0.40/dullahan-1.24.0.202510081738_139.0.40_g465474a_chromium-139.0.7258.139-windows64-18353103947.tar.zst + https://github.com/secondlife/dullahan/releases/download/v1.26.1-CEF_139.0.40.accel/dullahan-1.26.0.202604010803_139.0.40_g465474a_chromium-139.0.7258.139-windows64-23838332395.tar.zst name windows64 @@ -495,7 +495,7 @@ copyright Copyright (c) 2017, Linden Research, Inc. version - 1.24.0.202510081737_139.0.40_g465474a_chromium-139.0.7258.139 + 1.26.0.202604010801_139.0.40_g465474a_chromium-139.0.7258.139 name dullahan description From 60094e4fe754da1f149934626d93882cd7805d2a Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Wed, 1 Apr 2026 10:26:28 -0700 Subject: [PATCH 5/8] Only enable CEF shared textures when WGL_NV_DX_interop is available Pass gGLManager.mHasNVDXInterop from the viewer through the plugin init message so the CEF plugin can conditionally set shared_texture_enable. When the extension is not available, Dullahan falls back to the standard OnPaint software rendering path. Co-Authored-By: Claude Opus 4.6 (1M context) --- indra/llplugin/llpluginclassmedia.cpp | 3 ++- indra/llplugin/llpluginclassmedia.h | 3 ++- indra/media_plugins/cef/media_plugin_cef.cpp | 5 ++++- indra/newview/llviewermedia.cpp | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/indra/llplugin/llpluginclassmedia.cpp b/indra/llplugin/llpluginclassmedia.cpp index be011db6019..6ad457d194b 100644 --- a/indra/llplugin/llpluginclassmedia.cpp +++ b/indra/llplugin/llpluginclassmedia.cpp @@ -67,7 +67,7 @@ LLPluginClassMedia::~LLPluginClassMedia() reset(); } -bool LLPluginClassMedia::init(const std::string &launcher_filename, const std::string &plugin_dir, const std::string &plugin_filename, bool debug, S32 adapter_luid_high, U32 adapter_luid_low) +bool LLPluginClassMedia::init(const std::string &launcher_filename, const std::string &plugin_dir, const std::string &plugin_filename, bool debug, S32 adapter_luid_high, U32 adapter_luid_low, bool shared_texture_enable) { LL_DEBUGS("Plugin") << "launcher: " << launcher_filename << LL_ENDL; LL_DEBUGS("Plugin") << "dir: " << plugin_dir << LL_ENDL; @@ -85,6 +85,7 @@ bool LLPluginClassMedia::init(const std::string &launcher_filename, const std::s message.setValueS32("adapter_luid_high", adapter_luid_high); message.setValueU32("adapter_luid_low", adapter_luid_low); #endif + message.setValueBoolean("shared_texture_enable", shared_texture_enable); sendMessage(message); mPlugin->init(launcher_filename, plugin_dir, plugin_filename, debug); diff --git a/indra/llplugin/llpluginclassmedia.h b/indra/llplugin/llpluginclassmedia.h index f1891271ac2..a2e2ebd3b2a 100644 --- a/indra/llplugin/llpluginclassmedia.h +++ b/indra/llplugin/llpluginclassmedia.h @@ -48,7 +48,8 @@ class LLPluginClassMedia : public LLPluginProcessParentOwner const std::string &plugin_filename, bool debug, S32 adapter_luid_high = 0, - U32 adapter_luid_low = 0); + U32 adapter_luid_low = 0, + bool shared_texture_enable = false); // undoes everything init() didm called by the media manager when destroying a source void reset(); diff --git a/indra/media_plugins/cef/media_plugin_cef.cpp b/indra/media_plugins/cef/media_plugin_cef.cpp index 7dedf9f7b19..b018c7b3ef7 100644 --- a/indra/media_plugins/cef/media_plugin_cef.cpp +++ b/indra/media_plugins/cef/media_plugin_cef.cpp @@ -127,6 +127,7 @@ class MediaPluginCEF : #if LL_WINDOWS DWORD mViewerProcessID; LUID mViewerAdapterLuid; + bool mSharedTextureEnable; ID3D11Device* mD3DDevice; ID3D11Device1* mD3DDevice1; ID3D11DeviceContext* mD3DContext; @@ -178,6 +179,7 @@ MediaPluginBase(host_send_func, host_user_data) #if LL_WINDOWS mViewerProcessID = 0; mViewerAdapterLuid = {}; + mSharedTextureEnable = false; mD3DDevice = nullptr; mD3DDevice1 = nullptr; mD3DContext = nullptr; @@ -707,6 +709,7 @@ void MediaPluginCEF::receiveMessage(const char* message_string) mViewerProcessID = message_in.getValueU32("viewer_pid"); mViewerAdapterLuid.HighPart = (LONG)message_in.getValueS32("adapter_luid_high"); mViewerAdapterLuid.LowPart = (DWORD)message_in.getValueU32("adapter_luid_low"); + mSharedTextureEnable = message_in.getValueBoolean("shared_texture_enable"); #endif // event callbacks from Dullahan mCEFLib->setOnPageChangedCallback(std::bind(&MediaPluginCEF::onPageChangedCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); @@ -812,7 +815,7 @@ void MediaPluginCEF::receiveMessage(const char* message_string) settings.use_adapter_luid = true; settings.adapter_luid.high_part = mViewerAdapterLuid.HighPart; settings.adapter_luid.low_part = mViewerAdapterLuid.LowPart; - settings.shared_texture_enable = true; + settings.shared_texture_enable = mSharedTextureEnable; #endif std::vector custom_schemes(1, "secondlife"); diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index 6521845c7b7..e06b08b6b9b 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -1884,7 +1884,7 @@ LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_ const std::string plugin_dir = gDirUtilp->getLLPluginDir(); if (media_source->init(launcher_name, plugin_dir, plugin_name, gSavedSettings.getBOOL("PluginAttachDebuggerToPlugins"), - gGLManager.mGLAdapterLuidHigh, gGLManager.mGLAdapterLuidLow)) + gGLManager.mGLAdapterLuidHigh, gGLManager.mGLAdapterLuidLow, gGLManager.mHasNVDXInterop)) { return media_source; } From cfcf5bc98e82a2e06081edf66773d2b7b0683cdb Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Thu, 2 Apr 2026 10:53:21 -0700 Subject: [PATCH 6/8] Remove hardcoded 2048 media texture size clamp Let CEF texture size match the viewer window size instead of clamping to 2048. The accelerated path clamps to the GPU max texture size (GL_MAX_TEXTURE_SIZE) as a safety limit. Co-Authored-By: Claude Opus 4.6 (1M context) --- indra/llplugin/llpluginclassmedia.cpp | 10 ---------- indra/newview/llviewermedia.cpp | 4 ++-- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/indra/llplugin/llpluginclassmedia.cpp b/indra/llplugin/llpluginclassmedia.cpp index 6ad457d194b..53c78c9f43e 100644 --- a/indra/llplugin/llpluginclassmedia.cpp +++ b/indra/llplugin/llpluginclassmedia.cpp @@ -381,16 +381,6 @@ void LLPluginClassMedia::setSizeInternal(void) mRequestedMediaHeight = nextPowerOf2(mRequestedMediaHeight); } -#if LL_DARWIN - if (!gHiDPISupport) -#endif - { - if (mRequestedMediaWidth > 2048) - mRequestedMediaWidth = 2048; - - if (mRequestedMediaHeight > 2048) - mRequestedMediaHeight = 2048; - } } void LLPluginClassMedia::setAutoScale(bool auto_scale) diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index e06b08b6b9b..f22c75bb31b 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -3642,8 +3642,8 @@ void LLViewerMediaImpl::handleMediaEvent(LLPluginClassMedia* plugin, LLPluginCla { // Pass power-of-two dimensions so the copy texture matches // what the renderer expects (content in top-left sub-region) - S32 tex_width = plugin->getTextureWidth(); - S32 tex_height = plugin->getTextureHeight(); + S32 tex_width = llmin(plugin->getTextureWidth(), gGLManager.mGLMaxTextureSize); + S32 tex_height = llmin(plugin->getTextureHeight(), gGLManager.mGLMaxTextureSize); LLGLuint tex_name = 0; if (media_tex->createGLTextureFromHandle(accel_handle, tex_width, tex_height, &tex_name)) From 792daec23635175798d7a636cf29a1d5c4bee9f6 Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Wed, 1 Apr 2026 01:18:08 -0700 Subject: [PATCH 7/8] mInteropGLHandle is a windows only thing --- indra/llrender/llgltexture.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/indra/llrender/llgltexture.h b/indra/llrender/llgltexture.h index adaaf49acb7..6eb931792ad 100644 --- a/indra/llrender/llgltexture.h +++ b/indra/llrender/llgltexture.h @@ -139,7 +139,13 @@ class LLGLTexture : public LLTexture void setTarget(const LLGLenum target, const LLTexUnit::eTextureType bind_target); bool createGLTextureFromHandle(void* handle, S32 width, S32 height, LLGLuint* tex_name = nullptr); - bool hasInteropTexture() const { return mInteropGLHandle != nullptr; } + bool hasInteropTexture() const { +#ifdef LL_WINDOWS + return mInteropGLHandle != nullptr; +#else + return false; +#endif + } void releaseInteropResources(); LLTexUnit::eTextureAddressMode getAddressMode(void) const ; From 5e8ca89369871828180930e052a7123e2cc51528 Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Wed, 1 Apr 2026 01:35:36 -0700 Subject: [PATCH 8/8] Mac build fixes --- indra/llrender/llgltexture.cpp | 4 ++-- indra/newview/llmediactrl.cpp | 6 ++++++ indra/newview/llviewerparcelmedia.cpp | 6 ++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/indra/llrender/llgltexture.cpp b/indra/llrender/llgltexture.cpp index 55d39beb7c8..e67c15cf76b 100644 --- a/indra/llrender/llgltexture.cpp +++ b/indra/llrender/llgltexture.cpp @@ -662,9 +662,9 @@ bool LLGLTexture::createGLTextureFromHandle(void* handle, S32 width, S32 height, #endif } -#if LL_WINDOWS void LLGLTexture::releaseInteropResources() { +#if LL_WINDOWS if (mInteropBlitFBO) { glDeleteFramebuffers(1, &mInteropBlitFBO); @@ -706,8 +706,8 @@ void LLGLTexture::releaseInteropResources() ((ID3D11Device1*)mInteropDevice)->Release(); mInteropDevice = nullptr; } -} #endif +} void LLGLTexture::setTexelsPerImage() { diff --git a/indra/newview/llmediactrl.cpp b/indra/newview/llmediactrl.cpp index 679157dc1e3..5914aa4a189 100644 --- a/indra/newview/llmediactrl.cpp +++ b/indra/newview/llmediactrl.cpp @@ -1007,6 +1007,12 @@ void LLMediaCtrl::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) }; break; + case MEDIA_EVENT_ACCELERATED_UPDATE: + { + // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_ACCELERATED_UPDATE " << LL_ENDL; + }; + break; + case MEDIA_EVENT_TIME_DURATION_UPDATED: { // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_TIME_DURATION_UPDATED, time is " << self->getCurrentTime() << " of " << self->getDuration() << LL_ENDL; diff --git a/indra/newview/llviewerparcelmedia.cpp b/indra/newview/llviewerparcelmedia.cpp index 9af0062f1ef..25d6a0de80f 100644 --- a/indra/newview/llviewerparcelmedia.cpp +++ b/indra/newview/llviewerparcelmedia.cpp @@ -488,6 +488,12 @@ void LLViewerParcelMedia::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent }; break; + case MEDIA_EVENT_ACCELERATED_UPDATE: + { + // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_ACCELERATED_UPDATE " << LL_ENDL; + }; + break; + case MEDIA_EVENT_TIME_DURATION_UPDATED: { // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_TIME_DURATION_UPDATED, time is " << self->getCurrentTime() << " of " << self->getDuration() << LL_ENDL;