STATIC_DRAW DYNAMIC_DRAW STREAM_DRAW 作用与区别
一、核心区别
标志 |
数据更新频率 |
数据访问模式 |
典型内存位置 |
适用场景 |
STATIC_DRAW |
一次写入,极少修改 |
频繁读取,长期使用 |
高性能静态内存区 |
静态模型、地形、UI 静态元素 |
DYNAMIC_DRAW |
中等频率修改 |
多次读取,多次写入 |
可重写内存区 |
动态 UI、角色动画、可编辑网格 |
STREAM_DRAW |
每帧/高频修改 |
写入后立即使用 |
流式内存区 |
粒子系统、实时生成几何体、GPU 计算 |
二、详细解析
1. STATIC_DRAW
- 行为特点:
- 数据上传后几乎不会修改
- WebGL 会将数据放置在最适合快速读取的内存区域
- 修改数据时性能开销较大(需重新分配内存)
- 代码示例:
// 静态地形顶点数据
const staticVertices = new Float32Array([...]);
gl.bufferData(gl.ARRAY_BUFFER, staticVertices, gl.STATIC_DRAW);
2. DYNAMIC_DRAW
- 行为特点:
- 数据会被多次修改,但修改频率低于每帧
- WebGL 会优先选择可高效重写的内存区域
- 支持 bufferSubData 部分更新
- 代码示例:
// 动态 UI 元素位置数据
const dynamicUI = new Float32Array([...]);
gl.bufferData(gl.ARRAY_BUFFER, dynamicUI, gl.DYNAMIC_DRAW);
// 后续更新(例如响应窗口大小变化)
function updateUI() {
dynamicUI.set(newPositions);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, dynamicUI);
}
3. STREAM_DRAW
- 行为特点:
- 数据每帧都会完全更新
- WebGL 会使用可快速写入但读取效率较低的内存
- 通常与 bufferData 全量更新配合使用(而非 bufferSubData)
- 代码示例:
// 粒子系统位置数据(每帧更新)
let particlePositions = new Float32Array(MAX_PARTICLES * 3);
gl.bufferData(gl.ARRAY_BUFFER, particlePositions, gl.STREAM_DRAW);
function animate() {
// 每帧生成新数据
generateNewParticlePositions(particlePositions);
gl.bufferData(gl.ARRAY_BUFFER, particlePositions, gl.STREAM_DRAW);
gl.drawArrays(gl.POINTS, 0, MAX_PARTICLES);
requestAnimationFrame(animate);
}
三、性能优化原则
-
1. 不要滥用 DYNAMIC/STREAM,对静态数据滥用 DYNAMIC/STREAM 会浪费优化机会
-
2. 更新策略匹配标志
标志 |
推荐更新方法 |
避免的操作 |
STATIC_DRAW |
初始化时一次性 bufferData |
后续任何更新操作 |
DYNAMIC_DRAW |
使用 bufferSubData 部分更新 |
频繁全量 bufferData |
STREAM_DRAW |
每帧全量 bufferData |
多次 bufferSubData 调用 |
-
3. 内存管理注意事项: STREAM_DRAW 缓冲区的大小变化会导致内存重新分配:
// 错误:频繁改变数据大小
const varyingSizeData = new Float32Array(frameCount % 100);
gl.bufferData(gl.ARRAY_BUFFER, varyingSizeData, gl.STREAM_DRAW);
四、底层实现差异(以现代 GPU 为例)
标志 |
内存类型 |
典型访问延迟 |
写入带宽 |
读取带宽 |
STATIC_DRAW |
VRAM 静态区 |
低 (~100ns) |
低 |
高 |
DYNAMIC_DRAW |
VRAM 可映射区 |
中 (~300ns) |
中 |
中 |
STREAM_DRAW |
系统内存 → 直接上传路径 |
高 (~1μs) |
高 |
低 |