//----------------------------------------------------------------------------- /// Handles operations that need to occur before profiling an API call. /// \param funcId The FuncId corresponding to the API call being traced. /// \param pWrappedCmdBuf The interface pointer used to invoke the API call. //----------------------------------------------------------------------------- void VktFrameProfilerLayer::PreCall(FuncId funcId, VktWrappedCmdBuf* pWrappedCmdBuf) { if (pWrappedCmdBuf->IsProfilingEnabled() && ShouldProfileFunction(funcId)) { osThreadId threadId = osGetCurrentThreadId(); SampleInfo* pSampleInfo = GetSampleInfoForThread(threadId); if (pSampleInfo != nullptr) { UINT64 nextSampleId = SetNextSampleId(pSampleInfo); ProfilerMeasurementId measurementId = {}; VktUtil::ConstructMeasurementInfo(funcId, nextSampleId, pWrappedCmdBuf, GetParentLayerManager()->GetFrameCount(), pWrappedCmdBuf->FillCount(), measurementId); ProfilerResultCode beginResult = pWrappedCmdBuf->BeginCmdMeasurement(&measurementId); if (beginResult == PROFILER_SUCCESS) { pSampleInfo->mSampleId = measurementId.sampleId; pSampleInfo->mbBeginSampleSuccessful = true; } else { Log(logERROR, "Failed BeginCmdMeasurement. CmdBuf='0x%p' SampleId='%d'\n", pWrappedCmdBuf->AppHandle(), measurementId.sampleId); } } else { Log(logERROR, "Failed to find or create SampleInfo instance for Thread %d\n", threadId); } } }
//----------------------------------------------------------------------------- /// Construct a measurement info structure for each call that will be profiled. /// \param inFuncId The function ID for the function being profiled. /// \param inSampleId The SampleID associated with the profiled command. /// \param pCmdListWrapped The command list that executed the profiled command. /// \param out A ProfilerMeasurementId containing metadata for the new measurement. //----------------------------------------------------------------------------- void DX12FrameProfilerLayer::ConstructMeasurementInfo(FuncId inFuncId, UINT64 inSampleId, Wrapped_ID3D12GraphicsCommandListCustom* pCmdListWrapped, ProfilerMeasurementId& out) { out.pWrappedCmdList = pCmdListWrapped; out.mCmdListType = pCmdListWrapped->GetCmdListType(); out.mSampleId = inSampleId; out.mFrameIndex = GetParentLayerManager()->GetFrameCount(); out.mFunctionId = inFuncId; }
//-------------------------------------------------------------------------- /// BeginFrame is called when a new frame is started, and should setup the /// collection process to log each API call issued during the frame. /// \return Nothing. //-------------------------------------------------------------------------- void MultithreadedTraceAnalyzerLayer::BeginFrame() { bool bFrameCaptureWithSaveActive = GetParentLayerManager()->mCmdFrameCaptureWithSave.IsActive(); // Check if automatic tracing is enabled for a specific frame. Determine which trace type by examining the result. int autotraceFlags = GetTraceTypeFlags(); // Pick up new requests that are active and flip the switch to begin tracing calls. bool bLinkedTraceRequested = (mCmdLinkedTrace.IsActive() || bFrameCaptureWithSaveActive) || (autotraceFlags == kTraceType_Linked) || mbLinkedTraceForCapture; // If a linked trace is required, turn on both trace switches. bool bAPITraceNeeded = OnlyAPITraceRequested() || bLinkedTraceRequested || (autotraceFlags & kTraceType_API); bool bGPUTraceNeeded = OnlyGPUTraceRequested() || bLinkedTraceRequested || (autotraceFlags & kTraceType_GPU); mFramestartTime = mFramestartTimer.GetRaw(); if (bAPITraceNeeded || bGPUTraceNeeded) { // Set the flag indicating that the frame is being traced. We'll be rendering the next frame when this flag is checked. mLastTracedFrameIndex = GetParentLayerManager()->GetFrameCount(); // Clear out the previous trace data before tracing the new frame. Clear(); // We need to enable tracing no matter what so that we go into the PreCall/PostCall. SetCollectTrace(true); if (bAPITraceNeeded) { // Enable global trace collection so API and GPU trace can happen. BeforeAPITrace(); mbCollectingApiTrace = true; } if (bGPUTraceNeeded) { BeforeGPUTrace(); // Enable GPU time collection ModernAPIFrameProfilerLayer* frameProfiler = GetParentLayerManager()->GetFrameProfilerLayer(); frameProfiler->SetProfilingEnabled(true); mbCollectingGPUTrace = true; } } }
//-------------------------------------------------------------------------- /// Check if the newly-started frame should be automatically traced at a specific frame. /// \returns An eTraceType value, corresponding to the value set in the server config. //-------------------------------------------------------------------------- int MultithreadedTraceAnalyzerLayer::GetTraceTypeFlags() { int traceTypeFlags = kTraceType_None; // It's time to capture a frame. Check the trace type that was requested. if (GetParentLayerManager()->IsAutocaptureFrame()) { traceTypeFlags = SG_GET_INT(OptionTraceType); } return traceTypeFlags; }
//-------------------------------------------------------------------------- /// Write a trace's metadata file and return the contenst through the out-param. /// \param inHeaderString The full response string for a collected linked trace request. /// \param inResponseString Response string /// \param outMetadataXML The XML metadata string to return to the client. /// \returns True if writing the metadata file was succesful. //-------------------------------------------------------------------------- bool MultithreadedTraceAnalyzerLayer::WriteTraceAndMetadataFiles(const gtASCIIString& inHeaderString, const gtASCIIString& inResponseString, std::string& outMetadataXML) { bool bWrittenSuccessfully = false; // Empty out the incoming path to the metadata file. We'll know the exact path later. outMetadataXML.assign(""); // Use this object to pass data into and out of the GetSessionManagaerData() method. SessionManagerData smd; smd.frameIndex = GetParentLayerManager()->GetFrameCount(); bool result = SessionManager::Instance()->GetSessionManagerData(smd); if (result == false) { return result; } gtASCIIString pathToMetadataFile = smd.pathToDataDirectory; //pathToDataDirectory; pathToMetadataFile.appendFormattedString("%s", smd.metadataFilename.asCharArray()); gtString fullMetadataFilepathAsGTString; fullMetadataFilepathAsGTString.fromASCIIString(pathToMetadataFile.asCharArray()); osFile metadataFile(fullMetadataFilepathAsGTString); bool bMetadataFileOpened = metadataFile.open(osChannel::OS_ASCII_TEXT_CHANNEL, osFile::OS_OPEN_TO_WRITE); // If we've successfully opened the metadata file, we'll also attempt to write the trace file. if (bMetadataFileOpened == false) { Log(logERROR, "Failed to open trace metadata file for writing: '%s'\n", smd.metadataFilename.asCharArray()); return false; } osFilePath traceFileDirectory; traceFileDirectory.setPath(osFilePath::OS_TEMP_DIRECTORY); traceFileDirectory.appendSubDirectory(smd.toolDirectory); // Construct a filename for the cached trace response. gtASCIIString fullTraceFilename; fullTraceFilename.appendFormattedString("LinkedTrace-%s-%d-%d-%d-%d-%d-%d.ltr", smd.appName.asASCIICharArray(), smd.year, smd.month, smd.day, smd.hour, smd.minute, smd.second); gtASCIIString fullTraceFilePath = smd.pathToDataDirectory; fullTraceFilePath.appendFormattedString("%s", fullTraceFilename.asCharArray()); gtString fullTraceResponseFilepathGTString; fullTraceResponseFilepathGTString.fromASCIIString(fullTraceFilePath.asCharArray()); // Write the contents of the trace response file. osFile traceResponseFile(fullTraceResponseFilepathGTString); bool bTraceResponseFileOpened = traceResponseFile.open(osChannel::OS_ASCII_TEXT_CHANNEL, osFile::OS_OPEN_TO_WRITE); if (bTraceResponseFileOpened) { traceResponseFile.writeString(inHeaderString); traceResponseFile.writeString(inResponseString); traceResponseFile.close(); } else { Log(logERROR, "Failed to write trace response to file: '%s'\n", fullTraceResponseFilepathGTString.asASCIICharArray()); } // Write the filename for the associated trace response that was just collected. std::string traceFilepathAsString; traceFilepathAsString.assign(fullTraceResponseFilepathGTString.asASCIICharArray()); TraceMetadata metadataToWrite; // Insert the location of the metadata file being written out. metadataToWrite.mMetadataFilepath = pathToMetadataFile.asCharArray(); // Insert the path to the cached trace file. metadataToWrite.mPathToTraceFile = fullTraceFilePath.asCharArray(); // Write object files info. It is currently assumed trace and object data will exists simultaneously. metadataToWrite.mPathToObjectTreeFile = smd.pathToDataDirectory.asCharArray(); metadataToWrite.mPathToObjectTreeFile.append("ObjectTree.xml"); metadataToWrite.mPathToObjectDatabaseFile = smd.pathToDataDirectory.asCharArray(); metadataToWrite.mPathToObjectDatabaseFile.append("FullObjectDatabase.xml"); ModernAPIFrameDebuggerLayer* frameDebugger = GetParentLayerManager()->GetFrameDebuggerLayer(); unsigned char* pngData = NULL; unsigned int numBytes = 0; // NOTE: Passing in 0 for the width and height will cause the renderer to render the PNG image at the same resolution as the applications frame buffer (i.e full resolution). bool bCapturedSuccessfully = frameDebugger->CaptureFrameBuffer(0, 0, &pngData, &numBytes, true); if (bCapturedSuccessfully) { gtASCIIString fullImageFilename; fullImageFilename.appendFormattedString("%s_FrameBuffer%d.png", smd.appName.asASCIICharArray(), smd.frameIndex); gtASCIIString imageFilepath = smd.pathToDataDirectory; imageFilepath.appendFormattedString("%s", fullImageFilename.asCharArray()); FILE* frameBufferImageFile = fopen(imageFilepath.asCharArray(), "wb"); if (frameBufferImageFile != NULL) { fwrite(pngData, sizeof(unsigned char), numBytes, frameBufferImageFile); fclose(frameBufferImageFile); // Add the captured image's path into the XML metadata. metadataToWrite.mPathToFrameBufferImage = imageFilepath.asCharArray(); } else { Log(logERROR, "Failed to write frame buffer image file.\n"); } SAFE_DELETE_ARRAY(pngData); } else { metadataToWrite.mPathToFrameBufferImage = "ERROR - Failed to capture frame buffer image."; Log(logERROR, "Failed to capture frame buffer for captured frame.\n"); } // @TODO: This is placeholder for now to get a prototype working. // We should also be able to set this to "Capture". metadataToWrite.mTraceType = kTraceType_Linked; // @TODO: This is temporary until TraceMetadata generation is moved to the LayerManager. FrameInfo frameInfo; frameDebugger->GetFrameInfo(&frameInfo); // Populate the metadata structure with the values stored in the LayerManager. metadataToWrite.mFrameInfo = &frameInfo; metadataToWrite.mArchitecture = smd.moduleArchitecture; metadataToWrite.mAPICallCount = GetNumTracedAPICalls(); metadataToWrite.mDrawCallCount = GetNumTracedDrawCalls(); bool bMetadataWriteSuccessful = WriteMetadataFile(&metadataToWrite, pathToMetadataFile.asCharArray(), outMetadataXML); if (bMetadataWriteSuccessful) { bWrittenSuccessfully = true; } return bWrittenSuccessfully; }
//-------------------------------------------------------------------------- /// Handle what happens when a Linked Trace is requested. We can either: /// 1. Return the trace response as normal. /// 2. Cache the response to disk, and generate a "trace metadata" file used to retrieve the trace later. /// \param inFullResponseString The response string built by tracing the application. /// \param inbSaveResponseToFile A switch used to determine which response method to use. //-------------------------------------------------------------------------- void MultithreadedTraceAnalyzerLayer::HandleLinkedTraceResponse(gtASCIIString& inFullResponseString, bool inbSaveResponseToFile) { ModernAPILayerManager* parentLayerManager = GetParentLayerManager(); if (parentLayerManager->InCapturePlayer()) { const std::string& metadataFile = parentLayerManager->GetPathToTargetMetadataFile(); if (metadataFile.length() > 0) { // Read the metadata file and store the contents in a structure. TraceMetadata traceMetadata; traceMetadata.mFrameInfo = new FrameInfo; bool bReadMetadataFileSuccessfully = ReadMetadataFile(metadataFile, &traceMetadata); if (bReadMetadataFileSuccessfully) { gtASCIIString traceContents; bool bReadTraceSuccessfully = LoadTraceFile(traceMetadata.mPathToTraceFile, traceContents); if (bReadTraceSuccessfully) { // At this point the full trace response text should be loaded into our string and ready to be sent back to the client. mCmdLinkedTrace.Send(traceContents.asCharArray()); } else { Log(logERROR, "Failed to read trace file at '%s'.", traceMetadata.mPathToTraceFile.c_str()); } } else { Log(logERROR, "Failed to read metadata file at '%s'.", metadataFile.c_str()); } // Destroy the FrameInfo instance that was created above. SAFE_DELETE(traceMetadata.mFrameInfo); } else { Log(logERROR, "Failed to locate valid path to trace metadata file."); } } else { gtASCIIString traceHeaderBlock; bool bBuiltHeaderSuccessfully = GenerateLinkedTraceHeader(traceHeaderBlock); if (bBuiltHeaderSuccessfully) { bool bKeypressTrigger = parentLayerManager->IsTraceTriggeredByKeypress(); // Collect a trace and generate the trace metadata string. Write the trace and metadata files to disk. std::string metadataXMLString; bool bWriteMetadataSuccessful = WriteTraceAndMetadataFiles(traceHeaderBlock, inFullResponseString, metadataXMLString); // If the trace wasn't triggered by a keypress, we'll need to send a response back through either of the following commands. CommandResponse& frameCaptureWithSaveResponse = (inbSaveResponseToFile == true) ? parentLayerManager->mCmdFrameCaptureWithSave : mCmdLinkedTrace; if (bWriteMetadataSuccessful) { // We only need to send the response back through a request if the client triggered collection. if (!bKeypressTrigger) { // Check if we want to cache the response to disk, or return it as-is. if (inbSaveResponseToFile) { if (bWriteMetadataSuccessful) { // Send a response back to the client indicating which trace metadata file was written to disk. frameCaptureWithSaveResponse.Send(metadataXMLString.c_str()); } else { Log(logERROR, "Failed to write trace metadata XML.\n"); frameCaptureWithSaveResponse.Send("Failed"); } } else { // Send a response containing the API and GPU trace text. frameCaptureWithSaveResponse.Send(inFullResponseString.asCharArray()); } } else { Log(logMESSAGE, "Successfully traced frame %d.\n", parentLayerManager->GetFrameCount()); } } else { Log(logERROR, "Failed to write trace metadata XML.\n"); // If a failed trace collection was triggered by a command, we need to respond with an error message. if (!bKeypressTrigger) { frameCaptureWithSaveResponse.Send("Failed"); } } } } }
//-------------------------------------------------------------------------- /// EndFrame is used to signal the end of a rendered frame and to stop/send /// the captured results that were logged during the frame render. /// \return Nothing. //-------------------------------------------------------------------------- void MultithreadedTraceAnalyzerLayer::EndFrame() { // Check again which trace type is active at the end of the frame. Need to match how it was started. int autotraceFlags = GetTraceTypeFlags(); // Will we be required to dump the trace response to a file on disk? bool bSaveResponseToFile = GetParentLayerManager()->mCmdFrameCaptureWithSave.IsActive() || mbLinkedTraceForCapture; // If the linked trace was requested, return all of the results through a single response. bool bLinkedTraceRequested = bSaveResponseToFile || mCmdLinkedTrace.IsActive() || (autotraceFlags == kTraceType_Linked); // If we want a linked trace, we'll need responses from all trace types. bool bAPITraceResponseNeeded = OnlyAPITraceRequested() || bLinkedTraceRequested || (autotraceFlags & kTraceType_API); bool bGPUTraceResponseNeeded = OnlyGPUTraceRequested() || bLinkedTraceRequested || (autotraceFlags & kTraceType_GPU); if (bAPITraceResponseNeeded || bGPUTraceResponseNeeded) { // We're done collecting, so turn off trace collection. SetCollectTrace(false); // Also disable frame profiling. ModernAPIFrameProfilerLayer* frameProfiler = GetParentLayerManager()->GetFrameProfilerLayer(); frameProfiler->SetProfilingEnabled(false); AfterAPITrace(); AfterGPUTrace(); std::string apiTraceResponseString, gpuTraceResponseString; if (bAPITraceResponseNeeded) { mbCollectingApiTrace = false; apiTraceResponseString.assign(GetAPITraceTXT().c_str()); } if (bGPUTraceResponseNeeded) { mbGPUTraceAlreadyCollected = false; gpuTraceResponseString.assign(GetGPUTraceTXT().c_str()); } gtASCIIString fullResponseString; if (bAPITraceResponseNeeded) { fullResponseString += apiTraceResponseString.c_str(); fullResponseString += "\n"; } if (bGPUTraceResponseNeeded) { fullResponseString += gpuTraceResponseString.c_str(); fullResponseString += "\n"; } // If the autotrace flags are anything besides "None," we'll just store the trace log internally so the client can pick it up later. bool bShouldCacheForAutotrace = (autotraceFlags != kTraceType_None); if (bShouldCacheForAutotrace) { // Don't send the response back through a command yet. // The client will know to pick it up through a special AutoCapture command. mCachedTraceResponse.assign(fullResponseString.asCharArray()); mbWaitingForAutocaptureClient = true; } else { // Send the response string back to the server through a specific request command. if (bLinkedTraceRequested) { HandleLinkedTraceResponse(fullResponseString, bSaveResponseToFile); // If tracing was active, disable it after handling the response. if (mbLinkedTraceForCapture) { DisableLinkedTraceCollection(); } } else { if (bAPITraceResponseNeeded) { m_apiTraceTXT.Send(apiTraceResponseString.c_str()); } else if (bGPUTraceResponseNeeded) { m_cmdGPUTrace.Send(gpuTraceResponseString.c_str()); } } } } // When AutoCapture is enabled, we need to delay rendering so the user has time to retrieve the cached response. if (mbWaitingForAutocaptureClient) { // For AutoCapture the client will send the separate request below to collect the cached trace response. if (mCmdAutoCaptureCachedTrace.IsActive()) { // We're done waiting to send the cached response to the client. Move on with playback. mbWaitingForAutocaptureClient = false; mCmdAutoCaptureCachedTrace.Send(mCachedTraceResponse.c_str()); mCachedTraceResponse.clear(); } else { // Sleep to give the user a chance to connect during playback. osSleep(500); } } }