Query API: Re-enable Timestamp Query on Metal

Add timestamp query back in Metal supported extensions, and calculate
the timestamp period based on CPU timestamps and GPU timestamps sampled
on device.

There is a crash issue (dawn:545) on macOS 10.15 on AMD devices, but
cannot be reproduced on macOS 11.0+, we can just disable it on AMD with
macOS 10.15 in the following CL.

Bug: dawn:434
Change-Id: Icb4823e7c3115776e64c6a41fd0aea0f6536ccdf
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/51720
Commit-Queue: Hao Li <hao.x.li@intel.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
This commit is contained in:
Hao Li 2021-06-07 03:17:10 +00:00 committed by Dawn LUCI CQ
parent adc5cddde0
commit 84481bbb35
4 changed files with 111 additions and 7 deletions

View File

@ -221,10 +221,9 @@ namespace dawn_native { namespace metal {
if ([*mDevice supportsFamily:MTLGPUFamilyMac2] || if ([*mDevice supportsFamily:MTLGPUFamilyMac2] ||
[*mDevice supportsFamily:MTLGPUFamilyApple5]) { [*mDevice supportsFamily:MTLGPUFamilyApple5]) {
mSupportedExtensions.EnableExtension(Extension::PipelineStatisticsQuery); mSupportedExtensions.EnableExtension(Extension::PipelineStatisticsQuery);
// TODO(crbug.com/dawn/545): Crash occurs if we only call WriteTimestamp in a
// TODO(crbug.com/dawn/434): Not enable timestamp query here becuase it's not // command encoder without any copy commands on Metal on AMD GPU.
// clear how to convert timestamps to nanoseconds on Metal. mSupportedExtensions.EnableExtension(Extension::TimestampQuery);
// See https://github.com/gpuweb/gpuweb/issues/1325
} }
} }
if (@available(macOS 10.11, iOS 11.0, *)) { if (@available(macOS 10.11, iOS 11.0, *)) {

View File

@ -32,6 +32,10 @@
namespace dawn_native { namespace metal { namespace dawn_native { namespace metal {
namespace {
struct KalmanInfo;
}
class Device : public DeviceBase { class Device : public DeviceBase {
public: public:
static ResultOrError<Device*> Create(AdapterBase* adapter, static ResultOrError<Device*> Create(AdapterBase* adapter,
@ -127,6 +131,15 @@ namespace dawn_native { namespace metal {
// a different thread so we guard access to it with a mutex. // a different thread so we guard access to it with a mutex.
std::mutex mLastSubmittedCommandsMutex; std::mutex mLastSubmittedCommandsMutex;
NSPRef<id<MTLCommandBuffer>> mLastSubmittedCommands; NSPRef<id<MTLCommandBuffer>> mLastSubmittedCommands;
// The current estimation of timestamp period
float mTimestampPeriod = 1.0f;
// The base of CPU timestamp and GPU timestamp to measure the linear regression between GPU
// and CPU timestamps.
MTLTimestamp mCpuTimestamp API_AVAILABLE(macos(10.15), ios(14.0)) = 0;
MTLTimestamp mGpuTimestamp API_AVAILABLE(macos(10.15), ios(14.0)) = 0;
// The parameters for kalman filter
std::unique_ptr<KalmanInfo> mKalmanInfo;
}; };
}} // namespace dawn_native::metal }} // namespace dawn_native::metal

View File

@ -42,6 +42,69 @@
namespace dawn_native { namespace metal { namespace dawn_native { namespace metal {
namespace {
// The time interval for each round of kalman filter
static constexpr uint64_t kFilterIntervalInMs = static_cast<uint64_t>(NSEC_PER_SEC / 10);
struct KalmanInfo {
float filterValue; // The estimation value
float kalmanGain; // The kalman gain
float R; // The covariance of the observation noise
float P; // The a posteriori estimate covariance
};
// A simplified kalman filter for estimating timestamp period based on measured values
float KalmanFilter(KalmanInfo* info, float measuredValue) {
// Optimize kalman gain
info->kalmanGain = info->P / (info->P + info->R);
// Correct filter value
info->filterValue =
info->kalmanGain * measuredValue + (1.0 - info->kalmanGain) * info->filterValue;
// Update estimate covariance
info->P = (1.0f - info->kalmanGain) * info->P;
return info->filterValue;
}
void API_AVAILABLE(macos(10.15), ios(14))
UpdateTimestampPeriod(id<MTLDevice> device,
KalmanInfo* info,
MTLTimestamp* cpuTimestampStart,
MTLTimestamp* gpuTimestampStart,
float* timestampPeriod) {
// The filter value is converged to an optimal value when the kalman gain is less than
// 0.01. At this time, the weight of the measured value is too small to change the next
// filter value, the sampling and calculations do not need to continue anymore.
if (info->kalmanGain < 0.01f) {
return;
}
MTLTimestamp cpuTimestampEnd = 0, gpuTimestampEnd = 0;
[device sampleTimestamps:&cpuTimestampEnd gpuTimestamp:&gpuTimestampEnd];
// Update the timestamp start values when timestamp reset happens
if (cpuTimestampEnd < *cpuTimestampStart || gpuTimestampEnd < *gpuTimestampStart) {
*cpuTimestampStart = cpuTimestampEnd;
*gpuTimestampStart = gpuTimestampEnd;
return;
}
if (cpuTimestampEnd - *cpuTimestampStart >= kFilterIntervalInMs) {
// The measured timestamp period
float measurement = (cpuTimestampEnd - *cpuTimestampStart) /
static_cast<float>(gpuTimestampEnd - *gpuTimestampStart);
// Measurement update
*timestampPeriod = KalmanFilter(info, measurement);
*cpuTimestampStart = cpuTimestampEnd;
*gpuTimestampStart = gpuTimestampEnd;
}
}
} // namespace
// static // static
ResultOrError<Device*> Device::Create(AdapterBase* adapter, ResultOrError<Device*> Device::Create(AdapterBase* adapter,
NSPRef<id<MTLDevice>> mtlDevice, NSPRef<id<MTLDevice>> mtlDevice,
@ -70,6 +133,27 @@ namespace dawn_native { namespace metal {
mCommandQueue.Acquire([*mMtlDevice newCommandQueue]); mCommandQueue.Acquire([*mMtlDevice newCommandQueue]);
if (GetAdapter()->GetSupportedExtensions().IsEnabled(Extension::TimestampQuery)) {
// Make a best guess of timestamp period based on device vendor info, and converge it to
// an accurate value by the following calculations.
mTimestampPeriod =
gpu_info::IsIntel(GetAdapter()->GetPCIInfo().vendorId) ? 83.333f : 1.0f;
// Initialize kalman filter parameters
mKalmanInfo = std::make_unique<KalmanInfo>();
mKalmanInfo->filterValue = 0.0f;
mKalmanInfo->kalmanGain = 0.5f;
mKalmanInfo->R =
0.0001f; // The smaller this value is, the smaller the error of measured value is,
// the more we can trust the measured value.
mKalmanInfo->P = 1.0f;
if (@available(macos 10.15, iOS 14.0, *)) {
// Sample CPU timestamp and GPU timestamp for first time at device creation
[*mMtlDevice sampleTimestamps:&mCpuTimestamp gpuTimestamp:&mGpuTimestamp];
}
}
return DeviceBase::Initialize(new Queue(this)); return DeviceBase::Initialize(new Queue(this));
} }
@ -193,6 +277,14 @@ namespace dawn_native { namespace metal {
SubmitPendingCommandBuffer(); SubmitPendingCommandBuffer();
} }
// Just run timestamp period calculation when timestamp extension is enabled.
if (GetAdapter()->GetSupportedExtensions().IsEnabled(Extension::TimestampQuery)) {
if (@available(macos 10.15, iOS 14.0, *)) {
UpdateTimestampPeriod(GetMTLDevice(), mKalmanInfo.get(), &mCpuTimestamp,
&mGpuTimestamp, &mTimestampPeriod);
}
}
return {}; return {};
} }
@ -372,7 +464,7 @@ namespace dawn_native { namespace metal {
} }
float Device::GetTimestampPeriodInNS() const { float Device::GetTimestampPeriodInNS() const {
return 1.0f; return mTimestampPeriod;
} }
}} // namespace dawn_native::metal }} // namespace dawn_native::metal

View File

@ -534,8 +534,8 @@ TEST_P(TimestampQueryTests, QuerySetCreation) {
// Test calling timestamp query from command encoder // Test calling timestamp query from command encoder
TEST_P(TimestampQueryTests, TimestampOnCommandEncoder) { TEST_P(TimestampQueryTests, TimestampOnCommandEncoder) {
// TODO(hao.x.li@intel.com): Crash occurs if we only call WriteTimestamp in a command encoder // TODO(crbug.com/dawn/545): Crash occurs if we only call WriteTimestamp in a command encoder
// without any copy commands on Metal on AMD GPU. See https://crbug.com/dawn/545. // without any copy commands on Metal on AMD GPU.
DAWN_SUPPRESS_TEST_IF(IsMetal() && IsAMD()); DAWN_SUPPRESS_TEST_IF(IsMetal() && IsAMD());
constexpr uint32_t kQueryCount = 2; constexpr uint32_t kQueryCount = 2;