From 275183ef150dc4738d8dcedaa5c99d6f3404821b Mon Sep 17 00:00:00 2001 From: lawnjelly Date: Wed, 3 Jun 2020 10:54:05 +0100 Subject: [PATCH] GLES2 batching - Add UV precision adjustment for tilemaps Scaling tilemaps can cause border artifacts around the edges of tiles. This has been traced to precision issues in the GPU. This PR adds an adjustment to allow a minor contraction of the UVs of rects in order to compensate for the incorrect classification of texels across the UV border. --- doc/classes/ProjectSettings.xml | 9 ++++ drivers/gles2/rasterizer_canvas_gles2.cpp | 53 ++++++++++++++++++----- drivers/gles2/rasterizer_canvas_gles2.h | 7 +++ servers/visual_server.cpp | 3 ++ 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 3658c9cd1f5..cc8642f4805 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1003,6 +1003,14 @@ Sets the number of commands to lookahead to determine whether to batch render items. A value of 1 can join items consisting of single commands, 0 turns off joining. Higher values are in theory more likely to join, however this has diminishing returns and has a runtime cost so a small value is recommended. + + On some platforms (especially mobile), precision issues in shaders can lead to reading 1 texel outside of bounds, particularly where rects are scaled. This can particularly lead to border artifacts around tiles in tilemaps. + This adjustment corrects for this by making a small contraction to the UV coordinates used. Note that this can result in a slight squashing of border texels. + + + The amount of UV contraction. This figure is divided by 1000000, and is a proportion of the total texture dimensions, where the width and height are both ranged from 0.0 to 1.0. + Use the default unless correcting for a problem on particular hardware. + Default background clear color. Overridable per [Viewport] using its [Environment]. See [member Environment.background_mode] and [member Environment.background_color] in particular. To change this default color programmatically, use [method VisualServer.set_default_clear_color]. @@ -1042,6 +1050,7 @@ If [code]true[/code], forces snapping of polygons to pixels in 2D rendering. May help in some pixel art styles. + Consider using the project setting [member rendering/batching/precision/uv_contract] to prevent artifacts. If [code]true[/code], allocates the main framebuffer with high dynamic range. High dynamic range allows the use of [Color] values greater than 1. diff --git a/drivers/gles2/rasterizer_canvas_gles2.cpp b/drivers/gles2/rasterizer_canvas_gles2.cpp index b5ea3c993f9..edf47f6f4d1 100644 --- a/drivers/gles2/rasterizer_canvas_gles2.cpp +++ b/drivers/gles2/rasterizer_canvas_gles2.cpp @@ -75,6 +75,9 @@ RasterizerCanvasGLES2::BatchData::BatchData() { settings_use_single_rect_fallback = false; settings_light_max_join_items = 16; + settings_uv_contract = false; + settings_uv_contract_amount = 0.0f; + stats_items_sorted = 0; stats_light_items_joined = 0; } @@ -165,10 +168,12 @@ int RasterizerCanvasGLES2::_batch_find_or_create_tex(const RID &p_texture, const if (texture) { new_batch_tex.tex_pixel_size.x = 1.0 / texture->width; new_batch_tex.tex_pixel_size.y = 1.0 / texture->height; + new_batch_tex.flags = texture->flags; } else { // maybe doesn't need doing... new_batch_tex.tex_pixel_size.x = 1.0; new_batch_tex.tex_pixel_size.y = 1.0; + new_batch_tex.flags = 0; } if (p_tile) { @@ -246,8 +251,9 @@ bool RasterizerCanvasGLES2::prefill_joined_item(FillState &r_fill_state, int &r_ int command_count = p_item->commands.size(); Item::Command *const *commands = p_item->commands.ptr(); - // just a local, might be more efficient in a register (check) + // locals, might be more efficient in a register (check) Vector2 texpixel_size = r_fill_state.texpixel_size; + const float uv_epsilon = bdata.settings_uv_contract_amount; // checking the color for not being white makes it 92/90 times faster in the case where it is white bool multiply_final_modulate = false; @@ -382,7 +388,12 @@ bool RasterizerCanvasGLES2::prefill_joined_item(FillState &r_fill_state, int &r_ if (change_batch) { // put the tex pixel size in a local (less verbose and can be a register) - bdata.batch_textures[r_fill_state.batch_tex_id].tex_pixel_size.to(texpixel_size); + const BatchTex &batchtex = bdata.batch_textures[r_fill_state.batch_tex_id]; + batchtex.tex_pixel_size.to(texpixel_size); + + if (bdata.settings_uv_contract) { + r_fill_state.contract_uvs = (batchtex.flags & VS::TEXTURE_FLAG_FILTER) == 0; + } // need to preserve texpixel_size between items r_fill_state.texpixel_size = texpixel_size; @@ -445,15 +456,34 @@ bool RasterizerCanvasGLES2::prefill_joined_item(FillState &r_fill_state, int &r_ } // uvs - Rect2 src_rect = (rect->flags & CANVAS_RECT_REGION) ? Rect2(rect->source.position * texpixel_size, rect->source.size * texpixel_size) : Rect2(0, 0, 1, 1); + Vector2 src_min; + Vector2 src_max; + if (rect->flags & CANVAS_RECT_REGION) { + src_min = rect->source.position; + src_max = src_min + rect->source.size; + + src_min *= texpixel_size; + src_max *= texpixel_size; + + // nudge offset for the maximum to prevent precision error on GPU reading into line outside the source rect + // this is very difficult to get right. + if (r_fill_state.contract_uvs) { + src_min.x += uv_epsilon; + src_min.y += uv_epsilon; + src_max.x -= uv_epsilon; + src_max.y -= uv_epsilon; + } + } else { + src_min = Vector2(0, 0); + src_max = Vector2(1, 1); + } // 10% faster calculating the max first - Vector2 pos_max = src_rect.position + src_rect.size; Vector2 uvs[4] = { - src_rect.position, - Vector2(pos_max.x, src_rect.position.y), - pos_max, - Vector2(src_rect.position.x, pos_max.y), + src_min, + Vector2(src_max.x, src_min.y), + src_max, + Vector2(src_min.x, src_max.y), }; if (rect->flags & CANVAS_RECT_TRANSPOSE) { @@ -2810,7 +2840,6 @@ void RasterizerCanvasGLES2::_canvas_render_item(Item *p_ci, RenderItemState &r_r ci->final_modulate.b * p_modulate.b, ci->final_modulate.a * p_modulate.a ); - state.canvas_shader.set_uniform(CanvasShaderGLES2::MODELVIEW_MATRIX,state.final_transform); state.canvas_shader.set_uniform(CanvasShaderGLES2::EXTRA_MATRIX,Transform2D()); state.canvas_shader.set_uniform(CanvasShaderGLES2::FINAL_MODULATE,state.canvas_item_modulate); @@ -3220,7 +3249,6 @@ void RasterizerCanvasGLES2::render_joined_item(const BItemJoined &p_bij, RenderI ci->final_modulate.b * p_modulate.b, ci->final_modulate.a * p_modulate.a ); - state.canvas_shader.set_uniform(CanvasShaderGLES2::MODELVIEW_MATRIX,state.final_transform); state.canvas_shader.set_uniform(CanvasShaderGLES2::EXTRA_MATRIX,Transform2D()); state.canvas_shader.set_uniform(CanvasShaderGLES2::FINAL_MODULATE,state.canvas_item_modulate); @@ -3346,6 +3374,11 @@ void RasterizerCanvasGLES2::initialize() { bdata.settings_light_max_join_items = GLOBAL_GET("rendering/batching/lights/max_join_items"); bdata.settings_use_single_rect_fallback = GLOBAL_GET("rendering/batching/options/single_rect_fallback"); + // alternatively only enable uv contract if pixel snap in use, + // but with this enable bool, it should not be necessary + bdata.settings_uv_contract = GLOBAL_GET("rendering/batching/precision/uv_contract"); + bdata.settings_uv_contract_amount = (float)GLOBAL_GET("rendering/batching/precision/uv_contract_amount") / 1000000.0f; + // we can use the threshold to determine whether to turn scissoring off or on bdata.settings_scissor_threshold = GLOBAL_GET("rendering/batching/lights/scissor_area_threshold"); if (bdata.settings_scissor_threshold > 0.999f) { diff --git a/drivers/gles2/rasterizer_canvas_gles2.h b/drivers/gles2/rasterizer_canvas_gles2.h index c7ed774663d..c7c1a26a2c0 100644 --- a/drivers/gles2/rasterizer_canvas_gles2.h +++ b/drivers/gles2/rasterizer_canvas_gles2.h @@ -113,6 +113,7 @@ class RasterizerCanvasGLES2 : public RasterizerCanvasBaseGLES2 { RID RID_normal; TileMode tile_mode; BatchVector2 tex_pixel_size; + uint32_t flags; }; // items in a list to be sorted prior to joining @@ -237,6 +238,10 @@ class RasterizerCanvasGLES2 : public RasterizerCanvasBaseGLES2 { bool settings_use_single_rect_fallback; int settings_light_max_join_items; + // uv contraction + bool settings_uv_contract; + float settings_uv_contract_amount; + // only done on diagnose frame void reset_stats() { stats_items_sorted = 0; @@ -278,10 +283,12 @@ class RasterizerCanvasGLES2 : public RasterizerCanvasBaseGLES2 { curr_batch = 0; batch_tex_id = -1; texpixel_size = Vector2(1, 1); + contract_uvs = false; } Batch *curr_batch; int batch_tex_id; bool use_hardware_transform; + bool contract_uvs; Vector2 texpixel_size; Color final_modulate; TransformMode transform_mode; diff --git a/servers/visual_server.cpp b/servers/visual_server.cpp index 86a905a28ef..a4a399998bb 100644 --- a/servers/visual_server.cpp +++ b/servers/visual_server.cpp @@ -2432,6 +2432,8 @@ VisualServer::VisualServer() { GLOBAL_DEF("rendering/batching/debug/flash_batching", false); GLOBAL_DEF("rendering/batching/debug/diagnose_frame", false); GLOBAL_DEF("rendering/gles2/compatibility/disable_half_float", false); + GLOBAL_DEF("rendering/batching/precision/uv_contract", false); + GLOBAL_DEF("rendering/batching/precision/uv_contract_amount", 100); ProjectSettings::get_singleton()->set_custom_property_info("rendering/batching/parameters/max_join_item_commands", PropertyInfo(Variant::INT, "rendering/batching/parameters/max_join_item_commands", PROPERTY_HINT_RANGE, "0,65535")); ProjectSettings::get_singleton()->set_custom_property_info("rendering/batching/parameters/colored_vertex_format_threshold", PropertyInfo(Variant::REAL, "rendering/batching/parameters/colored_vertex_format_threshold", PROPERTY_HINT_RANGE, "0.0,1.0,0.01")); @@ -2439,6 +2441,7 @@ VisualServer::VisualServer() { ProjectSettings::get_singleton()->set_custom_property_info("rendering/batching/lights/scissor_area_threshold", PropertyInfo(Variant::REAL, "rendering/batching/lights/scissor_area_threshold", PROPERTY_HINT_RANGE, "0.0,1.0")); ProjectSettings::get_singleton()->set_custom_property_info("rendering/batching/lights/max_join_items", PropertyInfo(Variant::INT, "rendering/batching/lights/max_join_items", PROPERTY_HINT_RANGE, "0,512")); ProjectSettings::get_singleton()->set_custom_property_info("rendering/batching/parameters/item_reordering_lookahead", PropertyInfo(Variant::INT, "rendering/batching/parameters/item_reordering_lookahead", PROPERTY_HINT_RANGE, "0,256")); + ProjectSettings::get_singleton()->set_custom_property_info("rendering/batching/precision/uv_contract_amount", PropertyInfo(Variant::INT, "rendering/batching/precision/uv_contract_amount", PROPERTY_HINT_RANGE, "0,10000")); } VisualServer::~VisualServer() {