From 1a2597a513f6894a7599a426a9b9e14a85a4c1db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 24 Sep 2020 01:56:20 +0200 Subject: [PATCH 1/6] SSE-optimize IndexGenerator::AddStrip. Shaves about half a percent from GoW. --- GPU/Common/IndexGenerator.cpp | 48 +++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/GPU/Common/IndexGenerator.cpp b/GPU/Common/IndexGenerator.cpp index 4bd0953c47..b0ef2fb0bb 100644 --- a/GPU/Common/IndexGenerator.cpp +++ b/GPU/Common/IndexGenerator.cpp @@ -17,6 +17,13 @@ #include +#include "CPUDetect.h" +#include "Common.h" + +#ifdef _M_SSE +#include +#endif + #include "IndexGenerator.h" // Points don't need indexing... @@ -82,12 +89,47 @@ void IndexGenerator::AddList(int numVerts, bool clockwise) { } } +inline __m128i mm_set_epi16_backwards(short w0, short w1, short w2, short w3, short w4, short w5, short w6, short w7) { + return _mm_set_epi16(w7, w6, w5, w4, w3, w2, w1, w0); +} + void IndexGenerator::AddStrip(int numVerts, bool clockwise) { int wind = clockwise ? 1 : 2; - const int numTris = numVerts - 2; + int numTris = numVerts - 2; u16 *outInds = inds_; int ibase = index_; - size_t numPairs = numTris / 2; + + int remainingTris = numTris; +#ifdef _M_SSE + // In an SSE2 register we can fit 8 16-bit integers. + // However, we need to output a multiple of 3 indices. + // The first such multiple is 24, which means we'll generate 24 indices per cycle, + // which corresponds to 8 triangles. That's pretty cool. + + int numChunks = numTris / 8; + if (numChunks) { + __m128i ibase8 = _mm_set1_epi16(ibase); + __m128i increment = _mm_set1_epi16(8); + // TODO: Precompute two sets of these depending on wind, and just load directly. + __m128i offsets0 = mm_set_epi16_backwards(0, 0 + wind, (wind ^ 3), /**/ 1, 1 + (wind ^ 3), 1 + wind, /**/ 2, 2 + wind); + __m128i offsets1 = mm_set_epi16_backwards(2 + (wind ^ 3), /**/ 3, 3 + (wind ^ 3), 3 + wind, /**/ 4, 4 + wind, 4 + (wind ^ 3), /**/ 5); + __m128i offsets2 = mm_set_epi16_backwards(5 + (wind ^ 3), 5 + wind, /**/ 6, 6 + wind, 6 + (wind ^ 3), /**/ 7, 7 + (wind ^ 3), 7 + wind); + __m128i *dst = (__m128i *)outInds; + for (int i = 0; i < numChunks; i++) { + _mm_storeu_si128(dst, _mm_add_epi16(ibase8, offsets0)); + _mm_storeu_si128(dst + 1, _mm_add_epi16(ibase8, offsets1)); + _mm_storeu_si128(dst + 2, _mm_add_epi16(ibase8, offsets2)); + ibase8 = _mm_add_epi16(ibase8, increment); + dst += 3; + } + remainingTris -= numChunks * 8; + outInds += numChunks * 24; + ibase += numChunks * 8; + } + // wind doesn't need to be updated, an even number of triangles have been drawn. +#endif + + size_t numPairs = remainingTris / 2; while (numPairs > 0) { *outInds++ = ibase; *outInds++ = ibase + wind; @@ -95,6 +137,8 @@ void IndexGenerator::AddStrip(int numVerts, bool clockwise) { *outInds++ = ibase + 1; *outInds++ = ibase + 1 + (wind ^ 3); *outInds++ = ibase + 1 + wind; + // *outInds++ = ibase + 2; + // *outInds++ = ibase + 2 + wind; ibase += 2; numPairs--; } From df9a5cc0f293a9bf86e1670d6d0d3ae94f8d5d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 24 Sep 2020 02:09:39 +0200 Subject: [PATCH 2/6] Buildfix --- GPU/Common/IndexGenerator.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/GPU/Common/IndexGenerator.cpp b/GPU/Common/IndexGenerator.cpp index b0ef2fb0bb..dab1ad6214 100644 --- a/GPU/Common/IndexGenerator.cpp +++ b/GPU/Common/IndexGenerator.cpp @@ -89,9 +89,11 @@ void IndexGenerator::AddList(int numVerts, bool clockwise) { } } +#ifdef _M_SSE inline __m128i mm_set_epi16_backwards(short w0, short w1, short w2, short w3, short w4, short w5, short w6, short w7) { return _mm_set_epi16(w7, w6, w5, w4, w3, w2, w1, w0); } +#endif void IndexGenerator::AddStrip(int numVerts, bool clockwise) { int wind = clockwise ? 1 : 2; From be54050521dfc68c9f4555a8de251e390e3e1204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 24 Sep 2020 09:24:03 +0200 Subject: [PATCH 3/6] Also optimize IndexGenerator::AddStrip for ARM NEON. --- GPU/Common/DrawEngineCommon.cpp | 2 +- GPU/Common/IndexGenerator.cpp | 61 +++++++++++++++++++++++++++++++-- GPU/Common/IndexGenerator.h | 2 +- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp index b326a832ad..994475d718 100644 --- a/GPU/Common/DrawEngineCommon.cpp +++ b/GPU/Common/DrawEngineCommon.cpp @@ -102,7 +102,7 @@ void DrawEngineCommon::DecodeVerts(u8 *dest) { if (indexGen.Prim() < 0) { ERROR_LOG_REPORT(G3D, "DecodeVerts: Failed to deduce prim: %i", indexGen.Prim()); // Force to points (0) - indexGen.AddPrim(GE_PRIM_POINTS, 0); + indexGen.AddPrim(GE_PRIM_POINTS, 0, true); } } diff --git a/GPU/Common/IndexGenerator.cpp b/GPU/Common/IndexGenerator.cpp index dab1ad6214..607bf23e6d 100644 --- a/GPU/Common/IndexGenerator.cpp +++ b/GPU/Common/IndexGenerator.cpp @@ -17,13 +17,21 @@ #include +#include "ppsspp_config.h" #include "CPUDetect.h" #include "Common.h" #ifdef _M_SSE #include #endif +#if PPSSPP_ARCH(ARM_NEON) +#if defined(_MSC_VER) && PPSSPP_ARCH(ARM64) +#include +#else +#include +#endif +#endif #include "IndexGenerator.h" // Points don't need indexing... @@ -95,6 +103,28 @@ inline __m128i mm_set_epi16_backwards(short w0, short w1, short w2, short w3, sh } #endif +alignas(16) static const u16 offsets_clockwise[24] = { + 0, (u16)(0 + 1), (u16)(0 + 2), + 1, (u16)(1 + 2), (u16)(1 + 1), + 2, (u16)(2 + 1), (u16)(2 + 2), + 3, (u16)(3 + 2), (u16)(3 + 1), + 4, (u16)(4 + 1), (u16)(4 + 2), + 5, (u16)(5 + 2), (u16)(5 + 1), + 6, (u16)(6 + 1), (u16)(6 + 2), + 7, (u16)(7 + 2), (u16)(7 + 1), +}; + +alignas(16) static const uint16_t offsets_counter_clockwise[24] = { + 0, (u16)(0 + 2), (u16)(0 + 1), + 1, (u16)(1 + 1), (u16)(1 + 2), + 2, (u16)(2 + 2), (u16)(2 + 1), + 3, (u16)(3 + 1), (u16)(3 + 2), + 4, (u16)(4 + 2), (u16)(4 + 1), + 5, (u16)(5 + 1), (u16)(5 + 2), + 6, (u16)(6 + 2), (u16)(6 + 1), + 7, (u16)(7 + 1), (u16)(7 + 2), +}; + void IndexGenerator::AddStrip(int numVerts, bool clockwise) { int wind = clockwise ? 1 : 2; int numTris = numVerts - 2; @@ -108,14 +138,16 @@ void IndexGenerator::AddStrip(int numVerts, bool clockwise) { // The first such multiple is 24, which means we'll generate 24 indices per cycle, // which corresponds to 8 triangles. That's pretty cool. + // TODO: Overshooting wouldn't be so bad here - maybe better than entering the narrow loop? int numChunks = numTris / 8; if (numChunks) { __m128i ibase8 = _mm_set1_epi16(ibase); __m128i increment = _mm_set1_epi16(8); + const __m128i *offsets = (const __m128i *)(clockwise ? offsets_clockwise : offsets_counter_clockwise); // TODO: Precompute two sets of these depending on wind, and just load directly. - __m128i offsets0 = mm_set_epi16_backwards(0, 0 + wind, (wind ^ 3), /**/ 1, 1 + (wind ^ 3), 1 + wind, /**/ 2, 2 + wind); - __m128i offsets1 = mm_set_epi16_backwards(2 + (wind ^ 3), /**/ 3, 3 + (wind ^ 3), 3 + wind, /**/ 4, 4 + wind, 4 + (wind ^ 3), /**/ 5); - __m128i offsets2 = mm_set_epi16_backwards(5 + (wind ^ 3), 5 + wind, /**/ 6, 6 + wind, 6 + (wind ^ 3), /**/ 7, 7 + (wind ^ 3), 7 + wind); + __m128i offsets0 = _mm_load_si128(offsets); + __m128i offsets1 = _mm_load_si128(offsets + 1); + __m128i offsets2 = _mm_load_si128(offsets + 2); __m128i *dst = (__m128i *)outInds; for (int i = 0; i < numChunks; i++) { _mm_storeu_si128(dst, _mm_add_epi16(ibase8, offsets0)); @@ -129,6 +161,29 @@ void IndexGenerator::AddStrip(int numVerts, bool clockwise) { ibase += numChunks * 8; } // wind doesn't need to be updated, an even number of triangles have been drawn. +#elif PPSSPP_ARCH(ARM_NEON) + int numChunks = numTris / 8; + if (numChunks) { + uint16x8_t ibase8 = vdupq_n_u16(ibase); + uint16x8_t increment = vdupq_n_u16(8); + const u16 *offsets = clockwise ? offsets_clockwise : offsets_counter_clockwise; + + // TODO: Precompute two sets of these depending on wind, and just load directly. + uint16x8_t offsets0 = vld1q_u16(offsets); + uint16x8_t offsets1 = vld1q_u16(offsets + 8); + uint16x8_t offsets2 = vld1q_u16(offsets + 16); + uint16x8_t *dst = (uint16x8_t *)outInds; + for (int i = 0; i < numChunks; i++) { + vst1q_u16(outInds, vaddq_u16(ibase8, offsets0)); + vst1q_u16(outInds + 8, vaddq_u16(ibase8, offsets1)); + vst1q_u16(outInds + 16, vaddq_u16(ibase8, offsets2)); + ibase8 = vaddq_u16(ibase8, increment); + dst += 3; + outInds += 24; + } + remainingTris -= numChunks * 8; + ibase += numChunks * 8; + } #endif size_t numPairs = remainingTris / 2; diff --git a/GPU/Common/IndexGenerator.h b/GPU/Common/IndexGenerator.h index dd983536f8..05ac01d0f2 100644 --- a/GPU/Common/IndexGenerator.h +++ b/GPU/Common/IndexGenerator.h @@ -48,7 +48,7 @@ public: GEPrimitiveType Prim() const { return prim_; } - void AddPrim(int prim, int vertexCount, bool clockwise = true); + void AddPrim(int prim, int vertexCount, bool clockwise); void TranslatePrim(int prim, int numInds, const u8 *inds, int indexOffset, bool clockwise); void TranslatePrim(int prim, int numInds, const u16_le *inds, int indexOffset, bool clockwise); void TranslatePrim(int prim, int numInds, const u32_le *inds, int indexOffset, bool clockwise); From d8ddef150f91e5c57330b7f4209f8a60b17ca850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 24 Sep 2020 09:36:39 +0200 Subject: [PATCH 4/6] IndexGenerator::AddStrip: Avoid the fallback by writing a few extra indices if necessary. Actually a decent boost. --- GPU/Common/IndexGenerator.cpp | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/GPU/Common/IndexGenerator.cpp b/GPU/Common/IndexGenerator.cpp index 607bf23e6d..9a45b343ec 100644 --- a/GPU/Common/IndexGenerator.cpp +++ b/GPU/Common/IndexGenerator.cpp @@ -138,8 +138,9 @@ void IndexGenerator::AddStrip(int numVerts, bool clockwise) { // The first such multiple is 24, which means we'll generate 24 indices per cycle, // which corresponds to 8 triangles. That's pretty cool. - // TODO: Overshooting wouldn't be so bad here - maybe better than entering the narrow loop? - int numChunks = numTris / 8; + // We allow ourselves to write some extra indices to avoid the fallback loop. + // That's alright as we're appending to a buffer - they will get overwritten anyway. + int numChunks = (numTris + 7) / 8; if (numChunks) { __m128i ibase8 = _mm_set1_epi16(ibase); __m128i increment = _mm_set1_epi16(8); @@ -156,13 +157,11 @@ void IndexGenerator::AddStrip(int numVerts, bool clockwise) { ibase8 = _mm_add_epi16(ibase8, increment); dst += 3; } - remainingTris -= numChunks * 8; - outInds += numChunks * 24; - ibase += numChunks * 8; + outInds += numTris * 3; } // wind doesn't need to be updated, an even number of triangles have been drawn. #elif PPSSPP_ARCH(ARM_NEON) - int numChunks = numTris / 8; + int numChunks = (numTris + 7) / 8; if (numChunks) { uint16x8_t ibase8 = vdupq_n_u16(ibase); uint16x8_t increment = vdupq_n_u16(8); @@ -172,20 +171,18 @@ void IndexGenerator::AddStrip(int numVerts, bool clockwise) { uint16x8_t offsets0 = vld1q_u16(offsets); uint16x8_t offsets1 = vld1q_u16(offsets + 8); uint16x8_t offsets2 = vld1q_u16(offsets + 16); - uint16x8_t *dst = (uint16x8_t *)outInds; + u16 *dst = outInds; for (int i = 0; i < numChunks; i++) { - vst1q_u16(outInds, vaddq_u16(ibase8, offsets0)); - vst1q_u16(outInds + 8, vaddq_u16(ibase8, offsets1)); - vst1q_u16(outInds + 16, vaddq_u16(ibase8, offsets2)); + vst1q_u16(dst, vaddq_u16(ibase8, offsets0)); + vst1q_u16(dst + 8, vaddq_u16(ibase8, offsets1)); + vst1q_u16(dst + 16, vaddq_u16(ibase8, offsets2)); ibase8 = vaddq_u16(ibase8, increment); - dst += 3; - outInds += 24; + dst += 3 * 8; } - remainingTris -= numChunks * 8; - ibase += numChunks * 8; + outInds += numTris * 3; } -#endif - +#else + // Slow fallback loop. size_t numPairs = remainingTris / 2; while (numPairs > 0) { *outInds++ = ibase; @@ -205,6 +202,8 @@ void IndexGenerator::AddStrip(int numVerts, bool clockwise) { wind ^= 3; // toggle between 1 and 2 *outInds++ = ibase + wind; } +#endif + inds_ = outInds; index_ += numVerts; if (numTris > 0) From deedc7a1fe091ddb2645d339c0f2aec9d646e3b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 24 Sep 2020 09:49:28 +0200 Subject: [PATCH 5/6] Cleanup --- GPU/Common/IndexGenerator.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/GPU/Common/IndexGenerator.cpp b/GPU/Common/IndexGenerator.cpp index 9a45b343ec..b33b84f3fc 100644 --- a/GPU/Common/IndexGenerator.cpp +++ b/GPU/Common/IndexGenerator.cpp @@ -97,12 +97,6 @@ void IndexGenerator::AddList(int numVerts, bool clockwise) { } } -#ifdef _M_SSE -inline __m128i mm_set_epi16_backwards(short w0, short w1, short w2, short w3, short w4, short w5, short w6, short w7) { - return _mm_set_epi16(w7, w6, w5, w4, w3, w2, w1, w0); -} -#endif - alignas(16) static const u16 offsets_clockwise[24] = { 0, (u16)(0 + 1), (u16)(0 + 2), 1, (u16)(1 + 2), (u16)(1 + 1), @@ -145,7 +139,6 @@ void IndexGenerator::AddStrip(int numVerts, bool clockwise) { __m128i ibase8 = _mm_set1_epi16(ibase); __m128i increment = _mm_set1_epi16(8); const __m128i *offsets = (const __m128i *)(clockwise ? offsets_clockwise : offsets_counter_clockwise); - // TODO: Precompute two sets of these depending on wind, and just load directly. __m128i offsets0 = _mm_load_si128(offsets); __m128i offsets1 = _mm_load_si128(offsets + 1); __m128i offsets2 = _mm_load_si128(offsets + 2); @@ -166,8 +159,6 @@ void IndexGenerator::AddStrip(int numVerts, bool clockwise) { uint16x8_t ibase8 = vdupq_n_u16(ibase); uint16x8_t increment = vdupq_n_u16(8); const u16 *offsets = clockwise ? offsets_clockwise : offsets_counter_clockwise; - - // TODO: Precompute two sets of these depending on wind, and just load directly. uint16x8_t offsets0 = vld1q_u16(offsets); uint16x8_t offsets1 = vld1q_u16(offsets + 8); uint16x8_t offsets2 = vld1q_u16(offsets + 16); @@ -191,8 +182,6 @@ void IndexGenerator::AddStrip(int numVerts, bool clockwise) { *outInds++ = ibase + 1; *outInds++ = ibase + 1 + (wind ^ 3); *outInds++ = ibase + 1 + wind; - // *outInds++ = ibase + 2; - // *outInds++ = ibase + 2 + wind; ibase += 2; numPairs--; } From 2a2a4a21d9727d82b42c8b46b8336247ee4c3065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 24 Sep 2020 10:03:07 +0200 Subject: [PATCH 6/6] More cleanup --- GPU/Common/IndexGenerator.cpp | 71 ++++++++++++++++------------------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/GPU/Common/IndexGenerator.cpp b/GPU/Common/IndexGenerator.cpp index b33b84f3fc..446d7a680e 100644 --- a/GPU/Common/IndexGenerator.cpp +++ b/GPU/Common/IndexGenerator.cpp @@ -120,12 +120,8 @@ alignas(16) static const uint16_t offsets_counter_clockwise[24] = { }; void IndexGenerator::AddStrip(int numVerts, bool clockwise) { - int wind = clockwise ? 1 : 2; int numTris = numVerts - 2; - u16 *outInds = inds_; - int ibase = index_; - int remainingTris = numTris; #ifdef _M_SSE // In an SSE2 register we can fit 8 16-bit integers. // However, we need to output a multiple of 3 indices. @@ -135,46 +131,45 @@ void IndexGenerator::AddStrip(int numVerts, bool clockwise) { // We allow ourselves to write some extra indices to avoid the fallback loop. // That's alright as we're appending to a buffer - they will get overwritten anyway. int numChunks = (numTris + 7) / 8; - if (numChunks) { - __m128i ibase8 = _mm_set1_epi16(ibase); - __m128i increment = _mm_set1_epi16(8); - const __m128i *offsets = (const __m128i *)(clockwise ? offsets_clockwise : offsets_counter_clockwise); - __m128i offsets0 = _mm_load_si128(offsets); - __m128i offsets1 = _mm_load_si128(offsets + 1); - __m128i offsets2 = _mm_load_si128(offsets + 2); - __m128i *dst = (__m128i *)outInds; - for (int i = 0; i < numChunks; i++) { - _mm_storeu_si128(dst, _mm_add_epi16(ibase8, offsets0)); - _mm_storeu_si128(dst + 1, _mm_add_epi16(ibase8, offsets1)); - _mm_storeu_si128(dst + 2, _mm_add_epi16(ibase8, offsets2)); - ibase8 = _mm_add_epi16(ibase8, increment); - dst += 3; - } - outInds += numTris * 3; + __m128i ibase8 = _mm_set1_epi16(index_); + __m128i increment = _mm_set1_epi16(8); + const __m128i *offsets = (const __m128i *)(clockwise ? offsets_clockwise : offsets_counter_clockwise); + __m128i offsets0 = _mm_load_si128(offsets); + __m128i offsets1 = _mm_load_si128(offsets + 1); + __m128i offsets2 = _mm_load_si128(offsets + 2); + __m128i *dst = (__m128i *)inds_; + for (int i = 0; i < numChunks; i++) { + _mm_storeu_si128(dst, _mm_add_epi16(ibase8, offsets0)); + _mm_storeu_si128(dst + 1, _mm_add_epi16(ibase8, offsets1)); + _mm_storeu_si128(dst + 2, _mm_add_epi16(ibase8, offsets2)); + ibase8 = _mm_add_epi16(ibase8, increment); + dst += 3; } + inds_ += numTris * 3; // wind doesn't need to be updated, an even number of triangles have been drawn. #elif PPSSPP_ARCH(ARM_NEON) int numChunks = (numTris + 7) / 8; - if (numChunks) { - uint16x8_t ibase8 = vdupq_n_u16(ibase); - uint16x8_t increment = vdupq_n_u16(8); - const u16 *offsets = clockwise ? offsets_clockwise : offsets_counter_clockwise; - uint16x8_t offsets0 = vld1q_u16(offsets); - uint16x8_t offsets1 = vld1q_u16(offsets + 8); - uint16x8_t offsets2 = vld1q_u16(offsets + 16); - u16 *dst = outInds; - for (int i = 0; i < numChunks; i++) { - vst1q_u16(dst, vaddq_u16(ibase8, offsets0)); - vst1q_u16(dst + 8, vaddq_u16(ibase8, offsets1)); - vst1q_u16(dst + 16, vaddq_u16(ibase8, offsets2)); - ibase8 = vaddq_u16(ibase8, increment); - dst += 3 * 8; - } - outInds += numTris * 3; + uint16x8_t ibase8 = vdupq_n_u16(index_); + uint16x8_t increment = vdupq_n_u16(8); + const u16 *offsets = clockwise ? offsets_clockwise : offsets_counter_clockwise; + uint16x8_t offsets0 = vld1q_u16(offsets); + uint16x8_t offsets1 = vld1q_u16(offsets + 8); + uint16x8_t offsets2 = vld1q_u16(offsets + 16); + u16 *dst = inds_; + for (int i = 0; i < numChunks; i++) { + vst1q_u16(dst, vaddq_u16(ibase8, offsets0)); + vst1q_u16(dst + 8, vaddq_u16(ibase8, offsets1)); + vst1q_u16(dst + 16, vaddq_u16(ibase8, offsets2)); + ibase8 = vaddq_u16(ibase8, increment); + dst += 3 * 8; } + inds_ += numTris * 3; #else // Slow fallback loop. - size_t numPairs = remainingTris / 2; + int wind = clockwise ? 1 : 2; + int ibase = index_; + size_t numPairs = numTris / 2; + u16 *outInds = inds_; while (numPairs > 0) { *outInds++ = ibase; *outInds++ = ibase + wind; @@ -191,9 +186,9 @@ void IndexGenerator::AddStrip(int numVerts, bool clockwise) { wind ^= 3; // toggle between 1 and 2 *outInds++ = ibase + wind; } + inds_ = outInds; #endif - inds_ = outInds; index_ += numVerts; if (numTris > 0) count_ += numTris * 3;