Design Strategies for Graphics Code That PerformsIt is appropriate to think of graphics code separately from higher-level user interface code. User interface code generally uses higher-level abstractions offered by the operating system or the programming framework. Concepts such as Button controls, ListBoxes, ListViews, Panels, and Grid controls are examples of these abstractions. At the lowest-level controls are implemented as a series of drawing instructions and low-level interactions with the operating system and users. Application developers almost never have to deal with the controls at this low-level of abstraction. Typically, when using user interface controls, the developer relies on the fact that the underlying graphics needed to draw and maintain the controls are done in an optimized way; the developer's goal is to use the controls in as efficient a way as possible at a high level. In contrast, when working with graphics the developer needs to specifically think of the low-level painting tasks that need to be done and how to accomplish them most efficiently.From a level of abstraction point of view, higher-level user interface code is analogous to doing network communications using HTTP requests and responses; graphics code is analogous to working with sockets or even packet-level communications. As with networking, whenever possible it is better to use the higher-level abstractions. The higher-level abstractions are easy to use, efficient for most tasks, and well tested. Working efficiently with lower-level frameworks such as graphics libraries requires a more detailed understanding and management of what is going on and careful consideration of what efficiencies can be achieved across the application. Graphics code looks and acts differently than user interface code.Because the end goal of low-level graphics work is to provide parts of a rich user interface, low-level graphics code and higher-level user interface code must interact at some point. The graphics you create will almost certainly coexist alongside higher-level user interface components such as Buttons, Menus, and ListView controls. Only rarely does it make sense for the entire user interface to be driven by low-level graphics code, for the simple reason that it is usually not useful for an application to do things such as manually paint controls and deal with lower-level user interactions. Instead it is much more efficient for application developers to use predeveloped and proven libraries for user interface needs and only use graphics functions to drive those parts of the user interface that have extraordinary needs. For this there needs to be well-defined models for how to mix higher-level user interface code with lower-level graphics code. Ways to Integrate Graphics with User Interface CodeThere are several ways to integrate low-level graphics code and higher-level user interface code. It is important to choose the right one for the task your application is trying to accomplish. Three of the most common ways for graphics code and user interface code it interact are as follows:Displaying bitmap images in a PictureBoxPainting to a FormImplementing a custom controlEach of these are described and demonstrated below. Displaying Bitmap Images in a PictureBoxDrawing graphics onto an offscreen bitmap and displaying that bitmap in a PictureBox control on a form is one of the easiest and most powerful ways to leverage custom-drawn graphics. For almost all business graphics you want to draw, and even for many games, this is an excellent approach because of its simplicity.To utilize this approach, your application simply creates a Bitmap object and its corresponding Graphics object and then uses the Graphics object to draw everything it wants to onto the bitmap before assigning the bitmap to the PictureBox control's Image property. The PictureBox control takes care of the rest. It makes sure the onscreen image is updated and it handles all repaints necessary; if part or all of the PictureBox gets obscured by another control or window temporarily, the PictureBox handles repainting itself when it becomes unobscured. The PictureBox makes sure that it is displaying an image identical to that of the bitmap; no additional application logic is required to facilitate this.Because of its simplicity, this approach is excellent for displaying graphical data that does not change second to second. It is also reasonably efficient because the PictureBox knows when it needs to redraw itself. PictureBox controls can also expose some events that your application code can hook into (for example, Click), which enables you to add some interactivity to them as well, further raising the utility of this approach.Figure 11.5 shows an application consisting of a Button and a PictureBox. Pressing the Button creates an offscreen bitmap, draws an ellipse and some text to it, and assigns the bitmap to the PictureBox. Listing 11.6 shows the code to place into button1's click event to do this. Perform the following steps to build and run the application:
Figure 11.5. Drawing into an offscreen bitmap and sending it to a picture box.![]() Listing 11.6. Drawing into an Offscreen Bitmap and Sending It to a Picture Box
Painting to a FormThere are two ways to paint to a form:Create a Graphics object for it and use that object to do your painting.Hook into the OnPaint() function for the form and get handed a Graphics object to draw with.Creating a Graphics object for a form and drawing onto it is generally of limited use because any drawing you make is volatile. The form will be perfectly happy to let your application draw everything it wants onto its surface, but will not keep track of anything that you have drawn. This means that the next time something causes the form to be repainted everything your code has painted will disappear. Therefore, this method is not suitable for graphics that you want to hold on the screen for any length of time. For example, this is a poor way to draw a chart for the user to view on their mobile device because your application is not in control of when all or parts the picture it has drawn will be overwritten. However, this drawing method can be useful in cases where you are continually redrawing the whole onscreen image, such as in an action game. If the image is redrawn several times a second, it does not matter if it is volatile.Listing 11.7 demonstrates this kind of drawing. To build and run the application, follow these steps:
Listing 11.7. Creating a Graphics Object for a FormHooking into the Paint requests for a form is the way to get involved in the low-level drawing mechanics of that form. When the operating system needs help in restoring the way the user interface looks, it requests that windows and controls repaint themselves. Think of the operating system as a museum curator looking after each piece of art your application has created. For simple maintenance, it may be able to do the work itself, but for more complex tasks it is going to need to call the original artist in to restore the work to its pristine condition. The operating system is in charge of determining when it needs your application to redraw parts of its user interface; however, you can tell the operating system that your form or control needs to be redrawn by calling its Invalidate() function.Paint functions are intended to be called by the operating system when your application needs to have "touch up" work done on it. If you have a reasonably static graphical image that you want to redraw only when necessary, hooking into the Paint function is a good way to do this. However, you should consider whether using a PictureBox control to display your information would also meet your needs; with the PictureBox approach, you do not need to deal with any low-level repaint requests.Listing 11.8 contains an example Paint function implementation that draws a rectangle and a piece of text onto the form when paint requests are made if it. This paint function should be placed inside a form's code. There are three experiments worth trying with this code to give you a better idea of how and when the operating system calls the paint function:Place a button on the form that calls this.Update(). Note that this typically does not cause the OnPaint function to be called. Calls to Update() only request that the form or control be repainted if there are regions that are invalid.Place a button on the form that calls this.Invalidate(). This does cause the OnPaint requests to get queued up and handled as soon as the operating system has time to do it, which is usually very quickly. Calling this.Invalidate(); this.Update(); will cause a repaint to immediately occur.Place a button on the form that displays a MessageBox control. Move the message box around above the Form and see what causes OnPaint() to be called. In my experiments moving the MessageBox over the colored rectangle did not cause OnPaint() to get called (the operating system seemed to take care of this repainting without needing our application's help), but moving the MessageBox over the printed text did cause OnPaint() to get called. Hence, it appears that the operating system is capable of performing rudimentary repaints for you without needing to call OnPaint() for everything. The key thing to understand about hooking into Paint functions is that you are not in control of when they get called. You can force them to happen by declaring an area invalidated, but you cannot control external factors that may cause Paint requests. For this reason, it is extremely important that the painting operations occur as quickly and with as little overhead as possible. Slow repainting will make your application look and behave very sluggishly. Listing 11.8. Hooking into the Paint Function for a Form
Implementing a Custom ControlEncapsulating graphics code into a custom control is a good way to package up graphics functionality in a way that can be reused. Implementing a custom control is very similar to overriding the Paint function of a form. It is a low-level approach for manually rendering and maintaining a user interface component.Custom controls are useful if you want to build an interactive experience for users working with graphical data. If you need a chart that dynamically changes and updates as the user clicks different portions of it or a grid that pivots data as users work with it, a custom control may be the best choice. If all you are doing is outputting graphical data that the user will look at in a read-only way, creating a custom control is unneeded complexity. As with hooking into the OnPaint() function as shown above, it is worth considering whether a PictureBox can meet your needs before jumping into a low-level custom control implementation. If your goal is to produce good looking read-only charts, a class that creates an offscreen bitmap and then displays it in a PictureBox control is sufficient for the task. If you decide later that what your application really needs is an interactive custom control experience, the same class you used to create the static chart image is a good place to start for building your custom control. Start simple and only move to lower-level paradigms when higher-level alternatives prove insufficient. Using this approach you will get the job done faster and will do so with fewer low-level debugging headaches.There are good reasons to build custom controls and the main one is interactivity. The advantages of implementing a custom control are that it gets the ability to respond to low-level events such as mouse movements over the screen area it is in charge of and it gets full control of painting in the region owned by the control. In cases where you want to be able to draw onscreen highlights dynamically, having a custom control implementation can make a lot of sense. Custom controls can also create their own events that the rest of the application can hook into and respond to.The case of a bar-chart control may illustrate the power of custom controls. Most controls have a Click event to let the application know that a part of the control has been clicked. In a bar-chart control, it may be desirable not just to let the application's developer know that the bar-chart has been clicked, but additionally to indicate which bar-chart column, if any, was selected by the user. To enable this, a bar-chart custom control could create a ColumnClick event that is triggered whenever the area of a chart column it is displaying gets clicked. This event could give the application's developer specific information on which bar-chart column has been clicked so they could write code to respond to this event. The control could also offer the device's users visual feedback such as highlighting the selected column to let them know that their selection has been registered.Custom controls can be powerful abstractions to use in your application development, but it is important not to invent unnecessary reasons to use them. If high interactivity is required with the end user and/or detailed custom events are useful for application developers to have exposed to them, a custom control solution is worth considering.Listing 11.9 and Listing 11.10 show a very simple implementation of a custom control in the .NET Compact Framework. Listing 11.9 shows the code for the control itself, and Listing 11.10 shows the code that is used to dynamically create the control instance, place it into a form, and hook up an event handler to the control. Figure 11.6 shows an image of a Pocket PC running an application that uses this basic custom control. Figure 11.6. MessageBox shown in response to the custom control's event being triggered.![]()
Listing 11.9. A Simple Custom Control That Changes Colors and Fires a Custom Event
Listing 11.10. Code to Place Inside a Form to Create an Instance of the Custom Control
Think About Where You Are Doing Your Drawing, Onscreen or OffscreenGood-looking graphics are as much an art as engineering; this means that technique and planning are equally important. Drawing a single rectangle on a screen or a single piece of text will run quickly enough. Drawing a complex chart or a game board "on-the-fly" onto the visible screen area will likely result in a shoddy user experience. Consider the case of a complex chart that takes 1.5 seconds to draw. This amount of time is well above the minimum human threshold of awareness, and human eyesight can discern quite a few discrete events that go on during this time span. If drawing a chart consists of drawing a background image, drawing and labeling the axis of the chart, drawing lines on the chart, placing dots where actual data points are, and generating a key table that identifies each data set by line color, this can result in a very messy visual build if done in front of the user. The result is even worse if the graphics being produced are for an action game; in this case, the user will experience a great deal of flicker as things get moved around and painted onto the screen.A second reason for not drawing in sight of the user is that the user interface bitmap space is usually volatile. Unlike an offscreen bitmap that your code owns to itself, onscreen real estate is a resource shared by all the running applications' windows and controls and the operating system. Most operating systems do not maintain whatever images you may draw onto the surface of the user interface. This means that you will lose any images you drew onto the user interface if another application dialog comes up in front of the window your code was drawing to.It is far more aesthetically pleasing and less error prone to draw to an offscreen buffer and then copy the results into the foreground when your drawing is complete. This kind of solution is also more portable and easier to design, maintain, and debug. Instead of needing to learn the intricacies of the operating system's painting model, your application can own as much of the drawing as possible in its own well-understood environment and interface specifically with the user interface only when it is done. An offscreen drawing approach is almost always the best way to go for graphical operations of any complexity, even if you need to bring up a wait cursor while the image is rendering.As discussed in the previous sections, the .NET Compact Framework has two good ways to move an offscreen drawing into the user interface. The first is using a PictureBox control and setting its Image property to the bitmap you have just completed drawing (for example, pictureBox1.Image = myNewBitmap).The second way is to get a Graphics object for the user interface element it is drawing into (usually a Form object) and to call Graphics.DrawImage() to do the image transfer: It is specifically worth noting that there are several overloads available for Graphics.DrawImage(). The one listed above is the simplest and fastest; it just takes the bits in one bitmap and copies them into another. Other overloads allow more complex abilities that enable things such as drawing only a specified region of the source image into the destination image, stretching or compacting the image being copied and using a transparency mask to allow see-through regions in an image. Each of these performs some kind of a transformation when copying the image from the source to the destination. In general, the more complex a transformation your code wants to perform during the image copy, the more costly it will be performance wise.One of the best things you can do to maintain good performance is to make the source and destination bitmaps match as closely as possible. If it is possible make sure that the image transfer is a 1:1 bit copy and that the pixels are not being stretched or compressed. Use transparency masks only when they make sense. Although not an issue when using the .NET Compact Framework, other runtime frameworks may offer a choice in the number of colors used in an image or the bit depth used to maintain this information. Matching this information in the source and destination bitmaps can improve performance significantly. The overall goal is to get rid of any impedance mismatches between source and destination so that the image transfer operation resembles a direct memory-copy operation as much as possible. Direct memory copies are generally optimized for performance on all platforms. Define Your Rendering ProcessSophisticated drawings tend to grow organically in their complexity. An initial prototype is built to do graphing. Onto this prototype is added the ability not just to graph but also label the graph axis. Marker points are added to designate where the data points lie and where the connecting lines are. Support for displaying multiple data sets simultaneously is added. A key table is added to identify the different data sets. Support for multiple colors is added. The ability to draw on top of a background image is added. Chart titles are added. Suddenly a huge pile of code exists that does the graphing. Each graphing piece is worried only about its own work, and little thought has been given to overall efficiency. Game code can suffer the same feature creep as new features get added and piled on top of the list of things that need to be done to render a game board.For this reason, it is important to spend time and rationalize the rendering process you are using. Doing so will help you get the most performance out of your system as well as give you added flexibility to add new features. It should be a milestone task to review any changes to your rendering model and to rationalize them into a system tuned for maximum overall throughput. Doing this work is not hard; it just requires that you write down in order what steps are being taken and what resources are being used at each step.Consider the following example.Unrationalized Chart-Graphing Pipeline
In looking at the model above, we can see that we are creating and disposing of a lot of identical pens, brushes, and fonts. Without much hard work, this model can be rationalized to require fewer resource allocations.Consider this more rationalized chart-graphing pipeline.Resource Creation
Just getting all the pieces laid out in order can often identify areas for improvement. If you cannot state in a few simple steps what your application's rendering process is, it is a good sign that the model needs to be looked at and rationalized. Procrastination Is Bad, Precalculate Everything PossibleWhen designing higher-level user interface code, it is a good strategy to try to defer work for as long as possible; populating controls with a large amount of data that a user may never view can be a waste of processing time. Deferring work is a good strategy because the data being populated into the user interface controls is dynamic, and the potential amount of data is often large enough that populating the user interface with it would be wasteful. If the information was bounded in size and known in advance, it would make a great deal of sense to try to precalculate or pre-initialize the data ahead of time. This is often the case with graphical data.Graphics work is often made up of many small repetitive tasks strung together one after another. As such, it is a good candidate for using prefabricated pieces. Any drawing that can be done in advance will save time. This is doubly true if the work can be done at design time before the application ever runs. If it is possible to examine the steps of your application's rendering process and remove steps that can be done outside of your rendering process, your application's performance will benefit.Let's take a look at two common cases to illustrate this point. Example 1: Pre-Rendering for a Business ChartFigure 11.7 shows a fairly rich image that displays (fictitious) data in a bar chart. The chart attempts to visually show the relative growth rates of different economies over a period of time. To aid the user in visually comprehending the data, it was decided to show each country's data in columns painted with the country's currency symbol. Further, the chart has a background image; in this case, a smooth gradient going from dark to light; this could in theory be any background image. Figure 11.7. Graph showing growth rates for different countries.![]() Figure 11.8. Predrawn background for graph.![]() Figure 11.9. Predrawn bar chart for decorated columns.![]() Example 2: Pre-Rendering for an Action GameIt is interesting to observe that the optimizations for rendering in an action game have a strong resemblance to those used in rendering our chart. Figure 11.10 shows an image of an action game I wrote for the .NET Compact Framework to demonstrate graphics concepts. Figure 11.10. Action game written for the .NET Compact Framework.![]() The game has a static photograph used as a background image.Static foreground These are all the items that are in the foreground that do not change from frame to frame. These are the floors and ladders on the game field. They are an important part of the foreground and change from level to level, but they do not change from frame to frame when rendering the game.Dynamic foreground These are all the active elements on the screen that may change from frame to frame. They include the caveman, the cavewoman, the two boulders, the bird, and the four animated torches on the screen. These kinds of graphical elements are commonly referred to as "sprites." Also in the dynamic foreground are the energy bar on the upper left of the play field and the Score/Bonus counter on the upper right. All of these elements are represented programmatically by objects in a collection.Preceding the rendering loop is the loading of the background image and the drawing of the play field floors and ladders onto it. These are combined in memory and held as our background image. The floors and ladder do not change from frame to frame, so there is no point in rendering them along with every frame. Because we already need to have a background image loaded, we take no additional size hit for drawing our static play field onto it.The basics of the rendering engine are simple. Every active element on the screen has an object associated with it that holds its position on the play field as well as instructions on how to render it. The rendering loop holds a destination bitmap. This destination bitmap is the same size as the background image. A Graphics object for the destination bitmap has been created and is maintained throughout the application's lifetime. All the Fonts, Brushes, and Pens needed for drawing are also kept globally so that any image rending code that needs them can access them without the need to allocate its own drawing resources.When a frame is rendered, the background image is first copied into the destination bitmap. After this, each of the objects in the render collection is asked to render itself onto the destination bitmap. Some of these objects are text objects; these call Graphics.DrawString() to draw their text onto the destination bitmap. One of the other nontext objects is a rectangle "energy bar"; this is drawn as two rectangles, an outer border and an inner filled rectangle. The rest of the objects are multiframe bitmap images, meaning that they have several bitmaps that can represent the different possible states of the sprite; this allows the objects to be animated by flipping through the images.Hank (the caveman) has 8 images associated with him; 2 for running left, 2 for running right, 2 for climbing, 2 facing forward. Each sprite has an internal state machine that keeps track of which image to display when it is rendered. The images are small; Hank's images are 21 x 35 pixels, rounding up to the nearest 4 (probable alignment in memory) this gives us 24 x 36 = 864 pixels. 864 pixels x 4 bytes/pixel = about 3.5KB per image. Loading all of Hank's 8 images into memory should take somewhere on the order of 28KB of memory, which is pretty manageable.Each of these small sprites is rendered using the same background transparency mask when they are copied onto the background image. This means that one of the colors of the sprite's bitmap is designated as transparent and allows the background image to show through when it is copied onto it. This is not as fast as a straight memory copy of opaque rectangular images but produces a much nicer result by allowing images to appear as nonrectangular.As noted above, this game-rendering model has a great deal in common with our chart-drawing scheme. The game does as much pre-rendering as possible outside of our rendering loop. It is also using predrawn images wherever possible. The game's rendering loop only does a simple bitmap copy of the background image, draws a handful of small sprites with background transparency bitmaps, draws a small amount of text, and draws a few simple rectangles. As long as the game does not allocate memory or otherwise waste time inside the rendering loop, this can all occur very quickly. Cache Commonly Used ResourcesOften when writing graphical processing code your application will use the same resources over and over again. Common classes that get reused are Bitmaps, Pens, Brushes, and Fonts. It is wasteful to keep loading or creating the same resources over and over; unnecessary processing time is spent either reloading a resource from storage or re-requesting it from the operating system. Having equivalent resources loaded in memory at the same time is wasteful as well; for instance, multiple copies of the same bitmap image are wasteful to have around when a single loaded copy could be shared. Finally, disposing of resources further taxes system performance. To avoid these performance taxes, there are cases where it is useful for your application to have an application global resource dispenser that takes care of loading, caching, and disposing of commonly used resources.Listing 11.11 shows an example with three approaches to loading and maintaining resources in memory.Approach 1 is a latch-based approach Any code that needs a common resource calls a static property on the GraphicsGlobals class to get it. If the resource is already loaded, it is returned to the requester. If the resource has not yet been loaded, it is created, cached, and then returned to the user. This approach as two advantages. (1) The requester need not be concerned about any initialization code; the request will always return a valid object. (2) The managed resource can be freed if the application concludes that it will not be needed for a while; if needed, the next request will re-create it. The only disadvantage of this approach is the slight overhead of going through a property accessor function whenever the resource is requested; this overhead is typically negligible.Approach 2 is a batch-based approach. When a group of resources have similar usage patterns and lifetimes, they can be initialized in bulk. Code that wants to make use of these global resources can directly access the variables, but it must be sure that they have been initialized before they are used. These resources should also be released and have Dispose() called on them when the application gets to a state when the resources will not be utilized for a while.Approach 3 is a collection-based approach. When a group of resources are always used together, such as the bitmap frames of an animated image, it can make sense to load them together and return them as an array or collection of resources. If the resources are expensive to load or take up significant memory, it may be useful to keep a copy of them cached centrally so that duplicate creations of the same resources do not occur. As above, it is important to have a strategy to release and Dispose() of these resources when they are no longer needed. Listing 11.11. Three Useful Ways to Cache Graphical Resources
Find Ways to Avoid Object Allocations for Repeated or Continuous DrawingUnnecessary object allocation and destruction is one of the most common causes of poorly performing graphics code. Graphics code often runs inside loops or is called often. Further, graphics objects are relatively expensive, using both significant amounts of memory as well as system resources. If your mobile device application is allocating and freeing memory each time it is called to render graphics, you will create memory pressure and this will lead to frequent garbage collections and stalls in your application's performance. It is therefore important keep a close eye out for any kinds of object allocations that may be occurring inside your graphics rendering code. This particularly applies to objects associated with graphics resources but is also true for any other objects (for example, collections, arrays, strings) that may be present in your graphics rendering code.A few examples of things to look out for:Bitmap objects There are often cases when it is useful to have a Bitmap object that is used as a temporary holding space for an image or part of an image your application is building. If your mobile device application needs temporary space to do drawing in, this is fine; but avoid creating and disposing of multiple bitmaps in your drawing cycles. It is usually much better to have one scratchpad space that is used over and over again as a common resource than it is to go through the expense of continually creating and disposing temporary bitmaps. The scratchpad bitmap should be of the dimensions of the largest scratchpad space you need in your routines (but no larger). If the scratchpad space needs to be cleared before drawing to it this is easy and fast to do with a call to Graphics.Clear(). Also, as shown in the sample code above, it is worth loading and caching commonly used bitmaps in memory. Make sure only one instance of each bitmap is loaded at any given time. If your application has multiple copies of identical bitmap resources loaded, it is wasting a lot of memory.Graphics objects If you are going to do continuous drawing into a bitmap, such as drawing frame after frame of game board, it is probably worth caching the Graphics objects of the offscreen bitmap as well as the onscreen destination for the drawing. Doing so will prevent you from needing to continually allocate and free these objects. In your drawing cycle, you should never have to allocate the Graphics object for a bitmap or form more than once; ideally you should avoid needing to allocate and dispose of them at all. The same holds true for any other bitmaps that you are drawing to; cache their Graphics objects instead of continually recreating them. Keep in mind that a Graphics object is only needed for bitmaps that you are drawing into; if a bitmap is used only as a source of image information, you should not need a Graphics object for it at all.Fonts, Brushes, Pens, ImageAttributes A common and understandable mistake that developers make is overly aggressively managing resource lifetimes at a micro level without looking at their usage at a macro level. A graphics image you are drawing may consist of 30 discrete drawing steps where lines are drawn, ellipses are filled, text is printed, and bitmaps are copied. Each of these operations uses some combination of Pens, Fonts, Brushes, and, if you are using transparency masks, ImageAttributes. The situation is made more critical on mobile devices because unlike the desktop .NET Framework, the .NET Compact Framework does not have static versions of the basic Brushes and Pens; for example System.Drawing.Pens.Blue and System.Drawing.Brushes.DarkOrange exist on the desktop .NET Framework but must be allocated on the .NET Compact Framework. The solution to this challenge it to create your own global set of Pens and Brushes that you will use throughout for your application's drawing needs. You should review the code in your application's drawing cycles and keep a careful eye out for the repeated creation of identical resources and remove these redundancies. The Fonts, Brushes, Pens, and ImageAttributes you need should either only be allocated once in the drawing cycle or be cached globally when continual drawing is needed by your application.Value types that get cast to objects (a.k.a."boxed"into objects) The most common value type used in graphics code is the System.Drawing.Rectangle structure. Working with value types is normally very efficient because they can be allocated on the stack (and not on the global memory heap as objects are). However value types can also be treated as objects and placed into arrays or collections or passed any place that accepts an object type. Keep a careful eye on your use of value types to make certain that you are not continually allocating and releasing memory implicitly through their "boxing" and "unboxing" into objects. The need to avoid continual object allocations in your application's graphics code must be balanced with the need to manage your mobile device application's global memory usage. If only certain parts of your application make use of graphics functionality, consider disposing of these resources when your application enters a state where the graphics resources are not of imminent use. Using a state machine and defining which graphics resources can be released when leaving a specific application state is a good design practice. |