Search code examples
performancelispracket

Running racket/gui at 60fps


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?

#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)

Solution

  • Answered on racket discourse, quotation below:

    "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:

       (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

    [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."