Import Cobalt 13.95615 Change-Id: Id63ba05afe04cf6cef5344b716b7f5a2929de0a5
diff --git a/src/base/base_paths_starboard.cc b/src/base/base_paths_starboard.cc index e02d2f4..b3b6e8e 100644 --- a/src/base/base_paths_starboard.cc +++ b/src/base/base_paths_starboard.cc
@@ -91,25 +91,21 @@ return PathProviderStarboard(base::DIR_CACHE, result); case base::DIR_SYSTEM_FONTS: -#if SB_API_VERSION >= 4 if (SbSystemGetPath(kSbSystemPathFontDirectory, path, SB_ARRAY_SIZE_INT(path))) { *result = FilePath(path); return true; } DLOG(INFO) << "DIR_SYSTEM_FONTS not defined."; -#endif // SB_API_VERSION >= 4 return false; case base::DIR_SYSTEM_FONTS_CONFIGURATION: -#if SB_API_VERSION >= 4 if (SbSystemGetPath(kSbSystemPathFontConfigurationDirectory, path, SB_ARRAY_SIZE_INT(path))) { *result = FilePath(path); return true; } DLOG(INFO) << "DIR_SYSTEM_FONTS_CONFIGURATION not defined."; -#endif // SB_API_VERSION >= 4 return false; }
diff --git a/src/base/time_starboard.cc b/src/base/time_starboard.cc index 9225951..75dd719 100644 --- a/src/base/time_starboard.cc +++ b/src/base/time_starboard.cc
@@ -98,7 +98,7 @@ // static TimeTicks TimeTicks::ThreadNow() { -#if SB_API_VERSION >= 3 && SB_HAS(TIME_THREAD_NOW) +#if SB_HAS(TIME_THREAD_NOW) return TimeTicks(SbTimeGetMonotonicThreadNow()); #else return HighResNow(); @@ -107,7 +107,7 @@ // static bool TimeTicks::HasThreadNow() { -#if SB_API_VERSION >= 3 && SB_HAS(TIME_THREAD_NOW) +#if SB_HAS(TIME_THREAD_NOW) return true; #else return false;
diff --git a/src/cobalt/base/c_val_collection_timer_stats.h b/src/cobalt/base/c_val_collection_timer_stats.h index bee81ee..2fdd87a 100644 --- a/src/cobalt/base/c_val_collection_timer_stats.h +++ b/src/cobalt/base/c_val_collection_timer_stats.h
@@ -71,6 +71,9 @@ start_time_ = base::TimeTicks(); } + // Stops any started timer and this sample is ignored. + void Cancel() { start_time_ = base::TimeTicks(); } + // Flush the collection. This causes |CValCollectionStats| to update its stats // and clear the entries. void Flush() { entry_stats_.Flush(); }
diff --git a/src/cobalt/browser/browser.gyp b/src/cobalt/browser/browser.gyp index 97fff2f..1629c67 100644 --- a/src/cobalt/browser/browser.gyp +++ b/src/cobalt/browser/browser.gyp
@@ -96,6 +96,8 @@ 'suspend_fuzzer.h', 'switches.cc', 'switches.h', + 'system_platform_error_handler.cc', + 'system_platform_error_handler.h', 'trace_manager.cc', 'trace_manager.h', 'url_handler.cc',
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc index cfc4a45..870d7b5 100644 --- a/src/cobalt/browser/browser_module.cc +++ b/src/cobalt/browser/browser_module.cc
@@ -14,6 +14,7 @@ #include "cobalt/browser/browser_module.h" +#include <algorithm> #include <vector> #include "base/bind.h" @@ -252,10 +253,13 @@ timeout_polling_thread_(kTimeoutPollingThreadName), render_timeout_count_(0), #endif + on_error_retry_count_(0), will_quit_(false), application_state_(initial_application_state), splash_screen_cache_(new SplashScreenCache()), - produced_render_tree_(false) { + navigation_produced_main_render_tree_(false) { + h5vcc_url_handler_.reset(new H5vccURLHandler(this)); + #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT) SbCoreDumpRegisterHandler(BrowserModule::CoreDumpHandler, this); on_error_triggered_count_ = 0; @@ -330,12 +334,15 @@ fallback_splash_screen_url_ = options.fallback_splash_screen_url; // Synchronously construct our WebModule object. - NavigateInternal(url); + Navigate(url); DCHECK(web_module_); } BrowserModule::~BrowserModule() { DCHECK_EQ(MessageLoop::current(), self_message_loop_); + if (on_error_retry_timer_.IsRunning()) { + on_error_retry_timer_.Stop(); + } #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT) SbCoreDumpUnregisterHandler(BrowserModule::CoreDumpHandler, this); #endif @@ -343,42 +350,17 @@ void BrowserModule::Navigate(const GURL& url) { TRACE_EVENT0("cobalt::browser", "BrowserModule::Navigate()"); + // Reset the waitable event regardless of the thread. This ensures that the + // webdriver won't incorrectly believe that the webmodule has finished loading + // when it calls Navigate() and waits for the |web_module_loaded_| signal. web_module_loaded_.Reset(); - // Always post this as a task in case this is being called from the WebModule. - self_message_loop_->PostTask( - FROM_HERE, base::Bind(&BrowserModule::NavigateInternal, weak_this_, url)); -} - -void BrowserModule::Reload() { - TRACE_EVENT0("cobalt::browser", "BrowserModule::Reload()"); - DCHECK_EQ(MessageLoop::current(), self_message_loop_); - DCHECK(web_module_); - web_module_->ExecuteJavascript( - "location.reload();", - base::SourceLocation("[object BrowserModule]", 1, 1), - NULL /* output: succeeded */); -} - -#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT) -// static -void BrowserModule::CoreDumpHandler(void* browser_module_as_void) { - BrowserModule* browser_module = - static_cast<BrowserModule*>(browser_module_as_void); - SbCoreDumpLogInteger("BrowserModule.on_error_triggered_count_", - browser_module->on_error_triggered_count_); -#if defined(COBALT_CHECK_RENDER_TIMEOUT) - SbCoreDumpLogInteger("BrowserModule.recovery_mechanism_triggered_count_", - browser_module->recovery_mechanism_triggered_count_); - SbCoreDumpLogInteger("BrowserModule.timeout_response_trigger_count_", - browser_module->timeout_response_trigger_count_); -#endif // defined(COBALT_CHECK_RENDER_TIMEOUT) -} -#endif // SB_HAS(CORE_DUMP_HANDLER_SUPPORT) - -void BrowserModule::NavigateInternal(const GURL& url) { - TRACE_EVENT0("cobalt::browser", "BrowserModule::NavigateInternal()"); - DCHECK_EQ(MessageLoop::current(), self_message_loop_); + // Repost to our own message loop if necessary. + if (MessageLoop::current() != self_message_loop_) { + self_message_loop_->PostTask( + FROM_HERE, base::Bind(&BrowserModule::Navigate, weak_this_, url)); + return; + } // First try the registered handlers (e.g. for h5vcc://). If one of these // handles the URL, we don't use the web module. @@ -386,6 +368,11 @@ return; } + on_error_url_.clear(); + if (on_error_retry_timer_.IsRunning()) { + on_error_retry_timer_.Stop(); + } + // Destroy old WebModule first, so we don't get a memory high-watermark after // the second WebModule's constructor runs, but before scoped_ptr::reset() is // run. @@ -402,12 +389,15 @@ const math::Size& viewport_size = GetViewportSize(); DestroySplashScreen(); + navigation_produced_main_render_tree_ = false; base::optional<std::string> key = SplashScreenCache::GetKeyForStartUrl(url); if (fallback_splash_screen_url_ || (key && splash_screen_cache_->IsSplashScreenCached(*key))) { // Create the splash screen layer. - splash_screen_layer_ = - render_tree_combiner_->CreateLayer(kSplashScreenZIndex); + if (render_tree_combiner_) { + splash_screen_layer_ = + render_tree_combiner_->CreateLayer(kSplashScreenZIndex); + } splash_screen_.reset(new SplashScreen( application_state_, @@ -470,6 +460,32 @@ } } +void BrowserModule::Reload() { + TRACE_EVENT0("cobalt::browser", "BrowserModule::Reload()"); + DCHECK_EQ(MessageLoop::current(), self_message_loop_); + DCHECK(web_module_); + web_module_->ExecuteJavascript( + "location.reload();", + base::SourceLocation("[object BrowserModule]", 1, 1), + NULL /* output: succeeded */); +} + +#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT) +// static +void BrowserModule::CoreDumpHandler(void* browser_module_as_void) { + BrowserModule* browser_module = + static_cast<BrowserModule*>(browser_module_as_void); + SbCoreDumpLogInteger("BrowserModule.on_error_triggered_count_", + browser_module->on_error_triggered_count_); +#if defined(COBALT_CHECK_RENDER_TIMEOUT) + SbCoreDumpLogInteger("BrowserModule.recovery_mechanism_triggered_count_", + browser_module->recovery_mechanism_triggered_count_); + SbCoreDumpLogInteger("BrowserModule.timeout_response_trigger_count_", + browser_module->timeout_response_trigger_count_); +#endif // defined(COBALT_CHECK_RENDER_TIMEOUT) +} +#endif // SB_HAS(CORE_DUMP_HANDLER_SUPPORT) + void BrowserModule::OnLoad() { TRACE_EVENT0("cobalt::browser", "BrowserModule::OnLoad()"); // Repost to our own message loop if necessary. This also prevents @@ -484,6 +500,9 @@ // changed unless the corresponding benchmark logic is changed as well. LOG(INFO) << "Loaded WebModule"; + // Clear |on_error_retry_count_| after a successful load. + on_error_retry_count_ = 0; + on_load_event_time_ = base::TimeTicks::Now().ToInternalValue(); web_module_loaded_.Signal(); } @@ -541,15 +560,16 @@ TRACE_EVENT0("cobalt::browser", "BrowserModule::OnRenderTreeProduced()"); DCHECK_EQ(MessageLoop::current(), self_message_loop_); - if (splash_screen_ && !produced_render_tree_) { + if (splash_screen_ && !navigation_produced_main_render_tree_) { splash_screen_->Shutdown(); } - produced_render_tree_ = true; - if (application_state_ == base::kApplicationStatePreloading || !render_tree_combiner_ || !main_web_module_layer_) { return; } + + navigation_produced_main_render_tree_ = true; + renderer::Submission renderer_submission(layout_results.render_tree, layout_results.layout_time); renderer_submission.on_rasterized_callback = base::Bind( @@ -760,16 +780,44 @@ void BrowserModule::OnError(const GURL& url, const std::string& error) { TRACE_EVENT0("cobalt::browser", "BrowserModule::OnError()"); + if (MessageLoop::current() != self_message_loop_) { + self_message_loop_->PostTask( + FROM_HERE, base::Bind(&BrowserModule::OnError, weak_this_, url, error)); + return; + } + #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT) on_error_triggered_count_++; #endif - LOG(ERROR) << error; - std::string url_string = "h5vcc://network-failure"; - // Retry the current URL. - url_string += "?retry-url=" + url.spec(); + on_error_url_ = url.spec(); - Navigate(GURL(url_string)); + // Start the OnErrorRetry() timer if it isn't already running. + // The minimum delay between calls to OnErrorRetry() exponentially grows as + // |on_error_retry_count_| increases. |on_error_retry_count_| is reset when + // OnLoad() is called. + if (!on_error_retry_timer_.IsRunning()) { + const int64 kBaseRetryDelayInMilliseconds = 1000; + // Cap the max error shift at 10 (1024 * kBaseDelayInMilliseconds) + // This results in the minimum delay being capped at ~17 minutes. + const int kMaxOnErrorRetryCountShift = 10; + int64 min_delay = kBaseRetryDelayInMilliseconds << std::min( + kMaxOnErrorRetryCountShift, on_error_retry_count_); + int64 required_delay = std::max( + min_delay - + (base::TimeTicks::Now() - on_error_retry_time_).InMilliseconds(), + static_cast<int64>(0)); + + on_error_retry_timer_.Start( + FROM_HERE, base::TimeDelta::FromMilliseconds(required_delay), this, + &BrowserModule::OnErrorRetry); + } +} + +void BrowserModule::OnErrorRetry() { + ++on_error_retry_count_; + on_error_retry_time_ = base::TimeTicks::Now(); + TryURLHandlers(GURL("h5vcc://network-failure?retry-url=" + on_error_url_)); } bool BrowserModule::FilterKeyEvent(base::Token type, @@ -1091,9 +1139,6 @@ #if defined(ENABLE_SCREENSHOT) screen_shot_writer_.reset(new ScreenShotWriter(renderer_module_->pipeline())); #endif // defined(ENABLE_SCREENSHOT) - // TODO: Pass in dialog closure instead of system window, and initialize - // earlier. - h5vcc_url_handler_.reset(new H5vccURLHandler(this, system_window_.get())); media_module_ = media::MediaModule::Create(system_window_.get(), GetResourceProvider(), @@ -1211,6 +1256,12 @@ FOR_EACH_OBSERVER(LifecycleObserver, lifecycle_observers_, Resume(resource_provider)); } + + // If no navigate has occurred since the last OnError call, then attempt to + // navigate to |on_error_url_| now. + if (!on_error_url_.empty()) { + Navigate(GURL(on_error_url_)); + } } math::Size BrowserModule::GetViewportSize() {
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h index 386eb0e..16961ec 100644 --- a/src/cobalt/browser/browser_module.h +++ b/src/cobalt/browser/browser_module.h
@@ -23,6 +23,7 @@ #include "base/synchronization/lock.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread.h" +#include "base/timer.h" #include "cobalt/account/account_manager.h" #include "cobalt/base/application_state.h" #include "cobalt/base/message_queue.h" @@ -34,6 +35,7 @@ #include "cobalt/browser/screen_shot_writer.h" #include "cobalt/browser/splash_screen.h" #include "cobalt/browser/suspend_fuzzer.h" +#include "cobalt/browser/system_platform_error_handler.h" #include "cobalt/browser/url_handler.h" #include "cobalt/browser/web_module.h" #include "cobalt/dom/array_buffer.h" @@ -107,6 +109,10 @@ // Reloads web module. void Reload(); + SystemPlatformErrorHandler* system_platform_error_handler() { + return &system_platform_error_handler_; + } + // Adds/removes a URL handler. void AddURLHandler(const URLHandler::URLHandlerCallback& callback); void RemoveURLHandler(const URLHandler::URLHandlerCallback& callback); @@ -159,9 +165,6 @@ #endif // defined(COBALT_CHECK_RENDER_TIMEOUT) #endif // SB_HAS(CORE_DUMP_HANDLER_SUPPORT) - // Recreates web module with the given URL. - void NavigateInternal(const GURL& url); - // Called when the WebModule's Window.onload event is fired. void OnLoad(); @@ -214,6 +217,10 @@ // Error callback for any error that stops the program. void OnError(const GURL& url, const std::string& error); + // OnErrorRetry() runs a retry URL through the URL handlers. It should only be + // called by |on_error_retry_timer_|. + void OnErrorRetry(); + // Filters a key event. // Returns true if the event should be passed on to other handlers, // false if it was consumed within this function. @@ -328,6 +335,9 @@ // The browser module runs on this message loop. MessageLoop* const self_message_loop_; + // Handler for system errors, which is owned by browser module. + SystemPlatformErrorHandler system_platform_error_handler_; + // Collection of URL handlers that can potentially handle a URL before // using it to initialize a new WebModule. URLHandlerCollection url_handlers_; @@ -448,6 +458,20 @@ int render_timeout_count_; #endif + // The URL associated with the last OnError() call. It is cleared on the next + // call to Navigate(). + std::string on_error_url_; + // The number of OnErrorRetry() calls that have occurred since the last + // OnDone() call. This is used to determine the exponential backoff delay + // between the call to OnError() and the timer call to OnErrorRetry(). + int on_error_retry_count_; + // The time OnErrorRetry() was last called. This is used to limit how + // frequently |on_error_retry_timer_| can call OnErrorRetry(). + base::TimeTicks on_error_retry_time_; + // The timer for the next call to OnErrorRetry(). It is started in OnError() + // when it is not already active. + base::OneShotTimer<BrowserModule> on_error_retry_timer_; + // Set when the application is about to quit. May be set from a thread other // than the one hosting this object, and read from another. bool will_quit_; @@ -474,8 +498,9 @@ // The splash screen cache. scoped_ptr<SplashScreenCache> splash_screen_cache_; - // Whether or not the main WebModule has produced any render trees yet. - bool produced_render_tree_; + // Whether or not the main WebModule has produced any render trees yet for the + // current navigation. + bool navigation_produced_main_render_tree_; }; } // namespace browser
diff --git a/src/cobalt/browser/h5vcc_url_handler.cc b/src/cobalt/browser/h5vcc_url_handler.cc index 94a8ed9..d02a4a3 100644 --- a/src/cobalt/browser/h5vcc_url_handler.cc +++ b/src/cobalt/browser/h5vcc_url_handler.cc
@@ -18,6 +18,7 @@ #include "base/bind.h" #include "cobalt/browser/browser_module.h" +#include "cobalt/browser/system_platform_error_handler.h" namespace cobalt { namespace browser { @@ -75,12 +76,10 @@ const char kRetryParam[] = "retry-url"; } // namespace -H5vccURLHandler::H5vccURLHandler(BrowserModule* browser_module, - system_window::SystemWindow* system_window) +H5vccURLHandler::H5vccURLHandler(BrowserModule* browser_module) : ALLOW_THIS_IN_INITIALIZER_LIST(URLHandler( browser_module, - base::Bind(&H5vccURLHandler::HandleURL, base::Unretained(this)))), - system_window_(system_window) {} + base::Bind(&H5vccURLHandler::HandleURL, base::Unretained(this)))) {} bool H5vccURLHandler::HandleURL(const GURL& url) { bool was_handled = false; @@ -97,20 +96,21 @@ } bool H5vccURLHandler::HandleNetworkFailure() { - system_window::SystemWindow::DialogOptions dialog_options; - dialog_options.message_code = - system_window::SystemWindow::kDialogConnectionError; - dialog_options.callback = base::Bind( - &H5vccURLHandler::OnNetworkFailureDialogResponse, base::Unretained(this)); - system_window_->ShowDialog(dialog_options); + SystemPlatformErrorHandler::SystemPlatformErrorOptions options; + options.error_type = kSbSystemPlatformErrorTypeConnectionError; + options.callback = + base::Bind(&H5vccURLHandler::OnNetworkFailureSystemPlatformResponse, + base::Unretained(this)); + browser_module()->system_platform_error_handler()->RaiseSystemPlatformError( + options); return true; } -void H5vccURLHandler::OnNetworkFailureDialogResponse( - system_window::SystemWindow::DialogResponse response) { +void H5vccURLHandler::OnNetworkFailureSystemPlatformResponse( + SbSystemPlatformErrorResponse response) { const std::string retry_url = GetH5vccUrlQueryParam(url_, kRetryParam); // A positive response means we should retry. - if (response == system_window::SystemWindow::kDialogPositiveResponse && + if (response == kSbSystemPlatformErrorResponsePositive && retry_url.length() > 0) { GURL url(retry_url); if (url.is_valid()) {
diff --git a/src/cobalt/browser/h5vcc_url_handler.h b/src/cobalt/browser/h5vcc_url_handler.h index 203e4a2..6fa46a7 100644 --- a/src/cobalt/browser/h5vcc_url_handler.h +++ b/src/cobalt/browser/h5vcc_url_handler.h
@@ -17,7 +17,6 @@ #include "cobalt/account/account_manager.h" #include "cobalt/browser/url_handler.h" -#include "cobalt/system_window/system_window.h" namespace cobalt { namespace browser { @@ -27,19 +26,16 @@ // handled separately, e.g. by showing a system dialog. class H5vccURLHandler : public URLHandler { public: - explicit H5vccURLHandler(BrowserModule* browser_module, - system_window::SystemWindow* system_window); + explicit H5vccURLHandler(BrowserModule* browser_module); ~H5vccURLHandler() {} private: bool HandleURL(const GURL& url); bool HandleNetworkFailure(); - // Dialog response handlers. - void OnNetworkFailureDialogResponse( - system_window::SystemWindow::DialogResponse response); + void OnNetworkFailureSystemPlatformResponse( + SbSystemPlatformErrorResponse response); - system_window::SystemWindow* system_window_; GURL url_; };
diff --git a/src/cobalt/browser/memory_settings/auto_mem.cc b/src/cobalt/browser/memory_settings/auto_mem.cc index 2d5df3a..855e8ad 100644 --- a/src/cobalt/browser/memory_settings/auto_mem.cc +++ b/src/cobalt/browser/memory_settings/auto_mem.cc
@@ -121,7 +121,8 @@ return setting.Pass(); } - setting->set_value(MemorySetting::kStarboardAPI, -1); + // This will mark the value as invalid. + setting->set_value(MemorySetting::kUnset, -1); return setting.Pass(); }
diff --git a/src/cobalt/browser/memory_settings/auto_mem_test.cc b/src/cobalt/browser/memory_settings/auto_mem_test.cc index 9982a6f..3d6c0dc 100644 --- a/src/cobalt/browser/memory_settings/auto_mem_test.cc +++ b/src/cobalt/browser/memory_settings/auto_mem_test.cc
@@ -432,6 +432,18 @@ EXPECT_LE(memory_consumption, kSmallEngineGpuMemorySize); } +// Tests that if the gpu memory could not be queried then the resulting +// max_gpu_bytes will not be valid. +TEST(AutoMem, NoDefaultGpuMemory) { + AutoMemSettings command_line_settings(AutoMemSettings::kTypeCommandLine); + AutoMemSettings build_settings(AutoMemSettings::kTypeBuild); + + AutoMem auto_mem(kResolution1080p, command_line_settings, + build_settings); + + EXPECT_FALSE(auto_mem.max_gpu_bytes()->valid()); +} + } // namespace memory_settings } // namespace browser } // namespace cobalt
diff --git a/src/cobalt/browser/render_tree_combiner.cc b/src/cobalt/browser/render_tree_combiner.cc index 78fe4be..74727ec 100644 --- a/src/cobalt/browser/render_tree_combiner.cc +++ b/src/cobalt/browser/render_tree_combiner.cc
@@ -35,6 +35,7 @@ RenderTreeCombiner::Layer::~Layer() { DCHECK(render_tree_combiner_); render_tree_combiner_->RemoveLayer(this); + render_tree_combiner_->SubmitToRenderer(); } void RenderTreeCombiner::Layer::Submit(
diff --git a/src/cobalt/browser/system_platform_error_handler.cc b/src/cobalt/browser/system_platform_error_handler.cc new file mode 100644 index 0000000..7c1ef7d --- /dev/null +++ b/src/cobalt/browser/system_platform_error_handler.cc
@@ -0,0 +1,54 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cobalt/browser/system_platform_error_handler.h" + +#include "base/logging.h" + +namespace cobalt { +namespace browser { +namespace { + +void OnSystemPlatformErrorResponse(SbSystemPlatformErrorResponse response, + void* user_data) { + DCHECK(user_data); + SystemPlatformErrorHandler* error_handler = + static_cast<SystemPlatformErrorHandler*>(user_data); + error_handler->HandleSystemPlatformErrorResponse(response); +} + +} // namespace + +void SystemPlatformErrorHandler::RaiseSystemPlatformError( + const SystemPlatformErrorOptions& options) { + current_system_platform_error_callback_ = options.callback; + + SbSystemPlatformError handle = SbSystemRaisePlatformError( + options.error_type, OnSystemPlatformErrorResponse, this); + if (!SbSystemPlatformErrorIsValid(handle) && + !current_system_platform_error_callback_.is_null()) { + DLOG(WARNING) << "Did not handle error: " << options.error_type; + current_system_platform_error_callback_.Reset(); + } +} + +void SystemPlatformErrorHandler::HandleSystemPlatformErrorResponse( + SbSystemPlatformErrorResponse response) { + DCHECK(!current_system_platform_error_callback_.is_null()); + current_system_platform_error_callback_.Run(response); + current_system_platform_error_callback_.Reset(); +} + +} // namespace browser +} // namespace cobalt
diff --git a/src/cobalt/browser/system_platform_error_handler.h b/src/cobalt/browser/system_platform_error_handler.h new file mode 100644 index 0000000..64b01a2 --- /dev/null +++ b/src/cobalt/browser/system_platform_error_handler.h
@@ -0,0 +1,55 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COBALT_BROWSER_SYSTEM_PLATFORM_ERROR_HANDLER_H_ +#define COBALT_BROWSER_SYSTEM_PLATFORM_ERROR_HANDLER_H_ + +#include "base/callback.h" +#include "starboard/system.h" + +namespace cobalt { +namespace browser { + +// This class handles raising system errors to the platform and returning the +// response to the caller via the provided callback. +class SystemPlatformErrorHandler { + public: + // Type of callback to run when the platform responds to a system error. + typedef base::Callback<void(SbSystemPlatformErrorResponse response)> + SystemPlatformErrorCallback; + + // Options structure for raised system platform errors, including both the + // system error being raised and the callback to run with the response. + struct SystemPlatformErrorOptions { + SbSystemPlatformErrorType error_type; + SystemPlatformErrorCallback callback; + }; + + // Raises a system error with the specified error type and callback. + void RaiseSystemPlatformError(const SystemPlatformErrorOptions& options); + + // Called when the platform responds to the system error. + void HandleSystemPlatformErrorResponse( + SbSystemPlatformErrorResponse response); + + private: + // The current platform error callback. Only one platform error callback may + // be active at a time. + SystemPlatformErrorCallback current_system_platform_error_callback_; +}; + +} // namespace browser +} // namespace cobalt + +#endif // COBALT_BROWSER_SYSTEM_PLATFORM_ERROR_HANDLER_H_
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id index 248bc70..96e2ce3 100644 --- a/src/cobalt/build/build.id +++ b/src/cobalt/build/build.id
@@ -1 +1 @@ -93830 \ No newline at end of file +95615 \ No newline at end of file
diff --git a/src/cobalt/build/config/base.gni b/src/cobalt/build/config/base.gni index 21e6bef..2bc87b6 100644 --- a/src/cobalt/build/config/base.gni +++ b/src/cobalt/build/config/base.gni
@@ -152,7 +152,7 @@ # 'stub' -- Stub graphics rasterization. A rasterizer object will # still be available and valid, but it will do nothing. if (!defined(rasterizer_type)) { - rasterizer_type = "hardware" + rasterizer_type = "direct-gles" } # If set to true, will enable support for rendering only the regions of the
diff --git a/src/cobalt/build/config/base.gypi b/src/cobalt/build/config/base.gypi index 02c5ec2..110e70d 100644 --- a/src/cobalt/build/config/base.gypi +++ b/src/cobalt/build/config/base.gypi
@@ -174,7 +174,7 @@ # output window. # 'stub' -- Stub graphics rasterization. A rasterizer object will # still be available and valid, but it will do nothing. - 'rasterizer_type%': 'hardware', + 'rasterizer_type%': 'direct-gles', # If set to 1, will enable support for rendering only the regions of the # display that are modified due to animations, instead of re-rendering the
diff --git a/src/cobalt/dom/document.cc b/src/cobalt/dom/document.cc index ec42f43..85603a2 100644 --- a/src/cobalt/dom/document.cc +++ b/src/cobalt/dom/document.cc
@@ -81,6 +81,8 @@ #if defined(ENABLE_PARTIAL_LAYOUT_CONTROL) partial_layout_is_enabled_(true), #endif // defined(ENABLE_PARTIAL_LAYOUT_CONTROL) + selector_tree_(new cssom::SelectorTree()), + should_recreate_selector_tree_(false), navigation_start_clock_(options.navigation_start_clock ? options.navigation_start_clock : new base::SystemMonotonicClock()), @@ -550,28 +552,30 @@ namespace { void RemoveRulesFromCSSRuleListFromSelectorTree( - cssom::SelectorTree* selector_tree, - const scoped_refptr<cssom::CSSRuleList>& css_rule_list) { + const scoped_refptr<cssom::CSSRuleList>& css_rule_list, + cssom::SelectorTree* maybe_selector_tree) { for (unsigned int i = 0; i < css_rule_list->length(); ++i) { cssom::CSSRule* rule = css_rule_list->Item(i); cssom::CSSStyleRule* css_style_rule = rule->AsCSSStyleRule(); if (css_style_rule && css_style_rule->added_to_selector_tree()) { - selector_tree->RemoveRule(css_style_rule); + if (maybe_selector_tree) { + maybe_selector_tree->RemoveRule(css_style_rule); + } css_style_rule->set_added_to_selector_tree(false); } cssom::CSSMediaRule* css_media_rule = rule->AsCSSMediaRule(); if (css_media_rule) { - RemoveRulesFromCSSRuleListFromSelectorTree(selector_tree, - css_media_rule->css_rules()); + RemoveRulesFromCSSRuleListFromSelectorTree(css_media_rule->css_rules(), + maybe_selector_tree); } } } void AppendRulesFromCSSRuleListToSelectorTree( - cssom::SelectorTree* selector_tree, - const scoped_refptr<cssom::CSSRuleList>& css_rule_list) { + const scoped_refptr<cssom::CSSRuleList>& css_rule_list, + cssom::SelectorTree* selector_tree) { for (unsigned int i = 0; i < css_rule_list->length(); ++i) { cssom::CSSRule* rule = css_rule_list->Item(i); @@ -584,21 +588,26 @@ cssom::CSSMediaRule* css_media_rule = rule->AsCSSMediaRule(); if (css_media_rule) { if (css_media_rule->condition_value()) { - AppendRulesFromCSSRuleListToSelectorTree(selector_tree, - css_media_rule->css_rules()); + AppendRulesFromCSSRuleListToSelectorTree(css_media_rule->css_rules(), + selector_tree); } else { - RemoveRulesFromCSSRuleListFromSelectorTree(selector_tree, - css_media_rule->css_rules()); + RemoveRulesFromCSSRuleListFromSelectorTree(css_media_rule->css_rules(), + selector_tree); } } } } -void UpdateSelectorTreeFromCSSStyleSheet( - cssom::SelectorTree* selector_tree, +void AppendRulesFromCSSStyleSheetToSelectorTree( + const scoped_refptr<cssom::CSSStyleSheet>& style_sheet, + cssom::SelectorTree* selector_tree) { + AppendRulesFromCSSRuleListToSelectorTree(style_sheet->css_rules(), + selector_tree); +} + +void ClearAddedToSelectorTreeFromCSSStyleSheetRules( const scoped_refptr<cssom::CSSStyleSheet>& style_sheet) { - AppendRulesFromCSSRuleListToSelectorTree(selector_tree, - style_sheet->css_rules()); + RemoveRulesFromCSSRuleListFromSelectorTree(style_sheet->css_rules(), NULL); } } // namespace @@ -789,16 +798,37 @@ UpdateStyleSheets(); UpdateMediaRules(); + // If the selector tree is being recreated, then clear the added state from + // the document's style sheets. This will cause them to be added to the new + // selector tree. + if (should_recreate_selector_tree_) { + DLOG(WARNING) << "A style sheet was removed from the document or the " + "document's style sheets have been reordered. This " + "triggers a recreation of the selector tree and should " + "be avoided if possible."; + if (user_agent_style_sheet_) { + ClearAddedToSelectorTreeFromCSSStyleSheetRules(user_agent_style_sheet_); + } + for (unsigned int style_sheet_index = 0; + style_sheet_index < style_sheets_->length(); ++style_sheet_index) { + scoped_refptr<cssom::CSSStyleSheet> css_style_sheet = + style_sheets_->Item(style_sheet_index)->AsCSSStyleSheet(); + ClearAddedToSelectorTreeFromCSSStyleSheetRules(css_style_sheet); + } + selector_tree_.reset(new cssom::SelectorTree()); + should_recreate_selector_tree_ = false; + } + if (user_agent_style_sheet_) { - UpdateSelectorTreeFromCSSStyleSheet(&selector_tree_, - user_agent_style_sheet_); + AppendRulesFromCSSStyleSheetToSelectorTree(user_agent_style_sheet_, + selector_tree_.get()); } for (unsigned int style_sheet_index = 0; style_sheet_index < style_sheets_->length(); ++style_sheet_index) { scoped_refptr<cssom::CSSStyleSheet> css_style_sheet = style_sheets_->Item(style_sheet_index)->AsCSSStyleSheet(); - - UpdateSelectorTreeFromCSSStyleSheet(&selector_tree_, css_style_sheet); + AppendRulesFromCSSStyleSheetToSelectorTree(css_style_sheet, + selector_tree_.get()); } scoped_refptr<HTMLHtmlElement> current_html = html(); if (current_html) { @@ -914,6 +944,22 @@ child = child->next_element_sibling()) { child->CollectStyleSheetsOfElementAndDescendants(&style_sheet_vector); } + + // Check for the removal or reordering of any of the pre-existing style + // sheets. In either of these cases, the selector tree must be recreated. + if (style_sheets_->length() > style_sheet_vector.size()) { + should_recreate_selector_tree_ = true; + } + for (unsigned int style_sheet_index = 0; + !should_recreate_selector_tree_ && + style_sheet_index < style_sheets_->length(); + ++style_sheet_index) { + if (style_sheets_->Item(style_sheet_index)->AsCSSStyleSheet().get() != + style_sheet_vector[style_sheet_index]->AsCSSStyleSheet().get()) { + should_recreate_selector_tree_ = true; + } + } + style_sheets_ = new cssom::StyleSheetList(style_sheet_vector, this); are_style_sheets_dirty_ = false; }
diff --git a/src/cobalt/dom/document.h b/src/cobalt/dom/document.h index d0f8853..5291806 100644 --- a/src/cobalt/dom/document.h +++ b/src/cobalt/dom/document.h
@@ -251,7 +251,7 @@ return &scripts_to_be_executed_; } - cssom::SelectorTree* selector_tree() { return &selector_tree_; } + cssom::SelectorTree* selector_tree() { return selector_tree_.get(); } // Returns a mapping from keyframes name to CSSKeyframesRule. This can be // used to quickly lookup the @keyframes rule given a string identifier. @@ -465,7 +465,12 @@ // List of document observers. ObserverList<DocumentObserver> observers_; // Selector Tree. - cssom::SelectorTree selector_tree_; + scoped_ptr<cssom::SelectorTree> selector_tree_; + // This is set when the document has a style sheet removed or the order of its + // style sheets changed. In this case, it is more straightforward to simply + // recreate the selector tree than to attempt to manage updating all of its + // internal state. + bool should_recreate_selector_tree_; // The document's latest sample from the global clock, used for updating // animations. const scoped_refptr<base::Clock> navigation_start_clock_;
diff --git a/src/cobalt/dom/html_element.cc b/src/cobalt/dom/html_element.cc index a539d68..6aff330 100644 --- a/src/cobalt/dom/html_element.cc +++ b/src/cobalt/dom/html_element.cc
@@ -588,26 +588,28 @@ } void HTMLElement::InvalidateMatchingRulesRecursively() { - if (!matching_rules_valid_) { - return; - } + InvalidateMatchingRulesRecursivelyInternal(true /*is_initial_invalidation*/); +} - matching_rules_valid_ = false; +void HTMLElement::InvalidateMatchingRulesRecursivelyInternal( + bool is_initial_invalidation) { + if (matching_rules_valid_) { + // Move |matching_rules_| into |old_matching_rules_|. This is used for + // determining whether or not the matching rules actually changed when they + // are updated. + old_matching_rules_.swap(matching_rules_); - // Move |matching_rules_| into |old_matching_rules_|. This is used for - // determining whether or not the matching rules actually changed when they - // are updated. - old_matching_rules_.swap(matching_rules_); - - matching_rules_.clear(); - rule_matching_state_.matching_nodes.clear(); - rule_matching_state_.descendant_potential_nodes.clear(); - rule_matching_state_.following_sibling_potential_nodes.clear(); - for (int pseudo_element_type = 0; pseudo_element_type < kMaxPseudoElementType; - ++pseudo_element_type) { - if (pseudo_elements_[pseudo_element_type]) { - pseudo_elements_[pseudo_element_type]->ClearMatchingRules(); + matching_rules_.clear(); + rule_matching_state_.matching_nodes.clear(); + rule_matching_state_.descendant_potential_nodes.clear(); + rule_matching_state_.following_sibling_potential_nodes.clear(); + for (int pseudo_element_type = 0; + pseudo_element_type < kMaxPseudoElementType; ++pseudo_element_type) { + if (pseudo_elements_[pseudo_element_type]) { + pseudo_elements_[pseudo_element_type]->ClearMatchingRules(); + } } + matching_rules_valid_ = false; } // Invalidate matching rules on all children. @@ -615,18 +617,22 @@ element = element->next_element_sibling()) { HTMLElement* html_element = element->AsHTMLElement(); if (html_element) { - html_element->InvalidateMatchingRulesRecursively(); + html_element->InvalidateMatchingRulesRecursivelyInternal( + false /*is_initial_invalidation*/); } } - // Invalidate matching rules on all following siblings if sibling combinators - // are used. - if (node_document()->selector_tree()->has_sibling_combinators()) { + // Invalidate matching rules on all following siblings if this is the initial + // invalidation and sibling combinators are used; if this is not the initial + // invalidation, then these will already be handled by a previous call. + if (is_initial_invalidation && + node_document()->selector_tree()->has_sibling_combinators()) { for (Element* element = next_element_sibling(); element; element = element->next_element_sibling()) { HTMLElement* html_element = element->AsHTMLElement(); if (html_element) { - html_element->InvalidateMatchingRulesRecursively(); + html_element->InvalidateMatchingRulesRecursivelyInternal( + false /*is_initial_invalidation*/); } } } @@ -1216,7 +1222,6 @@ if (!matching_rules_valid_) { dom_stat_tracker_->OnUpdateMatchingRules(); UpdateMatchingRules(this); - matching_rules_valid_ = true; // Check for whether the matching rules have changed. If they have, then a // new computed style must be generated from them.
diff --git a/src/cobalt/dom/html_element.h b/src/cobalt/dom/html_element.h index 5806cfc..21fb9ee 100644 --- a/src/cobalt/dom/html_element.h +++ b/src/cobalt/dom/html_element.h
@@ -190,8 +190,8 @@ } // Returns the rule matching state of this element. RuleMatchingState* rule_matching_state() { return &rule_matching_state_; } - // Invalidates the matching rules and rule matching state in this element and - // its descendants. + // Invalidates the matching rules and rule matching state in this element, + // its descendants and its siblings. void InvalidateMatchingRulesRecursively(); // Computed style related methods. @@ -263,6 +263,7 @@ return descendant_computed_styles_valid_; } bool matching_rules_valid() const { return matching_rules_valid_; } + void set_matching_rules_valid() { matching_rules_valid_ = true; } // Returns whether the element has been designated. // https://www.w3.org/TR/selectors4/#hover-pseudo @@ -317,6 +318,11 @@ // directionality does not invalidate the computed style. void SetDirectionality(const std::string& value); + // Invalidates the matching rules and rule matching state in this element and + // its descendants. In the case where this is the the initial invalidation, + // it will also invalidate the rule matching state of its siblings. + void InvalidateMatchingRulesRecursivelyInternal(bool is_initial_invalidation); + // Clear the list of active background images, and notify the animated image // tracker to stop the animations. void ClearActiveBackgroundImages(); @@ -374,12 +380,11 @@ cssom::RulesWithCascadePrecedence old_matching_rules_; cssom::RulesWithCascadePrecedence matching_rules_; RuleMatchingState rule_matching_state_; + bool matching_rules_valid_; // This contains information about the boxes generated from the element. scoped_ptr<LayoutBoxes> layout_boxes_; - bool matching_rules_valid_; - scoped_ptr<PseudoElement> pseudo_elements_[kMaxPseudoElementType]; base::WeakPtr<DOMStringMap> dataset_;
diff --git a/src/cobalt/dom/html_media_element.cc b/src/cobalt/dom/html_media_element.cc index 51200ba..779f7d3 100644 --- a/src/cobalt/dom/html_media_element.cc +++ b/src/cobalt/dom/html_media_element.cc
@@ -1528,7 +1528,7 @@ EndProcessingMediaPlayerCallback(); } -void HTMLMediaElement::TimeChanged(bool eos_played) { +void HTMLMediaElement::TimeChanged() { DCHECK(player_); if (!player_) { return; @@ -1554,9 +1554,8 @@ // When the current playback position reaches the end of the media resource // when the direction of playback is forwards, then the user agent must follow // these steps: - eos_played |= - !SbDoubleIsNan(dur) && (0.0f != dur) && now >= dur && playback_rate_ > 0; - if (eos_played) { + if (!SbDoubleIsNan(dur) && (0.0f != dur) && now >= dur && + playback_rate_ > 0) { // If the media element has a loop attribute specified and does not have a // current media controller, if (loop()) {
diff --git a/src/cobalt/dom/html_media_element.h b/src/cobalt/dom/html_media_element.h index 31314e3..b3d1f0d 100644 --- a/src/cobalt/dom/html_media_element.h +++ b/src/cobalt/dom/html_media_element.h
@@ -231,7 +231,7 @@ // WebMediaPlayerClient methods void NetworkStateChanged() OVERRIDE; void ReadyStateChanged() OVERRIDE; - void TimeChanged(bool eos_played) OVERRIDE; + void TimeChanged() OVERRIDE; void DurationChanged() OVERRIDE; void OutputModeChanged() OVERRIDE; void PlaybackStateChanged() OVERRIDE;
diff --git a/src/cobalt/dom/rule_matching.cc b/src/cobalt/dom/rule_matching.cc index 1437827..938d78f 100644 --- a/src/cobalt/dom/rule_matching.cc +++ b/src/cobalt/dom/rule_matching.cc
@@ -683,6 +683,8 @@ } } } + + current_element->set_matching_rules_valid(); } scoped_refptr<Element> QuerySelector(Node* node, const std::string& selectors,
diff --git a/src/cobalt/dom/rule_matching_test.cc b/src/cobalt/dom/rule_matching_test.cc index 73f9fdb..6a562fc 100644 --- a/src/cobalt/dom/rule_matching_test.cc +++ b/src/cobalt/dom/rule_matching_test.cc
@@ -244,11 +244,14 @@ head_->set_inner_html("<style>:focus {}</style>"); body_->set_inner_html("<div tabIndex=\"-1\"/>"); body_->first_element_child()->AsHTMLElement()->Focus(); + // This is required because RunFocusingSteps() earlies out as a result of the + // document not having a browsing context. + root_->InvalidateMatchingRulesRecursively(); UpdateAllMatchingRules(); cssom::RulesWithCascadePrecedence* matching_rules = body_->first_element_child()->AsHTMLElement()->matching_rules(); - ASSERT_EQ(2, matching_rules->size()); + ASSERT_EQ(1, matching_rules->size()); EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0), (*matching_rules)[0].first); } @@ -705,5 +708,103 @@ EXPECT_EQ(0, node_list->length()); } +TEST_F(RuleMatchingTest, StyleElementRemoval) { + head_->set_inner_html("<style>* {}</style>"); + body_->set_inner_html("<div/>"); + UpdateAllMatchingRules(); + head_->set_inner_html("<style/>"); + UpdateAllMatchingRules(); + + cssom::RulesWithCascadePrecedence* matching_rules = + body_->first_element_child()->AsHTMLElement()->matching_rules(); + ASSERT_EQ(0, matching_rules->size()); +} + +TEST_F(RuleMatchingTest, StyleElementAddition) { + head_->set_inner_html("<style/>"); + body_->set_inner_html("<div/>"); + UpdateAllMatchingRules(); + head_->set_inner_html("<style>* {}</style>"); + UpdateAllMatchingRules(); + + cssom::RulesWithCascadePrecedence* matching_rules = + body_->first_element_child()->AsHTMLElement()->matching_rules(); + ASSERT_EQ(1, matching_rules->size()); +} + +TEST_F(RuleMatchingTest, StyleElementReorderingOneMatching) { + scoped_refptr<HTMLElement> div1 = + document_->CreateElement("div")->AsHTMLElement(); + div1->set_inner_html("<style/>"); + + scoped_refptr<HTMLElement> div2 = + document_->CreateElement("div")->AsHTMLElement(); + div2->set_inner_html("<style>* {}</style>"); + + body_->set_inner_html("<div/>"); + head_->AppendChild(div1); + head_->AppendChild(div2); + + UpdateAllMatchingRules(); + + cssom::RulesWithCascadePrecedence* matching_rules = + head_->first_element_child()->AsHTMLElement()->matching_rules(); + ASSERT_EQ(1, matching_rules->size()); + EXPECT_NE(GetDocumentStyleSheet(0)->css_rules()->Item(0), + (*matching_rules)[0].first); + EXPECT_EQ(GetDocumentStyleSheet(1)->css_rules()->Item(0), + (*matching_rules)[0].first); + + head_->RemoveChild(div2); + head_->InsertBefore(div2, div1); + + UpdateAllMatchingRules(); + + matching_rules = + head_->first_element_child()->AsHTMLElement()->matching_rules(); + ASSERT_EQ(1, matching_rules->size()); + EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0), + (*matching_rules)[0].first); + EXPECT_NE(GetDocumentStyleSheet(1)->css_rules()->Item(0), + (*matching_rules)[0].first); +} + +TEST_F(RuleMatchingTest, StyleElementReorderingTwoMatching) { + scoped_refptr<HTMLElement> div1 = + document_->CreateElement("div")->AsHTMLElement(); + div1->set_inner_html("<style>* {}</style>"); + + scoped_refptr<HTMLElement> div2 = + document_->CreateElement("div")->AsHTMLElement(); + div2->set_inner_html("<style>* {}</style>"); + + body_->set_inner_html("<div/>"); + head_->AppendChild(div1); + head_->AppendChild(div2); + + UpdateAllMatchingRules(); + + cssom::RulesWithCascadePrecedence* matching_rules = + head_->first_element_child()->AsHTMLElement()->matching_rules(); + ASSERT_EQ(2, matching_rules->size()); + EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0), + (*matching_rules)[0].first); + EXPECT_NE(GetDocumentStyleSheet(1)->css_rules()->Item(0), + (*matching_rules)[0].first); + + head_->RemoveChild(div2); + head_->InsertBefore(div2, div1); + + UpdateAllMatchingRules(); + + matching_rules = + head_->first_element_child()->AsHTMLElement()->matching_rules(); + ASSERT_EQ(2, matching_rules->size()); + EXPECT_EQ(GetDocumentStyleSheet(0)->css_rules()->Item(0), + (*matching_rules)[0].first); + EXPECT_NE(GetDocumentStyleSheet(1)->css_rules()->Item(0), + (*matching_rules)[0].first); +} + } // namespace dom } // namespace cobalt
diff --git a/src/cobalt/dom/window.cc b/src/cobalt/dom/window.cc index fab5c8c..67cd077 100644 --- a/src/cobalt/dom/window.cc +++ b/src/cobalt/dom/window.cc
@@ -16,6 +16,7 @@ #include <algorithm> +#include "base/base64.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "cobalt/base/polymorphic_downcast.h" @@ -288,6 +289,18 @@ scoped_refptr<Crypto> Window::crypto() const { return crypto_; } +std::string Window::Btoa(const std::string& string_to_encode) { + std::string output; + base::Base64Encode(string_to_encode, &output); + return output; +} + +std::string Window::Atob(const std::string& encoded_string) { + std::string output; + base::Base64Decode(encoded_string, &output); + return output; +} + int Window::SetTimeout(const WindowTimers::TimerCallbackArg& handler, int timeout) { DLOG_IF(WARNING, timeout < 0)
diff --git a/src/cobalt/dom/window.h b/src/cobalt/dom/window.h index 9cd4cb6..0a60269 100644 --- a/src/cobalt/dom/window.h +++ b/src/cobalt/dom/window.h
@@ -228,6 +228,11 @@ // https://www.w3.org/TR/WebCryptoAPI/#crypto-interface scoped_refptr<Crypto> crypto() const; + // base64 encoding and decoding + std::string Btoa(const std::string& string_to_encode); + + std::string Atob(const std::string& encoded_string); + // Web API: WindowTimers (implements) // https://www.w3.org/TR/html5/webappapis.html#timers //
diff --git a/src/cobalt/dom/window.idl b/src/cobalt/dom/window.idl index d04fb7a..8749653 100644 --- a/src/cobalt/dom/window.idl +++ b/src/cobalt/dom/window.idl
@@ -36,6 +36,10 @@ // the user agent readonly attribute Navigator navigator; + // base64 encoding and decoding + DOMString btoa(DOMString string_to_encode); + DOMString atob(DOMString encoded_string); + // Custom, not in any spec. // readonly attribute Console console;
diff --git a/src/cobalt/layout/box.cc b/src/cobalt/layout/box.cc index 34a3853..ff4edae 100644 --- a/src/cobalt/layout/box.cc +++ b/src/cobalt/layout/box.cc
@@ -1638,20 +1638,21 @@ } } -void Box::ApplyTransformActionToCoordinate(TransformAction action, +bool Box::ApplyTransformActionToCoordinate(TransformAction action, math::Vector2dF* coordinate) const { std::vector<math::Vector2dF> coordinate_vector; coordinate_vector.push_back(*coordinate); - ApplyTransformActionToCoordinates(action, &coordinate_vector); + bool result = ApplyTransformActionToCoordinates(action, &coordinate_vector); *coordinate = coordinate_vector[0]; + return result; } -void Box::ApplyTransformActionToCoordinates( +bool Box::ApplyTransformActionToCoordinates( TransformAction action, std::vector<math::Vector2dF>* coordinates) const { const scoped_refptr<cssom::PropertyValue>& transform = computed_style()->transform(); if (transform == cssom::KeywordValue::GetNone()) { - return; + return true; } // The border box offset is calculated in two steps because we want to @@ -1673,6 +1674,11 @@ if (!matrix.IsIdentity()) { if (action == kEnterTransform) { matrix = matrix.Inverse(); + // The matrix is not invertible. Return that applying the transform + // failed. + if (matrix.IsZeros()) { + return false; + } } for (std::vector<math::Vector2dF>::iterator coordinate_iter = @@ -1702,6 +1708,7 @@ coordinate += containing_block_offset_from_root_as_float; } } + return true; } } // namespace layout
diff --git a/src/cobalt/layout/box.h b/src/cobalt/layout/box.h index 51c1758..e90a9f1 100644 --- a/src/cobalt/layout/box.h +++ b/src/cobalt/layout/box.h
@@ -572,9 +572,11 @@ RenderSequence other_render_sequence); // Applies the specified transform action to the provided coordinates. - void ApplyTransformActionToCoordinate(TransformAction action, + // Returns false if the transform is not invertible and the action requires + // it being inverted. + bool ApplyTransformActionToCoordinate(TransformAction action, math::Vector2dF* coordinate) const; - void ApplyTransformActionToCoordinates( + bool ApplyTransformActionToCoordinates( TransformAction action, std::vector<math::Vector2dF>* coordinates) const; protected:
diff --git a/src/cobalt/layout/box_generator.cc b/src/cobalt/layout/box_generator.cc index dc00344..18bf26b 100644 --- a/src/cobalt/layout/box_generator.cc +++ b/src/cobalt/layout/box_generator.cc
@@ -17,6 +17,7 @@ #include <string> #include "base/bind.h" +#include "base/debug/trace_event.h" #include "base/memory/scoped_ptr.h" #include "cobalt/cssom/computed_style.h" #include "cobalt/cssom/css_computed_style_declaration.h" @@ -64,6 +65,7 @@ scoped_refptr<render_tree::Image> GetVideoFrame( const scoped_refptr<ShellVideoFrameProvider>& frame_provider, render_tree::ResourceProvider* resource_provider) { + TRACE_EVENT0("cobalt::layout", "GetVideoFrame()"); SbDecodeTarget decode_target = frame_provider->GetCurrentSbDecodeTarget(); if (SbDecodeTargetIsValid(decode_target)) { #if SB_HAS(GRAPHICS)
diff --git a/src/cobalt/layout/layout_manager.cc b/src/cobalt/layout/layout_manager.cc index 7f0cc65..8f38f9e 100644 --- a/src/cobalt/layout/layout_manager.cc +++ b/src/cobalt/layout/layout_manager.cc
@@ -116,9 +116,7 @@ void UpdateCamera( float width_to_height_aspect_ratio, scoped_refptr<input::Camera3D> camera, float max_horizontal_fov_rad, float max_vertical_fov_rad, - render_tree::MatrixTransform3DNode::Builder* transform_node_builder, - base::TimeDelta time) { - UNREFERENCED_PARAMETER(time); + render_tree::MatrixTransform3DNode::Builder* transform_node_builder) { float vertical_fov_rad = std::min(max_vertical_fov_rad, 2 * static_cast<float>(atan(tan(max_horizontal_fov_rad * 0.5f) /
diff --git a/src/cobalt/layout/replaced_box.cc b/src/cobalt/layout/replaced_box.cc index c61322a..6c3e322 100644 --- a/src/cobalt/layout/replaced_box.cc +++ b/src/cobalt/layout/replaced_box.cc
@@ -231,9 +231,7 @@ } void AnimateVideoImage(const ReplacedBox::ReplaceImageCB& replace_image_cb, - ImageNode::Builder* image_node_builder, - base::TimeDelta time) { - UNREFERENCED_PARAMETER(time); + ImageNode::Builder* image_node_builder) { DCHECK(!replace_image_cb.is_null()); DCHECK(image_node_builder); @@ -250,14 +248,27 @@ void AnimateVideoWithLetterboxing( const ReplacedBox::ReplaceImageCB& replace_image_cb, math::SizeF destination_size, - CompositionNode::Builder* composition_node_builder, base::TimeDelta time) { - UNREFERENCED_PARAMETER(time); - + CompositionNode::Builder* composition_node_builder) { DCHECK(!replace_image_cb.is_null()); DCHECK(composition_node_builder); scoped_refptr<render_tree::Image> image = replace_image_cb.Run(); + // If the image hasn't changed, then no need to change anything else. The + // image should be the first child (see AddLetterboxedImageToRenderTree()). + if (!composition_node_builder->children().empty()) { + render_tree::ImageNode* existing_image_node = + base::polymorphic_downcast<render_tree::ImageNode*>( + composition_node_builder->GetChild(0)->get()); + if (existing_image_node->data().source.get() == image.get()) { + return; + } + } + + // Reset the composition node from whatever it was before, we will recreate + // it anew in each animation frame. + *composition_node_builder = CompositionNode::Builder(); + // TODO: Detect better when the intrinsic video size is used for the // node size, and trigger a re-layout from the media element when the size // changes.
diff --git a/src/cobalt/layout/topmost_event_target.cc b/src/cobalt/layout/topmost_event_target.cc index 97e84fe..f825db7 100644 --- a/src/cobalt/layout/topmost_event_target.cc +++ b/src/cobalt/layout/topmost_event_target.cc
@@ -81,8 +81,12 @@ if (layout_boxes) { const Box* box = layout_boxes->boxes().front(); if (box->computed_style() && box->IsTransformed()) { - box->ApplyTransformActionToCoordinate(Box::kEnterTransform, - &element_coordinate); + // Early out if the transform cannot be applied. This can occur if the + // transform matrix is not invertible. + if (!box->ApplyTransformActionToCoordinate(Box::kEnterTransform, + &element_coordinate)) { + return; + } } scoped_refptr<dom::HTMLElement> html_element = element->AsHTMLElement();
diff --git a/src/cobalt/layout_tests/layout_tests.cc b/src/cobalt/layout_tests/layout_tests.cc index 77b0def..f377d78 100644 --- a/src/cobalt/layout_tests/layout_tests.cc +++ b/src/cobalt/layout_tests/layout_tests.cc
@@ -90,7 +90,7 @@ scoped_refptr<render_tree::animations::AnimateNode> animate_node = new render_tree::animations::AnimateNode(layout_results.render_tree); scoped_refptr<render_tree::Node> animated_tree = - animate_node->Apply(layout_results.layout_time).animated; + animate_node->Apply(layout_results.layout_time).animated->source(); bool results = pixel_tester.TestTree(animated_tree, GetParam().base_file_path);
diff --git a/src/cobalt/layout_tests/web_platform_tests.cc b/src/cobalt/layout_tests/web_platform_tests.cc index 5421370..01788fd 100644 --- a/src/cobalt/layout_tests/web_platform_tests.cc +++ b/src/cobalt/layout_tests/web_platform_tests.cc
@@ -281,6 +281,7 @@ std::string json_results = RunWebPlatformTest(test_url, &got_results); EXPECT_TRUE(got_results); std::vector<TestResult> results = ParseResults(json_results); + bool failed_at_least_once = false; for (size_t i = 0; i < results.size(); ++i) { const WebPlatformTestInfo& test_info = GetParam(); const TestResult& test_result = results[i]; @@ -290,6 +291,15 @@ if (it != test_info.exceptions.end()) { should_pass = !should_pass; } + // If expected to fail but current subtest did not fail, wait to report + // the entire test failed after the last subtest passed and none failed. + if (!should_pass && test_result.status == WebPlatformTestInfo::kPass && + (i != results.size() - 1 || failed_at_least_once)) { + should_pass = true; + } else { + failed_at_least_once = failed_at_least_once || + test_result.status == WebPlatformTestInfo::kFail; + } EXPECT_PRED_FORMAT2(CheckResult, should_pass, test_result); } }
diff --git a/src/cobalt/loader/embedded_resources/you_tube_logo.png b/src/cobalt/loader/embedded_resources/you_tube_logo.png deleted file mode 100644 index 14563d0..0000000 --- a/src/cobalt/loader/embedded_resources/you_tube_logo.png +++ /dev/null Binary files differ
diff --git a/src/cobalt/loader/embedded_resources/youtube_splash_screen.css b/src/cobalt/loader/embedded_resources/youtube_splash_screen.css deleted file mode 100644 index 81ff7cb..0000000 --- a/src/cobalt/loader/embedded_resources/youtube_splash_screen.css +++ /dev/null
@@ -1,158 +0,0 @@ -/* - * Copyright 2015 Google Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. -*/ - -body { - overflow: hidden; - font-size: 1.4815vh; /* Corresponds to 16px at 1080p. */ -} - -#splash { - background-color: #e62117; - background-image: url("h5vcc-embedded://you_tube_logo.png"); - background-position: center center; - background-repeat: no-repeat; - background-size: 50%; - height: 100%; - left: 0; - position: absolute; - top: 0; - width: 100%; -} - -#loading { - position: absolute; - top: 52em; - width: 100%; -} - -#spinner { - /* The spinner starts with display set to none, and JavaScript will set this - to 'block' after some time has passed, if the splash screen is still - visible. */ - display: none; - - height: 5.33em; - margin: 0 auto; - position: relative; - width: 5.33em; -} - -.dot { - background-color: #cbcbcb; - border-radius: 50%; - height: 1.17em; - position: absolute; - width: 1.17em; -} - -@keyframes fade1 { - 0%,100% {opacity: 0} - 50% {opacity: 1} -} - -@keyframes fade2 { - 0%,100% {opacity: .25} - 37.5% {opacity: 1} - 87.5% {opacity: 0} -} - -@keyframes fade3 { - 0%,100% {opacity: .5} - 25% {opacity: 1} - 75% {opacity: 0} -} - -@keyframes fade4 { - 0%,100% {opacity: .75} - 12.5% {opacity: 1} - 62.5% {opacity: 0} -} - -@keyframes fade5 { - 0%,100% {opacity: 1} - 50% {opacity: 0} -} - -@keyframes fade6 { - 0%,100% {opacity: .75} - 37.5% {opacity: 0} - 87.5% {opacity: 1} -} - -@keyframes fade7 { - 0%,100% {opacity: .5} - 25% {opacity: 0} - 75% {opacity: 1} -} - -@keyframes fade8 { - 0%,100% {opacity: .25} - 12.5% {opacity: 0} - 62.5% {opacity: 1} -} - -#dot1 { - animation: fade8 .72s infinite ease; - left: 0; - top: 2.09em; -} - -#dot2 { - animation: fade7 .72s infinite ease; - left: .61em; - top: .61em; -} - -#dot3 { - animation: fade6 .72s infinite ease; - left: 2.09em; - top: 0; -} - -#dot4 { - animation: fade5 .72s infinite ease; - right: .61em; - top: .61em; -} - -#dot5 { - animation: fade4 .72s infinite ease; - right: 0; - top: 2.09em; -} - -#dot6 { - animation: fade3 .72s infinite ease; - bottom: .61em; - right: .61em; -} - -#dot7 { - animation: fade2 .72s infinite ease; - bottom: 0; - left: 2.09em; -} - -#dot8 { - animation: fade1 .72s infinite ease; - bottom: .61em; - left: .61em; -} - -.hidden { - height: 0; - visibility: hidden; -}
diff --git a/src/cobalt/loader/embedded_resources/youtube_splash_screen.html b/src/cobalt/loader/embedded_resources/youtube_splash_screen.html index d40bf9b..0c704e5 100644 --- a/src/cobalt/loader/embedded_resources/youtube_splash_screen.html +++ b/src/cobalt/loader/embedded_resources/youtube_splash_screen.html
@@ -1,6 +1,6 @@ <!DOCTYPE html> <!-- - Copyright 2015 Google Inc. All Rights Reserved. + Copyright 2017 Google Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,34 +15,205 @@ limitations under the License. --> <html> + <head> + <meta http-equiv="Content-Security-Policy" content="default-src 'none'; + script-src 'unsafe-inline'; + style-src 'unsafe-inline'; + img-src 'self' data:;"> +<style> +#background { + background-color: #282828; + margin: 0; + height: 100vh; + width: 100vw; + transition: 200ms; + transition-delay: 500ms; +} +#lozengeContainer { + position: fixed; + z-index: 1; + top: 50%; + left: 25%; + transform: translate(0, -50%); + width: 33%; + transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); + transition-delay: 0ms; +} +#lozengeContainer.move { + transform: translate(-51%, -50%); -<head> - <meta http-equiv="Content-Security-Policy" content="default-src 'none'; - script-src h5vcc-embedded://*/splash_screen.js; - style-src h5vcc-embedded://*/youtube_splash_screen.css; - img-src h5vcc-embedded://*/you_tube_logo.png;"> -<link rel="stylesheet" type="text/css" - href="h5vcc-embedded://youtube_splash_screen.css"> +} +#lozengeBg { + position: absolute; + z-index: -1; + background-color: #282828; +} + + +#wordmarkContainer { + position: absolute; + z-index: -1; + top: 50%; + left: 25%; + transform: translate(0, -50%); + width: 33%; + transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); + transition-delay: 0ms; +} +#wordmarkContainer.move { + transform: translate(54%, -50%); + z-index: 0; +} +#wordmark { + background-size: contain; +} + +#spinnerContainer { + position: fixed; + top: 71.5%; + left: 50%; + width: 166px; + height: 166px; + opacity: .2; + margin: 0 0 0 -83px; + display: none; +} + +#spinnerContainer.active { + display: block; + animation: loading 5s linear infinite; +} + +#loader { + border-radius: 50%; + color: #ffffff; + position: relative; + width: 166px; + height: 166px; + box-shadow: inset 0 0 0 15px; +} + +.beforeafter { + background-color: #282828; + position: absolute; + top: -1px; + width: 84px; + height: 169px; +} +#loadbefore { + left: -1px; + transform-origin: 83px 83px; + animation: loading 1000ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite 750ms; +} + +#loadafter { + left: 83px; + transform-origin: 0 83px; + animation: loading 1000ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite; +} + +@keyframes loading { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +@media only screen and (min-device-width : 1921px){ + #lozengeBg { + width: 960px; + height: 425px; + } + #lozenge { + background: transparent url('data:image/png;base64,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') no-repeat right; + width: 1267px; + height: 425px; + } + #wordmark { + background: transparent url('data:image/png;base64,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') no-repeat; + width: 1267px; + height: 385px; + } +} + +@media only screen and (min-device-width : 1281px) and (max-device-width : 1920px) { + #lozengeBg { + width: 480px; + height: 212px; + } + #lozenge { + background: transparent url('data:image/png;base64,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') no-repeat right; + width: 633px; + height: 212px; + } + #wordmark { + background: transparent url('data:image/png;base64,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') no-repeat; + width: 633px; + height: 192px; + } + #loader { + transform: scale(0.5, 0.5); + } +} +@media only screen and (max-device-width : 1280px) { + #lozengeBg { + width: 320px; + height: 141px; + } + #lozenge { + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAM4AAACQCAYAAABAm1RDAAABS2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxMzggNzkuMTU5ODI0LCAyMDE2LzA5LzE0LTAxOjA5OjAxICAgICAgICAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIi8+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+IEmuOgAAC0tJREFUeJzt3X9sVeUdx/F3j7ellVYKll8KxG04MzNFMuPw1wbZFmVuRvzPH5mZ+ofBbQoYJGLiP6jEoOKIMWqIZluIf0mcU4gxKzpUNGZMNpEVXAwqBax4S4stbWn3x/cc7untvb0/eu597rn380pOeu6Pc++XpB+e5zk953nqRqgJU4EE0AJMAs4EJgMNwFn+a61APdAM1PmPCT0H0Ag0+fvB8fifcUbo+5r94wLBd2fS4r9eiCGgJ8trPf7r2d57Cjju7w8AJ/z9PqDf3+8FBv39JDASei7pf0Z36PhvgZOh7/6mwH9P7NRVeHA8oA2YDkzDfpmnpv1sxX5Rg5/BNoXifiklGkFAu7Fw9WLBSvr73VjAkmk/jwFdwFfAcNmrzpOr4NQBc4F5/nauv50DzCQVljb/vVJ7RrAABSE6AhwCvvS3g/72BQ4CVo7gfA+4DFgAnB/aGkv/1VID+oEDQAewH9gDfOA/VzKlCM4MYBnwK2AR1mqIlNvXwHvAa8DLwNEoPzzK4CwB/gBcx+iBsYhrp4DtwEbgzSg+MIrg/BR4FLh84h8lUnLvA2uAHRP5kIkEZzrwBHDrRAoQcWQLsIIiu3DFBufnwJ+A2cUdLlIRDgO3AW8UeqBXxJetxPqLCo3E3SzgdeC+Qg8spMWpAx4r5ktEYuAprOuWVyQKCc4GYFVRJYnEw5NYjyqnfLtqK1FopPqtIM8eVT4tzi+AbYy+iFGkWp0CfkmOEwa5gjMdu4RhVlRVicTAEeBixjlVnaurthGFRmrPTOxkQVbjtTg/Ad6Kth6RWPkZ8PdML4wXnF3Aj0tTj0gsfECWDGTrqi3JdoBIDbkMOzk2RrbgrChdLSKxck+mJzN11WZid9XplmMRm0NhHtAZfjJTi7MMhUYkkABuSH8yU3CuK30tIrHy6/Qn0rtqHnbLaWv6G0VqWDdwNnZVATC2xfkhCo1IuinAReEn0oNzZflqEYmVq8IP0oPzozIWIhIno7KRHpxLyliISJwsCD8InxxIYFOTTipvPSKxMIBNrTwIo1ucC1BoRLJpwDICjA7O98tfi0isZAzOBRneKCIppxsXBUckfz8IdsLB+a6DQkTi5DvBTjg455W/DpFYOS/YCU5H12NL2WkmG5HshrGlLAeCFmcuCo1ILh52b87prtq57moRiZU5kArOTIeFiMTJDEgFRysPiORnNqSCM8NhISJxMhPU4kRr2za48ELXVUhpzYJUcDTNbRSuvRY++gg2boRW3UhbpUYFRycHopJIwD33wP79sHy5PZZqoq5aSbW1wdNPWwt0zTWuq5HonAOpKwf60b04EzcyzkzcW7fC6tVw4ED56pFSGAQa6kbsEoJvXVdTFcYLDsDAAGzaBOvWQTJZnpqkFFo8YKrrKmpGQwOsWmXjnzvvBK+YRb+lArR6aB618mtrg+efhw8/hMWLXVcjhVNwnFq4ENrb4eWXYf5819VI/qaqq1YJli2DPXts7NPc7Loaya1VwakUTU2wdi10dMDtt2v8U9nUVas4s2fD5s02/rniCtfVSGZTPWySNak0CxfCzp2wZQvMm+e6GhntTA+biV0qUV0d3HQT7Nun8U9lmeIBZ7quQnIIxj+ffGJBqqtzXVGta/CwKwckDubMsa7bu+/CokWuq6llTR4w2XUVUqBFiyw8W7bYyQQpt8ke0OK6CilCMP7p6LBuXJM6DmVUr65a3DU324mDffs0/imfszyg0XUVEoF586zr9tZbdipbSqlBXbVqc/XV9sfTzZs1/imdJo+xyxlK3HmeXbbT0QEPPGC3M0iUmnVWrZo1N8PDD8PHH9uFpBKVeg9b+1Oq2fz5dutCe7vGP9Fo0cmBWrJ4sY1/nnnGbqaTYk3y0CQdtcXz4K677PbtVas0/ilOo04M1KrWVtiwwcY/11/vuprYUXBq3fz58Mormr63MI11I5BjTiPJW67poSrd0BA89xw89BB0dbmupqIpOFGKe3ACySQ8+CA8+6yFScZQcKJULcEJ7N1rJxC2b3ddScXRGEekCAqOjJVMwooVsGCBWpssElhXTdeii04OFCABHEcTdsgbb1grs3ev60ri4KS6arXuwAG48UZbw0ehyVe/LvCsVcmk3Tm6aZMtPyIFSQAnXRchZTQ8DC+8AGvWaBxTvP4Ethqb1IIdO2DlSti923UlcXfSw5Zmk2r26ac2jlmyRKGJRk8CLWNYvXp7Yf16eOIJ6OtzXU01GUgAp1xXIREbHoYXX7TrzTo7XVdTjU4kgF7XVUiE3nsP7r5bXbLS6vPQyYHqcPAg3HwzXHmlQlN6AwlAnd840zjGheMJoMd1FVKEkRF46SW4/374/HPX1dSaQY1x4mjXLruubNcu15XUqhMe6qrFR2cn3HKLrQ2q0Lj0rcY4cdDXZ2OY9ettTCOuDSaAbtdVSBbBOGbNGjtrJpWiW2OcSrV7N9x7L7z9tutKZKwTHpB0XYWEdHbCHXfApZcqNJUrmQCOua5CsHtiHn8cHnlE45jKl0ygFse9rVth9Wq7G1PiQMFxavduuz9mxw7XlUhhjnnAN66rqDldXbB8uY1jFJo4UotTVgMDdo//unV2z7/EVbLOn7S1H62TM3HjTYH76qvWLdM4Ju4G8VedBvjKZSVVbe9eWLrU1qBRaKpBF6SmwD3ssJDq1NVlN5RpGtlqcwhSC+cqOFEJppFdu1bjmOp0FFLBOeKwkOqxfbsti6EZMavZYUgFRzM6RGHpUtcVSOkdhtQYRy2OSH6OQCo4anFE8nMI1OKIFOoopILzhcNCROLkC7DFc8FOEvQDZ7irR6TiDQNNwEDQ4gwBX7qrRyQWDgEDMHrx3M+clCISH58FO+HgfFr+OkRi5X/BTjg4/3VQiEic7At2wsHpcFCISJycblzCwdmX4Y0iknK6cakL3XoVTMDeWP56RCreSaAFf+nPcIszBPzHRUUiMfAxofVyvbQXPypvLSKx8a/wg/TgfFjGQkTi5J/hB+nBeaeMhYjEyc7wg7q0eVk8bOKOaeWrR6TiJYGzsWvVgLEtzjBqdUTS7SQUGhgbHIBXy1OLSGy8nv5EelcNYAZ2z0F96esRqXhDwFzSZoLK1OIcBf5WjopEYmAbGaZPyxQcgD+WthaR2Hgy05OZumqBd4HLS1SMSBy8DyzK9EK2FgdgNTBOrkSq3upsL4wXnJ3An6OvRSQW/gJkXYR1vK4a2B999gDnRFqSSGXrBC7GX5kgk/FaHICvgVuBUxEWJVLJTgG/YZzQQO7gALQDK6OoSCQG7gPezPWmfIIDdnp6/YTKEal8jwEb83ljrjFOukeBNYXXI1LxNlDAmeRCgwPwO+yPQolcbxSJgSGse/ZUIQcVExyAJcCfgDnFHS5SEQ5hJ7/aCz0w3zFOunZgAfAi+iOpxM8I9h//AooIDRQfHIBjwG+BK4B/TOBzRMrpHeAq4DZynHIeT7FdtUyuBn4PXA9Miu5jRSZsELvi/0ki+k8+yuAEpgE3ANdhYZoe/VeI5NSFheQ14K/YlACRKUVwRn0+cD5wCXCRvx9sLaX9aqkRvdgMmwf8n//GpnLaTwnH36UOznhmAfOw6+DmAuf622yslWrzf+pO1No0iLUaX/lbJ7aG05fA59gZsYNkuMmsHFwGJ1+t2O3c0/xtqv9cKzDF31qBZn+b7L+nGVs9Sy2bGz3YKn892CwxvaEtCRz3f3YD3/jbMf/nEf+1ihWH4EShEQvRZKABC9sZWODqSYWs0d+vx/7A25J2PKHX8T/Hw7qkrVm+Myx4fzZBDZn0A33jHDuM/RKG9fnHhXX77x0h9cs5iP1Cpx/Tg/2BMHg9qKHXf67bf70bW6nsRJbvrDr/B9aWlvwbNMFRAAAAAElFTkSuQmCC') no-repeat right; + width: 422px; + height: 141px; + } + #wordmark { + background: transparent url('data:image/png;base64,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') no-repeat; + width: 422px; + height: 128px; + } + #loader { + transform: scale(0.33, 0.33); + } +} +</style> + </head> <body> - <div id="splash"> - <img src="h5vcc-embedded://you_tube_logo.png" class="hidden"> +<div id="background"> +<div id="lozengeContainer"> + <div id="lozengeBg"></div> + <div id="lozenge"></div> +</div> +<div id="wordmarkContainer"> + <div id="wordmark"></div> +</div> +<div id="spinnerContainer" class="active"> + <div id="loader"> + <div id="loadbefore" class="beforeafter"></div> + <div id="loadafter" class="beforeafter"></div> </div> - <div id="loading"> - <div id="spinner"> - <div class="dot" id="dot1"></div> - <div class="dot" id="dot2"></div> - <div class="dot" id="dot3"></div> - <div class="dot" id="dot4"></div> - <div class="dot" id="dot5"></div> - <div class="dot" id="dot6"></div> - <div class="dot" id="dot7"></div> - <div class="dot" id="dot8"></div> - </div> - </div> - <script type="text/javascript" src="h5vcc-embedded://splash_screen.js"> - </script> -</body> +</div> +</div> +<script> + function backgroundTransitionEnd(e) { + if (e.target == e.currentTarget) { + window.close(); + } + } + function expand() { + document.getElementById('lozengeContainer').className = 'move'; + document.getElementById('wordmarkContainer').className = 'move'; + document.getElementById('spinnerContainer').classList.remove("active"); + document.getElementById('background').addEventListener('transitionend', + backgroundTransitionEnd); + document.getElementById('background').style.opacity = 0; + return ''; + } + + window.onbeforeunload = expand; +</script> +</body> </html>
diff --git a/src/cobalt/loader/image/image.h b/src/cobalt/loader/image/image.h index 2b72010..740c9b5 100644 --- a/src/cobalt/loader/image/image.h +++ b/src/cobalt/loader/image/image.h
@@ -140,10 +140,7 @@ scoped_refptr<FrameProvider> frame_provider, const math::RectF& destination_rect, const math::Matrix3F& local_transform, - render_tree::ImageNode::Builder* image_node_builder, - base::TimeDelta time) { - UNREFERENCED_PARAMETER(time); - + render_tree::ImageNode::Builder* image_node_builder) { image_node_builder->source = frame_provider->GetFrame(); image_node_builder->destination_rect = destination_rect; image_node_builder->local_transform = local_transform;
diff --git a/src/cobalt/loader/image/image_decoder_starboard.cc b/src/cobalt/loader/image/image_decoder_starboard.cc index 1183eea..49d52a3 100644 --- a/src/cobalt/loader/image/image_decoder_starboard.cc +++ b/src/cobalt/loader/image/image_decoder_starboard.cc
@@ -23,7 +23,7 @@ #include "starboard/decode_target.h" #include "starboard/image.h" -#if SB_TRUE && SB_HAS(GRAPHICS) +#if SB_HAS(GRAPHICS) namespace cobalt { namespace loader { @@ -35,11 +35,7 @@ : ImageDataDecoder(resource_provider), mime_type_(mime_type), format_(format), -#if SB_TRUE provider_(resource_provider->GetSbDecodeTargetGraphicsContextProvider()), -#else // #if SB_TRUE - provider_(resource_provider->GetSbDecodeTargetProvider()), -#endif // #if SB_TRUE target_(kSbDecodeTargetInvalid) { TRACE_EVENT0("cobalt::loader::image", "ImageDecoderStarboard::ImageDecoderStarboard()"); @@ -75,6 +71,6 @@ } // namespace loader } // namespace cobalt -#endif // SB_TRUE && SB_HAS(GRAPHICS) +#endif // SB_HAS(GRAPHICS) #endif // #if defined(STARBOARD)
diff --git a/src/cobalt/loader/loader.gyp b/src/cobalt/loader/loader.gyp index 6a07f01..cf037bf 100644 --- a/src/cobalt/loader/loader.gyp +++ b/src/cobalt/loader/loader.gyp
@@ -184,10 +184,8 @@ '<(input_directory)/cobalt_splash_screen.css', '<(input_directory)/cobalt_splash_screen.html', '<(input_directory)/equirectangular_40_40.msh', - '<(input_directory)/youtube_splash_screen.css', '<(input_directory)/youtube_splash_screen.html', '<(input_directory)/splash_screen.js', - '<(input_directory)/you_tube_logo.png', ], 'actions': [ {
diff --git a/src/cobalt/loader/unencoded_data_urls/lozenge_1080.png b/src/cobalt/loader/unencoded_data_urls/lozenge_1080.png new file mode 100644 index 0000000..1e131c4 --- /dev/null +++ b/src/cobalt/loader/unencoded_data_urls/lozenge_1080.png Binary files differ
diff --git a/src/cobalt/loader/unencoded_data_urls/lozenge_2160.png b/src/cobalt/loader/unencoded_data_urls/lozenge_2160.png new file mode 100644 index 0000000..49f2670 --- /dev/null +++ b/src/cobalt/loader/unencoded_data_urls/lozenge_2160.png Binary files differ
diff --git a/src/cobalt/loader/unencoded_data_urls/lozenge_720.png b/src/cobalt/loader/unencoded_data_urls/lozenge_720.png new file mode 100644 index 0000000..1a10d3f --- /dev/null +++ b/src/cobalt/loader/unencoded_data_urls/lozenge_720.png Binary files differ
diff --git a/src/cobalt/loader/unencoded_data_urls/wordmark_1080.png b/src/cobalt/loader/unencoded_data_urls/wordmark_1080.png new file mode 100644 index 0000000..1c153f1 --- /dev/null +++ b/src/cobalt/loader/unencoded_data_urls/wordmark_1080.png Binary files differ
diff --git a/src/cobalt/loader/unencoded_data_urls/wordmark_2160.png b/src/cobalt/loader/unencoded_data_urls/wordmark_2160.png new file mode 100644 index 0000000..3d106c5 --- /dev/null +++ b/src/cobalt/loader/unencoded_data_urls/wordmark_2160.png Binary files differ
diff --git a/src/cobalt/loader/unencoded_data_urls/wordmark_720.png b/src/cobalt/loader/unencoded_data_urls/wordmark_720.png new file mode 100644 index 0000000..7fcb9f1 --- /dev/null +++ b/src/cobalt/loader/unencoded_data_urls/wordmark_720.png Binary files differ
diff --git a/src/cobalt/math/matrix3_f.cc b/src/cobalt/math/matrix3_f.cc index 2f1f19d..929340e 100644 --- a/src/cobalt/math/matrix3_f.cc +++ b/src/cobalt/math/matrix3_f.cc
@@ -84,6 +84,12 @@ return matrix; } +bool Matrix3F::IsZeros() const { + return data_[M00] == 0.0f && data_[M01] == 0.0f && data_[M02] == 0.0f && + data_[M10] == 0.0f && data_[M11] == 0.0f && data_[M12] == 0.0f && + data_[M20] == 0.0f && data_[M21] == 0.0f && data_[M22] == 0.0f; +} + bool Matrix3F::IsIdentity() const { return data_[M00] == 1.0f && data_[M01] == 0.0f && data_[M02] == 0.0f && data_[M10] == 0.0f && data_[M11] == 1.0f && data_[M12] == 0.0f &&
diff --git a/src/cobalt/math/matrix3_f.h b/src/cobalt/math/matrix3_f.h index d4560a6..c2e8c63 100644 --- a/src/cobalt/math/matrix3_f.h +++ b/src/cobalt/math/matrix3_f.h
@@ -39,6 +39,7 @@ data_[8] = m22; } + bool IsZeros() const; bool IsIdentity() const; bool IsEqual(const Matrix3F& rhs) const;
diff --git a/src/cobalt/media/player/web_media_player.h b/src/cobalt/media/player/web_media_player.h index beee2aa..b3b5cb6 100644 --- a/src/cobalt/media/player/web_media_player.h +++ b/src/cobalt/media/player/web_media_player.h
@@ -194,7 +194,7 @@ public: virtual void NetworkStateChanged() = 0; virtual void ReadyStateChanged() = 0; - virtual void TimeChanged(bool eos_played) = 0; + virtual void TimeChanged() = 0; virtual void DurationChanged() = 0; virtual void OutputModeChanged() = 0; virtual void PlaybackStateChanged() = 0;
diff --git a/src/cobalt/media/player/web_media_player_impl.cc b/src/cobalt/media/player/web_media_player_impl.cc index c8cf80a..bd0d14b 100644 --- a/src/cobalt/media/player/web_media_player_impl.cc +++ b/src/cobalt/media/player/web_media_player_impl.cc
@@ -590,8 +590,7 @@ // Update our paused time. if (state_.paused) state_.paused_time = pipeline_->GetMediaTime(); - const bool eos_played = false; - GetClient()->TimeChanged(eos_played); + GetClient()->TimeChanged(); } void WebMediaPlayerImpl::OnPipelineEnded(PipelineStatus status) { @@ -600,9 +599,7 @@ OnPipelineError(status); return; } - - const bool eos_played = true; - GetClient()->TimeChanged(eos_played); + GetClient()->TimeChanged(); } void WebMediaPlayerImpl::OnPipelineError(PipelineStatus error) {
diff --git a/src/cobalt/media/sandbox/web_media_player_helper.cc b/src/cobalt/media/sandbox/web_media_player_helper.cc index 1909a5c..58618a2 100644 --- a/src/cobalt/media/sandbox/web_media_player_helper.cc +++ b/src/cobalt/media/sandbox/web_media_player_helper.cc
@@ -35,7 +35,7 @@ // WebMediaPlayerClient methods void NetworkStateChanged() OVERRIDE {} void ReadyStateChanged() OVERRIDE {} - void TimeChanged(bool) OVERRIDE {} + void TimeChanged() OVERRIDE {} void DurationChanged() OVERRIDE {} void OutputModeChanged() OVERRIDE {} void PlaybackStateChanged() OVERRIDE {} @@ -48,8 +48,9 @@ #endif // defined(COBALT_MEDIA_SOURCE_2016) std::string SourceURL() const OVERRIDE { return ""; } #if defined(COBALT_MEDIA_SOURCE_2016) - void EncryptedMediaInitDataEncountered(EmeInitDataType, const unsigned char*, - unsigned) OVERRIDE {} + void EncryptedMediaInitDataEncountered(EmeInitDataType init_data_type, + const unsigned char* init_data, + unsigned init_data_length) OVERRIDE {} #endif // defined(COBALT_MEDIA_SOURCE_2016) };
diff --git a/src/cobalt/render_tree/animations/animate_node.cc b/src/cobalt/render_tree/animations/animate_node.cc index 58c11b4..46435b5 100644 --- a/src/cobalt/render_tree/animations/animate_node.cc +++ b/src/cobalt/render_tree/animations/animate_node.cc
@@ -66,7 +66,8 @@ TraverseList* traverse_list) : animation_map_(animation_map), traverse_list_(traverse_list), - expiry_(-base::TimeDelta::Max()) {} + expiry_(-base::TimeDelta::Max()), + depends_on_time_expiry_(-base::TimeDelta::Max()) {} void Visit(animations::AnimateNode* animate) OVERRIDE; void Visit(CompositionNode* composition) OVERRIDE { VisitNode(composition); } @@ -117,6 +118,10 @@ // The time after which all animations will have completed and be constant. base::TimeDelta expiry_; + // Similar to |expiry_| but accumulated only for animations whose callback + // depends on the time parameter. + base::TimeDelta depends_on_time_expiry_; + friend class AnimateNode; }; @@ -146,6 +151,9 @@ // Update our expiry in accordance with the sub-AnimateNode's expiry. expiry_ = std::max(expiry_, animate->expiry()); + + depends_on_time_expiry_ = + std::max(depends_on_time_expiry_, animate->depends_on_time_expiry()); } template <typename T> @@ -208,8 +216,10 @@ void AnimateNode::TraverseListBuilder::AddToTraverseList( Node* node, AnimateNode::Builder::InternalMap::const_iterator found) { if (found != animation_map_.end()) { - traverse_list_->push_back(TraverseListEntry(node, found->second)); + traverse_list_->push_back(TraverseListEntry(node, found->second, false)); expiry_ = std::max(expiry_, found->second->GetExpiry()); + depends_on_time_expiry_ = std::max(depends_on_time_expiry_, + found->second->GetDependsOnTimeExpiry()); } else { traverse_list_->push_back(TraverseListEntry(node)); } @@ -304,7 +314,9 @@ TraverseListEntry current_entry = AdvanceIterator(node); DCHECK(current_entry.animations); - ProcessAnimatedNodeBounds(current_entry, node); + if (current_entry.did_animate_previously) { + ProcessAnimatedNodeBounds(current_entry, node); + } } template <typename T> @@ -336,7 +348,7 @@ } transform_ = old_transform; - if (current_entry.animations) { + if (current_entry.did_animate_previously) { ProcessAnimatedNodeBounds(current_entry, node); } } @@ -376,7 +388,8 @@ // tree. class AnimateNode::ApplyVisitor : public NodeVisitor { public: - ApplyVisitor(const TraverseList& traverse_list, base::TimeDelta time_offset); + ApplyVisitor(const TraverseList& traverse_list, base::TimeDelta time_offset, + const base::optional<base::TimeDelta>& snapshot_time); void Visit(animations::AnimateNode* /* animate */) OVERRIDE { // An invariant of AnimateNodes is that they should never contain descendant @@ -405,7 +418,9 @@ // As we compute the animated nodes, we create a new traverse list that leads // to the newly created animated nodes. This can be used afterwards to // calculate the bounding boxes around the active animated nodes. - TraverseList* animated_traverse_list() { return &animated_traverse_list_; } + const TraverseList& animated_traverse_list() const { + return animated_traverse_list_; + } private: template <typename T> @@ -415,8 +430,8 @@ typename base::enable_if<ChildIterator<T>::has_children>::type VisitNode( T* node); template <typename T> - scoped_refptr<Node> ApplyAnimations(const TraverseListEntry& entry, - typename T::Builder* builder); + scoped_refptr<T> ApplyAnimations(const TraverseListEntry& entry, + typename T::Builder* builder); TraverseListEntry AdvanceIterator(Node* node); // The time offset to be passed in to individual animations. @@ -435,11 +450,18 @@ // An iterator pointing to the next valid render tree node to visit. TraverseList::const_iterator iterator_; + + // Time at which the existing source render tree was created/last animated + // at. + base::optional<base::TimeDelta> snapshot_time_; }; -AnimateNode::ApplyVisitor::ApplyVisitor(const TraverseList& traverse_list, - base::TimeDelta time_offset) - : time_offset_(time_offset), traverse_list_(traverse_list) { +AnimateNode::ApplyVisitor::ApplyVisitor( + const TraverseList& traverse_list, base::TimeDelta time_offset, + const base::optional<base::TimeDelta>& snapshot_time) + : time_offset_(time_offset), + traverse_list_(traverse_list), + snapshot_time_(snapshot_time) { animated_traverse_list_.reserve(traverse_list.size()); iterator_ = traverse_list_.begin(); } @@ -452,9 +474,18 @@ // have animations. DCHECK(current_entry.animations); typename T::Builder builder(node->data()); - animated_ = ApplyAnimations<T>(current_entry, &builder); + scoped_refptr<T> animated = ApplyAnimations<T>(current_entry, &builder); + // If nothing ends up getting animated, then just re-use the existing node. + bool did_animate = false; + if (animated->data() == node->data()) { + animated_ = node; + } else { + animated_ = animated.get(); + did_animate = true; + } + animated_traverse_list_.push_back( - TraverseListEntry(animated_, current_entry.animations)); + TraverseListEntry(animated_, current_entry.animations, did_animate)); } template <typename T> @@ -464,7 +495,7 @@ size_t animated_traverse_list_index = animated_traverse_list_.size(); animated_traverse_list_.push_back( - TraverseListEntry(NULL, current_entry.animations)); + TraverseListEntry(NULL, current_entry.animations, false)); // Traverse the child nodes, but only the ones that are on the // |traverse_list_|. In particular, the next node we are allowed to visit @@ -482,41 +513,60 @@ // If one of our children is next up on the path to animation, traverse // into it. child->Accept(this); - // Traversing into the child means that it was animated, and so replaced - // by an animated node while it was visited. Thus, replace it in the - // current node's child list with its animated version. - child_iterator.ReplaceCurrent(animated_); - children_modified = true; + if (animated_ != child) { + // Traversing into the child and seeing |animated_| emerge from the + // traversal equal to something other than |child| means that the child + // was animated, and so replaced by an animated node while it was + // visited. Thus, replace it in the current node's child list with its + // animated version. + child_iterator.ReplaceCurrent(animated_); + children_modified = true; + } } child_iterator.Next(); } + base::optional<typename T::Builder> builder; + if (children_modified) { + // Reuse the modified Builder object from child traversal if one of + // our children was animated. + builder.emplace(child_iterator.TakeReplacedChildrenBuilder()); + } + + bool did_animate = false; if (current_entry.animations) { - base::optional<typename T::Builder> builder; - if (children_modified) { - // Reuse the modified Builder object from child traversal if one of - // our children was animated. - builder.emplace(child_iterator.TakeReplacedChildrenBuilder()); - } else { + if (!builder) { // Create a fresh copy of the Builder object for this animated node, to // be passed into the animations. builder.emplace(node->data()); } - animated_ = ApplyAnimations<T>(current_entry, &(*builder)); + typename T::Builder original_builder(*builder); + scoped_refptr<T> animated = ApplyAnimations<T>(current_entry, &(*builder)); + if (!(original_builder == *builder)) { + did_animate = true; + } + // If the data didn't actually change, then no animation took place and + // so we should note this by not modifying the original render tree node. + animated_ = animated->data() == node->data() ? node : animated.get(); } else { - // If there were no animations targeting this node directly, then its - // children must have been modified since otherwise it wouldn't be in - // the traverse list. - DCHECK(children_modified); - animated_ = new T(child_iterator.TakeReplacedChildrenBuilder()); + // If there were no animations targeting this node directly, it may still + // need to be animated if its children are animated, which will be the + // case if |builder| is populated. + if (builder) { + animated_ = new T(std::move(*builder)); + } else { + animated_ = node; + } } animated_traverse_list_[animated_traverse_list_index].node = animated_; + animated_traverse_list_[animated_traverse_list_index].did_animate_previously = + did_animate; } template <typename T> -scoped_refptr<Node> AnimateNode::ApplyVisitor::ApplyAnimations( +scoped_refptr<T> AnimateNode::ApplyVisitor::ApplyAnimations( const TraverseListEntry& entry, typename T::Builder* builder) { TRACE_EVENT0("cobalt::renderer", "AnimateNode::ApplyVisitor::ApplyAnimations()"); @@ -525,11 +575,16 @@ base::polymorphic_downcast<const AnimationList<T>*>( entry.animations.get()); - // Iterate through each animation applying them one at a time. - for (typename AnimationList<T>::InternalList::const_iterator iter = - typed_node_animations->data().animations.begin(); - iter != typed_node_animations->data().animations.end(); ++iter) { - iter->Run(builder, time_offset_); + // Only execute the animation updates on nodes that have not expired. + if (!snapshot_time_ || + typed_node_animations->data().expiry >= *snapshot_time_) { + TRACE_EVENT0("cobalt::renderer", "Running animation callbacks"); + // Iterate through each animation applying them one at a time. + for (typename AnimationList<T>::InternalList::const_iterator iter = + typed_node_animations->data().animations.begin(); + iter != typed_node_animations->data().animations.end(); ++iter) { + iter->Run(builder, time_offset_); + } } return new T(*builder); @@ -559,9 +614,9 @@ class AnimateNode::RefCountedTraversalList : public base::RefCounted<RefCountedTraversalList> { public: - explicit RefCountedTraversalList(TraverseList* to_swap_in) { - traverse_list_.swap(*to_swap_in); - } + explicit RefCountedTraversalList(const TraverseList& traverse_list) + : traverse_list_(traverse_list) {} + const TraverseList& traverse_list() const { return traverse_list_; } private: @@ -594,24 +649,41 @@ AnimateNode::AnimateResults AnimateNode::Apply(base::TimeDelta time_offset) { TRACE_EVENT0("cobalt::renderer", "AnimateNode::Apply()"); + if (snapshot_time_) { + // Assume we are always animating forward. + DCHECK_LE(*snapshot_time_, time_offset); + } + AnimateResults results; if (traverse_list_.empty()) { - results.animated = source_; + results.animated = this; // There are no animations, so there is no bounding rectangle, so setup the // bounding box function to trivially return an empty rectangle. results.get_animation_bounds_since = base::Bind(&ReturnTrivialEmptyRectBound); } else { - ApplyVisitor apply_visitor(traverse_list_, time_offset); + ApplyVisitor apply_visitor(traverse_list_, time_offset, snapshot_time_); source_->Accept(&apply_visitor); - results.animated = apply_visitor.animated(); - // Setup a function for returning + // Setup a function for returning the bounds on the regions modified by + // animations given a specific starting point in time ("since"). This + // can be used by rasterizers to determine which regions need to be + // re-drawn or not. results.get_animation_bounds_since = base::Bind( &GetAnimationBoundsSince, scoped_refptr<RefCountedTraversalList>(new RefCountedTraversalList( apply_visitor.animated_traverse_list())), - time_offset, results.animated); + time_offset, apply_visitor.animated()); + + if (apply_visitor.animated() == source()) { + // If no animations were actually applied, indicate this by returning + // this exact node as the animated node. + results.animated = this; + } else { + results.animated = new AnimateNode(apply_visitor.animated_traverse_list(), + apply_visitor.animated(), expiry_, + depends_on_time_expiry_, time_offset); + } } return results; } @@ -638,6 +710,7 @@ } expiry_ = traverse_list_builder.expiry_; + depends_on_time_expiry_ = traverse_list_builder.depends_on_time_expiry_; } } // namespace animations
diff --git a/src/cobalt/render_tree/animations/animate_node.h b/src/cobalt/render_tree/animations/animate_node.h index 70ed9ba..c71e620 100644 --- a/src/cobalt/render_tree/animations/animate_node.h +++ b/src/cobalt/render_tree/animations/animate_node.h
@@ -20,6 +20,7 @@ #include "base/containers/small_map.h" #include "base/memory/ref_counted.h" +#include "base/optional.h" #include "cobalt/math/rect_f.h" #include "cobalt/render_tree/animations/animation_list.h" #include "cobalt/render_tree/movable.h" @@ -80,12 +81,30 @@ } template <typename T> + void Add( + const scoped_refptr<T>& target_node, + const typename Animation<T>::TimeIndependentFunction& single_animation, + base::TimeDelta expiry) { + AddInternal(target_node, + scoped_refptr<AnimationListBase>( + new AnimationList<T>(single_animation, expiry))); + } + + template <typename T> void Add(const scoped_refptr<T>& target_node, const typename Animation<T>::Function& single_animation) { AddInternal(target_node, scoped_refptr<AnimationListBase>(new AnimationList<T>( single_animation, base::TimeDelta::Max()))); } + template <typename T> + void Add(const scoped_refptr<T>& target_node, + const typename Animation<T>::TimeIndependentFunction& + single_animation) { + AddInternal(target_node, + scoped_refptr<AnimationListBase>(new AnimationList<T>( + single_animation, base::TimeDelta::Max()))); + } // Merge all mappings from another AnimateNode::Builder into this one. // There cannot be any keys that are in both the merge target and source. @@ -140,8 +159,11 @@ struct AnimateResults { // The animated render tree, which is guaranteed to not contain any - // AnimateNodes. - scoped_refptr<Node> animated; + // AnimateNodes. Note that it is returned as an AnimateNode... The actual + // animated render tree can be obtained via |animated->source()|, the + // AnimateNode however allows one to animate the animated node so that + // it can be checked if anything has actually been animated or not. + scoped_refptr<AnimateNode> animated; // Can be called in order to return a bounding rectangle around all // nodes that are actively animated. The parameter specifies a "since" @@ -152,6 +174,9 @@ base::Callback<math::RectF(base::TimeDelta)> get_animation_bounds_since; }; // Apply the animations to the sub render tree with the given |time_offset|. + // Note that if |animated| in the results is equivalent to |this| on which + // Apply() is called, then no animations have actually taken place, and a + // re-render can be avoided. AnimateResults Apply(base::TimeDelta time_offset); // Returns the sub-tree for which the animations apply to. @@ -163,6 +188,12 @@ // all x, y >= expiry(). const base::TimeDelta& expiry() const { return expiry_; } + // Similar to |expiry()|, but returns the expiration for all animations whose + // callback function actually depends on the time parameter passed into them. + const base::TimeDelta& depends_on_time_expiry() const { + return depends_on_time_expiry_; + } + private: // The compiled node animation list is a sequence of nodes that are either // animated themselves, or on the path to an animated node. Only nodes in @@ -170,12 +201,19 @@ // list, complete with animations to be applied to the given node, if any. struct TraverseListEntry { TraverseListEntry(Node* node, - const scoped_refptr<AnimationListBase>& animations) - : node(node), animations(animations) {} - explicit TraverseListEntry(Node* node) : node(node) {} + const scoped_refptr<AnimationListBase>& animations, + bool did_animate_previously) + : node(node), + animations(animations), + did_animate_previously(did_animate_previously) {} + explicit TraverseListEntry(Node* node) + : node(node), did_animate_previously(false) {} Node* node; scoped_refptr<AnimationListBase> animations; + // Used when checking animation bounds to see which nodes were actually + // animated and whose bounds we need to accumulate. + bool did_animate_previously; }; typedef std::vector<TraverseListEntry> TraverseList; @@ -192,6 +230,18 @@ // rectangle for all active animations. class BoundsVisitor; + // This private constructor is used by AnimateNode::Apply() to create a new + // AnimateNode that matches the resulting animated render tree. + AnimateNode(const TraverseList& traverse_list, scoped_refptr<Node> source, + const base::TimeDelta& expiry, + const base::TimeDelta& depends_on_time_expiry, + const base::TimeDelta& snapshot_time) + : traverse_list_(traverse_list), + source_(source), + expiry_(expiry), + depends_on_time_expiry_(depends_on_time_expiry), + snapshot_time_(snapshot_time) {} + void CommonInit(const Builder::InternalMap& node_animation_map, const scoped_refptr<Node>& source); @@ -205,6 +255,12 @@ TraverseList traverse_list_; scoped_refptr<Node> source_; base::TimeDelta expiry_; + base::TimeDelta depends_on_time_expiry_; + // Time when the |source_| render tree was last animated, if known. This + // will get set when Apply() is called to produce a new AnimateNode that can + // then be Apply()d again. It can be used during the second apply to check + // if some animations have expired. + base::optional<base::TimeDelta> snapshot_time_; }; } // namespace animations
diff --git a/src/cobalt/render_tree/animations/animate_node_test.cc b/src/cobalt/render_tree/animations/animate_node_test.cc index 7f539b9..b19733e 100644 --- a/src/cobalt/render_tree/animations/animate_node_test.cc +++ b/src/cobalt/render_tree/animations/animate_node_test.cc
@@ -81,10 +81,10 @@ scoped_refptr<AnimateNode> with_animations = CreateSingleAnimation(text_node, base::Bind(&AnimateText)); - scoped_refptr<Node> animated_render_tree = + scoped_refptr<AnimateNode> animated_render_tree = with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated; TextNode* animated_text_node = - dynamic_cast<TextNode*>(animated_render_tree.get()); + dynamic_cast<TextNode*>(animated_render_tree->source().get()); EXPECT_TRUE(animated_text_node); EXPECT_EQ(ColorRGBA(.5f, .5f, .5f), animated_text_node->data().color); } @@ -101,10 +101,10 @@ scoped_refptr<AnimateNode> with_animations = CreateSingleAnimation(image_node, base::Bind(&AnimateImage)); - scoped_refptr<Node> animated_render_tree = + scoped_refptr<AnimateNode> animated_render_tree = with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated; ImageNode* animated_image_node = - dynamic_cast<ImageNode*>(animated_render_tree.get()); + dynamic_cast<ImageNode*>(animated_render_tree->source().get()); EXPECT_TRUE(animated_image_node); EXPECT_TRUE(animated_image_node); EXPECT_EQ(RectF(2.0f, 2.0f), animated_image_node->data().destination_rect); @@ -122,10 +122,10 @@ scoped_refptr<AnimateNode> with_animations = CreateSingleAnimation(rect_node, base::Bind(&AnimateRect)); - scoped_refptr<Node> animated_render_tree = + scoped_refptr<AnimateNode> animated_render_tree = with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated; RectNode* animated_rect_node = - dynamic_cast<RectNode*>(animated_render_tree.get()); + dynamic_cast<RectNode*>(animated_render_tree->source().get()); EXPECT_TRUE(animated_rect_node); EXPECT_TRUE(animated_rect_node); EXPECT_EQ(RectF(2.0f, 2.0f), animated_rect_node->data().rect); @@ -147,26 +147,29 @@ scoped_refptr<AnimateNode> with_animations = CreateSingleAnimation(image_node, base::Bind(&AnimateImageAddWithTime)); - scoped_refptr<Node> animated_render_tree; + scoped_refptr<AnimateNode> animated_render_tree; ImageNode* animated_image_node; animated_render_tree = with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated; - animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get()); + animated_image_node = + dynamic_cast<ImageNode*>(animated_render_tree->source().get()); EXPECT_TRUE(animated_image_node); EXPECT_TRUE(animated_image_node); EXPECT_FLOAT_EQ(3.0f, animated_image_node->data().destination_rect.width()); animated_render_tree = with_animations->Apply(base::TimeDelta::FromSeconds(2)).animated; - animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get()); + animated_image_node = + dynamic_cast<ImageNode*>(animated_render_tree->source().get()); EXPECT_TRUE(animated_image_node); EXPECT_TRUE(animated_image_node); EXPECT_FLOAT_EQ(5.0f, animated_image_node->data().destination_rect.width()); animated_render_tree = with_animations->Apply(base::TimeDelta::FromSeconds(4)).animated; - animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get()); + animated_image_node = + dynamic_cast<ImageNode*>(animated_render_tree->source().get()); EXPECT_TRUE(animated_image_node); EXPECT_TRUE(animated_image_node); EXPECT_FLOAT_EQ(9.0f, animated_image_node->data().destination_rect.width()); @@ -202,11 +205,11 @@ scoped_refptr<AnimateNode> with_animations( new AnimateNode(animations_builder, image_node)); - scoped_refptr<Node> animated_render_tree = + scoped_refptr<AnimateNode> animated_render_tree = with_animations->Apply(base::TimeDelta::FromSeconds(3)).animated; ImageNode* animated_image_node = - dynamic_cast<ImageNode*>(animated_render_tree.get()); + dynamic_cast<ImageNode*>(animated_render_tree->source().get()); EXPECT_TRUE(animated_image_node); EXPECT_FLOAT_EQ(21.0f, animated_image_node->data().destination_rect.width()); @@ -228,11 +231,11 @@ scoped_refptr<AnimateNode> with_animations = CreateSingleAnimation(transform_node, base::Bind(&AnimateTransform)); - scoped_refptr<Node> animated_render_tree = + scoped_refptr<AnimateNode> animated_render_tree = with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated; MatrixTransformNode* animated_transform_node = - dynamic_cast<MatrixTransformNode*>(animated_render_tree.get()); + dynamic_cast<MatrixTransformNode*>(animated_render_tree->source().get()); EXPECT_TRUE(animated_transform_node); EXPECT_EQ(math::TranslateMatrix(2.0f, 2.0f), @@ -260,11 +263,11 @@ scoped_refptr<AnimateNode> with_animations = new AnimateNode(animation_builder, composition_node); - scoped_refptr<Node> animated_render_tree = + scoped_refptr<AnimateNode> animated_render_tree = with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated; CompositionNode* animated_composition_node = - dynamic_cast<CompositionNode*>(animated_render_tree.get()); + dynamic_cast<CompositionNode*>(animated_render_tree->source().get()); EXPECT_TRUE(animated_composition_node); ImageNode* animated_image_node_a = dynamic_cast<ImageNode*>( @@ -291,11 +294,11 @@ scoped_refptr<AnimateNode> with_animations = CreateSingleAnimation(transform_node, base::Bind(&AnimateTransform)); - scoped_refptr<Node> animated_render_tree = + scoped_refptr<AnimateNode> animated_render_tree = with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated; MatrixTransformNode* animated_transform_node = - dynamic_cast<MatrixTransformNode*>(animated_render_tree.get()); + dynamic_cast<MatrixTransformNode*>(animated_render_tree->source().get()); EXPECT_TRUE(animated_transform_node); // Check that the matrix transform node is animated. @@ -328,11 +331,11 @@ scoped_refptr<AnimateNode> with_animations = new AnimateNode(transform_node_b); - scoped_refptr<Node> animated_render_tree = + scoped_refptr<AnimateNode> animated_render_tree = with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated; MatrixTransformNode* animated_transform_node = - dynamic_cast<MatrixTransformNode*>(animated_render_tree.get()); + dynamic_cast<MatrixTransformNode*>(animated_render_tree->source().get()); EXPECT_TRUE(animated_transform_node); // Check that the image node is animated.
diff --git a/src/cobalt/render_tree/animations/animation_list.h b/src/cobalt/render_tree/animations/animation_list.h index 78f1e65..b51144d 100644 --- a/src/cobalt/render_tree/animations/animation_list.h +++ b/src/cobalt/render_tree/animations/animation_list.h
@@ -17,6 +17,7 @@ #include <list> +#include "base/bind.h" #include "base/callback.h" #include "base/memory/ref_counted.h" #include "base/time.h" @@ -68,6 +69,16 @@ // and ultimately add that to a AnimateNode::Builder object so that it can be // mapped to a specific TextNode that it should be applied to. typedef base::Callback<void(typename T::Builder*, base::TimeDelta)> Function; + typedef base::Callback<void(typename T::Builder*)> TimeIndependentFunction; + + // Helper function to convert from time independent function to a time + // dependent function. + static void CallTimeIndependentFunction( + const TimeIndependentFunction& time_independent_function, + typename T::Builder* builder, base::TimeDelta time) { + UNREFERENCED_PARAMETER(time); + time_independent_function.Run(builder); + } }; // The AnimationListBase is used so that we can acquire a non-template handle @@ -77,6 +88,7 @@ class AnimationListBase : public base::RefCountedThreadSafe<AnimationListBase> { public: virtual base::TimeDelta GetExpiry() const = 0; + virtual base::TimeDelta GetDependsOnTimeExpiry() const = 0; protected: virtual ~AnimationListBase() {} @@ -100,18 +112,36 @@ struct Builder { DECLARE_AS_MOVABLE(Builder); - Builder() : expiry(base::TimeDelta::Max()) {} + Builder() : expiry(base::TimeDelta::Max()), + depends_on_time_expiry(base::TimeDelta::Max()) {} explicit Builder(Moved moved) { animations.swap(moved->animations); } explicit Builder(const typename Animation<T>::Function& single_animation, base::TimeDelta expiry) - : expiry(expiry) { + : expiry(expiry), depends_on_time_expiry(expiry) { animations.push_back(single_animation); } + explicit Builder( + const typename Animation<T>::TimeIndependentFunction& single_animation, + base::TimeDelta expiry) + : expiry(expiry), + // Since this animation is time independent, we mark its + // time-dependent expiration to be already expired. + depends_on_time_expiry(-base::TimeDelta::Max()) { + // Transform from a time independent function to a time dependent + // function, since we store all functions as time dependent functions. + animations.push_back(base::Bind(Animation<T>::CallTimeIndependentFunction, + single_animation)); + } InternalList animations; // When do the animations expire? base::TimeDelta::Max() implies that they // never expire. base::TimeDelta expiry; + + // Similar to |expiry|, but only set for animations that depend on the + // time parameter passed into their animation callback functions. + // Optimizations can be performed for animations that don't depend on this. + base::TimeDelta depends_on_time_expiry; }; explicit AnimationList(typename Builder::Moved builder) : data_(builder) {} @@ -122,10 +152,17 @@ const typename Animation<T>::Function& single_animation, base::TimeDelta expiry) : data_(single_animation, expiry) {} + explicit AnimationList( + const typename Animation<T>::TimeIndependentFunction& single_animation, + base::TimeDelta expiry) + : data_(single_animation, expiry) {} const Builder& data() const { return data_; } base::TimeDelta GetExpiry() const OVERRIDE { return data_.expiry; } + base::TimeDelta GetDependsOnTimeExpiry() const OVERRIDE { + return data_.depends_on_time_expiry; + } private: ~AnimationList() OVERRIDE {}
diff --git a/src/cobalt/render_tree/blur_filter.h b/src/cobalt/render_tree/blur_filter.h index 5f6fbe7..cbe95a0 100644 --- a/src/cobalt/render_tree/blur_filter.h +++ b/src/cobalt/render_tree/blur_filter.h
@@ -29,6 +29,10 @@ // |blur_sigma| must be non-negative. explicit BlurFilter(float blur_sigma) { set_blur_sigma(blur_sigma); } + bool operator==(const BlurFilter& other) const { + return blur_sigma_ == other.blur_sigma_; + } + void set_blur_sigma(float blur_sigma) { DCHECK_LE(0.0f, blur_sigma); blur_sigma_ = blur_sigma;
diff --git a/src/cobalt/render_tree/border.h b/src/cobalt/render_tree/border.h index 761c99f..9b72706 100644 --- a/src/cobalt/render_tree/border.h +++ b/src/cobalt/render_tree/border.h
@@ -53,6 +53,11 @@ Border(const BorderSide& left, const BorderSide& right, const BorderSide& top, const BorderSide& bottom); + bool operator==(const Border& other) const { + return left == other.left && right == other.right && top == other.top && + bottom == other.bottom; + } + BorderSide left; BorderSide right; BorderSide top;
diff --git a/src/cobalt/render_tree/brush.h b/src/cobalt/render_tree/brush.h index b5be829..841ca6c 100644 --- a/src/cobalt/render_tree/brush.h +++ b/src/cobalt/render_tree/brush.h
@@ -47,6 +47,10 @@ public: explicit SolidColorBrush(const ColorRGBA& color) : color_(color) {} + bool operator==(const SolidColorBrush& other) const { + return color_ == other.color_; + } + // A type-safe branching. void Accept(BrushVisitor* visitor) const OVERRIDE; @@ -70,6 +74,10 @@ ColorStop(float position, const ColorRGBA& color) : position(position), color(color) {} + bool operator==(const ColorStop& other) const { + return position == other.position && color == other.color; + } + float position; ColorRGBA color; }; @@ -134,12 +142,20 @@ source_color, dest_color) {} + bool operator==(const LinearGradientBrush& other) const { + return data_ == other.data_; + } + struct Data { Data(); Data(const math::PointF& source, const math::PointF& dest, const ColorStopList& color_stops); Data(const math::PointF& source, const math::PointF& dest); + bool operator==(const Data& other) const { + return source_ == other.source_ && dest_ == other.dest_ && + color_stops_ == other.color_stops_; + } math::PointF source_; math::PointF dest_; @@ -203,6 +219,11 @@ float radius_y, const ColorRGBA& source_color, const ColorRGBA& dest_color); + bool operator==(const RadialGradientBrush& other) const { + return center_ == other.center_ && radius_x_ == other.radius_x_ && + radius_y_ == other.radius_y_ && color_stops_ == other.color_stops_; + } + // A type-safe branching. void Accept(BrushVisitor* visitor) const OVERRIDE; @@ -228,6 +249,36 @@ scoped_ptr<Brush> CloneBrush(const Brush* brush); +class EqualsBrushVisitor : public BrushVisitor { + public: + explicit EqualsBrushVisitor(Brush* brush_a) + : brush_a_(brush_a), result_(false) {} + + bool result() const { return result_; } + + void Visit(const SolidColorBrush* solid_color_brush) OVERRIDE { + result_ = + brush_a_->GetTypeId() == base::GetTypeId<SolidColorBrush>() && + *static_cast<const SolidColorBrush*>(brush_a_) == *solid_color_brush; + } + + void Visit(const LinearGradientBrush* linear_gradient_brush) OVERRIDE { + result_ = brush_a_->GetTypeId() == base::GetTypeId<LinearGradientBrush>() && + *static_cast<const LinearGradientBrush*>(brush_a_) == + *linear_gradient_brush; + } + + void Visit(const RadialGradientBrush* radial_gradient_brush) OVERRIDE { + result_ = brush_a_->GetTypeId() == base::GetTypeId<RadialGradientBrush>() && + *static_cast<const RadialGradientBrush*>(brush_a_) == + *radial_gradient_brush; + } + + private: + Brush* brush_a_; + bool result_; +}; + } // namespace render_tree } // namespace cobalt
diff --git a/src/cobalt/render_tree/composition_node.h b/src/cobalt/render_tree/composition_node.h index a6175a0..d39da3f 100644 --- a/src/cobalt/render_tree/composition_node.h +++ b/src/cobalt/render_tree/composition_node.h
@@ -72,6 +72,10 @@ children_.swap(moved->children_); } + bool operator==(const Builder& other) const { + return offset_ == other.offset_ && children_ == other.children_; + } + // Add a child node to the list of children we are building. When a // CompositionNode is constructed from this CompositionNode::Builder, it // will have as children all nodes who were passed into the builder via this @@ -106,6 +110,9 @@ explicit CompositionNode(Builder::Moved builder) : data_(builder), cached_bounds_(ComputeBounds()) {} + explicit CompositionNode(Builder&& builder) + : data_(builder.Pass()), cached_bounds_(ComputeBounds()) {} + void Accept(NodeVisitor* visitor) OVERRIDE; math::RectF GetBounds() const OVERRIDE;
diff --git a/src/cobalt/render_tree/filter_node.h b/src/cobalt/render_tree/filter_node.h index dc3df7c..612d76a 100644 --- a/src/cobalt/render_tree/filter_node.h +++ b/src/cobalt/render_tree/filter_node.h
@@ -51,6 +51,13 @@ math::RectF GetBounds() const; + bool operator==(const Builder& other) const { + return source == other.source && opacity_filter == other.opacity_filter && + viewport_filter == other.viewport_filter && + blur_filter == other.blur_filter && + map_to_mesh_filter == other.map_to_mesh_filter; + } + // The source tree, which will be used as the input to the filters specified // in this FilterNode. scoped_refptr<render_tree::Node> source;
diff --git a/src/cobalt/render_tree/image_node.h b/src/cobalt/render_tree/image_node.h index ccd4773..7b5f048 100644 --- a/src/cobalt/render_tree/image_node.h +++ b/src/cobalt/render_tree/image_node.h
@@ -36,6 +36,12 @@ const math::RectF& destination_rect, const math::Matrix3F& local_transform); + bool operator==(const Builder& other) const { + return source == other.source && + destination_rect == other.destination_rect && + local_transform == other.local_transform; + } + // A source of pixels. May be smaller or larger than layed out image. // The class does not own the image, it merely refers it from a resource // pool.
diff --git a/src/cobalt/render_tree/map_to_mesh_filter.h b/src/cobalt/render_tree/map_to_mesh_filter.h index 060b080..a5c248f 100644 --- a/src/cobalt/render_tree/map_to_mesh_filter.h +++ b/src/cobalt/render_tree/map_to_mesh_filter.h
@@ -47,6 +47,12 @@ public: struct Builder { Builder() {} + bool operator==(const Builder& other) const { + return resolution_matched_meshes_ == other.resolution_matched_meshes_ && + left_eye_default_mesh_ == other.left_eye_default_mesh_ && + right_eye_default_mesh_ == other.right_eye_default_mesh_; + } + void SetDefaultMeshes( const scoped_refptr<render_tree::Mesh>& left_eye_mesh, const scoped_refptr<render_tree::Mesh>& right_eye_mesh) { @@ -113,6 +119,10 @@ DCHECK(stereo_mode != kLeftRightUnadjustedTextureCoords); } + bool operator==(const MapToMeshFilter& other) const { + return stereo_mode_ == other.stereo_mode_ && data_ == other.data_; + } + StereoMode stereo_mode() const { return stereo_mode_; } // The omission of the |resolution| parameter will yield the default
diff --git a/src/cobalt/render_tree/matrix_transform_3d_node.h b/src/cobalt/render_tree/matrix_transform_3d_node.h index bbb517a..3732b68 100644 --- a/src/cobalt/render_tree/matrix_transform_3d_node.h +++ b/src/cobalt/render_tree/matrix_transform_3d_node.h
@@ -37,6 +37,10 @@ Builder(const scoped_refptr<Node>& source, const glm::mat4& transform) : source(source), transform(transform) {} + bool operator==(const Builder& other) const { + return source == other.source && transform == other.transform; + } + // The subtree that will be rendered with |transform| applied to it. scoped_refptr<Node> source;
diff --git a/src/cobalt/render_tree/matrix_transform_node.h b/src/cobalt/render_tree/matrix_transform_node.h index 3baafcc..93d9685 100644 --- a/src/cobalt/render_tree/matrix_transform_node.h +++ b/src/cobalt/render_tree/matrix_transform_node.h
@@ -34,6 +34,10 @@ Builder(const scoped_refptr<Node>& source, const math::Matrix3F& transform) : source(source), transform(transform) {} + bool operator==(const Builder& other) const { + return source == other.source && transform == other.transform; + } + // The subtree that will be rendered with |transform| applied to it. scoped_refptr<Node> source;
diff --git a/src/cobalt/render_tree/opacity_filter.h b/src/cobalt/render_tree/opacity_filter.h index 93e8718..113c909 100644 --- a/src/cobalt/render_tree/opacity_filter.h +++ b/src/cobalt/render_tree/opacity_filter.h
@@ -27,6 +27,10 @@ public: explicit OpacityFilter(float opacity) { set_opacity(opacity); } + bool operator==(const OpacityFilter& other) const { + return opacity_ == other.opacity_; + } + void set_opacity(float opacity) { DCHECK_LE(0.0f, opacity); DCHECK_GE(1.0f, opacity);
diff --git a/src/cobalt/render_tree/punch_through_video_node.h b/src/cobalt/render_tree/punch_through_video_node.h index 3683d6a..f96da9a 100644 --- a/src/cobalt/render_tree/punch_through_video_node.h +++ b/src/cobalt/render_tree/punch_through_video_node.h
@@ -45,6 +45,10 @@ Builder(const math::RectF& rect, const SetBoundsCB& set_bounds_cb) : rect(rect), set_bounds_cb(set_bounds_cb) {} + bool operator==(const Builder& other) const { + return rect == other.rect && set_bounds_cb.Equals(other.set_bounds_cb); + } + // The destination rectangle (size includes border). math::RectF rect; const SetBoundsCB set_bounds_cb;
diff --git a/src/cobalt/render_tree/rect_node.cc b/src/cobalt/render_tree/rect_node.cc index 17b046e..b628303 100644 --- a/src/cobalt/render_tree/rect_node.cc +++ b/src/cobalt/render_tree/rect_node.cc
@@ -84,6 +84,23 @@ border(moved->border.Pass()), rounded_corners(moved->rounded_corners.Pass()) {} +bool RectNode::Builder::operator==(const Builder& other) const { + bool brush_equals = !background_brush && !other.background_brush; + if (background_brush && other.background_brush) { + EqualsBrushVisitor brush_equals_visitor(background_brush.get()); + other.background_brush->Accept(&brush_equals_visitor); + brush_equals = brush_equals_visitor.result(); + } + bool border_equals = (!border && !other.border) || + (border && other.border && *border == *other.border); + bool rounded_corners_equals = (!rounded_corners && !other.rounded_corners) || + (rounded_corners && other.rounded_corners && + *rounded_corners == *other.rounded_corners); + + return rect == other.rect && brush_equals && border_equals && + rounded_corners_equals; +} + void RectNode::Accept(NodeVisitor* visitor) { visitor->Visit(this); } math::RectF RectNode::GetBounds() const { return data_.rect; }
diff --git a/src/cobalt/render_tree/rect_node.h b/src/cobalt/render_tree/rect_node.h index a257cd8..c70fd44 100644 --- a/src/cobalt/render_tree/rect_node.h +++ b/src/cobalt/render_tree/rect_node.h
@@ -52,6 +52,8 @@ explicit Builder(const Builder& other); explicit Builder(Moved moved); + bool operator==(const Builder& other) const; + // The destination rectangle (size includes border). math::RectF rect;
diff --git a/src/cobalt/render_tree/rect_shadow_node.h b/src/cobalt/render_tree/rect_shadow_node.h index 072c80b..8ff107e 100644 --- a/src/cobalt/render_tree/rect_shadow_node.h +++ b/src/cobalt/render_tree/rect_shadow_node.h
@@ -38,6 +38,11 @@ float spread) : rect(rect), shadow(shadow), inset(inset), spread(spread) {} + bool operator==(const Builder& other) const { + return rect == other.rect && rounded_corners == other.rounded_corners && + shadow == other.shadow && inset == other.inset && + spread == other.spread; + } // The destination rectangle. math::RectF rect;
diff --git a/src/cobalt/render_tree/rounded_corners.h b/src/cobalt/render_tree/rounded_corners.h index 7646f65..ef3bf38 100644 --- a/src/cobalt/render_tree/rounded_corners.h +++ b/src/cobalt/render_tree/rounded_corners.h
@@ -81,6 +81,12 @@ bottom_right(bottom_right), bottom_left(bottom_left) {} + bool operator==(const RoundedCorners& other) const { + return top_left == other.top_left && top_right == other.top_right && + bottom_right == other.bottom_right && + bottom_left == other.bottom_left; + } + RoundedCorners Inset(float left, float top, float right, float bottom) const { return RoundedCorners( top_left.Inset(left, top), top_right.Inset(right, top),
diff --git a/src/cobalt/render_tree/shadow.h b/src/cobalt/render_tree/shadow.h index caefb8a..057c30a 100644 --- a/src/cobalt/render_tree/shadow.h +++ b/src/cobalt/render_tree/shadow.h
@@ -32,6 +32,11 @@ const ColorRGBA& color) : offset(offset), blur_sigma(blur_sigma), color(color) {} + bool operator==(const Shadow& other) const { + return offset == other.offset && blur_sigma == other.blur_sigma && + color == other.color; + } + // Since the blur parameters represent standard deviations, most of the // blur is contained within 3 of them, so report that as the extent of the // blur.
diff --git a/src/cobalt/render_tree/text_node.h b/src/cobalt/render_tree/text_node.h index 2dde330..3d35b43 100644 --- a/src/cobalt/render_tree/text_node.h +++ b/src/cobalt/render_tree/text_node.h
@@ -46,6 +46,11 @@ const scoped_refptr<GlyphBuffer>& glyph_buffer, const ColorRGBA& color); + bool operator==(const Builder& other) const { + return offset == other.offset && glyph_buffer == other.glyph_buffer && + color == other.color && shadows == other.shadows; + } + math::Vector2dF offset; // All of the glyph data needed to render the text.
diff --git a/src/cobalt/render_tree/viewport_filter.h b/src/cobalt/render_tree/viewport_filter.h index 92f3d58..e4a0afa 100644 --- a/src/cobalt/render_tree/viewport_filter.h +++ b/src/cobalt/render_tree/viewport_filter.h
@@ -33,6 +33,11 @@ const RoundedCorners& rounded_corners) : viewport_(viewport), rounded_corners_(rounded_corners) {} + bool operator==(const ViewportFilter& other) const { + return viewport_ == other.viewport_ && + rounded_corners_ == other.rounded_corners_; + } + const math::RectF& viewport() const { return viewport_; } bool has_rounded_corners() const { return !!rounded_corners_; }
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texture_masked_texture_domain.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texture_masked_texture_domain.glsl index 168df28..6db8fa8 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texture_masked_texture_domain.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texture_masked_texture_domain.glsl
@@ -7,7 +7,7 @@ varying vec2 vMatrixCoord_Stage0; varying vec2 vMatrixCoord_Stage1; -void main() +void main() { vec4 output_Stage0; {
diff --git a/src/cobalt/renderer/pipeline.cc b/src/cobalt/renderer/pipeline.cc index 3f8d7dd..8827ef4 100644 --- a/src/cobalt/renderer/pipeline.cc +++ b/src/cobalt/renderer/pipeline.cc
@@ -82,18 +82,23 @@ submission_disposal_thread_("Rasterizer Submission Disposal"), submit_even_if_render_tree_is_unchanged_( submit_even_if_render_tree_is_unchanged), - last_render_animations_active_(false), + last_did_rasterize_(false), + last_animations_expired_(true), rasterize_periodic_timer_("Renderer.Rasterize.Duration", kRasterizePeriodicTimerEntriesPerUpdate, false /*enable_entry_list_c_val*/), - ALLOW_THIS_IN_INITIALIZER_LIST(rasterize_animations_interval_timer_( - "Renderer.Rasterize.AnimationsInterval", - kRasterizeAnimationsTimerMaxEntries, true /*enable_entry_list_c_val*/, - base::Bind(&Pipeline::FrameStatsOnFlushCallback, - base::Unretained(this)))), rasterize_animations_timer_("Renderer.Rasterize.Animations", kRasterizeAnimationsTimerMaxEntries, true /*enable_entry_list_c_val*/), + ALLOW_THIS_IN_INITIALIZER_LIST(rasterize_periodic_interval_timer_( + "Renderer.Rasterize.DurationInterval", + kRasterizeAnimationsTimerMaxEntries, true /*enable_entry_list_c_val*/, + base::Bind(&Pipeline::FrameStatsOnFlushCallback, + base::Unretained(this)))), + rasterize_animations_interval_timer_( + "Renderer.Rasterize.AnimationsInterval", + kRasterizeAnimationsTimerMaxEntries, + true /*enable_entry_list_c_val*/), new_render_tree_rasterize_count_( "Count.Renderer.Rasterize.NewRenderTree", 0, "Total number of new render trees rasterized."), @@ -136,7 +141,7 @@ clear_on_shutdown_mode_(clear_on_shutdown_mode), enable_fps_stdout_(options.enable_fps_stdout), enable_fps_overlay_(options.enable_fps_overlay), - fps_overlay_updated_(false) { + fps_overlay_update_pending_(false) { TRACE_EVENT0("cobalt::renderer", "Pipeline::Pipeline()"); // The actual Pipeline can be constructed from any thread, but we want // rasterizer_thread_checker_ to be associated with the rasterizer thread, @@ -170,6 +175,7 @@ // must be destroyed before we shutdown the rasterizer thread since it may // contain references to render tree nodes and resources. last_render_tree_ = NULL; + last_animated_render_tree_ = NULL; // Submit a shutdown task to the rasterizer thread so that it can shutdown // anything that must be shutdown from that thread. @@ -263,18 +269,19 @@ DCHECK(rasterizer_thread_checker_.CalledOnValidThread()); TRACE_EVENT0("cobalt::renderer", "Pipeline::RasterizeCurrentTree()"); - base::TimeTicks now = base::TimeTicks::Now(); - Submission submission = submission_queue_->GetCurrentSubmission(now); + base::TimeTicks start_rasterize_time = base::TimeTicks::Now(); + Submission submission = + submission_queue_->GetCurrentSubmission(start_rasterize_time); bool is_new_render_tree = submission.render_tree != last_render_tree_; bool has_render_tree_changed = - last_render_animations_active_ || is_new_render_tree; + !last_animations_expired_ || is_new_render_tree; // If our render tree hasn't changed from the one that was previously // rendered and it's okay on this system to not flip the display buffer // frequently, then we can just not do anything here. - if (!fps_overlay_updated_ && !submit_even_if_render_tree_is_unchanged_ && - !has_render_tree_changed) { + if (!fps_overlay_update_pending_ && + !submit_even_if_render_tree_is_unchanged_ && !has_render_tree_changed) { return; } @@ -283,77 +290,93 @@ render_tree::animations::AnimateNode* animate_node = base::polymorphic_downcast<render_tree::animations::AnimateNode*>( submission.render_tree.get()); - bool are_animations_active = animate_node->expiry() > submission.time_offset; - // If animations are going from being inactive to active, then set the c_val - // prior to starting the animation so that it's in the correct state while the - // tree is being rendered. - // Also, start the interval timer now. While the first entry only captures a - // partial interval, it's recorded to include the duration of the first - // submission. All subsequent entries will record a full interval. - if (!last_render_animations_active_ && are_animations_active) { - has_active_animations_c_val_ = true; - rasterize_animations_interval_timer_.Start(now); - } + // Rasterize the last submitted render tree. + bool did_rasterize = + RasterizeSubmissionToRenderTarget(submission, render_target_); + + bool animations_expired = animate_node->expiry() <= submission.time_offset; + + UpdateRasterizeStats(did_rasterize, animations_expired, is_new_render_tree, + start_rasterize_time, base::TimeTicks::Now()); + + last_did_rasterize_ = did_rasterize; + last_animations_expired_ = animations_expired; +} + +void Pipeline::UpdateRasterizeStats(bool did_rasterize, bool animations_expired, + bool is_new_render_tree, + base::TimeTicks start_time, + base::TimeTicks end_time) { + bool last_animations_active = + !last_animations_expired_ && last_did_rasterize_; + bool animations_active = !animations_expired && did_rasterize; // The rasterization is only timed with the periodic timer when the render // tree has changed. This ensures that the frames being timed are consistent // between platforms that submit unchanged trees and those that don't. - bool should_run_periodic_timer = has_render_tree_changed; - - // The rasterization is only timed with the animations timer when there are - // animations to track. This applies when animations were active during either - // the last rasterization or the current one. The reason for including the - // last one is that if animations have just expired, then this rasterization - // produces the final state of the animated tree. - bool should_run_animations_timer = - last_render_animations_active_ || are_animations_active; - - if (should_run_periodic_timer) { - rasterize_periodic_timer_.Start(now); - } - if (should_run_animations_timer) { - rasterize_animations_timer_.Start(now); + if (did_rasterize) { + rasterize_periodic_timer_.Start(start_time); + rasterize_periodic_timer_.Stop(end_time); } - // Rasterize the last submitted render tree. - RasterizeSubmissionToRenderTarget(submission, render_target_); + if (last_animations_active || animations_active) { + // The rasterization is only timed with the animations timer when there are + // animations to track. This applies when animations were active during + // either the last rasterization or the current one. The reason for + // including the last one is that if animations have just expired, then this + // rasterization produces the final state of the animated tree. + if (did_rasterize) { + rasterize_animations_timer_.Start(start_time); + rasterize_animations_timer_.Stop(end_time); + } - // Update now with the post-submission time. - now = base::TimeTicks::Now(); + // If animations are going from being inactive to active, then set the c_val + // prior to starting the animation so that it's in the correct state while + // the tree is being rendered. + // Also, start the interval timer now. While the first entry only captures a + // partial interval, it's recorded to include the duration of the first + // submission. All subsequent entries will record a full interval. + if (!last_animations_active && animations_active) { + has_active_animations_c_val_ = true; + rasterize_periodic_interval_timer_.Start(start_time); + rasterize_animations_interval_timer_.Start(start_time); + } - if (should_run_periodic_timer) { - rasterize_periodic_timer_.Stop(now); - } - if (should_run_animations_timer) { - rasterize_animations_interval_timer_.Stop(now); - rasterize_animations_timer_.Stop(now); + if (!did_rasterize) { + // If we didn't actually rasterize anything, don't count this sample. + rasterize_periodic_interval_timer_.Cancel(); + rasterize_animations_interval_timer_.Cancel(); + } else { + rasterize_periodic_interval_timer_.Stop(end_time); + rasterize_animations_interval_timer_.Stop(end_time); + } + // If animations are active, then they are guaranteed at least one more // interval. Start the timer to record its duration. - if (are_animations_active) { - rasterize_animations_interval_timer_.Start(now); + if (animations_active) { + rasterize_periodic_interval_timer_.Start(end_time); + rasterize_animations_interval_timer_.Start(end_time); + } + + // Check for if the animations are starting or ending. + if (!last_animations_active && animations_active) { + animations_start_time_ = end_time.ToInternalValue(); + } else if (last_animations_active && !animations_active) { + animations_end_time_ = end_time.ToInternalValue(); + has_active_animations_c_val_ = false; + rasterize_animations_interval_timer_.Flush(); + rasterize_animations_timer_.Flush(); } } if (is_new_render_tree) { ++new_render_tree_rasterize_count_; - new_render_tree_rasterize_time_ = now.ToInternalValue(); + new_render_tree_rasterize_time_ = end_time.ToInternalValue(); } - - // Check for if the animations are starting or ending. - if (!last_render_animations_active_ && are_animations_active) { - animations_start_time_ = now.ToInternalValue(); - } else if (last_render_animations_active_ && !are_animations_active) { - animations_end_time_ = now.ToInternalValue(); - has_active_animations_c_val_ = false; - rasterize_animations_interval_timer_.Flush(); - rasterize_animations_timer_.Flush(); - } - - last_render_animations_active_ = are_animations_active; } -void Pipeline::RasterizeSubmissionToRenderTarget( +bool Pipeline::RasterizeSubmissionToRenderTarget( const Submission& submission, const scoped_refptr<backend::RenderTarget>& render_target) { TRACE_EVENT0("cobalt::renderer", @@ -364,14 +387,17 @@ // |previous_animated_area_|. if (submission.render_tree != last_render_tree_) { last_render_tree_ = submission.render_tree; + last_animated_render_tree_ = NULL; previous_animated_area_ = base::nullopt; last_render_time_ = base::nullopt; } // Animate the render tree using the submitted animations. render_tree::animations::AnimateNode* animate_node = - base::polymorphic_downcast<render_tree::animations::AnimateNode*>( - submission.render_tree.get()); + last_animated_render_tree_ + ? last_animated_render_tree_.get() + : base::polymorphic_downcast<render_tree::animations::AnimateNode*>( + submission.render_tree.get()); // Some animations require a GL graphics context to be current. Specifically, // a call to SbPlayerGetCurrentFrame() may be made to get the current video @@ -381,6 +407,13 @@ render_tree::animations::AnimateNode::AnimateResults results = animate_node->Apply(submission.time_offset); + if (results.animated == last_animated_render_tree_ && + !submit_even_if_render_tree_is_unchanged_ && + !fps_overlay_update_pending_) { + return false; + } + last_animated_render_tree_ = results.animated; + // Calculate a bounding box around the active animations. Union it with the // bounding box around active animations from the previous frame, and we get // a scissor rectangle marking the dirty regions of the screen. @@ -393,9 +426,11 @@ } previous_animated_area_ = rounded_bounds; - scoped_refptr<render_tree::Node> submit_tree = results.animated; + scoped_refptr<render_tree::Node> submit_tree = results.animated->source(); if (enable_fps_overlay_ && fps_overlay_) { - submit_tree = fps_overlay_->AnnotateRenderTreeWithOverlay(results.animated); + submit_tree = + fps_overlay_->AnnotateRenderTreeWithOverlay(results.animated->source()); + fps_overlay_update_pending_ = false; } // Rasterize the animated render tree. @@ -408,6 +443,8 @@ } last_render_time_ = submission.time_offset; + + return true; } void Pipeline::InitializeRasterizerThread( @@ -499,7 +536,8 @@ render_tree::animations::AnimateNode::AnimateResults results = animate_node->Apply(submission.time_offset); - std::string tree_dump = render_tree::DumpRenderTreeToString(results.animated); + std::string tree_dump = + render_tree::DumpRenderTreeToString(results.animated->source()); if (message.empty() || message == "undefined") { // If no filename was specified, send output to the console. LOG(INFO) << tree_dump.c_str(); @@ -533,6 +571,7 @@ } enable_fps_overlay_ = !enable_fps_overlay_; + fps_overlay_update_pending_ = enable_fps_overlay_; } #endif // #if defined(ENABLE_DEBUG_CONSOLE) @@ -575,7 +614,7 @@ } fps_overlay_->UpdateOverlay(flush_results); - fps_overlay_updated_ = true; + fps_overlay_update_pending_ = true; } if (enable_fps_stdout_) {
diff --git a/src/cobalt/renderer/pipeline.h b/src/cobalt/renderer/pipeline.h index e304e59..49308c9 100644 --- a/src/cobalt/renderer/pipeline.h +++ b/src/cobalt/renderer/pipeline.h
@@ -118,10 +118,17 @@ // Rasterize the animated |render_tree_submission| to |render_target|, // applying the time_offset in the submission to the animations. - void RasterizeSubmissionToRenderTarget( + // Returns true only if a rasterization actually took place. + bool RasterizeSubmissionToRenderTarget( const Submission& render_tree_submission, const scoped_refptr<backend::RenderTarget>& render_target); + // Updates the rasterizer timer stats according to the |start_time| and + // |end_time| of the most recent rasterize call. + void UpdateRasterizeStats(bool did_rasterize, bool are_animations_active, + bool is_new_render_tree, base::TimeTicks start_time, + base::TimeTicks end_time); + // This method is executed on the rasterizer thread and is responsible for // constructing the rasterizer. void InitializeRasterizerThread( @@ -193,6 +200,10 @@ // Keeps track of the last rendered animated render tree. scoped_refptr<render_tree::Node> last_render_tree_; + + scoped_refptr<render_tree::animations::AnimateNode> + last_animated_render_tree_; + // Keeps track of the area of the screen that animations previously existed // within, so that we can know which regions of the screens would be dirty // next frame. @@ -201,22 +212,31 @@ base::optional<base::TimeDelta> last_render_time_; // Keep track of whether the last rendered tree had active animations. This // allows us to skip rasterizing that render tree if we see it again and it - // did not have active animations. - bool last_render_animations_active_; + // did have expired animations. + bool last_animations_expired_; + + // Did a rasterization take place in the last frame? + bool last_did_rasterize_; // Timer tracking the amount of time spent in // |RasterizeSubmissionToRenderTarget| when the render tree has changed. // The tracking is flushed when the max count is hit. base::CValCollectionTimerStats<base::CValPublic> rasterize_periodic_timer_; + // Timer tracking the amount of time spent in + // |RasterizeSubmissionToRenderTarget| while animations are active. The + // tracking is flushed when the animations expire. + base::CValCollectionTimerStats<base::CValDebug> rasterize_animations_timer_; + + // Accumulates render tree rasterization interval times but does not flush + // them until the maximum number of samples is gathered. + base::CValCollectionTimerStats<base::CValPublic> + rasterize_periodic_interval_timer_; + // Timer tracking the amount of time between calls to // |RasterizeSubmissionToRenderTarget| while animations are active. The // tracking is flushed when the animations expire. base::CValCollectionTimerStats<base::CValPublic> rasterize_animations_interval_timer_; - // Timer tracking the amount of time spent in - // |RasterizeSubmissionToRenderTarget| while animations are active. The - // tracking is flushed when the animations expire. - base::CValCollectionTimerStats<base::CValDebug> rasterize_animations_timer_; // The total number of new render trees that have been rasterized. base::CVal<int> new_render_tree_rasterize_count_; @@ -254,7 +274,9 @@ bool enable_fps_overlay_; base::optional<FpsOverlay> fps_overlay_; - bool fps_overlay_updated_; + + // True if the overlay has been updated and it needs to be re-rasterized. + bool fps_overlay_update_pending_; }; } // namespace renderer
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.cc b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.cc index fcabd2d..3261089 100644 --- a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.cc +++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.cc
@@ -42,41 +42,55 @@ void SetBlurRRectUniforms(const ShaderFragmentColorBlurRrects& shader, math::RectF rect, render_tree::RoundedCorners corners, float sigma) { - // Ensure a minimum radius for each corner to avoid division by zero. - const float kMinSize = 0.01f; + const float kBlurExtentInPixels = kBlurExtentInSigmas * sigma; + const float kOffsetScale = kBlurDistance / kBlurExtentInPixels; + // Ensure a minimum radius for each corner to avoid division by zero. + // NOTE: The rounded rect is already specified in terms of sigma. + const float kMinSize = 0.01f * kOffsetScale; rect.Outset(kMinSize, kMinSize); corners = corners.Inset(-kMinSize, -kMinSize, -kMinSize, -kMinSize); corners = corners.Normalize(rect); - // Specify the blur extent size and the (min.y, max.y) for the rect. - const float kBlurExtentInPixels = kBlurExtentInSigmas * sigma; - GL_CALL(glUniform3f(shader.u_blur_extent(), - kBlurExtentInPixels, rect.y(), rect.bottom())); + // A normalized rounded rect should have at least one Y value which the + // corners do not cross. + const float kCenterY = + 0.5f * (rect.y() + std::max(corners.top_left.vertical, + corners.top_right.vertical)) + + 0.5f * (rect.bottom() - std::max(corners.bottom_left.vertical, + corners.bottom_right.vertical)); - // Set the "start" and "scale" values so that (pos - start) * scale is in the - // first quadrant and normalized. Then specify "radius" so that normalized * - // radius + start specifies a point on the respective corner. - GL_CALL(glUniform4f(shader.u_spread_start_x(), + // The blur extent is (blur_size.y, rect_min.y, rect_max.y, rect_center.y). + GL_CALL(glUniform4f(shader.u_blur_extent(), + kBlurExtentInPixels * kOffsetScale, + rect.y(), rect.bottom(), kCenterY)); + + // The blur rounded rect is split into top and bottom halves. + // The "start" values represent (left_start.xy, right_start.xy). + // The "scale" values represent (left_radius.x, 1 / left_radius.y, + // right_radius.x, 1 / right_radius.y). The sign of the scale value helps + // to translate between position and corner offset values, where the corner + // offset is positive if the position is inside the rounded corner. + GL_CALL(glUniform4f(shader.u_blur_start_top(), rect.x() + corners.top_left.horizontal, - rect.right() - corners.top_right.horizontal, - rect.x() + corners.bottom_left.horizontal, - rect.right() - corners.bottom_right.horizontal)); - GL_CALL(glUniform4f(shader.u_spread_start_y(), rect.y() + corners.top_left.vertical, - rect.y() + corners.top_right.vertical, + rect.right() - corners.top_right.horizontal, + rect.y() + corners.top_right.vertical)); + GL_CALL(glUniform4f(shader.u_blur_start_bottom(), + rect.x() + corners.bottom_left.horizontal, rect.bottom() - corners.bottom_left.vertical, + rect.right() - corners.bottom_right.horizontal, rect.bottom() - corners.bottom_right.vertical)); - GL_CALL(glUniform4f(shader.u_spread_scale_y(), - -1.0f / corners.top_left.vertical, - -1.0f / corners.top_right.vertical, - 1.0f / corners.bottom_left.vertical, - 1.0f / corners.bottom_right.vertical)); - GL_CALL(glUniform4f(shader.u_spread_radius_x(), + GL_CALL(glUniform4f(shader.u_blur_scale_top(), -corners.top_left.horizontal, + -1.0f / corners.top_left.vertical, corners.top_right.horizontal, + -1.0f / corners.top_right.vertical)); + GL_CALL(glUniform4f(shader.u_blur_scale_bottom(), -corners.bottom_left.horizontal, - corners.bottom_right.horizontal)); + 1.0f / corners.bottom_left.vertical, + corners.bottom_right.horizontal, + 1.0f / corners.bottom_right.vertical)); } } // namespace @@ -89,10 +103,12 @@ } DrawRectShadowBlur::VertexAttributesRound::VertexAttributesRound( - float x, float y, const RCorner& init) { + float x, float y, float offset_scale, const RCorner& rcorner) { position[0] = x; position[1] = y; - rcorner_scissor = RCorner(position, init); + offset[0] = x * offset_scale; + offset[1] = y * offset_scale; + rcorner_scissor = RCorner(position, rcorner); } DrawRectShadowBlur::DrawRectShadowBlur(GraphicsState* graphics_state, @@ -117,12 +133,10 @@ OptionalRoundedCorners scaled_base_corners(base_corners); if (scaled_base_corners) { scaled_base_corners = scaled_base_corners->Scale(scale.x(), scale.y()); - scaled_base_corners = scaled_base_corners->Normalize(scaled_base_rect); } spread_rect_.Scale(scale.x(), scale.y()); if (spread_corners_) { spread_corners_ = spread_corners_->Scale(scale.x(), scale.y()); - spread_corners_ = spread_corners_->Normalize(spread_rect_); } // The blur algorithms used by the shaders do not produce good results with @@ -168,13 +182,6 @@ SetupVertexShader(graphics_state, program->GetVertexShader()); SetFragmentUniforms(program->GetFragmentShader().u_color(), program->GetFragmentShader().u_scale_add()); - float sigma_scale = kBlurDistance / (kBlurExtentInSigmas * blur_sigma_); - GL_CALL(glUniform2f(program->GetFragmentShader().u_sigma_scale(), - sigma_scale, sigma_scale)); - // Pre-calculate the scale values to calculate the normalized gaussian. - GL_CALL(glUniform2f(program->GetFragmentShader().u_gaussian_scale(), - -1.0f / (2.0f * blur_sigma_ * blur_sigma_), - 1.0f / (kSqrt2 * kSqrtPi * blur_sigma_))); SetBlurRRectUniforms(program->GetFragmentShader(), spread_rect_, *spread_corners_, blur_sigma_); GL_CALL(glDrawElements(GL_TRIANGLES, indices_.size(), GL_UNSIGNED_SHORT, @@ -234,6 +241,10 @@ sizeof(VertexAttributesRound), vertex_buffer_ + offsetof(VertexAttributesRound, position)); graphics_state->VertexAttribPointer( + shader.a_offset(), 2, GL_FLOAT, GL_FALSE, + sizeof(VertexAttributesRound), vertex_buffer_ + + offsetof(VertexAttributesRound, offset)); + graphics_state->VertexAttribPointer( shader.a_rcorner(), 4, GL_FLOAT, GL_FALSE, sizeof(VertexAttributesRound), vertex_buffer_ + offsetof(VertexAttributesRound, rcorner_scissor)); @@ -310,10 +321,9 @@ void DrawRectShadowBlur::SetGeometry(GraphicsState* graphics_state, const math::RectF& inner_rect, const math::RectF& outer_rect) { - // Express offset in terms of blur sigma for the shader. + // The spread rect and offsets should be expressed in terms of sigma for the + // shader. float offset_scale = kBlurDistance / (kBlurExtentInSigmas * blur_sigma_); - - // The spread rect should also be expressed in terms of sigma. spread_rect_.Scale(offset_scale, offset_scale); // The box shadow is a triangle strip covering the area between outer rect @@ -360,21 +370,15 @@ void DrawRectShadowBlur::SetGeometry(GraphicsState* graphics_state, const RRectAttributes (&rrect)[8]) { + // The spread rect and offsets should be expressed in terms of sigma for the + // shader. + float offset_scale = kBlurDistance / (kBlurExtentInSigmas * blur_sigma_); + spread_rect_.Scale(offset_scale, offset_scale); + *spread_corners_ = spread_corners_->Scale(offset_scale, offset_scale); + // The shadowed area is already split into quads. for (int i = 0; i < arraysize(rrect); ++i) { - uint16_t vert = static_cast<uint16_t>(attributes_round_.size()); - const math::RectF& bounds = rrect[i].bounds; - const RCorner& rcorner = rrect[i].rcorner; - attributes_round_.emplace_back(bounds.x(), bounds.y(), rcorner); - attributes_round_.emplace_back(bounds.right(), bounds.y(), rcorner); - attributes_round_.emplace_back(bounds.x(), bounds.bottom(), rcorner); - attributes_round_.emplace_back(bounds.right(), bounds.bottom(), rcorner); - indices_.emplace_back(vert); - indices_.emplace_back(vert + 1); - indices_.emplace_back(vert + 2); - indices_.emplace_back(vert + 1); - indices_.emplace_back(vert + 2); - indices_.emplace_back(vert + 3); + AddQuad(rrect[i].bounds, offset_scale, rrect[i].rcorner); } graphics_state->ReserveVertexData( @@ -385,6 +389,12 @@ void DrawRectShadowBlur::SetGeometry(GraphicsState* graphics_state, const RRectAttributes (&rrect_outer)[4], const RRectAttributes (&rrect_inner)[8]) { + // The spread rect and offsets should be expressed in terms of sigma for the + // shader. + float offset_scale = kBlurDistance / (kBlurExtentInSigmas * blur_sigma_); + spread_rect_.Scale(offset_scale, offset_scale); + *spread_corners_ = spread_corners_->Scale(offset_scale, offset_scale); + // Draw the area between the inner rect and outer rect using the outer rect's // rounded corners. The inner quads already exclude the inscribed rectangle. for (int i = 0; i < arraysize(rrect_inner); ++i) { @@ -392,19 +402,7 @@ math::RectF rect = math::IntersectRects( rrect_inner[i].bounds, rrect_outer[o].bounds); if (!rect.IsEmpty()) { - // Use two triangles to draw the intersection. - const RCorner& rcorner = rrect_outer[o].rcorner; - uint16_t vert = static_cast<uint16_t>(attributes_round_.size()); - attributes_round_.emplace_back(rect.x(), rect.y(), rcorner); - attributes_round_.emplace_back(rect.right(), rect.y(), rcorner); - attributes_round_.emplace_back(rect.x(), rect.bottom(), rcorner); - attributes_round_.emplace_back(rect.right(), rect.bottom(), rcorner); - indices_.emplace_back(vert); - indices_.emplace_back(vert + 1); - indices_.emplace_back(vert + 2); - indices_.emplace_back(vert + 1); - indices_.emplace_back(vert + 2); - indices_.emplace_back(vert + 3); + AddQuad(rect, offset_scale, rrect_outer[o].rcorner); } } } @@ -414,6 +412,21 @@ graphics_state->ReserveVertexIndices(indices_.size()); } +void DrawRectShadowBlur::AddQuad(const math::RectF& rect, float scale, + const RCorner& rcorner) { + uint16_t vert = static_cast<uint16_t>(attributes_round_.size()); + attributes_round_.emplace_back(rect.x(), rect.y(), scale, rcorner); + attributes_round_.emplace_back(rect.right(), rect.y(), scale, rcorner); + attributes_round_.emplace_back(rect.x(), rect.bottom(), scale, rcorner); + attributes_round_.emplace_back(rect.right(), rect.bottom(), scale, rcorner); + indices_.emplace_back(vert); + indices_.emplace_back(vert + 1); + indices_.emplace_back(vert + 2); + indices_.emplace_back(vert + 1); + indices_.emplace_back(vert + 2); + indices_.emplace_back(vert + 3); +} + } // namespace egl } // namespace rasterizer } // namespace renderer
diff --git a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.h b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.h index 107af85..6ba4e85 100644 --- a/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.h +++ b/src/cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.h
@@ -73,8 +73,10 @@ }; struct VertexAttributesRound { - VertexAttributesRound(float x, float y, const RCorner& init); + VertexAttributesRound(float x, float y, float offset_scale, + const RCorner& rcorner); float position[2]; + float offset[2]; RCorner rcorner_scissor; }; @@ -95,6 +97,7 @@ void SetGeometry(GraphicsState* graphics_state, const RRectAttributes (&rrect_outer)[4], const RRectAttributes (&rrect_inner)[8]); + void AddQuad(const math::RectF& rect, float scale, const RCorner& rcorner); math::RectF spread_rect_; OptionalRoundedCorners spread_corners_;
diff --git a/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc b/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc index bcd8e49..6043afa 100644 --- a/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc +++ b/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.cc
@@ -300,6 +300,8 @@ purge_skia_font_caches_on_destruction)) { } +HardwareRasterizer::~HardwareRasterizer() {} + void HardwareRasterizer::Submit( const scoped_refptr<render_tree::Node>& render_tree, const scoped_refptr<backend::RenderTarget>& render_target,
diff --git a/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.h b/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.h index cd7eb6e..fd3c144 100644 --- a/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.h +++ b/src/cobalt/renderer/rasterizer/egl/hardware_rasterizer.h
@@ -52,6 +52,7 @@ int scratch_surface_cache_size_in_bytes, int offscreen_target_cache_size_in_bytes, bool purge_skia_font_caches_on_destruction); + virtual ~HardwareRasterizer(); void Submit(const scoped_refptr<render_tree::Node>& render_tree, const scoped_refptr<backend::RenderTarget>& render_target,
diff --git a/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc b/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc index 72b7da7..677f941 100644 --- a/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc +++ b/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
@@ -64,8 +64,12 @@ } bool IsOnlyScaleAndTranslate(const math::Matrix3F& matrix) { - return matrix(2, 0) == 0 && matrix(2, 1) == 0 && matrix(2, 2) == 1 && - matrix(0, 1) == 0 && matrix(1, 0) == 0; + const float kEpsilon = 0.0001f; + return std::abs(matrix(0, 1)) < kEpsilon && + std::abs(matrix(1, 0)) < kEpsilon && + std::abs(matrix(2, 0)) < kEpsilon && + std::abs(matrix(2, 1)) < kEpsilon && + std::abs(matrix(2, 2) - 1.0f) < kEpsilon; } math::Matrix3F GetTexcoordTransform( @@ -672,15 +676,28 @@ // Get a suitable cache of the render tree node if one exists, or allocate // a new offscreen target if possible. The OffscreenTargetErrorFunction will // determine whether any caches are fit for use. - *out_content_cached = offscreen_target_manager_->GetCachedOffscreenTarget( - node, base::Bind(&OffscreenTargetErrorFunction, mapped_bounds), - out_target_info); - if (!(*out_content_cached)) { - offscreen_target_manager_->AllocateOffscreenTarget(node, - content_size, mapped_bounds, out_target_info); + + // Do not cache rotating nodes since these will result in inappropriate + // reuse of offscreen targets. Transforms that are rotations of angles in + // the first quadrant will produce the same mapped rect sizes as angles in + // the other 3 quadrants. Also avoid caching reflections. + bool allow_caching = IsOnlyScaleAndTranslate(draw_state_.transform) && + draw_state_.transform(0, 0) > 0.0f && + draw_state_.transform(1, 1) > 0.0f; + if (allow_caching) { + *out_content_cached = offscreen_target_manager_->GetCachedOffscreenTarget( + node, base::Bind(&OffscreenTargetErrorFunction, mapped_bounds), + out_target_info); + if (!(*out_content_cached)) { + offscreen_target_manager_->AllocateOffscreenTarget(node, + content_size, mapped_bounds, out_target_info); + } else { + // Maintain the size of the cached contents to avoid scaling artifacts. + content_size = out_target_info->region.size(); + } } else { - // Maintain the size of the cached contents to avoid scaling artifacts. - content_size = out_target_info->region.size(); + *out_content_cached = false; + out_target_info->framebuffer = nullptr; } // If no offscreen target could be allocated, then the render tree node will
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur_rrects.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur_rrects.glsl index efc8747..ffbee99 100644 --- a/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur_rrects.glsl +++ b/src/cobalt/renderer/rasterizer/egl/shaders/fragment_color_blur_rrects.glsl
@@ -14,26 +14,19 @@ precision mediump float; -// The rounded spread rect is represented in a way to optimize calculation of -// the extents. Each element of a vec4 represents a corner's value -- order -// is top left, top right, bottom left, bottom right. Extents for each corner -// can be calculated as: -// extents_x = start_x + radius_x * sqrt(1 - scaled_y^2) where -// scaled_y = clamp((pos.yyyy - start_y) * scale_y, 0.0, 1.0) -// To simplify handling left vs right and top vs bottom corners, the sign of -// scale_y and radius_x handles negation as needed. -uniform vec4 u_spread_start_x; -uniform vec4 u_spread_start_y; -uniform vec4 u_spread_scale_y; -uniform vec4 u_spread_radius_x; +// The blur rounded rect is split into top and bottom halves. +// The "start" values represent (left_start.xy, right_start.xy). +// The "scale" values represent (left_radius.x, 1 / left_radius.y, +// right_radius.x, 1 / right_radius.y). The sign of the scale value helps +// to translate between position and corner offset values, where the corner +// offset is positive if the position is inside the rounded corner. +uniform vec4 u_blur_start_top; +uniform vec4 u_blur_start_bottom; +uniform vec4 u_blur_scale_top; +uniform vec4 u_blur_scale_bottom; -// The blur extent specifies (3 * sigma, min_rect_y, max_rect_y). This is used -// to clamp the interval over which integration should be evaluated. -uniform vec3 u_blur_extent; - -// The gaussian scale uniform is used to simplify calculation of the gaussian -// function at a particular point. -uniform vec2 u_gaussian_scale; +// The blur extent specifies (blur_size, min_rect_y, max_rect_y, center_rect_y). +uniform vec4 u_blur_extent; // The scale_add uniform is used to switch the shader between generating // outset shadows and inset shadows. It impacts the shadow gradient and @@ -44,39 +37,25 @@ uniform vec4 u_color; -// Blur calculations happen in terms in sigma distances. Use sigma_scale to -// translate pixel distances into sigma distances. -uniform vec2 u_sigma_scale; - varying vec2 v_offset; varying vec4 v_rcorner; #include "function_is_outside_rcorner.inc" #include "function_gaussian_integral.inc" -vec2 GetXExtents(float y) { - // Use x^2 / a^2 + y^2 / b^2 = 1 to solve for the x value of each rounded - // corner at the given y. - vec4 scaled = clamp((y - u_spread_start_y) * u_spread_scale_y, 0.0, 1.0); - vec4 root = sqrt(1.0 - scaled * scaled); - vec4 extent = u_spread_start_x + u_spread_radius_x * root; - - // If the y value was before a corner started, then the calculated extent - // would equal the unrounded rectangle's extents (since negative values were - // clamped to 0 in the above calculation). So smaller extents (i.e. extents - // closer to the rectangle center), represent the relevant corners' extents. - return vec2(max(extent.x, extent.z), min(extent.y, extent.w)); -} - float GetXBlur(float x, float y) { - // Get the integral over the interval occupied by the rectangle. - vec2 pos = (GetXExtents(y) - x) * u_sigma_scale; - return GaussianIntegral(pos); -} + // Solve for X of the rounded corners at the given Y based on the equation + // for an ellipse: x^2 / a^2 + y^2 / b^2 = 1. + vec4 corner_start = + (y < u_blur_extent.w) ? u_blur_start_top : u_blur_start_bottom; + vec4 corner_scale = + (y < u_blur_extent.w) ? u_blur_scale_top : u_blur_scale_bottom; + vec2 scaled = clamp((y - corner_start.yw) * corner_scale.yw, 0.0, 1.0); + vec2 root = sqrt(1.0 - scaled * scaled); + vec2 extent_x = corner_start.xz + corner_scale.xz * root; -vec3 GetGaussian(vec3 offset) { - // Evaluate the gaussian at the given offsets. - return exp(offset * offset * u_gaussian_scale.x); + // Get the integral over the interval occupied by the rectangle. + return GaussianIntegral(extent_x - x); } float GetBlur(vec2 pos) { @@ -111,12 +90,13 @@ vec3 xblur2 = vec3(GetXBlur(pos.x, pos2.x), GetXBlur(pos.x, pos2.y), GetXBlur(pos.x, pos2.z)); - vec3 yblur1 = GetGaussian(offset1) * weight; - vec3 yblur2 = GetGaussian(offset2) * weight; + vec3 yblur1 = exp(-offset1 * offset1) * weight; + vec3 yblur2 = exp(-offset2 * offset2) * weight; - // Since each yblur value should be scaled by u_gaussian_scale.y, save some - // cycles and multiply the sum by it. - return (dot(xblur1, yblur1) + dot(xblur2, yblur2)) * u_gaussian_scale.y; + // Since each yblur value should be normalized by kNormalizeGaussian, just + // scale the sum by it. + const float kNormalizeGaussian = 0.564189584; // 1 / sqrt(pi) + return (dot(xblur1, yblur1) + dot(xblur2, yblur2)) * kNormalizeGaussian; } void main() {
diff --git a/src/cobalt/renderer/rasterizer/egl/shaders/vertex_offset_rcorner.glsl b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_offset_rcorner.glsl index 3d9ff46..fbcead7 100644 --- a/src/cobalt/renderer/rasterizer/egl/shaders/vertex_offset_rcorner.glsl +++ b/src/cobalt/renderer/rasterizer/egl/shaders/vertex_offset_rcorner.glsl
@@ -15,6 +15,7 @@ uniform vec4 u_clip_adjustment; uniform mat3 u_view_matrix; attribute vec2 a_position; +attribute vec2 a_offset; attribute vec4 a_rcorner; varying vec2 v_offset; varying vec4 v_rcorner; @@ -23,6 +24,6 @@ vec3 pos2d = u_view_matrix * vec3(a_position, 1); gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy + u_clip_adjustment.zw, 0, pos2d.z); - v_offset = a_position; + v_offset = a_offset; v_rcorner = a_rcorner; }
diff --git a/src/cobalt/renderer/rasterizer/lib/external_rasterizer.cc b/src/cobalt/renderer/rasterizer/lib/external_rasterizer.cc index e1cb238..00dc1ee 100644 --- a/src/cobalt/renderer/rasterizer/lib/external_rasterizer.cc +++ b/src/cobalt/renderer/rasterizer/lib/external_rasterizer.cc
@@ -27,6 +27,7 @@ #include "cobalt/renderer/backend/egl/graphics_context.h" #include "cobalt/renderer/backend/egl/render_target.h" #include "cobalt/renderer/rasterizer/common/find_node.h" +#include "cobalt/renderer/rasterizer/egl/hardware_rasterizer.h" #include "cobalt/renderer/rasterizer/lib/exported/graphics.h" #include "cobalt/renderer/rasterizer/lib/exported/video.h" #include "cobalt/renderer/rasterizer/skia/hardware_image.h" @@ -154,8 +155,12 @@ backend::GraphicsContextEGL* graphics_context_; +#if defined(COBALT_FORCE_DIRECT_GLES_RASTERIZER) + egl::HardwareRasterizer hardware_rasterizer_; +#else skia::HardwareRasterizer hardware_rasterizer_; - skia::HardwareRasterizer::Options options_; +#endif + Rasterizer::Options options_; // The main offscreen render target to use when rendering UI or rectangular // video. @@ -178,7 +183,7 @@ int skia_atlas_width, int skia_atlas_height, int skia_cache_size_in_bytes, int scratch_surface_cache_size_in_bytes, - int surface_cache_size_in_bytes, + int rasterizer_gpu_cache_size_in_bytes, bool purge_skia_font_caches_on_destruction) : graphics_context_( base::polymorphic_downcast<backend::GraphicsContextEGL*>( @@ -186,11 +191,12 @@ hardware_rasterizer_( graphics_context, skia_atlas_width, skia_atlas_height, skia_cache_size_in_bytes, scratch_surface_cache_size_in_bytes, - surface_cache_size_in_bytes, purge_skia_font_caches_on_destruction), + rasterizer_gpu_cache_size_in_bytes, + purge_skia_font_caches_on_destruction), video_projection_type_(kCbLibVideoProjectionTypeNone), video_stereo_mode_(render_tree::StereoMode::kMono), video_texture_rgb_(0) { - options_.flags = skia::HardwareRasterizer::kSubmitFlags_Clear; + options_.flags = Rasterizer::kSubmitFlags_Clear; graphics_context_->MakeCurrent(); // TODO: Import the correct size for this and any other textures from the lib @@ -397,12 +403,14 @@ ExternalRasterizer::ExternalRasterizer( backend::GraphicsContext* graphics_context, int skia_atlas_width, int skia_atlas_height, int skia_cache_size_in_bytes, - int scratch_surface_cache_size_in_bytes, int surface_cache_size_in_bytes, + int scratch_surface_cache_size_in_bytes, + int rasterizer_gpu_cache_size_in_bytes, bool purge_skia_font_caches_on_destruction) : impl_(new Impl( graphics_context, skia_atlas_width, skia_atlas_height, skia_cache_size_in_bytes, scratch_surface_cache_size_in_bytes, - surface_cache_size_in_bytes, purge_skia_font_caches_on_destruction)) { + rasterizer_gpu_cache_size_in_bytes, + purge_skia_font_caches_on_destruction)) { } ExternalRasterizer::~ExternalRasterizer() {}
diff --git a/src/cobalt/renderer/rasterizer/lib/external_rasterizer.h b/src/cobalt/renderer/rasterizer/lib/external_rasterizer.h index 9ad1fd0..d2f32e6 100644 --- a/src/cobalt/renderer/rasterizer/lib/external_rasterizer.h +++ b/src/cobalt/renderer/rasterizer/lib/external_rasterizer.h
@@ -37,7 +37,7 @@ int skia_atlas_width, int skia_atlas_height, int skia_cache_size_in_bytes, int scratch_surface_cache_size_in_bytes, - int surface_cache_size_in_bytes, + int rasterizer_gpu_cache_size_in_bytes, bool purge_skia_font_caches_on_destruction); virtual ~ExternalRasterizer();
diff --git a/src/cobalt/renderer/rasterizer/lib/lib.gyp b/src/cobalt/renderer/rasterizer/lib/lib.gyp index 0b5a2c5..53a5e51 100644 --- a/src/cobalt/renderer/rasterizer/lib/lib.gyp +++ b/src/cobalt/renderer/rasterizer/lib/lib.gyp
@@ -27,6 +27,7 @@ 'dependencies': [ '<(DEPTH)/base/base.gyp:base', '<(DEPTH)/cobalt/render_tree/render_tree.gyp:render_tree', + '<(DEPTH)/cobalt/renderer/rasterizer/egl/rasterizer.gyp:hardware_rasterizer', '<(DEPTH)/cobalt/renderer/rasterizer/skia/common.gyp:common', '<(DEPTH)/cobalt/renderer/rasterizer/skia/rasterizer.gyp:hardware_rasterizer', '<(DEPTH)/cobalt/renderer/rasterizer/skia/skia/skia.gyp:skia',
diff --git a/src/cobalt/renderer/rasterizer/lib/renderer_module_default_options_lib.cc b/src/cobalt/renderer/rasterizer/lib/renderer_module_default_options_lib.cc index f98baf7..2b2ea4b 100644 --- a/src/cobalt/renderer/rasterizer/lib/renderer_module_default_options_lib.cc +++ b/src/cobalt/renderer/rasterizer/lib/renderer_module_default_options_lib.cc
@@ -28,7 +28,11 @@ options.skia_glyph_texture_atlas_dimensions.height(), options.skia_cache_size_in_bytes, options.scratch_surface_cache_size_in_bytes, +#if defined(COBALT_FORCE_DIRECT_GLES_RASTERIZER) + options.offscreen_target_cache_size_in_bytes, +#else options.surface_cache_size_in_bytes, +#endif options.purge_skia_font_caches_on_destruction)); } } // namespace
diff --git a/src/cobalt/renderer/submission_queue.cc b/src/cobalt/renderer/submission_queue.cc index 20a1e39..b7b861e 100644 --- a/src/cobalt/renderer/submission_queue.cc +++ b/src/cobalt/renderer/submission_queue.cc
@@ -89,8 +89,11 @@ base::polymorphic_downcast<render_tree::animations::AnimateNode*>( submission_queue_.front().render_tree.get()); - if (animate_node->expiry() <= submission_time(now) && - animate_node->expiry() <= + // Check the expiration of only animations that depend on the time + // parameter, since they are the only ones that will be affected by snapping + // time. + if (animate_node->depends_on_time_expiry() <= submission_time(now) && + animate_node->depends_on_time_expiry() <= latest_to_submission_time + render_time(now)) { to_submission_time_in_ms_.SnapToTarget(); }
diff --git a/src/cobalt/system_window/system_window.cc b/src/cobalt/system_window/system_window.cc index 3a21efb..1e2c38f 100644 --- a/src/cobalt/system_window/system_window.cc +++ b/src/cobalt/system_window/system_window.cc
@@ -211,51 +211,6 @@ } } -void OnDialogClose(SbSystemPlatformErrorResponse response, void* user_data) { - DCHECK(user_data); - SystemWindow* system_window = static_cast<SystemWindow*>(user_data); - system_window->HandleDialogClose(response); -} - -void SystemWindow::ShowDialog(const SystemWindow::DialogOptions& options) { - SbSystemPlatformErrorType error_type = - kSbSystemPlatformErrorTypeConnectionError; - switch (options.message_code) { - case kDialogConnectionError: - error_type = kSbSystemPlatformErrorTypeConnectionError; - break; - default: - NOTREACHED(); - break; - } - - SbSystemPlatformError handle = - SbSystemRaisePlatformError(error_type, OnDialogClose, this); - if (SbSystemPlatformErrorIsValid(handle)) { - current_dialog_callback_ = options.callback; - } else { - DLOG(WARNING) << "Failed to notify user of error: " << options.message_code; - } -} - -void SystemWindow::HandleDialogClose(SbSystemPlatformErrorResponse response) { - DCHECK(!current_dialog_callback_.is_null()); - switch (response) { - case kSbSystemPlatformErrorResponsePositive: - current_dialog_callback_.Run(kDialogPositiveResponse); - break; - case kSbSystemPlatformErrorResponseNegative: - current_dialog_callback_.Run(kDialogNegativeResponse); - break; - case kSbSystemPlatformErrorResponseCancel: - current_dialog_callback_.Run(kDialogCancelResponse); - break; - default: - DLOG(WARNING) << "Unrecognized dialog response: " << response; - break; - } -} - void HandleInputEvent(const SbEvent* event) { if (event->type != kSbEventTypeInput) { return;
diff --git a/src/cobalt/system_window/system_window.h b/src/cobalt/system_window/system_window.h index b9f6017..121313c 100644 --- a/src/cobalt/system_window/system_window.h +++ b/src/cobalt/system_window/system_window.h
@@ -15,17 +15,12 @@ #ifndef COBALT_SYSTEM_WINDOW_SYSTEM_WINDOW_H_ #define COBALT_SYSTEM_WINDOW_SYSTEM_WINDOW_H_ -#include <string> - -#include "base/callback.h" -#include "base/memory/scoped_ptr.h" #include "base/optional.h" #include "cobalt/base/event_dispatcher.h" #include "cobalt/math/size.h" #include "cobalt/system_window/input_event.h" #include "starboard/input.h" #include "starboard/key.h" -#include "starboard/system.h" namespace cobalt { namespace system_window { @@ -36,38 +31,10 @@ // create a display render target for a graphics system. class SystemWindow { public: - // Enumeration of possible responses for the dialog callback. - enum DialogResponse { - kDialogPositiveResponse, - kDialogNegativeResponse, - kDialogCancelResponse - }; - - // Type of callback to run when user closes a dialog. - typedef base::Callback<void(DialogResponse response)> DialogCallback; - - // Enumeration of possible message codes for a dialog. - enum DialogMessageCode { - kDialogConnectionError - }; - - // Options structure for dialog creation. It is expected that each platform - // will implement a modal dialog with possible support for: - // A message code specifying the text to be displayed, which should be - // localized according to the platform. - // A callback indicating the user's response: positive, negative or cancel. - struct DialogOptions { - DialogMessageCode message_code; - DialogCallback callback; - }; - SystemWindow(base::EventDispatcher* event_dispatcher, const base::optional<math::Size>& window_size); ~SystemWindow(); - // Launches a system dialog. - void ShowDialog(const DialogOptions& options); - // Returns the dimensions of the window. math::Size GetWindowSize() const; @@ -92,9 +59,6 @@ // Handles a single Starboard input event, dispatching any appropriate events. void HandleInputEvent(const SbInputData& data); - // Called when the user closes the dialog. - void HandleDialogClose(SbSystemPlatformErrorResponse response); - private: void UpdateModifiers(SbKey key, bool pressed); InputEvent::Modifiers GetModifiers(); @@ -107,9 +71,6 @@ SbWindow window_; bool key_down_; - - // The current dialog callback. Only one dialog may be open at a time. - DialogCallback current_dialog_callback_; }; } // namespace system_window
diff --git a/src/cobalt/xhr/xml_http_request.cc b/src/cobalt/xhr/xml_http_request.cc index c1774a4..4c2e893 100644 --- a/src/cobalt/xhr/xml_http_request.cc +++ b/src/cobalt/xhr/xml_http_request.cc
@@ -163,7 +163,8 @@ error_(false), sent_(false), stop_timeout_(false), - upload_complete_(false) { + upload_complete_(false), + active_requests_count_(0) { DCHECK(settings_); dom::GlobalStats::GetInstance()->Add(this); xhr_id_ = ++s_xhr_sequence_num_; @@ -365,8 +366,9 @@ // Step 9 sent_ = true; // Now that a send is happening, prevent this object - // from being collected until it's complete or aborted. - PreventGarbageCollection(); + // from being collected until it's complete or aborted + // if no currently active request has called it before. + IncrementActiveRequests(); FireProgressEvent(this, base::Tokens::loadstart()); if (!upload_complete_) { FireProgressEvent(upload_, base::Tokens::loadstart()); @@ -677,7 +679,7 @@ ChangeState(kDone); UpdateProgress(); // Undo the ref we added in Send() - AllowGarbageCollection(); + DecrementActiveRequests(); } else { HandleRequestError(kNetworkError); } @@ -775,7 +777,7 @@ FireProgressEvent(this, base::Tokens::loadend()); fetch_callback_.reset(); - AllowGarbageCollection(); + DecrementActiveRequests(); } void XMLHttpRequest::OnTimeout() { @@ -856,6 +858,21 @@ } } +void XMLHttpRequest::IncrementActiveRequests() { + if (active_requests_count_ == 0) { + PreventGarbageCollection(); + } + active_requests_count_++; +} + +void XMLHttpRequest::DecrementActiveRequests() { + DCHECK_GT(active_requests_count_, 0); + active_requests_count_--; + if (active_requests_count_ == 0) { + AllowGarbageCollection(); + } +} + void XMLHttpRequest::PreventGarbageCollection() { settings_->global_environment()->PreventGarbageCollection( make_scoped_refptr(this));
diff --git a/src/cobalt/xhr/xml_http_request.h b/src/cobalt/xhr/xml_http_request.h index ab9a5e4..209292a 100644 --- a/src/cobalt/xhr/xml_http_request.h +++ b/src/cobalt/xhr/xml_http_request.h
@@ -218,6 +218,13 @@ void AllowGarbageCollection(); void StartRequest(const std::string& request_body); + // The following two methods are used to determine if garbage collection is + // needed. It is legal to reuse XHR and send a new request in last request's + // onload event listener. We should not allow garbage collection until + // the last request is fetched. + void IncrementActiveRequests(); + void DecrementActiveRequests(); + // Accessors / mutators for testing. const GURL& request_url() const { return request_url_; } bool error() const { return error_; } @@ -271,6 +278,7 @@ bool sent_; bool stop_timeout_; bool upload_complete_; + int active_requests_count_; static bool verbose_; // Unique ID for debugging.
diff --git a/src/nb/nb.gyp b/src/nb/nb.gyp index 8c50a66..b33d946 100644 --- a/src/nb/nb.gyp +++ b/src/nb/nb.gyp
@@ -60,6 +60,8 @@ 'scoped_ptr.h', 'simple_thread.cc', 'simple_thread.h', + 'simple_profiler.cc', + 'simple_profiler.h', 'std_allocator.h', 'string_interner.cc', 'string_interner.h', @@ -104,6 +106,7 @@ 'multipart_allocator_test.cc', 'rewindable_vector_test.cc', 'run_all_unittests.cc', + 'simple_profiler_test.cc', 'std_allocator_test.cc', 'string_interner_test.cc', 'test_thread.h',
diff --git a/src/nb/simple_profiler.cc b/src/nb/simple_profiler.cc new file mode 100644 index 0000000..6b313aa --- /dev/null +++ b/src/nb/simple_profiler.cc
@@ -0,0 +1,187 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <algorithm> +#include <string> +#include <vector> + +#include "nb/rewindable_vector.h" +#include "nb/simple_profiler.h" +#include "nb/thread_local_object.h" +#include "starboard/atomic.h" +#include "starboard/once.h" +#include "starboard/string.h" +#include "starboard/time.h" + +namespace nb { +namespace { + +class SimpleProfilerManager { + public: + struct Entry { + Entry() : name(nullptr), time_delta(0), indent_value(0) {} + Entry(const char* n, SbTimeMonotonic dt, int ind) + : name(n), time_delta(dt), indent_value(ind) {} + Entry(const Entry& e) = default; + const char* name; + SbTimeMonotonic time_delta; + int indent_value; + }; + + using MessageHandlerFunction = SimpleProfiler::MessageHandlerFunction; + using ClockFunction = SimpleProfiler::ClockFunction; + SimpleProfilerManager() + : default_enabled_(true), message_handler_(nullptr), + clock_function_(nullptr) {} + + int BeginInstance(const char* name) { + Data& d = ThreadLocal(); + if (!d.enable_flag) { + return -1; + } + SbTimeMonotonic now = NowTime(); + // SbAtomicMemoryBarrier() to Keep order of operations so clock doesn't + // get sampled out of order. + SbAtomicMemoryBarrier(); + d.profiles.emplace_back(name, now, d.instance_count); + ++d.instance_count; + return d.profiles.size() - 1; + } + + void Output(const Entry& entry, std::stringstream* sstream) { + for (auto i = 0; i < entry.indent_value; ++i) { + (*sstream) << ' '; + } + (*sstream) << entry.name << ": " << entry.time_delta << "us\n"; + } + + void FinishInstance(int index) { + Data& d = ThreadLocal(); + if (!d.enable_flag || index < 0) { + return; + } + + --d.instance_count; + Entry& entry = d.profiles[static_cast<size_t>(index)]; + entry.time_delta = NowTime() - entry.time_delta; + // SbAtomicMemoryBarrier() to Keep order of operations so clock doesn't + // get sampled out of order. + SbAtomicMemoryBarrier(); + + if (d.instance_count == 0) { + std::stringstream ss; + for (auto it = d.profiles.begin(); it != d.profiles.end(); ++it) { + Output(*it, &ss); + } + d.profiles.clear(); + HandleMessage(ss.str()); + } + } + bool ThreadLocalEnabled() { return ThreadLocal().enable_flag; } + void SetThreadLocalEnabled(bool value) { + Data& d = ThreadLocal(); + d.enable_flag = value; + d.enable_flag_set = true; + } + + bool IsEnabled() const { + const Data& d = ThreadLocal(); + if (d.enable_flag_set) { + return d.enable_flag; + } + return default_enabled_; + } + void SetDefaultEnabled(bool value) { default_enabled_ = value; } + + void SetMessageHandler(MessageHandlerFunction handler) { + message_handler_ = handler; + } + + void SetClockFunction(ClockFunction fcn) { + clock_function_ = fcn; + } + + SbTimeMonotonic NowTime() { + if (!clock_function_) { + return SbTimeGetMonotonicNow(); + } else { + return clock_function_(); + } + } + + private: + struct Data { + bool enable_flag = true; + bool enable_flag_set = false; + int instance_count = 0; + std::vector<Entry> profiles; + }; + + void HandleMessage(const std::string& str) { + if (message_handler_) { + message_handler_(str.c_str()); + } else { + SbLogRaw(str.c_str()); + } + } + + Data& ThreadLocal() { return *thread_local_.GetOrCreate(); } + const Data& ThreadLocal() const { return *thread_local_.GetOrCreate(); } + mutable nb::ThreadLocalObject<Data> thread_local_; + bool default_enabled_; + MessageHandlerFunction message_handler_; + ClockFunction clock_function_; +}; + +SB_ONCE_INITIALIZE_FUNCTION(SimpleProfilerManager, GetSimpleProfilerManager); + +} // namespace + +SimpleProfiler::EnableScope::EnableScope(bool enabled) { + SimpleProfilerManager* mgr = GetSimpleProfilerManager(); + prev_enabled_ = mgr->ThreadLocalEnabled(); + mgr->SetThreadLocalEnabled(enabled); +} + +SimpleProfiler::EnableScope::~EnableScope() { + GetSimpleProfilerManager()->SetThreadLocalEnabled(prev_enabled_); +} + +SimpleProfiler::SimpleProfiler(const char* name) { + momento_index_ = GetSimpleProfilerManager()->BeginInstance(name); +} + +SimpleProfiler::~SimpleProfiler() { + GetSimpleProfilerManager()->FinishInstance(momento_index_); +} + +void SimpleProfiler::SetEnabledByDefault(bool value) { + GetSimpleProfilerManager()->SetDefaultEnabled(value); +} + +bool SimpleProfiler::IsEnabled() { + return GetSimpleProfilerManager()->IsEnabled(); +} + +void SimpleProfiler::SetLoggingFunction(MessageHandlerFunction fcn) { + GetSimpleProfilerManager()->SetMessageHandler(fcn); +} + +void SimpleProfiler::SetClockFunction(ClockFunction fcn) { + GetSimpleProfilerManager()->SetClockFunction(fcn); +} + +} // namespace nb
diff --git a/src/nb/simple_profiler.h b/src/nb/simple_profiler.h new file mode 100644 index 0000000..2c9e4f4 --- /dev/null +++ b/src/nb/simple_profiler.h
@@ -0,0 +1,105 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NB_SIMPLE_PROFILER_H_ +#define NB_SIMPLE_PROFILER_H_ + +#include <string> + +#include "starboard/time.h" +#include "starboard/types.h" + +namespace nb { + +// SimpleProfiler is useful for development. It will allow the developer +// to track where the CPU spends most of it's time in a call chain. +// CPU time is tracked via thread-local-storage. When the last SimpleProfiler +// in the thread is destroyed then a printout of the timing will be printed +// to the output log. +// +// By default, SimpleProfiler will generate output whenever it's used. To +// selectively profile a thread see class SimpleProfiler::EnableScope. +// +// Example: +// void Foo() { +// SimpleProfiler profile(__FUNCTION__); +// Bar(); +// Baz(); +// } +// void Bar() { +// SimpleProfiler profile(__FUNCTION__); +// // ... do something expensive ... +// Qux(); +// } +// void Baz() { +// SimpleProfiler profile(__FUNCTION__); +// // ... do something cheap ... +// } +// void Qux() { +// SimpleProfiler profile(__FUNCTION__); +// // ... do something nearly free ... +// } +// +// Outputs: "Foo: 25us\n" +// " Bar: 20us\n" +// " Qux: 1us\n" +// " Baz: 4us\n" +class SimpleProfiler { + public: + explicit SimpleProfiler(const char* name); + ~SimpleProfiler(); + // EnableScope can be used to enable and disable SimpleProfiler in the + // thread. A scoped object is used so that SimpleProfiler + // constructor / destructor order is maintained in relation to + // enabling / disabling. + // Example: + // // Assume SimpleProfiler was globally disabled by default. + // void Foo() { + // SimpleProfiler::ThreadScope enable_scope(true); // enabled in scope. + // SimpleProfiler profile(__FUNCTION__); + // DoWork(); + // } + class EnableScope { + public: + explicit EnableScope(bool enabled); + ~EnableScope(); + private: + bool prev_enabled_; + }; + // If SetThreadLocalEnableFlag() isn't explicitly set by the thread + // then this |input| value will control whether the SimpleProfiler + // is active or not. For best results, set this value as early as + // possible during program execution. + static void SetEnabledByDefault(bool value); + // Is SimpleProfiler enabled? If Get/SetThreadLocalEnableFlag() isn't + // set then this will return an enabled by default flag, which defaults + // to true. + static bool IsEnabled(); + typedef void (*MessageHandlerFunction)(const char* msg); + // Useful for tests. Setting to nullptr will reset the behavior to + // default functionality. + static void SetLoggingFunction(MessageHandlerFunction fcn); + // Useful for tests. Setting to nullptr will reset the behavior to + // default functionality. + typedef SbTimeMonotonic (*ClockFunction)(); + static void SetClockFunction(ClockFunction fcn); + private: + int momento_index_; +}; + +} // namespace nb + +#endif // NB_SIMPLE_PROFILER_H_
diff --git a/src/nb/simple_profiler_test.cc b/src/nb/simple_profiler_test.cc new file mode 100644 index 0000000..41b5cee --- /dev/null +++ b/src/nb/simple_profiler_test.cc
@@ -0,0 +1,95 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "nb/simple_profiler.h" + +#include <map> +#include <string> +#include <vector> + +#include "starboard/configuration.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace nb { +class SimpleProfilerTest : public ::testing::Test { + public: + SimpleProfilerTest() {} + virtual void SetUp() SB_OVERRIDE { + s_log_buffer_.clear(); + SimpleProfiler::SetLoggingFunction(TestLogFunction); + } + virtual void TearDown() SB_OVERRIDE { + SimpleProfiler::SetLoggingFunction(nullptr); + SimpleProfiler::SetClockFunction(nullptr); + s_log_buffer_.clear(); + } + + std::string GetProfilerOutput() { return s_log_buffer_; } + + private: + static std::string s_log_buffer_; + static void TestLogFunction(const char* value) { s_log_buffer_ = value; } +}; +std::string SimpleProfilerTest::s_log_buffer_; + +struct CallChain { + static void Foo() { + SimpleProfiler profile("Foo"); + Bar(); + Qux(); + } + + static void Bar() { + SimpleProfiler profile("Bar"); + Baz(); + } + + static void Baz() { SimpleProfiler profile("Baz"); } + static void Qux() { SimpleProfiler profile("Qux"); } +}; + +TEST_F(SimpleProfilerTest, IsEnabledByDefault) { + EXPECT_TRUE(SimpleProfiler::IsEnabled()); +} + +SbTimeMonotonic NullTime() { + return SbTimeMonotonic(0); +} +// Tests the expectation that SimpleProfiler can be used in a call +// chain and will generate the expected string. +TEST_F(SimpleProfilerTest, CallChain) { + SimpleProfiler::SetClockFunction(NullTime); + CallChain::Foo(); + std::string profiler_log = GetProfilerOutput(); + + std::string expected_output = + "Foo: 0us\n" + " Bar: 0us\n" + " Baz: 0us\n" + " Qux: 0us\n"; + + EXPECT_EQ(expected_output, profiler_log) << " actual output:\n" + << profiler_log; +} + +TEST_F(SimpleProfilerTest, EnableScopeDisabled) { + SimpleProfiler::EnableScope enable(false); + CallChain::Foo(); + std::string profiler_log = GetProfilerOutput(); + EXPECT_TRUE(profiler_log.empty()); +} + +} // namespace nb
diff --git a/src/net/dial/dial_http_server.cc b/src/net/dial/dial_http_server.cc index a1a44c4..482d27d 100644 --- a/src/net/dial/dial_http_server.cc +++ b/src/net/dial/dial_http_server.cc
@@ -76,9 +76,6 @@ // get the port information int ret = http_server_->GetLocalAddress(addr); -#if defined(OS_STARBOARD) - -#if SB_API_VERSION >= 4 if (ret != 0) { return ERR_FAILED; } @@ -101,24 +98,6 @@ } return ERR_FAILED; -#else - SbSocketAddress address; - ret |= SbSocketGetLocalInterfaceAddress(&address) ? 0 : -1; - address.port = addr->port(); - return (ret == 0 && addr->FromSbSocketAddress(&address)) ? OK : ERR_FAILED; -#endif // SB_API_VERSION >= 4 - -#else - // Now get the IPAddress of the network card. - SockaddrStorage sock_addr; - struct sockaddr_in *in = (struct sockaddr_in *)sock_addr.addr; - ret |= lb_get_local_ip_address(&in->sin_addr); - in->sin_family = AF_INET; - in->sin_port = htons(addr->port()); - - return (ret == 0 && addr->FromSockAddr(sock_addr.addr, sock_addr.addr_len)) - ? OK : ERR_FAILED; -#endif } void DialHttpServer::OnHttpRequest(int conn_id,
diff --git a/src/starboard/linux/x64x11/skia/atomic_public.h b/src/starboard/linux/x64x11/skia/atomic_public.h new file mode 100644 index 0000000..048b3c9 --- /dev/null +++ b/src/starboard/linux/x64x11/skia/atomic_public.h
@@ -0,0 +1,20 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_LINUX_X64X11_SKIA_ATOMIC_PUBLIC_H_ +#define STARBOARD_LINUX_X64X11_SKIA_ATOMIC_PUBLIC_H_ + +#include "starboard/linux/shared/atomic_public.h" + +#endif // STARBOARD_LINUX_X64X11_SKIA_ATOMIC_PUBLIC_H_
diff --git a/src/starboard/linux/x64x11/skia/configuration_public.h b/src/starboard/linux/x64x11/skia/configuration_public.h new file mode 100644 index 0000000..ad03114 --- /dev/null +++ b/src/starboard/linux/x64x11/skia/configuration_public.h
@@ -0,0 +1,28 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The Starboard configuration for Desktop X86 Linux configured for the future +// starboard API. + +#ifndef STARBOARD_LINUX_X64X11_SKIA_CONFIGURATION_PUBLIC_H_ +#define STARBOARD_LINUX_X64X11_SKIA_CONFIGURATION_PUBLIC_H_ + +// This is not a released configuration, so it should implement the +// experimental API version to validate trunk's viability. +#define SB_API_VERSION SB_EXPERIMENTAL_API_VERSION + +// Include the X64X11 Linux configuration. +#include "starboard/linux/x64x11/configuration_public.h" + +#endif // STARBOARD_LINUX_X64X11_SKIA_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/linux/x64x11/skia/gyp_configuration.gypi b/src/starboard/linux/x64x11/skia/gyp_configuration.gypi new file mode 100644 index 0000000..06b67cc --- /dev/null +++ b/src/starboard/linux/x64x11/skia/gyp_configuration.gypi
@@ -0,0 +1,41 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{ + 'variables': { + # Use the skia hardware rasterizer. + 'rasterizer_type': 'hardware', + }, + 'target_defaults': { + 'default_configuration': 'linux-x64x11-skia_debug', + 'configurations': { + 'linux-x64x11-skia_debug': { + 'inherit_from': ['debug_base'], + }, + 'linux-x64x11-skia_devel': { + 'inherit_from': ['devel_base'], + }, + 'linux-x64x11-skia_qa': { + 'inherit_from': ['qa_base'], + }, + 'linux-x64x11-skia_gold': { + 'inherit_from': ['gold_base'], + }, + }, # end of configurations + }, + + 'includes': [ + '../gyp_configuration.gypi', + ], +}
diff --git a/src/starboard/linux/x64x11/skia/gyp_configuration.py b/src/starboard/linux/x64x11/skia/gyp_configuration.py new file mode 100644 index 0000000..45b4461 --- /dev/null +++ b/src/starboard/linux/x64x11/skia/gyp_configuration.py
@@ -0,0 +1,27 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Starboard Linux X64 X11 future platform configuration for gyp_cobalt.""" + +import logging + +# Import the shared Linux platform configuration. +from starboard.linux.shared import gyp_configuration + + +def CreatePlatformConfig(): + try: + return gyp_configuration.PlatformConfig('linux-x64x11-skia') + except RuntimeError as e: + logging.critical(e) + return None
diff --git a/src/starboard/linux/x64x11/skia/starboard_platform.gyp b/src/starboard/linux/x64x11/skia/starboard_platform.gyp new file mode 100644 index 0000000..528d1cd --- /dev/null +++ b/src/starboard/linux/x64x11/skia/starboard_platform.gyp
@@ -0,0 +1,33 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{ + 'includes': [ + '../starboard_platform.gypi' + ], + 'targets': [ + { + 'target_name': 'starboard_platform', + 'type': 'static_library', + 'sources': ['<@(starboard_platform_sources)'], + 'defines': [ + # This must be defined when building Starboard, and must not when + # building Starboard client code. + 'STARBOARD_IMPLEMENTATION', + ], + 'dependencies': [ + '<@(starboard_platform_dependencies)', + ], + }, + ], +}
diff --git a/src/starboard/linux/x64x11/skia/thread_types_public.h b/src/starboard/linux/x64x11/skia/thread_types_public.h new file mode 100644 index 0000000..5e74da8 --- /dev/null +++ b/src/starboard/linux/x64x11/skia/thread_types_public.h
@@ -0,0 +1,20 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_LINUX_X64X11_SKIA_THREAD_TYPES_PUBLIC_H_ +#define STARBOARD_LINUX_X64X11_SKIA_THREAD_TYPES_PUBLIC_H_ + +#include "starboard/linux/shared/thread_types_public.h" + +#endif // STARBOARD_LINUX_X64X11_SKIA_THREAD_TYPES_PUBLIC_H_
diff --git a/src/starboard/nplb/audio_sink_helpers.cc b/src/starboard/nplb/audio_sink_helpers.cc index 981c0e1..51aa218 100644 --- a/src/starboard/nplb/audio_sink_helpers.cc +++ b/src/starboard/nplb/audio_sink_helpers.cc
@@ -151,7 +151,7 @@ bool AudioSinkTestEnvironment::WaitUntilAllFramesAreConsumed() { ScopedLock lock(mutex_); SbTimeMonotonic start = SbTimeGetMonotonicNow(); - while (frames_appended_ == frames_consumed_) { + while (frames_appended_ != frames_consumed_) { SbTime time_elapsed = SbTimeGetMonotonicNow() - start; if (time_elapsed >= kTimeToTry) { return false;
diff --git a/src/starboard/nplb/system_get_total_gpu_memory_test.cc b/src/starboard/nplb/system_get_total_gpu_memory_test.cc index 0cd7d9b..c39814c 100644 --- a/src/starboard/nplb/system_get_total_gpu_memory_test.cc +++ b/src/starboard/nplb/system_get_total_gpu_memory_test.cc
@@ -23,7 +23,7 @@ if (SbSystemHasCapability(kSbSystemCapabilityCanQueryGPUMemoryStats)) { // If we claim to have GPU memory reporting capabilities, then this value // should be larger than 0. - EXPECT_LT(0, SbSystemGetTotalCPUMemory()); + EXPECT_LT(0, SbSystemGetTotalGPUMemory()); } }
diff --git a/src/starboard/raspi/2/skia/atomic_public.h b/src/starboard/raspi/2/skia/atomic_public.h new file mode 100644 index 0000000..dca0903 --- /dev/null +++ b/src/starboard/raspi/2/skia/atomic_public.h
@@ -0,0 +1,20 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_RASPI_2_SKIA_ATOMIC_PUBLIC_H_ +#define STARBOARD_RASPI_2_SKIA_ATOMIC_PUBLIC_H_ + +#include "starboard/raspi/2/atomic_public.h" + +#endif // STARBOARD_RASPI_2_SKIA_ATOMIC_PUBLIC_H_
diff --git a/src/starboard/raspi/2/skia/configuration_public.h b/src/starboard/raspi/2/skia/configuration_public.h new file mode 100644 index 0000000..6c65ff5 --- /dev/null +++ b/src/starboard/raspi/2/skia/configuration_public.h
@@ -0,0 +1,25 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The Starboard configuration for Raspberry PI 2 Raspbian. + +// Other source files should never include this header directly, but should +// include the generic "starboard/configuration.h" instead. + +#ifndef STARBOARD_RASPI_2_SKIA_CONFIGURATION_PUBLIC_H_ +#define STARBOARD_RASPI_2_SKIA_CONFIGURATION_PUBLIC_H_ + +#include "starboard/raspi/2/configuration_public.h" + +#endif // STARBOARD_RASPI_2_SKIA_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/raspi/2/skia/gyp_configuration.gypi b/src/starboard/raspi/2/skia/gyp_configuration.gypi new file mode 100644 index 0000000..21c8ea4 --- /dev/null +++ b/src/starboard/raspi/2/skia/gyp_configuration.gypi
@@ -0,0 +1,43 @@ +# Copyright 2017 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{ + 'variables': { + # Use the skia hardware rasterizer. + 'rasterizer_type': 'hardware', + }, + + 'target_defaults': { + 'default_configuration': 'raspi-2-skia_debug', + 'configurations': { + 'raspi-2-skia_debug': { + 'inherit_from': ['debug_base'], + }, + 'raspi-2-skia_devel': { + 'inherit_from': ['devel_base'], + }, + 'raspi-2-skia_qa': { + 'inherit_from': ['qa_base'], + }, + 'raspi-2-skia_gold': { + 'inherit_from': ['gold_base'], + }, + }, # end of configurations + }, + + 'includes': [ + '../architecture.gypi', + '../../shared/gyp_configuration.gypi', + ], +}
diff --git a/src/starboard/raspi/2/skia/gyp_configuration.py b/src/starboard/raspi/2/skia/gyp_configuration.py new file mode 100644 index 0000000..ccf358f --- /dev/null +++ b/src/starboard/raspi/2/skia/gyp_configuration.py
@@ -0,0 +1,32 @@ +# Copyright 2017 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Starboard Raspberry Pi 2 platform configuration for gyp_cobalt.""" + +import logging +import os +import sys + +_SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__)) +sys.path.insert(0, os.path.join(_SCRIPT_DIR, '../..')) + +# pylint: disable=g-import-not-at-top +from shared.gyp_configuration import RaspiPlatformConfig + + +def CreatePlatformConfig(): + try: + return RaspiPlatformConfig('raspi-2-skia') + except RuntimeError as e: + logging.critical(e) + return None
diff --git a/src/starboard/raspi/2/skia/starboard_platform.gyp b/src/starboard/raspi/2/skia/starboard_platform.gyp new file mode 100644 index 0000000..a304ac6 --- /dev/null +++ b/src/starboard/raspi/2/skia/starboard_platform.gyp
@@ -0,0 +1,18 @@ +# Copyright 2017 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{ + 'includes': [ + '../../shared/starboard_platform.gypi', + ], +}
diff --git a/src/starboard/raspi/2/skia/thread_types_public.h b/src/starboard/raspi/2/skia/thread_types_public.h new file mode 100644 index 0000000..98cf1fb --- /dev/null +++ b/src/starboard/raspi/2/skia/thread_types_public.h
@@ -0,0 +1,20 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_RASPI_2_SKIA_THREAD_TYPES_PUBLIC_H_ +#define STARBOARD_RASPI_2_SKIA_THREAD_TYPES_PUBLIC_H_ + +#include "starboard/raspi/2/thread_types_public.h" + +#endif // STARBOARD_RASPI_2_SKIA_THREAD_TYPES_PUBLIC_H_
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc index 2ad5415..c219c59 100644 --- a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc +++ b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc
@@ -86,6 +86,8 @@ audio_sink_(kSbAudioSinkInvalid), can_accept_more_data_(true), process_audio_data_scheduled_(false), + process_audio_data_closure_( + Bind(&AudioRendererImpl::ProcessAudioData, this)), decoder_needs_full_reset_(false), audio_frame_tracker_(audio_frame_tracker.Pass()) { SB_DCHECK(decoder_ != NULL); @@ -177,6 +179,12 @@ // TODO: Remove SetPlaybackRate() support from audio sink as it only need to // support play/pause. audio_sink_->SetPlaybackRate(playback_rate_ > 0.0 ? 1.0 : 0.0); + if (playback_rate_ > 0.0) { + if (process_audio_data_scheduled_) { + Remove(process_audio_data_closure_); + } + ProcessAudioData(); + } } } @@ -353,9 +361,7 @@ ++pending_decoder_outputs_; if (process_audio_data_scheduled_) { - // A ProcessAudioData() callback has been scheduled and we should let it - // process the output. - return; + Remove(process_audio_data_closure_); } process_audio_data_scheduled_ = true; @@ -381,7 +387,7 @@ const SbTimeMonotonic delay = kMaxCachedFrames * kSbTimeSecond / decoder_->GetSamplesPerSecond() / 4; process_audio_data_scheduled_ = true; - Schedule(Bind(&AudioRendererImpl::ProcessAudioData, this), delay); + Schedule(process_audio_data_closure_, delay); return; } @@ -419,6 +425,12 @@ } } + if (seeking_.load() || playback_rate_ == 0.0) { + process_audio_data_scheduled_ = true; + Schedule(process_audio_data_closure_, 5 * kSbTimeMillisecond); + return; + } + int64_t frames_in_buffer = frames_sent_to_sink_.load() - frames_consumed_by_sink_.load(); if (kMaxCachedFrames - frames_in_buffer < kFrameAppendUnit && @@ -431,13 +443,21 @@ delay = frames_to_delay * kSbTimeSecond / decoder_->GetSamplesPerSecond(); } process_audio_data_scheduled_ = true; - Schedule(Bind(&AudioRendererImpl::ProcessAudioData, this), delay); + Schedule(process_audio_data_closure_, delay); } } bool AudioRendererImpl::AppendAudioToFrameBuffer() { SB_DCHECK(BelongsToCurrentThread()); + if (seeking_.load() && time_stretcher_.IsQueueFull()) { + seeking_.store(false); + } + + if (seeking_.load() || playback_rate_ == 0.0) { + return false; + } + int frames_in_buffer = static_cast<int>(frames_sent_to_sink_.load() - frames_consumed_by_sink_.load()); @@ -447,18 +467,13 @@ int offset_to_append = frames_sent_to_sink_.load() % kMaxCachedFrames; - // When |playback_rate_| is 0, try to fill the buffer with playback rate as 1. - // Otherwise the preroll will never finish. - float playback_rate_to_fill = - playback_rate_ == 0.0 ? 1.f : static_cast<float>(playback_rate_); scoped_refptr<DecodedAudio> decoded_audio = - time_stretcher_.Read(kFrameAppendUnit, playback_rate_to_fill); + time_stretcher_.Read(kFrameAppendUnit, playback_rate_); SB_DCHECK(decoded_audio); if (decoded_audio->frames() == 0 && eos_state_.load() == kEOSDecoded) { eos_state_.store(kEOSSentToSink); } - audio_frame_tracker_->AddFrames(decoded_audio->frames(), - playback_rate_to_fill); + audio_frame_tracker_->AddFrames(decoded_audio->frames(), playback_rate_); // TODO: Support kSbMediaAudioFrameStorageTypePlanar. decoded_audio->SwitchFormatTo(sink_sample_type_, kSbMediaAudioFrameStorageTypeInterleaved); @@ -482,12 +497,6 @@ frames_sent_to_sink_.fetch_add(frames_appended); - int64_t preroll_frames = - decoder_->GetSamplesPerSecond() * kPrerollTime / kSbTimeSecond; - if (seeking_.load() && frames_sent_to_sink_.load() > preroll_frames) { - seeking_.store(false); - } - return frames_appended > 0; }
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h index f1b7730..cd36913 100644 --- a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h +++ b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h
@@ -44,10 +44,6 @@ // |AudioDecoder| interface, rather than a platform specific implementation. class AudioRendererImpl : public AudioRenderer, private JobQueue::JobOwner { public: - // Preroll is considered as finished after either the amount of audio caches - // exceeds kPrerollTime or if EOS is reached. - static const size_t kPrerollTime = kSbTimeSecond / 4; - AudioRendererImpl( scoped_ptr<AudioDecoder> decoder, const SbMediaAudioHeader& audio_header, @@ -146,6 +142,7 @@ bool can_accept_more_data_; bool process_audio_data_scheduled_; + Closure process_audio_data_closure_; // Our owner will attempt to seek to pts 0 when playback begins. In // general, seeking could require a full reset of the underlying decoder on
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal_test.cc b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal_test.cc index 517c3ef..4037ee2 100644 --- a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal_test.cc +++ b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal_test.cc
@@ -185,19 +185,16 @@ const int kFramesPerBuffer = 1024; int frames_written = 0; - int preroll_frames = kDefaultSamplesPerSecond * - AudioRendererImpl::kPrerollTime / kSbTimeSecond; - while (frames_written <= preroll_frames) { - SbMediaTime pts = frames_written / kDefaultSamplesPerSecond; + while (audio_renderer_->IsSeekingInProgress()) { + SbMediaTime pts = + frames_written * kSbMediaTimeSecond / kDefaultSamplesPerSecond; WriteSample(CreateInputBuffer(pts)); CallConsumedCB(); SendDecoderOutput(CreateDecodedAudio(pts, kFramesPerBuffer)); frames_written += kFramesPerBuffer; } - EXPECT_FALSE(audio_renderer_->IsSeekingInProgress()); - WriteEndOfStream(); EXPECT_EQ(audio_renderer_->GetCurrentTime(), 0); @@ -210,9 +207,11 @@ SbMediaTime media_time = audio_renderer_->GetCurrentTime(); while (!audio_renderer_->IsEndOfStreamPlayed()) { - SbThreadSleep(AudioRendererImpl::kPrerollTime); + SbThreadSleep(kSbTimeMillisecond); SbMediaTime new_media_time = audio_renderer_->GetCurrentTime(); - EXPECT_GT(new_media_time, media_time); + // TODO: Replace it with EXPECT_GT once audio time reporting is more + // accurate. + EXPECT_GE(new_media_time, media_time); media_time = new_media_time; } } @@ -229,24 +228,16 @@ const int kFramesPerBuffer = 1024; int frames_written = 0; - int preroll_frames = kDefaultSamplesPerSecond * - AudioRendererImpl::kPrerollTime / kSbTimeSecond * - kPlaybackRate * 2; - while (frames_written <= preroll_frames) { - SbMediaTime pts = frames_written / kDefaultSamplesPerSecond; + while (audio_renderer_->IsSeekingInProgress()) { + SbMediaTime pts = + frames_written * kSbMediaTimeSecond / kDefaultSamplesPerSecond; WriteSample(CreateInputBuffer(pts)); CallConsumedCB(); SendDecoderOutput(CreateDecodedAudio(pts, kFramesPerBuffer)); frames_written += kFramesPerBuffer; - - if (!audio_renderer_->IsSeekingInProgress()) { - break; - } } - EXPECT_FALSE(audio_renderer_->IsSeekingInProgress()); - WriteEndOfStream(); EXPECT_EQ(audio_renderer_->GetCurrentTime(), 0); @@ -259,9 +250,11 @@ SbMediaTime media_time = audio_renderer_->GetCurrentTime(); while (!audio_renderer_->IsEndOfStreamPlayed()) { - SbThreadSleep(AudioRendererImpl::kPrerollTime); + SbThreadSleep(kSbTimeMillisecond); SbMediaTime new_media_time = audio_renderer_->GetCurrentTime(); - EXPECT_GT(new_media_time, media_time); + // TODO: Replace it with EXPECT_GT once audio time reporting is more + // accurate. + EXPECT_GE(new_media_time, media_time); media_time = new_media_time; } } @@ -271,31 +264,30 @@ const int kFramesPerBuffer = 1024; - int frames_written = 0; - int preroll_frames = kDefaultSamplesPerSecond * - AudioRendererImpl::kPrerollTime / kSbTimeSecond; - audio_renderer_->Play(); - while (frames_written <= preroll_frames) { - SbMediaTime pts = frames_written / kDefaultSamplesPerSecond; + int frames_written = 0; + + while (audio_renderer_->IsSeekingInProgress()) { + SbMediaTime pts = + frames_written * kSbMediaTimeSecond / kDefaultSamplesPerSecond; WriteSample(CreateInputBuffer(pts)); CallConsumedCB(); SendDecoderOutput(CreateDecodedAudio(pts, kFramesPerBuffer)); frames_written += kFramesPerBuffer; } - EXPECT_FALSE(audio_renderer_->IsSeekingInProgress()); - WriteEndOfStream(); SendDecoderOutput(new DecodedAudio); SbMediaTime media_time = audio_renderer_->GetCurrentTime(); while (!audio_renderer_->IsEndOfStreamPlayed()) { - SbThreadSleep(AudioRendererImpl::kPrerollTime); + SbThreadSleep(kSbTimeMillisecond); SbMediaTime new_media_time = audio_renderer_->GetCurrentTime(); - EXPECT_GT(new_media_time, media_time); + // TODO: Replace it with EXPECT_GT once audio time reporting is more + // accurate. + EXPECT_GE(new_media_time, media_time); media_time = new_media_time; } } @@ -354,22 +346,19 @@ const int kFramesPerBuffer = 1024; int frames_written = 0; - int preroll_frames = kDefaultSamplesPerSecond * - AudioRendererImpl::kPrerollTime / kSbTimeSecond; - while (frames_written <= preroll_frames) { - SbMediaTime pts = frames_written / kDefaultSamplesPerSecond; + while (audio_renderer_->IsSeekingInProgress()) { + SbMediaTime pts = + frames_written * kSbMediaTimeSecond / kDefaultSamplesPerSecond; WriteSample(CreateInputBuffer(pts)); CallConsumedCB(); SendDecoderOutput(CreateDecodedAudio(pts, kFramesPerBuffer / 2)); frames_written += kFramesPerBuffer / 2; - pts = frames_written / kDefaultSamplesPerSecond; + pts = frames_written * kSbMediaTimeSecond / kDefaultSamplesPerSecond; SendDecoderOutput(CreateDecodedAudio(pts, kFramesPerBuffer / 2)); frames_written += kFramesPerBuffer / 2; } - EXPECT_FALSE(audio_renderer_->IsSeekingInProgress()); - WriteEndOfStream(); EXPECT_EQ(audio_renderer_->GetCurrentTime(), 0); @@ -382,9 +371,11 @@ SbMediaTime media_time = audio_renderer_->GetCurrentTime(); while (!audio_renderer_->IsEndOfStreamPlayed()) { - SbThreadSleep(AudioRendererImpl::kPrerollTime); + SbThreadSleep(kSbTimeMillisecond); SbMediaTime new_media_time = audio_renderer_->GetCurrentTime(); - EXPECT_GT(new_media_time, media_time); + // TODO: Replace it with EXPECT_GT once audio time reporting is more + // accurate. + EXPECT_GE(new_media_time, media_time); media_time = new_media_time; } } @@ -395,20 +386,21 @@ const int kFramesPerBuffer = 1024; int frames_written = 0; - int preroll_frames = kDefaultSamplesPerSecond * - AudioRendererImpl::kPrerollTime / kSbTimeSecond; - while (frames_written <= preroll_frames) { - SbMediaTime pts = frames_written / kDefaultSamplesPerSecond; + while (audio_renderer_->IsSeekingInProgress()) { + SbMediaTime pts = + frames_written * kSbMediaTimeSecond / kDefaultSamplesPerSecond; + SbMediaTime output_pts = pts; WriteSample(CreateInputBuffer(pts)); CallConsumedCB(); - frames_written += kFramesPerBuffer; + frames_written += kFramesPerBuffer / 2; + pts = frames_written * kSbMediaTimeSecond / kDefaultSamplesPerSecond; + WriteSample(CreateInputBuffer(pts)); + CallConsumedCB(); + frames_written += kFramesPerBuffer / 2; + SendDecoderOutput(CreateDecodedAudio(output_pts, kFramesPerBuffer)); } - SendDecoderOutput(CreateDecodedAudio(0, frames_written)); - - EXPECT_FALSE(audio_renderer_->IsSeekingInProgress()); - WriteEndOfStream(); EXPECT_EQ(audio_renderer_->GetCurrentTime(), 0); @@ -421,13 +413,16 @@ SbMediaTime media_time = audio_renderer_->GetCurrentTime(); while (!audio_renderer_->IsEndOfStreamPlayed()) { - SbThreadSleep(AudioRendererImpl::kPrerollTime); + SbThreadSleep(kSbTimeMillisecond); SbMediaTime new_media_time = audio_renderer_->GetCurrentTime(); - EXPECT_GT(new_media_time, media_time); + // TODO: Replace it with EXPECT_GT once audio time reporting is more + // accurate. + EXPECT_GE(new_media_time, media_time); media_time = new_media_time; } } +// TODO: Implement test for Seek() TEST_F(AudioRendererImplTest, Seek) {} } // namespace
diff --git a/src/starboard/shared/starboard/player/filter/wsola_internal.cc b/src/starboard/shared/starboard/player/filter/wsola_internal.cc index f1f32f5..3742d0a 100644 --- a/src/starboard/shared/starboard/player/filter/wsola_internal.cc +++ b/src/starboard/shared/starboard/player/filter/wsola_internal.cc
@@ -33,6 +33,12 @@ #include "starboard/memory.h" #include "starboard/shared/internal_only.h" +// TODO: Detect Neon on ARM platform and enable SIMD. +#if SB_IS(ARCH_X86) +#define USE_SIMD 1 +#include <xmmintrin.h> +#endif // SB_IS(ARCH_X86) + namespace starboard { namespace shared { namespace starboard { @@ -68,9 +74,53 @@ SB_DCHECK(frame_offset_a + num_frames <= a->frames()); SB_DCHECK(frame_offset_b + num_frames <= b->frames()); - SbMemorySet(dot_product, 0, sizeof(*dot_product) * a->channels()); const float* a_frames = reinterpret_cast<const float*>(a->buffer()); const float* b_frames = reinterpret_cast<const float*>(b->buffer()); + +// SIMD optimized variants can provide a massive speedup to this operation. +#if defined(USE_SIMD) + const int rem = num_frames % 4; + const int last_index = num_frames - rem; + const int channels = a->channels(); + for (int ch = 0; ch < channels; ++ch) { + const float* a_src = a_frames + frame_offset_a * a->channels() + ch; + const float* b_src = b_frames + frame_offset_b * b->channels() + ch; + +#if SB_IS(ARCH_X86) + // First sum all components. + __m128 m_sum = _mm_setzero_ps(); + for (int s = 0; s < last_index; s += 4) { + m_sum = _mm_add_ps( + m_sum, _mm_mul_ps(_mm_loadu_ps(a_src + s), _mm_loadu_ps(b_src + s))); + } + + // Reduce to a single float for this channel. Sadly, SSE1,2 doesn't have a + // horizontal sum function, so we have to condense manually. + m_sum = _mm_add_ps(_mm_movehl_ps(m_sum, m_sum), m_sum); + _mm_store_ss(dot_product + ch, + _mm_add_ss(m_sum, _mm_shuffle_ps(m_sum, m_sum, 1))); +#elif SB_IS(ARCH_ARM) + // First sum all components. + float32x4_t m_sum = vmovq_n_f32(0); + for (int s = 0; s < last_index; s += 4) + m_sum = vmlaq_f32(m_sum, vld1q_f32(a_src + s), vld1q_f32(b_src + s)); + + // Reduce to a single float for this channel. + float32x2_t m_half = vadd_f32(vget_high_f32(m_sum), vget_low_f32(m_sum)); + dot_product[ch] = vget_lane_f32(vpadd_f32(m_half, m_half), 0); +#endif // SB_IS(ARCH_X86) + } + + if (!rem) { + return; + } + num_frames = rem; + frame_offset_a += last_index; + frame_offset_b += last_index; +#else // defined(USE_SIMD) + SbMemorySet(dot_product, 0, sizeof(*dot_product) * a->channels()); +#endif // defined(USE_SIMD) + for (int k = 0; k < a->channels(); ++k) { const float* ch_a = a_frames + frame_offset_a * a->channels() + k; const float* ch_b = b_frames + frame_offset_b * b->channels() + k;
diff --git a/src/starboard/shared/starboard/player/job_queue.h b/src/starboard/shared/starboard/player/job_queue.h index 361d82e..53b7576 100644 --- a/src/starboard/shared/starboard/player/job_queue.h +++ b/src/starboard/shared/starboard/player/job_queue.h
@@ -53,6 +53,7 @@ void Schedule(Closure closure, SbTimeMonotonic delay = 0) { job_queue_->Schedule(closure, this, delay); } + void Remove(Closure closure) { job_queue_->Remove(closure); } void CancelPendingJobs() { job_queue_->RemoveJobsByToken(this); } private:
diff --git a/src/starboard/shared/uwp/application_uwp.cc b/src/starboard/shared/uwp/application_uwp.cc index 6ebcda9..acc7057 100644 --- a/src/starboard/shared/uwp/application_uwp.cc +++ b/src/starboard/shared/uwp/application_uwp.cc
@@ -26,9 +26,11 @@ #include "starboard/event.h" #include "starboard/log.h" +#include "starboard/mutex.h" #include "starboard/shared/starboard/application.h" #include "starboard/shared/starboard/audio_sink/audio_sink_internal.h" #include "starboard/shared/starboard/player/video_frame_internal.h" +#include "starboard/shared/uwp/async_utils.h" #include "starboard/shared/uwp/window_internal.h" #include "starboard/shared/win32/thread_private.h" #include "starboard/shared/win32/wchar_utils.h" @@ -42,6 +44,7 @@ using starboard::shared::starboard::CommandLine; using starboard::shared::uwp::ApplicationUwp; using starboard::shared::uwp::GetArgvZero; +using starboard::shared::uwp::WaitForResult; using starboard::shared::win32::stringToPlatformString; using starboard::shared::win32::wchar_tToUTF8; using starboard::shared::starboard::player::VideoFrame; @@ -59,6 +62,13 @@ using Windows::Foundation::TimeSpan; using Windows::Foundation::TypedEventHandler; using Windows::Foundation::Uri; +using Windows::Media::Protection::HdcpProtection; +using Windows::Media::Protection::HdcpSession; +using Windows::Media::Protection::HdcpSetProtectionResult; +using Windows::Networking::Connectivity::ConnectionProfile; +using Windows::Networking::Connectivity::NetworkConnectivityLevel; +using Windows::Networking::Connectivity::NetworkInformation; +using Windows::Networking::Connectivity::NetworkStatusChangedEventHandler; using Windows::System::Threading::ThreadPoolTimer; using Windows::System::Threading::TimerElapsedHandler; using Windows::System::UserAuthenticationStatus; @@ -203,7 +213,7 @@ ref class App sealed : public IFrameworkView { public: - App() : previously_activated_(false) {} + App() : previously_activated_(false), has_internet_access_(false) {} // IFrameworkView methods. virtual void Initialize(CoreApplicationView^ applicationView) { @@ -215,7 +225,37 @@ applicationView->Activated += ref new TypedEventHandler<CoreApplicationView^, IActivatedEventArgs^>( this, &App::OnActivated); + + has_internet_access_ = HasInternetAccess(); + + SB_LOG(INFO) << "Has internet access? " << std::boolalpha + << has_internet_access_; + + NetworkInformation::NetworkStatusChanged += + ref new NetworkStatusChangedEventHandler(this, &App::OnNetworkChanged); } + + void OnNetworkChanged(Platform::Object^ sender) { + SB_UNREFERENCED_PARAMETER(sender); + bool has_internet_access = HasInternetAccess(); + + if (has_internet_access == has_internet_access_) { + return; + } + + SB_LOG(INFO) << "NetworkChanged. Has internet access? " << std::boolalpha + << has_internet_access; + + has_internet_access_ = has_internet_access; + + const SbEventType network_event = + (has_internet_access ? kSbEventTypeNetworkConnect + : kSbEventTypeNetworkDisconnect); + + ApplicationUwp::Get()->Inject( + new ApplicationUwp::Event(network_event, nullptr, nullptr)); + } + virtual void SetWindow(CoreWindow^ window) { ApplicationUwp::Get()->SetCoreWindow(window); window->KeyUp += ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>( @@ -363,7 +403,33 @@ } } + bool HasInternetAccess() { + ConnectionProfile^ connection_profile = + NetworkInformation::GetInternetConnectionProfile(); + + if (connection_profile == nullptr) { + return false; + } + const NetworkConnectivityLevel connectivity_level = + connection_profile->GetNetworkConnectivityLevel(); + + switch (connectivity_level) { + case NetworkConnectivityLevel::InternetAccess: + case NetworkConnectivityLevel::ConstrainedInternetAccess: + return true; + case NetworkConnectivityLevel::None: + case NetworkConnectivityLevel::LocalAccess: + return false; + } + SB_NOTREACHED() << "Unknown network connectivity level found " + << sbwin32::platformStringToString( + connectivity_level.ToString()); + + return false; + } + bool previously_activated_; + bool has_internet_access_; // Only valid if previously_activated_ is true ActivationKind previous_activation_kind_; std::vector<std::string> args_; @@ -448,6 +514,23 @@ return false; } +Windows::Media::Protection::HdcpSession^ ApplicationUwp::GetHdcpSession() { + if (!hdcp_session_) { + hdcp_session_ = ref new HdcpSession(); + } + return hdcp_session_; +} + +void ApplicationUwp::ResetHdcpSession() { + // delete will call the destructor, but not free memory. + // The destructor is called explicitly so that HDCP session can be + // torn down immediately. + if (hdcp_session_) { + delete hdcp_session_; + hdcp_session_ = nullptr; + } +} + void ApplicationUwp::Inject(Application::Event* event) { RunInMainThreadAsync([this, event]() { bool result = DispatchAndDelete(event); @@ -529,6 +612,54 @@ return stringToPlatformString(localized_strings_.GetString(id, fallback)); } +bool ApplicationUwp::IsHdcpOn() { + ::starboard::ScopedLock lock(hdcp_session_mutex_); + + return GetHdcpSession()->IsEffectiveProtectionAtLeast(HdcpProtection::On); +} + +bool ApplicationUwp::TurnOnHdcp() { + HdcpSetProtectionResult protection_result; + { + ::starboard::ScopedLock lock(hdcp_session_mutex_); + + protection_result = + WaitForResult(GetHdcpSession()->SetDesiredMinProtectionAsync( + HdcpProtection::On)); + } + + if (IsHdcpOn()) { + return true; + } + + // If the operation did not have intended result, log something. + switch (protection_result) { + case HdcpSetProtectionResult::Success: + SB_LOG(INFO) << "Successfully set HDCP."; + break; + case HdcpSetProtectionResult::NotSupported: + SB_LOG(INFO) << "HDCP is not supported."; + break; + case HdcpSetProtectionResult::TimedOut: + SB_LOG(INFO) << "Setting HDCP timed out."; + break; + case HdcpSetProtectionResult::UnknownFailure: + SB_LOG(INFO) << "Unknown failure returned while setting HDCP."; + break; + } + + return false; +} + +bool ApplicationUwp::TurnOffHdcp() { + { + ::starboard::ScopedLock lock(hdcp_session_mutex_); + ResetHdcpSession(); + } + bool success = !SbMediaIsOutputProtected(); + return success; +} + void ApplicationUwp::AcceptFrame(SbPlayer player, const scoped_refptr<VideoFrame>& frame, int x,
diff --git a/src/starboard/shared/uwp/application_uwp.h b/src/starboard/shared/uwp/application_uwp.h index fda18e2..e62305c 100644 --- a/src/starboard/shared/uwp/application_uwp.h +++ b/src/starboard/shared/uwp/application_uwp.h
@@ -32,6 +32,8 @@ namespace shared { namespace uwp { +using Windows::Media::Protection::HdcpSession; + // Returns win32's GetModuleFileName(). For cases where we'd like an argv[0]. std::string GetArgvZero(); @@ -97,6 +99,12 @@ Platform::String^ GetString(const char* id, const char* fallback) const; + bool IsHdcpOn(); + // Returns true on success. + bool TurnOnHdcp(); + // Returns true on success. + bool TurnOffHdcp(); + private: // --- Application overrides --- bool IsStartImmediate() SB_OVERRIDE { return false; } @@ -116,6 +124,11 @@ int width, int height) SB_OVERRIDE; + // These two functions should only be called while holding + // |hdcp_session_mutex_|. + Windows::Media::Protection::HdcpSession^ GetHdcpSession(); + void ResetHdcpSession(); + // The single open window, if any. SbWindow window_; Platform::Agile<Windows::UI::Core::CoreWindow> core_window_; @@ -123,9 +136,13 @@ shared::starboard::LocalizedStrings localized_strings_; Mutex mutex_; - // Locked by mutex_ + // |timer_event_map_| is locked by |mutex_|. std::unordered_map<SbEventId, Windows::System::Threading::ThreadPoolTimer^> timer_event_map_; + + // |hdcp_session_| is locked by |hdcp_session_mutex_|. + Mutex hdcp_session_mutex_; + Windows::Media::Protection::HdcpSession^ hdcp_session_; }; } // namespace uwp
diff --git a/src/starboard/shared/uwp/async_utils.h b/src/starboard/shared/uwp/async_utils.h index 7e01f82..ad4d4e7 100644 --- a/src/starboard/shared/uwp/async_utils.h +++ b/src/starboard/shared/uwp/async_utils.h
@@ -34,6 +34,7 @@ concurrency::create_task(operation, task_continuation_context::use_arbitrary()) .then([&event](TResult result) { + SB_UNREFERENCED_PARAMETER(result); BOOL success = SetEvent(event); SB_DCHECK(success); }, task_continuation_context::use_arbitrary());
diff --git a/src/starboard/shared/uwp/media_is_output_protected.cc b/src/starboard/shared/uwp/media_is_output_protected.cc new file mode 100644 index 0000000..117f65a --- /dev/null +++ b/src/starboard/shared/uwp/media_is_output_protected.cc
@@ -0,0 +1,21 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/media.h" + +#include "starboard/shared/uwp/application_uwp.h" + +bool SbMediaIsOutputProtected() { + return starboard::shared::uwp::ApplicationUwp::Get()->IsHdcpOn(); +}
diff --git a/src/starboard/shared/uwp/media_set_output_protection.cc b/src/starboard/shared/uwp/media_set_output_protection.cc new file mode 100644 index 0000000..1a3cc31 --- /dev/null +++ b/src/starboard/shared/uwp/media_set_output_protection.cc
@@ -0,0 +1,50 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/media.h" + +#include "starboard/log.h" +#include "starboard/shared/uwp/application_uwp.h" +#include "starboard/time.h" + +using starboard::shared::uwp::ApplicationUwp; + +bool SbMediaSetOutputProtection(bool should_enable_dhcp) { + bool is_hdcp_on = SbMediaIsOutputProtected(); + + if (is_hdcp_on == should_enable_dhcp) { + return true; + } + + SB_LOG(INFO) << "Attempting to " + << (should_enable_dhcp ? "enable" : "disable") + << " output protection. Current status: " + << (is_hdcp_on ? "enabled" : "disabled"); + SbTimeMonotonic tick = SbTimeGetMonotonicNow(); + + bool hdcp_success = false; + if (should_enable_dhcp) { + hdcp_success = ApplicationUwp::Get()->TurnOnHdcp(); + } else { + hdcp_success = ApplicationUwp::Get()->TurnOffHdcp(); + } + + is_hdcp_on = (hdcp_success ? should_enable_dhcp : !should_enable_dhcp); + + SbTimeMonotonic tock = SbTimeGetMonotonicNow(); + SB_LOG(INFO) << "Output protection is " + << (is_hdcp_on ? "enabled" : "disabled") << ". Toggling HDCP took " + << (tock - tick) / kSbTimeMillisecond << " milliseconds."; + return hdcp_success; +}
diff --git a/src/starboard/shared/uwp/starboard_platform.gypi b/src/starboard/shared/uwp/starboard_platform.gypi index 85f0f72..8d082dc 100644 --- a/src/starboard/shared/uwp/starboard_platform.gypi +++ b/src/starboard/shared/uwp/starboard_platform.gypi
@@ -19,6 +19,8 @@ 'application_uwp.h', 'async_utils.h', 'get_home_directory.cc', + 'media_is_output_protected.cc', + 'media_set_output_protection.cc', 'system_clear_platform_error.cc', 'system_get_device_type.cc', 'system_get_property.cc',
diff --git a/src/starboard/shared/win32/drm_system_playready.cc b/src/starboard/shared/win32/drm_system_playready.cc index 38fb628..bfa8dcd 100644 --- a/src/starboard/shared/win32/drm_system_playready.cc +++ b/src/starboard/shared/win32/drm_system_playready.cc
@@ -184,8 +184,9 @@ } if (item.second->IsHDCPRequired()) { - // TODO: Enforce HDCP - // if (!is_hdcp_enabled()) { return kFailure; } + if (!SbMediaSetOutputProtection(true)) { + return kFailure; + } } return kSuccess;
diff --git a/src/starboard/shared/win32/media_is_output_protected.cc b/src/starboard/shared/win32/media_is_output_protected.cc index 213e5f5..03e6642 100644 --- a/src/starboard/shared/win32/media_is_output_protected.cc +++ b/src/starboard/shared/win32/media_is_output_protected.cc
@@ -1,21 +1,20 @@ -// Copyright 2017 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "starboard/media.h" - -bool SbMediaIsOutputProtected() { - // Pretend that HDCP is always on. - // TODO: Fix this with proper HDCP query. - return true; -} +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/media.h" + +bool SbMediaIsOutputProtected() { + SB_NOTIMPLEMENTED(); + return false; +}
diff --git a/src/starboard/shared/x11/window_internal.h b/src/starboard/shared/x11/window_internal.h index 5dcc153..6740a9e 100644 --- a/src/starboard/shared/x11/window_internal.h +++ b/src/starboard/shared/x11/window_internal.h
@@ -15,6 +15,7 @@ #ifndef STARBOARD_SHARED_X11_WINDOW_INTERNAL_H_ #define STARBOARD_SHARED_X11_WINDOW_INTERNAL_H_ +#include <X11/extensions/Xrender.h> #include <X11/Xlib.h> #include "starboard/configuration.h" @@ -22,18 +23,12 @@ #include "starboard/shared/starboard/player/video_frame_internal.h" #include "starboard/window.h" -#if SB_TRUE || SB_IS(PLAYER_PUNCHED_OUT) -#include <X11/extensions/Xrender.h> -#endif // SB_TRUE || - // SB_IS(PLAYER_PUNCHED_OUT) - struct SbWindowPrivate { SbWindowPrivate(Display* display, const SbWindowOptions* options); ~SbWindowPrivate(); Window window; -#if SB_TRUE || SB_IS(PLAYER_PUNCHED_OUT) typedef ::starboard::shared::starboard::player::VideoFrame VideoFrame; // Composites graphics and the given video frame video for this window. In @@ -80,8 +75,6 @@ // A cached XRender Picture wrapper for |gl_window|. Picture gl_picture; -#endif // SB_TRUE || - // SB_IS(PLAYER_PUNCHED_OUT) // The display that this window was created from. Display* display;
diff --git a/src/starboard/win/shared/gyp_configuration.gypi b/src/starboard/win/shared/gyp_configuration.gypi index 6b18f49..c112351 100644 --- a/src/starboard/win/shared/gyp_configuration.gypi +++ b/src/starboard/win/shared/gyp_configuration.gypi
@@ -139,6 +139,11 @@ # Enable some warnings, even those that are disabled by default. # See https://msdn.microsoft.com/en-us/library/23k5d385.aspx 'WarningLevel': '4', + 'AdditionalOptions': [ + # Warn if an enumeration value is unhandled in switch (C4062). + # This warning is off by default, so it must be turned on explicitly. + '/w44062', + ], }, }, }],
diff --git a/src/starboard/win/shared/starboard_platform.gypi b/src/starboard/win/shared/starboard_platform.gypi index 42dacc9..2a4362a 100644 --- a/src/starboard/win/shared/starboard_platform.gypi +++ b/src/starboard/win/shared/starboard_platform.gypi
@@ -246,7 +246,6 @@ '<(DEPTH)/starboard/shared/stub/cryptography_transform.cc', '<(DEPTH)/starboard/shared/stub/image_decode.cc', '<(DEPTH)/starboard/shared/stub/image_is_decode_supported.cc', - '<(DEPTH)/starboard/shared/stub/media_set_output_protection.cc', '<(DEPTH)/starboard/shared/stub/system_get_stack.cc', '<(DEPTH)/starboard/shared/stub/system_get_total_gpu_memory.cc', '<(DEPTH)/starboard/shared/stub/system_get_used_gpu_memory.cc',
diff --git a/src/starboard/win/win32/starboard_platform.gypi b/src/starboard/win/win32/starboard_platform.gypi index 2335dbd..b96b643 100644 --- a/src/starboard/win/win32/starboard_platform.gypi +++ b/src/starboard/win/win32/starboard_platform.gypi
@@ -33,6 +33,8 @@ '<(DEPTH)/starboard/shared/starboard/system_request_unpause.cc', '<(DEPTH)/starboard/shared/stub/decode_target_get_info.cc', '<(DEPTH)/starboard/shared/stub/decode_target_release.cc', + '<(DEPTH)/starboard/shared/stub/media_is_output_protected.cc', + '<(DEPTH)/starboard/shared/stub/media_set_output_protection.cc', '<(DEPTH)/starboard/win/shared/system_get_path.cc', '<@(uwp_incompatible_win32)', ],
diff --git a/src/third_party/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp b/src/third_party/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp index 4f10d28..7559686 100644 --- a/src/third_party/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp +++ b/src/third_party/angle/src/libANGLE/renderer/d3d/SurfaceD3D.cpp
@@ -107,8 +107,7 @@ out = sizeof(mDXGIBuffer); hr = static_cast<ID3D11DeviceChild*>(mD3DTexture)-> GetPrivateData(kCobaltDxgiBuffer, &out, &mDXGIBuffer); - ASSERT(SUCCEEDED(hr)); - if (mDXGIBuffer != nullptr) { + if (SUCCEEDED(hr) && mDXGIBuffer != nullptr) { mDXGIBuffer->AddRef(); } break; @@ -148,8 +147,11 @@ D3D11_TEXTURE2D_DESC texture_desc; d3Texture->GetDesc(&texture_desc); - if ((texture_desc.BindFlags & D3D11_BIND_RENDER_TARGET) == 0) + + if (texture_desc.Format == DXGI_FORMAT_NV12) { + // NV12 textures cannot be rendered to, + // so don't proceed to making a swap chain. return egl::Error(EGL_SUCCESS); } }
diff --git a/src/third_party/libevent/kqueue.c b/src/third_party/libevent/kqueue.c index 556b73c..bcad213 100644 --- a/src/third_party/libevent/kqueue.c +++ b/src/third_party/libevent/kqueue.c
@@ -30,6 +30,22 @@ #include "config.h" #endif +#ifdef STARBOARD +#include "libevent-starboard.h" + +#include <assert.h> +#include <sys/types.h> +#include <sys/event.h> +#include <sys/socket.h> + +// Use libevent's local compatibility versions of these. +#include "third_party/libevent/compat/sys/queue.h" +#include "third_party/libevent/compat/sys/_libevent_time.h" + +// Include Starboard poems after all system headers. +#include "starboard/client_porting/poem/stdio_poem.h" +#include "starboard/client_porting/poem/string_poem.h" +#else // STARBOARD #define _GNU_SOURCE 1 #include <sys/types.h> @@ -50,6 +66,7 @@ #ifdef HAVE_INTTYPES_H #include <inttypes.h> #endif +#endif // STARBOARD /* Some platforms apparently define the udata field of struct kevent as * intptr_t, whereas others define it as void*. There doesn't seem to be an @@ -62,6 +79,9 @@ #include "event.h" #include "event-internal.h" +#ifndef STARBOARD +#include "evsignal.h" +#endif // STARBOARD #include "log.h" #define EVLIST_X_KQINKERNEL 0x1000 @@ -148,11 +168,12 @@ * stick an error in events[0]. If kqueue is broken, then * kevent will fail. */ + // https://github.com/azat/libevent/commit/a9f6f32e7773347334149d89bc58bf9b984e1714 if (kevent(kq, kqueueop->changes, 1, kqueueop->events, NEVENT, NULL) != 1 || kqueueop->events[0].ident != -1 || - kqueueop->events[0].flags != EV_ERROR) { - event_warn("%s: detected broken kqueue; not using.", __func__); + !(kqueueop->events[0].flags & EV_ERROR)) { + event_warn("%s: detected broken kqueue; not using.", __func__); free(kqueueop->changes); free(kqueueop->events); free(kqueueop); @@ -234,7 +255,6 @@ kqop->nchanges = 0; if (res == -1) { if (errno != EINTR) { - event_warn("kevent"); return (-1); }
diff --git a/src/third_party/libevent/libevent.gyp b/src/third_party/libevent/libevent.gyp index 3b9f4ed..d62a6c9 100644 --- a/src/third_party/libevent/libevent.gyp +++ b/src/third_party/libevent/libevent.gyp
@@ -24,6 +24,7 @@ 'evrpc.c', 'evutil.c', 'http.c', + 'kqueue.c', 'log.c', 'poll.c', 'select.c', @@ -54,10 +55,22 @@ 'include_dirs': [ 'starboard' ], 'conditions': [ [ 'sb_libevent_method == "epoll"', { - 'sources!': [ 'poll.c' ], + 'sources!': [ + 'kqueue.c', + 'poll.c', + ], }], [ 'sb_libevent_method == "poll"', { - 'sources!': [ 'epoll.c' ], + 'sources!': [ + 'epoll.c', + 'kqueue.c', + ], + }], + [ 'sb_libevent_method == "kqueue"', { + 'sources!': [ + 'epoll.c', + 'poll.c', + ], }], [ 'target_os == "linux"', { 'sources': [ 'epoll_sub.c' ], @@ -74,6 +87,10 @@ 'include_dirs': [ 'starboard/linux' ], } ], + [ 'target_os == "ios"', { + 'include_dirs': [ 'starboard/darwin' ], + } + ], [ 'target_os == "orbis"', { 'include_dirs': [ 'starboard/ps4' ], }
diff --git a/src/third_party/libevent/starboard/darwin/libevent-starboard.h b/src/third_party/libevent/starboard/darwin/libevent-starboard.h new file mode 100644 index 0000000..e1d5a42 --- /dev/null +++ b/src/third_party/libevent/starboard/darwin/libevent-starboard.h
@@ -0,0 +1,281 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LIBEVENT_STARBOARD_H_ +#define _LIBEVENT_STARBOARD_H_ + +#include "starboard/atomic.h" +#include "starboard/types.h" + +#include <errno.h> +#include <sys/time.h> +#include <time.h> +#include <unistd.h> + +/* Define if clock_gettime is available in libc */ +/* #undef _EVENT_DNS_USE_CPU_CLOCK_FOR_ID */ + +/* Define is no secure id variant is available */ +#define _EVENT_DNS_USE_GETTIMEOFDAY_FOR_ID 1 + +/* Define to 1 if you have the `clock_gettime' function. */ +/* #undef _EVENT_HAVE_CLOCK_GETTIME */ + +/* Define if /dev/poll is available */ +/* #undef _EVENT_HAVE_DEVPOLL */ + +/* Define to 1 if you have the <dlfcn.h> header file. */ +#define _EVENT_HAVE_DLFCN_H 1 + +/* Define if your system supports the epoll system calls */ +/* #undef _EVENT_HAVE_EPOLL */ + +/* Define to 1 if you have the `epoll_ctl' function. */ +/* #undef _EVENT_HAVE_EPOLL_CTL */ + +/* Define if your system supports event ports */ +/* #undef _EVENT_HAVE_EVENT_PORTS */ + +/* Define to 1 if you have the `fcntl' function. */ +#define _EVENT_HAVE_FCNTL 1 + +/* Define to 1 if you have the <fcntl.h> header file. */ +#define _EVENT_HAVE_FCNTL_H 1 + +/* Define to 1 if the system has the type `fd_mask'. */ +#define _EVENT_HAVE_FD_MASK 1 + +/* Define to 1 if you have the `getaddrinfo' function. */ +#define _EVENT_HAVE_GETADDRINFO 1 + +/* Define to 1 if you have the `getegid' function. */ +#define _EVENT_HAVE_GETEGID 1 + +/* Define to 1 if you have the `geteuid' function. */ +#define _EVENT_HAVE_GETEUID 1 + +/* Define to 1 if you have the `getnameinfo' function. */ +#define _EVENT_HAVE_GETNAMEINFO 1 + +/* Define to 1 if you have the `gettimeofday' function. */ +#define _EVENT_HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have the `inet_ntop' function. */ +#define _EVENT_HAVE_INET_NTOP 1 + +/* Define to 1 if you have the <inttypes.h> header file. */ +#define _EVENT_HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `issetugid' function. */ +#define _EVENT_HAVE_ISSETUGID 1 + +/* Define to 1 if you have the `kqueue' function. */ +#define _EVENT_HAVE_KQUEUE 1 + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +/* #undef _EVENT_HAVE_LIBNSL */ + +/* Define to 1 if you have the `resolv' library (-lresolv). */ +#define _EVENT_HAVE_LIBRESOLV 1 + +/* Define to 1 if you have the `rt' library (-lrt). */ +/* #undef _EVENT_HAVE_LIBRT */ + +/* Define to 1 if you have the `socket' library (-lsocket). */ +/* #undef _EVENT_HAVE_LIBSOCKET */ + +/* Define to 1 if you have the <memory.h> header file. */ +#define _EVENT_HAVE_MEMORY_H 1 + +/* Define to 1 if you have the <netinet/in6.h> header file. */ +/* #undef _EVENT_HAVE_NETINET_IN6_H */ + +/* Define to 1 if you have the `poll' function. */ +#define _EVENT_HAVE_POLL 1 + +/* Define to 1 if you have the <poll.h> header file. */ +#define _EVENT_HAVE_POLL_H 1 + +/* Define to 1 if you have the `port_create' function. */ +/* #undef _EVENT_HAVE_PORT_CREATE */ + +/* Define to 1 if you have the <port.h> header file. */ +/* #undef _EVENT_HAVE_PORT_H */ + +/* Define to 1 if you have the `select' function. */ +#define _EVENT_HAVE_SELECT 1 + +/* Define if F_SETFD is defined in <fcntl.h> */ +#define _EVENT_HAVE_SETFD 1 + +/* Define to 1 if you have the `sigaction' function. */ +#define _EVENT_HAVE_SIGACTION 1 + +/* Define to 1 if you have the `signal' function. */ +#define _EVENT_HAVE_SIGNAL 1 + +/* Define to 1 if you have the <signal.h> header file. */ +#define _EVENT_HAVE_SIGNAL_H 1 + +/* Define to 1 if you have the <stdarg.h> header file. */ +#define _EVENT_HAVE_STDARG_H 1 + +/* Define to 1 if you have the <stdint.h> header file. */ +#define _EVENT_HAVE_STDINT_H 1 + +/* Define to 1 if you have the <stdlib.h> header file. */ +#define _EVENT_HAVE_STDLIB_H 1 + +/* Define to 1 if you have the <strings.h> header file. */ +#define _EVENT_HAVE_STRINGS_H 1 + +/* Define to 1 if you have the <string.h> header file. */ +#define _EVENT_HAVE_STRING_H 1 + +/* Define to 1 if you have the `strlcpy' function. */ +#define _EVENT_HAVE_STRLCPY 1 + +/* Define to 1 if you have the `strsep' function. */ +#define _EVENT_HAVE_STRSEP 1 + +/* Define to 1 if you have the `strtok_r' function. */ +#define _EVENT_HAVE_STRTOK_R 1 + +/* Define to 1 if you have the `strtoll' function. */ +#define _EVENT_HAVE_STRTOLL 1 + +/* Define to 1 if the system has the type `struct in6_addr'. */ +#define _EVENT_HAVE_STRUCT_IN6_ADDR 1 + +/* Define to 1 if you have the <sys/devpoll.h> header file. */ +/* #undef _EVENT_HAVE_SYS_DEVPOLL_H */ + +/* Define to 1 if you have the <sys/epoll.h> header file. */ +/* #undef _EVENT_HAVE_SYS_EPOLL_H */ + +/* Define to 1 if you have the <sys/event.h> header file. */ +#define _EVENT_HAVE_SYS_EVENT_H 1 + +/* Define to 1 if you have the <sys/ioctl.h> header file. */ +#define _EVENT_HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the <sys/param.h> header file. */ +#define _EVENT_HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the <sys/queue.h> header file. */ +#define _EVENT_HAVE_SYS_QUEUE_H 1 + +/* Define to 1 if you have the <sys/select.h> header file. */ +#define _EVENT_HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the <sys/socket.h> header file. */ +#define _EVENT_HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#define _EVENT_HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the <sys/time.h> header file. */ +#define _EVENT_HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the <sys/types.h> header file. */ +#define _EVENT_HAVE_SYS_TYPES_H 1 + +/* Define if TAILQ_FOREACH is defined in <sys/queue.h> */ +#define _EVENT_HAVE_TAILQFOREACH 1 + +/* Define if timeradd is defined in <sys/time.h> */ +#define _EVENT_HAVE_TIMERADD 1 + +/* Define if timerclear is defined in <sys/time.h> */ +#define _EVENT_HAVE_TIMERCLEAR 1 + +/* Define if timercmp is defined in <sys/time.h> */ +#define _EVENT_HAVE_TIMERCMP 1 + +/* Define if timerisset is defined in <sys/time.h> */ +#define _EVENT_HAVE_TIMERISSET 1 + +/* Define to 1 if the system has the type `uint16_t'. */ +#define _EVENT_HAVE_UINT16_T 1 + +/* Define to 1 if the system has the type `uint32_t'. */ +#define _EVENT_HAVE_UINT32_T 1 + +/* Define to 1 if the system has the type `uint64_t'. */ +#define _EVENT_HAVE_UINT64_T 1 + +/* Define to 1 if the system has the type `uint8_t'. */ +#define _EVENT_HAVE_UINT8_T 1 + +/* Define to 1 if you have the <unistd.h> header file. */ +#define _EVENT_HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `vasprintf' function. */ +#define _EVENT_HAVE_VASPRINTF 1 + +/* Define if kqueue works correctly with pipes */ +#define HAVE_WORKING_KQUEUE 1 +#define _EVENT_HAVE_WORKING_KQUEUE 1 + +/* Name of package */ +#define _EVENT_PACKAGE "libevent" + +/* Define to the address where bug reports for this package should be sent. */ +#define _EVENT_PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define _EVENT_PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define _EVENT_PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define _EVENT_PACKAGE_TARNAME "" + +/* Define to the version of this package. */ +#define _EVENT_PACKAGE_VERSION "" + +/* Define to 1 if you have the ANSI C header files. */ +#define _EVENT_STDC_HEADERS 1 + +/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */ +#define _EVENT_TIME_WITH_SYS_TIME 1 + +/* Version number of package */ +#define _EVENT_VERSION "1.4.13-stable" + +/* Define to appropriate substitue if compiler doesnt have __func__ */ +/* #undef _EVENT___func__ */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef _EVENT_const */ + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef _EVENT___cplusplus +/* #undef _EVENT_inline */ +#endif + +/* Define to `int' if <sys/types.h> does not define. */ +/* #undef _EVENT_pid_t */ + +/* Define to `unsigned int' if <sys/types.h> does not define. */ +/* #undef _EVENT_size_t */ + +/* Define to unsigned int if you dont have it */ +/* #undef _EVENT_socklen_t */ + +#endif // _LIBEVENT_STARBOARD_H_
diff --git a/src/third_party/mozjs/mfbt/Assertions.h b/src/third_party/mozjs/mfbt/Assertions.h index a520892..5e3f391 100644 --- a/src/third_party/mozjs/mfbt/Assertions.h +++ b/src/third_party/mozjs/mfbt/Assertions.h
@@ -154,7 +154,7 @@ * in release builds as well as debug builds. But if the failure is one that * should be debugged and fixed, MOZ_ASSERT is generally preferable. */ -#if defined(STARBOARD) && (SB_API_VERSION >= 4) +#if defined(STARBOARD) # define MOZ_CRASH() \ do { \ *((volatile int*) NULL) = 123; \
diff --git a/src/third_party/openssl/openssl/crypto/evp/e_aes.c b/src/third_party/openssl/openssl/crypto/evp/e_aes.c index fb0d026..96b6934 100644 --- a/src/third_party/openssl/openssl/crypto/evp/e_aes.c +++ b/src/third_party/openssl/openssl/crypto/evp/e_aes.c
@@ -112,7 +112,7 @@ # define MAXBITCHUNK ((size_t)1<<(sizeof(size_t)*8-4)) -#if defined(OPENSSL_SYS_STARBOARD) && SB_API_VERSION >= 4 +#if defined(OPENSSL_SYS_STARBOARD) static bool sb_translate_mode(int flags, SbCryptographyBlockCipherMode *mode) { switch (flags & EVP_CIPH_MODE) { case EVP_CIPH_CBC_MODE: @@ -338,7 +338,7 @@ return 1; \ } -#else // defined(OPENSSL_SYS_STARBOARD) && SB_API_VERSION >= 4 +#else // defined(OPENSSL_SYS_STARBOARD) static inline bool sb_has_stream(EVP_CIPHER_CTX *context) { return false; @@ -409,7 +409,7 @@ #define SB_TRY_CIPHER(context, out, in, len) -#endif // defined(OPENSSL_SYS_STARBOARD) && SB_API_VERSION >= 4 +#endif // defined(OPENSSL_SYS_STARBOARD) # ifdef VPAES_ASM int vpaes_set_encrypt_key(const unsigned char *userKey, int bits, @@ -963,11 +963,11 @@ static int aes_gcm_cleanup(EVP_CIPHER_CTX *c) { EVP_AES_GCM_CTX *gctx = c->cipher_data; -#if defined(OPENSSL_SYS_STARBOARD) && SB_API_VERSION >= 4 +#if defined(OPENSSL_SYS_STARBOARD) SbCryptographyDestroyTransformer(gctx->gcm.gcm_transformer); SbCryptographyDestroyTransformer(gctx->gcm.ctr_transformer); SbCryptographyDestroyTransformer(gctx->gcm.ecb_transformer); -#endif // defined(OPENSSL_SYS_STARBOARD) && SB_API_VERSION >= 4 +#endif // defined(OPENSSL_SYS_STARBOARD) OPENSSL_cleanse(&gctx->gcm, sizeof(gctx->gcm)); if (gctx->iv != c->iv) OPENSSL_free(gctx->iv); @@ -1003,7 +1003,7 @@ gctx->taglen = -1; gctx->iv_gen = 0; gctx->tls_aad_len = -1; -#if defined(OPENSSL_SYS_STARBOARD) && SB_API_VERSION >= 4 +#if defined(OPENSSL_SYS_STARBOARD) gctx->ctr = NULL; gctx->gcm.gcm_transformer = kSbCryptographyInvalidTransformer; gctx->gcm.ctr_transformer = kSbCryptographyInvalidTransformer;
diff --git a/src/third_party/openssl/openssl/crypto/evp/evp.h b/src/third_party/openssl/openssl/crypto/evp/evp.h index 6e50fa7..7f03387 100644 --- a/src/third_party/openssl/openssl/crypto/evp/evp.h +++ b/src/third_party/openssl/openssl/crypto/evp/evp.h
@@ -436,7 +436,7 @@ int buf_len; /* number we have left */ unsigned char oiv[EVP_MAX_IV_LENGTH]; /* original iv */ unsigned char iv[EVP_MAX_IV_LENGTH]; /* working iv */ -#if defined(OPENSSL_SYS_STARBOARD) && SB_API_VERSION >= 4 +#if defined(OPENSSL_SYS_STARBOARD) // A handle to a transformer for the full stream cipher. Intermediate state // is kept internally to the transformer, so fields like |iv| and |num| are // not used. @@ -444,7 +444,7 @@ // This is not used for GCM. The GCM transformer is inside the GCM context, // which is stored as the |cipher_data| for the GCM cipher. SbCryptographyTransformer stream_transformer; -#endif // defined(STARBOARD) && SB_API_VERSION >= 4 +#endif // defined(STARBOARD) unsigned char buf[EVP_MAX_BLOCK_LENGTH]; /* saved partial block */ int num; /* used by cfb/ofb/ctr mode */ void *app_data; /* application stuff */
diff --git a/src/third_party/openssl/openssl/crypto/evp/evp_enc.c b/src/third_party/openssl/openssl/crypto/evp/evp_enc.c index 3021e52..8e85db5 100644 --- a/src/third_party/openssl/openssl/crypto/evp/evp_enc.c +++ b/src/third_party/openssl/openssl/crypto/evp/evp_enc.c
@@ -548,9 +548,9 @@ int EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *c) { -#if defined(OPENSSL_SYS_STARBOARD) && SB_API_VERSION >= 4 +#if defined(OPENSSL_SYS_STARBOARD) SbCryptographyDestroyTransformer(c->stream_transformer); -#endif // defined(OPENSSL_SYS_STARBOARD) && SB_API_VERSION >= 4 +#endif // defined(OPENSSL_SYS_STARBOARD) #ifndef OPENSSL_FIPS if (c->cipher != NULL) { if (c->cipher->cleanup && !c->cipher->cleanup(c))
diff --git a/src/third_party/openssl/openssl/crypto/modes/gcm128.c b/src/third_party/openssl/openssl/crypto/modes/gcm128.c index 1df77da..18433b1 100644 --- a/src/third_party/openssl/openssl/crypto/modes/gcm128.c +++ b/src/third_party/openssl/openssl/crypto/modes/gcm128.c
@@ -87,7 +87,7 @@ } while(0) -#if defined(OPENSSL_SYS_STARBOARD) && SB_API_VERSION >= 4 +#if defined(OPENSSL_SYS_STARBOARD) static const int kBlockSizeBits = 128; static const int kBlockSizeBytes = 16; static inline void sb_block128(GCM128_CONTEXT *ctx, @@ -120,7 +120,7 @@ (*ctr)(in, out, blocks, key, ivec); } } -#else // defined(OPENSSL_SYS_STARBOARD) && SB_API_VERSION >= 4 +#else // defined(OPENSSL_SYS_STARBOARD) static inline void sb_block128(GCM128_CONTEXT *ctx, block128_f block, const unsigned char in[16], @@ -140,7 +140,7 @@ SB_UNREFERENCED_PARAMETER(ctx); (*ctr)(in, out, blocks, key, ivec); } -#endif // defined(OPENSSL_SYS_STARBOARD) && SB_API_VERSION >= 4 +#endif // defined(OPENSSL_SYS_STARBOARD) /*- * Even though permitted values for TABLE_BITS are 8, 4 and 1, it should
diff --git a/src/third_party/openssl/openssl/crypto/modes/modes_lcl.h b/src/third_party/openssl/openssl/crypto/modes/modes_lcl.h index db4bf70..421b0d8 100644 --- a/src/third_party/openssl/openssl/crypto/modes/modes_lcl.h +++ b/src/third_party/openssl/openssl/crypto/modes/modes_lcl.h
@@ -121,7 +121,7 @@ block128_f block; void *key; -#if defined(OPENSSL_SYS_STARBOARD) && SB_API_VERSION >= 4 +#if defined(OPENSSL_SYS_STARBOARD) // Whether in decrypt or encrypt mode. int encrypt; @@ -145,7 +145,7 @@ // mode. If valid, then this is used instead of any other state in the // context. SbCryptographyTransformer gcm_transformer; -#endif // defined(OPENSSL_SYS_STARBOARD) && SB_API_VERSION >= 4 +#endif // defined(OPENSSL_SYS_STARBOARD) }; struct xts128_context {