Over the wire and through the woods.

I’ve just been informed by LordHoto that the 1900UTC August 17th line is not, in fact, a deadline for students, but the end of their obligation to perform, and I can continue up until the 24th, to submit code which may be evaluated.

So, I’ll be continuing to work on the API in the hopes of doing just that.

Down to the wire

As I am writing this, the GSoC Coding period ends in just under 19 hours. Shortly after my last blog post, I began to make some headway on the task, and I’ve kept up a reasonable pace since then, and I’ve now got modifiers working in a way that I’m satisfied with, so I’m finally starting on the real meat of the task — the engine<->keymapper interface. Unfortunately, I haven’t managed to take a whole lot of time yet to sit and think about the details of how this is going to work, so I’m going to be flying by the seat of my pants a bit.

I hope to have a more fully-fleshed proposal for an API, and some work on an initial implementation done, before the 19:00 UTC deadline.

Wish me good luck, I will need it.

EDIT:

With 6 and a half hours remaining in the GSoC coding period, and badly in need of sleep, it looks like I won’t be able to get the API proposal finished before the pencils down deadline.

In fact, what I’ve managed to get done towards it is embarassingly small, but I will post it here for posterity, anyway:

How this is going to work:

  • simple method to create simple common actions such as mouse clicks and key presses
    • Autogenerate keypress and mouseclick action IDs and descriptions
      • What is the bare minimum required for an Action structure to be functional?
      • What aspects of an Action can be defaulted or autogenerated in the majority of cases?
        • preferredkey
        • events
        • id?
        • description?
        • Priority??
          • How does priority work? (is 0 the highest priority, or the lowest?)
          • What is the point of having a “parent-child” keymap hierarchy?
  • model Keymapper interface after CursorManager
    • implement a stack for keymaps
      • implement pushKeyMap
        • What input should it take?
          • cursor manager takes the components for a cursor, and handles cursor assembly internally
          • But maybe if there’s a streamlined method for keymap production, the engine could handle that and push the completed map
      • replaceKeyMap
      • popKeyMap
      • Surely there are other distinguishing features to the CursorManager

slow going

As anyone who’s been following the commit logs can tell, I’m having a lot of difficulty keeping up a good pace of work on the keymapper. The reason for this is that, unlike with my prior project, nearly all the progress I make on this task comes at the expense of code that someone else wrote before, and because I did not get a lot of time to look over it before I started working (which is mostly my own fault), I don’t really understand how it all works together well enough to be certain that the code I’m replacing it with will be better.

As a result, I’m being very hesitant about any changes I make because I always want to be certain I understand what the existing code is doing (and how) before I destroy it to make room for my own.

However, I am still working on the keymapper improvements, and I have no intent of giving up before I’ve finished them, no matter how long I have to keep working on this past the end of this year’s GSoC.

Keymapper API proposal for engine to define actions

Note: I’m staying up late to get this proposal written before the deadline my mentor has given, so my words in this post might not make a lot of sense.

It seems to me that the problem with the currently implemented approach is that the Action structure is too complicated to require engine authors to create and initialize a full structure for every single action that they wish to be mapped to a key. Additionally, it requires engine authors to give each individual action a universally unique 4 character ID, so that actions and their keymappings can be saved to the config without any other game that ScummVM can run ever overwriting it with a different key. However, some of the more complicated features of the Action structure may be necessary for some games to fully function in a reduced-key environment.

Because of these factors, it seems to me that there are two steps which should be taken to make the keymapper work better for engine authors:

  1. The Common::Action structure needs to be closely examined, and unnecessary components removed.
  2. One or more constructors should be provided to allow for easy mass production of more common types of Actions, especially ones that simply need to map one key press to some other button.

I fear that this API proposal is grossly insufficient, because the task of properly implementing modifiers in the keymapper has been much more interesting and less straightforward than I expected, and has left me with very little time before to properly examine the other aspects of the keymapper component before now.

Gaining momentum

After a long break from coding (longer than what I’d wanted by half), I’ve started working on properly implementing modifiers for the keymapper, instead of feeding every key in an additional time for each possible combination of modifiers that exists.

I’ve got it halfway working in a temporary way — it only feeds the keys in once, but the remap dialog can recognize modified keys and tell that they are different from unmodified keys (and that ctrl-a is different from shift-a), but events mapped to modified keys aren’t triggering at all for some reason.

The more important thing, though, is that I’m back in the swing of coding and getting work done again. Hopefully I should be back up to full speed in another day or two.

Back in the saddle

Alright, I’m finally back from vacation, and well rested, and shaking off my cold, so it’s time to get back to work on this.

From this point, I’m actually going to be working on the keymapper, so I’m making this blog post to organize my thoughts into a plan of action.

  1. Become familiar with the current working of the keymapper.
  2. Create a standardized framework for backends to describe keys and define keymaps.
  3. Make a stack for keymaps to be pushed onto and popped from as needed.
  4. Make use of the framework to allow the keymapper to automatically generate keymaps.
  5. Figure out some way to make the keymaps easily userconfigurable.

This is only the most general of outlines at the moment, I expect to add subtasks, and rearrange the task list as I become more familiar with the keymapper as it currently exists.

Note: I wrote this post on July 30, but somehow wordpress never published it, thankfully it was saved to drafts, at least.

Still alive

I’m still alive. Also still on vacation. I haven’t yet had an opportunity to discuss keymappers with Sev, but have not forgotten about my obligations. However, my schedule of having fun with people I haven’t seen in years is very demanding, and takes priority because of the limited window of opportunity. I hope to be back to working full swing on the project next Wednesday.

Initial project goal completed. Also, vacation.

Ahead of even my wildest dreams, I have already completed the initial project goal of a color format API. (see prior blog entry for more information).

I must thank Eugene (sev), Max (fingolfin), Johannes (LordHoto), and Travis (Kirben), for the great amount of support and help they have provided me on this. I would also like to thank Sven (DrMcCoy), Willem (wjp), and Jordi (jvprat) for the interest they have shown and the insight they have provided.

Just in timed for the vacation I have already scheduled, which begins in the morning after I post this, and continues for two weeks. I will still be somewhat active on the next project goal, when I can get a moment away from having fun times with friends, but I do not expect to make regular SVN commits during this time, and it may be slightly longer than one week before my next blog update gets posted.

When I return, I expect to be tackling the issue of keymappers with full force.

Updated API reference

Note: the following is copied from the source of the wiki reference article I just wrote for it.

Introduction

This page is meant as a reference for developers implementing the Truecolor API in their engine and backend modules. This page will provide a complete spec of API requirements and suggestions, as well as a protocol for engines and backends to follow during setup.

NOTE: This API was designed with backwards-compatibility for 8-bit Graphics only engines in mind. If your engine only uses 256 color graphics, you should not have to change anything, so long as the engine’s ENABLE_RGB_COLOR setting matches the backend’s during compilation, so that functions link properly.

Truecolor API specifications

Engine specifications

  • Engines capable of some, but not all RGB formats must use Graphics::findCompatibleFormat with OSystem::getSupportedFormats() as the first parameter, and the engine’s supported format list as the second parameter.
  • Lists of formats supported by the engine must be in descending order of preference. That is, the first value is the most wanted, and the last value id the least wanted.
  • Engines capable of any RGB format must use the first item in the backend’s getSupportedFormats() list to generate graphics.
  • Engines with static graphical resources should use the backend’s preferred format, and convert resources upon loading, but are not required to.
  • Engines which do not require the backend to handle alpha should not use XRBG1555 or XBGR1555 modes, but may do so.

Backend specifications

  • When no format has been requested, or no game is running, the return value of getScreenFormat must equal that of Graphics::PixelFormat::createFormatCLUT8().
  • If a requested format can not be set up, the backend must revert to 256 color mode (that is, Graphics::PixelFormat::createFormatCLUT8()).
  • Backends must not change the return value of getScreenFormat outside of initBackend, initSize, or endGFXTransaction.
  • Backends must be able to display 256 color cursors even when the screen format is set differently.
  • Backends supporting GFX transactions must return kTransactionFormatNotSupported from endGFXTransaction when screen format change fails.
  • Backends must place the highest color mode that is supported by the backend and the hardware at the beginning of the list returned by getSupportedFormats.
  • Backends should support graphics in RGB(A) color order, even if their hardware uses a different color order, but are not required to.
  • Backends supporting color order conversion with limited hardware may use Graphics::crossBlit, but are strongly recommended to use platform-optimized code.

Truecolor API initialization protocol

Engine initialization protocol

NOTE: This API was designed with backwards-compatibility for 8-bit Graphics only engines in mind. If your engine does not make use of per-pixel RGB color graphics, you should not have to change anything, so long as ENABLE_RGB_COLOR is set in configuration during compilation, so that functions link properly.

  1. Init with desired pixel format
    • If your engine can only produce graphics in one RGB color format, initialize a Graphics::PixelFormat to the desired format, and call initGraphics with a pointer to that format as the fourth parameter.
      • For instance, if your engine can only produce graphics in RGB555, you would say Graphics::PixelFormat myFormat(2, 3, 3, 3, 8, 10, 5, 0, 0);
    • If your engine can easily support any RGB mode (for instance if it converts from YUV), call initGraphics with NULL for the fourth parameter.
    • If your engine can support more than one RGB mode, but not all of them…
      1. Produce a Common::List of Graphics::PixelFormat objects describing the supported formats. This list must be in order of descending preference, so that the most desired format is first, and the least desired is last.
      2. call initGraphics with this list of formats as the fourth parameter
  2. Check the return value of OSystem::getScreenFormat() to see if setup of your desired format was successful. If setup was not successful, it will return Graphics::PixelFormat::createFormatCLUT8();
    • If the setup was not successful, and your engine cannot run in 256 colors, display an error and return.
    • Otherwise, initialize your engine to use the pixel format that getScreenFormat returned, and run normally.

Example

Here is an example of a simple engine that uses the best color depth available to display and color-cycle this gradient:

Common::Error QuuxEngine::run() {
	Graphics::PixelFormat ourFormat;

	// Request the backend to initialize a 640 x 480 surface with the best available format.
	initGraphics(640, 480, true, NULL);

	// If our engine could only handle one format, we would specify it here instead of asking the backend:
	// 	// RGB555
	// 	ourFormat(2, 3, 3, 3, 8, 10, 5, 0,  0);
	// 	initGraphics(640, 480, true, &ourFormat);

	// If our engine could handle only a few formats, this would look quite different:
	//  	Common::List ourFormatList;
	//
	// 	// RGB555
	// 	ourFormat(2, 3, 3, 3, 8, 10, 5, 0,  0); 
	// 	ourFormatList.push_back(ourFormat);

	//
	// 	// XRGB1555
	// 	ourFormat(2, 3, 3, 3, 7, 10, 5, 0, 15); 
	// 	ourFormatList.push_back(ourFormat);
	// 	
	// 	// Use the best format which is compatible between our engine and the backend

	// 	initGraphics(640, 480, true, ourFormatList);

	// Get the format the system was able to provide
	// in case it cannot support that format at our requested resolution
	ourFormat = _system->getScreenFormat();

 	byte *offscreenBuffer = (byte *)malloc(640 * 480 * ourFormat.bytesPerPixel);

 	if (ourFormat.bytesPerPixel == 1) {
		// Initialize palette to simulate RGB332

		// If our engine had no 256 color mode support, we would error out here:
		//  	return Common::kUnsupportedColorMode;

		byte palette[1024];
		memset(&palette,0,1024);

		byte *dst = palette;
		for (byte r = 0; r < 8; r++) {

			for (byte g = 0; g < 8; g++) {

				for (byte b = 0; b < 4; b++) {

					dst[0] = r << 5;
					dst[1] = g << 5;

					dst[2] = b << 6; dst[3] = 0; dst += 4; } } } _system->setPalette(palette,0,256);
	}

	uint32 t = 0;

	// Create a mask to limit the color from exceeding the bitdepth
	// The result is equivalent to:
	// 	uint32 mask = 0;
	// 	for (int i = ourFormat.bytesPerPixel; i > 0; i--) {
	// 		mask <<= 8;
	// 		mask |= 0xFF;
	// 	}
	uint32 mask = (1 << (ourFormat.bytesPerPixel << 3)) - 1;

	// Repeat this until the event manager tells us to stop
	while (!shouldQuit()) {

		// Keep t from exceeding the number of bits in each pixel.
		// I think this is faster than "t %= (ourFormat.bytesPerPixel * 8);" would be.
		t &= (ourFormat.bytesPerPixel << 3) - 1;

		// Draw the actual gradient
		for (int16 y = 0; y < 480; y++) {

			uint8 *dst = offscreenBuffer + (y * 640 * ourFormat.bytesPerPixel);

			for (int16 x = 0; x < 640; x++) {

				uint32 color = (x * y) & mask;
				color = (color << t) | (color >> ((ourFormat.bytesPerPixel << 3) - t));

				// Currently we have to jump through hoops to write variable-length data in an endian-safe manner.
				// In a real-life implementation, it would probably be better to have an if/else-if tree or
				// a switch to determine the correct WRITE_UINT* function to use in the current bitdepth.
				// Though, something like this might end up being necessary for 24-bit pixels, anyway.

#ifdef SCUMM_BIG_ENDIAN
				for (int i = 0; i < ourFormat.bytesPerPixel; i++) { dst[ourFormat.bytesPerPixel - i] = color & 0xFF; color >>= 8;
				}
				dst += ourFormat.bytesPerPixel;

#else
				for (int i = ourFormat.bytesPerPixel; i > 0; i--) {

					*dst++ = color & 0xFF;
					color >>= 8;

				}
#endif
			}
		}
		// Copy our gradient to the screen. The pitch of our image is the width * the number of bytes per pixel.
		_system->copyRectToScreen(offscreenBuffer, 640 * ourFormat.bytesPerPixel, 0, 0, 640, 480);

		// Tell the system to update the screen.
		_system->updateScreen();

		// Get new events from the event manager so the window doesn't appear non-responsive.
		parseEvents();

		// Wait a semi-arbitrary length in order to animate fluidly, but not insanely fast
		_system->delayMillis(66);

		// Increment our time variable, which doubles as our bit-shift counter.
		t++;
	}
	return Common::kNoError;
}

Backend initialization protocol

  1. During first initialization, set the value that getScreenFormat returns to Graphics::PixelFormat::createFormatCLUT8()
  2. When initSize is called, attempt to set screen format with the PixelFormat pointed to by the format parameter
    • If format is NULL, use Graphics::PixelFormat::createFormatCLUT8()
    • If requested screen format is supported, attempt to set screen up with it.
      • If setup is unsuccessful, fall back to previous color mode and set the value that getScreenFormat returns accordingly.
        • Note: During game initialization, this must always result in a fall-back to 256 color mode with getScreenFormat returning a value equivalent to Graphics::PixelFormat::createFormatCLUT8. This may only have any other result if the same game has already run an initSize with a different format, and is trying to switch formats during runtime.
      • If setup is successful, update the value that getScreenFormat returns to the value that was requested.
        • If format is supported by backend but not directly in hardware, ensure that graphics are converted in copyRectToScreen
    • If requested screen format is not supported, continue running in 256 color mode.

Complete API reference

New functions

OSystem

  • Graphics::PixelFormat OSystem::getScreenFormat(void)
    • Returns the pixel format currently accepted for graphics from the engine.
  • Common::List<Graphics::PixelFormat> OSystem::getSupportedFormats(void)
    • Returns a list of all the pixel formats the backend can accept graphics in.
    • The first item in this list must be the highest color graphics mode supported by the backend which is directly supported by the hardware.
    • The remainder of the list must be in order of descending preference, such that the last item in the list is the one that the backend functions worst in.
    • Backends which do not support fast conversion must put all modes directly supported in hardware, (and CLUT8), before modes that will require conversion during copyRectToScreen.
    • Backends which support fast conversion should put larger colorspaces before smaller color spaces, but are not required to.

Graphics::PixelFormat

  • inline Graphics::PixelFormat Graphics::findCompatibleFormat(Common::List<Graphics::PixelFormat> backend, Common::List<Graphics::PixelFormat> frontend)
    • Returns the first entry on the backend list that also occurs in the frontend list, or CLUT8 if there is no matching format.
  • inline Graphics::PixelFormat (void)
    • creates an uninitialized PixelFormat.
  • inline Graphics::PixelFormat(byte BytesPerPixel, byte RBits, byte GBits, byte BBits, byte ABits, byte RShift, byte GShift, byte BShift, byte AShift)
    • creates an initialized PixelFormat.
    • [_]Bits is the width in bits of the relevant channel
      • RBits = red bits, GBits = green bits, BBits = blue bits, ABits = alpha bits.
    • [_]Shift is the number (starting from 0) of the least significant bit in the relevant channel, which is equal to the bitshift required to make a channel.
      • In RGB565, RShift is 11, GShift is 5, and BShift is 0.
      • In RGBA4444, RShift is 12, GShift is 8, BShift is 4, and AShift is 0.
  • static inline Graphics::PixelFormat Graphics::PixelFormat::createFormatCLUT8(void)
    • creates a PixelFormat set to indicate 256 color paletted mode
    • This method is provided for convenience, and is equivalent to initializing a Graphics::PixelFormat with the bytedepth of 1, component losses of 8, and component shifts of 0.
      • Which would be accomplished normally via Graphics::PixelFormat(1,8,8,8,8,0,0,0,0);
    • Because this methods are static, it can be called without creating a pixel format first
      • For instance, if (format == NULL) newFormat = Graphics::PixelFormat::createFormatCLUT8();

Miscellaneous

  • bool Graphics::crossBlit(byte *dst, const byte *src, int dstpitch, int srcpitch, int w, int h, Graphics::PixelFormat dstFmt, Graphics::PixelFormat srcFmt)
    • blits a rectangle from a “surface” in srcFmt to a “surface” in dstFmt
    • returns false if the blit fails (due to unsupported format conversion)
    • returns true if the blit succeeds
    • can convert the rectangle in place if src and dst are the same, and srcFmt and dstFmt have the same bytedepth

Modified functions

engine

  • void initGraphics(int width, int height, bool defaultTo1xScaler, const Graphics::PixelFormat *format)
    • Now takes a format parameter, which is a pointer to a requested pixelformat
    • Uses top item in backend’s getSupportedFormats list if format is NULL
    • Now displays a warning if it recieves OSystem::kTransactionFormatNotSupported in return from endGFXTransaction
    • Now overloaded to simplify initialization for the three engine types:
  • void initGraphics(int width, int height, bool defaultTo1xScaler)
    • A wrapper which sets format as a pointer to Graphics::PixelFormat::createFormatCLUT8();
  • void initGraphics(int width, int height, bool defaultTo1xScaler, const Commmon::List<Graphics::PixelFormat> &formatList)
    • A wrapper which sets format as a pointer to the return value from Graphics::findCompatibleFormat(OSystem::getSupportedFormats(),formatList)

OSystem

  • virtual void OSystem::initSize(uint width, uint height, Graphics::PixelFormat *format = NULL)
    • Can now take a format parameter, which is a pointer to a requested pixelformat, and defaults to NULL
    • Uses 256 color mode if format is NULL
  • OSystem::TransactionError OSystem::endGFXTransaction(void)
    • Must now return kTransactionFormatNotSupported if the backend fails in an attempt to initialize to a new pixel format during a GFX transaction.

CursorMan

  • void Graphics::CursorManager::pushCursor(const byte *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, int targetScale, Graphics::PixelFormat *format)
    • Can now take a format parameter, which is a pointer to a Graphics::PixelFormat describing the pixel format of the cursor graphic, and defaults to NULL.
    • Uses 256 color mode if format is NULL
  • void Graphics::CursorManager::replaceCursor(const byte *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, int targetScale, Graphics::PixelFormat *format)
    • Can now take a format parameter, which is a pointer to a Graphics::PixelFormat describing the pixel format of the cursor graphic, and defaults to NULL.
    • Uses 256 color mode if format is NULL
  • Graphics::CursorManager::Cursor(const byte *data, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor = 0xFFFFFFFF, int targetScale = 1, Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8())
    • Can now take a format parameter, which is a Graphics::PixelFormat describing the pixel format of the cursor graphic, and defaults to 256 color mode.

Modified Types

  • enum Common::Error
    • Now includes a kUnsupportedColorMode value, for engines which get unsupported pixel formats after a format change request fails.
  • enum OSystem::TransactionError
    • Now includes a kTransactionFormatNotSupported value, for backends to announce failure to supply game screen with requested pixel format.

Taking the left fork

Of my initial project (16-bit support), all that remains to be done is to document the API on the wiki. My mentor, Sev has requested of me that I move on to improving the keymapper, which he sees as a much higher priority than scaler and backend support for 24 or 32 bit color formats.

I agree with that assessment, and I can’t really say I was looking forward to upgrading the scalers.

In light of this, I am providing a new planned timeline:

Optimistic:

  • Jul 5th – Jul 6th: Fully document the API in this blog, doxygen, and wiki.
  • Jul 7th – Jul 10th: Midterm evaluation for google, and any finishing touches necessary for the API docs.
  • Jul 11th – Jul 13th: Begin research on the keymapper
  • Jul 14th – Jul 28th: The vacation I mentioned in my application, possibly continue research on the keymapper.
  • Jul 29th –  Aug 5th: Design and implement the requested enhancements.
  • Aug 6th – Aug 17th: Improve scalers, gui, and SDL backend to support 24 and 32 bit pixel formats, if time permits.

Pessimistic:

  • Jul 5th – Jul 11th: Fully document the API in this blog, doxygen, and wiki.
  • Jul 12th – Jul 13th: Last minute midterm evaluation for google, and a round of Q&A to finish the API docs.
  • Jul 14th – Jul 28th: The vacation I mentioned in my application, begin research on the keymapper.
  • Jul 29th –  Aug 17th: Design and implement the requested keymapper enhancements.