Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c21c7ff
Add frontFace method support (#8447)
AlexAPPi Feb 6, 2026
3c51fbc
Changed the order to conform to WebGPU specifications.
AlexAPPi Feb 6, 2026
d56c6e1
Fix lint
AlexAPPi Feb 6, 2026
0b7aad8
Delete mode from name
AlexAPPi Feb 6, 2026
fc6ba7b
Merge branch 'main' into alx-add-render-front-face-mode
AlexAPPi Feb 6, 2026
e1f01e0
Merge branch 'main' into alx-add-render-front-face-mode
AlexAPPi Feb 10, 2026
dca04b4
Remove default value in functions with private API
AlexAPPi Feb 13, 2026
3d626f4
Remove comment
AlexAPPi Feb 13, 2026
3542c9a
Add setupFrontFace method to renderer
AlexAPPi Feb 14, 2026
c2db7bb
Fix: 'FRONTFACE_CCW' is defined but never used
AlexAPPi Feb 14, 2026
a9ea299
Bly setupFrontFace
AlexAPPi Feb 14, 2026
c16b5fe
Merge remote-tracking branch 'upstream/main' into alx-add-render-fron…
AlexAPPi Feb 14, 2026
485bd58
Update src/platform/graphics/constants.js
AlexAPPi Feb 14, 2026
598919a
Update src/platform/graphics/constants.js
AlexAPPi Feb 14, 2026
227debf
Update src/platform/graphics/graphics-device.js
AlexAPPi Feb 14, 2026
acbf75b
Update src/scene/materials/material.js
AlexAPPi Feb 14, 2026
27e7feb
Merge branch 'main' into alx-add-render-front-face-mode
AlexAPPi Feb 14, 2026
d17349a
Merge branch 'main' into alx-add-render-front-face-mode
AlexAPPi Feb 17, 2026
20576c9
Apply suggestions from code review
mvaligursky Feb 23, 2026
3bca468
Update src/platform/graphics/constants.js
mvaligursky Feb 23, 2026
f837ae5
Update src/scene/materials/material.js
mvaligursky Feb 23, 2026
57ff0f0
Merge branch 'main' into alx-add-render-front-face-mode
AlexAPPi Feb 23, 2026
69914f3
Merge branch 'main' into alx-add-render-front-face-mode
AlexAPPi Feb 24, 2026
8d3ccd3
Add frontFace test
AlexAPPi Feb 25, 2026
a42ed02
Merge branch 'main' into alx-add-render-front-face-mode
AlexAPPi Feb 25, 2026
e4edbd3
Add flipFactor for setupFontFace
AlexAPPi Feb 25, 2026
509b5c4
Merge branch 'alx-add-render-front-face-mode' of https://github.com/A…
AlexAPPi Feb 25, 2026
94df637
Backward compatibility
AlexAPPi Feb 25, 2026
485791f
lint
AlexAPPi Feb 25, 2026
f1c773d
Merge branch 'main' into alx-add-render-front-face-mode
AlexAPPi Feb 25, 2026
df4b9fd
Ok
AlexAPPi Feb 25, 2026
eb2fc22
Merge branch 'alx-add-render-front-face-mode' of https://github.com/A…
AlexAPPi Feb 25, 2026
8ea6447
Change setup logic
AlexAPPi Feb 25, 2026
263b9c7
Change setup logic
AlexAPPi Feb 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/extras/renderers/outline-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { BlendState } from '../../platform/graphics/blend-state.js';
import {
ADDRESS_CLAMP_TO_EDGE, BLENDEQUATION_ADD, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDMODE_SRC_ALPHA,
CULLFACE_NONE,
FILTER_LINEAR, FILTER_LINEAR_MIPMAP_LINEAR, PIXELFORMAT_SRGBA8,
FILTER_LINEAR, FILTER_LINEAR_MIPMAP_LINEAR, FRONTFACE_CCW, PIXELFORMAT_SRGBA8,
SEMANTIC_POSITION
} from '../../platform/graphics/constants.js';
import { DepthState } from '../../platform/graphics/depth-state.js';
Expand Down Expand Up @@ -316,6 +316,7 @@ class OutlineRenderer {

device.setDepthState(DepthState.NODEPTH);
device.setCullMode(CULLFACE_NONE);
device.setFrontFace(FRONTFACE_CCW);
device.setBlendState(this.blendState);
this.quadRenderer.render();
}
Expand Down
14 changes: 14 additions & 0 deletions src/platform/graphics/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,20 @@ export const CULLFACE_FRONT = 2;
*/
export const CULLFACE_FRONTANDBACK = 3;

/**
* The counterclockwise winding. Specifies whether polygons are front- or back-facing by setting a winding orientation.
*
* @category Graphics
*/
export const FRONTFACE_CCW = 0;

/**
* The clockwise winding. Specifies whether polygons are front- or back-facing by setting a winding orientation.
*
* @category Graphics
*/
export const FRONTFACE_CW = 1;

/**
* Point sample filtering.
*
Expand Down
17 changes: 16 additions & 1 deletion src/platform/graphics/graphics-device.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
INDEXFORMAT_UINT16,
PRIMITIVE_POINTS, PRIMITIVE_TRIFAN, SEMANTIC_POSITION, TYPE_FLOAT32, PIXELFORMAT_111110F, PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA32F,
DISPLAYFORMAT_LDR,
semanticToLocation
semanticToLocation,
FRONTFACE_CCW
} from './constants.js';
import { BlendState } from './blend-state.js';
import { DepthState } from './depth-state.js';
Expand Down Expand Up @@ -772,6 +773,7 @@ class GraphicsDevice extends EventHandler {
this.blendState = new BlendState();
this.depthState = new DepthState();
this.cullMode = CULLFACE_BACK;
this.frontFace = FRONTFACE_CCW;

// Cached viewport and scissor dimensions
this.vx = this.vy = this.vw = this.vh = 0;
Expand Down Expand Up @@ -839,6 +841,19 @@ class GraphicsDevice extends EventHandler {
Debug.assert(false);
}

/**
* Controls whether polygons are front- or back-facing by setting a winding
* orientation. The default frontFace is {@link FRONTFACE_CCW}.
*
* @param {number} frontFace - The front face to set. Can be:
*
* - {@link FRONTFACE_CW}
* - {@link FRONTFACE_CCW}
*/
setFrontFace(frontFace) {
Debug.assert(false);
}

/**
* Sets the specified render target on the device. If null is passed as a parameter, the back
* buffer becomes the current target for all rendering operations.
Expand Down
3 changes: 3 additions & 0 deletions src/platform/graphics/null/null-graphics-device.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ class NullGraphicsDevice extends GraphicsDevice {
setCullMode(cullMode) {
}

setFrontFace(frontFace) {
}

setAlphaToCoverage(state) {
}

Expand Down
13 changes: 13 additions & 0 deletions src/platform/graphics/webgl/webgl-graphics-device.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,11 @@ class WebglGraphicsDevice extends GraphicsDevice {
gl.FRONT_AND_BACK
];

this.glFrontFace = [
gl.CCW,
gl.CW
];

this.glFilter = [
gl.NEAREST,
gl.LINEAR,
Expand Down Expand Up @@ -2512,6 +2517,14 @@ class WebglGraphicsDevice extends GraphicsDevice {
}
}

setFrontFace(frontFace) {
if (this.frontFace !== frontFace) {
const mode = this.glFrontFace[frontFace];
this.gl.frontFace(mode);
this.frontFace = frontFace;
}
}

/**
* Sets the active shader to be used during subsequent draw calls.
*
Expand Down
4 changes: 3 additions & 1 deletion src/platform/graphics/webgpu/webgpu-clear-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
CULLFACE_NONE,
PRIMITIVE_TRISTRIP, SHADERLANGUAGE_WGSL,
UNIFORMTYPE_FLOAT, UNIFORMTYPE_VEC4, BINDGROUP_MESH, CLEARFLAG_COLOR, CLEARFLAG_DEPTH, CLEARFLAG_STENCIL,
BINDGROUP_MESH_UB
BINDGROUP_MESH_UB,
FRONTFACE_CCW
} from '../constants.js';
import { Shader } from '../shader.js';
import { DynamicBindGroup } from '../bind-group.js';
Expand Down Expand Up @@ -138,6 +139,7 @@ class WebgpuClearRenderer {
uniformBuffer.endUpdate();

device.setCullMode(CULLFACE_NONE);
device.setFrontFace(FRONTFACE_CCW);

// render 4 vertices without vertex buffer
device.setShader(this.shader);
Expand Down
6 changes: 5 additions & 1 deletion src/platform/graphics/webgpu/webgpu-graphics-device.js
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,7 @@ class WebgpuGraphicsDevice extends GraphicsDevice {
// render pipeline
pipeline = this.renderPipeline.get(primitive, vb0?.format, vb1?.format, indexBuffer?.format, this.shader, this.renderTarget,
this.bindGroupFormats, this.blendState, this.depthState, this.cullMode,
this.stencilEnabled, this.stencilFront, this.stencilBack);
this.stencilEnabled, this.stencilFront, this.stencilBack, this.frontFace);
Debug.assert(pipeline);

if (this.pipeline !== pipeline) {
Expand Down Expand Up @@ -851,6 +851,10 @@ class WebgpuGraphicsDevice extends GraphicsDevice {
this.cullMode = cullMode;
}

setFrontFace(frontFace) {
this.frontFace = frontFace;
}

setAlphaToCoverage(state) {
}

Expand Down
17 changes: 12 additions & 5 deletions src/platform/graphics/webgpu/webgpu-render-pipeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ const _cullModes = [
'front' // CULLFACE_FRONT
];

const _frontFace = [
'ccw', // FRONTFACE_CCW
'cw' // FRONTFACE_CW
];

const _stencilOps = [
'keep', // STENCILOP_KEEP
'zero', // STENCILOP_ZERO
Expand Down Expand Up @@ -107,7 +112,7 @@ class CacheEntry {
}

class WebgpuRenderPipeline extends WebgpuPipeline {
lookupHashes = new Uint32Array(14);
lookupHashes = new Uint32Array(15);

constructor(device) {
super(device);
Expand Down Expand Up @@ -141,11 +146,12 @@ class WebgpuRenderPipeline extends WebgpuPipeline {
* @param {boolean} stencilEnabled - Whether stencil is enabled.
* @param {StencilParameters} stencilFront - The stencil state for front faces.
* @param {StencilParameters} stencilBack - The stencil state for back faces.
* @param {number} frontFace - The front face.
* @returns {GPURenderPipeline} Returns the render pipeline.
* @private
*/
get(primitive, vertexFormat0, vertexFormat1, ibFormat, shader, renderTarget, bindGroupFormats, blendState,
depthState, cullMode, stencilEnabled, stencilFront, stencilBack) {
depthState, cullMode, stencilEnabled, stencilFront, stencilBack, frontFace) {

Debug.assert(bindGroupFormats.length <= 3);

Expand Down Expand Up @@ -177,6 +183,7 @@ class WebgpuRenderPipeline extends WebgpuPipeline {
lookupHashes[11] = stencilEnabled ? stencilFront.key : 0;
lookupHashes[12] = stencilEnabled ? stencilBack.key : 0;
lookupHashes[13] = ibFormat ?? 0;
lookupHashes[14] = frontFace;
const hash = hash32Fnv1a(lookupHashes);

// cached pipeline
Expand Down Expand Up @@ -206,7 +213,7 @@ class WebgpuRenderPipeline extends WebgpuPipeline {
const cacheEntry = new CacheEntry();
cacheEntry.hashes = new Uint32Array(lookupHashes);
cacheEntry.pipeline = this.create(primitiveTopology, ibFormat, shader, renderTarget, pipelineLayout, blendState,
depthState, vertexBufferLayout, cullMode, stencilEnabled, stencilFront, stencilBack);
depthState, vertexBufferLayout, cullMode, stencilEnabled, stencilFront, stencilBack, frontFace);

// add to cache
if (cacheEntries) {
Expand Down Expand Up @@ -313,7 +320,7 @@ class WebgpuRenderPipeline extends WebgpuPipeline {
}

create(primitiveTopology, ibFormat, shader, renderTarget, pipelineLayout, blendState, depthState, vertexBufferLayout,
cullMode, stencilEnabled, stencilFront, stencilBack) {
cullMode, stencilEnabled, stencilFront, stencilBack, frontFace) {

const wgpu = this.device.wgpu;

Expand All @@ -330,7 +337,7 @@ class WebgpuRenderPipeline extends WebgpuPipeline {

primitive: {
topology: primitiveTopology,
frontFace: 'ccw',
frontFace: _frontFace[frontFace],
cullMode: _cullModes[cullMode]
},

Expand Down
2 changes: 2 additions & 0 deletions src/scene/graphics/quad-render.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const _dynamicBindGroup = new DynamicBindGroup();
* you should set up the following states as needed, otherwise previously set states will be used:
* - Blend state via {@link GraphicsDevice#setBlendState}
* - Cull mode via {@link GraphicsDevice#setCullMode}
* - FrontFace via {@link GraphicsDevice#setFrontFace}
* - Depth state via {@link GraphicsDevice#setDepthState}
* - Stencil state via {@link GraphicsDevice#setStencilState}
*
Expand All @@ -46,6 +47,7 @@ const _dynamicBindGroup = new DynamicBindGroup();
* // Set up render states before rendering
* app.graphicsDevice.setBlendState(BlendState.NOBLEND);
* app.graphicsDevice.setCullMode(CULLFACE_NONE);
* app.graphicsDevice.setFrontFace(FRONTFACE_CCW);
* app.graphicsDevice.setDepthState(DepthState.NODEPTH);
* app.graphicsDevice.setStencilState(null, null);
*
Expand Down
3 changes: 2 additions & 1 deletion src/scene/graphics/render-pass-quad.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CULLFACE_NONE } from '../../platform/graphics/constants.js';
import { CULLFACE_NONE, FRONTFACE_CCW } from '../../platform/graphics/constants.js';
import { DebugGraphics } from '../../platform/graphics/debug-graphics.js';
import { DepthState } from '../../platform/graphics/depth-state.js';
import { RenderPass } from '../../platform/graphics/render-pass.js';
Expand All @@ -20,6 +20,7 @@ class RenderPassQuad extends RenderPass {
DebugGraphics.pushGpuMarker(device, `${this.name}:${this.quad.shader.name}`);

device.setCullMode(CULLFACE_NONE);
device.setFrontFace(FRONTFACE_CCW);
device.setDepthState(DepthState.NODEPTH);
device.setStencilState(null, null);

Expand Down
8 changes: 7 additions & 1 deletion src/scene/graphics/render-pass-shader-quad.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { QuadRender } from './quad-render.js';
import { BlendState } from '../../platform/graphics/blend-state.js';
import { CULLFACE_NONE } from '../../platform/graphics/constants.js';
import { CULLFACE_NONE, FRONTFACE_CCW } from '../../platform/graphics/constants.js';
import { DepthState } from '../../platform/graphics/depth-state.js';
import { RenderPass } from '../../platform/graphics/render-pass.js';

Expand Down Expand Up @@ -33,6 +33,11 @@ class RenderPassShaderQuad extends RenderPass {
*/
cullMode = CULLFACE_NONE;

/**
* The front face to use when rendering the quad. Defaults to {@link FRONTFACE_CCW}.
*/
frontFace = FRONTFACE_CCW;

/**
* A blend state to use when rendering the quad. Defaults to {@link BlendState.NOBLEND}.
*
Expand Down Expand Up @@ -106,6 +111,7 @@ class RenderPassShaderQuad extends RenderPass {
const device = this.device;
device.setBlendState(this.blendState);
device.setCullMode(this.cullMode);
device.setFrontFace(this.frontFace);
device.setDepthState(this.depthState);
device.setStencilState(this.stencilFront, this.stencilBack);

Expand Down
3 changes: 2 additions & 1 deletion src/scene/gsplat-unified/gsplat-work-buffer-render-pass.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { RenderPass } from '../../platform/graphics/render-pass.js';
import { DebugGraphics } from '../../platform/graphics/debug-graphics.js';
import { BlendState } from '../../platform/graphics/blend-state.js';
import { DepthState } from '../../platform/graphics/depth-state.js';
import { CULLFACE_NONE } from '../../platform/graphics/constants.js';
import { CULLFACE_NONE, FRONTFACE_CCW } from '../../platform/graphics/constants.js';

/**
* @import { GSplatInfo } from './gsplat-info.js'
Expand Down Expand Up @@ -105,6 +105,7 @@ class GSplatWorkBufferRenderPass extends RenderPass {
// Set up render state
device.setBlendState(BlendState.NOBLEND);
device.setCullMode(CULLFACE_NONE);
device.setFrontFace(FRONTFACE_CCW);
device.setDepthState(DepthState.NODEPTH);
device.setStencilState();

Expand Down
2 changes: 2 additions & 0 deletions src/scene/gsplat/gsplat-resolve-sh.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Mat4 } from '../../core/math/mat4.js';
import { BlendState } from '../../platform/graphics/blend-state.js';
import {
CULLFACE_NONE,
FRONTFACE_CCW,
PIXELFORMAT_RGBA8,
SEMANTIC_POSITION
} from '../../platform/graphics/constants.js';
Expand Down Expand Up @@ -290,6 +291,7 @@ class GSplatResolveSH {
});

device.setCullMode(CULLFACE_NONE);
device.setFrontFace(FRONTFACE_CCW);
device.setDepthState(DepthState.NODEPTH);
device.setStencilState(null, null);
device.setBlendState(BlendState.NOBLEND);
Expand Down
17 changes: 16 additions & 1 deletion src/scene/materials/material.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
BLENDEQUATION_ADD, BLENDEQUATION_REVERSE_SUBTRACT,
BLENDEQUATION_MIN, BLENDEQUATION_MAX,
CULLFACE_BACK,
SHADERLANGUAGE_GLSL
SHADERLANGUAGE_GLSL,
FRONTFACE_CCW
} from '../../platform/graphics/constants.js';
import { BlendState } from '../../platform/graphics/blend-state.js';
import { DepthState } from '../../platform/graphics/depth-state.js';
Expand Down Expand Up @@ -167,6 +168,19 @@ class Material {
*/
cull = CULLFACE_BACK;

/**
* Controls whether polygons are front- or back-facing by setting a winding
* orientation. Can be:
*
* - {@link FRONTFACE_CW}: The clock-wise winding.
* - {@link FRONTFACE_CCW}: The counterclockwise winding.
*
* Defaults to {@link FRONTFACE_CCW}.
*
* @type {number}
*/
frontFace = FRONTFACE_CCW;

/**
* Stencil parameters for front faces (default is null).
*
Expand Down Expand Up @@ -637,6 +651,7 @@ class Material {
this._depthState.copy(source._depthState);

this.cull = source.cull;
this.frontFace = source.frontFace;

this.stencilFront = source.stencilFront?.clone();
if (source.stencilBack) {
Expand Down
3 changes: 2 additions & 1 deletion src/scene/particle-system/gpu-updater.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Mat3 } from '../../core/math/mat3.js';
import { Mat4 } from '../../core/math/mat4.js';
import { Vec3 } from '../../core/math/vec3.js';

import { CULLFACE_NONE } from '../../platform/graphics/constants.js';
import { CULLFACE_NONE, FRONTFACE_CCW } from '../../platform/graphics/constants.js';
import { DebugGraphics } from '../../platform/graphics/debug-graphics.js';
import { BlendState } from '../../platform/graphics/blend-state.js';
import { DepthState } from '../../platform/graphics/depth-state.js';
Expand Down Expand Up @@ -96,6 +96,7 @@ class ParticleGPUUpdater {
device.setBlendState(BlendState.NOBLEND);
device.setDepthState(DepthState.NODEPTH);
device.setCullMode(CULLFACE_NONE);
device.setFrontFace(FRONTFACE_CCW);

this.randomize();

Expand Down
2 changes: 1 addition & 1 deletion src/scene/renderer/forward-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ class ForwardRenderer extends Renderer {

DebugGraphics.pushGpuMarker(device, `Node: ${drawCall.node.name}, Material: ${material.name}`);

this.setupCullMode(camera._cullFaces, flipFactor, drawCall);
this.setupCullModeAndFrontFace(camera._cullFaces, flipFactor, drawCall);

const stencilFront = drawCall.stencilFront ?? material.stencilFront;
const stencilBack = drawCall.stencilBack ?? material.stencilBack;
Expand Down
3 changes: 2 additions & 1 deletion src/scene/renderer/render-pass-cookie-renderer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Debug } from '../../core/debug.js';
import { Vec4 } from '../../core/math/vec4.js';
import { Mat4 } from '../../core/math/mat4.js';
import { CULLFACE_NONE, SEMANTIC_POSITION } from '../../platform/graphics/constants.js';
import { CULLFACE_NONE, FRONTFACE_CCW, SEMANTIC_POSITION } from '../../platform/graphics/constants.js';
import { DebugGraphics } from '../../platform/graphics/debug-graphics.js';
import { LIGHTTYPE_DIRECTIONAL, LIGHTTYPE_OMNI } from '../constants.js';
import { ShaderUtils } from '../shader-lib/shader-utils.js';
Expand Down Expand Up @@ -165,6 +165,7 @@ class RenderPassCookieRenderer extends RenderPass {
const device = this.device;
device.setBlendState(BlendState.NOBLEND);
device.setCullMode(CULLFACE_NONE);
device.setFrontFace(FRONTFACE_CCW);
device.setDepthState(DepthState.NODEPTH);
device.setStencilState();

Expand Down
Loading