diff options
Diffstat (limited to 'doc/src')
-rw-r--r-- | doc/src/declarative/qdeclarativeperformance.qdoc | 694 |
1 files changed, 645 insertions, 49 deletions
diff --git a/doc/src/declarative/qdeclarativeperformance.qdoc b/doc/src/declarative/qdeclarativeperformance.qdoc index 12b345a3ff..a2e72ce156 100644 --- a/doc/src/declarative/qdeclarativeperformance.qdoc +++ b/doc/src/declarative/qdeclarativeperformance.qdoc @@ -30,17 +30,471 @@ \inqmlmodule QtQuick 2 \title QML Performance -\section1 Opaque Items +\tableofcontents -Items hidden behind an opaque item incur a cost. If an item will be enitrely -obscured by an opaque item, set its opacity to 0. One common example of -this is when a "details" page is shown over the main application view. +\section1 Timing Considerations -\section1 Clipping +As an application developer, you must strive to allow the rendering engine +to achieve a consistent 60 frames-per-second refresh rate. 60 FPS means +that there is approximately 16 milliseconds between each frame in which +processing can be done, which includes the processing required to upload +the draw primitives to the graphics hardware. -\i clip is set to false by default. Enable clipping only when necessary. +In practice, this means that the application developer should use asynchronous, +event driven programming wherever possible, should use worker threads to do +significant processing, should never manually spin the event loop, and should +never spend more than a couple of milliseconds per frame within blocking functions. +Failure to do so will result in skipped frames, which has a drastic effect on the +user experience. -\section1 Anchors vs. Binding +\section1 Profiling + +The most important tip is: use the QML profiler included with Qt Creator. Knowing +where time is spent in an application will allow you to focus on problem areas which +actually exist, rather than problem areas which potentially exist. See the Qt Creator +manual for more information on how to use the QML profiling tool. + +Determining which bindings are being run the most often, or which functions your +application is spending the most time in, will allow you to decide whether you need +to optimize the problem areas, or redesign some implementation details of your +application so that the performance is improved. Attempting to optimize code without +profiling is likely to result in very minor rather than significant performance +improvements. + +\section1 JavaScript + +Most QML applications will have a large amount of JavaScript code in them, in the +form of dynamic functions, signal handlers, and property binding expressions. +This is not generally a problem (in fact, due to some optimizations in the QML engine +(bindings compiler, etc) it can for some use-cases be faster than calling a C++ function) +however care must be taken to ensure that unnecessary processing isn't triggered +accidentally. + +\section2 Type-Conversion + +One major cost of using JavaScript is that in most cases when a property from a QML +element is accessed, a JavaScript object with an external resource containing the +underlying C++ data (or a reference to it) is created. In most cases, this is fairly +inexpensive, but in others it can be quite expensive. One example of where it is +expensive is assigning a C++ QVariantMap Q_PROPERTY to a QML "variant" property. +Lists can also be expensive, although sequences of specific types (QList of int, +qreal, bool, QString, and QUrl) should be inexpensive; other list types involve an +expensive conversion cost (creating a new JavaScript Array, and adding new elements +one by one, with per-element conversion from C++ type instance to JavaScript value). + +Converting between some basic property types (such as "string" and "url" properties) +can also be expensive. Using the closest matching property type will avoid unnecessary +conversion. + +If you must expose a QVariantMap to QML, use a "var" property rather than a "variant" +property. In general, "property var" should be considered to be superior to +"property variant" for every use-case in QtQuick 2.0 (note that "property variant" +is marked as obsolete in the QtQuick 2.0 documentation), as it allows a true JavaScript +reference to be stored (which can reduce the number of conversions required in certain +expressions). + +\section2 Resolving Properties + +Property resolution takes time. While in some cases the result of a lookup can be +cached and reused, it is always best to avoid doing unnecessary work altogether, if +possible. + +In the following example, we have a block of code which is run often (in this case, it +is the contents of an explicit loop; but it could be a commonly-evaluated binding expression, +for example) and in it, we resolve the element with the "rect" id and its "color" property +multiple times: + +\qml +// bad.qml +import QtQuick 2.0 + +Item { + width: 400 + height: 200 + Rectangle { + id: rect + anchors.fill: parent + color: "blue" + } + + function printValue(which, value) { + console.log(which + " = " + value); + } + + Component.onCompleted: { + var t0 = new Date(); + for (var i = 0; i < 1000; ++i) { + printValue("red", rect.color.r); + printValue("green", rect.color.g); + printValue("blue", rect.color.b); + printValue("alpha", rect.color.a); + } + var t1 = new Date(); + console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations"); + } +} +\endqml + +We could instead resolve the common base just once in the block: + +\qml +// good.qml +import QtQuick 2.0 + +Item { + width: 400 + height: 200 + Rectangle { + id: rect + anchors.fill: parent + color: "blue" + } + + function printValue(which, value) { + console.log(which + " = " + value); + } + + Component.onCompleted: { + var t0 = new Date(); + for (var i = 0; i < 1000; ++i) { + var rectColor = rect.color; // resolve the common base. + printValue("red", rectColor.r); + printValue("green", rectColor.g); + printValue("blue", rectColor.b); + printValue("alpha", rectColor.a); + } + var t1 = new Date(); + console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations"); + + } +} +\endqml + +Just this simple change results in a significant performance improvement. + +\section2 Property Bindings + +A property binding expression will be re-evaluated if any of the properties +it references are changed. As such, binding expressions should be kept as +simple as possible. + +If you have a loop where you do some processing, but only the final result +of the processing is important, it is often better to update a temporary +accumulator which you afterwards assign to the property you need to update, +rather than incrementally updating the property itself, in order to avoid +triggering re-evaluation of binding expressions during the intermediate +stages of accumulation. + +The following contrived example illustrates this point: + +\qml +// bad.qml +import QtQuick 2.0 + +Item { + id: root + width: 200 + height: 200 + property int accumulatedValue: 0 + + Text { + anchors.fill: parent + text: root.accumulatedValue.toString() + onTextChanged: console.log("text binding re-evaluated") + } + + Component.onCompleted: { + var someData = [ 1, 2, 3, 4, 5, 20 ]; + for (var i = 0; i < someData.length; ++i) { + accumulatedValue = accumulatedValue + someData[i]; + } + } +} +\endqml + +The loop in the onCompleted handler causes the "text" property binding to +be re-evaluated six times (which then results in any other property bindings +which rely on the text value, as well as the onTextChanged signal handler, +to be re-evaluated each time, and lays out the text for display each time). +This is clearly unnecessary in this case, since we really only care about +the final value of the accumulation. + +It could be rewritten as follows: + +\qml +// good.qml +import QtQuick 2.0 + +Item { + id: root + width: 200 + height: 200 + property int accumulatedValue: 0 + + Text { + anchors.fill: parent + text: root.accumulatedValue.toString() + onTextChanged: console.log("text binding re-evaluated") + } + + Component.onCompleted: { + var someData = [ 1, 2, 3, 4, 5, 20 ]; + var temp = accumulatedValue; + for (var i = 0; i < someData.length; ++i) { + temp = temp + someData[i]; + } + accumulatedValue = temp; + } +} +\endqml + +\section2 Sequence tips + +As mentioned earlier, some sequence types are fast (eg, QList<int>, QList<qreal>, +QList<bool>, QList<QString>, QStringList and QList<QUrl>) while others will be +much slower. Aside from using these types wherever possible instead of slower types, +there are some other performance-related semantics you need to be aware of to achieve +the best performance. + +Firstly, there are two different implementations for sequence types: one for where +the sequence is a Q_PROPERTY of a QObject (we'll call this a reference sequence), +and another for where the sequence is returned from a Q_INVOKABLE function of a +QObject (we'll call this a copy sequence). + +A reference sequence is read and written via QMetaObject::property() and thus is read +and written as a QVariant. This means that changing the value of any element in the +sequence from JavaScript will result in three steps occurring: the complete sequence +will be read from the QObject (as a QVariant, but then cast to a sequence of the correct +type); the element at the specified index will be changed in that sequence; and the +complete sequence will be written back to the QObject (as a QVariant). + +A copy sequence is far simpler as the actual sequence is stored in the JavaScript +object's resource data, so no read/modify/write cycle occurs (instead, the resource +data is modified directly). + +Therefore, writes to elements of a reference sequence will be much slower than writes +to elements of a copy sequence. In fact, writing to a single element of an N-element +reference sequence is equivalent in cost to assigning a N-element copy sequence to that +reference sequence, so you're usually better off modifying a temporary copy sequence +and then assigning the result to a reference sequence, during computation. + +Assume the existence (and prior registration into the "Qt.example 1.0" namespace) of the +following C++ type: + +\code +class SequenceTypeExample : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY (QList<qreal> qrealListProperty READ qrealListProperty WRITE setQrealListProperty NOTIFY qrealListPropertyChanged) + +public: + SequenceTypeExample() : QQuickItem() { m_list << 1.1 << 2.2 << 3.3; } + ~SequenceTypeExample() {} + + QList<qreal> qrealListProperty() const { return m_list; } + void setQrealListProperty(const QList<qreal> &list) { m_list = list; emit qrealListPropertyChanged(); } + +signals: + void qrealListPropertyChanged(); + +private: + QList<qreal> m_list; +}; +\endcode + +The following example writes to elements of a reference sequence in a +tight loop, resulting in bad performance: + +\qml +// bad.qml +import QtQuick 2.0 +import Qt.example 1.0 + +SequenceTypeExample { + id: root + width: 200 + height: 200 + + Component.onCompleted: { + var t0 = new Date(); + qrealListProperty.length = 100; + for (var i = 0; i < 500; ++i) { + for (var j = 0; j < 100; ++j) { + qrealListProperty[j] = j; + } + } + var t1 = new Date(); + console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds"); + } +} +\endqml + +The QObject property read and write in the inner loop caused by the +\c{"qrealListProperty[j] = j"} expression makes this code very suboptimal. Instead, +something functionally equivalent but much faster would be: + +\qml +// good.qml +import QtQuick 2.0 +import Qt.example 1.0 + +SequenceTypeExample { + id: root + width: 200 + height: 200 + + Component.onCompleted: { + var t0 = new Date(); + var someData = [1.1, 2.2, 3.3] + someData.length = 100; + for (var i = 0; i < 500; ++i) { + for (var j = 0; j < 100; ++j) { + someData[j] = j; + } + qrealListProperty = someData; + } + var t1 = new Date(); + console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds"); + } +} +\endqml + +Secondly, a change signal for the property is emitted if any element in it changes. +If you have many bindings to a particular element in a sequence property, it is better +to create a dynamic property which is bound to that element, and use that dynamic +property as the symbol in the binding expressions instead of the sequence element, +as it will only cause re-evaluation of bindings if its value changes. + +This is an unusual use-case which most clients should never hit, but is worth being +aware of, in case you find yourself doing something like this: + +\qml +// bad.qml +import QtQuick 2.0 +import Qt.example 1.0 + +SequenceTypeExample { + id: root + + property int firstBinding: qrealListProperty[1] + 10; + property int secondBinding: qrealListProperty[1] + 20; + property int thirdBinding: qrealListProperty[1] + 30; + + Component.onCompleted: { + var t0 = new Date(); + for (var i = 0; i < 1000; ++i) { + qrealListProperty[2] = i; + } + var t1 = new Date(); + console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds"); + } +} +\endqml + +Note that even though only the element at index 2 is modified in the loop, the three +bindings will all be re-evaluated since the granularity of the change signal is that +the entire property has changed. As such, adding an intermediate binding can +sometimes be beneficial: + +\qml +// good.qml +import QtQuick 2.0 +import Qt.example 1.0 + +SequenceTypeExample { + id: root + + property int intermediateBinding: qrealListProperty[1] + property int firstBinding: intermediateBinding + 10; + property int secondBinding: intermediateBinding + 20; + property int thirdBinding: intermediateBinding + 30; + + Component.onCompleted: { + var t0 = new Date(); + for (var i = 0; i < 1000; ++i) { + qrealListProperty[2] = i; + } + var t1 = new Date(); + console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds"); + } +} +\endqml + +In the above example, only the intermediate binding will be re-evaluated each time, +resulting in a significant performance increase. + +\section2 Value-Type tips + +Value-type properties (font, color, vector3d, etc) have similar QObject property +and change notification semantics to sequence type properties. As such, the tips +given above for sequences are also applicable for value-type properties. While +they are usually less of a problem with value-types (since the number of +sub-properties of a value-type is usually far less than the number of elements +in a sequence), any increase in the number of bindings being re-evaluated needlessly +will have a negative impact on performance. + +\section2 Other JavaScript Objects + +Different JavaScript engines provide different optimizations. The JavaScript engine +which QtQuick 2 uses is optimized for object instantiation and property lookup, but +the optimizations which it provides relies on certain criteria. If your application +does not meet the criteria, the JavaScript engine falls back to a "slow-path" mode +with much worse performance. As such, always try to ensure you meet the following +criteria: + +\list +\o Avoid using eval() if at all possible +\o Do not delete properties of objects +\o Try to maintain property-assignment order in different constructor functions +\endlist + +\section1 Common Interface Elements + +\section2 Text Elements + +Calculating text layouts can be a slow operation. Consider using the \c PlainText +format instead of \c StyledText wherever possible, as this reduces the amount of work +required of the layout engine. If you cannot use \c PlainText (as you need to embed +images, or use tags to specify ranges of characters to have certain formatting (bold, +italic, etc) as opposed to the entire text) then you should use \c StyledText. + +You should only use \c AutoText if the text might be (but probably isn't) +\c StyledText as this mode will incur a parsing cost. The \c RichText mode should +not be used, as \c StyledText provides almost all of its features at a fraction of +its cost. + +\section2 Images + +Images are a vital part of any user interface. Unfortunately, they are also a big +source of problems due to the time it takes to load them, the amount of memory they +consume, and the way in which they are used. + +\section3 Asynchronous Loading + +Images are often quite large, and so it is wise to ensure that loading an image doesn't +block the UI thread. Set the "asynchronous" property of the QML Image element to +\c true to enable asynchronous loading of images from the local file system (remote +images are always loaded asynchronously) where this would not result in a negative impact +upon the aesthetics of the user interface. + +Image elements with the "asynchronous" property set to \c true will load images in +a low-priority worker thread. + +\section3 Explicit Source Size + +If your application loads a large image but displays it in a small-sized element, set +the "sourceSize" property to the size of the element being rendered to ensure that the +smaller-scaled version of the image is kept in memory, rather than the large one. + +Beware that changing the sourceSize will cause the image to be reloaded. + +\section3 Avoid Run-time Composition + +Also remember that you can avoid doing composition work at run-time by providing the +pre-composed image resource with your application (e.g., providing elements with shadow +effects). + +\section2 Position Elements With Anchors It is more efficient to use anchors rather than bindings to position items relative to each other. Consider this use of bindings to position rect2 @@ -79,67 +533,209 @@ Rectangle { } \endcode -\section1 Images +\section1 Models and Views + +Most applications will have at least one model feeding data to a view. There are +some semantics which application developers need to be aware of, in order to achieve +maximal performance. + +\section2 Custom C++ Models + +It is often desirable to write your own custom model in C++ for use with a view in +QML. While the optimal implementation of any such model will depend heavily on the +use-case it must fulfil, some general guidelines are as follows: + +\list +\o Be as asynchronous as possible +\o Do all processing in a (low priority) worker thread +\o Batch up backend operations so that (potentially slow) I/O and IPC is minimised +\o Use a sliding slice window to cache results, whose parameters are determined with the help of profiling +\endlist -Images consume a great deal of memory and may also be costly to load. In order -to deal with large images efficiently it is recommended that the Image::sourceSize -property be set to a size no greater than that necessary to render it. Beware that -changing the sourceSize will cause the image to be reloaded. +It is important to note that using a low-priority worker thread is recommended to +minimise the risk of starving the GUI thread (which could result in worse perceived +performance). Also, remember that synchronisation and locking mechanisms can be a +significant cause of slow performance, and so care should be taken to avoid +unnecessary locking. -Images on the local filesystem are usually loaded synchronously. This is usually -the desired behavior for user interface elements, however for large images that -do not necessarily need to be visible immediately, set the Image::asynchronous -property to true. This will load the image in a low priority thread. +\section2 ListModel -\section1 View Delegates +QML provides a ListModel element which can be used to feed data to a ListView. +It should suffice for most use-cases and be relatively performant so long as +it is used correctly. -Delegates must be created quickly as the view is flicked. There are two important -aspects to maintaining a smooth view: +\section3 Populate Within A Worker Thread +ListModel elements can be populated in a (low priority) worker thread in JavaScript. The +developer must explicitly call "sync()" on the ListModel from within the WorkerScript to +have the changes synchronised to the main thread. See the WorkerScript documentation +for more information. + +\section3 Don't Use Dynamic Roles + +The ListModel element in QtQuick 2.0 is much more performant than in QtQuick 1.0. The +performance improvements mainly come from assumptions about the type of roles within each +element in a given model - if the type doesn't change, the caching performance improves +dramatically. If the type can change dynamically from element to element, this optimization +becomes impossible, and the performance of the model will be an order of magnitude worse. + +Therefore, dynamic typing is disabled by default; the developer must specifically set +the boolean "dynamicRoles" property of the model to enable dynamic typing (and suffer +the attendant performance degradation). We recommend that you do not use dynamic typing +if it is possible to redesign your application to avoid it. + +\section2 Views + +View delegates should be kept as simple as possible. Have just enough QML in the delegate +to display the necessary information. Any additional functionality which is not immediately +required (e.g., if it displays more information when clicked) should not be created until +needed (see the upcoming section on lazy initialisation). + +The following list is a good summary of things to keep in mind when designing a delegate: \list -\o Small delegates - keep the amount of QML to a minimum. Have just enough -QML in the delegate to display the necessary information. Any additional functionality -that is only needed when the delegate is clicked, for example, should be created by -a Loader as needed. -\o Fast data access - ensure the data model is as fast as possible. +\o The fewer elements that are in a delegate, the faster they can be created, and thus + the faster the view can be scrolled. +\o Keep the number of bindings in a delegate to a minimum; in particular, use anchors + rather than bindings for relative positioning within a delegate. +\o Set a cacheBuffer to allow asynchronous creation of delegates outside the visible area. + Be mindful that this creates additional delegates and therefore the size of the + cacheBuffer must be balanced against additional memory usage. +\o Avoid using ShaderEffect elements within delegates. +\o Never enable clipping on a delegate. \endlist -\section1 Image resources over composition +\section1 Visual Effects + +QtQuick 2 includes several features which allow developers and designers to create +exceptionally appealing user interfaces. Fluidity and dynamic transitions as well +as visual effects can be used to great effect in an application, but some care must +be taken when using some of the features in QML as they can have performance implications. + +\section2 Animations + +In general, animating a property will cause any bindings which reference that property +to be re-evaluated. Usually, this is what is desired but in other cases it may be better +to disable the binding prior to performing the animation, and then reassign the binding +once the animation has completed. + +Avoid running JavaScript during animation. For example, running a complex JavaScript +expression for each frame of an x property animation should be avoided. -If possible, provide a single image resource, rather than using composition -of a number of elements. For example, a frame with a shadow could be created using -a Rectangle placed over an Image providing the shadow. It is more efficient to -provide an image that includes the frame and the shadow. +Developers should be especially careful using script animations, as these are run in the main +thread (and therefore can cause frames to be skipped if they take too long to complete). -\section1 Limit JavaScript +\section2 Particles -Avoid running JavaScript during animation. For example, running a complex -JavaScript expression for each frame of an x property animation. +The QtQuick 2.0 Particles module allows beautiful particle effects to be integrated +seamlessly into user interfaces. However every platform has different graphics hardware +capabilities, and the Particles module is unable to limit parameters to what your hardware +can gracefully support. The more particles you attempt to render (and the larger they are), +the faster your graphics hardware will need to be in order to render at 60 FPS. Affecting +more particles requires a faster CPU. It is therefore important to test all +particle effects on your target platform carefully, to calibrate the number and size of +particles you can render at 60 FPS. -\section1 Loading later +It should be noted that a particle system can be disabled when not in use +(e.g., on a non-visible element) to avoid doing unnecessary simulation. -Startup time is influenced by the amount of QML that must be loaded. Breaking your -application into components which can be loaded when needed will allow faster startup time. -This allows better runtime memory management by unloading the components when no -longer needed. +See the \l{Qt Quick Particle System Performance Guide} for more in-depth information. -This may be achieved by using either \l Loader or creating components +\section2 Shaders + +Shaders written in GLSL allow for complex transformations and visual effects to be written, +however they should be used with care. Using a ShaderEffectSource causes a scene to +prerendered into an FBO before it can be drawn. This extra overhead is quite expensive. + +A ShaderEffect element can imply a ShaderEffectSource (and the indirect rendering costs +associated with that) and also involves uploading a vertex and fragment shader program +(which is then compiled into a GLSL shader). Each fragment shader runs once for every +pixel of the scene, and so these should be kept as simple as possible. + +\section1 Controlling Element Lifetime + +By partitioning an application into simple, modular components, each contained in a single +QML file, you can achieve faster application startup time and better control over memory +usage, and reduce the number of active-but-invisible elements in your application. + +\section2 Lazy Initialisation + +The QML engine does some tricky things to try to ensure that loading and initialisation of +components doesn't cause frames to be skipped, however there is no better way to reduce +startup time than to avoid doing work you don't need to do, and delaying the work until +it is necessary. This may be achieved by using either \l Loader or creating components \l {Dynamic Object Management in QML}{dynamically}. -\section1 Property Types +\section3 Using Loader -When possible use a specific type, rather than variant, when declaring properties. +The Loader is an element which allows dynamic loading and unloading of components. -\code -Item { - property variant foo: 10 - property real bar: 10 +\list +\o Using the "active" property of a Loader, initialisation can be delayed until required. +\o Using the overloaded version of the "setSource()" function, initial property values can + be supplied. +\o Setting the Loader \l {Loader::asynchronous}{asynchronous} property to true may also + improve fluidity while a component is instantiated. +\endlist - x: foo * 2 - y: bar *3 -} -\endcode +\section3 Using Dynamic Creation + +Developers can use the Qt.createComponent() function to create a component dynamically at +runtime from within JavaScript, and then call createObject() to instantiate it. Depending +on the ownership semantics specified in the call, the developer may have to delete the +created object manually. See \l {Dynamic Object Management in QML} for more information. + +\section2 Destroy Unused Elements + +Elements which are invisible because they are a child of a non-visible element (e.g., the +second tab in a tab-widget, while the first tab is shown) should be initialised lazily in +most cases, and deleted when no longer in use, to avoid the ongoing cost of leaving them +active (e.g., rendering, animations, property binding evaluation, etc). + +An item loaded with a Loader element may be released by resetting the "source" or +"sourceComponent" property of the Loader, while other items may be explicitly +released by calling destroy() on them. In some cases, it may be necessary to +leave the item active, in which case it should be made invisible at the very least. + +See the upcoming section on Rendering for more information on active but invisible elements. + +\section1 Rendering + +The scene graph used for rendering in QtQuick 2.0 allows highly dynamic, animated user +interfaces to be rendered fluidly at 60 FPS. There are some things which can +dramatically decrease rendering performance, however, and developers should be careful +to avoid these pitfalls wherever possible. + +\section2 Clipping + +Clipping is disabled by default, and should only be enabled when required. + +Clipping is a visual effect, NOT an optimization. It increases (rather than reduces) +complexity for the renderer. If clipping is enabled, an item will clip its own painting, +as well as the painting of its children, to its bounding rectangle. This stops the renderer +from being able to reorder the drawing order of elements freely, resulting in a sub-optimal +best-case scene graph traversal. + +Clipping inside a delegate is especially bad and should be avoided at all costs. + +\section2 Over-drawing and Invisible Elements + +If you have elements which are totally covered by other (opaque) elements, it is best to +set their "visible" property to \c false or they will be needlessly drawn. + +Similarly, elements which are invisible (e.g., the second tab in a tab widget, while the +first tab is shown) but need to be initialised at startup time (e.g., if the cost of +instantiating the second tab takes too long to be able to do it only when the tab is +activated), should have their "visible" property set to \c false, in order to avoid the +cost of drawing them (although as previously explained, they will still incur the cost of +any animations or bindings evaluation since they are still active). + +\section2 Manual Layouts + +The scene graph renderer is able to batch up certain operations to minimise the number of +OpenGL state changes required. However, this optimization is only possible for the +built-in layout elements provided by QtQuick 2.0, and cannot be applied to manual layouts. -bar is faster than foo. +Therefore, application developers should use the Row, Column, Grid, GridView and ListView +elements instead of manual layouts wherever possible. */ |