CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/GPU/Software/Clipper.cpp
Views: 1401
1
// Copyright (c) 2013- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include <algorithm>
19
20
#include "GPU/GPUState.h"
21
22
#include "GPU/Software/BinManager.h"
23
#include "GPU/Software/Clipper.h"
24
#include "GPU/Software/Rasterizer.h"
25
#include "GPU/Software/RasterizerRectangle.h"
26
#include "GPU/Software/TransformUnit.h"
27
28
#include "Common/Profiler/Profiler.h"
29
30
namespace Clipper {
31
32
enum {
33
SKIP_FLAG = -1,
34
CLIP_NEG_Z_BIT = 0x20,
35
};
36
37
static inline int CalcClipMask(const ClipCoords &v) {
38
// This checks `x / w` compared to 1 or -1, skipping the division.
39
if (v.z < -v.w)
40
return -1;
41
return 0;
42
}
43
44
inline bool different_signs(float x, float y) {
45
return ((x <= 0 && y > 0) || (x > 0 && y <= 0));
46
}
47
48
inline float clip_dotprod(const ClipVertexData &vert, float A, float B, float C, float D) {
49
return (vert.clippos.x * A + vert.clippos.y * B + vert.clippos.z * C + vert.clippos.w * D);
50
}
51
52
inline void clip_interpolate(ClipVertexData &dest, float t, const ClipVertexData &a, const ClipVertexData &b) {
53
bool outsideRange = false;
54
dest.Lerp(t, a, b);
55
dest.v.screenpos = TransformUnit::ClipToScreen(dest.clippos, &outsideRange);
56
dest.v.clipw = dest.clippos.w;
57
58
// If the clipped coordinate is outside range, then we throw it away.
59
// This prevents a lot of inversions that shouldn't be drawn.
60
if (outsideRange)
61
dest.v.screenpos.x = 0x7FFFFFFF;
62
}
63
64
#define CLIP_POLY( PLANE_BIT, A, B, C, D ) \
65
{ \
66
if (mask & PLANE_BIT) { \
67
int idxPrev = inlist[0]; \
68
float dpPrev = clip_dotprod(*Vertices[idxPrev], A, B, C, D );\
69
int outcount = 0; \
70
\
71
inlist[n] = inlist[0]; \
72
for (int j = 1; j <= n; j++) { \
73
int idx = inlist[j]; \
74
float dp = clip_dotprod(*Vertices[idx], A, B, C, D ); \
75
if (dpPrev >= 0) { \
76
outlist[outcount++] = idxPrev; \
77
} \
78
\
79
/* Skipping w sign mismatches avoids inversions, but is incorrect. See #16131. */ \
80
/* For now, it's better to avoid inversions as they usually are undesired. */ \
81
if (different_signs(dp, dpPrev)) { \
82
auto &vert = Vertices[numVertices++]; \
83
if (dp < 0) { \
84
float t = dp / (dp - dpPrev); \
85
clip_interpolate(*vert, t, *Vertices[idx], *Vertices[idxPrev]); \
86
} else { \
87
float t = dpPrev / (dpPrev - dp); \
88
clip_interpolate(*vert, t, *Vertices[idxPrev], *Vertices[idx]); \
89
} \
90
outlist[outcount++] = numVertices - 1; \
91
} \
92
\
93
idxPrev = idx; \
94
dpPrev = dp; \
95
} \
96
\
97
if (outcount < 3) \
98
continue; \
99
\
100
{ \
101
int *tmp = inlist; \
102
inlist = outlist; \
103
outlist = tmp; \
104
n = outcount; \
105
} \
106
} \
107
}
108
109
#define CLIP_LINE(PLANE_BIT, A, B, C, D) \
110
{ \
111
if (mask & PLANE_BIT) { \
112
float dp0 = clip_dotprod(*Vertices[0], A, B, C, D ); \
113
float dp1 = clip_dotprod(*Vertices[1], A, B, C, D ); \
114
\
115
if (mask0 & PLANE_BIT) { \
116
if (dp0 < 0) { \
117
float t = dp1 / (dp1 - dp0); \
118
clip_interpolate(*Vertices[0], t, *Vertices[1], *Vertices[0]); \
119
} \
120
} \
121
dp0 = clip_dotprod(*Vertices[0], A, B, C, D ); \
122
\
123
if (mask1 & PLANE_BIT) { \
124
if (dp1 < 0) { \
125
float t = dp1 / (dp1- dp0); \
126
clip_interpolate(*Vertices[1], t, *Vertices[1], *Vertices[0]); \
127
} \
128
} \
129
} \
130
}
131
132
static inline bool CheckOutsideZ(ClipCoords p, int &pos, int &neg) {
133
constexpr float outsideValue = 1.000030517578125f;
134
float z = p.z / p.w;
135
if (z >= outsideValue) {
136
pos++;
137
return true;
138
}
139
if (-z >= outsideValue) {
140
neg++;
141
return true;
142
}
143
return false;
144
}
145
146
static void RotateUV(const VertexData &tl, const VertexData &br, VertexData &tr, VertexData &bl) {
147
const int x1 = tl.screenpos.x;
148
const int x2 = br.screenpos.x;
149
const int y1 = tl.screenpos.y;
150
const int y2 = br.screenpos.y;
151
152
if ((x1 < x2 && y1 > y2) || (x1 > x2 && y1 < y2)) {
153
std::swap(bl.texturecoords, tr.texturecoords);
154
}
155
}
156
157
// This is used for rectangle texture projection, which is very uncommon.
158
// To avoid complicating the common rectangle path, this just uses triangles.
159
static void AddTriangleRect(const VertexData &v0, const VertexData &v1, BinManager &binner) {
160
VertexData buf[4];
161
buf[0] = v1;
162
buf[0].screenpos = ScreenCoords(v0.screenpos.x, v0.screenpos.y, v1.screenpos.z);
163
buf[0].texturecoords = v0.texturecoords;
164
165
buf[1] = v1;
166
buf[1].screenpos = ScreenCoords(v0.screenpos.x, v1.screenpos.y, v1.screenpos.z);
167
buf[1].texturecoords = Vec3Packed<float>(v0.texturecoords.x, v1.texturecoords.y, v0.texturecoords.z);
168
169
buf[2] = v1;
170
buf[2].screenpos = ScreenCoords(v1.screenpos.x, v0.screenpos.y, v1.screenpos.z);
171
buf[2].texturecoords = Vec3Packed<float>(v1.texturecoords.x, v0.texturecoords.y, v1.texturecoords.z);
172
173
buf[3] = v1;
174
175
VertexData *topleft = &buf[0];
176
VertexData *topright = &buf[1];
177
VertexData *bottomleft = &buf[2];
178
VertexData *bottomright = &buf[3];
179
180
// DrawTriangle always culls, so sort out the drawing order.
181
for (int i = 0; i < 4; ++i) {
182
if (buf[i].screenpos.x < topleft->screenpos.x && buf[i].screenpos.y < topleft->screenpos.y)
183
topleft = &buf[i];
184
if (buf[i].screenpos.x > topright->screenpos.x && buf[i].screenpos.y < topright->screenpos.y)
185
topright = &buf[i];
186
if (buf[i].screenpos.x < bottomleft->screenpos.x && buf[i].screenpos.y > bottomleft->screenpos.y)
187
bottomleft = &buf[i];
188
if (buf[i].screenpos.x > bottomright->screenpos.x && buf[i].screenpos.y > bottomright->screenpos.y)
189
bottomright = &buf[i];
190
}
191
192
RotateUV(v0, v1, *topright, *bottomleft);
193
194
binner.AddTriangle(*topleft, *topright, *bottomleft);
195
binner.AddTriangle(*bottomleft, *topright, *topleft);
196
binner.AddTriangle(*topright, *bottomright, *bottomleft);
197
binner.AddTriangle(*bottomleft, *bottomright, *topright);
198
}
199
200
void ProcessRect(const ClipVertexData &v0, const ClipVertexData &v1, BinManager &binner) {
201
if (!binner.State().throughMode) {
202
// If any verts were outside range, throw the entire prim away.
203
if (v0.OutsideRange() || v1.OutsideRange())
204
return;
205
206
// We may discard the entire rect based on depth values.
207
int outsidePos = 0, outsideNeg = 0;
208
CheckOutsideZ(v0.clippos, outsidePos, outsideNeg);
209
CheckOutsideZ(v1.clippos, outsidePos, outsideNeg);
210
211
// With depth clamp off, we discard the rectangle if even one vert is outside.
212
if (outsidePos + outsideNeg > 0 && !gstate.isDepthClampEnabled())
213
return;
214
// With it on, both must be outside in the same direction.
215
else if (outsidePos >= 2 || outsideNeg >= 2)
216
return;
217
218
bool splitFog = v0.v.fogdepth != v1.v.fogdepth;
219
if (splitFog) {
220
// If they match the same 1/255, we can consider the fog flat. Seen in Resistance.
221
// More efficient if we can avoid splitting.
222
static constexpr float foghalfstep = 0.5f / 255.0f;
223
if (v1.v.fogdepth - foghalfstep <= v0.v.fogdepth && v1.v.fogdepth + foghalfstep >= v0.v.fogdepth)
224
splitFog = false;
225
}
226
if (splitFog) {
227
// Rectangles seem to always use nearest along X for fog depth, but reversed.
228
// TODO: Check exactness of middle.
229
VertexData vhalf0 = v1.v;
230
vhalf0.screenpos.x = v0.v.screenpos.x + (v1.v.screenpos.x - v0.v.screenpos.x) / 2;
231
vhalf0.texturecoords.x = v0.v.texturecoords.x + (v1.v.texturecoords.x - v0.v.texturecoords.x) / 2;
232
233
VertexData vhalf1 = v1.v;
234
vhalf1.screenpos.x = v0.v.screenpos.x + (v1.v.screenpos.x - v0.v.screenpos.x) / 2;
235
vhalf1.screenpos.y = v0.v.screenpos.y;
236
vhalf1.texturecoords.x = v0.v.texturecoords.x + (v1.v.texturecoords.x - v0.v.texturecoords.x) / 2;
237
vhalf1.texturecoords.y = v0.v.texturecoords.y;
238
239
VertexData vrev1 = v1.v;
240
vrev1.fogdepth = v0.v.fogdepth;
241
242
if (binner.State().textureProj) {
243
AddTriangleRect(v0.v, vhalf0, binner);
244
AddTriangleRect(vhalf1, vrev1, binner);
245
} else {
246
binner.AddRect(v0.v, vhalf0);
247
binner.AddRect(vhalf1, vrev1);
248
}
249
} else if (binner.State().textureProj) {
250
AddTriangleRect(v0.v, v1.v, binner);
251
} else {
252
binner.AddRect(v0.v, v1.v);
253
}
254
} else {
255
// through mode handling
256
if (Rasterizer::RectangleFastPath(v0.v, v1.v, binner)) {
257
return;
258
} else if (gstate.isModeClear() && !gstate.isDitherEnabled()) {
259
binner.AddClearRect(v0.v, v1.v);
260
} else {
261
binner.AddRect(v0.v, v1.v);
262
}
263
}
264
}
265
266
void ProcessPoint(const ClipVertexData &v0, BinManager &binner) {
267
// If any verts were outside range, throw the entire prim away.
268
if (!binner.State().throughMode) {
269
if (v0.OutsideRange())
270
return;
271
}
272
273
// Points need no clipping. Will be bounds checked in the rasterizer (which seems backwards?)
274
binner.AddPoint(v0.v);
275
}
276
277
void ProcessLine(const ClipVertexData &v0, const ClipVertexData &v1, BinManager &binner) {
278
if (binner.State().throughMode) {
279
// Actually, should clip this one too so we don't need to do bounds checks in the rasterizer.
280
binner.AddLine(v0.v, v1.v);
281
return;
282
}
283
284
// If any verts were outside range, throw the entire prim away.
285
if (v0.OutsideRange() || v1.OutsideRange())
286
return;
287
288
int outsidePos = 0, outsideNeg = 0;
289
CheckOutsideZ(v0.clippos, outsidePos, outsideNeg);
290
CheckOutsideZ(v1.clippos, outsidePos, outsideNeg);
291
292
// With depth clamp off, we discard the line if even one vert is outside.
293
if (outsidePos + outsideNeg > 0 && !gstate.isDepthClampEnabled())
294
return;
295
// With it on, both must be outside in the same direction.
296
else if (outsidePos >= 2 || outsideNeg >= 2)
297
return;
298
299
int mask0 = CalcClipMask(v0.clippos);
300
int mask1 = CalcClipMask(v1.clippos);
301
int mask = mask0 | mask1;
302
if ((mask & CLIP_NEG_Z_BIT) == 0) {
303
binner.AddLine(v0.v, v1.v);
304
return;
305
}
306
307
ClipVertexData ClippedVertices[2] = { v0, v1 };
308
ClipVertexData *Vertices[2] = { &ClippedVertices[0], &ClippedVertices[1] };
309
CLIP_LINE(CLIP_NEG_Z_BIT, 0, 0, 1, 1);
310
311
ClipVertexData data[2] = { *Vertices[0], *Vertices[1] };
312
if (!data[0].OutsideRange() && !data[1].OutsideRange())
313
binner.AddLine(data[0].v, data[1].v);
314
}
315
316
void ProcessTriangle(const ClipVertexData &v0, const ClipVertexData &v1, const ClipVertexData &v2, const ClipVertexData &provoking, BinManager &binner) {
317
int mask = 0;
318
if (!binner.State().throughMode) {
319
// If any verts were outside range, throw the entire prim away.
320
if (v0.OutsideRange() || v1.OutsideRange() || v2.OutsideRange())
321
return;
322
// If all verts have negative W, we also cull.
323
if (v0.clippos.w < 0.0f && v1.clippos.w < 0.0f && v2.clippos.w < 0.0f)
324
return;
325
326
mask |= CalcClipMask(v0.clippos);
327
mask |= CalcClipMask(v1.clippos);
328
mask |= CalcClipMask(v2.clippos);
329
330
// We may discard the entire triangle based on depth values. First check what's outside.
331
int outsidePos = 0, outsideNeg = 0;
332
CheckOutsideZ(v0.clippos, outsidePos, outsideNeg);
333
CheckOutsideZ(v1.clippos, outsidePos, outsideNeg);
334
CheckOutsideZ(v2.clippos, outsidePos, outsideNeg);
335
336
// With depth clamp off, we discard the triangle if even one vert is outside.
337
if (outsidePos + outsideNeg > 0 && !gstate.isDepthClampEnabled())
338
return;
339
// With it on, all three must be outside in the same direction.
340
else if (outsidePos >= 3 || outsideNeg >= 3)
341
return;
342
}
343
344
// No clipping is common, let's skip processing if we can.
345
if ((mask & CLIP_NEG_Z_BIT) == 0) {
346
if (gstate.getShadeMode() == GE_SHADE_FLAT) {
347
// So that the order of clipping doesn't matter...
348
VertexData corrected2 = v2.v;
349
corrected2.color0 = provoking.v.color0;
350
corrected2.color1 = provoking.v.color1;
351
binner.AddTriangle(v0.v, v1.v, corrected2);
352
} else {
353
binner.AddTriangle(v0.v, v1.v, v2.v);
354
}
355
return;
356
}
357
358
enum { NUM_CLIPPED_VERTICES = 3, NUM_INDICES = NUM_CLIPPED_VERTICES + 3 };
359
360
ClipVertexData* Vertices[NUM_INDICES];
361
ClipVertexData ClippedVertices[NUM_INDICES];
362
for (int i = 0; i < NUM_INDICES; ++i)
363
Vertices[i] = &ClippedVertices[i];
364
365
// TODO: Change logic when it's a backface (why? In what way?)
366
ClippedVertices[0] = v0;
367
ClippedVertices[1] = v1;
368
ClippedVertices[2] = v2;
369
370
int indices[NUM_INDICES] = { 0, 1, 2, SKIP_FLAG, SKIP_FLAG, SKIP_FLAG };
371
int numIndices = 3;
372
373
for (int i = 0; i < 3; i += 3) {
374
int vlist[2][2*6+1];
375
int *inlist = vlist[0], *outlist = vlist[1];
376
int n = 3;
377
int numVertices = 3;
378
379
inlist[0] = 0;
380
inlist[1] = 1;
381
inlist[2] = 2;
382
383
// mark this triangle as unused in case it should be completely clipped
384
indices[0] = SKIP_FLAG;
385
indices[1] = SKIP_FLAG;
386
indices[2] = SKIP_FLAG;
387
388
// The PSP only clips on negative Z (importantly, regardless of viewport.)
389
CLIP_POLY(CLIP_NEG_Z_BIT, 0, 0, 1, 1);
390
391
// transform the poly in inlist into triangles
392
indices[0] = inlist[0];
393
indices[1] = inlist[1];
394
indices[2] = inlist[2];
395
for (int j = 3; j < n; ++j) {
396
indices[numIndices++] = inlist[0];
397
indices[numIndices++] = inlist[j - 1];
398
indices[numIndices++] = inlist[j];
399
}
400
}
401
402
for (int i = 0; i + 3 <= numIndices; i += 3) {
403
if (indices[i] != SKIP_FLAG) {
404
ClipVertexData &subv0 = *Vertices[indices[i + 0]];
405
ClipVertexData &subv1 = *Vertices[indices[i + 1]];
406
ClipVertexData &subv2 = *Vertices[indices[i + 2]];
407
408
if (subv0.OutsideRange() || subv1.OutsideRange() || subv2.OutsideRange())
409
continue;
410
411
if (gstate.getShadeMode() == GE_SHADE_FLAT) {
412
// So that the order of clipping doesn't matter...
413
subv2.v.color0 = provoking.v.color0;
414
subv2.v.color1 = provoking.v.color1;
415
}
416
417
binner.AddTriangle(subv0.v, subv1.v, subv2.v);
418
}
419
}
420
}
421
422
} // namespace
423
424