aboutsummaryrefslogtreecommitdiff
path: root/stockton-passes/src
diff options
context:
space:
mode:
Diffstat (limited to 'stockton-passes/src')
-rw-r--r--stockton-passes/src/camera.rs61
-rw-r--r--stockton-passes/src/data/3d.frag15
-rw-r--r--stockton-passes/src/data/3d.vert23
-rw-r--r--stockton-passes/src/data/ui.frag15
-rw-r--r--stockton-passes/src/data/ui.vert37
-rw-r--r--stockton-passes/src/level.rs478
-rw-r--r--stockton-passes/src/lib.rs9
-rw-r--r--stockton-passes/src/ui.rs398
-rw-r--r--stockton-passes/src/window.rs259
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))
+}