OSGL - EditableImage graphics library

function WindowPrivate:Render()
	local final = table.create(#self.pixels, 0) :: { number }
	local n = math.round(#final / 2)

	for i, color in ipairs(self.pixels) do
		if i % n == 0 then
			task.wait()
		end

		local i = (i - 1) * 4
		final[i + 1] = bit32.rshift(color, 24) / 255
		final[i + 2] = bit32.band(bit32.rshift(color, 16), 0xFF) / 255
		final[i + 3] = bit32.band(bit32.rshift(color, 8), 0xFF) / 255
		final[i + 4] = bit32.band(color, 0xFF) / 255
	end

	(self.renderer :: EditableImage):WritePixels(Vector2.zero, self.size, final)
end

@saaawdust whatā€™s the purpose of having to allocate a new framebuffer and having to do bit32 per pixel, per frame? why cant you not pass self.pixels to WritePixels?

For OSGL to use less memory, that had to be done (more said later).

is this the reason? maybe this is why buffers should be allowed in WritePixels, iā€™d imagine there would be less overhead there than to allocate, iterate and bit32

The whole thing is very unoptimized, also whatā€™s the point of this:

	local final = table.create(#self.pixels, 0) :: { number }
	local n = math.round(#final / 2)

that can be turned into:

local final = table.create(#self.pixels * 4, 0) :: { number }

The fact this came with the promise that it would perform faster than CanvasDraw, but turned out to be a false statement and very unoptimized.

Memory also almost never matters at the cost of CPU

If it supported buffers it would indeed be way better, but thatā€™s for Roblox. We donā€™t use buffers either way. And yes, thatā€™s why editing pixels takes more time.

The 2 code snippets are completely irrelevant. That shows how desperate you are to prove OSGL is bad. Why are you answering questions? Itā€™s for the developers of OSGL, not you.

1 Like

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.