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
11pub use fragment_only::FragmentOnlyRenderBundleEncoder;
12pub use fragment_only::FragmentOnlyRenderBundleEncoderDescriptor;
13pub use fragment_only::FragmentOnlyRenderPass;
14pub use fragment_only::FragmentOnlyRenderPassDescriptor;
15pub use fragment_only::FragmentOnlyRenderPipeline;
16pub use fragment_only::FragmentOnlyRenderPipelineDescriptor;
17pub use game::window::GameWindow;
18pub use game::window::WindowSizeDependent;
19pub use game::ExitFlag;
20pub use game::Game;
21pub use game::GameCommand;
22pub use game::GameData;
23pub use game::InputMode;
24pub mod input {
25    pub use crate::game::input::*;
26}
27pub mod local_storage;
28
29// Resolve https://github.com/rust-lang/rustc-hash/issues/14 by wrapping `rustc_hash::FxHasher`.
30pub struct FastHashState {
31    seed: u32,
32}
33impl std::hash::BuildHasher for FastHashState {
34    type Hasher = rustc_hash::FxHasher;
35
36    fn build_hasher(&self) -> Self::Hasher {
37        use std::hash::Hasher;
38
39        let mut hasher = rustc_hash::FxHasher::default();
40        hasher.write_u32(self.seed);
41        return hasher;
42    }
43}
44impl Default for FastHashState {
45    fn default() -> Self {
46        use rand::Rng;
47        Self {
48            seed: rand::rng().random(),
49        }
50    }
51}
52
53/// A non-cryptographic hash set
54pub type FastHashSet<T> = std::collections::HashSet<T, FastHashState>;
55/// A non-cryptographic hash map
56pub type FastHashMap<K, V> = std::collections::HashMap<K, V, FastHashState>;
57
58use wgpu::util::DeviceExt;
59
60fn next_multiple_of(
61    value: wgpu::BufferAddress,
62    multiple: wgpu::BufferAddress,
63) -> wgpu::BufferAddress {
64    match value % multiple {
65        0 => value,
66        r => value + (multiple - r),
67    }
68}
69
70/// Provides semi-equivalent functionality to `pollster::block_on`, but without crashing on the web.
71pub fn block_on(future: impl std::future::Future<Output = ()> + 'static) {
72    #[cfg(target_arch = "wasm32")]
73    wasm_bindgen_futures::spawn_local(future);
74
75    #[cfg(not(target_arch = "wasm32"))]
76    pollster::block_on(future);
77}
78
79/// Gives whether the current execution environment (i.e. Browser) supports the provided HTML tag.
80/// Returns `true` on native.
81pub fn is_html_tag_supported(tag: &str) -> bool {
82    #[cfg(not(target_arch = "wasm32"))]
83    {
84        let _ = tag;
85        return true;
86    }
87
88    #[cfg(target_arch = "wasm32")]
89    {
90        let window = match web_sys::window() {
91            None => return false,
92            Some(window) => window,
93        };
94        let document = match window.document() {
95            None => return false,
96            Some(document) => document,
97        };
98        return document.create_element(tag).is_ok();
99    }
100}
101
102#[cfg(target_arch = "wasm32")]
103mod pretty_alert {
104    pub(super) struct PrettyAlertFailure;
105
106    impl From<wasm_bindgen::JsValue> for PrettyAlertFailure {
107        fn from(_: wasm_bindgen::JsValue) -> Self {
108            Self
109        }
110    }
111    impl From<()> for PrettyAlertFailure {
112        fn from(_: ()) -> Self {
113            Self
114        }
115    }
116
117    pub(super) fn try_pretty_alert(msg: &str) -> Result<(), PrettyAlertFailure> {
118        use wasm_bindgen::JsCast;
119
120        let document = web_sys::window()
121            .expect("app requires window")
122            .document()
123            .expect("app requires DOM");
124        let body = document.body().expect("DOM has body");
125
126        let dialog = document.create_element("dialog")?;
127        dialog.set_id("lf-alert");
128        dialog.set_attribute("style", r#"font-family: mono; max-width: 50%;"#)?;
129        {
130            let text_div = document.create_element("div")?;
131            text_div.set_text_content(Some(msg));
132            dialog.append_child(&text_div)?;
133
134            let button_div = document.create_element("div")?;
135            button_div.set_attribute("style", r#"display: grid; margin-top: 10px;"#)?;
136            {
137                let button = document.create_element("button")?;
138                button.set_attribute(
139                    "style",
140                    r#"border: 0px; font-family: sans; font-size: 20px; padding: 10px;"#,
141                )?;
142                button.set_attribute("autofocus", "true")?;
143                button.set_attribute("onclick", "document.getElementById('lf-alert').remove();")?;
144                button.set_text_content(Some("Okay"));
145                button_div.append_child(&button)?;
146            }
147            dialog.append_child(&button_div)?;
148        }
149        body.append_child(&dialog)?;
150
151        dialog
152            .dyn_ref::<web_sys::HtmlDialogElement>()
153            .ok_or(())?
154            .show_modal()?;
155
156        Ok(())
157    }
158}
159
160/// Produces a dialogue box with an `okay` response. Good for quick and dirty errors when something has gone very wrong.
161pub fn alert_dialogue(msg: &str) {
162    #[cfg(target_arch = "wasm32")]
163    {
164        let res = pretty_alert::try_pretty_alert(msg);
165        if res.is_err() {
166            web_sys::window()
167                .expect("app requires window")
168                .alert_with_message(msg)
169                .expect("all browsers should have alert");
170        }
171    }
172    #[cfg(not(target_arch = "wasm32"))]
173    {
174        use dialog::DialogBox;
175        dialog::Message::new(msg)
176            .title("Alert")
177            .show()
178            .expect("dialog box unavailable")
179    }
180}
181
182/// Some operations care about alignment in such a way that it is often easier to simply round all buffer sizes up to the nearest
183/// multiple of some power of two. This constant gives that power of two, and the corresponding [`LfDeviceExt::create_buffer_padded`],
184/// [`LfDeviceExt::new_buffer_init_padded`] and [`LfQueueExt::write_buffer_padded`] all extend their data lengths to the nearest
185/// multiple of this constant.
186pub const BUFFER_PADDING: wgpu::BufferAddress = 256;
187
188// Link in to existing objects
189// We're only adding methods to specific wgpu objects
190mod sealed {
191    pub trait SealedDevice {}
192    impl SealedDevice for wgpu::Device {}
193
194    pub trait SealedInstance {}
195    impl SealedInstance for wgpu::Instance {}
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(
233        &self,
234        desc: &FragmentOnlyRenderBundleEncoderDescriptor,
235    ) -> FragmentOnlyRenderBundleEncoder;
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(
273        &self,
274        desc: &FragmentOnlyRenderBundleEncoderDescriptor,
275    ) -> FragmentOnlyRenderBundleEncoder {
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        device.poll(wgpu::Maintain::Wait);
419
420        receiver
421            .recv()
422            .expect("failed to get result of map")
423            .expect("failed to read buffer");
424
425        let slice = staging.slice(..).get_mapped_range();
426        slice.to_vec()
427    }
428}
429
430/// Extensions to [`wgpu::BindGroupLayoutEntry`].
431pub trait LfBindGroupLayoutEntryExt: sealed::SealedBindGroupLayoutEntry {
432    // Some common bindings as constructors
433    fn read_only_compute_storage(binding: u32) -> Self;
434    fn mutable_compute_storage(binding: u32) -> Self;
435}
436
437impl LfBindGroupLayoutEntryExt for wgpu::BindGroupLayoutEntry {
438    fn read_only_compute_storage(binding: u32) -> Self {
439        wgpu::BindGroupLayoutEntry {
440            binding,
441            visibility: wgpu::ShaderStages::COMPUTE,
442            ty: wgpu::BindingType::Buffer {
443                ty: wgpu::BufferBindingType::Storage { read_only: true },
444                has_dynamic_offset: false,
445                min_binding_size: None,
446            },
447            count: None,
448        }
449    }
450
451    fn mutable_compute_storage(binding: u32) -> Self {
452        wgpu::BindGroupLayoutEntry {
453            binding,
454            visibility: wgpu::ShaderStages::COMPUTE,
455            ty: wgpu::BindingType::Buffer {
456                ty: wgpu::BufferBindingType::Storage { read_only: false },
457                has_dynamic_offset: false,
458                min_binding_size: None,
459            },
460            count: None,
461        }
462    }
463}
464
465/// Extensions to an implemented game object.
466pub trait LfGameExt: sealed::SealedGame {
467    type InitData;
468
469    /// Runs the game.
470    fn run(init: Self::InitData);
471}
472
473impl<T: Game + 'static> LfGameExt for T {
474    type InitData = T::InitData;
475
476    /// 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).
477    fn run(init: T::InitData) {
478        game::GameState::<T>::run(init);
479    }
480}