Update readme, example project, fix runtime errors

This commit is contained in:
Szymon Nowakowski
2023-08-15 22:18:08 +02:00
committed by Szymon Nowakowski
parent 7f33a0eaf5
commit b42f8d62e4
23 changed files with 441 additions and 42 deletions

View File

@@ -53,18 +53,20 @@ export class _BinaryWriter {
}
ensureUnusedCapacity(desiredUnusedCapacity: number): _BinaryWriter {
return this.ensureCapacity(this._buffer.byteLength + desiredUnusedCapacity);
return this.ensureCapacity(this._length + desiredUnusedCapacity);
}
writeU32(value: number): _BinaryWriter {
this.ensureUnusedCapacity(4);
this._dataView.setUint32(this._length, value, true);
this._length += 4;
return this;
}
writeF32(value: number): _BinaryWriter {
this.ensureUnusedCapacity(4);
this._dataView.setFloat32(this._length, value, true);
this._length += 4;
return this;
}
@@ -116,6 +118,19 @@ export class _BinaryWriter {
return this;
}
padToAlign(alignment: number): _BinaryWriter {
const alignedLength = (this._length + alignment - 1) & ~(alignment - 1);
const padding = alignedLength - this._length;
if (padding === 0) {
return this;
}
this.ensureUnusedCapacity(padding);
this._typedArray.fill(0, this._length, alignedLength);
this._length = alignedLength;
return this;
}
alloc(byteLength: number): DataView {
this.ensureUnusedCapacity(byteLength);
const dataView = new DataView(this._buffer, this._length, byteLength);

View File

@@ -26,7 +26,7 @@ export interface PerspectiveCameraProps {
export class OrthographicCamera {
readonly type!: "OrthographicCamera";
declare readonly type: "OrthographicCamera";
_name: string;
@@ -101,7 +101,7 @@ export class OrthographicCamera {
export class PerspectiveCamera {
readonly type!: "PerspectiveCamera";
declare readonly type: "PerspectiveCamera";
_name: string;

View File

@@ -56,7 +56,7 @@ export type ColorTuple = readonly [r: number, g: number, b: number];
export class Color {
readonly type!: "Color";
declare readonly type: "Color";
r: number;
g: number;

View File

@@ -22,7 +22,7 @@ export interface PointLightProps {
export class DirectionalLight {
readonly type!: "DirectionalLight";
get type(): "DirectionalLight" {};
_name: string;
@@ -81,7 +81,7 @@ export class DirectionalLight {
export class PointLight {
readonly type!: "PointLight";
declare readonly type: "PointLight";
_name: string;

View File

@@ -34,7 +34,7 @@ export interface MaterialProps {
export class Material {
readonly type!: "Material";
declare readonly type: "Material";
_name: string;

View File

@@ -34,7 +34,7 @@ export type Matrix4x4Tuple = readonly [
export class Matrix4x4 {
readonly type!: "Matrix4x4";
declare readonly type: "Matrix4x4";
ix: number;
iy: number;

View File

@@ -23,7 +23,7 @@ export interface NodeProps {
export class Node {
readonly type!: "Node";
declare readonly type: "Node";
_name: string;

View File

@@ -15,7 +15,7 @@ export type QuaternionTuple = readonly [x: number, y: number, z: number, w: numb
export class Quaternion {
readonly type!: "Quaternion";
declare readonly type: "Quaternion";
x: number;
y: number;

View File

@@ -16,7 +16,7 @@ export interface SceneProps {
export class Scene {
readonly type!: "Scene";
declare readonly type: "Scene";
_name: string;

View File

@@ -13,7 +13,7 @@ export type Vector2Tuple = readonly [x: number, y: number];
export class Vector2 {
readonly type!: "Vector2";
declare readonly type: "Vector2";
x: number;
y: number;

View File

@@ -14,7 +14,7 @@ export type Vector3Tuple = readonly [x: number, y: number, z: number];
export class Vector3 {
readonly type!: "Vector3";
declare readonly type: "Vector3";
x: number;
y: number;

View File

@@ -15,7 +15,7 @@ export type Vector4Tuple = readonly [x: number, y: number, z: number, w: number]
export class Vector4 {
readonly type!: "Vector4";
declare readonly type: "Vector4";
x: number;
y: number;

View File

@@ -11,7 +11,7 @@ import { _BinaryWriter as BinaryWriter } from "./_BinaryWriter";
import { _Mapping as Mapping } from "./_Mapping";
import { Camera, Material, Matrix4x4, Node, Scene, Vector3, isDirectionalLight, isPointLight, preOrder } from "./data";
import { IndexBuffer, IndexBufferProps, Texture2D, Texture2DProps, VertexBuffer, VertexBufferProps } from "./resources";
import { ShaderFlagKey, ShaderFlags, createPipeline, shaderFlagsKey } from "./shader";
import { GLOBAL_UNIFORMS_SIZE, MATERIAL_UNIFORMS_SIZE, OBJECT_UNIFORMS_SIZE, ShaderFlagKey, ShaderFlags, _createPipeline, _shaderFlagsKey } from "./shader";
const _matrixOStoWSNormal = new Matrix4x4(
NaN, NaN, NaN, NaN,
@@ -117,6 +117,7 @@ export class Renderer {
width: framebufferTexture.width,
height: framebufferTexture.height,
format: "depth",
usage: GPUTextureUsage.RENDER_ATTACHMENT,
});
this._globalBindGroupLayout = device.createBindGroupLayout({
@@ -266,7 +267,7 @@ export class Renderer {
this._globalBindGroup = device.createBindGroup({
layout: this._globalBindGroupLayout,
entries: [
{ binding: 0, resource: { buffer: this._uniformBuffer } },
{ binding: 0, resource: { buffer: this._uniformBuffer, size: GLOBAL_UNIFORMS_SIZE } },
{ binding: 1, resource: { buffer: this._pointLightBuffer } },
{ binding: 2, resource: { buffer: this._directionalLightBuffer } },
],
@@ -275,7 +276,7 @@ export class Renderer {
this._objectBindGroup = device.createBindGroup({
layout: this._objectBindGroupLayout,
entries: [
{ binding: 0, resource: { buffer: this._uniformBuffer } },
{ binding: 0, resource: { buffer: this._uniformBuffer, size: OBJECT_UNIFORMS_SIZE } },
],
label: "Object",
});
@@ -336,14 +337,14 @@ export class Renderer {
}
_getOrCreatePipeline(flags: ShaderFlags): GPURenderPipeline {
const key = shaderFlagsKey(flags);
const key = _shaderFlagsKey(flags);
let pipeline = this._pipelineCache.get(key);
if (pipeline !== undefined) {
return pipeline;
}
pipeline = createPipeline(this, flags);
pipeline = _createPipeline(this, flags);
this._pipelineCache.set(key, pipeline);
return pipeline;
}
@@ -401,11 +402,12 @@ export class Renderer {
this._uniformWriter.writeF32(material._normalScale);
this._uniformWriter.writeColorF32(material._emissive);
this._uniformWriter.writeF32(material._ior);
this._uniformWriter.padToAlign(256);
const bindGroup = this._device.createBindGroup({
layout: this._materialBindGroupLayout,
entries: [
{ binding: 0, resource: { buffer: this._uniformBuffer } },
{ binding: 0, resource: { buffer: this._uniformBuffer, size: MATERIAL_UNIFORMS_SIZE } },
{ binding: 1, resource: this._sampler },
{ binding: 2, resource: material._baseColorPartialCoverageTexture?._textureView ?? this._textureWhite._textureView },
{ binding: 3, resource: material._occlusionTexture?._textureView ?? this._textureWhite._textureView },
@@ -433,6 +435,7 @@ export class Renderer {
object._updateWorldMatrix();
this._uniformWriter.writeMatrix4x4(object._worldMatrix);
this._uniformWriter.writeMatrix4x4(_matrixOStoWSNormal.setObject(object._worldMatrix).inverseTransposeAffine());
this._uniformWriter.padToAlign(256);
return offset;
});
@@ -491,9 +494,7 @@ export class Renderer {
this._uniformWriter.writeColorF32(scene._ambientLight);
this._uniformWriter.writeU32(pointLightCount);
this._uniformWriter.writeU32(directionalLightCount);
this._uniformWriter.writeU32(0);
this._uniformWriter.writeU32(0);
this._uniformWriter.writeU32(0);
this._uniformWriter.padToAlign(256);
// upload uniforms
@@ -520,11 +521,18 @@ export class Renderer {
pass.setPipeline(renderPipeline);
/* WORKAROUND
*
* As of writing, Chrome doesn't support passing null as the second
* argument. We could (and should) bind the buffers unconditionally
* for increased safety. For now, we only do this when they are not
* null.
*/
pass.setVertexBuffer(0, vertexBuffer._positionBuffer);
pass.setVertexBuffer(1, vertexBuffer._texCoordBuffer);
pass.setVertexBuffer(2, vertexBuffer._lightTexCoordBuffer);
pass.setVertexBuffer(3, vertexBuffer._normalBuffer);
pass.setVertexBuffer(4, vertexBuffer._tangentBuffer);
if (vertexBuffer._texCoordBuffer !== null) pass.setVertexBuffer(1, vertexBuffer._texCoordBuffer);
if (vertexBuffer._lightTexCoordBuffer !== null) pass.setVertexBuffer(2, vertexBuffer._lightTexCoordBuffer);
if (vertexBuffer._normalBuffer !== null) pass.setVertexBuffer(3, vertexBuffer._normalBuffer);
if (vertexBuffer._tangentBuffer !== null) pass.setVertexBuffer(4, vertexBuffer._tangentBuffer);
pass.setIndexBuffer(indexBuffer._buffer, indexBuffer._indexFormat);
pass.setBindGroup(2, this._objectBindGroup, [objectOffset]);

View File

@@ -20,7 +20,7 @@ export interface IndexBufferResizeProps {
export class IndexBuffer {
readonly type!: "IndexBuffer";
declare readonly type: "IndexBuffer";
_renderer: Renderer;
_name: string;

View File

@@ -21,12 +21,15 @@ export interface Texture2DProps {
readonly height: number;
readonly format: Texture2DFormat;
readonly usage?: GPUTextureUsageFlags;
}
export interface Texture2DResizeProps {
readonly width?: number;
readonly height?: number;
readonly format?: Texture2DFormat;
readonly usage?: GPUTextureUsageFlags;
}
export interface Texture2DAdvancedWriteProps {
@@ -39,7 +42,7 @@ export interface Texture2DAdvancedWriteProps {
export class Texture2D {
readonly type!: "Texture2D";
declare readonly type: "Texture2D";
_renderer: Renderer;
_name: string;
@@ -53,6 +56,7 @@ export class Texture2D {
width,
height,
format,
usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
}: Texture2DProps) {
this._renderer = renderer;
@@ -62,7 +66,7 @@ export class Texture2D {
this._renderer = renderer;
this._texture = renderer._device.createTexture({
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
usage,
size: { width, height },
format: gpuFormat,
label: name
@@ -147,13 +151,14 @@ export class Texture2D {
width = this._texture.width,
height = this._texture.height,
format = this._format,
usage = this._texture.usage,
}: Texture2DResizeProps): Texture2D {
this._texture.destroy();
const gpuFormat = gpuTextureFormat(format);
this._texture = this._renderer._device.createTexture({
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
usage,
size: { width, height },
format: gpuFormat,
label: this._name

View File

@@ -51,7 +51,7 @@ export interface VertexBufferWriteTypedArrayProps {
export class VertexBuffer {
readonly type!: "VertexBuffer";
declare readonly type: "VertexBuffer";
_renderer: Renderer;
_name: string;

View File

@@ -6,6 +6,13 @@
import { Renderer } from "./oktaeder";
// 152 bytes padded to 256
export const GLOBAL_UNIFORMS_SIZE = 256;
// 64 bytes padded to 256
export const MATERIAL_UNIFORMS_SIZE = 256;
// 128 bytes padded to 256
export const OBJECT_UNIFORMS_SIZE = 256;
export type ShaderFlagKey = number;
export interface ShaderFlags {
@@ -15,7 +22,7 @@ export interface ShaderFlags {
readonly tangent: boolean;
}
export function shaderFlagsKey({
export function _shaderFlagsKey({
texCoord,
lightTexCoord,
normal,
@@ -29,13 +36,13 @@ export function shaderFlagsKey({
return key;
}
export function createPipeline(renderer: Renderer, {
export function _createPipeline(renderer: Renderer, {
texCoord,
lightTexCoord,
normal,
tangent,
}: ShaderFlags): GPURenderPipeline {
const shaderCode = createShaderCode({ texCoord, lightTexCoord, normal, tangent });
const shaderCode = _createShaderCode({ texCoord, lightTexCoord, normal, tangent });
const shaderModule = renderer._device.createShaderModule({
code: shaderCode,
@@ -130,7 +137,7 @@ export function createPipeline(renderer: Renderer, {
return pipeline;
}
export function createShaderCode({
export function _createShaderCode({
texCoord,
lightTexCoord,
normal,
@@ -147,7 +154,7 @@ struct Vertex {
struct Varyings {
@builtin(position) positionCS: vec4<f32>,
@location(0) positionVS: vec4<f32>,
@location(0) positionVS: vec3<f32>,
${texCoord ? `@location(1) texCoord: vec2<f32>,` : ""}
${lightTexCoord ? `@location(2) lightTexCoord: vec2<f32>,` : ""}
${normal ? `@location(3) normalVS: vec3<f32>,` : ""}
@@ -219,7 +226,7 @@ fn visibilityGGX(dotNL: f32, dotNV: f32, alpha: f32) -> f32 {
let vGGX = dotNL * sqrt(dotNV * dotNV * (1.0 - alphaSquared) + alphaSquared);
let lGGX = dotNV * sqrt(dotNL * dotNL * (1.0 - alphaSquared) + alphaSquared);
let GGX = vGGX + lGGX;
return GGX > 0.0 ? 0.5 / GGX : 0.0;
return select(0.0, 0.5 / GGX, GGX > 0.0);
}
fn distributionGGX(dotNH: f32, alpha: f32) -> f32 {
@@ -270,7 +277,7 @@ fn screenSpaceMatrixTStoVS(positionVS: vec3<f32>, normalVS: vec3<f32>, texCoord:
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);
let scale = select(0.0, inverseSqrt(det), det != 0.0);
return mat3x3(tangentVS * scale, bitangentVS * scale, normalVS);
}
@@ -297,10 +304,11 @@ fn vert(vertex: Vertex) -> Varyings {
` : ""}
${texCoord ? "output.texCoord = vertex.texCoord;" : ""}
${lightTexCoord ? "output.lightTexCoord = vertex.lightTexCoord;" : ""}
return output;
}
@fragment
fn frag(fragment: Varyings) -> @location(0) vec2<f32> {
fn frag(fragment: Varyings) -> @location(0) vec4<f32> {
var baseColor = _Material.baseColor;
var partialCoverage = _Material.partialCoverage;
var occlusion = 1.0;
@@ -357,7 +365,7 @@ fn frag(fragment: Varyings) -> @location(0) vec2<f32> {
var outgoingRadiance = vec3(0.0);
for (var i: u32 = 0; i < _Global.pointLightCount; ++i) {
for (var i: u32 = 0; i < _Global.pointLightCount; i++) {
let light = _PointLights[i];
let lightPositionVS = (_Global.matrixWStoVS * vec4(light.positionWS, 1.0)).xyz;
@@ -373,7 +381,7 @@ fn frag(fragment: Varyings) -> @location(0) vec2<f32> {
);
}
for (var i: u32 = 0; i < _Global.directionalLightCount; ++i) {
for (var i: u32 = 0; i < _Global.directionalLightCount; i++) {
let light = _DirectionalLights[i];
let lightDirectionVS = normalize((_Global.matrixWStoVS * vec4(light.directionWS, 0.0)).xyz);