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