Files
renderdoc/renderdoc/driver/ihv/amd/amd_counters.cpp
T
Amit PrakashandBaldur Karlsson 2db56c2a86 Remove peak clock flag in opening GPA context
* Remove peak clock flag while interrogating GPA about counter
information.
2018-12-11 11:08:28 +00:00

790 lines
20 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015-2018 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "amd_counters.h"
#include "common/common.h"
#include "common/timing.h"
#include "core/core.h"
#include "core/plugins.h"
#include "official/GPUPerfAPI/Include/GPUPerfAPIFunctionTypes.h"
#include "strings/string_utils.h"
inline bool AMD_FAILED(GPA_Status status)
{
return status < GPA_STATUS_OK;
}
inline bool AMD_SUCCEEDED(GPA_Status status)
{
return status >= GPA_STATUS_OK;
}
static void GPA_LoggingCallback(GPA_Logging_Type messageType, const char *pMessage)
{
if(messageType == GPA_LOGGING_ERROR)
{
RDCWARN(pMessage);
}
else
{
RDCLOG(pMessage);
}
}
#define GPA_ERROR(text, status) \
RDCERR(text ". %s", m_pGPUPerfAPI->GPA_GetStatusAsStr((GPA_Status)status));
#define GPA_WARNING(text, status) \
RDCWARN(text ". %s", m_pGPUPerfAPI->GPA_GetStatusAsStr((GPA_Status)status));
AMDCounters::AMDCounters(bool dx12DebugLayerEnabled)
: m_pGPUPerfAPI(NULL),
m_gpaSessionCounter(0u),
m_passCounter(-1),
m_dx12DebugLayerEnabled(dx12DebugLayerEnabled)
{
}
bool AMDCounters::Init(ApiType apiType, void *pContext)
{
#if DISABLED(RDOC_WIN32) && DISABLED(RDOC_LINUX)
(void)m_dx12DebugLayerEnabled;
return false;
#else
std::string dllName("GPUPerfAPI");
switch(apiType)
{
case ApiType::Dx11: dllName += "DX11"; break;
case ApiType::Dx12: dllName += "DX12"; break;
case ApiType::Ogl: dllName += "GL"; break;
case ApiType::Vk: dllName += "VK"; break;
default:
RDCWARN(
"AMD GPU performance counters could not be initialized successfully. "
"Unsupported API type specified");
return false;
}
#if ENABLED(RDOC_WIN32)
#if ENABLED(RDOC_X64)
dllName += "-x64";
#endif
dllName += ".dll";
#else
dllName = "lib" + dllName;
dllName += ".so";
#endif
// first try in the plugin location it will be in distributed builds
std::string dllPath = LocatePluginFile("amd/counters", dllName.c_str());
void *module = Process::LoadModule(dllPath.c_str());
if(module == NULL)
{
module = Process::LoadModule(dllName.c_str());
}
if(module == NULL)
{
RDCWARN(
"AMD GPU performance counters could not be initialized successfully. "
"Are you missing the DLLs?");
return false;
}
bool disableCounters = false;
if(apiType == ApiType::Dx12 && m_dx12DebugLayerEnabled)
{
// Disable counters in DX12 Debug configuration
void *versionFunc = Process::GetFunctionAddress(module, "GPA_GetVersion");
if(NULL == versionFunc)
{
disableCounters = true;
}
}
if(disableCounters)
{
RDCLOG("AMD counters are disabled in DX12 Debug version for GPA v3.0");
return false;
}
GPA_GetFuncTablePtrType getFuncTable =
(GPA_GetFuncTablePtrType)Process::GetFunctionAddress(module, "GPA_GetFuncTable");
m_pGPUPerfAPI = new GPAFunctionTable();
if(getFuncTable)
{
GPA_Status gpaStatus = getFuncTable((void *)m_pGPUPerfAPI);
if(AMD_FAILED(gpaStatus))
{
RDCERR("Failed to load the GPA function entrypoint.");
return false;
}
}
else
{
SAFE_DELETE(m_pGPUPerfAPI);
RDCERR("Failed to get GPA function table. Invalid dynamic library?");
return false;
}
GPA_Logging_Type loggingType = GPA_LOGGING_ERROR;
#if ENABLED(RDOC_DEVEL)
loggingType = GPA_LOGGING_ERROR_AND_MESSAGE;
#endif
GPA_Status status = m_pGPUPerfAPI->GPA_RegisterLoggingCallback(loggingType, GPA_LoggingCallback);
if(AMD_FAILED(status))
{
GPA_ERROR("Failed to initialize logging", status);
return false;
}
status = m_pGPUPerfAPI->GPA_Initialize(GPA_INITIALIZE_DEFAULT_BIT);
if(AMD_FAILED(status))
{
GPA_ERROR("Initialization failed", status);
SAFE_DELETE(m_pGPUPerfAPI);
return false;
}
status = m_pGPUPerfAPI->GPA_OpenContext(
pContext, GPA_OPENCONTEXT_HIDE_SOFTWARE_COUNTERS_BIT | GPA_OPENCONTEXT_CLOCK_MODE_NONE_BIT,
&m_gpaContextId);
if(AMD_FAILED(status))
{
GPA_WARNING("Open context for counters failed", status);
m_pGPUPerfAPI->GPA_Destroy();
SAFE_DELETE(m_pGPUPerfAPI);
return false;
}
m_Counters = EnumerateCounters();
m_apiType = apiType;
status = m_pGPUPerfAPI->GPA_CloseContext(m_gpaContextId);
if(AMD_FAILED(status))
{
GPA_ERROR("Close context failed", status);
}
m_gpaContextId = NULL;
return true;
#endif
}
AMDCounters::~AMDCounters()
{
if(m_pGPUPerfAPI)
{
if(m_gpaContextId)
{
GPA_Status status = m_pGPUPerfAPI->GPA_CloseContext(m_gpaContextId);
if(AMD_FAILED(status))
{
GPA_ERROR("Close context failed", status);
}
}
GPA_Status status = m_pGPUPerfAPI->GPA_Destroy();
if(AMD_FAILED(status))
{
GPA_ERROR("Destroy failed", status);
}
SAFE_DELETE(m_pGPUPerfAPI);
}
}
std::map<uint32_t, CounterDescription> AMDCounters::EnumerateCounters()
{
std::map<uint32_t, CounterDescription> counters;
gpa_uint32 num;
GPA_Status status = m_pGPUPerfAPI->GPA_GetNumCounters(m_gpaContextId, &num);
if(AMD_FAILED(status))
{
GPA_ERROR("Get number of counters", status);
return counters;
}
for(uint32_t i = 0; i < num; ++i)
{
GPA_Usage_Type usageType;
status = m_pGPUPerfAPI->GPA_GetCounterUsageType(m_gpaContextId, i, &usageType);
if(AMD_FAILED(status))
{
GPA_ERROR("Get counter usage type.", status);
return counters;
}
// Ignore percentage counters due to aggregate roll-up support
if(usageType == GPA_USAGE_TYPE_PERCENTAGE)
{
continue;
}
CounterDescription desc = InternalGetCounterDescription(i);
desc.counter = MakeAMDCounter(i);
counters[i] = desc;
m_PublicToInternalCounter[desc.counter] = i;
}
return counters;
}
std::vector<GPUCounter> AMDCounters::GetPublicCounterIds() const
{
std::vector<GPUCounter> ret;
for(const std::pair<GPUCounter, uint32_t> &entry : m_PublicToInternalCounter)
ret.push_back(entry.first);
return ret;
}
CounterDescription AMDCounters::GetCounterDescription(GPUCounter counter)
{
return m_Counters[m_PublicToInternalCounter[counter]];
}
CounterDescription AMDCounters::InternalGetCounterDescription(uint32_t internalIndex)
{
CounterDescription desc = {};
const char *tmp = NULL;
GPA_Status status = m_pGPUPerfAPI->GPA_GetCounterName(m_gpaContextId, internalIndex, &tmp);
if(AMD_FAILED(status))
{
GPA_ERROR("Get counter name.", status);
return desc;
}
desc.name = tmp;
status = m_pGPUPerfAPI->GPA_GetCounterDescription(m_gpaContextId, internalIndex, &tmp);
if(AMD_FAILED(status))
{
GPA_ERROR("Get counter description.", status);
return desc;
}
desc.description = tmp;
status = m_pGPUPerfAPI->GPA_GetCounterGroup(m_gpaContextId, internalIndex, &tmp);
if(AMD_FAILED(status))
{
GPA_ERROR("Get counter category.", status);
return desc;
}
desc.category = tmp;
GPA_Usage_Type usageType;
status = m_pGPUPerfAPI->GPA_GetCounterUsageType(m_gpaContextId, internalIndex, &usageType);
if(AMD_FAILED(status))
{
GPA_ERROR("Get counter usage type.", status);
return desc;
}
switch(usageType)
{
case GPA_USAGE_TYPE_RATIO: ///< Result is a ratio of two different values or types
desc.unit = CounterUnit::Ratio;
break;
case GPA_USAGE_TYPE_PERCENTAGE: ///< Result is a percentage, typically within [0,100] range,
/// but may be higher for certain counters
desc.unit = CounterUnit::Percentage;
break;
case GPA_USAGE_TYPE_CYCLES: ///< Result is in clock cycles
desc.unit = CounterUnit::Cycles;
break;
case GPA_USAGE_TYPE_MILLISECONDS: ///< Result is in milliseconds
desc.unit = CounterUnit::Seconds;
break;
case GPA_USAGE_TYPE_KILOBYTES: ///< Result is in kilobytes
case GPA_USAGE_TYPE_BYTES: ///< Result is in bytes
desc.unit = CounterUnit::Bytes;
break;
case GPA_USAGE_TYPE_ITEMS: ///< Result is a count of items or objects (ie, vertices,
/// triangles, threads, pixels, texels, etc)
desc.unit = CounterUnit::Absolute;
break;
default: desc.unit = CounterUnit::Absolute;
}
GPA_Data_Type type;
status = m_pGPUPerfAPI->GPA_GetCounterDataType(m_gpaContextId, internalIndex, &type);
if(AMD_FAILED(status))
{
GPA_ERROR("Get counter data type.", status);
return desc;
}
// results should either be float32/64 or uint32/64 as the GetSample functions only support those
switch(type)
{
case GPA_DATA_TYPE_FLOAT64: ///< Result will be a 64-bit float
desc.resultType = CompType::Double;
desc.resultByteWidth = sizeof(double);
break;
case GPA_DATA_TYPE_UINT64: ///< Result will be a 64-bit unsigned int
desc.resultType = CompType::UInt;
desc.resultByteWidth = sizeof(uint64_t);
break;
default: desc.resultType = CompType::UInt; desc.resultByteWidth = sizeof(uint32_t);
}
status = m_pGPUPerfAPI->GPA_GetCounterUuid(m_gpaContextId, internalIndex, (GPA_UUID *)&desc.uuid);
if(AMD_FAILED(status))
{
GPA_ERROR("Get counter UUID.", status);
return desc;
}
return desc;
}
void AMDCounters::EnableCounter(GPUCounter counter)
{
const uint32_t internalIndex = m_PublicToInternalCounter[counter];
GPA_Status status = m_pGPUPerfAPI->GPA_EnableCounter(m_gpaSessionInfo.back(), internalIndex);
if(AMD_FAILED(status))
{
GPA_ERROR("Enable counter.", status);
}
}
void AMDCounters::EnableAllCounters()
{
GPA_Status status = m_pGPUPerfAPI->GPA_EnableAllCounters(m_gpaSessionInfo.back());
if(AMD_FAILED(status))
{
GPA_ERROR("Enable all counters.", status);
}
}
void AMDCounters::DisableAllCounters()
{
GPA_Status status = m_pGPUPerfAPI->GPA_DisableAllCounters(m_gpaSessionInfo.back());
if(AMD_FAILED(status))
{
GPA_ERROR("Disable all counters.", status);
}
}
bool AMDCounters::BeginMeasurementMode(ApiType apiType, void *pContext)
{
RDCASSERT(apiType == m_apiType);
RDCASSERT(pContext);
RDCASSERT(!m_gpaContextId);
GPA_Status status = m_pGPUPerfAPI->GPA_OpenContext(
pContext, GPA_OPENCONTEXT_HIDE_SOFTWARE_COUNTERS_BIT | GPA_OPENCONTEXT_CLOCK_MODE_PEAK_BIT,
&m_gpaContextId);
if(AMD_FAILED(status))
{
GPA_WARNING("Creating context for analysis failed", status);
return false;
}
return true;
}
void AMDCounters::EndMeasurementMode()
{
if(m_gpaContextId)
{
GPA_Status status = m_pGPUPerfAPI->GPA_CloseContext(m_gpaContextId);
if(AMD_FAILED(status))
{
GPA_ERROR("Close context failed", status);
}
m_gpaContextId = NULL;
}
}
uint32_t AMDCounters::GetPassCount()
{
gpa_uint32 numRequiredPasses = 0;
GPA_Status status = m_pGPUPerfAPI->GPA_GetPassCount(m_gpaSessionInfo.back(), &numRequiredPasses);
if(AMD_FAILED(status))
{
GPA_ERROR("Get pass count.", status);
}
return (uint32_t)numRequiredPasses;
}
uint32_t AMDCounters::CreateSession()
{
uint32_t sessionID = m_gpaSessionCounter;
GPA_SessionId gpaSessionId = NULL;
GPA_Status status = m_pGPUPerfAPI->GPA_CreateSession(
m_gpaContextId, GPA_SESSION_SAMPLE_TYPE_DISCRETE_COUNTER, &gpaSessionId);
if(AMD_FAILED(status))
{
GPA_ERROR("Create session.", status);
}
else
{
InitializeCmdInfo();
m_gpaSessionInfo.push_back(gpaSessionId);
++m_gpaSessionCounter;
}
return sessionID;
}
void AMDCounters::BeginSession(uint32_t sessionId)
{
GPA_Status status = m_pGPUPerfAPI->GPA_BeginSession(m_gpaSessionInfo.at(sessionId));
if(AMD_FAILED(status))
{
GPA_ERROR("Begin session.", status);
}
else
{
m_passCounter = -1;
}
}
void AMDCounters::EndSesssion(uint32_t sessionId)
{
GPA_Status status = m_pGPUPerfAPI->GPA_EndSession(m_gpaSessionInfo.at(sessionId));
if(AMD_FAILED(status))
{
GPA_ERROR("End session.", status);
}
m_passCounter = 0u;
}
void AMDCounters::InitializeCmdInfo()
{
switch(m_apiType)
{
case ApiType::Dx11:
case ApiType::Ogl: break;
case ApiType::Vk:
case ApiType::Dx12:
if(NULL == m_gpaCmdListInfo.m_pCommandListMap)
{
m_gpaCmdListInfo.m_pCommandListMap = new std::map<void *, GPA_CommandListId>();
}
break;
}
}
void AMDCounters::DeInitializeCmdInfo()
{
switch(m_apiType)
{
case ApiType::Dx11:
case ApiType::Ogl: break;
case ApiType::Vk:
case ApiType::Dx12: SAFE_DELETE(m_gpaCmdListInfo.m_pCommandListMap); break;
}
}
void AMDCounters::DeleteSession(uint32_t sessionId)
{
GPA_Status status = m_pGPUPerfAPI->GPA_DeleteSession(m_gpaSessionInfo.at(sessionId));
DeInitializeCmdInfo();
if(AMD_FAILED(status))
{
GPA_ERROR("Create session.", status);
}
}
std::vector<CounterResult> AMDCounters::GetCounterData(uint32_t sessionID, uint32_t maxSampleIndex,
const std::vector<uint32_t> &eventIDs,
const std::vector<GPUCounter> &counters)
{
std::vector<CounterResult> ret;
bool isReady = false;
const uint32_t timeoutPeriod = 10000; // ms
PerformanceTimer timeout;
do
{
isReady = IsSessionReady(sessionID);
if(!isReady)
{
Threading::Sleep(0);
if(timeout.GetMilliseconds() > timeoutPeriod)
{
GPA_LoggingCallback(GPA_LOGGING_ERROR, "GetCounterData failed due to elapsed timeout.");
return ret;
}
}
} while(!isReady);
GPA_SessionId gpaSessionId = m_gpaSessionInfo.at(sessionID);
size_t sampleResultSize = 0u;
GPA_Status status = m_pGPUPerfAPI->GPA_GetSampleResultSize(gpaSessionId, 0, &sampleResultSize);
if(AMD_FAILED(status))
{
GPA_ERROR("Get Sample Result Size", status);
}
void *pSampleResult = malloc(sampleResultSize);
for(uint32_t s = 0; s < maxSampleIndex; s++)
{
status = m_pGPUPerfAPI->GPA_GetSampleResult(gpaSessionId, s, sampleResultSize, pSampleResult);
if(AMD_FAILED(status))
{
GPA_ERROR("Get Sample Result ", status);
}
for(size_t c = 0; c < counters.size(); c++)
{
const CounterDescription desc = GetCounterDescription(counters[c]);
const uint32_t internalIndex = m_PublicToInternalCounter[counters[c]];
GPA_Usage_Type usageType;
status = m_pGPUPerfAPI->GPA_GetCounterUsageType(m_gpaContextId, internalIndex, &usageType);
if(AMD_FAILED(status))
{
GPA_ERROR("Get counter usage type.", status);
}
switch(desc.resultType)
{
case CompType::UInt:
{
uint64_t value = 0;
memcpy(&value, (uint64_t *)(pSampleResult) + c, sizeof(uint64_t));
// normalise units as expected
if(usageType == GPA_USAGE_TYPE_KILOBYTES)
{
value *= 1000;
}
if(desc.unit == CounterUnit::Percentage)
{
value = RDCCLAMP(value, (uint64_t)0ULL, (uint64_t)100ULL);
}
ret.push_back(CounterResult(eventIDs[s], counters[c], (uint64_t)value));
}
break;
case CompType::Double:
{
double value = 0.0;
memcpy(&value, (double *)(pSampleResult) + c, sizeof(double));
// normalise units as expected
if(usageType == GPA_USAGE_TYPE_KILOBYTES)
{
value *= 1000.0;
}
else if(usageType == GPA_USAGE_TYPE_MILLISECONDS)
{
value /= 1000.0;
}
ret.push_back(CounterResult(eventIDs[s], counters[c], value));
}
break;
default: RDCASSERT(0); break;
};
}
}
free(pSampleResult);
DeleteSession(sessionID);
return ret;
}
bool AMDCounters::IsSessionReady(uint32_t sessionIndex)
{
GPA_Status status = m_pGPUPerfAPI->GPA_IsSessionComplete(m_gpaSessionInfo.at(sessionIndex));
if(AMD_FAILED(status))
{
GPA_ERROR("Is session ready", status);
}
return status == GPA_STATUS_OK;
}
void AMDCounters::BeginPass()
{
m_passCounter++;
if(m_apiType == ApiType::Dx12 || m_apiType == ApiType::Vk)
{
if(NULL != m_gpaCmdListInfo.m_pCommandListMap)
{
m_gpaCmdListInfo.m_pCommandListMap->clear();
}
}
}
void AMDCounters::EndPass()
{
bool isReady = false;
const uint32_t timeoutPeriod = 10000; // ms
PerformanceTimer timeout;
do
{
isReady =
GPA_STATUS_OK == m_pGPUPerfAPI->GPA_IsPassComplete(m_gpaSessionInfo.back(), m_passCounter);
if(!isReady)
{
Threading::Sleep(0);
PerformanceTimer endTime;
if(timeout.GetMilliseconds() > timeoutPeriod)
{
GPA_LoggingCallback(GPA_LOGGING_ERROR, "GPA_IsPassComplete failed due to elapsed timeout.");
break;
}
}
} while(!isReady);
}
void AMDCounters::BeginSample(uint32_t sampleID, void *pCommandList)
{
GPA_CommandListId startingSampleCmd = GPA_NULL_COMMAND_LIST;
switch(m_apiType)
{
case ApiType::Dx11:
case ApiType::Ogl: startingSampleCmd = m_gpaCmdListInfo.m_gpaCommandListId; break;
case ApiType::Vk:
case ApiType::Dx12:
startingSampleCmd = m_gpaCmdListInfo.m_pCommandListMap->at(pCommandList);
break;
}
GPA_Status status = m_pGPUPerfAPI->GPA_BeginSample(sampleID, startingSampleCmd);
if(AMD_FAILED(status))
{
GPA_ERROR("Begin sample.", status);
}
}
void AMDCounters::EndSample(void *pCommandList)
{
GPA_CommandListId endingSampleCmd = GPA_NULL_COMMAND_LIST;
switch(m_apiType)
{
case ApiType::Dx11:
case ApiType::Ogl: endingSampleCmd = m_gpaCmdListInfo.m_gpaCommandListId; break;
case ApiType::Vk:
case ApiType::Dx12:
endingSampleCmd = m_gpaCmdListInfo.m_pCommandListMap->at(pCommandList);
break;
}
GPA_Status status = m_pGPUPerfAPI->GPA_EndSample(endingSampleCmd);
if(AMD_FAILED(status))
{
GPA_ERROR("End sample.", status);
}
}
void AMDCounters::BeginCommandList(void *pCommandList)
{
void *cmdList = NULL;
GPA_Command_List_Type cmdType = GPA_COMMAND_LIST_NONE;
switch(m_apiType)
{
case ApiType::Dx11:
case ApiType::Ogl: cmdList = GPA_NULL_COMMAND_LIST; break;
case ApiType::Vk:
case ApiType::Dx12:
cmdList = pCommandList;
cmdType = GPA_COMMAND_LIST_PRIMARY;
break;
}
GPA_CommandListId gpaCmdId = NULL;
GPA_Status status = m_pGPUPerfAPI->GPA_BeginCommandList(m_gpaSessionInfo.back(), m_passCounter,
cmdList, cmdType, &gpaCmdId);
if(AMD_FAILED(status))
{
GPA_ERROR("BeginCommandList.", status);
}
else
{
switch(m_apiType)
{
case ApiType::Dx11:
case ApiType::Ogl: m_gpaCmdListInfo.m_gpaCommandListId = gpaCmdId; break;
case ApiType::Vk:
case ApiType::Dx12:
m_gpaCmdListInfo.m_pCommandListMap->insert(
std::pair<void *, GPA_CommandListId>(cmdList, gpaCmdId));
break;
}
}
}
void AMDCounters::EndCommandList(void *pCommandList)
{
GPA_CommandListId endingCmd = GPA_NULL_COMMAND_LIST;
switch(m_apiType)
{
case ApiType::Dx11:
case ApiType::Ogl: endingCmd = m_gpaCmdListInfo.m_gpaCommandListId; break;
case ApiType::Vk:
case ApiType::Dx12: endingCmd = m_gpaCmdListInfo.m_pCommandListMap->at(pCommandList); break;
}
GPA_Status status = m_pGPUPerfAPI->GPA_EndCommandList(endingCmd);
if(AMD_FAILED(status))
{
GPA_ERROR("EndCommandList.", status);
}
}