From db75d2e8ac3e64078a5a6a363b16ec7d9e632e3a Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Thu, 11 Jun 2026 10:45:05 -0700 Subject: [PATCH] NativeEngine: MultiRenderTarget framebuffers + OIT alpha blend modes Foundational native-engine support for MultiRenderTarget (MRT) and the order- independent-transparency blend modes, removing several "engine._gl is null" crash classes. It does not by itself land the MRT/OIT/FrameGraph validation tests, which need further work (see follow-ups below). - Add NativeEngine::CreateMultiFrameBuffer: build one bgfx framebuffer with N color attachments (+ optional depth) so a MultiRenderTarget renders to all targets at once (bgfx writes every attachment of the bound framebuffer, so no drawBuffers is needed). JS-controlled attachment count is validated against caps->limits.maxFBAttachments. - Add alpha blend modes ALPHA_ONEONE_ONEONE (11) and ALPHA_LAYER_ACCUMULATE (17) used by the depth-peeling OIT renderer. Pairs with the Babylon.js change (createMultipleRenderTarget + MRT helper overrides, applyStates, reverse-Z clear). Known follow-ups: the OIT depth- peeling path still faults inside the D3D11 driver on submit (needs interactive GPU debugging), and the blend equation (MAX) is not yet applied natively. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Plugins/NativeEngine/Source/NativeEngine.cpp | 65 ++++++++++++++++++++ Plugins/NativeEngine/Source/NativeEngine.h | 1 + 2 files changed, 66 insertions(+) diff --git a/Plugins/NativeEngine/Source/NativeEngine.cpp b/Plugins/NativeEngine/Source/NativeEngine.cpp index 864031710..460ab1969 100644 --- a/Plugins/NativeEngine/Source/NativeEngine.cpp +++ b/Plugins/NativeEngine/Source/NativeEngine.cpp @@ -77,6 +77,8 @@ namespace Babylon 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); 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); 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); + 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); + 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); 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); 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); 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 StaticValue("ALPHA_MULTIPLY", Napi::Number::From(env, AlphaMode::MULTIPLY)), StaticValue("ALPHA_MAXIMIZED", Napi::Number::From(env, AlphaMode::MAXIMIZED)), StaticValue("ALPHA_ONEONE", Napi::Number::From(env, AlphaMode::ONEONE)), + StaticValue("ALPHA_ONEONE_ONEONE", Napi::Number::From(env, AlphaMode::ONEONE_ONEONE)), + StaticValue("ALPHA_LAYER_ACCUMULATE", Napi::Number::From(env, AlphaMode::LAYER_ACCUMULATE)), StaticValue("ALPHA_PREMULTIPLIED", Napi::Number::From(env, AlphaMode::PREMULTIPLIED)), StaticValue("ALPHA_PREMULTIPLIED_PORTERDUFF", Napi::Number::From(env, AlphaMode::PREMULTIPLIED_PORTERDUFF)), StaticValue("ALPHA_INTERPOLATE", Napi::Number::From(env, AlphaMode::INTERPOLATE)), @@ -726,6 +730,7 @@ namespace Babylon InstanceMethod("resizeImageBitmap", &NativeEngine::ResizeImageBitmap), InstanceMethod("createFrameBuffer", &NativeEngine::CreateFrameBuffer), + InstanceMethod("createMultiFrameBuffer", &NativeEngine::CreateMultiFrameBuffer), InstanceMethod("getRenderWidth", &NativeEngine::GetRenderWidth), InstanceMethod("getRenderHeight", &NativeEngine::GetRenderHeight), @@ -1912,6 +1917,66 @@ namespace Babylon return Napi::Pointer::Create(info.Env(), frameBuffer, Napi::NapiPointerDeleter(frameBuffer)); } + Napi::Value NativeEngine::CreateMultiFrameBuffer(const Napi::CallbackInfo& info) + { + const auto colorTextures = info[0].As(); + const uint16_t width = static_cast(info[1].As().Uint32Value()); + const uint16_t height = static_cast(info[2].As().Uint32Value()); + const bool generateStencilBuffer = info[3].As(); + const bool generateDepth = info[4].As(); + const uint32_t samples = info[5].IsUndefined() ? 1 : info[5].As().Uint32Value(); + + const bgfx::Caps* caps = bgfx::getCaps(); + const uint32_t colorCount = colorTextures.Length(); + // One slot per color attachment plus a single depth/stencil attachment. bgfx caps the total via + // maxFBAttachments; reject out-of-range counts up front rather than relying on the validation assert. + if (colorCount == 0 || colorCount + 1 > caps->limits.maxFBAttachments) + { + throw Napi::Error::New(info.Env(), "Invalid number of color attachments for multi render target frame buffer"); + } + + // BGFX_CONFIG_MAX_FRAME_BUFFER_ATTACHMENTS is 8, so 8 color + 1 depth fits in a fixed array. + std::array attachments{}; + uint8_t numAttachments = 0; + + for (uint32_t i = 0; i < colorCount; ++i) + { + const auto texture = colorTextures.Get(i).As>().Get(); + attachments[numAttachments++].init(texture->Handle(), bgfx::Access::Write, 0, 1, 0 + , 0 != (caps->formats[texture->Format()] & BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN) ? BGFX_RESOLVE_AUTO_GEN_MIPS : BGFX_RESOLVE_NONE + ); + } + + bgfx::TextureHandle depthStencilTextureHandle = BGFX_INVALID_HANDLE; + int8_t depthStencilAttachmentIndex = -1; + if (generateStencilBuffer || generateDepth) + { + auto flags = BGFX_TEXTURE_RT_WRITE_ONLY | RenderTargetSamplesToBgfxMsaaFlag(samples); +#ifdef ANDROID + const auto depthStencilFormat{bgfx::TextureFormat::D24S8}; +#else + const auto depthStencilFormat{generateStencilBuffer ? bgfx::TextureFormat::D24S8 : bgfx::TextureFormat::D32}; +#endif + depthStencilTextureHandle = bgfx::createTexture2D(width, height, false, 1, depthStencilFormat, flags); + depthStencilAttachmentIndex = numAttachments; + attachments[numAttachments++].init(depthStencilTextureHandle, bgfx::Access::Write, 0, 1, 0, BGFX_RESOLVE_NONE); + } + + bgfx::FrameBufferHandle frameBufferHandle = bgfx::createFrameBuffer(numAttachments, attachments.data()); + if (!bgfx::isValid(frameBufferHandle)) + { + if (bgfx::isValid(depthStencilTextureHandle)) + { + bgfx::destroy(depthStencilTextureHandle); + } + + throw Napi::Error::New(info.Env(), "Failed to create multi render target frame buffer"); + } + + Graphics::FrameBuffer* frameBuffer = new Graphics::FrameBuffer(m_deviceContext, frameBufferHandle, width, height, false, generateDepth, generateStencilBuffer, depthStencilAttachmentIndex); + return Napi::Pointer::Create(info.Env(), frameBuffer, Napi::NapiPointerDeleter(frameBuffer)); + } + // TODO: This doesn't get called when an Engine instance is disposed. void NativeEngine::DeleteFrameBuffer(NativeDataStream::Reader& data) { diff --git a/Plugins/NativeEngine/Source/NativeEngine.h b/Plugins/NativeEngine/Source/NativeEngine.h index 229ef0e3a..4c24ffe76 100644 --- a/Plugins/NativeEngine/Source/NativeEngine.h +++ b/Plugins/NativeEngine/Source/NativeEngine.h @@ -115,6 +115,7 @@ namespace Babylon void DeleteTexture(const Napi::CallbackInfo& info); Napi::Value ReadTexture(const Napi::CallbackInfo& info); Napi::Value CreateFrameBuffer(const Napi::CallbackInfo& info); + Napi::Value CreateMultiFrameBuffer(const Napi::CallbackInfo& info); void DeleteFrameBuffer(NativeDataStream::Reader& data); void BindFrameBuffer(NativeDataStream::Reader& data); void UnbindFrameBuffer(NativeDataStream::Reader& data);