Silverfrost Forums

Welcome to our forums

Avoiding flicker with rapid %pl updates

15 Jan 2025 11:13 #31816

The discussion about sorting in another thread reminded me of the short demo program below which was sitting on my desktop.

When a %pl is embedded in a window, if it is desired to continually update the plot, for example to show the progress of a simulation, this can lead to “flicker” of the display.

One way to circumvent this is demonstrated in the program below. Replace the %pl with a %gr in the window which is to display the progress of your calculation. Use %pl[external] to plot the intermediate results to an memory resident graphics region created using create_graphics_region@, then copy from that graphics region back to the %gr that is displayed in your output window.

The sample program is not optimised, as you don’t need to create and delete the memory resident graphics region each time the function plot() is called, but the additional logic obscures the simplicity of what the sample program is illustrating.

In the sample program, a cocktail sort on a small random array is animated with the display updated every time a swap of two variables occurs, without any discernible flicker of the display.

module demo
use clrwin
implicit none
real*8 :: arr(50)
integer :: uid_gr = 1000, uid_pl = 2000, gw = 900, gh = 500
contains
integer function sort()
integer :: iw, i
  i = new_data()  ! New data
  iw = winio@('%mn[Exit]&','exit')
  iw = winio@('%^tt[Sort]%^tt[New data]&',cocktail_cb,new_data)
  iw = winio@('%bg%nl%`gr&', rgb@(220,220,220), gw, gh, uid_gr)
  iw = winio@('%sc',plot) !Call plot after window is initially formed
  sort = 2
end function sort

integer function cocktail_cb()
  call set_cursor_waiting@(1) ; call cocktailsort(arr) ; cocktail_cb = 2
  call set_cursor_waiting@(0)
end function cocktail_cb

integer function new_data()
logical, save :: first = .true.
integer :: i
  call random_number(arr)
  if ( .not. first ) i = plot()
  first = .false. ; new_data = 2
end function new_data

integer function plot()
integer :: i, iw
  i = create_graphics_region@(uid_pl, gw, gh )
  i = select_graphics_region@(uid_pl)
  iw = winio@('%pl[native,frame,gridlines,etched,width=3,link=columns,colour=blue,'//&
                  'dx=5,y_max=1,dy=0.5,x-axis=@,y-axis=@,external]',&
                  size(arr,kind=3),0.d0,1.d0,arr)
  i = copy_graphics_region@( uid_gr, 1, 1, gw, gh, uid_pl, 1, 1, gw, gh, 13369376 )
  i = delete_graphics_region@(uid_pl)
  call sleep1@(0.01)  
  plot = 2
end function plot

subroutine cocktailsort(arr)
  real*8, intent(inout) :: arr(:)
  integer :: startp, endp, i, k, n
  real*8 :: temp
  logical :: swapped
  n = size(arr) ; startp = 1 ; endp = n ; swapped = .true.
  do while (swapped)
    call temporary_yield@()
    swapped = .false.
    do i = startp, endp - 1, +1
      if (arr(i) > arr(i + 1)) then
        temp = arr(i) ; arr(i) = arr(i + 1) ; arr(i + 1) = temp ; swapped = .true. 
        k = plot()  ! update plot after swap
      end if
    end do
    if (.not. swapped) exit
    endp = endp - 1 ; swapped = .false.
    do i = endp, startp + 1, -1
      if (arr(i - 1) > arr(i)) then
        temp = arr(i) ; arr(i) = arr(i - 1) ; arr(i - 1) = temp ; swapped = .true.
        k = plot() ! update plot after swap
      end if
    end do
    startp = startp + 1
  end do
end subroutine cocktailsort
end module demo

program p
use demo
i = sort()
end program p
15 Jan 2025 12:05 #31817

For reference here is the equivalent code, without the 'no flicker' approach.

module demo
use clrwin
implicit none
real*8 :: arr(50)
integer :: gw = 900, gh = 500
contains
integer function sort()
integer :: iw, i
  i = new_data()  ! New data
  iw = winio@('%mn[Exit]&','exit')
  iw = winio@('%^tt[Sort]%^tt[New data]&',cocktail_cb,new_data)
  iw = winio@('%bg%nl&',rgb@(220,220,220))
  iw = winio@('%pl[native,frame,gridlines,etched,width=3,link=columns,colour=blue,'//&
                  'dx=5,y_max=1,dy=0.5,x-axis=@,y-axis=@]',&
                  gw,gh,size(arr,kind=3),0.d0,1.d0,arr)
  sort = 2
end function sort

integer function cocktail_cb()
  call set_cursor_waiting@(1) ; call cocktailsort(arr) ; cocktail_cb = 2
  call set_cursor_waiting@(0)
end function cocktail_cb

integer function new_data()
logical, save :: first = .true.
integer :: i
  call random_number(arr)
  if ( .not. first ) call simpleplot_redraw@()
  first = .false. ; new_data = 2
end function new_data


subroutine cocktailsort(arr)
  real*8, intent(inout) :: arr(:)
  integer :: startp, endp, i, k, n
  real*8 :: temp
  logical :: swapped
  n = size(arr) ; startp = 1 ; endp = n ; swapped = .true.
  do while (swapped)
    call temporary_yield@()
    swapped = .false.
    do i = startp, endp - 1, +1
      if (arr(i) > arr(i + 1)) then
        temp = arr(i) ; arr(i) = arr(i + 1) ; arr(i + 1) = temp ; swapped = .true. 
        call simpleplot_redraw@()
        call sleep1@(0.01)
      end if
    end do
    if (.not. swapped) exit
    endp = endp - 1 ; swapped = .false.
    do i = endp, startp + 1, -1
      if (arr(i - 1) > arr(i)) then
        temp = arr(i) ; arr(i) = arr(i - 1) ; arr(i - 1) = temp ; swapped = .true.
        call simpleplot_redraw@()
        call sleep1@(0.01)
      end if
    end do
    startp = startp + 1
  end do
end subroutine cocktailsort
end module demo

program p
use demo
i = sort()
end program p
16 Jan 2025 5:53 #31819

Ken,

Nice, but I'd prefer if Silverfrost updated %pl and made it capable what unappreciated by the users but great Clearwin's OpenGL %og[double] is doing.Then when you would call simpleplot_redraw@() it automatically write it to the one of two currently empty buffers instead of like right now when it cleaning current plot and then do simpleplot_redraw@() making sometimes flickering. At the same time it would be cleaning buffers of another channel which has been done behind the screen hidden to the user. Then next time you call simpleplot_redraw@() it would write to another buffer which was emptied in previous step, display result and hiddenly cleaning and preparing the other buffer and so on.

Not a single line of additional code would be needed from the user or to apply any additional tricks like above or changing usual pattern of simple usage of %pl -- you just add the keyword [double] to %pl. All described above would be done behind the Clearwin hood without even user knowledge

Of course I completely understand that it is easy to say than to actually implement this.

I remember that this damn flicker annoyed me since 1993 when I first time tried it with older FTN77 DBOS and showed the movie on some conference

16 Jan 2025 10:47 #31820

Dan,

One disadvantage of the approach in my example code is that %pl[external] cannot have a callback. Understandable since you cannot detect mouse clicks etc in a memory resident graphics region, however this also means that it is not possible to take advantage of the “plot_adjust” callback reason, for example to draw additional elements on the graph. Thus my example uses [link=columns] rather than drawing these explicitly within that call back (which is the way I produce histograms).

I agree, that if possible, the functionality you described based on %og[double] (which I have not used) i.e. %pl[double] would be the ideal solution.

I posted this example of one way around the flicker problem, for any future new users of %pl who may come across this issue.

In that regard, I hope others will find it useful, i.e. when making rapid updates to a relatively simple graph.

17 Jan 2025 7:31 #31821

Thank you for this helpful feedback.

Here are two possible alternative approaches that could be explored.

  1. The subroutines FREEZE_WINDOW_CONTENTS@(hwnd) and UNFREEZE_WINDOW_CONTENTS@(hwnd) might work in this context.

  2. i = SendMessage(hwnd, WM_SETREDRAW, 0, 0) and i = SendMessage(hwnd, WM_SETREDRAW, 1, 0) before and after might work.

The second approach is better if both work and should be tested first. hwnd is the Windows handle of the %pl control (%lc).

17 Jan 2025 11:42 #31822

Paul,

Thanks for the two suggestions. I investigated these this morning.

With:

  call FREEZE_WINDOW_CONTENTS@(hwnd)
  call simpleplot_redraw@()
  call UNFREEZE_WINDOW_CONTENTS@(hwnd)

the flicker is not eliminated.

With:

  use mswin
  .
  .
  i =  SendMessage(hwnd, WM_SETREDRAW, 0, 0)
  call simpleplot_redraw@()
  i = SendMessage(hwnd, WM_SETREDRAW, 1, 0)

the flicker is eliminated, BUT the plot remains permanently “frozen” in its initial state.

So unfortunately neither of these provide the desired end result. Perhaps I have misinterpreted what you were suggesting?

17 Jan 2025 1:49 #31823

Ken

Sorry to take you on a wild goose chase. I think you are right. These alternatives don't work in this context.

19 Jan 2025 10:58 #31824

No problem Paul. It was worth an hour experimenting with these. I would have been delighted if I'd caught the wild goose, (or perhaps a wild haggis at this time of year in Scotland)!

20 Jan 2025 9:35 #31826

A new option %pl[buffered] has now been added for the next release of ClearWin+.

Thank you for the feedback that triggered this enhancement.

21 Jan 2025 10:48 #31831

Paul,

Thank you for this unexpected, but very welcome enhancement.

23 Jan 2025 1:33 #31842

After downloading the new DLLs (133) I modified the second example I posted above to test this new enhancement.

In the code below the first window that is formed does not use the %pl[buffered] option, and it flickers (as before). The second window uses the new option (which is recognised) but all I see is a blank %pl region.

I'd be grateful if others who have downloaded the new DLLs could report what happens when they compile and run this code. Or perhaps Paul can tell me where I have gone wrong?

module demo
use clrwin ; use iso_fortran_env
implicit none
real*8 :: arr(50)
integer :: gw = 900, gh = 500
contains
integer function sort()
integer :: iw, i
  print*, compiler_version()
  ! ##### Without the new buffered option
  i = new_data()  ! New data
  iw = winio@('%mn[Exit]&','exit')
  iw = winio@('%fn[Tahoma]%ts&',1.5d0)
  iw = winio@('Without new buffered option %2nl%ts&',1.d0)
  iw = winio@('%^tt[Sort]%^tt[New data]&',cocktail_cb,new_data)
  iw = winio@('%bg%nl&',rgb@(220,220,220))
  iw = winio@('%pl[native,frame,gridlines,etched,width=3,link=columns,colour=blue,'//&
                  'dx=5,y_max=1,dy=0.5,x-axis=@,y-axis=@]',&
                  gw,gh,size(arr,kind=3),0.d0,1.d0,arr)
  ! ### With the new buffered option
  i = new_data()  ! New data
  iw = winio@('%mn[Exit]&','exit')
  iw = winio@('%fn[Tahoma]%ts&',1.5d0)
  iw = winio@('With new buffered option %2nl%ts&',1.d0)
  iw = winio@('%^tt[Sort]%^tt[New data]&',cocktail_cb,new_data)
  iw = winio@('%bg%nl&',rgb@(220,220,220))
  iw = winio@('%pl[native,buffered,frame,gridlines,etched,width=3,link=columns,colour=blue,'//&
                  'dx=5,y_max=1,dy=0.5,x-axis=@,y-axis=@]',&
                  gw,gh,size(arr,kind=3),0.d0,1.d0,arr)
  sort = 2
end function sort

integer function cocktail_cb()
  call cocktailsort(arr) ; cocktail_cb = 2
end function cocktail_cb

integer function new_data()
logical, save :: first = .true.
integer :: i
  call random_number(arr)
  if ( .not. first ) call simpleplot_redraw@()
  first = .false. ; new_data = 2
end function new_data

subroutine cocktailsort(arr)
  real*8, intent(inout) :: arr(:)
  integer :: startp, endp, i, k, n
  real*8 :: temp
  logical :: swapped
  n = size(arr) ; startp = 1 ; endp = n ; swapped = .true.
  do while (swapped)
    swapped = .false.
    do i = startp, endp - 1, +1
      if (arr(i) > arr(i + 1)) then
        temp = arr(i) ; arr(i) = arr(i + 1) ; arr(i + 1) = temp ; swapped = .true.
        print*, 'Calling simpleplot_redraw@()' ; call simpleplot_redraw@()
      end if
    end do
    if (.not. swapped) exit
    endp = endp - 1 ; swapped = .false.
    do i = endp, startp + 1, -1
      if (arr(i - 1) > arr(i)) then
        temp = arr(i) ; arr(i) = arr(i - 1) ; arr(i - 1) = temp ; swapped = .true.
        print*, 'Calling simpleplot_redraw@()' ; call simpleplot_redraw@()
      end if
    end do
    startp = startp + 1
  end do
  print*, 'sort completed'
end subroutine cocktailsort
end module demo
program p
use demo
i = sort()
end program p
23 Jan 2025 11:32 #31846

Ken

At the moment I don't understand the logic in your new program. This is a direct adaptation of your original version...

module demo
use clrwin
implicit none
real*8 :: arr(50)
integer :: gw = 900, gh = 500
contains
integer function sort()
integer :: iw, i
  i = new_data()  ! New data
  iw = winio@('%mn[Exit]&','exit')
  iw = winio@('%^tt[Sort]%^tt[New data]&',cocktail_cb,new_data)
  iw = winio@('%bg%nl%gr&', rgb@(220,220,220), gw, gh)
  iw = winio@('%sc',plot) !Call plot after window is initially formed
  sort = 2
end function sort

integer function cocktail_cb()
  call cocktailsort(arr) ;
  cocktail_cb = 2
end function cocktail_cb

integer function new_data()
logical, save :: first = .true.
integer :: i
  call random_number(arr)
  if ( .not. first ) i = plot()
  first = .false. ; new_data = 2
end function new_data

integer function plot()
integer :: iw
  iw = winio@('%pl[native,frame,gridlines,etched,width=3,link=columns,colour=blue,'//&
                  'dx=5,y_max=1,dy=0.5,x-axis=@,y-axis=@,external,buffered]',&
                   size(arr,kind=3),0.d0,1.d0,arr)
  plot = 2
end function plot

subroutine cocktailsort(arr)
  real*8, intent(inout) :: arr(:)
  integer :: startp, endp, i, k, n
  real*8 :: temp
  logical :: swapped
  n = size(arr) ; startp = 1 ; endp = n ; swapped = .true.
  do while (swapped)
    call temporary_yield@()
    swapped = .false.
    do i = startp, endp - 1, +1
      if (arr(i) > arr(i + 1)) then
        temp = arr(i) ; arr(i) = arr(i + 1) ; arr(i + 1) = temp ; swapped = .true.
        k = plot()  ! update plot after swap
      end if
    end do
    if (.not. swapped) exit
    endp = endp - 1 ; swapped = .false.
    do i = endp, startp + 1, -1
      if (arr(i - 1) > arr(i)) then
        temp = arr(i) ; arr(i) = arr(i - 1) ; arr(i - 1) = temp ; swapped = .true.
        k = plot() ! update plot after swap
      end if
    end do
    startp = startp + 1
  end do
end subroutine cocktailsort
end module demo

winapp
program p
use demo
i = sort()
end program p
23 Jan 2025 1:21 #31850

Paul,

Thanks, the proverbial penny has dropped and I can see the “recipe” now.

24 Jan 2025 10:30 #31853

Paul,

The buffered option works well, I cobbled together a second example yesterday evening, but then got a little stuck when trying to produce a slightly more pleasing appearance for the plot.

This concerns changing the font and size of the font used by the %pl[external].

If I modify the plot function in your example from yesterday to include %fn to change the font and its size via %ts:

integer function plot()
integer :: iw
  iw = winio@('%fn[Consolas]%ts&',1.2d0)   ! ### Does not work here
  iw = winio@('%pl[native,frame,gridlines,etched,width=3,link=columns,colour=blue,'//&
                  'dx=5,y_max=1,dy=0.5,x-axis=@,y-axis=@,external,buffered]',&
                   size(arr,kind=3),0.d0,1.d0,arr)
  plot = 2
end function plot

this approach fails.

If I add a title to the plot I can change its font and size via winop_hdl@ :

integer function plot()
integer :: iw
integer(7) :: font_handle
  iw = winio@('%fn[Consolas]%ts[1.2]%gf%sf&',font_handle)
  call winop_hdl@('%pl[title_hfont]',font_handle)
  iw = winio@('%pl[native,frame,gridlines,etched,width=3,link=columns,colour=blue,'//&
                  'Title='Cocktail sort','//&
                  'dx=5,y_max=1,dy=0.5,x-axis=@,y-axis=@,external,buffered]',&
                   size(arr,kind=3),0.d0,1.d0,arr)
  plot = 2
end function plot

But this leaves the axes numbers and text with very small standard text.

Is there a way to change the axes font and size in a %pl[external]? I cannot find anything in the cwplus.enh related to this.

I seem to recall being defeated on this one before, but never asked the question.

24 Jan 2025 11:49 #31854

Using [external] causes ClearWin+ to call Draw_Simpleplot@ without creating a window or control. The window is created via the %gr and updated via the %pl.

It may be possible to add something to ClearWin+ but at the moment these changes won't work.

It should be possible to use [buffered] without [external] by making your own calls to Draw_Simpleplot@.

25 Jan 2025 6:43 #31856

Ken

Upon reflection, I think that %pl[external] ought to pick up the local font and I will take a look at this.

In the mean time I would expect the font (and background colour) to be picked up by the original %gr.

25 Jan 2025 11:58 #31857

Paul,

Thanks I will experiment with these suggestions. I am rather like a wee bairn with a new toy!

Here’s what I can up with last the other evening which animates a pen plotter or CRO displaying the step response of a second order system.

https://www.dropbox.com/scl/fi/9zy9m3tgpnao0c1a54o6c/Second_order_step_response.f95?rlkey=nhyyj436y2780v57s41ohh0xl&st=kumwh0z5&dl=0

26 Jan 2025 2:07 #31858

It took me a while to realise that the %pl[external,buffered] has only a fleeting existence, being destroyed after the image is copied to the %gr. So calls later to simpleplot_redraw@ do not update the %gr.

Here is a minimum example to show what is required to get this approach to work.

module sin_wave
use clrwin
implicit none
  integer, parameter :: max_size = 100, gw = 900, gh = 500
  real*8,  parameter :: freq = 50.d0, omega = 2.d0*freq*4.d0*atan(1.d0)
  real*8,  parameter :: t_end = 1.d0/freq, dt = t_end / (max_size - 1)
  real*8 :: y(max_size) 
  integer :: i
  integer :: npts
    
contains

  integer function gui()
  integer :: iw
    y =  [(sin(omega*((i - 1) * dt)), i = 1, max_size)]
    iw = winio@('%^bt[Run]%nl&',run_cb)
    iw = winio@('%gr[white]&', gw, gh)
    iw = winio@('')
    gui = 1
  end function gui

  integer function plot()
  integer :: iw
    iw = winio@('%pl[native,n_graphs=1,axes_pen=2,width=3,external,buffered]',& 
                 npts,0.d0,dt,y)
    plot = 2
  end function plot

  integer function run_cb()
  integer :: i, k
    do i = 1, max_size
      npts = i  
      k = plot()
      call sleep@(0.02)
    end do
    run_cb = 2
  end function run_cb
  
end module sin_wave

winapp
program test
use sin_wave,  only : gui
i = gui()
end program test
27 Jan 2025 7:47 #31859

I have fixed the font issue for %pl[external,buffered]. Here is a link to DLLs that includes this single fix.

https://www.dropbox.com/scl/fi/wty9jw7f6o54ynnmyaspk/newDLLs134.zip?rlkey=1cyumj3kxm9qa2kt36tp2zggs&dl=0

The fix allows the font name and font size to be set locally before %pl.

27 Jan 2025 9:30 #31860

Paul, Thanks for your efforts on this. My test code works as expected now.

Please login to reply.