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.
This commit is contained in:
lawnjelly 2020-06-03 10:54:05 +01:00
parent 6e1af78df4
commit 275183ef15
4 changed files with 62 additions and 10 deletions

View File

@ -1003,6 +1003,14 @@
<member name="rendering/batching/parameters/max_join_item_commands" type="int" setter="" getter="" default="16"> <member name="rendering/batching/parameters/max_join_item_commands" type="int" setter="" getter="" default="16">
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. 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.
</member> </member>
<member name="rendering/batching/precision/uv_contract" type="bool" setter="" getter="" default="false">
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.
</member>
<member name="rendering/batching/precision/uv_contract_amount" type="int" setter="" getter="" default="100">
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.
</member>
<member name="rendering/environment/default_clear_color" type="Color" setter="" getter="" default="Color( 0.3, 0.3, 0.3, 1 )"> <member name="rendering/environment/default_clear_color" type="Color" setter="" getter="" default="Color( 0.3, 0.3, 0.3, 1 )">
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]. 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].
</member> </member>
@ -1042,6 +1050,7 @@
</member> </member>
<member name="rendering/quality/2d/use_pixel_snap" type="bool" setter="" getter="" default="false"> <member name="rendering/quality/2d/use_pixel_snap" type="bool" setter="" getter="" default="false">
If [code]true[/code], forces snapping of polygons to pixels in 2D rendering. May help in some pixel art styles. 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.
</member> </member>
<member name="rendering/quality/depth/hdr" type="bool" setter="" getter="" default="true"> <member name="rendering/quality/depth/hdr" type="bool" setter="" getter="" default="true">
If [code]true[/code], allocates the main framebuffer with high dynamic range. High dynamic range allows the use of [Color] values greater than 1. If [code]true[/code], allocates the main framebuffer with high dynamic range. High dynamic range allows the use of [Color] values greater than 1.

View File

@ -75,6 +75,9 @@ RasterizerCanvasGLES2::BatchData::BatchData() {
settings_use_single_rect_fallback = false; settings_use_single_rect_fallback = false;
settings_light_max_join_items = 16; settings_light_max_join_items = 16;
settings_uv_contract = false;
settings_uv_contract_amount = 0.0f;
stats_items_sorted = 0; stats_items_sorted = 0;
stats_light_items_joined = 0; stats_light_items_joined = 0;
} }
@ -165,10 +168,12 @@ int RasterizerCanvasGLES2::_batch_find_or_create_tex(const RID &p_texture, const
if (texture) { if (texture) {
new_batch_tex.tex_pixel_size.x = 1.0 / texture->width; 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.tex_pixel_size.y = 1.0 / texture->height;
new_batch_tex.flags = texture->flags;
} else { } else {
// maybe doesn't need doing... // maybe doesn't need doing...
new_batch_tex.tex_pixel_size.x = 1.0; new_batch_tex.tex_pixel_size.x = 1.0;
new_batch_tex.tex_pixel_size.y = 1.0; new_batch_tex.tex_pixel_size.y = 1.0;
new_batch_tex.flags = 0;
} }
if (p_tile) { 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(); int command_count = p_item->commands.size();
Item::Command *const *commands = p_item->commands.ptr(); 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; 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 // 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; bool multiply_final_modulate = false;
@ -382,7 +388,12 @@ bool RasterizerCanvasGLES2::prefill_joined_item(FillState &r_fill_state, int &r_
if (change_batch) { if (change_batch) {
// put the tex pixel size in a local (less verbose and can be a register) // 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 // need to preserve texpixel_size between items
r_fill_state.texpixel_size = texpixel_size; r_fill_state.texpixel_size = texpixel_size;
@ -445,15 +456,34 @@ bool RasterizerCanvasGLES2::prefill_joined_item(FillState &r_fill_state, int &r_
} }
// uvs // 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 // 10% faster calculating the max first
Vector2 pos_max = src_rect.position + src_rect.size;
Vector2 uvs[4] = { Vector2 uvs[4] = {
src_rect.position, src_min,
Vector2(pos_max.x, src_rect.position.y), Vector2(src_max.x, src_min.y),
pos_max, src_max,
Vector2(src_rect.position.x, pos_max.y), Vector2(src_min.x, src_max.y),
}; };
if (rect->flags & CANVAS_RECT_TRANSPOSE) { 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.b * p_modulate.b,
ci->final_modulate.a * p_modulate.a ); ci->final_modulate.a * p_modulate.a );
state.canvas_shader.set_uniform(CanvasShaderGLES2::MODELVIEW_MATRIX,state.final_transform); 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::EXTRA_MATRIX,Transform2D());
state.canvas_shader.set_uniform(CanvasShaderGLES2::FINAL_MODULATE,state.canvas_item_modulate); 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.b * p_modulate.b,
ci->final_modulate.a * p_modulate.a ); ci->final_modulate.a * p_modulate.a );
state.canvas_shader.set_uniform(CanvasShaderGLES2::MODELVIEW_MATRIX,state.final_transform); 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::EXTRA_MATRIX,Transform2D());
state.canvas_shader.set_uniform(CanvasShaderGLES2::FINAL_MODULATE,state.canvas_item_modulate); 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_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"); 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 // 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"); bdata.settings_scissor_threshold = GLOBAL_GET("rendering/batching/lights/scissor_area_threshold");
if (bdata.settings_scissor_threshold > 0.999f) { if (bdata.settings_scissor_threshold > 0.999f) {

View File

@ -113,6 +113,7 @@ class RasterizerCanvasGLES2 : public RasterizerCanvasBaseGLES2 {
RID RID_normal; RID RID_normal;
TileMode tile_mode; TileMode tile_mode;
BatchVector2 tex_pixel_size; BatchVector2 tex_pixel_size;
uint32_t flags;
}; };
// items in a list to be sorted prior to joining // items in a list to be sorted prior to joining
@ -237,6 +238,10 @@ class RasterizerCanvasGLES2 : public RasterizerCanvasBaseGLES2 {
bool settings_use_single_rect_fallback; bool settings_use_single_rect_fallback;
int settings_light_max_join_items; int settings_light_max_join_items;
// uv contraction
bool settings_uv_contract;
float settings_uv_contract_amount;
// only done on diagnose frame // only done on diagnose frame
void reset_stats() { void reset_stats() {
stats_items_sorted = 0; stats_items_sorted = 0;
@ -278,10 +283,12 @@ class RasterizerCanvasGLES2 : public RasterizerCanvasBaseGLES2 {
curr_batch = 0; curr_batch = 0;
batch_tex_id = -1; batch_tex_id = -1;
texpixel_size = Vector2(1, 1); texpixel_size = Vector2(1, 1);
contract_uvs = false;
} }
Batch *curr_batch; Batch *curr_batch;
int batch_tex_id; int batch_tex_id;
bool use_hardware_transform; bool use_hardware_transform;
bool contract_uvs;
Vector2 texpixel_size; Vector2 texpixel_size;
Color final_modulate; Color final_modulate;
TransformMode transform_mode; TransformMode transform_mode;

View File

@ -2432,6 +2432,8 @@ VisualServer::VisualServer() {
GLOBAL_DEF("rendering/batching/debug/flash_batching", false); GLOBAL_DEF("rendering/batching/debug/flash_batching", false);
GLOBAL_DEF("rendering/batching/debug/diagnose_frame", false); GLOBAL_DEF("rendering/batching/debug/diagnose_frame", false);
GLOBAL_DEF("rendering/gles2/compatibility/disable_half_float", 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/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")); 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/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/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/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() { VisualServer::~VisualServer() {