Files
filament/docs/notes/framegraph.html
Filament Bot f14ff6606a [automated] Updating /docs due to commit 5628b8b
Full commit hash is 5628b8b6e7

DOCS_ALLOW_DIRECT_EDITS
2025-07-25 23:31:26 +00:00

381 lines
17 KiB
HTML

<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Framegraph - Filament</title>
<!-- Custom HTML head -->
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- MathJax -->
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<!-- Provide site root to javascript -->
<script>
var path_to_root = "../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc.js"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div style="display:flex;align-items:center;justify-content:center">
<img class="flogo" src="../images/filament_logo_small.png"></img>
</div>
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<!-- Filament: disable themes because the markdeep part does not look good for dark themes -->
<!--
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
-->
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Filament</h1>
<div class="right-buttons">
<a href="https://github.com/google/filament" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="framegraph"><a class="header" href="#framegraph">FrameGraph</a></h1>
<p>FrameGraph is a framework within Filament for computing resources needed to
render a frame. The framework enables declaring dependencies between resources.</p>
<p>For example, when rendering shadows, we would need to first compute and store the
shadow map into a texture resource, and then the later color pass would then
sample that texture to attenuate the final output color. That creates a
dependency on the shadow map from the color pass. Filament uses FrameGraph to
declare that dependency.</p>
<h2 id="details"><a class="header" href="#details">Details</a></h2>
<h3 id="dependency-graph"><a class="header" href="#dependency-graph">Dependency Graph</a></h3>
<p>The core of this framework is
<a href="https://github.com/google/filament/blob/main/libs/gltfio/src/DependencyGraph.h">a class that defines a dependency graph</a> — that is, the class
defines nodes and connections between nodes. This class makes assumptions about
the types of its nodes. Like many other classes within Filament, this class is
without virtual function declaration to avoid paying the cost of virtual calls.</p>
<p>This class has additional functions to detect whether there is a cycle in the
graph, and it is able to cull unreachable nodes.</p>
<h3 id="framegraph-1"><a class="header" href="#framegraph-1">FrameGraph</a></h3>
<p>A frame graph consists of two types of nodes</p>
<ul>
<li>Resource
<ul>
<li>This represents a generic resource such as a texture</li>
<li>90% of the time, this is a texture.</li>
</ul>
</li>
<li>Pass
<ul>
<li>This represents a "computation/rendering process"</li>
<li>It takes a set of resources</li>
<li>It outputs a set of resources</li>
</ul>
</li>
</ul>
<p>Edges can be created in the following three directions:</p>
<ul>
<li>Resource → Pass = A read</li>
<li>Pass → Resource = A write</li>
<li>Resource → Resource = A resource/subresource relationship.</li>
</ul>
<h3 id="an-example"><a class="header" href="#an-example">An example</a></h3>
<p>To better understand FrameGraph, we consider the following graphical
representation of a real graph. In this graph, blue nodes denote "Resources" and
orange nodes denote "Passes."</p>
<p><img src="../images/framegraph.png" alt="Sample frame graph" /></p>
<p>In this graph, we see that the "Color Pass" takes as input the "Shadowmap",
which has edges going into it, meaning that it's a texture array. The output of
the "Color Pass" are "viewRenderTarget" and "Depth Buffer."</p>
<p>Note that there is an outgoing edge from "viewRenderTarget", where the color
buffer will be used as input in the post-processing passes. But since "Depth
Buffer" is not relevant to the rest of the rendering, it does not have an
outgoing edge.</p>
<p>Since the graph is guaranteed to be acyclic, we can produce a
dependency-respecting ordering of the nodes by traversal of the graph (e.g.
topological sort).</p>
<h3 id="example-code"><a class="header" href="#example-code">Example code</a></h3>
<p>We take a snippet of in production code to look through the details of building
a graph.</p>
<pre><code>struct StructurePassData {
FrameGraphId&lt;FrameGraphTexture&gt; depth;
FrameGraphId&lt;FrameGraphTexture&gt; picking;
};
...
// generate depth pass at the requested resolution
auto&amp; structurePass = fg.addPass&lt;StructurePassData&gt;("Structure Pass",
[&amp;](FrameGraph::Builder&amp; builder, auto&amp; data) {
bool const isES2 = mEngine.getDriverApi().getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0;
data.depth = builder.createTexture("Structure Buffer", {
.width = width, .height = height,
.levels = uint8_t(levelCount),
.format = isES2 ? TextureFormat::DEPTH24 : TextureFormat::DEPTH32F });
// workaround: since we have levels, this implies SAMPLEABLE (because of the gl
// backend, which implements non-sampleables with renderbuffers, which don't have levels).
// (should the gl driver revert to textures, in that case?)
data.depth = builder.write(data.depth,
FrameGraphTexture::Usage::DEPTH_ATTACHMENT | FrameGraphTexture::Usage::SAMPLEABLE);
if (config.picking) {
data.picking = builder.createTexture("Picking Buffer", {
.width = width, .height = height,
.format = isES2 ? TextureFormat::RGBA8 : TextureFormat::RG32F });
data.picking = builder.write(data.picking,
FrameGraphTexture::Usage::COLOR_ATTACHMENT);
}
builder.declareRenderPass("Structure Target", {
.attachments = { .color = { data.picking }, .depth = data.depth },
.clearFlags = TargetBufferFlags::COLOR0 | TargetBufferFlags::DEPTH
});
},
[=, renderPass = pass](FrameGraphResources const&amp; resources,
auto const&amp;, DriverApi&amp;) mutable {
Variant structureVariant(Variant::DEPTH_VARIANT);
structureVariant.setPicking(config.picking);
auto out = resources.getRenderPassInfo();
renderPass.setRenderFlags(structureRenderFlags);
renderPass.setVariant(structureVariant);
renderPass.appendCommands(mEngine, RenderPass::CommandTypeFlags::SSAO);
renderPass.sortCommands(mEngine);
renderPass.execute(mEngine, resources.getPassName(), out.target, out.params);
}
);
</code></pre>
<p>The <code>addPass</code> method creates a node and it take in two lambda functions as its
parameter. The first lambda sets up the resources that will be used in the
execution of the Pass. This lambda is executed immediately and synchronously when
<code>addPass</code> is called. The second lambda is the actual execution of the pass; it is
executed when the graph has been completed and is traversed.</p>
<h3 id="what-does-it-do"><a class="header" href="#what-does-it-do">What does it do?</a></h3>
<p>In the above, we see through a graph and code what a frame graph looks like and
how to build it. We provide here a more detailed description of what it does:</p>
<ul>
<li>Manages the lifetime of the resources
<ul>
<li>Know how the resources are allocated, when it is used, and when it can
be freed</li>
</ul>
</li>
<li>Calculates the usage bit of the texture resource
<ul>
<li>The usage bit is used to indicate what the resources are used for: for
example, will it be blitted to or sampled from?</li>
</ul>
</li>
<li>Calculates the load/store bits of the rendertargets within a renderpass.
<ul>
<li>For example, if we are rendering into a texture, we would want to mark
it with the bit "keep" as oppose to "discard".</li>
</ul>
</li>
</ul>
<h2 id="additional-details"><a class="header" href="#additional-details">Additional details</a></h2>
<ul>
<li>In a previous version of FrameGraph, there were only edges between Resource
and Pass. For example, a Pass and Pass edge would not make logical sense.
The following iteration, allowed for edges between two Resource nodes to
indicate that one is a subresource of another (i.e. a layer in a mip-mapped
texture).</li>
<li>There are two extra features of FrameGraph that are important but has a lot
subtlety, and, incidentally, their inclusion added great complexity to the
implementation
<ul>
<li>Importing/exporting resources outside of the graph
<ul>
<li>In most cases, the graph and its resources are "alive" for only for
a frame.</li>
<li>For techniques like TAA (Temporal Anti-aliasing), we need to be able
to import past output into the current FrameGraph</li>
</ul>
</li>
</ul>
</li>
<li>Future Work
<ul>
<li>For CPU only passes, explore multi-threading and re-ordering of the Pass
nodes</li>
<li>A graphical debugger for online debugging session in the spirit of
<code>matdbg</code>.</li>
</ul>
</li>
<li>"RenderGraph" might be a more fitting name for this framework.</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../notes/coverage.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../notes/libs.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../notes/coverage.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../notes/libs.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>