Skip to content

Commit

Permalink
Encapsulate initialization code in dume-winit crate, now with WebAsse…
Browse files Browse the repository at this point in the history
…mbly support
  • Loading branch information
caelunshun committed Oct 28, 2021
1 parent 60a2c04 commit 23001a0
Show file tree
Hide file tree
Showing 31 changed files with 455 additions and 267 deletions.
40 changes: 6 additions & 34 deletions Cargo.toml
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
23 changes: 23 additions & 0 deletions crates/dume-winit/Cargo.toml
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"
80 changes: 80 additions & 0 deletions crates/dume-winit/examples/text.rs
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);
});
}
226 changes: 226 additions & 0 deletions crates/dume-winit/src/lib.rs
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);
}
}
Loading

0 comments on commit 23001a0

Please sign in to comment.