Hi y'all!
I've followed nim for some time now, and when I discovered picostdlib that gave me a good reason to use it trying out stuff with the pico.
I would like to drive different displays with the pico and for that I want to write a small library, as many of the functions needed to draw graphics are the same for different types of displays. This led me to giving concepts a try, and the following is what I'm trying to get to work:
import bitops
# Concept describing a display, analogous to "Indexable" in tests/concepts/tspec.nim
type
AnyDisplay*[T] = concept
proc getPixel(disp: Self; x, y: int) : T
proc setPixel(disp: var Self; x, y: int; color: T)
# Function that should work on any display that implements the concept above
proc drawQuad*[T](disp: AnyDisplay[T]; x, y: int; color: T) =
disp.setPixel(x,y,color)
disp.setPixel(x+1,y,color)
disp.setPixel(x,y+1,color)
disp.setPixel(x+1,y+1,color)
# Implementation for a monochromatic display where one byte holds 8 pixels
const W = 128
const H = 64
type
SSD1309* = object
framebuffer*: array[(H div 8) * W, uint8]
proc getPixel*(disp: SSD1309; x, y: int; color: uint8): uint8 =
var
shift: uint8 = uint8(1 shl bitand(y, 7))
index: int = x + (y div 8) * W
return bitand(disp.framebuffer[index],shift)
proc setPixel*(disp: var SSD1309; x, y: int; color: uint8) =
var
shift: uint8 = uint8(1 shl bitand(y, 7))
index: int = x + (y div 8) * W
if color > 0:
disp.framebuffer[index] = bitor(disp.framebuffer[index],shift)
else:
disp.framebuffer[index] = bitand(disp.framebuffer[index],shift)
var disp: SSD1309
disp.drawQuad(12,34,1)
Unfortunately, this does not compile:
/home/sei/projects/nimoled/test.nim(50, 5) Error: type mismatch
Expression: drawQuad(disp, 12, 34, 1)
[1] disp: SSD1309
[2] 12: int literal(12)
[3] 34: int literal(34)
[4] 1: int literal(1)
Expected one of (first mismatch at [position]):
[1] proc drawQuad[T](disp: AnyDisplay[T]; x, y: int; color: T)
I'm using the nim develop version:
Nim Compiler Version 2.1.1 [Linux: amd64]
Compiled at 2023-10-14
Copyright (c) 2006-2023 by Andreas Rumpf
git hash: f5d70e7fa7195658e3200f71a1653e07fe81275a
active boot switches: -d:release
I guess the problem is that the generic type T is not resolved for the type SSD1309, how can I make that happen? I'd also be happy about any ideas about different ways to do this that might be better.
Are you sure the concept should be at the "Display" level and not at the "Color" level?
For color conversions I've implemented a RGB concept here https://github.com/mratsim/trace-of-radiance/blob/e928285/trace_of_radiance/io/color_conversions.nim#L77-L82
type
RGB_Concept* = concept rgb
## RGB data stored in any order,
## RGB or BGR or ...
rgb.r is uint8
rgb.g is uint8
rgb.b is uint8
to handle various RGB ordering.
The issue with querying or drawing pixel by pixel is that it's very inefficient and cannot use SIMD.
Thank you for your suggestion, but I'm afraid that won't quite work for me:
This library will only be useful embedded programming, and the difficulty with these kinds of displays is that you can have a variety of color types, such as monochromatic with on byte describing 8 pixels, or 16 bit colors where 5 bits are red, 6 are green and 5 are blue. Also for most displays you send the data byte by byte by using a SPI or I2C-Bus, so the drawing and querying is already basically byte by byte.
"Color" level abstraction is useful nevertheless and would be encoded in the generic T.
Ah thanks! Missed that one entiely, I removed the color argument and now the disp: AnyDisplay[T] argument of drawQuad is matched correctly.
Unfortunately, it still does not compile, and fails to match the first argument of setPixel:
/home/sei/projects/nimoled/test.nim(50, 5) template/generic instantiation of `drawQuad` from here
/home/sei/projects/nimoled/test.nim(13, 9) Error: type mismatch
Expression: setPixel(disp, x, y, color)
[1] disp: AnyDisplay[system.uint8]
[2] x: int
[3] y: int
[4] color: uint8
Expected one of (first mismatch at [position]):
[1] proc setPixel(disp: var SSD1309; x, y: int; color: uint8)
Personally I always have better luck with old style concepts
import bitops
# Concept describing a display, analogous to "Indexable" in tests/concepts/tspec.nim
type
AnyDisplay*[T] = concept display
display.getPixel(int, int) is T
display.setPixel(int, int, T)
# Function that should work on any display that implements the concept above
proc drawQuad*[T](disp: var AnyDisplay[T]; x, y: int; color: T) =
mixin setPixel
disp.setPixel(x,y,color)
disp.setPixel(x+1,y,color)
disp.setPixel(x,y+1,color)
disp.setPixel(x+1,y+1,color)
# Implementation for a monochromatic display where one byte holds 8 pixels
const W = 128
const H = 64
type
SSD1309* = object
framebuffer*: array[(H div 8) * W, uint8]
proc getPixel*(disp: SSD1309; x, y: int): uint8 =
var
shift: uint8 = uint8(1 shl bitand(y, 7))
index: int = x + (y div 8) * W
return bitand(disp.framebuffer[index],shift)
proc setPixel*(disp: var SSD1309; x, y: int; color: uint8) =
var
shift: uint8 = uint8(1 shl bitand(y, 7))
index: int = x + (y div 8) * W
if color > 0:
disp.framebuffer[index] = bitor(disp.framebuffer[index],shift)
else:
disp.framebuffer[index] = bitand(disp.framebuffer[index],shift)
var disp: SSD1309
disp.drawQuad(12,34,1)
If you want I have already written several bookstores (display) for rp2040 ... try to take a look if you want