CEF离屏渲染优化:OnAcceleratedPaint与D3D11共享纹理实战指南
2026/9/23 19:12:33 网站建设 项目流程

简介:使用CEF的高性能离屏渲染演示工程,是面向桌面应用开发者的完整示例,基于浏览器内核与图形接口的共享纹理技术,展示借助硬件加速回调完成网页离屏绘制,可解决常规软件渲染在复杂界面下的帧率与资源占用问题,适合需要将网页用户界面嵌入原生程序并追求流畅交互的中高级开发人员。资源包内含三十二个文件,压缩后仅一百三十七KB,主体为源代码与头文件,并配套构建脚本、工程生成批处理、补丁文件及说明文档,各模块划分清晰,方便直接查阅与编译。已有七十七人参与学习下载,内容聚焦最新的图形加速离屏渲染路径,覆盖从环境配置到工程生成的完整关键环节。通过学习该示例,可深入理解渲染合成与图层管理的实现思路,掌握把网页内容高效输出到原生窗口的具体方法,并能在实际项目中复用相关补丁与代码结构,有效缩短自研渲染方案的开发周期。

1. CEF 离屏渲染与 OnAcceleratedPaint:这套演示解决的不只是「不卡」

CEF(Chromium Embedded Framework)做离屏渲染(OSR)时,画质和功能都不缺,真正卡住项目的是画面怎么从 Chromium 里高效拿出来。老接口 OnPaint 给的是 CPU 位图,每帧要先把 RGBA 数组拷出渲染进程,再上传 GPU,帧率稍高一点 CPU 就吃不消;而 OnAcceleratedPaint 直接交出 GPU 共享纹理,绕开了这条最贵的路。这个演示包把完整的 D3D11 共享纹理 OSR 混合器工程摊开给你看:CMake 构建、composition/web_layer/image_layer 多层合成、透明 HUD 叠加,一套全齐。适合做游戏启动器、桌面工具箱、监控面板,也适合想搞懂 CEF 多进程构建的人抄作业。

2. 先读架构再编译:composition、web_layer 与 D3D11 共享纹理的分工

2.1 老回调 OnPaint 慢在哪:CPU 位图到 GPU 的冤枉路

先说最核心的痛点。CEF 在窗口模式(on-screen)下有自己的合成器,你不需要碰像素;但切到 OSR 之后,CEF 把每帧画面通过CefRenderHandler::OnPaint交给你。老接口拿到的是一块纯 CPU 内存,里面是 RGBA 字节,宽高就是页面尺寸。你要做的事是:把这块内存拷贝到自己的 D3D11 纹理,再UpdateSubresource上传到显存,最后在渲染循环里把它贴到后缓冲上。

这条链路有两个明显浪费。第一,Chromium 的 GPU 进程本来已经在显存里合成好了页面,OnPaint 却强制把结果读回 CPU,等于把已经画好的画从墙上摘下来、扫描成照片、再打印一份挂回去。第二,UpdateSubresource是一次 CPU→GPU 的 PCIe 传输,1080p 一帧就是 8MB 左右,几十帧下来 CPU 占用非常可观。实测里这种路径轻松吃掉 2~5ms/帧,还没算上后续的混合叠加。

维度OnPaintOnAcceleratedPaint
数据形态CPU 侧 RGBA 位图指针GPU 共享纹理句柄
数据流GPU 合成 → 读回 CPU → 再上传 GPUGPU 显存内直接跨进程共享
带宽开销每帧两次 PCIe 往返几乎没有 CPU 拷贝
透明 HUD 叠加要手动处理 alpha 通道纹理自带 alpha,可直接混合
典型耗时1080p 下约 2~5ms/帧通常 0.1~0.5ms/帧

这套演示核心就是第二行:用OnAcceleratedPaint()配合 D3D11 共享纹理,把「读回 CPU」这一步彻底省掉。

2.2 OnAcceleratedPaint 给的不是像素,是纹理句柄

新回调的声明在老回调旁边,一眼能看出差别:

// 老的 CPU 位图回调 void OnPaint(CefRefPtr<CefBrowser> browser, PaintElementType type, const RectList& dirtyRects, const void* buffer, int width, int height) override { // buffer 是 RGBA 字节数组,宽高分别由 width/height 给出。 // 这里不能直接跨线程保存 buffer,下一次回调它就被 CEF 回收了。 } // 新的 GPU 共享纹理回调 void OnAcceleratedPaint(CefRefPtr<CefBrowser> browser, PaintElementType type, const RectList& dirtyRects, const CefAcceleratedPaintInfo& info) override { // 这不是位图,而是一个 D3D11 共享纹理的句柄。 // 具体字段名以你当前 CEF 头文件为准,关键信息包括: // info.release_handle —— 共享句柄,传给 OpenSharedResource 用 // info.sync_token —— 帧完成同步信息,保证读到的是完整一帧 // info.shared_rect —— 有效内容区域,不一定等于整个纹理尺寸 }

这里要注意,OnAcceleratedPaint通常跑在 CEF 的内部 IO 线程上,不是你的渲染线程。常见做法是只把release_handleshared_rect记下来,通过队列丢给 D3D11 渲染线程去OpenSharedResource,不要在回调里直接调用 D3D11 设备。原因很简单:回调频率跟 Chromium 合成帧率一致,直接在里面做重活会把 CEF 的合成线程拖住,反而掉帧。

拿到句柄之后,你的 D3D11 设备要用OpenSharedResource打开这份纹理:

ComPtr<ID3D11Texture2D> layer_tex; HRESULT hr = d3d11_device->OpenSharedResource( handle, __uuidof(ID3D11Texture2D), (void**)&layer_tex); if (FAILED(hr)) { // 句柄可能已过期,需要从 CEF 侧重新拿最新一帧的 handle。 return; }

参数上,handleinfo.release_handle,但不同 CEF 版本的字段来源可能不同,有的版本走shared_handle,有的版本需要你自己维护base::SharedMemory映射。这套演示之所以叫「高性能」,就是因为它把这步封装好了,你不需要自己调 Mojo 或共享内存协议。

2.3 演示里的三层分层:composition / web_layer / image_layer

打开src目录,核心文件其实就三个角色:composition.cpp管合成顺序,web_layer.cpp管 CEF 页面纹理,image_layer.cpp管静态图片,d3d11.cpp统一管设备和纹理生命周期。

先说为什么分层。OSR 场景里,你往往不只是显示一个网页:启动器可能需要底部一张背景图、中间是网页内容、最上面浮一层实时状态 HUD。如果每个元素都自己往后缓冲上画,顺序一出错画面就乱。这套演示的做法是:每个 layer 先渲染到自己的共享纹理,最后一帧在 composition 阶段统一混合。

// 伪代码:composition 每帧的合成顺序 void CompositionFrame() { d3d11_context->ClearRenderTargetView(rtv, clear_color); // 先清屏 for (Layer* layer : layers) { // 按 z-order 从底到顶 ID3D11Texture2D* tex = layer->GetSharedTexture(); layer->BlendTo(back_buffer, tex); // 各层做 alpha 混合 } swapchain->Present(1, 0); }

每一层都持有独立纹理,这点很关键。如果你的 web_layer 直接把纹理画到后缓冲,下一帧 image_layer 再往上画,那网页内容就被覆盖了;独立纹理 + 统一合成才能做到网页、图片、HUD 三个元素互不干扰地叠加。

web_layer.cpp里通常维护一个CefRefPtr<CefBrowser>,在OnAcceleratedPaint里更新共享纹理句柄;image_layer.cpp处理overlay.svg这类静态资源,把它解码成 D3D11 纹理后一次上传、反复使用。这也是推荐做法:活动网页走 CEF 纹理,静态装饰走 image layer,别让 CEF 去加载不必要的东西,省一整个渲染进程的开销。

3. 从零构建:CEF_ROOT、gen_vs2017.bat 与 x86/x64 的差别

3.1 先把 CEF_ROOT 指对:二进制分发版本决定后面所有事

想编译这套演示,你需要先准备三样东西:CMake、Visual Studio 2017 或 2019(装 C/C++ 开发工具)、一份 CEF 二进制分发版。示例里给的是 Chromium 72 x64 的 release 分发,下载解压后目录名大概长这样:cef_binary_3.3440.xxxx_windows64,里面能看到libcef.dllcmake文件夹和include头文件目录。

打开命令提示符,先设置环境变量:

set CEF_ROOT=D:\libs\cef_binary_3.3440.1804_windows64

注意,CEF_ROOT必须指向「包含 libcef.dll 和 cmake 文件夹」的那一层,不能多一层也不能少一层。FindCEF.cmake会直接用这个路径去拼libcef.lib、头文件路径和cef_sandbox.lib的完整路径,路径指错了,CMake 会在 configure 阶段直接报错,错误信息通常是找不到libcef_dll_wrappercef_variables.cmake

另外,这套演示的示例二进制分发只有 Release 版本,没有 Debug 版 libcef.dll。所以后面生成工程时,建议直接选 Release 配置编译;硬编 Debug 会卡在链接阶段,因为导入库根本不存在。

3.2 gen_vs2017.bat 实际做了什么

设置完CEF_ROOT后,运行gen_vs2017.bat。脚本内容不长,拆开看就是标准的 CMake 两步:

@echo off if "%CEF_ROOT%"=="" ( echo [ERROR] CEF_ROOT is not set. exit /b 1 ) cmake -S . -B build_vs2017 -G "Visual Studio 15 2017" -A x64 ^ -DCEF_ROOT="%CEF_ROOT%" cmake --build build_vs2017 --config Release --target osr_demo

第一段检查环境变量有没有漏设;第二段用-A x64显式指定 64 位架构,避免旧写法里依赖 generator 默认值;第三段直接编译 Release。如果你用 VS2019,就把 generator 换成"Visual Studio 16 2019",脚本里已经准备了gen_vs2019.bat

工程里带的三个 cmake 文件各司其职。cef_variables.cmake负责把 CEF 的编译选项、平台宏、OSR 相关特性读进来;FindCEF.cmake负责定位libcef.lib、沙箱库和头文件目录;cef_macros.cmake提供cef_compile_app_utilcef_add_resources这类封装宏,帮你把libcef_dll_wrapper、字符集设置和 Windows 入口点一次性链接对。新手最容易漏的其实是CEF_USE_SANDBOX这个变量,演示脚本里一般默认关掉沙箱,不然运行时需要额外配置cef_sandbox.lib

3.3 x86 构建:三个地方要一起改

如果你要产出 32 位程序,光改 CMake generator 不够,必须三处同步。第一处是gen_vs2017.bat里的-A x64改成-A Win32;第二处是CEF_ROOT必须指向 x86 版本的 CEF 二进制分发,比如cef_binary_3.3440.xxxx_windows32;第三处是确认你的 Visual Studio 安装了 x86 版本的 C/C++ 编译工具,通常 VS2017 默认会带,但精简安装可能没装。

set CEF_ROOT=D:\libs\cef_binary_3.3440.1804_windows32 cmake -S . -B build_vs2017_x86 -G "Visual Studio 15 2017" -A Win32 ^ -DCEF_ROOT="%CEF_ROOT%" cmake --build build_vs2017_x86 --config Release --target osr_demo

x86 构建设置好之后,接下来看main.cpp。CEF 是多进程架构,主程序既能当浏览器进程,也能当渲染子进程和 GPU 子进程,靠的就是CefExecuteProcess分流:

int APIENTRY wWinMain(HINSTANCE inst, HINSTANCE, LPWSTR, int) { CefMainArgs main_args(inst); CefRefPtr<CefApp> app(new DemoApp); int exit_code = CefExecuteProcess(main_args, app, nullptr); if (exit_code >= 0) { // exit_code >= 0 说明当前进程被 CEF 用作子进程,跑完就退出。 return exit_code; } CefSettings settings; CefString(&settings.browser_subprocess_path).FromASCII("osr_demo.exe"); settings.multi_threaded_message_loop = true; settings.background_color = 0; // 透明背景,配合 OSR 使用 if (!CefInitialize(main_args, settings, app, nullptr)) { return -1; } CefRunMessageLoop(); CefShutdown(); return 0; }

这段逻辑值得细看。CefExecuteProcess返回负数时,才说明当前进程是浏览器进程,可以继续CefInitialize;如果返回非负值,说明 CEF 把这个进程当成了子进程,跑完直接退出。browser_subprocess_path建议显式设置,尤其当你的 exe 改了名,如果不设置,CEF 默认用自己的子进程逻辑按主程序名去找,名字对不上就会白屏或直接崩。

4. 避坑记录:OnAcceleratedPaint 不生效、纹理黑屏与 GPU 子进程崩溃

4.1 现象一:OnAcceleratedPaint 一次都没进,只有白屏

编译成功、窗口也能打开,但页面始终白屏,打断点在老回调 OnPaint 里也没命中。

原因通常是三选一:CEF 版本太老,不支持OnAcceleratedPaint;创建窗口时没有开启共享纹理开关;或者 GPU 合成被禁用。示例二进制分发是 Chromium 72,本身就带这个回调,所以排第一的是窗口信息。

CefWindowInfo window_info; window_info.SetAsWindowless(kNullWindowHandle, true); window_info.shared_texture_enabled = true; // 老版本字段名可能不同

解决方法是把shared_texture_enabled显式置为 true。CEF 之所以给这个开关,是因为共享纹理路径依赖 GPU 进程,有些纯软件渲染的部署环境必须退回OnPaint。如果你的程序没有实现OnPaint,而shared_texture_enabled恰好是 false,就会白屏。

如果开关已经打开还是没回调,去chrome://gpu对应的日志里看 GPU 进程是否用了硬件加速。远程桌面、虚拟机里 D3D11 设备可能创建失败,CEF 会退到 SwiftShader,而 SwiftShader 路径通常不走共享纹理。

4.2 现象二:纹理能打开但画面一直是黑的

OpenSharedResource成功、不报错,CopyResource 也执行了,但屏幕上就是黑块。

这多半是同步问题。CEF 在 GPU 进程里写完共享纹理后,需要通知你「这一帧已经可用」,而这个通知就是sync_token。如果你拿到句柄后立刻OpenSharedResourceCopyResource,很可能读到的是上一帧残留,甚至是一块未初始化的显存。

常见做法是用 IDXGIKeyedMutex 做同步,CEF 内部如果用了这个机制:

ComPtr<IDXGIKeyedMutex> keyed_mutex; hr = layer_tex->QueryInterface(__uuidof(IDXGIKeyedMutex), (void**)&keyed_mutex); if (SUCCEEDED(hr)) { // 参数一:key,CEF 侧写入时用同一个 key。 // 参数二:等待毫秒数,这里给 100ms,超时就放弃本帧。 keyed_mutex->AcquireSync(0, 100); context->CopyResource(back_buffer, layer_tex.Get()); keyed_mutex->ReleaseSync(1); }

如果你的 CEF 版本不走 KeyedMutex,而是用 D3D11 Fence,那就改成在 GPU 队列上做 Wait。判断标准很简单:打开d3d11.cpp看它创建纹理时用的是D3D11_RESOURCE_MISC_SHARED还是D3D11_RESOURCE_MISC_SHARED_NTHANDLE,后者往往配 Fence。

另一个隐蔽坑是句柄过期。release_handle不是创建一次就能永久使用的,CEF 每次合成新帧可能释放旧共享纹理。一旦 OpenSharedResource 返回DXGI_ERROR_INVALID_CALL,不要重试同一个句柄,而是取 CEF 侧最新回调里的 handle 再试。

4.3 现象三:gpu 子进程反复崩溃重启,日志里一堆 D3D11 device removed

表现是程序启动后窗口闪一下,然后 GPU 进程不断被拉起又崩溃,任务管理器里能看到多个同名 exe 进程反复出现。

CEF 用自己的子进程承担渲染和 GPU 合成。如果主程序入口没有CefExecuteProcess分流,CEF 就找不到合法的子进程入口,导致 GPU 进程初始化失败。解决方法是回到第 3 章的main.cpp模板,确保CefExecuteProcessCefInitialize之前执行。

第二类原因是 GPU 设备不匹配。OSR 模式下 CE4 通常用 D3D11 创建设备,如果你的宿主程序也用 D3D11,两台设备能力差异过大时,Chromium 在D3D11CreateDevice阶段就会返回E_INVALIDARG。比如 CEF 要求 feature level 11_0,而你的环境只支持 10_0。开发阶段可以临时在CefSettings里设置:

settings.no_sandbox = true;

沙箱会限制 GPU 进程的部分 D3D 调用,开发期关掉能省很多排查时间。发布时再重新打开沙箱并做完整测试。另外注意browser_subprocess_path指向的 exe 必须有正确的资源段(manifest),被改过图标或压缩过的 exe 有时会导致子进程启动失败。

4.4 现象四:x86 构建报 LNK1112 模块计算机类型冲突

错误大概长这样:LNK1112: module machine type 'x64' conflicts with target machine type 'x86'

原因很直接:CMake generator 是 Win32,但CEF_ROOT指到的 CEF 二进制是 x64;或者反过来。CEF 的导入库libcef.lib是分架构的,x64 的 lib 不可能链接进 x86 的 exe。解决方法是按 3.3 的做法,把-A Win32和 x86 的 CEF_ROOT 配套使用。

还有个小坑是 Debug 配置。示例分发版只有 Release,如果你用 Visual Studio 直接在 Debug 下 F5,会报找不到libcef.dll或导入库不匹配。不要试图混用:Debug 工程链接 Release 导入库在 CEF 这种带libcef_dll_wrapper的项目里会产生大量符号冲突,最省事的做法是始终用 Release 生成,调试日志用CEF.Log输出。

4.5 现象五:透明 HUD 一直垫底,或被网页盖住

演示包里overlay.svghud.html都准备好了,但合成时 HUD 要么在最底下看不见,要么盖住网页全部内容。

这是图层顺序和混合状态的问题。composition.cpp的 for 循环按 z-order 从底到顶绘制,如果你把 HUD 的 layer 加到了最前面,网页内容就会被盖上。正确顺序是:先画背景/网页层,最后画 HUD 层。

但如果顺序对、HUD 还是被网页盖住,问题往往出在混合状态。D3D11 默认的 blend state 是不透明的,直接把带 alpha 的纹理画上去会整个覆盖。需要手动开启 alpha blend:

D3D11_BLEND_DESC bd{}; bd.RenderTarget[0].BlendEnable = TRUE; bd.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; bd.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; bd.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; bd.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; bd.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; ComPtr<ID3D11BlendState> blend_state; d3d11_device->CreateBlendState(&bd, &blend_state); d3d11_context->OMSetBlendState(blend_state.Get(), nullptr, 0xffffffff);

这套配置表示源像素 alpha 参与混合,目标像素按 1-alpha 保留,正是 HUD 叠加最常见的模式。逐层确认 blend state 生效后,透明叠加就不会再出问题。

5. 自己编 CEF 才需要 patches:shared_textures 补丁怎么对应怎么打

5.1 三个补丁分别干什么

演示包里patches目录放了三个 patch,很多第一次拿到资源的人会困惑:示例不是能跑吗,为什么还要补丁?

答案是:示例二进制分发版已经内置了OnAcceleratedPaint,你不打补丁就能跑。但 CEF 官方版本并非所有分支都默认开启共享纹理路径,尤其当你需要基于特定 Chromium 版本自己编译 CEF、定制显示模块时,就得把共享纹理能力移植过去。

补丁文件适用分支典型用途
cef_issue_2559.patch较老 CEF 分支对应 CEF issue 2559,把 OSR 共享纹理开关补回指定分支
shared_textures_3440.patchChromium 3440 系列(约 Chrome 72)在 3440 分支启用 D3D11 共享纹理路径
shared_textures_3497.patchChromium 3497 系列把共享纹理改动同步到更新的 3497 分支

命名里的 3440、3497 是 Chromium 版本号,不是 CEF 自己的版本号。示例二进制分发是 Chromium 72 x64,对应 3440 这条线,所以shared_textures_3440.patch和它最匹配。cef_issue_2559.patch是功能开关类补丁,通常补在更早的位置。

5.2 给 CEF 源码树打补丁的完整流程

先明确一点:补丁是打在 CEF/Chromium 源码树上的,不是打在libcef.dll上的。你需要先按 CEF 官方流程用automate-git.py拉一份源码,切到对应分支,然后再打补丁。

# 假设你已经用 automate-git.py 拉好了 CEF 源码,目录叫 cef cd cef # 先切到要改的分支,这里以 3440 为例 git checkout 3440 # 先检查能不能干净打上,不要直接 apply git apply --check patches/cef_issue_2559.patch git apply patches/cef_issue_2559.patch # 再打 shared_textures 补丁 git apply --check patches/shared_textures_3440.patch git apply patches/shared_textures_3440.patch # 打完后重新生成工程并编译 libcef python cef_create_projects.py ninja -C out/Release_GN_x64 cef

git apply --check这一步很重要,它只做校验不写入,能提前发现补丁与当前代码差异过大。如果 check 通过再执行真正的git apply。顺序上建议先打小补丁再打大补丁,cef_issue_2559.patch通常是功能前提,先让它生效,后续 shared texture 的改动才有挂载点。

编译时注意,ninja一定要用 GN 生成的 Release 配置,且目标架构和你最终要链接的宿主程序一致。打完补丁后的libcef.dlllibcef.lib会覆盖到 CEF 二进制分发对应位置,之后回到演示工程目录,重新走一遍set CEF_ROOTgen_vs2017.bat,让 CMake 拾取新编译的产物。

5.3 补丁冲突、回退和什么时候别打

git apply --check失败是常态,尤其你切的分支和补丁基线差了好几个小版本。现象是报一串error: patch does not apply,有时还带fuzz提示。

解决方法是改用--reject强制应用:

git apply --reject patches/shared_textures_3497.patch

该命令会尽量打上能打的部分,并把失败部分写到.rej文件里。然后逐个打开.rej文件,对照上下文手动合入。这种操作要求你对 Chromium 的 Overlay 合成流程有一定熟悉度,如果只是做应用层开发,不建议深入。

还有一个更容易踩的坑:别把补丁打到官方二进制的头文件目录上。有些人图省事,把patches复制到cef_binary_.../下直接git apply,结果提示「not a git repository」。因为二进制分发里没有.git,补丁根本无处落地,正确做法永远是「针对源码树」操作。

最后说说什么情况不需要打补丁。如果你的 CEF 版本已经较新(例如 4xxx 以后),OnAcceleratedPaint是默认能力,打旧补丁反而会把代码改坏。新版本会直接提供CefAcceleratedPaintInfoshared_texture_enabled开关,你只需要在工程里把开关打开。也就是说:示例二进制跑得动,就别动 patches;真的需要自编 CEF 时,再看 README 确认补丁顺序。

6. 进阶技巧:把透明 HUD 接进你自己的 D3D11 管线

6.1 用 file:// 加载透明 HTML 界面

演示包里hud.htmloverlay.svg就是为 HUD 准备的。先把 HUD 页面加载到独立 browser 里:

CefWindowInfo hud_window_info; hud_window_info.SetAsWindowless(kNullWindowHandle, true); hud_window_info.shared_texture_enabled = true; CefBrowserSettings hud_settings; CefRefPtr<CefBrowser> hud_browser = CefBrowserHost::CreateBrowserSync( hud_window_info, hud_handler, // 复用同一个 RenderHandler "file:///D:/demo/hud.html", // 透明背景的 HTML 页面 hud_settings, nullptr, nullptr);

hud.html内部要把htmlbody的背景全部设成透明,才能让上层叠加看不出网页底色。实时数据可以直接用 JavaScript 更新 DOM,不必每帧重建页面,CPU 占用会低很多。

6.2 合成顺序与性能验证

接入自有管线时,把 HUD 层放到合成循环的最后一步,并保持每层独立纹理。验证是否真的走通了共享纹理路径,用QueryPerformanceCounter量一下从回调进入到CopyResource返回的时间:

LARGE_INTEGER freq, t0, t1; QueryPerformanceFrequency(&freq); QueryPerformanceCounter(&t0); // OnAcceleratedPaint 回调里做 OpenSharedResource + CopyResource CopySharedTextureToBackBuffer(); QueryPerformanceCounter(&t1); double copy_ms = (t1.QuadPart - t0.QuadPart) * 1000.0 / freq.QuadPart; // 如果 copy_ms 长期大于 1ms,说明可能退回 OnPaint 路径了。

量的时候注意context->Flush(),否则 D3D11 命令只是排队,CPU 计时会偏短,看起来很快但实际 GPU 还没干完。我习惯在复制结束后调用一次 Flush,再取结束时间。

我吃过一次亏:升级 CEF 版本后发现shared_texture_enabled默认值变了,整个 OSR 退回软渲染,帧率从 60 掉到 15,最后逐行查回调才发现是新版本把开关默认关了。从那以后我每次换 CEF 二进制,第一件事就是跑一遍OnAcceleratedPaint回调计数,确认走的还是新纹理路径,再动业务代码。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询