WIP: cross-platform

This commit is contained in:
Akash Shakdwipeea
2026-08-24 00:27:03 +05:30
parent df8b69e09d
commit f1fd4df540
7 changed files with 267 additions and 187 deletions
+18 -80
View File
@@ -7,9 +7,11 @@ use serde::de::DeserializeOwned;
use serde_json::Value;
use crate::gpu::Wgpu;
use crate::graph::{Binding, Execution, Extent, Pass, RenderGraph, RenderPipeline, Texture};
use crate::graph::{self, Binding, Execution, Extent, Pass, RenderGraph, RenderPipeline, Texture};
use crate::render_data::RenderData;
pub mod profiler;
pub struct GpuResources {
pub buffers: HashMap<String, GpuBuffer>,
pub textures: Vec<GpuTexture>,
@@ -18,21 +20,7 @@ pub struct GpuResources {
pub render_pipelines: HashMap<String, wgpu::RenderPipeline>,
pub compute_pipelines: HashMap<String, wgpu::ComputePipeline>,
pub passes: Vec<GpuPass>,
pub profiler: Option<GpuProfiler>,
}
pub struct GpuProfiler {
query_set: wgpu::QuerySet,
resolve: wgpu::Buffer,
readback: Arc<wgpu::Buffer>,
query_count: u32,
}
pub struct ProfileMap {
readback: Arc<wgpu::Buffer>,
state: Arc<AtomicU8>,
labels: Vec<String>,
timestamp_period: f32,
pub profiler: Option<profiler::Profiler>,
}
pub struct GpuBuffer {
@@ -175,30 +163,6 @@ impl GpuResources {
})
.collect::<Result<HashMap<_, _>, _>>()?;
let profiler = (gpu.timestamp_queries && !graph.executions.is_empty()).then(|| {
let query_count = graph.executions.len() as u32 * 2;
let bytes = u64::from(query_count) * 8;
GpuProfiler {
query_set: gpu.device.create_query_set(&wgpu::QuerySetDescriptor {
label: Some("frame-profile"),
ty: wgpu::QueryType::Timestamp,
count: query_count,
}),
resolve: gpu.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("frame-profile-resolve"),
size: bytes,
usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
}),
readback: Arc::new(gpu.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("frame-profile-readback"),
size: bytes,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
})),
query_count,
}
});
let mut resources = Self {
buffers,
textures,
@@ -207,7 +171,7 @@ impl GpuResources {
render_pipelines,
compute_pipelines,
passes: Vec::new(),
profiler,
profiler: None,
};
for execution in &graph.executions {
let compiled = match execution {
@@ -256,6 +220,17 @@ impl GpuResources {
};
resources.passes.push(compiled);
}
let pass_labels = resources
.passes
.iter()
.map(|pass| pass.label().to_owned())
.collect::<Vec<_>>();
resources.profiler = (gpu.timestamp_queries && !graph.executions.is_empty()).then(|| {
profiler::Profiler::new(&gpu.device, graph.executions.len() as u32, pass_labels)
});
Ok(resources)
}
@@ -308,45 +283,6 @@ impl GpuResources {
.is_some_and(|texture| !texture.uploaded_mips.contains(&mip_level))
}
pub fn render_timestamps(&self, pass: usize) -> Option<wgpu::RenderPassTimestampWrites<'_>> {
self.profiler
.as_ref()
.map(|profiler| wgpu::RenderPassTimestampWrites {
query_set: &profiler.query_set,
beginning_of_pass_write_index: Some(pass as u32 * 2),
end_of_pass_write_index: Some(pass as u32 * 2 + 1),
})
}
pub fn compute_timestamps(&self, pass: usize) -> Option<wgpu::ComputePassTimestampWrites<'_>> {
self.profiler
.as_ref()
.map(|profiler| wgpu::ComputePassTimestampWrites {
query_set: &profiler.query_set,
beginning_of_pass_write_index: Some(pass as u32 * 2),
end_of_pass_write_index: Some(pass as u32 * 2 + 1),
})
}
pub fn resolve_profile(&self, encoder: &mut wgpu::CommandEncoder) {
let Some(profiler) = &self.profiler else {
return;
};
encoder.resolve_query_set(
&profiler.query_set,
0..profiler.query_count,
&profiler.resolve,
0,
);
encoder.copy_buffer_to_buffer(
&profiler.resolve,
0,
&profiler.readback,
0,
u64::from(profiler.query_count) * 8,
);
}
pub fn map_profile(&self, timestamp_period: f32) -> Option<ProfileMap> {
let profiler = self.profiler.as_ref()?;
let state = Arc::new(AtomicU8::new(0));
@@ -448,11 +384,13 @@ impl GpuResources {
.render_pipelines
.get(&pass.pipeline)
.ok_or("GRAPH_PIPELINE")?;
let pipeline_changed = previous_pipeline != Some(pass.pipeline.as_str());
if pipeline_changed {
encoder.set_pipeline(pipeline);
previous_pipeline = Some(pass.pipeline.as_str());
}
if pipeline_changed || previous_bindings != Some(pass.bindings.as_slice()) {
for (group, bind_group) in
self.bind_groups(pass, |group| pipeline.get_bind_group_layout(group), gpu)?
+127
View File
@@ -0,0 +1,127 @@
use serde::Serialize;
pub struct Profiler {
pub query_set: wgpu::QuerySet,
pub resolve: wgpu::Buffer,
pub readback: wgpu::Buffer,
pub query_count: u32,
pub pass_labels: Vec<String>,
}
impl Profiler {
pub fn new(device: &wgpu::Device, render_pass_count: u32, pass_labels: Vec<String>) -> Self {
let query_count = render_pass_count * 2;
let size = u64::from(query_count) * 8;
Self {
query_set: device.create_query_set(&wgpu::QuerySetDescriptor {
label: Some("frame-profile"),
ty: wgpu::QueryType::Timestamp,
count: query_count,
}),
resolve: device.create_buffer(&wgpu::BufferDescriptor {
label: Some("frame-profile-resolve"),
size,
usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
}),
readback: device.create_buffer(&wgpu::BufferDescriptor {
label: Some("frame-profile-readback"),
size,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
}),
query_count,
pass_labels,
}
}
pub fn encode_query_set(&self, encoder: &mut wgpu::CommandEncoder) {
encoder.resolve_query_set(&self.query_set, 0..self.query_count, &self.resolve, 0);
encoder.copy_buffer_to_buffer(
&self.resolve,
0,
&self.readback,
0,
u64::from(self.query_count) * 8,
);
}
pub fn read_passes(self) -> Vec<Pass> {
let bytes = self.readback.get_mapped_range(..);
let values = bytes
.chunks_exact(8)
.map(|bytes| u64::from_le_bytes(bytes.try_into().unwrap()))
.collect::<Vec<_>>();
let profile = self
.pass_labels
.into_iter()
.zip(values.chunks_exact(2))
.map(|(label, timestamps)| Pass {
name: label,
milliseconds: timestamps[1].saturating_sub(timestamps[0]) as f64
* f64::from(self.timestamp_period)
/ 1_000_000.0,
})
.collect();
drop(bytes);
self.readback.unmap();
profile
}
pub async fn read_render_pass_data(&self) -> Result<Vec<Pass>, wgpu::BufferMapError> {
self.readback
.map_async(wgpu::MapMode::Read, .., |result| {
result.and(self.read_passes());
})
.await?;
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FrameData {
pub frame: u32,
pub milliseconds: f64,
pub readback_milliseconds: f64,
pub adapter: String,
pub canvas: Canvas,
pub passes: Vec<Pass>,
}
impl std::fmt::Display for FrameData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
serde_json::to_string(self)
.map_err(|_| std::fmt::Error)?
.fmt(f)
}
}
#[derive(Serialize)]
pub struct Canvas {
pub width: u32,
pub height: u32,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Pass {
pub name: String,
pub milliseconds: f64,
}
pub enum ProfileState {
Detached,
Attached,
Ready(FrameData),
}
// pub struct ProfileMap {
// readback: Arc<wgpu::Buffer>,
// state: Arc<AtomicU8>,
// labels: Vec<String>,
// timestamp_period: f32,
// }
-1
View File
@@ -14,7 +14,6 @@ pub struct Loadout {
pub struct Store {
graphs: HashMap<String, RenderGraph>,
texture_sources: HashMap<String, HashMap<u32, web_sys::ImageBitmap>>,
active: Option<Loadout>,
}
impl Store {