Android 13 VSYNC重学习

news2024/11/16 23:47:55

Android 13 VSYNC重学习



引言

学无止境,一个字干就完事!

源码参考基于Android 13 aosp!




一. Android VSync模块开胃菜

在开始正式的分析之前,我们先简单对Android的Vsync模块简单介绍下,如下图所示,其中:

  • HW_VSync是由屏幕产生的脉冲信号,用于控制屏幕的刷新
  • VSync-app和VSync-sf统称为软件VSync,它们是由SurfaceFlinger通过模拟硬件VSync而产生的VSync信号量,再分发给app和sf用来控制它们的合成节奏

image



image





二. Android VSync小结

这里有几点需要补充:

  • VSync-sf是没有对应的EventThread和DispSyncSource

  • VSync-app和VSync-appSf各自都有对应的EventThread和DispSyncSource

  • VSync-sf和VSync-app以及Sync-appSf通过Scheduler的成员mVsyncSchedule指向的VSyncDispatchTimerQueue实例对象关联



Android下VSync设计,牵涉的核心关系图如下:

image


2.1 VSync信号的分类

VSync信号分为两种:硬件VSync信号HW-VSync和软件VSync信号SW-VSync。SW-VSync信号由SW-VSync模型产生。HW-VSync信号负责对SW-VSync模型进行校准。


2.2 HW-Vsync信号的开启

三种场景下会开启硬件VSync信号HW-VSync会对软件VSync信号SW-VSync进行校准

  • SurfaceFlinger初始化。

  • 连续两次请求VSync-app信号的时间间隔超过750ms。

  • SurfaceFlinger合成后,添加FenceTime到VSyncTracker中导致模型计算误差过大。


2.3 SW-VSync模型与计算

谷歌官方采用一元线性回归分析预测法(最小二乘法),通过采样的HW-VSync信号样本(屏幕刷新率),计算对应的SW-VSync信号周期。最终得到一条y=bx+a的拟合曲线。其中,b称为回归系数,a称为截距。SW-VSync模型就是这这条曲线的回归系数和截距。


2.4 SW-VSync信号的分类

SW-VSync信号也分为两种,VSync-sf信号和Vsync-app信号。这两个信号,各司其职:

  • VSync-sf信号用于控制SurfaceFlinger的Layer合成
    - VSync-app信号用于控制App渲染UI

VSync-sf信号和VSync-app信号是在SW-VSync信号的基础上通过叠加不同的偏移量产生,这些偏移量被称为VSync相位偏移。由于偏移量不同VSync-sf信号和VSync-app信号的回调时机也不同。




三. VSync-sf的申请和分发

VSync-sf用于控制SurfaceFlinger合成和渲染一帧图像。当SurfaceFlinger上帧时(BufferQueue中有新的GraphicBuffer),SurfaceFlinger会触发MessageQueue的scheduleFrame方法。接下来我们看下,VSync-sf是如何完成从申请到分发的流程。

3.1 VSync-sf的申请

SurfaceFlinger::scheduleCommit(...)//请求上帧
    mScheduler->scheduleFrame()//MessageQueue.cpp
        mVsync.registration->schedule()//这里的registration实现是VSyncCallbackRegistration,定义在Scheduler/VSyncDispatchTimerQueue.cpp
            mDispatch.get().schedule()//这里的mDispatch指向VSyncDispatchTimerQueue对象


/**
 * @brief 
 * 
 * @param token 
 * @param scheduleTiming 
 * @return ScheduleResult 
 * 1)根据CallbackToken找到所有满足要求的VSyncDispatchTimerQueueEntry。VSyncDispatchTimerQueueEntry是VSyncDispatchTimerQueue中对外部VSync信号请求的封装。
 * 2)遍历调用VSyncDispatchTimerQueue的schedule方法,计算下一次VSync信号的发送时间。
 * 3)对发射时间进行定时,等待下一次VSync信号的发送
 */
ScheduleResult VSyncDispatchTimerQueue::schedule(CallbackToken token,
                                                 ScheduleTiming scheduleTiming) {
              ...
              //根据CallbackToken找到所有满足要求的VSyncDispatchTimerQueueEntry。VSyncDispatchTimerQueueEntry是VSyncDispatchTimerQueue中对外部VSync信号请求的封装。
              auto it = mCallbacks.find(token);
              auto& callback = it->second;
              
              //遍历调用VSyncDispatchTimerQueue的schedule方法,计算下一次VSync信号的发送时间
              result = callback->schedule(scheduleTiming, mTracker, now);
              
              //对发射时间进行定时,等待下一次VSync-sf信号的发送
              rearmTimerSkippingUpdateFor(now, it);
                VSyncDispatchTimerQueue::setTimer()

void VSyncDispatchTimerQueue::setTimer(nsecs_t targetTime, nsecs_t /*now*/) {
    mIntendedWakeupTime = targetTime;
    mTimeKeeper->alarmAt(std::bind(&VSyncDispatchTimerQueue::timerCallback, this),
                         mIntendedWakeupTime);
    mLastTimerSchedule = mTimeKeeper->now();
}     



/**
 * @brief 
 * 1)遍历CallbackMap找到达到唤醒时间的VSyncDispatchTimerQueueEntry,并封装成Invocation,加入Invocation列表。
 * 2)遍历Invocation列表,通过Invocation获取VSyncDispatchTimerQueueEntry,并调用VSyncDispatchTimerQueueEntry的callback方法分发VSync信号。
 */
 //Scheduler/VSyncDispatchTimerQueue.cpp
void VSyncDispatchTimerQueue::timerCallback() {
    struct Invocation {
        std::shared_ptr<VSyncDispatchTimerQueueEntry> callback;
        nsecs_t vsyncTimestamp;
        nsecs_t wakeupTimestamp;
        nsecs_t deadlineTimestamp;
    };
    std::vector<Invocation> invocations;
    {
        std::lock_guard lock(mMutex);
        auto const now = mTimeKeeper->now();
        mLastTimerCallback = now;
        for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
            auto& callback = it->second;
            auto const wakeupTime = callback->wakeupTime();
            if (!wakeupTime) {
                continue;
            }

            auto const readyTime = callback->readyTime();

            auto const lagAllowance = std::max(now - mIntendedWakeupTime, static_cast<nsecs_t>(0));
            if (*wakeupTime < mIntendedWakeupTime + mTimerSlack + lagAllowance) {
                callback->executing();
                invocations.emplace_back(Invocation{callback, *callback->lastExecutedVsyncTarget(),
                                                    *wakeupTime, *readyTime});
            }
        }

        mIntendedWakeupTime = kInvalidTime;
        rearmTimer(mTimeKeeper->now());
    }

    for (auto const& invocation : invocations) {
        invocation.callback->callback(invocation.vsyncTimestamp, invocation.wakeupTimestamp,
                                      invocation.deadlineTimestamp);
    }
}

}
            

3.2 VSync-sf的分发

那么VSync-df的callback是怎么注册到VSyncDispatchTimerQueue的呢,这个我们看下:

SurfaceFlinger::initScheduler(...)
    mScheduler->initVsync(...)//实现在Scheduler/MessageQueue.cpp中
        mVsync.registration = std::make_unique<
            scheduler::VSyncCallbackRegistration>(dispatch,
                                                  std::bind(&MessageQueue::vsyncCallback, this,
                                                            std::placeholders::_1,
                                                            std::placeholders::_2,
                                                            std::placeholders::_3),
                                                  "sf");//这里的dispatch指向VSyncDispatchTimerQueue
 
 //Scheduler/VSyncDispatchTimerQueue.cpp
 VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncDispatch& dispatch,
                                                     VSyncDispatch::Callback callback,
                                                     std::string callbackName)
      : mDispatch(dispatch),
        mToken(dispatch.registerCallback(std::move(callback), std::move(callbackName))),
        mValidToken(true) {}       
        

VSyncDispatchTimerQueue::CallbackToken VSyncDispatchTimerQueue::registerCallback(
        Callback callback, std::string callbackName) {
    std::lock_guard lock(mMutex);
    return CallbackToken{//最终注册到了mCallbacks中
            mCallbacks
                    .emplace(++mCallbackToken,
                             std::make_shared<VSyncDispatchTimerQueueEntry>(std::move(callbackName),
                                                                            std::move(callback),
                                                                            mMinVsyncDistance))
                    .first->first};
}        

所以最后VSync-sf的分发会调用到MessageQueue::vsyncCallback中,我们看下它的实现:

//Scheduler/MessageQueue.cpp
MessageQueue::vsyncCallback(...)
    mHandler->dispatchFrame(vsyncId, vsyncTime)
        mQueue.mLooper->sendMessage(this, Message())
        
        
//Handle的handleMessage接收前面发过来的消息
void MessageQueue::Handler::handleMessage(const Message&) {
    mFramePending.store(false);

    const nsecs_t frameTime = systemTime();
    auto& compositor = mQueue.mCompositor;//这里的compositor实现类是SurfaceFlinger

    if (!compositor.commit(frameTime, mVsyncId, mExpectedVsyncTime)) {
        return;
    }

    compositor.composite(frameTime, mVsyncId);
    compositor.sample();
}






四. VSync-app的申请和分发

在开始后续的章节编写前,我们先重点申明下:

VSync-app用于控制App的UI渲染

VSync-app用于控制App的UI渲染

VSync-app用于控制App的UI渲染


4.1 VSync-app的申请

当Choreographer通过FrameDisplayEventReceiver调用scheduleVsync方法时,会触发VSync-app信号的申请。在FrameDisplayEventReceiver的scheduleVsync方法中,会调用nativeScheduleVsync方法。

image

FrameDisplayEventReceiver的nativeScheduleVsync方法对应的native实现为android_view_DisplayEventReceiver的nativeScheduleVsync函数。

在nativeScheduleVsync函数中,主要做了两件事:

  • 取native层的DisplayEventDispatcher。

  • 调用DisplayEventDispatcher的scheduleVsync方法,请求VSync信号。

image

在DisplayEventDispatcher的scheduleVsync方法中,会调用DisplayEventReceiver的requestNextVsync方法。

image

在DisplayEventReceiver的requestNextVsync方法中,会调用IDisplayEventConnection的requestNextVsync方法。

image

IDisplayEventConnection是一个Binder类,对应bn端的实现类为BnDisplayEventConnection。而EventThreadConnection继承自BnDisplayEventConnection,因此实际调用的是EventThreadConnection的requestNextVsync方法。

image

在EventThreadConnection的requestNextVsync方法中,会调用EventThread的requestNextVsync方法。

image

在EventThread的requestNextVsync方法中,主要做了三件事:

  • 开启硬件VSync信号对软件VSync信号进行校准。

  • 标记EventThreadConnection的vsyncRequest,为后续信号分发做准备。

  • 唤起EventThread对应的线程继续执行VSync信号的分发。

image

//Scheduler/EventThread.cpp
void EventThread::requestNextVsync(const sp<EventThreadConnection>& connection) {
    if (connection->resyncCallback) {
        /**
         * @brief 
         * 调用到Scheduler::resync
         * 开启硬件Vsync信号对软件Vsync信号进行校准
         */
        connection->resyncCallback();
    }

    std::lock_guard<std::mutex> lock(mMutex);

    if (connection->vsyncRequest == VSyncRequest::None) {
        connection->vsyncRequest = VSyncRequest::Single;
        mCondition.notify_all();//唤起EventThread中的线程
    } else if (connection->vsyncRequest == VSyncRequest::SingleSuppressCallback) {
        connection->vsyncRequest = VSyncRequest::Single;
    }
}

在EventThread的threadMain中,会通过VSyncCallbackRegistration请求或取消VSync信号。

如果是请求VSync信号,会调用VSyncCallbackRegistration的schedule方法。在VSyncCallbackRegistration的schedule方法,会调用VSyncDispatch的schedule方法。

image

void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {

        if (mState != nextState) {
            if (mState == State::VSync) {
                mVSyncSource->setVSyncEnabled(false);
            } else if (nextState == State::VSync) {
                mVSyncSource->setVSyncEnabled(true);
            }

            mState = nextState;
        }
    
}

之后的流程与VSync-sf信号的申请流程相同。在VSyncDispatchTimerQueue的schedule方法中,会调用scheduleLocked方法。

在VSyncDispatchTimerQueue的scheduleLocked方法中,主要做了三件事:

  • 根据CallbackToken找到所有满足要求的VSyncDispatchTimerQueueEntry。VSyncDispatchTimerQueueEntry是VSyncDispatchTimerQueue中对外部VSync信号请求的封装。

  • 遍历调用VSyncDispatchTimerQueue的schedule方法,计算下一次VSync信号的发送时间。

  • 对发射时间进行定时,等待下一次VSync信号的发送。

image


4.2 VSync-app的分发

当定时时间到达时,TimerKeeper会回调VSyncDispatchTimerQueue的timerCallback方法。

在VSyncDispatchTimerQueue的timerCallback方法方法中,主要做了两件事:

  • 遍历CallbackMap找到达到唤醒时间的VSyncDispatchTimerQueueEntry,并封装成Invocation,加入Invocation列表。

  • 遍历Invocation列表,通过Invocation获取VSyncDispatchTimerQueueEntry,并调用VSyncDispatchTimerQueueEntry的callback方法分发VSync信号。

image

在VSyncDispatchTimerQueueEntry的callback方法中,会调用类型为CallbackRepeater::callbackk,然后在该方法中接着调用mCallback(vsyncTime, wakeupTime, readyTime)方法,而这里的mCallback(指向DispSyncSource::onVsyncCallback,最后回调EventThread的onVSyncEvent方法。

对于上述的分发流程是不是还有点懵逼,我们反过来看看VSync-app分发的注册,其核心是DispSyncSource和EventThread以及VSyncDispatchTimerQueue的各种回调callback流程:


//Scheduler/VSyncDispatchTimerQueue.cpp
VSyncDispatchTimerQueue::CallbackToken VSyncDispatchTimerQueue::registerCallback(
        Callback callback, std::string callbackName) {
    std::lock_guard lock(mMutex);
    return CallbackToken{
            mCallbacks
                    .emplace(++mCallbackToken,
                             std::make_shared<VSyncDispatchTimerQueueEntry>(std::move(callbackName),
                                                                            std::move(callback),
                                                                            mMinVsyncDistance))
                    .first->first};
}

//Scheduler/VSyncDispatchTimerQueue.cpp
VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncDispatch& dispatch,
                                                     VSyncDispatch::Callback callback,
                                                     std::string callbackName)
      : mDispatch(dispatch),
        mToken(dispatch.registerCallback(std::move(callback), std::move(callbackName))),
        mValidToken(true) {}


//Scheduler/DispSyncSource.cpp
class CallbackRepeater {
public:
    CallbackRepeater(VSyncDispatch& dispatch, VSyncDispatch::Callback cb, const char* name,
                     std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration,
                     std::chrono::nanoseconds notBefore)
          : mName(name),
            mCallback(cb),
            //VSyncCallbackRegistration mRegistration GUARDED_BY(mMutex);
            mRegistration(dispatch,
                          std::bind(&CallbackRepeater::callback, this, std::placeholders::_1,
                                    std::placeholders::_2, std::placeholders::_3),
                          mName),
            mStarted(false),
            mWorkDuration(workDuration),
            mReadyDuration(readyDuration),
            mLastCallTime(notBefore) {}

    ~CallbackRepeater() {
        std::lock_guard lock(mMutex);
        mRegistration.cancel();
    }

    void start(std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration) {
        std::lock_guard lock(mMutex);
        mStarted = true;
        mWorkDuration = workDuration;
        mReadyDuration = readyDuration;

        auto const scheduleResult = 
                mRegistration.schedule({.workDuration = mWorkDuration.count(),
                                        .readyDuration = mReadyDuration.count(),
                                        .earliestVsync = mLastCallTime.count()});
        LOG_ALWAYS_FATAL_IF((!scheduleResult.has_value()), "Error scheduling callback");
    }

    void stop() {
        std::lock_guard lock(mMutex);
        LOG_ALWAYS_FATAL_IF(!mStarted, "DispSyncInterface misuse: callback already stopped");
        mStarted = false;
        mRegistration.cancel();
    }

    void dump(std::string& result) const {
        std::lock_guard lock(mMutex);
        const auto relativeLastCallTime =
                mLastCallTime - std::chrono::steady_clock::now().time_since_epoch();
        StringAppendF(&result, "\t%s: ", mName.c_str());
        StringAppendF(&result, "mWorkDuration=%.2f mReadyDuration=%.2f last vsync time ",
                      mWorkDuration.count() / 1e6f, mReadyDuration.count() / 1e6f);
        StringAppendF(&result, "%.2fms relative to now (%s)\n", relativeLastCallTime.count() / 1e6f,
                      mStarted ? "running" : "stopped");
    }

private:
    void callback(nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) {
        {
            std::lock_guard lock(mMutex);
            mLastCallTime = std::chrono::nanoseconds(vsyncTime);
        }

        mCallback(vsyncTime, wakeupTime, readyTime);

        {
            std::lock_guard lock(mMutex);
            if (!mStarted) {
                return;
            }
            auto const scheduleResult =
                    mRegistration.schedule({.workDuration = mWorkDuration.count(),
                                            .readyDuration = mReadyDuration.count(),
                                            .earliestVsync = vsyncTime});
            LOG_ALWAYS_FATAL_IF(!scheduleResult.has_value(), "Error rescheduling callback");
        }
    }

    const std::string mName;
    scheduler::VSyncDispatch::Callback mCallback;

    mutable std::mutex mMutex;
    VSyncCallbackRegistration mRegistration GUARDED_BY(mMutex);
    bool mStarted GUARDED_BY(mMutex) = false;
    std::chrono::nanoseconds mWorkDuration GUARDED_BY(mMutex) = 0ns;
    std::chrono::nanoseconds mReadyDuration GUARDED_BY(mMutex) = 0ns;
    std::chrono::nanoseconds mLastCallTime GUARDED_BY(mMutex) = 0ns;
};



mAppConnectionHandle =
            mScheduler->createConnection("app" .....)
            Scheduler::createConnection()
                auto vsyncSource = makePrimaryDispSyncSource(connectionName, workDuration, readyDuration)
                    return std::make_unique<scheduler::DispSyncSource>(mVsyncSchedule->getDispatch(),
                                                       mVsyncSchedule->getTracker(), workDuration,
                                                       readyDuration, traceVsync, name);
                        //std::unique_ptr<CallbackRepeater> mCallbackRepeater;
                        mCallbackRepeater =
                                std::make_unique<CallbackRepeater>(vSyncDispatch,
                                                                   std::bind(&DispSyncSource::onVsyncCallback, this,
                                                                             std::placeholders::_1,
                                                                             std::placeholders::_2,
                                                                             std::placeholders::_3),
                                                                   name, workDuration, readyDuration,
                                                                   std::chrono::steady_clock::now().time_since_epoch());      
                mVSyncSource->setCallback(this);//为DispVsyncSource设置回调
                     void DispSyncSource::setCallback(VSyncSource::Callback* callback) {
                        std::lock_guard lock(mCallbackMutex);
                        mCallback = callback;
                    }               
                    
 //最终整理出来的Vsync-app分发流程为,各种弯弯绕绕:
 VSyncDispatchTimerQueue::timerCallback()//Scheduler/VSyncDispatchTimerQueue.cpp
               invocation.callback->callback(...)//这里的callback指向VSyncDispatchTimerQueueEntry::callback,Scheduler/VSyncDispatchTimerQueue.cpp
                    mCallback(vsyncTimestamp, wakeupTimestamp, deadlineTimestamp)//这里的 mCallback指向CallbackRepeater::callback,实现在Scheduler/DispSyncSource.cpp 
                        mCallback(vsyncTime, wakeupTime, readyTime)//这里的callback指向DispSyncSource::onVsyncCallback。是现在Scheduler/DispSyncSource.cpp
                            callback = mCallback;
                            callback->onVSyncEvent(targetWakeupTime, {vsyncTime, readyTime})//这里的callback指向EventThread::onVSyncEvent

在EventThread的onVSyncEvent方法中,主要做了三件事:

  • 调用makeVSync函数,创建Event。

  • 将Event加入到vector<DisplayEventReceiver::Event> 中。

  • 唤醒等待线程,执行threadMain方法。

image

void EventThread::onVSyncEvent(nsecs_t timestamp, VSyncSource::VSyncData vsyncData) {
    std::lock_guard<std::mutex> lock(mMutex);

    LOG_FATAL_IF(!mVSyncState);
    //包装为DisplayEventReceiver::Event对象,存入mPendingEvents尾部
    mPendingEvents.push_back(makeVSync(mVSyncState->displayId, timestamp, ++mVSyncState->count,
                                       vsyncData.expectedPresentationTime,
                                       vsyncData.deadlineTimestamp));
    //唤醒线程
    mCondition.notify_all();
}

我们接下来看EventThread是如何处理分发事件的:

//Scheduler/EventThread.cpp
void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {
    DisplayEventConsumers consumers;

    while (mState != State::Quit) {
        std::optional<DisplayEventReceiver::Event> event;
        // Determine next event to dispatch.
        if (!mPendingEvents.empty()) {
            event = mPendingEvents.front();
            mPendingEvents.pop_front();        
            ...
        }
        
        // Find connections that should consume this event.
        auto it = mDisplayEventConnections.begin();
        while (it != mDisplayEventConnections.end()) {
            if (const auto connection = it->promote()) {
                vsyncRequested |= connection->vsyncRequest != VSyncRequest::None;
                //用来在任务的循环执行中保存当前Vsync信号的消费者
                if (event && shouldConsumeEvent(*event, connection)) {
                    consumers.push_back(connection);//这里的consumers就是待分发的目标
                }

                ++it;
            } else {
                it = mDisplayEventConnections.erase(it);
            }
        }        

        /**
         * @brief 
         * 在该方法中,会循环分发信号,主要做了五件事情
         * 1) 从Vsync信息队列中获取消息
         * 2)收集监听Vsync信号的EventThreadConnection,并加入到consumers中
         * 3) 调用dispatchEvent方法来分发Vsync信号
         * 4)计算当前状态,根据状态请求或取消下一次VSync信号
         * 5)如果没有Vsync信号需要分发,线程进入等待状态
         */
        if (!consumers.empty()) {
            dispatchEvent(*event, consumers);
                consumer->postEvent(copy)
                    DisplayEventReceiver::sendEvents(...)
            consumers.clear();
        }        
        

最终VSync-app分发的事件会被Choreographer模块接收,开始安排应用相关的渲染UI逻辑!




Andoid SurfaceFlinger(二) VSYNC的开始,连续,结束
VSYNC研究-最后的窗户纸
Android 12(S) 图像显示系统 - SurfaceFlinger之VSync-上篇(十六)
Android 12(S) 图像显示系统 - SurfaceFlinger 之 VSync - 中篇(十七)
深度详解 Android S(12.0)屏幕刷新机制之 Choreographer
View绘制流程3-Vsync信号是如何发送和接受的
Android R Vsync相关梳理
显示框架之深入Vsync原理
App/Sf的Vsync部分源码流程结合perfetto/systrace分析
Android-View绘制原理(02)-VSync原理之SurfaceFlinger篇
一文搞定Android VSync来龙机制去脉
VSync信号系统与SurfaceFlinger
SurfaceFlinger-Vsync信号
Android VSync事件分发过程源码分析

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

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

相关文章

Dolphinscheduler不重启加载Oracle驱动

转载自刘茫茫看山 问题背景 某天我们的租户反馈数据库连接缺少必要的驱动&#xff0c;我们通过日志查看确实是缺少部分数据库的驱动&#xff0c;因为DolphinScheduler默认只带了Oracle和MySQL的驱动&#xff0c;并且需要将pom文件中的test模式去掉才可以在打包的时候引入。我…

C# 中 async 与 await 关键字详解

async 和 await 关键字的作用是使方法能够异步执行并等待异步操作的完成。&#xff08;最重要的一点是记住 “异步执行”与“等待异步操作完成”&#xff0c;不是等待主线程操作完成&#xff09; async 修饰符可将 方法、lambda 表达式或匿名方法指定为异步。 async 关键字用于…

一张图一个表——CSS选择器总结

CSS选择器总结&#xff1a; (这些表是一张图片^_^) 看底部 完整思维导图图片和表格的下载地址&#xff1a;https://download.csdn.net/download/denlnyyr/10597820

LeetCode 377.组合总和Ⅳ

这题是我蠢了&#xff0c;它说是组合数我就信了&#xff0c;言尽于此 class Solution { public:int combinationSum4(vector<int>& nums, int target) {vector<int> dp(2000,0);dp[0]1;for(int i0;i<target;i){for(int j0;j<nums.size();j){if(i>nums…

22.Volatile原理

文章目录 Volatile原理1.Volatile语义中的内存屏障1.1.volatile写操作的内存屏障1.1.1.StoreStore 屏障1.1.2.StoreLoad 屏障 1.2.volatile读操作的内存屏障1.2.1.LoadStore屏障1.2.2.LoadLoad屏障 2.volatile不具备原子性2.1.原理 Volatile原理 1.Volatile语义中的内存屏障 在…

定位器与PWM的LED控制

文章目录 一、STM32定时器二、脉宽调制pwm三、定时器控制led&#xff08;1&#xff09;实验内容&#xff08;2&#xff09;创建工程&#xff08;3&#xff09;Keli程序&#xff08;4&#xff09;观察波形图&#xff08;5&#xff09;实物连接图&#xff08;6&#xff09;实践效…

Python机器学习 Tensorflow + keras 实现CNN

一、实验目的 1. 了解SkLearn Tensorlow使用方法 2. 了解SkLearn keras使用方法 二、实验工具&#xff1a; 1. SkLearn 三、实验内容 &#xff08;贴上源码及结果&#xff09; 使用Tensorflow对半环形数据集分 #encoding:utf-8import numpy as npfrom sklearn.datasets i…

AI图书推荐:用ChatGPT和Python搭建AI应用来变现

《用ChatGPT和Python搭建AI应用来变现》&#xff08;Building AI Applications with ChatGPT API&#xff09;将ChatGPT API与Python结合使用&#xff0c;可以开启构建非凡AI应用的大门。通过利用这些API&#xff0c;你可以专注于应用逻辑和用户体验&#xff0c;而ChatGPT强大的…

clocking wizard IP核通过AXI4-Lite接口实现动态重新配置应用实例

在最近的FPGA应用中&#xff0c;应用到了基于Zynq 7000的Uart串口设计&#xff0c;为了让串口的时钟更精确&#xff0c;采用了外部时钟模式&#xff0c;如下图所示。外部时钟连接到了Clocking Wizard IP核的输出端。 在串口通信时&#xff0c;发现串口有错码出现。例如&#xf…

ADS基础教程16 - 存档和导入(workspace、cell、view)

设计加密保护IP 一、引言二、workspace归档二、Cell归档三、View归档四、导入归档文件 一、引言 介绍如何ADS中如何对workspace、cell和view进行存档&#xff0c;以及如何将存档文件导入到工程中。 二、workspace归档 (1)在菜单栏中&#xff0c;选择File–>Archive Works…

Follow Your Pose: Pose-Guided Text-to-Video Generation using Pose-Free Videos

清华深&港科&深先进&Tencent AAAI24https://github.com/mayuelala/FollowYourPose 问题引入 本文的任务是根据文本来生成高质量的角色视频&#xff0c;并且可以通过pose来控制任务的姿势&#xff1b;当前缺少video-pose caption数据集&#xff0c;所以提出一个两…

Chisel入门——在windows下vscode搭建|部署Scala2.13.3开发环境|用Chisel点亮FPGA小灯

文章目录 前言一、vscode搭建scala开发环境1.1 安装Scala官方插件Scala Syntax1.2 创建hello_world.scala文件1.3 确认java的版本(博主使用的是1.8)1.4 下载Scala Windows版本的二进制文件1.5 配置环境变量1.6 交互模式测试一下1.7 vscode运行scala 二、windows安装sbt2.1 下载…

Matlab|主动配电网故障恢复与孤岛划分模型【多时段】

目录 1 主要内容 1.1 模型目标 1.2 约束条件 2 部分代码 3 程序结果 4 下载链接 1 主要内容 程序主要方法复现《主动配电网故障恢复的重构与孤岛划分统一模型》&#xff0c;完全复现检修策略约束和潮流约束&#xff0c;辐射状与连通性约束考虑孤岛划分情形&#xff0c;采…

翻译《Use FILE_SHARE_DELETE in your shell extension》

在写 《翻译《The Old New Thing》- What did MakeProcInstance do?》 文章时&#xff0c;了解到了 Michael Geary &#xff0c;他也有不少优秀的技术文章&#xff0c;现翻译一篇关于文件操作的细节的文章 原文 Use FILE_SHARE_DELETE in your shell extension | mg.tohttps:…

【Unity之FGUI】白仙章Fairy GUI控件详解二

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;就业…

硬盘监控,保障硬盘性能

硬盘驱动器是个人计算机和服务器中用于存储数字数据的硬件部件&#xff0c;硬盘突然故障可能导致永久数据丢失&#xff0c;大多数硬盘驱动器使用自我监控、分析和报告技术&#xff08;SMART&#xff09;来跟踪各种性能指标并分析其自身的运行状况。然而&#xff0c;并不是所有的…

HackTheBox-Machines--Popcorn

文章目录 0x01 端口扫描0x02 测试思路2.1 80端口测试 0x03 /torrent 目录文件上传测试0x04 权限提升 Popcorn 测试过程 0x01 端口扫描 (base) gryphonwsdl ~ %nmap -sC -sV 10.129.138.22 Starting Nmap 7.94 ( https://nmap.org ) at 2024-05-28 14:22 CST Nmap scan report …

Linux shell编程学习笔记51: cat /proc/cpuinfo:查看CPU详细信息

0 前言 2024年的网络安全检查又开始了&#xff0c;对于使用基于Linux的国产电脑&#xff0c;我们可以编写一个脚本来收集系统的有关信息。对于中央处理器CPU比如&#xff0c;我们可以使用cat /proc/cpuinfo命令来收集中央处理器CPU的信息。 1. /proc/cpuinfo 保存了系统的cpu…

贵州大学24计算机考研数据速览,国家重点实验室22408复试线285分!贵州大学计算机考研考情分析!

贵州大学计算机科学与技术学院坐落在贵州大学北校区&#xff08;贵阳花溪&#xff09;。 学院现有教职工139人&#xff0c;其中专职教师126人&#xff0c;教授17人&#xff0c;副教授37人&#xff0c;讲师46人&#xff0c;高级实验师4人&#xff0c;实验师17人。具有博士学位的…

【易错题】数据可视化基础练习题(30道选择题)#CDA Level 1

本文整理了数据可视化基础知识相关的练习题&#xff0c;共30道&#xff0c;适用于想巩固数据可视化知识的同学&#xff0c;也可作为备考CDA一级的补充习题。来源&#xff1a;如荷学数据科学题库&#xff08;技术专项-可视化&#xff09;。 1&#xff09; 2&#xff09; 3&…