← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands

 

SirVer has proposed merging lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands.

Commit message:
Fixes line drawing by replacing the broken use of GL_LINES with a tessellation algorithm for drawing lines.

We are drawing a line segment as two triangles. The width of the lines is found by calculating the normal of a line and pushing the vertices of the triangles along this normal. At line segment joints, we construct a proper miter joint now, which makes line segments look really nice. We also antialias lines now, so that they are not as harsh as they were before. 

Reference I used for the implementation:
https://www.mapbox.com/blog/drawing-antialiased-lines
https://forum.libcinder.org/topic/smooth-thick-lines-using-geometry-shader#23286000001269127


Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #1296889 in widelands: "Soldiers' healthbar misses pixel"
  https://bugs.launchpad.net/widelands/+bug/1296889
  Bug #1531114 in widelands: "Lines are not drawn correctly "
  https://bugs.launchpad.net/widelands/+bug/1531114

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/beautiful_correct_lines/+merge/284517

See commit message.

Testers: see the statistics windows and the solider boxes and lines in general. Should be all fixed now. 


-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/beautiful_correct_lines into lp:widelands.
=== modified file 'src/base/point.h'
--- src/base/point.h	2014-11-27 18:27:31 +0000
+++ src/base/point.h	2016-01-30 15:36:48 +0000
@@ -20,10 +20,13 @@
 #ifndef WL_BASE_POINT_H
 #define WL_BASE_POINT_H
 
+#include <cmath>
 #include <limits>
 
 #include <stdint.h>
 
+// This class is our 2D vector class. We use it as Points, but also as Vectors
+// in some cases.
 template <typename T> struct GenericPoint {
 	GenericPoint(const T& px, const T& py) : x(px), y(py) {
 	}
@@ -76,4 +79,16 @@
 /// values).
 Point middle(const Point& a, const Point& b);
 
+// Returns a normalized version of 'v'.
+template <typename Vector> FloatPoint normalize(const Vector& v) {
+	const float len = std::hypot(v.x, v.y);
+	return FloatPoint(v.x / len, v.y / len);
+}
+
+// Returns the dot product of v1 with v2.
+inline float dot(const FloatPoint& v1, const FloatPoint& v2) {
+	return v1.x * v2.x + v1.y * v2.y;
+}
+
+
 #endif  // end of include guard: WL_BASE_POINT_H

=== modified file 'src/graphic/CMakeLists.txt'
--- src/graphic/CMakeLists.txt	2016-01-24 12:43:26 +0000
+++ src/graphic/CMakeLists.txt	2016-01-30 15:36:48 +0000
@@ -127,6 +127,7 @@
   SRCS
     blend_mode.h
     blit_mode.h
+    line_strip_mode.h
     gl/blit_program.cc
     gl/blit_program.h
     gl/draw_line_program.cc

=== modified file 'src/graphic/game_renderer.cc'
--- src/graphic/game_renderer.cc	2016-01-23 11:42:26 +0000
+++ src/graphic/game_renderer.cc	2016-01-30 15:36:48 +0000
@@ -218,7 +218,7 @@
 	RenderQueue::Item i;
 	i.program_id = RenderQueue::Program::kTerrainBase;
 	i.blend_mode = BlendMode::Copy;
-	i.destination_rect =
+	i.terrain_arguments.destination_rect =
 	   FloatRect(bounding_rect.x,
 	             surface_height - bounding_rect.y - bounding_rect.h,
 	             bounding_rect.w,

=== modified file 'src/graphic/gl/draw_line_program.cc'
--- src/graphic/gl/draw_line_program.cc	2016-01-09 15:27:05 +0000
+++ src/graphic/gl/draw_line_program.cc	2016-01-30 15:36:48 +0000
@@ -33,11 +33,14 @@
 // Attributes.
 attribute vec3 attr_position;
 attribute vec3 attr_color;
+attribute float attr_distance_from_center;
 
 varying vec3 var_color;
+varying float var_distance_from_center;
 
 void main() {
 	var_color = attr_color;
+	var_distance_from_center = attr_distance_from_center;
 	gl_Position = vec4(attr_position, 1.);
 }
 )";
@@ -46,18 +49,22 @@
 #version 120
 
 varying vec3 var_color;
+varying float var_distance_from_center;
+
+// The percentage of the line that should be solid, i.e. not feathered into
+// alpha = 0.
+#define SOLID 0.3
 
 void main() {
-	gl_FragColor = vec4(var_color.rgb, 1.);
+	// This means till SOLID we want full alpha, then a (1 - t**2) with t [0, 1]
+	// falloff.
+	float d = abs(var_distance_from_center);
+	float opaqueness =
+		step(SOLID, d) * ((d - SOLID) / (1. - SOLID));
+	gl_FragColor = vec4(var_color.rgb, 1. - pow(opaqueness, 2));
 }
 )";
 
-struct DrawBatch {
-	int offset;
-	int count;
-	int line_width;
-};
-
 }  // namespace
 
 // static
@@ -71,76 +78,52 @@
 
 	attr_position_ = glGetAttribLocation(gl_program_.object(), "attr_position");
 	attr_color_ = glGetAttribLocation(gl_program_.object(), "attr_color");
-}
-
-void DrawLineProgram::draw(const FloatPoint& start,
-                           const FloatPoint& end,
-                           const float z_value,
-                           const RGBColor& color,
-									int line_width) {
-	draw({Arguments{FloatRect(start.x, start.y, end.x - start.x, end.y - start.y),
-	                z_value,
-	                color,
-						 static_cast<uint8_t>(line_width),
-	                BlendMode::Copy}});
-}
-
-void DrawLineProgram::draw(std::vector<Arguments> arguments) {
-	size_t i = 0;
-
+	attr_distance_from_center_ = glGetAttribLocation(gl_program_.object(), "attr_distance_from_center");
+}
+
+void DrawLineProgram::draw(const std::vector<Arguments>& arguments) {
 	glUseProgram(gl_program_.object());
 
 	auto& gl_state = Gl::State::instance();
 	gl_state.enable_vertex_attrib_array({
-	   attr_position_, attr_color_,
+	   attr_position_, attr_color_, attr_distance_from_center_
 	});
 
 	gl_array_buffer_.bind();
 
 	Gl::vertex_attrib_pointer(attr_position_, 3, sizeof(PerVertexData), offsetof(PerVertexData, gl_x));
-	Gl::vertex_attrib_pointer(attr_color_, 3, sizeof(PerVertexData), offsetof(PerVertexData, color_r));
+	Gl::vertex_attrib_pointer(
+	   attr_color_, 3, sizeof(PerVertexData), offsetof(PerVertexData, color_r));
+	Gl::vertex_attrib_pointer(attr_distance_from_center_, 1, sizeof(PerVertexData),
+	                          offsetof(PerVertexData, distance_from_center));
 
 	vertices_.clear();
 
-	std::vector<DrawBatch> draw_batches;
-	int offset = 0;
-	while (i < arguments.size()) {
-		const Arguments& template_args = arguments[i];
-
-		while (i < arguments.size()) {
-			const Arguments& current_args = arguments[i];
-			if (current_args.line_width != template_args.line_width) {
-				break;
-			}
-			// We do not support anything else for drawing lines, really.
-			assert(current_args.blend_mode == BlendMode::Copy);
-
-			vertices_.emplace_back(current_args.destination_rect.x,
-			                       current_args.destination_rect.y,
-			                       current_args.z_value,
-			                       current_args.color.r / 255.,
-			                       current_args.color.g / 255.,
-			                       current_args.color.b / 255.);
-
-			vertices_.emplace_back(current_args.destination_rect.x + current_args.destination_rect.w,
-			                       current_args.destination_rect.y + current_args.destination_rect.h,
-			                       current_args.z_value,
-			                       current_args.color.r / 255.,
-			                       current_args.color.g / 255.,
-			                       current_args.color.b / 255.);
-			++i;
+	for (const Arguments& current_args : arguments) {
+		// We do not support anything else for drawing lines, really.
+		assert(current_args.blend_mode == BlendMode::UseAlpha);
+		assert(current_args.points.size() % 4 == 0);
+
+		const float r = current_args.color.r / 255.;
+		const float g = current_args.color.g / 255.;
+		const float b = current_args.color.b / 255.;
+		const float z_value = current_args.z_value;
+
+		const auto& p = current_args.points;
+		for (size_t i = 0; i < p.size(); i += 4) {
+			vertices_.emplace_back(
+			   PerVertexData{p[i].x, p[i].y, z_value, r, g, b, 1.f});
+			vertices_.emplace_back(
+			   PerVertexData{p[i + 1].x, p[i + 1].y, z_value, r, g, b, 1.f});
+			vertices_.emplace_back(
+			   PerVertexData{p[i + 2].x, p[i + 2].y, z_value, r, g, b, -1.f});
+			vertices_.emplace_back(vertices_[vertices_.size() - 2]);
+			vertices_.emplace_back(vertices_[vertices_.size() - 2]);
+			vertices_.emplace_back(
+			   PerVertexData{p[i + 3].x, p[i + 3].y, z_value, r, g, b, -1.f});
 		}
-
-		draw_batches.emplace_back(
-		   DrawBatch{offset, static_cast<int>(vertices_.size() - offset), template_args.line_width});
-		offset = vertices_.size();
 	}
-
 	gl_array_buffer_.update(vertices_);
 
-	// Now do the draw calls.
-	for (const auto& draw_arg : draw_batches) {
-		glLineWidth(draw_arg.line_width);
-		glDrawArrays(GL_LINES, draw_arg.offset, draw_arg.count);
-	}
+	glDrawArrays(GL_TRIANGLES, 0, vertices_.size());
 }

=== modified file 'src/graphic/gl/draw_line_program.h'
--- src/graphic/gl/draw_line_program.h	2016-01-05 11:28:54 +0000
+++ src/graphic/gl/draw_line_program.h	2016-01-30 15:36:48 +0000
@@ -31,51 +31,29 @@
 class DrawLineProgram {
 public:
 	struct Arguments {
-		// The line is drawn from the top left to the bottom right of
-		// this rectangle.
-		FloatRect destination_rect;
+		// Vertices of the quads that make up the lines plus. That means
+		// points.size() == <number of lines> * 4.
+		std::vector<FloatPoint> points;
+		RGBColor color;
 		float z_value;
-		RGBAColor color;
-		uint8_t line_width;
-		BlendMode blend_mode;
+		BlendMode blend_mode;  // Always BlendMode::kUseAlpha.
 	};
 
 	// Returns the (singleton) instance of this class.
 	static DrawLineProgram& instance();
 
-	// Draws a line from (x1, y1) to (x2, y2) which are in gl
-	// coordinates in 'color' with a 'line_width' in pixels.
-	void draw(const FloatPoint& start,
-	          const FloatPoint& end,
-	          const float z_value,
-	          const RGBColor& color,
-	          const int line_width);
-
-	void draw(std::vector<Arguments> arguments);
-
+	void draw(const std::vector<Arguments>& arguments);
 
 private:
-	DrawLineProgram();
-
 	struct PerVertexData {
-		PerVertexData(float init_gl_x,
-		              float init_gl_y,
-		              float init_gl_z,
-		              float init_color_r,
-		              float init_color_g,
-		              float init_color_b)
-		   : gl_x(init_gl_x),
-		     gl_y(init_gl_y),
-		     gl_z(init_gl_z),
-		     color_r(init_color_r),
-		     color_g(init_color_g),
-		     color_b(init_color_b) {
-		}
-
 		float gl_x, gl_y, gl_z;
 		float color_r, color_g, color_b;
+		// This value is changing from -1 to 1 and is used for alpha blending.
+		float distance_from_center;
 	};
-	static_assert(sizeof(PerVertexData) == 24, "Wrong padding.");
+	static_assert(sizeof(PerVertexData) == 28, "Wrong padding.");
+
+	DrawLineProgram();
 
 	// This is only kept around so that we do not constantly
 	// allocate memory for it.
@@ -90,6 +68,7 @@
 	// Attributes.
 	GLint attr_position_;
 	GLint attr_color_;
+	GLint attr_distance_from_center_;
 
 	DISALLOW_COPY_AND_ASSIGN(DrawLineProgram);
 };

=== added file 'src/graphic/line_strip_mode.h'
--- src/graphic/line_strip_mode.h	1970-01-01 00:00:00 +0000
+++ src/graphic/line_strip_mode.h	2016-01-30 15:36:48 +0000
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2006-2016 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#ifndef WL_GRAPHIC_LINE_STRIP_MODE_H
+#define WL_GRAPHIC_LINE_STRIP_MODE_H
+
+// A line strip can either be closed, which draws a line between the last and
+// the first line again or open which does not do that.
+enum class LineStripMode {
+	kClose,
+	kOpen,
+};
+
+#endif  // end of include guard: WL_GRAPHIC_LINE_STRIP_MODE_H

=== modified file 'src/graphic/render_queue.cc'
--- src/graphic/render_queue.cc	2016-01-20 20:04:52 +0000
+++ src/graphic/render_queue.cc	2016-01-30 15:36:48 +0000
@@ -77,6 +77,7 @@
 
 inline void from_item(const RenderQueue::Item& item, FillRectProgram::Arguments* args) {
 	args->color = item.rect_arguments.color;
+	args->destination_rect = item.rect_arguments.destination_rect;
 }
 
 inline void from_item(const RenderQueue::Item& item, BlitProgram::Arguments* args) {
@@ -84,11 +85,12 @@
 	args->blend = item.blit_arguments.blend;
 	args->mask = item.blit_arguments.mask;
 	args->blit_mode = item.blit_arguments.mode;
+	args->destination_rect = item.blit_arguments.destination_rect;
 }
 
 inline void from_item(const RenderQueue::Item& item, DrawLineProgram::Arguments* args) {
+	args->points = item.line_arguments.points;
 	args->color = item.line_arguments.color;
-	args->line_width = item.line_arguments.line_width;
 }
 
 // Batches up as many items from 'items' that have the same 'program_id'.
@@ -106,7 +108,6 @@
 		}
 		all_args.emplace_back();
 		T& args = all_args.back();
-		args.destination_rect = current_item.destination_rect;
 		args.z_value = current_item.z_value;
 		args.blend_mode = current_item.blend_mode;
 		from_item(current_item, &args);
@@ -159,10 +160,6 @@
 		   extra_value = given_item.blit_arguments.texture.texture_id;
 			break;
 
-		case Program::kLine:
-		   extra_value = given_item.line_arguments.line_width;
-			break;
-
 		case Program::kRect:
 		case Program::kTerrainBase:
 		case Program::kTerrainDither:
@@ -238,7 +235,7 @@
 			break;
 
 		case Program::kTerrainBase: {
-			ScopedScissor scoped_scissor(item.destination_rect);
+			ScopedScissor scoped_scissor(item.terrain_arguments.destination_rect);
 			terrain_program_->draw(item.terrain_arguments.gametime,
 			                       *item.terrain_arguments.terrains,
 			                       *item.terrain_arguments.fields_to_draw,
@@ -247,7 +244,7 @@
 		} break;
 
 		case Program::kTerrainDither: {
-			ScopedScissor scoped_scissor(item.destination_rect);
+			ScopedScissor scoped_scissor(item.terrain_arguments.destination_rect);
 			dither_program_->draw(item.terrain_arguments.gametime,
 			                      *item.terrain_arguments.terrains,
 			                      *item.terrain_arguments.fields_to_draw,
@@ -256,7 +253,7 @@
 		} break;
 
 		case Program::kTerrainRoad: {
-			ScopedScissor scoped_scissor(item.destination_rect);
+			ScopedScissor scoped_scissor(item.terrain_arguments.destination_rect);
 			road_program_->draw(item.terrain_arguments.renderbuffer_width,
 			                    item.terrain_arguments.renderbuffer_height,
 			                    *item.terrain_arguments.fields_to_draw,

=== modified file 'src/graphic/render_queue.h'
--- src/graphic/render_queue.h	2016-01-13 07:27:55 +0000
+++ src/graphic/render_queue.h	2016-01-30 15:36:48 +0000
@@ -98,15 +98,17 @@
 		BlitData texture;
 		BlitData mask;
 		RGBAColor blend;
+		FloatRect destination_rect;
 	};
 
 	struct RectArguments {
 		RGBAColor color;
+		FloatRect destination_rect;
 	};
 
 	struct LineArguments {
+		std::vector<FloatPoint> points;
 		RGBColor color;
-		uint8_t line_width;
 	};
 
 	struct TerrainArguments {
@@ -117,14 +119,13 @@
 		int renderbuffer_height;
 		const DescriptionMaintainer<Widelands::TerrainDescription>* terrains;
 		FieldsToDraw* fields_to_draw;
+		FloatRect destination_rect;
 	};
 
 	// The union of all possible program arguments represents an Item that is
 	// enqueued in the Queue. This is on purpose not done with OOP so that the
 	// queue is more cache friendly.
 	struct Item {
-		Item() {}
-
 		inline bool operator<(const Item& other) const {
 			return key < other.key;
 		}
@@ -136,9 +137,6 @@
 		// The z-value in GL space that will be used for drawing.
 		float z_value;
 
-		// The bounding box in the renderbuffer where this draw will change pixels.
-		FloatRect destination_rect;
-
 		// The key for sorting this item in the queue. It depends on the type of
 		// item how this is calculated, but it will contain at least the program,
 		// the z-layer, if it is opaque or transparent and program specific
@@ -148,12 +146,12 @@
 		// If this is opaque or, if not, which blend_mode to use.
 		BlendMode blend_mode;
 
-		union {
-			BlitArguments blit_arguments;
-			TerrainArguments terrain_arguments;
-			RectArguments rect_arguments;
-			LineArguments line_arguments;
-		};
+		// This is a logical union, i.e. only one of these members will be filled
+		// with useful data. It cannot be because some items are non POD.
+		BlitArguments blit_arguments;
+		TerrainArguments terrain_arguments;
+		RectArguments rect_arguments;
+		LineArguments line_arguments;
 	};
 
 	static RenderQueue& instance();

=== modified file 'src/graphic/rendertarget.cc'
--- src/graphic/rendertarget.cc	2016-01-27 08:06:40 +0000
+++ src/graphic/rendertarget.cc	2016-01-30 15:36:48 +0000
@@ -111,14 +111,16 @@
 /**
  * This functions draws a line in the target
  */
-void RenderTarget::draw_line(const Point& start,
-                             const Point& end,
-                             const RGBColor& color,
-                             uint8_t line_width) {
-	m_surface->draw_line(Point(start.x + m_offset.x + m_rect.x, start.y + m_offset.y + m_rect.y),
-	                     Point(end.x + m_offset.x + m_rect.x, end.y + m_offset.y + m_rect.y),
-	                     color,
-	                     line_width);
+void RenderTarget::draw_line_strip(const LineStripMode& line_strip_mode,
+                                   const std::vector<Point>& points,
+                                   const RGBColor& color,
+                                   float line_width) {
+	std::vector<Point> adjusted_points;
+	adjusted_points.reserve(points.size());
+	for (const Point& p : points) {
+		adjusted_points.emplace_back(p.x + m_offset.x + m_rect.x, p.y + m_offset.y + m_rect.y);
+	}
+	m_surface->draw_line_strip(line_strip_mode, adjusted_points, color, line_width);
 }
 
 /**

=== modified file 'src/graphic/rendertarget.h'
--- src/graphic/rendertarget.h	2016-01-28 21:27:04 +0000
+++ src/graphic/rendertarget.h	2016-01-30 15:36:48 +0000
@@ -27,6 +27,7 @@
 #include "graphic/blend_mode.h"
 #include "graphic/color.h"
 #include "graphic/image.h"
+#include "graphic/line_strip_mode.h"
 
 class Animation;
 class Surface;
@@ -57,7 +58,10 @@
 	int32_t width() const;
 	int32_t height() const;
 
-	void draw_line(const Point& start, const Point& end, const RGBColor& color, uint8_t width = 1);
+	void draw_line_strip(const LineStripMode& line_strip_mode,
+	                     const std::vector<Point>& points,
+	                     const RGBColor& color,
+	                     float width);
 	void draw_rect(const Rect&, const RGBColor&);
 	void fill_rect(const Rect&, const RGBAColor&);
 	void brighten_rect(const Rect&, int32_t factor);

=== modified file 'src/graphic/screen.cc'
--- src/graphic/screen.cc	2016-01-08 15:50:48 +0000
+++ src/graphic/screen.cc	2016-01-30 15:36:48 +0000
@@ -65,13 +65,13 @@
                      float opacity,
                      BlendMode blend_mode) {
 	RenderQueue::Item i;
-	i.destination_rect = dst_rect;
 	i.program_id = RenderQueue::Program::kBlit;
 	i.blend_mode = blend_mode;
-	i.blit_arguments.texture = texture;
-	i.blit_arguments.mask.texture_id = 0;
 	i.blit_arguments.blend = RGBAColor(0, 0, 0, 255 * opacity);
+	i.blit_arguments.destination_rect = dst_rect;
+	i.blit_arguments.mask.texture_id = 0;
 	i.blit_arguments.mode = BlitMode::kDirect;
+	i.blit_arguments.texture = texture;
 	RenderQueue::instance().enqueue(i);
 }
 
@@ -80,13 +80,13 @@
                              const BlitData& mask,
                              const RGBColor& blend) {
 	RenderQueue::Item i;
-	i.destination_rect = dst_rect;
 	i.program_id = RenderQueue::Program::kBlit;
 	i.blend_mode = BlendMode::UseAlpha;
-	i.blit_arguments.texture = texture;
+	i.blit_arguments.blend = blend;
+	i.blit_arguments.destination_rect = dst_rect;
 	i.blit_arguments.mask = mask;
-	i.blit_arguments.blend = blend;
 	i.blit_arguments.mode = BlitMode::kBlendedWithMask;
+	i.blit_arguments.texture = texture;
 	RenderQueue::instance().enqueue(i);
 }
 
@@ -94,34 +94,31 @@
                                 const BlitData& texture,
                                 const RGBAColor& blend) {
 	RenderQueue::Item i;
-	i.destination_rect = dst_rect;
 	i.program_id = RenderQueue::Program::kBlit;
 	i.blend_mode = BlendMode::UseAlpha;
-	i.blit_arguments.texture = texture;
+	i.blit_arguments.blend = blend;
+	i.blit_arguments.destination_rect = dst_rect;
 	i.blit_arguments.mask.texture_id = 0;
-	i.blit_arguments.blend = blend;
 	i.blit_arguments.mode = BlitMode::kMonochrome;
+	i.blit_arguments.texture = texture;
 	RenderQueue::instance().enqueue(i);
 }
 
-void Screen::do_draw_line(const FloatPoint& start,
-                          const FloatPoint& end,
-                          const RGBColor& color,
-								  const int line_width) {
+void Screen::do_draw_line_strip(const std::vector<FloatPoint>& gl_points,
+                                const RGBColor& color) {
 	RenderQueue::Item i;
 	i.program_id = RenderQueue::Program::kLine;
-	i.blend_mode = BlendMode::Copy;
-	i.destination_rect = FloatRect(start.x, start.y, end.x - start.x, end.y - start.y);
+	i.blend_mode = BlendMode::UseAlpha;
+	i.line_arguments.points = gl_points;
 	i.line_arguments.color = color;
-	i.line_arguments.line_width = line_width;
 	RenderQueue::instance().enqueue(i);
 }
 
 void Screen::do_fill_rect(const FloatRect& dst_rect, const RGBAColor& color, BlendMode blend_mode) {
 	RenderQueue::Item i;
+	i.blend_mode = blend_mode;
 	i.program_id = RenderQueue::Program::kRect;
-	i.blend_mode = blend_mode;
-	i.destination_rect = dst_rect;
 	i.rect_arguments.color = color;
+	i.rect_arguments.destination_rect = dst_rect;
 	RenderQueue::instance().enqueue(i);
 }

=== modified file 'src/graphic/screen.h'
--- src/graphic/screen.h	2016-01-04 20:54:08 +0000
+++ src/graphic/screen.h	2016-01-30 15:36:48 +0000
@@ -53,10 +53,8 @@
 	void do_blit_monochrome(const FloatRect& dst_rect,
 	                        const BlitData& texture,
 	                        const RGBAColor& blend) override;
-	void do_draw_line(const FloatPoint& start,
-	                  const FloatPoint& end,
-	                  const RGBColor& color,
-	                  int width) override;
+	void do_draw_line_strip(const std::vector<FloatPoint>& gl_points,
+	                        const RGBColor& color) override;
 	void
 	do_fill_rect(const FloatRect& dst_rect, const RGBAColor& color, BlendMode blend_mode) override;
 

=== modified file 'src/graphic/surface.cc'
--- src/graphic/surface.cc	2016-01-04 20:54:08 +0000
+++ src/graphic/surface.cc	2016-01-30 15:36:48 +0000
@@ -26,9 +26,11 @@
 #include <SDL.h>
 
 #include "base/macros.h"
+#include "base/point.h"
 #include "base/rect.h"
 #include "graphic/gl/coordinate_conversion.h"
 #include "graphic/gl/utils.h"
+#include "graphic/line_strip_mode.h"
 
 namespace {
 
@@ -41,13 +43,35 @@
 	return blit_data;
 }
 
+// Get the normal of the line between 'start' and 'end'.
+template <typename PointType>
+FloatPoint calculate_line_normal(const PointType& start, const PointType& end) {
+	const float dx = end.x - start.x;
+	const float dy = end.y - start.y;
+	const float len = std::hypot(dx, dy);
+	return FloatPoint(-dy / len, dx / len);
+}
+
+// Finds the pseudo-normal of a point at the join of two lines. We construct
+// this like a miter joint from woodworks. The best explanation I found for
+// this algorithm is here
+// https://forum.libcinder.org/topic/smooth-thick-lines-using-geometry-shader#23286000001269127
+FloatPoint calculate_pseudo_normal(const Point& p0, const Point& p1, const Point& p2) {
+	FloatPoint tangent = normalize(normalize(p2 - p1) + normalize(p1 - p0));
+	FloatPoint miter(-tangent.y, tangent.x);
+	float len = 1. / dot(miter, calculate_line_normal(p0, p1));
+	return FloatPoint(miter.x * len, miter.y * len);
+}
+
 }  // namespace
 
 void draw_rect(const Rect& rc, const RGBColor& clr, Surface* surface) {
-	surface->draw_line(Point(rc.x, rc.y), Point(rc.x + rc.w, rc.y), clr, 1);
-	surface->draw_line(Point(rc.x + rc.w, rc.y), Point(rc.x + rc.w, rc.y + rc.h), clr, 1);
-	surface->draw_line(Point(rc.x + rc.w, rc.y + rc.h), Point(rc.x, rc.y + rc.h), clr, 1);
-	surface->draw_line(Point(rc.x, rc.y + rc.h), Point(rc.x, rc.y), clr, 1);
+	surface->draw_line_strip(LineStripMode::kClose, {
+		Point(rc.x, rc.y),
+		Point(rc.x + rc.w, rc.y),
+		Point(rc.x + rc.w, rc.y + rc.h),
+		Point(rc.x, rc.y + rc.h),
+		}, clr, 1);
 }
 
 void Surface::fill_rect(const Rect& rc, const RGBAColor& clr) {
@@ -68,19 +92,81 @@
 	do_fill_rect(rect, color, blend_mode);
 }
 
-void Surface::draw_line
-	(const Point& start, const Point& end, const RGBColor& color, int line_width)
-{
-	float gl_x1 = start.x;
-	float gl_y1 = start.y;
-
-	// Include the end pixel.
-	float gl_x2 = end.x + 1.;
-	float gl_y2 = end.y + 1.;
-	pixel_to_gl_renderbuffer(width(), height(), &gl_x1, &gl_y1);
-	pixel_to_gl_renderbuffer(width(), height(), &gl_x2, &gl_y2);
-
-	do_draw_line(FloatPoint(gl_x1, gl_y1), FloatPoint(gl_x2, gl_y2), color, line_width);
+void Surface::draw_line_strip(const LineStripMode& line_strip_mode,
+                     std::vector<Point> points,
+                     const RGBColor& color,
+                     float line_width) {
+	assert(points.size() > 1);
+
+	// Figure out the tesselation for the line. The normal for a point is the
+	// average of the up to two lines it is part of.
+	std::vector<FloatPoint> normals;
+	for (size_t i = 0; i < points.size(); ++i) {
+		if (i == 0) {
+			switch (line_strip_mode) {
+			case LineStripMode::kOpen:
+				normals.push_back(calculate_line_normal(points.front(), points[1]));
+				break;
+
+			case LineStripMode::kClose:
+				normals.push_back(calculate_pseudo_normal(points.back(), points.front(), points[1]));
+				break;
+			}
+		} else if (i == points.size() - 1) {
+			switch (line_strip_mode) {
+			case LineStripMode::kOpen:
+				normals.push_back(calculate_line_normal(points[points.size() - 2], points.back()));
+				break;
+
+			case LineStripMode::kClose:
+				normals.push_back(
+				   calculate_pseudo_normal(points[points.size() - 2], points.back(), points.front()));
+				break;
+			}
+		} else {
+			normals.push_back(calculate_pseudo_normal(points[i - 1], points[i], points[i + 1]));
+		}
+	}
+	if (line_strip_mode == LineStripMode::kClose) {
+		// Push the first point as the last point again. For drawing we do not
+		// need to special case closed or open lines anymore.
+		normals.push_back(normals.front());
+		points.push_back(points.front());
+	}
+	assert(points.size() == normals.size());
+	assert(!points.empty());
+
+	std::vector<FloatPoint> gl_points;
+
+	// Iterate over each line segment, i.e. all points but the last, convert
+	// them from pixel space to gl space and draw them.
+	const auto w = width();
+	const auto h = height();
+	for (size_t i = 0; i < points.size() - 1; ++i) {
+		const FloatPoint p1 = FloatPoint(points[i].x, points[i].y);
+		const FloatPoint p2 = FloatPoint(points[i + 1].x, points[i + 1].y);
+		const FloatPoint scaled_n1(0.5 * line_width * normals[i].x, 0.5 * line_width * normals[i].y);
+		const FloatPoint scaled_n2(
+		   0.5 * line_width * normals[i + 1].x, 0.5 * line_width * normals[i + 1].y);
+
+		// Quad points are created in rendering order for OpenGL.
+		FloatPoint quad_a = p1 - scaled_n1;
+		pixel_to_gl_renderbuffer(w, h, &quad_a.x, &quad_a.y);
+		gl_points.emplace_back(quad_a);
+
+		FloatPoint quad_b = p2 - scaled_n2;
+		pixel_to_gl_renderbuffer(w, h, &quad_b.x, &quad_b.y);
+		gl_points.emplace_back(quad_b);
+
+		FloatPoint quad_c = p1 + scaled_n1 + scaled_n1;
+		pixel_to_gl_renderbuffer(w, h, &quad_c.x, &quad_c.y);
+		gl_points.emplace_back(quad_c);
+
+		FloatPoint quad_d = p2 + scaled_n2 + scaled_n2;
+		pixel_to_gl_renderbuffer(w, h, &quad_d.x, &quad_d.y);
+		gl_points.emplace_back(quad_d);
+	}
+	do_draw_line_strip(gl_points, color);
 }
 
 void Surface::blit_monochrome(const Rect& dst_rect,

=== modified file 'src/graphic/surface.h'
--- src/graphic/surface.h	2016-01-08 15:50:48 +0000
+++ src/graphic/surface.h	2016-01-30 15:36:48 +0000
@@ -21,12 +21,14 @@
 #define WL_GRAPHIC_SURFACE_H
 
 #include <memory>
+#include <vector>
 
 #include "base/macros.h"
 #include "base/rect.h"
 #include "graphic/blend_mode.h"
 #include "graphic/color.h"
 #include "graphic/image.h"
+#include "graphic/line_strip_mode.h"
 
 class Texture;
 
@@ -63,8 +65,13 @@
 	// in the target are just replaced (i.e. / BlendMode would be BlendMode::Copy).
 	void fill_rect(const Rect&, const RGBAColor&);
 
-	/// draw a line to the destination
-	void draw_line(const Point& start, const Point& end, const RGBColor& color, int width);
+	// Draw a 'width' pixel wide line to the destination. If 'line_strip_mode' is
+	// kClosed, the last point will be connected to the first one again.
+	// 'points' are taken by value on purpose.
+	void draw_line_strip(const LineStripMode& line_strip_mode,
+	                     std::vector<Point> points,
+	                     const RGBColor& color,
+	                     float width);
 
 	/// makes a rectangle on the destination brighter (or darker).
 	void brighten_rect(const Rect&, int factor);
@@ -85,8 +92,8 @@
 	                                const BlitData& texture,
 	                                const RGBAColor& blend) = 0;
 
-	virtual void
-	do_draw_line(const FloatPoint& start, const FloatPoint& end, const RGBColor& color, int width) = 0;
+	virtual void do_draw_line_strip(const std::vector<FloatPoint>& gl_points,
+	                                const RGBColor& color) = 0;
 
 	virtual void
 	do_fill_rect(const FloatRect& dst_rect, const RGBAColor& color, BlendMode blend_mode) = 0;

=== modified file 'src/graphic/texture.cc'
--- src/graphic/texture.cc	2016-01-28 06:31:40 +0000
+++ src/graphic/texture.cc	2016-01-30 15:36:48 +0000
@@ -304,13 +304,14 @@
 	BlitProgram::instance().draw_monochrome(dst_rect, 0.f, texture, blend);
 }
 
-void
-Texture::do_draw_line(const FloatPoint& start, const FloatPoint& end, const RGBColor& color, int line_width) {
+void Texture::do_draw_line_strip(const std::vector<FloatPoint>& gl_points,
+                        const RGBColor& color) {
 	if (m_blit_data.texture_id == 0) {
 		return;
 	}
 	setup_gl();
-	DrawLineProgram::instance().draw(start, end, 0.f, color, line_width);
+	DrawLineProgram::instance().draw({
+		DrawLineProgram::Arguments{gl_points, color, 0.f, BlendMode::UseAlpha}});
 }
 
 void

=== modified file 'src/graphic/texture.h'
--- src/graphic/texture.h	2016-01-04 20:54:08 +0000
+++ src/graphic/texture.h	2016-01-30 15:36:48 +0000
@@ -94,8 +94,8 @@
 	void do_blit_monochrome(const FloatRect& dst_rect,
 	                        const BlitData& texture,
 	                        const RGBAColor& blend) override;
-	void
-	do_draw_line(const FloatPoint& start, const FloatPoint& end, const RGBColor& color, int width) override;
+	void do_draw_line_strip(const std::vector<FloatPoint>& gl_points,
+	                        const RGBColor& color) override;
 	void
 	do_fill_rect(const FloatRect& dst_rect, const RGBAColor& color, BlendMode blend_mode) override;
 

=== modified file 'src/wui/plot_area.cc'
--- src/wui/plot_area.cc	2016-01-28 21:27:04 +0000
+++ src/wui/plot_area.cc	2016-01-30 15:36:48 +0000
@@ -48,7 +48,6 @@
 const int32_t space_left_of_label = 15;
 const uint32_t nr_samples = 30;   // How many samples per diagramm when relative plotting
 
-
 const uint32_t time_in_ms[] = {
 	15 * minutes,
 	30 * minutes,
@@ -60,8 +59,10 @@
 };
 
 const char BG_PIC[] = "pics/plot_area_bg.png";
-const RGBColor LINE_COLOR(0, 0, 0);
-const RGBColor ZERO_LINE_COLOR(255, 255, 255);
+const RGBColor kAxisLineColor(0, 0, 0);
+constexpr float kAxisLinesWidth = 1.5f;
+constexpr float kPlotLinesWidth = 2.5f;
+const RGBColor kZeroLineColor(255, 255, 255);
 
 enum UNIT {
 	UNIT_MIN,
@@ -184,34 +185,34 @@
 
 	// Draw coordinate system
 	// X Axis
-	dst.draw_line(Point(spacing, inner_h - space_at_bottom),
-	              Point(inner_w - space_at_right, inner_h - space_at_bottom),
-	              LINE_COLOR,
-	              2);
+	dst.draw_line_strip(LineStripMode::kOpen, {
+		Point(spacing, inner_h - space_at_bottom),
+		Point(inner_w - space_at_right, inner_h - space_at_bottom)},
+		kAxisLineColor, kAxisLinesWidth);
 	// Arrow
-	dst.draw_line(Point(spacing, inner_h - space_at_bottom),
-	              Point(spacing + 5, inner_h - space_at_bottom - 3),
-	              LINE_COLOR,
-	              2);
-	dst.draw_line(Point(spacing, inner_h - space_at_bottom),
-	              Point(spacing + 5, inner_h - space_at_bottom + 3),
-	              LINE_COLOR,
-	              2);
+	dst.draw_line_strip(LineStripMode::kOpen,
+		{
+		Point(spacing + 5, inner_h - space_at_bottom - 3),
+		Point(spacing, inner_h - space_at_bottom),
+		Point(spacing + 5, inner_h - space_at_bottom + 3),
+		},
+		kAxisLineColor, kAxisLinesWidth);
+
 	//  Y Axis
-	dst.draw_line(Point(inner_w - space_at_right, spacing),
-	              Point(inner_w - space_at_right, inner_h - space_at_bottom),
-	              LINE_COLOR,
-	              2);
+	dst.draw_line_strip(LineStripMode::kOpen, {
+		Point(inner_w - space_at_right, spacing),
+		Point(inner_w - space_at_right, inner_h - space_at_bottom)},
+		kAxisLineColor, kAxisLinesWidth);
 	//  No Arrow here, since this doesn't continue.
 
 	float sub = (xline_length - space_left_of_label) / how_many_ticks;
 	float posx = inner_w - space_at_right;
 
 	for (uint32_t i = 0; i <= how_many_ticks; ++i) {
-		dst.draw_line(Point(static_cast<int32_t>(posx), inner_h - space_at_bottom),
-		              Point(static_cast<int32_t>(posx), inner_h - space_at_bottom + 3),
-		              LINE_COLOR,
-		              2);
+		dst.draw_line_strip(LineStripMode::kOpen, {
+			Point(static_cast<int32_t>(posx), inner_h - space_at_bottom),
+			Point(static_cast<int32_t>(posx), inner_h - space_at_bottom + 3)},
+			kAxisLineColor, kAxisLinesWidth);
 
 		// The space at the end is intentional to have the tick centered
 		// over the number, not to the left
@@ -225,15 +226,14 @@
 	}
 
 	//  draw yticks, one at full, one at half
-	dst.draw_line(Point(inner_w - space_at_right, spacing),
-	              Point(inner_w - space_at_right - 3, spacing),
-	              LINE_COLOR,
-	              2);
-	dst.draw_line(
-	   Point(inner_w - space_at_right, spacing + ((inner_h - space_at_bottom) - spacing) / 2),
-	   Point(inner_w - space_at_right - 3, spacing + ((inner_h - space_at_bottom) - spacing) / 2),
-	   LINE_COLOR,
-	   2);
+	dst.draw_line_strip(LineStripMode::kOpen, {Point(inner_w - space_at_right, spacing),
+	                                           Point(inner_w - space_at_right - 3, spacing)},
+	                    kAxisLineColor, kAxisLinesWidth);
+	dst.draw_line_strip(
+		LineStripMode::kOpen, {
+		Point(inner_w - space_at_right, spacing + ((inner_h - space_at_bottom) - spacing) / 2),
+			Point(inner_w - space_at_right - 3, spacing + ((inner_h - space_at_bottom) - spacing) / 2),
+		}, kAxisLineColor, kAxisLinesWidth);
 
 	//  print the used unit
 	const Image* xtick = UI::g_fh1->render(xtick_text_style((boost::format(get_unit_name(unit)) % "").str()));
@@ -410,17 +410,18 @@
 		(RenderTarget & dst, std::vector<uint32_t> const * dataset, float const yline_length,
 		 uint32_t const highest_scale, float const sub, RGBColor const color, int32_t const yoffset)
 {
-
 	float posx = get_inner_w() - space_at_right;
-
-	int32_t lx = get_inner_w() - space_at_right;
-	int32_t ly = yoffset;
-	//init start point of the plot line with the first data value.
-	//this prevent that the plot start always at zero
-	if (int32_t value = (*dataset)[dataset->size() - 1]) {
+	const int lx = get_inner_w() - space_at_right;
+	int ly = yoffset;
+	// init start point of the plot line with the first data value.
+	// this prevent that the plot start always at zero
+	if (int value = (*dataset)[dataset->size() - 1]) {
 		ly -= static_cast<int32_t>(scale_value(yline_length, highest_scale, value));
 	}
 
+	std::vector<Point> points;
+	points.emplace_back(lx, ly);
+
 	for (int32_t i = dataset->size() - 1; i > 0 && posx > spacing; --i) {
 		int32_t const curx = static_cast<int32_t>(posx);
 		int32_t       cury = yoffset;
@@ -428,17 +429,12 @@
 		//scale the line to the available space
 		if (int32_t value = (*dataset)[i]) {
 			const float length_y = scale_value(yline_length, highest_scale, value);
-
 			cury -= static_cast<int32_t>(length_y);
 		}
-
-		dst.draw_line(Point(lx, ly), Point(curx, cury), color, 2);
-
+		points.emplace_back(curx, cury);
 		posx -= sub;
-
-		lx = curx;
-		ly = cury;
 	}
+	dst.draw_line_strip(LineStripMode::kOpen, points, color, kPlotLinesWidth);
 }
 
 /*
@@ -511,10 +507,10 @@
 	draw_diagram(time_ms, get_inner_w(), get_inner_h(), xline_length, dst);
 
 	// draw zero line
-	dst.draw_line(Point(get_inner_w() - space_at_right, yoffset),
-	              Point(get_inner_w() - space_at_right - xline_length, yoffset),
-	              ZERO_LINE_COLOR,
-	              2);
+	dst.draw_line_strip(LineStripMode::kOpen, {
+		Point(get_inner_w() - space_at_right, yoffset),
+		Point(get_inner_w() - space_at_right - xline_length, yoffset)},
+		kZeroLineColor, kPlotLinesWidth);
 
 	// How many do we take together when relative ploting
 	const int32_t how_many = calc_how_many(time_ms, sample_rate_);


Follow ups