-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
TransformUnit.cpp
736 lines (627 loc) · 22.8 KB
/
TransformUnit.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
// Copyright (c) 2013- PPSSPP Project.
// This program 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.0 or later versions.
// This program 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. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <cmath>
#include <algorithm>
#include "Common/CPUDetect.h"
#include "Common/Math/math_util.h"
#include "Common/MemoryUtil.h"
#include "Common/Profiler/Profiler.h"
#include "Core/Config.h"
#include "GPU/GPUState.h"
#include "GPU/Common/DrawEngineCommon.h"
#include "GPU/Common/VertexDecoderCommon.h"
#include "GPU/Common/SplineCommon.h"
#include "GPU/Common/TextureDecoder.h"
#include "GPU/Debugger/Debugger.h"
#include "GPU/Software/Clipper.h"
#include "GPU/Software/FuncId.h"
#include "GPU/Software/Lighting.h"
#include "GPU/Software/Rasterizer.h"
#include "GPU/Software/RasterizerRectangle.h"
#include "GPU/Software/TransformUnit.h"
#define TRANSFORM_BUF_SIZE (65536 * 48)
TransformUnit::TransformUnit() {
decoded_ = (u8 *)AllocateMemoryPages(TRANSFORM_BUF_SIZE, MEM_PROT_READ | MEM_PROT_WRITE);
}
TransformUnit::~TransformUnit() {
FreeMemoryPages(decoded_, DECODED_VERTEX_BUFFER_SIZE);
}
SoftwareDrawEngine::SoftwareDrawEngine() {
// All this is a LOT of memory, need to see if we can cut down somehow. Used for splines.
decoded = (u8 *)AllocateMemoryPages(DECODED_VERTEX_BUFFER_SIZE, MEM_PROT_READ | MEM_PROT_WRITE);
decIndex = (u16 *)AllocateMemoryPages(DECODED_INDEX_BUFFER_SIZE, MEM_PROT_READ | MEM_PROT_WRITE);
}
SoftwareDrawEngine::~SoftwareDrawEngine() {
FreeMemoryPages(decoded, DECODED_VERTEX_BUFFER_SIZE);
FreeMemoryPages(decIndex, DECODED_INDEX_BUFFER_SIZE);
}
void SoftwareDrawEngine::DispatchFlush() {
}
void SoftwareDrawEngine::DispatchSubmitPrim(void *verts, void *inds, GEPrimitiveType prim, int vertexCount, u32 vertTypeID, int cullMode, int *bytesRead) {
_assert_msg_(cullMode == gstate.getCullMode(), "Mixed cull mode not supported.");
transformUnit.SubmitPrimitive(verts, inds, prim, vertexCount, vertTypeID, bytesRead, this);
}
VertexDecoder *SoftwareDrawEngine::FindVertexDecoder(u32 vtype) {
const u32 vertTypeID = (vtype & 0xFFFFFF) | (gstate.getUVGenMode() << 24);
return DrawEngineCommon::GetVertexDecoder(vertTypeID);
}
WorldCoords TransformUnit::ModelToWorld(const ModelCoords &coords) {
return Vec3ByMatrix43(coords, gstate.worldMatrix);
}
WorldCoords TransformUnit::ModelToWorldNormal(const ModelCoords &coords) {
return Norm3ByMatrix43(coords, gstate.worldMatrix);
}
ViewCoords TransformUnit::WorldToView(const WorldCoords &coords) {
return Vec3ByMatrix43(coords, gstate.viewMatrix);
}
ClipCoords TransformUnit::ViewToClip(const ViewCoords &coords) {
return Vec3ByMatrix44(coords, gstate.projMatrix);
}
static inline ScreenCoords ClipToScreenInternal(const ClipCoords& coords, bool *outside_range_flag) {
ScreenCoords ret;
// Parameters here can seem invalid, but the PSP is fine with negative viewport widths etc.
// The checking that OpenGL and D3D do is actually quite superflous as the calculations still "work"
// with some pretty crazy inputs, which PSP games are happy to do at times.
float xScale = gstate.getViewportXScale();
float xCenter = gstate.getViewportXCenter();
float yScale = gstate.getViewportYScale();
float yCenter = gstate.getViewportYCenter();
float zScale = gstate.getViewportZScale();
float zCenter = gstate.getViewportZCenter();
float x = coords.x * xScale / coords.w + xCenter;
float y = coords.y * yScale / coords.w + yCenter;
float z = coords.z * zScale / coords.w + zCenter;
// Account for rounding for X and Y.
// TODO: Validate actual rounding range.
const float SCREEN_BOUND = 4095.0f + (15.5f / 16.0f);
// This matches hardware tests - depth is clamped when this flag is on.
if (gstate.isDepthClampEnabled()) {
// Note: if the depth is clipped (z/w <= -1.0), the outside_range_flag should NOT be set, even for x and y.
if (outside_range_flag && coords.z > -coords.w && (x >= SCREEN_BOUND || y >= SCREEN_BOUND || x < 0 || y < 0)) {
*outside_range_flag = true;
}
if (z < 0.f)
z = 0.f;
else if (z > 65535.0f)
z = 65535.0f;
} else if (outside_range_flag && (x > SCREEN_BOUND || y >= SCREEN_BOUND || x < 0 || y < 0)) {
*outside_range_flag = true;
}
// 16 = 0xFFFF / 4095.9375
// Round up at 0.625 to the nearest subpixel.
return ScreenCoords(x * 16.0f + 0.375f, y * 16.0f + 0.375f, z);
}
ScreenCoords TransformUnit::ClipToScreen(const ClipCoords& coords)
{
return ClipToScreenInternal(coords, nullptr);
}
DrawingCoords TransformUnit::ScreenToDrawing(const ScreenCoords& coords)
{
DrawingCoords ret;
// TODO: What to do when offset > coord?
ret.x = ((s32)coords.x - gstate.getOffsetX16()) / 16;
ret.y = ((s32)coords.y - gstate.getOffsetY16()) / 16;
ret.z = coords.z;
return ret;
}
ScreenCoords TransformUnit::DrawingToScreen(const DrawingCoords& coords)
{
ScreenCoords ret;
ret.x = (u32)coords.x * 16 + gstate.getOffsetX16();
ret.y = (u32)coords.y * 16 + gstate.getOffsetY16();
ret.z = coords.z;
return ret;
}
VertexData TransformUnit::ReadVertex(VertexReader &vreader, bool &outside_range_flag) {
PROFILE_THIS_SCOPE("read_vert");
VertexData vertex;
ModelCoords pos;
// VertexDecoder normally scales z, but we want it unscaled.
vreader.ReadPosThroughZ16(pos.AsArray());
if (!gstate.isModeClear() && gstate.isTextureMapEnabled() && vreader.hasUV()) {
vreader.ReadUV(vertex.texturecoords.AsArray());
}
if (vreader.hasNormal()) {
vreader.ReadNrm(vertex.normal.AsArray());
if (gstate.areNormalsReversed())
vertex.normal = -vertex.normal;
}
if (vertTypeIsSkinningEnabled(gstate.vertType) && !gstate.isModeThrough()) {
float W[8] = { 1.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f };
vreader.ReadWeights(W);
Vec3<float> tmppos(0.f, 0.f, 0.f);
Vec3<float> tmpnrm(0.f, 0.f, 0.f);
for (int i = 0; i < vertTypeGetNumBoneWeights(gstate.vertType); ++i) {
Vec3<float> step = Vec3ByMatrix43(pos, gstate.boneMatrix + i * 12);
tmppos += step * W[i];
if (vreader.hasNormal()) {
step = Norm3ByMatrix43(vertex.normal, gstate.boneMatrix + i * 12);
tmpnrm += step * W[i];
}
}
pos = tmppos;
if (vreader.hasNormal())
vertex.normal = tmpnrm;
}
if (vreader.hasColor0()) {
float col[4];
vreader.ReadColor0(col);
vertex.color0 = Vec4<int>(col[0]*255, col[1]*255, col[2]*255, col[3]*255);
} else {
vertex.color0 = Vec4<int>::FromRGBA(gstate.getMaterialAmbientRGBA());
}
if (vreader.hasColor1()) {
float col[3];
vreader.ReadColor1(col);
vertex.color1 = Vec3<int>(col[0]*255, col[1]*255, col[2]*255);
} else {
vertex.color1 = Vec3<int>(0, 0, 0);
}
if (!gstate.isModeThrough()) {
vertex.modelpos = pos;
vertex.worldpos = WorldCoords(TransformUnit::ModelToWorld(vertex.modelpos));
ModelCoords viewpos = TransformUnit::WorldToView(vertex.worldpos);
vertex.clippos = ClipCoords(TransformUnit::ViewToClip(viewpos));
if (gstate.isFogEnabled()) {
float fog_end = getFloat24(gstate.fog1);
float fog_slope = getFloat24(gstate.fog2);
// Same fixup as in ShaderManagerGLES.cpp
if (my_isnanorinf(fog_end)) {
// Not really sure what a sensible value might be, but let's try 64k.
fog_end = std::signbit(fog_end) ? -65535.0f : 65535.0f;
}
if (my_isnanorinf(fog_slope)) {
fog_slope = std::signbit(fog_slope) ? -65535.0f : 65535.0f;
}
vertex.fogdepth = (viewpos.z + fog_end) * fog_slope;
} else {
vertex.fogdepth = 1.0f;
}
vertex.screenpos = ClipToScreenInternal(vertex.clippos, &outside_range_flag);
if (vreader.hasNormal()) {
vertex.worldnormal = TransformUnit::ModelToWorldNormal(vertex.normal);
vertex.worldnormal.NormalizeOr001();
} else {
vertex.worldnormal = Vec3<float>(0.0f, 0.0f, 1.0f);
}
// Time to generate some texture coords. Lighting will handle shade mapping.
if (gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX) {
Vec3f source;
switch (gstate.getUVProjMode()) {
case GE_PROJMAP_POSITION:
source = vertex.modelpos;
break;
case GE_PROJMAP_UV:
source = Vec3f(vertex.texturecoords, 0.0f);
break;
case GE_PROJMAP_NORMALIZED_NORMAL:
source = vertex.normal.NormalizedOr001(cpu_info.bSSE4_1);
break;
case GE_PROJMAP_NORMAL:
source = vertex.normal;
break;
default:
source = Vec3f::AssignToAll(0.0f);
ERROR_LOG_REPORT(G3D, "Software: Unsupported UV projection mode %x", gstate.getUVProjMode());
break;
}
// TODO: What about uv scale and offset?
Vec3<float> stq = Vec3ByMatrix43(source, gstate.tgenMatrix);
float z_recip = 1.0f / stq.z;
vertex.texturecoords = Vec2f(stq.x * z_recip, stq.y * z_recip);
} else if (gstate.getUVGenMode() == GE_TEXMAP_ENVIRONMENT_MAP) {
Lighting::GenerateLightST(vertex);
}
PROFILE_THIS_SCOPE("light");
if (gstate.isLightingEnabled())
Lighting::Process(vertex, vreader.hasColor0());
} else {
vertex.screenpos.x = (int)(pos[0] * 16) + gstate.getOffsetX16();
vertex.screenpos.y = (int)(pos[1] * 16) + gstate.getOffsetY16();
vertex.screenpos.z = pos[2];
vertex.clippos.w = 1.f;
vertex.fogdepth = 1.f;
}
return vertex;
}
#define START_OPEN_U 1
#define END_OPEN_U 2
#define START_OPEN_V 4
#define END_OPEN_V 8
struct SplinePatch {
VertexData points[16];
int type;
int pad[3];
};
void TransformUnit::SubmitPrimitive(void* vertices, void* indices, GEPrimitiveType prim_type, int vertex_count, u32 vertex_type, int *bytesRead, SoftwareDrawEngine *drawEngine)
{
VertexDecoder &vdecoder = *drawEngine->FindVertexDecoder(vertex_type);
const DecVtxFormat &vtxfmt = vdecoder.GetDecVtxFmt();
if (bytesRead)
*bytesRead = vertex_count * vdecoder.VertexSize();
// Frame skipping.
if (gstate_c.skipDrawReason & SKIPDRAW_SKIPFRAME) {
return;
}
u16 index_lower_bound = 0;
u16 index_upper_bound = vertex_count - 1;
IndexConverter ConvertIndex(vertex_type, indices);
if (indices)
GetIndexBounds(indices, vertex_count, vertex_type, &index_lower_bound, &index_upper_bound);
vdecoder.DecodeVerts(decoded_, vertices, index_lower_bound, index_upper_bound);
VertexReader vreader(decoded_, vtxfmt, vertex_type);
static VertexData data[4]; // Normally max verts per prim is 3, but we temporarily need 4 to detect rectangles from strips.
// This is the index of the next vert in data (or higher, may need modulus.)
static int data_index = 0;
static GEPrimitiveType prev_prim = GE_PRIM_POINTS;
if (prim_type != GE_PRIM_KEEP_PREVIOUS) {
data_index = 0;
prev_prim = prim_type;
} else {
prim_type = prev_prim;
}
int vtcs_per_prim;
switch (prim_type) {
case GE_PRIM_POINTS: vtcs_per_prim = 1; break;
case GE_PRIM_LINES: vtcs_per_prim = 2; break;
case GE_PRIM_TRIANGLES: vtcs_per_prim = 3; break;
case GE_PRIM_RECTANGLES: vtcs_per_prim = 2; break;
default: vtcs_per_prim = 0; break;
}
// TODO: Do this in two passes - first process the vertices (before indexing/stripping),
// then resolve the indices. This lets us avoid transforming shared vertices twice.
Rasterizer::RasterizerState state;
ComputeRasterizerState(&state);
bool outside_range_flag = false;
switch (prim_type) {
case GE_PRIM_POINTS:
case GE_PRIM_LINES:
case GE_PRIM_TRIANGLES:
{
for (int vtx = 0; vtx < vertex_count; ++vtx) {
if (indices) {
vreader.Goto(ConvertIndex(vtx) - index_lower_bound);
} else {
vreader.Goto(vtx);
}
data[data_index++] = ReadVertex(vreader, outside_range_flag);
if (data_index < vtcs_per_prim) {
// Keep reading. Note: an incomplete prim will stay read for GE_PRIM_KEEP_PREVIOUS.
continue;
}
// Okay, we've got enough verts. Reset the index for next time.
data_index = 0;
if (outside_range_flag) {
// Cull the prim if it was outside, and move to the next prim.
outside_range_flag = false;
continue;
}
switch (prim_type) {
case GE_PRIM_TRIANGLES:
{
if (!gstate.isCullEnabled() || gstate.isModeClear()) {
Clipper::ProcessTriangle(data[0], data[1], data[2], data[2], state);
Clipper::ProcessTriangle(data[2], data[1], data[0], data[2], state);
} else if (!gstate.getCullMode()) {
Clipper::ProcessTriangle(data[2], data[1], data[0], data[2], state);
} else {
Clipper::ProcessTriangle(data[0], data[1], data[2], data[2], state);
}
break;
}
case GE_PRIM_LINES:
Clipper::ProcessLine(data[0], data[1], state);
break;
case GE_PRIM_POINTS:
Clipper::ProcessPoint(data[0], state);
break;
default:
_dbg_assert_msg_(false, "Unexpected prim type: %d", prim_type);
}
}
break;
}
case GE_PRIM_RECTANGLES:
for (int vtx = 0; vtx < vertex_count; ++vtx) {
if (indices) {
vreader.Goto(ConvertIndex(vtx) - index_lower_bound);
} else {
vreader.Goto(vtx);
}
data[data_index++] = ReadVertex(vreader, outside_range_flag);
if (outside_range_flag) {
outside_range_flag = false;
// Note: this is the post increment index. If odd, we set the first vert.
if (data_index & 1) {
// Skip the next one and forget this one.
vtx++;
data_index--;
} else {
// Forget both of the last 2.
data_index -= 2;
}
}
if (data_index == 4 && gstate.isModeThrough()) {
if (Rasterizer::DetectRectangleSlices(data)) {
data[1] = data[3];
data_index = 2;
}
}
if (data_index == 4) {
Clipper::ProcessRect(data[0], data[1], state);
Clipper::ProcessRect(data[2], data[3], state);
data_index = 0;
}
}
if (data_index >= 2) {
Clipper::ProcessRect(data[0], data[1], state);
data_index -= 2;
}
break;
case GE_PRIM_LINE_STRIP:
{
// Don't draw a line when loading the first vertex.
// If data_index is 1 or 2, etc., it means we're continuing a line strip.
int skip_count = data_index == 0 ? 1 : 0;
for (int vtx = 0; vtx < vertex_count; ++vtx) {
if (indices) {
vreader.Goto(ConvertIndex(vtx) - index_lower_bound);
} else {
vreader.Goto(vtx);
}
data[(data_index++) & 1] = ReadVertex(vreader, outside_range_flag);
if (outside_range_flag) {
// Drop all primitives containing the current vertex
skip_count = 2;
outside_range_flag = false;
continue;
}
if (skip_count) {
--skip_count;
} else {
// We already incremented data_index, so data_index & 1 is previous one.
Clipper::ProcessLine(data[data_index & 1], data[(data_index & 1) ^ 1], state);
}
}
break;
}
case GE_PRIM_TRIANGLE_STRIP:
{
// Don't draw a triangle when loading the first two vertices.
int skip_count = data_index >= 2 ? 0 : 2 - data_index;
// If index count == 4, check if we can convert to a rectangle.
// This is for Darkstalkers (and should speed up many 2D games).
if (data_index == 0 && vertex_count == 4 && gstate.isModeThrough()) {
for (int vtx = 0; vtx < 4; ++vtx) {
if (indices) {
vreader.Goto(ConvertIndex(vtx) - index_lower_bound);
}
else {
vreader.Goto(vtx);
}
data[vtx] = ReadVertex(vreader, outside_range_flag);
}
// If a strip is effectively a rectangle, draw it as such!
if (!outside_range_flag && Rasterizer::DetectRectangleFromThroughModeStrip(data)) {
Clipper::ProcessRect(data[0], data[3], state);
break;
}
}
outside_range_flag = false;
for (int vtx = 0; vtx < vertex_count; ++vtx) {
if (indices) {
vreader.Goto(ConvertIndex(vtx) - index_lower_bound);
} else {
vreader.Goto(vtx);
}
int provoking_index = (data_index++) % 3;
data[provoking_index] = ReadVertex(vreader, outside_range_flag);
if (outside_range_flag) {
// Drop all primitives containing the current vertex
skip_count = 2;
outside_range_flag = false;
continue;
}
if (skip_count) {
--skip_count;
continue;
}
if (!gstate.isCullEnabled() || gstate.isModeClear()) {
Clipper::ProcessTriangle(data[0], data[1], data[2], data[provoking_index], state);
Clipper::ProcessTriangle(data[2], data[1], data[0], data[provoking_index], state);
} else if ((!gstate.getCullMode()) ^ ((data_index - 1) % 2)) {
// We need to reverse the vertex order for each second primitive,
// but we additionally need to do that for every primitive if CCW cullmode is used.
Clipper::ProcessTriangle(data[2], data[1], data[0], data[provoking_index], state);
} else {
Clipper::ProcessTriangle(data[0], data[1], data[2], data[provoking_index], state);
}
}
break;
}
case GE_PRIM_TRIANGLE_FAN:
{
// Don't draw a triangle when loading the first two vertices.
// (this doesn't count the central one.)
int skip_count = data_index <= 1 ? 1 : 0;
int start_vtx = 0;
// Only read the central vertex if we're not continuing.
if (data_index == 0) {
if (indices) {
vreader.Goto(ConvertIndex(0) - index_lower_bound);
} else {
vreader.Goto(0);
}
data[0] = ReadVertex(vreader, outside_range_flag);
data_index++;
start_vtx = 1;
// If the central vertex is outside range, all the points are toast.
if (outside_range_flag)
break;
}
if (data_index == 1 && vertex_count == 4 && gstate.isModeThrough()) {
for (int vtx = start_vtx; vtx < vertex_count; ++vtx) {
if (indices) {
vreader.Goto(ConvertIndex(vtx) - index_lower_bound);
} else {
vreader.Goto(vtx);
}
data[vtx] = ReadVertex(vreader, outside_range_flag);
}
int tl = -1, br = -1;
if (!outside_range_flag && Rasterizer::DetectRectangleFromThroughModeFan(data, vertex_count, &tl, &br)) {
Clipper::ProcessRect(data[tl], data[br], state);
break;
}
}
outside_range_flag = false;
for (int vtx = start_vtx; vtx < vertex_count; ++vtx) {
if (indices) {
vreader.Goto(ConvertIndex(vtx) - index_lower_bound);
} else {
vreader.Goto(vtx);
}
int provoking_index = 2 - ((data_index++) % 2);
data[provoking_index] = ReadVertex(vreader, outside_range_flag);
if (outside_range_flag) {
// Drop all primitives containing the current vertex
skip_count = 2;
outside_range_flag = false;
continue;
}
if (skip_count) {
--skip_count;
continue;
}
if (!gstate.isCullEnabled() || gstate.isModeClear()) {
Clipper::ProcessTriangle(data[0], data[1], data[2], data[provoking_index], state);
Clipper::ProcessTriangle(data[2], data[1], data[0], data[provoking_index], state);
} else if ((!gstate.getCullMode()) ^ ((data_index - 1) % 2)) {
// We need to reverse the vertex order for each second primitive,
// but we additionally need to do that for every primitive if CCW cullmode is used.
Clipper::ProcessTriangle(data[2], data[1], data[0], data[provoking_index], state);
} else {
Clipper::ProcessTriangle(data[0], data[1], data[2], data[provoking_index], state);
}
}
break;
}
default:
ERROR_LOG(G3D, "Unexpected prim type: %d", prim_type);
break;
}
GPUDebug::NotifyDraw();
}
// TODO: This probably is not the best interface.
// Also, we should try to merge this into the similar function in DrawEngineCommon.
bool TransformUnit::GetCurrentSimpleVertices(int count, std::vector<GPUDebugVertex> &vertices, std::vector<u16> &indices) {
// This is always for the current vertices.
u16 indexLowerBound = 0;
u16 indexUpperBound = count - 1;
if (count > 0 && (gstate.vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) {
const u8 *inds = Memory::GetPointer(gstate_c.indexAddr);
const u16_le *inds16 = (const u16_le *)inds;
const u32_le *inds32 = (const u32_le *)inds;
if (inds) {
GetIndexBounds(inds, count, gstate.vertType, &indexLowerBound, &indexUpperBound);
indices.resize(count);
switch (gstate.vertType & GE_VTYPE_IDX_MASK) {
case GE_VTYPE_IDX_8BIT:
for (int i = 0; i < count; ++i) {
indices[i] = inds[i];
}
break;
case GE_VTYPE_IDX_16BIT:
for (int i = 0; i < count; ++i) {
indices[i] = inds16[i];
}
break;
case GE_VTYPE_IDX_32BIT:
WARN_LOG_REPORT_ONCE(simpleIndexes32, G3D, "SimpleVertices: Decoding 32-bit indexes");
for (int i = 0; i < count; ++i) {
// These aren't documented and should be rare. Let's bounds check each one.
if (inds32[i] != (u16)inds32[i]) {
ERROR_LOG_REPORT_ONCE(simpleIndexes32Bounds, G3D, "SimpleVertices: Index outside 16-bit range");
}
indices[i] = (u16)inds32[i];
}
break;
}
} else {
indices.clear();
}
} else {
indices.clear();
}
static std::vector<u32> temp_buffer;
static std::vector<SimpleVertex> simpleVertices;
temp_buffer.resize(std::max((int)indexUpperBound, 8192) * 128 / sizeof(u32));
simpleVertices.resize(indexUpperBound + 1);
VertexDecoder vdecoder;
VertexDecoderOptions options{};
vdecoder.SetVertexType(gstate.vertType, options);
if (!Memory::IsValidRange(gstate_c.vertexAddr, (indexUpperBound + 1) * vdecoder.VertexSize()))
return false;
DrawEngineCommon::NormalizeVertices((u8 *)(&simpleVertices[0]), (u8 *)(&temp_buffer[0]), Memory::GetPointer(gstate_c.vertexAddr), &vdecoder, indexLowerBound, indexUpperBound, gstate.vertType);
float world[16];
float view[16];
float worldview[16];
float worldviewproj[16];
ConvertMatrix4x3To4x4(world, gstate.worldMatrix);
ConvertMatrix4x3To4x4(view, gstate.viewMatrix);
Matrix4ByMatrix4(worldview, world, view);
Matrix4ByMatrix4(worldviewproj, worldview, gstate.projMatrix);
vertices.resize(indexUpperBound + 1);
for (int i = indexLowerBound; i <= indexUpperBound; ++i) {
const SimpleVertex &vert = simpleVertices[i];
if (gstate.isModeThrough()) {
if (gstate.vertType & GE_VTYPE_TC_MASK) {
vertices[i].u = vert.uv[0];
vertices[i].v = vert.uv[1];
} else {
vertices[i].u = 0.0f;
vertices[i].v = 0.0f;
}
vertices[i].x = vert.pos.x;
vertices[i].y = vert.pos.y;
vertices[i].z = vert.pos.z;
} else {
float clipPos[4];
Vec3ByMatrix44(clipPos, vert.pos.AsArray(), worldviewproj);
ScreenCoords screenPos = ClipToScreen(clipPos);
DrawingCoords drawPos = ScreenToDrawing(screenPos);
if (gstate.vertType & GE_VTYPE_TC_MASK) {
vertices[i].u = vert.uv[0] * (float)gstate.getTextureWidth(0);
vertices[i].v = vert.uv[1] * (float)gstate.getTextureHeight(0);
} else {
vertices[i].u = 0.0f;
vertices[i].v = 0.0f;
}
vertices[i].x = drawPos.x;
vertices[i].y = drawPos.y;
vertices[i].z = drawPos.z;
}
if (gstate.vertType & GE_VTYPE_COL_MASK) {
memcpy(vertices[i].c, vert.color, sizeof(vertices[i].c));
} else {
memset(vertices[i].c, 0, sizeof(vertices[i].c));
}
vertices[i].nx = vert.nrm.x;
vertices[i].ny = vert.nrm.y;
vertices[i].nz = vert.nrm.z;
}
// The GE debugger expects these to be set.
gstate_c.curTextureWidth = gstate.getTextureWidth(0);
gstate_c.curTextureHeight = gstate.getTextureHeight(0);
return true;
}