1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "Layers.h" // for LayerManager
9 #include "nsRefreshDriver.h"
10 #include "nsContentUtils.h"
11 #include "mozilla/SizeOfState.h"
12 #include "mozilla/TimeStamp.h"
13 #include "mozilla/Tuple.h" // for Tie
14 #include "mozilla/layers/SharedSurfacesChild.h"
19 ///////////////////////////////////////////////////////////////////////////////
21 ///////////////////////////////////////////////////////////////////////////////
23 ImageMemoryCounter::ImageMemoryCounter(Image
* aImage
, SizeOfState
& aState
,
28 // Extract metadata about the image.
29 nsCOMPtr
<nsIURI
> imageURL(aImage
->GetURI());
31 imageURL
->GetSpec(mURI
);
36 aImage
->GetWidth(&width
);
37 aImage
->GetHeight(&height
);
38 mIntrinsicSize
.SizeTo(width
, height
);
40 mType
= aImage
->GetType();
42 // Populate memory counters for source and decoded data.
43 mValues
.SetSource(aImage
->SizeOfSourceWithComputedFallback(aState
));
44 aImage
->CollectSizeOfSurfaces(mSurfaces
, aState
.mMallocSizeOf
);
47 for (const SurfaceMemoryCounter
& surfaceCounter
: mSurfaces
) {
48 mValues
+= surfaceCounter
.Values();
52 ///////////////////////////////////////////////////////////////////////////////
54 ///////////////////////////////////////////////////////////////////////////////
56 bool ImageResource::GetSpecTruncatedTo1k(nsCString
& aSpec
) const {
57 static const size_t sMaxTruncatedLength
= 1024;
60 if (sMaxTruncatedLength
>= aSpec
.Length()) {
64 aSpec
.Truncate(sMaxTruncatedLength
);
68 void ImageResource::SetCurrentImage(ImageContainer
* aContainer
,
69 SourceSurface
* aSurface
,
70 const Maybe
<IntRect
>& aDirtyRect
) {
71 MOZ_ASSERT(NS_IsMainThread());
72 MOZ_ASSERT(aContainer
);
75 // The OS threw out some or all of our buffer. We'll need to wait for the
76 // redecode (which was automatically triggered by GetFrame) to complete.
80 // |image| holds a reference to a SourceSurface which in turn holds a lock on
81 // the current frame's data buffer, ensuring that it doesn't get freed as
82 // long as the layer system keeps this ImageContainer alive.
83 RefPtr
<layers::Image
> image
= new layers::SourceSurfaceImage(aSurface
);
85 // We can share the producer ID with other containers because it is only
86 // used internally to validate the frames given to a particular container
87 // so that another object cannot add its own. Similarly the frame ID is
88 // only used internally to ensure it is always increasing, and skipping
89 // IDs from an individual container's perspective is acceptable.
90 AutoTArray
<ImageContainer::NonOwningImage
, 1> imageList
;
91 imageList
.AppendElement(ImageContainer::NonOwningImage(
92 image
, TimeStamp(), mLastFrameID
++, mImageProducerID
));
95 aContainer
->SetCurrentImagesInTransaction(imageList
);
97 aContainer
->SetCurrentImages(imageList
);
100 // If we are generating full frames, and we are animated, then we should
101 // request that the image container be treated as such, to avoid display
102 // list rebuilding to update frames for WebRender.
103 if (gfxPrefs::ImageAnimatedGenerateFullFrames() &&
104 mProgressTracker
->GetProgress() & FLAG_IS_ANIMATED
) {
106 layers::SharedSurfacesChild::UpdateAnimation(aContainer
, aSurface
,
109 IntRect
dirtyRect(IntPoint(0, 0), aSurface
->GetSize());
110 layers::SharedSurfacesChild::UpdateAnimation(aContainer
, aSurface
,
116 ImgDrawResult
ImageResource::GetImageContainerImpl(
117 LayerManager
* aManager
, const IntSize
& aSize
,
118 const Maybe
<SVGImageContext
>& aSVGContext
, uint32_t aFlags
,
119 ImageContainer
** aOutContainer
) {
120 MOZ_ASSERT(NS_IsMainThread());
121 MOZ_ASSERT(aManager
);
123 (aFlags
& ~(FLAG_SYNC_DECODE
| FLAG_SYNC_DECODE_IF_FAST
|
124 FLAG_ASYNC_NOTIFY
| FLAG_HIGH_QUALITY_SCALING
)) == FLAG_NONE
,
125 "Unsupported flag passed to GetImageContainer");
127 ImgDrawResult drawResult
;
129 Tie(drawResult
, size
) = GetImageContainerSize(aManager
, aSize
, aFlags
);
130 if (drawResult
!= ImgDrawResult::SUCCESS
) {
134 MOZ_ASSERT(!size
.IsEmpty());
136 if (mAnimationConsumers
== 0) {
137 SendOnUnlockedDraw(aFlags
);
140 uint32_t flags
= (aFlags
& ~(FLAG_SYNC_DECODE
| FLAG_SYNC_DECODE_IF_FAST
)) |
142 RefPtr
<layers::ImageContainer
> container
;
143 ImageContainerEntry
* entry
= nullptr;
144 int i
= mImageContainers
.Length() - 1;
145 for (; i
>= 0; --i
) {
146 entry
= &mImageContainers
[i
];
147 container
= entry
->mContainer
.get();
148 if (size
== entry
->mSize
&& flags
== entry
->mFlags
&&
149 aSVGContext
== entry
->mSVGContext
) {
150 // Lack of a container is handled below.
152 } else if (!container
) {
153 // Stop tracking if our weak pointer to the image container was freed.
154 mImageContainers
.RemoveElementAt(i
);
156 // It isn't a match, but still valid. Forget the container so we don't
157 // try to reuse it below.
163 switch (entry
->mLastDrawResult
) {
164 case ImgDrawResult::SUCCESS
:
165 case ImgDrawResult::BAD_IMAGE
:
166 case ImgDrawResult::BAD_ARGS
:
167 case ImgDrawResult::NOT_SUPPORTED
:
168 container
.forget(aOutContainer
);
169 return entry
->mLastDrawResult
;
170 case ImgDrawResult::NOT_READY
:
171 case ImgDrawResult::INCOMPLETE
:
172 case ImgDrawResult::TEMPORARY_ERROR
:
173 // Temporary conditions where we need to rerequest the frame to recover.
175 case ImgDrawResult::WRONG_SIZE
:
176 // Unused by GetFrameInternal
178 MOZ_ASSERT_UNREACHABLE("Unhandled ImgDrawResult type!");
179 container
.forget(aOutContainer
);
180 return entry
->mLastDrawResult
;
185 NotifyDrawingObservers();
189 RefPtr
<SourceSurface
> surface
;
190 Tie(drawResult
, bestSize
, surface
) = GetFrameInternal(
191 size
, aSVGContext
, FRAME_CURRENT
, aFlags
| FLAG_ASYNC_NOTIFY
);
193 // The requested size might be refused by the surface cache (i.e. due to
194 // factor-of-2 mode). In that case we don't want to create an entry for this
195 // specific size, but rather re-use the entry for the substituted size.
196 if (bestSize
!= size
) {
197 MOZ_ASSERT(!bestSize
.IsEmpty());
199 // We can only remove the entry if we no longer have a container, because if
200 // there are strong references to it remaining, we need to still update it
201 // in UpdateImageContainer.
202 if (i
>= 0 && !container
) {
203 mImageContainers
.RemoveElementAt(i
);
206 // Forget about the stale container, if any. This lets the entry creation
207 // logic do its job below, if it turns out there is no existing best entry
208 // or the best entry doesn't have a container.
211 // We need to do the entry search again for the new size. We skip pruning
212 // because we did this above once already, but ImageContainer is threadsafe,
213 // so there is a remote possibility it got freed.
214 i
= mImageContainers
.Length() - 1;
215 for (; i
>= 0; --i
) {
216 entry
= &mImageContainers
[i
];
217 if (bestSize
== entry
->mSize
&& flags
== entry
->mFlags
&&
218 aSVGContext
== entry
->mSVGContext
) {
219 container
= entry
->mContainer
.get();
221 switch (entry
->mLastDrawResult
) {
222 case ImgDrawResult::SUCCESS
:
223 case ImgDrawResult::BAD_IMAGE
:
224 case ImgDrawResult::BAD_ARGS
:
225 case ImgDrawResult::NOT_SUPPORTED
:
226 container
.forget(aOutContainer
);
227 return entry
->mLastDrawResult
;
228 case ImgDrawResult::NOT_READY
:
229 case ImgDrawResult::INCOMPLETE
:
230 case ImgDrawResult::TEMPORARY_ERROR
:
231 // Temporary conditions where we need to rerequest the frame to
232 // recover. We have already done so!
234 case ImgDrawResult::WRONG_SIZE
:
235 // Unused by GetFrameInternal
237 MOZ_ASSERT_UNREACHABLE("Unhandled DrawResult type!");
238 container
.forget(aOutContainer
);
239 return entry
->mLastDrawResult
;
248 // We need a new ImageContainer, so create one.
249 container
= LayerManager::CreateImageContainer();
252 entry
->mContainer
= container
;
254 entry
= mImageContainers
.AppendElement(
255 ImageContainerEntry(bestSize
, aSVGContext
, container
.get(), flags
));
259 SetCurrentImage(container
, surface
, Nothing());
260 entry
->mLastDrawResult
= drawResult
;
261 container
.forget(aOutContainer
);
265 bool ImageResource::UpdateImageContainer(const Maybe
<IntRect
>& aDirtyRect
) {
266 MOZ_ASSERT(NS_IsMainThread());
268 for (int i
= mImageContainers
.Length() - 1; i
>= 0; --i
) {
269 ImageContainerEntry
& entry
= mImageContainers
[i
];
270 RefPtr
<ImageContainer
> container
= entry
.mContainer
.get();
273 RefPtr
<SourceSurface
> surface
;
274 Tie(entry
.mLastDrawResult
, bestSize
, surface
) = GetFrameInternal(
275 entry
.mSize
, entry
.mSVGContext
, FRAME_CURRENT
, entry
.mFlags
);
277 // It is possible that this is a factor-of-2 substitution. Since we
278 // managed to convert the weak reference into a strong reference, that
279 // means that an imagelib user still is holding onto the container. thus
280 // we cannot consolidate and must keep updating the duplicate container.
282 SetCurrentImage(container
, surface
, aDirtyRect
);
284 IntRect
dirtyRect(IntPoint(0, 0), bestSize
);
285 SetCurrentImage(container
, surface
, Some(dirtyRect
));
288 // Stop tracking if our weak pointer to the image container was freed.
289 mImageContainers
.RemoveElementAt(i
);
293 return !mImageContainers
.IsEmpty();
296 void ImageResource::ReleaseImageContainer() {
297 MOZ_ASSERT(NS_IsMainThread());
298 mImageContainers
.Clear();
302 ImageResource::ImageResource(nsIURI
* aURI
)
305 mAnimationConsumers(0),
306 mAnimationMode(kNormalAnimMode
),
310 mImageProducerID(ImageContainer::AllocateProducerID()),
313 ImageResource::~ImageResource() {
314 // Ask our ProgressTracker to drop its weak reference to us.
315 mProgressTracker
->ResetImage();
318 void ImageResource::IncrementAnimationConsumers() {
319 MOZ_ASSERT(NS_IsMainThread(),
320 "Main thread only to encourage serialization "
321 "with DecrementAnimationConsumers");
322 mAnimationConsumers
++;
325 void ImageResource::DecrementAnimationConsumers() {
326 MOZ_ASSERT(NS_IsMainThread(),
327 "Main thread only to encourage serialization "
328 "with IncrementAnimationConsumers");
329 MOZ_ASSERT(mAnimationConsumers
>= 1, "Invalid no. of animation consumers!");
330 mAnimationConsumers
--;
333 nsresult
ImageResource::GetAnimationModeInternal(uint16_t* aAnimationMode
) {
335 return NS_ERROR_FAILURE
;
338 NS_ENSURE_ARG_POINTER(aAnimationMode
);
340 *aAnimationMode
= mAnimationMode
;
344 nsresult
ImageResource::SetAnimationModeInternal(uint16_t aAnimationMode
) {
346 return NS_ERROR_FAILURE
;
349 NS_ASSERTION(aAnimationMode
== kNormalAnimMode
||
350 aAnimationMode
== kDontAnimMode
||
351 aAnimationMode
== kLoopOnceAnimMode
,
352 "Wrong Animation Mode is being set!");
354 mAnimationMode
= aAnimationMode
;
359 bool ImageResource::HadRecentRefresh(const TimeStamp
& aTime
) {
360 // Our threshold for "recent" is 1/2 of the default refresh-driver interval.
361 // This ensures that we allow for frame rates at least as fast as the
362 // refresh driver's default rate.
363 static TimeDuration recentThreshold
=
364 TimeDuration::FromMilliseconds(nsRefreshDriver::DefaultInterval() / 2.0);
366 if (!mLastRefreshTime
.IsNull() &&
367 aTime
- mLastRefreshTime
< recentThreshold
) {
371 // else, we can proceed with a refresh.
372 // But first, update our last refresh time:
373 mLastRefreshTime
= aTime
;
377 void ImageResource::EvaluateAnimation() {
378 if (!mAnimating
&& ShouldAnimate()) {
379 nsresult rv
= StartAnimation();
380 mAnimating
= NS_SUCCEEDED(rv
);
381 } else if (mAnimating
&& !ShouldAnimate()) {
386 void ImageResource::SendOnUnlockedDraw(uint32_t aFlags
) {
387 if (!mProgressTracker
) {
391 if (!(aFlags
& FLAG_ASYNC_NOTIFY
)) {
392 mProgressTracker
->OnUnlockedDraw();
394 NotNull
<RefPtr
<ImageResource
>> image
= WrapNotNull(this);
395 nsCOMPtr
<nsIEventTarget
> eventTarget
= mProgressTracker
->GetEventTarget();
396 nsCOMPtr
<nsIRunnable
> ev
= NS_NewRunnableFunction(
397 "image::ImageResource::SendOnUnlockedDraw", [=]() -> void {
398 RefPtr
<ProgressTracker
> tracker
= image
->GetProgressTracker();
400 tracker
->OnUnlockedDraw();
403 eventTarget
->Dispatch(ev
.forget(), NS_DISPATCH_NORMAL
);
408 void ImageResource::NotifyDrawingObservers() {
409 if (!mURI
|| !NS_IsMainThread()) {
414 if ((NS_FAILED(mURI
->SchemeIs("resource", &match
)) || !match
) &&
415 (NS_FAILED(mURI
->SchemeIs("chrome", &match
)) || !match
)) {
419 // Record the image drawing for startup performance testing.
420 nsCOMPtr
<nsIURI
> uri
= mURI
;
421 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
422 "image::ImageResource::NotifyDrawingObservers", [uri
]() {
423 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
424 NS_WARNING_ASSERTION(obs
, "Can't get an observer service handle");
428 obs
->NotifyObservers(nullptr, "image-drawing",
429 NS_ConvertUTF8toUTF16(spec
).get());
436 } // namespace mozilla