Initial commit
This commit is contained in:
commit
3516d6b906
10 changed files with 2648 additions and 0 deletions
13
.editorconfig
Normal file
13
.editorconfig
Normal 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
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
11
.rustfmt.toml
Normal file
11
.rustfmt.toml
Normal 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
2339
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
21
Cargo.toml
Normal file
21
Cargo.toml
Normal 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
22
LICENSE-MIT
Normal 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
1
README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# Simple wgpu renderer
|
128
src/lib.rs
Normal file
128
src/lib.rs
Normal 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
101
tests/hello_triangle.rs
Normal 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
11
tests/shader.wgsl
Normal 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);
|
||||||
|
}
|
Loading…
Reference in a new issue