use std::collections::hash_map::Entry; use std::collections::HashMap; use std::io::Cursor; use std::mem; use glow::{Buffer, HasContext, PixelUnpackData, Program, Texture, VertexArray}; use image::{DynamicImage, EncodableLayout, ImageReader}; use tracing::{debug, error, warn}; use crate::map::tiles::{LayerId, XCoord, YCoord, ZoomLevel}; use crate::map::Tileset; pub struct MapRender { vbo: Buffer, vao: VertexArray, ebo: Buffer, shader_program: Program, test_texture: Texture, texture_cache: HashMap<(ZoomLevel, XCoord, YCoord, LayerId), Texture> } #[allow(unsafe_code)] impl MapRender { pub fn new(gl: &glow::Context) -> Option { use glow::HasContext as _; let shader_version = egui_glow::ShaderVersion::get(gl); unsafe { if !shader_version.is_new_shader_interface() { warn!( "Custom 3D painting hasn't been ported to {:?}", shader_version ); return None; } let vbo = gl.create_buffer().unwrap(); let ebo = gl.create_buffer().unwrap(); let vao = gl.create_vertex_array().unwrap(); let vertex_shader = gl.create_shader(glow::VERTEX_SHADER).unwrap(); gl.shader_source(vertex_shader, include_str!("vertex.glsl")); gl.compile_shader(vertex_shader); if !gl.get_shader_compile_status(vertex_shader) { // shader compilation failed error!("vertex shader compilation failed: {}", gl.get_shader_info_log(vertex_shader)); return None; } let fragment_shader = gl.create_shader(glow::FRAGMENT_SHADER).unwrap(); gl.shader_source(fragment_shader, include_str!("frag.glsl")); gl.compile_shader(fragment_shader); if !gl.get_shader_compile_status(fragment_shader) { // shader compilation failed error!("fragment shader compilation failed: {}", gl.get_shader_info_log(fragment_shader)); return None; } let shader_program = gl.create_program().unwrap(); gl.attach_shader(shader_program, vertex_shader); gl.attach_shader(shader_program, fragment_shader); gl.link_program(shader_program); if !gl.get_program_link_status(shader_program) { error!("linking shader failed: {}", gl.get_program_info_log(shader_program)); } gl.use_program(Some(shader_program)); gl.delete_shader(vertex_shader); gl.delete_shader(fragment_shader); let test_img = ImageReader::new(Cursor::new(include_bytes!("../../0.png"))).with_guessed_format().unwrap().decode().unwrap(); let test_img_rgb8 = test_img.flipv().into_rgb8(); let w = test_img_rgb8.width(); let h = test_img_rgb8.height(); let raw = test_img_rgb8.into_raw(); let test_texture = gl.create_texture().unwrap(); gl.bind_texture(glow::TEXTURE_2D, Some(test_texture)); gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::REPEAT as i32); gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::REPEAT as i32); gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::NEAREST as i32); gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::NEAREST as i32); gl.tex_image_2d( glow::TEXTURE_2D, 0, glow::RGB as i32, w as i32, h as i32, 0, glow::RGB, glow::UNSIGNED_BYTE, PixelUnpackData::Slice(Some(&raw)), ); gl.generate_mipmap(glow::TEXTURE_2D); gl.uniform_1_i32( gl.get_uniform_location(shader_program, "tex").as_ref(), 0 ); Some(Self { vbo, shader_program, vao, ebo, test_texture, texture_cache: HashMap::new() }) } } pub fn destroy(&self, gl: &glow::Context) { use glow::HasContext as _; unsafe { gl.delete_program(self.shader_program); gl.delete_buffer(self.vbo); gl.delete_vertex_array(self.vao); } } pub fn paint(&mut self, gl: &glow::Context, width: f32, height: f32, tileset: Tileset, options: ExtraRenderOptions) { use glow::HasContext as _; let vertices: &[f32] = &[ // X Y Z S T 1.0, 1.0, 0.0, 1.0, 1.0, // top right 1.0, -1.0, 0.0, 1.0, 0.0, // bottom right -1.0, -1.0, 0.0, 0.0, 0.0, // bottom left -1.0, 1.0, 0.0, 0.0, 1.0, // top left ]; let indices: &[u32] = &[ 0, 1, 3, 1, 2, 3 ]; unsafe { gl.clear_color(0.5, 0.1, 0.1, 1.0); gl.clear(glow::COLOR_BUFFER_BIT); let lock = tileset.tiles.lock().unwrap(); for tile in lock.iter() { let this_tile_img_data = &tile.tile; let texture_cache_img_data = self.texture_cache.entry(tile.tileid); if let Entry::Vacant(_) = texture_cache_img_data { if this_tile_img_data.is_none() { continue; } } // nothing to render yet let texture = texture_cache_img_data .or_insert_with(|| { let img = this_tile_img_data.as_ref().unwrap(); let img_rgb8 = img.flipv().into_rgb8(); let w = img_rgb8.width(); let h = img_rgb8.height(); let raw = img_rgb8.into_raw(); let texture = gl.create_texture().unwrap(); gl.bind_texture(glow::TEXTURE_2D, Some(texture)); gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::REPEAT as i32); gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::REPEAT as i32); gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::NEAREST as i32); gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::NEAREST as i32); gl.tex_image_2d( glow::TEXTURE_2D, 0, glow::RGB as i32, w as i32, h as i32, 0, glow::RGB, glow::UNSIGNED_BYTE, PixelUnpackData::Slice(Some(&raw)), ); gl.generate_mipmap(glow::TEXTURE_2D); texture }); gl.bind_vertex_array(Some(self.vao)); gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo)); gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, bytemuck::cast_slice(vertices), glow::DYNAMIC_DRAW); gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.ebo)); gl.buffer_data_u8_slice(glow::ELEMENT_ARRAY_BUFFER, bytemuck::cast_slice(indices), glow::DYNAMIC_DRAW); gl.vertex_attrib_pointer_f32(0, 3, glow::FLOAT, false, (5 * mem::size_of::()) as i32, 0); gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, (5 * mem::size_of::()) as i32, 3 * mem::size_of::() as i32); gl.enable_vertex_attrib_array(0); gl.enable_vertex_attrib_array(1); gl.use_program(Some(self.shader_program)); let w = 256.0_f32; let h = 256.0_f32; let x = 2.0*tile.x / width; let y = 2.0*tile.y / height; let transform = nalgebra_glm::translate(&nalgebra_glm::Mat4::identity(), &nalgebra_glm::vec3(x, y, 0.0)); let transform = nalgebra_glm::scale(&transform, &nalgebra_glm::vec3(w, h, 1.0)); let transform = nalgebra_glm::scale(&transform, &nalgebra_glm::vec3(1.0/width, 1.0/height, 1.0)); // screenspace-ify: // map to 0 to 1 space gl.uniform_matrix_4_f32_slice( gl.get_uniform_location(self.shader_program, "transform").as_ref(), false, (&transform).into(), ); gl.active_texture(glow::TEXTURE0); gl.bind_texture(glow::TEXTURE_2D, Some(*texture)); gl.bind_vertex_array(Some(self.vao)); gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.ebo)); gl.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_INT, 0); } } } } #[derive(Copy, Clone)] pub struct ExtraRenderOptions { }