Skip to content

Commit

Permalink
Introducing loop support for NME (#15599)
Browse files Browse the repository at this point in the history
* Initial loop components

* Loop done
  • Loading branch information
deltakosh authored Sep 19, 2024
1 parent e2500f3 commit c7e709f
Show file tree
Hide file tree
Showing 15 changed files with 531 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { NodeMaterial } from "../../nodeMaterial";
import type { Scene } from "../../../../scene";
import { NodeMaterialConnectionPointCustomObject } from "../../nodeMaterialConnectionPointCustomObject";
import { EngineStore } from "../../../../Engines/engineStore";
import { ShaderLanguage } from "core/Materials/shaderLanguage";
/**
* Block used to provide an image for a TextureBlock
*/
Expand Down Expand Up @@ -68,6 +69,8 @@ export class ImageSourceBlock extends NodeMaterialBlock {
NodeMaterialBlockTargets.VertexAndFragment,
new NodeMaterialConnectionPointCustomObject("source", this, NodeMaterialConnectionPointDirection.Output, ImageSourceBlock, "ImageSourceBlock")
);

this.registerOutput("dimensions", NodeMaterialBlockConnectionPointTypes.Vector2);
}

public override bind(effect: Effect) {
Expand Down Expand Up @@ -101,6 +104,13 @@ export class ImageSourceBlock extends NodeMaterialBlock {
return this._outputs[0];
}

/**
* Gets the dimension component
*/
public get dimensions(): NodeMaterialConnectionPoint {
return this._outputs[1];
}

protected override _buildBlock(state: NodeMaterialBuildState) {
super._buildBlock(state);

Expand All @@ -113,6 +123,17 @@ export class ImageSourceBlock extends NodeMaterialBlock {
state.sharedData.bindableBlocks.push(this);
}

if (this.dimensions.isConnected) {
let affect: string = "";
if (state.shaderLanguage === ShaderLanguage.WGSL) {
affect = `vec2f(textureDimensions(${this._samplerName}, 0).xy)`;
} else {
affect = `vec2(textureSize(${this._samplerName}, 0).xy)`;
}

state.compilationString += `${state._declareOutput(this.dimensions)} = ${affect};\n`;
}

state._emit2DSampler(this._samplerName);

return this;
Expand Down
65 changes: 37 additions & 28 deletions packages/dev/core/src/Materials/Node/Blocks/Dual/textureBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,36 @@ export class TextureBlock extends NodeMaterialBlock {
return this._outputs[6];
}

public override get target() {
private _isTiedToFragment(input: NodeMaterialConnectionPoint) {
if (input.target === NodeMaterialBlockTargets.Fragment) {
return true;
}

if (input.target === NodeMaterialBlockTargets.Vertex) {
return false;
}

if (input.target === NodeMaterialBlockTargets.Neutral || input.target === NodeMaterialBlockTargets.VertexAndFragment) {
const parentBlock = input.ownerBlock;

if (parentBlock.target === NodeMaterialBlockTargets.Fragment) {
return true;
}

for (const input of parentBlock.inputs) {
if (!input.isConnected) {
continue;
}
if (this._isTiedToFragment(input.connectedPoint!)) {
return true;
}
}
}

return false;
}

private _getEffectiveTarget() {
if (this._fragmentOnly) {
return NodeMaterialBlockTargets.Fragment;
}
Expand All @@ -286,35 +315,15 @@ export class TextureBlock extends NodeMaterialBlock {
return NodeMaterialBlockTargets.VertexAndFragment;
}

let parent = this.uv.connectedPoint;

while (parent) {
if (parent.target === NodeMaterialBlockTargets.Fragment) {
return NodeMaterialBlockTargets.Fragment;
}

if (parent.target === NodeMaterialBlockTargets.Vertex) {
return NodeMaterialBlockTargets.VertexAndFragment;
}

if (parent.target === NodeMaterialBlockTargets.Neutral || parent.target === NodeMaterialBlockTargets.VertexAndFragment) {
const parentBlock = parent.ownerBlock;

if (parentBlock.target === NodeMaterialBlockTargets.Fragment) {
return NodeMaterialBlockTargets.Fragment;
}

parent = null;
for (const input of parentBlock.inputs) {
if (input.connectedPoint) {
parent = input.connectedPoint;
break;
}
}
}
if (this._isTiedToFragment(this.uv.connectedPoint!)) {
return NodeMaterialBlockTargets.Fragment;
}

return NodeMaterialBlockTargets.VertexAndFragment;
return NodeMaterialBlockTargets.Fragment;
}

public override get target() {
return this._getEffectiveTarget();
}

public override set target(value: NodeMaterialBlockTargets) {}
Expand Down
3 changes: 3 additions & 0 deletions packages/dev/core/src/Materials/Node/Blocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,6 @@ export * from "./matrixTransposeBlock";
export * from "./meshAttributeExistsBlock";
export * from "./curveBlock";
export * from "./colorConverterBlock";
export * from "./loopBlock";
export * from "./storageReadBlock";
export * from "./storageWriteBlock";
145 changes: 145 additions & 0 deletions packages/dev/core/src/Materials/Node/Blocks/loopBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { NodeMaterialBlock } from "../nodeMaterialBlock";
import { NodeMaterialBlockConnectionPointTypes } from "../Enums/nodeMaterialBlockConnectionPointTypes";
import type { NodeMaterialBuildState } from "../nodeMaterialBuildState";
import { NodeMaterialConnectionPointDirection } from "../nodeMaterialBlockConnectionPoint";
import type { NodeMaterialConnectionPoint } from "../nodeMaterialBlockConnectionPoint";
import { NodeMaterialBlockTargets } from "../Enums/nodeMaterialBlockTargets";
import { RegisterClass } from "../../../Misc/typeStore";
import { editableInPropertyPage, PropertyTypeForEdition } from "core/Decorators/nodeDecorator";
import type { Scene } from "core/scene";
import { ShaderLanguage } from "core/Materials/shaderLanguage";
import { NodeMaterialConnectionPointCustomObject } from "../nodeMaterialConnectionPointCustomObject";
/**
* Block used to repeat code
*/
export class LoopBlock extends NodeMaterialBlock {
/**
* Gets or sets number of iterations
* Will be ignored if the iterations input is connected
*/
@editableInPropertyPage("Iterations", PropertyTypeForEdition.Int)
public iterations = 4;

/**
* Creates a new LoopBlock
* @param name defines the block name
*/
public constructor(name: string) {
super(name, NodeMaterialBlockTargets.Neutral);

this.registerInput("input", NodeMaterialBlockConnectionPointTypes.AutoDetect);
this.registerInput("iterations", NodeMaterialBlockConnectionPointTypes.Float, true);
this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.BasedOnInput);
this.registerOutput("index", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Fragment);
this.registerOutput(
"loopID",
NodeMaterialBlockConnectionPointTypes.Object,
undefined,
new NodeMaterialConnectionPointCustomObject("loopID", this, NodeMaterialConnectionPointDirection.Output, LoopBlock, "LoopBlock")
);

this._outputs[0]._typeConnectionSource = this._inputs[0];
this._outputs[0]._forPostBuild = true;

this._outputs[2]._redirectedSource = this._inputs[0];

this._outputs[1]._preventBubbleUp = true;
this._outputs[2]._preventBubbleUp = true;
}

/**
* Gets the current class name
* @returns the class name
*/
public override getClassName() {
return "LoopBlock";
}

/**
* Gets the main input component
*/
public get input(): NodeMaterialConnectionPoint {
return this._inputs[0];
}

/**
* Gets the iterations input component
*/
public get iterationsInput(): NodeMaterialConnectionPoint {
return this._inputs[1];
}

/**
* Gets the output component
*/
public get output(): NodeMaterialConnectionPoint {
return this._outputs[0];
}

/**
* Gets the index component which will be incremented at each iteration
*/
public get index(): NodeMaterialConnectionPoint {
return this._outputs[1];
}

/**
* Gets the loop ID component
*/
public get loopID(): NodeMaterialConnectionPoint {
return this._outputs[2];
}

protected override _buildBlock(state: NodeMaterialBuildState) {
super._buildBlock(state);

const output = this._outputs[0];
const index = this._outputs[1];

const indexName = state._getFreeVariableName("index");

const decl = state.shaderLanguage === ShaderLanguage.WGSL ? "var" : "int";
const castFloat = state.shaderLanguage === ShaderLanguage.WGSL ? "f32" : "float";
const castInt = state.shaderLanguage === ShaderLanguage.WGSL ? "i32" : "int";

// Declare storage variable and store initial value
state.compilationString += state._declareOutput(output) + ` = ${this.input.associatedVariableName};\n`;

// Iterations
const iterations = this.iterationsInput.isConnected ? `${castInt}(${this.iterationsInput.associatedVariableName})` : this.iterations;

// Loop
state.compilationString += `for (${decl} ${indexName} = 0; ${indexName} < ${iterations}; ${indexName}++){\n`;
state.compilationString += `${state._declareOutput(index)} = ${castFloat}(${indexName});\n`;

return this;
}

protected override _postBuildBlock(state: NodeMaterialBuildState) {
super._postBuildBlock(state);

state.compilationString += `}\n`;

return this;
}

protected override _dumpPropertiesCode() {
return super._dumpPropertiesCode() + `${this._codeVariableName}.iterations = ${this.iterations};\n`;
}

public override serialize(): any {
const serializationObject = super.serialize();

serializationObject.iterations = this.iterations;

return serializationObject;
}

public override _deserialize(serializationObject: any, scene: Scene, rootUrl: string) {
super._deserialize(serializationObject, scene, rootUrl);

this.iterations = serializationObject.iterations;
}
}

RegisterClass("BABYLON.LoopBlock", LoopBlock);
71 changes: 71 additions & 0 deletions packages/dev/core/src/Materials/Node/Blocks/storageReadBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { NodeMaterialBlock } from "../nodeMaterialBlock";
import { NodeMaterialBlockConnectionPointTypes } from "../Enums/nodeMaterialBlockConnectionPointTypes";
import type { NodeMaterialBuildState } from "../nodeMaterialBuildState";
import { NodeMaterialConnectionPointDirection, type NodeMaterialConnectionPoint } from "../nodeMaterialBlockConnectionPoint";
import { NodeMaterialBlockTargets } from "../Enums/nodeMaterialBlockTargets";
import { RegisterClass } from "../../../Misc/typeStore";
import { LoopBlock } from "./loopBlock";
import { NodeMaterialConnectionPointCustomObject } from "../nodeMaterialConnectionPointCustomObject";
/**
* Block used to read from a variable within a loop
*/
export class StorageReadBlock extends NodeMaterialBlock {
/**
* Creates a new StorageReadBlock
* @param name defines the block name
*/
public constructor(name: string) {
super(name, NodeMaterialBlockTargets.Neutral);

this.registerInput(
"loopID",
NodeMaterialBlockConnectionPointTypes.Object,
false,
undefined,
new NodeMaterialConnectionPointCustomObject("loopID", this, NodeMaterialConnectionPointDirection.Input, LoopBlock, "LoopBlock")
);
this.registerOutput("value", NodeMaterialBlockConnectionPointTypes.AutoDetect);

this._outputs[0]._linkedConnectionSource = this._inputs[0];
}

/**
* Gets the current class name
* @returns the class name
*/
public override getClassName() {
return "StorageReadBlock";
}

/**
* Gets the loop link component
*/
public get loopID(): NodeMaterialConnectionPoint {
return this._inputs[0];
}

/**
* Gets the value component
*/
public get value(): NodeMaterialConnectionPoint {
return this._outputs[0];
}

protected override _buildBlock(state: NodeMaterialBuildState) {
super._buildBlock(state);

const value = this.value;

if (!this.loopID.isConnected) {
return this;
}

const loopBlock = this.loopID.connectedPoint!.ownerBlock as LoopBlock;

state.compilationString += state._declareOutput(value) + ` = ${loopBlock.output.associatedVariableName};\n`;

return this;
}
}

RegisterClass("BABYLON.StorageReadBlock", StorageReadBlock);
Loading

0 comments on commit c7e709f

Please sign in to comment.