Initial commit

This commit is contained in:
davidon-top 2024-01-28 20:55:49 +01:00
commit 3516d6b906
Signed by: DavidOnTop
GPG key ID: FAB914DDC2F180EB
10 changed files with 2648 additions and 0 deletions

13
.editorconfig Normal file
View file

@ -0,0 +1,13 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
[*.{yml,yaml}]
indent_style = space
indent_size = 2

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

11
.rustfmt.toml Normal file
View file

@ -0,0 +1,11 @@
condense_wildcard_suffixes = true
edition = "2021"
fn_single_line = true
format_code_in_doc_comments = true
format_macro_matchers = true
hard_tabs = true
match_block_trailing_comma = true
imports_granularity = "Crate"
newline_style = "Unix"
group_imports = "StdExternalCrate"
tab_spaces = 4

2339
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

21
Cargo.toml Normal file
View file

@ -0,0 +1,21 @@
[package]
name = "randr"
version = "0.1.0"
edition = "2021"
description = "Simple wgpu renderer"
authors = ["DavidOnTop <me@davidon.top>"]
readme = "README.md"
documentation = "https://docs.rs/randr"
license = "MIT"
repository = "git@github.com:davidon-top/randr.git"
[dependencies]
anyhow = "1.0.75"
wgpu = "0.18.0"
winit = { version = "0.29.4", features = ["rwh_05"] }
winit_input_helper = "0.15.1"
tracing = "0.1"
[dev-dependencies]
tokio = { version = "1.35.1", features = ["full"] }
tracing-subscriber = "0.3"

22
LICENSE-MIT Normal file
View file

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2023 DavidOnTop
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.

1
README.md Normal file
View file

@ -0,0 +1 @@
# Simple wgpu renderer

128
src/lib.rs Normal file
View file

@ -0,0 +1,128 @@
pub use wgpu;
pub use winit;
pub use winit_input_helper;
use wgpu::{SurfaceError, TextureFormat};
use winit::{event::{WindowEvent, Event}, error::EventLoopError, event_loop::EventLoop};
pub trait Renderer: Sized {
fn init(&mut self, _window: &winit::window::Window, _wgpu: &Wgpu, _swapchain_formats: TextureFormat) -> anyhow::Result<()> {Ok(())}
fn resize(&mut self, _size: winit::dpi::PhysicalSize<u32>) {}
fn input(&mut self, _input: &winit_input_helper::WinitInputHelper) {}
fn update(&mut self, _wgpu: &Wgpu) {}
fn render(&mut self, wgpu: &Wgpu) -> Result<(), wgpu::SurfaceError>;
}
pub struct App<R: Renderer> {
pub window: winit::window::Window,
pub renderer: R,
pub input: winit_input_helper::WinitInputHelper,
pub wgpu: Wgpu,
}
pub struct Wgpu {
pub surface: wgpu::Surface,
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub config: wgpu::SurfaceConfiguration,
pub size: winit::dpi::PhysicalSize<u32>,
}
/// dependency injection for creating App
#[derive(Default)]
pub struct AppDI {
window: Option<winit::window::WindowBuilder>,
power_preference: Option<wgpu::PowerPreference>,
features: Option<wgpu::Features>,
limits: Option<wgpu::Limits>,
present_mode: Option<wgpu::PresentMode>,
}
impl<R: Renderer> App<R> {
pub async fn new(mut renderer: R, event_loop: &EventLoop<()>, di: AppDI) -> Self {
let input = winit_input_helper::WinitInputHelper::new();
let window = di.window.unwrap_or_else(|| winit::window::WindowBuilder::new())
.build(&event_loop).unwrap();
let size = window.inner_size();
let instance = wgpu::Instance::default();
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: di.power_preference.unwrap_or_default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
}).await.unwrap();
let (device, queue) = adapter.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: di.features.unwrap_or_default(),
limits: di.limits.unwrap_or_default(),
},
None,
).await.unwrap();
let swapchain_capabilities = surface.get_capabilities(&adapter);
let swapchain_formats = swapchain_capabilities.formats[0];
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: swapchain_formats,
width: size.width,
height: size.height,
present_mode: di.present_mode.unwrap_or_default(),
alpha_mode: swapchain_capabilities.alpha_modes[0],
view_formats: vec![],
};
surface.configure(&device, &config);
let wgpu = Wgpu { surface, device, queue, config, size };
renderer.init(&window, &wgpu, swapchain_formats).unwrap();
Self { renderer, input, window, wgpu }
}
fn resize(&mut self, size: winit::dpi::PhysicalSize<u32>) {
self.wgpu.size = size;
self.wgpu.config.width = size.width;
self.wgpu.config.height = size.height;
self.wgpu.surface.configure(&self.wgpu.device, &self.wgpu.config);
self.renderer.resize(size);
}
pub fn run(mut self, event_loop: EventLoop<()>) -> Result<(), EventLoopError> {
event_loop.run(move |event, elwt| {
if self.input.update(&event) {
if self.input.close_requested() {
elwt.exit();
}
if let Some(size) = self.input.window_resized() {
self.resize(size);
self.window.request_redraw();
} if let Some(_) = self.input.scale_factor_changed() {
self.resize(self.window.inner_size());
self.window.request_redraw();
}
self.renderer.input(&self.input);
}
if let Event::WindowEvent { event, .. } = event {
if let WindowEvent::RedrawRequested = event {
self.renderer.update(&self.wgpu);
let res = self.renderer.render(&self.wgpu);
match res {
Ok(_) => {},
Err(wgpu::SurfaceError::Lost) => self.resize(self.wgpu.size),
Err(wgpu::SurfaceError::OutOfMemory) => elwt.exit(),
Err(e) => tracing::error!("{:?}", e),
}
}
}
})
}
}

101
tests/hello_triangle.rs Normal file
View file

@ -0,0 +1,101 @@
use randr::{Renderer, App, Wgpu, AppDI};
use wgpu::{include_wgsl, TextureFormat};
use winit::{event_loop::{EventLoop, EventLoopBuilder}, platform::wayland::EventLoopBuilderExtWayland};
struct Randr {
color: wgpu::Color,
clear_color: wgpu::Color,
render_pipeline: Option<wgpu::RenderPipeline>,
}
impl Default for Randr {
fn default() -> Self {
Self {
color: wgpu::Color::BLUE,
clear_color: wgpu::Color::BLACK,
render_pipeline: None,
}
}
}
impl Renderer for Randr {
fn render(&mut self, wgpu: &Wgpu) -> Result<(), wgpu::SurfaceError> {
let frame = wgpu.surface.get_current_texture()?;
let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = wgpu.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(self.clear_color),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
rpass.set_pipeline(&self.render_pipeline.as_ref().unwrap());
rpass.draw(0..3, 0..1);
drop(rpass);
wgpu.queue.submit(Some(encoder.finish()));
frame.present();
Ok(())
}
fn init(&mut self, _window: &winit::window::Window, wgpu: &Wgpu, swapchain_formats: TextureFormat) -> anyhow::Result<()> {
let shader = wgpu.device.create_shader_module(include_wgsl!("shader.wgsl"));
let pipeline_layout = wgpu.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[],
push_constant_ranges: &[],
});
let render_pipeline = wgpu.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(swapchain_formats.into())],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
});
self.render_pipeline = Some(render_pipeline);
Ok(())
}
fn resize(&mut self, _size: winit::dpi::PhysicalSize<u32>) {}
fn input(&mut self, _input: &winit_input_helper::WinitInputHelper) {}
fn update(&mut self, _wgpu: &Wgpu) {}
}
#[test]
fn hello_triangle() {
tracing_subscriber::fmt().with_max_level(tracing::Level::TRACE).init();
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let event_loop = EventLoopBuilder::new().with_wayland().with_any_thread(true).build().unwrap();
App::new(Randr::default(), &event_loop, AppDI::default()).await.run(event_loop).unwrap();
});
}

11
tests/shader.wgsl Normal file
View file

@ -0,0 +1,11 @@
@vertex
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
let x = f32(i32(in_vertex_index) - 1);
let y = f32(i32(in_vertex_index & 1u) * 2 - 1);
return vec4<f32>(x, y, 0.0, 1.0);
}
@fragment
fn fs_main() -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}