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:
parent
6e1af78df4
commit
275183ef15
|
@ -1003,6 +1003,14 @@
|
|||
<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.
|
||||
</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 )">
|
||||
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>
|
||||
|
@ -1042,6 +1050,7 @@
|
|||
</member>
|
||||
<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.
|
||||
Consider using the project setting [member rendering/batching/precision/uv_contract] to prevent artifacts.
|
||||
</member>
|
||||
<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.
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue