关于gpu:WebGPU-规范篇-04-纹理

55次阅读

共计 9377 个字符,预计需要花费 24 分钟才能阅读完成。

系列博客总目录:https://segmentfault.com/a/1190000040716735


WebGPU 之纹理

1 纹理的创立

纹理由 device.createTexture() 创立,类型是 GPUTexture

[Exposed=(Window, DedicatedWorker), SecureContext]
interface GPUTexture {GPUTextureView createView(optional GPUTextureViewDescriptor descriptor = {});

  undefined destroy();};
GPUTexture includes GPUObjectBase;

createTexture 办法须要一个 GPUTextureDescriptor 类型的对象:

dictionary GPUTextureDescriptor : GPUObjectDescriptorBase {
  required GPUExtent3D size;
  GPUIntegerCoordinate mipLevelCount = 1;
  GPUSize32 sampleCount = 1;
  GPUTextureDimension dimension = "2d";
  required GPUTextureFormat format;
  required GPUTextureUsageFlags usage;
};

有三个必选参数:

  • size: GPUExtent3D 类型,示意

    • GPUExtent3D 类型
  • format: GPUTextureFormat 类型,即纹理的格局;

    • GPUTextureFormat 是一个比拟大的局部,见本文中的前面局部介绍 纹理格局
  • usage: GPUTextureUsageFlags 类型,即纹理的用处;

    • GPUTextureUsage 是一个枚举类型,有 5 个可选值 COPY_SRCCOPY_DSTTEXTURE_BINDINGSTORAGE_BINDINGRENDER_ATTACHMENT,别离代表十六进制值 0x010x020x040x080x10
typedef [EnforceRange] unsigned long GPUTextureUsageFlags;
[Exposed=(Window, DedicatedWorker)]
namespace GPUTextureUsage {
  const GPUFlagsConstant COPY_SRC          = 0x01;
  const GPUFlagsConstant COPY_DST          = 0x02;
  const GPUFlagsConstant TEXTURE_BINDING   = 0x04;
  const GPUFlagsConstant STORAGE_BINDING   = 0x08;
  const GPUFlagsConstant RENDER_ATTACHMENT = 0x10;
};

还有三个可选参数:

  • mipLevelCount,类型是 unsigned long,默认值是 1,示意 mipmap 的等级数
  • sampleCount,类型是 unsigned long,默认值是 1,示意采样次数,只能是 1 或 4
  • dimension,类型是 GPUTextureDimension 枚举,默认值是 "2d",示意纹理的维度,即默认是二维纹理;

GPUTextureDimension 的定义如下:

enum GPUTextureDimension {
  "1d",
  "2d",
  "3d",
};

译者注:mipLevelCount 中所提及的 mipmap,即多级纹理,相似金字塔技术,而 mipLevelCount 即多级纹理的有多少级。

举例

例如,创立一个用于多重采样抗锯齿渲染的纹理,其用处是 GPUTextureUsage.RENDER_ATTACHMENT(即色彩附件),采样次数为 4 次,分辨率同 canvas 的绘制分辨率(设为 800 × 600),那么:

const msaaTexture = device.createTexture({
  size: {
    width: 800,
    height: 600,
  },
  sampleCount: 4,
  format: "bgra8unorm",
  usage: GPUTextureUsage.RENDER_ATTACHMENT
})

又或者,你要创立一个再一般不过的漫反射贴图纹理,这个漫反射贴图图片大小是 256 × 256:

const texture = device.createTexture({size: [256, 256],
  format: "rgba8unorm",
  usage: GPUTextureUsage.SAMPLED | GPUTextureUsage.COPY_DST
})

对于如何将 JavaScript 中读取到。的图片数据传递给纹理对象,请参考本文 为纹理输出图片 / 视频数据。

创立时参数不非法的状况

接下来要阐明创立纹理对象时,参数设置有问题的状况:

  • 如果 format 参数用的 GPUTextureFormatdevice.features 中没有启用,那么会报错;
  • 如果触发了下列逻辑关系,那么会返回一个不可用的 GPUTexture 对象,并产生一个 GPUValidationError 谬误:

    • 设施对象不可用;
    • size 参数的 width、height、depthOrArrayLayers 属性不是正整数;
    • mipLevelCount 不是正整数;
    • sampleCount 既不是 1 也不是 4;
    • dimension 参数如果是 “1d” 且

      • size 参数的 width 属性大于了设施限度列表中的 maxTextureDimension1D
      • size 参数的 height 属性值不是 1
      • size 参数的 depthOrArrayLayers 不是 1
      • sampleCount 参数不是 1
      • format 被设为一种压缩格局或 深度 / 模板 类型的格局
    • dimension 参数如果是 “2d” 且

      • size 参数的 width 属性大于了设施限度列表中的 maxTextureDimension2D
      • size 参数的 height 属性大于了设施限度列表中的 maxTextureDimension2D
      • size 参数的 depthOrArrayLayers 属性大于或等于了设施限度列表中的 maxTextureArrayLayers
    • dimension 参数如果是 “3d” 且

      • size 参数的 width 属性大于了设施限度列表中的 maxTextureDimension3D
      • size 参数的 height 属性大于了设施限度列表中的 maxTextureDimension3D
      • size 参数的 depthOrArrayLayers 属性大于或等于了设施限度列表中的 maxTextureArrayLayers
      • sampleCount 参数不是 1
      • format 被设为一种压缩格局或 深度 / 模板 类型的格局
    • size 参数的 width 属性不是 纹素 (texel) 块的宽的整数倍;
    • size 参数的 height 属性不是 纹素块高的整数倍
    • 如果 sampleCount 参数大于 1 且

      • mipLevelCount 不是 1
      • size.depthOrArrayLayers 不是 1
      • usage 包含了 STORAGE_BINDING 类型
      • format 不是可渲染类型(即色彩格局或深度 / 模板格局)
    • mipLevelCount 大于了最大 mip 等级计数值(最大 mip 等级计数值取值函数 稍后)
    • usage 不是 GPUTextureUsage 的联结类型
    • usage 若包含 RENDER_ATTACHMENT,然而 format 参数不是可渲染类型(即色彩格局或深度 / 模板格局)
    • usage 包含 STORAGE_ATTACHMENT,然而 format 参数不是纯色格局表中的 STORAGE_BINDING 类型的格局(参考 WebGPU Spec 24.1.1 Plain color formats 纯色格局)

很长,然而不须要齐全记忆,仅需在报错的时候来找起因即可。

最大 mip 等级计数值取值函数 伪代码:

创立 GPUTexture 的参数:dimension, size;
如果 dimension:
    是 "1d", 令 m = size.width
    是 "2d", 令 m = max(size.width, size.height)
    是 "3d", 令 m = max(max(size.width, size.height), size.depthOrArrayLayers)
    
返回 floor(log_2(m)) + 1

2 纹理视图 GPUTextureView

截至发文,官网临时还未对纹理视图对象做出定义,可能借鉴了经典的 Model – View 设计吧。

总之,纹理要通过绑定组对象(GPUBindGroup)传递给着色器,或者要传递给可编程通道编码器(GPUProgramablePassEncoder),必须是传递其纹理视图对象。

创立纹理视图对象其实蛮简略,由纹理对象调用其 createView() 办法即可,这个办法的参数对象能够不传递(通常大多数时候是如此)。

来看看这个可选但不可空的创立参数对象的类型 GPUTextureViewDescriptor

dictionary GPUTextureViewDescriptor : GPUObjectDescriptorBase {
  GPUTextureFormat format;
  GPUTextureViewDimension dimension;
  GPUTextureAspect aspect = "all";
  GPUIntegerCoordinate baseMipLevel = 0;
  GPUIntegerCoordinate mipLevelCount;
  GPUIntegerCoordinate baseArrayLayer = 0;
  GPUIntegerCoordinate arrayLayerCount;
};

enum GPUTextureViewDimension {
  "1d",
  "2d",
  "2d-array",
  "cube",
  "cube-array",
  "3d"
};

enum GPUTextureAspect {
  "all",
  "stencil-only",
  "depth-only"
};
  • 参数 format 即格局,同 GPUTexture;
  • 参数 dimension 与 GPUTexture 的 GPUTextureDimension 不大一样,是 GPUTextureViewDimension 类型的,多了几个值;
  • 参数 aspect 是枚举类型 GPUTextureAspect 的,指定这个 GPUTextureView 用到纹理对象的哪些方面;
  • 参数 baseMipLevel 为 unsigned long 类型,它指定其 mipmap(多级纹理)的根底等级,默认是 0;
  • 参数 mipLevelCount 为 unsigned long 类型,它与 GPUTexture 的 mipLevelCount 意义雷同;
  • 参数 baseArrayLayer 为 unsigned long 类型,它默认值是 0;
  • 参数 arrayLayerCount 为 unsigned long 类型。

创立时参数不非法的状况

同样的,对这些参数也有肯定的限度。

一旦有合乎以下逻辑的,会产生 GPUValidationError,并返回一个有效的 GPUTextureView

  • 对应的纹理对象生效;
  • 如果参数 aspect 是 “stencil-only”:

    • 参数 format 不是 WebGPU Spec 24.1.2 深度 / 模板纹理格局类型 的纹理格局中有模板的那一类
  • 如果参数 aspect 是 “depth-only”:

    • 参数 format 不是 WebGPU Spec 24.1.2 深度 / 模板纹理格局类型 的纹理格局中有深度的那一类
  • 参数 mipLevelCount 小于等于 0;
  • 参数 baseMipLevel + mipLevelCount 的和大于了 mipLevelCount
  • 参数 arrayLayerCount 小于等于 0;
  • 参数 baseArrayLayer + arrayLayerCount 的和大于了 arrayLayerCoun;
  • 参数 format 不是 GPUTextureFormat 类型的;
  • 如果参数 dimension 是 “1d”,且 arrayLayerCount 不是 1;
  • 如果参数 dimension 是 “2d”,且 arrayLayerCount 不是 1;
  • 如果参数 dimension 是 “2d-array”,且纹理对象的 descriptor 的 dimension 不是 “2d”
  • 如果参数 dimension 是 “cube”,且

    • arrayLayerCount 不是 6 或
    • 纹理对象的 descriptor.size 的 width 和 height 不一样
    • 纹理对象的 descriptor 的 dimension 不是 “2d”
  • 如果参数 dimension 是 “cube-array”,且

    • arrayLayerCount 不是 6 的倍数
    • 纹理对象的 descriptor.size 的 width 和 height 不一样
    • 纹理对象的 descriptor 的 dimension 不是 “2d”
  • 如果参数 dimension 是 “3d”,且

    • arrayLayerCount 不是 1
    • 纹理对象的 descriptor 的 dimension 不是 “3d”

* 简述创立过程

这个过程仅作辅助了解材料。

创立一个类型为 GPUTextureView 的对象,将它的 [[texture]] 设为调用创立办法的纹理对象;而后将 [[descriptor]] 设为传入创立办法的参数对象;配置其 extent;

若没在创立纹理视图对象的参数中传递 dimension,那么就会继承纹理对象的 dimension;

若没在创立纹理视图对象的参数中传递 arrayLayerCount,那么依据上一步 dimension 的值:

  • 若为 “1d”、”2d”、”3d”,则 arrayLayerCount 设为 1
  • 若为 “cube”,则 arrayLayerCount 设为 6
  • 若为 “2d-array” 或 “cube-array”,则 arrayLayerCount 设为纹理对象的 size.depthOrArrayLayers 减去 baseArrayLayer 的差值;

最初返回此 GPUTextureView 对象。

如果你不好决定 arrayLayerCount,你能够依据 texture 的 dimension 来判断,若为 “1d” 或 “3d”,则为 1,否则为纹理对象 size.depthOrArrayLayers 的值。

3 纹理格局 GPUTextureFormat

纹理格局的名称中有一些简写,这些简写决定了纹理各个组件的程序、比特位数、数据类型。

  • r,g,b,a 即红绿蓝、阿尔法通明
  • unorm 即 unsigned normalized(无符号归一化)
  • snorm 即 signed normalized(有符号归一化)
  • uint 即 unsigned int(无符号整数)
  • sint 即 signed int(有符号整数)
  • float 即 float point(浮点数)

若格局名有 -srgb 后缀,则在着色器中读取、写入色彩值是要通过 sRGB 色彩转换的;

压缩纹理格局由 GPU 性能列表提供(见适配器局部文档),这种格局应用前缀辨别,例如 etc2-rgba8unorm

纹素块(Texel block) 是纹理中单个可索引的元素(像素纹理中)或压缩块(压缩纹理中)。

纹素块的宽高决定了一个纹素块的大小。

  • 像素纹理的纹素块宽×高永远是 1 × 1
  • 压缩纹理中,宽度是指整个纹素块一行的纹素个数,高度是整个纹素块的行数。

纹素块大小(Texel block size)指的是一个纹素块的字节大小,除了 "stencil8""depth24plus""depth24plus-stencil8" 之外,每种纹理格局的纹素块大小都是恒定的。

上面列举所有纹理格局:

enum GPUTextureFormat {
  // 8-bit 纹理格局
  "r8unorm",
  "r8snorm",
  "r8uint",
  "r8sint",

  // 16-bit 纹理格局
  "r16uint",
  "r16sint",
  "r16float",
  "rg8unorm",
  "rg8snorm",
  "rg8uint",
  "rg8sint",

  // 32-bit 纹理格局
  "r32uint",
  "r32sint",
  "r32float",
  "rg16uint",
  "rg16sint",
  "rg16float",
  "rgba8unorm",
  "rgba8unorm-srgb",
  "rgba8snorm",
  "rgba8uint",
  "rgba8sint",
  "bgra8unorm",
  "bgra8unorm-srgb",
  // Packed 32-bit 纹理格局
  "rgb9e5ufloat",
  "rgb10a2unorm",
  "rg11b10ufloat",

  // 64-bit 纹理格局
  "rg32uint",
  "rg32sint",
  "rg32float",
  "rgba16uint",
  "rgba16sint",
  "rgba16float",

  // 128-bit 纹理格局
  "rgba32uint",
  "rgba32sint",
  "rgba32float",

  // 深度和模板纹理格局
  "stencil8",
  "depth16unorm",
  "depth24plus",
  "depth24plus-stencil8",
  "depth32float",

  // BC 压缩纹理格局:须要在设施上具备 "texture-compression-bc" 性能,并且适配器反对此性能
  "bc1-rgba-unorm",
  "bc1-rgba-unorm-srgb",
  "bc2-rgba-unorm",
  "bc2-rgba-unorm-srgb",
  "bc3-rgba-unorm",
  "bc3-rgba-unorm-srgb",
  "bc4-r-unorm",
  "bc4-r-snorm",
  "bc5-rg-unorm",
  "bc5-rg-snorm",
  "bc6h-rgb-ufloat",
  "bc6h-rgb-float",
  "bc7-rgba-unorm",
  "bc7-rgba-unorm-srgb",

  // 须要在申请设施时启用 "depth24unorm-stencil8" 性能
  "depth24unorm-stencil8",

  // 须要在申请设施时启用 "depth32float-stencil8" 性能
  "depth32float-stencil8",
};

其中,"depth24plus""depth24plus-stencil8" 格局的深度局部能够用 24 位无符号归一化值("depth24unorm")或 32 位 IEEE754 规范浮点数("depth32float")实现。

"stencil8" 格局能够独自实现,也能够在 "depth24stencil8" 种屏蔽掉深度局部实现。

可渲染格局 包含 色彩可渲染格局 深度模板可渲染格局,这部分分类可在 WebGPU Spec 24.1.1 Plain color formats | 纯色格局 列表中的 “RENDER_ATTACHMENT” 类别中找到。

4 为纹理输出图片 / 视频数据

应用 GPUQueue.copyExternalImageToTexture 办法

从内部(也就是 HTMLCanvasElement、HTMLVideoElement、ImageBitmap)中传数据到 GPUTexture,能够应用 GPUQueue.copyExternalImageToTexture 这个办法,这里应用 ImageBitmap 作示例:

// typescript

let texture: GPUTexture;
// 应用代码块宰割作用域;{
    // 申请图像并异步解码为 ImageBitmap
  const img = document.createElement('img');
  img.src = `http://path/to/your/image.png`;
  await img.decode();
  const imageBitmap = await createImageBitmap(img);

  // 创立纹理对象
  texture = device.createTexture({size: [imageBitmap.width, imageBitmap.height, 1],
    format: 'rgba8unorm',
    usage: GPUTextureUsage.TEXTURE_BINDING | 
        GPUTextureUsage.COPY_DST | 
        GPUTextureUsage.RENDER_ATTACHMENT,
  });
  
  // 立刻让队列执行一个“拷贝内部图像数据到纹理”的操作
  device.queue.copyExternalImageToTexture({ source: imageBitmap},
    {texture: cubeTexture},
    [imageBitmap.width, imageBitmap.height]
  );
}

参考

WebGPU Spec 12.3.5 GPUImageCopyExternalImage

WebGPU Spec 17 Queue copyExternalImageToTexture

dictionary GPUImageCopyExternalImage {required (ImageBitmap or HTMLCanvasElement or OffscreenCanvas) source;
  GPUOrigin2D origin = {};};

应用 GPUQueue.writeTexture 办法

这个办法与 GPUQueue.copyExternalImageToTexture 办法略有不同,请读者自行思考二者异同。


应用 GPUDevice.importExternalTexture 办法

对于视频数据的传递,则须要应用 device.importExternalTexture 这个办法,具体请参考:

WebGPU Spec 4.5 GPUDevice importExternalTexture

WebGPU Spec 6.4.1 Import External Textures

译者注

在应用上述导入数据到纹理的办法时,须要分外留神纹理的 usage,在这些办法的文档中应该都有详尽的阐明。譬如,GPUQueue.copyExternalImageToTexture 这个办法,就指明了纹理的用处必须是 RENDER_ATTACHMENTCOPY_DST 的合并:

const texture = device.createTexture({
    /* ... */,
    usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST
})

3 类比 WebGLFramebuffer 离屏渲染的容器

WebGL 绘制的指标是通过 gl.bindFramebuffer() 实现的,通常能够为 WebGLFramebuffer(也就是所谓的 FBO)应用 gl.framebufferTexture2D() 设一个 WebGLTexture 对象作为其色彩附件,来进行离屏渲染,否则就默认绘制到 Canvas 这个“纹理”上。

WebGPU 就没有 FBO 这个类了,然而一次绘制指令作用的容器这个概念还是有的,在 WebGPU 中治理着色彩附件、深度模板附件的类,叫做 GPURenderPassEncoder,到之后讲到 GPURenderPassEncoder, 渲染通道编码器的时候便会高深莫测,在这里提一嘴。

正文完
 0