Tiny bitfield based text renderer
source link: https://www.onirom.fr/wiki/blog/25-09-2022_tiny_bitfield_based_text_renderer/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Tiny bitfield based text renderer
Tiny bitfield based text renderer
Introduction
Result
What is ORZ ?
Font / Typeface
The case of stylized fonts
Bit field
11-bit rows packing
11101110110 -> 1910
10101000010 -> 1346
11101000011 -> 1859
9-bit rows packing
111111110 -> 510
101100010 -> 354
111100011 -> 483
(483 << 18) ∨ (354 <<
9) ∨ 510
Accessing data
Alternatives
Usage in < 256 bytes intros
mov
cl,30 ;
sigma of bit-glyphs
a:mov eax,171445ddh ; logo
10111000101000100010111011101b
shr
eax,cl ; bt
eax,ecx; ^reversed logo 29-bit bitmask%10
salc
; xor al,al=ascii(NUL)~space glyph=mov al,20h
jnc
c
; cf=block (ie: non-space)
mov al,0dbh
; block glyph
c:int
29h
; fast putch(al)
mov
al,cl ;
al=cl
aam
0ah
; =aam 10=>ah=cl/10 al=cl%10
jnz
e
; time for crlf-2*rows ?
mov w[fs:450h],ax ; bda curs pos
col=[40:50h]=cx%10 row=[40:51h]=int(cx/10)
e:loop
a
; process 30 bi
Renderer : Introduction
Renderer #1
- with rows as three separate values (with spaces between glyph)
- non generic variant with single 32-bit value (a row is encoded as 11-bit values which include spaces; the last glyph block will be missed which is okay for some set of glyph)
- with single 32-bit value (a row is encoded as 9-bit values which does not include spaces)
function setup() {
pixelDensity(1)
createCanvas(960, 540)
background(0)
}
function draw() {
loadPixels()
const rowHeight = 90
// row area to scan
const pstart = width * 4 * 32
const pend = pstart+(width * rowHeight)
// iterate over a whole row
for (let i = pstart; i < pend; i += 1) {
const xOffset = 128
const x = i % width - xOffset
const bi = x >> 6 // bit field index
// 1.
// string rows
const line1 = 887
const line2 = 533
const line3 = 1559
// get a row bit from given index and make it
either white (256) or black (0) with << 8 (note : in C this
would be a multiply because of overflowing issues, there is also a
trick by using negative numbers instead of a multiply)
b1 = ((line1 >> bi) & 1) << 8
b2 = ((line2 >> bi) & 1) << 8
b3 = ((line3 >> bi) & 1) << 8
// 2. non generic variant with single 32-bit
value
// only works if you don't
care about the last glyph block
/*
let logo1 = 2245045111
b1 = (((logo1 & 2047) >> bi) & 1) <<
8
b2 = ((((logo1 >> 11) & 2047) >> bi)
& 1) << 8
b3 = ((((logo1 >> 22) & 2047) >> bi)
& 1) << 8
*/
// 3. generic with single 32-bit value
// not encoding spaces
/*
let logo2 = 104667903
b1 = (((logo2 & 511) >> bi) & 0x1)
<< 8
b2 = ((((logo2 >> 9) & 511) >> bi) &
0x1) << 8
b3 = ((((logo2 >> 18) & 511) >> bi)
& 0x1) << 8
*/
// first row
let index = i * 4
pixels[index + 0] = b1
pixels[index + 1] = b1
pixels[index + 2] = b1
// second row
index += width * rowHeight * 4
pixels[index + 0] = b2
pixels[index + 1] = b2
pixels[index + 2] = b2
// third row
index += width * rowHeight * 4
pixels[index + 0] = b3
pixels[index + 1] = b3
pixels[index + 2] = b3
}
updatePixels()
}
const logo = 104667903
const rowHeight = 9 << 3 // row height is based on row
iteration value (step) to optimize
const pstart = 128 * width
const pend = pstart + (rowHeight * width)
for (let row = 0; row <= 18; row += 9) {
const rowData = (logo >> row) & 511
for (let i = pstart; i < pend; i += 1) {
const x = i % width
if (x % 192) {
const bi = x >> 6
const br = ((rowData >> bi) &
1) * 255
const xp = 192
const yp = (row << 3) *
width
const index = (i + xp + yp) * 4
pixels[index + 0] = br
pixels[index + 1] = br
pixels[index + 2] = br
}
}
}
Renderer #2
function setup() {
pixelDensity(1)
createCanvas(960, 540)
background(0)
}
function draw() {
loadPixels()
// bit field data
let logo = 105684975
// text position
const offsetX = 128
const offsetY = 128
let ox = offsetX + offsetY * width
// some constants
const blockWidth = 64
const blockHeight = 64
const glyphWidth = 64 * 4
const glyphHeight = 64 * 3
// iterate until there is no glyph
while (logo > 0) {
// iterate whole glyph on screen
for (let i = 0; i < glyphWidth * glyphHeight;
i += 1) {
const x = i & 255
const y = i >>
8
const bi = x >>
6
const bbi = bi + (y
>> 6) * 3 // bit field bit index
const br = ((logo
>> bbi) & 0x1) * 255 // get glyph bit from given index
if (bi < 3) { //
spacing
const index
= (ox + x + y * width) * 4
pixels[index
+ 0] = br
pixels[index
+ 1] = br
pixels[index
+ 2] = br
}
}
// go to the next glyph
logo >>= 9
ox += glyphWidth
}
updatePixels()
}
int width = 960;
int offsetX = 128;
int offsetY = 128;
int ox = offsetX + offsetY * width;
unsigned int logo = 4160300832;
int blockWidth = 64;
int glyphHeight = 64 * 3;
for (int o = ox; o < offsetX + offsetY * width + blockWidth *
11; o += blockWidth) {
for (int y = 0; y < glyphHeight; y += 1) {
int bi = y >> 6;
for (int x = 0; x < blockWidth; x += 1) {
int br = -((logo << bi)
>> 31);
int index = o + x + y * width;
buffer[index] = br;
}
}
logo <<= 3;
if (!(unsigned char)o) {
o += blockWidth;
}
}
x86 implementation
mov esi,4159852416 ; bit field
logo
mov eax,128 + 128 * width
b:
mov ch,64*3-1
mov edx,esp
gh:
push 64
pop edi
bw:
mov cl,ch
shr cl,6
mov ebx,esi
sal ebx,cl
sar ebx,31
; (o + x) * 4 + y * width
lea ebp,[edi + eax]
mov dword [edx + ebp * 4],ebx
dec edi
jns short bw
add edx,1920*4
dec ch
jnz short gh
test al,al
jne cont1
add eax,64
cont1:
add eax,64
sal esi,3
jnz short b
Using extensions
mov esi,4159852416 ; bit
field logo
mov ebx,128 + 128 * width
b:
mov al,64*3-1
mov edx,esp
gh:
mov cl,64
bw:
mov ebp,eax
shr ebp,6
shlx edi,esi,ebp
sar edi,31
; (o + x) * 4 + y * width
lea ebp,[ecx + ebx]
mov dword [edx + ebp * 4],edi
loopne bw
add edx,1920*4
dec al
jnz short gh
test bl,bl
jne cont1
add ebx,64
cont1:
add ebx,64
sal esi,3
jnz short b
Smallest "generic" so far (59 bytes)
mov esi,32657391 ; bit field
logo
mov ebx,128 + 128 * width
b:
mov al,64*3-1
mov edx,esp
gh:
mov cl,64
bw:
mov ebp,eax
shr ebp,6
bt esi,ebp
sbb edi,edi
; (o + x) * 4 + y * width
lea ebp,[ecx + ebx]
mov dword [edx + ebp * 4],edi
loopnz bw
add edx,1920*4
dec al
jnz short gh
test bl,bl
jne cont1
add ebx,64
cont1:
add ebx,64
sar esi,3
jnz short b
With encoded spaces (52 bytes)
mov esi,2081583599 ; bit field
logo
mov ebx,150 * 4 + 100 * width*4
b:
mov al,64*3-1
mov edx,esp
gh:
mov cl,64
bw:
mov ebp,eax
shr ebp,6
bt esi,ebp
sbb edi,edi
lea ebp,[ecx + ebx]
mov dword [edx + ebp * 4],edi
loopnz bw
add edx,1920*4
dec al
jnz short gh
add ebx,64
sar esi,3
jnz short b
Wire-frame renderer
function setup() {
pixelDensity(1);
createCanvas(1920, 768)
background(0)
loadPixels()
// start logo position
let line_length = 50 // also scale
let offx = (height / 2 - line_length / 2) * width * 4 +
(width / 2 - line_length * 5 / 2) * 4
for (let i = 0; i < line_length; i += 1) {
for (let k = 0; k < 3; k += 1) { // just to
handle all color components (same as * 4)
let c = 255 // color
// O
pixels[offx + (i * width) * 4 + k] =
c
pixels[offx + (line_length + i *
width) * 4 + k] = c
pixels[offx + i * 4 + k] = c
pixels[offx + (i + line_length *
width) * 4 + k] = c
// R
pixels[offx + (line_length*2+i *
width) * 4 + k] = c
pixels[offx + (line_length*2+i) * 4
+ k] = c
// Z
pixels[offx + (i + line_length*4) *
4 + k] = c
pixels[offx + (i + line_length*4 +
(line_length - i) * width) * 4 + k] = c
pixels[offx + (i + line_length*4 +
line_length * width) * 4 + k] = c
}
}
updatePixels()
}
mov cl,150 ; line length
lea eax,[esp+4725540] ; start screen offset
dec esi ; assume esi was 0 before and set it to -1 (full white for
a 32-bit framebuffer)
plot:
imul ebx,ecx,-7676
; O
mov dword [eax],esi
sub eax,7680
mov dword [eax+8280],esi
mov dword [esp+3573540+ecx*4],esi
mov dword [esp+4725540+ecx*4],esi
; R
mov dword [eax+8880],esi
mov dword [esp+3574740+ecx*4],esi
; Z
mov dword [esp+3575940+ecx*4],esi
mov dword [esp+4727940+ebx],esi
mov dword [esp+4727940+ecx*4],esi
loop plot
mov cl,150 ; line length
lea eax,[esp+4725540] ; start screen offset
dec esi ; assume esi was 0 before and set it to -1 (full white)
plot:
; Z diagonal line
imul ebx,ecx,-7676
mov dword [esp+4727940+ebx],esi
;
xor edx,edx
push eax
lea ebx,[esp+3573540]
plot2:
mov dword [ebx+ecx*4],esi ; top lines
mov dword [eax],esi ; vertical lines
; bottom lines
cmp edx,1
jz short skip
mov dword [ebx+1920*150*4+ecx*4],esi
skip:
add eax,150*4;can be ax (-1 byte with artifacts)
add ebx,300*4;can be bx (-1 byte with artifacts)
inc edx
jpo plot2
pop eax
sub eax,7680
loop plot
Generic ?
Curves ?
My version of a 'generic' wire-frame text
renderer
// p5js code
let lineLength = 64
let index = 0
function computeStartPosition() {
// compute start drawing position given as a precomputed
index (a single instruction on x86)
let x = width / 2 - lineLength * 5 / 2 + lineLength
let y = height / 2 + lineLength / 2
// fix the pixels to the grid
x = Math.floor(x)
y = Math.floor(y)
index = (x + y * width) * 4
//
}
function setup() {
createCanvas(512, 512);
background(0);
computeStartPosition()
drawOnce()
}
function drawOnce() {
let commands = [-width, -1, width, 1, 0, -width, 1, 0, 1,
width - 1, 1]
loadPixels()
for (let l = 0; l < commands.length; l += 1) {
let r = commands[l]
let c = abs(r) << 8
// draw line
for (let i = 0; i < lineLength; i += 1) {
pixels[index + 0] = c
pixels[index + 1] = c
pixels[index + 2] = c
if (r == 0) {
index += 4
} else {
index += r * 4
}
}
}
updatePixels()
}
function draw() {
}
; assume esi = 0, edx = 0, ecx = 1
; prepare line drawing list (value which will be added to the
index, 0 = skip drawing)
mov edi, width - 1
push ecx ; 1
push edi ; width - 1
inc edi ; width
mov ebx,edi
neg ebx ; ebx = -width
push ecx ; 1
push edx ; 0 ; zero means skip drawing but still increment with
previous increment value
push ecx ; 1
push ebx ; -width
push edx ; 0
push ecx ; 1
push edi ; width
sbb edx,edx ; edx = -1 (note: also used below as white color
0xffffffff)
push edx ; push -1
push ebx ; -width
; end of lines data
mov edi, 1099104 * 4 ; start position index
add edi,esp ; add screen address
; at this point esi is assumed to be the drawing index position
mov al,11 ; 11 lines (eax = line loop counter)
cmds_loop:
pop ebx ; fetch value from the stack
mov cl,64 ; line length
line_loop:
test ebx, ebx
jne short line_draw
inc esi ; test was 0 ? skip drawing but increment index
jmp short skip_draw
line_draw:
mov dword [edi + esi * 4], edx ; draw (add computed screen/start
address + drawing position)
add esi,ebx ; move drawing position, direction depend on stack
fetched value
skip_draw:
loop line_loop
dec eax
jnz cmds_loop
; this use fancier x86 instruction
tricks :)
; assume edi = 0
; assume ecx = 0 for generic (it is okay here because first line is
vertical)
mov esi, 1099104 * 4 ; start position index
add esi,esp ; add screen address
mov ebp,14858637 ; bitfield logo (2-bit; axis and direction for
each lines)
mov bh,3 ; 3 chars
chars_loop:
mov bl,4 ; 4 lines per char
cmds_loop:
sar ebp,1 ; fetch
direction
sbb eax,eax ; eax = -1
if CF=0 or 0 if CF=1
or al,1 ; eax = line
direction (-1 or 1)
inc ecx ; ecx = 1
sar ebp,1 ; fetch
axis
mov edx,width
cmovnc ecx,edx
imul ecx ; eax = final
index add: line direction * (width|1)
stc
sbb edx,edx ; edx =
0xffffffff
; draw line loop
push 64 ; line
length
pop ecx
line_loop:
mov dword [esi + edi * 4], edx
add edi,eax
loop line_loop
dec bl
jnz cmds_loop
add edi,64 ; space (next char)
dec bh
jnz chars_loop
Stylized wire-frame ?
Blocky font and cheap fancy fonts with the wire-frame renderer ?
Smaller alternative ?
for (let y = 0; y < h; y +=
1) {
for (let x = 0; x < w; x += 1) {
let b = (x | y) >> 6 //
try to replace | by + or - also try : (x + (x ^ y)) >>
7
let index = x + y * w // you can
also try to rotate 45° shape (with b = x; this will produce a
upside down triangle) : ((x + y) >> 1) + ((y - x) >> 1)
* w
buffer[index] = b << 8
}
}
w=3 -- grid width
h=3 -- grid height
n=w*h -- pixels count
c=2^n -- number of combinations (2 colors)
function cb(i,x,y,d,s)
-- d: color (palette index)
-- s: scale
for k=0,w*s-1 do
for p=0,h*s-1 do
j=k//s+(p//s+p//s+p//s)
b=i&(1<<j)
b=b>0 and pix(x+k,y+p,d)
end
end
end
cb(495,0,0,12,24) -- O
cb(79,24*3+1,0,12,24) -- R
cb(403,24*6+2,0,12,24) -- Z
function cb(i,x,y,d,s)
i=abs(403+i)&511
...
end
cb(92,0,0,12,24) -- O
cb(188,24*3+1,0,12,24) -- R
cb(0,24*6+2,0,12,24) -- Z
Sources
Conclusion
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK