/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use gleam::gl; use std::mem; use std::rc::Rc; use crate::device::GpuFrameId; use crate::profiler::GpuProfileTag; #[derive(Copy, Clone, Debug)] pub enum GpuDebugMethod { None, MarkerEXT, KHR, } #[derive(Debug, Clone)] pub struct GpuTimer { pub tag: GpuProfileTag, pub time_ns: u64, } #[derive(Debug, Clone)] pub struct GpuSampler { pub tag: GpuProfileTag, pub count: u64, } pub struct QuerySet { set: Vec, data: Vec, pending: gl::GLuint, } impl QuerySet { fn new() -> Self { QuerySet { set: Vec::new(), data: Vec::new(), pending: 0, } } fn reset(&mut self) { self.data.clear(); self.pending = 0; } fn add(&mut self, value: T) -> Option { assert_eq!(self.pending, 0); self.set.get(self.data.len()).cloned().map(|query_id| { self.data.push(value); self.pending = query_id; query_id }) } fn take(&mut self, fun: F) -> Vec { let mut data = mem::replace(&mut self.data, Vec::new()); for (value, &query) in data.iter_mut().zip(self.set.iter()) { fun(value, query) } data } } pub struct GpuFrameProfile { gl: Rc, timers: QuerySet, samplers: QuerySet, frame_id: GpuFrameId, inside_frame: bool, debug_method: GpuDebugMethod, } impl GpuFrameProfile { fn new(gl: Rc, debug_method: GpuDebugMethod) -> Self { GpuFrameProfile { gl, timers: QuerySet::new(), samplers: QuerySet::new(), frame_id: GpuFrameId::new(0), inside_frame: false, debug_method } } fn enable_timers(&mut self, count: i32) { self.timers.set = self.gl.gen_queries(count); } fn disable_timers(&mut self) { if !self.timers.set.is_empty() { self.gl.delete_queries(&self.timers.set); } self.timers.set = Vec::new(); } fn enable_samplers(&mut self, count: i32) { self.samplers.set = self.gl.gen_queries(count); } fn disable_samplers(&mut self) { if !self.samplers.set.is_empty() { self.gl.delete_queries(&self.samplers.set); } self.samplers.set = Vec::new(); } fn begin_frame(&mut self, frame_id: GpuFrameId) { self.frame_id = frame_id; self.timers.reset(); self.samplers.reset(); self.inside_frame = true; } fn end_frame(&mut self) { self.finish_timer(); self.finish_sampler(); self.inside_frame = false; } fn finish_timer(&mut self) { debug_assert!(self.inside_frame); if self.timers.pending != 0 { self.gl.end_query(gl::TIME_ELAPSED); self.timers.pending = 0; } } fn finish_sampler(&mut self) { debug_assert!(self.inside_frame); if self.samplers.pending != 0 { self.gl.end_query(gl::SAMPLES_PASSED); self.samplers.pending = 0; } } fn start_timer(&mut self, tag: GpuProfileTag) -> GpuTimeQuery { self.finish_timer(); let marker = GpuMarker::new(&self.gl, tag.label, self.debug_method); if let Some(query) = self.timers.add(GpuTimer { tag, time_ns: 0 }) { self.gl.begin_query(gl::TIME_ELAPSED, query); } GpuTimeQuery(marker) } fn start_sampler(&mut self, tag: GpuProfileTag) -> GpuSampleQuery { self.finish_sampler(); if let Some(query) = self.samplers.add(GpuSampler { tag, count: 0 }) { self.gl.begin_query(gl::SAMPLES_PASSED, query); } GpuSampleQuery } fn build_samples(&mut self) -> (GpuFrameId, Vec, Vec) { debug_assert!(!self.inside_frame); let gl = &self.gl; ( self.frame_id, self.timers.take(|timer, query| { timer.time_ns = gl.get_query_object_ui64v(query, gl::QUERY_RESULT) }), self.samplers.take(|sampler, query| { sampler.count = gl.get_query_object_ui64v(query, gl::QUERY_RESULT) }), ) } } impl Drop for GpuFrameProfile { fn drop(&mut self) { self.disable_timers(); self.disable_samplers(); } } const NUM_PROFILE_FRAMES: usize = 4; pub struct GpuProfiler { gl: Rc, frames: [GpuFrameProfile; NUM_PROFILE_FRAMES], next_frame: usize, debug_method: GpuDebugMethod } impl GpuProfiler { pub fn new(gl: Rc, debug_method: GpuDebugMethod) -> Self { let f = || GpuFrameProfile::new(Rc::clone(&gl), debug_method); let frames = [f(), f(), f(), f()]; GpuProfiler { gl, next_frame: 0, frames, debug_method } } pub fn enable_timers(&mut self) { const MAX_TIMERS_PER_FRAME: i32 = 256; for frame in &mut self.frames { frame.enable_timers(MAX_TIMERS_PER_FRAME); } } pub fn disable_timers(&mut self) { for frame in &mut self.frames { frame.disable_timers(); } } pub fn enable_samplers(&mut self) { const MAX_SAMPLERS_PER_FRAME: i32 = 16; if cfg!(target_os = "macos") { warn!("Expect macOS driver bugs related to sample queries") } for frame in &mut self.frames { frame.enable_samplers(MAX_SAMPLERS_PER_FRAME); } } pub fn disable_samplers(&mut self) { for frame in &mut self.frames { frame.disable_samplers(); } } pub fn build_samples(&mut self) -> (GpuFrameId, Vec, Vec) { self.frames[self.next_frame].build_samples() } pub fn begin_frame(&mut self, frame_id: GpuFrameId) { self.frames[self.next_frame].begin_frame(frame_id); } pub fn end_frame(&mut self) { self.frames[self.next_frame].end_frame(); self.next_frame = (self.next_frame + 1) % self.frames.len(); } pub fn start_timer(&mut self, tag: GpuProfileTag) -> GpuTimeQuery { self.frames[self.next_frame].start_timer(tag) } pub fn start_sampler(&mut self, tag: GpuProfileTag) -> GpuSampleQuery { self.frames[self.next_frame].start_sampler(tag) } pub fn finish_sampler(&mut self, _sampler: GpuSampleQuery) { self.frames[self.next_frame].finish_sampler() } pub fn start_marker(&mut self, label: &str) -> GpuMarker { GpuMarker::new(&self.gl, label, self.debug_method) } pub fn place_marker(&mut self, label: &str) { GpuMarker::fire(&self.gl, label, self.debug_method) } } #[must_use] pub struct GpuMarker { gl: Option<(Rc, GpuDebugMethod)>, } impl GpuMarker { fn new(gl: &Rc, message: &str, debug_method: GpuDebugMethod) -> Self { let gl = match debug_method { GpuDebugMethod::KHR => { gl.push_debug_group_khr(gl::DEBUG_SOURCE_APPLICATION, 0, message); Some((Rc::clone(gl), debug_method)) }, GpuDebugMethod::MarkerEXT => { gl.push_group_marker_ext(message); Some((Rc::clone(gl), debug_method)) }, GpuDebugMethod::None => None, }; GpuMarker { gl } } fn fire(gl: &Rc, message: &str, debug_method: GpuDebugMethod) { match debug_method { GpuDebugMethod::KHR => gl.debug_message_insert_khr(gl::DEBUG_SOURCE_APPLICATION, gl::DEBUG_TYPE_MARKER, 0, gl::DEBUG_SEVERITY_NOTIFICATION, message), GpuDebugMethod::MarkerEXT => gl.insert_event_marker_ext(message), GpuDebugMethod::None => {} }; } } impl Drop for GpuMarker { fn drop(&mut self) { if let Some((ref gl, debug_method)) = self.gl { match debug_method { GpuDebugMethod::KHR => gl.pop_debug_group_khr(), GpuDebugMethod::MarkerEXT => gl.pop_group_marker_ext(), GpuDebugMethod::None => {} }; } } } #[must_use] pub struct GpuTimeQuery(#[allow(dead_code)] GpuMarker); #[must_use] pub struct GpuSampleQuery;