Copied from the PR discussion…
To speed things along, I will try to explain how the resource handling works:
Each FrameGraph has a ResourceList, which handles RenderResources. RenderResources are not actual resources (which are called RenderObjects in the code) , they are simply promises of actual resources (RenderObjects) that will exist in the future. RenderPasses do not have direct access to the RenderResources, but can perform operations on them through ResourceTickets, which are essentially indices pointing to particular RenderResources within an array.
When a RenderPass makes a request for a RenderObject from the ResourceList using a ResourceTicket (called acquiring), the ResourceList will check if the RenderResource located by the ResourceTicket is virtual (is not associated with a RenderObject). If not, great! The associated RenderObject is simply returned. But if the RenderResource is virtual, the ResourceList asks the RenderObjectMap (held by the RenderManager) to either create a new RenderObject to use or find an existing one to reallocate.
The RenderObjectMap considers creating new RenderObjects expensive, so it tries to reallocate existing RenderObjects wherever it can. The ResourceDef (which defines the behavior of a RenderResource and the associated RenderObject) attached to the RenderResource is used to determine which RenderObjects are suitable for reallocation. Also the RenderObjectMap doesn’t like to keep unused RenderObjects alive, so once a RenderObject has gone a number of frames without being used, it is disposed.
As an example to better understand how render passes and resource handling works, here is a basic render pass that performs downsampling. That is, it takes an input texture and renders to an output texture that is a quarter of the size.
public class MyRenderPass extends RenderPass {
private ResourceTicket<Texture2D> inTex, outTex;
private TextureDef<Texture2D> texDef;
@Override
protected void initialize(FrameGraph frameGraph) {
// Initialize is called when the pass is assigned to the FrameGraph.
// Add an input named "Texture". This allows this pass to access a texture
// from another pass.
inTex = addInput("Texture");
// Add an output named "Texture". This allows other passes to access the
// resulting texture.
outTex = addOutput("Texture");
// Declare the texture definition, defining how the output texture is created
// and how it behaves.
texDef = new TextureDef(Texture2D.class, img -> new Texture2D(img));
}
@Override
protected void prepare(FGRenderContext context) {
// Prepare is called before execution for every ViewPort rendered, and is used primarily to determine
// what resources are referenced where, so that unused passes can be culled.
// Note that a prepare call does not necessarily proceed a execute call, due to culling.
// Declare a new resource as the output texture
declare(texDef, outTex);
// Reserve the output texture. This greatly increases the chances of getting
// the same texture from frame to frame, and thus minimizes texture binds.
reserve(outTex);
// Reference the input texture, so this pass can safely access it
reference(inTex);
}
@Override
protected void execute(FGRenderContext context) {
// Execute is called to perform the rendering operations. This is not guaranteed
// to be called for every ViewPort rendered, because of culling.
// Acquire the input texture provided by another pass
Texture2D texture = resources.acquire(inTex);
Image img = texture.getImage();
// Set the size of the output texture as half the input texture on each demension
int w = img.getWidth() / 2;
int h = img.getHeight() / 2;
texDef.setSize(w, h);
// Set the format of the output texture to match the input texture
texDef.setFormat(img.getFormat());
// Get a framebuffer object matching the width, height, and samples. If no
// such framebuffer exists, a new one will be created. This is handled only by
// the RenderPass superclass and not the resource manager.
FrameBuffer fb = getFrameBuffer(w, h, 1);
// Acquire the output texture, which is immediately attached to the framebuffer.
// This operation is specifically designed to limit texture binds and framebuffer updates.
resources.acquireColorTargets(fb, outTex);
// Attach the framebuffer for rendering
context.getRenderer().setFrameBuffer(fb);
context.getRenderer().clearBuffers(true, true, true);
context.getRenderer().setBackgroundColor(ColorRGBA.BlackNoAlpha);
// edit: set the camera width and height to the output texture width and height
context.resizeCamera(w, h, false, false, false);
// Render the input texture to the output texture on a fullscreen quad.
// The depth texture is null, so the quad is rendered at a depth of one.
context.renderTextures(texture, null);
}
@Override
protected void reset(FGRenderContext context) {
// Reset is called after execution. Passes cannot be culled from this step.
}
@Override
protected void cleanup(FrameGraph frameGraph) {
// Cleanup is called when this pass is removed from the FrameGraph.
}
}
Further details for each function and class can be found in the relavent Javadoc.