use alpha2_engine::FrameProvider;
use alpha2_media::SourceId;
use alpha2_render::{
PendingSourceWork, RenderCacheKey, RenderedTexture, SourceView, UploadTexture,
};
use alpha2_time::MediaTime;
use std::sync::atomic::{AtomicBool, Ordering};
use wasm_bindgen::prelude::*;
use crate::error::Code;
use crate::frame_store::FrameStore;
use crate::web::{coded_error, invalid_state};
pub(crate) fn present_texture(
surface: Option<&wgpu::Surface<'_>>,
surface_config: Option<&wgpu::SurfaceConfiguration>,
presenter: Option<&Presenter>,
device: &wgpu::Device,
gpu: &alpha2_gpu::GpuContext,
device_lost: &AtomicBool,
source: &wgpu::TextureView,
) -> Result<(), JsValue> {
let surface = surface.ok_or_else(|| invalid_state("renderer was created without a canvas"))?;
let presenter = presenter.ok_or_else(|| invalid_state("canvas presenter is unavailable"))?;
let current = match surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(texture)
| wgpu::CurrentSurfaceTexture::Suboptimal(texture) => texture,
wgpu::CurrentSurfaceTexture::Outdated => {
let config = surface_config
.ok_or_else(|| invalid_state("surface configuration is unavailable"))?;
surface.configure(device, config);
match surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(texture)
| wgpu::CurrentSurfaceTexture::Suboptimal(texture) => texture,
_ => {
return Err(coded_error(
Code::GPU_TIMEOUT,
"canvas surface remains unavailable",
));
}
}
}
wgpu::CurrentSurfaceTexture::Lost if !device_lost.load(Ordering::Acquire) => {
let config = surface_config
.ok_or_else(|| invalid_state("surface configuration is unavailable"))?;
surface.configure(device, config);
match surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(texture)
| wgpu::CurrentSurfaceTexture::Suboptimal(texture) => texture,
_ => return Err(surface_lost(device_lost)),
}
}
wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Validation => {
return Err(surface_lost(device_lost));
}
wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
return Err(coded_error(
Code::GPU_TIMEOUT,
"canvas surface is temporarily unavailable",
));
}
};
let target_view = current
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
presenter.draw(device, gpu, source, &target_view);
drop(target_view);
gpu.present(current);
Ok(())
}
fn surface_lost(device_lost: &AtomicBool) -> JsValue {
device_lost.store(true, Ordering::Release);
coded_error(Code::GPU_DEVICE_LOST, "canvas GPU surface was lost")
}
pub(crate) enum UploadedTextureBacking {
Raw { _texture: UploadTexture },
Rendered { _texture: RenderedTexture },
}
pub(crate) struct UploadedTexture {
pub(crate) backing: UploadedTextureBacking,
pub(crate) view: wgpu::TextureView,
pub(crate) output_bytes: u64,
pub(crate) pending: Option<PendingSourceWork>,
}
impl UploadedTexture {
pub(crate) fn retained_bytes(&self) -> u64 {
self.output_bytes.saturating_add(
self.pending
.as_ref()
.map_or(0, PendingSourceWork::retained_bytes),
)
}
}
impl FrameProvider for FrameStore<UploadedTexture> {
fn texture_for(&self, source: &SourceId, time: MediaTime) -> Option<SourceView<'_>> {
self.get(source.as_str(), time.ticks())
.map(|texture| SourceView::from_wgpu(&texture.view))
}
fn pending_source_work(
&self,
source: &SourceId,
time: MediaTime,
) -> Option<&PendingSourceWork> {
self.get(source.as_str(), time.ticks())
.and_then(|texture| texture.pending.as_ref())
}
fn source_key(&self, source: &SourceId, time: MediaTime) -> Option<RenderCacheKey> {
let generation = self.generation(source.as_str(), time.ticks())?;
let mut hash = std::hash::DefaultHasher::new();
std::hash::Hash::hash(&(source.as_str(), generation), &mut hash);
Some(RenderCacheKey::source(std::hash::Hasher::finish(&hash)))
}
}
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct WebPlaybackResult {
pub(crate) requested_time_ticks: i64,
pub(crate) presented_time_ticks: Option<i64>,
pub(crate) missing_sources: Vec<String>,
pub(crate) missing_frames: Vec<WebMissingFrame>,
pub(crate) repeated: bool,
pub(crate) unchanged: bool,
}
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct WebMissingFrame {
pub(crate) clip_id: String,
pub(crate) reader_id: Option<String>,
pub(crate) source_id: String,
pub(crate) media_time_ticks: String,
}
pub(crate) struct Presenter {
pub(crate) layout: wgpu::BindGroupLayout,
pub(crate) pipeline: wgpu::RenderPipeline,
pub(crate) sampler: wgpu::Sampler,
}
impl Presenter {
pub(crate) fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("alpha2 canvas presenter layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("alpha2 canvas presenter shader"),
source: wgpu::ShaderSource::Wgsl(alpha2_shaders::CANVAS_PRESENT_WGSL.into()),
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("alpha2 canvas presenter pipeline layout"),
bind_group_layouts: &[Some(&layout)],
immediate_size: 0,
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("alpha2 canvas presenter pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
buffers: &[],
},
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
}),
multiview_mask: None,
cache: None,
});
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("alpha2 canvas presenter sampler"),
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
..Default::default()
});
Self {
layout,
pipeline,
sampler,
}
}
pub(crate) fn draw(
&self,
device: &wgpu::Device,
gpu: &alpha2_gpu::GpuContext,
source: &wgpu::TextureView,
target: &wgpu::TextureView,
) {
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("alpha2 canvas presenter bind group"),
layout: &self.layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(source),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&self.sampler),
},
],
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("alpha2 canvas presenter encoder"),
});
{
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("alpha2 canvas presenter pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: target,
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &bind_group, &[]);
pass.draw(0..3, 0..1);
}
gpu.submit([encoder.finish()]);
}
}