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 should do so by calling convertScreenRect from copyRectToScreen when conversion is necessary, but are not required to.
- Backends supporting color order conversion with limited hardware should override convertScreenRect with platform-optimized code, but are not required to.
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.
- Determine/obtain desired pixel format
- If your engine can only produce graphics in one RGB color format, use Graphics::PixelFormat::createFormat{the desired format}.
- For instance, if your engine can only produce graphics in RGB555, you might say Graphics::PixelFormat requestedFormat = Graphics::PixelFormat::createFormatRGB555();
- If your engine can easily support any RGB mode (for instance if it converts from YUV), call OSystem::getSupportedFormats and use the first value in the returned Common::List.
- If your engine can support more than one RGB mode, but not all of them...
- 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.
- Use the return value from Graphics::findCompatibleFormat(OSystem::getSupportedFormats(),{your format list}
- If your engine can only produce graphics in one RGB color format, use Graphics::PixelFormat::createFormat{the desired format}.
- 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: <syntax type="C++"> Common::Error QuuxEngine::run() { // Obtain the best possible format from the backend. Graphics::PixelFormat ourFormat = _system->getSupportedFormats().front();
// If our engine could only handle one format, we would specify it here instead of asking the backend: // // RGB555 // Graphics::PixelFormat ourFormat(2, 3, 3, 3, 8, 10, 5, 0, 0);
// If our engine could handle only a few formats, this would look quite different: // Common::List<Graphics::PixelFormat> ourFormatList; // // // RGB555 // Graphics::PixelFormat 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); // // // Get the best format which is compatible between our engine and the backend // ourFormat = Graphics::findCompatibleFormat(_system->getSupportedFormats(),ourFormatList);
// Attempt to initialize a 640 x 480 surface with that format initGraphics(640, 480, true, &ourFormat);
// Use the format the system was able to provide // in case it cannot support that format at our requested resolution format = _system->getScreenFormat();
byte *offscreenBuffer = (byte *)malloc(640 * 480 * format.bytesPerPixel);
if (format.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; uint32 mask = (1 << (format.bytesPerPixel << 3)) - 1; while (!shouldQuit()) {
// Draw the actual gradient for (int16 y = 0; y < 480; y++) { uint8 *dst = offscreenBuffer + (y * 640 * format.bytesPerPixel); for (int16 x = 0; x < 640; x++) { uint32 color = (x * y) & mask; t &= (format.bytesPerPixel << 3) - 1; color = (color << t) | (color >> ((format.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 < format.bytesPerPixel; i++) { dst[format.bytesPerPixel - i] = color & 0xFF; color >>= 8; } dst += format.bytesPerPixel;
- else
for (int i = format.bytesPerPixel; i > 0; i--) { *dst++ = color & 0xFF; color >>= 8; }
- endif
} } _system->copyRectToScreen(offscreenBuffer, 640 * format.bytesPerPixel, 0, 0, 640, 480); _system->updateScreen(); parseEvents();
// Wait a semi-arbitrary length in order to animate fluidly, but not insanely fast _system->delayMillis(66); t++; } return Common::kNoError; } </syntax>
Backend initialization protocol
- During first initialization, set the value that getScreenFormat returns to Graphics::PixelFormat::createFormatCLUT8()
- 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 256 color mode and set the value that getScreenFormat returns to Graphics::PixelFormat::createFormatCLUT8().
- 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 function 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.
- virtual bool OSystem::convertScreenRect(byte *dstbuf, const byte *srcbuf, int dstpitch, int srcpitch, int w, int h, Graphics::PixelFormat hwFmt)
- Blits a rect from screen format known by the game to the hardware format known by the backend.
- This must return false if the blit fails (due to unsupported format conversion).
- This must return true if the blit succeeds.
- This should be overridden by backends where helpful, but is not required to be.
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.
Miscellaneous
- bool 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 = 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
- Now displays a warning if it recieves OSystem::kTransactionFormatNotSupported in return from endGFXTransaction
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.