Pipeline creation, work on shader code
This commit is contained in:
		
							
								
								
									
										112
									
								
								src/_BinaryWriter.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/_BinaryWriter.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | ||||
| import { Matrix4x4Object, Vector2Object, Vector3Object, Vector4Object } from "./data"; | ||||
|  | ||||
| export class _BinaryWriter { | ||||
|  | ||||
| 	static readonly DEFAULT_CAPACITY = 16; | ||||
|  | ||||
| 	_buffer: ArrayBuffer; | ||||
| 	_dataView: DataView; | ||||
| 	_typedArray: Uint8Array; | ||||
| 	_length: number; | ||||
|  | ||||
| 	get subarray(): Uint8Array { return new Uint8Array(this._buffer, 0, this._length); } | ||||
|  | ||||
| 	constructor(capacity = _BinaryWriter.DEFAULT_CAPACITY) { | ||||
| 		capacity = Math.max(capacity, 1); | ||||
| 		this._buffer = new ArrayBuffer(capacity); | ||||
| 		this._dataView = new DataView(this._buffer); | ||||
| 		this._typedArray = new Uint8Array(this._buffer); | ||||
| 		this._length = 0; | ||||
| 	} | ||||
|  | ||||
| 	clear(): _BinaryWriter { | ||||
| 		this._length = 0; | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	ensureCapacity(desiredCapacity: number): _BinaryWriter { | ||||
| 		if (this._buffer.byteLength >= desiredCapacity) { | ||||
| 			return this; | ||||
| 		} | ||||
|  | ||||
| 		let newCapacity = this._buffer.byteLength * 2; | ||||
| 		while (newCapacity < desiredCapacity) { | ||||
| 			newCapacity *= 2; | ||||
| 		} | ||||
|  | ||||
| 		const newBuffer = new ArrayBuffer(newCapacity); | ||||
| 		const newDataView = new DataView(newBuffer); | ||||
| 		const newTypedArray = new Uint8Array(newBuffer); | ||||
|  | ||||
| 		newTypedArray.set(this.subarray); | ||||
|  | ||||
| 		this._buffer = newBuffer; | ||||
| 		this._dataView = newDataView; | ||||
| 		this._typedArray = newTypedArray; | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	ensureUnusedCapacity(desiredUnusedCapacity: number): _BinaryWriter { | ||||
| 		return this.ensureCapacity(this._buffer.byteLength + desiredUnusedCapacity); | ||||
| 	} | ||||
|  | ||||
| 	writeU32(value: number): _BinaryWriter { | ||||
| 		this.ensureUnusedCapacity(4); | ||||
| 		this._dataView.setUint32(this._length, value, true); | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	writeF32(value: number): _BinaryWriter { | ||||
| 		this.ensureUnusedCapacity(4); | ||||
| 		this._dataView.setFloat32(this._length, value, true); | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	writeVector2(value: Vector2Object): _BinaryWriter { | ||||
| 		this.writeF32(value.x); | ||||
| 		this.writeF32(value.y); | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	writeVector3(value: Vector3Object): _BinaryWriter { | ||||
| 		this.writeF32(value.x); | ||||
| 		this.writeF32(value.y); | ||||
| 		this.writeF32(value.z); | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	writeVector4(value: Vector4Object): _BinaryWriter { | ||||
| 		this.writeF32(value.x); | ||||
| 		this.writeF32(value.y); | ||||
| 		this.writeF32(value.z); | ||||
| 		this.writeF32(value.w); | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	writeMatrix4x4(value: Matrix4x4Object): _BinaryWriter { | ||||
| 		this.writeF32(value.ix); | ||||
| 		this.writeF32(value.iy); | ||||
| 		this.writeF32(value.iz); | ||||
| 		this.writeF32(value.iw); | ||||
| 		this.writeF32(value.jx); | ||||
| 		this.writeF32(value.jy); | ||||
| 		this.writeF32(value.jz); | ||||
| 		this.writeF32(value.jw); | ||||
| 		this.writeF32(value.kx); | ||||
| 		this.writeF32(value.ky); | ||||
| 		this.writeF32(value.kz); | ||||
| 		this.writeF32(value.kw); | ||||
| 		this.writeF32(value.tx); | ||||
| 		this.writeF32(value.ty); | ||||
| 		this.writeF32(value.tz); | ||||
| 		this.writeF32(value.tw); | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	alloc(byteLength: number): DataView { | ||||
| 		this.ensureUnusedCapacity(byteLength); | ||||
| 		const dataView = new DataView(this._buffer, this._length, byteLength); | ||||
| 		this._length += byteLength; | ||||
| 		return dataView; | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										154
									
								
								src/oktaeder.ts
									
									
									
									
									
								
							
							
						
						
									
										154
									
								
								src/oktaeder.ts
									
									
									
									
									
								
							| @@ -4,8 +4,13 @@ | ||||
|  * obtain one at http://mozilla.org/MPL/2.0/. | ||||
|  */ | ||||
|  | ||||
| export * from "./_BinaryWriter"; | ||||
| export * from "./shader"; | ||||
|  | ||||
| import { _BinaryWriter as BinaryWriter } from "./_BinaryWriter"; | ||||
| import { Camera, Scene } from "./data"; | ||||
| import { IndexBuffer, IndexBufferProps, Material, MaterialProps, Texture2D, Texture2DProps, VertexBuffer, VertexBufferProps } from "./resources"; | ||||
| import { ShaderFlagKey, ShaderFlags, createPipeline, shaderFlagsKey } from "./shader"; | ||||
|  | ||||
| export class Renderer { | ||||
|  | ||||
| @@ -23,11 +28,20 @@ export class Renderer { | ||||
|  | ||||
| 	_depthBuffer: Texture2D; | ||||
|  | ||||
| 	_globalBindGroupLayout: GPUBindGroupLayout; | ||||
| 	_materialBindGroupLayout: GPUBindGroupLayout; | ||||
| 	_objectBindGroupLayout: GPUBindGroupLayout; | ||||
| 	_pipelineLayout: GPUPipelineLayout; | ||||
|  | ||||
| 	_pipelineCache: Map<ShaderFlagKey, GPURenderPipeline>; | ||||
|  | ||||
| 	_uniformWriter: BinaryWriter; | ||||
|  | ||||
| 	/** | ||||
| 	 * This constructor is intended primarily for internal use. Consider using | ||||
| 	 * `Renderer.createIndexBuffer` instead. | ||||
| 	 */ | ||||
| 	private constructor ( | ||||
| 	private constructor( | ||||
| 		adapter: GPUAdapter, | ||||
| 		device: GPUDevice, | ||||
| 		context: GPUCanvasContext, | ||||
| @@ -69,9 +83,130 @@ export class Renderer { | ||||
| 			height: framebufferTexture.height, | ||||
| 			format: "depth", | ||||
| 		}); | ||||
|  | ||||
| 		this._globalBindGroupLayout = device.createBindGroupLayout({ | ||||
| 			entries: [ | ||||
| 				{ | ||||
| 					binding: 0, | ||||
| 					visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, | ||||
| 					buffer: { | ||||
| 						hasDynamicOffset: true, | ||||
| 						type: "uniform", | ||||
| 					}, | ||||
| 				}, | ||||
| 				{ | ||||
| 					binding: 1, | ||||
| 					visibility: GPUShaderStage.FRAGMENT, | ||||
| 					buffer: { | ||||
| 						hasDynamicOffset: true, | ||||
| 						type: "read-only-storage", | ||||
| 					}, | ||||
| 				}, | ||||
| 				{ | ||||
| 					binding: 2, | ||||
| 					visibility: GPUShaderStage.FRAGMENT, | ||||
| 					buffer: { | ||||
| 						hasDynamicOffset: true, | ||||
| 						type: "read-only-storage", | ||||
| 					}, | ||||
| 				}, | ||||
| 			], | ||||
| 			label: "Global", | ||||
| 		}); | ||||
| 		this._materialBindGroupLayout = device.createBindGroupLayout({ | ||||
| 			entries: [ | ||||
| 				{ | ||||
| 					binding: 0, | ||||
| 					visibility: GPUShaderStage.FRAGMENT, | ||||
| 					buffer: { | ||||
| 						hasDynamicOffset: true, | ||||
| 						type: "uniform", | ||||
| 					}, | ||||
| 				}, | ||||
| 				{ | ||||
| 					binding: 1, | ||||
| 					visibility: GPUShaderStage.FRAGMENT, | ||||
| 					sampler: { type: "filtering" }, | ||||
| 				}, | ||||
| 				{ | ||||
| 					binding: 2, | ||||
| 					visibility: GPUShaderStage.FRAGMENT, | ||||
| 					texture: { | ||||
| 						sampleType: "float", | ||||
| 						viewDimension: "2d", | ||||
| 					}, | ||||
| 				}, | ||||
| 				{ | ||||
| 					binding: 3, | ||||
| 					visibility: GPUShaderStage.FRAGMENT, | ||||
| 					texture: { | ||||
| 						sampleType: "float", | ||||
| 						viewDimension: "2d", | ||||
| 					}, | ||||
| 				}, | ||||
| 				{ | ||||
| 					binding: 4, | ||||
| 					visibility: GPUShaderStage.FRAGMENT, | ||||
| 					texture: { | ||||
| 						sampleType: "float", | ||||
| 						viewDimension: "2d", | ||||
| 					}, | ||||
| 				}, | ||||
| 				{ | ||||
| 					binding: 5, | ||||
| 					visibility: GPUShaderStage.FRAGMENT, | ||||
| 					texture: { | ||||
| 						sampleType: "float", | ||||
| 						viewDimension: "2d", | ||||
| 					}, | ||||
| 				}, | ||||
| 				{ | ||||
| 					binding: 6, | ||||
| 					visibility: GPUShaderStage.FRAGMENT, | ||||
| 					texture: { | ||||
| 						sampleType: "float", | ||||
| 						viewDimension: "2d", | ||||
| 					}, | ||||
| 				}, | ||||
| 				{ | ||||
| 					binding: 7, | ||||
| 					visibility: GPUShaderStage.FRAGMENT, | ||||
| 					texture: { | ||||
| 						sampleType: "float", | ||||
| 						viewDimension: "2d", | ||||
| 					}, | ||||
| 				}, | ||||
| 			], | ||||
| 			label: "Material", | ||||
| 		}); | ||||
| 		this._objectBindGroupLayout = device.createBindGroupLayout({ | ||||
| 			entries: [ | ||||
| 				{ | ||||
| 					binding: 0, | ||||
| 					visibility: GPUShaderStage.VERTEX, | ||||
| 					buffer: { | ||||
| 						hasDynamicOffset: true, | ||||
| 						type: "uniform", | ||||
| 					}, | ||||
| 				}, | ||||
| 			], | ||||
| 			label: "Object", | ||||
| 		}); | ||||
|  | ||||
| 		this._pipelineLayout = device.createPipelineLayout({ | ||||
| 			bindGroupLayouts: [ | ||||
| 				this._globalBindGroupLayout, | ||||
| 				this._materialBindGroupLayout, | ||||
| 				this._objectBindGroupLayout, | ||||
| 			], | ||||
| 		}); | ||||
|  | ||||
| 		this._pipelineCache = new Map(); | ||||
|  | ||||
| 		this._uniformWriter = new BinaryWriter(); | ||||
| 	} | ||||
|  | ||||
| 	static async init(canvas: HTMLCanvasElement) { | ||||
| 	static async init(canvas: HTMLCanvasElement): Promise<Renderer> { | ||||
| 		if (!navigator.gpu) { | ||||
| 			throw new Error("WebGPU is not supported"); | ||||
| 		} | ||||
| @@ -92,6 +227,8 @@ export class Renderer { | ||||
|  | ||||
| 		const format = navigator.gpu.getPreferredCanvasFormat(); | ||||
| 		context.configure({ device, format }); | ||||
|  | ||||
| 		return new Renderer(adapter, device, context, format); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| @@ -124,6 +261,19 @@ export class Renderer { | ||||
| 		return new VertexBuffer(this, props); | ||||
| 	} | ||||
|  | ||||
| 	_getOrCreatePipeline(flags: ShaderFlags): GPURenderPipeline { | ||||
| 		const key = shaderFlagsKey(flags); | ||||
|  | ||||
| 		let pipeline = this._pipelineCache.get(key); | ||||
| 		if (pipeline !== undefined) { | ||||
| 			return pipeline; | ||||
| 		} | ||||
|  | ||||
| 		pipeline = createPipeline(this, flags); | ||||
| 		this._pipelineCache.set(key, pipeline); | ||||
| 		return pipeline; | ||||
| 	} | ||||
|  | ||||
| 	render(scene: Scene, camera: Camera): Renderer { | ||||
| 		const { width, height } = this._context.getCurrentTexture(); | ||||
| 		if (this._depthBuffer.width !== width || this._depthBuffer.height !== height) { | ||||
|   | ||||
							
								
								
									
										164
									
								
								src/shader.ts
									
									
									
									
									
								
							
							
						
						
									
										164
									
								
								src/shader.ts
									
									
									
									
									
								
							| @@ -1,8 +1,127 @@ | ||||
| import { Renderer } from "./oktaeder"; | ||||
|  | ||||
| export type ShaderFlagKey = number; | ||||
|  | ||||
| export interface ShaderFlags { | ||||
| 	texCoord: boolean; | ||||
| 	lightTexCoord: boolean; | ||||
| 	normal: boolean; | ||||
| 	tangent: boolean; | ||||
| 	readonly texCoord: boolean; | ||||
| 	readonly lightTexCoord: boolean; | ||||
| 	readonly normal: boolean; | ||||
| 	readonly tangent: boolean; | ||||
| } | ||||
|  | ||||
| export function shaderFlagsKey({ | ||||
| 	texCoord, | ||||
| 	lightTexCoord, | ||||
| 	normal, | ||||
| 	tangent, | ||||
| }: ShaderFlags): ShaderFlagKey { | ||||
| 	let key = 0; | ||||
| 	key |= Number(texCoord) << 0; | ||||
| 	key |= Number(lightTexCoord) << 1; | ||||
| 	key |= Number(normal) << 2; | ||||
| 	key |= Number(tangent) << 3; | ||||
| 	return key; | ||||
| } | ||||
|  | ||||
| export function createPipeline(renderer: Renderer, { | ||||
| 	texCoord, | ||||
| 	lightTexCoord, | ||||
| 	normal, | ||||
| 	tangent, | ||||
| }: ShaderFlags): GPURenderPipeline { | ||||
| 	const shaderCode = createShaderCode({ texCoord, lightTexCoord, normal, tangent }); | ||||
|  | ||||
| 	const shaderModule = renderer._device.createShaderModule({ | ||||
| 		code: shaderCode, | ||||
| 		hints: { | ||||
| 			"vert": { layout: renderer._pipelineLayout }, | ||||
| 			"frag": { layout: renderer._pipelineLayout }, | ||||
| 		}, | ||||
| 	}); | ||||
|  | ||||
| 	let vertexLocation = 0; | ||||
|  | ||||
| 	const pipeline = renderer._device.createRenderPipeline({ | ||||
| 		layout: renderer._pipelineLayout, | ||||
| 		vertex: { | ||||
| 			entryPoint: "vert", | ||||
| 			module: shaderModule, | ||||
| 			buffers: [ | ||||
| 				{ | ||||
| 					arrayStride: 12, | ||||
| 					attributes: [{ | ||||
| 						shaderLocation: vertexLocation++, | ||||
| 						format: "float32x3", | ||||
| 						offset: 0, | ||||
| 					}], | ||||
| 				}, | ||||
| 				...(texCoord ? [{ | ||||
| 					arrayStride: 8, | ||||
| 					attributes: [{ | ||||
| 						shaderLocation: vertexLocation++, | ||||
| 						format: "float32x2", | ||||
| 						offset: 0, | ||||
| 					}], | ||||
| 				} satisfies GPUVertexBufferLayout] : []), | ||||
| 				...(lightTexCoord ? [{ | ||||
| 					arrayStride: 8, | ||||
| 					attributes: [{ | ||||
| 						shaderLocation: vertexLocation++, | ||||
| 						format: "float32x2", | ||||
| 						offset: 0, | ||||
| 					}], | ||||
| 				} satisfies GPUVertexBufferLayout] : []), | ||||
| 				...(normal ? [{ | ||||
| 					arrayStride: 12, | ||||
| 					attributes: [{ | ||||
| 						shaderLocation: vertexLocation++, | ||||
| 						format: "float32x3", | ||||
| 						offset: 0, | ||||
| 					}], | ||||
| 				} satisfies GPUVertexBufferLayout] : []), | ||||
| 				...(tangent ? [{ | ||||
| 					arrayStride: 16, | ||||
| 					attributes: [{ | ||||
| 						shaderLocation: vertexLocation++, | ||||
| 						format: "float32x4", | ||||
| 						offset: 0, | ||||
| 					}], | ||||
| 				} satisfies GPUVertexBufferLayout] : []), | ||||
| 			], | ||||
| 		}, | ||||
| 		fragment: { | ||||
| 			entryPoint: "frag", | ||||
| 			module: shaderModule, | ||||
| 			targets: [{ | ||||
| 				format: renderer._format, | ||||
| 				blend: { | ||||
| 					color: { | ||||
| 						operation: "add", | ||||
| 						srcFactor: "one", | ||||
| 						dstFactor: "one-minus-src-alpha", | ||||
| 					}, | ||||
| 					alpha: { | ||||
| 						operation: "add", | ||||
| 						srcFactor: "one", | ||||
| 						dstFactor: "one-minus-src-alpha", | ||||
| 					}, | ||||
| 				}, | ||||
| 				writeMask: GPUColorWrite.ALL, | ||||
| 			}], | ||||
| 		}, | ||||
| 		depthStencil: { | ||||
| 			depthCompare: "greater", | ||||
| 			depthWriteEnabled: true, | ||||
| 			format: "depth32float", | ||||
| 		}, | ||||
| 		primitive: { | ||||
| 			cullMode: "back", | ||||
| 			frontFace: "ccw", | ||||
| 			topology: "triangle-list", | ||||
| 		}, | ||||
| 	}); | ||||
|  | ||||
| 	return pipeline; | ||||
| } | ||||
|  | ||||
| export function createShaderCode({ | ||||
| @@ -84,6 +203,24 @@ struct ObjectUniforms { | ||||
| @group(1) @binding(6) var _EmissiveTexture: texture_2d<f32>; | ||||
| @group(1) @binding(7) var _TransmissionCollimationTexture: texture_2d<f32>; | ||||
|  | ||||
| fn screenSpaceMatrixTStoVS(positionVS: vec3<f32>, normalVS: vec3<f32>, texCoord: vec2<f32>) -> mat3x3<f32> { | ||||
| 	let q0 = dpdx(positionVS); | ||||
| 	let q1 = dpdy(positionVS); | ||||
| 	let uv0 = dpdx(texCoord); | ||||
| 	let uv1 = dpdy(texCoord); | ||||
|  | ||||
| 	let q1perp = cross(q1, normalVS); | ||||
| 	let q0perp = cross(normalVS, q0); | ||||
|  | ||||
| 	let tangentVS = q1perp * uv0.x + q0perp * uv1.x; | ||||
| 	let bitangentVS = q1perp * uv0.y + q0perp * uv1.y; | ||||
|  | ||||
| 	let det = max(dot(tangentVS, tangentVS), dot(bitangentVS, bitangentVS)); | ||||
| 	let scale = (det == 0.0) ? 0.0 : inserseSqrt(det); | ||||
|  | ||||
| 	return mat3x3(tangentVS * scale, bitangentVS * scale, normalVS); | ||||
| } | ||||
|  | ||||
| @vertex | ||||
| fn vert(vertex: Vertex) -> Varyings { | ||||
| 	var output: Varyings; | ||||
| @@ -122,7 +259,7 @@ fn frag(fragment: Varyings) -> @location(0) vec2<f32> { | ||||
| 		let baseColorPartialCoverageTexel = texture(_BaseColorPartialCoverageTexture, _Sampler, fragment.texCoord); | ||||
| 		baseColor *= baseColorPartialCoverageTexel.rgb; | ||||
| 		partialCoverage *= baseColorPartialCoverageTexel.a; | ||||
| 		let roughnessMetallicTexel = texture(_RoughnessMetallic, _Sampler, fragment.texCoord); | ||||
| 		let roughnessMetallicTexel = texture(_RoughnessMetallicTexture, _Sampler, fragment.texCoord); | ||||
| 		roughness *= roughnessMetallicTexel.g; | ||||
| 		metallic *= roughnessMetallicTexel.b; | ||||
| 		let emissiveTexel = texture(_EmissiveTexture, _Sampler, fragment.texCoord); | ||||
| @@ -140,14 +277,19 @@ fn frag(fragment: Varyings) -> @location(0) vec2<f32> { | ||||
| 		let dPositionVSdx = dpdx(positionVS); | ||||
| 		let dPositionVSdy = dpdy(positionVS); | ||||
| 		let geometricNormalVS = normalize(cross(dPositionVSdx, dPositionVSdy)); | ||||
| 		let actualNormalVS = geometricNormalVS; | ||||
| 	`} | ||||
| 	${texCoord ? ` | ||||
| 	` : ` | ||||
| 		let actualNormalVS = geometricNormalVS; | ||||
| 	`} | ||||
| 	${tangent ? ` | ||||
| 		let tangentVS = | ||||
| 		${tangent ? ` | ||||
| 			let tangentVS = normalize(fragment.tangentVS); | ||||
| 			let bitangentVS = normalize(fragment.bitangentVS); | ||||
| 			let matrixTStoVS = mat3x3(tangentVS, bitangentVS, geometricNormalVS); | ||||
| 		` : ` | ||||
| 			let matrixTStoVS = screenSpaceMatrixTStoVS(positionVS, geometricNormalVS, fragment.texCoord); | ||||
| 		`} | ||||
| 		let normalTextureTexel = texture(_NormalTexture, _Sampler, fragment.texCoord); | ||||
| 		var normalTS = normalTextureTexel.xyz * 2.0 - 1.0; | ||||
| 		normalTS.xy *= _Material.normalScale; | ||||
| 		let actualNormalVS = normalize(matrixTStoVS * geometricNormalVS); | ||||
| 	` : ` | ||||
| 		let actualNormalVS = geometricNormalVS; | ||||
| 	`} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Szymon Nowakowski
					Szymon Nowakowski