系列博客总目录: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 作示例:
// 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_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, 渲染通道编码器的时候便会高深莫测,在这里提一嘴。