Skip to content

Commit 5e7e85e

Browse files
bkaradzicCopilot
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 4ff061b commit 5e7e85e

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
@@ -93,6 +93,14 @@ export interface INativeEngine {
9393
generateDepthBuffer: boolean,
9494
samples: number
9595
): NativeFramebuffer;
96+
createMultiFrameBuffer(
97+
textures: NativeTexture[],
98+
width: number,
99+
height: number,
100+
generateStencilBuffer: boolean,
101+
generateDepthBuffer: boolean,
102+
samples: number
103+
): NativeFramebuffer;
96104

97105
getRenderWidth(): number;
98106
getRenderHeight(): number;
@@ -261,6 +269,8 @@ interface INativeEngineConstructor {
261269
readonly ALPHA_MULTIPLY: number;
262270
readonly ALPHA_MAXIMIZED: number;
263271
readonly ALPHA_ONEONE: number;
272+
readonly ALPHA_ONEONE_ONEONE: number;
273+
readonly ALPHA_LAYER_ACCUMULATE: number;
264274
readonly ALPHA_PREMULTIPLIED: number;
265275
readonly ALPHA_PREMULTIPLIED_PORTERDUFF: number;
266276
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
@@ -18,6 +18,7 @@ import {
1818
type InternalTextureCreationOptions,
1919
} from "../Materials/Textures/textureCreationOptions";
2020
import { type IPipelineContext } from "./IPipelineContext";
21+
import { type IMultiRenderTargetOptions } from "../Materials/Textures/multiRenderTarget.pure";
2122
import { type IColor3Like, type IColor4Like, type IViewportLike } from "../Maths/math.like";
2223
import { Logger } from "../Misc/logger";
2324
import { Constants } from "./constants";
@@ -330,7 +331,7 @@ export class ThinNativeEngine extends ThinEngine {
330331
textureHalfFloatRender: true,
331332
textureLOD: true,
332333
texelFetch: false,
333-
drawBuffersExtension: false,
334+
drawBuffersExtension: true,
334335
depthTextureExtension: false,
335336
vertexArrayObject: true,
336337
instancedArrays: true,
@@ -556,8 +557,11 @@ export class ThinNativeEngine extends ThinEngine {
556557
}
557558

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

563567
this._commandBufferEncoder.startEncodingCommand(_native.Engine.COMMAND_CLEAR);
@@ -567,7 +571,7 @@ export class ThinNativeEngine extends ThinEngine {
567571
this._commandBufferEncoder.encodeCommandArgAsFloat32(color ? color.b : 0);
568572
this._commandBufferEncoder.encodeCommandArgAsFloat32(color ? color.a : 1);
569573
this._commandBufferEncoder.encodeCommandArgAsUInt32(depth ? 1 : 0);
570-
this._commandBufferEncoder.encodeCommandArgAsFloat32(1);
574+
this._commandBufferEncoder.encodeCommandArgAsFloat32(depth && this.useReverseDepthBuffer ? 0 : 1);
571575
this._commandBufferEncoder.encodeCommandArgAsUInt32(stencil ? 1 : 0);
572576
this._commandBufferEncoder.encodeCommandArgAsUInt32(stencilClearValue);
573577
this._commandBufferEncoder.finishEncodingCommand();
@@ -1060,6 +1064,20 @@ export class ThinNativeEngine extends ThinEngine {
10601064
this._commandBufferEncoder.finishEncodingCommand();
10611065
}
10621066

1067+
public override applyStates(): void {
1068+
// The base ThinEngine.applyStates() drives the WebGL context (this._gl) directly, which is null on
1069+
// Native. Flush the depth-culling state through the native command path instead, so callers that
1070+
// mutate engine.depthCullingState directly and then call applyStates() (e.g. the depth-peeling / OIT
1071+
// renderer) take effect. Alpha and stencil state are applied on Native via their dedicated setters
1072+
// (setAlphaMode / setStencil*), which encode their commands when called.
1073+
const depthCullingState = this._depthCullingState;
1074+
if (depthCullingState.depthFunc !== null && depthCullingState.depthFunc !== undefined) {
1075+
this.setDepthFunction(depthCullingState.depthFunc);
1076+
}
1077+
this.setDepthBuffer(depthCullingState.depthTest);
1078+
this.setDepthWrite(depthCullingState.depthMask);
1079+
}
1080+
10631081
/**
10641082
* Gets a boolean indicating if depth writing is enabled
10651083
* @returns the current depth writing state
@@ -2303,6 +2321,161 @@ export class ThinNativeEngine extends ThinEngine {
23032321
return rtWrapper;
23042322
}
23052323

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

0 commit comments

Comments
 (0)