diff --git a/game/main.ts b/game/main.ts index 3fcf3466..ebd13e60 100644 --- a/game/main.ts +++ b/game/main.ts @@ -1,4 +1,4 @@ -import { cls } from "./graphics"; +import { cls, get_dimensions, scale } from "./graphics"; import { since_start } from "./time"; import { new_v2 } from "./vector"; import { load_world, World, Level, draw_level } from "./level"; @@ -102,6 +102,13 @@ export function update() { export function draw() { cls(0.1, 0.2, 0.3); + const dimensions = get_dimensions(); + const s = Math.max( + 1, + Math.floor(Math.min(dimensions[0] / 320, dimensions[1] / 240)) + ); + scale(s); + if (level != undefined) { draw_level(level, 0, 0); } diff --git a/oden-js/src/conversion/into.rs b/oden-js/src/conversion/into.rs index 2e4960bf..9c2a6364 100644 --- a/oden-js/src/conversion/into.rs +++ b/oden-js/src/conversion/into.rs @@ -149,3 +149,10 @@ impl TryIntoValue for () { Ok(ctx.undefined()) } } + +impl TryIntoValue for &[T] { + #[inline] + fn try_into_value(self, ctx: &ContextRef) -> ValueResult { + Ok(ctx.undefined()) + } +} diff --git a/oden-js/src/value.rs b/oden-js/src/value.rs index cc73369f..55530347 100644 --- a/oden-js/src/value.rs +++ b/oden-js/src/value.rs @@ -232,8 +232,11 @@ impl ValueRef { Ok(Value::from_raw(result, ctx)) } - pub fn get_property(&self, ctx: &ContextRef, prop: &str) -> Result { - let atom = ctx.new_atom(prop)?; + pub fn get_property<'a, T>(&self, ctx: &ContextRef, prop: T) -> Result + where + T: Into<&'a str>, + { + let atom = ctx.new_atom(prop.into())?; self.get_property_atom(ctx, &atom) } @@ -241,6 +244,10 @@ impl ValueRef { ctx.check_exception(unsafe { sys::JS_GetProperty(ctx.ctx, self.val, prop.atom) }) } + pub fn get_index(&self, ctx: &ContextRef, index: u32) -> Result { + ctx.check_exception(unsafe { sys::JS_GetPropertyUint32(ctx.ctx, self.val, index) }) + } + pub fn set_property(&mut self, ctx: &ContextRef, prop: &str, val: &ValueRef) -> Result<()> { // TODO: Consume API let atom = ctx.new_atom(prop)?; @@ -264,6 +271,18 @@ impl ValueRef { } } + pub fn set_index(&mut self, ctx: &ContextRef, index: u32, val: &ValueRef) -> Result<()> { + unsafe { + sys::JS_DupValue(ctx.ctx, val.val); + let result = sys::JS_SetPropertyUint32(ctx.ctx, self.val, index, val.val); + if result == -1 { + Err(ctx.exception_error()) + } else { + Ok(()) + } + } + } + pub fn set_fn( &mut self, ctx: &ContextRef, diff --git a/src/graphics.ts b/src/graphics.ts index 11a50ada..8ceca02d 100644 --- a/src/graphics.ts +++ b/src/graphics.ts @@ -36,6 +36,16 @@ export function stroke(r: number, g: number, b: number, a: number = 1) { core.stroke(r, g, b, a); } +/** + * Set the current scale factor. + * + * @param x - The scale factor in the x dimension. + * @param y - The scale factor in the y dimension. If omitted, defaults to x. + */ +export function scale(x: number, y?: number) { + core.scale(x, y ?? x); +} + /** * Print a message to the console. * @@ -148,3 +158,10 @@ export function write_to_screen() { export function write_to_texture(texture: Texture) { core.write_to_texture(texture.id()); } + +/** + * Get the dimensions of the screen. + */ +export function get_dimensions(): [number, number] { + return core.get_dimensions(); +} diff --git a/src/lib.rs b/src/lib.rs index 8c2fd2fc..72e6c69f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -202,14 +202,12 @@ impl GlyphInstance { #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] struct ScreenUniforms { resolution: [f32; 2], - scale: [f32; 2], } impl ScreenUniforms { - fn new(width: u32, height: u32, scale_x: f32, scale_y: f32) -> ScreenUniforms { + fn new(width: u32, height: u32) -> ScreenUniforms { ScreenUniforms { resolution: [width as f32, height as f32], - scale: [scale_x, scale_y], } } } @@ -485,7 +483,7 @@ impl State { label: Some("sprite_bind_group_layout"), }); - let screen_uniform = ScreenUniforms::new(size.width, size.height, 10.0, 10.0); + let screen_uniform = ScreenUniforms::new(size.width, size.height); let screen_uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Screen Uniform Buffer"), contents: bytemuck::cast_slice(&[screen_uniform]), @@ -792,12 +790,6 @@ impl State { let h = self.size.height as f32; self.screen_uniform.resolution = [w, h]; - // How do I compute the zoom for an effective resolution? - let z_x = w / 320.0; - let z_y = h / 240.0; - let z = z_y.min(z_x); - self.screen_uniform.scale = [z, z]; - self.queue.write_buffer( &self.screen_uniform_buffer, 0, @@ -1076,6 +1068,11 @@ struct FrameBuilder<'a> { encoder: wgpu::CommandEncoder, output: wgpu::SurfaceTexture, + // TODO: Should we be persisting these settings across frames? + // Right now the whole GPU resets every frame, which I guess is + // good and bad? + // + // TODO: Should we actually be trying to maintain a RenderPassDescriptor? mode: DrawMode, last_texture: Option, fill_color: [f32; 4], @@ -1083,6 +1080,7 @@ struct FrameBuilder<'a> { target: Rc, color: Option<[f64; 4]>, draw_calls: Vec, + scale: [f32; 2], } impl<'a> FrameBuilder<'a> { @@ -1116,6 +1114,7 @@ impl<'a> FrameBuilder<'a> { target: last_view, color: None, draw_calls: Vec::new(), + scale: [1.0, 1.0], }) } @@ -1144,6 +1143,7 @@ impl<'a> FrameBuilder<'a> { // followed nearly immediately by a clear command and we // wind up starting an extra pass in that case which sucks. GraphicsCommand::Clear(cc) => self.start_pass(Some(cc.color), self.last_view.clone()), + GraphicsCommand::Scale(s) => self.scale = s, GraphicsCommand::WriteToTexture(id) => { let texture = self.state.write_textures.get(&id).unwrap(); self.last_view = Rc::new( @@ -1166,7 +1166,7 @@ impl<'a> FrameBuilder<'a> { self.stroke_color = c; } GraphicsCommand::Print(pc) => self.push_text(pc), - GraphicsCommand::Sprite(si) => self.push_sprite(si), + GraphicsCommand::Sprite(sc) => self.push_sprite(sc), GraphicsCommand::Circle(cc) => self.push_circle(cc), GraphicsCommand::UseTexture(id) => self.use_texture(id), GraphicsCommand::EndFrame => self.flush(), @@ -1234,31 +1234,43 @@ impl<'a> FrameBuilder<'a> { T::get_vertex_buffer(self.state, &vb) } - fn push_sprite(&mut self, si: SpriteInstance) { + fn push_sprite(&mut self, sc: script::graphics::SpriteCommand) { if let Some(id) = self.last_texture { - let vertex_buffer = self.get_instance_buffer(DrawMode::Sprites(id)); - vertex_buffer.vec.push(si); + let sprite = SpriteInstance { + src_top_left: sc.src_top_left, + src_dims: sc.src_dims, + + dest_top_left: [ + sc.dest_top_left[0] * self.scale[0], + sc.dest_top_left[1] * self.scale[1], + ], + dest_dims: [ + sc.dest_dims[0] * self.scale[0], + sc.dest_dims[1] * self.scale[1], + ], + }; + + self.get_instance_buffer(DrawMode::Sprites(id)) + .vec + .push(sprite); } else { eprintln!("WARNING: sprite drawn with no active texture"); } } fn push_circle(&mut self, cc: script::graphics::CircleCommand) { - let stroke_color = self.stroke_color.clone(); - let fill_color = self.fill_color.clone(); + let circle = CircleInstance { + center: [cc.center[0] * self.scale[0], cc.center[1] * self.scale[1]], + radius: cc.radius * self.scale[0], // WRONG! + stroke_width: cc.stroke_width * self.scale[0], // ALSO WRONG! + stroke_color: self.stroke_color, + fill_color: self.fill_color, + }; - let vertex_buffer = self.get_instance_buffer(DrawMode::Circles); - vertex_buffer.vec.push(CircleInstance { - center: cc.center, - radius: cc.radius, - stroke_width: cc.stroke_width, - stroke_color, - fill_color, - }); + self.get_instance_buffer(DrawMode::Circles).vec.push(circle); } fn push_text(&mut self, pc: script::graphics::PrintCommand) { - // println!("{}", pc.text); let mut cursor_x = pc.pos[0]; let cursor_y = pc.pos[1]; @@ -1273,10 +1285,13 @@ impl<'a> FrameBuilder<'a> { src_top_left: [glyph.x, glyph.y], src_dims: [glyph.w, glyph.h], dest_top_left: [ - cursor_x + (glyph.adjust_x / TEXT_SCALE), - cursor_y + (glyph.adjust_y / TEXT_SCALE), + self.scale[0] * (cursor_x + (glyph.adjust_x / TEXT_SCALE)), + self.scale[1] * (cursor_y + (glyph.adjust_y / TEXT_SCALE)), + ], + dest_dims: [ + self.scale[0] * (glyph.w / TEXT_SCALE), + self.scale[1] * (glyph.h / TEXT_SCALE), ], - dest_dims: [glyph.w / TEXT_SCALE, glyph.h / TEXT_SCALE], color, }); cursor_x += glyph.advance_width / TEXT_SCALE; @@ -1422,7 +1437,7 @@ fn main_thread(event_loop: EventLoopProxy, state: State, reciever: Re { let _span = span!("update"); - script.update(); + script.update((state.size.width as f32, state.size.height as f32)); state.update(); } diff --git a/src/script.rs b/src/script.rs index c36f804e..addcd757 100644 --- a/src/script.rs +++ b/src/script.rs @@ -154,9 +154,11 @@ impl ScriptContext { } } - pub fn update(&mut self) { + pub fn update(&mut self, dimensions: (f32, f32)) { let _span = span!("script update"); + self.gfx.set_dimensions(dimensions); + if self.error_lines.len() > 0 { return; // Don't bother, nothing. } @@ -190,6 +192,7 @@ impl ScriptContext { let _span = span!("script render"); if self.error_lines.len() > 0 { // TODO: Use font 0 for a fallback. + // TODO: Scale!! Remember you're using a font at size 8 or something. let mut commands = vec![ GraphicsCommand::Clear(ClearCommand { color: [0.0, 0.0, 1.0, 1.0], diff --git a/src/script/graphics.rs b/src/script/graphics.rs index 91418b91..a3488c99 100644 --- a/src/script/graphics.rs +++ b/src/script/graphics.rs @@ -1,4 +1,5 @@ use oden_js::{module, ContextRef, Error, Result, Value}; +use std::cell::RefCell; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::mpsc::Sender; use std::sync::Arc; @@ -23,6 +24,15 @@ pub struct CreateTextureCommand { pub label: Option, } +#[derive(Debug)] +pub struct SpriteCommand { + pub src_top_left: [f32; 2], + pub src_dims: [f32; 2], + + pub dest_top_left: [f32; 2], + pub dest_dims: [f32; 2], +} + #[derive(Debug)] pub struct CircleCommand { pub center: [f32; 2], @@ -35,9 +45,10 @@ pub enum GraphicsCommand { Clear(ClearCommand), FillColor([f32; 4]), StrokeColor([f32; 4]), + Scale([f32; 2]), Print(PrintCommand), Circle(CircleCommand), - Sprite(crate::SpriteInstance), + Sprite(SpriteCommand), CreateTexture(CreateTextureCommand), CreateWritableTexture { id: u32, @@ -53,6 +64,7 @@ pub enum GraphicsCommand { struct GraphicsImpl { next_texture_id: AtomicU32, + dimensions: (f32, f32), sender: Sender, } @@ -60,6 +72,7 @@ impl GraphicsImpl { pub fn new(sender: Sender) -> Self { GraphicsImpl { sender, + dimensions: (0.0, 0.0), next_texture_id: AtomicU32::new(0), } } @@ -84,15 +97,17 @@ impl GraphicsImpl { let _ = self.sender.send(GraphicsCommand::StrokeColor([r, g, b, a])); } + fn scale(&self, x: f32, y: f32) -> () { + let _ = self.sender.send(GraphicsCommand::Scale([x, y])); + } + fn spr(&self, x: f32, y: f32, w: f32, h: f32, u: f32, v: f32, sw: f32, sh: f32) { - let _ = self - .sender - .send(GraphicsCommand::Sprite(crate::SpriteInstance { - src_top_left: [u, v], - src_dims: [sw, sh], - dest_top_left: [x, y], - dest_dims: [w, h], - })); + let _ = self.sender.send(GraphicsCommand::Sprite(SpriteCommand { + src_top_left: [u, v], + src_dims: [sw, sh], + dest_top_left: [x, y], + dest_dims: [w, h], + })); } fn circle(&self, x: f32, y: f32, r: f32, s: f32) { @@ -155,28 +170,42 @@ impl GraphicsImpl { fn write_to_texture(&self, id: u32) { let _ = self.sender.send(GraphicsCommand::WriteToTexture(id)); } + + fn get_dimensions(&self, ctx: &ContextRef) -> Result { + let width = ctx.new_f64(self.dimensions.0)?; + let height = ctx.new_f64(self.dimensions.1)?; + + let mut result = ctx.new_array()?; + result.set_index(ctx, 0, &width)?; + result.set_index(ctx, 1, &height)?; + Ok(result) + } } pub struct GraphicsAPI { - gfx: Arc, + gfx: Arc>, } impl GraphicsAPI { pub fn define(ctx: &ContextRef, sender: Sender) -> oden_js::Result { - let gfx = Arc::new(GraphicsImpl::new(sender)); + let gfx = Arc::new(RefCell::new(GraphicsImpl::new(sender))); let mut builder = module::native::NativeModuleBuilder::new(ctx); { let gfx = gfx.clone(); builder.export( "print", - ctx.new_fn(move |_: &ContextRef, t: String, x: f32, y: f32| gfx.print(t, x, y))?, + ctx.new_fn(move |_: &ContextRef, t: String, x: f32, y: f32| { + gfx.borrow().print(t, x, y) + })?, )?; } { let gfx = gfx.clone(); builder.export( "cls", - ctx.new_fn(move |_: &ContextRef, r: f64, g: f64, b: f64| gfx.cls(r, g, b))?, + ctx.new_fn(move |_: &ContextRef, r: f64, g: f64, b: f64| { + gfx.borrow().cls(r, g, b) + })?, )?; } { @@ -184,7 +213,7 @@ impl GraphicsAPI { builder.export( "color", ctx.new_fn(move |_: &ContextRef, r: f32, g: f32, b: f32, a: f32| { - gfx.color(r, g, b, a) + gfx.borrow().color(r, g, b, a) })?, )?; } @@ -193,10 +222,17 @@ impl GraphicsAPI { builder.export( "stroke", ctx.new_fn(move |_: &ContextRef, r: f32, g: f32, b: f32, a: f32| { - gfx.stroke(r, g, b, a) + gfx.borrow().stroke(r, g, b, a) })?, )?; } + { + let gfx = gfx.clone(); + builder.export( + "scale", + ctx.new_fn(move |_: &ContextRef, x: f32, y: f32| gfx.borrow().scale(x, y))?, + )?; + } { let gfx = gfx.clone(); builder.export( @@ -210,7 +246,7 @@ impl GraphicsAPI { u: f32, v: f32, sw: f32, - sh: f32| gfx.spr(x, y, w, h, u, v, sw, sh), + sh: f32| gfx.borrow().spr(x, y, w, h, u, v, sw, sh), )?, )?; } @@ -219,7 +255,7 @@ impl GraphicsAPI { builder.export( "circle", ctx.new_fn(move |_: &ContextRef, x: f32, y: f32, r: f32, s: f32| { - gfx.circle(x, y, r, s) + gfx.borrow().circle(x, y, r, s) })?, )?; } @@ -227,7 +263,7 @@ impl GraphicsAPI { let gfx = gfx.clone(); builder.export( "use_texture", - ctx.new_fn(move |_: &ContextRef, id: u32| gfx.use_texture(id))?, + ctx.new_fn(move |_: &ContextRef, id: u32| gfx.borrow().use_texture(id))?, )?; } { @@ -236,7 +272,7 @@ impl GraphicsAPI { "create_texture", ctx.new_fn( move |c: &ContextRef, buffer: Value, label: Option| { - gfx.create_texture(c, buffer, label) + gfx.borrow().create_texture(c, buffer, label) }, )?, )?; @@ -247,7 +283,7 @@ impl GraphicsAPI { "create_writable_texture", ctx.new_fn( move |_: &ContextRef, width: u32, height: u32, label: Option| { - gfx.create_writable_texture(width, height, label) + gfx.borrow().create_writable_texture(width, height, label) }, )?, )?; @@ -256,14 +292,21 @@ impl GraphicsAPI { let gfx = gfx.clone(); builder.export( "write_to_screen", - ctx.new_fn(move |_: &ContextRef| gfx.write_to_screen())?, + ctx.new_fn(move |_: &ContextRef| gfx.borrow().write_to_screen())?, )?; } { let gfx = gfx.clone(); builder.export( "write_to_texture", - ctx.new_fn(move |_: &ContextRef, id: u32| gfx.write_to_texture(id))?, + ctx.new_fn(move |_: &ContextRef, id: u32| gfx.borrow().write_to_texture(id))?, + )?; + } + { + let gfx = gfx.clone(); + builder.export( + "get_dimensions", + ctx.new_fn(move |ctx: &ContextRef| gfx.borrow().get_dimensions(ctx))?, )?; } @@ -271,7 +314,11 @@ impl GraphicsAPI { Ok(GraphicsAPI { gfx }) } + pub fn set_dimensions(&mut self, dimensions: (f32, f32)) { + self.gfx.borrow_mut().dimensions = dimensions; + } + pub fn end_frame(&self) { - let _ = self.gfx.sender.send(GraphicsCommand::EndFrame); + let _ = self.gfx.borrow().sender.send(GraphicsCommand::EndFrame); } } diff --git a/src/util.wgsl b/src/util.wgsl index f5008afb..af8bd887 100644 --- a/src/util.wgsl +++ b/src/util.wgsl @@ -8,7 +8,6 @@ struct ScreenUniform { resolution : vec2f, - zoom : vec2f, }; @group(0) @binding(0) // 1. var screen : ScreenUniform; @@ -20,7 +19,7 @@ fn adjust_for_resolution(in_pos: vec2) -> vec2 { // // Put result in the range [0-2], where (2,2) is the bottom-right corner // of the screen. - var result = (in_pos * screen.zoom * 2.0) / screen.resolution; + var result = (in_pos * 2.0) / screen.resolution; // Put result in the range [-1,1] where [1,1] is the bottom-right corner // of the screen. diff --git a/types/graphics-core.d.ts b/types/graphics-core.d.ts index af53ae1c..57d507b5 100644 --- a/types/graphics-core.d.ts +++ b/types/graphics-core.d.ts @@ -8,6 +8,8 @@ export function color(r: number, g: number, b: number, a: number); export function stroke(r: number, g: number, b: number, a: number); +export function scale(x: number, y: number); + export function spr( x: number, y: number, @@ -37,3 +39,5 @@ export function create_writable_texture( export function write_to_screen(); export function write_to_texture(id: number); + +export function get_dimensions(): [number, number];