This book is a work in progress, comments are welcome to: johno(at)johno(dot)se

Back to index...

The Pitch

Here is a PDF version of this pitch which I made for a lecture.

Origins

MVC origins (Reenskaug?)

multiple views example: Model, PieChart, SpreadSheet, BarChart

Compare to MFC's Document/View, where MFC's View acts as both Controller (handles input) and View (output/visualisation)

Document/View is quite useful, becase very often the context in which user input is applied depends on visualisation (i.e. a scrolling view of a document)

Implementation

Classic MVC and D/V both assume that Views are stateful. This means, in OOP terms, that View is implemented as one or more objects with encapsulated state and behaviour.

Example with charts... virtual void IView::draw() = 0;

This brings up the issue of synchronizing View state with Model state.

GOF covers various tradeoffs with the Observer pattern.

In general, MVC is useful in a wide range of applications, because it clearly separates concerns, and not least allows for multiple "views" of the same state.

However!

Though I agree that the premise of a View being stateful is indeed intuitive (due to trends towards training in OOP, i.e. classes/objects, encapsulation), it is at the same time severely limiting.

The main issue is the fact that Views implicitely cache Model state (as private object members), which brings rise to sync issues.

I believe that the premise that visualisation is/should be a stateful thing is false.

This premise or assumption is DEEPLY entrenched in a wide range of popular object oriented visualisation / gui packages:

During my time in the Swedish game development industry, every professional renderer that I have seen has conformed to this paradign, which is known as Retained Mode (in that the visualisation package retains state internally).

example: Ogre instance concept (Mesh/Texture/transform containment), general architecture of scene graphs, ff state as objects, i.e. z buffer on/off

Historically, this classic architecture was REQUIRED in order to deliver any kind of performance, i.e. heirarchical bounding trees for heirarchical frustum culling, matrix transform caches, etc. The premise was to "retain much state, and only update this state when absolutely required".

Win32/GDI is based on the same principle. Since it was historically (ca 1993) too expensive to repaint high resolution (640x480) displays at 60hz, due to multiple overlapping windows, etc, GDI operates asynchronously with a system of dirty rects and centrally managed rendering (WM_PAINT, callbacks, etc).

MFC, being an objects oriented skin over Win32, hides much of this, and instead exposes windows and widgets as classes/objects with interfaces for updating internal state required for correct rendering. The application constantly (and manually) moves state to and from the MFC objects in order to keep the "model" and "view" in sync.

Again, the premise was that this "retained" mode of operation was required for interactive performance.

However, due to the rapide advances of GPU based rendering over the past 10+ years, this premise no longer holds.

At the opposite pole is Immedate Mode rendering, where all state required for visualisation is passed to the rendering system explicitly when a drawing call is made, and little or no application state is retained inside the rendering system between explicit rendering calls.

Observe, somewhat ironically, that DirectX 3, ca 1996 had 2 modes of operation for graphics, namely Retained Mode and Immediate Mode. At least before DirectX 6 in 1998, Retained Mode was dropeed from the API, because game devs simply did not use it. They wanted more control. However, the trend since then has been to STILL build a Retained Mode wrapper (i.e. rendering engine) on top of this lower level IM interface to the hardware.

The reasons for this? ca 1998 HW T&L didn't exist, and an application was still required to do a lot of work in order to ensure good performance. For example, Quake 2 (1997) definitely used BSP/PVS to minimize the amount of geometry sent to the GPU.

Also, the low level interfaces were somewhat hard to use, and required specialists to utilize effectively, which for most games meant that some kind of "graphics engine / scene graph" abstraction made sense in order to keep the app-level programmers productive. Also, issues with asset pipelines (getting assets from the art tools into the game) probably also encouraged an "engine mentality".

Today (need reference) experiments with CUDA on SLI chipsets have outperformed supercomputers for computationally expensive programs. It stands to reason, that since the bus between the CPU and the GPU is quickly becoming the dominant bottleneck in graphics applications, there aren't very many "smart CPU optimizations" that we as programmers can apply that will speed things up. Brute force approaches are becoming surprisingly viable.

In DirectX9 is possible to render very large batches of primitives per draw call. At Jungle Peak we rendered 800 000+ vertices in a single call on nVidia GeForce 6 class hardware, with good performance. The meant a number of things, such as discarding the concept of camera culling. We simply batched together all instances of a particular mesh into a single huge vertex/index buffer pair (one per texture basically), and sent them all to the hardware with very few calls. This gave us better performance than attempting to chop up the world into segments and cull them on the app level before committing to hardware.

At a very high level, changes like this mean that there is no longer any performance reason to have a stateful visualisation.

MVC revisited

What is a non-stateful view? Basically it is a procedural interface (as opposed to a collection of objects with methods), in essence very much to what DirectX 9 is. What about ::SetRenderState() etc? I argue that all retained state inside of DirectX, to greatly simplify, is there to avoid having hundreds/thousands of parameters in the DrawPrimitiveXXX() methods. Drivers reserve the right to propagate state to the GPU at any time, even at DrawPrimitive time.

Observe also that the design of HLSL, which even further pushes the stateless concept (all render state is provided by the application to the shader at render time).

So, if View is "a bunch of functions" without state, where do we decided what gets rendered and when and how?

Enter the Controller. In my reasoning Controller has 2 jobs:

  1. doInput(): react to used input and direct how that input is allowed to change Model state
  2. doOutput(): dynamically, in real time, compose the current "view" of the application using View

This means that Controller dynamically and interactively "programs" View to present a visualisation to the user. This includes everything you see on the screen, including guis.

Controller manages both input and output.

View exposes, for use by Controller, ways to query user input as well as render output, but is in itself entirely passive.

Model encapsulates application state, and in the case of games, also applications logic / behaviour. OOP (i.e. classes and objects) is useful within the context of Model.

What are the gains?

Firstly the removal of most if not all of the state caches in View make synchronization with changing Model state completely trivial, if not non-existent. In many game applications, the job of keeping these two aspects of the system in sync is a MAJOR burden and source of bugs.

Secondly, the fact that Controller dynamically and procedurally "calculates" the "view" of the application each frame makes any number of very disparate Controllers (and therefore "views"), as well as the switching between them, very easy to implement.

For rapid game development this is very valuable, because it allows for the easy integration of various dev/debug Controllers. As Controller manages input as well as output this means that you can have radically different input schemes and the ability to switch between them, including "in-app" editing of various game specific concepts.

IMGUI, a new way of approaching guis, is central to exploiting this capacity. Ideas from Casey... the parallell to MVC became clear over time... the fit with MVC is very good.

The combination of MVC (multiple Controllers with easy switching) "views", IMGUI (rapid gui development and deployment) and Automated Persistence (basically a language extension that removes the need to worry about how data persists across application executions) enables a whole new level of game development, with better productivity from less code, faster design/implement/test/repeat cycles, and less bugs.

IMGUI? Automated Persistence? BUY THE BOOK!

Back to index...