[WIP] Arduboy2 Implementation Overhaul

Just tried both of them. They run indeed very smooth!

Butā€¦ they look so tiny :slight_smile:

1 Like

Iā€™m going to ramble on a bit about the Sprite::drawExternalMask bug and the process I went through to solve it.

Iā€™m going to put it in a details block because I donā€™t know how many people are actually interested in the ramblings of a madman, and frankly itā€™s probably a bit boring, but maybe someone will find it useful or interesting.

I guess Arduboy2 porting isnā€™t as shiny as 3D rendering and emulationā€¦

(Warning: these are really long.)

The Long And Tedious Process

My first port of call was to narrow down the cause of the bug.
I donā€™t have a hardware debugger yet, so I had to do things the old fashioned way - inserting print statements into the code to print information onto the screen.

This can be very tedious.
First I narrowed it down to gameplay(),
then I narrowed it down to drawObjects()
then I narrowed it down to drawObject(object)
and from there I narrowed it down to Sprites::drawExternalMask.

I tried substituting Spritess:drawOverwrite and SpritesB::drawExternalMaskā€¦ same result.
I then thought ā€œah, maybe I broke it during clean upā€, so I substituted the original Arduboy2 Sprites code (minus the assembly).
Same problem, which means that for some reason code that works perfectly well on the Arduboy doesnā€™t work perfectly well on the Pokitto.

Next I tried commenting out drawObject to see if everything else worked.
I discovered drawBackground() had the same issue, coming from the exact same functions.
So I commented out drawBackground() and suddenly the game worked - all the logic was there and working perfectly without that rendering code.

I realised that other parts of the code were still using Sprites::drawExternalMask just fine, so I decided to log the parameters being fed into the drawing function in drawObject (since that was the first breaking point).

At first I thought it was specific sprites causing the problem so I disabled specific object types.
Iā€™d got to the third disabled object when I suddenly spotted a pattern.
Every time it froze, the y input was negative.
So I inserted an if(y > -1) above the drawing call, and sure enough everything started working.

So at this point Iā€™d established that:

  • The code works fine on Arduboy
  • Thereā€™s something causing it to behave differently on the Pokitto
    • That something is related to negative y values

So I moved into the drawing functions and started logging everywhere to find the breaking point, as standard.

I manage to pinpoint the break to one exact line:

uint8_t data = Arduboy2Base::sBuffer[ofs + WIDTH];

And I discovered that actually ofs was surprisingly high (in about the 65000 range).
I know from experience thatā€™s usually a tell-tale sign of negative values being converted to unsigned values.
So I trace the code to ofs's defnition.

uint16_t ofs = ((sRow * WIDTH) + x + xOffset);

I find that sRow is defined:

const int8_t yOffset = (y % 8);
const int8_t tempSRow = (y / 8);
int8_t sRow = ((y < 0) && (yOffset > 0)) ? (tempSRow - 1) : tempSRow;

So evidently this is somehow related.

I start analysing the uint16_t ofs = ((sRow * WIDTH) + x + xOffset); expression and logging its components.
I find that sRow = -1, WIDTH = 128, x = 63 and xOffset = 0, which means (sRow * WIDTH) = -128 and x + xOffset = 63, so obviously -128 + 63 = -65 and of course -65 converted to a uint16_t is 65471.

So then I come back down to uint8_t data = Arduboy2Base::sBuffer[ofs + WIDTH];.
I think ā€œok, so 65471 + 128 = 65599, and obviously -65 + 128 = 63, which cancels out the -128 from earlier (which was from sRow * WIDTH while sRow = -1)ā€¦ā€
ā€œButā€¦ā€ I ask myself, ā€œthis works on Arduboy, so what would cause it to not be working now?ā€

And then it hit me.
I remembered a basic law of C++ and suddenly everything made senseā€¦

Note

(There was a point where I went down the wrong path and started looking into endianness, but after establishing both devices are little endian I scrapped that idea. A small 15-20 minute red herring.)

The Solution To The Problem

So eventually I had isolated the problem and had a moment of inspiration.

I realised two important bits of information and put 2 and 2 together and arrived at a wonderful 4.

The first thing I realised was the solution to the thing that was bugging me - why would it work on Pokitto and not Arduboy?
The answer has to be something thatā€™s different between the Arduboy and Pokitto, and sure enough it is.

The answer is int.
On the Arduboy an int is 16 bits wide.
On the Pokitto an int is 32 bits` wide.

But if ofs is a uint16_t, then where is this int?
And then I remembered one of the fundamental rules of C++.
Something that happens all the time right under everyoneā€™s noses but it often goes unnoticed.

The answer is ā€˜integer promotionā€™.
From cppreferenceā€™s section on Numeric Promotions:

unsigned char or unsigned short can be converted to int if it can hold its entire value range, and unsigned int otherwise;
arithmetic operators do not accept types smaller than int as arguments
And on the Pokitto, uint16_t is a type alias for unsigned short, hence ofs + WIDTH (i.e. uint16_t + <integer literal>) causes ofs to be promoted to int.

On the Arduboy, with or without promotion the arithmetic is 16-bit arithmetic, thus overflow occurs, the overflow bits are discarded and the addition correctly cancels out the earlier addition of a negative.

On the Pokitto, the promotion causes 32-bit arithmetic to be used, so instead of the overflow bit being discarded, the overflow bit is kept and the result is larger than the size of the display buffer, thus resulting in a buffer overrun.
It is that buffer overrun that caused the Pokitto to halt.

With this realisation I instantly form a solution to mimic the original behaviour:

uint8_t data = Arduboy2Base::sBuffer[(ofs + WIDTH) & 0xFFFF];

Mask off the higher bits that are dropped in the Arduboyā€™s original calculations.

To think that the addition of one single mask operation can be the difference between perfectly running code and a processor halting bug.

So what can we learn from thisā€¦

The Moral Of This Story

There are many things to take away from this.

Type safety is important. Always know the possible ranges of types.
int is at least 16 bits. It could be 16 bits, it could be 32 bits, it could be 64 bits.
If you want to write portable code, keep that in mind.

Be aware of integer promotion.
It can sneak up on you if you arenā€™t careful.
Sometimes people think their code means one thing,
but integer promotion changes its meaning and result.

Be careful of mixing signed and unsigned values.
Try not to mix them, but if you must mix them, make sure to double check the behaviour.

Always try to make type conversion explicit.
Hiding type conversion makes code harder to undestand.
If conversions arenā€™t explicit then sometimes you have to really look to understand whatā€™s going on.

And lastly and most importantly:
Avoid integer overflow like the plague.
If your solution to a problem relies on overflow or underflow then you are probably tackling the problem incorrectly.
Donā€™t wait for overflow to happen - prevent it!

1 Like

Firstly, compared to the actual Arduboy screen theyā€™re not that much smaller.
An Arduboy screen is about ~2.9x1.9cm.
The Arduboy emulation on Pokitto results in a ~2.3x1.2cm image (so about 6mm either side).

Secondly, they canā€™t exactly fit the whole screen, unless someoneā€™s prepare to make a scaling algorithm that gives them a width of 1.5 pixels (by mixing black and white to get grey).

Thirdly, I have a magic trick up my sleve for later.
I donā€™t want to ruin the suprise because itā€™s really going to be the cherry on top.


At most Iā€™ve told 3-4 people, 2 of whom are actually partly involved in the development.

Jonne (listens to my ramblings and answers the odd question),
Vampirics (told him for a specific reason)
SpaceBruce (I know him in person so I often tell him what Iā€™m working on),
and Filmote (I might not have told Filmote, I canā€™t remember).

Absolutely nobody else knows.

2 Likes

Iā€™ve now properly commited the fix:

And marked this point in history with another release,
which includes the Minesweeper.bin.

1 Like

I couldnā€™t get the insignificant screen manipulation functions that barely 1% of Arduboy games use working today because of the effort involved in adapting my current code to incorporate the requred changesā€¦

So instead of some boring old screen functions, I guess Iā€™ll have to implement EEPROM so you people can save your highscores and stuffā€¦


Soā€¦ fully functional LoveRush and Minesweeper anyone? :P

LoveRush.bin (56.9 KB)

Minesweeper.bin (53.7 KB)

1 Like

Just a minute, pardner!

I use a lot of time to come up with a 100% safe EEPROM handler and it seems you intend to write directly to the EEPROM.

Why? Have I failed to explain how it works and/or what are the benefits?

giphy

EDIT: this is all toungue-in-cheek ofcourse :yum:

EDIT2: I will, ofcourse, help make the safe implementation. But it is essential we donā€™t go back down to that level where settings get wiped out or something because everyone writes wherever they please in the EEPROM

EDIT3:

It should work something like this.

  • The Arduboy program reserves a block of EEPROM using the Pokitto::Cookie mechanism.
  • That block is filled with an array of bytes (for example 256 bytes)
  • EEPROM write bytes and read bytes are actually wrappers that manipulate individual bytes in that array
  • everything happens inside a Pokitto::Cookie, allocation tables do not get broken and there is no possibility of overwriting EEPROM saves from other programs, including other Arduboy programs that use EEPROM

p.s.

The Arduboy2 port is a fantastic contribution. Sorry for the hard comment on the EEPROM write/read bits, afterall its only a part of the problem. But I am dead serious. We will never get the EEPROM situation under control if there is no system to prevent accidental overlaps between programs. AFAIK this is a recurring problem on the Arduboy

I havenā€™t forgotten about the ā€˜cookieā€™ system. All in good time.
Before I go about implementing the ā€˜cookieā€™ system I need to inspect the ā€˜cookieā€™ API and decide the best way to apply it.

Thereā€™s some other bits and pieces I want to get working first.
Despite my little sidetrack with the timers earlier today
(which was primarily for testing the water of how accurate I can get the screen),
I am intending to focus on implementing the minimum amount of features required to get games running first, and then refine them afterwards.


As far as Iā€™m aware, very few people worry about it.
Most people just accept it and remember to back up their saves.

There is actually a way to communicate with the Arduboyā€™s bootloader over serial to get a copy of the EEPROM for backup purposes but Iā€™ve never tried it myself.

@Mr.Blinky wrote a quick and easy Python script for it, included among his Arduboy utilities:

Thereā€™s a video of it in action here.

I havenā€™t used it myself because I havenā€™t needed it.


Also, if you have a dig around my Minesweeper game youā€™ll find this little gem:

Fully checksummed to detect data corruption,
and both forward and backwards compatible
(provided that new versions of a game only ever add new save fields and donā€™t remove old ones).

(I was intending to try to get people to adopt it for Arduboy,
but my to do heap is ever-growing.)


And in case it looks like I have no idea what Iā€™m doing,
I assure everyone that I have a plan.

I have a ā€˜to doā€™ list (categorised rather than ordered):

To Do

Sprites.cpp

  • Fix drawPlusMask

ArduboyCore

  • Implement setCPUSpeed8MHz or mark it as intentionally empty
  • Implement boolOLED or mark it as intentionally empty
  • Implement idle or mark it as intentionally empty
  • Implement bootPowerSaving or mark it as intentionally empty
  • Implement boot or mark it as intentionally empty
  • Implement displayOff *
  • Implement displayOn *
  • Implement paint8Pixels
  • Implement sendLCDCommand or mark it as intentionally empty
  • Implement invert *
  • Implement allPixelsOn *
  • Implement flipVertical *
  • Implement flipHorizontal *
  • Implement setRGBled variants
  • Implement freeRGBled
  • Implement digitalWriteRGB variants
  • Implement exitToBootloader
  • Implement mainNoUSB
  • Implement TXLED1
  • Implement TXLED0

wiring.cpp

  • Implement init or mark it as intentionally empty

stdlib.cpp

  • Implement itoa
  • Implement utoa
  • Implement ltoa
  • Implement ultoa
  • Implement dtostrf
  • Implement dtostre

ArduboyAudio

  • Implement everything

ArduboyBeep

  • Implement everything

And outside of that I have various notes on paper.
(I prefer paper for notes because Iā€™m always misplacing my .txt files and struggling to think of names for them. A piece of paper doesnā€™t need a name, just some physical space to occupy.)

2 Likes

Just an idea. How about emulating Arduboy EEPROM by saving and loading to SD card?

I hope to get back to working on this if the interest is still there.

Today I implemented itoa, utoa, ltoa and ultoa.
Theyā€™re written in a way thatā€™s portable and doesnā€™t have the bugs that pokItoa, pokUtoa and pokLtoa have (although they might not be as small/fast).

1 Like

I think there might be interests in thus. But whatā€™s killing it is the fact that the 128*64 res makes it that not many will want to play them that small on the Pokittoā€¦

I keep hearing this argument, but if you look at them side by side you realise itā€™s not actually that much of a difference:




(Sorry these are poor quality, my camera doesnā€™t like artificial light.)
(Also getting the timers 10 ticks apart was completely unintentional.)

The Pokittoā€™s screen is actually significantly larger than the Arduboyā€™s screen,
but the individual pixels arenā€™t that much smaller.
At most, the Pokittoā€™s pixels are 2/3 of the Arduboyā€™s.


In the future it might be possible to scale it up slightly,
but ultimately itā€™ll come down to the quality of scaling algorithm that can be developed.

Scaling algorithms arenā€™t easy at the best of times, but most are designed for scales of x2, x3 or x4 and what weā€™d be looking at here is more like x1.5 or x1.6 (1.75 would be too big unless youā€™re happy to have some pixels chopped off and the LED indicator to be above or below the gameplay area).

If the Pokitto has enough processing power then scaling up 3x and then scaling down x0.5 might be an option, but Iā€™ll cross that bridge when I come to it.
For now I want to focus on other things, like getting an on-screen LED indicator.

I didnā€™t mean to say it in a bad way. I know itā€™s not that much smaller then on the actual Arduboy. But we have to take into account to that since the Pokitto screen is quite bigger, that makes it ā€˜lookā€™ even smaller.

I just think that some might skip the Arduboy ports because for them, it doesnā€™t take advantage of the Pokitto.

I am all for Arduboy ports, mainly because it could port most of the game I made with Filmote on the pokitto, but knowing that a minority will really try them makes me want to maybe think twice about it before doing anything.

Now, if someone find a voodoo magic way to scale it well on the Pokitto or make the library adjustable in some ways to help make hybrids pokitto/Arduboy gamesā€¦

I have a solution for that, but I havenā€™t got to that part yet.

You donā€™t really have to do much.

As long as an Arduboy game obeys the rules of standard C++ then all you have to do is rename the .ino to .cpp and hook up the library and everything works automatically.

It took me a mere 8 commits (of which 1 was initialising the repo and 1 was uploading the original files) to get CastleBoy into a playable state, and half of that was commenting out stuff I havenā€™t got round to implementing.

If the library had been in a fully working state then I would have only had to make only one change:
renaming CastleBoy.ino to CastleBoy.cpp.

Thatā€™s what this library is going to be capable of when itā€™s finished.
The point of this library isnā€™t to make porting Arduboy2 games to the Pokitto easier,
itā€™s to make Arduboy2 games work for the Pokitto practically out of the box, no extra effort involved.

Unfortunately there will be cases where this wonā€™t work because people have depended too much on the Arduino environment
(i.e. -fpermissive, the behaviour of Arduinoā€™s .ino file processing and GCCā€™s compiler extensions),
but apart from those cases this library should make direct porting of Arduboy games take practically zero effort.

Like I said, thatā€™s going to be difficult because scaling algorithms arenā€™t easy.

Scaling algorithms are the kind of thing computer science professors sit around writing really boring mathematical papers about.

Like I said earlier,
I think the best hope for scaling will be to use a x3 scaling algorithm and then downscale to x0.5,
but Iā€™m not even going to think about that until Iā€™ve fixed more of the unimplemented stuff.

If I fixed it tomorrow the games would look better,
but youā€™d still be left with no LED, no ā€˜flashlight modeā€™, no sound,
so youā€™d still have people saying they donā€™t want to play it.

That is equal than scaling to x1.5 like this( e.g. b/w horizontal pattern):
10101010 ==> 100100100100
I do not think it will look good.
If we do filtered scaling it will decay contrast and eat cpu.

It depends which scaling algorithm is used.
I havenā€™t specified anything.
It could be nearest neighbour, it could be linear, it could be bilinearā€¦

But again, Iā€™m not even thinking about that right now,
scaling is not even close to being the first thing on my todo list.

Iā€™ve got all these functions to implement first:

  • dtostrf
  • dtostre
  • Sprites::drawBitmap (SPRITE_PLUS_MASK)
  • TXLED0
  • TXLED1
  • init()
  • Arduboy2Base::flashlight
  • Arduboy2Core::setCPUSpeed8MHz
  • Arduboy2Core::boot
  • Arduboy2Core::bootOLED
  • Arduboy2Core::idle
  • Arduboy2Core::bootPowerSaving
  • Arduboy2Core::exitToBootloader
  • Arduboy2Core::displayOff
  • Arduboy2Core::displayOn
  • Arduboy2Core::paint8Pixels
  • Arduboy2Core::sendLCDCommand
  • Arduboy2Core::invert
  • Arduboy2Core::allPixelsOn
  • Arduboy2Core::flipVertical
  • Arduboy2Core::flipHorizontal
  • Arduboy2Core::setRGBled(r, g, b)
  • Arduboy2Core::setRGBled(colour, value)
  • Arduboy2Core::freeRGBled
  • Arduboy2Core::digitalWriteRGB(r, g, b)
  • Arduboy2Core::digitalWriteRGB(colour, value)
  • Arduboy2Core::mainNoUSB

And thatā€™s not even counting the sound functions.

Donā€™t forget we have grayscale on Pokitto. Or we can do the interlacing I experimented with in the other thread.

My guess is the 3/2 scale can be done in one step and pretty quickly. I might try to do this once the code is open. I like experimenting with this kind of things.

Things like these could be a compile time option. Iā€™m also thinking about additional options like different colors, or something like a ā€œCRTā€ look ā€“ maybe the bright pixels could have a small ā€œglowā€ around them. Iā€™ll try to make some mockups when I come home.

going slightly offtopic

Nice postprocessing can do miracles. Old games were made with the CRT look in mind, take a look at that difference:

From this very nice article.

1 Like

Iā€™ve done the ā€œdumb 1.5x scalingā€ versions on Pokitto (every second pixel 2 pixels wide).

I donā€™t think it looks half bad.

But thats just me

If you can do that fast enough for the full screen, that would be very nice indeed!

Technically 2/3rds is already open source* (I havenā€™t got round to adding avr-libcā€™s licence because for that one Iā€™ve been working from the documentation, not the actual source code),
but I probably wonā€™t be accepting any PRs until Iā€™m done because later on Iā€™m going to split the repo into three parts so we end up with an avr-libc implementation, an Arduino implementation and an Arduboy2 implementation.

* I didnā€™t exactly get a say in the matter, I modified the original source code so I have to obey the already existing licences, LGPL for Arduino and MIT for Arduboy2.


Like I say though, Iā€™m not even thinking about the scaling until Iā€™ve got the other stuff out of the way.

Core functionality before anything flashy.

1 Like

The core libs are LGPL, no? That means you can license your code what you want if it only uses the library. The modified library is still copyleft.

Sure, I donā€™t want to push it, I would just try it myself and leave the code somewhere as usual. Unfortunately Iā€™ve discovered a new game and am spending most time playing it now, not doing much coding :[