 Good morning, everyone. I'm Ashod Nakashyan, and I also go by Ash. So this talk is going to be a little bit about tiles, essentially, and tiles in online, calc-specific bits, and rendering in LibreOffice in general. So hopefully a little bit for everyone. So just a quick overview. I'm going to give a quick introduction as to what is tile rendering in general, and what problem I was trying to solve, and how I went about solving it, and then just thoughts for a long-term better solution. So quickly, the rendering in desktop is obvious to everybody, because we don't think about it. It just works. So this is just a desktop version. This is a spreadsheet. It has images in it, and a couple of buttons. It has some cells with background. Nothing fancy. You see there is a selection there. We're going to get back to that. This is the same thing in online. So this is in the browser. And what actually is happening here is that highlighted area is, in fact, an image that shows part of the spreadsheet. So the browser doesn't know what's going on, except it just has a matrix of PNGs just stacked next to each other. And you can see it just is a random place in the spreadsheet. There is no clear boundaries as to where things start, which is distinctly different from what is happening on the desktop where you see the selection is actually cleanly on the cell boundaries. So this is what happened when we tried to figure out why do we have a slow performance. Whenever you go to a large spreadsheet, you see that your tiles are getting slower and slower to come back until you've actually hit a place where you actually have to wait tens of seconds at the lower rows. So clearly, something is wrong. Let's go back to the desktop and see how it's performing. This looks much better. It's pretty much the same and rather snappy too. OK, so what is going on? The desktop, it seems to be fast because it just doesn't need to worry about alignment. You just scroll and then you're cleanly on a column boundary. That's good. That helps. And you're only rendering divisible stuff, obvious. Why wouldn't you? And it actually has a streamlined version. We'll see what that means because we need to compare it to what's going on with the tile version online. This is roughly what's going on on the desktop. So imagine that selection is your view. So if you zoom in or if you scroll and you have a bunch of cells visible on your window, and that's what you really want to render. And internally, what is happening is you get the offset and you actually have two offsets. There is the offset of the corner of your first cell, the top left corner. And you potentially could have an offset within that cell. On desktop, you can't do that. You can't really scroll to the middle of a column. So they are identical. So the origin that I point to is negative, in this case, relative to your rendering area. And your rendering area starts at the screen x and screen y coordinates. So it seems straightforward. Now, why is the tile rendering going on slowly? Because we really need to, apparently, render everything from the 00 origin, which is the left top most pixel, essentially, in the spreadsheet in that document. And that gives you the linear progression that we've seen. So the further you are from that origin, the bigger an area you need to render, and then throw away all that work. So something clearly is very wrong here, and we need to fix it. And this is what we try to do. Here is an approach. Why don't we just do something similar to what desktop is doing? Just render whatever you really need to show on the screen. And to do that, just figure out what your actual offset is. And you actually need two offsets. Because remember, for tile rendering, the tile can start at any random place in the spreadsheet. So you don't have a clean boundary. So you need two offsets. You need to figure out what is the column and row that you need to render first. That's the first visible row, first visible column going from top to bottom and left to right. And then you need to figure out your offset in pixels, in this case, within those row and column boundaries. And that's it. You just need to render essentially all these cells and then cut the part that you're interested in. And that's your PNG image that then you send to the browser. So we do that, and this is what we get. This is a tile. And this tile is supposedly the part where there's a button. But we don't get the button. So now we're okay, but it's fast, right? Because you're not rendering anything more than you need. So right, you just gave it the offsets and it's rendering and everything is fine. And here's another tile. Here there was supposed to be another image. So you're not getting your images and you're not getting the buttons or whatever is embedded. Okay, so okay, we have more work to do. So what is the problem? The problem is that when you're rendering, you actually are rendering in layers, right? So you do your background first and then you draw the cell boundaries, that grid that we see, and then you do the cell backgrounds, those yellow ones that we had with the cell merge. And then when you're done with that, you do the text and then you do the buttons and images and so on. Okay, so you have a layered approach and what is happening is that these rendering implementations, each they have their own coordinate system, essentially. And for the desktop, that's fine because everything is streamlined. So all these layers, they all know what the coordinates that they need are and they do the conversions and it's been debugged and well maintained or not maintained. It really doesn't matter at this point because it is working. But for online, we need a different approach because we are rendering on a virtual device. Everything is in pixels. We don't really have any choice, it's just an image. And so everything is in pixel coordinates. So we need to make sure that all these stages, they're using the pixel coordinates. And to do that, we need to add logic that says, am I in the tile rendering mode? If yes, then okay, changed my coordinate into pixels and scale things differently and so on. So we have special cases for tile rendering and that's where the problem is coming from because our special cases don't take into account that you're drawing a bitmap now or you're drawing a button or something else. So that seems to be the problem and the solution is that you render everything from the 00 origin, so you end up rendering everything. You make sure everything shows on your screen but it's extremely slow. So we really need to go and fix the problem of the coordinates so that we get the elements that we have in the document itself, okay? So it's a special handling that's the problem and so how do we do that? We do that using the map mode. Anyone who had the privilege and pleasure of actually looking at the rendering logic would know what I'm talking about. This is a magic class, right? This guy is responsible for storing and converting your coordinates. So you assign a map mode to your device and the map mode holds the scale essentially of things. It holds the unit that you're dealing with and it actually does a conversion between logical and physical, okay? And it has, essentially it has a support by simply being assigned to the device and every rendering implementation, it calls essentially logic to physical and you convert the coordinates and then it does the correct thing. So it is pervasively everywhere in the code. All the rendering logic depends on it. So if you don't have the correct parameters here, everything goes to how essentially, nothing works, okay? So this is the situation that I'm describing. You end up with a system where instead of having a perfect coordinate system, you end up having multiple coordinate systems and you need to convert between them but to do that you need to make sure that you know what coordinate system you're in and what you need to transition to and that code needs to know what it is expecting, right? Before it does the conversion, otherwise nothing would work and the way it breaks, it breaks like that. Things just show up in the wrong place or things don't show up at all. So this is what we have. We have essentially a bunch of different units that we're using and all the time there's conversions going on. The pixel one is what the tiled rendering wants. Twips is what is used in many, many places but in reality we wanna really go to the 100th millimeter approach which seems to be a low enough denominator that it should work for pretty much everything, okay? But it actually gets worse. You can have a map mode set that is correct but it can be disabled. So the code actually selectively enables, disables the map mode so sometimes it is in the correct one but it is completely disabled so there is no conversion going on. Your numbers are one to one. So when you render something, whatever units it has for the offset where it begins and its size and everything it essentially is being multiplied by one and nothing changes so you get exactly the same numbers on your device. Sometimes it is enabled and there is a conversion going on so you get a different number, you get a different position, you get a different scale and this is happening essentially throughout the whole rendering stack and you don't know when it is happening and how something's worked just by accident. Some parts of the code explicitly set the map mode to a certain thing. Some parts they just reset it redundantly so you actually don't need to do that but we're doing anyway. So any change and things break. Essentially you have no guarantees, you don't have any control on anything. Everything is manual, everything is custom and special case. Now what we want to do is, we want to actually make sure that everything works in pixel mode for tile rendering and we want to make sure we don't touch and we don't break the desktop because the desktop again, we don't want regressions so you only want to selectively say I'm in tile mode, change the map mode to pixel and you need to do that and make sure that whatever you're rendering is happy to work in pixel mode. So it's not just the map mode that you need to change, you also need to make sure that your rendering is working. Now again, you have a solution but you don't have an approach. How do you find all these places? Okay, so this is the algorithm I came up with. I just dumped essentially the map mode details which hopefully is gonna give me a clue as to what numbers I should get out of the conversion from logic to physical coordinates. And I do this and I run and I find a case that renders incorrectly in tiled versus the desktop which renders perfectly fine. And if I do that, I get a log and then I just dip the log. So I spend a lot of time in essentially a merge tool or something that gives me a dip and then I can selectively try and find which part the numbers has started to change and then find that code and try to figure out what's going on. And this is a repetitive thing and I'm essentially binary searching where do things diverge and try to figure out how to fix that. But even then, I'm not able to get 100% correction and I'm gonna talk a little bit about that later. So this is the results that I got after I did that for a while. I was able to find the correct location where the button and the image rendering need the correct map mode. I set that and it's essentially a few lines of code that I need to plug in the right place but it took me the good part of a week. So you can imagine how expensive this is. And this is a tile that you can see, I'm not rendering from the topmost of the document. So it's a random position and I'm still getting it rendered correctly. So now I'm very happy and I push the patch. And after pushing the patch, I open another document and this is what I see. You see that little guy up there in the corner? That's actually a graph and that is completely messed up. It's just wrong scale, wrong position and it actually gets better. If you zoom in and out this document with this bug, that thing starts to move and grow and shrink with your zoom. Why is that happening? Because it's scale is wrong and it is changing with the zoom of the document, it's your view in general. Okay, that's not working, but everything else seems to be fine. You see the text is rendered correctly and you get the image in the right place. So I have another special case that I need to work on. And so okay, this is clearly, we can't spend a week to find every special case like this and fix it. So I go back and create my logs and essentially recreate my testing environment. But what has happened is actually far worse than I was hoping that would happen. Because of the fix that I put in for the image and button, I actually changed everything. I changed everything downstream from that point on in the rendering stack because I am changing the map mode. So now the coordinates are completely different. So I need to have a special case where maybe before fixing the image and button cases, I didn't need, okay? So maybe the graph was actually working without my map mode change because it just accidentally happened to be in the correct mode. So nothing was wrong with it, but I broke it in fact, because again, this code, it's all implicit stuff and somebody's doing logic to physical and it expects to be in the correct scale, correct units, correct origin and everything. So I need essentially to start from scratch every time that I touch the map mode in the rendering stack, okay? So I just have to do everything all over again. Every time that I change anything, every time that I find a special case, I need to repeat. This is the end result after I'm done with it, after a little over two weeks later, I get to a place where I'm really happy because now we have a fast rendering for any tile anywhere, okay? It's essentially a const rendering. There is no more a factor or a coefficient to dependent on the position and we're pretty much the same performance as the desk. So there is a very slight overhead that we have, but this is actually not real numbers. It's just for illustration, obviously. You don't get the same numbers on every machine. So what is the real solution here? The real solution is that you should only set the map mode on your device and the device is actually an abstract concept. Just set it once and you don't really need to know what device you're rendering on, which seems to be the case. Everybody's happy with that. So why do you need to know the coordinates of your device and why do you need to change it and why do you need to customize it? So ideally we shouldn't be doing that, but we don't live in an ideal world. In practice you wanna have an even better thing. You wanna have all devices to support your most generic, most sensible units, okay? So that not only all the devices they have the map mode set once, but also that you know you're rendering is in a single unit, in a single coordinate system and then everybody is able to debug and understand the coordinate system because not only they are abstract, but also they are the same and this will give you a very high precision in terms of how you're rendering and where you're showing your stuff without any problem. Now you can support anything, including anti-aliasing for text, for images, for graphs, anything fancy you want because this coordinate system is precise enough essentially. So we can't do that, why? Because apparently it's really, really difficult and because it is difficult, we've spent a lot of time and a lot of people actually try to fix it. There are a lot of special cases and removing these map modes from every single special case is breaking everything. The reason is the code is so complex that it just, one part depends on the other, it depends on the other. So you can't just remove a single map mode and then figure out how you're gonna fix it and move to the next one. You need to change a whole bunch of them and then everything falls in the right place and it works, otherwise you have a case that works but you have a lot of regressions and nobody's gonna be happy. But I'm fairly confident there is somebody out there who can figure it out, how to do it because we really need to fix this. This is broken in core in a very fundamental way and anytime anyone needs to touch it, we'll just have to go through pretty much the same agony. So with that, thank you and questions.