Skip to content

Commit a1089b8

Browse files
committed
fgviewer: visualize intermediate buffers
* Intermediate Buffer Visualization: Enabled live monitoring of internal `FrameGraph` render targets directly in the `fgviewer` web UI. * HTTP Polling Architecture: Switched from WebSocket binary pushes to native `<img>` polling (`/api/image`) * Robust Resource Tracking: Replaced string-based lookups with `(ViewId, ResourceId)` composite keys to prevent cross-view collisions and ensure accurate reads. * Format Post-Processing: Extracted readback conversions (HDR tonemapping, depth normalization, MSAA downsampling, single-channel expansion) into `DebugServer`. * UI Polish: Added a live-updating full-screen image modal and explicitly filtered internal debug passes from the Graphviz/JSON exports to prevent DOM thrashing. * WIP: Currently Unsupported: * GL backend: failed with: OpenGL framebuffer error 0x8cd6 (GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) in "readTexture" at line 4198 * Mipmaps & Subresources: Reading specific mip levels or array layers is explicitly skipped. * Shadowmaps: Variance Shadow Maps (VSM) will physically evaluate to `0.0` and appear completely empty/black in scenes without active shadow casters (due to inverted-Z). * Stencil: Resolving and reading stencil buffer data is not supported by the backend. * WIP: Untested outside of MacOS+metal/vk
1 parent a007b59 commit a1089b8

26 files changed

Lines changed: 1340 additions & 200 deletions

filament/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ set(SRCS
155155
src/ds/StructureDescriptorSet.cpp
156156
src/fg/Blackboard.cpp
157157
src/fg/DependencyGraph.cpp
158+
src/fg/FgviewerManager.cpp
158159
src/fg/FrameGraph.cpp
159160
src/fg/FrameGraphPass.cpp
160161
src/fg/FrameGraphResources.cpp

filament/backend/src/DataReshaper.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ class DataReshaper {
284284
switch (srcType) {
285285
case UBYTE: reshaper = reshapeImageImpl<float, uint8_t>; break;
286286
case FLOAT: reshaper = reshapeImageImpl<float, float>; break;
287+
case HALF: reshaper = reshapeImageImpl<float, math::half>; break;
287288
case INT: reshaper = reshapeImageImpl<float, int32_t>; break;
288289
case UINT: reshaper = reshapeImageImpl<float, uint32_t>; break;
289290
case UINT_10F_11F_11F_REV:
@@ -300,6 +301,7 @@ class DataReshaper {
300301
switch (srcType) {
301302
case UBYTE: reshaper = reshapeImageImpl<int32_t, uint8_t>; break;
302303
case FLOAT: reshaper = reshapeImageImpl<int32_t, float>; break;
304+
case HALF: reshaper = reshapeImageImpl<int32_t, math::half>; break;
303305
case INT: reshaper = reshapeImageImpl<int32_t, int32_t>; break;
304306
case UINT: reshaper = reshapeImageImpl<int32_t, uint32_t>; break;
305307
case UINT_10F_11F_11F_REV:
@@ -316,6 +318,7 @@ class DataReshaper {
316318
switch (srcType) {
317319
case UBYTE: reshaper = reshapeImageImpl<uint32_t, uint8_t>; break;
318320
case FLOAT: reshaper = reshapeImageImpl<uint32_t, float>; break;
321+
case HALF: reshaper = reshapeImageImpl<uint32_t, math::half>; break;
319322
case INT: reshaper = reshapeImageImpl<uint32_t, int32_t>; break;
320323
case UINT: reshaper = reshapeImageImpl<uint32_t, uint32_t>; break;
321324
case UINT_10F_11F_11F_REV:

filament/backend/src/vulkan/VulkanReadPixels.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,16 +223,19 @@ void VulkanReadPixels::run(fvkmemory::resource_ptr<VulkanTexture> srcTexture, ui
223223
bpp *= componentCount;
224224
}
225225

226+
uint32_t const samples = srcTexture->samples > 1 ? srcTexture->samples : 1;
227+
226228
// Use a VkBuffer as the staging area for readback.
227229
// Using a buffer instead of a linearly tiled VkImage unifies the readback path and avoids
228230
// driver/validation layer issues since some implementations strictly prohibit linear tiling
229231
// for certain formats (such as depth/stencil).
230232
VkBuffer stagingBuffer = VK_NULL_HANDLE;
231233
VkMemoryRequirements memReqs;
232234

235+
uint32_t const stagingSize = width * height * bpp * samples;
233236
VkBufferCreateInfo bufferInfo = {
234237
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
235-
.size = width * height * bpp,
238+
.size = stagingSize,
236239
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT,
237240
};
238241
// TODO: we could use a staging buffer pool, but this is on another thread. We'd need to
@@ -371,6 +374,9 @@ void VulkanReadPixels::run(fvkmemory::resource_ptr<VulkanTexture> srcTexture, ui
371374
uint8_t const* srcPixels;
372375
vkMapMemory(device, stagingMemory, 0, VK_WHOLE_SIZE, 0, (void**) &srcPixels);
373376

377+
// If MSAA, MoltenVK returns samples in planar layout (Sample 0 is the first width * height
378+
// pixels). So we can simply ask DataReshaper to read width * height elements with standard
379+
// row pitch!
374380
int const rowPitch = width * bpp;
375381
if (!DataReshaper::reshapeImage(&p, componentType, componentCount, srcPixels, rowPitch,
376382
static_cast<int>(width), static_cast<int>(height), swizzle)) {

filament/backend/src/vulkan/utils/Conversion.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,7 @@ PixelDataType getComponentType(VkFormat format) {
770770
case VK_FORMAT_D16_UNORM: return PixelDataType::USHORT;
771771
case VK_FORMAT_D32_SFLOAT: return PixelDataType::FLOAT;
772772
case VK_FORMAT_X8_D24_UNORM_PACK32: return PixelDataType::UINT;
773+
case VK_FORMAT_B10G11R11_UFLOAT_PACK32: return PixelDataType::UINT_10F_11F_11F_REV;
773774
// For combined depth/stencil formats, we return the primary (depth) aspect type.
774775
// Stencil aspect overrides are handled dynamically during readback.
775776
case VK_FORMAT_D24_UNORM_S8_UINT: return PixelDataType::UINT;
@@ -882,6 +883,9 @@ uint32_t getComponentCount(VkFormat format) {
882883
case VK_FORMAT_R32G32B32A32_SINT:
883884
case VK_FORMAT_R32G32B32A32_SFLOAT:
884885
return 4;
886+
case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
887+
case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
888+
return 3;
885889
default: assert_invariant(false && "Unknown data type, conversion is not supported.");
886890
}
887891
return {};

filament/src/PostProcessManager.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3676,7 +3676,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blitDepth(FrameGraph& fg,
36763676

36773677
FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
36783678
utils::StaticString outputBufferName, FrameGraphId<FrameGraphTexture> const input,
3679-
FrameGraphTexture::Descriptor outDesc) noexcept {
3679+
FrameGraphTexture::Descriptor outDesc, utils::CString customPassName) noexcept {
36803680

36813681
// Don't do anything if we're not a MSAA buffer
36823682
auto const& inDesc = fg.getDescriptor(input);
@@ -3690,7 +3690,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
36903690
// through shaders or some other manipulation.
36913691
if ((isDepthFormat(inDesc.format) || isStencilFormat(inDesc.format)) &&
36923692
(!mDepthStencilResolveSupported)) {
3693-
return resolveDepthWithShader(fg, outputBufferName, input, outDesc);
3693+
return resolveDepthWithShader(fg, outputBufferName, input, outDesc,
3694+
std::move(customPassName));
36943695
}
36953696

36963697
outDesc.width = inDesc.width;
@@ -3703,7 +3704,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
37033704
FrameGraphId<FrameGraphTexture> output;
37043705
};
37053706

3706-
auto const& ppResolve = fg.addPass<ResolveData>("resolve",
3707+
auto const& ppResolve = fg.addPass<ResolveData>(
3708+
customPassName.empty() ? "resolve" : customPassName.c_str(),
37073709
[&](FrameGraph::Builder& builder, auto& data) {
37083710
data.input = builder.read(input, FrameGraphTexture::Usage::BLIT_SRC);
37093711
data.output = builder.createTexture(outputBufferName, outDesc);
@@ -3731,7 +3733,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
37313733

37323734
FrameGraphId<FrameGraphTexture> PostProcessManager::resolveDepthWithShader(FrameGraph& fg,
37333735
utils::StaticString outputBufferName, FrameGraphId<FrameGraphTexture> const input,
3734-
FrameGraphTexture::Descriptor outDesc) noexcept {
3736+
FrameGraphTexture::Descriptor outDesc, utils::CString customPassName) noexcept {
37353737

37363738
// Don't do anything if we're not a MSAA buffer
37373739
auto const& inDesc = fg.getDescriptor(input);
@@ -3754,7 +3756,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolveDepthWithShader(Frame
37543756
FrameGraphId<FrameGraphTexture> output;
37553757
};
37563758

3757-
auto const& ppResolve = fg.addPass<ResolveData>("resolveDepthWithShader",
3759+
auto const& ppResolve = fg.addPass<ResolveData>(
3760+
customPassName.empty() ? "resolveDepthWithShader" : customPassName.c_str(),
37583761
[&](FrameGraph::Builder& builder, auto& data) {
37593762
data.input = builder.sample(input);
37603763
data.output = builder.createTexture(outputBufferName, outDesc);

filament/src/PostProcessManager.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,16 +301,19 @@ class PostProcessManager {
301301

302302
// Resolves base level of input and outputs a texture from outDesc.
303303
// outDesc with, height, format and samples will be overridden.
304-
FrameGraphId<FrameGraphTexture> resolve(FrameGraph& fg,
305-
utils::StaticString outputBufferName, FrameGraphId<FrameGraphTexture> input,
306-
FrameGraphTexture::Descriptor outDesc) noexcept;
304+
// customPassName is an optional parameter to override the default "resolve" pass name.
305+
FrameGraphId<FrameGraphTexture> resolve(FrameGraph& fg, utils::StaticString outputBufferName,
306+
FrameGraphId<FrameGraphTexture> input, FrameGraphTexture::Descriptor outDesc,
307+
utils::CString customPassName = {}) noexcept;
307308

308309
// Resolves base level of input and outputs a texture from outDesc using a shader instead of
309310
// driver-implemented API.
310311
// outDesc with, height, format and samples will be overridden.
312+
// customPassName is an optional parameter to override the default "resolveDepthWithShader" pass
313+
// name.
311314
FrameGraphId<FrameGraphTexture> resolveDepthWithShader(FrameGraph& fg,
312315
utils::StaticString outputBufferName, FrameGraphId<FrameGraphTexture> input,
313-
FrameGraphTexture::Descriptor outDesc) noexcept;
316+
FrameGraphTexture::Descriptor outDesc, utils::CString customPassName = {}) noexcept;
314317

315318
// VSM shadow mipmap pass
316319
FrameGraphId<FrameGraphTexture> vsmMipmapPass(FrameGraph& fg,

filament/src/details/Engine.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -919,8 +919,15 @@ int FEngine::loop() {
919919
#endif
920920
if (fgviewerPortString != nullptr) {
921921
const int fgviewerPort = atoi(fgviewerPortString);
922-
debug.fgviewerServer = new fgviewer::DebugServer(fgviewerPort);
923-
922+
debug.fgviewerServer = new fgviewer::DebugServer(fgviewerPort,
923+
fgviewer::DebugServer::ReadbackRequest(
924+
[this](fgviewer::ViewHandle viewId, uint32_t id, utils::CString const& name,
925+
std::function<void(fgviewer::DebugServer::PixelBuffer, uint32_t,
926+
uint32_t, fgviewer::DebugServer::PixelDataFormat,
927+
fgviewer::DebugServer::FormatInfo)>
928+
callback) {
929+
requestTextureReadback(viewId, id, name, std::move(callback));
930+
}));
924931
// Sometimes the server can fail to spin up (e.g. if the above port is already in use).
925932
// When this occurs, carry onward, developers can look at civetweb.txt for details.
926933
if (!debug.fgviewerServer->isReady()) {
@@ -1775,4 +1782,15 @@ Engine::Config Engine::BuilderDetails::validateConfig(Config config) noexcept {
17751782
return config;
17761783
}
17771784

1785+
#if FILAMENT_ENABLE_FGVIEWER
1786+
void FEngine::requestTextureReadback(fgviewer::ViewHandle viewId, uint32_t id,
1787+
const utils::CString& name,
1788+
std::function<void(fgviewer::DebugServer::PixelBuffer, uint32_t, uint32_t,
1789+
fgviewer::DebugServer::PixelDataFormat, fgviewer::DebugServer::FormatInfo)>&&
1790+
callback) {
1791+
std::unique_lock<utils::Mutex> lock(mReadbackRequestsMutex);
1792+
mReadbackRequests.emplace_back(ReadbackRequest{ viewId, id, name, std::move(callback) });
1793+
}
1794+
#endif
1795+
17781796
} // namespace filament

filament/src/details/Engine.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,26 @@ class FEngine : public Engine, public utils::FeatureFlagManager {
569569

570570
backend::Driver& getDriver() const noexcept { return *mDriver; }
571571

572+
#if FILAMENT_ENABLE_FGVIEWER
573+
struct ReadbackRequest {
574+
using Callback = std::function<void(fgviewer::DebugServer::PixelBuffer, uint32_t, uint32_t,
575+
fgviewer::DebugServer::PixelDataFormat, fgviewer::DebugServer::FormatInfo)>;
576+
fgviewer::ViewHandle viewId;
577+
uint32_t id;
578+
utils::CString name;
579+
Callback callback;
580+
};
581+
582+
void requestTextureReadback(fgviewer::ViewHandle viewId, uint32_t id,
583+
utils::CString const& name,
584+
std::function<void(fgviewer::DebugServer::PixelBuffer, uint32_t, uint32_t,
585+
fgviewer::DebugServer::PixelDataFormat, fgviewer::DebugServer::FormatInfo)>&&
586+
callback);
587+
588+
utils::Mutex mReadbackRequestsMutex;
589+
std::vector<ReadbackRequest> mReadbackRequests;
590+
#endif
591+
572592
utils::Slice<const Engine::FeatureFlag> getFeatureFlags() const noexcept;
573593
bool setFeatureFlag(char const* name, bool value) noexcept;
574594
std::optional<bool> getFeatureFlag(char const* name) const noexcept;

filament/src/details/Renderer.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
#include "ds/StructureDescriptorSet.h"
3636

37+
#include "fg/FgviewerManager.h"
3738
#include "fg/FrameGraph.h"
3839
#include "fg/FrameGraphId.h"
3940
#include "fg/FrameGraphResources.h"
@@ -1549,25 +1550,23 @@ void FRenderer::renderJob(DriverApi& driver, RootArenaScope& rootArenaScope, FVi
15491550
// auto debug = structure
15501551
// fg.forwardResource(fgViewRenderTarget, debug ? debug : input);
15511552

1552-
fg.forwardResource(fgViewRenderTarget, input);
1553+
FgviewerManager::handleFgReadbacks(engine, fg, viewRenderTarget, view.getViewHandle());
15531554

1555+
fg.forwardResource(fgViewRenderTarget, input);
15541556
fg.present(fgViewRenderTarget);
15551557

15561558
fg.compile();
15571559

1558-
#if FILAMENT_ENABLE_FGVIEWER
1559-
fgviewer::DebugServer* fgviewerServer = engine.debug.fgviewerServer;
1560-
if (UTILS_LIKELY(fgviewerServer)) {
1561-
fgviewerServer->update(view.getViewHandle(), fg.getFrameGraphInfo(view.getName()));
1562-
}
1563-
#endif
1560+
FgviewerManager::update(engine, fg, view);
15641561

15651562
//utils::io::sstream graphviz;
15661563
//fg.export_graphviz(graphviz, view.getName());
15671564
//DLOG(INFO) << graphviz.c_str();
15681565

15691566
fg.execute(driver);
15701567

1568+
FgviewerManager::tick(engine);
1569+
15711570
// save the current history entry and destroy the oldest entry
15721571
view.commitFrameHistory(engine);
15731572

filament/src/fg/DependencyGraph.cpp

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
#include <utils/CString.h>
2424
#include <utils/ostream.h>
2525

26+
#if FILAMENT_ENABLE_FGVIEWER
27+
#include <fgviewer/FrameGraphInfo.h>
28+
#endif
29+
2630
#include <iterator>
2731
#include <cstdint>
2832

@@ -155,14 +159,32 @@ void DependencyGraph::export_graphviz(utils::io::ostream& out, char const* name)
155159

156160
auto const& nodes = mNodes;
157161

158-
for (Node const* node : nodes) {
162+
auto skipNode = [](char const* name) {
163+
#if FILAMENT_ENABLE_FGVIEWER
164+
if (!name) {
165+
return false;
166+
}
167+
std::string_view view{ name };
168+
return view == std::string_view(fgviewer::READBACK_PASS_NAME) || view == std::string_view(fgviewer::RESOLVED_MONITOR_PASS_NAME);
169+
#else
170+
return false;
171+
#endif
172+
};
173+
174+
for (Node const* node: nodes) {
175+
if (skipNode(node->getName())) {
176+
continue;
177+
}
159178
uint32_t id = node->getId();
160179
utils::CString s = node->graphvizify();
161180
out << "\"N" << id << "\" " << s.c_str() << "\n";
162181
}
163182

164183
out << "\n";
165-
for (Node const* node : nodes) {
184+
for (Node const* node: nodes) {
185+
if (skipNode(node->getName())) {
186+
continue;
187+
}
166188
uint32_t id = node->getId();
167189

168190
auto edges = getOutgoingEdges(node);
@@ -174,22 +196,48 @@ void DependencyGraph::export_graphviz(utils::io::ostream& out, char const* name)
174196

175197
// render the valid edges
176198
if (first != pos) {
177-
out << "N" << id << " -> { ";
178-
while (first != pos) {
179-
Node const* ref = getNode((*first++)->to);
180-
out << "N" << ref->getId() << " ";
199+
bool hasValidEdges = false;
200+
for (auto it = first; it != pos; ++it) {
201+
Node const* ref = getNode((*it)->to);
202+
if (skipNode(ref->getName())) {
203+
continue;
204+
}
205+
hasValidEdges = true;
206+
}
207+
if (hasValidEdges) {
208+
out << "N" << id << " -> { ";
209+
while (first != pos) {
210+
Node const* ref = getNode((*first++)->to);
211+
if (skipNode(ref->getName())) {
212+
continue;
213+
}
214+
out << "N" << ref->getId() << " ";
215+
}
216+
out << "} [color=" << s.c_str() << "2]\n";
181217
}
182-
out << "} [color=" << s.c_str() << "2]\n";
183218
}
184219

185220
// render the invalid edges
186221
if (first != edges.end()) {
187-
out << "N" << id << " -> { ";
188-
while (first != edges.end()) {
189-
Node const* ref = getNode((*first++)->to);
190-
out << "N" << ref->getId() << " ";
222+
bool hasInvalidEdges = false;
223+
for (auto it = first; it != edges.end(); ++it) {
224+
Node const* ref = getNode((*it)->to);
225+
if (skipNode(ref->getName())) {
226+
continue;
227+
}
228+
hasInvalidEdges = true;
229+
}
230+
if (hasInvalidEdges) {
231+
out << "N" << id << " -> { ";
232+
while (first != edges.end()) {
233+
Node const* ref = getNode((*first++)->to);
234+
if (skipNode(ref->getName())) {
235+
continue;
236+
}
237+
out << "N" << ref->getId() << " ";
238+
}
239+
out << "} [color=" << s.c_str() << "4 style=dashed]\n";
191240
}
192-
out << "} [color=" << s.c_str() << "4 style=dashed]\n";
193241
}
194242
}
195243

0 commit comments

Comments
 (0)