HOWTO-Backends/Graphics

From ScummVM :: Wiki
< HOWTO-Backends
Revision as of 21:34, 19 April 2022 by Ccawley2011 (talk | contribs) (First draft)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

The majority of the work involved in porting ScummVM to a new platform is usually related to the graphics APIs for displaying the game, UI and cursor on the screen.

Modular graphics backends

For new backends, it is highly recommended to split out the graphics code into a subclass of GraphicsManager to allow for code to be shared between backends, and to ease maintenance by having the backend split into smaller components. This requires the following:

  • The backend's OSystem subclass should inherit from the ModularGraphicsBackend class.
  • A subclass of GraphicsManager should be created in a subdirectory of backends/graphics, and the source files should be added to backends/module.mk.

Older backends may implement the graphics functionality directly in the OSystem subclass instead.

Subclassing the OpenGL graphics manager

If your platform supports OpenGL, then it's possible to subclass the existing OpenGL graphics manager to provide a well tested and mostly complete implementation of ScummVM's 2D graphics APIs with minimal complexity. Note that this approach does not yet handle 3D games such as Grim Fandango, so it may still be necessary to create a custom OpenGL graphics manager for the time being.

The OpenGL backend provides the following extra features out of the box:

  • Support for OpenGL 1.1 or later, OpenGL ES 1.1 or later or OpenGL ES 2.0 or later.
  • Fast aspect ratio correction, filtering and stretch modes.
  • Software scalers (e.g. AdvMame, HQx, Edge).
  • Hardware accelerated palette lookup for platforms with shaders available.
  • Software conversion for pixel formats that are unsupported by particular OpenGL implementations.
  • Support for OSD messages and icons.

To utilize the OpenGL graphics manager, you'll need to enable it in the configure script, which can be done by searching for references to _opengl_mode. It's also necessary to create a subclass of it that provides the necessary code for managing the OpenGL using EGL or the equivalent API for your operating system. This can be accomplished by implementing the following functions:

In general

The setContextType function should be called early on to informs the OpenGL graphics manager of what API should be used. Valid options are OpenGL::kContextGL, OpenGL::kContextGLES or OpenGL::kContextGLES2.

Once the context has been created, the notifyContextCreate function should be called to set up the graphics manager and create the necessary textures, shaders and pipelines.

Before the context is destroyed, the notifyContextDestroy function should be called to delete or clean up any resources that would be invalidated by the context loss.

Whenever the screen resolution is changed, the handleResize function should be called with the new resolution.

loadVideoMode()

This function is called whenever a new resolution is requested. For platforms that use a fixed output size, this can just return true.

refreshScreen()

This function is called whenever the OpenGL buffers should be swapped.

getProcAddress()

This function should return a function pointer to the OpenGL function specified by the name parameter. Note that this needs to return function pointers for core functions, not just extension functions, so platforms using eglGetProcAddress with EGL 1.4 or earlier may need to provide a fallback for those functions.

hasFeature() / setFeatureState() / getFeatureState()

TODO...

handleResizeImpl()

TODO...

Writing a new graphics manager

TODO...

updateScreen() method

The updateScreen() method is called by an engine when it finished drawing something on the screen. It may happen quite often, up to several hundred times per second, but some devices have a restriction on how many times per second screen blitting takes place. For example, some NTSC version can not physically update more often than 60 Hz, and attempts to make it more often will result in the machine to hang.

If you are writing a backend for such a system, you may use code like this:

void updateScreen() {
    uint32 newTime = getMillis();
    if (newTime - _oldTime < 1000 / MAX_FPS)
        return;

    _oldTime = newTime;

     // do actual screen update
}

That should do the trick for you. This code has the slight disadvantage that it skips some screen updates though. This means there is a possibility that it skips an important screen update, which is not followed by any other screen update for some time, leading to some graphics glitch.