Project Report: Reworking Impress Slideshow Rendering with DrawingLayer Primitives
Modernizing the Impress Slideshow
The Problem Statement
The LibreOffice Impress slideshow engine had an old and inefficient way of rendering slides. The main problem was that it used a complicated, multi-step process. All visual objects, represented by DrawingLayer primitives, were first converted into an old format called a StarView Metafile. This metafile was then rendered through several layers of code before appearing on the screen.
This extra conversion step was a major issue. It was slow and wasted resources by turning modern data into an old format and back again. The multiple layers of code made the system hard to debug, maintain, and improve. If there was a bug or a performance issue, it was difficult to find the cause. This old design stood in the way of making the Impress slideshow faster and more modern.
The main goal of this project was to remove this old process. The plan was to get rid of the metafile conversion and change the engine to render the DrawingLayer primitives directly to the screen. This would simplify the code, reduce performance overhead, and create a cleaner, more maintainable system for the future.
Project Goals and Impact
The project had several clear goals to modernize the Impress rendering system:
- Remove Metafile Dependency: The top priority was to stop using the metafile-generation process for rendering shapes in the slideshow.
- Direct Primitive Rendering: The main technical task was to switch to a direct, primitive-based rendering method, using existing, efficient tools like CairoPixelProcessor2D.
- Maintain Quality: It was essential that the new renderer produced the exact same visual output as the old one. All content, animations, and effects had to look identical to avoid any issues for users.
- Simplify the Architecture: A key goal was to prepare for future improvements by separating the slideshow engine from older, complex canvas code.
How the Project Was Implemented
The project was carried out in several phases, starting with an analysis of the old system, followed by implementation and testing, and ending with a major code restructuring that went beyond the original plan.
Phase I: Analysis and Change of Strategy (Week 1)
The project started with a close look at the existing rendering process. I traced how slide shapes were drawn on the screen and confirmed that an SdrObject creates a set of Primitive2D objects. I found that a component called UnoGraphicExporter was responsible for the conversion from primitives to a metafile using a Primitive Processor.
This understanding led to a key decision. The first plan was to write a new renderer that would still use the existing XCanvas interface. However, after talking with my mentor, we chose a better strategy: to bypass the XCanvas and cppcanvas layers completely. The new plan was to render primitives directly to the screen by getting the native drawing surface (like a Cairo surface) and using existing DrawingLayer tools like CairoPixelProcessor2D. This change in strategy was crucial, as it turned the project from a simple component replacement into a fundamental simplification of the rendering system.
Phase II: Matching the Old Renderer's Output (Weeks 2-3)
With a clear plan, the next phase was to implement and test the rendering of static slide content. I created Primitive2D sequences from the slide's shapes and sent them to a CairoPixelProcessor2D that was connected to the slideshow's Cairo surface.
At first, this resulted in a blank screen. To figure out why, I first drew simple lines directly on the surface using Cairo commands. The lines appeared correctly, which meant the surface was working. The real problem was an incorrect transformation matrix that was scaling and positioning the shapes off-screen. I found the correct transformation logic in the existing ViewLayer code, and once I applied it, all static shapes appeared in the right place and at the right size.
Next, I worked on animated shapes ("sprites"). This brought new challenges, like making sure a shape stayed in the correct final position after its animation, applying rotations correctly, and handling scaling properly. I solved each of these issues. To ensure the new system had no visual bugs, I rendered frames using both the old and new methods and compared the images to make sure they were identical. This careful testing confirmed that the new renderer perfectly matched the old one.
Phase III: Handling Advanced Features (Week 4)
With basic rendering working, I moved on to more advanced features. Animated GIFs were only showing their first frame. I found that the rendering system could already handle animated GIFs, but the slideshow engine wasn't telling it how much time had passed. The fix was simple: I added a timer to the DrawShape class and passed the elapsed time to the primitive processor, which then drew the correct frame of the GIF.
A bigger challenge was that many text-based animations were broken. This led me to discover a hidden feature of the Impress animation engine: for effects like "fly in by character," the engine doesn't animate the whole text block at once. Instead, it splits the text into parts—paragraphs, words, or even single characters—and animates each part one by one.
Implementing this required studying the text-splitting logic. I started with paragraph-level splitting, which was fairly simple. However, it was clear the old system also supported splitting by word and character. This was a good lesson: important features in old systems are often not obvious and must be carefully studied to ensure the new system works just as well.
Phase IV: Restructuring the Canvas Architecture (Weeks 5-10)
The work so far pointed towards a bigger issue: the decoupled canvas module was only used by the slideshow, and the decoupling caused by the UNO interfaces only added extra layers of code that were hard to maintain. To truly modernize the system, I had to restructure the canvas code itself. This led to the longest phase of the project: moving the necessary code from the canvas module into the Visual Class Library (VCL) module.
I approached this large task carefully to avoid breaking other parts of LibreOffice that used the old canvas module.
1. Copy and Isolate (Week 6): First, I copied the cairocanvas and canvas::tools code into the VCL module. This created a safe, isolated copy that I could change without affecting the stable code used by the rest of the application.
2. Simplify and Modernize (Weeks 7-8): With the code isolated in VCL, I began to simplify it. I removed the UNO interfaces around the canvas code, which were no longer needed, and replaced them with plain C++ classes. This also gave me a chance to update the code's memory management from UNO's reference counting to the modern C++ std::shared_ptr.
3. Integrate and Test (Weeks 9-11): Once the new VCL-based canvas was simplified and working, I began connecting it to the slideshow. This involved steps like forwarding window events to the new SpriteCanvas. I re-enabled and tested static shape rendering first, then did the same for animated sprites, making sure everything worked correctly.
4. Final Fixes: To finish the update for the Cairo backend, I fixed background rendering to use the new primitive-based method and removed an old hack that was used to draw the first frame of a slide. By the end of this phase, the new, refactored VCL-based canvas worked just as well as the old one for the Cairo backend.
Phase V: Prototyping for Other Backends (Week 11-12)
After successfully refactoring the cairocanvas, the project's focus shifted to extending primitive rendering support to the other backends: the generic vclcanvas and the Windows-specific directx canvas.The strategy employed was to first establish a minimal working implementation for each, proving feasibility before committing to the same large-scale architectural refactoring that cairocanvas underwent. To do this, development reverted to an earlier state of the project, where a less-refactored, "hacky" version of the primitive rendering pipeline was functional.
VCL Canvas: I had some success adding primitive rendering to the generic vclcanvas. Static content rendered correctly, showing that the basic approach worked on this backend. However, animated sprites still need more work.
DirectX Canvas: I ran into a major technical problem with the directxcanvas. The existing primitive processors use the Direct2D graphics API, which is not compatible with the older DirectX 9 API used by this canvas. Making them work together would be very complex. Because of this, I paused work on this backend. This exploration was useful because it clarified the challenges for modernizing the remaining backends.
Project Status and Future Work
Current Status
The project has achieved its main goals and completed a major architectural update.
Completed Work:
- The Impress slideshow on the Cairo (Linux/GTK) backend now renders DrawingLayer primitives directly, without creating a metafile.
- The core logic from cairocanvas has been moved into the VCL module, and its dependency on cppcanvas and UNO interfaces has been removed. Memory management was updated to use std::shared_ptr.
- The new renderer works identically to the old one on the Cairo backend, correctly displaying static shapes, animations, animated GIFs, and slide backgrounds.
- Paragraph-level text splitting for complex text animations has been implemented.
- Work has started on other backends, and static rendering now works for the generic vclcanvas.
Known Issues:
- The text splitting feature is not finished. Splitting by word and by character for some advanced animations still needs to be implemented.
- The new rendering system is not yet working on all VCL backends. vclcanvas does not support animation yet, and directxcanvas is blocked by a graphics API mismatch.
Next Steps
The work done in this project provides a strong foundation for fully modernizing the Impress rendering engine. The following steps are planned to complete this effort:
1. Finish VCL Canvas: The first priority is to finish the work on the generic vclcanvas backend by adding support for animated sprites.
2. Find a DirectX Canvas Solution: The problem with directxcanvas needs more research to find the best way to support primitive rendering. This might involve building a bridge between the Direct2D and DirectX 9 APIs or creating a new primitive processor for DirectX.
3. Complete Text Splitting: To match all of Impress's built-in animation effects, the remaining text splitting logic for words and characters must be implemented.
4. Remove Old Code: Once all VCL backends are using the new rendering path, the original canvas module and cppcanvas wrapper can be completely removed from the LibreOffice code.
5. Measure Performance: After the migration is complete, a performance analysis should be done to measure the speed improvements from the new renderer.
Code Contributions
Feature Branch
All work was done in a dedicated feature branch to keep it separate from the main code during development.
* Branch Name: feature/slideshow-primitives
* Link: https://git.libreoffice.org/core/+/refs/heads/feature/slideshow-primitives
Summary of Major Code Changes
The table below summarizes the key stages of work.
Technical Challenges
The project had several technical challenges, which were great learning experiences.
- The Blank Screen Problem: At the start, nothing would render. The step-by-step process of first checking the drawing surface and then finding the incorrect transformation matrix was a practical lesson in computer graphics.
- Hidden Text Animations: Discovering the text splitting feature was a reminder that old systems often have important but undocumented features. Figuring out how this worked and rebuilding it was a challenge that showed the importance of thorough testing.
- The Refactoring Effort: Moving the canvas logic into VCL was the most difficult part of the project. Separating the code from all its UNO dependencies and putting it back together without breaking anything required a lot of patience and attention to detail.
- Cross-Platform Development: Building and testing on Windows to support the DirectX canvas was also a challenge, as it required getting used to a different development environment.
Key Learnings
This project taught me a lot about software engineering, especially in a large open-source project.
- Working in a Large C++ Codebase: The most important skill I gained was how to navigate, understand, and change a huge, mature C++ codebase like LibreOffice. I learned how to use a debugger effectively and break down large problems into smaller, manageable parts.
- Practical Refactoring: The project was great hands-on experience in practical refactoring. The decision to remove the UNO interfaces was a targeted choice to reduce complexity for this specific part of the code. This, showed me how to make focused improvements to a system's architecture.
- Lifetime Management of Objects: A key part of the work involved changing object lifetime management from uno::Reference to std::shared_ptr. This required carefully studying how different lifetime management strategies work in large systems, and it gave me a deeper understanding of ownership, reference counting, and resource cleanup in C++.
- The Value of Mentorship: Finally, the project's success was greatly helped by my mentor's guidance. The advice during the first phase to change strategies set the project on a much better course than originally planned. This showed how important mentorship is in open-source development.
Comments
Post a Comment