diff options
Diffstat (limited to 'stockton-passes/src')
-rw-r--r-- | stockton-passes/src/camera.rs | 61 | ||||
-rw-r--r-- | stockton-passes/src/data/3d.frag | 15 | ||||
-rw-r--r-- | stockton-passes/src/data/3d.vert | 23 | ||||
-rw-r--r-- | stockton-passes/src/data/ui.frag | 15 | ||||
-rw-r--r-- | stockton-passes/src/data/ui.vert | 37 | ||||
-rw-r--r-- | stockton-passes/src/level.rs | 478 | ||||
-rw-r--r-- | stockton-passes/src/lib.rs | 9 | ||||
-rw-r--r-- | stockton-passes/src/ui.rs | 398 | ||||
-rw-r--r-- | stockton-passes/src/window.rs | 259 |
9 files changed, 1295 insertions, 0 deletions
diff --git a/stockton-passes/src/camera.rs b/stockton-passes/src/camera.rs new file mode 100644 index 0000000..49cce59 --- /dev/null +++ b/stockton-passes/src/camera.rs @@ -0,0 +1,61 @@ +//! Things related to converting 3D world space to 2D screen space + +use legion::maybe_changed; +use na::{look_at_lh, perspective_lh_zo}; +use stockton_types::{ + components::{CameraSettings, CameraVPMatrix, Transform}, + Vector3, +}; + +use stockton_render::Renderer; + +use stockton_render::draw_passes::DrawPass; + +fn euler_to_direction(euler: &Vector3) -> Vector3 { + let pitch = euler.x; + let yaw = euler.y; + let _roll = euler.z; // TODO: Support camera roll + + Vector3::new( + yaw.sin() * pitch.cos(), + pitch.sin(), + yaw.cos() * pitch.cos(), + ) +} + +#[system(for_each)] +#[filter(maybe_changed::<Transform>() | maybe_changed::<CameraSettings>())] +pub fn calc_vp_matrix<DP: DrawPass + 'static>( + transform: &Transform, + settings: &CameraSettings, + matrix: &mut CameraVPMatrix, + #[resource] renderer: &Renderer<DP>, +) { + // Get look direction from euler angles + let direction = euler_to_direction(&transform.rotation); + + // Converts world space to camera space + let view_matrix = look_at_lh( + &transform.position, + &(transform.position + direction), + &Vector3::new(0.0, 1.0, 0.0), + ); + + // Converts camera space to screen space + let projection_matrix = { + let mut temp = perspective_lh_zo( + renderer.get_aspect_ratio(), + settings.fov, + settings.near, + settings.far, + ); + + // Vulkan's co-ord system is different from OpenGLs + temp[(1, 1)] *= -1.0; + + temp + }; + + // Chain them together into a single matrix + matrix.vp_matrix = projection_matrix * view_matrix; +} diff --git a/stockton-passes/src/data/3d.frag b/stockton-passes/src/data/3d.frag new file mode 100644 index 0000000..336d9fe --- /dev/null +++ b/stockton-passes/src/data/3d.frag @@ -0,0 +1,15 @@ +#version 450 + +// DescriptorSet 0 = Textures +layout(set = 0, binding = 0) uniform texture2D tex[8]; +layout(set = 0, binding = 1) uniform sampler samp[8]; + +layout (location = 1) in vec2 frag_uv; +layout (location = 2) in flat int frag_tex; + +layout (location = 0) out vec4 color; + +void main() +{ + color = texture(sampler2D(tex[frag_tex % 8], samp[frag_tex % 8]), frag_uv); +}
\ No newline at end of file diff --git a/stockton-passes/src/data/3d.vert b/stockton-passes/src/data/3d.vert new file mode 100644 index 0000000..aaee1a5 --- /dev/null +++ b/stockton-passes/src/data/3d.vert @@ -0,0 +1,23 @@ +#version 450 + +// DescriptorSet 0 = Matrices +layout (push_constant) uniform PushConsts { + mat4 vp; +} push; + +layout (location = 0) in vec3 position; +layout (location = 1) in int tex; +layout (location = 2) in vec2 uv; + +out gl_PerVertex { + vec4 gl_Position; +}; +layout (location = 1) out vec2 frag_uv; +layout (location = 2) out flat int frag_tex; + +void main() +{ + gl_Position = push.vp * vec4(position, 1.0); + frag_uv = uv; + frag_tex = tex; +}
\ No newline at end of file diff --git a/stockton-passes/src/data/ui.frag b/stockton-passes/src/data/ui.frag new file mode 100644 index 0000000..c30c99e --- /dev/null +++ b/stockton-passes/src/data/ui.frag @@ -0,0 +1,15 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +// DescriptorSet 0 = Textures +layout(set = 0, binding = 0) uniform texture2D tex[8]; +layout(set = 0, binding = 1) uniform sampler samp[8]; + +layout (location = 1) in vec2 frag_uv; +layout (location = 2) in vec4 frag_col; + +layout (location = 0) out vec4 color; + +void main() { + color = texture(sampler2D(tex[0], samp[0]), frag_uv) * frag_col; +}
\ No newline at end of file diff --git a/stockton-passes/src/data/ui.vert b/stockton-passes/src/data/ui.vert new file mode 100644 index 0000000..8912e96 --- /dev/null +++ b/stockton-passes/src/data/ui.vert @@ -0,0 +1,37 @@ +#version 450 + +layout (push_constant) uniform PushConsts { + vec2 screen_size; +} push; + +layout (location = 0) in vec2 pos; +layout (location = 1) in vec2 uv; +layout (location = 2) in vec4 col; + +out gl_PerVertex { + vec4 gl_Position; +}; +layout (location = 1) out vec2 frag_uv; +layout (location = 2) out vec4 frag_col; + +vec3 linear_from_srgb(vec3 srgb) { + bvec3 cutoff = lessThan(srgb, vec3(10.31475)); + vec3 lower = srgb / vec3(3294.6); + vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4)); + return mix(higher, lower, cutoff); +} + +vec4 linear_from_srgba(vec4 srgba) { + return vec4(linear_from_srgb(srgba.rgb * 255.0), srgba.a); +} + +void main() { + gl_Position = vec4( + 2.0 * pos.x / push.screen_size.x - 1.0, + 2.0 * pos.y / push.screen_size.y - 1.0, + 0.0, + 1.0 + ); + frag_uv = uv; + frag_col = linear_from_srgba(col); +} diff --git a/stockton-passes/src/level.rs b/stockton-passes/src/level.rs new file mode 100644 index 0000000..cd3ce6a --- /dev/null +++ b/stockton-passes/src/level.rs @@ -0,0 +1,478 @@ +//! Minimal code for drawing any level, based on traits from stockton-levels + +use hal::{ + buffer::SubRange, + command::{ClearColor, ClearDepthStencil, ClearValue, RenderAttachmentInfo, SubpassContents}, + format::{Aspects, Format}, + image::{ + Filter, FramebufferAttachment, Layout, SubresourceRange, Usage, ViewCapabilities, WrapMode, + }, + pass::{Attachment, AttachmentLoadOp, AttachmentOps, AttachmentStoreOp}, + pso::{ + BlendDesc, BlendOp, BlendState, ColorBlendDesc, ColorMask, Comparison, DepthStencilDesc, + DepthTest, Face, Factor, FrontFace, InputAssemblerDesc, LogicOp, PolygonMode, Primitive, + Rasterizer, ShaderStageFlags, State, VertexInputRate, + }, +}; +use legion::{Entity, IntoQuery}; +use shaderc::ShaderKind; +use stockton_levels::{ + features::MinRenderFeatures, + parts::{data::Geometry, IsFace}, +}; +use stockton_render::{ + buffers::{ + DedicatedLoadedImage, DrawBuffers, ModifiableBuffer, INITIAL_INDEX_SIZE, INITIAL_VERT_SIZE, + }, + builders::{ + CompletePipeline, PipelineSpecBuilder, RenderpassSpec, ShaderDesc, VertexBufferSpec, + VertexPrimitiveAssemblerSpec, + }, + context::RenderingContext, + draw_passes::{util::TargetSpecificResources, DrawPass, IntoDrawPass}, + error::{EnvironmentError, LevelError, LockPoisoned}, + queue_negotiator::QueueNegotiator, + texture::{resolver::TextureResolver, TexLoadQueue, TextureLoadConfig, TextureRepo}, + types::*, +}; +use stockton_types::{ + components::{CameraSettings, CameraVPMatrix, Transform}, + *, +}; + +use std::{ + array::IntoIter, + convert::TryInto, + iter::{empty, once}, + marker::PhantomData, + sync::{Arc, RwLock}, +}; + +use anyhow::{Context, Result}; + +/// The Vertexes that go to the shader +#[derive(Debug, Clone, Copy)] +struct UvPoint(pub Vector3, pub i32, pub Vector2); + +/// Draw a level +pub struct LevelDrawPass<'a, M> { + pipeline: CompletePipeline, + repo: TextureRepo, + active_camera: Entity, + draw_buffers: DrawBuffers<'a, UvPoint>, + + framebuffers: TargetSpecificResources<FramebufferT>, + depth_buffers: TargetSpecificResources<DedicatedLoadedImage>, + + _d: PhantomData<M>, +} + +impl<'a, M> DrawPass for LevelDrawPass<'a, M> +where + M: for<'b> MinRenderFeatures<'b> + 'static, +{ + fn queue_draw( + &mut self, + session: &Session, + img_view: &ImageViewT, + cmd_buffer: &mut CommandBufferT, + ) -> anyhow::Result<()> { + // We might have loaded more textures + self.repo.process_responses(); + + // Make sure we update the vertex buffers after they're written to, but before they're read from. + self.draw_buffers + .vertex_buffer + .record_commit_cmds(cmd_buffer)?; + self.draw_buffers + .index_buffer + .record_commit_cmds(cmd_buffer)?; + + // Get level & camera + let mut query = <(&Transform, &CameraSettings, &CameraVPMatrix)>::query(); + let (camera_transform, camera_settings, camera_vp) = query + .get(&session.world, self.active_camera) + .context("Couldn't find camera components")?; + let map_lock: Arc<RwLock<M>> = session.resources.get::<Arc<RwLock<M>>>().unwrap().clone(); + let map = map_lock.read().map_err(|_| LockPoisoned::Map)?; + + // Get framebuffer and depth buffer + let fb = self.framebuffers.get_next(); + let db = self.depth_buffers.get_next(); + + unsafe { + cmd_buffer.begin_render_pass( + &self.pipeline.renderpass, + fb, + self.pipeline.render_area, + vec![ + RenderAttachmentInfo { + image_view: img_view, + clear_value: ClearValue { + color: ClearColor { + float32: [0.0, 0.0, 0.0, 1.0], + }, + }, + }, + RenderAttachmentInfo { + image_view: &*db.image_view, + clear_value: ClearValue { + depth_stencil: ClearDepthStencil { + depth: 1.0, + stencil: 0, + }, + }, + }, + ] + .into_iter(), + SubpassContents::Inline, + ); + cmd_buffer.bind_graphics_pipeline(&self.pipeline.pipeline); + + // VP Matrix + let vp = &*(camera_vp.vp_matrix.data.as_slice() as *const [f32] as *const [u32]); + + cmd_buffer.push_graphics_constants( + &self.pipeline.pipeline_layout, + ShaderStageFlags::VERTEX, + 0, + vp, + ); + + // Bind buffers + cmd_buffer.bind_vertex_buffers( + 0, + once(( + self.draw_buffers.vertex_buffer.get_buffer(), + SubRange { + offset: 0, + size: None, + }, + )), + ); + cmd_buffer.bind_index_buffer( + self.draw_buffers.index_buffer.get_buffer(), + SubRange { + offset: 0, + size: None, + }, + hal::IndexType::U16, + ); + } + + // Get visible faces + let mut faces = map.get_visible(camera_transform, camera_settings); + + // Iterate over faces, copying them in and drawing groups that use the same texture chunk all at once. + let face = faces.next(); + if let Some(face) = face { + let mut face = map.get_face(face).ok_or(LevelError::BadReference)?; + let mut current_chunk = face.texture_idx(&map) as usize / 8; + let mut chunk_start = 0; + + let mut curr_vert_idx: usize = 0; + let mut curr_idx_idx: usize = 0; + loop { + if current_chunk != face.texture_idx(&map) as usize / 8 { + // Last index was last of group, so draw it all if textures are loaded. + draw_or_queue( + current_chunk, + &mut self.repo, + cmd_buffer, + &*self.pipeline.pipeline_layout, + chunk_start as u32, + curr_idx_idx as u32, + )?; + + // Next group of same-chunked faces starts here. + chunk_start = curr_idx_idx; + current_chunk = face.texture_idx(&map) as usize / 8; + } + + match face.geometry(&map) { + Geometry::Vertices(v1, v2, v3) => { + for v in &[v1, v2, v3] { + let uvp = + UvPoint(v.position, face.texture_idx(&map).try_into()?, v.tex); + + self.draw_buffers.vertex_buffer[curr_vert_idx] = uvp; + curr_vert_idx += 1; + } + self.draw_buffers.index_buffer[curr_idx_idx] = ( + curr_vert_idx as u16 - 3, + curr_vert_idx as u16 - 2, + curr_vert_idx as u16 - 1, + ); + curr_idx_idx += 1; + } + } + + if curr_vert_idx >= INITIAL_VERT_SIZE.try_into()? + || curr_idx_idx >= INITIAL_INDEX_SIZE.try_into()? + { + println!("out of vertex buffer space!"); + break; + } + + match faces.next() { + Some(x) => face = map.get_face(x).ok_or(LevelError::BadReference)?, + None => break, + }; + } + + // Draw the final group of chunks + draw_or_queue( + current_chunk, + &mut self.repo, + cmd_buffer, + &*self.pipeline.pipeline_layout, + chunk_start as u32, + curr_idx_idx as u32, + )?; + } + + unsafe { + cmd_buffer.end_render_pass(); + } + + Ok(()) + } + + fn deactivate(self, context: &mut RenderingContext) -> Result<()> { + unsafe { + let mut device = context.device().write().map_err(|_| LockPoisoned::Device)?; + self.pipeline.deactivate(&mut device); + self.draw_buffers.deactivate(&mut device); + for fb in self.framebuffers.dissolve() { + device.destroy_framebuffer(fb); + } + for db in self.depth_buffers.dissolve() { + db.deactivate(&mut device); + } + } + self.repo.deactivate(context.device()); + + Ok(()) + } + + fn handle_surface_change( + &mut self, + _session: &Session, + _context: &mut RenderingContext, + ) -> Result<()> { + todo!() + } +} + +pub struct LevelDrawPassConfig<R> { + pub active_camera: Entity, + pub tex_resolver: R, +} + +impl<'a, M, R> IntoDrawPass<LevelDrawPass<'a, M>> for LevelDrawPassConfig<R> +where + M: for<'b> MinRenderFeatures<'b> + 'static, + R: TextureResolver + Send + Sync + 'static, +{ + fn init( + self, + _session: &mut Session, + context: &mut RenderingContext, + ) -> Result<LevelDrawPass<'a, M>> { + let spec = PipelineSpecBuilder::default() + .rasterizer(Rasterizer { + polygon_mode: PolygonMode::Fill, + cull_face: Face::BACK, + front_face: FrontFace::CounterClockwise, + depth_clamping: false, + depth_bias: None, + conservative: true, + line_width: State::Static(1.0), + }) + .depth_stencil(DepthStencilDesc { + depth: Some(DepthTest { + fun: Comparison::Less, + write: true, + }), + depth_bounds: false, + stencil: None, + }) + .blender(BlendDesc { + logic_op: Some(LogicOp::Copy), + targets: vec![ColorBlendDesc { + mask: ColorMask::ALL, + blend: Some(BlendState { + color: BlendOp::Add { + src: Factor::One, + dst: Factor::Zero, + }, + alpha: BlendOp::Add { + src: Factor::One, + dst: Factor::Zero, + }, + }), + }], + }) + .primitive_assembler(VertexPrimitiveAssemblerSpec::with_buffers( + InputAssemblerDesc::new(Primitive::TriangleList), + vec![VertexBufferSpec { + attributes: vec![Format::Rgb32Sfloat, Format::R32Sint, Format::Rg32Sfloat], + rate: VertexInputRate::Vertex, + }], + )) + .shader_vertex(ShaderDesc { + source: include_str!("./data/3d.vert").to_string(), + entry: "main".to_string(), + kind: ShaderKind::Vertex, + }) + .shader_fragment(ShaderDesc { + source: include_str!("./data/3d.frag").to_string(), + entry: "main".to_string(), + kind: ShaderKind::Fragment, + }) + .push_constants(vec![(ShaderStageFlags::VERTEX, 0..64)]) + .renderpass(RenderpassSpec { + colors: vec![Attachment { + format: Some(context.target_chain().properties().format), + samples: 1, + ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::Store), + stencil_ops: AttachmentOps::new( + AttachmentLoadOp::Clear, + AttachmentStoreOp::DontCare, + ), + layouts: Layout::ColorAttachmentOptimal..Layout::ColorAttachmentOptimal, + }], + depth: Some(Attachment { + format: Some(context.target_chain().properties().depth_format), + samples: 1, + ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::DontCare), + stencil_ops: AttachmentOps::new( + AttachmentLoadOp::DontCare, + AttachmentStoreOp::DontCare, + ), + layouts: Layout::Undefined..Layout::DepthStencilAttachmentOptimal, + }), + inputs: vec![], + resolves: vec![], + preserves: vec![], + }) + .build() + .context("Error building pipeline")?; + + let repo = TextureRepo::new( + context.device().clone(), + context + .queue_negotiator_mut() + .family::<TexLoadQueue>() + .ok_or(EnvironmentError::NoSuitableFamilies) + .context("Error finding texture queue")?, + context + .queue_negotiator_mut() + .get_queue::<TexLoadQueue>() + .ok_or(EnvironmentError::NoQueues) + .context("Error finding texture queue")?, + context.adapter(), + TextureLoadConfig { + resolver: self.tex_resolver, + filter: Filter::Linear, + wrap_mode: WrapMode::Tile, + }, + ) + .context("Error creating texture repo")?; + + let (draw_buffers, pipeline, framebuffers, depth_buffers) = { + let mut device = context.device().write().map_err(|_| LockPoisoned::Device)?; + let draw_buffers = DrawBuffers::new(&mut device, context.adapter()) + .context("Error creating draw buffers")?; + let pipeline = spec + .build( + &mut device, + context.target_chain().properties().extent, + context.target_chain().properties(), + once(&*repo.get_ds_layout()?), + ) + .context("Error building pipeline")?; + + let fat = context.target_chain().properties().framebuffer_attachment(); + let dat = FramebufferAttachment { + usage: Usage::DEPTH_STENCIL_ATTACHMENT, + format: context.target_chain().properties().depth_format, + view_caps: ViewCapabilities::empty(), + }; + let framebuffers = TargetSpecificResources::new( + || unsafe { + Ok(device.create_framebuffer( + &pipeline.renderpass, + IntoIter::new([fat.clone(), dat.clone()]), + context.target_chain().properties().extent, + )?) + }, + context.target_chain().properties().image_count as usize, + )?; + let depth_buffers = TargetSpecificResources::new( + || { + DedicatedLoadedImage::new( + &mut device, + context.adapter(), + context.target_chain().properties().depth_format, + Usage::DEPTH_STENCIL_ATTACHMENT, + SubresourceRange { + aspects: Aspects::DEPTH, + level_start: 0, + level_count: Some(1), + layer_start: 0, + layer_count: Some(1), + }, + context.target_chain().properties().extent.width as usize, + context.target_chain().properties().extent.height as usize, + ) + .context("Error creating depth buffer") + }, + context.target_chain().properties().image_count as usize, + )?; + + (draw_buffers, pipeline, framebuffers, depth_buffers) + }; + + Ok(LevelDrawPass { + pipeline, + repo, + draw_buffers, + active_camera: self.active_camera, + _d: PhantomData, + framebuffers, + depth_buffers, + }) + } + + fn find_aux_queues<'c>( + adapter: &'c Adapter, + queue_negotiator: &mut QueueNegotiator, + ) -> Result<Vec<(&'c QueueFamilyT, Vec<f32>)>> { + queue_negotiator.find(adapter, &TexLoadQueue)?; + + Ok(vec![queue_negotiator + .family_spec::<TexLoadQueue>(&adapter.queue_families, 1) + .ok_or(EnvironmentError::NoSuitableFamilies)?]) + } +} + +fn draw_or_queue( + current_chunk: usize, + tex_repo: &mut TextureRepo, + cmd_buffer: &mut CommandBufferT, + pipeline_layout: &PipelineLayoutT, + chunk_start: u32, + curr_idx_idx: u32, +) -> Result<()> { + if let Some(ds) = tex_repo.attempt_get_descriptor_set(current_chunk) { + unsafe { + cmd_buffer.bind_graphics_descriptor_sets(pipeline_layout, 0, once(ds), empty()); + cmd_buffer.draw_indexed(chunk_start * 3..(curr_idx_idx * 3) + 1, 0, 0..1); + } + } else { + tex_repo.queue_load(current_chunk)? + } + + Ok(()) +} diff --git a/stockton-passes/src/lib.rs b/stockton-passes/src/lib.rs new file mode 100644 index 0000000..e3e5bf8 --- /dev/null +++ b/stockton-passes/src/lib.rs @@ -0,0 +1,9 @@ +#[macro_use] +extern crate legion; +extern crate gfx_hal as hal; +extern crate nalgebra_glm as na; + +pub mod camera; +pub mod level; +pub mod ui; +pub mod window; diff --git a/stockton-passes/src/ui.rs b/stockton-passes/src/ui.rs new file mode 100644 index 0000000..4b4e1c2 --- /dev/null +++ b/stockton-passes/src/ui.rs @@ -0,0 +1,398 @@ +//! Minimal code for drawing any level, based on traits from stockton-levels + +use egui::{ClippedMesh, TextureId}; +use hal::{ + buffer::SubRange, + command::{ClearColor, ClearValue, RenderAttachmentInfo, SubpassContents}, + format::Format, + image::Layout, + pass::{Attachment, AttachmentLoadOp, AttachmentOps, AttachmentStoreOp}, + pso::{ + BlendDesc, BlendOp, BlendState, ColorBlendDesc, ColorMask, DepthStencilDesc, Face, Factor, + FrontFace, InputAssemblerDesc, LogicOp, PolygonMode, Primitive, Rasterizer, Rect, + ShaderStageFlags, State, VertexInputRate, + }, +}; +use shaderc::ShaderKind; +use stockton_render::{ + buffers::{DrawBuffers, ModifiableBuffer}, + builders::{ + CompletePipeline, PipelineSpecBuilder, RenderpassSpec, ShaderDesc, VertexBufferSpec, + VertexPrimitiveAssemblerSpec, + }, + context::RenderingContext, + draw_passes::{util::TargetSpecificResources, DrawPass, IntoDrawPass}, + error::{EnvironmentError, LockPoisoned}, + queue_negotiator::QueueNegotiator, + texture::{TexLoadQueue, TextureLoadConfig, TextureRepo}, + types::*, +}; +use stockton_types::{Session, Vector2}; + +use std::{ + array::IntoIter, + convert::TryInto, + iter::{empty, once}, +}; + +use anyhow::{anyhow, Context, Result}; +use egui::{CtxRef, Texture}; +use std::sync::Arc; +use stockton_render::texture::{resolver::TextureResolver, LoadableImage}; + +use crate::window::UiState; + +#[derive(Debug)] +pub struct UiPoint(pub Vector2, pub Vector2, pub [f32; 4]); + +/// Draw a Ui object +pub struct UiDrawPass<'a> { + pipeline: CompletePipeline, + repo: TextureRepo, + draw_buffers: DrawBuffers<'a, UiPoint>, + + framebuffers: TargetSpecificResources<FramebufferT>, +} + +impl<'a> DrawPass for UiDrawPass<'a> { + fn queue_draw( + &mut self, + session: &Session, + img_view: &ImageViewT, + cmd_buffer: &mut CommandBufferT, + ) -> anyhow::Result<()> { + // We might have loaded more textures + self.repo.process_responses(); + + // Make sure we update the vertex buffers after they're written to, but before they're read from. + self.draw_buffers + .vertex_buffer + .record_commit_cmds(cmd_buffer)?; + self.draw_buffers + .index_buffer + .record_commit_cmds(cmd_buffer)?; + + // Get level & camera + let ui: &mut UiState = &mut session.resources.get_mut::<UiState>().unwrap(); + + // Get framebuffer and depth buffer + let fb = self.framebuffers.get_next(); + unsafe { + cmd_buffer.begin_render_pass( + &self.pipeline.renderpass, + fb, + self.pipeline.render_area, + vec![RenderAttachmentInfo { + image_view: img_view, + clear_value: ClearValue { + color: ClearColor { + float32: [0.0, 0.0, 0.0, 1.0], + }, + }, + }] + .into_iter(), + SubpassContents::Inline, + ); + cmd_buffer.bind_graphics_pipeline(&self.pipeline.pipeline); + + // Bind buffers + cmd_buffer.bind_vertex_buffers( + 0, + once(( + self.draw_buffers.vertex_buffer.get_buffer(), + SubRange { + offset: 0, + size: None, + }, + )), + ); + cmd_buffer.bind_index_buffer( + self.draw_buffers.index_buffer.get_buffer(), + SubRange { + offset: 0, + size: None, + }, + hal::IndexType::U16, + ); + } + + let (_out, shapes) = ui.end_frame(); + let screen = ui.dimensions().ok_or(anyhow!("UI not set up properly."))?; + let shapes = ui.ctx().tessellate(shapes); + + for ClippedMesh(rect, tris) in shapes.iter() { + assert!(tris.texture_id == TextureId::Egui); + + // Copy triangles/indicies + for i in (0..tris.indices.len()).step_by(3) { + self.draw_buffers.index_buffer[i / 3] = ( + tris.indices[i].try_into()?, + tris.indices[i + 1].try_into()?, + tris.indices[i + 2].try_into()?, + ); + } + for (i, vertex) in tris.vertices.iter().enumerate() { + self.draw_buffers.vertex_buffer[i] = UiPoint( + Vector2::new(vertex.pos.x, vertex.pos.y), + Vector2::new(vertex.uv.x, vertex.uv.y), + [ + vertex.color.r() as f32 / 255.0, + vertex.color.g() as f32 / 255.0, + vertex.color.b() as f32 / 255.0, + vertex.color.a() as f32 / 255.0, + ], + ); + } + // TODO: *Properly* deal with textures + if let Some(ds) = self.repo.attempt_get_descriptor_set(0) { + unsafe { + cmd_buffer.push_graphics_constants( + &self.pipeline.pipeline_layout, + ShaderStageFlags::VERTEX, + 0, + &[screen.x.to_bits(), screen.y.to_bits()], + ); + + cmd_buffer.set_scissors( + 0, + IntoIter::new([Rect { + x: rect.min.x as i16, + y: rect.min.y as i16, + w: rect.width() as i16, + h: rect.height() as i16, + }]), + ); + cmd_buffer.bind_graphics_descriptor_sets( + &self.pipeline.pipeline_layout, + 0, + IntoIter::new([ds]), + empty(), + ); + // Call draw + cmd_buffer.draw_indexed(0..tris.indices.len() as u32, 0, 0..1); + } + } else { + self.repo.queue_load(0)?; + } + } + + unsafe { + cmd_buffer.end_render_pass(); + } + + Ok(()) + } + + fn deactivate(self, context: &mut RenderingContext) -> Result<()> { + unsafe { + let mut device = context.device().write().map_err(|_| LockPoisoned::Device)?; + self.pipeline.deactivate(&mut device); + self.draw_buffers.deactivate(&mut device); + for fb in self.framebuffers.dissolve() { + device.destroy_framebuffer(fb); + } + } + self.repo.deactivate(context.device()); + + Ok(()) + } + + fn handle_surface_change( + &mut self, + _session: &Session, + _context: &mut RenderingContext, + ) -> Result<()> { + todo!() + } +} + +impl<'a> IntoDrawPass<UiDrawPass<'a>> for () { + fn init(self, session: &mut Session, context: &mut RenderingContext) -> Result<UiDrawPass<'a>> { + let spec = PipelineSpecBuilder::default() + .rasterizer(Rasterizer { + polygon_mode: PolygonMode::Fill, + cull_face: Face::NONE, + front_face: FrontFace::CounterClockwise, + depth_clamping: false, + depth_bias: None, + conservative: true, + line_width: State::Static(1.0), + }) + .depth_stencil(DepthStencilDesc { + depth: None, + depth_bounds: false, + stencil: None, + }) + .blender(BlendDesc { + logic_op: Some(LogicOp::Copy), + targets: vec![ColorBlendDesc { + mask: ColorMask::ALL, + blend: Some(BlendState { + color: BlendOp::Add { + src: Factor::SrcAlpha, + dst: Factor::OneMinusSrcAlpha, + }, + alpha: BlendOp::Add { + src: Factor::SrcAlpha, + dst: Factor::OneMinusSrcAlpha, + }, + }), + }], + }) + .primitive_assembler(VertexPrimitiveAssemblerSpec::with_buffers( + InputAssemblerDesc::new(Primitive::TriangleList), + vec![VertexBufferSpec { + attributes: vec![Format::Rg32Sfloat, Format::Rg32Sfloat, Format::Rgba32Sfloat], + rate: VertexInputRate::Vertex, + }], + )) + .shader_vertex(ShaderDesc { + source: include_str!("./data/ui.vert").to_string(), + entry: "main".to_string(), + kind: ShaderKind::Vertex, + }) + .shader_fragment(ShaderDesc { + source: include_str!("./data/ui.frag").to_string(), + entry: "main".to_string(), + kind: ShaderKind::Fragment, + }) + .push_constants(vec![(ShaderStageFlags::VERTEX, 0..8)]) + .renderpass(RenderpassSpec { + colors: vec![Attachment { + format: Some(context.target_chain().properties().format), + samples: 1, + ops: AttachmentOps::new(AttachmentLoadOp::Load, AttachmentStoreOp::Store), + stencil_ops: AttachmentOps::new( + AttachmentLoadOp::DontCare, + AttachmentStoreOp::DontCare, + ), + layouts: Layout::ColorAttachmentOptimal..Layout::ColorAttachmentOptimal, + }], + depth: None, + inputs: vec![], + resolves: vec![], + preserves: vec![], + }) + .dynamic_scissor(true) + .build() + .context("Error building pipeline")?; + + let ui: &mut UiState = &mut session.resources.get_mut::<UiState>().unwrap(); + let repo = TextureRepo::new( + context.device().clone(), + context + .queue_negotiator_mut() + .family::<TexLoadQueue>() + .ok_or(EnvironmentError::NoSuitableFamilies) + .context("Error finding texture queue")?, + context + .queue_negotiator_mut() + .get_queue::<TexLoadQueue>() + .ok_or(EnvironmentError::NoQueues) + .context("Error finding texture queue")?, + context.adapter(), + TextureLoadConfig { + resolver: UiTextures::new(ui.ctx().clone()), + filter: hal::image::Filter::Linear, + wrap_mode: hal::image::WrapMode::Clamp, + }, + ) + .context("Error creating texture repo")?; + + let (draw_buffers, pipeline, framebuffers) = { + let mut device = context.device().write().map_err(|_| LockPoisoned::Device)?; + let draw_buffers = DrawBuffers::new(&mut device, context.adapter()) + .context("Error creating draw buffers")?; + let pipeline = spec + .build( + &mut device, + context.target_chain().properties().extent, + context.target_chain().properties(), + once(&*repo.get_ds_layout()?), + ) + .context("Error building pipeline")?; + + let fat = context.target_chain().properties().framebuffer_attachment(); + let framebuffers = TargetSpecificResources::new( + || unsafe { + Ok(device.create_framebuffer( + &pipeline.renderpass, + IntoIter::new([fat.clone()]), + context.target_chain().properties().extent, + )?) + }, + context.target_chain().properties().image_count as usize, + )?; + (draw_buffers, pipeline, framebuffers) + }; + + Ok(UiDrawPass { + pipeline, + repo, + draw_buffers, + framebuffers, + }) + } + + fn find_aux_queues<'c>( + adapter: &'c Adapter, + queue_negotiator: &mut QueueNegotiator, + ) -> Result<Vec<(&'c QueueFamilyT, Vec<f32>)>> { + queue_negotiator.find(adapter, &TexLoadQueue)?; + + Ok(vec![queue_negotiator + .family_spec::<TexLoadQueue>(&adapter.queue_families, 1) + .ok_or(EnvironmentError::NoSuitableFamilies)?]) + } +} + +pub struct UiTexture(Arc<Texture>); + +pub struct UiTextures { + ctx: CtxRef, +} + +impl TextureResolver for UiTextures { + type Image = UiTexture; + fn resolve(&mut self, tex: u32) -> Option<Self::Image> { + if tex == 0 { + Some(UiTexture(self.ctx.texture())) + } else { + None + } + } +} + +impl UiTextures { + pub fn new(ctx: CtxRef) -> Self { + UiTextures { ctx } + } +} + +impl LoadableImage for UiTexture { + fn width(&self) -> u32 { + self.0.width as u32 + } + fn height(&self) -> u32 { + self.0.height as u32 + } + fn copy_row(&self, y: u32, ptr: *mut u8) { + let row_size = self.0.width as u32; + let pixels = &self.0.pixels[(y * row_size) as usize..((y + 1) * row_size) as usize]; + + for (i, x) in pixels.iter().enumerate() { + unsafe { + *ptr.offset(i as isize * 4) = 255; + *ptr.offset((i as isize * 4) + 1) = 255; + *ptr.offset((i as isize * 4) + 2) = 255; + *ptr.offset((i as isize * 4) + 3) = *x; + } + } + } + + unsafe fn copy_into(&self, ptr: *mut u8, row_size: usize) { + for y in 0..self.height() { + self.copy_row(y, ptr.offset((row_size * y as usize).try_into().unwrap())); + } + } +} diff --git a/stockton-passes/src/window.rs b/stockton-passes/src/window.rs new file mode 100644 index 0000000..c806fa0 --- /dev/null +++ b/stockton-passes/src/window.rs @@ -0,0 +1,259 @@ +use std::sync::mpsc::channel; +use std::sync::mpsc::Receiver; +use std::sync::mpsc::Sender; +use std::sync::Arc; +use std::sync::RwLock; + +use egui::{Modifiers, Rect, Vec2}; +use legion::systems::Runnable; +use log::debug; +use stockton_render::{draw_passes::DrawPass, Renderer}; + +use egui::{CtxRef, Event, Output, Pos2, RawInput}; +use epaint::ClippedShape; +use stockton_input::{Action as KBAction, InputManager, Mouse}; + +use winit::event::{ + ElementState, Event as WinitEvent, MouseButton, WindowEvent as WinitWindowEvent, +}; +use winit::event_loop::ControlFlow; + +#[derive(Debug, Clone, Copy)] +pub enum WindowEvent { + SizeChanged(u32, u32), + CloseRequested, + KeyboardAction(KBAction), + MouseAction(KBAction), + MouseMoved(f32, f32), + MouseLeft, +} + +impl WindowEvent { + pub fn from(winit_event: &WinitEvent<()>) -> Option<WindowEvent> { + // TODO + match winit_event { + WinitEvent::WindowEvent { event, .. } => match event { + WinitWindowEvent::CloseRequested => Some(WindowEvent::CloseRequested), + WinitWindowEvent::Resized(size) => { + Some(WindowEvent::SizeChanged(size.width, size.height)) + } + WinitWindowEvent::KeyboardInput { input, .. } => match input.state { + ElementState::Pressed => Some(WindowEvent::KeyboardAction(KBAction::KeyPress( + input.scancode, + ))), + ElementState::Released => Some(WindowEvent::KeyboardAction( + KBAction::KeyRelease(input.scancode), + )), + }, + WinitWindowEvent::CursorMoved { position, .. } => Some(WindowEvent::MouseMoved( + position.x as f32, + position.y as f32, + )), + WinitWindowEvent::CursorLeft { .. } => Some(WindowEvent::MouseLeft), + WinitWindowEvent::MouseInput { button, state, .. } => { + let mb: stockton_input::MouseButton = match button { + MouseButton::Left => stockton_input::MouseButton::Left, + MouseButton::Right => stockton_input::MouseButton::Right, + MouseButton::Middle => stockton_input::MouseButton::Middle, + MouseButton::Other(x) => stockton_input::MouseButton::Other(*x), + }; + + match state { + ElementState::Pressed => { + Some(WindowEvent::MouseAction(KBAction::MousePress(mb))) + } + ElementState::Released => { + Some(WindowEvent::MouseAction(KBAction::MouseRelease(mb))) + } + } + } + _ => None, + }, + _ => None, + } + } +} + +pub struct UiState { + ctx: CtxRef, + raw_input: RawInput, + frame_active: bool, + + modifiers: Modifiers, + pointer_pos: Pos2, +} + +impl UiState { + pub fn new() -> Self { + UiState { + ctx: CtxRef::default(), + raw_input: RawInput::default(), + frame_active: false, + modifiers: Default::default(), + pointer_pos: Pos2::new(0.0, 0.0), + } + } + + pub fn populate_initial_state<'a, T: DrawPass>(&mut self, renderer: &Renderer<T>) { + let props = &renderer.context().target_chain().properties(); + self.set_dimensions(props.extent.width, props.extent.height); + self.set_pixels_per_point(Some(renderer.context().pixels_per_point())); + debug!("{:?}", self.raw_input); + } + + #[inline] + pub fn ctx(&mut self) -> &CtxRef { + if !self.frame_active { + self.begin_frame() + } + &self.ctx + } + + #[inline] + fn begin_frame(&mut self) { + #[allow(deprecated)] + let new_raw_input = RawInput { + scroll_delta: Vec2::new(0.0, 0.0), + zoom_delta: 0.0, + screen_size: self.raw_input.screen_size, + screen_rect: self.raw_input.screen_rect, + pixels_per_point: self.raw_input.pixels_per_point, + time: self.raw_input.time, + predicted_dt: self.raw_input.predicted_dt, + modifiers: self.modifiers, + events: Vec::new(), + }; + self.ctx.begin_frame(self.raw_input.take()); + self.raw_input = new_raw_input; + self.frame_active = true; + } + + #[inline] + pub(crate) fn end_frame(&mut self) -> (Output, Vec<ClippedShape>) { + self.frame_active = false; + self.ctx.end_frame() + } + + #[inline] + pub fn dimensions(&self) -> Option<egui::math::Vec2> { + Some(self.raw_input.screen_rect?.size()) + } + + fn set_mouse_pos(&mut self, x: f32, y: f32) { + self.raw_input + .events + .push(Event::PointerMoved(Pos2::new(x, y))); + + self.pointer_pos = Pos2::new(x, y); + } + + fn set_mouse_left(&mut self) { + self.raw_input.events.push(Event::PointerGone); + } + + fn set_dimensions(&mut self, w: u32, h: u32) { + self.raw_input.screen_rect = + Some(Rect::from_x_y_ranges(0.0..=(w as f32), 0.0..=(h as f32))); + } + fn set_pixels_per_point(&mut self, ppp: Option<f32>) { + debug!("Using {:?} pixels per point", ppp); + self.raw_input.pixels_per_point = ppp; + } + + fn handle_action(&mut self, action: KBAction) { + // TODO + match action { + KBAction::MousePress(stockton_input::MouseButton::Left) => { + self.raw_input.events.push(Event::PointerButton { + pos: self.pointer_pos, + button: egui::PointerButton::Primary, + pressed: true, + modifiers: self.modifiers, + }); + } + _ => (), + } + } +} + +pub struct WindowFlow { + window_events: Receiver<WindowEvent>, + update_control_flow: Arc<RwLock<ControlFlow>>, +} + +impl WindowFlow { + pub fn new(update_control_flow: Arc<RwLock<ControlFlow>>) -> (Self, Sender<WindowEvent>) { + let (tx, rx) = channel(); + ( + Self { + window_events: rx, + update_control_flow, + }, + tx, + ) + } +} + +#[system] +/// A system to process the window events sent to renderer by the winit event loop. +pub fn _process_window_events<T: 'static + InputManager, DP: 'static + DrawPass>( + #[resource] window_channel: &mut WindowFlow, + #[resource] manager: &mut T, + #[resource] mouse: &mut Mouse, + #[resource] ui_state: &mut UiState, + #[state] actions_buf: &mut Vec<KBAction>, +) { + let mut actions_buf_cursor = 0; + let mut mouse_delta = mouse.abs; + + while let Ok(event) = window_channel.window_events.try_recv() { + match event { + WindowEvent::SizeChanged(w, h) => { + ui_state.set_dimensions(w, h); + } + WindowEvent::CloseRequested => { + let mut flow = window_channel.update_control_flow.write().unwrap(); + // TODO: Let everything know this is our last frame + *flow = ControlFlow::Exit; + } + WindowEvent::KeyboardAction(action) => { + if actions_buf_cursor >= actions_buf.len() { + actions_buf.push(action); + } else { + actions_buf[actions_buf_cursor] = action; + } + actions_buf_cursor += 1; + + ui_state.handle_action(action); + } + WindowEvent::MouseMoved(x, y) => { + mouse_delta.x = x; + mouse_delta.y = y; + + ui_state.set_mouse_pos(x, y); + } + WindowEvent::MouseLeft => { + ui_state.set_mouse_left(); + } + WindowEvent::MouseAction(action) => { + if actions_buf_cursor >= actions_buf.len() { + actions_buf.push(action); + } else { + actions_buf[actions_buf_cursor] = action; + } + actions_buf_cursor += 1; + + ui_state.handle_action(action); + } + }; + } + + mouse.handle_frame(mouse_delta); + + manager.handle_frame(&actions_buf[0..actions_buf_cursor]); +} + +pub fn process_window_events_system<T: 'static + InputManager, DP: 'static + DrawPass>( +) -> impl Runnable { + _process_window_events_system::<T, DP>(Vec::with_capacity(4)) +} |