系列博客总目录: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_SRC
、COPY_DST
、TEXTURE_BINDING
、STORAGE_BINDING
和RENDER_ATTACHMENT
,别离代表十六进制值0x01
、0x02
、0x04
、0x08
和0x10
:
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 或 4dimension
,类型是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 参数用的
GPUTextureFormat
在device.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 被设为一种压缩格局或 深度/模板 类型的格局
- size 参数的 width 属性大于了设施限度列表中的
dimension 参数如果是 "2d" 且
- size 参数的 width 属性大于了设施限度列表中的
maxTextureDimension2D
- size 参数的 height 属性大于了设施限度列表中的
maxTextureDimension2D
- size 参数的 depthOrArrayLayers 属性大于或等于了设施限度列表中的
maxTextureArrayLayers
- size 参数的 width 属性大于了设施限度列表中的
dimension 参数如果是 "3d" 且
- size 参数的 width 属性大于了设施限度列表中的
maxTextureDimension3D
- size 参数的 height 属性大于了设施限度列表中的
maxTextureDimension3D
- size 参数的 depthOrArrayLayers 属性大于或等于了设施限度列表中的
maxTextureArrayLayers
- sampleCount 参数不是1
- format 被设为一种压缩格局或 深度/模板 类型的格局
- size 参数的 width 属性大于了设施限度列表中的
- 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 作示例:
// typescriptlet 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_ATTACHMENT
与 COPY_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, 渲染通道编码器的时候便会高深莫测,在这里提一嘴。