ScummVM RGB color progress blog

August 13, 2009

slow going

Filed under: Uncategorized — Upthorn @ 10:01 am

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.

Advertisements

July 21, 2009

Still alive

Filed under: Uncategorized — Upthorn @ 10:21 am

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.

July 6, 2009

Updated API reference

Filed under: Uncategorized — Upthorn @ 4:31 am

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

Contents

//

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:

GradientRGB565

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<Graphics::PixelFormat> 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.

June 23, 2009

Status report

Filed under: Uncategorized — Upthorn @ 2:19 pm

What follows is a copy of the status report email I sent the ScummVM GSoC mentors list, as it has been requested that I post it on my blog as well:

Hello Eugene, everyone:
Here is the status report and timeline for my project, as requested:

My project was to develop and implement 16-bit (and optionally 24/32 bit) graphical support for games that made use of it. I was to focus primarily on the later HE Scumm games.

The current status is that:

  • the 16-bit HE Scumm games are all displaying in 16-bit
  • A preliminary API has been designed for games to request specific graphical formats of the backend
  • this API has been implemented in the SDL backend

My currently active tasks are:

  • refining/simplifying the API
  • writing documentation for the API

My future tasks are:

  • Exhaustive testing of the 16-bit scumm engine games, to ensure that there are no 16-bit related display glitches
  • Work necessary in other engines with 16-bit RGB graphics to make use of the finalized API, (as time permits)
  • Upgrading the scalers, gui, and SDL backend to support 24/32 bit pixel formats, if time permits

Here is the timeline so far:

  • May 18th – May 21st: Began work on SDL backend, implementing 16-bit screen surface to test with
  • May 22nd – May 29th: Began work on Scumm engine, implementing 16-bit displays to test 16-bit SDL screen.
  • May 30th – Jun 1st: Travis assumed responsibility for Scumm HE rendering code for 16-bit support. I tested games for display bugs, and fixed a backend bug.
  • Jun 3rd: Max creates SVN branch for me, Kirben and I commited our patches.
  • Jun 4th – Jun 6th: developed hack to allow 8-bit and 16-bit cursors to display properly.
  • Jun 7th – Jun 13th: Discussed API concerns with key ScummVM developers, developed ad-hoc API while awaiting conclusions.
  • Jun 14th – Jun 17th: Documented discussion results and refactored ad-hoc API code to bring it in line with the decisions that were made.
  • Jun 18th – Jun 22rd: Began streamlining and refining the API.

Here is my expected timeline for the future (optimistic):

  • Jun 23rd – Jun 26th: Finalize and document the API.
  • Jun 27th – Jul 13th: Test 16-bit SCUMM HE games for display glitching.
  • Jul 14th – Jul 28th: The vacation I mentioned in my application, possible continued testing of SCUMM HE games.
  • Jul 29th – Jul 31st: Do research of other engines requiring RGB color support.
  • Aug 1st – Aug 7th: Enhance these engines to make use of the API
  • Aug 8th – Aug 17th: Improve scalers, gui, and SDL backend to support 24 and 32 bit pixel formats

Here is my expected timeline for the future (pessimistic):

  • Jun 23rd – Jul 13th: Finalize and document the API.
  • Jul 14th – Jul 28th: The vacation I mentioned in my application, begin testing 16-bit HE Scumm games for graphical glitching
  • Jul 29th – Aug 17th: Continued testing and bugfixing of 16-bit HE Scumm games.

I hope this is sufficient,
Jody Northup

April 30, 2009

Exposition.

Filed under: Uncategorized — Upthorn @ 2:46 am

At the request of the fine folks on the ScummVM team, I have created a blog to post progress updates on my Summer of Code 2009 task.

I probably will not begin regular updates until the coding period proper begins on May 23rd.

Blog at WordPress.com.