Open main menu

API-Truecolor

Revision as of 15:17, 25 October 2018 by Criezy (talk | contribs) (Text replacement - "</source>" to "</syntaxhighlight>")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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<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.