How to reduce flicker without adding a delay?

Just starting out? Need help? Post your questions and find answers here.
myutopia
User
User
Posts: 91
Joined: Sat Jul 04, 2009 1:29 am

How to reduce flicker without adding a delay?

Post by myutopia »

Hi all :)

Does anyone know how to reduce the flicker on the update of the TextGadget without using delay(1)?

Code: Select all

Enumeration
  #Window
  #Text
EndEnumeration

Procedure ChangeText(textgadget)
  For x = 0 To 999999
    SetGadgetText(textgadget,Str(x))


    ;Delay(1) ;This reduces the flicker when uncommented. Is there any other way besides this?


  Next
EndProcedure

If OpenWindow(#Window,#PB_Ignore,0,100,100,"Test")
  TextGadget(#Text,5,5,90,90,"Hello")
  
EndIf

CreateThread(@ChangeText(),#Text)

Repeat
  ev = WaitWindowEvent()
Until ev = #PB_Event_CloseWindow
Thanks!
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

I guess you're concerned about speed? The delay slows it down a lot. If it were me and I wanted it to go like a bat out of hell with no flicker I'd switch technologies away from the textgadget:

Code: Select all

Enumeration 
  #Window 
  #Text 
EndEnumeration 

Global threadrunning

Procedure ChangeText(textgadget) 

  threadrunning = #True
  x=0
  While threadrunning
    x+1
    ClearScreen(GetSysColor_(#COLOR_BTNFACE))
    StartDrawing(ScreenOutput())
      DrawingFont(GetStockObject_(#DEFAULT_GUI_FONT))
      DrawingMode(#PB_2DDrawing_Transparent)
      DrawText(0,0,Str(x),#Black)
    StopDrawing()
    FlipBuffers(0)
  Wend
  
EndProcedure 

If OpenWindow(#Window,#PB_Ignore,0,100,100,"Test") 
  InitSprite()
  OpenWindowedScreen(WindowID(0),5,5,90,20,0,0,0)
EndIf 

tid = CreateThread(@ChangeText(),#Text) 

Repeat 
  ev = WaitWindowEvent() 
Until ev = #PB_Event_CloseWindow 

threadrunning=#False
WaitThread(tid)
Tested with DX9 subsystem, really fast with no flicker here.
BERESHEIT
myutopia
User
User
Posts: 91
Joined: Sat Jul 04, 2009 1:29 am

Post by myutopia »

Yes, speed was my main concern. Didn't find it logical to purposely slow down the procedure just to make something look good.

Thanks, netmaestro! I'll try this one out :)
Ollivier
Enthusiast
Enthusiast
Posts: 281
Joined: Mon Jul 23, 2007 8:30 pm
Location: FR

Post by Ollivier »

NetMaestro wrote a good method if there is only one rectangular area in which the text is displayed.

In his code, I discover too these two API functions :
- GetStockObject_(#DEFAULT_GUI_FONT)
- GetSysColor_(#COLOR_BTNFACE)
(I use them in the following code)

But if you want to display two numbers (or changing text) and if there is a gadget between (for example, a button), I think you should use images instead of TextGadget(). This code loads characters table and display them via the images. For this test, you don't need DirectX. There is perhaps an other solution, but I don't know it.

Code: Select all

Enumeration
  #Window
  #Text
EndEnumeration

Global xStart.I
Global yStart.I
Global xChar.I
Global yChar.I

Procedure LoadCharMap()
    Protected I.I ; Iteration
    Protected A.S ; Ascii char
    Protected W.I ; char Width
    Protected H.I ; char Height
    Protected Font.I
    Font = GetStockObject_(#DEFAULT_GUI_FONT)
    CreateImage(256, 16, 16)
    For I = 32 To 255
        A = Chr(I)
        StartDrawing(ImageOutput(256) )
          DrawingFont(Font)
          W = TextWidth(A)
          H = TextHeight(A)
        StopDrawing()
        CreateImage(I, W, H)
        StartDrawing(ImageOutput(I) )
            Box(0, 0, W, H, GetSysColor_(#COLOR_BTNFACE) )
            DrawingFont(Font)
            DrawingMode(#PB_2DDrawing_Transparent)
            DrawText(0, 0, A, #Black)
        StopDrawing()
    Next I
EndProcedure

Procedure SetDisplayStart(x.I, y.I)
    xStart = x
    yStart = y
EndProcedure

Procedure DisplayHome()
    xChar = xStart
    yChar = yStart
EndProcedure

Procedure DisplayCarriageReturn()
    xChar = xStart
EndProcedure

Procedure DisplayLineFeed()
    yChar + ImageHeight(32)
EndProcedure

Procedure DisplayString(StringContent.S)
    Protected I.I
    Protected A.I
    For I = 1 To Len(StringContent)
        A = Asc(Mid(StringContent, I, 1) )
        If A > 31
            DrawImage(ImageID(A), xChar, yChar)
            xChar + ImageWidth(A)
        Else
            Select A
                Case 13
                    DisplayCarriageReturn()
                Case 10
                    DisplayLineFeed()
            EndSelect
        EndIf
    Next I
EndProcedure

Procedure DisplayText(X.I, Y.I, Text.S)
    SetDisplayStart(X, Y)
    DisplayHome()
    DisplayCarriageReturn()
    DisplayString(Text)
EndProcedure


If OpenWindow(#Window, #PB_Ignore, 0, 600, 100, "Test")
    LoadCharMap()
    ButtonGadget(0, 200, 5, 200, 25, "MyButton")
    Repeat
        Delay(1)
        Event = WindowEvent()
        N + 1
        StartDrawing(WindowOutput(#Window) )
            DisplayText(5, 5, Str(N) )
            DisplayText(550, 5, Str(N) )
            DisplayText(5, 30, "This is a long text to test if it flickers" + Chr(13) + Chr(10) + "And again a line to see if really there is no problem about that.")
        StopDrawing()
    Until Event = #PB_Event_CloseWindow
EndIf
Ollivier
Enthusiast
Enthusiast
Posts: 281
Joined: Mon Jul 23, 2007 8:30 pm
Location: FR

Post by Ollivier »

:roll:

Code: Select all

Enumeration
  #Window
  #Text
EndEnumeration

Procedure ChangeText(textgadget)
  For x = 0 To 9999999

    NoDelay + 1
    If NoDelay > 10000
        SetGadgetText(textgadget,Str(x))
        Delay(1)
        NoDelay = 1
    EndIf


  Next
  SetGadgetText(textgadget,Str(x))
EndProcedure

If OpenWindow(#Window,#PB_Ignore,0,100,100,"Test")
  TextGadget(#Text,5,5,90,90,"Hello")
 
EndIf

CreateThread(@ChangeText(),#Text)

Repeat
  ev = WindowEvent()
  Delay(1)
Until ev = #PB_Event_CloseWindow
myutopia
User
User
Posts: 91
Joined: Sat Jul 04, 2009 1:29 am

Post by myutopia »

Thanks, Ollivier :) I will try these out as well.
Ollivier
Enthusiast
Enthusiast
Posts: 281
Joined: Mon Jul 23, 2007 8:30 pm
Location: FR

Post by Ollivier »

@MyUtopia

Hi. You should publish the interesting question you asked me because I think NetMaestro has more informations than me about API functions. I have solutions but they are too complex. I suppose there is a function adapted to update simply and correctly the window when you want to clear the characters drawn on the window but I don't know it. I am sorry...

Edit: I would want you to observe the second code I published. Something says me it's more adapted to your first question. If you need a very fast counter, don't forget your eyes only see between 20 and 30 frames per seconds. That's the reason I modify the procedure inside the thread to control when you need to see the progress of the counter (i.e. every 1000 iterations). I think removing the Delay() function is not a good solution but using it every iteration is a wrong solution too. There is a "just middle"...

Ollivier
myutopia
User
User
Posts: 91
Joined: Sat Jul 04, 2009 1:29 am

Post by myutopia »

Erm... okay...

I opted to use this code because I tested its speed against netmaestro's version, and this one was faster on my computer. Netmaestro's code was "neater" however, in the sense that it doesn't have to keep redrawing to keep the drawn items on the screen if other window overlaps, or when the window is moved "off screen". This once does.

But for my purpose, I simply need it to show the progress flicker free, and once it's done, disappear.

This is how I'm doing it now, but I have a funny feeling there's a better way to accomplish this. Maybe someone can help?

Code: Select all

Enumeration
  #Window
  #Text
  #StatusBar
EndEnumeration

Global xStart.I
Global yStart.I
Global xChar.I
Global yChar.I

Procedure LoadCharMap()
    Protected I.I ; Iteration
    Protected A.S ; Ascii char
    Protected W.I ; char Width
    Protected H.I ; char Height
    Protected Font.I
    Font = FontID(LoadFont(#PB_Any, "Verdana", 10))
    CreateImage(256, 16, 16)
    For I = 32 To 255
        A = Chr(I)
        StartDrawing(ImageOutput(256) )
          DrawingFont(Font)
          W = TextWidth(A)
          H = TextHeight(A)
        StopDrawing()
        CreateImage(I, W, H)
        StartDrawing(ImageOutput(I) )
            Box(0, 0, W, H, GetSysColor_(#COLOR_BTNFACE) )
            DrawingFont(Font)
            DrawingMode(#PB_2DDrawing_Transparent)
            DrawText(0, 0, A, RGB(255,50,50))
        StopDrawing()
    Next I
EndProcedure

Procedure SetDisplayStart(x.I, y.I)
    xStart = x
    yStart = y
EndProcedure

Procedure DisplayHome()
    xChar = xStart
    yChar = yStart
EndProcedure

Procedure DisplayCarriageReturn()
    xChar = xStart
EndProcedure

Procedure DisplayLineFeed()
    yChar + ImageHeight(32)
EndProcedure

Procedure DisplayString(StringContent.S)
    Protected I.I
    Protected A.I
    For I = 1 To Len(StringContent)
        A = Asc(Mid(StringContent, I, 1) )
        If A > 31
            DrawImage(ImageID(A), xChar, yChar)
            xChar + ImageWidth(A)
        Else
            Select A
                Case 13
                    DisplayCarriageReturn()
                Case 10
                    DisplayLineFeed()
            EndSelect
        EndIf
    Next I
EndProcedure

Procedure DisplayText(X.I, Y.I, Text.S)
    SetDisplayStart(X, Y)
    DisplayHome()
    DisplayCarriageReturn()
    DisplayString(Text)
EndProcedure

Procedure DrawThisBaby(void)
  #Maxx = 999
 
  For x = 1 To #Maxx
            N + 1
        StartDrawing(WindowOutput(#Window) )
            DisplayText(5, 5, Str(N) )
            DisplayText(500, 5, Str(#Maxx-N) )
            DisplayText(5, 30, "This is a long text to test if it flickers")
            DisplayText(5, 80, Str(N) )
        StopDrawing()
  Next
;*******************************************************
; This is how I clear it now. Is there a "better" way?
;*******************************************************

;   StartDrawing(WindowOutput(#Window) )
;       DisplayText(5, 5, "        " )
;       DisplayText(500, 5, "       " )
;       DisplayText(5, 30, "                                                   ")
;       DisplayText(5, 80, "        " )
;   StopDrawing()   
EndProcedure

If OpenWindow(#Window, #PB_Ignore, 0, 600, 100, "Test")
    CreateStatusBar(#StatusBar, WindowID(#Window))
    LoadCharMap()
    ButtonGadget(0, 200, 5, 200, 25, "MyButton")
    CreateThread(@DrawThisBaby(),0)
    Repeat
        Event = WaitWindowEvent()
    Until Event = #PB_Event_CloseWindow
EndIf 
Thanks y'all! :D
Last edited by myutopia on Sun Aug 16, 2009 6:02 pm, edited 1 time in total.
myutopia
User
User
Posts: 91
Joined: Sat Jul 04, 2009 1:29 am

Post by myutopia »

@Ollivier,
Yes I saw that one too. Great idea :) I'm sure it'll come in handy for future projects.
Ollivier
Enthusiast
Enthusiast
Posts: 281
Joined: Mon Jul 23, 2007 8:30 pm
Location: FR

Post by Ollivier »

Okay, if you prefer this... :D ... I found a solution.

Code: Select all

Enumeration
  #Window
  #Text
  #StatusBar
EndEnumeration

Global xStart.I
Global yStart.I
Global xChar.I
Global yChar.I

Procedure LoadCharMap()
    Protected I.I ; Iteration
    Protected A.S ; Ascii char
    Protected W.I ; char Width
    Protected H.I ; char Height
    Protected Font.I
    Font = FontID(LoadFont(#PB_Any, "Verdana", 10))
    CreateImage(256, 16, 16)
    For I = 32 To 255
        A = Chr(I)
        StartDrawing(ImageOutput(256) )
          DrawingFont(Font)
          W = TextWidth(A)
          H = TextHeight(A)
        StopDrawing()
        CreateImage(I, W, H)
        StartDrawing(ImageOutput(I) )
            Box(0, 0, W, H, GetSysColor_(#COLOR_BTNFACE) )
            DrawingFont(Font)
            DrawingMode(#PB_2DDrawing_Transparent)
            DrawText(0, 0, A, RGB(255,50,50))
        StopDrawing()
    Next I
EndProcedure

Procedure SetDisplayStart(x.I, y.I)
    xStart = x
    yStart = y
EndProcedure

Procedure DisplayHome()
    xChar = xStart
    yChar = yStart
EndProcedure

Procedure DisplayCarriageReturn()
    xChar = xStart
EndProcedure

Procedure DisplayLineFeed()
    yChar + ImageHeight(32)
EndProcedure

Procedure DisplayString(StringContent.S)
    Protected I.I
    Protected A.I
    For I = 1 To Len(StringContent)
        A = Asc(Mid(StringContent, I, 1) )
        If A > 31
            DrawImage(ImageID(A), xChar, yChar)
            xChar + ImageWidth(A)
        Else
            Select A
                Case 13
                    DisplayCarriageReturn()
                Case 10
                    DisplayLineFeed()
            EndSelect
        EndIf
    Next I
EndProcedure

Procedure DisplayText(X.I, Y.I, Text.S)
    SetDisplayStart(X, Y)
    DisplayHome()
    DisplayCarriageReturn()
    DisplayString(Text)
EndProcedure

Procedure DrawThisBaby(void)
  #Maxx = 999
 
  For x = 1 To #Maxx
            N + 1
        StartDrawing(WindowOutput(#Window) )
            DisplayText(5, 5, Str(N) )
            DisplayText(500, 5, Str(#Maxx-N) )
            DisplayText(5, 30, "This is a long text to test if it flickers")
            DisplayText(5, 80, Str(N) )
        StopDrawing()
  Next
;*******************************************************
; This is how I clear it now. Is there a "better" way?
;*******************************************************

;   StartDrawing(WindowOutput(#Window) )
;       DisplayText(5, 5, "        " )
;       DisplayText(500, 5, "       " )
;       DisplayText(5, 30, "                                                   ")
;       DisplayText(5, 80, "        " )
;   StopDrawing()   
    HideWindow(#window, 1)
    HideWindow(#window, 0)
EndProcedure

If OpenWindow(#Window, #PB_Ignore, 0, 600, 100, "Test")
    CreateStatusBar(#StatusBar, WindowID(#Window))
    LoadCharMap()
    ButtonGadget(0, 200, 5, 200, 25, "MyButton")
    CreateThread(@DrawThisBaby(),0)
    Repeat
        Event = WaitWindowEvent()
    Until Event = #PB_Event_CloseWindow
EndIf
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Post by Rescator »

There is no point updating the numbers faster than the frame or refresh rate.
What many apps do is display intervals, i.e. just the tens, or the hundreds etc.
You can see this in Windows GUI as well, holding in the button on a cycle gadget, the longer you hold the more faster it seems to skip through numbers.

Remember that display all 10000 numbers vs every 100th number of those 10000 slows down the program as well.

This should give you the visual you need, but use hardly any CPU at all.
I have no idea what else your doing in that loop though, but... If's it's to run on a single core CPU you might want to move the Delay(0) right above the Next instead to make sure OS remains responsive.

Code: Select all

Enumeration
  #Window
  #Text
EndEnumeration

Procedure ChangeText(textgadget)
 c=0
  For x = 0 To 999999
   If c=123
    c=0
    SetGadgetText(textgadget,Str(x))
    Delay(0)
   Else
    Delay(1)
   EndIf
   c+1
  Next
EndProcedure

If OpenWindow(#Window,#PB_Ignore,0,100,100,"Test")
  TextGadget(#Text,5,5,90,90,"Hello")
 
EndIf

CreateThread(@ChangeText(),#Text)

Repeat
  ev = WaitWindowEvent()
Until ev = #PB_Event_CloseWindow
Oh in case you are wondering why I chose 123 for the c trigger, it's that it looks more random, if it triggered on 100 it would display 00 at the nd all the time ;)

You might also find this interesting http://www.purebasic.fr/english/viewtopic.php?t=3727
myutopia
User
User
Posts: 91
Joined: Sat Jul 04, 2009 1:29 am

Post by myutopia »

Thanks for the suggestions y'all. It has helped, plus I learned some new tricks. :)
User avatar
einander
Enthusiast
Enthusiast
Posts: 744
Joined: Thu Jun 26, 2003 2:09 am
Location: Spain (Galicia)

Post by einander »

You can try this trick from Sparkie (Windows only)
And it works without the delay.

Code: Select all

Enumeration
   #Window
   #Text
EndEnumeration
   
Macro NoFlickWin(Win)  ;- NoFlickWin(win) - this one is from Sparkie *************
   SetWindowLongPtr_(WindowID(Win), -20, GetWindowLongPtr_(WindowID(Win), -20) | $2000000)
EndMacro     


Procedure ChangeText(textgadget)
   For X = 0 To 999999
      SetGadgetText(textgadget,Str(X))
      
      
      ;Delay(1) ;This reduces the flicker when uncommented. Is there any other way besides this?
      
      
   Next
EndProcedure

If OpenWindow(#Window,#PB_Ignore,0,100,100,"Test")
  NoFlickWin(#Window) 
   TextGadget(#Text,5,5,90,90,"Hello")
   
EndIf

CreateThread(@ChangeText(),#Text)

Repeat
   Ev = WaitWindowEvent()
Until Ev = #PB_Event_CloseWindow
Cheers
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Post by Rescator »

@einander
That flickers here on Vista.

@myutopia
This may be an odd question to you but.
What is the point in something shown so fast it can't be read?
that kinda defeat the purpose of showing it in the first place.
If it's not readable there is no point showing it as it has no meaning :P
Sparkie
PureBatMan Forever
PureBatMan Forever
Posts: 2307
Joined: Tue Feb 10, 2004 3:07 am
Location: Ohio, USA

Post by Sparkie »

Macro NoFlickWin(Win) ;- NoFlickWin(win) - this one is from Sparkie *************
SetWindowLongPtr_(WindowID(Win), -20, GetWindowLongPtr_(WindowID(Win), -20) | $2000000)
EndMacro
For the life of me I cannot remember coming up with that. Must have been somebody else :wink:
What goes around comes around.

PB 5.21 LTS (x86) - Windows 8.1
Post Reply