[oden] lol sprite API

This commit is contained in:
John Doty 2023-06-23 06:25:45 -07:00
parent c7903382a0
commit aa90cea4a3
3 changed files with 168 additions and 75 deletions

View file

@ -1,5 +1,4 @@
use bytemuck; use bytemuck;
use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::{ControlFlow, EventLoop},
@ -8,13 +7,13 @@ use winit::{
}; };
mod script; mod script;
use script::graphics::{ClearCommand, GraphicsCommand, PrintCommand}; use script::graphics::GraphicsCommand;
mod texture; mod texture;
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct Vertex { struct Vertex {
position: [f32; 3], position: [f32; 3], // TODO: Why do I pass in a Z here?
tex_coords: [f32; 2], tex_coords: [f32; 2],
} }
@ -74,8 +73,7 @@ struct State {
render_pipeline: wgpu::RenderPipeline, render_pipeline: wgpu::RenderPipeline,
vertex_buffer: wgpu::Buffer, vertex_buffer: wgpu::Buffer,
index_buffer: wgpu::Buffer, max_vertices: usize,
num_indices: u32, // Indices in index_buffer
diffuse_bind_group: wgpu::BindGroup, diffuse_bind_group: wgpu::BindGroup,
@ -248,17 +246,15 @@ impl State {
multiview: None, multiview: None,
}); });
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { let max_vertices: usize = 4096;
let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Vertex Buffer"), label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(VERTICES), size: (max_vertices * std::mem::size_of::<Vertex>())
usage: wgpu::BufferUsages::VERTEX, .try_into()
.unwrap(),
mapped_at_creation: false,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
}); });
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"),
contents: bytemuck::cast_slice(INDICES),
usage: wgpu::BufferUsages::INDEX,
});
let num_indices = INDICES.len() as u32;
Self { Self {
window, window,
@ -269,8 +265,7 @@ impl State {
size, size,
render_pipeline, render_pipeline,
vertex_buffer, vertex_buffer,
index_buffer, max_vertices,
num_indices,
diffuse_bind_group, diffuse_bind_group,
mouse_x: 0.0, mouse_x: 0.0,
@ -302,11 +297,6 @@ impl State {
let view = output let view = output
.texture .texture
.create_view(&wgpu::TextureViewDescriptor::default()); .create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
});
// Group the commands into passes. // Group the commands into passes.
struct Pass { struct Pass {
@ -316,8 +306,8 @@ impl State {
let mut passes = Vec::new(); let mut passes = Vec::new();
for command in commands { for command in commands {
match command { match command {
GraphicsCommand::Clear(ClearCommand { color }) => passes.push(Pass { GraphicsCommand::Clear(cc) => passes.push(Pass {
color: Some(color), color: Some(cc.color),
commands: Vec::new(), commands: Vec::new(),
}), }),
GraphicsCommand::EndFrame => (), GraphicsCommand::EndFrame => (),
@ -331,7 +321,20 @@ impl State {
} }
} }
let mut vertices = Vec::new();
for pass in passes { for pass in passes {
// TODO: It would be great if we could use multiple passes in a
// single encoder but right now because of the dyanmic
// nature of vertices we can't, I think? Because
// queue.write_buffer doesn't actually happen until we call
// submit...
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
});
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"), label: Some("Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment { color_attachments: &[Some(wgpu::RenderPassColorAttachment {
@ -354,16 +357,55 @@ impl State {
depth_stencil_attachment: None, depth_stencil_attachment: None,
}); });
vertices.clear();
for command in pass.commands { for command in pass.commands {
match command { match command {
GraphicsCommand::Print(PrintCommand { text }) => { GraphicsCommand::Print(pc) => {
println!("{}", text); println!("{}", pc.text);
}
GraphicsCommand::Sprite(sc) => {
vertices.push(Vertex {
position: [sc.x, sc.y, 0.0],
tex_coords: [sc.u, sc.v],
});
vertices.push(Vertex {
position: [sc.x + sc.w, sc.y, 0.0],
tex_coords: [sc.u + sc.sw, sc.v],
});
vertices.push(Vertex {
position: [sc.x, sc.y + sc.h, 0.0],
tex_coords: [sc.u, sc.v],
});
vertices.push(Vertex {
position: [sc.x, sc.y + sc.h, 0.0],
tex_coords: [sc.u, sc.v],
});
vertices.push(Vertex {
position: [sc.x + sc.w, sc.y, 0.0],
tex_coords: [sc.u, sc.v],
});
vertices.push(Vertex {
position: [sc.x + sc.w, sc.y + sc.h, 0.0],
tex_coords: [sc.u, sc.v],
});
} }
GraphicsCommand::Clear(_) => (), // Already handled GraphicsCommand::Clear(_) => (), // Already handled
GraphicsCommand::EndFrame => (), // Should never appear GraphicsCommand::EndFrame => (), // Should never appear
} }
} }
assert!(vertices.len() < self.max_vertices); // !
self.queue
.write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&vertices));
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.draw(0..(vertices.len() as u32), 0..1);
}
// Submit will accept anything that implements IntoIter
self.queue.submit(std::iter::once(encoder.finish()));
} }
// { // {
@ -397,8 +439,6 @@ impl State {
// render_pass.draw_indexed(0..self.num_indices, 0, 0..1); // 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(); output.present();
Ok(()) Ok(())

View file

@ -1,4 +1,4 @@
import { cls, print } from "graphics"; import { cls, print, spr } from "graphics";
export function init() { export function init() {
print("Hello world!"); print("Hello world!");
@ -8,4 +8,5 @@ export function update() {}
export function draw() { export function draw() {
cls(0.1, 0.2, 0.3); cls(0.1, 0.2, 0.3);
spr(0, 0, 0.5, 0.5, 0, 0, 0.5, 0.5);
} }

View file

@ -1,4 +1,4 @@
use oden_js::{module, ContextRef, Error, Value, ValueRef, ValueResult}; use oden_js::{module, ContextRef, Value, ValueRef, ValueResult};
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use std::sync::Arc; use std::sync::Arc;
@ -10,9 +10,21 @@ pub struct ClearCommand {
pub color: [f64; 4], pub color: [f64; 4],
} }
pub struct SpriteCommand {
pub x: f32,
pub y: f32,
pub w: f32,
pub h: f32,
pub u: f32,
pub v: f32,
pub sw: f32,
pub sh: f32,
}
pub enum GraphicsCommand { pub enum GraphicsCommand {
Clear(ClearCommand), Clear(ClearCommand),
Print(PrintCommand), Print(PrintCommand),
Sprite(SpriteCommand),
EndFrame, EndFrame,
} }
@ -39,22 +51,37 @@ impl GraphicsImpl {
Ok(Value::undefined(ctx)) Ok(Value::undefined(ctx))
} }
fn cls_fn(&self, ctx: &ContextRef, args: &[&ValueRef]) -> ValueResult { fn cls_fn(&self, ctx: &ContextRef, r: f64, g: f64, b: f64) -> ValueResult {
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 { let _ = self.sender.send(GraphicsCommand::Clear(ClearCommand {
color: [r, g, b, 1.0], color: [r, g, b, 1.0],
})); }));
Ok(Value::undefined(ctx)) Ok(Value::undefined(ctx))
} }
fn spr_fn(
&self,
ctx: &ContextRef,
x: f32,
y: f32,
w: f32,
h: f32,
u: f32,
v: f32,
sw: Option<f32>,
sh: Option<f32>,
) -> ValueResult {
let _ = self.sender.send(GraphicsCommand::Sprite(SpriteCommand {
x,
y,
w,
h,
u,
v,
sw: sw.unwrap_or(w),
sh: sh.unwrap_or(h),
}));
Ok(Value::undefined(ctx))
}
} }
pub struct GraphicsAPI { pub struct GraphicsAPI {
@ -64,18 +91,43 @@ pub struct GraphicsAPI {
impl GraphicsAPI { impl GraphicsAPI {
pub fn define(ctx: &ContextRef, sender: Sender<GraphicsCommand>) -> oden_js::Result<Self> { pub fn define(ctx: &ContextRef, sender: Sender<GraphicsCommand>) -> oden_js::Result<Self> {
let gfx = Arc::new(GraphicsImpl::new(sender)); let gfx = Arc::new(GraphicsImpl::new(sender));
let gfx_a = gfx.clone(); let mut builder = module::NativeModuleBuilder::new(ctx);
let gfx_b = gfx.clone(); {
module::NativeModuleBuilder::new(ctx) let gfx = gfx.clone();
.export( builder.export(
"print", "print",
ctx.new_dynamic_fn(move |ctx, _, args| gfx_a.print_fn(ctx, args))?, ctx.new_dynamic_fn(move |ctx, _, args| gfx.print_fn(ctx, args))?,
)? )?;
.export( }
{
let gfx = gfx.clone();
builder.export(
"cls", "cls",
ctx.new_dynamic_fn(move |ctx, _, args| gfx_b.cls_fn(ctx, args))?, ctx.new_fn(move |ctx: &ContextRef, r: f64, g: f64, b: f64| {
)? gfx.cls_fn(ctx, r, g, b)
.build("graphics")?; })?,
)?;
}
{
let gfx = gfx.clone();
builder.export(
"spr",
ctx.new_fn(
move |ctx: &ContextRef,
x: f32,
y: f32,
w: f32,
h: f32,
u: f32,
v: f32,
sw: Option<f32>,
sh: Option<f32>| {
gfx.spr_fn(ctx, x, y, w, h, u, v, sw, sh)
},
)?,
)?;
}
builder.build("graphics")?;
Ok(GraphicsAPI { gfx }) Ok(GraphicsAPI { gfx })
} }