| // Copyright 2021 The piet-gpu authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| // Also licensed under MIT license, at your choice. |
| |
| //! Support for timer queries. |
| //! |
| //! Likely some of this should be upstreamed into metal-rs. |
| |
| use std::{ffi::CStr, ptr::null_mut}; |
| |
| use cocoa_foundation::{ |
| base::id, |
| foundation::{NSRange, NSUInteger}, |
| }; |
| use metal::{DeviceRef, MTLStorageMode}; |
| use objc::{class, msg_send, sel, sel_impl}; |
| |
| pub struct CounterSampleBuffer { |
| id: id, |
| count: u64, |
| } |
| |
| pub struct CounterSet { |
| id: id, |
| } |
| |
| #[derive(Default)] |
| pub struct TimeCalibration { |
| pub cpu_start_ts: u64, |
| pub gpu_start_ts: u64, |
| pub cpu_end_ts: u64, |
| pub gpu_end_ts: u64, |
| } |
| |
| impl Drop for CounterSampleBuffer { |
| fn drop(&mut self) { |
| unsafe { msg_send![self.id, release] } |
| } |
| } |
| |
| impl Clone for CounterSampleBuffer { |
| fn clone(&self) -> CounterSampleBuffer { |
| unsafe { |
| CounterSampleBuffer { |
| id: msg_send![self.id, retain], |
| count: self.count, |
| } |
| } |
| } |
| } |
| |
| impl CounterSampleBuffer { |
| pub fn id(&self) -> id { |
| self.id |
| } |
| } |
| |
| impl Drop for CounterSet { |
| fn drop(&mut self) { |
| unsafe { msg_send![self.id, release] } |
| } |
| } |
| |
| impl CounterSet { |
| pub fn get_timer_counter_set(device: &DeviceRef) -> Option<CounterSet> { |
| unsafe { |
| // TODO: version check |
| let sets: id = msg_send!(device, counterSets); |
| let count: NSUInteger = msg_send![sets, count]; |
| for i in 0..count { |
| let set: id = msg_send![sets, objectAtIndex: i]; |
| let name: id = msg_send![set, name]; |
| let name_cstr = CStr::from_ptr(msg_send![name, UTF8String]); |
| if name_cstr.to_bytes() == b"timestamp" { |
| return Some(CounterSet { id: set }); |
| } |
| } |
| None |
| } |
| } |
| } |
| |
| // copied from metal-rs; should be in common utilities maybe? |
| fn nsstring_as_str(nsstr: &objc::runtime::Object) -> &str { |
| let bytes = unsafe { |
| let bytes: *const std::os::raw::c_char = msg_send![nsstr, UTF8String]; |
| bytes as *const u8 |
| }; |
| let len: NSUInteger = unsafe { msg_send![nsstr, length] }; |
| unsafe { |
| let bytes = std::slice::from_raw_parts(bytes, len as usize); |
| std::str::from_utf8(bytes).unwrap() |
| } |
| } |
| |
| impl CounterSampleBuffer { |
| pub fn new( |
| device: &DeviceRef, |
| count: u64, |
| counter_set: &CounterSet, |
| ) -> Option<CounterSampleBuffer> { |
| unsafe { |
| let desc_cls = class!(MTLCounterSampleBufferDescriptor); |
| let descriptor: id = msg_send![desc_cls, alloc]; |
| let _: id = msg_send![descriptor, init]; |
| let count = count as NSUInteger; |
| let () = msg_send![descriptor, setSampleCount: count]; |
| let () = msg_send![descriptor, setCounterSet: counter_set.id]; |
| let () = msg_send![ |
| descriptor, |
| setStorageMode: MTLStorageMode::Shared as NSUInteger |
| ]; |
| let mut error: id = null_mut(); |
| let buf: id = msg_send![device, newCounterSampleBufferWithDescriptor: descriptor error: &mut error]; |
| let () = msg_send![descriptor, release]; |
| if !error.is_null() { |
| let description = msg_send![error, localizedDescription]; |
| println!( |
| "error allocating sample buffer, code = {}", |
| nsstring_as_str(description) |
| ); |
| let () = msg_send![error, release]; |
| return None; |
| } |
| Some(CounterSampleBuffer { id: buf, count }) |
| } |
| } |
| |
| // Read the timestamps. |
| // |
| // Safety: the lifetime of the returned slice is wrong, it's actually autoreleased. |
| pub unsafe fn resolve(&self) -> &[u64] { |
| let range = NSRange::new(0, self.count); |
| let data: id = msg_send![self.id, resolveCounterRange: range]; |
| if data.is_null() { |
| &[] |
| } else { |
| let bytes: *const u64 = msg_send![data, bytes]; |
| std::slice::from_raw_parts(bytes, self.count as usize) |
| } |
| } |
| } |
| |
| impl TimeCalibration { |
| /// Convert GPU timestamp into CPU time base. |
| /// |
| /// See https://developer.apple.com/documentation/metal/performance_tuning/correlating_cpu_and_gpu_timestamps |
| pub fn correlate(&self, raw_ts: u64) -> f64 { |
| let delta_cpu = self.cpu_end_ts - self.cpu_start_ts; |
| let delta_gpu = self.gpu_end_ts - self.gpu_start_ts; |
| let adj_ts = if delta_gpu > 0 { |
| let scale = delta_cpu as f64 / delta_gpu as f64; |
| self.cpu_start_ts as f64 + (raw_ts as f64 - self.gpu_start_ts as f64) * scale |
| } else { |
| // Default is ns on Apple Silicon; on other hardware this will be wrong |
| raw_ts as f64 |
| }; |
| adj_ts * 1e-9 |
| } |
| } |