From eca2aaa8bb07614f2e9d1ef436156d0a5da55ff3 Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Thu, 7 Nov 2024 18:45:22 +0000 Subject: [PATCH] Fix crash when using the Image graph node with an empty image --- doc/source/changelog.md | 1 + generators/graph/image_range_grid.cpp | 4 +++ generators/graph/nodes/image.h | 10 ++++++++ generators/graph/voxel_generator_graph.cpp | 4 +-- tests/tests.cpp | 1 + tests/voxel/test_voxel_graph.cpp | 29 ++++++++++++++++++++++ tests/voxel/test_voxel_graph.h | 1 + util/io/log.h | 9 +++++++ 8 files changed, 57 insertions(+), 2 deletions(-) diff --git a/doc/source/changelog.md b/doc/source/changelog.md index bb6a627ef..faaa34c01 100644 --- a/doc/source/changelog.md +++ b/doc/source/changelog.md @@ -29,6 +29,7 @@ Primarily developped with Godot 4.3. - Fixed wrong values when using `OutputWeight` with optimized execution map enabled, when weights are determined to be locally constant - Fixed occasional holes in terrain when using `FastNoise3D` nodes with the `OpenSimplex2S` noise type - Fixed shader generation error when using the `Distance3D` node (vec2 instead of vec3, thanks to scwich) + - Fixed crash when assigning an empty image to the `Image` node - `VoxelMesherTransvoxel`: revert texturing logic that attempted to prevent air voxels from contributing, but was lowering quality. It is now optional as an experimental property. - `VoxelStreamSQLite`: Fixed "empty size" errors when loading areas with edited `VoxelInstancer` data - `.vox` scene importer: disabled threaded import to workaround the editor freezing when saving meshes diff --git a/generators/graph/image_range_grid.cpp b/generators/graph/image_range_grid.cpp index e1a47b23e..fce53c082 100644 --- a/generators/graph/image_range_grid.cpp +++ b/generators/graph/image_range_grid.cpp @@ -23,6 +23,10 @@ void ImageRangeGrid::generate(const Image &im) { clear(); + if (im.is_empty()) { + return; + } + const int lod_base = 4; // Start at 16 // Compute first lod diff --git a/generators/graph/nodes/image.h b/generators/graph/nodes/image.h index c55571c6a..150641a24 100644 --- a/generators/graph/nodes/image.h +++ b/generators/graph/nodes/image.h @@ -148,6 +148,10 @@ void register_image_nodes(Span types) { .format(varray(Image::get_class_static()))); return; } + if (image->is_empty()) { + ctx.make_error(String(ZN_TTR("{0} is empty").format(varray(Image::get_class_static())))); + return; + } ImageRangeGrid *im_range = ZN_NEW(ImageRangeGrid); im_range->generate(**image); Params p; @@ -167,6 +171,12 @@ void register_image_nodes(Span types) { // Cache image size to reduce API calls in GDExtension const int w = im.get_width(); const int h = im.get_height(); +#ifdef DEBUG_ENABLED + if (w == 0 || h == 0) { + ZN_PRINT_ERROR_ONCE("Image is empty"); + return; + } +#endif // TODO Optimized path for most used formats, `get_pixel` is kinda slow if (p.filter == FILTER_NEAREST) { for (uint32_t i = 0; i < out.size; ++i) { diff --git a/generators/graph/voxel_generator_graph.cpp b/generators/graph/voxel_generator_graph.cpp index b2276489f..e79f2f7c1 100644 --- a/generators/graph/voxel_generator_graph.cpp +++ b/generators/graph/voxel_generator_graph.cpp @@ -1665,7 +1665,7 @@ VoxelSingleValue VoxelGeneratorGraph::generate_single(Vector3i position, unsigne RWLockRead rlock(_runtime_lock); runtime_ptr = _runtime; } - ERR_FAIL_COND_V(runtime_ptr == nullptr, v); + ERR_FAIL_COND_V_MSG(runtime_ptr == nullptr, v, "no compiled graph available"); if (runtime_ptr->sdf_output_buffer_index == -1) { return v; } @@ -1688,7 +1688,7 @@ VoxelSingleValue VoxelGeneratorGraph::generate_single(Vector3i position, unsigne RWLockRead rlock(_runtime_lock); runtime_ptr = _runtime; } - ERR_FAIL_COND_V(runtime_ptr == nullptr, v); + ERR_FAIL_COND_V_MSG(runtime_ptr == nullptr, v, "no compiled graph available"); if (runtime_ptr->single_texture_output_buffer_index == -1) { return v; } diff --git a/tests/tests.cpp b/tests/tests.cpp index cb48b2ff6..fdd6fe744 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -90,6 +90,7 @@ void run_voxel_tests() { VOXEL_TEST(test_voxel_graph_many_weight_outputs); VOXEL_TEST(test_voxel_graph_many_subdivisions); VOXEL_TEST(test_voxel_graph_non_square_image); + VOXEL_TEST(test_voxel_graph_empty_image); VOXEL_TEST(test_voxel_graph_4_default_weights); VOXEL_TEST(test_island_finder); VOXEL_TEST(test_unordered_remove_if); diff --git a/tests/voxel/test_voxel_graph.cpp b/tests/voxel/test_voxel_graph.cpp index 0f29f5bbd..6cc9ab6b7 100644 --- a/tests/voxel/test_voxel_graph.cpp +++ b/tests/voxel/test_voxel_graph.cpp @@ -2329,4 +2329,33 @@ void test_voxel_graph_4_default_weights() { // Related to issue #686 L::test(0.5, 0.2, 0.4, 0.8); } +void test_voxel_graph_empty_image() { + // This used to crash + + Ref generator; + generator.instantiate(); + + VoxelGraphFunction &g = **generator->get_main_function(); + + const uint32_t n_x = g.create_node(VoxelGraphFunction::NODE_INPUT_X); + const uint32_t n_z = g.create_node(VoxelGraphFunction::NODE_INPUT_Z); + const uint32_t n_image = g.create_node(VoxelGraphFunction::NODE_IMAGE_2D); + const uint32_t n_out_sdf = g.create_node(VoxelGraphFunction::NODE_OUTPUT_SDF); + + Ref image; + image.instantiate(); + g.set_node_param(n_image, 0, image); + + g.add_connection(n_x, 0, n_image, 0); + g.add_connection(n_z, 0, n_image, 1); + g.add_connection(n_image, 0, n_out_sdf, 0); + + CompilationResult result = generator->compile(false); + + // Try to generate before asserting compilation result. It should fail without crashing. + generator->generate_single(Vector3i(405, 2, 305), VoxelBuffer::CHANNEL_SDF); + + ZN_TEST_ASSERT(result.success == false); +} + } // namespace zylann::voxel::tests diff --git a/tests/voxel/test_voxel_graph.h b/tests/voxel/test_voxel_graph.h index a34939ebc..f71757c50 100644 --- a/tests/voxel/test_voxel_graph.h +++ b/tests/voxel/test_voxel_graph.h @@ -36,6 +36,7 @@ void test_image_range_grid(); void test_voxel_graph_many_subdivisions(); void test_voxel_graph_non_square_image(); void test_voxel_graph_4_default_weights(); +void test_voxel_graph_empty_image(); } // namespace zylann::voxel::tests diff --git a/util/io/log.h b/util/io/log.h index 1c3205dc3..dcebd2eb6 100644 --- a/util/io/log.h +++ b/util/io/log.h @@ -13,6 +13,15 @@ #define ZN_PRINT_WARNING(msg) zylann::print_warning(msg, __FUNCTION__, __FILE__, __LINE__) #define ZN_PRINT_ERROR(msg) zylann::print_error(msg, __FUNCTION__, __FILE__, __LINE__) +#define ZN_PRINT_ERROR_ONCE(msg) \ + { \ + static bool s_first_print = true; \ + if (s_first_print) { \ + s_first_print = false; \ + zylann::print_error(msg, __FUNCTION__, __FILE__, __LINE__); \ + } \ + } + namespace zylann { bool is_verbose_output_enabled();