• requestAnimationFrame在Chrome里的实现


    requestAnimationFrame是HTML5游戏和动画必不可少的函数,相对于setTimeout或setInterval它有两个优势,一是它注册的回调函数与浏览器的渲染同步,不用担心Timer的时间间隔太长或太短。二是时间间隔相对与Timer要稳定,requestAnimationFrame注册的回调函数最高执行频率是60FPS,虽然在HTML5游戏里通常是达不到的,但是它两次调用之间时间间隔要比Timer稳定一些。前段时间我在CanTK Runtime里自己模拟过requestAnimationFrame,为了深入的理解Chrome里实现requestAnimationFrame方法,花了一点时间去读Blink的代码。

    requestAnimationFrame的基本用法如下:

    var start = null;
    var element = document.getElementById("SomeElementYouWantToAnimate");
    
    function step(timestamp) {
      if (!start) start = timestamp;
      var progress = timestamp - start;
      element.style.left = Math.min(progress/10, 200) + "px";
      if (progress < 2000) {
        window.requestAnimationFrame(step);
      }
    }
    
    window.requestAnimationFrame(step);

    我比较关注的是回调函数的注册和调用过程:

    • 1.注册回调函数的实现。

    (WebKit/Source/core/dom/ScriptedAnimationController.cpp)

    ScriptedAnimationController::CallbackId ScriptedAnimationController::registerCallback(FrameRequestCallback* callback)
    {
        CallbackId id = m_callbackCollection.registerCallback(callback);
        scheduleAnimationIfNeeded();
        return id;
    }

    注册之后还需要请求重绘,scheduleAnimationIfNeeded最终会调到ThreadProxy::SendCommitRequestToImplThreadIfNeeded:

    (cc/trees/thread_proxy.cc)

    bool ThreadProxy::SendCommitRequestToImplThreadIfNeeded(
        CommitPipelineStage required_stage) {
      DCHECK(IsMainThread());
      DCHECK_NE(NO_PIPELINE_STAGE, required_stage);
      bool already_posted =
          main().max_requested_pipeline_stage != NO_PIPELINE_STAGE;
      main().max_requested_pipeline_stage =
          std::max(main().max_requested_pipeline_stage, required_stage);
      if (already_posted)
        return false;
      Proxy::ImplThreadTaskRunner()->PostTask(
          FROM_HERE,
          base::Bind(&ThreadProxy::SetNeedsCommitOnImplThread,
                     impl_thread_weak_ptr_));
      return true;
    }
    
    void ThreadProxy::SetNeedsCommitOnImplThread() {
      TRACE_EVENT0("cc", "ThreadProxy::SetNeedsCommitOnImplThread");
      DCHECK(IsImplThread());
      impl().scheduler->SetNeedsBeginMainFrame();
    }
    
    void Scheduler::SetNeedsBeginMainFrame() {
      state_machine_.SetNeedsBeginMainFrame();
      ProcessScheduledActions();
    } 
    • 2.执行回调函数的实现。

    WebKit/Source/core/dom/ScriptedAnimationController.cpp

    void ScriptedAnimationController::executeCallbacks(double monotonicTimeNow)
    {
        // dispatchEvents() runs script which can cause the document to be destroyed.
        if (!m_document)
            return;
    
        double highResNowMs = 1000.0 * m_document->loader()->timing().monotonicTimeToZeroBasedDocumentTime(monotonicTimeNow);
        double legacyHighResNowMs = 1000.0 * m_document->loader()->timing().monotonicTimeToPseudoWallTime(monotonicTimeNow);
        m_callbackCollection.executeCallbacks(highResNowMs, legacyHighResNowMs);
    }
    void FrameRequestCallbackCollection::executeCallbacks(double highResNowMs, double highResNowMsLegacy)
    {
        // First, generate a list of callbacks to consider.  Callbacks registered from this point
        // on are considered only for the "next" frame, not this one.
        ASSERT(m_callbacksToInvoke.isEmpty());
        m_callbacksToInvoke.swap(m_callbacks);
    
        for (size_t i = 0; i < m_callbacksToInvoke.size(); ++i) {
            FrameRequestCallback* callback = m_callbacksToInvoke[i].get();
            if (!callback->m_cancelled) {
                TRACE_EVENT1("devtools.timeline", "FireAnimationFrame", "data", InspectorAnimationFrameEvent::data(m_context, callback->m_id));
                InspectorInstrumentationCookie cookie = InspectorInstrumentation::willFireAnimationFrame(m_context, callback->m_id);
                if (callback->m_useLegacyTimeBase)
                    callback->handleEvent(highResNowMsLegacy);
                else
                    callback->handleEvent(highResNowMs);
                InspectorInstrumentation::didFireAnimationFrame(cookie);
                TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "UpdateCounters", TRACE_EVENT_SCOPE_THREAD, "data", InspectorUpdateCountersEvent::data());
            }
        }
    
        m_callbacksToInvoke.clear();
    }

    这行代码m_callbacksToInvoke.swap(m_callbacks);比较有意思,在循环执行的callbacks会有新的callback注册进来,我在实现这个功能时,没看过这段代码,当时费了点功夫才想明白怎么搞。

    上面的代码是由RenderWidgetCompositor::BeginMainFrame调过来的:
    (src/content/renderer/gpu/render_widget_compositor.cc)

    void RenderWidgetCompositor::BeginMainFrame(const cc::BeginFrameArgs& args) {
      double frame_time_sec = (args.frame_time - base::TimeTicks()).InSecondsF();
      double deadline_sec = (args.deadline - base::TimeTicks()).InSecondsF();
      double interval_sec = args.interval.InSecondsF();
      WebBeginFrameArgs web_begin_frame_args =
          WebBeginFrameArgs(frame_time_sec, deadline_sec, interval_sec);
      compositor_deps_->GetRendererScheduler()->WillBeginFrame(args);
      widget_->webwidget()->beginFrame(web_begin_frame_args);
    }
  • 相关阅读:
    最小生成树 kruskal算法&prim算法
    Floyd算法解决多源最短路问题
    dijkstra算法解决单源最短路问题
    改进后的作业排序
    第一篇 基本结构
    循环轮转算法
    在线工具生成接入信息mqtt.fx快速接入阿里云
    NodeMCU使用ArduinoJson判断指定键值对存在与否
    NodeMCU获取并解析心知天气信息
    快速导出jekyll博客文件进行上传部署
  • 原文地址:https://www.cnblogs.com/zhangyunlin/p/6167331.html
Copyright © 2020-2023  润新知