Fundamentals of OpenBVE Development

Started by E-man-25, Apr 17, 2021, 01:42 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Understanding performance rules and guidance on handling fps yields on objects

-Key fundamental before developing anything is always having your performance rules in mind , openBVE is not unlimited , in fact , there are a fair amount of limits that you dont want to have to try risking... the following is an ordered list with scattered demanding and non-demanding Characteristics:

    *Texture Sizes and Count: textures must follow the power of 2 rule in pixel sizes (e.g 64 x 64 , 128 x 64 , 512 x 64 , 1024 x 64 , 256 x 1024) , this is due to the way BVE interprets it internally , in the words of one of the main program devs , Christopher Lees or Chris Lees , "The biggest killer by far is number and size of textures, and there's not a lot that can be done easily engine-side for this problem.
The slowest OpenGL / Direct3D operation is changing texture by a massive margin. This is exponential based upon the size of the texture too, as stuff has to be passed over the memory bus to the appropriate shaders etc.
That's why essentially every commercial game engine uses texture atlasing heavily:
Far too much stuff also forgets the power of 2 rule, which is another total killer.
The next (possible) step I suppose would be for the engine to implement an internal texture atlas, but that's going to be a massive amount of work, and utterly pointless with some of the 2048px plus sized textures in the most egregious stuff.
Fundamentally, some of the content authors need to take a long hard look at the size and number of textures they're using, relative to the actual size they appear on a player's monitor."

-Pro tip: Try to create textures of power of 2 that wrap around objects by using texture coordinates appropriately. This will reduce or eliminate the need to change the texture when rendering, which is expensive.

    *Geometry: Avoid overly complex geometry where the end results are hardly noticeable. Avoid using Cube and Cylinder commands (B3D/CSV) if parts of the cube/cylinder will not be visible. Do not create cylinder caps unless they are likely to be visible. Avoid using external modelling programs unless absolutely necessary, it may seem as the faces count are good in the raw count but certain exporting methods may be a lethal mistake. The following is a considerable judgement system you can guide yourself with:

Face Count | Demand | Judgement
~ 0 - 50 | Nearly Unaffected | Optimal count for a single component of any complexity
~ 50 - 500 | Very Low | Optimal count for a basic group of components corresponding to an area
~ 500 - 1000| Low | Optimal count for a a group of components corresponding to an area with more complexity
~ 1000 - 5000 | Moderate | Optimal total count for a complex exterior model of 1 train car
~ 5000 - 7000 | High | Optimal total count for a complex fully developed  train car and/or station
~ 7000 - 12k | Very High| Not an optimal count for 1 car and / or over detailed against BVE's limits
~ 12k+ | Insane | Not an optimal count to aim for

-Recommendation: A face count fitting under Moderate demand should offer just enough user experience and features without going too high on the normals

-Pro tip: To get smoothly curved objects, use custom normals with fewer faces instead of no custom normals with many faces.
    *Face vs Face2: Just ... use face , period

    *Transparencies: For best visual quality, every transparency has its overhead. Avoid using transparency at all if there are alternatives of similar implementational and geometric complexity.
Use color-key transparency whenever possible. Avoid using alpha channels in textures or using Color (B3D) or SetColor (CSV) commands with an alpha setting at all costs.
When it is necessary to use alpha, bear in mind that depth sorting will be used to determine the rendering order. There is no perfect real-time depth sorting algorithm, thus artifacts cannot be completely avoided.
When using alpha, keep polygons parallel to each other, which will always render correctly. The worst case scenario is perpendicular faces. When such faces overlap on-screen, the rendering order might be erratic. Use perpendicular alpha faces only if they are unlikely to overlap on-screen.

    *Animated Functions: This ties up to all of the above , the execution of an idea regarding functionality should be planned out carefully , thoughtfully and smart. Not every method is optimal. There are certain kinds of animation which are less expensive, and others which are more. Also, the underlying object plays a significant role. If you want to design your animated objects with as best performance as possible for future releases of openBVE, take a look at the following performance table:(Source: Official OpenBVE Documentation)

Animation   Object                           Performance
State changes   Has only opaque faces           Good
State changes   Has partially transparent faces   Moderate
Translation   Has only opaque faces           Good
Translation   Has partially transparent faces   Moderate
Rotation   Has only opaque faces           Good
Rotation   Has partially transparent faces   Bad
Texture shifts   Has only opaque faces           Bad
Texture shifts   Has partially transparent faces   Bad

Performance is generally better if the result of a function only infrequently changes. So, even if you set the RefreshRate parameter to zero, performance is generally better if the outcome of your formula is constant over longer periods of time. On the other hand, if it changes every frame, performance is generally worse.

Generally, you should avoid using animation with partially transparent faces and stick to opaque faces when possible. Also, try to avoid texture shifts, and consider using state changes or translation where possible.

-Pro tips:(Source: Official OpenBVE Documentation for animated functions)
~Generally speaking, try to keep the complexity of functions as low as possible. This is not the most critical aspect, though, as most of the performance impact will result from applying the results of a function, e.g. rotating the object, and not evaluating the function.

~Use the RefreshRate parameter when possible to optimize performance. Usually, you can use this parameter when you don't need a smooth animation, or when you deliberately want the functions to only update in intervals.

~Don't use functions which always evaluate to the same constant. For example, don't use RotateXFunction = 3.14159, but rotate the underlying CSV/B3D/X object directly.

~State changes are very cheap as long as the state doesn't actually change in between two executions of the StateFunction. If a change occurs, this is a relatively expensive operation, though.

~Try to optimize out if conditions. Especially try to avoid nested if functions. Often, there is an elegant mathematical solution.

~Certain functions, e.g. Exp, Sin, Cos, etc., are relatively expensive. Use them only if absolutely necessary for an effect. Don't include unnecessary operations. For example, the result of StateFunction is automatically rounded toward the nearest integer, so don't apply an additional explicit Round.

~When working with car objects, bear in mind that some variables have an optional car index. You should use this index if you want to query the state of a particular car (that is, not necessarily the one the object is attached to). If, however, you just want to query the value of the particular car the object is attached to, use the variable without the index. For scenery objects, you should not generally use car indices as you can't be sure how many cars the queried train has.

Appropiate Texture Work and Object Specifications

-When Working on textures , apart from keeping in mind the performance rules , textures should be able to imitate correct lighting and shading fitting to the surroundings (e.g If the object is located near a light source , create appropiate illuminations on the textures and/or if the object recieves only partial light , cast shadows on the textures, others also apply like reflections , texture quality and more... This process has proper terminology called texture baking or "render to texture" : absence/56563#:~:text=Texture%20Baking%20is%20any%20process%20aimed%20at%20generating,associated%20with%20the%20information%20describing%20the%203D%20model.

-When undergoing object creations , i personally treat this as a must.. you should consider using EmissiveColor 255,255,255 , this is due to the various advantages this offers:
   1: Seamless Smooth Blending between 2 or more components
   2: Uses 100% of the texture's illumination at all times (hence the reason why it makes the seamless blend)
   3: You can specify your nighttime textures in the 'load' parameter,after specifying your default texture with 'load texturename.extension' put a comma and define your nighttime texture to use for the component right after.

-Avoid using post-transformation commands like 'translate , rotate , translateALL , rotateALL , scaleALL, etc...' because in the long run when making additional components you would have to mathematically calculate the actually coordinates of a vertex or placement of a component.


-The more you keep your assets organized , the better the efficiency of your development process and post-development process, consider structurizing your folders and internal code as sectioned as possible , in the internal code use comments appropiately (thats the reason they exist) , if you want to add on to your internal code dont just write it in a random place or at the very bottom, locate your sections and place them strategically.

-Always plan your start from beginning to end , keep backup plans if the original plan doesnt work out, but NEVER work on the fly , an appropiate planification should have milestones , goals , to-dos, procedure steps , features you wish to add , visions for the future , crew members with respective roles (if applicable) , never estimate 'time for release' if you're developing for the public, instead take all the time you need to work and it is up to your planifications if you want to leave your package as-is or to have separate releases for different features or add-ons.