diff --git a/OpenRCT2.xcodeproj/project.pbxproj b/OpenRCT2.xcodeproj/project.pbxproj index 2cfabc2da8..97c67e30f1 100644 --- a/OpenRCT2.xcodeproj/project.pbxproj +++ b/OpenRCT2.xcodeproj/project.pbxproj @@ -266,6 +266,7 @@ D45E09171F99CF2F00854B2B /* ApplyTransparencyShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D45E09161F99CF2F00854B2B /* ApplyTransparencyShader.cpp */; }; D47304D51C4FF8250015C0EA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D47304D41C4FF8250015C0EA /* libz.tbd */; }; D48AFDB71EF78DBF0081C644 /* BenchGfxCommmands.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D48AFDB61EF78DBF0081C644 /* BenchGfxCommmands.cpp */; }; + D4974F1C1FA04A1900F7FD7F /* TransparencyDepth.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D4974F1A1FA04A1900F7FD7F /* TransparencyDepth.cpp */; }; D4A8B4B41DB41873007A2F29 /* libpng16.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D4A8B4B31DB41873007A2F29 /* libpng16.dylib */; }; D4A8B4B51DB4188D007A2F29 /* libpng16.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D4A8B4B31DB41873007A2F29 /* libpng16.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; D4EC48E61C2637710024B507 /* g2.dat in Resources */ = {isa = PBXBuildFile; fileRef = D4EC48E31C2637710024B507 /* g2.dat */; }; @@ -1040,6 +1041,8 @@ D47304D41C4FF8250015C0EA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; D4895D321C23EFDD000CD788 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = distribution/macos/Info.plist; sourceTree = SOURCE_ROOT; }; D48AFDB61EF78DBF0081C644 /* BenchGfxCommmands.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BenchGfxCommmands.cpp; sourceTree = ""; }; + D4974F1A1FA04A1900F7FD7F /* TransparencyDepth.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TransparencyDepth.cpp; sourceTree = ""; }; + D4974F1B1FA04A1900F7FD7F /* TransparencyDepth.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TransparencyDepth.h; sourceTree = ""; }; D497D0781C20FD52002BF46A /* OpenRCT2.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OpenRCT2.app; sourceTree = BUILT_PRODUCTS_DIR; }; D4A8B4B31DB41873007A2F29 /* libpng16.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libpng16.dylib; sourceTree = ""; }; D4EC48E31C2637710024B507 /* g2.dat */ = {isa = PBXFileReference; lastKnownFileType = file; name = g2.dat; path = data/g2.dat; sourceTree = SOURCE_ROOT; }; @@ -2688,6 +2691,8 @@ F76C85A31EC4E82600FA49E2 /* SwapFramebuffer.h */, F76C85A41EC4E82600FA49E2 /* TextureCache.cpp */, F76C85A51EC4E82600FA49E2 /* TextureCache.h */, + D4974F1A1FA04A1900F7FD7F /* TransparencyDepth.cpp */, + D4974F1B1FA04A1900F7FD7F /* TransparencyDepth.h */, ); path = opengl; sourceTree = ""; @@ -3150,6 +3155,7 @@ 4CFE4E821F90A3F1005243C2 /* Staff.cpp in Sources */, F76C88791EC5324E00FA49E2 /* AudioContext.cpp in Sources */, 4C93F1571F8B744400A9330D /* WildMouse.cpp in Sources */, + D4974F1C1FA04A1900F7FD7F /* TransparencyDepth.cpp in Sources */, C666EE7A1F37ACB10061AA04 /* Themes.cpp in Sources */, 4C93F1781F8B745700A9330D /* SpaceRings.cpp in Sources */, 4CFE4E8A1F950164005243C2 /* TrackDataOld.cpp in Sources */, diff --git a/src/openrct2-ui/drawing/engines/opengl/DrawCommands.h b/src/openrct2-ui/drawing/engines/opengl/DrawCommands.h index 004a70d687..46f35ad69f 100644 --- a/src/openrct2-ui/drawing/engines/opengl/DrawCommands.h +++ b/src/openrct2-ui/drawing/engines/opengl/DrawCommands.h @@ -70,14 +70,31 @@ public: { return _instances.at(idx); } + typename std::vector::iterator begin() { return _instances.begin(); } + typename std::vector::const_iterator begin() const + { + return _instances.cbegin(); + } + typename std::vector::const_iterator cbegin() const + { + return _instances.cbegin(); + } typename std::vector::iterator end() { return _instances.begin() + _numInstances; } + typename std::vector::const_iterator end() const + { + return _instances.cbegin() + _numInstances; + } + typename std::vector::const_iterator cend() const + { + return _instances.cbegin() + _numInstances; + } }; struct DrawLineCommand diff --git a/src/openrct2-ui/drawing/engines/opengl/OpenGLDrawingEngine.cpp b/src/openrct2-ui/drawing/engines/opengl/OpenGLDrawingEngine.cpp index de60d2bbfc..d4327e30ac 100644 --- a/src/openrct2-ui/drawing/engines/opengl/OpenGLDrawingEngine.cpp +++ b/src/openrct2-ui/drawing/engines/opengl/OpenGLDrawingEngine.cpp @@ -16,10 +16,7 @@ #ifndef DISABLE_OPENGL -#include -#include #include -#include #include #include @@ -43,11 +40,12 @@ #include "OpenGLAPI.h" #include "OpenGLFramebuffer.h" #include "CopyFramebufferShader.h" +#include "DrawCommands.h" #include "DrawLineShader.h" #include "DrawRectShader.h" #include "SwapFramebuffer.h" #include "TextureCache.h" -#include "DrawCommands.h" +#include "TransparencyDepth.h" using namespace OpenRCT2; using namespace OpenRCT2::Drawing; @@ -122,7 +120,6 @@ public: void HandleTransparency(); void SetDPI(rct_drawpixelinfo * dpi); - sint32 MaxTransparencyDepth(); }; class OpenGLDrawingEngine : public IDrawingEngine @@ -882,7 +879,7 @@ void OpenGLDrawingContext::HandleTransparency() _drawRectShader->Use(); _drawRectShader->SetInstances(_commandBuffers.transparent); - sint32 max_depth = MaxTransparencyDepth(); + sint32 max_depth = MaxTransparencyDepth(_commandBuffers.transparent); for (sint32 i=0; i < max_depth; ++i) { _swapFramebuffer->BindTransparent(); @@ -928,141 +925,4 @@ void OpenGLDrawingContext::SetDPI(rct_drawpixelinfo * dpi) _dpi = dpi; } -sint32 OpenGLDrawingContext::MaxTransparencyDepth() -{ - sint32 max_depth = 1; - - struct xdata - { - sint32 xposition; - bool begin; - sint32 top, bottom; - }; - std::vector x_sweep; - x_sweep.reserve(_commandBuffers.transparent.size() * 2); - for (DrawRectCommand &command : _commandBuffers.transparent) - { - sint32 left = std::min(std::max(command.bounds.x, command.clip.x), command.clip.z); - sint32 top = std::min(std::max(command.bounds.y, command.clip.y), command.clip.w); - sint32 right = std::min(std::max(command.bounds.z, command.clip.x), command.clip.z); - sint32 bottom = std::min(std::max(command.bounds.w, command.clip.y), command.clip.w); - - assert(left <= right); - assert(top <= bottom); - if (left == right) continue; - if (top == bottom) continue; - - x_sweep.push_back({left, true, top, bottom}); - x_sweep.push_back({right, false, top, bottom}); - } - std::sort(x_sweep.begin(), x_sweep.end(), [](const xdata &a, const xdata &b) -> bool { - if (a.xposition != b.xposition) return a.xposition < b.xposition; - else return !a.begin && b.begin; - }); - - struct ydata - { - sint32 count, depth; - }; - std::map y_intersect; - for (const xdata &x : x_sweep) - { - assert(y_intersect.size() == 0 || y_intersect.begin()->second.depth == 0); - if (x.begin) - { - auto top_in = y_intersect.insert({x.top, {1, 0}}); - auto top_it = top_in.first; - if (top_in.second) - { - auto top_next = std::next(top_it); - if (top_next != y_intersect.end()) - { - top_it->second.depth = top_next->second.depth; - } - } - else - { - assert(top_it->second.count > 0); - ++top_it->second.count; - } - - auto bottom_in = y_intersect.insert({x.bottom, {1, 1}}); - auto bottom_it = bottom_in.first; - if (bottom_in.second) - { - auto bottom_next = std::next(bottom_it); - if (bottom_next != y_intersect.end()) - { - bottom_it->second.depth = bottom_next->second.depth + 1; - } - } - else - { - assert(bottom_it->second.count > 0); - ++bottom_it->second.count; - max_depth = std::max(max_depth, ++bottom_it->second.depth); - } - - for (auto it = std::next(top_it); it != bottom_it; ++it) - { - max_depth = std::max(max_depth, ++it->second.depth); - } - } - else - { - auto top_it = y_intersect.find(x.top); - assert(top_it != y_intersect.end()); - assert(top_it->second.count > 0); - auto bottom_it = y_intersect.find(x.bottom); - assert(bottom_it != y_intersect.end()); - assert(bottom_it->second.count > 0); - -#ifndef NDEBUG - if (top_it->second.count == 1) - { - auto top_next = std::next(top_it); - assert(top_next != y_intersect.end() && - top_it->second.depth == top_next->second.depth - 1); - } - - if (bottom_it->second.count == 1) - { - auto bottom_next = std::next(bottom_it); - assert(bottom_next == y_intersect.end() ? - bottom_it->second.depth == 1 : - bottom_it->second.depth == bottom_next->second.depth + 1); - } -#endif /* NDEBUG */ - - for (auto it = std::next(top_it); it != bottom_it; ++it) - { - assert(it->second.depth > 0); - --it->second.depth; - } - - if (top_it->second.count == 1) - { - y_intersect.erase(top_it); - } - else - { - --top_it->second.count; - } - - if (bottom_it->second.count == 1) - { - y_intersect.erase(bottom_it); - } - else - { - assert(bottom_it->second.depth > 0); - --bottom_it->second.count; - --bottom_it->second.depth; - } - } - } - - return max_depth; -} - #endif /* DISABLE_OPENGL */ diff --git a/src/openrct2-ui/drawing/engines/opengl/TransparencyDepth.cpp b/src/openrct2-ui/drawing/engines/opengl/TransparencyDepth.cpp new file mode 100644 index 0000000000..3a16324a5f --- /dev/null +++ b/src/openrct2-ui/drawing/engines/opengl/TransparencyDepth.cpp @@ -0,0 +1,211 @@ +#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#ifndef DISABLE_OPENGL + +#include "TransparencyDepth.h" + +#include +#include +#include + +/* + * Structure to store locations of vertical bounding box edge. + */ +struct XData +{ + sint32 xposition; + bool begin; + sint32 top, bottom; +}; +typedef std::vector SweepLine; + +/* + * Creates a list of vertical bounding box edges, stored as xdata and sorted + * from left to right. If multiple edges are at the same x coordinate, Then + * edges for boxes to the left will appear before edges for boxes to the right. + */ +static inline SweepLine CreateXList(const RectCommandBatch &transparent) +{ + SweepLine x_sweep; + x_sweep.reserve(transparent.size() * 2); + + for (const DrawRectCommand &command : transparent) + { + sint32 left = std::min(std::max(command.bounds.x, command.clip.x), command.clip.z); + sint32 top = std::min(std::max(command.bounds.y, command.clip.y), command.clip.w); + sint32 right = std::min(std::max(command.bounds.z, command.clip.x), command.clip.z); + sint32 bottom = std::min(std::max(command.bounds.w, command.clip.y), command.clip.w); + + assert(left <= right); + assert(top <= bottom); + if (left == right) continue; + if (top == bottom) continue; + + x_sweep.push_back({left, true, top, bottom}); + x_sweep.push_back({right, false, top, bottom}); + } + + std::sort(x_sweep.begin(), x_sweep.end(), [](const XData &a, const XData &b) -> bool { + if (a.xposition != b.xposition) return a.xposition < b.xposition; + else return !a.begin && b.begin; + }); + + return x_sweep; +} + +/* + * Structure that stores intervals. YData.count stores how many intervals have + * an endpoint at this position, and YData.depth stores how many intervals + * intersect on the left limit of this point. In other words, IntervalTree + * stores half-closed intervals, with the left endpoint open, and the right + * endpoint closed. The IntervalTree uses std::map because it stores the values + * sorted, and we can traverse it in order using its bidirectional iterators. + */ +struct YData +{ + sint32 count, depth; +}; +typedef std::map IntervalTree; + +/* + * Inserts the interval's top endpoint into the interval tree. If the endpoint + * already exists in the interval tree, it stacks the endpoints. + */ +static inline IntervalTree::iterator InsertTopEndpoint(IntervalTree &y_intersect, sint32 top) +{ + auto top_in = y_intersect.insert({top, {1, 0}}); + IntervalTree::iterator top_it = top_in.first; + if (top_in.second) + { + IntervalTree::iterator top_next = std::next(top_it); + if (top_next != y_intersect.end()) + { + top_it->second.depth = top_next->second.depth; + } + } + else + { + ++top_it->second.count; + } + return top_it; +} + +/* + * Inserts the interval's bottom endpoint into the interval tree. If the + * endpoint already exists in the interval tree, it stacks the endpoint. + * This function can produce a new maximum depth. + */ +static inline IntervalTree::iterator InsertBottomEndpoint(IntervalTree &y_intersect, sint32 bottom) +{ + auto bottom_in = y_intersect.insert({bottom, {1, 1}}); + IntervalTree::iterator bottom_it = bottom_in.first; + if (bottom_in.second) + { + IntervalTree::iterator bottom_next = std::next(bottom_it); + if (bottom_next != y_intersect.end()) + { + bottom_it->second.depth = bottom_next->second.depth + 1; + } + } + else + { + ++bottom_it->second.count; + ++bottom_it->second.depth; + } + return bottom_it; +} + +/* + * Removes the interval's top endpoint, handling stacked endpoints. + */ +static inline void RemoveTopEndpoint(IntervalTree &y_intersect, IntervalTree::iterator top_it) +{ + if (top_it->second.count == 1) + { + y_intersect.erase(top_it); + } + else + { + --top_it->second.count; + } +} + +/* + * Removes the interval's bottom endpoint, handling stacked endpoints. + */ +static inline void RemoveBottomEndpoint(IntervalTree &y_intersect, IntervalTree::iterator bottom_it) +{ + if (bottom_it->second.count == 1) + { + y_intersect.erase(bottom_it); + } + else + { + --bottom_it->second.count; + --bottom_it->second.depth; + } +} + +/* + * Determines an aproximation of the number of depth peeling iterations needed + * to render the command batch. It will never underestimate the number of + * iterations, but it can overestimate, usually by no more than +2. + */ +sint32 MaxTransparencyDepth(const RectCommandBatch &transparent) +{ + sint32 max_depth = 1; + SweepLine x_sweep = CreateXList(transparent); + IntervalTree y_intersect{}; + + for (const XData &x : x_sweep) + { + if (x.begin) + { + IntervalTree::iterator top_it = InsertTopEndpoint(y_intersect, x.top); + IntervalTree::iterator bottom_it = InsertBottomEndpoint(y_intersect, x.bottom); + max_depth = std::max(max_depth, bottom_it->second.depth); + + /* + * Increment the depth for endpoings that intersect this interval + */ + for (IntervalTree::iterator it = std::next(top_it); it != bottom_it; ++it) + { + max_depth = std::max(max_depth, ++it->second.depth); + } + } + else + { + IntervalTree::iterator top_it = y_intersect.find(x.top); + IntervalTree::iterator bottom_it = y_intersect.find(x.bottom); + + /* + * Decrement the depth for endpoings that intersected this interval + */ + for (IntervalTree::iterator it = std::next(top_it); it != bottom_it; ++it) + { + --it->second.depth; + } + + RemoveTopEndpoint(y_intersect, top_it); + RemoveBottomEndpoint(y_intersect, bottom_it); + } + } + + return max_depth; +} + +#endif /* DISABLE_OPENGL */ diff --git a/src/openrct2-ui/drawing/engines/opengl/TransparencyDepth.h b/src/openrct2-ui/drawing/engines/opengl/TransparencyDepth.h new file mode 100644 index 0000000000..693c063942 --- /dev/null +++ b/src/openrct2-ui/drawing/engines/opengl/TransparencyDepth.h @@ -0,0 +1,27 @@ +#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include +#include "DrawCommands.h" + +/* + * Determines an aproximation of the number of depth peeling iterations needed + * to render the command batch. It will never underestimate the number of + * iterations, but it can overestimate, usually by no more than +2. + */ +sint32 MaxTransparencyDepth(const RectCommandBatch &transparent);