Skip to content

Commit c5da3b6

Browse files
bkaradzicCopilot
authored andcommitted
[Native] MultiRenderTarget, reverse-Z clear, applyStates, OIT alpha modes
Foundational native-engine surface so MultiRenderTarget, the reverse depth buffer, and the order-independent-transparency renderer stop dereferencing the null _gl context. Removes several crash classes; does not by itself land the dependent validation tests. - createMultipleRenderTarget + the MRT helper overrides (bindAttachments, buildTextureLayout, restoreSingleAttachment[ForRenderTarget], generateMipMapsMultiFramebuffer, resolveMultiFramebuffer, unBindMultiColorAttachmentFramebuffer, updateMultipleRenderTargetTextureSampleCount). bgfx writes every color attachment of the bound framebuffer, so the WebGL drawBuffers / MSAA-resolve plumbing becomes no-ops on Native. Reports drawBuffersExtension = true. - clear(): implement the reverse depth buffer (clear depth to 0 + GEQUAL) instead of throwing. - applyStates(): override so it flushes the depth-culling state through the native command path (the base implementation drives the null _gl directly), fixing callers that mutate engine.depthCullingState then call applyStates() (e.g. the OIT depth-peeling renderer). - Map alpha modes ALPHA_ONEONE_ONEONE and ALPHA_LAYER_ACCUMULATE to the native engine. Pairs with the BabylonNative change (createMultiFrameBuffer + native alpha modes). Known follow-ups: OIT depth-peeling still faults in the D3D11 driver on submit; the blend equation (MAX) is not yet applied natively. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4ada000 commit c5da3b6

3 files changed

Lines changed: 191 additions & 4 deletions

File tree

packages/dev/core/src/Engines/Native/nativeHelpers.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,10 @@ export function getNativeAlphaMode(mode: number): number {
315315
return _native.Engine.ALPHA_MAXIMIZED;
316316
case Constants.ALPHA_ONEONE:
317317
return _native.Engine.ALPHA_ONEONE;
318+
case Constants.ALPHA_ONEONE_ONEONE:
319+
return _native.Engine.ALPHA_ONEONE_ONEONE;
320+
case Constants.ALPHA_LAYER_ACCUMULATE:
321+
return _native.Engine.ALPHA_LAYER_ACCUMULATE;
318322
case Constants.ALPHA_PREMULTIPLIED:
319323
return _native.Engine.ALPHA_PREMULTIPLIED;
320324
case Constants.ALPHA_PREMULTIPLIED_PORTERDUFF:

packages/dev/core/src/Engines/Native/nativeInterfaces.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,14 @@ export interface INativeEngine {
102102
generateDepthBuffer: boolean,
103103
samples: number
104104
): NativeFramebuffer;
105+
createMultiFrameBuffer(
106+
textures: NativeTexture[],
107+
width: number,
108+
height: number,
109+
generateStencilBuffer: boolean,
110+
generateDepthBuffer: boolean,
111+
samples: number
112+
): NativeFramebuffer;
105113

106114
getRenderWidth(): number;
107115
getRenderHeight(): number;
@@ -270,6 +278,8 @@ interface INativeEngineConstructor {
270278
readonly ALPHA_MULTIPLY: number;
271279
readonly ALPHA_MAXIMIZED: number;
272280
readonly ALPHA_ONEONE: number;
281+
readonly ALPHA_ONEONE_ONEONE: number;
282+
readonly ALPHA_LAYER_ACCUMULATE: number;
273283
readonly ALPHA_PREMULTIPLIED: number;
274284
readonly ALPHA_PREMULTIPLIED_PORTERDUFF: number;
275285
readonly ALPHA_INTERPOLATE: number;

packages/dev/core/src/Engines/thinNativeEngine.pure.ts

Lines changed: 177 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
type InternalTextureCreationOptions,
2020
} from "../Materials/Textures/textureCreationOptions";
2121
import { type IPipelineContext } from "./IPipelineContext";
22+
import { type IMultiRenderTargetOptions } from "../Materials/Textures/multiRenderTarget.pure";
2223
import { type IColor3Like, type IColor4Like, type IViewportLike } from "../Maths/math.like";
2324
import { Logger } from "../Misc/logger";
2425
import { Constants } from "./constants";
@@ -344,7 +345,7 @@ export class ThinNativeEngine extends ThinEngine {
344345
textureHalfFloatRender: true,
345346
textureLOD: true,
346347
texelFetch: false,
347-
drawBuffersExtension: false,
348+
drawBuffersExtension: true,
348349
depthTextureExtension: false,
349350
vertexArrayObject: true,
350351
instancedArrays: true,
@@ -570,8 +571,11 @@ export class ThinNativeEngine extends ThinEngine {
570571
}
571572

572573
public override clear(color: Nullable<IColor4Like>, backBuffer: boolean, depth: boolean, stencil: boolean = false, stencilClearValue = 0): void {
573-
if (this.useReverseDepthBuffer) {
574-
throw new Error("reverse depth buffer is not currently implemented");
574+
if (depth && this.useReverseDepthBuffer) {
575+
// Reverse-Z: the scene is rendered with a flipped projection (near maps to 1, far to 0), so the
576+
// depth buffer is cleared to 0 and the comparison must accept greater values. Mirror the WebGL
577+
// engine, which sets the depth-culling comparison to GEQUAL here and clears depth to 0.
578+
this._depthCullingState.depthFunc = Constants.GEQUAL;
575579
}
576580

577581
this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_CLEAR);
@@ -581,7 +585,7 @@ export class ThinNativeEngine extends ThinEngine {
581585
this._commandBufferEncoder.encodeCommandArgAsFloat32(color ? color.b : 0);
582586
this._commandBufferEncoder.encodeCommandArgAsFloat32(color ? color.a : 1);
583587
this._commandBufferEncoder.encodeCommandArgAsUInt32(depth ? 1 : 0);
584-
this._commandBufferEncoder.encodeCommandArgAsFloat32(1);
588+
this._commandBufferEncoder.encodeCommandArgAsFloat32(depth && this.useReverseDepthBuffer ? 0 : 1);
585589
this._commandBufferEncoder.encodeCommandArgAsUInt32(stencil ? 1 : 0);
586590
this._commandBufferEncoder.encodeCommandArgAsUInt32(stencilClearValue);
587591
this._commandBufferEncoder.finishEncodingCommand();
@@ -1074,6 +1078,20 @@ export class ThinNativeEngine extends ThinEngine {
10741078
this._commandBufferEncoder.finishEncodingCommand();
10751079
}
10761080

1081+
public override applyStates(): void {
1082+
// The base ThinEngine.applyStates() drives the WebGL context (this._gl) directly, which is null on
1083+
// Native. Flush the depth-culling state through the native command path instead, so callers that
1084+
// mutate engine.depthCullingState directly and then call applyStates() (e.g. the depth-peeling / OIT
1085+
// renderer) take effect. Alpha and stencil state are applied on Native via their dedicated setters
1086+
// (setAlphaMode / setStencil*), which encode their commands when called.
1087+
const depthCullingState = this._depthCullingState;
1088+
if (depthCullingState.depthFunc !== null && depthCullingState.depthFunc !== undefined) {
1089+
this.setDepthFunction(depthCullingState.depthFunc);
1090+
}
1091+
this.setDepthBuffer(depthCullingState.depthTest);
1092+
this.setDepthWrite(depthCullingState.depthMask);
1093+
}
1094+
10771095
/**
10781096
* Gets a boolean indicating if depth writing is enabled
10791097
* @returns the current depth writing state
@@ -2317,6 +2335,161 @@ export class ThinNativeEngine extends ThinEngine {
23172335
return rtWrapper;
23182336
}
23192337

2338+
public override createMultipleRenderTarget(size: TextureSize, options: IMultiRenderTargetOptions, _initializeBuffers = true): RenderTargetWrapper {
2339+
const rtWrapper = this._createHardwareRenderTargetWrapper(true, false, size) as NativeRenderTargetWrapper;
2340+
2341+
let generateMipMaps = false;
2342+
let generateDepthBuffer = true;
2343+
let generateStencilBuffer = false;
2344+
let generateDepthTexture = false;
2345+
let textureCount = 1;
2346+
let samples = 1;
2347+
let types: number[] = [];
2348+
let samplingModes: number[] = [];
2349+
let formats: number[] = [];
2350+
let targets: number[] = [];
2351+
let faceIndex: number[] = [];
2352+
let layerIndex: number[] = [];
2353+
let labels: string[] = [];
2354+
let dontCreateTextures = false;
2355+
2356+
if (options !== undefined) {
2357+
generateMipMaps = options.generateMipMaps ?? false;
2358+
generateDepthBuffer = options.generateDepthBuffer ?? true;
2359+
generateStencilBuffer = options.generateStencilBuffer ?? false;
2360+
generateDepthTexture = options.generateDepthTexture ?? false;
2361+
textureCount = options.textureCount ?? 1;
2362+
samples = options.samples ?? 1;
2363+
types = options.types || types;
2364+
samplingModes = options.samplingModes || samplingModes;
2365+
formats = options.formats || formats;
2366+
targets = options.targetTypes || targets;
2367+
faceIndex = options.faceIndex || faceIndex;
2368+
layerIndex = options.layerIndex || layerIndex;
2369+
labels = options.labels || labels;
2370+
dontCreateTextures = options.dontCreateTextures ?? false;
2371+
}
2372+
2373+
rtWrapper.label = options?.label ?? "MultiRenderTargetWrapper";
2374+
2375+
const width = (<{ width: number; height: number }>size).width ?? <number>size;
2376+
const height = (<{ width: number; height: number }>size).height ?? <number>size;
2377+
2378+
const textures: InternalTexture[] = [];
2379+
const attachments: number[] = [];
2380+
const colorHandles: NativeTexture[] = [];
2381+
2382+
for (let i = 0; i < textureCount; i++) {
2383+
const samplingMode = samplingModes[i] || Constants.TEXTURE_TRILINEAR_SAMPLINGMODE;
2384+
let type = types[i] || Constants.TEXTURETYPE_UNSIGNED_BYTE;
2385+
const format = formats[i] || Constants.TEXTUREFORMAT_RGBA;
2386+
const target = targets[i] || Constants.TEXTURE_2D;
2387+
2388+
attachments.push(i);
2389+
2390+
// target === -1 marks an attachment with no engine-created texture; dontCreateTextures defers them.
2391+
if (target === -1 || dontCreateTextures) {
2392+
continue;
2393+
}
2394+
2395+
if (type === Constants.TEXTURETYPE_FLOAT && !this._caps.textureFloat) {
2396+
type = Constants.TEXTURETYPE_UNSIGNED_BYTE;
2397+
Logger.Warn("Float textures are not supported. Multi render target attachment forced to TEXTURETYPE_UNSIGNED_BYTE type");
2398+
}
2399+
2400+
const texture = new InternalTexture(this, InternalTextureSource.MultiRenderTarget);
2401+
const nativeTexture = texture._hardwareTexture!.underlyingResource;
2402+
if (target !== Constants.TEXTURE_2D) {
2403+
// Native multi render targets currently only attach 2D color textures; cube / 2D-array
2404+
// attachments are created as 2D so the attachment still exists and mrt.textures[i] is populated.
2405+
Logger.Warn("Multi render target attachment target " + target + " is not supported on Native; using a 2D texture.");
2406+
}
2407+
// bgfx auto-generates the mip chain on resolve; avoid the mips + MSAA combo (see createRenderTargetTexture).
2408+
const hasMips = samples > 1 ? false : generateMipMaps;
2409+
this._engine.initializeTexture(nativeTexture, width, height, hasMips, getNativeTextureFormat(format, type), /*renderTarget*/ true, /*srgb*/ false, samples);
2410+
this._setTextureSampling(nativeTexture, getNativeSamplingMode(samplingMode));
2411+
2412+
texture.baseWidth = width;
2413+
texture.baseHeight = height;
2414+
texture.width = width;
2415+
texture.height = height;
2416+
texture.isReady = true;
2417+
texture.samples = samples;
2418+
texture.generateMipMaps = generateMipMaps;
2419+
texture.samplingMode = samplingMode;
2420+
texture.type = type;
2421+
texture.format = format;
2422+
texture.label = labels[i] ?? rtWrapper.label + "-Texture" + i;
2423+
2424+
textures[i] = texture;
2425+
colorHandles.push(nativeTexture);
2426+
this._internalTexturesCache.push(texture);
2427+
}
2428+
2429+
// The native engine creates one framebuffer with all the color attachments (bgfx writes to every
2430+
// attachment of the bound framebuffer, so there is no drawBuffers equivalent to issue per draw).
2431+
if (colorHandles.length > 0) {
2432+
rtWrapper._framebuffer = this._engine.createMultiFrameBuffer(colorHandles, width, height, generateStencilBuffer, generateDepthBuffer || generateDepthTexture, samples);
2433+
}
2434+
2435+
rtWrapper._generateDepthBuffer = generateDepthBuffer || generateDepthTexture;
2436+
rtWrapper._generateStencilBuffer = generateStencilBuffer;
2437+
rtWrapper._samples = samples;
2438+
rtWrapper._attachments = attachments;
2439+
2440+
rtWrapper.setTextures(textures);
2441+
rtWrapper.setLayerAndFaceIndices(layerIndex, faceIndex);
2442+
2443+
return rtWrapper;
2444+
}
2445+
2446+
public override bindAttachments(_attachments: number[]): void {
2447+
// No-op on Native: bgfx renders to every color attachment of the bound framebuffer, so there is
2448+
// no gl.drawBuffers equivalent to select a subset.
2449+
}
2450+
2451+
public override buildTextureLayout(textureStatus: boolean[], _backBufferLayout = false): number[] {
2452+
// Native has no gl draw-buffer enums; return a per-attachment index list (consumers only use the
2453+
// length/order, and bindAttachments is a no-op).
2454+
const result: number[] = [];
2455+
for (let i = 0; i < textureStatus.length; i++) {
2456+
result.push(textureStatus[i] ? i : -1);
2457+
}
2458+
return result;
2459+
}
2460+
2461+
public override restoreSingleAttachment(): void {
2462+
// No-op on Native (see bindAttachments).
2463+
}
2464+
2465+
public override restoreSingleAttachmentForRenderTarget(): void {
2466+
// No-op on Native (see bindAttachments).
2467+
}
2468+
2469+
public override generateMipMapsMultiFramebuffer(_texture: RenderTargetWrapper): void {
2470+
// No-op on Native: bgfx auto-generates mips on render-target resolve (as for 2D/cube RTTs).
2471+
}
2472+
2473+
public override resolveMultiFramebuffer(_texture: RenderTargetWrapper): void {
2474+
// No-op on Native: bgfx resolves MSAA render targets automatically.
2475+
}
2476+
2477+
public override unBindMultiColorAttachmentFramebuffer(_rtWrapper: RenderTargetWrapper, _disableGenerateMipMaps = false, onBeforeUnbind?: () => void): void {
2478+
this._currentRenderTarget = null;
2479+
if (onBeforeUnbind) {
2480+
onBeforeUnbind();
2481+
}
2482+
this._bindUnboundFramebuffer(null);
2483+
}
2484+
2485+
public override updateMultipleRenderTargetTextureSampleCount(rtWrapper: Nullable<RenderTargetWrapper>, samples: number, _initializeBuffers = true): number {
2486+
// Native MSAA is configured at texture creation; just record the requested sample count.
2487+
if (rtWrapper) {
2488+
(rtWrapper as NativeRenderTargetWrapper)._samples = samples;
2489+
}
2490+
return samples;
2491+
}
2492+
23202493
public override updateRenderTargetTextureSampleCount(rtWrapper: RenderTargetWrapper, samples: number): number {
23212494
if (rtWrapper.samples === samples) {
23222495
return samples;

0 commit comments

Comments
 (0)