Wasn't expecting to start this till tomorrow, but with my situation, I started today anyways. Torn my old render interface a new one and put together the skeleton for the new interface. Be proud of me, I kept the old interface and everything in the engine is still completely intact. There's actually two interface to choose from at the moment. The only thing lacking from the new one, is font and particleSystem support. The old system had a hackish way of using fonts and the particle system was very reliant on the old interface to work properly.
Funny I'm looking at the two headers... old header 145 lines ~15 of it comments... the new header 95 lines ~ half of it comments. That's also including that I incorporated the window, input, and frame timer into the new interface aswell.
new interface
#ifndef _ZECloud_H
#define _ZECloud_H
#include
#include "ZEMath.h"
#include "ZESystemWindow.h"
#include "ZESystemTimer.h"
#include "ZEInputInterface.h"
#include
#include
#include "ZEGraphicsDevice.h"
#include "ZEGraphicsTextureCache.h"
#include "ZEGraphicsEffectCache.h"
#include "ZEGraphicsCamera.h"
#include "ZEGraphicsRenderList.h"
#include "ZEGraphicsInterfaceParameters.h"
/***********************************************************************************
ZoloEngine - Cloud
2/2012 - Corey Marquette
***********************************************************************************/
namespace zecloud
{
class Cloud
{
public:
/***********************************************************************************
Default Constructor - This constructor should not be used and will popup a
warning message if it is.
***********************************************************************************/
Cloud();
/***********************************************************************************
Contructor(...) - This constructor must be used to initialize the engine.
It initializes everything based on the data from a config file.
***********************************************************************************/
Cloud(HINSTANCE instance, std::string configFilepath);
/***********************************************************************************
Destructor - all cleanup is done here. releasees everything from textures to
renderLists.
***********************************************************************************/
~Cloud();
/***********************************************************************************
Update - calls update on all sub components, and returns false if the engine
has shutdown from any errors. If it has shutdown, the engine should not be used.
***********************************************************************************/
bool Update();
/***********************************************************************************
Render - processess all renderLists and present to the screen, use this
function after all drawcalls have been made for the frame.
***********************************************************************************/
bool Render();
/***********************************************************************************
These accessors are meant to be used to create resources for the engine to use
instead of doubling implementations in the interface.
***********************************************************************************/
ZEGraphics::TextureCache& TextureCache();
ZEGraphics::EffectCache& EffectCache();
/***********************************************************************************
CreateRenderList - adds a new renderList to the queue and gives a pointer back
to be used for rendering. ownership of the RL's still remains with the
engine interface. RenderList are never released until the engine is.
***********************************************************************************/
bool CreateRenderList(ZEGraphics::Sprite* renderTarget, bool clearTarget, ZEGraphics::RenderList*& renderList);
private:
ZESystem::Window window;
ZEInput::Interface input;
ZESystem::Timer frameTimer;
// Renderer specific data
ZEGraphics::Device device;
ZEGraphics::Camera camera;
ZEGraphics::TextureCache textureCache;
ZEGraphics::EffectCache effectCache;
std::vector renderLists;
LPDIRECT3DVERTEXDECLARATION9 vertexDecl;
};
};
#endif
simple, just the way I like it. Starting a new project is as easy as adding a couple directies and creating a winmain file like so.
#include
#include "ZECloud.h"
int WINAPI WinMain(HINSTANCE _instance, HINSTANCE _prevInstance, LPSTR _cmdLine, int _cmdShow)
{
/*********************************************************
Engine initialization
*********************************************************/
srand(timeGetTime());
zecloud::Cloud cloud(_instance, "data/config.xml");
/*********************************************************
GAME SPECIFIC ASSETS
*********************************************************/
/*********************************************************
GAME LOOP
*********************************************************/
while (cloud.Update())
{
cloud.Render();
}
return 0;
}
super simple... me really like. I have the tendency to be creating new projects constantly just to test out new ideas all the time. So having a fast setup proccess is very crucial to me.
The old interface took on a master type role... every sub component had a double set of functions in the interface that would call the actual functions in the sub components. Don't know if that would be considered "PIMPL" or not, eitherway it was cumbersome. Anytime I wanted to add new functionaliy, I basically had to do it twice. Then ownership started to get blurry, which brings up the truth behind font rendering and the particle system. because they are seperate from the render interface, and they should be. They required the interface to render anything. This meant any changes to how the DrawQuad function worked, would effect how those system worked aswell. Especially when I added diffuse coloring to allow quickly changing the fonts colors. I had to dig deep into the rendeerlist, then change things in the interface, then had to change thins in how textures were loaded... it was just a mess.
This time I've centralize, the act of drawcalls to renderList only. Similar to how XNA uses spriteBatch. The interface is no longer required to draw sprites to the screen. It is instead the point were resources are created, and manages the work flow of the system. It's still a major joint in the system, but should facilitate changes better.
The one hurdle I'm still contemplating, is the difference between translated and untranslated coordinates. My camera is run in software, so its simple enough to just skip it when process translated coordinates. ie for things like the UI or other screen aligned sprites. I'm not sure if I want to store a tranlated flag in the drawcall struct or make it global and keep it in the renderList class. The former would allow alot of flexibility, but require an if branch for every drawcall (upwards of ~150K per frame). Keeping it in the renderList would mean only one if branch per renderList, but also means more work for the user (me). Creating seperate renderList for translated and untranslated rendering. I'm leaning towards the later, since I kinda already do this... but not always. and could easily lead to renderlist with only 10-20 calls stored, but requirring that many state changes. Defeating the purpose of batching.
This will also problably mean more clutter in the draw function. I've been trying to minimize the require arguments over time, so I really didn't want to add more. Should I just create seperate function for translated and untranslated? Probably not.
RenderList.DrawQuad(position, size, sprite, shader, rotation, diffuse, translatedFlag)
not too bad I guess.
Another big change for me, is moving from only translated coordinates, to using world coordinates then letting the camera translate these. I've been comtemplating letting the engine do all the culling instead of doing it in game logic. Should this be done early at the point when drawQuad is used or later when the renderer starts processing the renderLists? I'm thinking early, but this can have problems. Take for example, we make some drawcalls that get rejected for being outside the camera, but before processing the renderLists, we move the camera and the old calls would be in view now. This would cause were popping, were objects aren't visable for a split second, then suddenly are. But would also keep the RenderLists at resonable sizes. They have always been a bottle neck in the pipeline, so anthing to make them a little quicker is beneficial... it's just the drawbacks are icky.
for the weirdos out there, heres the old interface for comparison and a look into my madness.
#ifndef _ZEGRAPHICS_INTERFACE_H
#define _ZEGRAPHICS_INTERFACE_H
#include
#include
#include
#include "ZEGraphicsInterfaceParameters.h"
#include "ZEGraphicsTextureCache.h"
#include "ZEGraphicsEffectCache.h"
#include "ZEGraphicsDevice.h"
#include "ZEGraphicsCamera.h"
#include "ZEGraphicsRenderList.h"
#include "ZEGraphicsSprite.h"
#include "ZEGraphicsColor.h"
#include "ZEVector3.h"
#include "ZEVector2.h"
namespace ZEGraphics
{
/** DEBUG information struct. */
struct DI_Interface
{
DI_Interface()
: drawPrimitiveCalls(0),
trianglesDrawn(0),
smallestBatch(0),
largestBatch(0) {
};
void Clear() {
drawPrimitiveCalls = 0;
trianglesDrawn = 0;
smallestBatch = 0;
largestBatch = 0;
};
DWORD drawPrimitiveCalls;
DWORD trianglesDrawn;
DWORD smallestBatch;
DWORD largestBatch;
};
/** Graphics API main Interface. */
class Interface
{
public:
ZEGraphics::Device device;
ZEGraphics::Camera* camera;
ZEGraphics::TextureCache* textureCache;
ZEGraphics::EffectCache* effectCache;
std::vector renderLists;
IDirect3DVertexBuffer9* vertexBuffer;
LPDIRECT3DVERTEXDECLARATION9 vertexDecl;
ZEGraphics::DI_Interface dInfo;
/** Data used by the deferred lighting pipeline. */
ZEGraphics::Sprite gbNormals;
ZEGraphics::Sprite gbPositions;
ZEGraphics::Sprite gbColors;
DWORD gbShader;
DWORD dirLightShader;
public:
ZEGraphics::InterfaceParameters parameters;
Interface() { };
~Interface() { this->Release(); };
int Create(HWND _windowHandle, ZEGraphics::InterfaceParameters& _paramters);
/** Display will process all the renderlists. Internally it will run these lists through the desired pipeline, specified by the renderList. */
void Display();
/** Specific pipeline for rendering the drawcalls. */
void DefaultPipeline(DWORD rl, IDirect3DTexture9* _texture, DWORD _shader, UINT _primitiveCount, bool _isTransparent);
/** Release... releases all the allocated resources. */
void Release();
/** Drawing functions for drawing textured primitives. */
void DrawQuad(DWORD _renderList, ZE::VECTOR3 _pos, ZE::VECTOR2 _size, ZEGraphics::Sprite* _sprite, DWORD _shader, ZE::VECTOR3 _rot, ZEGraphics::COLOR _diffuse);
void DrawQuad(DWORD _renderList, ZE::VECTOR3 _pos, ZE::VECTOR2 _size, ZEGraphics::Sprite* _sprite, DWORD _shader, float _alpha);
/** Texture loaders. */
ZEGraphics::Texture* CreateTexture(std::string _filename, D3DFORMAT _format, bool _transparent);
ZEGraphics::Texture* CreateRenderTarget(int _width, int _height, D3DFORMAT _format, bool _transparent);
/** Effect Loaders */
bool CreateEffect(std::string _file, DWORD& _index) {
if (effectCache == NULL)
return false;
return effectCache->CreateEffect(device.direct3DDevice, _file, _index);
};
/** Deferred Lighting interface. */
bool SetupDeferredLighting();
void BuildGBuffer(LPDIRECT3DTEXTURE9 _texture, UINT& _primitiveCount);
void ProcessLights();
/** The renderer stores pointers to a light source, so that it can be moved easly outside the renderer.
The renderer won't cull the lights so it's up to the user to remove lights that are no longer visible. */
void AddLight();
void RemoveLight();
void ClearLights();
/** Misc resource loaders. */
DWORD CreateRenderList(ZEGraphics::Sprite* _renderTarget, bool _clearTarget);
/** Resource access. */
ZEGraphics::EffectCache* EffectCachePTR() { return effectCache; };
/** Debug info. */
ZEGraphics::DI_Interface& DebugInfo() {
return dInfo;
};
void ClearDebugInfo() {
dInfo.Clear();
};
/** anchors return the screen position at the specified position. */
ZE::VECTOR3 anchorTopLeft();
ZE::VECTOR3 anchorTopCenter();
ZE::VECTOR3 anchorTopRight();
ZE::VECTOR3 anchorCenterLeft();
ZE::VECTOR3 anchorCenter();
ZE::VECTOR3 anchorCenterRight();
ZE::VECTOR3 anchorBottomLeft();
ZE::VECTOR3 anchorBottomCenter();
ZE::VECTOR3 anchorBottomRight();
};
};
#endif
Tomorrow I bother you again about the renderLists, the backbone to my renderer... its a yordle, run!