Skip to content

Commit 2b4e16d

Browse files
bkaradzic-microsoftbkaradzicCopilot
authored
NativeEngine: MultiRenderTarget framebuffers + OIT alpha blend modes (#1754)
Paired engine PR: BabylonJS/Babylon.js#18568 ## What Foundational native support for `MultiRenderTarget` (MRT) and the order-independent-transparency blend modes, removing several "`engine._gl` is null" crash classes. It does **not** by itself turn the MRT/OIT/FrameGraph validation tests green — those need further work (see Follow-ups). ## Changes - **`Plugins/NativeEngine/Source/NativeEngine.cpp` / `.h`** - `CreateFrameBuffer` and `CreateMultiFrameBuffer` now share a private `CreateFrameBufferImpl(env, colorTextures, …)` helper that builds one bgfx framebuffer with N color attachments (+ an optional depth/stencil attachment), so a `MultiRenderTarget` renders to all targets at once (bgfx writes every attachment of the bound framebuffer — no `drawBuffers` needed). The two N-API methods are kept for protocol compatibility and only differ in how they parse `info[0]` (single nullable texture vs. array); the single-RT path is just the 0-or-1-color case. The shared helper validates the attachment count against `caps->limits.maxFBAttachments` and keeps the `"Stencil without depth…"` warning and the `isTextureValid` assert. - Add alpha blend modes `ALPHA_ONEONE_ONEONE` (11) and `ALPHA_LAYER_ACCUMULATE` (17) used by the depth-peeling OIT renderer. ## Paired engine PR Pairs with the Babylon.js change (`createMultipleRenderTarget` + MRT helper overrides, `applyStates` override, reverse-Z `clear`, alpha-mode mapping). That PR feature-detects both `createMultiFrameBuffer` and the two alpha constants, so it degrades gracefully on native binaries that predate this PR. ## CI The validation suite uses the published `babylonjs` npm, which doesn't yet contain the paired JS, so no new tests are enabled here and CI stays in the usual "pending dep bump" state. Verified locally against a `babylon.max.js` built from the paired branch, and the plugin compiles clean (`cmake --build build/win32 --target NativeEngine --config RelWithDebInfo`). ## Follow-ups (separate work) - OIT depth-peeling still faults inside the D3D11 driver on `submit` (multi-output / SRV↔RTV ping-pong); needs interactive GPU debugging (PIX / VS Graphics Debugger). - Native does not yet apply the blend **equation** (`MAX`) — `setAlphaEquation` is a no-op — so OIT blending would still be incorrect after the crash is fixed. - 2D-array / cube color attachments for MRT are approximated as 2D (test "MRT with different texture types"). --- ## Related PRs & landing order - **Babylon.js (engine / TS):** BabylonJS/Babylon.js#18568 - **BabylonNative (C++ engine):** #1754 These two are co-dependent and land in this order: 1. **Babylon.js #18568 first** — it only adds native-engine TS overrides, changes no WebGL behavior, re-enables no validation tests on its own, and feature-detects this PR's additions, so it can merge independently. 2. A **`babylonjs` npm release** ships that TS change. 3. **BabylonNative #1754 last** — after bumping the bundled `babylonjs` to that release. (This foundational pair does not yet turn the MRT/OIT/FrameGraph tests green — that is separate follow-up work.) --------- Co-authored-by: Branimir Karadzic <branimirkaradzic@gmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent fbc2c82 commit 2b4e16d

2 files changed

Lines changed: 63 additions & 6 deletions

File tree

‎Plugins/NativeEngine/Source/NativeEngine.cpp‎

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ namespace Babylon
7777
constexpr uint64_t MULTIPLY = BGFX_STATE_BLEND_FUNC_SEPARATE(BGFX_STATE_BLEND_DST_COLOR, BGFX_STATE_BLEND_ZERO, BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_ONE);
7878
constexpr uint64_t MAXIMIZED = BGFX_STATE_BLEND_FUNC_SEPARATE(BGFX_STATE_BLEND_SRC_ALPHA, BGFX_STATE_BLEND_INV_SRC_COLOR, BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_ONE);
7979
constexpr uint64_t ONEONE = BGFX_STATE_BLEND_FUNC_SEPARATE(BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_ZERO, BGFX_STATE_BLEND_ONE);
80+
constexpr uint64_t ONEONE_ONEONE = BGFX_STATE_BLEND_FUNC_SEPARATE(BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_ONE);
81+
constexpr uint64_t LAYER_ACCUMULATE = BGFX_STATE_BLEND_FUNC_SEPARATE(BGFX_STATE_BLEND_SRC_ALPHA, BGFX_STATE_BLEND_INV_SRC_ALPHA, BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_INV_SRC_ALPHA);
8082
constexpr uint64_t PREMULTIPLIED = BGFX_STATE_BLEND_FUNC_SEPARATE(BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_INV_SRC_ALPHA, BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_ONE);
8183
constexpr uint64_t PREMULTIPLIED_PORTERDUFF = BGFX_STATE_BLEND_FUNC_SEPARATE(BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_INV_SRC_ALPHA, BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_INV_SRC_ALPHA);
8284
constexpr uint64_t INTERPOLATE = BGFX_STATE_BLEND_FUNC_SEPARATE(BGFX_STATE_BLEND_FACTOR, BGFX_STATE_BLEND_INV_FACTOR, BGFX_STATE_BLEND_FACTOR, BGFX_STATE_BLEND_INV_FACTOR);
@@ -598,6 +600,8 @@ namespace Babylon
598600
StaticValue("ALPHA_MULTIPLY", Napi::Number::From(env, AlphaMode::MULTIPLY)),
599601
StaticValue("ALPHA_MAXIMIZED", Napi::Number::From(env, AlphaMode::MAXIMIZED)),
600602
StaticValue("ALPHA_ONEONE", Napi::Number::From(env, AlphaMode::ONEONE)),
603+
StaticValue("ALPHA_ONEONE_ONEONE", Napi::Number::From(env, AlphaMode::ONEONE_ONEONE)),
604+
StaticValue("ALPHA_LAYER_ACCUMULATE", Napi::Number::From(env, AlphaMode::LAYER_ACCUMULATE)),
601605
StaticValue("ALPHA_PREMULTIPLIED", Napi::Number::From(env, AlphaMode::PREMULTIPLIED)),
602606
StaticValue("ALPHA_PREMULTIPLIED_PORTERDUFF", Napi::Number::From(env, AlphaMode::PREMULTIPLIED_PORTERDUFF)),
603607
StaticValue("ALPHA_INTERPOLATE", Napi::Number::From(env, AlphaMode::INTERPOLATE)),
@@ -726,6 +730,7 @@ namespace Babylon
726730
InstanceMethod("resizeImageBitmap", &NativeEngine::ResizeImageBitmap),
727731

728732
InstanceMethod("createFrameBuffer", &NativeEngine::CreateFrameBuffer),
733+
InstanceMethod("createMultiFrameBuffer", &NativeEngine::CreateMultiFrameBuffer),
729734

730735
InstanceMethod("getRenderWidth", &NativeEngine::GetRenderWidth),
731736
InstanceMethod("getRenderHeight", &NativeEngine::GetRenderHeight),
@@ -1874,12 +1879,57 @@ namespace Babylon
18741879
const bool generateDepth = info[4].As<Napi::Boolean>();
18751880
const uint32_t samples = info[5].IsUndefined() ? 1 : info[5].As<Napi::Number>().Uint32Value();
18761881

1877-
std::array<bgfx::Attachment, 2> attachments{};
1882+
// A single render target is just the zero-or-one color attachment case of the shared implementation.
1883+
Graphics::Texture* const colorTextures[]{texture};
1884+
const gsl::span<Graphics::Texture* const> colorAttachments{colorTextures, texture != nullptr ? 1u : 0u};
1885+
1886+
return CreateFrameBufferImpl(info.Env(), colorAttachments, width, height, generateStencilBuffer, generateDepth, samples);
1887+
}
1888+
1889+
Napi::Value NativeEngine::CreateMultiFrameBuffer(const Napi::CallbackInfo& info)
1890+
{
1891+
const auto colorTexturesArray = info[0].As<Napi::Array>();
1892+
const uint16_t width = static_cast<uint16_t>(info[1].As<Napi::Number>().Uint32Value());
1893+
const uint16_t height = static_cast<uint16_t>(info[2].As<Napi::Number>().Uint32Value());
1894+
const bool generateStencilBuffer = info[3].As<Napi::Boolean>();
1895+
const bool generateDepth = info[4].As<Napi::Boolean>();
1896+
const uint32_t samples = info[5].IsUndefined() ? 1 : info[5].As<Napi::Number>().Uint32Value();
1897+
1898+
const uint32_t colorCount = colorTexturesArray.Length();
1899+
// BGFX_CONFIG_MAX_FRAME_BUFFER_ATTACHMENTS is 8, so at most 8 color attachments fit alongside the depth attachment.
1900+
std::array<Graphics::Texture*, 8> colorTextures{};
1901+
if (colorCount > colorTextures.size())
1902+
{
1903+
throw Napi::Error::New(info.Env(), "Invalid number of color attachments for multi render target frame buffer");
1904+
}
1905+
1906+
for (uint32_t i = 0; i < colorCount; ++i)
1907+
{
1908+
colorTextures[i] = colorTexturesArray.Get(i).As<Napi::Pointer<Graphics::Texture>>().Get();
1909+
}
1910+
1911+
return CreateFrameBufferImpl(info.Env(), gsl::span<Graphics::Texture* const>{colorTextures.data(), colorCount}, width, height, generateStencilBuffer, generateDepth, samples);
1912+
}
1913+
1914+
Napi::Value NativeEngine::CreateFrameBufferImpl(Napi::Env env, gsl::span<Graphics::Texture* const> colorTextures, uint16_t width, uint16_t height, bool generateStencilBuffer, bool generateDepth, uint32_t samples)
1915+
{
1916+
const bgfx::Caps* caps = bgfx::getCaps();
1917+
const uint32_t colorCount = static_cast<uint32_t>(colorTextures.size());
1918+
// One slot per color attachment, plus a single depth/stencil attachment only when one is
1919+
// generated. bgfx caps the total via maxFBAttachments; reject out-of-range counts up front
1920+
// rather than relying on the validation assert.
1921+
const uint32_t depthStencilCount = (generateStencilBuffer || generateDepth) ? 1u : 0u;
1922+
if (colorCount + depthStencilCount > caps->limits.maxFBAttachments)
1923+
{
1924+
throw Napi::Error::New(env, "Invalid number of color attachments for frame buffer");
1925+
}
1926+
1927+
// BGFX_CONFIG_MAX_FRAME_BUFFER_ATTACHMENTS is 8, so 8 color + 1 depth fits in a fixed array.
1928+
std::array<bgfx::Attachment, 9> attachments{};
18781929
uint8_t numAttachments = 0;
18791930

1880-
if (texture != nullptr)
1931+
for (Graphics::Texture* texture : colorTextures)
18811932
{
1882-
const bgfx::Caps* caps = bgfx::getCaps();
18831933
// bgfx validation now asserts when trying to use BGFX_RESOLVE_AUTO_GEN_MIPS with a texture that doesn't have the BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN flag,
18841934
// but before it would just ignore the flag and not generate mips without any warning. This prevents validation assert, but rendering might be broken if autogen
18851935
// mips were expected. Basically this change preserves previous behavior.
@@ -1894,7 +1944,7 @@ namespace Babylon
18941944
{
18951945
if (generateStencilBuffer && !generateDepth)
18961946
{
1897-
JsConsoleLogger::LogWarn(info.Env(), "Stencil without depth is not supported, assuming depth and stencil");
1947+
JsConsoleLogger::LogWarn(env, "Stencil without depth is not supported, assuming depth and stencil");
18981948
}
18991949

19001950
auto flags = BGFX_TEXTURE_RT_WRITE_ONLY | RenderTargetSamplesToBgfxMsaaFlag(samples);
@@ -1926,11 +1976,11 @@ namespace Babylon
19261976
bgfx::destroy(depthStencilTextureHandle);
19271977
}
19281978

1929-
throw Napi::Error::New(info.Env(), "Failed to create frame buffer");
1979+
throw Napi::Error::New(env, "Failed to create frame buffer");
19301980
}
19311981

19321982
Graphics::FrameBuffer* frameBuffer = new Graphics::FrameBuffer(m_deviceContext, frameBufferHandle, width, height, false, generateDepth, generateStencilBuffer, depthStencilAttachmentIndex);
1933-
return Napi::Pointer<Graphics::FrameBuffer>::Create(info.Env(), frameBuffer, Napi::NapiPointerDeleter(frameBuffer));
1983+
return Napi::Pointer<Graphics::FrameBuffer>::Create(env, frameBuffer, Napi::NapiPointerDeleter(frameBuffer));
19341984
}
19351985

19361986
// TODO: This doesn't get called when an Engine instance is disposed.

‎Plugins/NativeEngine/Source/NativeEngine.h‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232

3333
namespace Babylon
3434
{
35+
namespace Graphics
36+
{
37+
class Texture;
38+
}
39+
3540
class NativeEngine final : public Napi::ObjectWrap<NativeEngine>
3641
{
3742
static constexpr auto JS_CLASS_NAME = "_NativeEngine";
@@ -114,6 +119,8 @@ namespace Babylon
114119
void DeleteTexture(const Napi::CallbackInfo& info);
115120
Napi::Value ReadTexture(const Napi::CallbackInfo& info);
116121
Napi::Value CreateFrameBuffer(const Napi::CallbackInfo& info);
122+
Napi::Value CreateMultiFrameBuffer(const Napi::CallbackInfo& info);
123+
Napi::Value CreateFrameBufferImpl(Napi::Env env, gsl::span<Graphics::Texture* const> colorTextures, uint16_t width, uint16_t height, bool generateStencilBuffer, bool generateDepth, uint32_t samples);
117124
void DeleteFrameBuffer(NativeDataStream::Reader& data);
118125
void BindFrameBuffer(NativeDataStream::Reader& data);
119126
void UnbindFrameBuffer(NativeDataStream::Reader& data);

0 commit comments

Comments
 (0)