# HG changeset patch # User fkaa # Date 1553270468 -7200 # Node ID 29dbe9ce8b7d36c66f60d86356f9c7c8ffc60487 # Parent 5e2c892b0222e086baea01615de683b3e05f64e0 add basic map rendering with gl diff -r 5e2c892b0222 -r 29dbe9ce8b7d rust/hwphysics/src/collision.rs --- a/rust/hwphysics/src/collision.rs Thu Mar 21 01:23:05 2019 +0300 +++ b/rust/hwphysics/src/collision.rs Fri Mar 22 18:01:08 2019 +0200 @@ -125,4 +125,4 @@ fn add(&mut self, gear_id: GearId, gear_data: CollisionData) { self.grid.insert_static(gear_id, &gear_data.bounds); } -} \ No newline at end of file +} diff -r 5e2c892b0222 -r 29dbe9ce8b7d rust/hwrunner/Cargo.toml --- a/rust/hwrunner/Cargo.toml Thu Mar 21 01:23:05 2019 +0300 +++ b/rust/hwrunner/Cargo.toml Fri Mar 22 18:01:08 2019 +0200 @@ -5,8 +5,9 @@ edition = "2018" [dependencies] -gfx = "0.17" -glutin = "0.18" -gfx_window_glutin = "0.26" +#gfx = "0.17" +glutin = "0.20" +gl = "0.11" +#gfx_window_glutin = "0.26" lib-hedgewars-engine = { path = "../lib-hedgewars-engine" } diff -r 5e2c892b0222 -r 29dbe9ce8b7d rust/hwrunner/src/main.rs --- a/rust/hwrunner/src/main.rs Thu Mar 21 01:23:05 2019 +0300 +++ b/rust/hwrunner/src/main.rs Fri Mar 22 18:01:08 2019 +0200 @@ -1,24 +1,21 @@ use glutin::{ - dpi::LogicalSize, + dpi, Event, WindowEvent, + DeviceEvent, + ElementState, + MouseButton, + MouseScrollDelta, EventsLoop, - GlWindow, - GlContext + WindowedContext, + GlRequest, + GlProfile, + ContextTrait, }; -use gfx::{ - texture, - format, - Encoder, - Device -}; - -use gfx_window_glutin::init_existing; - use hedgewars_engine::instance::EngineInstance; -fn init(event_loop: &EventsLoop, size: LogicalSize) -> GlWindow { +fn init(event_loop: &EventsLoop, size: dpi::LogicalSize) -> WindowedContext { use glutin::{ ContextBuilder, WindowBuilder @@ -28,41 +25,96 @@ .with_title("hwengine") .with_dimensions(size); - let context = ContextBuilder::new(); - GlWindow::new(window, context, event_loop).unwrap() + let cxt = ContextBuilder::new() + .with_gl(GlRequest::Latest) + .with_gl_profile(GlProfile::Core) + .build_windowed(window, &event_loop).ok().unwrap(); + + unsafe { + cxt.make_current().unwrap(); + gl::load_with(|ptr| cxt.get_proc_address(ptr) as *const _); + + if let Some(sz) = cxt.get_inner_size() { + let phys = sz.to_physical(cxt.get_hidpi_factor()); + + gl::Viewport(0, 0, phys.width as i32, phys.height as i32); + } + } + + cxt } fn main() { let mut event_loop = EventsLoop::new(); - let window = init(&event_loop, LogicalSize::new(1024.0, 768.0)); + let (w, h) = (1024.0, 768.0); + let window = init(&event_loop, dpi::LogicalSize::new(w, h)); - let (mut device, mut factory, color_view, depth_view) = - init_existing::(&window); + let mut engine = EngineInstance::new(); - let mut encoder: Encoder<_, _> = factory.create_command_buffer().into(); + // dirty dirty code follows; DO NOT USE + let mut zoom = 1f32; + let mut dragging = false; + let mut x = 0f32; + let mut y = 0f32; - let engine = EngineInstance::new(); + use std::time::Instant; + let mut now = Instant::now(); + let mut is_running = true; while is_running { + let curr = Instant::now(); + let delta = curr - now; + now = curr; + let ms = delta.as_secs() as f64 * 1000.0 + delta.subsec_millis() as f64; + window.set_title(&format!("hwengine {:.3}ms", ms)); + event_loop.poll_events(|event| { match event { Event::WindowEvent { event, ..} => match event { WindowEvent::CloseRequested => { is_running = false; }, + WindowEvent::MouseInput { button, state, .. } => { + if let MouseButton::Right = button { + if let ElementState::Pressed = state { + dragging = true; + } else { + dragging = false; + } + } + } + WindowEvent::MouseWheel { delta, .. } => { + match delta { + MouseScrollDelta::LineDelta(x, y) => { + zoom += y as f32 * 0.1f32; + } + MouseScrollDelta::PixelDelta(delta) => { + let physical = delta.to_physical(window.get_hidpi_factor()); + zoom += physical.y as f32 * 0.1f32; + } + } + } _ => () }, + Event::DeviceEvent { event, .. } => match event { + DeviceEvent::MouseMotion { delta } => { + if dragging { + x -= delta.0 as f32; + y -= delta.1 as f32; + } + } + _ => {} + } _ => () } }); - encoder.clear(&color_view, [0.5, 0.0, 0.0, 1.0]); - engine.render(&mut encoder, &color_view); + unsafe { window.make_current().unwrap() }; - encoder.flush(&mut device); + // temporary params.. dont actually handle input here + engine.render(x, y, w as f32 * zoom, h as f32 * zoom); window.swap_buffers().unwrap(); - device.cleanup(); } } diff -r 5e2c892b0222 -r 29dbe9ce8b7d rust/land2d/src/lib.rs --- a/rust/land2d/src/lib.rs Thu Mar 21 01:23:05 2019 +0300 +++ b/rust/land2d/src/lib.rs Fri Mar 22 18:01:08 2019 +0200 @@ -30,6 +30,12 @@ &self.pixels.as_slice() } + pub fn raw_pixel_bytes(&self) -> &[u8] { + unsafe { + self.pixels.as_bytes() + } + } + #[inline] pub fn width(&self) -> usize { self.pixels.width() diff -r 5e2c892b0222 -r 29dbe9ce8b7d rust/lib-hedgewars-engine/Cargo.toml --- a/rust/lib-hedgewars-engine/Cargo.toml Thu Mar 21 01:23:05 2019 +0300 +++ b/rust/lib-hedgewars-engine/Cargo.toml Fri Mar 22 18:01:08 2019 +0200 @@ -5,8 +5,9 @@ edition = "2018" [dependencies] -gfx = "0.17" -gfx_device_gl = "0.15" +gl = "0.11" +#gfx = "0.17" +#gfx_device_gl = "0.15" netbuf = "0.4" fpnum = { path = "../fpnum" } @@ -16,6 +17,8 @@ landgen = { path = "../landgen" } hedgewars-engine-messages = { path = "../hedgewars-engine-messages" } hwphysics = { path = "../hwphysics" } +mapgen = { path = "../mapgen" } +vec2d = { path = "../vec2d" } [lib] name = "hedgewars_engine" diff -r 5e2c892b0222 -r 29dbe9ce8b7d rust/lib-hedgewars-engine/src/instance.rs --- a/rust/lib-hedgewars-engine/src/instance.rs Thu Mar 21 01:23:05 2019 +0300 +++ b/rust/lib-hedgewars-engine/src/instance.rs Fri Mar 22 18:01:08 2019 +0200 @@ -3,45 +3,43 @@ UnorderedEngineMessage::*, UnsyncedEngineMessage::*, *, }; -use self::gfx_gl::{CommandBuffer, Resources}; -use gfx::format::{Unorm, D24, R8_G8_B8_A8}; -use gfx_device_gl as gfx_gl; +use landgen::outline_template::OutlineTemplate; +use integral_geometry::{Point, Rect, Size}; use super::{ipc::IPC, world::World}; -pub struct EngineGlContext { - pub device: gfx_gl::Device, - pub factory: gfx_gl::Factory, - pub render_target: gfx::handle::RenderTargetView, - pub depth_buffer: gfx::handle::DepthStencilView, - pub command_buffer: gfx::Encoder, -} - pub struct EngineInstance { pub world: World, pub ipc: IPC, - pub gl_context: Option, } impl EngineInstance { pub fn new() -> Self { - let world = World::new(); + let mut world = World::new(); + + fn template() -> OutlineTemplate { + let mut template = OutlineTemplate::new(Size::new(4096*1, 2048*1)); + template.islands = vec![vec![ + Rect::from_size_coords(100, 2050, 1, 1), + Rect::from_size_coords(100, 500, 400, 1200), + Rect::from_size_coords(3600, 500, 400, 1200), + Rect::from_size_coords(3900, 2050, 1, 1), + ]]; + template.fill_points = vec![Point::new(1, 0)]; + + template + } + + world.init(template()); + Self { world, ipc: IPC::new(), - gl_context: None, } } - pub fn render( - &self, - command_buffer: &mut gfx::Encoder, - render_target: &gfx::handle::RenderTargetView, - ) where - R: gfx::Resources, - C: gfx::CommandBuffer, - { - command_buffer.clear(render_target, [0.0, 0.5, 0.0, 1.0]); + pub fn render(&mut self, x: f32, y: f32, w: f32, h: f32) { + self.world.render(x, y, w, h); } fn process_unordered_message(&mut self, message: &UnorderedEngineMessage) { diff -r 5e2c892b0222 -r 29dbe9ce8b7d rust/lib-hedgewars-engine/src/lib.rs --- a/rust/lib-hedgewars-engine/src/lib.rs Thu Mar 21 01:23:05 2019 +0300 +++ b/rust/lib-hedgewars-engine/src/lib.rs Fri Mar 22 18:01:08 2019 +0200 @@ -3,7 +3,6 @@ mod render; mod world; -use gfx::{format::Formatted, Encoder}; use std::{ ffi::CString, io::{Read, Write}, @@ -11,9 +10,7 @@ os::raw::{c_char, c_void}, }; -use gfx_device_gl as gfx_gl; - -use self::instance::{EngineGlContext, EngineInstance}; +use self::instance::{EngineInstance}; #[repr(C)] #[derive(Copy, Clone)] @@ -84,36 +81,11 @@ height: u16, gl_loader: extern "C" fn(*const c_char) -> *const c_void, ) { - let (device, mut factory) = gfx_gl::create(|name| { - let c_name = CString::new(name).unwrap(); - gl_loader(c_name.as_ptr()) - }); - - let dimensions = (width, height, 1u16, gfx::texture::AaMode::Single); - let (render_target, depth_buffer) = gfx_gl::create_main_targets_raw( - dimensions, - gfx::format::Rgba8::get_format().0, - gfx::format::Depth::get_format().0, - ); - - let mut command_buffer: Encoder<_, _> = factory.create_command_buffer().into(); - - engine_state.gl_context = Some(EngineGlContext { - device, - factory, - render_target: gfx::memory::Typed::new(render_target), - depth_buffer: gfx::memory::Typed::new(depth_buffer), - command_buffer, - }) } #[no_mangle] pub extern "C" fn render_frame(engine_state: &mut EngineInstance) { - let mut context = replace(&mut engine_state.gl_context, None); - if let Some(ref mut c) = context { - engine_state.render(&mut c.command_buffer, &mut c.render_target) - } - replace(&mut engine_state.gl_context, context); + //engine_state.render() } #[no_mangle] diff -r 5e2c892b0222 -r 29dbe9ce8b7d rust/lib-hedgewars-engine/src/render/gl.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/lib-hedgewars-engine/src/render/gl.rs Fri Mar 22 18:01:08 2019 +0200 @@ -0,0 +1,419 @@ + use integral_geometry::Rect; + +use std::{ + mem, + slice, + ptr, + ffi, + ffi::CString, +}; + +#[derive(Debug)] +pub struct Texture2D { + pub handle: u32, +} + +impl Drop for Texture2D { + fn drop(&mut self) { + if self.handle != 0 { + unsafe { + gl::DeleteTextures(1, &self.handle); + } + } + } +} + +impl Texture2D { + pub fn with_data( + data: &[u8], + data_stride: u32, + width: u32, + height: u32, + internal_format: u32, + format: u32, + ty: u32, + filter: u32 + ) -> Self { + let mut handle = 0; + + unsafe { + gl::GenTextures(1, &mut handle); + + gl::BindTexture(gl::TEXTURE_2D, handle); + gl::PixelStorei(gl::UNPACK_ROW_LENGTH, data_stride as i32); + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + internal_format as i32, + width as i32, + height as i32, + 0, + format as u32, + ty, + data.as_ptr() as *const _ + ); + + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, filter as i32); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, filter as i32); + } + + Texture2D { + handle + } + } + + pub fn update(&self, region: Rect, data: &[u8], data_stride: u32, format: u32, ty: u32) { + unsafe { + gl::BindTexture(gl::TEXTURE_2D, self.handle); + gl::PixelStorei(gl::UNPACK_ROW_LENGTH, data_stride as i32); + gl::TexSubImage2D( + gl::TEXTURE_2D, + 0, // texture level + region.left(), // texture region + region.top(), + region.width() as i32 - 1, + region.height() as i32 - 1, + format, // data format + ty, // data type + data.as_ptr() as *const _, // data ptr + ); + } + } +} + +#[derive(Debug)] +pub struct Buffer { + pub handle: u32, + pub ty: u32, + pub usage: u32, +} + +impl Buffer { + pub fn empty( + ty: u32, + usage: u32, + //size: isize + ) -> Buffer { + let mut buffer = 0; + + unsafe { + gl::GenBuffers(1, &mut buffer); + gl::BindBuffer(ty, buffer); + //gl::BufferData(ty, size, ptr::null_mut(), usage); + } + + Buffer { + handle: buffer, + ty, + usage, + } + } + + fn with_data( + ty: u32, + usage: u32, + data: &[u8] + ) -> Buffer { + let mut buffer = 0; + + unsafe { + gl::GenBuffers(1, &mut buffer); + gl::BindBuffer(ty, buffer); + gl::BufferData(ty, data.len() as isize, data.as_ptr() as _, usage); + } + + Buffer { + handle: buffer, + ty, + usage + } + } + + pub fn ty(&self) -> u32 { + self.ty + } + + pub fn handle(&self) -> u32 { + self.handle + } + + pub fn write_typed(&self, data: &[T]) { + unsafe { + let data = slice::from_raw_parts( + data.as_ptr() as *const u8, + data.len() * mem::size_of::(), + ); + + gl::BindBuffer(self.ty, self.handle); + gl::BufferData(self.ty, data.len() as isize, data.as_ptr() as *const _ as *const _, self.usage); + } + } + + pub fn write(&self, data: &[u8]) { + unsafe { + gl::BindBuffer(self.ty, self.handle); + gl::BufferData(self.ty, data.len() as isize, data.as_ptr() as *const _ as *const _, self.usage); + } + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + unsafe { + gl::DeleteBuffers(1, &self.handle); + } + } +} + +#[derive(Debug)] +pub enum VariableBinding<'a> { + Attribute(&'a str, u32), + Uniform(&'a str, u32), + UniformBlock(&'a str, u32), + Sampler(&'a str, u32), +} + +#[derive(Debug)] +pub struct Shader { + pub program: u32, +} + +impl Drop for Shader { + fn drop(&mut self) { + unsafe { + gl::DeleteProgram(self.program); + } + } +} + +impl Shader { + pub fn new<'a>( + vs: &str, + ps: Option<&str>, + bindings: &[VariableBinding<'a>] + ) -> Result { + unsafe fn compile_shader(ty: u32, shdr: &str) -> Result { + let shader = gl::CreateShader(ty); + let len = shdr.len() as i32; + let shdr = shdr.as_ptr() as *const i8; + gl::ShaderSource(shader, 1, &shdr, &len); + gl::CompileShader(shader); + + let mut success = 0i32; + gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut success as _); + + if success == gl::FALSE as i32 { + let mut log_size = 0i32; + gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut log_size as _); + + let mut log = vec![0u8; log_size as usize]; + gl::GetShaderInfoLog(shader, log_size, ptr::null_mut(), log.as_mut_ptr() as _); + + gl::DeleteShader(shader); + Err(String::from_utf8_unchecked(log)) + } else { + Ok(shader) + } + } + + let vs = unsafe { compile_shader(gl::VERTEX_SHADER, vs)? }; + let ps = if let Some(ps) = ps { + Some(unsafe { compile_shader(gl::FRAGMENT_SHADER, ps)? }) + } else { + None + }; + + unsafe { + let program = gl::CreateProgram(); + + gl::AttachShader(program, vs); + if let Some(ps) = ps { + gl::AttachShader(program, ps); + } + + for bind in bindings { + match bind { + &VariableBinding::Attribute(ref name, id) => { + let c_str = CString::new(name.as_bytes()).unwrap(); + gl::BindAttribLocation(program, id, c_str.to_bytes_with_nul().as_ptr() as *const _); + }, + _ => {} + } + } + + gl::LinkProgram(program); + + let mut success = 0i32; + gl::GetProgramiv(program, gl::LINK_STATUS, &mut success); + if success == gl::FALSE as i32 { + let mut log_size = 0i32; + gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut log_size as _); + + let mut log = vec![0u8; log_size as usize]; + gl::GetProgramInfoLog(program, log_size, ptr::null_mut(), log.as_mut_ptr() as _); + + gl::DeleteProgram(program); + return Err(String::from_utf8_unchecked(log)); + } + + //gl::DetachShader(program, vs); + if let Some(ps) = ps { + //gl::DetachShader(program, ps); + } + + gl::UseProgram(program); + + // after linking we setup sampler bindings as specified in the shader + for bind in bindings { + match bind { + VariableBinding::Uniform(name, id) => { + let c_str = CString::new(name.as_bytes()).unwrap(); + let index = gl::GetUniformLocation(program, c_str.to_bytes_with_nul().as_ptr() as *const _); + + // TODO: impl for block? + }, + VariableBinding::UniformBlock(name, id) => { + let c_str = CString::new(name.as_bytes()).unwrap(); + let index = gl::GetUniformBlockIndex(program, c_str.to_bytes_with_nul().as_ptr() as *const _); + + gl::UniformBlockBinding(program, index, *id); + } + VariableBinding::Sampler(name, id) => { + let c_str = CString::new(name.as_bytes()).unwrap(); + let index = gl::GetUniformLocation(program, c_str.to_bytes_with_nul().as_ptr() as *const _); + + gl::Uniform1i(index, *id as i32); + }, + _ => {} + } + } + + Ok(Shader { + program + }) + } + } + + pub fn bind(&self) { + unsafe { + gl::UseProgram(self.program); + } + } + + pub fn set_matrix(&self, name: &str, matrix: *const f32) { + unsafe { + let c_str = CString::new(name).unwrap(); + let index = gl::GetUniformLocation(self.program, c_str.to_bytes_with_nul().as_ptr() as *const _); + + gl::UniformMatrix4fv(index, 1, gl::FALSE, matrix); + } + } + + pub fn bind_texture_2d(&self, index: u32, texture: &Texture2D) { + self.bind(); + + unsafe { + gl::ActiveTexture(gl::TEXTURE0 + index); + gl::BindTexture(gl::TEXTURE_2D, texture.handle); + } + } +} + +pub enum InputFormat { + Float(u32, bool), + Integer(u32), +} + +pub struct InputElement { + pub shader_slot: u32, + pub buffer_slot: u32, + pub format: InputFormat, + pub components: u32, + pub stride: u32, + pub offset: u32, +} + +// TODO: +pub struct InputLayout { + pub elements: Vec, +} + +pub struct LayoutGuard { + vao: u32 +} + +impl Drop for LayoutGuard { + fn drop(&mut self) { + unsafe { + gl::DeleteVertexArrays(1, [self.vao].as_ptr()); + gl::BindVertexArray(0); + } + } +} + +impl InputLayout { + pub fn new(elements: Vec) -> Self { + InputLayout { + elements, + } + } + + pub fn bind(&mut self, buffers: &[(u32, &Buffer)], index_buffer: Option<&Buffer>) -> LayoutGuard { + let mut vao = 0; + + unsafe { + gl::GenVertexArrays(1, &mut vao); + gl::BindVertexArray(vao); + } + + for &(slot, ref buffer) in buffers { + unsafe { + gl::BindBuffer(buffer.ty(), buffer.handle()); + } + + for attr in self.elements.iter().filter(|a| a.buffer_slot == slot) { + unsafe { + gl::EnableVertexAttribArray(attr.shader_slot); + match attr.format { + InputFormat::Float(fmt, normalized) => { + gl::VertexAttribPointer( + attr.shader_slot, + attr.components as i32, + fmt, + if normalized { + gl::TRUE + } else { + gl::FALSE + }, + attr.stride as i32, + attr.offset as *const _ + ); + } + InputFormat::Integer(fmt) => { + gl::VertexAttribIPointer( + attr.shader_slot, + attr.components as i32, + fmt, + attr.stride as i32, + attr.offset as *const _ + ); + } + } + + } + } + } + + if let Some(buf) = index_buffer { + unsafe { + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, buf.handle()); + } + } + + LayoutGuard { + vao + } + } +} diff -r 5e2c892b0222 -r 29dbe9ce8b7d rust/lib-hedgewars-engine/src/render/map.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/lib-hedgewars-engine/src/render/map.rs Fri Mar 22 18:01:08 2019 +0200 @@ -0,0 +1,300 @@ +use integral_geometry::{Point, Rect, Size}; +use land2d::{Land2D}; +use vec2d::{Vec2D}; + +use super::gl::{ + Texture2D, + Buffer, + Shader, + InputLayout, + VariableBinding, + InputElement, + InputFormat, +}; + +// TODO: temp +const VERTEX_SHADER: &'static str = r#" +#version 150 + +in vec2 Position; +in vec3 Uv; + +out vec3 a_Uv; + +//uniform Common { +uniform mat4 Projection; +//}; + +void main() +{ + a_Uv = Uv; + gl_Position = Projection * vec4(Position, 0.0, 1.0); +} +"#; + +const PIXEL_SHADER: &'static str = r#" +#version 150 + +in vec3 a_Uv; + +uniform sampler2D Texture; + +out vec4 Target; + +void main() +{ + Target = texture2D(Texture, a_Uv.xy); +} +"#; + +pub struct MapTile { + // either index into GL texture array or emulated [Texture; N] + texture_index: u32, + + width: u32, + height: u32, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct TileVertex { + pos: [f32; 2], + // doesn't hurt to include another float, just in case.. + uv: [f32; 3], +} + +pub struct DrawTile { + texture_index: u32, + index_len: u32, +} + +pub struct MapRenderer { + tiles: Vec, + textures: Vec, + + tile_vertex_buffer: Buffer, + tile_index_buffer: Buffer, + tile_vertices: Vec, + tile_indices: Vec, + tile_draw_calls: Vec, + index_offset: u16, + tile_shader: Shader, + tile_layout: InputLayout, + + tile_width: u32, + tile_height: u32, + num_tile_x: i32, +} + +impl MapRenderer { + pub fn new(tile_width: u32, tile_height: u32) -> Self { + let tile_shader = Shader::new( + VERTEX_SHADER, + Some(PIXEL_SHADER), + &[ + VariableBinding::Attribute("Position", 0), + VariableBinding::Attribute("Uv", 1), + VariableBinding::Sampler("Texture", 0), + ] + ).unwrap(); + + let tile_layout = InputLayout::new(vec![ + // position + InputElement { + shader_slot: 0, + buffer_slot: 0, + format: InputFormat::Float(gl::FLOAT, false), + components: 2, + stride: 20, + offset: 0 + }, + // uv + InputElement { + shader_slot: 1, + buffer_slot: 0, + format: InputFormat::Float(gl::FLOAT, false), + components: 3, + stride: 20, + offset: 8 + }, + ]); + + MapRenderer { + tiles: Vec::new(), + textures: Vec::new(), + + tile_vertex_buffer: Buffer::empty(gl::ARRAY_BUFFER, gl::DYNAMIC_DRAW), + tile_index_buffer: Buffer::empty(gl::ELEMENT_ARRAY_BUFFER, gl::DYNAMIC_DRAW), + tile_vertices: Vec::new(), + tile_indices: Vec::new(), + index_offset: 0, + + tile_draw_calls: Vec::new(), + tile_shader, + tile_layout, + + tile_width, + tile_height, + num_tile_x: 0, + } + } + + pub fn init(&mut self, land: &Vec2D) { + // clear tiles, but keep our textures for potential re-use + self.tiles.clear(); + + let tw = self.tile_width as usize; + let th = self.tile_height as usize; + let lw = land.width(); + let lh = land.height(); + let num_tile_x = lw / tw + if lw % tw != 0 { 1 } else { 0 }; + let num_tile_y = lh / th + if lh % th != 0 { 1 } else { 0 }; + + self.num_tile_x = num_tile_x as i32; + + for y in 0..num_tile_y { + for x in 0..num_tile_x { + let idx = x + y * num_tile_x; + + let (data, stride) = { + let bpp = 4; + + let offset = x * tw * bpp + y * th * lw * bpp; + + let data = unsafe { &land.as_bytes()[offset..] }; + let stride = land.width(); + + (data, stride as u32) + }; + + let texture_index = if idx >= self.textures.len() { + let texture = Texture2D::with_data( + data, + stride, + self.tile_width, + self.tile_height, + gl::RGBA8, + gl::RGBA, + gl::UNSIGNED_BYTE, + gl::NEAREST + ); + + let texture_index = self.textures.len(); + self.textures.push(texture); + + texture_index + } else { + let texture_region = Rect::new( + Point::new(0, 0), + Point::new(self.tile_width as i32, self.tile_height as i32) + ); + + self.textures[idx].update(texture_region, data, stride, gl::RGBA, gl::UNSIGNED_BYTE); + idx + }; + + let tile = MapTile { + texture_index: texture_index as u32, + + // TODO: are there ever non-power of two textures? + width: self.tile_width, + height: self.tile_height, + }; + self.tiles.push(tile); + } + } + } + + pub fn update(&mut self, land: &Land2D, region: Rect) { + + } + + pub fn render(&mut self, viewport: Rect) { + self.tile_vertices.clear(); + self.tile_indices.clear(); + self.tile_draw_calls.clear(); + self.index_offset = 0; + + for (idx, tile) in self.tiles.iter().enumerate() { + let tile_x = idx as i32 % self.num_tile_x; + let tile_y = idx as i32 / self.num_tile_x; + let tile_w = self.tile_width as i32; + let tile_h = self.tile_height as i32; + + let origin = Point::new(tile_x * tile_w, tile_y * tile_h); + let tile_rect = Rect::new(origin, origin + Point::new(tile_w, tile_h)); + + if viewport.intersects(&tile_rect) { + // lazy + //dbg!(origin); + let tile_x = origin.x as f32; + let tile_y = origin.y as f32; + let tile_w = tile_x + tile_w as f32; + let tile_h = tile_y + tile_h as f32; + let uv_depth = tile.texture_index as f32; + + //dbg!(tile_x); + let tl = TileVertex { pos: [tile_x, tile_y], uv: [0f32, 0f32, uv_depth] }; + let bl = TileVertex { pos: [tile_x, tile_h], uv: [0f32, 1f32, uv_depth] }; + let br = TileVertex { pos: [tile_w, tile_h], uv: [1f32, 1f32, uv_depth] }; + let tr = TileVertex { pos: [tile_w, tile_y], uv: [1f32, 0f32, uv_depth] }; + + self.tile_vertices.extend(&[tl, bl, br, tr]); + + let i = self.index_offset; + self.tile_indices.extend(&[ + i + 0, i + 1, i + 2, + i + 2, i + 3, i + 0, + ]); + self.index_offset += 4; + + self.tile_draw_calls.push(DrawTile { + texture_index: tile.texture_index, + index_len: 6 + }); + } + } + + self.tile_vertex_buffer.write_typed(&self.tile_vertices); + self.tile_index_buffer.write_typed(&self.tile_indices); + + let _g = self.tile_layout.bind(&[ + (0, &self.tile_vertex_buffer) + ], Some(&self.tile_index_buffer)); + + let ortho = { + let l = viewport.left() as f32; + let r = viewport.right() as f32; + let b = viewport.bottom() as f32; + let t = viewport.top() as f32; + + [ + 2f32 / (r - l), 0f32, 0f32, 0f32, + 0f32, 2f32 / (t - b), 0f32, 0f32, + 0f32, 0f32, 0.5f32, 0f32, + (r + l) / (l - r), (t + b) / (b - t), 0.5f32, 1f32, + ] + }; + + self.tile_shader.bind(); + self.tile_shader.set_matrix("Projection", ortho.as_ptr()); + + let mut draw_offset = 0; + for draw_call in &self.tile_draw_calls { + unsafe { + self.tile_shader.bind_texture_2d(0, &self.textures[draw_call.texture_index as usize]); + + gl::DrawElements( + gl::TRIANGLES, + draw_call.index_len as i32, + gl::UNSIGNED_SHORT, + draw_offset as *const _ + ); + } + + draw_offset += draw_call.index_len * 2; + } + } +} + + diff -r 5e2c892b0222 -r 29dbe9ce8b7d rust/lib-hedgewars-engine/src/render/mod.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/lib-hedgewars-engine/src/render/mod.rs Fri Mar 22 18:01:08 2019 +0200 @@ -0,0 +1,5 @@ +mod map; +mod gl; + +pub use self::map::*; +use self::gl::*; diff -r 5e2c892b0222 -r 29dbe9ce8b7d rust/lib-hedgewars-engine/src/world.rs --- a/rust/lib-hedgewars-engine/src/world.rs Thu Mar 21 01:23:05 2019 +0300 +++ b/rust/lib-hedgewars-engine/src/world.rs Fri Mar 22 18:01:08 2019 +0200 @@ -10,6 +10,8 @@ use lfprng::LaggedFibonacciPRNG; use hwphysics as hwp; +use crate::render::MapRenderer; + struct GameState { land: Land2D, physics: hwp::World, @@ -28,6 +30,7 @@ random_numbers_gen: LaggedFibonacciPRNG, preview: Option>, game_state: Option, + renderer: MapRenderer, } impl World { @@ -36,6 +39,7 @@ random_numbers_gen: LaggedFibonacciPRNG::new(&[]), preview: None, game_state: None, + renderer: MapRenderer::new(512, 512), } } @@ -77,12 +81,38 @@ let landgen = TemplatedLandGenerator::new(template); let land = landgen.generate_land(¶ms, &mut self.random_numbers_gen); + use mapgen::{ + MapGenerator, + theme::{Theme, slice_u32_to_u8} + }; + + use std::path::Path; + + let theme = Theme::load(Path::new("../../share/hedgewars/Data/Themes/Cheese/")).unwrap(); + let texture = MapGenerator::new().make_texture32(&land, &theme); + self.renderer.init(&texture); + self.game_state = Some(GameState::new(land, physics)); } + pub fn render(&mut self, x: f32, y: f32, w: f32, h: f32) { + unsafe { + gl::ClearColor(0.4f32, 0f32, 0.2f32, 1f32); + gl::Clear(gl::COLOR_BUFFER_BIT); + } + + self.renderer.render(Rect::new( + Point::new(x as _, y as _), + Point::new((x + w) as _, (y + h) as _), + )); + } + pub fn step(&mut self) { if let Some(ref mut state) = self.game_state { state.physics.step(fp!(1), &state.land); } } } + + + diff -r 5e2c892b0222 -r 29dbe9ce8b7d rust/mapgen/src/lib.rs --- a/rust/mapgen/src/lib.rs Thu Mar 21 01:23:05 2019 +0300 +++ b/rust/mapgen/src/lib.rs Fri Mar 22 18:01:08 2019 +0200 @@ -170,6 +170,72 @@ texture } + + // TODO: no way to pass both u8 & u32? + pub fn make_texture32(&self, land: &Land2D, theme: &Theme) -> Vec2D { + let mut texture = Vec2D::new(land.size(), 0); + + if let Some(land_sprite) = theme.land_texture() { + for (row_index, (land_row, tex_row)) in land.rows() + .zip(texture.rows_mut()) + .enumerate() + { + let sprite_row = land_sprite.get_row(row_index % land_sprite.height()); + let mut x_offset = 0; + while sprite_row.len() < land.width() - x_offset { + let copy_range = x_offset..x_offset + sprite_row.len(); + tex_row_copy32( + &land_row[copy_range.clone()], + &mut tex_row[copy_range], + sprite_row + ); + + x_offset += land_sprite.width() + } + + if x_offset < land.width() { + let final_range = x_offset..land.width(); + tex_row_copy32( + &land_row[final_range.clone()], + &mut tex_row[final_range], + &sprite_row[..land.width() - x_offset] + ); + } + } + } + + if let Some(border_sprite) = theme.border_texture() { + assert!(border_sprite.height() <= 512); + let border_width = (border_sprite.height() / 2) as u8; + let border_sprite = border_sprite.to_tiled(); + + let mut offsets = vec![255u8; land.width()]; + + land_border_pass32( + land.rows().rev().zip(texture.rows_mut().rev()), + &mut offsets, + border_width, + |x, y| border_sprite.get_pixel( + x % border_sprite.width(), + border_sprite.height() - 1 - y, + ) + ); + + offsets.iter_mut().for_each(|v| *v = 255); + + land_border_pass32( + land.rows().zip(texture.rows_mut()), + &mut offsets, + border_width, + |x, y| border_sprite.get_pixel( + x % border_sprite.width(), + y, + ) + ); + } + + texture + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -238,6 +304,31 @@ } } +fn land_border_pass32<'a, T, F>(rows: T, offsets: &mut [u8], border_width: u8, pixel_getter: F) + where T: Iterator, + F: (Fn(usize, usize) -> u32) +{ + for (land_row, tex_row) in rows { + for (x, ((land_v, tex_v), offset_v)) in land_row.iter() + .zip(tex_row.iter_mut()) + .zip(offsets.iter_mut()) + .enumerate() + { + *offset_v = if *land_v == 0 { + if *offset_v < border_width { + *tex_v = blend( + pixel_getter(x, *offset_v as usize), + *tex_v, + ) + } + offset_v.saturating_add(1) + } else { + 0 + } + } + } +} + fn tex_row_copy(land_row: &[u8], tex_row: &mut [u32], sprite_row: &[u32]) { for ((land_v, tex_v), sprite_v) in land_row.iter().zip(tex_row.iter_mut()).zip(sprite_row) @@ -250,6 +341,18 @@ } } +fn tex_row_copy32(land_row: &[u32], tex_row: &mut [u32], sprite_row: &[u32]) { + for ((land_v, tex_v), sprite_v) in + land_row.iter().zip(tex_row.iter_mut()).zip(sprite_row) + { + *tex_v = if *land_v == 0 { + *sprite_v + } else { + 0 + } + } +} + #[cfg(test)] mod tests { use crate::{ diff -r 5e2c892b0222 -r 29dbe9ce8b7d rust/vec2d/src/lib.rs --- a/rust/vec2d/src/lib.rs Thu Mar 21 01:23:05 2019 +0300 +++ b/rust/vec2d/src/lib.rs Fri Mar 22 18:01:08 2019 +0200 @@ -95,6 +95,19 @@ let width = self.width(); self.data.chunks_exact_mut(width) } + + #[inline] + pub unsafe fn as_bytes(&self) -> &[u8] { + use std::{ + slice, + mem + }; + + slice::from_raw_parts( + self.data.as_ptr() as *const u8, + self.data.len() * mem::size_of::(), + ) + } } impl AsRef<[T]> for Vec2D {