Refactor: separate camera state from renderer-owned rasterization (cairo) + consolidate renderer related modules - #4989
Refactor: separate camera state from renderer-owned rasterization (cairo) + consolidate renderer related modules#4989behackl wants to merge 13 commits into
Conversation
| margin: float = 0, | ||
| only_mobjects_in_frame: bool = False, | ||
| animate: Literal[False] = False, | ||
| ) -> Mobject: ... |
| margin: float = 0, | ||
| only_mobjects_in_frame: bool = False, | ||
| animate: Literal[True] = True, | ||
| ) -> _AnimationBuilder: ... |
Retire unused nested targets, release retained buffers, and guard closed operations. Reject unused renderer keywords and build dispatch tables once per draw.
Expose Scene.get_image() and Scene.show() through Manager without stepping animations or emitting movie frames. Preserve active raster targets and capture OpenGL meshes on the context-owning thread.
Compute default frame bounds directly without a semantic cache, preserve custom frame hooks, and share perspective scaling between axes. Add mutation, boundary, and projection parity tests.
There was a problem hiding this comment.
Did a superficial deslopping pass of some of the documentation and comments.
More generally, I think it would be useful to consciously choose some language for the terms used in a camera/render context. I don't like "logical/semantic" "frame/view". I would suggest that we adopt camera frame (or just frame when the context is clear) to describe the rectangle which controls the camera view (the position, angle, and size of the view), and I would suggest that the rectangle which defines the output image is called the viewport - in graphics engine circles, this is the usual nomenclature for the "screen rectangle". Another choice might be canvas but that doesn't work quite as well for 3D.
So for example, you might say "When both the width and height of the camera frame are provided, you must ensure that the pixel dimensions of the viewport have the same aspect ratio of the frame, otherwise (bad things happen)."
Happy to discuss more! I would also be happy to give the new camera guide a more thorough rewrite in a later PR once things settle down a bit.
| configured pixel aspect ratio. Square and portrait output therefore preserve ordinary | ||
| geometry. One explicit camera dimension determines the other using that aspect ratio; |
There was a problem hiding this comment.
What does Square and portrait output therefore preserve ordinary geometry mean?
| Run this example with ``--renderer=cairo``. Selecting ``MultiCamera`` in the constructor | ||
| ensures it is installed before the scene's renderer is initialized. |
There was a problem hiding this comment.
I suggest a separate "changing camera class" section which explains this, since this is the current intended approach for all custom cameras. Also "installed" is weird phrasing here, is there a more pythonesque word you could use instead?
| self.play(self.camera.auto_zoom([square])) | ||
| self.play(Restore(self.camera.frame)) | ||
|
|
||
| :class:`.MovingCameraScene` remains available as a descriptive name for this behavior. |
There was a problem hiding this comment.
I would either remove this entirely or make explicit that MovingCameraScene exists for backwards compatibility and will be deleted in the future.
| Run this example with ``--renderer=cairo``. Selecting ``MultiCamera`` in the constructor | ||
| ensures it is installed before the scene's renderer is initialized. | ||
|
|
||
| Both registration and scene membership matter: registering a display tells MultiCamera |
There was a problem hiding this comment.
"Both registration and scene membership matter" is mega-slopspeak :D
| The renderer retires unused secondary targets on the next draw. A scene image requested | ||
| with ``self.get_image()`` includes all currently registered and visible views; there is | ||
| no need to copy camera pixels or refresh each inset yourself. |
There was a problem hiding this comment.
Another instance of the doc specifying what the user doesn't need to do.
|
|
||
|
|
||
| class _CameraFrame(ScreenRectangle): | ||
| """Default frame with a direct bounding-box center query, not a cached center.""" |
There was a problem hiding this comment.
Rewrite so the docstring doesn't say what the frame doesn't do.
| class Camera: | ||
| """Describe the logical view used by a rendering backend. | ||
|
|
||
| Camera owns an animatable frame, semantic background settings, display ordering, |
There was a problem hiding this comment.
Personal preference: I don't like the heavy usage of "owns". "Has" works perfectly fine and aligns with how we generally talk about classes and their attributes.
There was a problem hiding this comment.
I guess in some contexts "owns" is used to mean "is responsible for" rather than "has-a". In these cases I would also argue that "is responsible for" is clearer than "owns".
| ) | ||
|
|
||
| def get_mobjects_indicating_movement(self) -> list[Mobject]: | ||
| """Camera controls whose animation changes every projected pixel.""" |
There was a problem hiding this comment.
I don't understand this docstring? Maybe give it another pass.
Co-authored-by: nikolajmunk <28557236+nikolajmunk@users.noreply.github.com>
Address review feedback on camera terminology, backend controls, custom camera selection, snapshot behavior, and inset views. Preserve the earlier camera test cleanup.
nikolajmunk
left a comment
There was a problem hiding this comment.
Everything reads much more nicely now! I nevertheless went through and remarked on a few LLM-isms.
As a general note, I'm not personally convinced that "semantic (camera) view" and "owned" are useful terms to the reader.
| The ``camera`` parameter allows for a different camera to be used to generate | ||
| the image. Without it, a new default :class:`.Camera` is created. Only the | ||
| mobject and its submobjects are drawn; pass ``camera=self.camera`` to use the | ||
| scene's current view. |
There was a problem hiding this comment.
| The ``camera`` parameter allows for a different camera to be used to generate | |
| the image. Without it, a new default :class:`.Camera` is created. Only the | |
| mobject and its submobjects are drawn; pass ``camera=self.camera`` to use the | |
| scene's current view. | |
| These methods render an image from the view of a camera such that only the | |
| chosen mobject and its submobjects are drawn; anything else in the scene is | |
| ignored. | |
| The ``camera`` parameter allows for a different camera to be used to generate | |
| the image. Without it, a new default :class:`.Camera` is created. To use the | |
| view of the current camera, pass ``camera=self.camera``. |
Separate information about what is drawn and how the camera param works.
| For an inset magnified view, :class:`.ZoomedScene` provides the camera and display | ||
| relationship:: |
There was a problem hiding this comment.
Not crazy about "the camera and display relationship" but I'm unsure what a better phrasing would look like.
| ``view.add_display_frame()`` adds the visible border around the display. To also | ||
| show the region that the secondary camera looks at, give its ``frame`` a visible | ||
| stroke and add it to the scene:: |
There was a problem hiding this comment.
| ``view.add_display_frame()`` adds the visible border around the display. To also | |
| show the region that the secondary camera looks at, give its ``frame`` a visible | |
| stroke and add it to the scene:: | |
| ``view.add_display_frame()`` adds a visible border around the display. | |
| To show the region which the secondary camera is currently looking at, give its | |
| ``frame`` a visible stroke and add it to the scene:: |
| A display initially matches its source camera's aspect ratio. Scale it uniformly to | ||
| preserve that ratio; stretching only its width or height can distort the image. | ||
| Each inset's pixel resolution follows its display size relative to the primary | ||
| camera frame. |
There was a problem hiding this comment.
| A display initially matches its source camera's aspect ratio. Scale it uniformly to | |
| preserve that ratio; stretching only its width or height can distort the image. | |
| Each inset's pixel resolution follows its display size relative to the primary | |
| camera frame. | |
| A display initially matches the aspect ratio of its source camera. When resizing | |
| this display, make sure it is scaled uniformly to preserve its aspect ratio; | |
| stretching only its width or height can distort the image. | |
| Each inset's pixel resolution follows its display size relative to the primary | |
| camera frame. |
| dimensions. The camera itself does not own pixels or a Cairo context; its constructor | ||
| accepts semantic view settings rather than pixel dimensions or frame-rate options. |
There was a problem hiding this comment.
"The camera itself does not own pixels or a Cairo context; its constructor accepts semantic view settings rather than pixel dimensions or frame-rate options." should probably be removed; the user doesn't care what the camera doesn't do.
| to target pixel coordinates and composited by renderer helpers. | ||
| - A :class:`.MultiCamera` describes nested camera-backed views. Their | ||
| :class:`.ImageMobjectFromCamera` display mobjects contain geometry and sampling | ||
| settings but no placeholder or live pixels. The renderer creates secondary targets |
There was a problem hiding this comment.
Not clear what it means for a camera to (not) contain "placeholder or live pixels".
| this array to the file writer. | ||
| - Then the scene asks the renderer to do its job. The only mobject that needs to | ||
| be processed at this point is the main mobject attached to the transformation. | ||
| The camera supplies its semantic view transform, while renderer-owned Cairo |
There was a problem hiding this comment.
Perhaps just "supplies its view transform"?
| be processed at this point is the main mobject attached to the transformation. | ||
| The camera supplies its semantic view transform, while renderer-owned Cairo | ||
| helpers draw the current mobject state into the renderer's raster target. The | ||
| renderer reads that target and passes an owned array to the file writer. |
There was a problem hiding this comment.
"im not owned! im not owned!!", i continue to insist as i slowly shrink and transform into a corn cob
I think it should be more clear exactly which array is being passed to the file writer; it tells the user nothing that it is "owned".
Overview: What does this pull request change?
This PR makes the camera/renderer ownership boundary explicit: Cairo cameras describe semantic views, while
CairoRendererowns raster targets, PyCairo drawing, composition, static-image reuse, readback, and cleanup.It also packages concrete implementations under
manim.renderer.cairoandmanim.renderer.opengl, gives the default CairoCameraan animatable frame andauto_zoom(), and preserves ThreeD, ZoomedScene, nested-camera, image, point-cloud, background, and static-cache behavior.This change assumes the renderer-independent writer boundary introduced by #4976. Renderer-owned play loops, clocks, cache decisions, and writer scheduling remain unchanged for the later Manager execution refactor.
Motivation and Explanation: Why and how do your changes improve the library?
Cairo cameras previously combined semantic view state with pixel buffers, PyCairo contexts, drawing, composition, and readback. That made camera movement a special case and made render-resource ownership difficult to reason about.
After this change:
Camera,MovingCamera,MultiCamera, andThreeDCameraown semantic frame, background, ordering, projection, shading, tracker, and nested-view state;CairoRendererand private helpers own all Cairo resources and raster work;For ordinary Cairo scenes, camera movement no longer requires a specialized scene class:
MovingCameraandMovingCameraSceneremain supported as thin named subclasses.Breaking changes and migration
Supported top-level imports remain unchanged:
Implementation-module imports are intentionally a clean break; no duplicate compatibility modules are retained.
Module paths
manim.camera.cameramanim.renderer.cairo.cameramanim.camera.moving_cameramanim.renderer.cairo.cameramanim.camera.multi_cameramanim.renderer.cairo.cameramanim.camera.three_d_cameramanim.renderer.cairo.cameramanim.renderer.cairo_renderermanim.renderer.cairo.rendererormanim.renderer.cairomanim.renderer.opengl_renderermanim.renderer.opengl.renderer,manim.renderer.opengl.camera, ormanim.renderer.openglmanim.renderer.opengl_renderer_windowmanim.renderer.opengl.windowmanim.renderer.shadermanim.renderer.opengl.shadermanim.renderer.shader_wrappermanim.renderer.opengl.shader_wrappermanim.renderer.vectorized_mobject_renderingmanim.renderer.opengl.vectorized_mobject_renderingOpenGL shader resources move from
manim/renderer/shaderstomanim/renderer/opengl/shaders. Plugins loading those resources directly must update their package-relative paths.Camera construction and raster access
Cairo camera constructors now accept semantic view settings only. The following raster-oriented arguments are removed:
pixel_width,pixel_height, andframe_rate;image_mode,n_channels, andpixel_array_dtype;backgroundpixel arrays andcairo_line_width_multiple; andMovingCamera.fixed_dimension.Use
config.pixel_width,config.pixel_height, andconfig.frame_ratefor render execution settings. Cairo output is standardized on renderer-owneduint8RGBA targets. Usebackground_image,background_color, andbackground_opacityfor camera backgrounds. Configure logical view geometry withframe=,frame_width=,frame_height=, or the publiccamera.framemobject. Unknown camera keywords now raiseTypeErrorinstead of being retained silently.Camera-owned raster APIs are also removed, including
camera.pixel_array,capture_mobjects(),get_image(), Cairo-context caches, anddisplay_*/apply_*drawing helpers. Direct raster users should render throughCairoRenderer:For one-off mobject images,
mobject.get_image(camera=camera)manages that temporary renderer automatically.Custom camera subclasses should implement semantic hooks such as object ordering, point projection, shading, and movement controls. Backend drawing extensions belong in a renderer integration rather than a camera subclass.
Removed camera implementations
MappingCamera,OldMultiCamera, andSplitScreenCameraare removed.apply_function()rather than throughMappingCamera.MultiCamerawith positionedImageMobjectFromCameradisplay mobjects.ImageMobjectFromCameraremains available, but it is now a geometry-bearingMobjectrather than anAbstractImageMobject. It stores nopixel_arrayand has noget_pixel_array()API;CairoRendererrenders the source camera into a secondary target and composites it into the display geometry. Existing positioning, display-frame, and resampling usage remains supported.For ThreeD camera positioning, use
camera.frameorcamera.frame_center; the private_frame_centermobject is removed.Links to added or changed documentation pages
docs/source/guides/deep_dive.rstdocs/source/reference.rstdocs/source/reference_index/cameras.rstPreview links can be added after the PR is opened.
Further Information and Comments
This PR does not move scheduling or resource lifecycle into
Manager, redesign OpenGL context lifetime, or unify Cairo and OpenGL cameras. Tests etc. pass locally.AI assistance was used during implementation and review. The resulting design, code, documentation, and tests were reviewed by the author.