~starkingdoms/starkingdoms

28f124e8058c5af6ad245a0edfb58ab2d38c901d — core 11 months ago efdb9b1
format
M starkingdoms-client/src/ecs.rs => starkingdoms-client/src/ecs.rs +4 -4
@@ 5,7 5,7 @@ use bevy_ecs::system::Resource;
#[derive(Component, Debug, Clone, Copy)]
pub struct Position {
    pub x: f32,
    pub y: f32
    pub y: f32,
}
#[derive(Component, Debug, Clone, Copy)]
pub struct Scale {


@@ 22,12 22,12 @@ pub struct SpriteTexture {
pub struct SpriteBundle {
    pub position: Position,
    pub scale: Scale,
    pub texture: SpriteTexture
    pub texture: SpriteTexture,
}

#[derive(Resource, Debug)]
pub struct Camera {
    pub x: f32,
    pub y: f32,
    pub zoom: f32
}
\ No newline at end of file
    pub zoom: f32,
}

M starkingdoms-client/src/input.rs => starkingdoms-client/src/input.rs +3 -9
@@ 2,12 2,6 @@ use bevy_ecs::event::Event;

#[derive(Event, Debug, Copy, Clone)]
pub enum MouseWheelEvent {
    Line {
        x: f64,
        y: f64,
    },
    Pixel {
        x: f64,
        y: f64,
    }
}
\ No newline at end of file
    Line { x: f64, y: f64 },
    Pixel { x: f64, y: f64 },
}

M starkingdoms-client/src/lib.rs => starkingdoms-client/src/lib.rs +27 -17
@@ 1,16 1,16 @@
use std::ops::Add;
use std::time::Duration;
use crate::ecs::{Camera, Position, Scale, SpriteBundle, SpriteTexture};
use crate::input::MouseWheelEvent;
use crate::rendering::ui::UiRenderable;
use crate::rendering::App;
use bevy_ecs::event::Events;
use bevy_ecs::schedule::Schedule;
use bevy_ecs::world::World;
use egui::Context;
use std::ops::Add;
use std::time::Duration;
use tracing::info;
use web_time::Instant;
use winit::event_loop::{ControlFlow, EventLoop};
use crate::ecs::{Camera, Position, Scale, SpriteBundle, SpriteTexture};
use crate::input::MouseWheelEvent;
use crate::rendering::App;
use crate::rendering::ui::UiRenderable;

#[cfg(target_arch = "wasm32")]
#[path = "wasm/mod.rs"]


@@ 23,10 23,17 @@ pub mod ecs;
pub mod input;
pub mod rendering;


// Hi, you've found the real main function! This is called AFTER platform-specific initialization code.
pub fn start() {
    info!("Hello, world! StarKingdoms.TK v{} says hello, running on {}", env!("CARGO_PKG_VERSION"), if cfg!(target_arch = "wasm32") { "wasm" } else { "native" });
    info!(
        "Hello, world! StarKingdoms.TK v{} says hello, running on {}",
        env!("CARGO_PKG_VERSION"),
        if cfg!(target_arch = "wasm32") {
            "wasm"
        } else {
            "native"
        }
    );

    info!("Creating the ECS world...");
    let mut world = World::new();


@@ 49,17 56,20 @@ pub fn start() {

    world.spawn(SpriteBundle {
        position: Position { x: 0.0, y: 0.0 },
        scale: Scale { width: 50.0, height: 50.0 },
        texture: SpriteTexture { texture: "happy-tree".to_string() },
        scale: Scale {
            width: 50.0,
            height: 50.0,
        },
        texture: SpriteTexture {
            texture: "happy-tree".to_string(),
        },
    });
    

    let event_loop = EventLoop::new().unwrap();
    event_loop.set_control_flow(ControlFlow::Poll);
    event_loop.run_app(&mut App::new(
        world,
        update_schedule,
        Gui {}
    )).unwrap();
    event_loop
        .run_app(&mut App::new(world, update_schedule, Gui {}))
        .unwrap();
}

pub struct Gui {}


@@ 72,4 82,4 @@ impl UiRenderable for Gui {
                ui.label("A game about floating through space");
            });
    }
}
\ No newline at end of file
}

M starkingdoms-client/src/native/mod.rs => starkingdoms-client/src/native/mod.rs +2 -2
@@ 7,7 7,7 @@ use wgpu::{Backends, Limits};
/// DO NOT RENAME
pub fn entrypoint() {
    tracing_subscriber::fmt::init();
    

    // All done with platform-specific initialization, call back into the common code path
    crate::start();
}
\ No newline at end of file
}

M starkingdoms-client/src/rendering/mipmap.rs => starkingdoms-client/src/rendering/mipmap.rs +40 -32
@@ 1,7 1,13 @@
use crate::rendering::texture::Texture;
use std::collections::HashMap;
use tracing::debug;
use wgpu::{BindGroupDescriptor, BindGroupEntry, BindingResource, Color, ColorTargetState, ColorWrites, CommandEncoderDescriptor, Device, FilterMode, FragmentState, include_wgsl, LoadOp, Operations, Queue, RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor, Sampler, SamplerDescriptor, ShaderModule, StoreOp, TextureFormat, TextureViewDescriptor, VertexState};
use crate::rendering::texture::Texture;
use wgpu::{
    include_wgsl, BindGroupDescriptor, BindGroupEntry, BindingResource, Color, ColorTargetState,
    ColorWrites, CommandEncoderDescriptor, Device, FilterMode, FragmentState, LoadOp, Operations,
    Queue, RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline,
    RenderPipelineDescriptor, Sampler, SamplerDescriptor, ShaderModule, StoreOp, TextureFormat,
    TextureViewDescriptor, VertexState,
};

pub struct MipGenerator {
    shader: ShaderModule,


@@ 11,8 17,7 @@ pub struct MipGenerator {
impl MipGenerator {
    pub fn new(device: &Device) -> Self {
        debug!("initializing MipGenerator, compiling shader module");
        let shader =
            device.create_shader_module(include_wgsl!("../shaders/text_quad_mips.wgsl"));
        let shader = device.create_shader_module(include_wgsl!("../shaders/text_quad_mips.wgsl"));
        Self {
            shader,
            sampler: device.create_sampler(&SamplerDescriptor {


@@ 25,33 30,36 @@ impl MipGenerator {
    }

    pub fn generate_mips(&mut self, texture: &Texture, device: &Device, queue: &Queue) {
        let pipeline = self.pipelines.entry(texture.texture.format()).or_insert_with(|| {
            device.create_render_pipeline(&RenderPipelineDescriptor {
                label: Some("MipGenerator format pipeline"),
                layout: None,
                vertex: VertexState {
                    module: &self.shader,
                    entry_point: Some("vs"),
                    compilation_options: Default::default(),
                    buffers: &[],
                },
                primitive: Default::default(),
                depth_stencil: None,
                multisample: Default::default(),
                fragment: Some(FragmentState {
                    module: &self.shader,
                    entry_point: Some("fs"),
                    compilation_options: Default::default(),
                    targets: &[Some(ColorTargetState {
                        format: texture.texture.format(),
                        blend: None,
                        write_mask: ColorWrites::default(),
                    })],
                }),
                multiview: None,
                cache: None,
            })
        });
        let pipeline = self
            .pipelines
            .entry(texture.texture.format())
            .or_insert_with(|| {
                device.create_render_pipeline(&RenderPipelineDescriptor {
                    label: Some("MipGenerator format pipeline"),
                    layout: None,
                    vertex: VertexState {
                        module: &self.shader,
                        entry_point: Some("vs"),
                        compilation_options: Default::default(),
                        buffers: &[],
                    },
                    primitive: Default::default(),
                    depth_stencil: None,
                    multisample: Default::default(),
                    fragment: Some(FragmentState {
                        module: &self.shader,
                        entry_point: Some("fs"),
                        compilation_options: Default::default(),
                        targets: &[Some(ColorTargetState {
                            format: texture.texture.format(),
                            blend: None,
                            write_mask: ColorWrites::default(),
                        })],
                    }),
                    multiview: None,
                    cache: None,
                })
            });

        let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor {
            label: Some("MipGenerator command encoder"),


@@ 123,4 131,4 @@ impl MipGenerator {
        let command_buffer = encoder.finish();
        queue.submit(std::iter::once(command_buffer));
    }
}
\ No newline at end of file
}

M starkingdoms-client/src/rendering/mod.rs => starkingdoms-client/src/rendering/mod.rs +55 -39
@@ 1,14 1,18 @@
mod mipmap;
mod renderer;
pub mod ui;
mod texture;
mod mipmap;
pub mod ui;

use crate::input::MouseWheelEvent;
use crate::rendering::renderer::RenderInitRes::{Initialized, NotReadyYet};
use crate::rendering::renderer::{RenderInitRes, Renderer};
use crate::rendering::ui::UiRenderable;
use bevy_ecs::schedule::Schedule;
use bevy_ecs::world::World;
use std::ops::Add;
use std::process::exit;
use std::sync::Arc;
use std::time::Duration;
use bevy_ecs::schedule::Schedule;
use bevy_ecs::world::World;
use tracing::{debug, error, info};
use web_time::Instant;
use winit::application::ApplicationHandler;


@@ 16,10 20,6 @@ use winit::dpi::LogicalSize;
use winit::event::{MouseScrollDelta, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::{Window, WindowId};
use crate::input::MouseWheelEvent;
use crate::rendering::renderer::{Renderer, RenderInitRes};
use crate::rendering::renderer::RenderInitRes::{Initialized, NotReadyYet};
use crate::rendering::ui::UiRenderable;

pub struct App<T: UiRenderable> {
    window: Option<Arc<Window>>,


@@ 30,7 30,7 @@ pub struct App<T: UiRenderable> {

    world: Option<World>,
    update_schedule: Option<Schedule>,
    ui_renderable: Option<T>
    ui_renderable: Option<T>,
}

impl<T: UiRenderable> App<T> {


@@ 42,15 42,14 @@ impl<T: UiRenderable> App<T> {
            renderer_rx: None,
            world: Some(world),
            update_schedule: Some(update_schedule),
            ui_renderable: Some(ui_renderable)
            ui_renderable: Some(ui_renderable),
        }
    }
}
impl<T: UiRenderable + 'static> ApplicationHandler for App<T> {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        if self.window.is_none() {
            let attributes = Window::default_attributes()
                .with_title("StarKingdoms.TK");
            let attributes = Window::default_attributes().with_title("StarKingdoms.TK");

            let window = Arc::new(event_loop.create_window(attributes).unwrap());
            self.window = Some(window.clone());


@@ 59,14 58,15 @@ impl<T: UiRenderable + 'static> ApplicationHandler for App<T> {
            let update_schedule = self.update_schedule.take().unwrap();
            let ui_renderable = self.ui_renderable.take().unwrap();

            #[cfg(not(target_arch = "wasm32"))] {
            #[cfg(not(target_arch = "wasm32"))]
            {
                let renderer = pollster::block_on(async move {
                    Renderer::new(window.clone(), world, update_schedule, ui_renderable).await
                });
                match renderer {
                    Initialized(r) => {
                        self.renderer = Some(r);
                    },
                    }
                    NotReadyYet(w, u, t) => {
                        self.world = Some(w);
                        self.update_schedule = Some(u);


@@ 74,7 74,8 @@ impl<T: UiRenderable + 'static> ApplicationHandler for App<T> {
                    }
                }
            }
            #[cfg(target_arch = "wasm32")] {
            #[cfg(target_arch = "wasm32")]
            {
                use winit::platform::web::WindowExtWebSys;
                // Add it to the DOM
                web_sys::window()


@@ 89,15 90,20 @@ impl<T: UiRenderable + 'static> ApplicationHandler for App<T> {
                let (tx, rx) = futures::channel::oneshot::channel();
                self.renderer_rx = Some(rx);
                wasm_bindgen_futures::spawn_local(async move {
                    let renderer = Renderer::new(window.clone(), world, update_schedule, ui_renderable).await;
                    let renderer =
                        Renderer::new(window.clone(), world, update_schedule, ui_renderable).await;
                    tx.send(renderer.into()).unwrap();
                });
            }
        }

    }

    fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
    fn window_event(
        &mut self,
        event_loop: &ActiveEventLoop,
        window_id: WindowId,
        event: WindowEvent,
    ) {
        if event == WindowEvent::CloseRequested {
            info!("Close requested by underlying event system");
            event_loop.exit();


@@ 116,41 122,45 @@ impl<T: UiRenderable + 'static> ApplicationHandler for App<T> {
                    if new.width > 0 && new.height > 0 {
                        renderer.size = new;
                        renderer.scale_factor = renderer.window.scale_factor();
                        renderer.logical_size = LogicalSize::from_physical(renderer.size, renderer.scale_factor);
                        renderer.logical_size =
                            LogicalSize::from_physical(renderer.size, renderer.scale_factor);

                        renderer.surface_configuration.width = new.width;
                        renderer.surface_configuration.height = new.height;
                        renderer.surface.configure(&renderer.device, &renderer.surface_configuration);
                        renderer
                            .surface
                            .configure(&renderer.device, &renderer.surface_configuration);
                    }
                }
            },
            }
            WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
                if let Some(renderer) = &mut self.renderer {
                    renderer.scale_factor = scale_factor;
                    renderer.logical_size = LogicalSize::from_physical(renderer.size, renderer.scale_factor);
                    renderer.logical_size =
                        LogicalSize::from_physical(renderer.size, renderer.scale_factor);
                }
            },
            }
            WindowEvent::MouseWheel { delta, .. } => {
                if let Some(renderer) = &mut self.renderer {
                    renderer.world.send_event(match delta {
                        MouseScrollDelta::PixelDelta(pos) => MouseWheelEvent::Pixel {
                            x: pos.x,
                            y: pos.y
                        },
                        MouseScrollDelta::PixelDelta(pos) => {
                            MouseWheelEvent::Pixel { x: pos.x, y: pos.y }
                        }
                        MouseScrollDelta::LineDelta(x, y) => MouseWheelEvent::Line {
                            x: x as f64,
                            y: y as f64
                        }
                            y: y as f64,
                        },
                    });
                }
            },
            }
            _ => {}
        }
    }

    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
        if let Some(window) = self.window.clone() {
            #[cfg(target_arch = "wasm32")] {
            #[cfg(target_arch = "wasm32")]
            {
                let mut renderer_rxd = false;
                if let Some(rx) = self.renderer_rx.as_mut() {
                    if let Ok(Some(renderer)) = rx.try_recv() {


@@ 158,7 168,7 @@ impl<T: UiRenderable + 'static> ApplicationHandler for App<T> {
                            Initialized(r) => {
                                self.renderer = Some(r);
                                renderer_rxd = true;
                            },
                            }
                            NotReadyYet(w, u, t) => {
                                let (tx, rx) = futures::channel::oneshot::channel();
                                self.renderer_rx = Some(rx);


@@ 175,7 185,8 @@ impl<T: UiRenderable + 'static> ApplicationHandler for App<T> {
                }
            }

            #[cfg(not(target_arch = "wasm32"))] {
            #[cfg(not(target_arch = "wasm32"))]
            {
                if self.renderer.is_none() {
                    if let Some(window) = self.window.clone() {
                        let world = self.world.take().unwrap();


@@ 183,13 194,14 @@ impl<T: UiRenderable + 'static> ApplicationHandler for App<T> {
                        let ui_renderable = self.ui_renderable.take().unwrap();

                        let renderer = pollster::block_on(async move {
                            Renderer::new(window.clone(), world, update_schedule, ui_renderable).await
                            Renderer::new(window.clone(), world, update_schedule, ui_renderable)
                                .await
                        });

                        match renderer {
                            Initialized(r) => {
                                self.renderer = Some(r);
                            },
                            }
                            NotReadyYet(w, u, t) => {
                                self.world = Some(w);
                                self.update_schedule = Some(u);


@@ 202,11 214,15 @@ impl<T: UiRenderable + 'static> ApplicationHandler for App<T> {
                }
            }

            let Some(renderer) = &mut self.renderer else { return; };
            
            let Some(renderer) = &mut self.renderer else {
                return;
            };

            renderer.render();

            event_loop.set_control_flow(ControlFlow::WaitUntil(Instant::now().add(Duration::from_secs_f64(1.0 / 60.0))));
            event_loop.set_control_flow(ControlFlow::WaitUntil(
                Instant::now().add(Duration::from_secs_f64(1.0 / 60.0)),
            ));
        }
    }
}
\ No newline at end of file
}

M starkingdoms-client/src/rendering/renderer.rs => starkingdoms-client/src/rendering/renderer.rs +82 -53
@@ 1,20 1,27 @@
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use std::sync::Arc;
use crate::ecs::{Camera, Position, Scale, SpriteTexture};
use crate::rendering::mipmap::MipGenerator;
use crate::rendering::renderer::RenderInitRes::{Initialized, NotReadyYet};
use crate::rendering::texture;
use crate::rendering::ui::UiRenderable;
use bevy_ecs::schedule::Schedule;
use bevy_ecs::world::World;
use egui::ViewportId;
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use std::sync::Arc;
use tracing::info;
use web_time::Instant;
use wgpu::{Adapter, Backends, BindGroupDescriptor, BindGroupEntry, BindingResource, Buffer, BufferDescriptor, BufferUsages, Color, ColorTargetState, CommandEncoderDescriptor, Device, DeviceDescriptor, Features, FragmentState, include_wgsl, Instance, InstanceDescriptor, Limits, LoadOp, Operations, Queue, RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor, RequestAdapterOptions, ShaderModule, StoreOp, Surface, TextureViewDescriptor, VertexState};
use wgpu::SurfaceConfiguration;
use wgpu::{
    include_wgsl, Adapter, Backends, BindGroupDescriptor, BindGroupEntry, BindingResource, Buffer,
    BufferDescriptor, BufferUsages, Color, ColorTargetState, CommandEncoderDescriptor, Device,
    DeviceDescriptor, Features, FragmentState, Instance, InstanceDescriptor, Limits, LoadOp,
    Operations, Queue, RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline,
    RenderPipelineDescriptor, RequestAdapterOptions, ShaderModule, StoreOp, Surface,
    TextureViewDescriptor, VertexState,
};
use winit::dpi::{LogicalSize, PhysicalSize};
use winit::window::Window;
use crate::ecs::{Camera, Position, Scale, SpriteTexture};
use crate::rendering::mipmap::MipGenerator;
use crate::rendering::renderer::RenderInitRes::{Initialized, NotReadyYet};
use crate::rendering::texture;
use crate::rendering::ui::UiRenderable;

pub struct Renderer<T: UiRenderable> {
    pub last_frame_time: Instant,


@@ 45,24 52,29 @@ pub struct Renderer<T: UiRenderable> {
    pub window: Arc<Window>,

    pub size: PhysicalSize<u32>,
    pub logical_size: LogicalSize<u32>
    pub logical_size: LogicalSize<u32>,
}

pub enum RenderInitRes<T: UiRenderable> {
    Initialized(Renderer<T>),
    NotReadyYet(World, Schedule, T)
    NotReadyYet(World, Schedule, T),
}
impl<T: UiRenderable> Debug for RenderInitRes<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Initialized(..) => write!(f, "[initialized renderer]"),
            NotReadyYet(..) => write!(f, "[pending initialization]")
            NotReadyYet(..) => write!(f, "[pending initialization]"),
        }
    }
}

impl<T: UiRenderable> Renderer<T> {
    pub async fn new(window: Arc<Window>, world: World, update_schedule: Schedule, gui_renderable: T) -> RenderInitRes<T> {
    pub async fn new(
        window: Arc<Window>,
        world: World,
        update_schedule: Schedule,
        gui_renderable: T,
    ) -> RenderInitRes<T> {
        let size = window.inner_size();
        if size.width == 0 || size.height == 0 {
            return NotReadyYet(world, update_schedule, gui_renderable);


@@ 71,31 83,43 @@ impl<T: UiRenderable> Renderer<T> {
        // First, create an instance. This is our handle to wgpu, and is the equivalent of navigator.gpu in WebGPU
        let instance = Instance::new(InstanceDescriptor {
            backends: Backends::all(), // Select the appropriate backend for the HAL to use. Defined in the platform module, as it's platform-specific
            ..Default::default() // Other fields aren't relevant here
            ..Default::default()       // Other fields aren't relevant here
        });

        // Next, get our render surface
        let surface = instance.create_surface(window.clone()).unwrap();

        // Next, request out adapter
        let adapter = instance.request_adapter(&RequestAdapterOptions {
            power_preference: Default::default(), // Don't care
            force_fallback_adapter: false, // We want a real GPU
            compatible_surface: Some(&surface), // Find an adapter that is able to render to our window
        }).await.unwrap();

        let (device, queue) = adapter.request_device(&DeviceDescriptor {
            label: Some("Basic render device"),
            required_features: Features::default(),
            required_limits: Limits::downlevel_webgl2_defaults(),
            memory_hints: Default::default(),
        }, None).await.unwrap();
        let adapter = instance
            .request_adapter(&RequestAdapterOptions {
                power_preference: Default::default(), // Don't care
                force_fallback_adapter: false,        // We want a real GPU
                compatible_surface: Some(&surface), // Find an adapter that is able to render to our window
            })
            .await
            .unwrap();

        let (device, queue) = adapter
            .request_device(
                &DeviceDescriptor {
                    label: Some("Basic render device"),
                    required_features: Features::default(),
                    required_limits: Limits::downlevel_webgl2_defaults(),
                    memory_hints: Default::default(),
                },
                None,
            )
            .await
            .unwrap();

        let format = surface.get_default_config(&adapter, size.width, size.height).unwrap();
        let format = surface
            .get_default_config(&adapter, size.width, size.height)
            .unwrap();

        surface.configure(&device, &format);

        let sprite_shader_module = device.create_shader_module(include_wgsl!("../shaders/sprite.wgsl"));
        let sprite_shader_module =
            device.create_shader_module(include_wgsl!("../shaders/sprite.wgsl"));
        let sprite_pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
            label: Some("Sprite pipeline"),
            layout: None,


@@ 129,15 153,9 @@ impl<T: UiRenderable> Renderer<T> {
            &window,
            Some(window.scale_factor() as f32),
            None,
            Some(device.limits().max_texture_dimension_2d as usize)
        );
        let gui_renderer = egui_wgpu::Renderer::new(
            &device,
            format.format,
            None,
            1,
            false
            Some(device.limits().max_texture_dimension_2d as usize),
        );
        let gui_renderer = egui_wgpu::Renderer::new(&device, format.format, None, 1, false);

        let uniform_buffer = device.create_buffer(&BufferDescriptor {
            label: Some("quad uniforms"),


@@ 167,7 185,7 @@ impl<T: UiRenderable> Renderer<T> {
            window,
            uniform_buffer,
            size,
            surface_configuration: format
            surface_configuration: format,
        })
    }



@@ 175,16 193,18 @@ impl<T: UiRenderable> Renderer<T> {
        // update the world
        self.update_schedule.run(&mut self.world);
        // update the UI
        let egui_output = self.gui_ctx.run(
            self.gui_winit.take_egui_input(&self.window),
            |ctx| {
        let egui_output = self
            .gui_ctx
            .run(self.gui_winit.take_egui_input(&self.window), |ctx| {
                self.gui_renderable.render(ctx, &mut self.world)
            }
        );
        self.gui_winit.handle_platform_output(&self.window, egui_output.platform_output);
            });
        self.gui_winit
            .handle_platform_output(&self.window, egui_output.platform_output);

        let output = self.surface.get_current_texture().unwrap();
        let view = output.texture.create_view(&TextureViewDescriptor::default());
        let view = output
            .texture
            .create_view(&TextureViewDescriptor::default());

        let render_pass_descriptor = RenderPassDescriptor {
            label: Some("basic render pass"),


@@ 289,15 309,24 @@ impl<T: UiRenderable> Renderer<T> {
        // main game rendering done
        // next up: egui UI rendering
        for (id, image_delta) in &egui_output.textures_delta.set {
            self.gui_renderer.update_texture(&self.device, &self.queue, *id, image_delta);
            self.gui_renderer
                .update_texture(&self.device, &self.queue, *id, image_delta);
        }
        {
            let paint_jobs = self.gui_ctx.tessellate(egui_output.shapes, self.scale_factor as f32);
            let paint_jobs = self
                .gui_ctx
                .tessellate(egui_output.shapes, self.scale_factor as f32);
            let screen_descriptor = egui_wgpu::ScreenDescriptor {
                size_in_pixels: [self.size.width, self.size.height],
                pixels_per_point: self.scale_factor as f32
                pixels_per_point: self.scale_factor as f32,
            };
            self.gui_renderer.update_buffers(&self.device, &self.queue, &mut encoder, &paint_jobs, &screen_descriptor);
            self.gui_renderer.update_buffers(
                &self.device,
                &self.queue,
                &mut encoder,
                &paint_jobs,
                &screen_descriptor,
            );
            let render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
                label: Some("ui render pass"),
                color_attachments: &[Some(RenderPassColorAttachment {


@@ 305,24 334,24 @@ impl<T: UiRenderable> Renderer<T> {
                    resolve_target: None,
                    ops: Operations {
                        load: LoadOp::Load,
                        store: StoreOp::Store
                    }
                        store: StoreOp::Store,
                    },
                })],
                depth_stencil_attachment: None,
                timestamp_writes: None,
                occlusion_query_set: None,
            });
            let mut forgotten_render_pass = render_pass.forget_lifetime();
            self.gui_renderer.render(&mut forgotten_render_pass, &paint_jobs, &screen_descriptor);
            self.gui_renderer
                .render(&mut forgotten_render_pass, &paint_jobs, &screen_descriptor);
            for id in egui_output.textures_delta.free {
                self.gui_renderer.free_texture(&id);
            }
        }


        let buffer = encoder.finish();
        self.queue.submit(std::iter::once(buffer));

        output.present();
    }
}
\ No newline at end of file
}

M starkingdoms-client/src/rendering/texture.rs => starkingdoms-client/src/rendering/texture.rs +6 -3
@@ 1,6 1,9 @@
use image::EncodableLayout;
use wgpu::{Device, Extent3d, FilterMode, ImageCopyTexture, ImageDataLayout, Origin3d, Queue, SamplerDescriptor, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages};
use crate::rendering::mipmap::MipGenerator;
use image::EncodableLayout;
use wgpu::{
    Device, Extent3d, FilterMode, ImageCopyTexture, ImageDataLayout, Origin3d, Queue,
    SamplerDescriptor, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
};

#[derive(Debug)]
pub struct Texture {


@@ 71,4 74,4 @@ impl Texture {

        tex
    }
}
\ No newline at end of file
}

M starkingdoms-client/src/rendering/ui.rs => starkingdoms-client/src/rendering/ui.rs +1 -1
@@ 2,4 2,4 @@ use bevy_ecs::world::World;

pub trait UiRenderable {
    fn render(&mut self, ctx: &egui::Context, world: &mut World);
}
\ No newline at end of file
}

M starkingdoms-client/src/wasm/mod.rs => starkingdoms-client/src/wasm/mod.rs +4 -5
@@ 1,9 1,9 @@
use tracing::Level;
use tracing_subscriber::fmt::format::Pretty;
use tracing_subscriber::prelude::*;
use wgpu::{Backends, Limits};
use tracing_web::{performance_layer, MakeWebConsoleWriter};
use wasm_bindgen::prelude::wasm_bindgen;
use tracing_web::{MakeWebConsoleWriter, performance_layer};
use wgpu::{Backends, Limits};

/// --- IMPORTANT: THIS IS A DUAL TARGET CRATE ---
/// THIS WILL ONLY EXECUTE ON WEBASSEMBLY


@@ 18,9 18,8 @@ pub fn entrypoint() {
    /* ----- Logging setup ----- */
    let fmt_layer = tracing_subscriber::fmt::layer()
        .with_ansi(false) // not supported in browsers
        .without_time()   // std::time doesn't exist in wasm
        .without_time() // std::time doesn't exist in wasm
        .with_writer(MakeWebConsoleWriter::new().with_max_level(Level::DEBUG)); // wgpu spams the console, and this is slow as hell
        

    let perf_layer = performance_layer() // enable performance tracing
        .with_details_from_fields(Pretty::default()); // ... with pretty fields


@@ 32,4 31,4 @@ pub fn entrypoint() {

    // All done with platform-specific initialization, call back into the common code path
    crate::start();
}
\ No newline at end of file
}