# Running racket/gui at 60fps

**URL:** <https://racket.discourse.group/t/running-racket-gui-at-60fps/2276>\
**Category:** Questions & Answers\
**Tags:** question, gui\
**Created:** [September 2, 2023, 12:44pm UTC](https://racket.discourse.group/t/running-racket-gui-at-60fps/2276 "2023-09-02T12:44:00Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![PastAghast](https://avatars.discourse-cdn.com/v4/letter/p/7bcc69/32.png) [@PastAghast](https://racket.discourse.group/u/PastAghast)\
**Post date:** [September 2, 2023, 12:44pm UTC](https://racket.discourse.group/t/running-racket-gui-at-60fps/2276/1 "2023-09-02T12:44:00Z")

</div>

I'm working on my first project in Racket. I think it's awesome that the language includes a cross platform GUI toolkit, and I'l like to wrap my head around how to make it perform well.

I tried making a small canvas that draws a grid of white and black squares which get randomized every frame. When you run the program below and press a key on your keyboard, it quits and dumps out the overall measured FPS.

In its current state it runs on my macbook M1 at ~20fps. I'd really like to be able to run at least 60 fps. Does anyone have some tips to help me get there?

```scheme
#lang racket

(require racket/gui)
(require racket/random)

(define *width* 64)
(define *height* 32)
(define *scale* 10)
(define *counted-frames* 0)
(define *start-time* 0)
(define *end-time* 0)

(define *timer*
  (new timer%
       [notify-callback
        (lambda ()
          (update-grid!)
          (send *canvas* refresh-now)
          (flush-output))]))

(define *grid*
  (for/vector ([i (in-range (* *height* *width*))])
    #f))

(define (update-grid!)
  (for ([i (vector-length *grid*)])
    (if (zero? (random 2))
        (vector-set! *grid* i #f)
        (vector-set! *grid* i #t))))

(define (sample-grid)
  (for ([i (in-range 10)])
    (println (vector-ref *grid* i))))

(define (get-element x y)
  (vector-ref *grid* (+ (* y *width*) x)))

(define *frame*
  (new frame% [label "Checkboard Grid"]
       [width (* *scale* *width*)]
       [height (* *scale* *height*)]))

(define rect-canvas%
  (class canvas%
    (inherit get-dc)
    (super-new)
    (define/override (on-char event)
      (begin
             (fprintf (current-error-port)
                      "Received key event: ~a, quitting\n" event)
             (set! *end-time* (current-inexact-milliseconds))
             (println (format "FPS: ~a"
                              (/ (* 1000 *counted-frames*)
                                 (- *end-time* *start-time*))))
             (exit)))
    (define/override (on-paint)
      (set! *counted-frames* (+ 1 *counted-frames*))
      (let ((dc (get-dc)))
        (for* ([x (in-range *width*)] [y (in-range *height*)])
          (if (get-element x y)
              (begin
                (send dc set-pen "black" 1 'solid)
                (send dc set-brush "black" 'solid))
              (begin
                (send dc set-pen "white" 1 'solid)
                (send dc set-brush "white" 'solid)))
          (send dc draw-rectangle (* x 10) (* y 10) 10 10))))))

(define *canvas* (new rect-canvas% [parent *frame*]))

(define (run)
  (set! *start-time* (current-inexact-milliseconds))
  (send *frame* show #t)
  (send *timer* start 1))

(run)

```

---

<div class="post-metadata">

**Author:** ![soegaard](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/soegaard/32/19_2.png) [@soegaard](https://racket.discourse.group/u/soegaard)\
**Post date:** [September 2, 2023, 3:42pm UTC](https://racket.discourse.group/t/running-racket-gui-at-60fps/2276/2 "2023-09-02T15:42:18Z")

</div>

I don't think, you'll see much higher fps without doing less work in `on-paint`.

This might give you a small gain though:

```scheme
   (define black-pen (new pen% [color "black"] [width 1] [style 'solid]))
    (define white-pen (new pen% [color "white"] [width 1] [style 'solid]))
    (define black-brush (new brush% [color "black"] [style 'solid]))
    (define white-brush (new brush% [color "white"] [style 'solid]))
    (define dc (get-dc))
    (define/override (on-paint)
      (set! *counted-frames* (+ 1 *counted-frames*))
      (for* ([x (in-range *width*)] [y (in-range *height*)])
        (if (get-element x y)
            (begin
              (send dc set-pen black-pen)
              (send dc set-brush black-brush))
            (begin
              (send dc set-pen white-pen)
              (send dc set-brush white-brush)))
        (send dc draw-rectangle (* x 10) (* y 10) 10 10)))

```

If your game is a retro game with 10x10 "pixels", then you can get a speedup  
by having a bitmap with a low resolution, say, 100x80 and then  
in `on-paint` finish by drawing a scaled copy to the bitmap to the screen.

This technique is used in here:

[https://github.com/soegaard/sketching/blob/main/sketching-examples/examples/pacman/maze-game.rkt](https://github.com/soegaard/sketching/blob/main/sketching-examples/examples/pacman/maze-game.rkt)

[It's not 60 fps though.]

If that's not fast enough, you'll need to look at how to use the GPU - either  
directly or using one of the available libraries.

---

<div class="post-metadata">

**Author:** ![PastAghast](https://avatars.discourse-cdn.com/v4/letter/p/7bcc69/32.png) [@PastAghast](https://racket.discourse.group/u/PastAghast)\
**Post date:** [September 2, 2023, 5:26pm UTC](https://racket.discourse.group/t/running-racket-gui-at-60fps/2276/3 "2023-09-02T17:26:30Z")

</div>

I am indeed working on retro gaming stuff, I'm finally getting around to writing a CHIP-8 emulator and learning Racket, both of which I've been putting off for years.

The game source you linked is an extremely helpful, thanks so much for sharing it! And your suggestion gave me a nice performance bump to \>40fps. I'll give the bitmap approach a try and see how that works. I figured someone with more graphics know-how would help me find some low hanging fruit 🙂

Even if I don't quite hit 60 fps, I still think I would rather use the built in GUI than reach for external libraries in this case. I actually had looked at a CL implementation of a chip-8 emulator that I couldn't run on my system because one of the dependencies does not build on aarch64, and I would rather avoid creating a similar situation for my project if possible. I am a big fan of the "just works" factor that you get with Racket's built in goodies, it strikes me as one of the defining advantages of the language that you don't really get elsewhere.

---

<div class="post-metadata">

**Author:** ![LiberalArtist](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/liberalartist/32/151_2.png) [@LiberalArtist](https://racket.discourse.group/u/LiberalArtist)\
**Post date:** [September 2, 2023, 8:04pm UTC](https://racket.discourse.group/t/running-racket-gui-at-60fps/2276/4 "2023-09-02T20:04:12Z")

</div>

You might be interested in some of @jeapostrophe’s gaming projects, e.g.:

[![](https://global.discourse-cdn.com/free1/uploads/racket/original/2X/2/205df15c26501a236d23fb7c30fb64be085e5d8c.jpeg "(fourth RacketCon): Jay McCarthy — Get Bonus! Infinite Functional Entertainment at 60 FPS!") ](https://www.youtube.com/watch?v=_x0Ob2HY8C4)

---

<div class="post-metadata">

**Author:** ![gus-massa](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/gus-massa/32/507_2.png) [@gus-massa](https://racket.discourse.group/u/gus-massa)\
**Post date:** [September 3, 2023, 4:05pm UTC](https://racket.discourse.group/t/running-racket-gui-at-60fps/2276/5 "2023-09-03T16:05:56Z")

</div>

I doubled the fps (from 8 to 15) skipping the squares that didn't change. I added `[style (list 'no-autoclear)]` to the canvas, and save the last color in `grid/old`.

In this case that is random, half of the squares can be skipped (In average). In a game with a fixed background this can be better, but in a game with a lot of scrolling and colors it may be worse.

```scheme
#lang racket

(require racket/gui)
(require racket/random)

(define *width* 64)
(define *height* 32)
(define *scale* 10)
(define *counted-frames* 0)
(define *start-time* 0)
(define *end-time* 0)

(define *timer*
  (new timer%
       [notify-callback
        (lambda ()
          (update-grid!)
          (send *canvas* refresh-now)
          (flush-output))]))

(define *grid*
  (for/vector ([i (in-range (* *height* *width*))])
    (void)))

(define *grid/old*
  (for/vector ([i (in-range (* *height* *width*))])
    (void)))

(define (update-grid!)
  (for ([i (vector-length *grid*)])
    (if (zero? (random 2))
        (vector-set! *grid* i #f)
        (vector-set! *grid* i #t))))

(define (sample-grid)
  (for ([i (in-range 10)])
    (println (vector-ref *grid* i))))

(define (get-element x y)
  (vector-ref *grid* (+ (* y *width*) x)))

(define (get-element/old x y)
  (vector-ref *grid/old* (+ (* y *width*) x)))

(define (set-element/old! x y v)
  (vector-set! *grid/old* (+ (* y *width*) x) v))

(define *frame*
  (new frame% [label "Checkboard Grid"]
       [width (* *scale* *width*)]
       [height (* *scale* *height*)]))

(define rect-canvas%
  (class canvas%
    (inherit get-dc)
    (super-new [style (list 'no-autoclear)])
    (define/override (on-char event)
      (begin
             (fprintf (current-error-port)
                      "Received key event: ~a, quitting\n" event)
             (set! *end-time* (current-inexact-milliseconds))
             (println (format "FPS: ~a"
                              (/ (* 1000 *counted-frames*)
                                 (- *end-time* *start-time*))))
              (exit)))
    (define black-pen (new pen% [color "black"] [width 1] [style 'solid]))
    (define white-pen (new pen% [color "white"] [width 1] [style 'solid]))
    (define black-brush (new brush% [color "black"] [style 'solid]))
    (define white-brush (new brush% [color "white"] [style 'solid]))
    (define dc (get-dc))
    (define/override (on-paint)
      (set! *counted-frames* (+ 1 *counted-frames*))
      (for* ([x (in-range *width*)] [y (in-range *height*)])
        (unless (eq? (get-element x y) (get-element/old x y))
          (set-element/old! x y (get-element x y))
          (if (get-element x y)
              (begin
                (send dc set-pen black-pen)
                (send dc set-brush black-brush))
              (begin
                (send dc set-pen white-pen)
                (send dc set-brush white-brush)))
          (send dc draw-rectangle (* x 10) (* y 10) 10 10))))))

(define *canvas* (new rect-canvas% [parent *frame*]))

(define (run)
  (set! *start-time* (current-inexact-milliseconds))
  (send *frame* show #t)
  (send *timer* start 1))

(run)
``
```

---

<div class="post-metadata">

**Author:** ![bogdan](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/bogdan/32/8_2.png) [@bogdan](https://racket.discourse.group/u/bogdan)\
**Post date:** [September 3, 2023, 5:48pm UTC](https://racket.discourse.group/t/running-racket-gui-at-60fps/2276/6 "2023-09-03T17:48:16Z")

</div>

Here is a version that hits 60 FPS on my machine:

```scheme
#lang racket/base

(require racket/fixnum
         racket/gui)

(define columns 64)
(define rows 32)
(define scale 10)
(define ticks 0)
(define grid
  (make-fxvector (fx* rows columns)))
(define (update-grid!)
  (for ([i (in-range (fxvector-length grid))])
    (fxvector-set! grid i (if (zero? (random 2)) 0 1))))
(define frame
  (new frame%
       [label "Checkboard Grid"]
       [width (fx* scale columns)]
       [height (fx* scale rows)]))
(define canvas
  (new
   (class canvas%
     (inherit get-dc)
     (super-new [parent frame] [style '(no-autoclear)])
     (define/override (on-char event)
       (fprintf (current-error-port) "Received key event: ~a, quitting\n" event)
       (define end-time (current-inexact-milliseconds))
       (println (format "FPS: ~a" (/ (* 1000 ticks) (- end-time start-time))))
       (send frame show #f))
     (define dc (get-dc))
     (send dc set-background "white")
     (send dc set-pen "black" 0 'transparent)
     (send dc set-brush "black" 'solid)
     (define/override (on-paint)
       (send dc clear)
       (for ([(v i) (in-indexed (in-fxvector grid))]
             #:when (fx= v 1))
         (define x (fxremainder i columns))
         (define y (fxquotient i columns))
         (send dc draw-rectangle (fx* x scale) (fx* y scale) scale scale))))))
(define start-time
  (current-inexact-milliseconds))
(send frame show #t)
(void
 (thread
  (lambda ()
    (let tick ()
      (update-grid!)
      (send canvas refresh-now)
      (set! ticks (fx+ ticks 1))
      (sleep 0)
      (tick)))))

```

The biggest win (over @gus-massa's version) comes from using a transparent pen. The rest of the changes are mostly golfing and don't account for much.

---

<div class="post-metadata">

**Author:** ![ryanc](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/ryanc/32/71_2.png) [@ryanc](https://racket.discourse.group/u/ryanc)\
**Post date:** [September 3, 2023, 6:13pm UTC](https://racket.discourse.group/t/running-racket-gui-at-60fps/2276/7 "2023-09-03T18:13:15Z")

</div>

I tried a couple different approaches, and I got the biggest speedup in reported FPS by creating a white and black `dc-path%`s, adding rectangles to those, and then drawing the paths with `draw-path`. Adding a bitmap between the dc-paths and the canvas added a few FPS on top of that.

The original ran at about 14 FPS for me. With the dc-path approach the program reported about 93 FPS. However, by watching the window, Racket was clearly not actually displaying every "frame"; in fact, it appeared to be updating more slowly than it did with approaches like @soegaard's. So I think that there are limitations to Racket's canvas flushing that are going to limit your actual FPS beyond the work you do in `on-paint`.

On the other other hand, I notice you are running the timer as fast as you can (1ms intervals). If I change that to 10ms, I get a reported 50 FPS that looks like it might actually be 50 FPS, with fairly little stutter. I always got always awful stutter in all of the approaches running under a 1ms timer. I thought maybe it was due to GC pauses, but now I think it might be scheduler-related instead.
