经纬志
首页 / luma.gl / 教程

Device 与渲染管线

先看结论

luma.gl 9 把所有 GPU 资源收到一个设备对象下,绘制不再是「用 program 画」, 而是「编译着色器、组装管线、在渲染通道里绘制」。三者的生命周期互相独立, 释放顺序也要跟着这个结构走。

最后验证 2026-11-03 · luma.gl 9.4 · 更新 2026-10-29 · 引用本文

v9 的资源结构

对象由谁创建负责什么对应原生 WebGL
Deviceluma.createDevice()上下文、画布、资源工厂WebGL2RenderingContext
Shaderdevice.createShader()单个阶段的编译(顶点或片元)createShader / compileShader
RenderPipelinedevice.createRenderPipeline()着色器 + 布局 + 固定状态linkProgram 加状态设置
VertexArraydevice.createVertexArray()缓冲区与属性的绑定vertexAttribPointer
RenderPassdevice.beginRenderPass()渲染目标、清除、绘制命令bindFramebuffer / clear / drawArrays

v8 的 Program 在 v9 中被移除。它原本同时承担「着色器链接」与 「uniform 与 attribute 管理」,在 WebGPU 上无法对应: WebGPU 的管线是不可变对象,着色器源码、顶点布局、深度与混合状态必须一次性确定。 v9 因此把它拆成 Shader 与 RenderPipeline, 日常绘制再由 @luma.gl/engine 的 Model 封装。

完整代码:一个三角形

UMD 版本没有内置适配器,必须按顺序加载四个包:core 提供设备与资源、 webgl 提供 WebGL2 适配器、shadertools 提供着色器组装、 engine 提供 Model 与 AnimationLoop。 全局对象是 luma,创建设备的入口写成 luma.luma.createDevice, 适配器写 luma.webgl2Adapter。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <style>
    html, body { height: 100%; margin: 0; background: #0d1117; }
    #cv { display: block; width: 100%; height: 100%; }
  </style>
</head>
<body>
  <canvas id="cv"></canvas>

  <script src="https://cdn.jsdelivr.net/npm/@luma.gl/core@9.4.2/dist/dist.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/@luma.gl/webgl@9.4.2/dist/dist.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/@luma.gl/shadertools@9.4.2/dist/dist.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/@luma.gl/engine@9.4.2/dist/dist.min.js"></script>

  <script>
    (async function () {
      const device = await luma.luma.createDevice({
        type: 'webgl',
        adapters: [luma.webgl2Adapter],
        createCanvasContext: {
          canvas: document.getElementById('cv'),
          useDevicePixels: Math.min(window.devicePixelRatio, 2),
          autoResize: true
        }
      });

      // 1. 两个阶段分别编译
      const vs = device.createShader({
        stage: 'vertex',
        source: [
          '#version 300 es',
          'in vec3 positions;',
          'void main() { gl_Position = vec4(positions, 1.0); }'
        ].join('\n')
      });

      const fs = device.createShader({
        stage: 'fragment',
        source: [
          '#version 300 es',
          'precision highp float;',
          'out vec4 fragColor;',
          'void main() { fragColor = vec4(0.35, 0.80, 0.62, 1.0); }'
        ].join('\n')
      });

      // 2. 管线固定拓扑、布局与状态
      const pipeline = device.createRenderPipeline({
        vs: vs,
        fs: fs,
        topology: 'triangle-list',
        bufferLayout: [
          { name: 'positions', stepMode: 'vertex', format: 'float32x3' }
        ],
        parameters: {
          depthCompare: 'less-equal',
          depthWriteEnabled: true
        }
      });

      // 3. 顶点数组把缓冲区挂到属性上
      const positions = device.createBuffer(new Float32Array([
        -0.6, -0.45, 0,
         0.6, -0.45, 0,
         0.0,  0.55, 0
      ]));

      const vertexArray = device.createVertexArray({
        shaderLayout: pipeline.shaderLayout,
        bufferLayout: pipeline.bufferLayout
      });

      const slot = vertexArray.getBufferSlot('positions');
      if (slot !== null) vertexArray.setBuffer(slot, positions);

      // 4. 通道里绘制
      const pass = device.beginRenderPass({
        clearColor: [0.05, 0.07, 0.10, 1]
      });
      pass.setPipeline(pipeline);
      pass.setVertexArray(vertexArray);
      pass.draw({ vertexCount: 3 });
      pass.end();
    })();
  </script>
</body>
</html>

createShader 的 source 必须是完整的单阶段源码, 包含 #version 300 es 与 precision 声明。 createRenderPipeline 接收的是 Shader 对象, 传源码字符串不会自动编译,管线拿到的是无效引用。

运行期查询与释放

设备上挂着能力查询与上限,用来在运行期决定分支。格式支持与否必须在创建资源前问清楚, 写死的格式在部分显卡上会直接失败。

console.log(device.type);                     // 'webgl'
console.log(device.info.renderer);            // 显卡型号
console.log(device.info.shadingLanguage);     // 'glsl'
console.log(JSON.stringify(device.limits));   // 各类上限

if (device.isTextureFormatSupported('rgba16float')) {
  // 可以创建半浮点离屏目标
}

// 释放顺序:先子后父
pipeline.destroy();
vertexArray.destroy();
positions.destroy();
device.destroy();

device.destroy() 会让它创建的所有资源失效,之后继续绘制拿到的是空句柄。 上下文丢失时 device.lost 兑现,需要重建设备并重新上传资源。

注意事项

拿 v8 的 Program 写法硬套

new Program(gl, {vs, fs}) 在 v9 里没有对应类。 迁移时把每个 program 拆成两个 Shader 加一条 RenderPipeline,把原来在绘制前设置的深度、混合状态搬进 parameters。

管线创建后立刻绘制

链接状态可能是 pending。首帧出现空洞时先查 pipeline.isPending 与 linkStatus, 不要急着怀疑顶点数据或布局。

设备只建一次

WebGL 后端一个设备只挂一个画布,重复调用 createCanvasContext 会抛错。 多个视图需要共享上下文时,用同一台设备加不同的帧缓冲, 而不是建多台设备。

引用本文

经纬志(2026)。《Device 与渲染管线》。https://www.jingweizhi.com/lumagl/tutorial/device-pipeline.html(访问日期 2026-11-03)。