Skip to content

Commit f04206f

Browse files
committed
test: harden gpu timestamp probe
1 parent b214a0b commit f04206f

3 files changed

Lines changed: 273 additions & 20 deletions

File tree

goud_engine/src/libs/graphics/backend/wgpu_backend/timestamp_probe.rs

Lines changed: 200 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ use std::{
66
use super::{
77
force_fallback_adapter,
88
timestamps::{
9-
GpuTimestampQueries, RENDER_BEGIN_QUERY, RENDER_END_QUERY, SHADOW_BEGIN_QUERY,
10-
SHADOW_END_QUERY, SUBMIT_BEGIN_QUERY, SUBMIT_END_QUERY, SUBMIT_MARKER_COPY_SIZE,
11-
TIMESTAMP_BUFFER_SIZE, TIMESTAMP_QUERY_COUNT,
9+
raw_timestamp_delta_us, GpuTimestampQueries, RENDER_BEGIN_QUERY, RENDER_END_QUERY,
10+
SHADOW_BEGIN_QUERY, SHADOW_END_QUERY, SUBMIT_BEGIN_QUERY, SUBMIT_END_QUERY,
11+
SUBMIT_MARKER_COPY_SIZE, TIMESTAMP_BUFFER_SIZE, TIMESTAMP_QUERY_COUNT,
1212
},
1313
};
1414

@@ -17,8 +17,18 @@ use super::{
1717
pub struct GpuTimestampProbeReport {
1818
/// Whether the selected adapter exposes the timestamp-query feature set.
1919
pub supported: bool,
20+
/// Adapter name used for the probe.
21+
pub adapter_name: String,
22+
/// wgpu backend used for the probe.
23+
pub backend: String,
2024
/// Raw timestamp slots resolved from the GPU query set.
2125
pub raw_queries: [u64; TIMESTAMP_QUERY_COUNT as usize],
26+
/// Resolved shadow-pass GPU timestamp duration.
27+
pub shadow_us: u64,
28+
/// Resolved main render-pass GPU timestamp duration.
29+
pub render_us: u64,
30+
/// Resolved submit-tail GPU timestamp duration.
31+
pub submit_us: u64,
2232
}
2333

2434
/// Runs a headless wgpu timestamp-query pass pair and resolves the raw query slots.
@@ -30,18 +40,26 @@ async fn probe_gpu_timestamp_queries_async() -> Result<GpuTimestampProbeReport,
3040
let instance = wgpu::Instance::default();
3141
let adapter = instance
3242
.request_adapter(&wgpu::RequestAdapterOptions {
33-
power_preference: wgpu::PowerPreference::LowPower,
43+
power_preference: wgpu::PowerPreference::HighPerformance,
3444
compatible_surface: None,
3545
force_fallback_adapter: force_fallback_adapter(),
3646
})
3747
.await
3848
.map_err(|e| format!("No suitable headless adapter: {e}"))?;
49+
let adapter_info = adapter.get_info();
50+
let adapter_name = adapter_info.name;
51+
let backend = adapter_info.backend.to_str().to_string();
3952

4053
let features = GpuTimestampQueries::requested_features(adapter.features());
4154
if features.is_empty() {
4255
return Ok(GpuTimestampProbeReport {
4356
supported: false,
57+
adapter_name,
58+
backend,
4459
raw_queries: [0; TIMESTAMP_QUERY_COUNT as usize],
60+
shadow_us: 0,
61+
render_us: 0,
62+
submit_us: 0,
4563
});
4664
}
4765

@@ -67,6 +85,12 @@ async fn probe_gpu_timestamp_queries_async() -> Result<GpuTimestampProbeReport,
6785
usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
6886
mapped_at_creation: false,
6987
});
88+
let submit_marker_buffer = device.create_buffer(&wgpu::BufferDescriptor {
89+
label: Some("goud-gpu-timestamp-probe-submit-marker"),
90+
size: SUBMIT_MARKER_COPY_SIZE,
91+
usage: wgpu::BufferUsages::COPY_SRC,
92+
mapped_at_creation: false,
93+
});
7094
let readback_buffer = device.create_buffer(&wgpu::BufferDescriptor {
7195
label: Some("goud-gpu-timestamp-probe-readback"),
7296
size: TIMESTAMP_BUFFER_SIZE,
@@ -88,6 +112,26 @@ async fn probe_gpu_timestamp_queries_async() -> Result<GpuTimestampProbeReport,
88112
"goud-gpu-timestamp-probe-depth",
89113
wgpu::TextureFormat::Depth32Float,
90114
);
115+
let shader = create_probe_shader(&device);
116+
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
117+
label: Some("goud-gpu-timestamp-probe-pipeline-layout"),
118+
bind_group_layouts: &[],
119+
immediate_size: 0,
120+
});
121+
let shadow_pipeline = create_probe_pipeline(
122+
&device,
123+
&pipeline_layout,
124+
&shader,
125+
"goud-gpu-timestamp-probe-shadow-pipeline",
126+
None,
127+
);
128+
let render_pipeline = create_probe_pipeline(
129+
&device,
130+
&pipeline_layout,
131+
&shader,
132+
"goud-gpu-timestamp-probe-render-pipeline",
133+
Some(wgpu::TextureFormat::Rgba8Unorm),
134+
);
91135

92136
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
93137
label: Some("goud-gpu-timestamp-probe"),
@@ -99,11 +143,26 @@ async fn probe_gpu_timestamp_queries_async() -> Result<GpuTimestampProbeReport,
99143
&shadow_texture.create_view(&wgpu::TextureViewDescriptor::default()),
100144
&color_texture.create_view(&wgpu::TextureViewDescriptor::default()),
101145
&depth_texture.create_view(&wgpu::TextureViewDescriptor::default()),
146+
&shadow_pipeline,
147+
&render_pipeline,
102148
);
103-
record_probe_resolve(&mut encoder, &query_set, &resolve_buffer, &readback_buffer);
149+
record_probe_submit_tail(
150+
&mut encoder,
151+
&query_set,
152+
&submit_marker_buffer,
153+
&readback_buffer,
154+
);
155+
queue.submit(std::iter::once(encoder.finish()));
156+
157+
wait_for_submitted_work(&device, &queue, "timestamp pass")?;
158+
159+
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
160+
label: Some("goud-gpu-timestamp-probe-resolve"),
161+
});
162+
record_probe_readback(&mut encoder, &query_set, &resolve_buffer, &readback_buffer);
104163
queue.submit(std::iter::once(encoder.finish()));
105164

106-
read_probe_queries(&device, &readback_buffer)
165+
read_probe_queries(&device, &queue, &readback_buffer, adapter_name, backend)
107166
}
108167

109168
fn create_probe_texture(
@@ -127,19 +186,86 @@ fn create_probe_texture(
127186
})
128187
}
129188

189+
fn create_probe_shader(device: &wgpu::Device) -> wgpu::ShaderModule {
190+
device.create_shader_module(wgpu::ShaderModuleDescriptor {
191+
label: Some("goud-gpu-timestamp-probe-shader"),
192+
source: wgpu::ShaderSource::Wgsl(
193+
r#"
194+
@vertex
195+
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> @builtin(position) vec4<f32> {
196+
let x = f32(i32(vertex_index) - 1);
197+
let y = select(-1.0, 1.0, vertex_index == 1u);
198+
return vec4<f32>(x, y, 0.0, 1.0);
199+
}
200+
201+
@fragment
202+
fn fs_main() -> @location(0) vec4<f32> {
203+
return vec4<f32>(1.0, 0.25, 0.0, 1.0);
204+
}
205+
"#
206+
.into(),
207+
),
208+
})
209+
}
210+
211+
fn create_probe_pipeline(
212+
device: &wgpu::Device,
213+
layout: &wgpu::PipelineLayout,
214+
shader: &wgpu::ShaderModule,
215+
label: &'static str,
216+
color_format: Option<wgpu::TextureFormat>,
217+
) -> wgpu::RenderPipeline {
218+
let color_targets = color_format.map(|format| {
219+
[Some(wgpu::ColorTargetState {
220+
format,
221+
blend: None,
222+
write_mask: wgpu::ColorWrites::ALL,
223+
})]
224+
});
225+
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
226+
label: Some(label),
227+
layout: Some(layout),
228+
vertex: wgpu::VertexState {
229+
module: shader,
230+
entry_point: Some("vs_main"),
231+
buffers: &[],
232+
compilation_options: wgpu::PipelineCompilationOptions::default(),
233+
},
234+
primitive: wgpu::PrimitiveState::default(),
235+
depth_stencil: Some(wgpu::DepthStencilState {
236+
format: wgpu::TextureFormat::Depth32Float,
237+
depth_write_enabled: Some(true),
238+
depth_compare: Some(wgpu::CompareFunction::Less),
239+
stencil: wgpu::StencilState::default(),
240+
bias: wgpu::DepthBiasState::default(),
241+
}),
242+
multisample: wgpu::MultisampleState::default(),
243+
fragment: color_targets.as_ref().map(|targets| wgpu::FragmentState {
244+
module: shader,
245+
entry_point: Some("fs_main"),
246+
targets,
247+
compilation_options: wgpu::PipelineCompilationOptions::default(),
248+
}),
249+
multiview_mask: None,
250+
cache: None,
251+
})
252+
}
253+
130254
fn record_probe_passes(
131255
encoder: &mut wgpu::CommandEncoder,
132256
query_set: &wgpu::QuerySet,
133257
use_render_pass_writes: bool,
134258
shadow_view: &wgpu::TextureView,
135259
color_view: &wgpu::TextureView,
136260
depth_view: &wgpu::TextureView,
261+
shadow_pipeline: &wgpu::RenderPipeline,
262+
render_pipeline: &wgpu::RenderPipeline,
137263
) {
138264
if !use_render_pass_writes {
139265
encoder.write_timestamp(query_set, SHADOW_BEGIN_QUERY);
140266
}
141267
{
142-
let _shadow_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
268+
let mut shadow_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
143269
label: Some("goud-gpu-timestamp-probe-shadow"),
144270
color_attachments: &[],
145271
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
@@ -158,13 +284,15 @@ fn record_probe_passes(
158284
occlusion_query_set: None,
159285
multiview_mask: None,
160286
});
287+
shadow_pass.set_pipeline(shadow_pipeline);
288+
shadow_pass.draw(0..3, 0..1);
161289
}
162290
if !use_render_pass_writes {
163291
encoder.write_timestamp(query_set, SHADOW_END_QUERY);
164292
encoder.write_timestamp(query_set, RENDER_BEGIN_QUERY);
165293
}
166294
{
167-
let _render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
295+
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
168296
label: Some("goud-gpu-timestamp-probe-render"),
169297
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
170298
view: color_view,
@@ -191,34 +319,77 @@ fn record_probe_passes(
191319
occlusion_query_set: None,
192320
multiview_mask: None,
193321
});
322+
render_pass.set_pipeline(render_pipeline);
323+
render_pass.draw(0..3, 0..1);
194324
}
195325
if !use_render_pass_writes {
196326
encoder.write_timestamp(query_set, RENDER_END_QUERY);
197327
}
198328
}
199329

200-
fn record_probe_resolve(
330+
fn record_probe_submit_tail(
201331
encoder: &mut wgpu::CommandEncoder,
202332
query_set: &wgpu::QuerySet,
203-
resolve_buffer: &wgpu::Buffer,
333+
submit_marker_buffer: &wgpu::Buffer,
204334
readback_buffer: &wgpu::Buffer,
205335
) {
206336
encoder.write_timestamp(query_set, SUBMIT_BEGIN_QUERY);
207337
encoder.copy_buffer_to_buffer(
208-
resolve_buffer,
338+
submit_marker_buffer,
209339
0,
210340
readback_buffer,
211341
0,
212342
SUBMIT_MARKER_COPY_SIZE,
213343
);
214344
encoder.write_timestamp(query_set, SUBMIT_END_QUERY);
345+
}
346+
347+
fn record_probe_readback(
348+
encoder: &mut wgpu::CommandEncoder,
349+
query_set: &wgpu::QuerySet,
350+
resolve_buffer: &wgpu::Buffer,
351+
readback_buffer: &wgpu::Buffer,
352+
) {
215353
encoder.resolve_query_set(query_set, 0..TIMESTAMP_QUERY_COUNT, resolve_buffer, 0);
216354
encoder.copy_buffer_to_buffer(resolve_buffer, 0, readback_buffer, 0, TIMESTAMP_BUFFER_SIZE);
217355
}
218356

357+
fn wait_for_submitted_work(
358+
device: &wgpu::Device,
359+
queue: &wgpu::Queue,
360+
label: &'static str,
361+
) -> Result<(), String> {
362+
let (tx, rx) = mpsc::channel();
363+
queue.on_submitted_work_done(move || {
364+
let _ = tx.send(());
365+
});
366+
367+
let deadline = Instant::now() + Duration::from_secs(5);
368+
loop {
369+
device
370+
.poll(wgpu::PollType::Poll)
371+
.map_err(|e| format!("Probe {label} poll failed: {e}"))?;
372+
match rx.try_recv() {
373+
Ok(()) => return Ok(()),
374+
Err(TryRecvError::Disconnected) => {
375+
return Err(format!("Probe {label} callback disconnected"));
376+
}
377+
Err(TryRecvError::Empty) if Instant::now() >= deadline => {
378+
return Err(format!("Timed out waiting for timestamp probe {label}"));
379+
}
380+
Err(TryRecvError::Empty) => {
381+
std::thread::sleep(Duration::from_millis(10));
382+
}
383+
}
384+
}
385+
}
386+
219387
fn read_probe_queries(
220388
device: &wgpu::Device,
389+
queue: &wgpu::Queue,
221390
readback_buffer: &wgpu::Buffer,
391+
adapter_name: String,
392+
backend: String,
222393
) -> Result<GpuTimestampProbeReport, String> {
223394
let slice = readback_buffer.slice(..);
224395
let (tx, rx) = mpsc::channel();
@@ -254,8 +425,26 @@ fn read_probe_queries(
254425
drop(mapped);
255426
readback_buffer.unmap();
256427

428+
let timestamp_period_ns = queue.get_timestamp_period();
257429
Ok(GpuTimestampProbeReport {
258430
supported: true,
431+
adapter_name,
432+
backend,
433+
shadow_us: raw_timestamp_delta_us(
434+
raw_queries[SHADOW_BEGIN_QUERY as usize],
435+
raw_queries[SHADOW_END_QUERY as usize],
436+
timestamp_period_ns,
437+
),
438+
render_us: raw_timestamp_delta_us(
439+
raw_queries[RENDER_BEGIN_QUERY as usize],
440+
raw_queries[RENDER_END_QUERY as usize],
441+
timestamp_period_ns,
442+
),
443+
submit_us: raw_timestamp_delta_us(
444+
raw_queries[SUBMIT_BEGIN_QUERY as usize],
445+
raw_queries[SUBMIT_END_QUERY as usize],
446+
timestamp_period_ns,
447+
),
259448
raw_queries,
260449
})
261450
}

0 commit comments

Comments
 (0)