diff --git a/src/dawn_native/metal/BackendMTL.mm b/src/dawn_native/metal/BackendMTL.mm index 6155fe33ad..49e8ed31b8 100644 --- a/src/dawn_native/metal/BackendMTL.mm +++ b/src/dawn_native/metal/BackendMTL.mm @@ -221,10 +221,9 @@ namespace dawn_native { namespace metal { if ([*mDevice supportsFamily:MTLGPUFamilyMac2] || [*mDevice supportsFamily:MTLGPUFamilyApple5]) { mSupportedExtensions.EnableExtension(Extension::PipelineStatisticsQuery); - - // TODO(crbug.com/dawn/434): Not enable timestamp query here becuase it's not - // clear how to convert timestamps to nanoseconds on Metal. - // See https://github.com/gpuweb/gpuweb/issues/1325 + // 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. + mSupportedExtensions.EnableExtension(Extension::TimestampQuery); } } if (@available(macOS 10.11, iOS 11.0, *)) { diff --git a/src/dawn_native/metal/DeviceMTL.h b/src/dawn_native/metal/DeviceMTL.h index 413ba5d0d1..3749994dc1 100644 --- a/src/dawn_native/metal/DeviceMTL.h +++ b/src/dawn_native/metal/DeviceMTL.h @@ -32,6 +32,10 @@ namespace dawn_native { namespace metal { + namespace { + struct KalmanInfo; + } + class Device : public DeviceBase { public: static ResultOrError Create(AdapterBase* adapter, @@ -127,6 +131,15 @@ namespace dawn_native { namespace metal { // a different thread so we guard access to it with a mutex. std::mutex mLastSubmittedCommandsMutex; NSPRef> 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 mKalmanInfo; }; }} // namespace dawn_native::metal diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm index 60159e4e9b..45dce66ca4 100644 --- a/src/dawn_native/metal/DeviceMTL.mm +++ b/src/dawn_native/metal/DeviceMTL.mm @@ -42,6 +42,69 @@ namespace dawn_native { namespace metal { + namespace { + + // The time interval for each round of kalman filter + static constexpr uint64_t kFilterIntervalInMs = static_cast(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 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(gpuTimestampEnd - *gpuTimestampStart); + + // Measurement update + *timestampPeriod = KalmanFilter(info, measurement); + + *cpuTimestampStart = cpuTimestampEnd; + *gpuTimestampStart = gpuTimestampEnd; + } + } + + } // namespace + // static ResultOrError Device::Create(AdapterBase* adapter, NSPRef> mtlDevice, @@ -70,6 +133,27 @@ namespace dawn_native { namespace metal { 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(); + 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)); } @@ -193,6 +277,14 @@ namespace dawn_native { namespace metal { 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 {}; } @@ -372,7 +464,7 @@ namespace dawn_native { namespace metal { } float Device::GetTimestampPeriodInNS() const { - return 1.0f; + return mTimestampPeriod; } }} // namespace dawn_native::metal diff --git a/src/tests/end2end/QueryTests.cpp b/src/tests/end2end/QueryTests.cpp index d6c5a75cea..69b4de502e 100644 --- a/src/tests/end2end/QueryTests.cpp +++ b/src/tests/end2end/QueryTests.cpp @@ -534,8 +534,8 @@ TEST_P(TimestampQueryTests, QuerySetCreation) { // Test calling timestamp query from command encoder TEST_P(TimestampQueryTests, TimestampOnCommandEncoder) { - // TODO(hao.x.li@intel.com): 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. + // 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. DAWN_SUPPRESS_TEST_IF(IsMetal() && IsAMD()); constexpr uint32_t kQueryCount = 2;