Skip to content

Commit c8dccd4

Browse files
committed
Provoking vertex (software transform): Simpler solution
Simply rotate each primitive in the index buffer to simulate a different provoking vertex. Since at this point we have already generated a plain primitive index buffer, it's easy to manipulate like this. An even better solution would be to generate rotated index buffers directly during decode, although that code is super critical and does not need more complexity.. We could now also enable this for hardware transform but I'm leaving that for later.
1 parent a69d92c commit c8dccd4

File tree

7 files changed

+63
-74
lines changed

7 files changed

+63
-74
lines changed

GPU/Common/SoftwareTransformCommon.cpp

Lines changed: 35 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -125,33 +125,6 @@ static bool IsReallyAClear(const TransformedVertex *transformed, int numVerts, f
125125
return true;
126126
}
127127

128-
static int ColorIndexOffset(int prim, GEShadeMode shadeMode, bool clearMode) {
129-
if (shadeMode != GE_SHADE_FLAT || clearMode) {
130-
return 0;
131-
}
132-
133-
switch (prim) {
134-
case GE_PRIM_LINES:
135-
case GE_PRIM_LINE_STRIP:
136-
return 1;
137-
138-
case GE_PRIM_TRIANGLES:
139-
case GE_PRIM_TRIANGLE_STRIP:
140-
return 2;
141-
142-
case GE_PRIM_TRIANGLE_FAN:
143-
return 1;
144-
145-
case GE_PRIM_RECTANGLES:
146-
// We already use BR color when expanding, so no need to offset.
147-
return 0;
148-
149-
default:
150-
break;
151-
}
152-
return 0;
153-
}
154-
155128
void SoftwareTransform::SetProjMatrix(const float mtx[14], bool invertedX, bool invertedY, const Lin::Vec3 &trans, const Lin::Vec3 &scale) {
156129
memcpy(&projMatrix_.m, mtx, 16 * sizeof(float));
157130

@@ -202,11 +175,6 @@ void SoftwareTransform::Transform(int prim, u32 vertType, const DecVtxFormat &de
202175
fog_slope = std::signbit(fog_slope) ? -65535.0f : 65535.0f;
203176
}
204177

205-
int provokeIndOffset = 0;
206-
if (!params_.provokingVertexLast) {
207-
provokeIndOffset = ColorIndexOffset(prim, gstate.getShadeMode(), gstate.isModeClear());
208-
}
209-
210178
VertexReader reader(decoded, decVtxFormat, vertType);
211179
if (throughmode) {
212180
const u32 materialAmbientRGBA = gstate.getMaterialAmbientRGBA();
@@ -221,13 +189,7 @@ void SoftwareTransform::Transform(int prim, u32 vertType, const DecVtxFormat &de
221189
vert.pos_w = 1.0f;
222190

223191
if (hasColor) {
224-
if (provokeIndOffset != 0 && index + provokeIndOffset < numDecodedVerts) {
225-
reader.Goto(index + provokeIndOffset);
226-
vert.color0_32 = reader.ReadColor0_8888();
227-
reader.Goto(index);
228-
} else {
229-
vert.color0_32 = reader.ReadColor0_8888();
230-
}
192+
vert.color0_32 = reader.ReadColor0_8888();
231193
} else {
232194
vert.color0_32 = materialAmbientRGBA;
233195
}
@@ -268,10 +230,7 @@ void SoftwareTransform::Transform(int prim, u32 vertType, const DecVtxFormat &de
268230
if (reader.hasUV())
269231
reader.ReadUV(ruv);
270232

271-
// Read all the provoking vertex values here.
272233
Vec4f unlitColor;
273-
if (provokeIndOffset != 0 && index + provokeIndOffset < numDecodedVerts)
274-
reader.Goto(index + provokeIndOffset);
275234
if (reader.hasColor0())
276235
reader.ReadColor0(unlitColor.AsArray());
277236
else
@@ -342,34 +301,14 @@ void SoftwareTransform::Transform(int prim, u32 vertType, const DecVtxFormat &de
342301
break;
343302

344303
case GE_PROJMAP_NORMALIZED_NORMAL: // Use normalized normal as source
345-
// Flat uses the vertex normal, not provoking.
346-
if (provokeIndOffset == 0) {
347-
source = normal.Normalized(cpu_info.bSSE4_1);
348-
} else {
349-
reader.Goto(index);
350-
if (reader.hasNormal())
351-
reader.ReadNrm(source.AsArray());
352-
if (gstate.areNormalsReversed())
353-
source = -source;
354-
source.Normalize();
355-
}
304+
source = normal.Normalized(cpu_info.bSSE4_1);
356305
if (!reader.hasNormal()) {
357306
ERROR_LOG_REPORT(Log::G3D, "Normal projection mapping without normal?");
358307
}
359308
break;
360309

361310
case GE_PROJMAP_NORMAL: // Use non-normalized normal as source!
362-
// Flat uses the vertex normal, not provoking.
363-
if (provokeIndOffset == 0) {
364-
source = normal;
365-
} else {
366-
// Need to read the normal for this vertex and weight it again..
367-
reader.Goto(index);
368-
if (reader.hasNormal())
369-
reader.ReadNrm(source.AsArray());
370-
if (gstate.areNormalsReversed())
371-
source = -source;
372-
}
311+
source = normal;
373312
if (!reader.hasNormal()) {
374313
ERROR_LOG_REPORT(Log::G3D, "Normal projection mapping without normal?");
375314
}
@@ -751,6 +690,38 @@ bool SoftwareTransform::ExpandRectangles(int vertexCount, int &numDecodedVerts,
751690
return true;
752691
}
753692

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+
754725
bool SoftwareTransform::ExpandLines(int vertexCount, int &numDecodedVerts, int vertsSize, u16 *&inds, int indsSize, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int &numTrans, bool throughmode) {
755726
// Before we start, do a sanity check - does the output fit?
756727
if ((vertexCount / 2) * 6 > indsSize) {

GPU/Common/SoftwareTransformCommon.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,13 @@ struct SoftwareTransformParams {
5757
bool allowSeparateAlphaClear;
5858
bool flippedY;
5959
bool usesHalfZ;
60-
bool provokingVertexLast;
6160
};
6261

62+
// Converts an index buffer to make the provoking vertex the last.
63+
// In-place. So, better not be doing this on GPU memory!
64+
// TODO: We could do this already during index decode.
65+
void IndexBufferProvokingLastToFirst(int prim, u16 *inds, int indsSize);
66+
6367
class SoftwareTransform {
6468
public:
6569
SoftwareTransform(SoftwareTransformParams &params) : params_(params) {}

GPU/D3D11/D3D11Util.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ std::vector<uint8_t> CompileShaderToBytecodeD3D11(const char *code, size_t codeS
3636
if (trimmed.find("pow(f, e) will not work for negative f") != std::string::npos) {
3737
continue;
3838
}
39-
WARN_LOG(Log::G3D, "%.*s", (int)trimmed.length(), trimmed.data());
39+
if (trimmed.size() > 1) { // ignore single nulls, not sure how they appear.
40+
WARN_LOG(Log::G3D, "%.*s", (int)trimmed.length(), trimmed.data());
41+
}
4042
}
4143
} else {
4244
ERROR_LOG(Log::G3D, "%s: %s\n\n%s", "errors", errors.c_str(), numberedCode.c_str());

GPU/D3D11/DrawEngineD3D11.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,10 +382,15 @@ void DrawEngineD3D11::DoFlush() {
382382
params.texCache = textureCache_;
383383
params.allowClear = true;
384384
params.allowSeparateAlphaClear = false; // D3D11 doesn't support separate alpha clears
385-
params.provokingVertexLast = false;
386385
params.flippedY = false;
387386
params.usesHalfZ = true;
388387

388+
if (gstate.getShadeMode() == GE_SHADE_FLAT) {
389+
// We need to rotate the index buffer to simulate a different provoking vertex.
390+
// We do this before line expansion etc.
391+
IndexBufferProvokingLastToFirst(prim, inds, vertexCount);
392+
}
393+
389394
// We need correct viewport values in gstate_c already.
390395
if (gstate_c.IsDirty(DIRTY_VIEWPORTSCISSOR_STATE)) {
391396
ViewportAndScissor vpAndScissor;

GPU/Directx9/DrawEngineDX9.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,10 +334,16 @@ void DrawEngineDX9::DoFlush() {
334334
params.texCache = textureCache_;
335335
params.allowClear = true;
336336
params.allowSeparateAlphaClear = false;
337-
params.provokingVertexLast = false;
338337
params.flippedY = false;
339338
params.usesHalfZ = true;
340339

340+
if (gstate.getShadeMode() == GE_SHADE_FLAT) {
341+
// We need to rotate the index buffer to simulate a different provoking vertex.
342+
// We do this before line expansion etc.
343+
int indexCount = RemainingIndices(inds);
344+
IndexBufferProvokingLastToFirst(prim, inds, vertexCount);
345+
}
346+
341347
// We need correct viewport values in gstate_c already.
342348
if (gstate_c.IsDirty(DIRTY_VIEWPORTSCISSOR_STATE)) {
343349
ViewportAndScissor vpAndScissor;

GPU/GLES/DrawEngineGLES.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,6 @@ void DrawEngineGLES::DoFlush() {
356356
params.texCache = textureCache_;
357357
params.allowClear = true; // Clear in OpenGL respects scissor rects, so we'll use it.
358358
params.allowSeparateAlphaClear = true;
359-
params.provokingVertexLast = true;
360359
params.flippedY = framebufferManager_->UseBufferedRendering();
361360
params.usesHalfZ = false;
362361

GPU/Vulkan/DrawEngineVulkan.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -405,11 +405,13 @@ void DrawEngineVulkan::DoFlush() {
405405
// do not respect scissor rects.
406406
params.allowClear = framebufferManager_->UseBufferedRendering();
407407
params.allowSeparateAlphaClear = false;
408-
if (renderManager->GetVulkanContext()->GetDeviceFeatures().enabled.provokingVertex.provokingVertexLast) {
409-
// We can get the OpenGL behavior, no need for workarounds.
410-
params.provokingVertexLast = true;
411-
} else {
412-
params.provokingVertexLast = false;
408+
409+
if (gstate.getShadeMode() == GE_SHADE_FLAT) {
410+
if (!renderManager->GetVulkanContext()->GetDeviceFeatures().enabled.provokingVertex.provokingVertexLast) {
411+
// If we can't have the hardware do it, we need to rotate the index buffer to simulate a different provoking vertex.
412+
// We do this before line expansion etc.
413+
IndexBufferProvokingLastToFirst(prim, inds, vertexCount);
414+
}
413415
}
414416
params.flippedY = true;
415417
params.usesHalfZ = true;

0 commit comments

Comments
 (0)