1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "pdf/paint_manager.h" 6 7 #include <algorithm> 8 9 #include "base/logging.h" 10 #include "ppapi/c/pp_errors.h" 11 #include "ppapi/cpp/instance.h" 12 #include "ppapi/cpp/module.h" 13 14 PaintManager::PaintManager(pp::Instance* instance, 15 Client* client, 16 bool is_always_opaque) 17 : instance_(instance), 18 client_(client), 19 is_always_opaque_(is_always_opaque), 20 callback_factory_(NULL), 21 manual_callback_pending_(false), 22 flush_pending_(false), 23 has_pending_resize_(false), 24 graphics_need_to_be_bound_(false), 25 pending_device_scale_(1.0), 26 device_scale_(1.0), 27 in_paint_(false), 28 first_paint_(true), 29 view_size_changed_waiting_for_paint_(false) { 30 // Set the callback object outside of the initializer list to avoid a 31 // compiler warning about using "this" in an initializer list. 32 callback_factory_.Initialize(this); 33 34 // You can not use a NULL client pointer. 35 DCHECK(client); 36 } 37 38 PaintManager::~PaintManager() { 39 } 40 41 // static 42 pp::Size PaintManager::GetNewContextSize(const pp::Size& current_context_size, 43 const pp::Size& plugin_size) { 44 // The amount of additional space in pixels to allocate to the right/bottom of 45 // the context. 46 const int kBufferSize = 50; 47 48 // Default to returning the same size. 49 pp::Size result = current_context_size; 50 51 // The minimum size of the plugin before resizing the context to ensure we 52 // aren't wasting too much memory. We deduct twice the kBufferSize from the 53 // current context size which gives a threshhold that is kBufferSize below 54 // the plugin size when the context size was last computed. 55 pp::Size min_size( 56 std::max(current_context_size.width() - 2 * kBufferSize, 0), 57 std::max(current_context_size.height() - 2 * kBufferSize, 0)); 58 59 // If the plugin size is bigger than the current context size, we need to 60 // resize the context. If the plugin size is smaller than the current 61 // context size by a given threshhold then resize the context so that we 62 // aren't wasting too much memory. 63 if (plugin_size.width() > current_context_size.width() || 64 plugin_size.height() > current_context_size.height() || 65 plugin_size.width() < min_size.width() || 66 plugin_size.height() < min_size.height()) { 67 // Create a larger context than needed so that if we only resize by a 68 // small margin, we don't need a new context. 69 result = pp::Size(plugin_size.width() + kBufferSize, 70 plugin_size.height() + kBufferSize); 71 } 72 73 return result; 74 } 75 76 void PaintManager::Initialize(pp::Instance* instance, 77 Client* client, 78 bool is_always_opaque) { 79 DCHECK(!instance_ && !client_); // Can't initialize twice. 80 instance_ = instance; 81 client_ = client; 82 is_always_opaque_ = is_always_opaque; 83 } 84 85 void PaintManager::SetSize(const pp::Size& new_size, float device_scale) { 86 if (GetEffectiveSize() == new_size && 87 GetEffectiveDeviceScale() == device_scale) 88 return; 89 90 has_pending_resize_ = true; 91 pending_size_ = new_size; 92 pending_device_scale_ = device_scale; 93 94 view_size_changed_waiting_for_paint_ = true; 95 96 Invalidate(); 97 } 98 99 void PaintManager::Invalidate() { 100 // You must call SetSize before using. 101 DCHECK(!graphics_.is_null() || has_pending_resize_); 102 103 EnsureCallbackPending(); 104 aggregator_.InvalidateRect(pp::Rect(GetEffectiveSize())); 105 } 106 107 void PaintManager::InvalidateRect(const pp::Rect& rect) { 108 DCHECK(!in_paint_); 109 110 // You must call SetSize before using. 111 DCHECK(!graphics_.is_null() || has_pending_resize_); 112 113 // Clip the rect to the device area. 114 pp::Rect clipped_rect = rect.Intersect(pp::Rect(GetEffectiveSize())); 115 if (clipped_rect.IsEmpty()) 116 return; // Nothing to do. 117 118 EnsureCallbackPending(); 119 aggregator_.InvalidateRect(clipped_rect); 120 } 121 122 void PaintManager::ScrollRect(const pp::Rect& clip_rect, 123 const pp::Point& amount) { 124 DCHECK(!in_paint_); 125 126 // You must call SetSize before using. 127 DCHECK(!graphics_.is_null() || has_pending_resize_); 128 129 EnsureCallbackPending(); 130 131 aggregator_.ScrollRect(clip_rect, amount); 132 } 133 134 pp::Size PaintManager::GetEffectiveSize() const { 135 return has_pending_resize_ ? pending_size_ : plugin_size_; 136 } 137 138 float PaintManager::GetEffectiveDeviceScale() const { 139 return has_pending_resize_ ? pending_device_scale_ : device_scale_; 140 } 141 142 void PaintManager::EnsureCallbackPending() { 143 // The best way for us to do the next update is to get a notification that 144 // a previous one has completed. So if we're already waiting for one, we 145 // don't have to do anything differently now. 146 if (flush_pending_) 147 return; 148 149 // If no flush is pending, we need to do a manual call to get back to the 150 // main thread. We may have one already pending, or we may need to schedule. 151 if (manual_callback_pending_) 152 return; 153 154 pp::Module::Get()->core()->CallOnMainThread( 155 0, 156 callback_factory_.NewCallback(&PaintManager::OnManualCallbackComplete), 157 0); 158 manual_callback_pending_ = true; 159 } 160 161 void PaintManager::DoPaint() { 162 in_paint_ = true; 163 164 std::vector<ReadyRect> ready; 165 std::vector<pp::Rect> pending; 166 167 DCHECK(aggregator_.HasPendingUpdate()); 168 169 // Apply any pending resize. Setting the graphics to this class must happen 170 // before asking the plugin to paint in case it requests invalides or resizes. 171 // However, the bind must not happen until afterward since we don't want to 172 // have an unpainted device bound. The needs_binding flag tells us whether to 173 // do this later. 174 if (has_pending_resize_) { 175 plugin_size_ = pending_size_; 176 // Only create a new graphics context if the current context isn't big 177 // enough or if it is far too big. This avoids creating a new context if 178 // we only resize by a small amount. 179 pp::Size new_size = GetNewContextSize(graphics_.size(), pending_size_); 180 if (graphics_.size() != new_size) { 181 graphics_ = pp::Graphics2D(instance_, new_size, is_always_opaque_); 182 graphics_need_to_be_bound_ = true; 183 184 // Since we're binding a new one, all of the callbacks have been canceled. 185 manual_callback_pending_ = false; 186 flush_pending_ = false; 187 callback_factory_.CancelAll(); 188 } 189 190 if (pending_device_scale_ != 1.0) 191 graphics_.SetScale(1.0 / pending_device_scale_); 192 device_scale_ = pending_device_scale_; 193 194 // This must be cleared before calling into the plugin since it may do 195 // additional invalidation or sizing operations. 196 has_pending_resize_ = false; 197 pending_size_ = pp::Size(); 198 } 199 200 PaintAggregator::PaintUpdate update = aggregator_.GetPendingUpdate(); 201 client_->OnPaint(update.paint_rects, &ready, &pending); 202 203 if (ready.empty() && pending.empty()) { 204 in_paint_ = false; 205 return; // Nothing was painted, don't schedule a flush. 206 } 207 208 std::vector<PaintAggregator::ReadyRect> ready_now; 209 if (pending.empty()) { 210 std::vector<PaintAggregator::ReadyRect> temp_ready; 211 for (size_t i = 0; i < ready.size(); ++i) 212 temp_ready.push_back(ready[i]); 213 aggregator_.SetIntermediateResults(temp_ready, pending); 214 ready_now = aggregator_.GetReadyRects(); 215 aggregator_.ClearPendingUpdate(); 216 217 // Apply any scroll first. 218 if (update.has_scroll) 219 graphics_.Scroll(update.scroll_rect, update.scroll_delta); 220 221 view_size_changed_waiting_for_paint_ = false; 222 } else { 223 std::vector<PaintAggregator::ReadyRect> ready_later; 224 for (size_t i = 0; i < ready.size(); ++i) { 225 // Don't flush any part (i.e. scrollbars) if we're resizing the browser, 226 // as that'll lead to flashes. Until we flush, the browser will use the 227 // previous image, but if we flush, it'll revert to using the blank image. 228 // We make an exception for the first paint since we want to show the 229 // default background color instead of the pepper default of black. 230 if (ready[i].flush_now && 231 (!view_size_changed_waiting_for_paint_ || first_paint_)) { 232 ready_now.push_back(ready[i]); 233 } else { 234 ready_later.push_back(ready[i]); 235 } 236 } 237 // Take the rectangles, except the ones that need to be flushed right away, 238 // and save them so that everything is flushed at once. 239 aggregator_.SetIntermediateResults(ready_later, pending); 240 241 if (ready_now.empty()) { 242 in_paint_ = false; 243 EnsureCallbackPending(); 244 return; 245 } 246 } 247 248 for (size_t i = 0; i < ready_now.size(); ++i) { 249 graphics_.PaintImageData( 250 ready_now[i].image_data, ready_now[i].offset, ready_now[i].rect); 251 } 252 253 int32_t result = graphics_.Flush( 254 callback_factory_.NewCallback(&PaintManager::OnFlushComplete)); 255 256 // If you trigger this assertion, then your plugin has called Flush() 257 // manually. When using the PaintManager, you should not call Flush, it will 258 // handle that for you because it needs to know when it can do the next paint 259 // by implementing the flush callback. 260 // 261 // Another possible cause of this assertion is re-using devices. If you 262 // use one device, swap it with another, then swap it back, we won't know 263 // that we've already scheduled a Flush on the first device. It's best to not 264 // re-use devices in this way. 265 DCHECK(result != PP_ERROR_INPROGRESS); 266 267 if (result == PP_OK_COMPLETIONPENDING) { 268 flush_pending_ = true; 269 } else { 270 DCHECK(result == PP_OK); // Catch all other errors in debug mode. 271 } 272 273 in_paint_ = false; 274 first_paint_ = false; 275 276 if (graphics_need_to_be_bound_) { 277 instance_->BindGraphics(graphics_); 278 graphics_need_to_be_bound_ = false; 279 } 280 } 281 282 void PaintManager::OnFlushComplete(int32_t) { 283 DCHECK(flush_pending_); 284 flush_pending_ = false; 285 286 // If more paints were enqueued while we were waiting for the flush to 287 // complete, execute them now. 288 if (aggregator_.HasPendingUpdate()) 289 DoPaint(); 290 } 291 292 void PaintManager::OnManualCallbackComplete(int32_t) { 293 DCHECK(manual_callback_pending_); 294 manual_callback_pending_ = false; 295 296 // Just because we have a manual callback doesn't mean there are actually any 297 // invalid regions. Even though we only schedule this callback when something 298 // is pending, a Flush callback could have come in before this callback was 299 // executed and that could have cleared the queue. 300 if (aggregator_.HasPendingUpdate()) 301 DoPaint(); 302 } 303