Chapter 101: Browser Rendering Constraints
浏览器端图形程序的主问题是:同一帧里的 JavaScript、资源加载、DOM/CSS、GPU 提交和安全策略共享一个受控运行环境,渲染系统必须在这些约束内维持交互、图像正确性和资源稳定性。读完本章后,读者应能定位浏览器图形卡顿来自主线程、资源上传、GPU 执行、DOM 合成还是安全策略,并能给出一套可复用排查顺序。
本章使用一个在线地图编辑器作为贯穿材料。页面中心是 WebGL 或 WebGPU canvas,画布中显示几十万个点、线、纹理图标和选区高亮;页面上方有搜索框,右侧是 DOM 属性面板,鼠标移动需要命中地图对象并同步更新 tooltip。这个材料覆盖跨域纹理、主线程任务、worker 分工、canvas 与 DOM overlay、设备像素比、内存预算、GPU context loss 和 frame time 波动。
浏览器图形程序和桌面原生渲染器的差异来自两个事实。第一,浏览器把网页当成不可信内容运行,跨域像素读取、GPU 访问、计时精度和共享内存都会受安全边界限制。第二,图形帧和普通网页交互共用事件循环、布局、样式计算和合成路径。渲染器的工程目标由此变成:先把数据路径放进安全模型,再把每帧工作拆到稳定的时间预算中。
判断浏览器端瓶颈时,应按 安全策略 → 主线程时间 → worker/上传路径 → GPU pass → DOM/CSS 合成 → 内存与恢复 的顺序收集证据。安全策略决定资源能否被读取和提交;主线程时间决定输入、动画回调和提交是否准时;worker 与上传路径决定数据准备是否阻塞交互;GPU pass 决定 shader、带宽和 overdraw;DOM/CSS 合成决定 canvas 之外的 overlay 是否引入额外帧成本;内存与恢复决定长会话是否稳定。
101.1 浏览器环境对渲染的安全与沙箱限制
浏览器图形 API 的第一层约束来自安全模型。网页脚本来自网络,浏览器必须阻止页面通过 canvas、GPU、计时器或跨源资源读取用户本地信息和其他站点内容。WebGL、WebGPU、Canvas 2D、ImageBitmap、视频纹理和 worker 都运行在这个模型之内,渲染代码拿到的是受控能力集合。
在线地图编辑器的第一个风险点是纹理来源。地图图标、用户头像、瓦片图片和视频帧可能来自不同域名。MDN 的 cross-origin canvas 文档 说明,缺少 CORS 许可的跨源图片被绘制进 canvas 后,canvas 会进入 tainted 状态,随后读取像素、导出图片或捕获流会触发 SecurityError。对图形工程而言,这条规则直接影响截图、拾取、调试像素和离屏导出能力。
这类问题的稳定处理方式是把“是否需要读回像素”放在资源加载设计阶段。只用于显示的纹理可以走普通图片请求;需要参与 readPixels、getImageData、离线缩略图、选区导出或测试快照的图片,需要服务器返回正确的 CORS 响应,并在客户端设置 crossOrigin。资源加载器应把纹理分成 display-only 与 readable 两类,日志中记录 URL、响应头、加载路径和最终 GPU 资源状态。
WebGPU 的安全边界更靠近硬件。MDN 的 WebGPU API 文档 把 WebGPU 定位为在浏览器中访问 GPU 的低层 API,实际使用时需要通过 navigator.gpu.requestAdapter() 和 adapter.requestDevice() 获取受控设备。工程上应把 adapter、device、features、limits、preferred format 和错误回调集中到一个 profile 对象,后续 pipeline、buffer、texture 和 bind group 创建都从这个 profile 读取能力边界。
计时精度也属于安全约束。高精度计时会放大侧信道风险,因此浏览器可能降低 performance.now() 等计时结果的精度。MDN 的 High precision timing 文档 将时间戳精度和安全要求放在同一语境下说明。图形程序在统计 frame time 时,应使用滑动窗口、百分位数和分阶段 marker,少依赖单帧尖峰判断。单帧 28 ms 只能说明这一帧超预算,连续多帧在相同阶段出现尖峰才支撑瓶颈结论。
沙箱限制还会影响内存和上下文恢复。WebGL 有 context lost 事件,OffscreenCanvas 也有 contextlost 与 contextrestored 事件入口;MDN 的 OffscreenCanvas 文档 将离屏画布、worker 渲染和上下文事件放在同一个 API 页面中说明。地图编辑器应把 GPU 资源描述符保存在 CPU 侧可重建结构中,例如纹理 URL、buffer layout、pipeline key、sampler key 和 bind group key;当 context 或 device 失效时,恢复流程按资源依赖顺序重新上传。
安全与沙箱约束可以画成一条资源路径。下图只覆盖“图片进入 GPU 并可能被读回”的路径,重点是 CORS 决定 canvas 与 GPU 读回能力,device profile 决定低层 GPU 资源创建范围。
这条图的工程含义是:跨源资源的决定点在 fetch 和响应头阶段,后续 shader 写得再正确也无法改变 tainted canvas 的读回限制。浏览器图形系统的资源管理器需要把安全状态和 GPU 状态一起记录,形成“资源是否可显示、是否可读回、是否可重建”的最小元数据。
101.2 单线程 JS 执行模型的影响
浏览器页面的主线程承担脚本执行、事件处理、样式与布局、部分渲染调度和用户输入分发。图形程序把大规模数据解析、场景更新、命中测试、buffer 上传准备和 draw submit 都压到主线程时,输入事件和下一帧动画回调会排队等待,用户看到的症状是鼠标拖动断续、tooltip 延迟、缩放手势卡顿或动画间隔忽长忽短。
requestAnimationFrame 是浏览器提供的动画调度入口。MDN 的 requestAnimationFrame 文档 说明,浏览器会在下一次重绘之前调用回调,并向回调传入时间戳。对地图编辑器来说,这个时间戳应成为仿真、相机惯性、渐变动画和性能统计的统一输入;每帧逻辑应围绕当前可用时间预算做增量推进。
一个稳定的 render loop 需要把“帧内必须完成的工作”和“可分摊工作”分开。必须完成的工作包括读取输入状态、更新相机矩阵、提交当前可见层的 draw call 和更新少量 overlay。可分摊工作包括解析大型 GeoJSON、构建空间索引、创建缩略图、合并静态 buffer、预编译后续样式变体和回收旧资源。前者放在 rAF 内,后者切片调度到 worker、idle 回调或多帧队列。
下面的代码展示一个预算式 render loop。它用 rAF 驱动当前帧,把重型加载任务限制在固定预算内,并把剩余工作留到后续帧。示例省略具体渲染 API 调用,重点是帧调度结构。
const frameState = {
lastTime: 0,
pendingJobs: [],
camera: createCameraState(),
};
function renderFrame(now) {
const deltaSeconds = Math.min((now - frameState.lastTime) / 1000, 0.05);
frameState.lastTime = now;
updateInput(frameState.camera, deltaSeconds);
runSmallJobs(frameState.pendingJobs, 3.0);
submitVisibleLayers(frameState.camera);
updateDomOverlay(frameState.camera);
requestAnimationFrame(renderFrame);
}
function runSmallJobs(queue, budgetMs) {
const deadline = performance.now() + budgetMs;
while (queue.length > 0 && performance.now() < deadline) {
queue.shift()();
}
}
requestAnimationFrame(renderFrame);
这段代码对应的判断是:rAF 回调只提供与刷新节奏对齐的入口,性能稳定性仍由帧内工作量、对象分配、同步 API 调用、资源上传和 DOM 读写顺序决定。runSmallJobs 的预算值应来自目标设备和画面复杂度,移动端和高 DPR 屏幕通常需要更保守的 CPU 预算。
Microtask 会改变帧内时间分布。Promise 回调、异步函数续体和框架内部调度可能在当前任务结束后立即运行,密集 microtask 会推迟浏览器进入渲染阶段。地图编辑器中常见触发点是一次加载完成后连续解析多个图层、连续更新状态管理器、连续触发 React/Vue 组件刷新。排查时应在 Performance 面板或自定义 marker 中区分 rAF 时间、microtask 时间、layout 时间和 GPU submit 时间。
主线程问题的证据应来自帧时间拆分。Long task 表示一个主线程任务占用较长时间,MDN 的 PerformanceLongTaskTiming 文档 提供了观察长任务的 API 入口;长动画帧还可以通过 MDN 的 Long animation frame timing 文档 关联 script、rendering 和 blocking duration。图形程序应把这些观察结果和自定义阶段名对应起来,例如 decodeTiles、buildIndex、uploadBuffers、layoutPanel、submitFrame。
主线程排查的顺序是:先确认 rAF 间隔是否超过目标刷新周期,再看超预算帧中是否有长任务,然后看长任务来自数据解析、状态更新、DOM 布局、同步像素读取还是资源上传准备。只有在主线程空闲但画面仍慢时,才进入 GPU pass、带宽、shader 和合成路径排查。
101.3 控制浏览器卡顿与性能下降风险
浏览器卡顿来自帧预算被持续消耗。60 Hz 屏幕下,单帧总预算约为 16.67 ms;120 Hz 屏幕下约为 8.33 ms。这个预算覆盖输入处理、JS、样式、布局、绘制、合成、GPU 提交和浏览器自身调度余量。地图编辑器想维持稳定交互,应把每帧主线程 JS 控制在更小窗口内,并保留足够空间给浏览器完成合成。
Worker 是控制主线程压力的主要工具。MDN 的 Using Web Workers 文档 说明,worker 可以在后台线程运行脚本,通过 message 与主线程通信,且 worker 无法直接操作 DOM。图形工程中适合放进 worker 的任务包括文件解码、数据过滤、空间索引构建、瓦片裁剪、属性统计、几何压缩和局部排序。保留在主线程的任务包括 DOM 事件、可见 canvas 尺寸、最终显示层状态和与页面框架绑定的 UI 更新。
OffscreenCanvas 进一步把部分 canvas 渲染工作移出主线程。MDN 的 OffscreenCanvas 页面说明,它可以脱离 DOM 与 Canvas API 的强绑定,并可在 worker 中执行渲染。地图编辑器可以把底图、海量点层和动态图层放到 worker canvas,把 DOM 面板、输入框和 tooltip 留在主线程。这样做的收益来自主线程 input/layout 与绘制提交解耦,成本来自消息协议、资源转移、调试复杂度和浏览器兼容边界。
下面的代码展示主线程把 canvas 控制权转移给 worker。示例只表达资源所有权转移和消息边界,真实项目还需要处理 resize、DPR、context loss、错误回传和资源释放。
const canvas = document.querySelector("#map-canvas");
const offscreen = canvas.transferControlToOffscreen();
const renderWorker = new Worker(new URL("./map-render-worker.js", import.meta.url), {
type: "module",
});
renderWorker.postMessage(
{
type: "init",
canvas: offscreen,
width: canvas.clientWidth,
height: canvas.clientHeight,
devicePixelRatio: window.devicePixelRatio,
},
[offscreen],
);
这段代码的关键点是 transfer list。offscreen 的所有权被转移给 worker,主线程随后通过消息发送 resize、相机、图层变化和交互状态。消息结构应使用明确的 type 和版本字段,二进制数据优先使用 transferable ArrayBuffer,高频状态可以合并到每帧一次的 camera packet。低频资产加载和高频输入更新走同一条消息通道时,应加优先级队列,保证输入不会被大数据包长期排队。
Chunked loading 是大型可视化的基础策略。一次性解析 200 MB 数据会制造长任务、内存峰值和 GC 压力。更稳的路径是把数据拆成 tile、page 或 chunk:网络层按视野和缩放级别请求,worker 层解析成 typed array,主线程或 render worker 按预算上传,GPU 层使用多个小 buffer 或环形 buffer 管理增量更新。每个 chunk 应记录 CPU bytes、GPU bytes、feature count、上传耗时和最后使用帧号。
内存预算需要覆盖三份数据:原始压缩数据、CPU 解码数据和 GPU 资源。地图编辑器加载纹理图标时,PNG/JPEG 文件体积只代表网络成本;解码后的 RGBA 位图、mipmap、纹理格式和多 DPR 资源会显著提高显存占用。矢量数据也一样,JSON 文本、解析对象、typed array、索引结构和 GPU buffer 可能同时存在。稳定做法是定义资源生命周期:fetch buffer 在解码后释放,临时对象在上传后释放,GPU buffer 根据可见性和最近使用帧回收。
GC pause 是浏览器图形程序常见的帧尖峰来源。每帧创建大量对象、数组、闭包、临时矩阵和事件 payload,会把内存回收推到交互过程中。图形层应复用矩阵、向量、命令对象和 typed array 片段;事件层应合并高频 pointer move;状态层应控制不可变对象扩散到整棵 UI 树。证据上,GC pause 常表现为主线程没有明显业务函数占满整帧,但任务间出现回收段,随后 rAF 回调延迟。
控制卡顿的检查顺序是:先把 rAF 内必需工作压缩到稳定预算,再把解析和索引构建迁移到 worker,然后把大资源改成 chunked loading,接着建立 CPU/GPU 内存预算与回收规则,最后用 long task、frame percentile、heap size、GPU resource count 和 context loss 记录验证长会话稳定性。
101.4 与 DOM/CSS 渲染的互操作性
浏览器图形界面通常由 canvas 和 DOM/CSS 共同构成。canvas 负责高密度像素绘制,DOM 负责输入框、按钮、文本面板、无障碍语义和普通布局。两者共享窗口尺寸、滚动、缩放、事件分发和合成器。互操作问题的核心是坐标、层级、尺寸、像素比和命中测试要一致。
Canvas overlay 的常见结构是一个全屏或容器内 canvas 加多个 DOM 叠层。地图编辑器里,WebGL/WebGPU canvas 绘制地图,DOM tooltip 显示属性,侧边栏显示选中对象,CSS transform 控制面板动画。canvas 绘制结果进入浏览器合成器后,DOM overlay 会按 stacking context、opacity、transform 和 compositing 规则叠到最终页面。面板阴影、模糊、透明和大面积 fixed 元素可能增加合成成本。
尺寸处理需要同时更新 CSS size 和 drawing buffer size。CSS 像素决定页面布局,drawing buffer 像素决定 GPU 渲染分辨率。设备像素比(Device Pixel Ratio, DPR)把两者连接起来。DPR 提高会增加 color/depth attachment 像素数量、纹理采样量和 resolve 成本。地图编辑器在 4K 屏幕和 DPR 2 下绘制全屏 canvas,实际像素量可能达到 CSS 面积的 4 倍。
下面的代码展示 resize 时如何同步 CSS 尺寸、DPR 和 GPU backing store。示例用普通 canvas 表达尺寸关系,WebGL/WebGPU 还需要同步 viewport、swap chain 或 canvas configuration。
function resizeCanvasToDisplaySize(canvas, maxDpr = 2) {
const rect = canvas.getBoundingClientRect();
const dpr = Math.min(window.devicePixelRatio || 1, maxDpr);
const width = Math.max(1, Math.round(rect.width * dpr));
const height = Math.max(1, Math.round(rect.height * dpr));
if (canvas.width === width && canvas.height === height) {
return false;
}
canvas.width = width;
canvas.height = height;
return true;
}
这段代码表达两个判断。第一,canvas 内部像素尺寸应从实际布局结果读取,因此使用 getBoundingClientRect()。第二,DPR 应有上限,尤其在热力图、粒子、后处理和大型编辑器中,高 DPR 会把 fill rate、render target bandwidth 和内存占用同步放大。项目可以把静态 UI 保持浏览器原生清晰,把高成本 canvas 限制到 maxDpr = 1.5 或 2,再用文字和矢量 overlay 保持关键 UI 清晰。
CSS transform 与 canvas 坐标需要统一。页面滚动、容器缩放、面板折叠、CSS transform、浏览器 zoom 和 DPR 都会改变鼠标坐标到世界坐标的映射。命中测试路径应明确写成 pointer event → viewport CSS coordinate → canvas normalized coordinate → clip/screen coordinate → world/ray/tiles。每次 resize、滚动或 transform 变化后,映射矩阵都需要更新。否则用户看到的症状是鼠标停在点上,tooltip 却落到偏移位置。
DOM 与 canvas 的读写顺序会触发布局成本。图形循环中先写 DOM style,再读取 layout,再继续写 style,可能导致浏览器提前刷新布局。地图编辑器可以把 DOM overlay 更新分成两个阶段:帧开始读取必要布局信息,渲染后批量写入 tooltip 位置和面板状态。UI 框架更新也应和图形层解耦,高频相机状态只写入轻量 store,低频选中对象再触发组件树更新。
事件命中测试需要分层。DOM 自身能处理按钮、输入框和面板滚动;canvas 内对象需要图形层处理。常用方式是让 DOM overlay 捕获 UI 事件,让 canvas 处理地图区域事件,并把 pointer id、坐标、按键状态和时间戳传给渲染层。复杂编辑器可以使用一个命中测试顺序:先测 DOM 控件,再测临时 gizmo,再测高亮对象,最后测普通图层。这个顺序能让用户交互结果和视觉层级一致。
互操作排查应先看尺寸:CSS size、drawing buffer size、DPR、viewport 和投影矩阵是否一致。再看坐标:事件坐标、canvas rect、滚动偏移和世界矩阵是否一致。接着看合成:overlay 是否引入大面积透明、filter、backdrop blur 或频繁 transform。最后看 UI 更新:图形帧是否触发整棵组件树重渲染。
101.5 Browser Graphics Bottleneck Case Path
现在把前面约束放回在线地图编辑器。用户加载一个包含 300 万个要素的数据集,页面先白屏数秒,随后地图可见;拖动画面时每隔一两秒停顿;打开右侧属性面板后帧率进一步下降;点击导出图片时出现安全错误。这个案例同时包含资源安全、主线程、worker、GPU、DOM 合成和内存路径。
第一步定位安全错误。导出失败说明当前 canvas 可能被跨源资源污染,或者导出路径读取了未经许可的像素。检查顺序是:记录所有参与当前帧的图片、视频和瓦片 URL;确认需要导出的资源是否带 CORS 响应;确认客户端是否设置 crossOrigin;确认渲染路径中是否混入第三方头像或 SVG。结论应落到资源级别,例如“第 17 层 POI 图标可显示但不可读回”,随后决定替换资源域名、代理资源、关闭导出该层或把导出能力限制到合规资源集合。
第二步定位白屏。白屏期间如果主线程被 JSON parse、对象创建和索引构建占满,rAF 回调无法准时执行,首帧提交延迟。处理顺序是把首屏资源拆成可见 tile,先上传低精度或低层级数据,再在 worker 中解析后续 chunk。首帧策略应追求“可交互的粗略图像先出现”,后续帧逐步替换为完整数据。证据来自 network waterfall、long task、worker message timing、首个 draw submit 时间和首次有内容绘制时间。
第三步定位拖动停顿。拖动本身只需要更新相机矩阵和可见集,持续停顿通常来自主线程夹杂重任务、GPU buffer 大量重建、同步读回或 GC pause。排查顺序是先看 Performance trace 中 pointer move 到 rAF 的延迟,再看每帧是否重新分配大 typed array,再看是否调用同步像素读取或等待查询结果,最后看 GPU pass 中点层、线层、标签层的耗时。稳定修复通常是复用 buffer、使用脏区增量更新、把拾取改成异步或低频、把标签布局迁移到 worker。
第四步定位属性面板导致的帧率下降。面板打开后,canvas 画面复杂度没有变化,帧率却下降,说明瓶颈可能在 DOM/CSS 合成或 UI 框架更新。检查顺序是观察 layout、style recalculation、paint、composite layers 和组件更新时间。大面积半透明面板、backdrop-filter、复杂阴影、频繁改变 layout 属性和整棵组件树刷新都会扩大主线程与合成器成本。修复方向是固定面板布局边界,减少每帧 DOM 状态写入,把 tooltip 高频位置更新和属性面板低频内容更新分离。
第五步定位长会话退化。地图编辑器运行 30 分钟后越来越慢,常见原因是缓存无限增长、GPU 资源未释放、worker 队列积压、历史状态保留过多或 context 恢复路径缺失。检查顺序是记录 CPU heap、GPU resource count、tile cache 命中率、pending job 数量、worker message backlog 和 context loss 次数。资源缓存应使用字节预算和最近使用帧号,而非只按对象数量限制。释放规则需要同时处理 CPU 解码数据、空间索引、GPU buffer、texture 和 sampler 引用。
这个案例的瓶颈路径可以压缩成一张检查表。表中的“证据”是进入下一步前要拿到的最低材料,“常见结论”用于帮助把症状放回管线阶段。
| 症状 | 优先证据 | 对应阶段 | 常见结论 |
|---|---|---|---|
| 导出报错 | 资源 URL、CORS 响应、canvas 安全状态 | 安全策略 | 可显示资源未必可读回 |
| 首屏白屏 | Long task、首个 draw submit、worker 消息 | 主线程与加载 | 解码和索引构建阻塞首帧 |
| 拖动停顿 | rAF 间隔、GC、buffer upload、GPU timing | 帧循环与 GPU 提交 | 每帧重建资源或同步读回 |
| 面板打开变慢 | layout、paint、composite、组件更新 | DOM/CSS 合成 | overlay 和 UI 刷新进入帧预算 |
| 长会话退化 | heap、GPU resource count、cache bytes | 内存生命周期 | CPU/GPU 缓存缺少预算和回收 |
浏览器图形瓶颈的最终判断顺序是:先确认资源是否具备所需安全权限,再确认主线程是否准时进入 rAF,然后确认 worker、chunk 和上传是否把数据准备从交互路径移开,接着确认 GPU pass 是否稳定,随后确认 DOM/CSS overlay 是否扩展合成成本,最后确认资源生命周期是否支持长会话恢复。这个顺序能把“浏览器很卡”拆成可证伪的阶段结论。
最小自检任务
你维护一个在线 3D 产品编辑器。页面中间是 WebGPU canvas,右侧是 DOM 材质面板,用户可以拖动模型、上传贴图、调整材质并导出当前视图。最近出现四个问题:拖动模型时每隔几秒停顿一次;上传第三方贴图后导出视图报 SecurityError;打开右侧面板后帧率下降;页面运行一小时后显存占用持续上升。请按本章方法写出排查顺序,并说明每一步要看哪些证据。
答案要点
先处理 SecurityError,因为它来自资源安全边界,后续渲染优化无法改变已经 tainted 的读回限制。检查第三方贴图 URL、CORS 响应、客户端 crossOrigin 设置、贴图是否进入导出帧,以及导出路径是否读取 canvas 像素。结论应落到资源级别:该贴图可显示、可上传 GPU 或可参与导出中的哪一种能力。
再处理拖动停顿。先看 rAF 间隔和 pointer event 到 rAF 的延迟,确认停顿是否发生在主线程。若 trace 中有 long task,继续定位到材质更新、模型解析、纹理解码、对象分配或同步 readback。若主线程空闲,继续看 WebGPU 提交、buffer/texture upload、shader/pipeline 切换和 GPU timing。修复方向应优先压缩 rAF 内工作,把重型解析、贴图处理和索引构建迁移到 worker 或多帧队列。
接着处理右侧面板导致的帧率下降。检查 layout、style recalculation、paint、composite 和组件更新时间,确认材质参数滑动是否触发整棵 DOM 面板更新。若 overlay 使用大面积透明、blur、复杂阴影或频繁 layout 属性变化,应把高频预览状态和低频表单状态分离,并减少每帧 DOM 写入。
最后处理长会话显存上升。记录每类 GPU texture、buffer、sampler、bind group 和 pipeline 的创建与释放数量;同时记录 CPU 解码数据、贴图缓存和历史 undo 栈。给材质贴图、预览缩略图、旧模型 buffer 和临时 render target 设置字节预算与最近使用帧号。验证时看一小时内 GPU resource count 是否随操作回落,context/device loss 后是否能按资源描述符重建。
本章知识点总结
- 安全边界:浏览器图形 API 运行在网页安全模型内,跨源资源、GPU 访问、计时精度和共享能力都会影响渲染路径。
- 跨源读回:跨源图片缺少 CORS 许可时,canvas 可以显示图像,但像素读回、截图导出和测试快照会受到安全限制。
- 能力查询:WebGPU 和 WebGL 工程应集中记录 adapter、device、feature、limit、format 和错误回调,后续资源创建都依赖这个 profile。
- 计时证据:单帧尖峰只能提示异常,连续阶段性尖峰才支撑主线程、上传、GPU 或合成瓶颈判断。
- 主线程预算:rAF 提供动画调度入口,帧稳定性取决于 rAF 内工作量、对象分配、资源上传和 DOM 读写顺序。
- 任务切片:大型解析、索引构建、几何压缩和统计任务应拆成 worker 或多帧 chunk,交互帧只保留必需更新。
- 离屏渲染:OffscreenCanvas 可以把部分 canvas 工作移到 worker,但消息协议、资源所有权和恢复路径需要明确设计。
- 内存预算:浏览器图形资源通常同时存在网络数据、CPU 解码数据和 GPU 资源三份成本,生命周期需要逐层释放。
- DPR 成本:设备像素比会放大 render target 像素量、带宽、采样和内存成本,canvas 渲染分辨率应有上限策略。
- 坐标一致:DOM overlay 与 canvas 互操作需要统一 CSS 坐标、drawing buffer 坐标、DPR、viewport 和世界矩阵。
- 合成成本:透明面板、复杂阴影、filter、频繁 transform 和组件树刷新都会进入图形帧预算。
- 排查顺序:浏览器图形瓶颈应按安全策略、主线程、worker/上传、GPU pass、DOM/CSS 合成和内存生命周期逐层定位。