03-28-2014 04:45 PM
I'm working on a photo app, which involves displaying photos on the screen, and swiping between them.
Currently my app is not a joy to use, because there are frequent pauses and framerate drops. For example, when swiping between photos, the photo might get half way across and then pause for 0.2 seconds. Or perhaps it's not a large amount of time, but it's obvious the animation isn't achieving anything close to 60 fps.
To contrast this, if I open an app like the Facebook app, the scrolling is completely smooth... no stuttering, no pausing, completely smooth scrolling. And the Facebook app involves lots of photos.
Here are my current thoughts on how to _try_ and create smooth UIs:
- I learned that there seems to be a UI freeze the first time an ImageView is displayed on the screen, even if it has finished "loading" its photo. Thus, if there's an offscreen photo that I'm going to want to slide on screen, I first place it so that exactly 1 pixel of the image is showing on the screen, and its opacity is 0.01. Then, since it's already "on screen", there's no lag when I set its opacity to 1 and start sliding it onto the screen.
- If a UI event, such as a touch event, triggers some C++ code, I use a QTimer::singleShot to allow the C++ function to return to the UI that called it. My hope is that the QTimer's signal is handled in another thread, so as to free up the UI.
The above are just my off-the-cuff attempts at trying to do what makes sense.
What I'm hoping is that there's a resource out there, or a person, who knows lots about the tricks of the trade for creating perfectly smooth UIs. What are the DOs and the DO NOTs? Are there tricky things that I might never guess? Are there tricks to using ImageViews that avoid the lags that I've noticed?
One curious thought I've had is that because most photos are displayed at screen resolution, but are actually 8 MP photos, perhaps I need to resize them in memory and put the resized image into the image view. The problem there is that I do support pinch-zoom, so upon a pinch I'd need to swap out the small photo for the large photo, and I wouldn't want lag there either. Also, I would imagine the ImageView must itself resize the image and store a cached copy of the resized image, so doing that myself might no save any time. (but perhaps that's what explains the ImageView lag when it first comes onscreen -- maybe that's when it's resizing the 8MP photo down to the size of the screen)
Anyway, hopefully people out there have these things solved.
03-28-2014 08:35 PM
I've done some more reading and have found seemingly contradictory documentation:
This states "The Cascades UI framework (bb/cascades) is both fast and easy to use. The application logic and the UI rendering engine run on separate execution threads that communicate asynchronously. The separation of application and rendering logic means that the renderer doesn't need to wait for a long-running computation to complete before updating the UI. This is how Cascades helps your application present a UI at a consistently high frame rate, which keeps your UI smooth and responsive."
This seems to imply that you need not worry about making the UI sluggish, because it runs in a different thread.
This states "Does your overall application performance seem sluggish or unresponsive? The poor performance might be due to running too many resource-intensive processes on the UI thread. Here are a few things that you should consider when loading data or running other resource intensive operations: ..."
Wait, now I'm confused. The fundamentals page basically says "Don't worry, the UI runs on another thread, so your application logic won't slow things down". But the performance page says "Be careful not to run too many resource intensive processes on the UI thread"!
So what is it? Is my application logic running on the UI thread or not?
Given that my app _is_ sluggish, I figured I'd assume that the performance page knows what it's talking about and that I should try moving my application logic into a new thread.
That's another point of confusion. I tried QtConcurrent::run, but when I try and access a UI object from that thread, the app crashes.
It might be helpful if there was a documentation page that demonstrated how to use QtConcurrent::run in Cascades -- what to do, and what not to do.
Does anyone know how to clarify that?
I've also found this page:
... which is alright, but it's a tonne of code if all I need/want to do is run a function asynchronously, no?
If I were to guess, the answer is probably something like "You can't access UI objects from another thread. Rather, you should use "The-Recommended-Way-to-Use-QThread" approach and use the signals/slots to communicate between threads in a thread-safe manner, such that you can update your UI objects from the UI thread."
If someone can confirm/deny that, it would be helpful. (and likewise, if people can shed light on my other above questions, that would be helpful)
03-28-2014 09:44 PM
Experimentation seems to suggest that certain classes, even bb::cascades::Image, cannot be used in other threads. For whatever reason, I seem to be able to use a bb::ImageData member variable, but not a bb:cascades::Image member variable. I wonder why that is?
The other non-obvious thing I discoverd is that if you do something like:
QFuture<void> future = QtConcurrent::run(this, &ImageViewContainer::loadViaQImage);
connect(&watcher, SIGNAL(finished()), this, SLOT(loadViaQImageFinished()));
(within a function)
... then the finished slot never gets called. Apparently this is because the future and watcher objects have been created on the stack, and get deleted after the currently executing function ends.
So, you have to define the future and watcher as member variables.
The next non-obvious thing that happens is that while you're using the app, the finished slot starts being called _multiple_ times. That's because the 'connect' function is getting called every time QtConcurrent::run is called. So, to avoid that, the connect should be setup once, not each time.
After all of that, it seems to work.
But I'm still not finding the UI to be lag-free. It would appear that some of the lag, as I've surmised before, is coming from Cascades itself, and not my application logic.
I have a handleTouch(bb::cascades::TouchEvent* event) in C++ where I respond to various touch events, such as TouchType::Move, so that the user can slide their finger across the screen and have the photo move with their finger. At the moment, what tends to happen is that as the next photo is entering the scene, perhaps 30-40% into the scene, there is somethings a slight freeze of the UI, for anywhere from 150-300 ms. Sometimes it happens, sometimes it doesn't. Sometimes it's not that noticeable, other times it's quite ugly.
This is discouraging, because all I'm doing is:
ImplicitAnimationController allDisabled = ImplicitAnimationController::create(imageView) .enabled(false); imageView->setTranslationX(imageView->translationX
() + diffx);
... and adjusting the opacity.
(for the three photos: The current photo, the one to the right moving into the scene, and the one to the left, going further out of scene)
Is the conclusion that Cascades can't handle adjusting the translationX of three ImageViews in response to Touch move events? Surely it must, but it's very unclear to me what I would be doing that would be getting in the way from that happening properly.
03-28-2014 11:37 PM
I created a new project to reproduce my issue with the smallest amount of code and QML as possible, and I couldn't reproduce my issue Oh oh. You know what that means. So, after lots and lots of hunting I discovered that my touch handler (which is quite large and complex) was incorrectly calling a function during the finger movement that loaded a QImage, and that took in the ballpark of 150 ms. (and, once that had been done once for an image, it didn't do it again, which explained why the first swipe on an image wasn't performant, but later ones were)
So, mystery solved. I'm very glad this was my issue and not bad cascades performance.
That said, if people have thoughts as to the questions I pose above, that would still be helpful.
04-01-2014 10:43 AM
I'm glad you sorted it out
The docs about whether you can slow down the UI or not are a bit ambiguous. Generally, the actual rendering should happen smoothly because that's happening on another thread, but you can slow down the user interaction part with complicated slots, since that's all happening on your application thread.
And yes, you can't access UI stuff from another thread. Doing signals and slots is the recommended way to do it (and most other things ).
04-05-2014 02:30 PM
Thanks so much for your reply.
Something that I still find unintuitive is why the UI gets slowed down by the application's main thread loading JPEGs into ImageViews. You mentioned that the application thread should in general not slow down the UI because they are in separate threads, except if there are complicated signals being sent around. So far as I know, I don't (?) have complicated signals getting sent around, and so I'm still confused why application logic would be slowing down the UI thread.