SMIDP2lib for SDCC

From CPCWiki - THE Amstrad CPC encyclopedia!
Revision as of 11:42, 20 December 2011 by Mr lou (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Current version of this page: v0.1

Current version of sMIDP2lib: None

What is sMIDP2lib?

sMIDP2lib is currently only an experimental project-idea spawned by mr_lou in December 2011. Being a sparetime J2ME developer (Java Micro Edition, used for cellphone development) since 2006 (see www.lublu.dk), he got the idea of doing CPC development using a framework library for SDCC containing the same method-names used when doing J2ME development.

The idea is simple: Implement selected methods from the MIDP2.0 API (Mobile Information Device Profile) in assembler routines, and embed those routines in SDCC functions. The end result should be a C library to be included in your SDCC project.

The point of this would first and foremost be to make it easier and quicker for J2ME developers to develop for the CPC, but of course also to provide an alternative library to CPCRSlib for Z88DK. A library like this would make it possible for J2ME developers to develop for the CPC much faster. Or even develop the same game for J2ME and the CPC at the same time (with some music and graphic conversion).

Naturally not the whole MIDP2.0 API needs to be implemented. Thus the name sMIDP2lib = Semi MIDP2.0 API library.

Why a J2ME-based library?

J2ME was designed to run on small limited devices, and it consists of simple methods. To compare, Android has a ton of advanced and complex drawing methods that would be very difficult to implement for the CPC. But the Graphics class of J2ME is very simple and straightforward easy to understand for everyone. Because of the simplicity of the MIDP2.0 API, it should theoretically also be possible to implement these methods using assembler routines on the CPC, and get an acceptable speed too. Maybe not at an ultra fast framerate, but an acceptable one. And that's what this project is about in stage 1: Seeing if it's possible.

Portability

In the past, mr_lou has been working on sMIDP2lib versions for other platforms as well, such as

  • HTML5/Javascript
  • Flash
  • Android

This opens up for potential easy porting to 4 other platforms: If CPC developers use sMIDP2lib with SDCC, they should theoretically be able to port their software to J2ME, Android, Flash or HTML5/Javascript relatively fast. (See external links at the bottom of this page for a HTML5/Javascript and Flash example of sMIDP2lib).

HTML5/Javascript and Flash source of sMIDP2lib is free for anyone who wants it. The Android version is private for now though.

General guidelines

Since the idea behind sMIDP2lib is to be a semi MIDP2.0 library, all CPC firmware routines can (theoretically) be dumped. They will never be used anyway (as far as I can see right now). This means we're free to use EXX instructions and other tricks to speed things up - and speed IS important in this library. Speed comes before most other things. As a result, it might be ok to settle for e.g. a drawLine() method that can only draw a line 255 pixels long.

This also means that we'll need to implement a few extra methods, in order to do stuff like setting inks and border color. That's ok though, because we'd probably need to do that anyway since we'll be using custom methods.

ints vs shorts vs chars

In the description of the methods below, you will notice that all methods take ints. This will not be the case in sMIDP2lib though. Since it has been decided to limit the screen resolution to 256x256 pixels in order to gain more speed, all values can be given in unsigned char size.

Memory requirements and handling

Executables produced with sMIDP2lib will require a CPC with 128kb of RAM, and since loading of data is done dynamically, a disk-drive is also a very good idea. Otherwise it should run on all CPC's. No special requirements for CRTC type and such.

All loading of image and music into memory must of course be handled in some way. I am far from an expert on this area, but I imagine some kind of iterator, keeping track of where the next available memory is. Therefor we also need some way of freeing memory again, e.g. when we close a Player (for example to load a new tune). This is far from worked out at this stage though.

RULES DECIDED SO FAR

Screen resolution

Has been set to 256x256 for optimal performance.

Global values accessible from all assembler routines

  • There will be 4 assembler values called sMIDP2lib_clipX, sMIDP2lib_clipY, sMIDP2lib_clipWidth and sMIDP2lib_clipHeight. They'll contain the coordinates and dimensions set by setClip(int x, int y, int width, int height) method, so that all other assembler routines can retrieve them. Each of these 4 values will be 1 byte.
  • There will be 1 assembler value called sMIDP2lib_color which contains the current color set by setColor(int color), so that all other assembler routines can retrieve it. This value will be 1 byte.
  • There will be 1 assembler value called sMIDP2lib_currentBufferScreen which contains the number of the current non-visible buffer-screen. It'll either be 0 or 1. Each buffer-screen naturally has an address. These addresses will always be the same. They will never change. So assembler code can ask which screen is currently the non-visible buffer-screen. Note that the non-visible buffer-screen is the one being drawn to. The other screen is the one currently visible.

Terminilogy-wise, "buffer-screen" is what we call the screen we're currently drawing to. This screen will always be hidden as long as we're drawing to it. Only when called flushGraphics() will it become visible. "Visible screen" is the current visible screen (which is never touched while it is visible). So flushGraphics() will mainly just do a check on which screen is currently the buffer-screen, and then make it visible screen, while making the visible screen the buffer-screen - and then next frame can begin.


sMIDP2lib_clipX:
	DB &00	; Initial startup value
sMIDP2lib_clipY:
	DB &00	; Initial startup value
sMIDP2lib_clipWidth:
	DB &ff	; Initial startup value
sMIDP2lib_clipHeight:
	DB &ff	; Initial startup value

sMIDP2lib_color:
	DB &01	; Initial start value

sMIDP2lib_currentBufferScreen:
	DB &00	; Initial start value
sMIDP2lib_currentBufferScreenAddress:
	DB &xx,&xx	; I don't know what the initial value will be yet

Supported Methods

The initial very first version of sMIDP2lib is imagined to support to following few methods of MIDP2.0:

  • javax.microedition.lcdui.game.GameCanvas.getGraphics();
  • javax.microedition.lcdui.game.GameCanvas.flushGraphics();
  • javax.microedition.lcdui.Displayable.getWidth();
  • javax.microedition.lcdui.Displayable.getHeight();
  • javax.microedition.lcdui.Image.createImage(String source-filename);
  • javax.microedition.lcdui.Image.getHeight();
  • javax.microedition.lcdui.Image.getWidth();
  • javax.microedition.lcdui.Image.getRGB(int[] rgbData, int offset, int scanlength, int x, int y, int width, int height);
  • javax.microedition.lcdui.Graphics.drawImage(Image img, int x, int y, int anchor);
  • javax.microedition.lcdui.Graphics.drawRGB(int[] rgbData, int offset, int scanlength, int x, int y, int width, int height, boolean processAlpha);
  • javax.microedition.lcdui.Graphics.drawLine(int x1, int y1, int x2, int y2);
  • javax.microedition.lcdui.Graphics.drawRect(int x, int y, int width, int height);
  • javax.microedition.lcdui.Graphics.fillRect(int x, int y, int width, int height);
  • javax.microedition.lcdui.Graphics.setClip(int x, int y, int width, int height);
  • javax.microedition.lcdui.Graphics.setColor(int RGB);
  • javax.microedition.media.Manager.createPlayer(String source-filename);

The ones written in italic are some of the more heavier methods, and thus some that will most likely be implemented last in this first stage.

For all these methods goes, that coordinates (0,0) is located in the upper left corner of the screen. On the CPC, the lower right corner will be coordinate (255,255). Only MODE 1 will be supported in the first many versions of sMIDP2lib. If the project becomes popular, it's likely a MODE 0 version will be produced also.

Besides the methods from the MIDP2.0 API, we also need a few additional methods that aren't part of the MIDP2.0 API. These methods are:

  • setBorder(int color);
  • setInk(int pen, int color);

They are quite self-explanatory, so I won't go into further details about them.

But all the MIDP2.0 API methods are described below along with how they could be adapted to the CPC.

GameCanvas.getGraphics()

J2ME description: Obtains the Graphics object for rendering a GameCanvas. In J2ME this method returns a Graphics object, needed for doing all the drawing on the screen.

In sMIDP2lib, this method should return a struct with pointers to the methods of the Graphics class. In other words, getGraphics() will be a method that simply does: return new Graphics();

More knowledge about C and SDCC is needed before giving an example of how such a struct could look like.

GameCanvas.flushGraphics()

J2ME description: Flushes the off-screen buffer to the display. In J2ME this is the call that throws all your drawing onto the screen. If you don't call this method, nothing will be displayed.

In sMIDP2lib, this method should toggle between 2 buffer-screens. When one is visible, drawing takes place on the other. When flushGraphics() is called, the visible buffer-screen and the hidden buffer-screen switches place.

J2ME example for getGraphics() and flushGraphics():


Graphics g = getGraphics();
g.setColor(0x00ff00); // Green
g.fillRect(10,10,30,30); // Draw a 30x30 big box at 10,10 with green color
flushGraphics(); // flush to screen

sMIDP2lib example for getGraphics() and flushGraphics():


Graphics g = getGraphics();
g.setColor(1); // Pen 1
g.fillRect(10,10,30,30); // Draw a 30x30 big box at 10,10 with pen 1
flushGraphics(); // Toggle screen address

Displayable.getWidth()

J2ME description: Gets the width in pixels of the displayable area available to the application.

In sMIDP2lib, this will simply return 255

Displayable.getHeight()

J2ME description: Gets the height in pixels of the displayable area available to the application.

In sMIDP2lib, this will simply return 255

Image.createImage(String source-filename)

J2ME description: Creates an immutable image from a source image. This is the method used in J2ME for loading an image from within the jar file. (A zip file containing all the classes and resources). It returns an Image object.

In sMIDP2lib, I imagine it will load the image from disk, store it in memory, create a struct that holds a pointer to the image data, along with two shorts holding the image dimensions, and two pointers to the methods getHeight() and getWidth() that returns the dimensions.

That way you will be able to do this:


g = getGraphics();
myImage = Image.createImage("/pic.dat");
g.drawImage(myImage, 0, 0, 0);
flushGraphics();

The fileformat will be a custom fileformat. Read more below.

In the above example, notice that the filename is preceeded by a slash. This is required in J2ME for some reason, when loading images, while other similar looking methods doesn't require it. Therefor sMIDP2lib should always check if the first character in a filename is a slash, and simply ignore it if it is. (Goes for createImage and createPlayer for example).

Image.getRGB(int[] rgbData, int offset, int scanlength, int x, int y, int width, int height)

J2ME description: Obtains ARGB pixel data from the specified region of this image and stores it in the provided array of integers. This method is used in J2ME to save a piece of an image in an array.

A method like this will be particular useful on the CPC, since it allows us to not re-draw the whole screen in each iteration, but instead simply save the pieces of background-layers that was drawn over in the previous iteration.

Naturally we won't have RGB data, but instead the byteData of the CPC-representation of image.

The tricky part about this method is, that in J2ME it's always performed on an Image object - which we don't have in sMIDP2lib (and aren't interested in either). But to stay within the MIDP2.0 API specs, we need to keep it that way. In order to use this method in J2ME the way we'd use it on the CPC, J2ME devs would need to create a buffer-image to draw too, before drawing to the Graphics object of the canvas. In other words they'd do something like this:


g = getGraphics(); // Get the graphics object of the canvas
bufferImage = Image.createImage(getWidth(), getHeight()); // Creates an image same size as the screen display size
buffer_g = bufferImage.getGraphics(); // Get the graphics object of that Image so we can draw on it. (Not in sMIDP2lib)
buffer_g.setClip(0,0,getWidth(),getHeight());
buffer_g.setColor(0x000000); // Black color
buffer_g.fillRect(0,0,getWidth(),getHeight()); // Clear the background
buffer_g.setColor(0xffffff); // White color
buffer_g.drawLine(0,0,getHeight(),getWidth()); // Draw a white line across the image
int[] storedPieceOfBackground = new int[256];
bufferImage.getRGB(storedPieceOfBackground,0,16,0,0,16,16); // Copy the image data at (0,0) 16x16 pixels into the array storedPieceOfBackground
bufferImage.setClip(0,0,16,16); // Set clip to the rectangle we just copied
bufferImage.drawImage(mySprites,-16,0,0); // Draw the 2nd sprite of my sprite-sheet in that clip

// In the next loop-iteration, we can simply draw the storedPieceOfBackground on the spot where the sprite were.
// That way we don't have to re-draw the entire screen.

g.setClip(0,0,getWidth(),getHeight()); // And finally...
g.drawImage(bufferImage,0,0,0); // Draw the bufferImage onto the screen
flushGraphics();

This is called double-buffering in J2ME. Since sMIDP2lib will also be doing automatic double-buffering, we can adapt this method. Instead of creating another Image, we'll simply have access to to the already existing buffer-screen.


unsigned char[] storedPieceOfBackground = new char[64]; // We only need space for 64 bytes, since that equals 256 pixels.
sMIDP2lib_bufferScreen.getRGB(storedPieceOfBackground, 0, 0, 0, 0, 16 16); // Size is still given in pixels, so the getRGB method needs to take care of the division by 4 and such
// We ignore the offset and scanlength. They'll need to be part of the method, but will be ignored.

The variable sMIDP2lib_bufferScreen is a struct containing a pointer to the address of the current buffer-screen, meaning the one we're currently drawing to. (We'll always be drawing to the non-visible screen). When ever flushGraphics() is called, the current non-visible buffer-screen is shown, and the previous visible screen not becomes the buffer-screen. Therefor, the pointer in the sMIDP2lib_bufferScreen struct, will change value when flushGraphics() is called.

This is a good solution because we keep the MIDP2.0 API structure, and we aren't creating overhead since the data in the arrays can be overridden again and again.

Since we now have an array of byte data, we need a corresponding drawRGB() method to draw it again. See below.

Graphics.drawImage(Image img, int x, int y, int anchor)

J2ME description: Draws the specified image by using the anchor point. In J2ME, this method takes an Image object and renders on the screen. The anchor point is used to indicate where on the image x and y is. For example, anchor can be Graphics.TOP|Graphics.LEFT (which is typically equal to 0) which means that x,y is in the upper left corner of the image.

In sMIDP2lib, this method obviously can't take an object as parameter. Instead it will take a struct as parameter (as described above). This struct contains the address of the image data, along with the dimensions of the image. In the initial very first version of sMIDP2lib, the anchor parameter will be ignored. x,y will always be in the upper left corner.

Graphics.drawRGB(int[] rgbData, int offset, int scanlength, int x, int y, int width, int height, boolean processAlpha)

J2ME description: Renders a series of device-independent RGB+transparency values in a specified region.

This method will always be used in combination with Graphics.getRGB(), and will thus need adaptions similar to that method. Like with getRGB() we will ignore the scanlength parameter. (At least in the first versions of sMIDP2lib). We will also ignore the processAlpha parameter. But both parameters will still need to be part of the method.


g.drawRGB(storedPieceOfBackground, 0, 0, 0, 0, 16, 16, false);

I don't know what possibilities we have in C regarding arrays as parameter. Logically for it to work on the CPC, only a pointer to the array should be used. I need to experiment and ask around though.

Graphics.drawLine(int x1, int y1, int x2, int y2)

J2ME description: Draws a line between the coordinates (x1,y1) and (x2,y2) using the current color and stroke style.

Graphics.drawRect(int x, int y, int width, int height)

J2ME description: Draws the outline of the specified rectangle using the current color and stroke style.

Graphics.fillRect(int x, int y, int width, int height)

J2ME description: Fills the specified rectangle with the current color.

Graphics.setClip(int x, int y, int width, int height)

J2ME description: Sets the current clip to the rectangle specified by the given coordinates. This is a very important method, that defines where on the canvas the following drawing-methods should draw. It is this method that allows you to load a sprite-sheet containing 4x4 sprites, and then only draw one of them.

Example:


g = getGraphics();
mySprites = Image.createImage("sprites.img"); // Loads sprites.img that contains 4x4 sprites of 16x16 pixels each
g.setClip(50,50,16,16); // Defines the clip to be at (50,50) and 16x16 pixels big.
g.drawImage(mySprites, 34, 50, 0); // Draws the spritesheet at (34,50);
flushGraphics();

In the above example, it will be the 2nd sprite in the top row that'll be displayed, because the whole picture is drawn -16 pixels from the clip X.

Any Graphics method called after a setClip() will only affect the defined clip.


g = getGraphics();
g.setClip(50,50,16,16); // Defines the clip to be at (50,50) and 16x16 pixels big.
g.fillRect(0,0,100,100); // Only fills the above clip despite of different values in the fillRect method
flushGraphics();

Initially before setting a clip, the default clip will be the whole screen (0,0,getWidth(),getHeight()). However, because your main loop will probably contains a lot of calls to setClip(), you should always call setClip(0,0,getWidth(),getHeight()) before calling fillRect(0,0,getWidth(),getHeight()).

Graphics.setColor(int RGB)

J2ME description: Sets the current color to the specified RGB values.

In sMIDP2lib we obviously don't have RGB colors. So this method will be adapted into taking just the pen-number. In other words, a number between 0 and 3 (because we only support MODE 1 for now). Since this leaves us without any way of setting inks for each pen, we need to add some additional methods that aren't part of the MIDP2.0 API. These are: setBorder(int color); setInk(int pen, int color);

Manager.createPlayer(String source-filename)

J2ME description: Create a Player to play back media. Returns a Player object, that has methods such as start() and stop(). With J2ME you can play mp3, wav, and midi. As we know, we can't do that with the CPC.

So instead sMIDP2lib must be able to load STarKos music files. The method should, like the createImage() method, return a struct with pointers to methods like start() and stop().


music = Manager.createPlayer("music.bin"); // Loads the music from disk
music.start(); // Calls STarKos playback routine

Image file format

I imagine the fileformat of images to be a rather simple one. Its first byte will contain the width of the image. The next byte will contain the height of the image. That way an image can max be 255x255 pixels big. This is only a thought, and will possibly change into 2 bytes for each value instead. The rest of the data is the image itself, using standard CPC representation of image-data.

Any image-size within that limitation can then be loaded. Doesn't have to be fullscreen images. You can load a small sprite-sheet of e.g. 64*128 pixels, and then also load another image containing a font and having a size of 480x8 pixels. Each image will be accessible through their structs.

A Java command-line utility will ideally be created (if one doesn't exist already) that will convert 8bit PNG files (and/or maybe other formats) into the sMIDP2lib image format.

Issues to solve

Currently, there are the following issues to work out a solution for:

Double-buffering makes it problematic to "un-draw"

While double-buffering can potentially make things more smooth, it also causes some problems when it updates only parts of the screen. Consider a sprite moving from left to right, an unknown number of pixels each frame, on top of a background. You would then use sMIDP2lib_bufferScreen.getRGB() to store the piece of background you're about to draw on, in order to be able to redraw the background in next frame. Let's say the sprite is about to be drawn at (10,0) and it's a 16x16 pixels bit sprite. Then before drawing the sprite, you would call sMIDP2lib_bufferScreen.getRGB() to save the 16x16 pixels piece of background, and then draw the sprite and then call flushGraphics(). Next frame, you will then want to draw that piece of the background again to cover our sprite before drawing the sprite in the next location. But since we switched buffer-screen with the flushGraphics(), we are now drawing on another screen where our sprite is actually placed on another location. Therefor we will most likely not be deleting it properly.

I don't have any ideas on how to solve this at the moment, other that either skipping the double-buffering part, or breaking the MIDP2.0 API rules. But I hope we'll find a solution.

Contributers

The following people are contributing to this project at the moment:

  • mr_lou (available on IRC, channel #CPC on EsperNET, channel #AmstradCPC on EpiKnet and channel #CPC on freenode)
  • Demoniak (available on IRC, channel #AmstradCPC on EpiKnet)
  • TFM/FS

History log

  • December 20th, This project page was created.
  • December 19th, Demoniak helped a lot by supplying two routines, drawLine() and FastCls() that convinced mr_lou that this whole idea is quite possible to make a reality. His drawLine routine was twice as fast as the firmware routine. Check comparion
  • December 11th, mr_lou created a thread about the idea at the forum.

External links