Tic-80: Clamped 1,2[,3?] bits palette mode

Created on 3 May 2020  路  11Comments  路  Source: nesbox/TIC-80

Proposed feature

I think it would be nice to be able to draw sprites using a lower bit depth palette.

The idea would be to pass a bit_depth parameter to sprite drawing functions that would clamp the palette and read bit_depth bits per pixel from the sprite memory (with appropriately scaled coordinates)

The spirit of this proposal is really a low-level memory trick, which is IMHO completely in the style of a fantasy computer.

Use cases

  • This would allow a better use of the sprite memory for low-color graphics such as fonts, ui elements, textures, etc.
  • Games with 4color sprites would have a double-sized spritesheet
  • The user can use the PALETTE_MAP section of the memory to locally adapt the palette to their needs.
  • This could also be leveraged in the config cartridge where most sprites are actually low-palette

What to modify (non exhaustive)

  • add bit_depth mode to spr, possibly map, remap and textri
  • add bit_depth mode switch to the sprite[/map] editor
  • poke1, poke2, poke3 ? or general poke with bitmask

Possible caveats

  • the sprite memory segment size isn't a multiple of 3, therefore a 3bit mode could leave a few bits unused
  • map (and textri from map) would be harder to adapt to mixed bit_depth data. remap can help

Possible improvements

  • #952 (true coordinates blit function) would be a good companion to this feature
  • the sprite editor might need some additional masking feature to ease the manipulation of a spritesheet with mixed bit_depth data.

What do you think ?

Most helpful comment

Judging by that image, I think the caption should instead read "PLEASE KILL ME!"

All 11 comments

So, I've been trying to prototype this as an exercise...
https://github.com/ddelemeny/TIC-80/commit/303713a39a33d4fc77d32aea1cfabe028034ccf8 implements a (very unpolished) sspr function with a bit_depth parameter.

Key takeaways:

  • the spritesheet's pixel layout in ram could make it difficult to implement a 3bits palette mode. 2bits and 1bit is easy.
  • implementation of true-coordinates sspr is pretty straightforward, ~although my version of it does not implement arbitrary bitmap scaling~ (edit: now it does, see comments below) (beyond the scope of this issue, but I didn't want to consider what happens to the tile indexing in lower bit_depth modes).

Here's the result, each 4bits pixel value from the spritesheet is interpreted as two 2bits values, effectively doubling the size of the sprite :

x=72
y=24

function TIC()
        cls(13)
        -- sspr(sx, sy, sw, sh, x, y, colorkey, scale, flip, rotate, bit_depth)
        sspr(16,0,32,16,x,y,3,3,0,0,2)
        print("Ed... WaRlD...",84,84)
end

-- <TILES>
-- 001:eccccccccc888888caaaaaaaca888888cacccccccacc0ccccacc0ccccacc0ccc
-- 002:ccccceee8888cceeaaaa0cee888a0ceeccca0ccc0cca0c0c0cca0c0c0cca0c0c
-- 003:eccccccccc888888caaaaaaaca888888cacccccccacccccccacc0ccccacc0ccc
-- 004:ccccceee8888cceeaaaa0cee888a0ceeccca0cccccca0c0c0cca0c0c0cca0c0c
-- 017:cacccccccaaaaaaacaaacaaacaaaaccccaaaaaaac8888888cc000cccecccccec
-- 018:ccca00ccaaaa0ccecaaa0ceeaaaa0ceeaaaa0cee8888ccee000cceeecccceeee
-- 019:cacccccccaaaaaaacaaacaaacaaaaccccaaaaaaac8888888cc000cccecccccec
-- 020:ccca00ccaaaa0ccecaaa0ceeaaaa0ceeaaaa0cee8888ccee000cceeecccceeee
-- </TILES>

-- <PALETTE>
-- 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57
-- </PALETTE>

tic_sspr_bit_depth

Judging by that image, I think the caption should instead read "PLEASE KILL ME!"

I'd prefer replacing the scale parameter with w and h:
sspr(sx, sy, sw, sh, x, y, colorkey, w, h, flip, rotate, bit_depth)
This would allow the sprite to be scaled freely both horizontally and vertically (with float values allowed, which is a sole purpose of sspr() in PICO-8).

@StinkerB06 I think it's more relevant to #952, but I was going for it anyway : https://github.com/ddelemeny/TIC-80/commit/503fcb9f7572da0f76a475427041ebd29b0e27d0
tic_sspr_bit_depth_scale

I preferred keeping dx,dy,dw,dh together in the function signature (as pico-8 does) to keep some coherence in surface coordinates definition between source and dest, but I'm open to counter-arguments on this. Order of arguments is closer to textri than to spr.

https://github.com/ddelemeny/TIC-80/commit/7d82ac9a581667f5b30b9e82e4c7361db920db04 implements drawSurface an optimized version of this _almighty_ drawing function (true coordinates, arbitrary bitmap scaling, bit-depth aware... it just lacks a texture-repeat mode at this point) that could serve as a drawing backend for the other drawing functions of the api (spr, map).

But there's a catch.
The current drawing backend, drawTile, is tightly optimized. I've tried to re-implement the same optimizations except the following :

  1. in drawTile, the tile buffer address is known in advance, there's no need to recompute from source coordinates on each drawn pixel.
  2. in drawTile, the integer scale parameter allows fast drawing of block of pixels without having to interpolate source coordinates on each drawn pixel.

This means drawSurface is quite fast, but not as fast as drawTile yet.
Using https://tic.computer/play?cart=631 as a naive benchmark on my laptop, I managed to obtain 85% of the performance of drawTile at 1:1 scale (drawSurface can draw 85% of the sprites that drawTile can draw at 30fps), and I lose 40 to 50% of drawTile's performance at scale 3:1.

Fast, but not fast enough. I need to find a way to match the fast paths of drawTile.
Any ideas welcome.

Current progress of the bit_depth feature experiment https://github.com/ddelemeny/TIC-80/commit/ada039a101dc8dcc5b9ec2fa02668f6c00a048ca

Here's the editor working in 2bits mode:
2bit-editor-screen

And here's a bit_depth enabled spr drawing the same animated sprite :

  • left is 4bits per pixel data
  • center is 2 bits per pixel
  • right is 2 bits per pixel with palette remapping

2bit-sprite-example

Now I need to find a good UI to handle that mode in the sprite editor, right now you can switch between mode 2 and 4 bits with the Ctrl-Tab hotkey (to mimic bank switching)


Edit : Bonus test cartridge for the current implementation.

-- title:  TIC80 - 2bit sprite test
-- author: ddelemeny
-- desc:   Example for the bit_depth feature
-- script: lua  

t=0
x=48
y=48

PALETTE_MAP = 2*0x3FF0 
function mapColors(colors)
 for i=0,#colors-1 do
  poke4(PALETTE_MAP+i,tonumber(string.sub(colors,i+1,i+1),16) or i)
 end
end

function TIC()
 cls(13)
 -- regular spr call
 spr(1,x,y,1,3,0,0,2,2)
 -- spr in 2bpp mode
 spr(2+t%60//30*2,x+54,y,1,3,0,0,2,2,2) -- that last '2' is the bit_depth parameter
 -- same with remap
 mapColors("F1AB")
 spr(2+t%60//30*2,x+108,y,1,3,0,0,2,2,2)
 mapColors("0123")
 t=t+1
end

-- <TILES>
-- 001:dfffff75f00000f5baaaaac5baaaaac5bffffbcfbfcfcbccbfcfcbccbfcfcbcc
-- 002:dfffff75f00000f5baaaaac5baaaaac5bffffbcfbffffbccbfcfcbccbfcfcbcc
-- 017:bffffbccbaaaaa0fbababac7baefaac5baaaaac5baaaaaf5f0cf0c75dffdff55
-- 018:bffffbccbaaaaa0fbababac7baefaac5baaaaac5baaaaaf5f0cf0c75dffdff55
-- </TILES>

-- <PALETTE>
-- 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57
-- </PALETTE>

Some remaining concerns to resolve :

  • Can bit_depth be enabled on more functions (map, textri, font) ?
  • Can bit_depth be enabled in the map editor ? Should it ?
  • Can config use a low-palette spritesheet ? Should it ?
  • SPRITE FLAGS memory segment is limited. What to do of flags for extra sprite indices in low-palette modes ? Solution : Less flags per sprite on lower bit modes.
  • MOUSE CURSOR could benefit from low palette, but the interpretation of value in is quite tied to the 4bits mode, is there something doable with this ?
  • Can the new drawSurface backend be optimized further ? How do less naive benchmarks compare ?
  • The texture-repeat drawing mode I joked about in a previous comment about the _almighty_ drawSurface actually sounds useful and doable.
  • Should I rename bit_depth everywhere I used it for a more accurate bpp, or less descriptive bit_mode ?

Nice work on the UI!!!!

More UI experiments at https://github.com/ddelemeny/TIC-80/commit/7f1cdb5d48a97dcc812a0de89c666f24f8aa15a5 , some prompted by @nesbox's suggestions

  • put bitmode selector under the palette
  • swap bank and page tabs positions
  • reduce number of flags per sprite on lower bitmodes
  • increase palette cell size instead of shrinking the palette
  • cue extended spritesheet with dashed borders

ezgif com-optimize

Edit update at https://github.com/ddelemeny/TIC-80/commit/6ac8c944e34c8c9377417d30fd7fe22c0317979f :

  • bitmode radio switch
  • page selector as tab instead of sliding button

ezgif com-optimize(3)

Progress update:

  • renamed bit_depth to bpp
  • bpp is now activated by poking a value to a register in vram. Full register specification in progress.
  • reverted changes to api to avoid overloading it
  • bpp enabled fset/fget
  • bpp enabled font (see demo below)

ezgif com-optimize(6)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Spreit picture Spreit  路  4Comments

Joe-Gigs picture Joe-Gigs  路  3Comments

Spreit picture Spreit  路  4Comments

BuoYancYdabl picture BuoYancYdabl  路  5Comments

nesbox picture nesbox  路  5Comments