Regarding the Matrix and Script-Textures

  • Here I want to make some reflections on the current script-texture system and propose improvements. I will base myself on experience with OMSI 2, as I don't own LOTUS yet, because the macros are basically the same. I also made some reflections based on Busfanat's thread about his matrix.


    A few weeks ago I tried to implement a matrix with scrolling text in OMSI 2 (this effect), so the script-texture had to be updated every frame.

    These are the main steps I perform:


    When the destination/line number changes: do all the calculations to format and position the text. For each text line save: string, starting position, font index, width, available width and if it should scroll or not.


    At each frame:

    1. create an empty memory-bitmap
    2. write the destination lines (scrolling one pixel left at each frame where necessary)
    3. blank out the area where the line number has to be written
    4. write the line number
    5. copy the memory bitmap to the texture

    Problems

    As you can easily state, this is just a modification of the standard approach used by the Krueger, and I also think that it's the most elegant way of dealing with scrolling matrix at the moment (I also saw people representing matrix with strings of points and spaces), but it has two main issues:


    Overlapping


    Due to the fact that the destination doesn't fit in the display, when the display is updated TexWriteLn has to start writing over the line number and the pre-existing destination, which leads to an overlapping. So it's necessary to "mask" the exceeding part and re-write also the static parts of the display at each frame, which in turn makes it easier to clean it up completely and re-write

    it from scratch.


    lotus-simulator.de/index.php?attachment/3443/


    Transferring the bitmap


    Once you're done writing the memory bitmap, it has to be copied to the final bitmap (which is twice the resolution) pixel-by-pixel.

    This is very time consuming and basically it's the thing which makes it very impractical to have decent performance with scrolling destinations.

    Proposed solutions

    These problems may now be addressed with some ugly workarounds (like using double-resolution fonts to write directly on the final texture), but I tried to come up with more elegant solutions,

    trying to be as little ground-braking as possible with the current system.


    New macro for writing text

    The fix for the overlapping problem would be to introduce a new macro for writing out text, which constrains text in the rectangular frame (x1, y1), (x2, fontHeight) so it does not overlap with existing text:


    Code
    1. procedure TexWriteLnFrame(
    2. self: integer;
    3. text: string;
    4. x1, y1, x2: integer;
    5. fontindex: integer;
    6. fullcolor: boolean;
    7. addSpaces: byte,
    8. cropPixels: integer
    9. );


    This macro is a modified version of TexWriteLn() with the following additions:

    1. the frame (x1, y1), (x2, fontHeight) is blanked out (nothing which was previously there should be seen afterwards)
    2. the first cropPixels pixels of the output text are cropped out (not written)
    3. writing starts at the pixel (x1,y1)
    4. writing stops when it reaches the horizontal coordinate x2


    This solves the left-overlapping problem and also makes it deadly simple to have a scrolling destination, by incrementing cropPixels at each frame.


    lotus-simulator.de/index.php?attachment/3444/


    Alternative: Passing width instead of coordinate x2 (width = x2-x1)


    Using nearest-point sampling

    The reason we're transferring pixels from the memory bitmap to the final bitmap is just to get a higher resolution texture which matches with the dots/LEDs of the matrix display, but this operation does not bring in any new information (apart from the error-pixel maybe) and should possibly be avoided at this point, as it degrades performance (unless you have to copy the memory a few pixels at a time, as the Krueger does).


    The need of a higher-res texture comes from the fact that the use of linear/bilinear texture filtering (I guess that's what you use) "smoothens" the edges of the lower-res texture, which does not match the underlying hi-res texture for the dots/LEDs. This is very clear in this image from Busfanat:


    3304-006-raster-mit-scripttextur-als-lightmap-png


    My suggestion is to do a test using the lo-res memory bitmap with nearest-point sampling, which gives out sharp edges when upscaling by a constant factor (2 in our case). Here a single pixel of the memory bitmap will be the dimension of four pixels of the matrix texture, no need for transferring.

    I couldn't try this myself, but if it works fine users may be given the ability to choose the filtering method when creating the script-texture in the Content-Tool.


    Nearest-point sampling is natively available in DirectX, see these reference docs: D3D9,D3D11.


    ____________________________


    Thanks for reading this far.

    This post is especially addressed to LOTUS developers Marcel Kuhnt and Janine , but the discussion is of course open to anyone, so feel free to comment on these features and bring in alternative solutions or other related features.


    A note for LOTUS developers:

    First of all thanks for making us part of the development of this wonderful project.

    I based my proposals on how I guess you manage Script-Textures under-the-hood, as I obviously can't see the implementation, and wrote them down as clear as I could. If you need more information or suggestions on implementation just ask me and don't be afraid to go technical because I'm a developer too.


    The last thing I want to ask you is if the operations on Script-Textures are handled synchronously or asynchronously with the script. This came to my mind when I worked with the lock/unlock statements in OMSI and saw some blinkering.

    If you use an asynchronous approach, it may also be seen as an opportunity (or at least it's something to be aware of).

    Edited once, last by fuljo: Corrected Marcel's name ().

  • Good evening,


    at first I want to thank you for your input. To be honest, I do know scrolling Displays on buses in real life but I didn't give it a try in script. This is, as you state, due to the performance problem for which I wasn't able to think of a concept against. I didn't want to move the pixels on the texture, much more I thought about moving the whole texture around the face. Like the OMSI-rollerblind-movement with fixed increments instead of a smooth animation, but in x-direction, not in y. For fixed line numbers I thought about the use of transparency and a separate texture layer. Since Marcel Kuhnt (not only the first name, that's another user) implemented the rollerblinds in a special material kind, I didn't give it a try yet.


    As for the smoothing effect, as I use a texture of a 8 times higher resolution in each direction: I quite like that effect, since in real life the lit LEDs do reflect on their turned off neighbours. Also keep in mind, I've increased the effect of the night texture on the result in the settings of the materials while keeping the "LED-texture" quite dark so you see the written text much brighter in a darker environment than in a lighter one but it still would be readable.

    with normal night map effect it would look like this:

    (the upper and the lower textures. I didn't reset the prep-material for the middle texture)


    On top of that in reality the dots of, at least LED displays, might be a bit smaller and more spread than on my script. I've seen displays where the spacing of the LEDs is two or three times as wide as one LED.


    Additionally I think this smoothening effect isn't that big of an issue, since in the simulation you won't see this texture directly, there's always at least a layer of "glass" in front which as well affects how it looks. Secondly you mostly look at the vehicle at a whole and don't get that near to the destination display. I hope you at least don't look that close when driving the vehicle ;) Although I spend a lot of time with that stuff I'm quite sure that most of the users want to look on the road and not at their destination sign.


    Never the less I really appreciate beeing not the only one having those thoughts,


    Busfanat

  • First of all thanks for making us part of the development of this wonderful project.

    Thank you very much! ;-)


    fuljo : Until I read Busfanat's post, I thought, your solution would be a very good one for that problem. And there is no reason why not implementing this new writing procedure. But I must say, that Busfanat's solution with a "roller blind" is much more better! OK, of course you would need a horizontally moving blind... ;-)


    Creating a self-made texture with "nearest point" wouldn't be complecated.

  • Good evening and thanks to both of you for your quick response.


    First I'd like to address the matter of the smooth edges of the pixels: I didn't think about it, but as Busfanat said it's not a very noticeable effect, so the thing about the nearest-point sampling is no longer needed. Thanks for pointing that out and also for the useful tips on the night-textures!


    Now let's come to the big part: using roller-blinds for scrolling.

    Since I saw how the roller-blinds were implemented in OMSI I asked myself if I could use that to fake the scrolling effect on the matrix.

    The roller-blind material in LOTUS is certainly more capable, but I think this approach is very impractical for many reasons.


    1. Two textures for each destination line

    If you embrace this approach, you need to have at least one roller blind and two separate textures for each destination line (textures 1 and 2 of the roller blind) which have to be managed in a very magical way in order to have continuity (I don't even know if I can use a trasparent script-texture on the roller blinds).


    2. Replicas for various configurations

    Then the mesh for each destination line (blind) will have to exist in two versions: a wide one where there is no line number, and another one which doesn't

    cover the line number, since I can't assign a transmap if the transparency is already used for the text.


    3. Need to use layered meshes, problems with transparency and night textures

    A good thing about Busfanat's solutions is that he uses only one mesh with the base texture set to "all off" pixels and the script-texture set as a full-color nightmap wich is multiplied by the base texture to have the "grid" effect and finally brightened to make it more visible (I managed to replicate that).

    This makes it very easy to have a full-color display, even though each pixel is constrained to a specific HUE (you don't show the texture of a on-pixel in front of the off-pixel, just brighten it).


    If I use roller blinds I won't be able to use the script-texture as a night texture, because it's static, so I'll have to set the thing up like this:

    - background mesh with off-pixels
    - front mesh with static text

    - front mesh (roller blind) with color and alpha-transparency provided by the script-texture, static night-map with "all on" pixels

    This setup doesn't give good results, as Busfanat has already pointed out, and moreover the alpha transparency doesn't seem to work (at least in the content tool) because I can't see the background mesh.


    TL; DR;

    The material for roller blinds is good for roller blinds, it's not good for a versatile destination display.

    _______________________________________________


    In the meanwhile I've been able to implement a proof-of-concept of the scrolling display, following Busfanat's guidelines and with my approach for scrolling (re-writing the script-texture).

    I must say it's really good-looking and I can see no flickering, at least in the content tool, so I would go for this solution at the moment, unless it proves to be problematic afterwards.
    It may not be the best one in terms of performance, but I think the reason why one chooses a matrix/LED display is the ability to have colors and formatting options, not constraints.


    Here are a couple demos of my work:

    1. https://www.dropbox.com/s/l1k0…11-11%2017-47-14.mp4?dl=0

    2. https://www.dropbox.com/s/akr1…11-11%2022-49-57.mp4?dl=0


    In the latter video you can see a problem: a letter is not drawn if its starting pixel is < 0. I guess this is a feature of the TexWriteLn()

    procedure to prevent anti-aesthetic half-drawn letters.


    To Marcel Kuhnt :

    In conclusion, if you agree, I propose to implement the TexWriteLnFrame procedure I mentioned in the first post (choose whatever name you think fits best). This also needs to print a letter even if it's not completely in the drawing area.

    For the nearest-neighbor (aka point sample) filter don't worry. As I used LOTUS I understood that you would have to change the format of the material files, which is of course very tedious and would bring incompatibilities.


    Thanks again for your time and patience, hope to hear you soon.


    P.S. One more bug... In the Bitmap Font import tool the keyboard shortcuts (e.g. Ctrl+S, Ctrl+O) don't work at all, at least on an Italian keyboard.


    Edit:

    This morning, while having my tea, another approach came to my mind, which still implies writing directly to the script-texture and not using roller blinds.


    Of course the thing that feels very bad about the current approach is calling TexWriteLn at each frame always with the same parameters, except for the starting position (or the number of pixels to crop). This wastes a lot of time, I think, because the font has to be re-selected and each letter must be copied, so you have a lot of searching and addressing (I haven't seen your internal structures, just making assumptions).

    Since we now have the possibility to represent colors with the ordinal type and use arrays (also dynamic ones, I assume) we can operate like this:

    1. Call width := TexGetTextLenPixel() to get the width of the entire string
    2. Get the font height (either hardcoded or with a dedicated procedure), let's say height := TexGetFontHeight (Self, FontIndex) (thanks Busfanat)
    3. Allocate a matrix (double array) of colors memory[height][width] (or swapped, to have x,y indexing)
    4. Write the text to that matrix, with a dedicated procedure, similar to TexWriteLn which has to be implemented (or have the procedure returning the matrix or a dedicated record, but sounds odd to me)
    5. At each SimStep copy the needed pixels from memory to the script-texture with a combination of TexSetColor and TexDrawPixel in a for loop

    Here's an illustration:


    Again, I don't know how much overhead there is between our scripts and LOTUS' internal structures, so you have to evaluate if hundreds of calls of small procedures are faster than a single call to a complex procedure.

    I can just tell that if you're sure that a procedure activation record gets allocated in stack each time a procedure like TexDrawPixel is called, this generates much overhead, but it always has to be compared to the complexity of the TexWriteLn procedure.

  • OK, thank you for testing, getting this information and posting them! :-)


    I added the TexWriteLnFrame function to the wishlist already, so you will get it anyway! ;-)


    Thank you for your bug report concerning the shortcuts, I will check it out.