tirbofish/dropbear · diff
wip: new library called kino-gui doesnt work (yet), thats tomorrow's problem. Unverified
@@ -1,3 +1,3 @@ [target.x86_64-unknown-linux-gnu] linker = "clang" -rustflags = ["-C", "link-arg=-fuse-ld=mold"] +rustflags = ["-C", "link-arg=-fuse-ld=mold", "-C", "relocation-model=pic"] @@ -11,20 +11,19 @@ If you might have not realised, all the crates/projects names are after Australi ## Projects -- [dropbear-engine](https://github.com/tirbofish/dropbear/tree/main/dropbear-engine) is the rendering engine that uses wgpu and the main name of the project. -- [dropbear-shader](https://github.com/tirbofish/dropbear/tree/main/dropbear-shader) contains WESL shaders for users to import -- [eucalyptus-editor](https://github.com/tirbofish/dropbear/tree/main/eucalyptus-editor) is the visual editor used to create games visually, taking inspiration from Unity, Unreal, Roblox Studio and other engines. -- [eucalyptus-core](https://github.com/tirbofish/dropbear/tree/main/eucalyptus-core) is the library used by both `redback-runtime` and `eucalyptus-editor` to share configs and metadata between each other. -- [redback-runtime](https://github.com/tirbofish/dropbear/tree/main/redback-runtime) is the runtime used to load .eupak files and run the game loaded on them. +- [dropbear-engine](https://github.com/tirbofish/dropbear/tree/main/crates/dropbear-engine) is the rendering engine that uses wgpu and the main name of the project. +- [eucalyptus-editor](https://github.com/tirbofish/dropbear/tree/main/crates/eucalyptus-editor) is the visual editor used to create games visually, taking inspiration from Unity, Unreal, Roblox Studio and other engines. +- [eucalyptus-core](https://github.com/tirbofish/dropbear/tree/main/crates/eucalyptus-core) is the library used by both `redback-runtime` and `eucalyptus-editor` to share configs and metadata between each other. +- [redback-runtime](https://github.com/tirbofish/dropbear/tree/main/crates/redback-runtime) is the runtime used to load .eupak files and run the game loaded on them. +- [magna-carta](https://github.com/tirbofish/dropbear/tree/main/crates/magna-carta) is a rust library used to generate compile-time Kotlin/Native and Kotlin/JVM metadata for searching. +- [kino-gui](https://github.com/tirbofish/dropbear/tree/main/crates/kino-gui) is a wgpu primary UI renderer used for displaying shapes, text, and images in a simple immediate-mode way. [//]: # (- [eucalyptus-sdk](https://github.com/tirbofish/dropbear/tree/main/eucalyptus-sdk) is used to develop plugins to be used with the `eucalyptus-editor`) ### Related Projects -- [magna-carta](https://github.com/tirbofish/dropbear/tree/main/magna-carta) is a rust library used to generate compile-time Kotlin/Native and Kotlin/JVM metadata for searching. -- [magna-carta-plugin](https://github.com/tirbofish/dropbear/tree/main/magna-carta-plugin) is a Gradle plugin for generating metadata during compile time with the help of the magna-carta cli tool. - [dropbear_future-queue](https://github.com/tirbofish/dropbear/tree/main/dropbear_future-queue) is a handy library for dealing with async in a sync context -- [model_to_image](https://github.com/tirbofish/model_to_image) is a library used to generate thumbnails and images from a 3D model with the help of `russimp-ng` and a custom made rasteriser. _(very crude but usable)_ +- [model_to_image](https://github.com/tirbofish/model_to_image) is a library used to generate thumbnails and images from a 3D model with the help of `russimp-ng` and a custom made rasterizer. _(very crude but usable)_ ## Build @@ -327,11 +327,14 @@ Hardware: ], }); + let device = Arc::new(device); + let queue = Arc::new(queue); + let result = Self { surface: Arc::new(surface), surface_format: Texture::TEXTURE_FORMAT, - device: Arc::new(device), - queue: Arc::new(queue), + device, + queue, config, is_surface_configured, depth_texture, @@ -13,6 +13,7 @@ crate-type = ["rlib", "dylib"] dropbear-traits = { path = "../dropbear-traits" } dropbear-macro = { path = "../dropbear-macro" } magna-carta = { path = "../magna-carta" } +kino-gui = { path = "../kino-gui" } anyhow.workspace = true postcard.workspace = true @@ -1,88 +1,68 @@ pub mod rect; -use egui::{Rect, Response, Sense}; use once_cell::sync::Lazy; use parking_lot::Mutex; -use crate::utils::hashmap::StaleTracker; +use kino_gui::prelude::*; +use std::collections::HashMap; pub static UI_COMMAND_BUFFER: Lazy<UiContext> = Lazy::new(|| UiContext::new()); +#[derive(Clone, Copy, Debug)] +pub struct UiResponse { + pub id: u64, + pub was_clicked: bool, +} + +impl UiResponse { + pub fn clicked(&self) -> bool { + self.was_clicked + } +} + pub enum UiCommand { Rect { id: u64, - initial: (f32, f32), - size: (f32, f32), + initial: Vector2, + size: Size, corner_radius: f32, - stroke: egui::Stroke, - fill: egui::Color32, - stroke_kind: egui::StrokeKind, + stroke: f32, + fill: Colour, + stroke_kind: String, }, Circle { id: u64, - center_x: f64, - center_y: f64, - radius: f64, + center: Vector2, + radius: f32, + fill: Colour, + stroke: f32, }, } pub struct UiContext { - command_buffer: Mutex<Vec<UiCommand>>, - currently_rendering: Mutex<StaleTracker<u64, Response>>, + commands: Mutex<Vec<UiCommand>>, + pub currently_rendering: Mutex<HashMap<u64, UiResponse>>, } impl UiContext { pub fn new() -> Self { Self { - command_buffer: Mutex::new(Vec::new()), - currently_rendering: Mutex::new(StaleTracker::new()), + commands: Mutex::new(Vec::new()), + currently_rendering: Mutex::new(HashMap::new()), } } pub fn push(&self, command: UiCommand) { - self.command_buffer.lock().push(command); + self.commands.lock().push(command); } -} - -pub fn poll(ui: &mut egui::Ui) -> anyhow::Result<()> { - let mut buffer = UI_COMMAND_BUFFER.command_buffer.lock(); - let mut rendering = UI_COMMAND_BUFFER.currently_rendering.lock(); - rendering.tick(); - for cmd in buffer.drain(..) { - match cmd { - UiCommand::Rect { - id, - initial, - size, - corner_radius, - stroke, - fill, - stroke_kind - } => { - let (resp, painter) = ui.allocate_painter(size.into(), Sense::hover()); - - painter.rect( - Rect { - min: initial.into(), - max: [initial.0 + size.0, initial.1 + size.1].into(), - }, - corner_radius, - fill, - stroke, - stroke_kind - ); - - rendering.insert(id, resp); - } - UiCommand::Circle { .. } => { - } - } + pub fn drain_commands(&self) -> Vec<UiCommand> { + self.commands.lock().drain(..).collect() } - // remove anything past 3 gen - rendering.remove_stale(3); - - Ok(()) + pub fn update_responses(&self, responses: HashMap<u64, UiResponse>) { + let mut current = self.currently_rendering.lock(); + *current = responses; + } } pub mod jni { @@ -91,10 +71,11 @@ pub mod jni { use jni::JNIEnv; use jni::sys::{jboolean, jlong}; use jni::objects::JObject; + use kino_gui::prelude::shapes::Rectangle; + use kino_gui::Widget; use crate::{convert_ptr}; use crate::scripting::jni::utils::FromJObject; use crate::ui::{UiCommand, UiContext}; - use crate::ui::rect::Rect; #[unsafe(no_mangle)] pub extern "system" fn Java_com_dropbear_ui_UINative_pushRect( @@ -105,22 +86,22 @@ pub mod jni { ) { let ui = convert_ptr!(ui_buffer_handle => UiContext); - let rect: Rect = match Rect::from_jobject(&mut env, &rect) { + let rect = match Rectangle::from_jobject(&mut env, &rect) { Ok(v) => v, Err(e) => { - let _ = env.throw_new("java/lang/RuntimeException", format!("Unable to convert Rectangle->Rect: {:?}", e)); + let _ = env.throw_new("java/lang/RuntimeException", format!("Unable to convert scripting::Rectangle->kino::Rectangle: {:?}", e)); return; } }; ui.push(UiCommand::Rect { - id: rect.id, - initial: rect.initial_pos, + id: rect.id().as_u64(), + initial: rect.initial, size: rect.size, - corner_radius: rect.corner_radius, - stroke: rect.stroke, - fill: rect.fill, - stroke_kind: rect.stroke_kind, + corner_radius: 0.0, // rect.corner_radius when available + stroke: 0.0, // rect.stroke when available + fill: rect.fill_colour, + stroke_kind: "Middle".to_string(), // rect.stroke_kind when available }); } @@ -131,7 +112,7 @@ pub mod jni { ui_buffer_handle: jlong, circle: JObject, ) { - let ui = convert_ptr!(ui_buffer_handle => UiContext); + let _ui = convert_ptr!(ui_buffer_handle => UiContext); // Extract Circle fields let id_obj = match env @@ -145,7 +126,7 @@ pub mod jni { } }; - let id = match env.call_method(&id_obj, "getId", "()J", &[]).and_then(|v| v.j()) { + let _id = match env.call_method(&id_obj, "getId", "()J", &[]).and_then(|v| v.j()) { Ok(val) => val as u64, Err(e) => { let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get id value: {}", e)); @@ -164,7 +145,7 @@ pub mod jni { } }; - let center_x = match env.get_field(¢er_obj, "x", "D").and_then(|v| v.d()) { + let _center_x = match env.get_field(¢er_obj, "x", "D").and_then(|v| v.d()) { Ok(val) => val, Err(e) => { let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get center.x: {}", e)); @@ -172,7 +153,7 @@ pub mod jni { } }; - let center_y = match env.get_field(¢er_obj, "y", "D").and_then(|v| v.d()) { + let _center_y = match env.get_field(¢er_obj, "y", "D").and_then(|v| v.d()) { Ok(val) => val, Err(e) => { let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get center.y: {}", e)); @@ -180,7 +161,7 @@ pub mod jni { } }; - let radius = match env.get_field(&circle, "radius", "D").and_then(|v| v.d()) { + let _radius = match env.get_field(&circle, "radius", "D").and_then(|v| v.d()) { Ok(val) => val, Err(e) => { let _ = env.throw_new("java/lang/RuntimeException", format!("Failed to get radius: {}", e)); @@ -188,12 +169,7 @@ pub mod jni { } }; - ui.push(UiCommand::Circle { - id, - center_x, - center_y, - radius, - }); + panic!("this is not implemented yet :(") } #[unsafe(no_mangle)] @@ -1,22 +1,13 @@ -use egui::{Stroke, StrokeKind}; use jni::JNIEnv; use jni::objects::JObject; use crate::scripting::jni::utils::{FromJObject, ToJObject}; use crate::scripting::result::DropbearNativeResult; use crate::scripting::native::DropbearNativeError; +use kino_gui::prelude::*; +use kino_gui::prelude::shapes::Rectangle; +use kino_gui::WidgetId; -/// Maps directly to a `com.dropbear.ui.primitive.Rectangle` Kotlin class -pub struct Rect { - pub id: u64, - pub initial_pos: (f32, f32), - pub size: (f32, f32), - pub corner_radius: f32, - pub stroke: Stroke, - pub fill: egui::Color32, - pub stroke_kind: StrokeKind, -} - -impl FromJObject for Rect { +impl FromJObject for Rectangle { fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> where Self: Sized @@ -122,8 +113,14 @@ impl FromJObject for Rect { .b() .map_err(|_| DropbearNativeError::JNIUnwrapFailed)? as u8; - let fill = egui::Color32::from_rgba_unmultiplied(r, g, b, a); - + // let fill = egui::Color32::from_rgba_unmultiplied(r, g, b, a); + let fill = Colour { + r: r as f32 / 255.0, + g: g as f32 / 255.0, + b: b as f32 / 255.0, + a: a as f32 / 255.0, + }; + // Get stroke colour let stroke_colour_obj = env .get_field(obj, "strokeColour", "Lcom/dropbear/utils/Colour;") @@ -156,7 +153,7 @@ impl FromJObject for Rect { .map_err(|_| DropbearNativeError::JNIUnwrapFailed)? as u8; let stroke_color = egui::Color32::from_rgba_unmultiplied(stroke_r, stroke_g, stroke_b, stroke_a); - let stroke = Stroke::new(stroke_width, stroke_color); + // let stroke = Stroke::new(stroke_width, stroke_color); // Get stroke kind (enum) let stroke_kind_obj = env @@ -176,26 +173,25 @@ impl FromJObject for Rect { .map_err(|_| DropbearNativeError::JNIUnwrapFailed)? .into(); - let stroke_kind = match stroke_kind_str.as_str() { - "Inside" => StrokeKind::Inside, - "Middle" => StrokeKind::Middle, - "Outside" => StrokeKind::Outside, - _ => StrokeKind::Middle, // default - }; - - Ok(Rect { - id, - initial_pos: (initial_x, initial_y), - size: (width, height), - corner_radius, - stroke, - fill, - stroke_kind, - }) + // let stroke_kind = match stroke_kind_str.as_str() { + // "Inside" => StrokeKind::Inside, + // "Middle" => StrokeKind::Middle, + // "Outside" => StrokeKind::Outside, + // _ => StrokeKind::Middle, // default + // }; + + let rect = Rectangle::new( + WidgetId::new(id), + [initial_x, initial_y].into(), + [width, height].into(), + fill + ); + + Ok(rect) } } -impl ToJObject for Rect { +impl ToJObject for Rectangle { fn to_jobject<'a>(&self, _env: &mut JNIEnv<'a>) -> DropbearNativeResult<JObject<'a>> { todo!() } @@ -0,0 +1,13 @@ +[package] +name = "kino-gui" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +readme.workspace = true + +[dependencies] +wgpu.workspace = true +anyhow.workspace = true +bytemuck.workspace = true +parking_lot.workspace = true @@ -0,0 +1,9 @@ +# kino-gui + +_kino-gui_ is a 2D UI immediate mode library that is wgpu native and game design oriented. Named after the sap that comes out of a +eucalyptus tree, this library can be used to create 2D designs, create HUDs for a 3D map or create menus for 2D contexts. + +This library is simple to use, and designs itself to be "injected" as an overlay with your surface. + +## Note +It is a heavy WIP, and there are many features still not done yet. @@ -0,0 +1,77 @@ +use std::hash::{DefaultHasher, Hash, Hasher}; +use parking_lot::Mutex; +use crate::math::Size; +use crate::rendering::KinoRenderer; + +pub mod rendering; +pub(crate) mod math; +pub(crate) mod primitives; +pub(crate) mod utils; + +pub mod prelude { + pub use crate::{ + rendering::KinoRenderer, + math::*, + primitives::*, + WidgetId, + Widget, + }; +} + +pub struct GumContext { + screen_size: Size, +} + +impl GumContext { + pub fn new() -> Self { + Self { screen_size: Size::default() } + } +} + +pub struct KinoUICommandBuffer { + contents: Mutex<Vec<Box<dyn Widget>>>, +} + +impl KinoUICommandBuffer { + pub(crate) fn new() -> Self { + Self { + contents: Mutex::new(Vec::new()), + } + } + + /// Drains the [self.contents] and sends back to the renderer + pub(crate) fn process<'a>(&self) -> Vec<Box<dyn Widget>> { + let mut contents = self.contents.lock(); + contents.drain(..).collect() + } + + pub fn add<T: Widget + 'static>(&self, widget: T) { + self.contents.lock().push(Box::new(widget)); + } +} + +pub trait Widget { + fn id(&self) -> WidgetId; + fn draw<'a>(&mut self, renderer: &KinoRenderer, pass: &mut wgpu::RenderPass<'a>); +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct WidgetId(u64); + +impl WidgetId { + pub fn new(id: u64) -> Self { + Self(id) + } + + pub fn as_u64(&self) -> u64 { + self.0 + } +} + +impl Into<WidgetId> for String { + fn into(self) -> WidgetId { + let mut hasher = DefaultHasher::new(); + self.hash(&mut hasher); + WidgetId(hasher.finish()) + } +} @@ -0,0 +1,106 @@ +#[derive(Debug, Clone, PartialEq)] +pub struct Rect { + /// States the left most corner of the rectangle in reference to the window + pub initial: Vector2, + /// The width and height that extends from the initial point, with the width extending + /// left and the height extending down. + pub size: Size, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Vector2 { + pub x: f32, + pub y: f32, +} + +impl Into<Size> for Vector2 { + fn into(self) -> Size { + Size { + width: self.x, + height: self.y, + } + } +} + +impl Into<[f32; 2]> for Vector2 { + fn into(self) -> [f32; 2] { + [self.x, self.y] + } +} + +impl Into<Vector2> for [f32; 2] { + fn into(self) -> Vector2 { + Vector2 { + x: self[0], + y: self[1], + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Size { + pub width: f32, + pub height: f32 +} + +impl Default for Size { + fn default() -> Self { + Self { + // cant be zero otherwise wgpu panics + width: 800.0, + height: 600.0, + } + } +} + +impl Into<Vector2> for Size { + fn into(self) -> Vector2 { + Vector2 { + x: self.width, + y: self.height, + } + } +} + +impl Into<[f32; 2]> for Size { + fn into(self) -> [f32; 2] { + [self.width, self.height] + } +} + +impl Into<Size> for [f32; 2] { + fn into(self) -> Size { + Size { + width: self[0], + height: self[1], + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Colour { + pub r: f32, + pub g: f32, + pub b: f32, + pub a: f32, +} + +pub fn create_orthographic_projection( + left: f32, + right: f32, + bottom: f32, + top: f32, + near: f32, + far: f32, +) -> [[f32; 4]; 4] { + let width = right - left; + let height = top - bottom; + let depth = far - near; + + [ + [2.0 / width, 0.0, 0.0, 0.0], + [0.0, 2.0 / height, 0.0, 0.0], + [0.0, 0.0, -2.0 / depth, 0.0], + [-(right + left) / width, -(bottom + top) / height, -(far + near) / depth, 1.0], + ] +} @@ -0,0 +1 @@ +pub mod shapes; @@ -0,0 +1,120 @@ +use wgpu::RenderPass; +use wgpu::util::DeviceExt; +use crate::math::{create_orthographic_projection, Colour, Size, Vector2}; +use crate::{Widget, WidgetId}; +use crate::rendering::{Globals, KinoRenderer}; +use crate::utils::UniformBuffer; + +pub struct Rectangle { + pub id: WidgetId, + pub initial: Vector2, + pub size: Size, + pub fill_colour: Colour, + + globals_uniform: Option<UniformBuffer<Globals>>, + vertex_buffer: Option<wgpu::Buffer>, +} + +impl Rectangle { + pub fn new(id: WidgetId, initial: Vector2, size: Size, fill_colour: Colour) -> Self { + Self { + id, + initial, + size, + fill_colour, + globals_uniform: None, + vertex_buffer: None, + } + } + + pub(crate) fn create_vertex_buffer(&self, device: &wgpu::Device) -> wgpu::Buffer { + let x = self.initial.x; + let y = self.initial.y; + let w = self.size.width; + let h = self.size.height; + let c = [ + self.fill_colour.r, + self.fill_colour.g, + self.fill_colour.b, + self.fill_colour.a, + ]; + + #[repr(C)] + #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] + struct Vertex { + position: [f32; 3], + fill_colour: [f32; 4], + } + + let vertices: [Vertex; 6] = [ + Vertex { position: [x, y, 0.0], fill_colour: c }, + Vertex { position: [x + w, y, 0.0], fill_colour: c }, + Vertex { position: [x + w, y + h, 0.0], fill_colour: c }, + Vertex { position: [x, y, 0.0], fill_colour: c }, + Vertex { position: [x + w, y + h, 0.0], fill_colour: c }, + Vertex { position: [x, y + h, 0.0], fill_colour: c }, + ]; + + device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("rectangle vertex buffer"), + contents: bytemuck::cast_slice(&vertices), + usage: wgpu::BufferUsages::VERTEX, + }) + } +} + +impl Widget for Rectangle { + fn id(&self) -> WidgetId { + self.id + } + + fn draw<'a>(&mut self, renderer: &KinoRenderer, pass: &mut RenderPass<'a>) { + if self.globals_uniform.is_none() { + let globals = Globals::new( + create_orthographic_projection( + 0.0, + renderer.context.screen_size.width, + renderer.context.screen_size.height, + 0.0, + -1.0, + 1.0 + ), + [ + renderer.context.screen_size.width, + renderer.context.screen_size.height, + ], + ); + + self.globals_uniform = Some(UniformBuffer::new( + &renderer.render.device, + Some(globals), + Some("rectangle globals uniform"), + )); + } + + if self.vertex_buffer.is_none() { + self.vertex_buffer = Some(self.create_vertex_buffer(&renderer.render.device)); + } + + let globals_bind_group = renderer.render.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("rectangle globals bind group"), + layout: &renderer.render.globals_uniform_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: self.globals_uniform.as_ref().unwrap().buffer(), + offset: 0, + size: None, + }), + } + ], + }); + + pass.set_pipeline(&renderer.render.pipeline); + pass.set_bind_group(0, &globals_bind_group, &[]); + pass.set_vertex_buffer(0, self.vertex_buffer.as_ref().unwrap().slice(..)); + + pass.draw(0..6, 0..1); + } +} @@ -0,0 +1,195 @@ +use std::num::NonZeroU64; +use std::sync::Arc; +use anyhow::Context; +use wgpu::{include_wgsl, BindGroupLayoutEntry, ShaderStages, VertexAttribute, VertexStepMode}; +use crate::{GumContext, KinoUICommandBuffer}; +use crate::math::Size; + +pub struct KinoRenderer { + pub(crate) context: GumContext, + pub(crate) render: KinoRenderPipeline, + pub(crate) ui: Arc<KinoUICommandBuffer>, +} + +pub(crate) struct KinoRenderPipeline { + pub(crate) globals_uniform_layout: wgpu::BindGroupLayout, + pub(crate) pipeline: wgpu::RenderPipeline, + pub(crate) device: Arc<wgpu::Device>, + pub(crate) queue: Arc<wgpu::Queue>, +} + +impl KinoRenderer { + pub fn new( + device: Arc<wgpu::Device>, + queue: Arc<wgpu::Queue>, + texture_format: wgpu::TextureFormat, + ) -> anyhow::Result<Self> { + let shader = device.create_shader_module(include_wgsl!("shaders/primitive.wgsl")); + + let globals_uniform_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("kino globals uniform"), + entries: &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + } + ], + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("kino render pipeline layout"), + bind_group_layouts: &[ + &globals_uniform_layout + ], + push_constant_ranges: &[], + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("kino render pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + compilation_options: Default::default(), + buffers: &[ + VertexInput::desc() + ], + }, + primitive: Default::default(), + depth_stencil: None, + multisample: Default::default(), + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + compilation_options: Default::default(), + targets: &[ + Some(wgpu::ColorTargetState { + format: texture_format, + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: Default::default(), + }) + ], + }), + multiview: None, + cache: None, + }); + + Ok(Self { + context: GumContext::new(), + render: KinoRenderPipeline { + globals_uniform_layout, + pipeline, + device, + queue, + }, + ui: Arc::new(KinoUICommandBuffer::new()), + }) + } + + pub fn get_ui(&self) -> Arc<KinoUICommandBuffer> { + self.ui.clone() + } + + /// Uses [self.render] in the backend, creates an encoder with the device provided, and + /// renders the content. + pub fn render_without_encoder( + &mut self, + view: &wgpu::TextureView, + size_to_render: Size, + ) { + let mut encoder = self.render.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("kino render encoder"), + }); + + self.render(&mut encoder, view, size_to_render); + + self.render.queue.submit(std::iter::once(encoder.finish())); + } + + /// Renders the content onto the provided texture/view. + pub fn render( + &mut self, + encoder: &mut wgpu::CommandEncoder, + view: &wgpu::TextureView, + size_to_render: Size, + ) { + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("kino render pass"), + color_attachments: &[ + Some(wgpu::RenderPassColorAttachment { + view, + depth_slice: None, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + }) + ], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + self.context.screen_size = size_to_render; + + let mut contents = self.ui.process(); + for mut widget in contents.drain(..) { + widget.draw(&self, &mut pass); + } + } +} + +pub struct VertexInput { + position: [f32; 3], + fill_colour: [f32; 4], +} + +impl VertexInput { + pub fn desc() -> wgpu::VertexBufferLayout<'static> { + wgpu::VertexBufferLayout { + array_stride: 0, + step_mode: VertexStepMode::Vertex, + attributes: &[ + // position + VertexAttribute { + format: wgpu::VertexFormat::Float32x3, + offset: 0, + shader_location: 0, + }, + + // fill_colour + VertexAttribute { + format: wgpu::VertexFormat::Float32x4, + offset: size_of::<[f32; 3]>() as u64, + shader_location: 1, + } + ], + } + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +#[derive(Default)] +pub struct Globals { + pub proj: [[f32; 4]; 4], + pub screen_size: [f32; 2], + pub _padding: [f32; 2], +} + +impl Globals { + pub fn new(proj: [[f32; 4]; 4], screen_size: [f32; 2]) -> Self { + Self { + proj, + screen_size, + _padding: [0.0; 2], + } + } +} @@ -0,0 +1,30 @@ +struct Globals { + proj: mat4x4<f32>, + screen_size: vec2<f32>, +} + +@group(0) @binding(0) +var<uniform> globals: Globals; + +struct VertexInput { + @location(0) position: vec3<f32>, + @location(1) fill_colour: vec4<f32>, +} + +struct VertexOutput { + @builtin(position) position: vec4<f32>, + @location(0) colour: vec4<f32>, +} + +@vertex +fn vs_main(in: VertexInput) -> VertexOutput { + var out: VertexOutput; + out.position = globals.proj * vec4<f32>(in.position, 1.0); + out.colour = in.fill_colour; + return out; +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> { + return in.colour; +} @@ -0,0 +1,35 @@ +use wgpu::util::{BufferInitDescriptor, DeviceExt}; + +pub struct UniformBuffer<T> { + pub inner: T, + buffer: wgpu::Buffer, +} + +impl<T: bytemuck::Pod + Default> UniformBuffer<T> { + pub fn new(device: &wgpu::Device, uniform: Option<T>, label: Option<&str>) -> Self { + let uniform = if let Some(uniform) = uniform { + uniform + } else { + T::default() + }; + + let buffer = device.create_buffer_init(&BufferInitDescriptor { + label, + contents: bytemuck::cast_slice(&[uniform]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + Self { + inner: uniform, + buffer, + } + } + + pub fn write(&self, queue: &wgpu::Queue, uniform: T) { + queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&[uniform])); + } + + pub fn buffer(&self) -> &wgpu::Buffer { + &self.buffer + } +} @@ -9,6 +9,7 @@ readme = "README.md" [dependencies] dropbear-engine = { path = "../dropbear-engine" } eucalyptus-core = { path = "../eucalyptus-core", features = ["runtime"], default-features = false } +kino-gui = { path = "../kino-gui" } anyhow.workspace = true log.workspace = true @@ -85,6 +85,7 @@ pub struct PlayMode { main_pipeline: Option<MainRenderPipeline>, shader_globals: Option<GlobalsUniform>, collider_wireframe_pipeline: Option<ColliderWireframePipeline>, + kino_renderer: Option<kino_gui::prelude::KinoRenderer>, initial_scene: Option<String>, current_scene: Option<String>, @@ -152,6 +153,7 @@ impl PlayMode { collision_event_receiver: Some(ce_r), collision_force_event_receiver: Some(cfe_r), event_collector, + kino_renderer: None, }; log::debug!("Created new play mode instance"); @@ -164,6 +166,20 @@ impl PlayMode { self.main_pipeline = Some(MainRenderPipeline::new(graphics.clone())); self.shader_globals = Some(GlobalsUniform::new(graphics.clone(), Some("runtime shader globals"))); self.collider_wireframe_pipeline = Some(ColliderWireframePipeline::new(graphics.clone())); + + match kino_gui::prelude::KinoRenderer::new( + graphics.device.clone(), + graphics.queue.clone(), + graphics.surface_format, + ) { + Ok(renderer) => { + self.kino_renderer = Some(renderer); + log::debug!("KinoRenderer initialized successfully"); + } + Err(e) => { + log::error!("Failed to initialize KinoRenderer: {}", e); + } + } } fn reload_scripts_for_current_world(&mut self) { @@ -446,16 +446,6 @@ impl Scene for PlayMode { size: egui::vec2(display_width, display_height), })); }); - - // render overlay - egui::Area::new("overlay".into()) - .fixed_pos(egui::pos2(center_x, center_y)) - .show(&graphics.get_egui_context(), |o_ui| { - // render the scripting overlay - if let Err(e) = eucalyptus_core::ui::poll(o_ui) { - log_once::error_once!("Unable to poll the UI: {}", e); - } - }); } else { log::warn!("No such camera exists in the world"); } @@ -830,6 +820,43 @@ impl Scene for PlayMode { log_once::error_once!("{}", e); } } + + if let Some(kino_renderer) = &mut self.kino_renderer { + let mut encoder = CommandEncoder::new(graphics.clone(), Some("kino ui render encoder")); + + let commands = eucalyptus_core::ui::UI_COMMAND_BUFFER.drain_commands(); + let ui_buffer = kino_renderer.get_ui(); + + for command in commands { + match command { + eucalyptus_core::ui::UiCommand::Rect { id, initial, size, fill, .. } => { + let rect = kino_gui::prelude::shapes::Rectangle::new( + kino_gui::prelude::WidgetId::new(id), + initial, + size, + fill + ); + ui_buffer.add(rect); + } + _ => {} + } + } + + let screen_size = kino_gui::prelude::Size { + width: graphics.viewport_texture.size.width as f32, + height: graphics.viewport_texture.size.height as f32, + }; + + kino_renderer.render( + &mut *encoder, + &graphics.viewport_texture.view, + screen_size, + ); + + if let Err(e) = encoder.submit(graphics.clone()) { + log_once::error_once!("Failed to submit kino renderer: {}", e); + } + } } fn exit(&mut self, _event_loop: &ActiveEventLoop) {}