Android SurfaceFlinger导读(04)理解BufferQueue

news2024/11/19 4:40:29

该系列文章总纲链接:Android GUI系统之SurfaceFlinger 系列文章目录

说明:

  • 关于导读:导读部分主要是方便初学者理解SurfaceFlinger代码中的机制,为后面分析代码打下一个更好的基础,这样就可以把更多的精力放在surfaceFlinger的业务逻辑分析上。
  • 关于代码分支:以下代码分析均在android5.1.1_r3分支上 目录frameworks/native/services/surfaceflinger为root目录

在SurfaceFlinger中理解BufferQueue 相关内容对于图形渲染和显示非常重要:

  • BufferQueue 的作用:BufferQueue 是 SurfaceFlinger 的一部分,用于协调生产者(通常是应用程序或图形渲染引擎)和消费者(通常是显示系统)之间的图形数据传递。它允许生产者将渲染好的图形数据排队以供消费者显示,并确保在异步图形处理过程中进行同步和缓冲区的管理。
  • 图形缓冲区(Buffers):BufferQueue 管理图形缓冲区,这些缓冲区可以包含图像数据、帧数据或其他图形数据。了解如何创建、填充、排队和释放这些缓冲区是非常重要的,因为它们是图形渲染和显示的核心。
  • BufferQueue 的状态:BufferQueue 可以处于不同的状态,如 FREE、DEQUEUED、QUEUED 和 ACQUIRED。这些状态用于表示图形缓冲区的可用性和所有权,以确保生产者和消费者之间的正确同步和数据传递。
  • BufferQueueProducer 和 BufferQueueConsumer:生产者通过 BufferQueueProducer 接口与 BufferQueue 交互,而消费者通过 BufferQueueConsumer 接口与之交互。了解这两个接口以及它们的用法对于理解SurfaceFlinger 如何工作至关重要。
  • 同步和时间戳:了解如何使用同步信号(fence)以及如何处理时间戳(timestamp)对于确保图形帧在正确时间显示非常重要。这些功能帮助确保图形帧的正确顺序和时间戳。
  • BufferQueue 的线程处理:SurfaceFlinger 使用线程来管理和处理图形缓冲区。理解这些线程的角色和工作方式对于理解图形渲染和显示的异步性质非常关键。
  • 硬件加速和图形合成:SurfaceFlinger 可以与硬件加速图形合成引擎一起工作,以实现高性能的图形渲染。了解如何与硬件合成引擎交互以及如何配置硬件加速也很重要。

总之,理解这些概念是 Android 图形渲染和显示的关键,尤其是在处理图形性能和显示质量时。接下来根据上面的基本概念我们主要研究以下内容:

  • BufferQueue的4种状态详细解读。
  • BufferQueue的生产者消费者模型。
  • BufferQueue中的fence同步。

研究BufferQueue,主要涉及的几个关键文件如下:

  • AOSP/frameworks/native/libs/gui/BufferQueueProducer.cpp
  • AOSP/frameworks/native/libs/gui/BufferQueueCore.cpp
  • AOSP/frameworks/native/libs/gui/BufferQueueConsumer.cpp
  • AOSP/frameworks/native/libs/gui/BufferQueue.cpp
  • AOSP/frameworks/native/libs/gui/BufferSlot.cpp
  • AOSP/frameworks/native/include/gui/BufferSlot.h
  • AOSP/frameworks/native/include/gui/BufferQueueProducer.h
  • AOSP/frameworks/native/include/gui/BufferQueueCore.h
  • AOSP/frameworks/native/include/gui/BufferQueueConsumer.h
  • AOSP/frameworks/native/include/gui/BufferQueue.h
  • AOSP/frameworks/native/include/gui/BufferQueueDefs.h

本文主要针对BufferQueue的原理进行解读,关于流程的解读会在后面篇章中进行详细说明。

1 BufferQueue的4种状态解读

BufferQueue的状态定义在文件AOSP/frameworks/native/libs/gui/BufferSlot.cpp中, 结构体 struct BufferSlot中定义为BufferState,实现如下:

enum BufferState {
        FREE = 0,
        DEQUEUED = 1,
        QUEUED = 2,
        ACQUIRED = 3
    };

这段代码定义了一个名为BufferState的枚举类型,用于表示图形缓冲区的不同状态。这些状态通常在 Android 的 SurfaceFlinger 系统中使用,用于协调图形数据的生产者和消费者之间的操作。每个状态解释如下:

  • FREE(自由):表示缓冲区是可供生产者(dequeue)使用的状态。在此状态下,缓冲区可能在有限的时间内由消费者使用,因此在相关联的"ready fence"(准备就绪标志)被触发之前,不得修改缓冲区。这个状态的槽位(slot)由BufferQueue拥有,当调用dequeueBuffer时,它会转换为DEQUEUED状态。
  • DEQUEUED(已出队):表示生产者已经从队列中取出了缓冲区,但尚未将其重新排入队列或取消。在此状态下,生产者可以在相关的"ready fence"被触发后立即修改缓冲区内容。这个状态的槽位仍然由生产者拥有,可以通过queueBuffer转换为QUEUED状态,或通过cancelBuffer转换回FREE状态。
  • QUEUED(已排队):表示生产者已经填充了缓冲区并将其排队以供消费者使用。在此状态下,缓冲区内容可能在有限时间内继续被修改,因此在相关的"ready fence"被触发之前,不得访问缓冲区内容。这个状态的槽位由BufferQueue拥有,可以通过acquireBuffer转换为ACQUIRED状态,或者在异步模式下通过排队另一个缓冲区转换回FREE状态。
  • ACQUIRED(已获取):表示消费者已经获取了缓冲区。与QUEUED状态一样,消费者在相关的"ready fence"被触发之前不得访问缓冲区内容。这个状态的槽位由消费者拥有,当调用releaseBuffer时,它会转换回FREE状态。

这些状态用于确保生产者和消费者之间对图形缓冲区的访问是协调的,以防止竞态条件和不一致性。这是 Android 图形系统中重要的一部分,用于实现高性能的图形渲染和显示。

2 BufferQueue的生产者/消费者模型

2.1 生产者-消费者流程

应用端为生产者,执行DequeueBuffer操作,然后填充Buffer,完成后执行QueueBuffer操作;SurfaceFlinger为消费者,执行AcquireBuffer,接下来调用HWC来合成绘制Buffer。完成后执行ReleaseBuffer。整体流程如下图所示:

同时,这里BufferQueue的状态变化为:FREE->DEQUEUED->QUEUED->ACQUIRED->FREE。

BufferQueue的通信过程如下(以下部分源自官网翻译)

BufferQueues 是 Android 图形组件之间的粘合剂。它们是一对队列,可以调解缓冲区从生产方到消耗方的固定周期。一旦生产方移交其缓冲区,SurfaceFlinger 便会负责将所有内容合成到显示部分。

BufferQueue 包含将图像流生产方与图像流消耗方结合在一起的逻辑。图像生产方的一些示例包括由相机 HAL 或 OpenGL ES 游戏生成的相机预览。图像消耗方的一些示例包括 SurfaceFlinger 或显示 OpenGL ES 流的另一个应用,如显示相机取景器的相机应用。

其中,BufferQueue 可以在三种不同的模式下运行:

  • 类同步模式 - 默认情况下,BufferQueue 在类同步模式下运行,在该模式下,从生产方进入的每个缓冲区都在消耗方那退出。在此模式下不会舍弃任何缓冲区。如果生产方速度太快,创建缓冲区的速度比消耗缓冲区的速度更快,它将阻塞并等待可用的缓冲区。
  • 非阻塞模式 - BufferQueue 还可以在非阻塞模式下运行,在此类情况下,它会生成错误,而不是等待缓冲区。在此模式下也不会舍弃缓冲区。这有助于避免可能不了解图形框架的复杂依赖项的应用软件出现潜在死锁现象。
  • 舍弃模式 - 最后,BufferQueue 可以配置为丢弃旧缓冲区,而不是生成错误或进行等待。例如,如果对纹理视图执行 GL 渲染并尽快绘制,则必须丢弃缓冲区。

为了执行这项工作的大部分环节,SurfaceFlinger 就像另一个 OpenGL ES 客户端一样工作。例如,当 SurfaceFlinger 正在积极地将一个缓冲区或两个缓冲区合成到第三个缓冲区中时,它使用的是 OpenGL ES。

2.2 测试源码解读,深入理解生产者-消费者模式

接下来我们专注于BufferQueue代码是如何使用的,先看原生代码中给出的测试程序。在文件AOSP/frameworks/native/libs/gui/tests/BufferQueue_test.cpp,这里抽取了其中给一个有代表性的测试用例,对应代码如下:


class BufferQueueTest : public ::testing::Test {

public:
protected:
    BufferQueueTest() {
        const ::testing::TestInfo* const testInfo =
            ::testing::UnitTest::GetInstance()->current_test_info();
        ALOGV("Begin test: %s.%s", testInfo->test_case_name(),
                testInfo->name());
    }

    ~BufferQueueTest() {
        const ::testing::TestInfo* const testInfo =
            ::testing::UnitTest::GetInstance()->current_test_info();
        ALOGV("End test:   %s.%s", testInfo->test_case_name(),
                testInfo->name());
    }

    void GetMinUndequeuedBufferCount(int* bufferCount) {
        ASSERT_TRUE(bufferCount != NULL);
        ASSERT_EQ(OK, mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
                    bufferCount));
        ASSERT_GE(*bufferCount, 0);
    }

    void createBufferQueue() {
        BufferQueue::createBufferQueue(&mProducer, &mConsumer);
    }

    sp<IGraphicBufferProducer> mProducer;
    sp<IGraphicBufferConsumer> mConsumer;
};

struct DummyConsumer : public BnConsumerListener {
    virtual void onFrameAvailable() {}
    virtual void onBuffersReleased() {}
    virtual void onSidebandStreamChanged() {}
};

// XXX: Tests that fork a process to hold the BufferQueue must run before tests
// that use a local BufferQueue, or else Binder will get unhappy
TEST_F(BufferQueueTest, BufferQueueInAnotherProcess) {
    const String16 PRODUCER_NAME = String16("BQTestProducer");
    const String16 CONSUMER_NAME = String16("BQTestConsumer");

    pid_t forkPid = fork();
    ASSERT_NE(forkPid, -1);

    if (forkPid == 0) {//子进程
        sp<IGraphicBufferProducer> producer;
        sp<IGraphicBufferConsumer> consumer;
        BufferQueue::createBufferQueue(&producer, &consumer);
        sp<IServiceManager> serviceManager = defaultServiceManager();
        serviceManager->addService(PRODUCER_NAME, producer->asBinder());
        serviceManager->addService(CONSUMER_NAME, consumer->asBinder());
        ProcessState::self()->startThreadPool();
        IPCThreadState::self()->joinThreadPool();
        LOG_ALWAYS_FATAL("Shouldn't be here");
    }
    //父进程
    sp<IServiceManager> serviceManager = defaultServiceManager();
    sp<IBinder> binderProducer =
        serviceManager->getService(PRODUCER_NAME);
    mProducer = interface_cast<IGraphicBufferProducer>(binderProducer);
    EXPECT_TRUE(mProducer != NULL);
    sp<IBinder> binderConsumer =
        serviceManager->getService(CONSUMER_NAME);
    mConsumer = interface_cast<IGraphicBufferConsumer>(binderConsumer);
    EXPECT_TRUE(mConsumer != NULL);

    sp<DummyConsumer> dc(new DummyConsumer);
    ASSERT_EQ(OK, mConsumer->consumerConnect(dc, false));
    IGraphicBufferProducer::QueueBufferOutput output;
    ASSERT_EQ(OK,
            mProducer->connect(NULL, NATIVE_WINDOW_API_CPU, false, &output));

    int slot;
    sp<Fence> fence;
    sp<GraphicBuffer> buffer;
    //生产者 dequeueBuffer操作
    ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
            mProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0,
                    GRALLOC_USAGE_SW_WRITE_OFTEN));
    //生产者 requestBuffer操作
    ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));

    //生产者填充Buffer缓冲区
    uint32_t* dataIn;
    ASSERT_EQ(OK, buffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN,
            reinterpret_cast<void**>(&dataIn)));
    *dataIn = 0x12345678;
    ASSERT_EQ(OK, buffer->unlock());

    IGraphicBufferProducer::QueueBufferInput input(0, false, Rect(0, 0, 1, 1),
            NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, false, Fence::NO_FENCE);
    //生产者 queueBuffer操作
    ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));

    IGraphicBufferConsumer::BufferItem item;
    //消费者 acquireBuffer操作
    ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));

    //消费者 缓冲区数据验证操作,看上屏的Buffer内容和之前渲染的Buffer内容是否一致
    uint32_t* dataOut;
    ASSERT_EQ(OK, item.mGraphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN,
            reinterpret_cast<void**>(&dataOut)));
    ASSERT_EQ(*dataOut, 0x12345678);
    ASSERT_EQ(OK, item.mGraphicBuffer->unlock());
}
//...
} // namespace android

从这里也可以看到生产者和消费者模式在代码中的实际使用case,先是生产者执行了dequeuebuffer操作->requestbuffer操作->对buffer进行填充渲染;消费者执行acquire操作,之后验证缓冲区内容是否和之前填充的内容一致。而在实际的android源码中,BufferQueue的生产者在一个进程中,完成渲染和填充后,通过binder通信通知消费者的进程获取Buffer并通过HWC去合成绘制到屏幕上。

3 BufferQueue的Fence机制

3.1 理解Fence机制

在 Android 的 BufferQueue 中,Fence(也称为同步信号或屏障)用于确保图形缓冲区的正确同步和顺序处理。Fence主要用于表示某个操作的完成状态或某个资源的可用性状态。在图形缓冲区的上下文中,Fence 主要用于跟踪图形数据的生产和消费的状态。例如,一个Fence可以表示图形缓冲区的填充已经完成,或者表示图形缓冲区的内容已经准备好用于显示。Fence的应用如下:

@1 Fence在生产者与消费者之间的同步:

  • 在异步图形处理中,生产者(例如应用程序或图形渲染引擎)通常会填充图形缓冲区,并使用 Fence 来表示缓冲区是否已经填充完毕。
  • 消费者(例如显示系统)使用 Fence 来等待缓冲区的就绪状态,确保不会在缓冲区尚未填充完毕时访问其内容。

这种同步机制有助于防止竞态条件和不一致性,确保图形数据的正确传递和显示。

@2 Fence 的等待和触发:

  •  消费者可以使用 Fence 来等待某个操作的完成,通过等待操作的 Fence 变为“已触发”状态。
  •  生产者可以触发 Fence,表示相关操作已经完成,Fence 变为“已触发”状态,从而通知消费者可以安全地使用相关资源或数据。
  • Fence 通常与系统的同步机制一起使用,例如 sync_wait(等待 Fence 触发)和 sync_merge(合并多个 Fence)等函数。

@3 时间戳和缓冲区的正确顺序:

  • Fence 通常伴随着时间戳一起使用,以确保图形缓冲区的正确顺序。时间戳可以用于指示每个图形帧的呈现时间。
  • 使用时间戳和 Fence,消费者可以按照正确的顺序呈现图形帧,从而实现平滑的图形显示。

总之,Fence 在 Android 的 BufferQueue 中用于确保图形数据的正确同步、顺序处理和及时呈现。它是异步图形处理中的重要工具,有助于防止竞态条件和确保图形帧的正确显示。在实际开发中,开发者通常会与 Fence 交互,以确保图形数据的正确传递和显示。

3.2 BufferSlot中的mEglFence和mFence 解读

在SurfaceFlinger中,BufferSlot 类中的定义了2个fence相关成员变量:mEglFence 和 mFence ,两者都是用于同步和管理图形缓冲区状态的属性,但它们在具体的分工上有一些不同:

  • mEglFence:mEglFence是一个 EGL同步对象。它用于表示 GPU是否已经完成了与图形缓冲区相关的渲染操作。当 GPU渲染操作完成时,它会信号通知到EGLFence。EglFence主要用于同步GPU的图形渲染操作,确保 GPU已经完成了渲染,以确保渲染的完整性。
  • mFence:mFence用于SurfaceFlinger内部的图形合成和显示操作。它用于表示CPU和GPU之间的同步,确保图形缓冲区中的图形数据在合成和显示之前正确准备和完成。mFence通常用于 SurfaceFlinger的图形合成和显示管道,以确保图形数据的正确顺序和同步。

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

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

相关文章

使用opencv及FFmpeg编辑视频

使用opencv及FFmpeg编辑视频 1.融合两个视频2.为视频添加声音2.1 安装ffmpy Python包2.2 下载ffmpeg2.3 代码实现 3.效果参考文献 帮朋友做了一个小作业&#xff0c;具体实现分为几个过程&#xff1a; 将两个mp4格式视频融合到一起为新视频添加声音 1.融合两个视频 其中一个…

AI智能创作系统ChatGPT商业运营源码+AI绘画系统/支持GPT联网提问/支持Midjourney绘画+支持国内AI提问模型+Prompt应用

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统&#xff0c;支持国内AI提问模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作ChatGPT&#xff1f…

VSCode Intellij IDEA CE 数据库连接

VSCode & Intellij IDEA CE 数据库连接 大概记一下现在正在用的几个工具/插件 VSCode VSCode 里面的工具我下载了很多&#xff0c;如果只是链接 MySQL 的话&#xff0c;可能用 Jun Han 这位大佬的 MySQL 就好了&#xff1a; 使用这个插件直接打开 .sql 文件单击运行就能…

VMware16.1.2安装及密钥

文章目录 一、VMware 16 虚拟机下载二、安装步骤三、VMware 各版本注册密钥1 、VMware 16密钥2 、VMware 14密钥3 、VMware 15密钥4 、VMware 17密钥 一、VMware 16 虚拟机下载 VMware 16 下载地址&#xff1a; https://www.vmware.com/cn/products/workstation-pro/workstati…

数据结构与算法设计分析——贪心算法的应用

目录 一、贪心算法的定义二、贪心算法的基本步骤三、贪心算法的性质&#xff08;一&#xff09;最优子结构性质&#xff08;二&#xff09;贪心选择性质 四、贪心算法的应用&#xff08;一&#xff09;哈夫曼树——哈夫曼编码&#xff08;二&#xff09;图的应用——求最小生成…

《Linux 内核设计与实现》13. 虚拟文件系统

通用文件接口 VFS 使得可以直接使用 open()、read()、write() 这样的系统调用而无需考虑具体文件系统和实际物理介质。 好处&#xff1a;新的文件系统和新类型的存储介质需要挂载时&#xff0c;程序无需重写&#xff0c;甚至无需重新编译。 VFS 将各种不同的文件系统抽象后采…

[强网杯 2022]factor有感

可直接私信&#xff0b;Q 3431550587 此题记录主要是他运用了几个新看见的攻击思路和拜读了一篇论文&#xff0c;所以写写。题目源码&#xff1a; #encoding:utf-8 from Crypto.Util.number import * from gmpy2 import * from random import randint from flag import flagd…

云服务仿真:完全模拟 AWS 服务的本地体验 | 开源日报 No.45

localstack/localstack Stars: 48.7k License: NOASSERTION LocalStack 是一个云服务仿真器&#xff0c;可以在您的笔记本电脑或 CI 环境中以单个容器运行。它提供了一个易于使用的测试/模拟框架&#xff0c;用于开发云应用程序。主要功能包括&#xff1a; 在本地机器上完全…

CTF之CTF(夺旗赛)介绍

什么是CTF&#xff1f; CTF&#xff08;Capture The Flag&#xff0c;中文一般译作“夺旗赛”&#xff09;在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式。CTF起源于1996年DEFCON全球黑客大会&#xff0c;用以代替之前黑客们通过互相发起真实攻击进行…

最新AI智能创作系统源码SparkAi系统V2.6.3/AI绘画系统/支持GPT联网提问/支持Prompt应用/支持国内AI模型

一、智能AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统&#xff0c;已支持OpenAIGPT全模型国内AI全模型&#xff0c;已支持国内AI模型 百度文心一言、微软Azure、阿里云通义千问模型、清华智谱AIChatGLM、科大讯飞星火大模型等。本期针对源码…

Javascript - 轮播图

轮播图也称banner图、广告图、焦点图、滑片。是指在一个模块或者窗口,通过鼠标点击或手指滑动后,可以看到多张图片。这些图片统称为轮播图,这个模块叫做轮播模块。可以通过运用 javascript去实现定时自动转换图片。以下通过一个小Demo演示如何运用Javascript实现。 <!DOCTYP…

d3dcompiler_47.dll是什么文件?游戏确实d3dcompiler_47.dll的常用解决方法

d3dcompiler_47.dll 是一个动态链接库&#xff08;DLL&#xff09;文件&#xff0c;属于 Microsoft DirectX 软件组件的一部分。它主要负责处理 DirectX 中的图形和多媒体内容&#xff0c;以确保游戏和应用程序能够正常运行。d3dcompiler_47.dll 的主要功能是将 DirectX API 转…

基于SpringBoot的在线宠物用品交易网站

目录 前言 一、技术栈 二、系统功能介绍 用户信息管理 商品分类管理 品牌信息管理 商品信息管理 商品信息 我的收藏 我的订单 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实…

C++设计模式-组合(Composite)

目录 C设计模式-组合&#xff08;Composite&#xff09; 一、意图 二、适用性 三、结构 四、参与者 五、代码 C设计模式-组合&#xff08;Composite&#xff09; 一、意图 将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的…

stm32之手动创建keil工程--HAL库

用CubeMx创建了好多stm32的工程&#xff0c;这里记录下手动创建keil工程的过程。 一、准备工作 1.1、下载对应的HAL库&#xff0c; 这里使用的是stm32f103c8t6, 下载地址stm32HAL库 在页面中输入对应型号点击进行二级页面进行下载 1.2、准备工程 各文件夹下具体操作如下&am…

Qt之QDial(表盘)

简介 QDial类提供了一个四舍五入的范围控制&#xff08;如速度计或电位计&#xff09;&#xff0c;非常适合需要循环计数的情况&#xff0c;例如角度等。 头文件&#xff1a;#include <QDial> qmake&#xff1a;QT widgets 继承&#xff1a;QAbstractSlider …

在WIN10平台上体验Microsoft古老的Quick C 1.0编程

前言&#xff1a; 90年代初&#xff0c;微软出了Quick系统对抗Borland Turbo系列&#xff0c;其中包括 QuickBasic, QuickPascal和Quick C。1991年&#xff0c;Quick C for Windows 1.0发布&#xff0c;后来它被Visual C取代。我自己觉得微软成就在那个winstub.exe桩上&#xf…

连接虚拟机工具推荐

连接虚拟机工具推荐 连接虚拟机的工具有很多种&#xff0c;以下是一些常用的推荐&#xff1a; PuTTY&#xff1a;这是一个非常常用的SSH和telnet客户端&#xff0c;适用于Windows系统。它允许你在本地机器上通过命令行接口远程登录到虚拟机。 SecureCRT&#xff1a;这是一个支…

vscode 配置自定义code snippets 来快速生成你的常用代码

一、功能介绍 什么是 snippets 功能&#xff1f; 其实大家可能体验过vscode 预先内置的许多 snippets, 比如 for 循环。 在输入 for Tab 的时候&#xff0c;就可以自动生成代码模版&#xff0c; 展开就是这样的代码 有一个小窍门是通过 tab 键可以在参数之间进行跳转&#xf…

力扣 -- 1745. 分割回文串 IV

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:bool checkPartitioning(string s) {int ns.size();vector<vector<bool>> dp(n,vector<bool>(n));for(int in-1;i>0;i--){for(int ji;j<n;j){if(s[i]s[j]){dp[i][j]i1<j?dp[i…