diff --git a/Cargo.lock b/Cargo.lock index 25cff6c8..64dcd0ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -511,6 +511,7 @@ dependencies = [ "nfd 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "pathfinder_geometry 0.3.0", "pathfinder_gl 0.1.0", + "pathfinder_gpu 0.1.0", "pathfinder_renderer 0.1.0", "pathfinder_svg 0.1.0", "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -537,12 +538,23 @@ dependencies = [ "gl 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "pathfinder_geometry 0.3.0", + "pathfinder_gpu 0.1.0", "pathfinder_renderer 0.1.0", + "pathfinder_simd 0.3.0", "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pathfinder_gpu" +version = "0.1.0" +dependencies = [ + "image 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pathfinder_geometry 0.3.0", + "pathfinder_simd 0.3.0", +] + [[package]] name = "pathfinder_renderer" version = "0.1.0" @@ -570,6 +582,10 @@ dependencies = [ "usvg 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pathfinder_ui" +version = "0.1.0" + [[package]] name = "phf" version = "0.7.24" @@ -1045,10 +1061,6 @@ name = "ucd-util" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "ui" -version = "0.1.0" - [[package]] name = "unicode-segmentation" version = "1.2.1" diff --git a/Cargo.toml b/Cargo.toml index 87f39d63..83839536 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "demo/native", "geometry", "gl", + "gpu", "renderer", "simd", "svg", diff --git a/demo/common/Cargo.toml b/demo/common/Cargo.toml index 45916487..4d9ca20e 100644 --- a/demo/common/Cargo.toml +++ b/demo/common/Cargo.toml @@ -24,6 +24,9 @@ path = "../../geometry" [dependencies.pathfinder_gl] path = "../../gl" +[dependencies.pathfinder_gpu] +path = "../../gpu" + [dependencies.pathfinder_renderer] path = "../../renderer" diff --git a/demo/common/src/device.rs b/demo/common/src/device.rs index a9362bac..9e252435 100644 --- a/demo/common/src/device.rs +++ b/demo/common/src/device.rs @@ -11,11 +11,9 @@ //! GPU rendering code specifically for the demo. use crate::GRIDLINE_COUNT; -use gl::types::{GLsizei, GLvoid}; -use pathfinder_gl::device::{Buffer, BufferTarget, BufferUploadMode, Device, Program, Uniform}; -use pathfinder_gl::device::{VertexArray, VertexAttr}; -use pathfinder_renderer::paint::ColorU; +use pathfinder_gpu::{BufferTarget, BufferUploadMode, Device, Resources, VertexAttrType}; +/* pub struct DemoDevice { #[allow(dead_code)] device: Device, @@ -62,65 +60,67 @@ impl DemoDevice { pixels } } +*/ -pub struct GroundProgram { - pub program: Program, - pub transform_uniform: Uniform, - pub color_uniform: Uniform, +pub struct GroundProgram where D: Device { + pub program: D::Program, + pub transform_uniform: D::Uniform, + pub color_uniform: D::Uniform, } -impl GroundProgram { - pub fn new(device: &Device) -> GroundProgram { - let program = device.create_program("demo_ground"); - let transform_uniform = Uniform::new(&program, "Transform"); - let color_uniform = Uniform::new(&program, "Color"); +impl GroundProgram where D: Device { + pub fn new(device: &D, resources: &Resources) -> GroundProgram { + let program = device.create_program(resources, "demo_ground"); + let transform_uniform = device.get_uniform(&program, "Transform"); + let color_uniform = device.get_uniform(&program, "Color"); GroundProgram { program, transform_uniform, color_uniform } } } -pub struct GroundSolidVertexArray { - pub vertex_array: VertexArray, +pub struct GroundSolidVertexArray where D: Device { + pub vertex_array: D::VertexArray, } -impl GroundSolidVertexArray { - pub fn new(ground_program: &GroundProgram, quad_vertex_positions_buffer: &Buffer) - -> GroundSolidVertexArray { - let vertex_array = VertexArray::new(); - unsafe { - let position_attr = VertexAttr::new(&ground_program.program, "Position"); +impl GroundSolidVertexArray where D: Device { + pub fn new(device: &D, + ground_program: &GroundProgram, + quad_vertex_positions_buffer: &D::Buffer) + -> GroundSolidVertexArray { + let vertex_array = device.create_vertex_array(); - gl::BindVertexArray(vertex_array.gl_vertex_array); - gl::UseProgram(ground_program.program.gl_program); - gl::BindBuffer(gl::ARRAY_BUFFER, quad_vertex_positions_buffer.gl_buffer); - position_attr.configure_float(2, gl::UNSIGNED_BYTE, false, 0, 0, 0); - } + let position_attr = device.get_vertex_attr(&ground_program.program, "Position"); + + device.bind_vertex_array(&vertex_array); + device.use_program(&ground_program.program); + device.bind_buffer(quad_vertex_positions_buffer, BufferTarget::Vertex); + device.configure_float_vertex_attr(&position_attr, 2, VertexAttrType::U8, false, 0, 0, 0); GroundSolidVertexArray { vertex_array } } } -pub struct GroundLineVertexArray { - pub vertex_array: VertexArray, +pub struct GroundLineVertexArray where D: Device { + pub vertex_array: D::VertexArray, #[allow(dead_code)] - grid_vertex_positions_buffer: Buffer, + grid_vertex_positions_buffer: D::Buffer, } -impl GroundLineVertexArray { - pub fn new(ground_program: &GroundProgram) -> GroundLineVertexArray { - let grid_vertex_positions_buffer = Buffer::new(); - grid_vertex_positions_buffer.upload(&create_grid_vertex_positions(), - BufferTarget::Vertex, - BufferUploadMode::Static); +impl GroundLineVertexArray where D: Device { + pub fn new(device: &D, ground_program: &GroundProgram) -> GroundLineVertexArray { + let grid_vertex_positions_buffer = device.create_buffer(); + device.upload_to_buffer(&grid_vertex_positions_buffer, + &create_grid_vertex_positions(), + BufferTarget::Vertex, + BufferUploadMode::Static); - let vertex_array = VertexArray::new(); - unsafe { - let position_attr = VertexAttr::new(&ground_program.program, "Position"); + let vertex_array = device.create_vertex_array(); - gl::BindVertexArray(vertex_array.gl_vertex_array); - gl::UseProgram(ground_program.program.gl_program); - gl::BindBuffer(gl::ARRAY_BUFFER, grid_vertex_positions_buffer.gl_buffer); - position_attr.configure_float(2, gl::UNSIGNED_BYTE, false, 0, 0, 0); - } + let position_attr = device.get_vertex_attr(&ground_program.program, "Position"); + + device.bind_vertex_array(&vertex_array); + device.use_program(&ground_program.program); + device.bind_buffer(&grid_vertex_positions_buffer, BufferTarget::Vertex); + device.configure_float_vertex_attr(&position_attr, 2, VertexAttrType::U8, false, 0, 0, 0); GroundLineVertexArray { vertex_array, grid_vertex_positions_buffer } } diff --git a/demo/common/src/lib.rs b/demo/common/src/lib.rs index 1189d042..383191fa 100644 --- a/demo/common/src/lib.rs +++ b/demo/common/src/lib.rs @@ -10,7 +10,7 @@ //! A demo app for Pathfinder. -use crate::device::{DemoDevice, GroundLineVertexArray, GroundProgram, GroundSolidVertexArray}; +use crate::device::{GroundLineVertexArray, GroundProgram, GroundSolidVertexArray}; use crate::ui::{DemoUI, UIAction, UIEvent}; use clap::{App, Arg}; use gl::types::GLsizei; @@ -20,8 +20,9 @@ use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32, Point3DF32}; use pathfinder_geometry::basic::rect::RectF32; use pathfinder_geometry::basic::transform2d::Transform2DF32; use pathfinder_geometry::basic::transform3d::{Perspective, Transform3DF32}; -use pathfinder_gl::device::Device; +use pathfinder_gl::device::GLDevice; use pathfinder_gl::renderer::Renderer; +use pathfinder_gpu::{Device, Resources}; use pathfinder_renderer::builder::{RenderOptions, RenderTransform, SceneBuilder}; use pathfinder_renderer::gpu_data::BuiltScene; use pathfinder_renderer::paint::ColorU; @@ -96,14 +97,13 @@ pub struct DemoApp { mouselook_enabled: bool, dirty: bool, - ui: DemoUI, + ui: DemoUI, scene_thread_proxy: SceneThreadProxy, - renderer: Renderer, + renderer: Renderer, - device: DemoDevice, - ground_program: GroundProgram, - ground_solid_vertex_array: GroundSolidVertexArray, - ground_line_vertex_array: GroundLineVertexArray, + ground_program: GroundProgram, + ground_solid_vertex_array: GroundSolidVertexArray, + ground_line_vertex_array: GroundLineVertexArray, } impl DemoApp { @@ -130,8 +130,9 @@ impl DemoApp { let sdl_event_pump = sdl_context.event_pump().unwrap(); - let device = Device::new(); - let options = Options::get(&device); + let device = GLDevice::new(); + let resources = Resources::locate(); + let options = Options::get(&resources); let (window_width, _) = window.size(); let (drawable_width, drawable_height) = window.drawable_size(); @@ -139,7 +140,7 @@ impl DemoApp { let base_scene = load_scene(&options.input_path); let scene_view_box = base_scene.view_box; - let renderer = Renderer::new(&device, drawable_size); + let renderer = Renderer::new(device, &resources, drawable_size); let scene_thread_proxy = SceneThreadProxy::new(base_scene, options.clone()); update_drawable_size(&window, &scene_thread_proxy); @@ -149,10 +150,15 @@ impl DemoApp { Camera::new_2d(scene_view_box, drawable_size) }; - let ground_program = GroundProgram::new(&device); + let ground_program = GroundProgram::new(&renderer.device, &resources); let ground_solid_vertex_array = - GroundSolidVertexArray::new(&ground_program, &renderer.quad_vertex_positions_buffer()); - let ground_line_vertex_array = GroundLineVertexArray::new(&ground_program); + GroundSolidVertexArray::new(&renderer.device, + &ground_program, + &renderer.quad_vertex_positions_buffer()); + let ground_line_vertex_array = GroundLineVertexArray::new(&renderer.device, + &ground_program); + + let ui = DemoUI::new(&renderer.device, &resources, options); DemoApp { window, @@ -173,11 +179,10 @@ impl DemoApp { mouselook_enabled: false, dirty: true, - ui: DemoUI::new(&device, options), + ui, scene_thread_proxy, renderer, - device: DemoDevice::new(device), ground_program, ground_solid_vertex_array, ground_line_vertex_array, @@ -342,7 +347,7 @@ impl DemoApp { tile_time, } = render_msg; - self.device.clear(self.background_color()); + self.renderer.device.clear(Some(self.background_color().to_f32().0), Some(1.0), Some(0)); self.draw_environment(&render_transform); self.render_vector_scene(&built_scene); @@ -353,14 +358,17 @@ impl DemoApp { let rendering_time = self.renderer.shift_timer_query(); let stats = built_scene.stats(); self.renderer.debug_ui.add_sample(stats, tile_time, rendering_time); - self.renderer.debug_ui.draw(); + self.renderer.debug_ui.draw(&self.renderer.device); if !ui_event.is_none() { self.dirty = true; } let mut ui_action = UIAction::None; - self.ui.update(&mut self.renderer.debug_ui, &mut ui_event, &mut ui_action); + self.ui.update(&self.renderer.device, + &mut self.renderer.debug_ui, + &mut ui_event, + &mut ui_action); self.handle_ui_action(&mut ui_action); // Switch camera mode (2D/3D) if requested. @@ -548,7 +556,8 @@ impl DemoApp { fn take_screenshot(&mut self) { let screenshot_path = self.pending_screenshot_path.take().unwrap(); let (drawable_width, drawable_height) = self.window.drawable_size(); - let pixels = self.device.readback_pixels(drawable_width, drawable_height); + let drawable_size = Point2DI32::new(drawable_width as i32, drawable_height as i32); + let pixels = self.renderer.device.read_pixels_from_default_framebuffer(drawable_size); image::save_buffer(screenshot_path, &pixels, drawable_width, @@ -644,7 +653,7 @@ pub struct Options { } impl Options { - fn get(device: &Device) -> Options { + fn get(resources: &Resources) -> Options { let matches = App::new("tile-svg") .arg( Arg::with_name("jobs") @@ -666,7 +675,7 @@ impl Options { let input_path = match matches.value_of("INPUT") { Some(path) => PathBuf::from(path), None => { - let mut path = device.resources_directory.clone(); + let mut path = resources.resources_directory.clone(); path.push("svg"); path.push(DEFAULT_SVG_FILENAME); path diff --git a/demo/common/src/ui.rs b/demo/common/src/ui.rs index e3cd77c9..39a0f2c4 100644 --- a/demo/common/src/ui.rs +++ b/demo/common/src/ui.rs @@ -13,7 +13,7 @@ use nfd::Response; use pathfinder_geometry::basic::point::Point2DI32; use pathfinder_geometry::basic::rect::RectI32; use pathfinder_gl::debug::{DebugUI, PADDING, TEXT_COLOR, WINDOW_COLOR}; -use pathfinder_gl::device::{Device, Texture}; +use pathfinder_gpu::{Device, Resources}; use pathfinder_renderer::paint::ColorU; use std::f32::consts::PI; use std::path::PathBuf; @@ -57,15 +57,15 @@ static BG_LIGHT_PNG_NAME: &'static str = "demo-bg-light"; static BG_DARK_PNG_NAME: &'static str = "demo-bg-dark"; static SCREENSHOT_PNG_NAME: &'static str = "demo-screenshot"; -pub struct DemoUI { - effects_texture: Texture, - open_texture: Texture, - rotate_texture: Texture, - zoom_in_texture: Texture, - zoom_out_texture: Texture, - bg_light_texture: Texture, - bg_dark_texture: Texture, - screenshot_texture: Texture, +pub struct DemoUI where D: Device { + effects_texture: D::Texture, + open_texture: D::Texture, + rotate_texture: D::Texture, + zoom_in_texture: D::Texture, + zoom_out_texture: D::Texture, + bg_light_texture: D::Texture, + bg_dark_texture: D::Texture, + screenshot_texture: D::Texture, effects_panel_visible: bool, rotate_panel_visible: bool, @@ -78,16 +78,16 @@ pub struct DemoUI { pub rotation: i32, } -impl DemoUI { - pub fn new(device: &Device, options: Options) -> DemoUI { - let effects_texture = device.create_texture_from_png(EFFECTS_PNG_NAME); - let open_texture = device.create_texture_from_png(OPEN_PNG_NAME); - let rotate_texture = device.create_texture_from_png(ROTATE_PNG_NAME); - let zoom_in_texture = device.create_texture_from_png(ZOOM_IN_PNG_NAME); - let zoom_out_texture = device.create_texture_from_png(ZOOM_OUT_PNG_NAME); - let bg_light_texture = device.create_texture_from_png(BG_LIGHT_PNG_NAME); - let bg_dark_texture = device.create_texture_from_png(BG_DARK_PNG_NAME); - let screenshot_texture = device.create_texture_from_png(SCREENSHOT_PNG_NAME); +impl DemoUI where D: Device { + pub fn new(device: &D, resources: &Resources, options: Options) -> DemoUI { + let effects_texture = device.create_texture_from_png(resources, EFFECTS_PNG_NAME); + let open_texture = device.create_texture_from_png(resources, OPEN_PNG_NAME); + let rotate_texture = device.create_texture_from_png(resources, ROTATE_PNG_NAME); + let zoom_in_texture = device.create_texture_from_png(resources, ZOOM_IN_PNG_NAME); + let zoom_out_texture = device.create_texture_from_png(resources, ZOOM_OUT_PNG_NAME); + let bg_light_texture = device.create_texture_from_png(resources, BG_LIGHT_PNG_NAME); + let bg_dark_texture = device.create_texture_from_png(resources, BG_DARK_PNG_NAME); + let screenshot_texture = device.create_texture_from_png(resources, SCREENSHOT_PNG_NAME); DemoUI { effects_texture, @@ -115,19 +115,27 @@ impl DemoUI { (self.rotation as f32 / SLIDER_WIDTH as f32 * 2.0 - 1.0) * PI } - pub fn update(&mut self, debug_ui: &mut DebugUI, event: &mut UIEvent, action: &mut UIAction) { + pub fn update(&mut self, + device: &D, + debug_ui: &mut DebugUI, + event: &mut UIEvent, + action: &mut UIAction) { let bottom = debug_ui.framebuffer_size().y() - PADDING; // Draw effects button. let effects_button_position = Point2DI32::new(PADDING, bottom - BUTTON_HEIGHT); - if self.draw_button(debug_ui, event, effects_button_position, &self.effects_texture) { + if self.draw_button(device, + debug_ui, + event, + effects_button_position, + &self.effects_texture) { self.effects_panel_visible = !self.effects_panel_visible; } // Draw open button. let lower_button_y = bottom - BUTTON_HEIGHT; let open_button_position = Point2DI32::new(OPEN_BUTTON_X, lower_button_y); - if self.draw_button(debug_ui, event, open_button_position, &self.open_texture) { + if self.draw_button(device, debug_ui, event, open_button_position, &self.open_texture) { if let Ok(Response::Okay(file)) = nfd::open_file_dialog(Some("svg"), None) { *action = UIAction::OpenFile(PathBuf::from(file)); } @@ -135,7 +143,8 @@ impl DemoUI { // Draw screenshot button. let screenshot_button_position = Point2DI32::new(SCREENSHOT_BUTTON_X, lower_button_y); - if self.draw_button(debug_ui, + if self.draw_button(device, + debug_ui, event, screenshot_button_position, &self.screenshot_texture) { @@ -146,7 +155,8 @@ impl DemoUI { // Draw 3D switch. let three_d_switch_origin = Point2DI32::new(THREE_D_SWITCH_X, lower_button_y); - self.three_d_enabled = self.draw_text_switch(debug_ui, + self.three_d_enabled = self.draw_text_switch(device, + debug_ui, event, three_d_switch_origin, "2D", @@ -155,7 +165,8 @@ impl DemoUI { // Draw background switch. let background_switch_origin = Point2DI32::new(BACKGROUND_SWITCH_X, lower_button_y); - self.dark_background_enabled = self.draw_image_switch(debug_ui, + self.dark_background_enabled = self.draw_image_switch(device, + debug_ui, event, background_switch_origin, &self.bg_light_texture, @@ -166,19 +177,28 @@ impl DemoUI { if !self.three_d_enabled { let rotate_button_y = bottom - BUTTON_HEIGHT; let rotate_button_position = Point2DI32::new(ROTATE_PANEL_X, rotate_button_y); - if self.draw_button(debug_ui, event, rotate_button_position, &self.rotate_texture) { + if self.draw_button(device, + debug_ui, + event, + rotate_button_position, + &self.rotate_texture) { self.rotate_panel_visible = !self.rotate_panel_visible; } let zoom_in_button_x = ROTATE_PANEL_X + BUTTON_WIDTH + PADDING; let zoom_in_button_position = Point2DI32::new(zoom_in_button_x, rotate_button_y); - if self.draw_button(debug_ui, event, zoom_in_button_position, &self.zoom_in_texture) { + if self.draw_button(device, + debug_ui, + event, + zoom_in_button_position, + &self.zoom_in_texture) { *action = UIAction::ZoomIn; } let zoom_out_button_x = ROTATE_PANEL_X + (BUTTON_WIDTH + PADDING) * 2; let zoom_out_button_position = Point2DI32::new(zoom_out_button_x, rotate_button_y); - if self.draw_button(debug_ui, + if self.draw_button(device, + debug_ui, event, zoom_out_button_position, &self.zoom_out_texture) { @@ -187,40 +207,44 @@ impl DemoUI { } // Draw effects panel, if necessary. - self.draw_effects_panel(debug_ui, event); + self.draw_effects_panel(device, debug_ui, event); // Draw rotate panel, if necessary. - self.draw_rotate_panel(debug_ui, event, action); + self.draw_rotate_panel(device, debug_ui, event, action); } - fn draw_effects_panel(&mut self, debug_ui: &mut DebugUI, event: &mut UIEvent) { + fn draw_effects_panel(&mut self, device: &D, debug_ui: &mut DebugUI, event: &mut UIEvent) { if !self.effects_panel_visible { return; } let bottom = debug_ui.framebuffer_size().y() - PADDING; let effects_panel_y = bottom - (BUTTON_HEIGHT + PADDING + EFFECTS_PANEL_HEIGHT); - debug_ui.draw_solid_rounded_rect(RectI32::new(Point2DI32::new(PADDING, effects_panel_y), + debug_ui.draw_solid_rounded_rect(device, + RectI32::new(Point2DI32::new(PADDING, effects_panel_y), Point2DI32::new(EFFECTS_PANEL_WIDTH, EFFECTS_PANEL_HEIGHT)), WINDOW_COLOR); self.gamma_correction_effect_enabled = - self.draw_effects_switch(debug_ui, + self.draw_effects_switch(device, + debug_ui, event, "Gamma Correction", 0, effects_panel_y, self.gamma_correction_effect_enabled); self.stem_darkening_effect_enabled = - self.draw_effects_switch(debug_ui, + self.draw_effects_switch(device, + debug_ui, event, "Stem Darkening", 1, effects_panel_y, self.stem_darkening_effect_enabled); self.subpixel_aa_effect_enabled = - self.draw_effects_switch(debug_ui, + self.draw_effects_switch(device, + debug_ui, event, "Subpixel AA", 2, @@ -230,7 +254,8 @@ impl DemoUI { } fn draw_rotate_panel(&mut self, - debug_ui: &mut DebugUI, + device: &D, + debug_ui: &mut DebugUI, event: &mut UIEvent, action: &mut UIAction) { if !self.rotate_panel_visible { @@ -241,7 +266,8 @@ impl DemoUI { let rotate_panel_y = bottom - (BUTTON_HEIGHT + PADDING + ROTATE_PANEL_HEIGHT); let rotate_panel_origin = Point2DI32::new(ROTATE_PANEL_X, rotate_panel_y); let rotate_panel_size = Point2DI32::new(ROTATE_PANEL_WIDTH, ROTATE_PANEL_HEIGHT); - debug_ui.draw_solid_rounded_rect(RectI32::new(rotate_panel_origin, rotate_panel_size), + debug_ui.draw_solid_rounded_rect(device, + RectI32::new(rotate_panel_origin, rotate_panel_size), WINDOW_COLOR); let (widget_x, widget_y) = (ROTATE_PANEL_X + PADDING, rotate_panel_y + PADDING); @@ -257,32 +283,35 @@ impl DemoUI { let slider_track_rect = RectI32::new(Point2DI32::new(widget_x, slider_track_y), Point2DI32::new(SLIDER_WIDTH, SLIDER_TRACK_HEIGHT)); - debug_ui.draw_rect_outline(slider_track_rect, TEXT_COLOR); + debug_ui.draw_rect_outline(device, slider_track_rect, TEXT_COLOR); let slider_knob_x = widget_x + self.rotation - SLIDER_KNOB_WIDTH / 2; let slider_knob_rect = RectI32::new(Point2DI32::new(slider_knob_x, widget_y), Point2DI32::new(SLIDER_KNOB_WIDTH, SLIDER_KNOB_HEIGHT)); - debug_ui.draw_solid_rect(slider_knob_rect, TEXT_COLOR); + debug_ui.draw_solid_rect(device, slider_knob_rect, TEXT_COLOR); } fn draw_button(&self, - debug_ui: &mut DebugUI, + device: &D, + debug_ui: &mut DebugUI, event: &mut UIEvent, origin: Point2DI32, - texture: &Texture) + texture: &D::Texture) -> bool { let button_rect = RectI32::new(origin, Point2DI32::new(BUTTON_WIDTH, BUTTON_HEIGHT)); - debug_ui.draw_solid_rounded_rect(button_rect, WINDOW_COLOR); - debug_ui.draw_rounded_rect_outline(button_rect, OUTLINE_COLOR); - debug_ui.draw_texture(origin + Point2DI32::new(PADDING, PADDING), + debug_ui.draw_solid_rounded_rect(device, button_rect, WINDOW_COLOR); + debug_ui.draw_rounded_rect_outline(device, button_rect, OUTLINE_COLOR); + debug_ui.draw_texture(device, + origin + Point2DI32::new(PADDING, PADDING), texture, BUTTON_ICON_COLOR); event.handle_mouse_down_in_rect(button_rect).is_some() } fn draw_effects_switch(&self, - debug_ui: &mut DebugUI, + device: &D, + debug_ui: &mut DebugUI, event: &mut UIEvent, text: &str, index: i32, @@ -291,11 +320,12 @@ impl DemoUI { -> bool { let text_x = PADDING * 2; let text_y = window_y + PADDING + BUTTON_TEXT_OFFSET + (BUTTON_HEIGHT + PADDING) * index; - debug_ui.draw_text(text, Point2DI32::new(text_x, text_y), false); + debug_ui.draw_text(device, text, Point2DI32::new(text_x, text_y), false); let switch_x = PADDING + EFFECTS_PANEL_WIDTH - (SWITCH_SIZE + PADDING); let switch_y = window_y + PADDING + (BUTTON_HEIGHT + PADDING) * index; - self.draw_text_switch(debug_ui, + self.draw_text_switch(device, + debug_ui, event, Point2DI32::new(switch_x, switch_y), "Off", @@ -304,14 +334,15 @@ impl DemoUI { } fn draw_text_switch(&self, - debug_ui: &mut DebugUI, + device: &D, + debug_ui: &mut DebugUI, event: &mut UIEvent, origin: Point2DI32, off_text: &str, on_text: &str, mut value: bool) -> bool { - value = self.draw_switch(debug_ui, event, origin, value); + value = self.draw_switch(device, debug_ui, event, origin, value); let off_size = debug_ui.measure_text(off_text); let on_size = debug_ui.measure_text(on_text); @@ -319,32 +350,39 @@ impl DemoUI { let on_offset = SWITCH_HALF_SIZE + SWITCH_HALF_SIZE / 2 - on_size / 2; let text_top = BUTTON_TEXT_OFFSET; - debug_ui.draw_text(off_text, origin + Point2DI32::new(off_offset, text_top), !value); - debug_ui.draw_text(on_text, origin + Point2DI32::new(on_offset, text_top), value); + debug_ui.draw_text(device, + off_text, + origin + Point2DI32::new(off_offset, text_top), + !value); + debug_ui.draw_text(device, on_text, origin + Point2DI32::new(on_offset, text_top), value); value } fn draw_image_switch(&self, - debug_ui: &mut DebugUI, + device: &D, + debug_ui: &mut DebugUI, event: &mut UIEvent, origin: Point2DI32, - off_texture: &Texture, - on_texture: &Texture, + off_texture: &D::Texture, + on_texture: &D::Texture, mut value: bool) -> bool { - value = self.draw_switch(debug_ui, event, origin, value); + value = self.draw_switch(device, debug_ui, event, origin, value); - let off_offset = SWITCH_HALF_SIZE / 2 - off_texture.size.x() / 2; - let on_offset = SWITCH_HALF_SIZE + SWITCH_HALF_SIZE / 2 - on_texture.size.x() / 2; + let off_offset = SWITCH_HALF_SIZE / 2 - device.texture_size(off_texture).x() / 2; + let on_offset = SWITCH_HALF_SIZE + SWITCH_HALF_SIZE / 2 - + device.texture_size(on_texture).x() / 2; let off_color = if !value { WINDOW_COLOR } else { TEXT_COLOR }; let on_color = if value { WINDOW_COLOR } else { TEXT_COLOR }; - debug_ui.draw_texture(origin + Point2DI32::new(off_offset, PADDING), + debug_ui.draw_texture(device, + origin + Point2DI32::new(off_offset, PADDING), off_texture, off_color); - debug_ui.draw_texture(origin + Point2DI32::new(on_offset, PADDING), + debug_ui.draw_texture(device, + origin + Point2DI32::new(on_offset, PADDING), on_texture, on_color); @@ -352,7 +390,8 @@ impl DemoUI { } fn draw_switch(&self, - debug_ui: &mut DebugUI, + device: &D, + debug_ui: &mut DebugUI, event: &mut UIEvent, origin: Point2DI32, mut value: bool) @@ -362,15 +401,18 @@ impl DemoUI { value = !value; } - debug_ui.draw_solid_rounded_rect(widget_rect, WINDOW_COLOR); - debug_ui.draw_rounded_rect_outline(widget_rect, OUTLINE_COLOR); + debug_ui.draw_solid_rounded_rect(device, widget_rect, WINDOW_COLOR); + debug_ui.draw_rounded_rect_outline(device, widget_rect, OUTLINE_COLOR); let highlight_size = Point2DI32::new(SWITCH_HALF_SIZE, BUTTON_HEIGHT); if !value { - debug_ui.draw_solid_rounded_rect(RectI32::new(origin, highlight_size), TEXT_COLOR); + debug_ui.draw_solid_rounded_rect(device, + RectI32::new(origin, highlight_size), + TEXT_COLOR); } else { let x_offset = SWITCH_HALF_SIZE + 1; - debug_ui.draw_solid_rounded_rect(RectI32::new(origin + Point2DI32::new(x_offset, 0), + debug_ui.draw_solid_rounded_rect(device, + RectI32::new(origin + Point2DI32::new(x_offset, 0), highlight_size), TEXT_COLOR); } diff --git a/geometry/src/basic/transform3d.rs b/geometry/src/basic/transform3d.rs index d05170c7..14d9f5a8 100644 --- a/geometry/src/basic/transform3d.rs +++ b/geometry/src/basic/transform3d.rs @@ -144,13 +144,6 @@ impl Transform3DF32 { } } - #[inline] - pub fn transpose(&self) -> Transform3DF32 { - let mut m = *self; - F32x4::transpose_4x4(&mut m.c0, &mut m.c1, &mut m.c2, &mut m.c3); - m - } - // FIXME(pcwalton): Is this right, due to transposition? I think we may have to reverse the // two. // @@ -384,19 +377,6 @@ mod test { assert_eq!(a.transform_point(p), q); } - #[test] - fn test_transpose() { - let a = Transform3DF32::row_major(3.0, 1.0, 4.0, 5.0, - 9.0, 2.0, 6.0, 5.0, - 3.0, 5.0, 8.0, 9.0, - 7.0, 9.0, 3.0, 2.0); - let b = Transform3DF32::row_major(3.0, 9.0, 3.0, 7.0, - 1.0, 2.0, 5.0, 9.0, - 4.0, 6.0, 8.0, 3.0, - 5.0, 5.0, 9.0, 2.0); - assert_eq!(a.transpose(), b); - } - #[test] fn test_inverse() { // Random matrix. diff --git a/gl/Cargo.toml b/gl/Cargo.toml index 51b026c3..e2e69101 100644 --- a/gl/Cargo.toml +++ b/gl/Cargo.toml @@ -18,5 +18,11 @@ features = ["png_codec"] [dependencies.pathfinder_geometry] path = "../geometry" +[dependencies.pathfinder_gpu] +path = "../gpu" + [dependencies.pathfinder_renderer] path = "../renderer" + +[dependencies.pathfinder_simd] +path = "../simd" diff --git a/gl/src/debug.rs b/gl/src/debug.rs index 848b2f69..b47ca092 100644 --- a/gl/src/debug.rs +++ b/gl/src/debug.rs @@ -15,26 +15,24 @@ //! //! The debug font atlas was generated using: https://evanw.github.io/font-texture-generator/ -use crate::device::{Buffer, BufferTarget, BufferUploadMode, Device, Program, Texture}; -use crate::device::{Uniform, VertexAttr}; -use gl::types::{GLfloat, GLint, GLsizei, GLuint}; -use gl; use pathfinder_geometry::basic::point::Point2DI32; use pathfinder_geometry::basic::rect::RectI32; +use pathfinder_gpu::{BlendState, BufferTarget, BufferUploadMode, Device, Primitive, RenderState}; +use pathfinder_gpu::{Resources, UniformData, VertexAttrType}; use pathfinder_renderer::gpu_data::Stats; use pathfinder_renderer::paint::ColorU; +use pathfinder_simd::default::F32x4; use serde_json; use std::collections::{HashMap, VecDeque}; use std::fs::File; use std::io::BufReader; use std::ops::{Add, Div}; -use std::ptr; use std::time::Duration; const SAMPLE_BUFFER_SIZE: usize = 60; -const DEBUG_TEXTURE_VERTEX_SIZE: GLint = 8; -const DEBUG_SOLID_VERTEX_SIZE: GLint = 4; +const DEBUG_TEXTURE_VERTEX_SIZE: usize = 8; +const DEBUG_SOLID_VERTEX_SIZE: usize = 4; pub const PADDING: i32 = 12; @@ -84,43 +82,44 @@ struct DebugCharacter { } impl DebugFont { - fn load(device: &Device) -> DebugFont { - let mut path = device.resources_directory.clone(); + fn load(resources: &Resources) -> DebugFont { + let mut path = resources.resources_directory.clone(); path.push(FONT_JSON_FILENAME); serde_json::from_reader(BufReader::new(File::open(path).unwrap())).unwrap() } } -pub struct DebugUI { +pub struct DebugUI where D: Device { framebuffer_size: Point2DI32, - texture_program: DebugTextureProgram, - texture_vertex_array: DebugTextureVertexArray, + texture_program: DebugTextureProgram, + texture_vertex_array: DebugTextureVertexArray, font: DebugFont, - solid_program: DebugSolidProgram, - solid_vertex_array: DebugSolidVertexArray, + solid_program: DebugSolidProgram, + solid_vertex_array: DebugSolidVertexArray, - font_texture: Texture, - corner_fill_texture: Texture, - corner_outline_texture: Texture, + font_texture: D::Texture, + corner_fill_texture: D::Texture, + corner_outline_texture: D::Texture, cpu_samples: SampleBuffer, gpu_samples: SampleBuffer, } -impl DebugUI { - pub fn new(device: &Device, framebuffer_size: Point2DI32) -> DebugUI { - let texture_program = DebugTextureProgram::new(device); - let texture_vertex_array = DebugTextureVertexArray::new(&texture_program); - let font = DebugFont::load(device); +impl DebugUI where D: Device { + pub fn new(device: &D, resources: &Resources, framebuffer_size: Point2DI32) -> DebugUI { + let texture_program = DebugTextureProgram::new(device, resources); + let texture_vertex_array = DebugTextureVertexArray::new(device, &texture_program); + let font = DebugFont::load(resources); - let solid_program = DebugSolidProgram::new(device); - let solid_vertex_array = DebugSolidVertexArray::new(&solid_program); + let solid_program = DebugSolidProgram::new(device, resources); + let solid_vertex_array = DebugSolidVertexArray::new(device, &solid_program); - let font_texture = device.create_texture_from_png(FONT_PNG_NAME); - let corner_fill_texture = device.create_texture_from_png(CORNER_FILL_PNG_NAME); - let corner_outline_texture = device.create_texture_from_png(CORNER_OUTLINE_PNG_NAME); + let font_texture = device.create_texture_from_png(resources, FONT_PNG_NAME); + let corner_fill_texture = device.create_texture_from_png(resources, CORNER_FILL_PNG_NAME); + let corner_outline_texture = device.create_texture_from_png(resources, + CORNER_OUTLINE_PNG_NAME); DebugUI { framebuffer_size, @@ -158,47 +157,55 @@ impl DebugUI { } } - pub fn draw(&self) { + pub fn draw(&self, device: &D) { // Draw performance window. let bottom = self.framebuffer_size.y() - PADDING; let window_rect = RectI32::new( Point2DI32::new(self.framebuffer_size.x() - PADDING - PERF_WINDOW_WIDTH, bottom - PERF_WINDOW_HEIGHT), Point2DI32::new(PERF_WINDOW_WIDTH, PERF_WINDOW_HEIGHT)); - self.draw_solid_rounded_rect(window_rect, WINDOW_COLOR); + self.draw_solid_rounded_rect(device, window_rect, WINDOW_COLOR); let origin = window_rect.origin() + Point2DI32::new(PADDING, PADDING + FONT_ASCENT); let mean_cpu_sample = self.cpu_samples.mean(); - self.draw_text(&format!("Objects: {}", mean_cpu_sample.stats.object_count), origin, false); - self.draw_text(&format!("Solid Tiles: {}", mean_cpu_sample.stats.solid_tile_count), + self.draw_text(device, + &format!("Objects: {}", mean_cpu_sample.stats.object_count), + origin, + false); + self.draw_text(device, + &format!("Solid Tiles: {}", mean_cpu_sample.stats.solid_tile_count), origin + Point2DI32::new(0, LINE_HEIGHT * 1), false); - self.draw_text(&format!("Mask Tiles: {}", mean_cpu_sample.stats.mask_tile_count), + self.draw_text(device, + &format!("Mask Tiles: {}", mean_cpu_sample.stats.mask_tile_count), origin + Point2DI32::new(0, LINE_HEIGHT * 2), false); - self.draw_text(&format!("Fills: {}", mean_cpu_sample.stats.fill_count), + self.draw_text(device, + &format!("Fills: {}", mean_cpu_sample.stats.fill_count), origin + Point2DI32::new(0, LINE_HEIGHT * 3), false); - self.draw_text(&format!("CPU Time: {:.3} ms", duration_to_ms(mean_cpu_sample.elapsed)), + self.draw_text(device, + &format!("CPU Time: {:.3} ms", duration_to_ms(mean_cpu_sample.elapsed)), origin + Point2DI32::new(0, LINE_HEIGHT * 4), false); let mean_gpu_sample = self.gpu_samples.mean(); - self.draw_text(&format!("GPU Time: {:.3} ms", duration_to_ms(mean_gpu_sample.elapsed)), + self.draw_text(device, + &format!("GPU Time: {:.3} ms", duration_to_ms(mean_gpu_sample.elapsed)), origin + Point2DI32::new(0, LINE_HEIGHT * 5), false); } - pub fn draw_solid_rect(&self, rect: RectI32, color: ColorU) { - self.draw_rect(rect, color, true); + pub fn draw_solid_rect(&self, device: &D, rect: RectI32, color: ColorU) { + self.draw_rect(device, rect, color, true); } - pub fn draw_rect_outline(&self, rect: RectI32, color: ColorU) { - self.draw_rect(rect, color, false); + pub fn draw_rect_outline(&self, device: &D, rect: RectI32, color: ColorU) { + self.draw_rect(device, rect, color, false); } - fn draw_rect(&self, rect: RectI32, color: ColorU, filled: bool) { + fn draw_rect(&self, device: &D, rect: RectI32, color: ColorU, filled: bool) { let vertex_data = [ DebugSolidVertex::new(rect.origin()), DebugSolidVertex::new(rect.upper_right()), @@ -207,44 +214,50 @@ impl DebugUI { ]; if filled { - self.draw_solid_rects_with_vertex_data(&vertex_data, &QUAD_INDICES, color, true); + self.draw_solid_rects_with_vertex_data(device, + &vertex_data, + &QUAD_INDICES, + color, + true); } else { - self.draw_solid_rects_with_vertex_data(&vertex_data, &RECT_LINE_INDICES, color, false); + self.draw_solid_rects_with_vertex_data(device, + &vertex_data, + &RECT_LINE_INDICES, + color, + false); } } fn draw_solid_rects_with_vertex_data(&self, + device: &D, vertex_data: &[DebugSolidVertex], index_data: &[u32], color: ColorU, filled: bool) { - unsafe { - gl::BindVertexArray(self.solid_vertex_array.gl_vertex_array); - } + device.bind_vertex_array(&self.solid_vertex_array.vertex_array); - self.solid_vertex_array - .vertex_buffer - .upload(vertex_data, BufferTarget::Vertex, BufferUploadMode::Dynamic); - self.solid_vertex_array - .index_buffer - .upload(index_data, BufferTarget::Index, BufferUploadMode::Dynamic); + device.upload_to_buffer(&self.solid_vertex_array.vertex_buffer, + vertex_data, + BufferTarget::Vertex, + BufferUploadMode::Dynamic); + device.upload_to_buffer(&self.solid_vertex_array.index_buffer, + index_data, + BufferTarget::Index, + BufferUploadMode::Dynamic); - unsafe { - gl::UseProgram(self.solid_program.program.gl_program); - gl::Uniform2f(self.solid_program.framebuffer_size_uniform.location, - self.framebuffer_size.x() as GLfloat, - self.framebuffer_size.y() as GLfloat); - set_color_uniform(&self.solid_program.color_uniform, color); - gl::BlendEquation(gl::FUNC_ADD); - gl::BlendFunc(gl::ONE, gl::ONE_MINUS_SRC_ALPHA); - gl::Enable(gl::BLEND); - let primitive = if filled { gl::TRIANGLES } else { gl::LINES }; - gl::DrawElements(primitive, index_data.len() as GLint, gl::UNSIGNED_INT, ptr::null()); - gl::Disable(gl::BLEND); - } + device.use_program(&self.solid_program.program); + device.set_uniform(&self.solid_program.framebuffer_size_uniform, + UniformData::Vec2(self.framebuffer_size.0.to_f32x4())); + set_color_uniform(device, &self.solid_program.color_uniform, color); + + let primitive = if filled { Primitive::Triangles } else { Primitive::Lines }; + device.draw_elements(primitive, index_data.len() as u32, &RenderState { + blend: BlendState::RGBOneAlphaOneMinusSrcAlpha, + ..RenderState::default() + }); } - pub fn draw_text(&self, string: &str, origin: Point2DI32, invert: bool) { + pub fn draw_text(&self, device: &D, string: &str, origin: Point2DI32, invert: bool) { let mut next = origin; let char_count = string.chars().count(); let mut vertex_data = Vec::with_capacity(char_count * 4); @@ -274,12 +287,20 @@ impl DebugUI { } let color = if invert { INVERTED_TEXT_COLOR } else { TEXT_COLOR }; - self.draw_texture_with_vertex_data(&vertex_data, &index_data, &self.font_texture, color); + self.draw_texture_with_vertex_data(device, + &vertex_data, + &index_data, + &self.font_texture, + color); } - pub fn draw_texture(&self, origin: Point2DI32, texture: &Texture, color: ColorU) { - let position_rect = RectI32::new(origin, texture.size); - let tex_coord_rect = RectI32::new(Point2DI32::default(), texture.size); + pub fn draw_texture(&self, + device: &D, + origin: Point2DI32, + texture: &D::Texture, + color: ColorU) { + let position_rect = RectI32::new(origin, device.texture_size(&texture)); + let tex_coord_rect = RectI32::new(Point2DI32::default(), position_rect.size()); let vertex_data = [ DebugTextureVertex::new(position_rect.origin(), tex_coord_rect.origin()), DebugTextureVertex::new(position_rect.upper_right(), tex_coord_rect.upper_right()), @@ -287,7 +308,7 @@ impl DebugUI { DebugTextureVertex::new(position_rect.lower_left(), tex_coord_rect.lower_left()), ]; - self.draw_texture_with_vertex_data(&vertex_data, &QUAD_INDICES, texture, color); + self.draw_texture_with_vertex_data(device, &vertex_data, &QUAD_INDICES, texture, color); } pub fn measure_text(&self, string: &str) -> i32 { @@ -303,10 +324,10 @@ impl DebugUI { next } - pub fn draw_solid_rounded_rect(&self, rect: RectI32, color: ColorU) { + pub fn draw_solid_rounded_rect(&self, device: &D, rect: RectI32, color: ColorU) { let corner_texture = self.corner_texture(true); - let corner_rects = CornerRects::new(rect, corner_texture); - self.draw_rounded_rect_corners(color, corner_texture, &corner_rects); + let corner_rects = CornerRects::new(device, rect, corner_texture); + self.draw_rounded_rect_corners(device, color, corner_texture, &corner_rects); let solid_rect_mid = RectI32::from_points(corner_rects.upper_left.upper_right(), corner_rects.lower_right.lower_left()); @@ -336,13 +357,17 @@ impl DebugUI { index_data.extend(QUAD_INDICES.iter().map(|&index| index + 4)); index_data.extend(QUAD_INDICES.iter().map(|&index| index + 8)); - self.draw_solid_rects_with_vertex_data(&vertex_data, &index_data[0..18], color, true); + self.draw_solid_rects_with_vertex_data(device, + &vertex_data, + &index_data[0..18], + color, + true); } - pub fn draw_rounded_rect_outline(&self, rect: RectI32, color: ColorU) { + pub fn draw_rounded_rect_outline(&self, device: &D, rect: RectI32, color: ColorU) { let corner_texture = self.corner_texture(false); - let corner_rects = CornerRects::new(rect, corner_texture); - self.draw_rounded_rect_corners(color, corner_texture, &corner_rects); + let corner_rects = CornerRects::new(device, rect, corner_texture); + self.draw_rounded_rect_corners(device, color, corner_texture, &corner_rects); let vertex_data = vec![ DebugSolidVertex::new(corner_rects.upper_left.upper_right()), @@ -356,14 +381,15 @@ impl DebugUI { ]; let index_data = &OUTLINE_RECT_LINE_INDICES; - self.draw_solid_rects_with_vertex_data(&vertex_data, index_data, color, false); + self.draw_solid_rects_with_vertex_data(device, &vertex_data, index_data, color, false); } fn draw_rounded_rect_corners(&self, + device: &D, color: ColorU, - texture: &Texture, + texture: &D::Texture, corner_rects: &CornerRects) { - let corner_size = texture.size; + let corner_size = device.texture_size(&texture); let tex_coord_rect = RectI32::new(Point2DI32::default(), corner_size); let vertex_data = vec![ @@ -410,151 +436,126 @@ impl DebugUI { index_data.extend(QUAD_INDICES.iter().map(|&index| index + 8)); index_data.extend(QUAD_INDICES.iter().map(|&index| index + 12)); - self.draw_texture_with_vertex_data(&vertex_data, &index_data, texture, color); + self.draw_texture_with_vertex_data(device, &vertex_data, &index_data, texture, color); } - fn corner_texture(&self, filled: bool) -> &Texture { + fn corner_texture(&self, filled: bool) -> &D::Texture { if filled { &self.corner_fill_texture } else { &self.corner_outline_texture } } fn draw_texture_with_vertex_data(&self, + device: &D, vertex_data: &[DebugTextureVertex], index_data: &[u32], - texture: &Texture, + texture: &D::Texture, color: ColorU) { - self.texture_vertex_array - .vertex_buffer - .upload(&vertex_data, BufferTarget::Vertex, BufferUploadMode::Dynamic); - self.texture_vertex_array - .index_buffer - .upload(&index_data, BufferTarget::Index, BufferUploadMode::Dynamic); + device.upload_to_buffer(&self.texture_vertex_array.vertex_buffer, + vertex_data, + BufferTarget::Vertex, + BufferUploadMode::Dynamic); + device.upload_to_buffer(&self.texture_vertex_array.index_buffer, + index_data, + BufferTarget::Index, + BufferUploadMode::Dynamic); - unsafe { - gl::BindVertexArray(self.texture_vertex_array.gl_vertex_array); - gl::UseProgram(self.texture_program.program.gl_program); - gl::Uniform2f(self.texture_program.framebuffer_size_uniform.location, - self.framebuffer_size.x() as GLfloat, - self.framebuffer_size.y() as GLfloat); - gl::Uniform2f(self.texture_program.texture_size_uniform.location, - texture.size.x() as GLfloat, - texture.size.y() as GLfloat); - set_color_uniform(&self.texture_program.color_uniform, color); - texture.bind(0); - gl::Uniform1i(self.texture_program.texture_uniform.location, 0); - gl::BlendEquation(gl::FUNC_ADD); - gl::BlendFunc(gl::ONE, gl::ONE_MINUS_SRC_ALPHA); - gl::Enable(gl::BLEND); - gl::DrawElements(gl::TRIANGLES, - index_data.len() as GLsizei, - gl::UNSIGNED_INT, - ptr::null()); - gl::Disable(gl::BLEND); - } + device.bind_vertex_array(&self.texture_vertex_array.vertex_array); + device.use_program(&self.texture_program.program); + device.set_uniform(&self.texture_program.framebuffer_size_uniform, + UniformData::Vec2(self.framebuffer_size.0.to_f32x4())); + device.set_uniform(&self.texture_program.texture_size_uniform, + UniformData::Vec2(device.texture_size(&texture).0.to_f32x4())); + set_color_uniform(device, &self.texture_program.color_uniform, color); + device.bind_texture(texture, 0); + device.set_uniform(&self.texture_program.texture_uniform, UniformData::TextureUnit(0)); + + device.draw_elements(Primitive::Triangles, index_data.len() as u32, &RenderState { + blend: BlendState::RGBOneAlphaOneMinusSrcAlpha, + ..RenderState::default() + }); } } -struct DebugTextureVertexArray { - gl_vertex_array: GLuint, - vertex_buffer: Buffer, - index_buffer: Buffer, +struct DebugTextureVertexArray where D: Device { + vertex_array: D::VertexArray, + vertex_buffer: D::Buffer, + index_buffer: D::Buffer, } -impl DebugTextureVertexArray { - fn new(debug_texture_program: &DebugTextureProgram) -> DebugTextureVertexArray { - let vertex_buffer = Buffer::new(); - let index_buffer = Buffer::new(); - let mut gl_vertex_array = 0; - unsafe { - let position_attr = VertexAttr::new(&debug_texture_program.program, "Position"); - let tex_coord_attr = VertexAttr::new(&debug_texture_program.program, "TexCoord"); +impl DebugTextureVertexArray where D: Device { + fn new(device: &D, debug_texture_program: &DebugTextureProgram) + -> DebugTextureVertexArray { + let (vertex_buffer, index_buffer) = (device.create_buffer(), device.create_buffer()); + let vertex_array = device.create_vertex_array(); - gl::GenVertexArrays(1, &mut gl_vertex_array); - gl::BindVertexArray(gl_vertex_array); - gl::UseProgram(debug_texture_program.program.gl_program); - gl::BindBuffer(gl::ARRAY_BUFFER, vertex_buffer.gl_buffer); - position_attr.configure_float(2, - gl::UNSIGNED_SHORT, - false, - DEBUG_TEXTURE_VERTEX_SIZE, - 0, - 0); - tex_coord_attr.configure_float(2, - gl::UNSIGNED_SHORT, + let position_attr = device.get_vertex_attr(&debug_texture_program.program, "Position"); + let tex_coord_attr = device.get_vertex_attr(&debug_texture_program.program, "TexCoord"); + + device.bind_vertex_array(&vertex_array); + device.use_program(&debug_texture_program.program); + device.bind_buffer(&vertex_buffer, BufferTarget::Vertex); + device.bind_buffer(&index_buffer, BufferTarget::Index); + device.configure_float_vertex_attr(&position_attr, + 2, + VertexAttrType::U16, + false, + DEBUG_TEXTURE_VERTEX_SIZE, + 0, + 0); + device.configure_float_vertex_attr(&tex_coord_attr, + 2, + VertexAttrType::U16, false, DEBUG_TEXTURE_VERTEX_SIZE, 4, 0); - gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, index_buffer.gl_buffer); - } - DebugTextureVertexArray { gl_vertex_array, vertex_buffer, index_buffer } + DebugTextureVertexArray { vertex_array, vertex_buffer, index_buffer } } } -impl Drop for DebugTextureVertexArray { - #[inline] - fn drop(&mut self) { - unsafe { - gl::DeleteVertexArrays(1, &mut self.gl_vertex_array); - } +struct DebugSolidVertexArray where D: Device { + vertex_array: D::VertexArray, + vertex_buffer: D::Buffer, + index_buffer: D::Buffer, +} + +impl DebugSolidVertexArray where D: Device { + fn new(device: &D, debug_solid_program: &DebugSolidProgram) -> DebugSolidVertexArray { + let (vertex_buffer, index_buffer) = (device.create_buffer(), device.create_buffer()); + let vertex_array = device.create_vertex_array(); + + let position_attr = device.get_vertex_attr(&debug_solid_program.program, "Position"); + device.bind_vertex_array(&vertex_array); + device.use_program(&debug_solid_program.program); + device.bind_buffer(&vertex_buffer, BufferTarget::Vertex); + device.bind_buffer(&index_buffer, BufferTarget::Index); + device.configure_float_vertex_attr(&position_attr, + 2, + VertexAttrType::U16, + false, + DEBUG_SOLID_VERTEX_SIZE, + 0, + 0); + + DebugSolidVertexArray { vertex_array, vertex_buffer, index_buffer } } } -struct DebugSolidVertexArray { - gl_vertex_array: GLuint, - vertex_buffer: Buffer, - index_buffer: Buffer, +struct DebugTextureProgram where D: Device { + program: D::Program, + framebuffer_size_uniform: D::Uniform, + texture_size_uniform: D::Uniform, + texture_uniform: D::Uniform, + color_uniform: D::Uniform, } -impl DebugSolidVertexArray { - fn new(debug_solid_program: &DebugSolidProgram) -> DebugSolidVertexArray { - let vertex_buffer = Buffer::new(); - let index_buffer = Buffer::new(); - let mut gl_vertex_array = 0; - unsafe { - let position_attr = VertexAttr::new(&debug_solid_program.program, "Position"); - - gl::GenVertexArrays(1, &mut gl_vertex_array); - gl::BindVertexArray(gl_vertex_array); - gl::UseProgram(debug_solid_program.program.gl_program); - gl::BindBuffer(gl::ARRAY_BUFFER, vertex_buffer.gl_buffer); - position_attr.configure_float(2, - gl::UNSIGNED_SHORT, - false, - DEBUG_SOLID_VERTEX_SIZE, - 0, - 0); - gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, index_buffer.gl_buffer); - } - - DebugSolidVertexArray { gl_vertex_array, vertex_buffer, index_buffer } - } -} - -impl Drop for DebugSolidVertexArray { - #[inline] - fn drop(&mut self) { - unsafe { - gl::DeleteVertexArrays(1, &mut self.gl_vertex_array); - } - } -} - -struct DebugTextureProgram { - program: Program, - framebuffer_size_uniform: Uniform, - texture_size_uniform: Uniform, - texture_uniform: Uniform, - color_uniform: Uniform, -} - -impl DebugTextureProgram { - fn new(device: &Device) -> DebugTextureProgram { - let program = device.create_program("debug_texture"); - let framebuffer_size_uniform = Uniform::new(&program, "FramebufferSize"); - let texture_size_uniform = Uniform::new(&program, "TextureSize"); - let texture_uniform = Uniform::new(&program, "Texture"); - let color_uniform = Uniform::new(&program, "Color"); +impl DebugTextureProgram where D: Device { + fn new(device: &D, resources: &Resources) -> DebugTextureProgram { + let program = device.create_program(resources, "debug_texture"); + let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize"); + let texture_size_uniform = device.get_uniform(&program, "TextureSize"); + let texture_uniform = device.get_uniform(&program, "Texture"); + let color_uniform = device.get_uniform(&program, "Color"); DebugTextureProgram { program, framebuffer_size_uniform, @@ -565,17 +566,17 @@ impl DebugTextureProgram { } } -struct DebugSolidProgram { - program: Program, - framebuffer_size_uniform: Uniform, - color_uniform: Uniform, +struct DebugSolidProgram where D: Device { + program: D::Program, + framebuffer_size_uniform: D::Uniform, + color_uniform: D::Uniform, } -impl DebugSolidProgram { - fn new(device: &Device) -> DebugSolidProgram { - let program = device.create_program("debug_solid"); - let framebuffer_size_uniform = Uniform::new(&program, "FramebufferSize"); - let color_uniform = Uniform::new(&program, "Color"); +impl DebugSolidProgram where D: Device { + fn new(device: &D, resources: &Resources) -> DebugSolidProgram { + let program = device.create_program(resources, "debug_solid"); + let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize"); + let color_uniform = device.get_uniform(&program, "Color"); DebugSolidProgram { program, framebuffer_size_uniform, color_uniform } } } @@ -645,14 +646,9 @@ impl SampleBuffer where S: Add + Div + Clone + } } -fn set_color_uniform(uniform: &Uniform, color: ColorU) { - unsafe { - gl::Uniform4f(uniform.location, - color.r as f32 * (1.0 / 255.0), - color.g as f32 * (1.0 / 255.0), - color.b as f32 * (1.0 / 255.0), - color.a as f32 * (1.0 / 255.0)); - } +fn set_color_uniform(device: &D, uniform: &D::Uniform, color: ColorU) where D: Device { + let color = F32x4::new(color.r as f32, color.g as f32, color.b as f32, color.a as f32); + device.set_uniform(uniform, UniformData::Vec4(color * F32x4::splat(1.0 / 255.0))); } #[derive(Clone, Default)] @@ -722,8 +718,8 @@ struct CornerRects { } impl CornerRects { - fn new(rect: RectI32, texture: &Texture) -> CornerRects { - let size = texture.size; + fn new(device: &D, rect: RectI32, texture: &D::Texture) -> CornerRects where D: Device { + let size = device.texture_size(texture); CornerRects { upper_left: RectI32::new(rect.origin(), size), upper_right: RectI32::new(rect.upper_right() - Point2DI32::new(size.x(), 0), size), diff --git a/gl/src/device.rs b/gl/src/device.rs index aea68e98..a80d6168 100644 --- a/gl/src/device.rs +++ b/gl/src/device.rs @@ -1,4 +1,4 @@ -// pathfinder/demo/src/device.rs +// pathfinder/gl/src/device.rs // // Copyright © 2019 The Pathfinder Project Developers. // @@ -8,72 +8,175 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! Minimal abstractions over GPU device capabilities. +//! An OpenGL implementation of the device abstraction. -use gl::types::{GLchar, GLint, GLsizei, GLsizeiptr, GLuint, GLvoid}; +use gl::types::{GLboolean, GLchar, GLdouble, GLenum, GLint, GLsizei, GLsizeiptr, GLuint, GLvoid}; use pathfinder_geometry::basic::point::Point2DI32; -use std::env; +use pathfinder_gpu::{BlendState, BufferTarget, BufferUploadMode, DepthFunc, Device, Primitive}; +use pathfinder_gpu::{RenderState, ShaderKind, StencilFunc, TextureFormat}; +use pathfinder_gpu::{UniformData, VertexAttrType}; +use pathfinder_simd::default::F32x4; use std::ffi::CString; -use std::fs::File; -use std::io::Read; use std::mem; -use std::path::PathBuf; use std::ptr; +use std::time::Duration; -pub struct Device { - pub resources_directory: PathBuf, -} +pub struct GLDevice; -impl Device { +impl GLDevice { #[inline] - pub fn new() -> Device { - Device { resources_directory: locate_resources_directory() } + pub fn new() -> GLDevice { GLDevice } + + fn set_texture_parameters(&self, texture: &GLTexture) { + self.bind_texture(texture, 0); + unsafe { + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as GLint); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as GLint); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as GLint); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as GLint); + } } - #[inline] - pub fn create_texture_from_png(&self, name: &str) -> Texture { - let mut path = self.resources_directory.clone(); - path.push("textures"); - path.push(format!("{}.png", name)); - - let image = image::open(&path).unwrap().to_luma(); - - let mut texture = Texture { - gl_texture: 0, - size: Point2DI32::new(image.width() as i32, image.height() as i32), - }; - + fn set_render_state(&self, render_state: &RenderState) { unsafe { - gl::GenTextures(1, &mut texture.gl_texture); - texture.bind(0); - gl::TexImage2D(gl::TEXTURE_2D, - 0, - gl::RED as GLint, - image.width() as GLsizei, - image.height() as GLsizei, - 0, - gl::RED, - gl::UNSIGNED_BYTE, - image.as_ptr() as *const GLvoid); + // Set blend. + match render_state.blend { + BlendState::Off => gl::Disable(gl::BLEND), + BlendState::RGBOneAlphaOneMinusSrcAlpha => { + gl::BlendFuncSeparate(gl::ONE, gl::ONE_MINUS_SRC_ALPHA, gl::ONE, gl::ONE); + gl::Enable(gl::BLEND); + } + BlendState::RGBOneAlphaOne => { + gl::BlendFunc(gl::ONE, gl::ONE); + gl::Enable(gl::BLEND); + } + } + + // Set depth. + match render_state.depth { + None => gl::Disable(gl::DEPTH_TEST), + Some(ref state) => { + gl::Enable(gl::DEPTH_TEST); + gl::DepthFunc(state.func.to_gl_depth_func()); + gl::DepthMask(state.write as GLboolean); + } + } + + // Set stencil. + match render_state.stencil { + None => gl::Disable(gl::STENCIL_TEST), + Some(ref state) => { + gl::StencilFunc(state.func.to_gl_stencil_func(), + state.reference as GLint, + state.mask); + let pass_action = if state.pass_replace { gl::REPLACE } else { gl::KEEP }; + gl::StencilOp(gl::KEEP, gl::KEEP, pass_action); + gl::Enable(gl::STENCIL_TEST); + } + } + + // Set color mask. + let color_mask = render_state.color_mask as GLboolean; + gl::ColorMask(color_mask, color_mask, color_mask, color_mask); + } + } + + fn reset_render_state(&self, render_state: &RenderState) { + unsafe { + match render_state.blend { + BlendState::Off => {} + BlendState::RGBOneAlphaOneMinusSrcAlpha | BlendState::RGBOneAlphaOne => { + gl::Disable(gl::BLEND); + } + } + + if render_state.depth.is_some() { + gl::Disable(gl::DEPTH_TEST); + } + + if render_state.stencil.is_some() { + gl::Disable(gl::STENCIL_TEST); + } + + gl::ColorMask(gl::TRUE, gl::TRUE, gl::TRUE, gl::TRUE); + } + } +} + +impl Device for GLDevice { + type Buffer = GLBuffer; + type Framebuffer = GLFramebuffer; + type Program = GLProgram; + type Shader = GLShader; + type Texture = GLTexture; + type TimerQuery = GLTimerQuery; + type Uniform = GLUniform; + type VertexArray = GLVertexArray; + type VertexAttr = GLVertexAttr; + + fn create_texture(&self, format: TextureFormat, size: Point2DI32) -> GLTexture { + let (gl_internal_format, gl_format, gl_type); + match format { + TextureFormat::R16F => { + gl_internal_format = gl::R16F as GLint; + gl_format = gl::RED; + gl_type = gl::HALF_FLOAT; + } + TextureFormat::RGBA8 => { + gl_internal_format = gl::RGBA as GLint; + gl_format = gl::RGBA; + gl_type = gl::UNSIGNED_BYTE; + } } - texture.set_parameters(); + let mut texture = GLTexture { gl_texture: 0, size }; + unsafe { + gl::GenTextures(1, &mut texture.gl_texture); + self.bind_texture(&texture, 0); + gl::TexImage2D(gl::TEXTURE_2D, + 0, + gl_internal_format, + size.x() as GLsizei, + size.y() as GLsizei, + 0, + gl_format, + gl_type, + ptr::null()); + } + + self.set_texture_parameters(&texture); texture } - fn create_shader(&self, name: &str, kind: ShaderKind) -> Shader { - let suffix = match kind { ShaderKind::Vertex => 'v', ShaderKind::Fragment => 'f' }; - let mut path = self.resources_directory.clone(); - path.push("shaders"); - path.push(format!("{}.{}s.glsl", name, suffix)); + fn create_texture_from_data(&self, size: Point2DI32, data: &[u8]) -> GLTexture { + assert!(data.len() >= size.x() as usize * size.y() as usize); + + let mut texture = GLTexture { gl_texture: 0, size }; + unsafe { + gl::GenTextures(1, &mut texture.gl_texture); + self.bind_texture(&texture, 0); + gl::TexImage2D(gl::TEXTURE_2D, + 0, + gl::RED as GLint, + size.x() as GLsizei, + size.y() as GLsizei, + 0, + gl::RED, + gl::UNSIGNED_BYTE, + data.as_ptr() as *const GLvoid); + } + + self.set_texture_parameters(&texture); + texture + } + + fn create_shader_from_source(&self, name: &str, source: &[u8], kind: ShaderKind) -> GLShader { + let gl_shader_kind = match kind { + ShaderKind::Vertex => gl::VERTEX_SHADER, + ShaderKind::Fragment => gl::FRAGMENT_SHADER, + }; - let mut source = vec![]; - File::open(&path).unwrap().read_to_end(&mut source).unwrap(); unsafe { - let gl_shader_kind = match kind { - ShaderKind::Vertex => gl::VERTEX_SHADER, - ShaderKind::Fragment => gl::FRAGMENT_SHADER, - }; let gl_shader = gl::CreateShader(gl_shader_kind); gl::ShaderSource(gl_shader, 1, @@ -95,14 +198,15 @@ impl Device { panic!("{:?} shader '{}' compilation failed", kind, name); } - Shader { gl_shader } + GLShader { gl_shader } } } - pub fn create_program(&self, name: &str) -> Program { - let vertex_shader = self.create_shader(name, ShaderKind::Vertex); - let fragment_shader = self.create_shader(name, ShaderKind::Fragment); - + fn create_program_from_shaders(&self, + name: &str, + vertex_shader: GLShader, + fragment_shader: GLShader) + -> GLProgram { let gl_program; unsafe { gl_program = gl::CreateProgram(); @@ -125,15 +229,332 @@ impl Device { } } - Program { gl_program, vertex_shader, fragment_shader } + GLProgram { gl_program, vertex_shader, fragment_shader } + } + + #[inline] + fn create_vertex_array(&self) -> GLVertexArray { + unsafe { + let mut array = GLVertexArray { gl_vertex_array: 0 }; + gl::GenVertexArrays(1, &mut array.gl_vertex_array); + array + } + } + + fn get_vertex_attr(&self, program: &Self::Program, name: &str) -> GLVertexAttr { + let name = CString::new(format!("a{}", name)).unwrap(); + let attr = unsafe { + gl::GetAttribLocation(program.gl_program, name.as_ptr() as *const GLchar) as GLuint + }; + GLVertexAttr { attr } + } + + fn get_uniform(&self, program: &GLProgram, name: &str) -> GLUniform { + let name = CString::new(format!("u{}", name)).unwrap(); + let location = unsafe { + gl::GetUniformLocation(program.gl_program, name.as_ptr() as *const GLchar) + }; + GLUniform { location } + } + + fn use_program(&self, program: &Self::Program) { + unsafe { + gl::UseProgram(program.gl_program); + } + } + + fn configure_float_vertex_attr(&self, + attr: &GLVertexAttr, + size: usize, + attr_type: VertexAttrType, + normalized: bool, + stride: usize, + offset: usize, + divisor: u32) { + unsafe { + gl::VertexAttribPointer(attr.attr, + size as GLint, + attr_type.to_gl_type(), + if normalized { gl::TRUE } else { gl::FALSE }, + stride as GLint, + offset as *const GLvoid); + gl::VertexAttribDivisor(attr.attr, divisor); + gl::EnableVertexAttribArray(attr.attr); + } + } + + fn configure_int_vertex_attr(&self, + attr: &GLVertexAttr, + size: usize, + attr_type: VertexAttrType, + stride: usize, + offset: usize, + divisor: u32) { + unsafe { + gl::VertexAttribIPointer(attr.attr, + size as GLint, + attr_type.to_gl_type(), + stride as GLint, + offset as *const GLvoid); + gl::VertexAttribDivisor(attr.attr, divisor); + gl::EnableVertexAttribArray(attr.attr); + } + } + + fn set_uniform(&self, uniform: &Self::Uniform, data: UniformData) { + unsafe { + match data { + UniformData::Vec2(data) => gl::Uniform2f(uniform.location, data.x(), data.y()), + UniformData::Vec4(data) => { + gl::Uniform4f(uniform.location, data.x(), data.y(), data.z(), data.w()); + } + UniformData::TextureUnit(unit) => gl::Uniform1i(uniform.location, unit as GLint), + } + } + } + + fn create_framebuffer(&self, texture: GLTexture) -> GLFramebuffer { + let mut gl_framebuffer = 0; + unsafe { + gl::GenFramebuffers(1, &mut gl_framebuffer); + assert_eq!(gl::GetError(), gl::NO_ERROR); + gl::BindFramebuffer(gl::FRAMEBUFFER, gl_framebuffer); + self.bind_texture(&texture, 0); + gl::FramebufferTexture2D(gl::FRAMEBUFFER, + gl::COLOR_ATTACHMENT0, + gl::TEXTURE_2D, + texture.gl_texture, + 0); + assert_eq!(gl::CheckFramebufferStatus(gl::FRAMEBUFFER), gl::FRAMEBUFFER_COMPLETE); + } + + GLFramebuffer { gl_framebuffer, texture } + } + + fn create_buffer(&self) -> GLBuffer { + unsafe { + let mut gl_buffer = 0; + gl::GenBuffers(1, &mut gl_buffer); + GLBuffer { gl_buffer } + } + } + + fn upload_to_buffer(&self, + buffer: &GLBuffer, + data: &[T], + target: BufferTarget, + mode: BufferUploadMode) { + let target = match target { + BufferTarget::Vertex => gl::ARRAY_BUFFER, + BufferTarget::Index => gl::ELEMENT_ARRAY_BUFFER, + }; + let mode = match mode { + BufferUploadMode::Static => gl::STATIC_DRAW, + BufferUploadMode::Dynamic => gl::DYNAMIC_DRAW, + }; + unsafe { + gl::BindBuffer(target, buffer.gl_buffer); + gl::BufferData(target, + (data.len() * mem::size_of::()) as GLsizeiptr, + data.as_ptr() as *const GLvoid, + mode); + } + } + + #[inline] + fn framebuffer_texture<'f>(&self, framebuffer: &'f Self::Framebuffer) -> &'f Self::Texture { + &framebuffer.texture + } + + #[inline] + fn texture_size(&self, texture: &Self::Texture) -> Point2DI32 { + texture.size + } + + fn upload_to_texture(&self, texture: &Self::Texture, size: Point2DI32, data: &[u8]) { + assert!(data.len() >= size.x() as usize * size.y() as usize * 4); + unsafe { + self.bind_texture(texture, 0); + gl::TexImage2D(gl::TEXTURE_2D, + 0, + gl::RGBA as GLint, + size.x() as GLsizei, + size.y() as GLsizei, + 0, + gl::RGBA, + gl::UNSIGNED_BYTE, + data.as_ptr() as *const GLvoid); + } + + self.set_texture_parameters(texture); + } + + fn read_pixels_from_default_framebuffer(&self, size: Point2DI32) -> Vec { + let mut pixels = vec![0; size.x() as usize * size.y() as usize * 4]; + unsafe { + gl::BindFramebuffer(gl::FRAMEBUFFER, 0); + gl::ReadPixels(0, + 0, + size.x() as GLsizei, + size.y() as GLsizei, + gl::RGBA, + gl::UNSIGNED_BYTE, + pixels.as_mut_ptr() as *mut GLvoid); + } + + // Flip right-side-up. + let stride = size.x() as usize * 4; + for y in 0..(size.y() as usize / 2) { + let (index_a, index_b) = (y * stride, (size.y() as usize - y - 1) * stride); + for offset in 0..stride { + pixels.swap(index_a + offset, index_b + offset); + } + } + + pixels + } + + // TODO(pcwalton): Switch to `ColorF`! + fn clear(&self, color: Option, depth: Option, stencil: Option) { + unsafe { + let mut flags = 0; + if let Some(color) = color { + gl::ClearColor(color.x(), color.y(), color.z(), color.w()); + flags |= gl::COLOR_BUFFER_BIT; + } + if let Some(depth) = depth { + gl::ClearDepth(depth as GLdouble); + flags |= gl::DEPTH_BUFFER_BIT; + } + if let Some(stencil) = stencil { + gl::ClearStencil(stencil as GLint); + flags |= gl::STENCIL_BUFFER_BIT; + } + if flags != 0 { + gl::Clear(flags); + } + } + } + + fn draw_arrays(&self, primitive: Primitive, index_count: u32, render_state: &RenderState) { + self.set_render_state(render_state); + unsafe { + gl::DrawArrays(primitive.to_gl_primitive(), 0, index_count as GLsizei); + } + self.reset_render_state(render_state); + } + + fn draw_elements(&self, primitive: Primitive, index_count: u32, render_state: &RenderState) { + self.set_render_state(render_state); + unsafe { + gl::DrawElements(primitive.to_gl_primitive(), + index_count as GLsizei, + gl::UNSIGNED_INT, + ptr::null()); + } + self.reset_render_state(render_state); + } + + fn draw_arrays_instanced(&self, + primitive: Primitive, + index_count: u32, + instance_count: u32, + render_state: &RenderState) { + self.set_render_state(render_state); + unsafe { + gl::DrawArraysInstanced(primitive.to_gl_primitive(), + 0, + index_count as GLsizei, + instance_count as GLsizei); + } + self.reset_render_state(render_state); + } + + #[inline] + fn create_timer_query(&self) -> GLTimerQuery { + let mut query = GLTimerQuery { gl_query: 0 }; + unsafe { + gl::GenQueries(1, &mut query.gl_query); + } + query + } + + #[inline] + fn begin_timer_query(&self, query: &Self::TimerQuery) { + unsafe { + gl::BeginQuery(gl::TIME_ELAPSED, query.gl_query); + } + } + + #[inline] + fn end_timer_query(&self, _: &Self::TimerQuery) { + unsafe { + gl::EndQuery(gl::TIME_ELAPSED); + } + } + + #[inline] + fn timer_query_is_available(&self, query: &Self::TimerQuery) -> bool { + unsafe { + let mut result = 0; + gl::GetQueryObjectiv(query.gl_query, gl::QUERY_RESULT_AVAILABLE, &mut result); + result != gl::FALSE as GLint + } + } + + #[inline] + fn get_timer_query(&self, query: &Self::TimerQuery) -> Duration { + unsafe { + let mut result = 0; + gl::GetQueryObjectui64v(query.gl_query, gl::QUERY_RESULT, &mut result); + Duration::from_nanos(result) + } + } + + #[inline] + fn bind_vertex_array(&self, vertex_array: &GLVertexArray) { + unsafe { + gl::BindVertexArray(vertex_array.gl_vertex_array); + } + } + + #[inline] + fn bind_buffer(&self, buffer: &GLBuffer, target: BufferTarget) { + unsafe { + gl::BindBuffer(target.to_gl_target(), buffer.gl_buffer); + } + } + + #[inline] + fn bind_default_framebuffer(&self, size: Point2DI32) { + unsafe { + gl::BindFramebuffer(gl::FRAMEBUFFER, 0); + gl::Viewport(0, 0, size.x(), size.y()); + } + } + + #[inline] + fn bind_framebuffer(&self, framebuffer: &GLFramebuffer) { + unsafe { + gl::BindFramebuffer(gl::FRAMEBUFFER, framebuffer.gl_framebuffer); + gl::Viewport(0, 0, framebuffer.texture.size.x(), framebuffer.texture.size.y()); + } + } + + #[inline] + fn bind_texture(&self, texture: &GLTexture, unit: u32) { + unsafe { + gl::ActiveTexture(gl::TEXTURE0 + unit); + gl::BindTexture(gl::TEXTURE_2D, texture.gl_texture); + } } } -pub struct VertexArray { +pub struct GLVertexArray { pub gl_vertex_array: GLuint, } -impl Drop for VertexArray { +impl Drop for GLVertexArray { #[inline] fn drop(&mut self) { unsafe { @@ -142,30 +563,11 @@ impl Drop for VertexArray { } } -impl VertexArray { - #[inline] - pub fn new() -> VertexArray { - unsafe { - let mut array = VertexArray { gl_vertex_array: 0 }; - gl::GenVertexArrays(1, &mut array.gl_vertex_array); - array - } - } -} - -pub struct VertexAttr { +pub struct GLVertexAttr { attr: GLuint, } -impl VertexAttr { - pub fn new(program: &Program, name: &str) -> VertexAttr { - let name = CString::new(format!("a{}", name)).unwrap(); - let attr = unsafe { - gl::GetAttribLocation(program.gl_program, name.as_ptr() as *const GLchar) as GLuint - }; - VertexAttr { attr } - } - +impl GLVertexAttr { pub fn configure_float(&self, size: GLint, gl_type: GLuint, @@ -199,37 +601,12 @@ impl VertexAttr { } } -pub struct Framebuffer { +pub struct GLFramebuffer { pub gl_framebuffer: GLuint, - pub texture: Texture, + pub texture: GLTexture, } -impl Framebuffer { - pub fn new(texture: Texture) -> Framebuffer { - let mut gl_framebuffer = 0; - unsafe { - gl::GenFramebuffers(1, &mut gl_framebuffer); - assert_eq!(gl::GetError(), gl::NO_ERROR); - gl::BindFramebuffer(gl::FRAMEBUFFER, gl_framebuffer); - texture.bind(0); - gl::FramebufferTexture2D(gl::FRAMEBUFFER, - gl::COLOR_ATTACHMENT0, - gl::TEXTURE_2D, - texture.gl_texture, - 0); - assert_eq!(gl::CheckFramebufferStatus(gl::FRAMEBUFFER), gl::FRAMEBUFFER_COMPLETE); - } - Framebuffer { gl_framebuffer, texture } - } - - pub fn bind(&self) { - unsafe { - gl::BindFramebuffer(gl::FRAMEBUFFER, self.gl_framebuffer); - } - } -} - -impl Drop for Framebuffer { +impl Drop for GLFramebuffer { fn drop(&mut self) { unsafe { gl::DeleteFramebuffers(1, &mut self.gl_framebuffer) @@ -237,39 +614,11 @@ impl Drop for Framebuffer { } } -pub struct Buffer { +pub struct GLBuffer { pub gl_buffer: GLuint, } -impl Buffer { - pub fn new() -> Buffer { - unsafe { - let mut gl_buffer = 0; - gl::GenBuffers(1, &mut gl_buffer); - Buffer { gl_buffer } - } - } - - pub fn upload(&self, data: &[T], target: BufferTarget, mode: BufferUploadMode) { - let target = match target { - BufferTarget::Vertex => gl::ARRAY_BUFFER, - BufferTarget::Index => gl::ELEMENT_ARRAY_BUFFER, - }; - let mode = match mode { - BufferUploadMode::Static => gl::STATIC_DRAW, - BufferUploadMode::Dynamic => gl::DYNAMIC_DRAW, - }; - unsafe { - gl::BindBuffer(target, self.gl_buffer); - gl::BufferData(target, - (data.len() * mem::size_of::()) as GLsizeiptr, - data.as_ptr() as *const GLvoid, - mode); - } - } -} - -impl Drop for Buffer { +impl Drop for GLBuffer { fn drop(&mut self) { unsafe { gl::DeleteBuffers(1, &mut self.gl_buffer) @@ -277,40 +626,20 @@ impl Drop for Buffer { } } -pub enum BufferTarget { - Vertex, - Index, -} - -pub enum BufferUploadMode { - Static, - Dynamic, -} - #[derive(Debug)] -pub struct Uniform { +pub struct GLUniform { pub location: GLint, } -impl Uniform { - pub fn new(program: &Program, name: &str) -> Uniform { - let name = CString::new(format!("u{}", name)).unwrap(); - let location = unsafe { - gl::GetUniformLocation(program.gl_program, name.as_ptr() as *const GLchar) - }; - Uniform { location } - } -} - -pub struct Program { +pub struct GLProgram { pub gl_program: GLuint, #[allow(dead_code)] - vertex_shader: Shader, + vertex_shader: GLShader, #[allow(dead_code)] - fragment_shader: Shader, + fragment_shader: GLShader, } -impl Drop for Program { +impl Drop for GLProgram { fn drop(&mut self) { unsafe { gl::DeleteProgram(self.gl_program) @@ -318,11 +647,11 @@ impl Drop for Program { } } -struct Shader { +pub struct GLShader { gl_shader: GLuint, } -impl Drop for Shader { +impl Drop for GLShader { fn drop(&mut self) { unsafe { gl::DeleteShader(self.gl_shader) @@ -330,99 +659,16 @@ impl Drop for Shader { } } -#[derive(Clone, Copy, Debug, PartialEq)] -enum ShaderKind { - Vertex, - Fragment, -} - -pub struct Texture { +pub struct GLTexture { gl_texture: GLuint, pub size: Point2DI32, } -impl Texture { - pub fn new_r16f(size: Point2DI32) -> Texture { - let mut texture = Texture { gl_texture: 0, size }; - unsafe { - gl::GenTextures(1, &mut texture.gl_texture); - texture.bind(0); - gl::TexImage2D(gl::TEXTURE_2D, - 0, - gl::R16F as GLint, - size.x() as GLsizei, - size.y() as GLsizei, - 0, - gl::RED, - gl::HALF_FLOAT, - ptr::null()); - } - - texture.set_parameters(); - texture - } - - pub fn new_rgba(size: Point2DI32) -> Texture { - let mut texture = Texture { gl_texture: 0, size }; - unsafe { - gl::GenTextures(1, &mut texture.gl_texture); - texture.bind(0); - gl::TexImage2D(gl::TEXTURE_2D, - 0, - gl::RGBA as GLint, - size.x() as GLsizei, - size.y() as GLsizei, - 0, - gl::RGBA, - gl::UNSIGNED_BYTE, - ptr::null()); - } - - texture.set_parameters(); - texture - } - - pub fn bind(&self, unit: u32) { - unsafe { - gl::ActiveTexture(gl::TEXTURE0 + unit); - gl::BindTexture(gl::TEXTURE_2D, self.gl_texture); - } - } - - pub fn upload_rgba(&self, size: Point2DI32, data: &[u8]) { - assert!(data.len() >= size.x() as usize * size.y() as usize * 4); - unsafe { - self.bind(0); - gl::TexImage2D(gl::TEXTURE_2D, - 0, - gl::RGBA as GLint, - size.x() as GLsizei, - size.y() as GLsizei, - 0, - gl::RGBA, - gl::UNSIGNED_BYTE, - data.as_ptr() as *const GLvoid); - } - - self.set_parameters(); - } - - fn set_parameters(&self) { - self.bind(0); - unsafe { - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as GLint); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as GLint); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as GLint); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as GLint); - } - } -} - -pub struct TimerQuery { +pub struct GLTimerQuery { gl_query: GLuint, } -impl Drop for TimerQuery { +impl Drop for GLTimerQuery { #[inline] fn drop(&mut self) { unsafe { @@ -431,70 +677,71 @@ impl Drop for TimerQuery { } } -impl TimerQuery { - #[inline] - pub fn new() -> TimerQuery { - let mut query = TimerQuery { gl_query: 0 }; - unsafe { - gl::GenQueries(1, &mut query.gl_query); - } - query - } +trait BufferTargetExt { + fn to_gl_target(self) -> GLuint; +} - #[inline] - pub fn begin(&self) { - unsafe { - gl::BeginQuery(gl::TIME_ELAPSED, self.gl_query); - } - } - - #[inline] - pub fn end(&self) { - unsafe { - gl::EndQuery(gl::TIME_ELAPSED); - } - } - - #[inline] - pub fn is_available(&self) -> bool { - unsafe { - let mut result = 0; - gl::GetQueryObjectiv(self.gl_query, gl::QUERY_RESULT_AVAILABLE, &mut result); - result != gl::FALSE as GLint - } - } - - #[inline] - pub fn get(&self) -> u64 { - unsafe { - let mut result = 0; - gl::GetQueryObjectui64v(self.gl_query, gl::QUERY_RESULT, &mut result); - result +impl BufferTargetExt for BufferTarget { + fn to_gl_target(self) -> GLuint { + match self { + BufferTarget::Vertex => gl::ARRAY_BUFFER, + BufferTarget::Index => gl::ELEMENT_ARRAY_BUFFER, } } } -// FIXME(pcwalton): Do something better! -fn locate_resources_directory() -> PathBuf { - let mut parent_directory = env::current_dir().unwrap(); - loop { - // So ugly :( - let mut resources_directory = parent_directory.clone(); - resources_directory.push("resources"); - if resources_directory.is_dir() { - let mut shaders_directory = resources_directory.clone(); - let mut textures_directory = resources_directory.clone(); - shaders_directory.push("shaders"); - textures_directory.push("textures"); - if shaders_directory.is_dir() && textures_directory.is_dir() { - return resources_directory; - } - } +trait DepthFuncExt { + fn to_gl_depth_func(self) -> GLenum; +} - if !parent_directory.pop() { - break; +impl DepthFuncExt for DepthFunc { + fn to_gl_depth_func(self) -> GLenum { + match self { + DepthFunc::Less => gl::LESS, + DepthFunc::Always => gl::ALWAYS, + } + } +} + +trait PrimitiveExt { + fn to_gl_primitive(self) -> GLuint; +} + +impl PrimitiveExt for Primitive { + fn to_gl_primitive(self) -> GLuint { + match self { + Primitive::Triangles => gl::TRIANGLES, + Primitive::TriangleFan => gl::TRIANGLE_FAN, + Primitive::Lines => gl::LINES, + } + } +} + +trait StencilFuncExt { + fn to_gl_stencil_func(self) -> GLenum; +} + +impl StencilFuncExt for StencilFunc { + fn to_gl_stencil_func(self) -> GLenum { + match self { + StencilFunc::Always => gl::ALWAYS, + StencilFunc::Equal => gl::EQUAL, + StencilFunc::NotEqual => gl::NOTEQUAL, + } + } +} + +trait VertexAttrTypeExt { + fn to_gl_type(self) -> GLuint; +} + +impl VertexAttrTypeExt for VertexAttrType { + fn to_gl_type(self) -> GLuint { + match self { + VertexAttrType::F32 => gl::FLOAT, + VertexAttrType::I16 => gl::SHORT, + VertexAttrType::U16 => gl::UNSIGNED_SHORT, + VertexAttrType::U8 => gl::UNSIGNED_BYTE, } } - - panic!("No suitable `resources/` directory found!"); } diff --git a/gl/src/renderer.rs b/gl/src/renderer.rs index 499abbc4..c5df17e8 100644 --- a/gl/src/renderer.rs +++ b/gl/src/renderer.rs @@ -9,14 +9,15 @@ // except according to those terms. use crate::debug::DebugUI; -use crate::device::{Buffer, BufferTarget, BufferUploadMode, Device, Framebuffer, Program, Texture}; -use crate::device::{TimerQuery, Uniform, VertexArray, VertexAttr}; -use gl::types::{GLfloat, GLint}; use pathfinder_geometry::basic::point::{Point2DI32, Point3DF32}; +use pathfinder_gpu::{BlendState, BufferTarget, BufferUploadMode, DepthFunc, DepthState, Device}; +use pathfinder_gpu::{Primitive, RenderState, Resources, StencilFunc, StencilState, TextureFormat}; +use pathfinder_gpu::{UniformData, VertexAttrType}; use pathfinder_renderer::gpu_data::{Batch, BuiltScene, SolidTileScenePrimitive}; use pathfinder_renderer::paint::{ColorU, ObjectShader}; use pathfinder_renderer::post::DefringingKernel; use pathfinder_renderer::tiles::{TILE_HEIGHT, TILE_WIDTH}; +use pathfinder_simd::default::{F32x4, I32x4}; use std::collections::VecDeque; use std::time::Duration; @@ -26,40 +27,43 @@ const MASK_FRAMEBUFFER_WIDTH: i32 = TILE_WIDTH as i32 * 256; const MASK_FRAMEBUFFER_HEIGHT: i32 = TILE_HEIGHT as i32 * 256; // TODO(pcwalton): Replace with `mem::size_of` calls? -const FILL_INSTANCE_SIZE: GLint = 8; -const SOLID_TILE_INSTANCE_SIZE: GLint = 6; -const MASK_TILE_INSTANCE_SIZE: GLint = 8; +const FILL_INSTANCE_SIZE: usize = 8; +const SOLID_TILE_INSTANCE_SIZE: usize = 6; +const MASK_TILE_INSTANCE_SIZE: usize = 8; const FILL_COLORS_TEXTURE_WIDTH: i32 = 256; const FILL_COLORS_TEXTURE_HEIGHT: i32 = 256; -pub struct Renderer { - // Core shaders - fill_program: FillProgram, - solid_tile_program: SolidTileProgram, - mask_tile_program: MaskTileProgram, - area_lut_texture: Texture, - quad_vertex_positions_buffer: Buffer, - fill_vertex_array: FillVertexArray, - mask_tile_vertex_array: MaskTileVertexArray, - solid_tile_vertex_array: SolidTileVertexArray, - mask_framebuffer: Framebuffer, - fill_colors_texture: Texture, +pub struct Renderer where D: Device { + // Device + pub device: D, + + // Core data + fill_program: FillProgram, + solid_tile_program: SolidTileProgram, + mask_tile_program: MaskTileProgram, + area_lut_texture: D::Texture, + quad_vertex_positions_buffer: D::Buffer, + fill_vertex_array: FillVertexArray, + mask_tile_vertex_array: MaskTileVertexArray, + solid_tile_vertex_array: SolidTileVertexArray, + mask_framebuffer: D::Framebuffer, + fill_colors_texture: D::Texture, // Postprocessing shader - postprocess_source_framebuffer: Option, - postprocess_program: PostprocessProgram, - postprocess_vertex_array: PostprocessVertexArray, - gamma_lut_texture: Texture, + postprocess_source_framebuffer: Option, + postprocess_program: PostprocessProgram, + postprocess_vertex_array: PostprocessVertexArray, + gamma_lut_texture: D::Texture, // Stencil shader - stencil_program: StencilProgram, - stencil_vertex_array: StencilVertexArray, + stencil_program: StencilProgram, + stencil_vertex_array: StencilVertexArray, // Debug - pending_timer_queries: VecDeque, - free_timer_queries: Vec, - pub debug_ui: DebugUI, + pending_timer_queries: VecDeque, + free_timer_queries: Vec, + pub debug_ui: DebugUI, // Extra info main_framebuffer_size: Point2DI32, @@ -67,43 +71,53 @@ pub struct Renderer { use_depth: bool, } -impl Renderer { - pub fn new(device: &Device, main_framebuffer_size: Point2DI32) -> Renderer { - let fill_program = FillProgram::new(device); - let solid_tile_program = SolidTileProgram::new(device); - let mask_tile_program = MaskTileProgram::new(device); +impl Renderer where D: Device { + pub fn new(device: D, resources: &Resources, main_framebuffer_size: Point2DI32) + -> Renderer { + let fill_program = FillProgram::new(&device, &resources); + let solid_tile_program = SolidTileProgram::new(&device, &resources); + let mask_tile_program = MaskTileProgram::new(&device, &resources); - let postprocess_program = PostprocessProgram::new(device); - let stencil_program = StencilProgram::new(device); + let postprocess_program = PostprocessProgram::new(&device, &resources); + let stencil_program = StencilProgram::new(&device, &resources); - let area_lut_texture = device.create_texture_from_png("area-lut"); - let gamma_lut_texture = device.create_texture_from_png("gamma-lut"); + let area_lut_texture = device.create_texture_from_png(&resources, "area-lut"); + let gamma_lut_texture = device.create_texture_from_png(&resources, "gamma-lut"); - let quad_vertex_positions_buffer = Buffer::new(); - quad_vertex_positions_buffer.upload(&QUAD_VERTEX_POSITIONS, - BufferTarget::Vertex, - BufferUploadMode::Static); + let quad_vertex_positions_buffer = device.create_buffer(); + device.upload_to_buffer(&quad_vertex_positions_buffer, + &QUAD_VERTEX_POSITIONS, + BufferTarget::Vertex, + BufferUploadMode::Static); - let fill_vertex_array = FillVertexArray::new(&fill_program, &quad_vertex_positions_buffer); - let mask_tile_vertex_array = MaskTileVertexArray::new(&mask_tile_program, + let fill_vertex_array = FillVertexArray::new(&device, + &fill_program, + &quad_vertex_positions_buffer); + let mask_tile_vertex_array = MaskTileVertexArray::new(&device, + &mask_tile_program, &quad_vertex_positions_buffer); - let solid_tile_vertex_array = SolidTileVertexArray::new(&solid_tile_program, + let solid_tile_vertex_array = SolidTileVertexArray::new(&device, + &solid_tile_program, &quad_vertex_positions_buffer); - - let postprocess_vertex_array = PostprocessVertexArray::new(&postprocess_program, + let postprocess_vertex_array = PostprocessVertexArray::new(&device, + &postprocess_program, &quad_vertex_positions_buffer); - let stencil_vertex_array = StencilVertexArray::new(&stencil_program); + let stencil_vertex_array = StencilVertexArray::new(&device, &stencil_program); - let mask_framebuffer_texture = Texture::new_r16f(Point2DI32::new(MASK_FRAMEBUFFER_WIDTH, - MASK_FRAMEBUFFER_HEIGHT)); - let mask_framebuffer = Framebuffer::new(mask_framebuffer_texture); + let mask_framebuffer_size = Point2DI32::new(MASK_FRAMEBUFFER_WIDTH, + MASK_FRAMEBUFFER_HEIGHT); + let mask_framebuffer_texture = device.create_texture(TextureFormat::R16F, + mask_framebuffer_size); + let mask_framebuffer = device.create_framebuffer(mask_framebuffer_texture); - let fill_colors_texture = Texture::new_rgba(Point2DI32::new(FILL_COLORS_TEXTURE_WIDTH, - FILL_COLORS_TEXTURE_HEIGHT)); + let fill_colors_size = Point2DI32::new(FILL_COLORS_TEXTURE_WIDTH, + FILL_COLORS_TEXTURE_HEIGHT); + let fill_colors_texture = device.create_texture(TextureFormat::RGBA8, fill_colors_size); - let debug_ui = DebugUI::new(device, main_framebuffer_size); + let debug_ui = DebugUI::new(&device, &resources, main_framebuffer_size); Renderer { + device, fill_program, solid_tile_program, mask_tile_program, @@ -137,8 +151,10 @@ impl Renderer { pub fn render_scene(&mut self, built_scene: &BuiltScene) { self.init_postprocessing_framebuffer(); - let timer_query = self.free_timer_queries.pop().unwrap_or_else(|| TimerQuery::new()); - timer_query.begin(); + let timer_query = self.free_timer_queries + .pop() + .unwrap_or_else(|| self.device.create_timer_query()); + self.device.begin_timer_query(&timer_query); self.upload_shaders(&built_scene.shaders); @@ -159,17 +175,17 @@ impl Renderer { self.postprocess(); } - timer_query.end(); + self.device.end_timer_query(&timer_query); self.pending_timer_queries.push_back(timer_query); } pub fn shift_timer_query(&mut self) -> Option { let query = self.pending_timer_queries.front()?; - if !query.is_available() { + if !self.device.timer_query_is_available(&query) { return None } let query = self.pending_timer_queries.pop_front().unwrap(); - let result = Duration::from_nanos(query.get()); + let result = self.device.get_timer_query(&query); self.free_timer_queries.push(query); Some(result) } @@ -211,7 +227,7 @@ impl Renderer { } #[inline] - pub fn quad_vertex_positions_buffer(&self) -> &Buffer { + pub fn quad_vertex_positions_buffer(&self) -> &D::Buffer { &self.quad_vertex_positions_buffer } @@ -224,204 +240,199 @@ impl Renderer { fill_colors[shader_index * 4 + 2] = shader.fill_color.b; fill_colors[shader_index * 4 + 3] = shader.fill_color.a; } - self.fill_colors_texture.upload_rgba(size, &fill_colors); + self.device.upload_to_texture(&self.fill_colors_texture, size, &fill_colors); } fn upload_solid_tiles(&mut self, solid_tiles: &[SolidTileScenePrimitive]) { - self.solid_tile_vertex_array - .vertex_buffer - .upload(solid_tiles, BufferTarget::Vertex, BufferUploadMode::Dynamic); + self.device.upload_to_buffer(&self.solid_tile_vertex_array.vertex_buffer, + solid_tiles, + BufferTarget::Vertex, + BufferUploadMode::Dynamic); } fn upload_batch(&mut self, batch: &Batch) { - self.fill_vertex_array - .vertex_buffer - .upload(&batch.fills, BufferTarget::Vertex, BufferUploadMode::Dynamic); - self.mask_tile_vertex_array - .vertex_buffer - .upload(&batch.mask_tiles, BufferTarget::Vertex, BufferUploadMode::Dynamic); + self.device.upload_to_buffer(&self.fill_vertex_array.vertex_buffer, + &batch.fills, + BufferTarget::Vertex, + BufferUploadMode::Dynamic); + self.device.upload_to_buffer(&self.mask_tile_vertex_array.vertex_buffer, + &batch.mask_tiles, + BufferTarget::Vertex, + BufferUploadMode::Dynamic); } fn draw_batch_fills(&mut self, batch: &Batch) { - unsafe { - self.mask_framebuffer.bind(); - gl::Viewport(0, 0, MASK_FRAMEBUFFER_WIDTH as GLint, MASK_FRAMEBUFFER_HEIGHT as GLint); - // TODO(pcwalton): Only clear the appropriate portion? - gl::ClearColor(0.0, 0.0, 0.0, 0.0); - gl::Clear(gl::COLOR_BUFFER_BIT); + self.device.bind_framebuffer(&self.mask_framebuffer); + // TODO(pcwalton): Only clear the appropriate portion? + self.device.clear(Some(F32x4::splat(0.0)), None, None); - gl::BindVertexArray(self.fill_vertex_array.vertex_array.gl_vertex_array); - gl::UseProgram(self.fill_program.program.gl_program); - gl::Uniform2f(self.fill_program.framebuffer_size_uniform.location, - MASK_FRAMEBUFFER_WIDTH as GLfloat, - MASK_FRAMEBUFFER_HEIGHT as GLfloat); - gl::Uniform2f(self.fill_program.tile_size_uniform.location, - TILE_WIDTH as GLfloat, - TILE_HEIGHT as GLfloat); - self.area_lut_texture.bind(0); - gl::Uniform1i(self.fill_program.area_lut_uniform.location, 0); - gl::Disable(gl::DEPTH_TEST); - gl::Disable(gl::STENCIL_TEST); - gl::BlendEquation(gl::FUNC_ADD); - gl::BlendFunc(gl::ONE, gl::ONE); - gl::Enable(gl::BLEND); - gl::DrawArraysInstanced(gl::TRIANGLE_FAN, 0, 4, batch.fills.len() as GLint); - gl::Disable(gl::BLEND); - } + self.device.bind_vertex_array(&self.fill_vertex_array.vertex_array); + self.device.use_program(&self.fill_program.program); + self.device.set_uniform(&self.fill_program.framebuffer_size_uniform, + UniformData::Vec2(I32x4::new(MASK_FRAMEBUFFER_WIDTH, + MASK_FRAMEBUFFER_HEIGHT, + 0, + 0).to_f32x4())); + self.device.set_uniform(&self.fill_program.tile_size_uniform, + UniformData::Vec2(I32x4::new(TILE_WIDTH as i32, + TILE_HEIGHT as i32, + 0, + 0).to_f32x4())); + self.device.bind_texture(&self.area_lut_texture, 0); + self.device.set_uniform(&self.fill_program.area_lut_uniform, + UniformData::TextureUnit(0)); + let render_state = RenderState { + blend: BlendState::RGBOneAlphaOne, + ..RenderState::default() + }; + self.device.draw_arrays_instanced(Primitive::TriangleFan, + 4, + batch.fills.len() as u32, + &render_state); } fn draw_batch_mask_tiles(&mut self, batch: &Batch) { - unsafe { - self.bind_draw_framebuffer(); - self.set_main_viewport(); + self.bind_draw_framebuffer(); - gl::BindVertexArray(self.mask_tile_vertex_array.vertex_array.gl_vertex_array); - gl::UseProgram(self.mask_tile_program.program.gl_program); - gl::Uniform2f(self.mask_tile_program.framebuffer_size_uniform.location, - self.main_framebuffer_size.x() as GLfloat, - self.main_framebuffer_size.y() as GLfloat); - gl::Uniform2f(self.mask_tile_program.tile_size_uniform.location, - TILE_WIDTH as GLfloat, - TILE_HEIGHT as GLfloat); - self.mask_framebuffer.texture.bind(0); - gl::Uniform1i(self.mask_tile_program.stencil_texture_uniform.location, 0); - gl::Uniform2f(self.mask_tile_program.stencil_texture_size_uniform.location, - MASK_FRAMEBUFFER_WIDTH as GLfloat, - MASK_FRAMEBUFFER_HEIGHT as GLfloat); - self.fill_colors_texture.bind(1); - gl::Uniform1i(self.mask_tile_program.fill_colors_texture_uniform.location, 1); - gl::Uniform2f(self.mask_tile_program.fill_colors_texture_size_uniform.location, - FILL_COLORS_TEXTURE_WIDTH as GLfloat, - FILL_COLORS_TEXTURE_HEIGHT as GLfloat); - // FIXME(pcwalton): Fill this in properly! - gl::Uniform2f(self.mask_tile_program.view_box_origin_uniform.location, 0.0, 0.0); - self.enable_blending(); - gl::Disable(gl::DEPTH_TEST); - self.setup_stencil_mask(); - gl::DrawArraysInstanced(gl::TRIANGLE_FAN, 0, 4, batch.mask_tiles.len() as GLint); - gl::Disable(gl::BLEND); - gl::Disable(gl::STENCIL_TEST); - } + self.device.bind_vertex_array(&self.mask_tile_vertex_array.vertex_array); + self.device.use_program(&self.mask_tile_program.program); + self.device.set_uniform(&self.mask_tile_program.framebuffer_size_uniform, + UniformData::Vec2(self.main_framebuffer_size.0.to_f32x4())); + self.device.set_uniform(&self.mask_tile_program.tile_size_uniform, + UniformData::Vec2(I32x4::new(TILE_WIDTH as i32, + TILE_HEIGHT as i32, + 0, + 0).to_f32x4())); + self.device.bind_texture(self.device.framebuffer_texture(&self.mask_framebuffer), 0); + self.device.set_uniform(&self.mask_tile_program.stencil_texture_uniform, + UniformData::TextureUnit(0)); + self.device.set_uniform(&self.mask_tile_program.stencil_texture_size_uniform, + UniformData::Vec2(I32x4::new(MASK_FRAMEBUFFER_WIDTH, + MASK_FRAMEBUFFER_HEIGHT, + 0, + 0).to_f32x4())); + self.device.bind_texture(&self.fill_colors_texture, 1); + self.device.set_uniform(&self.mask_tile_program.fill_colors_texture_uniform, + UniformData::TextureUnit(1)); + self.device.set_uniform(&self.mask_tile_program.fill_colors_texture_size_uniform, + UniformData::Vec2(I32x4::new(FILL_COLORS_TEXTURE_WIDTH, + FILL_COLORS_TEXTURE_HEIGHT, + 0, + 0).to_f32x4())); + // FIXME(pcwalton): Fill this in properly! + self.device.set_uniform(&self.mask_tile_program.view_box_origin_uniform, + UniformData::Vec2(F32x4::default())); + let render_state = RenderState { + blend: BlendState::RGBOneAlphaOneMinusSrcAlpha, + stencil: self.stencil_state(), + ..RenderState::default() + }; + self.device.draw_arrays_instanced(Primitive::TriangleFan, + 4, + batch.mask_tiles.len() as u32, + &render_state); } fn draw_solid_tiles(&mut self, built_scene: &BuiltScene) { - unsafe { - self.bind_draw_framebuffer(); - self.set_main_viewport(); - - gl::BindVertexArray(self.solid_tile_vertex_array.vertex_array.gl_vertex_array); - gl::UseProgram(self.solid_tile_program.program.gl_program); - gl::Uniform2f(self.solid_tile_program.framebuffer_size_uniform.location, - self.main_framebuffer_size.x() as GLfloat, - self.main_framebuffer_size.y() as GLfloat); - gl::Uniform2f(self.solid_tile_program.tile_size_uniform.location, - TILE_WIDTH as GLfloat, - TILE_HEIGHT as GLfloat); - self.fill_colors_texture.bind(0); - gl::Uniform1i(self.solid_tile_program.fill_colors_texture_uniform.location, 0); - gl::Uniform2f(self.solid_tile_program.fill_colors_texture_size_uniform.location, - FILL_COLORS_TEXTURE_WIDTH as GLfloat, - FILL_COLORS_TEXTURE_HEIGHT as GLfloat); - // FIXME(pcwalton): Fill this in properly! - gl::Uniform2f(self.solid_tile_program.view_box_origin_uniform.location, 0.0, 0.0); - gl::Disable(gl::BLEND); - gl::Disable(gl::DEPTH_TEST); - self.setup_stencil_mask(); - let count = built_scene.solid_tiles.len(); - gl::DrawArraysInstanced(gl::TRIANGLE_FAN, 0, 4, count as GLint); - gl::Disable(gl::STENCIL_TEST); - } + self.device.bind_vertex_array(&self.solid_tile_vertex_array.vertex_array); + self.device.use_program(&self.solid_tile_program.program); + self.device.set_uniform(&self.solid_tile_program.framebuffer_size_uniform, + UniformData::Vec2(self.main_framebuffer_size.0.to_f32x4())); + self.device.set_uniform(&self.solid_tile_program.tile_size_uniform, + UniformData::Vec2(I32x4::new(TILE_WIDTH as i32, + TILE_HEIGHT as i32, + 0, + 0).to_f32x4())); + self.device.bind_texture(&self.fill_colors_texture, 0); + self.device.set_uniform(&self.solid_tile_program.fill_colors_texture_uniform, + UniformData::TextureUnit(0)); + self.device.set_uniform(&self.solid_tile_program.fill_colors_texture_size_uniform, + UniformData::Vec2(I32x4::new(FILL_COLORS_TEXTURE_WIDTH, + FILL_COLORS_TEXTURE_HEIGHT, + 0, + 0).to_f32x4())); + // FIXME(pcwalton): Fill this in properly! + self.device.set_uniform(&self.solid_tile_program.view_box_origin_uniform, + UniformData::Vec2(F32x4::default())); + let render_state = RenderState { + stencil: self.stencil_state(), + ..RenderState::default() + }; + let count = built_scene.solid_tiles.len() as u32; + self.device.draw_arrays_instanced(Primitive::TriangleFan, 4, count, &render_state); } fn postprocess(&mut self) { - unsafe { - gl::BindFramebuffer(gl::FRAMEBUFFER, 0); - self.set_main_viewport(); + self.device.bind_default_framebuffer(self.main_framebuffer_size); - gl::BindVertexArray(self.postprocess_vertex_array.vertex_array.gl_vertex_array); - gl::UseProgram(self.postprocess_program.program.gl_program); - gl::Uniform2f(self.postprocess_program.framebuffer_size_uniform.location, - self.main_framebuffer_size.x() as GLfloat, - self.main_framebuffer_size.y() as GLfloat); - match self.postprocess_options.defringing_kernel { - Some(ref kernel) => { - debug_assert!(kernel.0.len() == 4); - let data: *const f32 = kernel.0.as_ptr(); - gl::Uniform4fv(self.postprocess_program.kernel_uniform.location, 1, data); - } - None => { - gl::Uniform4f(self.postprocess_program.kernel_uniform.location, - 0.0, - 0.0, - 0.0, - 0.0); - } + self.device.bind_vertex_array(&self.postprocess_vertex_array.vertex_array); + self.device.use_program(&self.postprocess_program.program); + self.device.set_uniform(&self.postprocess_program.framebuffer_size_uniform, + UniformData::Vec2(self.main_framebuffer_size.to_f32().0)); + match self.postprocess_options.defringing_kernel { + Some(ref kernel) => { + self.device.set_uniform(&self.postprocess_program.kernel_uniform, + UniformData::Vec4(F32x4::from_slice(&kernel.0))); } - self.postprocess_source_framebuffer.as_ref().unwrap().texture.bind(0); - gl::Uniform1i(self.postprocess_program.source_uniform.location, 0); - self.gamma_lut_texture.bind(1); - gl::Uniform1i(self.postprocess_program.gamma_lut_uniform.location, 1); - let gamma_correction_bg_color_uniform_location = - self.postprocess_program.gamma_correction_bg_color_uniform.location; - match self.postprocess_options.gamma_correction_bg_color { - None => { - gl::Uniform4f(gamma_correction_bg_color_uniform_location, 0.0, 0.0, 0.0, 0.0); - } - Some(color) => { - gl::Uniform4f(gamma_correction_bg_color_uniform_location, - color.r as f32 / 255.0, - color.g as f32 / 255.0, - color.b as f32 / 255.0, - color.a as f32 / 255.0); - } + None => { + self.device.set_uniform(&self.postprocess_program.kernel_uniform, + UniformData::Vec4(F32x4::default())); } - self.enable_blending(); - gl::DrawArrays(gl::TRIANGLE_FAN, 0, 4); - gl::Disable(gl::BLEND); } + let source_texture = + self.device.framebuffer_texture(self.postprocess_source_framebuffer.as_ref().unwrap()); + self.device.bind_texture(&source_texture, 0); + self.device.set_uniform(&self.postprocess_program.source_uniform, + UniformData::TextureUnit(0)); + self.device.bind_texture(&self.gamma_lut_texture, 1); + self.device.set_uniform(&self.postprocess_program.gamma_lut_uniform, + UniformData::TextureUnit(1)); + let gamma_correction_bg_color_uniform = &self.postprocess_program + .gamma_correction_bg_color_uniform; + match self.postprocess_options.gamma_correction_bg_color { + None => { + self.device.set_uniform(gamma_correction_bg_color_uniform, + UniformData::Vec4(F32x4::default())); + } + Some(color) => { + self.device.set_uniform(gamma_correction_bg_color_uniform, + UniformData::Vec4(color.to_f32().0)); + } + } + self.device.draw_arrays(Primitive::TriangleFan, 4, &RenderState { + blend: BlendState::RGBOneAlphaOneMinusSrcAlpha, + ..RenderState::default() + }); } fn draw_stencil(&self, quad_positions: &[Point3DF32]) { - self.stencil_vertex_array - .vertex_buffer - .upload(quad_positions, BufferTarget::Vertex, BufferUploadMode::Dynamic); + self.device.upload_to_buffer(&self.stencil_vertex_array.vertex_buffer, + quad_positions, + BufferTarget::Vertex, + BufferUploadMode::Dynamic); self.bind_draw_framebuffer(); - unsafe { - gl::BindVertexArray(self.stencil_vertex_array.vertex_array.gl_vertex_array); - gl::UseProgram(self.stencil_program.program.gl_program); - gl::ColorMask(gl::FALSE, gl::FALSE, gl::FALSE, gl::FALSE); - gl::DepthFunc(gl::LESS); - gl::Enable(gl::DEPTH_TEST); - gl::StencilFunc(gl::ALWAYS, 1, 1); - gl::StencilOp(gl::KEEP, gl::KEEP, gl::REPLACE); - gl::StencilMask(1); - gl::Enable(gl::STENCIL_TEST); - gl::Disable(gl::BLEND); - gl::DrawArrays(gl::TRIANGLE_FAN, 0, quad_positions.len() as GLint); - gl::ColorMask(gl::TRUE, gl::TRUE, gl::TRUE, gl::TRUE); - gl::Disable(gl::DEPTH_TEST); - gl::Disable(gl::STENCIL_TEST); - } + self.device.bind_vertex_array(&self.stencil_vertex_array.vertex_array); + self.device.use_program(&self.stencil_program.program); + self.device.draw_arrays(Primitive::TriangleFan, 4, &RenderState { + // FIXME(pcwalton): Should we really write to the depth buffer? + depth: Some(DepthState { func: DepthFunc::Less, write: true }), + stencil: Some(StencilState { + func: StencilFunc::Always, + reference: 1, + mask: 1, + pass_replace: true, + }), + color_mask: false, + ..RenderState::default() + }) } fn bind_draw_framebuffer(&self) { - unsafe { - if self.postprocessing_needed() { - let fbo = self.postprocess_source_framebuffer.as_ref().unwrap().gl_framebuffer; - gl::BindFramebuffer(gl::FRAMEBUFFER, fbo); - } else { - gl::BindFramebuffer(gl::FRAMEBUFFER, 0); - } - } - } - - fn set_main_viewport(&self) { - unsafe { - gl::Viewport(0, - 0, - self.main_framebuffer_size.x(), - self.main_framebuffer_size.y()); + if self.postprocessing_needed() { + self.device.bind_framebuffer(self.postprocess_source_framebuffer.as_ref().unwrap()); + } else { + self.device.bind_default_framebuffer(self.main_framebuffer_size); } } @@ -432,19 +443,18 @@ impl Renderer { } match self.postprocess_source_framebuffer { - Some(ref existing_framebuffer) if - existing_framebuffer.texture.size == self.main_framebuffer_size => {} + Some(ref framebuffer) if + self.device.texture_size(self.device.framebuffer_texture(framebuffer)) == + self.main_framebuffer_size => {} _ => { - self.postprocess_source_framebuffer = - Some(Framebuffer::new(Texture::new_rgba(self.main_framebuffer_size))); + let texture = self.device.create_texture(TextureFormat::RGBA8, + self.main_framebuffer_size); + self.postprocess_source_framebuffer = Some(self.device.create_framebuffer(texture)) } }; - unsafe { - self.postprocess_source_framebuffer.as_ref().unwrap().bind(); - gl::ClearColor(0.0, 0.0, 0.0, 0.0); - gl::Clear(gl::COLOR_BUFFER_BIT); - } + self.device.bind_framebuffer(self.postprocess_source_framebuffer.as_ref().unwrap()); + self.device.clear(Some(F32x4::default()), None, None); } fn postprocessing_needed(&self) -> bool { @@ -452,24 +462,12 @@ impl Renderer { self.postprocess_options.gamma_correction_bg_color.is_some() } - fn enable_blending(&self) { - unsafe { - gl::BlendEquation(gl::FUNC_ADD); - gl::BlendFuncSeparate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, gl::ONE, gl::ONE); - gl::Enable(gl::BLEND); + fn stencil_state(&self) -> Option { + if !self.use_depth { + return None; } - } - fn setup_stencil_mask(&self) { - unsafe { - if self.use_depth { - gl::StencilFunc(gl::EQUAL, 1, 1); - gl::StencilOp(gl::KEEP, gl::KEEP, gl::KEEP); - gl::Enable(gl::STENCIL_TEST); - } else { - gl::Disable(gl::STENCIL_TEST); - } - } + Some(StencilState { func: StencilFunc::Equal, reference: 1, mask: 1, pass_replace: false }) } } @@ -479,132 +477,208 @@ struct PostprocessOptions { gamma_correction_bg_color: Option, } -struct FillVertexArray { - vertex_array: VertexArray, - vertex_buffer: Buffer, +struct FillVertexArray where D: Device { + vertex_array: D::VertexArray, + vertex_buffer: D::Buffer, } -impl FillVertexArray { - fn new(fill_program: &FillProgram, quad_vertex_positions_buffer: &Buffer) -> FillVertexArray { - let (vertex_array, vertex_buffer) = (VertexArray::new(), Buffer::new()); - unsafe { - let tess_coord_attr = VertexAttr::new(&fill_program.program, "TessCoord"); - let from_px_attr = VertexAttr::new(&fill_program.program, "FromPx"); - let to_px_attr = VertexAttr::new(&fill_program.program, "ToPx"); - let from_subpx_attr = VertexAttr::new(&fill_program.program, "FromSubpx"); - let to_subpx_attr = VertexAttr::new(&fill_program.program, "ToSubpx"); - let tile_index_attr = VertexAttr::new(&fill_program.program, "TileIndex"); +impl FillVertexArray where D: Device { + fn new(device: &D, fill_program: &FillProgram, quad_vertex_positions_buffer: &D::Buffer) + -> FillVertexArray { + let vertex_array = device.create_vertex_array(); + let vertex_buffer = device.create_buffer(); - gl::BindVertexArray(vertex_array.gl_vertex_array); - gl::UseProgram(fill_program.program.gl_program); - gl::BindBuffer(gl::ARRAY_BUFFER, quad_vertex_positions_buffer.gl_buffer); - tess_coord_attr.configure_float(2, gl::UNSIGNED_BYTE, false, 0, 0, 0); - gl::BindBuffer(gl::ARRAY_BUFFER, vertex_buffer.gl_buffer); - from_px_attr.configure_int(1, gl::UNSIGNED_BYTE, FILL_INSTANCE_SIZE, 0, 1); - to_px_attr.configure_int(1, gl::UNSIGNED_BYTE, FILL_INSTANCE_SIZE, 1, 1); - from_subpx_attr.configure_float(2, gl::UNSIGNED_BYTE, true, FILL_INSTANCE_SIZE, 2, 1); - to_subpx_attr.configure_float(2, gl::UNSIGNED_BYTE, true, FILL_INSTANCE_SIZE, 4, 1); - tile_index_attr.configure_int(1, gl::UNSIGNED_SHORT, FILL_INSTANCE_SIZE, 6, 1); - } + let tess_coord_attr = device.get_vertex_attr(&fill_program.program, "TessCoord"); + let from_px_attr = device.get_vertex_attr(&fill_program.program, "FromPx"); + let to_px_attr = device.get_vertex_attr(&fill_program.program, "ToPx"); + let from_subpx_attr = device.get_vertex_attr(&fill_program.program, "FromSubpx"); + let to_subpx_attr = device.get_vertex_attr(&fill_program.program, "ToSubpx"); + let tile_index_attr = device.get_vertex_attr(&fill_program.program, "TileIndex"); + + device.bind_vertex_array(&vertex_array); + device.use_program(&fill_program.program); + device.bind_buffer(quad_vertex_positions_buffer, BufferTarget::Vertex); + device.configure_float_vertex_attr(&tess_coord_attr, + 2, + VertexAttrType::U8, + false, + 0, + 0, + 0); + device.bind_buffer(&vertex_buffer, BufferTarget::Vertex); + device.configure_int_vertex_attr(&from_px_attr, + 1, + VertexAttrType::U8, + FILL_INSTANCE_SIZE, + 0, + 1); + device.configure_int_vertex_attr(&to_px_attr, + 1, + VertexAttrType::U8, + FILL_INSTANCE_SIZE, + 1, + 1); + device.configure_float_vertex_attr(&from_subpx_attr, + 2, + VertexAttrType::U8, + true, + FILL_INSTANCE_SIZE, + 2, + 1); + device.configure_float_vertex_attr(&to_subpx_attr, + 2, + VertexAttrType::U8, + true, + FILL_INSTANCE_SIZE, + 4, + 1); + device.configure_int_vertex_attr(&tile_index_attr, + 1, + VertexAttrType::U16, + FILL_INSTANCE_SIZE, + 6, + 1); FillVertexArray { vertex_array, vertex_buffer } } } -struct MaskTileVertexArray { - vertex_array: VertexArray, - vertex_buffer: Buffer, +struct MaskTileVertexArray where D: Device { + vertex_array: D::VertexArray, + vertex_buffer: D::Buffer, } -impl MaskTileVertexArray { - fn new(mask_tile_program: &MaskTileProgram, quad_vertex_positions_buffer: &Buffer) - -> MaskTileVertexArray { - let (vertex_array, vertex_buffer) = (VertexArray::new(), Buffer::new()); - unsafe { - let tess_coord_attr = VertexAttr::new(&mask_tile_program.program, "TessCoord"); - let tile_origin_attr = VertexAttr::new(&mask_tile_program.program, "TileOrigin"); - let backdrop_attr = VertexAttr::new(&mask_tile_program.program, "Backdrop"); - let object_attr = VertexAttr::new(&mask_tile_program.program, "Object"); +impl MaskTileVertexArray where D: Device { + fn new(device: &D, + mask_tile_program: &MaskTileProgram, + quad_vertex_positions_buffer: &D::Buffer) + -> MaskTileVertexArray { + let (vertex_array, vertex_buffer) = (device.create_vertex_array(), device.create_buffer()); - // NB: The object must be of type short, not unsigned short, to work around a macOS - // Radeon driver bug. - gl::BindVertexArray(vertex_array.gl_vertex_array); - gl::UseProgram(mask_tile_program.program.gl_program); - gl::BindBuffer(gl::ARRAY_BUFFER, quad_vertex_positions_buffer.gl_buffer); - tess_coord_attr.configure_float(2, gl::UNSIGNED_BYTE, false, 0, 0, 0); - gl::BindBuffer(gl::ARRAY_BUFFER, vertex_buffer.gl_buffer); - tile_origin_attr.configure_float(2, gl::SHORT, false, MASK_TILE_INSTANCE_SIZE, 0, 1); - backdrop_attr.configure_int(1, gl::SHORT, MASK_TILE_INSTANCE_SIZE, 4, 1); - object_attr.configure_int(2, gl::SHORT, MASK_TILE_INSTANCE_SIZE, 6, 1); - } + let tess_coord_attr = device.get_vertex_attr(&mask_tile_program.program, "TessCoord"); + let tile_origin_attr = device.get_vertex_attr(&mask_tile_program.program, "TileOrigin"); + let backdrop_attr = device.get_vertex_attr(&mask_tile_program.program, "Backdrop"); + let object_attr = device.get_vertex_attr(&mask_tile_program.program, "Object"); + + // NB: The object must be of type `I16`, not `U16`, to work around a macOS Radeon + // driver bug. + device.bind_vertex_array(&vertex_array); + device.use_program(&mask_tile_program.program); + device.bind_buffer(quad_vertex_positions_buffer, BufferTarget::Vertex); + device.configure_float_vertex_attr(&tess_coord_attr, + 2, + VertexAttrType::U8, + false, + 0, + 0, + 0); + device.bind_buffer(&vertex_buffer, BufferTarget::Vertex); + device.configure_float_vertex_attr(&tile_origin_attr, + 2, + VertexAttrType::I16, + false, + MASK_TILE_INSTANCE_SIZE, + 0, + 1); + device.configure_int_vertex_attr(&backdrop_attr, + 1, + VertexAttrType::I16, + MASK_TILE_INSTANCE_SIZE, + 4, + 1); + device.configure_int_vertex_attr(&object_attr, + 2, + VertexAttrType::I16, + MASK_TILE_INSTANCE_SIZE, + 6, + 1); MaskTileVertexArray { vertex_array, vertex_buffer } } } -struct SolidTileVertexArray { - vertex_array: VertexArray, - vertex_buffer: Buffer, +struct SolidTileVertexArray where D: Device { + vertex_array: D::VertexArray, + vertex_buffer: D::Buffer, } -impl SolidTileVertexArray { - fn new(solid_tile_program: &SolidTileProgram, quad_vertex_positions_buffer: &Buffer) - -> SolidTileVertexArray { - let (vertex_array, vertex_buffer) = (VertexArray::new(), Buffer::new()); - unsafe { - let tess_coord_attr = VertexAttr::new(&solid_tile_program.program, "TessCoord"); - let tile_origin_attr = VertexAttr::new(&solid_tile_program.program, "TileOrigin"); - let object_attr = VertexAttr::new(&solid_tile_program.program, "Object"); +impl SolidTileVertexArray where D: Device { + fn new(device: &D, + solid_tile_program: &SolidTileProgram, + quad_vertex_positions_buffer: &D::Buffer) + -> SolidTileVertexArray { + let (vertex_array, vertex_buffer) = (device.create_vertex_array(), device.create_buffer()); - // NB: The object must be of type short, not unsigned short, to work around a macOS - // Radeon driver bug. - gl::BindVertexArray(vertex_array.gl_vertex_array); - gl::UseProgram(solid_tile_program.program.gl_program); - gl::BindBuffer(gl::ARRAY_BUFFER, quad_vertex_positions_buffer.gl_buffer); - tess_coord_attr.configure_float(2, gl::UNSIGNED_BYTE, false, 0, 0, 0); - gl::BindBuffer(gl::ARRAY_BUFFER, vertex_buffer.gl_buffer); - tile_origin_attr.configure_float(2, gl::SHORT, false, SOLID_TILE_INSTANCE_SIZE, 0, 1); - object_attr.configure_int(1, gl::SHORT, SOLID_TILE_INSTANCE_SIZE, 4, 1); - } + let tess_coord_attr = device.get_vertex_attr(&solid_tile_program.program, "TessCoord"); + let tile_origin_attr = device.get_vertex_attr(&solid_tile_program.program, "TileOrigin"); + let object_attr = device.get_vertex_attr(&solid_tile_program.program, "Object"); + + // NB: The object must be of type short, not unsigned short, to work around a macOS + // Radeon driver bug. + device.bind_vertex_array(&vertex_array); + device.use_program(&solid_tile_program.program); + device.bind_buffer(quad_vertex_positions_buffer, BufferTarget::Vertex); + device.configure_float_vertex_attr(&tess_coord_attr, + 2, + VertexAttrType::U8, + false, + 0, + 0, + 0); + device.bind_buffer(&vertex_buffer, BufferTarget::Vertex); + device.configure_float_vertex_attr(&tile_origin_attr, + 2, + VertexAttrType::I16, + false, + SOLID_TILE_INSTANCE_SIZE, + 0, + 1); + device.configure_int_vertex_attr(&object_attr, + 1, + VertexAttrType::I16, + SOLID_TILE_INSTANCE_SIZE, + 4, + 1); SolidTileVertexArray { vertex_array, vertex_buffer } } } -struct FillProgram { - program: Program, - framebuffer_size_uniform: Uniform, - tile_size_uniform: Uniform, - area_lut_uniform: Uniform, +struct FillProgram where D: Device { + program: D::Program, + framebuffer_size_uniform: D::Uniform, + tile_size_uniform: D::Uniform, + area_lut_uniform: D::Uniform, } -impl FillProgram { - fn new(device: &Device) -> FillProgram { - let program = device.create_program("fill"); - let framebuffer_size_uniform = Uniform::new(&program, "FramebufferSize"); - let tile_size_uniform = Uniform::new(&program, "TileSize"); - let area_lut_uniform = Uniform::new(&program, "AreaLUT"); +impl FillProgram where D: Device { + fn new(device: &D, resources: &Resources) -> FillProgram { + let program = device.create_program(resources, "fill"); + let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize"); + let tile_size_uniform = device.get_uniform(&program, "TileSize"); + let area_lut_uniform = device.get_uniform(&program, "AreaLUT"); FillProgram { program, framebuffer_size_uniform, tile_size_uniform, area_lut_uniform } } } -struct SolidTileProgram { - program: Program, - framebuffer_size_uniform: Uniform, - tile_size_uniform: Uniform, - fill_colors_texture_uniform: Uniform, - fill_colors_texture_size_uniform: Uniform, - view_box_origin_uniform: Uniform, +struct SolidTileProgram where D: Device { + program: D::Program, + framebuffer_size_uniform: D::Uniform, + tile_size_uniform: D::Uniform, + fill_colors_texture_uniform: D::Uniform, + fill_colors_texture_size_uniform: D::Uniform, + view_box_origin_uniform: D::Uniform, } -impl SolidTileProgram { - fn new(device: &Device) -> SolidTileProgram { - let program = device.create_program("solid_tile"); - let framebuffer_size_uniform = Uniform::new(&program, "FramebufferSize"); - let tile_size_uniform = Uniform::new(&program, "TileSize"); - let fill_colors_texture_uniform = Uniform::new(&program, "FillColorsTexture"); - let fill_colors_texture_size_uniform = Uniform::new(&program, "FillColorsTextureSize"); - let view_box_origin_uniform = Uniform::new(&program, "ViewBoxOrigin"); +impl SolidTileProgram where D: Device { + fn new(device: &D, resources: &Resources) -> SolidTileProgram { + let program = device.create_program(resources, "solid_tile"); + let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize"); + let tile_size_uniform = device.get_uniform(&program, "TileSize"); + let fill_colors_texture_uniform = device.get_uniform(&program, "FillColorsTexture"); + let fill_colors_texture_size_uniform = device.get_uniform(&program, + "FillColorsTextureSize"); + let view_box_origin_uniform = device.get_uniform(&program, "ViewBoxOrigin"); SolidTileProgram { program, framebuffer_size_uniform, @@ -616,27 +690,28 @@ impl SolidTileProgram { } } -struct MaskTileProgram { - program: Program, - framebuffer_size_uniform: Uniform, - tile_size_uniform: Uniform, - stencil_texture_uniform: Uniform, - stencil_texture_size_uniform: Uniform, - fill_colors_texture_uniform: Uniform, - fill_colors_texture_size_uniform: Uniform, - view_box_origin_uniform: Uniform, +struct MaskTileProgram where D: Device { + program: D::Program, + framebuffer_size_uniform: D::Uniform, + tile_size_uniform: D::Uniform, + stencil_texture_uniform: D::Uniform, + stencil_texture_size_uniform: D::Uniform, + fill_colors_texture_uniform: D::Uniform, + fill_colors_texture_size_uniform: D::Uniform, + view_box_origin_uniform: D::Uniform, } -impl MaskTileProgram { - fn new(device: &Device) -> MaskTileProgram { - let program = device.create_program("mask_tile"); - let framebuffer_size_uniform = Uniform::new(&program, "FramebufferSize"); - let tile_size_uniform = Uniform::new(&program, "TileSize"); - let stencil_texture_uniform = Uniform::new(&program, "StencilTexture"); - let stencil_texture_size_uniform = Uniform::new(&program, "StencilTextureSize"); - let fill_colors_texture_uniform = Uniform::new(&program, "FillColorsTexture"); - let fill_colors_texture_size_uniform = Uniform::new(&program, "FillColorsTextureSize"); - let view_box_origin_uniform = Uniform::new(&program, "ViewBoxOrigin"); +impl MaskTileProgram where D: Device { + fn new(device: &D, resources: &Resources) -> MaskTileProgram { + let program = device.create_program(resources, "mask_tile"); + let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize"); + let tile_size_uniform = device.get_uniform(&program, "TileSize"); + let stencil_texture_uniform = device.get_uniform(&program, "StencilTexture"); + let stencil_texture_size_uniform = device.get_uniform(&program, "StencilTextureSize"); + let fill_colors_texture_uniform = device.get_uniform(&program, "FillColorsTexture"); + let fill_colors_texture_size_uniform = device.get_uniform(&program, + "FillColorsTextureSize"); + let view_box_origin_uniform = device.get_uniform(&program, "ViewBoxOrigin"); MaskTileProgram { program, framebuffer_size_uniform, @@ -650,23 +725,24 @@ impl MaskTileProgram { } } -struct PostprocessProgram { - program: Program, - source_uniform: Uniform, - framebuffer_size_uniform: Uniform, - kernel_uniform: Uniform, - gamma_lut_uniform: Uniform, - gamma_correction_bg_color_uniform: Uniform, +struct PostprocessProgram where D: Device { + program: D::Program, + source_uniform: D::Uniform, + framebuffer_size_uniform: D::Uniform, + kernel_uniform: D::Uniform, + gamma_lut_uniform: D::Uniform, + gamma_correction_bg_color_uniform: D::Uniform, } -impl PostprocessProgram { - fn new(device: &Device) -> PostprocessProgram { - let program = device.create_program("post"); - let source_uniform = Uniform::new(&program, "Source"); - let framebuffer_size_uniform = Uniform::new(&program, "FramebufferSize"); - let kernel_uniform = Uniform::new(&program, "Kernel"); - let gamma_lut_uniform = Uniform::new(&program, "GammaLUT"); - let gamma_correction_bg_color_uniform = Uniform::new(&program, "GammaCorrectionBGColor"); +impl PostprocessProgram where D: Device { + fn new(device: &D, resources: &Resources) -> PostprocessProgram { + let program = device.create_program(resources, "post"); + let source_uniform = device.get_uniform(&program, "Source"); + let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize"); + let kernel_uniform = device.get_uniform(&program, "Kernel"); + let gamma_lut_uniform = device.get_uniform(&program, "GammaLUT"); + let gamma_correction_bg_color_uniform = device.get_uniform(&program, + "GammaCorrectionBGColor"); PostprocessProgram { program, source_uniform, @@ -678,54 +754,65 @@ impl PostprocessProgram { } } -struct PostprocessVertexArray { - vertex_array: VertexArray, +struct PostprocessVertexArray where D: Device { + vertex_array: D::VertexArray, } -impl PostprocessVertexArray { - fn new(postprocess_program: &PostprocessProgram, quad_vertex_positions_buffer: &Buffer) - -> PostprocessVertexArray { - let vertex_array = VertexArray::new(); - unsafe { - let position_attr = VertexAttr::new(&postprocess_program.program, "Position"); +impl PostprocessVertexArray where D: Device { + fn new(device: &D, + postprocess_program: &PostprocessProgram, + quad_vertex_positions_buffer: &D::Buffer) + -> PostprocessVertexArray { + let vertex_array = device.create_vertex_array(); + let position_attr = device.get_vertex_attr(&postprocess_program.program, "Position"); - gl::BindVertexArray(vertex_array.gl_vertex_array); - gl::UseProgram(postprocess_program.program.gl_program); - gl::BindBuffer(gl::ARRAY_BUFFER, quad_vertex_positions_buffer.gl_buffer); - position_attr.configure_float(2, gl::UNSIGNED_BYTE, false, 0, 0, 0); - } + device.bind_vertex_array(&vertex_array); + device.use_program(&postprocess_program.program); + device.bind_buffer(quad_vertex_positions_buffer, BufferTarget::Vertex); + device.configure_float_vertex_attr(&position_attr, + 2, + VertexAttrType::U8, + false, + 0, + 0, + 0); PostprocessVertexArray { vertex_array } } } -struct StencilProgram { - program: Program, +struct StencilProgram where D: Device { + program: D::Program, } -impl StencilProgram { - fn new(device: &Device) -> StencilProgram { - let program = device.create_program("stencil"); +impl StencilProgram where D: Device { + fn new(device: &D, resources: &Resources) -> StencilProgram { + let program = device.create_program(resources, "stencil"); StencilProgram { program } } } -struct StencilVertexArray { - vertex_array: VertexArray, - vertex_buffer: Buffer, +struct StencilVertexArray where D: Device { + vertex_array: D::VertexArray, + vertex_buffer: D::Buffer, } -impl StencilVertexArray { - fn new(stencil_program: &StencilProgram) -> StencilVertexArray { - let (vertex_array, vertex_buffer) = (VertexArray::new(), Buffer::new()); - unsafe { - let position_attr = VertexAttr::new(&stencil_program.program, "Position"); +impl StencilVertexArray where D: Device { + fn new(device: &D, stencil_program: &StencilProgram) -> StencilVertexArray { + let (vertex_array, vertex_buffer) = (device.create_vertex_array(), device.create_buffer()); - gl::BindVertexArray(vertex_array.gl_vertex_array); - gl::UseProgram(stencil_program.program.gl_program); - gl::BindBuffer(gl::ARRAY_BUFFER, vertex_buffer.gl_buffer); - position_attr.configure_float(3, gl::FLOAT, false, 4 * 4, 0, 0); - } + let position_attr = device.get_vertex_attr(&stencil_program.program, "Position"); + + device.bind_vertex_array(&vertex_array); + device.use_program(&stencil_program.program); + device.bind_buffer(&vertex_buffer, BufferTarget::Vertex); + device.configure_float_vertex_attr(&position_attr, + 3, + VertexAttrType::F32, + false, + 4 * 4, + 0, + 0); StencilVertexArray { vertex_array, vertex_buffer } } diff --git a/gpu/Cargo.toml b/gpu/Cargo.toml new file mode 100644 index 00000000..c493efa2 --- /dev/null +++ b/gpu/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "pathfinder_gpu" +version = "0.1.0" +authors = ["Patrick Walton "] +edition = "2018" + +[dependencies] + +[dependencies.image] +version = "0.21" +default-features = false +features = ["png_codec"] + +[dependencies.pathfinder_geometry] +path = "../geometry" + +[dependencies.pathfinder_simd] +path = "../simd" diff --git a/gpu/src/lib.rs b/gpu/src/lib.rs new file mode 100644 index 00000000..a81ab8f9 --- /dev/null +++ b/gpu/src/lib.rs @@ -0,0 +1,266 @@ +// pathfinder/gpu/src/lib.rs +// +// Copyright © 2019 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Minimal abstractions over GPU device capabilities. + +use pathfinder_geometry::basic::point::Point2DI32; +use pathfinder_simd::default::F32x4; +use std::env; +use std::fs::File; +use std::io::Read; +use std::path::PathBuf; +use std::time::Duration; + +pub trait Device { + type Buffer; + type Framebuffer; + type Program; + type Shader; + type Texture; + type TimerQuery; + type Uniform; + type VertexArray; + type VertexAttr; + + fn create_texture(&self, format: TextureFormat, size: Point2DI32) -> Self::Texture; + fn create_texture_from_data(&self, size: Point2DI32, data: &[u8]) -> Self::Texture; + fn create_shader_from_source(&self, name: &str, source: &[u8], kind: ShaderKind) + -> Self::Shader; + fn create_vertex_array(&self) -> Self::VertexArray; + fn create_program_from_shaders(&self, + name: &str, + vertex_shader: Self::Shader, + fragment_shader: Self::Shader) + -> Self::Program; + fn get_vertex_attr(&self, program: &Self::Program, name: &str) -> Self::VertexAttr; + fn get_uniform(&self, program: &Self::Program, name: &str) -> Self::Uniform; + fn use_program(&self, program: &Self::Program); + fn configure_float_vertex_attr(&self, + attr: &Self::VertexAttr, + size: usize, + attr_type: VertexAttrType, + normalized: bool, + stride: usize, + offset: usize, + divisor: u32); + fn configure_int_vertex_attr(&self, + attr: &Self::VertexAttr, + size: usize, + attr_type: VertexAttrType, + stride: usize, + offset: usize, + divisor: u32); + fn set_uniform(&self, uniform: &Self::Uniform, data: UniformData); + fn create_framebuffer(&self, texture: Self::Texture) -> Self::Framebuffer; + fn create_buffer(&self) -> Self::Buffer; + fn upload_to_buffer(&self, + buffer: &Self::Buffer, + data: &[T], + target: BufferTarget, + mode: BufferUploadMode); + fn framebuffer_texture<'f>(&self, framebuffer: &'f Self::Framebuffer) -> &'f Self::Texture; + fn texture_size(&self, texture: &Self::Texture) -> Point2DI32; + fn upload_to_texture(&self, texture: &Self::Texture, size: Point2DI32, data: &[u8]); + fn read_pixels_from_default_framebuffer(&self, size: Point2DI32) -> Vec; + // TODO(pcwalton): Switch to `ColorF`! + fn clear(&self, color: Option, depth: Option, stencil: Option); + fn draw_arrays(&self, primitive: Primitive, index_count: u32, render_state: &RenderState); + fn draw_elements(&self, primitive: Primitive, index_count: u32, render_state: &RenderState); + fn draw_arrays_instanced(&self, + primitive: Primitive, + index_count: u32, + instance_count: u32, + render_state: &RenderState); + fn create_timer_query(&self) -> Self::TimerQuery; + fn begin_timer_query(&self, query: &Self::TimerQuery); + fn end_timer_query(&self, query: &Self::TimerQuery); + fn timer_query_is_available(&self, query: &Self::TimerQuery) -> bool; + fn get_timer_query(&self, query: &Self::TimerQuery) -> Duration; + + // TODO(pcwalton): Go bindless... + fn bind_vertex_array(&self, vertex_array: &Self::VertexArray); + fn bind_buffer(&self, buffer: &Self::Buffer, target: BufferTarget); + fn bind_default_framebuffer(&self, size: Point2DI32); + fn bind_framebuffer(&self, framebuffer: &Self::Framebuffer); + fn bind_texture(&self, texture: &Self::Texture, unit: u32); + + fn create_texture_from_png(&self, resources: &Resources, name: &str) -> Self::Texture { + let mut path = resources.resources_directory.clone(); + path.push("textures"); + path.push(format!("{}.png", name)); + + let image = image::open(&path).unwrap().to_luma(); + let size = Point2DI32::new(image.width() as i32, image.height() as i32); + self.create_texture_from_data(size, &image) + } + + fn create_shader(&self, resources: &Resources, name: &str, kind: ShaderKind) -> Self::Shader { + let suffix = match kind { ShaderKind::Vertex => 'v', ShaderKind::Fragment => 'f' }; + let mut path = resources.resources_directory.clone(); + path.push("shaders"); + path.push(format!("{}.{}s.glsl", name, suffix)); + + let mut source = vec![]; + File::open(&path).unwrap().read_to_end(&mut source).unwrap(); + self.create_shader_from_source(name, &source, kind) + } + + fn create_program(&self, resources: &Resources, name: &str) -> Self::Program { + let vertex_shader = self.create_shader(resources, name, ShaderKind::Vertex); + let fragment_shader = self.create_shader(resources, name, ShaderKind::Fragment); + self.create_program_from_shaders(name, vertex_shader, fragment_shader) + } +} + +#[derive(Clone, Copy, Debug)] +pub enum TextureFormat { + R16F, + RGBA8, +} + +#[derive(Clone, Copy, Debug)] +pub enum VertexAttrType { + F32, + I16, + U16, + U8, +} + +#[derive(Clone, Copy, Debug)] +pub enum BufferTarget { + Vertex, + Index, +} + +#[derive(Clone, Copy, Debug)] +pub enum BufferUploadMode { + Static, + Dynamic, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ShaderKind { + Vertex, + Fragment, +} + +#[derive(Clone, Copy)] +pub enum UniformData { + Vec2(F32x4), + Vec4(F32x4), + TextureUnit(u32), +} + +#[derive(Clone, Copy)] +pub enum Primitive { + Triangles, + TriangleFan, + Lines, +} + +#[derive(Clone, Default, Debug)] +pub struct RenderState { + pub blend: BlendState, + pub depth: Option, + pub stencil: Option, + pub color_mask: bool, +} + +#[derive(Clone, Copy, Debug)] +pub enum BlendState { + Off, + RGBOneAlphaOneMinusSrcAlpha, + RGBOneAlphaOne, +} + +#[derive(Clone, Copy, Default, Debug)] +pub struct DepthState { + pub func: DepthFunc, + pub write: bool, +} + +#[derive(Clone, Copy, Debug)] +pub enum DepthFunc { + Less, + Always, +} + +#[derive(Clone, Copy, Debug)] +pub struct StencilState { + pub func: StencilFunc, + pub reference: u32, + pub mask: u32, + pub pass_replace: bool, +} + +#[derive(Clone, Copy, Debug)] +pub enum StencilFunc { + Always, + Equal, + NotEqual, +} + +impl Default for BlendState { + #[inline] + fn default() -> BlendState { + BlendState::Off + } +} + +impl Default for StencilState { + #[inline] + fn default() -> StencilState { + StencilState { func: StencilFunc::default(), reference: 0, mask: !0, pass_replace: false } + } +} + +impl Default for DepthFunc { + #[inline] + fn default() -> DepthFunc { + DepthFunc::Less + } +} + +impl Default for StencilFunc { + #[inline] + fn default() -> StencilFunc { + StencilFunc::Always + } +} + +pub struct Resources { + pub resources_directory: PathBuf, +} + +impl Resources { + pub fn locate() -> Resources { + let mut parent_directory = env::current_dir().unwrap(); + loop { + // So ugly :( + let mut resources_directory = parent_directory.clone(); + resources_directory.push("resources"); + if resources_directory.is_dir() { + let mut shaders_directory = resources_directory.clone(); + let mut textures_directory = resources_directory.clone(); + shaders_directory.push("shaders"); + textures_directory.push("textures"); + if shaders_directory.is_dir() && textures_directory.is_dir() { + return Resources { resources_directory }; + } + } + + if !parent_directory.pop() { + break; + } + } + + panic!("No suitable `resources/` directory found!"); + } +} diff --git a/simd/src/extras.rs b/simd/src/extras.rs index ade3b42b..51442c37 100644 --- a/simd/src/extras.rs +++ b/simd/src/extras.rs @@ -8,9 +8,16 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::default::{F32x4, U32x4}; +use crate::default::F32x4; impl F32x4 { + // Constructors + + #[inline] + pub fn from_slice(slice: &[f32]) -> F32x4 { + F32x4::new(slice[0], slice[1], slice[2], slice[3]) + } + // Accessors #[inline] diff --git a/simd/src/scalar.rs b/simd/src/scalar.rs index 701fb7eb..9282c6aa 100644 --- a/simd/src/scalar.rs +++ b/simd/src/scalar.rs @@ -131,11 +131,6 @@ impl F32x4 { F32x4([self[2], other[2], self[3], other[3]])) } - #[inline] - pub fn transpose4(a: &mut F32x4, b: &mut F32x4, c: &mut F32x4, d: &mut F32x4) { - unimplemented!() - } - #[inline] pub fn cross(&self, other: F32x4) -> F32x4 { unimplemented!() diff --git a/simd/src/x86.rs b/simd/src/x86.rs index 1c7669de..bedb5a1d 100644 --- a/simd/src/x86.rs +++ b/simd/src/x86.rs @@ -1427,11 +1427,6 @@ impl F32x4 { unsafe { F32x4(x86_64::_mm_shuffle_ps(self.0, other.0, 0b0001_1011)) } } - #[inline] - pub fn transpose_4x4(a: &mut F32x4, b: &mut F32x4, c: &mut F32x4, d: &mut F32x4) { - unsafe { x86_64::_MM_TRANSPOSE4_PS(&mut a.0, &mut b.0, &mut c.0, &mut d.0) } - } - // FIXME(pcwalton): Move to `Point3DF32`! #[inline] pub fn cross(&self, other: F32x4) -> F32x4 { diff --git a/ui/Cargo.toml b/ui/Cargo.toml new file mode 100644 index 00000000..28835996 --- /dev/null +++ b/ui/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "pathfinder_ui" +version = "0.1.0" +authors = ["Patrick Walton "] +edition = "2018" diff --git a/ui/src/lib.rs b/ui/src/lib.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/ui/src/lib.rs @@ -0,0 +1 @@ +