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;