-
Notifications
You must be signed in to change notification settings - Fork 2
Rendering
DAYBREAK has many abstractions to make rendering faster and easier to write, safer to use, and more resistant to unknown contexts (such as when layered on top of other mods).
RenderTargets are buffers provided by the GraphicsDevice. They will not be explained here.
A RenderTargetLease is an object returned by RenderTargetPools which hold a reference to a RenderTarget2D. It is possible for the owning pool to mutate the target inside the lease (a function utilized by ScreenspaceTargetPool to provide targets which are tied to the size of the screen).
These objects should be directly returned to the pool by calling Dispose. If you need to give up ownership over the lease to an external API that may use the target arbitrarily, you can queue it to be automatically disposed next render frame with RenderTargetPool.ReturnNextFrame.
RenderTargetPools provide APIs for obtaining RenderTarget2Ds which are managed by the pool. This pattern promotes reuse across buffers and decreases VRAM usage, as well as providing a reliable way to obtain non-null targets which can be mutated.
The default implementation can be accessed through RenderTargetPool.Shared, which will hold onto rented targets indefinitely to be recycled later.
Rents a target. This API has various iterations depending on the context. Always returns a valid lease to a target.
Returns a leased target to the pool. Should rarely be invoked manually; use RenderTargetLease.Dispose.
A RenderTargetPool which provides targets whose size is determined by the screen size, automatically disposing of targets and acquiring new targets in response to size changes. This pool does not actually cache targets for reuse later currently.
You should generally not initialize this object directly; use ScreenspaceTargetPool.Shared.
Avoid using the default Rent APIs, instead preferring APIs that have callbacks in their signature that determine the size based on the size of the backbuffer (and optionally the size of the vanilla tileTarget and related which take offscreenRange into account).
Example of requesting a target that is always the size of the backbuffer:
rtLease = ScreenspaceTargetPool.Shared.Rent(Main.instance.GraphicsDevice);Example of requesting a target that is always half the size of the backbuffer:
rtLease = ScreenspaceTargetPool.Shared.Rent(
Main.instance.GraphicsDevice,
(width, height) => (width / 2, height / 2)
);Example of requesting a target that is the size of Main.tileTarget and related:
rtLease = ScreenspaceTargetPool.Shared.Rent(
Main.instance.GraphicsDevice,
(width, height, targetWidth, targetHeight) => (targetWidth, targetHeight)
);Returns the leased target and stops reinitializing the underlying RenderTarget2D on resize.
Defines a scope which allows you to easily swap out the active targets of a graphics device, automatically restoring the previous targets (if any) when disposed. By default, this API will preserve the contents of the swapped targets;
This example from NIGHTSHADE showcases rendering a texture to an RT through scoping it.
using (cursorShadowTarget.Scope(clearColor: Color.Transparent))
{
var asset = TextureAssets.Cursors[smart ? 1 : 0].Value;
spriteBatch.Begin(ss with { SortMode = SpriteSortMode.Deferred });
spriteBatch.Draw(asset, Vector2.Zero, color);
spriteBatch.End();
}Tip
It is preferred that you create scopes through the Scope extension method: rtLease.Scope().
In DAYBREAK, you can use buffers to chain multiple effects. This is done by defining a DrawWithEffectsScope which captures the data you want to apply effects to to a buffer and then applies those effects.
You should use the DrawWithEffects extension to SpriteBatch.
This example showcases rendering a texture from a DrawData object by initializing a target the size of the image, copying that image to the target, and applying a collection of effects:
var effectChain = Main.spriteBatch.DrawWithEffects(
RenderTargetPool.Shared,
cdd.texture.Size().ToPoint(),
clearColor: Color.Transparent,
effects: shaders[..^1].Select(x => EffectChainEntry.FromPlayerShader(player, PlayerShader.FromPacked(x), cd)).ToArray()
);
using (effectChain)
{
Main.spriteBatch.Draw(cdd.texture, Vector2.Zero, Color.White);
}Note in the above example that effectChain itself is preserved after disposal. This is because we need the leased target it produces to use later:
cdd.texture = effectChain.Lease.Target;
RenderTargetPool.ReturnNextFrame(effectChain.Lease);The DrawData object (cdd) now references our buffer with the effects applied. In the context of this code, the DrawData is then consumed past this scope and we have to queue the lease to be disposed next frame since we cannot know how it's used/when it's safe to dispose it in the same frame.
TODO; probably should wait until we use a shader for it.
It is often that you will want to swap RenderTargets in the middle of drawing. Vanilla's solution to this is INeedRenderTargetContent/ARenderTargetContentByRequest, which queues special rendering operations to occur all at once and manages the target partially for you. The issue with this is data can lag behind a frame and it's somewhat clunky. This is designed to bypass an issue with swapping targets causing the screen to go black because targets' contents get disposed on swap. RenderTargetPreserver is designed to address this directly, preserving execution order.
Important
DAYBREAK automatically changes the render target usage of presentation parameters of the graphics device to PreserveContents.
Tip
It is rare that you need to use this API directly. While it is easy and safe to use, you should generally prefer RenderTargetScope to preserve targets for you. Other scopes such as DrawWithEffectsScope and DrawOutlinedScope use this implicitly.
Preserves the contents of the given target bindings.
Requests the targets currently in use by Main.instance.GraphicsDevice and preserves them.
SpriteBatch is a convenient API provided by XNA/FNA to quickly and easily render quads with textures on them (that is, images). DAYBREAK extends a lot of functionality to make it easy to use SpriteBatches (namely Main.spriteBatch) safely.
Represents the state of a SpriteBatch based on the parameters passed to Begin. Used to save the settings of a SpriteBatch to be reapplied later.
Can be converted to a SpriteBatchParameters through ToParameters.
Also provides extensions to SpriteBatch to get these values and apply them.
Extensions are provided to get SpriteBatchSnapshot instances by calling SpriteBatch.End:
Main.spriteBatch.End(out var ss);You can then immediately begin a SpriteBatch again given a snapshot:
Main.spriteBatch.Begin(ss);Ends the SpriteBatch and immediately begins it with the given snapshot:
spriteBatch.Restart(ss);This is most commonly used to commit deferred data to be rendered while reusing snapshot data, but may also be used to end a SpriteBatch regardless of its current state to apply the new snapshot state.
Like a SpritebatchSnapshot, but its parameters are nullable. This object is designed to be used in cases where a user wishes to define an API that takes SpriteBatch parameters with default values. Can be combined with a SpriteBatchSnapshot to get a new SpriteBatchSnapshot instance which uses the values specified in the SpriteBatchParameters instance with fallback values for null parameters provided by the SpriteBatchSnapshot.
Combines this object with definite values from SpritebatchSnapshot to create a new SpritebatchSnapshot whose values are that of SpriteBatchParameters with null values replaced with that of the given SpritebatchSnapshot.
Lets you use a SpriteBatch without being concerned about its outside state (such as whether it's already started). Useful for intercepting existing rendering routines.
Saves the incoming Begin parameters of the SpriteBatch if the SpriteBatch is already started and applies it upon Dispose. Initializing the scope only ends the SpriteBatch without starting it, so be sure to pass your parameters to SpriteBatch.Begin when you're ready to begin it.
Tip
It is preferred that you create scopes through the Scope extension method: Main.spriteBatch.Scope().
This snippet from the DrawOutlinedScope implementation demonstrates what it excels at:
// In DrawOutlinedScope's constructor. DrawOutlinedScope keeps sbScope as a field.
// Scoping it here ensures it is ended and can be begun as we'd like.
sbScope = spriteBatch.Scope();
{
rtScope = contentLease.Scope(clearColor: Color.Transparent);
spriteBatch.Begin(initParameters.ToSnapshot(default_snapshot));
}
// In the Dispose function, we assume the batch is still began.
// We can end it and do whatever before disposing the scope.
spriteBatch.End();
// ...
using (outlineLease.Scope(clearColor: Color.Transparent))
{
spriteBatch.Begin(outlineParameters.ToSnapshot(default_snapshot));
// ...
spriteBatch.End();
}
// ...
// Dispose of the scope.
// If you don't End it before, the scope will know to End it.
sbScope.Dispose();Begins the SpriteBatch with the given SpriteBatchSnapshot.
Provides utilities to help with matrices, primarily applying matrices to vectors and scaling vectors.
A thin wrapper over an integer which lets one directly access the local index of a player shader as well as its configuration type. Also makes it easy to get the packed shader integer value by constructing the object directly.