OpenTTD Source  13.2.1
viewport_sprite_sorter_sse4.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of OpenTTD.
3  * OpenTTD 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, version 2.
4  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6  */
7 
10 #ifdef WITH_SSE
11 
12 #include "stdafx.h"
13 #include "cpu.h"
14 #include "smmintrin.h"
15 #include "viewport_sprite_sorter.h"
16 #include <forward_list>
17 #include <map>
18 #include <stack>
19 
20 #include "safeguards.h"
21 
22 #ifdef POINTER_IS_64BIT
23  static_assert((sizeof(ParentSpriteToDraw) % 16) == 0);
24 # define LOAD_128 _mm_load_si128
25 #else
26 # define LOAD_128 _mm_loadu_si128
27 #endif
28 
29 GNU_TARGET("sse4.1")
30 void ViewportSortParentSpritesSSE41(ParentSpriteToSortVector *psdv)
31 {
32  if (psdv->size() < 2) return;
33 
34  const __m128i mask_ptest = _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0);
35 
36  /* We rely on sprites being, for the most part, already ordered.
37  * So we don't need to move many of them and can keep track of their
38  * order efficiently by using stack. We always move sprites to the front
39  * of the current position, i.e. to the top of the stack.
40  * Also use special constants to indicate sorting state without
41  * adding extra fields to ParentSpriteToDraw structure.
42  */
43  const uint32 ORDER_COMPARED = UINT32_MAX; // Sprite was compared but we still need to compare the ones preceding it
44  const uint32 ORDER_RETURNED = UINT32_MAX - 1; // Mark sorted sprite in case there are other occurrences of it in the stack
45  std::stack<ParentSpriteToDraw *> sprite_order;
46  uint32 next_order = 0;
47 
48  std::forward_list<std::pair<int64, ParentSpriteToDraw *>> sprite_list; // We store sprites in a list sorted by xmin+ymin
49 
50  /* Initialize sprite list and order. */
51  for (auto p = psdv->rbegin(); p != psdv->rend(); p++) {
52  sprite_list.push_front(std::make_pair((*p)->xmin + (*p)->ymin, *p));
53  sprite_order.push(*p);
54  (*p)->order = next_order++;
55  }
56 
57  sprite_list.sort();
58 
59  std::vector<ParentSpriteToDraw *> preceding; // Temporarily stores sprites that precede current and their position in the list
60  auto preceding_prev = sprite_list.begin(); // Store iterator in case we need to delete a single preciding sprite
61  auto out = psdv->begin(); // Iterator to output sorted sprites
62 
63  while (!sprite_order.empty()) {
64 
65  auto s = sprite_order.top();
66  sprite_order.pop();
67 
68  /* Sprite is already sorted, ignore it. */
69  if (s->order == ORDER_RETURNED) continue;
70 
71  /* Sprite was already compared, just need to output it. */
72  if (s->order == ORDER_COMPARED) {
73  *(out++) = s;
74  s->order = ORDER_RETURNED;
75  continue;
76  }
77 
78  preceding.clear();
79 
80  /* We only need sprites with xmin <= s->xmax && ymin <= s->ymax && zmin <= s->zmax
81  * So by iterating sprites with xmin + ymin <= s->xmax + s->ymax
82  * we get all we need and some more that we filter out later.
83  * We don't include zmin into the sum as there are usually more neighbors on x and y than z
84  * so including it will actually increase the amount of false positives.
85  * Also min coordinates can be > max so using max(xmin, xmax) + max(ymin, ymax)
86  * to ensure that we iterate the current sprite as we need to remove it from the list.
87  */
88  auto ssum = std::max(s->xmax, s->xmin) + std::max(s->ymax, s->ymin);
89  auto prev = sprite_list.before_begin();
90  auto x = sprite_list.begin();
91  while (x != sprite_list.end() && ((*x).first <= ssum)) {
92  auto p = (*x).second;
93  if (p == s) {
94  /* We found the current sprite, remove it and move on. */
95  x = sprite_list.erase_after(prev);
96  continue;
97  }
98 
99  auto p_prev = prev;
100  prev = x++;
101 
102  /* Check that p->xmin <= s->xmax && p->ymin <= s->ymax && p->zmin <= s->zmax */
103  __m128i s_max = LOAD_128((__m128i*) &s->xmax);
104  __m128i p_min = LOAD_128((__m128i*) &p->xmin);
105  __m128i r1 = _mm_cmplt_epi32(s_max, p_min);
106  if (!_mm_testz_si128(mask_ptest, r1))
107  continue;
108 
109  /* Check if sprites overlap, i.e.
110  * s->xmin <= p->xmax && s->ymin <= p->ymax && s->zmin <= p->zmax
111  */
112  __m128i s_min = LOAD_128((__m128i*) &s->xmin);
113  __m128i p_max = LOAD_128((__m128i*) &p->xmax);
114  __m128i r2 = _mm_cmplt_epi32(p_max, s_min);
115  if (_mm_testz_si128(mask_ptest, r2)) {
116  /* Use X+Y+Z as the sorting order, so sprites closer to the bottom of
117  * the screen and with higher Z elevation, are drawn in front.
118  * Here X,Y,Z are the coordinates of the "center of mass" of the sprite,
119  * i.e. X=(left+right)/2, etc.
120  * However, since we only care about order, don't actually divide / 2
121  */
122  if (s->xmin + s->xmax + s->ymin + s->ymax + s->zmin + s->zmax <=
123  p->xmin + p->xmax + p->ymin + p->ymax + p->zmin + p->zmax) {
124  continue;
125  }
126  }
127 
128  preceding.push_back(p);
129  preceding_prev = p_prev;
130  }
131 
132  if (preceding.empty()) {
133  /* No preceding sprites, add current one to the output */
134  *(out++) = s;
135  s->order = ORDER_RETURNED;
136  continue;
137  }
138 
139  /* Optimization for the case when we only have 1 sprite to move. */
140  if (preceding.size() == 1) {
141  auto p = preceding[0];
142  /* We can only output the preceding sprite if there can't be any other sprites preceding it. */
143  if (p->xmax <= s->xmax && p->ymax <= s->ymax && p->zmax <= s->zmax) {
144  p->order = ORDER_RETURNED;
145  s->order = ORDER_RETURNED;
146  sprite_list.erase_after(preceding_prev);
147  *(out++) = p;
148  *(out++) = s;
149  continue;
150  }
151  }
152 
153  /* Sort all preceding sprites by order and assign new orders in reverse (as original sorter did). */
154  std::sort(preceding.begin(), preceding.end(), [](const ParentSpriteToDraw *a, const ParentSpriteToDraw *b) {
155  return a->order > b->order;
156  });
157 
158  s->order = ORDER_COMPARED;
159  sprite_order.push(s); // Still need to output so push it back for now
160 
161  for (auto p: preceding) {
162  p->order = next_order++;
163  sprite_order.push(p);
164  }
165  }
166 }
167 
168 
173 bool ViewportSortParentSpritesSSE41Checker()
174 {
175  return HasCPUIDFlag(1, 2, 19);
176 }
177 
178 #endif /* WITH_SSE */
safeguards.h
stdafx.h
cpu.h
ParentSpriteToDraw
Parent sprite that should be drawn.
Definition: viewport_sprite_sorter.h:18
viewport_sprite_sorter.h
HasCPUIDFlag
bool HasCPUIDFlag(uint type, uint index, uint bit)
Check whether the current CPU has the given flag.
Definition: cpu.cpp:171