OSGL - EditableImage graphics library

This argument has been settled in discord, please do not continue it!

For anyone wondering, yes, as msix29 mentioned, OSGL takes longer to draw a pixel, (around 30 - 40% slower) than CD. OSGL however does this for lower-memory-usage, and slightly higher overall FPS. CD, on the other hand, can draw much faster than OSGL, yet you will get a lower FPS, and higher memory usage. OSGL has less memory usage since it uses a width*height length array instead of a width*height*4 due to the way colors are stored (Read more about this here) although the final array is computed in the end.

Memory does matter as much as the CPU. If you want to draw faster, use CD. If you want better overall FPS, then use OSGL. CD is good if you want just to render 1 specific thing on the screen. OSGL is good if you also want other things on the screen at the same time. Use whichever one suits your project better, which in this case, since you want the speed, is CD.

If you want to improve something, or believe something could be faster, remember the project is Open-Source on Github here. Just because you believe one thing is more important than the other, doesn’t mean everyone has to agree with you, lol.

3 Likes

Hey, Vera.

Apologies for not replying to your message earlier, I actually never saw this one. We can’t pass self.pixels as we use a custom memory-efficient format other than the one EditableImages use. We store each color in one u32 (each channel is a u8), while EditableImages expect 4 different numbers for each channel, so we have to unpack the u8s from our u32 to get them.

1 Like

well now you can, with the new WritePixelsBuffer function

1 Like

Whenever it’s used, it says EditableImage:WritePixelsBuffer is not yet enabled!. Until then, it’s of no use.

oh my bad I forgot I enabled fflags for it. just to give you an idea, it’s way faster than using tables and it’s a lot faster to clear too since you can use buffer.fill to clear it instead of manually iterating.

This is an incredibly interesting library! I’ve only casually perused the code, though I’d like to ask – have you tried benchmarking the code to use Vector3’s instead of numbers? Unlike Vector2’s, Vector3’s are native types, and as such have faster operations. You seem to be performing a lot of operations with coordinate numbers & number pairs, so I’d recommend switching em out to Vector3’s to see if there’s any performance gains to be made :wink:

from Native Luau Vector3 Beta:

2 Likes

Well, for the mean time, I would leave the code as it is, since buffers are behind a FFlag they’re probably unstable. I’ll still be comparing performances (away from OSGL) though.

1 Like

The suggestion is quite nice, we may consider implementing it.

In the meantime, it’s likely that the performance will actually be worse. This is because metatables are most likely used, which could potentially slow down the process compared to plain numbers. Generally speaking, there’s no operation we perform that the Vector3 would be superior at (it’s primarily multiplication and division, lol), as there’s a third number that will also have to undergo that operation. Furthermore, it could be quite confusing to use it in our API.

1 Like

Are you referencing the data structures used within this module? Vector3’s are a native type, much like numbers, booleans and the like.

Does the use of a native type necessarily imply that it would not utilize metatables? I don’t think so. As previously stated, it is “most likely used”, not 100% used. I have also mentioned other factors that would influence this.

I am confused as to the point you are trying to make. For 1-dimensional scalar operations it does not make much sense to use vectors, however given that this module works with multiple dimensions, vector operations are more optimal and efficient over scalar operations. The DevForum post I linked, which was written by a Roblox engineer, states this very same point.

1 Like

My point is that there’s an extra number that we don’t use. We use 2D values, a Vector3 has 3, meaning there’s one extra number that all operations happen to, and we don’t even use it! I’m unsure of where exactly do you want to use them, but depending on that, the 1 operation on the 1 extra number can turn into hundreds.

1 Like

Vector operations are a single instruction, while two separate number operations need to be 2 separate instructions. The overhead from running an instruction is bigger than the actual math operation so vectors will actually be faster than numbers here, from what I tested vector operations are around 30% faster than 2 number operations.

You can for example use a Vector3 as a replacement to what would have been a Vector2 and make your functions accept them as an argument, instead of asking 2 number arguments for each coordinate.

Also vector operations being native means their math operations don’t go through metatables but have a fast-path internally, not too sure about their methods though.

Hmm. Pretty interesting. They are not a single instruction, though. Yes you may only be calling one function instead of 2 math operations, but the function would be doing atleast 3 math operations; more than 2 plain numbers. As to the overhead of the instructions… I’m no expert on that so I wouldn’t be commenting. I have no idea how your testing code showed it 30% faster, but I would love to see it. And I’ll try it myself.

Pretty confusing, isn’t it?? The whole library is 2D.

No, no. I didn’t mean metatables for in their operations, but rather the overhead of actually creating the metatables (in .new). I doubt I’ll be using any of the methods, anyways.

From my own tests:

Testing for: two numbers
Took a total of 0.0865918950876221ms for 1000000 iterations.
With an average of 8.65918950876221e-08 ms/iteration.

Testing for: Vector3
Took a total of 0.33612719230586663ms for 1000000 iterations.
With an average of 3.361271923058666e-07 ms/iteration.
Testing for: two numbers
Took a total of 10.434128615015652ms for 100000000 iterations.
With an average of 1.0434128615015652e-07 ms/iteration.

Testing for: Vector3
Took a total of 70.19527274835855ms for 100000000 iterations.
With an average of 7.019527274835855e-07 ms/iteration.

I couldn’t find the beta, but looking at the date (2021), I think it’s been out of beta.

These math operations take sub nanoseconds, it’s executing the instruction that tends to take a little longer. From what I tested, a vector math operation takes 3.1 nanoseconds and two number math operations take a little above 4 nanoseconds.

Vector creation doesn’t set any metatables by itself, that’s what makes it a native object. Even strings have metatables but creating them doesn’t “set” the metatable again, they already have the metatable on them.

What’s your benchmark code? If you’re creating the vectors inside of the for loop then it isn’t a fair comparison, since you don’t normally need to create new vectors for every operation.

I am. And that is the fair way. Assuming the app draws according to user input, the Vector3 will be created in the loop. A fair test would include everything, not just the operations. Tests with all Vector3 constructions outside of the timing function:

Testing for: two numbers
Took a total of 12.061226765683386ms for 100000000 iterations.
With an average of 1.2061226765683385e-07 ms/iteration.

Testing for: Vector3
Took a total of 25.689543790533207ms for 100000000 iterations.
With an average of 2.5689543790533205e-07 ms/iteration.

Still slower! I would really love to see how your tests showed it faster.

What does native has to do with metatables?? I’m not sure if it means it’s written in C++ or luau, but if it’s in C++, there’s an overhead from calling the C++ functions, and if luau, that doesn’t mean it’s not using metatables, It may even make it more likely that it uses metatables.

The app draws according to the user input which creates a single vector, from there you calculate everything using that one vector that was created. That’s different from creating a new vector for every single operation which is extremely unrealistic.

Try adding “–!optimize 2” at the top of your script, which is what the game normally runs with. Studio has optimization level 1 by default.

Metatables being applied whenever an object is created doesn’t work the same with natively builtin types as it does with actual tables which require you to set their metatable every time you create a new table.

It creates a single vector for every user input. But sure.

For the sake of testing faster, I’ve decreased number of iterations from 100 million to 10 million (I’m not willing to wait a whole minute for a test .-.).

Testing for: two numbers
Took a total of 0.9112083129584789ms for 10000000 iterations.
With an average of 9.112083129584789e-08 ms/iteration.

Testing for: Vector3
Took a total of 3.0777601695153862ms for 10000000 iterations.
With an average of 3.077760169515386e-07 ms/iteration.

Still slower. Though, not as slow as before. I’ve ran all these through the command bar. Just to be sure that in-game it wouldn’t be any different, I ran the game (and ofc added a wait before to exclude Studio loading time). Here are the results:

Testing for: two numbers
Took a total of 0.8625850056996569ms for 10000000 iterations.
With an average of 8.62585005699657e-08 ms/iteration.

Testing for: Vector3
Took a total of 3.0970254158601165ms for 10000000 iterations.
With an average of 3.097025415860116e-07 ms/iteration.

Still slower.

I doubt you and I would know how they actually work. since we don’t have access to their source code, so we better not make assumptions about whether or not they use metatables.