<AWT Dev> [OpenJDK 2D-Dev] [8] Review request for 8011059 [macosx] Make JDK demos look perfect on retina displays
Alexander Scherbatiy
alexandr.scherbatiy at oracle.com
Tue Dec 3 03:48:43 PST 2013
Hello,
Could you review the updated fix:
http://cr.openjdk.java.net/~alexsch/8011059/webrev.13/
- observer cache is returned back
- resolution variant x/y/width/height are divided by 2 in the
observer wrapper
On 12/3/2013 1:16 AM, Jim Graham wrote:
> Hi Alexander,
>
> There must have been a miscommunication. While I think the cache has
> some issues that worry me, it is absolutely needed if you are going to
> wrap the observer. You can't just delete it and re-wrap the observer
> on every call because that will create a number of problems. If you
> are going to use a wrapper solution then we will need to live with the
> potential issues of a cache.
>
> Note that the underlying ImageReps store lists of observers to be
> notified. You will be growing that list every time a call is made on
> an image because each new wrapper looks like a new observer. For an
> animated GIF the number of drawImage calls and associated wrappers
> could be infinite (since they are never fully loaded). Also, for each
> new wrapper, an additional callback is performed. This will drive the
> performance of an animated GIF into the ground after a time when
> thousands of growing callbacks are issued for each frame.
>
> If you are going to use observer wrappers then you must cache the
> wrapper so you reuse the same wrapper on every new call.
>
> On 12/2/13 4:55 AM, Alexander Scherbatiy wrote:
>>
>> Could you review the updated fix:
>> http://cr.openjdk.java.net/~alexsch/8011059/webrev.12/
>> - Image observers are not cached now for the resolution variants
>> - base image width and height are checked in the wrapped image
>> observer
>>
>> On 11/30/2013 3:27 AM, Jim Graham wrote:
>>> Hi Alexander,
>>>
>>> I suppose the wrapping solution works for ImageObservers, but I think
>>> the suggestion I gave in recent emails was to simply modify the
>>> newInfo method (and a couple of other methods) to deliver the same
>>> information with no caches, no hashmaps/lookups, no wrapping the
>>> observer, and no wrapping with soft references. Keep in mind that
>>> observers are typically references to Component objects so any delay
>>> in processing the soft references could keep relatively large
>>> component hierarchies in play (if they are parents). It should work
>>> well for a first draft, though.
>> It seems that just updating the newInfo method is not enough.
>
> There were 5 or 6 places that called imageUpdate when I did a quick
> search and most of the calls went through newInfo. They'd all have to
> be updated. Other than that, I'm not sure why it would not be enough?
Consider the following scenario. There are image.png and image at 2x.png
files on the disk.
Image image1 = Toolkit.getImage("image.png"); // load as
multi-resolution image
Image image2 = Toolkit.getImage("image at 2x.png"); // load the image
from cache
toolkit.prepareImage(image2,.., imageObserver2);
The image2 has image1 as the base image so it rescale its
coordinates/dimension and passes the base instead of self to the
imageObserver2 which does not look correct.
>
>> The resolution variant can have a reference to the base image.
>> Loading the base image it still should compare its result with the
>> resolution variant loading.
>> So the image loading status is just moved from the SunToolkit to
>> the ToolkitImage.
>
> If you are referring to comparing the new and old dimensions then the
> newInfo method would have access to that information if it could ask a
> resolution variant what its base image is. That should be a very
> simple change to ToolkitImage (add a set/getBaseImage() method). Then
> it can do the associated comparisons and parameter adjustments right
> before calling back to the observer.
>
> Perhaps I haven't explained my vision of modifying newInfo very well.
> I'll look into sketching it out in another email since I think the
> cache/wrappers are OK for now.
>
>> I think that the current fix for the checkImage/prepareImage in the
>> SunToolkit is the simplest for the current implementation.
>> The separate fix for the ToolkitImage/ImageRepresentation can get
>> all things right.
>
> I didn't really follow you here, but I said above that the cache
> should be fine for now. The cache is definitely needed if you are
> wrapping observers as I said above in the first couple of paragraphs.
>
>>> Also, why does ObserverCache exist? Couldn't the cache just be a
>>> static field on MRToolkitImage?
>> MRToolkitImage can be used in drawImage(Image,..,ImageObserver)
>> method always with null observer. So the is no need to create the
>> observer cache or use a synchronization during the cache initialization.
>
> Maybe I'm missing something about your answer here, but I think you
> may have misunderstood my question. You placed the field that holds
> the reference to the cache inside an inner class. I didn't see why
> the reference could not be stored in the base class. Why is there an
> empty inner class to wrap a single field? In other words, why was
> this used:
>
> 56
> 57 private static class ObserverCache {
> 58
> 59 static final SoftCache INSTANCE = new SoftCache();
> 60 }
> 61
>
> Instead of just:
>
> 56
> 59 static final SoftCache INSTANCE = new SoftCache();
> 61
Just to not create the cache in case if MRToolkitImageis used but
image observers are always null. See the comment above.
Thanks,
Alexandr,.
>
>>> The current way of baking in the image dimensions assumes that they
>>> are known. If the image is loaded asynchronously, then the calls to
>>> getWidth/Height(null) may return -1 and the cached observer wrapper
>>> has no way to get them later. I would think this would fail in the
>>> typical default scenario where the user gets an image and the first
>>> even that triggers loading it is to render to the screen which would
>>> bake the -1's into the observer wrapper in all of those cases. This
>>> should probably be addressed sooner rather than later.
>> I added the base image width/height check to the observer wrapper.
>> However, there is no way to rescale image representation width and
>> height in case if the base image width and height are not known.
>
> That is true if we are basing the scaling solely on the difference in
> dimensions. In the case of @2x we also could assume that the scaling
> would be 2.0. I'm not sure if that kind of assumption can be made for
> all future resolution variant mechanisms, but I'd imagine that many of
> them come with a standard for determining the scaling of the variants
> up front so that the correct media can be chosen without having to
> load all of it first just to compare dimensions.
>
> If the image dimensions weren't known when your observer was created
> then you never attempt to recover, though. You should be querying
> "image.getWidth(this)" to try to grab the dimensions on the fly when
> they come in rather than always punting. In the current case where
> nothing is cached, you end up with multiple wrappers so some of them
> will have been created before the dimensions were known and some will
> be created after the dimensions are known and the multiple callbacks
> will all have conflicting information. You'll need to go back to a
> cache and that will mean a single wrapper for any given observer and,
> at that point, inserting calls to "image.getWidth(this /* wrapper */)"
> will be needed to make sure you get the proper dimensions at some point.
>
>>> If an @2x image gets an error we silently start using just the regular
>>> version of the image. In addition MediaTracker should not reflect
>>> that error in its answers, but I think it currently does since you add
>>> it as an implicit extra media with the same ID. I think this is OK
>>> for the first pass, but we need to track it as an issue.
>>>
>>> I saw that you fixed the toolkit versions of check/prepare image.
>>> Component also has the same operations (via its peers). The Component
>>> versions do back off to the toolkit versions, but only if they fail to
>>> find an actual peer to delegate to. Note that MediaTracker uses the
>>> Component versions so it is affected by this, but I don't think it
>>> will cause functional problems that I can think of. Eventually we'd
>>> want MT to only load the version that is appropriate for the indicated
>>> Component - and at that point then this might be an issue, but I don't
>>> think it is an issue for this first draft if it tracks both variations.
>> I checked all peers like W/LW/XComponentPeer and toolkits. They all
>> delegate check/prepareImage calls to the default toolkit.
>
> Sounds good. Eventually we might want to have them prepare just the
> default resolution for that particular device if they are already
> associated with a screen, but this should be good to go for now...
>
> The outstanding issues for me are:
>
> - The cache was necessary to eliminate the overhead of duplicated
> wrappers so it should go back (unless you want to investigate an
> alternate solution by modifying all places that call imageUpdate
> instead, which are hopefully few)
>
> - Retrying on the base image dimensions if they weren't known when a
> wrapper was created (or switching to a pre-determined scale derived
> from the resolution mechanism which benefits @2x and keeping the
> "relative dimension" scaling for the general case)
>
> ...jim
>
>> Thanks,
>> Alexandr.
>>
>>> I think it is OK to send multiple notifications to an observer since
>>> we've always been loose on the exact sequencing and the operations are
>>> all asynchronous. There could potentially be several notifications in
>>> flight at the time that the observer indicates a lack of interest and
>>> there is no way to stop them. This could be considered "another case
>>> of that". Eventually we could consider addressing this with a tighter
>>> integration between the wrapper mechanism and the code that calls
>>> imageUpdate() and receives the answer that the observer is no longer
>>> interested, but I think this is all OK for now.
>>>
>>> The only thing I think we should worry about now for this first draft
>>> is the issue of getting the right dimensions for the observer
>>> wrappers. They need to be more asynchronous about that. It already has
>>> a handle on the original image anyway, so I think it just needs to get
>>> the dimensions from there instead of passing them into the
>>> constructor, with appropriate checks for -1's...
>>>
>>> ...jim
>>>
>>> On 11/28/13 6:45 AM, Alexander Scherbatiy wrote:
>>>>
>>>> Could you review the updated fix:
>>>> http://cr.openjdk.java.net/~alexsch/8011059/webrev.11/
>>>>
>>>> I have moved new API supporting to the separate issue:
>>>> JDK-8029339 Custom MultiResolution image support on HiDPI
>>>> displays
>>>> https://bugs.openjdk.java.net/browse/JDK-8029339
>>>>
>>>> - SunToolkit.checkImage/updateImage are updated to handle
>>>> multi-resolution image
>>>> Both base image and the resolution variant are loaded now.
>>>> However, in future, it would be useful to have an ability to load, for
>>>> example, only @2x image
>>>> if the system has only Retina display on Mac OS X. So there are
>>>> can be problems with backward compatibility.
>>>>
>>>> - MediaTracker is updated to handle multi-resolution image
>>>> MediaTracker uses notifications both form the
>>>> Toolkit.prepareImage() method and image decoders. So only updating
>>>> Toolkit.prepareImage() is not enough.
>>>>
>>>> - ImageObserver is wrapped both in drawImage() and
>>>> SunToolkit.prepareImage() methods. Size and coordinates are
>>>> converted to
>>>> the base image.
>>>> A user can receive several notifications about the complete image
>>>> loading from the original image and from the resolution variant.
>>>> It can breaks his code if the code relies that only one
>>>> notification should be received.
>>>>
>>>> - Resolution variant is ignored in case of errors in the
>>>> drawImage()
>>>> It needs to be decided on which step a user should be notified
>>>> that a resolution variant has error or this notification should be
>>>> just
>>>> ignored.
>>>>
>>>> - Test is updated
>>>>
>>>> Icons and CustomCursor issues are handled by 8028212 and 8024926
>>>> bugs
>>>> and depend on the current bug implementation.
>>>>
>>>> Thanks,
>>>> Alexandr.
>>>>
>>>> On 11/28/2013 3:21 AM, Jim Graham wrote:
>>>>> The things I was talking about in the quoted email were mostly future
>>>>> directions, but I did point out some issues in another email that
>>>>> should be addressed before FCS. In particular:
>>>>>
>>>>> We do need to get the callbacks working whether they draw hi res or
>>>>> not. I think the current fix delivers no notifications at all
>>>>> because
>>>>> the observer is not registered anywhere during the course of a
>>>>> drawImage(). That's a big bug. I don't think we need to wrap
>>>>> observers to do that, there are only a few helper functions that
>>>>> deliver the notifications and they can be taught how to translate a
>>>>> resolution variant into the base image easily enough.
>>>>>
>>>>> You point out that there was no error checking for the hidpi
>>>>> image. I
>>>>> agree. Also, if the base image errors out, should we draw the @2x
>>>>> anyway? Or should that base image be required? Minimally I think we
>>>>> need to not attempt the @2x for FCS if it errored and we can worry
>>>>> about how to respond to errors in the base image later.
>>>>>
>>>>> I think that com.sun would require CCC approval for a new interface
>>>>> and the bar is really high for API in 8.0 right now. Unless a
>>>>> developer really needs to use it it might be best to move the API to
>>>>> sun.*. I didn't see any developers clamoring for it in the
>>>>> discussions I read, but let me know if I missed something. If we get
>>>>> automatic use of @2x images then I think that satisfies the
>>>>> discussions I saw.
>>>>>
>>>>> MediaTracker defers to Component.prepareImage() for which variants
>>>>> should be loaded. I think we need to teach prepareImage() to trigger
>>>>> the variant appropriate for the screen that the Component is on
>>>>> (perhaps the default screen if it isn't displayed yet).
>>>>>
>>>>> Also, there should probably be some MediaTracker, ImageIcon, and
>>>>> Cursor test cases that show that those mechanisms all work on images
>>>>> that have @2x variants. Minimally they shouldn't fail entirely, and
>>>>> very desireably they should use the @2x version when appropriate, but
>>>>> that could be a follow on bug fix as long as they don't fail for the
>>>>> first fix.
>>>>>
>>>>> I've included below the quote from my last "review" email about the
>>>>> specific issues I found. The main "blocking" issue right now is that
>>>>> it looks to me that no imageUpdate notifications at all happen for a
>>>>> hidpi image due to the ImageObserver getting dropped on the floor.
>>>>> Can
>>>>> we at least get imageUpdate() called with the base image, even if the
>>>>> xywh are wrong before we push the initial fix? I hope my info below
>>>>> helps make that an easy task.
>>>>>
>>>>>> If they only ever use the image on a retina display (or some scaled
>>>>>> context) then I don't think we ever send any notifications the way
>>>>>> the current fix is written. Also, if you don't send notifications
>>>>>> for
>>>>>> the scaled variant, but load the original and expect its
>>>>>> notifications to suffice, then we have a race condition - if the
>>>>>> original variant is fully constructed before the scaled version is
>>>>>> done then the final "OK to draw everything" notice would not happen
>>>>>> and they'd be left with a partial image.
>>>>>>
>>>>>> I think it should be easy to pass along the observer and simply have
>>>>>> ImageRep translate the image variant into the base image in its
>>>>>> newInfo method (and there are a couple of other locations that
>>>>>> imageUpdate is called that could do the same). All it takes is
>>>>>> adding "ToolkitImage.set/getBaseImage()" to ToolkitImage, defaulting
>>>>>> to "this", but the Multi-Res image will set the base image on
>>>>>> both of
>>>>>> its variants to the Multi-Res instance.
>>>>>>
>>>>>> Then we get full notices of all resolution variants.
>>>>>>
>>>>>> It would be best to scale the xywh supplied there as well. That
>>>>>> should be fairly easy, but if it is a big problem then that is lower
>>>>>> priority than getting the "ALLBITS" notification right, as that is
>>>>>> the main trigger for so many uses.
>>>>>>
>>>>>> (In the future we can add ImageObserver2 (or MultiResObserver?)
>>>>>> which
>>>>>> has support for receiving notifications of variants without
>>>>>> translation.)
>>>>>
>>>>>
>>>>> ...jim
>>>>>
>>>>> On 11/27/13 6:28 AM, Sergey Bylokhov wrote:
>>>>>> On 27.11.2013 15:12, Sergey Bylokhov wrote:
>>>>>>> Hello.
>>>>>>> Probably we are in a point when it is necessary to stop and move
>>>>>>> out
>>>>>>> all extensions to the separate bugs.
>>>>>>> We should give a time to our sqe to test these changes.
>>>>>>> Actually current version looks good to me.
>>>>>> Small additional notes, It would be good:
>>>>>> - to wrap initial observer in the drawImage and replace
>>>>>> img/x,y,....
>>>>>> in the observer.
>>>>>> - don't draw hidpi image, if it was loaded with errors.
>>>>>> - sun.com package should be used?
>>>>>> - If MediaTracker was requested to load MultiResolutionImage,it
>>>>>> should
>>>>>> load all versions?
>>>>>>>
>>>>>>> On 26.11.2013 2:31, Jim Graham wrote:
>>>>>>>>
>>>>>>>>
>>>>>>>> On 11/25/13 5:51 AM, Alexander Scherbatiy wrote:
>>>>>>>>> On 11/23/2013 5:11 AM, Jim Graham wrote:
>>>>>>>>>> Hi Alexander,
>>>>>>>>>>
>>>>>>>>>> If we are going to be advertising it as something that
>>>>>>>>>> developers
>>>>>>>>>> use
>>>>>>>>>> in production code then we would need to file this via CCC.
>>>>>>>>>> With the
>>>>>>>>>> current implementation, any user that has their own
>>>>>>>>>> ImageObservers
>>>>>>>>>> must use this API and so it becomes not only public, at a time
>>>>>>>>>> when
>>>>>>>>>> there is a lockdown on new public API in 8.0, but it also means
>>>>>>>>>> that
>>>>>>>>>> we are tied to its implementation and we can't rely on "it was
>>>>>>>>>> experimental and so we can change it at any point" - you
>>>>>>>>>> can't say
>>>>>>>>>> that about an API that is necessary to correctly use a touted
>>>>>>>>>> feature.
>>>>>>>>>
>>>>>>>>> This issue has its own history. It has been already fixed
>>>>>>>>> for the
>>>>>>>>> JDK 7u for the Java2D demo. It was decided to not bring new API
>>>>>>>>> for the
>>>>>>>>> HiDPI support in the JDK 7.
>>>>>>>>> It was suggested to using code like below to create a
>>>>>>>>> ScalableImage.
>>>>>>>>> ------------------------------
>>>>>>>>> /**
>>>>>>>>> * Class that loads and returns images according to
>>>>>>>>> * scale factor of graphic device
>>>>>>>>> *
>>>>>>>>> * <pre> {@code
>>>>>>>>> * Image image = new ScalableImage() {
>>>>>>>>> *
>>>>>>>>> * public Image createScaledImage(float scaleFactor) {
>>>>>>>>> * return (scaleFactor <= 1) ? loadImage("image.png")
>>>>>>>>> * : loadImage("image at 2x.png");
>>>>>>>>> * }
>>>>>>>>> *
>>>>>>>>> * Image loadImage(String name){
>>>>>>>>> * // load image
>>>>>>>>> * }
>>>>>>>>> * };}</pre>
>>>>>>>>> */
>>>>>>>>> ------------------------------
>>>>>>>>>
>>>>>>>>> It was based on the display scale factor. The scale factor
>>>>>>>>> should be
>>>>>>>>> manually retrieved from the Graphics2D and was not a part of the
>>>>>>>>> public
>>>>>>>>> API:
>>>>>>>>> g2d.drawImage(scalbleImage.getScaledImage(scaleFactor),..).
>>>>>>>>>
>>>>>>>>> From that time it was clear that there should be 2 basic
>>>>>>>>> operations
>>>>>>>>> for the HiDPI images support:
>>>>>>>>> - retrieve an image with necessary resolution
>>>>>>>>> - retrieve images with all resolutions
>>>>>>>>>
>>>>>>>>> The second one is useful when it is necessary to create one
>>>>>>>>> set of
>>>>>>>>> images using the given one (for example, to draw a shape on all
>>>>>>>>> images).
>>>>>>>>
>>>>>>>> For now, these are just ToolkitImages and you can't draw on
>>>>>>>> them, so
>>>>>>>> this is moot for the case of @2x support in getImage().
>>>>>>>>
>>>>>>>>> The JDK 7 solution has been revisited for the JDK 8 and the
>>>>>>>>> neccesary
>>>>>>>>> CCC request 8011059 has been created and approved.
>>>>>>>>>
>>>>>>>>> Both the JDK-8011059 issue description and the approved CCC
>>>>>>>>> request
>>>>>>>>> says:
>>>>>>>>> -----------------------
>>>>>>>>> A user should have a unified way to provide high resolution
>>>>>>>>> images
>>>>>>>>> that can be drawn on HIDPI displays according to the user
>>>>>>>>> provided
>>>>>>>>> algorithm.
>>>>>>>>> -----------------------
>>>>>>>>
>>>>>>>> We've implemented the Hint part of that solution, but the
>>>>>>>> mechanism
>>>>>>>> for creating custom multi-res images was not workable. I no longer
>>>>>>>> sit as the client rep on the CCC or I would have pointed that
>>>>>>>> out, my
>>>>>>>> apologies.
>>>>>>>>
>>>>>>>>> The 8011059 fix consists of two parts:
>>>>>>>>> - Provide a way for a custom image creation that holds images
>>>>>>>>> with
>>>>>>>>> different resolutions
>>>>>>>>> - Load @2x images on Mac OS X
>>>>>>>>>
>>>>>>>>> The first one of the fix can be used without the second. it
>>>>>>>>> is not
>>>>>>>>> difficult to crate just a class which loads @2x images using the
>>>>>>>>> given
>>>>>>>>> file path/url.
>>>>>>>>
>>>>>>>> If we support @2x transparently behind the scenes as we are doing
>>>>>>>> now, who is the requester for the way to create a custom set of
>>>>>>>> resolution variants? I think the most important customer-driven
>>>>>>>> request was to support @2x for the Mac developers, wasn't it?
>>>>>>>>
>>>>>>>>> Now it is suggested to implement the given
>>>>>>>>> MultiResolutionImage
>>>>>>>>> interface and override two methods:
>>>>>>>>> - Image getResolutionVariant(int width, int height)
>>>>>>>>> - List<Image> getResolutionVariants()
>>>>>>>>>
>>>>>>>>> An image with necessary resolution should be drawn
>>>>>>>>> automatically in
>>>>>>>>> SunGraphics2D.
>>>>>>>>>
>>>>>>>>> What we need is to focus on the first part of the fix.
>>>>>>>>> From this point of view it up to the user' code to load all or
>>>>>>>>> some
>>>>>>>>> resolution variants by MediaTracker and using
>>>>>>>>> Component.prepareImage/checkImage methods.
>>>>>>>>
>>>>>>>> This is all an interesting goal, but I'm not sure it is as high a
>>>>>>>> priority if we support a convention (based on platform
>>>>>>>> conventions)
>>>>>>>> for high resolution variants behind the scenes. I do agree
>>>>>>>> that we
>>>>>>>> need something like this before too long, but I don't think it was
>>>>>>>> workable to target the getScaledInstance() method as the
>>>>>>>> mechanism to
>>>>>>>> implement it. That was the only convention that was approved in
>>>>>>>> that
>>>>>>>> CCC request, so a more complete API based on another mechanism is
>>>>>>>> not
>>>>>>>> covered there.
>>>>>>>>
>>>>>>>> Also, Win8 has its own conventions as well which come into play on
>>>>>>>> the new high resolution Surface tablets and we should consider
>>>>>>>> those
>>>>>>>> to guide the API we develop.
>>>>>>>>
>>>>>>>> All, in all, though, I think if we get @2x support in with no code
>>>>>>>> changes needed for apps then we have taken care of the primary use
>>>>>>>> case. We can probably even handle the Win8 conventions behind the
>>>>>>>> scenes in an 8u release...
>>>>>>>>
>>>>>>>> ...jim
>>>>>>>
>>>>>>>
>>>>>>
>>>>>>
>>>>
>>
More information about the awt-dev
mailing list