为什么SurfaceFlinger是Android流畅显示的幕后英雄?合成过程详解

Wan2.2-I2V-A14B

Wan2.2是由通义万相开源高效文本到视频生成模型,是有​50亿参数的轻量级视频生成模型,专为快速内容创作优化。支持480P视频生成,具备优秀的时序连贯性和运动推理能力

一、什么是SurfaceFlinger

SurfaceFlinger 是 Android 系统的核心系统服务之一,负责合成和显示所有图形界面内容。它作为系统级图形合成引擎,接收来自各个应用和系统组件的图形缓冲区(如 SurfaceView、TextureView、系统UI等),通过 GPU 硬件加速 将这些独立的图形层合成为最终的屏幕显示图像,并精确控制帧率同步(VSync)、窗口层级管理、动画渲染和多屏显示等关键功能,确保整个系统的流畅、无撕裂的视觉体验。

二、SurfaceFlinger服务启动和初始化

当系统内核启动第一个用户空间进程init进程后,init进程解析init.rc文件来启动一些必要的系统服务,其中包含:

...
on boot
...
  class_start core
...

class_start core表示启动所有类别属于core的服务,那我们在看下surfaceflinger.rc文件:

service surfaceflinger /system/bin/surfaceflinger
    class core animation
    user system
    group graphics drmrpc readproc
    capabilities SYS_NICE
    onrestart restart --only-if-running zygote
    task_profiles HighPerformance

我们可以看到 class core animation,说明surfaceflinger属于系统核心组同时也属于动画服务组,所以这里我们就可以知道init进程会在系统启动的时候启动core组成员之一的surfaceflinger服务。

那么我们来看下SurfaceFlinger进程对应的类的入口在哪里,怎么看呢,我们可以找到AOSP中SurfaceFlinger模块,找到模块中的frameworks/native/services/surfaceflinger/Android.bp这个文件:

...
filegroup {
   // 看命名可以知道是surfaceflinger二进制文件对应的源码文件
    name: "surfaceflinger_binary_sources",
    srcs: [
        ":libsurfaceflinger_sources",
        // 入口在这个文件中
        "main_surfaceflinger.cpp",
    ],
}
...

SurfaceFlinger被编译生成的二进制文件对应的源码上面说的很清楚了,所以我们知道进程的启动入口就是在类main_surfaceflinger.cpp文件中的main()函数:

int main() {
    // 对于SIGPIPE信号,选择忽略(SIG_ING)
    // SIGPIPE信号可能是连接surfaceflinger的APP异常退出的时候系统发出此信号
    // 这里选择忽略,避免surfaflinger服务被影响kill掉
    signal(SIGPIPE, SIG_IGN);

    // 
    hardware::configureRpcThreadpool(1 /* maxThreads */,
            false /* callerWillJoin */);

    // 核心流程1:
    startGraphicsAllocatorService();

    // When SF is launched in its own process, limit the number of
    // binder threads to 4.
    ProcessState::self()->setThreadPoolMaxThreadCount(4);

    // Set uclamp.min setting on all threads, maybe an overkill but we want
    // to cover important threads like RenderEngine.
    SurfaceFlinger::setSchedAttr(true, __func__);

    // The binder threadpool we start will inherit sched policy and priority
    // of (this) creating thread. We want the binder thread pool to have
    // SCHED_FIFO policy and priority 1 (lowest RT priority)
    // Once the pool is created we reset this thread's priority back to
    // original.
    int newPriority = 0;
    int origPolicy = sched_getscheduler(0);
    struct sched_param origSchedParam;

    int errorInPriorityModification = sched_getparam(0, &origSchedParam);
    if (errorInPriorityModification == 0) {
        int policy = SCHED_FIFO;
        newPriority = sched_get_priority_min(policy);

        struct sched_param param;
        param.sched_priority = newPriority;

        errorInPriorityModification = sched_setscheduler(0, policy, &param);
    }

    // start the thread pool
    sp<ProcessState> ps(ProcessState::self());
    ps->startThreadPool();

    // Reset current thread's policy and priority
    if (errorInPriorityModification == 0) {
        errorInPriorityModification = sched_setscheduler(0, origPolicy, &origSchedParam);
    } else {
        ALOGE("Failed to set SurfaceFlinger binder threadpool priority to SCHED_FIFO");
    }

    // 核心流程2:
    // instantiate surfaceflinger
    sp<SurfaceFlinger> flinger = surfaceflinger::createSurfaceFlinger();

    // Set the minimum policy of surfaceflinger node to be SCHED_FIFO.
    // So any thread with policy/priority lower than {SCHED_FIFO, 1}, will run
    // at least with SCHED_FIFO policy and priority 1.
    if (errorInPriorityModification == 0 &&
        !FlagManager::getInstance().disable_sched_fifo_sf_binder()) {
        flinger->setMinSchedulerPolicy(SCHED_FIFO, newPriority);
    }

    setpriority(PRIO_PROCESS, 0, PRIORITY_URGENT_DISPLAY);

    set_sched_policy(0, SP_FOREGROUND);

    // 核心流程3:
    // initialize before clients can connect
    flinger->init();

    // 核心流程4:
    // publish surface flinger
    sp<IServiceManager> sm(defaultServiceManager());
    sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false,
                   IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO);

    // publish gui::ISurfaceComposer, the new AIDL interface
    sp<SurfaceComposerAIDL> composerAIDL = sp<SurfaceComposerAIDL>::make(flinger);
    if (FlagManager::getInstance().misc1() &&
        !FlagManager::getInstance().disable_sched_fifo_composer()) {
        composerAIDL->setMinSchedulerPolicy(SCHED_FIFO, newPriority);
    }

    // 核心流程5:
    sm->addService(String16("SurfaceFlingerAIDL"), composerAIDL, false,
                   IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO);

    startDisplayService(); // dependency on SF getting registered above

    SurfaceFlinger::setSchedFifo(true, __func__);

    // 核心流程6:
    flinger->run();
}

核心流程步骤:

  • startGraphicsAllocatorService();启动图形内存分配服务,是Android图形系统中的核心服务,负责分配缓存图片帧的内存

    ,首先通过读取系统配置属性判断是否禁用服务,若禁用直接返回,然后通过Passthrough的模式(独立进程,不依赖于SurfaceFlinger)开始注册Gralloc V3.0服务(HIDL服务),若失败则降级注册V2.0服务:

static status_t startGraphicsAllocatorService() {
    using android::hardware::configstore::getBool;
    using android::hardware::configstore::V1_0::ISurfaceFlingerConfigs;

    // 读取系统配置属性,是否禁用图形内存分配服务,如果禁用直接返回
    if (!android::sysprop::start_graphics_allocator_service(false)) {
        return OK;
    }

    // 通过Passthrough的模式注册Gralloc V3.0服务
    status_t result = hardware::registerPassthroughServiceImplementation<
            android::hardware::graphics::allocator::V3_0::IAllocator>();
    if (result == OK) {
        return OK;
    }

    // 如果Gralloc V3.0注册失败,降级为注册Gralloc V2.0服务
    result = hardware::registerPassthroughServiceImplementation<
            android::hardware::graphics::allocator::V2_0::IAllocator>();
    if (result != OK) {
        ALOGE("could not start graphics allocator service");
        return result;
    }

    return OK;
}


Gralloc服务就是位于硬件抽象层(HAL)提供给上层(SurfaceFlinger等)封装好统一管理图形缓存内存的接口,对于不同硬件其不同的实现对上层隔离,是图形系统中重要的一环,在初始化SurfaceFlinger的时候先注册好Gralloc服务做好准备工作(因为后续的处理图像都需要依赖这个服务分配实际的内存空间)。

  • surfaceflinger::createSurfaceFlinger();
sp<SurfaceFlinger> createSurfaceFlinger() {
    static DefaultFactory factory;

    return sp<SurfaceFlinger>::make(factory);
}

通过工厂类SurfaceFlingerFactory实例化一个SurfaceFlinger对象。

  • flinger->init();

设计特点

   异步初始化 :大量使用异步操作避免阻塞主线程

   错误处理 :关键步骤使用 LOG_ALWAYS_FATAL_IF 确保初始化失败时系统崩溃

   性能优化 :预加载着色器、异步属性设置

   分层架构 :RenderEngine、HWComposer、Scheduler等组件解耦

   实时性保证 :渲染线程设置为实时优先级

这个init()函数完成了从GPU初始化到显示设备配置,从VSync调度到电源管理的全方位系统初始化,使SurfaceFlinger成为功能完整的系统服务。

  • sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false,IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO);

  • sm->addService(String16("SurfaceFlingerAIDL"), composerAIDL, false, IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO);

这里为什么添加两个服务到ServiceManager中呢?这里主要是考虑新老版本的兼容问题,对于老的版本还是通过手动写binder通信(第一种方式),需要将flinger服务添加进去,对于新的支持使用API更丰富的AIDL的方式调用(第二种)。AOSP中已经将一些旧的手动binder重构成AIDL的方式,下面我们看一些AOSP中常用模块这两者的占比情况的统计数据:

模块

AIDL使用

手动Binder

主要原因

SurfaceFlinger

20%

80%

性能关键

AudioFlinger

30%

70%

实时音频

CameraService

40%

60%

复杂数据结构

SystemServer

90%

10%

系统服务

看的出来一些模块AOSP已经在往新的AIDL这种形式迁移,但是在有些场景还是需要手动写Binder通信,比如对性能要求高,需要修改通信过程中的细节,有些复杂数据结构参数需要传递,AIDL还不能做的得用手动的方式。举个不恰当的例子:AOSP中的IPC像一辆精心调校的赛车——AIDL是自动挡,手动Binder是手动挡,在不同场景选择最合适的档位才能发挥最佳性能和最佳开发效率。

  • flinger->run();
void Scheduler::run() {
    while (true) {
        waitMessage();
    }
}
void MessageQueue::waitMessage() {
    do {
        IPCThreadState::self()->flushCommands();
        int32_t ret = mLooper->pollOnce(-1);
        switch (ret) {
            case Looper::POLL_WAKE:
            case Looper::POLL_CALLBACK:
                continue;
            case Looper::POLL_ERROR:
                ALOGE("Looper::POLL_ERROR");
                continue;
            case Looper::POLL_TIMEOUT:
                // timeout (should not happen)
                continue;
            default:
                // should not happen
                ALOGE("Looper::pollOnce() returned unknown status %d", ret);
                continue;
        }
    } while (true);
}

这里看起来比较有意思,居然有两个嵌套的死循环。为什么会这么写?读者也可以思考一下,本人的一点看法是第一个死循环是保证Scheduler这个线程能一直跟随系统存活(作为图形系统关键的一环),第二个死循环是消息事件的业务逻辑,保证一直能监听到事件消息,当前看起来外层的Scheduler的死循环是冗余的,因为去掉Scheduler的循环,由于waitMessage中会开启死循环,也能保证Scheduler线程持续存活。这里可能是历史代码问题,又或者是一种保护设计,万一后面waitMessage函数的业务逻辑不再是无限循环而影响图形系统的调度模块Scheduler的存活。大家也可以发表下对于这里为什么使用两个嵌套的死循环的看法。

三、SurfaceFlinger合成过程

前面的一篇文章中也略微讲过合成的过程,这里从SurfaceFlinger启动开始来理一下这个逻辑过程。当Scheduler开启死循环监听事件消息后,具备监听Vsync信号的能力。当Vsync-sf信号到来时,MessageQueue.cpp中的vsyncCallback()函数:

void MessageQueue::vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, nsecs_t readyTime) {
    SFTRACE_CALL();
    // Trace VSYNC-sf,相当与可以可视化VSync信号,0-1交替变化
    mVsync.value = (mVsync.value + 1) % 2;

    // 记录vsync发生的时间
    const auto expectedVsyncTime = TimePoint::fromNs(vsyncTime);
    {
        std::lock_guard lock(mVsync.mutex);
        mVsync.lastCallbackTime = expectedVsyncTime;
        mVsync.scheduledFrameTimeOpt.reset();
    }

    // 每一次Vsync生成一个唯一的ID
    const auto vsyncId = VsyncId{mVsync.tokenManager->generateTokenForPredictions(
            {targetWakeupTime, readyTime, vsyncTime})};

    // 将Vync的时间和ID分发下去
    mHandler->dispatchFrame(vsyncId, expectedVsyncTime);
}

mHandler->dispatchFrame(vsyncId, expectedVsyncTime);通过MessageQueue.cpp中的Handler分发:

void MessageQueue::Handler::dispatchFrame(VsyncId vsyncId, TimePoint expectedVsyncTime) {
    if (!mFramePending.exchange(true)) {
        mVsyncId = vsyncId;
        mExpectedVsyncTime = expectedVsyncTime;
        mQueue.mLooper->sendMessage(sp<MessageHandler>::fromExisting(this), Message());
    }
}

通过Looper发送消息,触发waitMessage中的阻塞监听消息被唤醒(有消息要处理),looper调用handleMessage函数,继续调用到mQueue.onFrameSignal(mQueue.mCompositor, mVsyncId, mExpectedVsyncTime);mQueue的实现为Scheduler.cpp,所以调到:

void Scheduler::onFrameSignal(ICompositor& compositor, VsyncId vsyncId,
                              TimePoint expectedVsyncTime) {
    ...

    ftl::NonNull<const Display*> pacesetterPtr = pacesetterPtrLocked();
    pacesetterPtr->targeterPtr->beginFrame(beginFrameArgs, *pacesetterPtr->schedulePtr);

    {
        ...

        // 合成准备阶段,将本帧所有的信息提交给合成器
        if (!compositor.commit(pacesetterPtr->displayId, targets)) {
            if (FlagManager::getInstance().vrr_config()) {
                compositor.sendNotifyExpectedPresentHint(pacesetterPtr->displayId);
            }
            mSchedulerCallback.onCommitNotComposited();
            return;
        }
    }

    ...

    // 实际进行合成操作的入口
    const auto resultsPerDisplay = compositor.composite(pacesetterPtr->displayId, targeters);

    compositor.sample();

    for (const auto& [id, targeter] : targeters) {
        ...
        targeter->endFrame(*resultOpt);
    }
}

compositor.commit(pacesetterPtr->displayId, targets),commit函数的主要工作:

  • 收集和整理本帧所有需要显示的内容(比如每个 Layer 的缓冲区、变换、透明度等)。
  • 将这些内容传递给合成引擎(CompositionEngine),为后续的合成做准备。
  • 可能会进行一些前置检查,比如判断哪些 Layer 需要合成、哪些可以跳过、是否有内容变化等。
  • 为后续的合成(composite)做准备,比如分配合成缓冲区、设置合成参数等。

为什么先commit呢?

  • 分离准备和执行:commit 主要是“准备”阶段,收集和锁定所有本帧需要用到的资源和数据,确保在真正合成(composite)时不会被其他线程或事务打断。
  • 保证一致性:在多线程环境下,commit阶段可以保证所有Layer的状态在本帧合成时是原子一致的。
  • 性能优化:有些 Layer 可能没有变化,commit 阶段可以提前判断,减少不必要的合成操作。

就是把本帧所有要显示的内容和目标,提交给合成器,为后续的实际合成(composite)做准备。

它是 VSYNC 驱动的 SurfaceFlinger 合成流程中的“准备阶段”核心入口。

compositor.composite(pacesetterPtr->displayId, targeters),这里的compositor的实现就是SurfaceFlinger,在SurfaceFlinger的composite函数中实际是通过调用CompositionEngine->present()函数:

void CompositionEngine::present(CompositionRefreshArgs& args) {
    SFTRACE_CALL();
    ALOGV(__FUNCTION__);

    // 1、合成前预处理
    preComposition(args);

    {
        // latchedLayers is used to track the set of front-end layer state that
        // has been latched across all outputs for the prepare step, and is not
        // needed for anything else.
        LayerFESet latchedLayers;

        // 2、遍历调用各output(输出设备)的prepare,确定各输出设备的图层Z-Order顺序
        for (const auto& output : args.outputs) {
            output->prepare(args, latchedLayers);
        }
    }

    // Offloading the HWC call for `present` allows us to simultaneously call it
    // on multiple displays. This is desirable because these calls block and can
    // be slow.
    // 3、负载卸载到异步线程处理多输出设备场景
    offloadOutputs(args.outputs);

    ui::DisplayVector<ftl::Future<std::monostate>> presentFutures;
    for (const auto& output : args.outputs) {
        // 4、合成的核心操作
        presentFutures.push_back(output->present(args));
    }

    {
        SFTRACE_NAME("Waiting on HWC");
        for (auto& future : presentFutures) {
            // TODO(b/185536303): Call ftl::Future::wait() once it exists, since
            // we do not need the return value of get().
            future.get();
        }
    }
    // 5、释放缓存
    postComposition(args);
}

1、首先调用preComposition函数进行合成前的预处理动作

具体到函数里面会遍历所有的图层layer,调用layer->onPreComposition()函数在即将合成前做一次检查,判断是否有新内容更新,如果有新内容更新就停止当前不必要的合成动作,避免浪费资源。

根据调用链来跟进源码:

CompositionEngine::present()

  └─ preComposition()

      └─ layer->onPreComposition() [LayerFE层]

          └─ 检查hasReadyFrame状态

void CompositionEngine::preComposition(CompositionRefreshArgs& args) {
    SFTRACE_CALL();
    ALOGV(__FUNCTION__);

    bool needsAnotherUpdate = false;
    mRefreshStartTime = args.refreshStartTime;

    for (auto& layer : args.layers) {
        if (layer->onPreComposition(args.updatingOutputGeometryThisFrame)) {
            needsAnotherUpdate = true;
        }
    }
    mNeedsAnotherUpdate = needsAnotherUpdate;
}
bool LayerFE::onPreComposition(bool) {
    return mSnapshot->hasReadyFrame;
}

hasReadyFrame来自复杂的状态判断链:

bool RequestedLayerState::hasReadyFrame() const {
    return hasFrameUpdate() || changes.test(Changes::SidebandStream) || autoRefresh;
}

三个条件满足其一即为true:

  • hasFrameUpdate: 新的纹理内容更新或者背景的颜色更新
  • changes.test(Changes::SidebandStream):有新的sideBand流更新(视频帧更新)
  • autoRefresh:动画更新

2、output->prepare(args, latchedLayers);

确定各输出设备的图层序列,

void Output::prepare(const compositionengine::CompositionRefreshArgs& refreshArgs,
                     LayerFESet& geomSnapshots) {
    SFTRACE_CALL();
    ALOGV(__FUNCTION__);

    rebuildLayerStacks(refreshArgs, geomSnapshots);
    uncacheBuffers(refreshArgs.bufferIdsToUncache);
}

rebuildLayerStacks函数发起图层序列栈的重建工作,根据Z-Order的顺序。

uncacheBuffers函数清理不再使用的缓冲区,避免内存浪费

3、offloadOutputs(args.outputs);负载卸载压力

offload 可以理解为给主线程卸载部分压力到异步线程处理,具体到本函数的场景中,针对多输出屏幕设备的场景,判断输出设备是否具备硬件加速以及多线程present的条件,使用多线程卸载主线程的压力,进行多线程并发present操作。这个函数通过这些判断来标记是否支持offload。

设计核心思想和优化收益:

  • 并发性能优化
    问题: HWC的present调用是阻塞的,多显示器顺序处理延迟高
    解决方案: 将不同显示器的present操作并行化
    收益: 显著减少多显示器场景下的总延迟
  • 智能资源管理
    主线程保留策略: 避免主线程空闲,实现真正并发而非简单的任务分发
    工作线程复用: HwcAsyncWorker在帧间保留,避免频繁创建销毁
    按需分配: 只在需要时创建异步工作线程
  • 兼容性保证
    全有或全无策略: 确保所有显示器都支持多线程present
    能力检查: 区分HWC和客户端合成,针对性优化
    特性开关: 通过multithreaded_present()标志控制功能启用
  • 安全的异步设计
    每帧重置: mOffloadPresent每帧重置,避免状态泄露
    Future同步: 使用ftl::Future确保所有异步操作完成

    异常安全: 异步操作的异常会传播到主线程

4、多线程执行present操作

ui::DisplayVector<ftl::Future<std::monostate>> presentFutures;
for (const auto& output : args.outputs) {
    // 4、合成的核心操作
    presentFutures.push_back(output->present(args));
}

这里的ftl::Future是专门为Android系统高性能异步操作设计的,这里就可以根据Output是否支持offload进行异步执行,加快执行的速度。

当循环执行的时候,

  • 立即启动所有present操作:每次output->present(args)调用都会立即开始该显示器的present流程
  • 收集Future句柄:将每个操作的Future对象保存到presentFutures容器中
  • 混合处理模式:根据mOffloadPresent标志决定同步还是异步执行

核心实现机制:

ftl::Future<std::monostate> future;
const bool flushEvenWhenDisabled = !refreshArgs.bufferIdsToUncache.empty();
if (mOffloadPresent) {
    future = presentFrameAndReleaseLayersAsync(flushEvenWhenDisabled);
    mOffloadPresent = false;
} else {
    presentFrameAndReleaseLayers(flushEvenWhenDisabled);
    future = ftl::yield<std::monostate>({});
}

并发执行时序分析:


Future模式的关键设计优势

  • 统一的返回接口,无论是异步还是同步
  • 非阻塞启动模式,循环中所有的启动几乎同时开启
  • 延迟同步点:分离启动和等待,先启动所有操作再统一等待,最优等待时间≈max(各显示器present的时间)

{
    SFTRACE_NAME("Waiting on HWC");
    for (auto& future : presentFutures) {
        future.get();
    }
}

以上是针对使用Future模式的一些分析,具体到执行单个输出设备的present操作:

ftl::Future<std::monostate> Output::present(
        const compositionengine::CompositionRefreshArgs& refreshArgs) {
    // 1. 更新颜色配置
    updateColorProfile(refreshArgs);

    // 2. 更新合成状态  
    updateCompositionState(refreshArgs);

    // 3. 规划合成策略
    planComposition();

    // 4. 写入合成状态到HWC
    writeCompositionState(refreshArgs);

    // 5. 设置颜色变换
    setColorTransform(refreshArgs);

    // 6. 开始帧合成前的判断,是否有脏区域(内容变化)以及是否图层不为空并且上一次合成的时候图层也不是空
    // 以上条件均满足的时候合成,否则不做合成
    beginFrame();

    // 7. 准备帧(决策GPU vs HWC合成)
    if (predictCompositionStrategy) {
        result = prepareFrameAsync();
    } else {
        prepareFrame();
    }

    // 8. 完成帧GPU合成
    finishFrame(std::move(result));

    // 9. HWC合成+混合+提交到显示器并释放资源
    if (mOffloadPresent) {
        future = presentFrameAndReleaseLayersAsync(flushEvenWhenDisabled);
    } else {
        presentFrameAndReleaseLayers(flushEvenWhenDisabled);
        future = ftl::yield<std::monostate>({});
    }

    return future;
}

GPU合成帧的关键操作在finishFrame()函数,其逻辑流程如下:

presentFrameAndReleaseLayers执行HWC合成和提交帧,流程如下所示:

小结:

CompositionEngine::present()
    ├── preComposition()          // 预处理
    ├── output->prepare()         // 准备输出
    ├── offloadOutputs()          // 多线程卸载
    ├── output->present()         // 呈现输出
    │   ├── updateColorProfile()  // 更新颜色配置
    │   ├── updateCompositionState() // 更新合成状态
    │   ├── planComposition()     // 规划合成
    │   ├── writeCompositionState() // 写入状态
    │   ├── setColorTransform()   // 设置颜色变换
    │   ├── beginFrame()          // 开始帧
    │   ├── prepareFrame()        // 准备帧
    │   ├── finishFrame()        // 完成帧
    │   └── presentFrameAndReleaseLayers() // 呈现帧并释放层
    │       ├── presentFrame()    // 呈现帧
    │       │   ├── impl::Output::presentFrame() // 基类实现
    │       │   └── hwc.presentAndGetReleaseFences() //HWC调用
    │       └── onPresentDisplayCompleted() // 呈现完成通知
    └── postComposition()         // 后处理

四、总结

SurfaceFlinger如同Android图形的“交响乐指挥家”,以VSync为节拍器,调度GPU/HWC多乐器协奏。其精妙的异步架构、双循环线程模型、Future并发控制,共同奏响了60FPS流畅体验的乐章。每一次像素点亮背后,都是对实时性、原子性、高效性的极致追求——这,就是Android图形系统的灵魂所在。

最后给大家上一波整个图形子系统的分层设计,本文基于最新AOSP源码分析,

您可能感兴趣的与本文相关的镜像

Wan2.2-I2V-A14B

Wan2.2-I2V-A14B

图生视频
Wan2.2

Wan2.2是由通义万相开源高效文本到视频生成模型,是有​50亿参数的轻量级视频生成模型,专为快速内容创作优化。支持480P视频生成,具备优秀的时序连贯性和运动推理能力

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值