Page 1 of 2

How to reduce flicker without adding a delay?

Posted: Sun Aug 16, 2009 3:00 am
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!

Posted: Sun Aug 16, 2009 4:35 am
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.

Posted: Sun Aug 16, 2009 4:49 am
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 :)

Posted: Sun Aug 16, 2009 6:13 am
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

Posted: Sun Aug 16, 2009 6:35 am
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

Posted: Sun Aug 16, 2009 7:12 am
by myutopia
Thanks, Ollivier :) I will try these out as well.

Posted: Sun Aug 16, 2009 4:44 pm
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

Posted: Sun Aug 16, 2009 5:58 pm
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

Posted: Sun Aug 16, 2009 6:01 pm
by myutopia
@Ollivier,
Yes I saw that one too. Great idea :) I'm sure it'll come in handy for future projects.

Posted: Sun Aug 16, 2009 7:43 pm
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

Posted: Mon Aug 17, 2009 5:57 am
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

Posted: Mon Aug 17, 2009 6:37 am
by myutopia
Thanks for the suggestions y'all. It has helped, plus I learned some new tricks. :)

Posted: Mon Aug 17, 2009 10:18 am
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

Posted: Tue Aug 18, 2009 12:45 am
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

Posted: Tue Aug 18, 2009 2:15 am
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: