mod descriptor;
mod resources;
mod template;
mod vertices;
use resources::{Resource, Resources};
use tracing::instrument;
use wgpu::{CommandEncoder, Device, ShaderSource, TextureView};
pub use descriptor::ShadyDescriptor;
#[cfg(feature = "audio")]
pub use shady_audio;
#[cfg(feature = "mouse")]
pub use resources::MouseState;
pub use template::TemplateLang;
pub const FRAGMENT_ENTRYPOINT: &str = "main";
const BIND_GROUP_INDEX: u32 = 0;
const VBUFFER_INDEX: u32 = 0;
#[derive(Debug, Clone)]
pub struct ShadyRenderPipeline(wgpu::RenderPipeline);
impl AsRef<ShadyRenderPipeline> for ShadyRenderPipeline {
fn as_ref(&self) -> &Self {
self
}
}
pub struct Shady {
resources: Resources,
bind_group: wgpu::BindGroup,
vbuffer: wgpu::Buffer,
ibuffer: wgpu::Buffer,
}
impl Shady {
#[instrument(level = "trace", skip_all)]
pub fn new<'a>(desc: ShadyDescriptor) -> Self {
let ShadyDescriptor { device, .. } = &desc;
let resources = Resources::new(&desc);
let bind_group = resources.bind_group(device);
Self {
resources,
bind_group,
vbuffer: vertices::vertex_buffer(device),
ibuffer: vertices::index_buffer(device),
}
}
pub fn add_render_pass(
&self,
encoder: &mut CommandEncoder,
texture_view: &TextureView,
pipelines: impl IntoIterator<Item = impl AsRef<ShadyRenderPipeline>>,
) {
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: texture_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
})],
..Default::default()
});
render_pass.set_bind_group(BIND_GROUP_INDEX, &self.bind_group, &[]);
render_pass.set_vertex_buffer(VBUFFER_INDEX, self.vbuffer.slice(..));
render_pass.set_index_buffer(self.ibuffer.slice(..), wgpu::IndexFormat::Uint16);
for pipeline in pipelines.into_iter() {
render_pass.set_pipeline(&pipeline.as_ref().0);
render_pass.draw_indexed(vertices::index_buffer_range(), 0, 0..1);
}
}
}
impl Shady {
#[inline]
#[cfg(feature = "resolution")]
pub fn set_resolution(&mut self, width: u32, height: u32) {
debug_assert!(width > 0);
debug_assert!(height > 0);
self.resources.resolution.set(width, height);
}
#[inline]
#[cfg(feature = "mouse")]
pub fn set_mouse_state(&mut self, state: MouseState) {
self.resources.mouse.set_state(state);
}
#[inline]
#[cfg(feature = "mouse")]
pub fn set_mouse_pos(&mut self, x: f32, y: f32) {
self.resources.mouse.set_pos(x, y);
}
#[inline]
#[cfg(feature = "frame")]
pub fn inc_frame(&mut self) {
self.resources.frame.inc();
}
#[inline]
#[cfg(feature = "audio")]
pub fn set_audio_frequency_range(
&mut self,
sample_processor: &shady_audio::SampleProcessor,
freq_range: std::ops::Range<std::num::NonZeroU16>,
) {
self.resources
.audio
.set_frequency_range(sample_processor, freq_range);
}
#[inline]
#[cfg(feature = "audio")]
pub fn set_audio_bars(
&mut self,
device: &Device,
sample_processor: &shady_audio::SampleProcessor,
amount_bars: std::num::NonZeroUsize,
) {
self.resources
.audio
.set_bars(device, sample_processor, amount_bars);
self.bind_group = self.resources.bind_group(device);
}
}
impl Shady {
#[inline]
#[cfg(feature = "audio")]
pub fn update_audio_buffer(
&mut self,
queue: &wgpu::Queue,
sample_processor: &shady_audio::SampleProcessor,
) {
self.resources.audio.fetch_audio(sample_processor);
self.resources.audio.update_buffer(queue);
}
#[inline]
#[cfg(feature = "frame")]
pub fn update_frame_buffer(&mut self, queue: &wgpu::Queue) {
self.resources.frame.update_buffer(queue);
}
#[inline]
#[cfg(feature = "mouse")]
pub fn update_mouse_buffer(&mut self, queue: &wgpu::Queue) {
self.resources.mouse.update_buffer(queue);
}
#[inline]
#[cfg(feature = "resolution")]
pub fn update_resolution_buffer(&mut self, queue: &wgpu::Queue) {
self.resources.resolution.update_buffer(queue);
}
#[inline]
#[cfg(feature = "time")]
pub fn update_time_buffer(&mut self, queue: &wgpu::Queue) {
self.resources.time.update_buffer(queue);
}
}
pub fn create_render_pipeline<'a>(
device: &Device,
shader_source: ShaderSource<'a>,
texture_format: &'a wgpu::TextureFormat,
) -> ShadyRenderPipeline {
let bind_group_layout = Resources::bind_group_layout(device);
let pipeline = get_render_pipeline(device, shader_source, bind_group_layout, texture_format);
ShadyRenderPipeline(pipeline)
}
fn get_render_pipeline(
device: &Device,
shader_source: ShaderSource<'_>,
bind_group_layout: wgpu::BindGroupLayout,
texture_format: &wgpu::TextureFormat,
) -> wgpu::RenderPipeline {
let vertex_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shady vertex shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("vertex_shader.wgsl").into()),
});
let fragment_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shady fragment shader"),
source: shader_source,
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Shady pipeline layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Shady render pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &vertex_shader,
entry_point: Some("vertex_main"),
buffers: &[vertices::BUFFER_LAYOUT],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
fragment: Some(wgpu::FragmentState {
module: &fragment_shader,
entry_point: Some("main"),
targets: &[Some(wgpu::ColorTargetState {
format: *texture_format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
multiview: None,
cache: None,
})
}