このページでは

C

画面へのグラフィックの表示

この章では、画面にピクセルを描画するためのプラットフォームインターフェースの実装方法について説明します。ハードウェアレイヤーやハードウェアアクセラレーションを使用しない、単一の画面に対する基本的なサポートの実装方法について解説します。このトピックについては、「ハードウェアレイヤーサポートの実装」を参照してください。

概要

Qt Quick Ultraliteコアは、QMLシーンを一連のレンダリングコマンドに変換し、それらをフレームバッファに合成します。 このフレームバッファを提供し、それを画面に表示するのは、プラットフォームライブラリの役割です。ターゲットハードウェアにハードウェアアクセラレーションによるグラフィックスサポートが備わっている場合、プラットフォームライブラリはそれに対応したレンダリングコマンドの実装も提供します。

基本的なグラフィックスの実装

Qt Quick Ultraliteコアは、PlatformContext インスタンスを介してプラットフォームのハードウェアと通信します。画面にコンテンツを表示するには、プラットフォーム側がコンテキストのグラフィックス関連部分を実装する必要があります。最初に実装すべきメソッドはPlatformContext::availableScreens です。

注:この 実装例では 、追加のレイヤーがなく、スクリーンが1つだけ存在することを前提としています。

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 デバイス上で利用可能な画面の配列を返します。 返されるPlatformInterface::Screen インスタンスは、画面識別子、ピクセル単位のサイズ、および色形式を提供します。また、画面がサイズ変更に対応しているかどうかをQt Quick Ultraliteコアに伝えます。これは実際には、プラットフォームが複数の解像度やフレームバッファの寸法に対応できるかどうかを意味します。この例では、Screen コンストラクタのデフォルトの動作(サイズ変更非対応)を利用しています。

デバイスが複数の画面をサポートしている場合は、各画面のコンストラクタで識別子として名前を指定する必要があります。

次に、アプリケーションが使用したい画面ごとに 1 回呼び出される `PlatformContext::initializeDisplay ` メソッドを実装します。画面がリサイズに対応している場合、ルート QML アイテムのサイズに応じて自動的にリサイズされます。画面サイズに基づいて、プラットフォームライブラリはディスプレイハードウェアを初期化できるようになります。

void ExamplePlatform::initializeDisplay(const PlatformInterface::Screen *screen)
{
    // const int screenIndex = screen - platformScreens;
    // initLcd(screenIndex, screen->size().width(), screen->size().height());
    // initTouchInput(screenIndex);
}

また、レンダリング用のフレームバッファも指定する必要があります。画面のサイズが固定されている場合は、次のように静的に割り当てることができます。

// Assuming we use 32bpp framebuffers
static const int BytesPerPixel = 4;
unsigned char framebuffer[2][BytesPerPixel * ScreenWidth * ScreenHeight]
    __attribute__((section(".framebufferSection")));
static int backBufferIndex = 0;

通常、パフォーマンスを向上させるために 2 つのフレームバッファが使用され、ディスプレイハードウェアが必要とする際にメモリから直接繰り返しリフレッシュが行われます。1 つはバックバッファで、Qt Quick Ultralite コアが次のフレームのレンダリングを行う対象となり、もう 1 つはフロントバッファで、画面に表示されるものです。 このバッファリング方式が使用されていることを報告するには、PlatformContext::frameBufferingType からFlippedDoubleBuffering を返します。変数backBufferIndex は、2つのフレームバッファのうち、現在どちらがバックバッファとして使用されているかを追跡するために使用されます。

FrameBufferingType ExamplePlatform::frameBufferingType(const PlatformInterface::LayerEngine::ItemLayer *) const
{
    return FlippedDoubleBuffering;
}

Qt Quick Ultraliteコアは、PlatformContext::frameBufferingType から返される値を使用して、フレームごとにフレームバッファ全体を再描画するのではなく、変更された画面部分のみの部分的なレンダリング更新が可能かどうかを判断します。

注:一部の カスタムアーキテクチャでは、フレームバッファをまったく使用せず、たとえばコマンドバッファを使用してディスプレイをジャストインタイムで更新する場合があります。そのような場合、Platform::OtherBuffering を返して、Qt Quick Ultraliteコアに対し、各フレームごとに完全な再描画を行う必要があることを通知する必要があります。

一部のプラットフォームでは、1つの完全なフレームバッファさえ収めるのに十分なRAMがない場合があります。部分フレームバッファを使用すると、アプリケーションのメモリ要件を大幅に低減できますが、複雑なアプリケーションではパフォーマンスの低下や、画面のティアリング現象が発生する可能性があります。 そのような場合、Platform::PartialBuffering を返して、Qt Quick Ultraliteコアに対し、部分的な更新を部分フレームバッファのサイズに合わせて分割する必要があることを通知する必要があります。詳細については、「部分フレームバッファリングのサポートの実装」を参照してください。

部分バッファリングが選択されていない場合、Qt Quick Ultraliteコアは、画面の視覚的な更新が必要なたびに、フレームごとに1回ずつPlatformContext::beginFrame およびPlatformContext::endFrame を呼び出します。

beginFrame PlatformInterface::DrawingDevice のインスタンスを返し、その中には以下が含まれます:

  • ピクセルフォーマットおよび画面またはレイヤーのサイズ、
  • レンダリング先のフレームバッファへのポインタ、
  • 1行あたりのバイト数、
  • および、バッファへのレンダリングに使用する描画エンジン。

Qt Quick Ultraliteに標準で含まれるデフォルトのソフトウェアレンダリングのみを使用し、ハードウェアアクセラレーションを一切行わない場合は、単純なPlatformInterface::DrawingEngine を使用できます。それ以外の場合は、PlatformInterface::DrawingEngine をサブクラス化したカスタム描画エンジンを実装する必要があります。これについては、後のセクションで詳しく説明します。

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 は、バックバッファ(前のフレームのフロントバッファだったもの)がディスプレイコントローラによって解放され、レンダリングの準備が整っていることを確認するために呼び出されます。その実装方法については、後述のpresentFrame の文脈で説明します。

PlatformContext::endFrame は、特定の画面に対するフレームのレンダリングが完了した後に呼び出されます。複数の画面やレイヤーがある場合、プラットフォームによってはここでハードウェアアクセラレーションされたブレンディングユニットのコマンドバッファをフラッシュしたい場合もありますが、ほとんどの場合は空のままにしておいても問題ありません。

void ExamplePlatform::endFrame(const PlatformInterface::LayerEngine::ItemLayer *layer) {}

ある画面のレンダリングが完了すると、Qt Quick Ultraliteコアによって関数PlatformContext::presentFrame が呼び出されます。ここで、プラットフォームは、レンダリングされたばかりのバッファをディスプレイ上に表示可能にする必要があります。 この例ではダブルバッファリングを使用しているため、バックバッファへのポインタをディスプレイに渡して、フロントバッファとバックバッファを入れ替えます。これにより、次のフレームは前のフレームのフロントバッファだった場所にレンダリングされるようになります。

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();
}

presentFrame 関数はwaitForRefreshInterval を呼び出し、Qt Quick のUltraliteコアから要求があった際にリフレッシュレートを低減できるようにします。これには、presentFrame が最後に呼び出されてから何回のリフレッシュが行われたかを追跡し、このカウントが要求されたリフレッシュ間隔に達するまで待機する必要があります。

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;
}

これには、画面のリフレッシュ回数を追跡する割り込みハンドラも存在することが前提となります:

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;
}

最後に、presentFrame を設定することで、ディスプレイコントローラに対し、現在保持されているフロントバッファではなく、バックバッファからリフレッシュを開始するよう指示できます。これは非同期に行われます。なぜなら、ディスプレイコントローラがまだ表示のためにフロントバッファをスキャンしている最中である可能性があるためです。 そのため、waitingForBufferFlip をtrueに設定し、古いフロントバッファが解放され、描画用の新しいバックバッファとして使用可能になった際に、ディスプレイコントローラから通知を受けるために割り込みハンドラを使用する必要があります:

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 は、デフォルト構築されたFrameStatistics 値を返すだけでよい。

グラフィックスエンジンの初期化

グラフィックスレンダリングを行う前に、グラフィックスレンダリングエンジンの内部を初期化する必要があります。前の章で説明した「プラットフォームの初期化」の実装に、初期化呼び出しを追加してください。

フレームバッファの色深度に応じて、使用するカラーフォーマットに対応する各関数を呼び出す必要があります:

これは、Qt Quick Ultraliteコア内のCPUベースのフォールバック描画エンジンを初期化するものです。プラットフォーム自体が、デフォルトの実装に依存せずに仮想DrawingEngine API全体を提供している場合、またはDrawingEngine::fallbackDrawingEngine() を明示的に使用している場合を除き、この処理は必須です。

void ExamplePlatform::initializePlatform()
{
    Qul::PlatformInterface::initializeRgb32CpuFallbackDrawingEngine();
    ...

ビルドしてデバイスにフラッシュすると、次のようなアニメーションが表示されるはずです。

これで移植ガイドの第2フェーズが完了し、状態をコミットできます。次のフェーズでは、画面からのタッチ入力の処理を行います。

フレームバッファの要件」および「部分フレームバッファも参照してください

特定のQtライセンスの下で利用可能です。
詳細はこちらをご覧ください。