From c7acbe5de63ec4290cdbb5d37ac156f88ff2e76b Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 15 Aug 2024 10:53:46 -0500 Subject: [PATCH 1/3] Brighten up fill --- manimlib/utils/shaders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/utils/shaders.py b/manimlib/utils/shaders.py index 18b3d24e..2aae129a 100644 --- a/manimlib/utils/shaders.py +++ b/manimlib/utils/shaders.py @@ -182,7 +182,7 @@ def get_fill_canvas(ctx: moderngl.Context) -> Tuple[Framebuffer, VertexArray]: if(color.a == 0) discard; // Counteract scaling in fill frag - color.a *= 1.06; + color *= 1.06; gl_FragDepth = texture(DepthTexture, uv)[0]; } From a5ba721f96479dff29b677bde1c6399fb43ce183 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 15 Aug 2024 14:03:16 -0500 Subject: [PATCH 2/3] Cease useless rendering Change so that with a live window, rendering only happens if there has been an event (mouse motion, key press, etc.) to respond to. --- manimlib/camera/camera.py | 9 +++++++-- manimlib/scene/scene.py | 18 +++++++++++------- manimlib/window.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index 08fe1abc..d4a1e14e 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -124,6 +124,8 @@ class Camera(object): def clear(self) -> None: self.fbo.clear(*self.background_rgba) + if self.window: + self.window.clear() def blit(self, src_fbo, dst_fbo): """ @@ -227,8 +229,11 @@ class Camera(object): self.fbo.use() for mobject in mobjects: mobject.render(self.ctx, self.uniforms) - if self.window is not None and self.fbo is not self.window_fbo: - self.blit(self.fbo, self.window_fbo) + + if self.window: + self.window.swap_buffers() + if self.fbo is not self.window_fbo: + self.blit(self.fbo, self.window_fbo) def refresh_uniforms(self) -> None: frame = self.frame diff --git a/manimlib/scene/scene.py b/manimlib/scene/scene.py index 6035c0a2..c0b88f5e 100644 --- a/manimlib/scene/scene.py +++ b/manimlib/scene/scene.py @@ -214,7 +214,7 @@ class Scene(object): ) -> None: if not self.preview: # Embed is only relevant with a preview - return + return self.stop_skipping() self.update_frame() self.save_state() @@ -269,6 +269,7 @@ class Scene(object): # Operation to run after each ipython command def post_cell_func(*args, **kwargs): if not self.is_window_closing(): + self.window._has_undrawn_event = True self.update_frame(dt=0, ignore_skipping=True) shell.events.register("post_run_cell", post_cell_func) @@ -325,16 +326,18 @@ class Scene(object): if self.is_window_closing(): raise EndScene() - if self.window: - self.window.clear() + if self.window and dt == 0 and not self.window.has_undrawn_event(): + # In this case, there's no need for new rendering, but we + # shoudl still listen for new events + self.window._window.dispatch_events() + return + self.camera.capture(*self.render_groups) if self.window: - self.window.swap_buffers() vt = self.time - self.virtual_animation_start_time rt = time.time() - self.real_animation_start_time - if rt < vt: - self.update_frame(0) + time.sleep(max(vt - rt, 0)) def emit_frame(self) -> None: if not self.skip_animations: @@ -530,6 +533,7 @@ class Scene(object): def stop_skipping(self) -> None: self.virtual_animation_start_time = self.time + self.real_animation_start_time = time.time() self.skip_animations = False # Methods associated with running animations @@ -596,8 +600,8 @@ class Scene(object): self.file_writer.begin_animation() if self.window: - self.real_animation_start_time = time.time() self.virtual_animation_start_time = self.time + self.real_animation_start_time = time.time() def post_play(self): if not self.skip_animations: diff --git a/manimlib/window.py b/manimlib/window.py index 152da54d..d6369166 100644 --- a/manimlib/window.py +++ b/manimlib/window.py @@ -6,6 +6,7 @@ import moderngl_window as mglw from moderngl_window.context.pyglet.window import Window as PygletWindow from moderngl_window.timers.clock import Timer from screeninfo import get_monitors +from functools import wraps from manimlib.constants import FRAME_SHAPE from manimlib.utils.customization import get_customization @@ -39,6 +40,8 @@ class Window(PygletWindow): self.title = str(scene) self.size = size + self._has_undrawn_event = True + mglw.activate_context(window=self) self.timer = Timer() self.config = mglw.WindowConfig(ctx=self.ctx, wnd=self, timer=self.timer) @@ -95,56 +98,82 @@ class Window(PygletWindow): coords[:2] -= 0.5 * fixed_frame_shape return frame.from_fixed_frame_point(coords, relative) + def has_undrawn_event(self) -> bool: + return self._has_undrawn_event + + def swap_buffers(self): + super().swap_buffers() + self._has_undrawn_event = False + + @staticmethod + def note_undrawn_event(func: Callable[..., T]) -> Callable[..., T]: + @wraps(func) + def wrapper(self, *args, **kwargs): + func(self, *args, **kwargs) + self._has_undrawn_event = True + return wrapper + + @note_undrawn_event def on_mouse_motion(self, x: int, y: int, dx: int, dy: int) -> None: super().on_mouse_motion(x, y, dx, dy) point = self.pixel_coords_to_space_coords(x, y) d_point = self.pixel_coords_to_space_coords(dx, dy, relative=True) self.scene.on_mouse_motion(point, d_point) + @note_undrawn_event def on_mouse_drag(self, x: int, y: int, dx: int, dy: int, buttons: int, modifiers: int) -> None: super().on_mouse_drag(x, y, dx, dy, buttons, modifiers) point = self.pixel_coords_to_space_coords(x, y) d_point = self.pixel_coords_to_space_coords(dx, dy, relative=True) self.scene.on_mouse_drag(point, d_point, buttons, modifiers) + @note_undrawn_event def on_mouse_press(self, x: int, y: int, button: int, mods: int) -> None: super().on_mouse_press(x, y, button, mods) point = self.pixel_coords_to_space_coords(x, y) self.scene.on_mouse_press(point, button, mods) + @note_undrawn_event def on_mouse_release(self, x: int, y: int, button: int, mods: int) -> None: super().on_mouse_release(x, y, button, mods) point = self.pixel_coords_to_space_coords(x, y) self.scene.on_mouse_release(point, button, mods) + @note_undrawn_event def on_mouse_scroll(self, x: int, y: int, x_offset: float, y_offset: float) -> None: super().on_mouse_scroll(x, y, x_offset, y_offset) point = self.pixel_coords_to_space_coords(x, y) offset = self.pixel_coords_to_space_coords(x_offset, y_offset, relative=True) self.scene.on_mouse_scroll(point, offset, x_offset, y_offset) + @note_undrawn_event def on_key_press(self, symbol: int, modifiers: int) -> None: self.pressed_keys.add(symbol) # Modifiers? super().on_key_press(symbol, modifiers) self.scene.on_key_press(symbol, modifiers) + @note_undrawn_event def on_key_release(self, symbol: int, modifiers: int) -> None: self.pressed_keys.difference_update({symbol}) # Modifiers? super().on_key_release(symbol, modifiers) self.scene.on_key_release(symbol, modifiers) + @note_undrawn_event def on_resize(self, width: int, height: int) -> None: super().on_resize(width, height) self.scene.on_resize(width, height) + @note_undrawn_event def on_show(self) -> None: super().on_show() self.scene.on_show() + @note_undrawn_event def on_hide(self) -> None: super().on_hide() self.scene.on_hide() + @note_undrawn_event def on_close(self) -> None: super().on_close() self.scene.on_close() From 65d594796663082ce26365751d264f18f7cf3643 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 15 Aug 2024 14:05:32 -0500 Subject: [PATCH 3/3] Change ignore_skipping name to force_draw --- manimlib/scene/scene.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/manimlib/scene/scene.py b/manimlib/scene/scene.py index c0b88f5e..c46587f2 100644 --- a/manimlib/scene/scene.py +++ b/manimlib/scene/scene.py @@ -269,8 +269,7 @@ class Scene(object): # Operation to run after each ipython command def post_cell_func(*args, **kwargs): if not self.is_window_closing(): - self.window._has_undrawn_event = True - self.update_frame(dt=0, ignore_skipping=True) + self.update_frame(dt=0, force_draw=True) shell.events.register("post_run_cell", post_cell_func) @@ -314,19 +313,19 @@ class Scene(object): return image def show(self) -> None: - self.update_frame(ignore_skipping=True) + self.update_frame(force_draw=True) self.get_image().show() - def update_frame(self, dt: float = 0, ignore_skipping: bool = False) -> None: + def update_frame(self, dt: float = 0, force_draw: bool = False) -> None: self.increment_time(dt) self.update_mobjects(dt) - if self.skip_animations and not ignore_skipping: + if self.skip_animations and not force_draw: return if self.is_window_closing(): raise EndScene() - if self.window and dt == 0 and not self.window.has_undrawn_event(): + if self.window and dt == 0 and not self.window.has_undrawn_event() and not force_draw: # In this case, there's no need for new rendering, but we # shoudl still listen for new events self.window._window.dispatch_events() @@ -609,7 +608,7 @@ class Scene(object): if self.skip_animations and self.window is not None: # Show some quick frames along the way - self.update_frame(dt=0, ignore_skipping=True) + self.update_frame(dt=0, force_draw=True) self.num_plays += 1