2025-03-08 15:30:13 -05:00
|
|
|
use std::collections::hash_map::Entry;
|
|
|
|
use std::collections::HashMap;
|
2025-03-08 13:29:30 -05:00
|
|
|
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};
|
2025-03-08 15:30:13 -05:00
|
|
|
use crate::map::tiles::{LayerId, XCoord, YCoord, ZoomLevel};
|
2025-03-07 23:34:29 -05:00
|
|
|
use crate::map::Tileset;
|
|
|
|
|
|
|
|
pub struct MapRender {
|
2025-03-08 13:29:30 -05:00
|
|
|
vbo: Buffer,
|
|
|
|
vao: VertexArray,
|
|
|
|
ebo: Buffer,
|
|
|
|
shader_program: Program,
|
|
|
|
test_texture: Texture,
|
2025-03-08 15:30:13 -05:00
|
|
|
texture_cache: HashMap<(ZoomLevel, XCoord, YCoord, LayerId), Texture>
|
2025-03-07 23:34:29 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(unsafe_code)]
|
|
|
|
impl MapRender {
|
|
|
|
pub fn new(gl: &glow::Context) -> Option<Self> {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2025-03-08 13:29:30 -05:00
|
|
|
let vbo = gl.create_buffer().unwrap();
|
|
|
|
let ebo = gl.create_buffer().unwrap();
|
|
|
|
let vao = gl.create_vertex_array().unwrap();
|
2025-03-07 23:34:29 -05:00
|
|
|
|
2025-03-08 13:29:30 -05:00
|
|
|
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;
|
|
|
|
}
|
2025-03-07 23:34:29 -05:00
|
|
|
|
2025-03-08 13:29:30 -05:00
|
|
|
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;
|
2025-03-07 23:34:29 -05:00
|
|
|
}
|
|
|
|
|
2025-03-08 13:29:30 -05:00
|
|
|
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
|
|
|
|
);
|
2025-03-07 23:34:29 -05:00
|
|
|
|
|
|
|
Some(Self {
|
2025-03-08 13:29:30 -05:00
|
|
|
vbo,
|
|
|
|
shader_program,
|
|
|
|
vao,
|
|
|
|
ebo,
|
|
|
|
test_texture,
|
2025-03-08 15:30:13 -05:00
|
|
|
texture_cache: HashMap::new()
|
2025-03-07 23:34:29 -05:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn destroy(&self, gl: &glow::Context) {
|
|
|
|
use glow::HasContext as _;
|
|
|
|
unsafe {
|
2025-03-08 13:29:30 -05:00
|
|
|
gl.delete_program(self.shader_program);
|
|
|
|
gl.delete_buffer(self.vbo);
|
|
|
|
gl.delete_vertex_array(self.vao);
|
2025-03-07 23:34:29 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-08 15:30:13 -05:00
|
|
|
pub fn paint(&mut self, gl: &glow::Context, width: f32, height: f32, tileset: Tileset, options: ExtraRenderOptions) {
|
2025-03-07 23:34:29 -05:00
|
|
|
use glow::HasContext as _;
|
2025-03-08 13:29:30 -05:00
|
|
|
|
2025-03-08 23:28:23 -05:00
|
|
|
let vertices: &[f32] = &[
|
2025-03-08 13:29:30 -05:00
|
|
|
// X Y Z S T
|
2025-03-08 15:30:13 -05:00
|
|
|
1.0, 1.0, 0.0, 1.0, 1.0, // top right
|
2025-03-08 23:28:23 -05:00
|
|
|
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
|
2025-03-08 13:29:30 -05:00
|
|
|
];
|
2025-03-08 23:28:23 -05:00
|
|
|
let indices: &[u32] = &[
|
2025-03-08 13:29:30 -05:00
|
|
|
0, 1, 3,
|
|
|
|
1, 2, 3
|
|
|
|
];
|
|
|
|
|
2025-03-07 23:34:29 -05:00
|
|
|
unsafe {
|
2025-03-08 13:29:30 -05:00
|
|
|
gl.clear_color(0.5, 0.1, 0.1, 1.0);
|
|
|
|
gl.clear(glow::COLOR_BUFFER_BIT);
|
|
|
|
|
2025-03-08 15:30:13 -05:00
|
|
|
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));
|
2025-03-08 23:28:23 -05:00
|
|
|
gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, bytemuck::cast_slice(vertices), glow::DYNAMIC_DRAW);
|
2025-03-08 15:30:13 -05:00
|
|
|
|
|
|
|
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.ebo));
|
2025-03-08 23:28:23 -05:00
|
|
|
gl.buffer_data_u8_slice(glow::ELEMENT_ARRAY_BUFFER, bytemuck::cast_slice(indices), glow::DYNAMIC_DRAW);
|
2025-03-08 15:30:13 -05:00
|
|
|
|
|
|
|
gl.vertex_attrib_pointer_f32(0, 3, glow::FLOAT, false, (5 * mem::size_of::<f32>()) as i32, 0);
|
|
|
|
gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, (5 * mem::size_of::<f32>()) as i32, 3 * mem::size_of::<f32>() 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;
|
2025-03-08 23:28:23 -05:00
|
|
|
let x = 2.0*tile.x / width;
|
|
|
|
let y = 2.0*tile.y / height;
|
2025-03-08 15:30:13 -05:00
|
|
|
|
|
|
|
let transform = nalgebra_glm::translate(&nalgebra_glm::Mat4::identity(), &nalgebra_glm::vec3(x, y, 0.0));
|
2025-03-08 23:28:23 -05:00
|
|
|
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));
|
2025-03-08 15:30:13 -05:00
|
|
|
|
|
|
|
// 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(),
|
|
|
|
);
|
2025-03-08 13:29:30 -05:00
|
|
|
|
2025-03-08 15:30:13 -05:00
|
|
|
gl.active_texture(glow::TEXTURE0);
|
2025-03-08 13:29:30 -05:00
|
|
|
|
2025-03-08 15:30:13 -05:00
|
|
|
gl.bind_texture(glow::TEXTURE_2D, Some(*texture));
|
2025-03-08 13:29:30 -05:00
|
|
|
|
2025-03-08 15:30:13 -05:00
|
|
|
gl.bind_vertex_array(Some(self.vao));
|
|
|
|
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.ebo));
|
2025-03-07 23:34:29 -05:00
|
|
|
|
|
|
|
|
2025-03-08 15:30:13 -05:00
|
|
|
gl.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_INT, 0);
|
|
|
|
}
|
2025-03-08 13:29:30 -05:00
|
|
|
|
2025-03-07 23:34:29 -05:00
|
|
|
}
|
|
|
|
}
|
2025-03-08 13:29:30 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
pub struct ExtraRenderOptions {
|
2025-03-07 23:34:29 -05:00
|
|
|
}
|