Skip to content

Size bgfx render-item arrays dynamically instead of reserving the maximum - #1806

Open
bghgary wants to merge 5 commits into
BabylonJS:masterfrom
bghgary:bgfx-dynamic-draw-calls
Open

Size bgfx render-item arrays dynamically instead of reserving the maximum#1806
bghgary wants to merge 5 commits into
BabylonJS:masterfrom
bghgary:bgfx-dynamic-draw-calls

Conversation

@bghgary

@bghgary bghgary commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

[Created by Copilot on behalf of @bghgary]

Context

bgfx sizes its per-frame render-item arrays (sort keys, RenderItem, RenderBind) from Init::Limits::numDrawCalls, which defaults to BGFX_CONFIG_MAX_DRAW_CALLS (65535) and is allocated up front for every Frame. A consumer drawing a handful of quads still reserves tens of megabytes.

bgfx can grow and shrink those arrays on demand, but it is opt-in — the defaults are numDrawCalls = BGFX_CONFIG_MAX_DRAW_CALLS and numDrawCallPeakFrames = 0 (disabled) — so picking up the newer bgfx alone changes nothing. This PR starts at 4096 slots and lets bgfx grow them.

Worth a look

  • numDrawCallPeakFrames must be non-zero: at 0, adjustCapacity() returns immediately and the arrays stay fixed.
  • Growth is reactive — the frame that first exceeds capacity discards the draws past it, and only the next frame sees the larger arrays. 4096 sits above where any realistic scene starts, so the growth path is a safety net rather than something a scene walks through on its way up.
  • Lowering BGFX_CONFIG_MAX_DRAW_CALLS instead would shrink the allocation but lower the ceiling with it, and submissions past the limit are dropped rather than queued (m_maxDrawCalls <= renderItemIdx discards the draw and counts it in m_numDropped). Geometry disappears with nothing raised to say so.

Measurements

At Babylon Native's configuration the slot is 522 bytes, so the 65536 reserved slots cost 65 MB across the submit and render Frame. 4096 slots cost 4 MB, saving about 61 MB.

UnitTests pass (17/17).

…imum

bgfx sizes its per-frame render-item arrays (sort keys, RenderItem, RenderBind)
from Init::Limits::numDrawCalls. That defaults to BGFX_CONFIG_MAX_DRAW_CALLS,
65535, and is allocated up front for every Frame object, so a consumer that
draws a handful of quads still reserves tens of megabytes.

bgfx gained the ability to grow and shrink those arrays on demand, but it is
opt-in: the defaults remain numDrawCalls = BGFX_CONFIG_MAX_DRAW_CALLS and
numDrawCallPeakFrames = 0 (disabled), so simply picking up the newer bgfx
changes nothing.

Opt in by starting empty and letting bgfx grow up to the same
BGFX_CONFIG_MAX_DRAW_CALLS ceiling, in BGFX_CONFIG_DRAW_CALL_BLOCK-sized steps.
The headroom is unchanged for scenes that need it; it is just no longer paid for
when unused.

Lowering BGFX_CONFIG_MAX_DRAW_CALLS instead would also shrink the allocation,
but it lowers the ceiling with it. Submissions past the limit are dropped rather
than queued -- Frame::m_maxDrawCalls <= renderItemIdx discards the draw and
counts it in m_numDropped -- so geometry disappears with nothing raised to say
so. Growing on demand keeps the ceiling and the memory.

Measured in a video-compositing embedder rendering 49 tiles: peak usage is 50
draw calls, and this change accounts for roughly 73 MB of a 111 MB reduction in
process memory. No change to rendering output; UnitTests pass (17/17).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 714b4495-258e-4645-abf7-26c17bc29d5b
Copilot AI review requested due to automatic review settings July 30, 2026 23:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates BabylonNative’s bgfx initialization to avoid reserving bgfx’s maximum per-frame render-item buffers up front, instead enabling bgfx’s dynamic resizing behavior to reduce baseline memory usage while preserving the same upper ceiling for draw-call-heavy scenes.

Changes:

  • Set bgfx::Init::Limits::numDrawCalls to 0 to avoid eager preallocation at BGFX_CONFIG_MAX_DRAW_CALLS.
  • Set bgfx::Init::Limits::numDrawCallPeakFrames to a non-zero value to enable runtime growth/shrink behavior.
  • Add in-code rationale describing bgfx’s allocation behavior and why these settings are chosen.

Comment thread Core/Graphics/Source/DeviceImpl.cpp Outdated
bghgary and others added 4 commits July 30, 2026 16:34
bgfx::init clamps limits.numDrawCalls up to kDrawCallBlock
(BGFX_CONFIG_DRAW_CALL_BLOCK, 1024 by default):

    init.limits.numDrawCalls = alignDrawCalls(
        bx::max(init.limits.numDrawCalls, kDrawCallBlock) );

so 0 asks for one block, not for an empty allocation. Frame::create ->
allocArrays applies no floor of its own, which is what the previous comment
described, but the clamp happens a layer above it in bgfx::init.

The behaviour and the memory saving are unchanged: the reserved slot count
still drops from BGFX_CONFIG_MAX_DRAW_CALLS to one block. Only the comment
was wrong.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 714b4495-258e-4645-abf7-26c17bc29d5b
Requesting the smallest capacity bgfx allows meant any scene crossing one
BGFX_CONFIG_DRAW_CALL_BLOCK (1024) lost the draws past the boundary for a
frame, because bgfx grows reactively: the frame that overflows discards the
excess and only the next frame sees the larger arrays. Babylon Native never
dropped draws before this change, so that was a regression.

Start at 4096 instead, which no realistic scene begins above, and expose the
value as Configuration::InitialDrawCallCapacity so an embedder that knows its
own ceiling can go lower. Paired with an override of BGFX_CONFIG_DRAW_CALL_BLOCK
that lets a small embedder reserve well under a block.

Reserved slots still drop from BGFX_CONFIG_MAX_DRAW_CALLS (65535) to 4096, so
the bulk of the saving is unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 714b4495-258e-4645-abf7-26c17bc29d5b
Replaces the Configuration::InitialDrawCallCapacity field added in the previous
commit. bgfx already has a compile-time setting for this: numDrawCalls = 0 asks
for exactly one BGFX_CONFIG_DRAW_CALL_BLOCK, and the block is also the growth,
shrink and floor granularity, so it is the only value a consumer needs to set.
A runtime field alongside it was a second way to say the same thing.

Set the block to 4096 for Babylon Native. Growth is reactive -- the frame that
first exceeds capacity drops the draws past it -- so leaving the block at bgfx's
default of 1024 would have cost a frame of geometry on any scene crossing 1024
draws. A consumer that knows its own ceiling can override the block down.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 714b4495-258e-4645-abf7-26c17bc29d5b
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 714b4495-258e-4645-abf7-26c17bc29d5b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants