From 4409c786caeb7b917c33c24a95ab3cb80e3e7643 Mon Sep 17 00:00:00 2001 From: Hugo Locurcio Date: Thu, 24 Jul 2025 23:53:17 +0200 Subject: [PATCH] Ensure all MovieWriter frames have the same resolution as the first frame This fixes issues with certain video formats (like OGV) and video players not supporting resolution changes during playback. Resizing the viewport during recording is still not recommended (as cropping and resizing has a performance cost and can impact visuals), but it shouldn't cause crashes or broken files anymore. --- main/main.cpp | 16 ++++++++- servers/movie_writer/movie_writer.cpp | 48 +++++++++++++++++++-------- servers/movie_writer/movie_writer.h | 4 +++ 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/main/main.cpp b/main/main.cpp index 5f9c0094e8..064c20dab4 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -4704,7 +4704,21 @@ int Main::start() { } if (movie_writer) { - movie_writer->begin(DisplayServer::get_singleton()->window_get_size(), fixed_fps, Engine::get_singleton()->get_write_movie_path()); + Size2i movie_size = Size2i(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); + String stretch_mode = GLOBAL_GET("display/window/stretch/mode"); + if (stretch_mode != "viewport") { + // `canvas_items` and `disabled` modes use the window size override instead, + // which allows for higher resolution recording with 2D elements designed for a lower resolution. + const int window_width_override = GLOBAL_GET("display/window/size/window_width_override"); + if (window_width_override > 0) { + movie_size.width = window_width_override; + } + const int window_height_override = GLOBAL_GET("display/window/size/window_height_override"); + if (window_height_override > 0) { + movie_size.height = window_height_override; + } + } + movie_writer->begin(movie_size, fixed_fps, Engine::get_singleton()->get_write_movie_path()); } GDExtensionManager::get_singleton()->startup(); diff --git a/servers/movie_writer/movie_writer.cpp b/servers/movie_writer/movie_writer.cpp index c6c86e9bb2..2c20bf8df0 100644 --- a/servers/movie_writer/movie_writer.cpp +++ b/servers/movie_writer/movie_writer.cpp @@ -97,20 +97,9 @@ void MovieWriter::get_supported_extensions(List *r_extensions) const { void MovieWriter::begin(const Size2i &p_movie_size, uint32_t p_fps, const String &p_base_path) { project_name = GLOBAL_GET("application/config/name"); + movie_size = p_movie_size; - print_line(vformat("Movie Maker mode enabled, recording movie at %d FPS...", p_fps)); - - // When using Display/Window/Stretch/Mode = Viewport, use the project's - // configured viewport size instead of the size of the window in the OS - Size2i actual_movie_size = p_movie_size; - String stretch_mode = GLOBAL_GET("display/window/stretch/mode"); - if (stretch_mode == "viewport") { - actual_movie_size.width = GLOBAL_GET("display/window/size/viewport_width"); - actual_movie_size.height = GLOBAL_GET("display/window/size/viewport_height"); - - print_line(vformat("Movie Maker mode using project viewport size: %dx%d", - actual_movie_size.width, actual_movie_size.height)); - } + print_line(vformat(U"Movie Maker mode enabled, recording movie in %s×%s @ %d FPS...", movie_size.width, movie_size.height, p_fps)); // Check for available disk space and warn the user if needed. Ref dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); @@ -139,7 +128,7 @@ void MovieWriter::begin(const Size2i &p_movie_size, uint32_t p_fps, const String audio_channels = AudioDriverDummy::get_dummy_singleton()->get_channels(); audio_mix_buffer.resize(mix_rate * audio_channels / fps); - write_begin(actual_movie_size, p_fps, p_base_path); + write_begin(movie_size, p_fps, p_base_path); } void MovieWriter::_bind_methods() { @@ -206,6 +195,37 @@ void MovieWriter::add_frame() { RID main_vp_rid = RenderingServer::get_singleton()->viewport_find_from_screen_attachment(DisplayServer::MAIN_WINDOW_ID); RID main_vp_texture = RenderingServer::get_singleton()->viewport_get_texture(main_vp_rid); Ref vp_tex = RenderingServer::get_singleton()->texture_2d_get(main_vp_texture); + + if (vp_tex->get_size() != movie_size) { + // Resize the texture to the output resolution if it differs from the current viewport size. + // This ensures all frames have the same resolution, as not all video formats and players + // support resolution changes during playback. + + const float src_aspect = vp_tex->get_size().aspect(); + const float dst_aspect = movie_size.aspect(); + + int crop_width = vp_tex->get_size().width; + int crop_height = vp_tex->get_size().height; + int crop_x = 0; + int crop_y = 0; + + // If the aspect ratio differs, crop the image to cover the base resolution's aspect ratio + // in a way similar to `TextureRect.STRETCH_KEEP_ASPECT_COVERED`. + if (src_aspect > dst_aspect) { + // Source is wider, crop horizontally. + crop_width = int(vp_tex->get_size().height * dst_aspect); + crop_x = (vp_tex->get_size().width - crop_width) / 2; + vp_tex->crop_from_point(crop_x, crop_y, crop_width, crop_height); + } else if (src_aspect < dst_aspect) { + // Source is taller, crop vertically. + crop_height = int(vp_tex->get_size().width / dst_aspect); + crop_y = (vp_tex->get_size().height - crop_height) / 2; + vp_tex->crop_from_point(crop_x, crop_y, crop_width, crop_height); + } + + vp_tex->resize(movie_size.width, movie_size.height, Image::INTERPOLATE_BILINEAR); + } + if (RenderingServer::get_singleton()->viewport_is_using_hdr_2d(main_vp_rid)) { vp_tex->convert(Image::FORMAT_RGBA8); vp_tex->linear_to_srgb(); diff --git a/servers/movie_writer/movie_writer.h b/servers/movie_writer/movie_writer.h index f218221f9d..870fc6c451 100644 --- a/servers/movie_writer/movie_writer.h +++ b/servers/movie_writer/movie_writer.h @@ -41,6 +41,10 @@ class MovieWriter : public Object { uint64_t mix_rate = 0; uint32_t audio_channels = 0; + // The output resolution, which can differ from the window size. + // Used as a base for resizing all subsequent frames if their resolution differs. + Vector2i movie_size; + float cpu_time = 0.0f; float gpu_time = 0.0f; uint64_t encoding_time_usec = 0;