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