{"id":75,"date":"2013-08-23T17:23:29","date_gmt":"2013-08-23T17:23:29","guid":{"rendered":"https:\/\/blogs.scummvm.org\/t0by\/?p=75"},"modified":"2022-05-24T17:25:14","modified_gmt":"2022-05-24T17:25:14","slug":"multi-rects","status":"publish","type":"post","link":"https:\/\/blogs.scummvm.org\/t0by\/2013\/08\/23\/multi-rects\/","title":{"rendered":"Multi rects"},"content":{"rendered":"<p>In our last post we were talking about the dirty rect system.<br \/>\nThe simplest implementation uses a single rectangle; it can save us quite some unnecessary blitting already, but there is some dangers to be taken into<\/p>\n<p>account that could spoil our grand plan.<\/p>\n<p>For example, what happens if a character is moving in the SE corner of the screen and I\u2019m moving the mouse cursor in the NW corner?<\/p>\n<p><a href=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/08\/untitled-1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-77\" src=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/08\/untitled-1.png\" alt=\"\" width=\"787\" height=\"488\" srcset=\"https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/08\/untitled-1.png 787w, https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/08\/untitled-1-300x186.png 300w, https:\/\/blogs.scummvm.org\/t0by\/wp-content\/uploads\/sites\/43\/2013\/08\/untitled-1-768x476.png 768w\" sizes=\"auto, (max-width: 787px) 100vw, 787px\" \/><\/a>What would be the screen area to be considered \u201cdirty\u201d and thus redrawn?<\/p>\n<p>Yu guessed it \u2013 basically, the whole screen.<\/p>\n<p>Baaaaad.<\/p>\n<p>This was in fact the way dirty recting was originally implemented in the WME engine for ScummVM:<\/p>\n<pre>void BaseRenderOSystem::addDirtyRect(const Common::Rect &amp;rect) {\r\n  if (!_dirtyRect) {\r\n    _dirtyRect = new Common::Rect(rect);\r\n  } else {\r\n    _dirtyRect-&gt;extend(rect);\r\n  }\r\n  _dirtyRect-&gt;clip(_renderRect); \r\n}\r\n<\/pre>\n<p>That\u2019s it, one rect, extend it as needed.<\/p>\n<p>We needed multiple dirty rects.<\/p>\n<p>Whenever we \u201cdirty\u201d a screen area, we need to mark that separately.<br \/>\nBut a simple array is probably not enough.<\/p>\n<p>For example, what happens if multiple partially overlapping sprites are to be redrawn and we naively traverse the array?<\/p>\n<p>We redraw the same area over and over again; if the rects are big enough, we may end up doing *more* blitting than without dirty rects, *more* blitting than<\/p>\n<p>with a single rect.<\/p>\n<p>Also bad, very, very bad.<\/p>\n<p>Even if unnecessary redrawing is avoided, needlessly big rect lists can be a problem.<\/p>\n<p>I chose to address the issue with a special class that:<\/p>\n<p>a) Takes requests for adding rects\u2026<br \/>\na\u2019) \u2026while doing some on-the-fly-optimizations (e.g. discarding duplicates)<br \/>\nb) Can spit out, upon request, an optimized version with only the necessary rectangles.<\/p>\n<p>It is still in the works so this is not final code, but the basic idea is definitely there.<br \/>\nHave a look \u2013 code is partial and amended as usual, but you don\u2019t want to know about all the boring housekeeping and utility methods, do you?<\/p>\n<pre>namespace Wintermute {\r\n \r\n \r\nDirtyRectContainer::DirtyRectContainer() {\r\n    _disableDirtyRects = false;\r\n    _clipRect = nullptr;\r\n}\r\n \r\nDirtyRectContainer::~DirtyRectContainer() {\r\n    delete _clipRect;\r\n}\r\n \r\nvoid DirtyRectContainer::addDirtyRect(const Common::Rect &amp;rect, const Common::Rect *clipRect) {\r\n \r\n    if (_clipRect == nullptr) {\r\n        _clipRect = new Common::Rect(*clipRect);\r\n    } else {\r\n        assert(clipRect-&gt;equals(*_clipRect));\r\n    }\r\n \r\n    if (target &gt; kMaxInputRects) {\r\n        _disableDirtyRects = true;\r\n    } else if (isHuge(&amp;rect)) { \/\/ More than 90% of the viewport\r\n        _disableDirtyRects = true;\r\n    } else if (rect.width() == 0 || rect.height() == 0) {\r\n    } else {\r\n        _rectArray.insert_at(target, tmp);\r\n        _rectArray[target]-&gt;clip(*clipRect);\r\n    }\r\n}\r\n \r\n \r\nCommon::Array DirtyRectContainer::getOptimized() {\r\n \r\n    if (_disableDirtyRects) {\r\n        return getFallback();\r\n    }\r\n \r\n    Common::Array ret;\r\n    Common::Array queue;\r\n \r\n    for (int i = 0; i &lt; _rectArray.size(); i++) {\r\n        queue.insert_at(queue.size(), _rectArray[i]);\r\n    }\r\n \r\n    int j = 0;\r\n    while (j &lt; queue.size()) { Common::Rect *candidate = queue[j]; if (candidate-&gt;width() == 0 || candidate-&gt;height() == 0) {\r\n            \/\/ We have no use for this\r\n            queue.remove_at(j);\r\n            continue;\r\n        }\r\n \r\n        if (ret.size() &gt; kMaxOutputRects) {\r\n            _disableDirtyRects = true;\r\n            return getFallback();\r\n        }\r\n \r\n        bool discard = false;\r\n \r\n        \/\/ See if it's contained or containes\r\n        for (uint i = 0; i &lt; ret.size() &amp;&amp; !discard; i++) { Common::Rect *existing = ret[i]; if (existing-&gt;contains(*candidate) || existing-&gt;equals(*candidate)) {\r\n                \/\/ It is already contained by an existing rect - useless!\r\n                discard = true;\r\n                continue;\r\n            }\r\n \r\n            if (candidate-&gt;contains(*(existing))) {\r\n                \/\/ Contains an existing one.\r\n                \/\/ Extend the pre-existing one and discard this.\r\n                existing-&gt;extend(*candidate);\r\n                discard = true;\r\n                continue;\r\n            }\r\n \r\n            \/\/ Okay, we now see if we have an overlapping corner and slice accordingly.\r\n            Common::Rect intersecting = existing-&gt;findIntersectingRect(*candidate);\r\n            \/\/ We have to remove intersecting and enqueue the rest\r\n            \/\/ We know that it's not a simple contained rect, we know that\r\n            \/\/ it's not a cross-shaped thing like above, we know that it's not\r\n            \/\/ a Commodore-logo type of deal (where the C is the bigger rect)\r\n            \/\/ So intersecting is either the NE, SE, SW or NW corner of candidate\r\n            if (intersecting.width() &gt;= kMaxSplicingX &amp;&amp;\r\n                    intersecting.height() &gt;= kMaxSplicingY\r\n               ) {\r\n                if (intersecting.top &gt;= candidate-&gt;top &amp;&amp;\r\n                        intersecting.left &gt;= candidate-&gt;left &amp;&amp;\r\n                        intersecting.bottom == candidate-&gt;bottom &amp;&amp;\r\n                        intersecting.right == candidate-&gt;right\r\n                   ) { \/\/ SE case\r\n                    Common::Rect *neslice = new Common::Rect(*candidate);\r\n                    neslice-&gt;bottom = intersecting.top;\r\n                    neslice-&gt;left = intersecting.left;\r\n                    if (neslice-&gt;width() != 0 &amp;&amp; neslice-&gt;height() != 0) {\r\n                        queue.insert_at(queue.size(), neslice);\r\n                    }\r\n                    candidate-&gt;right = intersecting.left;\r\n                    if (candidate-&gt;width() == 0) {\r\n                        discard = true;\r\n                    }\r\n                     \r\n                } else if () \/\/SAME FOR NE \r\n                { \/\/ ...\r\n                } else if () \/\/SAME FOR NW\r\n                { \/\/ ...\r\n                } else if () \/\/SAME FOR SW \r\n                { \/\/ ...\r\n                } else if (existing-&gt;left &lt;= candidate-&gt;left &amp;&amp;\r\n                           existing-&gt;right &gt;= candidate-&gt;right &amp;&amp;\r\n                           existing-&gt;top &gt;= candidate-&gt;top &amp;&amp;\r\n                           existing-&gt;bottom &lt;= candidate-&gt;bottom) { \/\/ Cross shaped intersections\r\n                    Common::Rect *top_slice = new Common::Rect(*candidate);\r\n                    Common::Rect *bottom_slice = new Common::Rect(*candidate);\r\n                    top_slice-&gt;bottom = existing-&gt;top;\r\n                    bottom_slice-&gt;top = existing-&gt;bottom;\r\n \r\n                    if (top_slice-&gt;height() &gt; 0 &amp;&amp; top_slice-&gt;width() &gt; 0) {\r\n                        queue.insert_at(queue.size(), top_slice);\r\n                    }\r\n \r\n                    if (bottom_slice-&gt;height() &gt; 0 &amp;&amp; bottom_slice-&gt;width() &gt; 0) {\r\n                        queue.insert_at(queue.size(), bottom_slice);\r\n                    }\r\n \r\n                    discard = true;\r\n                    continue;\r\n                } else if () \/\/ Same for vertical vs horizontal cross intersection\r\n                {\r\n                }\r\n            } \/\/ End of intersecting test\r\n \r\n        } \/\/ End loop\r\n        if (discard) {\r\n            queue.remove_at(j);\r\n        } else {\r\n            if (isHuge(candidate)) {\r\n                _disableDirtyRects = true;\r\n                return getFallback();\r\n            }\r\n            ret.insert_at(ret.size(), candidate);\r\n            j++;\r\n        }\r\n    } \/\/ End loop\r\n \r\n    return ret;\r\n}\r\n \r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>In our last post we were talking about the dirty rect system. The simplest implementation uses a single rectangle; it can save us quite some unnecessary blitting already, but there is some dangers to be taken into account that could spoil our grand plan. For example, what happens if a character is moving in the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-75","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/posts\/75","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/comments?post=75"}],"version-history":[{"count":2,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/posts\/75\/revisions"}],"predecessor-version":[{"id":78,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/posts\/75\/revisions\/78"}],"wp:attachment":[{"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/media?parent=75"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/categories?post=75"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.scummvm.org\/t0by\/wp-json\/wp\/v2\/tags?post=75"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}