26 Aug 2007

More fun with HTTP...

I pretty much finished the HttpServer stuff over the weekend. All in all it took me about 10 hours of work (I don't even want to imagine how long it would have taken in Nebula2, probably a week or even longer, and the result wouldn't look half as elegant). The HttpServer is a singleton with a TcpServer inside, and a number of attached HttpRequestHandlers. A HttpRequestHandler is a user-derivable class which processes an http request (decoded by a HttpRequestReader) and produces a content stream (usually an HTML page) which is wrapped into a HttpResponseWriter object and sent back to a web browser. HttpRequestHandlers may accept or reject a http request. When a new request comes in from a web browser, the HttpServer asks each HttpRequestHandler in turn whether it wants to handle the request. The first handler that accepts will process the request. If no attached request handler accepts, the "default handler" has to produce a valid response (the default handler usually serves the "home page" of the Nebula3 application).

To simplify producing valid HTML pages, I wrote a HtmlPageWriter. This is a subclass of StreamWriter and has an interface which allows to insert HTML elements and text into the stream. All basic HTML markup elements up to tables are supported, so a HttpRequestHandler can do pretty advanced stuff if it wants to.

Here's how it works from the outside:

When a Nebula3 application is running (should be derived from App::RenderApplication, this sets up everything automatically), start a web browser on the same machine, and type the address http://127.0.0.1:2100 into the address field. This opens a HTTP connection on the local machine on port 2100 (it's also possible of course to do this from another machine as long as the port 2100 isn't blocked).

The browser should now display something like this:


This is basically the debug home page of the application. At the top there's some basic info like the company and application name that have been set in the Application object. The calendar time displays what the application thinks the current time is. It will update when hitting F5 in the browser.

The Available Pages section lists all attached HttpRequestHandler objects. By deriving new classes from HttpRequestHandler and attaching an instance to the HttpServer singleton the Available Pages list will grow automatically.

To go to the application's Display debug page, either click on the link, or type http://127.0.0.1:2100/display into the browser's address bar. This should bring up the following page:

The Display page lists the properties of the currently open display, and some information about the display adapters in the system.

But there's also more powerful stuff possible. The IO page lists (among other things) all currently defined assigns, and how they resolve into file system paths. The IO page can be reached through the URI http://127.0.0.1:2100/inout. However, the request handler that builds the IO debug page also has a complete directory lister integrated, so one can check what files the application actually can see under its assigns. Typing http://127.0.0.1:2100/inout?ls=home: into the browser's address field produces this page:


You can actually click on subdirectories and navigate through the entire directory structure. This is all basic stuff which I wrote in the last 3 hours (in the ICE train to Berlin), so it's really simple and fast to get results from the HttpServer system. The one thing that's missing now is a Base64Writer so that it would be possible to send binary data over the line (especially image data), and of course a few more HttpRequestHandlers which expose more debug information to the web browser.

25 Aug 2007

Fun with HTTP

Now that Nebula3 is getting a little more complex it's important to know what's going on inside a running application. In Nebula2, there were a couple of in-game debug windows (a texture browser, a "watcher variable" browser, and so on...). But creating new windows was a difficult and boring job (especially because of all the layout code). For Nebula3 I wanted something easier and more powerful: a simple built-in HTTP server which serves HTML pages with all types of debug information. The idea isn't new, others have done this already, but with Nebula2's IO and networking subsystems it wasn't a trivial task to write a HTTP server.

Turns out in Nebula3 it's just a couple of lines (I was actually surprised myself a little bit, even though I wrote the stuff), the TcpServer class already handles all the connection stuff, throw in a couple of stream readers and writers to decode and encode HTTP requests and responses and you have a simple HTTP server written in a couple of hours.

Here's roughly how it works:
  1. create and open a TcpServer object
  2. once per frame (or less frequently) poll the TcpServer for an array of TcpClientConnections (these are all the outstanding requests from web browsers)
  3. for each TcpClientConnection:
    1. attach a HttpRequestReader to the receive stream
    2. attach a HttpResponseWriter to the send stream
    3. decide what to send back based on the request, and fill the response with the result
    4. call TcpClientConnection::Send()
That's it. The actual HTTP protocol coding/decoding happens in the new classes HttpRequestReader and HttpResponseWriter.

Here's the first message served from a Nebula3 application into a web browser:



What's missing now is some sort of HtmlWriter to write out simple HTML pages with the actual content, and a HttpImageWriter to send images to the web browser (to provide a gallery of all currently existing Texture objects).

Fun stuff.

21 Aug 2007

Dead Rising: 2nd Chance

The first time I looked at Dead Rising when it came out I was like "meh, broken game mechanics". The things that put me off where the time-window mission system and the save-mechanism. I'm glad I gave it a second chance last week, now I can't stop playing it, trying to get all the different endings and I still have to beat the final boss before the mighty BIOSHOCK comes out.

There are 2 things you just need to accept to enjoy the game:
  1. you need to hit level 10 before the zombie killing is really fun
  2. "replay" is the central gameplay element, you'll never be able to solve everything in a single play-through
Frank West keeps his level, equipment and skills across game sessions, and that's extremely important. You'll have to play through several times to level Frank up before you're ready for the true ending. The psychos are extremely annoying in the beginning, but on higher levels, and with the right tactic it's extremely satisfying to finally beat the shit out them after they killed you so often on the lower levels. The story cutscenes are actually very funny and entertaining if you like old-school zombie flicks. Did I mention the car and the maintenance-tunnels packed with zombies? 1000 kills-per-minute. Easily :)

Nebula3 PowerPoint slides

Here's the PowerPoint of the talk I gave yesterday at the GCDC in Leipzig:

n3talk_gcdc07.zip

If you don't have PowerPoint, there's a standalone reader available for download from MS, just google for "PowerPoint viewer".

GCDC is always fun. Unfortunately I could only stay for a few hours and then had to return to Berlin for work. GC is going to be HUUUGE this time. Looks like they've added an additional hall, the business center had to move into a new smaller hall across the "campus". I've been going to GC every year since it started, but I'm still impressed by the Leipzig Exhibition Centre's architecture and size, it's like a trip into the future.

14 Aug 2007

First Render: Xbox360

Ok, here's the Tiger example scene on the 360:


Visually not very impressive, just as the last screen. The point is that the basic Nebula3 render loop is now also running on the 360 (complete with asynchronous resource loading), side by side through exactly the same highlevel code and from the same source-assets as the PC-version.

It took me a bit longer then planned because of the 360's tiled rendering architecture, and because the rendering code is *not* just a straight port from the PC version (you pretty much can do this on the 360, it's just not as optimal as using the more 360-specific APIs).

Because of the tiled rendering I had to do a few basic changes to the CoreGraphics subsystem in order to hide the details from the higher level code:

CoreGraphics now gets hinted by the higher level rendering code what the rendered stuff actually is, for instance, depth-only geometry, solid or transparent geometry, occlusion checks, fullscreen-posteffects etc...

The second change is that RenderTargets have become much smarter. A RenderTarget object isn't just a single rendering surface as in Nebula2, instead it may contain up to 4 color buffers (with multisampling if supported in the specific configuration) and an optional depth/stencil buffer, it has Begin()/End() methods which mark the beginning and end of rendering to the render target (this is where the rendering hints come in, to let the RenderTarget classes perform platform-specific actions before and after rendering).

A render target now also knows how to resolve its content most efficiently into a texture or make it available for presentation. Traditionally, a render target and a texture was the same thing in Direct3D. So you could render to a render target, and when rendering was finished the render target could immediately be used as a texture. This doesn't work anymore with multisampled render targets, those have to be resolved into a non-multisampled texture using the IDirect3DDevice9::StretchRect() method. On the 360 everything is still a little bit different depending on the rendering scenario (720p vs. 1080p vs. MSAA types). So the best thing was to hide all those platform-specifics inside the RenderTarget object itself. A Nebula3 application doesn't have to be aware of all of these details, it just sets the current render target, does some rendering, and either gets a texture from the render target for subsequent rendering, or presents the result directly.

I also started to work on a "FrameShader" system. This is the next (simplified) version of Nebula2's RenderPath system. It's basically a simple XML schema which contains a description of how a frame is exactly rendered. It has 2 main purposes:
  • grouping render batches into frame passes (e.g. depth pass, solid pass, alpha pass) and thus eliminating redundant per-batch state switches (state which is constant across all objects in apass is set only once)
  • easy configuration of offscreen rendering and post effects without having to recompile the application
Since RenderPaths were hard to maintain under Nebula2 I originally planned to scrap them and do the frame rendering through hard-coded C++ classes. But going on with the Nebula3 rendering code I realized that the basic idea is good, the Nebula2 render paths are just much too granular and complex (a typical N2 render path file is ~500 lines of XML). So the focus for the FrameShader system will be on a simpler system that is easier to maintain.

4 Aug 2007

The Nebula3 Render Layer: Graphics

The Graphics subsystem is the highest level graphics-related subsystem in the Render Layer. It's basically the next version of the Mangalore graphics subsystem, but now integrated into Nebula, and connected much tighter to the lower level rendering code. The basic idea is to have a completely autonomous graphics "world" with model-, light- and camera-entities and which only requires minimal communication with the outside world. The main operations on the graphics world are adding and removing entities to/from the world, and updating their positions.
Since the clear separation line between Mangalore's Graphics subsystem and Nebula2's Scene subsystem has been removed completely in Nebula3, many concepts can be implemented with less code and communication overhead.

The Graphics subsystem will also be the "multithreading border" for asynchronous rendering. Graphics and all lower-level rendering subsystems will live in their own fat-thread. This is fairly high up in the Nebula3 layer model, but I choose this location because this is where the least amount of communication needs to happen between the game-play related code and the graphics-related code. With some more "built-in autonomy" of the graphics code it should be possible to run the game code at a completely different frame rate then the render code, although it needs to be determined through real-world experience how practical that is. But it's definitely something I'll try out, since often there's no reason to run game play code at more then 10 frames per second (Virtua Fighter fans may disagree).

The most important public classes of the Graphics subsystem are:
  • ModelEntity
  • CameraEntity
  • LightEntity
  • Stage
  • View
A ModelEntity represents a visible graphics object with a position, bounding volume and an embedded Model resource. A Model resource is a complete 3d model with geometry, materials, animations, transformation hierarchies etc... (more on that in a later post).

A CameraEntity describes a view volume in the graphics world. It provides the View and Projection matrices for rendering.

A LightEntity describes a dynamic light source. The exact properties of Nebula3 light sources haven't been laid-out yet, but I'm aiming for a relatively flexible approach (in the end a light source isn't much more then a set of shader parameters).

Stages and Views are new concepts in the Nebula3 Graphics subsystem. In Mangalore there was a single graphics Level class where the graphics entities lived in. There could only be one Level and one active Camera entity at any time. This was fine for the usual case where one world needs to be rendered into the frame buffer. Many game applications require more flexible rendering, it may be necessary to render 3d objects isolated from the rest of the graphics world with their own lighting for use in GUIs, additional views into the graphics world may be required for reflections or things like surveillance monitors, and so on... In Mangalore, the problem was solved with the OffscreenRenderer classes, which are simple to use but were added as an after-thought and have some usage restrictions.

Nebula3 provides a much cleaner solution to the problem through Stages and Views. A Stage is a container for graphics entities and represents its own little graphics world. There may exist multiple stages at the same time but they are completely isolated from the other stages. An entity may only be connected to one stage at a time (although it is easily possible to create a clone of an existing entity). Apart from simply grouping entities into a graphics world, the main job of a Stage is to speed up visibility queries by organizing entities by their spatial relationship. An application may implement radically different visibility query schemes by deriving subclasses of Stage.

A View object renders a view into a stage through a CameraEntity into a RenderTarget. There may be any number of View objects connected to any stage. View objects may depend on each other (also on Views connected to a different stage), so that updating one View may force another View to update its RenderTarget first (this is handy when one View's rendering process requires the content of another View's render target as a texture). View objects completely implement their own render loop. Applications are free to implement their own render strategies in subclasses of View (e.g. one-pass-per-light vs. multiple-lights-per-pass, render-to-cubemap, etc...).

So, in summary, a Stage completely controls the visibility query process, while a View completely controls the rendering process.

One of the main jobs of the Graphics subsystem is to determine what actually needs to be rendered by performing visibility queries between entities. A visibility query establishes bi-directional visibility links between entities. Visibility links come in 2 flavors: camera links and light links. Camera links connect a camera to the models in its view volume. Since visibility links are bi-directional, a camera knows all the models in its view volume, and a model knows all the cameras it is visible through. Light links establish the same relationship between lights and models. A light has links to all models it influences, and a model knows all lights it is influenced by.

The most important class to speed up visibility queries is the internal Cell class. A Cell is a visibility container for graphics entities and child cells. A Cell must adhere to 2 simple rules:
  1. if a Cell is completely visible, all its entities and child Cells must be completely visible
  2. if a Cell is completely invisible, all its entities and child Cells must be completely invisible
Cells are attached to a Stage, and they form hierarchies with one root Cell at the top. The standard Cell class allows simple spatial subdivision schemes like quad-trees or oct-trees, but an application may derive its own Cell subclasses to implement different visibility schemes like portals. The only limitations to the functionality of subclasses are the 2 visibility rules outlined above.

When a graphics entity is attached to a Stage, it will be inserted into the lowest level Cell which "accepts" (completely contains, usually) the entity. When updating the transformation or bounding volume of a graphics entity it will change its position in the Cell hierarchy if necessary.

Stages are populated through the StageBuilder class. An application should derive from StageBuilder to create the initial state of a Stage by adding Cells and Entities to it. Nebula3 comes with a standard set of StageBuilders which should suffice for most applications.

This was just a rough overview of the Graphics subsystem. Since there exists only a very basic implementation at the moment, many details may change over the next few weeks.

29 Jul 2007

First Render

First Nebula3 screenshot fresh from my notebook (please excuse the jpg artefacts):


It's the Tiger tank from our toolkit examples, loaded through the legacy N2 resource loaders. Visually, it's nothing to write home about of course - it's rendered using the most simple textured shader possible. But nonetheless this is a very important milestone for Nebula3 because all essential building blocks of the Render Layer are now up and running, and a simple render loop is possible. A lot of things still need to be done of course: putting the renderer into its own thread, occlusion culling, realtime lights and shadows, lots of tests and benchmarks, and so on... but once the first triangle is on the screen, the rest is easy ;)