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/Common/SoftwareTransformCommon.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
#include <cmath>
20
21
#include "Common/CPUDetect.h"
22
#include "Common/Math/math_util.h"
23
#include "Common/GPU/OpenGL/GLFeatures.h"
24
#include "Core/Config.h"
25
#include "GPU/GPUState.h"
26
#include "GPU/Math3D.h"
27
#include "GPU/Common/FramebufferManagerCommon.h"
28
#include "GPU/Common/GPUStateUtils.h"
29
#include "GPU/Common/SoftwareTransformCommon.h"
30
#include "GPU/Common/TransformCommon.h"
31
#include "GPU/Common/TextureCacheCommon.h"
32
#include "GPU/Common/VertexDecoderCommon.h"
33
34
// This is the software transform pipeline, which is necessary for supporting RECT
35
// primitives correctly without geometry shaders, and may be easier to use for
36
// debugging than the hardware transform pipeline.
37
38
// There's code here that simply expands transformed RECTANGLES into plain triangles.
39
40
// We're gonna have to keep software transforming RECTANGLES, unless we use a geom shader which we can't on OpenGL ES 2.0 or DX9.
41
// Usually, though, these primitives don't use lighting etc so it's no biggie performance wise, but it would be nice to get rid of
42
// this code.
43
44
// Actually, if we find the camera-relative right and down vectors, it might even be possible to add the extra points in pre-transformed
45
// space and thus make decent use of hardware transform.
46
47
// Actually again, single quads could be drawn more efficiently using GL_TRIANGLE_STRIP, no need to duplicate verts as for
48
// GL_TRIANGLES. Still need to sw transform to compute the extra two corners though.
49
//
50
51
// The verts are in the order: BR BL TL TR
52
static void SwapUVs(TransformedVertex &a, TransformedVertex &b) {
53
float tempu = a.u;
54
float tempv = a.v;
55
a.u = b.u;
56
a.v = b.v;
57
b.u = tempu;
58
b.v = tempv;
59
}
60
61
// 2 3 3 2 0 3 2 1
62
// to to or
63
// 1 0 0 1 1 2 3 0
64
65
// Note: 0 is BR and 2 is TL.
66
67
static void RotateUV(TransformedVertex v[4], bool flippedY) {
68
// We use the transformed tl/br coordinates to figure out whether they're flipped or not.
69
float ySign = flippedY ? -1.0 : 1.0;
70
71
const float x1 = v[2].x;
72
const float x2 = v[0].x;
73
const float y1 = v[2].y * ySign;
74
const float y2 = v[0].y * ySign;
75
76
if ((x1 < x2 && y1 < y2) || (x1 > x2 && y1 > y2))
77
SwapUVs(v[1], v[3]);
78
}
79
80
static void RotateUVThrough(TransformedVertex v[4]) {
81
float x1 = v[2].x;
82
float x2 = v[0].x;
83
float y1 = v[2].y;
84
float y2 = v[0].y;
85
86
if ((x1 < x2 && y1 > y2) || (x1 > x2 && y1 < y2))
87
SwapUVs(v[1], v[3]);
88
}
89
90
// Clears on the PSP are best done by drawing a series of vertical strips
91
// in clear mode. This tries to detect that.
92
static bool IsReallyAClear(const TransformedVertex *transformed, int numVerts, float x2, float y2) {
93
if (transformed[0].x < 0.0f || transformed[0].y < 0.0f || transformed[0].x > 0.5f || transformed[0].y > 0.5f)
94
return false;
95
96
const float originY = transformed[0].y;
97
98
// Color and Z are decided by the second vertex, so only need to check those for matching color.
99
const u32 matchcolor = transformed[1].color0_32;
100
const float matchz = transformed[1].z;
101
102
for (int i = 1; i < numVerts; i++) {
103
if ((i & 1) == 0) {
104
// Top left of a rectangle
105
if (transformed[i].y != originY)
106
return false;
107
float gap = fabsf(transformed[i].x - transformed[i - 1].x); // Should probably do some smarter check.
108
if (i > 0 && gap > 0.0625)
109
return false;
110
} else {
111
if (transformed[i].color0_32 != matchcolor || transformed[i].z != matchz)
112
return false;
113
// Bottom right
114
if (transformed[i].y < y2)
115
return false;
116
if (transformed[i].x <= transformed[i - 1].x)
117
return false;
118
}
119
}
120
121
// The last vertical strip often extends outside the drawing area.
122
if (transformed[numVerts - 1].x < x2)
123
return false;
124
125
return true;
126
}
127
128
void SoftwareTransform::SetProjMatrix(const float mtx[14], bool invertedX, bool invertedY, const Lin::Vec3 &trans, const Lin::Vec3 &scale) {
129
memcpy(&projMatrix_.m, mtx, 16 * sizeof(float));
130
131
if (invertedY) {
132
projMatrix_.xy = -projMatrix_.xy;
133
projMatrix_.yy = -projMatrix_.yy;
134
projMatrix_.zy = -projMatrix_.zy;
135
projMatrix_.wy = -projMatrix_.wy;
136
}
137
if (invertedX) {
138
projMatrix_.xx = -projMatrix_.xx;
139
projMatrix_.yx = -projMatrix_.yx;
140
projMatrix_.zx = -projMatrix_.zx;
141
projMatrix_.wx = -projMatrix_.wx;
142
}
143
144
projMatrix_.translateAndScale(trans, scale);
145
}
146
147
void SoftwareTransform::Transform(int prim, u32 vertType, const DecVtxFormat &decVtxFormat, int numDecodedVerts, SoftwareTransformResult *result) {
148
u8 *decoded = params_.decoded;
149
TransformedVertex *transformed = params_.transformed;
150
bool throughmode = (vertType & GE_VTYPE_THROUGH_MASK) != 0;
151
bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled();
152
153
float uscale = 1.0f;
154
float vscale = 1.0f;
155
if (throughmode && prim != GE_PRIM_RECTANGLES) {
156
// For through rectangles, we do this scaling in Expand.
157
uscale /= gstate_c.curTextureWidth;
158
vscale /= gstate_c.curTextureHeight;
159
}
160
161
const int w = gstate.getTextureWidth(0);
162
const int h = gstate.getTextureHeight(0);
163
float widthFactor = (float) w / (float) gstate_c.curTextureWidth;
164
float heightFactor = (float) h / (float) gstate_c.curTextureHeight;
165
166
Lighter lighter(vertType);
167
float fog_end = getFloat24(gstate.fog1);
168
float fog_slope = getFloat24(gstate.fog2);
169
// Same fixup as in ShaderManagerGLES.cpp
170
if (my_isnanorinf(fog_end)) {
171
// Not really sure what a sensible value might be, but let's try 64k.
172
fog_end = std::signbit(fog_end) ? -65535.0f : 65535.0f;
173
}
174
if (my_isnanorinf(fog_slope)) {
175
fog_slope = std::signbit(fog_slope) ? -65535.0f : 65535.0f;
176
}
177
178
VertexReader reader(decoded, decVtxFormat, vertType);
179
if (throughmode) {
180
const u32 materialAmbientRGBA = gstate.getMaterialAmbientRGBA();
181
const bool hasColor = reader.hasColor0();
182
const bool hasUV = reader.hasUV();
183
for (int index = 0; index < numDecodedVerts; index++) {
184
// Do not touch the coordinates or the colors. No lighting.
185
reader.Goto(index);
186
// TODO: Write to a flexible buffer, we don't always need all four components.
187
TransformedVertex &vert = transformed[index];
188
reader.ReadPos(vert.pos);
189
vert.pos_w = 1.0f;
190
191
if (hasColor) {
192
vert.color0_32 = reader.ReadColor0_8888();
193
} else {
194
vert.color0_32 = materialAmbientRGBA;
195
}
196
197
if (hasUV) {
198
reader.ReadUV(vert.uv);
199
200
vert.u *= uscale;
201
vert.v *= vscale;
202
} else {
203
vert.u = 0.0f;
204
vert.v = 0.0f;
205
}
206
vert.uv_w = 1.0f;
207
208
// Ignore color1 and fog, never used in throughmode anyway.
209
// The w of uv is also never used (hardcoded to 1.0.)
210
}
211
} else {
212
const Vec4f materialAmbientRGBA = Vec4f::FromRGBA(gstate.getMaterialAmbientRGBA());
213
// Okay, need to actually perform the full transform.
214
for (int index = 0; index < numDecodedVerts; index++) {
215
reader.Goto(index);
216
217
float v[3] = {0, 0, 0};
218
Vec4f c0 = Vec4f(1, 1, 1, 1);
219
Vec4f c1 = Vec4f(0, 0, 0, 0);
220
float uv[3] = {0, 0, 1};
221
float fogCoef = 1.0f;
222
223
float out[3];
224
float pos[3];
225
Vec3f normal(0, 0, 1);
226
Vec3f worldnormal(0, 0, 1);
227
reader.ReadPos(pos);
228
229
float ruv[2] = { 0.0f, 0.0f };
230
if (reader.hasUV())
231
reader.ReadUV(ruv);
232
233
Vec4f unlitColor;
234
if (reader.hasColor0())
235
reader.ReadColor0(unlitColor.AsArray());
236
else
237
unlitColor = materialAmbientRGBA;
238
if (reader.hasNormal())
239
reader.ReadNrm(normal.AsArray());
240
241
Vec3ByMatrix43(out, pos, gstate.worldMatrix);
242
if (reader.hasNormal()) {
243
if (gstate.areNormalsReversed()) {
244
normal = -normal;
245
}
246
Norm3ByMatrix43(worldnormal.AsArray(), normal.AsArray(), gstate.worldMatrix);
247
worldnormal = worldnormal.NormalizedOr001(cpu_info.bSSE4_1);
248
}
249
250
// Perform lighting here if enabled.
251
if (gstate.isLightingEnabled()) {
252
float litColor0[4];
253
float litColor1[4];
254
lighter.Light(litColor0, litColor1, unlitColor.AsArray(), out, worldnormal);
255
256
// Don't ignore gstate.lmode - we should send two colors in that case
257
for (int j = 0; j < 4; j++) {
258
c0[j] = litColor0[j];
259
}
260
if (lmode) {
261
// Separate colors
262
for (int j = 0; j < 4; j++) {
263
c1[j] = litColor1[j];
264
}
265
} else {
266
// Summed color into c0 (will clamp in ToRGBA().)
267
for (int j = 0; j < 4; j++) {
268
c0[j] += litColor1[j];
269
}
270
}
271
} else {
272
for (int j = 0; j < 4; j++) {
273
c0[j] = unlitColor[j];
274
}
275
if (lmode) {
276
// c1 is already 0.
277
}
278
}
279
280
// Perform texture coordinate generation after the transform and lighting - one style of UV depends on lights.
281
switch (gstate.getUVGenMode()) {
282
case GE_TEXMAP_TEXTURE_COORDS: // UV mapping
283
case GE_TEXMAP_UNKNOWN: // Seen in Riviera. Unsure of meaning, but this works.
284
// We always prescale in the vertex decoder now.
285
uv[0] = ruv[0];
286
uv[1] = ruv[1];
287
uv[2] = 1.0f;
288
break;
289
290
case GE_TEXMAP_TEXTURE_MATRIX:
291
{
292
// Projection mapping
293
Vec3f source(0.0f, 0.0f, 1.0f);
294
switch (gstate.getUVProjMode()) {
295
case GE_PROJMAP_POSITION: // Use model space XYZ as source
296
source = pos;
297
break;
298
299
case GE_PROJMAP_UV: // Use unscaled UV as source
300
source = Vec3f(ruv[0], ruv[1], 0.0f);
301
break;
302
303
case GE_PROJMAP_NORMALIZED_NORMAL: // Use normalized normal as source
304
source = normal.Normalized(cpu_info.bSSE4_1);
305
if (!reader.hasNormal()) {
306
ERROR_LOG_REPORT(Log::G3D, "Normal projection mapping without normal?");
307
}
308
break;
309
310
case GE_PROJMAP_NORMAL: // Use non-normalized normal as source!
311
source = normal;
312
if (!reader.hasNormal()) {
313
ERROR_LOG_REPORT(Log::G3D, "Normal projection mapping without normal?");
314
}
315
break;
316
}
317
318
float uvw[3];
319
Vec3ByMatrix43(uvw, &source.x, gstate.tgenMatrix);
320
uv[0] = uvw[0];
321
uv[1] = uvw[1];
322
uv[2] = uvw[2];
323
}
324
break;
325
326
case GE_TEXMAP_ENVIRONMENT_MAP:
327
// Shade mapping - use two light sources to generate U and V.
328
{
329
auto getLPosFloat = [&](int l, int i) {
330
return getFloat24(gstate.lpos[l * 3 + i]);
331
};
332
auto getLPos = [&](int l) {
333
return Vec3f(getLPosFloat(l, 0), getLPosFloat(l, 1), getLPosFloat(l, 2));
334
};
335
auto calcShadingLPos = [&](int l) {
336
Vec3f pos = getLPos(l);
337
return pos.NormalizedOr001(cpu_info.bSSE4_1);
338
};
339
// Might not have lighting enabled, so don't use lighter.
340
Vec3f lightpos0 = calcShadingLPos(gstate.getUVLS0());
341
Vec3f lightpos1 = calcShadingLPos(gstate.getUVLS1());
342
343
uv[0] = (1.0f + Dot(lightpos0, worldnormal))/2.0f;
344
uv[1] = (1.0f + Dot(lightpos1, worldnormal))/2.0f;
345
uv[2] = 1.0f;
346
}
347
break;
348
349
default:
350
// Illegal
351
ERROR_LOG_REPORT(Log::G3D, "Impossible UV gen mode? %d", gstate.getUVGenMode());
352
break;
353
}
354
355
uv[0] = uv[0] * widthFactor;
356
uv[1] = uv[1] * heightFactor;
357
358
// Transform the coord by the view matrix.
359
Vec3ByMatrix43(v, out, gstate.viewMatrix);
360
fogCoef = (v[2] + fog_end) * fog_slope;
361
362
// TODO: Write to a flexible buffer, we don't always need all four components.
363
Vec3ByMatrix44(transformed[index].pos, v, projMatrix_.m);
364
transformed[index].fog = fogCoef;
365
memcpy(&transformed[index].uv, uv, 3 * sizeof(float));
366
transformed[index].color0_32 = c0.ToRGBA();
367
transformed[index].color1_32 = c1.ToRGBA();
368
369
// Vertex depth rounding is done in the shader, to simulate the 16-bit depth buffer.
370
}
371
}
372
373
// Here's the best opportunity to try to detect rectangles used to clear the screen, and
374
// replace them with real clears. This can provide a speedup on certain mobile chips.
375
//
376
// An alternative option is to simply ditch all the verts except the first and last to create a single
377
// rectangle out of many. Quite a small optimization though.
378
// TODO: This bleeds outside the play area in non-buffered mode. Big deal? Probably not.
379
// TODO: Allow creating a depth clear and a color draw.
380
bool reallyAClear = false;
381
if (numDecodedVerts > 1 && prim == GE_PRIM_RECTANGLES && gstate.isModeClear() && throughmode) {
382
int scissorX2 = gstate.getScissorX2() + 1;
383
int scissorY2 = gstate.getScissorY2() + 1;
384
reallyAClear = IsReallyAClear(transformed, numDecodedVerts, scissorX2, scissorY2);
385
if (reallyAClear && gstate.getColorMask() != 0xFFFFFFFF && (gstate.isClearModeColorMask() || gstate.isClearModeAlphaMask())) {
386
result->setSafeSize = true;
387
result->safeWidth = scissorX2;
388
result->safeHeight = scissorY2;
389
}
390
}
391
if (params_.allowClear && reallyAClear && gl_extensions.gpuVendor != GPU_VENDOR_IMGTEC) {
392
// If alpha is not allowed to be separate, it must match for both depth/stencil and color. Vulkan requires this.
393
bool alphaMatchesColor = gstate.isClearModeColorMask() == gstate.isClearModeAlphaMask();
394
bool depthMatchesStencil = gstate.isClearModeAlphaMask() == gstate.isClearModeDepthMask();
395
bool matchingComponents = params_.allowSeparateAlphaClear || (alphaMatchesColor && depthMatchesStencil);
396
bool stencilNotMasked = !gstate.isClearModeAlphaMask() || gstate.getStencilWriteMask() == 0x00;
397
if (matchingComponents && stencilNotMasked) {
398
DepthScaleFactors depthScale = GetDepthScaleFactors(gstate_c.UseFlags());
399
result->color = transformed[1].color0_32;
400
// Need to rescale from a [0, 1] float. This is the final transformed value.
401
result->depth = depthScale.EncodeFromU16((float)(int)(transformed[1].z * 65535.0f));
402
result->action = SW_CLEAR;
403
gpuStats.numClears++;
404
return;
405
}
406
}
407
408
// Detect full screen "clears" that might not be so obvious, to set the safe size if possible.
409
if (!result->setSafeSize && prim == GE_PRIM_RECTANGLES && numDecodedVerts == 2 && throughmode) {
410
bool clearingColor = gstate.isModeClear() && (gstate.isClearModeColorMask() || gstate.isClearModeAlphaMask());
411
bool writingColor = gstate.getColorMask() != 0xFFFFFFFF;
412
bool startsZeroX = transformed[0].x <= 0.0f && transformed[1].x > 0.0f && transformed[1].x > transformed[0].x;
413
bool startsZeroY = transformed[0].y <= 0.0f && transformed[1].y > 0.0f && transformed[1].y > transformed[0].y;
414
415
if (startsZeroX && startsZeroY && (clearingColor || writingColor)) {
416
int scissorX2 = gstate.getScissorX2() + 1;
417
int scissorY2 = gstate.getScissorY2() + 1;
418
result->setSafeSize = true;
419
result->safeWidth = std::min(scissorX2, (int)transformed[1].x);
420
result->safeHeight = std::min(scissorY2, (int)transformed[1].y);
421
}
422
}
423
}
424
425
void SoftwareTransform::BuildDrawingParams(int prim, int vertexCount, u32 vertType, u16 *&inds, int indsSize, int &numDecodedVerts, int vertsSize, SoftwareTransformResult *result) {
426
TransformedVertex *transformed = params_.transformed;
427
TransformedVertex *transformedExpanded = params_.transformedExpanded;
428
bool throughmode = (vertType & GE_VTYPE_THROUGH_MASK) != 0;
429
430
// Step 2: expand and process primitives.
431
result->drawBuffer = transformed;
432
int numTrans = 0;
433
434
FramebufferManagerCommon *fbman = params_.fbman;
435
bool useBufferedRendering = fbman->UseBufferedRendering();
436
437
if (prim == GE_PRIM_RECTANGLES) {
438
if (!ExpandRectangles(vertexCount, numDecodedVerts, vertsSize, inds, indsSize, transformed, transformedExpanded, numTrans, throughmode, &result->pixelMapped)) {
439
result->drawNumTrans = 0;
440
result->pixelMapped = false;
441
return;
442
}
443
result->drawBuffer = transformedExpanded;
444
445
// We don't know the color until here, so we have to do it now, instead of in StateMapping.
446
// Might want to reconsider the order of things later...
447
if (gstate.isModeClear() && gstate.isClearModeAlphaMask()) {
448
result->setStencil = true;
449
if (vertexCount > 1) {
450
// Take the bottom right alpha value of the first rect as the stencil value.
451
// Technically, each rect could individually fill its stencil, but most of the
452
// time they use the same one.
453
result->stencilValue = transformed[inds[1]].color0[3];
454
} else {
455
result->stencilValue = 0;
456
}
457
}
458
} else if (prim == GE_PRIM_POINTS) {
459
result->pixelMapped = false;
460
if (!ExpandPoints(vertexCount, numDecodedVerts, vertsSize, inds, indsSize, transformed, transformedExpanded, numTrans, throughmode)) {
461
result->drawNumTrans = 0;
462
return;
463
}
464
result->drawBuffer = transformedExpanded;
465
} else if (prim == GE_PRIM_LINES) {
466
result->pixelMapped = false;
467
if (!ExpandLines(vertexCount, numDecodedVerts, vertsSize, inds, indsSize, transformed, transformedExpanded, numTrans, throughmode)) {
468
result->drawNumTrans = 0;
469
return;
470
}
471
result->drawBuffer = transformedExpanded;
472
} else {
473
// We can simply draw the unexpanded buffer.
474
numTrans = vertexCount;
475
result->pixelMapped = false;
476
477
// If we don't support custom cull in the shader, process it here.
478
if (!gstate_c.Use(GPU_USE_CULL_DISTANCE) && vertexCount > 0 && !throughmode) {
479
const u16 *indsIn = (const u16 *)inds;
480
u16 *newInds = inds + vertexCount;
481
u16 *indsOut = newInds;
482
483
float minZValue, maxZValue;
484
CalcCullParams(minZValue, maxZValue);
485
486
std::vector<int> outsideZ;
487
outsideZ.resize(vertexCount);
488
489
// First, check inside/outside directions for each index.
490
for (int i = 0; i < vertexCount; ++i) {
491
float z = transformed[indsIn[i]].z / transformed[indsIn[i]].pos_w;
492
if (z > maxZValue)
493
outsideZ[i] = 1;
494
else if (z < minZValue)
495
outsideZ[i] = -1;
496
else
497
outsideZ[i] = 0;
498
}
499
500
// Now, for each primitive type, throw away the indices if:
501
// - Depth clamp on, and ALL verts are outside *in the same direction*.
502
// - Depth clamp off, and ANY vert is outside.
503
if (prim == GE_PRIM_TRIANGLES && gstate.isDepthClampEnabled()) {
504
numTrans = 0;
505
for (int i = 0; i < vertexCount - 2; i += 3) {
506
if (outsideZ[i + 0] != 0 && outsideZ[i + 0] == outsideZ[i + 1] && outsideZ[i + 0] == outsideZ[i + 2]) {
507
// All outside, and all the same direction. Nuke this triangle.
508
continue;
509
}
510
511
memcpy(indsOut, indsIn + i, 3 * sizeof(uint16_t));
512
indsOut += 3;
513
numTrans += 3;
514
}
515
516
inds = newInds;
517
} else if (prim == GE_PRIM_TRIANGLES) {
518
numTrans = 0;
519
for (int i = 0; i < vertexCount - 2; i += 3) {
520
if (outsideZ[i + 0] != 0 || outsideZ[i + 1] != 0 || outsideZ[i + 2] != 0) {
521
// Even one outside, and we cull.
522
continue;
523
}
524
525
memcpy(indsOut, indsIn + i, 3 * sizeof(uint16_t));
526
indsOut += 3;
527
numTrans += 3;
528
}
529
530
inds = newInds;
531
}
532
} else if (throughmode && g_Config.bSmart2DTexFiltering && !gstate_c.textureIsVideo) {
533
// We check some common cases for pixel mapping.
534
// TODO: It's not really optimal that some previous step has removed the triangle strip.
535
if (vertexCount <= 6 && prim == GE_PRIM_TRIANGLES) {
536
// It's enough to check UV deltas vs pos deltas between vertex pairs:
537
// 0-1 1-3 3-2 2-0. Maybe can even skip the last one. Probably some simple math can get us that sequence.
538
// Unfortunately we need to reverse the previous UV scaling operation. Fortunately these are powers of two
539
// so the operations are exact.
540
bool pixelMapped = true;
541
const u16 *indsIn = (const u16 *)inds;
542
for (int t = 0; t < vertexCount; t += 3) {
543
float uscale = gstate_c.curTextureWidth;
544
float vscale = gstate_c.curTextureHeight;
545
struct { int a; int b; } pairs[] = { {0, 1}, {1, 2}, {2, 0} };
546
for (int i = 0; i < ARRAY_SIZE(pairs); i++) {
547
int a = indsIn[t + pairs[i].a];
548
int b = indsIn[t + pairs[i].b];
549
float du = fabsf((transformed[a].u - transformed[b].u) * uscale);
550
float dv = fabsf((transformed[a].v - transformed[b].v) * vscale);
551
float dx = fabsf(transformed[a].x - transformed[b].x);
552
float dy = fabsf(transformed[a].y - transformed[b].y);
553
if (du != dx || dv != dy) {
554
pixelMapped = false;
555
}
556
}
557
if (!pixelMapped) {
558
break;
559
}
560
}
561
result->pixelMapped = pixelMapped;
562
}
563
}
564
}
565
566
if (gstate.isModeClear()) {
567
gpuStats.numClears++;
568
}
569
570
result->action = SW_DRAW_INDEXED;
571
result->drawNumTrans = numTrans;
572
}
573
574
void SoftwareTransform::CalcCullParams(float &minZValue, float &maxZValue) const {
575
// The projected Z can be up to 0x3F8000FF, which is where this constant is from.
576
// It seems like it may only maintain 15 mantissa bits (excluding implied.)
577
maxZValue = 1.000030517578125f * gstate_c.vpDepthScale;
578
minZValue = -maxZValue;
579
// Scale and offset the Z appropriately, since we baked that into a projection transform.
580
if (params_.usesHalfZ) {
581
maxZValue = maxZValue * 0.5f + 0.5f + gstate_c.vpZOffset * 0.5f;
582
minZValue = minZValue * 0.5f + 0.5f + gstate_c.vpZOffset * 0.5f;
583
} else {
584
maxZValue += gstate_c.vpZOffset;
585
minZValue += gstate_c.vpZOffset;
586
}
587
// In case scale was negative, flip.
588
if (minZValue > maxZValue)
589
std::swap(minZValue, maxZValue);
590
}
591
592
bool SoftwareTransform::ExpandRectangles(int vertexCount, int &numDecodedVerts, int vertsSize, u16 *&inds, int indsSize, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int &numTrans, bool throughmode, bool *pixelMappedExactly) const {
593
// Before we start, do a sanity check - does the output fit?
594
if ((vertexCount / 2) * 6 > indsSize) {
595
// Won't fit, kill the draw.
596
return false;
597
}
598
if ((vertexCount / 2) * 4 > vertsSize) {
599
// Won't fit, kill the draw.
600
return false;
601
}
602
603
// Rectangles always need 2 vertices, disregard the last one if there's an odd number.
604
vertexCount = vertexCount & ~1;
605
numTrans = 0;
606
TransformedVertex *trans = &transformedExpanded[0];
607
608
const u16 *indsIn = (const u16 *)inds;
609
u16 *newInds = inds + vertexCount;
610
u16 *indsOut = newInds;
611
612
numDecodedVerts = 4 * (vertexCount / 2);
613
614
float uscale = 1.0f;
615
float vscale = 1.0f;
616
if (throughmode) {
617
uscale /= gstate_c.curTextureWidth;
618
vscale /= gstate_c.curTextureHeight;
619
}
620
621
bool pixelMapped = g_Config.bSmart2DTexFiltering && !gstate_c.textureIsVideo;
622
623
for (int i = 0; i < vertexCount; i += 2) {
624
const TransformedVertex &transVtxTL = transformed[indsIn[i + 0]];
625
const TransformedVertex &transVtxBR = transformed[indsIn[i + 1]];
626
627
if (pixelMapped) {
628
float dx = transVtxBR.x - transVtxTL.x;
629
float dy = transVtxBR.y - transVtxTL.y;
630
float du = transVtxBR.u - transVtxTL.u;
631
float dv = transVtxBR.v - transVtxTL.v;
632
633
// NOTE: We will accept it as pixel mapped if only one dimension is stretched. This fixes dialog frames in FFI.
634
// Though, there could be false positives in other games due to this. Let's see if it is a problem...
635
if (dx <= 0 || dy <= 0 || (dx != du && dy != dv)) {
636
pixelMapped = false;
637
}
638
}
639
640
// We have to turn the rectangle into two triangles, so 6 points.
641
// This is 4 verts + 6 indices.
642
643
// bottom right
644
trans[0] = transVtxBR;
645
trans[0].u = transVtxBR.u * uscale;
646
trans[0].v = transVtxBR.v * vscale;
647
648
// top right
649
trans[1] = transVtxBR;
650
trans[1].y = transVtxTL.y;
651
trans[1].u = transVtxBR.u * uscale;
652
trans[1].v = transVtxTL.v * vscale;
653
654
// top left
655
trans[2] = transVtxBR;
656
trans[2].x = transVtxTL.x;
657
trans[2].y = transVtxTL.y;
658
trans[2].u = transVtxTL.u * uscale;
659
trans[2].v = transVtxTL.v * vscale;
660
661
// bottom left
662
trans[3] = transVtxBR;
663
trans[3].x = transVtxTL.x;
664
trans[3].u = transVtxTL.u * uscale;
665
trans[3].v = transVtxBR.v * vscale;
666
667
// That's the four corners. Now process UV rotation.
668
if (throughmode) {
669
RotateUVThrough(trans);
670
} else {
671
RotateUV(trans, params_.flippedY);
672
}
673
674
// Triangle: BR-TR-TL
675
indsOut[0] = i * 2 + 0;
676
indsOut[1] = i * 2 + 1;
677
indsOut[2] = i * 2 + 2;
678
// Triangle: BL-BR-TL
679
indsOut[3] = i * 2 + 3;
680
indsOut[4] = i * 2 + 0;
681
indsOut[5] = i * 2 + 2;
682
683
trans += 4;
684
indsOut += 6;
685
686
numTrans += 6;
687
}
688
inds = newInds;
689
*pixelMappedExactly = pixelMapped;
690
return true;
691
}
692
693
// In-place. So, better not be doing this on GPU memory!
694
void IndexBufferProvokingLastToFirst(int prim, u16 *inds, int indsSize) {
695
switch (prim) {
696
case GE_PRIM_LINES:
697
// Swap every two indices.
698
for (int i = 0; i < indsSize - 1; i += 2) {
699
u16 temp = inds[i];
700
inds[i] = inds[i + 1];
701
inds[i + 1] = temp;
702
}
703
break;
704
case GE_PRIM_TRIANGLES:
705
// Rotate the triangle so the last becomes the first, without changing the winding order.
706
// This could be done with a series of pshufb.
707
for (int i = 0; i < indsSize - 2; i += 3) {
708
u16 temp = inds[i + 2];
709
inds[i + 2] = inds[i + 1];
710
inds[i + 1] = inds[i];
711
inds[i] = temp;
712
}
713
break;
714
case GE_PRIM_POINTS:
715
// Nothing to do,
716
break;
717
case GE_PRIM_RECTANGLES:
718
// Nothing to do, already using the 2nd vertex.
719
break;
720
default:
721
_dbg_assert_msg_(false, "IndexBufferProvokingFirstToLast: Only works with plain indexed primitives, no strips or fans")
722
}
723
}
724
725
bool SoftwareTransform::ExpandLines(int vertexCount, int &numDecodedVerts, int vertsSize, u16 *&inds, int indsSize, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int &numTrans, bool throughmode) {
726
// Before we start, do a sanity check - does the output fit?
727
if ((vertexCount / 2) * 6 > indsSize) {
728
// Won't fit, kill the draw.
729
return false;
730
}
731
if ((vertexCount / 2) * 4 > vertsSize) {
732
return false;
733
}
734
735
// Lines always need 2 vertices, disregard the last one if there's an odd number.
736
vertexCount = vertexCount & ~1;
737
numTrans = 0;
738
TransformedVertex *trans = &transformedExpanded[0];
739
740
const u16 *indsIn = (const u16 *)inds;
741
u16 *newInds = inds + vertexCount;
742
u16 *indsOut = newInds;
743
744
float dx = 1.0f * gstate_c.vpWidthScale * (1.0f / fabsf(gstate.getViewportXScale()));
745
float dy = 1.0f * gstate_c.vpHeightScale * (1.0f / fabsf(gstate.getViewportYScale()));
746
float du = 1.0f / gstate_c.curTextureWidth;
747
float dv = 1.0f / gstate_c.curTextureHeight;
748
749
if (throughmode) {
750
dx = 1.0f;
751
dy = 1.0f;
752
}
753
754
numDecodedVerts = 4 * (vertexCount / 2);
755
756
if (PSP_CoreParameter().compat.flags().CenteredLines) {
757
// Lines meant to be pretty in 3D like in Echochrome.
758
759
// We expand them in both directions for symmetry, so we need to halve the expansion.
760
dx *= 0.5f;
761
dy *= 0.5f;
762
763
for (int i = 0; i < vertexCount; i += 2) {
764
const TransformedVertex &transVtx1 = transformed[indsIn[i + 0]];
765
const TransformedVertex &transVtx2 = transformed[indsIn[i + 1]];
766
767
// Okay, let's calculate the perpendicular.
768
float horizontal = transVtx2.x * transVtx2.pos_w - transVtx1.x * transVtx1.pos_w;
769
float vertical = transVtx2.y * transVtx2.pos_w - transVtx1.y * transVtx1.pos_w;
770
771
Vec2f addWidth = Vec2f(-vertical, horizontal).Normalized();
772
773
float xoff = addWidth.x * dx;
774
float yoff = addWidth.y * dy;
775
776
// bottom right
777
trans[0].CopyFromWithOffset(transVtx2, xoff * transVtx2.pos_w, yoff * transVtx2.pos_w);
778
// top right
779
trans[1].CopyFromWithOffset(transVtx1, xoff * transVtx1.pos_w, yoff * transVtx1.pos_w);
780
// top left
781
trans[2].CopyFromWithOffset(transVtx1, -xoff * transVtx1.pos_w, -yoff * transVtx1.pos_w);
782
// bottom left
783
trans[3].CopyFromWithOffset(transVtx2, -xoff * transVtx2.pos_w, -yoff * transVtx2.pos_w);
784
785
// Triangle: BR-TR-TL
786
indsOut[0] = i * 2 + 0;
787
indsOut[1] = i * 2 + 1;
788
indsOut[2] = i * 2 + 2;
789
// Triangle: BL-BR-TL
790
indsOut[3] = i * 2 + 3;
791
indsOut[4] = i * 2 + 0;
792
indsOut[5] = i * 2 + 2;
793
trans += 4;
794
indsOut += 6;
795
796
numTrans += 6;
797
}
798
} else {
799
// Lines meant to be as closely compatible with upscaled 2D drawing as possible.
800
// We use this as default.
801
802
for (int i = 0; i < vertexCount; i += 2) {
803
const TransformedVertex &transVtx1 = transformed[indsIn[i + 0]];
804
const TransformedVertex &transVtx2 = transformed[indsIn[i + 1]];
805
806
const TransformedVertex &transVtxT = transVtx1.y <= transVtx2.y ? transVtx1 : transVtx2;
807
const TransformedVertex &transVtxB = transVtx1.y <= transVtx2.y ? transVtx2 : transVtx1;
808
const TransformedVertex &transVtxL = transVtx1.x <= transVtx2.x ? transVtx1 : transVtx2;
809
const TransformedVertex &transVtxR = transVtx1.x <= transVtx2.x ? transVtx2 : transVtx1;
810
811
// Sort the points so our perpendicular will bias the right direction.
812
const TransformedVertex &transVtxTL = (transVtxT.y != transVtxB.y || transVtxT.x > transVtxB.x) ? transVtxT : transVtxB;
813
const TransformedVertex &transVtxBL = (transVtxT.y != transVtxB.y || transVtxT.x > transVtxB.x) ? transVtxB : transVtxT;
814
815
// Okay, let's calculate the perpendicular.
816
float horizontal = transVtxTL.x * transVtxTL.pos_w - transVtxBL.x * transVtxBL.pos_w;
817
float vertical = transVtxTL.y * transVtxTL.pos_w - transVtxBL.y * transVtxBL.pos_w;
818
Vec2f addWidth = Vec2f(-vertical, horizontal).Normalized();
819
820
// bottom right
821
trans[0] = transVtxBL;
822
trans[0].x += addWidth.x * dx * trans[0].pos_w;
823
trans[0].y += addWidth.y * dy * trans[0].pos_w;
824
trans[0].u += addWidth.x * du * trans[0].uv_w;
825
trans[0].v += addWidth.y * dv * trans[0].uv_w;
826
827
// top right
828
trans[1] = transVtxTL;
829
trans[1].x += addWidth.x * dx * trans[1].pos_w;
830
trans[1].y += addWidth.y * dy * trans[1].pos_w;
831
trans[1].u += addWidth.x * du * trans[1].uv_w;
832
trans[1].v += addWidth.y * dv * trans[1].uv_w;
833
834
// top left
835
trans[2] = transVtxTL;
836
837
// bottom left
838
trans[3] = transVtxBL;
839
840
// Triangle: BR-TR-TL
841
indsOut[0] = i * 2 + 0;
842
indsOut[1] = i * 2 + 1;
843
indsOut[2] = i * 2 + 2;
844
// Triangle: BL-BR-TL
845
indsOut[3] = i * 2 + 3;
846
indsOut[4] = i * 2 + 0;
847
indsOut[5] = i * 2 + 2;
848
trans += 4;
849
indsOut += 6;
850
851
numTrans += 6;
852
}
853
}
854
855
inds = newInds;
856
return true;
857
}
858
859
bool SoftwareTransform::ExpandPoints(int vertexCount, int &maxIndex, int vertsSize, u16 *&inds, int indsSize, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int &numTrans, bool throughmode) {
860
// Before we start, do a sanity check - does the output fit?
861
if (vertexCount * 6 > indsSize) {
862
// Won't fit, kill the draw.
863
return false;
864
}
865
if (vertexCount * 4 > vertsSize) {
866
// Won't fit, kill the draw.
867
return false;
868
}
869
870
numTrans = 0;
871
TransformedVertex *trans = &transformedExpanded[0];
872
873
const u16 *indsIn = (const u16 *)inds;
874
u16 *newInds = inds + vertexCount;
875
u16 *indsOut = newInds;
876
877
float dx = 1.0f * gstate_c.vpWidthScale * (1.0f / gstate.getViewportXScale());
878
float dy = 1.0f * gstate_c.vpHeightScale * (1.0f / gstate.getViewportYScale());
879
float du = 1.0f / gstate_c.curTextureWidth;
880
float dv = 1.0f / gstate_c.curTextureHeight;
881
882
if (throughmode) {
883
dx = 1.0f;
884
dy = 1.0f;
885
}
886
887
maxIndex = 4 * vertexCount;
888
for (int i = 0; i < vertexCount; ++i) {
889
const TransformedVertex &transVtxTL = transformed[indsIn[i]];
890
891
// Create the bottom right version.
892
TransformedVertex transVtxBR = transVtxTL;
893
transVtxBR.x += dx * transVtxTL.pos_w;
894
transVtxBR.y += dy * transVtxTL.pos_w;
895
transVtxBR.u += du * transVtxTL.uv_w;
896
transVtxBR.v += dv * transVtxTL.uv_w;
897
898
// We have to turn the rectangle into two triangles, so 6 points.
899
// This is 4 verts + 6 indices.
900
901
// bottom right
902
trans[0] = transVtxBR;
903
904
// top right
905
trans[1] = transVtxBR;
906
trans[1].y = transVtxTL.y;
907
trans[1].v = transVtxTL.v;
908
909
// top left
910
trans[2] = transVtxBR;
911
trans[2].x = transVtxTL.x;
912
trans[2].y = transVtxTL.y;
913
trans[2].u = transVtxTL.u;
914
trans[2].v = transVtxTL.v;
915
916
// bottom left
917
trans[3] = transVtxBR;
918
trans[3].x = transVtxTL.x;
919
trans[3].u = transVtxTL.u;
920
921
// Triangle: BR-TR-TL
922
indsOut[0] = i * 4 + 0;
923
indsOut[1] = i * 4 + 1;
924
indsOut[2] = i * 4 + 2;
925
// Triangle: BL-BR-TL
926
indsOut[3] = i * 4 + 3;
927
indsOut[4] = i * 4 + 0;
928
indsOut[5] = i * 4 + 2;
929
trans += 4;
930
indsOut += 6;
931
932
numTrans += 6;
933
}
934
inds = newInds;
935
return true;
936
}
937
938