Adding Graphics to uEforth 🙜 February 26, 2022 BACKGROUND 🙜 ⦿ uEforth = ESP32forth ⦿ Colorful pictures make me happy ⦿ uEforth has limited built-ins ( though more than before ) GOALS 🙜 ⦿ Same interface on Windows and Linux ⦿ X11 not used unless graphics is ⦿ Limited memory used unless graphics is ⦿ Rasterize in Forth ⦿ Don't break ESP32forth COMPONENTS 🙜 ⦿ Common graphics state variables ⦿ Common rasterizer ⦿ "Low level" graphics + "Higher level" rasterizer ⦿ Separate Windows/Linux window + event implementation "LOW-LEVEL" INTERFACE Startup: window ( w h -- ) Drawing region: pixel ( x y -- a ) (format [b g r x]) width ( -- n ) height ( -- n ) flip ( -- ) Getting events: wait ( -- ) poll ( -- ) event ( -- n ) Event constants: IDLE RESIZED EXPOSED MOTION PRESSED RELEASED TYPED FINISHED Event info: mouse-x ( -- n ) mouse-y ( -- n ) last-key ( -- n ) last-char ( -- n ) pressed? ( k -- f ) Key/Button constants: LEFT-BUTTON MIDDLE-BUTTON RIGHT-BUTTON "HIGH-LEVEL" INTERFACE Pen: ( $rrggbb ) to color Drawing: box ( x y w h -- ) Transforms: g{ ( -- ) Preserve transform }g ( -- ) Restore transform translate ( x y -- ) scale ( nx dx ny dy -- ) viewport ( w h -- ) vertical-flip ( -- ) Use math style viewport. Conversions: screen>g ( x y -- x' y' ) Transform screen to viewport g{ 640 480 viewport ... \ draw stuff on 640x480 area 100 10 40 20 box ... }g g{ vertical-flip 2000 2000 viewport -1000 -1000 translate ... \ draw stuff in (-1000, 1000) in x, y ... }g g{ ... g{ -100 -100 translate wheel } g{ 100 -100 translate wheel } g{ -100 100 translate wheel } g{ 100 100 translate wheel } }g g{ ... g{ 1 10 1 10 scale disk } g{ 2 10 2 10 scale disk } g{ 3 10 3 10 scale disk } }g IMPLEMENTATION 0 value mouse-x 0 value mouse-y 0 value last-key 0 value last-char 0 value event 0 value width 0 value height 0 value backbuffer : pixel ( w h -- a ) width * + 4* backbuffer + ; : hline { x y w } x y pixel w 1- for color over l! 4 + next drop ; YV(internals, fill32, \ cell_t c = tos; DROP; \ cell_t n = tos; DROP; \ uint32_t *a = (uint32_t *) tos; DROP; \ for (;n;--n) *a++ = c) \ : hline { x y w } x y pixel w color fill32 ; : raw-box { left top w h } left w + top h + { right bottom } left right 2dup min to left max to right top bottom 2dup min to top max to bottom left 0 max to left top 0 max to top right width min to right bottom height min to bottom left right >= top bottom >= or if exit then right left - to w bottom top - to h top h 1- for left over w hline 1+ next drop ; : box { left top w h } left sx * tx + 16 rshift top sy * ty + 16 rshift w sx * 16 rshift h sy * 16 rshift raw-box ; TRANSFORMS create gstack 1024 cells allot gstack value gp : >g ( n -- ) gp ! gp cell+ to gp ; : g> ( -- n ) gp cell - to gp gp @ ; : g{ sx >g sy >g tx >g ty >g ; : }g g> to ty g> to tx g> to sy g> to sx ; : screen>g ( x y -- x' y' ) 16 lshift ty - sy / swap 16 lshift tx - sx / swap ; : translate ( x y -- ) sy * +to ty sx * +to tx ; : scale ( nx dx ny dy -- ) sy -rot */ to sy sx -rot */ to sx ; : viewport { w h } width 2/ height 2/ translate 10000 width height */ 10000 w h */ < if width w width h w */ 1 max h scale else height w h */ 1 max w height h scale then w 2/ negate h 2/ negate translate ; : vertical-flip 0 height 2/ translate 1 1 -1 1 scale 0 height 2/ negate translate ; INTERFACING TO WIN32 / X11 z" libX11.so" shared-library xlib z" XOpenDisplay" 1 xlib XOpenDisplay ( a -- a ) z" XBlackPixel" 2 xlib XBlackPixel ( a n -- n ) z" XWhitePixel" 2 xlib XWhitePixel ( a n -- n ) z" XDisplayOfScreen" 1 xlib XDisplayOfScreen ( a -- a ) z" XScreenOfDisplay" 2 xlib XScreenOfDisplay ( a n -- a ) z" XDefaultColormap" 2 xlib XDefaultColormap ( a n -- n ) z" XDefaultScreen" 1 xlib XDefaultScreen ( a -- n ) z" XRootWindow" 2 xlib XRootWindow ( a n -- n ) ... : window { w h } w 0< if 640 to w 480 to h then NULL XOpenDisplay to display display XDefaultScreen to screen display screen XDefaultColorMap to colormap display screen XDefaultVisual to visual display screen XDefaultDepth to screen-depth display screen XBlackPixel to black display screen XWhitePixel to white display screen XRootWindow to root-window display root-window 0 0 w h 0 black white XCreateSimpleWindow to window-handle display window-handle XMapWindow drop display window-handle 0 NULL XCreateGC to gc display window-handle EVENT-MASK XSelectInput drop 1 1 image-resize ; STRUCTURES struct WINDCLASSA i16 field ->style ptr field ->lpfnWndProc i32 field ->cbClsExtra i32 field ->cbWndExtra ptr field ->hInstance ptr field ->hIcon ptr field ->hCursor ptr field ->hbrBackground ptr field ->lpszMenuName ptr field ->lpszClassName vocabulary xkey also xkey definitions struct XKeyEvent i32 field ->type long field ->serial bool field ->send_event ptr field ->display win field ->window win field ->root win field ->subwindow time field ->time i32 field ->x i32 field ->y i32 field ->x_root i32 field ->y_root i32 field ->state i32 field ->keycode bool field ->same_screen previous definitions variable last-align : typer ( align sz "name" ) create , , does> dup cell+ @ last-align ! @ ; 1 1 typer i8 2 2 typer i16 4 4 typer i32 cell 8 typer i64 cell cell typer ptr long-size long-size typer long variable last-struct : struct ( "name" ) 1 0 typer latestxt >body last-struct ! ; : align-by ( a n -- a ) 1- dup >r + r> invert and ; : struct-align ( n -- ) dup last-struct @ cell+ @ max last-struct @ cell+ ! last-struct @ @ swap align-by last-struct @ ! ; : field ( n "name" ) last-align @ struct-align create last-struct @ @ , last-struct @ +! does> @ + ; WINDOWS CALLBACKS static LRESULT WindowProcShim(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (msg == WM_NCCREATE) { SetWindowLongPtr( hwnd, GWLP_USERDATA, (LONG_PTR) ((CREATESTRUCT *) lParam)->lpCreateParams); } if (!GetWindowLongPtr(hwnd, GWLP_USERDATA)) { return DefWindowProc(hwnd, msg, wParam, lParam); } cell_t stacks[STACK_CELLS * 3 + 4]; cell_t *at = stacks; at += 4; float *fp = (float *) (at + 1); at += STACK_CELLS; cell_t *rp = at + 1; at += STACK_CELLS; cell_t *sp = at + 1; at += STACK_CELLS; cell_t *ip = (cell_t *) GetWindowLongPtr(hwnd, GWLP_USERDATA); cell_t tos = 0; DUP; tos = (cell_t) hwnd; DUP; tos = (cell_t) msg; DUP; tos = (cell_t) wParam; DUP; tos = (cell_t) lParam; PARK; rp = forth_run(rp); UNPARK; return tos; } pad WINDCLASSA erase WindowProcShim pad ->lpfnWndProc ! hinstance pad ->hInstance ! GrfClassName pad ->lpszClassName ! NULL IDC_ARROW LoadCursorA pad ->hCursor ! hinstance IDI_MAIN_ICON LoadIconA pad ->hIcon ! pad RegisterClassA to GrfClass 0 GrfClass GrfWindowTitle WS_OVERLAPPEDWINDOW CW_USEDEFAULT CW_USEDEFAULT width height NULL NULL hinstance ['] GrfWindowProc callback CreateWindowExA to hwnd 79 windows/windows_console.fs 68 windows/windows_core.fs 77 windows/windows_files.fs 74 windows/windows_gdi.fs 360 windows/windows_messages.fs 73 windows/windows_test.fs 173 windows/windows_user.fs 178 windows/grf.fs 81 common/grf.fs 100 common/grf_utils.fs 1263 total 213 posix/x11.fs 171 posix/grf.fs 81 common/grf.fs 100 common/grf_utils.fs 565 total HEARTS Hx(t) = 16 * sin^3(t) Hy(t) = 13 * cos(t) - 5 * cos(2t) - 2 * cos(3t) - cos(4t) @heart1.png : heart-f ( f: t -- x y ) fdup fsin 3e f** 16e f* fswap fdup fcos 13e f* fover 2e f* fcos 5e f* f- fover 3e f* fcos 2e f* f- fswap 4e f* fcos f- ; 4000 constant heart-steps 1024 constant heart-size create heart-start heart-size allot create heart-end heart-size allot heart-start heart-size 0 fill heart-end heart-size 0 fill | | [--] | [----] |[-------] [--------] [-------] [------] [----] [--] [-] [] | : cmin! ( n a ) dup >r c@ min r> c! ; : cmax! ( n a ) dup >r c@ max r> c! ; : heart-initialize heart-start heart-size 7 29 */ 128 fill heart-end heart-size 7 29 */ 128 fill heart-steps 0 do i s>f heart-steps s>f f/ pi f* heart-f fnegate 12e f+ 29.01e f/ heart-size s>f f* fswap 16e f* f>s f>s 2dup heart-start + cmin! heart-end + cmax! loop heart-size 0 do heart-end i + c@ heart-start i + c@ - heart-end i + c! loop ; 512 29 32 */ constant heart-ratio : raw-heart 0 { x y sx sy r } heart-start c@ 0= if heart-initialize then y sy 2/ - to y sy 0< if y sy + to y sy abs to sy then sy 0 do i heart-size sy */ to r x heart-start r + c@ sx heart-ratio */ + y i + heart-end r + c@ sx heart-ratio */ 1 raw-box x heart-start r + c@ heart-end r + c@ + sx heart-ratio */ - y i + heart-end r + c@ sx heart-ratio */ 1 raw-box loop ; QUESTIONS? ⚘ Thank you!