From d6db1a0fcfacaf95afcf8277add5c0bea822ffe4 Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Sat, 28 Mar 2026 21:17:02 -0700 Subject: [PATCH 1/5] Add OnAcceleratedPaint support with D3D11/GL interop for GPU-resident texture sharing Enable CEF's shared texture mode and add an OnAcceleratedPaint override to dullahan_render_handler that passes the D3D11 shared texture handle through a new onAcceleratedPageChanged callback. On Windows, the opengl-example now uses WGL_NV_DX_interop2 to display the shared texture directly via GL without CPU readback. CEF's keyed-mutex shared textures are copied to a plain local D3D11 texture for interop compatibility. Mac/Linux accelerated paint paths are stubbed out. Also extracts GetParentProcess into a shared dullahan_platform_utils.h header, adds RelWithDebInfo build support with /DEBUG:FULL PDBs, and links d3d11/dxgi for the opengl-example on Windows. Co-Authored-By: Claude Opus 4.6 (1M context) --- CMakeLists.txt | 17 +- .../opengl-example/src/opengl-example.cpp | 178 +++++++++++++++++- examples/opengl-example/src/opengl-example.h | 38 +++- src/dullahan.cpp | 6 + src/dullahan.h | 12 ++ src/dullahan_callback_manager.cpp | 14 ++ src/dullahan_callback_manager.h | 4 + src/dullahan_impl.cpp | 1 + src/dullahan_platform_utils.h | 65 +++++++ src/dullahan_render_handler.cpp | 31 +++ src/dullahan_render_handler.h | 3 + src/host/dullahan_host.cpp | 33 +--- 12 files changed, 362 insertions(+), 40 deletions(-) create mode 100644 src/dullahan_platform_utils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d40424d..b618000 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.13...4.0) if(CMAKE_CONFIGURATION_TYPES) - set(CMAKE_CONFIGURATION_TYPES "Release") + set(CMAKE_CONFIGURATION_TYPES "Release;RelWithDebInfo") endif() set(CMAKE_CXX_STANDARD 17) @@ -169,8 +169,11 @@ check_exists(CEF_DLL_LIBRARY) # 4505 "unreferenced local function has been removed" - supress meaningless freeglut warning if(IS_WINDOWS) set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -W4 -wd4100 -wd4127 -wd4505") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -W4 -wd4100 -wd4127 -wd4505") + set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /DEBUG:FULL") elseif(IS_MACOS) set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -xobjective-c++") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -xobjective-c++") elseif(IS_LINUX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-array-bounds") endif() @@ -199,6 +202,7 @@ add_library( src/dullahan_debug.h src/dullahan_impl.cpp src/dullahan_impl.h + src/dullahan_platform_utils.h src/dullahan_version.h src/dullahan_version.h.in ${KEYBOARD_IMPL_SRC_FILE} @@ -259,6 +263,7 @@ if(IS_WINDOWS) target_include_directories( dullahan_host PUBLIC + ${CMAKE_SOURCE_DIR}/src ${CEF_INCLUDE_DIR} ${CEF_INCLUDE_DIR}/.. ) @@ -313,6 +318,7 @@ elseif(IS_MACOS) target_include_directories( ${_helper_target} PUBLIC + ${CMAKE_SOURCE_DIR}/src ${CEF_INCLUDE_DIR} ${CEF_INCLUDE_DIR}/.. ) @@ -330,6 +336,7 @@ elseif(IS_LINUX) target_include_directories( dullahan_host PUBLIC + ${CMAKE_SOURCE_DIR}/src ${CEF_INCLUDE_DIR} ${CEF_INCLUDE_DIR}/.. ) @@ -348,7 +355,7 @@ if(IS_WINDOWS) add_custom_command( TARGET dullahan_host POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_directory - "$<$:${CEF_RELEASE_BIN_DIR}>" + "$<$,$>:${CEF_RELEASE_BIN_DIR}>" "$" COMMENT "Copying runtime files to executable directory") @@ -408,6 +415,8 @@ if (BUILD_EXAMPLES) dullahan opengl32 comctl32 + d3d11 + dxgi ${CEF_LIBRARY} ${CEF_DLL_LIBRARY} ${THIRD-PARTY-PREFIX}/${GLFW_FOLDER}/${GLFW_LIB_PATH} @@ -448,7 +457,7 @@ if (BUILD_EXAMPLES) endif() if(CMAKE_CONFIGURATION_TYPES) - set(CMAKE_CONFIGURATION_TYPES "Release") + set(CMAKE_CONFIGURATION_TYPES "Release;RelWithDebInfo") endif() # Install the Dullahan library and host executable @@ -473,4 +482,4 @@ if( USE_SPOTIFY_CEF ) install( FILES ${CEF_RESOURCE_DIR}/${resFile} DESTINATION resources) endif() endforeach() -endif() \ No newline at end of file +endif() diff --git a/examples/opengl-example/src/opengl-example.cpp b/examples/opengl-example/src/opengl-example.cpp index f024f10..d7ef357 100644 --- a/examples/opengl-example/src/opengl-example.cpp +++ b/examples/opengl-example/src/opengl-example.cpp @@ -36,8 +36,6 @@ #include "opengl-example.h" -#include "dullahan.h" - void errorCallback(int error, const char* description) { std::cerr << "GLFW error: (" << error << ") - " << description << std::endl; @@ -272,8 +270,9 @@ bool openglExample::init() resizeCallback(width, height); // Texture used to display browser output on the quad + // Only allocate the name - do not bind, as wglDXRegisterObjectNV + // requires the texture object to not have existing storage glGenTextures(1, &mTextureId); - glBindTexture(GL_TEXTURE_2D, mTextureId); // Generates the picking texture - each pixel in the texture // holds the coordinates of its location for mouse picking @@ -309,7 +308,45 @@ bool openglExample::init() { resizeBrowser(mTextureWidth, mTextureHeight); +#ifdef WIN32 + // Create D3D11 device for shared texture interop + ID3D11Device* baseDevice = nullptr; + D3D_FEATURE_LEVEL featureLevel; + D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, + nullptr, 0, D3D11_SDK_VERSION, + &baseDevice, &featureLevel, &mD3DContext); + if (baseDevice) + { + baseDevice->QueryInterface(__uuidof(ID3D11Device1), (void**)&mD3DDevice); + baseDevice->Release(); + } + + // Load WGL_NV_DX_interop2 extension functions + wglDXOpenDeviceNV = (PFNWGLDXOPENDEVICENVPROC)wglGetProcAddress("wglDXOpenDeviceNV"); + wglDXCloseDeviceNV = (PFNWGLDXCLOSEDEVICENVPROC)wglGetProcAddress("wglDXCloseDeviceNV"); + wglDXRegisterObjectNV = (PFNWGLDXREGISTEROBJECTNVPROC)wglGetProcAddress("wglDXRegisterObjectNV"); + wglDXUnregisterObjectNV = (PFNWGLDXUNREGISTEROBJECTNVPROC)wglGetProcAddress("wglDXUnregisterObjectNV"); + wglDXLockObjectsNV = (PFNWGLDXLOCKOBJECTSNVPROC)wglGetProcAddress("wglDXLockObjectsNV"); + wglDXUnlockObjectsNV = (PFNWGLDXUNLOCKOBJECTSNVPROC)wglGetProcAddress("wglDXUnlockObjectsNV"); + + // Open D3D11 device for GL interop + if (mD3DDevice && wglDXOpenDeviceNV) + { + mInteropDevice = wglDXOpenDeviceNV(mD3DDevice); + } + + std::cerr << "D3D11/GL interop setup: " + << "D3DDevice=" << mD3DDevice + << " wglDXOpenDeviceNV=" << (void*)wglDXOpenDeviceNV + << " InteropDevice=" << mInteropDevice + << std::endl; + + mDullahan->setOnAcceleratedPageChangedCallback( + std::bind(&openglExample::onAcceleratedPageChanged, this, + std::placeholders::_1, std::placeholders::_2)); +#else mDullahan->setOnPageChangedCallback(std::bind(&openglExample::onPageChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); +#endif mDullahan->setOnRequestExitCallback(std::bind(&openglExample::onRequestExitCallback, this)); mDullahan->navigate(mHomeUrl); @@ -439,7 +476,110 @@ bool openglExample::draw(int* tx, int* ty) return hit_browser; } -// Triggered when browser page content changes +#ifdef WIN32 +// Triggered when accelerated (GPU) page content changes +void openglExample::onAcceleratedPageChanged(void* handle, const std::vector& dirty_rects) +{ + if (!handle || !mInteropDevice) + { + return; + } + + // Open the shared D3D11 texture from the NT handle + ID3D11Texture2D* sharedTexture = nullptr; + HRESULT hr = mD3DDevice->OpenSharedResource1((HANDLE)handle, __uuidof(ID3D11Texture2D), (void**)&sharedTexture); + if (FAILED(hr)) + { + return; + } + + D3D11_TEXTURE2D_DESC desc; + sharedTexture->GetDesc(&desc); + + // Acquire keyed mutex if present (CEF shared textures use keyed mutexes) + IDXGIKeyedMutex* keyedMutex = nullptr; + sharedTexture->QueryInterface(__uuidof(IDXGIKeyedMutex), (void**)&keyedMutex); + if (keyedMutex) + { + hr = keyedMutex->AcquireSync(0, 16); + if (FAILED(hr)) + { + keyedMutex->Release(); + sharedTexture->Release(); + return; + } + } + + // Recreate local texture and GL interop if dimensions changed + if ((int)desc.Width != mLocalTextureWidth || (int)desc.Height != mLocalTextureHeight) + { + // Tear down old interop + if (mInteropObject) + { + wglDXUnlockObjectsNV(mInteropDevice, 1, &mInteropObject); + wglDXUnregisterObjectNV(mInteropDevice, mInteropObject); + mInteropObject = nullptr; + } + if (mLocalTexture) + { + mLocalTexture->Release(); + mLocalTexture = nullptr; + } + + // Create a local texture without keyed mutex for GL interop + D3D11_TEXTURE2D_DESC localDesc = {}; + localDesc.Width = desc.Width; + localDesc.Height = desc.Height; + localDesc.MipLevels = 1; + localDesc.ArraySize = 1; + localDesc.Format = desc.Format; + localDesc.SampleDesc.Count = 1; + localDesc.Usage = D3D11_USAGE_DEFAULT; + localDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + mD3DDevice->CreateTexture2D(&localDesc, nullptr, &mLocalTexture); + + if (mLocalTexture) + { + mInteropObject = wglDXRegisterObjectNV(mInteropDevice, mLocalTexture, + mTextureId, GL_TEXTURE_2D, + WGL_ACCESS_READ_ONLY_NV); + } + + mLocalTextureWidth = desc.Width; + mLocalTextureHeight = desc.Height; + mTextureWidth = desc.Width; + mTextureHeight = desc.Height; + resizeBrowser(mTextureWidth, mTextureHeight); + } + + // Unlock GL access so D3D11 can write to the local texture + if (mInteropObject) + { + wglDXUnlockObjectsNV(mInteropDevice, 1, &mInteropObject); + } + + // Copy from shared texture to local texture (GPU-to-GPU) + if (mLocalTexture) + { + mD3DContext->CopyResource(mLocalTexture, sharedTexture); + } + + // Release the keyed mutex and shared texture + if (keyedMutex) + { + keyedMutex->ReleaseSync(0); + keyedMutex->Release(); + } + sharedTexture->Release(); + + // Lock the local texture for GL access + if (mInteropObject) + { + wglDXLockObjectsNV(mInteropDevice, 1, &mInteropObject); + } +} +#else +// Triggered when browser page content changes (software path) void openglExample::onPageChanged(const unsigned char* pixels, int x, int y, const int width, const int height) { if (width != mTextureWidth || height != mTextureHeight) @@ -456,6 +596,7 @@ void openglExample::onPageChanged(const unsigned char* pixels, int x, int y, con glBindTexture(GL_TEXTURE_2D, (GLuint)mTextureId); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)mTextureWidth, (GLsizei)mTextureHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels); } +#endif // Triggered by Dullahan when cleanup is complete and it's okay to exit void openglExample::onRequestExitCallback() @@ -683,6 +824,35 @@ bool openglExample::run() bool openglExample::reset() { +#ifdef WIN32 + if (mInteropObject) + { + wglDXUnlockObjectsNV(mInteropDevice, 1, &mInteropObject); + wglDXUnregisterObjectNV(mInteropDevice, mInteropObject); + mInteropObject = nullptr; + } + if (mLocalTexture) + { + mLocalTexture->Release(); + mLocalTexture = nullptr; + } + if (mInteropDevice) + { + wglDXCloseDeviceNV(mInteropDevice); + mInteropDevice = nullptr; + } + if (mD3DContext) + { + mD3DContext->Release(); + mD3DContext = nullptr; + } + if (mD3DDevice) + { + mD3DDevice->Release(); + mD3DDevice = nullptr; + } +#endif + resetUI(); glfwDestroyWindow(mWindow); diff --git a/examples/opengl-example/src/opengl-example.h b/examples/opengl-example/src/opengl-example.h index cb85f0c..0293653 100644 --- a/examples/opengl-example/src/opengl-example.h +++ b/examples/opengl-example/src/opengl-example.h @@ -41,12 +41,25 @@ #include #include #include +#include #else #define GLFW_INCLUDE_NONE #include #endif -class dullahan; +#include "dullahan.h" + +#ifdef WIN32 +// WGL_NV_DX_interop2 function typedefs +typedef HANDLE (WINAPI *PFNWGLDXOPENDEVICENVPROC)(void*); +typedef BOOL (WINAPI *PFNWGLDXCLOSEDEVICENVPROC)(HANDLE); +typedef HANDLE (WINAPI *PFNWGLDXREGISTEROBJECTNVPROC)(HANDLE, void*, GLuint, GLenum, GLenum); +typedef BOOL (WINAPI *PFNWGLDXUNREGISTEROBJECTNVPROC)(HANDLE, HANDLE); +typedef BOOL (WINAPI *PFNWGLDXLOCKOBJECTSNVPROC)(HANDLE, GLint, HANDLE*); +typedef BOOL (WINAPI *PFNWGLDXUNLOCKOBJECTSNVPROC)(HANDLE, GLint, HANDLE*); + +#define WGL_ACCESS_READ_ONLY_NV 0x0000 +#endif class openglExample { @@ -67,7 +80,11 @@ class openglExample bool reset(); // callbacks +#ifdef WIN32 + void onAcceleratedPageChanged(void* handle, const std::vector& dirty_rects); +#else void onPageChanged(const unsigned char* pixels, int x, int y, const int width, const int height); +#endif void onRequestExitCallback(); private: @@ -105,6 +122,25 @@ class openglExample const unsigned char mBrowserId = 23; dullahan* mDullahan; +#ifdef WIN32 + // D3D11/GL interop for accelerated paint + ID3D11Device1* mD3DDevice = nullptr; + ID3D11DeviceContext* mD3DContext = nullptr; + HANDLE mInteropDevice = nullptr; + HANDLE mInteropObject = nullptr; + ID3D11Texture2D* mSharedTexture = nullptr; + ID3D11Texture2D* mLocalTexture = nullptr; + int mLocalTextureWidth = 0; + int mLocalTextureHeight = 0; + + PFNWGLDXOPENDEVICENVPROC wglDXOpenDeviceNV = nullptr; + PFNWGLDXCLOSEDEVICENVPROC wglDXCloseDeviceNV = nullptr; + PFNWGLDXREGISTEROBJECTNVPROC wglDXRegisterObjectNV = nullptr; + PFNWGLDXUNREGISTEROBJECTNVPROC wglDXUnregisterObjectNV = nullptr; + PFNWGLDXLOCKOBJECTSNVPROC wglDXLockObjectsNV = nullptr; + PFNWGLDXUNLOCKOBJECTSNVPROC wglDXUnlockObjectsNV = nullptr; +#endif + void generatePickTexture(); bool mousePosToTexturePos(int* tx, int* ty); void resizeBrowser(int width, int height); diff --git a/src/dullahan.cpp b/src/dullahan.cpp index afc0cce..a9ade1d 100644 --- a/src/dullahan.cpp +++ b/src/dullahan.cpp @@ -446,3 +446,9 @@ void dullahan::setOnJSBeforeUnloadCallback(std::function callback) { mImpl->getCallbackManager()->setOnJSBeforeUnloadCallback(callback); } + +void dullahan::setOnAcceleratedPageChangedCallback(std::function& dirty_rects)> callback) +{ + mImpl->getCallbackManager()->setOnAcceleratedPageChangedCallback(callback); +} diff --git a/src/dullahan.h b/src/dullahan.h index 04eee29..b3d4c67 100644 --- a/src/dullahan.h +++ b/src/dullahan.h @@ -130,6 +130,14 @@ class dullahan FD_SAVE_FILE, } EFileDialogType; + struct dullahan_rect + { + int x; + int y; + int width; + int height; + }; + public: //////////// initialization settings //////////// struct dullahan_settings @@ -397,6 +405,10 @@ class dullahan // JS before unload callback (alert) void setOnJSBeforeUnloadCallback(std::function callback); + // accelerated (GPU) page contents change - handle is a duplicated D3D11 shared texture HANDLE on Windows + void setOnAcceleratedPageChangedCallback(std::function& dirty_rects)> callback); + private: std::unique_ptr mImpl; }; diff --git a/src/dullahan_callback_manager.cpp b/src/dullahan_callback_manager.cpp index 715c3c9..bf6f255 100644 --- a/src/dullahan_callback_manager.cpp +++ b/src/dullahan_callback_manager.cpp @@ -292,3 +292,17 @@ bool dullahan_callback_manager::onJSBeforeUnloadCallback() return false; } +void dullahan_callback_manager::setOnAcceleratedPageChangedCallback( + std::function& dirty_rects)> callback) +{ + mOnAcceleratedPageChangedCallbackFunc = callback; +} + +void dullahan_callback_manager::onAcceleratedPageChanged(void* handle, const std::vector& dirty_rects) +{ + if (mOnAcceleratedPageChangedCallbackFunc) + { + mOnAcceleratedPageChangedCallbackFunc(handle, dirty_rects); + } +} + diff --git a/src/dullahan_callback_manager.h b/src/dullahan_callback_manager.h index 8141f0e..3277977 100644 --- a/src/dullahan_callback_manager.h +++ b/src/dullahan_callback_manager.h @@ -91,6 +91,9 @@ class dullahan_callback_manager void setOnJSBeforeUnloadCallback(std::function callback); bool onJSBeforeUnloadCallback(); + void setOnAcceleratedPageChangedCallback(std::function& dirty_rects)> callback); + void onAcceleratedPageChanged(void* handle, const std::vector& dirty_rects); + private: std::function mOnAddressChangeCallbackFunc; std::function mOnConsoleMessageCallbackFunc; @@ -111,6 +114,7 @@ class dullahan_callback_manager std::function(dullahan::EFileDialogType, const std::string, const std::string, const std::string, bool&)> mOnFileDialogCallbackFunc; std::function mOnJSDialogCallbackFunc; std::function mOnJSBeforeUnloadCallbackFunc; + std::function&)> mOnAcceleratedPageChangedCallbackFunc; }; #endif //_DULLAHAN_CALLBACK_MANAGER diff --git a/src/dullahan_impl.cpp b/src/dullahan_impl.cpp index a5e0123..6e79e20 100644 --- a/src/dullahan_impl.cpp +++ b/src/dullahan_impl.cpp @@ -443,6 +443,7 @@ bool dullahan_impl::init(dullahan::dullahan_settings& user_settings) CefWindowInfo window_info; window_info.SetAsWindowless(0); window_info.windowless_rendering_enabled = true; + window_info.shared_texture_enabled = true; const int width = user_settings.initial_width; const int height = user_settings.initial_height; window_info.bounds = { 0, 0, width, height }; diff --git a/src/dullahan_platform_utils.h b/src/dullahan_platform_utils.h new file mode 100644 index 0000000..6c14bbf --- /dev/null +++ b/src/dullahan_platform_utils.h @@ -0,0 +1,65 @@ +/* + @brief Dullahan - a headless browser rendering engine + based around the Chromium Embedded Framework + @author Callum Prentice 2017 + + Copyright (c) 2017, Linden Research, Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef _DULLAHAN_PLATFORM_UTILS +#define _DULLAHAN_PLATFORM_UTILS + +#ifdef WIN32 +#include +#include + +// Retrieves a handle to the parent process with the specified access rights. +// Taken from http://magpcss.org/ceforum/viewtopic.php?f=6&t=15817&start=10#p37820 +// Works around a CEF issue where the host process is not destroyed +// after CEF exits in some cases on Windows 7. +inline HANDLE GetParentProcess(DWORD desired_access = SYNCHRONIZE) +{ + HANDLE Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + + PROCESSENTRY32 ProcessEntry = {}; + ProcessEntry.dwSize = sizeof(PROCESSENTRY32); + + if (Process32First(Snapshot, &ProcessEntry)) + { + DWORD CurrentProcessId = GetCurrentProcessId(); + + do + { + if (ProcessEntry.th32ProcessID == CurrentProcessId) + { + break; + } + } + while (Process32Next(Snapshot, &ProcessEntry)); + } + + CloseHandle(Snapshot); + + return OpenProcess(desired_access, FALSE, ProcessEntry.th32ParentProcessID); +} +#endif // WIN32 + +#endif // _DULLAHAN_PLATFORM_UTILS diff --git a/src/dullahan_render_handler.cpp b/src/dullahan_render_handler.cpp index 6b305a7..45fad3d 100644 --- a/src/dullahan_render_handler.cpp +++ b/src/dullahan_render_handler.cpp @@ -33,6 +33,7 @@ #include "dullahan_impl.h" #include "dullahan_callback_manager.h" + dullahan_render_handler::dullahan_render_handler(dullahan_impl* parent) : mParent(parent) { @@ -199,4 +200,34 @@ bool dullahan_render_handler::GetScreenInfo(CefRefPtr browser, CefSc // indicate we changed the structure return true; +} + +// CefRenderHandler override +void dullahan_render_handler::OnAcceleratedPaint(CefRefPtr browser, + PaintElementType type, + const RectList& dirtyRects, + const CefAcceleratedPaintInfo& info) +{ + CEF_REQUIRE_UI_THREAD(); + + // convert CEF dirty rects to dullahan rects + std::vector dullahan_dirty_rects; + dullahan_dirty_rects.reserve(dirtyRects.size()); + for (const auto& rect : dirtyRects) + { + dullahan::dullahan_rect dr; + dr.x = rect.x; + dr.y = rect.y; + dr.width = rect.width; + dr.height = rect.height; + dullahan_dirty_rects.push_back(dr); + } + +#ifdef WIN32 + mParent->getCallbackManager()->onAcceleratedPageChanged(info.shared_texture_handle, dullahan_dirty_rects); +#elif defined(__APPLE__) || defined(__linux__) + // TODO: implement accelerated paint for this platform + (void)info; + mParent->getCallbackManager()->onAcceleratedPageChanged(nullptr, dullahan_dirty_rects); +#endif } \ No newline at end of file diff --git a/src/dullahan_render_handler.h b/src/dullahan_render_handler.h index 1fe24bf..4a153ca 100644 --- a/src/dullahan_render_handler.h +++ b/src/dullahan_render_handler.h @@ -46,6 +46,9 @@ class dullahan_render_handler : void OnPopupShow(CefRefPtr browser, bool show) override; void OnPopupSize(CefRefPtr browser, const CefRect& rect) override; bool GetScreenInfo(CefRefPtr browser, CefScreenInfo& screen_info) override; + void OnAcceleratedPaint(CefRefPtr browser, PaintElementType type, + const RectList& dirtyRects, + const CefAcceleratedPaintInfo& info) override; IMPLEMENT_REFCOUNTING(dullahan_render_handler); diff --git a/src/host/dullahan_host.cpp b/src/host/dullahan_host.cpp index 4d10a64..8c6f857 100644 --- a/src/host/dullahan_host.cpp +++ b/src/host/dullahan_host.cpp @@ -47,7 +47,8 @@ int main(int argc, char* argv[]) // Ignore c:\program files (x86)\microsoft visual studio 12.0\vc\include\thr\xthread(196): warning C4702: unreachable code #pragma warning( disable : 4702) #include -#include + +#include "dullahan_platform_utils.h" /* Nasty hack to stop flash from displaying a popup with "NO SANDBOX" @@ -99,36 +100,6 @@ void enablePPAPIFlashHack(LPSTR lpCmdLine) ::CloseHandle(hProc); ::CloseHandle(hJob); } - -// taken from http://magpcss.org/ceforum/viewtopic.php?f=6&t=15817&start=10#p37820 -// works around a CEF issue (yet to be filed) where the host process is not destroyed -// after CEF exits in some case on Windows 7 -// Making it switchable for now while I investigate it a bit -HANDLE GetParentProcess() -{ - HANDLE Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - - PROCESSENTRY32 ProcessEntry = {}; - ProcessEntry.dwSize = sizeof(PROCESSENTRY32); - - if (Process32First(Snapshot, &ProcessEntry)) - { - DWORD CurrentProcessId = GetCurrentProcessId(); - - do - { - if (ProcessEntry.th32ProcessID == CurrentProcessId) - { - break; - } - } - while (Process32Next(Snapshot, &ProcessEntry)); - } - - CloseHandle(Snapshot); - - return OpenProcess(SYNCHRONIZE, FALSE, ProcessEntry.th32ParentProcessID); -} #endif #if defined(NO_STACK_PROTECTOR) From b81b4be2a74b9aa63f78a2448142b157a76cbd5d Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Sun, 29 Mar 2026 15:32:16 -0700 Subject: [PATCH 2/5] Pass LUID adapter ID in during configuration and tell CEF to use it. --- src/dullahan.h | 9 +++++++++ src/dullahan_impl.cpp | 14 ++++++++++++++ src/dullahan_impl.h | 2 ++ 3 files changed, 25 insertions(+) diff --git a/src/dullahan.h b/src/dullahan.h index b3d4c67..c180394 100644 --- a/src/dullahan.h +++ b/src/dullahan.h @@ -192,6 +192,15 @@ class dullahan // host name:port to use as a web proxy std::string proxy_host_port = std::string(); + // adapter LUID to use for GPU rendering + // like adding --use-adapter-luid=VALUE to Chrome command line + // set use_adapter_luid to true and populate adapter_luid to enable + bool use_adapter_luid = false; + struct { + uint32_t low_part = 0; + int32_t high_part = 0; + } adapter_luid; + // background color displayed before first page loaded (RRGGBB) unsigned int background_color = 0xffffff; diff --git a/src/dullahan_impl.cpp b/src/dullahan_impl.cpp index 6e79e20..71cfb8e 100644 --- a/src/dullahan_impl.cpp +++ b/src/dullahan_impl.cpp @@ -158,6 +158,11 @@ void dullahan_impl::OnBeforeCommandLineProcessing(const CefString& process_type, command_line->AppendSwitchWithValue("--proxy-server", mProxyHostPort); } + if (mUseAdapterLUID) + { + command_line->AppendSwitchWithValue("use-adapter-luid", mAdapterLUIDStr); + } + // Hardcode the switch to turn off the HTTP Basic Auth dialogs // as per this issue: https://github.com/chromiumembedded/cef/issues/3603 // Having these dialogs appear with new (139) version of the CEF is @@ -393,6 +398,15 @@ bool dullahan_impl::initCEF(dullahan::dullahan_settings& user_settings) // provide their own ("Allow, "Disallow") UI. mFakeUIForMediaStream = user_settings.fake_ui_for_media_stream; + // the adapter LUID to use for GPU rendering - pass through to CEF command line + mUseAdapterLUID = user_settings.use_adapter_luid; + if (mUseAdapterLUID) + { + int64_t luid_val = ((int64_t)user_settings.adapter_luid.high_part << 32) | + (int64_t)(uint32_t)user_settings.adapter_luid.low_part; + mAdapterLUIDStr = std::to_string(luid_val); + } + // if true, this setting inverts the pixels in Y direction - useful if your texture // coords are upside down compared to default for Dullahan mFlipPixelsY = user_settings.flip_pixels_y; diff --git a/src/dullahan_impl.h b/src/dullahan_impl.h index a72a7fb..f033e50 100644 --- a/src/dullahan_impl.h +++ b/src/dullahan_impl.h @@ -178,6 +178,8 @@ class dullahan_impl : bool mUseMockKeyChain; bool mAutoPlayWithoutGesture; bool mFakeUIForMediaStream; + bool mUseAdapterLUID; + std::string mAdapterLUIDStr; bool mFlipPixelsY; bool mFlipMouseY; double mRequestedPageZoom; From ab96b3f9f373f7eb5fcf73f2c682d37843aa1bbf Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Tue, 31 Mar 2026 21:01:15 -0700 Subject: [PATCH 3/5] Allow Accelerated Painting for mac and linux clients --- .../opengl-example/src/opengl-example.cpp | 85 ++++++++++++++++--- examples/opengl-example/src/opengl-example.h | 1 + src/dullahan.h | 1 + src/dullahan_impl.cpp | 10 ++- src/dullahan_render_handler.cpp | 8 +- 5 files changed, 80 insertions(+), 25 deletions(-) diff --git a/examples/opengl-example/src/opengl-example.cpp b/examples/opengl-example/src/opengl-example.cpp index d7ef357..fd3a500 100644 --- a/examples/opengl-example/src/opengl-example.cpp +++ b/examples/opengl-example/src/opengl-example.cpp @@ -303,24 +303,73 @@ bool openglExample::init() settings.use_mock_keychain = true; #endif - bool result = mDullahan->init(settings); - if (result) - { - resizeBrowser(mTextureWidth, mTextureHeight); +#ifdef WIN32 + settings.shared_texture_enable = true; +#endif #ifdef WIN32 - // Create D3D11 device for shared texture interop - ID3D11Device* baseDevice = nullptr; - D3D_FEATURE_LEVEL featureLevel; + // Enumerate DXGI adapters to find the default GPU, create a D3D11 + // device on it, and pass its LUID to dullahan so CEF renders on + // the same adapter. + IDXGIFactory1* dxgiFactory = nullptr; + IDXGIAdapter1* dxgiAdapter = nullptr; + CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&dxgiFactory); + if (dxgiFactory) + { + // Use adapter index 1 (secondary GPU) + if (dxgiFactory->EnumAdapters1(0, &dxgiAdapter) != DXGI_ERROR_NOT_FOUND) + { + DXGI_ADAPTER_DESC1 adapterDesc; + dxgiAdapter->GetDesc1(&adapterDesc); + + settings.use_adapter_luid = true; + settings.adapter_luid.low_part = adapterDesc.AdapterLuid.LowPart; + settings.adapter_luid.high_part = adapterDesc.AdapterLuid.HighPart; + + std::cerr << "Using GPU adapter: "; + std::wcerr << adapterDesc.Description; + std::cerr << " LUID=" << adapterDesc.AdapterLuid.HighPart + << ":" << adapterDesc.AdapterLuid.LowPart << std::endl; + } + } + + // Create D3D11 device on the selected adapter for shared texture interop + ID3D11Device* baseDevice = nullptr; + D3D_FEATURE_LEVEL featureLevel; + if (dxgiAdapter) + { + D3D11CreateDevice(dxgiAdapter, D3D_DRIVER_TYPE_UNKNOWN, nullptr, 0, + nullptr, 0, D3D11_SDK_VERSION, + &baseDevice, &featureLevel, &mD3DContext); + } + else + { D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0, D3D11_SDK_VERSION, &baseDevice, &featureLevel, &mD3DContext); - if (baseDevice) - { - baseDevice->QueryInterface(__uuidof(ID3D11Device1), (void**)&mD3DDevice); - baseDevice->Release(); - } + } + if (baseDevice) + { + baseDevice->QueryInterface(__uuidof(ID3D11Device1), (void**)&mD3DDevice); + baseDevice->Release(); + } + + if (dxgiAdapter) + { + dxgiAdapter->Release(); + } + if (dxgiFactory) + { + dxgiFactory->Release(); + } +#endif + bool result = mDullahan->init(settings); + if (result) + { + resizeBrowser(mTextureWidth, mTextureHeight); + +#ifdef WIN32 // Load WGL_NV_DX_interop2 extension functions wglDXOpenDeviceNV = (PFNWGLDXOPENDEVICENVPROC)wglGetProcAddress("wglDXOpenDeviceNV"); wglDXCloseDeviceNV = (PFNWGLDXCLOSEDEVICENVPROC)wglGetProcAddress("wglDXCloseDeviceNV"); @@ -484,10 +533,18 @@ void openglExample::onAcceleratedPageChanged(void* handle, const std::vectorOpenSharedResource1((HANDLE)handle, __uuidof(ID3D11Texture2D), (void**)&sharedTexture); + HRESULT hr = mD3DDevice->OpenSharedResource1((HANDLE)viewer_handle, __uuidof(ID3D11Texture2D), (void**)&sharedTexture); if (FAILED(hr)) { return; diff --git a/examples/opengl-example/src/opengl-example.h b/examples/opengl-example/src/opengl-example.h index 0293653..3eb455c 100644 --- a/examples/opengl-example/src/opengl-example.h +++ b/examples/opengl-example/src/opengl-example.h @@ -42,6 +42,7 @@ #include #include #include +#include #else #define GLFW_INCLUDE_NONE #include diff --git a/src/dullahan.h b/src/dullahan.h index c180394..f0e1e3c 100644 --- a/src/dullahan.h +++ b/src/dullahan.h @@ -176,6 +176,7 @@ class dullahan bool javascript_enabled = true; // javascript bool media_stream_enabled = false; // web cams etc. (caution) bool plugins_enabled = true; // all plugins + bool shared_texture_enable = false; // enable shared texture for accelerated rendering bool webgl_enabled = true; // webgl // explicitly set the path to the locales folder since defaults no longer work on some systems diff --git a/src/dullahan_impl.cpp b/src/dullahan_impl.cpp index 71cfb8e..f57e34d 100644 --- a/src/dullahan_impl.cpp +++ b/src/dullahan_impl.cpp @@ -38,6 +38,7 @@ #include "include/cef_request_context_handler.h" #include "include/cef_waitable_event.h" #include "include/base/cef_logging.h" +#include // Required for std::hex, std::setw, std::setfill #include "dullahan_version.h" #ifdef __APPLE__ @@ -402,9 +403,10 @@ bool dullahan_impl::initCEF(dullahan::dullahan_settings& user_settings) mUseAdapterLUID = user_settings.use_adapter_luid; if (mUseAdapterLUID) { - int64_t luid_val = ((int64_t)user_settings.adapter_luid.high_part << 32) | - (int64_t)(uint32_t)user_settings.adapter_luid.low_part; - mAdapterLUIDStr = std::to_string(luid_val); + std::ostringstream oss; + oss << "0x" << std::hex << std::setw(8) << std::setfill('0') << user_settings.adapter_luid.high_part + << ":0x" << std::hex << std::setw(8) << std::setfill('0') << user_settings.adapter_luid.low_part; + mAdapterLUIDStr = oss.str(); } // if true, this setting inverts the pixels in Y direction - useful if your texture @@ -457,7 +459,7 @@ bool dullahan_impl::init(dullahan::dullahan_settings& user_settings) CefWindowInfo window_info; window_info.SetAsWindowless(0); window_info.windowless_rendering_enabled = true; - window_info.shared_texture_enabled = true; + window_info.shared_texture_enabled = user_settings.shared_texture_enable; const int width = user_settings.initial_width; const int height = user_settings.initial_height; window_info.bounds = { 0, 0, width, height }; diff --git a/src/dullahan_render_handler.cpp b/src/dullahan_render_handler.cpp index 45fad3d..af665e1 100644 --- a/src/dullahan_render_handler.cpp +++ b/src/dullahan_render_handler.cpp @@ -223,11 +223,5 @@ void dullahan_render_handler::OnAcceleratedPaint(CefRefPtr browser, dullahan_dirty_rects.push_back(dr); } -#ifdef WIN32 mParent->getCallbackManager()->onAcceleratedPageChanged(info.shared_texture_handle, dullahan_dirty_rects); -#elif defined(__APPLE__) || defined(__linux__) - // TODO: implement accelerated paint for this platform - (void)info; - mParent->getCallbackManager()->onAcceleratedPageChanged(nullptr, dullahan_dirty_rects); -#endif -} \ No newline at end of file +} From 2e7efe89bb37273ca2102eaa7b9354abccd1224c Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Tue, 31 Mar 2026 22:53:36 -0700 Subject: [PATCH 4/5] Use platform-specific shared texture handle in OnAcceleratedPaint Use shared_texture_io_surface on macOS instead of shared_texture_handle. Linux does not support accelerated painting and falls back to OnPaint. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/dullahan_render_handler.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/dullahan_render_handler.cpp b/src/dullahan_render_handler.cpp index af665e1..023cbfc 100644 --- a/src/dullahan_render_handler.cpp +++ b/src/dullahan_render_handler.cpp @@ -223,5 +223,10 @@ void dullahan_render_handler::OnAcceleratedPaint(CefRefPtr browser, dullahan_dirty_rects.push_back(dr); } + // Dullahan for Linux does not currently support accelerated painting and will fall back to OnPaint +#ifdef WIN32 mParent->getCallbackManager()->onAcceleratedPageChanged(info.shared_texture_handle, dullahan_dirty_rects); +#elif defined(__APPLE__) + mParent->getCallbackManager()->onAcceleratedPageChanged(info.shared_texture_io_surface, dullahan_dirty_rects); +#endif } From ece94308c2dc70d67df3d2cdc6bdff21f1a3a724 Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Tue, 31 Mar 2026 23:27:30 -0700 Subject: [PATCH 5/5] Restrict adapter LUID settings to WIN32 only LUIDs are a Windows-specific concept used by DXGI to identify GPU adapters and have no equivalent on macOS or Linux. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/dullahan.h | 2 ++ src/dullahan_impl.cpp | 4 ++++ src/dullahan_impl.h | 2 ++ 3 files changed, 8 insertions(+) diff --git a/src/dullahan.h b/src/dullahan.h index f0e1e3c..4d7816f 100644 --- a/src/dullahan.h +++ b/src/dullahan.h @@ -193,6 +193,7 @@ class dullahan // host name:port to use as a web proxy std::string proxy_host_port = std::string(); +#ifdef WIN32 // adapter LUID to use for GPU rendering // like adding --use-adapter-luid=VALUE to Chrome command line // set use_adapter_luid to true and populate adapter_luid to enable @@ -201,6 +202,7 @@ class dullahan uint32_t low_part = 0; int32_t high_part = 0; } adapter_luid; +#endif // background color displayed before first page loaded (RRGGBB) unsigned int background_color = 0xffffff; diff --git a/src/dullahan_impl.cpp b/src/dullahan_impl.cpp index f57e34d..ea0e85b 100644 --- a/src/dullahan_impl.cpp +++ b/src/dullahan_impl.cpp @@ -159,10 +159,12 @@ void dullahan_impl::OnBeforeCommandLineProcessing(const CefString& process_type, command_line->AppendSwitchWithValue("--proxy-server", mProxyHostPort); } +#ifdef WIN32 if (mUseAdapterLUID) { command_line->AppendSwitchWithValue("use-adapter-luid", mAdapterLUIDStr); } +#endif // Hardcode the switch to turn off the HTTP Basic Auth dialogs // as per this issue: https://github.com/chromiumembedded/cef/issues/3603 @@ -399,6 +401,7 @@ bool dullahan_impl::initCEF(dullahan::dullahan_settings& user_settings) // provide their own ("Allow, "Disallow") UI. mFakeUIForMediaStream = user_settings.fake_ui_for_media_stream; +#ifdef WIN32 // the adapter LUID to use for GPU rendering - pass through to CEF command line mUseAdapterLUID = user_settings.use_adapter_luid; if (mUseAdapterLUID) @@ -408,6 +411,7 @@ bool dullahan_impl::initCEF(dullahan::dullahan_settings& user_settings) << ":0x" << std::hex << std::setw(8) << std::setfill('0') << user_settings.adapter_luid.low_part; mAdapterLUIDStr = oss.str(); } +#endif // if true, this setting inverts the pixels in Y direction - useful if your texture // coords are upside down compared to default for Dullahan diff --git a/src/dullahan_impl.h b/src/dullahan_impl.h index f033e50..8aa3f00 100644 --- a/src/dullahan_impl.h +++ b/src/dullahan_impl.h @@ -178,8 +178,10 @@ class dullahan_impl : bool mUseMockKeyChain; bool mAutoPlayWithoutGesture; bool mFakeUIForMediaStream; +#ifdef WIN32 bool mUseAdapterLUID; std::string mAdapterLUIDStr; +#endif bool mFlipPixelsY; bool mFlipMouseY; double mRequestedPageZoom;