requestAnimationFrame在Chrome里的实现
2015-10-02 07:19
701 查看
requestAnimationFrame是HTML5游戏和动画必不可少的函数,相对于setTimeout或setInterval它有两个优势,一是它注册的回调函数与浏览器的渲染同步,不用担心Timer的时间间隔太长或太短。二是时间间隔相对与Timer要稳定,requestAnimationFrame注册的回调函数最高执行频率是60FPS,虽然在HTML5游戏里通常是达不到的,但是它两次调用之间时间间隔要比Timer稳定一些。前段时间我在CanTK Runtime里自己模拟过requestAnimationFrame,为了深入的理解Chrome里实现requestAnimationFrame方法,花了一点时间去读Blink的代码。
requestAnimationFrame的基本用法如下:
我比较关注的是回调函数的注册和调用过程:
1.注册回调函数的实现。
(WebKit/Source/core/dom/ScriptedAnimationController.cpp)
注册之后还需要请求重绘,scheduleAnimationIfNeeded最终会调到ThreadProxy::SendCommitRequestToImplThreadIfNeeded:
(cc/trees/thread_proxy.cc)
2.执行回调函数的实现。
WebKit/Source/core/dom/ScriptedAnimationController.cpp
这行代码m_callbacksToInvoke.swap(m_callbacks);比较有意思,在循环执行的callbacks会有新的callback注册进来,我在实现这个功能时,没看过这段代码,当时费了点功夫才想明白怎么搞。
上面的代码是由RenderWidgetCompositor::BeginMainFrame调过来的:
(src/content/renderer/gpu/render_widget_compositor.cc)
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); }
相关文章推荐
- SpriteBuilder中关节的Breaking force属性
- SpriteBuilder中关节的Breaking force属性
- SpriteBuilder中关节的Breaking force属性
- QSqlQuery类
- [Leetcode]Letter Combinations of a Phone Number My Submissions Question Solution
- [LeetCode#51]N-Queens
- Service API Guides
- [Leetcode] Unique Binary Search Trees
- easyui实现datagrid行内编辑
- HDOJ 1503 Advanced Fruits(LCS+记录路径)
- [LeetCode] Unique Word Abbreviation
- 创建easyui-dialog实现增加/修改bean信息
- Ch6-Queue_Linked
- Ch6-Queue_Array
- 转载:iOS开发系列之UITableView实例全面解析
- 关于looper,handle,messageQuerry
- UIView常见方法总结(一)
- JDBC中Statement接口提供的execute、executeQuery和executeUpdate之间的区别(转)
- UIAlertView
- uva 1626 - Brackets sequence