From 14f9eb655f3347644bfaec7a2e34cb80e971bab7 Mon Sep 17 00:00:00 2001 From: John Doty Date: Wed, 21 Jun 2023 21:57:32 -0700 Subject: [PATCH] [oden] Oh boy here we go --- src/lib.rs | 98 +++++++++++++++++++++++++++++++++--------- src/main.js | 14 +++--- src/script.rs | 32 ++++++++++++-- src/script/graphics.rs | 93 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+), 31 deletions(-) create mode 100644 src/script/graphics.rs diff --git a/src/lib.rs b/src/lib.rs index 562f9e35..a21e5470 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,8 +8,7 @@ use winit::{ }; mod script; -// use script::ScriptContext; - +use script::graphics::{ClearCommand, GraphicsCommand, PrintCommand}; mod texture; #[repr(C)] @@ -298,7 +297,7 @@ impl State { fn update(&mut self) {} - fn render(&mut self) -> Result<(), wgpu::SurfaceError> { + fn render(&mut self, commands: Vec) -> Result<(), wgpu::SurfaceError> { let output = self.surface.get_current_texture()?; let view = output .texture @@ -309,37 +308,95 @@ impl State { label: Some("Render Encoder"), }); - { - // BEGIN GARBAGE - let r: f64 = (self.mouse_x / f64::from(self.size.width)).clamp(0.0, 1.0) * 0.1; - let g: f64 = (self.mouse_y / f64::from(self.size.height)).clamp(0.0, 1.0) * 0.2; - // END GARBAGE + // Group the commands into passes. + struct Pass { + color: Option<[f64; 4]>, + commands: Vec, + } + let mut passes = Vec::new(); + for command in commands { + match command { + GraphicsCommand::Clear(ClearCommand { color }) => passes.push(Pass { + color: Some(color), + commands: Vec::new(), + }), + GraphicsCommand::EndFrame => (), + other => match passes.last_mut() { + Some(pass) => pass.commands.push(other), + None => passes.push(Pass { + color: None, + commands: vec![other], + }), + }, + } + } + for pass in passes { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("Render Pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &view, resolve_target: None, ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r, //0.1, - g, //0.2, - b: 0.3, - a: 1.0, - }), + load: if let Some([r, g, b, a]) = pass.color { + wgpu::LoadOp::Clear(wgpu::Color { + r, //0.1, + g, //0.2, + b, + a, + }) + } else { + wgpu::LoadOp::Load + }, store: true, }, })], depth_stencil_attachment: None, }); - render_pass.set_pipeline(&self.render_pipeline); - render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); - render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); - render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); - render_pass.draw_indexed(0..self.num_indices, 0, 0..1); + for command in pass.commands { + match command { + GraphicsCommand::Print(PrintCommand { text }) => { + println!("{}", text); + } + + GraphicsCommand::Clear(_) => (), // Already handled + GraphicsCommand::EndFrame => (), // Should never appear + } + } } + // { + // // BEGIN GARBAGE + // let r: f64 = (self.mouse_x / f64::from(self.size.width)).clamp(0.0, 1.0) * 0.1; + // let g: f64 = (self.mouse_y / f64::from(self.size.height)).clamp(0.0, 1.0) * 0.2; + // // END GARBAGE + + // let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + // label: Some("Render Pass"), + // color_attachments: &[Some(wgpu::RenderPassColorAttachment { + // view: &view, + // resolve_target: None, + // ops: wgpu::Operations { + // load: wgpu::LoadOp::Clear(wgpu::Color { + // r, //0.1, + // g, //0.2, + // b: 0.3, + // a: 1.0, + // }), + // store: true, + // }, + // })], + // depth_stencil_attachment: None, + // }); + + // render_pass.set_pipeline(&self.render_pipeline); + // render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); + // render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + // render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); + // render_pass.draw_indexed(0..self.num_indices, 0, 0..1); + // } + // Submit will accept anything that implements IntoIter self.queue.submit(std::iter::once(encoder.finish())); output.present(); @@ -403,8 +460,7 @@ pub async fn run() { context.update(); state.update(); - context.render(); - match state.render() { + match state.render(context.render()) { Ok(_) => {} // Reconfigure the surface if lost Err(wgpu::SurfaceError::Lost) => state.resize(state.size), diff --git a/src/main.js b/src/main.js index b20da253..7bc0a8ba 100644 --- a/src/main.js +++ b/src/main.js @@ -1,11 +1,11 @@ -import * as graphics from 'graphics'; +import { cls, print } from "graphics"; -function init() { - graphics.print("Hello world!"); +export function init() { + print("Hello world!"); } -function update() {} +export function update() {} -function draw() {} - -export { init, update, draw } +export function draw() { + cls(0.1, 0.2, 0.3); +} diff --git a/src/script.rs b/src/script.rs index d09ada7c..a34d149d 100644 --- a/src/script.rs +++ b/src/script.rs @@ -1,12 +1,17 @@ use oden_js::{Context, Runtime, Value}; +use std::sync::mpsc::{channel, Receiver}; -mod graphics; +pub mod graphics; +use graphics::GraphicsCommand; pub struct ScriptContext { context: Context, init: Value, update: Value, draw: Value, + + gfx: graphics::GraphicsAPI, + gfx_receive: Receiver, } impl ScriptContext { @@ -18,7 +23,10 @@ impl ScriptContext { context.add_intrinsic_bigdecimal(); context.add_intrinsic_operators(); - graphics::GraphicsAPI::define(&context).expect("Graphics module should load without error"); + let (gfx_send, gfx_receive) = channel(); + + let gfx = graphics::GraphicsAPI::define(&context, gfx_send) + .expect("Graphics module should load without error"); let js = include_str!("main.js"); let module = context @@ -37,12 +45,20 @@ impl ScriptContext { ScriptContext { context, + init, update, draw, + + gfx, + gfx_receive, } } + // TODO: The script could really be on a background thread you know. + // We would want a bi-directional gate for frames to not let the + // game thread go to fast probably? And to discard whole frames &c. + pub fn init(&self) { self.init.call(&self.context).expect("Exception in init"); } @@ -53,7 +69,17 @@ impl ScriptContext { .expect("Exception in update"); } - pub fn render(&self) { + pub fn render(&self) -> Vec { self.draw.call(&self.context).expect("Exception in draw"); + self.gfx.end_frame(); + + let mut commands = Vec::new(); + loop { + match self.gfx_receive.recv().unwrap() { + GraphicsCommand::EndFrame => break, + other => commands.push(other), + } + } + commands } } diff --git a/src/script/graphics.rs b/src/script/graphics.rs new file mode 100644 index 00000000..6223ed0b --- /dev/null +++ b/src/script/graphics.rs @@ -0,0 +1,93 @@ +use oden_js::{module, ContextRef, Error, Value, ValueRef, ValueResult}; +use std::sync::mpsc::Sender; +use std::sync::Arc; + +pub struct PrintCommand { + pub text: String, +} + +pub struct ClearCommand { + pub color: [f64; 4], +} + +pub enum GraphicsCommand { + Clear(ClearCommand), + Print(PrintCommand), + EndFrame, +} + +const MAGIC_VALUE: u64 = 0xABCDB00BABCDBEEB; + +struct GraphicsImpl { + magic: u64, + sender: Sender, +} + +impl GraphicsImpl { + pub fn new(sender: Sender) -> Self { + GraphicsImpl { + magic: MAGIC_VALUE, + sender, + } + } + + fn print_fn(&self, ctx: &ContextRef, args: &[&ValueRef]) -> ValueResult { + assert_eq!(self.magic, MAGIC_VALUE); + let mut text = String::with_capacity(128); + for arg in args { + let v = arg.to_string(ctx)?; + text.push_str(&v); + } + + let _ = self + .sender + .send(GraphicsCommand::Print(PrintCommand { text })); + + Ok(Value::undefined(ctx)) + } + + fn cls_fn(&self, ctx: &ContextRef, args: &[&ValueRef]) -> ValueResult { + assert_eq!(self.magic, MAGIC_VALUE); + if args.len() != 3 { + return Err(Error::ArgumentCountMismatch { + expected: 3, + received: args.len(), + }); + } + let r = args[0].to_float64(&ctx)?; + let g = args[1].to_float64(&ctx)?; + let b = args[1].to_float64(&ctx)?; + + let _ = self.sender.send(GraphicsCommand::Clear(ClearCommand { + color: [r, g, b, 1.0], + })); + Ok(Value::undefined(ctx)) + } +} + +pub struct GraphicsAPI { + gfx: Arc, +} + +impl GraphicsAPI { + pub fn define(ctx: &ContextRef, sender: Sender) -> oden_js::Result { + let gfx = Arc::new(GraphicsImpl::new(sender)); + let gfx_a = gfx.clone(); + let gfx_b = gfx.clone(); + module::NativeModuleBuilder::new(ctx) + .export( + "print", + ctx.new_dynamic_fn(move |ctx, _, args| gfx_a.print_fn(ctx, args))?, + )? + .export( + "cls", + ctx.new_dynamic_fn(move |ctx, _, args| gfx_b.cls_fn(ctx, args))?, + )? + .build("graphics")?; + Ok(GraphicsAPI { gfx }) + } + + pub fn end_frame(&self) { + let _ = self.gfx.sender.send(GraphicsCommand::EndFrame); + } +}