Carpe diem, c'est la vie.
Posts: 6,433
Karma: 10773670
Join Date: Nov 2011
Location: Multiverse 6627A
Device: K1 to PW3
|
hoser-2.0 - eink demo, all-new dither, K3 speed++
MAJOR UPDATE:This version adds the eink update method used in sparkle-1.0 (which is WHY it was so fast). On a K3 there is such a HUGE speed increase, it deserved a major version number upgrade. K3 owners - rejoice!
All new eink dither routines based on total redesign to support hybrid K4 main boot eink mode (K3 eink drivers but 8-bit framebuffer). These dither routines are optimized to dither an entire framebuffer (not just a moving object), so every time you see any movement on the display, that was just the result of an entire screen of dithered pixels being created from the image in /tmp/wb0.
This demo runs full throttle with no delays, so it shows some eink artifacts ("ball in tube" after-images on newer kindles), but it is an interesting effect. The older "non-Pearl" eink display on the DX shows a DIFFERENT "ball in tube" artifact that leaves a white spot ghosted on the surface of the hoses (caused by a sudden black-to-white transition). The speed of this demo is limited ONLY by the system eink update logic. The dither routines can go a lot faster than the eink display.
EDIT: The K5(touch) is MUCH faster than the K4(booted from main), and the K3 is much slower than the K4 (UPDATE: hoser-2.0 runs about the same speed on the K3 and K4), and the DX is even slower than that. You really should see it hustle on the K5(touch).
This program writes all updates to an 8-bit virtual framebuffer using 256 shades of gray. While it is running, you can use dd to copy 256-color screenshots from /tmp/wb0, or you can copy dither screenshots from /dev/fb0. The virtual framebuffer (wb0 work buffer) is updated as fast as the program can fill it, but this will be speed-limited in future code.
The screenshots:Spoiler:
Hoser /dev/fb0:
Hoser /tmp/wb0:

The source code:
Spoiler:
PHP Code:
//==================================================== // hoser 2.0 - eink demo with all-new dither design // (yet another "dithermation" demo by geekmaster) // Copyright (C) 2012 by geekmaster, with MIT license: // http://www.opensource.org/licenses/mit-license.php //---------------------------------------------------- // This version adds the eink update method used in // sparkle-1.0 (which is WHY it was so fast). On a K3 // there is such a HUGE speed increase, it deserved a // major version number upgrade. K3 owners - rejoice! // This was tested on DX,DXG,K3,K4(Mini),K5(Touch). //----------------------------------------------------
#include <stdio.h> // printf #include <stdlib.h> // malloc, free #include <string.h> // memset, memcpy #include <unistd.h> // usleep #include <fcntl.h> // open, close, write #include <time.h> // time #include <sys/mman.h> // mmap, munmap #include <sys/ioctl.h> // ioctl #include <linux/fb.h> // screeninfo
#define FBIO_EINK_UPDATE_DISPLAY_AREA 0x46dd enum GMLIB_op { GMLIB_INIT,GMLIB_CLOSE,GMLIB_UPDATE }; typedef unsigned char u8; typedef unsigned int u32; // function prototypes void circle(int,int,int); int gmlib(int); inline void setpx(int,int,int); void d4w(void); void d8w(void); void d8b(void);
// gmlib global vars static u8 dt[64] = { 3,129,34,160,10,136,42,168,192,66,223,97,200,73,231,105,50, 176,18,144,58,184,26,152,239,113,207,81,247,121,215,89,14, 140,46,180,7,133,38,164,203,77,235,109,196,70,227,101,62,188, 30,156,54,180,22,148,251,125,219,93,243,117,211,85 }; // 0-255 dither table u8 *fb0=NULL; // framebuffer pointer u8 *wb=NULL; // workbuffer pointer u32 mpu=100; // msec/update int fdFB=0; // fb0 file descriptor int fdWB=0; // wb file descriptor u32 fs=0; // fb0 stride u32 MX=0; // xres (visible) u32 MY=0; // yres (visible) u8 blk=0; // black u8 msk=0; // black mask u8 ppb=0; // pixels per byte
//=============================================== // hoser - eink demo showing all-new dither design // This works on all kindle eink models. Enjoy! //----------------------------------------------- void hoser(void) { u32 x,y,c,px1,py1,vx1,vy1,dx,dy,cc,cu,cl; gmlib(GMLIB_INIT); // init geekmaster functions c=0,px1=MX/2,py1=MY/2,vx1=1,vy1=5,cc=31,cl=16; for (cu=0;cu<20000;cu++) { if (0==cu%3000) { // periodic background display for (y=0; y<=MY/2; y++) for (x=0; x<=MX/2; x++) { dx=MX/2-x; dy=MY/2-y; c=255-(dx*dx+dy*dy)*255/(MX*220); setpx(x,y,c); setpx(MX-x,y,c); setpx(x,MY-y,c); setpx(MX-x,MY-y,c); } } circle(px1,py1,30); circle(px1,py1,31); circle(px1,py1,32); circle(px1,py1,29); circle(px1,py1,28); circle(px1,py1,27); circle(px1,py1,26); circle(px1,py1,25); circle(px1,py1,24); circle(px1,py1,23); circle(px1,py1,22); circle(px1,py1,21); circle(px1,py1,20); circle(px1,py1,19); circle(px1,py1,18); circle(px1,py1,17); circle(px1,py1,16); circle(px1,py1,15); circle(px1,py1,14); circle(px1,py1,13); circle(px1,py1,12); px1+=vx1; py1+=vy1; if (px1>MX-40 || px1<40) vx1=-vx1; if (py1<40) { py1=40; vy1=-vy1; } if (py1>MY-40) { py1=MY-40; vy1=-vy1; } if (0==cu%cl) { vy1++; gmlib(GMLIB_UPDATE); // update display } cc=(cc+4)%256; // cycle big box color }
// cleanup - close and free resources gmlib(GMLIB_CLOSE); // close geekmaster functions }
//==================================== // gmlib - geekmaster function library // op (init, update, close) //------------------------------------ int gmlib(int op) { struct update_area_t { int x1,y1,x2,y2,fx; u8 *buffer; } ua; struct fb_var_screeninfo screeninfo; static int fdUpdate=-1; if (GMLIB_INIT==op) { fdWB=open("/tmp/wb0",O_RDWR|O_CREAT); // work framebuffer fdFB=open("/dev/fb0",O_RDWR); // eink framebuffer ioctl(fdFB,FBIOGET_VSCREENINFO,&screeninfo); ppb=8/screeninfo.bits_per_pixel; // pixels per byte fs=screeninfo.xres_virtual/ppb; // fb0 stride blk=screeninfo.rotate-1; // black MX=screeninfo.xres; // max X+1 MY=screeninfo.yres; // max Y+1 msk=1/ppb-1; // black mask (4-bit=255,8-bit=0) fb0=(u8 *)mmap(0,MY*fs,PROT_READ|PROT_WRITE,MAP_SHARED,fdFB,0); // map fb0 lseek(fdWB,MY*MX-1,SEEK_SET); write(fdWB,"",1); // create work buffer file wb=(u8 *)mmap(0,MY*MX,PROT_READ|PROT_WRITE,MAP_SHARED,fdWB,0); // map wb fdUpdate=open("/proc/eink_fb/update_display",O_WRONLY); } else if (GMLIB_CLOSE==op) { gmlib(GMLIB_UPDATE); // update display munmap(fb0,MY*fs); // unmap fb0 munmap(wb,MY*MX); // unmap wb close(fdUpdate); // close update proc close(fdFB); // close fb0 close(fdWB); // close wb } else if (GMLIB_UPDATE==op) { if (ppb/2) { d4w(); ua.x1=0; ua.y1=0; ua.x2=MX; ua.y2=MY; ua.fx=0; ua.buffer=NULL; ioctl(fdFB, FBIO_EINK_UPDATE_DISPLAY_AREA, &ua); // fx_update_partial } else if (blk) { d8w(); write(fdUpdate,"1\n",2); } else { d8b(); system("eips ''"); } } else { return -1; } return fdUpdate; }
//======================================== // setpx - draw pixel to 8-bit work buffer // x,y:screen coordinates, c:color(0-255). //---------------------------------------- inline void setpx(int x,int y,int c) { wb[y*MX+x]=c; }
//=========================== // d8b - dither 8-bit black 0 //--------------------------- void d8b(void) { u8 *pi,*po; int x,y; pi=wb; po=fb0; for (y=0;y<MY;y++) { for (x=0;x<MX;x++) { *po++=dt[(y&7)*8|x&7]-*pi++>>8; } po+=(fs-MX); } }
//=========================== // d8w - dither 8-bit white 0 //--------------------------- void d8w(void) { u8 *pi,*po; int x,y; pi=wb; po=fb0; for (y=0;y<MY;y++) { for (x=0;x<MX;x++) { *po++=~(dt[(y&7)*8|x&7]-*pi++>>8); } po+=(fs-MX); } }
//=========================== // d4w - dither 4-bit white 0 //--------------------------- void d4w(void) { u8 *pi,*po; int x,y,ys; pi=wb; po=fb0; for (y=0;y<MY;y++) { ys=(y&7)*8; for (x=0;x<MX;x+=8) { *po++=(~(dt[ys]-*pi++>>8)|15)&(~(dt[ys+1]-*pi++>>8)|240); *po++=(~(dt[ys+2]-*pi++>>8)|15)&(~(dt[ys+3]-*pi++>>8)|240); *po++=(~(dt[ys+4]-*pi++>>8)|15)&(~(dt[ys+5]-*pi++>>8)|240); *po++=(~(dt[ys+6]-*pi++>>8)|15)&(~(dt[ys+7]-*pi++>>8)|240); } } }
//============================================== // circle - optimized midpoint circle algorithm //---------------------------------------------- void circle(int cx,int cy,int r) { int e=-r,x=r,y=0; while (x>y) { setpx(cx+y,cy-x,255); setpx(cx+x,cy-y,159); setpx(cx+x,cy+y,95); setpx(cx+y,cy+x,31); setpx(cx-y,cy+x,0); setpx(cx-x,cy+y,63); setpx(cx-x,cy-y,127); setpx(cx-y,cy-x,191); e+=y; y++; e+=y; if (e>0) { e-=x; x-=1; e-=x; } } }
//================== // main - start here //------------------ int main(int argc,char **argv) { if (argc>1) { mpu=atoi(argv[1]); } hoser(); // do the hoser demo :D return 0; }
Enjoy and learn!
EDIT: This demo really needs to be viewed on a K5(Touch) to appreciate the speed of my new dither routines. Somebody should post a youtube video showing it running on a K5. I will give a bunch karma points for a youtube link.
NOTE: I just realized that the gmlib CLOSE function should probably delete the workbuffer file from RAMdisk (rm /tmp/wb0). It is consuming nearly 1MB of RAM on a 1200x824 DX or DXG, and they only have 128MB of RAM. Newer kindles use a smaller workbuffer and have more RAM, so not much of a problem. Oh well, the next version will do that if I do not forget... ...But if the hoser program *had* deleted that file, then I would not have been able to get that "last frame" screenshot shown in the screenshots spoiler above. I could have gotten an "in progress" image while it was running though. 
EDIT: I just decided to give you a little bonus golden nugget of knowledge. I will let you in on a "little secret" about how I derived such simple dither logic formulas that work for all eink kindles including the weird hybrid K4 main boot mode. The "magic sauce" has TWO names: Karnaugh Maps, and DeMorgan's Theorems (and a weekend of intense perseverence and little sleep).
Last edited by geekmaster; 04-24-2012 at 02:22 AM.
|