// Copyright (c) 2015- 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 "Common/Common.h" #include "Common/TimeUtil.h" #include "Core/Config.h" #include "Core/ConfigValues.h" #include "GPU/Common/VertexDecoderCommon.h" #include "GPU/ge_constants.h" #include "GPU/GPUState.h" #include "unittest/TestVertexJit.h" #include "unittest/UnitTest.h" class VertexDecoderTestHarness { static const int BUFFER_SIZE = 64 * 65536; static const int ROUNDS = 200; public: VertexDecoderTestHarness() : dec_(nullptr), needsReset_(true), dstPos_(0), assertFailed_(false) { src_ = new u8[BUFFER_SIZE]; dst_ = new u8[BUFFER_SIZE]; cache_ = new VertexDecoderJitCache(); g_Config.bVertexDecoderJit = true; // Required for jit to be enabled. g_Config.iCpuCore = (int)CPUCore::JIT; gstate_c.uv.uScale = 1.0f; gstate_c.uv.vScale = 1.0f; } ~VertexDecoderTestHarness() { delete src_; delete dst_; delete cache_; delete dec_; } void Reset() { memset(src_, 0, BUFFER_SIZE); memset(dst_, 0, BUFFER_SIZE); memset(&options_, 0, sizeof(options_)); delete dec_; dec_ = nullptr; indexLowerBound_ = 0; indexUpperBound_ = 0; srcPos_ = 0; dstPos_ = 0; needsReset_ = false; } void SetOptions(const VertexDecoderOptions &opts) { if (needsReset_) { Reset(); } memcpy(&options_, &opts, sizeof(options_)); } void SetIndexLowerBound(const int lower) { if (needsReset_) { Reset(); } indexLowerBound_ = lower; } void Execute(int vtype, int indexUpperBound, bool useJit) { SetupExecute(vtype, useJit); dec_->DecodeVerts(dst_, src_, indexLowerBound_, indexUpperBound); } double ExecuteTimed(int vtype, int indexUpperBound, bool useJit) { SetupExecute(vtype, useJit); int total = 0; double st = real_time_now(); do { for (int j = 0; j < ROUNDS; ++j) { dec_->DecodeVerts(dst_, src_, indexLowerBound_, indexUpperBound); ++total; } } while (real_time_now() - st < 0.5); double elapsed = real_time_now() - st; return total / elapsed; } void Add8(u8 x) { if (needsReset_) { Reset(); } memcpy(src_ + srcPos_, &x, sizeof(x)); srcPos_ += sizeof(x); } void Add8(u8 x, u8 y) { Add8(x); Add8(y); } void Add8(u8 x, u8 y, u8 z) { Add8(x); Add8(y); Add8(z); } void Add8(u8 x, u8 y, u8 z, u8 w) { Add8(x); Add8(y); Add8(z); Add8(w); } void Add16(u16_le x) { if (needsReset_) { Reset(); } memcpy(src_ + srcPos_, &x, sizeof(x)); srcPos_ += sizeof(x); } void Add16(u16_le x, u16_le y) { Add16(x); Add16(y); } void Add16(u16_le x, u16_le y, u16_le z) { Add16(x); Add16(y); Add16(z); } void AddFloat(float_le x) { if (needsReset_) { Reset(); } memcpy(src_ + srcPos_, &x, sizeof(x)); srcPos_ += sizeof(x); } void AddFloat(float_le x, float_le y) { AddFloat(x); AddFloat(y); } void AddFloat(float_le x, float_le y, float_le z) { AddFloat(x); AddFloat(y); AddFloat(z); } u8 Get8() { return dst_[dstPos_++]; } u16 Get16() { u16_le result; memcpy(&result, dst_ + dstPos_, sizeof(result)); dstPos_ += sizeof(result); return result; } float GetFloat() { float_le result; memcpy(&result, dst_ + dstPos_, sizeof(result)); dstPos_ += sizeof(result); return result; } void Assert8(const char *title, u8 x, u8 y) { u8 resx = Get8(); u8 resy = Get8(); if (resx != x || resy != y) { assertFailed_ = true; printf("%s: Failed %d, %d != expected %d, %d\n", title, resx, resy, x, y); } } void Assert8(const char *title, u8 x, u8 y, u8 z) { u8 resx = Get8(); u8 resy = Get8(); u8 resz = Get8(); if (resx != x || resy != y || resz != z) { assertFailed_ = true; printf("%s: Failed %d, %d, %d != expected %d, %d, %d\n", title, resx, resy, resz, x, y, z); } } void Assert8(const char *title, u8 x, u8 y, u8 z, u8 w) { u8 resx = Get8(); u8 resy = Get8(); u8 resz = Get8(); u8 resw = Get8(); if (resx != x || resy != y || resz != z || resw != w) { assertFailed_ = true; printf("%s: Failed %d, %d, %d, %d != expected %d, %d, %d, %d\n", title, resx, resy, resz, resw, x, y, z, w); } } void Assert16(const char *title, u16 x, u16 y) { u16 resx = Get16(); u16 resy = Get16(); if (resx != x || resy != y) { assertFailed_ = true; printf("%s: Failed %d, %d != expected %d, %d\n", title, resx, resy, x, y); } } void Assert16(const char *title, u16 x, u16 y, u16 z) { u16 resx = Get16(); u16 resy = Get16(); u16 resz = Get16(); if (resx != x || resy != y || resz != z) { assertFailed_ = true; printf("%s: Failed %d, %d, %d != expected %d, %d, %d\n", title, resx, resy, resz, x, y, z); } } bool CompareFloat(float a, float b) { return a - fmodf(a, 0.0000001f) == b - fmodf(b, 0.0000001f); } void AssertFloat(const char *title, float x) { float resx = GetFloat(); if (!CompareFloat(resx, x)) { assertFailed_ = true; printf("%s: Failed %f != expected %f\n", title, resx, x); } } void AssertFloat(const char *title, float x, float y) { float resx = GetFloat(); float resy = GetFloat(); if (!CompareFloat(resx, x) || !CompareFloat(resy, y)) { assertFailed_ = true; printf("%s: Failed %f, %f != expected %f, %f\n", title, resx, resy, x, y); } } void AssertFloat(const char *title, float x, float y, float z) { float resx = GetFloat(); float resy = GetFloat(); float resz = GetFloat(); if (!CompareFloat(resx, x) || !CompareFloat(resy, y) || !CompareFloat(resz, z)) { assertFailed_ = true; printf("%s: Failed %f, %f, %f != expected %f, %f, %f\n", title, resx, resy, resz, x, y, z); } } void Skip(u32 c) { dstPos_ += c; } void *GetData() { return dst_; } int GetDstStride() { if (dec_) { return dec_->decFmt.stride; } return 0; } bool HasFailed() { return assertFailed_; } private: void SetupExecute(int vtype, bool useJit) { if (dec_ != nullptr) { delete dec_; } dec_ = new VertexDecoder(); dec_->SetVertexType(vtype, options_, useJit ? cache_ : nullptr); dstPos_ = 0; needsReset_ = true; } u8 *src_; u8 *dst_; VertexDecoderJitCache *cache_; VertexDecoderOptions options_; VertexDecoder *dec_; int indexLowerBound_; int indexUpperBound_; bool needsReset_; size_t srcPos_; size_t dstPos_; bool assertFailed_; }; static bool TestVertex8() { VertexDecoderTestHarness dec; int vtype = GE_VTYPE_POS_8BIT | GE_VTYPE_NRM_8BIT | GE_VTYPE_TC_8BIT; dec.Add8(127, 128); dec.Add8(127, 0, 128); dec.Add8(127, 0, 128); for (int jit = 0; jit <= 1; ++jit) { dec.Execute(vtype, 0, jit == 1); dec.AssertFloat("TestVertex8-TC", 127.0f / 128.0f, 1.0f); dec.Assert8("TestVertex8-Nrm", 127, 0, 128); dec.Skip(1); dec.AssertFloat("TestVertex8-Pos", 127.0f / 128.0f, 0.0f, -1.0f); } return !dec.HasFailed(); } static bool TestVertex16() { VertexDecoderTestHarness dec; int vtype = GE_VTYPE_POS_16BIT | GE_VTYPE_NRM_16BIT | GE_VTYPE_TC_16BIT; dec.Add16(32767, 32768); dec.Add16(32767, 0, 32768); dec.Add16(32767, 0, 32768); for (int jit = 0; jit <= 1; ++jit) { dec.Execute(vtype, 0, jit == 1); dec.AssertFloat("TestVertex16-TC", 32767.0f / 32768.0f, 1.0f); dec.Assert16("TestVertex16-Nrm", 32767, 0, 32768); dec.Skip(2); dec.AssertFloat("TestVertex16-Pos", 32767.0f / 32768.0f, 0.0f, -1.0f); } return !dec.HasFailed(); } static bool TestVertexFloat() { VertexDecoderTestHarness dec; int vtype = GE_VTYPE_POS_FLOAT | GE_VTYPE_NRM_FLOAT | GE_VTYPE_TC_FLOAT; dec.AddFloat(1.0f, -1.0f); dec.AddFloat(1.0f, 0.5f, -1.0f); dec.AddFloat(1.0f, 0.5f, -1.0f); for (int jit = 0; jit <= 1; ++jit) { dec.Execute(vtype, 0, jit == 1); dec.AssertFloat("TestVertexFloat-TC", 1.0f, -1.0f); dec.AssertFloat("TestVertexFloat-Nrm", 1.0f, 0.5f, -1.0f); dec.AssertFloat("TestVertexFloat-Pos", 1.0f, 0.5f, -1.0f); } return !dec.HasFailed(); } static bool TestVertex8Through() { VertexDecoderTestHarness dec; int vtype = GE_VTYPE_POS_8BIT | GE_VTYPE_NRM_8BIT | GE_VTYPE_TC_8BIT | GE_VTYPE_THROUGH; dec.Add8(127, 128); dec.Add8(127, 0, 128); dec.Add8(127, 0, 128); for (int jit = 0; jit <= 1; ++jit) { dec.Execute(vtype, 0, jit == 1); // Note: this is correct, even in through. dec.AssertFloat("TestVertex8Through-TC", 127.0f / 128.0f, 1.0f); dec.Assert8("TestVertex8Through-Nrm", 127, 0, 128); // Ignoring Pos since s8 through isn't really an option. } return !dec.HasFailed(); } static bool TestVertex16Through() { VertexDecoderTestHarness dec; int vtype = GE_VTYPE_POS_16BIT | GE_VTYPE_NRM_16BIT | GE_VTYPE_TC_16BIT | GE_VTYPE_THROUGH; dec.Add16(32767, 32768); dec.Add16(32767, 0, 32768); dec.Add16(32767, 0, 32768); for (int jit = 0; jit <= 1; ++jit) { dec.Execute(vtype, 0, jit == 1); dec.AssertFloat("TestVertex16Through-TC", 32767.0f, 32768.0f); dec.Assert16("TestVertex16Through-Nrm", 32767, 0, 32768); dec.Skip(2); dec.AssertFloat("TestVertex16Through-Pos", 32767.0f, 0.0f, 32768.0f); } return !dec.HasFailed(); } static bool TestVertexFloatThrough() { VertexDecoderTestHarness dec; int vtype = GE_VTYPE_POS_FLOAT | GE_VTYPE_NRM_FLOAT | GE_VTYPE_TC_FLOAT | GE_VTYPE_THROUGH; dec.AddFloat(1.0f, -1.0f); dec.AddFloat(1.0f, 0.5f, -1.0f); dec.AddFloat(1.0f, 0.5f, -1.0f); for (int jit = 0; jit <= 1; ++jit) { dec.Execute(vtype, 0, jit == 1); dec.AssertFloat("TestVertexFloatThrough-TC", 1.0f, -1.0f); dec.AssertFloat("TestVertexFloatThrough-Nrm", 1.0f, 0.5f, -1.0f); dec.AssertFloat("TestVertexFloatThrough-Pos", 1.0f, 0.5f, -1.0f); } return !dec.HasFailed(); } static bool TestVertexColor8888() { VertexDecoderTestHarness dec; int vtype = GE_VTYPE_POS_FLOAT | GE_VTYPE_COL_8888; bool failed = false; dec.Add8(1, 2, 3, 4); dec.AddFloat(1.0f, 0.5f, -1.0f); for (int jit = 0; jit <= 1; ++jit) { gstate_c.vertexFullAlpha = true; dec.Execute(vtype, 0, jit == 1); dec.Assert8("TestVertexColor8888-Col", 1, 2, 3, 4); dec.AssertFloat("TestVertexColor8888-Pos", 1.0f, 0.5f, -1.0f); if (gstate_c.vertexFullAlpha) { printf("TestVertexColor8888: failed to clear vertexFullAlpha\n"); failed = true; } } dec.Add8(255, 255, 255, 255); dec.AddFloat(1.0f, 0.5f, -1.0f); for (int jit = 0; jit <= 1; ++jit) { gstate_c.vertexFullAlpha = true; dec.Execute(vtype, 0, jit == 1); dec.Assert8("TestVertexColor8888-Col", 255, 255, 255, 255); dec.AssertFloat("TestVertexColor8888-Pos", 1.0f, 0.5f, -1.0f); if (!gstate_c.vertexFullAlpha) { printf("TestVertexColor8888: cleared vertexFullAlpha\n"); failed = true; } } return !dec.HasFailed() && !failed; } static bool TestVertexColor4444() { VertexDecoderTestHarness dec; int vtype = GE_VTYPE_POS_FLOAT | GE_VTYPE_COL_4444; bool failed = false; dec.Add16(0x1234, 0); dec.AddFloat(1.0f, 0.5f, -1.0f); for (int jit = 0; jit <= 1; ++jit) { gstate_c.vertexFullAlpha = true; dec.Execute(vtype, 0, jit == 1); dec.Assert8("TestVertexColor4444-Col", 0x44, 0x33, 0x22, 0x11); dec.AssertFloat("TestVertexColor4444-Pos", 1.0f, 0.5f, -1.0f); if (gstate_c.vertexFullAlpha) { printf("TestVertexColor4444: failed to clear vertexFullAlpha\n"); failed = true; } } dec.Add16(0xFFFF, 0); dec.AddFloat(1.0f, 0.5f, -1.0f); for (int jit = 0; jit <= 1; ++jit) { gstate_c.vertexFullAlpha = true; dec.Execute(vtype, 0, jit == 1); dec.Assert8("TestVertexColor4444-Col", 255, 255, 255, 255); dec.AssertFloat("TestVertexColor4444-Pos", 1.0f, 0.5f, -1.0f); if (!gstate_c.vertexFullAlpha) { printf("TestVertexColor4444: cleared vertexFullAlpha\n"); failed = true; } } return !dec.HasFailed() && !failed; } static bool TestVertexColor5551() { VertexDecoderTestHarness dec; int vtype = GE_VTYPE_POS_FLOAT | GE_VTYPE_COL_5551; bool failed = false; dec.Add16((0 << 15) | (1 << 10) | (2 << 5) | 3, 0); dec.AddFloat(1.0f, 0.5f, -1.0f); for (int jit = 0; jit <= 1; ++jit) { gstate_c.vertexFullAlpha = true; dec.Execute(vtype, 0, jit == 1); dec.Assert8("TestVertexColor5551-Col", 0x18, 0x10, 0x8, 0x0); dec.AssertFloat("TestVertexColor5551-Pos", 1.0f, 0.5f, -1.0f); if (gstate_c.vertexFullAlpha) { printf("TestVertexColor5551: failed to clear vertexFullAlpha\n"); failed = true; } } dec.Add16(0xFFFF, 0); dec.AddFloat(1.0f, 0.5f, -1.0f); for (int jit = 0; jit <= 1; ++jit) { gstate_c.vertexFullAlpha = true; dec.Execute(vtype, 0, jit == 1); dec.Assert8("TestVertexColor5551-Col", 255, 255, 255, 255); dec.AssertFloat("TestVertexColor5551-Pos", 1.0f, 0.5f, -1.0f); if (!gstate_c.vertexFullAlpha) { printf("TestVertexColor5551: cleared vertexFullAlpha\n"); failed = true; } } return !dec.HasFailed() && !failed; } static bool TestVertexColor565() { VertexDecoderTestHarness dec; int vtype = GE_VTYPE_POS_FLOAT | GE_VTYPE_COL_565; bool failed = false; dec.Add16((1 << 11) | (2 << 5) | 3, 0); dec.AddFloat(1.0f, 0.5f, -1.0f); for (int jit = 0; jit <= 1; ++jit) { gstate_c.vertexFullAlpha = true; dec.Execute(vtype, 0, jit == 1); dec.Assert8("TestVertexColor565-Col", 0x18, 0x8, 0x8, 255); dec.AssertFloat("TestVertexColor565-Pos", 1.0f, 0.5f, -1.0f); if (!gstate_c.vertexFullAlpha) { printf("TestVertexColor565: cleared vertexFullAlpha\n"); failed = true; } } return !dec.HasFailed() && !failed; } static bool TestVertex8Skin() { VertexDecoderTestHarness dec; g_Config.bSoftwareSkinning = true; for (int i = 0; i < 8 * 12; ++i) { gstate.boneMatrix[i] = 0.0f; } gstate.boneMatrix[0] = 2.0f; gstate.boneMatrix[4] = 1.0f; gstate.boneMatrix[8] = 5.0f; gstate.boneMatrix[12] = 1.0f; gstate.boneMatrix[16] = 2.0f; gstate.boneMatrix[20] = 5.0f; int vtype = GE_VTYPE_POS_8BIT | GE_VTYPE_NRM_8BIT | GE_VTYPE_WEIGHT_8BIT | (1 << GE_VTYPE_WEIGHTCOUNT_SHIFT); dec.Add8(128 + 64, 128 - 64); dec.Add8(127, 0, 128); dec.Add8(127, 0, 128); for (int jit = 0; jit <= 1; ++jit) { dec.Execute(vtype, 0, jit == 1); dec.AssertFloat("TestVertex8Skin-Nrm", (2.0f * 1.5f + 1.0f * 0.5f) * 127.0f / 128.0f, 0.0f, 2.0f * 5.0f * -1.0f); dec.AssertFloat("TestVertex8Skin-Pos", (2.0f * 1.5f + 1.0f * 0.5f) * 127.0f / 128.0f, 0.0f, 2.0f * 5.0f * -1.0f); } return !dec.HasFailed(); } static bool TestVertex16Skin() { VertexDecoderTestHarness dec; g_Config.bSoftwareSkinning = true; for (int i = 0; i < 8 * 12; ++i) { gstate.boneMatrix[i] = 0.0f; } gstate.boneMatrix[0] = 2.0f; gstate.boneMatrix[4] = 1.0f; gstate.boneMatrix[8] = 5.0f; gstate.boneMatrix[12] = 1.0f; gstate.boneMatrix[16] = 2.0f; gstate.boneMatrix[20] = 5.0f; int vtype = GE_VTYPE_POS_16BIT | GE_VTYPE_NRM_16BIT | GE_VTYPE_WEIGHT_16BIT | (1 << GE_VTYPE_WEIGHTCOUNT_SHIFT); dec.Add16(32768 + 16384, 32768 - 16384); dec.Add16(32767, 0, 32768); dec.Add16(32767, 0, 32768); for (int jit = 0; jit <= 1; ++jit) { dec.Execute(vtype, 0, jit == 1); dec.AssertFloat("TestVertex16Skin-Nrm", (2.0f * 1.5f + 1.0f * 0.5f) * 32767.0f / 32768.0f, 0.0f, 2.0f * 5.0f * -1.0f); dec.AssertFloat("TestVertex16Skin-Pos", (2.0f * 1.5f + 1.0f * 0.5f) * 32767.0f / 32768.0f, 0.0f, 2.0f * 5.0f * -1.0f); } return !dec.HasFailed(); } static bool TestVertexFloatSkin() { VertexDecoderTestHarness dec; g_Config.bSoftwareSkinning = true; for (int i = 0; i < 8 * 12; ++i) { gstate.boneMatrix[i] = 0.0f; } gstate.boneMatrix[0] = 2.0f; gstate.boneMatrix[4] = 1.0f; gstate.boneMatrix[8] = 5.0f; gstate.boneMatrix[12] = 1.0f; gstate.boneMatrix[16] = 2.0f; gstate.boneMatrix[20] = 5.0f; int vtype = GE_VTYPE_POS_FLOAT | GE_VTYPE_NRM_FLOAT | GE_VTYPE_WEIGHT_FLOAT | (1 << GE_VTYPE_WEIGHTCOUNT_SHIFT); dec.AddFloat(1.5f, 0.5f); dec.AddFloat(1.0f, 0, -1.0f); dec.AddFloat(1.0f, 0, -1.0f); for (int jit = 0; jit <= 1; ++jit) { dec.Execute(vtype, 0, jit == 1); dec.AssertFloat("TestVertexFloatSkin-Nrm", (2.0f * 1.5f + 1.0f * 0.5f) * 1.0f, 0.0f, 2.0f * 5.0f * -1.0f); dec.AssertFloat("TestVertexFloatSkin-Pos", (2.0f * 1.5f + 1.0f * 0.5f) * 1.0f, 0.0f, 2.0f * 5.0f * -1.0f); } return !dec.HasFailed(); } // TODO: Morph (col, pos, nrm), weights (no skin), morph + weights? typedef bool (*VertexTestFunc)(); static VertexTestFunc vertdecTestFuncs[] = { &TestVertex8, &TestVertex16, &TestVertexFloat, &TestVertex8Through, &TestVertex16Through, &TestVertexFloatThrough, &TestVertexColor8888, &TestVertexColor4444, &TestVertexColor5551, &TestVertexColor565, &TestVertex8Skin, &TestVertex16Skin, &TestVertexFloatSkin, }; bool TestVertexJit() { VertexDecoderTestHarness dec; /*for (int i = 0; i < 100; ++i) { dec.AddFloat(0.5f, 1.0f, -1.0f); } int vtype = GE_VTYPE_POS_FLOAT;*/ /*for (int i = 0; i < 100; ++i) { dec.Add16(32767, 0, 32768); } int vtype = GE_VTYPE_POS_16BIT;*/ for (int i = 0; i < 100; ++i) { dec.Add8(127, 0, 128); } int vtype = GE_VTYPE_POS_8BIT; double yesJit = dec.ExecuteTimed(vtype, 100, true); double noJit = dec.ExecuteTimed(vtype, 100, false); float x = dec.GetFloat(); float y = dec.GetFloat(); float z = dec.GetFloat(); printf("Result: %f, %f, %f\n", x, y, z); printf("Jit was %fx faster than steps.\n\n", yesJit / noJit); bool pass = true; for (size_t i = 0; i < ARRAY_SIZE(vertdecTestFuncs); ++i) { if (!vertdecTestFuncs[i]()) { pass = false; } } return pass; }