{"id":275,"date":"2013-09-25T23:51:22","date_gmt":"2013-09-26T06:51:22","guid":{"rendered":"http:\/\/www.teamfizzgames.com\/Matt\/?p=275"},"modified":"2013-09-26T02:18:21","modified_gmt":"2013-09-26T09:18:21","slug":"new-year-old-problems","status":"publish","type":"post","link":"https:\/\/www.teamfizzgames.com\/Matt\/new-year-old-problems\/","title":{"rendered":"New Year, Old Problems"},"content":{"rendered":"<p>It&#8217;s a new school year, and my senior year, so of course I decided that the most reasonable course of action was to put together a new team to build a new game in a new custom engine. \u00a0And I&#8217;d heavily rebuild my rendering system, too! \u00a0I&#8217;m smart like that. \u00a0And with new work come new posts, so aren&#8217;t you all just so lucky? \u00a0Also, this post should have been made last Thursday as I&#8217;ve made pretty major strides since the information I&#8217;m about to talk about, but&#8230; school.<\/p>\n<p>At the end of last year I was fairly happy with what I had accomplished, but there were also a lot of things I wanted to add and\/or fix that I just never found the time for. \u00a0Story of everyone&#8217;s life. \u00a0My initial work has targeted two major refactor issues that are unsurprisingly intertwined: making the renderer a standalone library and multithreading.<\/p>\n<p>Two years ago, I saw Reggie Meisler give a talk to Game Engine Architecture Club about a basic render-thread system in DX9 (sorry, I don&#8217;t have a link to the slides offhand) and it got me started thinking. \u00a0Then last year at GDC, I saw a great talk by Bryan Dudash\u00a0about utilizing <a href=\"https:\/\/developer.nvidia.com\/sites\/default\/files\/akamai\/gamedev\/docs\/GDC_2013_DUDASH_DeferredContexts.pdf\" target=\"_blank\">deferred contexts in DX11<\/a> and the gears really started turning. \u00a0I quickly realized that I could combine the knowledge from those two talks into my existing framework and get a lot of benefit, but I also knew that the messy interface (or lack thereof really) into my renderer prevented me from realistically ensuring thread-safety. \u00a0And that&#8217;s why that never happened last year.<\/p>\n<p>The general concept can be illustrated in this amazing diagram I drew in MS Paint.<\/p>\n<p><a href=\"http:\/\/www.teamfizzgames.com\/Matt\/wp-content\/uploads\/2013\/09\/MTRenderingDiagram.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-277\" alt=\"MTRenderingDiagram\" src=\"http:\/\/www.teamfizzgames.com\/Matt\/wp-content\/uploads\/2013\/09\/MTRenderingDiagram-1024x576.png\" width=\"800\" height=\"450\" srcset=\"https:\/\/www.teamfizzgames.com\/Matt\/wp-content\/uploads\/2013\/09\/MTRenderingDiagram-1024x576.png 1024w, https:\/\/www.teamfizzgames.com\/Matt\/wp-content\/uploads\/2013\/09\/MTRenderingDiagram-300x168.png 300w, https:\/\/www.teamfizzgames.com\/Matt\/wp-content\/uploads\/2013\/09\/MTRenderingDiagram-250x140.png 250w, https:\/\/www.teamfizzgames.com\/Matt\/wp-content\/uploads\/2013\/09\/MTRenderingDiagram-150x84.png 150w, https:\/\/www.teamfizzgames.com\/Matt\/wp-content\/uploads\/2013\/09\/MTRenderingDiagram.png 1152w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><\/a><\/p>\n<p>There&#8217;s two layers of parallelism in my current system, with the ability to add more later as time and performance dictate. \u00a0The first layer is to put the actual rendering on a separate thread that constantly loops, consuming the last command list buffer sent to it. \u00a0That was the basis of Reggie&#8217;s talk and has been a common technique for a while. \u00a0The second layer is to utilize deferred contexts to build the command lists for each pass process in parallel, and is the basic implementation discussed in\u00a0Bryan Dudash&#8217;s GDC presentation. \u00a0Of course, currently I&#8217;m only utilizing those two layers to draw a triangle into a render target and then composite that render target into the final presentation buffer, which is super impressive and all, but it provides a successful proof of concept that I can move forward from.<\/p>\n<div style=\"width: 673px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/www.anguishgames.com\/download\/images\/GoodGraphics26.png\"><img loading=\"lazy\" decoding=\"async\" class=\"  \" alt=\"\" src=\"http:\/\/www.anguishgames.com\/download\/images\/GoodGraphics26.png\" width=\"663\" height=\"387\" \/><\/a><p class=\"wp-caption-text\">GoodGraphics26.png, a triangle is reborn!<\/p><\/div>\n<p>It turns out that DX11 does a pretty great job of keeping itself thread-safe as long as you don&#8217;t try to utilize a single context over two threads, so the major task in getting this working was in keeping my own data thread-safe. \u00a0This necessitated three things: a simple public interface into the renderer, a transfer system for the command list buffers, and a transfer system for entities and assets. \u00a0The public interface was simple once I refactored and reorganized my classes, and now allows the renderer to run as a standalone library, fulfilling one of my major initial desires with this refactor.<\/p>\n<p>The command list buffer is a read\/write\/pivot transfer system where the gameloop side copies to the write buffer, the renderloop side copies from the read buffer, and any copy causes a swap between the target buffer and the pivot. \u00a0This does introduce a lock into the system, but I used a <a href=\"http:\/\/preshing.com\/20120226\/roll-your-own-lightweight-mutex\/\" target=\"_blank\">Benaphore<\/a> to keep it as lightweight as possible.  Here&#8217;s the code:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\/*\r\nProject: Graphics\r\nPurpose: Declaration of the command list buffer container\r\nCoder\/s: Matt Sutherlin (matt.sutherlin@digipen.edu)\r\nCopyright: \u201cAll content \u00a9 2013 DigiPen (USA) Corporation, all rights reserved.\u201d\r\n*\/\r\n\r\n#pragma once\r\n\r\n#include &quot;..\/Definitions\/ProcessEnums.hpp&quot;\r\n#include &quot;..\/Definitions\/DirectXIncludes.hpp&quot;\r\n#include &quot;..\/Renderer\/Benaphore.hpp&quot;\r\n\r\nstruct CommandListLayer\r\n{\r\n  CommandListLayer() {\r\n    m_bIsDirty = false;\r\n\r\n    for (unsigned lI = 0; lI &lt; Passes::NumberOf; ++lI)\r\n    {\r\n      m_commandLists&#x5B;lI] = nullptr;\r\n    }\r\n  }\r\n  ~CommandListLayer() {\r\n    for (unsigned lI = 0; lI &lt; Passes::NumberOf; ++lI)\r\n    {\r\n      SAFE_RELEASE(m_commandLists&#x5B;lI]);\r\n    }\r\n  }\r\n\r\n  bool                m_bIsDirty;\r\n  ID3D11CommandList*  m_commandLists&#x5B;Passes::NumberOf];\r\n};\r\n\r\nclass CommandListBuffer\r\n{\r\npublic:\r\n  CommandListBuffer() {\r\n    for (unsigned lI = 0; lI &lt; CommandListLayers::NumberOf; ++lI)\r\n    {\r\n      m_layers&#x5B;lI] = new CommandListLayer();\r\n    }\r\n  }\r\n  ~CommandListBuffer() {\r\n    for (unsigned lI = 0; lI &lt; CommandListLayers::NumberOf; ++lI)\r\n    {\r\n      delete m_layers&#x5B;lI];\r\n    }\r\n  }\r\n  ID3D11CommandList** GetLayerCommandLists(CommandListLayers::Layer pLayer) {\r\n    if (pLayer == CommandListLayers::Read)\r\n    {\r\n      m_lock.Lock();\r\n\r\n      if (m_layers&#x5B;CommandListLayers::Pivot]-&gt;m_bIsDirty)\r\n      {\r\n        m_layers&#x5B;CommandListLayers::Read]-&gt;m_bIsDirty = false;\r\n\r\n        std::swap(m_layers&#x5B;CommandListLayers::Read], m_layers&#x5B;CommandListLayers::Pivot]);\r\n      }\r\n\r\n      m_lock.Unlock();\r\n    }\r\n\r\n    return m_layers&#x5B;pLayer]-&gt;m_commandLists;\r\n  }\r\n  void SwapWriteLayer() {\r\n    m_lock.Lock();\r\n\r\n    m_layers&#x5B;CommandListLayers::Write]-&gt;m_bIsDirty = true;\r\n\r\n    std::swap(m_layers&#x5B;CommandListLayers::Write], m_layers&#x5B;CommandListLayers::Pivot]);\r\n\r\n    m_lock.Unlock();\r\n  }\r\nprivate:\r\n  CommandListLayer*   m_layers&#x5B;CommandListLayers::NumberOf];\r\n  Benaphore           m_lock;\r\n};<\/pre>\n<p>The entity\/asset system was by far the most complicated to implement, but it came down to a single container class that enqueued data changes (add, update, or remove) until a synchronization was called. \u00a0Here is the code:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\/*\r\nProject: Graphics\r\nPurpose: Declaration of the transfer buffer container\r\nCoder\/s: Matt Sutherlin (matt.sutherlin@digipen.edu)\r\nCopyright: \u201cAll content \u00a9 2013 DigiPen (USA) Corporation, all rights reserved.\u201d\r\n*\/\r\n\r\n#pragma once\r\n\r\n#include &lt;unordered_map&gt;\r\n#include &lt;queue&gt;\r\n#include &lt;type_traits&gt;\r\n\r\n\/\/Q01:  How does this work?\r\n\/\/A01:  The game engine could potentially make requests for adding, removing, or\r\n\/\/      deleting resources at any time.  While the ID3D11Device is thread-free, we \r\n\/\/      still need to be careful with resource management.\r\n\/\/\r\n\/\/      Adds can create their new resources immediately (and need to do so to return \r\n\/\/      a resource ID to the caller), but should not be added to traversal lists \r\n\/\/      while process threads are running.  So we defer that until the next game loop.\r\n\/\/\r\n\/\/      Updates should only occur at the start of a new game loop, so they're deferred \r\n\/\/      until the game thread calls for a synch.  We need to ensure the data we're \r\n\/\/      traversing doesn't get changed out from under us, so we synch this before \r\n\/\/      producer threads start for the frame.\r\n\/\/\r\n\/\/      Deletes have two levels of synchronization to deal with.  Removing the resource \r\n\/\/      from traversal lists needs to happen at the next game loop and removing it \r\n\/\/      from memory needs to happen at the next render loop after that.\r\n\/\/\r\n\/\/Q02:  What are the limitations?\r\n\/\/A02:  t_entry objects need to have an UpdateData function, and that function needs to \r\n\/\/      take a t_data object.\r\n\/\/\r\n\/\/      t_id objects need to be able to be initialized by setting = 0, and need to \r\n\/\/      properly increment when post-incremented.\r\n\/\/\r\n\/\/      t_entry and t_data objects MUST be pointer types.\r\n\r\ntemplate &lt;typename t_entry, typename t_data, typename t_id&gt;\r\nclass TransferBuffer\r\n{\r\n  typedef std::pair&lt;t_id, t_entry&gt;                              t_entryPair;\r\n  typedef std::pair&lt;t_id, t_data&gt;                               t_dataPair;\r\n  typedef typename std::unordered_map&lt;t_id, t_entry&gt;::iterator  t_iterator;\r\nprivate:\r\n  std::unordered_map&lt;t_id, t_entry&gt;   m_entries;\r\n  std::queue&lt;t_entryPair&gt;             m_pendingAdditions;\r\n  std::queue&lt;t_id&gt;                    m_markedDeletions;\r\n  std::queue&lt;t_entry&gt;                 m_pendingDeletions;\r\n  std::queue&lt;t_dataPair&gt;              m_pendingUpdates;\r\n  t_id                                m_nextID;\r\npublic:\r\n  TransferBuffer() {\r\n    m_nextID = 0;\r\n  }\r\n\r\n  ~TransferBuffer() {\r\n\r\n  }\r\n\r\n  \/\/This should only ever be called by the game engine!\r\n  t_id AddEntry(t_data pData) {\r\n    t_id lReturnID = m_nextID++;\r\n\r\n    t_entry lEntry = new std::remove_pointer&lt;t_entry&gt;::type(pData);\r\n    m_pendingAdditions.push(t_entryPair(lReturnID, lEntry));\r\n\r\n    return lReturnID;\r\n  }\r\n\r\n  \/\/This should only ever be called by the game engine!\r\n  void RemoveEntry(t_id pID) {\r\n    m_markedDeletions.push(pID);\r\n  }\r\n\r\n  \/\/This should only ever be called by the game engine!\r\n  void UpdateEntry(t_id pID, t_data pData) {\r\n    t_data lData = new std::remove_pointer&lt;t_data&gt;::type();\r\n    memcpy(lData, pData, sizeof(std::remove_pointer&lt;t_data&gt;::type));\r\n\r\n    m_pendingUpdates.push(t_dataPair(pID, lData));\r\n  }\r\n\r\n  \/\/This should only ever be called by parallel producer threads!\r\n  t_iterator GetEntries() {\r\n    return m_entries.begin();\r\n  }\r\n\r\n  t_iterator GetEnd() {\r\n    return m_entries.end();\r\n  }\r\n\r\n  \/\/This should only ever be called by the synchronous game thread!\r\n  \/\/Should get called once per game loop before threading deferred contexts\r\n  void SynchAdd() {\r\n    unsigned lSizeAdditions = m_pendingAdditions.size();\r\n\r\n    for (unsigned lI = 0; lI &lt; lSizeAdditions; ++lI)\r\n    {\r\n      t_entryPair lEntryPair = m_pendingAdditions.front();\r\n      m_pendingAdditions.pop();\r\n      m_entries.emplace(lEntryPair.first, lEntryPair.second);\r\n    }\r\n  }\r\n\r\n  \/\/This should only ever be called by the synchronous game thread!\r\n  \/\/Should get called directly after SynchAdd\r\n  void SynchUpdate() {\r\n    unsigned lSizeUpdates = m_pendingUpdates.size();\r\n\r\n    for (unsigned lI = 0; lI &lt; lSizeUpdates; ++lI)\r\n    {\r\n      t_dataPair lDataPair = m_pendingUpdates.front();\r\n      m_pendingUpdates.pop();\r\n\r\n      auto lIter = m_entries.find(lDataPair.first);\r\n\r\n      if (lIter != m_entries.end())\r\n      {\r\n        lIter-&gt;second-&gt;UpdateData(lDataPair.second);\r\n        delete lDataPair.second;\r\n      }\r\n    }\r\n  }\r\n\r\n  \/\/This should only ever be called by synchronous game thread!\r\n  \/\/Should get called directly after SynchUpdate\r\n  void SynchMarkedDelete() {\r\n    unsigned lSizeDeletions = m_markedDeletions.size();\r\n\r\n    for (unsigned lI = 0; lI &lt; lSizeDeletions; ++lI)\r\n    {\r\n      t_id lID = m_markedDeletions.front();\r\n      m_markedDeletions.pop();\r\n\r\n      auto lIter = m_entries.find(lID);\r\n\r\n      if (lIter != m_entries.end())\r\n      {\r\n        t_entry lEntry = lIter-&gt;second;\r\n        m_pendingDeletions.push(lEntry);\r\n        m_entries.erase(lIter);\r\n      }\r\n    }\r\n  }\r\n\r\n  \/\/This should only ever be called by the parallel consumer thread!\r\n  \/\/Should only get called at the start of a render loop\r\n  void SynchPendingDelete() {\r\n    unsigned lSizeDeletions = m_pendingDeletions.size();\r\n\r\n    for (unsigned lI = 0; lI &lt; lSizeDeletions; ++lI)\r\n    {\r\n      t_entry lEntry = m_pendingDeletions.front();\r\n      m_pendingDeletions.pop();\r\n      delete lEntry;\r\n    }\r\n  }\r\n};<\/pre>\n<p>And that&#8217;s how I solved my big thread-safety issue.  The FAQ at the top of the file is worth reading, and while I&#8217;m preplanning for t_id to be some kind of GUID functor, I&#8217;m just using unsigned int for that type in all cases right now.  I welcome any questions, comments, or criticisms of my methodology, but try not to be too hard on me as this is the first time I&#8217;ve actually posted code I&#8217;ve written.<\/p>\n<p>But that&#8217;s all for now.  Like I said, I&#8217;ve actually managed to take the system much further in the last week, but I&#8217;m just swamped right now. \u00a0Hopefully I&#8217;ll have time to make the next post before that information is also out of date, but no promises.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It&#8217;s a new school year, and my senior year, so of course I decided that the most reasonable course of action was to put together a new team to build a new game in a new custom engine. \u00a0And I&#8217;d heavily rebuild my rendering system, too! \u00a0I&#8217;m smart like that. \u2026 <a class=\"continue-reading-link\" href=\"https:\/\/www.teamfizzgames.com\/Matt\/new-year-old-problems\/\"> Continue reading <span class=\"meta-nav\">&rarr; <\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2}},"categories":[2],"tags":[18],"class_list":["post-275","post","type-post","status-publish","format-standard","hentry","category-graphicsdev","tag-threading"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p39ImV-4r","_links":{"self":[{"href":"https:\/\/www.teamfizzgames.com\/Matt\/wp-json\/wp\/v2\/posts\/275","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.teamfizzgames.com\/Matt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.teamfizzgames.com\/Matt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.teamfizzgames.com\/Matt\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.teamfizzgames.com\/Matt\/wp-json\/wp\/v2\/comments?post=275"}],"version-history":[{"count":15,"href":"https:\/\/www.teamfizzgames.com\/Matt\/wp-json\/wp\/v2\/posts\/275\/revisions"}],"predecessor-version":[{"id":298,"href":"https:\/\/www.teamfizzgames.com\/Matt\/wp-json\/wp\/v2\/posts\/275\/revisions\/298"}],"wp:attachment":[{"href":"https:\/\/www.teamfizzgames.com\/Matt\/wp-json\/wp\/v2\/media?parent=275"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.teamfizzgames.com\/Matt\/wp-json\/wp\/v2\/categories?post=275"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.teamfizzgames.com\/Matt\/wp-json\/wp\/v2\/tags?post=275"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}