Skip to content

Commit 81a7bfe

Browse files
bkaradzic-microsoftBranimir Karadzic
andauthored
Add programmatic GPU frame capture via TestUtils.captureNextFrame. (#1675)
Introduces a one-shot, cross-backend API for requesting a GPU debugger capture of the next submitted frame. Wraps bgfx's `BGFX_FRAME_DEBUG_CAPTURE` flag, so the same call works with Xcode Metal capture on macOS/iOS, PIX or RenderDoc on Windows, and RenderDoc on Linux/Android. Primary use case: producing a .gputrace for a failing validation test without having to manually time a capture in the IDE. Add "capture": true to a test entry in Apps/Playground/Scripts/config.json and the test harness will request a capture on the screenshot frame; with the platform GPU debugger attached, a capture document opens automatically. Implementation: - DeviceImpl owns an `std::atomic<bool> m_captureNextFrame`. `Frame()` consumes it via exchange(false) and ORs `BGFX_FRAME_DEBUG_CAPTURE` into the `bgfx::frame()` flags, guaranteeing the request is applied to exactly one frame even under concurrent JS-thread calls. - DeviceContext exposes `RequestCaptureNextFrame()`as a thin forwarder, extending the existing public surface alongside RequestScreenShot. - TestUtils plugin adds a cross-platform `captureNextFrame()` JS method (no per-platform .mm/.cpp variants required). validation_native.js honors an optional per-test "capture" flag and arms a capture before the final `currentScene.render()` of the screenshot frame. When no GPU debugger is attached the bgfx flag is a no-op, so the API is safe to leave in JS that ships in release builds. Co-authored-by: Branimir Karadzic <branimirkaradzic@Bgmail.com>
1 parent d63e9fb commit 81a7bfe

7 files changed

Lines changed: 27 additions & 1 deletion

File tree

Apps/Playground/Scripts/validation_native.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@
118118
}
119119
engine.runRenderLoop(function () {
120120
try {
121+
if (test.capture && renderCount === 1 && TestUtils.captureNextFrame) {
122+
TestUtils.captureNextFrame();
123+
}
121124
currentScene.render();
122125
renderCount--;
123126

Core/Graphics/InternalInclude/Babylon/Graphics/DeviceContext.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ namespace Babylon::Graphics
9696
Update GetUpdate(const char* updateName);
9797

9898
void RequestScreenShot(std::function<void(std::vector<uint8_t>)> callback);
99+
void RequestCaptureNextFrame();
99100
void SetRenderResetCallback(std::function<void()> callback);
100101

101102
arcana::task<void, std::exception_ptr> ReadTextureAsync(bgfx::TextureHandle handle, gsl::span<uint8_t> data, uint8_t mipLevel = 0);

Core/Graphics/Source/DeviceContext.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ namespace Babylon::Graphics
6161
return m_graphicsImpl.RequestScreenShot(std::move(callback));
6262
}
6363

64+
void DeviceContext::RequestCaptureNextFrame()
65+
{
66+
m_graphicsImpl.RequestCaptureNextFrame();
67+
}
68+
6469
void DeviceContext::SetRenderResetCallback(std::function<void()> callback)
6570
{
6671
return m_graphicsImpl.SetRenderResetCallback(std::move(callback));

Core/Graphics/Source/DeviceImpl.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,11 @@ namespace Babylon::Graphics
373373
m_screenShotCallbacks.push(std::move(callback));
374374
}
375375

376+
void DeviceImpl::RequestCaptureNextFrame()
377+
{
378+
m_captureNextFrame.store(true);
379+
}
380+
376381
arcana::task<void, std::exception_ptr> DeviceImpl::ReadTextureAsync(bgfx::TextureHandle handle, gsl::span<uint8_t> data, uint8_t mipLevel)
377382
{
378383
arcana::task_completion_source<void, std::exception_ptr> completionSource{};
@@ -462,7 +467,8 @@ namespace Babylon::Graphics
462467
RequestScreenShots();
463468

464469
// Advance frame and render!
465-
uint32_t frameNumber{bgfx::frame()};
470+
const uint8_t frameFlags = m_captureNextFrame.exchange(false) ? BGFX_FRAME_DEBUG_CAPTURE : 0;
471+
uint32_t frameNumber{bgfx::frame(frameFlags)};
466472

467473
// Process read texture requests.
468474
while (!m_readTextureRequests.empty() && m_readTextureRequests.front().first <= frameNumber)

Core/Graphics/Source/DeviceImpl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ namespace Babylon::Graphics
8787

8888
void RequestScreenShot(std::function<void(std::vector<uint8_t>)> callback);
8989

90+
void RequestCaptureNextFrame();
91+
9092
arcana::task<void, std::exception_ptr> ReadTextureAsync(bgfx::TextureHandle handle, gsl::span<uint8_t> data, uint8_t mipLevel);
9193

9294
using CaptureCallbackTicketT = arcana::ticketed_collection<std::function<void(const BgfxCallback::CaptureData&)>>::ticket;
@@ -123,6 +125,8 @@ namespace Babylon::Graphics
123125

124126
std::atomic<bgfx::ViewId> m_nextViewId{0};
125127

128+
std::atomic<bool> m_captureNextFrame{false};
129+
126130
std::optional<arcana::cancellation_source> m_cancellationSource{};
127131

128132
struct

Plugins/TestUtils/Source/TestUtils.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ namespace Babylon::Plugins::Internal
9191
});
9292
});
9393
}
94+
95+
void TestUtils::CaptureNextFrame(const Napi::CallbackInfo& /*info*/)
96+
{
97+
m_deviceContext.RequestCaptureNextFrame();
98+
}
9499
}
95100

96101
namespace Babylon::Plugins::TestUtils

Plugins/TestUtils/Source/TestUtils.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ namespace Babylon::Plugins::Internal
3737
ParentT::InstanceMethod("getImageData", &TestUtils::GetImageData),
3838
ParentT::InstanceMethod("getOutputDirectory", &TestUtils::GetOutputDirectory),
3939
ParentT::InstanceMethod("getFrameBufferData", &TestUtils::GetFrameBufferData),
40+
ParentT::InstanceMethod("captureNextFrame", &TestUtils::CaptureNextFrame),
4041
},
4142
&window);
4243

@@ -69,6 +70,7 @@ namespace Babylon::Plugins::Internal
6970
Napi::Value DecodeImage(const Napi::CallbackInfo& info);
7071
Napi::Value GetImageData(const Napi::CallbackInfo& info);
7172
void GetFrameBufferData(const Napi::CallbackInfo& info);
73+
void CaptureNextFrame(const Napi::CallbackInfo& info);
7274

7375
JsRuntime& m_runtime;
7476
Graphics::DeviceContext& m_deviceContext;

0 commit comments

Comments
 (0)