diff --git a/crates/components/src/command_view/command_ui.rs b/crates/components/src/command_view/command_ui.rs index e5a10da9..8c99bf68 100644 --- a/crates/components/src/command_view/command_ui.rs +++ b/crates/components/src/command_view/command_ui.rs @@ -35,11 +35,3 @@ impl super::CommandView { todo!() } } - -fn parameter_label( - _string: &mut String, - _parameter: &luminol_data::commands::Parameter, - _command: &mut luminol_data::rpg::EventCommand, -) -> std::fmt::Result { - todo!() -} diff --git a/crates/components/src/tilepicker.rs b/crates/components/src/tilepicker.rs index 68ff9b5c..7d8eaa19 100644 --- a/crates/components/src/tilepicker.rs +++ b/crates/components/src/tilepicker.rs @@ -61,7 +61,6 @@ impl egui_wgpu::CallbackTrait for Callback { render_pass: &mut wgpu::RenderPass<'a>, _callback_resources: &'a egui_wgpu::CallbackResources, ) { - self.resources.viewport.bind(1, render_pass); self.resources.tiles.draw( &self.graphics_state, &self.resources.viewport, @@ -71,7 +70,6 @@ impl egui_wgpu::CallbackTrait for Callback { ); if self.coll_enabled { - self.resources.viewport.bind(0, render_pass); self.resources.collision.draw( &self.graphics_state, &self.resources.viewport, @@ -153,6 +151,7 @@ impl Tilepicker { let tiles = luminol_graphics::tiles::Tiles::new( &update_state.graphics, + &viewport, atlas, &tilepicker_data, update_state.graphics.push_constants_supported(), @@ -176,6 +175,7 @@ impl Tilepicker { .copy_from_slice(&tileset.passages.as_slice()[384..384 + length]); let collision = luminol_graphics::collision::Collision::new( &update_state.graphics, + &viewport, &passages, update_state.graphics.push_constants_supported(), ); diff --git a/crates/graphics/src/binding_helpers.rs b/crates/graphics/src/binding_helpers.rs new file mode 100644 index 00000000..ceabde65 --- /dev/null +++ b/crates/graphics/src/binding_helpers.rs @@ -0,0 +1,140 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +// +// Additional permission under GNU GPL version 3 section 7 +// +// If you modify this Program, or any covered work, by linking or combining +// it with Steamworks API by Valve Corporation, containing parts covered by +// terms of the Steamworks API by Valve Corporation, the licensors of this +// Program grant you additional permission to convey the resulting work. + +pub struct BindGroupLayoutBuilder { + entries: Vec, +} + +impl Default for BindGroupLayoutBuilder { + fn default() -> Self { + Self::new() + } +} + +impl BindGroupLayoutBuilder { + pub fn new() -> Self { + Self { + entries: Vec::new(), + } + } + + pub fn append( + &mut self, + visibility: wgpu::ShaderStages, + ty: wgpu::BindingType, + count: Option, + ) -> &mut Self { + self.entries.push(wgpu::BindGroupLayoutEntry { + binding: self.entries.len() as u32, + visibility, + ty, + count, + }); + self + } + + #[must_use] + pub fn build(self, device: &wgpu::Device, label: wgpu::Label<'_>) -> wgpu::BindGroupLayout { + let descriptor = wgpu::BindGroupLayoutDescriptor { + label, + entries: &self.entries, + }; + device.create_bind_group_layout(&descriptor) + } +} + +pub struct BindGroupBuilder<'res> { + entries: Vec>, +} + +impl<'res> Default for BindGroupBuilder<'res> { + fn default() -> Self { + Self::new() + } +} + +impl<'res> BindGroupBuilder<'res> { + pub fn new() -> Self { + Self { + entries: Vec::new(), + } + } + + pub fn append(&mut self, resource: wgpu::BindingResource<'res>) -> &mut Self { + self.entries.push(wgpu::BindGroupEntry { + binding: self.entries.len() as u32, + resource, + }); + self + } + + pub fn append_buffer(&mut self, buffer: &'res wgpu::Buffer) -> &mut Self { + self.append(buffer.as_entire_binding()) + } + + pub fn append_buffer_with_size(&mut self, buffer: &'res wgpu::Buffer, size: u64) -> &mut Self { + self.append(wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer, + offset: 0, + size: std::num::NonZeroU64::new(size), + })) + } + + pub fn append_sampler(&mut self, sampler: &'res wgpu::Sampler) -> &mut Self { + self.append(wgpu::BindingResource::Sampler(sampler)) + } + + pub fn append_sampler_array( + &mut self, + sampler_array: &'res [&'res wgpu::Sampler], + ) -> &mut Self { + self.append(wgpu::BindingResource::SamplerArray(sampler_array)) + } + + pub fn append_texture_view(&mut self, texture: &'res wgpu::TextureView) -> &mut Self { + self.append(wgpu::BindingResource::TextureView(texture)) + } + + pub fn append_texture_view_array( + &mut self, + texture_view_array: &'res [&'res wgpu::TextureView], + ) -> &mut Self { + self.append(wgpu::BindingResource::TextureViewArray(texture_view_array)) + } + + #[must_use] + pub fn build( + self, + device: &wgpu::Device, + label: wgpu::Label<'_>, + layout: &wgpu::BindGroupLayout, + ) -> wgpu::BindGroup { + let descriptor = wgpu::BindGroupDescriptor { + label, + layout, + entries: &self.entries, + }; + device.create_bind_group(&descriptor) + } +} diff --git a/crates/graphics/src/collision/mod.rs b/crates/graphics/src/collision/mod.rs index 97391a4f..efafd678 100644 --- a/crates/graphics/src/collision/mod.rs +++ b/crates/graphics/src/collision/mod.rs @@ -15,6 +15,11 @@ // You should have received a copy of the GNU General Public License // along with Luminol. If not, see . +use crate::{ + viewport::{self, Viewport}, + BindGroupBuilder, BindGroupLayoutBuilder, +}; + use instance::Instances; use itertools::Itertools; use vertex::Vertex; @@ -26,6 +31,7 @@ mod vertex; #[derive(Debug)] pub struct Collision { pub instances: Instances, + pub bind_group: wgpu::BindGroup, pub use_push_constants: bool, } @@ -141,13 +147,25 @@ pub fn calculate_passage(layers: impl Iterator impl Collision { pub fn new( graphics_state: &crate::GraphicsState, + viewport: &Viewport, passages: &luminol_data::Table2, use_push_constants: bool, ) -> Self { let instances = Instances::new(&graphics_state.render_state, passages); + let mut bind_group_builder = BindGroupBuilder::new(); + if use_push_constants { + bind_group_builder.append_buffer(viewport.as_buffer().unwrap()); + } + let bind_group = bind_group_builder.build( + &graphics_state.render_state.device, + Some("collision bind group"), + &graphics_state.bind_group_layouts.tiles, + ); + Self { instances, + bind_group, use_push_constants, } } @@ -188,3 +206,13 @@ impl Collision { render_pass.pop_debug_group(); } } + +pub fn create_bind_group_layout(render_state: &egui_wgpu::RenderState) -> wgpu::BindGroupLayout { + let mut builder = BindGroupLayoutBuilder::new(); + + if crate::push_constants_supported(render_state) { + viewport::add_to_bind_group_layout(&mut builder); + } + + builder.build(&render_state.device, Some("collision bind group layout")) +} diff --git a/crates/graphics/src/collision/shader.rs b/crates/graphics/src/collision/shader.rs index 63d569a8..4715aeb5 100644 --- a/crates/graphics/src/collision/shader.rs +++ b/crates/graphics/src/collision/shader.rs @@ -55,29 +55,31 @@ pub fn create_render_pipeline( source: wgpu::ShaderSource::Naga(std::borrow::Cow::Owned(module)), }); - let pipeline_layout = if crate::push_constants_supported(render_state) { - render_state - .device - .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Tilemap Collision Render Pipeline Layout (push constants)"), - bind_group_layouts: &[], - push_constant_ranges: &[ - // Viewport - wgpu::PushConstantRange { - stages: wgpu::ShaderStages::VERTEX, - range: 0..64, - }, - ], - }) + let push_constant_ranges: &[_] = if push_constants_supported { + &[ + // Viewport + wgpu::PushConstantRange { + stages: wgpu::ShaderStages::VERTEX, + range: 0..64, + }, + ] } else { + &[] + }; + let label = if push_constants_supported { + "Tilemap Collision Render Pipeline Layout (push constants)" + } else { + "Tilemap Collision Render Pipeline Layout (uniforms)" + }; + + let pipeline_layout = render_state .device .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Tilemap Collision Render Pipeline Layout (uniforms)"), - bind_group_layouts: &[&bind_group_layouts.viewport], - push_constant_ranges: &[], - }) - }; + label: Some(label), + bind_group_layouts: &[&bind_group_layouts.collision], + push_constant_ranges, + }); render_state .device diff --git a/crates/graphics/src/event.rs b/crates/graphics/src/event.rs index 464e438f..7f66c4be 100644 --- a/crates/graphics/src/event.rs +++ b/crates/graphics/src/event.rs @@ -17,6 +17,8 @@ use std::sync::Arc; +use crate::{sprite::Sprite, viewport::Viewport, GraphicsState}; + #[derive(Debug)] pub struct Event { resources: Arc, @@ -25,13 +27,13 @@ pub struct Event { #[derive(Debug)] struct Resources { - sprite: crate::sprite::Sprite, - viewport: crate::viewport::Viewport, + sprite: Sprite, + viewport: Viewport, } struct Callback { resources: Arc, - graphics_state: Arc, + graphics_state: Arc, } //? SAFETY: @@ -49,7 +51,6 @@ impl egui_wgpu::CallbackTrait for Callback { render_pass: &mut wgpu::RenderPass<'a>, _callback_resources: &'a egui_wgpu::CallbackResources, ) { - self.resources.viewport.bind(1, render_pass); self.resources .sprite .draw(&self.graphics_state, &self.resources.viewport, render_pass); @@ -70,8 +71,7 @@ impl Event { }; let texture = if let Some(ref filename) = page.graphic.character_name { - graphics_state.image_cache.load_wgpu_image( - graphics_state, + graphics_state.texture_loader.load_now_dir( filesystem, "Graphics/Characters", filename, @@ -125,6 +125,7 @@ impl Event { let sprite = crate::sprite::Sprite::new( graphics_state, + &viewport, quads, texture, page.graphic.blend_type, diff --git a/crates/graphics/src/image_cache.rs b/crates/graphics/src/image_cache.rs deleted file mode 100644 index a7821362..00000000 --- a/crates/graphics/src/image_cache.rs +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright (C) 2023 Lily Lyons -// -// This file is part of Luminol. -// -// Luminol is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Luminol is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Luminol. If not, see . -// -// Additional permission under GNU GPL version 3 section 7 -// -// If you modify this Program, or any covered work, by linking or combining -// it with Steamworks API by Valve Corporation, containing parts covered by -// terms of the Steamworks API by Valve Corporation, the licensors of this -// Program grant you additional permission to convey the resulting work. - -use std::sync::Arc; - -use anyhow::Context; -use egui_extras::RetainedImage; -use wgpu::util::DeviceExt; - -#[derive(Default)] -pub struct Cache { - // FIXME: This may not handle reloading textures properly. - retained_images: dashmap::DashMap>, - wgpu_textures: dashmap::DashMap>, -} - -#[derive(Debug)] -pub struct WgpuTexture { - pub texture: wgpu::Texture, - pub bind_group: wgpu::BindGroup, -} - -impl WgpuTexture { - pub fn new(texture: wgpu::Texture, bind_group: wgpu::BindGroup) -> Self { - Self { - texture, - bind_group, - } - } - - pub fn size(&self) -> wgpu::Extent3d { - self.texture.size() - } - - pub fn size_vec2(&self) -> egui::Vec2 { - egui::vec2(self.texture.width() as _, self.texture.height() as _) - } - - pub fn width(&self) -> u32 { - self.texture.width() - } - - pub fn height(&self) -> u32 { - self.texture.height() - } - - pub fn bind<'rpass>(&'rpass self, render_pass: &mut wgpu::RenderPass<'rpass>) { - render_pass.set_bind_group(0, &self.bind_group, &[]); - } -} - -impl Cache { - pub fn load_egui_image( - &self, - filesystem: &impl luminol_filesystem::FileSystem, - directory: impl AsRef, - filename: impl AsRef, - ) -> anyhow::Result> { - let directory = directory.as_ref(); - let filename = filename.as_ref(); - - let entry = self - .retained_images - .entry(format!("{directory}/{filename}")) - .or_try_insert_with(|| -> anyhow::Result<_> { - let image = self - .load_image(filesystem, directory, filename)? - .into_rgba8(); - let image = egui_extras::RetainedImage::from_color_image( - format!("{directory}/{filename}"), - egui::ColorImage::from_rgba_unmultiplied( - [image.width() as usize, image.height() as usize], - &image, - ), - ) - .with_options(egui::TextureOptions::NEAREST); - Ok(Arc::new(image)) - })?; - Ok(Arc::clone(&entry)) - } - - pub fn load_image( - &self, - filesystem: &impl luminol_filesystem::FileSystem, - directory: impl AsRef, - filename: impl AsRef, - ) -> anyhow::Result { - let path = directory.as_ref().join(filename); - let data = filesystem - .read(&path) - .with_context(|| format!("while loading an image at {path}"))?; - image::load_from_memory(&data).map_err(anyhow::Error::from) - } - - pub fn create_texture_bind_group( - graphics_state: &crate::GraphicsState, - texture: &wgpu::Texture, - ) -> wgpu::BindGroup { - // We *really* don't care about the fields here. - let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - // We want our texture to use Nearest filtering and repeat. - // The only time our texture should be repeating is for fogs and panoramas. - let sampler = graphics_state - .render_state - .device - .create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::Repeat, - address_mode_v: wgpu::AddressMode::Repeat, - address_mode_w: wgpu::AddressMode::Repeat, - mag_filter: wgpu::FilterMode::Nearest, - min_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Nearest, - ..Default::default() - }); - - // Create the bind group - // Again, I have no idea why its setup this way - graphics_state - .render_state - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: None, - layout: &graphics_state.bind_group_layouts.image_cache_texture, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&texture_view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&sampler), - }, - ], - }) - } - - pub fn load_wgpu_image( - &self, - graphics_state: &crate::GraphicsState, - filesystem: &impl luminol_filesystem::FileSystem, - directory: impl AsRef, - filename: impl AsRef, - ) -> anyhow::Result> { - let directory = directory.as_ref(); - let filename = filename.as_ref(); - - let entry = self - .wgpu_textures - .entry(format!("{directory}/{filename}")) - .or_try_insert_with(|| -> anyhow::Result<_> { - // We force the image to be rgba8 to avoid any weird texture errors. - // If the image was not rgba8 (say it was rgb8) we would get weird texture errors - let image = self - .load_image(filesystem, directory, filename)? - .into_rgba8(); - // Check that the image will fit into the texture - // If we dont perform this check, we may get a segfault (dont ask me how i know this) - assert_eq!(image.len() as u32, image.width() * image.height() * 4); - - // Create the texture and upload the data at the same time. - // This is just a utility function to avoid boilerplate - let texture = graphics_state.render_state.device.create_texture_with_data( - &graphics_state.render_state.queue, - &wgpu::TextureDescriptor { - label: Some(&format!("{directory}/{filename}")), - size: wgpu::Extent3d { - width: image.width(), - height: image.height(), - depth_or_array_layers: 1, - }, - dimension: wgpu::TextureDimension::D2, - mip_level_count: 1, - sample_count: 1, - format: wgpu::TextureFormat::Rgba8Unorm, - usage: wgpu::TextureUsages::COPY_SRC - | wgpu::TextureUsages::COPY_DST - | wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[], - }, - &image, - ); - let bind_group = Self::create_texture_bind_group(graphics_state, &texture); - - let texture = WgpuTexture { - texture, - bind_group, - }; - - Ok(Arc::new(texture)) - })?; - Ok(Arc::clone(&entry)) - } - - pub fn clear(&self) { - self.retained_images.clear(); - self.wgpu_textures.clear(); - } -} - -pub fn create_bind_group_layout(render_state: &egui_wgpu::RenderState) -> wgpu::BindGroupLayout { - render_state - .device - .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: None, - // I just copy pasted this stuff from the wgpu guide. - // No clue why I need it. - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - // This should match the filterable field of the - // corresponding Texture entry above. - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - ], - }) -} diff --git a/crates/graphics/src/lib.rs b/crates/graphics/src/lib.rs index f1d513d2..639c9130 100644 --- a/crates/graphics/src/lib.rs +++ b/crates/graphics/src/lib.rs @@ -15,6 +15,9 @@ // You should have received a copy of the GNU General Public License // along with Luminol. If not, see . +pub mod binding_helpers; +pub use binding_helpers::{BindGroupBuilder, BindGroupLayoutBuilder}; + pub mod collision; pub mod quad; pub mod sprite; @@ -27,30 +30,32 @@ pub mod map; pub mod plane; pub mod atlas_cache; -pub mod image_cache; + pub mod texture_loader; +use std::sync::Arc; + pub use event::Event; pub use map::Map; pub use plane::Plane; -pub use texture_loader::TextureLoader; +pub use texture_loader::{Texture, TextureLoader}; pub struct GraphicsState { - pub image_cache: image_cache::Cache, + pub texture_loader: Arc, pub atlas_cache: atlas_cache::Cache, pub render_state: egui_wgpu::RenderState, + pub nearest_sampler: wgpu::Sampler, + pipelines: Pipelines, bind_group_layouts: BindGroupLayouts, } pub struct BindGroupLayouts { - image_cache_texture: wgpu::BindGroupLayout, - viewport: wgpu::BindGroupLayout, - sprite_graphic: wgpu::BindGroupLayout, - atlas_autotiles: wgpu::BindGroupLayout, - tile_layer_opacity: wgpu::BindGroupLayout, + sprite: wgpu::BindGroupLayout, + tiles: wgpu::BindGroupLayout, + collision: wgpu::BindGroupLayout, } pub struct Pipelines { @@ -62,11 +67,9 @@ pub struct Pipelines { impl GraphicsState { pub fn new(render_state: egui_wgpu::RenderState) -> Self { let bind_group_layouts = BindGroupLayouts { - image_cache_texture: image_cache::create_bind_group_layout(&render_state), - viewport: viewport::create_bind_group_layout(&render_state), - sprite_graphic: sprite::graphic::create_bind_group_layout(&render_state), - atlas_autotiles: tiles::autotiles::create_bind_group_layout(&render_state), - tile_layer_opacity: tiles::opacity::create_bind_group_layout(&render_state), + sprite: sprite::create_bind_group_layout(&render_state), + tiles: tiles::create_bind_group_layout(&render_state), + collision: collision::create_bind_group_layout(&render_state), }; let pipelines = Pipelines { @@ -78,13 +81,25 @@ impl GraphicsState { ), }; - let image_cache = image_cache::Cache::default(); + let texture_loader = Arc::new(TextureLoader::new(render_state.clone())); let atlas_cache = atlas_cache::Cache::default(); + let nearest_sampler = render_state + .device + .create_sampler(&wgpu::SamplerDescriptor { + label: Some("luminol tileset atlas sampler"), + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + Self { - image_cache, + texture_loader, atlas_cache, render_state, + + nearest_sampler, + pipelines, bind_group_layouts, } diff --git a/crates/graphics/src/map.rs b/crates/graphics/src/map.rs index 12c64221..1b9a6365 100644 --- a/crates/graphics/src/map.rs +++ b/crates/graphics/src/map.rs @@ -15,14 +15,15 @@ // You should have received a copy of the GNU General Public License // along with Luminol. If not, see . -use crate::Plane; use std::sync::Arc; use std::time::Duration; +use crate::{collision::Collision, tiles::Tiles, viewport::Viewport, GraphicsState, Plane}; + #[derive(Debug)] pub struct Map { - resources: std::sync::Arc, + resources: Arc, ani_time: Option, pub fog_enabled: bool, @@ -33,16 +34,16 @@ pub struct Map { #[derive(Debug)] struct Resources { - tiles: crate::tiles::Tiles, - viewport: crate::viewport::Viewport, + tiles: Tiles, + viewport: Viewport, panorama: Option, fog: Option, - collision: crate::collision::Collision, + collision: Collision, } struct Callback { resources: Arc, - graphics_state: Arc, + graphics_state: Arc, pano_enabled: bool, enabled_layers: Vec, @@ -76,8 +77,6 @@ impl egui_wgpu::CallbackTrait for Callback { render_pass: &mut wgpu::RenderPass<'a>, _callback_resources: &'a egui_wgpu::CallbackResources, ) { - self.resources.viewport.bind(1, render_pass); - if self.pano_enabled { if let Some(panorama) = &self.resources.panorama { panorama.draw(&self.graphics_state, &self.resources.viewport, render_pass); @@ -101,8 +100,6 @@ impl egui_wgpu::CallbackTrait for OverlayCallback { render_pass: &mut wgpu::RenderPass<'a>, _callback_resources: &'a egui_wgpu::CallbackResources, ) { - self.resources.viewport.bind(1, render_pass); - if self.fog_enabled { if let Some(fog) = &self.resources.fog { fog.draw(&self.graphics_state, &self.resources.viewport, render_pass); @@ -110,7 +107,6 @@ impl egui_wgpu::CallbackTrait for OverlayCallback { } if self.coll_enabled { - self.resources.viewport.bind(0, render_pass); self.resources.collision.draw( &self.graphics_state, &self.resources.viewport, @@ -133,15 +129,38 @@ impl Map { .atlas_cache .load_atlas(graphics_state, filesystem, tileset)?; - let tiles = crate::tiles::Tiles::new(graphics_state, atlas, &map.data, use_push_constants); - let collision = - crate::collision::Collision::new(graphics_state, passages, use_push_constants); + let viewport = crate::viewport::Viewport::new( + graphics_state, + glam::Mat4::orthographic_rh( + 0.0, + map.width as f32 * 32., + map.height as f32 * 32., + 0.0, + -1.0, + 1.0, + ), + use_push_constants, + ); + + let tiles = Tiles::new( + graphics_state, + &viewport, + atlas, + &map.data, + use_push_constants, + ); + let collision = crate::collision::Collision::new( + graphics_state, + &viewport, + passages, + use_push_constants, + ); let panorama = if let Some(ref panorama_name) = tileset.panorama_name { Some(Plane::new( graphics_state, - graphics_state.image_cache.load_wgpu_image( - graphics_state, + &viewport, + graphics_state.texture_loader.load_now_dir( filesystem, "Graphics/Panoramas", panorama_name, @@ -160,8 +179,8 @@ impl Map { let fog = if let Some(ref fog_name) = tileset.fog_name { Some(Plane::new( graphics_state, - graphics_state.image_cache.load_wgpu_image( - graphics_state, + &viewport, + graphics_state.texture_loader.load_now_dir( filesystem, "Graphics/Fogs", fog_name, @@ -177,18 +196,6 @@ impl Map { } else { None }; - let viewport = crate::viewport::Viewport::new( - graphics_state, - glam::Mat4::orthographic_rh( - 0.0, - map.width as f32 * 32., - map.height as f32 * 32., - 0.0, - -1.0, - 1.0, - ), - use_push_constants, - ); Ok(Self { resources: std::sync::Arc::new(Resources { diff --git a/crates/graphics/src/plane.rs b/crates/graphics/src/plane.rs index dccbee2b..bcdec70d 100644 --- a/crates/graphics/src/plane.rs +++ b/crates/graphics/src/plane.rs @@ -15,17 +15,21 @@ // You should have received a copy of the GNU General Public License // along with Luminol. If not, see . +use crate::{quad::Quad, sprite::Sprite, viewport::Viewport, GraphicsState, Texture}; +use std::sync::Arc; + #[derive(Debug)] pub struct Plane { - sprite: crate::sprite::Sprite, + sprite: Sprite, } impl Plane { // FIXME lots of arguments #[allow(clippy::too_many_arguments)] pub fn new( - graphics_state: &crate::GraphicsState, - texture: std::sync::Arc, + graphics_state: &GraphicsState, + viewport: &Viewport, + texture: Arc, hue: i32, zoom: i32, blend_mode: luminol_data::BlendMode, @@ -43,14 +47,15 @@ impl Plane { egui::vec2(map_width / zoom, map_height / zoom), ); - let quad = crate::quad::Quad::new( + let quad = Quad::new( egui::Rect::from_min_size(egui::pos2(0.0, 0.0), egui::vec2(map_width, map_height)), tex_coords, 0.0, ); - let sprite = crate::sprite::Sprite::new( + let sprite = Sprite::new( graphics_state, + viewport, quad, texture, blend_mode, diff --git a/crates/graphics/src/sprite/graphic.rs b/crates/graphics/src/sprite/graphic.rs index 83acd179..a101d1ce 100644 --- a/crates/graphics/src/sprite/graphic.rs +++ b/crates/graphics/src/sprite/graphic.rs @@ -21,13 +21,7 @@ use wgpu::util::DeviceExt; #[derive(Debug)] pub struct Graphic { data: AtomicCell, - uniform: Option, -} - -#[derive(Debug)] -struct GraphicUniform { - buffer: wgpu::Buffer, - bind_group: wgpu::BindGroup, + uniform: Option, } #[repr(C)] @@ -55,29 +49,15 @@ impl Graphic { _padding: 0, }; - let uniform = - if !use_push_constants { - let buffer = graphics_state.render_state.device.create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("tilemap sprite graphic buffer"), - contents: bytemuck::cast_slice(&[data]), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - }, - ); - let bind_group = graphics_state.render_state.device.create_bind_group( - &wgpu::BindGroupDescriptor { - label: Some("tilemap sprite graphic bind group"), - layout: &graphics_state.bind_group_layouts.sprite_graphic, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: buffer.as_entire_binding(), - }], - }, - ); - Some(GraphicUniform { buffer, bind_group }) - } else { - None - }; + let uniform = (!use_push_constants).then(|| { + graphics_state.render_state.device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("tilemap sprite graphic buffer"), + contents: bytemuck::cast_slice(&[data]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }, + ) + }); Self { data: AtomicCell::new(data), @@ -137,37 +117,29 @@ impl Graphic { bytemuck::cast(self.data.load()) } - fn regen_buffer(&self, render_state: &egui_wgpu::RenderState) { - if let Some(uniform) = &self.uniform { - render_state.queue.write_buffer( - &uniform.buffer, - 0, - bytemuck::cast_slice(&[self.data.load()]), - ); - } + pub fn as_buffer(&self) -> Option<&wgpu::Buffer> { + self.uniform.as_ref() } - pub fn bind<'rpass>(&'rpass self, render_pass: &mut wgpu::RenderPass<'rpass>) { + fn regen_buffer(&self, render_state: &egui_wgpu::RenderState) { if let Some(uniform) = &self.uniform { - render_pass.set_bind_group(2, &uniform.bind_group, &[]); + render_state + .queue + .write_buffer(uniform, 0, bytemuck::cast_slice(&[self.data.load()])); } } } -pub fn create_bind_group_layout(render_state: &egui_wgpu::RenderState) -> wgpu::BindGroupLayout { - render_state - .device - .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - label: Some("tilemap sprite graphic bind group layout"), - }) +pub fn add_to_bind_group_layout( + layout_builder: &mut crate::BindGroupLayoutBuilder, +) -> &mut crate::BindGroupLayoutBuilder { + layout_builder.append( + wgpu::ShaderStages::VERTEX_FRAGMENT, + wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + None, + ) } diff --git a/crates/graphics/src/sprite/mod.rs b/crates/graphics/src/sprite/mod.rs index 4f932190..0b8b8fdd 100644 --- a/crates/graphics/src/sprite/mod.rs +++ b/crates/graphics/src/sprite/mod.rs @@ -16,24 +16,33 @@ // along with Luminol. If not, see . use std::sync::Arc; +use crate::{ + quad::Quad, + viewport::{self, Viewport}, + BindGroupBuilder, BindGroupLayoutBuilder, GraphicsState, Texture, +}; + pub(crate) mod graphic; pub(crate) mod shader; mod vertices; #[derive(Debug)] pub struct Sprite { - pub texture: Arc, + pub texture: Arc, pub graphic: graphic::Graphic, pub vertices: vertices::Vertices, pub blend_mode: luminol_data::BlendMode, pub use_push_constants: bool, + + pub bind_group: wgpu::BindGroup, } impl Sprite { pub fn new( - graphics_state: &crate::GraphicsState, - quad: crate::quad::Quad, - texture: Arc, + graphics_state: &GraphicsState, + viewport: &Viewport, + quad: Quad, + texture: Arc, blend_mode: luminol_data::BlendMode, hue: i32, opacity: i32, @@ -43,12 +52,28 @@ impl Sprite { vertices::Vertices::from_quads(&graphics_state.render_state, &[quad], texture.size()); let graphic = graphic::Graphic::new(graphics_state, hue, opacity, use_push_constants); + let mut bind_group_builder = BindGroupBuilder::new(); + bind_group_builder + .append_texture_view(&texture.view) + .append_sampler(&graphics_state.nearest_sampler); + if crate::push_constants_supported(&graphics_state.render_state) { + bind_group_builder + .append_buffer(viewport.as_buffer().unwrap()) + .append_buffer(graphic.as_buffer().unwrap()); + } + let bind_group = bind_group_builder.build( + &graphics_state.render_state.device, + Some("sprite bind group"), + &graphics_state.bind_group_layouts.tiles, + ); + Self { texture, graphic, vertices, blend_mode, use_push_constants, + bind_group, } } @@ -72,6 +97,7 @@ impl Sprite { render_pass: &mut wgpu::RenderPass<'rpass>, ) { render_pass.set_pipeline(&graphics_state.pipelines.sprites[&self.blend_mode]); + render_pass.set_bind_group(0, &self.bind_group, &[]); if self.use_push_constants { render_pass.set_push_constants(wgpu::ShaderStages::VERTEX, 0, &viewport.as_bytes()); @@ -82,8 +108,32 @@ impl Sprite { ); } - self.texture.bind(render_pass); - self.graphic.bind(render_pass); self.vertices.draw(render_pass); } } + +pub fn create_bind_group_layout(render_state: &egui_wgpu::RenderState) -> wgpu::BindGroupLayout { + let mut builder = BindGroupLayoutBuilder::new(); + builder + .append( + wgpu::ShaderStages::FRAGMENT, + wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: false }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + None, + ) + .append( + wgpu::ShaderStages::FRAGMENT, + wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), + None, + ); + + if crate::push_constants_supported(render_state) { + viewport::add_to_bind_group_layout(&mut builder); + graphic::add_to_bind_group_layout(&mut builder); + } + + builder.build(&render_state.device, Some("sprite bind group layout")) +} diff --git a/crates/graphics/src/sprite/shader.rs b/crates/graphics/src/sprite/shader.rs index 5004e89a..911b0681 100644 --- a/crates/graphics/src/sprite/shader.rs +++ b/crates/graphics/src/sprite/shader.rs @@ -48,37 +48,36 @@ fn create_shader( source: wgpu::ShaderSource::Naga(std::borrow::Cow::Owned(module)), }); - let pipeline_layout = if push_constants_supported { - render_state - .device - .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Tilemap Sprite Pipeline Layout (push constants)"), - bind_group_layouts: &[&bind_group_layouts.image_cache_texture], - push_constant_ranges: &[ - // Viewport - wgpu::PushConstantRange { - stages: wgpu::ShaderStages::VERTEX, - range: 0..64, - }, - wgpu::PushConstantRange { - stages: wgpu::ShaderStages::FRAGMENT, - range: 64..(64 + 16), - }, - ], - }) + let push_constant_ranges: &[_] = if push_constants_supported { + &[ + // Viewport + wgpu::PushConstantRange { + stages: wgpu::ShaderStages::VERTEX, + range: 0..64, + }, + wgpu::PushConstantRange { + stages: wgpu::ShaderStages::FRAGMENT, + range: 64..(64 + 16), + }, + ] } else { + &[] + }; + let label = if push_constants_supported { + "Sprite Pipeline Layout (push constants)" + } else { + "Sprite Pipeline Layout (uniforms)" + }; + + let pipeline_layout = render_state .device .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Tilemap Sprite Pipeline Layout (uniforms)"), - bind_group_layouts: &[ - &bind_group_layouts.image_cache_texture, - &bind_group_layouts.viewport, - &bind_group_layouts.sprite_graphic, - ], - push_constant_ranges: &[], - }) - }; + label: Some(label), + bind_group_layouts: &[&bind_group_layouts.sprite], + push_constant_ranges, + }); + render_state .device .create_render_pipeline(&wgpu::RenderPipelineDescriptor { diff --git a/crates/graphics/src/sprite/sprite.wgsl b/crates/graphics/src/sprite/sprite.wgsl index f3d4f04e..7f8ca28a 100644 --- a/crates/graphics/src/sprite/sprite.wgsl +++ b/crates/graphics/src/sprite/sprite.wgsl @@ -20,6 +20,11 @@ struct Graphic { _padding: u32, } +@group(0) @binding(0) +var t_diffuse: texture_2d; +@group(0) @binding(1) +var s_diffuse: sampler; + #if USE_PUSH_CONSTANTS == true struct PushConstants { viewport: Viewport, @@ -27,16 +32,12 @@ struct PushConstants { } var push_constants: PushConstants; #else -@group(1) @binding(0) +@group(0) @binding(2) var viewport: Viewport; -@group(2) @binding(0) +@group(0) @binding(3) var graphic: Graphic; #endif -@group(0) @binding(0) -var t_diffuse: texture_2d; -@group(0) @binding(1) -var s_diffuse: sampler; fn rgb_to_hsv(c: vec3) -> vec3 { let K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); diff --git a/crates/graphics/src/texture_loader.rs b/crates/graphics/src/texture_loader.rs index d962723a..9f88d497 100644 --- a/crates/graphics/src/texture_loader.rs +++ b/crates/graphics/src/texture_loader.rs @@ -39,9 +39,11 @@ pub struct TextureLoader { render_state: egui_wgpu::RenderState, } +#[derive(Debug)] pub struct Texture { - pub wgpu: wgpu::Texture, - pub egui: egui::TextureId, + pub texture: wgpu::Texture, + pub view: wgpu::TextureView, + pub texture_id: egui::TextureId, } pub const TEXTURE_LOADER_ID: &str = egui::load::generate_loader_id!(TextureLoader); @@ -95,19 +97,19 @@ fn supported_uri_to_path(uri: &str) -> Option<&camino::Utf8Path> { impl Texture { pub fn size(&self) -> wgpu::Extent3d { - self.wgpu.size() + self.texture.size() } pub fn size_vec2(&self) -> egui::Vec2 { - egui::vec2(self.wgpu.width() as _, self.wgpu.height() as _) + egui::vec2(self.texture.width() as _, self.texture.height() as _) } pub fn width(&self) -> u32 { - self.wgpu.width() + self.texture.width() } pub fn height(&self) -> u32 { - self.wgpu.height() + self.texture.height() } } @@ -130,7 +132,7 @@ impl TextureLoader { // dashmap has no drain method so this is the best we can do let mut renderer = self.render_state.renderer.write(); for path in self.unloaded_textures.iter() { - let wgpu_texture = match load_wgpu_texture_from_path( + let texture = match load_wgpu_texture_from_path( filesystem, &self.render_state.device, &self.render_state.queue, @@ -143,21 +145,23 @@ impl TextureLoader { continue; } }; + let view = texture.create_view(&wgpu::TextureViewDescriptor { + label: Some(path.as_str()), + ..Default::default() + }); let texture_id = renderer.register_native_texture( &self.render_state.device, - &wgpu_texture.create_view(&wgpu::TextureViewDescriptor { - label: Some(path.as_str()), - ..Default::default() - }), + &view, wgpu::FilterMode::Nearest, ); self.loaded_textures.insert( path.clone(), Arc::new(Texture { - wgpu: wgpu_texture, - egui: texture_id, + texture, + view, + texture_id, }), ); } @@ -169,6 +173,16 @@ impl TextureLoader { self.unloaded_textures.clear(); } + pub fn load_now_dir( + &self, + filesystem: &impl luminol_filesystem::FileSystem, + directory: impl AsRef, + file: impl AsRef, + ) -> anyhow::Result> { + let path = directory.as_ref().join(file.as_ref()); + self.load_now(filesystem, path) + } + pub fn load_now( &self, filesystem: &impl luminol_filesystem::FileSystem, @@ -176,37 +190,40 @@ impl TextureLoader { ) -> anyhow::Result> { let path = path.as_ref().as_str(); - let wgpu_texture = load_wgpu_texture_from_path( + let texture = load_wgpu_texture_from_path( filesystem, &self.render_state.device, &self.render_state.queue, path, )?; - Ok(self.register_texture(path.to_string(), wgpu_texture)) + Ok(self.register_texture(path.to_string(), texture)) } pub fn register_texture( &self, path: impl Into, - wgpu_texture: wgpu::Texture, + texture: wgpu::Texture, ) -> Arc { let path = path.into(); + let view = texture.create_view(&wgpu::TextureViewDescriptor { + label: Some(path.as_str()), + ..Default::default() + }); + // todo maybe use custom sampler descriptor? // would allow for better texture names in debuggers let texture_id = self.render_state.renderer.write().register_native_texture( &self.render_state.device, - &wgpu_texture.create_view(&wgpu::TextureViewDescriptor { - label: Some(path.as_str()), - ..Default::default() - }), + &view, wgpu::FilterMode::Nearest, ); let texture = Arc::new(Texture { - wgpu: wgpu_texture, - egui: texture_id, + texture, + view, + texture_id, }); self.loaded_textures.insert(path, texture.clone()); texture @@ -236,7 +253,7 @@ impl egui::load::TextureLoader for TextureLoader { if let Some(texture) = self.loaded_textures.get(path).as_deref() { return Ok(TexturePoll::Ready { - texture: SizedTexture::new(texture.egui, texture.size_vec2()), + texture: SizedTexture::new(texture.texture_id, texture.size_vec2()), }); } @@ -274,7 +291,7 @@ impl egui::load::TextureLoader for TextureLoader { fn byte_size(&self) -> usize { self.loaded_textures .iter() - .map(|texture| texture_size_bytes(&texture.wgpu) as usize) + .map(|texture| texture_size_bytes(&texture.texture) as usize) .sum() } } diff --git a/crates/graphics/src/tiles/atlas.rs b/crates/graphics/src/tiles/atlas.rs index b5ecc0db..4aa5b155 100644 --- a/crates/graphics/src/tiles/atlas.rs +++ b/crates/graphics/src/tiles/atlas.rs @@ -19,6 +19,7 @@ use itertools::Itertools; use super::autotile_ids::AUTOTILES; use crate::quad::Quad; +use crate::{GraphicsState, Texture}; pub const MAX_SIZE: u32 = 8192; // Max texture size in one dimension pub const TILE_SIZE: u32 = 32; // Tiles are 32x32 @@ -45,7 +46,7 @@ use std::sync::Arc; #[derive(Debug, Clone)] pub struct Atlas { - pub atlas_texture: Arc, + pub atlas_texture: Arc, pub autotile_width: u32, pub tileset_height: u32, pub autotile_frames: [u32; AUTOTILE_AMOUNT as usize], @@ -53,16 +54,15 @@ pub struct Atlas { impl Atlas { pub fn new( - graphics_state: &crate::GraphicsState, + graphics_state: &GraphicsState, filesystem: &impl luminol_filesystem::FileSystem, tileset: &luminol_data::rpg::Tileset, ) -> anyhow::Result { let tileset_img = match &tileset.tileset_name { Some(tileset_name) => { - let tileset_img = graphics_state - .image_cache - .load_image(filesystem, "Graphics/Tilesets", tileset_name) - .context("while loading atlas tileset")?; + let file = filesystem + .read(camino::Utf8Path::new("Graphics/Tilesets").join(tileset_name))?; + let tileset_img = image::load_from_memory(&file)?; Some(tileset_img.to_rgba8()) } None => None, @@ -81,8 +81,8 @@ impl Atlas { Ok(None) } else { graphics_state - .image_cache - .load_wgpu_image(graphics_state, filesystem, "Graphics/Autotiles", s) + .texture_loader + .load_now_dir(filesystem, "Graphics/Autotiles", s) .map(Some) } }) @@ -92,7 +92,7 @@ impl Atlas { let autotile_frames = std::array::from_fn(|i| { autotiles[i] .as_deref() - .map(crate::image_cache::WgpuTexture::width) + .map(Texture::width) // Why unwrap with a width of 96? Even though the autotile doesn't exist, it still has an effective width on the atlas of one frame. // Further rendering code breaks down with an autotile width of 0, anyway. .unwrap_or(96) @@ -254,17 +254,13 @@ impl Atlas { } } - let bind_group = - crate::image_cache::Cache::create_texture_bind_group(graphics_state, &atlas_texture); - let atlas_texture = Arc::new(crate::image_cache::WgpuTexture::new( - atlas_texture, - bind_group, - )); + let atlas_texture = graphics_state + .texture_loader + .register_texture(format!("tileset_atlases/{}", tileset.id), atlas_texture); Ok(Atlas { atlas_texture, autotile_width, - tileset_height, autotile_frames, }) @@ -279,8 +275,10 @@ impl Atlas { let is_under_autotiles = !is_autotile && tile_u32 - TOTAL_AUTOTILE_ID_AMOUNT < max_tiles_under_autotiles; - let atlas_tile_position = if !(AUTOTILE_ID_AMOUNT..(MAX_SIZE / TILESET_WIDTH) * ROWS_UNDER_AUTOTILES_TIMES_COLUMNS - + TOTAL_AUTOTILE_ID_AMOUNT).contains(&tile_u32) + let atlas_tile_position = if !(AUTOTILE_ID_AMOUNT + ..(MAX_SIZE / TILESET_WIDTH) * ROWS_UNDER_AUTOTILES_TIMES_COLUMNS + + TOTAL_AUTOTILE_ID_AMOUNT) + .contains(&tile_u32) { egui::pos2(0., 0.) } else if is_autotile { @@ -326,10 +324,6 @@ impl Atlas { 0.0, ) } - - pub fn bind<'rpass>(&'rpass self, render_pass: &mut wgpu::RenderPass<'rpass>) { - self.atlas_texture.bind(render_pass); - } } fn write_texture_region

( diff --git a/crates/graphics/src/tiles/autotiles.rs b/crates/graphics/src/tiles/autotiles.rs index 4821ac83..f2a124e1 100644 --- a/crates/graphics/src/tiles/autotiles.rs +++ b/crates/graphics/src/tiles/autotiles.rs @@ -21,13 +21,7 @@ use wgpu::util::DeviceExt; #[derive(Debug)] pub struct Autotiles { data: AtomicCell, - uniform: Option, -} - -#[derive(Debug)] -struct AutotilesUniform { - buffer: wgpu::Buffer, - bind_group: wgpu::BindGroup, + uniform: Option, } #[repr(C, align(16))] @@ -54,29 +48,15 @@ impl Autotiles { _end_padding: 0, }; - let uniform = - if !use_push_constants { - let buffer = graphics_state.render_state.device.create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("tilemap autotile buffer"), - contents: bytemuck::cast_slice(&[autotiles]), - usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, - }, - ); - let bind_group = graphics_state.render_state.device.create_bind_group( - &wgpu::BindGroupDescriptor { - label: Some("tilemap autotiles bind group"), - layout: &graphics_state.bind_group_layouts.atlas_autotiles, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: buffer.as_entire_binding(), - }], - }, - ); - Some(AutotilesUniform { buffer, bind_group }) - } else { - None - }; + let uniform = (!use_push_constants).then(|| { + graphics_state.render_state.device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("tilemap autotile buffer"), + contents: bytemuck::cast_slice(&[autotiles]), + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, + }, + ) + }); Autotiles { data: AtomicCell::new(autotiles), @@ -97,37 +77,29 @@ impl Autotiles { bytemuck::cast(self.data.load()) } - fn regen_buffer(&self, render_state: &egui_wgpu::RenderState) { - if let Some(uniform) = &self.uniform { - render_state.queue.write_buffer( - &uniform.buffer, - 0, - bytemuck::cast_slice(&[self.data.load()]), - ); - } + pub fn as_buffer(&self) -> Option<&wgpu::Buffer> { + self.uniform.as_ref() } - pub fn bind<'rpass>(&'rpass self, render_pass: &mut wgpu::RenderPass<'rpass>) { + fn regen_buffer(&self, render_state: &egui_wgpu::RenderState) { if let Some(uniform) = &self.uniform { - render_pass.set_bind_group(2, &uniform.bind_group, &[]); + render_state + .queue + .write_buffer(uniform, 0, bytemuck::cast_slice(&[self.data.load()])); } } } -pub fn create_bind_group_layout(render_state: &egui_wgpu::RenderState) -> wgpu::BindGroupLayout { - render_state - .device - .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - label: Some("tilemap autotiles bind group layout"), - }) +pub fn add_to_bind_group_layout( + layout_builder: &mut crate::BindGroupLayoutBuilder, +) -> &mut crate::BindGroupLayoutBuilder { + layout_builder.append( + wgpu::ShaderStages::VERTEX_FRAGMENT, + wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + None, + ) } diff --git a/crates/graphics/src/tiles/mod.rs b/crates/graphics/src/tiles/mod.rs index e3ff17d5..9301bf35 100644 --- a/crates/graphics/src/tiles/mod.rs +++ b/crates/graphics/src/tiles/mod.rs @@ -15,6 +15,11 @@ // You should have received a copy of the GNU General Public License // along with Luminol. If not, see . +use crate::{ + viewport::{self, Viewport}, + BindGroupBuilder, BindGroupLayoutBuilder, GraphicsState, +}; + pub use atlas::Atlas; use autotiles::Autotiles; @@ -35,11 +40,14 @@ pub struct Tiles { pub instances: Instances, pub opacity: Opacity, pub use_push_constants: bool, + + pub bind_group: wgpu::BindGroup, } impl Tiles { pub fn new( - graphics_state: &crate::GraphicsState, + graphics_state: &GraphicsState, + viewport: &Viewport, atlas: Atlas, tiles: &luminol_data::Table3, use_push_constants: bool, @@ -52,12 +60,30 @@ impl Tiles { ); let opacity = Opacity::new(graphics_state, use_push_constants); + let mut bind_group_builder = BindGroupBuilder::new(); + bind_group_builder + .append_texture_view(&atlas.atlas_texture.view) + .append_sampler(&graphics_state.nearest_sampler); + if crate::push_constants_supported(&graphics_state.render_state) { + bind_group_builder + .append_buffer(viewport.as_buffer().unwrap()) + .append_buffer(autotiles.as_buffer().unwrap()) + .append_buffer(opacity.as_buffer().unwrap()); + } + let bind_group = bind_group_builder.build( + &graphics_state.render_state.device, + Some("tilemap bind group"), + &graphics_state.bind_group_layouts.tiles, + ); + Self { autotiles, atlas, instances, opacity, use_push_constants, + + bind_group, } } @@ -87,7 +113,8 @@ impl Tiles { render_pass.push_debug_group("tilemap tiles renderer"); render_pass.set_pipeline(&graphics_state.pipelines.tiles); - self.autotiles.bind(render_pass); + render_pass.set_bind_group(0, &self.bind_group, &[]); + if self.use_push_constants { render_pass.set_push_constants( wgpu::ShaderStages::VERTEX, @@ -99,28 +126,53 @@ impl Tiles { ); } - self.atlas.bind(render_pass); - self.opacity.bind(render_pass); - for (layer, enabled) in enabled_layers.iter().copied().enumerate() { - let opacity = match selected_layer { - Some(selected_layer) if selected_layer == layer => 1.0, - Some(_) => 0.5, - None => 1.0, + let opacity = if selected_layer.is_some_and(|s| s != layer) { + 0.5 + } else { + 1.0 }; - self.opacity - .set_opacity(&graphics_state.render_state, layer, opacity); - if self.use_push_constants { - render_pass.set_push_constants( - wgpu::ShaderStages::FRAGMENT, - 64 + 48, - bytemuck::bytes_of::(&opacity), - ); - } if enabled { + self.opacity + .set_opacity(&graphics_state.render_state, layer, opacity); + if self.use_push_constants { + render_pass.set_push_constants( + wgpu::ShaderStages::FRAGMENT, + 64 + 48, + bytemuck::bytes_of::(&opacity), + ); + } + self.instances.draw(render_pass, layer); } } render_pass.pop_debug_group(); } } + +pub fn create_bind_group_layout(render_state: &egui_wgpu::RenderState) -> wgpu::BindGroupLayout { + let mut builder = BindGroupLayoutBuilder::new(); + builder + .append( + wgpu::ShaderStages::FRAGMENT, + wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: false }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + None, + ) + .append( + wgpu::ShaderStages::FRAGMENT, + wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), + None, + ); + + if crate::push_constants_supported(render_state) { + viewport::add_to_bind_group_layout(&mut builder); + autotiles::add_to_bind_group_layout(&mut builder); + opacity::add_to_bind_group_layout(&mut builder); + } + + builder.build(&render_state.device, Some("tilemap bind group layout")) +} diff --git a/crates/graphics/src/tiles/opacity.rs b/crates/graphics/src/tiles/opacity.rs index 8ebdccb8..5a6d011d 100644 --- a/crates/graphics/src/tiles/opacity.rs +++ b/crates/graphics/src/tiles/opacity.rs @@ -21,42 +21,22 @@ use wgpu::util::DeviceExt; #[derive(Debug)] pub struct Opacity { data: AtomicCell<[f32; 4]>, // length has to be a multiple of 4 - uniform: Option, -} - -#[derive(Debug)] -struct OpacityUniform { - buffer: wgpu::Buffer, - bind_group: wgpu::BindGroup, + uniform: Option, } impl Opacity { pub fn new(graphics_state: &crate::GraphicsState, use_push_constants: bool) -> Self { let opacity = [1.; 4]; - let uniform = - if !use_push_constants { - let buffer = graphics_state.render_state.device.create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("tilemap opacity buffer"), - contents: bytemuck::cast_slice(&[opacity]), - usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, - }, - ); - let bind_group = graphics_state.render_state.device.create_bind_group( - &wgpu::BindGroupDescriptor { - label: Some("tilemap opacity bind group"), - layout: &graphics_state.bind_group_layouts.tile_layer_opacity, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: buffer.as_entire_binding(), - }], - }, - ); - Some(OpacityUniform { buffer, bind_group }) - } else { - None - }; + let uniform = (!use_push_constants).then(|| { + graphics_state.render_state.device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("tilemap opacity buffer"), + contents: bytemuck::cast_slice(&[opacity]), + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, + }, + ) + }); Self { data: AtomicCell::new(opacity), @@ -68,6 +48,10 @@ impl Opacity { self.data.load()[layer] } + pub fn as_buffer(&self) -> Option<&wgpu::Buffer> { + self.uniform.as_ref() + } + pub fn set_opacity(&self, render_state: &egui_wgpu::RenderState, layer: usize, opacity: f32) { let mut data = self.data.load(); if data[layer] != opacity { @@ -79,35 +63,23 @@ impl Opacity { fn regen_buffer(&self, render_state: &egui_wgpu::RenderState) { if let Some(uniform) = &self.uniform { - render_state.queue.write_buffer( - &uniform.buffer, - 0, - bytemuck::cast_slice(&[self.data.load()]), - ); - } - } - - pub fn bind<'rpass>(&'rpass self, render_pass: &mut wgpu::RenderPass<'rpass>) { - if let Some(uniform) = &self.uniform { - render_pass.set_bind_group(3, &uniform.bind_group, &[]); + render_state + .queue + .write_buffer(uniform, 0, bytemuck::cast_slice(&[self.data.load()])); } } } -pub fn create_bind_group_layout(render_state: &egui_wgpu::RenderState) -> wgpu::BindGroupLayout { - render_state - .device - .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - label: Some("tilemap opacity bind group layout"), - }) +pub fn add_to_bind_group_layout( + layout_builder: &mut crate::BindGroupLayoutBuilder, +) -> &mut crate::BindGroupLayoutBuilder { + layout_builder.append( + wgpu::ShaderStages::VERTEX_FRAGMENT, + wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + None, + ) } diff --git a/crates/graphics/src/tiles/shader.rs b/crates/graphics/src/tiles/shader.rs index 6e7af927..00b33b34 100644 --- a/crates/graphics/src/tiles/shader.rs +++ b/crates/graphics/src/tiles/shader.rs @@ -55,39 +55,36 @@ pub fn create_render_pipeline( source: wgpu::ShaderSource::Naga(std::borrow::Cow::Owned(module)), }); - let pipeline_layout = if crate::push_constants_supported(render_state) { - render_state - .device - .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Tilemap Render Pipeline Layout (push constants)"), - bind_group_layouts: &[&bind_group_layouts.image_cache_texture], - push_constant_ranges: &[ - // Viewport + Autotiles - wgpu::PushConstantRange { - stages: wgpu::ShaderStages::VERTEX, - range: 0..(64 + 48), - }, - // Fragment - wgpu::PushConstantRange { - stages: wgpu::ShaderStages::FRAGMENT, - range: (64 + 48)..(64 + 48 + 4), - }, - ], - }) + let push_constant_ranges: &[_] = if push_constants_supported { + &[ + // Viewport + Autotiles + wgpu::PushConstantRange { + stages: wgpu::ShaderStages::VERTEX, + range: 0..(64 + 48), + }, + // Fragment + wgpu::PushConstantRange { + stages: wgpu::ShaderStages::FRAGMENT, + range: (64 + 48)..(64 + 48 + 4), + }, + ] } else { + &[] + }; + let label = if push_constants_supported { + "Tilemap Render Pipeline Layout (push constants)" + } else { + "Tilemap Render Pipeline Layout (uniforms)" + }; + + let pipeline_layout = render_state .device .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Tilemap Render Pipeline Layout (uniforms)"), - bind_group_layouts: &[ - &bind_group_layouts.image_cache_texture, - &bind_group_layouts.viewport, - &bind_group_layouts.atlas_autotiles, - &bind_group_layouts.tile_layer_opacity, - ], - push_constant_ranges: &[], - }) - }; + label: Some(label), + bind_group_layouts: &[&bind_group_layouts.tiles], + push_constant_ranges, + }); render_state .device diff --git a/crates/graphics/src/tiles/tilemap.wgsl b/crates/graphics/src/tiles/tilemap.wgsl index fe5cc240..4cc5b927 100644 --- a/crates/graphics/src/tiles/tilemap.wgsl +++ b/crates/graphics/src/tiles/tilemap.wgsl @@ -26,6 +26,11 @@ struct Autotiles { max_frame_count: u32, } +@group(0) @binding(0) +var atlas: texture_2d; +@group(0) @binding(1) +var atlas_sampler: sampler; + #if USE_PUSH_CONSTANTS == true struct PushConstants { viewport: Viewport, @@ -34,11 +39,11 @@ struct PushConstants { } var push_constants: PushConstants; #else -@group(1) @binding(0) +@group(0) @binding(2) var viewport: Viewport; -@group(2) @binding(0) +@group(0) @binding(3) var autotiles: Autotiles; -@group(3) @binding(0) +@group(1) @binding(4) var opacity: array, 1>; #endif @@ -107,10 +112,6 @@ fn vs_main(vertex: VertexInput, instance: InstanceInput) -> VertexOutput { return out; } -@group(0) @binding(0) -var atlas: texture_2d; -@group(0) @binding(1) -var atlas_sampler: sampler; @fragment fn fs_main(input: VertexOutput) -> @location(0) vec4 { diff --git a/crates/graphics/src/viewport.rs b/crates/graphics/src/viewport.rs index b514af2e..6b58bc28 100644 --- a/crates/graphics/src/viewport.rs +++ b/crates/graphics/src/viewport.rs @@ -21,13 +21,7 @@ use wgpu::util::DeviceExt; #[derive(Debug)] pub struct Viewport { data: AtomicCell, - uniform: Option, -} - -#[derive(Debug)] -struct ViewportUniform { - buffer: wgpu::Buffer, - bind_group: wgpu::BindGroup, + uniform: Option, } impl Viewport { @@ -36,29 +30,15 @@ impl Viewport { proj: glam::Mat4, use_push_constants: bool, ) -> Self { - let uniform = - if !use_push_constants { - let buffer = graphics_state.render_state.device.create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("tilemap viewport buffer"), - contents: bytemuck::cast_slice(&[proj]), - usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, - }, - ); - let bind_group = graphics_state.render_state.device.create_bind_group( - &wgpu::BindGroupDescriptor { - label: Some("tilemap viewport uniform bind group"), - layout: &graphics_state.bind_group_layouts.viewport, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: buffer.as_entire_binding(), - }], - }, - ); - Some(ViewportUniform { buffer, bind_group }) - } else { - None - }; + let uniform = (!use_push_constants).then(|| { + graphics_state.render_state.device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("tilemap viewport buffer"), + contents: bytemuck::cast_slice(&[proj]), + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, + }, + ) + }); Self { data: AtomicCell::new(proj), @@ -78,41 +58,29 @@ impl Viewport { bytemuck::cast(self.data.load()) } - fn regen_buffer(&self, render_state: &egui_wgpu::RenderState) { - if let Some(uniform) = &self.uniform { - render_state.queue.write_buffer( - &uniform.buffer, - 0, - bytemuck::cast_slice(&[self.data.load()]), - ); - } + pub fn as_buffer(&self) -> Option<&wgpu::Buffer> { + self.uniform.as_ref() } - pub fn bind<'rpass>( - &'rpass self, - group_index: u32, - render_pass: &mut wgpu::RenderPass<'rpass>, - ) { + fn regen_buffer(&self, render_state: &egui_wgpu::RenderState) { if let Some(uniform) = &self.uniform { - render_pass.set_bind_group(group_index, &uniform.bind_group, &[]); + render_state + .queue + .write_buffer(uniform, 0, bytemuck::cast_slice(&[self.data.load()])); } } } -pub fn create_bind_group_layout(render_state: &egui_wgpu::RenderState) -> wgpu::BindGroupLayout { - render_state - .device - .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - label: Some("tilemap viewport bind group layout"), - }) +pub fn add_to_bind_group_layout( + layout_builder: &mut crate::BindGroupLayoutBuilder, +) -> &mut crate::BindGroupLayoutBuilder { + layout_builder.append( + wgpu::ShaderStages::VERTEX_FRAGMENT, + wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + None, + ) } diff --git a/crates/ui/src/windows/appearance.rs b/crates/ui/src/windows/appearance.rs index 4b78e927..71f86203 100644 --- a/crates/ui/src/windows/appearance.rs +++ b/crates/ui/src/windows/appearance.rs @@ -22,6 +22,7 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. +use egui::load::TextureLoader; use strum::IntoEnumIterator; #[derive(Default)] @@ -96,7 +97,7 @@ impl luminol_core::Window for Window { ) .clicked() { - update_state.graphics.image_cache.clear(); + update_state.graphics.texture_loader.forget_all(); update_state.graphics.atlas_cache.clear(); } });