widelands-dev team mailing list archive
-
widelands-dev team
-
Mailing list archive
-
Message #05022
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