Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1fd4df540 |
Generated
+23
@@ -117,12 +117,32 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-channel"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.34"
|
version = "0.3.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.34"
|
version = "0.3.34"
|
||||||
@@ -136,6 +156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-macro",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"slab",
|
"slab",
|
||||||
@@ -717,6 +738,8 @@ dependencies = [
|
|||||||
name = "yawn-core"
|
name = "yawn-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-util",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ crate-type = ["cdylib"]
|
|||||||
path = "lib.rs"
|
path = "lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
futures-channel = "0.3"
|
||||||
|
futures-util = { version = "0.3", default-features = false, features = ["async-await-macro", "std"] }
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|||||||
+18
-17
@@ -34,7 +34,7 @@ struct RowRequest {
|
|||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub struct Core {
|
pub struct Core {
|
||||||
data: Rc<RefCell<RenderData>>,
|
data: Rc<RefCell<RenderData>>,
|
||||||
gpu: Rc<RefCell<Option<Wgpu>>>,
|
gpu: Wgpu,
|
||||||
store: Rc<RefCell<Store>>,
|
store: Rc<RefCell<Store>>,
|
||||||
render: Rc<RenderLoop>,
|
render: Rc<RenderLoop>,
|
||||||
}
|
}
|
||||||
@@ -42,25 +42,26 @@ pub struct Core {
|
|||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl Core {
|
impl Core {
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new(arena_bytes: u32) -> Result<Self, JsError> {
|
pub async fn new(arena_bytes: u32, canvas: web_sys::OffscreenCanvas) -> Result<Self, JsError> {
|
||||||
Ok(Self {
|
|
||||||
data: Rc::new(RefCell::new(
|
|
||||||
RenderData::new(arena_bytes).map_err(JsError::new)?,
|
|
||||||
)),
|
|
||||||
gpu: Rc::new(RefCell::new(None)),
|
|
||||||
store: Rc::new(RefCell::new(Store::default())),
|
|
||||||
render: Rc::new(RenderLoop::new()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn initialize(&self, canvas: web_sys::OffscreenCanvas) -> Result<(), JsError> {
|
|
||||||
let gpu = Wgpu::new(canvas)
|
let gpu = Wgpu::new(canvas)
|
||||||
.await
|
.await
|
||||||
.map_err(|error| JsError::new(&error))?;
|
.map_err(|error| JsError::new(&error))?;
|
||||||
*self.gpu.borrow_mut() = Some(gpu);
|
|
||||||
self.render
|
let render = Rc::new(RenderLoop::new());
|
||||||
.start(self.gpu.clone(), self.store.clone(), self.data.clone());
|
|
||||||
Ok(())
|
let data = Rc::new(RefCell::new(
|
||||||
|
RenderData::new(arena_bytes).map_err(JsError::new)?,
|
||||||
|
));
|
||||||
|
|
||||||
|
// what does clone mean here
|
||||||
|
render.start(&gpu, data.clone());
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
data,
|
||||||
|
gpu,
|
||||||
|
store: Rc::new(RefCell::new(Store::default())),
|
||||||
|
render,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rows(&self) -> String {
|
pub fn rows(&self) -> String {
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ use serde::de::DeserializeOwned;
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::gpu::Wgpu;
|
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;
|
use crate::render_data::RenderData;
|
||||||
|
|
||||||
|
pub mod profiler;
|
||||||
|
|
||||||
pub struct GpuResources {
|
pub struct GpuResources {
|
||||||
pub buffers: HashMap<String, GpuBuffer>,
|
pub buffers: HashMap<String, GpuBuffer>,
|
||||||
pub textures: Vec<GpuTexture>,
|
pub textures: Vec<GpuTexture>,
|
||||||
@@ -18,21 +20,7 @@ pub struct GpuResources {
|
|||||||
pub render_pipelines: HashMap<String, wgpu::RenderPipeline>,
|
pub render_pipelines: HashMap<String, wgpu::RenderPipeline>,
|
||||||
pub compute_pipelines: HashMap<String, wgpu::ComputePipeline>,
|
pub compute_pipelines: HashMap<String, wgpu::ComputePipeline>,
|
||||||
pub passes: Vec<GpuPass>,
|
pub passes: Vec<GpuPass>,
|
||||||
pub profiler: Option<GpuProfiler>,
|
pub profiler: Option<profiler::Profiler>,
|
||||||
}
|
|
||||||
|
|
||||||
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 struct GpuBuffer {
|
pub struct GpuBuffer {
|
||||||
@@ -175,30 +163,6 @@ impl GpuResources {
|
|||||||
})
|
})
|
||||||
.collect::<Result<HashMap<_, _>, _>>()?;
|
.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 {
|
let mut resources = Self {
|
||||||
buffers,
|
buffers,
|
||||||
textures,
|
textures,
|
||||||
@@ -207,7 +171,7 @@ impl GpuResources {
|
|||||||
render_pipelines,
|
render_pipelines,
|
||||||
compute_pipelines,
|
compute_pipelines,
|
||||||
passes: Vec::new(),
|
passes: Vec::new(),
|
||||||
profiler,
|
profiler: None,
|
||||||
};
|
};
|
||||||
for execution in &graph.executions {
|
for execution in &graph.executions {
|
||||||
let compiled = match execution {
|
let compiled = match execution {
|
||||||
@@ -256,6 +220,17 @@ impl GpuResources {
|
|||||||
};
|
};
|
||||||
resources.passes.push(compiled);
|
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)
|
Ok(resources)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,45 +283,6 @@ impl GpuResources {
|
|||||||
.is_some_and(|texture| !texture.uploaded_mips.contains(&mip_level))
|
.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> {
|
pub fn map_profile(&self, timestamp_period: f32) -> Option<ProfileMap> {
|
||||||
let profiler = self.profiler.as_ref()?;
|
let profiler = self.profiler.as_ref()?;
|
||||||
let state = Arc::new(AtomicU8::new(0));
|
let state = Arc::new(AtomicU8::new(0));
|
||||||
@@ -448,11 +384,13 @@ impl GpuResources {
|
|||||||
.render_pipelines
|
.render_pipelines
|
||||||
.get(&pass.pipeline)
|
.get(&pass.pipeline)
|
||||||
.ok_or("GRAPH_PIPELINE")?;
|
.ok_or("GRAPH_PIPELINE")?;
|
||||||
|
|
||||||
let pipeline_changed = previous_pipeline != Some(pass.pipeline.as_str());
|
let pipeline_changed = previous_pipeline != Some(pass.pipeline.as_str());
|
||||||
if pipeline_changed {
|
if pipeline_changed {
|
||||||
encoder.set_pipeline(pipeline);
|
encoder.set_pipeline(pipeline);
|
||||||
previous_pipeline = Some(pass.pipeline.as_str());
|
previous_pipeline = Some(pass.pipeline.as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if pipeline_changed || previous_bindings != Some(pass.bindings.as_slice()) {
|
if pipeline_changed || previous_bindings != Some(pass.bindings.as_slice()) {
|
||||||
for (group, bind_group) in
|
for (group, bind_group) in
|
||||||
self.bind_groups(pass, |group| pipeline.get_bind_group_layout(group), gpu)?
|
self.bind_groups(pass, |group| pipeline.get_bind_group_layout(group), gpu)?
|
||||||
|
|||||||
@@ -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,
|
||||||
|
// }
|
||||||
@@ -14,7 +14,6 @@ pub struct Loadout {
|
|||||||
pub struct Store {
|
pub struct Store {
|
||||||
graphs: HashMap<String, RenderGraph>,
|
graphs: HashMap<String, RenderGraph>,
|
||||||
texture_sources: HashMap<String, HashMap<u32, web_sys::ImageBitmap>>,
|
texture_sources: HashMap<String, HashMap<u32, web_sys::ImageBitmap>>,
|
||||||
active: Option<Loadout>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Store {
|
impl Store {
|
||||||
|
|||||||
+78
-88
@@ -4,7 +4,7 @@ use std::rc::Rc;
|
|||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
|
|
||||||
use crate::gpu::Wgpu;
|
use crate::gpu::Wgpu;
|
||||||
use crate::gpu_resource::{GpuPass, ProfileMap};
|
use crate::gpu_resource::{profiler, GpuPass, ProfileMap};
|
||||||
use crate::graph::{ColorAttachment, DepthAttachment};
|
use crate::graph::{ColorAttachment, DepthAttachment};
|
||||||
use crate::render_data::RenderData;
|
use crate::render_data::RenderData;
|
||||||
use crate::store::{Loadout, Store};
|
use crate::store::{Loadout, Store};
|
||||||
@@ -39,10 +39,8 @@ pub struct RenderLoop {
|
|||||||
frame: Cell<u32>,
|
frame: Cell<u32>,
|
||||||
elapsed: Cell<f64>,
|
elapsed: Cell<f64>,
|
||||||
last: Cell<f64>,
|
last: Cell<f64>,
|
||||||
profiling: Cell<bool>,
|
active_loadout: Loadout,
|
||||||
profile_pending: Cell<bool>,
|
profile_state: RefCell<profiler::ProfileState>,
|
||||||
profile_after: Cell<f64>,
|
|
||||||
profile: RefCell<Option<String>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderLoop {
|
impl RenderLoop {
|
||||||
@@ -54,10 +52,8 @@ impl RenderLoop {
|
|||||||
frame: Cell::new(0),
|
frame: Cell::new(0),
|
||||||
elapsed: Cell::new(0.0),
|
elapsed: Cell::new(0.0),
|
||||||
last: Cell::new(js_sys::Date::now()),
|
last: Cell::new(js_sys::Date::now()),
|
||||||
profiling: Cell::new(false),
|
active_loadout: todo!(),
|
||||||
profile_pending: Cell::new(false),
|
profile_state: RefCell::new(profiler::ProfileState::Detached),
|
||||||
profile_after: Cell::new(0.0),
|
|
||||||
profile: RefCell::new(None),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,24 +74,19 @@ impl RenderLoop {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_profiling(&self, enabled: bool) {
|
pub fn set_profiling(&mut self) {
|
||||||
self.profiling.set(enabled);
|
self.profile_state = RefCell::new(profiler::ProfileState::Attached);
|
||||||
self.profile_after.set(0.0);
|
|
||||||
if !enabled {
|
|
||||||
self.profile.borrow_mut().take();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn take_profile(&self) -> Option<String> {
|
pub fn take_profile(&self) -> Option<String> {
|
||||||
self.profile.borrow_mut().take()
|
if let profiler::ProfileState::Ready(data) = self.profile_state.get_mut().take() {
|
||||||
|
Some(data)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(
|
pub fn start(self: &Rc<Self>, gpu: &Wgpu, data: Rc<RefCell<RenderData>>) {
|
||||||
self: &Rc<Self>,
|
|
||||||
gpu: Rc<RefCell<Option<Wgpu>>>,
|
|
||||||
store: Rc<RefCell<Store>>,
|
|
||||||
data: Rc<RefCell<RenderData>>,
|
|
||||||
) {
|
|
||||||
if self.started.replace(true) {
|
if self.started.replace(true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -106,6 +97,7 @@ impl RenderLoop {
|
|||||||
if !control.playing.get() {
|
if !control.playing.get() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let started = js_sys::Date::now();
|
let started = js_sys::Date::now();
|
||||||
let fps = control.fps.get();
|
let fps = control.fps.get();
|
||||||
if fps != 0 && started - control.last.get() < 900.0 / f64::from(fps) {
|
if fps != 0 && started - control.last.get() < 900.0 / f64::from(fps) {
|
||||||
@@ -120,36 +112,25 @@ impl RenderLoop {
|
|||||||
let mut data = data.borrow_mut();
|
let mut data = data.borrow_mut();
|
||||||
data.update_signals(delta as f32, frame, elapsed as f32, control.fps.get());
|
data.update_signals(delta as f32, frame, elapsed as f32, control.fps.get());
|
||||||
}
|
}
|
||||||
let submission = if data.borrow().render_pending() {
|
|
||||||
if let (Some(gpu), Some(loadout)) =
|
// returns false when no render is pending
|
||||||
(gpu.borrow_mut().as_mut(), store.borrow_mut().active_mut())
|
if !data.borrow_mut().begin_render() {
|
||||||
{
|
continue;
|
||||||
if data.borrow_mut().begin_render() {
|
|
||||||
let profile = control.profiling.get()
|
|
||||||
&& !control.profile_pending.get()
|
|
||||||
&& started >= control.profile_after.get();
|
|
||||||
let result = {
|
|
||||||
let data = data.borrow();
|
|
||||||
gpu.render(loadout, &data, profile)
|
|
||||||
};
|
|
||||||
match result {
|
|
||||||
Ok(profile) => profile.map(|profile| {
|
|
||||||
(profile, gpu.adapter.clone(), gpu.width, gpu.height)
|
|
||||||
}),
|
|
||||||
Err(_) => {
|
|
||||||
data.borrow_mut().mark_dirty();
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
let should_profile = matches!(
|
||||||
None
|
*control.profile_state.borrow(),
|
||||||
}
|
profiler::ProfileState::Attached
|
||||||
} else {
|
);
|
||||||
None
|
|
||||||
}
|
gpu.render(&mut self.active_loadout, &data.borrow(), should_profile)
|
||||||
} else {
|
.and_then(|profile_metadata| {
|
||||||
None
|
// we are done with render loop but we got a profile data for this
|
||||||
};
|
// frame, so we need to process that
|
||||||
|
|
||||||
|
control.profile_state
|
||||||
|
});
|
||||||
|
|
||||||
if let Some((profile, adapter, width, height)) = submission {
|
if let Some((profile, adapter, width, height)) = submission {
|
||||||
control.profile_pending.set(true);
|
control.profile_pending.set(true);
|
||||||
control.profile_after.set(started + 250.0);
|
control.profile_after.set(started + 250.0);
|
||||||
@@ -162,27 +143,20 @@ impl RenderLoop {
|
|||||||
let passes = profile
|
let passes = profile
|
||||||
.read()
|
.read()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(name, milliseconds)| {
|
.map(|(name, milliseconds)| profiler::Pass { name, milliseconds })
|
||||||
serde_json::json!({
|
|
||||||
"name": name,
|
|
||||||
"milliseconds": milliseconds,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let milliseconds = passes
|
let milliseconds =
|
||||||
.iter()
|
passes.iter().map(|pass| pass.milliseconds).sum::<f64>();
|
||||||
.filter_map(|pass| pass["milliseconds"].as_f64())
|
|
||||||
.sum::<f64>();
|
|
||||||
if control.profiling.get() {
|
if control.profiling.get() {
|
||||||
*control.profile.borrow_mut() = Some(
|
*control.profile_state.borrow_mut() = Some(
|
||||||
serde_json::json!({
|
profiler::FrameData {
|
||||||
"frame": frame,
|
frame,
|
||||||
"milliseconds": milliseconds,
|
milliseconds,
|
||||||
"readbackMilliseconds": js_sys::Date::now() - started,
|
readback_milliseconds: js_sys::Date::now() - started,
|
||||||
"adapter": adapter,
|
adapter,
|
||||||
"canvas": { "width": width, "height": height },
|
canvas: profiler::Canvas { width, height },
|
||||||
"passes": passes,
|
passes,
|
||||||
})
|
}
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -201,7 +175,7 @@ impl Wgpu {
|
|||||||
loadout: &mut Loadout,
|
loadout: &mut Loadout,
|
||||||
data: &RenderData,
|
data: &RenderData,
|
||||||
profile: bool,
|
profile: bool,
|
||||||
) -> Result<Option<ProfileMap>, String> {
|
) -> Result<ProfileMap, String> {
|
||||||
for buffer in loadout.resources.buffers.values() {
|
for buffer in loadout.resources.buffers.values() {
|
||||||
if !buffer.sync_each_frame {
|
if !buffer.sync_each_frame {
|
||||||
continue;
|
continue;
|
||||||
@@ -212,6 +186,7 @@ impl Wgpu {
|
|||||||
data.bytes(&buffer.source).ok_or("ROWS_UNKNOWN")?,
|
data.bytes(&buffer.source).ok_or("ROWS_UNKNOWN")?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = match self.surface.get_current_texture() {
|
let output = match self.surface.get_current_texture() {
|
||||||
Ok(output) => output,
|
Ok(output) => output,
|
||||||
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
|
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
|
||||||
@@ -221,9 +196,21 @@ impl Wgpu {
|
|||||||
Err(wgpu::SurfaceError::Timeout) => return Err("SURFACE".into()),
|
Err(wgpu::SurfaceError::Timeout) => return Err("SURFACE".into()),
|
||||||
Err(_) => return Err("SURFACE".into()),
|
Err(_) => return Err("SURFACE".into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let surface_view = output
|
let surface_view = output
|
||||||
.texture
|
.texture
|
||||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
|
// profiler is used only when enabled
|
||||||
|
// the presence of a profiler means:
|
||||||
|
// - profiler is available and
|
||||||
|
// - we want to profile this frame
|
||||||
|
let profiler = if profile {
|
||||||
|
loadout.resources.profiler
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let mut encoder = self
|
let mut encoder = self
|
||||||
.device
|
.device
|
||||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
@@ -238,9 +225,12 @@ impl Wgpu {
|
|||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let pass = &loadout.graph.passes[*pass];
|
let pass = &loadout.graph.passes[*pass];
|
||||||
let timestamp_writes = profile
|
let timestamp_writes = profiler.and(|p| wgpu::ComputePassTimestampWrites {
|
||||||
.then(|| loadout.resources.compute_timestamps(pass_index))
|
query_set: &p.query_set,
|
||||||
.flatten();
|
beginning_of_pass_write_index: Some(pass_index as u32 * 2),
|
||||||
|
end_of_pass_write_index: Some(pass_index as u32 + 1),
|
||||||
|
});
|
||||||
|
|
||||||
let mut command = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
|
let mut command = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
|
||||||
label: Some(&pass.id),
|
label: Some(&pass.id),
|
||||||
timestamp_writes,
|
timestamp_writes,
|
||||||
@@ -296,9 +286,13 @@ impl Wgpu {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let timestamp_writes = profile
|
|
||||||
.then(|| loadout.resources.render_timestamps(pass_index))
|
let timestamp_writes = profiler.and(|p| wgpu::RenderPassTimestampWrites {
|
||||||
.flatten();
|
query_set: &p.query_set,
|
||||||
|
beginning_of_pass_write_index: Some(pass_index as u32 * 2),
|
||||||
|
end_of_pass_write_index: Some(pass_index as u32 * 2 + 1),
|
||||||
|
});
|
||||||
|
|
||||||
let mut command = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut command = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: Some(&pass.id),
|
label: Some(&pass.id),
|
||||||
color_attachments: &colors,
|
color_attachments: &colors,
|
||||||
@@ -310,19 +304,15 @@ impl Wgpu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if profile {
|
|
||||||
loadout.resources.resolve_profile(&mut encoder);
|
profiler.as_ref().map(|p| p.encode_query_set(&mut encoder));
|
||||||
}
|
|
||||||
self.queue.submit([encoder.finish()]);
|
self.queue.submit([encoder.finish()]);
|
||||||
let profile = profile
|
|
||||||
.then(|| {
|
|
||||||
loadout
|
|
||||||
.resources
|
|
||||||
.map_profile(self.queue.get_timestamp_period())
|
|
||||||
})
|
|
||||||
.flatten();
|
|
||||||
output.present();
|
output.present();
|
||||||
Ok(profile)
|
|
||||||
|
profiler
|
||||||
|
.and_then(|p| p.map_profile(self.queue.get_timestamp_period()))
|
||||||
|
.flatten();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user