このページでは

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つのフレームバッファが使用されます。ひとつは、Qt Quick Ultraliteコアが次のフレームのレンダリングを行うバックバッファで、もうひとつは画面に表示されるフロントバッファです。このバッファリングタイプが使用されていることを報告するには、PlatformContext::frameBufferingType からFlippedDoubleBuffering を返します。変数backBufferIndex は、2つのフレームバッファのうちどちらが現在バックバッファとして使用されているかを追跡するために使用されます。

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

Qt Quick Ultraliteコアは、PlatformContext::frameBufferingType から返された値を使用して、各フレームに対してフレームバッファ全体を再描画する代わりに、変更された画面部分のみの部分的なレンダリング更新を行うことができるかどうかを決定します。

注意: カスタム・アーキテクチャの中には、フレームバッファをまったく使用しないものもあるかもしれません。たとえば、コマンドバッファを使用して表示をジャスト・イン・タイムで更新するような場合です。そのような場合、Qt Quick Ultraliteコアに、各フレームに対してフルリペイントを行う必要があることを通知するために、Platform::OtherBuffering

プラットフォームによっては、1つのフルフレームバッファを収めるのに十分なRAMがないかもしれません。パーシャルフレームバッファを使用すると、アプリケーションのメモリ要件を大幅に下げることができますが、複雑なアプリケーションではパフォーマンスが低下し、ビジュアルティアリングのアーチファクトが発生する可能性があります。このような場合、Qt Quick Ultraliteコアに、部分フレームバッファサイズに合わせて部分更新を分割する必要があることを通知するために、Platform::PartialBuffering を返す必要があります。詳細については、部分フレームバッファリング・サポートの実装を参照してください。

パーシャルバッファリングが選択されない限り、Qt Quick Ultraliteコアは、スクリーンがビジュアルアップデートを必要とするときはいつでも、各フレームごとにPlatformContext::beginFramePlatformContext::endFrame を1回呼び出します。

beginFramePlatformInterface::DrawingDevice のインスタンスを返します:

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

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) {}

1画面分のレンダリングが終わると、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 ライセンスによっては利用可能です。