-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Encapsulate initialization code in dume-winit crate, now with WebAsse…
…mbly support
- Loading branch information
1 parent
60a2c04
commit 23001a0
Showing
31 changed files
with
455 additions
and
267 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,9 @@ | ||
[package] | ||
name = "dume" | ||
version = "0.1.0" | ||
authors = ["caelunshun <caelunshun@gmail.com>"] | ||
edition = "2021" | ||
|
||
[dependencies] | ||
ahash = "0.7" | ||
anyhow = "1" | ||
bytemuck = { version = "1", features = [ "derive" ] } | ||
flume = "0.10" | ||
glam = { version = "0.17", features = [ "bytemuck" ] } | ||
guillotiere = "0.6" | ||
log = "0.4" | ||
lru = "0.7" | ||
lyon = "0.17" | ||
once_cell = "1" | ||
palette = "0.6" | ||
parking_lot = "0.11" | ||
rectangle-pack = "0.4" | ||
serde = { version = "1", features = [ "derive" ] } | ||
slotmap = "1" | ||
smallvec = "1" | ||
smartstring = { version = "0.2", features = [ "serde" ] } | ||
swash = "0.1" | ||
thiserror = "1" | ||
unicode-bidi = "0.3" | ||
wgpu = "0.11" | ||
|
||
[dev-dependencies] | ||
image = { version = "0.23", default-features = false, features = [ "jpeg" ] } | ||
pollster = "0.2" | ||
simple_logger = "1" | ||
winit = { version = "0.25", default-features = false, features = [ "x11" ] } | ||
[workspace] | ||
members = [ | ||
"crates/dume", | ||
"crates/dume-winit" | ||
] | ||
resolver = "2" | ||
|
||
[profile.dev] | ||
opt-level = 3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
[package] | ||
name = "dume-winit" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
dume = { path = "../dume" } | ||
glam = "0.17" | ||
wgpu = { version = "0.11", features = ["webgl"] } | ||
winit = { version = "0.25", default-features = false, features = ["web-sys"] } | ||
|
||
[features] | ||
default = ["x11"] | ||
x11 = ["winit/x11"] | ||
wayland = ["winit/wayland"] | ||
|
||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] | ||
pollster = "0.2" | ||
|
||
[target.'cfg(target_arch = "wasm32")'.dependencies] | ||
console_error_panic_hook = "0.1" | ||
wasm-bindgen-futures = "0.4" | ||
web-sys = "0.3" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
//! Renders a bunch of text. | ||
|
||
use std::iter; | ||
|
||
use dume::{Canvas, Context, Text, TextBlob, TextSection, TextStyle}; | ||
use dume_winit::{block_on, Application, DumeWinit}; | ||
use glam::vec2; | ||
use winit::{event_loop::EventLoop, window::WindowBuilder}; | ||
|
||
static TEXT: &str = r#" | ||
The spotted hawk swoops by and accuses me, he complains of my gab and my loitering. | ||
I too am not a bit tamed, I too am untranslatable, | ||
I sound my barbaric yawp over the roofs of the world. | ||
The last scud of day holds back for me, | ||
It flings my likeness after the rest and true as any on the shadow’d wilds, | ||
It coaxes me to the vapor and the dusk. | ||
I depart as air, I shake my white locks at the runaway sun, | ||
I effuse my flesh in eddies, and drift it in lacy jags. | ||
I bequeath myself to the dirt to grow from the grass I love, | ||
If you want me again look for me under your boot-soles. | ||
You will hardly know who I am or what I mean, | ||
But I shall be good health to you nevertheless, | ||
And filter and fibre your blood. | ||
Failing to fetch me at first keep encouraged, | ||
Missing me one place search another, | ||
I stop somewhere waiting for you. | ||
"#; | ||
|
||
struct App { | ||
text: TextBlob, | ||
} | ||
|
||
impl App { | ||
pub fn new(cx: &Context) -> Self { | ||
cx.add_font(include_bytes!("../../../assets/ZenAntiqueSoft-Regular.ttf").to_vec()) | ||
.unwrap(); | ||
cx.set_default_font_family("Zen Antique Soft"); | ||
|
||
let text = cx.create_text_blob( | ||
Text::from_sections(iter::once(TextSection::Text { | ||
text: TEXT.into(), | ||
style: TextStyle { | ||
size: 20., | ||
..Default::default() | ||
}, | ||
})), | ||
Default::default(), | ||
); | ||
Self { text } | ||
} | ||
} | ||
|
||
impl Application for App { | ||
fn draw(&mut self, canvas: &mut Canvas) { | ||
canvas.draw_text(&self.text, vec2(10., 50.), 1.); | ||
} | ||
} | ||
|
||
fn main() { | ||
std::panic::set_hook(Box::new(console_error_panic_hook::hook)); | ||
let event_loop = EventLoop::new(); | ||
let window = WindowBuilder::new() | ||
.with_title("Dume Text Example") | ||
.build(&event_loop) | ||
.unwrap(); | ||
|
||
block_on(async move { | ||
let dume = DumeWinit::new(window).await; | ||
|
||
let app = App::new(dume.context()); | ||
|
||
dume.run(event_loop, app); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
use std::{future::Future, iter, sync::Arc}; | ||
|
||
use dume::{Canvas, Context}; | ||
use glam::{vec2, Vec2}; | ||
use winit::{ | ||
dpi::PhysicalSize, | ||
event::{Event, WindowEvent}, | ||
event_loop::{ControlFlow, EventLoop}, | ||
window::Window, | ||
}; | ||
|
||
pub struct DumeWinit { | ||
context: Context, | ||
main_canvas: Canvas, | ||
|
||
sample_texture: wgpu::TextureView, | ||
surface: wgpu::Surface, | ||
|
||
window: Window, | ||
} | ||
|
||
impl DumeWinit { | ||
/// Creates an app given the window. | ||
/// | ||
/// This function initializes `wgpu` state and creates a [`Context`](dume::Context). | ||
/// For more control over initialization, use [`from_context`]. | ||
/// | ||
/// | ||
/// On WebAssembly targets, this also adds the window to the root HTML element. | ||
pub async fn new(window: Window) -> Self { | ||
#[cfg(target_arch = "wasm32")] | ||
{ | ||
use winit::platform::web::WindowExtWebSys; | ||
|
||
let canvas = window.canvas(); | ||
|
||
let window = web_sys::window().unwrap(); | ||
let document = window.document().unwrap(); | ||
let body = document.body().unwrap(); | ||
|
||
body.append_child(&canvas) | ||
.expect("failed to append canvas to HTML body"); | ||
} | ||
|
||
let (context, surface) = init_context(&window).await; | ||
|
||
Self::from_context(context, surface, window) | ||
} | ||
|
||
/// Creates an `App` from an existing `Context`. | ||
pub fn from_context(context: Context, surface: wgpu::Surface, window: Window) -> Self { | ||
let sample_texture = create_sample_texture(window.inner_size(), context.device()); | ||
configure_surface(&surface, context.device(), window.inner_size()); | ||
|
||
Self { | ||
main_canvas: context.create_canvas(logical_size(&window), window.scale_factor() as f32), | ||
context, | ||
sample_texture, | ||
surface, | ||
window, | ||
} | ||
} | ||
|
||
pub fn context(&self) -> &Context { | ||
&self.context | ||
} | ||
|
||
pub fn main_canvas(&mut self) -> &mut Canvas { | ||
&mut self.main_canvas | ||
} | ||
|
||
pub fn window(&self) -> &Window { | ||
&self.window | ||
} | ||
|
||
/// Runs the main event loop. | ||
/// | ||
/// Calls `draw` whenever a frame should be drawn. | ||
/// Calls `on_event` with any window events received from `winit`. | ||
pub fn run(mut self, event_loop: EventLoop<()>, mut application: impl Application + 'static) { | ||
event_loop.run(move |event, _, control_flow| { | ||
*control_flow = ControlFlow::Poll; | ||
|
||
match event { | ||
Event::MainEventsCleared => self.window.request_redraw(), | ||
Event::RedrawRequested(_) => { | ||
let mut encoder = self | ||
.context | ||
.device() | ||
.create_command_encoder(&Default::default()); | ||
|
||
let frame = self | ||
.surface | ||
.get_current_texture() | ||
.expect("failed to get swap chain frame"); | ||
|
||
application.draw(&mut self.main_canvas); | ||
|
||
self.main_canvas.render( | ||
&mut encoder, | ||
&frame.texture.create_view(&Default::default()), | ||
&self.sample_texture, | ||
); | ||
|
||
self.context.queue().submit(iter::once(encoder.finish())); | ||
|
||
frame.present(); | ||
} | ||
Event::WindowEvent { event, .. } => { | ||
application.on_event(&self.context, &event); | ||
match event { | ||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, | ||
WindowEvent::Resized(new_size) => { | ||
configure_surface(&self.surface, self.context.device(), new_size); | ||
self.sample_texture = | ||
create_sample_texture(new_size, self.context.device()); | ||
|
||
self.main_canvas.resize( | ||
logical_size(&self.window), | ||
self.window.scale_factor() as f32, | ||
); | ||
} | ||
_ => {} | ||
} | ||
} | ||
_ => {} | ||
} | ||
}); | ||
} | ||
} | ||
|
||
fn create_sample_texture(size: PhysicalSize<u32>, device: &wgpu::Device) -> wgpu::TextureView { | ||
device | ||
.create_texture(&wgpu::TextureDescriptor { | ||
label: None, | ||
size: wgpu::Extent3d { | ||
width: size.width, | ||
height: size.height, | ||
depth_or_array_layers: 1, | ||
}, | ||
mip_level_count: 1, | ||
sample_count: dume::SAMPLE_COUNT, | ||
dimension: wgpu::TextureDimension::D2, | ||
format: dume::TARGET_FORMAT, | ||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT, | ||
}) | ||
.create_view(&Default::default()) | ||
} | ||
|
||
fn configure_surface(surface: &wgpu::Surface, device: &wgpu::Device, size: PhysicalSize<u32>) { | ||
surface.configure( | ||
device, | ||
&wgpu::SurfaceConfiguration { | ||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT, | ||
format: dume::TARGET_FORMAT, | ||
width: size.width, | ||
height: size.height, | ||
present_mode: wgpu::PresentMode::Mailbox, | ||
}, | ||
) | ||
} | ||
|
||
pub trait Application { | ||
fn draw(&mut self, canvas: &mut Canvas); | ||
|
||
fn on_event(&mut self, context: &Context, event: &WindowEvent) { | ||
let _ = (context, event); | ||
} | ||
} | ||
|
||
fn logical_size(window: &Window) -> Vec2 { | ||
let size = window.inner_size().to_logical(window.scale_factor()); | ||
vec2(size.width, size.height) | ||
} | ||
|
||
async fn init_context(window: &Window) -> (Context, wgpu::Surface) { | ||
let (device, queue, surface) = init_wgpu(window).await; | ||
let device = Arc::new(device); | ||
let queue = Arc::new(queue); | ||
|
||
(Context::builder(device, queue).build(), surface) | ||
} | ||
|
||
async fn init_wgpu(window: &Window) -> (wgpu::Device, wgpu::Queue, wgpu::Surface) { | ||
let instance = wgpu::Instance::new(wgpu::Backends::all()); | ||
let surface = unsafe { instance.create_surface(window) }; | ||
let adapter = instance | ||
.request_adapter(&wgpu::RequestAdapterOptions { | ||
power_preference: wgpu::PowerPreference::HighPerformance, | ||
force_fallback_adapter: false, | ||
compatible_surface: Some(&surface), | ||
}) | ||
.await | ||
.expect("failed to get a suitable adapter"); | ||
|
||
let (device, queue) = adapter | ||
.request_device( | ||
&wgpu::DeviceDescriptor { | ||
label: None, | ||
features: wgpu::Features::default(), | ||
#[cfg(target_arch = "wasm32")] | ||
limits: wgpu::Limits::downlevel_webgl2_defaults(), | ||
#[cfg(not(target_arch = "wasm32"))] | ||
limits: wgpu::Limits::default(), | ||
}, | ||
None, | ||
) | ||
.await | ||
.expect("failed to get wgpu device"); | ||
|
||
(device, queue, surface) | ||
} | ||
|
||
/// Convenience function to block on a future, useful for [`Context::new`]. | ||
/// | ||
/// This function works on both native and WebAssembly targets. | ||
pub fn block_on(future: impl Future<Output = ()> + 'static) { | ||
#[cfg(target_arch = "wasm32")] | ||
{ | ||
wasm_bindgen_futures::spawn_local(future); | ||
} | ||
#[cfg(not(target_arch = "wasm32"))] | ||
{ | ||
pollster::block_on(future); | ||
} | ||
} |
Oops, something went wrong.