Memory Allocation

As discussed in Overridable Allocator in Key Memory Concepts, Scaleform allows developers to customize memory allocation by choosing one of the three approaches:

  1. Overriding the SysAlloc interface to delegate allocations to the application memory system,
  2. Providing Scaleform with one or more fixed-size memory blocks, or
  3. Instructing Scaleform to allocate memory directly from the operating system.

For best memory efficiency, it is important that developers select an approach that matches their application. The differences between approaches (1) and (2), which are the most commonly used, are discussed below.

When developers override SysAlloc, they delegate Scaleform allocations to their own memory system. When Scaleform needs memory for loading a new SWF file, for example, it requests it by calling the Scaleform::SysAlloc::Alloc function; it calls Scaleform::SysAlloc::Free when the content is unloaded. In this scenario, the best possible memory efficiency is achieved when all systems, including Scaleform, use a single shared global allocator, as any freed memory block becomes available for reuse by any system that may need it. Any compartmentalization of allocations reduces the overall sharing efficiency, thus increasing the total memory footprint. The details of overriding SysAlloc are provided in Overriding Allocation section below.

The greatest problem with the global allocation approach is fragmentation, which is the reason why many console developers prefer to use a predetermined, fixed-memory layout instead. With predetermined memory layout, fixed-size memory regions are allocated for each system, with the size of each of these regions determined at startup, or during level loading. Overall, this approach trades the efficiency of a global system for a more predictable memory layout.

If an application uses this fixed-memory approach, developers should use a fixed-sized memory block allocator for Scaleform as well – a configuration that is described in Implementing a custom SysAlloc. To make this scenario more practical, Scaleform also allows creation of secondary memory arenas, or regions of memory which are used for limited durations of time, such as when a “Pause Menu” is displayed.

Overriding Allocation

To replace the external Scaleform allocator, developers may follow these two steps:

  1. Create a custom implementation of the SysAlloc interface, which defines the Alloc, Free, and Realloc methods.
  2. Provide an instance of this allocator in the GFx::System constructor during Scaleform initialization.

A default Scaleform::SysAllocMalloc implementation, which relies on the standard malloc/free and their system-specific alternatives with alignment support, is provided by Scaleform and may be used directly, or as reference.

Implementing a custom SysAlloc

The simplest way to implement a custom memory heap is to copy the Scaleform SysAllocMalloc implementation, modifying it to make calls to another memory allocator. A slightly-modified Windows-specific SysAllocMalloc implementation is included below as reference:

class MySysAlloc : public SysAlloc
{
    public:
    virtual void* Alloc(UPInt size, UPInt align)
    {
        return _aligned_malloc(size, align);
    }

    virtual void  Free(void* ptr, UPInt size, UPInt align)
    {
        SF_UNUSED2(size, align);
        _aligned_free(ptr);
        return true;
    }
    virtual void* Realloc(void* oldPtr, UPInt oldSize, UPInt newSize, UPInt align)
    {
        SF_UNUSED(oldSize);
        return _aligned_realloc(oldPtr, newSize, align);
    }
};

As can be seen, the implementation of the allocator is relatively simple. The MySysAlloc class derives from a base SysAlloc interface and implements three virtual functions: Alloc, Free, and Realloc. Although implementation of these functions must honor alignment, Scaleform will typically request only small alignments, such as 16 bytes or less. The oldSize and align arguments are passed into Free/Realloc interface to simplify implementation; they are helpful when implementing Realloc as a wrapper for a pair of Alloc/Free calls, for example.

Once developers have created an instance of their own allocator, it can be passed to the GFx::System constructor during Scaleform initialization:

MySysAlloc myAlloc;
System gfxSystem(&myAlloc);

In Scaleform 3.0, the GFx::System object needs to be created before any other Scaleform object and destroyed after all Scaleform objects are released. This is typically best done as part of the allocation initialization function, which calls code that uses Scaleform. However, it can also be part of another allocated object whose lifetime exceeds that of Scaleform. GFx::System should NOT be globally declared. If such use is not convenient, the GFx::System::Init() and GFxSystem::Destroy() static functions may be called instead, without instantiating an object. Similar to the GFx::System constructor, GFx::System::Init() takes a SysAlloc pointer argument.