aboutsummaryrefslogtreecommitdiff
path: root/renderer
diff options
context:
space:
mode:
authormetamuffin <yvchraiqi@protonmail.com>2022-06-09 09:46:00 +0200
committermetamuffin <yvchraiqi@protonmail.com>2022-06-09 09:46:00 +0200
commit7a0d09e5cd0075e2a0d3db4505d7ec77dff35ae0 (patch)
tree5586745b7a9b871b31512cba9f964dabda4651f0 /renderer
downloadtwclient-7a0d09e5cd0075e2a0d3db4505d7ec77dff35ae0.tar
twclient-7a0d09e5cd0075e2a0d3db4505d7ec77dff35ae0.tar.bz2
twclient-7a0d09e5cd0075e2a0d3db4505d7ec77dff35ae0.tar.zst
(reset git)
Diffstat (limited to 'renderer')
-rw-r--r--renderer/Cargo.toml18
-rw-r--r--renderer/src/main.rs254
-rw-r--r--renderer/src/map.rs163
3 files changed, 435 insertions, 0 deletions
diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml
new file mode 100644
index 0000000..6ec23ca
--- /dev/null
+++ b/renderer/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "twrenderer"
+version = "0.1.0"
+edition = "2021"
+authors = [
+ "metamuffin <metamuffin@disroot.org>",
+ "heinrich5991 <heinrich5991@gmail.com>",
+]
+license = "AGPL-3.0-only"
+
+[dependencies]
+twclient = { path = "../client" }
+skia-safe = { version = "0.50.0", features = ["gl", "wayland"] }
+glutin = "0.28.0"
+gl = "0.14.0"
+log = "0.4.17"
+env_logger = "0.9.0"
+signal-hook = "0.3.14"
diff --git a/renderer/src/main.rs b/renderer/src/main.rs
new file mode 100644
index 0000000..d763336
--- /dev/null
+++ b/renderer/src/main.rs
@@ -0,0 +1,254 @@
+pub mod map;
+
+use glutin::{
+ event::{Event, KeyboardInput, VirtualKeyCode, WindowEvent},
+ event_loop::{ControlFlow, EventLoop},
+ window::{Window, WindowBuilder},
+ ContextWrapper, GlProfile, PossiblyCurrent,
+};
+use log::{error, info, warn};
+use map::MapRenderer;
+use signal_hook::{
+ consts::{SIGINT, SIGTERM},
+ iterator::Signals,
+};
+use skia_safe::{
+ gpu::{gl::FramebufferInfo, BackendRenderTarget, SurfaceOrigin},
+ Canvas, Color, Color4f, ColorSpace, ColorType, Paint, Point, Surface,
+};
+use std::{
+ convert::TryInto, net::IpAddr, process::exit, str::FromStr, sync::atomic::Ordering, thread,
+ time::Duration,
+};
+use twclient::{
+ client::{Client, ClientConfig, ClientInterface},
+ world::World,
+ SHOULD_EXIT,
+};
+
+fn main() {
+ env_logger::init();
+
+ let event_loop = EventLoop::new();
+ let wb = WindowBuilder::new().with_title("teeworlds");
+
+ let cb = glutin::ContextBuilder::new()
+ .with_depth_buffer(0)
+ .with_stencil_buffer(8)
+ .with_pixel_format(24, 8)
+ .with_gl_profile(GlProfile::Core);
+
+ // TODO
+ // #[cfg(not(feature = "wayland"))]
+ // let cb = cb.with_double_buffer(Some(true));
+
+ let windowed_context = cb.build_windowed(wb, &event_loop).unwrap();
+
+ let windowed_context = unsafe { windowed_context.make_current().unwrap() };
+
+ gl::load_with(|s| windowed_context.get_proc_address(s));
+
+ let mut gr_context = skia_safe::gpu::DirectContext::new_gl(None, None).unwrap();
+
+ let fb_info = {
+ use gl::types::GLint;
+ let mut fboid: GLint = 0;
+ unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) };
+
+ FramebufferInfo {
+ fboid: fboid.try_into().unwrap(),
+ format: skia_safe::gpu::gl::Format::RGBA8.into(),
+ }
+ };
+
+ windowed_context
+ .window()
+ .set_inner_size(glutin::dpi::Size::new(glutin::dpi::LogicalSize::new(
+ 1024.0, 1024.0,
+ )));
+
+ let surface = create_surface(&windowed_context, &fb_info, &mut gr_context);
+
+ struct Env {
+ surface: Surface,
+ gr_context: skia_safe::gpu::DirectContext,
+ windowed_context: ContextWrapper<PossiblyCurrent, Window>,
+ }
+
+ let mut env = Env {
+ surface,
+ gr_context,
+ windowed_context,
+ };
+
+ let mut args = std::env::args().skip(1);
+ let ip = IpAddr::from_str(args.next().unwrap().as_str()).unwrap();
+ let port = u16::from_str(args.next().unwrap().as_str()).unwrap();
+ let mut evloop = Client::new_evloop();
+ let (client, client_interface) = Client::new(
+ &mut evloop,
+ ip,
+ port,
+ ClientConfig {
+ nick: "metamuffin".to_string(),
+ clan: "rustacean".to_string(),
+ timeout: "sdfhaiusdfhus".to_string(),
+ },
+ );
+ let mut network_thread = Some(std::thread::spawn(move || client.run(evloop)));
+
+ let mut signals = Signals::new(&[SIGTERM, SIGINT]).unwrap();
+ info!("setting up signal handlers");
+ thread::spawn(move || {
+ for sig in signals.forever() {
+ warn!("received signal {:?}", sig);
+ SHOULD_EXIT.store(true, Ordering::Relaxed);
+ thread::sleep(Duration::from_secs(3));
+ error!("exit timeout!");
+ exit(1);
+ }
+ });
+
+ let mut renderer = Renderer {
+ client_interface,
+ map_renderer: MapRenderer::new(),
+ world: World::new(),
+ };
+
+ event_loop.run(move |event, _, control_flow| {
+ *control_flow = ControlFlow::Wait;
+
+ #[allow(deprecated)]
+ match event {
+ Event::LoopDestroyed => {}
+ Event::RedrawEventsCleared => {
+ if SHOULD_EXIT.load(Ordering::Relaxed) {
+ warn!("waiting for networt thread to finish");
+ network_thread.take().unwrap().join().unwrap();
+ warn!("exiting renderer");
+ exit(0);
+ }
+ renderer.tick();
+ env.windowed_context.window().request_redraw();
+ }
+ Event::WindowEvent { event, .. } => match event {
+ WindowEvent::Resized(physical_size) => {
+ env.surface =
+ create_surface(&env.windowed_context, &fb_info, &mut env.gr_context);
+ env.windowed_context.resize(physical_size)
+ }
+ WindowEvent::CloseRequested => {
+ warn!("renderer event loop stopped, telling the client to exit too");
+ SHOULD_EXIT.store(true, Ordering::Relaxed);
+ *control_flow = ControlFlow::Exit
+ }
+ WindowEvent::KeyboardInput {
+ input:
+ KeyboardInput {
+ virtual_keycode,
+ modifiers,
+ ..
+ },
+ ..
+ } => {
+ if modifiers.logo() {
+ if let Some(VirtualKeyCode::Q) = virtual_keycode {
+ *control_flow = ControlFlow::Exit;
+ }
+ }
+ env.windowed_context.window().request_redraw();
+ }
+ _ => (),
+ },
+ Event::RedrawRequested(_) => {
+ {
+ let dims = (env.surface.width() as f32, env.surface.height() as f32);
+ let canvas = env.surface.canvas();
+ renderer.draw(canvas, dims)
+ }
+ env.surface.canvas().flush();
+ env.windowed_context.swap_buffers().unwrap();
+ }
+ _ => (),
+ }
+ });
+}
+
+pub struct Renderer {
+ client_interface: ClientInterface,
+ map_renderer: MapRenderer,
+ world: World,
+}
+
+impl Renderer {
+ pub fn tick(&mut self) {
+ for m in self.client_interface.receive.try_iter() {
+ self.world.update(&m);
+ match m {
+ twclient::client::ClientMesgOut::MapChange { .. } => {
+ self.map_renderer.map_changed(&self.world)
+ }
+ _ => (),
+ }
+ }
+ }
+ pub fn draw(&mut self, canvas: &mut Canvas, dims: (f32, f32)) {
+ canvas.clear(Color::TRANSPARENT);
+ let center = self.world.local_tee().map(|t| (t.x, t.y)).unwrap_or((0, 0));
+
+ canvas.save();
+ canvas.translate((dims.0 / 2.0, dims.1 / 2.0));
+ canvas.translate((-center.0 as f32, -center.1 as f32));
+
+ self.map_renderer.draw(&self.world, canvas);
+
+ let tee_paint = Paint::new(
+ Color4f {
+ a: 1.0,
+ r: 1.0,
+ g: 0.0,
+ b: 1.0,
+ },
+ &ColorSpace::new_srgb(),
+ );
+ for t in self.world.tees.values() {
+ canvas.draw_circle(
+ Point {
+ x: t.x as f32,
+ y: t.y as f32,
+ },
+ 16.0,
+ &tee_paint,
+ );
+ }
+
+ canvas.restore();
+ }
+}
+
+fn create_surface(
+ windowed_context: &ContextWrapper<PossiblyCurrent, Window>,
+ fb_info: &FramebufferInfo,
+ gr_context: &mut skia_safe::gpu::DirectContext,
+) -> skia_safe::Surface {
+ let pixel_format = windowed_context.get_pixel_format();
+ let size = windowed_context.window().inner_size();
+ let backend_render_target = BackendRenderTarget::new_gl(
+ (
+ size.width.try_into().unwrap(),
+ size.height.try_into().unwrap(),
+ ),
+ pixel_format.multisampling.map(|s| s.try_into().unwrap()),
+ pixel_format.stencil_bits.try_into().unwrap(),
+ *fb_info,
+ );
+ Surface::from_backend_render_target(
+ gr_context,
+ &backend_render_target,
+ SurfaceOrigin::BottomLeft,
+ ColorType::RGBA8888,
+ None,
+ None,
+ )
+ .unwrap()
+}
diff --git a/renderer/src/map.rs b/renderer/src/map.rs
new file mode 100644
index 0000000..0ea3a6b
--- /dev/null
+++ b/renderer/src/map.rs
@@ -0,0 +1,163 @@
+use std::collections::HashMap;
+
+use log::{info, warn};
+use skia_safe::{
+ canvas::SrcRectConstraint, Canvas, Color4f, ColorSpace, ISize, Image, Paint, Rect,
+};
+use twclient::world::{
+ map::{format, TILE_NUM},
+ World,
+};
+
+pub struct MapRenderer {
+ tileset: HashMap<Option<usize>, Image>,
+}
+
+impl MapRenderer {
+ pub fn new() -> Self {
+ Self {
+ tileset: HashMap::new(),
+ }
+ }
+
+ pub fn tick(&mut self) {}
+ pub fn map_changed(&mut self, world: &World) {
+ self.tileset.clear();
+ for (key, t) in &world.map.tilesets {
+ let mut bytes: Vec<u8> = Vec::with_capacity(t.dim().0 * t.dim().1 * 4);
+ info!("loading tileset: {:?} => {:?}", key, t.dim());
+ for ((_x, _y), c) in t.indexed_iter() {
+ bytes.push(c.red);
+ bytes.push(c.green);
+ bytes.push(c.blue);
+ bytes.push(c.alpha);
+ }
+ let d = skia_safe::Data::new_copy(&bytes);
+ let v = skia_safe::Image::from_raster_data(
+ &skia_safe::ImageInfo::new(
+ ISize::new(t.dim().0 as i32, t.dim().1 as i32),
+ skia_safe::ColorType::RGBA8888,
+ skia_safe::AlphaType::Opaque,
+ None,
+ ),
+ d,
+ t.dim().0 * 4,
+ )
+ .unwrap();
+ self.tileset.insert(*key, v);
+ }
+ }
+
+ pub fn draw(&mut self, world: &World, canvas: &mut Canvas) {
+ let mut grid_paint = Paint::new(
+ Color4f {
+ a: 1.0,
+ r: 1.0,
+ g: 1.0,
+ b: 1.0,
+ },
+ &ColorSpace::new_srgb(),
+ );
+ grid_paint.set_style(skia_safe::PaintStyle::Stroke);
+ grid_paint.set_anti_alias(true);
+
+ let center = world
+ .local_tee()
+ .map(|t| (t.x / 16, t.y / 16))
+ .unwrap_or((0, 0));
+
+ let tile_rect = Rect {
+ top: center.1 as f32 * 16.0,
+ left: center.0 as f32 * 16.0,
+ bottom: center.1 as f32 * 16.0 + 16.0,
+ right: center.0 as f32 * 16.0 + 16.0,
+ };
+ canvas.draw_rect(tile_rect, &grid_paint);
+
+ for l in &world.map.layers {
+ let tileset = match world.map.tilesets.get(&l.image) {
+ Some(t) => t,
+ None => {
+ warn!("missing tileset for {:?}, skipping layer", l.image);
+ continue;
+ }
+ };
+ if tileset.dim() == (1, 1) {
+ continue;
+ }
+
+ for layer_y in (center.1 - 30)..(center.1 + 30) {
+ for layer_x in (center.0 - 30)..(center.0 + 30) {
+ let layer_x = layer_x.try_into().unwrap_or(0);
+ let layer_y = layer_y.try_into().unwrap_or(0);
+
+ if layer_x >= l.tiles.dim().1 || layer_y >= l.tiles.dim().0 {
+ continue;
+ }
+ let tile = l.tiles[(layer_y, layer_x)];
+
+ let _rotate = tile.flags & format::TILEFLAG_ROTATE != 0;
+ let _vflip = tile.flags & format::TILEFLAG_VFLIP != 0;
+ let _hflip = tile.flags & format::TILEFLAG_HFLIP != 0;
+ let tile_x = tile.index as u32 % TILE_NUM;
+ let tile_y = tile.index as u32 / TILE_NUM;
+
+ if tile_x == 0 && tile_y == 0 {
+ continue;
+ }
+
+ let tile_rect = Rect {
+ top: layer_y as f32 * 16.0,
+ left: layer_x as f32 * 16.0,
+ bottom: layer_y as f32 * 16.0 + 16.0,
+ right: layer_x as f32 * 16.0 + 16.0,
+ };
+ canvas.draw_rect(tile_rect, &grid_paint);
+
+ const TL: u32 = 16;
+ canvas.draw_image_rect(
+ self.tileset.get(&l.image).unwrap(),
+ Some((
+ &Rect {
+ top: (tile_y * TL) as f32,
+ left: (tile_x * TL) as f32,
+ bottom: ((tile_y + 1) * TL) as f32,
+ right: ((tile_x + 1) * TL) as f32,
+ },
+ SrcRectConstraint::Strict,
+ )),
+ tile_rect,
+ &Paint::default(),
+ );
+ // for iy in 0..TL {
+ // for ix in 0..TL {
+ // let (x, y) = ((layer_x as u32 * TL + iy), (layer_y as u32 * TL + ix));
+ // let p_tile =
+ // tileset[((tile_y * TL + iy) as usize, (tile_x * TL + ix) as usize)];
+ // let mut p = Paint::new(
+ // Color4f {
+ // a: 0.0,
+ // b: 0.0,
+ // g: 0.0,
+ // r: 0.0,
+ // },
+ // &ColorSpace::new_srgb(),
+ // );
+ // p.set_argb(p_tile.alpha, p_tile.red, p_tile.green, p_tile.green);
+ // p.set_style(PaintStyle::Fill);
+ // canvas.draw_rect(
+ // Rect {
+ // left: x as f32,
+ // top: y as f32,
+ // right: x as f32 + 1.0,
+ // bottom: y as f32 + 1.0,
+ // },
+ // &p,
+ // );
+ // }
+ // }
+ }
+ }
+ }
+ }
+}