学废了系列 - WebGL与Node.js中的Buffer
WebGL 和 Node.js 中都有 Buffer 的使用,简单对比记录一下两个完全不相干的领域中 Buffer 异同,加强记忆。
Buffer 是用来存储二进制数据的「缓冲区」,其本身的定义和用途在任何技术领域都是一致的,跟 WebGL 和 Node.js 没有直接关系,两者唯一的共同点就是都使用 JavaScript。
在 ES6 将TypedArray(二进制类型数组)正式加入 ECMA 标准之前,JavaScript 语言本身并没有标准的处理二进制数据的能力,Buffer 就是为了弥补这一缺陷。
TypedArray成为 ECMA 标准之前就已经在 WebGL 领域广泛使用了。
Node.js 加入 Buffer 的作用主要是为了处理 stream,比如网络流、文件流等等。Buffer 占用预申请的一整片内存,stream 被消费的速度如果低于接收速度,就会被暂存在缓冲区内,然后被消费者从缓存区依序取出消费。
Node.js 中的 Buffer 是 Uint8Array 的子类,Uint8Array 是ECMA 标准中 TypedArray 中的一种数据类型。
console.log(Buffer.__proto__)
// 打印 [Function: Uint8Array]
其实 Node.js 中的 Buffer 与 ECMA 标准的 TypedArray 并没有直接关系,Node.js 很早期的版本(v0.10.0)版本就支持了 Buffer。Uint8Array,或者说 ECMA 标准中所有的 TypedArray 都是 JavaScript 引擎提供的一种 API,早期未被加入 ECMA 标准的时候就已经有不少引擎实现了这些 API,而最早使用二进制类型数组的场景就是 WebGL。
话说回来,ECMA 标准做的不就是“集百家之长”(修辞手法-反讽)的事吗哈哈
然后说到 WebGL 中的 Buffer。
WebGL 有两种 Buffer 类型:
ARRAY_BUFFER:顶点属性数据的 Buffer,用来传递任何跟顶点相关的数据,比如坐标、颜色等等。这些数据一般是浮点数,最常用的类型是Float32Array;ELEMENT_ARRAY_BUFFER:元素索引数据的 Buffer,用来传递读取ARRAY_BUFFER元素的顺序。每个元素必须是整数,使用Uint8Array,这一点跟 Node.js 中的 Buffer 一致。此 buffer 是可选项,如果不使用的话 ,ARRAY_BUFFER的元素会被按照 index 依序读取。
虽然 WebGL 中没有 stream 的概念(严格来说是从开发者的认知层面没有 stream,底层 OpenGL 处理 buffer 数据的流程中是有 stream 的),但 Buffer 的作用跟 Node.js 是一致的,都是将数据暂存在一整片预申请的内存中,供后续进程逻辑消费,区别是消费者不同。
在WebGL渲染管线中,但从CPU到GPU完整的数据传输链路中,有以下几种buffer:
- VBO,Vertex Buffer Object,顶点缓冲对象储存顶点属性数据,消费者是 shader,严格的说是 vertex shader;
- FBO,Fragment Buffer Object,帧缓冲对象可以简单理解为一个指针集合体,附着 RBO、颜色、纹理等用于渲染的所有信息;
- RBO,Rendering Buffer Object,渲染缓冲对象储存 depth(深度)、stencil(模板)值。
FBO 与 RBO、纹理的关系如下图:

另外一点需要了解的是 buffer 对象从 CPU 流转到 GPU 的过程,这个过程涉及到总线通讯,虽然这些跟 Node.js 没有一毛钱关系,但是其中的一些实现跟 Node.js 常见八股文面试题「跨进程通信」有一些相同的理念。
WebGL中buffer最初被创建和寄存在CPU内存中,如何让GPU访问CPU内存呢?回答这个问题之前先介绍几个基本概念:
- CPU 的内存一般称为 main memory
- GPU 自己的储存称为 local memory
在 WebGL/OpenGL 中,顶点数据被创建被寄存在 main memory 中,GPU 需要得到这部分数据进行渲染,但是 main memory 和 local memory 是绝对隔离的,不能互相访问。
对于集成显卡来说,GPU 和 CPU 共享总线,GPU 没有自己独立的储存空间,一般是从 CPU 储存中分配出一块空间给 GPU 使用,我们把这部分空间姑且叫做显存(严格来说集成显卡没有显存的概念)。为了实现 GPU 和 CPU 数据的共享,业内引入了一种叫做 GART(Graphic Address Remapping Table)的技术,GART简单说就是一个映射 main memory 和 local memory 地址的表。集成显卡的显存一般很小,必然是小于内存的(一般默认上限是内存总量的1/4),OS 将整个 local memory 空间映射到 main memory,维护一个 GART。此时 buffer 数据的流转如下图所示:

但是这套流程在独立显卡中是行不通的,因为独立显卡的显存非常大,如果使用 GART 将显存空间完全映射到 CPU 内存中会占用非常大的内存空间,32位系统的整个内存空间也就仅仅4GB,如果分出 2GB 给显存映射,那就别干啥了。
这下明白为啥64位系统玩游戏更爽了吧~
所以对于独立显卡需要另外一套 CPU 与 GPU 的数据共享机制。目前比较普遍的方式是在内存中单独划出一块物理空间用于 CPU 和 GPU 之间的数据交换中转,这部分内存空间叫做 pinned memory(锁定内存)。buffer 数据首先会被从 main memory 中拷贝到 pinned memory 中,然后通过 DMA(Direct Memory Access,直接内存访问)机制将数据传输到 GPU,整个过程如下:

请注意, pinned memory 是一块物理内存而不是虚拟内存,这样能够保证DMA的传输性能。
这下明白为啥打游戏一定要加大内存了吧~
独立显卡的这套数据交换机制跟 Node.js 八股文「跨进程通信」的共享内存理念很接近,不过复杂度更高一些。
上面这些内容大都是 OpenGL 和计算机底层的机制,对 WebGL 开发者来说是无感知的,具体到涉及 Buffer 的代码层面, WebGL 需要比 Node.js 更谨慎的处理 Buffer 的内存管理。
Node.js 中 Buffer 在分配内存时采用了 slab 预先申请、事后分配机制,这是在底层C++的逻辑,开发者不可控。这套机制能够提高 Node.js 需要频繁申请 buffer 内存场景下的性能表现。而 WebGL 中并没有这套机制,需要开发者自行处理。一般的做法是预申请一个容量很大的 buffer,然后使用 gl.bufferSubData(类似Node.js 的 Buffer.fill)局部更新数据,这样能避免频繁申请内存空间造成的性能损耗。
以上。
学废了系列 - WebGL与Node.js中的Buffer的更多相关文章
- Node.js中的Buffer
Buffer介绍 为什么要用Buffer? 在Node/ES6 出现之前,前端工程师只需要进行一些简单的额字符串或者ODM操作就可以满足业务需求了,所有对二进制数据比较陌生. 在node出现之后,前端 ...
- node.js中的buffer.fill
buffer.fill(value, [offset], [end]) 接收参数: value 将要填充的数据 offet 填充数据的开始位置,不指定默认为 0 ...
- Node.js缓冲模块Buffer
前言 Javascript是为浏览器而设计的,能很好的处理unicode编码的字符串,但对于二进制或非unicode编码的数据就显得无能为力. Node.js继承Javascript的语言特性,同时又 ...
- node.js系列笔记之node.js初识《一》
node.js系列笔记之node.js初识<一> 一:环境说明 1.1 Linux系统CentOS 5.8 1.2 nodejs v0.10.15 1.3 nodejs源码下载地址 htt ...
- 在Node.js中使用RabbitMQ系列二 任务队列
在上一篇文章在Node.js中使用RabbitMQ系列一 Hello world我有使用一个任务队列,不过当时的场景是将消息发送给一个消费者,本篇文章我将讨论有多个消费者的场景. 其实,任务队列最核心 ...
- node.js中的回调
同步和阻塞:这两个术语可以互换使用,指的是代码的执行会在函数返回之前停止.如果某个操作阻塞,那么脚本就无法继续,这意味着必须等待. 异步和非阻塞:这两个术语可以互换使用,指的是基于回调的.允许脚本并行 ...
- Node.js中setTimeout和setInterval的使用
Node.js和js一样也有计时器,超时计时器.间隔计时器.及时计时器,它们以及process.nextTick(callback)函数来实现事件调度.今天先学下setTimeout和setInter ...
- 如何在node.js中使用neo4j
本章中你将会学到如何在node.js中使用neo4j图形数据库. 当你想存储或者查询和数据紧密关联的数据的时候,图形数据库很有用. neo4j是一个可有效存储,处理和查询你数据模型中紧密相连的元素的数 ...
- 在node.js中,使用基于ORM架构的Sequelize,操作mysql数据库之增删改查
Sequelize是一个基于promise的关系型数据库ORM框架,这个库完全采用JavaScript开发并且能够用在Node.JS环境中,易于使用,支持多SQL方言(dialect),.它当前支持M ...
随机推荐
- ceph-csi源码分析(3)-rbd driver-服务入口分析
更多ceph-csi其他源码分析,请查看下面这篇博文:kubernetes ceph-csi分析目录导航 ceph-csi源码分析(3)-rbd driver-服务入口分析 当ceph-csi组件启动 ...
- Springboot下载Excel的3种方式
Springboot下载Excel的3种方式 汇总一下浏览器下载和代码本地下载实现的3种方式. (其实一般都是在代码生成excel,然后上传到oss,然后传链接给前台,但是我好像没有实现过直接点击就能 ...
- python之set集合,基础篇
集合:set 特点:1>.无序 ,因为集合是无序的,所以不可用下标值查询,也不可切片2>.去重 ,一个集合内不能有两个相同的元素3>.可添加,可删除,不可修改等等4>.集合内的 ...
- angular组件间的通信(父子、不同组件的数据、方法的传递和调用)
angular组件间的通信(父子.不同组件的数据.方法的传递和调用) 一.不同组件的传值(使用服务解决) 1.创建服务组件 不同组件相互传递,使用服务组件,比较方便,简单,容易.先将公共组件写在服务的 ...
- Hadoop:Hadoop的安装
CentOS7安装Hadoop需要有JDK,所以先下载安装JDK后,在进行安装Hadoop 下载Hadoop #联网状态下使用wget命令 wget http://archive.apache.org ...
- mybatis框架的第二天
一.mybatis的基础crud的操作 先在接口中,写对应的方法名称,返回类型,访问符. 之后在映射配置文件中,写具体的实现 二.mybati中crud的细节 1.模糊查询 这是接口中 这是xml中 ...
- TestComplete 64位和32位之间的区别
在64位系统上,有两种版本的TestComplete:32位和64位.本主题描述了TestComplete x64及其32位版本之间的区别.关于TestComplete x64启动TestComple ...
- 『无为则无心』Python函数 — 28、Python函数的简单应用
目录 1.函数嵌套调用 2.Python函数的简单应用 (1)打印线条 (2)函数计算 (3)打印图形 3.函数的说明文档 (1)函数的说明文档的作用 (2)函数说明文档的语法 (3)查看函数的说明文 ...
- 洛谷P5463 小鱼比可爱(加强版) 题解
写博客不易,来玩会? 这道题我和dalao们的做法略有不同,我用的是归并排序做法qwq 归并排序求逆序对大家应该很清楚了,我这里就来讲讲如何用归并排序求出这道题的答案 让我们先观察一下规律 举个栗子, ...
- Spark RDD编程-大数据课设
目录 一.实验目的 二.实验平台 三.实验内容.要求 1.pyspark交互式编程 2.编写独立应用程序实现数据去重 3.编写独立应用程序实现求平均值问题 四.实验过程 (一)pyspark交互式编程 ...