Skip to content

Commit

Permalink
refactor project singelton
Browse files Browse the repository at this point in the history
  • Loading branch information
YoanWithY committed Sep 14, 2024
1 parent 46f6256 commit 814e77d
Show file tree
Hide file tree
Showing 24 changed files with 294 additions and 202 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host",
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
Expand Down
2 changes: 1 addition & 1 deletion src/ts/GPUX.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ if (!gpu) {
export const gpuAdapter: GPUAdapter = await gpu.requestAdapter({ powerPreference: undefined }) as GPUAdapter;
if (!gpuAdapter) {
alert("Could not create GPU adapter.");
throw new Error("Could not get GPU adapter.");
throw new Error("Could not create GPU adapter.");
}

export const gpuDevice = await gpuAdapter.requestDevice({
Expand Down
2 changes: 1 addition & 1 deletion src/ts/Material/AbstractPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const fragmentEntryPoint = "fragment_main";

export abstract class AbstractPipeline {
abstract gpuPipeline: GPURenderPipeline;
abstract readonly isDisplayOutputPipeline: boolean;
readonly label: string;

abstract readonly shaderModule: GPUShaderModule;
Expand Down Expand Up @@ -52,7 +53,6 @@ export abstract class AbstractPipeline {
}

abstract createPipelineLayout(): GPUPipelineLayout | "auto";
abstract readonly isDisplayOutputPipeline: boolean;

protected createVertexState(): GPUVertexState {
return {
Expand Down
8 changes: 1 addition & 7 deletions src/ts/app.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { gpuInit as initGPU } from "./GPUX";
import { initUI as initUIClasses } from "./ui/ui";
import { Project } from "./project/Project";
import HeightFieldR3 from "./dynamicObject/HeightFieldR3";
import { GridPipeline } from "./debug/GridPipeline";

initGPU();
initUIClasses();
const p = new Project();
p.open();

p.scene.heightFieldObjects.add(new HeightFieldR3());
p.scene.gridPipeline = new GridPipeline(p.renderer.bindGroup0Layout);
new Project();
9 changes: 5 additions & 4 deletions src/ts/debug/GridPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import staticShaderCode from "../../wgsl/debug/grid.wgsl?raw"
import Renderer from "../rendering/Renderer";
import { resolveIncludes } from "../rendering/Shader";
import { gpuDevice } from "../GPUX";
import { Project } from "../project/Project";

export class GridPipeline extends AbstractPipeline {
gpuPipeline: GPURenderPipeline;
Expand All @@ -22,20 +23,20 @@ export class GridPipeline extends AbstractPipeline {
depthWriteEnabled: boolean;
bindGroup0Layout: GPUBindGroupLayout;

constructor(bindGroup0Layout: GPUBindGroupLayout) {
constructor(project: Project) {
super("Grid Pipeline");
this.bindGroup0Layout = bindGroup0Layout;
this.bindGroup0Layout = project.renderer.bindGroup0Layout;
this.shaderCode = staticShaderCode;
this.preProzessedShaderCoder = resolveIncludes(this.shaderCode);
this.vertexConstants = {};
this.vertexBufferLayout = [];
this.fragmentConstants = Renderer.getDisplayFragmentOutputConstantsCopy();
this.fragmentConstants = project.getDisplayFragmentOutputConstantsCopy();
this.topology = "triangle-strip";
this.cullMode = "none";
this.depthCompare = "less";
this.depthWriteEnabled = false;
this.depthStencilFormat = Renderer.getDepthStencilFormat();
this.fragmentTargets = Renderer.getFragmentTargets();
this.fragmentTargets = project.getFragmentTargets();
this.fragmentTargets[0].blend = {
color: {
srcFactor: "src-alpha",
Expand Down
9 changes: 5 additions & 4 deletions src/ts/dynamicObject/HeightFieldPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { fragmentEntryPoint, AbstractPipeline, vertexEntryPoint } from "../Mater
import staticShaderCode from "../../wgsl/heightField.wgsl?raw";
import { resolveIncludes } from "../rendering/Shader";
import Renderer from "../rendering/Renderer";
import { Project } from "../project/Project";

export class HeightFieldPipeline extends AbstractPipeline {
gpuPipeline: GPURenderPipeline;
Expand All @@ -22,20 +23,20 @@ export class HeightFieldPipeline extends AbstractPipeline {
depthWriteEnabled: boolean;
bindGroup0Layout: GPUBindGroupLayout;

constructor(label: string, bindGroup0Layout: GPUBindGroupLayout) {
constructor(project: Project, label: string) {
super(label);
this.bindGroup0Layout = bindGroup0Layout;
this.bindGroup0Layout = project.renderer.bindGroup0Layout;
this.shaderCode = staticShaderCode;
this.preProzessedShaderCoder = resolveIncludes(this.shaderCode);
this.vertexConstants = {};
this.vertexBufferLayout = [];
this.fragmentConstants = Renderer.getDisplayFragmentOutputConstantsCopy();
this.fragmentConstants = project.getDisplayFragmentOutputConstantsCopy();
this.topology = "triangle-strip";
this.cullMode = "none";
this.depthCompare = "less";
this.depthWriteEnabled = true;
this.depthStencilFormat = Renderer.getDepthStencilFormat();
this.fragmentTargets = Renderer.getFragmentTargets();
this.fragmentTargets = project.getFragmentTargets();

this.shaderModule = gpuDevice.createShaderModule(
{
Expand Down
6 changes: 3 additions & 3 deletions src/ts/dynamicObject/HeightFieldR3.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { HeightFieldPipeline } from "./HeightFieldPipeline";
import { openProject } from "../project/Project";
import { Project } from "../project/Project";
import R3Object from "../project/R3Object";

export default class HeightFieldR3 extends R3Object {
pipeline: HeightFieldPipeline;
xVerts: number;
yVerts: number;
constructor(xVerts: number = 100, yVerts: number = 100) {
constructor(project: Project, xVerts: number = 1000, yVerts: number = 1000) {
super();
this.xVerts = xVerts;
this.yVerts = yVerts;
this.pipeline = new HeightFieldPipeline("Height Field Material", openProject.renderer.bindGroup0Layout);
this.pipeline = new HeightFieldPipeline(project, "Height Field Material");
}

getVerts(): number {
Expand Down
2 changes: 2 additions & 0 deletions src/ts/project/Config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export type SwapChainBitDepth = "8bpc" | "16bpc";
export type RenderMode = "forward" | "deferred";
export type MSAAOptions = 1 | 4;

export class ProjectConfig {
output = new OutputConfig();
Expand All @@ -22,6 +23,7 @@ export abstract class OutputRenderConfig {

export class OutputForwardRenderConfig extends OutputRenderConfig {
mode: "forward" = "forward";
msaa: MSAAOptions = 4;
}

export class OutputDeferredRenderConfig extends OutputRenderConfig {
Expand Down
44 changes: 28 additions & 16 deletions src/ts/project/Project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import Renderer from "../rendering/Renderer";
import Scene from "./Scene";
import { WrappingPane } from "../ui/Wrapping/WrappingPane";
import { ViewportPane } from "../ui/panes/ViewportPane";

export let openProject: Project;
import { gpu } from "../GPUX";
import { GridPipeline } from "../debug/GridPipeline";
import HeightFieldR3 from "../dynamicObject/HeightFieldR3";
export class Project {

config: ProjectConfig;
Expand All @@ -16,27 +17,38 @@ export class Project {

constructor() {
this.config = new ProjectConfig();
this.state = new ProjectState(this.config);
}

open() {
if (openProject)
openProject.close();
openProject = this;
this.renderer = new Renderer();
this.state = new ProjectState(this, this.config);
this.renderer = new Renderer(this);
this.scene = new Scene();
this.uiRoot = WrappingPane.createWrappingPane();
this.uiRoot = WrappingPane.createWrappingPane(this);
document.body.appendChild(this.uiRoot);
}

close() {
const wrapper = document.getElementById("wrapper")
if (wrapper)
document.body.removeChild(wrapper);
this.scene.heightFieldObjects.add(new HeightFieldR3(this));
this.scene.gridPipeline = new GridPipeline(this);
}

fullRerender() {
for (const vp of ViewportPane.viewportPanes)
this.renderer.requestAnimationFrameWith(vp);
}

getDisplayFragmentOutputConstantsCopy(): Record<string, number> {
return {
targetColorSpace: this.config.output.display.swapChainColorSpace == "srgb" ? 0 : 1,
componentTranfere: this.config.output.render.mode === "deferred" ? 0 : 1,
};
}

bitDepthToSwapChainFormat(): GPUTextureFormat {
if (this.config.output.display.swapChainBitDepth === "8bpc")
return gpu.getPreferredCanvasFormat();
return "rgba16float";
}

getFragmentTargets(): GPUColorTargetState[] {
return [
{
format: this.bitDepthToSwapChainFormat()
}];
}
}
91 changes: 54 additions & 37 deletions src/ts/project/State.ts
Original file line number Diff line number Diff line change
@@ -1,82 +1,99 @@
import { OutputConfig, OutputDeferredRenderConfig, OutputDisplayConfig, OutputForwardRenderConfig, OutputRenderConfig, ProjectConfig, RenderMode, SwapChainBitDepth } from "./Config";
import { openProject } from "./Project";
import { MSAAOptions as MSAAOption, OutputConfig, OutputDeferredRenderConfig, OutputDisplayConfig, OutputForwardRenderConfig, OutputRenderConfig, ProjectConfig, RenderMode, SwapChainBitDepth } from "./Config";
import { Project } from "./Project";
import StateVariable from "./StateVariable";

export class ProjectState {
project: Project;
output: OutputState;

constructor(config: ProjectConfig) {
this.output = new OutputState(config.output);
constructor(project: Project, config: ProjectConfig) {
this.project = project;
this.output = new OutputState(project, config.output);
}
}

export class OutputState {
project: Project;
render: OutputRenderState;
display: OutputDisplayState;
render: OutputRenderConfig;

constructor(output: OutputConfig) {
this.display = new OutputDisplayState(output.display);
this.render = OutputRenderState.create(output.render);
constructor(project: Project, output: OutputConfig) {
this.project = project;
this.display = new OutputDisplayState(project, output.display);
this.render = OutputRenderState.create(project, output.render);
}
}

export class OutputDisplayState {
project: Project
swapChainColorSpace: StateVariable<PredefinedColorSpace>;
swapChainBitDepth: StateVariable<SwapChainBitDepth>;
swapChainToneMappingMode: StateVariable<GPUCanvasToneMappingMode>;

constructor(display: OutputDisplayConfig) {
this.swapChainColorSpace = new StateVariable(display.swapChainColorSpace);
constructor(project: Project, display: OutputDisplayConfig) {
this.project = project;
this.swapChainColorSpace = new StateVariable(project, display.swapChainColorSpace);
this.swapChainColorSpace.addChangeListener((value: PredefinedColorSpace) => {
openProject.config.output.display.swapChainColorSpace = value;
openProject.renderer.needsPipleineRebuild = true;
openProject.renderer.needsContextReconfiguration = true;
this.project.config.output.display.swapChainColorSpace = value;
this.project.renderer.needsPipleineRebuild = true;
this.project.renderer.needsContextReconfiguration = true;
}, "deferred");
this.swapChainColorSpace.addChangeListener(() => openProject.fullRerender(), "immediate");
this.swapChainColorSpace.addChangeListener(() => this.project.fullRerender(), "immediate");

this.swapChainBitDepth = new StateVariable(display.swapChainBitDepth);
this.swapChainBitDepth = new StateVariable(project, display.swapChainBitDepth);
this.swapChainBitDepth.addChangeListener((value: SwapChainBitDepth) => {
openProject.config.output.display.swapChainBitDepth = value;
openProject.renderer.needsContextReconfiguration = true;
openProject.renderer.needsPipleineRebuild = true;
this.project.config.output.display.swapChainBitDepth = value;
this.project.renderer.needsContextReconfiguration = true;
this.project.renderer.needsPipleineRebuild = true;
}, "deferred");
this.swapChainBitDepth.addChangeListener(() => openProject.fullRerender(), "immediate");
this.swapChainBitDepth.addChangeListener(() => this.project.fullRerender(), "immediate");


this.swapChainToneMappingMode = new StateVariable(display.swapChainToneMappingMode);
this.swapChainToneMappingMode = new StateVariable(project, display.swapChainToneMappingMode);
this.swapChainToneMappingMode.addChangeListener((value: GPUCanvasToneMappingMode) => {
openProject.renderer.needsContextReconfiguration = true;
openProject.config.output.display.swapChainToneMappingMode = value;
this.project.renderer.needsContextReconfiguration = true;
this.project.config.output.display.swapChainToneMappingMode = value;
}, "deferred");
this.swapChainToneMappingMode.addChangeListener(() => openProject.fullRerender(), "immediate");
this.swapChainToneMappingMode.addChangeListener(() => this.project.fullRerender(), "immediate");
}
}

export abstract class OutputRenderState {
abstract mode: RenderMode;
static create(renderConfig: OutputRenderConfig) {
project: Project;
abstract mode: StateVariable<RenderMode>;
constructor(project: Project) {
this.project = project;
}
static create(project: Project, renderConfig: OutputRenderConfig) {
if (renderConfig.mode === "forward") {
return new OutputForwardRenderState(renderConfig as OutputForwardRenderConfig);
return new OutputForwardRenderState(project, renderConfig as OutputForwardRenderConfig);
} else {
return new OutputDeferredRenderState(renderConfig as OutputDeferredRenderConfig);
return new OutputDeferredRenderState(project, renderConfig as OutputDeferredRenderConfig);
}
}
}

export class OutputForwardRenderState extends OutputRenderState {
mode: RenderMode;
mode: StateVariable<RenderMode>;
msaa: StateVariable<MSAAOption>;

constructor(project: Project, forwardRenderConfig: OutputForwardRenderConfig) {
super(project);
this.mode = new StateVariable<RenderMode>(project, forwardRenderConfig.mode);

constructor(forwardRenderConfig: OutputForwardRenderConfig) {
super();
this.mode = forwardRenderConfig.mode;
this.msaa = new StateVariable<MSAAOption>(project, forwardRenderConfig.msaa);
this.msaa.addChangeListener((value: MSAAOption) => {
(this.project.config.output.render as OutputForwardRenderConfig).msaa = value;
this.project.renderer.needsPipleineRebuild = true;
}, "deferred");
this.msaa.addChangeListener(() => this.project.fullRerender(), "immediate");
}
}

export class OutputDeferredRenderState extends OutputRenderState {
mode: RenderMode;
constructor(deferredRenderConfig: OutputDeferredRenderConfig) {
super();
this.mode = deferredRenderConfig.mode;
mode: StateVariable<RenderMode>;
constructor(project: Project, deferredRenderConfig: OutputDeferredRenderConfig) {
super(project);
this.mode = new StateVariable<RenderMode>(project, deferredRenderConfig.mode);
}
}

}
8 changes: 5 additions & 3 deletions src/ts/project/StateVariable.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { openProject } from "./Project";
import { Project } from "./Project";

export type StateVariableChangeCallback<T> = (value: T) => void;
export type StateChangeExecutionMode = "immediate" | "deferred";
Expand All @@ -10,7 +10,9 @@ export default class StateVariable<T> {
private _value: T;
private _immediateObserverFunctions: Set<StateVariableChangeCallback<T>> = new Set();
private _deferredObserverFunctions: Set<StateVariableChangeCallback<T>> = new Set();
constructor(value: T) {
project: Project;
constructor(project: Project, value: T) {
this.project = project;
this._value = value;
}

Expand Down Expand Up @@ -58,6 +60,6 @@ export default class StateVariable<T> {
for (const f of this._immediateObserverFunctions)
f(this._value);
for (const f of this._deferredObserverFunctions)
openProject.renderer.preRenderFunctions.add({ val: this.value, f });
this.project.renderer.preRenderFunctions.add({ val: this.value, f });
}
}
Loading

0 comments on commit 814e77d

Please sign in to comment.