Initial commit
This commit is contained in:
commit
d57f56014e
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
/.vscode
|
||||
/.idea
|
File diff suppressed because it is too large
Load Diff
|
@ -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" }
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "stern_component"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "stern_lang"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
miette = "5.6"
|
|
@ -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,
|
||||
}
|
|
@ -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"
|
|
@ -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,
|
||||
};
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
pub use cgmath as math;
|
||||
|
||||
pub mod color;
|
|
@ -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"
|
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "stern_runtime"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
|
@ -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(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue