lf_gfx/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(unused_extern_crates)]
3
4mod fragment_only;
5mod game;
6mod limits;
7
8#[cfg(target_arch = "wasm32")]
9mod wasm;
10
11use std::sync::atomic::AtomicU32;
12use std::sync::atomic::Ordering;
13
14pub use fragment_only::FragmentOnlyRenderBundleEncoder;
15pub use fragment_only::FragmentOnlyRenderBundleEncoderDescriptor;
16pub use fragment_only::FragmentOnlyRenderPass;
17pub use fragment_only::FragmentOnlyRenderPassDescriptor;
18pub use fragment_only::FragmentOnlyRenderPipeline;
19pub use fragment_only::FragmentOnlyRenderPipelineDescriptor;
20pub use game::window::GameWindow;
21pub use game::window::WindowSizeDependent;
22pub use game::ExitFlag;
23pub use game::Game;
24pub use game::GameCommand;
25pub use game::GameData;
26pub use game::InputMode;
27pub mod input {
28    pub use crate::game::input::*;
29}
30pub mod local_storage;
31
32// Resolve https://github.com/rust-lang/rustc-hash/issues/14 by wrapping `rustc_hash::FxHasher`.
33static FAST_HASH_SEED: AtomicU32 = AtomicU32::new(0);
34pub struct FastHashState {
35    seed: u32,
36}
37impl std::hash::BuildHasher for FastHashState {
38    type Hasher = rustc_hash::FxHasher;
39
40    fn build_hasher(&self) -> Self::Hasher {
41        use std::hash::Hasher;
42
43        let mut hasher = rustc_hash::FxHasher::default();
44        hasher.write_u32(self.seed);
45        return hasher;
46    }
47}
48impl Default for FastHashState {
49    fn default() -> Self {
50        Self {
51            seed: FAST_HASH_SEED.fetch_add(1, Ordering::SeqCst),
52        }
53    }
54}
55
56/// A non-cryptographic hash set
57pub type FastHashSet<T> = std::collections::HashSet<T, FastHashState>;
58/// A non-cryptographic hash map
59pub type FastHashMap<K, V> = std::collections::HashMap<K, V, FastHashState>;
60
61use wgpu::util::DeviceExt;
62
63fn next_multiple_of(
64    value: wgpu::BufferAddress,
65    multiple: wgpu::BufferAddress,
66) -> wgpu::BufferAddress {
67    match value % multiple {
68        0 => value,
69        r => value + (multiple - r),
70    }
71}
72
73/// Provides semi-equivalent functionality to `pollster::block_on`, but without crashing on the web.
74pub fn block_on(future: impl std::future::Future<Output = ()> + 'static) {
75    #[cfg(target_arch = "wasm32")]
76    wasm_bindgen_futures::spawn_local(future);
77
78    #[cfg(not(target_arch = "wasm32"))]
79    pollster::block_on(future);
80}
81
82/// Gives whether the current execution environment (i.e. Browser) supports the provided HTML tag.
83/// Returns `true` on native.
84pub fn is_html_tag_supported(tag: &str) -> bool {
85    #[cfg(not(target_arch = "wasm32"))]
86    {
87        let _ = tag;
88        return true;
89    }
90
91    #[cfg(target_arch = "wasm32")]
92    {
93        let window = match web_sys::window() {
94            None => return false,
95            Some(window) => window,
96        };
97        let document = match window.document() {
98            None => return false,
99            Some(document) => document,
100        };
101        return document.create_element(tag).is_ok();
102    }
103}
104
105#[cfg(target_arch = "wasm32")]
106mod pretty_alert {
107    pub(super) struct PrettyAlertFailure;
108
109    impl From<wasm_bindgen::JsValue> for PrettyAlertFailure {
110        fn from(_: wasm_bindgen::JsValue) -> Self {
111            Self
112        }
113    }
114    impl From<()> for PrettyAlertFailure {
115        fn from(_: ()) -> Self {
116            Self
117        }
118    }
119
120    pub(super) fn try_pretty_alert(msg: &str) -> Result<(), PrettyAlertFailure> {
121        use wasm_bindgen::JsCast;
122
123        let document = web_sys::window()
124            .expect("app requires window")
125            .document()
126            .expect("app requires DOM");
127        let body = document.body().expect("DOM has body");
128
129        let dialog = document.create_element("dialog")?;
130        dialog.set_id("lf-alert");
131        dialog.set_attribute("style", r#"font-family: mono; max-width: 50%;"#)?;
132        {
133            let text_div = document.create_element("div")?;
134            text_div.set_text_content(Some(msg));
135            dialog.append_child(&text_div)?;
136
137            let button_div = document.create_element("div")?;
138            button_div.set_attribute("style", r#"display: grid; margin-top: 10px;"#)?;
139            {
140                let button = document.create_element("button")?;
141                button.set_attribute(
142                    "style",
143                    r#"border: 0px; font-family: sans; font-size: 20px; padding: 10px;"#,
144                )?;
145                button.set_attribute("autofocus", "true")?;
146                button.set_attribute("onclick", "document.getElementById('lf-alert').remove();")?;
147                button.set_text_content(Some("Okay"));
148                button_div.append_child(&button)?;
149            }
150            dialog.append_child(&button_div)?;
151        }
152        body.append_child(&dialog)?;
153
154        dialog
155            .dyn_ref::<web_sys::HtmlDialogElement>()
156            .ok_or(())?
157            .show_modal()?;
158
159        Ok(())
160    }
161}
162
163/// Produces a dialogue box with an `okay` response. Good for quick and dirty errors when something has gone very wrong.
164pub fn alert_dialogue(msg: &str) {
165    #[cfg(target_arch = "wasm32")]
166    {
167        let res = pretty_alert::try_pretty_alert(msg);
168        if res.is_err() {
169            web_sys::window()
170                .expect("app requires window")
171                .alert_with_message(msg)
172                .expect("all browsers should have alert");
173        }
174    }
175    #[cfg(not(target_arch = "wasm32"))]
176    {
177        use dialog::DialogBox;
178        dialog::Message::new(msg)
179            .title("Alert")
180            .show()
181            .expect("dialog box unavailable")
182    }
183}
184
185/// Some operations care about alignment in such a way that it is often easier to simply round all buffer sizes up to the nearest
186/// multiple of some power of two. This constant gives that power of two, and the corresponding [`LfDeviceExt::create_buffer_padded`],
187/// [`LfDeviceExt::new_buffer_init_padded`] and [`LfQueueExt::write_buffer_padded`] all extend their data lengths to the nearest
188/// multiple of this constant.
189pub const BUFFER_PADDING: wgpu::BufferAddress = 256;
190
191// Link in to existing objects
192// We're only adding methods to specific wgpu objects
193mod sealed {
194    pub trait SealedDevice {}
195    impl SealedDevice for wgpu::Device {}
196
197    pub trait SealedCommandEncoder {}
198    impl SealedCommandEncoder for wgpu::CommandEncoder {}
199
200    pub trait SealedLimits {}
201    impl SealedLimits for wgpu::Limits {}
202
203    pub trait SealedBuffer {}
204    impl SealedBuffer for wgpu::Buffer {}
205
206    pub trait SealedQueue {}
207    impl SealedQueue for wgpu::Queue {}
208
209    pub trait SealedBindGroupLayoutEntry {}
210    impl SealedBindGroupLayoutEntry for wgpu::BindGroupLayoutEntry {}
211
212    // We even want to extend our own objects
213    pub trait SealedGame {}
214    impl<T: crate::Game> SealedGame for T {}
215}
216
217pub struct PaddedBufferInitDescriptor<'a> {
218    /// Debug label of a buffer. This will show up in graphics debuggers for easy identification.
219    pub label: wgpu::Label<'a>,
220    /// Contents of a buffer on creation. Will be extended to the next pad interval.
221    pub contents: Vec<u8>,
222    /// Usages of a buffer. If the buffer is used in any way that isn't specified here, the operation
223    /// will panic.
224    pub usage: wgpu::BufferUsages,
225}
226
227/// Extensions to [`wgpu::Device`].
228pub trait LfDeviceExt: sealed::SealedDevice {
229    fn create_buffer_padded(&self, desc: wgpu::BufferDescriptor) -> wgpu::Buffer;
230    fn create_buffer_init_padded(&self, desc: PaddedBufferInitDescriptor) -> wgpu::Buffer;
231
232    fn create_fragment_only_render_bundle_encoder<'a>(
233        &'a self,
234        desc: &FragmentOnlyRenderBundleEncoderDescriptor,
235    ) -> FragmentOnlyRenderBundleEncoder<'a>;
236
237    fn create_fragment_only_render_pipeline(
238        &self,
239        desc: &FragmentOnlyRenderPipelineDescriptor,
240    ) -> FragmentOnlyRenderPipeline;
241
242    /// Creates a module, either with `create_shader_module` on debug or wasm, or `create_shader_module_unchecked` on release.
243    ///
244    /// Safety requirements carry from `create_shader_module_unchecked`.
245    unsafe fn create_shader_module_unchecked_on_release(
246        &self,
247        desc: wgpu::ShaderModuleDescriptor,
248    ) -> wgpu::ShaderModule;
249
250    /// Pops an error scope and asserts that it isn't an error.
251    fn assert_pop_error_scope(&self, msg: impl Into<String>);
252}
253
254impl LfDeviceExt for wgpu::Device {
255    fn create_buffer_padded(&self, mut desc: wgpu::BufferDescriptor) -> wgpu::Buffer {
256        desc.size = next_multiple_of(desc.size, BUFFER_PADDING);
257
258        self.create_buffer(&desc)
259    }
260
261    fn create_buffer_init_padded(&self, mut desc: PaddedBufferInitDescriptor) -> wgpu::Buffer {
262        let new_len = next_multiple_of(desc.contents.len() as wgpu::BufferAddress, BUFFER_PADDING);
263        desc.contents.resize(new_len as usize, 0u8);
264
265        self.create_buffer_init(&wgpu::util::BufferInitDescriptor {
266            label: desc.label,
267            contents: &desc.contents,
268            usage: desc.usage,
269        })
270    }
271
272    fn create_fragment_only_render_bundle_encoder<'a>(
273        &'a self,
274        desc: &FragmentOnlyRenderBundleEncoderDescriptor,
275    ) -> FragmentOnlyRenderBundleEncoder<'a> {
276        FragmentOnlyRenderBundleEncoder::new(self, desc)
277    }
278
279    fn create_fragment_only_render_pipeline(
280        &self,
281        desc: &FragmentOnlyRenderPipelineDescriptor,
282    ) -> FragmentOnlyRenderPipeline {
283        FragmentOnlyRenderPipeline::new(self, desc)
284    }
285
286    unsafe fn create_shader_module_unchecked_on_release(
287        &self,
288        desc: wgpu::ShaderModuleDescriptor,
289    ) -> wgpu::ShaderModule {
290        #[cfg(any(target_arch = "wasm32", debug_assertions))]
291        {
292            self.create_shader_module(desc)
293        }
294        #[cfg(not(any(target_arch = "wasm32", debug_assertions)))]
295        {
296            self.create_shader_module_unchecked(desc)
297        }
298    }
299
300    fn assert_pop_error_scope(&self, msg: impl Into<String>) {
301        let f = self.pop_error_scope();
302        async fn check_device_scope(
303            f: impl std::future::Future<Output = Option<wgpu::Error>>,
304            msg: String,
305        ) {
306            let res = f.await;
307            if let Some(e) = res {
308                panic!("error scope failure - {}: {:?}", msg, e)
309            }
310        }
311        block_on(check_device_scope(f, msg.into()));
312    }
313}
314
315/// Extensions to [`wgpu::CommandEncoder`].
316pub trait LfCommandEncoderExt: sealed::SealedCommandEncoder {
317    fn begin_fragment_only_render_pass<'pass>(
318        &'pass mut self,
319        desc: &FragmentOnlyRenderPassDescriptor<'pass, '_>,
320    ) -> FragmentOnlyRenderPass<'pass>;
321}
322
323impl LfCommandEncoderExt for wgpu::CommandEncoder {
324    fn begin_fragment_only_render_pass<'pass>(
325        &'pass mut self,
326        desc: &FragmentOnlyRenderPassDescriptor<'pass, '_>,
327    ) -> FragmentOnlyRenderPass<'pass> {
328        FragmentOnlyRenderPass::new(self, desc)
329    }
330}
331
332/// Extensions to [`wgpu::Limits`].
333pub trait LfLimitsExt: sealed::SealedLimits {
334    /// Gets the set of limits supported both by this and the other limits.
335    fn intersection<'a>(&self, other: &wgpu::Limits) -> wgpu::Limits;
336    /// Gets the set of limits supported by either this ot the other limits.
337    fn union<'a>(&self, other: &wgpu::Limits) -> wgpu::Limits;
338}
339
340impl LfLimitsExt for wgpu::Limits {
341    /// Gets the set of limits supported both by this and the other limits.
342    fn intersection<'a>(&self, other: &wgpu::Limits) -> wgpu::Limits {
343        crate::limits::limits_intersection(self, other)
344    }
345    /// Gets the set of limits supported by either this ot the other limits.
346    fn union<'a>(&self, other: &wgpu::Limits) -> wgpu::Limits {
347        crate::limits::limits_union(self, other)
348    }
349}
350
351/// Extensions to [`wgpu::Queue`].
352pub trait LfQueueExt: sealed::SealedQueue {
353    /// Writes the given data to the given buffer using [`wgpu::Queue::write_buffer`],
354    /// but pads the data to the nearest multiple of the alignment required for buffer writing.
355    ///
356    /// # Panics
357    ///
358    /// Panics if the padded data would overrun the given buffer.
359    fn write_buffer_padded(
360        &self,
361        buffer: &wgpu::Buffer,
362        offset: wgpu::BufferAddress,
363        data: Vec<u8>,
364    );
365}
366
367impl LfQueueExt for wgpu::Queue {
368    fn write_buffer_padded(
369        &self,
370        buffer: &wgpu::Buffer,
371        offset: wgpu::BufferAddress,
372        mut data: Vec<u8>,
373    ) {
374        const PAD_ALIGNMENT: usize = BUFFER_PADDING as usize;
375        let len = data.len();
376        let target_size = match len % PAD_ALIGNMENT {
377            0 => len,
378            r => len + (PAD_ALIGNMENT - r),
379        };
380        data.resize(target_size, 0u8);
381
382        self.write_buffer(buffer, offset, &data)
383    }
384}
385
386/// Extensions to [`wgpu::Buffer`].
387pub trait LfBufferExt: sealed::SealedBuffer {
388    /// Blocks and reads the entire buffer, giving the bytes contained. Allocates the temporary staging buffer for
389    /// this operation. Panics on error, or if the buffer was not created with `wgpu::BufferUsages::COPY_SRC`.
390    ///
391    /// Just use `wgpu::Queue::write_buffer` if you want to write.
392    fn debug_read_blocking(&self, device: &wgpu::Device, queue: &wgpu::Queue) -> Vec<u8>;
393}
394
395impl LfBufferExt for wgpu::Buffer {
396    fn debug_read_blocking(&self, device: &wgpu::Device, queue: &wgpu::Queue) -> Vec<u8> {
397        assert!(self.usage().contains(wgpu::BufferUsages::COPY_SRC));
398
399        let staging = device.create_buffer(&wgpu::BufferDescriptor {
400            label: Some("debug-read-staging"),
401            size: self.size(),
402            usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
403            mapped_at_creation: false,
404        });
405
406        let mut cmd = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
407            label: Some("debug-read-cmd-encoder"),
408        });
409        cmd.copy_buffer_to_buffer(self, 0, &staging, 0, self.size());
410
411        queue.submit(vec![cmd.finish()]);
412
413        let (sender, receiver) = std::sync::mpsc::channel();
414        staging.slice(..).map_async(wgpu::MapMode::Read, move |e| {
415            sender.send(e).expect("failed to send result of map");
416        });
417
418        let _ = device.poll(wgpu::PollType::Wait {
419            submission_index: None,
420            timeout: None,
421        });
422
423        receiver
424            .recv()
425            .expect("failed to get result of map")
426            .expect("failed to read buffer");
427
428        let slice = staging.slice(..).get_mapped_range();
429        slice.to_vec()
430    }
431}
432
433/// Extensions to [`wgpu::BindGroupLayoutEntry`].
434pub trait LfBindGroupLayoutEntryExt: sealed::SealedBindGroupLayoutEntry {
435    // Some common bindings as constructors
436    fn read_only_compute_storage(binding: u32) -> Self;
437    fn mutable_compute_storage(binding: u32) -> Self;
438}
439
440impl LfBindGroupLayoutEntryExt for wgpu::BindGroupLayoutEntry {
441    fn read_only_compute_storage(binding: u32) -> Self {
442        wgpu::BindGroupLayoutEntry {
443            binding,
444            visibility: wgpu::ShaderStages::COMPUTE,
445            ty: wgpu::BindingType::Buffer {
446                ty: wgpu::BufferBindingType::Storage { read_only: true },
447                has_dynamic_offset: false,
448                min_binding_size: None,
449            },
450            count: None,
451        }
452    }
453
454    fn mutable_compute_storage(binding: u32) -> Self {
455        wgpu::BindGroupLayoutEntry {
456            binding,
457            visibility: wgpu::ShaderStages::COMPUTE,
458            ty: wgpu::BindingType::Buffer {
459                ty: wgpu::BufferBindingType::Storage { read_only: false },
460                has_dynamic_offset: false,
461                min_binding_size: None,
462            },
463            count: None,
464        }
465    }
466}
467
468/// Extensions to an implemented game object.
469pub trait LfGameExt: sealed::SealedGame {
470    type InitData;
471
472    /// Runs the game.
473    fn run(init: Self::InitData);
474}
475
476impl<T: Game + 'static> LfGameExt for T {
477    type InitData = T::InitData;
478
479    /// Runs the game. Must be executed on the main thread on Web, and will not block the main thread (although will loop until the game is completed).
480    fn run(init: T::InitData) {
481        game::GameState::<T>::run(init);
482    }
483}