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 SealedInstance {}
198    impl SealedInstance for wgpu::Instance {}
199
200    pub trait SealedCommandEncoder {}
201    impl SealedCommandEncoder for wgpu::CommandEncoder {}
202
203    pub trait SealedLimits {}
204    impl SealedLimits for wgpu::Limits {}
205
206    pub trait SealedBuffer {}
207    impl SealedBuffer for wgpu::Buffer {}
208
209    pub trait SealedQueue {}
210    impl SealedQueue for wgpu::Queue {}
211
212    pub trait SealedBindGroupLayoutEntry {}
213    impl SealedBindGroupLayoutEntry for wgpu::BindGroupLayoutEntry {}
214
215    // We even want to extend our own objects
216    pub trait SealedGame {}
217    impl<T: crate::Game> SealedGame for T {}
218}
219
220pub struct PaddedBufferInitDescriptor<'a> {
221    /// Debug label of a buffer. This will show up in graphics debuggers for easy identification.
222    pub label: wgpu::Label<'a>,
223    /// Contents of a buffer on creation. Will be extended to the next pad interval.
224    pub contents: Vec<u8>,
225    /// Usages of a buffer. If the buffer is used in any way that isn't specified here, the operation
226    /// will panic.
227    pub usage: wgpu::BufferUsages,
228}
229
230/// Extensions to [`wgpu::Device`].
231pub trait LfDeviceExt: sealed::SealedDevice {
232    fn create_buffer_padded(&self, desc: wgpu::BufferDescriptor) -> wgpu::Buffer;
233    fn create_buffer_init_padded(&self, desc: PaddedBufferInitDescriptor) -> wgpu::Buffer;
234
235    fn create_fragment_only_render_bundle_encoder(
236        &self,
237        desc: &FragmentOnlyRenderBundleEncoderDescriptor,
238    ) -> FragmentOnlyRenderBundleEncoder;
239
240    fn create_fragment_only_render_pipeline(
241        &self,
242        desc: &FragmentOnlyRenderPipelineDescriptor,
243    ) -> FragmentOnlyRenderPipeline;
244
245    /// Creates a module, either with `create_shader_module` on debug or wasm, or `create_shader_module_unchecked` on release.
246    ///
247    /// Safety requirements carry from `create_shader_module_unchecked`.
248    unsafe fn create_shader_module_unchecked_on_release(
249        &self,
250        desc: wgpu::ShaderModuleDescriptor,
251    ) -> wgpu::ShaderModule;
252
253    /// Pops an error scope and asserts that it isn't an error.
254    fn assert_pop_error_scope(&self, msg: impl Into<String>);
255}
256
257impl LfDeviceExt for wgpu::Device {
258    fn create_buffer_padded(&self, mut desc: wgpu::BufferDescriptor) -> wgpu::Buffer {
259        desc.size = next_multiple_of(desc.size, BUFFER_PADDING);
260
261        self.create_buffer(&desc)
262    }
263
264    fn create_buffer_init_padded(&self, mut desc: PaddedBufferInitDescriptor) -> wgpu::Buffer {
265        let new_len = next_multiple_of(desc.contents.len() as wgpu::BufferAddress, BUFFER_PADDING);
266        desc.contents.resize(new_len as usize, 0u8);
267
268        self.create_buffer_init(&wgpu::util::BufferInitDescriptor {
269            label: desc.label,
270            contents: &desc.contents,
271            usage: desc.usage,
272        })
273    }
274
275    fn create_fragment_only_render_bundle_encoder(
276        &self,
277        desc: &FragmentOnlyRenderBundleEncoderDescriptor,
278    ) -> FragmentOnlyRenderBundleEncoder {
279        FragmentOnlyRenderBundleEncoder::new(self, desc)
280    }
281
282    fn create_fragment_only_render_pipeline(
283        &self,
284        desc: &FragmentOnlyRenderPipelineDescriptor,
285    ) -> FragmentOnlyRenderPipeline {
286        FragmentOnlyRenderPipeline::new(self, desc)
287    }
288
289    unsafe fn create_shader_module_unchecked_on_release(
290        &self,
291        desc: wgpu::ShaderModuleDescriptor,
292    ) -> wgpu::ShaderModule {
293        #[cfg(any(target_arch = "wasm32", debug_assertions))]
294        {
295            self.create_shader_module(desc)
296        }
297        #[cfg(not(any(target_arch = "wasm32", debug_assertions)))]
298        {
299            self.create_shader_module_unchecked(desc)
300        }
301    }
302
303    fn assert_pop_error_scope(&self, msg: impl Into<String>) {
304        let f = self.pop_error_scope();
305        async fn check_device_scope(
306            f: impl std::future::Future<Output = Option<wgpu::Error>>,
307            msg: String,
308        ) {
309            let res = f.await;
310            if let Some(e) = res {
311                panic!("error scope failure - {}: {:?}", msg, e)
312            }
313        }
314        block_on(check_device_scope(f, msg.into()));
315    }
316}
317
318/// Extensions to [`wgpu::CommandEncoder`].
319pub trait LfCommandEncoderExt: sealed::SealedCommandEncoder {
320    fn begin_fragment_only_render_pass<'pass>(
321        &'pass mut self,
322        desc: &FragmentOnlyRenderPassDescriptor<'pass, '_>,
323    ) -> FragmentOnlyRenderPass<'pass>;
324}
325
326impl LfCommandEncoderExt for wgpu::CommandEncoder {
327    fn begin_fragment_only_render_pass<'pass>(
328        &'pass mut self,
329        desc: &FragmentOnlyRenderPassDescriptor<'pass, '_>,
330    ) -> FragmentOnlyRenderPass<'pass> {
331        FragmentOnlyRenderPass::new(self, desc)
332    }
333}
334
335/// Extensions to [`wgpu::Limits`].
336pub trait LfLimitsExt: sealed::SealedLimits {
337    /// Gets the set of limits supported both by this and the other limits.
338    fn intersection<'a>(&self, other: &wgpu::Limits) -> wgpu::Limits;
339    /// Gets the set of limits supported by either this ot the other limits.
340    fn union<'a>(&self, other: &wgpu::Limits) -> wgpu::Limits;
341}
342
343impl LfLimitsExt for wgpu::Limits {
344    /// Gets the set of limits supported both by this and the other limits.
345    fn intersection<'a>(&self, other: &wgpu::Limits) -> wgpu::Limits {
346        crate::limits::limits_intersection(self, other)
347    }
348    /// Gets the set of limits supported by either this ot the other limits.
349    fn union<'a>(&self, other: &wgpu::Limits) -> wgpu::Limits {
350        crate::limits::limits_union(self, other)
351    }
352}
353
354/// Extensions to [`wgpu::Queue`].
355pub trait LfQueueExt: sealed::SealedQueue {
356    /// Writes the given data to the given buffer using [`wgpu::Queue::write_buffer`],
357    /// but pads the data to the nearest multiple of the alignment required for buffer writing.
358    ///
359    /// # Panics
360    ///
361    /// Panics if the padded data would overrun the given buffer.
362    fn write_buffer_padded(
363        &self,
364        buffer: &wgpu::Buffer,
365        offset: wgpu::BufferAddress,
366        data: Vec<u8>,
367    );
368}
369
370impl LfQueueExt for wgpu::Queue {
371    fn write_buffer_padded(
372        &self,
373        buffer: &wgpu::Buffer,
374        offset: wgpu::BufferAddress,
375        mut data: Vec<u8>,
376    ) {
377        const PAD_ALIGNMENT: usize = BUFFER_PADDING as usize;
378        let len = data.len();
379        let target_size = match len % PAD_ALIGNMENT {
380            0 => len,
381            r => len + (PAD_ALIGNMENT - r),
382        };
383        data.resize(target_size, 0u8);
384
385        self.write_buffer(buffer, offset, &data)
386    }
387}
388
389/// Extensions to [`wgpu::Buffer`].
390pub trait LfBufferExt: sealed::SealedBuffer {
391    /// Blocks and reads the entire buffer, giving the bytes contained. Allocates the temporary staging buffer for
392    /// this operation. Panics on error, or if the buffer was not created with `wgpu::BufferUsages::COPY_SRC`.
393    ///
394    /// Just use `wgpu::Queue::write_buffer` if you want to write.
395    fn debug_read_blocking(&self, device: &wgpu::Device, queue: &wgpu::Queue) -> Vec<u8>;
396}
397
398impl LfBufferExt for wgpu::Buffer {
399    fn debug_read_blocking(&self, device: &wgpu::Device, queue: &wgpu::Queue) -> Vec<u8> {
400        assert!(self.usage().contains(wgpu::BufferUsages::COPY_SRC));
401
402        let staging = device.create_buffer(&wgpu::BufferDescriptor {
403            label: Some("debug-read-staging"),
404            size: self.size(),
405            usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
406            mapped_at_creation: false,
407        });
408
409        let mut cmd = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
410            label: Some("debug-read-cmd-encoder"),
411        });
412        cmd.copy_buffer_to_buffer(self, 0, &staging, 0, self.size());
413
414        queue.submit(vec![cmd.finish()]);
415
416        let (sender, receiver) = std::sync::mpsc::channel();
417        staging.slice(..).map_async(wgpu::MapMode::Read, move |e| {
418            sender.send(e).expect("failed to send result of map");
419        });
420
421        device.poll(wgpu::Maintain::Wait);
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}