Lightmapper: Prevent infinite loop when blitting lightmaps into an atlas

This commit is contained in:
BlueCube3310 2024-07-11 23:42:52 +02:00
parent 14877d1f99
commit 60a255a0d0
6 changed files with 29 additions and 6 deletions

View File

@ -137,6 +137,12 @@
<constant name="BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL" value="9" enum="BakeError"> <constant name="BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL" value="9" enum="BakeError">
Lightmap baking failed as the maximum texture size is too small to fit some of the meshes marked for baking. Lightmap baking failed as the maximum texture size is too small to fit some of the meshes marked for baking.
</constant> </constant>
<constant name="BAKE_ERROR_LIGHTMAP_TOO_SMALL" value="10" enum="BakeError">
Lightmap baking failed as the lightmap is too small.
</constant>
<constant name="BAKE_ERROR_ATLAS_TOO_SMALL" value="11" enum="BakeError">
Lightmap baking failed as the lightmap was unable to fit into an atlas.
</constant>
<constant name="ENVIRONMENT_MODE_DISABLED" value="0" enum="EnvironmentMode"> <constant name="ENVIRONMENT_MODE_DISABLED" value="0" enum="EnvironmentMode">
Ignore environment lighting when baking lightmaps. Ignore environment lighting when baking lightmaps.
</constant> </constant>

View File

@ -110,6 +110,9 @@ void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) {
case LightmapGI::BAKE_ERROR_LIGHTMAP_TOO_SMALL: { case LightmapGI::BAKE_ERROR_LIGHTMAP_TOO_SMALL: {
EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images. Make sure all meshes selected to bake have `lightmap_size_hint` value set high enough, and `texel_scale` value of LightmapGI is not too low.")); EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images. Make sure all meshes selected to bake have `lightmap_size_hint` value set high enough, and `texel_scale` value of LightmapGI is not too low."));
} break; } break;
case LightmapGI::BAKE_ERROR_ATLAS_TOO_SMALL: {
EditorNode::get_singleton()->show_warning(TTR("Failed fitting a lightmap image into an atlas. This should never happen and should be reported."));
} break;
default: { default: {
} break; } break;
} }

View File

@ -240,7 +240,7 @@ Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_
max = MAX(max, nearest_power_of_2_templated(atlas_size.height)); max = MAX(max, nearest_power_of_2_templated(atlas_size.height));
if (max > p_max_texture_size) { if (max > p_max_texture_size) {
return BAKE_ERROR_LIGHTMAP_TOO_SMALL; return BAKE_ERROR_TEXTURE_EXCEEDS_MAX_SIZE;
} }
if (p_step_function) { if (p_step_function) {
@ -254,19 +254,27 @@ Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_
int best_atlas_memory = 0x7FFFFFFF; int best_atlas_memory = 0x7FFFFFFF;
Vector<Vector3i> best_atlas_offsets; Vector<Vector3i> best_atlas_offsets;
//determine best texture array atlas size by bruteforce fitting // Determine best texture array atlas size by bruteforce fitting.
while (atlas_size.x <= p_max_texture_size && atlas_size.y <= p_max_texture_size) { while (atlas_size.x <= p_max_texture_size && atlas_size.y <= p_max_texture_size) {
Vector<Vector2i> source_sizes; Vector<Vector2i> source_sizes;
Vector<int> source_indices; Vector<int> source_indices;
source_sizes.resize(sizes.size()); source_sizes.resize(sizes.size());
source_indices.resize(sizes.size()); source_indices.resize(sizes.size());
for (int i = 0; i < source_indices.size(); i++) { for (int i = 0; i < source_indices.size(); i++) {
source_sizes.write[i] = sizes[i] + Vector2i(2, 2).maxi(p_denoiser_range); // Add padding between lightmaps source_sizes.write[i] = sizes[i] + Vector2i(2, 2).maxi(p_denoiser_range); // Add padding between lightmaps.
source_indices.write[i] = i; source_indices.write[i] = i;
} }
Vector<Vector3i> atlas_offsets; Vector<Vector3i> atlas_offsets;
atlas_offsets.resize(source_sizes.size()); atlas_offsets.resize(source_sizes.size());
// Ensure the sizes can all fit into a single atlas layer.
// This should always happen, and this check is only in place to prevent an infinite loop.
for (int i = 0; i < source_sizes.size(); i++) {
if (source_sizes[i] > atlas_size) {
return BAKE_ERROR_ATLAS_TOO_SMALL;
}
}
int slices = 0; int slices = 0;
while (source_sizes.size() > 0) { while (source_sizes.size() > 0) {

View File

@ -1104,10 +1104,12 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, denoiser_strength, denoiser_range, bounces, bounce_indirect_energy, bias, max_texture_size, directional, use_texture_for_bounces, Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization); Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, denoiser_strength, denoiser_range, bounces, bounce_indirect_energy, bias, max_texture_size, directional, use_texture_for_bounces, Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization);
if (bake_err == Lightmapper::BAKE_ERROR_LIGHTMAP_TOO_SMALL) { if (bake_err == Lightmapper::BAKE_ERROR_TEXTURE_EXCEEDS_MAX_SIZE) {
return BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL; return BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL;
} else if (bake_err == Lightmapper::BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES) { } else if (bake_err == Lightmapper::BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES) {
return BAKE_ERROR_MESHES_INVALID; return BAKE_ERROR_MESHES_INVALID;
} else if (bake_err == Lightmapper::BAKE_ERROR_ATLAS_TOO_SMALL) {
return BAKE_ERROR_ATLAS_TOO_SMALL;
} }
// POSTBAKE: Save Textures. // POSTBAKE: Save Textures.
@ -1711,6 +1713,8 @@ void LightmapGI::_bind_methods() {
BIND_ENUM_CONSTANT(BAKE_ERROR_CANT_CREATE_IMAGE); BIND_ENUM_CONSTANT(BAKE_ERROR_CANT_CREATE_IMAGE);
BIND_ENUM_CONSTANT(BAKE_ERROR_USER_ABORTED); BIND_ENUM_CONSTANT(BAKE_ERROR_USER_ABORTED);
BIND_ENUM_CONSTANT(BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL); BIND_ENUM_CONSTANT(BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL);
BIND_ENUM_CONSTANT(BAKE_ERROR_LIGHTMAP_TOO_SMALL);
BIND_ENUM_CONSTANT(BAKE_ERROR_ATLAS_TOO_SMALL);
BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_DISABLED); BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_DISABLED);
BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_SCENE); BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_SCENE);

View File

@ -143,6 +143,7 @@ public:
BAKE_ERROR_USER_ABORTED, BAKE_ERROR_USER_ABORTED,
BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL, BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL,
BAKE_ERROR_LIGHTMAP_TOO_SMALL, BAKE_ERROR_LIGHTMAP_TOO_SMALL,
BAKE_ERROR_ATLAS_TOO_SMALL,
}; };
enum EnvironmentMode { enum EnvironmentMode {

View File

@ -143,9 +143,10 @@ public:
}; };
enum BakeError { enum BakeError {
BAKE_ERROR_LIGHTMAP_TOO_SMALL, BAKE_OK,
BAKE_ERROR_TEXTURE_EXCEEDS_MAX_SIZE,
BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES,
BAKE_OK BAKE_ERROR_ATLAS_TOO_SMALL,
}; };
enum BakeQuality { enum BakeQuality {