widelands-dev team mailing list archive
-
widelands-dev team
-
Mailing list archive
-
Message #09287
[Merge] lp:~nha/widelands/graphics into lp:widelands
Nicolai Hähnle has proposed merging lp:~nha/widelands/graphics into lp:widelands.
Requested reviews:
Widelands Developers (widelands-dev)
For more details, see:
https://code.launchpad.net/~nha/widelands/graphics/+merge/314279
A relatively large change to the rendering system that I've been working on occasionally over the last few weeks. I guess this is the best way to get some feedback.
The goal of the changes is to improve rendering performance by using more advanced OpenGL features. Since those are not available everywhere, the blitting and terrain rendering paths are split into "GL2" (existing) and "GL4" (new) rendering paths. The name "GL4" is not to be interpreted too literally: whether the new path is used is simply keyed off some extensions, mostly ARB_shader_storage_buffer_object, which were added with OpenGL 4.x.
Generally, the idea of the GL4 rendering paths is to do as little work as possible on the CPU, and to figure out more information on the GPU. So:
BlitProgram: Rather than set up all vertices on the CPU like the GL2 path, the GL4 path populates an SSBO with rectangle information; the vertex shader extracts the information for the required rectangle based on the vertex ID.
Terrain rendering: Map data like terrain type, height, and brightness is stored in textures. Rendering happens in instanced patches (small rectangular chunks of the map), so that basically no vertex data has to be uploaded per frame. The vertex shader samples the textures to compute vertex positions, terrain texture coordinates, and brightness. Terrain base rendering and dithering is done in a single pass in the fragment shader. Player-based brightness data has to be re-uploaded each frame, but apart from that, the CPU has to do almost no work.
Minimap rendering: The minimap is almost entirely rendered in a fragment shader on the GPU. This requires an additional texture containing information about field ownership and the presence of flags/roads/buildings. This texture must be updated each frame. This requires less CPU work than computing the full minimap on the CPU, but is still quite expensive, so the texture is updated in a "rolling" fashion: each frame, a strip of width 8 across the whole map is updated.
Whether the new rendering path is used or not is based simply on the existence of the required extensions, although there's an override: use --disable_gl4=true to use the old rendering path even when the new one would be supported.
--
Your team Widelands Developers is requested to review the proposed merge of lp:~nha/widelands/graphics into lp:widelands.
=== modified file 'cmake/codecheck/rules/correct_include_order'
--- cmake/codecheck/rules/correct_include_order 2015-04-04 15:03:14 +0000
+++ cmake/codecheck/rules/correct_include_order 2017-01-07 12:36:16 +0000
@@ -45,9 +45,10 @@
delims = set(entry[1] for entry in block)
if len(delims) != 1:
errors.append((fn, block[0][0], """Use either '"' or '<' for all includes in a block."""))
- if sorted(includes) != includes:
- errors.append((fn, block[0][0], "Include block is not sorted alphabetically."))
- return errors
+ for include_sorted, include_actual in zip(sorted(includes), includes):
+ if include_sorted != include_actual:
+ errors.append((fn, block[0][0], "Include block is not sorted alphabetically: '%s' must be included before '%s'" % (include_sorted, include_actual)))
+ return errors
if ".cc" in fn:
base_file = os.path.basename(fn)[:-3]
=== modified file 'data/shaders/blit.fp'
--- data/shaders/blit.fp 2016-02-01 10:24:34 +0000
+++ data/shaders/blit.fp 2017-01-07 12:36:16 +0000
@@ -3,13 +3,12 @@
uniform sampler2D u_texture;
uniform sampler2D u_mask;
-varying vec2 out_mask_texture_coordinate;
-varying vec2 out_texture_coordinate;
+varying vec4 out_texture_coordinates;
varying vec4 out_blend;
varying float out_program_flavor;
void main() {
- vec4 texture_color = texture2D(u_texture, out_texture_coordinate);
+ vec4 texture_color = texture2D(u_texture, out_texture_coordinates.xy);
// See http://en.wikipedia.org/wiki/YUV.
float luminance = dot(vec3(0.299, 0.587, 0.114), texture_color.rgb);
@@ -19,7 +18,7 @@
} else if (out_program_flavor == 1.) {
gl_FragColor = vec4(vec3(luminance) * out_blend.rgb, out_blend.a * texture_color.a);
} else {
- vec4 mask_color = texture2D(u_mask, out_mask_texture_coordinate);
+ vec4 mask_color = texture2D(u_mask, out_texture_coordinates.zw);
float blend_influence = mask_color.r * mask_color.a;
gl_FragColor = vec4(
mix(texture_color.rgb, out_blend.rgb * luminance, blend_influence),
=== modified file 'data/shaders/blit.vp'
--- data/shaders/blit.vp 2016-02-01 10:24:34 +0000
+++ data/shaders/blit.vp 2017-01-07 12:36:16 +0000
@@ -7,14 +7,15 @@
attribute vec4 attr_blend;
attribute float attr_program_flavor;
-varying vec2 out_mask_texture_coordinate;
-varying vec2 out_texture_coordinate;
+// xy = texture_coordinate; zw = mask_texture_coordinate
+// Sharing a single varying in this way can save hardware resources.
+varying vec4 out_texture_coordinates;
varying vec4 out_blend;
varying float out_program_flavor;
void main() {
- out_mask_texture_coordinate = attr_mask_texture_position;
- out_texture_coordinate = attr_texture_position;
+ out_texture_coordinates.zw = attr_mask_texture_position;
+ out_texture_coordinates.xy = attr_texture_position;
out_blend = attr_blend;
out_program_flavor = attr_program_flavor;
gl_Position = vec4(attr_position, 1.);
=== added file 'data/shaders/blit_gl4.vp'
--- data/shaders/blit_gl4.vp 1970-01-01 00:00:00 +0000
+++ data/shaders/blit_gl4.vp 2017-01-07 12:36:16 +0000
@@ -0,0 +1,70 @@
+#version 130
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_ARB_shader_storage_buffer_object : enable
+#extension GL_ARB_uniform_buffer_object : enable
+
+// Varyings.
+out vec4 out_texture_coordinates;
+out vec4 out_blend;
+out float out_program_flavor;
+
+struct Rect {
+ float dst_x, dst_y, dst_width, dst_height;
+ uint src_x, src_y, src_width, src_height;
+ uint src_parent_width, src_parent_height;
+ uint mask_x, mask_y, mask_width, mask_height;
+ uint mask_parent_width, mask_parent_height;
+ uint blend_r, blend_g, blend_b, blend_a;
+ float program_flavor, z;
+};
+
+layout(std140, binding=0) buffer ssbo_rects {
+ Rect rects[];
+};
+
+void main() {
+ // Determine rect and vertex relative to rect
+ uint rect_idx = uint(gl_VertexID) >> 2;
+ uint vertex_idx = uint(gl_VertexID) & 3u;
+ Rect r = rects[rect_idx];
+
+ out_program_flavor = r.program_flavor;
+
+ // Position
+ gl_Position = vec4(r.dst_x, r.dst_y, r.z, 1.);
+ if ((vertex_idx & 1u) != 0u)
+ gl_Position.x += r.dst_width;
+ if ((vertex_idx & 2u) != 0u)
+ gl_Position.y += r.dst_height;
+
+ // Texture coordinate
+ uint tx = r.src_x;
+ uint ty = r.src_y;
+ if ((vertex_idx & 1u) != 0u)
+ tx += r.src_width;
+ if ((vertex_idx & 2u) == 0u)
+ ty += r.src_height;
+ out_texture_coordinates.x = tx * (1. / r.src_parent_width);
+ out_texture_coordinates.y = 1.0 - ty * (1. / r.src_parent_height);
+
+ // Blending
+ out_blend.a = r.blend_a * (1. / 255.);
+
+// if (out_program_flavor >= 1.) {
+ out_blend.r = r.blend_r * (1. / 255.);
+ out_blend.g = r.blend_g * (1. / 255.);
+ out_blend.b = r.blend_b * (1. / 255.);
+
+// if (out_program_flavor >= 2.) {
+ // Mask texture coordinate
+ tx = r.mask_x;
+ ty = r.mask_y;
+ if ((vertex_idx & 1u) != 0u)
+ tx += r.mask_width;
+ if ((vertex_idx & 2u) == 0u)
+ ty += r.mask_height;
+ out_texture_coordinates.z = tx * (1. / r.mask_parent_width);
+ out_texture_coordinates.w = 1.0 - ty * (1. / r.mask_parent_height);
+// }
+// }
+}
=== added file 'data/shaders/minimap_gl4.fp'
--- data/shaders/minimap_gl4.fp 1970-01-01 00:00:00 +0000
+++ data/shaders/minimap_gl4.fp 2017-01-07 12:36:16 +0000
@@ -0,0 +1,108 @@
+#version 130
+#extension GL_ARB_uniform_buffer_object: enable
+
+// Varyings
+in vec2 var_field;
+
+// Uniforms
+uniform bool u_layer_terrain;
+uniform bool u_layer_owner;
+uniform uint u_layer_details;
+
+uniform vec2 u_frame_topleft;
+uniform vec2 u_frame_bottomright;
+
+// Textures (map data).
+uniform usampler2D u_terrain_base;
+uniform sampler2D u_player_brightness;
+uniform usampler2D u_minimap_extra;
+uniform sampler2D u_terrain_color;
+uniform sampler2D u_player_color;
+
+float calc_node_brightness(uint node_ubrightness) {
+ // Brightness is really an 8-bit signed value, but it's stored in an
+ // GL_RGBA8UI texture, so here we use signed (arithmetic) shifts to do
+ // the conversion.
+ int node_brightness = int(node_ubrightness << 24) >> 24;
+ float brightness = 144. / 255. + node_brightness * (1. / 255.);
+ brightness = min(1., brightness * (255. / 160.));
+ return brightness;
+}
+
+// Return true if a and b are closer than threshold modulo 1.
+bool wrap_close(float a, float b, float threshold) {
+ float dist = a - b;
+ dist -= floor(dist + 0.5);
+ return abs(dist) < threshold;
+}
+
+void main() {
+ float player_brightness = texture(u_player_brightness, var_field).r;
+
+ // Determine whether we're on the frame
+ bool on_frame = false;
+ float dfdx = abs(dFdx(var_field.x)) * 0.5;
+ float dfdy = abs(dFdy(var_field.y)) * 0.5;
+ float low, high, pix, width;
+
+ if (wrap_close(var_field.x, u_frame_topleft.x, dfdx) ||
+ wrap_close(var_field.x, u_frame_bottomright.x, dfdx)) {
+ on_frame = true;
+ low = u_frame_topleft.y;
+ high = u_frame_bottomright.y;
+ pix = var_field.y;
+ width = dfdy;
+ } else if (wrap_close(var_field.y, u_frame_topleft.y, dfdy) ||
+ wrap_close(var_field.y, u_frame_bottomright.y, dfdy)) {
+ on_frame = true;
+ low = u_frame_topleft.x;
+ high = u_frame_bottomright.x;
+ pix = var_field.x;
+ width = dfdx;
+ }
+
+ if (on_frame) {
+ pix -= floor(pix - low); // Normalize to range [low, low + 1)
+ if (pix <= high &&
+ (int((pix - low) / (2 * width)) & 1) == 0) {
+ gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+ return;
+ }
+ }
+
+ // Determine minimap color
+ vec3 color = vec3(0.0, 0.0, 0.0);
+ if (player_brightness > 0.0) {
+ if (u_layer_terrain) {
+ uvec4 node = texture(u_terrain_base, var_field);
+ float brightness = calc_node_brightness(node.w);
+
+ color = texelFetch(u_terrain_color, ivec2(node.y, 0), 0).rgb;
+ color *= brightness;
+ }
+
+ if (u_layer_owner || u_layer_details != 0u) {
+ uint extra = texture(u_minimap_extra, var_field).r;
+
+ if (u_layer_owner) {
+ uint owner = extra & 0x3fu;
+ if (owner > 0u) {
+ vec3 player_color = texelFetch(u_player_color, ivec2(owner - 1u, 0), 0).rgb;
+ color = mix(color, player_color, 0.5);
+ }
+ }
+
+ uint detail = extra >> 6u;
+ if ((u_layer_details & 1u) != 0u && detail == 1u) {
+ // Road
+ color = mix(color, vec3(1, 1, 1), 0.5);
+ } else if (detail != 0u && (u_layer_details & (1u << (detail - 1u))) != 0u) {
+ // Flag or building
+ color = vec3(1, 1, 1);
+ }
+ }
+ }
+
+ gl_FragColor.rgb = color;
+ gl_FragColor.w = 1.0;
+}
=== added file 'data/shaders/minimap_gl4.vp'
--- data/shaders/minimap_gl4.vp 1970-01-01 00:00:00 +0000
+++ data/shaders/minimap_gl4.vp 2017-01-07 12:36:16 +0000
@@ -0,0 +1,11 @@
+#version 130
+
+in vec3 in_position;
+in vec2 in_field; // Fractional field
+
+out vec2 var_field;
+
+void main() {
+ gl_Position = vec4(in_position, 1.);
+ var_field = in_field;
+}
=== added file 'data/shaders/road_gl4.vp'
--- data/shaders/road_gl4.vp 1970-01-01 00:00:00 +0000
+++ data/shaders/road_gl4.vp 2017-01-07 12:36:16 +0000
@@ -0,0 +1,89 @@
+#version 130
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_ARB_shader_storage_buffer_object : enable
+#extension GL_ARB_uniform_buffer_object : enable
+
+// From terrain_common_gl4:
+void init_common();
+uvec4 get_field_base(ivec2 coord);
+float calc_node_brightness(uint node_ubrightness);
+float calc_brightness(ivec2 coord, uint node_ubrightness);
+void calc_basepix(ivec2 coord, out vec2 basepix, out float heightpix, out uint node_brightness);
+void calc_pix(ivec2 coord, out vec2 pix, out uint node_brightness);
+
+// Attributes.
+uniform vec2 u_position_scale;
+uniform vec2 u_position_offset;
+uniform float u_z_value;
+
+// Road data.
+struct Road {
+ ivec2 start;
+ uint direction; // WALK_E = 2, WALK_SE = 3, WALK_SW = 4
+ uint texture;
+};
+
+layout(std140, binding=0) buffer ssbo_roads {
+ Road u_roads[];
+};
+
+// Road texture information.
+struct Texture {
+ float x, y, w, h;
+};
+
+layout(std140) uniform block_textures {
+ Texture u_textures[128];
+};
+
+// Outputs.
+varying vec2 out_texture_position;
+varying float out_brightness;
+
+// Constants
+const float kRoadThicknessInPixels = 5.;
+const float kRoadElongationFraction = .1;
+
+void main() {
+ init_common();
+
+ Road road = u_roads[gl_VertexID >> 2];
+ bool is_end = (gl_VertexID & 2) != 0;
+ bool is_right = (gl_VertexID & 1) != 0;
+
+ // Calculate end coordinate.
+ ivec2 end = road.start;
+ end.x += 3 - int(road.direction);
+ if (road.direction >= 3u) {
+ end.y += 1;
+ end.x += (road.start.y & 1) != 0 ? 1 : 0;
+ }
+
+ // Map coordinates
+ vec2 start_pix, end_pix;
+ uint start_node_brightness, end_node_brightness;
+ calc_pix(road.start, start_pix, start_node_brightness);
+ calc_pix(end, end_pix, end_node_brightness);
+
+ vec2 delta_pix = end_pix - start_pix;
+ vec2 road_overshoot = delta_pix * kRoadElongationFraction;
+ vec2 road_thickness = vec2(-delta_pix.y, delta_pix.x) * (kRoadThicknessInPixels / length(delta_pix));
+
+ vec2 pix =
+ (is_end ? end_pix + road_overshoot : start_pix - road_overshoot) +
+ (is_right ? -road_thickness : road_thickness);
+ gl_Position = vec4(pix * u_position_scale + u_position_offset, u_z_value, 1.);
+
+ // Brightness
+ out_brightness = calc_brightness(
+ is_end ? end : road.start,
+ is_end ? end_node_brightness : start_node_brightness);
+
+ // Texture coordinates.
+ Texture tex = u_textures[road.texture];
+ out_texture_position = vec2(tex.x, tex.y);
+ if (is_end)
+ out_texture_position.x += tex.w;
+ if (!is_right)
+ out_texture_position.y += tex.h;
+}
=== added file 'data/shaders/terrain_common_gl4.vp'
--- data/shaders/terrain_common_gl4.vp 1970-01-01 00:00:00 +0000
+++ data/shaders/terrain_common_gl4.vp 2017-01-07 12:36:16 +0000
@@ -0,0 +1,59 @@
+// Helper functions that are shared by the vertex shaders for terrain and
+// roads.
+#version 130
+#extension GL_ARB_uniform_buffer_object: enable
+
+// Textures (map data).
+uniform usampler2D u_terrain_base;
+uniform sampler2D u_player_brightness;
+
+// Constants
+const float kTriangleWidth = 64;
+const float kTriangleHeight = 32;
+const float kHeightFactor = 5;
+
+vec2 map_size;
+vec2 map_inv_size;
+
+void init_common() {
+ map_size = vec2(textureSize(u_terrain_base, 0));
+ map_inv_size = 1. / map_size;
+}
+
+uvec4 get_field_base(ivec2 coord) {
+ return texture(u_terrain_base, (coord + 0.5) * map_inv_size);
+}
+
+float calc_node_brightness(uint node_ubrightness) {
+ // Brightness is really an 8-bit signed value, but it's stored in an
+ // GL_RGBA8UI texture, so here we use signed (arithmetic) shifts to do
+ // the conversion.
+ int node_brightness = int(node_ubrightness << 24) >> 24;
+ float brightness = 144. / 255. + node_brightness * (1. / 255.);
+ brightness = min(1., brightness * (255. / 160.));
+ return brightness;
+}
+
+float calc_brightness(ivec2 coord, uint node_ubrightness) {
+ float brightness = calc_node_brightness(node_ubrightness);
+
+ brightness *= texture(u_player_brightness, (coord + 0.5) * map_inv_size).r;
+
+ return brightness;
+}
+
+void calc_basepix(ivec2 coord, out vec2 basepix, out float heightpix, out uint node_brightness) {
+ uvec4 field = get_field_base(coord);
+ basepix.x = coord.x * kTriangleWidth;
+ if ((coord.y & 1) != 0)
+ basepix.x += kTriangleWidth / 2;
+ basepix.y = coord.y * kTriangleHeight;
+ heightpix = field.z * kHeightFactor;
+ node_brightness = field.w;
+}
+
+void calc_pix(ivec2 coord, out vec2 pix, out uint node_brightness) {
+ float heightpix;
+ calc_basepix(coord, pix, heightpix, node_brightness);
+ pix.y -= heightpix;
+}
=== added file 'data/shaders/terrain_gl4.fp'
--- data/shaders/terrain_gl4.fp 1970-01-01 00:00:00 +0000
+++ data/shaders/terrain_gl4.fp 2017-01-07 12:36:16 +0000
@@ -0,0 +1,58 @@
+#version 130
+
+uniform sampler2D u_terrain_texture;
+uniform sampler2D u_dither_texture;
+
+uniform vec2 u_texture_dimensions;
+
+in vec4 var_texture;
+in vec4 var_dither[3];
+in float var_brightness;
+
+// TODO(sirver): This is a hack to make sure we are sampling inside of the
+// terrain texture. This is a common problem with OpenGL and texture atlases.
+#define MARGIN 1e-2
+
+vec3 sample_terrain(vec2 offset, vec2 texcoord) {
+ // The arbitrary multiplication by 0.99 makes sure that we never sample
+ // outside of the texture in the texture atlas - this means non-perfect
+ // pixel mapping of textures to the screen, but we are pretty meh about that
+ // here.
+ vec2 texture_fract = clamp(
+ fract(texcoord),
+ vec2(MARGIN, MARGIN),
+ vec2(1. - MARGIN, 1. - MARGIN));
+ return texture2D(u_terrain_texture, offset + u_texture_dimensions * texture_fract).rgb;
+}
+
+vec3 apply_dither(vec3 color, vec2 texcoord, vec4 dither) {
+ vec2 offset = dither.xy;
+ vec2 dither_tc = dither.zw;
+
+ // Cut-off value to avoid unnecessary texture samples. The cut-off value
+ // is chosen based on the dither mask, which happens to be 1 in 3/4 of the
+ // texture.
+ //
+ // Note that cuting off in this way would be slightly incorrect if mipmaps
+ // were used, because derivatives become undefined under non-uniform
+ // control flow.
+ if (dither_tc.y < 0.75)
+ return color;
+
+ float dither_factor = texture2D(u_dither_texture, dither_tc).a;
+ vec3 dither_color = sample_terrain(offset, texcoord);
+
+ // dither_factor is 0 when the other texture replace the base texture
+ // entirely, and 1 when the base texture is not replaced at all.
+ return mix(dither_color, color, dither_factor);
+}
+
+void main() {
+ vec2 texcoord = var_texture.zw;
+ vec3 clr = sample_terrain(var_texture.xy, texcoord);
+ clr = apply_dither(clr, texcoord, var_dither[0]);
+ clr = apply_dither(clr, texcoord, var_dither[1]);
+ clr = apply_dither(clr, texcoord, var_dither[2]);
+ clr *= var_brightness;
+ gl_FragColor = vec4(clr, 1.);
+}
=== added file 'data/shaders/terrain_gl4.vp'
--- data/shaders/terrain_gl4.vp 1970-01-01 00:00:00 +0000
+++ data/shaders/terrain_gl4.vp 2017-01-07 12:36:16 +0000
@@ -0,0 +1,157 @@
+#version 130
+#extension GL_ARB_uniform_buffer_object: enable
+
+// From terrain_common_gl4:
+void init_common();
+uvec4 get_field_base(ivec2 coord);
+float calc_brightness(ivec2 coord, uint node_ubrightness);
+void calc_basepix(ivec2 coord, out vec2 basepix, out float heightpix, out uint node_brightness);
+void calc_pix(ivec2 coord, out vec2 pix, out uint node_brightness);
+
+// Per-vertex Attributes.
+// X,Y: relative vertex map coordinate
+// Z,W: relative triangle map coordinate
+// low bit of Z: 0 for d-triangle, 1 for r-triangle
+// bits 1&2 of Z: vertex ID for dithering
+in ivec4 in_vertex_coordinate;
+
+// Per-instance/patch Attributes.
+in ivec2 in_patch_coordinate;
+
+// Primary Constants.
+uniform vec2 u_position_scale;
+uniform vec2 u_position_offset;
+uniform float u_z_value;
+
+// Terrain information (secondary constants in a uniform buffer).
+struct Terrain {
+ vec2 offset;
+ int dither_layer;
+};
+
+layout(std140) uniform block_terrains {
+ Terrain u_terrains[128];
+};
+
+// Varyings: Output of vertex shader.
+
+// X,Y: offset into texture atlas
+// Z,W: texture coordinate
+out vec4 var_texture;
+
+// X,Y: offset into texture atlas
+// Z,W: dither texture coordinate
+out vec4 var_dither[3];
+
+out float var_brightness;
+
+// Constants
+const float kTextureSideLength = 64;
+
+void main() {
+ init_common();
+
+ // Compute coordinate of field that owns the triangle to which this
+ // vertex belongs.
+ ivec2 field_coord;
+ field_coord.x = in_patch_coordinate.x + (in_vertex_coordinate.z >> 3);
+ field_coord.y = in_patch_coordinate.y + in_vertex_coordinate.w;
+
+ uvec4 field = get_field_base(field_coord);
+ bool is_down = (in_vertex_coordinate.z & 1) == 0;
+ uint dither_vid = uint((in_vertex_coordinate.z >> 1) & 3);
+ uint terrain = is_down ? field.y : field.x;
+
+ ivec2 node_coord = in_patch_coordinate.xy + in_vertex_coordinate.xy;
+ uint node_brightness;
+ float node_heightpix;
+ vec2 vertex_basepix;
+
+ calc_basepix(node_coord, vertex_basepix, node_heightpix, node_brightness);
+ // On-screen position
+ gl_Position.x = vertex_basepix.x;
+ gl_Position.y = vertex_basepix.y - node_heightpix;
+ gl_Position.xy = gl_Position.xy * u_position_scale + u_position_offset;
+ gl_Position.z = u_z_value;
+ gl_Position.w = 1.;
+
+ // Brightness
+ var_brightness = calc_brightness(node_coord, node_brightness);
+
+ // Texture coordinates
+ vec2 vertex_texcoord;
+ vertex_texcoord.x = vertex_basepix.x * (1. / kTextureSideLength);
+ vertex_texcoord.y = -vertex_basepix.y * (1. / kTextureSideLength);
+
+ // Base texture information
+ var_texture.xy = u_terrains[terrain].offset;
+ var_texture.zw = vertex_texcoord;
+
+ // Dithering. Vertices are labeled by their dither_vid. Note that the VIDs
+ // are different depending on whether the vertex is part of the r- or d-
+ // triangle.
+ //
+ // 2-1---d---0
+ // / \ /
+ // r r\d d
+ // / \ /
+ // 0---r--1-2
+ //
+ // Channel 0: opposite triangle of the same field
+ // Channel 1: x-major outside neighbor
+ // Channel 2: y-major outside neighbor
+ int dither_layer = u_terrains[terrain].dither_layer;
+ {
+ uint other_terrain = is_down ? field.x : field.y;
+
+ var_dither[0].zw = vec2(0.5, 0);
+
+ if (other_terrain != terrain) {
+ int other_dither_layer = u_terrains[other_terrain].dither_layer;
+
+ if (other_dither_layer > dither_layer) {
+ var_dither[0].xy = u_terrains[other_terrain].offset;
+ if (dither_vid != 0u)
+ var_dither[0].zw = vec2(2u - dither_vid, 1);
+ }
+ }
+ }
+ {
+ ivec2 other_coord = field_coord;
+ other_coord.x += is_down ? -1 : 1;
+ uvec4 other_field = get_field_base(other_coord);
+ uint other_terrain = is_down ? other_field.x : other_field.y;
+
+ var_dither[1].zw = vec2(0.5, 0);
+
+ if (other_terrain != terrain) {
+ int other_dither_layer = u_terrains[other_terrain].dither_layer;
+
+ if (other_dither_layer > dither_layer) {
+ var_dither[1].xy = u_terrains[other_terrain].offset;
+ if (dither_vid != 1u)
+ var_dither[1].zw = vec2(dither_vid / 2u, 1);
+ }
+ }
+ }
+ {
+ ivec2 other_coord = field_coord;
+ other_coord.y += is_down ? 1 : -1;
+ other_coord.x += is_down ? -1 : 0;
+ other_coord.x += ((field_coord.y & 1) != 0) ? 1 : 0;
+ uvec4 other_field = get_field_base(other_coord);
+ uint other_terrain = is_down ? other_field.x : other_field.y;
+
+ var_dither[2].zw = vec2(0.5, 0);
+
+ if (other_terrain != terrain) {
+ int other_dither_layer = u_terrains[other_terrain].dither_layer;
+
+ if (other_dither_layer > dither_layer) {
+ var_dither[2].xy = u_terrains[other_terrain].offset;
+ if (dither_vid != 2u)
+ var_dither[2].zw = vec2(1u - dither_vid, 1);
+ }
+ }
+ }
+}
=== modified file 'src/graphic/CMakeLists.txt'
--- src/graphic/CMakeLists.txt 2016-10-25 07:07:14 +0000
+++ src/graphic/CMakeLists.txt 2017-01-07 12:36:16 +0000
@@ -120,6 +120,8 @@
gl/road_program.h
gl/terrain_program.cc
gl/terrain_program.h
+ gl/terrain_program_gl4.cc
+ gl/terrain_program_gl4.h
gl/dither_program.cc
gl/dither_program.h
DEPENDS
@@ -195,6 +197,8 @@
SRCS
game_renderer.cc
game_renderer.h
+ game_renderer_gl4.cc
+ game_renderer_gl4.h
DEPENDS
base_geometry
base_macros
=== modified file 'src/graphic/game_renderer.cc'
--- src/graphic/game_renderer.cc 2016-11-17 18:33:09 +0000
+++ src/graphic/game_renderer.cc 2017-01-07 12:36:16 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010-2013 by the Widelands Development Team
+ * Copyright (C) 2010-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
@@ -21,7 +21,9 @@
#include <memory>
+#include "graphic/game_renderer_gl4.h"
#include "graphic/gl/coordinate_conversion.h"
+#include "graphic/gl/fields_to_draw.h"
#include "graphic/graphic.h"
#include "graphic/render_queue.h"
#include "graphic/rendertarget.h"
@@ -71,6 +73,23 @@
using namespace Widelands;
+class GameRendererGl2 : public GameRenderer {
+public:
+ // Draw the map for the given parameters (see rendermap). 'player'
+ // can be nullptr in which case the whole map is drawn.
+ void draw(const Widelands::EditorGameBase& egbase,
+ const Vector2f& view_offset,
+ const float scale,
+ const TextToDraw draw_text,
+ const Widelands::Player* player,
+ RenderTarget* dst) override;
+
+private:
+ // This is owned and handled by us, but handed to the RenderQueue, so we
+ // basically promise that this stays valid for one frame.
+ FieldsToDrawGl2 fields_to_draw_;
+};
+
// Returns the brightness value in [0, 1.] for 'fcoords' at 'gametime' for
// 'player' (which can be nullptr).
float field_brightness(const FCoords& fcoords,
@@ -98,7 +117,7 @@
}
void draw_objects_for_visible_field(const EditorGameBase& egbase,
- const FieldsToDraw::Field& field,
+ const FieldToDrawBase& field,
const float zoom,
const TextToDraw draw_text,
const Player* player,
@@ -128,7 +147,7 @@
}
}
-void draw_objets_for_formerly_visible_field(const FieldsToDraw::Field& field,
+void draw_objets_for_formerly_visible_field(const FieldToDrawBase& field,
const Player::Field& player_field,
const float zoom,
RenderTarget* dst) {
@@ -204,49 +223,51 @@
}
}
+} // namespace
+
// Draws the objects (animations & overlays).
-void draw_objects(const EditorGameBase& egbase,
- const float zoom,
- const FieldsToDraw& fields_to_draw,
- const Player* player,
- const TextToDraw draw_text,
- RenderTarget* dst) {
+void GameRenderer::draw_objects(const EditorGameBase& egbase,
+ const float scale,
+ const FieldsToDrawRefBase& fields_to_draw,
+ const Player* player,
+ const TextToDraw draw_text,
+ RenderTarget* dst) {
std::vector<FieldOverlayManager::OverlayInfo> overlay_info;
- for (size_t current_index = 0; current_index < fields_to_draw.size(); ++current_index) {
- const FieldsToDraw::Field& field = fields_to_draw.at(current_index);
- if (!field.all_neighbors_valid()) {
+ for (auto cursor = fields_to_draw.cursor(); cursor.valid(); cursor.next()) {
+ const FieldToDrawBase& field = cursor.field();
+ if (!cursor.all_neighbors_valid()) {
continue;
}
- const FieldsToDraw::Field& rn = fields_to_draw.at(field.rn_index);
- const FieldsToDraw::Field& bln = fields_to_draw.at(field.bln_index);
- const FieldsToDraw::Field& brn = fields_to_draw.at(field.brn_index);
+ const FieldToDrawBase& rn = cursor.rn();
+ const FieldToDrawBase& bln = cursor.bln();
+ const FieldToDrawBase& brn = cursor.brn();
if (field.is_border) {
assert(field.owner != nullptr);
uint32_t const anim_idx = field.owner->tribe().frontier_animation();
if (field.vision) {
dst->blit_animation(
- field.rendertarget_pixel, zoom, anim_idx, 0, field.owner->get_playercolor());
+ field.rendertarget_pixel, scale, anim_idx, 0, field.owner->get_playercolor());
}
for (const auto& nf : {rn, bln, brn}) {
if ((field.vision || nf.vision) && nf.is_border &&
(field.owner == nf.owner || nf.owner == nullptr)) {
- dst->blit_animation(middle(field.rendertarget_pixel, nf.rendertarget_pixel), zoom,
+ dst->blit_animation(middle(field.rendertarget_pixel, nf.rendertarget_pixel), scale,
anim_idx, 0, field.owner->get_playercolor());
}
}
}
if (1 < field.vision) { // Render stuff that belongs to the node.
- draw_objects_for_visible_field(egbase, field, zoom, draw_text, player, dst);
+ draw_objects_for_visible_field(egbase, field, scale, draw_text, player, dst);
} else if (field.vision == 1) {
// We never show census or statistics for objects in the fog.
assert(player != nullptr);
const Map& map = egbase.map();
const Player::Field& player_field =
player->fields()[map.get_index(field.fcoords, map.get_width())];
- draw_objets_for_formerly_visible_field(field, player_field, zoom, dst);
+ draw_objets_for_formerly_visible_field(field, player_field, scale, dst);
}
const FieldOverlayManager& overlay_manager = egbase.get_ibase()->field_overlay_manager();
@@ -255,8 +276,8 @@
overlay_manager.get_overlays(field.fcoords, &overlay_info);
for (const auto& overlay : overlay_info) {
dst->blitrect_scale(
- Rectf(field.rendertarget_pixel - overlay.hotspot.cast<float>() * zoom,
- overlay.pic->width() * zoom, overlay.pic->height() * zoom),
+ Rectf(field.rendertarget_pixel - overlay.hotspot.cast<float>() * scale,
+ overlay.pic->width() * scale, overlay.pic->height() * scale),
overlay.pic, Recti(0, 0, overlay.pic->width(), overlay.pic->height()), 1.f,
BlendMode::UseAlpha);
}
@@ -272,8 +293,8 @@
(field.rendertarget_pixel.y + rn.rendertarget_pixel.y + brn.rendertarget_pixel.y) /
3.f);
for (const auto& overlay : overlay_info) {
- dst->blitrect_scale(Rectf(tripos - overlay.hotspot.cast<float>() * zoom,
- overlay.pic->width() * zoom, overlay.pic->height() * zoom),
+ dst->blitrect_scale(Rectf(tripos - overlay.hotspot.cast<float>() * scale,
+ overlay.pic->width() * scale, overlay.pic->height() * scale),
overlay.pic,
Recti(0, 0, overlay.pic->width(), overlay.pic->height()), 1.f,
BlendMode::UseAlpha);
@@ -291,8 +312,8 @@
(field.rendertarget_pixel.y + bln.rendertarget_pixel.y + brn.rendertarget_pixel.y) /
3.f);
for (const auto& overlay : overlay_info) {
- dst->blitrect_scale(Rectf(tripos - overlay.hotspot.cast<float>() * zoom,
- overlay.pic->width() * zoom, overlay.pic->height() * zoom),
+ dst->blitrect_scale(Rectf(tripos - overlay.hotspot.cast<float>() * scale,
+ overlay.pic->width() * scale, overlay.pic->height() * scale),
overlay.pic,
Recti(0, 0, overlay.pic->width(), overlay.pic->height()), 1.f,
BlendMode::UseAlpha);
@@ -301,37 +322,42 @@
}
}
-} // namespace
-
GameRenderer::GameRenderer() {
}
GameRenderer::~GameRenderer() {
}
+std::unique_ptr<GameRenderer>
+GameRenderer::create() {
+ if (GameRendererGl4::supported())
+ return std::unique_ptr<GameRenderer>(new GameRendererGl4);
+ return std::unique_ptr<GameRenderer>(new GameRendererGl2);
+}
+
void GameRenderer::rendermap(const Widelands::EditorGameBase& egbase,
- const Vector2f& viewpoint,
- const float zoom,
+ const Vector2f& view_offset,
+ float zoom,
const Widelands::Player& player,
- const TextToDraw draw_text,
+ TextToDraw draw_text,
RenderTarget* dst) {
- draw(egbase, viewpoint, zoom, draw_text, &player, dst);
+ draw(egbase, view_offset, zoom, draw_text, &player, dst);
}
void GameRenderer::rendermap(const Widelands::EditorGameBase& egbase,
- const Vector2f& viewpoint,
- const float zoom,
- const TextToDraw draw_text,
+ const Vector2f& view_offset,
+ float zoom,
+ TextToDraw draw_text,
RenderTarget* dst) {
- draw(egbase, viewpoint, zoom, draw_text, nullptr, dst);
+ draw(egbase, view_offset, zoom, draw_text, nullptr, dst);
}
-void GameRenderer::draw(const EditorGameBase& egbase,
- const Vector2f& viewpoint,
- const float zoom,
- const TextToDraw draw_text,
- const Player* player,
- RenderTarget* dst) {
+void GameRendererGl2::draw(const EditorGameBase& egbase,
+ const Vector2f& viewpoint,
+ const float zoom,
+ const TextToDraw draw_text,
+ const Player* player,
+ RenderTarget* dst) {
assert(viewpoint.x >= 0); // divisions involving negative numbers are bad
assert(viewpoint.y >= 0);
assert(dst->get_offset().x <= 0);
@@ -372,59 +398,48 @@
const float scale = 1.f / zoom;
fields_to_draw_.reset(minfx, maxfx, minfy, maxfy);
- for (int32_t fy = minfy; fy <= maxfy; ++fy) {
- for (int32_t fx = minfx; fx <= maxfx; ++fx) {
- FieldsToDraw::Field& f =
- *fields_to_draw_.mutable_field(fields_to_draw_.calculate_index(fx, fy));
-
- f.geometric_coords = Coords(fx, fy);
-
- f.ln_index = fields_to_draw_.calculate_index(fx - 1, fy);
- f.rn_index = fields_to_draw_.calculate_index(fx + 1, fy);
- f.trn_index = fields_to_draw_.calculate_index(fx + (fy & 1), fy - 1);
- f.bln_index = fields_to_draw_.calculate_index(fx + (fy & 1) - 1, fy + 1);
- f.brn_index = fields_to_draw_.calculate_index(fx + (fy & 1), fy + 1);
-
- // Texture coordinates for pseudo random tiling of terrain and road
- // graphics. Since screen space X increases top-to-bottom and OpenGL
- // increases bottom-to-top we flip the y coordinate to not have
- // terrains and road graphics vertically mirrorerd.
- Vector2f map_pixel =
- MapviewPixelFunctions::to_map_pixel_ignoring_height(f.geometric_coords);
- f.texture_coords.x = map_pixel.x / kTextureSideLength;
- f.texture_coords.y = -map_pixel.y / kTextureSideLength;
-
- Coords normalized = f.geometric_coords;
- map.normalize_coords(normalized);
- f.fcoords = map.get_fcoords(normalized);
-
- map_pixel.y -= f.fcoords.field->get_height() * kHeightFactor;
-
- f.rendertarget_pixel = MapviewPixelFunctions::map_to_panel(viewpoint, zoom, map_pixel);
- f.gl_position = f.surface_pixel = f.rendertarget_pixel +
- dst->get_rect().origin().cast<float>() +
- dst->get_offset().cast<float>();
- pixel_to_gl_renderbuffer(
- surface_width, surface_height, &f.gl_position.x, &f.gl_position.y);
-
- f.brightness = field_brightness(f.fcoords, gametime, map, player);
-
- PlayerNumber owned_by = f.fcoords.field->get_owned_by();
- f.owner = owned_by != 0 ? &egbase.player(owned_by) : nullptr;
- f.is_border = f.fcoords.field->is_border();
- f.vision = 2;
- f.roads = f.fcoords.field->get_roads();
- if (player && !player->see_all()) {
- const Player::Field& pf = player->fields()[map.get_index(f.fcoords, map.get_width())];
- f.roads = pf.roads;
- f.vision = pf.vision;
- if (pf.vision == 1) {
- f.owner = pf.owner != 0 ? &egbase.player(owned_by) : nullptr;
- f.is_border = pf.border;
- }
+ for (auto cursor = fields_to_draw_.cursor(); cursor.valid(); cursor.next()) {
+ FieldToDrawGl2& f = cursor.mutable_field();
+
+ // Texture coordinates for pseudo random tiling of terrain and road
+ // graphics. Since screen space X increases top-to-bottom and OpenGL
+ // increases bottom-to-top we flip the y coordinate to not have
+ // terrains and road graphics vertically mirrorerd.
+ Vector2f map_pixel =
+ MapviewPixelFunctions::to_map_pixel_ignoring_height(cursor.geometric_coords());
+ f.texture_coords.x = map_pixel.x / kTextureSideLength;
+ f.texture_coords.y = -map_pixel.y / kTextureSideLength;
+
+ Coords normalized = cursor.geometric_coords();
+ map.normalize_coords(normalized);
+ f.fcoords = map.get_fcoords(normalized);
+
+ map_pixel.y -= f.fcoords.field->get_height() * kHeightFactor;
+
+ f.rendertarget_pixel = MapviewPixelFunctions::map_to_panel(viewpoint, zoom, map_pixel);
+ f.gl_position = f.surface_pixel = f.rendertarget_pixel +
+ dst->get_rect().origin().cast<float>() +
+ dst->get_offset().cast<float>();
+ pixel_to_gl_renderbuffer(
+ surface_width, surface_height, &f.gl_position.x, &f.gl_position.y);
+
+ f.brightness = field_brightness(f.fcoords, gametime, map, player);
+
+ PlayerNumber owned_by = f.fcoords.field->get_owned_by();
+ f.owner = owned_by != 0 ? &egbase.player(owned_by) : nullptr;
+ f.is_border = f.fcoords.field->is_border();
+ f.vision = 2;
+ f.roads = f.fcoords.field->get_roads();
+ if (player && !player->see_all()) {
+ const Player::Field& pf = player->fields()[map.get_index(f.fcoords, map.get_width())];
+ f.roads = pf.roads;
+ f.vision = pf.vision;
+ if (pf.vision == 1) {
+ f.owner = pf.owner != 0 ? &egbase.player(owned_by) : nullptr;
+ f.is_border = pf.border;
}
- f.roads |= edge_overlay_manager.get_overlay(f.fcoords);
}
+ f.roads |= edge_overlay_manager.get_overlay(f.fcoords);
}
// Enqueue the drawing of the terrain.
=== modified file 'src/graphic/game_renderer.h'
--- src/graphic/game_renderer.h 2016-10-26 19:43:40 +0000
+++ src/graphic/game_renderer.h 2017-01-07 12:36:16 +0000
@@ -34,11 +34,26 @@
class RenderTarget;
-// Renders the MapView on screen.
+/**
+ * This abstract base class renders the main game view into an
+ * arbitrary @ref RenderTarget.
+ *
+ * Specializations exist for different OpenGL rendering paths.
+ *
+ * Users of this class should keep instances alive for as long as possible,
+ * so that target-specific optimizations (such as caching data) can
+ * be effective.
+ *
+ * Every instance can only perform one render operation per frame. When
+ * multiple views of the map are open, each needs its own instance of
+ * GameRenderer.
+ */
class GameRenderer {
public:
- GameRenderer();
- ~GameRenderer();
+ virtual ~GameRenderer();
+
+ // Create a game renderer instance.
+ static std::unique_ptr<GameRenderer> create();
// Renders the map from a player's point of view into the given drawing
// window. The 'viewpoint' is the top left screens pixel map pixel and
@@ -58,19 +73,23 @@
TextToDraw draw_text,
RenderTarget* dst);
-private:
- // Draw the map for the given parameters (see rendermap). 'player'
- // can be nullptr in which case the whole map is drawn.
- void draw(const Widelands::EditorGameBase& egbase,
- const Vector2f& viewpoint,
- float scale,
- TextToDraw draw_text,
- const Widelands::Player* player,
- RenderTarget* dst);
-
- // This is owned and handled by us, but handed to the RenderQueue, so we
- // basically promise that this stays valid for one frame.
- FieldsToDraw fields_to_draw_;
+protected:
+ GameRenderer();
+
+ virtual void draw(const Widelands::EditorGameBase& egbase,
+ const Vector2f& view_offset,
+ const float zoom,
+ const TextToDraw draw_text,
+ const Widelands::Player* player,
+ RenderTarget* dst) = 0;
+
+ // Draws the objects (animations & overlays).
+ void draw_objects(const Widelands::EditorGameBase& egbase,
+ const float scale,
+ const FieldsToDrawRefBase& fields_to_draw,
+ const Widelands::Player* player,
+ const TextToDraw draw_text,
+ RenderTarget* dst);
DISALLOW_COPY_AND_ASSIGN(GameRenderer);
};
=== added file 'src/graphic/game_renderer_gl4.cc'
--- src/graphic/game_renderer_gl4.cc 1970-01-01 00:00:00 +0000
+++ src/graphic/game_renderer_gl4.cc 2017-01-07 12:36:16 +0000
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2010-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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "graphic/game_renderer_gl4.h"
+
+#include "graphic/gl/terrain_program_gl4.h"
+#include "graphic/render_queue.h"
+#include "graphic/rendertarget.h"
+#include "graphic/surface.h"
+#include "logic/editor_game_base.h"
+#include "logic/map.h"
+#include "logic/map_objects/walkingdir.h"
+#include "logic/player.h"
+#include "logic/roadtype.h"
+#include "wui/edge_overlay_manager.h"
+#include "wui/interactive_base.h"
+#include "wui/mapviewpixelconstants.h"
+#include "wui/mapviewpixelfunctions.h"
+
+using namespace Widelands;
+
+/**
+ * This is the front-end of the GL4 rendering path. See game_renderer.cc for a
+ * general overview of terrain rendering.
+ *
+ * Only terrain rendering (terrain textures, dithering, and roads) differs
+ * substantially from the GL2 rendering path. To avoid CPU work, a persistent
+ * TerrainInformationGl4 object maintains texture data and other information
+ * across frames.
+ *
+ * The GameRendererGl4 contains per-view information, but the underlying
+ * TerrainInformationGl4 instance is automatically shared between different
+ * views when possible.
+ */
+
+GameRendererGl4::GameRendererGl4() {
+}
+
+GameRendererGl4::~GameRendererGl4() {
+}
+
+bool GameRendererGl4::supported() {
+ return TerrainProgramGl4::supported();
+}
+
+void GameRendererGl4::draw(const EditorGameBase& egbase,
+ const Vector2f& view_offset,
+ const float zoom,
+ const TextToDraw draw_text,
+ const Player* player,
+ RenderTarget* dst) {
+ Surface* surface = dst->get_surface();
+ if (!surface)
+ return;
+
+ // Upload map changes.
+ if (!args_.terrain || &args_.terrain->egbase() != &egbase ||
+ args_.terrain->player() != player)
+ args_.terrain = TerrainInformationGl4::get(egbase, player);
+
+ // Determine the set of patches to draw.
+ float scale = 1.f / zoom;
+ Vector2f tl_map = view_offset - dst->get_offset().cast<float>() * zoom;
+
+ assert(tl_map.x >= 0); // divisions involving negative numbers are bad
+ assert(tl_map.y >= 0);
+
+ args_.minfx = tl_map.x / kTriangleWidth - 1;
+ args_.minfy = tl_map.y / kTriangleHeight - 1;
+ args_.maxfx = (tl_map.x + dst->get_rect().w * zoom + (kTriangleWidth / 2)) / kTriangleWidth;
+ args_.maxfy = (tl_map.y + dst->get_rect().h * zoom) / kTriangleHeight;
+
+ // Fudge for triangle boundary effects, for height differences, and for
+ // large immovables.
+ args_.minfx -= 1;
+ args_.minfy -= 1;
+ args_.maxfx += 3;
+ args_.maxfy += 10;
+
+ const Recti& bounding_rect = dst->get_rect();
+ const uint32_t gametime = egbase.get_gametime();
+
+ args_.scale = scale;
+ args_.surface_offset = (bounding_rect.origin() + dst->get_offset()).cast<float>() * zoom - view_offset;
+ args_.surface_width = surface->width();
+ args_.surface_height = surface->height();
+
+ scan_fields(view_offset);
+
+ args_.terrain->update(args_.minfx, args_.maxfx, args_.minfy, args_.maxfy);
+
+ // Enqueue the drawing of the terrain.
+ RenderQueue::Item i;
+ i.program_id = RenderQueue::Program::kTerrainGl4;
+ i.blend_mode = BlendMode::Copy;
+ i.terrain_arguments.destination_rect =
+ Rectf(bounding_rect.x, args_.surface_height - bounding_rect.y - bounding_rect.h,
+ bounding_rect.w, bounding_rect.h);
+ i.terrain_arguments.gametime = gametime;
+ i.terrain_arguments.renderbuffer_width = args_.surface_width;
+ i.terrain_arguments.renderbuffer_height = args_.surface_height;
+ i.terrain_gl4_arguments = &args_;
+ RenderQueue::instance().enqueue(i);
+
+ if (!args_.roads.empty()) {
+ i.program_id = RenderQueue::Program::kTerrainRoadGl4;
+ i.blend_mode = BlendMode::UseAlpha;
+ RenderQueue::instance().enqueue(i);
+ }
+
+ draw_objects(egbase, scale, fields_to_draw_, player, draw_text, dst);
+}
+
+void GameRendererGl4::scan_fields(const Vector2f& view_offset) {
+ const EditorGameBase& egbase = args_.terrain->egbase();
+ const Player* player = args_.terrain->player();
+ auto& map = egbase.map();
+ const EdgeOverlayManager& edge_overlay_manager = egbase.get_ibase()->edge_overlay_manager();
+
+ args_.roads.clear();
+ fields_to_draw_.reset(args_.minfx, args_.maxfx, args_.minfy, args_.maxfy);
+
+ for (auto cursor = fields_to_draw_.cursor(); cursor.valid(); cursor.next()) {
+ FieldToDrawBase& f = cursor.mutable_field();
+
+ Vector2f map_pixel =
+ MapviewPixelFunctions::to_map_pixel_ignoring_height(cursor.geometric_coords());
+
+ Coords normalized = cursor.geometric_coords();
+ map.normalize_coords(normalized);
+ f.fcoords = map.get_fcoords(normalized);
+
+ map_pixel.y -= f.fcoords.field->get_height() * kHeightFactor;
+
+ f.rendertarget_pixel = MapviewPixelFunctions::map_to_panel(view_offset, 1. / args_.scale, map_pixel);
+
+ PlayerNumber owned_by = f.fcoords.field->get_owned_by();
+ f.owner = owned_by != 0 ? &egbase.player(owned_by) : nullptr;
+ f.is_border = f.fcoords.field->is_border();
+ f.vision = 2;
+ if (player && !player->see_all()) {
+ const Player::Field& pf = player->fields()[map.get_index(f.fcoords, map.get_width())];
+ f.vision = pf.vision;
+ if (pf.vision == 1) {
+ f.owner = pf.owner != 0 ? &egbase.player(owned_by) : nullptr;
+ f.is_border = pf.border;
+ }
+ }
+
+ uint8_t roads;
+ if (!player || player->see_all()) {
+ roads = f.fcoords.field->get_roads();
+ } else {
+ const Player::Field& pf = player->fields()[map.get_index(f.fcoords, map.get_width())];
+ roads = pf.roads;
+ }
+ if (player)
+ roads |= edge_overlay_manager.get_overlay(f.fcoords);
+
+ uint8_t type = (roads >> RoadType::kEast) & RoadType::kMask;
+ if (type) {
+ args_.roads.emplace_back(cursor.geometric_coords(), type,
+ WalkingDir::WALK_E, f.fcoords.field->get_owned_by());
+ }
+
+ type = (roads >> RoadType::kSouthEast) & RoadType::kMask;
+ if (type) {
+ args_.roads.emplace_back(cursor.geometric_coords(), type,
+ WalkingDir::WALK_SE, f.fcoords.field->get_owned_by());
+ }
+
+ type = (roads >> RoadType::kSouthWest) & RoadType::kMask;
+ if (type) {
+ args_.roads.emplace_back(cursor.geometric_coords(), type,
+ WalkingDir::WALK_SW, f.fcoords.field->get_owned_by());
+ }
+ }
+}
=== added file 'src/graphic/game_renderer_gl4.h'
--- src/graphic/game_renderer_gl4.h 1970-01-01 00:00:00 +0000
+++ src/graphic/game_renderer_gl4.h 2017-01-07 12:36:16 +0000
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2010-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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef WL_GRAPHIC_GAME_RENDERER_GL4_H
+#define WL_GRAPHIC_GAME_RENDERER_GL4_H
+
+#include <vector>
+
+#include "graphic/game_renderer.h"
+#include "graphic/minimap_layer.h"
+#include "logic/widelands_geometry.h"
+
+class TerrainInformationGl4;
+
+/**
+ * This structure is used for the @ref RenderQueue by terrain rendering, road
+ * rendering, and minimap rendering. Each use only uses a subset of memers.
+ */
+struct TerrainGl4Arguments {
+ struct Road {
+ Widelands::Coords coord;
+
+ // One of the RoadTypes
+ uint8_t type;
+
+ // One of the WalkingDirs
+ uint8_t direction;
+
+ // Player number
+ uint8_t owner;
+
+ Road(Widelands::Coords coord_, uint8_t type_, uint8_t direction_, uint8_t owner_)
+ : coord(coord_), type(type_), direction(direction_), owner(owner_) {
+ }
+ };
+ static_assert(sizeof(Road) == 8, "bad alignment");
+
+ std::shared_ptr<TerrainInformationGl4> terrain;
+ float scale;
+ Vector2f surface_offset;
+ int surface_width;
+ int surface_height;
+ int minfx, minfy, maxfx, maxfy;
+ std::vector<Road> roads;
+
+ int minimap_tl_fx, minimap_tl_fy;
+ MiniMapLayer minimap_layers;
+};
+
+/**
+ * This is the front-end of the GL4 rendering path game renderer.
+ */
+class GameRendererGl4 : public GameRenderer {
+public:
+ GameRendererGl4();
+ virtual ~GameRendererGl4();
+
+ static bool supported();
+
+ void draw(const Widelands::EditorGameBase& egbase,
+ const Vector2f& view_offset,
+ const float zoom,
+ const TextToDraw draw_text,
+ const Widelands::Player* player,
+ RenderTarget* dst) override;
+
+private:
+ void scan_fields(const Vector2f& view_offset);
+
+ TerrainGl4Arguments args_;
+ FieldsToDrawBase fields_to_draw_;
+
+ DISALLOW_COPY_AND_ASSIGN(GameRendererGl4);
+};
+
+#endif // end of include guard: WL_GRAPHIC_GAME_RENDERER_H
=== modified file 'src/graphic/gl/blit_program.cc'
--- src/graphic/gl/blit_program.cc 2016-12-03 13:32:28 +0000
+++ src/graphic/gl/blit_program.cc 2017-01-07 12:36:16 +0000
@@ -19,13 +19,16 @@
#include "graphic/gl/blit_program.h"
+#include <memory>
#include <vector>
#include "base/log.h"
#include "graphic/blit_mode.h"
#include "graphic/gl/blit_data.h"
#include "graphic/gl/coordinate_conversion.h"
+#include "graphic/gl/streaming_buffer.h"
#include "graphic/gl/utils.h"
+#include "profile/profile.h"
namespace {
@@ -41,9 +44,120 @@
BlendMode blend_mode;
};
+class BlitProgramGl2 : public BlitProgram {
+public:
+ BlitProgramGl2();
+
+ void draw(const std::vector<Arguments>& arguments) override;
+
+private:
+ struct PerVertexData {
+ PerVertexData(float init_gl_x,
+ float init_gl_y,
+ float init_gl_z,
+ float init_texture_x,
+ float init_texture_y,
+ float init_mask_texture_x,
+ float init_mask_texture_y,
+ float init_blend_r,
+ float init_blend_g,
+ float init_blend_b,
+ float init_blend_a,
+ float init_program_flavor)
+ : gl_x(init_gl_x),
+ gl_y(init_gl_y),
+ gl_z(init_gl_z),
+ texture_x(init_texture_x),
+ texture_y(init_texture_y),
+ mask_texture_x(init_mask_texture_x),
+ mask_texture_y(init_mask_texture_y),
+ blend_r(init_blend_r),
+ blend_g(init_blend_g),
+ blend_b(init_blend_b),
+ blend_a(init_blend_a),
+ program_flavor(init_program_flavor) {
+ }
+
+ float gl_x, gl_y, gl_z;
+ float texture_x, texture_y;
+ float mask_texture_x, mask_texture_y;
+ float blend_r, blend_g, blend_b, blend_a;
+ float program_flavor;
+ };
+ static_assert(sizeof(PerVertexData) == 48, "Wrong padding.");
+
+ // The buffer that will contain the quad for rendering.
+ Gl::Buffer<PerVertexData> gl_array_buffer_;
+
+ // The program.
+ Gl::Program gl_program_;
+
+ // Attributes.
+ GLint attr_blend_;
+ GLint attr_mask_texture_position_;
+ GLint attr_position_;
+ GLint attr_texture_position_;
+ GLint attr_program_flavor_;
+
+ // Uniforms.
+ GLint u_texture_;
+ GLint u_mask_;
+
+ // Cached for efficiency.
+ std::vector<PerVertexData> vertices_;
+};
+
+class BlitProgramGl4 : public BlitProgram {
+public:
+ BlitProgramGl4();
+
+ static bool supported();
+
+ void draw(const std::vector<Arguments>& arguments) override;
+
+private:
+ struct PerRectData {
+ float dst_x, dst_y, dst_width, dst_height;
+ uint32_t src_x, src_y, src_width, src_height;
+ uint32_t src_parent_width, src_parent_height;
+ uint32_t mask_x, mask_y, mask_width, mask_height;
+ uint32_t mask_parent_width, mask_parent_height;
+ uint32_t blend_r, blend_g, blend_b, blend_a;
+ float program_flavor, z;
+
+ // Standard OpenGL packing aligns arrays to a multiple of 16 bytes.
+ float padding[2];
+ };
+ static_assert(sizeof(PerRectData) == 96, "Wrong padding.");
+
+ void setup_index_buffer(unsigned num_rects);
+
+ // The index buffer.
+ Gl::Buffer<uint16_t> gl_index_buffer_;
+ unsigned num_index_rects_;
+
+ // The per-rect data buffer.
+ Gl::StreamingBuffer<PerRectData> gl_rects_buffer_;
+
+ // The program.
+ Gl::Program gl_program_;
+
+ // Uniform locations.
+ GLint u_texture_;
+ GLint u_mask_;
+};
+
} // namespace
BlitProgram::BlitProgram() {
+}
+
+BlitProgram::~BlitProgram() {
+}
+
+BlitProgramGl2::BlitProgramGl2() {
+ log("Using GL2 blit path\n");
+
gl_program_.build("blit");
attr_blend_ = glGetAttribLocation(gl_program_.object(), "attr_blend");
@@ -57,10 +171,7 @@
u_mask_ = glGetUniformLocation(gl_program_.object(), "u_mask");
}
-BlitProgram::~BlitProgram() {
-}
-
-void BlitProgram::draw(const std::vector<Arguments>& arguments) {
+void BlitProgramGl2::draw(const std::vector<Arguments>& arguments) {
glUseProgram(gl_program_.object());
auto& gl_state = Gl::State::instance();
@@ -176,6 +287,165 @@
}
}
+BlitProgramGl4::BlitProgramGl4()
+ : gl_rects_buffer_(GL_ARRAY_BUFFER) {
+ log("Using GL4 blit path\n");
+
+ gl_program_.build_vp_fp({"blit_gl4"}, {"blit"});
+
+ u_texture_ = glGetUniformLocation(gl_program_.object(), "u_texture");
+ u_mask_ = glGetUniformLocation(gl_program_.object(), "u_mask");
+
+ num_index_rects_ = 0;
+}
+
+bool BlitProgramGl4::supported() {
+ const auto& caps = Gl::State::instance().capabilities();
+
+ if (caps.glsl_version < 130)
+ return false;
+
+ if (!caps.ARB_separate_shader_objects ||
+ !caps.ARB_shader_storage_buffer_object ||
+ !caps.ARB_uniform_buffer_object)
+ return false;
+
+ return !g_options.pull_section("global").get_bool("disable_gl4", false);
+}
+
+void BlitProgramGl4::draw(const std::vector<Arguments>& arguments) {
+ glUseProgram(gl_program_.object());
+
+ auto& gl_state = Gl::State::instance();
+
+ gl_state.enable_vertex_attrib_array({});
+
+ glUniform1i(u_texture_, 0);
+ glUniform1i(u_mask_, 1);
+
+ // Prepare the buffer for many draw calls.
+ std::vector<DrawBatch> draw_batches;
+ auto rects = gl_rects_buffer_.stream(arguments.size());
+
+ size_t i = 0;
+ while (i < arguments.size()) {
+ const auto& template_args = arguments[i];
+ const int start = i;
+
+ // Batch common blit operations up.
+ while (i < arguments.size()) {
+ const auto& current_args = arguments[i];
+ if (current_args.blend_mode != template_args.blend_mode ||
+ current_args.texture.texture_id != template_args.texture.texture_id ||
+ (current_args.mask.texture_id != 0 &&
+ current_args.mask.texture_id != template_args.mask.texture_id)) {
+ break;
+ }
+
+ rects.emplace_back();
+ auto& rect = rects.back();
+ rect.dst_x = current_args.destination_rect.x;
+ rect.dst_y = current_args.destination_rect.y;
+ rect.dst_width = current_args.destination_rect.w;
+ rect.dst_height = current_args.destination_rect.h;
+
+ rect.src_x = current_args.texture.rect.x;
+ rect.src_y = current_args.texture.rect.y;
+ rect.src_width = current_args.texture.rect.w;
+ rect.src_height = current_args.texture.rect.h;
+ rect.src_parent_width = current_args.texture.parent_width;
+ rect.src_parent_height = current_args.texture.parent_height;
+
+ rect.mask_x = current_args.mask.rect.x;
+ rect.mask_y = current_args.mask.rect.y;
+ rect.mask_width = current_args.mask.rect.w;
+ rect.mask_height = current_args.mask.rect.h;
+ rect.mask_parent_width = current_args.mask.parent_width;
+ rect.mask_parent_height = current_args.mask.parent_height;
+
+ rect.blend_r = current_args.blend.r;
+ rect.blend_g = current_args.blend.g;
+ rect.blend_b = current_args.blend.b;
+ rect.blend_a = current_args.blend.a;
+
+ switch (current_args.blit_mode) {
+ case BlitMode::kDirect:
+ rect.program_flavor = 0.;
+ break;
+
+ case BlitMode::kMonochrome:
+ rect.program_flavor = 1.;
+ break;
+
+ case BlitMode::kBlendedWithMask:
+ rect.program_flavor = 2.;
+ break;
+ }
+
+ rect.z = current_args.z_value;
+
+ ++i;
+ }
+
+ draw_batches.emplace_back(DrawBatch{int(start), int(i - start),
+ template_args.texture.texture_id,
+ template_args.mask.texture_id, template_args.blend_mode});
+ }
+
+ glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, gl_rects_buffer_.object(),
+ rects.unmap(), i * sizeof(PerRectData));
+
+ setup_index_buffer(i);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gl_index_buffer_.object());
+
+ // Now do the draw calls.
+ for (const auto& draw_arg : draw_batches) {
+ gl_state.bind(GL_TEXTURE0, draw_arg.texture);
+ gl_state.bind(GL_TEXTURE1, draw_arg.mask);
+
+ if (draw_arg.blend_mode == BlendMode::Copy) {
+ glBlendFunc(GL_ONE, GL_ZERO);
+ }
+
+ glDrawElements(GL_TRIANGLES, 6 * draw_arg.count, GL_UNSIGNED_SHORT,
+ static_cast<uint16_t*>(nullptr) + 6 * draw_arg.offset);
+
+ if (draw_arg.blend_mode == BlendMode::Copy) {
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ }
+ }
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
+
+void BlitProgramGl4::setup_index_buffer(unsigned num_rects)
+{
+ if (num_rects <= num_index_rects_)
+ return;
+
+ if (num_rects > 65536 / 4)
+ throw wexception("Too many rectangles for 16-bit indices");
+
+ std::vector<uint16_t> indices;
+ indices.reserve(num_rects * 6);
+
+ for (unsigned i = 0; i < num_rects; ++i) {
+ indices.push_back(4 * i);
+ indices.push_back(4 * i + 1);
+ indices.push_back(4 * i + 2);
+
+ indices.push_back(4 * i + 2);
+ indices.push_back(4 * i + 1);
+ indices.push_back(4 * i + 3);
+ }
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gl_index_buffer_.object());
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint16_t) * indices.size(),
+ indices.data(), GL_STATIC_DRAW);
+ num_index_rects_ = num_rects;
+}
+
+
void BlitProgram::draw(const Rectf& gl_dest_rect,
const float z_value,
const BlitData& texture,
@@ -194,8 +464,29 @@
BlendMode::UseAlpha, BlitMode::kMonochrome}});
}
+namespace {
+
+class BlitProgramHolder {
+public:
+ BlitProgramHolder() {
+ if (BlitProgramGl4::supported())
+ program_.reset(new BlitProgramGl4);
+ else
+ program_.reset(new BlitProgramGl2);
+ }
+
+ BlitProgram& program() {
+ return *program_;
+ }
+
+private:
+ std::unique_ptr<BlitProgram> program_;
+};
+
+} // namespace
+
// static
BlitProgram& BlitProgram::instance() {
- static BlitProgram blit_program;
- return blit_program;
+ static BlitProgramHolder holder;
+ return holder.program();
}
=== modified file 'src/graphic/gl/blit_program.h'
--- src/graphic/gl/blit_program.h 2016-10-16 09:50:48 +0000
+++ src/graphic/gl/blit_program.h 2017-01-07 12:36:16 +0000
@@ -47,7 +47,7 @@
// Returns the (singleton) instance of this class.
static BlitProgram& instance();
- ~BlitProgram();
+ virtual ~BlitProgram();
// Draws the rectangle 'gl_src_rect' from the texture with the name
// 'gl_texture_image' to 'gl_dest_rect' in the currently bound framebuffer. All
@@ -70,66 +70,12 @@
const RGBAColor& blend);
// Draws a bunch of items at once.
- void draw(const std::vector<Arguments>& arguments);
+ virtual void draw(const std::vector<Arguments>& arguments) = 0;
+
+protected:
+ BlitProgram();
private:
- BlitProgram();
-
- struct PerVertexData {
- PerVertexData(float init_gl_x,
- float init_gl_y,
- float init_gl_z,
- float init_texture_x,
- float init_texture_y,
- float init_mask_texture_x,
- float init_mask_texture_y,
- float init_blend_r,
- float init_blend_g,
- float init_blend_b,
- float init_blend_a,
- float init_program_flavor)
- : gl_x(init_gl_x),
- gl_y(init_gl_y),
- gl_z(init_gl_z),
- texture_x(init_texture_x),
- texture_y(init_texture_y),
- mask_texture_x(init_mask_texture_x),
- mask_texture_y(init_mask_texture_y),
- blend_r(init_blend_r),
- blend_g(init_blend_g),
- blend_b(init_blend_b),
- blend_a(init_blend_a),
- program_flavor(init_program_flavor) {
- }
-
- float gl_x, gl_y, gl_z;
- float texture_x, texture_y;
- float mask_texture_x, mask_texture_y;
- float blend_r, blend_g, blend_b, blend_a;
- float program_flavor;
- };
- static_assert(sizeof(PerVertexData) == 48, "Wrong padding.");
-
- // The buffer that will contain the quad for rendering.
- Gl::Buffer<PerVertexData> gl_array_buffer_;
-
- // The program.
- Gl::Program gl_program_;
-
- // Attributes.
- GLint attr_blend_;
- GLint attr_mask_texture_position_;
- GLint attr_position_;
- GLint attr_texture_position_;
- GLint attr_program_flavor_;
-
- // Uniforms.
- GLint u_texture_;
- GLint u_mask_;
-
- // Cached for efficiency.
- std::vector<PerVertexData> vertices_;
-
DISALLOW_COPY_AND_ASSIGN(BlitProgram);
};
=== modified file 'src/graphic/gl/dither_program.cc'
--- src/graphic/gl/dither_program.cc 2016-12-03 13:32:28 +0000
+++ src/graphic/gl/dither_program.cc 2017-01-07 12:36:16 +0000
@@ -54,7 +54,7 @@
DitherProgram::~DitherProgram() {
}
-void DitherProgram::add_vertex(const FieldsToDraw::Field& field,
+void DitherProgram::add_vertex(const FieldToDrawGl2& field,
const TrianglePoint triangle_point,
const Vector2f& texture_offset) {
vertices_.emplace_back();
@@ -87,10 +87,9 @@
void DitherProgram::maybe_add_dithering_triangle(
const uint32_t gametime,
const DescriptionMaintainer<Widelands::TerrainDescription>& terrains,
- const FieldsToDraw& fields_to_draw,
- const int idx1,
- const int idx2,
- const int idx3,
+ const FieldToDrawGl2& f1,
+ const FieldToDrawGl2& f2,
+ const FieldToDrawGl2& f3,
const int my_terrain,
const int other_terrain) {
if (my_terrain == other_terrain) {
@@ -100,9 +99,9 @@
if (terrains.get(my_terrain).dither_layer() < other_terrain_description.dither_layer()) {
const Vector2f texture_offset =
to_gl_texture(other_terrain_description.get_texture(gametime).blit_data()).origin();
- add_vertex(fields_to_draw.at(idx1), TrianglePoint::kTopRight, texture_offset);
- add_vertex(fields_to_draw.at(idx2), TrianglePoint::kTopLeft, texture_offset);
- add_vertex(fields_to_draw.at(idx3), TrianglePoint::kBottomMiddle, texture_offset);
+ add_vertex(f1, TrianglePoint::kTopRight, texture_offset);
+ add_vertex(f2, TrianglePoint::kTopLeft, texture_offset);
+ add_vertex(f3, TrianglePoint::kBottomMiddle, texture_offset);
}
}
@@ -141,7 +140,7 @@
void DitherProgram::draw(const uint32_t gametime,
const DescriptionMaintainer<Widelands::TerrainDescription>& terrains,
- const FieldsToDraw& fields_to_draw,
+ const FieldsToDrawGl2& fields_to_draw,
const float z_value) {
// This method expects that all terrains have the same dimensions and that
// all are packed into the same texture atlas, i.e. all are in the same GL
@@ -150,49 +149,49 @@
vertices_.clear();
vertices_.reserve(fields_to_draw.size() * 3);
- for (size_t current_index = 0; current_index < fields_to_draw.size(); ++current_index) {
- const FieldsToDraw::Field& field = fields_to_draw.at(current_index);
+ for (auto cursor = fields_to_draw.cursor(); cursor.valid(); cursor.next()) {
+ const FieldToDrawGl2& field = cursor.field();
// The bottom right neighbor fields_to_draw is needed for both triangles
// associated with this field. If it is not in fields_to_draw, there is no need to
// draw any triangles.
- if (field.brn_index == FieldsToDraw::kInvalidIndex) {
+ if (!cursor.brn_valid()) {
continue;
}
// Dithering triangles for Down triangle.
- if (field.bln_index != FieldsToDraw::kInvalidIndex) {
+ if (cursor.bln_valid()) {
maybe_add_dithering_triangle(
- gametime, terrains, fields_to_draw, field.brn_index, current_index, field.bln_index,
+ gametime, terrains, cursor.brn(), cursor.field(), cursor.bln(),
field.fcoords.field->terrain_d(), field.fcoords.field->terrain_r());
- const int terrain_dd = fields_to_draw.at(field.bln_index).fcoords.field->terrain_r();
- maybe_add_dithering_triangle(gametime, terrains, fields_to_draw, field.bln_index,
- field.brn_index, current_index,
+ const int terrain_dd = cursor.bln().fcoords.field->terrain_r();
+ maybe_add_dithering_triangle(gametime, terrains, cursor.bln(),
+ cursor.brn(), cursor.field(),
field.fcoords.field->terrain_d(), terrain_dd);
- if (field.ln_index != FieldsToDraw::kInvalidIndex) {
- const int terrain_l = fields_to_draw.at(field.ln_index).fcoords.field->terrain_r();
- maybe_add_dithering_triangle(gametime, terrains, fields_to_draw, current_index,
- field.bln_index, field.brn_index,
+ if (cursor.ln_valid()) {
+ const int terrain_l = cursor.ln().fcoords.field->terrain_r();
+ maybe_add_dithering_triangle(gametime, terrains, cursor.field(),
+ cursor.bln(), cursor.brn(),
field.fcoords.field->terrain_d(), terrain_l);
}
}
// Dithering for right triangle.
- if (field.rn_index != FieldsToDraw::kInvalidIndex) {
+ if (cursor.rn_valid()) {
maybe_add_dithering_triangle(
- gametime, terrains, fields_to_draw, current_index, field.brn_index, field.rn_index,
+ gametime, terrains, cursor.field(), cursor.brn(), cursor.rn(),
field.fcoords.field->terrain_r(), field.fcoords.field->terrain_d());
- int terrain_rr = fields_to_draw.at(field.rn_index).fcoords.field->terrain_d();
- maybe_add_dithering_triangle(gametime, terrains, fields_to_draw, field.brn_index,
- field.rn_index, current_index,
+ int terrain_rr = cursor.rn().fcoords.field->terrain_d();
+ maybe_add_dithering_triangle(gametime, terrains, cursor.brn(),
+ cursor.rn(), cursor.field(),
field.fcoords.field->terrain_r(), terrain_rr);
- if (field.trn_index != FieldsToDraw::kInvalidIndex) {
- const int terrain_u = fields_to_draw.at(field.trn_index).fcoords.field->terrain_d();
- maybe_add_dithering_triangle(gametime, terrains, fields_to_draw, field.rn_index,
- current_index, field.brn_index,
+ if (cursor.trn_valid()) {
+ const int terrain_u = cursor.trn().fcoords.field->terrain_d();
+ maybe_add_dithering_triangle(gametime, terrains, cursor.rn(),
+ cursor.field(), cursor.brn(),
field.fcoords.field->terrain_r(), terrain_u);
}
}
=== modified file 'src/graphic/gl/dither_program.h'
--- src/graphic/gl/dither_program.h 2016-10-16 09:31:42 +0000
+++ src/graphic/gl/dither_program.h 2017-01-07 12:36:16 +0000
@@ -38,7 +38,7 @@
// Draws the terrain.
void draw(uint32_t gametime,
const DescriptionMaintainer<Widelands::TerrainDescription>& terrains,
- const FieldsToDraw& fields_to_draw,
+ const FieldsToDrawGl2& fields_to_draw,
float z_value);
private:
@@ -54,17 +54,16 @@
void maybe_add_dithering_triangle(
uint32_t gametime,
const DescriptionMaintainer<Widelands::TerrainDescription>& terrains,
- const FieldsToDraw& fields_to_draw,
- int idx1,
- int idx2,
- int idx3,
+ const FieldToDrawGl2& f1,
+ const FieldToDrawGl2& f2,
+ const FieldToDrawGl2& f3,
int my_terrain,
int other_terrain);
// Adds the 'field' as an vertex to the 'vertices_'. The 'order_index'
// defines which texture position in the dithering texture will be used for
// this vertex.
- void add_vertex(const FieldsToDraw::Field& field,
+ void add_vertex(const FieldToDrawGl2& field,
TrianglePoint triangle_point,
const Vector2f& texture_offset);
=== modified file 'src/graphic/gl/fields_to_draw.h'
--- src/graphic/gl/fields_to_draw.h 2016-10-24 20:07:22 +0000
+++ src/graphic/gl/fields_to_draw.h 2017-01-07 12:36:16 +0000
@@ -33,51 +33,47 @@
#include "logic/widelands.h"
#include "logic/widelands_geometry.h"
+template<typename FTD>
+class FieldsToDrawCursor;
+
+template<typename F>
+class FieldsToDrawRef;
+
+struct FieldToDrawBase {
+ Widelands::FCoords fcoords; // The normalized coords and the field this is refering to.
+ Vector2f rendertarget_pixel;
+
+ // The next values are not necessarily the true data of this field, but
+ // what the player should see. For example in fog of war we always draw
+ // what we saw last.
+ //
+ // Note: we put the roads member variable here for better alignment even
+ // though it isn't needed in the Gl4 path.
+ Widelands::Player* owner; // can be nullptr.
+ uint8_t roads; // Bitmask of roads to render, see logic/roadtype.h.
+ bool is_border;
+ Widelands::Vision vision;
+};
+
+struct FieldToDrawGl2 : FieldToDrawBase {
+ Vector2f gl_position; // GL Position of this field.
+
+ // Surface pixel this will be plotted on.
+ Vector2f surface_pixel;
+
+ // Rendertarget pixel this will be plotted on. This is only different by
+ // the Rendertarget::get_rect().origin() of the view window.
+ Vector2f texture_coords; // Texture coordinates.
+ float brightness; // brightness of the pixel
+};
+
// Helper struct that contains the data needed for drawing all fields. All
// methods are inlined for performance reasons.
-class FieldsToDraw {
+class FieldsToDrawImplBase {
public:
static constexpr int kInvalidIndex = std::numeric_limits<int>::min();
- struct Field {
- Widelands::Coords geometric_coords; // geometric coordinates (i.e. map coordinates that can
- // be out of bounds).
- Widelands::FCoords fcoords; // The normalized coords and the field this is refering to.
- Vector2f gl_position; // GL Position of this field.
-
- // Surface pixel this will be plotted on.
- Vector2f surface_pixel;
-
- // Rendertarget pixel this will be plotted on. This is only different by
- // the Rendertarget::get_rect().origin() of the view window.
- Vector2f rendertarget_pixel;
- Vector2f texture_coords; // Texture coordinates.
- float brightness; // brightness of the pixel
-
- // The next values are not necessarily the true data of this field, but
- // what the player should see. For example in fog of war we always draw
- // what we saw last.
- uint8_t roads; // Bitmask of roads to render, see logic/roadtype.h.
- bool is_border;
- Widelands::Vision vision;
- Widelands::Player* owner; // can be nullptr.
-
- // Index of neighbors in this 'FieldsToDraw'. kInvalidIndex if this
- // neighbor is not contained.
- int ln_index;
- int rn_index;
- int trn_index;
- int bln_index;
- int brn_index;
-
- inline bool all_neighbors_valid() const {
- return ln_index != kInvalidIndex && rn_index != kInvalidIndex &&
- trn_index != kInvalidIndex && bln_index != kInvalidIndex &&
- brn_index != kInvalidIndex;
- }
- };
-
- FieldsToDraw() {
+ FieldsToDrawImplBase() {
}
// Resize this fields to draw for reuse.
@@ -87,55 +83,277 @@
min_fy_ = minfy;
max_fy_ = maxfy;
w_ = max_fx_ - min_fx_ + 1;
- h_ = max_fy_ - min_fy_ + 1;
- const size_t dimension = w_ * h_;
- if (fields_.size() != dimension) {
- fields_.resize(dimension);
- }
- }
-
- // Calculates the index of the given field with ('fx', 'fy') being geometric
- // coordinates in the map. Returns kInvalidIndex if this field is not in the
- // fields_to_draw.
- inline int calculate_index(int fx, int fy) const {
- uint16_t xidx = fx - min_fx_;
- if (xidx >= w_) {
- return kInvalidIndex;
- }
- uint16_t yidx = fy - min_fy_;
- if (yidx >= h_) {
- return kInvalidIndex;
- }
- return yidx * w_ + xidx;
- }
-
- // The number of fields to draw.
- inline size_t size() const {
- return fields_.size();
- }
-
- // Get the field at 'index' which must be in bound.
- inline const Field& at(const int index) const {
- return fields_.at(index);
- }
-
- // Returns a mutable field at 'index' which must be in bound.
- inline Field* mutable_field(const int index) {
- return &fields_[index];
- }
-
-private:
+ }
+
+ int min_fx() const {
+ return min_fx_;
+ }
+
+ int max_fx() const {
+ return max_fx_;
+ }
+
+ int min_fy() const {
+ return min_fy_;
+ }
+
+ int max_fy() const {
+ return max_fy_;
+ }
+
+ int get_w() const {
+ return w_;
+ }
+
+protected:
// Minimum and maximum field coordinates (geometric) to render. Can be negative.
int min_fx_;
int max_fx_;
int min_fy_;
int max_fy_;
- // Width and height in number of fields.
+ // Width in number of fields.
int w_;
- int h_;
+};
+
+// Helper struct that contains the data needed for drawing all fields. All
+// methods are inlined for performance reasons.
+template<typename Field_>
+class FieldsToDraw : public FieldsToDrawImplBase {
+public:
+ using Field = Field_;
+
+ FieldsToDraw() {
+ }
+
+ // Resize this fields to draw for reuse.
+ void reset(int minfx, int maxfx, int minfy, int maxfy) {
+ FieldsToDrawImplBase::reset(minfx, maxfx, minfy, maxfy);
+
+ int h = max_fy_ - min_fy_ + 1;
+ const size_t dimension = FieldsToDrawImplBase::get_w() * h;
+ if (fields_.size() != dimension) {
+ fields_.resize(dimension);
+ }
+ }
+
+ // The number of fields to draw.
+ inline size_t size() const {
+ return fields_.size();
+ }
+
+ Field& operator[](int index) {
+ return fields_[index];
+ }
+
+ const Field& operator[](int index) const {
+ return fields_[index];
+ }
+
+ FieldsToDrawCursor<FieldsToDraw> cursor();
+ FieldsToDrawCursor<const FieldsToDraw> cursor() const;
+
+private:
+ template<typename F>
+ friend class FieldsToDrawRef;
+
+ const Field* get_fields() const {
+ return &fields_[0];
+ }
+
+ intptr_t get_fields_stride() const {
+ return sizeof(fields_[0]);
+ }
std::vector<Field> fields_;
};
+template<typename Field_>
+class FieldsToDrawRef : public FieldsToDrawImplBase {
+public:
+ using Field = Field_;
+
+ template<typename FTD>
+ FieldsToDrawRef(const FTD& fields_to_draw) {
+ reset(fields_to_draw.min_fx(), fields_to_draw.max_fx(),
+ fields_to_draw.min_fy(), fields_to_draw.max_fy());
+
+ // First assign to type Field* for type-checking, before reinterpreting
+ // to the char* that will be used for pointer arithmetic.
+ const Field* fields = fields_to_draw.get_fields();
+
+ fields_ = reinterpret_cast<const char*>(fields);
+ stride_ = fields_to_draw.get_fields_stride();
+ }
+
+ const Field& operator[](int index) const {
+ const char* base = fields_ + index * stride_;
+ return *reinterpret_cast<const Field*>(base);
+ }
+
+ FieldsToDrawCursor<const FieldsToDrawRef> cursor() const;
+
+private:
+ const char* fields_;
+ intptr_t stride_;
+};
+
+using FieldsToDrawBase = FieldsToDraw<FieldToDrawBase>;
+using FieldsToDrawGl2 = FieldsToDraw<FieldToDrawGl2>;
+using FieldsToDrawRefBase = FieldsToDrawRef<FieldToDrawBase>;
+
+// For iteration over fields.
+//
+// Template for const-correctness.
+template<typename FTD>
+class FieldsToDrawCursor {
+public:
+ using Field = typename FTD::Field;
+
+ FieldsToDrawCursor(FTD& fields_to_draw)
+ : fields_(fields_to_draw) {
+ type_check(fields_to_draw);
+
+ geometric_coords_.x = fields_.min_fx();
+ geometric_coords_.y = fields_.min_fy();
+ geometric_tblx_shift_ = (fields_.min_fy() & 1) - 1;
+
+ index_ = 0;
+ }
+
+ bool valid() const {
+ return index_ >= 0;
+ }
+
+ void next() {
+ assert(valid());
+
+ index_++;
+ geometric_coords_.x++;
+ if (geometric_coords_.x > fields_.max_fx()) {
+ geometric_coords_.x = fields_.min_fx();
+ geometric_tblx_shift_ = -1 - geometric_tblx_shift_;
+ geometric_coords_.y++;
+ if (geometric_coords_.y > fields_.max_fy())
+ index_ = -1;
+ }
+ }
+
+ const Field& field() const {
+ assert(valid());
+ return fields_[index_];
+ }
+
+ Field& mutable_field() {
+ assert(valid());
+ return fields_[index_];
+ }
+
+ // Return current geometric coordinates (i.e. map coordinates that can
+ // be out of bounds).
+ Widelands::Coords geometric_coords() const {
+ assert(valid());
+ return geometric_coords_;
+ }
+
+ bool tln_valid() const {
+ assert(valid());
+ return (geometric_coords_.y > fields_.min_fy()) &&
+ (geometric_coords_.x + geometric_tblx_shift_ >= fields_.min_fx());
+ }
+
+ const Field& tln() const {
+ assert(tln_valid());
+ return fields_[index_ + geometric_tblx_shift_ - fields_.get_w()];
+ }
+
+ bool trn_valid() const {
+ assert(valid());
+ return (geometric_coords_.y > fields_.min_fy()) &&
+ (geometric_coords_.x + geometric_tblx_shift_ + 1 <= fields_.max_fx());
+ }
+
+ const Field& trn() const {
+ assert(trn_valid());
+ return fields_[index_ + geometric_tblx_shift_ + 1 - fields_.get_w()];
+ }
+
+ bool ln_valid() const {
+ assert(valid());
+ return (geometric_coords_.x - 1 >= fields_.min_fx());
+ }
+
+ const Field& ln() const {
+ assert(ln_valid());
+ return fields_[index_ - 1];
+ }
+
+ bool rn_valid() const {
+ assert(valid());
+ return (geometric_coords_.x + 1 <= fields_.max_fx());
+ }
+
+ const Field& rn() const {
+ assert(rn_valid());
+ return fields_[index_ + 1];
+ }
+
+ bool bln_valid() const {
+ assert(valid());
+ return (geometric_coords_.y < fields_.max_fy()) &&
+ (geometric_coords_.x + geometric_tblx_shift_ >= fields_.min_fx());
+ }
+
+ const Field& bln() const {
+ assert(bln_valid());
+ return fields_[index_ + geometric_tblx_shift_ + fields_.get_w()];
+ }
+
+ bool brn_valid() const {
+ assert(valid());
+ return (geometric_coords_.y < fields_.max_fy()) &&
+ (geometric_coords_.x + geometric_tblx_shift_ + 1 <= fields_.max_fx());
+ }
+
+ const Field& brn() const {
+ assert(brn_valid());
+ return fields_[index_ + geometric_tblx_shift_ + 1 + fields_.get_w()];
+ }
+
+ bool all_neighbors_valid() const {
+ assert(valid());
+ return (geometric_coords_.y > fields_.min_fy() &&
+ geometric_coords_.y < fields_.max_fy() &&
+ geometric_coords_.x > fields_.min_fx() &&
+ geometric_coords_.x < fields_.max_fx());
+ }
+
+private:
+ template<typename F>
+ void type_check(const FieldsToDraw<F>&) {}
+ template<typename F>
+ void type_check(const FieldsToDrawRef<F>&) {}
+
+ FTD& fields_;
+
+ Widelands::Coords geometric_coords_;
+ int geometric_tblx_shift_; // top/bottom left neighbor geometric x-coordinate offset
+ int index_;
+};
+
+template<typename F>
+inline FieldsToDrawCursor<FieldsToDraw<F>> FieldsToDraw<F>::cursor() {
+ return {*this};
+}
+
+template<typename F>
+inline FieldsToDrawCursor<const FieldsToDraw<F>> FieldsToDraw<F>::cursor() const {
+ return {*this};
+}
+
+template<typename F>
+inline FieldsToDrawCursor<const FieldsToDrawRef<F>> FieldsToDrawRef<F>::cursor() const {
+ return {*this};
+}
+
#endif // end of include guard: WL_GRAPHIC_GL_FIELDS_TO_DRAW_H
=== modified file 'src/graphic/gl/initialize.cc'
--- src/graphic/gl/initialize.cc 2016-08-04 15:49:05 +0000
+++ src/graphic/gl/initialize.cc 2017-01-07 12:36:16 +0000
@@ -135,6 +135,8 @@
log("Graphics: OpenGL: ShadingLanguage: \"%s\"\n",
reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION)));
+ Gl::State::instance().check_capabilities();
+
glDrawBuffer(GL_BACK);
glDisable(GL_DEPTH_TEST);
=== modified file 'src/graphic/gl/road_program.cc'
--- src/graphic/gl/road_program.cc 2016-12-01 17:35:34 +0000
+++ src/graphic/gl/road_program.cc 2017-01-07 12:36:16 +0000
@@ -48,8 +48,9 @@
void RoadProgram::add_road(const int renderbuffer_width,
const int renderbuffer_height,
- const FieldsToDraw::Field& start,
- const FieldsToDraw::Field& end,
+ Widelands::Coords geometric_coords,
+ const FieldToDrawGl2& start,
+ const FieldToDrawGl2& end,
const float scale,
const Widelands::RoadType road_type,
const Direction direction,
@@ -82,8 +83,8 @@
const Image& texture =
road_type == Widelands::RoadType::kNormal ?
visible_owner->tribe().road_textures().get_normal_texture(
- start.geometric_coords, direction) :
- visible_owner->tribe().road_textures().get_busy_texture(start.geometric_coords, direction);
+ geometric_coords, direction) :
+ visible_owner->tribe().road_textures().get_busy_texture(geometric_coords, direction);
if (*gl_texture == 0) {
*gl_texture = texture.blit_data().texture_id;
}
@@ -135,42 +136,42 @@
void RoadProgram::draw(const int renderbuffer_width,
const int renderbuffer_height,
- const FieldsToDraw& fields_to_draw,
+ const FieldsToDrawGl2& fields_to_draw,
const float scale,
const float z_value) {
vertices_.clear();
uint32_t gl_texture = 0;
- for (size_t current_index = 0; current_index < fields_to_draw.size(); ++current_index) {
- const FieldsToDraw::Field& field = fields_to_draw.at(current_index);
+ for (auto cursor = fields_to_draw.cursor(); cursor.valid(); cursor.next()) {
+ const FieldToDrawGl2& field = cursor.field();
// Road to right neighbor.
- if (field.rn_index != FieldsToDraw::kInvalidIndex) {
+ if (cursor.rn_valid()) {
const Widelands::RoadType road =
static_cast<Widelands::RoadType>(field.roads & Widelands::RoadType::kMask);
if (road != Widelands::RoadType::kNone) {
- add_road(renderbuffer_width, renderbuffer_height, field,
- fields_to_draw.at(field.rn_index), scale, road, kEast, &gl_texture);
+ add_road(renderbuffer_width, renderbuffer_height, cursor.geometric_coords(), field,
+ cursor.rn(), scale, road, kEast, &gl_texture);
}
}
// Road to bottom right neighbor.
- if (field.brn_index != FieldsToDraw::kInvalidIndex) {
+ if (cursor.brn_valid()) {
const Widelands::RoadType road =
static_cast<Widelands::RoadType>((field.roads >> 2) & Widelands::RoadType::kMask);
if (road != Widelands::RoadType::kNone) {
- add_road(renderbuffer_width, renderbuffer_height, field,
- fields_to_draw.at(field.brn_index), scale, road, kSouthEast, &gl_texture);
+ add_road(renderbuffer_width, renderbuffer_height, cursor.geometric_coords(), field,
+ cursor.brn(), scale, road, kSouthEast, &gl_texture);
}
}
// Road to bottom right neighbor.
- if (field.bln_index != FieldsToDraw::kInvalidIndex) {
+ if (cursor.bln_valid()) {
const Widelands::RoadType road =
static_cast<Widelands::RoadType>((field.roads >> 4) & Widelands::RoadType::kMask);
if (road != Widelands::RoadType::kNone) {
- add_road(renderbuffer_width, renderbuffer_height, field,
- fields_to_draw.at(field.bln_index), scale, road, kSouthWest, &gl_texture);
+ add_road(renderbuffer_width, renderbuffer_height, cursor.geometric_coords(), field,
+ cursor.bln(), scale, road, kSouthWest, &gl_texture);
}
}
}
=== modified file 'src/graphic/gl/road_program.h'
--- src/graphic/gl/road_program.h 2016-11-03 07:20:57 +0000
+++ src/graphic/gl/road_program.h 2017-01-07 12:36:16 +0000
@@ -41,7 +41,7 @@
// space.
void draw(int renderbuffer_width,
int renderbuffer_height,
- const FieldsToDraw& fields_to_draw,
+ const FieldsToDrawGl2& fields_to_draw,
float scale,
float z_value);
@@ -60,8 +60,9 @@
enum Direction { kEast, kSouthEast, kSouthWest };
void add_road(int renderbuffer_width,
int renderbuffer_height,
- const FieldsToDraw::Field& start,
- const FieldsToDraw::Field& end,
+ Widelands::Coords geometric_coords,
+ const FieldToDrawGl2& start,
+ const FieldToDrawGl2& end,
float scale,
const Widelands::RoadType road_type,
const Direction direction,
=== added file 'src/graphic/gl/streaming_buffer.h'
--- src/graphic/gl/streaming_buffer.h 1970-01-01 00:00:00 +0000
+++ src/graphic/gl/streaming_buffer.h 2017-01-07 12:36:16 +0000
@@ -0,0 +1,173 @@
+/*
+ * Copyright 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef WL_GRAPHIC_GL_STREAMING_BUFFER_H
+#define WL_GRAPHIC_GL_STREAMING_BUFFER_H
+
+#include <cassert>
+
+#include "base/macros.h"
+#include "base/wexception.h"
+#include "graphic/gl/system_headers.h"
+
+namespace Gl {
+
+// Wrapper around an OpenGL buffer object that is intended for streaming use.
+//
+// Requires GL_ARB_direct_state_access.
+template <typename T> class StreamingBuffer {
+public:
+ class Inserter {
+ public:
+ Inserter(Inserter&& o)
+ : buffer_(o.buffer_), map_(o.map_), count_(o.count_), max_(o.max_) {
+ o.buffer_ = nullptr;
+ o.map_ = nullptr;
+ }
+
+ ~Inserter() {
+ assert(!buffer_ || buffer_->inserting_);
+ if (buffer_)
+ buffer_->inserting_ = false;
+
+ if (map_) {
+ buffer_->bind();
+ glUnmapBuffer(buffer_->target());
+ }
+ }
+
+ // The number of elements that have already been inserted.
+ size_t count() const {
+ return count_;
+ }
+
+ // The maximum number of elements that can be inserted.
+ size_t max() const {
+ return max_;
+ }
+
+ // Finish inserting. Return the buffer offset for the beginning of
+ // the inserted range, as must be passed to OpenGL.
+ GLintptr unmap() {
+ assert(map_);
+ buffer_->bind();
+ glUnmapBuffer(buffer_->target());
+ map_ = nullptr;
+ return 0;
+ }
+
+ // Append an item to the buffer.
+ template<typename... Args>
+ void emplace_back(Args&&... args) {
+ assert(map_);
+ assert(count_ < max_);
+
+ new(&map_[count_]) T(std::forward<Args>(args)...);
+ count_++;
+ }
+
+ // Append space for count items, return a pointer to the first one.
+ T* add(size_t count) {
+ assert(count <= max_ && count_ <= max_ - count);
+ T* ret = &map_[count_];
+ count_ += count;
+ return ret;
+ }
+
+ T& back() {
+ assert(map_ && count_ >= 1);
+ return map_[count_ - 1];
+ }
+
+ private:
+ friend class StreamingBuffer;
+
+ Inserter(StreamingBuffer& buffer, size_t max)
+ : buffer_(&buffer), map_(nullptr), count_(0), max_(max)
+ {
+ assert(!buffer_->inserting_);
+ buffer_->inserting_ = true;
+
+ buffer_->bind();
+ map_ = reinterpret_cast<T*>(glMapBuffer(buffer_->target(), GL_WRITE_ONLY));
+ if (!map_)
+ throw wexception("Could not map GL buffer.");
+ }
+
+ StreamingBuffer* buffer_;
+ T* map_;
+ size_t count_;
+ size_t max_;
+
+ DISALLOW_COPY_AND_ASSIGN(Inserter);
+ };
+
+ StreamingBuffer(GLenum target) {
+ target_ = target;
+ glGenBuffers(1, &object_);
+ if (!object_) {
+ throw wexception("Could not create GL buffer.");
+ }
+ }
+
+ ~StreamingBuffer() {
+ assert(!inserting_);
+
+ if (object_) {
+ glDeleteBuffers(1, &object_);
+ }
+ }
+
+ // Returns the OpenGL object for direct use.
+ GLuint object() const {
+ return object_;
+ }
+
+ GLenum target() const {
+ return target_;
+ }
+
+ void bind() const {
+ glBindBuffer(target_, object_);
+ }
+
+ // Set the buffer up for streaming up to the given number of elements.
+ //
+ // Previous contents of the buffer are discarded (this does not affect
+ // OpenGL functions that have been called previously).
+ Inserter stream(size_t max) {
+ // Always re-allocate the buffer. We rely on fast swap-out by the
+ // driver. If backing store were to be shared globally, it might make
+ // sense to consider an alternative scheme using unsynchronized maps
+ // and explicit flushing.
+ glBindBuffer(target_, object_);
+ glBufferData(target_, sizeof(T) * max, NULL, GL_STREAM_DRAW);
+ return Inserter(*this, max);
+ }
+
+private:
+ GLenum target_;
+ GLuint object_;
+ bool inserting_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(StreamingBuffer);
+};
+
+} // namespace Gl
+
+#endif // end of include guard: WL_GRAPHIC_GL_STREAMING_BUFFER_H
=== modified file 'src/graphic/gl/terrain_program.cc'
--- src/graphic/gl/terrain_program.cc 2016-11-03 07:20:57 +0000
+++ src/graphic/gl/terrain_program.cc 2017-01-07 12:36:16 +0000
@@ -30,6 +30,8 @@
// http://www.opengl.org/registry/doc/GLSLangSpec.Full.1.20.8.pdf
// We target OpenGL 2.1 for the desktop here.
TerrainProgram::TerrainProgram() {
+ log("Using GL2 terrain rendering path\n");
+
gl_program_.build("terrain");
attr_brightness_ = glGetAttribLocation(gl_program_.object(), "attr_brightness");
@@ -70,7 +72,7 @@
glDrawArrays(GL_TRIANGLES, 0, vertices_.size());
}
-void TerrainProgram::add_vertex(const FieldsToDraw::Field& field, const Vector2f& texture_offset) {
+void TerrainProgram::add_vertex(const FieldToDrawGl2& field, const Vector2f& texture_offset) {
vertices_.emplace_back();
PerVertexData& back = vertices_.back();
@@ -85,7 +87,7 @@
void TerrainProgram::draw(uint32_t gametime,
const DescriptionMaintainer<Widelands::TerrainDescription>& terrains,
- const FieldsToDraw& fields_to_draw,
+ const FieldsToDrawGl2& fields_to_draw,
float z_value) {
// This method expects that all terrains have the same dimensions and that
// all are packed into the same texture atlas, i.e. all are in the same GL
@@ -94,36 +96,36 @@
vertices_.clear();
vertices_.reserve(fields_to_draw.size() * 3);
- for (size_t current_index = 0; current_index < fields_to_draw.size(); ++current_index) {
- const FieldsToDraw::Field& field = fields_to_draw.at(current_index);
+ for (auto cursor = fields_to_draw.cursor(); cursor.valid(); cursor.next()) {
+ const FieldToDrawGl2& field = cursor.field();
// The bottom right neighbor fields_to_draw is needed for both triangles
// associated with this field. If it is not in fields_to_draw, there is no need to
// draw any triangles.
- if (field.brn_index == FieldsToDraw::kInvalidIndex) {
+ if (!cursor.brn_valid()) {
continue;
}
// Down triangle.
- if (field.bln_index != FieldsToDraw::kInvalidIndex) {
+ if (cursor.bln_valid()) {
const Vector2f texture_offset =
to_gl_texture(
terrains.get(field.fcoords.field->terrain_d()).get_texture(gametime).blit_data())
.origin();
- add_vertex(fields_to_draw.at(current_index), texture_offset);
- add_vertex(fields_to_draw.at(field.bln_index), texture_offset);
- add_vertex(fields_to_draw.at(field.brn_index), texture_offset);
+ add_vertex(field, texture_offset);
+ add_vertex(cursor.bln(), texture_offset);
+ add_vertex(cursor.brn(), texture_offset);
}
// Right triangle.
- if (field.rn_index != FieldsToDraw::kInvalidIndex) {
+ if (cursor.rn_valid()) {
const Vector2f texture_offset =
to_gl_texture(
terrains.get(field.fcoords.field->terrain_r()).get_texture(gametime).blit_data())
.origin();
- add_vertex(fields_to_draw.at(current_index), texture_offset);
- add_vertex(fields_to_draw.at(field.brn_index), texture_offset);
- add_vertex(fields_to_draw.at(field.rn_index), texture_offset);
+ add_vertex(field, texture_offset);
+ add_vertex(cursor.brn(), texture_offset);
+ add_vertex(cursor.rn(), texture_offset);
}
}
=== modified file 'src/graphic/gl/terrain_program.h'
--- src/graphic/gl/terrain_program.h 2016-10-16 09:31:42 +0000
+++ src/graphic/gl/terrain_program.h 2017-01-07 12:36:16 +0000
@@ -36,7 +36,7 @@
// Draws the terrain.
void draw(uint32_t gametime,
const DescriptionMaintainer<Widelands::TerrainDescription>& terrains,
- const FieldsToDraw& fields_to_draw,
+ const FieldsToDrawGl2& fields_to_draw,
float z_value);
private:
@@ -54,7 +54,7 @@
void gl_draw(int gl_texture, float texture_w, float texture_h, float z_value);
// Adds a vertex to the end of vertices with data from 'field' and 'texture_coordinates'.
- void add_vertex(const FieldsToDraw::Field& field, const Vector2f& texture_coordinates);
+ void add_vertex(const FieldToDrawGl2& field, const Vector2f& texture_coordinates);
// The program used for drawing the terrain.
Gl::Program gl_program_;
=== added file 'src/graphic/gl/terrain_program_gl4.cc'
--- src/graphic/gl/terrain_program_gl4.cc 1970-01-01 00:00:00 +0000
+++ src/graphic/gl/terrain_program_gl4.cc 2017-01-07 12:36:16 +0000
@@ -0,0 +1,1065 @@
+/*
+ * 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.
+ *
+ */
+
+#include "graphic/gl/terrain_program_gl4.h"
+
+#include "graphic/game_renderer_gl4.h"
+#include "graphic/gl/coordinate_conversion.h"
+#include "graphic/gl/utils.h"
+#include "graphic/image_io.h"
+#include "io/filesystem/layered_filesystem.h"
+#include "logic/editor_game_base.h"
+#include "logic/map.h"
+#include "logic/map_objects/tribes/tribe_descr.h"
+#include "logic/map_objects/world/terrain_description.h"
+#include "logic/map_objects/world/world.h"
+#include "profile/profile.h"
+#include "wui/mapviewpixelfunctions.h"
+
+using namespace Widelands;
+
+/**
+ * This is the back-end of the GL4 rendering path.
+ *
+ * Per-field data is uploaded directly into integer-valued textures that span
+ * the entire map, and vertex shaders do most of the heavy lifting. The
+ * following textures are used:
+ *
+ * Fields texture: data that is usually constant throughout a game, GL_RGBA8UI:
+ * R = r-triangle texture index
+ * G = d-triangle texture index
+ * B = height
+ * A = brightness
+ * This information can be modified in the editor and in scenarios. Note that
+ * the triangle textures depend on the player perspective.
+ *
+ * Player brightness texture:
+ * R = player-perspective-dependent brightness modulation
+ * This information is re-uploaded every frame.
+ *
+ * Semi-permanent information (GL_R8UI):
+ * R = bits 0..5: field ownership (player number)
+ * bits 6..7: road/flag/building data:
+ * 0: nothing, 1: road, 2: flag, 3: building
+ * This information is only needed for the minimap, and re-uploaded every frame
+ * when it is shown.
+ *
+ * Terrain is rendered in patches of a fixed structure, and many patches are
+ * rendered in one call via instancing. Per-instance data is processed in a
+ * vertex shader.
+ *
+ * Each patch consists of the triangles associated to a WxH "rectangle" of
+ * fields, where the top-left field must be at an even y-coordinate, and H is
+ * even, e.g. a 2x4-patch:
+ *
+ * (0,0)
+ * O-------O-------*
+ * / \ / \ /
+ * / \ / \ /
+ * / \ / \ /
+ * *-------O-------O-------*
+ * / \ / \ /
+ * / \ / \ /
+ * / \ / \ /
+ * O-------O-------*
+ * / \ / \ /
+ * / \ / \ /
+ * / \ / \ /
+ * *-------O-------O-------*
+ * / \ / \ /
+ * / \ / \ /
+ * / \ / \ /
+ * *-------*-------*
+ *
+ * OpenGL vertices of triangles are not shared; this allows separate textures
+ * and dithering in a single pass.
+ *
+ * Road rendering is also handled here. Roads are rendered as two triangles per
+ * segment. Only per-road data is uploaded; the vertex shader sources data
+ * from the per-road buffer based on the vertex ID, and an index buffer
+ * (element array buffer in OpenGL terms) is used to share two vertices between
+ * the triangles that make up each segment.
+ */
+
+TerrainInformationGl4::GlobalMap TerrainInformationGl4::global_map_;
+
+std::shared_ptr<TerrainInformationGl4>
+TerrainInformationGl4::get(const Widelands::EditorGameBase& egbase,
+ const Widelands::Player* player) {
+ GlobalKey key(&egbase, player);
+ auto it = global_map_.find(key);
+ if (it != global_map_.end())
+ return it->second.lock();
+
+ std::shared_ptr<TerrainInformationGl4> instance(
+ new TerrainInformationGl4(egbase, player));
+ global_map_[key] = instance;
+ return instance;
+}
+
+TerrainInformationGl4::TerrainInformationGl4(const Widelands::EditorGameBase& egbase,
+ const Widelands::Player* player)
+ : egbase_(egbase), player_(player), uploads_(GL_PIXEL_UNPACK_BUFFER) {
+ glGenTextures(1, &brightness_texture_);
+ glGenTextures(1, &fields_texture_);
+ glGenTextures(1, &minimap_texture_);
+
+ const Map& map = egbase.map();
+ auto& gl = Gl::State::instance();
+ gl.bind(GL_TEXTURE0, fields_texture_);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ gl.bind(GL_TEXTURE0, brightness_texture_);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, map.get_width(), map.get_height(), 0,
+ GL_RED, GL_UNSIGNED_BYTE, NULL);
+ brightness_see_all_ = false;
+
+ gl.bind(GL_TEXTURE0, minimap_texture_);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_R8UI, map.get_width(), map.get_height(), 0,
+ GL_RED_INTEGER, GL_UNSIGNED_BYTE, NULL);
+
+ fields_update();
+ upload_road_textures();
+ upload_constant_textures();
+
+ updated_minimap_ = false;
+ need_update_minimap_ = false;
+ minimap_update_next_ = 0;
+}
+
+TerrainInformationGl4::~TerrainInformationGl4() {
+ if (brightness_texture_)
+ glDeleteTextures(1, &brightness_texture_);
+
+ if (fields_texture_)
+ glDeleteTextures(1, &fields_texture_);
+
+ if (minimap_texture_)
+ glDeleteTextures(1, &minimap_texture_);
+
+ if (terrain_color_texture_)
+ glDeleteTextures(1, &terrain_color_texture_);
+
+ global_map_.erase(GlobalKey(&egbase_, player_));
+}
+
+/// Add @p rect to @p rects, merging it with any overlapping or touching
+/// pre-existing rects (where merging means that the rectangles are replaced by
+/// a single rectangle that contains their union). The order of rectangles in
+/// @p rects is not preserved.
+///
+/// Rectangles are interpreted as half-open.
+static void add_rect(std::vector<Recti>& rects, const Recti& rect) {
+ Recti new_rect = rect;
+
+ for (size_t i = 0; i < rects.size(); ++i) {
+ // Merge rectangles even if they only touch instead of fully overlapping.
+ // The rationale is that reducing the number of uploads is often a
+ // benefit even when the total size of uploads becomes larger.
+ if (new_rect.x + new_rect.w < rects[i].x ||
+ rects[i].x + rects[i].w < new_rect.x ||
+ new_rect.y + new_rect.h < rects[i].y ||
+ rects[i].y + rects[i].h < new_rect.y)
+ continue;
+
+ rects[i] = rects.back();
+ rects.pop_back();
+ i--;
+ }
+
+ rects.push_back(new_rect);
+}
+
+void TerrainInformationGl4::update(int minfx, int maxfx, int minfy, int maxfy) {
+ const Map& map = egbase().map();
+ int width = map.get_width();
+ int height = map.get_height();
+
+ auto normalize = [](int& min, int& max, int size) {
+ while (min < 0) {
+ min += size;
+ max += size;
+ }
+ while (min >= size) {
+ min -= size;
+ max -= size;
+ }
+ };
+
+ normalize(minfx, maxfx, width);
+ normalize(minfy, maxfy, height);
+
+ // Ensure proper row alignment during texture uploads.
+ minfx = (minfx / 4) * 4;
+ maxfx = ((maxfx + 4) / 4) * 4 - 1;
+
+ auto add = [&](int startx, int endx) {
+ add_rect(update_, Recti(startx, minfy, endx - startx, std::min(maxfy + 1, height) - minfy));
+ if (maxfy >= height)
+ add_rect(update_, Recti(startx, 0, endx - startx, std::min(maxfy + 1 - height, height)));
+ };
+
+ add(minfx, std::min(maxfx + 1, width));
+ if (maxfx >= width)
+ add(0, std::min(maxfx + 1 - width, width));
+}
+
+void TerrainInformationGl4::update_minimap() {
+ need_update_minimap_ = true;
+}
+
+void TerrainInformationGl4::do_prepare_frame() {
+ const Map& map = egbase().map();
+
+ upload_terrain_data();
+
+ if (need_update_minimap_) {
+ if (!updated_minimap_) {
+ // Need a full update when the minimap is drawn for the first
+ // time.
+ update_.clear();
+ update_.emplace_back(0, 0, map.get_width(), map.get_height());
+ } else {
+ // For the minimap, we want to do rolling texture updates of
+ // stripes that cover the whole width or height of the map,
+ // depending on which is smaller. For consistency and simplicity,
+ // expand all other dirty rectangles to full strips as well.
+ //
+ // Furthermore, use stripes of a size that is a multiple of a small
+ // power of two, since that likely has bandwidth benefits due to
+ // how textures are laid out in memory. This also avoids confusion
+ // due to pixel (un)packing row alignments.
+ unsigned width = map.get_width();
+ unsigned height = map.get_height();
+ bool horiz = width <= height;
+ std::vector<std::pair<unsigned, unsigned>> stripes;
+
+ // Massage existing stripes, effectively a form of insertion sort.
+ stripes.reserve(update_.size() + 1);
+ for (size_t i = 0; i < update_.size(); ++i) {
+ unsigned min = horiz ? update_[i].y : update_[i].x;
+ unsigned max = min + (horiz ? update_[i].h : update_[i].w);
+
+ min = (min / 8) * 8;
+ max = ((max + 7) / 8) * 8;
+
+ size_t j;
+ for (j = 0; j < stripes.size(); ++j) {
+ if (max < stripes[j].first) {
+ stripes.insert(stripes.begin() + j, std::make_pair(min, max));
+ break;
+ }
+
+ if (min <= stripes[j].second) {
+ stripes[j].first = std::min(stripes[j].first, min);
+ stripes[j].second = std::max(stripes[j].second, max);
+
+ size_t k;
+ for (k = j + 1; k < stripes.size(); ++k) {
+ if (stripes[j].second < stripes[k].first)
+ break;
+
+ stripes[j].second = std::max(stripes[j].second, stripes[k].second);
+ }
+
+ stripes.erase(stripes.begin() + j + 1, stripes.begin() + k);
+ break;
+ }
+ }
+ if (j >= stripes.size())
+ stripes.emplace_back(min, max);
+ }
+
+ if (stripes.empty() || stripes[0].first != 0 ||
+ stripes[0].second < (horiz ? height : width)) {
+ // Add a stripe (or expand an existing one) for the rolling minimap
+ // update.
+ if (minimap_update_next_ >= (horiz ? height : width))
+ minimap_update_next_ = 0;
+
+ unsigned min = minimap_update_next_;
+ size_t j;
+ for (j = 0; j < stripes.size(); ++j) {
+ unsigned max = min + 8;
+ if (max < stripes[j].first) {
+ stripes.insert(stripes.begin() + j, std::make_pair(min, max));
+ break;
+ }
+
+ if (min <= stripes[j].second) {
+ if (min < stripes[j].first) {
+ assert(max == stripes[j].first); // due to multiples of 8
+ stripes[j].first = min;
+ break;
+ }
+
+ min = minimap_update_next_ = stripes[j].second;
+ if (min >= (horiz ? height : width)) {
+ min = 0;
+ j = 0;
+ continue;
+ }
+ max = std::min(min + 8, horiz ? height : width);
+ stripes[j].second = max;
+
+ size_t k;
+ for (k = j + 1; k < stripes.size(); ++k) {
+ if (stripes[j].second < stripes[k].first)
+ break;
+
+ stripes[j].second = std::max(stripes[j].second, stripes[k].second);
+ }
+ stripes.erase(stripes.begin() + j + 1, stripes.begin() + k);
+ break;
+ }
+ }
+ if (j >= stripes.size())
+ stripes.emplace_back(min, min + 8);
+
+ minimap_update_next_ += 8;
+ }
+
+ // Convert stripes back to update rectangles.
+ update_.resize(stripes.size());
+ for (size_t i = 0; i < stripes.size(); ++i) {
+ if (horiz) {
+ update_[i].x = 0;
+ update_[i].w = width;
+ update_[i].y = stripes[i].first;
+ update_[i].h = stripes[i].second - stripes[i].first;
+ } else {
+ update_[i].y = 0;
+ update_[i].h = height;
+ update_[i].x = stripes[i].first;
+ update_[i].w = stripes[i].second - stripes[i].first;
+ }
+ }
+ }
+ }
+
+ // Fields data updates are guarded by version numbers instead of
+ // rectangles.
+ if (fields_base_version_ != map.get_fields_base_version() ||
+ (player() && terrain_vision_version_ != player()->get_terrain_vision_version()))
+ fields_update();
+
+ brightness_update();
+
+ if (need_update_minimap_)
+ do_update_minimap();
+
+ update_.clear();
+ updated_minimap_ = need_update_minimap_;
+ need_update_minimap_ = false;
+}
+
+void TerrainInformationGl4::prepare_frame() {
+ for (auto& entries : global_map_) {
+ std::shared_ptr<TerrainInformationGl4> ti = entries.second.lock();
+
+ ti->do_prepare_frame();
+ }
+}
+
+void TerrainInformationGl4::do_update_minimap() {
+ // Re-upload minimap data.
+ auto& gl = Gl::State::instance();
+ const Map& map = egbase().map();
+ unsigned width = map.get_width();
+ std::vector<uint8_t> data;
+ const bool see_all = !player() || player()->see_all();
+
+ auto detail_bits = [&](const Widelands::BaseImmovable* imm) -> uint8_t {
+ if (imm) {
+ Widelands::MapObjectType type = imm->descr().type();
+ if (type == Widelands::MapObjectType::ROAD)
+ return 1u << 6;
+ if (type == Widelands::MapObjectType::FLAG)
+ return 2u << 6;
+ if (type >= Widelands::MapObjectType::BUILDING)
+ return 3u << 6;
+ }
+ return 0;
+ };
+
+ gl.bind(GL_TEXTURE0, minimap_texture_);
+
+ for (const Recti& rect : update_) {
+ data.resize(rect.w * rect.h);
+ if (see_all) {
+ unsigned i = 0;
+ for (unsigned y = 0; y < unsigned(rect.h); ++y) {
+ unsigned idx = (rect.y + y) * width + rect.x;
+ for (unsigned x = 0; x < unsigned(rect.w); ++x, ++i, ++idx) {
+ const Field& f = map[idx];
+ data[i] = f.get_owned_by();
+ data[i] |= detail_bits(f.get_immovable());
+ }
+ }
+ } else {
+ unsigned i = 0;
+ for (unsigned y = 0; y < unsigned(rect.h); ++y) {
+ unsigned idx = (rect.y + y) * width + rect.x;
+ for (unsigned x = 0; x < unsigned(rect.w); ++x, ++i, ++idx) {
+ const Player::Field& pf = player()->fields()[idx];
+ data[i] = pf.owner;
+
+ if (pf.vision >= 2) {
+ const Field& f = map[idx];
+ data[i] |= detail_bits(f.get_immovable());
+ }
+ }
+ }
+ }
+
+ glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x, rect.y, rect.w, rect.h,
+ GL_RED_INTEGER, GL_UNSIGNED_BYTE, data.data());
+ }
+}
+
+void TerrainInformationGl4::fields_update() {
+ auto& gl = Gl::State::instance();
+ const Map& map = egbase().map();
+ auto stream = uploads_.stream(sizeof(PerFieldData) * uint(map.get_width()) * map.get_height());
+ PerFieldData* fd =
+ reinterpret_cast<PerFieldData*>
+ (stream.add(sizeof(PerFieldData) * uint(map.get_width()) * map.get_height()));
+ MapIndex max_index = map.max_index();
+ const bool see_all = !player() || player()->see_all();
+
+ if (see_all) {
+ for (MapIndex i = 0; i < max_index; ++i) {
+ const Field& f = map[i];
+ fd[i].terrain_r = f.terrain_r();
+ fd[i].terrain_d = f.terrain_d();
+ fd[i].height = f.get_height();
+ fd[i].brightness = f.get_brightness();
+ }
+ } else {
+ const Player::Field* player_fields = player()->fields();
+
+ for (MapIndex i = 0; i < max_index; ++i) {
+ const Field& f = map[i];
+ const Player::Field& pf = player_fields[i];
+ fd[i].terrain_r = pf.terrains.r;
+ fd[i].terrain_d = pf.terrains.d;
+ fd[i].height = f.get_height();
+ fd[i].brightness = f.get_brightness();
+ }
+ }
+
+ GLintptr offset = stream.unmap();
+ uploads_.bind();
+
+ gl.bind(GL_TEXTURE0, fields_texture_);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8UI, map.get_width(), map.get_height(), 0,
+ GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, reinterpret_cast<void*>(offset));
+
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+
+ fields_base_version_ = map.get_fields_base_version();
+ if (player())
+ terrain_vision_version_ = player()->get_terrain_vision_version();
+}
+
+TerrainInformationGl4::PerRoadTextureData::PerRoadTextureData(const Rectf& rect)
+ : x(rect.x), y(rect.y), w(rect.w), h(rect.h) {
+}
+
+void TerrainInformationGl4::upload_road_textures() {
+ std::vector<PerRoadTextureData> roads;
+ std::map<const TribeDescr*, unsigned> tribe_map;
+ PlayerNumber const nr_players = egbase().map().get_nrplayers();
+
+ player_roads_.resize(nr_players + 1);
+
+ iterate_players_existing_const(p, nr_players, egbase(), player) {
+ const TribeDescr& tribe = player->tribe();
+ auto it = tribe_map.find(&tribe);
+ if (it != tribe_map.end()) {
+ player_roads_[p] = player_roads_[it->second];
+ } else {
+ const auto& normal_textures = tribe.road_textures().get_normal_textures();
+ player_roads_[p].normal_roads = roads.size();
+ player_roads_[p].num_normal_roads = normal_textures.size();
+ for (const Image* image : normal_textures) {
+ const BlitData& blit_data = image->blit_data();
+ roads.emplace_back(to_gl_texture(blit_data));
+ road_texture_object_ = blit_data.texture_id;
+ }
+
+ const auto& busy_textures = tribe.road_textures().get_busy_textures();
+ player_roads_[p].busy_roads = roads.size();
+ player_roads_[p].num_busy_roads = busy_textures.size();
+ for (const Image* image : busy_textures)
+ roads.emplace_back(to_gl_texture(image->blit_data()));
+
+ tribe_map[&tribe] = p;
+ }
+ }
+
+ road_textures_.bind();
+ road_textures_.update(roads);
+}
+
+unsigned TerrainInformationGl4::road_texture_idx(PlayerNumber owner,
+ RoadType road_type,
+ const Coords& coords,
+ WalkingDir direction) const {
+ const PlayerRoads& roads = player_roads_[owner];
+ unsigned base, count;
+
+ if (road_type == RoadType::kNormal) {
+ base = roads.normal_roads;
+ count = roads.num_normal_roads;
+ } else {
+ base = roads.busy_roads;
+ count = roads.num_busy_roads;
+ }
+
+ return base + unsigned(coords.x + coords.y + direction) % count;
+}
+
+// Upload the per-terrain texture data. This is done on every draw call because
+// it depends on the gametime.
+void TerrainInformationGl4::upload_terrain_data() {
+ uint32_t gametime = egbase().get_gametime();
+ const auto& terrains = egbase().world().terrains();
+ std::vector<PerTerrainData> data;
+
+ data.resize(terrains.size());
+
+ for (unsigned i = 0; i < terrains.size(); ++i) {
+ PerTerrainData& terrain = data[i];
+ const TerrainDescription& descr = terrains.get(i);
+ terrain.offset =
+ to_gl_texture(descr.get_texture(gametime).blit_data()).origin();
+ terrain.dither_layer = descr.dither_layer();
+ }
+
+ terrain_data_.bind();
+ terrain_data_.update(data);
+}
+
+void TerrainInformationGl4::brightness_update() {
+ auto& gl = Gl::State::instance();
+ bool see_all = !player_ || player_->see_all();
+
+ gl.bind(GL_TEXTURE0, brightness_texture_);
+
+ if (see_all) {
+ if (!brightness_see_all_) {
+ // Pixel unpacking has a per-row alignment of 4 bytes. Usually this
+ // is not a problem for us, because maps' widths are always multiples
+ // of 4, but in this particular case, OpenGL implementations disagree
+ // about whether the alignment should be considered for the bounds
+ // check in glTexImage2D. If we only allocate 1 byte, some
+ // implementations flag a GL_INVALID_OPERATION.
+ static const uint8_t data[4] = {255, 255, 255, 255};
+
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE,
+ data);
+ brightness_see_all_ = true;
+ }
+ } else {
+ const Map& map = egbase().map();
+ int width = map.get_width();
+ int height = map.get_height();
+ uint32_t gametime = egbase().get_gametime();
+ std::vector<uint8_t> data;
+
+ if (brightness_see_all_) {
+ // Resize the texture when switching between see-all and not-see-all.
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED,
+ GL_UNSIGNED_BYTE, NULL);
+ brightness_see_all_ = false;
+ }
+
+ for (const Recti& rect : update_) {
+ data.resize(rect.w * rect.h);
+
+ unsigned dst = 0;
+ for (unsigned y = 0; y < unsigned(rect.h); ++y) {
+ unsigned src = (rect.y + y) * width + rect.x;
+ for (unsigned x = 0; x < unsigned(rect.w); ++x, ++src, ++dst) {
+ const Player::Field& pf = player_->fields()[src];
+ if (pf.vision == 0) {
+ data[dst] = 0;
+ } else if (pf.vision == 1) {
+ static const uint32_t kDecayTimeInMs = 20000;
+ const Duration time_ago = gametime - pf.time_node_last_unseen;
+ if (time_ago < kDecayTimeInMs) {
+ data[dst] = 255 * (2 * kDecayTimeInMs - time_ago) / (2 * kDecayTimeInMs);
+ } else {
+ data[dst] = 128;
+ }
+ } else {
+ data[dst] = 255;
+ }
+ }
+ }
+
+ glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x, rect.y, rect.w, rect.h,
+ GL_RED, GL_UNSIGNED_BYTE, &data[0]);
+ }
+ }
+}
+
+void TerrainInformationGl4::upload_constant_textures() {
+ auto& gl = Gl::State::instance();
+ const auto& terrains = egbase().world().terrains();
+ std::vector<RGBColor> colors;
+
+ for (Widelands::DescriptionIndex i = 0; i < terrains.size(); ++i)
+ colors.push_back(terrains.get(i).get_minimap_color(0));
+
+ glGenTextures(1, &terrain_color_texture_);
+
+ gl.bind(GL_TEXTURE0, terrain_color_texture_);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, colors.size(), 1, 0, GL_RGB, GL_UNSIGNED_BYTE,
+ colors.data());
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ colors.resize(kMaxPlayers);
+ for (int i = 1; i <= kMaxPlayers; ++i) {
+ const Widelands::Player* player = egbase().get_player(i);
+ if (player)
+ colors[i - 1] = player->get_playercolor();
+ }
+
+ glGenTextures(1, &player_color_texture_);
+
+ gl.bind(GL_TEXTURE0, player_color_texture_);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, colors.size(), 1, 0, GL_RGB, GL_UNSIGNED_BYTE,
+ colors.data());
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+}
+
+TerrainProgramGl4::Terrain::Terrain()
+ : instance_data(GL_ARRAY_BUFFER) {
+ // Initialize program.
+ gl_program.build_vp_fp({"terrain_gl4", "terrain_common_gl4"}, {"terrain_gl4"});
+
+ in_vertex_coordinate = glGetAttribLocation(gl_program.object(), "in_vertex_coordinate");
+ in_patch_coordinate = glGetAttribLocation(gl_program.object(), "in_patch_coordinate");
+
+ u_position_scale = glGetUniformLocation(gl_program.object(), "u_position_scale");
+ u_position_offset = glGetUniformLocation(gl_program.object(), "u_position_offset");
+ u_z_value = glGetUniformLocation(gl_program.object(), "u_z_value");
+ u_texture_dimensions = glGetUniformLocation(gl_program.object(), "u_texture_dimensions");
+
+ u_terrain_base = glGetUniformLocation(gl_program.object(), "u_terrain_base");
+ u_player_brightness = glGetUniformLocation(gl_program.object(), "u_player_brightness");
+ u_terrain_texture = glGetUniformLocation(gl_program.object(), "u_terrain_texture");
+ u_dither_texture = glGetUniformLocation(gl_program.object(), "u_dither_texture");
+
+ block_terrains_idx = glGetUniformBlockIndex(gl_program.object(), "block_terrains");
+}
+
+TerrainProgramGl4::Terrain::~Terrain() {
+}
+
+TerrainProgramGl4::Roads::Roads()
+ : road_data(GL_SHADER_STORAGE_BUFFER) {
+ num_index_roads = 0;
+
+ // Initialize program.
+ gl_program.build_vp_fp({"road_gl4", "terrain_common_gl4"}, {"road"});
+
+ u_position_scale = glGetUniformLocation(gl_program.object(), "u_position_scale");
+ u_position_offset = glGetUniformLocation(gl_program.object(), "u_position_offset");
+ u_z_value = glGetUniformLocation(gl_program.object(), "u_z_value");
+
+ u_terrain_base = glGetUniformLocation(gl_program.object(), "u_terrain_base");
+ u_player_brightness = glGetUniformLocation(gl_program.object(), "u_player_brightness");
+ u_texture = glGetUniformLocation(gl_program.object(), "u_texture");
+
+ block_textures_idx = glGetUniformBlockIndex(gl_program.object(), "block_textures");
+}
+
+TerrainProgramGl4::Roads::~Roads() {
+}
+
+TerrainProgramGl4::MiniMap::MiniMap()
+ : vertex_data(GL_ARRAY_BUFFER) {
+ gl_program.build_vp_fp({"minimap_gl4"}, {"minimap_gl4"});
+
+ in_position = glGetAttribLocation(gl_program.object(), "in_position");
+ in_field = glGetAttribLocation(gl_program.object(), "in_field");
+
+ u_layer_terrain = glGetUniformLocation(gl_program.object(), "u_layer_terrain");
+ u_layer_owner = glGetUniformLocation(gl_program.object(), "u_layer_owner");
+ u_layer_details = glGetUniformLocation(gl_program.object(), "u_layer_details");
+
+ u_frame_topleft = glGetUniformLocation(gl_program.object(), "u_frame_topleft");
+ u_frame_bottomright = glGetUniformLocation(gl_program.object(), "u_frame_bottomright");
+
+ u_terrain_base = glGetUniformLocation(gl_program.object(), "u_terrain_base");
+ u_player_brightness = glGetUniformLocation(gl_program.object(), "u_player_brightness");
+ u_minimap_extra = glGetUniformLocation(gl_program.object(), "u_minimap_extra");
+ u_terrain_color = glGetUniformLocation(gl_program.object(), "u_terrain_color");
+ u_player_color = glGetUniformLocation(gl_program.object(), "u_player_color");
+}
+
+TerrainProgramGl4::MiniMap::~MiniMap() {
+}
+
+TerrainProgramGl4::TerrainProgramGl4() {
+ log("Using GL4 terrain rendering path\n");
+
+ // Initialize vertex buffer (every instance/path has the same structure).
+ init_vertex_data();
+
+ // Load mask texture for dithering.
+ terrain_.dither_mask.reset(new Texture(load_image_as_sdl_surface("world/pics/edge.png", g_fs), true));
+
+ Gl::State::instance().bind(GL_TEXTURE0, terrain_.dither_mask->blit_data().texture_id);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, static_cast<GLint>(GL_CLAMP_TO_EDGE));
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, static_cast<GLint>(GL_CLAMP_TO_EDGE));
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, static_cast<GLint>(GL_LINEAR));
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, static_cast<GLint>(GL_LINEAR));
+}
+
+TerrainProgramGl4::~TerrainProgramGl4() {
+}
+
+bool TerrainProgramGl4::supported() {
+ const auto& caps = Gl::State::instance().capabilities();
+
+ if (caps.glsl_version < 130)
+ return false;
+
+ if (!caps.ARB_separate_shader_objects ||
+ !caps.ARB_shader_storage_buffer_object ||
+ !caps.ARB_uniform_buffer_object)
+ return false;
+
+ return !g_options.pull_section("global").get_bool("disable_gl4", false);
+}
+
+void TerrainProgramGl4::draw(const TerrainGl4Arguments* args,
+ float z_value) {
+ auto& gl = Gl::State::instance();
+
+ // First, draw the terrain.
+ glUseProgram(terrain_.gl_program.object());
+
+ // Coordinate transform from map coordinates to GL coordinates.
+ float scale_x = 2.0 / args->surface_width * args->scale;
+ float scale_y = -2.0 / args->surface_height * args->scale;
+ float offset_x = args->surface_offset.x * scale_x - 1.0;
+ float offset_y = args->surface_offset.y * scale_y + 1.0;
+
+ // Texture size
+ const BlitData& blit_data = args->terrain->egbase().world().terrains().get(0).get_texture(0).blit_data();
+ const Rectf texture_coordinates = to_gl_texture(blit_data);
+
+ // Prepare uniforms.
+ glBindBufferBase(GL_UNIFORM_BUFFER, terrain_.block_terrains_idx,
+ args->terrain->terrain_data_buffer_object());
+
+ glUniform2f(terrain_.u_position_scale, scale_x, scale_y);
+ glUniform2f(terrain_.u_position_offset, offset_x, offset_y);
+ glUniform1f(terrain_.u_z_value, z_value);
+ glUniform2f(terrain_.u_texture_dimensions, texture_coordinates.w, texture_coordinates.h);
+
+ // Prepare textures & sampler uniforms.
+ glUniform1i(terrain_.u_terrain_base, 0);
+ gl.bind(GL_TEXTURE0, args->terrain->fields_texture());
+
+ glUniform1i(terrain_.u_player_brightness, 1);
+ gl.bind(GL_TEXTURE1, args->terrain->player_brightness_texture());
+
+ glUniform1i(terrain_.u_terrain_texture, 2);
+ gl.bind(GL_TEXTURE2, blit_data.texture_id);
+
+ glUniform1i(terrain_.u_dither_texture, 3);
+ gl.bind(GL_TEXTURE3, terrain_.dither_mask->blit_data().texture_id);
+
+ // Setup vertex and instance attribute data.
+ gl.enable_vertex_attrib_array(
+ {terrain_.in_vertex_coordinate, terrain_.in_patch_coordinate});
+
+ unsigned num_instances = upload_instance_data(args);
+
+ terrain_.vertex_data.bind();
+ glVertexAttribIPointer(terrain_.in_vertex_coordinate, 4, GL_INT, sizeof(PerVertexData), nullptr);
+
+ glDrawArraysInstanced(GL_TRIANGLES, 0, 6 * kPatchWidth * kPatchHeight, num_instances);
+
+ glVertexBindingDivisor(terrain_.in_patch_coordinate, 0);
+}
+
+void TerrainProgramGl4::draw_minimap(const TerrainGl4Arguments* args,
+ float z_value) {
+ auto& gl = Gl::State::instance();
+ const Widelands::Map& map = args->terrain->egbase().map();
+ float width = map.get_width();
+ float height = map.get_height();
+
+ glUseProgram(minimap_.gl_program.object());
+
+ // Prepare minimap setting uniforms
+ glUniform1i(minimap_.u_layer_terrain, (args->minimap_layers & MiniMapLayer::Terrain) ? 1 : 0);
+ glUniform1i(minimap_.u_layer_owner, (args->minimap_layers & MiniMapLayer::Owner) ? 1 : 0);
+
+ uint details = 0;
+ if (args->minimap_layers & MiniMapLayer::Road)
+ details |= 1;
+ if (args->minimap_layers & MiniMapLayer::Flag)
+ details |= 2;
+ if (args->minimap_layers & MiniMapLayer::Building)
+ details |= 4;
+
+ glUniform1ui(minimap_.u_layer_details, details);
+
+ // Prepare textures & sampler uniforms.
+ glUniform1i(minimap_.u_terrain_base, 0);
+ gl.bind(GL_TEXTURE0, args->terrain->fields_texture());
+
+ glUniform1i(minimap_.u_player_brightness, 1);
+ gl.bind(GL_TEXTURE1, args->terrain->player_brightness_texture());
+
+ glUniform1i(minimap_.u_minimap_extra, 2);
+ gl.bind(GL_TEXTURE2, args->terrain->minimap_texture());
+
+ glUniform1i(minimap_.u_terrain_color, 3);
+ gl.bind(GL_TEXTURE3, args->terrain->terrain_color_texture());
+
+ glUniform1i(minimap_.u_player_color, 4);
+ gl.bind(GL_TEXTURE4, args->terrain->player_color_texture());
+
+ glUniform2f(minimap_.u_frame_topleft, (args->minfx + 0.001) / width, (args->minfy + 0.001) / height);
+ glUniform2f(minimap_.u_frame_bottomright, (args->maxfx - 0.001) / width, (args->maxfy - 0.001) / height);
+
+ // Compute coordinates and upload vertex data.
+ if (args->minimap_layers & MiniMapLayer::Zoom2) {
+ width *= 2;
+ height *= 2;
+ }
+
+ float left = args->surface_offset.x;
+ float right = left + width;
+ float top = args->surface_offset.y;
+ float bottom = top + height;
+
+ pixel_to_gl_renderbuffer(args->surface_width, args->surface_height, &left, &top);
+ pixel_to_gl_renderbuffer(args->surface_width, args->surface_height, &right, &bottom);
+
+ float tx = args->minimap_tl_fx * (1.0 / map.get_width());
+ float ty = args->minimap_tl_fy * (1.0 / map.get_height());
+
+ auto stream = minimap_.vertex_data.stream(4);
+ MiniMap::VertexData* v = stream.add(4);
+
+ v[0].x = left;
+ v[0].y = top;
+ v[0].z = z_value;
+ v[0].tx = tx;
+ v[0].ty = ty;
+
+ v[1].x = left;
+ v[1].y = bottom;
+ v[1].z = z_value;
+ v[1].tx = tx;
+ v[1].ty = ty + 1.0;
+
+ v[2].x = right;
+ v[2].y = top;
+ v[2].z = z_value;
+ v[2].tx = tx + 1.0;
+ v[2].ty = ty;
+
+ v[3].x = right;
+ v[3].y = bottom;
+ v[3].z = z_value;
+ v[3].tx = tx + 1.0;
+ v[3].ty = ty + 1.0;
+
+ GLintptr offset = stream.unmap();
+
+ gl.enable_vertex_attrib_array({minimap_.in_position, minimap_.in_field});
+
+ minimap_.vertex_data.bind();
+ Gl::vertex_attrib_pointer(minimap_.in_position, 3, sizeof(MiniMap::VertexData), offset);
+ Gl::vertex_attrib_pointer(minimap_.in_field, 2, sizeof(MiniMap::VertexData),
+ offset + offsetof(MiniMap::VertexData, tx));
+
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+}
+
+void TerrainProgramGl4::draw_roads(const TerrainGl4Arguments* args,
+ float z_value) {
+ auto& gl = Gl::State::instance();
+
+ // Coordinate transform from map coordinates to GL coordinates.
+ float scale_x = 2.0 / args->surface_width * args->scale;
+ float scale_y = -2.0 / args->surface_height * args->scale;
+ float offset_x = args->surface_offset.x * scale_x - 1.0;
+ float offset_y = args->surface_offset.y * scale_y + 1.0;
+
+ glUseProgram(roads_.gl_program.object());
+
+ setup_road_index_buffer(args->roads.size());
+
+ // Prepare uniforms.
+ glUniform2f(roads_.u_position_scale, scale_x, scale_y);
+ glUniform2f(roads_.u_position_offset, offset_x, offset_y);
+ glUniform1f(roads_.u_z_value, z_value);
+
+ // Prepare textures & sampler uniforms.
+ glUniform1i(roads_.u_terrain_base, 0);
+ gl.bind(GL_TEXTURE0, args->terrain->fields_texture());
+
+ glUniform1i(roads_.u_player_brightness, 1);
+ gl.bind(GL_TEXTURE1, args->terrain->player_brightness_texture());
+
+ glUniform1i(roads_.u_texture, 2);
+ gl.bind(GL_TEXTURE2, args->terrain->road_texture_object());
+
+ glBindBufferBase(GL_UNIFORM_BUFFER, roads_.block_textures_idx,
+ args->terrain->road_textures_buffer_object());
+
+ upload_road_data(args);
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, roads_.gl_index_buffer.object());
+ glDrawElements(GL_TRIANGLES, 6 * args->roads.size(), GL_UNSIGNED_SHORT, nullptr);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
+
+void TerrainProgramGl4::init_vertex_data() {
+ std::vector<PerVertexData> vertices;
+
+ for (int y = 0; y < int(kPatchHeight); ++y) {
+ for (int x = 0; x < int(kPatchWidth); ++x) {
+ int blx = (y & 1) ? x : (x - 1);
+
+ // Down triangle.
+ vertices.emplace_back(x, y, x, y, false, 2);
+ vertices.emplace_back(blx, y + 1, x, y, false, 0);
+ vertices.emplace_back(blx + 1, y + 1, x, y, false, 1);
+
+ // Right triangle.
+ vertices.emplace_back(x, y, x, y, true, 1);
+ vertices.emplace_back(blx + 1, y + 1, x, y, true, 2);
+ vertices.emplace_back(x + 1, y, x, y, true, 0);
+ }
+ }
+
+ assert(vertices.size() == 6 * kPatchWidth * kPatchHeight);
+
+ terrain_.vertex_data.bind();
+ terrain_.vertex_data.update(vertices);
+}
+
+// Determine which instances/patches to draw, upload the data and set up the
+// vertex attributes.
+unsigned TerrainProgramGl4::upload_instance_data(const TerrainGl4Arguments* args) {
+ int minfx = args->minfx;
+ int minfy = args->minfy;
+ int maxfx = args->maxfx;
+ int maxfy = args->maxfy;
+ if (minfy & 1)
+ minfy--;
+
+ int ph = (maxfy - minfy + kPatchHeight) / kPatchHeight;
+ int pw = (maxfx - minfx + kPatchWidth) / kPatchWidth;
+ int num_patches = pw * ph;
+
+ auto stream = terrain_.instance_data.stream(num_patches);
+ for (int py = 0; py < ph; ++py) {
+ for (int px = 0; px < pw; ++px) {
+ const int fx = minfx + px * kPatchWidth;
+ const int fy = minfy + py * kPatchHeight;
+
+ stream.emplace_back();
+ PerInstanceData& i = stream.back();
+ i.coordinate.x = fx;
+ i.coordinate.y = fy;
+ }
+ }
+
+ GLintptr offset = stream.unmap();
+
+ glVertexAttribIPointer(terrain_.in_patch_coordinate, 2, GL_INT, sizeof(PerInstanceData),
+ reinterpret_cast<void*>(offset + offsetof(PerInstanceData, coordinate)));
+
+ glVertexBindingDivisor(terrain_.in_patch_coordinate, 1);
+
+ return num_patches;
+}
+
+void TerrainProgramGl4::setup_road_index_buffer(unsigned num_roads) {
+ if (num_roads <= roads_.num_index_roads)
+ return;
+
+ if (num_roads > 65536 / 4)
+ throw wexception("Too many roads for 16-bit indices");
+
+ std::vector<uint16_t> indices;
+ indices.reserve(num_roads * 6);
+
+ for (unsigned i = 0; i < num_roads; ++i) {
+ indices.push_back(4 * i);
+ indices.push_back(4 * i + 1);
+ indices.push_back(4 * i + 2);
+
+ indices.push_back(4 * i + 2);
+ indices.push_back(4 * i + 1);
+ indices.push_back(4 * i + 3);
+ }
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, roads_.gl_index_buffer.object());
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint16_t) * indices.size(),
+ indices.data(), GL_STATIC_DRAW);
+ roads_.num_index_roads = num_roads;
+}
+
+void TerrainProgramGl4::upload_road_data(const TerrainGl4Arguments* args) {
+ assert(!args->roads.empty());
+
+ auto stream = roads_.road_data.stream(args->roads.size());
+
+ for (const TerrainGl4Arguments::Road& road : args->roads) {
+ stream.emplace_back(
+ Vector2i(road.coord.x, road.coord.y), road.direction,
+ args->terrain->road_texture_idx(
+ road.owner, RoadType(road.type), road.coord, WalkingDir(road.direction)));
+ }
+
+ glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, roads_.road_data.object(),
+ stream.unmap(), args->roads.size() * sizeof(PerRoadData));
+}
=== added file 'src/graphic/gl/terrain_program_gl4.h'
--- src/graphic/gl/terrain_program_gl4.h 1970-01-01 00:00:00 +0000
+++ src/graphic/gl/terrain_program_gl4.h 2017-01-07 12:36:16 +0000
@@ -0,0 +1,352 @@
+/*
+ * 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_GL_TERRAIN_PROGRAM_GL4_H
+#define WL_GRAPHIC_GL_TERRAIN_PROGRAM_GL4_H
+
+#include <map>
+#include <memory>
+#include <unordered_map>
+
+#include "base/rect.h"
+#include "graphic/gl/streaming_buffer.h"
+#include "graphic/gl/utils.h"
+#include "logic/map_objects/walkingdir.h"
+#include "logic/roadtype.h"
+#include "logic/widelands.h"
+
+namespace Widelands {
+struct Coords;
+class EditorGameBase;
+class Player;
+}
+
+struct TerrainGl4Arguments;
+class Texture;
+
+/**
+ * This class maintains the terrain information textures.
+ */
+class TerrainInformationGl4 {
+public:
+ ~TerrainInformationGl4();
+
+ // Get the global instance associated to the given editor/game instance
+ // and player perspective. If player is nullptr, an omniscient perspective
+ // is returned.
+ static std::shared_ptr<TerrainInformationGl4>
+ get(const Widelands::EditorGameBase& egbase,
+ const Widelands::Player* player = nullptr);
+
+ static void prepare_frame();
+
+ const Widelands::EditorGameBase& egbase() const {
+ return egbase_;
+ }
+
+ const Widelands::Player* player() const {
+ return player_;
+ }
+
+ GLuint fields_texture() const {
+ return fields_texture_;
+ }
+
+ GLuint player_brightness_texture() const {
+ return brightness_texture_;
+ }
+
+ GLint road_texture_object() const {
+ return road_texture_object_;
+ }
+
+ GLuint road_textures_buffer_object() const {
+ return road_textures_.object();
+ }
+
+ GLuint terrain_data_buffer_object() const {
+ return terrain_data_.object();
+ }
+
+ GLuint minimap_texture() const {
+ return minimap_texture_;
+ }
+
+ GLuint terrain_color_texture() const {
+ return terrain_color_texture_;
+ }
+
+ GLuint player_color_texture() const {
+ return player_color_texture_;
+ }
+
+ // Get the index into the road textures array stored in the road textures
+ // buffer.
+ unsigned road_texture_idx(Widelands::PlayerNumber owner,
+ Widelands::RoadType road_type,
+ const Widelands::Coords& coords,
+ Widelands::WalkingDir direction) const;
+
+ // Mark regions/types of information that need to be updated.
+ void update(int minfx, int maxfx, int minfy, int maxfy);
+ void update_minimap();
+
+private:
+ TerrainInformationGl4(const Widelands::EditorGameBase& egbase,
+ const Widelands::Player* player);
+
+ using GlobalKey = std::pair<const Widelands::EditorGameBase*, const Widelands::Player*>;
+ using GlobalMap = std::map<GlobalKey, std::weak_ptr<TerrainInformationGl4>>;
+
+ void do_prepare_frame();
+ void fields_update();
+ void upload_road_textures();
+ void upload_terrain_data();
+ void brightness_update();
+ void do_update_minimap();
+ void upload_constant_textures();
+
+ struct PerTerrainData {
+ Vector2f offset;
+ int dither_layer;
+ float padding[1];
+ };
+ static_assert(sizeof(PerTerrainData) == 16, "incorrect padding");
+
+ struct PerFieldData {
+ uint8_t terrain_r;
+ uint8_t terrain_d;
+ uint8_t height;
+
+ // Will be interpreted as unsigned by the texel fetch in the shader.
+ int8_t brightness;
+ };
+ static_assert(sizeof(PerFieldData) == 4, "incorrect padding");
+
+ struct PerRoadTextureData {
+ float x, y, w, h;
+
+ PerRoadTextureData(const Rectf& rect);
+ };
+ static_assert(sizeof(PerRoadTextureData) == 16, "incorrect padding");
+
+ struct PlayerRoads {
+ unsigned normal_roads = 0;
+ unsigned num_normal_roads = 1;
+ unsigned busy_roads = 0;
+ unsigned num_busy_roads = 1;
+ };
+
+ static GlobalMap global_map_;
+
+ const Widelands::EditorGameBase& egbase_;
+ const Widelands::Player* player_;
+ uint32_t fields_base_version_;
+ uint32_t terrain_vision_version_;
+ std::vector<Recti> update_;
+ bool updated_minimap_;
+ bool need_update_minimap_;
+ unsigned minimap_update_next_;
+
+ Gl::StreamingBuffer<uint8_t> uploads_;
+
+ // The texture containing per-field information.
+ GLuint fields_texture_;
+
+ // Brightness texture: GL_R8.
+ GLuint brightness_texture_;
+ bool brightness_see_all_;
+
+ // Road textures information
+ Gl::Buffer<PerRoadTextureData> road_textures_;
+ std::vector<PlayerRoads> player_roads_;
+ GLuint road_texture_object_;
+
+ // Uniform buffer with per-terrain information.
+ Gl::Buffer<PerTerrainData> terrain_data_;
+
+ // Texture containing additional, minimap-only information.
+ GLuint minimap_texture_;
+
+ // Texture containing terrain colors for minimap.
+ GLuint terrain_color_texture_;
+
+ // Texture containing player colors.
+ GLuint player_color_texture_;
+
+ DISALLOW_COPY_AND_ASSIGN(TerrainInformationGl4);
+};
+
+class TerrainProgramGl4 {
+public:
+ // The patch height must be even.
+ static constexpr unsigned kPatchWidth = 8;
+ static constexpr unsigned kPatchHeight = 8;
+
+public:
+ TerrainProgramGl4();
+ ~TerrainProgramGl4();
+
+ static bool supported();
+
+ // Draws the terrain.
+ void draw(const TerrainGl4Arguments* args,
+ float z_value);
+
+ // Draws a mini-map.
+ void draw_minimap(const TerrainGl4Arguments* args,
+ float z_value);
+
+ // Draw roads.
+ void draw_roads(const TerrainGl4Arguments* args, float z_value);
+
+private:
+ void init_vertex_data();
+ void upload_terrain_data(const TerrainGl4Arguments* args, uint32_t gametime);
+ unsigned upload_instance_data(const TerrainGl4Arguments* args);
+
+ void setup_road_index_buffer(unsigned num_roads);
+ void upload_road_data(const TerrainGl4Arguments* args);
+
+ struct PerInstanceData {
+ Vector2i coordinate;
+ };
+ static_assert(sizeof(PerInstanceData) == 8, "incorrect padding");
+
+ struct PerVertexData {
+ Vector2i vertex_coordinate;
+ Vector2i triangle_coordinate;
+
+ PerVertexData(int vx, int vy, int tx, int ty, bool r, uint dither_vid)
+ : vertex_coordinate(vx, vy), triangle_coordinate((tx << 3) | (dither_vid << 1) | r, ty) {
+ }
+ };
+ static_assert(sizeof(PerVertexData) == 16, "incorrect padding");
+
+ struct PerRoadData {
+ Vector2i start;
+ uint32_t direction;
+ uint32_t texture;
+
+ PerRoadData(const Vector2i& start_, uint32_t direction_, uint32_t texture_)
+ : start(start_), direction(direction_), texture(texture_) {
+ }
+ };
+ static_assert(sizeof(PerRoadData) == 16, "incorrect padding");
+
+ struct Terrain {
+ Terrain();
+ ~Terrain();
+
+ // The program used for drawing the terrain.
+ Gl::Program gl_program;
+
+ // Per-instance/patch data.
+ Gl::StreamingBuffer<PerInstanceData> instance_data;
+
+ // Per-vertex data.
+ Gl::Buffer<PerVertexData> vertex_data;
+
+ std::unique_ptr<Texture> dither_mask;
+
+ // Vertex attributes.
+ GLint in_vertex_coordinate;
+ GLint in_patch_coordinate;
+
+ // Uniforms.
+ GLint u_position_scale;
+ GLint u_position_offset;
+ GLint u_z_value;
+ GLint u_texture_dimensions;
+
+ GLint u_terrain_base;
+ GLint u_player_brightness;
+ GLint u_terrain_texture;
+ GLint u_dither_texture;
+
+ // Uniform block.
+ GLint block_terrains_idx;
+ } terrain_;
+
+ struct Roads {
+ Roads();
+ ~Roads();
+
+ // The program used for drawing the roads.
+ Gl::Program gl_program;
+
+ // Index (element array) buffer.
+ Gl::Buffer<uint16_t> gl_index_buffer;
+ unsigned num_index_roads;
+
+ // The per-road data buffer.
+ Gl::StreamingBuffer<PerRoadData> road_data;
+
+ // Uniforms.
+ GLint u_position_scale;
+ GLint u_position_offset;
+ GLint u_z_value;
+
+ GLint u_terrain_base;
+ GLint u_player_brightness;
+ GLint u_texture;
+
+ // Uniform block.
+ GLint block_textures_idx;
+ } roads_;
+
+ struct MiniMap {
+ MiniMap();
+ ~MiniMap();
+
+ struct VertexData {
+ float x, y, z;
+ float tx, ty;
+ };
+ static_assert(sizeof(VertexData) == 20, "incorrect padding");
+
+ // The program used for drawing the minimap.
+ Gl::Program gl_program;
+
+ // The vertex array.
+ Gl::StreamingBuffer<VertexData> vertex_data;
+
+ // Vertex attributes.
+ GLint in_position;
+ GLint in_field;
+
+ // Uniforms.
+ GLint u_layer_terrain;
+ GLint u_layer_owner;
+ GLint u_layer_details;
+
+ GLint u_frame_topleft;
+ GLint u_frame_bottomright;
+
+ GLint u_terrain_base;
+ GLint u_player_brightness;
+ GLint u_minimap_extra;
+ GLint u_terrain_color;
+ GLint u_player_color;
+ } minimap_;
+
+ DISALLOW_COPY_AND_ASSIGN(TerrainProgramGl4);
+};
+
+#endif // end of include guard: WL_GRAPHIC_GL_TERRAIN_PROGRAM_GL4_H
=== modified file 'src/graphic/gl/utils.cc'
--- src/graphic/gl/utils.cc 2016-09-06 07:59:30 +0000
+++ src/graphic/gl/utils.cc 2017-01-07 12:36:16 +0000
@@ -82,6 +82,7 @@
class Shader {
public:
Shader(GLenum type);
+ Shader(Shader&& other);
~Shader();
GLuint object() const {
@@ -89,11 +90,11 @@
}
// Compiles 'source'. Throws an exception on error.
- void compile(const char* source);
+ void compile(const char* source, const char* shader_name = nullptr);
private:
- const GLenum type_;
- const GLuint shader_object_;
+ GLenum type_;
+ GLuint shader_object_;
DISALLOW_COPY_AND_ASSIGN(Shader);
};
@@ -104,13 +105,19 @@
}
}
+Shader::Shader(Shader&& other) {
+ type_ = other.type_;
+ shader_object_ = other.shader_object_;
+ other.shader_object_ = 0;
+}
+
Shader::~Shader() {
if (shader_object_) {
glDeleteShader(shader_object_);
}
}
-void Shader::compile(const char* source) {
+void Shader::compile(const char* source, const char* shader_name) {
glShaderSource(shader_object_, 1, &source, nullptr);
glCompileShader(shader_object_);
@@ -123,7 +130,8 @@
std::unique_ptr<char[]> infoLog(new char[infoLen]);
glGetShaderInfoLog(shader_object_, infoLen, NULL, infoLog.get());
throw wexception(
- "Error compiling %s shader:\n%s", shader_to_string(type_).c_str(), infoLog.get());
+ "Error compiling %s shader (%s):\n%s", shader_to_string(type_).c_str(),
+ shader_name ? shader_name : "unnamed", infoLog.get());
}
}
}
@@ -140,17 +148,23 @@
}
}
-void Program::build(const std::string& program_name) {
- std::string fragment_shader_source = read_file("shaders/" + program_name + ".fp");
- std::string vertex_shader_source = read_file("shaders/" + program_name + ".vp");
-
- vertex_shader_.reset(new Shader(GL_VERTEX_SHADER));
- vertex_shader_->compile(vertex_shader_source.c_str());
- glAttachShader(program_object_, vertex_shader_->object());
-
- fragment_shader_.reset(new Shader(GL_FRAGMENT_SHADER));
- fragment_shader_->compile(fragment_shader_source.c_str());
- glAttachShader(program_object_, fragment_shader_->object());
+void Program::build_vp_fp(const std::vector<std::string>& vp_names,
+ const std::vector<std::string>& fp_names) {
+ // Shader objects are marked for deletion immediately, but the GL holds
+ // onto them as long as they're attached to the program object.
+ for (const std::string& vp_name : vp_names) {
+ std::string vertex_shader_source = read_file("shaders/" + vp_name + ".vp");
+ Shader shader(GL_VERTEX_SHADER);
+ shader.compile(vertex_shader_source.c_str(), vp_name.c_str());
+ glAttachShader(program_object_, shader.object());
+ }
+
+ for (const std::string& fp_name : fp_names) {
+ std::string fragment_shader_source = read_file("shaders/" + fp_name + ".fp");
+ Shader shader(GL_FRAGMENT_SHADER);
+ shader.compile(fragment_shader_source.c_str(), fp_name.c_str());
+ glAttachShader(program_object_, shader.object());
+ }
glLinkProgram(program_object_);
@@ -169,8 +183,49 @@
}
}
+void Program::build(const std::string& program_name) {
+ build_vp_fp({program_name}, {program_name});
+}
+
+void Capabilities::check() {
+ // Reset all variables
+ *this = {};
+
+ const char* glsl_version_string = reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION));
+ int major = 0, minor = 0;
+
+ if (sscanf(glsl_version_string, "%d.%d", &major, &minor) != 2)
+ log("Warning: Malformed GLSL version string: %s\n", glsl_version_string);
+
+ glsl_version = major * 100 + minor;
+
+ GLint num_extensions;
+ glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
+
+ for (GLint i = 0; i < num_extensions; ++i) {
+ const char* extension = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, i));
+
+#define EXTENSION(basename) \
+ do { \
+ if (!strcmp(extension, "GL_" #basename)) \
+ basename = true; \
+ } while (false)
+
+ EXTENSION(ARB_separate_shader_objects);
+ EXTENSION(ARB_shader_storage_buffer_object);
+ EXTENSION(ARB_uniform_buffer_object);
+
+#undef EXTENSION
+ }
+}
+
State::State()
- : last_active_texture_(NONE), current_framebuffer_(0), current_framebuffer_texture_(0) {
+ : target_to_texture_(kMaxTextureTargets), last_active_texture_(NONE),
+ current_framebuffer_(0), current_framebuffer_texture_(0) {
+}
+
+void State::check_capabilities() {
+ caps_.check();
}
void State::bind(const GLenum target, const GLuint texture) {
@@ -181,7 +236,8 @@
}
void State::do_bind(const GLenum target, const GLuint texture) {
- const auto currently_bound_texture = target_to_texture_[target];
+ const unsigned target_idx = target - GL_TEXTURE0;
+ const auto currently_bound_texture = target_to_texture_[target_idx];
if (currently_bound_texture == texture) {
return;
}
@@ -191,28 +247,19 @@
}
glBindTexture(GL_TEXTURE_2D, texture);
- target_to_texture_[target] = texture;
- texture_to_target_[currently_bound_texture] = NONE;
- texture_to_target_[texture] = target;
-}
-
-void State::unbind_texture_if_bound(const GLuint texture) {
- if (texture == 0) {
- return;
- }
- const auto target = texture_to_target_[texture];
- if (target != 0) {
- do_bind(target, 0);
- }
+ target_to_texture_[target_idx] = texture;
}
void State::delete_texture(const GLuint texture) {
- unbind_texture_if_bound(texture);
glDeleteTextures(1, &texture);
if (current_framebuffer_texture_ == texture) {
current_framebuffer_texture_ = 0;
}
+ for (unsigned i = 0; i < target_to_texture_.size(); ++i) {
+ if (target_to_texture_[i] == texture)
+ target_to_texture_[i] = 0;
+ }
}
void State::bind_framebuffer(const GLuint framebuffer, const GLuint texture) {
@@ -227,7 +274,6 @@
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
if (framebuffer != 0) {
- unbind_texture_if_bound(texture);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
}
=== modified file 'src/graphic/gl/utils.h'
--- src/graphic/gl/utils.h 2016-08-04 15:49:05 +0000
+++ src/graphic/gl/utils.h 2017-01-07 12:36:16 +0000
@@ -53,11 +53,11 @@
// Creates and compiles shader objects based on the corresponding files in data/shaders,
// then links them into the program.
void build(const std::string& program_name);
+ void build_vp_fp(const std::vector<std::string>& vp_names,
+ const std::vector<std::string>& fp_names);
private:
const GLuint program_object_;
- std::unique_ptr<Shader> vertex_shader_;
- std::unique_ptr<Shader> fragment_shader_;
DISALLOW_COPY_AND_ASSIGN(Program);
};
@@ -79,6 +79,11 @@
}
}
+ // Returns the OpenGL object for direct use.
+ GLuint object() const {
+ return object_;
+ }
+
// Calls glBindBuffer on the underlying buffer data.
void bind() const {
glBindBuffer(GL_ARRAY_BUFFER, object_);
@@ -99,6 +104,17 @@
DISALLOW_COPY_AND_ASSIGN(Buffer);
};
+// Selected GL capabilities that are queried during initialization.
+struct Capabilities {
+ unsigned glsl_version = 0; // major * 100 + minor
+
+ bool ARB_separate_shader_objects = false;
+ bool ARB_shader_storage_buffer_object = false;
+ bool ARB_uniform_buffer_object = false;
+
+ void check();
+};
+
// Some GL drivers do not remember the current pipeline state. If you rebind a
// texture that has already bound to the same target, they will happily stall
// the pipeline. We therefore cache the state of the GL driver in this class
@@ -110,28 +126,30 @@
void bind_framebuffer(GLuint framebuffer, GLuint texture);
// Wrapper around glActiveTexture() and glBindTexture(). We never unbind a
- // texture, i.e. calls with texture == 0 are ignored. It costs only time and
- // is only needed when the bounded texture is rendered on - see
- // 'unbind_texture_if_bound'.
+ // texture, i.e. calls with texture == 0 are ignored.
void bind(GLenum target, GLuint texture);
- // Checks if the texture is bound to any target. If so, unbinds it. This is
- // needed before the texture is used as target for rendering.
- void unbind_texture_if_bound(GLuint texture);
-
void delete_texture(GLuint texture);
// Calls glEnableVertexAttribArray on all 'entries' and disables all others
// that are activated. 'entries' is taken by value on purpose.
void enable_vertex_attrib_array(std::unordered_set<GLint> entries);
+ const Capabilities& capabilities() const {
+ return caps_;
+ }
+
+ void check_capabilities();
+
private:
- std::unordered_map<GLenum, GLuint> target_to_texture_;
- std::unordered_map<GLuint, GLenum> texture_to_target_;
+ static const unsigned kMaxTextureTargets = 16;
+
+ std::vector<GLuint> target_to_texture_;
std::unordered_set<GLint> enabled_attrib_arrays_;
GLenum last_active_texture_;
GLuint current_framebuffer_;
GLuint current_framebuffer_texture_;
+ Capabilities caps_;
State();
=== modified file 'src/graphic/image_io.cc'
--- src/graphic/image_io.cc 2016-08-04 15:49:05 +0000
+++ src/graphic/image_io.cc 2017-01-07 12:36:16 +0000
@@ -124,7 +124,7 @@
std::unique_ptr<png_byte[]> row(new png_byte[row_size]);
// Write each row
- texture->lock();
+ texture->lock(Texture::Lock_Preserve);
// Write each row
RGBAColor color;
=== added file 'src/graphic/minimap_layer.h'
--- src/graphic/minimap_layer.h 1970-01-01 00:00:00 +0000
+++ src/graphic/minimap_layer.h 2017-01-07 12:36:16 +0000
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010-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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef WL_GRAPHIC_MINIMAP_LAYER_H
+#define WL_GRAPHIC_MINIMAP_LAYER_H
+
+// Layers for selecting what do display on the minimap.
+enum class MiniMapLayer {
+ Terrain = 1,
+ Owner = 2,
+ Flag = 4,
+ Road = 8,
+ Building = 16,
+ Zoom2 = 32,
+ ViewWindow = 64,
+};
+
+// A bunch of operators that turn MiniMapLayer into a bitwise combinable flag class.
+inline MiniMapLayer operator|(MiniMapLayer left, MiniMapLayer right) {
+ return MiniMapLayer(static_cast<int>(left) | static_cast<int>(right));
+}
+inline int operator&(MiniMapLayer left, MiniMapLayer right) {
+ return static_cast<int>(left) & static_cast<int>(right);
+}
+inline MiniMapLayer operator^(MiniMapLayer left, MiniMapLayer right) {
+ return MiniMapLayer(static_cast<int>(left) ^ static_cast<int>(right));
+}
+
+#endif // end of include guard: WL_GRAPHIC_MINIMAP_LAYER_H
=== modified file 'src/graphic/minimap_renderer.cc'
--- src/graphic/minimap_renderer.cc 2016-12-03 13:32:28 +0000
+++ src/graphic/minimap_renderer.cc 2017-01-07 12:36:16 +0000
@@ -24,7 +24,13 @@
#include "base/macros.h"
#include "economy/flag.h"
#include "economy/road.h"
+#include "graphic/game_renderer_gl4.h"
+#include "graphic/gl/terrain_program_gl4.h"
#include "graphic/graphic.h"
+#include "graphic/image_io.h"
+#include "graphic/render_queue.h"
+#include "graphic/rendertarget.h"
+#include "graphic/texture.h"
#include "logic/field.h"
#include "logic/map_objects/world/terrain_description.h"
#include "logic/map_objects/world/world.h"
@@ -189,13 +195,118 @@
owner = field.owner;
}
- if (vision > 0) {
- texture->set_pixel(x, y, calc_minimap_color(egbase, f, layers, owner, vision > 1));
- }
+ RGBColor color;
+ if (vision > 0)
+ color = calc_minimap_color(egbase, f, layers, owner, vision > 1);
+ texture->set_pixel(x, y, color);
}
}
}
+/**
+ * Mini-map renderer implementation that generates a mini-map texture in
+ * software and blits it.
+ */
+class MiniMapRendererSoftware : public MiniMapRenderer {
+public:
+ MiniMapRendererSoftware(const Widelands::EditorGameBase& egbase,
+ const Widelands::Player* player)
+ : MiniMapRenderer(egbase, player) {
+ }
+
+ void draw(RenderTarget& dst,
+ const Rectf& viewarea,
+ MiniMapType type,
+ MiniMapLayer layers) override {
+ texture_ = draw_minimap(egbase(), player(), viewarea, type, layers);
+ dst.blit(Vector2f(), texture_.get());
+ }
+
+private:
+ std::unique_ptr<Texture> texture_;
+};
+
+/**
+ * Mini-map renderer that delegates to the GL4 programs for mini-map drawing
+ * in a pixel shader.
+ */
+class MiniMapRendererGl4 : public MiniMapRenderer {
+public:
+ MiniMapRendererGl4(const Widelands::EditorGameBase& egbase,
+ const Widelands::Player* player)
+ : MiniMapRenderer(egbase, player) {
+ args_.terrain = TerrainInformationGl4::get(egbase, player);
+ }
+
+ void draw(RenderTarget& dst,
+ const Rectf& view_area,
+ MiniMapType minimap_type,
+ MiniMapLayer layers) override {
+ Surface* surface = dst.get_surface();
+ if (!surface)
+ return;
+
+ args_.terrain->update_minimap();
+
+ const Recti& bounding_rect = dst.get_rect();
+
+ // Determine the field shown at the top-left of the minimap.
+ const bool zoom = layers & MiniMapLayer::Zoom2;
+ Vector2f top_left =
+ minimap_pixel_to_mappixel(egbase().map(), Vector2i(0, 0), view_area, minimap_type, zoom);
+ const Coords node =
+ MapviewPixelFunctions::calc_node_and_triangle(egbase().map(), top_left.x, top_left.y).node;
+
+ args_.minimap_tl_fx = node.x;
+ args_.minimap_tl_fy = node.y;
+ args_.minimap_layers = layers;
+
+ // Calculate frame coordinates
+ int frame_width = view_area.w / kTriangleWidth;
+ int frame_height = view_area.h / kTriangleHeight;
+
+ Vector2i center_field;
+ switch (minimap_type) {
+ case MiniMapType::kStaticViewWindow:
+ center_field.x = node.x + egbase().map().get_width() / 2;
+ center_field.y = node.y + egbase().map().get_height() / 2;
+ break;
+
+ case MiniMapType::kStaticMap: {
+ Vector2f origin = view_area.center();
+ MapviewPixelFunctions::normalize_pix(egbase().map(), &origin);
+ center_field.x = origin.x / kTriangleWidth;
+ center_field.y = origin.y / kTriangleHeight;
+ break;
+ }
+ }
+
+ args_.minfx = center_field.x - frame_width / 2;
+ args_.minfy = center_field.y - frame_height / 2;
+ args_.maxfx = center_field.x + frame_width / 2;
+ args_.maxfy = center_field.y + frame_height / 2;
+
+ args_.surface_offset = (bounding_rect.origin() + dst.get_offset()).cast<float>();
+ args_.surface_width = surface->width();
+ args_.surface_height = surface->height();
+
+ // Enqueue the drawing.
+ RenderQueue::Item i;
+ i.program_id = RenderQueue::Program::kMiniMapGl4;
+ i.blend_mode = BlendMode::Copy;
+ i.terrain_arguments.destination_rect =
+ Rectf(bounding_rect.x, args_.surface_height - bounding_rect.y - bounding_rect.h,
+ bounding_rect.w, bounding_rect.h);
+ i.terrain_arguments.renderbuffer_width = args_.surface_width;
+ i.terrain_arguments.renderbuffer_height = args_.surface_height;
+ i.terrain_gl4_arguments = &args_;
+ RenderQueue::instance().enqueue(i);
+ }
+
+private:
+ TerrainGl4Arguments args_;
+};
+
} // namespace
Vector2f minimap_pixel_to_mappixel(const Widelands::Map& map,
@@ -235,10 +346,6 @@
const int16_t map_w = (layers & MiniMapLayer::Zoom2) ? map.get_width() * 2 : map.get_width();
const int16_t map_h = (layers & MiniMapLayer::Zoom2) ? map.get_height() * 2 : map.get_height();
- std::unique_ptr<Texture> texture(new Texture(map_w, map_h));
-
- texture->fill_rect(Rectf(0, 0, texture->width(), texture->height()), RGBAColor(0, 0, 0, 255));
-
// Center the view on the middle of the 'view_area'.
const bool zoom = layers & MiniMapLayer::Zoom2;
Vector2f top_left =
@@ -246,7 +353,8 @@
const Coords node =
MapviewPixelFunctions::calc_node_and_triangle(map, top_left.x, top_left.y).node;
- texture->lock();
+ std::unique_ptr<Texture> texture(new Texture(map_w, map_h));
+ texture->lock(Texture::Lock_Discard);
do_draw_minimap(texture.get(), egbase, player, Vector2i(node.x, node.y), layers);
if (layers & MiniMapLayer::ViewWindow) {
@@ -256,3 +364,16 @@
return texture;
}
+
+MiniMapRenderer::MiniMapRenderer(const Widelands::EditorGameBase& egbase,
+ const Widelands::Player* player)
+ : egbase_(egbase), player_(player) {
+}
+
+std::unique_ptr<MiniMapRenderer>
+MiniMapRenderer::create(const Widelands::EditorGameBase& egbase,
+ const Widelands::Player* player) {
+ if (TerrainProgramGl4::supported())
+ return std::unique_ptr<MiniMapRenderer>(new MiniMapRendererGl4(egbase, player));
+ return std::unique_ptr<MiniMapRenderer>(new MiniMapRendererSoftware(egbase, player));
+}
=== modified file 'src/graphic/minimap_renderer.h'
--- src/graphic/minimap_renderer.h 2016-10-24 20:07:22 +0000
+++ src/graphic/minimap_renderer.h 2017-01-07 12:36:16 +0000
@@ -24,31 +24,16 @@
#include "base/rect.h"
#include "base/vector.h"
-#include "graphic/texture.h"
-#include "logic/editor_game_base.h"
-#include "logic/map.h"
-#include "logic/player.h"
-
-// Layers for selecting what do display on the minimap.
-enum class MiniMapLayer {
- Terrain = 1,
- Owner = 2,
- Flag = 4,
- Road = 8,
- Building = 16,
- Zoom2 = 32,
- ViewWindow = 64,
-};
-
-// A bunch of operators that turn MiniMapLayer into a bitwise combinable flag class.
-inline MiniMapLayer operator|(MiniMapLayer left, MiniMapLayer right) {
- return MiniMapLayer(static_cast<int>(left) | static_cast<int>(right));
-}
-inline int operator&(MiniMapLayer left, MiniMapLayer right) {
- return static_cast<int>(left) & static_cast<int>(right);
-}
-inline MiniMapLayer operator^(MiniMapLayer left, MiniMapLayer right) {
- return MiniMapLayer(static_cast<int>(left) ^ static_cast<int>(right));
+#include "graphic/minimap_layer.h"
+
+class RenderTarget;
+class StreamWrite;
+class Texture;
+
+namespace Widelands {
+class Map;
+class Player;
+class EditorGameBase;
}
enum class MiniMapType {
@@ -59,6 +44,45 @@
kStaticMap,
};
+/**
+ * Virtual base class for mini-map renderers. Each mini-map view should own
+ * an instance of this class, which must be kept alive during rendering (due
+ * to commands sent to the RenderQueue).
+ */
+class MiniMapRenderer {
+public:
+ static std::unique_ptr<MiniMapRenderer>
+ create(const Widelands::EditorGameBase& egbase,
+ const Widelands::Player* player);
+
+ virtual ~MiniMapRenderer() {}
+
+ const Widelands::EditorGameBase& egbase() const {
+ return egbase_;
+ }
+
+ const Widelands::Player* player() const {
+ return player_;
+ }
+
+ /**
+ * Draw the minimap into the given destination. The @p viewpoint is the
+ * map field shown in the top-left corner of the minimap.
+ */
+ virtual void draw(RenderTarget& dst,
+ const Rectf& viewarea,
+ MiniMapType type,
+ MiniMapLayer layers) = 0;
+
+protected:
+ MiniMapRenderer(const Widelands::EditorGameBase& egbase,
+ const Widelands::Player* player);
+
+private:
+ const Widelands::EditorGameBase& egbase_;
+ const Widelands::Player* player_;
+};
+
// Converts between minimap pixel and map pixel.
// Remember to call 'normalize_pix' after applying the transformation.
Vector2f minimap_pixel_to_mappixel(const Widelands::Map& map,
=== modified file 'src/graphic/render_queue.cc'
--- src/graphic/render_queue.cc 2016-10-22 18:19:22 +0000
+++ src/graphic/render_queue.cc 2017-01-07 12:36:16 +0000
@@ -30,6 +30,7 @@
#include "graphic/gl/fill_rect_program.h"
#include "graphic/gl/road_program.h"
#include "graphic/gl/terrain_program.h"
+#include "graphic/gl/terrain_program_gl4.h"
namespace {
@@ -47,7 +48,7 @@
// - we batch up by program to have maximal batching.
// - and we want to render frontmost objects first, so that we do not render
// any pixel more than once.
-static_assert(RenderQueue::Program::kHighestProgramId <= 8,
+static_assert(RenderQueue::Program::kHighestProgramId <= 16,
"Need to change sorting keys."); // 4 bits.
uint64_t
@@ -139,10 +140,14 @@
} // namespace
RenderQueue::RenderQueue()
- : next_z_(1),
- terrain_program_(new TerrainProgram()),
- dither_program_(new DitherProgram()),
- road_program_(new RoadProgram()) {
+ : next_z_(1) {
+ if (!TerrainProgramGl4::supported()) {
+ terrain_program_.reset(new TerrainProgram);
+ dither_program_.reset(new DitherProgram);
+ road_program_.reset(new RoadProgram);
+ } else {
+ terrain_program_gl4_.reset(new TerrainProgramGl4);
+ }
}
// static
@@ -165,6 +170,9 @@
case Program::kTerrainBase:
case Program::kTerrainDither:
case Program::kTerrainRoad:
+ case Program::kTerrainGl4:
+ case Program::kTerrainRoadGl4:
+ case Program::kMiniMapGl4:
/* all fallthroughs intended */
break;
@@ -191,6 +199,8 @@
throw wexception("Too many drawn layers. Ran out of z-values.");
}
+ TerrainInformationGl4::prepare_frame();
+
Gl::State::instance().bind_framebuffer(0, 0);
glViewport(0, 0, screen_width, screen_height);
@@ -256,6 +266,27 @@
++i;
} break;
+ case Program::kTerrainGl4: {
+ ScopedScissor scoped_scissor(item.terrain_arguments.destination_rect);
+ terrain_program_gl4_->draw(item.terrain_gl4_arguments,
+ item.z_value);
+ ++i;
+ } break;
+
+ case Program::kTerrainRoadGl4: {
+ ScopedScissor scoped_scissor(item.terrain_arguments.destination_rect);
+ terrain_program_gl4_->draw_roads(item.terrain_gl4_arguments,
+ item.z_value);
+ ++i;
+ } break;
+
+ case Program::kMiniMapGl4: {
+ ScopedScissor scoped_scissor(item.terrain_arguments.destination_rect);
+ terrain_program_gl4_->draw_minimap(item.terrain_gl4_arguments,
+ item.z_value);
+ ++i;
+ } break;
+
default:
throw wexception("Unknown item.program_id: %d", item.program_id);
}
=== modified file 'src/graphic/render_queue.h'
--- src/graphic/render_queue.h 2016-10-22 11:20:33 +0000
+++ src/graphic/render_queue.h 2017-01-07 12:36:16 +0000
@@ -38,6 +38,8 @@
class DitherProgram;
class RoadProgram;
class TerrainProgram;
+class TerrainProgramGl4;
+struct TerrainGl4Arguments;
// The RenderQueue is a singleton implementing the concept of deferred
// rendering: Every rendering call that pretends to draw onto the screen will
@@ -83,6 +85,9 @@
kTerrainBase,
kTerrainDither,
kTerrainRoad,
+ kTerrainGl4,
+ kTerrainRoadGl4,
+ kMiniMapGl4,
kBlit,
kRect,
kLine,
@@ -120,7 +125,7 @@
int renderbuffer_width;
int renderbuffer_height;
const DescriptionMaintainer<Widelands::TerrainDescription>* terrains;
- FieldsToDraw* fields_to_draw;
+ FieldsToDrawGl2* fields_to_draw;
float scale;
Rectf destination_rect;
};
@@ -156,6 +161,7 @@
TerrainArguments terrain_arguments;
RectArguments rect_arguments;
LineArguments line_arguments;
+ TerrainGl4Arguments* terrain_gl4_arguments;
};
static RenderQueue& instance();
@@ -179,6 +185,7 @@
int next_z_;
std::unique_ptr<TerrainProgram> terrain_program_;
+ std::unique_ptr<TerrainProgramGl4> terrain_program_gl4_;
std::unique_ptr<DitherProgram> dither_program_;
std::unique_ptr<RoadProgram> road_program_;
=== modified file 'src/graphic/texture.cc'
--- src/graphic/texture.cc 2016-10-24 14:07:28 +0000
+++ src/graphic/texture.cc 2017-01-07 12:36:16 +0000
@@ -189,7 +189,7 @@
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, static_cast<GLint>(GL_LINEAR));
}
-void Texture::lock() {
+void Texture::lock(LockMode mode) {
if (blit_data_.texture_id == 0) {
return;
}
@@ -203,8 +203,10 @@
pixels_.reset(new uint8_t[width() * height() * 4]);
- Gl::State::instance().bind(GL_TEXTURE0, blit_data_.texture_id);
- glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels_.get());
+ if (mode == Lock_Preserve) {
+ Gl::State::instance().bind(GL_TEXTURE0, blit_data_.texture_id);
+ glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels_.get());
+ }
}
void Texture::unlock(UnlockMode mode) {
=== modified file 'src/graphic/texture.h'
--- src/graphic/texture.h 2016-11-03 07:20:57 +0000
+++ src/graphic/texture.h 2017-01-07 12:36:16 +0000
@@ -50,6 +50,25 @@
// Implements Image.
const BlitData& blit_data() const override;
+ enum LockMode {
+ /**
+ * Previously existing pixel data will be discarded.
+ *
+ * The contents of the texture will be undefined unless all pixels
+ * values are explicitly set and \ref unlock is called in Unlock_Update
+ * mode.
+ */
+ Lock_Discard = 0,
+
+ /**
+ * The existing data in the texture will be preserved.
+ *
+ * Avoid this when possible, since the texture may have to be
+ * re-downloaded from the GPU which involves a graphics pipeline stall.
+ */
+ Lock_Preserve
+ };
+
enum UnlockMode {
/**
* Update mode will ensure that any changes in the pixel data
@@ -68,7 +87,7 @@
// Lock/Unlock pairs must guard any of the direct pixel access using the
// functions below. Lock/Unlock pairs cannot be nested.
- void lock();
+ void lock(LockMode);
void unlock(UnlockMode);
// Returns the color of the pixel.
=== modified file 'src/logic/map.cc'
--- src/logic/map.cc 2016-09-07 09:30:49 +0000
+++ src/logic/map.cc 2017-01-07 12:36:16 +0000
@@ -69,7 +69,8 @@
scenario_types_(NO_SCENARIO),
width_(0),
height_(0),
- pathfieldmgr_(new PathfieldManager) {
+ pathfieldmgr_(new PathfieldManager),
+ fields_base_version_(0) {
}
Map::~Map() {
@@ -386,6 +387,8 @@
new_port_spaces.insert(temp);
}
port_spaces_ = new_port_spaces;
+
+ fields_base_version_++;
}
/*
@@ -403,6 +406,7 @@
memset(fields_.get(), 0, sizeof(Field) * w * h);
pathfieldmgr_->set_size(w * h);
+ fields_base_version_++;
}
/*
@@ -1757,6 +1761,7 @@
clear_resources(f_sw_e);
}
+ fields_base_version_++;
Notifications::publish(NoteFieldTerrainChanged{c, static_cast<MapIndex>(c.field - &fields_[0])});
// Changing the terrain can affect ports, which can be up to 3 fields away.
@@ -1847,6 +1852,7 @@
uint32_t radius = 2;
check_neighbour_heights(fc, radius);
recalc_for_field_area(world, Area<FCoords>(fc, radius));
+ fields_base_version_++;
return radius;
}
@@ -1872,6 +1878,7 @@
} while (mr.advance(*this));
area.radius += regional_radius + 2;
recalc_for_field_area(world, area);
+ fields_base_version_++;
return area.radius;
}
@@ -1913,6 +1920,7 @@
area.radius = mr.radius();
}
recalc_for_field_area(world, area);
+ fields_base_version_++;
return area.radius;
}
=== modified file 'src/logic/map.h'
--- src/logic/map.h 2016-08-04 15:49:05 +0000
+++ src/logic/map.h 2017-01-07 12:36:16 +0000
@@ -275,6 +275,17 @@
return height_;
}
+ // Returns a "map data version number" that is incremented whenever one of
+ // the following changes:
+ // - width/height of the map
+ // - some triangle's terrain
+ // - some field's height
+ // The intention is that this allows the rendering system can cache data
+ // that doesn't usually change during a game for performance.
+ uint32_t get_fields_base_version() const {
+ return fields_base_version_;
+ }
+
// The next few functions are only valid when the map is loaded as a
// scenario.
const std::string& get_scenario_player_tribe(PlayerNumber) const;
@@ -501,6 +512,8 @@
PortSpacesSet port_spaces_;
Objectives objectives_;
+ uint32_t fields_base_version_;
+
void recalc_brightness(const FCoords&);
void recalc_nodecaps_pass1(const World& world, const FCoords&);
void recalc_nodecaps_pass2(const World& world, const FCoords& f);
=== modified file 'src/logic/map_objects/map_object.h'
--- src/logic/map_objects/map_object.h 2016-10-26 19:43:40 +0000
+++ src/logic/map_objects/map_object.h 2017-01-07 12:36:16 +0000
@@ -214,12 +214,15 @@
* be fully created.
*/
-/// If you find a better way to do this that doesn't cost a virtual function
-/// or additional member variable, go ahead
+// Use a pure static_cast in release builds, which should end up as plain
+// pointer arithmetic, but add a dynamic_cast in an assertion as an additional
+// safe-guard in debug builds.
#define MO_DESCR(type) \
public: \
- const type& descr() const { \
- return dynamic_cast<const type&>(*descr_); \
+ const type& descr() const { \
+ const type* converted = static_cast<const type*>(descr_); \
+ assert(converted == dynamic_cast<const type*>(descr_)); \
+ return *converted; \
}
class MapObject {
=== modified file 'src/logic/map_objects/tribes/road_textures.h'
--- src/logic/map_objects/tribes/road_textures.h 2016-10-15 17:28:22 +0000
+++ src/logic/map_objects/tribes/road_textures.h 2017-01-07 12:36:16 +0000
@@ -34,6 +34,13 @@
const Image& get_normal_texture(const Widelands::Coords& coords, int direction) const;
const Image& get_busy_texture(const Widelands::Coords& coords, int direction) const;
+ const std::vector<const Image*>& get_normal_textures() const {
+ return normal_textures_;
+ }
+ const std::vector<const Image*>& get_busy_textures() const {
+ return busy_textures_;
+ }
+
// Adds a new road texture.
void add_normal_road_texture(const Image* texture);
void add_busy_road_texture(const Image* texture);
=== modified file 'src/logic/map_objects/world/terrain_description.cc'
--- src/logic/map_objects/world/terrain_description.cc 2016-12-03 13:32:28 +0000
+++ src/logic/map_objects/world/terrain_description.cc 2017-01-07 12:36:16 +0000
@@ -266,7 +266,7 @@
}
}
-const RGBColor& TerrainDescription::get_minimap_color(int shade) {
+const RGBColor& TerrainDescription::get_minimap_color(int shade) const {
assert(-128 <= shade && shade <= 127);
return minimap_colors_[128 + shade];
}
=== modified file 'src/logic/map_objects/world/terrain_description.h'
--- src/logic/map_objects/world/terrain_description.h 2016-08-04 15:49:05 +0000
+++ src/logic/map_objects/world/terrain_description.h 2017-01-07 12:36:16 +0000
@@ -80,7 +80,7 @@
// Return the basic terrain colour to be used in the minimap.
// 'shade' must be a brightness value, i.e. in [-128, 127].
- const RGBColor& get_minimap_color(int shade);
+ const RGBColor& get_minimap_color(int shade) const;
/// Returns the type of terrain this is (water, walkable, and so on).
Is get_is() const;
=== modified file 'src/logic/player.cc'
--- src/logic/player.cc 2016-12-18 17:02:44 +0000
+++ src/logic/player.cc 2017-01-07 12:36:16 +0000
@@ -133,6 +133,7 @@
civil_blds_lost_(0),
civil_blds_defeated_(0),
fields_(nullptr),
+ terrain_vision_version_(0),
allowed_worker_types_(the_egbase.tribes().nrworkers(), true),
allowed_building_types_(the_egbase.tribes().nrbuildings(), true),
ai_(""),
@@ -897,6 +898,9 @@
assert(&field < fields_ + map.max_index());
{ // discover everything (above the ground) in this field
+ if (field.terrains.d != f.field->get_terrains().d ||
+ field.terrains.r != f.field->get_terrains().r)
+ terrain_vision_version_++;
field.terrains = f.field->get_terrains();
field.roads = f.field->get_roads();
field.owner = f.field->get_owned_by();
@@ -961,6 +965,8 @@
FCoords tr = map.tr_n(f);
Field& tr_field = fields_[tr.field - &first_map_field];
if (tr_field.vision <= 1) {
+ if (tr_field.terrains.d != tr.field->terrain_d())
+ terrain_vision_version_++;
tr_field.terrains.d = tr.field->terrain_d();
tr_field.roads &= ~(RoadType::kMask << RoadType::kSouthWest);
tr_field.roads |= RoadType::kMask << RoadType::kSouthWest & tr.field->get_roads();
@@ -970,6 +976,9 @@
FCoords tl = map.tl_n(f);
Field& tl_field = fields_[tl.field - &first_map_field];
if (tl_field.vision <= 1) {
+ if (tl_field.terrains.d != tl.field->terrain_d() ||
+ tl_field.terrains.r != tl.field->terrain_r())
+ terrain_vision_version_++;
tl_field.terrains = tl.field->get_terrains();
tl_field.roads &= ~(RoadType::kMask << RoadType::kSouthEast);
tl_field.roads |= RoadType::kMask << RoadType::kSouthEast & tl.field->get_roads();
@@ -979,6 +988,8 @@
FCoords l = map.l_n(f);
Field& l_field = fields_[l.field - &first_map_field];
if (l_field.vision <= 1) {
+ if (l_field.terrains.r != l.field->terrain_r())
+ terrain_vision_version_++;
l_field.terrains.r = l.field->terrain_r();
l_field.roads &= ~(RoadType::kMask << RoadType::kEast);
l_field.roads |= RoadType::kMask << RoadType::kEast & l.field->get_roads();
=== modified file 'src/logic/player.h'
--- src/logic/player.h 2016-10-21 08:21:41 +0000
+++ src/logic/player.h 2017-01-07 12:36:16 +0000
@@ -434,6 +434,10 @@
return t;
}
+ uint32_t get_terrain_vision_version() const {
+ return terrain_vision_version_;
+ }
+
/**
* Update this player's information about this node and the surrounding
* triangles and edges.
@@ -633,6 +637,7 @@
std::unordered_set<std::string> remaining_shipnames_;
Field* fields_;
+ uint32_t terrain_vision_version_; /**< for lazy updates of rendering data, not saved */
std::vector<bool> allowed_worker_types_;
std::vector<bool> allowed_building_types_;
Economies economies_;
=== modified file 'src/wlapplication.cc'
--- src/wlapplication.cc 2016-12-09 20:01:12 +0000
+++ src/wlapplication.cc 2017-01-07 12:36:16 +0000
@@ -739,6 +739,7 @@
s.get_int("panel_snap_distance");
s.get_int("autosave");
s.get_int("rolling_autosave");
+ s.get_bool("disable_gl4");
s.get_bool("single_watchwin");
s.get_bool("auto_roadbuild_mode");
s.get_bool("workareapreview");
=== modified file 'src/wui/mapview.cc'
--- src/wui/mapview.cc 2016-11-16 10:01:52 +0000
+++ src/wui/mapview.cc 2017-01-07 12:36:16 +0000
@@ -60,7 +60,7 @@
MapView::MapView(
UI::Panel* parent, int32_t x, int32_t y, uint32_t w, uint32_t h, InteractiveBase& player)
: UI::Panel(parent, x, y, w, h),
- renderer_(new GameRenderer()),
+ renderer_(GameRenderer::create()),
intbase_(player),
viewpoint_(0.f, 0.f),
zoom_(1.f),
=== modified file 'src/wui/minimap.cc'
--- src/wui/minimap.cc 2016-10-24 14:07:28 +0000
+++ src/wui/minimap.cc 2017-01-07 12:36:16 +0000
@@ -50,9 +50,14 @@
}
void MiniMap::View::draw(RenderTarget& dst) {
- minimap_image_ = draw_minimap(ibase_.egbase(), ibase_.get_player(), view_area_, *minimap_type_,
- *minimap_layers_ | MiniMapLayer::ViewWindow);
- dst.blit(Vector2f(), minimap_image_.get());
+ const Widelands::EditorGameBase& egbase = ibase_.egbase();
+ const Widelands::Player* player = ibase_.get_player();
+
+ if (!renderer_ || &egbase != &renderer_->egbase() || player != renderer_->player())
+ renderer_ = MiniMapRenderer::create(egbase, player);
+
+ renderer_->draw(dst, view_area_, *minimap_type_,
+ *minimap_layers_ | MiniMapLayer::ViewWindow);
}
/*
=== modified file 'src/wui/minimap.h'
--- src/wui/minimap.h 2016-11-03 07:20:57 +0000
+++ src/wui/minimap.h 2017-01-07 12:36:16 +0000
@@ -87,7 +87,7 @@
// This needs to be owned since it will be rendered by the RenderQueue
// later, so it must be valid for the whole frame.
- std::unique_ptr<Texture> minimap_image_;
+ std::unique_ptr<MiniMapRenderer> renderer_;
public:
MiniMapLayer* minimap_layers_;
Follow ups
-
Re: [Merge] lp:~widelands-dev/widelands/graphics into lp:widelands
From: GunChleoc, 2017-04-10
-
[Merge] lp:~widelands-dev/widelands/graphics into lp:widelands
From: GunChleoc, 2017-04-10
-
[Merge] lp:~widelands-dev/widelands/graphics into lp:widelands
From: SirVer, 2017-04-10
-
Re: [Merge] lp:~widelands-dev/widelands/graphics into lp:widelands
From: SirVer, 2017-04-10
-
Re: [Merge] lp:~widelands-dev/widelands/graphics into lp:widelands
From: SirVer, 2017-04-10
-
[Merge] lp:~widelands-dev/widelands/graphics into lp:widelands
From: bunnybot, 2017-01-15
-
[Merge] lp:~nha/widelands/graphics into lp:widelands
From: bunnybot, 2017-01-10
-
[Merge] lp:~nha/widelands/graphics into lp:widelands
From: bunnybot, 2017-01-10
-
[Merge] lp:~nha/widelands/graphics into lp:widelands
From: bunnybot, 2017-01-10
-
[Merge] lp:~nha/widelands/graphics into lp:widelands
From: bunnybot, 2017-01-10
-
Re: [Merge] lp:~nha/widelands/graphics into lp:widelands
From: Nicolai Hähnle, 2017-01-09
-
Re: [Merge] lp:~nha/widelands/graphics into lp:widelands
From: SirVer, 2017-01-08
-
Re: [Merge] lp:~nha/widelands/graphics into lp:widelands
From: Klaus Halfmann, 2017-01-07
-
Re: [Merge] lp:~nha/widelands/graphics into lp:widelands
From: Klaus Halfmann, 2017-01-07
-
[Merge] lp:~nha/widelands/graphics into lp:widelands
From: bunnybot, 2017-01-07
-
Re: [Merge] lp:~nha/widelands/graphics into lp:widelands
From: Klaus Halfmann, 2017-01-07