← Back to team overview

widelands-dev team mailing list archive

Re: [Merge] lp:~widelands-dev/widelands/render_queue into lp:widelands

 

Review: Approve

I agree that it is an improvement.

Since I'm still on he dead slow virtual machine, I will give my OK if somebody who can reproduce the fuzzy roads does some testing to confirm that the problem is fixed on their machine - I expect that the problem will have been the same one.

Note to the tester: please check as well if https://bugs.launchpad.net/widelands/+bug/1519361 is fixed. We also shouldn't forget to double-check the minimap, including colors and visibility for both players and observers.

Code LGTM, just smoe minor code style nits and ideas.


Diff comments:

> 
> === added file 'src/graphic/gl/coordinate_conversion.h'
> --- src/graphic/gl/coordinate_conversion.h	1970-01-01 00:00:00 +0000
> +++ src/graphic/gl/coordinate_conversion.h	2016-01-04 20:54:29 +0000
> @@ -0,0 +1,73 @@
> +/*
> + * Copyright (C) 2006-2015 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_GL_COORDINATE_CONVERSION_H
> +#define WL_GRAPHIC_GL_COORDINATE_CONVERSION_H
> +
> +#include "base/rect.h"
> +#include "graphic/gl/blit_data.h"
> +
> +// Converts the pixel (x, y) in a texture to a gl coordinate in [0, 1].
> +inline void pixel_to_gl_texture(const int width, const int height, float* x, float* y) {
> +	*x = (*x / width);
> +	*y = 1. - (*y / height);
> +}
> +
> +// Converts the given pixel into an OpenGl point in the renderbuffer.
> +inline void pixel_to_gl_renderbuffer(const int width, const int height, float* x, float* y) {
> +	*x = (*x / width) * 2. - 1.;
> +	*y = 1. - (*y / height) * 2.;
> +}
> +
> +// Converts 'rect' given on a screen of 'width' x 'height' pixels into a rect
> +// in opengl coordinates in a renderbuffer, i.e. in [-1, 1]. The edges The returned

"The edges The returned" is not part of a sentence.

> +// rectangle has positive width and height.
> +inline FloatRect
> +rect_to_gl_renderbuffer(const int width, const int height, const Rect& rect) {
> +	float left = rect.x;
> +	float top = rect.y;
> +	float right = rect.x + rect.w;
> +	float bottom = rect.y + rect.h;
> +	pixel_to_gl_renderbuffer(width, height, &left, &top);
> +	pixel_to_gl_renderbuffer(width, height, &right, &bottom);
> +	return FloatRect(left, bottom, right - left, top - bottom);
> +}
> +
> +// Converts 'rect' given on a texture of 'width' x 'height' pixels into a rect
> +// in opengl coordinates in a texture, i.e. in [0, 1]. Texture pixels are sampled in their center.
> +// The returned rectangle has positive width and height.
> +inline FloatRect
> +rect_to_gl_texture(const int width, const int height, const FloatRect& rect) {
> +	float left = rect.x;
> +	float top = rect.y;
> +	float right = rect.x + rect.w;
> +	float bottom = rect.y + rect.h;
> +	pixel_to_gl_texture(width, height, &left, &top);
> +	pixel_to_gl_texture(width, height, &right, &bottom);
> +	return FloatRect(left, bottom, right - left, top - bottom);
> +}
> +
> +// Convert 'blit_data' from pixel space into opengl space.
> +inline FloatRect to_gl_texture(const BlitData& blit_data) {
> +	return rect_to_gl_texture(
> +	   blit_data.parent_width, blit_data.parent_height,
> +	   FloatRect(blit_data.rect.x, blit_data.rect.y, blit_data.rect.w, blit_data.rect.h));
> +}
> +
> +#endif  // end of include guard: WL_GRAPHIC_GL_COORDINATE_CONVERSION_H
> 
> === modified file 'src/graphic/gl/dither_program.cc'
> --- src/graphic/gl/dither_program.cc	2015-06-07 14:52:11 +0000
> +++ src/graphic/gl/dither_program.cc	2016-01-04 20:54:29 +0000
> @@ -116,16 +126,16 @@
>  
>  	switch (order_index) {
>  	case 0:

Can we use an enum class to name these, so they have some semantics?

> +		back.dither_texture_x = 1.;
> +		back.dither_texture_y = 1.;
> +		break;
> +	case 1:
>  		back.dither_texture_x = 0.;
> -		back.dither_texture_y = 0.;
> -		break;
> -	case 1:
> -		back.dither_texture_x = 1.;
> -		back.dither_texture_y = 0.;
> +		back.dither_texture_y = 1.;
>  		break;
>  	case 2:
>  		back.dither_texture_x = 0.5;
> -		back.dither_texture_y = 1.;
> +		back.dither_texture_y = 0.;
>  		break;
>  	default:
>  		throw wexception("Never here.");
> 
> === modified file 'src/graphic/gl/utils.h'
> --- src/graphic/gl/utils.h	2014-11-08 13:59:33 +0000
> +++ src/graphic/gl/utils.h	2016-01-04 20:54:29 +0000
> @@ -59,22 +59,54 @@
>  };
>  
>  // Thin wrapper around a OpenGL buffer object to ensure proper cleanup. Throws
> -// on all errors.
> -class Buffer {
> +// on all errors. Also grows the server memory only when needed.
> +template<typename T>
> +class NewBuffer {

Can this be renamed back to "Buffer", since I expect that you tracked down all occurrences now?

>  public:
> -	Buffer();
> -	~Buffer();
> -
> -	GLuint object() const {
> -		return buffer_object_;
> +	NewBuffer() : buffer_size_(0) {
> +		glGenBuffers(1, &object_);
> +		if (!object_) {
> +			throw wexception("Could not create GL program.");
> +		}
> +	}
> +
> +	~NewBuffer() {
> +		if (object_) {
> +			glDeleteBuffers(1, &object_);
> +		}
> +	}
> +
> +	// Calls glBindBuffer on the underlying buffer data.
> +	void bind() const {
> +		glBindBuffer(GL_ARRAY_BUFFER, object_);
> +	}
> +
> +
> +	// Copies 'elements' into the buffer. If the buffer is too small to hold the
> +	// data, it is reallocated. Does not check if the buffer is already bound.
> +	void update(const std::vector<T>& items) {
> +		if (buffer_size_ < items.size()) {
> +			glBufferData(GL_ARRAY_BUFFER, items.size() * sizeof(T), items.data(), GL_DYNAMIC_DRAW);
> +			buffer_size_ = items.size();
> +		} else {
> +			glBufferSubData(GL_ARRAY_BUFFER, 0, items.size() * sizeof(T), items.data());
> +		}
>  	}
>  
>  private:
> -	const GLuint buffer_object_;
> +	GLuint object_;
> +	size_t buffer_size_;  // In number of elements.
>  
> -	DISALLOW_COPY_AND_ASSIGN(Buffer);
> +	DISALLOW_COPY_AND_ASSIGN(NewBuffer);
>  };
>  
> +// Calls glVertexAttribPointer.
> +void vertex_attrib_pointer(int vertex_index, int num_items, int stride, int offset);
> +
> +// Swap order of rows in m_pixels, to compensate for the upside-down nature of the
> +// OpenGL coordinate system.
> +void swap_rows(int width, int height, int pitch, int bpp, uint8_t* pixels);
> +
>  }  // namespace Gl
>  
>  /**
> 
> === modified file 'src/graphic/minimap_renderer.cc'
> --- src/graphic/minimap_renderer.cc	2014-12-07 21:34:11 +0000
> +++ src/graphic/minimap_renderer.cc	2016-01-04 20:54:29 +0000
> @@ -168,90 +147,64 @@
>  	const int32_t mapwidth = egbase.get_map().get_width();
>  	const int32_t mapheight = map.get_height();
>  
> -	Point ptopleft; // top left point of the current display frame
> +	Point ptopleft;  // top left point of the current display frame
>  	ptopleft.x = viewpoint.x + mapwidth / 2 - xsize;
> -	if (ptopleft.x < 0) ptopleft.x += mapwidth;
> +	if (ptopleft.x < 0)

How about:

    if (ptopleft.x < 0) {
        ptopleft.x += mapwidth;
    }

etc.

> +		ptopleft.x += mapwidth;
>  	ptopleft.y = viewpoint.y + mapheight / 2 - ysize;
> -	if (ptopleft.y < 0) ptopleft.y += mapheight;
> +	if (ptopleft.y < 0)
> +		ptopleft.y += mapheight;
>  
> -	Point pbottomright; // bottom right point of the current display frame
> +	Point pbottomright;  // bottom right point of the current display frame
>  	pbottomright.x = viewpoint.x + mapwidth / 2 + xsize;
> -	if (pbottomright.x >= mapwidth) pbottomright.x -= mapwidth;
> +	if (pbottomright.x >= mapwidth)
> +		pbottomright.x -= mapwidth;
>  	pbottomright.y = viewpoint.y + mapheight / 2 + ysize;
> -	if (pbottomright.y >= mapheight) pbottomright.y -= mapheight;
> +	if (pbottomright.y >= mapheight)
> +		pbottomright.y -= mapheight;
>  
>  	uint32_t modx = pbottomright.x % 2;
>  	uint32_t mody = pbottomright.y % 2;
>  
> -	if (!player || player->see_all()) {
> -			for (uint32_t y = 0; y < surface_h; ++y) {
> -			uint8_t * pix = pixels + y * pitch;
> -			Widelands::FCoords f
> -				(Widelands::Coords
> -					(viewpoint.x, viewpoint.y + (layers & MiniMapLayer::Zoom2 ? y / 2 : y)));
> -			map.normalize_coords(f);
> -			f.field = &map[f];
> -			Widelands::MapIndex i = Widelands::Map::get_index(f, mapwidth);
> -			for (uint32_t x = 0; x < surface_w; ++x, pix += sizeof(uint32_t)) {
> -				if (x % 2 || !(layers & MiniMapLayer::Zoom2))
> -					move_r(mapwidth, f, i);
> -
> -				if ((layers & MiniMapLayer::ViewWindow) &&
> -				    is_minimap_frameborder(
> -				       f, ptopleft, pbottomright, mapwidth, mapheight, modx, mody)) {
> -					*reinterpret_cast<uint32_t *>(pix) = static_cast<uint32_t>
> -						(SDL_MapRGB(&const_cast<SDL_PixelFormat &>(format), 255, 0, 0));
> -				} else {
> -					*reinterpret_cast<uint32_t *>(pix) = static_cast<uint32_t>
> -						(calc_minimap_color
> -							(format, egbase, f, layers, f.field->get_owned_by(), true));
> +	for (uint32_t y = 0; y < surface_h; ++y) {
> +		Widelands::FCoords f(
> +		   Widelands::Coords(viewpoint.x, viewpoint.y + (layers & MiniMapLayer::Zoom2 ? y / 2 : y)));
> +		map.normalize_coords(f);
> +		f.field = &map[f];
> +		Widelands::MapIndex i = Widelands::Map::get_index(f, mapwidth);
> +		for (uint32_t x = 0; x < surface_w; ++x) {
> +			if (x % 2 || !(layers & MiniMapLayer::Zoom2))
> +				move_r(mapwidth, f, i);
> +
> +			RGBColor pixel_color;
> +			if ((layers & MiniMapLayer::ViewWindow) &&
> +			    is_minimap_frameborder(f, ptopleft, pbottomright, mapwidth, mapheight, modx, mody)) {
> +				pixel_color = RGBColor(255, 0, 0);
> +			} else {
> +				uint16_t vision =
> +				   0;  // See Player::Field::Vision: 1 if seen once, > 1 if seen right now.
> +				Widelands::PlayerNumber owner = 0;
> +				if (player == nullptr || player->see_all()) {
> +					vision = 2;  // Seen right now.
> +					owner = f.field->get_owned_by();
> +				} else if (player != nullptr) {
> +					const auto& field = player->fields()[i];
> +					vision = field.vision;
> +					owner = field.owner;
> +				}
> +
> +				if (vision > 0) {
> +					pixel_color = calc_minimap_color(egbase, f, layers, owner, vision > 1);
>  				}
>  			}
> -		}
> -	} else {
> -		Widelands::Player::Field const * const player_fields = player->fields();
> -		for (uint32_t y = 0; y < surface_h; ++y) {
> -			uint8_t * pix = pixels + y * pitch;
> -			Widelands::FCoords f
> -				(Widelands::Coords
> -			 		(viewpoint.x, viewpoint.y +
> -			 		 (layers & MiniMapLayer::Zoom2 ? y / 2 : y)));
> -			map.normalize_coords(f);
> -			f.field = &map[f];
> -			Widelands::MapIndex i = Widelands::Map::get_index(f, mapwidth);
> -			for (uint32_t x = 0; x < surface_w; ++x, pix += sizeof(uint32_t)) {
> -				if (x % 2 || !(layers & MiniMapLayer::Zoom2))
> -					move_r(mapwidth, f, i);
> -
> -				if ((layers & MiniMapLayer::ViewWindow) &&
> -				    is_minimap_frameborder(
> -				       f, ptopleft, pbottomright, mapwidth, mapheight, modx, mody)) {
> -					*reinterpret_cast<uint32_t *>(pix) = static_cast<uint32_t>
> -						(SDL_MapRGB
> -							(&const_cast<SDL_PixelFormat &>(format), 255, 0, 0));
> -				} else {
> -					const Widelands::Player::Field & player_field = player_fields[i];
> -					Widelands::Vision const vision = player_field.vision;
> -
> -					*reinterpret_cast<uint32_t *>(pix) =
> -						static_cast<uint32_t>
> -						(vision ?
> -						 calc_minimap_color
> -						 	(format,
> -						 	 egbase,
> -						 	 f,
> -						 	 layers,
> -						 	 player_field.owner,
> -						 	 1 < vision)
> -						 :
> -						 SDL_MapRGB(&const_cast<SDL_PixelFormat &>(format), 0, 0, 0));
> -				}
> +
> +			if (pixel_color.r != 0 || pixel_color.g != 0 || pixel_color.b != 0) {

How about:

  if ((pixel_color.r + pixel_color.g + pixel_color.b) > 0) {

Would this be more readable?

> +				texture->set_pixel(x, y, pixel_color);
>  			}
>  		}
>  	}
>  }
>  
> -
>  }  // namespace
>  
>  std::unique_ptr<Texture> draw_minimap(const EditorGameBase& egbase,
> 
> === added file 'src/graphic/render_queue.h'
> --- src/graphic/render_queue.h	1970-01-01 00:00:00 +0000
> +++ src/graphic/render_queue.h	2016-01-04 20:54:29 +0000
> @@ -0,0 +1,197 @@
> +/*
> + * Copyright (C) 2006-2014 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_RENDER_QUEUE_H
> +#define WL_GRAPHIC_RENDER_QUEUE_H
> +
> +#include <memory>
> +#include <vector>
> +
> +#include <stdint.h>
> +
> +#include "base/macros.h"
> +#include "base/rect.h"
> +#include "graphic/blend_mode.h"
> +#include "graphic/color.h"
> +#include "graphic/gl/fields_to_draw.h"
> +#include "logic/description_maintainer.h"
> +#include "logic/world/terrain_description.h"
> +
> +class DitherProgram;
> +class RoadProgram;
> +class TerrainProgram;
> +
> +// The RenderQueue is a singleton implementing the concept of deferred
> +// rendering: Every rendering call that pretends to draw onto the screen will
> +// instead enqueue an item into the RenderQueue. The Graphic::refresh() will
> +// then setup OpenGL to render onto the screen and then call
> +// RenderQueue::draw() which will execute all the draw calls.
> +//
> +// The advantage of this design is that render calls can be reordered and
> +// batched up to avoid OpenGL state changes as much as possible. This can
> +// reduce the amount of OpenGL calls done in the system per frame by an order
> +// of magnitude if assets are properly batched up into texture atlases.
> +//
> +// Rendering is simple: first everything fully opaque is rendered front to back
> +// (so that no pixel is drawn twice). This allows for maximum program batching,
> +// as for example all opaque rectangles can be rendered in one draw call,
> +// ignoring z-value.
> +//
> +// In the second step, all drawing calls with (partially) transparent pixels
> +// are done. This has to be done strictly in z ordering (back to front), so
> +// that transparency works correctly. But common operations can still be
> +// batched - for example the blitting of houses could all be done with the same
> +// z value and using a common texture atlas. Then they could be drawn in one
> +// woosh.
> +//
> +// Non overlapping rectangles can be drawn in parallel, ignoring z-order. I
> +// experimented with a linear algorithm to find all overlapping rectangle
> +// pairs (see bzr history), but it did not buy the performance I was hoping it
> +// would. So I abandoned this idea again.
> +//
> +// Note: all draw calls that target a Texture are not going to the RenderQueue,
> +// but are still immediately executed. The RenderQueue is only used for
> +// rendering onto the screen.
> +//
> +// TODO(sirver): we could (even) better performance by being z-layer aware
> +// while drawing. For example the UI could draw non-overlapping windows and
> +// sibling children with the same z-value for better batching. Also for example
> +// build-help symbols, buildings, and flags could all be drawn with the same
> +// z-layer for better batching up. This would also get rid of the z-layer
> +// issues we are having.
> +class RenderQueue {
> +public:
> +	enum Program {
> +		TERRAIN_BASE,

Code style: kTerrainBase etc.

> +		TERRAIN_DITHER,
> +		TERRAIN_ROAD,
> +		BLIT,
> +		BLIT_MONOCHROME,
> +		BLIT_BLENDED,
> +		RECT,
> +		LINE,
> +		HIGHEST_PROGRAM_ID,
> +	};
> +
> +	struct VanillaBlitArguments {
> +		BlitData texture;
> +		float opacity;
> +	};
> +
> +	struct MonochromeBlitArguments {
> +		BlitData texture;
> +		RGBAColor blend;
> +	};
> +
> +	struct BlendedBlitArguments {
> +		BlitData texture;
> +		BlitData mask;
> +		RGBAColor blend;
> +	};
> +
> +	struct RectArguments {
> +		RGBAColor color;
> +	};
> +
> +	struct LineArguments {
> +		RGBColor color;
> +		uint8_t line_width;
> +	};
> +
> +	struct TerrainArguments {
> +		TerrainArguments() {}
> +
> +		int gametime;
> +		int renderbuffer_width;
> +		int renderbuffer_height;
> +		const DescriptionMaintainer<Widelands::TerrainDescription>* terrains;
> +		FieldsToDraw* fields_to_draw;
> +	};
> +
> +	// 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;
> +		}
> +
> +		// The program that will be used to draw this item. Also defines which
> +		// union type is filled in.
> +		int program_id;
> +
> +		// 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
> +		// options. After ordering the queue by this, it defines the batching.
> +		uint64_t key;
> +
> +		// If this is opaque or, if not, which blend_mode to use.
> +		BlendMode blend_mode;
> +
> +		union {
> +			VanillaBlitArguments vanilla_blit_arguments;
> +			MonochromeBlitArguments monochrome_blit_arguments;
> +			BlendedBlitArguments blended_blit_arguments;
> +			TerrainArguments terrain_arguments;
> +			RectArguments rect_arguments;
> +			LineArguments line_arguments;
> +		};
> +	};
> +
> +	static RenderQueue& instance();
> +
> +	// Enqueues 'item' in the queue with a higher 'z' value than the last enqueued item.
> +	void enqueue(const Item& item);
> +
> +	// Draws all items in the queue in an optimal ordering and as much batching
> +	// as possible. This will draw one complete frame onto the screen and this
> +	// function is the only one that actually triggers draws to the screen
> +	// directly.
> +	void draw(int screen_width, int screen_height);
> +
> +private:
> +	RenderQueue();
> +
> +	void draw_items(const std::vector<Item>& items);
> +
> +	// The z value that should be used for the next draw, so that it is on top
> +	// of everything before.
> +	int next_z_;
> +
> +	std::unique_ptr<TerrainProgram> terrain_program_;
> +	std::unique_ptr<DitherProgram> dither_program_;
> +	std::unique_ptr<RoadProgram> road_program_;
> +
> +	std::vector<Item> blended_items_;
> +	std::vector<Item> opaque_items_;
> +
> +	DISALLOW_COPY_AND_ASSIGN(RenderQueue);
> +};
> +
> +
> +#endif  // end of include guard: WL_GRAPHIC_RENDER_QUEUE_H


-- 
https://code.launchpad.net/~widelands-dev/widelands/render_queue/+merge/250524
Your team Widelands Developers is subscribed to branch lp:~widelands-dev/widelands/render_queue.


References