refactoring shaders for reflection and automating binding
This commit is contained in:
parent
2c5ac481a4
commit
42be3fcaa8
16 changed files with 1039 additions and 95 deletions
100
index.js
100
index.js
|
@ -1,5 +1,6 @@
|
|||
import { GraphicsDevice } from './src/core/graphics-device.js'
|
||||
import { PowerPreference } from './src/enum.js'
|
||||
import { PowerPreference, VertexFormat } from './src/enum.js'
|
||||
import { ShaderType } from './src/utils/internal-enums.js'
|
||||
|
||||
async function main() {
|
||||
const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('webgpu-canvas'))
|
||||
|
@ -27,25 +28,48 @@ async function main() {
|
|||
}
|
||||
|
||||
const shaderCode = `
|
||||
@vertex
|
||||
fn vs_main(@location(0) in_pos : vec3<f32>) -> @builtin(position) vec4<f32> {
|
||||
return vec4<f32>(in_pos, 1.0);
|
||||
}
|
||||
@group(0) @binding(0)
|
||||
var<uniform> transform : mat4x4<f32>;
|
||||
|
||||
@fragment
|
||||
fn fs_main() -> @location(0) vec4<f32> {
|
||||
return vec4<f32>(0.0, 0.5, 1.0, 1.0);
|
||||
}
|
||||
`
|
||||
@vertex
|
||||
fn vs_main(@location(0) in_pos : vec3<f32>) -> @builtin(position) vec4<f32> {
|
||||
return transform * vec4<f32>(in_pos, 1.0);
|
||||
}
|
||||
|
||||
const shaderModule = graphicsDevice.createShaderModule(shaderCode, 'TriangleShader')
|
||||
@fragment
|
||||
fn fs_main() -> @location(0) vec4<f32> {
|
||||
return vec4<f32>(0.0, 0.5, 1.0, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
const shaderModule = graphicsDevice.createShaderModule(
|
||||
shaderCode,
|
||||
'SquareShader',
|
||||
ShaderType.Vertex | ShaderType.Fragment
|
||||
)
|
||||
|
||||
const vertices = new Float32Array([
|
||||
0.0, 0.5, 0.0,
|
||||
-0.5, 0.5, 0.0,
|
||||
-0.5, -0.5, 0.0,
|
||||
0.5, 0.5, 0.0,
|
||||
|
||||
0.5, 0.5, 0.0,
|
||||
-0.5, -0.5, 0.0,
|
||||
0.5, -0.5, 0.0
|
||||
])
|
||||
|
||||
const indices = new Uint16Array([
|
||||
0, 1, 2,
|
||||
3, 4, 5
|
||||
])
|
||||
|
||||
const indexBuffer = graphicsDevice.createBuffer({
|
||||
size: indices.byteLength,
|
||||
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
|
||||
})
|
||||
|
||||
indexBuffer.write(indices)
|
||||
|
||||
const vertexBuffer = graphicsDevice.createBuffer(
|
||||
{
|
||||
label: 'TriangleVertexBuffer',
|
||||
|
@ -80,35 +104,29 @@ async function main() {
|
|||
|
||||
const bindGroupLayout = graphicsDevice.createBindGroupLayout(bindings, 'UniformLayout')
|
||||
|
||||
const material = graphicsDevice.createMaterial(
|
||||
{ vertex: shaderModule, fragment: shaderModule },
|
||||
[bindGroupLayout]
|
||||
)
|
||||
|
||||
const pipelineLayout = graphicsDevice.createPipelineLayout([bindGroupLayout], 'PipelineLayout')
|
||||
|
||||
const pipeline = graphicsDevice.createRenderPipeline({
|
||||
label: 'TrianglePipeline',
|
||||
layout: pipelineLayout,
|
||||
vertex: {
|
||||
module: shaderModule.handle,
|
||||
entryPoint: 'vs_main',
|
||||
buffers: [
|
||||
{
|
||||
arrayStride: 3 * 4,
|
||||
attributes: [
|
||||
{ shaderLocation: 0, offset: 0, format: 'float32x3' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
fragment: {
|
||||
module: shaderModule.handle,
|
||||
entryPoint: 'fs_main',
|
||||
targets: [
|
||||
{ format: graphicsDevice.swapChain.format }
|
||||
]
|
||||
},
|
||||
primitive: {
|
||||
topology: 'triangle-list',
|
||||
},
|
||||
})
|
||||
const vertexBufferLayout = {
|
||||
arrayStride: 3 * 4,
|
||||
attributes: [
|
||||
{ shaderLocation: 0, offset: 0, format: VertexFormat.Float32x3 }
|
||||
]
|
||||
}
|
||||
|
||||
const pipelineDescriptor = material.getRenderPipelineDescriptor(
|
||||
[vertexBufferLayout],
|
||||
'SquarePipeline'
|
||||
)
|
||||
|
||||
pipelineDescriptor.fragment.targets = [
|
||||
{ format: graphicsDevice.swapChain.format }
|
||||
]
|
||||
|
||||
const pipeline = graphicsDevice.createRenderPipeline(pipelineDescriptor)
|
||||
|
||||
/** @type {Array<import('./src/core/graphics-device.js').BindGroupEntry>} */
|
||||
const entries = [{
|
||||
|
@ -123,6 +141,7 @@ async function main() {
|
|||
return
|
||||
}
|
||||
|
||||
graphicsDevice.queue.writeBuffer(uniformBuffer.handle, 0, matrixData)
|
||||
const commandRecorder = graphicsDevice.createCommandRecorder('FrameCommands')
|
||||
|
||||
const passEncoder = commandRecorder.beginRenderPass()
|
||||
|
@ -130,8 +149,9 @@ async function main() {
|
|||
if (passEncoder) {
|
||||
passEncoder.setPipeline(pipeline.handle)
|
||||
passEncoder.setVertexBuffer(0, vertexBuffer.handle)
|
||||
passEncoder.setIndexBuffer(indexBuffer.handle, 'uint16')
|
||||
passEncoder.setBindGroup(0, uniformBindGroup.handle)
|
||||
passEncoder.draw(3)
|
||||
passEncoder.drawIndexed(indices.length)
|
||||
|
||||
commandRecorder.endRenderPass()
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "es2020",
|
||||
"module": "es2022",
|
||||
"moduleResolution": "node",
|
||||
"target": "es6",
|
||||
"lib": ["es2022", "dom"],
|
||||
"types": ["@webgpu/types"],
|
||||
"checkJs": true,
|
||||
"paths": {
|
||||
"/*": ["./*"]
|
||||
}
|
||||
"checkJs": true"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
|
|
9
package-lock.json
generated
9
package-lock.json
generated
|
@ -8,6 +8,9 @@
|
|||
"name": "wgpu",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wgsl_reflect": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@webgpu/types": "^0.1.60"
|
||||
}
|
||||
|
@ -18,6 +21,12 @@
|
|||
"integrity": "sha512-8B/tdfRFKdrnejqmvq95ogp8tf52oZ51p3f4QD5m5Paey/qlX4Rhhy5Y8tgFMi7Ms70HzcMMw3EQjH/jdhTwlA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/wgsl_reflect": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wgsl_reflect/-/wgsl_reflect-1.2.0.tgz",
|
||||
"integrity": "sha512-bDYcmWfbg4WsrBmPv6lsyjqXx02r8dkNAzR77OCNqIcR8snO4aNSBTjir9zqgR7rLnw6PaisiZxtCitSCIUlnQ==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,5 +12,8 @@
|
|||
"description": "",
|
||||
"devDependencies": {
|
||||
"@webgpu/types": "^0.1.60"
|
||||
},
|
||||
"dependencies": {
|
||||
"wgsl_reflect": "^1.2.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@ import { Texture } from '../resources/texture.js'
|
|||
*/
|
||||
|
||||
import { Sampler } from '../resources/sampler.js'
|
||||
import { ResourceType } from '../utils/internal-enums.js'
|
||||
import { Material } from '../resources/material.js'
|
||||
|
||||
class GraphicsDeviceBuilder {
|
||||
_canvas
|
||||
|
@ -280,13 +282,15 @@ export class GraphicsDevice extends EventEmitter {
|
|||
* Creates a shader module from WGSL code.
|
||||
* @param {string} code
|
||||
* @param {string} [label]
|
||||
* @param {number} [shaderType]
|
||||
* @param {string} [entryPoint]
|
||||
* @returns {ShaderModule}
|
||||
*/
|
||||
createShaderModule(code, label) {
|
||||
createShaderModule(code, label, shaderType, entryPoint) {
|
||||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||
|
||||
try {
|
||||
return ShaderModule.create(this.device, { code, label })
|
||||
return ShaderModule.create(this.device, { code, label, shaderType, entryPoint })
|
||||
} catch (err) {
|
||||
throw WebGPUObjectError.from(err, ShaderModule)
|
||||
}
|
||||
|
@ -301,13 +305,27 @@ export class GraphicsDevice extends EventEmitter {
|
|||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||
|
||||
try {
|
||||
const gpuPipeline = this.device.createRenderPipeline(descriptor)
|
||||
return new RenderPipeline(gpuPipeline, descriptor.label)
|
||||
const pipeline = this.device.createRenderPipeline(descriptor)
|
||||
return new RenderPipeline(pipeline, descriptor.label)
|
||||
} catch (err) {
|
||||
throw WebGPUObjectError.from(err, RenderPipeline)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('../resources/material.js').ShaderPairDescriptor} shaders
|
||||
* @param {BindGroupLayout[]} bindGroupLayouts
|
||||
*/
|
||||
createMaterial(shaders, bindGroupLayouts) {
|
||||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||
|
||||
try {
|
||||
return new Material(this.device, shaders, bindGroupLayouts)
|
||||
} catch (err) {
|
||||
throw WebGPUObjectError.from(err, Material)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a CommandRecorder to begin recording GPU commands.
|
||||
* @param {string} [label]
|
||||
|
@ -332,6 +350,41 @@ export class GraphicsDevice extends EventEmitter {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BindGroupEntry} binding
|
||||
* @returns {GPUBindingResource}
|
||||
*/
|
||||
_getBindingResource(binding) {
|
||||
const resource = binding.resource
|
||||
switch (resource.resourceType) {
|
||||
case ResourceType.Sampler:
|
||||
return resource.handle
|
||||
|
||||
case ResourceType.TextureView:
|
||||
return 'textureView' in binding ?
|
||||
binding.textureView :
|
||||
resource.getView()
|
||||
|
||||
case ResourceType.Buffer:
|
||||
const bufSize = resource.size
|
||||
|
||||
const offset = 'offset' in binding ? binding.offset : 0
|
||||
const size = 'size' in binding ?
|
||||
binding.size :
|
||||
bufSize - offset
|
||||
|
||||
if (offset + size > bufSize) {
|
||||
throw BufferError.outOfBounds(offset, size)
|
||||
}
|
||||
|
||||
return {
|
||||
buffer: resource.handle,
|
||||
offset,
|
||||
size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BindGroupLayout} layout
|
||||
* @param {BindGroupEntry[]} bindings
|
||||
|
@ -343,7 +396,7 @@ export class GraphicsDevice extends EventEmitter {
|
|||
|
||||
const entries = bindings.map(def => ({
|
||||
binding: def.binding,
|
||||
resource: def.resource.asBindingResource(def)
|
||||
resource: this._getBindingResource(def)
|
||||
}))
|
||||
|
||||
return BindGroup.create(this.device, {
|
||||
|
|
3
src/rendering/render-graph.js
Normal file
3
src/rendering/render-graph.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export class RenderGraph {
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { BufferError, WebGPUError } from '../utils/errors.js'
|
||||
import { ResourceType } from '../utils/internal-enums.js'
|
||||
|
||||
/** @import { TypedArray, TypedArrayConstructor } from '../utils.js' */
|
||||
/** @import { BindGroupEntry, BindingResource, BufferBindingResource } from '../core/graphics-device.js' */
|
||||
|
@ -24,6 +25,10 @@ export class Buffer {
|
|||
return this._handle.usage
|
||||
}
|
||||
|
||||
get resourceType() {
|
||||
return ResourceType.Buffer
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GPUDevice} device
|
||||
* @param {GPUBuffer} texture
|
||||
|
@ -168,24 +173,6 @@ export class Buffer {
|
|||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BindGroupEntry} entry
|
||||
*/
|
||||
asBindingResource(entry) {
|
||||
const offset = 'offset' in entry ? entry.offset : 0
|
||||
const size = 'size' in entry ? entry.size : this.size - offset
|
||||
|
||||
if (offset + size > this.size) {
|
||||
throw BufferError.outOfBounds(offset, size)
|
||||
}
|
||||
|
||||
return {
|
||||
buffer: this._handle,
|
||||
offset,
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._handle?.destroy()
|
||||
this._handle = null
|
||||
|
|
87
src/resources/material.js
Normal file
87
src/resources/material.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
import { ResourceType, TemplateInfo, VariableInfo } from 'wgsl_reflect'
|
||||
import { BindGroupLayout } from './bind-group-layout.js'
|
||||
import { ShaderModule } from './shader-module.js'
|
||||
import { MaterialError } from '../utils/errors.js'
|
||||
import { GroupBindingMap } from '../utils/bindings.js'
|
||||
import { accessToBufferType, accessToStorageTextureAccess, parseTextureType, typeToViewDimension, wgslToWgpuFormat } from '../utils/wgsl-to-wgpu.js'
|
||||
|
||||
/** @import {Either} from '../utils.js' */
|
||||
/** @import { WGSLAccess, WGSLTextureType } from '../utils/wgsl-to-wgpu.js' */
|
||||
|
||||
/**
|
||||
* @typedef Shader2
|
||||
* @property {ShaderModule} vertex
|
||||
* @property {ShaderModule} fragment
|
||||
*
|
||||
* @typedef {Omit<Shader2, 'fragment'>} VertexOnly
|
||||
* @typedef {Omit<Shader2, 'vertex'>} FragmentOnly
|
||||
*
|
||||
* @typedef Unified1
|
||||
* @property {ShaderModule} shaderModule
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {
|
||||
(Shader2 | Either<VertexOnly, FragmentOnly>) |
|
||||
Unified1
|
||||
* } ShaderPairDescriptor
|
||||
*/
|
||||
|
||||
export class Material {
|
||||
_device
|
||||
_shaders
|
||||
_bindGroupLayouts
|
||||
_pipelineLayout
|
||||
|
||||
get shaders() {
|
||||
return this._shaders
|
||||
}
|
||||
|
||||
get bindGroupLayouts() {
|
||||
return this._bindGroupLayouts
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GPUDevice} device
|
||||
* @param {ShaderPairDescriptor} shaders
|
||||
* @param {BindGroupLayout[]} bindGroupLayouts
|
||||
*/
|
||||
constructor(device, shaders, bindGroupLayouts) {
|
||||
this._device = device
|
||||
this._shaders = Material._parseShaders(shaders)
|
||||
this._bindGroupLayouts = bindGroupLayouts
|
||||
|
||||
if (bindGroupLayouts && bindGroupLayouts.length > 0) {
|
||||
this._pipelineLayout = device.createPipelineLayout({
|
||||
bindGroupLayouts: bindGroupLayouts.map(bgl => bgl.handle)
|
||||
})
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Attempts to handle shader modules which represent multiple
|
||||
* shader types.
|
||||
*
|
||||
* @param {ShaderPairDescriptor} shaders
|
||||
* @returns {UnifiedShader | ShaderPair}
|
||||
*/
|
||||
static _parseShaders(shaders) {
|
||||
if (shaders == null) {
|
||||
throw MaterialError.missingShader('both')
|
||||
}
|
||||
|
||||
if (shaders instanceof ShaderModule) {
|
||||
return new UnifiedShader(shaders)
|
||||
}
|
||||
|
||||
const hasVertex = 'vertex' in shaders
|
||||
const hasFragment = 'fragment' in shaders
|
||||
|
||||
if (hasVertex && hasFragment) {
|
||||
return new ShaderPair(shaders.vertex, shaders.fragment)
|
||||
} else if (!hasFragment) {
|
||||
throw MaterialError.missingShader('fragment')
|
||||
} else if (!hasVertex) {
|
||||
throw MaterialError.missingShader('vertex')
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import { WebGPUObjectError } from '../utils/errors.js'
|
||||
import { ResourceType } from '../utils/internal-enums.js'
|
||||
|
||||
/** @import { BindGroupEntry } from '../core/graphics-device.js' */
|
||||
|
||||
|
@ -14,6 +15,10 @@ export class Sampler {
|
|||
return this._handle.label
|
||||
}
|
||||
|
||||
get resourceType() {
|
||||
return ResourceType.Sampler
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GPUDevice} device
|
||||
* @param {GPUSampler} sampler
|
||||
|
@ -37,12 +42,5 @@ export class Sampler {
|
|||
throw WebGPUObjectError.from(err, Sampler)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BindGroupEntry} _entry
|
||||
*/
|
||||
asBindingResource(_entry) {
|
||||
return this._handle
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,35 +1,314 @@
|
|||
import { ResourceType, VariableInfo, WgslReflect } from 'wgsl_reflect'
|
||||
import { WebGPUObjectError } from '../utils/errors.js'
|
||||
import { BindingMap, GroupBindingMap, VariableStageInfo } from '../utils/bindings.js'
|
||||
|
||||
import {
|
||||
accessToBufferType,
|
||||
accessToStorageTextureAccess,
|
||||
parseTextureType,
|
||||
typeToViewDimension,
|
||||
wgslToWgpuFormat
|
||||
} from '../utils/wgsl-to-wgpu.js'
|
||||
|
||||
/** @import { WGSLAccess, WGSLSampledType, WGSLSamplerType, WGSLTextureType } from '../utils/wgsl-to-wgpu.js' */
|
||||
|
||||
export class ShaderModule {
|
||||
_handle
|
||||
_code
|
||||
|
||||
get label() {
|
||||
return this._handle.label
|
||||
}
|
||||
/** @type {WgslReflect | undefined} */
|
||||
_reflection
|
||||
|
||||
get handle() {
|
||||
return this._handle
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GPUShaderModule} module
|
||||
*/
|
||||
constructor(module) {
|
||||
this._handle = module
|
||||
get label() {
|
||||
return this._handle.label
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GPUDevice} device
|
||||
* @param {GPUShaderModuleDescriptor} descriptor
|
||||
* @param {string} code
|
||||
* @param {string} [label]
|
||||
*/
|
||||
static create(device, descriptor) {
|
||||
constructor(device, code, label) {
|
||||
this._code = code
|
||||
|
||||
try {
|
||||
return new ShaderModule(
|
||||
device.createShaderModule(descriptor)
|
||||
)
|
||||
this._handle = device.createShaderModule({ code, label })
|
||||
} catch (err) {
|
||||
throw WebGPUObjectError.from(err, ShaderModule)
|
||||
}
|
||||
}
|
||||
|
||||
reflect() {
|
||||
if (this._reflection == null) {
|
||||
this._reflection = new WgslReflect(this._code)
|
||||
this._code = undefined
|
||||
}
|
||||
|
||||
return this._reflection
|
||||
}
|
||||
}
|
||||
|
||||
export class ReflectedShader {
|
||||
/**
|
||||
* @param {WgslReflect} reflection
|
||||
* @param {GPUShaderStageFlags} stages
|
||||
* @param {GroupBindingMap} [out=new GroupBindingMap()]
|
||||
*/
|
||||
_getBindingsForStage(reflection, stages, out = new GroupBindingMap()) {
|
||||
const groups = reflection.getBindGroups()
|
||||
|
||||
groups.forEach((bindings, groupIndex) => {
|
||||
if (!out.has(groupIndex)) {
|
||||
out.set(groupIndex, new BindingMap())
|
||||
}
|
||||
|
||||
const bindingsMap = out.get(groupIndex)
|
||||
|
||||
bindings.filter(x => x != null).forEach(variableInfo => {
|
||||
if (!bindingsMap.has(variableInfo.binding)) {
|
||||
bindingsMap.set(variableInfo.binding, { variableInfo, stages })
|
||||
} else {
|
||||
bindingsMap.get(variableInfo.binding).stages |= stages
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return out
|
||||
}
|
||||
/**
|
||||
* @param {Map<any, any>} map
|
||||
* @returns {number[]}
|
||||
*/
|
||||
_sortKeyIndices(map) {
|
||||
return Array.from(map.keys()).sort((a, b) => a - b)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {VariableInfo} _variableInfo
|
||||
* @returns {GPUBufferBindingLayout}
|
||||
*/
|
||||
_parseUniform(_variableInfo) {
|
||||
return {
|
||||
type: 'uniform',
|
||||
// TODO: infer these two properties
|
||||
hasDynamicOffset: false,
|
||||
minBindingSize: 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {VariableInfo} variableInfo
|
||||
* @returns {GPUBufferBindingLayout}
|
||||
*/
|
||||
_parseStorage(variableInfo) {
|
||||
return {
|
||||
type: accessToBufferType(
|
||||
/** @type {WGSLAccess} */
|
||||
(variableInfo.access)
|
||||
),
|
||||
// TODO: infer these two properties
|
||||
hasDynamicOffset: false,
|
||||
minBindingSize: 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WGSLTextureType} type
|
||||
* @param {WGSLSampledType} sampledType
|
||||
* @returns {GPUTextureSampleType}
|
||||
*/
|
||||
_parseSampleType(type, sampledType) {
|
||||
if (type.includes('depth')) {
|
||||
return 'depth'
|
||||
}
|
||||
|
||||
switch (sampledType) {
|
||||
case 'f32':
|
||||
case 'i32':
|
||||
case 'u32':
|
||||
default:
|
||||
return 'float'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {VariableInfo} variableInfo
|
||||
* @returns {GPUTextureBindingLayout}
|
||||
*/
|
||||
_parseTexture(variableInfo) {
|
||||
const [type, sampledType] = parseTextureType(variableInfo.type.name)
|
||||
|
||||
return {
|
||||
sampleType: this._parseSampleType(type, sampledType),
|
||||
viewDimension: typeToViewDimension(type),
|
||||
multisampled: type.includes('multisampled')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WGSLSamplerType} type
|
||||
* @returns {GPUSamplerBindingType}
|
||||
*/
|
||||
_parseSamplerType(type) {
|
||||
switch (type) {
|
||||
case 'sampler_comparison':
|
||||
return 'comparison'
|
||||
case 'sampler':
|
||||
default:
|
||||
return 'filtering'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {VariableInfo} variableInfo
|
||||
* @returns {GPUSamplerBindingLayout}
|
||||
*/
|
||||
_parseSampler(variableInfo) {
|
||||
return {
|
||||
type: this._parseSamplerType(
|
||||
/** @type {WGSLSamplerType} */(variableInfo.type.name)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {VariableInfo} variableInfo
|
||||
* @returns {GPUStorageTextureBindingLayout}
|
||||
*/
|
||||
_parseStorageTexture(variableInfo) {
|
||||
const [type] = parseTextureType(variableInfo.type.name)
|
||||
|
||||
return {
|
||||
access: accessToStorageTextureAccess(
|
||||
/** @type {WGSLAccess} */(variableInfo.access)
|
||||
),
|
||||
format: wgslToWgpuFormat(variableInfo.type.name),
|
||||
viewDimension: typeToViewDimension(type)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {VariableStageInfo} variableStageInfo
|
||||
* @returns {GPUBindGroupLayoutEntry}
|
||||
*/
|
||||
_variableInfoToEntry(variableStageInfo) {
|
||||
const { stages: visibility, variableInfo } = variableStageInfo
|
||||
|
||||
switch (variableInfo.resourceType) {
|
||||
case ResourceType.Uniform:
|
||||
return {
|
||||
binding: variableInfo.binding,
|
||||
visibility,
|
||||
buffer: this._parseUniform(variableInfo)
|
||||
}
|
||||
case ResourceType.Storage:
|
||||
return {
|
||||
binding: variableInfo.binding,
|
||||
visibility,
|
||||
buffer: this._parseStorage(variableInfo)
|
||||
}
|
||||
case ResourceType.Texture:
|
||||
return {
|
||||
binding: variableInfo.binding,
|
||||
visibility,
|
||||
texture: this._parseTexture(variableInfo)
|
||||
}
|
||||
case ResourceType.Sampler:
|
||||
return {
|
||||
binding: variableInfo.binding,
|
||||
visibility,
|
||||
sampler: this._parseSampler(variableInfo)
|
||||
}
|
||||
case ResourceType.StorageTexture:
|
||||
return {
|
||||
binding: variableInfo.binding,
|
||||
visibility,
|
||||
storageTexture: this._parseStorageTexture(variableInfo)
|
||||
}
|
||||
default:
|
||||
console.warn(`Unsupported resource type for reflection: ${ResourceType[variableInfo.resourceType]}`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GroupBindingMap} groupBindings
|
||||
*/
|
||||
_createBindGroupLayoutEntries(groupBindings) {
|
||||
const sortedGroupIndices = this._sortKeyIndices(groupBindings)
|
||||
|
||||
return sortedGroupIndices.map(groupIndex => {
|
||||
const bindingsMap = groupBindings.get(groupIndex)
|
||||
return this._sortKeyIndices(bindingsMap).map(bindingIndex => (
|
||||
this._variableInfoToEntry(bindingsMap.get(bindingIndex))
|
||||
))
|
||||
})
|
||||
.filter(desc => desc.length > 0)
|
||||
}
|
||||
}
|
||||
|
||||
export class UnifiedShader extends ReflectedShader {
|
||||
_shader
|
||||
|
||||
/**
|
||||
* @param {ShaderModule} shader
|
||||
*/
|
||||
constructor(shader) {
|
||||
super()
|
||||
|
||||
this._shader = shader
|
||||
}
|
||||
|
||||
createBindGroupLayoutEntries() {
|
||||
return this._createBindGroupLayoutEntries(
|
||||
this._getBindingsForStage(
|
||||
this._shader.reflect(),
|
||||
GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class ReflectedShaderPair extends ReflectedShader {
|
||||
_vertex
|
||||
_fragment
|
||||
|
||||
/**
|
||||
* @param {ShaderModule} vertex
|
||||
* @param {ShaderModule} [fragment]
|
||||
*/
|
||||
constructor(vertex, fragment) {
|
||||
super()
|
||||
|
||||
this._vertex = vertex
|
||||
this._fragment = fragment
|
||||
}
|
||||
|
||||
_createGroupBindings() {
|
||||
const groupBindings = new GroupBindingMap()
|
||||
this.getBindingsForStage(
|
||||
this._vertex.reflect(),
|
||||
GPUShaderStage.VERTEX,
|
||||
groupBindings
|
||||
)
|
||||
|
||||
this.getBindingsForStage(
|
||||
this._fragment.reflect(),
|
||||
GPUShaderStage.FRAGMENT,
|
||||
groupBindings
|
||||
)
|
||||
|
||||
return groupBindings
|
||||
}
|
||||
|
||||
createBindGroupLayoutEntries() {
|
||||
return this._createBindGroupLayoutEntries(
|
||||
this._createGroupBindings()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { WebGPUObjectError } from '../utils/errors.js'
|
||||
import { ResourceType } from '../utils/internal-enums.js'
|
||||
|
||||
/** @import { BindGroupEntry } from '../core/graphics-device.js' */
|
||||
|
||||
|
@ -45,6 +46,10 @@ export class Texture {
|
|||
return this._handle.label
|
||||
}
|
||||
|
||||
get resourceType() {
|
||||
return ResourceType.TextureView
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GPUDevice} device
|
||||
* @param {GPUTexture} texture
|
||||
|
@ -91,13 +96,6 @@ export class Texture {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BindGroupEntry} entry
|
||||
*/
|
||||
asBindingResource(entry) {
|
||||
return 'textureView' in entry ? entry.textureView : this.getView()
|
||||
}
|
||||
|
||||
getView() {
|
||||
return this.createDefaultView()
|
||||
}
|
||||
|
|
140
src/utils.js
140
src/utils.js
|
@ -1,3 +1,16 @@
|
|||
/**
|
||||
* @template T, U
|
||||
* @typedef {
|
||||
{ [P in keyof T]: T[P] } &
|
||||
{ [P in keyof U]?: never}
|
||||
* } Only
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T, U
|
||||
* @typedef {Only<T, U> | Only<U, T>} Either
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {new (...args: any[]) => T} Newable<T>
|
||||
|
@ -35,6 +48,15 @@
|
|||
} TypedArrayConstructor
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {
|
||||
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024,
|
||||
2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576,
|
||||
2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456,
|
||||
536870912, 1073741824, 2147483648]
|
||||
* } PowersOfTwo
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {number} T
|
||||
* @typedef {`${T}` extends `-${string}` | '0' | `${string}.${string}` ? never : T } PositiveInteger
|
||||
|
@ -77,6 +99,17 @@
|
|||
* @typedef {T extends String ? PascalCaseString<T> : T extends ReadonlyArray<string> ? Join<CapitalizeAll<T>, ''> : T} PascalCase
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {readonly any[]} T
|
||||
* @template V
|
||||
* @template {any[]} [Acc=[]]
|
||||
* @typedef {
|
||||
T extends readonly [infer Head, ...infer Tail]
|
||||
? Head extends V ? Acc['length'] : IndexOf<Tail, V, [...Acc, Head]>
|
||||
: never
|
||||
* } IndexOf
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {string} T
|
||||
* @param {T} s
|
||||
|
@ -125,6 +158,25 @@ export const Enum = (...values) => /** @type {Readonly<{ [K in T as PascalCase<K
|
|||
}, {})
|
||||
))
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {T extends `${infer N extends number}` ? N : never } ParseInt
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {string} const T
|
||||
* @template {T[]} const A
|
||||
* @param {A} values
|
||||
* @returns {Readonly<{ [K in keyof A as A[K] extends T ? PascalCase<A[K]> : never]: PowersOfTwo[ParseInt<K>] }>}
|
||||
*/
|
||||
export const FlagEnum = (...values) => /** @type {never} */(Object.freeze(
|
||||
values.reduce((acc, x, i) => {
|
||||
const key = pascal(fromKebab(x)).toString()
|
||||
acc[key] = 1 << i
|
||||
return acc
|
||||
}, {})
|
||||
))
|
||||
|
||||
|
||||
/** @typedef {(...args: any) => void} Listener */
|
||||
export class EventEmitter {
|
||||
|
@ -169,3 +221,91 @@ export class EventEmitter {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {{}} T
|
||||
* @template {keyof T} K
|
||||
* @param {T} obj
|
||||
* @param {...K} keys
|
||||
* @returns {Pick<T, K>}
|
||||
*/
|
||||
export const pick = (obj, ...keys) => /** @type {Pick<T, K>} */(Object.fromEntries(
|
||||
keys
|
||||
.filter(key => key in obj)
|
||||
.map(key => [key, obj[key]])
|
||||
))
|
||||
|
||||
/**
|
||||
* @template {{}} T
|
||||
* @template {PropertyKey & keyof T} K
|
||||
* @param {T} obj
|
||||
* @param {...K} keys
|
||||
* @returns {{ [Key in K]: Key extends keyof T ? T[Key] : never }}
|
||||
*/
|
||||
export const inclusivePick = (obj, ...keys) => /** @type {{[Key in K]: Key extends keyof T ? T[Key] : never}} */(Object.fromEntries(
|
||||
keys.map(key => [key, obj[key]])
|
||||
))
|
||||
|
||||
/**
|
||||
* @template {{}} T
|
||||
* @template {PropertyKey & keyof T} K
|
||||
* @param {T} obj
|
||||
* @param {...K} keys
|
||||
* @returns {Omit<T, K>}
|
||||
*/
|
||||
export const omit = (obj, ...keys) => /** @type {Omit<T, K>} */(Object.fromEntries(
|
||||
Object.entries(obj)
|
||||
.filter(([key]) => !keys.includes(/** @type {K} */(key)))
|
||||
))
|
||||
|
||||
/** @template {number} T */
|
||||
export class Flags {
|
||||
_value
|
||||
|
||||
/**
|
||||
* @param {T} value
|
||||
*/
|
||||
constructor(value) {
|
||||
this._value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if `flag` exists in `bitflags`
|
||||
*
|
||||
* @template {number} T
|
||||
* @param {T} flag
|
||||
* @param {T} bitflags
|
||||
*/
|
||||
static has(flag, bitflags) {
|
||||
return bitflags & flag
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine two flags
|
||||
*
|
||||
* @template {number} T
|
||||
* @param {T} a
|
||||
* @param {T} b
|
||||
*/
|
||||
static add(a, b) {
|
||||
return a | b
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if `flag` exists in these bitflags
|
||||
*
|
||||
* @param {T} flag
|
||||
*/
|
||||
has(flag) {
|
||||
return Flags.has(flag, this._value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add `flag` to these bitflags
|
||||
* @param {T} flag
|
||||
*/
|
||||
add(flag) {
|
||||
return Flags.add(flag, this._value)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
28
src/utils/bindings.js
Normal file
28
src/utils/bindings.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { VariableInfo } from 'wgsl_reflect'
|
||||
|
||||
export class VariableStageInfo {
|
||||
stages
|
||||
variableInfo
|
||||
|
||||
/**
|
||||
* @param {GPUShaderStageFlags} stages
|
||||
* @param {VariableInfo} variableInfo
|
||||
*/
|
||||
constructor(stages, variableInfo) {
|
||||
this.stages = stages
|
||||
this.variableInfo = variableInfo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends Map<number, VariableStageInfo>
|
||||
*/
|
||||
export class BindingMap extends Map { }
|
||||
|
||||
/**
|
||||
* @extends Map<number, BindingMap>
|
||||
*/
|
||||
export class GroupBindingMap extends Map { }
|
||||
|
||||
|
||||
|
|
@ -78,3 +78,27 @@ export class BufferError extends WebGPUObjectError {
|
|||
return new BufferError(`buffer offset/size (${offset}/${size}) exceeds buffer dimensions`)
|
||||
}
|
||||
}
|
||||
|
||||
export class MaterialError extends WebGPUObjectError {
|
||||
/**
|
||||
* @param {string} message
|
||||
* @param {ErrorOptions} [options]
|
||||
*/
|
||||
constructor(message, options) {
|
||||
super(`MaterialError: ${message}`, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Error} cause
|
||||
*/
|
||||
static from(cause) {
|
||||
return new BufferError('could not create material', { cause })
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} shaderType
|
||||
*/
|
||||
static missingShader(shaderType) {
|
||||
return new BufferError(`missing ${shaderType} shader`)
|
||||
}
|
||||
}
|
||||
|
|
16
src/utils/internal-enums.js
Normal file
16
src/utils/internal-enums.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { FlagEnum } from '../utils.js'
|
||||
|
||||
export const ShaderType = FlagEnum(
|
||||
'auto',
|
||||
'vertex',
|
||||
'fragment',
|
||||
'compute'
|
||||
)
|
||||
|
||||
export const ResourceType = Object.freeze({
|
||||
Sampler: 0,
|
||||
TextureView: 1,
|
||||
Buffer: 2,
|
||||
ExternalTexture: 3
|
||||
})
|
||||
|
301
src/utils/wgsl-to-wgpu.js
Normal file
301
src/utils/wgsl-to-wgpu.js
Normal file
|
@ -0,0 +1,301 @@
|
|||
/**
|
||||
* @typedef {'read' | 'write' | 'read_write'} WGSLAccess
|
||||
* @typedef {'f32' | 'i32' | 'u32'} WGSLSampledType
|
||||
*/
|
||||
|
||||
import { BufferBindingType, StorageTextureAccess, TextureFormat } from '../enum.js'
|
||||
|
||||
/**
|
||||
* @typedef {
|
||||
'texture_1d'
|
||||
| 'texture_2d'
|
||||
| 'texture_2d_array'
|
||||
| 'texture_3d'
|
||||
| 'texture_cube'
|
||||
| 'texture_cube_array'
|
||||
* } WGSLSampledTextureType
|
||||
*
|
||||
* @typedef {
|
||||
'texture_multisampled_2d'
|
||||
| 'texture_depth_multisampled_2d'
|
||||
* } WGSLMultisampledTextureType
|
||||
*
|
||||
* @typedef {
|
||||
'texture_storage_1d'
|
||||
| 'texture_storage_2d'
|
||||
| 'texture_storage_2d_array'
|
||||
| 'texture_storage_3d'
|
||||
* } WGSLStorageTextureType
|
||||
*
|
||||
* @typedef {
|
||||
'texture_depth_2d'
|
||||
| 'texture_depth_2d_array'
|
||||
| 'texture_depth_cube'
|
||||
| 'texture_depth_cube_array'
|
||||
* } WGSLDepthTextureType
|
||||
*
|
||||
* @typedef {
|
||||
WGSLSampledTextureType
|
||||
| WGSLMultisampledTextureType
|
||||
| WGSLStorageTextureType
|
||||
| WGSLDepthTextureType
|
||||
* } WGSLTextureType
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {
|
||||
'sampler'
|
||||
| 'sampler_comparison'
|
||||
* } WGSLSamplerType
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} typeName
|
||||
* @returns {[WGSLTextureType, WGSLSampledType]}
|
||||
*/
|
||||
export const parseTextureType = (typeName) => {
|
||||
const chevronIndex = typeName.indexOf('<')
|
||||
const type = typeName.slice(0, chevronIndex)
|
||||
const sampledType = typeName.slice(chevronIndex + 1, -1)
|
||||
return [
|
||||
/** @type {WGSLTextureType} */ (type),
|
||||
/** @type {WGSLSampledType} */ (sampledType)
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WGSLAccess} access
|
||||
* @returns {GPUBufferBindingType}
|
||||
*/
|
||||
export const accessToBufferType = access => {
|
||||
switch (access) {
|
||||
case 'read': return BufferBindingType.ReadOnlyStorage
|
||||
case 'write':
|
||||
case 'read_write':
|
||||
default:
|
||||
return BufferBindingType.Storage
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WGSLAccess} access
|
||||
* @returns {GPUStorageTextureAccess}
|
||||
*/
|
||||
export const accessToStorageTextureAccess = access => {
|
||||
switch (access) {
|
||||
case 'read': return StorageTextureAccess.ReadOnly
|
||||
case 'write': return StorageTextureAccess.WriteOnly
|
||||
case 'read_write': return StorageTextureAccess.ReadWrite
|
||||
}
|
||||
}
|
||||
|
||||
export const FormatToFilterType = Object.freeze({
|
||||
r8unorm: 'float',
|
||||
r8snorm: 'float',
|
||||
r8uint: 'uint',
|
||||
r8sint: 'sint',
|
||||
r16uint: 'uint',
|
||||
r16sint: 'sint',
|
||||
r16float: 'float',
|
||||
rg8unorm: 'float',
|
||||
rg8snorm: 'float',
|
||||
rg8uint: 'uint',
|
||||
rg8sint: 'sint',
|
||||
r32uint: 'uint',
|
||||
r32sint: 'sint',
|
||||
r32float: 'unfilterable-float',
|
||||
rg16uint: 'uint',
|
||||
rg16sint: 'sint',
|
||||
rg16float: 'float',
|
||||
rgba8unorm: 'float',
|
||||
'rgba8unorm-srgb': 'float',
|
||||
rgba8snorm: 'float',
|
||||
rgba8uint: 'uint',
|
||||
rgba8sint: 'sint',
|
||||
bgra8unorm: 'float',
|
||||
'bgra8unorm-srgb': 'float',
|
||||
rgb10a2unorm: 'float',
|
||||
rg11b10ufloat: 'float',
|
||||
rgb9e5ufloat: 'float',
|
||||
rg32uint: 'uint',
|
||||
rg32sint: 'sint',
|
||||
rg32float: 'unfilterable-float',
|
||||
rgba16uint: 'uint',
|
||||
rgba16sint: 'sint',
|
||||
rgba16float: 'float',
|
||||
rgba32uint: 'uint',
|
||||
rgba32sint: 'sint',
|
||||
rgba32float: 'unfilterable-float',
|
||||
|
||||
depth16unorm: 'depth',
|
||||
depth24plus: 'depth',
|
||||
'depth24plus-stencil8': 'depth',
|
||||
depth32float: 'unfilterable-float',
|
||||
'depth32float-stencil8': 'unfilterable-float',
|
||||
stencil8: 'uint'
|
||||
})
|
||||
|
||||
/** @param {string} format */
|
||||
export const formatToFilterType = format => (
|
||||
FormatToFilterType[format] || 'float'
|
||||
)
|
||||
|
||||
export const FormatToStride = Object.freeze({
|
||||
r8unorm: 1,
|
||||
r8snorm: 1,
|
||||
r8uint: 1,
|
||||
r8sint: 1,
|
||||
r16uint: 2,
|
||||
r16sint: 2,
|
||||
r16float: 2,
|
||||
rg8unorm: 2,
|
||||
rg8snorm: 2,
|
||||
rg8uint: 2,
|
||||
rg8sint: 2,
|
||||
r32uint: 4,
|
||||
r32sint: 4,
|
||||
r32float: 4,
|
||||
rg16uint: 4,
|
||||
rg16sint: 4,
|
||||
rg16float: 4,
|
||||
rgba8unorm: 4,
|
||||
'rgba8unorm-srgb': 4,
|
||||
rgba8snorm: 4,
|
||||
rgba8uint: 4,
|
||||
rgba8sint: 4,
|
||||
bgra8unorm: 4,
|
||||
'bgra8unorm-srgb': 4,
|
||||
rgb10a2unorm: 4,
|
||||
rg11b10ufloat: 4,
|
||||
rgb9e5ufloat: 4,
|
||||
rg32uint: 4,
|
||||
rg32sint: 4,
|
||||
rg32float: 4,
|
||||
rgba16uint: 4,
|
||||
rgba16sint: 4,
|
||||
rgba16float: 4,
|
||||
rgba32uint: 4,
|
||||
rgba32sint: 4,
|
||||
rgba32float: 4,
|
||||
|
||||
depth16unorm: 1,
|
||||
depth24plus: 1,
|
||||
'depth24plus-stencil8': 1,
|
||||
depth32float: 1,
|
||||
'depth32float-stencil8': 1,
|
||||
stencil8: 1
|
||||
})
|
||||
|
||||
/** @param {string} typeName */
|
||||
export const typeToStride = typeName => (
|
||||
FormatToStride[typeName] || 1
|
||||
)
|
||||
|
||||
|
||||
/** @param {WGSLTextureType} typeName */
|
||||
export const typeToViewDimension = typeName => {
|
||||
switch (typeName) {
|
||||
case 'texture_1d':
|
||||
case 'texture_storage_1d':
|
||||
return '1d'
|
||||
|
||||
case 'texture_2d':
|
||||
case 'texture_storage_2d':
|
||||
case 'texture_multisampled_2d':
|
||||
case 'texture_depth_2d':
|
||||
case 'texture_depth_multisampled_2d':
|
||||
return '2d'
|
||||
|
||||
case 'texture_2d_array':
|
||||
case 'texture_storage_2d_array':
|
||||
case 'texture_depth_2d_array':
|
||||
return '2d-array'
|
||||
|
||||
case 'texture_3d':
|
||||
case 'texture_storage_3d':
|
||||
return '3d'
|
||||
|
||||
case 'texture_cube':
|
||||
case 'texture_depth_cube':
|
||||
return 'cube'
|
||||
|
||||
case 'texture_cube_array':
|
||||
case 'texture_depth_cube_array':
|
||||
return 'cube-array'
|
||||
|
||||
default:
|
||||
return '2d'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GPUTextureViewDimension} dimension
|
||||
* @returns {GPUTextureDimension}
|
||||
*/
|
||||
export const textureToImageDimension = dimension => {
|
||||
switch (dimension) {
|
||||
case '1d':
|
||||
return '1d'
|
||||
|
||||
case '2d':
|
||||
case '2d-array':
|
||||
case 'cube-array':
|
||||
return '2d'
|
||||
|
||||
case '3d':
|
||||
return '3d'
|
||||
|
||||
default:
|
||||
return '2d'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} format
|
||||
* @returns {GPUTextureFormat}
|
||||
*/
|
||||
export const wgslToWgpuFormat = (format) => {
|
||||
switch (format) {
|
||||
case 'f32':
|
||||
return TextureFormat.Bgra8unorm
|
||||
case 'vec4<f32>':
|
||||
case 'vec4f':
|
||||
return TextureFormat.Bgra8unorm
|
||||
case 'vec4<u32>':
|
||||
case 'vec4u':
|
||||
return TextureFormat.Rgba32uint
|
||||
case 'vec4<i32>':
|
||||
case 'vec4i':
|
||||
return TextureFormat.Rgba32sint
|
||||
case 'vec2<f32>':
|
||||
case 'vec2f':
|
||||
return TextureFormat.Rg32float
|
||||
case 'u32':
|
||||
case 'u':
|
||||
return TextureFormat.R32uint
|
||||
case 'i32':
|
||||
case 'i':
|
||||
return TextureFormat.R32sint
|
||||
case 'f16':
|
||||
return TextureFormat.Rgba16float
|
||||
case 'vec4<f16>':
|
||||
return TextureFormat.Rgba16float
|
||||
case 'vec2<f16>':
|
||||
return TextureFormat.Rg16float
|
||||
case 'u16':
|
||||
return TextureFormat.R16uint
|
||||
case 'i16':
|
||||
return TextureFormat.R16sint
|
||||
case 'vec4unorm':
|
||||
return TextureFormat.Rgba8unorm
|
||||
case 'vec4snorm':
|
||||
return TextureFormat.Rgba8snorm
|
||||
case 'vec4<u8>':
|
||||
return TextureFormat.Rgba8uint
|
||||
case 'vec4<i8>':
|
||||
return TextureFormat.Rgba8sint
|
||||
default:
|
||||
return TextureFormat.Rgba8unorm
|
||||
}
|
||||
}
|
||||
|
Loading…
Add table
Reference in a new issue