C

Memory allocation in Qt Quick Ultralite platform abstraction

All memory allocations from the Qt Quick Ultralite core libary use a dedicated set of functions, enabling you to handle dynamic memory allocation requests depending on the platform you are porting to.

Basic memory allocation

The Qt Quick Ultralite core library handles small memory allocations using the following functions:

You can forward these calls to the platform-specific functions as shown below:

void *qul_malloc(std::size_t size)
{
    return std::malloc(size);
}

void qul_free(void *ptr)
{
    std::free(ptr);
}

void *qul_realloc(void *ptr, size_t s)
{
    return std::realloc(ptr, s);
}

Memory allocator API

The Qt Quick Ultralite core library uses the memory allocator API for larger memory allocations. This API provides more control over how and where to allocate memory for different purposes. For example, it might make sense to store image data in VRAM on some platforms. The API also requires the Qt Quick Ultralite core library to explicitly acquire and release memory before and after writing to it, letting the platform do any necessary synchronization.

Here's what an example memory allocator might look like:

class ExampleMemoryAllocator : public PlatformInterface::MemoryAllocator
{
public:
    void *allocate(std::size_t size, UsagePattern) override
    {
        void *ptr = nullptr;
        // ptr = HW_VideoMemAlloc(size);
        return ptr;
    }

    void *reallocate(void *ptr, std::size_t size) override
    {
        void *new_ptr = nullptr;
        // new_ptr = HW_VideoMemRealloc(ptr, size);
        return new_ptr;
    }

    void free(void *ptr) override
    {
        // HW_VideoMemFree(ptr);
    }

    void acquire(void * /* ptr */, std::size_t /* offset */, std::size_t /* size */) override
    {
        // HW_FinishRendering();
    }

    void release(void * /* ptr */, std::size_t /* offset */, std::size_t /* size */) override
    {
        // CleanInvalidateDCache_by_Addr(static_cast<char *>(addr) + offset, size);
    }
};

In this example, it's assumed that there's a hardware API available to allocate video memory instead of normal memory. Although, a custom platform port might choose to use some other memory regions.

The Qul::PlatformInterface::MemoryAllocator::acquire function override is called before writing to a video memory. It ensures that the pending asynchronous hardware rendering operations finish first, to avoid overwriting a memory location that is read by those asynchronous operations.

In the Qul::PlatformInterface::MemoryAllocator::release function override, the data caches are flushed to ensure that the hardware blending unit reads the newly written data.

Next, here's the example implementation of the Qul::Platform::memoryAllocator function:

PlatformInterface::MemoryAllocator *memoryAllocator(PlatformInterface::MemoryAllocator::AllocationType type)
{
    static ExampleMemoryAllocator exampleMemoryAllocator;
    static PlatformInterface::MemoryAllocator defaultMemoryAllocator;

    switch (type) {
    case PlatformInterface::MemoryAllocator::Image:
        return &exampleMemoryAllocator;
    default:
        return &defaultMemoryAllocator;
    }
}

Here we use our example memory allocator for image allocations, but fall back to the default implementation for other allocations. The default implementation forwards control to the Qul::Platform::qul_malloc, Qul::Platform::qul_realloc, and Qul::Platform::qul_free functions.

Memory statistics

The platform offers functions to get memory statistics like heap and stack sizes.

The Qul::Platform::printHeapStats and Qul::Platform::printStackStats functions must be implemented to print such information. You could use the mallinfo() function, if it is available on your platform.

For GCC compilers:

void printHeapStats(void)
{
    struct mallinfo mi = mallinfo();
    PlatformInterface::log("Heap: %u/%u (in-use/total)\r\n", mi.uordblks, mi.arena);
}

void printStackStats(void)
{
    // Replace this with actual measuring for your platform
    uint32_t maxUsedStackSize = 0;
    PlatformInterface::log("Stack: %u (peak)\r\n", maxUsedStackSize);
}

For IAR compilers:

void printHeapStats(void)
{
    struct mallinfo mi = __iar_dlmallinfo();
    PlatformInterface::log("Heap: %u/%u (in-use/total)\r\n", mi.uordblks, mi.arena);
}

void printStackStats(void)
{
    // Replace this with actual measuring for your platform
    uint32_t maxUsedStackSize = 0;
    PlatformInterface::log("Stack: %u (peak)\r\n", maxUsedStackSize);
}

Fast memory allocations

Use Qul::Platform::StackAllocator to allocate short-term memory faster than a regular allocation. An example usage can be found in its class documentation.

Preallocate static memory to prepare the allocator. This is done by reserving a static amount of memory and initializing the StackAllocator's static members.

static char qul_scratch_buffer[16 * 1024];

char *StackAllocator::m_buffer = qul_scratch_buffer;
char *StackAllocator::m_top = StackAllocator::m_buffer;
int32_t StackAllocator::m_size = sizeof(qul_scratch_buffer);

The code in the earlier example reserves 16kB of buffer, and sets its pointers and size to the StackAllocator.

Note: The stack allocator must be initialized so that the Qt Quick Ultralite core library can use it.

Available under certain Qt licenses.
Find out more.