比QT更高效的一款开源嵌入式图形工具EGT-Ensemble Graphics Toolkit

news2025/3/10 21:29:53

文章目录

    • EGT-Ensemble Graphics Toolkit介绍
    • EGT具备非常高的图形渲染效率
      • EGT采用了非常优秀的开源2D图形处理引擎-Cairo
      • 开源2D图形处理引擎Cairo的优势
      • Cairo 2D图像引擎的性能
      • Cairo 2D图像引擎的实际应用案例
      • 彩蛋 - 开源EDA软件KiCAD也在使用Cairo
    • EGT高效的秘诀还有哪些
      • Cairo需要依赖Pixman
      • Pixman针对不同平台有优化
    • EGT vs QT5实际效果PK
    • 代码贴图

很多介绍资料直接来自豆包,仅代表个人意见和理解,不喜勿喷

  • EGT-Ensemble Graphics Toolkit

EGT-Ensemble Graphics Toolkit介绍

The Ensemble Graphics Toolkit (EGT)是MIcrochip针对旗下ARM9、SAMA5处理器推出来的一款运行于嵌入式Linux的C++ GUI开发工具套件。EGT(嵌入式图形工具包)提供了现代化的图形用户界面(GUI)功能、外观样式,并在嵌入式 Linux 应用中尽可能贴近底层硬件的同时最大限度地提升性能。关键词是开源、免费商用

官方介绍可以点击这里
官方给出的EGT组件依赖框图

EGT具备非常高的图形渲染效率

EGT采用了非常优秀的开源2D图形处理引擎-Cairo

在这里插入图片描述
在这里插入图片描述

开源2D图形处理引擎Cairo的优势

在这里插入图片描述
在这里插入图片描述

Cairo 2D图像引擎的性能

在这里插入图片描述

Cairo 2D图像引擎的实际应用案例

在这里插入图片描述
在这里插入图片描述

彩蛋 - 开源EDA软件KiCAD也在使用Cairo

在这里插入图片描述

EGT高效的秘诀还有哪些

Cairo使用Pixman来加速底层像素的操作,Pixman能够提供图像的合成、alpha 通道处理、色彩空间转换等基本的像素级别的操作

Cairo需要依赖Pixman

在这里插入图片描述

Pixman针对不同平台有优化

Pixman针对ARM SIMD架构、带NEON或者MIPS、X86等架构,都有专门针对性的优化代码,来尽可能利用处理器硬件特性,加速像素的处理速度在这里插入图片描述

EGT vs QT5实际效果PK

在Microchip SAMA5D27开发板上,跑Microchip EGT提供的潜水员例程,该例程会有潜水员的动态图片,同时也有2条鱼在界面上从左到右在游动,另外还不断有泡泡从海底喷出。同时将该例程功能移植到QT5上,然后两者在实际硬件上运行,对比CPU占用率的差异
在这里插入图片描述
视频对比
基于EGT开发的demo,全程CPU占用率在22%左右,而QT5基本都在60%以上

代码贴图

个人对QT开发不是很熟悉,欢迎提出更好的优化方案
以下是QT5

#include <QApplication>
#include <QPainter>
#include <QTimer>
#include <QTime>
#include <QLabel>
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , fishFrame(0)
    , fish2Frame(0)
    , diverFrame(0)
    , bubblePixmap(":/images/smallbubble.png")
{
    ui->setupUi(this);

    // Load fish images and split them into 6 parts
    QPixmap fishPixmap(":/images/fish.png");
    QPixmap fish2Pixmap(":/images/fish2.png");
    QPixmap diverPixmap(":/images/diver.png");

    int frameWidth  = fishPixmap.width() /4;
    int frameHeight = fishPixmap.height()/2;

    for (int j = 0; j < 2; j++) {
        for (int i = 0; i < 4; ++i) {
            fishPixmaps.push_back(fishPixmap.copy(i * frameWidth, frameHeight * j, frameWidth, frameHeight));
        }
    }

    frameWidth  = fish2Pixmap.width() /2;
    frameHeight = fish2Pixmap.height()/3;

    for (int j = 0; j < 3; j++) {
        for (int i = 0; i < 2; ++i) {
            fish2Pixmaps.push_back(fish2Pixmap.copy(i * frameWidth, frameHeight * j, frameWidth, frameHeight));
        }
    }

    frameWidth  = diverPixmap.width();
    frameHeight = diverPixmap.height()/16;

    for (int i = 0; i < 16; ++i) {
        diverPixmaps.push_back(diverPixmap.copy(0, frameHeight * i, frameWidth, frameHeight));
    }

    // Set the background image
    ui->backgroundLabel->setPixmap(QPixmap(":/images/water_1080.png"));
    ui->backgroundLabel->setScaledContents(true);

    // Set the initial fish images
    ui->fishLabel->setPixmap(fishPixmaps[0]);
    ui->fishLabel->setScaledContents(true);

    ui->fish2Label->setPixmap(fish2Pixmaps[0]);
    ui->fish2Label->setScaledContents(true);

    // Set the diver image
    ui->diverLabel->setPixmap(diverPixmaps[0]);
    ui->diverLabel->setScaledContents(true);

    // Create timers for moving the fish
    moveTimer = new QTimer(this);
    connect(moveTimer, &QTimer::timeout, this, &MainWindow::moveFish);
    moveTimer->start(50);

    moveTimer2 = new QTimer(this);
    connect(moveTimer2, &QTimer::timeout, this, &MainWindow::moveFish2);
    moveTimer2->start(50);

    // Create timers for animating the fish
    animateTimer = new QTimer(this);
    connect(animateTimer, &QTimer::timeout, this, &MainWindow::animateFish);
    animateTimer->start(100);

    animateTimer2 = new QTimer(this);
    connect(animateTimer2, &QTimer::timeout, this, &MainWindow::animateFish2);
    animateTimer2->start(100);

    // Create timers for animating the diver
    animateTimer3 = new QTimer(this);
    connect(animateTimer3, &QTimer::timeout, this, &MainWindow::animateDiver);
    animateTimer3->start(100);

    cpuTimer = new QTimer(this);
    connect(cpuTimer, &QTimer::timeout, this, &MainWindow::updateClock);
    cpuTimer->start(1000); // 每秒更新一次时钟

    // Create timer for creating bubbles
    bubbleTimer = new QTimer(this);
    connect(bubbleTimer, &QTimer::timeout, this, &MainWindow::createBubble);
    bubbleTimer->start(1000);

    // 显示 CPU 使用率的标签
    cpuLabel = new QLabel(this);
    cpuLabel->setGeometry(580, 5, 200, 40);
    cpuLabel->setStyleSheet("font-size: 20px; color: red;");
    cpuLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);

    cpuUsage = new CPUUsage(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::moveFish()
{
    int x = ui->fishLabel->x() + 5;
    if (x > this->width()) {
        x = -ui->fishLabel->width();
        int y = QRandomGenerator::global()->bounded(this->height() - ui->fishLabel->height());
        ui->fishLabel->move(x, y);
    } else {
        ui->fishLabel->move(x, ui->fishLabel->y());
    }
}

void MainWindow::moveFish2()
{
    int x = ui->fish2Label->x() + 5;
    if (x > this->width()) {
        x = -ui->fish2Label->width();
        int y = QRandomGenerator::global()->bounded(this->height() - ui->fish2Label->height());
        ui->fish2Label->move(x, y);
    } else {
        ui->fish2Label->move(x, ui->fish2Label->y());
    }
}

void MainWindow::animateFish()
{
    fishFrame = (fishFrame + 1) % fishPixmaps.size();
    ui->fishLabel->setPixmap(fishPixmaps[fishFrame]);
}

void MainWindow::animateFish2()
{
    fish2Frame = (fish2Frame + 1) % fish2Pixmaps.size();
    ui->fish2Label->setPixmap(fish2Pixmaps[fish2Frame]);
}

void MainWindow::animateDiver()
{
    diverFrame = (diverFrame + 1) % diverPixmaps.size();
    ui->diverLabel->setPixmap(diverPixmaps[diverFrame]);
}

void MainWindow::updateClock()
{
    cpuLabel->setText(QString("CPU Usage: %1%").arg(cpuUsage->getCPUUsage(), 0, 'f', 2));
    update();
}

void MainWindow::createBubble()
{
    int bubbleCount = QRandomGenerator::global()->bounded(1, 4); // Random number of bubbles between 1 and 5
    for (int i = 0; i < bubbleCount; ++i) {
        int x = QRandomGenerator::global()->bounded(this->width());
        int y = this->height();
        int size = QRandomGenerator::global()->bounded(5, 32); // Random size between 10 and 50
        int xspeed = 0;
        int yspeed = -QRandomGenerator::global()->bounded(5, 20); // Random speed between 1 and 10
        Bubble* bubble = new Bubble(bubblePixmap, xspeed, yspeed, QPoint(x, y), size, this);
        bubbles.push_back(bubble);
        bubble->show();
        if (bubble->getcount() >= 20) {
            break;
        }
    }
}

以下是EGT

/*
 * Copyright (C) 2018 Microchip Technology Inc.  All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#include <chrono>
#include <cmath>
#include <cstring>
#include <iostream>
#include <map>
#include <egt/ui>
#include <random>
#include <sstream>
#include <string>
#include <vector>

using namespace std;
using namespace egt;

class Bubble : public ImageLabel
{
public:
    Bubble(int xspeed, int yspeed, const Point& point) noexcept
        : ImageLabel(Image("file:smallbubble.png"), "", Rect(point, Size())),
          m_xspeed(xspeed),
          m_yspeed(yspeed)
    {
        flags().set(Widget::Flag::no_layout);
    }

    Bubble(const Bubble&) = default;
    Bubble(Bubble&&) = default;
    Bubble& operator=(const Bubble&) = default;
    Bubble& operator=(Bubble&&) = default;
    virtual ~Bubble() = default;

    bool animate()
    {
        bool visible = Rect(Point(0, 0), Application::instance().screen()->size()).intersect(box());

        if (visible)
        {
            Point to(box().point());
            to += Point(m_xspeed, m_yspeed);
            move(to);
        }

        return visible;
    }

private:

    Bubble() = delete;

    int m_xspeed;
    int m_yspeed;
};

class MainWindow : public TopWindow
{
public:
    MainWindow()
        : TopWindow(Size()),
          e1(r())
    {
        background(Image("file:water.png"));

        m_label = make_shared<Label>("Objects: 0",
                                     Rect(Point(10, 10),
                                          Size(150, 40)),
                                     AlignFlag::left | AlignFlag::center_vertical);
        m_label->color(Palette::ColorId::text, Palette::white);
        m_label->color(Palette::ColorId::bg, Palette::transparent);
        add(top(left(m_label)));

        m_sprite = make_shared<Sprite>(Image("file:diver.png"), Size(390, 312), 16, Point(0, 0));
        m_sprite->no_layout(true);
        add(m_sprite);
        m_sprite->show();
    }

    void handle(Event& event) override
    {
        TopWindow::handle(event);

        switch (event.id())
        {
        case EventId::raw_pointer_move:
            spawn(display_to_local(event.pointer().point));
            break;
        default:
            break;
        }
    }

    void spawn(const Point& p)
    {
        auto xspeed = 0;
        auto yspeed = speed_dist(e1);
        auto offset = offdist(e1);
        auto size = size_dist(e1);

        // has to move at some speed
        if (yspeed == 0)
            yspeed = 1;

        m_images.emplace_back(make_shared<Bubble>(xspeed, yspeed, p));
        auto& image = m_images.back();
        add(image);
        image->image_align(AlignFlag::expand_horizontal | AlignFlag::expand_vertical);
        image->resize_by_ratio(size);
        image->move(Point(p.x() - image->box().width() / 2 + offset,
                          p.y() - image->box().height() / 2 + offset));
        objects_changed();
    }

    void animate()
    {
        for (auto x = m_images.begin(); x != m_images.end();)
        {
            auto& image = *x;
            if (!image->animate())
            {
                image->detach();
                x = m_images.erase(x);
                objects_changed();
            }
            else
            {
                x++;
            }
        }
    }

    void objects_changed()
    {
        ostringstream ss;
        ss << "Objects: " << m_images.size();
        m_label->text(ss.str());
    }

    vector<shared_ptr<Bubble>> m_images;
    shared_ptr<Label> m_label;
    shared_ptr<Sprite> m_sprite;

    std::random_device r;
    std::default_random_engine e1;
    std::uniform_int_distribution<int> speed_dist{-20, -1};
    std::uniform_int_distribution<int> offdist{-20, 20};
    std::uniform_int_distribution<int> size_dist{10, 100};
};

int main(int argc, char** argv)
{
    Application app(argc, argv, "water");
#ifdef EXAMPLEDATA
    add_search_path(EXAMPLEDATA);
#endif
    MainWindow win;

    vector<Sprite*> sprites;

#define SPRITE1
#ifdef SPRITE1
    Sprite sprite1(Image("file:fish.png"), Size(252, 209), 8, Point(0, 0));
    sprite1.no_layout(true);
    win.add(sprite1);
    sprite1.show();
    sprites.push_back(&sprite1);
#endif

#define SPRITE2
#ifdef SPRITE2
    Sprite sprite2(Image("file:fish2.png"), Size(100, 87), 6, Point(0, 0));
    sprite2.no_layout(true);
    win.add(sprite2);
    sprite2.show();
    sprites.push_back(&sprite2);
#endif

    sprites.push_back(win.m_sprite.get());

    PeriodicTimer animatetimer(std::chrono::milliseconds(30));
    animatetimer.on_timeout([&win]()
    {
        win.animate();
    });
    animatetimer.start();

    PeriodicTimer animatetimer2(std::chrono::milliseconds(100));
    animatetimer2.on_timeout([&sprites]()
    {
        for (auto& sprite : sprites)
            sprite->advance();
    });
    animatetimer2.start();

    PeriodicTimer spawntimer(std::chrono::seconds(1));
    spawntimer.on_timeout([&win]()
    {
        if (win.m_images.size() > 30)
            return;

        static std::uniform_int_distribution<int> xoffdist(-win.width() / 2, win.width() / 2);
        int offset = xoffdist(win.e1);

        static std::uniform_int_distribution<int> count_dist(1, 10);
        int count = count_dist(win.e1);

        Point p(win.box().center());
        p.y(win.box().height());
        p.x(p.x() + offset);

        while (count--)
            win.spawn(p);
    });
    spawntimer.start();

#ifdef SPRITE1
    PropertyAnimator a1(-sprite1.size().width(), Application::instance().screen()->size().width(),
                        std::chrono::milliseconds(10000),
                        easing_linear);
    a1.on_change([&sprite1](PropertyAnimator::Value value){
            sprite1.x(value);
        });
    a1.start();

    PeriodicTimer floattimer(std::chrono::milliseconds(1000 * 12));
    floattimer.on_timeout([&a1, &sprite1, &win]()
    {

        static std::uniform_int_distribution<int> yoffdist(0, win.height() - sprite1.size().height());
        int y = yoffdist(win.e1);

        sprite1.move(Point(-sprite1.size().width(), y));
        a1.start();
    });
    floattimer.start();
#endif

#ifdef SPRITE2
    PropertyAnimator a2(-sprite2.size().width(), Application::instance().screen()->size().width(),
                        std::chrono::milliseconds(12000),
                        easing_linear);
    a2.on_change([&sprite2](PropertyAnimator::Value value){
            sprite2.x(value);
        });
    a2.start();

    PeriodicTimer floattimer2(std::chrono::milliseconds(1000 * 15));
    floattimer2.on_timeout([&a2, &sprite2, &win]()
    {
        static std::uniform_int_distribution<int> yoffdist(0, win.height() - sprite2.size().height());
        int y = yoffdist(win.e1);

        sprite2.move(Point(-sprite2.size().width(), y));
        a2.start();
    });
    floattimer2.start();
#endif

    Label label1("CPU: ----");
    label1.color(Palette::ColorId::text, Palette::red);
    label1.color(Palette::ColorId::bg, Palette::transparent);
    win.add(bottom(left(label1)));

    egt::experimental::CPUMonitorUsage tools;
    PeriodicTimer cputimer(std::chrono::seconds(1));
    cputimer.on_timeout([&label1, &tools]()
    {
        tools.update();
        ostringstream ss;
        ss << "CPU: " << static_cast<int>(tools.usage()) << "%";
        label1.text(ss.str());
    });
    cputimer.start();

    win.show();

    return app.run();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2272003.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

密码学精简版

密码学是数学上的一个分支&#xff0c;同时也是计算机安全方向上很重要的基础原理&#xff0c;设置密码的目的是保证信息的机密性、完整性和不可抵赖性&#xff0c;安全方向上另外的功能——可用性则无法保证&#xff0c;可用性有两种方案保证&#xff0c;冗余和备份&#xff0…

WPF通过反射机制动态加载控件

Activator.CreateInstance 是 .NET 提供的一个静态方法&#xff0c;它属于 System 命名空间。此方法通过反射机制根据提供的类型信息。 写一个小demo演示一下 要求&#xff1a;在用户反馈界面点击建议或者评分按钮 弹出相应界面 编写MainWindow.xmal 主窗体 <Window x:C…

C语言 递归编程练习

1.将参数字符串中的字符反向排列&#xff0c;不是逆序打印。 要求&#xff1a;不能使用C函数库中的字符串操作函数。 比如&#xff1a; char arr[] "abcdef"; 逆序之后数组的内容变成&#xff1a;fedcba 1.非函数实现&#xff08;循环&#xff09; 2.用递归方法…

数据插入操作的深度分析:INSERT 语句使用及实践

title: 数据插入操作的深度分析:INSERT 语句使用及实践 date: 2025/1/5 updated: 2025/1/5 author: cmdragon excerpt: 在数据库管理系统中,数据插入(INSERT)操作是数据持久化的基础,也是应用程序与用户交互的核心功能之一。它不仅影响数据的完整性与一致性,还在数据建…

【Linux系列】使用 `nohup` 命令运行 Python 脚本并保存输出日志的详细解析

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【USRP】教程:在Macos M1(Apple芯片)上安装UHD驱动(最正确的安装方法)

Apple芯片 前言安装Homebrew安装uhd安装gnuradio使用b200mini安装好的路径下载固件后续启动频谱仪功能启动 gnu radio关于博主 前言 请参考本文进行安装&#xff0c;好多人买了Apple芯片的电脑&#xff0c;这种情况下&#xff0c;可以使用UHD吗&#xff1f;答案是肯定的&#…

多媒体素材库系统

本文结尾处获取源码。 本文结尾处获取源码。 本文结尾处获取源码。 一、相关技术 后端&#xff1a;Java、JavaWeb / Springboot。前端&#xff1a;Vue、HTML / CSS / Javascript 等。数据库&#xff1a;MySQL 二、相关软件&#xff08;列出的软件其一均可运行&#xff09; I…

EdgeX规则引擎eKuiper

EdgeX 规则引擎eKuiper 一、架构设计 LF Edge eKuiper 是物联网数据分析和流式计算引擎。它是一个通用的边缘计算服务或中间件,为资源有限的边缘网关或设备而设计。 eKuiper 采用 Go 语言编写,其架构如下图所示: eKuiper 是 Golang 实现的轻量级物联网边缘分析、流式处理开源…

即插即用,无痛增强模型生成美感!字节跳动提出VMix:细粒度美学控制,光影、色彩全搞定

文章链接&#xff1a;https://arxiv.org/pdf/2412.20800 代码地址&#xff1a;https://github.com/fenfenfenfan/VMix 项目地址&#xff1a;https://vmix-diffusion.github.io/VMix/ 亮点直击 分析并探索现有模型在光影、色彩等细粒度美学维度上生成图像的差异&#xff0c;提出…

I.MX6ull-PWM

一、PWM介绍 PWM&#xff08;Pulse Width Modulation&#xff09;简称脉宽调制&#xff0c;是利用微处理器的数字输出来对模拟电路进行控制的 一种非常有效的技术&#xff0c;广泛应用在测量、通信、工控等方面。 pwm的工作电路 它的四种时钟信号 (1)High-frequency referenc…

性能测试04|JMeter:连接数据库、逻辑控制器、定时器

目录 一、连接数据库 1、使用场景 2、直连数据库的关键配置 3、案例 ​编辑 二、逻辑控制器 1、if控制器 2、循环控制器 3、ForEach控制器 三、定时器 1、同步定时器 2、常数吞吐量定时器&#xff08;用的少&#xff0c;了解即可&#xff09; 3、固定定时器 一、连…

基于SpringBoot和OAuth2,实现通过Github授权登录应用

基于SpringBoot和OAuth2&#xff0c;实现通过Github授权登录应用 文章目录 基于SpringBoot和OAuth2&#xff0c;实现通过Github授权登录应用0. 引言1. 创建Github应用2. 创建SpringBoot测试项目2.1 初始化项目2.2 设置配置文件信息2.3 创建Controller层2.4 创建Html页面 3. 启动…

Visual Studio C++使用笔记

个人学习笔记 右侧项目不显示 CTRL ALT L 创建第一个项目 添加类&#xff08;头文件、CPP文件&#xff09;

USB射频微波功率计的功能与优势-盛铂科技

USB射频功率计是一种用于测量射频信号&#xff08;RF&#xff09;功率的仪器&#xff0c;它通过USB接口与计算机或其他设备连接&#xff0c;以便于进行数据采集、处理和显示。 主要功能 功率测量&#xff1a;能够测量射频信号的功率&#xff0c;通常以毫瓦&#xff08;mW&…

百度贴吧的ip属地什么意思?怎么看ip属地

在数字化时代&#xff0c;IP地址不仅是网络设备的唯一标识符&#xff0c;更承载着用户的网络身份与位置信息。百度贴吧作为广受欢迎的社交平台&#xff0c;也遵循相关规定&#xff0c;在用户个人主页等位置展示账号IP属地信息。那么&#xff0c;百度贴吧的IP属地究竟意味着什么…

基于氢氧燃料电池的分布式三相电力系统Simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于氢氧燃料电池的分布式三相电力系统Simulink建模与仿真&#xff0c;仿真输出燃料电池中氢氧元素含量变化以及生成的H2O变化情况。 2.系统仿真结果 3.核心程序与模型 版本…

Linux驱动开发(16):输入子系统–电容触摸驱动实验

有关电容触摸的基础知识内容可以参考野火STM32相关教程&#xff0c;这里只介绍电容触摸驱动的相关内容。 本章配套源码、设备树以及更新固件位于“~/embed_linux_driver_tutorial_imx6_code/linux_driver/touch_scream_GTxxx”目录下。 触摸面板通过双面胶粘在显示屏上&#…

73 mysql replication 集群的交互

前言 新建两个数据库, 分别为 192.168.220.132:3001, 192.168.220.132:3002 设置 192.168.220.132:3001 为 master, 192.168.220.132:3002 为 slave 配置文件如下 然后使用 mysqld --initialize 来初始化 data 目录, 以及相关基础数据库 这里会为 root 账户创建一个随机的…

Unity-Mirror网络框架-从入门到精通之Benchmark示例

文章目录 前言什么是Benchmark&#xff1f;Benchmark 简要说明Benchmark示例BenchmarkNetworkManagerMonsterMovementPlayerMovementInterestManagement性能指标 BenchmarkIdle示例BenchmarkPrediction示例BenchmarkStinkySteak示例 前言 在现代游戏开发中&#xff0c;网络功能…

反射--反射机制

目录 一 java 1.反射的引入&#xff1a; 2.反射机制 1&#xff09; 反射会生成一个类对象------类型class类型 2&#xff09;【加载阶段】class类型相当于一面镜子------透过class 反射出 真正的类的结构 3&#xff09;反射机制原理---编译阶段---加载阶段---运行阶段 2.…