Skip to content

Texture Handling

Popax21 edited this page Sep 1, 2022 · 1 revision

Celeste has multiple types which are used to represent textures:

  • Texture2D: a low level XNA/FNA texture. It represents an actual texture stored on the GPU.
  • VirtualTexture: a virtual asset texture. This class is tied into the virtual content system of Monocle / Everest, and wraps the low level Texture2D instance. It is responsible for loading the texture's contents (this is the place where the FTL code is), and can also use lazy loading to speed up loading times.
  • MTexture: a high level Monocle texture. It contains both a VirtualTexture and a clip rectangle, which is used for atlases, as well as some other metadata. This is the type you give components like Image, as well as what is used by sprites and other game elements.

However, Procedurline has its own texture handling system which wraps these types, as Monocle's / Everest's system was not designed with dynamic texture creation / destruction / modification in mind. This is implemented in the form of the TextureScope, TextureHandle and TextureData classes.

TextureData

Represents a texture's data. It consists a width, a height, and a pixel buffer which stores an RGBA32 Color value for every pair of pixel coordinates. TextureData instances provide methods to efficiently copy texture data onto different instances, or download / uploaded their contents from / to Texture2Ds.

For optimization purposes, TextureData uses an unmanaged pixel buffer - as such TextureData instances have to be disposed once you finished using them!

TextureScope

A texture scope, despite what one might think, has nothing to do with a DataScope. It instead is Procedurline's solution to the issue of texture lifecycles. Usually, textures are static - they are assets on disk. As such, for every vanilla atlas / mod texture, one VirtualTexture is created during initialization, which is then loaded just once. During game shutdown, all virtual assets (including textures) are disposed, and if a specific texture's file changes, it is instructed to reload, resetting it back to its original state. There is no texture hierarchy or grouping mechanism - all VirtualTextures are created and disposed of at the same time.

However, Procedurline regularly creates and discards of groups of texture during runtime. As such it assigns each texture a parent in a tree of texture scopes. A texture scope (represented using the TextureScope class) is a collection of so called texture owners, which are things capable of owning (an arbitrary number of) textures. TextureHandle and TextureScope are subclasses of TextureOwner. Every texture owner must be the child of a texture scope, with the exception of the global texture scope. Because TextureScope is also a TextureOwner, this creates a hierarchical tree of texture scopes, with TextureHandles as the leafs.

As soon as a texture scope is disposed, it recursively disposes all child texture owners, which will cause child texture scopes to now also dispose all of their children, and so on. This provides an effective mechanism to quickly dispose of all textures belong to a certain shared group.

TextureHandle

This is the class actually representing a texture. It is a subclass of TextureOwner, and encapsulates a VirtualTexture which is discarded once the texture handle gets disposed. It also provides helper methods related to Everest's FTL / lazy loading implementations, which allow one to check if loading has finished / is currently occurring, or to trigger or short circuit texture loading entirely. Additionally, they also contain texture data getter / setter functions, which in comparison to the texture manager's download / upload functions cache the texture's data..

The texture manager provides a function to obtain a TextureHandle for a VirtualTexture not created / owned by Procedurline: TextureManager.GetHandle(). The obtained handle will be part of the UNOWNED texture scope, and once disposed will not discard of the underlying VirtualTexture.