24 Jul 2007

The Nebula3 Render Layer: CoreGraphics

The CoreGraphics subsystem is mainly a compatibility wrapper around the host's 3d rendering API. It's designed to support a Direct3D/OpenGL-style API with programmable shaders without any functionality- or performance-compromises. The general functionality of the CoreGraphics subsystem is roughly the same as the Nebula2 gfx2-subsystem, however CoreGraphics fixes many of the issues that popped up during the lifetime of the Nebula2 graphics system.

At first glance, CoreGraphics looks much more complex then Nebula2 because there are many more classes. The reason for this is simply that CoreGraphics classes are smaller and more specialized. The functionality of most classes can be described in one simple sentence, while Nebula2 had quite a few classes (like nGfxServer2) which were quite big because they tried to do several things.

A typical Nebula3 application won't have to deal very much with the CoreGraphics subsystem, but instead with higher-level subsystems like Graphics (which will be described in a later post).

Some of the more important design goals of CoreGraphics are:
  • allow ports to Direct3D9, Direct3D10 and Xbox360 without ANY compromises:
    • CoreGraphics allows much more freedom when porting to other platforms, instead of Nebula2's porting-through-virtual-functions approach, CoreGraphics uses porting-by-conditional-typedefs (and -subclassing). A port is free do override any single class without any performance compromises (e.g. platform-dependent inline methods are possible)
  • improved resource management:
    • Nebula3 decouples resource usage and resource initialization. Initialization happens through ResourceLoader classes, which keeps the actual resource classes small and tight, and the resource system is much more modular (to see why this is a problem that had to be solved, look at Nebula2's nTexture2 class)
  • less centralized:
    • instead of one big nGfxServer2 class there are now several more specialized singletons:
      • RenderDevice: handles rendering of primitive groups to a render target
      • DisplayDevice: handles display-setup and -management (under Win32: owns the application window, runs the Windows message pump, can be queried about supported fullscreen modes, etc...)
      • TransformDevice: manages the transformation matrices required for rendering, takes View, Projection and Model matrices as input, and provides inverted and concatenated matrices (like ModelViewProjection, InvView, etc...)
      • ShaderServer: the heart of the shader system, see below for details
  • improved offscreen rendering:
    • rendering to an offscreen render target is now treated as the norm, as opposed to Nebula2 which was still designed around the render-to-backbuffer case, with offscreen-rendering being possible but somewhat awkward
  • vastly improved shader system:
    • provides the base to reduce the overhead for switching and updating shaders when rendering typical scenes with many different objects and materials
    • as in Nebula2, a Shader is basically a Direct3D effect (a collection of techniques, which are made of passes, which are collections of render states)
    • ShaderInstances are cloned effects with their own set of shader parameter values
    • setting shader parameters is now much more direct through ShaderVariables (same philosophy as DX10)
    • ShaderVariations and ShaderFeature bits: A shader may offer different specialized variations which are selected through a feature bit mask. For instance a feature may be named "Depth", "Color", "Opaque", "Translucent", "Skinned", "Unlit", "PointLight", and a shader may offer specialized variations for feature combinations like "Depth | Skinned", "Color | Skinned | Unlit", "Color | Skinned | PointLight". The high level rendering code would set feature bits as needed (during the depth pass, the Depth feature would be switched on for instance), and depending on the current feature bit mask, the right specialized shader variation would automatically be selected for rendering. Together with the right asset tools, ShaderVariations and ShaderFeatures should help a lot to fix the various maintenance and runtime problems associated with programmable shaders (think shader-library vs. über-shaders and so on...).
A few other smaller improvement of CoreGraphics vs. gfx2 are:
  • handling DeviceLost/Restored and of WinProc mouse and keyboard messages is now handled through general EventHandlers instead of being hardwired into the graphics system
  • VertexBuffer and IndexBuffer are back as public classes
  • vertex components now support compressed formats like Short2, Short4, UByte4N, etc...
  • DisplayDevice offers several convenience methods to get the list of supported display modes or the current desktop display mode, and to get detailed information about the current display (hardware, vendor and driver version info)
  • one can now actually check whether 3d rendering is supported on the current host by calling the static RenderDevice::CanCreate() method before actually opening the application window
That's pretty much all there is to know, next up are Resource, Models and Graphic subsystems...