OSGL - EditableImage graphics library

in Luau you should be able to get atleast 10 fps without needing native codegen, the problem is that math.random is slow to call compared to other functions. same thing applies with the Random object Roblox has.
if you for example calculate the UV coordinate by dividing the current x,y by the image size and use that for the RGB you can get about 30 fps with native code generation and 15 fps without.

in this example I use a window resolution of 1024x1024 and this for the colors:
R = UV.X
G = UV.Y
B = UV.X * UV.Y

With --!native

Without --!native

1 Like

I was able to get a 3D engine with some meshes of about 3000 vertices working in real-time, haven’t gotten to parallelize anything here and the code wasn’t really my proudest work but this is just for reference.

1 Like

the overhead of accessing the array is still going to slow things down, even when using static values

local size = 1024 * 1024 * 4
local screen = table.create(size,0)
while true do
	-- start timing the loop here with os.clock()
	local t = os.clock()
	for i=1,size,4 do

		screen[i] = 2
		screen[i + 1] = 5
		screen[i + 2] = 7
		-- alpha channel omitted for free performance boost
	end
	print(os.clock()-t)
	-- end timing the loop here ~> 0.03ish to 0.04 second per iteration
	task.wait()
end

my point stands, this language is woefully slow for graphics done in software

comparing to other languages, yes it’s slower by a lot, but it is still fast enough for real-time graphics at decent resolutions!

Here’s a 3D fully textured raycaster engine I made around a year ago or so using pure luau running around 80 to 110 FPS at 240x240
(no multi-threading)


Note there is also a bit of overdraw happening with the floors/ceiling and wall rendering

6 Likes

not sure what that has to do with what I said, all I’m saying is that you can still make cool things with it. You aren’t even supposed to be using the CPU for this kind of stuff so it makes sense that it might be kind of slow. but being able to get 30 fps with a 1024x1024 window without parallelization means you should easily be able to get around 120 fps with parallelization too.

1 Like

Version 1.2b is out!

This update contains an entire rewrite of the entire library yielding MUCH better performance!

Changelog:

  • Rewrite of API (more performant, and easier to understand)
  • New docs & API site
  • Removed “Fonts and text”
  • Removed the “argument buffer”

What really makes this better than the previous version of OSGL?

v1.2b is much faster than v1.1b. In certain contexts, OSGL surpasses CanvasDraw FPS-Wise by over 7fps.

More on CanvasDraw & OSGL

As mentioned, in certain contexts, OSGL is faster than CanvasDraw. Using OSGL, if you render nothing to the screen, CanvasDraw will be faster. The latency that CanvasDraw takes to draw is the same that OSGL yields, leading to OSGL having higher FPS on average (again, in some cases. The latency of CanvasDraw varies, and sometimes it’s faster!)

The exact tests that were used to measure this

The code that was used previously in this thread was reused for measurement - each time yielding a different number of frames, the difference between 1, 2 and 3 task.wait s will only affect OSGL, difference in CD is purely random. Out of these 3 results, “1” is currently being used in OSGL. Here are our results:
1 task.wait

Test 1: Wait every 200 * 1024 pixels
OSGL: 22 FPS
CD: 21 FPS

Test 2: Wait after every Render call
OSGL: 8.9 FPS
CD: 9 FPS

Test 3: No waits at all
OSGL: 6 FPS
CD: 5.1 FPS

2 task.waits

Test 1: Wait every 200 * 1024 pixels
OSGL: 25 FPS
CD: 24 FPS

Test 2: Wait after every Render call
OSGL: 11 FPS
CD: 11 FPS

Test 3: No waits at all
OSGL: 9 FPS
CD: 5.5 FPS

3 task.waits

Test 1: Wait every 200 * 1024 pixels
OSGL: 35 FPS
CD: 30 FPS

Test 2: Wait after every Render call
OSGL: 27 FPS
CD: 10.5 FPS

Test 3: No waits at all
OSGL: 25 FPS
CD: 5.5 FPS

You can contribute to the project here!

2 Likes

This is the greatest update of all time.

1 Like

Bug fix #1

  • Fixed a bug where the converter executable wouldn’t render some pixels

The exe has been updated and can be found here!

at this point, I’m feeling to create a os and put osgl32.dll (this module) in the kernel folder, and someone made a java interpreter.

Java (JBlox env) + OpenGl (OSGL module + tweaks) = minecraft?

edit : is OSGL like OpenGL? haven’t seen docs but ima bookmark for future use

1 Like

OSGL is sort of like OpenGL. OSGLs API is actually very similar to SFML which is a CPP library.

1 Like

how does the converter work ?
is there a certain way to set it up, because it wont open

It’s a Command-Line-Tool. Open it via terminal and you can use the -h flag for help

All the benchmarks have been done inaccurately/incorrectly for this. Although, its true that OSGL has certain functions that perform faster than CanvasDraw, but overall its worse than CanvasDraw. However, we can all agree OSGL is still new, and apparently there’s a lot of improvements coming.

The benchmarks that were done above were pretty much “same code”, but they hadn’t considered that CanvasDraw and OSGL have different designs in how the API, rendering, and yielding work.
After taking into consideration their designs, I had done separate tests on the same concepts for each of them, and CanvasDraw performed way better than OSGL did, at least that’s for now.

Here the result of the accurate benchmark each done according to their respective design:
~ OSGL:

  23:22:06.265  Wait Time was: 0.5  -  Client - Client:76
  23:22:06.266  Updates Count: 10  -  Client - Client:77
  23:22:06.266  ALL Updates Time Taken: 11.415006999999605  -  Client - Client:78
  23:22:06.266  Average time taken at each generation: 0.6331923000001097  -  Client - Client:88
  23:22:06.353  Average FPS 1: 1.6112302991284904  -  Client - Client:109
  23:22:06.353  Average FPS 2: 59.40013426167859  -  Client - Client:110
  23:22:06.354  Average FPS 3: 59.40013425523703  -  Client - Client:111

~ CanvasDraw:

  23:23:18.020  Wait Time was: 0.5  -  Client - LocalScript:75
  23:23:18.020  Updates Count: 10  -  Client - LocalScript:76
  23:23:18.020  ALL Updates Time Taken: 8.308398600000146  -  Client - LocalScript:77
  23:23:18.020  Average time taken at each generation: 0.32490845000002083  -  Client - LocalScript:87
  23:23:18.109  Average FPS 1: 58.01233495249014  -  Client - LocalScript:108
  23:23:18.109  Average FPS 2: 58.01233495249014  -  Client - LocalScript:109
  23:23:18.109  Average FPS 3: 58.01233494912888  -  Client - LocalScript:110

Regardless of this, OSGL still has a lot of potential, and i’m pretty sure it’s going to be great! But I’d say it’s still unfinished atm, and should be used only for testing purposes.

This is the codes for each [each done at 1024x1024 Canvas, and 500x500 Frame Size]:

  1. CanvasDraw Code for the benchmark:
-- Script

local UserInputService = game:GetService("UserInputService")
local finished = false

UserInputService.WindowFocusReleased:Connect(function()
	if not finished then
		print("WHY WINDOW RELEASED? THIS WILL MAKE IT INACCURATE")
	end
end)

local Gui = script.Parent
local Frame = Gui.Frame

local CanvasDraw = require(Gui.CanvasDraw)
local rs = game:GetService("RunService")

local Canvas = CanvasDraw.new(Frame, Vector2.new(1024, 1024), Color3.new(0, 0, 0))
Canvas.AutoRender = false

wait(5)

-- FPS Counter

local RunService = game:GetService("RunService")
local fps_table = {}
local fps_table2 = {}
local fps_table3 = {}
local curfps = 0

RunService.RenderStepped:Connect(function(frametime)
	curfps = 1/frametime
end)

-- Main

local c = 0

local arr = {}

local updates = 0

local waitTime = 0.5

local tt = os.clock()
while wait(waitTime) do
	local t1 = os.clock()

	for x = 1, 1024 do
		for y = 1, 1024 do
			Canvas:SetRGB(x, y, math.random(0, 1), math.random(0, 1), math.random(0, 1))
			fps_table3[#fps_table3 + 1] = curfps
		end
	end
	
	fps_table2[#fps_table2 + 1] = curfps
	
	Canvas:Render()
	
	local t = os.clock() - t1
	fps_table[#fps_table + 1] = curfps

	arr[#arr + 1] = t

	c += 1
	updates += 1

	if c >= 10 then
		break
	end
end

local timeTakenALL = os.clock() - tt
finished = true
print("Wait Time was: " .. waitTime)
print("Updates Count: " .. updates)
print("ALL Updates Time Taken: " .. timeTakenALL)

local med = 0
-- Get average of the 10
for i = 1, #arr do
	med += arr[i]
end

local avg = med / #arr

print("Average time taken at each generation: " .. avg)

local med_fps = 0
for i = 1, #fps_table do
	med_fps += fps_table[i]
end

local med_fps2 = 0
for i = 1, #fps_table2 do
	med_fps2 += fps_table2[i]
end

local med_fps3 = 0
for i = 1, #fps_table3 do
	med_fps3 += fps_table3[i]
end

local avg_fps = med_fps / #fps_table
local avg_fps2 = med_fps2 / #fps_table2
local avg_fps3 = med_fps3 / #fps_table3

print("Average FPS 1: " .. avg_fps)
print("Average FPS 2: " .. avg_fps2)
print("Average FPS 3: " .. avg_fps3)
  1. OSGL Code for the benchmark:
-- Script

local UserInputService = game:GetService("UserInputService")
local finished = false

UserInputService.WindowFocusReleased:Connect(function()
	if not finished then
		print("WHY WINDOW RELEASED? THIS WILL MAKE IT INACCURATE")
	end
end)

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local OSGL = require(ReplicatedStorage.Packages.OSGL)
local window = OSGL.Window
local color = OSGL.color
local draw = OSGL.draw

local myWindow = window.new(Players.LocalPlayer.PlayerGui:WaitForChild("Screen").Image, { sizeX = 1024, sizeY = 1024 })

wait(5)

-- FPS Counter

local RunService = game:GetService("RunService")
local fps_table = {}
local fps_table2 = {}
local fps_table3 = {}
local curfps = 0

RunService.RenderStepped:Connect(function(frametime)
	curfps = 1/frametime
end)

-- Main

local c = 0

local arr = {}

local updates = 0

local waitTime = 0.5

local tt = os.clock()
while wait(waitTime) do
	local t1 = os.clock()
	
	for y = 0, 1023 do
		for x = 0, 1023 do
			draw.pixel(myWindow, x, y, color.new(math.random(0, 255), math.random(0, 255), math.random(0, 255), 255))
			fps_table3[#fps_table3 + 1] = curfps
		end
	end
	
	fps_table2[#fps_table2 + 1] = curfps

	myWindow:Render()
	
	local t = os.clock() - t1
	fps_table[#fps_table + 1] = curfps

	arr[#arr + 1] = t

	c += 1
	updates += 1

	if c >= 10 then
		break
	end
end

local timeTakenALL = os.clock() - tt
finished = true
print("Wait Time was: " .. waitTime)
print("Updates Count: " .. updates)
print("ALL Updates Time Taken: " .. timeTakenALL)

local med = 0
-- Get average of the 10
for i = 1, #arr do
	med += arr[i]
end

local avg = med / #arr

print("Average time taken at each generation: " .. avg)

local med_fps = 0
for i = 1, #fps_table do
	med_fps += fps_table[i]
end

local med_fps2 = 0
for i = 1, #fps_table2 do
	med_fps2 += fps_table2[i]
end

local med_fps3 = 0
for i = 1, #fps_table3 do
	med_fps3 += fps_table3[i]
end

local avg_fps = med_fps / #fps_table
local avg_fps2 = med_fps2 / #fps_table2
local avg_fps3 = med_fps3 / #fps_table3

print("Average FPS 1: " .. avg_fps)
print("Average FPS 2: " .. avg_fps2)
print("Average FPS 3: " .. avg_fps3)

Wouldn’t hurt to also add Average FPS 4 before the drawing pixel, and measuring the average of how long each pixel would take to get drawn 1-4 like the FPS, but, it’s already clear, so not needed.

This is solely for the purpose of improvements, and feedback. I have nothing but respect for these guys that are working on this, and giving it away for free.

It would be great if @Ethanthegrand14 could also confirm this as I’m not the creator of CanvasDraw to have all the knowledge in it.

4 Likes

Ranu, you have texted me, and Saw, on Discord repeatedly for these same tests, where I explained over and over that adding waiting outside of the actual libraries defies the tests. The tests should have no outside factors, and you adding 0.5 wait time is one of those factors. You, yourself, have tried without the factors and saw that OSGL is faster. If you’re so desperate to prove CD is better for your project, feel free to use it and stop you’re nonsense arguments. No one forced you to use OSGL. To prove my point, here are the result, from your “tests”, with no wait times.



Now that it’s cleared. The test I used was the one originally provided by Ethan, which he originally used to prove CD was faster (Check reply 7).

Both libraries get the wait time, so the test is not unfair. In fact, it most actually be more representative of the performance we might expect: we rarely will render something right as the game starts.

Would we respond to a music reviewer’s criticism of a song with “just don’t listen to it”?

No, actually. I’ve explained to Ranu multiple times that OSGL has built-in time control, which optimizes its performance under specific conditions. By adding extra wait times, he is intentionally making OSGL run slower, despite my repeated clarifications. If you’re testing the raw speed of two algorithms, would you deliberately add wait times in the middle? That approach skews the results and doesn’t accurately reflect their real-world performance.

If you don’t like a song, would you keep listening to it? No! Similarly, if the testing methodology doesn’t accurately reflect real-world performance, the results are not meaningful.

No, actually. I’ve explained to Ranu multiple times, OSGL has built-in time control. Adding extra waits is him purposefully making it run slower, even after I made that as clear as it can be to him.

There is no extra wait, if you want completely comment the – while wait, and it would still be the same. I’m not sure if you have basic knowledge of Lua Scripting at this point.
The benchmarks I had done were the most realistic and accurate, more than any of the other ones.

  1. ~ OSGL:
    With Wait 0.5 and 10 updates:
  Wait Time was: 0.5
  Updates Count: 10
  ALL Updates Time Taken: 14.113941399999021
  Average time taken at each generation [After Render]: 0.9060726300005626
  Average time taken at each generation [Before Render]: 0.8271913499993389
  Average time taken for each pixel: 0.000000601
  Average FPS 1: 1.1470635935222968
  Average FPS 2: 60.51725532939746
  Average FPS 3: 60.51725532311502
  Average FPS 4: 60.51725532311502
  1. ~ OSGL:
    Without any wait and one call:
  Wait Time was: NaN
  Updates Count: 1
  ALL Updates Time Taken: 0.8866078999999445
  Average time taken at each generation [After Render]: 0.8866048999989289
  Average time taken at each generation [Before Render]: 0.8073473999975249
  Average time taken for each pixel: 0.000000594
  Average FPS 1: 1.1553775082153535
  Average FPS 2: 0
  Average FPS 3: 0
  Average FPS 4: 0
  1. ~ OSGL:
    Without any wait at all and 10 updates:
  Wait Time was: NaN
  Updates Count: 10
  ALL Updates Time Taken: 9.321586099998967
  Average time taken at each generation [After Render]: 0.9321564200006833
  Average time taken at each generation [Before Render]: 0.8385613099999318
  Average time taken for each pixel: 0.000000601
  Average FPS 1: 21.11482694379857
  Average FPS 2: 17.8423187153016
  Average FPS 3: 17.842318713860557
  Average FPS 4: 17.842318713860557
  • COMPARED TO CANVASDRAW
  1. ~ CanvasDraw:
    With Wait 0.5 and 10 updates:
  12:26:23.253  Wait Time was: 0.5  -  Client - LocalScript:85
  12:26:23.253  Updates Count: 10  -  Client - LocalScript:86
  12:26:23.253  ALL Updates Time Taken: 10.776391399995191  -  Client - LocalScript:87
  12:26:23.253  Average time taken at each generation [After Render]: 0.5685982899994997  -  Client - LocalScript:104
  12:26:23.253  Average time taken at each generation [Before Render]: 0.5611295300004713  -  Client - LocalScript:105
  12:26:23.340  Average time taken for each pixel: 0.000000351  -  Client - LocalScript:114
  12:26:23.521  Average FPS 1: 59.18413849193615  -  Client - LocalScript:141
  12:26:23.521  Average FPS 2: 59.18413849193615  -  Client - LocalScript:142
  12:26:23.521  Average FPS 3: 59.184138491733414  -  Client - LocalScript:143
  12:26:23.521  Average FPS 4: 59.184138491733414  -  Client - LocalScript:144
  1. ~ CanvasDraw:
    Without any wait and one call:
  12:35:45.820  Wait Time was: NaN  -  Client - LocalScript:85
  12:35:45.820  Updates Count: 1  -  Client - LocalScript:86
  12:35:45.820  ALL Updates Time Taken: 0.5511686000027112  -  Client - LocalScript:87
  12:35:45.820  Average time taken at each generation [After Render]: 0.5511671000058413  -  Client - LocalScript:104
  12:35:45.820  Average time taken at each generation [Before Render]: 0.5439007000022684  -  Client - LocalScript:105
  12:35:45.830  Average time taken for each pixel: 0.000000349  -  Client - LocalScript:114
  12:35:45.849  Average FPS 1: 0  -  Client - LocalScript:141
  12:35:45.849  Average FPS 2: 0  -  Client - LocalScript:142
  12:35:45.849  Average FPS 3: 0  -  Client - LocalScript:143
  12:35:45.849  Average FPS 4: 0  -  Client - LocalScript:144
  1. ~ CanvasDraw:
    Without any wait at all and 10 updates:
  12:40:31.055  Wait Time was: NaN  -  Client - LocalScript:85
  12:40:31.055  Updates Count: 10  -  Client - LocalScript:86
  12:40:31.055  ALL Updates Time Taken: 5.879683999999543  -  Client - LocalScript:87
  12:40:31.055  Average time taken at each generation [After Render]: 0.5879666699991504  -  Client - LocalScript:104
  12:40:31.055  Average time taken at each generation [Before Render]: 0.5798949299991364  -  Client - LocalScript:105
  12:40:31.140  Average time taken for each pixel: 0.000000352  -  Client - LocalScript:114
  12:40:31.343  Average FPS 1: 0  -  Client - LocalScript:141
  12:40:31.343  Average FPS 2: 0  -  Client - LocalScript:142
  12:40:31.343  Average FPS 3: 0  -  Client - LocalScript:143
  12:40:31.343  Average FPS 4: 0  -  Client - LocalScript:144

As you can see above,
In all the 3 tests CanvasDraw overall performed way better than OSGL did.
I don’t see a single thing in it that OSGL did better, all it seems to do is give you about 5-10% more FPS at the cost of total time taken by about 50% longer.
Anyone would easily be able to add their own waiting for the CanvasDraw, and it would still be able to complete in a shorter period of time than OSGL and yet provide you with more FPS.

Looking at all this in a realistic way, Drawing libraries most commonly would be used for and at “Brush”, and for Brush since no matter what, it would not have a big impact on FPS, so you would want it to take a shorter time to generate and finish. If you use OSGL, the only thing it does is makes this process take longer lol.

You should also be more open to criticism, and not act as if you are always right. You clearly are not.

1 Like

OSGL has built-in waits. Read the source code and you’ll find that out for yourself. I’ve said that so many times already yet you’re completely ignoring it! After reading and finding out about the wait and actually understanding, do the tests still look accurate? How are the tests realistic? Do you wait half a second per draw in a real game? I doubt you wait at all, to start with. You need the fastest feedback.


For your tests,

No? It’s slower, because of the internal waits. Surprising, isn’t it? In the last test, CD literally had 0 FPS, how is that “better” in your terms? And, I would like to see the testing code. What exactly is “After” and “Before” render average time? What do FPS 1, 2, 3, and 4 represent? The difference in time is about 37%, not 50. Except when you added waits which I repeatedly said wasn’t correct.


I am? Proven by the fact that I explained this over 10 times already in Discord. You not listening and saying “I’m not sure if you have basic knowledge of Lua Scripting at this point.” isn’t criticism.

You don’t seem to be capable of understanding how pretty basic things work, and I don’t think I’d want to bother to waste more time on this.

CD:
Average time taken for each pixel: 0.000000351
Average time taken for each pixel: 0.000000349
Average time taken for each pixel: 0.000000352

OSGL:
Average time taken for each pixel: 0.000000601
Average time taken for each pixel: 0.000000594
Average time taken for each pixel: 0.000000601

That’s 42% percent in that test for pixels, and about 40% for total generation time.
Regarding test #3 for CanvasDraw where you said they all have 0 FPS, its true, because it doesn’t wait and OSGL waits as its built-in function. You can easily see that it almost took half the time of OSGL. You can easily add a wait for CanvasDraw (not OSGL), and yet provide you with more FPS and still take shorter period of time.

OSGL Source Code:

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

As you can see the built-in task.wait. CanvasDraw doesn’t have that, means for the most fair testing, you’d have to add task.wait for CanvasDraw.
2 of the tests above for CD and OSGL were without wait, just a quick reminder.
The most realistic of all the tests was the first one, those WAITS do NOT matter, they just mean executing the function every half second, and it doesn’t make any difference other than making it more realistic. Imagining you have a Fill Color tool, Magic Wand, something like that, it would be pretty much equivalent to how test 2, and 1 work. There is 0-1 FPS difference, and CanvasDraw is about 40% faster.

Realistically looking at it, overall, all the tests included and even tests outside of these, would accumulate to over 50% OSGL taking longer than CanvasDraw [average].

I’m hoping somebody else would explain this to you as you seem to be stubborn and not able to understand accurate benchmarks and pretty basic stuff.

1 Like

Both of which CD had 0 FPS for.

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

“Criticism”, you said? :thinking:


Waits do matter. Look, if you wait 0.5 in CD, OSGL would be waiting a bit more because of the internal waiting.
Realistic = forcing the draws to be 0.5 seconds apart? - very realistic, indeed.
Away from all of that, I told you to use the diagnostic bar, which gives the actual FPS, which you, like everything else I said, ignored. And away from all of that, OSGL also uses 75% less memory to store the pixels (ie. it uses one fourth of the memory), so even if it’s slower, it still uses less memory. If you see that CD is better, I literally said, use it! I’m not holding you back, man. The choice is all yours.


Why didn’t you send the testing code?