Gaussian blur in real time?
Re: Gaussian blur in real time?
Confirm, GPU. Maybe it's just me then. Right now, I'm refactoring, I'll check that later (maybe it's the GrabSprite() part then?).
Re: Gaussian blur in real time?
I dont get why you would use GrabSprite (in a loop) just to blur it - why cant you use the backbuffer directly?Joubarbe wrote:Wow man, how much do I owe you?
Impressive results.
At 800x800, your DirectionalBlur costs me 2 FPS with #Blur_Full, which is great. However, GrabSprite() is a lot more expensive than I thought. 15 FPS in that case (800x800).Code: Select all
Static sprite_tmp.i, *buffer If IsSprite(sprite_tmp) : FreeSprite(sprite_tmp) : EndIf sprite_tmp = GrabSprite(#PB_Any, 100, 100, 800, 800) StartDrawing(SpriteOutput(sprite_tmp)) *buffer = DrawingBuffer() DirectionalBlur::BlurPixelBuf32(*buffer, OutputWidth(), OutputHeight(), 10, DirectionalBlur::#Blur_Full) StopDrawing() DisplaySprite(sprite_tmp, 100, 100)
The strength option doesn't seem to cost more at 30.
I'm not sure how exactly the LineStride option works. Putting random values is fun
But yeah, again, the following is the most expensive:
I'm pretty sure that I should be doing something elseCode: Select all
Static sprite_tmp.i, *buffer If IsSprite(sprite_tmp) : FreeSprite(sprite_tmp) : EndIf sprite_tmp = GrabSprite(#PB_Any, 100, 100, 800, 800) DisplaySprite(sprite_tmp, 100, 100)
Would be a lot faster.
Re: Gaussian blur in real time?
https://www.purebasic.com/documentation ... uffer.html
EDIT: @wilbert: High GPU comes from GrabSprite(). My bad!
How do you mean? Am I not supposed to use a StartDrawing() block at some point? Meaning if I don't grab a sprite, I must use ScreenOutput(), and this is even slower.Once StopDrawing() has been called, the buffer is invalidated and can no more be used.
EDIT: @wilbert: High GPU comes from GrabSprite(). My bad!
Re: Gaussian blur in real time?
I tried with threading.
Change mode by key 0-5.
Mode 0-4 only measures but doesn't blur. Mode 5 does blur by actually using the thread.
In proc() you can change manually what blur (or combination and repetition) is used.
GrabSprite() as well as CopyMemory() are very slow on my Windows 10 per default (DirectX9 I guess), ~160ms. But with subsystem OpenGL they are quite fast with ~8ms for GrabSprite() and ~1ms for 3 x CopyMemory().
The bottom line is that you have some overhead due to the screen drawing block and the CopyMemory but if you find a blur function that takes less time than you main loop (including the drawing block) then the thread won't drag the frame time any further. If the thread takes longer then the main loop has to wait, dragging the frame time to match the thread time.
The 2 semaphores manage that the main loop and the thread can run in parallel but don't overtake each other, i.e. they always alternate and wait for each other to finish. They also protect the data accessed by both main and thread.
Change mode by key 0-5.
Mode 0-4 only measures but doesn't blur. Mode 5 does blur by actually using the thread.
In proc() you can change manually what blur (or combination and repetition) is used.
GrabSprite() as well as CopyMemory() are very slow on my Windows 10 per default (DirectX9 I guess), ~160ms. But with subsystem OpenGL they are quite fast with ~8ms for GrabSprite() and ~1ms for 3 x CopyMemory().
The bottom line is that you have some overhead due to the screen drawing block and the CopyMemory but if you find a blur function that takes less time than you main loop (including the drawing block) then the thread won't drag the frame time any further. If the thread takes longer then the main loop has to wait, dragging the frame time to match the thread time.
The 2 semaphores manage that the main loop and the thread can run in parallel but don't overtake each other, i.e. they always alternate and wait for each other to finish. They also protect the data accessed by both main and thread.
Code: Select all
CompilerIf #PB_Compiler_Debugger
If MessageRequester("debugger?", "the debugger is enabled. run anyway?", #PB_MessageRequester_YesNo) = #PB_MessageRequester_No
End
EndIf
CompilerEndIf
CompilerIf Not #PB_Compiler_Thread
CompilerError "please enable threadsafe compiler option."
CompilerEndIf
EnableExplicit
#ScreenWidth = 800
#ScreenHeight = 600
;#ScreenWidth = 1024
;#ScreenHeight = 768
;#ScreenWidth = 1680
;#ScreenHeight = 1050
InitSprite()
InitKeyboard()
InitMouse()
ElapsedMilliseconds()
Structure Pixel
r.a
g.a
b.a
EndStructure
Define win, quit, mode, mode$
Define spr, i
Define sprFR, FR, FRt, FR$
Define time, timeTmp
win = OpenWindow(#PB_Any, 0, 0, #ScreenWidth, #ScreenHeight, "window")
OpenWindowedScreen(WindowID(win), 0, 0, #ScreenWidth, #ScreenHeight, 0, 0, 0, #PB_Screen_NoSynchronization)
SetFrameRate(999)
UsePNGImageDecoder()
CompilerIf #PB_Compiler_OS = #PB_OS_Windows
Define spr = LoadSprite(#PB_Any, #PB_Compiler_Home + "examples/3D/Data/PureBasic3DLogo.png") ; windows
CompilerElse
Define spr = LoadSprite(#PB_Any, #PB_Compiler_Home + "examples/3d/Data/PureBasic3DLogo.png") ; linux
CompilerEndIf
CompilerIf #PB_Compiler_OS = #PB_OS_Windows
Define font = LoadFont(#PB_Any, "courier new", 10)
CompilerElse
Define font = LoadFont(#PB_Any, "monospace", 10)
CompilerEndIf
; ------------------------------------------------------------
sprFR = CreateSprite(#PB_Any, 400, 80)
mode = 5
; ------------------------------------------------------------
Procedure blur_a(xStart, yStart, xEnd, yEnd, buffer, pitch, pixelSize, neighborDistance)
Protected x, y
Protected *pixel1.Pixel
Protected *pixel2lef.Pixel
Protected *pixel2rig.Pixel
Protected *pixel2top.Pixel
Protected *pixel2bot.Pixel
For y = yStart To yEnd
For x = xStart To xEnd
*pixel1 = buffer + ((y ) * pitch) + ((x ) * pixelSize)
*pixel2lef = buffer + ((y ) * pitch) + ((x-neighborDistance) * pixelSize)
*pixel2rig = buffer + ((y ) * pitch) + ((x+neighborDistance) * pixelSize)
*pixel2top = buffer + ((y-neighborDistance) * pitch) + ((x ) * pixelSize)
*pixel2bot = buffer + ((y+neighborDistance) * pitch) + ((x ) * pixelSize)
*pixel1\r = (*pixel1\r + *pixel2lef\r + *pixel2rig\r + *pixel2top\r + *pixel2bot\r) / 5.0
*pixel1\g = (*pixel1\g + *pixel2lef\g + *pixel2rig\g + *pixel2top\g + *pixel2bot\g) / 5.0
*pixel1\b = (*pixel1\b + *pixel2lef\b + *pixel2rig\b + *pixel2top\b + *pixel2bot\b) / 5.0
Next
Next
EndProcedure
Procedure blur_b(xStart, yStart, xEnd, yEnd, buffer, pitch, pixelSize)
Protected x, y
Protected *pixel1.Pixel
Protected *pixel2_1.Pixel
Protected *pixel2_2.Pixel
Protected *pixel2_3.Pixel
Protected *pixel2_4.Pixel
Protected *pixel2_5.Pixel
Protected *pixel2_6.Pixel
Protected *pixel2_7.Pixel
Protected *pixel2_8.Pixel
For y = yStart To yEnd
For x = xStart To xEnd
*pixel1 = buffer + ((y ) * pitch) + ((x ) * pixelSize)
*pixel2_1 = buffer + ((y-3) * pitch) + ((x+1) * pixelSize)
*pixel2_2 = buffer + ((y-1) * pitch) + ((x+3) * pixelSize)
*pixel2_3 = buffer + ((y+1) * pitch) + ((x+3) * pixelSize)
*pixel2_4 = buffer + ((y+3) * pitch) + ((x+1) * pixelSize)
*pixel2_5 = buffer + ((y+3) * pitch) + ((x-1) * pixelSize)
*pixel2_6 = buffer + ((y+1) * pitch) + ((x-3) * pixelSize)
*pixel2_7 = buffer + ((y-1) * pitch) + ((x-3) * pixelSize)
*pixel2_8 = buffer + ((y-3) * pitch) + ((x-1) * pixelSize)
*pixel1\r = (*pixel2_1\r + *pixel2_2\r + *pixel2_3\r + *pixel2_4\r + *pixel2_5\r + *pixel2_6\r + *pixel2_7\r + *pixel2_8\r) / 8.0
*pixel1\g = (*pixel2_1\g + *pixel2_2\g + *pixel2_3\g + *pixel2_4\g + *pixel2_5\g + *pixel2_6\g + *pixel2_7\g + *pixel2_8\g) / 8.0
*pixel1\b = (*pixel2_1\b + *pixel2_2\b + *pixel2_3\b + *pixel2_4\b + *pixel2_5\b + *pixel2_6\b + *pixel2_7\b + *pixel2_8\b) / 8.0
Next
Next
EndProcedure
Procedure blur_c(xStart, yStart, xEnd, yEnd, buffer, pitch, pixelSize);, buffer2)
Protected x, y
Protected *pixel1.Pixel
Protected *pixel2lef.Pixel
Protected *pixel2rig.Pixel
Protected *pixel2top.Pixel
Protected *pixel2bot.Pixel
;CopyMemory(buffer, buffer2, OutputHeight() * pitch)
Protected buffer2
buffer2 = buffer
Define n
For n = 1 To 4
For y = yStart To yEnd
For x = xStart To xEnd
*pixel1 = buffer + ((y ) * pitch) + ((x ) * pixelSize)
*pixel2lef = buffer2 + ((y ) * pitch) + ((x-n) * pixelSize)
*pixel2rig = buffer2 + ((y ) * pitch) + ((x+n) * pixelSize)
*pixel2top = buffer2 + ((y-n) * pitch) + ((x ) * pixelSize)
*pixel2bot = buffer2 + ((y+n) * pitch) + ((x ) * pixelSize)
*pixel1\r = (*pixel2lef\r + *pixel2rig\r + *pixel2top\r + *pixel2bot\r) / 4.0
*pixel1\g = (*pixel2lef\g + *pixel2rig\g + *pixel2top\g + *pixel2bot\g) / 4.0
*pixel1\b = (*pixel2lef\b + *pixel2rig\b + *pixel2top\b + *pixel2bot\b) / 4.0
Next
Next
Next
EndProcedure
Procedure blur_d(xStart, yStart, xEnd, yEnd, buffer, pitch, pixelSize, neighborDistance)
Protected x, y
Protected *pixel1.Pixel
Protected *pixel2lef.Pixel
Protected *pixel2rig.Pixel
For y = yStart To yEnd
For x = xStart To xEnd
*pixel1 = buffer + ((y ) * pitch) + ((x ) * pixelSize)
*pixel2lef = buffer + ((y ) * pitch) + ((x-neighborDistance) * pixelSize)
*pixel2rig = buffer + ((y ) * pitch) + ((x+neighborDistance) * pixelSize)
; *pixel1\r = (*pixel1\r + *pixel2lef\r + *pixel2rig\r) / 3.0
; *pixel1\g = (*pixel1\g + *pixel2lef\g + *pixel2rig\g) / 3.0
; *pixel1\b = (*pixel1\b + *pixel2lef\b + *pixel2rig\b) / 3.0
*pixel1\r = (*pixel2lef\r + *pixel2rig\r) / 2.0
*pixel1\g = (*pixel2lef\g + *pixel2rig\g) / 2.0
*pixel1\b = (*pixel2lef\b + *pixel2rig\b) / 2.0
Next
Next
EndProcedure
; ------------------------------------------------------------
Define buffer2
Define bufferTmp
Define yStart
Define yEnd
Define xStart
Define xEnd
; ------------------------------------------------------------
Define semaphoreThread = CreateSemaphore()
Define semaphoreMain = CreateSemaphore(1)
Enumeration #PB_Event_FirstCustomValue
#eventThreadTimeNotify
EndEnumeration
Structure s_threadInfo
buffer.i
pitch.i
pixelSize.i
yStart.i
yEnd.i
xStart.i
xEnd.i
quitThread.i
EndStructure
Procedure proc(*threadInfo.s_threadInfo)
Shared semaphoreThread
Shared semaphoreMain
Protected n
Protected nMax
Protected time
Protected dist
Protected quitThread
Repeat
WaitSemaphore(semaphoreThread) ;>
time = ElapsedMilliseconds()
If *threadInfo\buffer
; repeat
nMax = 1
For n=1 To nMax
; 4 neighbors of distance d
dist = 2
blur_a(*threadInfo\xStart, *threadInfo\yStart, *threadInfo\xEnd, *threadInfo\yEnd, *threadInfo\buffer, *threadInfo\pitch, *threadInfo\pixelSize, dist)
; 4 neighbors of distance d
;dist = 4
;blur_a(*threadInfo\xStart, *threadInfo\yStart, *threadInfo\xEnd, *threadInfo\yEnd, *threadInfo\buffer, *threadInfo\pitch, *threadInfo\pixelSize, dist)
; 8 neighbors
;blur_b(*threadInfo\xStart, *threadInfo\yStart, *threadInfo\xEnd, *threadInfo\yEnd, *threadInfo\buffer, *threadInfo\pitch, *threadInfo\pixelSize)
; 4 neighbors * multiple distance 1-4
;blur_c(*threadInfo\xStart, *threadInfo\yStart, *threadInfo\xEnd, *threadInfo\yEnd, *threadInfo\buffer, *threadInfo\pitch, *threadInfo\pixelSize)
; 2 neighbors of distance d
;dist = 3
;blur_d(*threadInfo\xStart, *threadInfo\yStart, *threadInfo\xEnd, *threadInfo\yEnd, *threadInfo\buffer, *threadInfo\pitch, *threadInfo\pixelSize, dist)
Next
EndIf
time = ElapsedMilliseconds() - time
PostEvent(#eventThreadTimeNotify, 0, 0, 0, time)
If *threadInfo\quitThread
quitThread = #True
EndIf
SignalSemaphore(semaphoreMain) ;<
Until quitThread
EndProcedure
Define threadInfo.s_threadInfo
Define thread = CreateThread(@proc(), @ threadInfo)
Define threadTime
; ------------------------------------------------------------
Repeat
ExamineMouse()
ExamineKeyboard()
If KeyboardPushed(#PB_Key_Escape) : quit = #True : EndIf
If KeyboardPushed(#PB_Key_0) : mode = 0 : EndIf
If KeyboardPushed(#PB_Key_1) : mode = 1 : EndIf
If KeyboardPushed(#PB_Key_2) : mode = 2 : EndIf
If KeyboardPushed(#PB_Key_3) : mode = 3 : EndIf
If KeyboardPushed(#PB_Key_4) : mode = 4 : EndIf
If KeyboardPushed(#PB_Key_5) : mode = 5 : EndIf
While WindowEvent()
Select Event()
Case #PB_Event_CloseWindow
quit = #True
Case #eventThreadTimeNotify
threadTime = EventData()
EndSelect
Wend
FR + 1
If ElapsedMilliseconds() > FRt
time / FR
Select mode
Case 0 : mode$ = "no screen drawing block"
Case 1 : mode$ = "only GrabSprite()"
Case 2 : mode$ = "empty screen drawing block"
Case 3 : mode$ = "base"
Case 4 : mode$ = "base + copy"
Case 5 : mode$ = "base + copy / thread"
EndSelect
FRt = ElapsedMilliseconds() + 500
FR$ = "fps:" + Str(FR * 2)
FR = 0
StartDrawing(SpriteOutput(sprFR))
DrawingMode(#PB_2DDrawing_Transparent)
Box(0, 0, OutputWidth(), OutputHeight(), $0)
DrawingFont(FontID(font))
DrawText(0, 0, FR$)
DrawText(0, 20, "mode: " + mode + " (" + mode$ + ")")
DrawText(0, 40, "frame time: " + time + "ms")
If mode >= 5
DrawText(0, 60, "thread time: " + threadTime + "ms")
EndIf
StopDrawing()
EndIf
ClearScreen($333333)
DisplayTransparentSprite(spr, MouseX(), MouseY())
If mode = 0
; no screen drawing block
ElseIf mode = 1
; test GrabSprite()
Define sprScreen
If sprScreen
FreeSprite(sprScreen)
sprScreen = 0
EndIf
sprScreen = GrabSprite(#PB_Any, 0, 0, #ScreenWidth, #ScreenHeight, #PB_Sprite_AlphaBlending)
;GrabSprite(0, 0, 0, #ScreenWidth, #ScreenHeight, #PB_Sprite_AlphaBlending)
ElseIf mode >= 2
; empty screen drawing block
If StartDrawing(ScreenOutput())
If mode >= 3
; base
Define buffer = DrawingBuffer()
Define pitch = DrawingBufferPitch()
Define pixelFormat = DrawingBufferPixelFormat()
Define pixelSize
; Debug pixelFormat
; Debug pixelFormat!#PB_PixelFormat_ReversedY
; Debug #PB_PixelFormat_24Bits_RGB
; Debug #PB_PixelFormat_32Bits_BGR
; End
If pixelFormat & (#PB_PixelFormat_24Bits_BGR | #PB_PixelFormat_24Bits_RGB)
pixelSize = 3
ElseIf pixelFormat & (#PB_PixelFormat_32Bits_BGR | #PB_PixelFormat_32Bits_RGB)
pixelSize = 4
Else
WaitSemaphore(semaphoreMain) ;>
threadInfo\quitThread = #True
SignalSemaphore(semaphoreThread) ;<
WaitThread(thread)
CloseScreen()
CloseWindow(win)
MessageRequester("error", "pixel format not supported")
End
EndIf
If pixelSize
;yStart = #ScreenHeight * 0.25
;yEnd = #ScreenHeight * 0.75
;xStart = #ScreenWidth * 0.25
;xEnd = #ScreenWidth * 0.75
yStart = #ScreenHeight * 0.1
yEnd = #ScreenHeight * 0.9
xStart = #ScreenWidth * 0.1
xEnd = #ScreenWidth * 0.9
DrawingMode(#PB_2DDrawing_Outlined)
Box(xStart - 20, yStart - 20, xEnd - xStart + 40, yEnd - yStart + 40, $ff00ff00)
If Not buffer2
buffer2 = AllocateMemory(pitch * OutputHeight())
EndIf
If Not bufferTmp
bufferTmp = AllocateMemory(pitch * OutputHeight())
EndIf
If mode >= 4
; copy / thread
WaitSemaphore(semaphoreMain) ;>
CopyMemory(buffer, bufferTmp, OutputHeight() * pitch)
CopyMemory(buffer2, buffer, OutputHeight() * pitch)
CopyMemory(bufferTmp, buffer2, OutputHeight() * pitch)
threadInfo\buffer = buffer2
threadInfo\pitch = pitch
threadInfo\pixelSize = pixelSize
threadInfo\yStart = yStart
threadInfo\yEnd = yEnd
threadInfo\xStart = xStart
threadInfo\xEnd = xEnd
If mode >= 5
SignalSemaphore(semaphoreThread) ;<
Else
SignalSemaphore(semaphoreMain) ;<
EndIf
;Delay(6)
EndIf
EndIf
EndIf
StopDrawing()
EndIf
EndIf
time = time + (ElapsedMilliseconds() - timeTmp)
timeTmp = ElapsedMilliseconds()
DisplaySprite(sprFR, MouseX(), MouseY() + 80)
FlipBuffers()
Until quit
WaitSemaphore(semaphoreMain) ;>
threadInfo\quitThread = #True
SignalSemaphore(semaphoreThread) ;<
WaitThread(thread)
Re: Gaussian blur in real time?
Drawing to the backbuffer is not slow (just access it once)!
I did some testing and used the excellent DirectionalBlur (BlurRect) from wilbert (i only modified it to have it as single function).
With a full hd screen blurring 3-4 areas (800x800) or the whole screen costs only 1-2 ms.
Thats rly fast imho.
I did some testing and used the excellent DirectionalBlur (BlurRect) from wilbert (i only modified it to have it as single function).
With a full hd screen blurring 3-4 areas (800x800) or the whole screen costs only 1-2 ms.
Thats rly fast imho.
Re: Gaussian blur in real time?
Could you post your part of the code where you draw to the backbuffer? I'm not sure to follow. (Don't need the directional blur functions)
Re: Gaussian blur in real time?
I posted one of the functions in wilberts directional blur thread.Joubarbe wrote:Could you post your part of the code where you draw to the backbuffer? I'm not sure to follow. (Don't need the directional blur functions)
Re: Gaussian blur in real time?
To my knowledge you always operate on the back buffer. DisplaySprite and screen drawing etc. always work on the back buffer. Flipbuffer() makes it the front buffer and the previous front buffer becomes the back buffer to work on.
Re: Gaussian blur in real time?
To explain a bit better my question, I would have thought that in PB, it was possible to catch the buffer before the main loop:
Then, use the Blur function:
But there must be something I don't get, because this obviously doesn't work. Nothing get blurred, even though the CPU is working.
And again, both StartDrawing(ScreenOutput()) and GrabSprite() are slow in real time. And the goal is to have an animated background blurred as a GUI background.
EDIT: The best I have right now is to blur a small part of the screen with GrabSprite() (inside the main loop):
At this size, GrabSprite() is not expensive.
EDIT2: Same with your code @Mijikai, why does the following doesn't work?
(main loop)
Code: Select all
StartDrawing(ScreenOutput())
DrawingBuffer()
*screen_buffer = AllocateMemory(DrawingBufferPitch() * OutputHeight())
CopyMemory(DrawingBuffer(), *screen_buffer, DrawingBufferPitch() * OutputHeight())
StopDrawing()
; ... Or just "*screen_buffer = DrawingBuffer()", I guess.
Code: Select all
Repeat
...
DirectionalBlur::BlurPixelBuf32(*screen_buffer, 200, 200, 10, DirectionalBlur::#Blur_Full)
...
FlipBuffer()
ForEver
And again, both StartDrawing(ScreenOutput()) and GrabSprite() are slow in real time. And the goal is to have an animated background blurred as a GUI background.
EDIT: The best I have right now is to blur a small part of the screen with GrabSprite() (inside the main loop):
Code: Select all
If IsSprite(sprite_tmp) : FreeSprite(sprite_tmp) : EndIf
sprite_tmp = GrabSprite(#PB_Any, 100, 100, 200, 200, #PB_Sprite_AlphaBlending)
StartDrawing(SpriteOutput(sprite_tmp))
DirectionalBlur::BlurPixelBuf32(DrawingBuffer(), OutputWidth(), OutputHeight(), 10, DirectionalBlur::#Blur_Full)
StopDrawing()
DisplayTransparentSprite(sprite_tmp, 100, 100)
EDIT2: Same with your code @Mijikai, why does the following doesn't work?
Code: Select all
DirectionalBlur::BlurSurface(*screen_buffer, *screen_buffer_pitch, 0, 0, 800, 800)
Re: Gaussian blur in real time?
Aquire access to the backbuffer once every frame and then do all the work you need.
Code: Select all
;...
If StartDrawing(ScreenOutput())
*pixel = DrawingBuffer()
pitch = DrawingBufferPitch()
; BlurSurface(*pixel,pitch,100,100,800,800,20)
; BlurSurface(*pixel,pitch,300,300,800,800,20)
; BlurSurface(*pixel,pitch,500,400,800,800,20)
; BlurSurface(*pixel,pitch,000,000,800,800,20)
BlurSurface(*pixel,pitch,0,0,1920,1080,10)
StopDrawing()
EndIf
;...
Last edited by Mijikai on Mon Oct 21, 2019 9:05 am, edited 1 time in total.
Re: Gaussian blur in real time?
Then I need StartDrawing(ScreenOutput())?! That's my question, how do you "access the backbuffer" (which should be called "buffer" only, because as #NULL mentions, you can't draw to the front buffer anyway). DrawingBuffer() requires a StartDrawing() block!
EDIT: Yeah, well... It's extremely slow to do that, it costs me 25 FPS.
EDIT2: Again, to be clear:
That is the expensive part. 25FPS for me. Just "doing nothing"
EDIT3: As I thought, the buffer is not "moving" at every frame, so I don't get why catching the buffer only once doesn't work. Would love to know why.
EDIT: Yeah, well... It's extremely slow to do that, it costs me 25 FPS.
EDIT2: Again, to be clear:
Code: Select all
Repeat
...
StartDrawing(ScreenOutput())
DrawingBuffer()
StopDrawing()
...
ForEver
EDIT3: As I thought, the buffer is not "moving" at every frame, so I don't get why catching the buffer only once doesn't work. Would love to know why.
Last edited by Joubarbe on Mon Oct 21, 2019 9:19 am, edited 1 time in total.
Re: Gaussian blur in real time?
YesJoubarbe wrote:Then I need StartDrawing(ScreenOutput())?!
No, seems like something is going wrong.Joubarbe wrote:EDIT: Yeah, well... It's extremely slow to do that, it costs me 25 FPS.