/*---------------------------------------------------------------------------* Copyright (C) Nintendo. All rights reserved. These coded instructions, statements, and computer programs contain proprietary information of Nintendo of America Inc. and/or Nintendo Company Ltd., and are protected by Federal copyright law. They may not be disclosed to third parties or copied or duplicated in any form, in whole or in part, without the prior written consent of Nintendo. *---------------------------------------------------------------------------*/ ////=========================================================================== /// demoTest.c /// /// This contains test-related functions for the demos /// ////=========================================================================== #include #include #include #include //---------------------------------------------------------------------- // Global Data typedef struct _DEMOTestDataStore { u32 count; u32 stopSelect; u32 testSelect; u32 captureSelect; u32 dumpSelect; u32 pm4CaptureSelect; u32 pm4CaptureNumFrames; u32 maxCPUTime; u32 maxGPUTime; char pm4CaptureFilename[1024]; int result; char compareFileName[1024]; char argv0[1024]; } DEMOTestDataStore; DEMOTestDataStore DEMOTestData = { 0, 0, (u32)-1, (u32)-1, }; //---------------------------------------------------------------------- // Local Data static u64 startCpuTime; static u64 endCpuTime; static u64 startGpuTime; static u64 endGpuTime; static u64* pTimestamp = NULL; //---------------------------------------------------------------------- // Utility hash functions u16 DEMOTestHashCRC16(const void *datap, u32 size) { static const u16 crc16_table[16] = { 0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401, 0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400, }; u32 r = 0; const u8 *p = (const u8 *)datap; while (size--) { u32 data = *p; p++; r = (r >> 4) ^ crc16_table[(r ^ data) & 0xf]; data >>= 4; r = (r >> 4) ^ crc16_table[(r ^ data) & 0xf]; } return (u16)r; } // DO NOT MODIFY THIS FORMULA WITHOUT CHANGING generate_crcs.pl IN THE TOOLS FOLDER AS WELL u32 DEMOTestHashCRC32(const void *datap, u32 size) { static const u32 crc32_table[16] = { 0x00000000U, 0x1DB71064U, 0x3B6E20C8U, 0x26D930ACU, 0x76DC4190U, 0x6B6B51F4U, 0x4DB26158U, 0x5005713CU, 0xEDB88320U, 0xF00F9344U, 0xD6D6A3E8U, 0xCB61B38CU, 0x9B64C2B0U, 0x86D3D2D4U, 0xA00AE278U, 0xBDBDF21CU, }; u32 r = 0xffffffffU; const u8 *p = (const u8 *)datap; while (size--) { u32 data = *p; p++; r = (r >> 4) ^ crc32_table[(r ^ data) & 0xf]; data >>= 4; r = (r >> 4) ^ crc32_table[(r ^ data) & 0xf]; } return ~r; } //---------------------------------------------------------------------- void DEMOTestInit(int argc, char *argv[]) { int i; char *p; DEMOTestData.count = 0; DEMOTestData.stopSelect = 0xFFFFFFFF; DEMOTestData.testSelect = 0xFFFFFFFF; DEMOTestData.captureSelect = 0xFFFFFFFF; DEMOTestData.dumpSelect = 0xFFFFFFFF; DEMOTestData.pm4CaptureSelect = 0xFFFFFFFF; DEMOTestData.pm4CaptureNumFrames = 1; DEMOTestData.pm4CaptureFilename[0] = '\0'; DEMOTestData.maxCPUTime = 0; DEMOTestData.maxGPUTime = 0; DEMOTestData.result = 0; DEMOTestData.compareFileName[0] = '\0'; if (argv != NULL) { strcpy(DEMOTestData.argv0, argv[0]); //note this might be different to OSGetArgcArgv(), e.g. under Meta. } else { strcpy(DEMOTestData.argv0, "unknown"); } #define SKIP_NON_DIGIT(c) ((c)!=0&&((c)<'0'||(c)>'9')) for (i = 0; i < argc; ++i) { // Simple parameter reading for now p = strstr(argv[i], "STOP_SELECT="); if (p) { DEMOTestData.stopSelect = (u32) atoi(p+strlen("STOP_SELECT=")); } p = strstr(argv[i], "TEST_SELECT="); if (p) { DEMOTestData.testSelect = (u32) atoi(p+strlen("TEST_SELECT=")); FSInit(); // Need to initialize FS before SAVEInit(). SAVEInit(); SAVEStatus status = SAVEInitSaveDir(ACT_SLOT_NO_COMMON); if (status != SAVE_STATUS_OK) { OSHalt("Failed to make common save directory for tests.\n"); } } p = strstr(argv[i], "CAPTURE_SELECT="); if (p) { DEMOTestData.captureSelect = (u32) atoi(p+strlen("CAPTURE_SELECT=")); } p = strstr(argv[i], "GX2R_DUMP_SELECT="); if (p) { DEMOTestData.dumpSelect = (u32) atoi(p+strlen("GX2R_DUMP_SELECT=")); } p = strstr(argv[i], "PM4_SELECT="); if (p) { u32 endFrame=0; u32 numParams = sscanf(p+strlen("PM4_SELECT="), "%d-%d", &DEMOTestData.pm4CaptureSelect, &endFrame); DEMOTestData.pm4CaptureNumFrames = (numParams==2) ? endFrame-DEMOTestData.pm4CaptureSelect+1 : 1; } p = strstr(argv[i], "PM4_FILE="); if (p) { strcpy(DEMOTestData.pm4CaptureFilename, p+strlen("PM4_FILE=")); // replace EOL or ',' with terminating 0 p = DEMOTestData.pm4CaptureFilename; for( ; *p != '\n' && *p != '\r' && *p != ',' && *p != 0; p++ ) {} *p = 0; } p = strstr(argv[i], "MAX_CPU_TIME="); if (p) { DEMOTestData.maxCPUTime = (u32) atoi(p+strlen("MAX_CPU_TIME=")); } p = strstr(argv[i], "MAX_GPU_TIME="); if (p) { DEMOTestData.maxGPUTime = (u32) atoi(p+strlen("MAX_GPU_TIME=")); } p = strstr(argv[i], "COMPARE_FILE="); if (p) { strcpy(DEMOTestData.compareFileName, p+strlen("COMPARE_FILE=")); // replace EOL or ',' with terminating 0 p = DEMOTestData.compareFileName; for( ; *p != '\n' && *p != '\r' && *p != ',' && *p != 0; p++ ) {} *p = 0; } } if (DEMOTestData.pm4CaptureSelect != 0xFFFFFFFF && DEMOTestData.pm4CaptureFilename[0]=='\0') { // if we asked for a PM4 capture but didn't specify a filename, default to argv[0].4mp char baseName[256]=""; strcpy(baseName, argv[0]); char* dot=NULL; if (dot = strrchr(baseName, '.')) *dot = 0; strcat(baseName, ".4mp"); strcpy(DEMOTestData.pm4CaptureFilename, baseName); } } void DEMOTestShutdown(void) { DEMOCaptureShutdown(); } void DEMOTestCheck(void) { // Do this even on first frame. Treat 1 as the first frame number to appear consistent with other checks, // which are done at the top of the following frame if (DEMOTestData.count+1 == DEMOTestData.pm4CaptureSelect) { char str[1024]=""; OSCalendarTime td; OSTicksToCalendarTime(OSGetTime(), &td); sprintf(str, "DEMO: start PM4 capture of %s frame %d-%d to file \"%s\" at %4d-%02d-%02d %02d:%02d:%02d", DEMOTestData.argv0, DEMOTestData.pm4CaptureSelect, DEMOTestData.pm4CaptureSelect+DEMOTestData.pm4CaptureNumFrames-1, DEMOTestData.pm4CaptureFilename, td.year, td.mon+1, td.mday, td.hour, td.min, td.sec); // Print it to the console OSReport(str); OSReport("\n"); // Start the capture GX2DebugCaptureStart(DEMOTestData.pm4CaptureFilename, GX2_DEBUG_CAPTURE_DEFAULT); // Add the info string to the capture itself as a nop tag GX2UTDebugTagComment(str); //GPUDB // We need to start capture with a context state, to bring in any initial state settings. // This could potentially be omitted if all relevant state is explicitly set up within the capture bracket. DEMOGfxSetContextState(); } if (DEMOTestData.count > 0) { // Do this first so we don't get the screen capture code in the PM4 capture if (DEMOTestData.count == DEMOTestData.pm4CaptureSelect+DEMOTestData.pm4CaptureNumFrames-1) { char str[1024]=""; sprintf(str, "DEMO: end PM4 capture at frame %d to file \"%s\"", DEMOTestData.count, DEMOTestData.pm4CaptureFilename); GX2UTDebugTagComment(str); OSReport(str); OSReport("\n"); GX2DebugCaptureEnd(GX2_DEBUG_CAPTURE_DEFAULT); } if (DEMOTestData.count == DEMOTestData.testSelect) { DEMOTestCompare(); DEMOStopRunning(); } if (DEMOTestData.count == DEMOTestData.captureSelect) { DEMOTestCapture(); DEMOStopRunning(); } if (DEMOTestData.count == DEMOTestData.stopSelect) { DEMOStopRunning(); } if (DEMOTestData.count == DEMOTestData.dumpSelect) { GX2TempDumpResources(); } } DEMOTestData.count++; } void _DEMODumpSerial(const u8 *, const u32); void DEMOTestCompare(void) { u32 cmpLen; u8 *cmpBuf; GX2Boolean testPassed; u32 captureCRC = 0; u32 compareCRC = 0; char cmpFile[1024]; DEMOGfxSetContextState(); DEMOCaptureInit(DEMOColorBuffer.surface.width, DEMOColorBuffer.surface.height, (DEMOColorBuffer.surface.format == GX2_SURFACE_FORMAT_TCS_R8_G8_B8_A8_UNORM) ? RGBA8 : SRGB8); DEMOCaptureCopy(&DEMOColorBuffer, NULL); captureCRC = DEMOTestHashCRC32(DEMOCaptureData.TGAData, DEMOCaptureData.TGALength); DEMOPrintf("Capture CRC: %08x\n", captureCRC); sprintf(cmpFile, "assets/golden_crcs/%s.crc", DEMOTestData.compareFileName); // Instead of panicking if the file isn't there, just have a zero CRC if (DEMOFSFileExists(cmpFile)) { cmpBuf = (u8 *) DEMOFSSimpleRead(cmpFile, &cmpLen); // Note: The first 4 bytes are null. The next 4 are the CRC. compareCRC = (cmpBuf[4] << 24) + (cmpBuf[5] << 16) + (cmpBuf[6] << 8) + (cmpBuf[7]); DEMOFree(cmpBuf); } DEMOPrintf("Compare CRC: %08x\n", compareCRC); // A test is considered to have passed if the image captured from the // color buffer matches the "golden" image. // This check is made by comparing the CRC of the golden image with the // CRC of the captured image. testPassed = (captureCRC == compareCRC) ? GX2_TRUE : GX2_FALSE; DEMOTestData.result |= (int) !testPassed; if (GX2_FALSE == testPassed) { // Before printing a failure message, output the TGA file. DEMOCaptureCopy(NULL, "/vol/save/common/GX2Capture.tga"); DEMOPrintf("Test result: FAIL\n"); } else { DEMOPrintf("Test result: PASS\n"); } } void DEMOTestCapture(void) { DEMOGfxSetContextState(); DEMOCaptureInit(DEMOColorBuffer.surface.width, DEMOColorBuffer.surface.height, (DEMOColorBuffer.surface.format == GX2_SURFACE_FORMAT_TCS_R8_G8_B8_A8_UNORM) ? RGBA8 : SRGB8); DEMOCaptureCopy(&DEMOColorBuffer, "/vol/save/common/GX2Capture.tga"); DEMOPrintf("DEMO_CAPTURE_FINISH\n"); DEMOPrintf("Capture CRC=%08x\n", DEMOTestHashCRC32(DEMOCaptureData.TGAData, DEMOCaptureData.TGALength)); } int DEMOTestResult() { return DEMOTestData.result; } u32 DEMOTestFrame() { return DEMOTestData.count; } //---------------------------------------------------------------------- // Perf Test util inline s64 _DEMOTestGpuTicksToMicroseconds(u64 tick){ return (s64)(tick / (f64)(GX2_TOP_BOTTOM_CLOCK_CYCLES / 1000000)); } void DEMOTestCheckPerfBegin(void) { if (DEMOTestData.testSelect == 0xFFFFFFFF) return; if (DEMOTestData.count == DEMOTestData.testSelect) { if(DEMOGfxIsRunning()){ // Get Gpu Time pTimestamp = (u64*)DEMOGfxAllocMEM2(sizeof(u64) * 2, 8); DEMOAssert(pTimestamp); GX2Invalidate(GX2_INVALIDATE_CPU, pTimestamp, 8); GX2Flush(); // Start timer when first command buffer is flushed GX2SampleTopGPUCycle(&pTimestamp[0]); } // Get Cpu Time startCpuTime = OSGetTime(); } } void DEMOTestCheckPerfEnd(void) { if (DEMOTestData.testSelect == 0xFFFFFFFF) return; if (DEMOTestData.count == DEMOTestData.testSelect) { // Get Cpu Time endCpuTime = OSGetTime(); DEMOPrintf("Demo CPU Time: %0.f usec\n", (f32)OSTicksToMicroseconds(endCpuTime - startCpuTime)); if(DEMOGfxIsRunning()){ // Get Gpu time GX2SampleBottomGPUCycle(&pTimestamp[1]); GX2DrawDone(); endGpuTime = pTimestamp[1]; startGpuTime = pTimestamp[0]; DEMOPrintf("Demo GPU Time: %0.f usec\n", (f32)_DEMOTestGpuTicksToMicroseconds(endGpuTime - startGpuTime)); DEMOGfxFreeMEM2(pTimestamp); } if (DEMOTestData.maxCPUTime > 0) { // Verify that the CPU time was no more than the max that was set DEMOPrintf("Maximum CPU Time: %d usec\n", DEMOTestData.maxCPUTime); if ((f32)DEMOTestData.maxCPUTime < (f32)OSTicksToMicroseconds(endCpuTime - startCpuTime)) { DEMOTestData.result = 1; DEMOPrintf("Test result: FAIL\n"); return; } } if (DEMOTestData.maxGPUTime > 0) { // Verify that the GPU time was no more than the max that was set DEMOPrintf("Maximum GPU Time: %d usec\n", DEMOTestData.maxGPUTime); if ((f32)DEMOTestData.maxGPUTime < (f32)_DEMOTestGpuTicksToMicroseconds(endGpuTime - startGpuTime)) { DEMOTestData.result = 1; DEMOPrintf("Test result: FAIL\n"); return; } } } }