{"id":155,"date":"2009-11-18T18:10:19","date_gmt":"2009-11-18T05:10:19","guid":{"rendered":"http:\/\/www.paradicesoftware.com\/blog\/?p=155"},"modified":"2009-11-18T22:53:03","modified_gmt":"2009-11-18T09:53:03","slug":"more-textures-than-memory","status":"publish","type":"post","link":"http:\/\/www.paradicesoftware.com\/blog\/2009\/11\/more-textures-than-memory\/","title":{"rendered":"More Textures than Memory"},"content":{"rendered":"<p>Icefall uses literally dozens of textures&#8230; (and it will probably be &#8216;hundreds&#8217; by the time the game is completed).<\/p>\n<p>Textures are used for everything, from the mouse cursor, menus, fonts, buttons, to the game world itself, spells\/action icons, monsters, equipment&#8230; everything.<\/p>\n<p>Although my own main PC has 512MB of video (texture) RAM, not everyone does! And<br \/>\nfollowing on from my <a href=\"http:\/\/www.paradicesoftware.com\/blog\/index.php\/2009\/11\/minimum-requirements\/\">minimum requirements<\/a> post, I don&#8217;t really want someone&#8217;s video card memory size to be a limiting factor if everything else meets the requirements, so Icefall needs a way to manage things when there are more textures than there is video memory.<\/p>\n<p>I don&#8217;t want each bit of code that uses textures to worry about whether they&#8217;re loaded or not, so the logical choice is to encapsulate all of the texture handling into one place: I call the class that handles this the <em>TContentManager<\/em> (&#8220;content&#8221; because it also handles sounds, fonts, music, etc.).<\/p>\n<p>When the game code asks the <em>TContentManager<\/em> for a texture: the first thing it does is look to see if that texture is already available in video memory: if so, it just hands out a reference. Easy! If not, it retrieves the filename for that texture from Icefall&#8217;s <em>ResourceDatabase<\/em>, and attempts to load the texture from disk. If that fails, it looks at <strong>why<\/strong> it failed: if it&#8217;s <em>D3DERR_OUTOFVIDEOMEMORY<\/em>, the next step is to unload some other texture and try again, repeating until the load succeeds or we have no loaded textures left. (If it still doesn&#8217;t load, or if the texture load fails for some other reason, it&#8217;s goodbye Icefall).<\/p>\n<p>Programmatically:<\/p>\n<blockquote>\n<pre>\r\nfunction TContentManager.GetTexture(ID: TTextureID): TLXTexture;\r\nvar\r\n   HRes: HRESULT;\r\n   NewTexture: TLXTexture;\r\n   FileName: String;\r\nbegin\r\n   if TextureLoaded(ID) then\r\n      exit(Texture[ID]);\r\n\r\n   FileName := ResourceManager.Textures[ID].FileName;\r\n   repeat\r\n      HRes := LXLoadTexture(FileName,NewTexture);\r\n      case HRes of\r\n         D3D_OK:\r\n            Texture[ID] := NewTexture;\r\n         D3DERR_OUTOFVIDEOMEMORY:\r\n            UnloadTexture(0);\r\n      else\r\n         \/\/ Fatal error!\r\n      end;\r\n   until TextureLoaded(ID);\r\n   result := Texture[ID];\r\nend function;\r\n<\/pre>\n<\/blockquote>\n<p><em>UnloadTexture<\/em> is a method that unloads a texture. It takes a <em>TTextureID<\/em> as a parameter, but passing 0 in this case tells the method we want it to choose a texture to unload itself. This is where it gets interesting.<\/p>\n<p>Before I got to this, I originally had set up a few states so that they explicitly called <em>TContentManager.UnloadTexture<\/em> to release textures when the game left that state (e.g. the game would unload the &#8216;Option-selection&#8217; texture when the player closed the Options dialog). However, this turned out to be sub-optimal for several reasons:<\/p>\n<ul>\n<li>Players might well leave and re-enter states (like Options) several times to accomplish whatever it is they&#8217;re trying to do.<\/li>\n<li>It doesn&#8217;t make use of extra video memory: it keeps loading from disk (or the disk-cache anyway) while the extra video memory stays idle.<\/li>\n<li>I had to explicitly declare what textures I was done with. Not a problem really but &#8216;just another thing&#8217; that I had to do when changing states.<\/li>\n<\/ul>\n<p>So now I no longer explicitly release anything. <em>UnloadTexture<\/em> always chooses the texture to unload that was least-recently used (a linked list makes this a very fast and efficient check &#8211; no timers or array scanning involved). In practise &#8220;least recently used&#8221; turns out to be about 97% optimal (when simulated against a 100% optimal algorithm which is permitted to see the future when deciding what to unload) so it&#8217;s virtually perfect &#8211; substantially better than my explicit declarations were. <\/p>\n<p>If for some crazy reason the user doesn&#8217;t want Icefall consuming all of their video RAM*, I can chuck in a call to <em>IDirect3DDevice9.GetAvailableTextureMem<\/em> and trigger <em>UnloadTexture<\/em> if it&#8217;s below some specific amount. Alternatively, I could keep a sum of the amount of texture memory I&#8217;m using, and trigger <em>UnloadTexture<\/em> if that amount threatens to exceed 32MB or whatever (I haven&#8217;t decided yet).<\/p>\n<p>The point is, if you find yourself manually balancing resources, it&#8217;s probably an excellent idea to profile your resource usage and see if you can find a pattern that will let you just automate the whole thing**. You&#8217;ll save yourself much time and your code will be cleaner and more flexible.<\/p>\n<p>*Note: this only actually matters on Vista or Windows 7. Under the Windows XP model, multiple applications can&#8217;t share texture memory anyway. As soon as an application takes focus, DirectX invalidates all texture resources belonging to everything else (forcing them to reload from system-memory or disk when they get restored). The WDDM (Windows Display Driver Model) in Vista &#8220;understands&#8221; video memory, and can share it amongst applications. This is one of the reasons DirectX 10+ can&#8217;t be backported to XP: XP has no concept of video card resources.<\/p>\n<p>**If you&#8217;re creating a game frame-rate locked or just frame-rate dependent, you might still need to explicitly acquire and release resources (e.g. between levels), because a 10ms pause at an unexpected point in your game could be noticeable or detrimental. Either that, or move the resource acquistion to another thread and have low-quality &#8217;emergency&#8217; textures to display while the real ones load&#8230; this is a very common technique for FPS games. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Icefall uses literally dozens of textures&#8230; (and it will probably be &#8216;hundreds&#8217; by the time the game is completed). Textures are used for everything, from the mouse cursor, menus, fonts, buttons, to the game world itself, spells\/action icons, monsters, equipment&#8230; everything. Although my own main PC has 512MB of video (texture) RAM, not everyone does!<\/p>\n","protected":false},"author":2722,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6,18,3],"tags":[77,13,4,76],"class_list":["post-155","post","type-post","status-publish","format-standard","hentry","category-code","category-good-coding-guidelines","category-icefall","tag-code","tag-directx","tag-freepascal","tag-icefall"],"_links":{"self":[{"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/posts\/155","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/users\/2722"}],"replies":[{"embeddable":true,"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/comments?post=155"}],"version-history":[{"count":10,"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/posts\/155\/revisions"}],"predecessor-version":[{"id":161,"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/posts\/155\/revisions\/161"}],"wp:attachment":[{"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/media?parent=155"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/categories?post=155"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.paradicesoftware.com\/blog\/wp-json\/wp\/v2\/tags?post=155"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}