Repository: godotengine/godot (language: cpp)

Issue:
Betsy GPU compression usage in the editor causes an error on exit
### Tested versions

Godot 4.4 RC3 15ff450680a40391aabbffde0a57ead2cd84db56

### System information

Windows 10 64 bits NVIDIA GeForce GTX 1060 6GB

### Issue description

While debugging something unrelated, I came across the following error when closing the editor:

```
ERROR: This function (finalize) can only be called from the render thread.
```

Since I had a debugger attached to the error callback, I got its call stack:

```
godot.windows.editor.dev.x86_64.exe!_err_print_error(const char * p_function, const char * p_file, int p_line, const char * p_error, const char * p_message, bool p_editor_notify, ErrorHandlerType p_type) Line 92 (godot4_fork\core\error\error_macros.cpp:92)
godot.windows.editor.dev.x86_64.exe!_err_print_error(const char * p_function, const char * p_file, int p_line, const char * p_error, const String & p_message, bool p_editor_notify, ErrorHandlerType p_type) Line 138 (godot4_fork\core\error\error_macros.cpp:138)
godot.windows.editor.dev.x86_64.exe!RenderingDevice::finalize() Line 7092 (godot4_fork\servers\rendering\rendering_device.cpp:7092)
godot.windows.editor.dev.x86_64.exe!RenderingDevice::~RenderingDevice() Line 8007 (godot4_fork\servers\rendering\rendering_device.cpp:8007)
godot.windows.editor.dev.x86_64.exe!RenderingDevice::`scalar deleting destructor'(unsigned int) (Unknown Source:0)
godot.windows.editor.dev.x86_64.exe!memdelete<RenderingDevice>(RenderingDevice * p_class) Line 143 (godot4_fork\core\os\memory.h:143)
godot.windows.editor.dev.x86_64.exe!BetsyCompressor::finish() Line 275 (godot4_fork\modules\betsy\image_compress_betsy.cpp:275)
godot.windows.editor.dev.x86_64.exe!free_device() Line 740 (godot4_fork\modules\betsy\image_compress_betsy.cpp:740)
godot.windows.editor.dev.x86_64.exe!uninitialize_betsy_module(ModuleInitializationLevel p_level) Line 50 (godot4_fork\modules\betsy\register_types.cpp:50)
godot.windows.editor.dev.x86_64.exe!uninitialize_modules(ModuleInitializationLevel p_level) Line 287 (godot4_fork\modules\register_module_types.gen.cpp:287)
godot.windows.editor.dev.x86_64.exe!Main::cleanup(bool p_force) Line 4706 (godot4_fork\main\main.cpp:4706)
godot.windows.editor.dev.x86_64.exe!widechar_main(int argc, wchar_t * * argv) Line 102 (godot4_fork\platform\windows\godot_windows.cpp:102)
godot.windows.editor.dev.x86_64.exe!_main() Line 122 (godot4_fork\platform\windows\godot_windows.cpp:122)
godot.windows.editor.dev.x86_64.exe!main(int argc, char * * argv) Line 136 (godot4_fork\platform\windows\godot_windows.cpp:136)
godot.windows.editor.dev.x86_64.exe!WinMain(HINSTANCE__ * hInstance, HINSTANCE__ * hPrevInstance, char * lpCmdLine, int nCmdShow) Line 150 (godot4_fork\platform\windows\godot_windows.cpp:150)
godot.windows.editor.dev.x86_64.exe!invoke_main() Line 107 (d:\a01\_work\12\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:107)
godot.windows.editor.dev.x86_64.exe!__scrt_common_main_seh() Line 288 (d:\a01\_work\12\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288)
godot.windows.editor.dev.x86_64.exe!__scrt_common_main() Line 331 (d:\a01\_work\12\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:331)
godot.windows.editor.dev.x86_64.exe!WinMainCRTStartup(void * __formal) Line 17 (d:\a01\_work\12\s\src\vctools\crt\vcstartup\src\startup\exe_winmain.cpp:17)
kernel32.dll!00007ffc3f8f7374() (Unknown Source:0)
```

Which shows it is because of the BetsyCompressor.

### Steps to reproduce

I don't have them since I was in the middle of something else in a non-trivial project, and wasn't a blocker.
But I suspect it involves importing textures in the editor such that the Betsy GPU compressor gets invoked (Forward+), then closing the editor.

### Minimal reproduction project (MRP)

N.A



Discussion / hints:
This should be a quick fix. The call to finalize should be wrapped in RenderingServer.call_on_render_thread()
I will go ahead and tackle this! This is the first issue I'm solving, so I hope I do it correctly.
Ran into a bit of an issue. It seems that deferring the `finalize` call to the render thread causes it to not get called anymore. I only found out about this because of this warning about a leak:

```
WARNING: ObjectDB instances leaked at exit (run with --verbose for details).
     at: cleanup (core/object/object.cpp:2378)
Leaked instance: Object:96217338099
```

After some classic print-debugging, it seems that the `finalize` method does not get called anymore. Maybe my code is wrong? Here is the line I placed in the `RenderingDevice::~RenderingDevice` (which is the only place where finalize was called as far as I can tell):
```cpp
RenderingServer::get_singleton()->call_on_render_thread(Callable(this, "finalize"));
```

I would really appreciate any and all guidance here!
I figured this out I think! Had to bind the finalize method in `_bind_methods` like this:

```cpp
ClassDB::bind_method(D_METHOD("finalize"), &RenderingDevice::finalize);
```

Fix patch:
diff --git a/modules/betsy/image_compress_betsy.cpp b/modules/betsy/image_compress_betsy.cpp
index 4138ad7679bf..468bb1c209e1 100644
--- a/modules/betsy/image_compress_betsy.cpp
+++ b/modules/betsy/image_compress_betsy.cpp
@@ -259,17 +259,7 @@ void BetsyCompressor::_thread_exit() {
 				compress_rd->free(cached_shaders[i].compiled);
 			}
 		}
-	}
-}
-
-void BetsyCompressor::finish() {
-	command_queue.push(this, &BetsyCompressor::_thread_exit);
-	if (task_id != WorkerThreadPool::INVALID_TASK_ID) {
-		WorkerThreadPool::get_singleton()->wait_for_task_completion(task_id);
-		task_id = WorkerThreadPool::INVALID_TASK_ID;
-	}
 
-	if (compress_rd != nullptr) {
 		// Free the RD (and RCD if necessary).
 		memdelete(compress_rd);
 		compress_rd = nullptr;
@@ -280,6 +270,14 @@ void BetsyCompressor::finish() {
 	}
 }
 
+void BetsyCompressor::finish() {
+	command_queue.push(this, &BetsyCompressor::_thread_exit);
+	if (task_id != WorkerThreadPool::INVALID_TASK_ID) {
+		WorkerThreadPool::get_singleton()->wait_for_task_completion(task_id);
+		task_id = WorkerThreadPool::INVALID_TASK_ID;
+	}
+}
+
 // Helper functions.
 
 static int get_next_multiple(int n, int m) {


Test patch: