Create window, handle simple input

This commit is contained in:
SeanOMik 2023-03-16 17:47:36 -04:00
commit bbc7c8e283
Signed by: SeanOMik
GPG Key ID: 568F326C7EB33ACB
15 changed files with 3276 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

2317
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

26
Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[package]
name = "lyra-engine"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
shipyard = "0.6.2"
winit = "0.28.1"
#winit-modular = "0.1.1"
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] }
tracing-log = "0.1.3"
wgpu = "0.15.1"
async-std = { version = "1.12.0", features = [ "unstable", "attributes" ] }
cfg-if = "1"
bytemuck = { version = "1.12", features = [ "derive" ] }
image = { version = "0.24", default-features = false, features = ["png", "jpeg"] }
anyhow = "1.0"
cgmath = "0.18"
tobj = { version = "3.2.1", features = [
"async",
]}
instant = "0.1"
async-trait = "0.1.65"

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 SeanOMik
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

18
shell.nix Normal file
View File

@ -0,0 +1,18 @@
{ pkgs ? import <nixpkgs> { } }:
with pkgs;
mkShell rec {
nativeBuildInputs = [
pkg-config
openssl
wasm-pack
trunk
];
buildInputs = [
udev alsa-lib vulkan-loader
xorg.libX11 xorg.libXcursor xorg.libXi xorg.libXrandr # To use the x11 feature
libxkbcommon wayland # To use the wayland feature
];
LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs;
}

6
src/ecs/component.rs Normal file
View File

@ -0,0 +1,6 @@
use std::any::Any;
pub trait Component {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}

12
src/ecs/entity.rs Normal file
View File

@ -0,0 +1,12 @@
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Entity {
pub handle: i32,
}
impl Entity {
pub fn new(handle: i32) -> Self {
Self {
handle
}
}
}

3
src/ecs/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod entity;
pub mod registry;
pub mod component;

131
src/ecs/registry.rs Normal file
View File

@ -0,0 +1,131 @@
use std::{collections::HashMap, cell::{RefCell, RefMut, Ref}, rc::Rc, borrow::BorrowMut, any::Any, ops::Deref};
use super::{entity::Entity, component::Component};
pub trait Registry {
/// Create an entity
fn create_entity(&mut self) -> Entity;
/// Remove the entity from the registry. Its components will also be destroyed
fn destroy_entity(&mut self, entity: Entity);
/// Add a component to an entity
fn entity_add_component<C: Component + 'static>(&mut self, entity: Entity, component: C) -> Option<Rc<RefCell<C>>>; // TODO: Return result if the component is already added.
/// Remove a component from an entity
fn entity_remove_component<C: Component + 'static>(&mut self, entity: Entity) -> bool;
fn entity_has_component<C: Component + 'static>(&self, entity: Entity) -> bool;
/// Get all the components that an entity has
fn entity_get_components(&self, entity: Entity) -> Option<Vec<Rc<RefCell<dyn Component>>>>;
/// Get a component of a specific type.
fn entity_get_component<C: Component + 'static>(&self, entity: Entity) -> Option<RefMut<'_, C>>;
}
pub struct BasicRegistry {
next_handle: i32,
map: HashMap<Entity, Vec<Rc<RefCell<dyn Component>>>>,
}
impl BasicRegistry {
pub fn new() -> Self {
Self {
next_handle: 0,
map: HashMap::new(),
}
}
}
impl Registry for BasicRegistry {
fn create_entity(&mut self) -> Entity {
//self.map.insert()
let entity = Entity::new(self.next_handle);
self.next_handle += 1;
self.map.insert(entity, Vec::new());
entity
}
fn destroy_entity(&mut self, entity: Entity) {
self.map.remove(&entity);
}
fn entity_add_component<C: Component + 'static>(&mut self, entity: Entity, component: C) -> Option<Rc<RefCell<C>>> {
if let Some(components) = self.map.get_mut(&entity) {
let rc = Rc::new(RefCell::new(component));
let clone = Rc::clone(&rc);
components.push(rc);
return Some(clone);
}
None
}
fn entity_remove_component<C: Component + 'static>(&mut self, entity: Entity) -> bool {
if let Some(components) = self.map.get_mut(&entity) {
let mut removed = false;
components.retain(|component| {
if removed {
return true;
}
let borrowed_comp = component.deref().borrow_mut();
let any_comp = borrowed_comp.as_any();
if any_comp.is::<C>() {
removed = true;
false
} else {
true
}
});
return removed;
}
false
}
fn entity_has_component<C: Component + 'static>(&self, entity: Entity) -> bool {
if let Some(components) = self.map.get(&entity) {
for component in components.iter() {
let borrowed_comp = component.deref().borrow_mut();
let any_comp = borrowed_comp.as_any();
if any_comp.is::<C>() {
return true;
}
}
}
false
}
fn entity_get_components(&self, entity: Entity) -> Option<Vec<Rc<RefCell<dyn Component>>>> {
if let Some(components) = self.map.get(&entity) {
let mut cloned_components = Vec::new();
for component in components.iter() {
cloned_components.push(Rc::clone(&component));
}
return Some(cloned_components);
}
None
}
fn entity_get_component<C: Component + 'static>(&self, entity: Entity) -> Option<RefMut<'_, C>> {
if let Some(components) = self.map.get(&entity) {
for component in components.iter() {
let borrowed_comp = component.deref().borrow_mut();
let any_comp = borrowed_comp.as_any();
if any_comp.is::<C>() {
return Some(RefMut::map(borrowed_comp, |c| c.as_any_mut().downcast_mut().unwrap()));
}
}
}
None
}
}

194
src/game.rs Normal file
View File

@ -0,0 +1,194 @@
use std::{sync::Arc, ops::Deref};
use async_std::{task::block_on, sync::Mutex};
use shipyard::{World, Workload};
use tracing::{metadata::LevelFilter, info, debug, Level, warn};
/* use tracing_subscriber::{filter::FilterFn, util::SubscriberInitExt};
use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt; */
use tracing_subscriber::{
layer::{Layer, SubscriberExt},
filter::FilterFn,
util::SubscriberInitExt,
};
use winit::{window::{WindowBuilder, Window}, event::{Event, WindowEvent, KeyboardInput, ElementState, VirtualKeyCode}, event_loop::{EventLoop, ControlFlow}};
use crate::{renderer::{Renderer, BasicRenderer}, input_event::InputEvent};
struct GameLoop {
window: Arc<Window>,
renderer: Box<dyn Renderer>,
}
impl GameLoop {
pub async fn new(window: &Arc<Window>) -> Self {
Self {
window: Arc::clone(&window),
renderer: Box::new(BasicRenderer::create_with_window(Arc::clone(window)).await),
}
}
pub async fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
self.renderer.on_resize(new_size).await;
}
pub fn run_sync(&mut self, event: Event<()>, control_flow: &mut ControlFlow) {
block_on(self.run_event_loop(event, control_flow))
}
async fn update(&mut self) {
todo!()
}
async fn input_update(&mut self, event: &InputEvent) -> Option<ControlFlow> {
match event {
InputEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape),
..
},
..
} => {
self.on_exit();
Some(ControlFlow::Exit)
},
_ => {
debug!("Got unhandled input event: \"{:?}\"", event);
None
}
}
}
fn render_window() {
todo!()
}
fn render_item() {
todo!()
}
fn on_exit(&mut self) {
info!("On exit!");
}
pub async fn run_event_loop(&mut self, event: Event<'_, ()>, control_flow: &mut ControlFlow) {
*control_flow = ControlFlow::Poll;
match event {
Event::WindowEvent {
ref event,
window_id,
} if window_id == self.window.id() => {
// If try_from failed, that means that the WindowEvent is not an
// input related event.
if let Ok(input_event) = InputEvent::try_from(event) {
if let Some(new_control) = self.input_update(&input_event).await {
*control_flow = new_control;
}
} else {
match event {
WindowEvent::CloseRequested => {
self.on_exit();
*control_flow = ControlFlow::Exit
},
WindowEvent::Resized(physical_size) => {
self.on_resize(*physical_size).await;
},
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
self.on_resize(**new_inner_size).await;
},
_ => {}
}
}
},
Event::RedrawRequested(window_id) if window_id == self.window.id() => {
match self.renderer.as_mut().render().await {
Ok(_) => {}
// Reconfigure the surface if lost
Err(wgpu::SurfaceError::Lost) => self.on_resize(self.renderer.as_ref().size()).await,
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// All other errors (Outdated, Timeout) should be resolved by the next frame
Err(e) => eprintln!("{:?}", e),
}
},
Event::MainEventsCleared => {
self.window.request_redraw();
},
_ => {}
}
}
}
pub struct Game {
}
impl Game {
/* fn lyra_engine_log_filter() {
} */
pub async fn initialize() -> Self {
let filter = FilterFn::new(|metadata| {
//metadata.
//println!("Metadata: {:?}", metadata);
metadata.module_path()
.unwrap_or_else(|| metadata.target())
.starts_with("lyra_engine") && (LevelFilter::INFO >= metadata.level().to_owned())
});
let layer = tracing_subscriber::fmt::layer();
tracing_subscriber::registry()
.with(layer.with_filter(filter))
.init();
Self {
}
}
fn update() {
todo!()
}
fn input_update() {
todo!()
}
fn render_window() {
todo!()
}
fn render_item() {
todo!()
}
fn exit() {
todo!()
}
pub async fn run(&mut self) {
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();
let mut g_loop = GameLoop::new(&Arc::new(window)).await;
event_loop.run(move |event, _, control_flow| {
g_loop.run_sync(event, control_flow);
});
}
}

248
src/input_event.rs Normal file
View File

@ -0,0 +1,248 @@
use winit::{event::{DeviceId, KeyboardInput, ModifiersState, MouseScrollDelta, TouchPhase, MouseButton, AxisId, Touch, WindowEvent, ElementState}, dpi::PhysicalPosition};
#[derive(Debug, PartialEq)]
pub enum InputEvent {
/// An event from the keyboard has been received.
KeyboardInput {
device_id: DeviceId,
input: KeyboardInput,
/// If true, the event was generated synthetically by winit in one of the following circumstances:
/// Synthetic key press events are generated for all keys pressed when a window gains focus.
/// Likewise, synthetic key release events are generated for all keys pressed when a window goes out of focus.
/// Currently, this is only functional on X11 and Windows
is_synthetic: bool,
},
/// The cursor has moved on the window.
CursorMoved {
device_id: DeviceId,
position: PhysicalPosition<f64>,
modifiers: ModifiersState,
},
/// The cursor has entered the window.
CursorEntered {
device_id: DeviceId,
},
/// The cursor has left the window.
CursorLeft {
device_id: DeviceId,
},
/// A mouse wheel movement or touchpad scroll occurred.
MouseWheel {
device_id: DeviceId,
delta: MouseScrollDelta,
phase: TouchPhase,
/// Deprecated in favor of WindowEvent::ModifiersChanged
modifiers: ModifiersState,
},
/// An mouse button press has been received.
MouseInput {
device_id: DeviceId,
state: ElementState,
button: MouseButton,
#[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"]
modifiers: ModifiersState,
},
/// Touchpad magnification event with two-finger pinch gesture.
/// Positive delta values indicate magnification (zooming in) and negative delta values indicate shrinking (zooming out).
///
/// Note: Only available on macOS
TouchpadMagnify {
device_id: DeviceId,
delta: f64,
phase: TouchPhase,
},
/// Smart magnification event.
///
/// On a Mac, smart magnification is triggered by a double tap with two fingers
/// on the trackpad and is commonly used to zoom on a certain object
/// (e.g. a paragraph of a PDF) or (sort of like a toggle) to reset any zoom.
/// The gesture is also supported in Safari, Pages, etc.
///
/// The event is general enough that its generating gesture is allowed to vary
/// across platforms. It could also be generated by another device.
///
/// Unfortunatly, neither [Windows](https://support.microsoft.com/en-us/windows/touch-gestures-for-windows-a9d28305-4818-a5df-4e2b-e5590f850741)
/// nor [Wayland](https://wayland.freedesktop.org/libinput/doc/latest/gestures.html)
/// support this gesture or any other gesture with the same effect.
///
/// ## Platform-specific
///
/// - Only available on **macOS 10.8** and later.
SmartMagnify { device_id: DeviceId },
/// Touchpad rotation event with two-finger rotation gesture.
///
/// Positive delta values indicate rotation counterclockwise and
/// negative delta values indicate rotation clockwise.
///
/// ## Platform-specific
///
/// - Only available on **macOS**.
TouchpadRotate {
device_id: DeviceId,
delta: f32,
phase: TouchPhase,
},
/// Touchpad pressure event.
///
/// At the moment, only supported on Apple forcetouch-capable macbooks.
/// The parameters are: pressure level (value between 0 and 1 representing how hard the touchpad
/// is being pressed) and stage (integer representing the click level).
TouchpadPressure {
device_id: DeviceId,
pressure: f32,
stage: i64,
},
/// Motion on some analog axis. May report data redundant to other, more specific events.
AxisMotion {
device_id: DeviceId,
axis: AxisId,
value: f64,
},
/// Touch event has been received
///
/// ## Platform-specific
///
/// - **macOS:** Unsupported.
Touch(Touch),
}
#[derive(Debug, PartialEq)]
pub enum InputEventConversionError<'a> {
FromError(&'a WindowEvent<'a>)
}
impl<'a> Into<WindowEvent<'a>> for InputEvent {
fn into(self) -> WindowEvent<'a> {
match self {
InputEvent::KeyboardInput { device_id, input, is_synthetic } => WindowEvent::KeyboardInput { device_id, input, is_synthetic },
InputEvent::CursorMoved { device_id, position, modifiers } => WindowEvent::CursorMoved { device_id, position, modifiers },
InputEvent::CursorEntered { device_id } => WindowEvent::CursorEntered { device_id },
InputEvent::CursorLeft { device_id } => WindowEvent::CursorLeft { device_id },
InputEvent::MouseWheel { device_id, delta, phase, modifiers } => WindowEvent::MouseWheel { device_id, delta, phase, modifiers },
InputEvent::MouseInput { device_id, state, button, modifiers } => WindowEvent::MouseInput { device_id, state, button, modifiers },
InputEvent::TouchpadMagnify { device_id, delta, phase } => WindowEvent::TouchpadMagnify { device_id, delta, phase },
InputEvent::SmartMagnify { device_id } => WindowEvent::SmartMagnify { device_id },
InputEvent::TouchpadRotate { device_id, delta, phase } => WindowEvent::TouchpadRotate { device_id, delta, phase },
InputEvent::TouchpadPressure { device_id, pressure, stage } => WindowEvent::TouchpadPressure { device_id, pressure, stage },
InputEvent::AxisMotion { device_id, axis, value } => WindowEvent::AxisMotion { device_id, axis, value },
InputEvent::Touch(t) => WindowEvent::Touch(t),
}
}
}
impl<'a> TryFrom<&'a WindowEvent<'a>> for InputEvent {
type Error = InputEventConversionError<'a>;
fn try_from(value: &'a WindowEvent<'a>) -> Result<Self, Self::Error> {
match value {
WindowEvent::KeyboardInput { device_id, input, is_synthetic } =>
Ok(InputEvent::KeyboardInput {
device_id: device_id.clone(),
input: input.clone(),
is_synthetic: is_synthetic.clone()
}),
WindowEvent::CursorMoved { device_id, position, modifiers } =>
Ok(InputEvent::CursorMoved {
device_id: device_id.clone(),
position: position.clone(),
modifiers: modifiers.clone()
}),
WindowEvent::CursorEntered { device_id } =>
Ok(InputEvent::CursorEntered {
device_id: device_id.clone()
}),
WindowEvent::CursorLeft { device_id } =>
Ok(InputEvent::CursorLeft {
device_id: device_id.clone()
}),
WindowEvent::MouseWheel { device_id, delta, phase, modifiers } =>
Ok(InputEvent::MouseWheel {
device_id: device_id.clone(),
delta: delta.clone(),
phase: phase.clone(),
modifiers: modifiers.clone()
}),
WindowEvent::MouseInput { device_id, state, button, modifiers } =>
Ok(InputEvent::MouseInput {
device_id: device_id.clone(),
state: state.clone(),
button: button.clone(),
modifiers: modifiers.clone()
}),
WindowEvent::TouchpadMagnify { device_id, delta, phase } =>
Ok(InputEvent::TouchpadMagnify {
device_id: device_id.clone(),
delta: delta.clone(),
phase: phase.clone()
}),
WindowEvent::SmartMagnify { device_id } =>
Ok(InputEvent::SmartMagnify {
device_id: device_id.clone()
}),
WindowEvent::TouchpadRotate { device_id, delta, phase } =>
Ok(InputEvent::TouchpadRotate {
device_id: device_id.clone(),
delta: delta.clone(),
phase: phase.clone()
}),
WindowEvent::TouchpadPressure { device_id, pressure, stage } =>
Ok(InputEvent::TouchpadPressure {
device_id: device_id.clone(),
pressure: pressure.clone(),
stage: stage.clone()
}),
WindowEvent::AxisMotion { device_id, axis, value } =>
Ok(InputEvent::AxisMotion {
device_id: device_id.clone(),
axis: axis.clone(),
value: value.clone()
}),
WindowEvent::Touch(t) => Ok(InputEvent::Touch(t.clone())),
_ => Err(InputEventConversionError::FromError(value))
}
}
}
/* impl<'a> TryFrom<WindowEvent<'a>> for InputEvent {
type Error = InputEventConversionError<'a>;
fn try_from(value: WindowEvent<'a>) -> Result<Self, Self::Error> {
match value {
WindowEvent::KeyboardInput { device_id, input, is_synthetic } =>
Ok(InputEvent::KeyboardInput { device_id, input, is_synthetic }),
WindowEvent::CursorMoved { device_id, position, modifiers } =>
Ok(InputEvent::CursorMoved { device_id, position, modifiers }),
WindowEvent::CursorEntered { device_id } => Ok(InputEvent::CursorEntered { device_id }),
WindowEvent::CursorLeft { device_id } => Ok(InputEvent::CursorLeft { device_id }),
WindowEvent::MouseWheel { device_id, delta, phase, modifiers } =>
Ok(InputEvent::MouseWheel { device_id, delta, phase, modifiers }),
WindowEvent::MouseInput { device_id, state, button, modifiers } =>
Ok(InputEvent::MouseInput { device_id, state, button, modifiers }),
WindowEvent::TouchpadMagnify { device_id, delta, phase } =>
Ok(InputEvent::TouchpadMagnify { device_id, delta, phase }),
WindowEvent::SmartMagnify { device_id } => Ok(InputEvent::SmartMagnify { device_id }),
WindowEvent::TouchpadRotate { device_id, delta, phase } =>
Ok(InputEvent::TouchpadRotate { device_id, delta, phase }),
WindowEvent::TouchpadPressure { device_id, pressure, stage } =>
Ok(InputEvent::TouchpadPressure { device_id, pressure, stage }),
WindowEvent::AxisMotion { device_id, axis, value } =>
Ok(InputEvent::AxisMotion { device_id, axis, value }),
WindowEvent::Touch(t) => Ok(InputEvent::Touch(t)),
_ => Err(InputEventConversionError::FromError(value))
}
}
} */

62
src/main.rs Normal file
View File

@ -0,0 +1,62 @@
mod system;
mod game;
mod renderer;
//mod ecs;
mod input_event;
use game::Game;
use shipyard::{Component, World, EntitiesViewMut, ViewMut, Get, Workload};
use shipyard::IntoWorkload;
//use ecs::{registry::Registry, component::Component};
#[derive(Component, Debug)]
struct Point2d {
x: i32,
y: i32,
}
impl Point2d {
pub fn new(x: i32, y: i32) -> Self {
Self {
x,
y,
}
}
}
#[async_std::main]
async fn main() {
let mut world = World::new();
let entity = world.add_entity(Point2d::new(10, 10));
let entity2 = world.add_entity(Point2d::new(846, 2188));
world.run(
|mut vm_point: ViewMut<Point2d>| {
let mut p = (&mut vm_point).get(entity).unwrap();
println!("Point: {:?}", p);
p.x = 100;
println!("Point after: {:?}", p);
drop(p);
let mut p = (&mut vm_point).get(entity2).unwrap();
println!("Point: {:?}", p);
p.x = 1000;
println!("Point after: {:?}", p);
//let mut vm_
//println!("Point2d: {:?}", vm_point.get);
/* let empty_entity = entities.add_entity((), ());
let single_component = entities.add_entity(&mut vm_pos, Pos::new());
let multiple_components =
entities.add_entity((&mut vm_pos, &mut vm_vel), (Pos::new(), Vel::new())); */
},
);
let mut game = Game::initialize().await;
game.run().await;
}

204
src/renderer.rs Normal file
View File

@ -0,0 +1,204 @@
use std::sync::Arc;
use async_trait::async_trait;
//use winit::{window::Window, event::WindowEvent};
//use winit::window::Window;
use winit::{window::Window, event::WindowEvent};
#[async_trait]
pub trait Renderer {
//fn new(surface: wgpu::Surface, config: wgpu::SurfaceConfiguration, device: wgpu::Device)
//async fn create_with_window(window: Window) -> Self;
async fn render(&mut self) -> Result<(), wgpu::SurfaceError>;
async fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>);
fn size(&self) -> winit::dpi::PhysicalSize<u32>;
}
pub struct BasicRenderer {
pub surface: wgpu::Surface,
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub config: wgpu::SurfaceConfiguration,
pub size: winit::dpi::PhysicalSize<u32>,
pub window: Arc<Window>,
pub clear_color: wgpu::Color,
pub render_pipeline: wgpu::RenderPipeline,
}
impl BasicRenderer {
pub async fn create_with_window(window: Arc<Window>) -> BasicRenderer {
let size = window.inner_size();
// Get a GPU handle
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
dx12_shader_compiler: Default::default(),
});
// This should be safe since surface will live as long as the window that created it
let surface = unsafe { instance.create_surface(window.as_ref()) }.unwrap();
let adapter = instance.request_adapter(
&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
},
).await.unwrap();
let (device, queue) = adapter.request_device(
&wgpu::DeviceDescriptor {
features: wgpu::Features::empty(),
// WebGL does not support all wgpu features.
// Not sure if the engine will ever completely support WASM,
// but its here just in case
limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults()
} else {
wgpu::Limits::default()
},
label: None,
},
None,
).await.unwrap();
let surface_caps = surface.get_capabilities(&adapter);
let surface_format = surface_caps.formats.iter()
.copied()
.filter(|f| f.describe().srgb)
.next()
.unwrap_or(surface_caps.formats[0]);
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width,
height: size.height,
present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![],
};
surface.configure(&device, &config);
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
});
// Create a render pipeline. At some point this will be created by something else and given to the renderer
let render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
bind_group_layouts: &[],
push_constant_ranges: &[],
});
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render Pipeline"),
layout: Some(&render_pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: config.format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
// Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE
polygon_mode: wgpu::PolygonMode::Fill,
// Requires Features::DEPTH_CLIP_CONTROL
unclipped_depth: false,
// Requires Features::CONSERVATIVE_RASTERIZATION
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
});
Self {
window,
surface,
device,
queue,
config,
size,
clear_color: wgpu::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
},
render_pipeline
}
}
}
#[async_trait]
impl Renderer for BasicRenderer {
async fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
let output = self.surface.get_current_texture()?;
let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Basic Renderer's Encoder")
});
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Basic Renderer's Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(self.clear_color),
store: true,
},
})],
depth_stencil_attachment: None,
});
render_pass.set_pipeline(&self.render_pipeline);
render_pass.draw(0..3, 0..1);
}
self.queue.submit(std::iter::once(encoder.finish()));
output.present();
Ok(())
}
async fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
if new_size.width > 0 && new_size.height > 0 {
self.size = new_size;
self.config.width = new_size.width;
self.config.height = new_size.height;
self.surface.configure(&self.device, &self.config);
}
}
fn size(&self) -> winit::dpi::PhysicalSize<u32> {
self.size
}
}

23
src/shader.wgsl Normal file
View File

@ -0,0 +1,23 @@
// Vertex shader
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
};
@vertex
fn vs_main(
@builtin(vertex_index) in_vertex_index: u32,
) -> VertexOutput {
var out: VertexOutput;
let x = f32(1 - i32(in_vertex_index)) * 0.5;
let y = f32(i32(in_vertex_index & 1u) * 2 - 1) * 0.5;
out.clip_position = vec4<f32>(x, y, 0.0, 1.0);
return out;
}
// Fragment shader
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(0.3, 0.2, 0.1, 1.0);
}

10
src/system.rs Normal file
View File

@ -0,0 +1,10 @@
use std::time::Duration;
trait System {
fn new();
fn update(dt: Duration);
fn input_update(dt: Duration);
fn render(interopolate_alpha: f32, dt: Duration);
fn destroy();
}