use awsm_renderer_core::{error::AwsmCoreError, renderer::AwsmRendererWebGpu};
use crate::buffer::mapped_staging_ring::{
AcquireOutcome, MappedStagingRing, UploadStats, DEFAULT_RING_DEPTH,
};
pub struct MappedUploader {
ring: Option<MappedStagingRing>,
last_dest_size: usize,
ring_depth: usize,
label: String,
stashed_stats: UploadStats,
}
impl MappedUploader {
pub fn new(label: impl Into<String>) -> Self {
Self::with_ring_depth(label, DEFAULT_RING_DEPTH)
}
pub fn with_ring_depth(label: impl Into<String>, depth: usize) -> Self {
Self {
ring: None,
last_dest_size: 0,
ring_depth: depth,
label: label.into(),
stashed_stats: UploadStats::default(),
}
}
pub fn stats(&self) -> UploadStats {
let mut s = self.stashed_stats;
if let Some(ring) = &self.ring {
let live = ring.stats();
s.peak_ring_depth_used = s.peak_ring_depth_used.max(live.peak_ring_depth_used);
s.fallback_count += live.fallback_count;
s.map_async_wait_ms += live.map_async_wait_ms;
s.bytes_uploaded_via_ring += live.bytes_uploaded_via_ring;
s.bytes_uploaded_via_fallback += live.bytes_uploaded_via_fallback;
s.bytes_uploaded_via_writebuffer += live.bytes_uploaded_via_writebuffer;
s.resize_count += live.resize_count;
}
s
}
pub fn reset_stats(&mut self) {
self.stashed_stats = UploadStats::default();
if let Some(ring) = &mut self.ring {
ring.reset_stats();
}
}
pub fn write_dirty_ranges(
&mut self,
gpu: &AwsmRendererWebGpu,
dest: &web_sys::GpuBuffer,
dest_size: usize,
raw_data: &[u8],
ranges: &[(usize, usize)],
) -> Result<(), AwsmCoreError> {
if ranges.is_empty() || raw_data.is_empty() {
return Ok(());
}
if dest_size == 0 {
tracing::debug!(
"MappedUploader {}: write_dirty_ranges called with dest_size=0; skipping",
self.label
);
return Ok(());
}
self.ensure_ring(gpu, dest_size)?;
let ring = self
.ring
.as_mut()
.expect("ring is created by ensure_ring above (dest_size > 0)");
let prepared = crate::buffer::helpers::prepare_dirty_ranges(ranges, raw_data.len());
if prepared.is_empty() {
return Ok(());
}
let acquired = match ring.acquire() {
AcquireOutcome::Acquired(slot) => {
for (off, sz) in &prepared {
let end = off.saturating_add(*sz).min(raw_data.len());
if *off < end {
slot.write(*off, &raw_data[*off..end]);
}
}
let encoder = gpu.create_command_encoder(Some(&self.label));
slot.finalize(&encoder, dest, &prepared)?;
gpu.submit_commands(&encoder.finish());
true
}
AcquireOutcome::Exhausted => false,
};
if acquired {
ring.kick_submitted_slots();
}
let exhausted = !acquired;
if exhausted {
let mut total = 0u64;
for (off, sz) in &prepared {
if *sz == 0 {
continue;
}
let end = off.saturating_add(*sz).min(raw_data.len());
if *off < end {
gpu.write_buffer(dest, Some(*off), &raw_data[*off..end], None, None)?;
total += (end - off) as u64;
}
}
ring.note_fallback_bytes(total);
}
Ok(())
}
pub fn ingest_foreign(
&mut self,
gpu: &AwsmRendererWebGpu,
dest: &web_sys::GpuBuffer,
offset: usize,
bytes: &[u8],
) -> Result<(), AwsmCoreError> {
gpu.write_buffer(dest, Some(offset), bytes, None, None)?;
if let Some(ring) = &mut self.ring {
ring.note_writebuffer_bytes(bytes.len() as u64);
} else {
self.stashed_stats.bytes_uploaded_via_writebuffer += bytes.len() as u64;
}
Ok(())
}
pub fn release_ring(&mut self) {
if let Some(ring) = self.ring.take() {
let live = ring.stats();
self.stashed_stats.peak_ring_depth_used = self
.stashed_stats
.peak_ring_depth_used
.max(live.peak_ring_depth_used);
self.stashed_stats.fallback_count += live.fallback_count;
self.stashed_stats.map_async_wait_ms += live.map_async_wait_ms;
self.stashed_stats.bytes_uploaded_via_ring += live.bytes_uploaded_via_ring;
self.stashed_stats.bytes_uploaded_via_fallback += live.bytes_uploaded_via_fallback;
self.stashed_stats.bytes_uploaded_via_writebuffer +=
live.bytes_uploaded_via_writebuffer;
self.stashed_stats.resize_count += live.resize_count;
}
self.last_dest_size = 0;
}
fn ensure_ring(
&mut self,
gpu: &AwsmRendererWebGpu,
dest_size: usize,
) -> Result<(), AwsmCoreError> {
if dest_size == 0 {
return Ok(());
}
match self.ring.as_mut() {
None => {
let ring =
MappedStagingRing::new(gpu, self.ring_depth, dest_size, self.label.clone())?;
self.ring = Some(ring);
self.last_dest_size = dest_size;
}
Some(ring) if dest_size != self.last_dest_size => {
ring.resize(gpu, dest_size)?;
self.last_dest_size = dest_size;
}
Some(_) => {}
}
Ok(())
}
}