Resource Providers

In Noesis, the process of loading resources can be customized by using resource providers. There is a provider for each kind of resource: Xaml, Texture and Font. You normally setup each provider after Noesis initialization.


This three kind of providers share the same abstraction for files, the Stream interface.

class Stream: public BaseComponent
    /// Set the current position within the stream
    virtual void SetPosition(uint32_t pos) = 0;

    /// Returns the current position within the stream
    virtual uint32_t GetPosition() const = 0;

    /// Returns the length of the stream in bytes
    virtual uint32_t GetLength() const = 0;

    /// Reads data at the current position and advances it by the number of bytes read
    /// Returns the total number of bytes read. This can be less than the number of bytes requested
    virtual uint32_t Read(void* buffer, uint32_t size) = 0;

    /// Closes the current stream and releases any resources associated with the current stream
    virtual void Close() = 0;

Xaml provider

The base class from implementing a provider of xamls is XamlProvider.

class XamlProvider: public BaseComponent
    /// Loads XAML from the specified URI. Returns null when xaml is not found
    virtual Ptr<Stream> LoadXaml(const Uri& uri) = 0;

The implementation is straightforward. You basically must provide a stream for each Uri that is requested. You can find two XamlProvider implementations in our Application Framework, LocalXamlProvider for loading XAMLs from disk and EmbeddedXamlProvider for loading XAMLs embedded in the executable.

Texture provider

Texture providers are a bit more complex because you need to implement two different functions. At the main thread the layout process needs information about the dimensions of the texture. You provide that information through the GetTextureInfo function. The function that really loads the texture is LoadTexture and it is always called from the render thread.

class TextureProvider: public BaseComponent
    /// Returns metadata for the texture at the given URI or empty rectangle if texture is not found
    virtual TextureInfo GetTextureInfo(const Uri& uri) = 0;

    /// Returns a texture compatible with the given device. Returns null if texture is not found
    virtual Ptr<Texture> LoadTexture(const Uri& uri, RenderDevice* device) = 0;

Note that since NoesisGUI 3.1.6, TextureInfo contains rectangle information to implement texture atlas.

// Texture metadata returned by TextureProvider::GetTextureInfo()
// The rectangle [x, y, width, height] defines the logical surface used by the texture
// Note that the provider can reuse the same texture with different names and rectangles to
// implementing texture atlas compatible with standard XAML. For example:
//  - ItemA ==> Atlas.png [0,   0, 100, 100]
//  - ItemB ==> Atlas.png [0, 100, 100, 200]
//  <StackPanel>
//    <Image Source="ItemA.png"/>
//    <Image Source="ItemB.png"/>
//  </StackPanel>
// The member 'dpiScale' can be used for scaling (eg: 96 dpi = 1.0, 72 dpi = 0.75 (72/96))
struct TextureInfo
    uint32_t width = 0;
    uint32_t height = 0;
    uint32_t x = 0;
    uint32_t y = 0;

    float dpiScale = 1.0f;

The Application Framework provides a helper class, FileTextureProvider, that will create textures from images stored in files. It exposes a virtual function you must implement to load the requested filename.

class FileTextureProvider: public TextureProvider
    virtual Ptr<Stream> OpenStream(const Uri& uri) const = 0;

Similar to XamlProvider you can find LocalTextureProvider and EmbeddedTextureProvider implementations within the Application Framework.

Font provider

The font provider is the most complex to implement. The base class in charge of loading fonts is FontProvider.

class FontProvider: public BaseComponent
    /// Finds the font in the given URI that best matches the specified properties
    /// Returns null stream when there are not matches
    virtual FontSource MatchFont(const Uri& baseUri, const char* familyName, FontWeight& weight,
        FontStretch& stretch, FontStyle& style) = 0;

    /// Returns true if the requested font family exists in given URI
    virtual bool FamilyExists(const Uri& baseUri, const char* familyName) = 0;

To help with the implementation of this provider there is an intermediate class, CachedFontProvider, that scans folders and extracts family information from TrueType and OpenType files. It also implements the font matching algorithm.

CachedFontProvider exposes two virtual functions, ScanFolder and OpenFont:

  • In ScanFolder you must register all the fonts available in the given folder. Each font is registered by calling RegisterFont.
  • OpenFont is the function in charge of providing the stream corresponding to each of the registered filenames.
class CachedFontProvider: public FontProvider
    /// Registers a font filename in the given folder. Each time this function is invoked, the
    /// given filename is opened and scanned (through OpenFont). It is recommended deferring
    /// this call as much as possible (for example, until ScanFolder is invoked)
    void RegisterFont(const Uri& folder, const char* filename);

    /// Registers a font face with given font properties. In comparison with the previous function
    /// this one doesn't open the filename to scan it. Always use this function if possible
    void RegisterFont(const Uri& folder, const char* filename, uint32_t index, const char* family,
        FontWeight weight, FontStretch stretch, FontStyle style);

    /// First time a font is requested from a folder, this function is invoked to give inheritors
    /// the opportunity to register faces found in that folder
    virtual void ScanFolder(const Uri& folder);

    /// Returns a stream to a previously registered filename
    virtual Ptr<Stream> OpenFont(const Uri& folder, const char* filename) const = 0;

The most efficient way to provide custom fonts to Noesis is creating a new class derived from CachedFontProvider and in its constructor registering a fixed list of fonts with its corresponding attributes.

Similar to the rest of providers, LocalFontProvider and EmbeddedFontProvider are sample implementations available in the Application Framework. We recommend using this classes instead of CachedFontProvider.

Hot Reloading

Providers also expose a delegate to inform when a resource needs to be reloaded. Each time this delegate is invoked the corresponding resource will be hot reloaded. RaiseXamlChanged, RaiseTextureChanged and RaiseFontChanged are the corresponding functions in charge of notifying Noesis that a resource needs to be reloaded.

class XamlProvider: public BaseComponent
    /// Delegate to notify changes to the XAML file text
    typedef Delegate<void (const Uri&)> XamlChangedDelegate;
    XamlChangedDelegate& XamlChanged() { return mXamlChanged; }
    void RaiseXamlChanged(const Uri& uri) { mXamlChanged(uri); }

This architecture also allows loading resources in a worker thread, for example, for textures:

  • First time a texture is requested, a small preloaded thumbnail is returned and a job is sent to a worker thread to start loading the real texture.
  • When the worker thread finishes, RaiseTextureChanged is invoked in the corresponding texture provider.
  • Noesis will reload the texture in all places where it is being used.
© 2017 Noesis Technologies