C
Getting graphics on the screen
This chapter explains how to implement the platform interface to render pixels onto the screen. It describes how to implement basic support for a single screen with no hardware layers and no hardware acceleration. See Implementing hardware layer support on that topic.
Overview
Qt Quick Ultralite core converts the QML scene into a series of rendering commands to blend them into a framebuffer. It's the platform library's responsibility to provide this framebuffer, which is then presented to the screen. If the target hardware comes with hardware-accelerated graphics support, the platform library also provides an implementation of the rendering commands for it.
Implementing basic graphics
Qt Quick Ultralite core communicates with the platform hardware through the PlatformContext instance. To show content on the screen, a platform has to implement the graphics-related parts of the context. The first method to implement is PlatformContext::availableScreens.
Note: The example implementation assumes that there is only one screen without additional layers.
static PlatformInterface::Screen platformScreens[] = { PlatformInterface::Screen(PlatformInterface::Size(ScreenWidth, ScreenHeight), ScreenColorFormat)}; Qul::PlatformInterface::Screen *ExamplePlatform::availableScreens(size_t *screenCount) const { *screenCount = sizeof(platformScreens) / sizeof(PlatformInterface::Screen); return platformScreens; }
PlatformContext::availableScreens returns an array of available screens on the device. The returned PlatformInterface::Screen instances provide the screen identifier, its size in pixels, and its color format. It also tells Qt Quick Ultralite core whether the screen supports resizing, which in practice means whether the platform can support multiple resolutions or framebuffer dimensions. In this example we rely on the default behavior of the Screen constructor, which is no resizing support.
If your device supports multiple screens, you should provide a name as identifier for each screen in its constructor.
Next, implement the PlatformContext::initializeDisplay method that is called once per screen that the application wants to use. If the screen supports resizing, it will resize itself according to the root QML item's size. Based on the screen size, the platform library can now initialize the display hardware.
void ExamplePlatform::initializeDisplay(const PlatformInterface::Screen *screen) { // const int screenIndex = screen - platformScreens; // initLcd(screenIndex, screen->size().width(), screen->size().height()); // initTouchInput(screenIndex); }
You also need to provide framebuffers to render into. If the screen has a fixed size, you can statically allocate them like this:
// Assuming we use 32bpp framebuffers static const int BytesPerPixel = 4; unsigned char framebuffer[2][BytesPerPixel * ScreenWidth * ScreenHeight] __attribute__((section(".framebufferSection"))); static int backBufferIndex = 0;
Two framebuffers are usually used to achieve better performance and repeatedly refresh directly from memory when the display hardware needs it. One is the back buffer, which Qt Quick Ultralite core does the rendering of the next frame into, and the other is the front buffer which gets displayed on screen. To report the fact that this buffering type is used, return FlippedDoubleBuffering from PlatformContext::frameBufferingType. The variable backBufferIndex
is used to track which of the two framebuffers is currently used as the back buffer.
FrameBufferingType ExamplePlatform::frameBufferingType(const PlatformInterface::LayerEngine::ItemLayer *) const { return FlippedDoubleBuffering; }
Qt Quick Ultralite core uses the value returned from PlatformContext::frameBufferingType to determine whether it can do partial rendering updates of only the parts of the screen that changed, instead of redrawing the full framebuffer for each frame.
Note: Some custom architectures might not use framebuffers at all, for example using command buffers to update the display just in time. In such a case, Platform::OtherBuffering must be returned to inform Qt Quick Ultralite core that full repaints have to be done for each frame.
Some platforms might not have enough RAM to fit even a single full framebuffer. Using a Partial framebuffer can significantly lower the memory requirements of an application, but can lead to reduced performance and potential visual tearing artifacts for complex applications. In such case, Platform::PartialBuffering must be returned to inform Qt Quick Ultralite core that partial updates should be split to fit the partial framebuffer size. See Implementing partial framebuffering support for more information.
Unless partial buffering is chosen, Qt Quick Ultralite core calls PlatformContext::beginFrame and PlatformContext::endFrame once for each frame, whenever a screen needs a visual update.
beginFrame returns an instance of PlatformInterface::DrawingDevice containing:
- the pixel format and screen or layer size,
- a pointer to the framebuffer to render into,
- the number of bytes per line,
- and the drawing engine to be used to render into the buffer.
When using only the default software rendering included in Qt Quick Ultralite, without any hardware acceleration, a plain PlatformInterface::DrawingEngine can be used. Otherwise a custom drawing engine subclassing PlatformInterface::DrawingEngine has to be implemented. This is explained in detail in a later section.
PlatformInterface::DrawingDevice *ExamplePlatform::beginFrame(const PlatformInterface::LayerEngine::ItemLayer *layer, const PlatformInterface::Rect &rect, int refreshInterval) { static Qul::PlatformInterface::DrawingEngine drawingEngine; requestedRefreshInterval = refreshInterval; // Wait until the back buffer is free, i.e. no longer held by the display waitForBufferFlip(); // A pointer to the back buffer uchar *bits = framebuffer[backBufferIndex]; static PlatformInterface::DrawingDevice buffer(Qul::PixelFormat_RGB32, PlatformInterface::Size(ScreenWidth, ScreenHeight), bits, ScreenWidth * BytesPerPixel, // Bytes per line &drawingEngine); buffer.setBits(bits); return &buffer; }
waitForBufferFlip
is called to make sure the back buffer (that was the front buffer of the previous frame) has been released by the display controller and is ready to be rendered into. How to implement it is explained later in the context of presentFrame
.
PlatformContext::endFrame is called after rendering of a frame for a given screen is completed. In the case of multiple screens or layers, some platforms might want to flush the hardware-accelerated blending unit's command buffers here, but in most cases it can be left empty.
void ExamplePlatform::endFrame(const PlatformInterface::LayerEngine::ItemLayer *layer) {}
After rendering for a screen, the function PlatformContext::presentFrame is called by Qt Quick Ultralite core. This is where the platform should make the buffer that has just been rendered visible on the display. The example uses double buffering, so it passes the pointer to the back buffer to the display, and swap the front and back buffers so that the next frame gets rendered to what was the previous frame's front buffer.
FrameStatistics ExamplePlatform::presentFrame(const PlatformInterface::Screen *screen, const PlatformInterface::Rect &rect) { waitForRefreshInterval(); // Update the framebuffer address // LCD_SetBufferAddr(framebuffer[backBufferIndex]); // Now the front and back buffers are swapped if (backBufferIndex == 0) backBufferIndex = 1; else backBufferIndex = 0; return FrameStatistics(); }
The presentFrame
function calls waitForRefreshInterval
, to enable reducing the refresh rate when requested to do so by the Qt Quick Ultralite core. This requires keeping track of how many refreshes have happened since the last call to presentFrame
, and waiting until this count reaches the requested refresh interval.
static void waitForRefreshInterval() { if (refreshCount < requestedRefreshInterval) { uint64_t startTime = getPlatformInstance()->currentTimestamp(); while (refreshCount < requestedRefreshInterval) { // The device may yield or go into sleep mode } idleTimeWaitingForDisplay += getPlatformInstance()->currentTimestamp() - startTime; } refreshCount = 0; }
This assumes there's also an interrupt handler that tracks the screen refresh count:
static volatile int refreshCount = 1; volatile unsigned int currentFrame = 0; // Note: This line incrementing the refreshCount will need to be moved to the // actual interrupt handler for the display available on the target platform. It // needs to be called once per vertical refresh of the display, to keep track of // how many refreshes have happened between calls to presentFrame in order to // support custom refresh intervals. On some implementations this can be done // using a so called "line event". void LCD_RefreshInterruptHandler() { ++refreshCount; // currentFrame is only needed if the layer backend is used ++currentFrame; }
Finally, presentFrame
can tell the display controller to start refreshing from the back buffer instead of the currently held front buffer. This happens asynchronously, as the display controller might still be in the process of scanning the front buffer for display. It's therefore necessary to set waitingForBufferFlip
to true, and use an interrupt handler to get notified by the display controller when the old front buffer is released and can be used as the new back buffer for drawing into:
static volatile bool waitingForBufferFlip = false; static uint32_t idleTimeWaitingForDisplay = 0; static void waitForBufferFlip() { // Has there already been a buffer flip? if (!waitingForBufferFlip) return; const uint64_t startTime = getPlatformInstance()->currentTimestamp(); while (waitingForBufferFlip) { // The device may yield or go into sleep mode } idleTimeWaitingForDisplay = getPlatformInstance()->currentTimestamp() - startTime; } // Note: This line clearing waitingForBufferFlip will need to be moved to the // actual interrupt handler for the display available on the target platform. // It's needed to inform about when the buffer address used to scan out pixels // to the display has been updated, making the buffer free in order to start // drawing the next frame. void LCD_BufferFlipInterruptHandler() { waitingForBufferFlip = false; }
PlatformContext::presentFrame should just return a default constructed FrameStatistics
value.
Initializing the graphics engine
The graphics rendering engine internals need to be initialized before any graphics rendering. Add the initialization call to your implementation of Initializing platform from the previous chapters.
Depending on the color-depth of the framebuffer, you should call each of the functions of the color format you a going to use:
- Qul::PlatformInterface::initializeRgb332CpuFallbackDrawingEngine()
- Qul::PlatformInterface::initializeRgb16CpuFallbackDrawingEngine()
- Qul::PlatformInterface::initializeArgb4444CpuFallbackDrawingEngine()
- Qul::PlatformInterface::initializeRgb888CpuFallbackDrawingEngine()
- Qul::PlatformInterface::initializeBgr888CpuFallbackDrawingEngine()
- Qul::PlatformInterface::initializeRgb32CpuFallbackDrawingEngine()
- Qul::PlatformInterface::initializeArgb32CpuFallbackDrawingEngine()
This initializes the CPU-based fallback drawing engine in Qt Quick Ultralite core. It is necessary unless the platform itself provides the entire virtual DrawingEngine API without relying on the default implementations, or explicitly uses the DrawingEngine::fallbackDrawingEngine()
.
void ExamplePlatform::initializePlatform() { Qul::PlatformInterface::initializeRgb32CpuFallbackDrawingEngine(); ...
After building and flashing to the device it should show animation similar to this.
You have now completed the second phase of the porting guide and can commit your state. The next phase will be handling touch input from the screen.
See also Framebuffer Requirements and Partial Framebuffer.
Available under certain Qt licenses.
Find out more.