Brand new site (almost)

8:37 pm in Tesla Engine, Website by Starnick

Introducing the newly re-skinned and feature improved site!

I can admit I don’t have a knack for web site development with php (seems like I waste so much time getting something up that by the time I’m done, I’m already dissatisfied with it!). We’re still running wordpress, but with buddypress plugins. I actually like the default buddypress theme (very similar to the old theme I was attempting to tweak). It feels sleek and not intrusive for the user. I’ve decided against groups and the bbpress that comes with buddypress. BBPress is lightweight, but feels too lightweight. I’m used to a regular forum, but it seems like there’s very few options to visually integrate forum software with wordpress and still have things load fast (and not be a hack nightmare). So I opted for WP-Forum, which I’ve tried in the past and reasonably liked. It seems to fit well, without any effort, and we’ll be using it as a messaging board.

The next item I’m really happy about with the new theme is the width. Biggest problem with the old one, on my home computer (wide screen monitor) the old theme made the site feel very small. This was a problem with code snippets, and long posts (like the last one). The new site isn’t 100% width, but it’s a lot bigger which feels just right.

Finally we have a dokuwiki that’s visually integrated with the site, which I’m quite happy to report was rather painless. So far I really like doku wiki. It’s simple and minimal, compared to mediawiki (which I’ve used in the past). And it visually integrates quite nicely. The integration is largely seamless and all wordpress functionality works with it. Still need to flesh it out – first order of business are installation requirements and tutorials.

There’s quite a lot to do, code wise. I’m aiming to get the first release (which includes the samples I’ve been preparing) out by the end of June. As always, the engine sources are available on google code.

Materials

3:00 am in Tesla Engine by Starnick

(Caution: Long post)

Background

In computer graphics the term “Material” tends to get thrown around as something that describes the texture or color of an object, and so tends to be very vague. The concept of materials has been something that I’ve wrestled with way back in the old days of Spark Engine, mostly because I hate how most materials end up to be a class that has a very specific purpose and not at all flexible. In most cases this implies the material has properties for color (diffuse, specular, ambient, emissive) and one or two textures.
As is the overall theme of Tesla, I like things to be modular and flexible. Nothing hard-coded, and thus the specifications of how I were to design the material system were:

  • Assume nothing! Every developer has their own needs and will want to use their own shaders. Do not impose a restriction, just let them do what they want. The engine’s shader library is a starting point and is there to fit most developer needs (much like XNA’s BasicEffect). I do not want to box people in that they have to use my shaders or my lighting design. This means no properties such as “DiffuseColor” or “Texture”.
  • Extensible! Be able to easily add new functionality and logic without having to subclass. This specification has two purposes:

1. Be able to easily bind shader uniforms to data automatically. Either have engine-defined values or let the developer define their own modules which will extract data from their geometry (or other classes) and then send that data to the shader without any effort on the developer’s part. An example of this is how lights are handled (read on for that).

2. This can be billed as an argument for “Preshaders”, where you’re able to add and execute logic in order to do computation on the CPU before sending it to the device for performance reasons (e.g. concatenate World-View-Projection matrix once on CPU, rather than doing it hundreds or thousands of times on the GPU).

My first attempt (with SparkEngine’s design) was a material class that was composed of definitions. You had a texture definition, a light definition, and a world definition. These “Material Definitions” would pluck data from your mesh (world matrix, lights and textures) and renderer (camera position, view/projection matrices). The developer could come up with their own definitions to re-implement the default logic in a different way, or expand on it. E.g. if you wanted to use a different lighting equation you’d replace the default light definition with your own and use a custom shader.

I was satisfied with this design scheme for the most part, but I ran into problems while trying to port it over to Tesla’s new design. The problem lied mostly with how render states were handled. In SparkEngine, lights and textures were also considered “render states” and added to the scene graph. In Tesla, lights are added to the scene graph, but textures are not. And they no longer were render states, instead Tesla followed the D3D10/11 example of the four basic render state objects (Sampler, Rasterizer, DepthStencil, and Blend). Additionally, I decided that these should not be managed by the scene graph, but be directly apart of the material. So I took another crack at the design.

Design Overview

At a high level, the Material class in Tesla is composed of the following:

  1. An Effect that it directly manages. You’re able to set the current technique, set parameters, etc.
  2. Render state management. For every technique and every pass in the effect, you’re able to set a render state. When the pass is applied, these are sent to the renderer (where if they are not found in the cache, are applied to the device). I prefer to set render states at the application level, and not the shader (fx / cgFx file) level.
  3. Parameter management. The material has SetParameter/GetParameter which are more or less identical to setting/getting on an IEffectParameter. However, it’s safer since setting/getting parameters that don’t exist or are invalid for the piece of data you’re setting will just fall through and it caches the parameter value. This allows us to clone a material without reading anything back from the device, which can be useful to quickly copying an effect as well as re-using data (or textures).
  4. Engine Values. This is a biggie and one of the more important details of the design. These are engine-defined values that are automatically sent to your shader, if you tell the engine to bind the enumerations to your material. This means parameters such as the World-View-Projection matrices do not have to be manually set by the user every frame, as well as other data such as the camera’s position (eye for lighting). More on this below.
  5. Material logic. This replaced the idea of “definitions” which had gotten a bit too cumbersome. You’re able to write and add pieces of logic (Preshaders) to a material that is then executed when the material is applied. If there’s something the system does not provide, this is how you remedy that. Logic is the first thin executed (before states are applied or engine values served). Every piece of logic is bound to its parent material, and is sent the renderer and renderable when ApplyMaterial() is called.

Material logic is actually how lighting is implemented in the engine. Every material is created with a default lighting logic module that binds effect parameters based on a semantic or name (configurable of course!) and uses IRenderable’s world lighting list to apply them to the shader. It can apply light data to either a single uniform or to an array of uniforms (by default, the logic looks for an effect parameter with a semantic called “LIGHTLIST”).

So this means you can ignore, reconfigure, or replace entirely how lighting data is sent to the device by the engine. This is very convenient if you want a different rendering path such as Deferred Rendering. The engine is by default, a Forward Renderer, but with a little tweaking you should be able to easily setup a deferred render path quite easily by bypassing the default light logic and build a global light list from all your renderables (or not even use them).

And there’s more!

Two words: Material scripts.

I”ve been quite happy with how Tesla’s Material system has turned out, which is ironic considering it’s a lot simpler than its previous incarnation (maybe not that ironic). However, from the very beginning I had the eventual goal to incorporate scripting (a la OGRE3D material script, but simpler). Scripting is a very powerful tool since you have a way to easily bind data to your shader in an artist-friendly way using text files. Also, in moy castes once an object’s material is defined, it tends to stay static. If you have a lizard-on-a-rock model that uses normal mapping, most likely its color and textures stay constant (if they do, we’ll let you handle that). Additionally, many models may use the same shader, but a different color or texture. So it would be great if you could define a template that describes a material, then re-use it later down the road and change some specifics. This also helps in managing content, since you’re removing it from the code almost completely (if you need to change something, you’re editing a text file, not a code file).

Enter “Tesla Engine Material” or TEM files (nifty name, eh?). Here’s a feature overview

  1. Every material script indicates an effect (by file path) and a technique to use.
  2. Shader parameters are able to be set, by name, directly in the script. This includes all supported parameter types (float, int, bool, Vectors, Matrix, Quaternion, and Textures). Setting a texture means setting its file path.
  3. Binding engine values to shader uniforms, by name. This tells the material that the effect parameters should be cached and be tied to one of the EngineValue enumerations. E.g. a float4x4 uniform named WVP bound to EngineValue.WorldViewProjection means your shader will be served that data everytime it prepares to render your geometry.
  4. Render states. You’re able to create render states and name them (currently the script only supports the pre-built states). You’re then able to bind these states to 1 or more of the passes in any of the effect’s techniques. By giving them a name, that allows you to re-use states. If two passes require a the same BlendState, you’re only creating one and re-using it.
  5. Inheritance – every material can inherit from another script (only one depth at the moment). This means all the parameters, engine values, and renderstates are carried over, or overwritten. This is very convenient when used in conjunction with the built-in shader library, since you’re able to inherit from the engine’s standard materials.

Bottom line:

You’re able to alter an object’s material, whether it be a texture/color/render state without having to write any C# code. That should make artists downright giddy with excitement. You don’t need to write any code, just edit a text file. Of course, you can completely bypass scripting and programmatically create the same shaders. Its your choice.

Note on engine values:

This is an area I recently enhanced. Originally they were managed by the Renderer, now they’re managed in a value map by the Engine static class. This value map requires you to set the current camera and/or the timer you use to get all the valid engine values. The map also provides a random number stream that can be used to feed random values to yoru shader. The only thing you need to do when rendering geometry (which the scene graph does for you) is set the world matrix, everything else is taken from the camera/timer.

Examples

Below are some examples, I won’t get into the script syntax but it should be easy enough to understand what’s going on.

From the engine’s shader library, a lit material that has no textures/vertex colors.

Material LitBasicColorMaterial {
	Effect {
		File : Shaders//LitBasicEffect.fx
		Technique : LitBasicColor
	}

	MaterialParameters {
		Vector3 MatDiffuse : 1.0 1.0 1.0
		Vector3 MatAmbient : .2 .2 .2
		Vector3 MatEmissive : 0 0 0
		Vector3 MatSpecular : 0 0 0
		float MatShininess : 16.0
		float Alpha : 1.0
	}

	EngineParameters {
		WorldViewProjection : WVP
		WorldMatrix : World
		WorldInverseTranspose : WorldIT
		CameraPosition : EyePos
	}
}

Here’s another that’s extremely simple but shows inheritance. The child material inherits from the built-in skybox shader that references a TEBO shader file and specifies the engine values and render states. This script uses all of that and adds in a cubemap.

Material MySkyBoxMaterial : SkyBox.tem {

	MaterialParameters {
		TextureCube DiffuseMap : Textures/purplenebula.dds
	}
}

Lastly, here’s a complex example showing render state setting. It’s the material I created for my planet shader I posted about earlier in the month.

Material PlanetMaterial {

  Effect {
    File : Shaders/PlanetEffect.tebo
    Technique : Planet
  }

  MaterialParameters {
    Texture2D DiffuseMap : Textures/earth_diffuse.dds
    Texture2D CloudMap : Textures/earth_clouds.dds
    Texture2D NormalMap : Textures/earth_normal.dds
    Texture2D SpecularMap : Textures/earth_specular.jpg
    Texture2D LightsMap : Textures/earth_lights.dds
    bool UseSpecularMap : true
    float SkyScale : 2.5
    float CloudScale : 1.0
    float CloudRotateDirection : -1.0
    Vector3 InvWaveLength : 5.602 9.478 19.646
    float InnerRadius : 200
    float OuterRadius : 205
    float InnerRadiusSquared : 40000
    float OuterRadiusSquared : 42025
    float KrESun : .0375
    float KmESun : .0225
    float Kr4PI : .0314
    float Km4PI : .0188
    float Scale : .2
    float ScaleOverScaleDepth : .8
    float ScaleDepth : .25
    float InvScaleDepth : 4
    float G : -.95
    float GSquared : .9025
  }

  EngineParameters {
    WorldViewProjection : WorldViewProj
    WorldMatrix : World
    CameraPosition : CamPos
  }

  RenderStates {
    RasterizerState rs : CullBackClockwiseFront
    BlendState bs : AdditiveBlend
  }

  Technique Planet {

    Pass SkyFromSpace {
      RasterizerState : rs
      BlendState : bs
    }

    Pass CloudsFromSpace {
      BlendState : bs
    }
  }
}

Content Management

As mentioned, the nice thing about material scripts is that you can reference a TEBO file and not fx/cgfx/glsl files. All the material knows is that there’s some effect it needs to load and that’s it. This means you’re able to use a material script that can potentially reference several different versions of the same shader that are each specific to a certain platform. For example, the skybox example inherits from a material that is found in the default content managers of the D3D10 and XNA render systems. There’s two versions of the same material for each, but the child material doesn’t know nor needs to know, it inherits from whichever is available at runtime.

So another benefit of material scripts is you can split up your shader content more easily if you want to support different platforms, but in you code you only need to reference one piece of content – your TEM file.

TODO List

This isn’t yet a feature complete system, some stuff left to do:

  1. Better stability. Currently it’s “stable enough”, although it may blow up in your face if you use comments. The parser does support // style comments, but ensure you space them out from your script variables. If it does return an error in parsing, the parser tells you roughly your line and column where the error happened.
  2. Render state configuration. Currently you can only set the pre-configured states (by their name). At some point I’d like to have it where you’re able to set their properties.
  3. More than one material in a single text file. Currently you can only have one. I’d like to make it where you can hand over a single TEM file with several materials to a model loader, and have the loader use materials whose names correspond to geometry names. This should make loading geometry with custom materials a lot easier when you have a model with more than one mesh. Or when you compile a scene graph, all the materials used are dumped in a single TEM file (and by scene graph I mean a model with more than 1 mesh).
  4. Specifiy material logic. Be able to specify logic modules directly in the script and specify their properties.

The last bit is inspired (and material logic in general) by valve’s material script actually, notably for the material used when the spy cloaks in Team Fortress 2, where an alpha value is set and changed over time to give the illusion he’s cloaking.

Release .5 and Engine Samples

9:29 pm in Tesla Engine by Starnick

I was asked the other day if there was a sample project, such as a “Hello World” to get started with using the engine. I am working on a sample browser where you’re able to adjust renderer parameters and choose a sample to run, although that’s not yet ready for release. In the meantime however, I have put together an extremely simple, and minimal example of creating engine services, a window, and updating/rendering a simple scene graph. You can find the download here. The example uses the Direct3D10 renderer (which is the most stable), although I provide the assemblies for the XNA 4.0 renderer (one line change). You will need the March 2011 release (for .NET 4.0) of SlimDX in order to run the sample with the Direct3D10 renderer. You can download the SlimDX SDK or end-user runtimes here (either should work).

I aim to have the sample browser very easy to install, complete with an installer to get the user setup with the right runtime dependencies. And eventually have an installer for the engine itself (end-user and a developer SDK). Usually to get started with many open source engines, you have to follow installation guidelines and download/install everything yourself. I aim to do better.

With that said, I’m starting to finalize the features for the engine’s first official release using the project’s issue tracker. I have quite a bit left to do, which is mostly to improve the stability of the engine’s renderers and to solidify the current API.

Engine Organization Part 2: Graphics System

9:25 pm in Tesla Engine by Starnick

In the last post, I explained that Tesla uses the concept of services, rather than singleton managers. Perhaps the best example of this is the render system. This post will go into more details about the Tesla.Graphics and Tesla.Graphics.Renderer namespaces and the differences between the two.

Graphics System

Tesla’s graphics API has two goals in mind:

1. To resemble the XNA API. I really do like the XNA API, although others may disagree. It’s clear and easy to use.

2. To be independent of the rest of the engine.

Goal #2 is a biggie. Much like how XNA 4.0 separated the framework out into multiple assemblies (e.g. core framework, Graphics, Game), I wanted the engine to be the same (this follows the idea of modularity explained in the last post too). Just like in XNA, you can choose to use a vertex buffer directly and not use the ModelMesh classes. The engine’s scene graph is the same, it uses the graphics classes, but it’s not a requirement. You can easily use the graphics objects in creating your own scene management.

With that said, unlike XNA, creating a vertex buffer or effect does require the renderer, which is not 100% analogous to XNA’s GraphicsDevice class. The renderer in Tesla is more than just a reference to the graphics device and has additional layers that XNA’s GraphicsDevice does not.

The Renderer

At the heart of every implementation of IRenderSystem is the renderer, which is defined by the interface IRenderer and partially implemented in BaseRenderer. This is the class that allows the user to draw primitives to the screen, set render states, and perform other graphics operations. The render system also provides:

  • Render state caching
    • Reduces redundant state switching
  • Render queue
    • Bucket system for organizing geometry and control the order of how they’re rendered (e.g. sorting transparent objects)
  • Cameras
  • Materials
    • An extension of Effect objects. I’ll have a whole post devoted to this. A material contains high level management of a shader.
  • Default Content
    • Content manager that serves the engine’s built-in shader library and other platform-specific content. E.g. SM 4.0 shaders for Direct3D10 and effect shaders for XNA 4.0 built using its content pipeline.

Geometry that is added to the render queue all implement the IRenderable interface, which the the scene graph implements, making the render system independent of the scene graph.

Engine Organization Part 1: Services

8:51 pm in Tesla Engine by Starnick

From the beginning, the goal for Tesla was to write an abstraction layer for the graphics system. I also wanted the primary engine assembly to be pure C# and not rely on any unmanaged code. This would make porting it from .NET to Mono easier in the future, for OpenGL. So the question that I had to answer was: How do we create objects and manage them?

Many graphics engines use an Abstract Factory design pattern where to create a D3D-specific object, you first create a D3D factory then ask it to create the D3D object. These classes would implement or extend abstract classes, so you’d be able to swap out different implementations at startup or even during runtime. In most cases these factories, or managers as they’re usually called (e.g. RenderSystemManager, InputManager, TextureManager, etc) are singleton classes. They’re singletons since obviously you only want one TextureManager or one RenderSystemManager. Or you’d like to be able to create a texture or vertex buffer without having to pass these managers all over the place. And since interfaces or abstract classes are used to represent the concrete objects, you don’t have a constructor for them. E.g. to create a D3DVertexBuffer that implements IVertexBuffer, you need to call RenderSystemManager.CreateVertexBuffer().

Singletons are generally considered bad, and I’m personally against them. If I need something like what a singleton offers, i.e. a class I can reference globally, I prefer static classes. It’s my opinion that having to ask a factory to create a vertex buffer object (or other similar objects) is a bit cumbersome. I’d much rather call a constructor and let the engine do the leg work.

At the time I was interested in using a dependency injection framework, such as Ninject. Eventually I opted for a simpler solution that doesn’t require any third party libraries: Service Providers. This is a design pattern that is more or less like Strategy. In the engine, all objects that would normally be singleton factories/managers are instead turned into services. In effect this is what Ninject would do; at startup I bind the concrete implementations of each service to its interface. The services are stored in a collection that is owned by a static class called Engine. While this may be a drawback similar to using singletons (e.g. coupling issues of relying on objects in global space), I only have to deal with a single static object which only serves as the initial point of entry in creating/destroying the engine services. The services themselves are regular objects. So, I have code similar to:


//Create render system
Engine.Services.AddService(typeof(IRenderSystemProvider),new D3D10RenderSystemProvider());

...

//Get the renderer
IRenderer renderer = Engine.Services.GetService<IRenderSystem>().Renderer

//Cull, draw scene
_rootNode.Draw(renderer);

//Flush render queue and render objects
renderer.RenderQueue.SortRenderClear();

Everything is a service in Tesla, not just the graphics system. This includes operations concerning input, windowing, and audio. Each of these systems is a self-contained service, so the engine is fairly modular. The engine can be extended to use different input means (e.g. RawInput) or a different sound system, without having to change code. Services can also be re-used, e.g. both XNA and DirectX Tesla applications use the same windowing system (for Windows). Here’s a quick list of the service providers currently in the engine:

  • IRenderSystemProvider (implementation in Tesla.Direct3D10 and Tesla.Xna assemblies)
    • Responsible for creating graphics object implementations.
    • Serves “default content” (more on this later)
    • Owns the renderer
  • IMouseProvider (implementation in Tesla.Windows assembly, using P/Invoke – eventually will have a RawInput  version)
    • Owns a mouse wrapper, used by the static “Mouse” class
  • IKeyboardProvider (implementation in Tesla.Windows assembly, using P/Invoke – eventually will have a RawInput version)
    • Owns a keyboard wrapper, used by the static “Keyboard” class
  • IWindowProvider (implementation in Tesla.Windows assembly, using Forms/Controls)
    • Creates window implementations and hosts to run your application.

Future services can easily be added (e.g. game pads, joysticks, although for the input classes we do use static classes just to make things easier. However, they’re initialized using these services). And it doesn’t have to be a big service (e.g. Audio is not yet present), it could be something more for logical organization. If you’re familiar with game services in XNA, engine services can be used in a similar fashion.

Creating Graphics Objects

When creating vertex buffers and effects, all you have to do is call their constructor. You don’t need to query for the render system, or call a factory method. Graphics objects were designed following the bridge design pattern. All graphics objects extend from a “GraphicsResource” abstract class. Each graphics resource owns an implementation. When you create a graphics object, it queries the render system provider to create the underlying implementation. So in effect, all the objects you see in the Tesla.Graphics namespace, like VertexBuffer, IndexBuffer, Texture2D, etc are all shell objects. The real importance are the VertexBufferImplementation, IndexBufferImplementation, and Texture2DImplementation classes, which have concrete implementations in the Tesla.Direct3D10 and Tesla.Xna assemblies. There is a certain level of redundancy here, but it’s more for consistency and ease of use. Since Tesla is the successor of an XNA engine, I also wanted to follow the XNA API. These classes really are just wrappers for managing their implementations. Creating graphics objects are as easy as:


VertexBuffer vb = new VertexBuffer(...);

Texture2D tex = new Texture2D(...);

...

//Inside the vertex buffer constructor

public VertexBuffer(...) {

IRenderSystemProvider rs = Engine.Services.GetService<IRenderSystemProvider>();

//Throw error if render system is null

_implementation = rs.CreateVertexBufferImplementation(...);

}

And there you have it, a basic overview of how the engine is organized.