GLES2: Make Nvidia flicker workaround opt-in

It has a big impact on 2D and text rendering performance (cf. #24466)
so the solution seems worse than the bug it aims to work around.

It's now opt-in via "rendering/quality/2d/gles2_use_nvidia_rect_flicker_workaround"
for those who need it and have a simple enough game for the performance
drop not to be an issue.

Fixes #24466.
This commit is contained in:
Rémi Verschelde 2019-01-14 18:26:41 +01:00
parent 6f884cc884
commit 02ffc59270
5 changed files with 163 additions and 147 deletions

View File

@ -1163,8 +1163,6 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("input/ui_end", action);
input_presets.push_back("input/ui_end");
//GLOBAL_DEF("display/window/handheld/orientation", "landscape");
custom_prop_info["display/window/handheld/orientation"] = PropertyInfo(Variant::STRING, "display/window/handheld/orientation", PROPERTY_HINT_ENUM, "landscape,portrait,reverse_landscape,reverse_portrait,sensor_landscape,sensor_portrait,sensor");
custom_prop_info["rendering/threads/thread_model"] = PropertyInfo(Variant::INT, "rendering/threads/thread_model", PROPERTY_HINT_ENUM, "Single-Unsafe,Single-Safe,Multi-Threaded");
custom_prop_info["physics/2d/thread_model"] = PropertyInfo(Variant::INT, "physics/2d/thread_model", PROPERTY_HINT_ENUM, "Single-Unsafe,Single-Safe,Multi-Threaded");

View File

@ -670,6 +670,10 @@
<member name="rendering/limits/time/time_rollover_secs" type="float" setter="" getter="">
Shaders have a time variable that constantly increases. At some point it needs to be rolled back to zero to avoid numerical errors on shader animations. This setting specifies when.
</member>
<member name="rendering/quality/2d/gles2_use_nvidia_rect_flicker_workaround" type="bool" setter="" getter="">
Some Nvidia GPU drivers have a bug, which produces flickering issues for the [code]draw_rect[/code] method, especially as used in [TileMap]. Refer to https://github.com/godotengine/godot/issues/9913 for details.
If [code]true[/code], this option enables a "safe" code path for such Nvidia GPUs, at the cost of performance. This option only impacts the GLES2 rendering backend (so the bug stays if you use GLES3), and only desktop platforms. Default value: [code]false[/code].
</member>
<member name="rendering/quality/2d/use_pixel_snap" type="bool" setter="" getter="">
Force snapping of polygons to pixels in 2D rendering. May help in some pixel art styles.
</member>

View File

@ -489,161 +489,164 @@ void RasterizerCanvasGLES2::_canvas_item_render_commands(Item *p_item, Item *cur
glDisableVertexAttribArray(VS::ARRAY_COLOR);
glVertexAttrib4fv(VS::ARRAY_COLOR, r->modulate.components);
//use a more compatible workaround, as this does not fail on nvidia
#ifdef GLES_OVER_GL
//more compatible
state.canvas_shader.set_conditional(CanvasShaderGLES2::USE_TEXTURE_RECT, false);
// On some widespread Nvidia cards, the normal draw method can produce some
// flickering in draw_rect and especially TileMap rendering (tiles randomly flicker).
// See GH-9913.
// To work it around, we use a simpler draw method which does not flicker, but gives
// a non negligible performance hit, so it's opt-in (GH-24466).
if (use_nvidia_rect_workaround) {
state.canvas_shader.set_conditional(CanvasShaderGLES2::USE_TEXTURE_RECT, false);
if (state.canvas_shader.bind()) {
_set_uniforms();
state.canvas_shader.use_material((void *)p_material);
}
if (state.canvas_shader.bind()) {
_set_uniforms();
state.canvas_shader.use_material((void *)p_material);
}
Vector2 points[4] = {
r->rect.position,
r->rect.position + Vector2(r->rect.size.x, 0.0),
r->rect.position + r->rect.size,
r->rect.position + Vector2(0.0, r->rect.size.y),
};
if (r->rect.size.x < 0) {
SWAP(points[0], points[1]);
SWAP(points[2], points[3]);
}
if (r->rect.size.y < 0) {
SWAP(points[0], points[3]);
SWAP(points[1], points[2]);
}
RasterizerStorageGLES2::Texture *texture = _bind_canvas_texture(r->texture, r->normal_map);
if (texture) {
Size2 texpixel_size(1.0 / texture->width, 1.0 / texture->height);
Rect2 src_rect = (r->flags & CANVAS_RECT_REGION) ? Rect2(r->source.position * texpixel_size, r->source.size * texpixel_size) : Rect2(0, 0, 1, 1);
Vector2 uvs[4] = {
src_rect.position,
src_rect.position + Vector2(src_rect.size.x, 0.0),
src_rect.position + src_rect.size,
src_rect.position + Vector2(0.0, src_rect.size.y),
Vector2 points[4] = {
r->rect.position,
r->rect.position + Vector2(r->rect.size.x, 0.0),
r->rect.position + r->rect.size,
r->rect.position + Vector2(0.0, r->rect.size.y),
};
if (r->flags & CANVAS_RECT_TRANSPOSE) {
SWAP(uvs[1], uvs[3]);
if (r->rect.size.x < 0) {
SWAP(points[0], points[1]);
SWAP(points[2], points[3]);
}
if (r->rect.size.y < 0) {
SWAP(points[0], points[3]);
SWAP(points[1], points[2]);
}
if (r->flags & CANVAS_RECT_FLIP_H) {
SWAP(uvs[0], uvs[1]);
SWAP(uvs[2], uvs[3]);
}
if (r->flags & CANVAS_RECT_FLIP_V) {
SWAP(uvs[0], uvs[3]);
SWAP(uvs[1], uvs[2]);
RasterizerStorageGLES2::Texture *texture = _bind_canvas_texture(r->texture, r->normal_map);
if (texture) {
Size2 texpixel_size(1.0 / texture->width, 1.0 / texture->height);
Rect2 src_rect = (r->flags & CANVAS_RECT_REGION) ? Rect2(r->source.position * texpixel_size, r->source.size * texpixel_size) : Rect2(0, 0, 1, 1);
Vector2 uvs[4] = {
src_rect.position,
src_rect.position + Vector2(src_rect.size.x, 0.0),
src_rect.position + src_rect.size,
src_rect.position + Vector2(0.0, src_rect.size.y),
};
if (r->flags & CANVAS_RECT_TRANSPOSE) {
SWAP(uvs[1], uvs[3]);
}
if (r->flags & CANVAS_RECT_FLIP_H) {
SWAP(uvs[0], uvs[1]);
SWAP(uvs[2], uvs[3]);
}
if (r->flags & CANVAS_RECT_FLIP_V) {
SWAP(uvs[0], uvs[3]);
SWAP(uvs[1], uvs[2]);
}
state.canvas_shader.set_uniform(CanvasShaderGLES2::COLOR_TEXPIXEL_SIZE, texpixel_size);
bool untile = false;
if (r->flags & CANVAS_RECT_TILE && !(texture->flags & VS::TEXTURE_FLAG_REPEAT)) {
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
untile = true;
}
_draw_gui_primitive(4, points, NULL, uvs);
if (untile) {
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
} else {
state.canvas_shader.set_uniform(CanvasShaderGLES2::COLOR_TEXPIXEL_SIZE, Vector2());
_draw_gui_primitive(4, points, NULL, NULL);
}
state.canvas_shader.set_uniform(CanvasShaderGLES2::COLOR_TEXPIXEL_SIZE, texpixel_size);
bool untile = false;
if (r->flags & CANVAS_RECT_TILE && !(texture->flags & VS::TEXTURE_FLAG_REPEAT)) {
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
untile = true;
}
_draw_gui_primitive(4, points, NULL, uvs);
if (untile) {
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
} else {
state.canvas_shader.set_uniform(CanvasShaderGLES2::COLOR_TEXPIXEL_SIZE, Vector2());
_draw_gui_primitive(4, points, NULL, NULL);
// This branch is better for performance, but can produce flicker on Nvidia, see above comment.
_bind_quad_buffer();
state.canvas_shader.set_conditional(CanvasShaderGLES2::USE_TEXTURE_RECT, true);
if (state.canvas_shader.bind()) {
_set_uniforms();
state.canvas_shader.use_material((void *)p_material);
}
RasterizerStorageGLES2::Texture *tex = _bind_canvas_texture(r->texture, r->normal_map);
if (!tex) {
Rect2 dst_rect = Rect2(r->rect.position, r->rect.size);
if (dst_rect.size.width < 0) {
dst_rect.position.x += dst_rect.size.width;
dst_rect.size.width *= -1;
}
if (dst_rect.size.height < 0) {
dst_rect.position.y += dst_rect.size.height;
dst_rect.size.height *= -1;
}
state.canvas_shader.set_uniform(CanvasShaderGLES2::DST_RECT, Color(dst_rect.position.x, dst_rect.position.y, dst_rect.size.x, dst_rect.size.y));
state.canvas_shader.set_uniform(CanvasShaderGLES2::SRC_RECT, Color(0, 0, 1, 1));
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
} else {
bool untile = false;
if (r->flags & CANVAS_RECT_TILE && !(tex->flags & VS::TEXTURE_FLAG_REPEAT)) {
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
untile = true;
}
Size2 texpixel_size(1.0 / tex->width, 1.0 / tex->height);
Rect2 src_rect = (r->flags & CANVAS_RECT_REGION) ? Rect2(r->source.position * texpixel_size, r->source.size * texpixel_size) : Rect2(0, 0, 1, 1);
Rect2 dst_rect = Rect2(r->rect.position, r->rect.size);
if (dst_rect.size.width < 0) {
dst_rect.position.x += dst_rect.size.width;
dst_rect.size.width *= -1;
}
if (dst_rect.size.height < 0) {
dst_rect.position.y += dst_rect.size.height;
dst_rect.size.height *= -1;
}
if (r->flags & CANVAS_RECT_FLIP_H) {
src_rect.size.x *= -1;
}
if (r->flags & CANVAS_RECT_FLIP_V) {
src_rect.size.y *= -1;
}
if (r->flags & CANVAS_RECT_TRANSPOSE) {
dst_rect.size.x *= -1; // Encoding in the dst_rect.z uniform
}
state.canvas_shader.set_uniform(CanvasShaderGLES2::COLOR_TEXPIXEL_SIZE, texpixel_size);
state.canvas_shader.set_uniform(CanvasShaderGLES2::DST_RECT, Color(dst_rect.position.x, dst_rect.position.y, dst_rect.size.x, dst_rect.size.y));
state.canvas_shader.set_uniform(CanvasShaderGLES2::SRC_RECT, Color(src_rect.position.x, src_rect.position.y, src_rect.size.x, src_rect.size.y));
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
if (untile) {
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
#else
//disabled because it fails on buggy nvidia drivers
_bind_quad_buffer();
state.canvas_shader.set_conditional(CanvasShaderGLES2::USE_TEXTURE_RECT, true);
if (state.canvas_shader.bind()) {
_set_uniforms();
state.canvas_shader.use_material((void *)p_material);
}
RasterizerStorageGLES2::Texture *tex = _bind_canvas_texture(r->texture, r->normal_map);
if (!tex) {
Rect2 dst_rect = Rect2(r->rect.position, r->rect.size);
if (dst_rect.size.width < 0) {
dst_rect.position.x += dst_rect.size.width;
dst_rect.size.width *= -1;
}
if (dst_rect.size.height < 0) {
dst_rect.position.y += dst_rect.size.height;
dst_rect.size.height *= -1;
}
state.canvas_shader.set_uniform(CanvasShaderGLES2::DST_RECT, Color(dst_rect.position.x, dst_rect.position.y, dst_rect.size.x, dst_rect.size.y));
state.canvas_shader.set_uniform(CanvasShaderGLES2::SRC_RECT, Color(0, 0, 1, 1));
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
} else {
bool untile = false;
if (r->flags & CANVAS_RECT_TILE && !(tex->flags & VS::TEXTURE_FLAG_REPEAT)) {
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
untile = true;
}
Size2 texpixel_size(1.0 / tex->width, 1.0 / tex->height);
Rect2 src_rect = (r->flags & CANVAS_RECT_REGION) ? Rect2(r->source.position * texpixel_size, r->source.size * texpixel_size) : Rect2(0, 0, 1, 1);
Rect2 dst_rect = Rect2(r->rect.position, r->rect.size);
if (dst_rect.size.width < 0) {
dst_rect.position.x += dst_rect.size.width;
dst_rect.size.width *= -1;
}
if (dst_rect.size.height < 0) {
dst_rect.position.y += dst_rect.size.height;
dst_rect.size.height *= -1;
}
if (r->flags & CANVAS_RECT_FLIP_H) {
src_rect.size.x *= -1;
}
if (r->flags & CANVAS_RECT_FLIP_V) {
src_rect.size.y *= -1;
}
if (r->flags & CANVAS_RECT_TRANSPOSE) {
dst_rect.size.x *= -1; // Encoding in the dst_rect.z uniform
}
state.canvas_shader.set_uniform(CanvasShaderGLES2::COLOR_TEXPIXEL_SIZE, texpixel_size);
state.canvas_shader.set_uniform(CanvasShaderGLES2::DST_RECT, Color(dst_rect.position.x, dst_rect.position.y, dst_rect.size.x, dst_rect.size.y));
state.canvas_shader.set_uniform(CanvasShaderGLES2::SRC_RECT, Color(src_rect.position.x, src_rect.position.y, src_rect.size.x, src_rect.size.y));
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
if (untile) {
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
#endif
} break;
case Item::Command::TYPE_NINEPATCH: {
@ -1856,4 +1859,10 @@ void RasterizerCanvasGLES2::finalize() {
}
RasterizerCanvasGLES2::RasterizerCanvasGLES2() {
#ifdef GLES_OVER_GL
use_nvidia_rect_workaround = GLOBAL_GET("rendering/quality/2d/gles2_use_nvidia_rect_flicker_workaround");
#else
// Not needed (a priori) on GLES devices
use_nvidia_rect_workaround = false;
#endif
}

View File

@ -102,6 +102,8 @@ public:
RasterizerStorageGLES2 *storage;
bool use_nvidia_rect_workaround;
virtual RID light_internal_create();
virtual void light_internal_update(RID p_rid, Light *p_light);
virtual void light_internal_free(RID p_rid);

View File

@ -880,6 +880,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF("rendering/quality/driver/driver_fallback", "Best");
ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/driver/driver_fallback", PropertyInfo(Variant::STRING, "rendering/quality/driver/driver_fallback", PROPERTY_HINT_ENUM, "Best,Never"));
// Assigning here even though it's GLES2-specific, to be sure that it appears in docs
GLOBAL_DEF("rendering/quality/2d/gles2_use_nvidia_rect_flicker_workaround", false);
GLOBAL_DEF("display/window/size/width", 1024);
ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/width", PropertyInfo(Variant::INT, "display/window/size/width", PROPERTY_HINT_RANGE, "0,7680,or_greater")); // 8K resolution
GLOBAL_DEF("display/window/size/height", 600);