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,iVBORw0KGgoAAAANSUhEUgAAATwAAADeCAYAAACg/rw7AAABS2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxMzggNzkuMTU5ODI0LCAyMDE2LzA5LzE0LTAxOjA5OjAxICAgICAgICAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIi8+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+IEmuOgAAEalJREFUeJzt3W+MHPV9x/H3Lec/Z599NpiYvxY0DlUNxXEbwCGp6jzCPAiNaQKEPKgUGlUiCRI4pYlAoVFMhCpKHiAUJUoqqigESJSIB4mvrRRMS6ibQoyTcBBsAqHBxrGx7/zv7LNz2we/Gd/e3u7d/pv57Z/3Sxrt7uzO3NcS+vCb78z8pq+I2lABGALmA4uBAWBh8t1S4Kzk/fLktR9YkrxfmPweYBCYl7xfBvQl2y4t+3vLyz6X7iOVbl9qKKl1LkuSGmdzGjhSw74mgbGydUVgtGzdOHCibN2hss+HgT+UbX8KOFphH0eSGkl+W0y2PZysO5H8/hgwkdQ4WcO/RznqM/AakgbMckKopMsQIUzmJ68LgEXJsiBZ108Ij3Qfabik4VRriKj9peGchmgaoGl4jiavh4GTwPFkOZmsm0hex5Lt0+UQ0wNYNerlwFsKrATOIQTQ8mRZNsvrUkIwLaywPylvJwgBeJgQnqOEMDxU8r789R1gH1Mj057SbYG3BLgYuAi4ALgQWAGcm7yel7yuIIy4pF51EjiQLG8nr/uT17eAPcDvkqVrwrHTAm8pcBnwnmT5I6aC7WKm+liSWuco8CZTQfgbYFeyvEoHBWK7Bt4C4L3ANcCVhHD7Y8IhqKT2sg/4NSEAfwH8DNhBGEW2lXYJvLOB64BrgasJYTc/akWSmjEBvEgIv/8GhoGDUSsibuANATcDtwB/wdyXLUjqXKeBZ4HvAk8w89KiXMQIvLXAXcDHmHmtl6TuNw58D/gqYRSYmzwDbz1wH+HQtfwCVkm9aRj4MvBcHn8sj8C7FPhnYFP2f0pSh/ohsBl4Pcs/kuUV/f3A3wO/wrCTNLtNhKy4mwz7+VmN8N4NPEY44ypJ9fgZcCvwWqt3nMUI72bg5xh2khpzNSFDbmn1jlsZeH3AFsJp5/LZOCSpHksJR4n308KTnK06pJ0H/Cvw8dbsTpLOeBz4G8LFzE1pReANEC4k/HDzu5Kkin5EuHZ3vJmdNBt48wkXEN7Q3G4kaU7DwF/RxEivmR5eH/AvGHaS8rGRkDkN9/SaCbx/BD7RxPaSVK9PAF9qdONGD2k/CjyJt4hJyl+RcMnKk/Vu2EjgXUK44Xeo/k0lqSUOE6aRq+tWtHoPac8iXBtj2EmKaSnwHeq8Da3ewLsDeH+d20hSFt5PyKSa1XNIezEwQnhqlyS1g6PA5YRnbsypnhHeAxh2ktrLICGbalLrCG8d8Dw+IFpS+ykCf054cNCsag2wL9fxW0nKUx9hkoG5f1jDCG8NYWI+r7mT1K6KwBWE8wxV1TJquxPDTlJ76yPMsD77j+YY4S0B9gKLW1KSJGXnOHA+4aLkiuYa4d2EYSepMywizLhe1VyB1/IpliUpQ7NOQjzbIe0QsJ8wm7EkdYJTwLnAWKUvZxvhXYdhJ6mzzAOur/blbIG3oeWlSFL2/rLaF7MF3gczKESSslY1u6r18JYDB/DuCkmdZ5LQxztY/kW1QPvALN9JUjsrANdW+6KSD2RXiyRlrmKGVQu8dRkWIklZ+7NKK6v18N4GVmZYjCRlaR9wXvnKSiO88zDsJHW2lYT7aqepFHjvzb4WScrc2vIVBp6kbjXjXESlwLsih0IkKWszsqxS4P1JDoVIUtZmZFn5Wdo+4AjOgSep8x0nTGI8ma4oH+FdhGEnqTssAi4sXVEeeO/OrxZJytx7Sj+UB95lORYiSVlbXfqhPPAuzbEQScratEwrD7xL8qtDkjJ3SemH8sBblV8dkpS5S0o/OMKT1M2mDeJKr8NbAIwTrsWTpG5QBAaAkzB9hHcxhp2k7tJHyZFraeDZv5PUjS5K35QG3gURCpGkrJ3JttLAmzFZniR1gTPZ5ghPUrerOMK7sMIPJanTnck2D2kldbuKI7wZT/iRpC5w5qFkhUor1QO2boU1a2JXIeXhzGAuDbyFhJlB1Ss2boSdO+Ghh2DZstjVSFlaTLjb4kzgnRuvFkXT3w933gm7dsHtt4fPUnd6F0wF3rsiFqLYVqyARx4JI76NG2NXI2XhXJgKvBURC1G7WLMm9PaeegpWr57791LnmBZ4nrDQlBtugJdeggcftL+nbrESpgLv7IiFqB3Nnw+bN9vfU7dYDlOBtzxiIWpnaX/vhRdgw4bY1UiNMvBUhyuvhKefhh/8wP6eOtHZMBV450QsRJ1k0yb7e+pE54A9PDUi7e+98gp88pNQKH80itR2po3wDDzVb+VK+Na34Pnn7e+p3U3r4Q1FLESdbt06+3tqd8vAwFMrbdoEv/wlbNkCg4Oxq5FKLYWpxzQeJ7m5Vj2iWJz7N83YuxfuvRcefRQmJ7P9W9LcTgADfUWYB0zErkY5yzrwUjt2wF13wbZt+fw9qboFBTycVZbWrYOf/AQeewxW+SRQRTVk4Cl7fX3w8Y+Hy1js7ymeoQJhcjwpewMDcM898Oqr4fq9vr7YFam3LC6QnL2QcnP++eH6veeeg/XrY1ej3rGkACyKXYV61Pr1IfTs7ykfiw08xVXe31vkf47KzICBp/aQ9vdefjkEoP09td4ie3hqL6tWhUNc+3tqvaEC3mGhdlTa37vootjVqDt4SKs2lvb3Xn45HO4O+P9mNWWgAHgVqNrb4GA4ofHKK/b31IxBA0+do7S/d9VVsatR5xm0h6fOs349bN8eLl4+//zY1ahzLDLw1JkKhXB72quv2t9TrRYaeOpspf29m26KXY3a26ICMD92FVLTVq2CJ54IU82vWxe7GrWneQVgQewqpJbZsCE8VMj+nmZaWAAWxq5CaqnS/t4XvhAeKyklMx73x65CysTgIHzlK+HB4Zs2xa5G8c1zAlB1v9WrwyMk7e/1ukUFwMvW1RvS/t43vwkrVsSuRvkrOFuKekuhALfdBrt2webN9vd6y5LC3L+RutCyZfDgg/b3ekwhWaTeVNrfW7MmdjXKVn8BWBK7Cim6DRtg50545BH7e91rsaM7KdXfD7ffbn+vi/UVYRQfxt17isXYFbS/3bvhjjtg69bYlag1xhzhSdWsXg0//nEIPPt7XcHJA6S5bNxof687LOwrgsc2vchD2saMjsIXvwhf+xqcPh27GtXJwOtVBl5zRkbCiY3h4diVqA728CT1DANPqsfoaBjZrV3r6K4D9QPjOM27NLvTp+Eb34D77oMDB2JXo8ac7AcmMPCk6oaHw6huZCR2JWrOCQ9ppWp274aPfASuv96w6xIGnlRudBQ+9zm4/HJ46qnY1aiFnN5dStmn63r9wBG8l1a9bts2uPNOePHF2JUoO8cKwGTsKqRodu+GG2+ED33IsOt+pz2kVW8aHYUtW+Dhh2FiInY1ykk/cDh2EVJuJifh0Ufh3nth797Y1ShfR/rxXlr1im3b4K67YMeO2JUojskCcCx2FVKmSvt0hl0vO94POMeNutPRo/DAA/DQQzA+HrsaxXeqHzgRuwqppezTqbKT/cDJ2FVILfPMM+F6Og9dNdOJAmHyAKmzvfkm3HqrfTrN5lQ6PZTUmezTqXbHDTx1pmIx9Onuucc+nWp1wsBT59m+PfTptm+PXYk6y3gBOBq7CqkmaZ/u2msNOzXiSD8Gntrd+Djcf799OjXraD9wPHYVUkXFIjz+OHz+82F0JzVn3B6e2pN9OrXeeAFnS1E72bPHPp2yMuYhrdrD+Hjo0T3wQLi2Tmq94wae4rJPp/yMG3iK54UX4DOf8dBVeTlmD0/527sXbrsNrr7asFOejvTjBKDKi306xXWsHxiLXYV6wPe/D5s326dTTGN9RZiPc+L1nmJOjzLZsSM8R2Lbtnz+nlTdgnQ+PC8+Vmulfbr3vc+wUzs4AUykz6UdAwYiFqNuMTER+nT332+fTu1kDMJzadMP58WrRV3hhz+Eu+8OTwmT2sthmB54UmPs06n9jQIUkg8HIxaiTnXgAHzqU/bp1AkOwdQIz8BT7SYm4OGHYcsWGB2NXY1Ui4MwFXjvRCxEncQ+nTrTOzAVeIciFqJOMDIS7nt9+unYlUiNOAhTPTwDT5UdOACf/jSsXWvYqZPZw9Ms7NOpu0wLvH0RC1G7GR6Gz37WPp26yT6YOqQ9ELEQtYuREbj++rAYduou+2Eq8H4fsRDFVtqnGx6OXY2Uhf0AfcmcGQtxAoHeUizC6dPw9a/Dvffap1O3WwSM95VMEnQYWBKrGuVs69YwP93ISOxKpKwdAwZhaoQHsAtYHaceScrMayTZVihZ+XacWiQpU2euQikNvL0RCpGkrO1J35QG3lsRCpGkrJ3JttLA21Phh5LU6SqO8DykldSNzmSbIzxJ3a7iCM8HhkrqRr9L35Reh7eAcLdFX/71SFImioQnMp6E6SO8k9jHk9Rd3iYJO5geeABv5FqKJGXrt6UfygPPPp6kbvJG6QdHeJK62RulH8oD7/X86pCkzL1R+qE88HblV4ckZW5appUHnvN6S+om0wKv9Do8CNfgHQEW51ePJGXiOGFS48l0RfkIrwj8Os+KJCkjv6Yk7GBm4AG8nE8tkpSpGVlWKfBeyqEQScrar8pXVAq8HTkUIklZe7F8RaXA25lDIZKUtRmBV36WNvU2sDLjYiQpK7+nQoZVGuGBozxJnW3G6A6qB97PMyxEkrJWMcOqBd5PMyxEkrJWMcOq9fDOBvZTPRAlqV1NAucCB8u/qBZoB/F6PEmdaYQKYQezj+CezaYWScpU1eyaLfCeyaAQScpa1eyq1sMDGCL08ea1vh5JysQpQv9urNKXs43wxoD/yqIiScrIs1QJO5j7LOxjra1FkjL13dm+nO2QFsLkeXtxQlBJ7e84cD5wuNoP5hrhHQGeaGVFkpSR7zFL2MHcIzyANYR5pfpaUpIktV4R+FPmuH64ljspRoDhVlQkSRkZpoabJWoZ4QGsA17AUZ6k9lMEriJk1KxqvVd2B/B4MxVJUkaepIawg9pHeAAXEw5vBxsqSZJa7yhwBfDbWn5cz2wo/wfc10hFkpSRL1Fj2EF9IzyAfuA/gffXt5kktdz/AB8ETte6Qb2BB3ApYfrkpfVvKkktcZhwMvU39WzUyASfrwN/SzgzIkl5KwKfos6wg8ZnNP4esKXBbSWpGfcTzszWrZFD2jPbAt8GPtH4LiSpLo8Dt9LgEWYzgQcwH/g+8OHmdiNJc/o34AZgotEdNPuQngngZuBHTe5HkmbzI2ATTYQdtOapZOPAjcwxD5UkNegJQsaMN7ujVj2GcYLQy/sKnr2V1BpFQqbcSpMju1SzPbxKbgG+QZg8VJIacQT4O1p85JhF4AG8mzA9/NXZ7F5SF/tfwqhud6t33KpD2nKvAR8A/oEw7bIkzWUc+DxwLRmEHWQ3wit1KfBPwF/jfHqSZioCPwDupoG7J+qR1Qiv1OvAxwgTDvx7Dn9PUuf4D8KI7qNkHHaQzwiv3HuBOwkhOJD/n5cU2Tjh9tSvEiYiyU2MwEsNERqTNxOmeDkrXimSMvYH4KeEoPsOcChGETEDr9Q5wHWEoe01wFpgXtSKJDXjFLCTMGfdc4Tbwt6JWhHtE3jlFhLmurqG8Oi1y5LlXTGLklTR74FXgV3AL4CfAT8HTsQsqpJ2DbxqlgGrmQrAS4CLgAuAVcDiaJVJ3esY8CawB3iLcCIyDbhdwGi80urTaYE3l2XAhYQHDl2QvF+RLCuBc5NlBR4yq7edAg4A+5NlX/L5ACHU9hCeY/MWHRRoc+m2wKvHcsIh8jnJ+2VVXtP3S5P3gxiWag+nCE/tOkSY8nw0eZ++Hqqw7h3CIWiUkwax9XLgNWMBIfiGCEG4JPk8SAjFBcCiZFmQ/KafEJz9ye8XEi7LSQN0KPluKMd/h7I3RnjIzBhTATVO6G8dSb4bTV4PAycJdycdT94fSrZJl7FkOZp8rzoYeO1pPqEfOUAIxiWEMEyDFEIwFsrWLWPqbpblJftbytRlP4uT/afSUE71JfspldZRqvRvzWb53D+ZppaRR5GZh1knmDl90CjTZ+9JwyQ1QehPQbhs4nCFOkr/VhpCldadJgRYWscxWjTDh1rn/wH97O5y1wa/fwAAAABJRU5ErkJggg==') 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,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') 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 {