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() {