Pipeline creation, work on shader code
This commit is contained in:
parent
89576c33bd
commit
7fef3c90d8
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;
|
||||
`}
|
||||
|
Loading…
Reference in New Issue
Block a user