 Welcome everyone to that session. So I'm Kevin Othens. I live in Toulouse in France, and I work for KDAB. Turns out that part-time I'm also a teacher at the university and one of my pet interests that's project management, but also testing techniques and refactoring techniques, and I tend to teach that there, and very often I get comments like, yeah, but I'm doing things with OpenGL, or I have something which is graphical, and then I cannot test it, right? That's very often the type of comment you get back in this kind of situations. And so I design an exercise which basically works you through a potential technique for this kind of situation. In case you've seen one of my previous sessions, because I had one at Dev Days two years ago about having all legacy code base, and then you start to test and you start to refactor it, that's a bit in that same mood. The difference being that it's highly graphical in that case, while the case we've seen couple of years ago was more about business rules and how to refactor that. But that's, let's say the main difference, that's really a game changer in the way you approach the thing. One other thing is that when I presented the technique back then, I talked about, so we will see that we put in place a test, which I call a PIN test, and I said at the time, so on the other exercise, which is named the Ingrid and Rose, that you basically have two potential approach, one which is the Golden Master, where you basically duplicate your original system and then you run your test against both the old and the new and verify they don't diverge, but sometimes that's too expensive, that would be a situation like that that we have here, and then there's the record replay approach and that's what we're going to use in that case. Okay, so let's complete with the context. So the idea here is to simulate, so you have to imagine that simplified situation because that's just an exercise, right? That's generally something I run over the course of several hours, smaller classes, right, and we debate what works, what doesn't work, won't be the case here, so I will use a few tricks to actually fast forward a bit in there, but the world context that's imagined that you jump in a new team, and that team has already some legacy software in there and then you're basically asked to add features in there or to refactor, and then you have to wonder how do I deal with that? So again, that's just an exercise, right? So keep in mind that the code base will be somewhat small, but imagine that you would have to do something like that, like 10-fold, right? That would be much bigger. And this one is actually inspired by work I've been doing for some customers, and obviously that was way bigger systems. Okay, so you would get something like that when joining the team, so I am welcome to team Lavatulip, yeah, I like followers. As you know, we are a small, collectively-owned Smithy, managed by a friendly resident blacksmith named William. We also help a visiting craftsman design their future pieces before warming up the earth and avoid wasting raw material. We have a system in place which guides the creation process by using predetermined primitives. It also gives real-time preview of the new piece. It was developed by a conservative developer named Duncan who left us to pursue a family quest. First, an introduction to a system. So all pieces are created from one or more primitive. A piece can be displayed in wireframe using its plain color or both. A piece can be rotated using the mouse. A piece can be shown in the X, Y, X, Z, the likewise planes. Pretty simple, right? Well, this is where it gets interesting. The preview has a point light of the same coordinate than the camera. As an alternative to the plain color, a piece can be displayed as a color map showing the normal map of the object. The X coordinate of the normal is impacting the right channel of the point, Y, the green channel, that's the blue channel. Recently, our visiting craftsman started complaining that the preview is not good enough for their use in some conditions. This requires an update to your system to move from the default rendering using Google Shading, which is pretty much what you get when you're doing the old OpenGL stuff, to the better phone shading, and perhaps PBR in the future, especially if you followed Sean's session yesterday. Feel free to make any changes to the GL View class that we will see in a minute, or add any new code as long as everything still works correctly. However, do not alter the geometry builder class at all, as it belongs to the family of dwarves from the neighbor mine. They are also our main provider of raw materials and we refuse to deliver further in case of change as they don't believe in shared code ownership. Your work needs to be completed by Thursday at noon. Just for clarification, a piece can be displayed plain or wireframe, but when it is rotated using the mouse, it is always displayed in wireframe. So that's something you might get from manager or from a customer, right? And there are those nice split specifications, and it's all, by the way, there's that thing in the corner that should not be broken, right? So, first approach to the thing, let's run it and see what it does, right? So we have this existing code base, so we have a small application, and in that application, so we can control the view, and then we can start to add primitives at a given coordinate. So we can create a bar, for instance. Okay, so I just get a bar, which I can rotate, and as mentioned in the potential email, just get the wireframe when I rotate, and if I stop rotating, then I get it back, right? For the primitives, I can have a ring, okay? Or I can have a squarely wave, and just for the sake of it, I will push it down. Okay, and so we can have this kind of object that we are specifying. And we have those layers, so I can display just the wireframe, or the wireframe and the model, or without the wireframe, but then if I rotate, I get the wireframe back, or I can have the normal map of my objects, okay? So not many features, but we can see there's plenty of different combinations already, and we have to make sure that we are changing the lighting model, and we will have to actually change the type of OpenGL code we have without breaking anything along the way, right? So, generally developers have that, especially when they start in their career, they have this hubris of, that's crappy, let's rewrite, okay? But if I rewrite, that's where I take the most risks in my project, right? Especially if I have something which is several millions of lines of code, so we need something to do that in a safer way. Okay, so that's it for, yeah, one more thing, so I can actually zoom that, right? Or I can do it with the spin box, and jump in X, Y, XZ, YZ, and perspective, okay? And so we want to keep all of that. Okay, so now let's have a first look at the actual code. So we have this geometry builder which was mentioned that we're not allowed to modify, that's basically the one from the GUI, we just say to the geometry builder, add a bar there, and that generates the geometry, okay? So we have build commands, and we specify the type of geometry we want to build, and the X, Y, Z that I could control from the GUI. And so we see that those functions are used internally, and then we have that function, that's the one which is actually used by the GUI, okay? I'm not getting in detail on that one because remember that's the one we don't want to change. I don't have the control of that, so apparently they don't see, not enough contrast, that's, yep, if someone can do something about the light, okay, but I won't get, yeah, bigger still. Does that help, at least, if I agree with it? Sir? Yes, okay. Okay, so I won't get into details for that particular class. Yeah, probably helps. Don't fall asleep, please. So I won't get into details on that one because that's the one we're basically forbidden to modify, okay? What we're interested more is what is that kind of black box is using and that black box is using that class and that's the one we will be particularly allowed to modify along with the view state that we will see in a minute. So what we have there publicly, that's a QGL widget and it has a view state that we will look at and then from the outside so that the geometry builder can ask to create a list, release a list, and call a list. And then we can know what the current list is, we can create, so we start to see it slightly fishy and we are mixing concepts in there because we have a create list, we have a begin list, how does it relate, so we'll have to find that out. And then we have an end list and we can set a color and notice the nice pointer to floats, right? And we can add faces and we can add a polyline and then the rest is pretty much about controlling the view. Controlling the camera Z, the rotation of the model, and so on. As for the view state, so the view state, we have several type of lists and as we see here, that's a different part of our model. We have the model or the wireframe or the normal map. So that's the layers that we were seeing on the GUI and we have this MrU's temp list. And then so we have an ID for the model, we have an ID for the wireframe, an ID for the normal map and we can enable or disable a given layer that seems to map to what we've seen earlier on the GUI. So let's get back to that one and take a look at the implementation, right? Okay, so we have our view which for some reason seems to be a singleton. I don't think we will fix that today but that's something we could attack. Then what basically constructor, then getting a reference to the state and then we have the create list and finally we start to see some of our OpenGL code. So here we are using, we see that the customer code is using draw lists so that's all the OpenGL, right? And with what have been asked by the customer, basically we want to move from, to have a chance to get to phone and potentially PBR model lighting, we can't stay with this very old way of doing OpenGL, right? So we love to replace those draw lists probably with shaders and buffers and so on. So we are creating a draw list, we are calling a draw list, that's basically simple wrappers for the draw list concepts around OpenGL one. And then we have this begin list which creates a different type of draw list. So again we see it creates a draw list but it stores the idea of our draw list inside of the view state, okay? We see here we have the set model ID. So we're keeping IDs of draw list for later usage. And we have different path for model list, wild frame, normal and we just crash in the other cases because we don't want to support that. Then some more code to specify some parameters on the materials that we display. You remember that was slightly shaded. And then we have the end list where we just wrap end list and we add memorize which list we were building. So we just reset to we have no list open right now. And then the set color just wrap GL call and then the ad faces starts to be interesting. So we have those very large arrays with indices and those loops which looks ugly. And more loops which looks ugly with Mr. U's numbers in there, right? And the quad and that looks ugly as well. And then we have the polyline, the ad polyline. So again, we are getting positions, color and number of points. Hopefully that's what we have on at the end of those pointers. And then we create a line strip, okay? And the rest, that's to control the view. So the camera change, model rotation and the initialized GL which is forced by the GL widget, the resize GL and the paint GL, okay? Where we are mingling with the matrices to position our model to position the model and to control the projection of the camera. And then we call one of the lists, okay? So that's for the model layer only if that's enabled. Then we change the state of open GL and then we call for the normal map and then for the wireframe, okay? So we see the rendering algorithm right there and then that's dealing with the mouse press, mouse move, wheel events which all change the rotation of the model. So that's way smaller than something real, okay? That's big enough already. There's enough complexity to play around with that and learn a few tricks. And so the idea is here that we want to refactor that. So generally when I have time, I get to discuss how we want to refactor it but that's pretty much the situation we have right now, okay? So we have our geometry builder which talks to the GL view and the GL view as internally a view state and this view state contains different ideas of draw list and the GL view as we can see that basically maps onto a very procedural way of dealing with open GL and that's pretty much this type of situations you would have with open GL one code. What we want to do is to move to a new world which would look like this, okay? We want to start to grow a bit like for a scene graph to be able to have something which is better suited for modern open GL where we use shaders, where we use buffers and so for that we need from our GL view to grow two different type of objects, right? So scene layer and scene node. So then it becomes explicit instead of having lists and some of the ideas start somewhere, some other we don't really store. We have two objects, one materializing a layer and one materializing whatever is inside my layer, okay? So we have a scene layer and a scene node. Scene layer will have the shader management because we've seen that the type of shader we have, I mean we have different rendering per layer so we'll have basically a shader per layer and then the scene node will have the buffer management because that's where you have the actual geometry you want to display, okay? So we want to move from that to that, okay? It's not a small fit, right? In something in a system like that and so the first question because I would try to keep it a bit interactive even though that's not a full-fledged workshop is what's the first thing you should try to do before to get there? Anyone? So you basically your proposal is I start to code the solution right away, okay? Yeah. Okay. Do we have an extra step we might want to do before that even? Yes. Yes, okay. Not unit tests, right? What we want is an automatic test, okay? To be able to do that, right? To be able to be sure that when I start to plug my scene node or when I start to plug my scene layer and when I start to move some of the code inside there I'm not breaking anything, right? Because otherwise I'm taking the same risks than if I would throw away everything and start to rewrite, right? I would have no way to make sure that I keep the features properly for as long as possible right in that process. So the first thing we want to do is an automated test. Why I'm saying no, that's not a unit test is that if you want to go for unit tests, automated unit tests, that would be way too expensive, okay? And we have to be done by Thursday, right? So we cannot spend a lot of time, right? Adding some unit tests, probably starting from the lower layers and building up and having very fine unit tests for everything. Especially since we are about to drastically change the design, so that means we would have to update those tests very frequently as we update the design. So in a situation like that, what you want is something which is much wider spectrum type of tests, okay? And that would be something on the fully integrated system if possible. And that's what we basically call a pin test, okay? The idea, why a pin test, that's the idea that you have like on a chalkboard, right? You have something you want to fix, right? And you put pins on your paper so that it doesn't fall off the wall, right? That's basically the idea. So we want to pin our system to have a test which verify that we have those pins in place. It's not perfect, it's not very fine grain but that will scan the whole system. So we try to do that. So the first thing we do is to have a test. So here I will use a Qt-based automated test depending on your situation. You might want to use Squish for that but I wanted to keep it small and not have Squish in the picture. So I'm going for something simple. In the beginning we start by just verifying that I can make a test which fails, okay? So I have a failing test. That's what we see with there, right? It should fail and we verify something which is false so it should fail, right? And if I run it, indeed I have something which fails. Now, obviously that's not satisfying. We're just creating the window and doing nothing with it. And so that's one of those times where I would have to fast track if we want to get a chance to do that in one hour. And so I will fast track a bit and pretend that I typed all of that right now. And so, right. So I have a failing test and I have one extra step, I think. Okay, so after a while I would probably end up with something a bit like that, okay? I try to plug, basically what I do is I'm introspecting on the window, right? That's what we're seeing here. For instance, with the showwz, okay, that function. We try to find the actual pushbutton which does that and we click it, right? So we simulate the user interaction to force the view to change. So we have several of those introspection functions, same thing for the primitive. Okay, so I'm wrapping the GUI behind a small API for my own consumption for my test. And then my test will become something like go through all views where I show the perspective and then I change all the modes and then I show X, Y, I change all the modes, I show X, Z, I change all the modes. Okay, I want to be very systematic in my test to be sure that I move the object around, I've seen the different views, I zoom in and out, I activate, deactivate all the layers to be sure that at least visually I can see the thing. So I won't get into detail there. What's important to remember because I change system by system is you have to first go through that phase, okay? Because that's something unlike a full suite of unit tests, you can pull something like that on a real system in a couple of days, okay? Unit tests you would spend monthies and not cover everything. So just running it, you see how it looks now with that. Okay, so that's my test. Now if we look closer to that though, I basically have no asserts, right? So that's nice, I have a test, right? But it's not really checking anything, right? The other thing is, okay, I've been doing all of that, but am I really covering all the code I'm about to refactor, right? That's one of the thing I need to verify. So the very next step for something like that is as you write this kind of things, what you want to do is to turn on code coverage, okay? So you turn on code coverage with QMeg, that's actually fairly easy, right? In the case of GCOV, just go for config plus equal GCOV. Then you have to rebuild everything though. So we'll rebuild all of it, okay? Then I will run it again. I hope you like it because we're going to see that a couple of times, okay? And now that I run it, so I have, maybe you can't read that, but I have GCOV which generated actually coverage information, right? And so I can go for GCOV. I have a script for that, if I can type properly, okay? And I have the coverage index. Okay, I want to be guided to make sure that I go through everything that I need to test, right? So I'm pulling over my coverage information and then I look that I cover GLVU 95%, okay? So that's not bad. Still, where are the 5% coming from? Well, apparently that one was never called by the system. Low risk of breaking something there, right? So we can probably deal with that. Then that's some very special case, again, very hard to reproduce, but I mean the real meat of the method, right? We actually go through all those cases, right? And so we cover that properly and we cover properly all our nasty loops, right? So that's actually somewhat safe, okay? Why somewhat safe is that even though I would have 100% coverage, maybe I wouldn't really test everything, right? That's why actually visually checking that we manipulate all the features is a good thing, right? You need both to have high coverage and to basically have the list of features and check, okay, that one I actually verify, that one I actually verify. Just the coverage doesn't tell the whole story. But since we've seen we are really moving the object and we have a very fine coverage, now I start to have something where my taste, at least, I know cover everything. Now the problem is that my test is still not checking anything, okay? And so that's when we get into the record replay approach I was mentioning. We're not testing anything because in something like that, that would be very, very hard to express assets which would say that's proper rendering or that's not proper rendering, right? How do I go in my automated test to say Q assert? Something, right? And that's why that's record replay. What we want to verify in our PIN test is that call after, run after run of my test, my system is always doing the same thing. So one of the approach you can do that's duplicating the system and comparing, but here we're comparing what? Right, I mean that would be screenshots, I mean that's kind of slow, you need to have physical comparison, probably not a good idea. The other possibility which is also used in system with no real business rule systems, that's to add logs inside of there, run the test, save the logs into a file before we start to refactor. That's what record phase inside of the record replay approach. So we would record that log and then for the other runs what we do is we generate a new log and then we do a diff between the two. And if the diff is empty, great, we didn't break anything, we didn't change any behavior which you perceive, right? From your log. If there's a difference then we have a problem, right? In the case of OpenGL we actually have an advantage for this kind of situation because there's this very nice tool which is named API trace. Well if you run GL application it records all the GL calls that your application is doing. So we don't have to bother figuring out where to put which line for the log strategically. We just go at wrapping that thing in scripts to do the record with API trace and then the replay and comparison with API trace as well. So that's what we will get in there. That wants to appear. Okay, so very simple scripts. Okay, so that's very simple shell scripts. So one which just does a record, right? And really is magic part of that one, right? Just call API trace, trace, honor test and that will generate a file and will save that file. Okay, we move it. We save it, that's our reference trace and then when we will do the replace because we will replay plenty of times, right? Then for the replace we basically do mostly the same thing, right? We have the API trace here. And then we compare, right? We do a diff between the dump of the reference and the dump of whatever we created and if that file is not empty then we have a problem. If that file is empty then our test actually pass. Okay, so let's start with that. So I will call the record script, okay? So I will run it. I thought you would have to like that one. Yeah, yeah. Well, I could have made it faster but I thought that would be too psychedelic for that talk. Okay, so here I generated the first trace and now I can do a replay, okay? So now I will replay. So we're generating another dump and then it will do the diff between the two. Obviously I didn't touch the system so I'm kind of expecting that thing to pass, right? If it doesn't I have a problem. Okay, so we have a pass, right? The very last line at the end, okay? Tells me it pass, no open GL code change. Now just to make sure, I generally like to do that, just to make sure, just to make sure we will actually get into GL view. And so we have all that stuff there and we randomly break something. And for some reason there's one which I'd like to break so I will break that one if I can find it. Faces, ooh, I have a problem there. I went one step too far, crap. That's annoying, okay. Okay, so excuse me people but I will have to restart that rebase project. Sorry for that. Let's start over, okay. And then next, so where am I? Okay, so back on track. So coverage, proper pin tests, record replay scripts, okay. So we are back on track. Sorry about that. So just to prove I'm not lying, you will have to see it twice again. So I'm doing another record because I had something going a bit too fast here. So I'm forced to do a record again. Otherwise you will pretend that I just lied about everything, so okay. And then my replay, and that should pass. Okay, that passes. And now I break something for real this time. So let's say I just get rid of the quads, right? Something very visible as well so that you can see it when I'm on a replay. So now I replay again. Obviously some stuff is missing now, okay. And now I've got to fail. And that's what I want, right? In this kind of situation, I created a regression. So it's not exactly the same calls than before, right? So I want that. And I have Qt Creator, which went amok for some reason. That's because it's loading the diff and I think the diff is to be. Common Qt Creator. You can do it. Okay, why it's trying to do it by itself? We can do that as well. So we have that diff, right? Okay, and so obviously there's some stuff missing. Okay, so that's the diff which got generated from my replay where I broke my test. So I can, I mean, with that, I can know when I break something and then I can look about, okay, which part, is that something where the order change or is that some parts missing like we have here? And I guess Qt Creator is buffing at it because it's for make diff, that case. I will Qt Creator and, okay, next time I will do a smaller one, a pretty. Okay, let's load that again. Okay, and so read me. Right, so now we are in a safe situation, okay? I have a test, it's covering all my system. I have the code coverage, so I verified it's really covering everything. If I break something, it tells me I broke something. So we actually like that, right? And I left my system broken. Yeah, so probably I should repair it first. Okay, and so now we are back on track. So now I have my safety net, okay? And at that point I can start to do the refactoring, okay? So that's the point where I can start to say, okay, from that I want to go there, okay? But that, I don't start before I have the record and replay thing. And so now we will start to improve our structure. So what we want is to have the layers appearing and the nodes appearing, okay? We want both, right? If I get back to that, right? We want layer appearing and node appearing and GLView creating those. And we want the view state to not store IDs anymore but to store the layer, layers instead, okay? So that's our ideal world. And so we start with that work and we'll start very probably with the nodes, right? Because what's a node? We said that the node that will contain the actual geometry and that's hidden behind some list which is consumed by the geometry creator, the geometry builder that we have. And that level of list at the first three that we have there, right? We have create list, call list, and release list. That's to manage the draw list for geometries and the current list begin list that we have done there, right? That's to manage the draw list for layers, okay? So we want to segregate both of them and for those we have that which is called, right? And if I would have the time, I would go in debug and actually show step by step but you would have to trust me on that one. And then for the geometry it called for specifying the color and adding faces or hiding polylines, okay? So what we want to do that's a refactoring which is basically extracting code in class, right? We want to create the scene node class have glView create instances of that class and all the code that we have in add polyline, add faces, set color, create list, release list, and that was call list. We want to move that, okay, in that class and just delegate to it. So again, I will speed that process a bit, okay? So that's the type of situation we want to have. Let me scroll up on that, right? So now we have our scene node appearing, right? And what we want is in create list, we create a node, okay? That's code which was here before. We have no reason to change it, so we keep it. We're just saying we're building a temporary list to one of our nodes and our node will have an ID. We store that ID, right? And we return that ID because that's what the geometry builder expects. And then in call list, we pass an ID. We memorize our list of node. We get the instance of the node for the given ID and we call on it. And then, so we are delegating again and for release list, same thing. We're delegating to the actual node. For set color, same thing. Add faces, same thing. Add polyline, same thing. If I go in GLC node, I basically find all the code that I had before, except that it moved in constructor here or in the release method or in the call method or in the set color or add faces method, okay? But that's exactly verbatim the code I had before, okay? It's just that I extracted all that code which was in the same mess than the rest of the view, okay, because my view was doing layer management, node management, and controlling the camera. Now I extracted at least the geometry management into its own class, okay? And so if I didn't screw up that, I can go in replay and I should have no change, right? Because one of the risks is I extracted but I didn't order some lines properly and then I'm doing one call before another and so I would have a different, potentially different rendering. So I want to verify that I have no change, okay? So no change. I completely change the structure there of that part at least for the geometry management and there's no change in the GL calls which are done by the system which is a good thing for us, right? Then at one point we might want to refactor, right? To refactor further. So if I get to GL view what we have left, right? We have several things. The second level of draw list for the layers, well clearly I mean that's all there still, okay? We didn't create a scene layer class yet. But when we look at it, let's look at it a bit closer because I skipped it a bit. So we have this switch on the type of layer that we start to interact with, okay? And in there we ask to the view state, give me the ID for the model. And if there's one, then I delete that list and then I create a new ID for the model which I store in the state and I actually create the list, right? With the ID we determined before. And then I have some state management around that particular layer and I store the current list, model list which is what we match there. Now if I look at the wireframe list, if wireframe ID in the state, then I delete, then I set the wireframe and I create a new one and I have some state and I memorize the current list. And for the normal map list where I get the normal map ID and then I have the delete list and then I store it and that looks fairly similar to some extent. There are some parts which vary but what forced to have everything inside of the switch is the fact that that's named normal map ID, wireframe ID and model ID, okay? And I have kind of a duplicate system, right? I have this enumon one end which allows me to specify which layer and on the other end to get to an actual layer I cannot use the enum, I just have three different methods. That's kind of stupid, right? So what we will do first to be able to start to extract some bits which are actually interesting is to say maybe, maybe if I get to the other, right? Maybe that API here is not very suitable, especially when you see the ease enabled and set enabled which actually takes the enum, right? So what we would want instead that we something like set list ID with the enum and that gives me the right ID for the layer and set list ID for the enum, right? And then we would be in a better position and so that's what we are going to do first. So we are preparing a bit the landscape for what's coming, right? So we introduce the layer ID and set layer ID which I mean there's no magic in there, right? Inside there, I find my switch. It's just that it's probably placed at the better place and being all over the place inside of GL view, right? And so if I have that and in GL view, then I mean in that switch, I just have what's really specific to all of my layers. And then before even getting in there, then I have my layer IDs management. And I also extracted the current list because that was really just a type, right? So no need to have that in the switch. So now I have a better place to start, right? Because now I can figure out that this is what I want to move in my layer, in my layer class, right? I want to extract that bit and move it in the layer. I don't have much time left. And so if it picks up, all right. So I will have a layer class and inside of that layer class, right? I will create, that's basically the gen list that we've seen before, the new list that we've seen before and we'll have the delete list that we've seen before but just put in a new class. So again, a new class. Again, we are basically delegating. And so now what's preventing us to use that new nice clear is the fact that we need to port the view state from using IDs to using pointers to layers. We don't really need the IDs in the view state anymore, right? So we are putting that to our layer. So we have objects that we create and that we delete and our view state deals only with pointers to layers now, okay? So if I get back to that picture, I created the scene node, I created the scene layer and I have this relationship between the view state and the scene layer. And now still I have some code in there, okay? Which I could move in scene layer as well, right? So I can complete the move to scene layer, okay? And my begin list really becomes almost nothing, same thing for end list, right? And set color and so on, that was also the case. And so in scene layer, I will find again, I just take the code, move it inside of the constructor, done, right? It's pretty much all there is and there's this call here, which should, I think that appears there. So in the pane GL, instead of having naked call to the draw list, we actually go through these methods that we put on layer, okay? So that's really bit by bit, we are moving stuff out of GL view and creating the layer and the nodes. And I will verify I didn't break anything. Faster, please. Stop, stop. Okay. So still no breakage, good. So now what's missing is basically we completed the scene layer. So we have all those relationships, the only one which is missing in our new ideal world that this one, right? The fact that the scene layer contains nodes. And so that's what we will add next. So on the scene layer, we will add the possibility of, we'll add the possibility of adding a node, okay? So we'll add a node and we will store that. Since we are having draw, since we're using draw list, adding a draw list inside of a draw list, that's calling it. So we are calling it now, right? Which seems a bit counter-intuitive if you're not used to that. Obviously when we report to shader and so on, that call move will move inside of the scene layer call method, which suddenly would make more sense, right? But for now, we are forced to do that to not break anything. So now I'm in a situation where I have all my relationships in place, okay? So I grew my whole new architecture inside while still using the GL, OpenGL1 still using the OpenGL1 API, okay? And since I moved all those old bits in the same classes in layers and node, now for me to move to something like shaders, buffers and so on, I know exactly where that is. I just have to port two classes and I'm done, right? I have to port the scene node to create buffers and I have to port the layers to create a shader and call the actual node. Okay, so that's what we are going to do. And for that, we will probably try to make our life easier, right? Because we have all those methods which needs to be refactored. And one other thing is that we will move at one point to buffers. And in buffers, I will have vertices, but then when I call for creating a geometry, I need all my vertices to all be about triangles or to be all about quads or to be all about points or to be, I can't really mix between the types, okay? The problem I have here with that method that, well, if I have four, then I'm creating triangles and that at one point we finish in buffer. Or if I have eight, I'm creating quads and that will probably finish in the same buffer depending on how I want to approach the thing. So if I want to avoid that problem, then I will need to change from quads to triangles, okay? And that's one of the situations where the record replay approach is more flexible than when you're using Golden Master, right? If you're using Golden Master and you duplicated your system and you compare them all the time, then you have no real way to, you have no flexibility, right? You cannot change anything at all. Why in that case, I can say, okay, I would take a small risk here because otherwise my refactoring basically stopped there and I have to do stuff without the test. So what I will do is I'm taking a small risk, I'm just changing that one from quads to triangles. I verify then that case has stopped. I'm not using the automated test, right? I verify everything manually, which I won't do here because we won't have the time, but I verify everything manually which is covered by the quads. If I'm satisfied with that, then I can make a new record and that becomes my new reference point and I can carry on, okay? So there's more flexibility built-in record replay because of that, in my opinion. So we'll talk, we'll bite the bullet, right? And take the small risk. So I have the JL triangles here, okay? And obviously, since I moved from one to the other, if I just run replay there, it will tell me, ooh, you broke something. So I don't want to run replay, obviously, because I know I will have broken something. What I want to do is to run, for instance, just a test because I don't have much time. So instead of doing it manually and I look at it and yeah, that seems fine, right? Again, I'm fast tracking a bit. In practice, I would never ever do that with a test like that, right? I would do it by hand, but that's just to make the point. So yeah, visually, that's fine. So then here is my new reference point, right? So I'm recording my new reference point and now I can carry on again with my refactoring. I don't know how much time we have left. Yeah, where am I there? Okay. So I can do that. Once I've done that, I can then start to really get into more fine-drain refactoring. Like, yeah, that's really ugly, right? And I don't want to keep that when I'm generating my buffers later on. So since I have a new reference point and I know I have triangles everywhere, I can start to refactor. Like for instance, having the GL begin, GL end outside of my switch. And then I can figure out that they all look fairly similar, right? That one, that one looks similar, that goes through a lookup table, lookup table. And that one, no lookup table, but maybe I could introduce one and then they're all similar, right? So you can get into this kind of set, you know, focus on one method, get a bit crazy about that because you can try stuff and see if that breaks or not, right? So you're very free in that regard. And since I have only a few minutes left, let me just show you how that could look, right? So we are adding more of those lookup tables and then we factor out as much as we can and we basically just have a neat loop here, right? We shrink that. We also dropped quite a bit of the arrays which were there and not really needed, right? Because we figured out, yeah, I can try to remove it and see if that still works. Still works, so carry on. So you can do this kind of work, right? Same thing here for the line strip when we will create buffers, we will have a problem, right? Because we have two line strips next to each other so the end of the line strip will become the beginning of the next one. So we need to port that to again. Record replay gives us flexibility there. We're saying, okay, I will take another small rinse and move from line strip to lines. Maybe this next one. Yeah, no, first we are verifying, yeah, one other thing you might want to do in this kind of situation, that's verifying theories, right? Because I know I will move to buffers. So I don't want to have triangles and line stripes in the same, stripes in the same buffer. So what I'm doing here is I'm verifying my theory. So to verifying my theory, I just add asserts in there. So I had, I memorized whatever primitive I'm about to create and then I verified that if something is set, right, that triangles to make sure that if I'm called once for that node on ad faces and ad polyline will never be called on that one, right? So you might want to try this kind of thing. So we have that in ad faces, we have that in ad polyline. And then I could run the test again and verify that it's not crashing but I just have a few minutes left. And then I can move to lines. So if I move to lines, now I'm basically ready to move all of that to buffers, right? And inside of layer where there's not much apart from the material and list management, okay? So no big deal, no big refactoring to do there. And so we can get into now preparing GL view to actually pass the information properly to the layer, right? Because what we have here is that we have the old way of specifying the matrices when we will port to shaders and so on, we want to set the matrices as uniforms on the shader. So what we want is to use the queue types for matrices in there and pass that to the call here, right? So I'm just creating for the model view while I'm doing exactly the same thing, right? Same translation, same rotation for the projection, same perspective, right? I'm duplicating temporality as a code and I'm passing that to my scene layer, okay? To the code to my scene layer. I don't consume it yet, but at that point, I can port layers to shaders, right? I can leave behind, I have all the information inside of that class at the right time to port from draw list to shaders. And so that's what I do here, right? I have my program and then I can bind it and I can set the uniforms with different matrices which are passed and then notice how the call that we had here, remember previously, I told you because of the draw list I had here, it moved up there, okay? And that's way more logical, right? I'm calling, so I'm calling the rendering of the layer and it delegates to the rendering of the different nodes, right? So that's much more natural way of approaching it than with the draw list. Then the other stuff we have for the program that's setting the uniform, right? For all the materials, the lights, whatever is consumed by your shader. And depending, you remember all those weird code we had inside of the switch has become, well, I'm, oh sorry about the tooltip, I'm in the model list, so I will use a Fong. Yes, I finally got to use a Fong model or I will use plain color if that's for the normal, or, okay? Obviously at that point I cannot use anymore my tests, right? I'm doing a port now, I'm not refactoring anymore. So my tests, I have to leave it behind at that very stage, but not before, right? I did all the rest safely and when I get to the port because it's a port and then I have no choice, then I'm in a better position to be able to do that. And last step, so we ported layer to shaders. So if I run it, I would just do it for fun, but if I run it again, so I think that the only step where we have something broken when we look at it, right? Because here I ported one class, the layer class, but not the nodes, right? So I mean, between two worlds right now, I still need to make the buffer so that my shader do something meaningful, right? So that's just because of that. So now I will port the nodes to buffers, so I will probably stay on that one, right? So that we see how it looks. Ah, right. Now there's one thing I forgot is that for the nodes, we have to do the same thing than we did with the layers, right? Since we need to attach our buffer to the shader, right? To set it as attributes, we have to pass the shader program to the node, right? So in the case of my layers, the protocol becomes slightly different, right? We're passing the actual program, and then I can actually port that really to buffers now. I jumped around, but you will notice that I basically have the same structure, right? It didn't change much. What changes is that now, instead of calling GL method, I basically store that into a Q vector, and then when it's released, I empty my vector inside of my buffer, and when it's called, I bind my buffer to the shader program, set the, explain to the shader program how my buffer is layouted, and then I have a GL draw raise call, okay? And at that point, I'm done with the port basically. And you will notice that the shading is actually slightly nicer than what we had before, not for the normals, but we'll just run it manually so that you can see something, right? But if I create, let's take that one, right? I mean, the look is not the same than before, right? We have a much nicer, smoother shading than what we had with the gold shading, right, in that one, and so we are basically done with the port, right? At that point, we satisfy the customer demand with the minimal amount of risk possible along the way, right? Changing the structure without breaking anything for sure, and then just having simple ports, right? Because we segregated the different levels properly, so each of those class became easier to port. If I try to port directly from within GL view with everything, there's high chances that I would break something along the way and spend a long period of time where I have black screen basically, right? I'm not seeing anything. After that stage, I will stop here just in a second, but after that stage, you basically, there's a few more cleanups you might want to do here and there because that's not relevant anymore into shader territory, but just removing a few calls here and there, so I'm not showing that. And we are out of time, so. Thank you for your attention. If you are not starving, you can ask me a question. If you're starving, feel free to grab some food. Ah, so, you're not allowed to have food yet. So, group picture first in C1, and then you can have your food. Thank you. Thank you.