Initial commit

This commit is contained in:
Natty 2023-03-28 23:56:38 +02:00
commit d57f56014e
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
16 changed files with 3107 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
/.vscode
/.idea

2056
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

24
Cargo.toml Normal file
View File

@ -0,0 +1,24 @@
[package]
name = "stern_engine"
version = "0.1.0"
edition = "2021"
[workspace]
members = [".", "core_lang", "core_lib", "core_component", "core_runtime"]
[dependencies]
cfg-if = "1"
log = "0.4"
env_logger = "0.10"
pollster = "0.3"
wgpu = { version = "0.15", features = ["naga"] }
naga = { version = "0.11", features = ["glsl-in", "wgsl-in", "validate"] }
winit = "0.28"
raw-window-handle = "0.5"
bytemuck = { version = "1.12", features = ["derive"] }
notify = "5.0"
egui_wgpu_backend = "0.22"
egui_winit_platform = "0.18"
egui = "0.21"
stern_core_lib = { path = "./core_lib" }

83
assets/mandelbrot.wgsl Normal file
View File

@ -0,0 +1,83 @@
struct CameraUniform {
view_proj: mat4x4<f32>,
};
@group(0) @binding(0)
var<uniform> camera: CameraUniform;
struct SpecialUniform {
t: f32,
center_x: f32,
center_y: f32,
};
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) tex_coords: vec2<f32>,
}
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) tex_coords: vec2<f32>,
}
@vertex
fn vs_main(
model: VertexInput,
) -> VertexOutput {
var out: VertexOutput;
out.tex_coords = model.tex_coords;
out.clip_position = camera.view_proj * vec4<f32>(model.position, 1.0);
return out;
}
@group(1) @binding(0)
var<uniform> special: SpecialUniform;
fn color(
tex_coords: vec2<f32>,
) -> vec4<f32> {
var x = tex_coords.x;
var y = tex_coords.y;
var scale = 5.0 / pow(1.34, special.t);
var cx = (x - 0.5) * scale + special.center_x;
var cy = (y - 0.5) * scale + special.center_y;
var c = vec2<f32>(cx, cy);
var z = vec2<f32>(cx, cy);
var i = 0.0;
var iter = 20.0 + pow(special.t, 2.1);
loop {
if (i >= iter) {
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
}
var ix = (z.x * z.x - z.y * z.y) + c.x;
var iy = (z.y * z.x + z.x * z.y) + c.y;
if (ix * ix + iy * iy > 4.0) {
break;
}
z.x = ix;
z.y = iy;
i += 1.0;
}
i /= iter;
var r = sin(i * 1.0);
var g = sin(i * 3.0);
var b = sin(i * 9.0);
return vec4<f32>(r, g, b, 1.0);
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return color(in.tex_coords);
}

View File

@ -0,0 +1,6 @@
[package]
name = "stern_component"
version = "0.1.0"
edition = "2021"
[dependencies]

View File

@ -0,0 +1 @@

7
core_lang/Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "stern_lang"
version = "0.1.0"
edition = "2021"
[dependencies]
miette = "5.6"

30
core_lang/src/lib.rs Normal file
View File

@ -0,0 +1,30 @@
use miette::SourceSpan;
struct Token {
typ: TokenType,
position: SourceSpan,
}
enum TokenType {
BraceLeft,
BraceRight,
BracketLeft,
BracketRight,
ParenthesisLeft,
ParenthesisRight,
Semicolon,
Colon,
DoubleColon,
Identifier(String),
Dot,
DoubleDot,
Plus,
Minus,
Asterisk,
Slash,
Backlash,
Eq,
NotEq,
Lt,
Gt,
}

13
core_lib/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "stern_core_lib"
version = "0.1.0"
edition = "2021"
[features]
render_wgpu = ["wgpu"]
[dependencies]
cgmath = "0.18"
wgpu = { version = "0.15", optional = true }
cfg-if = "1"

224
core_lib/src/color/mod.rs Normal file
View File

@ -0,0 +1,224 @@
use cgmath::Vector4;
cfg_if::cfg_if! {
if #[cfg(feature = "render_wgpu")] {
pub mod wgpu;
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct RGBAu8(pub u8, pub u8, pub u8, pub u8);
pub trait Color: From<RGBAu8> {
fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self::from(RGBAu8(r, g, b, a))
}
fn from_rgb(r: u8, g: u8, b: u8) -> Self {
Self::from_rgba(r, g, b, u8::MAX)
}
fn lerp(self, other: Self, ratio: f32) -> Self;
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct RGBA {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl From<Vector4<f32>> for RGBA {
fn from(vec: Vector4<f32>) -> Self {
unsafe { std::mem::transmute(vec) }
}
}
impl From<RGBA> for Vector4<f32> {
fn from(rgba: RGBA) -> Self {
unsafe { std::mem::transmute(rgba) }
}
}
impl From<RGBAu8> for RGBA {
fn from(rgba: RGBAu8) -> Self {
Self {
r: rgba.0 as f32 / u8::MAX as f32,
g: rgba.1 as f32 / u8::MAX as f32,
b: rgba.2 as f32 / u8::MAX as f32,
a: rgba.3 as f32 / u8::MAX as f32,
}
}
}
impl Color for RGBA {
fn lerp(self, other: Self, ratio: f32) -> Self {
RGBA {
r: self.r * ratio + other.r * (1.0 - ratio),
g: self.g * ratio + other.g * (1.0 - ratio),
b: self.b * ratio + other.b * (1.0 - ratio),
a: self.a * ratio + other.a * (1.0 - ratio),
}
}
}
#[derive(Copy, Clone)]
struct HSBA {
pub h: f32,
pub s: f32,
pub b: f32,
pub a: f32,
}
impl From<HSBA> for RGBA {
fn from(hsba: HSBA) -> Self {
let h6 = hsba.h / 60.0;
let hue_side = h6 as i32;
// The color component furthest on the hue wheel
let p = hsba.b * (1.0 - hsba.s);
let hue_fract_ccw = h6 - hue_side as f32;
// The second-nearest color component on the hue wheel - counter-clockwise
let q = hsba.b * (1.0 - hue_fract_ccw * hsba.s);
let hue_fract_cw = 1.0 - hue_fract_ccw;
// The second-nearest color component on the hue wheel - clockwise
let t = hsba.b * (1.0 - hue_fract_cw * hsba.s);
match hue_side % 6 {
// Hues 60°-119° -- Green is the brightest color, no blue is present at max saturation
1 => Self {
r: q,
g: hsba.b,
b: p,
a: hsba.a,
},
// Hues 120°-179° -- Green is the brightest color, no red is present at max saturation
2 => Self {
r: p,
g: hsba.b,
b: t,
a: hsba.a,
},
// Hues 180°-239° -- Blue is the brightest color, no red is present at max saturation
3 => Self {
r: p,
g: q,
b: hsba.b,
a: hsba.a,
},
// Hues 240°-299° -- Blue is the brightest color, no green is present at max saturation
4 => Self {
r: t,
g: p,
b: hsba.b,
a: hsba.a,
},
// Hues 300°-359° -- Red is the brightest color, no green is present at max saturation
5 => Self {
r: hsba.b,
g: p,
b: q,
a: hsba.a,
},
// Hues 0°-59° -- Red is the brightest color, no blue is present at max saturation
_ => Self {
r: hsba.b,
g: t,
b: p,
a: hsba.a,
},
}
}
}
impl From<RGBA> for HSBA {
fn from(rgba: RGBA) -> Self {
let brightness = rgba.r.max(rgba.g).max(rgba.b);
let min = rgba.r.min(rgba.g).min(rgba.b);
let chroma = brightness - min;
let saturation = chroma / brightness;
let hue = if brightness == rgba.r {
if rgba.g < rgba.b {
(rgba.g - rgba.b) / chroma + 6.0
} else {
(rgba.g - rgba.b) / chroma
}
} else if brightness == rgba.g {
(rgba.b - rgba.r) / chroma + 2.0
} else {
(rgba.r - rgba.g) / chroma + 4.0
};
Self {
h: hue * 60.0,
s: saturation,
b: brightness,
a: rgba.a,
}
}
}
impl From<RGBAu8> for HSBA {
fn from(rgba: RGBAu8) -> Self {
RGBA::from(rgba).into()
}
}
impl Color for HSBA {
fn lerp(self, other: Self, ratio: f32) -> Self {
HSBA {
h: self.h * ratio + other.h * (1.0 - ratio),
s: self.s * ratio + other.s * (1.0 - ratio),
b: self.b * ratio + other.b * (1.0 - ratio),
a: self.a * ratio + other.a * (1.0 - ratio),
}
}
}
pub const WHITE: RGBA = RGBA {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
};
pub const BLACK: RGBA = RGBA {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
pub const RED: RGBA = RGBA {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
pub const GREEN: RGBA = RGBA {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
pub const BLUE: RGBA = RGBA {
r: 0.0,
g: 0.0,
b: 1.0,
a: 1.0,
};
pub const YELLOW: RGBA = RGBA {
r: 1.0,
g: 1.0,
b: 0.0,
a: 1.0,
};

View File

@ -0,0 +1,23 @@
use crate::color;
impl From<color::RGBA> for wgpu::Color {
fn from(rgba: color::RGBA) -> Self {
Self {
r: rgba.r as f64,
g: rgba.g as f64,
b: rgba.b as f64,
a: rgba.a as f64,
}
}
}
impl From<wgpu::Color> for color::RGBA {
fn from(wc: wgpu::Color) -> Self {
Self {
r: wc.r as f32,
g: wc.g as f32,
b: wc.b as f32,
a: wc.a as f32,
}
}
}

3
core_lib/src/lib.rs Normal file
View File

@ -0,0 +1,3 @@
pub use cgmath as math;
pub mod color;

7
core_runtime/Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "stern-runtime"
version = "0.1.0"

6
core_runtime/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "stern_runtime"
version = "0.1.0"
edition = "2021"
[dependencies]

0
core_runtime/src/lib.rs Normal file
View File

621
src/main.rs Normal file
View File

@ -0,0 +1,621 @@
use std::borrow::Cow;
use std::path::Path;
use std::sync::{Arc, Mutex};
use egui::FontDefinitions;
use egui_wgpu_backend::ScreenDescriptor;
use egui_winit_platform::{Platform, PlatformDescriptor};
use log::{error, warn};
use notify::{RecursiveMode, Watcher};
use stern_core_lib::math;
use stern_core_lib::math::{Deg, Matrix4, SquareMatrix, Vector3};
use wgpu::util::DeviceExt;
use wgpu::{Backends, BlendState, CompositeAlphaMode, InstanceDescriptor, RequestAdapterOptions};
use winit::dpi::PhysicalSize;
use winit::event_loop::ControlFlow;
fn main() {
env_logger::builder()
.filter_level(log::LevelFilter::Warn)
.init();
pollster::block_on(app_main());
}
async fn app_main() {
let event_loop = winit::event_loop::EventLoop::new();
let window = winit::window::WindowBuilder::new()
.with_title("PGR!")
.with_inner_size(PhysicalSize::new(800, 600))
.build(&event_loop)
.unwrap();
warn!("Window created.");
let mut app = App::new(&window).await;
warn!("App created.");
event_loop.run(move |event, _, control_flow| {
app.egui_platform.handle_event(&event);
match event {
winit::event::Event::WindowEvent {
ref event,
window_id,
} if window_id == window.id() => match event {
winit::event::WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
app.resize(**new_inner_size);
}
winit::event::WindowEvent::Resized(size) => {
app.resize(*size);
}
_ => (),
},
winit::event::Event::RedrawRequested(window_id) if window_id == window.id() => {
match app.render(&window) {
Ok(_) => {}
Err(wgpu::SurfaceError::Lost) => app.resize(PhysicalSize::new(
app.surface_config.width,
app.surface_config.height,
)),
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
Err(e) => eprintln!("{:?}", e),
}
}
winit::event::Event::MainEventsCleared => {
window.request_redraw();
}
_ => (),
}
});
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
enum RenderMode {
Mandelbrot,
}
impl RenderMode {
fn get_shader(&self) -> &'static str {
match self {
RenderMode::Mandelbrot => "assets/mandelbrot.wgsl",
}
}
}
struct App {
surface: wgpu::Surface,
adapter: wgpu::Adapter,
device: Arc<wgpu::Device>,
queue: wgpu::Queue,
shader: Arc<Mutex<wgpu::ShaderModule>>,
surface_config: wgpu::SurfaceConfiguration,
shader_watcher: notify::RecommendedWatcher,
egui_rpass: egui_wgpu_backend::RenderPass,
egui_platform: Platform,
render_mode: RenderMode,
time: f32,
center_x: f32,
center_y: f32,
center_x_str: String,
center_y_str: String,
last_frame: Option<std::time::Instant>,
}
#[rustfmt::skip]
pub const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = Matrix4::new(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.0, 0.0, 0.5, 1.0,
);
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct Vertex {
position: [f32; 3],
uv: [f32; 2],
}
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct TransMatUniform {
view_proj: [[f32; 4]; 4],
}
impl TransMatUniform {
fn new() -> Self {
Self {
view_proj: Matrix4::identity().into(),
}
}
fn new_from(view_proj: Matrix4<f32>) -> Self {
Self {
view_proj: view_proj.into(),
}
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct TimeUniform {
time: f32,
center_x: f32,
center_y: f32,
}
impl Vertex {
const ATTRIBS: [wgpu::VertexAttribute; 2] =
wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x2];
#[inline]
fn new(position: [f32; 3], uv: [f32; 2]) -> Self {
Self { position, uv }
}
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &Self::ATTRIBS,
}
}
}
fn switch_shader(shader_path: &Path, device: &wgpu::Device) -> Option<wgpu::ShaderModule> {
let shader_code = std::fs::read_to_string(shader_path);
if let Err(e) = shader_code {
error!("Failed to read shader file: {}", e);
return None;
}
let code = shader_code.unwrap();
let module = naga::front::wgsl::parse_str(&code);
if let Err(e) = module {
error!("Failed to validate shader file:");
e.emit_to_stderr(&code);
return None;
}
let module = module.unwrap();
let valid = naga::valid::Validator::new(
naga::valid::ValidationFlags::all(),
naga::valid::Capabilities::all(),
)
.validate(&module);
if let Err(e) = valid {
error!("Failed to validate shader file:");
e.emit_to_stderr(&code);
return None;
}
let shader_new = wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Naga(Cow::Owned(module)),
};
Some(device.create_shader_module(shader_new))
}
fn create_shader_watcher(
shader_path: &Path,
device_arc: Arc<wgpu::Device>,
shader_arc: Arc<Mutex<wgpu::ShaderModule>>,
) -> notify::RecommendedWatcher {
let path_clone = Arc::new(shader_path.to_path_buf());
let mut watcher =
notify::recommended_watcher(move |res: Result<notify::Event, notify::Error>| {
warn!("Watcher: {:?}", res);
match res {
Ok(event) => {
if let notify::event::EventKind::Modify(_) = event.kind {
warn!("Reloading shader.");
if let Some(new_shader) = switch_shader(&path_clone, &device_arc) {
*shader_arc.lock().unwrap() = new_shader;
warn!("Shader reloaded.");
}
}
}
Err(e) => println!("watch error: {:?}", e),
}
})
.unwrap();
watcher
.watch(shader_path, RecursiveMode::NonRecursive)
.unwrap();
watcher
}
impl App {
async fn new(window: &winit::window::Window) -> Self {
let instance = wgpu::Instance::new(InstanceDescriptor {
backends: Backends::all(),
..Default::default()
});
let size = window.inner_size();
let surface = unsafe { instance.create_surface(window) }.unwrap();
let adapter = instance
.request_adapter(&RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.unwrap();
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: wgpu::Features::empty(),
limits: wgpu::Limits::default(),
},
None,
)
.await
.unwrap();
let device = Arc::new(device);
let surface_def_config = surface
.get_default_config(&adapter, size.width, size.height)
.unwrap();
println!("Surface format: {:#?}", surface_def_config.format);
let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::AutoVsync,
alpha_mode: CompositeAlphaMode::Auto,
..surface_def_config
};
surface.configure(&device, &surface_config);
let render_mode = RenderMode::Mandelbrot;
let shader_code = std::fs::read_to_string(render_mode.get_shader()).unwrap();
let shader = Arc::new(Mutex::new(device.create_shader_module(
wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(Cow::from(shader_code)),
},
)));
let shader_watcher = create_shader_watcher(
Path::new(render_mode.get_shader()),
device.clone(),
shader.clone(),
);
let egui_rpass = egui_wgpu_backend::RenderPass::new(&device, surface_config.format, 1);
let platform = Platform::new(PlatformDescriptor {
physical_width: size.width,
physical_height: size.height,
scale_factor: window.scale_factor(),
font_definitions: FontDefinitions::default(),
style: Default::default(),
});
Self {
surface,
adapter,
device,
shader,
queue,
surface_config,
egui_rpass,
egui_platform: platform,
render_mode,
shader_watcher,
time: 0.0,
center_x: -0.348425,
center_y: -0.60652,
center_x_str: "-0.348425".to_string(),
center_y_str: "-0.60652".to_string(),
last_frame: None,
}
}
fn resize(&mut self, new_size: PhysicalSize<u32>) {
if new_size.width > 0 && new_size.height > 0 {
self.surface_config.width = new_size.width;
self.surface_config.height = new_size.height;
self.surface.configure(&self.device, &self.surface_config);
}
}
fn render(&mut self, window: &winit::window::Window) -> Result<(), wgpu::SurfaceError> {
let frame_time = if let Some(last_frame) = self.last_frame {
let delta = last_frame.elapsed().as_secs_f32();
self.time += delta;
delta
} else {
0.0
};
self.last_frame = Some(std::time::Instant::now());
self.egui_platform.update_time(self.time as f64);
let frame = self.surface.get_current_texture()?;
let frame_view = frame.texture.create_view(&Default::default());
let bind_group_layout =
self.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let special_bind_group_layout =
self.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let pipeline_layout = self
.device
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[&bind_group_layout, &special_bind_group_layout],
push_constant_ranges: &[],
});
let mut shader = self.shader.lock().unwrap();
let pipeline = self
.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[Vertex::desc()],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: self.surface_config.format,
blend: Some(BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
unclipped_depth: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
});
let quad = &[
Vertex::new([-0.5, 0.5, 0.0], [0.0, 0.0]),
Vertex::new([-0.5, -0.5, 0.0], [0.0, 1.0]),
Vertex::new([0.5, -0.5, 0.0], [1.0, 1.0]),
Vertex::new([0.5, -0.5, 0.0], [1.0, 1.0]),
Vertex::new([0.5, 0.5, 0.0], [1.0, 0.0]),
Vertex::new([-0.5, 0.5, 0.0], [0.0, 0.0]),
];
let buffer = self
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
contents: bytemuck::cast_slice(quad),
usage: wgpu::BufferUsages::VERTEX,
});
let proj = math::perspective(
Deg(60.0),
self.surface_config.width as f32 / self.surface_config.height as f32,
0.01,
100.0,
);
let transformation = Matrix4::from_translation(Vector3::new(0.0, 0.0, -0.75));
let view_proj = proj * transformation;
let view_proj_struct = TransMatUniform::new_from(view_proj);
let view_proj_buf = self
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
contents: bytemuck::cast_slice(&[view_proj_struct]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: view_proj_buf.as_entire_binding(),
}],
});
let time_struct = TimeUniform {
time: self.time,
center_x: self.center_x,
center_y: self.center_y,
};
let time_uniform_buf = self
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
contents: bytemuck::cast_slice(&[time_struct]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let time_uniform_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &special_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: time_uniform_buf.as_entire_binding(),
}],
});
let mut encoder = self.device.create_command_encoder(&Default::default());
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &frame_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
}),
store: true,
},
})],
depth_stencil_attachment: None,
});
render_pass.set_pipeline(&pipeline);
render_pass.set_vertex_buffer(0, buffer.slice(..));
render_pass.set_bind_group(0, &bind_group, &[]);
render_pass.set_bind_group(1, &time_uniform_bind_group, &[]);
render_pass.draw(0..(quad.len() as u32), 0..1);
}
self.egui_platform.begin_frame();
let egui_ctx = self.egui_platform.context();
egui::Window::new("Debug Info").show(&egui_ctx, |ui| {
ui.label(format!("Device: {:#?}", self.adapter.get_info()));
ui.label(format!("Frame time: {:.2}", frame_time * 1000.0));
ui.separator();
ui.label("Render Mode");
let old_mode = self.render_mode;
ui.radio_value(&mut self.render_mode, RenderMode::Mandelbrot, "Mandelbrot");
if old_mode != self.render_mode {
self.time = 0.0;
if let Some(new_shader) = switch_shader(
Path::new(self.render_mode.get_shader()),
self.device.as_ref(),
) {
*shader = new_shader;
warn!("Shader switched: {}", self.render_mode.get_shader());
}
self.shader_watcher = create_shader_watcher(
Path::new(self.render_mode.get_shader()),
self.device.clone(),
self.shader.clone(),
);
}
ui.separator();
ui.label(format!("Time: {}", self.time));
let bttn = ui.button("Restart time").on_hover_text("Restart time");
if bttn.clicked() {
self.time = 0.0;
}
});
let full_output = self.egui_platform.end_frame(Some(window));
let paint_jobs = self.egui_platform.context().tessellate(full_output.shapes);
let screen_descriptor = ScreenDescriptor {
physical_width: self.surface_config.width,
physical_height: self.surface_config.height,
scale_factor: window.scale_factor() as f32,
};
let tdelta: egui::TexturesDelta = full_output.textures_delta;
self.egui_rpass
.add_textures(&self.device, &self.queue, &tdelta)
.expect("add texture ok");
self.egui_rpass
.update_buffers(&self.device, &self.queue, &paint_jobs, &screen_descriptor);
self.egui_rpass
.execute(
&mut encoder,
&frame_view,
&paint_jobs,
&screen_descriptor,
None,
)
.unwrap();
self.queue.submit(std::iter::once(encoder.finish()));
frame.present();
self.egui_rpass
.remove_textures(tdelta)
.expect("remove texture ok");
Ok(())
}
}